From 94f6443f3a56bd1b01bc52c8bfb26db0fa85437b Mon Sep 17 00:00:00 2001 From: geoprv Date: Fri, 28 Apr 2023 14:36:57 +0300 Subject: [PATCH 1/7] feat(core/async): added "flat" module WIP --- src/core/async/modules/base/index.ts | 8 + src/core/async/modules/flat/CHANGELOG.md | 16 ++ src/core/async/modules/flat/README.md | 0 src/core/async/modules/flat/helpers.ts | 64 +++++++ src/core/async/modules/flat/index.ts | 49 ++++++ src/core/async/modules/flat/interface.ts | 202 +++++++++++++++++++++++ src/core/async/modules/flat/spec.ts | 90 ++++++++++ 7 files changed, 429 insertions(+) create mode 100644 src/core/async/modules/flat/CHANGELOG.md create mode 100644 src/core/async/modules/flat/README.md create mode 100644 src/core/async/modules/flat/helpers.ts create mode 100644 src/core/async/modules/flat/index.ts create mode 100644 src/core/async/modules/flat/interface.ts create mode 100644 src/core/async/modules/flat/spec.ts diff --git a/src/core/async/modules/base/index.ts b/src/core/async/modules/base/index.ts index 62622f2ef..3029e2f41 100644 --- a/src/core/async/modules/base/index.ts +++ b/src/core/async/modules/base/index.ts @@ -31,10 +31,13 @@ import type { } from 'core/async/interface'; +import flatAsync from 'core/async/modules/flat'; + export * from 'core/async/modules/base/const'; export * from 'core/async/modules/base/helpers'; export * from 'core/async/interface'; +export * from 'core/async/modules/flat/interface'; export default class Async> { /** @@ -48,6 +51,11 @@ export default class Async> { */ static linkNames: NamespacesDictionary = namespaces; + /** + * {@link flatAsync} + */ + static flat: typeof flatAsync = flatAsync; + /** * The lock status. * If true, then all new tasks won't be registered. diff --git a/src/core/async/modules/flat/CHANGELOG.md b/src/core/async/modules/flat/CHANGELOG.md new file mode 100644 index 000000000..fcda6b45a --- /dev/null +++ b/src/core/async/modules/flat/CHANGELOG.md @@ -0,0 +1,16 @@ +Changelog +========= + +> **Tags:** +> - :boom: [Breaking Change] +> - :rocket: [New Feature] +> - :bug: [Bug Fix] +> - :memo: [Documentation] +> - :house: [Internal] +> - :nail_care: [Polish] + +## v3.96.2 (2023-05-??) + +#### :rocket: New Feature + +* Creating the module diff --git a/src/core/async/modules/flat/README.md b/src/core/async/modules/flat/README.md new file mode 100644 index 000000000..e69de29bb diff --git a/src/core/async/modules/flat/helpers.ts b/src/core/async/modules/flat/helpers.ts new file mode 100644 index 000000000..43fab4b8e --- /dev/null +++ b/src/core/async/modules/flat/helpers.ts @@ -0,0 +1,64 @@ +/*! + * V4Fire Core + * https://github.com/V4Fire/Core + * + * Released under the MIT license + * https://github.com/V4Fire/Core/blob/master/LICENSE + */ + +/** + * The function implements the logic of chaining promises flatly usign the `Proxy` object. + * It creates a chain of promises where each next promises takes a value from the previous one. + * + * @param getPromiseLike - the function that returns the previous promise in chain + */ +export function proxymify(getPromiseLike: (...args: unknown[]) => PromiseLike): unknown { + return new Proxy(getPromiseLike, { + get(_: unknown, prop: string): unknown { + const promiseLike = getPromiseLike(); + return handleNativePromise(promiseLike, prop) ?? proxymifyNextValue(promiseLike, prop); + }, + + apply(target: (...args: unknown[]) => PromiseLike, _: unknown, args: unknown[]): unknown { + return proxymifyNextValueFromFunctionCall(target, args); + } + }); +} + +/** + * + */ +function handleNativePromise(promiseLike: PromiseLike, prop: string | symbol): unknown { + if (!Object.hasOwnProperty(Promise.prototype, prop)) { + return; + } + + const value = promiseLike[prop]; + + if (Object.isFunction(value)) { + return value.bind(promiseLike); + } + + return value; +} + +/** + * + */ +function proxymifyNextValue(promiseLike: PromiseLike, prop: string | symbol): unknown { + return proxymify(async () => { + const data = await promiseLike; + const value = data[prop]; + return Object.isFunction(value) ? value.bind(data) : value; + }); +} + +/** + * + */ +function proxymifyNextValueFromFunctionCall(getFn: () => PromiseLike, args: unknown[]): unknown { + return proxymify(async () => { + const fn = await getFn(); + return fn(...args); + }); +} diff --git a/src/core/async/modules/flat/index.ts b/src/core/async/modules/flat/index.ts new file mode 100644 index 000000000..c658307cb --- /dev/null +++ b/src/core/async/modules/flat/index.ts @@ -0,0 +1,49 @@ +/*! + * V4Fire Core + * https://github.com/V4Fire/Core + * + * Released under the MIT license + * https://github.com/V4Fire/Core/blob/master/LICENSE + */ + +import type { Promisify } from 'core/async/modules/flat/interface'; +import { proxymify } from 'core/async/modules/flat/helpers'; + +export * from 'core/async/modules/flat/interface'; + +export function flatAsync(fn: T): Promisify; + +export function flatAsync(value: CanPromiseLike): Promisify; + +/** + * The function allows you to work flatly with promises using the `Proxy` object. + * + * The value you pass will be patched using the `Promisify` type in such a way that + * each of its members will be wrapped in a promise. However, you can still work with this value + * without worrying about the nested promises. + * + * @param value + * Can be any value or a function that returns any value. + * The final value will be wrapped in `Promise`. + * + * @example + * ```typescript + * function getData(): Promise[]> { + * return Promise.resolve([Promise.resolve(21)]); + * } + * + * // "21" + * const str = await flatAsync(getData)()[0].toFixed(1); + * ``` + */ +export default function flatAsync( + value: CanPromiseLike | AnyFunction +): Promisify { + if (Object.isFunction(value)) { + return Object.cast((...args: unknown[]) => proxymify( + () => Promise.resolve(value(...args)) + )); + } + + return Object.cast(proxymify(() => Promise.resolve(value))); +} diff --git a/src/core/async/modules/flat/interface.ts b/src/core/async/modules/flat/interface.ts new file mode 100644 index 000000000..6bc7b621e --- /dev/null +++ b/src/core/async/modules/flat/interface.ts @@ -0,0 +1,202 @@ +/*! + * V4Fire Core + * https://github.com/V4Fire/Core + * + * Released under the MIT license + * https://github.com/V4Fire/Core/blob/master/LICENSE + */ + +/* eslint-disable @typescript-eslint/ban-types */ + +type Overloads = T extends () => infer R + ? T extends (...args: infer A) => any + ? (...args: A) => Promisify + : () => Promisify + + : T extends { + (...args: infer A0): infer R0; + (...args: infer A1): infer R1; + (...args: infer A2): infer R2; + (...args: infer A3): infer R3; + (...args: infer A4): infer R4; + (...args: infer A5): infer R5; + (...args: infer A6): infer R6; + (...args: infer A7): infer R7; + (...args: infer A8): infer R8; + (...args: infer A9): infer R9; + } + ? { + (...args: A0): Promisify; + (...args: A1): Promisify; + (...args: A2): Promisify; + (...args: A3): Promisify; + (...args: A4): Promisify; + (...args: A5): Promisify; + (...args: A6): Promisify; + (...args: A7): Promisify; + (...args: A8): Promisify; + (...args: A9): Promisify; + } + + : T extends { + (...args: infer A0): infer R0; + (...args: infer A1): infer R1; + (...args: infer A2): infer R2; + (...args: infer A3): infer R3; + (...args: infer A4): infer R4; + (...args: infer A5): infer R5; + (...args: infer A6): infer R6; + (...args: infer A7): infer R7; + (...args: infer A8): infer R8; + } + ? { + (...args: A0): Promisify; + (...args: A1): Promisify; + (...args: A2): Promisify; + (...args: A3): Promisify; + (...args: A4): Promisify; + (...args: A5): Promisify; + (...args: A6): Promisify; + (...args: A7): Promisify; + (...args: A8): Promisify; + } + + : T extends { + (...args: infer A0): infer R0; + (...args: infer A1): infer R1; + (...args: infer A2): infer R2; + (...args: infer A3): infer R3; + (...args: infer A4): infer R4; + (...args: infer A5): infer R5; + (...args: infer A6): infer R6; + (...args: infer A7): infer R7; + } + ? { + (...args: A0): Promisify; + (...args: A1): Promisify; + (...args: A2): Promisify; + (...args: A3): Promisify; + (...args: A4): Promisify; + (...args: A5): Promisify; + (...args: A6): Promisify; + (...args: A7): Promisify; + } + + : T extends { + (...args: infer A0): infer R0; + (...args: infer A1): infer R1; + (...args: infer A2): infer R2; + (...args: infer A3): infer R3; + (...args: infer A4): infer R4; + (...args: infer A5): infer R5; + (...args: infer A6): infer R6; + } + ? { + (...args: A0): Promisify; + (...args: A1): Promisify; + (...args: A2): Promisify; + (...args: A3): Promisify; + (...args: A4): Promisify; + (...args: A5): Promisify; + (...args: A6): Promisify; + } + + : T extends { + (...args: infer A0): infer R0; + (...args: infer A1): infer R1; + (...args: infer A2): infer R2; + (...args: infer A3): infer R3; + (...args: infer A4): infer R4; + (...args: infer A5): infer R5; + } + ? { + (...args: A0): Promisify; + (...args: A1): Promisify; + (...args: A2): Promisify; + (...args: A3): Promisify; + (...args: A4): Promisify; + (...args: A5): Promisify; + } + + : T extends { + (...args: infer A0): infer R0; + (...args: infer A1): infer R1; + (...args: infer A2): infer R2; + (...args: infer A3): infer R3; + (...args: infer A4): infer R4; + } + ? { + (...args: A0): Promisify; + (...args: A1): Promisify; + (...args: A2): Promisify; + (...args: A3): Promisify; + (...args: A4): Promisify; + } + + : T extends { + (...args: infer A0): infer R0; + (...args: infer A1): infer R1; + (...args: infer A2): infer R2; + (...args: infer A3): infer R3; + } + ? { + (...args: A0): Promisify; + (...args: A1): Promisify; + (...args: A2): Promisify; + (...args: A3): Promisify; + } + + : T extends { + (...args: infer A0): infer R0; + (...args: infer A1): infer R1; + (...args: infer A2): infer R2; + } + ? { + (...args: A0): Promisify; + (...args: A1): Promisify; + (...args: A2): Promisify; + } + + : T extends { + (...args: infer A0): infer R0; + (...args: infer A1): infer R1; + } + ? { + (...args: A0): Promisify; + (...args: A1): Promisify; + } + + : T extends (...args: infer A) => infer R + ? (...args: A) => Promisify + + : never; + +type WithPromise = Wrapped & Promise; + +type GetSchema = Value extends string + ? String + : Value extends number + ? Number + : Value extends boolean + ? Boolean + : Value extends bigint + ? BigInt + : Value extends symbol + ? Symbol + : Value extends PromiseLike + ? GetSchema + : Value extends Array + ? WithPromise>, Item[]> + : Value; + +type PromisifySchema = Schema extends AnyFunction + ? Overloads + + : WithPromise< + { + [Key in keyof Schema]: Promisify; + }, + Origin + >; + +export type Promisify = PromisifySchema, Value>; diff --git a/src/core/async/modules/flat/spec.ts b/src/core/async/modules/flat/spec.ts new file mode 100644 index 000000000..bab009624 --- /dev/null +++ b/src/core/async/modules/flat/spec.ts @@ -0,0 +1,90 @@ +/*! + * V4Fire Core + * https://github.com/V4Fire/Core + * + * Released under the MIT license + * https://github.com/V4Fire/Core/blob/master/LICENSE + */ + +import Async from 'core/async'; +import flat from 'core/async/modules/flat'; + +type F = (() => G extends T ? 1 : 2); + +type AreEquals = + F extends F ? Y : N; + +// eslint-disable-next-line @typescript-eslint/no-empty-function +export function expectType>(): void {} + +describe.only('core/async/modules/flat', () => { + it('returns the last value from chain and infers its type', async () => { + const data = Promise.resolve({ + foo: { + bar: [(arg: number) => arg * 2] + } + }); + + const val = await flat(data) + .foo + .bar + .at(0)(21) + .toString() + .split(''); + + expect(val).toEqual(['4', '2']); + expectType(); + }); + + it.only('infers type of overloaded function', async () => { + function fn(arg: number): string; + function fn(arg: string): Promise; + function fn(arg: string | number): Promise | string { + return Object.isString(arg) ? Promise.resolve(Number(arg)) : String(arg); + } + + const + f = flat(fn), + s1 = await f(1).toUpperCase(), + s2 = await f('1').toFixed(1); + + expectType(); + expectType(); + + expect(s1).toBe('1'); + expect(s2).toBe('1.0'); + }); + + it('throws expection or rejects a promise if trying to access an undefined property', async () => { + const thenHandler = jest.fn(); + const promise = flat([1])[10] + .toString() + .then(thenHandler) + .catch((e) => Promise.reject(e.message)); + + await expect(promise).rejects.toBe("Cannot read properties of undefined (reading 'toString')"); + expect(thenHandler).not.toBeCalled(); + }); + + it('works with `Promise.all`', async () => { + const $a = new Async(); + const flatten = flat(async () => { + await $a.sleep(50); + + return { + foo: {bar: 'bar'}, + baz: async () => { + await $a.sleep(50); + return ['baz']; + } + }; + })(); + + const + {bar} = flatten.foo, + baz = flatten.baz()[0], + values = await Promise.all([bar, baz]); + + expect(values).toEqual(['bar', 'baz']); + }); +}); From 2a773127bc2403fe3754031e6d45ccf956a7daec Mon Sep 17 00:00:00 2001 From: geoprv Date: Tue, 2 May 2023 15:57:55 +0300 Subject: [PATCH 2/7] docs(core/async/modules/flat): added docs --- src/core/async/modules/base/index.ts | 5 +++ src/core/async/modules/flat/CHANGELOG.md | 2 +- src/core/async/modules/flat/README.md | 45 +++++++++++++++++++ src/core/async/modules/flat/helpers.ts | 19 +++++--- src/core/async/modules/flat/index.ts | 7 ++- .../modules/flat/interface/expect-type.ts | 17 +++++++ .../async/modules/flat/interface/index.ts | 1 + .../{interface.ts => interface/promisify.ts} | 19 +++++++- src/core/async/modules/flat/spec.ts | 17 +++---- 9 files changed, 108 insertions(+), 24 deletions(-) create mode 100644 src/core/async/modules/flat/interface/expect-type.ts create mode 100644 src/core/async/modules/flat/interface/index.ts rename src/core/async/modules/flat/{interface.ts => interface/promisify.ts} (89%) diff --git a/src/core/async/modules/base/index.ts b/src/core/async/modules/base/index.ts index 3029e2f41..b8aac7887 100644 --- a/src/core/async/modules/base/index.ts +++ b/src/core/async/modules/base/index.ts @@ -56,6 +56,11 @@ export default class Async> { */ static flat: typeof flatAsync = flatAsync; + /** + * {@link flatAsync} + */ + flat: typeof flatAsync = flatAsync; + /** * The lock status. * If true, then all new tasks won't be registered. diff --git a/src/core/async/modules/flat/CHANGELOG.md b/src/core/async/modules/flat/CHANGELOG.md index fcda6b45a..7b7e5662f 100644 --- a/src/core/async/modules/flat/CHANGELOG.md +++ b/src/core/async/modules/flat/CHANGELOG.md @@ -9,7 +9,7 @@ Changelog > - :house: [Internal] > - :nail_care: [Polish] -## v3.96.2 (2023-05-??) +## v3.??.?? (2023-??-??) #### :rocket: New Feature diff --git a/src/core/async/modules/flat/README.md b/src/core/async/modules/flat/README.md index e69de29bb..4f72cff3a 100644 --- a/src/core/async/modules/flat/README.md +++ b/src/core/async/modules/flat/README.md @@ -0,0 +1,45 @@ +# core/async/modules/flat + +This module provides a function for flatly working with a sequence of promises. +The function uses the `Proxy` object for creating a chain of promises where each next promise gets a value from the previous one. + +You can either specify a value that will be then wrapped in a `Promise` or a function which return value also will be wrapped in a `Promise`: + +```typescript +import flatAsync from 'core/async/modules/flat'; + +function getData(): Promise>> { + return Promise.resolve([Promise.resolve(21)]); +} + +// "21" +const str1 = await flatAsync(getData)().at(0)?.toFixed(); + +// "21" +const str2 = await flatAsync(getData())[0].toFixed(); +``` + +The function is also available as a static method of the `Async` class or as a method of its instance: + +```typescript +import Async from 'core/async'; + +Async.flat(promise); + +new Async().flat(promise); +``` + +The module provides a type `Promisify` for patching your entity fields: + +```typescript +import type { Promisify } from 'core/async/modules/flat'; + +const val: Promisify = Promise.resolve(21); + +val + .toString() + .split('') + .then() + .catch() + .finally(); +``` diff --git a/src/core/async/modules/flat/helpers.ts b/src/core/async/modules/flat/helpers.ts index 43fab4b8e..f7e06eb64 100644 --- a/src/core/async/modules/flat/helpers.ts +++ b/src/core/async/modules/flat/helpers.ts @@ -8,7 +8,7 @@ /** * The function implements the logic of chaining promises flatly usign the `Proxy` object. - * It creates a chain of promises where each next promises takes a value from the previous one. + * It creates a chain of promises where each next promise takes a value from the previous one. * * @param getPromiseLike - the function that returns the previous promise in chain */ @@ -26,7 +26,10 @@ export function proxymify(getPromiseLike: (...args: unknown[]) => PromiseLike } /** + * Checks if the passed prop is in the `Promise.prototype` and tries to get value by this prop. * + * @param promiseLike - previos `PromiseLike` + * @param prop - possible key from `Promise.prototype` */ function handleNativePromise(promiseLike: PromiseLike, prop: string | symbol): unknown { if (!Object.hasOwnProperty(Promise.prototype, prop)) { @@ -34,16 +37,14 @@ function handleNativePromise(promiseLike: PromiseLike, prop: string | symb } const value = promiseLike[prop]; - - if (Object.isFunction(value)) { - return value.bind(promiseLike); - } - - return value; + return Object.isFunction(value) ? value.bind(promiseLike) : value; } /** + * Creates next promise in chain that gets value from the previos one by accessing it using the specified prop. * + * @param promiseLike - previos `PromiseLike` + * @param prop - key to get next value */ function proxymifyNextValue(promiseLike: PromiseLike, prop: string | symbol): unknown { return proxymify(async () => { @@ -54,7 +55,11 @@ function proxymifyNextValue(promiseLike: PromiseLike, prop: string | } /** + * Creates next promise in chain that gets value from the previos one by calling the function. + * This function is called when we try to call a method on the previos proxied object. * + * @param getFn - the function that returns `PromiseLike` with currently calling function + * @param args - arguments for the function */ function proxymifyNextValueFromFunctionCall(getFn: () => PromiseLike, args: unknown[]): unknown { return proxymify(async () => { diff --git a/src/core/async/modules/flat/index.ts b/src/core/async/modules/flat/index.ts index c658307cb..0db59eb34 100644 --- a/src/core/async/modules/flat/index.ts +++ b/src/core/async/modules/flat/index.ts @@ -6,7 +6,7 @@ * https://github.com/V4Fire/Core/blob/master/LICENSE */ -import type { Promisify } from 'core/async/modules/flat/interface'; +import type { Promisify } from 'core/async/modules/flat/interface/promisify'; import { proxymify } from 'core/async/modules/flat/helpers'; export * from 'core/async/modules/flat/interface'; @@ -33,7 +33,10 @@ export function flatAsync(value: CanPromiseLike): Promisify; * } * * // "21" - * const str = await flatAsync(getData)()[0].toFixed(1); + * const str1 = await flatAsync(getData)()[0].toFixed(1); + + * // "21" + * const str2 = await flatAsync(getData())[0].toFixed(1); * ``` */ export default function flatAsync( diff --git a/src/core/async/modules/flat/interface/expect-type.ts b/src/core/async/modules/flat/interface/expect-type.ts new file mode 100644 index 000000000..b2493c974 --- /dev/null +++ b/src/core/async/modules/flat/interface/expect-type.ts @@ -0,0 +1,17 @@ +/*! + * V4Fire Core + * https://github.com/V4Fire/Core + * + * Released under the MIT license + * https://github.com/V4Fire/Core/blob/master/LICENSE + */ + +type Fn = (() => G extends T ? 1 : 0); + +type AreEquals = Fn extends Fn ? unknown : never; + +/** + * Util for checking two types equality + */ +// eslint-disable-next-line @typescript-eslint/no-empty-function +export function expectType>(): void {} diff --git a/src/core/async/modules/flat/interface/index.ts b/src/core/async/modules/flat/interface/index.ts new file mode 100644 index 000000000..2fccaeeec --- /dev/null +++ b/src/core/async/modules/flat/interface/index.ts @@ -0,0 +1 @@ +export * from 'core/async/modules/flat/interface/promisify'; diff --git a/src/core/async/modules/flat/interface.ts b/src/core/async/modules/flat/interface/promisify.ts similarity index 89% rename from src/core/async/modules/flat/interface.ts rename to src/core/async/modules/flat/interface/promisify.ts index 6bc7b621e..4cffa0bd6 100644 --- a/src/core/async/modules/flat/interface.ts +++ b/src/core/async/modules/flat/interface/promisify.ts @@ -8,6 +8,9 @@ /* eslint-disable @typescript-eslint/ban-types */ +/** + * Handles function overloads promisifying the return type + */ type Overloads = T extends () => infer R ? T extends (...args: infer A) => any ? (...args: A) => Promisify @@ -171,8 +174,14 @@ type Overloads = T extends () => infer R : never; +/** + * Adds `Promise` properties to the specified value + */ type WithPromise = Wrapped & Promise; +/** + * Maps primitive values to their object representation and "unwraps" `PromiseLike` objects + */ type GetSchema = Value extends string ? String : Value extends number @@ -185,10 +194,12 @@ type GetSchema = Value extends string ? Symbol : Value extends PromiseLike ? GetSchema - : Value extends Array - ? WithPromise>, Item[]> : Value; +/** + * Promisifies members of the specified schema by creating an object with the promisified properties + * or promisifying return type of the function overloads + */ type PromisifySchema = Schema extends AnyFunction ? Overloads @@ -199,4 +210,8 @@ type PromisifySchema = Schema extends AnyFunction Origin >; +/** + * Patches all members of the specified value in such a way that + * each of them will be wrapped in a promise but at the same time preserving its own properties + */ export type Promisify = PromisifySchema, Value>; diff --git a/src/core/async/modules/flat/spec.ts b/src/core/async/modules/flat/spec.ts index bab009624..faec3ec0d 100644 --- a/src/core/async/modules/flat/spec.ts +++ b/src/core/async/modules/flat/spec.ts @@ -8,16 +8,9 @@ import Async from 'core/async'; import flat from 'core/async/modules/flat'; +import { expectType } from 'core/async/modules/flat/interface/expect-type'; -type F = (() => G extends T ? 1 : 2); - -type AreEquals = - F extends F ? Y : N; - -// eslint-disable-next-line @typescript-eslint/no-empty-function -export function expectType>(): void {} - -describe.only('core/async/modules/flat', () => { +describe('core/async/modules/flat', () => { it('returns the last value from chain and infers its type', async () => { const data = Promise.resolve({ foo: { @@ -28,15 +21,15 @@ describe.only('core/async/modules/flat', () => { const val = await flat(data) .foo .bar - .at(0)(21) + .at(0)?.(21) .toString() .split(''); expect(val).toEqual(['4', '2']); - expectType(); + expectType, typeof val>(); }); - it.only('infers type of overloaded function', async () => { + it('infers type of overloaded function', async () => { function fn(arg: number): string; function fn(arg: string): Promise; function fn(arg: string | number): Promise | string { From 8bed3cfcb8f525da42ef6320de00dd2d354aaeee Mon Sep 17 00:00:00 2001 From: geoprv Date: Tue, 2 May 2023 17:39:19 +0300 Subject: [PATCH 3/7] docs: improved docs --- src/core/async/modules/flat/helpers.ts | 50 +++++++++++++------------- src/core/async/modules/flat/spec.ts | 1 + 2 files changed, 26 insertions(+), 25 deletions(-) diff --git a/src/core/async/modules/flat/helpers.ts b/src/core/async/modules/flat/helpers.ts index f7e06eb64..2525bbbe8 100644 --- a/src/core/async/modules/flat/helpers.ts +++ b/src/core/async/modules/flat/helpers.ts @@ -7,20 +7,20 @@ */ /** - * The function implements the logic of chaining promises flatly usign the `Proxy` object. + * The function implements the logic of chaining promises flatly using the `Proxy` object. * It creates a chain of promises where each next promise takes a value from the previous one. * - * @param getPromiseLike - the function that returns the previous promise in chain + * @param getPrevPromiseLike - the function that returns the previous promise in chain */ -export function proxymify(getPromiseLike: (...args: unknown[]) => PromiseLike): unknown { - return new Proxy(getPromiseLike, { - get(_: unknown, prop: string): unknown { - const promiseLike = getPromiseLike(); - return handleNativePromise(promiseLike, prop) ?? proxymifyNextValue(promiseLike, prop); +export function proxymify(getPrevPromiseLike: (...args: unknown[]) => PromiseLike): unknown { + return new Proxy(getPrevPromiseLike, { + get(_: unknown, nextProp: string): unknown { + const prevPromiseLike = getPrevPromiseLike(); + return handleNativePromise(prevPromiseLike, nextProp) ?? proxymifyNextValue(prevPromiseLike, nextProp); }, apply(target: (...args: unknown[]) => PromiseLike, _: unknown, args: unknown[]): unknown { - return proxymifyNextValueFromFunctionCall(target, args); + return proxymifyNextValueFromMethodCall(target, args); } }); } @@ -28,28 +28,28 @@ export function proxymify(getPromiseLike: (...args: unknown[]) => PromiseLike /** * Checks if the passed prop is in the `Promise.prototype` and tries to get value by this prop. * - * @param promiseLike - previos `PromiseLike` - * @param prop - possible key from `Promise.prototype` + * @param prevPromiseLike - previos `PromiseLike` + * @param nextProp - possible key from `Promise.prototype` */ -function handleNativePromise(promiseLike: PromiseLike, prop: string | symbol): unknown { - if (!Object.hasOwnProperty(Promise.prototype, prop)) { +function handleNativePromise(prevPromiseLike: PromiseLike, nextProp: string | symbol): unknown { + if (!Object.hasOwnProperty(Promise.prototype, nextProp)) { return; } - const value = promiseLike[prop]; - return Object.isFunction(value) ? value.bind(promiseLike) : value; + const value = prevPromiseLike[nextProp]; + return Object.isFunction(value) ? value.bind(prevPromiseLike) : value; } /** - * Creates next promise in chain that gets value from the previos one by accessing it using the specified prop. + * Creates next promise in chain that gets a value from the previos one by accessing it using the specified prop. * - * @param promiseLike - previos `PromiseLike` - * @param prop - key to get next value + * @param prevPromiseLike - previous `PromiseLike` + * @param nextProp - key to get next value */ -function proxymifyNextValue(promiseLike: PromiseLike, prop: string | symbol): unknown { +function proxymifyNextValue(prevPromiseLike: PromiseLike, nextProp: string | symbol): unknown { return proxymify(async () => { - const data = await promiseLike; - const value = data[prop]; + const data = await prevPromiseLike; + const value = data[nextProp]; return Object.isFunction(value) ? value.bind(data) : value; }); } @@ -58,12 +58,12 @@ function proxymifyNextValue(promiseLike: PromiseLike, prop: string | * Creates next promise in chain that gets value from the previos one by calling the function. * This function is called when we try to call a method on the previos proxied object. * - * @param getFn - the function that returns `PromiseLike` with currently calling function - * @param args - arguments for the function + * @param getPrevMethod - the function that returns `PromiseLike` with currently calling method + * @param args - arguments for the method */ -function proxymifyNextValueFromFunctionCall(getFn: () => PromiseLike, args: unknown[]): unknown { +function proxymifyNextValueFromMethodCall(getPrevMethod: () => PromiseLike, args: unknown[]): unknown { return proxymify(async () => { - const fn = await getFn(); - return fn(...args); + const method = await getPrevMethod(); + return method(...args); }); } diff --git a/src/core/async/modules/flat/spec.ts b/src/core/async/modules/flat/spec.ts index faec3ec0d..761a57364 100644 --- a/src/core/async/modules/flat/spec.ts +++ b/src/core/async/modules/flat/spec.ts @@ -79,5 +79,6 @@ describe('core/async/modules/flat', () => { values = await Promise.all([bar, baz]); expect(values).toEqual(['bar', 'baz']); + expectType<[string, string], typeof values>(); }); }); From 0dd19ec30a8e8c20ef753cfe96b7c2a3f5b7667f Mon Sep 17 00:00:00 2001 From: geoprv Date: Tue, 2 May 2023 17:42:18 +0300 Subject: [PATCH 4/7] docs: improved docs --- src/core/async/modules/flat/index.ts | 2 +- src/core/async/modules/flat/interface/promisify.ts | 4 ++-- src/core/async/modules/flat/spec.ts | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/core/async/modules/flat/index.ts b/src/core/async/modules/flat/index.ts index 0db59eb34..430147e80 100644 --- a/src/core/async/modules/flat/index.ts +++ b/src/core/async/modules/flat/index.ts @@ -24,7 +24,7 @@ export function flatAsync(value: CanPromiseLike): Promisify; * * @param value * Can be any value or a function that returns any value. - * The final value will be wrapped in `Promise`. + * The final value will be wrapped in the `Promise`. * * @example * ```typescript diff --git a/src/core/async/modules/flat/interface/promisify.ts b/src/core/async/modules/flat/interface/promisify.ts index 4cffa0bd6..f23eac91c 100644 --- a/src/core/async/modules/flat/interface/promisify.ts +++ b/src/core/async/modules/flat/interface/promisify.ts @@ -9,7 +9,7 @@ /* eslint-disable @typescript-eslint/ban-types */ /** - * Handles function overloads promisifying the return type + * Promisifies each function overload return type */ type Overloads = T extends () => infer R ? T extends (...args: infer A) => any @@ -198,7 +198,7 @@ type GetSchema = Value extends string /** * Promisifies members of the specified schema by creating an object with the promisified properties - * or promisifying return type of the function overloads + * or promisifying return type of each function overload */ type PromisifySchema = Schema extends AnyFunction ? Overloads diff --git a/src/core/async/modules/flat/spec.ts b/src/core/async/modules/flat/spec.ts index 761a57364..22f1dc440 100644 --- a/src/core/async/modules/flat/spec.ts +++ b/src/core/async/modules/flat/spec.ts @@ -29,7 +29,7 @@ describe('core/async/modules/flat', () => { expectType, typeof val>(); }); - it('infers type of overloaded function', async () => { + it('infers the type of the overloaded function', async () => { function fn(arg: number): string; function fn(arg: string): Promise; function fn(arg: string | number): Promise | string { @@ -48,7 +48,7 @@ describe('core/async/modules/flat', () => { expect(s2).toBe('1.0'); }); - it('throws expection or rejects a promise if trying to access an undefined property', async () => { + it('throws an expection or rejects a promise if trying to access an undefined property', async () => { const thenHandler = jest.fn(); const promise = flat([1])[10] .toString() From 2ee379eb542822137c74c457ff38654685ce9fdc Mon Sep 17 00:00:00 2001 From: geoprv Date: Tue, 2 May 2023 17:44:59 +0300 Subject: [PATCH 5/7] docs: improved docs --- src/core/async/modules/flat/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/async/modules/flat/README.md b/src/core/async/modules/flat/README.md index 4f72cff3a..aa8c203f2 100644 --- a/src/core/async/modules/flat/README.md +++ b/src/core/async/modules/flat/README.md @@ -29,7 +29,7 @@ Async.flat(promise); new Async().flat(promise); ``` -The module provides a type `Promisify` for patching your entity fields: +The module also provides the `Promisify` type for patching your entity fields: ```typescript import type { Promisify } from 'core/async/modules/flat'; From 51e369aba50ab9a4c3e54b76f8bd94f87352508d Mon Sep 17 00:00:00 2001 From: geopr Date: Tue, 2 May 2023 17:52:31 +0300 Subject: [PATCH 6/7] docs: improved docs --- src/core/async/modules/flat/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/async/modules/flat/README.md b/src/core/async/modules/flat/README.md index aa8c203f2..824239c41 100644 --- a/src/core/async/modules/flat/README.md +++ b/src/core/async/modules/flat/README.md @@ -29,7 +29,7 @@ Async.flat(promise); new Async().flat(promise); ``` -The module also provides the `Promisify` type for patching your entity fields: +The module also provides the `Promisify` type for patching your value: ```typescript import type { Promisify } from 'core/async/modules/flat'; From 91e703e15c1146bca4c5b893a30c99126956d698 Mon Sep 17 00:00:00 2001 From: geopr Date: Tue, 2 May 2023 17:57:33 +0300 Subject: [PATCH 7/7] docs: typos --- src/core/async/modules/flat/helpers.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/core/async/modules/flat/helpers.ts b/src/core/async/modules/flat/helpers.ts index 2525bbbe8..e7722d8cd 100644 --- a/src/core/async/modules/flat/helpers.ts +++ b/src/core/async/modules/flat/helpers.ts @@ -10,7 +10,7 @@ * The function implements the logic of chaining promises flatly using the `Proxy` object. * It creates a chain of promises where each next promise takes a value from the previous one. * - * @param getPrevPromiseLike - the function that returns the previous promise in chain + * @param getPrevPromiseLike - the function that returns the previous `PromiseLike` in chain */ export function proxymify(getPrevPromiseLike: (...args: unknown[]) => PromiseLike): unknown { return new Proxy(getPrevPromiseLike, { @@ -28,7 +28,7 @@ export function proxymify(getPrevPromiseLike: (...args: unknown[]) => Promise /** * Checks if the passed prop is in the `Promise.prototype` and tries to get value by this prop. * - * @param prevPromiseLike - previos `PromiseLike` + * @param prevPromiseLike - previous `PromiseLike` * @param nextProp - possible key from `Promise.prototype` */ function handleNativePromise(prevPromiseLike: PromiseLike, nextProp: string | symbol): unknown { @@ -41,7 +41,7 @@ function handleNativePromise(prevPromiseLike: PromiseLike, nextProp: strin } /** - * Creates next promise in chain that gets a value from the previos one by accessing it using the specified prop. + * Creates next promise in chain that gets a value from the previous one by accessing it using the specified prop. * * @param prevPromiseLike - previous `PromiseLike` * @param nextProp - key to get next value @@ -55,15 +55,15 @@ function proxymifyNextValue(prevPromiseLike: PromiseLike, nextProp: } /** - * Creates next promise in chain that gets value from the previos one by calling the function. - * This function is called when we try to call a method on the previos proxied object. + * Creates next promise in chain that gets value from the previous one by calling the function. + * This function is called when we try to call a method on the previous proxied object. * - * @param getPrevMethod - the function that returns `PromiseLike` with currently calling method + * @param getMethod - the function that returns `PromiseLike` with currently calling method * @param args - arguments for the method */ -function proxymifyNextValueFromMethodCall(getPrevMethod: () => PromiseLike, args: unknown[]): unknown { +function proxymifyNextValueFromMethodCall(getMethod: () => PromiseLike, args: unknown[]): unknown { return proxymify(async () => { - const method = await getPrevMethod(); + const method = await getMethod(); return method(...args); }); }