Skip to content
Open
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
16 changes: 11 additions & 5 deletions library/src/actions/check/checkAsync.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,10 @@ export interface CheckActionAsync<
/**
* The validation function.
*/
readonly requirement: (input: TInput) => MaybePromise<boolean>;
readonly requirement: (
input: TInput,
signal?: AbortSignal
) => MaybePromise<boolean>;
/**
* The error message.
*/
Expand All @@ -43,7 +46,7 @@ export interface CheckActionAsync<
* @returns A check action.
*/
export function checkAsync<TInput>(
requirement: (input: TInput) => MaybePromise<boolean>
requirement: (input: TInput, signal?: AbortSignal) => MaybePromise<boolean>
): CheckActionAsync<TInput, undefined>;

/**
Expand All @@ -58,13 +61,13 @@ export function checkAsync<
TInput,
const TMessage extends ErrorMessage<CheckIssue<TInput>> | undefined,
>(
requirement: (input: TInput) => MaybePromise<boolean>,
requirement: (input: TInput, signal?: AbortSignal) => MaybePromise<boolean>,
message: TMessage
): CheckActionAsync<TInput, TMessage>;

// @__NO_SIDE_EFFECTS__
export function checkAsync(
requirement: (input: unknown) => MaybePromise<boolean>,
requirement: (input: unknown, signal?: AbortSignal) => MaybePromise<boolean>,
message?: ErrorMessage<CheckIssue<unknown>>
): CheckActionAsync<unknown, ErrorMessage<CheckIssue<unknown>> | undefined> {
return {
Expand All @@ -76,7 +79,10 @@ export function checkAsync(
requirement,
message,
async '~run'(dataset, config) {
if (dataset.typed && !(await this.requirement(dataset.value))) {
if (
dataset.typed &&
!(await this.requirement(dataset.value, config.signal))
) {
_addIssue(this, 'input', dataset, config);
}
return dataset;
Expand Down
4 changes: 3 additions & 1 deletion library/src/actions/checkItems/checkItemsAsync.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,9 @@ export function checkItemsAsync(
async '~run'(dataset, config) {
if (dataset.typed) {
const requirementResults = await Promise.all(
dataset.value.map(this.requirement)
dataset.value.map((...args) =>
this.requirement(...args, config.signal)
)
);
for (let index = 0; index < dataset.value.length; index++) {
if (!requirementResults[index]) {
Expand Down
22 changes: 17 additions & 5 deletions library/src/actions/partialCheck/partialCheckAsync.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,10 @@ export interface PartialCheckActionAsync<
/**
* The validation function.
*/
readonly requirement: (input: TSelection) => MaybePromise<boolean>;
readonly requirement: (
input: TSelection,
signal?: AbortSignal
) => MaybePromise<boolean>;
/**
* The error message.
*/
Expand All @@ -63,7 +66,10 @@ export function partialCheckAsync<
const TSelection extends DeepPickN<TInput, TPaths>,
>(
paths: ValidPaths<TInput, TPaths>,
requirement: (input: TSelection) => MaybePromise<boolean>
requirement: (
input: TSelection,
signal?: AbortSignal
) => MaybePromise<boolean>
): PartialCheckActionAsync<TInput, TPaths, TSelection, undefined>;

/**
Expand All @@ -84,14 +90,20 @@ export function partialCheckAsync<
| undefined,
>(
paths: ValidPaths<TInput, TPaths>,
requirement: (input: TSelection) => MaybePromise<boolean>,
requirement: (
input: TSelection,
signal?: AbortSignal
) => MaybePromise<boolean>,
message: TMessage
): PartialCheckActionAsync<TInput, TPaths, TSelection, TMessage>;

// @__NO_SIDE_EFFECTS__
export function partialCheckAsync(
paths: Paths,
requirement: (input: PartialInput) => MaybePromise<boolean>,
requirement: (
input: PartialInput,
signal?: AbortSignal
) => MaybePromise<boolean>,
message?: ErrorMessage<PartialCheckIssue<PartialInput>>
): PartialCheckActionAsync<
PartialInput,
Expand All @@ -112,7 +124,7 @@ export function partialCheckAsync(
if (
(dataset.typed || _isPartiallyTyped(dataset, paths)) &&
// @ts-expect-error
!(await this.requirement(dataset.value))
!(await this.requirement(dataset.value, config.signal))
) {
_addIssue(this, 'input', dataset, config);
}
Expand Down
8 changes: 4 additions & 4 deletions library/src/actions/transform/transformAsync.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export interface TransformActionAsync<TInput, TOutput>
/**
* The transformation operation.
*/
readonly operation: (input: TInput) => Promise<TOutput>;
readonly operation: (input: TInput, signal?: AbortSignal) => Promise<TOutput>;
}

