From 9ac77b9cd3a72bafee197da6f13052cd38028c96 Mon Sep 17 00:00:00 2001 From: ZeRiix Date: Wed, 8 Jul 2026 11:19:19 +0200 Subject: [PATCH 1/8] feat(94): add dataParser coercer --- integration/correctExport.ts | 2 +- package.json | 6 +- scripts/dataParser/base.ts | 1 + scripts/dataParser/parsers/bigint/index.ts | 4 + scripts/dataParser/parsers/boolean.ts | 4 + scripts/dataParser/parsers/coerce/base.ts | 307 ++++++++++++++++++ scripts/dataParser/parsers/coerce/bigint.ts | 28 -- scripts/dataParser/parsers/coerce/boolean.ts | 28 -- scripts/dataParser/parsers/coerce/date.ts | 28 -- scripts/dataParser/parsers/coerce/empty.ts | 28 -- scripts/dataParser/parsers/coerce/index.ts | 10 +- scripts/dataParser/parsers/coerce/nil.ts | 28 -- scripts/dataParser/parsers/coerce/number.ts | 28 -- .../parsers/coerce/presets/bigint.ts | 21 ++ .../parsers/coerce/presets/boolean.ts | 21 ++ .../dataParser/parsers/coerce/presets/date.ts | 21 ++ .../parsers/coerce/presets/empty.ts | 21 ++ .../parsers/coerce/presets/index.ts | 8 + .../dataParser/parsers/coerce/presets/nil.ts | 21 ++ .../parsers/coerce/presets/number.ts | 21 ++ .../parsers/coerce/presets/string.ts | 21 ++ .../dataParser/parsers/coerce/presets/time.ts | 21 ++ scripts/dataParser/parsers/coerce/string.ts | 28 -- scripts/dataParser/parsers/coerce/time.ts | 28 -- scripts/dataParser/parsers/date.ts | 4 + scripts/dataParser/parsers/empty.ts | 4 + scripts/dataParser/parsers/index.ts | 2 +- scripts/dataParser/parsers/nil.ts | 4 + scripts/dataParser/parsers/nullable.ts | 2 +- scripts/dataParser/parsers/number/index.ts | 4 + scripts/dataParser/parsers/string/index.ts | 4 + scripts/dataParser/parsers/time/index.ts | 4 + tests/dataParser/parsers/coerce/base.test.ts | 92 ++++++ .../coerce/{ => presets}/bigint.test.ts | 12 + .../coerce/{ => presets}/boolean.test.ts | 17 + .../parsers/coerce/{ => presets}/date.test.ts | 21 +- .../coerce/{ => presets}/empty.test.ts | 12 + .../parsers/coerce/{ => presets}/nil.test.ts | 12 + .../coerce/{ => presets}/number.test.ts | 18 + .../coerce/{ => presets}/string.test.ts | 12 + .../parsers/coerce/{ => presets}/time.test.ts | 19 +- 41 files changed, 735 insertions(+), 242 deletions(-) create mode 100644 scripts/dataParser/parsers/coerce/base.ts delete mode 100644 scripts/dataParser/parsers/coerce/bigint.ts delete mode 100644 scripts/dataParser/parsers/coerce/boolean.ts delete mode 100644 scripts/dataParser/parsers/coerce/date.ts delete mode 100644 scripts/dataParser/parsers/coerce/empty.ts delete mode 100644 scripts/dataParser/parsers/coerce/nil.ts delete mode 100644 scripts/dataParser/parsers/coerce/number.ts create mode 100644 scripts/dataParser/parsers/coerce/presets/bigint.ts create mode 100644 scripts/dataParser/parsers/coerce/presets/boolean.ts create mode 100644 scripts/dataParser/parsers/coerce/presets/date.ts create mode 100644 scripts/dataParser/parsers/coerce/presets/empty.ts create mode 100644 scripts/dataParser/parsers/coerce/presets/index.ts create mode 100644 scripts/dataParser/parsers/coerce/presets/nil.ts create mode 100644 scripts/dataParser/parsers/coerce/presets/number.ts create mode 100644 scripts/dataParser/parsers/coerce/presets/string.ts create mode 100644 scripts/dataParser/parsers/coerce/presets/time.ts delete mode 100644 scripts/dataParser/parsers/coerce/string.ts delete mode 100644 scripts/dataParser/parsers/coerce/time.ts create mode 100644 tests/dataParser/parsers/coerce/base.test.ts rename tests/dataParser/parsers/coerce/{ => presets}/bigint.test.ts (81%) rename tests/dataParser/parsers/coerce/{ => presets}/boolean.test.ts (69%) rename tests/dataParser/parsers/coerce/{ => presets}/date.test.ts (70%) rename tests/dataParser/parsers/coerce/{ => presets}/empty.test.ts (80%) rename tests/dataParser/parsers/coerce/{ => presets}/nil.test.ts (80%) rename tests/dataParser/parsers/coerce/{ => presets}/number.test.ts (69%) rename tests/dataParser/parsers/coerce/{ => presets}/string.test.ts (81%) rename tests/dataParser/parsers/coerce/{ => presets}/time.test.ts (73%) diff --git a/integration/correctExport.ts b/integration/correctExport.ts index 940a77cd8..0c6d68b03 100644 --- a/integration/correctExport.ts +++ b/integration/correctExport.ts @@ -27,7 +27,7 @@ type Check2 = All.ExpectType< >; type Check22 = All.ExpectType< - typeof All.DDataParserCoerce, + typeof All.DDataParserCoerce.coerce, typeof DDPC, "strict" >; diff --git a/package.json b/package.json index f7b9b1130..3ab460433 100644 --- a/package.json +++ b/package.json @@ -55,9 +55,9 @@ "types": "./dist/dataParser/index.d.ts" }, "./dataParserCoerce": { - "import": "./dist/dataParser/parsers/coerce/index.mjs", - "require": "./dist/dataParser/parsers/coerce/index.cjs", - "types": "./dist/dataParser/parsers/coerce/index.d.ts" + "import": "./dist/dataParser/parsers/coerce/presets/index.mjs", + "require": "./dist/dataParser/parsers/coerce/presets/index.cjs", + "types": "./dist/dataParser/parsers/coerce/presets/index.d.ts" }, "./dataParserExtended": { "import": "./dist/dataParser/extended/index.mjs", diff --git a/scripts/dataParser/base.ts b/scripts/dataParser/base.ts index ec0422bd8..87e5d5f9a 100644 --- a/scripts/dataParser/base.ts +++ b/scripts/dataParser/base.ts @@ -75,6 +75,7 @@ export abstract class DataParserBase< prepareDefinition( ...args: never[] ): DataParserDefinition; + readonly specificKindHandler: DCommon.KindHandler; } ); diff --git a/scripts/dataParser/parsers/bigint/index.ts b/scripts/dataParser/parsers/bigint/index.ts index d911fa4b6..55d7895b3 100644 --- a/scripts/dataParser/parsers/bigint/index.ts +++ b/scripts/dataParser/parsers/bigint/index.ts @@ -12,6 +12,10 @@ export type DataParserBigIntCheckers = GetEligibleChecker; export interface DataParserDefinitionBigInt extends DataParserDefinition< DataParserBigIntCheckers > { + + /** + * @deprecated Use DDataParser.coercer(DDataParser.boolean()) or DDataParser.coerce.boolean() instead. + */ readonly coerce: boolean; } diff --git a/scripts/dataParser/parsers/boolean.ts b/scripts/dataParser/parsers/boolean.ts index b0d11c13e..8e87ce1ac 100644 --- a/scripts/dataParser/parsers/boolean.ts +++ b/scripts/dataParser/parsers/boolean.ts @@ -10,6 +10,10 @@ export type DataParserBooleanCheckers = GetEligibleChecker; export interface DataParserDefinitionBoolean extends DataParserDefinition< DataParserBooleanCheckers > { + + /** + * @deprecated Use DDataParser.coercer(DDataParser.boolean()) or DDataParser.coerce.boolean() instead. + */ readonly coerce: boolean; } diff --git a/scripts/dataParser/parsers/coerce/base.ts b/scripts/dataParser/parsers/coerce/base.ts new file mode 100644 index 000000000..1b19efb42 --- /dev/null +++ b/scripts/dataParser/parsers/coerce/base.ts @@ -0,0 +1,307 @@ +import { detachObjectMethod, type ComputedTypeError, type FixDeepFunctionInfer, type IsNever, type KindHandler, type NeverCoalescing } from "@scripts/common"; +import { DataParserBase, type DataParser, type DataParserDefinition } from "../../base"; +import { createDataParserKind } from "../../kind"; +import { addIssue, type DataParserError } from "../../error"; +import { type DataParserChecker } from "../../baseChecker"; +import type { ApplyRefinementOfDefinition, AddCheckersToDefinition, GetEligibleChecker, Input, MergeDefinition, Output, PrepareDataParserDefinition } from "../../types"; +import { bigIntKind, type DataParserBigInt } from "../bigint"; +import { booleanKind, type DataParserBoolean } from "../boolean"; +import { dateKind, type DataParserDate } from "../date"; +import { emptyKind, type DataParserEmpty } from "../empty"; +import { nilKind, type DataParserNil } from "../nil"; +import { numberKind, type DataParserNumber } from "../number"; +import { stringKind, type DataParserString } from "../string"; +import { timeKind, type DataParserTime } from "../time"; +import * as DDate from "@scripts/date"; +import * as DEither from "@scripts/either"; + +export type DataParserCoerceTransformer = (data: unknown) => unknown; + +export const dataParserCoerceTransformerMapper = new Map([ + [ + bigIntKind, + (data) => { + try { + return BigInt(data as never); + } catch { + return data; + } + }, + ], + [ + booleanKind, + (data) => { + if (typeof data === "string") { + const lower = data.trim().toLowerCase(); + + if (lower === "true" || lower === "false") { + return lower === "true"; + } + } else if (typeof data === "number" && (data === 0 || data === 1)) { + return data === 1; + } + + return data; + }, + ], + [ + emptyKind, + (data) => data === "undefined" + ? undefined + : data, + ], + [ + nilKind, + (data) => data === "null" + ? null + : data, + ], + [ + numberKind, + (data) => { + try { + return Number(data); + } catch { + return data; + } + }, + ], + [ + stringKind, + (data) => { + try { + return String(data); + } catch { + return data; + } + }, + ], + [ + timeKind, + (data) => { + if ( + typeof data === "string" + && DDate.isoTimeRegex.test(data) + ) { + const result = DDate.createTime({ value: data }); + + if (DEither.isRight(result)) { + return DEither.unwrapRight(result); + } + } + + return data; + }, + ], + [ + dateKind, + (data) => { + if ( + typeof data === "number" + && DDate.isSafeTimestamp(data) + ) { + return DDate.TheDate.new(data); + } + + if (typeof data === "string") { + const date = new Date(data); + const timestamp = date.getTime(); + if (DDate.isSafeTimestamp(timestamp)) { + return DDate.TheDate.new(timestamp); + } + } + + return data; + }, + ], +]); + +export interface EligibleDataParserCoerce< + GenericDataParser extends DataParser, +> { + bigint: GenericDataParser extends DataParserBigInt + ? Input + : never; + boolean: GenericDataParser extends DataParserBoolean + ? string | number | boolean + : never; + empty: GenericDataParser extends DataParserEmpty + ? "undefined" | Input + : never; + nil: GenericDataParser extends DataParserNil + ? "null" | Input + : never; + number: GenericDataParser extends DataParserNumber + ? string | number + : never; + string: GenericDataParser extends DataParserString + ? Input + : never; + date: GenericDataParser extends DataParserDate + ? string | number | Input + : never; + time: GenericDataParser extends DataParserTime + ? string | Input + : never; +} + +export interface EligibleDataParserCoerceOverride< + GenericDataParser extends DataParser, +> {} + +export type GetEligibleDataParserCoerce< + GenericDataParser extends DataParser, +> = ( + & Omit< + EligibleDataParserCoerce, + keyof EligibleDataParserCoerceOverride + > + & EligibleDataParserCoerceOverride +) extends infer InferredResult + ? InferredResult[keyof InferredResult] + : never; + +type MustBeEligibleDataParserCoerce< + GenericDataParser extends DataParser, +> = IsNever> extends true + ? ComputedTypeError<"This data parser is not eligible for coercion."> + : GenericDataParser; + +export type DataParserCoerceCheckers< + GenericInput extends unknown = unknown, +> = GetEligibleChecker; + +export type DataParserCoerceInput< + GenericDataParser extends DataParser, +> = ( + | Input + | GetEligibleDataParserCoerce +); + +export interface DataParserDefinitionCoerce< + GenericOutput extends unknown = unknown, +> extends DataParserDefinition< + DataParserCoerceCheckers + > { + readonly inner: DataParser; +} + +export const coerceKind = createDataParserKind("coerce"); + +export class DataParserCoerce< + GenericDefinition extends DataParserDefinitionCoerce = DataParserDefinitionCoerce, +> extends DataParserBase.init( + coerceKind, + )< + GenericDefinition, + ApplyRefinementOfDefinition< + Output, + GenericDefinition + >, + ApplyRefinementOfDefinition< + DataParserCoerceInput, + GenericDefinition + > + > { + public get classConstructor(): typeof DataParserCoerce { + return this.checkConstructor(DataParserCoerce); + } + + public declare addChecker: < + GenericChecker extends readonly [ + DataParserChecker>, + ...DataParserChecker>[], + ], + >( + ...args: FixDeepFunctionInfer< + readonly [ + DataParserChecker>, + ...DataParserChecker>[], + ], + GenericChecker + > + ) => DataParserCoerce< + AddCheckersToDefinition< + GenericDefinition, + GenericChecker + > + >; + + public static override execParse( + self: DataParserCoerce, + data: unknown, + error: DataParserError, + ): unknown { + const transformer = dataParserCoerceTransformerMapper.get( + self.definition.inner.classConstructor.specificKindHandler, + ); + + if (!transformer) { + return addIssue( + error, + "supported coerce data parser", + data, + self.definition.errorMessage, + self, + ); + } + + try { + return self.definition.inner.exec(transformer(data), error); + } catch (catchError) { + return addIssue( + error, + "successful coerce transform", + catchError, + self.definition.errorMessage, + self, + ); + } + } + + public static override dataParserIsAsynchronous(self: DataParserCoerce) { + return self.definition.inner.isAsynchronous(); + } + + public static override prepareDefinition( + inner: DataParser, + definition?: Partial>, + ): DataParserDefinitionCoerce { + return { + ...definition, + inner, + checkers: definition?.checkers ?? [], + errorMessage: definition?.errorMessage, + }; + } + + public static override create< + GenericDataParser extends DataParser, + const GenericDefinition extends PrepareDataParserDefinition< + DataParserDefinitionCoerce< + Output + >, + "inner" + > = never, + >( + inner: GenericDataParser & MustBeEligibleDataParserCoerce, + definition?: FixDeepFunctionInfer< + PrepareDataParserDefinition< + DataParserDefinitionCoerce< + Output + >, + "inner" + >, + GenericDefinition + >, + ): DataParserCoerce< + MergeDefinition< + DataParserDefinitionCoerce, + NeverCoalescing & { inner: GenericDataParser } + > + > { + return new DataParserCoerce(this.prepareDefinition(inner, definition)) as never; + } +} + +export const coercer = detachObjectMethod(DataParserCoerce, "create"); diff --git a/scripts/dataParser/parsers/coerce/bigint.ts b/scripts/dataParser/parsers/coerce/bigint.ts deleted file mode 100644 index 9ac299f90..000000000 --- a/scripts/dataParser/parsers/coerce/bigint.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { type FixDeepFunctionInfer, type NeverCoalescing } from "@scripts/common"; -import { type MergeDefinition, type PrepareDataParserDefinition } from "../../types"; -import * as dataParsers from ".."; - -export function bigint< - const GenericDefinition extends PrepareDataParserDefinition< - dataParsers.DataParserDefinitionBigInt, - "coerce" - > = never, ->( - definition?: FixDeepFunctionInfer< - PrepareDataParserDefinition< - dataParsers.DataParserDefinitionBigInt, - "coerce" - >, - GenericDefinition - >, -): dataParsers.DataParserBigInt< - MergeDefinition< - dataParsers.DataParserDefinitionBigInt, - NeverCoalescing & { coerce: true } - > - > { - return dataParsers.bigint({ - ...definition, - coerce: true, - }); -} diff --git a/scripts/dataParser/parsers/coerce/boolean.ts b/scripts/dataParser/parsers/coerce/boolean.ts deleted file mode 100644 index d642b32ad..000000000 --- a/scripts/dataParser/parsers/coerce/boolean.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { type FixDeepFunctionInfer, type NeverCoalescing } from "@scripts/common"; -import { type MergeDefinition, type PrepareDataParserDefinition } from "../../types"; -import * as dataParsers from ".."; - -export function boolean< - const GenericDefinition extends PrepareDataParserDefinition< - dataParsers.DataParserDefinitionBoolean, - "coerce" - > = never, ->( - definition?: FixDeepFunctionInfer< - PrepareDataParserDefinition< - dataParsers.DataParserDefinitionBoolean, - "coerce" - >, - GenericDefinition - >, -): dataParsers.DataParserBoolean< - MergeDefinition< - dataParsers.DataParserDefinitionBoolean, - NeverCoalescing & { coerce: true } - > - > { - return dataParsers.boolean({ - ...definition, - coerce: true, - }); -} diff --git a/scripts/dataParser/parsers/coerce/date.ts b/scripts/dataParser/parsers/coerce/date.ts deleted file mode 100644 index 127e2070a..000000000 --- a/scripts/dataParser/parsers/coerce/date.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { type FixDeepFunctionInfer, type NeverCoalescing } from "@scripts/common"; -import { type MergeDefinition, type PrepareDataParserDefinition } from "../../types"; -import * as dataParsers from ".."; - -export function date< - const GenericDefinition extends PrepareDataParserDefinition< - dataParsers.DataParserDefinitionDate, - "coerce" - > = never, ->( - definition?: FixDeepFunctionInfer< - PrepareDataParserDefinition< - dataParsers.DataParserDefinitionDate, - "coerce" - >, - GenericDefinition - >, -): dataParsers.DataParserDate< - MergeDefinition< - dataParsers.DataParserDefinitionDate, - NeverCoalescing & { coerce: true } - > - > { - return dataParsers.date({ - ...definition, - coerce: true, - }); -} diff --git a/scripts/dataParser/parsers/coerce/empty.ts b/scripts/dataParser/parsers/coerce/empty.ts deleted file mode 100644 index e5cc32503..000000000 --- a/scripts/dataParser/parsers/coerce/empty.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { type FixDeepFunctionInfer, type NeverCoalescing } from "@scripts/common"; -import { type MergeDefinition, type PrepareDataParserDefinition } from "../../types"; -import * as dataParsers from ".."; - -export function empty< - const GenericDefinition extends PrepareDataParserDefinition< - dataParsers.DataParserDefinitionEmpty, - "coerce" - > = never, ->( - definition?: FixDeepFunctionInfer< - PrepareDataParserDefinition< - dataParsers.DataParserDefinitionEmpty, - "coerce" - >, - GenericDefinition - >, -): dataParsers.DataParserEmpty< - MergeDefinition< - dataParsers.DataParserDefinitionEmpty, - NeverCoalescing & { coerce: true } - > - > { - return dataParsers.empty({ - ...definition, - coerce: true, - }); -} diff --git a/scripts/dataParser/parsers/coerce/index.ts b/scripts/dataParser/parsers/coerce/index.ts index 6e8405fe3..7eb92c6bd 100644 --- a/scripts/dataParser/parsers/coerce/index.ts +++ b/scripts/dataParser/parsers/coerce/index.ts @@ -1,8 +1,2 @@ -export * from "./bigint"; -export * from "./boolean"; -export * from "./date"; -export * from "./time"; -export * from "./empty"; -export * from "./nil"; -export * from "./number"; -export * from "./string"; +export * from "./base"; +export * as coerce from "./presets"; diff --git a/scripts/dataParser/parsers/coerce/nil.ts b/scripts/dataParser/parsers/coerce/nil.ts deleted file mode 100644 index 9fe3c5476..000000000 --- a/scripts/dataParser/parsers/coerce/nil.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { type FixDeepFunctionInfer, type NeverCoalescing } from "@scripts/common"; -import { type MergeDefinition, type PrepareDataParserDefinition } from "../../types"; -import * as dataParsers from ".."; - -export function nil< - const GenericDefinition extends PrepareDataParserDefinition< - dataParsers.DataParserDefinitionNil, - "coerce" - > = never, ->( - definition?: FixDeepFunctionInfer< - PrepareDataParserDefinition< - dataParsers.DataParserDefinitionNil, - "coerce" - >, - GenericDefinition - >, -): dataParsers.DataParserNil< - MergeDefinition< - dataParsers.DataParserDefinitionNil, - NeverCoalescing & { coerce: true } - > - > { - return dataParsers.nil({ - ...definition, - coerce: true, - }); -} diff --git a/scripts/dataParser/parsers/coerce/number.ts b/scripts/dataParser/parsers/coerce/number.ts deleted file mode 100644 index 35074e748..000000000 --- a/scripts/dataParser/parsers/coerce/number.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { type FixDeepFunctionInfer, type NeverCoalescing } from "@scripts/common"; -import { type MergeDefinition, type PrepareDataParserDefinition } from "../../types"; -import * as dataParsers from ".."; - -export function number< - const GenericDefinition extends PrepareDataParserDefinition< - dataParsers.DataParserDefinitionNumber, - "coerce" - > = never, ->( - definition?: FixDeepFunctionInfer< - PrepareDataParserDefinition< - dataParsers.DataParserDefinitionNumber, - "coerce" - >, - GenericDefinition - >, -): dataParsers.DataParserNumber< - MergeDefinition< - dataParsers.DataParserDefinitionNumber, - NeverCoalescing & { coerce: true } - > - > { - return dataParsers.number({ - ...definition, - coerce: true, - }); -} diff --git a/scripts/dataParser/parsers/coerce/presets/bigint.ts b/scripts/dataParser/parsers/coerce/presets/bigint.ts new file mode 100644 index 000000000..7986217cd --- /dev/null +++ b/scripts/dataParser/parsers/coerce/presets/bigint.ts @@ -0,0 +1,21 @@ +import { type FixDeepFunctionInfer } from "@scripts/common"; +import { type PrepareDataParserDefinition } from "../../../types"; +import * as dataParsers from "../.."; +import { coercer } from "../base"; + +export function bigint< + const GenericDefinition extends PrepareDataParserDefinition< + dataParsers.DataParserDefinitionBigInt, + "coerce" + > = never, +>( + definition?: FixDeepFunctionInfer< + PrepareDataParserDefinition< + dataParsers.DataParserDefinitionBigInt, + "coerce" + >, + GenericDefinition + >, +) { + return coercer(dataParsers.bigint(definition)); +} diff --git a/scripts/dataParser/parsers/coerce/presets/boolean.ts b/scripts/dataParser/parsers/coerce/presets/boolean.ts new file mode 100644 index 000000000..3fdcb8cfa --- /dev/null +++ b/scripts/dataParser/parsers/coerce/presets/boolean.ts @@ -0,0 +1,21 @@ +import { type FixDeepFunctionInfer } from "@scripts/common"; +import { type PrepareDataParserDefinition } from "../../../types"; +import * as dataParsers from "../.."; +import { coercer } from "../base"; + +export function boolean< + const GenericDefinition extends PrepareDataParserDefinition< + dataParsers.DataParserDefinitionBoolean, + "coerce" + > = never, +>( + definition?: FixDeepFunctionInfer< + PrepareDataParserDefinition< + dataParsers.DataParserDefinitionBoolean, + "coerce" + >, + GenericDefinition + >, +) { + return coercer(dataParsers.boolean(definition)); +} diff --git a/scripts/dataParser/parsers/coerce/presets/date.ts b/scripts/dataParser/parsers/coerce/presets/date.ts new file mode 100644 index 000000000..fc2237b72 --- /dev/null +++ b/scripts/dataParser/parsers/coerce/presets/date.ts @@ -0,0 +1,21 @@ +import { type FixDeepFunctionInfer } from "@scripts/common"; +import { type PrepareDataParserDefinition } from "../../../types"; +import * as dataParsers from "../.."; +import { coercer } from "../base"; + +export function date< + const GenericDefinition extends PrepareDataParserDefinition< + dataParsers.DataParserDefinitionDate, + "coerce" + > = never, +>( + definition?: FixDeepFunctionInfer< + PrepareDataParserDefinition< + dataParsers.DataParserDefinitionDate, + "coerce" + >, + GenericDefinition + >, +) { + return coercer(dataParsers.date(definition)); +} diff --git a/scripts/dataParser/parsers/coerce/presets/empty.ts b/scripts/dataParser/parsers/coerce/presets/empty.ts new file mode 100644 index 000000000..777434522 --- /dev/null +++ b/scripts/dataParser/parsers/coerce/presets/empty.ts @@ -0,0 +1,21 @@ +import type { FixDeepFunctionInfer } from "@scripts/common"; +import type { PrepareDataParserDefinition } from "@scripts/dataParser/types"; +import * as dataParsers from "../.."; +import { coercer } from "../base"; + +export function empty< + const GenericDefinition extends PrepareDataParserDefinition< + dataParsers.DataParserDefinitionEmpty, + "coerce" + > = never, +>( + definition?: FixDeepFunctionInfer< + PrepareDataParserDefinition< + dataParsers.DataParserDefinitionEmpty, + "coerce" + >, + GenericDefinition + >, +) { + return coercer(dataParsers.empty(definition)); +} diff --git a/scripts/dataParser/parsers/coerce/presets/index.ts b/scripts/dataParser/parsers/coerce/presets/index.ts new file mode 100644 index 000000000..6e8405fe3 --- /dev/null +++ b/scripts/dataParser/parsers/coerce/presets/index.ts @@ -0,0 +1,8 @@ +export * from "./bigint"; +export * from "./boolean"; +export * from "./date"; +export * from "./time"; +export * from "./empty"; +export * from "./nil"; +export * from "./number"; +export * from "./string"; diff --git a/scripts/dataParser/parsers/coerce/presets/nil.ts b/scripts/dataParser/parsers/coerce/presets/nil.ts new file mode 100644 index 000000000..f1e94072f --- /dev/null +++ b/scripts/dataParser/parsers/coerce/presets/nil.ts @@ -0,0 +1,21 @@ +import { type FixDeepFunctionInfer } from "@scripts/common"; +import type { PrepareDataParserDefinition } from "@scripts/dataParser/types"; +import * as dataParsers from "../.."; +import { coercer } from "../base"; + +export function nil< + const GenericDefinition extends PrepareDataParserDefinition< + dataParsers.DataParserDefinitionNil, + "coerce" + > = never, +>( + definition?: FixDeepFunctionInfer< + PrepareDataParserDefinition< + dataParsers.DataParserDefinitionNil, + "coerce" + >, + GenericDefinition + >, +) { + return coercer(dataParsers.nil(definition)); +} diff --git a/scripts/dataParser/parsers/coerce/presets/number.ts b/scripts/dataParser/parsers/coerce/presets/number.ts new file mode 100644 index 000000000..b39aae943 --- /dev/null +++ b/scripts/dataParser/parsers/coerce/presets/number.ts @@ -0,0 +1,21 @@ +import { type FixDeepFunctionInfer } from "@scripts/common"; +import type { PrepareDataParserDefinition } from "@scripts/dataParser/types"; +import * as dataParsers from "../.."; +import { coercer } from "../base"; + +export function number< + const GenericDefinition extends PrepareDataParserDefinition< + dataParsers.DataParserDefinitionNumber, + "coerce" + > = never, +>( + definition?: FixDeepFunctionInfer< + PrepareDataParserDefinition< + dataParsers.DataParserDefinitionNumber, + "coerce" + >, + GenericDefinition + >, +) { + return coercer(dataParsers.number(definition)); +} diff --git a/scripts/dataParser/parsers/coerce/presets/string.ts b/scripts/dataParser/parsers/coerce/presets/string.ts new file mode 100644 index 000000000..37b35f599 --- /dev/null +++ b/scripts/dataParser/parsers/coerce/presets/string.ts @@ -0,0 +1,21 @@ +import { type FixDeepFunctionInfer } from "@scripts/common"; +import type { PrepareDataParserDefinition } from "@scripts/dataParser/types"; +import * as dataParsers from "../.."; +import { coercer } from "../base"; + +export function string< + const GenericDefinition extends PrepareDataParserDefinition< + dataParsers.DataParserDefinitionString, + "coerce" + > = never, +>( + definition?: FixDeepFunctionInfer< + PrepareDataParserDefinition< + dataParsers.DataParserDefinitionString, + "coerce" + >, + GenericDefinition + >, +) { + return coercer(dataParsers.string(definition)); +} diff --git a/scripts/dataParser/parsers/coerce/presets/time.ts b/scripts/dataParser/parsers/coerce/presets/time.ts new file mode 100644 index 000000000..164a72215 --- /dev/null +++ b/scripts/dataParser/parsers/coerce/presets/time.ts @@ -0,0 +1,21 @@ +import { type FixDeepFunctionInfer } from "@scripts/common"; +import type { PrepareDataParserDefinition } from "@scripts/dataParser/types"; +import * as dataParsers from "../.."; +import { coercer } from "../base"; + +export function time< + const GenericDefinition extends PrepareDataParserDefinition< + dataParsers.DataParserDefinitionTime, + "coerce" + > = never, +>( + definition?: FixDeepFunctionInfer< + PrepareDataParserDefinition< + dataParsers.DataParserDefinitionTime, + "coerce" + >, + GenericDefinition + >, +) { + return coercer(dataParsers.time(definition)); +} diff --git a/scripts/dataParser/parsers/coerce/string.ts b/scripts/dataParser/parsers/coerce/string.ts deleted file mode 100644 index bfd56a581..000000000 --- a/scripts/dataParser/parsers/coerce/string.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { type FixDeepFunctionInfer, type NeverCoalescing } from "@scripts/common"; -import { type MergeDefinition, type PrepareDataParserDefinition } from "../../types"; -import * as dataParsers from ".."; - -export function string< - const GenericDefinition extends PrepareDataParserDefinition< - dataParsers.DataParserDefinitionString, - "coerce" - > = never, ->( - definition?: FixDeepFunctionInfer< - PrepareDataParserDefinition< - dataParsers.DataParserDefinitionString, - "coerce" - >, - GenericDefinition - >, -): dataParsers.DataParserString< - MergeDefinition< - dataParsers.DataParserDefinitionString, - NeverCoalescing & { coerce: true } - > - > { - return dataParsers.string({ - ...definition, - coerce: true, - }); -} diff --git a/scripts/dataParser/parsers/coerce/time.ts b/scripts/dataParser/parsers/coerce/time.ts deleted file mode 100644 index ee91844a7..000000000 --- a/scripts/dataParser/parsers/coerce/time.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { type FixDeepFunctionInfer, type NeverCoalescing } from "@scripts/common"; -import { type MergeDefinition, type PrepareDataParserDefinition } from "../../types"; -import * as dataParsers from ".."; - -export function time< - const GenericDefinition extends PrepareDataParserDefinition< - dataParsers.DataParserDefinitionTime, - "coerce" - > = never, ->( - definition?: FixDeepFunctionInfer< - PrepareDataParserDefinition< - dataParsers.DataParserDefinitionTime, - "coerce" - >, - GenericDefinition - >, -): dataParsers.DataParserTime< - MergeDefinition< - dataParsers.DataParserDefinitionTime, - NeverCoalescing & { coerce: true } - > - > { - return dataParsers.time({ - ...definition, - coerce: true, - }); -} diff --git a/scripts/dataParser/parsers/date.ts b/scripts/dataParser/parsers/date.ts index fea0e8ae0..166098136 100644 --- a/scripts/dataParser/parsers/date.ts +++ b/scripts/dataParser/parsers/date.ts @@ -11,6 +11,10 @@ export type DataParserDateCheckers = GetEligibleChecker; export interface DataParserDefinitionDate extends DataParserDefinition< DataParserDateCheckers > { + + /** + * @deprecated Use DDataParser.coercer(DDataParser.boolean()) or DDataParser.coerce.boolean() instead. + */ readonly coerce: boolean; } diff --git a/scripts/dataParser/parsers/empty.ts b/scripts/dataParser/parsers/empty.ts index cf646f809..6d24c4c7a 100644 --- a/scripts/dataParser/parsers/empty.ts +++ b/scripts/dataParser/parsers/empty.ts @@ -10,6 +10,10 @@ export type DataParserEmptyCheckers = GetEligibleChecker; export interface DataParserDefinitionEmpty extends DataParserDefinition< DataParserEmptyCheckers > { + + /** + * @deprecated Use DDataParser.coercer(DDataParser.boolean()) or DDataParser.coerce.boolean() instead. + */ readonly coerce: boolean; } diff --git a/scripts/dataParser/parsers/index.ts b/scripts/dataParser/parsers/index.ts index bbb2a8b68..c7248d59d 100644 --- a/scripts/dataParser/parsers/index.ts +++ b/scripts/dataParser/parsers/index.ts @@ -1,4 +1,4 @@ -export * as coerce from "./coerce"; +export * from "./coerce"; export * from "./string"; export * from "./number"; export * from "./array"; diff --git a/scripts/dataParser/parsers/nil.ts b/scripts/dataParser/parsers/nil.ts index 3fe4bed93..ed18be10e 100644 --- a/scripts/dataParser/parsers/nil.ts +++ b/scripts/dataParser/parsers/nil.ts @@ -10,6 +10,10 @@ export type DataParserNilCheckers = GetEligibleChecker; export interface DataParserDefinitionNil extends DataParserDefinition< DataParserNilCheckers > { + + /** + * @deprecated Use DDataParser.coercer(DDataParser.boolean()) or DDataParser.coerce.boolean() instead. + */ readonly coerce: boolean; } diff --git a/scripts/dataParser/parsers/nullable.ts b/scripts/dataParser/parsers/nullable.ts index a6adde7dd..0141db4e8 100644 --- a/scripts/dataParser/parsers/nullable.ts +++ b/scripts/dataParser/parsers/nullable.ts @@ -1,4 +1,4 @@ -import { detachObjectMethod, callThen, type FixDeepFunctionInfer, type IsEqual, type NeverCoalescing } from "@scripts/common"; +import { detachObjectMethod, type FixDeepFunctionInfer, type IsEqual, type NeverCoalescing } from "@scripts/common"; import { createDataParserKind } from "@scripts/dataParser/kind"; import { DataParserBase, type DataParser, type DataParserDefinition } from "../base"; import { type DataParserError } from "@scripts/dataParser/error"; diff --git a/scripts/dataParser/parsers/number/index.ts b/scripts/dataParser/parsers/number/index.ts index 7c37921a3..ee31688a8 100644 --- a/scripts/dataParser/parsers/number/index.ts +++ b/scripts/dataParser/parsers/number/index.ts @@ -12,6 +12,10 @@ export type DataParserNumberCheckers = GetEligibleChecker; export interface DataParserDefinitionNumber extends DataParserDefinition< DataParserNumberCheckers > { + + /** + * @deprecated Use DDataParser.coercer(DDataParser.boolean()) or DDataParser.coerce.boolean() instead. + */ readonly coerce: boolean; } diff --git a/scripts/dataParser/parsers/string/index.ts b/scripts/dataParser/parsers/string/index.ts index 9118df59b..e7dfe7dd6 100644 --- a/scripts/dataParser/parsers/string/index.ts +++ b/scripts/dataParser/parsers/string/index.ts @@ -12,6 +12,10 @@ export type DataParserStringCheckers = GetEligibleChecker; export interface DataParserDefinitionString extends DataParserDefinition< DataParserStringCheckers > { + + /** + * @deprecated Use DDataParser.coercer(DDataParser.boolean()) or DDataParser.coerce.boolean() instead. + */ readonly coerce: boolean; } diff --git a/scripts/dataParser/parsers/time/index.ts b/scripts/dataParser/parsers/time/index.ts index 6caba5806..6b1495eef 100644 --- a/scripts/dataParser/parsers/time/index.ts +++ b/scripts/dataParser/parsers/time/index.ts @@ -14,6 +14,10 @@ export type DataParserTimeCheckers = GetEligibleChecker; export interface DataParserDefinitionTime extends DataParserDefinition< DataParserTimeCheckers > { + + /** + * @deprecated Use DDataParser.coercer(DDataParser.boolean()) or DDataParser.coerce.boolean() instead. + */ readonly coerce: boolean; } diff --git a/tests/dataParser/parsers/coerce/base.test.ts b/tests/dataParser/parsers/coerce/base.test.ts new file mode 100644 index 000000000..b6e2bffa4 --- /dev/null +++ b/tests/dataParser/parsers/coerce/base.test.ts @@ -0,0 +1,92 @@ +import { DDate, DEither, DDataParser, type ExpectType } from "@scripts"; + +const dataParserCoerceMapperEntries = [...DDataParser.dataParserCoerceTransformerMapper.entries()]; + +function restoreDataParserCoerceMapper() { + DDataParser.dataParserCoerceTransformerMapper.clear(); + + dataParserCoerceMapperEntries.map( + ([kind, transformer]) => DDataParser.dataParserCoerceTransformerMapper.set(kind, transformer), + ); +} + +describe("DDataParser coercer", () => { + beforeEach(restoreDataParserCoerceMapper); + afterEach(restoreDataParserCoerceMapper); + + it("preserves output and extends input from eligible coerce parser", () => { + const schema = DDataParser.coercer(DDataParser.number()); + + type _CheckOut = ExpectType< + DDataParser.Output, + number, + "strict" + >; + + type _CheckIn = ExpectType< + DDataParser.Input, + string | number, + "strict" + >; + + expect(schema.parse("42")).toStrictEqual(DEither.success(42)); + }); + + it("runs checkers after inner parser succeeds", () => { + const schema = DDataParser.coercer(DDataParser.number()).addChecker( + DDataParser.checkerNumberMin(10), + ); + + expect(schema.parse("42")).toStrictEqual(DEither.success(42)); + expect(schema.parse("5")).toStrictEqual(DEither.error(expect.any(Object))); + }); + + it("coerces numeric boolean values through base coercer", () => { + const schema = DDataParser.coercer(DDataParser.boolean()); + + expect(schema.parse(1)).toStrictEqual(DEither.success(true)); + expect(schema.parse(0)).toStrictEqual(DEither.success(false)); + expect(schema.parse(2)).toStrictEqual(DEither.error(expect.any(Object))); + }); + + it("coerces ISO time strings through base coercer", () => { + const schema = DDataParser.coercer(DDataParser.time()); + + expect(schema.parse("01:02")).toStrictEqual( + DEither.success(DDate.createTimeOrThrow("time3720000+")), + ); + }); + + it("rejects ISO time strings when time creation fails through base coercer", () => { + const schema = DDataParser.coercer(DDataParser.time()); + const createTimeSpy = vi.spyOn(DDate, "createTime") + .mockReturnValueOnce(DEither.left("time-created-error", null) as never); + + try { + expect(schema.parse("01:02")).toStrictEqual(DEither.error(expect.any(Object))); + expect(createTimeSpy).toHaveBeenCalledWith({ value: "01:02" }); + } finally { + createTimeSpy.mockRestore(); + } + }); + + it("returns an error when no transformer is registered for inner parser kind", () => { + DDataParser.dataParserCoerceTransformerMapper.delete(DDataParser.numberKind); + + expect(DDataParser.coercer(DDataParser.number()).parse("42")).toStrictEqual( + DEither.error(expect.any(Object)), + ); + }); + + it("keeps coercion synchronous when inner parser is synchronous", () => { + expect(DDataParser.coercer(DDataParser.number()).isAsynchronous()).toBe(false); + }); + + it("transformer throw", () => { + DDataParser.dataParserCoerceTransformerMapper.set(DDataParser.numberKind, Number); + + expect(DDataParser.coerce.number().parse(Symbol("foo"))).toStrictEqual( + DEither.error(expect.any(Object)), + ); + }); +}); diff --git a/tests/dataParser/parsers/coerce/bigint.test.ts b/tests/dataParser/parsers/coerce/presets/bigint.test.ts similarity index 81% rename from tests/dataParser/parsers/coerce/bigint.test.ts rename to tests/dataParser/parsers/coerce/presets/bigint.test.ts index 0d2e74638..8117dd5d8 100644 --- a/tests/dataParser/parsers/coerce/bigint.test.ts +++ b/tests/dataParser/parsers/coerce/presets/bigint.test.ts @@ -24,6 +24,18 @@ describe("coerce.bigint", () => { }), ); + type _CheckOut = ExpectType< + DDataParser.Output, + bigint, + "strict" + >; + + type _CheckIn = ExpectType< + DDataParser.Input, + bigint, + "strict" + >; + void dataParser; }); diff --git a/tests/dataParser/parsers/coerce/boolean.test.ts b/tests/dataParser/parsers/coerce/presets/boolean.test.ts similarity index 69% rename from tests/dataParser/parsers/coerce/boolean.test.ts rename to tests/dataParser/parsers/coerce/presets/boolean.test.ts index 20ca9d89d..2aaa846ef 100644 --- a/tests/dataParser/parsers/coerce/boolean.test.ts +++ b/tests/dataParser/parsers/coerce/presets/boolean.test.ts @@ -24,6 +24,18 @@ describe("coerce.boolean", () => { }), ); + type _CheckOut = ExpectType< + DDataParser.Output, + boolean, + "strict" + >; + + type _CheckIn = ExpectType< + DDataParser.Input, + string | number | boolean, + "strict" + >; + void dataParser; }); @@ -37,4 +49,9 @@ describe("coerce.boolean", () => { DEither.error(expect.any(Object)), ); }); + + it("coerces number", () => { + expect(DDataParser.coerce.boolean().parse(1)).toStrictEqual(DEither.success(true)); + expect(DDataParser.coerce.boolean().parse(0)).toStrictEqual(DEither.success(false)); + }); }); diff --git a/tests/dataParser/parsers/coerce/date.test.ts b/tests/dataParser/parsers/coerce/presets/date.test.ts similarity index 70% rename from tests/dataParser/parsers/coerce/date.test.ts rename to tests/dataParser/parsers/coerce/presets/date.test.ts index e32079202..6fe9a6874 100644 --- a/tests/dataParser/parsers/coerce/date.test.ts +++ b/tests/dataParser/parsers/coerce/presets/date.test.ts @@ -24,15 +24,30 @@ describe("coerce.date", () => { }), ); + type _CheckOut = ExpectType< + DDataParser.Output, + DDate.TheDate, + "strict" + >; + + type _CheckIn = ExpectType< + DDataParser.Input, + DDate.TheDate | Date | string | number, + "strict" + >; + void dataParser; }); - it("coerces number, Date and TheDate inputs", () => { + it("coerces number, string, Date and TheDate inputs", () => { const parser = DDataParser.coerce.date(); const nativeDate = new Date("2021-01-01T00:00:00.000Z"); const existing = DDate.create("2021-01-01"); + const expected = DDate.createOrThrow("date1609459200000+"); - expect(parser.parse(nativeDate)).toStrictEqual(DEither.success(DDate.createOrThrow("date1609459200000+"))); + expect(parser.parse(1609459200000)).toStrictEqual(DEither.success(expected)); + expect(parser.parse("2021-01-01")).toStrictEqual(DEither.success(expected)); + expect(parser.parse(nativeDate)).toStrictEqual(DEither.success(expected)); expect(parser.parse(existing)).toStrictEqual(DEither.success(existing)); }); @@ -42,10 +57,12 @@ describe("coerce.date", () => { const invalidDate = new Date(tooHigh); const invalidTheDate = `date${DDate.maxTimestamp}+` as DDate.SerializedTheDate; const invalidType = true; + const invalidString = "not-a-date"; expect(parser.parse(tooHigh)).toStrictEqual(DEither.error(expect.any(Object))); expect(parser.parse(invalidDate)).toStrictEqual(DEither.error(expect.any(Object))); expect(parser.parse(invalidTheDate)).toStrictEqual(DEither.error(expect.any(Object))); expect(parser.parse(invalidType)).toStrictEqual(DEither.error(expect.any(Object))); + expect(parser.parse(invalidString)).toStrictEqual(DEither.error(expect.any(Object))); }); }); diff --git a/tests/dataParser/parsers/coerce/empty.test.ts b/tests/dataParser/parsers/coerce/presets/empty.test.ts similarity index 80% rename from tests/dataParser/parsers/coerce/empty.test.ts rename to tests/dataParser/parsers/coerce/presets/empty.test.ts index 5e1032ab0..e588d68b1 100644 --- a/tests/dataParser/parsers/coerce/empty.test.ts +++ b/tests/dataParser/parsers/coerce/presets/empty.test.ts @@ -24,6 +24,18 @@ describe("coerce.empty", () => { }), ); + type _CheckOut = ExpectType< + DDataParser.Output, + undefined, + "strict" + >; + + type _CheckIn = ExpectType< + DDataParser.Input, + "undefined" | undefined, + "strict" + >; + void dataParser; }); diff --git a/tests/dataParser/parsers/coerce/nil.test.ts b/tests/dataParser/parsers/coerce/presets/nil.test.ts similarity index 80% rename from tests/dataParser/parsers/coerce/nil.test.ts rename to tests/dataParser/parsers/coerce/presets/nil.test.ts index 7a9f53d64..1b560f436 100644 --- a/tests/dataParser/parsers/coerce/nil.test.ts +++ b/tests/dataParser/parsers/coerce/presets/nil.test.ts @@ -24,6 +24,18 @@ describe("coerce.nil", () => { }), ); + type _CheckOut = ExpectType< + DDataParser.Output, + null, + "strict" + >; + + type _CheckIn = ExpectType< + DDataParser.Input, + "null" | null, + "strict" + >; + void dataParser; }); diff --git a/tests/dataParser/parsers/coerce/number.test.ts b/tests/dataParser/parsers/coerce/presets/number.test.ts similarity index 69% rename from tests/dataParser/parsers/coerce/number.test.ts rename to tests/dataParser/parsers/coerce/presets/number.test.ts index cb9fbc81d..85ec40627 100644 --- a/tests/dataParser/parsers/coerce/number.test.ts +++ b/tests/dataParser/parsers/coerce/presets/number.test.ts @@ -24,6 +24,18 @@ describe("coerce.number", () => { }), ); + type _CheckOut = ExpectType< + DDataParser.Output, + number, + "strict" + >; + + type _CheckIn = ExpectType< + DDataParser.Input, + string | number, + "strict" + >; + void dataParser; }); @@ -36,4 +48,10 @@ describe("coerce.number", () => { DEither.error(expect.any(Object)), ); }); + + it("fails for symbol", () => { + expect(DDataParser.coerce.number({ errorMessage: "number.coerce" }).parse(Symbol("foo"))).toStrictEqual( + DEither.error(expect.any(Object)), + ); + }); }); diff --git a/tests/dataParser/parsers/coerce/string.test.ts b/tests/dataParser/parsers/coerce/presets/string.test.ts similarity index 81% rename from tests/dataParser/parsers/coerce/string.test.ts rename to tests/dataParser/parsers/coerce/presets/string.test.ts index bef6070d5..3e3ddf3c8 100644 --- a/tests/dataParser/parsers/coerce/string.test.ts +++ b/tests/dataParser/parsers/coerce/presets/string.test.ts @@ -24,6 +24,18 @@ describe("coerce.string", () => { }), ); + type _CheckOut = ExpectType< + DDataParser.Output, + string, + "strict" + >; + + type _CheckIn = ExpectType< + DDataParser.Input, + string, + "strict" + >; + void dataParser; }); diff --git a/tests/dataParser/parsers/coerce/time.test.ts b/tests/dataParser/parsers/coerce/presets/time.test.ts similarity index 73% rename from tests/dataParser/parsers/coerce/time.test.ts rename to tests/dataParser/parsers/coerce/presets/time.test.ts index 7eb352075..64055b4bf 100644 --- a/tests/dataParser/parsers/coerce/time.test.ts +++ b/tests/dataParser/parsers/coerce/presets/time.test.ts @@ -24,16 +24,31 @@ describe("coerce.time", () => { }), ); + type _CheckOut = ExpectType< + DDataParser.Output, + DDate.TheTime, + "strict" + >; + + type _CheckIn = ExpectType< + DDataParser.Input, + DDate.TheTime | string | number, + "strict" + >; + void dataParser; }); - it("coerces number, TheTime and ISO time inputs", () => { + it("coerces number, serialized string, ISO time string and TheTime inputs", () => { const parser = DDataParser.coerce.time(); const existing = DDate.createTime(1, "minute"); + const serialized = "time3720000+" as DDate.SerializedTheTime; + const expected = DDate.createTimeOrThrow(serialized); expect(parser.parse(1)).toStrictEqual((DEither.success(DDate.createTime(1, "millisecond")))); expect(parser.parse(-1)).toStrictEqual(DEither.success(DDate.createTime(-1, "millisecond"))); - expect(parser.parse("01:02")).toStrictEqual(DEither.success(DDate.createTimeOrThrow("time3720000+"))); + expect(parser.parse(serialized)).toStrictEqual(DEither.success(expected)); + expect(parser.parse("01:02")).toStrictEqual(DEither.success(expected)); expect(parser.parse(existing)).toStrictEqual(DEither.success(existing)); }); From dda940d2fe348c689ace7e4373dfab8a2bccf20c Mon Sep 17 00:00:00 2001 From: ZeRiix Date: Wed, 8 Jul 2026 11:38:28 +0200 Subject: [PATCH 2/8] feat(94): memoize transformer resolution at construction --- scripts/dataParser/parsers/coerce/base.ts | 16 +++++++++------- tests/dataParser/parsers/coerce/base.test.ts | 13 +++++++++++++ 2 files changed, 22 insertions(+), 7 deletions(-) diff --git a/scripts/dataParser/parsers/coerce/base.ts b/scripts/dataParser/parsers/coerce/base.ts index 1b19efb42..d5b9fd15c 100644 --- a/scripts/dataParser/parsers/coerce/base.ts +++ b/scripts/dataParser/parsers/coerce/base.ts @@ -1,4 +1,4 @@ -import { detachObjectMethod, type ComputedTypeError, type FixDeepFunctionInfer, type IsNever, type KindHandler, type NeverCoalescing } from "@scripts/common"; +import { detachObjectMethod, type Memoized, type ComputedTypeError, type FixDeepFunctionInfer, type IsNever, type KindHandler, type NeverCoalescing, memo } from "@scripts/common"; import { DataParserBase, type DataParser, type DataParserDefinition } from "../../base"; import { createDataParserKind } from "../../kind"; import { addIssue, type DataParserError } from "../../error"; @@ -203,6 +203,12 @@ export class DataParserCoerce< GenericDefinition > > { + readonly #transformer: Memoized = memo( + () => dataParserCoerceTransformerMapper.get( + this.definition.inner.classConstructor.specificKindHandler, + ), + ); + public get classConstructor(): typeof DataParserCoerce { return this.checkConstructor(DataParserCoerce); } @@ -232,11 +238,7 @@ export class DataParserCoerce< data: unknown, error: DataParserError, ): unknown { - const transformer = dataParserCoerceTransformerMapper.get( - self.definition.inner.classConstructor.specificKindHandler, - ); - - if (!transformer) { + if (!self.#transformer.value) { return addIssue( error, "supported coerce data parser", @@ -247,7 +249,7 @@ export class DataParserCoerce< } try { - return self.definition.inner.exec(transformer(data), error); + return self.definition.inner.exec(self.#transformer.value(data), error); } catch (catchError) { return addIssue( error, diff --git a/tests/dataParser/parsers/coerce/base.test.ts b/tests/dataParser/parsers/coerce/base.test.ts index b6e2bffa4..157a92564 100644 --- a/tests/dataParser/parsers/coerce/base.test.ts +++ b/tests/dataParser/parsers/coerce/base.test.ts @@ -78,6 +78,19 @@ describe("DDataParser coercer", () => { ); }); + it("reuses the transformer resolved during first parse", () => { + const schema = DDataParser.coercer(DDataParser.number()); + + expect(schema.parse("42")).toStrictEqual(DEither.success(42)); + + DDataParser.dataParserCoerceTransformerMapper.delete(DDataParser.numberKind); + + expect(schema.parse("43")).toStrictEqual(DEither.success(43)); + expect(DDataParser.coercer(DDataParser.number()).parse("44")).toStrictEqual( + DEither.error(expect.any(Object)), + ); + }); + it("keeps coercion synchronous when inner parser is synchronous", () => { expect(DDataParser.coercer(DDataParser.number()).isAsynchronous()).toBe(false); }); From 899d2f39000cc37a143339e0dab3d448f57965ff Mon Sep 17 00:00:00 2001 From: ZeRiix Date: Wed, 8 Jul 2026 15:25:15 +0200 Subject: [PATCH 3/8] fix(94): coercer architecture --- integration/correctExport.ts | 2 +- package.json | 6 +- scripts/dataParser/parsers/coerce/base.ts | 309 ------------------ .../parsers/coerce/{presets => }/bigint.ts | 9 +- .../parsers/coerce/{presets => }/boolean.ts | 9 +- .../parsers/coerce/{presets => }/date.ts | 9 +- .../parsers/coerce/{presets => }/empty.ts | 5 +- scripts/dataParser/parsers/coerce/index.ts | 11 +- .../parsers/coerce/{presets => }/nil.ts | 7 +- .../parsers/coerce/{presets => }/number.ts | 7 +- .../parsers/coerce/{presets => }/string.ts | 7 +- .../parsers/coerce/templateLiteral.ts | 26 ++ .../parsers/coerce/{presets => }/time.ts | 7 +- scripts/dataParser/parsers/coercer/index.ts | 204 ++++++++++++ .../parsers/coercer/transformers/bigint.ts | 7 + .../parsers/coercer/transformers/boolean.ts | 14 + .../parsers/coercer/transformers/date.ts | 20 ++ .../parsers/coercer/transformers/empty.ts | 5 + .../presets => coercer/transformers}/index.ts | 3 +- .../parsers/coercer/transformers/nil.ts | 5 + .../parsers/coercer/transformers/number.ts | 7 + .../parsers/coercer/transformers/string.ts | 7 + .../coercer/transformers/templateLiteral.ts | 11 + .../parsers/coercer/transformers/time.ts | 17 + .../dataParser/parsers/coercer/types/index.ts | 1 + .../parsers/coercer/types/transformer.ts | 1 + scripts/dataParser/parsers/index.ts | 3 +- tests/dataParser/parsers/coerce/base.test.ts | 105 ------ .../coerce/{presets => }/bigint.test.ts | 2 +- .../coerce/{presets => }/boolean.test.ts | 0 .../parsers/coerce/{presets => }/date.test.ts | 0 .../coerce/{presets => }/empty.test.ts | 0 .../parsers/coerce/{presets => }/nil.test.ts | 0 .../coerce/{presets => }/number.test.ts | 2 +- .../coerce/{presets => }/string.test.ts | 2 +- .../parsers/coerce/templateLiteral.test.ts | 63 ++++ .../parsers/coerce/{presets => }/time.test.ts | 0 .../dataParser/parsers/coercer/index.test.ts | 190 +++++++++++ 38 files changed, 624 insertions(+), 459 deletions(-) delete mode 100644 scripts/dataParser/parsers/coerce/base.ts rename scripts/dataParser/parsers/coerce/{presets => }/bigint.ts (55%) rename scripts/dataParser/parsers/coerce/{presets => }/boolean.ts (55%) rename scripts/dataParser/parsers/coerce/{presets => }/date.ts (55%) rename scripts/dataParser/parsers/coerce/{presets => }/empty.ts (78%) rename scripts/dataParser/parsers/coerce/{presets => }/nil.ts (67%) rename scripts/dataParser/parsers/coerce/{presets => }/number.ts (68%) rename scripts/dataParser/parsers/coerce/{presets => }/string.ts (68%) create mode 100644 scripts/dataParser/parsers/coerce/templateLiteral.ts rename scripts/dataParser/parsers/coerce/{presets => }/time.ts (67%) create mode 100644 scripts/dataParser/parsers/coercer/index.ts create mode 100644 scripts/dataParser/parsers/coercer/transformers/bigint.ts create mode 100644 scripts/dataParser/parsers/coercer/transformers/boolean.ts create mode 100644 scripts/dataParser/parsers/coercer/transformers/date.ts create mode 100644 scripts/dataParser/parsers/coercer/transformers/empty.ts rename scripts/dataParser/parsers/{coerce/presets => coercer/transformers}/index.ts (85%) create mode 100644 scripts/dataParser/parsers/coercer/transformers/nil.ts create mode 100644 scripts/dataParser/parsers/coercer/transformers/number.ts create mode 100644 scripts/dataParser/parsers/coercer/transformers/string.ts create mode 100644 scripts/dataParser/parsers/coercer/transformers/templateLiteral.ts create mode 100644 scripts/dataParser/parsers/coercer/transformers/time.ts create mode 100644 scripts/dataParser/parsers/coercer/types/index.ts create mode 100644 scripts/dataParser/parsers/coercer/types/transformer.ts delete mode 100644 tests/dataParser/parsers/coerce/base.test.ts rename tests/dataParser/parsers/coerce/{presets => }/bigint.test.ts (96%) rename tests/dataParser/parsers/coerce/{presets => }/boolean.test.ts (100%) rename tests/dataParser/parsers/coerce/{presets => }/date.test.ts (100%) rename tests/dataParser/parsers/coerce/{presets => }/empty.test.ts (100%) rename tests/dataParser/parsers/coerce/{presets => }/nil.test.ts (100%) rename tests/dataParser/parsers/coerce/{presets => }/number.test.ts (96%) rename tests/dataParser/parsers/coerce/{presets => }/string.test.ts (94%) create mode 100644 tests/dataParser/parsers/coerce/templateLiteral.test.ts rename tests/dataParser/parsers/coerce/{presets => }/time.test.ts (100%) create mode 100644 tests/dataParser/parsers/coercer/index.test.ts diff --git a/integration/correctExport.ts b/integration/correctExport.ts index 0c6d68b03..940a77cd8 100644 --- a/integration/correctExport.ts +++ b/integration/correctExport.ts @@ -27,7 +27,7 @@ type Check2 = All.ExpectType< >; type Check22 = All.ExpectType< - typeof All.DDataParserCoerce.coerce, + typeof All.DDataParserCoerce, typeof DDPC, "strict" >; diff --git a/package.json b/package.json index 3ab460433..f7b9b1130 100644 --- a/package.json +++ b/package.json @@ -55,9 +55,9 @@ "types": "./dist/dataParser/index.d.ts" }, "./dataParserCoerce": { - "import": "./dist/dataParser/parsers/coerce/presets/index.mjs", - "require": "./dist/dataParser/parsers/coerce/presets/index.cjs", - "types": "./dist/dataParser/parsers/coerce/presets/index.d.ts" + "import": "./dist/dataParser/parsers/coerce/index.mjs", + "require": "./dist/dataParser/parsers/coerce/index.cjs", + "types": "./dist/dataParser/parsers/coerce/index.d.ts" }, "./dataParserExtended": { "import": "./dist/dataParser/extended/index.mjs", diff --git a/scripts/dataParser/parsers/coerce/base.ts b/scripts/dataParser/parsers/coerce/base.ts deleted file mode 100644 index d5b9fd15c..000000000 --- a/scripts/dataParser/parsers/coerce/base.ts +++ /dev/null @@ -1,309 +0,0 @@ -import { detachObjectMethod, type Memoized, type ComputedTypeError, type FixDeepFunctionInfer, type IsNever, type KindHandler, type NeverCoalescing, memo } from "@scripts/common"; -import { DataParserBase, type DataParser, type DataParserDefinition } from "../../base"; -import { createDataParserKind } from "../../kind"; -import { addIssue, type DataParserError } from "../../error"; -import { type DataParserChecker } from "../../baseChecker"; -import type { ApplyRefinementOfDefinition, AddCheckersToDefinition, GetEligibleChecker, Input, MergeDefinition, Output, PrepareDataParserDefinition } from "../../types"; -import { bigIntKind, type DataParserBigInt } from "../bigint"; -import { booleanKind, type DataParserBoolean } from "../boolean"; -import { dateKind, type DataParserDate } from "../date"; -import { emptyKind, type DataParserEmpty } from "../empty"; -import { nilKind, type DataParserNil } from "../nil"; -import { numberKind, type DataParserNumber } from "../number"; -import { stringKind, type DataParserString } from "../string"; -import { timeKind, type DataParserTime } from "../time"; -import * as DDate from "@scripts/date"; -import * as DEither from "@scripts/either"; - -export type DataParserCoerceTransformer = (data: unknown) => unknown; - -export const dataParserCoerceTransformerMapper = new Map([ - [ - bigIntKind, - (data) => { - try { - return BigInt(data as never); - } catch { - return data; - } - }, - ], - [ - booleanKind, - (data) => { - if (typeof data === "string") { - const lower = data.trim().toLowerCase(); - - if (lower === "true" || lower === "false") { - return lower === "true"; - } - } else if (typeof data === "number" && (data === 0 || data === 1)) { - return data === 1; - } - - return data; - }, - ], - [ - emptyKind, - (data) => data === "undefined" - ? undefined - : data, - ], - [ - nilKind, - (data) => data === "null" - ? null - : data, - ], - [ - numberKind, - (data) => { - try { - return Number(data); - } catch { - return data; - } - }, - ], - [ - stringKind, - (data) => { - try { - return String(data); - } catch { - return data; - } - }, - ], - [ - timeKind, - (data) => { - if ( - typeof data === "string" - && DDate.isoTimeRegex.test(data) - ) { - const result = DDate.createTime({ value: data }); - - if (DEither.isRight(result)) { - return DEither.unwrapRight(result); - } - } - - return data; - }, - ], - [ - dateKind, - (data) => { - if ( - typeof data === "number" - && DDate.isSafeTimestamp(data) - ) { - return DDate.TheDate.new(data); - } - - if (typeof data === "string") { - const date = new Date(data); - const timestamp = date.getTime(); - if (DDate.isSafeTimestamp(timestamp)) { - return DDate.TheDate.new(timestamp); - } - } - - return data; - }, - ], -]); - -export interface EligibleDataParserCoerce< - GenericDataParser extends DataParser, -> { - bigint: GenericDataParser extends DataParserBigInt - ? Input - : never; - boolean: GenericDataParser extends DataParserBoolean - ? string | number | boolean - : never; - empty: GenericDataParser extends DataParserEmpty - ? "undefined" | Input - : never; - nil: GenericDataParser extends DataParserNil - ? "null" | Input - : never; - number: GenericDataParser extends DataParserNumber - ? string | number - : never; - string: GenericDataParser extends DataParserString - ? Input - : never; - date: GenericDataParser extends DataParserDate - ? string | number | Input - : never; - time: GenericDataParser extends DataParserTime - ? string | Input - : never; -} - -export interface EligibleDataParserCoerceOverride< - GenericDataParser extends DataParser, -> {} - -export type GetEligibleDataParserCoerce< - GenericDataParser extends DataParser, -> = ( - & Omit< - EligibleDataParserCoerce, - keyof EligibleDataParserCoerceOverride - > - & EligibleDataParserCoerceOverride -) extends infer InferredResult - ? InferredResult[keyof InferredResult] - : never; - -type MustBeEligibleDataParserCoerce< - GenericDataParser extends DataParser, -> = IsNever> extends true - ? ComputedTypeError<"This data parser is not eligible for coercion."> - : GenericDataParser; - -export type DataParserCoerceCheckers< - GenericInput extends unknown = unknown, -> = GetEligibleChecker; - -export type DataParserCoerceInput< - GenericDataParser extends DataParser, -> = ( - | Input - | GetEligibleDataParserCoerce -); - -export interface DataParserDefinitionCoerce< - GenericOutput extends unknown = unknown, -> extends DataParserDefinition< - DataParserCoerceCheckers - > { - readonly inner: DataParser; -} - -export const coerceKind = createDataParserKind("coerce"); - -export class DataParserCoerce< - GenericDefinition extends DataParserDefinitionCoerce = DataParserDefinitionCoerce, -> extends DataParserBase.init( - coerceKind, - )< - GenericDefinition, - ApplyRefinementOfDefinition< - Output, - GenericDefinition - >, - ApplyRefinementOfDefinition< - DataParserCoerceInput, - GenericDefinition - > - > { - readonly #transformer: Memoized = memo( - () => dataParserCoerceTransformerMapper.get( - this.definition.inner.classConstructor.specificKindHandler, - ), - ); - - public get classConstructor(): typeof DataParserCoerce { - return this.checkConstructor(DataParserCoerce); - } - - public declare addChecker: < - GenericChecker extends readonly [ - DataParserChecker>, - ...DataParserChecker>[], - ], - >( - ...args: FixDeepFunctionInfer< - readonly [ - DataParserChecker>, - ...DataParserChecker>[], - ], - GenericChecker - > - ) => DataParserCoerce< - AddCheckersToDefinition< - GenericDefinition, - GenericChecker - > - >; - - public static override execParse( - self: DataParserCoerce, - data: unknown, - error: DataParserError, - ): unknown { - if (!self.#transformer.value) { - return addIssue( - error, - "supported coerce data parser", - data, - self.definition.errorMessage, - self, - ); - } - - try { - return self.definition.inner.exec(self.#transformer.value(data), error); - } catch (catchError) { - return addIssue( - error, - "successful coerce transform", - catchError, - self.definition.errorMessage, - self, - ); - } - } - - public static override dataParserIsAsynchronous(self: DataParserCoerce) { - return self.definition.inner.isAsynchronous(); - } - - public static override prepareDefinition( - inner: DataParser, - definition?: Partial>, - ): DataParserDefinitionCoerce { - return { - ...definition, - inner, - checkers: definition?.checkers ?? [], - errorMessage: definition?.errorMessage, - }; - } - - public static override create< - GenericDataParser extends DataParser, - const GenericDefinition extends PrepareDataParserDefinition< - DataParserDefinitionCoerce< - Output - >, - "inner" - > = never, - >( - inner: GenericDataParser & MustBeEligibleDataParserCoerce, - definition?: FixDeepFunctionInfer< - PrepareDataParserDefinition< - DataParserDefinitionCoerce< - Output - >, - "inner" - >, - GenericDefinition - >, - ): DataParserCoerce< - MergeDefinition< - DataParserDefinitionCoerce, - NeverCoalescing & { inner: GenericDataParser } - > - > { - return new DataParserCoerce(this.prepareDefinition(inner, definition)) as never; - } -} - -export const coercer = detachObjectMethod(DataParserCoerce, "create"); diff --git a/scripts/dataParser/parsers/coerce/presets/bigint.ts b/scripts/dataParser/parsers/coerce/bigint.ts similarity index 55% rename from scripts/dataParser/parsers/coerce/presets/bigint.ts rename to scripts/dataParser/parsers/coerce/bigint.ts index 7986217cd..43695edab 100644 --- a/scripts/dataParser/parsers/coerce/presets/bigint.ts +++ b/scripts/dataParser/parsers/coerce/bigint.ts @@ -1,7 +1,6 @@ -import { type FixDeepFunctionInfer } from "@scripts/common"; -import { type PrepareDataParserDefinition } from "../../../types"; -import * as dataParsers from "../.."; -import { coercer } from "../base"; +import type { FixDeepFunctionInfer } from "@scripts/common"; +import type { PrepareDataParserDefinition } from "@scripts/dataParser/types"; +import * as dataParsers from ".."; export function bigint< const GenericDefinition extends PrepareDataParserDefinition< @@ -17,5 +16,5 @@ export function bigint< GenericDefinition >, ) { - return coercer(dataParsers.bigint(definition)); + return dataParsers.coercer(dataParsers.bigint(definition)); } diff --git a/scripts/dataParser/parsers/coerce/presets/boolean.ts b/scripts/dataParser/parsers/coerce/boolean.ts similarity index 55% rename from scripts/dataParser/parsers/coerce/presets/boolean.ts rename to scripts/dataParser/parsers/coerce/boolean.ts index 3fdcb8cfa..d633975bb 100644 --- a/scripts/dataParser/parsers/coerce/presets/boolean.ts +++ b/scripts/dataParser/parsers/coerce/boolean.ts @@ -1,7 +1,6 @@ -import { type FixDeepFunctionInfer } from "@scripts/common"; -import { type PrepareDataParserDefinition } from "../../../types"; -import * as dataParsers from "../.."; -import { coercer } from "../base"; +import type { FixDeepFunctionInfer } from "@scripts/common"; +import type { PrepareDataParserDefinition } from "@scripts/dataParser/types"; +import * as dataParsers from ".."; export function boolean< const GenericDefinition extends PrepareDataParserDefinition< @@ -17,5 +16,5 @@ export function boolean< GenericDefinition >, ) { - return coercer(dataParsers.boolean(definition)); + return dataParsers.coercer(dataParsers.boolean(definition)); } diff --git a/scripts/dataParser/parsers/coerce/presets/date.ts b/scripts/dataParser/parsers/coerce/date.ts similarity index 55% rename from scripts/dataParser/parsers/coerce/presets/date.ts rename to scripts/dataParser/parsers/coerce/date.ts index fc2237b72..1acc8b0cb 100644 --- a/scripts/dataParser/parsers/coerce/presets/date.ts +++ b/scripts/dataParser/parsers/coerce/date.ts @@ -1,7 +1,6 @@ -import { type FixDeepFunctionInfer } from "@scripts/common"; -import { type PrepareDataParserDefinition } from "../../../types"; -import * as dataParsers from "../.."; -import { coercer } from "../base"; +import type { FixDeepFunctionInfer } from "@scripts/common"; +import type { PrepareDataParserDefinition } from "@scripts/dataParser/types"; +import * as dataParsers from ".."; export function date< const GenericDefinition extends PrepareDataParserDefinition< @@ -17,5 +16,5 @@ export function date< GenericDefinition >, ) { - return coercer(dataParsers.date(definition)); + return dataParsers.coercer(dataParsers.date(definition)); } diff --git a/scripts/dataParser/parsers/coerce/presets/empty.ts b/scripts/dataParser/parsers/coerce/empty.ts similarity index 78% rename from scripts/dataParser/parsers/coerce/presets/empty.ts rename to scripts/dataParser/parsers/coerce/empty.ts index 777434522..4d28e8964 100644 --- a/scripts/dataParser/parsers/coerce/presets/empty.ts +++ b/scripts/dataParser/parsers/coerce/empty.ts @@ -1,7 +1,6 @@ import type { FixDeepFunctionInfer } from "@scripts/common"; import type { PrepareDataParserDefinition } from "@scripts/dataParser/types"; -import * as dataParsers from "../.."; -import { coercer } from "../base"; +import * as dataParsers from ".."; export function empty< const GenericDefinition extends PrepareDataParserDefinition< @@ -17,5 +16,5 @@ export function empty< GenericDefinition >, ) { - return coercer(dataParsers.empty(definition)); + return dataParsers.coercer(dataParsers.empty(definition)); } diff --git a/scripts/dataParser/parsers/coerce/index.ts b/scripts/dataParser/parsers/coerce/index.ts index 7eb92c6bd..72ee38a78 100644 --- a/scripts/dataParser/parsers/coerce/index.ts +++ b/scripts/dataParser/parsers/coerce/index.ts @@ -1,2 +1,9 @@ -export * from "./base"; -export * as coerce from "./presets"; +export * from "./bigint"; +export * from "./boolean"; +export * from "./date"; +export * from "./time"; +export * from "./empty"; +export * from "./nil"; +export * from "./number"; +export * from "./string"; +export * from "./templateLiteral"; diff --git a/scripts/dataParser/parsers/coerce/presets/nil.ts b/scripts/dataParser/parsers/coerce/nil.ts similarity index 67% rename from scripts/dataParser/parsers/coerce/presets/nil.ts rename to scripts/dataParser/parsers/coerce/nil.ts index f1e94072f..b9e240762 100644 --- a/scripts/dataParser/parsers/coerce/presets/nil.ts +++ b/scripts/dataParser/parsers/coerce/nil.ts @@ -1,7 +1,6 @@ -import { type FixDeepFunctionInfer } from "@scripts/common"; +import type { FixDeepFunctionInfer } from "@scripts/common"; import type { PrepareDataParserDefinition } from "@scripts/dataParser/types"; -import * as dataParsers from "../.."; -import { coercer } from "../base"; +import * as dataParsers from ".."; export function nil< const GenericDefinition extends PrepareDataParserDefinition< @@ -17,5 +16,5 @@ export function nil< GenericDefinition >, ) { - return coercer(dataParsers.nil(definition)); + return dataParsers.coercer(dataParsers.nil(definition)); } diff --git a/scripts/dataParser/parsers/coerce/presets/number.ts b/scripts/dataParser/parsers/coerce/number.ts similarity index 68% rename from scripts/dataParser/parsers/coerce/presets/number.ts rename to scripts/dataParser/parsers/coerce/number.ts index b39aae943..5ba004e29 100644 --- a/scripts/dataParser/parsers/coerce/presets/number.ts +++ b/scripts/dataParser/parsers/coerce/number.ts @@ -1,7 +1,6 @@ -import { type FixDeepFunctionInfer } from "@scripts/common"; +import type { FixDeepFunctionInfer } from "@scripts/common"; import type { PrepareDataParserDefinition } from "@scripts/dataParser/types"; -import * as dataParsers from "../.."; -import { coercer } from "../base"; +import * as dataParsers from ".."; export function number< const GenericDefinition extends PrepareDataParserDefinition< @@ -17,5 +16,5 @@ export function number< GenericDefinition >, ) { - return coercer(dataParsers.number(definition)); + return dataParsers.coercer(dataParsers.number(definition)); } diff --git a/scripts/dataParser/parsers/coerce/presets/string.ts b/scripts/dataParser/parsers/coerce/string.ts similarity index 68% rename from scripts/dataParser/parsers/coerce/presets/string.ts rename to scripts/dataParser/parsers/coerce/string.ts index 37b35f599..e467ee1ee 100644 --- a/scripts/dataParser/parsers/coerce/presets/string.ts +++ b/scripts/dataParser/parsers/coerce/string.ts @@ -1,7 +1,6 @@ -import { type FixDeepFunctionInfer } from "@scripts/common"; +import type { FixDeepFunctionInfer } from "@scripts/common"; import type { PrepareDataParserDefinition } from "@scripts/dataParser/types"; -import * as dataParsers from "../.."; -import { coercer } from "../base"; +import * as dataParsers from ".."; export function string< const GenericDefinition extends PrepareDataParserDefinition< @@ -17,5 +16,5 @@ export function string< GenericDefinition >, ) { - return coercer(dataParsers.string(definition)); + return dataParsers.coercer(dataParsers.string(definition)); } diff --git a/scripts/dataParser/parsers/coerce/templateLiteral.ts b/scripts/dataParser/parsers/coerce/templateLiteral.ts new file mode 100644 index 000000000..26bbb3020 --- /dev/null +++ b/scripts/dataParser/parsers/coerce/templateLiteral.ts @@ -0,0 +1,26 @@ +import type { FixDeepFunctionInfer } from "@scripts/common"; +import type { PrepareDataParserDefinition } from "@scripts/dataParser/types"; +import * as dataParsers from ".."; + +export function templateLiteral< + const GenericTemplate extends dataParsers.TemplateLiteralShape, + const GenericDefinition extends PrepareDataParserDefinition< + dataParsers.DataParserDefinitionTemplateLiteral< + dataParsers.TemplateLiteralShapeOutput + >, + "template" | "pattern" + > = never, +>( + template: GenericTemplate, + definition?: FixDeepFunctionInfer< + PrepareDataParserDefinition< + dataParsers.DataParserDefinitionTemplateLiteral< + dataParsers.TemplateLiteralShapeOutput + >, + "template" | "pattern" + >, + GenericDefinition + >, +) { + return dataParsers.coercer(dataParsers.templateLiteral(template, definition)); +} diff --git a/scripts/dataParser/parsers/coerce/presets/time.ts b/scripts/dataParser/parsers/coerce/time.ts similarity index 67% rename from scripts/dataParser/parsers/coerce/presets/time.ts rename to scripts/dataParser/parsers/coerce/time.ts index 164a72215..90a059341 100644 --- a/scripts/dataParser/parsers/coerce/presets/time.ts +++ b/scripts/dataParser/parsers/coerce/time.ts @@ -1,7 +1,6 @@ -import { type FixDeepFunctionInfer } from "@scripts/common"; +import type { FixDeepFunctionInfer } from "@scripts/common"; import type { PrepareDataParserDefinition } from "@scripts/dataParser/types"; -import * as dataParsers from "../.."; -import { coercer } from "../base"; +import * as dataParsers from ".."; export function time< const GenericDefinition extends PrepareDataParserDefinition< @@ -17,5 +16,5 @@ export function time< GenericDefinition >, ) { - return coercer(dataParsers.time(definition)); + return dataParsers.coercer(dataParsers.time(definition)); } diff --git a/scripts/dataParser/parsers/coercer/index.ts b/scripts/dataParser/parsers/coercer/index.ts new file mode 100644 index 000000000..092ccd8a8 --- /dev/null +++ b/scripts/dataParser/parsers/coercer/index.ts @@ -0,0 +1,204 @@ +import { callThen, detachObjectMethod, type FixDeepFunctionInfer, type Memoized, memo, type NeverCoalescing, type KindHandler, forward } from "@scripts/common"; +import { DataParserBase, type DataParser, type DataParserDefinition } from "../../base"; +import { createDataParserKind } from "../../kind"; +import { addIssue, type DataParserError } from "../../error"; +import type { DataParserChecker } from "../../baseChecker"; +import type { ApplyRefinementOfDefinition, AddCheckersToDefinition, GetEligibleChecker, Input, MergeDefinition, Output, PrepareDataParserDefinition } from "../../types"; +import type { DataParserCoercerTransformer } from "./types"; +import * as coercerTransformers from "./transformers"; +import * as dataParsers from ".."; + +export * from "./types"; + +export interface ComputeInputDataParserCoercer< + GenericDataParser extends DataParser, +> { + bigint: GenericDataParser extends dataParsers.DataParserBigInt + ? string | number | boolean + : never; + boolean: GenericDataParser extends dataParsers.DataParserBoolean + ? string | number + : never; + empty: GenericDataParser extends dataParsers.DataParserEmpty + ? "undefined" + : never; + nil: GenericDataParser extends dataParsers.DataParserNil + ? "null" + : never; + number: GenericDataParser extends dataParsers.DataParserNumber + ? string | bigint | boolean | null + : never; + string: GenericDataParser extends dataParsers.DataParserString + ? number | bigint | boolean | symbol | null | undefined + : never; + date: GenericDataParser extends dataParsers.DataParserDate + ? string | number + : never; + time: GenericDataParser extends dataParsers.DataParserTime + ? string + : never; + templateLiteral: GenericDataParser extends dataParsers.DataParserTemplateLiteral + ? number | bigint | boolean + : never; +} + +export type DataParserCoercerInput< + GenericDataParser extends DataParser, +> = ComputeInputDataParserCoercer extends infer InferredCoercer + ? ( + | Input + | InferredCoercer[keyof InferredCoercer] + ) + : never; + +export type DataParserCoercerCheckers< + GenericInput extends unknown = unknown, +> = GetEligibleChecker; + +export interface DataParserDefinitionCoercer< + GenericOutput extends unknown = unknown, +> extends DataParserDefinition< + DataParserCoercerCheckers + > { + readonly inner: DataParser; + readonly transformer: Memoized; +} + +export const coercerKind = createDataParserKind("coercer"); + +export class DataParserCoercer< + GenericDefinition extends DataParserDefinitionCoercer = DataParserDefinitionCoercer, +> extends DataParserBase.init( + coercerKind, + )< + GenericDefinition, + ApplyRefinementOfDefinition< + Output, + GenericDefinition + >, + ApplyRefinementOfDefinition< + DataParserCoercerInput, + GenericDefinition + > + > { + public get classConstructor() { + return this.checkConstructor(DataParserCoercer); + } + + public declare addChecker: < + GenericChecker extends readonly [ + DataParserChecker>, + ...DataParserChecker>[], + ], + >( + ...args: FixDeepFunctionInfer< + readonly [ + DataParserChecker>, + ...DataParserChecker>[], + ], + GenericChecker + > + ) => DataParserCoercer< + AddCheckersToDefinition< + GenericDefinition, + GenericChecker + > + >; + + public static override execParse( + self: DataParserCoercer, + data: unknown, + error: DataParserError, + ): unknown { + return callThen( + data, + (resolvedData) => { + const transformedData = self.definition.transformer.value + ? self.definition.transformer.value(resolvedData) + : resolvedData; + + return callThen( + self.definition.inner.exec(transformedData, error), + forward, + (catchError) => addIssue( + error, + "successful coerce result", + catchError, + self.definition.errorMessage, + self, + ), + ); + }, + (catchError) => addIssue( + error, + "successful coerce result", + catchError, + self.definition.errorMessage, + self, + ), + ); + } + + public static override dataParserIsAsynchronous(self: DataParserCoercer) { + return self.definition.inner.isAsynchronous(); + } + + public static transformers = new Map([ + [dataParsers.numberKind, coercerTransformers.numberTransformer], + [dataParsers.stringKind, coercerTransformers.stringTransformer], + [dataParsers.booleanKind, coercerTransformers.booleanTransformer], + [dataParsers.dateKind, coercerTransformers.dateTransformer], + [dataParsers.timeKind, coercerTransformers.timeTransformer], + [dataParsers.bigIntKind, coercerTransformers.bigintTransformer], + [dataParsers.emptyKind, coercerTransformers.emptyTransformer], + [dataParsers.nilKind, coercerTransformers.nilTransformer], + [dataParsers.templateLiteralKind, coercerTransformers.templateLiteralTransformer], + ]); + + public static override prepareDefinition( + inner: DataParser, + definition?: Partial>, + ): DataParserDefinitionCoercer { + return { + ...definition, + inner, + transformer: memo( + () => DataParserCoercer.transformers.get( + inner.classConstructor.specificKindHandler, + ), + ), + checkers: definition?.checkers ?? [], + errorMessage: definition?.errorMessage, + }; + } + + public static override create< + GenericDataParser extends DataParser, + const GenericDefinition extends PrepareDataParserDefinition< + DataParserDefinitionCoercer< + Output + >, + "inner" + > = never, + >( + inner: GenericDataParser, + definition?: FixDeepFunctionInfer< + PrepareDataParserDefinition< + DataParserDefinitionCoercer< + Output + >, + "inner" + >, + GenericDefinition + >, + ): DataParserCoercer< + MergeDefinition< + DataParserDefinitionCoercer, + NeverCoalescing & { inner: GenericDataParser } + > + > { + return new DataParserCoercer(this.prepareDefinition(inner, definition)) as never; + } +} + +export const coercer = detachObjectMethod(DataParserCoercer, "create"); diff --git a/scripts/dataParser/parsers/coercer/transformers/bigint.ts b/scripts/dataParser/parsers/coercer/transformers/bigint.ts new file mode 100644 index 000000000..52f6f4e7b --- /dev/null +++ b/scripts/dataParser/parsers/coercer/transformers/bigint.ts @@ -0,0 +1,7 @@ +export function bigintTransformer(data: unknown) { + try { + return BigInt(data as never); + } catch { + return data; + } +} diff --git a/scripts/dataParser/parsers/coercer/transformers/boolean.ts b/scripts/dataParser/parsers/coercer/transformers/boolean.ts new file mode 100644 index 000000000..6f1d8654b --- /dev/null +++ b/scripts/dataParser/parsers/coercer/transformers/boolean.ts @@ -0,0 +1,14 @@ +export function booleanTransformer(data: unknown) { + if (typeof data === "string") { + const lower = data.trim().toLowerCase(); + + if (lower === "true" || lower === "false") { + return lower === "true"; + } + } else if (typeof data === "number" && (data === 0 || data === 1)) { + return data === 1; + } + + return data; +} + diff --git a/scripts/dataParser/parsers/coercer/transformers/date.ts b/scripts/dataParser/parsers/coercer/transformers/date.ts new file mode 100644 index 000000000..9ae366bba --- /dev/null +++ b/scripts/dataParser/parsers/coercer/transformers/date.ts @@ -0,0 +1,20 @@ +import * as DDate from "@scripts/date"; + +export function dateTransformer(data: unknown) { + if ( + typeof data === "number" + && DDate.isSafeTimestamp(data) + ) { + return DDate.TheDate.new(data); + } + + if (typeof data === "string") { + const date = new Date(data); + const timestamp = date.getTime(); + if (DDate.isSafeTimestamp(timestamp)) { + return DDate.TheDate.new(timestamp); + } + } + + return data; +} diff --git a/scripts/dataParser/parsers/coercer/transformers/empty.ts b/scripts/dataParser/parsers/coercer/transformers/empty.ts new file mode 100644 index 000000000..79bb827fe --- /dev/null +++ b/scripts/dataParser/parsers/coercer/transformers/empty.ts @@ -0,0 +1,5 @@ +export function emptyTransformer(data: unknown) { + return data === "undefined" + ? undefined + : data; +} diff --git a/scripts/dataParser/parsers/coerce/presets/index.ts b/scripts/dataParser/parsers/coercer/transformers/index.ts similarity index 85% rename from scripts/dataParser/parsers/coerce/presets/index.ts rename to scripts/dataParser/parsers/coercer/transformers/index.ts index 6e8405fe3..f6d534bfa 100644 --- a/scripts/dataParser/parsers/coerce/presets/index.ts +++ b/scripts/dataParser/parsers/coercer/transformers/index.ts @@ -1,8 +1,9 @@ export * from "./bigint"; export * from "./boolean"; export * from "./date"; -export * from "./time"; export * from "./empty"; export * from "./nil"; export * from "./number"; export * from "./string"; +export * from "./time"; +export * from "./templateLiteral"; diff --git a/scripts/dataParser/parsers/coercer/transformers/nil.ts b/scripts/dataParser/parsers/coercer/transformers/nil.ts new file mode 100644 index 000000000..99845836e --- /dev/null +++ b/scripts/dataParser/parsers/coercer/transformers/nil.ts @@ -0,0 +1,5 @@ +export function nilTransformer(data: unknown) { + return data === "null" + ? null + : data; +} diff --git a/scripts/dataParser/parsers/coercer/transformers/number.ts b/scripts/dataParser/parsers/coercer/transformers/number.ts new file mode 100644 index 000000000..8b91c5d64 --- /dev/null +++ b/scripts/dataParser/parsers/coercer/transformers/number.ts @@ -0,0 +1,7 @@ +export function numberTransformer(data: unknown) { + try { + return Number(data); + } catch { + return data; + } +} diff --git a/scripts/dataParser/parsers/coercer/transformers/string.ts b/scripts/dataParser/parsers/coercer/transformers/string.ts new file mode 100644 index 000000000..0f12e2bb7 --- /dev/null +++ b/scripts/dataParser/parsers/coercer/transformers/string.ts @@ -0,0 +1,7 @@ +export function stringTransformer(data: unknown) { + try { + return String(data); + } catch { + return data; + } +} diff --git a/scripts/dataParser/parsers/coercer/transformers/templateLiteral.ts b/scripts/dataParser/parsers/coercer/transformers/templateLiteral.ts new file mode 100644 index 000000000..eab685fb0 --- /dev/null +++ b/scripts/dataParser/parsers/coercer/transformers/templateLiteral.ts @@ -0,0 +1,11 @@ +export function templateLiteralTransformer(data: unknown) { + if ( + typeof data === "number" + || typeof data === "bigint" + || typeof data === "boolean" + ) { + return String(data); + } + + return data; +} diff --git a/scripts/dataParser/parsers/coercer/transformers/time.ts b/scripts/dataParser/parsers/coercer/transformers/time.ts new file mode 100644 index 000000000..a66c92b3e --- /dev/null +++ b/scripts/dataParser/parsers/coercer/transformers/time.ts @@ -0,0 +1,17 @@ +import * as DDate from "@scripts/date"; +import * as DEither from "@scripts/either"; + +export function timeTransformer(data: unknown) { + if ( + typeof data === "string" + && DDate.isoTimeRegex.test(data) + ) { + const result = DDate.createTime({ value: data }); + + if (DEither.isRight(result)) { + return DEither.unwrapRight(result); + } + } + + return data; +} diff --git a/scripts/dataParser/parsers/coercer/types/index.ts b/scripts/dataParser/parsers/coercer/types/index.ts new file mode 100644 index 000000000..ba1ba509e --- /dev/null +++ b/scripts/dataParser/parsers/coercer/types/index.ts @@ -0,0 +1 @@ +export * from "./transformer"; diff --git a/scripts/dataParser/parsers/coercer/types/transformer.ts b/scripts/dataParser/parsers/coercer/types/transformer.ts new file mode 100644 index 000000000..c3418512f --- /dev/null +++ b/scripts/dataParser/parsers/coercer/types/transformer.ts @@ -0,0 +1 @@ +export type DataParserCoercerTransformer = (data: unknown) => unknown; diff --git a/scripts/dataParser/parsers/index.ts b/scripts/dataParser/parsers/index.ts index c7248d59d..7356c6d70 100644 --- a/scripts/dataParser/parsers/index.ts +++ b/scripts/dataParser/parsers/index.ts @@ -1,4 +1,3 @@ -export * from "./coerce"; export * from "./string"; export * from "./number"; export * from "./array"; @@ -23,3 +22,5 @@ export * from "./tuple"; export * from "./union"; export * from "./unknown"; export * from "./errorHandler"; +export * from "./coercer"; +export * as coerce from "./coerce"; diff --git a/tests/dataParser/parsers/coerce/base.test.ts b/tests/dataParser/parsers/coerce/base.test.ts deleted file mode 100644 index 157a92564..000000000 --- a/tests/dataParser/parsers/coerce/base.test.ts +++ /dev/null @@ -1,105 +0,0 @@ -import { DDate, DEither, DDataParser, type ExpectType } from "@scripts"; - -const dataParserCoerceMapperEntries = [...DDataParser.dataParserCoerceTransformerMapper.entries()]; - -function restoreDataParserCoerceMapper() { - DDataParser.dataParserCoerceTransformerMapper.clear(); - - dataParserCoerceMapperEntries.map( - ([kind, transformer]) => DDataParser.dataParserCoerceTransformerMapper.set(kind, transformer), - ); -} - -describe("DDataParser coercer", () => { - beforeEach(restoreDataParserCoerceMapper); - afterEach(restoreDataParserCoerceMapper); - - it("preserves output and extends input from eligible coerce parser", () => { - const schema = DDataParser.coercer(DDataParser.number()); - - type _CheckOut = ExpectType< - DDataParser.Output, - number, - "strict" - >; - - type _CheckIn = ExpectType< - DDataParser.Input, - string | number, - "strict" - >; - - expect(schema.parse("42")).toStrictEqual(DEither.success(42)); - }); - - it("runs checkers after inner parser succeeds", () => { - const schema = DDataParser.coercer(DDataParser.number()).addChecker( - DDataParser.checkerNumberMin(10), - ); - - expect(schema.parse("42")).toStrictEqual(DEither.success(42)); - expect(schema.parse("5")).toStrictEqual(DEither.error(expect.any(Object))); - }); - - it("coerces numeric boolean values through base coercer", () => { - const schema = DDataParser.coercer(DDataParser.boolean()); - - expect(schema.parse(1)).toStrictEqual(DEither.success(true)); - expect(schema.parse(0)).toStrictEqual(DEither.success(false)); - expect(schema.parse(2)).toStrictEqual(DEither.error(expect.any(Object))); - }); - - it("coerces ISO time strings through base coercer", () => { - const schema = DDataParser.coercer(DDataParser.time()); - - expect(schema.parse("01:02")).toStrictEqual( - DEither.success(DDate.createTimeOrThrow("time3720000+")), - ); - }); - - it("rejects ISO time strings when time creation fails through base coercer", () => { - const schema = DDataParser.coercer(DDataParser.time()); - const createTimeSpy = vi.spyOn(DDate, "createTime") - .mockReturnValueOnce(DEither.left("time-created-error", null) as never); - - try { - expect(schema.parse("01:02")).toStrictEqual(DEither.error(expect.any(Object))); - expect(createTimeSpy).toHaveBeenCalledWith({ value: "01:02" }); - } finally { - createTimeSpy.mockRestore(); - } - }); - - it("returns an error when no transformer is registered for inner parser kind", () => { - DDataParser.dataParserCoerceTransformerMapper.delete(DDataParser.numberKind); - - expect(DDataParser.coercer(DDataParser.number()).parse("42")).toStrictEqual( - DEither.error(expect.any(Object)), - ); - }); - - it("reuses the transformer resolved during first parse", () => { - const schema = DDataParser.coercer(DDataParser.number()); - - expect(schema.parse("42")).toStrictEqual(DEither.success(42)); - - DDataParser.dataParserCoerceTransformerMapper.delete(DDataParser.numberKind); - - expect(schema.parse("43")).toStrictEqual(DEither.success(43)); - expect(DDataParser.coercer(DDataParser.number()).parse("44")).toStrictEqual( - DEither.error(expect.any(Object)), - ); - }); - - it("keeps coercion synchronous when inner parser is synchronous", () => { - expect(DDataParser.coercer(DDataParser.number()).isAsynchronous()).toBe(false); - }); - - it("transformer throw", () => { - DDataParser.dataParserCoerceTransformerMapper.set(DDataParser.numberKind, Number); - - expect(DDataParser.coerce.number().parse(Symbol("foo"))).toStrictEqual( - DEither.error(expect.any(Object)), - ); - }); -}); diff --git a/tests/dataParser/parsers/coerce/presets/bigint.test.ts b/tests/dataParser/parsers/coerce/bigint.test.ts similarity index 96% rename from tests/dataParser/parsers/coerce/presets/bigint.test.ts rename to tests/dataParser/parsers/coerce/bigint.test.ts index 8117dd5d8..6d8605580 100644 --- a/tests/dataParser/parsers/coerce/presets/bigint.test.ts +++ b/tests/dataParser/parsers/coerce/bigint.test.ts @@ -32,7 +32,7 @@ describe("coerce.bigint", () => { type _CheckIn = ExpectType< DDataParser.Input, - bigint, + string | number | bigint | boolean, "strict" >; diff --git a/tests/dataParser/parsers/coerce/presets/boolean.test.ts b/tests/dataParser/parsers/coerce/boolean.test.ts similarity index 100% rename from tests/dataParser/parsers/coerce/presets/boolean.test.ts rename to tests/dataParser/parsers/coerce/boolean.test.ts diff --git a/tests/dataParser/parsers/coerce/presets/date.test.ts b/tests/dataParser/parsers/coerce/date.test.ts similarity index 100% rename from tests/dataParser/parsers/coerce/presets/date.test.ts rename to tests/dataParser/parsers/coerce/date.test.ts diff --git a/tests/dataParser/parsers/coerce/presets/empty.test.ts b/tests/dataParser/parsers/coerce/empty.test.ts similarity index 100% rename from tests/dataParser/parsers/coerce/presets/empty.test.ts rename to tests/dataParser/parsers/coerce/empty.test.ts diff --git a/tests/dataParser/parsers/coerce/presets/nil.test.ts b/tests/dataParser/parsers/coerce/nil.test.ts similarity index 100% rename from tests/dataParser/parsers/coerce/presets/nil.test.ts rename to tests/dataParser/parsers/coerce/nil.test.ts diff --git a/tests/dataParser/parsers/coerce/presets/number.test.ts b/tests/dataParser/parsers/coerce/number.test.ts similarity index 96% rename from tests/dataParser/parsers/coerce/presets/number.test.ts rename to tests/dataParser/parsers/coerce/number.test.ts index 85ec40627..e6d0ce6a4 100644 --- a/tests/dataParser/parsers/coerce/presets/number.test.ts +++ b/tests/dataParser/parsers/coerce/number.test.ts @@ -32,7 +32,7 @@ describe("coerce.number", () => { type _CheckIn = ExpectType< DDataParser.Input, - string | number, + string | number | bigint | boolean | null, "strict" >; diff --git a/tests/dataParser/parsers/coerce/presets/string.test.ts b/tests/dataParser/parsers/coerce/string.test.ts similarity index 94% rename from tests/dataParser/parsers/coerce/presets/string.test.ts rename to tests/dataParser/parsers/coerce/string.test.ts index 3e3ddf3c8..2906dbc1c 100644 --- a/tests/dataParser/parsers/coerce/presets/string.test.ts +++ b/tests/dataParser/parsers/coerce/string.test.ts @@ -32,7 +32,7 @@ describe("coerce.string", () => { type _CheckIn = ExpectType< DDataParser.Input, - string, + string | number | bigint | boolean | symbol | null | undefined, "strict" >; diff --git a/tests/dataParser/parsers/coerce/templateLiteral.test.ts b/tests/dataParser/parsers/coerce/templateLiteral.test.ts new file mode 100644 index 000000000..16c11416e --- /dev/null +++ b/tests/dataParser/parsers/coerce/templateLiteral.test.ts @@ -0,0 +1,63 @@ +import { DEither, DDataParser, type ExpectType } from "@scripts"; + +describe("coerce.templateLiteral", () => { + it("create data parser with checker", () => { + const dataParser = DDataParser.coerce.templateLiteral( + ["item-", DDataParser.number()], + { + checkers: [ + DDataParser.checkerRefine((value) => { + type check = ExpectType< + typeof value, + `item-${number}`, + "strict" + >; + return true; + }), + ], + }, + ).addChecker( + DDataParser.checkerRefine((value) => { + type check = ExpectType< + typeof value, + `item-${number}`, + "strict" + >; + return true; + }), + ); + + type _CheckOut = ExpectType< + DDataParser.Output, + `item-${number}`, + "strict" + >; + + type _CheckIn = ExpectType< + DDataParser.Input, + number | bigint | boolean | `item-${number}`, + "strict" + >; + + void dataParser; + }); + + it("coerces value to string", () => { + expect(DDataParser.coerce.templateLiteral(["item-", DDataParser.number()]).parse("item-42")).toStrictEqual(DEither.success("item-42")); + }); + + it("coerces primitive template literal input to string", () => { + expect(DDataParser.coerce.templateLiteral([DDataParser.number()]).parse(42)).toStrictEqual( + DEither.success("42"), + ); + }); + + it("fails when coercion not possible", () => { + expect(DDataParser.coerce.templateLiteral( + ["item-", DDataParser.number()], + { errorMessage: "templateLiteral.coerce" }, + ).parse(Object.create(null))).toStrictEqual( + DEither.error(expect.any(Object)), + ); + }); +}); diff --git a/tests/dataParser/parsers/coerce/presets/time.test.ts b/tests/dataParser/parsers/coerce/time.test.ts similarity index 100% rename from tests/dataParser/parsers/coerce/presets/time.test.ts rename to tests/dataParser/parsers/coerce/time.test.ts diff --git a/tests/dataParser/parsers/coercer/index.test.ts b/tests/dataParser/parsers/coercer/index.test.ts new file mode 100644 index 000000000..75d41fc7a --- /dev/null +++ b/tests/dataParser/parsers/coercer/index.test.ts @@ -0,0 +1,190 @@ +import { DDate, DEither, DDataParser, type ExpectType } from "@scripts"; + +const dataParserCoerceTransformersEntries = [...DDataParser.DataParserCoercer.transformers.entries()]; + +function restoreDataParserCoerceTransformers() { + DDataParser.DataParserCoercer.transformers.clear(); + + dataParserCoerceTransformersEntries.map( + ([kind, transformer]) => DDataParser.DataParserCoercer.transformers.set(kind, transformer), + ); +} + +describe("DDataParser coercer", () => { + beforeEach(restoreDataParserCoerceTransformers); + afterEach(restoreDataParserCoerceTransformers); + + it("preserves output and extends input from eligible coerce parser", () => { + const schema = DDataParser.coercer(DDataParser.number()); + + type _CheckOut = ExpectType< + DDataParser.Output, + number, + "strict" + >; + + type _CheckIn = ExpectType< + DDataParser.Input, + string | number | bigint | boolean | null, + "strict" + >; + + expect(schema.parse("42")).toStrictEqual(DEither.success(42)); + }); + + it("runs checkers after inner parser succeeds", () => { + const schema = DDataParser.coercer(DDataParser.number()).addChecker( + DDataParser.checkerNumberMin(10), + ); + + expect(schema.parse("42")).toStrictEqual(DEither.success(42)); + expect(schema.parse("5")).toStrictEqual(DEither.error(expect.any(Object))); + }); + + it("coerces numeric boolean values through base coercer", () => { + const schema = DDataParser.coercer(DDataParser.boolean()); + + expect(schema.parse(1)).toStrictEqual(DEither.success(true)); + expect(schema.parse(0)).toStrictEqual(DEither.success(false)); + expect(schema.parse(2)).toStrictEqual(DEither.error(expect.any(Object))); + }); + + it("coerces ISO time strings through base coercer", () => { + const schema = DDataParser.coercer(DDataParser.time()); + + expect(schema.parse("01:02")).toStrictEqual( + DEither.success(DDate.createTimeOrThrow("time3720000+")), + ); + }); + + it("rejects ISO time strings when time creation fails through base coercer", () => { + const schema = DDataParser.coercer(DDataParser.time()); + const createTimeSpy = vi.spyOn(DDate, "createTime") + .mockReturnValueOnce(DEither.left("time-created-error", null) as never); + + try { + expect(schema.parse("01:02")).toStrictEqual(DEither.error(expect.any(Object))); + expect(createTimeSpy).toHaveBeenCalledWith({ value: "01:02" }); + } finally { + createTimeSpy.mockRestore(); + } + }); + + it("returns an error when no transformer is registered for inner parser kind", () => { + DDataParser.DataParserCoercer.transformers.delete(DDataParser.numberKind); + + expect(DDataParser.coercer(DDataParser.number()).parse("42")).toStrictEqual( + DEither.error(expect.any(Object)), + ); + }); + + it("keeps unsupported parser input and output unchanged", () => { + const schema = DDataParser.coercer(DDataParser.literal("ready")); + + type _CheckOut = ExpectType< + DDataParser.Output, + "ready", + "strict" + >; + + type _CheckIn = ExpectType< + DDataParser.Input, + "ready", + "strict" + >; + + expect(schema.parse("ready")).toStrictEqual(DEither.success("ready")); + expect(schema.parse("42")).toStrictEqual(DEither.error(expect.any(Object))); + }); + + it("returns an error when asynchronous inner parser rejects", async() => { + interface RejectedParserDefinition extends DDataParser.DataParserDefinition { + readonly rejectedError: Error; + } + + class RejectedParser extends DDataParser.DataParserBase.init( + DDataParser.dataParserKind, + )< + RejectedParserDefinition, + number, + number + > { + public get classConstructor() { + return this.checkConstructor(RejectedParser); + } + + public static override execParse( + self: RejectedParser, + data: unknown, + error: DDataParser.DataParserError, + ) { + void data; + void error; + return Promise.reject(self.definition.rejectedError); + } + + public static override dataParserIsAsynchronous(self: RejectedParser) { + void self; + return true; + } + + public static override prepareDefinition( + definition?: Partial, + ): RejectedParserDefinition { + return { + ...definition, + rejectedError: definition?.rejectedError ?? new Error("rejected"), + checkers: definition?.checkers ?? [], + errorMessage: definition?.errorMessage, + }; + } + + public static override create( + definition?: Partial, + ) { + return new RejectedParser(this.prepareDefinition(definition)); + } + } + + const rejectedError = new Error("coercer rejected"); + const schema = DDataParser.coercer(RejectedParser.create({ rejectedError })); + + await expect(schema.asyncParse(42)).resolves.toStrictEqual( + DEither.error( + expect.objectContaining({ + issues: [ + expect.objectContaining({ + expected: "successful coerce result", + data: rejectedError, + }), + ], + }), + ), + ); + }); + + it("reuses the transformer resolved during first parse", () => { + const schema = DDataParser.coercer(DDataParser.number()); + + expect(schema.parse("42")).toStrictEqual(DEither.success(42)); + + DDataParser.DataParserCoercer.transformers.delete(DDataParser.numberKind); + + expect(schema.parse("43")).toStrictEqual(DEither.success(43)); + expect(DDataParser.coercer(DDataParser.number()).parse("44")).toStrictEqual( + DEither.error(expect.any(Object)), + ); + }); + + it("keeps coercion synchronous when inner parser is synchronous", () => { + expect(DDataParser.coercer(DDataParser.number()).isAsynchronous()).toBe(false); + }); + + it("transformer throw", () => { + DDataParser.DataParserCoercer.transformers.set(DDataParser.numberKind, Number); + + expect(DDataParser.coerce.number().parse(Symbol("foo"))).toStrictEqual( + DEither.error(expect.any(Object)), + ); + }); +}); From c796633c97bc89e374c4cfbd14eeaa53c56289e5 Mon Sep 17 00:00:00 2001 From: ZeRiix Date: Fri, 10 Jul 2026 12:51:37 +0200 Subject: [PATCH 4/8] fix(94): fix following review --- scripts/dataParser/parsers/coerce/bigint.ts | 18 +++++-- scripts/dataParser/parsers/coerce/boolean.ts | 18 +++++-- scripts/dataParser/parsers/coerce/date.ts | 18 +++++-- scripts/dataParser/parsers/coerce/empty.ts | 18 +++++-- scripts/dataParser/parsers/coerce/nil.ts | 18 +++++-- scripts/dataParser/parsers/coerce/number.ts | 18 +++++-- scripts/dataParser/parsers/coerce/string.ts | 18 +++++-- .../parsers/coerce/templateLiteral.ts | 20 +++++-- scripts/dataParser/parsers/coercer/index.ts | 54 ++++++++----------- .../dataParser/parsers/coercer/types/index.ts | 1 - .../parsers/coercer/types/transformer.ts | 1 - .../dataParser/parsers/coercer/index.test.ts | 22 ++++++++ 12 files changed, 167 insertions(+), 57 deletions(-) delete mode 100644 scripts/dataParser/parsers/coercer/types/index.ts delete mode 100644 scripts/dataParser/parsers/coercer/types/transformer.ts diff --git a/scripts/dataParser/parsers/coerce/bigint.ts b/scripts/dataParser/parsers/coerce/bigint.ts index 43695edab..300fe3d69 100644 --- a/scripts/dataParser/parsers/coerce/bigint.ts +++ b/scripts/dataParser/parsers/coerce/bigint.ts @@ -1,5 +1,5 @@ -import type { FixDeepFunctionInfer } from "@scripts/common"; -import type { PrepareDataParserDefinition } from "@scripts/dataParser/types"; +import type { FixDeepFunctionInfer, NeverCoalescing } from "@scripts/common"; +import type { MergeDefinition, PrepareDataParserDefinition } from "@scripts/dataParser/types"; import * as dataParsers from ".."; export function bigint< @@ -15,6 +15,18 @@ export function bigint< >, GenericDefinition >, -) { +): dataParsers.DataParserCoercer< + MergeDefinition< + dataParsers.DataParserDefinitionCoercer, + { + inner: dataParsers.DataParserBigInt< + MergeDefinition< + dataParsers.DataParserDefinitionBigInt, + NeverCoalescing + > + >; + } + > + > { return dataParsers.coercer(dataParsers.bigint(definition)); } diff --git a/scripts/dataParser/parsers/coerce/boolean.ts b/scripts/dataParser/parsers/coerce/boolean.ts index d633975bb..9b7ac0498 100644 --- a/scripts/dataParser/parsers/coerce/boolean.ts +++ b/scripts/dataParser/parsers/coerce/boolean.ts @@ -1,5 +1,5 @@ -import type { FixDeepFunctionInfer } from "@scripts/common"; -import type { PrepareDataParserDefinition } from "@scripts/dataParser/types"; +import type { FixDeepFunctionInfer, NeverCoalescing } from "@scripts/common"; +import type { MergeDefinition, PrepareDataParserDefinition } from "@scripts/dataParser/types"; import * as dataParsers from ".."; export function boolean< @@ -15,6 +15,18 @@ export function boolean< >, GenericDefinition >, -) { +): dataParsers.DataParserCoercer< + MergeDefinition< + dataParsers.DataParserDefinitionCoercer, + { + inner: dataParsers.DataParserBoolean< + MergeDefinition< + dataParsers.DataParserDefinitionBoolean, + NeverCoalescing + > + >; + } + > + > { return dataParsers.coercer(dataParsers.boolean(definition)); } diff --git a/scripts/dataParser/parsers/coerce/date.ts b/scripts/dataParser/parsers/coerce/date.ts index 1acc8b0cb..4937bac6a 100644 --- a/scripts/dataParser/parsers/coerce/date.ts +++ b/scripts/dataParser/parsers/coerce/date.ts @@ -1,5 +1,5 @@ -import type { FixDeepFunctionInfer } from "@scripts/common"; -import type { PrepareDataParserDefinition } from "@scripts/dataParser/types"; +import type { FixDeepFunctionInfer, NeverCoalescing } from "@scripts/common"; +import type { MergeDefinition, PrepareDataParserDefinition } from "@scripts/dataParser/types"; import * as dataParsers from ".."; export function date< @@ -15,6 +15,18 @@ export function date< >, GenericDefinition >, -) { +): dataParsers.DataParserCoercer< + MergeDefinition< + dataParsers.DataParserDefinitionCoercer, + { + inner: dataParsers.DataParserDate< + MergeDefinition< + dataParsers.DataParserDefinitionDate, + NeverCoalescing + > + >; + } + > + > { return dataParsers.coercer(dataParsers.date(definition)); } diff --git a/scripts/dataParser/parsers/coerce/empty.ts b/scripts/dataParser/parsers/coerce/empty.ts index 4d28e8964..842adf295 100644 --- a/scripts/dataParser/parsers/coerce/empty.ts +++ b/scripts/dataParser/parsers/coerce/empty.ts @@ -1,5 +1,5 @@ -import type { FixDeepFunctionInfer } from "@scripts/common"; -import type { PrepareDataParserDefinition } from "@scripts/dataParser/types"; +import type { FixDeepFunctionInfer, NeverCoalescing } from "@scripts/common"; +import type { MergeDefinition, PrepareDataParserDefinition } from "@scripts/dataParser/types"; import * as dataParsers from ".."; export function empty< @@ -15,6 +15,18 @@ export function empty< >, GenericDefinition >, -) { +): dataParsers.DataParserCoercer< + MergeDefinition< + dataParsers.DataParserDefinitionCoercer, + { + inner: dataParsers.DataParserEmpty< + MergeDefinition< + dataParsers.DataParserDefinitionEmpty, + NeverCoalescing + > + >; + } + > + > { return dataParsers.coercer(dataParsers.empty(definition)); } diff --git a/scripts/dataParser/parsers/coerce/nil.ts b/scripts/dataParser/parsers/coerce/nil.ts index b9e240762..b4902b0b6 100644 --- a/scripts/dataParser/parsers/coerce/nil.ts +++ b/scripts/dataParser/parsers/coerce/nil.ts @@ -1,5 +1,5 @@ -import type { FixDeepFunctionInfer } from "@scripts/common"; -import type { PrepareDataParserDefinition } from "@scripts/dataParser/types"; +import type { FixDeepFunctionInfer, NeverCoalescing } from "@scripts/common"; +import type { MergeDefinition, PrepareDataParserDefinition } from "@scripts/dataParser/types"; import * as dataParsers from ".."; export function nil< @@ -15,6 +15,18 @@ export function nil< >, GenericDefinition >, -) { +): dataParsers.DataParserCoercer< + MergeDefinition< + dataParsers.DataParserDefinitionCoercer, + { + inner: dataParsers.DataParserNil< + MergeDefinition< + dataParsers.DataParserDefinitionNil, + NeverCoalescing + > + >; + } + > + > { return dataParsers.coercer(dataParsers.nil(definition)); } diff --git a/scripts/dataParser/parsers/coerce/number.ts b/scripts/dataParser/parsers/coerce/number.ts index 5ba004e29..0028b6f2a 100644 --- a/scripts/dataParser/parsers/coerce/number.ts +++ b/scripts/dataParser/parsers/coerce/number.ts @@ -1,5 +1,5 @@ -import type { FixDeepFunctionInfer } from "@scripts/common"; -import type { PrepareDataParserDefinition } from "@scripts/dataParser/types"; +import type { FixDeepFunctionInfer, NeverCoalescing } from "@scripts/common"; +import type { MergeDefinition, PrepareDataParserDefinition } from "@scripts/dataParser/types"; import * as dataParsers from ".."; export function number< @@ -15,6 +15,18 @@ export function number< >, GenericDefinition >, -) { +): dataParsers.DataParserCoercer< + MergeDefinition< + dataParsers.DataParserDefinitionCoercer, + { + inner: dataParsers.DataParserNumber< + MergeDefinition< + dataParsers.DataParserDefinitionNumber, + NeverCoalescing + > + >; + } + > + > { return dataParsers.coercer(dataParsers.number(definition)); } diff --git a/scripts/dataParser/parsers/coerce/string.ts b/scripts/dataParser/parsers/coerce/string.ts index e467ee1ee..e91460ae3 100644 --- a/scripts/dataParser/parsers/coerce/string.ts +++ b/scripts/dataParser/parsers/coerce/string.ts @@ -1,5 +1,5 @@ -import type { FixDeepFunctionInfer } from "@scripts/common"; -import type { PrepareDataParserDefinition } from "@scripts/dataParser/types"; +import type { FixDeepFunctionInfer, NeverCoalescing } from "@scripts/common"; +import type { MergeDefinition, PrepareDataParserDefinition } from "@scripts/dataParser/types"; import * as dataParsers from ".."; export function string< @@ -15,6 +15,18 @@ export function string< >, GenericDefinition >, -) { +): dataParsers.DataParserCoercer< + MergeDefinition< + dataParsers.DataParserDefinitionCoercer, + { + inner: dataParsers.DataParserString< + MergeDefinition< + dataParsers.DataParserDefinitionString, + NeverCoalescing + > + >; + } + > + > { return dataParsers.coercer(dataParsers.string(definition)); } diff --git a/scripts/dataParser/parsers/coerce/templateLiteral.ts b/scripts/dataParser/parsers/coerce/templateLiteral.ts index 26bbb3020..9a51293fe 100644 --- a/scripts/dataParser/parsers/coerce/templateLiteral.ts +++ b/scripts/dataParser/parsers/coerce/templateLiteral.ts @@ -1,5 +1,5 @@ -import type { FixDeepFunctionInfer } from "@scripts/common"; -import type { PrepareDataParserDefinition } from "@scripts/dataParser/types"; +import type { FixDeepFunctionInfer, NeverCoalescing } from "@scripts/common"; +import type { MergeDefinition, PrepareDataParserDefinition } from "@scripts/dataParser/types"; import * as dataParsers from ".."; export function templateLiteral< @@ -21,6 +21,20 @@ export function templateLiteral< >, GenericDefinition >, -) { +): dataParsers.DataParserCoercer< + MergeDefinition< + dataParsers.DataParserDefinitionCoercer< + dataParsers.TemplateLiteralShapeOutput + >, + { + inner: dataParsers.DataParserTemplateLiteral< + MergeDefinition< + dataParsers.DataParserDefinitionTemplateLiteral, + NeverCoalescing & { template: GenericTemplate } + > + >; + } + > + > { return dataParsers.coercer(dataParsers.templateLiteral(template, definition)); } diff --git a/scripts/dataParser/parsers/coercer/index.ts b/scripts/dataParser/parsers/coercer/index.ts index 092ccd8a8..196772472 100644 --- a/scripts/dataParser/parsers/coercer/index.ts +++ b/scripts/dataParser/parsers/coercer/index.ts @@ -1,15 +1,12 @@ -import { callThen, detachObjectMethod, type FixDeepFunctionInfer, type Memoized, memo, type NeverCoalescing, type KindHandler, forward } from "@scripts/common"; +import { detachObjectMethod, type FixDeepFunctionInfer, type Memoized, memo, type NeverCoalescing, type KindHandler, type AnyFunction, callThen, forward } from "@scripts/common"; import { DataParserBase, type DataParser, type DataParserDefinition } from "../../base"; import { createDataParserKind } from "../../kind"; import { addIssue, type DataParserError } from "../../error"; import type { DataParserChecker } from "../../baseChecker"; import type { ApplyRefinementOfDefinition, AddCheckersToDefinition, GetEligibleChecker, Input, MergeDefinition, Output, PrepareDataParserDefinition } from "../../types"; -import type { DataParserCoercerTransformer } from "./types"; import * as coercerTransformers from "./transformers"; import * as dataParsers from ".."; -export * from "./types"; - export interface ComputeInputDataParserCoercer< GenericDataParser extends DataParser, > { @@ -61,7 +58,7 @@ export interface DataParserDefinitionCoercer< DataParserCoercerCheckers > { readonly inner: DataParser; - readonly transformer: Memoized; + readonly transformer: Memoized; } export const coercerKind = createDataParserKind("coercer"); @@ -76,10 +73,7 @@ export class DataParserCoercer< Output, GenericDefinition >, - ApplyRefinementOfDefinition< - DataParserCoercerInput, - GenericDefinition - > + DataParserCoercerInput > { public get classConstructor() { return this.checkConstructor(DataParserCoercer); @@ -110,40 +104,38 @@ export class DataParserCoercer< data: unknown, error: DataParserError, ): unknown { - return callThen( - data, - (resolvedData) => { - const transformedData = self.definition.transformer.value - ? self.definition.transformer.value(resolvedData) - : resolvedData; + try { + const transformedData = self.definition.transformer.value + ? self.definition.transformer.value(data) + : data; - return callThen( - self.definition.inner.exec(transformedData, error), - forward, - (catchError) => addIssue( - error, - "successful coerce result", - catchError, - self.definition.errorMessage, - self, - ), - ); - }, - (catchError) => addIssue( + return callThen( + self.definition.inner.exec(transformedData, error), + forward, + (catchError) => addIssue( + error, + "successful coerce result", + catchError, + self.definition.errorMessage, + self, + ), + ); + } catch (catchError) { + return addIssue( error, "successful coerce result", catchError, self.definition.errorMessage, self, - ), - ); + ); + } } public static override dataParserIsAsynchronous(self: DataParserCoercer) { return self.definition.inner.isAsynchronous(); } - public static transformers = new Map([ + public static transformers = new Map([ [dataParsers.numberKind, coercerTransformers.numberTransformer], [dataParsers.stringKind, coercerTransformers.stringTransformer], [dataParsers.booleanKind, coercerTransformers.booleanTransformer], diff --git a/scripts/dataParser/parsers/coercer/types/index.ts b/scripts/dataParser/parsers/coercer/types/index.ts deleted file mode 100644 index ba1ba509e..000000000 --- a/scripts/dataParser/parsers/coercer/types/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./transformer"; diff --git a/scripts/dataParser/parsers/coercer/types/transformer.ts b/scripts/dataParser/parsers/coercer/types/transformer.ts deleted file mode 100644 index c3418512f..000000000 --- a/scripts/dataParser/parsers/coercer/types/transformer.ts +++ /dev/null @@ -1 +0,0 @@ -export type DataParserCoercerTransformer = (data: unknown) => unknown; diff --git a/tests/dataParser/parsers/coercer/index.test.ts b/tests/dataParser/parsers/coercer/index.test.ts index 75d41fc7a..175f228fe 100644 --- a/tests/dataParser/parsers/coercer/index.test.ts +++ b/tests/dataParser/parsers/coercer/index.test.ts @@ -32,6 +32,28 @@ describe("DDataParser coercer", () => { expect(schema.parse("42")).toStrictEqual(DEither.success(42)); }); + it("keeps coercer input unrefined when checker refines output", () => { + const schema = DDataParser.coercer(DDataParser.number()).addChecker( + DDataParser.checkerRefine( + (value): value is 42 => value === 42, + ), + ); + + type _CheckOut = ExpectType< + DDataParser.Output, + 42, + "strict" + >; + + type _CheckIn = ExpectType< + DDataParser.Input, + string | number | bigint | boolean | null, + "strict" + >; + + expect(schema.parse("42")).toStrictEqual(DEither.success(42)); + }); + it("runs checkers after inner parser succeeds", () => { const schema = DDataParser.coercer(DDataParser.number()).addChecker( DDataParser.checkerNumberMin(10), From be2d83c400728834c2bd5907e256227c28a11cc8 Mon Sep 17 00:00:00 2001 From: ZeRiix Date: Sun, 12 Jul 2026 19:05:40 +0200 Subject: [PATCH 5/8] feat(hf): add dataParser coercer extended --- jsDoc/dataParser/extended/string/example.ts | 2 +- scripts/dataParser/extended/bigint.ts | 12 +++ scripts/dataParser/extended/boolean.ts | 12 +++ scripts/dataParser/extended/coerce/bigint.ts | 25 ++++-- scripts/dataParser/extended/coerce/boolean.ts | 25 ++++-- scripts/dataParser/extended/coerce/date.ts | 25 ++++-- scripts/dataParser/extended/coerce/empty.ts | 25 ++++-- scripts/dataParser/extended/coerce/index.ts | 1 + scripts/dataParser/extended/coerce/nil.ts | 25 ++++-- scripts/dataParser/extended/coerce/number.ts | 25 ++++-- scripts/dataParser/extended/coerce/string.ts | 25 ++++-- .../extended/coerce/templateLiteral.ts | 44 ++++++++++ scripts/dataParser/extended/coerce/time.ts | 25 ++++-- scripts/dataParser/extended/coercer.ts | 82 +++++++++++++++++++ scripts/dataParser/extended/date.ts | 12 +++ scripts/dataParser/extended/empty.ts | 12 +++ scripts/dataParser/extended/index.ts | 8 +- scripts/dataParser/extended/nil.ts | 12 +++ scripts/dataParser/extended/number.ts | 12 +++ scripts/dataParser/extended/string.ts | 12 +++ .../dataParser/extended/templateLiteral.ts | 12 +++ scripts/dataParser/extended/time.ts | 12 +++ scripts/dataParser/parsers/coerce/bigint.ts | 3 + scripts/dataParser/parsers/coerce/boolean.ts | 3 + scripts/dataParser/parsers/coerce/date.ts | 3 + scripts/dataParser/parsers/coerce/empty.ts | 3 + scripts/dataParser/parsers/coerce/nil.ts | 3 + scripts/dataParser/parsers/coerce/number.ts | 3 + scripts/dataParser/parsers/coerce/string.ts | 3 + .../parsers/coerce/templateLiteral.ts | 3 + scripts/dataParser/parsers/coerce/time.ts | 3 + scripts/dataParser/parsers/index.ts | 4 + scripts/index.ts | 7 ++ 33 files changed, 408 insertions(+), 75 deletions(-) create mode 100644 scripts/dataParser/extended/coerce/templateLiteral.ts create mode 100644 scripts/dataParser/extended/coercer.ts diff --git a/jsDoc/dataParser/extended/string/example.ts b/jsDoc/dataParser/extended/string/example.ts index e441019f5..95b8d04fc 100644 --- a/jsDoc/dataParser/extended/string/example.ts +++ b/jsDoc/dataParser/extended/string/example.ts @@ -10,5 +10,5 @@ if (E.isRight(result)) { const withRegex = DPE.string().regex(/^[A-Z][a-z]+$/); const regexResult = withRegex.parse("Duplo"); -const coerceParser = DPE.coerce.string().min(2); +const coerceParser = DPE.string().min(2).coerce(); const coerceResult = coerceParser.parse(123); diff --git a/scripts/dataParser/extended/bigint.ts b/scripts/dataParser/extended/bigint.ts index 0abac2d99..13e1bea32 100644 --- a/scripts/dataParser/extended/bigint.ts +++ b/scripts/dataParser/extended/bigint.ts @@ -3,6 +3,7 @@ import { DataParserBaseExtended } from "./base"; import { type AddCheckersToDefinition, type Output, type MergeDefinition, type PrepareDataParserDefinition, type Input } from "../types"; import * as dataParsers from "../parsers"; import { type DataParserChecker } from "../baseChecker"; +import { DataParserCoercerExtended } from "./coercer"; export class DataParserBigIntExtended< GenericDefinition extends dataParsers.DataParserDefinitionBigInt = dataParsers.DataParserDefinitionBigInt, @@ -71,6 +72,17 @@ export class DataParserBigIntExtended< return this.addChecker(dataParsers.checkerBigIntMax(max, definition)); } + public coerce< + GenericThis extends this = this, + >(): DataParserCoercerExtended< + MergeDefinition< + dataParsers.DataParserDefinitionCoercer, + { inner: GenericThis } + > + > { + return DataParserCoercerExtended.create(this) as never; + } + /** * {@include dataParser/extended/bigint/index.md} */ diff --git a/scripts/dataParser/extended/boolean.ts b/scripts/dataParser/extended/boolean.ts index c6b071d58..b0b073b26 100644 --- a/scripts/dataParser/extended/boolean.ts +++ b/scripts/dataParser/extended/boolean.ts @@ -4,6 +4,7 @@ import { DataParserBaseExtended } from "./base"; import { type AddCheckersToDefinition, type Output, type MergeDefinition, type PrepareDataParserDefinition, type Input } from "../types"; import * as dataParsers from "../parsers"; import { type DataParserChecker } from "../baseChecker"; +import { DataParserCoercerExtended } from "./coercer"; export class DataParserBooleanExtended< GenericDefinition extends dataParsers.DataParserDefinitionBoolean = dataParsers.DataParserDefinitionBoolean, @@ -48,6 +49,17 @@ export class DataParserBooleanExtended< > >; + public coerce< + GenericThis extends this = this, + >(): DataParserCoercerExtended< + MergeDefinition< + dataParsers.DataParserDefinitionCoercer, + { inner: GenericThis } + > + > { + return DataParserCoercerExtended.create(this) as never; + } + /** * {@include dataParser/extended/boolean/index.md} */ diff --git a/scripts/dataParser/extended/coerce/bigint.ts b/scripts/dataParser/extended/coerce/bigint.ts index f77767684..e0dc0c337 100644 --- a/scripts/dataParser/extended/coerce/bigint.ts +++ b/scripts/dataParser/extended/coerce/bigint.ts @@ -1,8 +1,11 @@ -import { type FixDeepFunctionInfer, type NeverCoalescing } from "@scripts/common"; -import { type MergeDefinition, type PrepareDataParserDefinition } from "../../types"; +import type { FixDeepFunctionInfer, NeverCoalescing } from "@scripts/common"; +import type { MergeDefinition, PrepareDataParserDefinition } from "@scripts/dataParser/types"; import type * as dataParsers from "../../parsers"; import * as dataParsersExtended from ".."; +/** + * @deprecated Use `dataParsersExtended.bigint().coerce()` instead. + */ export function bigint< const GenericDefinition extends PrepareDataParserDefinition< dataParsers.DataParserDefinitionBigInt, @@ -16,14 +19,18 @@ export function bigint< >, GenericDefinition >, -): dataParsersExtended.DataParserBigIntExtended< +): dataParsersExtended.DataParserCoercerExtended< MergeDefinition< - dataParsers.DataParserDefinitionBigInt, - NeverCoalescing & { coerce: true } + dataParsers.DataParserDefinitionCoercer, + { + inner: dataParsersExtended.DataParserBigIntExtended< + MergeDefinition< + dataParsers.DataParserDefinitionBigInt, + NeverCoalescing + > + >; + } > > { - return dataParsersExtended.bigint({ - ...definition, - coerce: true, - }); + return dataParsersExtended.coercer(dataParsersExtended.bigint(definition)); } diff --git a/scripts/dataParser/extended/coerce/boolean.ts b/scripts/dataParser/extended/coerce/boolean.ts index da75358c4..5bf4ecced 100644 --- a/scripts/dataParser/extended/coerce/boolean.ts +++ b/scripts/dataParser/extended/coerce/boolean.ts @@ -1,8 +1,11 @@ -import { type FixDeepFunctionInfer, type NeverCoalescing } from "@scripts/common"; -import { type MergeDefinition, type PrepareDataParserDefinition } from "../../types"; +import type { FixDeepFunctionInfer, NeverCoalescing } from "@scripts/common"; +import type { MergeDefinition, PrepareDataParserDefinition } from "@scripts/dataParser/types"; import type * as dataParsers from "../../parsers"; import * as dataParsersExtended from ".."; +/** + * @deprecated Use `dataParsersExtended.boolean().coerce()` instead. + */ export function boolean< const GenericDefinition extends PrepareDataParserDefinition< dataParsers.DataParserDefinitionBoolean, @@ -16,14 +19,18 @@ export function boolean< >, GenericDefinition >, -): dataParsersExtended.DataParserBooleanExtended< +): dataParsersExtended.DataParserCoercerExtended< MergeDefinition< - dataParsers.DataParserDefinitionBoolean, - NeverCoalescing & { coerce: true } + dataParsers.DataParserDefinitionCoercer, + { + inner: dataParsersExtended.DataParserBooleanExtended< + MergeDefinition< + dataParsers.DataParserDefinitionBoolean, + NeverCoalescing + > + >; + } > > { - return dataParsersExtended.boolean({ - ...definition, - coerce: true, - }); + return dataParsersExtended.coercer(dataParsersExtended.boolean(definition)); } diff --git a/scripts/dataParser/extended/coerce/date.ts b/scripts/dataParser/extended/coerce/date.ts index 10b29e2e5..54d06947e 100644 --- a/scripts/dataParser/extended/coerce/date.ts +++ b/scripts/dataParser/extended/coerce/date.ts @@ -1,8 +1,11 @@ -import { type FixDeepFunctionInfer, type NeverCoalescing } from "@scripts/common"; -import { type MergeDefinition, type PrepareDataParserDefinition } from "../../types"; +import type { FixDeepFunctionInfer, NeverCoalescing } from "@scripts/common"; +import type { MergeDefinition, PrepareDataParserDefinition } from "@scripts/dataParser/types"; import type * as dataParsers from "../../parsers"; import * as dataParsersExtended from ".."; +/** + * @deprecated Use `dataParsersExtended.date().coerce()` instead. + */ export function date< const GenericDefinition extends PrepareDataParserDefinition< dataParsers.DataParserDefinitionDate, @@ -16,14 +19,18 @@ export function date< >, GenericDefinition >, -): dataParsersExtended.DataParserDateExtended< +): dataParsersExtended.DataParserCoercerExtended< MergeDefinition< - dataParsers.DataParserDefinitionDate, - NeverCoalescing & { coerce: true } + dataParsers.DataParserDefinitionCoercer, + { + inner: dataParsersExtended.DataParserDateExtended< + MergeDefinition< + dataParsers.DataParserDefinitionDate, + NeverCoalescing + > + >; + } > > { - return dataParsersExtended.date({ - ...definition, - coerce: true, - }); + return dataParsersExtended.coercer(dataParsersExtended.date(definition)); } diff --git a/scripts/dataParser/extended/coerce/empty.ts b/scripts/dataParser/extended/coerce/empty.ts index 4ef510f64..39c57645a 100644 --- a/scripts/dataParser/extended/coerce/empty.ts +++ b/scripts/dataParser/extended/coerce/empty.ts @@ -1,8 +1,11 @@ -import { type FixDeepFunctionInfer, type NeverCoalescing } from "@scripts/common"; -import { type MergeDefinition, type PrepareDataParserDefinition } from "../../types"; +import type { FixDeepFunctionInfer, NeverCoalescing } from "@scripts/common"; +import type { MergeDefinition, PrepareDataParserDefinition } from "@scripts/dataParser/types"; import type * as dataParsers from "../../parsers"; import * as dataParsersExtended from ".."; +/** + * @deprecated Use `dataParsersExtended.empty().coerce()` instead. + */ export function empty< const GenericDefinition extends PrepareDataParserDefinition< dataParsers.DataParserDefinitionEmpty, @@ -16,14 +19,18 @@ export function empty< >, GenericDefinition >, -): dataParsersExtended.DataParserEmptyExtended< +): dataParsersExtended.DataParserCoercerExtended< MergeDefinition< - dataParsers.DataParserDefinitionEmpty, - NeverCoalescing & { coerce: true } + dataParsers.DataParserDefinitionCoercer, + { + inner: dataParsersExtended.DataParserEmptyExtended< + MergeDefinition< + dataParsers.DataParserDefinitionEmpty, + NeverCoalescing + > + >; + } > > { - return dataParsersExtended.empty({ - ...definition, - coerce: true, - }); + return dataParsersExtended.coercer(dataParsersExtended.empty(definition)); } diff --git a/scripts/dataParser/extended/coerce/index.ts b/scripts/dataParser/extended/coerce/index.ts index 6e8405fe3..72ee38a78 100644 --- a/scripts/dataParser/extended/coerce/index.ts +++ b/scripts/dataParser/extended/coerce/index.ts @@ -6,3 +6,4 @@ export * from "./empty"; export * from "./nil"; export * from "./number"; export * from "./string"; +export * from "./templateLiteral"; diff --git a/scripts/dataParser/extended/coerce/nil.ts b/scripts/dataParser/extended/coerce/nil.ts index 5fc3dcd85..1a817adab 100644 --- a/scripts/dataParser/extended/coerce/nil.ts +++ b/scripts/dataParser/extended/coerce/nil.ts @@ -1,8 +1,11 @@ -import { type FixDeepFunctionInfer, type NeverCoalescing } from "@scripts/common"; -import { type MergeDefinition, type PrepareDataParserDefinition } from "../../types"; +import type { FixDeepFunctionInfer, NeverCoalescing } from "@scripts/common"; +import type { MergeDefinition, PrepareDataParserDefinition } from "@scripts/dataParser/types"; import type * as dataParsers from "../../parsers"; import * as dataParsersExtended from ".."; +/** + * @deprecated Use `dataParsersExtended.nil().coerce()` instead. + */ export function nil< const GenericDefinition extends PrepareDataParserDefinition< dataParsers.DataParserDefinitionNil, @@ -16,14 +19,18 @@ export function nil< >, GenericDefinition >, -): dataParsersExtended.DataParserNilExtended< +): dataParsersExtended.DataParserCoercerExtended< MergeDefinition< - dataParsers.DataParserDefinitionNil, - NeverCoalescing & { coerce: true } + dataParsers.DataParserDefinitionCoercer, + { + inner: dataParsersExtended.DataParserNilExtended< + MergeDefinition< + dataParsers.DataParserDefinitionNil, + NeverCoalescing + > + >; + } > > { - return dataParsersExtended.nil({ - ...definition, - coerce: true, - }); + return dataParsersExtended.coercer(dataParsersExtended.nil(definition)); } diff --git a/scripts/dataParser/extended/coerce/number.ts b/scripts/dataParser/extended/coerce/number.ts index 37ac7a5aa..fed696dcc 100644 --- a/scripts/dataParser/extended/coerce/number.ts +++ b/scripts/dataParser/extended/coerce/number.ts @@ -1,8 +1,11 @@ -import { type FixDeepFunctionInfer, type NeverCoalescing } from "@scripts/common"; -import { type MergeDefinition, type PrepareDataParserDefinition } from "../../types"; +import type { FixDeepFunctionInfer, NeverCoalescing } from "@scripts/common"; +import type { MergeDefinition, PrepareDataParserDefinition } from "@scripts/dataParser/types"; import type * as dataParsers from "../../parsers"; import * as dataParsersExtended from ".."; +/** + * @deprecated Use `dataParsersExtended.number().coerce()` instead. + */ export function number< const GenericDefinition extends PrepareDataParserDefinition< dataParsers.DataParserDefinitionNumber, @@ -16,14 +19,18 @@ export function number< >, GenericDefinition >, -): dataParsersExtended.DataParserNumberExtended< +): dataParsersExtended.DataParserCoercerExtended< MergeDefinition< - dataParsers.DataParserDefinitionNumber, - NeverCoalescing & { coerce: true } + dataParsers.DataParserDefinitionCoercer, + { + inner: dataParsersExtended.DataParserNumberExtended< + MergeDefinition< + dataParsers.DataParserDefinitionNumber, + NeverCoalescing + > + >; + } > > { - return dataParsersExtended.number({ - ...definition, - coerce: true, - }); + return dataParsersExtended.coercer(dataParsersExtended.number(definition)); } diff --git a/scripts/dataParser/extended/coerce/string.ts b/scripts/dataParser/extended/coerce/string.ts index 9f058838f..79cb069a5 100644 --- a/scripts/dataParser/extended/coerce/string.ts +++ b/scripts/dataParser/extended/coerce/string.ts @@ -1,8 +1,11 @@ -import { type FixDeepFunctionInfer, type NeverCoalescing } from "@scripts/common"; -import { type MergeDefinition, type PrepareDataParserDefinition } from "../../types"; +import type { FixDeepFunctionInfer, NeverCoalescing } from "@scripts/common"; +import type { MergeDefinition, PrepareDataParserDefinition } from "@scripts/dataParser/types"; import type * as dataParsers from "../../parsers"; import * as dataParsersExtended from ".."; +/** + * @deprecated Use `dataParsersExtended.string().coerce()` instead. + */ export function string< const GenericDefinition extends PrepareDataParserDefinition< dataParsers.DataParserDefinitionString, @@ -16,14 +19,18 @@ export function string< >, GenericDefinition >, -): dataParsersExtended.DataParserStringExtended< +): dataParsersExtended.DataParserCoercerExtended< MergeDefinition< - dataParsers.DataParserDefinitionString, - NeverCoalescing & { coerce: true } + dataParsers.DataParserDefinitionCoercer, + { + inner: dataParsersExtended.DataParserStringExtended< + MergeDefinition< + dataParsers.DataParserDefinitionString, + NeverCoalescing + > + >; + } > > { - return dataParsersExtended.string({ - ...definition, - coerce: true, - }); + return dataParsersExtended.coercer(dataParsersExtended.string(definition)); } diff --git a/scripts/dataParser/extended/coerce/templateLiteral.ts b/scripts/dataParser/extended/coerce/templateLiteral.ts new file mode 100644 index 000000000..09f98bbe7 --- /dev/null +++ b/scripts/dataParser/extended/coerce/templateLiteral.ts @@ -0,0 +1,44 @@ +import type { FixDeepFunctionInfer, NeverCoalescing } from "@scripts/common"; +import type { MergeDefinition, PrepareDataParserDefinition } from "@scripts/dataParser/types"; +import type * as dataParsers from "../../parsers"; +import * as dataParsersExtended from ".."; + +/** + * @deprecated Use `dataParsersExtended.templateLiteral(...).coerce()` instead. + */ +export function templateLiteral< + const GenericTemplate extends dataParsers.TemplateLiteralShape, + const GenericDefinition extends PrepareDataParserDefinition< + dataParsers.DataParserDefinitionTemplateLiteral< + dataParsers.TemplateLiteralShapeOutput + >, + "template" | "pattern" + > = never, +>( + template: GenericTemplate, + definition?: FixDeepFunctionInfer< + PrepareDataParserDefinition< + dataParsers.DataParserDefinitionTemplateLiteral< + dataParsers.TemplateLiteralShapeOutput + >, + "template" | "pattern" + >, + GenericDefinition + >, +): dataParsersExtended.DataParserCoercerExtended< + MergeDefinition< + dataParsers.DataParserDefinitionCoercer< + dataParsers.TemplateLiteralShapeOutput + >, + { + inner: dataParsersExtended.DataParserTemplateLiteralExtended< + MergeDefinition< + dataParsers.DataParserDefinitionTemplateLiteral, + NeverCoalescing & { template: GenericTemplate } + > + >; + } + > + > { + return dataParsersExtended.coercer(dataParsersExtended.templateLiteral(template, definition)); +} diff --git a/scripts/dataParser/extended/coerce/time.ts b/scripts/dataParser/extended/coerce/time.ts index e1a872125..418af1d1a 100644 --- a/scripts/dataParser/extended/coerce/time.ts +++ b/scripts/dataParser/extended/coerce/time.ts @@ -1,8 +1,11 @@ -import { type FixDeepFunctionInfer, type NeverCoalescing } from "@scripts/common"; -import { type MergeDefinition, type PrepareDataParserDefinition } from "../../types"; +import type { FixDeepFunctionInfer, NeverCoalescing } from "@scripts/common"; +import type { MergeDefinition, PrepareDataParserDefinition } from "@scripts/dataParser/types"; import type * as dataParsers from "../../parsers"; import * as dataParsersExtended from ".."; +/** + * @deprecated Use `dataParsersExtended.time().coerce()` instead. + */ export function time< const GenericDefinition extends PrepareDataParserDefinition< dataParsers.DataParserDefinitionTime, @@ -16,14 +19,18 @@ export function time< >, GenericDefinition >, -): dataParsersExtended.DataParserTimeExtended< +): dataParsersExtended.DataParserCoercerExtended< MergeDefinition< - dataParsers.DataParserDefinitionTime, - NeverCoalescing & { coerce: true } + dataParsers.DataParserDefinitionCoercer, + { + inner: dataParsersExtended.DataParserTimeExtended< + MergeDefinition< + dataParsers.DataParserDefinitionTime, + NeverCoalescing + > + >; + } > > { - return dataParsersExtended.time({ - ...definition, - coerce: true, - }); + return dataParsersExtended.coercer(dataParsersExtended.time(definition)); } diff --git a/scripts/dataParser/extended/coercer.ts b/scripts/dataParser/extended/coercer.ts new file mode 100644 index 000000000..cfc3ce0e4 --- /dev/null +++ b/scripts/dataParser/extended/coercer.ts @@ -0,0 +1,82 @@ +import { type NeverCoalescing, type FixDeepFunctionInfer, detachObjectMethod } from "@scripts/common"; +import type { DataParserChecker } from "../baseChecker"; +import * as dataParsers from "../parsers"; +import type { AddCheckersToDefinition, Input, MergeDefinition, Output, PrepareDataParserDefinition } from "../types"; +import { DataParserBaseExtended } from "./base"; +import type { DataParser } from "../base"; + +export class DataParserCoercerExtended< + GenericDefinition extends dataParsers.DataParserDefinitionCoercer = dataParsers.DataParserDefinitionCoercer, +> extends DataParserBaseExtended.initExtended(dataParsers.DataParserCoercer)< + GenericDefinition, + Output>, + Input> + > { + public get classConstructor() { + return this.checkConstructor(DataParserCoercerExtended); + } + + public static transformers = dataParsers.DataParserCoercer.transformers; + + public declare addChecker: < + GenericChecker extends readonly [ + DataParserChecker>, + ...DataParserChecker>[], + ], + >( + ...args: FixDeepFunctionInfer< + readonly [ + DataParserChecker>, + ...DataParserChecker>[], + ], + GenericChecker + > + ) => DataParserCoercerExtended< + AddCheckersToDefinition< + GenericDefinition, + GenericChecker + > + >; + + public declare refine: ( + theFunction: (input: Output) => boolean, + definition?: Partial< + Omit + >, + ) => DataParserCoercerExtended< + AddCheckersToDefinition< + GenericDefinition, + readonly [dataParsers.CheckerRefineImplementation>] + > + >; + + public static override create< + GenericDataParser extends DataParser, + const GenericDefinition extends PrepareDataParserDefinition< + dataParsers.DataParserDefinitionCoercer< + Output + >, + "inner" + > = never, + >( + inner: GenericDataParser, + definition?: FixDeepFunctionInfer< + PrepareDataParserDefinition< + dataParsers.DataParserDefinitionCoercer< + Output + >, + "inner" + >, + GenericDefinition + >, + ): DataParserCoercerExtended< + MergeDefinition< + dataParsers.DataParserDefinitionCoercer, + NeverCoalescing & { inner: GenericDataParser } + > + > { + return new DataParserCoercerExtended(this.prepareDefinition(inner, definition)) as never; + } +} + +export const coercer = detachObjectMethod(DataParserCoercerExtended, "create"); diff --git a/scripts/dataParser/extended/date.ts b/scripts/dataParser/extended/date.ts index 2295ed1c5..57eafb53e 100644 --- a/scripts/dataParser/extended/date.ts +++ b/scripts/dataParser/extended/date.ts @@ -4,6 +4,7 @@ import { DataParserBaseExtended } from "./base"; import { type AddCheckersToDefinition, type Output, type MergeDefinition, type PrepareDataParserDefinition, type Input } from "../types"; import * as dataParsers from "../parsers"; import { type DataParserChecker } from "../baseChecker"; +import { DataParserCoercerExtended } from "./coercer"; export class DataParserDateExtended< GenericDefinition extends dataParsers.DataParserDefinitionDate = dataParsers.DataParserDefinitionDate, @@ -48,6 +49,17 @@ export class DataParserDateExtended< > >; + public coerce< + GenericThis extends this = this, + >(): DataParserCoercerExtended< + MergeDefinition< + dataParsers.DataParserDefinitionCoercer, + { inner: GenericThis } + > + > { + return DataParserCoercerExtended.create(this) as never; + } + /** * {@include dataParser/extended/date/index.md} */ diff --git a/scripts/dataParser/extended/empty.ts b/scripts/dataParser/extended/empty.ts index 96ed55aa5..de2d39e0d 100644 --- a/scripts/dataParser/extended/empty.ts +++ b/scripts/dataParser/extended/empty.ts @@ -4,6 +4,7 @@ import { DataParserBaseExtended } from "./base"; import { type AddCheckersToDefinition, type Output, type MergeDefinition, type PrepareDataParserDefinition, type Input } from "../types"; import * as dataParsers from "../parsers"; import { type DataParserChecker } from "../baseChecker"; +import { DataParserCoercerExtended } from "./coercer"; export class DataParserEmptyExtended< GenericDefinition extends dataParsers.DataParserDefinitionEmpty = dataParsers.DataParserDefinitionEmpty, @@ -48,6 +49,17 @@ export class DataParserEmptyExtended< > >; + public coerce< + GenericThis extends this = this, + >(): DataParserCoercerExtended< + MergeDefinition< + dataParsers.DataParserDefinitionCoercer, + { inner: GenericThis } + > + > { + return DataParserCoercerExtended.create(this) as never; + } + /** * {@include dataParser/extended/empty/index.md} */ diff --git a/scripts/dataParser/extended/index.ts b/scripts/dataParser/extended/index.ts index 55eb79ea6..2df92fc02 100644 --- a/scripts/dataParser/extended/index.ts +++ b/scripts/dataParser/extended/index.ts @@ -1,5 +1,3 @@ -export * as coerce from "./coerce"; - export * from "./string"; export * from "./array"; export * from "./bigint"; @@ -23,6 +21,12 @@ export * from "./tuple"; export * from "./unknown"; export * from "./recover"; export * from "./errorHandler"; +export * from "./coercer"; + +/** + * @deprecated Use the corresponding extended DataParser and call `.coerce()`. + */ +export * as coerce from "./coerce"; export * from "../error"; export { type DataParser } from "../base"; diff --git a/scripts/dataParser/extended/nil.ts b/scripts/dataParser/extended/nil.ts index e4c5ddd2e..f93ef5596 100644 --- a/scripts/dataParser/extended/nil.ts +++ b/scripts/dataParser/extended/nil.ts @@ -4,6 +4,7 @@ import { DataParserBaseExtended } from "./base"; import { type AddCheckersToDefinition, type Output, type MergeDefinition, type PrepareDataParserDefinition, type Input } from "../types"; import * as dataParsers from "../parsers"; import { type DataParserChecker } from "../baseChecker"; +import { DataParserCoercerExtended } from "./coercer"; export class DataParserNilExtended< GenericDefinition extends dataParsers.DataParserDefinitionNil = dataParsers.DataParserDefinitionNil, @@ -48,6 +49,17 @@ export class DataParserNilExtended< > >; + public coerce< + GenericThis extends this = this, + >(): DataParserCoercerExtended< + MergeDefinition< + dataParsers.DataParserDefinitionCoercer, + { inner: GenericThis } + > + > { + return DataParserCoercerExtended.create(this) as never; + } + /** * {@include dataParser/extended/nil/index.md} */ diff --git a/scripts/dataParser/extended/number.ts b/scripts/dataParser/extended/number.ts index e4393002a..c642d704b 100644 --- a/scripts/dataParser/extended/number.ts +++ b/scripts/dataParser/extended/number.ts @@ -3,6 +3,7 @@ import { DataParserBaseExtended } from "./base"; import { type AddCheckersToDefinition, type Output, type MergeDefinition, type PrepareDataParserDefinition, type Input } from "../types"; import * as dataParsers from "../parsers"; import { type DataParserChecker } from "../baseChecker"; +import { DataParserCoercerExtended } from "./coercer"; export class DataParserNumberExtended< GenericDefinition extends dataParsers.DataParserDefinitionNumber = dataParsers.DataParserDefinitionNumber, @@ -80,6 +81,17 @@ export class DataParserNumberExtended< return this.addChecker(dataParsers.checkerInt(definition)); } + public coerce< + GenericThis extends this = this, + >(): DataParserCoercerExtended< + MergeDefinition< + dataParsers.DataParserDefinitionCoercer, + { inner: GenericThis } + > + > { + return DataParserCoercerExtended.create(this) as never; + } + /** * {@include dataParser/extended/number/index.md} */ diff --git a/scripts/dataParser/extended/string.ts b/scripts/dataParser/extended/string.ts index afd4e8230..db86a921c 100644 --- a/scripts/dataParser/extended/string.ts +++ b/scripts/dataParser/extended/string.ts @@ -4,6 +4,7 @@ import { DataParserBaseExtended } from "./base"; import { type AddCheckersToDefinition, type Output, type MergeDefinition, type PrepareDataParserDefinition, type Input } from "../types"; import * as dataParsers from "../parsers"; import { type DataParserChecker } from "../baseChecker"; +import { DataParserCoercerExtended } from "./coercer"; export class DataParserStringExtended< GenericDefinition extends dataParsers.DataParserDefinitionString = dataParsers.DataParserDefinitionString, @@ -99,6 +100,17 @@ export class DataParserStringExtended< return this.addChecker(dataParsers.checkerRegex(regex, definition)); } + public coerce< + GenericThis extends this = this, + >(): DataParserCoercerExtended< + MergeDefinition< + dataParsers.DataParserDefinitionCoercer, + { inner: GenericThis } + > + > { + return DataParserCoercerExtended.create(this) as never; + } + /** * {@include dataParser/extended/string/index.md} */ diff --git a/scripts/dataParser/extended/templateLiteral.ts b/scripts/dataParser/extended/templateLiteral.ts index e2b213717..d3b8c6efc 100644 --- a/scripts/dataParser/extended/templateLiteral.ts +++ b/scripts/dataParser/extended/templateLiteral.ts @@ -3,6 +3,7 @@ import { DataParserBaseExtended } from "./base"; import { type AddCheckersToDefinition, type Output, type MergeDefinition, type PrepareDataParserDefinition, type Input } from "../types"; import * as dataParsers from "../parsers"; import { type DataParserChecker } from "../baseChecker"; +import { DataParserCoercerExtended } from "./coercer"; export class DataParserTemplateLiteralExtended< GenericDefinition extends dataParsers.DataParserDefinitionTemplateLiteral @@ -48,6 +49,17 @@ export class DataParserTemplateLiteralExtended< > >; + public coerce< + GenericThis extends this = this, + >(): DataParserCoercerExtended< + MergeDefinition< + dataParsers.DataParserDefinitionCoercer, + { inner: GenericThis } + > + > { + return DataParserCoercerExtended.create(this) as never; + } + /** * {@include dataParser/extended/templateLiteral/index.md} */ diff --git a/scripts/dataParser/extended/time.ts b/scripts/dataParser/extended/time.ts index ed50d58c4..b70fdc778 100644 --- a/scripts/dataParser/extended/time.ts +++ b/scripts/dataParser/extended/time.ts @@ -4,6 +4,7 @@ import { type AddCheckersToDefinition, type Output, type MergeDefinition, type P import * as dataParsers from "../parsers"; import { type DataParserChecker } from "../baseChecker"; import { type TheTime } from "@scripts/date"; +import { DataParserCoercerExtended } from "./coercer"; export class DataParserTimeExtended< GenericDefinition extends dataParsers.DataParserDefinitionTime = dataParsers.DataParserDefinitionTime, @@ -72,6 +73,17 @@ export class DataParserTimeExtended< return this.addChecker(dataParsers.checkerTimeMax(max, definition)); } + public coerce< + GenericThis extends this = this, + >(): DataParserCoercerExtended< + MergeDefinition< + dataParsers.DataParserDefinitionCoercer, + { inner: GenericThis } + > + > { + return DataParserCoercerExtended.create(this) as never; + } + /** * {@include dataParser/extended/time/index.md} */ diff --git a/scripts/dataParser/parsers/coerce/bigint.ts b/scripts/dataParser/parsers/coerce/bigint.ts index 300fe3d69..96254fa3e 100644 --- a/scripts/dataParser/parsers/coerce/bigint.ts +++ b/scripts/dataParser/parsers/coerce/bigint.ts @@ -2,6 +2,9 @@ import type { FixDeepFunctionInfer, NeverCoalescing } from "@scripts/common"; import type { MergeDefinition, PrepareDataParserDefinition } from "@scripts/dataParser/types"; import * as dataParsers from ".."; +/** + * @deprecated Use `DP.coercer(DP.bigint())` instead. + */ export function bigint< const GenericDefinition extends PrepareDataParserDefinition< dataParsers.DataParserDefinitionBigInt, diff --git a/scripts/dataParser/parsers/coerce/boolean.ts b/scripts/dataParser/parsers/coerce/boolean.ts index 9b7ac0498..87f48bffb 100644 --- a/scripts/dataParser/parsers/coerce/boolean.ts +++ b/scripts/dataParser/parsers/coerce/boolean.ts @@ -2,6 +2,9 @@ import type { FixDeepFunctionInfer, NeverCoalescing } from "@scripts/common"; import type { MergeDefinition, PrepareDataParserDefinition } from "@scripts/dataParser/types"; import * as dataParsers from ".."; +/** + * @deprecated Use `DP.coercer(DP.boolean())` instead. + */ export function boolean< const GenericDefinition extends PrepareDataParserDefinition< dataParsers.DataParserDefinitionBoolean, diff --git a/scripts/dataParser/parsers/coerce/date.ts b/scripts/dataParser/parsers/coerce/date.ts index 4937bac6a..ad8599e85 100644 --- a/scripts/dataParser/parsers/coerce/date.ts +++ b/scripts/dataParser/parsers/coerce/date.ts @@ -2,6 +2,9 @@ import type { FixDeepFunctionInfer, NeverCoalescing } from "@scripts/common"; import type { MergeDefinition, PrepareDataParserDefinition } from "@scripts/dataParser/types"; import * as dataParsers from ".."; +/** + * @deprecated Use `DP.coercer(DP.date())` instead. + */ export function date< const GenericDefinition extends PrepareDataParserDefinition< dataParsers.DataParserDefinitionDate, diff --git a/scripts/dataParser/parsers/coerce/empty.ts b/scripts/dataParser/parsers/coerce/empty.ts index 842adf295..864aef14d 100644 --- a/scripts/dataParser/parsers/coerce/empty.ts +++ b/scripts/dataParser/parsers/coerce/empty.ts @@ -2,6 +2,9 @@ import type { FixDeepFunctionInfer, NeverCoalescing } from "@scripts/common"; import type { MergeDefinition, PrepareDataParserDefinition } from "@scripts/dataParser/types"; import * as dataParsers from ".."; +/** + * @deprecated Use `DP.coercer(DP.empty())` instead. + */ export function empty< const GenericDefinition extends PrepareDataParserDefinition< dataParsers.DataParserDefinitionEmpty, diff --git a/scripts/dataParser/parsers/coerce/nil.ts b/scripts/dataParser/parsers/coerce/nil.ts index b4902b0b6..3bd347462 100644 --- a/scripts/dataParser/parsers/coerce/nil.ts +++ b/scripts/dataParser/parsers/coerce/nil.ts @@ -2,6 +2,9 @@ import type { FixDeepFunctionInfer, NeverCoalescing } from "@scripts/common"; import type { MergeDefinition, PrepareDataParserDefinition } from "@scripts/dataParser/types"; import * as dataParsers from ".."; +/** + * @deprecated Use `DP.coercer(DP.nil())` instead. + */ export function nil< const GenericDefinition extends PrepareDataParserDefinition< dataParsers.DataParserDefinitionNil, diff --git a/scripts/dataParser/parsers/coerce/number.ts b/scripts/dataParser/parsers/coerce/number.ts index 0028b6f2a..1911d5c2a 100644 --- a/scripts/dataParser/parsers/coerce/number.ts +++ b/scripts/dataParser/parsers/coerce/number.ts @@ -2,6 +2,9 @@ import type { FixDeepFunctionInfer, NeverCoalescing } from "@scripts/common"; import type { MergeDefinition, PrepareDataParserDefinition } from "@scripts/dataParser/types"; import * as dataParsers from ".."; +/** + * @deprecated Use `DP.coercer(DP.number())` instead. + */ export function number< const GenericDefinition extends PrepareDataParserDefinition< dataParsers.DataParserDefinitionNumber, diff --git a/scripts/dataParser/parsers/coerce/string.ts b/scripts/dataParser/parsers/coerce/string.ts index e91460ae3..1a9a1850c 100644 --- a/scripts/dataParser/parsers/coerce/string.ts +++ b/scripts/dataParser/parsers/coerce/string.ts @@ -2,6 +2,9 @@ import type { FixDeepFunctionInfer, NeverCoalescing } from "@scripts/common"; import type { MergeDefinition, PrepareDataParserDefinition } from "@scripts/dataParser/types"; import * as dataParsers from ".."; +/** + * @deprecated Use `DP.coercer(DP.string())` instead. + */ export function string< const GenericDefinition extends PrepareDataParserDefinition< dataParsers.DataParserDefinitionString, diff --git a/scripts/dataParser/parsers/coerce/templateLiteral.ts b/scripts/dataParser/parsers/coerce/templateLiteral.ts index 9a51293fe..866f19d4c 100644 --- a/scripts/dataParser/parsers/coerce/templateLiteral.ts +++ b/scripts/dataParser/parsers/coerce/templateLiteral.ts @@ -2,6 +2,9 @@ import type { FixDeepFunctionInfer, NeverCoalescing } from "@scripts/common"; import type { MergeDefinition, PrepareDataParserDefinition } from "@scripts/dataParser/types"; import * as dataParsers from ".."; +/** + * @deprecated Use `DP.coercer(DP.templateLiteral())` instead. + */ export function templateLiteral< const GenericTemplate extends dataParsers.TemplateLiteralShape, const GenericDefinition extends PrepareDataParserDefinition< diff --git a/scripts/dataParser/parsers/coerce/time.ts b/scripts/dataParser/parsers/coerce/time.ts index 90a059341..886dc3fed 100644 --- a/scripts/dataParser/parsers/coerce/time.ts +++ b/scripts/dataParser/parsers/coerce/time.ts @@ -2,6 +2,9 @@ import type { FixDeepFunctionInfer } from "@scripts/common"; import type { PrepareDataParserDefinition } from "@scripts/dataParser/types"; import * as dataParsers from ".."; +/** + * @deprecated Use `DP.coercer(DP.time())` instead. + */ export function time< const GenericDefinition extends PrepareDataParserDefinition< dataParsers.DataParserDefinitionTime, diff --git a/scripts/dataParser/parsers/index.ts b/scripts/dataParser/parsers/index.ts index 7356c6d70..c1ac39ccb 100644 --- a/scripts/dataParser/parsers/index.ts +++ b/scripts/dataParser/parsers/index.ts @@ -23,4 +23,8 @@ export * from "./union"; export * from "./unknown"; export * from "./errorHandler"; export * from "./coercer"; + +/** + * @deprecated + */ export * as coerce from "./coerce"; diff --git a/scripts/index.ts b/scripts/index.ts index f43d5267a..1d4390fc8 100644 --- a/scripts/index.ts +++ b/scripts/index.ts @@ -25,7 +25,14 @@ export * as DPattern from "./pattern"; export * as DP from "./dataParser"; export * as DDataParser from "./dataParser"; +/** + * @deprecated Use the corresponding extended DataParser and call `.coerce()`. + */ export * as DPC from "./dataParser/parsers/coerce"; + +/** + * @deprecated Use the corresponding extended DataParser and call `.coerce()`. + */ export * as DDataParserCoerce from "./dataParser/parsers/coerce"; export * as DPE from "./dataParser/extended"; From 6332668c4b7fc4c9fda794da04f8810a61f8c0e5 Mon Sep 17 00:00:00 2001 From: ZeRiix Date: Sun, 12 Jul 2026 21:36:00 +0200 Subject: [PATCH 6/8] feat(94): add tests and jsdoc dataParser coercer --- jsDoc/dataParser/classic/array/example.ts | 5 +- jsDoc/dataParser/classic/array/index.md | 2 +- .../dataParser/classic/base/clone/example.ts | 2 +- .../dataParser/classic/base/parse/example.ts | 8 +- .../classic/base/parseOrThrow/example.ts | 5 +- .../classic/base/parseOrThrow/index.md | 2 +- jsDoc/dataParser/classic/bigint/example.ts | 7 +- jsDoc/dataParser/classic/bigint/index.md | 2 +- jsDoc/dataParser/classic/boolean/example.ts | 7 +- jsDoc/dataParser/classic/boolean/index.md | 2 +- .../classic/checkerTimeMax/example.ts | 5 +- .../classic/checkerTimeMax/index.md | 2 +- .../classic/checkerTimeMin/example.ts | 5 +- .../classic/checkerTimeMin/index.md | 2 +- jsDoc/dataParser/classic/coercer/example.ts | 23 ++++++ jsDoc/dataParser/classic/coercer/index.md | 18 +++++ .../classic/coercer/transformers/example.ts | 13 +++ .../classic/coercer/transformers/index.md | 18 +++++ jsDoc/dataParser/classic/date/example.ts | 7 +- jsDoc/dataParser/classic/date/index.md | 4 +- jsDoc/dataParser/classic/empty/example.ts | 7 +- jsDoc/dataParser/classic/empty/index.md | 2 +- jsDoc/dataParser/classic/nil/example.ts | 7 +- jsDoc/dataParser/classic/nil/index.md | 2 +- jsDoc/dataParser/classic/nullable/example.ts | 5 +- jsDoc/dataParser/classic/nullable/index.md | 2 +- jsDoc/dataParser/classic/number/example.ts | 7 +- jsDoc/dataParser/classic/number/index.md | 2 +- jsDoc/dataParser/classic/optional/example.ts | 5 +- jsDoc/dataParser/classic/optional/index.md | 2 +- jsDoc/dataParser/classic/pipe/example.ts | 2 +- jsDoc/dataParser/classic/record/example.ts | 5 +- jsDoc/dataParser/classic/record/index.md | 2 +- jsDoc/dataParser/classic/string/example.ts | 7 +- jsDoc/dataParser/classic/string/index.md | 2 +- .../classic/templateLiteral/example.ts | 5 +- .../classic/templateLiteral/index.md | 2 +- jsDoc/dataParser/classic/time/example.ts | 7 +- jsDoc/dataParser/classic/time/index.md | 4 +- jsDoc/dataParser/classic/union/example.ts | 5 +- jsDoc/dataParser/classic/union/index.md | 2 +- .../dataParser/extended/base/array/example.ts | 6 +- .../extended/base/nullable/example.ts | 6 +- .../extended/base/optional/example.ts | 6 +- jsDoc/dataParser/extended/base/or/example.ts | 6 +- .../dataParser/extended/base/pipe/example.ts | 2 +- .../extended/bigint/coerce/example.ts | 18 +++++ .../extended/bigint/coerce/index.md | 17 ++++ jsDoc/dataParser/extended/bigint/example.ts | 2 +- .../extended/boolean/coerce/example.ts | 16 ++++ .../extended/boolean/coerce/index.md | 17 ++++ jsDoc/dataParser/extended/boolean/example.ts | 2 +- jsDoc/dataParser/extended/coercer/example.ts | 25 ++++++ jsDoc/dataParser/extended/coercer/index.md | 19 +++++ .../extended/coercer/transformers/example.ts | 17 ++++ .../extended/coercer/transformers/index.md | 18 +++++ .../extended/date/coerce/example.ts | 16 ++++ .../dataParser/extended/date/coerce/index.md | 17 ++++ jsDoc/dataParser/extended/date/example.ts | 2 +- jsDoc/dataParser/extended/date/index.md | 2 +- .../extended/empty/coerce/example.ts | 17 ++++ .../dataParser/extended/empty/coerce/index.md | 17 ++++ jsDoc/dataParser/extended/empty/example.ts | 2 +- .../dataParser/extended/nil/coerce/example.ts | 18 +++++ jsDoc/dataParser/extended/nil/coerce/index.md | 17 ++++ jsDoc/dataParser/extended/nil/example.ts | 2 +- .../extended/number/coerce/example.ts | 18 +++++ .../extended/number/coerce/index.md | 17 ++++ jsDoc/dataParser/extended/number/example.ts | 2 +- jsDoc/dataParser/extended/pipe/example.ts | 2 +- .../extended/string/coerce/example.ts | 18 +++++ .../extended/string/coerce/index.md | 17 ++++ jsDoc/dataParser/extended/string/example.ts | 4 +- .../templateLiteral/coerce/example.ts | 19 +++++ .../extended/templateLiteral/coerce/index.md | 17 ++++ .../extended/time/coerce/example.ts | 18 +++++ .../dataParser/extended/time/coerce/index.md | 17 ++++ jsDoc/dataParser/extended/time/example.ts | 2 +- jsDoc/dataParser/extended/time/index.md | 2 +- scripts/dataParser/extended/bigint.ts | 3 + scripts/dataParser/extended/boolean.ts | 3 + scripts/dataParser/extended/coerce/bigint.ts | 18 ++--- scripts/dataParser/extended/coerce/boolean.ts | 19 ++--- scripts/dataParser/extended/coerce/date.ts | 19 ++--- scripts/dataParser/extended/coerce/empty.ts | 19 ++--- scripts/dataParser/extended/coerce/index.ts | 1 - scripts/dataParser/extended/coerce/nil.ts | 18 ++--- scripts/dataParser/extended/coerce/number.ts | 18 ++--- scripts/dataParser/extended/coerce/string.ts | 18 ++--- .../extended/coerce/templateLiteral.ts | 44 ----------- scripts/dataParser/extended/coerce/time.ts | 18 ++--- scripts/dataParser/extended/coercer.ts | 9 +++ scripts/dataParser/extended/date.ts | 3 + scripts/dataParser/extended/empty.ts | 3 + scripts/dataParser/extended/index.ts | 2 +- scripts/dataParser/extended/nil.ts | 3 + scripts/dataParser/extended/number.ts | 3 + scripts/dataParser/extended/string.ts | 3 + .../dataParser/extended/templateLiteral.ts | 3 + scripts/dataParser/extended/time.ts | 3 + scripts/dataParser/parsers/bigint/index.ts | 2 +- scripts/dataParser/parsers/boolean.ts | 2 +- scripts/dataParser/parsers/coerce/bigint.ts | 18 ++--- scripts/dataParser/parsers/coerce/boolean.ts | 18 ++--- scripts/dataParser/parsers/coerce/date.ts | 18 ++--- scripts/dataParser/parsers/coerce/empty.ts | 18 ++--- scripts/dataParser/parsers/coerce/index.ts | 1 - scripts/dataParser/parsers/coerce/nil.ts | 18 ++--- scripts/dataParser/parsers/coerce/number.ts | 19 ++--- scripts/dataParser/parsers/coerce/string.ts | 19 ++--- .../parsers/coerce/templateLiteral.ts | 43 ---------- scripts/dataParser/parsers/coerce/time.ts | 16 +++- scripts/dataParser/parsers/coercer/index.ts | 9 +++ scripts/dataParser/parsers/date.ts | 2 +- scripts/dataParser/parsers/empty.ts | 2 +- scripts/dataParser/parsers/index.ts | 2 +- scripts/dataParser/parsers/nil.ts | 2 +- scripts/dataParser/parsers/number/index.ts | 2 +- scripts/dataParser/parsers/string/index.ts | 2 +- scripts/dataParser/parsers/time/index.ts | 2 +- tests/dataParser/extended/bigint.test.ts | 18 +++++ tests/dataParser/extended/boolean.test.ts | 18 +++++ tests/dataParser/extended/coercer.test.ts | 79 +++++++++++++++++++ tests/dataParser/extended/date.test.ts | 18 +++++ tests/dataParser/extended/empty.test.ts | 18 +++++ tests/dataParser/extended/nil.test.ts | 18 +++++ tests/dataParser/extended/number.test.ts | 18 +++++ tests/dataParser/extended/string.test.ts | 18 +++++ .../extended/templateLiteral.test.ts | 18 +++++ tests/dataParser/extended/time.test.ts | 18 +++++ .../dataParser/parsers/coerce/bigint.test.ts | 12 --- .../dataParser/parsers/coerce/boolean.test.ts | 12 --- tests/dataParser/parsers/coerce/date.test.ts | 12 --- tests/dataParser/parsers/coerce/empty.test.ts | 12 --- tests/dataParser/parsers/coerce/nil.test.ts | 12 --- .../dataParser/parsers/coerce/number.test.ts | 12 --- .../dataParser/parsers/coerce/string.test.ts | 12 --- .../parsers/coerce/templateLiteral.test.ts | 63 --------------- tests/dataParser/parsers/coerce/time.test.ts | 12 --- .../dataParser/parsers/coercer/index.test.ts | 23 +++++- .../coercer/transformers/bigint.test.ts | 38 +++++++++ .../coercer/transformers/boolean.test.ts | 40 ++++++++++ .../parsers/coercer/transformers/date.test.ts | 40 ++++++++++ .../coercer/transformers/empty.test.ts | 36 +++++++++ .../parsers/coercer/transformers/nil.test.ts | 36 +++++++++ .../coercer/transformers/number.test.ts | 40 ++++++++++ .../coercer/transformers/string.test.ts | 40 ++++++++++ .../transformers/templateLiteral.test.ts | 47 +++++++++++ .../parsers/coercer/transformers/time.test.ts | 38 +++++++++ 149 files changed, 1347 insertions(+), 539 deletions(-) create mode 100644 jsDoc/dataParser/classic/coercer/example.ts create mode 100644 jsDoc/dataParser/classic/coercer/index.md create mode 100644 jsDoc/dataParser/classic/coercer/transformers/example.ts create mode 100644 jsDoc/dataParser/classic/coercer/transformers/index.md create mode 100644 jsDoc/dataParser/extended/bigint/coerce/example.ts create mode 100644 jsDoc/dataParser/extended/bigint/coerce/index.md create mode 100644 jsDoc/dataParser/extended/boolean/coerce/example.ts create mode 100644 jsDoc/dataParser/extended/boolean/coerce/index.md create mode 100644 jsDoc/dataParser/extended/coercer/example.ts create mode 100644 jsDoc/dataParser/extended/coercer/index.md create mode 100644 jsDoc/dataParser/extended/coercer/transformers/example.ts create mode 100644 jsDoc/dataParser/extended/coercer/transformers/index.md create mode 100644 jsDoc/dataParser/extended/date/coerce/example.ts create mode 100644 jsDoc/dataParser/extended/date/coerce/index.md create mode 100644 jsDoc/dataParser/extended/empty/coerce/example.ts create mode 100644 jsDoc/dataParser/extended/empty/coerce/index.md create mode 100644 jsDoc/dataParser/extended/nil/coerce/example.ts create mode 100644 jsDoc/dataParser/extended/nil/coerce/index.md create mode 100644 jsDoc/dataParser/extended/number/coerce/example.ts create mode 100644 jsDoc/dataParser/extended/number/coerce/index.md create mode 100644 jsDoc/dataParser/extended/string/coerce/example.ts create mode 100644 jsDoc/dataParser/extended/string/coerce/index.md create mode 100644 jsDoc/dataParser/extended/templateLiteral/coerce/example.ts create mode 100644 jsDoc/dataParser/extended/templateLiteral/coerce/index.md create mode 100644 jsDoc/dataParser/extended/time/coerce/example.ts create mode 100644 jsDoc/dataParser/extended/time/coerce/index.md delete mode 100644 scripts/dataParser/extended/coerce/templateLiteral.ts delete mode 100644 scripts/dataParser/parsers/coerce/templateLiteral.ts create mode 100644 tests/dataParser/extended/coercer.test.ts delete mode 100644 tests/dataParser/parsers/coerce/templateLiteral.test.ts create mode 100644 tests/dataParser/parsers/coercer/transformers/bigint.test.ts create mode 100644 tests/dataParser/parsers/coercer/transformers/boolean.test.ts create mode 100644 tests/dataParser/parsers/coercer/transformers/date.test.ts create mode 100644 tests/dataParser/parsers/coercer/transformers/empty.test.ts create mode 100644 tests/dataParser/parsers/coercer/transformers/nil.test.ts create mode 100644 tests/dataParser/parsers/coercer/transformers/number.test.ts create mode 100644 tests/dataParser/parsers/coercer/transformers/string.test.ts create mode 100644 tests/dataParser/parsers/coercer/transformers/templateLiteral.test.ts create mode 100644 tests/dataParser/parsers/coercer/transformers/time.test.ts diff --git a/jsDoc/dataParser/classic/array/example.ts b/jsDoc/dataParser/classic/array/example.ts index 383a5bdf2..553c28655 100644 --- a/jsDoc/dataParser/classic/array/example.ts +++ b/jsDoc/dataParser/classic/array/example.ts @@ -7,9 +7,8 @@ if (E.isRight(result)) { // value: string[] } -const withCheckers = DP.array(DP.number(), { - checkers: [DP.checkerArrayMin(1), DP.checkerArrayMax(3)], -}); +const withCheckers = DP.array(DP.number()) + .addChecker(DP.checkerArrayMin(1), DP.checkerArrayMax(3)); const nested = DP.array(DP.array(DP.boolean())); const nestedResult = nested.parse([[true, false]]); diff --git a/jsDoc/dataParser/classic/array/index.md b/jsDoc/dataParser/classic/array/index.md index 8aa03090f..264286d62 100644 --- a/jsDoc/dataParser/classic/array/index.md +++ b/jsDoc/dataParser/classic/array/index.md @@ -7,7 +7,7 @@ Creates a data parser for arrays of a given element parser. Validates that the input is an array and validates each element with the provided parser. ```ts -{@include dataParser/classic/array/example.ts[3,15]} +{@include dataParser/classic/array/example.ts[3,14]} ``` @see https://utils.duplojs.dev/en/v1/api/dataParser/array diff --git a/jsDoc/dataParser/classic/base/clone/example.ts b/jsDoc/dataParser/classic/base/clone/example.ts index e948c6d5c..fe85af6c6 100644 --- a/jsDoc/dataParser/classic/base/clone/example.ts +++ b/jsDoc/dataParser/classic/base/clone/example.ts @@ -8,5 +8,5 @@ const withMin = DP.string() const withMinClone = withMin.clone(); -const coerceNumber = DP.coerce.number(); +const coerceNumber = DP.coercer(DP.number()); const coerceNumberClone = coerceNumber.clone(); diff --git a/jsDoc/dataParser/classic/base/parse/example.ts b/jsDoc/dataParser/classic/base/parse/example.ts index 3bf487f21..049debd6c 100644 --- a/jsDoc/dataParser/classic/base/parse/example.ts +++ b/jsDoc/dataParser/classic/base/parse/example.ts @@ -8,16 +8,16 @@ if (E.isRight(result)) { // value: string } -const resultWithError = DP.string({ - checkers: [DP.checkerStringMin(3)], -}).parse("ok"); +const resultWithError = DP.string() + .addChecker(DP.checkerStringMin(3)) + .parse("ok"); if (E.isLeft(resultWithError)) { const error = unwrap(resultWithError); // error: DP.DataParserError } -const numberSchema = DP.coerce.number(); +const numberSchema = DP.coercer(DP.number()); const numberResult = numberSchema.parse("42"); if (E.isRight(numberResult)) { const value = unwrap(numberResult); diff --git a/jsDoc/dataParser/classic/base/parseOrThrow/example.ts b/jsDoc/dataParser/classic/base/parseOrThrow/example.ts index a22ab0eb4..44e09a377 100644 --- a/jsDoc/dataParser/classic/base/parseOrThrow/example.ts +++ b/jsDoc/dataParser/classic/base/parseOrThrow/example.ts @@ -1,8 +1,7 @@ import { DP } from "@scripts"; -const stringSchema = DP.string({ - checkers: [DP.checkerStringMin(3)], -}); +const stringSchema = DP.string() + .addChecker(DP.checkerStringMin(3)); const value = stringSchema.parseOrThrow("DuploJS"); // value: string diff --git a/jsDoc/dataParser/classic/base/parseOrThrow/index.md b/jsDoc/dataParser/classic/base/parseOrThrow/index.md index a33aeea8d..300e05c9f 100644 --- a/jsDoc/dataParser/classic/base/parseOrThrow/index.md +++ b/jsDoc/dataParser/classic/base/parseOrThrow/index.md @@ -6,7 +6,7 @@ The parseOrThrow() method runs a data parser synchronously and returns the parse It executes the parser, applies all registered checkers, and never mutates the input. ```ts -{@include dataParser/classic/base/parseOrThrow/example.ts[3,23]} +{@include dataParser/classic/base/parseOrThrow/example.ts[3,15]} ``` @namespace DP diff --git a/jsDoc/dataParser/classic/bigint/example.ts b/jsDoc/dataParser/classic/bigint/example.ts index cbb19a70e..50e332af0 100644 --- a/jsDoc/dataParser/classic/bigint/example.ts +++ b/jsDoc/dataParser/classic/bigint/example.ts @@ -7,9 +7,8 @@ if (E.isRight(result)) { // value: bigint } -const withCheckers = DP.bigint({ - checkers: [DP.checkerBigIntMin(BigInt(1)), DP.checkerBigIntMax(BigInt(10))], -}); +const withCheckers = DP.bigint() + .addChecker(DP.checkerBigIntMin(BigInt(1)), DP.checkerBigIntMax(BigInt(10))); -const coerceParser = DP.coerce.bigint(); +const coerceParser = DP.coercer(DP.bigint()); const coerceResult = coerceParser.parse("42"); diff --git a/jsDoc/dataParser/classic/bigint/index.md b/jsDoc/dataParser/classic/bigint/index.md index 4bbbaed5d..72b716bda 100644 --- a/jsDoc/dataParser/classic/bigint/index.md +++ b/jsDoc/dataParser/classic/bigint/index.md @@ -7,7 +7,7 @@ Creates a data parser for bigint values. Validates that the input is a bigint, optionally applies coerce, and runs the configured checkers. ```ts -{@include dataParser/classic/bigint/example.ts[3,15]} +{@include dataParser/classic/bigint/example.ts[3,14]} ``` @see https://utils.duplojs.dev/en/v1/api/dataParser/bigint diff --git a/jsDoc/dataParser/classic/boolean/example.ts b/jsDoc/dataParser/classic/boolean/example.ts index 5ac649303..cb55ee825 100644 --- a/jsDoc/dataParser/classic/boolean/example.ts +++ b/jsDoc/dataParser/classic/boolean/example.ts @@ -7,9 +7,8 @@ if (E.isRight(result)) { // value: boolean } -const onlyTrue = DP.boolean({ - checkers: [DP.checkerRefine((value) => value === true)], -}); +const onlyTrue = DP.boolean() + .addChecker(DP.checkerRefine((value) => value === true)); -const coerceParser = DP.coerce.boolean(); +const coerceParser = DP.coercer(DP.boolean()); const coerceResult = coerceParser.parse("false"); diff --git a/jsDoc/dataParser/classic/boolean/index.md b/jsDoc/dataParser/classic/boolean/index.md index 17e30bb3a..2e4773b8a 100644 --- a/jsDoc/dataParser/classic/boolean/index.md +++ b/jsDoc/dataParser/classic/boolean/index.md @@ -7,7 +7,7 @@ Creates a data parser for boolean values. Validates that the input is a boolean, optionally applies coerce, and runs the configured checkers. ```ts -{@include dataParser/classic/boolean/example.ts[3,15]} +{@include dataParser/classic/boolean/example.ts[3,14]} ``` @see https://utils.duplojs.dev/en/v1/api/dataParser/boolean diff --git a/jsDoc/dataParser/classic/checkerTimeMax/example.ts b/jsDoc/dataParser/classic/checkerTimeMax/example.ts index 0247dd968..c65423523 100644 --- a/jsDoc/dataParser/classic/checkerTimeMax/example.ts +++ b/jsDoc/dataParser/classic/checkerTimeMax/example.ts @@ -1,8 +1,7 @@ import { D, DP } from "@scripts"; -const parser = DP.time({ - checkers: [DP.checkerTimeMax(D.createTime(2, "minute"))], -}); +const parser = DP.time() + .addChecker(DP.checkerTimeMax(D.createTime(2, "minute"))); const valid = parser.parse("time1500+"); // valid: Error | Success diff --git a/jsDoc/dataParser/classic/checkerTimeMax/index.md b/jsDoc/dataParser/classic/checkerTimeMax/index.md index 50aab4898..f4b23b924 100644 --- a/jsDoc/dataParser/classic/checkerTimeMax/index.md +++ b/jsDoc/dataParser/classic/checkerTimeMax/index.md @@ -5,7 +5,7 @@ Signature: `checkerTimeMax(max, definition?)` → `DataParserCheckerTimeMax` The checker passes when parsed value is less than or equal to `max`. ```ts -{@include dataParser/classic/checkerTimeMax/example.ts[3,13]} +{@include dataParser/classic/checkerTimeMax/example.ts[3,10]} ``` @remarks diff --git a/jsDoc/dataParser/classic/checkerTimeMin/example.ts b/jsDoc/dataParser/classic/checkerTimeMin/example.ts index 4923f0de6..45fe22f35 100644 --- a/jsDoc/dataParser/classic/checkerTimeMin/example.ts +++ b/jsDoc/dataParser/classic/checkerTimeMin/example.ts @@ -1,8 +1,7 @@ import { D, DP } from "@scripts"; -const parser = DP.time({ - checkers: [DP.checkerTimeMin(D.createTime(1, "minute"))], -}); +const parser = DP.time() + .addChecker(DP.checkerTimeMin(D.createTime(1, "minute"))); const valid = parser.parse("time1500+"); // valid: Error | Success diff --git a/jsDoc/dataParser/classic/checkerTimeMin/index.md b/jsDoc/dataParser/classic/checkerTimeMin/index.md index 2bfb6f6f4..8ab987239 100644 --- a/jsDoc/dataParser/classic/checkerTimeMin/index.md +++ b/jsDoc/dataParser/classic/checkerTimeMin/index.md @@ -5,7 +5,7 @@ Signature: `checkerTimeMin(min, definition?)` → `DataParserCheckerTimeMin` The checker passes when parsed value is greater than or equal to `min`. ```ts -{@include dataParser/classic/checkerTimeMin/example.ts[3,13]} +{@include dataParser/classic/checkerTimeMin/example.ts[3,10]} ``` @remarks diff --git a/jsDoc/dataParser/classic/coercer/example.ts b/jsDoc/dataParser/classic/coercer/example.ts new file mode 100644 index 000000000..f31a0e0f9 --- /dev/null +++ b/jsDoc/dataParser/classic/coercer/example.ts @@ -0,0 +1,23 @@ +import { DP, E, unwrap } from "@scripts"; + +const parser = DP.coercer(DP.number()); +const result = parser.parse("42"); +if (E.isRight(result)) { + const value = unwrap(result); + // value: number +} + +const withCheckers = DP.coercer( + DP.string(), +).addChecker(DP.checkerStringMin(3)); + +const checkedResult = withCheckers.parse(42); + +const complex = DP.object({ + name: DP.coercer(DP.string()), + age: DP.coercer(DP.number()), +}); +const complexResult = complex.parse({ + name: 123, + age: "42", +}); diff --git a/jsDoc/dataParser/classic/coercer/index.md b/jsDoc/dataParser/classic/coercer/index.md new file mode 100644 index 000000000..36e91c0bb --- /dev/null +++ b/jsDoc/dataParser/classic/coercer/index.md @@ -0,0 +1,18 @@ +Creates a classic coercer parser around another DataParser. + +Signature: `DP.coercer(inner, definition?)` -> `DataParserCoercer` + +This parser does not represent a data type by itself. It represents a parsing action, like `DP.pipe(...)` or `DP.lazy(...)`: it receives an input, transforms it with the transformer registered for the inner parser kind, then gives the transformed value to the inner parser. + +```ts +{@include dataParser/classic/coercer/example.ts[3,23]} +``` + +@remarks +- Parsed output is always the output of the inner parser. +- Accepted input is widened with the values supported by the inner parser coercion transformer. +- If no transformer is registered for the inner parser kind, the coercer still runs the inner parser with the original value. + +@see https://utils.duplojs.dev/en/v1/api/dataParser/coercer + +@namespace DP diff --git a/jsDoc/dataParser/classic/coercer/transformers/example.ts b/jsDoc/dataParser/classic/coercer/transformers/example.ts new file mode 100644 index 000000000..ee88f677b --- /dev/null +++ b/jsDoc/dataParser/classic/coercer/transformers/example.ts @@ -0,0 +1,13 @@ +import { DP } from "@scripts"; + +const numberTransformer = DP.DataParserCoercer.transformers.get( + DP.numberKind, +); + +DP.DataParserCoercer.transformers.set( + DP.stringKind, + (value) => String(value), +); + +const parser = DP.coercer(DP.string()); +const result = parser.parse(42); diff --git a/jsDoc/dataParser/classic/coercer/transformers/index.md b/jsDoc/dataParser/classic/coercer/transformers/index.md new file mode 100644 index 000000000..a3f70abcf --- /dev/null +++ b/jsDoc/dataParser/classic/coercer/transformers/index.md @@ -0,0 +1,18 @@ +Defines the transformer map used by classic coercer parsers. + +Signature: `DP.DataParserCoercer.transformers` -> `Map` + +The map links a DataParser kind to the function that prepares raw input before the inner parser runs. This is what makes `DP.coercer(DP.number())` accept values like numeric strings while still returning the output of `DP.number()`. + +```ts +{@include dataParser/classic/coercer/transformers/example.ts[3,13]} +``` + +@remarks +- This property is the source of truth for the parser kinds that support coercion. +- A coercer memoizes the transformer it resolves during its first parse. +- Changing this map is an advanced extension point. + +@see https://utils.duplojs.dev/en/v1/api/dataParser/coercer + +@namespace DP diff --git a/jsDoc/dataParser/classic/date/example.ts b/jsDoc/dataParser/classic/date/example.ts index 159be2dce..7d2d77d60 100644 --- a/jsDoc/dataParser/classic/date/example.ts +++ b/jsDoc/dataParser/classic/date/example.ts @@ -7,12 +7,11 @@ if (E.isRight(result)) { // value: TheDate } -const withCheckers = DP.date({ - checkers: [DP.checkerRefine((value) => value.getUTCFullYear() >= 2024)], -}); +const withCheckers = DP.date() + .addChecker(DP.checkerRefine((value) => value.getUTCFullYear() >= 2024)); const checked = withCheckers.parse("date1704067200000+"); // checked: E.Error | E.Success -const coerceParser = DP.coerce.date(); +const coerceParser = DP.coercer(DP.date()); const coerceResult = coerceParser.parse("2024-01-01T00:00:00.000Z"); // coerceResult: E.Error | E.Success diff --git a/jsDoc/dataParser/classic/date/index.md b/jsDoc/dataParser/classic/date/index.md index f92e20f41..62fd45ed3 100644 --- a/jsDoc/dataParser/classic/date/index.md +++ b/jsDoc/dataParser/classic/date/index.md @@ -6,12 +6,12 @@ The parser accepts `TheDate`, `SerializedTheDate`, and native `Date`. With `coerce: true`, safe timestamps and parsable date strings are also supported. ```ts -{@include dataParser/classic/date/example.ts[3,18]} +{@include dataParser/classic/date/example.ts[3,17]} ``` @remarks - Parsed output is always `TheDate`. -- Use `DP.coerce.date()` when you want coercion enabled by default. +- Use `DP.coercer(DP.date())` when you want coercion enabled by default. @see https://utils.duplojs.dev/en/v1/api/dataParser/date diff --git a/jsDoc/dataParser/classic/empty/example.ts b/jsDoc/dataParser/classic/empty/example.ts index 1b2f192d4..f0ad0585d 100644 --- a/jsDoc/dataParser/classic/empty/example.ts +++ b/jsDoc/dataParser/classic/empty/example.ts @@ -6,9 +6,8 @@ if (E.isRight(result)) { // E.Success } -const withCheckers = DP.empty({ - checkers: [DP.checkerRefine((value) => value === undefined)], -}); +const withCheckers = DP.empty() + .addChecker(DP.checkerRefine((value) => value === undefined)); -const coerceParser = DP.coerce.empty(); +const coerceParser = DP.coercer(DP.empty()); const coerceResult = coerceParser.parse("undefined"); diff --git a/jsDoc/dataParser/classic/empty/index.md b/jsDoc/dataParser/classic/empty/index.md index a71a2453a..df76ca439 100644 --- a/jsDoc/dataParser/classic/empty/index.md +++ b/jsDoc/dataParser/classic/empty/index.md @@ -7,7 +7,7 @@ Creates a data parser that accepts undefined. Accepts undefined (or the string "undefined" when coerce is enabled) and rejects other inputs. ```ts -{@include dataParser/classic/empty/example.ts[3,14]} +{@include dataParser/classic/empty/example.ts[3,13]} ``` @see https://utils.duplojs.dev/en/v1/api/dataParser/empty diff --git a/jsDoc/dataParser/classic/nil/example.ts b/jsDoc/dataParser/classic/nil/example.ts index a3fca6356..3c5087dc9 100644 --- a/jsDoc/dataParser/classic/nil/example.ts +++ b/jsDoc/dataParser/classic/nil/example.ts @@ -7,9 +7,8 @@ if (E.isRight(result)) { // value: null } -const withCheckers = DP.nil({ - checkers: [DP.checkerRefine((value) => value === null)], -}); +const withCheckers = DP.nil() + .addChecker(DP.checkerRefine((value) => value === null)); -const coerceParser = DP.coerce.nil(); +const coerceParser = DP.coercer(DP.nil()); const coerceResult = coerceParser.parse("null"); diff --git a/jsDoc/dataParser/classic/nil/index.md b/jsDoc/dataParser/classic/nil/index.md index 73f1399ac..647aec3e3 100644 --- a/jsDoc/dataParser/classic/nil/index.md +++ b/jsDoc/dataParser/classic/nil/index.md @@ -7,7 +7,7 @@ Creates a data parser that accepts null. Accepts null (or the string "null" when coerce is enabled) and rejects other inputs. ```ts -{@include dataParser/classic/nil/example.ts[3,15]} +{@include dataParser/classic/nil/example.ts[3,14]} ``` @see https://utils.duplojs.dev/en/v1/api/dataParser/nil diff --git a/jsDoc/dataParser/classic/nullable/example.ts b/jsDoc/dataParser/classic/nullable/example.ts index 59935b32b..376e5b0a3 100644 --- a/jsDoc/dataParser/classic/nullable/example.ts +++ b/jsDoc/dataParser/classic/nullable/example.ts @@ -10,6 +10,5 @@ if (E.isRight(result)) { const withCoalescing = DP.nullable(DP.number(), { coalescingValue: 0 }); const coalesced = withCoalescing.parse(null); -const withCheckers = DP.nullable(DP.boolean(), { - checkers: [DP.checkerRefine((value) => value !== null)], -}); +const withCheckers = DP.nullable(DP.boolean()) + .addChecker(DP.checkerRefine((value) => value !== null)); diff --git a/jsDoc/dataParser/classic/nullable/index.md b/jsDoc/dataParser/classic/nullable/index.md index b55a9e110..4deb00ae2 100644 --- a/jsDoc/dataParser/classic/nullable/index.md +++ b/jsDoc/dataParser/classic/nullable/index.md @@ -7,7 +7,7 @@ Creates a data parser that accepts null or the inner parser output. Returns null (or a coalescing value) when input is null, otherwise parses with the inner parser. ```ts -{@include dataParser/classic/nullable/example.ts[3,15]} +{@include dataParser/classic/nullable/example.ts[3,14]} ``` @see https://utils.duplojs.dev/en/v1/api/dataParser/nullable diff --git a/jsDoc/dataParser/classic/number/example.ts b/jsDoc/dataParser/classic/number/example.ts index a36c504e6..1ff13f0dd 100644 --- a/jsDoc/dataParser/classic/number/example.ts +++ b/jsDoc/dataParser/classic/number/example.ts @@ -7,9 +7,8 @@ if (E.isRight(result)) { // value: number } -const withCheckers = DP.number({ - checkers: [DP.checkerNumberMin(0), DP.checkerInt()], -}); +const withCheckers = DP.number() + .addChecker(DP.checkerNumberMin(0), DP.checkerInt()); -const coerceParser = DP.coerce.number(); +const coerceParser = DP.coercer(DP.number()); const coerceResult = coerceParser.parse("42"); diff --git a/jsDoc/dataParser/classic/number/index.md b/jsDoc/dataParser/classic/number/index.md index 01d23cfbe..1fc959e07 100644 --- a/jsDoc/dataParser/classic/number/index.md +++ b/jsDoc/dataParser/classic/number/index.md @@ -7,7 +7,7 @@ Creates a data parser for numbers. Validates that the input is a finite number, optionally applies coerce, and runs the configured checkers. `NaN`, `Infinity`, and `-Infinity` are rejected. ```ts -{@include dataParser/classic/number/example.ts[3,15]} +{@include dataParser/classic/number/example.ts[3,14]} ``` @see https://utils.duplojs.dev/en/v1/api/dataParser/number diff --git a/jsDoc/dataParser/classic/optional/example.ts b/jsDoc/dataParser/classic/optional/example.ts index 51d65f717..95aeda0dd 100644 --- a/jsDoc/dataParser/classic/optional/example.ts +++ b/jsDoc/dataParser/classic/optional/example.ts @@ -10,6 +10,5 @@ if (E.isRight(result)) { const withCoalescing = DP.optional(DP.number(), { coalescingValue: 0 }); const coalesced = withCoalescing.parse(undefined); -const withCheckers = DP.optional(DP.number(), { - checkers: [DP.checkerRefine((value) => value !== 13)], -}); +const withCheckers = DP.optional(DP.number()) + .addChecker(DP.checkerRefine((value) => value !== 13)); diff --git a/jsDoc/dataParser/classic/optional/index.md b/jsDoc/dataParser/classic/optional/index.md index 115c44aa1..4eca5b01e 100644 --- a/jsDoc/dataParser/classic/optional/index.md +++ b/jsDoc/dataParser/classic/optional/index.md @@ -7,7 +7,7 @@ Creates a data parser that accepts undefined or the inner parser output. Returns undefined (or a coalescing value) when input is undefined, otherwise parses with the inner parser. ```ts -{@include dataParser/classic/optional/example.ts[3,15]} +{@include dataParser/classic/optional/example.ts[3,14]} ``` @see https://utils.duplojs.dev/en/v1/api/dataParser/optional diff --git a/jsDoc/dataParser/classic/pipe/example.ts b/jsDoc/dataParser/classic/pipe/example.ts index aed9a29d6..848ea76a8 100644 --- a/jsDoc/dataParser/classic/pipe/example.ts +++ b/jsDoc/dataParser/classic/pipe/example.ts @@ -1,7 +1,7 @@ import { DP, E, unwrap } from "@scripts"; const schema = DP.pipe( - DP.coerce.number(), + DP.coercer(DP.number()), DP.transform( DP.number(), (value) => value + 1, diff --git a/jsDoc/dataParser/classic/record/example.ts b/jsDoc/dataParser/classic/record/example.ts index 2b7549537..b721b2af2 100644 --- a/jsDoc/dataParser/classic/record/example.ts +++ b/jsDoc/dataParser/classic/record/example.ts @@ -17,6 +17,5 @@ const strictResult = strictKeys.parse({ yPos: false, }); -const withCheckers = DP.record(DP.string(), DP.string(), { - checkers: [DP.checkerRefine((value) => Object.keys(value).length > 0)], -}); +const withCheckers = DP.record(DP.string(), DP.string()) + .addChecker(DP.checkerRefine((value) => Object.keys(value).length > 0)); diff --git a/jsDoc/dataParser/classic/record/index.md b/jsDoc/dataParser/classic/record/index.md index 79f81edc1..97a29ec4e 100644 --- a/jsDoc/dataParser/classic/record/index.md +++ b/jsDoc/dataParser/classic/record/index.md @@ -7,7 +7,7 @@ Creates a data parser for records with key and value parsers. Validates that the input is an object and parses each key and value with the provided parsers. ```ts -{@include dataParser/classic/record/example.ts[3,22]} +{@include dataParser/classic/record/example.ts[3,21]} ``` @see https://utils.duplojs.dev/en/v1/api/dataParser/record diff --git a/jsDoc/dataParser/classic/string/example.ts b/jsDoc/dataParser/classic/string/example.ts index 96e6f8c47..eb23dbe99 100644 --- a/jsDoc/dataParser/classic/string/example.ts +++ b/jsDoc/dataParser/classic/string/example.ts @@ -7,9 +7,8 @@ if (E.isRight(result)) { // value: string } -const withCheckers = DP.string({ - checkers: [DP.checkerStringMin(3), DP.checkerStringMax(10)], -}); +const withCheckers = DP.string() + .addChecker(DP.checkerStringMin(3), DP.checkerStringMax(10)); -const coerceParser = DP.coerce.string(); +const coerceParser = DP.coercer(DP.string()); const coerceResult = coerceParser.parse(123); diff --git a/jsDoc/dataParser/classic/string/index.md b/jsDoc/dataParser/classic/string/index.md index 89b1bb1dc..a1f15dbbd 100644 --- a/jsDoc/dataParser/classic/string/index.md +++ b/jsDoc/dataParser/classic/string/index.md @@ -7,7 +7,7 @@ Creates a data parser for strings. Validates that the input is a string, optionally applies coerce, and runs the configured checkers. ```ts -{@include dataParser/classic/string/example.ts[3,15]} +{@include dataParser/classic/string/example.ts[3,14]} ``` @see https://utils.duplojs.dev/en/v1/api/dataParser/string diff --git a/jsDoc/dataParser/classic/templateLiteral/example.ts b/jsDoc/dataParser/classic/templateLiteral/example.ts index b310d8823..37db80266 100644 --- a/jsDoc/dataParser/classic/templateLiteral/example.ts +++ b/jsDoc/dataParser/classic/templateLiteral/example.ts @@ -10,6 +10,5 @@ if (E.isRight(result)) { const orderParser = DP.templateLiteral(["order-", DP.literal("vip"), "-", DP.number()]); const orderResult = orderParser.parse("order-vip-12"); -const withCheckers = DP.templateLiteral(["id-", DP.number()], { - checkers: [DP.checkerRefine((value) => value.endsWith("0"))], -}); +const withCheckers = DP.templateLiteral(["id-", DP.number()]) + .addChecker(DP.checkerRefine((value) => value.endsWith("0"))); diff --git a/jsDoc/dataParser/classic/templateLiteral/index.md b/jsDoc/dataParser/classic/templateLiteral/index.md index e079434d8..1d45395ff 100644 --- a/jsDoc/dataParser/classic/templateLiteral/index.md +++ b/jsDoc/dataParser/classic/templateLiteral/index.md @@ -7,7 +7,7 @@ Creates a data parser for deterministic template literal strings. Validates that the input matches the provided template literal shape and pattern. Nested sub-parsers must not include their own checkers; add template-level `checkerRefine` rules when needed. ```ts -{@include dataParser/classic/templateLiteral/example.ts[3,15]} +{@include dataParser/classic/templateLiteral/example.ts[3,14]} ``` @see https://utils.duplojs.dev/en/v1/api/dataParser/templateLiteral diff --git a/jsDoc/dataParser/classic/time/example.ts b/jsDoc/dataParser/classic/time/example.ts index a9538940e..df52d85cd 100644 --- a/jsDoc/dataParser/classic/time/example.ts +++ b/jsDoc/dataParser/classic/time/example.ts @@ -7,12 +7,11 @@ if (E.isRight(result)) { // value: TheTime } -const withCheckers = DP.time({ - checkers: [DP.checkerRefine((value) => value.toNative() !== 0)], -}); +const withCheckers = DP.time() + .addChecker(DP.checkerRefine((value) => value.toNative() !== 0)); const checked = withCheckers.parse("time1000+"); // checked: E.Error | E.Success -const coerceParser = DP.coerce.time(); +const coerceParser = DP.coercer(DP.time()); const coerceResult = coerceParser.parse("10:20:00"); // coerceResult: E.Error | E.Success diff --git a/jsDoc/dataParser/classic/time/index.md b/jsDoc/dataParser/classic/time/index.md index e89d665f4..29b48ac33 100644 --- a/jsDoc/dataParser/classic/time/index.md +++ b/jsDoc/dataParser/classic/time/index.md @@ -6,12 +6,12 @@ The parser accepts `TheTime`, `SerializedTheTime`, and safe numeric time values. With `coerce: true`, ISO-like time strings are also supported. ```ts -{@include dataParser/classic/time/example.ts[3,18]} +{@include dataParser/classic/time/example.ts[3,17]} ``` @remarks - Parsed output is always `TheTime`. -- Use `DP.coerce.time()` when you want string coercion enabled by default. +- Use `DP.coercer(DP.time())` when you want string coercion enabled by default. @see https://utils.duplojs.dev/en/v1/api/dataParser/time diff --git a/jsDoc/dataParser/classic/union/example.ts b/jsDoc/dataParser/classic/union/example.ts index 1607d6cb6..fa63d4c43 100644 --- a/jsDoc/dataParser/classic/union/example.ts +++ b/jsDoc/dataParser/classic/union/example.ts @@ -11,6 +11,5 @@ const literals = DP.union([DP.literal("on"), DP.literal("off")]); const literalResult = literals.parse("off"); const withCheckers = DP.union( - [DP.string(), DP.coerce.number()], - { checkers: [DP.checkerRefine((value) => value !== "forbidden")] }, -); + [DP.string(), DP.coercer(DP.number())], +).addChecker(DP.checkerRefine((value) => value !== "forbidden")); diff --git a/jsDoc/dataParser/classic/union/index.md b/jsDoc/dataParser/classic/union/index.md index 1a59b5216..b5730cf28 100644 --- a/jsDoc/dataParser/classic/union/index.md +++ b/jsDoc/dataParser/classic/union/index.md @@ -7,7 +7,7 @@ Creates a data parser that accepts one of multiple parsers. Tries each option in order until one succeeds, then returns its output. ```ts -{@include dataParser/classic/union/example.ts[3,16]} +{@include dataParser/classic/union/example.ts[3,15]} ``` @see https://utils.duplojs.dev/en/v1/api/dataParser/union diff --git a/jsDoc/dataParser/extended/base/array/example.ts b/jsDoc/dataParser/extended/base/array/example.ts index 257a36c60..7cab743d6 100644 --- a/jsDoc/dataParser/extended/base/array/example.ts +++ b/jsDoc/dataParser/extended/base/array/example.ts @@ -7,9 +7,9 @@ if (E.isRight(result)) { // value: string[] } -const withCheckers = DPE.number().array({ - checkers: [DP.checkerArrayMin(1)], -}); +const withCheckers = DPE.number() + .array() + .addChecker(DP.checkerArrayMin(1)); const nested = DPE.string().array().array(); const nestedResult = nested.parse([["a"], ["b"]]); diff --git a/jsDoc/dataParser/extended/base/nullable/example.ts b/jsDoc/dataParser/extended/base/nullable/example.ts index 6aac28d0e..c7a3338cc 100644 --- a/jsDoc/dataParser/extended/base/nullable/example.ts +++ b/jsDoc/dataParser/extended/base/nullable/example.ts @@ -10,6 +10,6 @@ if (E.isRight(result)) { const withCoalescing = DPE.number().nullable({ coalescingValue: 0 }); const coalesced = withCoalescing.parse(null); -const withCheckers = DPE.boolean().nullable({ - checkers: [DP.checkerRefine((value) => value !== null)], -}); +const withCheckers = DPE.boolean() + .nullable() + .addChecker(DP.checkerRefine((value) => value !== null)); diff --git a/jsDoc/dataParser/extended/base/optional/example.ts b/jsDoc/dataParser/extended/base/optional/example.ts index 22590f3fd..1fb850798 100644 --- a/jsDoc/dataParser/extended/base/optional/example.ts +++ b/jsDoc/dataParser/extended/base/optional/example.ts @@ -10,6 +10,6 @@ if (E.isRight(result)) { const withCoalescing = DPE.number().optional({ coalescingValue: 0 }); const coalesced = withCoalescing.parse(undefined); -const withCheckers = DPE.number().optional({ - checkers: [DP.checkerRefine((value) => value !== 13)], -}); +const withCheckers = DPE.number() + .optional() + .addChecker(DP.checkerRefine((value) => value !== 13)); diff --git a/jsDoc/dataParser/extended/base/or/example.ts b/jsDoc/dataParser/extended/base/or/example.ts index 992a24820..5fc3186ca 100644 --- a/jsDoc/dataParser/extended/base/or/example.ts +++ b/jsDoc/dataParser/extended/base/or/example.ts @@ -10,6 +10,6 @@ if (E.isRight(result)) { const literals = DPE.literal("on").or(DPE.literal("off")); const literalResult = literals.parse("off"); -const withCheckers = DPE.string().or(DPE.coerce.number(), { - checkers: [DP.checkerRefine((value) => value !== "forbidden")], -}); +const withCheckers = DPE.string() + .or(DPE.number().coerce()) + .addChecker(DP.checkerRefine((value) => value !== "forbidden")); diff --git a/jsDoc/dataParser/extended/base/pipe/example.ts b/jsDoc/dataParser/extended/base/pipe/example.ts index 8afc57a6e..35384adc9 100644 --- a/jsDoc/dataParser/extended/base/pipe/example.ts +++ b/jsDoc/dataParser/extended/base/pipe/example.ts @@ -1,6 +1,6 @@ import { DPE, DP, E, unwrap } from "@scripts"; -const parser = DPE.string().pipe(DPE.coerce.number()); +const parser = DPE.string().pipe(DPE.number().coerce()); const result = parser.parse("42"); if (E.isRight(result)) { const value = unwrap(result); diff --git a/jsDoc/dataParser/extended/bigint/coerce/example.ts b/jsDoc/dataParser/extended/bigint/coerce/example.ts new file mode 100644 index 000000000..2856918c4 --- /dev/null +++ b/jsDoc/dataParser/extended/bigint/coerce/example.ts @@ -0,0 +1,18 @@ +import { DPE, E, unwrap } from "@scripts"; + +const parser = DPE.bigint().coerce(); +const result = parser.parse("42"); +if (E.isRight(result)) { + const value = unwrap(result); + // value: bigint +} + +const withMin = DPE.bigint() + .min(1n) + .coerce(); +const minResult = withMin.parse("42"); + +const withMax = DPE.bigint() + .max(100n) + .coerce(); +const maxResult = withMax.parse(42); diff --git a/jsDoc/dataParser/extended/bigint/coerce/index.md b/jsDoc/dataParser/extended/bigint/coerce/index.md new file mode 100644 index 000000000..b58af43c7 --- /dev/null +++ b/jsDoc/dataParser/extended/bigint/coerce/index.md @@ -0,0 +1,17 @@ +Wraps a bigint parser in an extended coercer parser. + +**Supported call styles:** +- Method: `dataParser.coerce()` -> returns a coercer parser + +Keeps the current bigint parser as the inner parser, then accepts coercible string, number, and boolean inputs before validating the final bigint. + +```ts +{@include dataParser/extended/bigint/coerce/example.ts[3,16]} +``` + +@remarks +Call this method after bigint-specific methods like `.min(...)` and `.max(...)` so the coercer keeps those checks. + +@see https://utils.duplojs.dev/en/v1/api/dataParser/bigint + +@namespace DPE diff --git a/jsDoc/dataParser/extended/bigint/example.ts b/jsDoc/dataParser/extended/bigint/example.ts index 1d70e9d75..2d9b5c10a 100644 --- a/jsDoc/dataParser/extended/bigint/example.ts +++ b/jsDoc/dataParser/extended/bigint/example.ts @@ -7,7 +7,7 @@ if (E.isRight(result)) { // value: bigint } -const coerceParser = DPE.coerce.bigint(); +const coerceParser = DPE.bigint().coerce(); const coerceResult = coerceParser.parse("42"); const onlySmall = DPE.bigint().max(3n); diff --git a/jsDoc/dataParser/extended/boolean/coerce/example.ts b/jsDoc/dataParser/extended/boolean/coerce/example.ts new file mode 100644 index 000000000..3d436b83a --- /dev/null +++ b/jsDoc/dataParser/extended/boolean/coerce/example.ts @@ -0,0 +1,16 @@ +import { DPE, E, unwrap } from "@scripts"; + +const parser = DPE.boolean().coerce(); +const result = parser.parse("true"); +if (E.isRight(result)) { + const value = unwrap(result); + // value: boolean +} + +const fromNumber = DPE.boolean().coerce(); +const numberResult = fromNumber.parse(1); + +const optionalBoolean = DPE.boolean() + .coerce() + .optional(); +const optionalResult = optionalBoolean.parse(undefined); diff --git a/jsDoc/dataParser/extended/boolean/coerce/index.md b/jsDoc/dataParser/extended/boolean/coerce/index.md new file mode 100644 index 000000000..7076d4477 --- /dev/null +++ b/jsDoc/dataParser/extended/boolean/coerce/index.md @@ -0,0 +1,17 @@ +Wraps a boolean parser in an extended coercer parser. + +**Supported call styles:** +- Method: `dataParser.coerce()` -> returns a coercer parser + +Keeps the current boolean parser as the inner parser, then accepts coercible string and number inputs before validating the final boolean. + +```ts +{@include dataParser/extended/boolean/coerce/example.ts[3,16]} +``` + +@remarks +The parser output remains boolean. The coercer only widens accepted input. + +@see https://utils.duplojs.dev/en/v1/api/dataParser/boolean + +@namespace DPE diff --git a/jsDoc/dataParser/extended/boolean/example.ts b/jsDoc/dataParser/extended/boolean/example.ts index 6f489aef4..0f9286838 100644 --- a/jsDoc/dataParser/extended/boolean/example.ts +++ b/jsDoc/dataParser/extended/boolean/example.ts @@ -7,7 +7,7 @@ if (E.isRight(result)) { // value: boolean } -const coerceParser = DPE.coerce.boolean(); +const coerceParser = DPE.boolean().coerce(); const coerceResult = coerceParser.parse("false"); const optionalBool = DPE.boolean().optional(); diff --git a/jsDoc/dataParser/extended/coercer/example.ts b/jsDoc/dataParser/extended/coercer/example.ts new file mode 100644 index 000000000..7f82dc378 --- /dev/null +++ b/jsDoc/dataParser/extended/coercer/example.ts @@ -0,0 +1,25 @@ +import { DPE, E, unwrap } from "@scripts"; + +const parser = DPE.number() + .min(4) + .max(10) + .coerce(); +const result = parser.parse("7"); +if (E.isRight(result)) { + const value = unwrap(result); + // value: number +} + +const direct = DPE.coercer(DPE.boolean()); +const directResult = direct.parse("true"); + +const complex = DPE.object({ + name: DPE.string().coerce(), + age: DPE.number() + .min(0) + .coerce(), +}); +const complexResult = complex.parse({ + name: 123, + age: "42", +}); diff --git a/jsDoc/dataParser/extended/coercer/index.md b/jsDoc/dataParser/extended/coercer/index.md new file mode 100644 index 000000000..f4d8d60b1 --- /dev/null +++ b/jsDoc/dataParser/extended/coercer/index.md @@ -0,0 +1,19 @@ +Creates an extended coercer parser around another DataParser. + +Signature: `DPE.coercer(inner, definition?)` -> `DataParserCoercerExtended` + +This parser does not represent a data type by itself. It represents a parsing action, like `DPE.pipe(...)` or `DPE.lazy(...)`: it receives an input, transforms it with the transformer registered for the inner parser kind, then gives the transformed value to the inner parser. + +```ts +{@include dataParser/extended/coercer/example.ts[3,25]} +``` + +@remarks +- Parsed output is always the output of the inner parser. +- Accepted input is widened with the values supported by the inner parser coercion transformer. +- Prefer the fluent `.coerce()` method on supported extended parsers when you need parser-specific methods before coercion. +- Before wrapping an extended parser with `DPE.coercer(parser)`, check whether that parser exposes `.coerce()`; when it does, prefer `parser.coerce()` so parser-specific fluent methods stay before coercion. + +@see https://utils.duplojs.dev/en/v1/api/dataParser/coercer + +@namespace DPE diff --git a/jsDoc/dataParser/extended/coercer/transformers/example.ts b/jsDoc/dataParser/extended/coercer/transformers/example.ts new file mode 100644 index 000000000..c303bd389 --- /dev/null +++ b/jsDoc/dataParser/extended/coercer/transformers/example.ts @@ -0,0 +1,17 @@ +import { DPE, DP } from "@scripts"; + +const numberTransformer = DPE.DataParserCoercerExtended.transformers.get( + DP.numberKind, +); + +DPE.DataParserCoercerExtended.transformers.set( + DP.stringKind, + (value) => String(value), +); + +const parser = DPE.string().coerce(); +const result = parser.parse(42); + +if (numberTransformer) { + DPE.DataParserCoercerExtended.transformers.set(DP.numberKind, numberTransformer); +} diff --git a/jsDoc/dataParser/extended/coercer/transformers/index.md b/jsDoc/dataParser/extended/coercer/transformers/index.md new file mode 100644 index 000000000..c4409368f --- /dev/null +++ b/jsDoc/dataParser/extended/coercer/transformers/index.md @@ -0,0 +1,18 @@ +Defines the transformer map used by extended coercer parsers. + +Signature: `DPE.DataParserCoercerExtended.transformers` -> `Map` + +This map is shared with the classic coercer. It links a DataParser kind to the function that prepares raw input before the inner parser runs. The fluent `.coerce()` methods are only added on extended parsers whose kind is supported by this coercion system. + +```ts +{@include dataParser/extended/coercer/transformers/example.ts[3,16]} +``` + +@remarks +- This property is the source of truth for the parser kinds that support coercion. +- A coercer memoizes the transformer it resolves during its first parse. +- Changing this map is an advanced extension point. + +@see https://utils.duplojs.dev/en/v1/api/dataParser/coercer + +@namespace DPE diff --git a/jsDoc/dataParser/extended/date/coerce/example.ts b/jsDoc/dataParser/extended/date/coerce/example.ts new file mode 100644 index 000000000..5a400651a --- /dev/null +++ b/jsDoc/dataParser/extended/date/coerce/example.ts @@ -0,0 +1,16 @@ +import { DPE, E, unwrap } from "@scripts"; + +const parser = DPE.date().coerce(); +const result = parser.parse("2024-01-01T00:00:00.000Z"); +if (E.isRight(result)) { + const value = unwrap(result); + // value: TheDate +} + +const fromTimestamp = DPE.date().coerce(); +const timestampResult = fromTimestamp.parse(1); + +const nullableDate = DPE.date() + .coerce() + .nullable(); +const nullableResult = nullableDate.parse(null); diff --git a/jsDoc/dataParser/extended/date/coerce/index.md b/jsDoc/dataParser/extended/date/coerce/index.md new file mode 100644 index 000000000..9fcff731f --- /dev/null +++ b/jsDoc/dataParser/extended/date/coerce/index.md @@ -0,0 +1,17 @@ +Wraps a date parser in an extended coercer parser. + +**Supported call styles:** +- Method: `dataParser.coerce()` -> returns a coercer parser + +Keeps the current date parser as the inner parser, then accepts coercible string and number inputs before validating the final `TheDate`. + +```ts +{@include dataParser/extended/date/coerce/example.ts[3,16]} +``` + +@remarks +Call this method after date-specific refinements so the coercer keeps those checks. + +@see https://utils.duplojs.dev/en/v1/api/dataParser/date + +@namespace DPE diff --git a/jsDoc/dataParser/extended/date/example.ts b/jsDoc/dataParser/extended/date/example.ts index 9d6c74d99..056754f46 100644 --- a/jsDoc/dataParser/extended/date/example.ts +++ b/jsDoc/dataParser/extended/date/example.ts @@ -7,7 +7,7 @@ if (E.isRight(result)) { // value: TheDate } -const coerceParser = DPE.coerce.date(); +const coerceParser = DPE.date().coerce(); const coerceResult = coerceParser.parse("2024-01-01T00:00:00.000Z"); // coerceResult: E.Error | E.Success diff --git a/jsDoc/dataParser/extended/date/index.md b/jsDoc/dataParser/extended/date/index.md index ccc41c326..6bd772fc9 100644 --- a/jsDoc/dataParser/extended/date/index.md +++ b/jsDoc/dataParser/extended/date/index.md @@ -10,7 +10,7 @@ This parser extends classic `DP.date(...)` behavior and keeps the extended chain @remarks - Parsed output is always `TheDate`. -- `DPE.coerce.date()` enables coercion by default. +- `DPE.date().coerce()` enables coercion after date-specific configuration. @see https://utils.duplojs.dev/en/v1/api/dataParser/date diff --git a/jsDoc/dataParser/extended/empty/coerce/example.ts b/jsDoc/dataParser/extended/empty/coerce/example.ts new file mode 100644 index 000000000..2a8a00b46 --- /dev/null +++ b/jsDoc/dataParser/extended/empty/coerce/example.ts @@ -0,0 +1,17 @@ +import { DPE, E } from "@scripts"; + +const parser = DPE.empty().coerce(); +const result = parser.parse("undefined"); +if (E.isRight(result)) { + // E.Success +} + +const nullableEmpty = DPE.empty() + .coerce() + .nullable(); +const nullableResult = nullableEmpty.parse(null); + +const optionalEmpty = DPE.empty() + .coerce() + .optional(); +const optionalResult = optionalEmpty.parse(undefined); diff --git a/jsDoc/dataParser/extended/empty/coerce/index.md b/jsDoc/dataParser/extended/empty/coerce/index.md new file mode 100644 index 000000000..8d7e8e04f --- /dev/null +++ b/jsDoc/dataParser/extended/empty/coerce/index.md @@ -0,0 +1,17 @@ +Wraps an empty parser in an extended coercer parser. + +**Supported call styles:** +- Method: `dataParser.coerce()` -> returns a coercer parser + +Keeps the current empty parser as the inner parser, then accepts the string `"undefined"` before validating the final undefined value. + +```ts +{@include dataParser/extended/empty/coerce/example.ts[3,16]} +``` + +@remarks +The parser output remains `undefined`. The coercer only widens accepted input. + +@see https://utils.duplojs.dev/en/v1/api/dataParser/empty + +@namespace DPE diff --git a/jsDoc/dataParser/extended/empty/example.ts b/jsDoc/dataParser/extended/empty/example.ts index 3265a7ad2..9f008cd87 100644 --- a/jsDoc/dataParser/extended/empty/example.ts +++ b/jsDoc/dataParser/extended/empty/example.ts @@ -6,7 +6,7 @@ if (E.isRight(result)) { // E.Success } -const coerceParser = DPE.coerce.empty(); +const coerceParser = DPE.empty().coerce(); const coerceResult = coerceParser.parse("undefined"); const optionalEmpty = DPE.empty().optional(); diff --git a/jsDoc/dataParser/extended/nil/coerce/example.ts b/jsDoc/dataParser/extended/nil/coerce/example.ts new file mode 100644 index 000000000..4ef130228 --- /dev/null +++ b/jsDoc/dataParser/extended/nil/coerce/example.ts @@ -0,0 +1,18 @@ +import { DPE, E, unwrap } from "@scripts"; + +const parser = DPE.nil().coerce(); +const result = parser.parse("null"); +if (E.isRight(result)) { + const value = unwrap(result); + // value: null +} + +const nullableNil = DPE.nil() + .coerce() + .nullable(); +const nullableResult = nullableNil.parse(null); + +const optionalNil = DPE.nil() + .coerce() + .optional(); +const optionalResult = optionalNil.parse(undefined); diff --git a/jsDoc/dataParser/extended/nil/coerce/index.md b/jsDoc/dataParser/extended/nil/coerce/index.md new file mode 100644 index 000000000..7ccba6660 --- /dev/null +++ b/jsDoc/dataParser/extended/nil/coerce/index.md @@ -0,0 +1,17 @@ +Wraps a nil parser in an extended coercer parser. + +**Supported call styles:** +- Method: `dataParser.coerce()` -> returns a coercer parser + +Keeps the current nil parser as the inner parser, then accepts the string `"null"` before validating the final null value. + +```ts +{@include dataParser/extended/nil/coerce/example.ts[3,16]} +``` + +@remarks +The parser output remains `null`. The coercer only widens accepted input. + +@see https://utils.duplojs.dev/en/v1/api/dataParser/nil + +@namespace DPE diff --git a/jsDoc/dataParser/extended/nil/example.ts b/jsDoc/dataParser/extended/nil/example.ts index 4d8d2fd01..434f91ac0 100644 --- a/jsDoc/dataParser/extended/nil/example.ts +++ b/jsDoc/dataParser/extended/nil/example.ts @@ -7,7 +7,7 @@ if (E.isRight(result)) { // value: null } -const coerceParser = DPE.coerce.nil(); +const coerceParser = DPE.nil().coerce(); const coerceResult = coerceParser.parse("null"); const nullableNil = DPE.nil().nullable(); diff --git a/jsDoc/dataParser/extended/number/coerce/example.ts b/jsDoc/dataParser/extended/number/coerce/example.ts new file mode 100644 index 000000000..fcf14d092 --- /dev/null +++ b/jsDoc/dataParser/extended/number/coerce/example.ts @@ -0,0 +1,18 @@ +import { DPE, E, unwrap } from "@scripts"; + +const parser = DPE.number().coerce(); +const result = parser.parse("42"); +if (E.isRight(result)) { + const value = unwrap(result); + // value: number +} + +const withMin = DPE.number() + .min(4) + .coerce(); +const minResult = withMin.parse("5"); + +const asInt = DPE.number() + .int() + .coerce(); +const intResult = asInt.parse("10"); diff --git a/jsDoc/dataParser/extended/number/coerce/index.md b/jsDoc/dataParser/extended/number/coerce/index.md new file mode 100644 index 000000000..2d1b01e0b --- /dev/null +++ b/jsDoc/dataParser/extended/number/coerce/index.md @@ -0,0 +1,17 @@ +Wraps a number parser in an extended coercer parser. + +**Supported call styles:** +- Method: `dataParser.coerce()` -> returns a coercer parser + +Keeps the current number parser as the inner parser, then accepts coercible inputs such as numeric strings, bigint, boolean, and null before validating the final number. + +```ts +{@include dataParser/extended/number/coerce/example.ts[3,16]} +``` + +@remarks +Call this method after number-specific methods like `.min(...)`, `.max(...)`, and `.int()` so the coercer keeps those checks. + +@see https://utils.duplojs.dev/en/v1/api/dataParser/number + +@namespace DPE diff --git a/jsDoc/dataParser/extended/number/example.ts b/jsDoc/dataParser/extended/number/example.ts index 9b977cb9b..9eecfb306 100644 --- a/jsDoc/dataParser/extended/number/example.ts +++ b/jsDoc/dataParser/extended/number/example.ts @@ -10,7 +10,7 @@ if (E.isRight(result)) { // value: number } -const coerceParser = DPE.coerce.number(); +const coerceParser = DPE.number().coerce(); const coerceResult = coerceParser.parse("42"); const intOnly = DPE.number().int(); diff --git a/jsDoc/dataParser/extended/pipe/example.ts b/jsDoc/dataParser/extended/pipe/example.ts index e8aac0dfd..7b60e626f 100644 --- a/jsDoc/dataParser/extended/pipe/example.ts +++ b/jsDoc/dataParser/extended/pipe/example.ts @@ -1,6 +1,6 @@ import { DPE, E, unwrap } from "@scripts"; -const parser = DPE.pipe(DPE.string(), DPE.coerce.number()); +const parser = DPE.pipe(DPE.string(), DPE.number().coerce()); const result = parser.parse("42"); if (E.isRight(result)) { const value = unwrap(result); diff --git a/jsDoc/dataParser/extended/string/coerce/example.ts b/jsDoc/dataParser/extended/string/coerce/example.ts new file mode 100644 index 000000000..d4acec7e4 --- /dev/null +++ b/jsDoc/dataParser/extended/string/coerce/example.ts @@ -0,0 +1,18 @@ +import { DPE, E, unwrap } from "@scripts"; + +const parser = DPE.string().coerce(); +const result = parser.parse(42); +if (E.isRight(result)) { + const value = unwrap(result); + // value: string +} + +const withMin = DPE.string() + .min(2) + .coerce(); +const minResult = withMin.parse(42); + +const withRegex = DPE.string() + .regex(/^\d+$/) + .coerce(); +const regexResult = withRegex.parse(42); diff --git a/jsDoc/dataParser/extended/string/coerce/index.md b/jsDoc/dataParser/extended/string/coerce/index.md new file mode 100644 index 000000000..85b179ef2 --- /dev/null +++ b/jsDoc/dataParser/extended/string/coerce/index.md @@ -0,0 +1,17 @@ +Wraps a string parser in an extended coercer parser. + +**Supported call styles:** +- Method: `dataParser.coerce()` -> returns a coercer parser + +Keeps the current string parser as the inner parser, then accepts coercible inputs such as number, bigint, boolean, symbol, null, and undefined before validating the final string. + +```ts +{@include dataParser/extended/string/coerce/example.ts[3,16]} +``` + +@remarks +Call this method after string-specific methods like `.min(...)`, `.max(...)`, and `.regex(...)` so the coercer keeps those checks. + +@see https://utils.duplojs.dev/en/v1/api/dataParser/string + +@namespace DPE diff --git a/jsDoc/dataParser/extended/string/example.ts b/jsDoc/dataParser/extended/string/example.ts index 95b8d04fc..3ac8f1b21 100644 --- a/jsDoc/dataParser/extended/string/example.ts +++ b/jsDoc/dataParser/extended/string/example.ts @@ -10,5 +10,7 @@ if (E.isRight(result)) { const withRegex = DPE.string().regex(/^[A-Z][a-z]+$/); const regexResult = withRegex.parse("Duplo"); -const coerceParser = DPE.string().min(2).coerce(); +const coerceParser = DPE.string() + .min(2) + .coerce(); const coerceResult = coerceParser.parse(123); diff --git a/jsDoc/dataParser/extended/templateLiteral/coerce/example.ts b/jsDoc/dataParser/extended/templateLiteral/coerce/example.ts new file mode 100644 index 000000000..625f0a901 --- /dev/null +++ b/jsDoc/dataParser/extended/templateLiteral/coerce/example.ts @@ -0,0 +1,19 @@ +import { DPE, E, unwrap } from "@scripts"; + +const parser = DPE.templateLiteral([DPE.number()]).coerce(); +const result = parser.parse(42); +if (E.isRight(result)) { + const value = unwrap(result); + // value: `${number}` +} + +const withPrefix = DPE.templateLiteral([ + "item-", + DPE.number(), +]).coerce(); +const prefixResult = withPrefix.parse("item-42"); + +const nullableTemplate = DPE.templateLiteral([DPE.number()]) + .coerce() + .nullable(); +const nullableResult = nullableTemplate.parse(null); diff --git a/jsDoc/dataParser/extended/templateLiteral/coerce/index.md b/jsDoc/dataParser/extended/templateLiteral/coerce/index.md new file mode 100644 index 000000000..caddcf72a --- /dev/null +++ b/jsDoc/dataParser/extended/templateLiteral/coerce/index.md @@ -0,0 +1,17 @@ +Wraps a template literal parser in an extended coercer parser. + +**Supported call styles:** +- Method: `dataParser.coerce()` -> returns a coercer parser + +Keeps the current template literal parser as the inner parser, then accepts primitive values that can be stringified before validating the final template literal string. + +```ts +{@include dataParser/extended/templateLiteral/coerce/example.ts[3,16]} +``` + +@remarks +Use this method when the whole input can be stringified into the expected template literal shape. + +@see https://utils.duplojs.dev/en/v1/api/dataParser/templateLiteral + +@namespace DPE diff --git a/jsDoc/dataParser/extended/time/coerce/example.ts b/jsDoc/dataParser/extended/time/coerce/example.ts new file mode 100644 index 000000000..7ca10b1d4 --- /dev/null +++ b/jsDoc/dataParser/extended/time/coerce/example.ts @@ -0,0 +1,18 @@ +import { D, DPE, E, unwrap } from "@scripts"; + +const parser = DPE.time().coerce(); +const result = parser.parse("01:02"); +if (E.isRight(result)) { + const value = unwrap(result); + // value: TheTime +} + +const withMin = DPE.time() + .min(D.createTime(1, "minute")) + .coerce(); +const minResult = withMin.parse("01:02"); + +const nullableTime = DPE.time() + .coerce() + .nullable(); +const nullableResult = nullableTime.parse(null); diff --git a/jsDoc/dataParser/extended/time/coerce/index.md b/jsDoc/dataParser/extended/time/coerce/index.md new file mode 100644 index 000000000..e25e966bd --- /dev/null +++ b/jsDoc/dataParser/extended/time/coerce/index.md @@ -0,0 +1,17 @@ +Wraps a time parser in an extended coercer parser. + +**Supported call styles:** +- Method: `dataParser.coerce()` -> returns a coercer parser + +Keeps the current time parser as the inner parser, then accepts coercible ISO-like time strings before validating the final `TheTime`. + +```ts +{@include dataParser/extended/time/coerce/example.ts[3,17]} +``` + +@remarks +Call this method after time-specific methods like `.min(...)` and `.max(...)` so the coercer keeps those checks. + +@see https://utils.duplojs.dev/en/v1/api/dataParser/time + +@namespace DPE diff --git a/jsDoc/dataParser/extended/time/example.ts b/jsDoc/dataParser/extended/time/example.ts index c5852ed7a..5e8602bac 100644 --- a/jsDoc/dataParser/extended/time/example.ts +++ b/jsDoc/dataParser/extended/time/example.ts @@ -12,7 +12,7 @@ if (E.isRight(result)) { // value: TheTime } -const coerceParser = DPE.coerce.time(); +const coerceParser = DPE.time().coerce(); const coerceResult = coerceParser.parse("10:20:00"); // E.Error | E.Success diff --git a/jsDoc/dataParser/extended/time/index.md b/jsDoc/dataParser/extended/time/index.md index d30f08339..5ddbf5be2 100644 --- a/jsDoc/dataParser/extended/time/index.md +++ b/jsDoc/dataParser/extended/time/index.md @@ -10,7 +10,7 @@ This parser extends the classic time parser behavior and adds fluent methods lik @remarks - `.min(...)` and `.max(...)` expect `TheTime` values. -- `DPE.coerce.time()` enables the same coercion flow as classic parser mode. +- `DPE.time().coerce()` enables the same coercion flow after time-specific configuration. @see https://utils.duplojs.dev/en/v1/api/dataParser/time diff --git a/scripts/dataParser/extended/bigint.ts b/scripts/dataParser/extended/bigint.ts index 13e1bea32..781fe2429 100644 --- a/scripts/dataParser/extended/bigint.ts +++ b/scripts/dataParser/extended/bigint.ts @@ -72,6 +72,9 @@ export class DataParserBigIntExtended< return this.addChecker(dataParsers.checkerBigIntMax(max, definition)); } + /** + * {@include dataParser/extended/bigint/coerce/index.md} + */ public coerce< GenericThis extends this = this, >(): DataParserCoercerExtended< diff --git a/scripts/dataParser/extended/boolean.ts b/scripts/dataParser/extended/boolean.ts index b0b073b26..95ded72bc 100644 --- a/scripts/dataParser/extended/boolean.ts +++ b/scripts/dataParser/extended/boolean.ts @@ -49,6 +49,9 @@ export class DataParserBooleanExtended< > >; + /** + * {@include dataParser/extended/boolean/coerce/index.md} + */ public coerce< GenericThis extends this = this, >(): DataParserCoercerExtended< diff --git a/scripts/dataParser/extended/coerce/bigint.ts b/scripts/dataParser/extended/coerce/bigint.ts index e0dc0c337..abf6a9dd4 100644 --- a/scripts/dataParser/extended/coerce/bigint.ts +++ b/scripts/dataParser/extended/coerce/bigint.ts @@ -19,18 +19,14 @@ export function bigint< >, GenericDefinition >, -): dataParsersExtended.DataParserCoercerExtended< +): dataParsersExtended.DataParserBigIntExtended< MergeDefinition< - dataParsers.DataParserDefinitionCoercer, - { - inner: dataParsersExtended.DataParserBigIntExtended< - MergeDefinition< - dataParsers.DataParserDefinitionBigInt, - NeverCoalescing - > - >; - } + dataParsers.DataParserDefinitionBigInt, + NeverCoalescing & { coerce: true } > > { - return dataParsersExtended.coercer(dataParsersExtended.bigint(definition)); + return dataParsersExtended.bigint({ + ...definition, + coerce: true, + }); } diff --git a/scripts/dataParser/extended/coerce/boolean.ts b/scripts/dataParser/extended/coerce/boolean.ts index 5bf4ecced..4f3bb9222 100644 --- a/scripts/dataParser/extended/coerce/boolean.ts +++ b/scripts/dataParser/extended/coerce/boolean.ts @@ -19,18 +19,15 @@ export function boolean< >, GenericDefinition >, -): dataParsersExtended.DataParserCoercerExtended< +): dataParsersExtended.DataParserBooleanExtended< MergeDefinition< - dataParsers.DataParserDefinitionCoercer, - { - inner: dataParsersExtended.DataParserBooleanExtended< - MergeDefinition< - dataParsers.DataParserDefinitionBoolean, - NeverCoalescing - > - >; - } + dataParsers.DataParserDefinitionBoolean, + NeverCoalescing & { coerce: true } > > { - return dataParsersExtended.coercer(dataParsersExtended.boolean(definition)); + return dataParsersExtended.boolean({ + ...definition, + coerce: true, + }); } + diff --git a/scripts/dataParser/extended/coerce/date.ts b/scripts/dataParser/extended/coerce/date.ts index 54d06947e..e9064f9a4 100644 --- a/scripts/dataParser/extended/coerce/date.ts +++ b/scripts/dataParser/extended/coerce/date.ts @@ -19,18 +19,15 @@ export function date< >, GenericDefinition >, -): dataParsersExtended.DataParserCoercerExtended< +): dataParsersExtended.DataParserDateExtended< MergeDefinition< - dataParsers.DataParserDefinitionCoercer, - { - inner: dataParsersExtended.DataParserDateExtended< - MergeDefinition< - dataParsers.DataParserDefinitionDate, - NeverCoalescing - > - >; - } + dataParsers.DataParserDefinitionDate, + NeverCoalescing & { coerce: true } > > { - return dataParsersExtended.coercer(dataParsersExtended.date(definition)); + return dataParsersExtended.date({ + ...definition, + coerce: true, + }); } + diff --git a/scripts/dataParser/extended/coerce/empty.ts b/scripts/dataParser/extended/coerce/empty.ts index 39c57645a..4a2ba9563 100644 --- a/scripts/dataParser/extended/coerce/empty.ts +++ b/scripts/dataParser/extended/coerce/empty.ts @@ -19,18 +19,15 @@ export function empty< >, GenericDefinition >, -): dataParsersExtended.DataParserCoercerExtended< +): dataParsersExtended.DataParserEmptyExtended< MergeDefinition< - dataParsers.DataParserDefinitionCoercer, - { - inner: dataParsersExtended.DataParserEmptyExtended< - MergeDefinition< - dataParsers.DataParserDefinitionEmpty, - NeverCoalescing - > - >; - } + dataParsers.DataParserDefinitionEmpty, + NeverCoalescing & { coerce: true } > > { - return dataParsersExtended.coercer(dataParsersExtended.empty(definition)); + return dataParsersExtended.empty({ + ...definition, + coerce: true, + }); } + diff --git a/scripts/dataParser/extended/coerce/index.ts b/scripts/dataParser/extended/coerce/index.ts index 72ee38a78..6e8405fe3 100644 --- a/scripts/dataParser/extended/coerce/index.ts +++ b/scripts/dataParser/extended/coerce/index.ts @@ -6,4 +6,3 @@ export * from "./empty"; export * from "./nil"; export * from "./number"; export * from "./string"; -export * from "./templateLiteral"; diff --git a/scripts/dataParser/extended/coerce/nil.ts b/scripts/dataParser/extended/coerce/nil.ts index 1a817adab..2d3dacb37 100644 --- a/scripts/dataParser/extended/coerce/nil.ts +++ b/scripts/dataParser/extended/coerce/nil.ts @@ -19,18 +19,14 @@ export function nil< >, GenericDefinition >, -): dataParsersExtended.DataParserCoercerExtended< +): dataParsersExtended.DataParserNilExtended< MergeDefinition< - dataParsers.DataParserDefinitionCoercer, - { - inner: dataParsersExtended.DataParserNilExtended< - MergeDefinition< - dataParsers.DataParserDefinitionNil, - NeverCoalescing - > - >; - } + dataParsers.DataParserDefinitionNil, + NeverCoalescing & { coerce: true } > > { - return dataParsersExtended.coercer(dataParsersExtended.nil(definition)); + return dataParsersExtended.nil({ + ...definition, + coerce: true, + }); } diff --git a/scripts/dataParser/extended/coerce/number.ts b/scripts/dataParser/extended/coerce/number.ts index fed696dcc..b1e3ac42e 100644 --- a/scripts/dataParser/extended/coerce/number.ts +++ b/scripts/dataParser/extended/coerce/number.ts @@ -19,18 +19,14 @@ export function number< >, GenericDefinition >, -): dataParsersExtended.DataParserCoercerExtended< +): dataParsersExtended.DataParserNumberExtended< MergeDefinition< - dataParsers.DataParserDefinitionCoercer, - { - inner: dataParsersExtended.DataParserNumberExtended< - MergeDefinition< - dataParsers.DataParserDefinitionNumber, - NeverCoalescing - > - >; - } + dataParsers.DataParserDefinitionNumber, + NeverCoalescing & { coerce: true } > > { - return dataParsersExtended.coercer(dataParsersExtended.number(definition)); + return dataParsersExtended.number({ + ...definition, + coerce: true, + }); } diff --git a/scripts/dataParser/extended/coerce/string.ts b/scripts/dataParser/extended/coerce/string.ts index 79cb069a5..018180fdc 100644 --- a/scripts/dataParser/extended/coerce/string.ts +++ b/scripts/dataParser/extended/coerce/string.ts @@ -19,18 +19,14 @@ export function string< >, GenericDefinition >, -): dataParsersExtended.DataParserCoercerExtended< +): dataParsersExtended.DataParserStringExtended< MergeDefinition< - dataParsers.DataParserDefinitionCoercer, - { - inner: dataParsersExtended.DataParserStringExtended< - MergeDefinition< - dataParsers.DataParserDefinitionString, - NeverCoalescing - > - >; - } + dataParsers.DataParserDefinitionString, + NeverCoalescing & { coerce: true } > > { - return dataParsersExtended.coercer(dataParsersExtended.string(definition)); + return dataParsersExtended.string({ + ...definition, + coerce: true, + }); } diff --git a/scripts/dataParser/extended/coerce/templateLiteral.ts b/scripts/dataParser/extended/coerce/templateLiteral.ts deleted file mode 100644 index 09f98bbe7..000000000 --- a/scripts/dataParser/extended/coerce/templateLiteral.ts +++ /dev/null @@ -1,44 +0,0 @@ -import type { FixDeepFunctionInfer, NeverCoalescing } from "@scripts/common"; -import type { MergeDefinition, PrepareDataParserDefinition } from "@scripts/dataParser/types"; -import type * as dataParsers from "../../parsers"; -import * as dataParsersExtended from ".."; - -/** - * @deprecated Use `dataParsersExtended.templateLiteral(...).coerce()` instead. - */ -export function templateLiteral< - const GenericTemplate extends dataParsers.TemplateLiteralShape, - const GenericDefinition extends PrepareDataParserDefinition< - dataParsers.DataParserDefinitionTemplateLiteral< - dataParsers.TemplateLiteralShapeOutput - >, - "template" | "pattern" - > = never, ->( - template: GenericTemplate, - definition?: FixDeepFunctionInfer< - PrepareDataParserDefinition< - dataParsers.DataParserDefinitionTemplateLiteral< - dataParsers.TemplateLiteralShapeOutput - >, - "template" | "pattern" - >, - GenericDefinition - >, -): dataParsersExtended.DataParserCoercerExtended< - MergeDefinition< - dataParsers.DataParserDefinitionCoercer< - dataParsers.TemplateLiteralShapeOutput - >, - { - inner: dataParsersExtended.DataParserTemplateLiteralExtended< - MergeDefinition< - dataParsers.DataParserDefinitionTemplateLiteral, - NeverCoalescing & { template: GenericTemplate } - > - >; - } - > - > { - return dataParsersExtended.coercer(dataParsersExtended.templateLiteral(template, definition)); -} diff --git a/scripts/dataParser/extended/coerce/time.ts b/scripts/dataParser/extended/coerce/time.ts index 418af1d1a..cc3dd21b7 100644 --- a/scripts/dataParser/extended/coerce/time.ts +++ b/scripts/dataParser/extended/coerce/time.ts @@ -19,18 +19,14 @@ export function time< >, GenericDefinition >, -): dataParsersExtended.DataParserCoercerExtended< +): dataParsersExtended.DataParserTimeExtended< MergeDefinition< - dataParsers.DataParserDefinitionCoercer, - { - inner: dataParsersExtended.DataParserTimeExtended< - MergeDefinition< - dataParsers.DataParserDefinitionTime, - NeverCoalescing - > - >; - } + dataParsers.DataParserDefinitionTime, + NeverCoalescing & { coerce: true } > > { - return dataParsersExtended.coercer(dataParsersExtended.time(definition)); + return dataParsersExtended.time({ + ...definition, + coerce: true, + }); } diff --git a/scripts/dataParser/extended/coercer.ts b/scripts/dataParser/extended/coercer.ts index cfc3ce0e4..f03e5db06 100644 --- a/scripts/dataParser/extended/coercer.ts +++ b/scripts/dataParser/extended/coercer.ts @@ -16,6 +16,9 @@ export class DataParserCoercerExtended< return this.checkConstructor(DataParserCoercerExtended); } + /** + * {@include dataParser/extended/coercer/transformers/index.md} + */ public static transformers = dataParsers.DataParserCoercer.transformers; public declare addChecker: < @@ -50,6 +53,9 @@ export class DataParserCoercerExtended< > >; + /** + * {@include dataParser/extended/coercer/index.md} + */ public static override create< GenericDataParser extends DataParser, const GenericDefinition extends PrepareDataParserDefinition< @@ -79,4 +85,7 @@ export class DataParserCoercerExtended< } } +/** + * {@include dataParser/extended/coercer/index.md} + */ export const coercer = detachObjectMethod(DataParserCoercerExtended, "create"); diff --git a/scripts/dataParser/extended/date.ts b/scripts/dataParser/extended/date.ts index 57eafb53e..542f04d4d 100644 --- a/scripts/dataParser/extended/date.ts +++ b/scripts/dataParser/extended/date.ts @@ -49,6 +49,9 @@ export class DataParserDateExtended< > >; + /** + * {@include dataParser/extended/date/coerce/index.md} + */ public coerce< GenericThis extends this = this, >(): DataParserCoercerExtended< diff --git a/scripts/dataParser/extended/empty.ts b/scripts/dataParser/extended/empty.ts index de2d39e0d..b41839394 100644 --- a/scripts/dataParser/extended/empty.ts +++ b/scripts/dataParser/extended/empty.ts @@ -49,6 +49,9 @@ export class DataParserEmptyExtended< > >; + /** + * {@include dataParser/extended/empty/coerce/index.md} + */ public coerce< GenericThis extends this = this, >(): DataParserCoercerExtended< diff --git a/scripts/dataParser/extended/index.ts b/scripts/dataParser/extended/index.ts index 2df92fc02..1b0656ff9 100644 --- a/scripts/dataParser/extended/index.ts +++ b/scripts/dataParser/extended/index.ts @@ -24,7 +24,7 @@ export * from "./errorHandler"; export * from "./coercer"; /** - * @deprecated Use the corresponding extended DataParser and call `.coerce()`. + * @deprecated Use `DPE.().coerce()` when the method exists, or `DPE.coercer(...)`. */ export * as coerce from "./coerce"; diff --git a/scripts/dataParser/extended/nil.ts b/scripts/dataParser/extended/nil.ts index f93ef5596..7f7a56979 100644 --- a/scripts/dataParser/extended/nil.ts +++ b/scripts/dataParser/extended/nil.ts @@ -49,6 +49,9 @@ export class DataParserNilExtended< > >; + /** + * {@include dataParser/extended/nil/coerce/index.md} + */ public coerce< GenericThis extends this = this, >(): DataParserCoercerExtended< diff --git a/scripts/dataParser/extended/number.ts b/scripts/dataParser/extended/number.ts index c642d704b..90df4b0a5 100644 --- a/scripts/dataParser/extended/number.ts +++ b/scripts/dataParser/extended/number.ts @@ -81,6 +81,9 @@ export class DataParserNumberExtended< return this.addChecker(dataParsers.checkerInt(definition)); } + /** + * {@include dataParser/extended/number/coerce/index.md} + */ public coerce< GenericThis extends this = this, >(): DataParserCoercerExtended< diff --git a/scripts/dataParser/extended/string.ts b/scripts/dataParser/extended/string.ts index db86a921c..987d4c3ae 100644 --- a/scripts/dataParser/extended/string.ts +++ b/scripts/dataParser/extended/string.ts @@ -100,6 +100,9 @@ export class DataParserStringExtended< return this.addChecker(dataParsers.checkerRegex(regex, definition)); } + /** + * {@include dataParser/extended/string/coerce/index.md} + */ public coerce< GenericThis extends this = this, >(): DataParserCoercerExtended< diff --git a/scripts/dataParser/extended/templateLiteral.ts b/scripts/dataParser/extended/templateLiteral.ts index d3b8c6efc..e21bd85e9 100644 --- a/scripts/dataParser/extended/templateLiteral.ts +++ b/scripts/dataParser/extended/templateLiteral.ts @@ -49,6 +49,9 @@ export class DataParserTemplateLiteralExtended< > >; + /** + * {@include dataParser/extended/templateLiteral/coerce/index.md} + */ public coerce< GenericThis extends this = this, >(): DataParserCoercerExtended< diff --git a/scripts/dataParser/extended/time.ts b/scripts/dataParser/extended/time.ts index b70fdc778..1be21b3d6 100644 --- a/scripts/dataParser/extended/time.ts +++ b/scripts/dataParser/extended/time.ts @@ -73,6 +73,9 @@ export class DataParserTimeExtended< return this.addChecker(dataParsers.checkerTimeMax(max, definition)); } + /** + * {@include dataParser/extended/time/coerce/index.md} + */ public coerce< GenericThis extends this = this, >(): DataParserCoercerExtended< diff --git a/scripts/dataParser/parsers/bigint/index.ts b/scripts/dataParser/parsers/bigint/index.ts index 55d7895b3..c26dac752 100644 --- a/scripts/dataParser/parsers/bigint/index.ts +++ b/scripts/dataParser/parsers/bigint/index.ts @@ -14,7 +14,7 @@ export interface DataParserDefinitionBigInt extends DataParserDefinition< > { /** - * @deprecated Use DDataParser.coercer(DDataParser.boolean()) or DDataParser.coerce.boolean() instead. + * @deprecated Use `DDataParser.coercer(DDataParser.bigint())` instead. */ readonly coerce: boolean; } diff --git a/scripts/dataParser/parsers/boolean.ts b/scripts/dataParser/parsers/boolean.ts index 8e87ce1ac..992a288d4 100644 --- a/scripts/dataParser/parsers/boolean.ts +++ b/scripts/dataParser/parsers/boolean.ts @@ -12,7 +12,7 @@ export interface DataParserDefinitionBoolean extends DataParserDefinition< > { /** - * @deprecated Use DDataParser.coercer(DDataParser.boolean()) or DDataParser.coerce.boolean() instead. + * @deprecated Use `DDataParser.coercer(DDataParser.boolean())` instead. */ readonly coerce: boolean; } diff --git a/scripts/dataParser/parsers/coerce/bigint.ts b/scripts/dataParser/parsers/coerce/bigint.ts index 96254fa3e..c7ad39b20 100644 --- a/scripts/dataParser/parsers/coerce/bigint.ts +++ b/scripts/dataParser/parsers/coerce/bigint.ts @@ -18,18 +18,14 @@ export function bigint< >, GenericDefinition >, -): dataParsers.DataParserCoercer< +): dataParsers.DataParserBigInt< MergeDefinition< - dataParsers.DataParserDefinitionCoercer, - { - inner: dataParsers.DataParserBigInt< - MergeDefinition< - dataParsers.DataParserDefinitionBigInt, - NeverCoalescing - > - >; - } + dataParsers.DataParserDefinitionBigInt, + NeverCoalescing & { coerce: true } > > { - return dataParsers.coercer(dataParsers.bigint(definition)); + return dataParsers.bigint({ + ...definition, + coerce: true, + }); } diff --git a/scripts/dataParser/parsers/coerce/boolean.ts b/scripts/dataParser/parsers/coerce/boolean.ts index 87f48bffb..761db71c2 100644 --- a/scripts/dataParser/parsers/coerce/boolean.ts +++ b/scripts/dataParser/parsers/coerce/boolean.ts @@ -18,18 +18,14 @@ export function boolean< >, GenericDefinition >, -): dataParsers.DataParserCoercer< +): dataParsers.DataParserBoolean< MergeDefinition< - dataParsers.DataParserDefinitionCoercer, - { - inner: dataParsers.DataParserBoolean< - MergeDefinition< - dataParsers.DataParserDefinitionBoolean, - NeverCoalescing - > - >; - } + dataParsers.DataParserDefinitionBoolean, + NeverCoalescing & { coerce: true } > > { - return dataParsers.coercer(dataParsers.boolean(definition)); + return dataParsers.boolean({ + ...definition, + coerce: true, + }); } diff --git a/scripts/dataParser/parsers/coerce/date.ts b/scripts/dataParser/parsers/coerce/date.ts index ad8599e85..757944ce9 100644 --- a/scripts/dataParser/parsers/coerce/date.ts +++ b/scripts/dataParser/parsers/coerce/date.ts @@ -18,18 +18,14 @@ export function date< >, GenericDefinition >, -): dataParsers.DataParserCoercer< +): dataParsers.DataParserDate< MergeDefinition< - dataParsers.DataParserDefinitionCoercer, - { - inner: dataParsers.DataParserDate< - MergeDefinition< - dataParsers.DataParserDefinitionDate, - NeverCoalescing - > - >; - } + dataParsers.DataParserDefinitionDate, + NeverCoalescing & { coerce: true } > > { - return dataParsers.coercer(dataParsers.date(definition)); + return dataParsers.date({ + ...definition, + coerce: true, + }); } diff --git a/scripts/dataParser/parsers/coerce/empty.ts b/scripts/dataParser/parsers/coerce/empty.ts index 864aef14d..b4543898d 100644 --- a/scripts/dataParser/parsers/coerce/empty.ts +++ b/scripts/dataParser/parsers/coerce/empty.ts @@ -18,18 +18,14 @@ export function empty< >, GenericDefinition >, -): dataParsers.DataParserCoercer< +): dataParsers.DataParserEmpty< MergeDefinition< - dataParsers.DataParserDefinitionCoercer, - { - inner: dataParsers.DataParserEmpty< - MergeDefinition< - dataParsers.DataParserDefinitionEmpty, - NeverCoalescing - > - >; - } + dataParsers.DataParserDefinitionEmpty, + NeverCoalescing & { coerce: true } > > { - return dataParsers.coercer(dataParsers.empty(definition)); + return dataParsers.empty({ + ...definition, + coerce: true, + }); } diff --git a/scripts/dataParser/parsers/coerce/index.ts b/scripts/dataParser/parsers/coerce/index.ts index 72ee38a78..6e8405fe3 100644 --- a/scripts/dataParser/parsers/coerce/index.ts +++ b/scripts/dataParser/parsers/coerce/index.ts @@ -6,4 +6,3 @@ export * from "./empty"; export * from "./nil"; export * from "./number"; export * from "./string"; -export * from "./templateLiteral"; diff --git a/scripts/dataParser/parsers/coerce/nil.ts b/scripts/dataParser/parsers/coerce/nil.ts index 3bd347462..42d0ff10a 100644 --- a/scripts/dataParser/parsers/coerce/nil.ts +++ b/scripts/dataParser/parsers/coerce/nil.ts @@ -18,18 +18,14 @@ export function nil< >, GenericDefinition >, -): dataParsers.DataParserCoercer< +): dataParsers.DataParserNil< MergeDefinition< - dataParsers.DataParserDefinitionCoercer, - { - inner: dataParsers.DataParserNil< - MergeDefinition< - dataParsers.DataParserDefinitionNil, - NeverCoalescing - > - >; - } + dataParsers.DataParserDefinitionNil, + NeverCoalescing & { coerce: true } > > { - return dataParsers.coercer(dataParsers.nil(definition)); + return dataParsers.nil({ + ...definition, + coerce: true, + }); } diff --git a/scripts/dataParser/parsers/coerce/number.ts b/scripts/dataParser/parsers/coerce/number.ts index 1911d5c2a..2f9b3eaa6 100644 --- a/scripts/dataParser/parsers/coerce/number.ts +++ b/scripts/dataParser/parsers/coerce/number.ts @@ -18,18 +18,15 @@ export function number< >, GenericDefinition >, -): dataParsers.DataParserCoercer< +): dataParsers.DataParserNumber< MergeDefinition< - dataParsers.DataParserDefinitionCoercer, - { - inner: dataParsers.DataParserNumber< - MergeDefinition< - dataParsers.DataParserDefinitionNumber, - NeverCoalescing - > - >; - } + dataParsers.DataParserDefinitionNumber, + NeverCoalescing & { coerce: true } > > { - return dataParsers.coercer(dataParsers.number(definition)); + return dataParsers.number({ + ...definition, + coerce: true, + }); } + diff --git a/scripts/dataParser/parsers/coerce/string.ts b/scripts/dataParser/parsers/coerce/string.ts index 1a9a1850c..d1f4e40d2 100644 --- a/scripts/dataParser/parsers/coerce/string.ts +++ b/scripts/dataParser/parsers/coerce/string.ts @@ -18,18 +18,15 @@ export function string< >, GenericDefinition >, -): dataParsers.DataParserCoercer< +): dataParsers.DataParserString< MergeDefinition< - dataParsers.DataParserDefinitionCoercer, - { - inner: dataParsers.DataParserString< - MergeDefinition< - dataParsers.DataParserDefinitionString, - NeverCoalescing - > - >; - } + dataParsers.DataParserDefinitionString, + NeverCoalescing & { coerce: true } > > { - return dataParsers.coercer(dataParsers.string(definition)); + return dataParsers.string({ + ...definition, + coerce: true, + }); } + diff --git a/scripts/dataParser/parsers/coerce/templateLiteral.ts b/scripts/dataParser/parsers/coerce/templateLiteral.ts deleted file mode 100644 index 866f19d4c..000000000 --- a/scripts/dataParser/parsers/coerce/templateLiteral.ts +++ /dev/null @@ -1,43 +0,0 @@ -import type { FixDeepFunctionInfer, NeverCoalescing } from "@scripts/common"; -import type { MergeDefinition, PrepareDataParserDefinition } from "@scripts/dataParser/types"; -import * as dataParsers from ".."; - -/** - * @deprecated Use `DP.coercer(DP.templateLiteral())` instead. - */ -export function templateLiteral< - const GenericTemplate extends dataParsers.TemplateLiteralShape, - const GenericDefinition extends PrepareDataParserDefinition< - dataParsers.DataParserDefinitionTemplateLiteral< - dataParsers.TemplateLiteralShapeOutput - >, - "template" | "pattern" - > = never, ->( - template: GenericTemplate, - definition?: FixDeepFunctionInfer< - PrepareDataParserDefinition< - dataParsers.DataParserDefinitionTemplateLiteral< - dataParsers.TemplateLiteralShapeOutput - >, - "template" | "pattern" - >, - GenericDefinition - >, -): dataParsers.DataParserCoercer< - MergeDefinition< - dataParsers.DataParserDefinitionCoercer< - dataParsers.TemplateLiteralShapeOutput - >, - { - inner: dataParsers.DataParserTemplateLiteral< - MergeDefinition< - dataParsers.DataParserDefinitionTemplateLiteral, - NeverCoalescing & { template: GenericTemplate } - > - >; - } - > - > { - return dataParsers.coercer(dataParsers.templateLiteral(template, definition)); -} diff --git a/scripts/dataParser/parsers/coerce/time.ts b/scripts/dataParser/parsers/coerce/time.ts index 886dc3fed..636ac596a 100644 --- a/scripts/dataParser/parsers/coerce/time.ts +++ b/scripts/dataParser/parsers/coerce/time.ts @@ -1,5 +1,5 @@ -import type { FixDeepFunctionInfer } from "@scripts/common"; -import type { PrepareDataParserDefinition } from "@scripts/dataParser/types"; +import type { FixDeepFunctionInfer, NeverCoalescing } from "@scripts/common"; +import type { MergeDefinition, PrepareDataParserDefinition } from "@scripts/dataParser/types"; import * as dataParsers from ".."; /** @@ -18,6 +18,14 @@ export function time< >, GenericDefinition >, -) { - return dataParsers.coercer(dataParsers.time(definition)); +): dataParsers.DataParserTime< + MergeDefinition< + dataParsers.DataParserDefinitionTime, + NeverCoalescing & { coerce: true } + > + > { + return dataParsers.time({ + ...definition, + coerce: true, + }); } diff --git a/scripts/dataParser/parsers/coercer/index.ts b/scripts/dataParser/parsers/coercer/index.ts index 196772472..771723d34 100644 --- a/scripts/dataParser/parsers/coercer/index.ts +++ b/scripts/dataParser/parsers/coercer/index.ts @@ -135,6 +135,9 @@ export class DataParserCoercer< return self.definition.inner.isAsynchronous(); } + /** + * {@include dataParser/classic/coercer/transformers/index.md} + */ public static transformers = new Map([ [dataParsers.numberKind, coercerTransformers.numberTransformer], [dataParsers.stringKind, coercerTransformers.stringTransformer], @@ -164,6 +167,9 @@ export class DataParserCoercer< }; } + /** + * {@include dataParser/classic/coercer/index.md} + */ public static override create< GenericDataParser extends DataParser, const GenericDefinition extends PrepareDataParserDefinition< @@ -193,4 +199,7 @@ export class DataParserCoercer< } } +/** + * {@include dataParser/classic/coercer/index.md} + */ export const coercer = detachObjectMethod(DataParserCoercer, "create"); diff --git a/scripts/dataParser/parsers/date.ts b/scripts/dataParser/parsers/date.ts index 166098136..0eb70d125 100644 --- a/scripts/dataParser/parsers/date.ts +++ b/scripts/dataParser/parsers/date.ts @@ -13,7 +13,7 @@ export interface DataParserDefinitionDate extends DataParserDefinition< > { /** - * @deprecated Use DDataParser.coercer(DDataParser.boolean()) or DDataParser.coerce.boolean() instead. + * @deprecated Use `DDataParser.coercer(DDataParser.date())` instead. */ readonly coerce: boolean; } diff --git a/scripts/dataParser/parsers/empty.ts b/scripts/dataParser/parsers/empty.ts index 6d24c4c7a..7d4961b52 100644 --- a/scripts/dataParser/parsers/empty.ts +++ b/scripts/dataParser/parsers/empty.ts @@ -12,7 +12,7 @@ export interface DataParserDefinitionEmpty extends DataParserDefinition< > { /** - * @deprecated Use DDataParser.coercer(DDataParser.boolean()) or DDataParser.coerce.boolean() instead. + * @deprecated Use `DDataParser.coercer(DDataParser.empty())` instead. */ readonly coerce: boolean; } diff --git a/scripts/dataParser/parsers/index.ts b/scripts/dataParser/parsers/index.ts index c1ac39ccb..846b6972a 100644 --- a/scripts/dataParser/parsers/index.ts +++ b/scripts/dataParser/parsers/index.ts @@ -25,6 +25,6 @@ export * from "./errorHandler"; export * from "./coercer"; /** - * @deprecated + * @deprecated Use DP.coercer() */ export * as coerce from "./coerce"; diff --git a/scripts/dataParser/parsers/nil.ts b/scripts/dataParser/parsers/nil.ts index ed18be10e..a91a1e52f 100644 --- a/scripts/dataParser/parsers/nil.ts +++ b/scripts/dataParser/parsers/nil.ts @@ -12,7 +12,7 @@ export interface DataParserDefinitionNil extends DataParserDefinition< > { /** - * @deprecated Use DDataParser.coercer(DDataParser.boolean()) or DDataParser.coerce.boolean() instead. + * @deprecated Use DDataParser.coercer(DDataParser.nil()) instead. */ readonly coerce: boolean; } diff --git a/scripts/dataParser/parsers/number/index.ts b/scripts/dataParser/parsers/number/index.ts index ee31688a8..04305ae21 100644 --- a/scripts/dataParser/parsers/number/index.ts +++ b/scripts/dataParser/parsers/number/index.ts @@ -14,7 +14,7 @@ export interface DataParserDefinitionNumber extends DataParserDefinition< > { /** - * @deprecated Use DDataParser.coercer(DDataParser.boolean()) or DDataParser.coerce.boolean() instead. + * @deprecated Use `DDataParser.coercer(DDataParser.number())` instead. */ readonly coerce: boolean; } diff --git a/scripts/dataParser/parsers/string/index.ts b/scripts/dataParser/parsers/string/index.ts index e7dfe7dd6..e3fe243db 100644 --- a/scripts/dataParser/parsers/string/index.ts +++ b/scripts/dataParser/parsers/string/index.ts @@ -14,7 +14,7 @@ export interface DataParserDefinitionString extends DataParserDefinition< > { /** - * @deprecated Use DDataParser.coercer(DDataParser.boolean()) or DDataParser.coerce.boolean() instead. + * @deprecated Use `DDataParser.coercer(DDataParser.string())` instead. */ readonly coerce: boolean; } diff --git a/scripts/dataParser/parsers/time/index.ts b/scripts/dataParser/parsers/time/index.ts index 6b1495eef..56a578b31 100644 --- a/scripts/dataParser/parsers/time/index.ts +++ b/scripts/dataParser/parsers/time/index.ts @@ -16,7 +16,7 @@ export interface DataParserDefinitionTime extends DataParserDefinition< > { /** - * @deprecated Use DDataParser.coercer(DDataParser.boolean()) or DDataParser.coerce.boolean() instead. + * @deprecated Use `DDataParser.coercer(DDataParser.time())` instead. */ readonly coerce: boolean; } diff --git a/tests/dataParser/extended/bigint.test.ts b/tests/dataParser/extended/bigint.test.ts index cce1c9e8b..17ab97de5 100644 --- a/tests/dataParser/extended/bigint.test.ts +++ b/tests/dataParser/extended/bigint.test.ts @@ -59,6 +59,24 @@ describe("extended.bigint", () => { >; }); + it("coerces values and preserves input and output types", () => { + const parser = extended.bigint().coerce(); + + type _CheckOut = ExpectType< + DDataParser.Output, + bigint, + "strict" + >; + + type _CheckIn = ExpectType< + DDataParser.Input, + string | number | boolean | bigint, + "strict" + >; + + expect(parser.parse("5")).toStrictEqual(DEither.success(5n)); + }); + it("supports min/max helpers", () => { const parser = extended.bigint(); expect(parser.min(2n).parse(1n)).toStrictEqual( diff --git a/tests/dataParser/extended/boolean.test.ts b/tests/dataParser/extended/boolean.test.ts index a61f9cb85..4fe24abc1 100644 --- a/tests/dataParser/extended/boolean.test.ts +++ b/tests/dataParser/extended/boolean.test.ts @@ -59,6 +59,24 @@ describe("extended.boolean", () => { >; }); + it("coerces values and preserves input and output types", () => { + const parser = extended.boolean().coerce(); + + type _CheckOut = ExpectType< + DDataParser.Output, + boolean, + "strict" + >; + + type _CheckIn = ExpectType< + DDataParser.Input, + string | number | boolean, + "strict" + >; + + expect(parser.parse("true")).toStrictEqual(DEither.success(true)); + }); + it("coerces when enabled", () => { const parser = extended.boolean({ coerce: true }); expect(parser.parse("true")).toStrictEqual(DEither.success(true)); diff --git a/tests/dataParser/extended/coercer.test.ts b/tests/dataParser/extended/coercer.test.ts new file mode 100644 index 000000000..957d2ba1d --- /dev/null +++ b/tests/dataParser/extended/coercer.test.ts @@ -0,0 +1,79 @@ +import { DDataParser, DEither, type ExpectType } from "@scripts"; + +const { extended } = DDataParser; + +describe("extended.coercer", () => { + it("preserves output and extends input from eligible extended parser", () => { + const parser = extended.coercer(extended.number()); + + type _CheckOut = ExpectType< + DDataParser.Output, + number, + "strict" + >; + + type _CheckIn = ExpectType< + DDataParser.Input, + string | number | bigint | boolean | null, + "strict" + >; + + expect(parser.parse("42")).toStrictEqual(DEither.success(42)); + }); + + it("keeps the exact inner parser type when coerce is called after fluent helpers", () => { + const inner = extended.number().min(4).max(10); + const parser = inner.coerce(); + + type _CheckInner = ExpectType< + typeof parser.definition.inner, + typeof inner, + "strict" + >; + + expect(parser.definition.inner).toBe(inner); + expect(parser.parse("5")).toStrictEqual(DEither.success(5)); + expect(parser.parse("3")).toStrictEqual(DEither.error(expect.any(Object))); + }); + + it("does not mutate the inner parser when it creates the coercer", () => { + const inner = extended.string().min(2); + const parser = inner.coerce(); + + expect(inner.parse(42)).toStrictEqual(DEither.error(expect.any(Object))); + expect(parser.parse(42)).toStrictEqual(DEither.success("42")); + }); + + it("runs coercer checkers after inner parser succeeds", () => { + const parser = extended.number() + .min(4) + .coerce() + .addChecker(DDataParser.checkerNumberMax(10)); + + expect(parser.parse("8")).toStrictEqual(DEither.success(8)); + expect(parser.parse("11")).toStrictEqual(DEither.error(expect.any(Object))); + }); + + it("keeps coercer input unrefined when checker refines output", () => { + const parser = extended.number().coerce().addChecker( + DDataParser.checkerRefine( + (value): value is 42 => value === 42, + ), + ); + + type _CheckOut = ExpectType< + DDataParser.Output, + 42, + "strict" + >; + + type _CheckIn = ExpectType< + DDataParser.Input, + string | number | bigint | boolean | null, + "strict" + >; + + expect(parser.parse("42")).toStrictEqual(DEither.success(42)); + expect(parser.parse("41")).toStrictEqual(DEither.error(expect.any(Object))); + }); +}); diff --git a/tests/dataParser/extended/date.test.ts b/tests/dataParser/extended/date.test.ts index 496312902..76a4c3dc8 100644 --- a/tests/dataParser/extended/date.test.ts +++ b/tests/dataParser/extended/date.test.ts @@ -63,6 +63,24 @@ describe("extended.date", () => { >; }); + it("coerces values and preserves input and output types", () => { + const parser = extended.date().coerce(); + + type _CheckOut = ExpectType< + DDataParser.Output, + DDate.TheDate, + "strict" + >; + + type _CheckIn = ExpectType< + DDataParser.Input, + string | number | Date | DDate.TheDate, + "strict" + >; + + expect(parser.parse(1)).toStrictEqual(DEither.success(DDate.createOrThrow("date1+"))); + }); + it("supports refine helper", () => { const parser = extended.date().refine( (date) => DDate.greaterThan(date, DDate.createOrThrow(0)), diff --git a/tests/dataParser/extended/empty.test.ts b/tests/dataParser/extended/empty.test.ts index 2f775e0f1..b999c8e4f 100644 --- a/tests/dataParser/extended/empty.test.ts +++ b/tests/dataParser/extended/empty.test.ts @@ -59,6 +59,24 @@ describe("extended.empty", () => { >; }); + it("coerces values and preserves input and output types", () => { + const parser = extended.empty().coerce(); + + type _CheckOut = ExpectType< + DDataParser.Output, + undefined, + "strict" + >; + + type _CheckIn = ExpectType< + DDataParser.Input, + undefined | "undefined", + "strict" + >; + + expect(parser.parse("undefined")).toStrictEqual(DEither.success(undefined)); + }); + it("coerces string when enabled", () => { const parser = extended.empty({ coerce: true }); expect(parser.parse("undefined")).toStrictEqual(DEither.success(undefined)); diff --git a/tests/dataParser/extended/nil.test.ts b/tests/dataParser/extended/nil.test.ts index 301546d9e..f87bef6c3 100644 --- a/tests/dataParser/extended/nil.test.ts +++ b/tests/dataParser/extended/nil.test.ts @@ -59,6 +59,24 @@ describe("extended.nil", () => { >; }); + it("coerces values and preserves input and output types", () => { + const parser = extended.nil().coerce(); + + type _CheckOut = ExpectType< + DDataParser.Output, + null, + "strict" + >; + + type _CheckIn = ExpectType< + DDataParser.Input, + null | "null", + "strict" + >; + + expect(parser.parse("null")).toStrictEqual(DEither.success(null)); + }); + it("coerces string when enabled", () => { const parser = extended.nil({ coerce: true }); expect(parser.parse("null")).toStrictEqual(DEither.success(null)); diff --git a/tests/dataParser/extended/number.test.ts b/tests/dataParser/extended/number.test.ts index 64580731d..9b09d413d 100644 --- a/tests/dataParser/extended/number.test.ts +++ b/tests/dataParser/extended/number.test.ts @@ -59,6 +59,24 @@ describe("extended.number", () => { >; }); + it("coerces values and preserves input and output types", () => { + const parser = extended.number().coerce(); + + type _CheckOut = ExpectType< + DDataParser.Output, + number, + "strict" + >; + + type _CheckIn = ExpectType< + DDataParser.Input, + string | number | bigint | boolean | null, + "strict" + >; + + expect(parser.parse("42")).toStrictEqual(DEither.success(42)); + }); + it("supports min/max helpers", () => { const parser = extended.number(); diff --git a/tests/dataParser/extended/string.test.ts b/tests/dataParser/extended/string.test.ts index 2fdfb5841..18f4321e0 100644 --- a/tests/dataParser/extended/string.test.ts +++ b/tests/dataParser/extended/string.test.ts @@ -59,6 +59,24 @@ describe("extended.string", () => { >; }); + it("coerces values and preserves input and output types", () => { + const parser = extended.string().coerce(); + + type _CheckOut = ExpectType< + DDataParser.Output, + string, + "strict" + >; + + type _CheckIn = ExpectType< + DDataParser.Input, + string | number | bigint | boolean | symbol | null | undefined, + "strict" + >; + + expect(parser.parse(42)).toStrictEqual(DEither.success("42")); + }); + it("supports min/max helpers", () => { const parser = extended.string(); const minParser = parser.min(3); diff --git a/tests/dataParser/extended/templateLiteral.test.ts b/tests/dataParser/extended/templateLiteral.test.ts index e986c35e2..acf2173d3 100644 --- a/tests/dataParser/extended/templateLiteral.test.ts +++ b/tests/dataParser/extended/templateLiteral.test.ts @@ -67,4 +67,22 @@ describe("extended.templateLiteral", () => { "strict" >; }); + + it("coerces values and preserves input and output types", () => { + const parser = extended.templateLiteral(["item-", extended.number()]).coerce(); + + type _CheckOut = ExpectType< + DDataParser.Output, + `item-${number}`, + "strict" + >; + + type _CheckIn = ExpectType< + DDataParser.Input, + number | bigint | boolean | `item-${number}`, + "strict" + >; + + expect(parser.parse("item-42")).toStrictEqual(DEither.success("item-42")); + }); }); diff --git a/tests/dataParser/extended/time.test.ts b/tests/dataParser/extended/time.test.ts index 69af7095b..fcacf42c7 100644 --- a/tests/dataParser/extended/time.test.ts +++ b/tests/dataParser/extended/time.test.ts @@ -63,6 +63,24 @@ describe("extended.time", () => { >; }); + it("coerces values and preserves input and output types", () => { + const parser = extended.time().coerce(); + + type _CheckOut = ExpectType< + DDataParser.Output, + DDate.TheTime, + "strict" + >; + + type _CheckIn = ExpectType< + DDataParser.Input, + string | number | DDate.TheTime, + "strict" + >; + + expect(parser.parse("01:02")).toStrictEqual(DEither.success(DDate.createTimeOrThrow("time3720000+"))); + }); + it("supports refine helper", () => { const parser = extended.time().refine( (time) => time.toNative() > 0, diff --git a/tests/dataParser/parsers/coerce/bigint.test.ts b/tests/dataParser/parsers/coerce/bigint.test.ts index 6d8605580..0d2e74638 100644 --- a/tests/dataParser/parsers/coerce/bigint.test.ts +++ b/tests/dataParser/parsers/coerce/bigint.test.ts @@ -24,18 +24,6 @@ describe("coerce.bigint", () => { }), ); - type _CheckOut = ExpectType< - DDataParser.Output, - bigint, - "strict" - >; - - type _CheckIn = ExpectType< - DDataParser.Input, - string | number | bigint | boolean, - "strict" - >; - void dataParser; }); diff --git a/tests/dataParser/parsers/coerce/boolean.test.ts b/tests/dataParser/parsers/coerce/boolean.test.ts index 2aaa846ef..90cd1fea2 100644 --- a/tests/dataParser/parsers/coerce/boolean.test.ts +++ b/tests/dataParser/parsers/coerce/boolean.test.ts @@ -24,18 +24,6 @@ describe("coerce.boolean", () => { }), ); - type _CheckOut = ExpectType< - DDataParser.Output, - boolean, - "strict" - >; - - type _CheckIn = ExpectType< - DDataParser.Input, - string | number | boolean, - "strict" - >; - void dataParser; }); diff --git a/tests/dataParser/parsers/coerce/date.test.ts b/tests/dataParser/parsers/coerce/date.test.ts index 6fe9a6874..96d2234f5 100644 --- a/tests/dataParser/parsers/coerce/date.test.ts +++ b/tests/dataParser/parsers/coerce/date.test.ts @@ -24,18 +24,6 @@ describe("coerce.date", () => { }), ); - type _CheckOut = ExpectType< - DDataParser.Output, - DDate.TheDate, - "strict" - >; - - type _CheckIn = ExpectType< - DDataParser.Input, - DDate.TheDate | Date | string | number, - "strict" - >; - void dataParser; }); diff --git a/tests/dataParser/parsers/coerce/empty.test.ts b/tests/dataParser/parsers/coerce/empty.test.ts index e588d68b1..5e1032ab0 100644 --- a/tests/dataParser/parsers/coerce/empty.test.ts +++ b/tests/dataParser/parsers/coerce/empty.test.ts @@ -24,18 +24,6 @@ describe("coerce.empty", () => { }), ); - type _CheckOut = ExpectType< - DDataParser.Output, - undefined, - "strict" - >; - - type _CheckIn = ExpectType< - DDataParser.Input, - "undefined" | undefined, - "strict" - >; - void dataParser; }); diff --git a/tests/dataParser/parsers/coerce/nil.test.ts b/tests/dataParser/parsers/coerce/nil.test.ts index 1b560f436..7a9f53d64 100644 --- a/tests/dataParser/parsers/coerce/nil.test.ts +++ b/tests/dataParser/parsers/coerce/nil.test.ts @@ -24,18 +24,6 @@ describe("coerce.nil", () => { }), ); - type _CheckOut = ExpectType< - DDataParser.Output, - null, - "strict" - >; - - type _CheckIn = ExpectType< - DDataParser.Input, - "null" | null, - "strict" - >; - void dataParser; }); diff --git a/tests/dataParser/parsers/coerce/number.test.ts b/tests/dataParser/parsers/coerce/number.test.ts index e6d0ce6a4..9070440b9 100644 --- a/tests/dataParser/parsers/coerce/number.test.ts +++ b/tests/dataParser/parsers/coerce/number.test.ts @@ -24,18 +24,6 @@ describe("coerce.number", () => { }), ); - type _CheckOut = ExpectType< - DDataParser.Output, - number, - "strict" - >; - - type _CheckIn = ExpectType< - DDataParser.Input, - string | number | bigint | boolean | null, - "strict" - >; - void dataParser; }); diff --git a/tests/dataParser/parsers/coerce/string.test.ts b/tests/dataParser/parsers/coerce/string.test.ts index 2906dbc1c..bef6070d5 100644 --- a/tests/dataParser/parsers/coerce/string.test.ts +++ b/tests/dataParser/parsers/coerce/string.test.ts @@ -24,18 +24,6 @@ describe("coerce.string", () => { }), ); - type _CheckOut = ExpectType< - DDataParser.Output, - string, - "strict" - >; - - type _CheckIn = ExpectType< - DDataParser.Input, - string | number | bigint | boolean | symbol | null | undefined, - "strict" - >; - void dataParser; }); diff --git a/tests/dataParser/parsers/coerce/templateLiteral.test.ts b/tests/dataParser/parsers/coerce/templateLiteral.test.ts deleted file mode 100644 index 16c11416e..000000000 --- a/tests/dataParser/parsers/coerce/templateLiteral.test.ts +++ /dev/null @@ -1,63 +0,0 @@ -import { DEither, DDataParser, type ExpectType } from "@scripts"; - -describe("coerce.templateLiteral", () => { - it("create data parser with checker", () => { - const dataParser = DDataParser.coerce.templateLiteral( - ["item-", DDataParser.number()], - { - checkers: [ - DDataParser.checkerRefine((value) => { - type check = ExpectType< - typeof value, - `item-${number}`, - "strict" - >; - return true; - }), - ], - }, - ).addChecker( - DDataParser.checkerRefine((value) => { - type check = ExpectType< - typeof value, - `item-${number}`, - "strict" - >; - return true; - }), - ); - - type _CheckOut = ExpectType< - DDataParser.Output, - `item-${number}`, - "strict" - >; - - type _CheckIn = ExpectType< - DDataParser.Input, - number | bigint | boolean | `item-${number}`, - "strict" - >; - - void dataParser; - }); - - it("coerces value to string", () => { - expect(DDataParser.coerce.templateLiteral(["item-", DDataParser.number()]).parse("item-42")).toStrictEqual(DEither.success("item-42")); - }); - - it("coerces primitive template literal input to string", () => { - expect(DDataParser.coerce.templateLiteral([DDataParser.number()]).parse(42)).toStrictEqual( - DEither.success("42"), - ); - }); - - it("fails when coercion not possible", () => { - expect(DDataParser.coerce.templateLiteral( - ["item-", DDataParser.number()], - { errorMessage: "templateLiteral.coerce" }, - ).parse(Object.create(null))).toStrictEqual( - DEither.error(expect.any(Object)), - ); - }); -}); diff --git a/tests/dataParser/parsers/coerce/time.test.ts b/tests/dataParser/parsers/coerce/time.test.ts index 64055b4bf..9a55f8584 100644 --- a/tests/dataParser/parsers/coerce/time.test.ts +++ b/tests/dataParser/parsers/coerce/time.test.ts @@ -24,18 +24,6 @@ describe("coerce.time", () => { }), ); - type _CheckOut = ExpectType< - DDataParser.Output, - DDate.TheTime, - "strict" - >; - - type _CheckIn = ExpectType< - DDataParser.Input, - DDate.TheTime | string | number, - "strict" - >; - void dataParser; }); diff --git a/tests/dataParser/parsers/coercer/index.test.ts b/tests/dataParser/parsers/coercer/index.test.ts index 175f228fe..f2833467d 100644 --- a/tests/dataParser/parsers/coercer/index.test.ts +++ b/tests/dataParser/parsers/coercer/index.test.ts @@ -202,11 +202,26 @@ describe("DDataParser coercer", () => { expect(DDataParser.coercer(DDataParser.number()).isAsynchronous()).toBe(false); }); - it("transformer throw", () => { - DDataParser.DataParserCoercer.transformers.set(DDataParser.numberKind, Number); + it("returns an error when transformer throws", () => { + const transformerError = new Error("transformer failed"); + DDataParser.DataParserCoercer.transformers.set( + DDataParser.numberKind, + () => { + throw transformerError; + }, + ); - expect(DDataParser.coerce.number().parse(Symbol("foo"))).toStrictEqual( - DEither.error(expect.any(Object)), + expect(DDataParser.coercer(DDataParser.number()).parse("42")).toStrictEqual( + DEither.error( + expect.objectContaining({ + issues: [ + expect.objectContaining({ + expected: "successful coerce result", + data: transformerError, + }), + ], + }), + ), ); }); }); diff --git a/tests/dataParser/parsers/coercer/transformers/bigint.test.ts b/tests/dataParser/parsers/coercer/transformers/bigint.test.ts new file mode 100644 index 000000000..206895583 --- /dev/null +++ b/tests/dataParser/parsers/coercer/transformers/bigint.test.ts @@ -0,0 +1,38 @@ +import { DDataParser, DEither, type ExpectType } from "@scripts"; + +describe("DDataParser coercer bigint transformer", () => { + it("infers coerced input, output, and checker refine value types", () => { + const schema = DDataParser.coercer(DDataParser.bigint()).addChecker( + DDataParser.checkerRefine((value) => { + type _CheckRefineValue = ExpectType; + return true; + }), + ); + + type _CheckOut = ExpectType< + DDataParser.Output, + bigint, + "strict" + >; + + type _CheckIn = ExpectType< + DDataParser.Input, + string | number | boolean | bigint, + "strict" + >; + }); + + it("coerces supported values through the coercer", () => { + const schema = DDataParser.coercer(DDataParser.bigint()); + + expect(schema.parse("42")).toStrictEqual(DEither.success(42n)); + expect(schema.parse(42)).toStrictEqual(DEither.success(42n)); + expect(schema.parse(true)).toStrictEqual(DEither.success(1n)); + }); + + it("fails when transformed value is not a bigint", () => { + const schema = DDataParser.coercer(DDataParser.bigint()); + + expect(schema.parse(1.5)).toStrictEqual(DEither.error(expect.any(Object))); + }); +}); diff --git a/tests/dataParser/parsers/coercer/transformers/boolean.test.ts b/tests/dataParser/parsers/coercer/transformers/boolean.test.ts new file mode 100644 index 000000000..1ab9b88c1 --- /dev/null +++ b/tests/dataParser/parsers/coercer/transformers/boolean.test.ts @@ -0,0 +1,40 @@ +import { DDataParser, DEither, type ExpectType } from "@scripts"; + +describe("DDataParser coercer boolean transformer", () => { + it("infers coerced input, output, and checker refine value types", () => { + const schema = DDataParser.coercer(DDataParser.boolean()).addChecker( + DDataParser.checkerRefine((value) => { + type _CheckRefineValue = ExpectType; + return true; + }), + ); + + type _CheckOut = ExpectType< + DDataParser.Output, + boolean, + "strict" + >; + + type _CheckIn = ExpectType< + DDataParser.Input, + string | number | boolean, + "strict" + >; + }); + + it("coerces supported values through the coercer", () => { + const schema = DDataParser.coercer(DDataParser.boolean()); + + expect(schema.parse(" true ")).toStrictEqual(DEither.success(true)); + expect(schema.parse("FALSE")).toStrictEqual(DEither.success(false)); + expect(schema.parse(1)).toStrictEqual(DEither.success(true)); + expect(schema.parse(0)).toStrictEqual(DEither.success(false)); + }); + + it("fails when transformed value is not a boolean", () => { + const schema = DDataParser.coercer(DDataParser.boolean()); + + expect(schema.parse("yes")).toStrictEqual(DEither.error(expect.any(Object))); + expect(schema.parse(2)).toStrictEqual(DEither.error(expect.any(Object))); + }); +}); diff --git a/tests/dataParser/parsers/coercer/transformers/date.test.ts b/tests/dataParser/parsers/coercer/transformers/date.test.ts new file mode 100644 index 000000000..ade689816 --- /dev/null +++ b/tests/dataParser/parsers/coercer/transformers/date.test.ts @@ -0,0 +1,40 @@ +import { DDataParser, DDate, DEither, type ExpectType } from "@scripts"; + +describe("DDataParser coercer date transformer", () => { + it("infers coerced input, output, and checker refine value types", () => { + const schema = DDataParser.coercer(DDataParser.date()).addChecker( + DDataParser.checkerRefine((value) => { + type _CheckRefineValue = ExpectType; + return true; + }), + ); + + type _CheckOut = ExpectType< + DDataParser.Output, + DDate.TheDate, + "strict" + >; + + type _CheckIn = ExpectType< + DDataParser.Input, + DDate.TheDate | Date | string | number, + "strict" + >; + }); + + it("coerces supported values through the coercer", () => { + const schema = DDataParser.coercer(DDataParser.date()); + + expect(schema.parse(1)).toStrictEqual(DEither.success(DDate.createOrThrow("date1+"))); + expect(schema.parse("2021-01-01T00:00:00.000Z")).toStrictEqual( + DEither.success(DDate.createOrThrow(new Date("2021-01-01T00:00:00.000Z"))), + ); + }); + + it("fails when transformed value is not a date", () => { + const schema = DDataParser.coercer(DDataParser.date()); + + expect(schema.parse("not-a-date")).toStrictEqual(DEither.error(expect.any(Object))); + expect(schema.parse(Number.POSITIVE_INFINITY)).toStrictEqual(DEither.error(expect.any(Object))); + }); +}); diff --git a/tests/dataParser/parsers/coercer/transformers/empty.test.ts b/tests/dataParser/parsers/coercer/transformers/empty.test.ts new file mode 100644 index 000000000..723bf2eb9 --- /dev/null +++ b/tests/dataParser/parsers/coercer/transformers/empty.test.ts @@ -0,0 +1,36 @@ +import { DDataParser, DEither, type ExpectType } from "@scripts"; + +describe("DDataParser coercer empty transformer", () => { + it("infers coerced input, output, and checker refine value types", () => { + const schema = DDataParser.coercer(DDataParser.empty()).addChecker( + DDataParser.checkerRefine((value) => { + type _CheckRefineValue = ExpectType; + return true; + }), + ); + + type _CheckOut = ExpectType< + DDataParser.Output, + undefined, + "strict" + >; + + type _CheckIn = ExpectType< + DDataParser.Input, + undefined | "undefined", + "strict" + >; + }); + + it("coerces the undefined string through the coercer", () => { + const schema = DDataParser.coercer(DDataParser.empty()); + + expect(schema.parse("undefined")).toStrictEqual(DEither.success(undefined)); + }); + + it("fails when transformed value is not undefined", () => { + const schema = DDataParser.coercer(DDataParser.empty()); + + expect(schema.parse("null")).toStrictEqual(DEither.error(expect.any(Object))); + }); +}); diff --git a/tests/dataParser/parsers/coercer/transformers/nil.test.ts b/tests/dataParser/parsers/coercer/transformers/nil.test.ts new file mode 100644 index 000000000..b02cb1d3e --- /dev/null +++ b/tests/dataParser/parsers/coercer/transformers/nil.test.ts @@ -0,0 +1,36 @@ +import { DDataParser, DEither, type ExpectType } from "@scripts"; + +describe("DDataParser coercer nil transformer", () => { + it("infers coerced input, output, and checker refine value types", () => { + const schema = DDataParser.coercer(DDataParser.nil()).addChecker( + DDataParser.checkerRefine((value) => { + type _CheckRefineValue = ExpectType; + return true; + }), + ); + + type _CheckOut = ExpectType< + DDataParser.Output, + null, + "strict" + >; + + type _CheckIn = ExpectType< + DDataParser.Input, + null | "null", + "strict" + >; + }); + + it("coerces the null string through the coercer", () => { + const schema = DDataParser.coercer(DDataParser.nil()); + + expect(schema.parse("null")).toStrictEqual(DEither.success(null)); + }); + + it("fails when transformed value is not null", () => { + const schema = DDataParser.coercer(DDataParser.nil()); + + expect(schema.parse("undefined")).toStrictEqual(DEither.error(expect.any(Object))); + }); +}); diff --git a/tests/dataParser/parsers/coercer/transformers/number.test.ts b/tests/dataParser/parsers/coercer/transformers/number.test.ts new file mode 100644 index 000000000..8ff0b99f8 --- /dev/null +++ b/tests/dataParser/parsers/coercer/transformers/number.test.ts @@ -0,0 +1,40 @@ +import { DDataParser, DEither, type ExpectType } from "@scripts"; + +describe("DDataParser coercer number transformer", () => { + it("infers coerced input, output, and checker refine value types", () => { + const schema = DDataParser.coercer(DDataParser.number()).addChecker( + DDataParser.checkerRefine((value) => { + type _CheckRefineValue = ExpectType; + return true; + }), + ); + + type _CheckOut = ExpectType< + DDataParser.Output, + number, + "strict" + >; + + type _CheckIn = ExpectType< + DDataParser.Input, + string | number | bigint | boolean | null, + "strict" + >; + }); + + it("coerces supported values through the coercer", () => { + const schema = DDataParser.coercer(DDataParser.number()); + + expect(schema.parse("42")).toStrictEqual(DEither.success(42)); + expect(schema.parse(42n)).toStrictEqual(DEither.success(42)); + expect(schema.parse(true)).toStrictEqual(DEither.success(1)); + expect(schema.parse(null)).toStrictEqual(DEither.success(0)); + }); + + it("fails when transformed value is not a finite number", () => { + const schema = DDataParser.coercer(DDataParser.number()); + + expect(schema.parse("not-a-number")).toStrictEqual(DEither.error(expect.any(Object))); + expect(schema.parse(Symbol("foo"))).toStrictEqual(DEither.error(expect.any(Object))); + }); +}); diff --git a/tests/dataParser/parsers/coercer/transformers/string.test.ts b/tests/dataParser/parsers/coercer/transformers/string.test.ts new file mode 100644 index 000000000..cce24921e --- /dev/null +++ b/tests/dataParser/parsers/coercer/transformers/string.test.ts @@ -0,0 +1,40 @@ +import { DDataParser, DEither, type ExpectType } from "@scripts"; + +describe("DDataParser coercer string transformer", () => { + it("infers coerced input, output, and checker refine value types", () => { + const schema = DDataParser.coercer(DDataParser.string()).addChecker( + DDataParser.checkerRefine((value) => { + type _CheckRefineValue = ExpectType; + return true; + }), + ); + + type _CheckOut = ExpectType< + DDataParser.Output, + string, + "strict" + >; + + type _CheckIn = ExpectType< + DDataParser.Input, + string | number | bigint | boolean | symbol | null | undefined, + "strict" + >; + }); + + it("coerces supported values through the coercer", () => { + const schema = DDataParser.coercer(DDataParser.string()); + + expect(schema.parse(42)).toStrictEqual(DEither.success("42")); + expect(schema.parse(42n)).toStrictEqual(DEither.success("42")); + expect(schema.parse(true)).toStrictEqual(DEither.success("true")); + expect(schema.parse(null)).toStrictEqual(DEither.success("null")); + expect(schema.parse(undefined)).toStrictEqual(DEither.success("undefined")); + }); + + it("fails when transformed value is not a string", () => { + const schema = DDataParser.coercer(DDataParser.string()); + + expect(schema.parse(Object.create(null))).toStrictEqual(DEither.error(expect.any(Object))); + }); +}); diff --git a/tests/dataParser/parsers/coercer/transformers/templateLiteral.test.ts b/tests/dataParser/parsers/coercer/transformers/templateLiteral.test.ts new file mode 100644 index 000000000..5a5e60249 --- /dev/null +++ b/tests/dataParser/parsers/coercer/transformers/templateLiteral.test.ts @@ -0,0 +1,47 @@ +import { DDataParser, DEither, type ExpectType } from "@scripts"; + +describe("DDataParser coercer template literal transformer", () => { + it("infers coerced input, output, and checker refine value types", () => { + const schema = DDataParser.coercer( + DDataParser.templateLiteral([DDataParser.boolean()]), + ).addChecker( + DDataParser.checkerRefine((value) => { + type _CheckRefineValue = ExpectType; + return true; + }), + ); + + type _CheckOut = ExpectType< + DDataParser.Output, + `${boolean}`, + "strict" + >; + + type _CheckIn = ExpectType< + DDataParser.Input, + `${boolean}` | number | bigint | boolean, + "strict" + >; + }); + + it("coerces supported primitive values through the coercer", () => { + const booleanSchema = DDataParser.coercer( + DDataParser.templateLiteral([DDataParser.boolean()]), + ); + const numberSchema = DDataParser.coercer( + DDataParser.templateLiteral([DDataParser.number()]), + ); + + expect(booleanSchema.parse(false)).toStrictEqual(DEither.success("false")); + expect(numberSchema.parse(42)).toStrictEqual(DEither.success("42")); + expect(numberSchema.parse(42n)).toStrictEqual(DEither.success("42")); + }); + + it("fails when transformed value does not match the template literal", () => { + const schema = DDataParser.coercer( + DDataParser.templateLiteral(["id-", DDataParser.number()]), + ); + + expect(schema.parse(42)).toStrictEqual(DEither.error(expect.any(Object))); + }); +}); diff --git a/tests/dataParser/parsers/coercer/transformers/time.test.ts b/tests/dataParser/parsers/coercer/transformers/time.test.ts new file mode 100644 index 000000000..5e8f7e179 --- /dev/null +++ b/tests/dataParser/parsers/coercer/transformers/time.test.ts @@ -0,0 +1,38 @@ +import { DDataParser, DDate, DEither, type ExpectType } from "@scripts"; + +describe("DDataParser coercer time transformer", () => { + it("infers coerced input, output, and checker refine value types", () => { + const schema = DDataParser.coercer(DDataParser.time()).addChecker( + DDataParser.checkerRefine((value) => { + type _CheckRefineValue = ExpectType; + return true; + }), + ); + + type _CheckOut = ExpectType< + DDataParser.Output, + DDate.TheTime, + "strict" + >; + + type _CheckIn = ExpectType< + DDataParser.Input, + DDate.TheTime | number | string, + "strict" + >; + }); + + it("coerces ISO time strings through the coercer", () => { + const schema = DDataParser.coercer(DDataParser.time()); + + expect(schema.parse("01:02")).toStrictEqual( + DEither.success(DDate.createTimeOrThrow("time3720000+")), + ); + }); + + it("fails when transformed value is not a time", () => { + const schema = DDataParser.coercer(DDataParser.time()); + + expect(schema.parse("not-a-time")).toStrictEqual(DEither.error(expect.any(Object))); + }); +}); From 76c2fe7bbe7aa27cf578e632316fec88bf5beceb Mon Sep 17 00:00:00 2001 From: ZeRiix Date: Tue, 14 Jul 2026 10:45:30 +0200 Subject: [PATCH 7/8] fix(94): relocation of the specific .coerce() to the Extended base --- .../extended/base/coerce/example.ts | 29 +++++ .../dataParser/extended/base/coerce/index.md | 19 ++++ .../extended/bigint/coerce/example.ts | 18 ---- .../extended/bigint/coerce/index.md | 17 --- .../extended/boolean/coerce/example.ts | 16 --- .../extended/boolean/coerce/index.md | 17 --- jsDoc/dataParser/extended/coercer/index.md | 4 +- .../extended/coercer/transformers/index.md | 2 +- .../extended/date/coerce/example.ts | 16 --- .../dataParser/extended/date/coerce/index.md | 17 --- .../extended/empty/coerce/example.ts | 17 --- .../dataParser/extended/empty/coerce/index.md | 17 --- .../dataParser/extended/nil/coerce/example.ts | 18 ---- jsDoc/dataParser/extended/nil/coerce/index.md | 17 --- .../extended/number/coerce/example.ts | 18 ---- .../extended/number/coerce/index.md | 17 --- .../extended/string/coerce/example.ts | 18 ---- .../extended/string/coerce/index.md | 17 --- .../templateLiteral/coerce/example.ts | 19 ---- .../extended/templateLiteral/coerce/index.md | 17 --- .../extended/time/coerce/example.ts | 18 ---- .../dataParser/extended/time/coerce/index.md | 17 --- scripts/dataParser/extended/base.ts | 100 +++++++++++++++++- scripts/dataParser/extended/bigint.ts | 19 +--- scripts/dataParser/extended/boolean.ts | 19 +--- scripts/dataParser/extended/coercer.ts | 88 +-------------- scripts/dataParser/extended/date.ts | 19 +--- scripts/dataParser/extended/empty.ts | 19 +--- scripts/dataParser/extended/literal.ts | 4 +- scripts/dataParser/extended/nil.ts | 19 +--- scripts/dataParser/extended/number.ts | 19 +--- scripts/dataParser/extended/object.ts | 4 +- scripts/dataParser/extended/string.ts | 19 +--- .../dataParser/extended/templateLiteral.ts | 19 +--- scripts/dataParser/extended/time.ts | 19 +--- scripts/dataParser/extended/unknown.ts | 4 +- 36 files changed, 174 insertions(+), 562 deletions(-) create mode 100644 jsDoc/dataParser/extended/base/coerce/example.ts create mode 100644 jsDoc/dataParser/extended/base/coerce/index.md delete mode 100644 jsDoc/dataParser/extended/bigint/coerce/example.ts delete mode 100644 jsDoc/dataParser/extended/bigint/coerce/index.md delete mode 100644 jsDoc/dataParser/extended/boolean/coerce/example.ts delete mode 100644 jsDoc/dataParser/extended/boolean/coerce/index.md delete mode 100644 jsDoc/dataParser/extended/date/coerce/example.ts delete mode 100644 jsDoc/dataParser/extended/date/coerce/index.md delete mode 100644 jsDoc/dataParser/extended/empty/coerce/example.ts delete mode 100644 jsDoc/dataParser/extended/empty/coerce/index.md delete mode 100644 jsDoc/dataParser/extended/nil/coerce/example.ts delete mode 100644 jsDoc/dataParser/extended/nil/coerce/index.md delete mode 100644 jsDoc/dataParser/extended/number/coerce/example.ts delete mode 100644 jsDoc/dataParser/extended/number/coerce/index.md delete mode 100644 jsDoc/dataParser/extended/string/coerce/example.ts delete mode 100644 jsDoc/dataParser/extended/string/coerce/index.md delete mode 100644 jsDoc/dataParser/extended/templateLiteral/coerce/example.ts delete mode 100644 jsDoc/dataParser/extended/templateLiteral/coerce/index.md delete mode 100644 jsDoc/dataParser/extended/time/coerce/example.ts delete mode 100644 jsDoc/dataParser/extended/time/coerce/index.md diff --git a/jsDoc/dataParser/extended/base/coerce/example.ts b/jsDoc/dataParser/extended/base/coerce/example.ts new file mode 100644 index 000000000..0814fc098 --- /dev/null +++ b/jsDoc/dataParser/extended/base/coerce/example.ts @@ -0,0 +1,29 @@ +import { DPE, E, unwrap } from "@scripts"; + +const numberParser = DPE.number() + .min(1) + .coerce(); +const numberResult = numberParser.parse("42"); +if (E.isRight(numberResult)) { + const value = unwrap(numberResult); + // value: number +} + +const booleanParser = DPE.boolean().coerce(); +const booleanResult = booleanParser.parse("true"); + +const userParser = DPE.object({ + name: DPE.string() + .min(1) + .coerce(), + active: DPE.boolean().coerce(), + createdAt: DPE.date().coerce(), +}); +const userResult = userParser.parse({ + name: 123, + active: 1, + createdAt: "2024-01-01T00:00:00.000Z", +}); + +const passThrough = DPE.object({ id: DPE.number() }).coerce(); +const passThroughResult = passThrough.parse({ id: 1 }); diff --git a/jsDoc/dataParser/extended/base/coerce/index.md b/jsDoc/dataParser/extended/base/coerce/index.md new file mode 100644 index 000000000..f5030acd7 --- /dev/null +++ b/jsDoc/dataParser/extended/base/coerce/index.md @@ -0,0 +1,19 @@ +Wraps the current extended parser in a coercer parser. + +**Supported call styles:** +- Method: `dataParser.coerce()` -> returns a coercer parser + +Keeps the current parser as the inner parser. When a coercion transformer is registered for the inner parser kind, input is transformed before validation; otherwise the original input is passed to the inner parser. + +```ts +{@include dataParser/extended/base/coerce/example.ts[3,25]} +``` + +@remarks +- Parsed output is always the output of the current parser. +- Accepted input is widened only for parser kinds registered in `DPE.DataParserCoercerExtended.transformers`. +- Call this method after parser-specific methods like `.min(...)`, `.max(...)`, `.regex(...)`, or `.int()` so the coercer keeps those checks. + +@see https://utils.duplojs.dev/en/v1/api/dataParser/coercer + +@namespace DPE diff --git a/jsDoc/dataParser/extended/bigint/coerce/example.ts b/jsDoc/dataParser/extended/bigint/coerce/example.ts deleted file mode 100644 index 2856918c4..000000000 --- a/jsDoc/dataParser/extended/bigint/coerce/example.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { DPE, E, unwrap } from "@scripts"; - -const parser = DPE.bigint().coerce(); -const result = parser.parse("42"); -if (E.isRight(result)) { - const value = unwrap(result); - // value: bigint -} - -const withMin = DPE.bigint() - .min(1n) - .coerce(); -const minResult = withMin.parse("42"); - -const withMax = DPE.bigint() - .max(100n) - .coerce(); -const maxResult = withMax.parse(42); diff --git a/jsDoc/dataParser/extended/bigint/coerce/index.md b/jsDoc/dataParser/extended/bigint/coerce/index.md deleted file mode 100644 index b58af43c7..000000000 --- a/jsDoc/dataParser/extended/bigint/coerce/index.md +++ /dev/null @@ -1,17 +0,0 @@ -Wraps a bigint parser in an extended coercer parser. - -**Supported call styles:** -- Method: `dataParser.coerce()` -> returns a coercer parser - -Keeps the current bigint parser as the inner parser, then accepts coercible string, number, and boolean inputs before validating the final bigint. - -```ts -{@include dataParser/extended/bigint/coerce/example.ts[3,16]} -``` - -@remarks -Call this method after bigint-specific methods like `.min(...)` and `.max(...)` so the coercer keeps those checks. - -@see https://utils.duplojs.dev/en/v1/api/dataParser/bigint - -@namespace DPE diff --git a/jsDoc/dataParser/extended/boolean/coerce/example.ts b/jsDoc/dataParser/extended/boolean/coerce/example.ts deleted file mode 100644 index 3d436b83a..000000000 --- a/jsDoc/dataParser/extended/boolean/coerce/example.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { DPE, E, unwrap } from "@scripts"; - -const parser = DPE.boolean().coerce(); -const result = parser.parse("true"); -if (E.isRight(result)) { - const value = unwrap(result); - // value: boolean -} - -const fromNumber = DPE.boolean().coerce(); -const numberResult = fromNumber.parse(1); - -const optionalBoolean = DPE.boolean() - .coerce() - .optional(); -const optionalResult = optionalBoolean.parse(undefined); diff --git a/jsDoc/dataParser/extended/boolean/coerce/index.md b/jsDoc/dataParser/extended/boolean/coerce/index.md deleted file mode 100644 index 7076d4477..000000000 --- a/jsDoc/dataParser/extended/boolean/coerce/index.md +++ /dev/null @@ -1,17 +0,0 @@ -Wraps a boolean parser in an extended coercer parser. - -**Supported call styles:** -- Method: `dataParser.coerce()` -> returns a coercer parser - -Keeps the current boolean parser as the inner parser, then accepts coercible string and number inputs before validating the final boolean. - -```ts -{@include dataParser/extended/boolean/coerce/example.ts[3,16]} -``` - -@remarks -The parser output remains boolean. The coercer only widens accepted input. - -@see https://utils.duplojs.dev/en/v1/api/dataParser/boolean - -@namespace DPE diff --git a/jsDoc/dataParser/extended/coercer/index.md b/jsDoc/dataParser/extended/coercer/index.md index f4d8d60b1..aaafcddda 100644 --- a/jsDoc/dataParser/extended/coercer/index.md +++ b/jsDoc/dataParser/extended/coercer/index.md @@ -11,8 +11,8 @@ This parser does not represent a data type by itself. It represents a parsing ac @remarks - Parsed output is always the output of the inner parser. - Accepted input is widened with the values supported by the inner parser coercion transformer. -- Prefer the fluent `.coerce()` method on supported extended parsers when you need parser-specific methods before coercion. -- Before wrapping an extended parser with `DPE.coercer(parser)`, check whether that parser exposes `.coerce()`; when it does, prefer `parser.coerce()` so parser-specific fluent methods stay before coercion. +- Prefer the fluent `.coerce()` method on extended parsers when you need parser-specific methods before coercion. +- Use `DPE.coercer(parser)` when you need the direct constructor form. @see https://utils.duplojs.dev/en/v1/api/dataParser/coercer diff --git a/jsDoc/dataParser/extended/coercer/transformers/index.md b/jsDoc/dataParser/extended/coercer/transformers/index.md index c4409368f..40edfdc7b 100644 --- a/jsDoc/dataParser/extended/coercer/transformers/index.md +++ b/jsDoc/dataParser/extended/coercer/transformers/index.md @@ -2,7 +2,7 @@ Defines the transformer map used by extended coercer parsers. Signature: `DPE.DataParserCoercerExtended.transformers` -> `Map` -This map is shared with the classic coercer. It links a DataParser kind to the function that prepares raw input before the inner parser runs. The fluent `.coerce()` methods are only added on extended parsers whose kind is supported by this coercion system. +This map is shared with the classic coercer. It links a DataParser kind to the function that prepares raw input before the inner parser runs. The fluent `.coerce()` method is available on all extended parsers, but only registered kinds widen their accepted input with a transformer. ```ts {@include dataParser/extended/coercer/transformers/example.ts[3,16]} diff --git a/jsDoc/dataParser/extended/date/coerce/example.ts b/jsDoc/dataParser/extended/date/coerce/example.ts deleted file mode 100644 index 5a400651a..000000000 --- a/jsDoc/dataParser/extended/date/coerce/example.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { DPE, E, unwrap } from "@scripts"; - -const parser = DPE.date().coerce(); -const result = parser.parse("2024-01-01T00:00:00.000Z"); -if (E.isRight(result)) { - const value = unwrap(result); - // value: TheDate -} - -const fromTimestamp = DPE.date().coerce(); -const timestampResult = fromTimestamp.parse(1); - -const nullableDate = DPE.date() - .coerce() - .nullable(); -const nullableResult = nullableDate.parse(null); diff --git a/jsDoc/dataParser/extended/date/coerce/index.md b/jsDoc/dataParser/extended/date/coerce/index.md deleted file mode 100644 index 9fcff731f..000000000 --- a/jsDoc/dataParser/extended/date/coerce/index.md +++ /dev/null @@ -1,17 +0,0 @@ -Wraps a date parser in an extended coercer parser. - -**Supported call styles:** -- Method: `dataParser.coerce()` -> returns a coercer parser - -Keeps the current date parser as the inner parser, then accepts coercible string and number inputs before validating the final `TheDate`. - -```ts -{@include dataParser/extended/date/coerce/example.ts[3,16]} -``` - -@remarks -Call this method after date-specific refinements so the coercer keeps those checks. - -@see https://utils.duplojs.dev/en/v1/api/dataParser/date - -@namespace DPE diff --git a/jsDoc/dataParser/extended/empty/coerce/example.ts b/jsDoc/dataParser/extended/empty/coerce/example.ts deleted file mode 100644 index 2a8a00b46..000000000 --- a/jsDoc/dataParser/extended/empty/coerce/example.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { DPE, E } from "@scripts"; - -const parser = DPE.empty().coerce(); -const result = parser.parse("undefined"); -if (E.isRight(result)) { - // E.Success -} - -const nullableEmpty = DPE.empty() - .coerce() - .nullable(); -const nullableResult = nullableEmpty.parse(null); - -const optionalEmpty = DPE.empty() - .coerce() - .optional(); -const optionalResult = optionalEmpty.parse(undefined); diff --git a/jsDoc/dataParser/extended/empty/coerce/index.md b/jsDoc/dataParser/extended/empty/coerce/index.md deleted file mode 100644 index 8d7e8e04f..000000000 --- a/jsDoc/dataParser/extended/empty/coerce/index.md +++ /dev/null @@ -1,17 +0,0 @@ -Wraps an empty parser in an extended coercer parser. - -**Supported call styles:** -- Method: `dataParser.coerce()` -> returns a coercer parser - -Keeps the current empty parser as the inner parser, then accepts the string `"undefined"` before validating the final undefined value. - -```ts -{@include dataParser/extended/empty/coerce/example.ts[3,16]} -``` - -@remarks -The parser output remains `undefined`. The coercer only widens accepted input. - -@see https://utils.duplojs.dev/en/v1/api/dataParser/empty - -@namespace DPE diff --git a/jsDoc/dataParser/extended/nil/coerce/example.ts b/jsDoc/dataParser/extended/nil/coerce/example.ts deleted file mode 100644 index 4ef130228..000000000 --- a/jsDoc/dataParser/extended/nil/coerce/example.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { DPE, E, unwrap } from "@scripts"; - -const parser = DPE.nil().coerce(); -const result = parser.parse("null"); -if (E.isRight(result)) { - const value = unwrap(result); - // value: null -} - -const nullableNil = DPE.nil() - .coerce() - .nullable(); -const nullableResult = nullableNil.parse(null); - -const optionalNil = DPE.nil() - .coerce() - .optional(); -const optionalResult = optionalNil.parse(undefined); diff --git a/jsDoc/dataParser/extended/nil/coerce/index.md b/jsDoc/dataParser/extended/nil/coerce/index.md deleted file mode 100644 index 7ccba6660..000000000 --- a/jsDoc/dataParser/extended/nil/coerce/index.md +++ /dev/null @@ -1,17 +0,0 @@ -Wraps a nil parser in an extended coercer parser. - -**Supported call styles:** -- Method: `dataParser.coerce()` -> returns a coercer parser - -Keeps the current nil parser as the inner parser, then accepts the string `"null"` before validating the final null value. - -```ts -{@include dataParser/extended/nil/coerce/example.ts[3,16]} -``` - -@remarks -The parser output remains `null`. The coercer only widens accepted input. - -@see https://utils.duplojs.dev/en/v1/api/dataParser/nil - -@namespace DPE diff --git a/jsDoc/dataParser/extended/number/coerce/example.ts b/jsDoc/dataParser/extended/number/coerce/example.ts deleted file mode 100644 index fcf14d092..000000000 --- a/jsDoc/dataParser/extended/number/coerce/example.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { DPE, E, unwrap } from "@scripts"; - -const parser = DPE.number().coerce(); -const result = parser.parse("42"); -if (E.isRight(result)) { - const value = unwrap(result); - // value: number -} - -const withMin = DPE.number() - .min(4) - .coerce(); -const minResult = withMin.parse("5"); - -const asInt = DPE.number() - .int() - .coerce(); -const intResult = asInt.parse("10"); diff --git a/jsDoc/dataParser/extended/number/coerce/index.md b/jsDoc/dataParser/extended/number/coerce/index.md deleted file mode 100644 index 2d1b01e0b..000000000 --- a/jsDoc/dataParser/extended/number/coerce/index.md +++ /dev/null @@ -1,17 +0,0 @@ -Wraps a number parser in an extended coercer parser. - -**Supported call styles:** -- Method: `dataParser.coerce()` -> returns a coercer parser - -Keeps the current number parser as the inner parser, then accepts coercible inputs such as numeric strings, bigint, boolean, and null before validating the final number. - -```ts -{@include dataParser/extended/number/coerce/example.ts[3,16]} -``` - -@remarks -Call this method after number-specific methods like `.min(...)`, `.max(...)`, and `.int()` so the coercer keeps those checks. - -@see https://utils.duplojs.dev/en/v1/api/dataParser/number - -@namespace DPE diff --git a/jsDoc/dataParser/extended/string/coerce/example.ts b/jsDoc/dataParser/extended/string/coerce/example.ts deleted file mode 100644 index d4acec7e4..000000000 --- a/jsDoc/dataParser/extended/string/coerce/example.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { DPE, E, unwrap } from "@scripts"; - -const parser = DPE.string().coerce(); -const result = parser.parse(42); -if (E.isRight(result)) { - const value = unwrap(result); - // value: string -} - -const withMin = DPE.string() - .min(2) - .coerce(); -const minResult = withMin.parse(42); - -const withRegex = DPE.string() - .regex(/^\d+$/) - .coerce(); -const regexResult = withRegex.parse(42); diff --git a/jsDoc/dataParser/extended/string/coerce/index.md b/jsDoc/dataParser/extended/string/coerce/index.md deleted file mode 100644 index 85b179ef2..000000000 --- a/jsDoc/dataParser/extended/string/coerce/index.md +++ /dev/null @@ -1,17 +0,0 @@ -Wraps a string parser in an extended coercer parser. - -**Supported call styles:** -- Method: `dataParser.coerce()` -> returns a coercer parser - -Keeps the current string parser as the inner parser, then accepts coercible inputs such as number, bigint, boolean, symbol, null, and undefined before validating the final string. - -```ts -{@include dataParser/extended/string/coerce/example.ts[3,16]} -``` - -@remarks -Call this method after string-specific methods like `.min(...)`, `.max(...)`, and `.regex(...)` so the coercer keeps those checks. - -@see https://utils.duplojs.dev/en/v1/api/dataParser/string - -@namespace DPE diff --git a/jsDoc/dataParser/extended/templateLiteral/coerce/example.ts b/jsDoc/dataParser/extended/templateLiteral/coerce/example.ts deleted file mode 100644 index 625f0a901..000000000 --- a/jsDoc/dataParser/extended/templateLiteral/coerce/example.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { DPE, E, unwrap } from "@scripts"; - -const parser = DPE.templateLiteral([DPE.number()]).coerce(); -const result = parser.parse(42); -if (E.isRight(result)) { - const value = unwrap(result); - // value: `${number}` -} - -const withPrefix = DPE.templateLiteral([ - "item-", - DPE.number(), -]).coerce(); -const prefixResult = withPrefix.parse("item-42"); - -const nullableTemplate = DPE.templateLiteral([DPE.number()]) - .coerce() - .nullable(); -const nullableResult = nullableTemplate.parse(null); diff --git a/jsDoc/dataParser/extended/templateLiteral/coerce/index.md b/jsDoc/dataParser/extended/templateLiteral/coerce/index.md deleted file mode 100644 index caddcf72a..000000000 --- a/jsDoc/dataParser/extended/templateLiteral/coerce/index.md +++ /dev/null @@ -1,17 +0,0 @@ -Wraps a template literal parser in an extended coercer parser. - -**Supported call styles:** -- Method: `dataParser.coerce()` -> returns a coercer parser - -Keeps the current template literal parser as the inner parser, then accepts primitive values that can be stringified before validating the final template literal string. - -```ts -{@include dataParser/extended/templateLiteral/coerce/example.ts[3,16]} -``` - -@remarks -Use this method when the whole input can be stringified into the expected template literal shape. - -@see https://utils.duplojs.dev/en/v1/api/dataParser/templateLiteral - -@namespace DPE diff --git a/jsDoc/dataParser/extended/time/coerce/example.ts b/jsDoc/dataParser/extended/time/coerce/example.ts deleted file mode 100644 index 7ca10b1d4..000000000 --- a/jsDoc/dataParser/extended/time/coerce/example.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { D, DPE, E, unwrap } from "@scripts"; - -const parser = DPE.time().coerce(); -const result = parser.parse("01:02"); -if (E.isRight(result)) { - const value = unwrap(result); - // value: TheTime -} - -const withMin = DPE.time() - .min(D.createTime(1, "minute")) - .coerce(); -const minResult = withMin.parse("01:02"); - -const nullableTime = DPE.time() - .coerce() - .nullable(); -const nullableResult = nullableTime.parse(null); diff --git a/jsDoc/dataParser/extended/time/coerce/index.md b/jsDoc/dataParser/extended/time/coerce/index.md deleted file mode 100644 index e25e966bd..000000000 --- a/jsDoc/dataParser/extended/time/coerce/index.md +++ /dev/null @@ -1,17 +0,0 @@ -Wraps a time parser in an extended coercer parser. - -**Supported call styles:** -- Method: `dataParser.coerce()` -> returns a coercer parser - -Keeps the current time parser as the inner parser, then accepts coercible ISO-like time strings before validating the final `TheTime`. - -```ts -{@include dataParser/extended/time/coerce/example.ts[3,17]} -``` - -@remarks -Call this method after time-specific methods like `.min(...)` and `.max(...)` so the coercer keeps those checks. - -@see https://utils.duplojs.dev/en/v1/api/dataParser/time - -@namespace DPE diff --git a/scripts/dataParser/extended/base.ts b/scripts/dataParser/extended/base.ts index 7e1aa3af0..695d8da22 100644 --- a/scripts/dataParser/extended/base.ts +++ b/scripts/dataParser/extended/base.ts @@ -2,9 +2,9 @@ import * as DCommon from "@scripts/common"; import { createDataParserKind } from "../kind"; import { DataParserBase, type DataParser, type DataParserDefinition } from "../base"; import * as dataParsers from "../parsers"; -import { type DataParserError } from "../error"; -import { type DataParserChecker, type DataParserCheckerBase, type DataParserCheckerDefinition } from "../baseChecker"; -import { type Output, type PrepareDataParserDefinition, type DataParserExtendedBaseInit, type MergeDefinition, type Input, type AddCheckersToDefinition } from "../types"; +import type { DataParserError } from "../error"; +import type { DataParserChecker, DataParserCheckerBase, DataParserCheckerDefinition } from "../baseChecker"; +import type { Output, PrepareDataParserDefinition, DataParserExtendedBaseInit, MergeDefinition, Input, AddCheckersToDefinition } from "../types"; export const dataParserExtendedKind = createDataParserKind("extended"); @@ -391,6 +391,20 @@ export abstract class DataParserBaseExtended< ); } + /** + * {@include dataParser/extended/base/coerce/index.md} + */ + public coerce< + GenericThis extends this = this, + >(): DataParserCoercerExtended< + MergeDefinition< + dataParsers.DataParserDefinitionCoercer, + { inner: GenericThis } + > + > { + return DataParserCoercerExtended.create(this) as never; + } + public static initExtended< GenericConstructor extends DCommon.SimplifyTopLevel>, >( @@ -1121,6 +1135,86 @@ export class DataParserErrorHandlerExtended< } } +export class DataParserCoercerExtended< + GenericDefinition extends dataParsers.DataParserDefinitionCoercer = dataParsers.DataParserDefinitionCoercer, +> extends DataParserBaseExtended.initExtended(dataParsers.DataParserCoercer)< + GenericDefinition, + Output>, + Input> + > { + public get classConstructor() { + return this.checkConstructor(DataParserCoercerExtended); + } + + /** + * {@include dataParser/extended/coercer/transformers/index.md} + */ + public static transformers = dataParsers.DataParserCoercer.transformers; + + public declare addChecker: < + GenericChecker extends readonly [ + DataParserChecker>, + ...DataParserChecker>[], + ], + >( + ...args: DCommon.FixDeepFunctionInfer< + readonly [ + DataParserChecker>, + ...DataParserChecker>[], + ], + GenericChecker + > + ) => DataParserCoercerExtended< + AddCheckersToDefinition< + GenericDefinition, + GenericChecker + > + >; + + public declare refine: ( + theFunction: (input: Output) => boolean, + definition?: Partial< + Omit + >, + ) => DataParserCoercerExtended< + AddCheckersToDefinition< + GenericDefinition, + readonly [dataParsers.CheckerRefineImplementation>] + > + >; + + /** + * {@include dataParser/extended/coercer/index.md} + */ + public static override create< + GenericDataParser extends DataParser, + const GenericDefinition extends PrepareDataParserDefinition< + dataParsers.DataParserDefinitionCoercer< + Output + >, + "inner" + > = never, + >( + inner: GenericDataParser, + definition?: DCommon.FixDeepFunctionInfer< + PrepareDataParserDefinition< + dataParsers.DataParserDefinitionCoercer< + Output + >, + "inner" + >, + GenericDefinition + >, + ): DataParserCoercerExtended< + MergeDefinition< + dataParsers.DataParserDefinitionCoercer, + DCommon.NeverCoalescing & { inner: GenericDataParser } + > + > { + return new DataParserCoercerExtended(this.prepareDefinition(inner, definition)) as never; + } +} + export interface DataParserExtended< GenericOutput extends unknown = unknown, GenericInput extends unknown = GenericOutput, diff --git a/scripts/dataParser/extended/bigint.ts b/scripts/dataParser/extended/bigint.ts index 781fe2429..1a85ad300 100644 --- a/scripts/dataParser/extended/bigint.ts +++ b/scripts/dataParser/extended/bigint.ts @@ -1,9 +1,8 @@ import { detachObjectMethod, type FixDeepFunctionInfer, type NeverCoalescing } from "@scripts/common"; import { DataParserBaseExtended } from "./base"; -import { type AddCheckersToDefinition, type Output, type MergeDefinition, type PrepareDataParserDefinition, type Input } from "../types"; +import type { AddCheckersToDefinition, Output, MergeDefinition, PrepareDataParserDefinition, Input } from "../types"; import * as dataParsers from "../parsers"; -import { type DataParserChecker } from "../baseChecker"; -import { DataParserCoercerExtended } from "./coercer"; +import type { DataParserChecker } from "../baseChecker"; export class DataParserBigIntExtended< GenericDefinition extends dataParsers.DataParserDefinitionBigInt = dataParsers.DataParserDefinitionBigInt, @@ -72,20 +71,6 @@ export class DataParserBigIntExtended< return this.addChecker(dataParsers.checkerBigIntMax(max, definition)); } - /** - * {@include dataParser/extended/bigint/coerce/index.md} - */ - public coerce< - GenericThis extends this = this, - >(): DataParserCoercerExtended< - MergeDefinition< - dataParsers.DataParserDefinitionCoercer, - { inner: GenericThis } - > - > { - return DataParserCoercerExtended.create(this) as never; - } - /** * {@include dataParser/extended/bigint/index.md} */ diff --git a/scripts/dataParser/extended/boolean.ts b/scripts/dataParser/extended/boolean.ts index 95ded72bc..957933c5f 100644 --- a/scripts/dataParser/extended/boolean.ts +++ b/scripts/dataParser/extended/boolean.ts @@ -1,10 +1,9 @@ import { detachObjectMethod, type FixDeepFunctionInfer, type NeverCoalescing } from "@scripts/common"; import { DataParserBaseExtended } from "./base"; -import { type AddCheckersToDefinition, type Output, type MergeDefinition, type PrepareDataParserDefinition, type Input } from "../types"; +import type { AddCheckersToDefinition, Output, MergeDefinition, PrepareDataParserDefinition, Input } from "../types"; import * as dataParsers from "../parsers"; -import { type DataParserChecker } from "../baseChecker"; -import { DataParserCoercerExtended } from "./coercer"; +import type { DataParserChecker } from "../baseChecker"; export class DataParserBooleanExtended< GenericDefinition extends dataParsers.DataParserDefinitionBoolean = dataParsers.DataParserDefinitionBoolean, @@ -49,20 +48,6 @@ export class DataParserBooleanExtended< > >; - /** - * {@include dataParser/extended/boolean/coerce/index.md} - */ - public coerce< - GenericThis extends this = this, - >(): DataParserCoercerExtended< - MergeDefinition< - dataParsers.DataParserDefinitionCoercer, - { inner: GenericThis } - > - > { - return DataParserCoercerExtended.create(this) as never; - } - /** * {@include dataParser/extended/boolean/index.md} */ diff --git a/scripts/dataParser/extended/coercer.ts b/scripts/dataParser/extended/coercer.ts index f03e5db06..789fae14b 100644 --- a/scripts/dataParser/extended/coercer.ts +++ b/scripts/dataParser/extended/coercer.ts @@ -1,89 +1,5 @@ -import { type NeverCoalescing, type FixDeepFunctionInfer, detachObjectMethod } from "@scripts/common"; -import type { DataParserChecker } from "../baseChecker"; -import * as dataParsers from "../parsers"; -import type { AddCheckersToDefinition, Input, MergeDefinition, Output, PrepareDataParserDefinition } from "../types"; -import { DataParserBaseExtended } from "./base"; -import type { DataParser } from "../base"; - -export class DataParserCoercerExtended< - GenericDefinition extends dataParsers.DataParserDefinitionCoercer = dataParsers.DataParserDefinitionCoercer, -> extends DataParserBaseExtended.initExtended(dataParsers.DataParserCoercer)< - GenericDefinition, - Output>, - Input> - > { - public get classConstructor() { - return this.checkConstructor(DataParserCoercerExtended); - } - - /** - * {@include dataParser/extended/coercer/transformers/index.md} - */ - public static transformers = dataParsers.DataParserCoercer.transformers; - - public declare addChecker: < - GenericChecker extends readonly [ - DataParserChecker>, - ...DataParserChecker>[], - ], - >( - ...args: FixDeepFunctionInfer< - readonly [ - DataParserChecker>, - ...DataParserChecker>[], - ], - GenericChecker - > - ) => DataParserCoercerExtended< - AddCheckersToDefinition< - GenericDefinition, - GenericChecker - > - >; - - public declare refine: ( - theFunction: (input: Output) => boolean, - definition?: Partial< - Omit - >, - ) => DataParserCoercerExtended< - AddCheckersToDefinition< - GenericDefinition, - readonly [dataParsers.CheckerRefineImplementation>] - > - >; - - /** - * {@include dataParser/extended/coercer/index.md} - */ - public static override create< - GenericDataParser extends DataParser, - const GenericDefinition extends PrepareDataParserDefinition< - dataParsers.DataParserDefinitionCoercer< - Output - >, - "inner" - > = never, - >( - inner: GenericDataParser, - definition?: FixDeepFunctionInfer< - PrepareDataParserDefinition< - dataParsers.DataParserDefinitionCoercer< - Output - >, - "inner" - >, - GenericDefinition - >, - ): DataParserCoercerExtended< - MergeDefinition< - dataParsers.DataParserDefinitionCoercer, - NeverCoalescing & { inner: GenericDataParser } - > - > { - return new DataParserCoercerExtended(this.prepareDefinition(inner, definition)) as never; - } -} +import { detachObjectMethod } from "@scripts/common"; +import { DataParserCoercerExtended } from "./base"; /** * {@include dataParser/extended/coercer/index.md} diff --git a/scripts/dataParser/extended/date.ts b/scripts/dataParser/extended/date.ts index 542f04d4d..b203d9584 100644 --- a/scripts/dataParser/extended/date.ts +++ b/scripts/dataParser/extended/date.ts @@ -1,10 +1,9 @@ import { detachObjectMethod, type FixDeepFunctionInfer, type NeverCoalescing } from "@scripts/common"; import { DataParserBaseExtended } from "./base"; -import { type AddCheckersToDefinition, type Output, type MergeDefinition, type PrepareDataParserDefinition, type Input } from "../types"; +import type { AddCheckersToDefinition, Output, MergeDefinition, PrepareDataParserDefinition, Input } from "../types"; import * as dataParsers from "../parsers"; -import { type DataParserChecker } from "../baseChecker"; -import { DataParserCoercerExtended } from "./coercer"; +import type { DataParserChecker } from "../baseChecker"; export class DataParserDateExtended< GenericDefinition extends dataParsers.DataParserDefinitionDate = dataParsers.DataParserDefinitionDate, @@ -49,20 +48,6 @@ export class DataParserDateExtended< > >; - /** - * {@include dataParser/extended/date/coerce/index.md} - */ - public coerce< - GenericThis extends this = this, - >(): DataParserCoercerExtended< - MergeDefinition< - dataParsers.DataParserDefinitionCoercer, - { inner: GenericThis } - > - > { - return DataParserCoercerExtended.create(this) as never; - } - /** * {@include dataParser/extended/date/index.md} */ diff --git a/scripts/dataParser/extended/empty.ts b/scripts/dataParser/extended/empty.ts index b41839394..f18d2d35b 100644 --- a/scripts/dataParser/extended/empty.ts +++ b/scripts/dataParser/extended/empty.ts @@ -1,10 +1,9 @@ import { detachObjectMethod, type FixDeepFunctionInfer, type NeverCoalescing } from "@scripts/common"; import { DataParserBaseExtended } from "./base"; -import { type AddCheckersToDefinition, type Output, type MergeDefinition, type PrepareDataParserDefinition, type Input } from "../types"; +import type { AddCheckersToDefinition, Output, MergeDefinition, PrepareDataParserDefinition, Input } from "../types"; import * as dataParsers from "../parsers"; -import { type DataParserChecker } from "../baseChecker"; -import { DataParserCoercerExtended } from "./coercer"; +import type { DataParserChecker } from "../baseChecker"; export class DataParserEmptyExtended< GenericDefinition extends dataParsers.DataParserDefinitionEmpty = dataParsers.DataParserDefinitionEmpty, @@ -49,20 +48,6 @@ export class DataParserEmptyExtended< > >; - /** - * {@include dataParser/extended/empty/coerce/index.md} - */ - public coerce< - GenericThis extends this = this, - >(): DataParserCoercerExtended< - MergeDefinition< - dataParsers.DataParserDefinitionCoercer, - { inner: GenericThis } - > - > { - return DataParserCoercerExtended.create(this) as never; - } - /** * {@include dataParser/extended/empty/index.md} */ diff --git a/scripts/dataParser/extended/literal.ts b/scripts/dataParser/extended/literal.ts index 4ae52688b..00414a609 100644 --- a/scripts/dataParser/extended/literal.ts +++ b/scripts/dataParser/extended/literal.ts @@ -1,8 +1,8 @@ import { detachObjectMethod, type FixDeepFunctionInfer, type NeverCoalescing } from "@scripts/common"; import { DataParserBaseExtended } from "./base"; -import { type AddCheckersToDefinition, type Output, type MergeDefinition, type PrepareDataParserDefinition, type Input } from "../types"; +import type { AddCheckersToDefinition, Output, MergeDefinition, PrepareDataParserDefinition, Input } from "../types"; import * as dataParsers from "../parsers"; -import { type DataParserChecker } from "../baseChecker"; +import type { DataParserChecker } from "../baseChecker"; export class DataParserLiteralExtended< GenericDefinition extends dataParsers.DataParserDefinitionLiteral = dataParsers.DataParserDefinitionLiteral, diff --git a/scripts/dataParser/extended/nil.ts b/scripts/dataParser/extended/nil.ts index 7f7a56979..a4477ad01 100644 --- a/scripts/dataParser/extended/nil.ts +++ b/scripts/dataParser/extended/nil.ts @@ -1,10 +1,9 @@ import { detachObjectMethod, type FixDeepFunctionInfer, type NeverCoalescing } from "@scripts/common"; import { DataParserBaseExtended } from "./base"; -import { type AddCheckersToDefinition, type Output, type MergeDefinition, type PrepareDataParserDefinition, type Input } from "../types"; +import type { AddCheckersToDefinition, Output, MergeDefinition, PrepareDataParserDefinition, Input } from "../types"; import * as dataParsers from "../parsers"; -import { type DataParserChecker } from "../baseChecker"; -import { DataParserCoercerExtended } from "./coercer"; +import type { DataParserChecker } from "../baseChecker"; export class DataParserNilExtended< GenericDefinition extends dataParsers.DataParserDefinitionNil = dataParsers.DataParserDefinitionNil, @@ -49,20 +48,6 @@ export class DataParserNilExtended< > >; - /** - * {@include dataParser/extended/nil/coerce/index.md} - */ - public coerce< - GenericThis extends this = this, - >(): DataParserCoercerExtended< - MergeDefinition< - dataParsers.DataParserDefinitionCoercer, - { inner: GenericThis } - > - > { - return DataParserCoercerExtended.create(this) as never; - } - /** * {@include dataParser/extended/nil/index.md} */ diff --git a/scripts/dataParser/extended/number.ts b/scripts/dataParser/extended/number.ts index 90df4b0a5..cb1b9137e 100644 --- a/scripts/dataParser/extended/number.ts +++ b/scripts/dataParser/extended/number.ts @@ -1,9 +1,8 @@ import { detachObjectMethod, type FixDeepFunctionInfer, type NeverCoalescing } from "@scripts/common"; import { DataParserBaseExtended } from "./base"; -import { type AddCheckersToDefinition, type Output, type MergeDefinition, type PrepareDataParserDefinition, type Input } from "../types"; +import type { AddCheckersToDefinition, Output, MergeDefinition, PrepareDataParserDefinition, Input } from "../types"; import * as dataParsers from "../parsers"; -import { type DataParserChecker } from "../baseChecker"; -import { DataParserCoercerExtended } from "./coercer"; +import type { DataParserChecker } from "../baseChecker"; export class DataParserNumberExtended< GenericDefinition extends dataParsers.DataParserDefinitionNumber = dataParsers.DataParserDefinitionNumber, @@ -81,20 +80,6 @@ export class DataParserNumberExtended< return this.addChecker(dataParsers.checkerInt(definition)); } - /** - * {@include dataParser/extended/number/coerce/index.md} - */ - public coerce< - GenericThis extends this = this, - >(): DataParserCoercerExtended< - MergeDefinition< - dataParsers.DataParserDefinitionCoercer, - { inner: GenericThis } - > - > { - return DataParserCoercerExtended.create(this) as never; - } - /** * {@include dataParser/extended/number/index.md} */ diff --git a/scripts/dataParser/extended/object.ts b/scripts/dataParser/extended/object.ts index 700403596..f6dd2d01b 100644 --- a/scripts/dataParser/extended/object.ts +++ b/scripts/dataParser/extended/object.ts @@ -1,9 +1,9 @@ import { detachObjectMethod, type Adaptor, type FixDeepFunctionInfer, type NeverCoalescing, type SimplifyTopLevel } from "@scripts/common"; import { type AssignObjects } from "@scripts/object"; import { DataParserBaseExtended } from "./base"; -import { type AddCheckersToDefinition, type Output, type MergeDefinition, type PrepareDataParserDefinition, type Input } from "../types"; +import type { AddCheckersToDefinition, Output, MergeDefinition, PrepareDataParserDefinition, Input } from "../types"; import * as dataParsers from "../parsers"; -import { type DataParserChecker } from "../baseChecker"; +import type { DataParserChecker } from "../baseChecker"; export class DataParserObjectExtended< GenericDefinition extends dataParsers.DataParserDefinitionObject = dataParsers.DataParserDefinitionObject, diff --git a/scripts/dataParser/extended/string.ts b/scripts/dataParser/extended/string.ts index 987d4c3ae..6ff744544 100644 --- a/scripts/dataParser/extended/string.ts +++ b/scripts/dataParser/extended/string.ts @@ -1,10 +1,9 @@ import { detachObjectMethod, type FixDeepFunctionInfer, type NeverCoalescing } from "@scripts/common"; import { DataParserBaseExtended } from "./base"; -import { type AddCheckersToDefinition, type Output, type MergeDefinition, type PrepareDataParserDefinition, type Input } from "../types"; +import type { AddCheckersToDefinition, Output, MergeDefinition, PrepareDataParserDefinition, Input } from "../types"; import * as dataParsers from "../parsers"; -import { type DataParserChecker } from "../baseChecker"; -import { DataParserCoercerExtended } from "./coercer"; +import type { DataParserChecker } from "../baseChecker"; export class DataParserStringExtended< GenericDefinition extends dataParsers.DataParserDefinitionString = dataParsers.DataParserDefinitionString, @@ -100,20 +99,6 @@ export class DataParserStringExtended< return this.addChecker(dataParsers.checkerRegex(regex, definition)); } - /** - * {@include dataParser/extended/string/coerce/index.md} - */ - public coerce< - GenericThis extends this = this, - >(): DataParserCoercerExtended< - MergeDefinition< - dataParsers.DataParserDefinitionCoercer, - { inner: GenericThis } - > - > { - return DataParserCoercerExtended.create(this) as never; - } - /** * {@include dataParser/extended/string/index.md} */ diff --git a/scripts/dataParser/extended/templateLiteral.ts b/scripts/dataParser/extended/templateLiteral.ts index e21bd85e9..406ab812b 100644 --- a/scripts/dataParser/extended/templateLiteral.ts +++ b/scripts/dataParser/extended/templateLiteral.ts @@ -1,9 +1,8 @@ import { detachObjectMethod, type FixDeepFunctionInfer, type NeverCoalescing } from "@scripts/common"; import { DataParserBaseExtended } from "./base"; -import { type AddCheckersToDefinition, type Output, type MergeDefinition, type PrepareDataParserDefinition, type Input } from "../types"; +import type { AddCheckersToDefinition, Output, MergeDefinition, PrepareDataParserDefinition, Input } from "../types"; import * as dataParsers from "../parsers"; -import { type DataParserChecker } from "../baseChecker"; -import { DataParserCoercerExtended } from "./coercer"; +import type { DataParserChecker } from "../baseChecker"; export class DataParserTemplateLiteralExtended< GenericDefinition extends dataParsers.DataParserDefinitionTemplateLiteral @@ -49,20 +48,6 @@ export class DataParserTemplateLiteralExtended< > >; - /** - * {@include dataParser/extended/templateLiteral/coerce/index.md} - */ - public coerce< - GenericThis extends this = this, - >(): DataParserCoercerExtended< - MergeDefinition< - dataParsers.DataParserDefinitionCoercer, - { inner: GenericThis } - > - > { - return DataParserCoercerExtended.create(this) as never; - } - /** * {@include dataParser/extended/templateLiteral/index.md} */ diff --git a/scripts/dataParser/extended/time.ts b/scripts/dataParser/extended/time.ts index 1be21b3d6..1d83acc8d 100644 --- a/scripts/dataParser/extended/time.ts +++ b/scripts/dataParser/extended/time.ts @@ -1,10 +1,9 @@ import { detachObjectMethod, type FixDeepFunctionInfer, type NeverCoalescing } from "@scripts/common"; import { DataParserBaseExtended } from "./base"; -import { type AddCheckersToDefinition, type Output, type MergeDefinition, type PrepareDataParserDefinition, type Input } from "../types"; +import type { AddCheckersToDefinition, Output, MergeDefinition, PrepareDataParserDefinition, Input } from "../types"; import * as dataParsers from "../parsers"; -import { type DataParserChecker } from "../baseChecker"; +import type { DataParserChecker } from "../baseChecker"; import { type TheTime } from "@scripts/date"; -import { DataParserCoercerExtended } from "./coercer"; export class DataParserTimeExtended< GenericDefinition extends dataParsers.DataParserDefinitionTime = dataParsers.DataParserDefinitionTime, @@ -73,20 +72,6 @@ export class DataParserTimeExtended< return this.addChecker(dataParsers.checkerTimeMax(max, definition)); } - /** - * {@include dataParser/extended/time/coerce/index.md} - */ - public coerce< - GenericThis extends this = this, - >(): DataParserCoercerExtended< - MergeDefinition< - dataParsers.DataParserDefinitionCoercer, - { inner: GenericThis } - > - > { - return DataParserCoercerExtended.create(this) as never; - } - /** * {@include dataParser/extended/time/index.md} */ diff --git a/scripts/dataParser/extended/unknown.ts b/scripts/dataParser/extended/unknown.ts index b3e4d6f1d..4a738e274 100644 --- a/scripts/dataParser/extended/unknown.ts +++ b/scripts/dataParser/extended/unknown.ts @@ -1,9 +1,9 @@ import { detachObjectMethod, type FixDeepFunctionInfer, type NeverCoalescing } from "@scripts/common"; import { DataParserBaseExtended } from "./base"; -import { type AddCheckersToDefinition, type Output, type MergeDefinition, type PrepareDataParserDefinition, type Input } from "../types"; +import type { AddCheckersToDefinition, Output, MergeDefinition, PrepareDataParserDefinition, Input } from "../types"; import * as dataParsers from "../parsers"; -import { type DataParserChecker } from "../baseChecker"; +import type { DataParserChecker } from "../baseChecker"; export class DataParserUnknownExtended< GenericDefinition extends dataParsers.DataParserDefinitionUnknown = dataParsers.DataParserDefinitionUnknown, From c8491098c007510b3b96bb8b78b1d5ee5fb38535 Mon Sep 17 00:00:00 2001 From: ZeRiix Date: Tue, 14 Jul 2026 12:20:02 +0200 Subject: [PATCH 8/8] feat(94): add inference on the input of the dateParser returned by toMapDataParser --- scripts/clean/toMapDataParser.ts | 57 ++++++++++++++--------------- tests/clean/toMapDataParser.test.ts | 56 ++++++++++++++++++++++++++++ 2 files changed, 84 insertions(+), 29 deletions(-) diff --git a/scripts/clean/toMapDataParser.ts b/scripts/clean/toMapDataParser.ts index 3fc57da6a..b395931af 100644 --- a/scripts/clean/toMapDataParser.ts +++ b/scripts/clean/toMapDataParser.ts @@ -3,8 +3,8 @@ import * as DPattern from "../pattern"; import { constrainedTypeKind, constraintHandlerKind, constraintsSetHandlerKind, type ConstraintHandler, type ConstraintsSetHandler, type GetConstraint, type GetConstraints } from "./constraint"; import { newTypeHandlerKind, newTypeKind } from "./newType"; import { primitiveHandlerKind, type PrimitiveHandler } from "./primitive"; -import { type EntityPropertyDefinition, entityPropertyUnionKind, entityPropertyIdentifierKind, entityPropertyStructureKind, entityPropertyArrayKind, entityPropertyNullableKind, entityPropertyDefinitionToDataParser, type EntityProperty } from "./entity"; -import { hasSomeKinds, keyWrappedValue } from "@scripts/common"; +import { type EntityPropertyDefinition, entityPropertyUnionKind, entityPropertyIdentifierKind, entityPropertyStructureKind, entityPropertyArrayKind, entityPropertyNullableKind, entityPropertyDefinitionToDataParser, type EntityInputRawProperty, type EntityProperty } from "./entity"; +import { hasSomeKinds, type IsNever, keyWrappedValue } from "@scripts/common"; type ToMapDataParserInput = ( | ConstraintHandler @@ -25,6 +25,24 @@ type OutputDataParser< ? EntityProperty : never; +type InputDataParser< + GenericInput extends ToMapDataParserInput, +> = GenericInput extends ConstraintHandler + ? IsNever extends true + ? InferredValue + : InferredInput + : GenericInput extends ConstraintsSetHandler + ? IsNever extends true + ? InferredValue + : InferredInput + : GenericInput extends PrimitiveHandler + ? IsNever extends true + ? InferredValue + : InferredInput + : GenericInput extends EntityPropertyDefinition + ? EntityInputRawProperty + : never; + interface ToMapDataParserParams { coerce?: boolean; } @@ -34,13 +52,14 @@ interface ToMapDataParserParams { */ export function toMapDataParser< GenericInput extends ToMapDataParserInput, - GenericOutput extends OutputDataParser, + GenericOutputDataParser extends OutputDataParser = OutputDataParser, + GenericInputDataParser extends InputDataParser = InputDataParser, >( input: GenericInput, params?: ToMapDataParserParams, ): DDataParser.DataParser< - NoInfer, - unknown + NoInfer, + NoInfer >; export function toMapDataParser( @@ -68,29 +87,7 @@ export function toMapDataParser( ); } - const dataParser = (primitiveHandlerKind.has(input) - ? input.internal.dataParser.clone() - : input.internal.dataParser.clone()) as DDataParser.DataParsers; - - if ( - params?.coerce - && hasSomeKinds( - dataParser, - [ - DDataParser.stringKind, - DDataParser.numberKind, - DDataParser.bigIntKind, - DDataParser.bigIntKind, - DDataParser.booleanKind, - DDataParser.dateKind, - DDataParser.timeKind, - DDataParser.emptyKind, - DDataParser.nilKind, - ], - ) - ) { - (dataParser.definition.coerce as any) = true; - } + const dataParser = input.internal.dataParser.clone() as DDataParser.DataParsers; const valueContainer = DPattern.match(input) .when( @@ -114,7 +111,9 @@ export function toMapDataParser( .exhaustive(); return DDataParser.transform( - dataParser, + params?.coerce + ? DDataParser.coercer(dataParser) + : dataParser, (value) => ({ ...valueContainer, [keyWrappedValue]: value, diff --git a/tests/clean/toMapDataParser.test.ts b/tests/clean/toMapDataParser.test.ts index e4b6993db..c3d38f4c5 100644 --- a/tests/clean/toMapDataParser.test.ts +++ b/tests/clean/toMapDataParser.test.ts @@ -26,6 +26,12 @@ describe("toMapDataParser", () => { DClean.GetNewType, "strict" >; + + type CheckInput = ExpectType< + DDataParser.Input, + string, + "strict" + >; }); it("maps constraint handler to a data parser with constraint kind", () => { @@ -48,6 +54,12 @@ describe("toMapDataParser", () => { DClean.GetConstraint, "strict" >; + + type CheckInput = ExpectType< + DDataParser.Input, + string, + "strict" + >; }); it("maps constraints set handler to a data parser with constraint kinds", () => { @@ -75,6 +87,12 @@ describe("toMapDataParser", () => { DClean.GetConstraints, "strict" >; + + type CheckInput = ExpectType< + DDataParser.Input, + string, + "strict" + >; }); it("maps primitive handler to a data parser with wrapped value only", () => { @@ -95,6 +113,12 @@ describe("toMapDataParser", () => { DClean.String, "strict" >; + + type CheckInput = ExpectType< + DDataParser.Input, + string, + "strict" + >; }); it("supports coerce option on supported parsers", () => { @@ -146,4 +170,36 @@ describe("toMapDataParser", () => { tags: [], })).toStrictEqual(DEither.error(expect.any(Object))); }); + + it("infers entity property from wrapped new type handlers", () => { + const Label = DClean.createNewType("label", DPE.string().coerce()); + const Count = DClean.createNewType("count", DPE.number().coerce()); + const parser = DClean.toMapDataParser( + DClean.entityPropertyDefinitionTools.structure({ + label: Label, + count: DClean.entityPropertyDefinitionTools.nullable(Count), + tags: DClean.entityPropertyDefinitionTools.array(Label), + }), + ); + + type CheckInput = ExpectType< + DDataParser.Input, + { + readonly label: string | number | bigint | boolean | symbol | null | undefined; + readonly count: string | number | bigint | boolean | null; + readonly tags: readonly (string | number | bigint | boolean | symbol | null | undefined)[]; + }, + "strict" + >; + + type CheckOut = ExpectType< + DDataParser.Output, + { + readonly label: DClean.NewType<"label", string, never>; + readonly count: DClean.NewType<"count", number, never> | null; + readonly tags: readonly DClean.NewType<"label", string, never>[]; + }, + "strict" + >; + }); });