Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 28 additions & 11 deletions docs/deps.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,9 +85,9 @@ const block = ( { generateId } ) => {
Иногда нам просто достаточно, чтобы один блок выполнился после окончания работы другого блока.
Но чаще всего, нам нужно результат одного блока использовать для запуска другого блока.

```js
```ts
const block = ( { generateId } ) => {
const fooId = generateId();
const fooId = generateId(blockFoo);

return de.object( {
block: {
Expand All @@ -101,13 +101,10 @@ const block = ( { generateId } ) => {
options: {
deps: fooId,

params: ( { deps } ) => {
// В deps приходят результаты работы всех блоков,
// от которых зависит блок.
//
// Достаем результат blockFoo.
//
const fooResult = deps[ fooId ];
params: ( { dep } ) => {
// dep — типобезопасный accessor для получения результатов всех блоков, от которых зависит блок.
// Достаем результат blockFoo c сохранением типов.
const fooResult = dep( fooId );

// Используем значение из fooResult в качестве
// параметра запроса блока blockBar.
Expand All @@ -124,8 +121,10 @@ const block = ( { generateId } ) => {
};
```

Если у блока были зависимости, то во все "хуки" (`options.params`, `options.before`, ...) в поле `deps`
будет приходить объект с результатами этих зависимостей.
Если у блока были зависимости, то во все "хуки" (`options.params`, `options.before`, ...) в аргументах приходят:

- `deps` — объект `Record<symbol, any>` с результатами всех зависимостей
- `dep` — типобезопасный accessor: `dep(id)` эквивалентен `deps[id]`, но сохраняет тип конкретной зависимости


## `generateId()`
Expand Down Expand Up @@ -154,6 +153,24 @@ id-шники, сгенерированные при помощи `generateId`,
И при попытке использовать какое-либо другое значение в качестве id, блок завершится с ошибкой `de.ERROR_ID.INVALID_DEPS_ID`.


### Типизированный `generateId`

`generateId` поддерживает несколько перегрузок:

```ts
// Нетипизированный id (UntypedId) — dep(id) вернет unknown
const id = generateId();
const id = generateId('label');

// Типизированный id с явным типом — dep(id) вернет MyType
const id = generateId<MyType>();

// Типизированный id, тип выведен из блока — dep(id) вернет тип результата blockFoo
const id = generateId(blockFoo);
```



## Ошибки `de.ERROR_ID.DEPS_ERROR` и `de.ERROR_ID.DEPS_NOT_RESOLVED`

Обе ошибки связаны с проблема при разрешении зависимостей, но случаются в немного разных ситуациях:
Expand Down
22 changes: 14 additions & 8 deletions lib/block.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { createError, ERROR_ID } from './error';

import type { DescriptBlockDeps, DescriptBlockId } from './depsDomain';
import DepsDomain from './depsDomain';
import type { DescriptBlockDeps, DescriptBlockId, UntypedId } from './depsDomain';
import DepsDomain, { createDepAccessor } from './depsDomain';
import type Cancel from './cancel';
import type ContextClass from './context';
import type { BlockResultOut, InferResultOrResult, DescriptBlockOptions, DepsIds } from './types';
Expand Down Expand Up @@ -42,6 +42,8 @@
ErrorResultOut = unknown,
Params = ParamsOut,
> {
declare readonly __resultType: InferResultOrResult<ResultOut>;

protected block: CustomBlock;
protected options: BlockOptions<Context, ParamsOut, BlockResult, BeforeResultOut, AfterResultOut, ErrorResultOut, Params>;

Expand Down Expand Up @@ -393,6 +395,8 @@
let resultAfter: AfterResultOut | undefined = undefined;
let errorResult: ErrorResultOut | undefined = undefined;

const dep = createDepAccessor(deps);

try {

if (step.params) {
Expand All @@ -401,14 +405,14 @@
}

// Тут не нужен cancel.
params = step.params({ params: params as unknown as Params, context, deps });
params = step.params({ params: params as unknown as Params, context, deps, dep });
if (!(params && typeof params === 'object')) {
throw createError('Result of options.params must be an object', ERROR_ID.INVALID_OPTIONS_PARAMS);
}
}

if (typeof step.before === 'function') {
resultBefore = await step.before({ cancel, params, context, deps });
resultBefore = await step.before({ cancel, params, context, deps, dep });
blockCancel.throwIfCancelled();

if (resultBefore instanceof BaseBlock) {
Expand Down Expand Up @@ -438,7 +442,7 @@
blockCancel.throwIfCancelled();

if (typeof step.after === 'function') {
resultAfter = await step.after({ cancel, params, context, deps, result: (resultBefore || resultBlock) as any });
resultAfter = await step.after({ cancel, params, context, deps, dep, result: (resultBefore || resultBlock) as any });

Check warning on line 445 in lib/block.ts

View workflow job for this annotation

GitHub Actions / build (20.x)

Unexpected any. Specify a different type
blockCancel.throwIfCancelled();

if (resultAfter instanceof BaseBlock) {
Expand All @@ -461,7 +465,7 @@
// FIXME: А нужно ли уметь options.error делать асинхронным?
//
if (typeof step.error === 'function') {
errorResult = step.error({ cancel, params, context, deps, error });
errorResult = step.error({ cancel, params, context, deps, dep, error });
} else {
throw error;
}
Expand Down Expand Up @@ -536,9 +540,11 @@
let key;
const optionsKey = this.options.key;

const dep = createDepAccessor(deps);

if (cache && optionsKey) {
// Тут не нужен cancel.
key = (typeof optionsKey === 'function') ? optionsKey({ params, context, deps }) : optionsKey;
key = (typeof optionsKey === 'function') ? optionsKey({ params, context, deps, dep }) : optionsKey;
if (typeof key !== 'string') {
key = null;
}
Expand Down Expand Up @@ -595,7 +601,7 @@

// --------------------------------------------------------------------------------------------------------------- //

function extendDeps(deps?: DescriptBlockId | DepsIds | null): DepsIds | null {
function extendDeps(deps?: DescriptBlockId | UntypedId | DepsIds | null): DepsIds | null {
if (!deps) {
return null;
}
Expand Down
11 changes: 5 additions & 6 deletions lib/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import BlockClass from './block';

import type { Deffered } from './getDeferred';
import getDeferred from './getDeferred';
import type { DescriptBlockId } from './depsDomain';
import type Cancel from './cancel';
import type DepsDomain from './depsDomain';
import type { BlockResultOut, InferResultOrResult } from './types';
Expand Down Expand Up @@ -34,8 +33,8 @@ class RunContext<
private nBlocks = 0;
private nActiveBlocks = 0;

private blockPromises: Record<DescriptBlockId, Deffered<ResultOut, DescriptError>> = {};
private blockResults: Record<DescriptBlockId, StoredResult<ResultOut, DescriptError>> = {};
private blockPromises: Record<symbol, Deffered<ResultOut, DescriptError>> = {};
private blockResults: Record<symbol, StoredResult<ResultOut, DescriptError>> = {};

private waitingForDeps: Array<Dependency> = [];

Expand Down Expand Up @@ -75,7 +74,7 @@ class RunContext<
return this.nActiveBlocks--;
}

getPromise(id: DescriptBlockId) {
getPromise(id: symbol) {
let deferred = this.blockPromises[ id ];
if (!deferred) {
const result = this.blockResults[ id ];
Expand All @@ -95,7 +94,7 @@ class RunContext<
return deferred.promise;
}

resolvePromise(id: DescriptBlockId, result: ResultOut) {
resolvePromise(id: symbol, result: ResultOut) {
this.blockResults[ id ] = {
result: result,
};
Expand All @@ -107,7 +106,7 @@ class RunContext<
}
}

rejectPromise(id: DescriptBlockId, error: DescriptError) {
rejectPromise(id: symbol, error: DescriptError) {
this.blockResults[ id ] = {
error: error,
};
Expand Down
55 changes: 44 additions & 11 deletions lib/depsDomain.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,58 @@
// TODO как это типизировать any этот?
export type DescriptBlockDeps = Record<DescriptBlockId, any>;
export type DescriptBlockId = symbol;
export type GenerateId = (label?: string) => DescriptBlockId;
declare const __covariant: unique symbol;
declare const __contravariant: unique symbol;
declare const __untyped: unique symbol;

export type DescriptBlockId<T = unknown> = symbol & {
readonly [__covariant]: T;
[__contravariant]: (_: T) => void;
};

export type UntypedId = symbol & { readonly [__untyped]: true };

type BlockResultOf<B> = B extends { readonly __resultType: infer R } ? R : unknown;

export type GenerateId = {
<B extends { readonly __resultType: any }>(block: B): DescriptBlockId<BlockResultOf<B>>;

Check warning on line 15 in lib/depsDomain.ts

View workflow job for this annotation

GitHub Actions / build (20.x)

Unexpected any. Specify a different type
(label?: string): UntypedId;
<T>(label?: string): DescriptBlockId<T>;
};

export type DescriptBlockDeps = Record<symbol, any>;

Check warning on line 20 in lib/depsDomain.ts

View workflow job for this annotation

GitHub Actions / build (20.x)

Unexpected any. Specify a different type

export function dep<T>(deps: DescriptBlockDeps, id: DescriptBlockId<T>): T;
export function dep(deps: DescriptBlockDeps, id: UntypedId): unknown;
export function dep(deps: DescriptBlockDeps, id: symbol): unknown {
return deps[ id ];
}

export type DepAccessor = {
<T>(id: DescriptBlockId<T>): T;
(id: UntypedId): unknown;
};

export function createDepAccessor(deps: DescriptBlockDeps): DepAccessor {
return ((id: symbol) => deps[ id ]) as DepAccessor;
}

class DepsDomain {
ids: Record<DescriptBlockId, boolean>;
ids: Record<symbol, boolean>;

constructor(parent: any) {

Check warning on line 40 in lib/depsDomain.ts

View workflow job for this annotation

GitHub Actions / build (20.x)

Unexpected any. Specify a different type
this.ids = (parent instanceof DepsDomain) ? Object.create(parent.ids) : {};

}

generateId: GenerateId = (label?: string): DescriptBlockId => {
const id = Symbol(label);
// Runtime ignores the block argument (used only for type inference).
// Symbol label is taken from a string arg or left undefined.
generateId: GenerateId = ((blockOrLabel?: any): DescriptBlockId<any> => {

Check warning on line 46 in lib/depsDomain.ts

View workflow job for this annotation

GitHub Actions / build (20.x)

Unexpected any. Specify a different type
const label = typeof blockOrLabel === 'string' ? blockOrLabel : undefined;
const id = Symbol(label) as DescriptBlockId<any>;
this.ids[ id ] = true;
return id;
};
}) as GenerateId;

isValidId(id: DescriptBlockId) {
isValidId(id: symbol): boolean {
return Boolean(this.ids[ id ]);
}

}

export default DepsDomain;
6 changes: 4 additions & 2 deletions lib/functionBlock.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import BaseBlock from './block';
import type { DescriptBlockDeps } from './depsDomain';
import DepsDomain from './depsDomain';
import type { DepAccessor, DescriptBlockDeps } from './depsDomain';
import DepsDomain, { createDepAccessor } from './depsDomain';
import { createError, ERROR_ID } from './error';
import type { BlockResultOut, DescriptBlockOptions, InferParamsOutFromBlock } from './types';
import type ContextClass from './context';
Expand All @@ -14,6 +14,7 @@ export type FunctionBlockDefinition<
params: Params;
context: Context;
deps: DescriptBlockDeps;
dep: DepAccessor;
generateId: DepsDomain['generateId'];
cancel: Cancel;
blockCancel: Cancel;
Expand Down Expand Up @@ -72,6 +73,7 @@ class FunctionBlock<
params: params,
context: context,
deps: deps,
dep: createDepAccessor(deps),
generateId: depsDomain.generateId,
}),
blockCancel.getPromise(),
Expand Down
2 changes: 2 additions & 0 deletions lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import type { LoggerEvent, LoggerInterface } from './logger';
import Cache, { CacheInterface } from './cache';

import request, { type RequestOptions, type GetRetryStrategyParams } from './request';
import { dep } from './depsDomain';
import type { GenerateId, DescriptBlockDeps, DescriptBlockId } from './depsDomain';
import Block from './block';
import ArrayBlock from './arrayBlock';
Expand Down Expand Up @@ -205,6 +206,7 @@ export {
DescriptHttpBlockQueryValue,
RetryStrategyInterface,
GetRetryStrategyParams,
dep,
GenerateId,
DescriptBlockId,
InferResultFromBlock,
Expand Down
13 changes: 9 additions & 4 deletions lib/types.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type Cancel from './cancel';
import type BaseBlock from './block';
import type { DescriptBlockDeps, DescriptBlockId } from './depsDomain';
import type { DepAccessor, DescriptBlockDeps, DescriptBlockId, UntypedId } from './depsDomain';
import type { DescriptError } from './error';
import type { CacheInterface } from './cache';
import type { IncomingHttpHeaders } from 'http';
Expand Down Expand Up @@ -142,7 +142,7 @@ export type Tail<T> =

export type Equal<A, B> = A extends B ? (B extends A ? A : never) : never;

export type DepsIds = Array<DescriptBlockId>;
export type DepsIds = Array<DescriptBlockId<any> | UntypedId>;
export interface DescriptBlockOptions<
Context,
ParamsOut,
Expand All @@ -154,26 +154,29 @@ export interface DescriptBlockOptions<
> {
name?: string;

id?: DescriptBlockId;
deps?: DescriptBlockId | DepsIds | null;
id?: NoInfer<DescriptBlockId<InferResultOrResult<BlockResultOut<BlockResult, BeforeResultOut, AfterResultOut, ErrorResultOut>>>> | UntypedId;
deps?: DescriptBlockId<any> | UntypedId | DepsIds | null;

params?: (args: {
params: Params;
context?: Context;
deps: DescriptBlockDeps;
dep: DepAccessor;
}) => ParamsOut;

before?: (args: {
params: ParamsOut;
context?: Context;
deps: DescriptBlockDeps;
dep: DepAccessor;
cancel: Cancel;
}) => BeforeResultOut;

after?: (args: {
params: ParamsOut;
context?: Context;
deps: DescriptBlockDeps;
dep: DepAccessor;
cancel: Cancel;
result: [ unknown ] extends [ Exclude<BeforeResultOut, undefined | void> ] ?
InferResultOrResult<BlockResult> : InferResultOrResult<Exclude<BeforeResultOut, undefined | void>> | InferResultOrResult<BlockResult>;
Expand All @@ -183,6 +186,7 @@ export interface DescriptBlockOptions<
params: ParamsOut;
context?: Context;
deps: DescriptBlockDeps;
dep: DepAccessor;
cancel: Cancel;
error: DescriptError;
}) => ErrorResultOut;
Expand All @@ -193,6 +197,7 @@ export interface DescriptBlockOptions<
params: ParamsOut;
context?: Context;
deps: DescriptBlockDeps;
dep: DepAccessor;
}) => string);
maxage?: number;
cache?: CacheInterface<BlockResult> | ((args: { params: ParamsOut }) => Promise<CacheInterface<BlockResult>>);
Expand Down
4 changes: 2 additions & 2 deletions tests/httpBlock.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import type { ServerResponse, ClientRequest } from 'node:http';
import strip_null_and_undefined_values from '../lib/stripNullAndUndefinedValues';
import type { DescriptBlockOptions, DescriptHttpBlockResult } from '../lib/types';
import type { DescriptHttpBlockDescription } from '../lib/httpBlock';
import type { DescriptBlockId } from '../lib/depsDomain';
import type { UntypedId } from '../lib/depsDomain';
// --------------------------------------------------------------------------------------------------------------- //

describe('http', <
Expand Down Expand Up @@ -115,7 +115,7 @@ describe('http', <
const spy = vi.fn((...args: any) => value);

let fooResult;
let id: DescriptBlockId;
let id!: UntypedId;
const block = de.func({
block: ({ generateId }) => {
id = generateId();
Expand Down
Loading
Loading