/**
Expand All @@ -31,17 +31,17 @@ export interface TransformActionAsync<TInput, TOutput>
*/
// @__NO_SIDE_EFFECTS__
export function transformAsync<TInput, TOutput>(
operation: (input: TInput) => Promise<TOutput>
operation: (input: TInput, signal?: AbortSignal) => Promise<TOutput>
): TransformActionAsync<TInput, TOutput> {
return {
kind: 'transformation',
type: 'transform',
reference: transformAsync,
async: true,
operation,
async '~run'(dataset) {
async '~run'(dataset, config) {
// @ts-expect-error
dataset.value = await this.operation(dataset.value);
dataset.value = await this.operation(dataset.value, config.signal);
// @ts-expect-error
return dataset as SuccessDataset<TOutput>;
},
Expand Down
3 changes: 2 additions & 1 deletion library/src/actions/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ export type ArrayRequirement<TInput extends ArrayInput> = (
export type ArrayRequirementAsync<TInput extends ArrayInput> = (
item: TInput[number],
index: number,
array: TInput
array: TInput,
signal?: AbortSignal
) => MaybePromise<boolean>;

/**
Expand Down
26 changes: 24 additions & 2 deletions library/src/methods/parse/parseAsync.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { describe, expect, test } from 'vitest';
import { transform } from '../../actions/index.ts';
import { checkAsync, transform } from '../../actions/index.ts';
import { number, objectAsync, string } from '../../schemas/index.ts';
import { pipe } from '../pipe/index.ts';
import { pipe, pipeAsync } from '../pipe/index.ts';
import { parseAsync } from './parseAsync.ts';

describe('parseAsync', () => {
Expand Down Expand Up @@ -29,4 +29,26 @@ describe('parseAsync', () => {
parseAsync(objectAsync(entries), null)
).rejects.toThrowError();
});

describe('abortSignal', () => {
test('should abort', async () => {
const abort = new AbortController();
const promise = expect(() =>
parseAsync(
pipeAsync(
string(),
checkAsync(async (_, signal) => {
await 0;
signal?.throwIfAborted();
return true;
})
),
'foo',
{ signal: abort.signal }
)
).rejects.toThrowError('abort');
abort.abort('abort');
await promise;
});
});
});
9 changes: 9 additions & 0 deletions library/src/methods/pipe/pipeAsync.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -154,4 +154,13 @@ describe('pipeAsync', () => {
});
});
});

describe('abortSignal', () => {
test('should abort', async () => {
const signal = AbortSignal.abort('abort');
await expect(() =>
schema['~run']({ value: 'foo' }, { signal })
).rejects.toThrowError('abort');
});
});
});
1 change: 1 addition & 0 deletions library/src/methods/pipe/pipeAsync.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3104,6 +3104,7 @@ export function pipeAsync<
}

// Continue pipe execution if there is no reason to abort early
config.signal?.throwIfAborted();
if (
!dataset.issues ||
(!config.abortEarly && !config.abortPipeEarly)
Expand Down
7 changes: 5 additions & 2 deletions library/src/schemas/custom/customAsync.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,10 @@ import type { CustomIssue } from './types.ts';
/**
* Check async type.
*/
type CheckAsync = (input: unknown) => MaybePromise<boolean>;
type CheckAsync = (
input: unknown,
signal?: AbortSignal
) => MaybePromise<boolean>;

/**
* Custom schema async interface.
Expand Down Expand Up @@ -85,7 +88,7 @@ export function customAsync<TInput>(
return _getStandardProps(this);
},
async '~run'(dataset, config) {
if (await this.check(dataset.value)) {
if (await this.check(dataset.value, config.signal)) {
// @ts-expect-error
dataset.typed = true;
} else {
Expand Down
10 changes: 9 additions & 1 deletion library/src/schemas/lazy/lazyAsync.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,14 @@ describe('lazyAsync', () => {
const getter = vi.fn(() => string());
const dataset = { value: 'foo' };
lazyAsync(getter)['~run'](dataset, {});
expect(getter).toHaveBeenCalledWith(dataset.value);
expect(getter).toHaveBeenCalledWith(dataset.value, undefined);
});

test('should call getter with signal', () => {
const getter = vi.fn(() => string());
const dataset = { value: 'foo' };
const signal = AbortSignal.abort();
lazyAsync(getter)['~run'](dataset, { signal });
expect(getter).toHaveBeenCalledWith(dataset.value, signal);
});
});
12 changes: 9 additions & 3 deletions library/src/schemas/lazy/lazyAsync.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,10 @@ export interface LazySchemaAsync<
/**
* The schema getter.
*/
readonly getter: (input: unknown) => MaybePromise<TWrapped>;
readonly getter: (
input: unknown,
signal?: AbortSignal
) => MaybePromise<TWrapped>;
}

/**
Expand All @@ -53,7 +56,7 @@ export function lazyAsync<
| BaseSchema<unknown, unknown, BaseIssue<unknown>>
| BaseSchemaAsync<unknown, unknown, BaseIssue<unknown>>,
>(
getter: (input: unknown) => MaybePromise<TWrapped>
getter: (input: unknown, signal?: AbortSignal) => MaybePromise<TWrapped>
): LazySchemaAsync<TWrapped> {
return {
kind: 'schema',
Expand All @@ -66,7 +69,10 @@ export function lazyAsync<
return _getStandardProps(this);
},
async '~run'(dataset, config) {
return (await this.getter(dataset.value))['~run'](dataset, config);
return (await this.getter(dataset.value, config.signal))['~run'](
dataset,
config
);
},
};
}
1 change: 1 addition & 0 deletions library/src/storages/globalConfig/globalConfig.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ describe('config', () => {
message: undefined,
abortEarly: undefined,
abortPipeEarly: undefined,
signal: undefined,
};

const customConfig: GlobalConfig = {
Expand Down
1 change: 1 addition & 0 deletions library/src/storages/globalConfig/globalConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ export function getGlobalConfig<const TIssue extends BaseIssue<unknown>>(
message: config?.message,
abortEarly: config?.abortEarly ?? store?.abortEarly,
abortPipeEarly: config?.abortPipeEarly ?? store?.abortPipeEarly,
signal: config?.signal,
};
}

Expand Down
6 changes: 6 additions & 0 deletions library/src/types/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,10 @@ export interface Config<TIssue extends BaseIssue<unknown>> {
* Whether a pipe should be aborted early.
*/
readonly abortPipeEarly?: boolean | undefined;
/**
* The abort signal.
*
* @see https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal
*/
readonly signal?: AbortSignal | undefined;
}