From e77bd9a54b565679798fc5b2b2e9742a0d014a17 Mon Sep 17 00:00:00 2001 From: Kirill Zaytsev Date: Mon, 9 Mar 2026 21:05:08 +0400 Subject: [PATCH 01/15] feat!: Object shape refinements were made async-first BREAKING CHANGE: shape.refine(...) was changed to the async-first object-level API. Use shape.refine.sync(...) for explicitly sync-safe object rules. BREAKING CHANGE: ObjectShapeRefinement now describes the async-first refinement contract. Use ObjectShapeRefinementSync for the explicit sync-only type name. BREAKING CHANGE: validate.sync(...), matches.sync(...), and shape.check(...) now throw when they encounter async object-level shape refinements. --- README.md | 4 +- docs/en/01-shape-api.md | 38 ++++-- docs/en/02-metadata-and-introspection.md | 3 +- docs/en/05-public-api.md | 2 + docs/en/07-ai-reference.md | 5 +- docs/ru/01-shape-api.md | 38 ++++-- docs/ru/02-metadata-and-introspection.md | 3 +- docs/ru/05-public-api.md | 2 + docs/ru/07-ai-reference.md | 5 +- docs/ru/README.md | 4 +- src/combinators.ts | 124 ++++++++++++++---- src/index.ts | 10 ++ tests/combinators.test-d.ts | 78 ++++++++++- tests/combinators.test.ts | 157 ++++++++++++++++++++++- tests/metadata.test-d.ts | 4 +- tests/metadata.test.ts | 1 + types/index.d.ts | 99 +++++++++++--- 17 files changed, 505 insertions(+), 72 deletions(-) diff --git a/README.md b/README.md index b16c9ea..7af0a8b 100644 --- a/README.md +++ b/README.md @@ -204,7 +204,7 @@ if (ok) { ## Shape API -`shape(...)` is the reusable object-shape API. It validates nested record-like objects and exposes small immutable helpers such as `strict()`, `pick()`, `omit()`, `partial()`, `extend()`, `merge()`, `refine()`, and `fieldsMatch(...)`. +`shape(...)` is the reusable object-shape API. It validates nested record-like objects and exposes small immutable helpers such as `strict()`, `pick()`, `omit()`, `partial()`, `extend()`, `merge()`, async-first `refine()`, explicit `refine.sync()`, and `fieldsMatch(...)`. ```typescript import { @@ -275,6 +275,8 @@ In the current API this usually looks like: - typed validation through `validate(...)` or `validate.sync(...)`; - narrowing of the original sync variable through `matches.sync(...)`. +`validate(...)` is the default async-first validation entrypoint. `validate.sync(...)` and `matches.sync(...)` are specialized sync APIs and will throw if they encounter async validators or async object-level `shape(...).refine(...)` rules. + ## Violations `validate(...)` returns machine-readable `Violation[]`, and `collection(...)` can wrap that list into a small helper API for exact path lookups and tree traversal. diff --git a/docs/en/01-shape-api.md b/docs/en/01-shape-api.md index 0905269..968dd79 100644 --- a/docs/en/01-shape-api.md +++ b/docs/en/01-shape-api.md @@ -190,13 +190,36 @@ Shapes can express cross-field invariants without introducing a second schema la ### `refine(...)` -`refine(...)` adds a synchronous object-level rule that runs only after the base shape has already validated successfully as an object. +`refine(...)` adds an async-first object-level rule that runs only after the base shape has already validated successfully as an object. + +```typescript +const registration = shape({ + email: isString, +}).refine(async value => { + const taken = await users.has(value.email) + + return taken + ? [{ path: ['email'], code: 'user.email.taken' }] + : [] +}) +``` + +`refine(...)` is intentionally thin: + +- it accepts sync or async callbacks; +- it returns `[]`, `null`, or `undefined` when the rule passes; +- it returns one issue or an array of issues when it fails; +- `path` is relative to the current shape and defaults to `[]`; +- `value` is optional and defaults to the object value at that relative path; +- `code` stays machine-readable. + +If you need an explicitly sync-safe object rule for `validate.sync(...)`, use `refine.sync(...)`: ```typescript const registration = shape({ password: isString, confirmPassword: isString, -}).refine(value => { +}).refine.sync(value => { return value.password === value.confirmPassword ? [] : [{ @@ -207,14 +230,7 @@ const registration = shape({ }) ``` -`refine(...)` is intentionally thin: - -- it is sync-only; -- it returns `[]`, `null`, or `undefined` when the rule passes; -- it returns one issue or an array of issues when it fails; -- `path` is relative to the current shape and defaults to `[]`; -- `value` is optional and defaults to the object value at that relative path; -- `code` stays machine-readable. +`validate.sync(...)`, `matches.sync(...)`, and `shape.check(...)` throw explicitly if they encounter an async `refine(...)` callback. Produced violations use: @@ -243,6 +259,8 @@ const registration = shape({ This shows up later in `describe(...)` under the shape `rules` array. +Rules registered through async-first `refine(...)` are marked with `async: true` in that descriptor array. + ### `fieldsMatch(...)` `fieldsMatch(...)` is a small helper for the common confirmation-field case. diff --git a/docs/en/02-metadata-and-introspection.md b/docs/en/02-metadata-and-introspection.md index 18a1a49..4c274c6 100644 --- a/docs/en/02-metadata-and-introspection.md +++ b/docs/en/02-metadata-and-introspection.md @@ -182,7 +182,8 @@ Instead, shapes keep lightweight rule descriptors in `rules`. Built-in examples: - `fieldsMatch(...)` produces a compact `fieldsMatch` rule descriptor; -- `refine(...)` can accept a custom compact rule descriptor object. +- `refine(...)` can accept a custom compact rule descriptor object; +- async-first `refine(...)` rules are marked with `async: true`. This keeps the public descriptor tree stable and serializable enough for tooling, while avoiding the impossible task of serializing arbitrary callbacks. diff --git a/docs/en/05-public-api.md b/docs/en/05-public-api.md index 05113f9..3d567c2 100644 --- a/docs/en/05-public-api.md +++ b/docs/en/05-public-api.md @@ -77,6 +77,8 @@ Practical meaning: - `ok` tells whether validation succeeded; - `validated` becomes strongly typed only in the success branch; - `violations` is empty on success and contains structured failures on error. +- `validate(...)` is the default async-first entrypoint; +- `validate.sync(...)` throws explicitly when it encounters async validators or async object-level `shape(...).refine(...)` rules. ## Predicates Subpath diff --git a/docs/en/07-ai-reference.md b/docs/en/07-ai-reference.md index 862811e..8a02c0f 100644 --- a/docs/en/07-ai-reference.md +++ b/docs/en/07-ai-reference.md @@ -53,7 +53,8 @@ Important consequences: - `validate(...)` narrows the `validated` tuple item in the success branch; - `validate(...)` does not narrow the original input variable; - `matches.sync(...)` is the API that narrows the original variable; -- `violations` is always empty on success. +- `violations` is always empty on success; +- `validate(...)` is async-first, while `validate.sync(...)` and `matches.sync(...)` are specialized sync APIs. ## Wrapper Semantics @@ -90,6 +91,8 @@ Important rule: - structural derivations intentionally drop object-level rules; - mode switches intentionally keep object-level rules. +- `.refine(...)` is async-first and may return a promise; +- `.refine.sync(...)` is the explicitly sync-safe object-level rule API. ## Violations Contract diff --git a/docs/ru/01-shape-api.md b/docs/ru/01-shape-api.md index d6fc4cb..b618265 100644 --- a/docs/ru/01-shape-api.md +++ b/docs/ru/01-shape-api.md @@ -190,13 +190,36 @@ Shapes позволяют выражать cross-field invariants без вне ### `refine(...)` -`refine(...)` добавляет синхронное object-level rule, которое запускается только после того, как базовая shape уже успешно провалидировалась как объект. +`refine(...)` добавляет async-first object-level rule, которое запускается только после того, как базовая shape уже успешно провалидировалась как объект. + +```typescript +const registration = shape({ + email: isString, +}).refine(async value => { + const taken = await users.has(value.email) + + return taken + ? [{ path: ['email'], code: 'user.email.taken' }] + : [] +}) +``` + +`refine(...)` специально остаётся тонким: + +- он принимает sync- и async-callbacks; +- при успехе возвращает `[]`, `null` или `undefined`; +- при ошибке возвращает один issue или массив issue; +- `path` задаётся относительно текущей shape и по умолчанию равен `[]`; +- `value` необязателен и по умолчанию берётся из значения объекта по этому относительному пути; +- `code` остаётся machine-readable. + +Если нужен явно sync-safe object rule для `validate.sync(...)`, используйте `refine.sync(...)`: ```typescript const registration = shape({ password: isString, confirmPassword: isString, -}).refine(value => { +}).refine.sync(value => { return value.password === value.confirmPassword ? [] : [{ @@ -207,14 +230,7 @@ const registration = shape({ }) ``` -`refine(...)` специально остаётся тонким: - -- он только sync; -- при успехе возвращает `[]`, `null` или `undefined`; -- при ошибке возвращает один issue или массив issue; -- `path` задаётся относительно текущей shape и по умолчанию равен `[]`; -- `value` необязателен и по умолчанию берётся из значения объекта по этому относительному пути; -- `code` остаётся machine-readable. +`validate.sync(...)`, `matches.sync(...)` и `shape.check(...)` выбрасывают явную ошибку, если встречают async-callback в `refine(...)`. Сгенерированные violations используют: @@ -243,6 +259,8 @@ const registration = shape({ Позже это попадает в `describe(...)` в массив `rules`. +Rules, зарегистрированные через async-first `refine(...)`, получают в этом массиве признак `async: true`. + ### `fieldsMatch(...)` `fieldsMatch(...)` — небольшой helper для частого случая с полем подтверждения. diff --git a/docs/ru/02-metadata-and-introspection.md b/docs/ru/02-metadata-and-introspection.md index 9ec3033..62f5314 100644 --- a/docs/ru/02-metadata-and-introspection.md +++ b/docs/ru/02-metadata-and-introspection.md @@ -182,7 +182,8 @@ const descriptor = describe(shape({ Built-in примеры: - `fieldsMatch(...)` создаёт компактный `fieldsMatch` rule descriptor; -- `refine(...)` может принимать собственный компактный rule descriptor object. +- `refine(...)` может принимать собственный компактный rule descriptor object; +- async-first rules из `refine(...)` помечаются через `async: true`. Так публичное descriptor tree остаётся стабильным и достаточно сериализуемым для tooling, при этом библиотека не пытается сериализовать произвольные callbacks. diff --git a/docs/ru/05-public-api.md b/docs/ru/05-public-api.md index 0f1ae08..c0ba4f8 100644 --- a/docs/ru/05-public-api.md +++ b/docs/ru/05-public-api.md @@ -77,6 +77,8 @@ type ValidationTuple = - `ok` показывает, прошла ли валидация; - `validated` становится строго типизированным только в успешной ветке; - `violations` пуст при успехе и содержит структурированные ошибки при неуспехе. +- `validate(...)` остаётся основным async-first entrypoint; +- `validate.sync(...)` явно выбрасывает ошибку при async validators и async object-level rules из `shape(...).refine(...)`. ## Subpath predicates diff --git a/docs/ru/07-ai-reference.md b/docs/ru/07-ai-reference.md index 283a48c..e213f43 100644 --- a/docs/ru/07-ai-reference.md +++ b/docs/ru/07-ai-reference.md @@ -53,7 +53,8 @@ type ValidationTuple = - `validate(...)` сужает `validated` в успешной ветке; - `validate(...)` не сужает исходную входную переменную; - `matches.sync(...)` — это API для сужения исходной переменной; -- `violations` при успехе всегда пуст. +- `violations` при успехе всегда пуст; +- `validate(...)` — async-first API, а `validate.sync(...)` и `matches.sync(...)` остаются специализированными sync API. ## Семантика wrappers @@ -90,6 +91,8 @@ type ValidationTuple = - structural derivations намеренно сбрасывают object-level rules; - mode switches намеренно сохраняют object-level rules. +- `.refine(...)` — async-first и может возвращать promise; +- `.refine.sync(...)` — явно sync-safe API для object-level rules. ## Контракт violations diff --git a/docs/ru/README.md b/docs/ru/README.md index c8f770b..903a0be 100644 --- a/docs/ru/README.md +++ b/docs/ru/README.md @@ -207,7 +207,7 @@ if (ok) { ## API объектных схем -`shape(...)` — это переиспользуемый API объектных схем. Он валидирует вложенные record-like objects и предоставляет небольшой неизменяемый API вроде `strict()`, `pick()`, `omit()`, `partial()`, `extend()`, `merge()`, `refine()` и `fieldsMatch(...)`. +`shape(...)` — это переиспользуемый API объектных схем. Он валидирует вложенные record-like objects и предоставляет небольшой неизменяемый API вроде `strict()`, `pick()`, `omit()`, `partial()`, `extend()`, `merge()`, async-first `refine()`, явного `refine.sync()` и `fieldsMatch(...)`. ```typescript import { @@ -278,6 +278,8 @@ const node = describe(registration) - типизированная валидация через `validate(...)` или `validate.sync(...)`; - narrowing исходной переменной в sync-коде через `matches.sync(...)`. +`validate(...)` — основной async-first entrypoint. `validate.sync(...)` и `matches.sync(...)` остаются специализированными sync API и выбрасывают ошибку, если встречают async validators или async object-level rules из `shape(...).refine(...)`. + ## Нарушения `validate(...)` возвращает машиночитаемый список `Violation[]`, а `collection(...)` может обернуть его в небольшой helper API для точного поиска по path и обхода дерева. diff --git a/src/combinators.ts b/src/combinators.ts index a7444df..47b6504 100644 --- a/src/combinators.ts +++ b/src/combinators.ts @@ -1,4 +1,5 @@ import type { + AsyncObjectShapeRuleDescriptor, Constraint, DiscriminatedUnionValidator, EachValidator, @@ -6,14 +7,17 @@ import type { GenericObjectShapeRuleDescriptor, InferConstraints, MaybeMany, + MaybePromise, NullableValidator, NullishValidator, + ObjectShape, ObjectShapeFieldSelector, ObjectShapeRefinement, + ObjectShapeRefineMethod, ObjectShapeRefinementIssue, + ObjectShapeRefinementSync, ObjectShapeRuleDescriptor, MergeObjectDescriptors, - ObjectShape, OptionalValidator, PartialObjectDescriptor, RecordValidator, @@ -52,10 +56,15 @@ export type InferShape = { } export type { - ObjectShapeFieldSelector, + AsyncObjectShapeRuleDescriptor, ObjectShape, + ObjectShapeFieldSelector, ObjectShapeRefinement, + ObjectShapeRefineMethod, + ObjectShapeRefineMethodSync, ObjectShapeRefinementIssue, + ObjectShapeRefinementSync, + ObjectShapeSyncRefinement, UnknownKeysMode, } from '~types' @@ -90,6 +99,11 @@ type ShapeRefinement< I extends ObjectShapeRefinementIssue = ObjectShapeRefinementIssue, > = ObjectShapeRefinement +type SyncShapeRefinement< + T, + I extends ObjectShapeRefinementIssue = ObjectShapeRefinementIssue, +> = ObjectShapeRefinementSync + const describeConstraintMap = >>( constraints: T ): Record> => { @@ -113,6 +127,13 @@ const normalizeRuleDescriptor = >( + descriptor: R +): Omit & AsyncObjectShapeRuleDescriptor => ({ + ...normalizeRuleDescriptor(descriptor), + async: true, +}) + const passthrough = , Accepted>( kind: 'optional' | 'nullable' | 'nullish', accepts: (value: unknown) => value is Accepted, @@ -212,16 +233,14 @@ const toPath = (selector: ObjectShapeFieldSelector): PropertyKey[] => Array.isAr ? [...selector] : [selector] -const collectShapeRefinementViolations = < +const toShapeRefinementViolations = < D extends ShapeDescriptor, RI extends ObjectShapeRefinementIssue, >( value: InferShape, path: PropertyKey[], - refinements: readonly ShapeRefinement, RI>[] -) => refinements.flatMap(refinement => { - const result = refinement(value) - + result: MaybeMany | null | undefined +) => { if (result === null || result === undefined) { return [] } @@ -242,8 +261,45 @@ const collectShapeRefinementViolations = < }, } }) +} + +const collectShapeRefinementViolations = < + D extends ShapeDescriptor, + RI extends ObjectShapeRefinementIssue, +>( + value: InferShape, + path: PropertyKey[], + refinements: readonly ShapeRefinement, RI>[] +) => refinements.flatMap(refinement => { + const result = refinement(value) + + if (result instanceof Promise) { + throw new Error('Found asynchronous object-level shape refinement') + } + + return toShapeRefinementViolations(value, path, result) }) +const collectAsyncShapeRefinementViolations = < + D extends ShapeDescriptor, + RI extends ObjectShapeRefinementIssue, +>( + value: InferShape, + path: PropertyKey[], + refinements: readonly ShapeRefinement, RI>[] +): MaybePromise => { + const results = refinements.map(refinement => refinement(value)) + + if (results.some(result => result instanceof Promise)) { + return Promise.all(results.map(result => Promise.resolve(result))).then(resolved => { + return resolved.flatMap(result => toShapeRefinementViolations(value, path, result)) + }) + } + + return (results as Array | null | undefined>) + .flatMap(result => toShapeRefinementViolations(value, path, result)) +} + const createObjectShape = < const D extends ShapeDescriptor, const M extends UnknownKeysMode, @@ -256,6 +312,35 @@ const createObjectShape = < rules: R ): ObjectShape => { const keys = keysOf(descriptor) + const addAsyncRefinement = < + const I extends ObjectShapeRefinementIssue = ObjectShapeRefinementIssue, + const RD extends GenericObjectShapeRuleDescriptor = GenericObjectShapeRuleDescriptor<'refine'> + >( + refinement: ShapeRefinement, I>, + ruleDescriptor: RD = { kind: 'refine' } as RD + ) => createObjectShape & AsyncObjectShapeRuleDescriptor], RI | I>( + descriptor, + unknownKeys, + [...refinements, refinement] as readonly ShapeRefinement, RI | I>[], + [...rules, normalizeAsyncRuleDescriptor(ruleDescriptor)] as [...R, Omit & AsyncObjectShapeRuleDescriptor] + ) + + const addSyncRefinement = < + const I extends ObjectShapeRefinementIssue = ObjectShapeRefinementIssue, + const RD extends GenericObjectShapeRuleDescriptor = GenericObjectShapeRuleDescriptor<'refine'> + >( + refinement: SyncShapeRefinement, I>, + ruleDescriptor: RD = { kind: 'refine' } as RD + ) => createObjectShape( + descriptor, + unknownKeys, + [...refinements, refinement] as readonly ShapeRefinement, RI | I>[], + [...rules, normalizeRuleDescriptor(ruleDescriptor)] as [...R, RD] + ) + + const refine = Object.assign(addAsyncRefinement, { + sync: addSyncRefinement, + }) as ObjectShapeRefineMethod return attachConstraintDescriptor({ descriptor, @@ -294,7 +379,7 @@ const createObjectShape = < return structuralViolations.length > 0 ? structuralViolations - : collectShapeRefinementViolations(value as InferShape, path, refinements) + : Promise.resolve(collectAsyncShapeRefinementViolations(value as InferShape, path, refinements)) }) as Validation] } @@ -302,22 +387,13 @@ const createObjectShape = < return structuralViolations.length > 0 ? validations - : [collectShapeRefinementViolations(value as InferShape, path, refinements) as Validation] - }, - refine< - const I extends ObjectShapeRefinementIssue = ObjectShapeRefinementIssue, - const RD extends GenericObjectShapeRuleDescriptor = GenericObjectShapeRuleDescriptor<'refine'> - >( - refinement: ShapeRefinement, I>, - ruleDescriptor: RD = { kind: 'refine' } as RD - ) { - return createObjectShape( - descriptor, - unknownKeys, - [...refinements, refinement] as readonly ShapeRefinement, RI | I>[], - [...rules, normalizeRuleDescriptor(ruleDescriptor)] as [...R, RD] - ) + : [( + validate.sync + ? collectShapeRefinementViolations(value as InferShape, path, refinements) + : collectAsyncShapeRefinementViolations(value as InferShape, path, refinements) + ) as Validation] }, + refine, fieldsMatch(selectedKeys: K) { const [left, right] = selectedKeys const leftPath = toPath(left) @@ -343,7 +419,7 @@ const createObjectShape = < code: 'shape.fields.mismatch', args: [selectedKeys], }] - }] as readonly ShapeRefinement< + }] as readonly SyncShapeRefinement< InferShape, RI | ObjectShapeRefinementIssue<'shape.fields.mismatch'> >[], diff --git a/src/index.ts b/src/index.ts index 28600ee..4496f3c 100644 --- a/src/index.ts +++ b/src/index.ts @@ -23,6 +23,7 @@ export type { AllOfConstraintDescriptor, AssertionDescriptor, AssertionDescriptorConstraint, + AsyncObjectShapeRuleDescriptor, Constraint, ConstraintDescriptor, ConstraintDescriptorBase, @@ -50,10 +51,15 @@ export type { KnownViolationCode, KnownViolationSubject, ObjectShapeFieldSelector, + ObjectShapeAsyncRefinement, ObjectShapeRefinement, + ObjectShapeRefineMethod, + ObjectShapeRefineMethodSync, ObjectShapeRefinementIssue, + ObjectShapeRefinementSync, ObjectShapeRuleDescriptor, ObjectShapeRuleDescriptorBase, + ObjectShapeSyncRefinement, OptionalValidator, RecordValidator, RecordConstraintDescriptor, @@ -123,6 +129,8 @@ const collectViolations = async ( return settle(value, path, validations) } +collectViolations.sync = false as const + const collectViolationsSync = ( value: unknown, constraints: MaybeMany, @@ -149,6 +157,8 @@ const collectViolationsSync = ( return flatten(violations) as Violation[] } +collectViolationsSync.sync = true as const + function toResult(value: unknown, violations: V[]): ValidationTuple { return violations.length === 0 ? [true, value as T, []] diff --git a/tests/combinators.test-d.ts b/tests/combinators.test-d.ts index 6101263..bd6765c 100644 --- a/tests/combinators.test-d.ts +++ b/tests/combinators.test-d.ts @@ -1,4 +1,9 @@ import type { + AsyncObjectShapeRuleDescriptor, + ObjectShapeAsyncRefinement, + ObjectShapeRefinement, + ObjectShapeRefinementSync, + ObjectShapeSyncRefinement, ValidationTuple, Violation, } from '@/index' @@ -24,6 +29,7 @@ import { isNumber, isString, matches, + describe as describeConstraint, validate, } from '@/index' @@ -210,7 +216,18 @@ describe('combinator types', () => { const registration = shape({ password: isString, confirmPassword: isString, - }).refine(value => { + }).refine(async value => { + assertType<{ + password: string; + confirmPassword: string; + }>(value) + + return [] + }) + const syncRegistration = shape({ + password: isString, + confirmPassword: isString, + }).refine.sync(value => { assertType<{ password: string; confirmPassword: string; @@ -221,13 +238,70 @@ describe('combinator types', () => { const confirmedRegistration = registration.fieldsMatch(['password', 'confirmPassword']) + assertType>>(validate({ + password: 'secret', + confirmPassword: 'secret', + }, registration)) + + assertType>>(validate({ + password: 'secret', + confirmPassword: 'secret', + }, confirmedRegistration)) + assertType>(validate.sync({ password: 'secret', confirmPassword: 'secret', - }, confirmedRegistration)) + }, syncRegistration)) + }) + + test('async refine descriptors are marked in shape metadata types', () => { + const schema = shape({ + password: isString, + confirmPassword: isString, + }).refine(async () => [], { + kind: 'passwordConfirmation', + }) + + const descriptor = describeConstraint(schema) + + if (descriptor.kind === 'shape') { + assertType]>(descriptor.rules) + } + }) + + test('object-level refinement type aliases expose async-first and sync-safe names', () => { + assertType>(async value => { + assertType(value.password) + + return [] + }) + + assertType>(value => { + assertType(value.password) + + return [] + }) + + assertType>(async value => { + assertType(value.password) + + return [] + }) + + assertType>(value => { + assertType(value.password) + + return [] + }) }) test('fieldsMatch also accepts nested path selectors without changing inferred shape types', () => { diff --git a/tests/combinators.test.ts b/tests/combinators.test.ts index be6bd0a..df221ad 100644 --- a/tests/combinators.test.ts +++ b/tests/combinators.test.ts @@ -1,5 +1,6 @@ import type { Assertion, + Validate, ViolationSubject, } from '~types' @@ -755,7 +756,7 @@ describe('shape object api', () => { }]]) }) - test('refine adds sync object-level rules that run after structural validation', async () => { + test('refine keeps sync callbacks valid in the async-first API', async () => { const registration = shape({ password: isString, confirmPassword: isString, @@ -790,6 +791,72 @@ describe('shape object api', () => { }]]) }) + test('refine awaits async object-level rules after structural validation', async () => { + const registration = shape({ + email: isString, + }).refine(async value => { + await Promise.resolve() + + return value.email === 'taken@example.com' + ? [{ + path: ['email'], + code: 'user.email.taken', + }] + : [] + }) + + expect(await validate({ + email: 'free@example.com', + }, registration)).toEqual([true, { + email: 'free@example.com', + }, []]) + + expect(await validate({ + email: 'taken@example.com', + }, registration)).toEqual([false, { + email: 'taken@example.com', + }, [{ + value: 'taken@example.com', + path: ['email'], + violates: validatorSubject('shape', 'user.email.taken'), + }]]) + }) + + test('refine.sync keeps the explicit sync path safe for validate.sync', () => { + const registration = shape({ + password: isString, + confirmPassword: isString, + }).refine.sync(value => { + return value.password === value.confirmPassword + ? [] + : [{ + path: ['confirmPassword'], + code: 'shape.fields.mismatch', + args: [['password', 'confirmPassword']], + }] + }) + + expect(validate.sync({ + password: 'secret', + confirmPassword: 'secret', + }, registration)).toEqual([true, { + password: 'secret', + confirmPassword: 'secret', + }, []]) + + expect(validate.sync({ + password: 'secret', + confirmPassword: 'different', + }, registration)).toEqual([false, { + password: 'secret', + confirmPassword: 'different', + }, [{ + value: 'different', + path: ['confirmPassword'], + violates: validatorSubject('shape', 'shape.fields.mismatch', [['password', 'confirmPassword']]), + }]]) + }) + test('refine can target nested paths when matching fields from different object levels', () => { const registration = shape({ password: isString, @@ -910,6 +977,94 @@ describe('shape object api', () => { expect(runs).toBe(0) }) + test('refine combines async field validation and async object-level validation in one flow', async () => { + const asyncDomain = createAsyncAssertion('asyncDomain', async value => { + return value === 'blocked@example.com' + ? { + value, + violates: assertionSubject('asyncDomain', 'user.email.blocked'), + } + : null + }) + let runs = 0 + const schema = shape({ + email: [isString, asyncDomain], + confirmEmail: isString, + }).refine(async value => { + runs += 1 + await Promise.resolve() + + return value.email === value.confirmEmail + ? [] + : [{ + path: ['confirmEmail'], + code: 'shape.fields.mismatch', + args: [['email', 'confirmEmail']], + }] + }) + + expect(await validate({ + email: 'blocked@example.com', + confirmEmail: 'blocked@example.com', + }, schema)).toEqual([false, { + email: 'blocked@example.com', + confirmEmail: 'blocked@example.com', + }, [{ + value: 'blocked@example.com', + path: ['email'], + violates: assertionSubject('asyncDomain', 'user.email.blocked'), + }]]) + expect(runs).toBe(0) + + expect(await validate({ + email: 'ok@example.com', + confirmEmail: 'other@example.com', + }, schema)).toEqual([false, { + email: 'ok@example.com', + confirmEmail: 'other@example.com', + }, [{ + value: 'other@example.com', + path: ['confirmEmail'], + violates: validatorSubject('shape', 'shape.fields.mismatch', [['email', 'confirmEmail']]), + }]]) + expect(runs).toBe(1) + }) + + test('validate.sync and matches.sync fail explicitly on async refine callbacks', () => { + const registration = shape({ + email: isString, + }).refine(async () => { + return [{ + code: 'shape.async.unreachable', + }] + }) + + expect(() => validate.sync({ + email: 'taken@example.com', + }, registration)).toThrow('Found asynchronous object-level shape refinement') + expect(() => matches.sync({ + email: 'taken@example.com', + }, registration)).toThrow('Found asynchronous object-level shape refinement') + expect(() => registration.check({ + email: 'taken@example.com', + })).toThrow('Found asynchronous object-level shape refinement') + }) + + test('shape.run keeps async-first refine fallback for custom runners without a sync marker', () => { + const registration = shape({}).refine(() => []) + const bareRunner = ((value: unknown) => { + return value === null + ? [{ + value, + path: [], + violates: validatorSubject('shape', 'type.record'), + }] + : [] + }) as unknown as Validate + + expect(registration.run(bareRunner, {}, [])).toEqual([[]]) + }) + test('fieldsMatch is a thin helper over refine for the common confirmation-field case', () => { const registration = shape({ password: isString, diff --git a/tests/metadata.test-d.ts b/tests/metadata.test-d.ts index c4bac99..10855c6 100644 --- a/tests/metadata.test-d.ts +++ b/tests/metadata.test-d.ts @@ -1,8 +1,8 @@ import type { + AsyncObjectShapeRuleDescriptor, AssertionDescriptor, ConstraintDescriptor, FieldsMatchObjectShapeRuleDescriptor, - GenericObjectShapeRuleDescriptor, ValidationTuple, ViolationCode, } from '@/index' @@ -82,7 +82,7 @@ describe('metadata and introspection types', () => { assertType<'strict' | 'passthrough'>(descriptor.unknownKeys) assertType, - GenericObjectShapeRuleDescriptor<'passwordConfirmation'>, + AsyncObjectShapeRuleDescriptor<'passwordConfirmation'>, ]>(descriptor.rules) } }) diff --git a/tests/metadata.test.ts b/tests/metadata.test.ts index 6aaecb0..b58bda4 100644 --- a/tests/metadata.test.ts +++ b/tests/metadata.test.ts @@ -122,6 +122,7 @@ suite('describe', () => { { kind: 'fieldsMatch', selectors: ['password', 'confirmPassword'] }, { kind: 'passwordConfirmation', + async: true, metadata: { fields: ['password', 'confirmPassword'], }, diff --git a/types/index.d.ts b/types/index.d.ts index 70c4db1..b6d3dee 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -317,11 +317,18 @@ export interface ObjectShapeRuleDescriptorBase { readonly metadata?: ConstraintMetadata } -/** Generic compact descriptor for sync object-level rules added via `.refine(...)`. */ +/** Generic compact descriptor for sync-safe object-level rules. */ export type GenericObjectShapeRuleDescriptor< K extends string = 'refine', > = ObjectShapeRuleDescriptorBase +/** Compact descriptor for object-level rules registered through the async-first `.refine(...)` API. */ +export interface AsyncObjectShapeRuleDescriptor< + K extends string = 'refine', +> extends ObjectShapeRuleDescriptorBase { + readonly async: true +} + /** Descriptor for the built-in `.fieldsMatch(...)` helper. */ export interface FieldsMatchObjectShapeRuleDescriptor< Left extends ObjectShapeFieldSelector = ObjectShapeFieldSelector, @@ -333,6 +340,7 @@ export interface FieldsMatchObjectShapeRuleDescriptor< /** Machine-readable summary of object-level rules registered on a shape. */ export type ObjectShapeRuleDescriptor = | GenericObjectShapeRuleDescriptor + | AsyncObjectShapeRuleDescriptor | FieldsMatchObjectShapeRuleDescriptor /** Descriptor for `shape(...)`. */ @@ -464,18 +472,22 @@ export type InferConstraints = : InferConstraint /** Internal async runner signature used by composed validators. */ -export type Validate = ( +export type Validate = (( value: unknown, constraints: MaybeMany, path?: PropertyKey[] -) => Promise +) => Promise) & { + readonly sync?: false +} /** Internal sync runner signature used by composed validators. */ -export type ValidateSync = ( +export type ValidateSync = (( value: unknown, constraints: MaybeMany, path?: PropertyKey[] -) => Violation[] +) => Violation[]) & { + readonly sync: true +} /** Internal union of async and sync runner signatures. */ export type ValidateLike = Validate | ValidateSync @@ -512,14 +524,76 @@ export type ObjectShapeRefinementIssue< value?: unknown; } -/** Sync object-level rule that runs after the base shape has validated successfully. */ -export type ObjectShapeRefinement< +/** Sync-safe object-level rule that runs after the base shape has validated successfully. */ +export type ObjectShapeRefinementSync< T, I extends ObjectShapeRefinementIssue = ObjectShapeRefinementIssue, > = ( value: T ) => MaybeMany | null | undefined +/** Async-first object-level rule that runs after the base shape has validated successfully. */ +export type ObjectShapeRefinement< + T, + I extends ObjectShapeRefinementIssue = ObjectShapeRefinementIssue, +> = ( + value: T +) => MaybePromise | null | undefined> + +/** Backward-compatible alias for the async-first object-level rule type. */ +export type ObjectShapeAsyncRefinement< + T, + I extends ObjectShapeRefinementIssue = ObjectShapeRefinementIssue, +> = ObjectShapeRefinement + +/** Backward-compatible alias for the sync-safe object-level rule type. */ +export type ObjectShapeSyncRefinement< + T, + I extends ObjectShapeRefinementIssue = ObjectShapeRefinementIssue, +> = ObjectShapeRefinementSync + +type AsyncObjectShapeRuleDescriptorOf> = + Omit & AsyncObjectShapeRuleDescriptor + +/** Explicitly sync-safe callable helper exposed as `shape(...).refine.sync(...)`. */ +export interface ObjectShapeRefineMethodSync< + D extends ObjectDescriptor = ObjectDescriptor, + M extends UnknownKeysMode = UnknownKeysMode, + R extends readonly ObjectShapeRuleDescriptor[] = readonly ObjectShapeRuleDescriptor[], + RI extends ObjectShapeRefinementIssue = never, +> { + ( + refinement: ObjectShapeRefinementSync, I> + ): ObjectShape], RI | I>; + < + const I extends ObjectShapeRefinementIssue = ObjectShapeRefinementIssue, + const RD extends GenericObjectShapeRuleDescriptor = GenericObjectShapeRuleDescriptor<'refine'> + >( + refinement: ObjectShapeRefinementSync, I>, + descriptor: RD + ): ObjectShape; +} + +/** Async-first callable shape helper exposed as `shape(...).refine(...)`. */ +export interface ObjectShapeRefineMethod< + D extends ObjectDescriptor = ObjectDescriptor, + M extends UnknownKeysMode = UnknownKeysMode, + R extends readonly ObjectShapeRuleDescriptor[] = readonly ObjectShapeRuleDescriptor[], + RI extends ObjectShapeRefinementIssue = never, +> { + ( + refinement: ObjectShapeRefinement, I> + ): ObjectShape], RI | I>; + < + const I extends ObjectShapeRefinementIssue = ObjectShapeRefinementIssue, + const RD extends GenericObjectShapeRuleDescriptor = GenericObjectShapeRuleDescriptor<'refine'> + >( + refinement: ObjectShapeRefinement, I>, + descriptor: RD + ): ObjectShape], RI | I>; + sync: ObjectShapeRefineMethodSync; +} + type KnownCodeViolation = Violation> type ShapeRefinementIssueSubject = @@ -676,16 +750,7 @@ export interface ObjectShape< > extends Validator> { readonly descriptor: D; readonly unknownKeys: M; - refine( - refinement: ObjectShapeRefinement, I> - ): ObjectShape], RI | I>; - refine< - const I extends ObjectShapeRefinementIssue = ObjectShapeRefinementIssue, - const RD extends GenericObjectShapeRuleDescriptor = GenericObjectShapeRuleDescriptor<'refine'> - >( - refinement: ObjectShapeRefinement, I>, - descriptor: RD - ): ObjectShape; + readonly refine: ObjectShapeRefineMethod; fieldsMatch( keys: K ): ObjectShape], RI | ObjectShapeRefinementIssue<'shape.fields.mismatch'>>; From 43fc3c5078c43beec70dfff5c84eb46dde6680bc Mon Sep 17 00:00:00 2001 From: Kirill Zaytsev Date: Mon, 9 Mar 2026 21:28:40 +0400 Subject: [PATCH 02/15] refactor(types)!: Public type names were simplified Canonical public type names were aligned with the runtime API vocabulary across declarations, exports, tests, and docs. Renamed public types include ValidationResult, InferViolations, DescribeConstraints, ShapeDescriptor, ShapeRefinement, SyncShapeRefinement, and ShapeRefinementViolationInput. BREAKING CHANGE: Exported public type names were renamed without compatibility aliases. --- docs/en/03-violations.md | 2 +- docs/en/05-public-api.md | 2 +- docs/en/07-ai-reference.md | 2 +- docs/en/08-violation-code-types.md | 4 +- docs/ru/03-violations.md | 2 +- docs/ru/05-public-api.md | 2 +- docs/ru/07-ai-reference.md | 2 +- docs/ru/08-violation-code-types.md | 4 +- src/combinators.ts | 93 ++++++------- src/index.ts | 51 +++---- src/metadata.ts | 8 +- tests/combinators.test-d.ts | 62 ++++----- tests/metadata.test-d.ts | 4 +- tests/validate.tuple.test-d.ts | 4 +- tests/violation-codes.test-d.ts | 4 +- types/index.d.ts | 217 ++++++++++++++--------------- 16 files changed, 214 insertions(+), 249 deletions(-) diff --git a/docs/en/03-violations.md b/docs/en/03-violations.md index ea9dd77..223c761 100644 --- a/docs/en/03-violations.md +++ b/docs/en/03-violations.md @@ -120,7 +120,7 @@ This is one of the reasons the library can stay adapter-friendly without stringi `validate(...)` and `validate.sync(...)` return: ```typescript -type ValidationTuple = +type ValidationResult = | [ok: true, validated: T, violations: []] | [ok: false, validated: unknown, violations: Violation[]] ``` diff --git a/docs/en/05-public-api.md b/docs/en/05-public-api.md index 3d567c2..5852e29 100644 --- a/docs/en/05-public-api.md +++ b/docs/en/05-public-api.md @@ -67,7 +67,7 @@ These are the main utilities for post-processing machine-readable validation fai `validate(...)` and `validate.sync(...)` return: ```typescript -type ValidationTuple = +type ValidationResult = | [ok: true, validated: T, violations: []] | [ok: false, validated: unknown, violations: Violation[]] ``` diff --git a/docs/en/07-ai-reference.md b/docs/en/07-ai-reference.md index 8a02c0f..61c04b5 100644 --- a/docs/en/07-ai-reference.md +++ b/docs/en/07-ai-reference.md @@ -43,7 +43,7 @@ Use `@modulify/validator/json-schema` for: ## Validation Result Contract ```typescript -type ValidationTuple = +type ValidationResult = | [ok: true, validated: T, violations: []] | [ok: false, validated: unknown, violations: Violation[]] ``` diff --git a/docs/en/08-violation-code-types.md b/docs/en/08-violation-code-types.md index eb6dfe8..4359a94 100644 --- a/docs/en/08-violation-code-types.md +++ b/docs/en/08-violation-code-types.md @@ -158,7 +158,7 @@ This part does not depend on the global union extraction. The literal is preserv The same idea applies to object-level refinement issues. ```typescript -import type { ObjectShapeRefinementIssue } from '@modulify/validator' +import type { ShapeRefinementViolationInput } from '@modulify/validator' import { isEmail, isString, @@ -180,7 +180,7 @@ const signUpForm = shape({ path: ['confirmation', 'password'], code: 'profile.password.mismatch', args: [], - }] satisfies ObjectShapeRefinementIssue<'profile.password.mismatch'> + }] satisfies ShapeRefinementViolationInput<'profile.password.mismatch'> }) ``` diff --git a/docs/ru/03-violations.md b/docs/ru/03-violations.md index 0acf809..1a4a2d4 100644 --- a/docs/ru/03-violations.md +++ b/docs/ru/03-violations.md @@ -120,7 +120,7 @@ const violation: Violation = { `validate(...)` и `validate.sync(...)` возвращают: ```typescript -type ValidationTuple = +type ValidationResult = | [ok: true, validated: T, violations: []] | [ok: false, validated: unknown, violations: Violation[]] ``` diff --git a/docs/ru/05-public-api.md b/docs/ru/05-public-api.md index c0ba4f8..4ddc3e3 100644 --- a/docs/ru/05-public-api.md +++ b/docs/ru/05-public-api.md @@ -67,7 +67,7 @@ Root package также содержит: `validate(...)` и `validate.sync(...)` возвращают: ```typescript -type ValidationTuple = +type ValidationResult = | [ok: true, validated: T, violations: []] | [ok: false, validated: unknown, violations: Violation[]] ``` diff --git a/docs/ru/07-ai-reference.md b/docs/ru/07-ai-reference.md index e213f43..46ed0b5 100644 --- a/docs/ru/07-ai-reference.md +++ b/docs/ru/07-ai-reference.md @@ -43,7 +43,7 @@ Violations — это прежде всего структурированные ## Контракт результата валидации ```typescript -type ValidationTuple = +type ValidationResult = | [ok: true, validated: T, violations: []] | [ok: false, validated: unknown, violations: Violation[]] ``` diff --git a/docs/ru/08-violation-code-types.md b/docs/ru/08-violation-code-types.md index 5d1235d..06d41f0 100644 --- a/docs/ru/08-violation-code-types.md +++ b/docs/ru/08-violation-code-types.md @@ -158,7 +158,7 @@ const isAvailableEmail = assert( Та же идея работает и для object-level refinement issues. ```typescript -import type { ObjectShapeRefinementIssue } from '@modulify/validator' +import type { ShapeRefinementViolationInput } from '@modulify/validator' import { isEmail, isString, @@ -180,7 +180,7 @@ const signUpForm = shape({ path: ['confirmation', 'password'], code: 'profile.password.mismatch', args: [], - }] satisfies ObjectShapeRefinementIssue<'profile.password.mismatch'> + }] satisfies ShapeRefinementViolationInput<'profile.password.mismatch'> }) ``` diff --git a/src/combinators.ts b/src/combinators.ts index 47b6504..8a9daee 100644 --- a/src/combinators.ts +++ b/src/combinators.ts @@ -4,23 +4,25 @@ import type { DiscriminatedUnionValidator, EachValidator, FieldsMatchObjectShapeRuleDescriptor, - GenericObjectShapeRuleDescriptor, InferConstraints, + InferShape, + MergeShapeDescriptors, MaybeMany, MaybePromise, NullableValidator, NullishValidator, ObjectShape, - ObjectShapeFieldSelector, - ObjectShapeRefinement, - ObjectShapeRefineMethod, - ObjectShapeRefinementIssue, - ObjectShapeRefinementSync, ObjectShapeRuleDescriptor, - MergeObjectDescriptors, OptionalValidator, - PartialObjectDescriptor, + PartialShapeDescriptor, RecordValidator, + ShapeDescriptor, + ShapeFieldSelector, + ShapeRefinement, + ShapeRefineMethod, + ShapeRefinementViolationInput, + SyncObjectShapeRuleDescriptor, + SyncShapeRefinement, TupleValidator, UnknownKeysMode, UnionValidator, @@ -49,22 +51,17 @@ import { isUndefined, } from '@/predicates' -export type ShapeDescriptor = Record> - -export type InferShape = { - [K in keyof D]: InferConstraints -} - export type { AsyncObjectShapeRuleDescriptor, ObjectShape, - ObjectShapeFieldSelector, - ObjectShapeRefinement, - ObjectShapeRefineMethod, - ObjectShapeRefineMethodSync, - ObjectShapeRefinementIssue, - ObjectShapeRefinementSync, - ObjectShapeSyncRefinement, + ShapeDescriptor, + ShapeFieldSelector, + ShapeRefinement, + ShapeRefineMethod, + ShapeRefineMethodSync, + ShapeRefinementViolationInput, + SyncObjectShapeRuleDescriptor, + SyncShapeRefinement, UnknownKeysMode, } from '~types' @@ -85,25 +82,15 @@ type InferDiscriminatedUnion = { type StrictObjectShape< D extends ShapeDescriptor, R extends readonly ObjectShapeRuleDescriptor[] = readonly ObjectShapeRuleDescriptor[], - RI extends ObjectShapeRefinementIssue = never, + RI extends ShapeRefinementViolationInput = never, > = ObjectShape type PassthroughObjectShape< D extends ShapeDescriptor, R extends readonly ObjectShapeRuleDescriptor[] = readonly ObjectShapeRuleDescriptor[], - RI extends ObjectShapeRefinementIssue = never, + RI extends ShapeRefinementViolationInput = never, > = ObjectShape -type ShapeRefinement< - T, - I extends ObjectShapeRefinementIssue = ObjectShapeRefinementIssue, -> = ObjectShapeRefinement - -type SyncShapeRefinement< - T, - I extends ObjectShapeRefinementIssue = ObjectShapeRefinementIssue, -> = ObjectShapeRefinementSync - const describeConstraintMap = >>( constraints: T ): Record> => { @@ -116,7 +103,7 @@ const describeConstraintMap = >(descriptor: R): R => { +const normalizeRuleDescriptor = >(descriptor: R): R => { if (!descriptor.metadata) { return descriptor } @@ -127,7 +114,7 @@ const normalizeRuleDescriptor = >( +const normalizeAsyncRuleDescriptor = >( descriptor: R ): Omit & AsyncObjectShapeRuleDescriptor => ({ ...normalizeRuleDescriptor(descriptor), @@ -206,13 +193,13 @@ const omitDescriptor = < const extendDescriptor = < D extends ShapeDescriptor, E extends ShapeDescriptor, ->(descriptor: D, extension: E): MergeObjectDescriptors => ({ +>(descriptor: D, extension: E): MergeShapeDescriptors => ({ ...descriptor, ...extension, -}) as MergeObjectDescriptors +}) as MergeShapeDescriptors -const partialDescriptor = (descriptor: D): PartialObjectDescriptor => { - const partial = {} as PartialObjectDescriptor +const partialDescriptor = (descriptor: D): PartialShapeDescriptor => { + const partial = {} as PartialShapeDescriptor keysOf(descriptor).forEach(key => { partial[key] = optional(descriptor[key]) @@ -229,13 +216,13 @@ const getPathValue = (value: unknown, path: readonly PropertyKey[]) => path.redu return Reflect.get(Object(current), key) }, value) -const toPath = (selector: ObjectShapeFieldSelector): PropertyKey[] => Array.isArray(selector) +const toPath = (selector: ShapeFieldSelector): PropertyKey[] => Array.isArray(selector) ? [...selector] : [selector] const toShapeRefinementViolations = < D extends ShapeDescriptor, - RI extends ObjectShapeRefinementIssue, + RI extends ShapeRefinementViolationInput, >( value: InferShape, path: PropertyKey[], @@ -265,7 +252,7 @@ const toShapeRefinementViolations = < const collectShapeRefinementViolations = < D extends ShapeDescriptor, - RI extends ObjectShapeRefinementIssue, + RI extends ShapeRefinementViolationInput, >( value: InferShape, path: PropertyKey[], @@ -282,7 +269,7 @@ const collectShapeRefinementViolations = < const collectAsyncShapeRefinementViolations = < D extends ShapeDescriptor, - RI extends ObjectShapeRefinementIssue, + RI extends ShapeRefinementViolationInput, >( value: InferShape, path: PropertyKey[], @@ -304,7 +291,7 @@ const createObjectShape = < const D extends ShapeDescriptor, const M extends UnknownKeysMode, const R extends readonly ObjectShapeRuleDescriptor[], - RI extends ObjectShapeRefinementIssue = never, + RI extends ShapeRefinementViolationInput = never, >( descriptor: D, unknownKeys: M, @@ -313,8 +300,8 @@ const createObjectShape = < ): ObjectShape => { const keys = keysOf(descriptor) const addAsyncRefinement = < - const I extends ObjectShapeRefinementIssue = ObjectShapeRefinementIssue, - const RD extends GenericObjectShapeRuleDescriptor = GenericObjectShapeRuleDescriptor<'refine'> + const I extends ShapeRefinementViolationInput = ShapeRefinementViolationInput, + const RD extends SyncObjectShapeRuleDescriptor = SyncObjectShapeRuleDescriptor<'refine'> >( refinement: ShapeRefinement, I>, ruleDescriptor: RD = { kind: 'refine' } as RD @@ -326,8 +313,8 @@ const createObjectShape = < ) const addSyncRefinement = < - const I extends ObjectShapeRefinementIssue = ObjectShapeRefinementIssue, - const RD extends GenericObjectShapeRuleDescriptor = GenericObjectShapeRuleDescriptor<'refine'> + const I extends ShapeRefinementViolationInput = ShapeRefinementViolationInput, + const RD extends SyncObjectShapeRuleDescriptor = SyncObjectShapeRuleDescriptor<'refine'> >( refinement: SyncShapeRefinement, I>, ruleDescriptor: RD = { kind: 'refine' } as RD @@ -340,7 +327,7 @@ const createObjectShape = < const refine = Object.assign(addAsyncRefinement, { sync: addSyncRefinement, - }) as ObjectShapeRefineMethod + }) as ShapeRefineMethod return attachConstraintDescriptor({ descriptor, @@ -394,7 +381,7 @@ const createObjectShape = < ) as Validation] }, refine, - fieldsMatch(selectedKeys: K) { + fieldsMatch(selectedKeys: K) { const [left, right] = selectedKeys const leftPath = toPath(left) const rightPath = toPath(right) @@ -407,7 +394,7 @@ const createObjectShape = < D, M, [...R, FieldsMatchObjectShapeRuleDescriptor], - RI | ObjectShapeRefinementIssue<'shape.fields.mismatch'> + RI | ShapeRefinementViolationInput<'shape.fields.mismatch'> >( descriptor, unknownKeys, @@ -421,7 +408,7 @@ const createObjectShape = < }] }] as readonly SyncShapeRefinement< InferShape, - RI | ObjectShapeRefinementIssue<'shape.fields.mismatch'> + RI | ShapeRefinementViolationInput<'shape.fields.mismatch'> >[], [...rules, ruleDescriptor] as [...R, FieldsMatchObjectShapeRuleDescriptor] ) @@ -445,7 +432,7 @@ const createObjectShape = < return createObjectShape(extendDescriptor(descriptor, extension), unknownKeys, [], []) }, merge( - shape: ObjectShape + shape: ObjectShape ) { return createObjectShape(extendDescriptor(descriptor, shape.descriptor), unknownKeys, [], []) }, diff --git a/src/index.ts b/src/index.ts index 4496f3c..deeb193 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,10 +1,10 @@ import type { Constraint, InferConstraints, - InferMaybeManyViolations, + InferViolations, MaybeMany, Recursive, - ValidationTuple, + ValidationResult, Violation, } from '~types' @@ -22,48 +22,51 @@ export { export type { AllOfConstraintDescriptor, AssertionDescriptor, - AssertionDescriptorConstraint, + AssertionConstraintDescriptor, AsyncObjectShapeRuleDescriptor, + BaseConstraintDescriptor, Constraint, ConstraintDescriptor, - ConstraintDescriptorBase, ConstraintMetadata, CustomConstraintDescriptor, DescribeAssertionConstraint, DescribeAssertionConstraintTuple, DescribeConstraint, DescribeConstraintTuple, - DescribeMaybeMany, - DescribeObjectDescriptor, + DescribeConstraints, + DescribeShapeDescriptor, DescribedValidator, DiscriminatedUnionValidator, EachValidator, FieldsMatchObjectShapeRuleDescriptor, - GenericObjectShapeRuleDescriptor, + InferShape, InferConstraint, InferConstraints, InferConstraintViolations, - InferMaybeManyViolations, + InferViolations, + MergeShapeDescriptors, + OpaqueValidatorDescriptor, + PartialShapeDescriptor, DiscriminatedUnionConstraintDescriptor, EachConstraintDescriptor, NullableValidator, NullishValidator, KnownViolationCode, KnownViolationSubject, - ObjectShapeFieldSelector, - ObjectShapeAsyncRefinement, - ObjectShapeRefinement, - ObjectShapeRefineMethod, - ObjectShapeRefineMethodSync, - ObjectShapeRefinementIssue, - ObjectShapeRefinementSync, ObjectShapeRuleDescriptor, ObjectShapeRuleDescriptorBase, - ObjectShapeSyncRefinement, OptionalValidator, RecordValidator, RecordConstraintDescriptor, + ShapeDescriptor, + ShapeFieldSelector, + ShapeRefinement, + ShapeRefineMethod, + ShapeRefineMethodSync, + ShapeRefinementViolationInput, ShapeConstraintDescriptor, + SyncObjectShapeRuleDescriptor, + SyncShapeRefinement, TupleValidator, TupleConstraintDescriptor, UnionValidator, @@ -80,10 +83,8 @@ export type { ViolationSubject, ViolationTreeNode, UnionConstraintDescriptor, - ValidationTuple, ValidationResult, Validator, - ValidatorDescriptor, Violation, WrapperConstraintDescriptor, } from '~types' @@ -159,7 +160,7 @@ const collectViolationsSync = ( collectViolationsSync.sync = true as const -function toResult(value: unknown, violations: V[]): ValidationTuple { +function toResult(value: unknown, violations: V[]): ValidationResult { return violations.length === 0 ? [true, value as T, []] : [false, value, violations] @@ -208,24 +209,24 @@ export const validate = Object.assign( async >( value: unknown, constraints: C - ): Promise, InferMaybeManyViolations>> => { + ): Promise, InferViolations>> => { const violations = await collectViolations(value, constraints) - return toResult, InferMaybeManyViolations>( + return toResult, InferViolations>( value, - violations as InferMaybeManyViolations[] + violations as InferViolations[] ) }, { sync>( value: unknown, constraints: C - ): ValidationTuple, InferMaybeManyViolations> { + ): ValidationResult, InferViolations> { const violations = collectViolationsSync(value, constraints) - return toResult, InferMaybeManyViolations>( + return toResult, InferViolations>( value, - violations as InferMaybeManyViolations[] + violations as InferViolations[] ) }, } diff --git a/src/metadata.ts b/src/metadata.ts index adadb80..9e9f63f 100644 --- a/src/metadata.ts +++ b/src/metadata.ts @@ -4,7 +4,7 @@ import type { ConstraintDescriptor, ConstraintMetadata, DescribeConstraint, - DescribeMaybeMany, + DescribeConstraints, MaybeMany, Validator, } from '~types' @@ -152,15 +152,15 @@ export const meta = (validator: V): V => validator -export const describeConstraints = >(constraints: C): DescribeMaybeMany => { +export const describeConstraints = >(constraints: C): DescribeConstraints => { const values = arrayify(constraints) return values.length === 1 - ? describe(values[0] as Constraint) as DescribeMaybeMany + ? describe(values[0] as Constraint) as DescribeConstraints : { kind: 'allOf', constraints: values.map(value => describe(value)), - } as unknown as DescribeMaybeMany + } as unknown as DescribeConstraints } export const describe = (constraint: C): DescribeConstraint => { diff --git a/tests/combinators.test-d.ts b/tests/combinators.test-d.ts index bd6765c..db2af45 100644 --- a/tests/combinators.test-d.ts +++ b/tests/combinators.test-d.ts @@ -1,10 +1,9 @@ import type { AsyncObjectShapeRuleDescriptor, - ObjectShapeAsyncRefinement, - ObjectShapeRefinement, - ObjectShapeRefinementSync, - ObjectShapeSyncRefinement, - ValidationTuple, + ShapeRefinement, + ShapeRefinementViolationInput, + SyncShapeRefinement, + ValidationResult, Violation, } from '@/index' @@ -37,7 +36,7 @@ describe('combinator types', () => { test('exact preserves literal types', () => { const result = validate.sync('admin', exact('admin')) - assertType>(result) + assertType>(result) }) test('optional, nullable and nullish affect inferred object types', () => { @@ -55,7 +54,7 @@ describe('combinator types', () => { role: 'admin', }, schema) - assertType { test('tuple preserves positional inference', () => { const result = validate.sync(['admin', undefined], tuple([exact('admin'), optional(isString)])) - assertType>(result) + assertType>(result) }) test('record wraps value inference for dynamic keys', () => { @@ -101,13 +100,13 @@ describe('combinator types', () => { backup: 'admin', }, record(exact('admin'))) - assertType>>(result) + assertType>>(result) }) test('union combines branch inference into a union type', () => { const result = validate.sync('admin', union([exact('admin'), isNumber])) - assertType>(result) + assertType>(result) }) test('union preserves object variant inference', () => { @@ -125,7 +124,7 @@ describe('combinator types', () => { }), ])) - assertType>(result) @@ -146,7 +145,7 @@ describe('combinator types', () => { }), })) - assertType>(result) @@ -165,7 +164,7 @@ describe('combinator types', () => { const extended = profile.extend({ team: isString }) const merged = profile.merge(shape({ role: exact('editor'), team: isString })) - assertType>(validate.sync({ @@ -173,7 +172,7 @@ describe('combinator types', () => { nickname: undefined, }, picked)) - assertType>(validate.sync({ @@ -181,13 +180,13 @@ describe('combinator types', () => { role: 'admin', }, omitted)) - assertType>(validate.sync({}, partialProfile)) - assertType { team: 'validators', }, extended)) - assertType { const confirmedRegistration = registration.fieldsMatch(['password', 'confirmPassword']) - assertType>>(validate({ @@ -246,7 +245,7 @@ describe('combinator types', () => { confirmPassword: 'secret', }, registration)) - assertType>>(validate({ @@ -254,7 +253,7 @@ describe('combinator types', () => { confirmPassword: 'secret', }, confirmedRegistration)) - assertType>(validate.sync({ @@ -278,29 +277,22 @@ describe('combinator types', () => { } }) - test('object-level refinement type aliases expose async-first and sync-safe names', () => { - assertType>(async value => { + test('object-level refinement types expose async-first and sync-safe names', () => { + assertType>(async value => { assertType(value.password) return [] }) - assertType>(value => { + assertType>(value => { assertType(value.password) return [] }) - assertType>(async value => { - assertType(value.password) - - return [] - }) - - assertType>(value => { - assertType(value.password) - - return [] + assertType>({ + code: 'shape.fields.mismatch', + args: [[['password'], ['confirmPassword']]], }) }) @@ -312,7 +304,7 @@ describe('combinator types', () => { }), }).fieldsMatch([['password'], ['confirm', 'password']]) - assertType { }), }).fieldsMatch(['password', ['confirm', 'password']]) - assertType { role: 'admin', }, schema) - assertType>(result) diff --git a/tests/validate.tuple.test-d.ts b/tests/validate.tuple.test-d.ts index c9e947b..3cf5317 100644 --- a/tests/validate.tuple.test-d.ts +++ b/tests/validate.tuple.test-d.ts @@ -1,5 +1,5 @@ import type { - ValidationTuple, + ValidationResult, Violation, } from '@/index' @@ -31,7 +31,7 @@ describe('validate tuple types', () => { tags: ['ts'], }, profile) - assertType>(result) diff --git a/tests/violation-codes.test-d.ts b/tests/violation-codes.test-d.ts index 6a67a4d..87c21c4 100644 --- a/tests/violation-codes.test-d.ts +++ b/tests/violation-codes.test-d.ts @@ -8,7 +8,7 @@ declare module '@/index' { import type { KnownViolationSubject, - ObjectShapeRefinementIssue, + ShapeRefinementViolationInput, ViolationArgs, ViolationCode, ViolationEntry, @@ -49,7 +49,7 @@ describe('violation code registry augmentation', () => { const issue = { code: 'shape.password.mismatch' as const, args: [] as const, - } satisfies ObjectShapeRefinementIssue<'shape.password.mismatch'> + } satisfies ShapeRefinementViolationInput<'shape.password.mismatch'> const legacySubject = { kind: 'runtime' as const, diff --git a/types/index.d.ts b/types/index.d.ts index b6d3dee..962a47c 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -79,7 +79,7 @@ export interface ViolationCodeRegistry { 'shape.fields.mismatch': ViolationCodeEntry< 'validator', 'shape', - readonly [selectors: readonly [ObjectShapeFieldSelector, ObjectShapeFieldSelector]] + readonly [selectors: readonly [ShapeFieldSelector, ShapeFieldSelector]] >; 'shape.unknown-key': ViolationCodeEntry<'validator', 'shape', readonly []>; 'size.exact': ViolationCodeEntry<'assertion', 'hasSize', readonly [exact: number]>; @@ -225,7 +225,7 @@ export declare const collection: (violations: readonly V[]) export type ConstraintMetadata = Readonly> /** Public descriptor entry for additional assertion-level checks. */ -export interface AssertionDescriptorConstraint< +export interface AssertionConstraintDescriptor< C extends string = string, A extends readonly unknown[] = readonly unknown[], > { @@ -234,7 +234,7 @@ export interface AssertionDescriptorConstraint< } /** Shared descriptor shape returned by `describe(...)`. */ -export interface ConstraintDescriptorBase { +export interface BaseConstraintDescriptor { readonly kind: K; readonly metadata?: ConstraintMetadata } @@ -243,8 +243,8 @@ export interface ConstraintDescriptorBase { export interface AssertionDescriptor< C extends string = string, A extends readonly unknown[] = readonly unknown[], - T extends readonly AssertionDescriptorConstraint[] = readonly AssertionDescriptorConstraint[], -> extends ConstraintDescriptorBase<'assertion'> { + T extends readonly AssertionConstraintDescriptor[] = readonly AssertionConstraintDescriptor[], +> extends BaseConstraintDescriptor<'assertion'> { readonly name: string; readonly bail: boolean; readonly code: C; @@ -253,17 +253,17 @@ export interface AssertionDescriptor< } /** Generic fallback descriptor for custom validators without structural instrumentation. */ -export type ValidatorDescriptor = ConstraintDescriptorBase<'validator'> +export type OpaqueValidatorDescriptor = BaseConstraintDescriptor<'validator'> /** Public extension descriptor for custom validators that expose their own `describe()` contract. */ -export interface CustomConstraintDescriptor extends ConstraintDescriptorBase { +export interface CustomConstraintDescriptor extends BaseConstraintDescriptor { readonly [key: string]: unknown } /** Descriptor for sequential arrays of constraints used in a single slot. */ export interface AllOfConstraintDescriptor< T extends readonly unknown[] = readonly ConstraintDescriptor[], -> extends ConstraintDescriptorBase<'allOf'> { +> extends BaseConstraintDescriptor<'allOf'> { readonly constraints: T } @@ -271,42 +271,42 @@ export interface AllOfConstraintDescriptor< export interface WrapperConstraintDescriptor< K extends 'optional' | 'nullable' | 'nullish' = 'optional' | 'nullable' | 'nullish', C = ConstraintDescriptor, -> extends ConstraintDescriptorBase { +> extends BaseConstraintDescriptor { readonly child: C } /** Descriptor for `each(...)`. */ export interface EachConstraintDescriptor< C = ConstraintDescriptor, -> extends ConstraintDescriptorBase<'each'> { +> extends BaseConstraintDescriptor<'each'> { readonly item: C } /** Descriptor for `tuple(...)`. */ export interface TupleConstraintDescriptor< T extends readonly unknown[] = readonly ConstraintDescriptor[], -> extends ConstraintDescriptorBase<'tuple'> { +> extends BaseConstraintDescriptor<'tuple'> { readonly items: T } /** Descriptor for `union(...)`. */ export interface UnionConstraintDescriptor< T extends readonly unknown[] = readonly ConstraintDescriptor[], -> extends ConstraintDescriptorBase<'union'> { +> extends BaseConstraintDescriptor<'union'> { readonly branches: T } /** Descriptor for `record(...)`. */ export interface RecordConstraintDescriptor< C = ConstraintDescriptor, -> extends ConstraintDescriptorBase<'record'> { +> extends BaseConstraintDescriptor<'record'> { readonly values: C } /** Descriptor for `discriminatedUnion(...)`. */ export interface DiscriminatedUnionConstraintDescriptor< V = Readonly>, -> extends ConstraintDescriptorBase<'discriminatedUnion'> { +> extends BaseConstraintDescriptor<'discriminatedUnion'> { readonly key: PropertyKey; readonly variants: V } @@ -318,7 +318,7 @@ export interface ObjectShapeRuleDescriptorBase { } /** Generic compact descriptor for sync-safe object-level rules. */ -export type GenericObjectShapeRuleDescriptor< +export type SyncObjectShapeRuleDescriptor< K extends string = 'refine', > = ObjectShapeRuleDescriptorBase @@ -331,15 +331,15 @@ export interface AsyncObjectShapeRuleDescriptor< /** Descriptor for the built-in `.fieldsMatch(...)` helper. */ export interface FieldsMatchObjectShapeRuleDescriptor< - Left extends ObjectShapeFieldSelector = ObjectShapeFieldSelector, - Right extends ObjectShapeFieldSelector = ObjectShapeFieldSelector, + Left extends ShapeFieldSelector = ShapeFieldSelector, + Right extends ShapeFieldSelector = ShapeFieldSelector, > extends ObjectShapeRuleDescriptorBase<'fieldsMatch'> { readonly selectors: readonly [Left, Right] } /** Machine-readable summary of object-level rules registered on a shape. */ export type ObjectShapeRuleDescriptor = - | GenericObjectShapeRuleDescriptor + | SyncObjectShapeRuleDescriptor | AsyncObjectShapeRuleDescriptor | FieldsMatchObjectShapeRuleDescriptor @@ -347,7 +347,7 @@ export type ObjectShapeRuleDescriptor = export interface ShapeConstraintDescriptor< F = Readonly>, R extends readonly ObjectShapeRuleDescriptor[] = readonly ObjectShapeRuleDescriptor[], -> extends ConstraintDescriptorBase<'shape'> { +> extends BaseConstraintDescriptor<'shape'> { readonly unknownKeys: UnknownKeysMode; readonly fields: F; readonly rules: R @@ -357,7 +357,7 @@ export interface ShapeConstraintDescriptor< export type BuiltInConstraintDescriptor = | AssertionDescriptor | AllOfConstraintDescriptor - | ValidatorDescriptor + | OpaqueValidatorDescriptor | WrapperConstraintDescriptor | EachConstraintDescriptor | TupleConstraintDescriptor @@ -390,7 +390,7 @@ export type AssertionConstraint< /** Maps an assertion checker tuple into its public descriptor entry. */ export type DescribeAssertionConstraint = C extends AssertionConstraint - ? AssertionDescriptorConstraint + ? AssertionConstraintDescriptor : never /** Maps an assertion checker tuple into the violation subject it can produce. */ @@ -501,18 +501,18 @@ export type Validation = F extends Validate export type UnknownKeysMode = 'passthrough' | 'strict' /** Field selector accepted by shape helpers that can point to the current level or a nested path. */ -export type ObjectShapeFieldSelector = PropertyKey | readonly PropertyKey[] +export type ShapeFieldSelector = PropertyKey | readonly PropertyKey[] -type ResolveObjectShapeRefinementIssueCode = COrA extends string ? COrA : string -type ResolveObjectShapeRefinementIssueArgs = [COrA] extends [readonly unknown[]] +type ResolveShapeRefinementViolationCode = COrA extends string ? COrA : string +type ResolveShapeRefinementViolationArgs = [COrA] extends [readonly unknown[]] ? COrA : ViolationArgs -/** Machine-readable issue returned by an object-level shape refinement. */ -export type ObjectShapeRefinementIssue< +/** Machine-readable violation input returned by an object-level shape refinement. */ +export type ShapeRefinementViolationInput< COrA extends string | readonly unknown[] = string, - C extends string = ResolveObjectShapeRefinementIssueCode, - A extends readonly unknown[] = ResolveObjectShapeRefinementIssueArgs, + C extends string = ResolveShapeRefinementViolationCode, + A extends readonly unknown[] = ResolveShapeRefinementViolationArgs, > = { path?: PropertyKey[]; code: C; @@ -525,79 +525,67 @@ export type ObjectShapeRefinementIssue< } /** Sync-safe object-level rule that runs after the base shape has validated successfully. */ -export type ObjectShapeRefinementSync< +export type SyncShapeRefinement< T, - I extends ObjectShapeRefinementIssue = ObjectShapeRefinementIssue, + I extends ShapeRefinementViolationInput = ShapeRefinementViolationInput, > = ( value: T ) => MaybeMany | null | undefined /** Async-first object-level rule that runs after the base shape has validated successfully. */ -export type ObjectShapeRefinement< +export type ShapeRefinement< T, - I extends ObjectShapeRefinementIssue = ObjectShapeRefinementIssue, + I extends ShapeRefinementViolationInput = ShapeRefinementViolationInput, > = ( value: T ) => MaybePromise | null | undefined> -/** Backward-compatible alias for the async-first object-level rule type. */ -export type ObjectShapeAsyncRefinement< - T, - I extends ObjectShapeRefinementIssue = ObjectShapeRefinementIssue, -> = ObjectShapeRefinement - -/** Backward-compatible alias for the sync-safe object-level rule type. */ -export type ObjectShapeSyncRefinement< - T, - I extends ObjectShapeRefinementIssue = ObjectShapeRefinementIssue, -> = ObjectShapeRefinementSync - -type AsyncObjectShapeRuleDescriptorOf> = +type AsyncObjectShapeRuleDescriptorOf> = Omit & AsyncObjectShapeRuleDescriptor /** Explicitly sync-safe callable helper exposed as `shape(...).refine.sync(...)`. */ -export interface ObjectShapeRefineMethodSync< - D extends ObjectDescriptor = ObjectDescriptor, +export interface ShapeRefineMethodSync< + D extends ShapeDescriptor = ShapeDescriptor, M extends UnknownKeysMode = UnknownKeysMode, R extends readonly ObjectShapeRuleDescriptor[] = readonly ObjectShapeRuleDescriptor[], - RI extends ObjectShapeRefinementIssue = never, + RI extends ShapeRefinementViolationInput = never, > { - ( - refinement: ObjectShapeRefinementSync, I> - ): ObjectShape], RI | I>; + ( + refinement: SyncShapeRefinement, I> + ): ObjectShape], RI | I>; < - const I extends ObjectShapeRefinementIssue = ObjectShapeRefinementIssue, - const RD extends GenericObjectShapeRuleDescriptor = GenericObjectShapeRuleDescriptor<'refine'> + const I extends ShapeRefinementViolationInput = ShapeRefinementViolationInput, + const RD extends SyncObjectShapeRuleDescriptor = SyncObjectShapeRuleDescriptor<'refine'> >( - refinement: ObjectShapeRefinementSync, I>, + refinement: SyncShapeRefinement, I>, descriptor: RD ): ObjectShape; } /** Async-first callable shape helper exposed as `shape(...).refine(...)`. */ -export interface ObjectShapeRefineMethod< - D extends ObjectDescriptor = ObjectDescriptor, +export interface ShapeRefineMethod< + D extends ShapeDescriptor = ShapeDescriptor, M extends UnknownKeysMode = UnknownKeysMode, R extends readonly ObjectShapeRuleDescriptor[] = readonly ObjectShapeRuleDescriptor[], - RI extends ObjectShapeRefinementIssue = never, + RI extends ShapeRefinementViolationInput = never, > { - ( - refinement: ObjectShapeRefinement, I> + ( + refinement: ShapeRefinement, I> ): ObjectShape], RI | I>; < - const I extends ObjectShapeRefinementIssue = ObjectShapeRefinementIssue, - const RD extends GenericObjectShapeRuleDescriptor = GenericObjectShapeRuleDescriptor<'refine'> + const I extends ShapeRefinementViolationInput = ShapeRefinementViolationInput, + const RD extends SyncObjectShapeRuleDescriptor = SyncObjectShapeRuleDescriptor<'refine'> >( - refinement: ObjectShapeRefinement, I>, + refinement: ShapeRefinement, I>, descriptor: RD ): ObjectShape], RI | I>; - sync: ObjectShapeRefineMethodSync; + sync: ShapeRefineMethodSync; } type KnownCodeViolation = Violation> -type ShapeRefinementIssueSubject = - I extends ObjectShapeRefinementIssue +type ShapeRefinementIssueSubject = + I extends ShapeRefinementViolationInput ? C extends KnownViolationCode ? { kind: 'validator'; @@ -608,13 +596,13 @@ type ShapeRefinementIssueSubject = : ViolationSubject : never -type ShapeRefinementIssueViolation = - I extends ObjectShapeRefinementIssue +type ShapeRefinementIssueViolation = + I extends ShapeRefinementViolationInput ? Violation> : never -type InferObjectDescriptorViolations = { - [K in keyof D]: InferMaybeManyViolations +type InferShapeViolations = { + [K in keyof D]: InferViolations }[keyof D] /** Maps a single constraint into the union of violations it can produce. */ @@ -629,34 +617,34 @@ export type InferConstraintViolations = : C extends ObjectShape ? KnownCodeViolation<'type.record'> | (M extends 'strict' ? KnownCodeViolation<'shape.unknown-key'> : never) - | InferObjectDescriptorViolations + | InferShapeViolations | ShapeRefinementIssueViolation : C extends OptionalValidator - ? InferMaybeManyViolations + ? InferViolations : C extends NullableValidator - ? InferMaybeManyViolations + ? InferViolations : C extends NullishValidator - ? InferMaybeManyViolations + ? InferViolations : C extends EachValidator - ? KnownCodeViolation<'type.array'> | InferMaybeManyViolations + ? KnownCodeViolation<'type.array'> | InferViolations : C extends TupleValidator ? KnownCodeViolation<'type.array'> | KnownCodeViolation<'tuple.length'> - | InferMaybeManyViolations + | InferViolations : C extends UnionValidator - ? KnownCodeViolation<'union.no-match'> | InferMaybeManyViolations + ? KnownCodeViolation<'union.no-match'> | InferViolations : C extends DiscriminatedUnionValidator ? KnownCodeViolation<'type.record'> | KnownCodeViolation<'union.invalid-discriminator'> - | InferMaybeManyViolations + | InferViolations : C extends RecordValidator - ? KnownCodeViolation<'type.record'> | InferMaybeManyViolations + ? KnownCodeViolation<'type.record'> | InferViolations : C extends Validator ? Violation : never /** Maps one-or-many constraints into the union of violations they can produce. */ -export type InferMaybeManyViolations> = +export type InferViolations> = C extends readonly [] ? never : C extends readonly Constraint[] @@ -672,7 +660,7 @@ export type ValidationSuccess = [ok: true, validated: T, violations: []] export type ValidationFailure = [ok: false, validated: unknown, violations: V[]] /** - * The result tuple returned by `validate(...)` and `validate.sync(...)`. + * The result returned by `validate(...)` and `validate.sync(...)`. * * Example: * `const [ok, validated, violations] = await validate(value, schema)` @@ -680,10 +668,7 @@ export type ValidationFailure = [ok: false, val * Example: * `if (ok) validated.name.toUpperCase()` */ -export type ValidationTuple = ValidationSuccess | ValidationFailure - -/** Alias for `ValidationTuple`. */ -export type ValidationResult = ValidationTuple +export type ValidationResult = ValidationSuccess | ValidationFailure /** Attaches read-only metadata to a constraint without changing validation semantics. */ export declare const meta: (constraint: C, metadata: M) => C @@ -718,22 +703,22 @@ export interface DescribedValidator< export declare const custom: (validator: V) => V /** Descriptor that maps object keys to one or many constraints. */ -export type ObjectDescriptor = Record> +export type ShapeDescriptor = Record> -/** Runtime type inferred from an object descriptor. */ -export type InferObjectDescriptor = { +/** Runtime type inferred from a shape descriptor. */ +export type InferShape = { [K in keyof D]: InferConstraints } /** Descriptor produced by `.partial()` where every field accepts `undefined`. */ -export type PartialObjectDescriptor = { +export type PartialShapeDescriptor = { [K in keyof D]: Validator | undefined> } /** Utility type for overriding descriptor keys from left to right. */ -export type MergeObjectDescriptors< - Left extends ObjectDescriptor, - Right extends ObjectDescriptor, +export type MergeShapeDescriptors< + Left extends ShapeDescriptor, + Right extends ShapeDescriptor, > = Omit & Right /** @@ -743,58 +728,58 @@ export type MergeObjectDescriptors< * `const user = shape({ name: isString }).strict()` */ export interface ObjectShape< - D extends ObjectDescriptor = ObjectDescriptor, + D extends ShapeDescriptor = ShapeDescriptor, M extends UnknownKeysMode = 'passthrough', R extends readonly ObjectShapeRuleDescriptor[] = readonly ObjectShapeRuleDescriptor[], - RI extends ObjectShapeRefinementIssue = never, -> extends Validator> { + RI extends ShapeRefinementViolationInput = never, +> extends Validator> { readonly descriptor: D; readonly unknownKeys: M; - readonly refine: ObjectShapeRefineMethod; - fieldsMatch( + readonly refine: ShapeRefineMethod; + fieldsMatch( keys: K - ): ObjectShape], RI | ObjectShapeRefinementIssue<'shape.fields.mismatch'>>; + ): ObjectShape], RI | ShapeRefinementViolationInput<'shape.fields.mismatch'>>; strict(): ObjectShape; passthrough(): ObjectShape; pick(keys: K): ObjectShape, M, [], never>; omit(keys: K): ObjectShape, M, [], never>; - partial(): ObjectShape, M, [], never>; - extend(descriptor: E): ObjectShape, M, [], never>; - merge( - shape: ObjectShape - ): ObjectShape, M, [], never>; + partial(): ObjectShape, M, [], never>; + extend(descriptor: E): ObjectShape, M, [], never>; + merge( + shape: ObjectShape + ): ObjectShape, M, [], never>; } /** Helper that maps a single constraint into its public `describe(...)` result. */ export type DescribeConstraint = C extends Assertion ? AssertionDescriptor> - : C extends ObjectShape - ? ShapeConstraintDescriptor, R> & { readonly unknownKeys: M } + : C extends ObjectShape + ? ShapeConstraintDescriptor, R> & { readonly unknownKeys: M } : C extends OptionalValidator - ? WrapperConstraintDescriptor<'optional', DescribeMaybeMany> + ? WrapperConstraintDescriptor<'optional', DescribeConstraints> : C extends NullableValidator - ? WrapperConstraintDescriptor<'nullable', DescribeMaybeMany> + ? WrapperConstraintDescriptor<'nullable', DescribeConstraints> : C extends NullishValidator - ? WrapperConstraintDescriptor<'nullish', DescribeMaybeMany> + ? WrapperConstraintDescriptor<'nullish', DescribeConstraints> : C extends EachValidator - ? EachConstraintDescriptor> + ? EachConstraintDescriptor> : C extends TupleValidator ? TupleConstraintDescriptor> : C extends UnionValidator ? UnionConstraintDescriptor> : C extends DiscriminatedUnionValidator - ? DiscriminatedUnionConstraintDescriptor> + ? DiscriminatedUnionConstraintDescriptor> : C extends RecordValidator - ? RecordConstraintDescriptor> + ? RecordConstraintDescriptor> : C extends DescribedValidator ? D & { readonly metadata?: ConstraintMetadata } : C extends Validator - ? ValidatorDescriptor + ? OpaqueValidatorDescriptor : never /** Helper that maps a one-or-many constraint slot into its public `describe(...)` result. */ -export type DescribeMaybeMany> = +export type DescribeConstraints> = C extends readonly [infer Only] ? Only extends Constraint ? DescribeConstraint @@ -808,14 +793,14 @@ export type DescribeMaybeMany> = : never /** Helper that maps object descriptors into their `describe(...)` field tree. */ -export type DescribeObjectDescriptor = { - [K in keyof D]: DescribeMaybeMany +export type DescribeShapeDescriptor = { + [K in keyof D]: DescribeConstraints } /** Helper that maps tuples of constraints into tuples of descriptors. */ export type DescribeConstraintTuple[]> = { - readonly [K in keyof T]: DescribeMaybeMany -} & ReadonlyArray> + readonly [K in keyof T]: DescribeConstraints +} & ReadonlyArray> declare const optionalValidatorBrand: unique symbol declare const nullableValidatorBrand: unique symbol From 4390ee7cbd658c358d716836c764138f8a585bd6 Mon Sep 17 00:00:00 2001 From: Kirill Zaytsev Date: Mon, 9 Mar 2026 21:50:52 +0400 Subject: [PATCH 03/15] refactor(assertions): Violation unions were narrowed for built-ins Parameterized built-in assertions now preserve more exact violation unions based on enabled options, and multipleOf keeps the literal step in typed args. Type tests and violation-code docs were updated to cover the stricter narrowing in validate(...) and ViolationCollection. --- docs/en/08-violation-code-types.md | 25 +++ docs/ru/08-violation-code-types.md | 25 +++ src/assertions.ts | 221 ++++++++++++++++++--------- src/checkers.ts | 2 +- tests/assertion-violations.test-d.ts | 113 ++++++++++++++ 5 files changed, 314 insertions(+), 72 deletions(-) create mode 100644 tests/assertion-violations.test-d.ts diff --git a/docs/en/08-violation-code-types.md b/docs/en/08-violation-code-types.md index 4359a94..fab1988 100644 --- a/docs/en/08-violation-code-types.md +++ b/docs/en/08-violation-code-types.md @@ -58,6 +58,31 @@ In TypeScript this means: This is useful when adapters inspect descriptors and want code-aware branching without hand-written casts. +The same precision now flows into `validate(...)` for parameterized built-ins. + +```typescript +import { + collection, + hasLength, + validate, +} from '@modulify/validator' + +const [ok, , violations] = validate.sync('ab', hasLength({ min: 3 })) + +if (!ok) { + collection(violations).map(violation => { + switch (violation.violates.code) { + case 'length.unsupported-type': + return violation.violates.name + case 'length.min': + return violation.violates.args[0] + } + }) +} +``` + +For calls like `hasLength({ min: 3 })`, impossible branches such as `'length.max'` or `'length.range'` are no longer carried into the violation union. + ## What The Global Registry Solves Exact literals on individual values are good for local introspection. diff --git a/docs/ru/08-violation-code-types.md b/docs/ru/08-violation-code-types.md index 06d41f0..a7ab18e 100644 --- a/docs/ru/08-violation-code-types.md +++ b/docs/ru/08-violation-code-types.md @@ -58,6 +58,31 @@ const lengthDescriptor = describe(hasLength({ min: 3 })) Это удобно для адаптеров и tooling-кода, который читает descriptors и хочет ветвиться по коду без ручных cast. +Та же точность теперь протекает и в `validate(...)` для параметризованных built-in assertions. + +```typescript +import { + collection, + hasLength, + validate, +} from '@modulify/validator' + +const [ok, , violations] = validate.sync('ab', hasLength({ min: 3 })) + +if (!ok) { + collection(violations).map(violation => { + switch (violation.violates.code) { + case 'length.unsupported-type': + return violation.violates.name + case 'length.min': + return violation.violates.args[0] + } + }) +} +``` + +Для вызовов вроде `hasLength({ min: 3 })` невозможные ветки, например `'length.max'` или `'length.range'`, больше не попадают в union violations. + ## Зачем нужен глобальный реестр Точные literals на отдельных значениях полезны для локальной интроспекции. diff --git a/src/assertions.ts b/src/assertions.ts index a8591c3..e4a517b 100644 --- a/src/assertions.ts +++ b/src/assertions.ts @@ -38,24 +38,145 @@ export { assert } // eslint-disable-next-line @typescript-eslint/no-empty-object-type type Defined = {} | null +type EmptyOptions = Record +type LengthExactConstraint = + AssertionConstraint +type LengthMaxConstraint = + AssertionConstraint +type LengthMinConstraint = + AssertionConstraint +type LengthRangeConstraint = + AssertionConstraint type LengthAssertionConstraint = - | AssertionConstraint - | AssertionConstraint - | AssertionConstraint - | AssertionConstraint + | LengthExactConstraint + | LengthMaxConstraint + | LengthMinConstraint + | LengthRangeConstraint +type SizeExactConstraint = + AssertionConstraint | Set, number, [exact: N], 'size.exact'> +type SizeMaxConstraint = + AssertionConstraint | Set, number, [max: N], 'size.max'> +type SizeMinConstraint = + AssertionConstraint | Set, number, [min: N], 'size.min'> +type SizeRangeConstraint = + AssertionConstraint | Set, number, [range: R], 'size.range'> type SizeAssertionConstraint = - | AssertionConstraint | Set, number, [exact: number], 'size.exact'> - | AssertionConstraint | Set, number, [max: number], 'size.max'> - | AssertionConstraint | Set, number, [min: number], 'size.min'> - | AssertionConstraint | Set, number, [range: [number, number]], 'size.range'> + | SizeExactConstraint + | SizeMaxConstraint + | SizeMinConstraint + | SizeRangeConstraint +type ValueExactConstraint = + AssertionConstraint +type ValueMaxConstraint = + AssertionConstraint +type ValueMinConstraint = + AssertionConstraint +type ValueRangeConstraint = + AssertionConstraint type ValueAssertionConstraint = - | AssertionConstraint - | AssertionConstraint - | AssertionConstraint - | AssertionConstraint + | ValueExactConstraint + | ValueMaxConstraint + | ValueMinConstraint + | ValueRangeConstraint + +type BoundedAssertionOptions = { + exact?: number | null; + max?: number | null; + min?: number | null; + range?: readonly [number, number] | null; + bail?: boolean; +} + +type OptionNumber = K extends keyof O ? Extract : never + +type OptionRange = + K extends keyof O + ? Extract extends infer R + ? R extends readonly [infer Min extends number, infer Max extends number] + ? readonly [Min, Max] + : never + : never + : never + +type IncludeConstraint< + Value, + C extends AssertionConstraint, +> = [Value] extends [never] ? [] : [C] + +type LengthConstraintTupleFromOptions = [ + ...IncludeConstraint, LengthExactConstraint>>, + ...IncludeConstraint, LengthMaxConstraint>>, + ...IncludeConstraint, LengthMinConstraint>>, + ...IncludeConstraint, LengthRangeConstraint>>, +] + +type SizeConstraintTupleFromOptions = [ + ...IncludeConstraint, SizeExactConstraint>>, + ...IncludeConstraint, SizeMaxConstraint>>, + ...IncludeConstraint, SizeMinConstraint>>, + ...IncludeConstraint, SizeRangeConstraint>>, +] + +type ValueConstraintTupleFromOptions = [ + ...IncludeConstraint, ValueExactConstraint>>, + ...IncludeConstraint, ValueMaxConstraint>>, + ...IncludeConstraint, ValueMinConstraint>>, + ...IncludeConstraint, ValueRangeConstraint>>, +] + +const buildLengthConstraints = (options: O): LengthConstraintTupleFromOptions => { + const { + exact = null, + max = null, + min = null, + range = null, + } = options + const constraints: LengthAssertionConstraint[] = [] + + if (exact !== null) constraints.push([length, isEqual, 'length.exact', exact] as const) + if (max !== null) constraints.push([length, isLte, 'length.max', max] as const) + if (min !== null) constraints.push([length, isGte, 'length.min', min] as const) + if (range !== null) constraints.push([length, inRange, 'length.range', range] as const) + + return constraints as unknown as LengthConstraintTupleFromOptions +} + +const buildSizeConstraints = (options: O): SizeConstraintTupleFromOptions => { + const { + exact = null, + max = null, + min = null, + range = null, + } = options + const constraints: SizeAssertionConstraint[] = [] + + if (exact !== null) constraints.push([size, isEqual, 'size.exact', exact] as const) + if (max !== null) constraints.push([size, isLte, 'size.max', max] as const) + if (min !== null) constraints.push([size, isGte, 'size.min', min] as const) + if (range !== null) constraints.push([size, inRange, 'size.range', range] as const) + + return constraints as unknown as SizeConstraintTupleFromOptions +} + +const buildValueConstraints = (options: O): ValueConstraintTupleFromOptions => { + const { + exact = null, + max = null, + min = null, + range = null, + } = options + const constraints: ValueAssertionConstraint[] = [] + + if (exact !== null) constraints.push([(value: number) => value, isEqual, 'number.exact', exact] as const) + if (max !== null) constraints.push([(value: number) => value, isLte, 'number.max', max] as const) + if (min !== null) constraints.push([(value: number) => value, isGte, 'number.min', min] as const) + if (range !== null) constraints.push([(value: number) => value, inRange, 'number.range', range] as const) + + return constraints as unknown as ValueConstraintTupleFromOptions +} export const isBoolean = assert(_isBoolean, { name: 'isBoolean', bail: true, code: 'type.boolean' }) export const isBigInt = assert(_isBigInt, { name: 'isBigInt', bail: true, code: 'type.bigint' }) @@ -77,25 +198,11 @@ export const isSet = assert(_isSet, { name: 'isSet', bail: true, code: 'type.set export const isString = assert(_isString, { name: 'isString', bail: true, code: 'type.string' }) export const isSymbol = assert(_isSymbol, { name: 'isSymbol', bail: true, code: 'type.symbol' }) -export const hasLength = ({ - exact = null, - max = null, - min = null, - range = null, - bail = false, -}: { - exact?: number | null; - max?: number | null; - min?: number | null; - range?: [number, number] | null; - bail?: boolean; -} = {}) => { - const constraints: LengthAssertionConstraint[] = [] - - if (exact !== null) constraints.push([length, isEqual, 'length.exact', exact] as const) - if (max !== null) constraints.push([length, isLte, 'length.max', max] as const) - if (min !== null) constraints.push([length, isGte, 'length.min', min] as const) - if (range !== null) constraints.push([length, inRange, 'length.range', range] as const) +export const hasLength = ( + options: O = {} as O +) => { + const { bail = false } = options + const constraints = buildLengthConstraints(options) return assert( (value: unknown): value is string | unknown[] => isArray(value) || _isString(value), @@ -108,25 +215,11 @@ export const hasLength = ({ ) } -export const hasSize = ({ - exact = null, - max = null, - min = null, - range = null, - bail = false, -}: { - exact?: number | null; - max?: number | null; - min?: number | null; - range?: [number, number] | null; - bail?: boolean; -} = {}) => { - const constraints: SizeAssertionConstraint[] = [] - - if (exact !== null) constraints.push([size, isEqual, 'size.exact', exact] as const) - if (max !== null) constraints.push([size, isLte, 'size.max', max] as const) - if (min !== null) constraints.push([size, isGte, 'size.min', min] as const) - if (range !== null) constraints.push([size, inRange, 'size.range', range] as const) +export const hasSize = ( + options: O = {} as O +) => { + const { bail = false } = options + const constraints = buildSizeConstraints(options) return assert( (value: unknown): value is Map | Set => _isMap(value) || _isSet(value), @@ -205,25 +298,11 @@ export const endsWith = ( ]] as const ) -export const hasValue = ({ - exact = null, - max = null, - min = null, - range = null, - bail = false, -}: { - exact?: number | null; - max?: number | null; - min?: number | null; - range?: [number, number] | null; - bail?: boolean; -} = {}) => { - const constraints: ValueAssertionConstraint[] = [] - - if (exact !== null) constraints.push([(value: number) => value, isEqual, 'number.exact', exact] as const) - if (max !== null) constraints.push([(value: number) => value, isLte, 'number.max', max] as const) - if (min !== null) constraints.push([(value: number) => value, isGte, 'number.min', min] as const) - if (range !== null) constraints.push([(value: number) => value, inRange, 'number.range', range] as const) +export const hasValue = ( + options: O = {} as O +) => { + const { bail = false } = options + const constraints = buildValueConstraints(options) return assert( _isNumber, @@ -236,8 +315,8 @@ export const hasValue = ({ ) } -export const multipleOf = ( - step: number, +export const multipleOf = ( + step: Step, { bail = false, }: { diff --git a/src/checkers.ts b/src/checkers.ts index 427265c..209adb2 100644 --- a/src/checkers.ts +++ b/src/checkers.ts @@ -2,7 +2,7 @@ export const isEqual = (value: number, exact: number) => value === exact export const isGte = (value: number, min: number) => value >= min export const isLte = (value: number, max: number) => value <= max -export const inRange = (value: number, [min, max]: [number, number]) => value >= min && value <= max +export const inRange = (value: number, [min, max]: readonly [number, number]) => value >= min && value <= max export const startsWith = (value: string, prefix: string) => value.startsWith(prefix) export const endsWith = (value: string, suffix: string) => value.endsWith(suffix) export const matchesPattern = (value: string, pattern: RegExp) => new RegExp(pattern.source, pattern.flags).test(value) diff --git a/tests/assertion-violations.test-d.ts b/tests/assertion-violations.test-d.ts new file mode 100644 index 0000000..1f57077 --- /dev/null +++ b/tests/assertion-violations.test-d.ts @@ -0,0 +1,113 @@ +import { + assertType, + describe, + test, +} from 'vitest' + +import { + collection, + hasLength, + hasSize, + hasValue, + multipleOf, + validate, +} from '@/index' + +describe('parameterized assertion violation types', () => { + test('hasLength({ min: 3 }) keeps only supported violation codes', () => { + const [ok, , violations] = validate.sync('ab', hasLength({ min: 3 })) + + if (!ok) { + collection(violations).map(violation => { + switch (violation.violates.code) { + case 'length.unsupported-type': + assertType<[]>(violation.violates.args) + return violation.violates.name + case 'length.min': + assertType<3>(violation.violates.args[0]) + return violation.violates.args[0] + default: + assertType(violation.violates) + return violation.violates + } + }) + } + }) + + test('hasLength({ min: 3, max: 5 }) keeps only the enabled range branches', () => { + const [ok, , violations] = validate.sync('abcdef', hasLength({ min: 3, max: 5 })) + + if (!ok) { + collection(violations).map(violation => { + switch (violation.violates.code) { + case 'length.unsupported-type': + return violation.violates.name + case 'length.min': + return violation.violates.args[0] + case 'length.max': + return violation.violates.args[0] + default: + assertType(violation.violates) + return violation.violates + } + }) + } + }) + + test('hasSize({ exact: 2 }) excludes unrelated size branches', () => { + const [ok, , violations] = validate.sync(new Set([1]), hasSize({ exact: 2 })) + + if (!ok) { + collection(violations).map(violation => { + switch (violation.violates.code) { + case 'size.unsupported-type': + return violation.violates.name + case 'size.exact': + assertType<2>(violation.violates.args[0]) + return violation.violates.args[0] + default: + assertType(violation.violates) + return violation.violates + } + }) + } + }) + + test('hasValue({ range: [1, 10] }) excludes exact/min/max branches', () => { + const [ok, , violations] = validate.sync(11, hasValue({ range: [1, 10] as const })) + + if (!ok) { + collection(violations).map(violation => { + switch (violation.violates.code) { + case 'number.unsupported-type': + return violation.violates.name + case 'number.range': + assertType(violation.violates.args[0]) + return violation.violates.args[0] + default: + assertType(violation.violates) + return violation.violates + } + }) + } + }) + + test('multipleOf(5) keeps the exact step in violation args', () => { + const [ok, , violations] = validate.sync(12, multipleOf(5)) + + if (!ok) { + collection(violations).map(violation => { + switch (violation.violates.code) { + case 'number.unsupported-type': + return violation.violates.name + case 'number.multiple-of': + assertType<5>(violation.violates.args[0]) + return violation.violates.args[0] + default: + assertType(violation.violates) + return violation.violates + } + }) + } + }) +}) From e0e7ec8334818a81ed96bababa3457c8afcea1e4 Mon Sep 17 00:00:00 2001 From: Kirill Zaytsev Date: Tue, 10 Mar 2026 00:07:05 +0400 Subject: [PATCH 04/15] refactor(assertions): Updated staged assertion pipelines --- README.md | 6 +- docs/en/01-shape-api.md | 7 + docs/en/05-public-api.md | 10 +- docs/en/06-common-recipes.md | 3 +- docs/en/08-violation-code-types.md | 7 +- docs/ru/01-shape-api.md | 7 + docs/ru/05-public-api.md | 10 +- docs/ru/06-common-recipes.md | 3 +- docs/ru/08-violation-code-types.md | 7 +- docs/ru/README.md | 6 +- src/assert.ts | 138 ++++++++++++++++--- src/assertions.ts | 31 ++--- src/combinators.ts | 55 +++++--- src/constraints.ts | 37 ++++- src/index.ts | 57 ++++++-- tests/assertion-violations.test-d.ts | 23 ++-- tests/assertions.test.ts | 36 ----- tests/combinators.test-d.ts | 8 ++ tests/json-schema.test.ts | 199 ++++++++++++++++++--------- tests/match.test.ts | 13 ++ tests/metadata.test.ts | 97 +++++++++++++ tests/validate.test.ts | 24 ++++ tests/validate.tuple.test-d.ts | 34 ++++- types/index.d.ts | 112 +++++++++++++-- 24 files changed, 728 insertions(+), 202 deletions(-) diff --git a/README.md b/README.md index 7af0a8b..55e4a4f 100644 --- a/README.md +++ b/README.md @@ -270,11 +270,15 @@ A practical way to think about the library is: In the current API this usually looks like: -- leaf checks with assertions such as `isString`, `isDefined`, `hasLength`, `oneOf`; +- guard assertions such as `isString`, `isNumber`, `isDefined`, `oneOf(...)` establish the base domain for a value; +- refinement assertions such as `hasLength(...)`, `hasPattern(...)`, `startsWith(...)`, `hasValue(...)`, `multipleOf(...)` validate properties inside that domain; - schema composition with combinators such as `exact`, `optional`, `nullable`, `nullish`, `shape(...)`, `each(...)`; - typed validation through `validate(...)` or `validate.sync(...)`; - narrowing of the original sync variable through `matches.sync(...)`. +Sequential assertion arrays are stage-aware. A tuple like `[isString, hasLength({ min: 3 })]` is valid, while incompatible combinations like `[isNumber, hasLength({ min: 3 })]` are rejected by TypeScript. +Refinement assertions are not meant to be passed to `validate(...)` or `matches.sync(...)` on their own. + `validate(...)` is the default async-first validation entrypoint. `validate.sync(...)` and `matches.sync(...)` are specialized sync APIs and will throw if they encounter async validators or async object-level `shape(...).refine(...)` rules. ## Violations diff --git a/docs/en/01-shape-api.md b/docs/en/01-shape-api.md index 968dd79..2deab34 100644 --- a/docs/en/01-shape-api.md +++ b/docs/en/01-shape-api.md @@ -49,6 +49,8 @@ Each field in the descriptor may contain: - an array of constraints that run sequentially; - another structural validator such as `shape(...)`, `each(...)`, `tuple(...)`, `record(...)`, `union(...)`, or `discriminatedUnion(...)`. +For assertion arrays, sequencing is stage-aware: guard assertions establish the field domain first, and refinement assertions must be compatible with that domain. + That means object validation stays aligned with the rest of the library: - field-level checks reuse the same assertions and combinators; @@ -79,6 +81,11 @@ Runtime behavior: - nested violations are reported on the field path; - unknown keys are allowed by default. +Type-level behavior: + +- `[isString, hasLength({ min: 8 })]` is valid; +- `[isNumber, hasLength({ min: 8 })]` is rejected by TypeScript before runtime. + ## Unknown Keys Shapes have two unknown-key modes: diff --git a/docs/en/05-public-api.md b/docs/en/05-public-api.md index 5852e29..e6c423b 100644 --- a/docs/en/05-public-api.md +++ b/docs/en/05-public-api.md @@ -19,6 +19,8 @@ The root package exports: - `validate` - `validate.sync` - `matches.sync` +- `Guard` +- `Refinement` - `meta` - `describe` - `custom` @@ -33,8 +35,9 @@ Use the root package when you need the main validation API, composed validators, The root package includes: -- low-level assertion construction through `assert(...)` -- built-in assertions such as `isString`, `isNumber`, `isBoolean`, `isNull`, `isEmail`, `hasLength(...)`, `oneOf(...)` +- low-level assertion construction through `assert(...)` and `refine(...)` +- built-in guard assertions such as `isString`, `isNumber`, `isBoolean`, `isNull`, `isEmail`, `oneOf(...)` +- built-in refinement assertions such as `hasLength(...)`, `hasSize(...)`, `hasPattern(...)`, `startsWith(...)`, `hasValue(...)`, `multipleOf(...)` - structural combinators such as `shape(...)`, `each(...)`, `tuple(...)`, `record(...)` - wrapper combinators such as `optional(...)`, `nullable(...)`, `nullish(...)` - branching combinators such as `union(...)` and `discriminatedUnion(...)` @@ -42,6 +45,9 @@ The root package includes: This is the main runtime-facing API surface of the library. +Sequential assertion arrays are stage-aware: a compatible tuple such as `[isString, hasLength({ min: 3 })]` is supported directly, while incompatible combinations are rejected by the type system. +Refinement assertions are staged helpers, so `validate(...)` and `matches.sync(...)` expect them to appear after a compatible guard instead of being passed on their own. + ## Metadata And Introspection The same root package also includes: diff --git a/docs/en/06-common-recipes.md b/docs/en/06-common-recipes.md index 6743370..616436d 100644 --- a/docs/en/06-common-recipes.md +++ b/docs/en/06-common-recipes.md @@ -14,7 +14,7 @@ It is intentionally recipe-oriented. Use it when you already understand the proj Use this quick rule of thumb: - use `@modulify/validator/predicates` when you only need runtime checks and type guards; -- use built-in assertions such as `isString`, `isDefined`, `hasLength(...)`, `oneOf(...)` when you want machine-readable failures; +- use built-in guard/refinement assertions such as `isString`, `isDefined`, `hasLength(...)`, `oneOf(...)` when you want machine-readable failures; - use combinators such as `shape(...)`, `each(...)`, `tuple(...)`, `record(...)`, `union(...)`, `discriminatedUnion(...)` when validation becomes structural; - use `meta(...)` and `describe(...)` when another layer needs stable machine-readable descriptors; - use `toJsonSchema(...)` only when you need an interoperability/export view, not as the source of runtime truth. @@ -44,6 +44,7 @@ Practical pattern: - use `.strict()` for request payloads when unknown keys should be rejected; - keep leaf checks small and composable; +- when you use an assertion array, start with a compatible guard such as `isString` before string refinements like `hasLength(...)`; - use the `validated` tuple item inside the success branch; - use `violations` as structured data for API responses, logs, or UI mapping. diff --git a/docs/en/08-violation-code-types.md b/docs/en/08-violation-code-types.md index fab1988..c1617f0 100644 --- a/docs/en/08-violation-code-types.md +++ b/docs/en/08-violation-code-types.md @@ -64,15 +64,16 @@ The same precision now flows into `validate(...)` for parameterized built-ins. import { collection, hasLength, + isString, validate, } from '@modulify/validator' -const [ok, , violations] = validate.sync('ab', hasLength({ min: 3 })) +const [ok, , violations] = validate.sync('ab', [isString, hasLength({ min: 3 })]) if (!ok) { collection(violations).map(violation => { switch (violation.violates.code) { - case 'length.unsupported-type': + case 'type.string': return violation.violates.name case 'length.min': return violation.violates.args[0] @@ -81,7 +82,7 @@ if (!ok) { } ``` -For calls like `hasLength({ min: 3 })`, impossible branches such as `'length.max'` or `'length.range'` are no longer carried into the violation union. +For staged calls like `[isString, hasLength({ min: 3 })]`, impossible branches such as `'length.max'` or `'length.range'` are no longer carried into the violation union, and the unsupported-type branch stays on the descriptor side instead of leaking into staged validation. ## What The Global Registry Solves diff --git a/docs/ru/01-shape-api.md b/docs/ru/01-shape-api.md index b618265..ed8a31a 100644 --- a/docs/ru/01-shape-api.md +++ b/docs/ru/01-shape-api.md @@ -49,6 +49,8 @@ Shape по-прежнему остаётся validator-ом. Дополните - массив constraint-ов, которые выполняются последовательно; - другой structural validator вроде `shape(...)`, `each(...)`, `tuple(...)`, `record(...)`, `union(...)` или `discriminatedUnion(...)`. +Для массивов assertions эта последовательность теперь stage-aware: сначала guard assertions задают домен поля, а refinement assertions обязаны быть совместимы с этим доменом. + Поэтому object validation остаётся согласованной с остальной библиотекой: - field-level checks переиспользуют те же assertions и combinators; @@ -79,6 +81,11 @@ Runtime behavior: - вложенные violations возвращаются на пути поля; - unknown keys по умолчанию разрешены. +Type-level behavior: + +- `[isString, hasLength({ min: 8 })]` типизируется корректно; +- `[isNumber, hasLength({ min: 8 })]` TypeScript отсекает ещё до runtime. + ## Неизвестные ключи У shape есть два режима работы с неизвестными ключами: diff --git a/docs/ru/05-public-api.md b/docs/ru/05-public-api.md index 4ddc3e3..d976540 100644 --- a/docs/ru/05-public-api.md +++ b/docs/ru/05-public-api.md @@ -19,6 +19,8 @@ Root package экспортирует: - `validate` - `validate.sync` - `matches.sync` +- `Guard` +- `Refinement` - `meta` - `describe` - `custom` @@ -33,8 +35,9 @@ Root package экспортирует: Root package включает: -- низкоуровневое создание assertions через `assert(...)` -- built-in assertions вроде `isString`, `isNumber`, `isBoolean`, `isNull`, `isEmail`, `hasLength(...)`, `oneOf(...)` +- низкоуровневое создание assertions через `assert(...)` и `refine(...)` +- built-in guard assertions вроде `isString`, `isNumber`, `isBoolean`, `isNull`, `isEmail`, `oneOf(...)` +- built-in refinement assertions вроде `hasLength(...)`, `hasSize(...)`, `hasPattern(...)`, `startsWith(...)`, `hasValue(...)`, `multipleOf(...)` - structural combinators вроде `shape(...)`, `each(...)`, `tuple(...)`, `record(...)` - wrappers вроде `optional(...)`, `nullable(...)`, `nullish(...)` - branching combinators вроде `union(...)` и `discriminatedUnion(...)` @@ -42,6 +45,9 @@ Root package включает: Это основной runtime-facing API surface библиотеки. +Последовательные массивы assertions теперь stage-aware: совместимый кортеж вроде `[isString, hasLength({ min: 3 })]` поддерживается напрямую, а несовместимые комбинации отсекаются типовой системой. +Refinement assertions в этой модели являются staged-helper'ами, поэтому `validate(...)` и `matches.sync(...)` ожидают их после совместимого guard-а, а не в одиночку. + ## Метаданные и интроспекция Тот же root package также включает: diff --git a/docs/ru/06-common-recipes.md b/docs/ru/06-common-recipes.md index 0ee8231..a61b17b 100644 --- a/docs/ru/06-common-recipes.md +++ b/docs/ru/06-common-recipes.md @@ -14,7 +14,7 @@ Быстрое правило выбора: - используйте `@modulify/validator/predicates`, когда нужны только runtime checks и type guards; -- используйте built-in assertions вроде `isString`, `isDefined`, `hasLength(...)`, `oneOf(...)`, когда нужны машиночитаемые ошибки; +- используйте built-in guard/refinement assertions вроде `isString`, `isDefined`, `hasLength(...)`, `oneOf(...)`, когда нужны машиночитаемые ошибки; - используйте combinators вроде `shape(...)`, `each(...)`, `tuple(...)`, `record(...)`, `union(...)`, `discriminatedUnion(...)`, когда валидация становится структурной; - используйте `meta(...)` и `describe(...)`, когда другой слой нуждается в стабильных машиночитаемых descriptors; - используйте `toJsonSchema(...)` только тогда, когда нужно представление для interoperability или экспорта, а не источник runtime truth. @@ -44,6 +44,7 @@ const [ok, validated, violations] = validate.sync(input, createUser) - используйте `.strict()` для request payload, если неизвестные ключи должны отклоняться; - держите leaf checks маленькими и хорошо сочетаемыми друг с другом; +- если используете массив assertions, начинайте его с совместимого guard-а вроде `isString` перед строковыми refinement-проверками вроде `hasLength(...)`; - используйте элемент кортежа `validated` внутри успешной ветки; - используйте `violations` как структурированные данные для ответов API, логов или сопоставления с UI. diff --git a/docs/ru/08-violation-code-types.md b/docs/ru/08-violation-code-types.md index a7ab18e..872f497 100644 --- a/docs/ru/08-violation-code-types.md +++ b/docs/ru/08-violation-code-types.md @@ -64,15 +64,16 @@ const lengthDescriptor = describe(hasLength({ min: 3 })) import { collection, hasLength, + isString, validate, } from '@modulify/validator' -const [ok, , violations] = validate.sync('ab', hasLength({ min: 3 })) +const [ok, , violations] = validate.sync('ab', [isString, hasLength({ min: 3 })]) if (!ok) { collection(violations).map(violation => { switch (violation.violates.code) { - case 'length.unsupported-type': + case 'type.string': return violation.violates.name case 'length.min': return violation.violates.args[0] @@ -81,7 +82,7 @@ if (!ok) { } ``` -Для вызовов вроде `hasLength({ min: 3 })` невозможные ветки, например `'length.max'` или `'length.range'`, больше не попадают в union violations. +Для staged-вызовов вроде `[isString, hasLength({ min: 3 })]` невозможные ветки, например `'length.max'` или `'length.range'`, больше не попадают в union violations, а unsupported-type остаётся только на стороне descriptor-а и не протекает в staged-валидацию. ## Зачем нужен глобальный реестр diff --git a/docs/ru/README.md b/docs/ru/README.md index 903a0be..a3d0531 100644 --- a/docs/ru/README.md +++ b/docs/ru/README.md @@ -273,11 +273,15 @@ const node = describe(registration) В текущем API это обычно выглядит так: -- leaf checks через assertions вроде `isString`, `isDefined`, `hasLength`, `oneOf`; +- guard assertions вроде `isString`, `isNumber`, `isDefined`, `oneOf(...)` задают базовый домен значения; +- refinement assertions вроде `hasLength(...)`, `hasPattern(...)`, `startsWith(...)`, `hasValue(...)`, `multipleOf(...)` проверяют свойства уже внутри этого домена; - композиция схем через combinators вроде `exact`, `optional`, `nullable`, `nullish`, `shape(...)`, `each(...)`; - типизированная валидация через `validate(...)` или `validate.sync(...)`; - narrowing исходной переменной в sync-коде через `matches.sync(...)`. +Последовательные массивы assertions теперь stage-aware. Кортеж вроде `[isString, hasLength({ min: 3 })]` типизируется, а несовместимые комбинации вроде `[isNumber, hasLength({ min: 3 })]` TypeScript отсекает. +Refinement assertions не предполагаются для одиночной передачи в `validate(...)` или `matches.sync(...)`. + `validate(...)` — основной async-first entrypoint. `validate.sync(...)` и `matches.sync(...)` остаются специализированными sync API и выбрасывают ошибку, если встречают async validators или async object-level rules из `shape(...).refine(...)`. ## Нарушения diff --git a/src/assert.ts b/src/assert.ts index 064785d..2f21016 100644 --- a/src/assert.ts +++ b/src/assert.ts @@ -1,8 +1,12 @@ import type { Assertion, + AssertionStage, + Constraint, AssertionConstraintSubject, AssertionConstraint, + Guard, Predicate, + Refinement, } from '~types' import { attachConstraintDescriptor } from '@/metadata' @@ -17,15 +21,30 @@ type AssertMeta = { type ResolveAssertionCode = Code extends string ? Code : Name type ResolveAssertionArgs = Args extends readonly unknown[] ? Args : [] +type ConstraintInput = C[number] extends AssertionConstraint + ? Input + : never -export const assert = < +type RuntimeAssertion = Assertion & { + [assertionStageSymbol]?: AssertionStage; + [runRefinementSymbol]?: (value: unknown) => ReturnType; + [checkRefinementSymbol]?: (value: unknown) => boolean; +} + +const assertionStageSymbol = Symbol('modulify.validator.assertion.stage') +const runRefinementSymbol = Symbol('modulify.validator.assertion.runRefinement') +const checkRefinementSymbol = Symbol('modulify.validator.assertion.checkRefinement') + +const createAssertion = < T, + const Stage extends AssertionStage, const Name extends string, const Bail extends boolean, const Code extends string | undefined = undefined, const Args extends readonly unknown[] | undefined = undefined, - const C extends readonly AssertionConstraint[] = [], + const C extends readonly AssertionConstraint[] = [], >( + stage: Stage, predicate: Predicate, meta: AssertMeta & { name: Name; @@ -34,7 +53,8 @@ export const assert = < args?: Args; }, constraints: C = [] as unknown as C -): Assertion, ResolveAssertionArgs, Name> => { +): Assertion, ResolveAssertionArgs, Name, Stage> => { + void stage const assertionCode = (meta.code ?? meta.name) as ResolveAssertionCode const assertionArgs = (meta.args ?? []) as ResolveAssertionArgs const violationSubject = { @@ -48,16 +68,7 @@ export const assert = < code: ResolveAssertionCode; args: ResolveAssertionArgs; } - const assertion = ((value: unknown): ReturnType< - Assertion, ResolveAssertionArgs, Name> - > => { - if (!predicate(value)) { - return { - value, - violates: violationSubject, - } - } - + const runConstraints = (value: T) => { for (const [extract, check, code, ...args] of constraints) { if (!check(extract(value), ...args)) { const constraintSubject = { @@ -75,7 +86,24 @@ export const assert = < } return null - }) as Assertion, ResolveAssertionArgs, Name> + } + const checkConstraints = (value: T) => ( + !constraints.length || constraints.every(([extract, check, , ...args]) => check(extract(value), ...args)) + ) + const assertion = ((value: unknown): ReturnType< + Assertion, ResolveAssertionArgs, Name, Stage> + > => { + if (!predicate(value)) { + return { + value, + violates: violationSubject, + } as ReturnType, ResolveAssertionArgs, Name, Stage>> + } + + return runConstraints(value) as ReturnType< + Assertion, ResolveAssertionArgs, Name, Stage> + > + }) as Assertion, ResolveAssertionArgs, Name, Stage> Object.defineProperties(assertion, { name: { @@ -92,9 +120,19 @@ export const assert = < }, check: { enumerable: true, - value: (value: unknown): value is T => predicate(value) && ( - !constraints.length || constraints.every(([extract, check, , ...args]) => check(extract(value), ...args)) - ), + value: (value: unknown): value is T => predicate(value) && checkConstraints(value), + }, + [assertionStageSymbol]: { + enumerable: false, + value: stage, + }, + [runRefinementSymbol]: { + enumerable: false, + value: (value: unknown) => runConstraints(value as T), + }, + [checkRefinementSymbol]: { + enumerable: false, + value: (value: unknown) => checkConstraints(value as T), }, }) @@ -110,3 +148,69 @@ export const assert = < })), })) } + +export const assert = < + T, + const Name extends string, + const Bail extends boolean, + const Code extends string | undefined = undefined, + const Args extends readonly unknown[] | undefined = undefined, + const C extends readonly AssertionConstraint[] = [], +>( + predicate: Predicate, + meta: AssertMeta & { + name: Name; + bail: Bail; + code?: Code; + args?: Args; + }, + constraints: C = [] as unknown as C +): Guard, ResolveAssertionArgs, Name> => { + return createAssertion('guard', predicate, meta, constraints) as Guard< + T, + C, + ResolveAssertionCode, + ResolveAssertionArgs, + Name + > +} + +export const isRefinementAssertion = (constraint: Constraint): constraint is Assertion => { + return typeof constraint === 'function' && (constraint as RuntimeAssertion)[assertionStageSymbol] === 'refinement' +} + +export const runRefinementAssertion = (constraint: Assertion, value: unknown): ReturnType => { + const runRefinement = (constraint as RuntimeAssertion)[runRefinementSymbol] + + return runRefinement ? runRefinement(value) : constraint(value) +} + +export const checkRefinementAssertion = (constraint: Assertion, value: unknown): boolean => { + const checkRefinement = (constraint as RuntimeAssertion)[checkRefinementSymbol] + + return checkRefinement ? checkRefinement(value) : constraint.check(value) +} + +export const refine = < + const Name extends string, + const Bail extends boolean, + const Code extends string | undefined = undefined, + const Args extends readonly unknown[] | undefined = undefined, + const C extends readonly AssertionConstraint[] = [], +>( + meta: AssertMeta & { + name: Name; + bail: Bail; + code?: Code; + args?: Args; + }, + constraints: C = [] as unknown as C +): Refinement, C, ResolveAssertionCode, ResolveAssertionArgs, Name> => { + return createAssertion('refinement', ((value: unknown): value is ConstraintInput => true), meta, constraints) as Refinement< + ConstraintInput, + C, + ResolveAssertionCode, + ResolveAssertionArgs, + Name + > +} diff --git a/src/assertions.ts b/src/assertions.ts index e4a517b..a32882a 100644 --- a/src/assertions.ts +++ b/src/assertions.ts @@ -1,6 +1,9 @@ import type { AssertionConstraint } from '~types' -import { assert } from './assert' +import { + assert, + refine, +} from './assert' import { endsWith as _endsWith, @@ -34,7 +37,10 @@ import { isSymbol as _isSymbol, } from '@/predicates' -export { assert } +export { + assert, + refine, +} // eslint-disable-next-line @typescript-eslint/no-empty-object-type type Defined = {} | null @@ -204,8 +210,7 @@ export const hasLength = isArray(value) || _isString(value), + return refine( { name: 'hasLength', bail, @@ -221,8 +226,7 @@ export const hasSize = ( const { bail = false } = options const constraints = buildSizeConstraints(options) - return assert( - (value: unknown): value is Map | Set => _isMap(value) || _isSet(value), + return refine( { name: 'hasSize', bail, @@ -239,8 +243,7 @@ export const hasPattern = ( }: { bail?: boolean; } = {} -) => assert( - _isString, +) => refine( { name: 'hasPattern', bail, @@ -261,8 +264,7 @@ export const startsWith = ( }: { bail?: boolean; } = {} -) => assert( - _isString, +) => refine( { name: 'startsWith', bail, @@ -283,8 +285,7 @@ export const endsWith = ( }: { bail?: boolean; } = {} -) => assert( - _isString, +) => refine( { name: 'endsWith', bail, @@ -304,8 +305,7 @@ export const hasValue = const { bail = false } = options const constraints = buildValueConstraints(options) - return assert( - _isNumber, + return refine( { name: 'hasValue', bail, @@ -322,8 +322,7 @@ export const multipleOf = ( }: { bail?: boolean; } = {} -) => assert( - _isNumber, +) => refine( { name: 'multipleOf', bail, diff --git a/src/combinators.ts b/src/combinators.ts index 8a9daee..91fcdfb 100644 --- a/src/combinators.ts +++ b/src/combinators.ts @@ -1,5 +1,7 @@ import type { AsyncObjectShapeRuleDescriptor, + CompatibleConstraints, + CompatibleShapeDescriptor, Constraint, DiscriminatedUnionValidator, EachValidator, @@ -74,6 +76,9 @@ type InferUnion[]> = { }[number] type VariantMap = Record> +type CompatibleConstraintTupleInputs[]> = { + readonly [K in keyof T]: CompatibleConstraints +} type InferDiscriminatedUnion = { [K in keyof T]: InferConstraints @@ -124,7 +129,7 @@ const normalizeAsyncRuleDescriptor = , Accepted>( kind: 'optional' | 'nullable' | 'nullish', accepts: (value: unknown) => value is Accepted, - constraints: C + constraints: CompatibleConstraints ): Validator | Accepted> => attachConstraintDescriptor({ check(value: unknown): value is InferConstraints | Accepted { return accepts(value) || matchesConstraints(value, constraints) @@ -136,7 +141,7 @@ const passthrough = , Accepted>( ): Validation[] { return accepts(value) ? [] - : [validate(value, constraints, path) as Validation] + : [validate(value, constraints as CompatibleConstraints>, path) as Validation] }, }, () => ({ kind, @@ -202,7 +207,7 @@ const partialDescriptor = (descriptor: D): PartialSha const partial = {} as PartialShapeDescriptor keysOf(descriptor).forEach(key => { - partial[key] = optional(descriptor[key]) + partial[key] = optional(descriptor[key] as CompatibleConstraints) }) return partial @@ -334,7 +339,7 @@ const createObjectShape = < unknownKeys, check(value: unknown): value is InferShape { return isRecord(value) - && keys.every(key => matchesConstraints(value[key], descriptor[key])) + && keys.every(key => matchesConstraints(value[key], descriptor[key] as CompatibleConstraints)) && (unknownKeys === 'passthrough' || !hasUnknownKeys(value, descriptor)) && collectShapeRefinementViolations(value as InferShape, [], refinements).length === 0 }, @@ -353,7 +358,7 @@ const createObjectShape = < const validations = keys.reduce[]>((all, key) => [ ...all, - validate(value[key], descriptor[key], [...path, key]) as Validation, + validate(value[key], descriptor[key] as CompatibleConstraints, [...path, key]) as Validation, ], []) if (unknownKeys === 'strict') { @@ -469,7 +474,7 @@ const collectUnionViolations = ( value: unknown, path: PropertyKey[] ) => { - const branchResults = branches.map(branch => validate(value, branch, path)) + const branchResults = branches.map(branch => validate(value, branch as CompatibleConstraints, path)) if (branchResults.some(result => result instanceof Promise)) { return Promise.all(branchResults.map(result => Promise.resolve(result))).then(results => { @@ -480,7 +485,7 @@ const collectUnionViolations = ( return toUnionFailure(branches, value, path, branchResults as Validation[]) } -export const each = >(constraints: C): EachValidator => attachConstraintDescriptor({ +export const each = >(constraints: CompatibleConstraints): EachValidator => attachConstraintDescriptor({ check(value: unknown): value is InferConstraints[] { return isArray(value) && value.every(item => matchesConstraints(item, constraints)) }, @@ -490,7 +495,7 @@ export const each = >(constraints: C): Eac path: PropertyKey[] ): Validation[] { return isArray(value) - ? value.map((item, index) => validate(item, constraints, [...path, index])) as Validation[] + ? value.map((item, index) => validate(item, constraints as CompatibleConstraints, [...path, index])) as Validation[] : [[{ value, path, @@ -502,7 +507,9 @@ export const each = >(constraints: C): Eac item: describeConstraints(constraints), })) -export const tuple = []>(constraints: T): TupleValidator => attachConstraintDescriptor({ +export const tuple = []>( + constraints: CompatibleConstraintTupleInputs +): TupleValidator => attachConstraintDescriptor({ check(value: unknown): value is InferTuple { return isArray(value) && value.length === constraints.length @@ -536,7 +543,9 @@ export const tuple = []>(constrai items: constraints.map(constraint => describeConstraints(constraint)), })) -export const union = []>(constraints: T): UnionValidator => attachConstraintDescriptor({ +export const union = []>( + constraints: CompatibleConstraintTupleInputs +): UnionValidator => attachConstraintDescriptor({ check(value: unknown): value is InferUnion { return constraints.some(constraint => matchesConstraints(value, constraint)) }, @@ -555,7 +564,7 @@ export const union = []>(constrai export const discriminatedUnion = < const K extends PropertyKey, const T extends VariantMap, ->(key: K, variants: T): DiscriminatedUnionValidator => attachConstraintDescriptor({ +>(key: K, variants: CompatibleShapeDescriptor): DiscriminatedUnionValidator => attachConstraintDescriptor({ check(value: unknown): value is InferDiscriminatedUnion { if (!isRecord(value)) { return false @@ -564,7 +573,7 @@ export const discriminatedUnion = < const discriminator = value[key] return Object.prototype.hasOwnProperty.call(variants, discriminator) - && matchesConstraints(value, variants[discriminator as keyof T]) + && matchesConstraints(value, variants[discriminator as keyof T] as CompatibleConstraints) }, run( validate: F, @@ -594,7 +603,11 @@ export const discriminatedUnion = < }]] as Validation[] } - return [validate(value, variants[discriminator as keyof T], path) as Validation] + return [validate( + value, + variants[discriminator as keyof T] as CompatibleConstraints, + path + ) as Validation] }, } as DiscriminatedUnionValidator, () => ({ kind: 'discriminatedUnion', @@ -602,7 +615,7 @@ export const discriminatedUnion = < variants: describeConstraintMap(variants), })) -export const record = >(constraints: C): RecordValidator => attachConstraintDescriptor({ +export const record = >(constraints: CompatibleConstraints): RecordValidator => attachConstraintDescriptor({ check(value: unknown): value is Record> { return isRecord(value) && Object.values(value).every(item => matchesConstraints(item, constraints)) }, @@ -619,14 +632,18 @@ export const record = >(constraints: C): R }]] as Validation[] } - return Object.keys(value).map(key => validate(value[key], constraints, [...path, key]) as Validation) + return Object.keys(value).map(key => validate( + value[key], + constraints as CompatibleConstraints, + [...path, key] + ) as Validation) }, } as RecordValidator, () => ({ kind: 'record', values: describeConstraints(constraints), })) -export const shape = (descriptor: D): ObjectShape => { +export const shape = (descriptor: CompatibleShapeDescriptor): ObjectShape => { return createObjectShape(descriptor, 'passthrough', [], []) } @@ -637,19 +654,19 @@ export const exact = (value: T) => assert(isExact(value), { args: [value], }) -export const optional = >(constraints: C): OptionalValidator => passthrough( +export const optional = >(constraints: CompatibleConstraints): OptionalValidator => passthrough( 'optional', isUndefined, constraints ) as OptionalValidator -export const nullable = >(constraints: C): NullableValidator => passthrough( +export const nullable = >(constraints: CompatibleConstraints): NullableValidator => passthrough( 'nullable', isNull, constraints ) as NullableValidator -export const nullish = >(constraints: C): NullishValidator => passthrough( +export const nullish = >(constraints: CompatibleConstraints): NullishValidator => passthrough( 'nullish', (value: unknown): value is null | undefined => isNull(value) || isUndefined(value), constraints diff --git a/src/constraints.ts b/src/constraints.ts index 2c189b8..6ee2afc 100644 --- a/src/constraints.ts +++ b/src/constraints.ts @@ -1,10 +1,17 @@ import type { + Assertion, + CompatibleConstraints, Constraint, InferConstraints, MaybeMany, Validator, } from '~types' +import { + checkRefinementAssertion, + isRefinementAssertion, +} from '@/assert' + export const isValidator = (constraint: Constraint): constraint is Validator => 'run' in constraint export function arrayify(value: MaybeMany): T[] { @@ -15,7 +22,33 @@ export function arrayify(value: MaybeMany): T[] { export function matchesConstraints>( value: unknown, - constraints: C + constraints: CompatibleConstraints ): value is InferConstraints { - return arrayify(constraints).every(constraint => constraint.check(value)) + let establishedDomain = false + + return arrayify(constraints).every(constraint => { + if (isValidator(constraint)) { + const matched = constraint.check(value) + + if (matched) { + establishedDomain = true + } + + return matched + } + + if (!establishedDomain && isRefinementAssertion(constraint)) { + return false + } + + const matched = establishedDomain && isRefinementAssertion(constraint) + ? checkRefinementAssertion(constraint as Assertion, value) + : constraint.check(value) + + if (matched) { + establishedDomain = true + } + + return matched + }) } diff --git a/src/index.ts b/src/index.ts index deeb193..5f0c5c3 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,4 +1,6 @@ import type { + Assertion, + CompatibleConstraints, Constraint, InferConstraints, InferViolations, @@ -22,9 +24,13 @@ export { export type { AllOfConstraintDescriptor, AssertionDescriptor, + AssertionStage, AssertionConstraintDescriptor, AsyncObjectShapeRuleDescriptor, BaseConstraintDescriptor, + CompatibleConstraints, + CompatibleConstraintTuple, + CompatibleShapeDescriptor, Constraint, ConstraintDescriptor, ConstraintMetadata, @@ -39,6 +45,7 @@ export type { DiscriminatedUnionValidator, EachValidator, FieldsMatchObjectShapeRuleDescriptor, + Guard, InferShape, InferConstraint, InferConstraints, @@ -57,6 +64,7 @@ export type { ObjectShapeRuleDescriptorBase, OptionalValidator, RecordValidator, + Refinement, RecordConstraintDescriptor, ShapeDescriptor, ShapeFieldSelector, @@ -89,6 +97,10 @@ export type { WrapperConstraintDescriptor, } from '~types' +import { + isRefinementAssertion, + runRefinementAssertion, +} from '@/assert' import { arrayify, isValidator, @@ -96,19 +108,29 @@ import { const collectViolations = async ( value: unknown, - constraints: MaybeMany, + constraints: CompatibleConstraints>, path: PropertyKey[] = [] ): Promise => { const validations: Promise[] = [] + let establishedAssertionDomain = false for (const c of arrayify(constraints)) { if (isValidator(c)) { + establishedAssertionDomain = false validations.push(...c.run(collectViolations, value, path).map(v => v instanceof Promise ? v : Promise.resolve(v))) continue } - const v = c(value) + if (!establishedAssertionDomain && isRefinementAssertion(c)) { + throw new Error(`Refinement ${String(c.name)} requires a compatible preceding guard`) + } + + const v = establishedAssertionDomain && isRefinementAssertion(c) + ? runRefinementAssertion(c as Assertion, value) + : c(value) + if (v instanceof Promise) { + establishedAssertionDomain = false if (c.bail) { const awaited = await v if (awaited) { @@ -119,11 +141,14 @@ const collectViolations = async ( validations.push(v.then(v => v ? [Object.assign(v, { path: [...path] })] : [])) } } else if (v) { + establishedAssertionDomain = false validations.push(Promise.resolve([Object.assign(v, { path: [...path] })])) if (c.bail) { break } + } else { + establishedAssertionDomain = true } } @@ -134,24 +159,36 @@ collectViolations.sync = false as const const collectViolationsSync = ( value: unknown, - constraints: MaybeMany, + constraints: CompatibleConstraints>, path: PropertyKey[] = [] ): Violation[] => { const violations: Recursive[] = [] + let establishedAssertionDomain = false for (const c of arrayify(constraints)) { if (isValidator(c)) { + establishedAssertionDomain = false violations.push(...c.run(collectViolationsSync, value, path)) continue } - const v = c(value) + if (!establishedAssertionDomain && isRefinementAssertion(c)) { + throw new Error(`Refinement ${String(c.name)} requires a compatible preceding guard`) + } + + const v = establishedAssertionDomain && isRefinementAssertion(c) + ? runRefinementAssertion(c as Assertion, value) + : c(value) + if (v instanceof Promise) { throw new Error('Found asynchronous validator ' + String(c.name)) } else if (v) { + establishedAssertionDomain = false violations.push(Object.assign(v, { path: [...path] })) if (c.bail) break + } else { + establishedAssertionDomain = true } } @@ -200,17 +237,17 @@ async function settle (value: unknown, path: PropertyKey[], validations: Promise } export const matches = { - sync>(value: unknown, constraints: C): value is InferConstraints { - return collectViolationsSync(value, constraints).length === 0 + sync>(value: unknown, constraints: CompatibleConstraints): value is InferConstraints { + return collectViolationsSync(value, constraints as CompatibleConstraints>).length === 0 }, } export const validate = Object.assign( async >( value: unknown, - constraints: C + constraints: CompatibleConstraints ): Promise, InferViolations>> => { - const violations = await collectViolations(value, constraints) + const violations = await collectViolations(value, constraints as CompatibleConstraints>) return toResult, InferViolations>( value, @@ -220,9 +257,9 @@ export const validate = Object.assign( { sync>( value: unknown, - constraints: C + constraints: CompatibleConstraints ): ValidationResult, InferViolations> { - const violations = collectViolationsSync(value, constraints) + const violations = collectViolationsSync(value, constraints as CompatibleConstraints>) return toResult, InferViolations>( value, diff --git a/tests/assertion-violations.test-d.ts b/tests/assertion-violations.test-d.ts index 1f57077..5b623d8 100644 --- a/tests/assertion-violations.test-d.ts +++ b/tests/assertion-violations.test-d.ts @@ -9,18 +9,21 @@ import { hasLength, hasSize, hasValue, + isNumber, + isSet, + isString, multipleOf, validate, } from '@/index' describe('parameterized assertion violation types', () => { test('hasLength({ min: 3 }) keeps only supported violation codes', () => { - const [ok, , violations] = validate.sync('ab', hasLength({ min: 3 })) + const [ok, , violations] = validate.sync('ab', [isString, hasLength({ min: 3 })]) if (!ok) { collection(violations).map(violation => { switch (violation.violates.code) { - case 'length.unsupported-type': + case 'type.string': assertType<[]>(violation.violates.args) return violation.violates.name case 'length.min': @@ -35,12 +38,12 @@ describe('parameterized assertion violation types', () => { }) test('hasLength({ min: 3, max: 5 }) keeps only the enabled range branches', () => { - const [ok, , violations] = validate.sync('abcdef', hasLength({ min: 3, max: 5 })) + const [ok, , violations] = validate.sync('abcdef', [isString, hasLength({ min: 3, max: 5 })]) if (!ok) { collection(violations).map(violation => { switch (violation.violates.code) { - case 'length.unsupported-type': + case 'type.string': return violation.violates.name case 'length.min': return violation.violates.args[0] @@ -55,12 +58,12 @@ describe('parameterized assertion violation types', () => { }) test('hasSize({ exact: 2 }) excludes unrelated size branches', () => { - const [ok, , violations] = validate.sync(new Set([1]), hasSize({ exact: 2 })) + const [ok, , violations] = validate.sync(new Set([1]), [isSet, hasSize({ exact: 2 })]) if (!ok) { collection(violations).map(violation => { switch (violation.violates.code) { - case 'size.unsupported-type': + case 'type.set': return violation.violates.name case 'size.exact': assertType<2>(violation.violates.args[0]) @@ -74,12 +77,12 @@ describe('parameterized assertion violation types', () => { }) test('hasValue({ range: [1, 10] }) excludes exact/min/max branches', () => { - const [ok, , violations] = validate.sync(11, hasValue({ range: [1, 10] as const })) + const [ok, , violations] = validate.sync(11, [isNumber, hasValue({ range: [1, 10] as const })]) if (!ok) { collection(violations).map(violation => { switch (violation.violates.code) { - case 'number.unsupported-type': + case 'type.number': return violation.violates.name case 'number.range': assertType(violation.violates.args[0]) @@ -93,12 +96,12 @@ describe('parameterized assertion violation types', () => { }) test('multipleOf(5) keeps the exact step in violation args', () => { - const [ok, , violations] = validate.sync(12, multipleOf(5)) + const [ok, , violations] = validate.sync(12, [isNumber, multipleOf(5)]) if (!ok) { collection(violations).map(violation => { switch (violation.violates.code) { - case 'number.unsupported-type': + case 'type.number': return violation.violates.name case 'number.multiple-of': assertType<5>(violation.violates.args[0]) diff --git a/tests/assertions.test.ts b/tests/assertions.test.ts index 77c620a..207b141 100644 --- a/tests/assertions.test.ts +++ b/tests/assertions.test.ts @@ -82,18 +82,6 @@ describe('hasLength', () => { }) }) - test.each([ - { options: { exact: 3 }, value: {} }, - { options: { exact: 3 }, value: null }, - { options: { max: 5 }, value: undefined }, - { options: { min: 3 }, value: new Date() }, - { options: { max: 5, min: 3 }, value: new Blob() }, - ])('unsupported #%#', ({ options, value }) => { - expect(hasLength(options)(value)).toEqual({ - value, - violates: assertionSubject('hasLength', 'length.unsupported-type'), - }) - }) }) describe('oneOf', () => { @@ -158,10 +146,6 @@ describe('string assertions', () => { value: 'Nick', violates: assertionSubject('hasPattern', 'string.pattern', [/^[a-z]+$/]), }) - expect(hasPattern(/^[a-z]+$/)(1)).toEqual({ - value: 1, - violates: assertionSubject('hasPattern', 'string.unsupported-type'), - }) }) test('startsWith', () => { @@ -171,10 +155,6 @@ describe('string assertions', () => { value: 'suffix', violates: assertionSubject('startsWith', 'string.starts-with', ['pre']), }) - expect(startsWith('pre')(false)).toEqual({ - value: false, - violates: assertionSubject('startsWith', 'string.unsupported-type'), - }) }) test('endsWith', () => { @@ -184,10 +164,6 @@ describe('string assertions', () => { value: 'index.js', violates: assertionSubject('endsWith', 'string.ends-with', ['.ts']), }) - expect(endsWith('.ts')(null)).toEqual({ - value: null, - violates: assertionSubject('endsWith', 'string.unsupported-type'), - }) }) }) @@ -205,10 +181,6 @@ describe('numeric and size assertions', () => { value: invalidSet, violates: assertionSubject('hasSize', 'size.max', [1]), }) - expect(hasSize({ min: 1 })([])).toEqual({ - value: [], - violates: assertionSubject('hasSize', 'size.unsupported-type'), - }) }) test('hasValue', () => { @@ -223,10 +195,6 @@ describe('numeric and size assertions', () => { value: 4, violates: assertionSubject('hasValue', 'number.max', [3]), }) - expect(hasValue({ max: 3 })('2')).toEqual({ - value: '2', - violates: assertionSubject('hasValue', 'number.unsupported-type'), - }) }) test('multipleOf', () => { @@ -242,10 +210,6 @@ describe('numeric and size assertions', () => { value: 10, violates: assertionSubject('multipleOf', 'number.multiple-of', [0]), }) - expect(multipleOf(5)('10')).toEqual({ - value: '10', - violates: assertionSubject('multipleOf', 'number.unsupported-type'), - }) }) }) diff --git a/tests/combinators.test-d.ts b/tests/combinators.test-d.ts index db2af45..f5cf8be 100644 --- a/tests/combinators.test-d.ts +++ b/tests/combinators.test-d.ts @@ -25,6 +25,7 @@ import { union, } from '@/combinators' import { + hasLength, isNumber, isString, matches, @@ -33,6 +34,13 @@ import { } from '@/index' describe('combinator types', () => { + test('shape rejects incompatible staged assertion tuples in fields', () => { + shape({ + // @ts-expect-error incompatible clarifying assertion + name: [isNumber, hasLength({ min: 3 })] as const, + }) + }) + test('exact preserves literal types', () => { const result = validate.sync('admin', exact('admin')) diff --git a/tests/json-schema.test.ts b/tests/json-schema.test.ts index cfa809c..569b189 100644 --- a/tests/json-schema.test.ts +++ b/tests/json-schema.test.ts @@ -265,7 +265,6 @@ describe('toJsonSchema', () => { [isMap, 'Map instances do not have a stable JSON Schema representation'], [isNaN, 'NaN cannot be represented in JSON Schema'], [isSet, 'Set instances do not have a stable JSON Schema representation'], - [hasSize({ min: 1 }), 'Map and Set sizes do not have a stable JSON Schema representation'], ] as const)('throws in strict mode for unsupported built-in assertion %s', (assertion, reason) => { expect(() => toJsonSchema(shape({ value: assertion, @@ -274,6 +273,12 @@ describe('toJsonSchema', () => { ) }) + test('throws in strict mode for unsupported built-in refinement export', () => { + expect(() => toJsonSchema(hasSize({ min: 1 }), { mode: 'strict' })).toThrowError( + 'Cannot export assertion at : Map and Set sizes do not have a stable JSON Schema representation' + ) + }) + test('exports remaining supported assertion variants, metadata primitives and optionality inference', () => { const hidden = Symbol('hidden') @@ -295,17 +300,17 @@ describe('toJsonSchema', () => { optionalUnion: union([exact(undefined), isString]), optionalAllOf: [optional(isString), exact(undefined)], optionalEnum: oneOf([undefined, 'draft']), - exactLength: hasLength({ exact: 3 }), - maxLength: hasLength({ max: 5 }), - rangeLength: hasLength({ range: [1, 3] }), - valueExact: hasValue({ exact: 3 }), - valueMin: hasValue({ min: 1 }), - valueMax: hasValue({ max: 10 }), - valueRange: hasValue({ range: [1, 10] }), - valueMultipleOf: multipleOf(5), - textPattern: hasPattern(/^[a-z]+$/), - textPrefix: startsWith('pre'), - textSuffix: endsWith('.ts'), + exactLength: [isString, hasLength({ exact: 3 })], + maxLength: [isString, hasLength({ max: 5 })], + rangeLength: [isString, hasLength({ range: [1, 3] })], + valueExact: [isNumber, hasValue({ exact: 3 })], + valueMin: [isNumber, hasValue({ min: 1 })], + valueMax: [isNumber, hasValue({ max: 10 })], + valueRange: [isNumber, hasValue({ range: [1, 10] })], + valueMultipleOf: [isNumber, multipleOf(5)], + textPattern: [isString, hasPattern(/^[a-z]+$/)], + textPrefix: [isString, startsWith('pre')], + textSuffix: [isString, endsWith('.ts')], [hidden]: isString, }))).toEqual({ type: 'object', @@ -339,68 +344,112 @@ describe('toJsonSchema', () => { }, optionalEnum: {}, exactLength: { - anyOf: [{ + allOf: [{ type: 'string', - minLength: 3, - maxLength: 3, }, { - type: 'array', - minItems: 3, - maxItems: 3, + anyOf: [{ + type: 'string', + minLength: 3, + maxLength: 3, + }, { + type: 'array', + minItems: 3, + maxItems: 3, + }], }], }, maxLength: { - anyOf: [{ + allOf: [{ type: 'string', - maxLength: 5, }, { - type: 'array', - maxItems: 5, + anyOf: [{ + type: 'string', + maxLength: 5, + }, { + type: 'array', + maxItems: 5, + }], }], }, rangeLength: { - anyOf: [{ + allOf: [{ type: 'string', - minLength: 1, - maxLength: 3, }, { - type: 'array', - minItems: 1, - maxItems: 3, + anyOf: [{ + type: 'string', + minLength: 1, + maxLength: 3, + }, { + type: 'array', + minItems: 1, + maxItems: 3, + }], }], }, valueExact: { - type: 'number', - const: 3, + allOf: [{ + type: 'number', + }, { + type: 'number', + const: 3, + }], }, valueMin: { - type: 'number', - minimum: 1, + allOf: [{ + type: 'number', + }, { + type: 'number', + minimum: 1, + }], }, valueMax: { - type: 'number', - maximum: 10, + allOf: [{ + type: 'number', + }, { + type: 'number', + maximum: 10, + }], }, valueRange: { - type: 'number', - minimum: 1, - maximum: 10, + allOf: [{ + type: 'number', + }, { + type: 'number', + minimum: 1, + maximum: 10, + }], }, valueMultipleOf: { - type: 'number', - multipleOf: 5, + allOf: [{ + type: 'number', + }, { + type: 'number', + multipleOf: 5, + }], }, textPattern: { - type: 'string', - pattern: '^[a-z]+$', + allOf: [{ + type: 'string', + }, { + type: 'string', + pattern: '^[a-z]+$', + }], }, textPrefix: { - type: 'string', - pattern: '^pre', + allOf: [{ + type: 'string', + }, { + type: 'string', + pattern: '^pre', + }], }, textSuffix: { - type: 'string', - pattern: '\\.ts$', + allOf: [{ + type: 'string', + }, { + type: 'string', + pattern: '\\.ts$', + }], }, }, required: [ @@ -534,16 +583,16 @@ describe('toJsonSchema', () => { emptyMultipleOfDescriptor, badExact: exact({ role: 'admin' }), badEnum: oneOf([{ role: 'admin' }] as never), - invalidExactLength: hasLength({ exact: -1 as never }), - invalidMinLength: hasLength({ min: -1 as never }), - invalidMaxLength: hasLength({ max: -1 as never }), - invalidRangeLength: hasLength({ range: [1, -1] as never }), - invalidValueExact: hasValue({ exact: Number.POSITIVE_INFINITY as never }), - invalidValueMin: hasValue({ min: Number.POSITIVE_INFINITY as never }), - invalidValueMax: hasValue({ max: Number.POSITIVE_INFINITY as never }), - invalidValueRange: hasValue({ range: [1, Number.NaN] as never }), - invalidMultipleOf: multipleOf(0), - invalidPattern: hasPattern(/ok/i), + invalidExactLength: [isString, hasLength({ exact: -1 as never })], + invalidMinLength: [isString, hasLength({ min: -1 as never })], + invalidMaxLength: [isString, hasLength({ max: -1 as never })], + invalidRangeLength: [isString, hasLength({ range: [1, -1] as never })], + invalidValueExact: [isNumber, hasValue({ exact: Number.POSITIVE_INFINITY as never })], + invalidValueMin: [isNumber, hasValue({ min: Number.POSITIVE_INFINITY as never })], + invalidValueMax: [isNumber, hasValue({ max: Number.POSITIVE_INFINITY as never })], + invalidValueRange: [isNumber, hasValue({ range: [1, Number.NaN] as never })], + invalidMultipleOf: [isNumber, multipleOf(0)], + invalidPattern: [isString, hasPattern(/ok/i)], bigInt: isBigInt, blob: isBlob, date: isDate, @@ -574,16 +623,36 @@ describe('toJsonSchema', () => { emptyMultipleOfDescriptor: {}, badExact: {}, badEnum: {}, - invalidExactLength: {}, - invalidMinLength: {}, - invalidMaxLength: {}, - invalidRangeLength: {}, - invalidValueExact: {}, - invalidValueMin: {}, - invalidValueMax: {}, - invalidValueRange: {}, - invalidMultipleOf: {}, - invalidPattern: {}, + invalidExactLength: { + allOf: [{ type: 'string' }, {}], + }, + invalidMinLength: { + allOf: [{ type: 'string' }, {}], + }, + invalidMaxLength: { + allOf: [{ type: 'string' }, {}], + }, + invalidRangeLength: { + allOf: [{ type: 'string' }, {}], + }, + invalidValueExact: { + allOf: [{ type: 'number' }, {}], + }, + invalidValueMin: { + allOf: [{ type: 'number' }, {}], + }, + invalidValueMax: { + allOf: [{ type: 'number' }, {}], + }, + invalidValueRange: { + allOf: [{ type: 'number' }, {}], + }, + invalidMultipleOf: { + allOf: [{ type: 'number' }, {}], + }, + invalidPattern: { + allOf: [{ type: 'string' }, {}], + }, bigInt: {}, blob: {}, date: {}, diff --git a/tests/match.test.ts b/tests/match.test.ts index b2ff38b..af8044c 100644 --- a/tests/match.test.ts +++ b/tests/match.test.ts @@ -5,10 +5,12 @@ import { } from 'vitest' import { + hasLength, isDefined, isString, matches, } from '@/index' +import { matchesConstraints } from '@/constraints' const expectString = (value: T) => value @@ -29,4 +31,15 @@ describe('matches.sync', () => { expect(matches.sync(value, [isDefined, isString])).toBe(false) }) + + test('returns false for dynamic refinement sequences without a preceding guard', () => { + const staged = [hasLength({ min: 2 })] + + expect(matchesConstraints('nickname', staged as never)).toBe(false) + }) + + test('uses staged refinement checks after a compatible guard succeeds', () => { + expect(matchesConstraints('neo', [isString, hasLength({ min: 2 })])).toBe(true) + expect(matchesConstraints('n', [isString, hasLength({ min: 2 })])).toBe(false) + }) }) diff --git a/tests/metadata.test.ts b/tests/metadata.test.ts index b58bda4..36dede0 100644 --- a/tests/metadata.test.ts +++ b/tests/metadata.test.ts @@ -6,6 +6,8 @@ import { test, } from 'vitest' +import type { Assertion } from '~types' + import { assert, custom, @@ -18,11 +20,16 @@ import { meta, optional, record, + refine, shape, tuple, union, validate, } from '@/index' +import { + checkRefinementAssertion, + runRefinementAssertion, +} from '@/assert' suite('meta', () => { test('attaches metadata without changing validation semantics or mutating the source constraint', () => { @@ -307,3 +314,93 @@ suite('describe', () => { }) }) }) + +suite('refinement helpers', () => { + test('checks staged refinements through the internal fast-path', () => { + const minLength = refine({ + name: 'minLength', + bail: true, + }, [ + [(value: string) => value.length, (length: number, min: number) => length >= min, 'length.min', 2], + ] as const) + + expect(checkRefinementAssertion(minLength, 'abcd')).toBe(true) + expect(checkRefinementAssertion(minLength, 'a')).toBe(false) + }) + + test('falls back to assertion.check when no internal refinement helper is attached', () => { + const fallback = (((value: unknown) => value === 'ok' ? null : { + value, + violates: { + kind: 'assertion' as const, + name: 'fallback', + code: 'fallback', + args: [], + }, + }) as unknown) as Assertion + + Object.defineProperties(fallback, { + name: { + configurable: true, + value: 'fallback', + }, + bail: { + enumerable: true, + value: true, + }, + constraints: { + enumerable: true, + value: [], + }, + check: { + enumerable: true, + value: (value: unknown): value is 'ok' => value === 'ok', + }, + }) + + expect(checkRefinementAssertion(fallback, 'ok')).toBe(true) + expect(checkRefinementAssertion(fallback, 'nope')).toBe(false) + }) + + test('falls back to direct assertion execution when no internal refinement runner is attached', () => { + const fallback = (((value: unknown) => value === 'ok' ? null : { + value, + violates: { + kind: 'assertion' as const, + name: 'fallback', + code: 'fallback', + args: [], + }, + }) as unknown) as Assertion + + Object.defineProperties(fallback, { + name: { + configurable: true, + value: 'fallback', + }, + bail: { + enumerable: true, + value: true, + }, + constraints: { + enumerable: true, + value: [], + }, + check: { + enumerable: true, + value: (value: unknown): value is 'ok' => value === 'ok', + }, + }) + + expect(runRefinementAssertion(fallback, 'ok')).toBeNull() + expect(runRefinementAssertion(fallback, 'nope')).toEqual({ + value: 'nope', + violates: { + kind: 'assertion', + name: 'fallback', + code: 'fallback', + args: [], + }, + }) + }) +}) diff --git a/tests/validate.test.ts b/tests/validate.test.ts index 5738f1f..022ba5a 100644 --- a/tests/validate.test.ts +++ b/tests/validate.test.ts @@ -204,6 +204,27 @@ describe('validate', () => { }])) }) + test('throws for dynamic refinement sequences without a preceding guard', () => { + const staged = [hasLength({ min: 2 })] as Assertion[] + + expect(() => validate.sync('abc', staged)).toThrow('Refinement hasLength requires a compatible preceding guard') + }) + + test('evaluates refinements in sync validation after a compatible guard', () => { + expect(validate.sync('neo', [isString, hasLength({ min: 2 })])).toEqual(valid('neo')) + expect(validate.sync('n', [isString, hasLength({ min: 2 })])).toEqual(invalid('n', [{ + value: 'n', + path: [], + violates: assertionSubject('hasLength', 'length.min', [2]), + }])) + }) + + test('rejects async validation for dynamic refinement sequences without a preceding guard', async () => { + const staged = [hasLength({ min: 2 })] as Assertion[] + + await expect(validate('abc', staged)).rejects.toThrow('Refinement hasLength requires a compatible preceding guard') + }) + test('awaits async assertions with bail and stops further checks on failure', async () => { expect(await validate('', [ createAsyncAssertion('asyncBail', async (value: unknown) => ({ @@ -224,6 +245,7 @@ describe('validate', () => { value, violates: assertionSubject('asyncNoBail', 'asyncNoBail'), })), + isString, hasLength({ min: 2 }), ])).toEqual(invalid('', [{ value: '', @@ -239,6 +261,7 @@ describe('validate', () => { test('continues after async bail assertions that succeed', async () => { expect(await validate('', [ createAsyncAssertion('asyncBailPass', async () => null, true), + isString, hasLength({ min: 2 }), ])).toEqual(invalid('', [{ value: '', @@ -250,6 +273,7 @@ describe('validate', () => { test('ignores async assertions without bail when they return null', async () => { expect(await validate('', [ createAsyncAssertion('asyncNoBailPass', async () => null), + isString, hasLength({ min: 2 }), ])).toEqual(invalid('', [{ value: '', diff --git a/tests/validate.tuple.test-d.ts b/tests/validate.tuple.test-d.ts index 3cf5317..1627002 100644 --- a/tests/validate.tuple.test-d.ts +++ b/tests/validate.tuple.test-d.ts @@ -1,7 +1,9 @@ import type { + Guard, + Refinement, ValidationResult, Violation, -} from '@/index' +} from '~types' import { describe, @@ -13,6 +15,7 @@ import { collection, each, hasLength, + isNumber, shape, isDefined, isString, @@ -20,6 +23,11 @@ import { } from '@/index' describe('validate tuple types', () => { + test('built-in assertions expose staged public types', () => { + assertType>(isString) + assertType>(hasLength({ min: 3 })) + }) + const profile = shape({ name: [isDefined, isString], tags: each(isString), @@ -120,11 +128,33 @@ describe('validate tuple types', () => { assertType(violation.violates.args) return String(violation.violates.args[0]) default: - return violation.violates.code + assertType(violation.violates) + return violation.violates } }) assertType(messages) } }) + + test('accepts staged tuples after a compatible guard prefix', () => { + const result = validate.sync('abcd', [isDefined, isString, hasLength({ min: 3 })]) + + assertType>(result) + }) + + test('rejects incompatible clarifying assertions in tuples', () => { + // @ts-expect-error incompatible clarifying assertion + validate.sync(12, [isNumber, hasLength({ min: 3 })] as const) + }) + + test('rejects tuples that start with a clarifying assertion', () => { + // @ts-expect-error clarifying assertions require an established domain + validate.sync('abc', [hasLength({ min: 3 })] as const) + }) + + test('rejects standalone clarifying assertions in validation APIs', () => { + // @ts-expect-error clarifying assertions are not standalone validation constraints + validate.sync('abc', hasLength({ min: 3 })) + }) }) diff --git a/types/index.d.ts b/types/index.d.ts index 962a47c..4930e00 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -27,6 +27,9 @@ export type Checker = (val /** Extracts a derived value from the original input before a checker runs. */ export type Extractor = (value: T) => V +/** Public stage marker for assertion pipelines. */ +export type AssertionStage = 'guard' | 'refinement' + /** Origin layer that produced a violation. */ export type ViolationKind = 'assertion' | 'validator' | 'runtime' @@ -411,6 +414,8 @@ export type DescribeAssertionConstraintTuple> +declare const assertionStageBrand: unique symbol + /** * Leaf-level validator that checks a single value and either succeeds with `null` * or returns a structured violation. @@ -423,20 +428,42 @@ export type Assertion< Code extends string = string, A extends readonly unknown[] = readonly unknown[], Name extends string = string, + Stage extends AssertionStage = AssertionStage, > = ((value: unknown) => MaybePromise + Stage extends 'refinement' + ? AssertionConstraintSubject + : { + kind: 'assertion'; + name: Name; + code: Code; + args: A; + } | AssertionConstraintSubject >, 'path'> | null>) & { readonly name: Name; readonly bail: boolean; readonly constraints: C; readonly check: Predicate; + readonly [assertionStageBrand]: Stage; } +/** Assertion stage that establishes the base domain for subsequent checks. */ +export type Guard< + T = unknown, + C extends readonly AssertionConstraint[] = readonly AssertionConstraint[], + Code extends string = string, + A extends readonly unknown[] = readonly unknown[], + Name extends string = string, +> = Assertion + +/** Assertion stage that validates properties inside an already established domain. */ +export type Refinement< + T = unknown, + C extends readonly AssertionConstraint[] = readonly AssertionConstraint[], + Code extends string = string, + A extends readonly unknown[] = readonly unknown[], + Name extends string = string, +> = Assertion + /** * Any reusable validation unit: either a leaf `Assertion` or a composed `Validator`. * @@ -445,9 +472,65 @@ export type Assertion< */ export type Constraint = Assertion | Validator +type ConstraintSequenceState< + Value, + Ready extends boolean, +> = { + value: Value; + ready: Ready; +} + +type ApplyConstraintStep< + State extends ConstraintSequenceState, + Step extends Constraint, +> = + [Step] extends [Refinement] + ? State['ready'] extends true + ? [State['value']] extends [Input] + ? ConstraintSequenceState + : never + : never + : Step extends Constraint + ? ConstraintSequenceState + : never + +type CompatibleConstraintTupleState< + T extends readonly Constraint[], + State extends ConstraintSequenceState = ConstraintSequenceState, +> = + T extends readonly [infer First extends Constraint, ...infer Rest extends readonly Constraint[]] + ? ApplyConstraintStep extends infer Next + ? [Next] extends [never] + ? never + : Next extends ConstraintSequenceState + ? CompatibleConstraintTupleState + : never + : never + : State + +/** Tuple of sequential constraints with stage-aware assertion compatibility checks. */ +export type CompatibleConstraintTuple = + number extends T['length'] + ? T + : T extends readonly [Constraint, ...Constraint[]] + ? CompatibleConstraintTupleState extends never + ? never + : T + : T + +/** One-or-many constraint input after applying staged assertion compatibility checks. */ +export type CompatibleConstraints = + C extends readonly Constraint[] + ? CompatibleConstraintTuple + : C extends Refinement + ? never + : C extends Constraint + ? C + : never + /** Extracts the validated TypeScript type from a single constraint. */ export type InferConstraint = - C extends Assertion + C extends Assertion ? T : C extends Validator ? T @@ -474,7 +557,7 @@ export type InferConstraints = /** Internal async runner signature used by composed validators. */ export type Validate = (( value: unknown, - constraints: MaybeMany, + constraints: CompatibleConstraints>, path?: PropertyKey[] ) => Promise) & { readonly sync?: false @@ -483,7 +566,7 @@ export type Validate = (( /** Internal sync runner signature used by composed validators. */ export type ValidateSync = (( value: unknown, - constraints: MaybeMany, + constraints: CompatibleConstraints>, path?: PropertyKey[] ) => Violation[]) & { readonly sync: true @@ -607,13 +690,15 @@ type InferShapeViolations = { /** Maps a single constraint into the union of violations it can produce. */ export type InferConstraintViolations = - C extends Assertion + C extends Guard ? Violation<{ kind: 'assertion'; name: Name; code: Code; args: Args; } | AssertionConstraintSubject> + : C extends Refinement + ? Violation> : C extends ObjectShape ? KnownCodeViolation<'type.record'> | (M extends 'strict' ? KnownCodeViolation<'shape.unknown-key'> : never) @@ -705,6 +790,11 @@ export declare const custom: (validator: V) => V /** Descriptor that maps object keys to one or many constraints. */ export type ShapeDescriptor = Record> +/** Shape descriptor with stage-aware compatibility checks applied to every field slot. */ +export type CompatibleShapeDescriptor = { + [K in keyof D]: CompatibleConstraints +} + /** Runtime type inferred from a shape descriptor. */ export type InferShape = { [K in keyof D]: InferConstraints @@ -752,7 +842,7 @@ export interface ObjectShape< /** Helper that maps a single constraint into its public `describe(...)` result. */ export type DescribeConstraint = - C extends Assertion + C extends Assertion ? AssertionDescriptor> : C extends ObjectShape ? ShapeConstraintDescriptor, R> & { readonly unknownKeys: M } From 046fee450c0a260d809cb1bfcaf6be81dbf3c38d Mon Sep 17 00:00:00 2001 From: Kirill Zaytsev Date: Tue, 10 Mar 2026 00:41:46 +0400 Subject: [PATCH 05/15] perf(build): Tree-shaking hints were added --- package.json | 1 + src/assert.ts | 2 +- src/assertions.ts | 46 +++++++++++++++++++++++----------------------- src/combinators.ts | 26 +++++++++++++------------- src/index.ts | 2 +- 5 files changed, 39 insertions(+), 38 deletions(-) diff --git a/package.json b/package.json index fa6ba89..9d31505 100644 --- a/package.json +++ b/package.json @@ -37,6 +37,7 @@ } }, "types": "dist/index.d.ts", + "sideEffects": false, "typesVersions": { "*": { "assertions": [ diff --git a/src/assert.ts b/src/assert.ts index 2f21016..ca42065 100644 --- a/src/assert.ts +++ b/src/assert.ts @@ -136,7 +136,7 @@ const createAssertion = < }, }) - return attachConstraintDescriptor(assertion, () => ({ + return /* @__PURE__ */ attachConstraintDescriptor(assertion, () => ({ kind: 'assertion', name: meta.name, bail: meta.bail, diff --git a/src/assertions.ts b/src/assertions.ts index a32882a..bf16b9d 100644 --- a/src/assertions.ts +++ b/src/assertions.ts @@ -184,25 +184,25 @@ const buildValueConstraints = (options: return constraints as unknown as ValueConstraintTupleFromOptions } -export const isBoolean = assert(_isBoolean, { name: 'isBoolean', bail: true, code: 'type.boolean' }) -export const isBigInt = assert(_isBigInt, { name: 'isBigInt', bail: true, code: 'type.bigint' }) -export const isBlob = assert(_isBlob, { name: 'isBlob', bail: true, code: 'type.blob' }) -export const isDate = assert(_isDate, { name: 'isDate', bail: true, code: 'type.date' }) -export const isDefined = assert((value: unknown): value is Defined => value !== undefined, { +export const isBoolean = /* @__PURE__ */ assert(_isBoolean, { name: 'isBoolean', bail: true, code: 'type.boolean' }) +export const isBigInt = /* @__PURE__ */ assert(_isBigInt, { name: 'isBigInt', bail: true, code: 'type.bigint' }) +export const isBlob = /* @__PURE__ */ assert(_isBlob, { name: 'isBlob', bail: true, code: 'type.blob' }) +export const isDate = /* @__PURE__ */ assert(_isDate, { name: 'isDate', bail: true, code: 'type.date' }) +export const isDefined = /* @__PURE__ */ assert((value: unknown): value is Defined => value !== undefined, { name: 'isDefined', bail: true, code: 'value.defined', }) -export const isEmail = assert(_isEmail, { name: 'isEmail', bail: true, code: 'string.email' }) -export const isFile = assert(_isFile, { name: 'isFile', bail: true, code: 'type.file' }) -export const isFunction = assert(_isFunction, { name: 'isFunction', bail: true, code: 'type.function' }) -export const isMap = assert(_isMap, { name: 'isMap', bail: true, code: 'type.map' }) -export const isNaN = assert(_isNaN, { name: 'isNaN', bail: true, code: 'number.nan' }) -export const isNull = assert(_isNull, { name: 'isNull', bail: true, code: 'type.null' }) -export const isNumber = assert(_isNumber, { name: 'isNumber', bail: true, code: 'type.number' }) -export const isSet = assert(_isSet, { name: 'isSet', bail: true, code: 'type.set' }) -export const isString = assert(_isString, { name: 'isString', bail: true, code: 'type.string' }) -export const isSymbol = assert(_isSymbol, { name: 'isSymbol', bail: true, code: 'type.symbol' }) +export const isEmail = /* @__PURE__ */ assert(_isEmail, { name: 'isEmail', bail: true, code: 'string.email' }) +export const isFile = /* @__PURE__ */ assert(_isFile, { name: 'isFile', bail: true, code: 'type.file' }) +export const isFunction = /* @__PURE__ */ assert(_isFunction, { name: 'isFunction', bail: true, code: 'type.function' }) +export const isMap = /* @__PURE__ */ assert(_isMap, { name: 'isMap', bail: true, code: 'type.map' }) +export const isNaN = /* @__PURE__ */ assert(_isNaN, { name: 'isNaN', bail: true, code: 'number.nan' }) +export const isNull = /* @__PURE__ */ assert(_isNull, { name: 'isNull', bail: true, code: 'type.null' }) +export const isNumber = /* @__PURE__ */ assert(_isNumber, { name: 'isNumber', bail: true, code: 'type.number' }) +export const isSet = /* @__PURE__ */ assert(_isSet, { name: 'isSet', bail: true, code: 'type.set' }) +export const isString = /* @__PURE__ */ assert(_isString, { name: 'isString', bail: true, code: 'type.string' }) +export const isSymbol = /* @__PURE__ */ assert(_isSymbol, { name: 'isSymbol', bail: true, code: 'type.symbol' }) export const hasLength = ( options: O = {} as O @@ -210,7 +210,7 @@ export const hasLength = ( const { bail = false } = options const constraints = buildSizeConstraints(options) - return refine( + return /* @__PURE__ */ refine( { name: 'hasSize', bail, @@ -243,7 +243,7 @@ export const hasPattern = ( }: { bail?: boolean; } = {} -) => refine( +) => /* @__PURE__ */ refine( { name: 'hasPattern', bail, @@ -264,7 +264,7 @@ export const startsWith = ( }: { bail?: boolean; } = {} -) => refine( +) => /* @__PURE__ */ refine( { name: 'startsWith', bail, @@ -285,7 +285,7 @@ export const endsWith = ( }: { bail?: boolean; } = {} -) => refine( +) => /* @__PURE__ */ refine( { name: 'endsWith', bail, @@ -305,7 +305,7 @@ export const hasValue = const { bail = false } = options const constraints = buildValueConstraints(options) - return refine( + return /* @__PURE__ */ refine( { name: 'hasValue', bail, @@ -322,7 +322,7 @@ export const multipleOf = ( }: { bail?: boolean; } = {} -) => refine( +) => /* @__PURE__ */ refine( { name: 'multipleOf', bail, @@ -348,7 +348,7 @@ export const oneOf = ( ) => { const haystack = isArray(values) ? values : Object.values(values) - return assert((value: unknown): value is Actual => haystack.some(item => equalTo(item, value)), { + return /* @__PURE__ */ assert((value: unknown): value is Actual => haystack.some(item => equalTo(item, value)), { name: 'oneOf', bail, code: 'value.one-of', diff --git a/src/combinators.ts b/src/combinators.ts index 91fcdfb..45cb62c 100644 --- a/src/combinators.ts +++ b/src/combinators.ts @@ -130,7 +130,7 @@ const passthrough = , Accepted>( kind: 'optional' | 'nullable' | 'nullish', accepts: (value: unknown) => value is Accepted, constraints: CompatibleConstraints -): Validator | Accepted> => attachConstraintDescriptor({ +): Validator | Accepted> => /* @__PURE__ */ attachConstraintDescriptor({ check(value: unknown): value is InferConstraints | Accepted { return accepts(value) || matchesConstraints(value, constraints) }, @@ -330,11 +330,11 @@ const createObjectShape = < [...rules, normalizeRuleDescriptor(ruleDescriptor)] as [...R, RD] ) - const refine = Object.assign(addAsyncRefinement, { + const refine = /* @__PURE__ */ Object.assign(addAsyncRefinement, { sync: addSyncRefinement, }) as ShapeRefineMethod - return attachConstraintDescriptor({ + return /* @__PURE__ */ attachConstraintDescriptor({ descriptor, unknownKeys, check(value: unknown): value is InferShape { @@ -485,7 +485,7 @@ const collectUnionViolations = ( return toUnionFailure(branches, value, path, branchResults as Validation[]) } -export const each = >(constraints: CompatibleConstraints): EachValidator => attachConstraintDescriptor({ +export const each = >(constraints: CompatibleConstraints): EachValidator => /* @__PURE__ */ attachConstraintDescriptor({ check(value: unknown): value is InferConstraints[] { return isArray(value) && value.every(item => matchesConstraints(item, constraints)) }, @@ -509,7 +509,7 @@ export const each = >(constraints: Compati export const tuple = []>( constraints: CompatibleConstraintTupleInputs -): TupleValidator => attachConstraintDescriptor({ +): TupleValidator => /* @__PURE__ */ attachConstraintDescriptor({ check(value: unknown): value is InferTuple { return isArray(value) && value.length === constraints.length @@ -545,7 +545,7 @@ export const tuple = []>( export const union = []>( constraints: CompatibleConstraintTupleInputs -): UnionValidator => attachConstraintDescriptor({ +): UnionValidator => /* @__PURE__ */ attachConstraintDescriptor({ check(value: unknown): value is InferUnion { return constraints.some(constraint => matchesConstraints(value, constraint)) }, @@ -564,7 +564,7 @@ export const union = []>( export const discriminatedUnion = < const K extends PropertyKey, const T extends VariantMap, ->(key: K, variants: CompatibleShapeDescriptor): DiscriminatedUnionValidator => attachConstraintDescriptor({ +>(key: K, variants: CompatibleShapeDescriptor): DiscriminatedUnionValidator => /* @__PURE__ */ attachConstraintDescriptor({ check(value: unknown): value is InferDiscriminatedUnion { if (!isRecord(value)) { return false @@ -615,7 +615,7 @@ export const discriminatedUnion = < variants: describeConstraintMap(variants), })) -export const record = >(constraints: CompatibleConstraints): RecordValidator => attachConstraintDescriptor({ +export const record = >(constraints: CompatibleConstraints): RecordValidator => /* @__PURE__ */ attachConstraintDescriptor({ check(value: unknown): value is Record> { return isRecord(value) && Object.values(value).every(item => matchesConstraints(item, constraints)) }, @@ -644,29 +644,29 @@ export const record = >(constraints: Compa })) export const shape = (descriptor: CompatibleShapeDescriptor): ObjectShape => { - return createObjectShape(descriptor, 'passthrough', [], []) + return /* @__PURE__ */ createObjectShape(descriptor, 'passthrough', [], []) } -export const exact = (value: T) => assert(isExact(value), { +export const exact = (value: T) => /* @__PURE__ */ assert(isExact(value), { name: 'exact', bail: true, code: 'value.exact', args: [value], }) -export const optional = >(constraints: CompatibleConstraints): OptionalValidator => passthrough( +export const optional = >(constraints: CompatibleConstraints): OptionalValidator => /* @__PURE__ */ passthrough( 'optional', isUndefined, constraints ) as OptionalValidator -export const nullable = >(constraints: CompatibleConstraints): NullableValidator => passthrough( +export const nullable = >(constraints: CompatibleConstraints): NullableValidator => /* @__PURE__ */ passthrough( 'nullable', isNull, constraints ) as NullableValidator -export const nullish = >(constraints: CompatibleConstraints): NullishValidator => passthrough( +export const nullish = >(constraints: CompatibleConstraints): NullishValidator => /* @__PURE__ */ passthrough( 'nullish', (value: unknown): value is null | undefined => isNull(value) || isUndefined(value), constraints diff --git a/src/index.ts b/src/index.ts index 5f0c5c3..424d017 100644 --- a/src/index.ts +++ b/src/index.ts @@ -242,7 +242,7 @@ export const matches = { }, } -export const validate = Object.assign( +export const validate = /* @__PURE__ */ Object.assign( async >( value: unknown, constraints: CompatibleConstraints From ee4cc29d546ab5b828242b26cbbfe02b21a389b4 Mon Sep 17 00:00:00 2001 From: Zaitsev Kirill Date: Sat, 26 Sep 2026 12:47:05 +0400 Subject: [PATCH 06/15] fix(predicates): Shape field validation and inference were corrected --- docs/en/05-public-api.md | 6 ++++++ docs/ru/05-public-api.md | 6 ++++++ src/predicates.ts | 36 ++++++++++++++++++------------- tests/predicates.test-d.ts | 43 ++++++++++++++++++++++++++++++++++++++ tests/predicates.test.ts | 23 ++++++++++++++++++++ 5 files changed, 100 insertions(+), 14 deletions(-) create mode 100644 tests/predicates.test-d.ts diff --git a/docs/en/05-public-api.md b/docs/en/05-public-api.md index e6c423b..34d324f 100644 --- a/docs/en/05-public-api.md +++ b/docs/en/05-public-api.md @@ -105,6 +105,12 @@ This subpath contains reusable runtime/type-guard helpers and predicate combinat Use this subpath when you want guard-style runtime checks without pulling in the higher-level validation layer. +In `isShape({ name: [isString, false] })`, an optional field may be absent, +but a present value is always checked by its predicate. This includes explicit +`undefined`: use `Or(isString, isUndefined)` to allow it. +The shorthand `name: isString` and the tuple `[isString, true]` define required fields. +Field presence is checked with `in`, including properties in the prototype chain. + ## JSON Schema Export Subpath JSON Schema export is available from: diff --git a/docs/ru/05-public-api.md b/docs/ru/05-public-api.md index d976540..f27df06 100644 --- a/docs/ru/05-public-api.md +++ b/docs/ru/05-public-api.md @@ -105,6 +105,12 @@ Predicates доступны из: Используйте этот subpath, когда нужны guard-style runtime checks без более высокого validation layer. +В `isShape({ name: [isString, false] })` optional-поле может отсутствовать, +но присутствующее значение всегда проверяется предикатом. Это относится и к +явному `undefined`: чтобы разрешить его, используйте `Or(isString, isUndefined)`. +Shorthand `name: isString` и кортеж `[isString, true]` задают обязательное поле. +Наличие поля проверяется через `in`, включая свойства из цепочки прототипов. + ## Subpath экспорта JSON Schema JSON Schema export доступен из: diff --git a/src/predicates.ts b/src/predicates.ts index a7d3651..013418d 100644 --- a/src/predicates.ts +++ b/src/predicates.ts @@ -142,18 +142,25 @@ export type Shape = { [K in keyof T]: [Predicate, boolean] | Predicate } -// Without `any` inference does not work properly -// eslint-disable-next-line -type ExtractType> = { - [K in keyof T]: T[K] extends [Predicate, true] +type ShapePropertyType = Config extends Predicate + ? U + : Config extends [Predicate, boolean] ? U - : T[K] extends [Predicate, false] - ? U | undefined - : never; -} extends infer O - ? O - : never + : never + +type OptionalShapeKeys = { + [K in keyof T]: T[K] extends [Predicate, infer Required] + ? false extends Required ? K : never + : never; +}[keyof T] +type ExtractType = { + [K in Exclude>]: ShapePropertyType; +} & { + [K in OptionalShapeKeys]?: ShapePropertyType; +} + +/** Checks present fields with their predicates; optional fields may be absent. */ // Without `any` inference does not work properly // eslint-disable-next-line export const isShape = >(shape: S) => { @@ -163,9 +170,10 @@ export const isShape = >(shape: S) => { const config = shape[p as keyof S] as [Predicate, boolean] | Predicate const [predicate, required] = isArray(config) ? config : [config, true] - return p in value - && value[p as keyof object] !== undefined - && predicate(value[p as keyof object]) - || !required + if (!(p in value)) { + return !required + } + + return predicate(value[p as keyof object]) }) } diff --git a/tests/predicates.test-d.ts b/tests/predicates.test-d.ts new file mode 100644 index 0000000..f7ae4c9 --- /dev/null +++ b/tests/predicates.test-d.ts @@ -0,0 +1,43 @@ +import { + describe, + expectTypeOf, + test, +} from 'vitest' + +import { + isNumber, + isShape, + isString, +} from '@/predicates' + +describe('isShape types', () => { + test('narrows shorthand and tuple fields while preserving optional keys', () => { + const predicate = isShape({ + id: isNumber, + title: [isString, true], + nickname: [isString, false], + }) + const value: unknown = {} + + if (predicate(value)) { + expectTypeOf(value.id).toEqualTypeOf() + expectTypeOf(value.title).toEqualTypeOf() + expectTypeOf(value.nickname).toEqualTypeOf() + expectTypeOf({ id: 1, title: 'Profile' }).toMatchTypeOf() + expectTypeOf<{ id: number; title: string; nickname?: string }>().toMatchTypeOf() + expectTypeOf(value).toMatchTypeOf<{ id: number; title: string; nickname?: string }>() + expectTypeOf({}).not.toMatchTypeOf() + } + }) + + test('keeps fields optional when required is decided at runtime', () => { + const required = false as boolean + const predicate = isShape({ name: [isString, required] }) + const value: unknown = {} + + if (predicate(value)) { + expectTypeOf({}).toMatchTypeOf() + expectTypeOf(value.name).toEqualTypeOf() + } + }) +}) diff --git a/tests/predicates.test.ts b/tests/predicates.test.ts index dcc74b1..6200bbe 100644 --- a/tests/predicates.test.ts +++ b/tests/predicates.test.ts @@ -322,8 +322,31 @@ describe('isShape', () => { expect(shape({ id: 1, name: 'Alice' })).toBe(true) expect(shape({ id: 1 })).toBe(true) + expect(shape({ id: 1, name: 2 })).toBe(false) expect(shape({ name: 'Alice' })).toBe(false) }) + + test('validates present undefined values through the field predicate', () => { + const stringShape = isShape({ name: [isString, false] }) + const optionalUndefinedShape = isShape({ name: [Or(isString, isUndefined), false] }) + const requiredUndefinedShape = isShape({ name: [isUndefined, true] }) + const shorthandUndefinedShape = isShape({ name: isUndefined }) + + expect(stringShape({ name: undefined })).toBe(false) + expect(optionalUndefinedShape({})).toBe(true) + expect(optionalUndefinedShape({ name: undefined })).toBe(true) + expect(requiredUndefinedShape({})).toBe(false) + expect(requiredUndefinedShape({ name: undefined })).toBe(true) + expect(shorthandUndefinedShape({})).toBe(false) + expect(shorthandUndefinedShape({ name: undefined })).toBe(true) + }) + + test('validates inherited optional properties when they are present', () => { + const shape = isShape({ name: [isString, false] }) + + expect(shape(Object.create({ name: 'Alice' }))).toBe(true) + expect(shape(Object.create({ name: 2 }))).toBe(false) + }) }) describe('isString', () => { From ae9ec92d0f4bb09812d72f2265d8368fa3a524dd Mon Sep 17 00:00:00 2001 From: Zaitsev Kirill Date: Sat, 26 Sep 2026 12:47:22 +0400 Subject: [PATCH 07/15] feat: Built-in value guards were expanded --- src/assertions.ts | 30 ++++++++ src/json-schema.ts | 33 ++++++++ src/predicates.ts | 37 +++++++++ tests/assertion-violations.test-d.ts | 18 +++++ tests/assertions.test.ts | 79 ++++++++++++++++++++ tests/json-schema.test.ts | 57 ++++++++++++++ tests/predicates.test.ts | 108 +++++++++++++++++++++++++++ types/index.d.ts | 7 ++ 8 files changed, 369 insertions(+) diff --git a/src/assertions.ts b/src/assertions.ts index bf16b9d..590600a 100644 --- a/src/assertions.ts +++ b/src/assertions.ts @@ -26,15 +26,22 @@ import { isBlob as _isBlob, isDate as _isDate, isEmail as _isEmail, + isError as _isError, + isFiniteNumber as _isFiniteNumber, isFile as _isFile, isFunction as _isFunction, + isInteger as _isInteger, isMap as _isMap, isNaN as _isNaN, isNull as _isNull, isNumber as _isNumber, + isPromiseLike as _isPromiseLike, + isRegExp as _isRegExp, + isSafeInteger as _isSafeInteger, isSet as _isSet, isString as _isString, isSymbol as _isSymbol, + isValidDate as _isValidDate, } from '@/predicates' export { @@ -194,15 +201,38 @@ export const isDefined = /* @__PURE__ */ assert((value: unknown): value is Defin code: 'value.defined', }) export const isEmail = /* @__PURE__ */ assert(_isEmail, { name: 'isEmail', bail: true, code: 'string.email' }) +export const isError = /* @__PURE__ */ assert(_isError, { name: 'isError', bail: true, code: 'type.error' }) +export const isFiniteNumber = /* @__PURE__ */ assert(_isFiniteNumber, { + name: 'isFiniteNumber', + bail: true, + code: 'number.finite', +}) export const isFile = /* @__PURE__ */ assert(_isFile, { name: 'isFile', bail: true, code: 'type.file' }) export const isFunction = /* @__PURE__ */ assert(_isFunction, { name: 'isFunction', bail: true, code: 'type.function' }) +export const isInteger = /* @__PURE__ */ assert(_isInteger, { name: 'isInteger', bail: true, code: 'number.integer' }) export const isMap = /* @__PURE__ */ assert(_isMap, { name: 'isMap', bail: true, code: 'type.map' }) export const isNaN = /* @__PURE__ */ assert(_isNaN, { name: 'isNaN', bail: true, code: 'number.nan' }) export const isNull = /* @__PURE__ */ assert(_isNull, { name: 'isNull', bail: true, code: 'type.null' }) export const isNumber = /* @__PURE__ */ assert(_isNumber, { name: 'isNumber', bail: true, code: 'type.number' }) +export const isPromiseLike = /* @__PURE__ */ assert(_isPromiseLike, { + name: 'isPromiseLike', + bail: true, + code: 'type.promise-like', +}) +export const isRegExp = /* @__PURE__ */ assert(_isRegExp, { name: 'isRegExp', bail: true, code: 'type.regexp' }) +export const isSafeInteger = /* @__PURE__ */ assert(_isSafeInteger, { + name: 'isSafeInteger', + bail: true, + code: 'number.safe-integer', +}) export const isSet = /* @__PURE__ */ assert(_isSet, { name: 'isSet', bail: true, code: 'type.set' }) export const isString = /* @__PURE__ */ assert(_isString, { name: 'isString', bail: true, code: 'type.string' }) export const isSymbol = /* @__PURE__ */ assert(_isSymbol, { name: 'isSymbol', bail: true, code: 'type.symbol' }) +export const isValidDate = /* @__PURE__ */ assert(_isValidDate, { + name: 'isValidDate', + bail: true, + code: 'date.valid', +}) export const hasLength = ( options: O = {} as O diff --git a/src/json-schema.ts b/src/json-schema.ts index 5ba8856..2b4263c 100644 --- a/src/json-schema.ts +++ b/src/json-schema.ts @@ -371,6 +371,20 @@ const numberSchema = ( ): JsonSchema => { const schema: MutableJsonSchema = { type: 'number' } + if (descriptor.name === 'isInteger') { + setSchemaProperty(schema, 'type', 'integer') + + return schema + } + + if (descriptor.name === 'isSafeInteger') { + setSchemaProperty(schema, 'type', 'integer') + setSchemaProperty(schema, 'minimum', Number.MIN_SAFE_INTEGER) + setSchemaProperty(schema, 'maximum', Number.MAX_SAFE_INTEGER) + + return schema + } + if (descriptor.name === 'multipleOf') { const [step] = descriptor.constraints[0]?.args ?? [] @@ -514,6 +528,13 @@ const exportAssertion = ( case 'isNumber': return { type: 'number' } + case 'isFiniteNumber': + return { type: 'number' } + + case 'isInteger': + case 'isSafeInteger': + return numberSchema(descriptor, context) + case 'isBoolean': return { type: 'boolean' } @@ -571,6 +592,18 @@ const exportAssertion = ( case 'isDate': return unsupported(descriptor, context, 'Date instances do not have a stable JSON Schema representation') + case 'isValidDate': + return unsupported(descriptor, context, 'Date instances do not have a stable JSON Schema representation') + + case 'isError': + return unsupported(descriptor, context, 'Error instances do not have a stable JSON Schema representation') + + case 'isPromiseLike': + return unsupported(descriptor, context, 'promise-like values do not have a stable JSON Schema representation') + + case 'isRegExp': + return unsupported(descriptor, context, 'regular expressions do not have a stable JSON Schema representation') + case 'isSet': return unsupported(descriptor, context, 'Set instances do not have a stable JSON Schema representation') diff --git a/src/predicates.ts b/src/predicates.ts index 013418d..64481b6 100644 --- a/src/predicates.ts +++ b/src/predicates.ts @@ -35,6 +35,11 @@ export function isDate (value: unknown): value is Date { return value instanceof Date } +/** Checks if value is a valid Date */ +export function isValidDate (value: unknown): value is Date { + return isDate(value) && !Number.isNaN(value.getTime()) +} + /** Checks if a value is an email */ export function isEmail (value: unknown): value is string { const pattern = /^(([^<>()[\].,;:\s@"]+(\.[^<>()[\].,;:\s@"]+)*)|(".+"))@(([^<>()[\].,;:\s@"]+\.)+[^<>()[\].,;:\s@"]{2,})$/i @@ -52,6 +57,11 @@ export function isFile (value: unknown): value is File { return typeof File !== 'undefined' && value instanceof File } +/** Checks if a value is an Error */ +export function isError (value: unknown): value is Error { + return value instanceof Error +} + /** Checks if a value is a function */ export function isFunction unknown = (...args: never[]) => unknown>( value: unknown @@ -79,16 +89,43 @@ export function isNumber (value: unknown): value is number { return typeof value === 'number' && !isNaN(value) } +/** Checks if a value is a finite number */ +export function isFiniteNumber (value: unknown): value is number { + return isNumber(value) && Number.isFinite(value) +} + +/** Checks if a value is an integer */ +export function isInteger (value: unknown): value is number { + return isNumber(value) && Number.isInteger(value) +} + /** Checks if a value is an object, excluding null value */ export function isObject (value: unknown): value is object { return typeof value === 'object' && value !== null } +/** Checks if a value is promise-like */ +export function isPromiseLike(value: unknown): value is PromiseLike { + return (isObject(value) || isFunction(value)) + && 'then' in value + && isFunction((value as PromiseLike & { then: unknown }).then) +} + +/** Checks if a value is a regular expression */ +export function isRegExp (value: unknown): value is RegExp { + return value instanceof RegExp +} + /** Check if a value is a record like Record */ export function isRecord (value: unknown): value is Record { return isObject(value) && !isNull(value) && constructorOf(value) === Object && Object.keys(prototypeOf(value)).length === 0 } +/** Checks if a value is a safe integer */ +export function isSafeInteger (value: unknown): value is number { + return isNumber(value) && Number.isSafeInteger(value) +} + /** Checks if a value is a Set */ export function isSet(value: unknown): value is Set { return value instanceof Set diff --git a/tests/assertion-violations.test-d.ts b/tests/assertion-violations.test-d.ts index 5b623d8..5566c73 100644 --- a/tests/assertion-violations.test-d.ts +++ b/tests/assertion-violations.test-d.ts @@ -9,12 +9,15 @@ import { hasLength, hasSize, hasValue, + isInteger, isNumber, + isValidDate, isSet, isString, multipleOf, validate, } from '@/index' +import { describe as describeConstraint } from '@/index' describe('parameterized assertion violation types', () => { test('hasLength({ min: 3 }) keeps only supported violation codes', () => { @@ -113,4 +116,19 @@ describe('parameterized assertion violation types', () => { }) } }) + + test('standalone integer and valid-date assertions preserve dedicated descriptor codes', () => { + const integerDescriptor = describeConstraint(isInteger) + const validDateDescriptor = describeConstraint(isValidDate) + + if (integerDescriptor.kind === 'assertion') { + assertType<'number.integer'>(integerDescriptor.code) + assertType(integerDescriptor.args) + } + + if (validDateDescriptor.kind === 'assertion') { + assertType<'date.valid'>(validDateDescriptor.code) + assertType(validDateDescriptor.args) + } + }) }) diff --git a/tests/assertions.test.ts b/tests/assertions.test.ts index 207b141..c657e93 100644 --- a/tests/assertions.test.ts +++ b/tests/assertions.test.ts @@ -11,11 +11,18 @@ import { hasValue, isBigInt, isBlob, + isError, + isFiniteNumber, isFile, isFunction, + isInteger, isMap, isNaN, + isPromiseLike, + isRegExp, + isSafeInteger, isSet, + isValidDate, multipleOf, oneOf, endsWith, @@ -236,6 +243,26 @@ describe('primitive assertions', () => { }) }) + test('isError', () => { + const value = new TypeError('boom') + + expect(isError.check(value)).toBe(true) + expect(isError(value)).toBe(null) + expect(isError({ message: 'boom' })).toEqual({ + value: { message: 'boom' }, + violates: assertionSubject('isError', 'type.error'), + }) + }) + + test('isFiniteNumber', () => { + expect(isFiniteNumber.check(1)).toBe(true) + expect(isFiniteNumber(1)).toBe(null) + expect(isFiniteNumber(Infinity)).toEqual({ + value: Infinity, + violates: assertionSubject('isFiniteNumber', 'number.finite'), + }) + }) + test('isFile', () => { const value = new File(['payload'], 'test.txt') const invalid = new Blob(['payload']) @@ -259,6 +286,15 @@ describe('primitive assertions', () => { }) }) + test('isInteger', () => { + expect(isInteger.check(1)).toBe(true) + expect(isInteger(1)).toBe(null) + expect(isInteger(1.5)).toEqual({ + value: 1.5, + violates: assertionSubject('isInteger', 'number.integer'), + }) + }) + test('isMap', () => { const value = new Map([['key', 1]]) @@ -279,6 +315,37 @@ describe('primitive assertions', () => { }) }) + test('isPromiseLike', () => { + const value = Promise.resolve(1) + + expect(isPromiseLike.check(value)).toBe(true) + expect(isPromiseLike(value)).toBe(null) + expect(isPromiseLike({})).toEqual({ + value: {}, + violates: assertionSubject('isPromiseLike', 'type.promise-like'), + }) + }) + + test('isRegExp', () => { + const value = /abc/ + + expect(isRegExp.check(value)).toBe(true) + expect(isRegExp(value)).toBe(null) + expect(isRegExp('abc')).toEqual({ + value: 'abc', + violates: assertionSubject('isRegExp', 'type.regexp'), + }) + }) + + test('isSafeInteger', () => { + expect(isSafeInteger.check(1)).toBe(true) + expect(isSafeInteger(1)).toBe(null) + expect(isSafeInteger(Number.MAX_SAFE_INTEGER + 1)).toEqual({ + value: Number.MAX_SAFE_INTEGER + 1, + violates: assertionSubject('isSafeInteger', 'number.safe-integer'), + }) + }) + test('isSet', () => { const value = new Set([1, 2, 3]) @@ -289,4 +356,16 @@ describe('primitive assertions', () => { violates: assertionSubject('isSet', 'type.set'), }) }) + + test('isValidDate', () => { + const value = new Date('2023-01-01') + const invalid = new Date('invalid') + + expect(isValidDate.check(value)).toBe(true) + expect(isValidDate(value)).toBe(null) + expect(isValidDate(invalid)).toEqual({ + value: invalid, + violates: assertionSubject('isValidDate', 'date.valid'), + }) + }) }) diff --git a/tests/json-schema.test.ts b/tests/json-schema.test.ts index 569b189..8391fd6 100644 --- a/tests/json-schema.test.ts +++ b/tests/json-schema.test.ts @@ -21,15 +21,22 @@ import { isDate, isDefined, isEmail, + isError, + isFiniteNumber, isFile, isFunction, + isInteger, isMap, isNaN, isNull, isNumber, + isPromiseLike, + isRegExp, + isSafeInteger, isSet, isString, isSymbol, + isValidDate, meta, multipleOf, nullable, @@ -260,11 +267,15 @@ describe('toJsonSchema', () => { test.each([ [isBigInt, 'bigint values cannot be represented in JSON Schema'], [isBlob, 'Blob instances do not have a stable JSON Schema representation'], + [isError, 'Error instances do not have a stable JSON Schema representation'], [isFile, 'File instances do not have a stable JSON Schema representation'], [isFunction, 'functions cannot be represented in JSON Schema'], [isMap, 'Map instances do not have a stable JSON Schema representation'], [isNaN, 'NaN cannot be represented in JSON Schema'], + [isPromiseLike, 'promise-like values do not have a stable JSON Schema representation'], + [isRegExp, 'regular expressions do not have a stable JSON Schema representation'], [isSet, 'Set instances do not have a stable JSON Schema representation'], + [isValidDate, 'Date instances do not have a stable JSON Schema representation'], ] as const)('throws in strict mode for unsupported built-in assertion %s', (assertion, reason) => { expect(() => toJsonSchema(shape({ value: assertion, @@ -300,6 +311,9 @@ describe('toJsonSchema', () => { optionalUnion: union([exact(undefined), isString]), optionalAllOf: [optional(isString), exact(undefined)], optionalEnum: oneOf([undefined, 'draft']), + finiteValue: isFiniteNumber, + integerValue: isInteger, + safeIntegerValue: isSafeInteger, exactLength: [isString, hasLength({ exact: 3 })], maxLength: [isString, hasLength({ max: 5 })], rangeLength: [isString, hasLength({ range: [1, 3] })], @@ -343,6 +357,17 @@ describe('toJsonSchema', () => { allOf: [{ type: 'string' }, {}], }, optionalEnum: {}, + finiteValue: { + type: 'number', + }, + integerValue: { + type: 'integer', + }, + safeIntegerValue: { + type: 'integer', + minimum: Number.MIN_SAFE_INTEGER, + maximum: Number.MAX_SAFE_INTEGER, + }, exactLength: { allOf: [{ type: 'string', @@ -459,6 +484,9 @@ describe('toJsonSchema', () => { 'nullValue', 'passthrough', 'nullableEmpty', + 'finiteValue', + 'integerValue', + 'safeIntegerValue', 'exactLength', 'maxLength', 'rangeLength', @@ -596,12 +624,19 @@ describe('toJsonSchema', () => { bigInt: isBigInt, blob: isBlob, date: isDate, + error: isError, + finite: isFiniteNumber, file: isFile, fn: isFunction, + integer: isInteger, map: isMap, nan: isNaN, + promise: isPromiseLike, + regexp: isRegExp, + safeInteger: isSafeInteger, set: isSet, symbol: isSymbol, + validDate: isValidDate, validator: bareValidator, weirdDescriptor, }))).toEqual({ @@ -656,12 +691,27 @@ describe('toJsonSchema', () => { bigInt: {}, blob: {}, date: {}, + error: {}, + finite: { + type: 'number', + }, file: {}, fn: {}, + integer: { + type: 'integer', + }, map: {}, nan: {}, + promise: {}, + regexp: {}, + safeInteger: { + type: 'integer', + minimum: Number.MIN_SAFE_INTEGER, + maximum: Number.MAX_SAFE_INTEGER, + }, set: {}, symbol: {}, + validDate: {}, validator: {}, weirdDescriptor: {}, }, @@ -692,12 +742,19 @@ describe('toJsonSchema', () => { 'bigInt', 'blob', 'date', + 'error', + 'finite', 'file', 'fn', + 'integer', 'map', 'nan', + 'promise', + 'regexp', + 'safeInteger', 'set', 'symbol', + 'validDate', 'validator', 'weirdDescriptor', ], diff --git a/tests/predicates.test.ts b/tests/predicates.test.ts index 6200bbe..624316e 100644 --- a/tests/predicates.test.ts +++ b/tests/predicates.test.ts @@ -8,20 +8,27 @@ import { isBlob, isDate, isEmail, + isError, isExact, + isFiniteNumber, isFile, isFunction, + isInteger, isMap, isNaN, isNull, isNumber, isObject, + isPromiseLike, + isRegExp, isRecord, + isSafeInteger, isSet, isShape, isString, isSymbol, isUndefined, + isValidDate, And, Or, Not, @@ -110,6 +117,7 @@ describe('isDate', () => { test('returns true when a value is a valid Date object', () => { expect(isDate(new Date())).toBe(true) expect(isDate(new Date('2023-01-01'))).toBe(true) + expect(isDate(new Date('invalid'))).toBe(true) }) test('returns false when a value is not a valid Date object', () => { @@ -122,6 +130,19 @@ describe('isDate', () => { }) }) +describe('isValidDate', () => { + test('returns true when a value is a valid Date object', () => { + expect(isValidDate(new Date())).toBe(true) + expect(isValidDate(new Date('2023-01-01'))).toBe(true) + }) + + test('returns false when a value is not a valid Date object', () => { + expect(isValidDate(new Date('invalid'))).toBe(false) + expect(isValidDate('2023-01-01')).toBe(false) + expect(isValidDate(null)).toBe(false) + }) +}) + describe('isEmail', () => { test('returns true when a value is email', () => { expect(isEmail('my@test.test')).toBe(true) @@ -139,6 +160,19 @@ describe('isEmail', () => { }) }) +describe('isError', () => { + test('returns true when a value is an Error', () => { + expect(isError(new Error('boom'))).toBe(true) + expect(isError(new TypeError('boom'))).toBe(true) + }) + + test('returns false when a value is not an Error', () => { + expect(isError({ message: 'boom' })).toBe(false) + expect(isError('boom')).toBe(false) + expect(isError(null)).toBe(false) + }) +}) + describe('isExact', () => { test('returns true when a value is equal exactly to the specified one', () => { expect(isExact(1)(1)).toBe(true) @@ -231,6 +265,7 @@ describe('isNumber', () => { test('returns true when a value\'s type is a number', () => { expect(isNumber(1)).toBe(true) expect(isNumber(1.5)).toBe(true) + expect(isNumber(Infinity)).toBe(true) }) test('returns false when a value\'s type is not a number', () => { @@ -240,6 +275,36 @@ describe('isNumber', () => { }) }) +describe('isFiniteNumber', () => { + test('returns true when a value is a finite number', () => { + expect(isFiniteNumber(1)).toBe(true) + expect(isFiniteNumber(1.5)).toBe(true) + expect(isFiniteNumber(-42)).toBe(true) + }) + + test('returns false when a value is not a finite number', () => { + expect(isFiniteNumber(Number.NaN)).toBe(false) + expect(isFiniteNumber(Infinity)).toBe(false) + expect(isFiniteNumber(-Infinity)).toBe(false) + expect(isFiniteNumber('1')).toBe(false) + }) +}) + +describe('isInteger', () => { + test('returns true when a value is an integer', () => { + expect(isInteger(0)).toBe(true) + expect(isInteger(42)).toBe(true) + expect(isInteger(-42)).toBe(true) + }) + + test('returns false when a value is not an integer', () => { + expect(isInteger(1.5)).toBe(false) + expect(isInteger(Number.NaN)).toBe(false) + expect(isInteger(Infinity)).toBe(false) + expect(isInteger('42')).toBe(false) + }) +}) + describe('isObject', () => { test('returns true when a value is an object', () => { expect(isObject({})).toBe(true) @@ -262,6 +327,34 @@ describe('isObject', () => { }) }) +describe('isPromiseLike', () => { + test('returns true when a value is promise-like', () => { + expect(isPromiseLike(Promise.resolve(1))).toBe(true) + expect(isPromiseLike({ then: () => undefined })).toBe(true) + expect(isPromiseLike(Object.assign(() => undefined, { then: () => undefined }))).toBe(true) + }) + + test('returns false when a value is not promise-like', () => { + expect(isPromiseLike({})).toBe(false) + expect(isPromiseLike({ then: true })).toBe(false) + expect(isPromiseLike(null)).toBe(false) + expect(isPromiseLike('promise')).toBe(false) + }) +}) + +describe('isRegExp', () => { + test('returns true when a value is a regular expression', () => { + expect(isRegExp(/abc/)).toBe(true) + expect(isRegExp(new RegExp('abc'))).toBe(true) + }) + + test('returns false when a value is not a regular expression', () => { + expect(isRegExp('abc')).toBe(false) + expect(isRegExp({})).toBe(false) + expect(isRegExp(null)).toBe(false) + }) +}) + describe('isRecord', () => { const a = Symbol('property') const b = Symbol('property') @@ -283,6 +376,21 @@ describe('isRecord', () => { }) }) +describe('isSafeInteger', () => { + test('returns true when a value is a safe integer', () => { + expect(isSafeInteger(0)).toBe(true) + expect(isSafeInteger(Number.MAX_SAFE_INTEGER)).toBe(true) + expect(isSafeInteger(Number.MIN_SAFE_INTEGER)).toBe(true) + }) + + test('returns false when a value is not a safe integer', () => { + expect(isSafeInteger(1.5)).toBe(false) + expect(isSafeInteger(Number.MAX_SAFE_INTEGER + 1)).toBe(false) + expect(isSafeInteger(Infinity)).toBe(false) + expect(isSafeInteger('42')).toBe(false) + }) +}) + describe('isSet', () => { test('returns true when a value is a Set', () => { expect(isSet(new Set())).toBe(true) diff --git a/types/index.d.ts b/types/index.d.ts index 4930e00..dcd2c5c 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -66,17 +66,21 @@ type ResolveViolationSubjectArgs; 'length.exact': ViolationCodeEntry<'assertion', 'hasLength', readonly [exact: number]>; 'length.max': ViolationCodeEntry<'assertion', 'hasLength', readonly [max: number]>; 'length.min': ViolationCodeEntry<'assertion', 'hasLength', readonly [min: number]>; 'length.range': ViolationCodeEntry<'assertion', 'hasLength', readonly [range: readonly [number, number]]>; 'length.unsupported-type': ViolationCodeEntry<'assertion', 'hasLength', readonly []>; 'number.exact': ViolationCodeEntry<'assertion', 'hasValue', readonly [exact: number]>; + 'number.finite': ViolationCodeEntry<'assertion', 'isFiniteNumber', readonly []>; + 'number.integer': ViolationCodeEntry<'assertion', 'isInteger', readonly []>; 'number.max': ViolationCodeEntry<'assertion', 'hasValue', readonly [max: number]>; 'number.min': ViolationCodeEntry<'assertion', 'hasValue', readonly [min: number]>; 'number.multiple-of': ViolationCodeEntry<'assertion', 'multipleOf', readonly [step: number]>; 'number.nan': ViolationCodeEntry<'assertion', 'isNaN', readonly []>; 'number.range': ViolationCodeEntry<'assertion', 'hasValue', readonly [range: readonly [number, number]]>; + 'number.safe-integer': ViolationCodeEntry<'assertion', 'isSafeInteger', readonly []>; 'number.unsupported-type': ViolationCodeEntry<'assertion', 'hasValue' | 'multipleOf', readonly []>; 'runtime.rejection': ViolationCodeEntry<'runtime', 'validate', readonly [reason: unknown]>; 'shape.fields.mismatch': ViolationCodeEntry< @@ -105,12 +109,15 @@ export interface ViolationCodeRegistry { 'type.blob': ViolationCodeEntry<'assertion', 'isBlob', readonly []>; 'type.boolean': ViolationCodeEntry<'assertion', 'isBoolean', readonly []>; 'type.date': ViolationCodeEntry<'assertion', 'isDate', readonly []>; + 'type.error': ViolationCodeEntry<'assertion', 'isError', readonly []>; 'type.file': ViolationCodeEntry<'assertion', 'isFile', readonly []>; 'type.function': ViolationCodeEntry<'assertion', 'isFunction', readonly []>; 'type.map': ViolationCodeEntry<'assertion', 'isMap', readonly []>; 'type.null': ViolationCodeEntry<'assertion', 'isNull', readonly []>; 'type.number': ViolationCodeEntry<'assertion', 'isNumber', readonly []>; + 'type.promise-like': ViolationCodeEntry<'assertion', 'isPromiseLike', readonly []>; 'type.record': ViolationCodeEntry<'validator', 'shape' | 'discriminatedUnion' | 'record', readonly []>; + 'type.regexp': ViolationCodeEntry<'assertion', 'isRegExp', readonly []>; 'type.set': ViolationCodeEntry<'assertion', 'isSet', readonly []>; 'type.string': ViolationCodeEntry<'assertion', 'isString', readonly []>; 'type.symbol': ViolationCodeEntry<'assertion', 'isSymbol', readonly []>; From 54df91f3cac9615c79c75ed294d9ec60a0763bad Mon Sep 17 00:00:00 2001 From: Zaitsev Kirill Date: Sat, 26 Sep 2026 13:06:51 +0400 Subject: [PATCH 08/15] fix(types): Strict assertion contracts were aligned --- src/assert.ts | 44 +++++++++++++++++--- src/assertions.ts | 15 +++---- src/combinators.ts | 6 +-- src/constraints.ts | 8 +--- src/index.ts | 6 +-- tests/assertions.test.ts | 2 +- tests/builtin-guards.test-d.ts | 69 +++++++++++++++++++++++++++++++ tests/custom-assertions.test-d.ts | 62 +++++++++++++++++++++++++++ tests/match.test.ts | 8 ++++ tests/predicates.test.ts | 10 ++--- tests/validate.test.ts | 6 ++- tests/validate.tuple.test-d.ts | 27 ++++++++++++ tsconfig.json | 3 ++ types/index.d.ts | 31 ++++++++++---- 14 files changed, 255 insertions(+), 42 deletions(-) create mode 100644 tests/builtin-guards.test-d.ts create mode 100644 tests/custom-assertions.test-d.ts diff --git a/src/assert.ts b/src/assert.ts index ca42065..abd437a 100644 --- a/src/assert.ts +++ b/src/assert.ts @@ -21,10 +21,24 @@ type AssertMeta = { type ResolveAssertionCode = Code extends string ? Code : Name type ResolveAssertionArgs = Args extends readonly unknown[] ? Args : [] -type ConstraintInput = C[number] extends AssertionConstraint - ? Input +type ConstraintInputFunction = C extends unknown + ? (value: Parameters[0]) => void : never +type ConstraintInput = C extends readonly [] + ? unknown + : ConstraintInputFunction extends (value: infer Input) => void + ? Input + : never + +type ConstraintArgs = C extends readonly [unknown, unknown, string, ...infer Args] + ? Args + : never + +type CompatibleAssertionConstraints = { + [K in keyof C]: AssertionConstraint, ConstraintArgs, C[K][2]>; +} + type RuntimeAssertion = Assertion & { [assertionStageSymbol]?: AssertionStage; [runRefinementSymbol]?: (value: unknown) => ReturnType; @@ -42,7 +56,8 @@ const createAssertion = < const Bail extends boolean, const Code extends string | undefined = undefined, const Args extends readonly unknown[] | undefined = undefined, - const C extends readonly AssertionConstraint[] = [], + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const C extends readonly AssertionConstraint[] = [], >( stage: Stage, predicate: Predicate, @@ -155,7 +170,8 @@ export const assert = < const Bail extends boolean, const Code extends string | undefined = undefined, const Args extends readonly unknown[] | undefined = undefined, - const C extends readonly AssertionConstraint[] = [], + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const C extends readonly AssertionConstraint[] = [], >( predicate: Predicate, meta: AssertMeta & { @@ -164,7 +180,7 @@ export const assert = < code?: Code; args?: Args; }, - constraints: C = [] as unknown as C + constraints: C & CompatibleAssertionConstraints = [] as unknown as C & CompatibleAssertionConstraints ): Guard, ResolveAssertionArgs, Name> => { return createAssertion('guard', predicate, meta, constraints) as Guard< T, @@ -191,7 +207,7 @@ export const checkRefinementAssertion = (constraint: Assertion, value: unknown): return checkRefinement ? checkRefinement(value) : constraint.check(value) } -export const refine = < +export const createRefinement = < const Name extends string, const Bail extends boolean, const Code extends string | undefined = undefined, @@ -214,3 +230,19 @@ export const refine = < Name > } + +export const refine: < + const Name extends string, + const Bail extends boolean, + const Code extends string | undefined = undefined, + const Args extends readonly unknown[] | undefined = undefined, + const C extends readonly AssertionConstraint[] = [], +>( + meta: AssertMeta & { + name: Name; + bail: Bail; + code?: Code; + args?: Args; + }, + constraints?: C & CompatibleAssertionConstraints, C> +) => Refinement, C, ResolveAssertionCode, ResolveAssertionArgs, Name> = createRefinement diff --git a/src/assertions.ts b/src/assertions.ts index 590600a..ef3f9cf 100644 --- a/src/assertions.ts +++ b/src/assertions.ts @@ -2,6 +2,7 @@ import type { AssertionConstraint } from '~types' import { assert, + createRefinement, refine, } from './assert' @@ -240,7 +241,7 @@ export const hasLength = ( const { bail = false } = options const constraints = buildSizeConstraints(options) - return /* @__PURE__ */ refine( + return /* @__PURE__ */ createRefinement( { name: 'hasSize', bail, @@ -273,7 +274,7 @@ export const hasPattern = ( }: { bail?: boolean; } = {} -) => /* @__PURE__ */ refine( +) => /* @__PURE__ */ createRefinement( { name: 'hasPattern', bail, @@ -294,7 +295,7 @@ export const startsWith = ( }: { bail?: boolean; } = {} -) => /* @__PURE__ */ refine( +) => /* @__PURE__ */ createRefinement( { name: 'startsWith', bail, @@ -315,7 +316,7 @@ export const endsWith = ( }: { bail?: boolean; } = {} -) => /* @__PURE__ */ refine( +) => /* @__PURE__ */ createRefinement( { name: 'endsWith', bail, @@ -335,7 +336,7 @@ export const hasValue = const { bail = false } = options const constraints = buildValueConstraints(options) - return /* @__PURE__ */ refine( + return /* @__PURE__ */ createRefinement( { name: 'hasValue', bail, @@ -352,7 +353,7 @@ export const multipleOf = ( }: { bail?: boolean; } = {} -) => /* @__PURE__ */ refine( +) => /* @__PURE__ */ createRefinement( { name: 'multipleOf', bail, diff --git a/src/combinators.ts b/src/combinators.ts index 45cb62c..d4166d4 100644 --- a/src/combinators.ts +++ b/src/combinators.ts @@ -570,7 +570,7 @@ export const discriminatedUnion = < return false } - const discriminator = value[key] + const discriminator = value[key] as PropertyKey return Object.prototype.hasOwnProperty.call(variants, discriminator) && matchesConstraints(value, variants[discriminator as keyof T] as CompatibleConstraints) @@ -588,7 +588,7 @@ export const discriminatedUnion = < }]] as Validation[] } - const discriminator = value[key] + const discriminator = value[key] as PropertyKey if (!Object.prototype.hasOwnProperty.call(variants, discriminator)) { return [[{ @@ -644,7 +644,7 @@ export const record = >(constraints: Compa })) export const shape = (descriptor: CompatibleShapeDescriptor): ObjectShape => { - return /* @__PURE__ */ createObjectShape(descriptor, 'passthrough', [], []) + return /* @__PURE__ */ createObjectShape(descriptor, 'passthrough', [], []) } export const exact = (value: T) => /* @__PURE__ */ assert(isExact(value), { diff --git a/src/constraints.ts b/src/constraints.ts index 6ee2afc..9f8cdb0 100644 --- a/src/constraints.ts +++ b/src/constraints.ts @@ -28,13 +28,9 @@ export function matchesConstraints>( return arrayify(constraints).every(constraint => { if (isValidator(constraint)) { - const matched = constraint.check(value) + establishedDomain = false - if (matched) { - establishedDomain = true - } - - return matched + return constraint.check(value) } if (!establishedDomain && isRefinementAssertion(constraint)) { diff --git a/src/index.ts b/src/index.ts index 424d017..faf18e7 100644 --- a/src/index.ts +++ b/src/index.ts @@ -237,7 +237,7 @@ async function settle (value: unknown, path: PropertyKey[], validations: Promise } export const matches = { - sync>(value: unknown, constraints: CompatibleConstraints): value is InferConstraints { + sync>(value: unknown, constraints: C & CompatibleConstraints): value is InferConstraints { return collectViolationsSync(value, constraints as CompatibleConstraints>).length === 0 }, } @@ -245,7 +245,7 @@ export const matches = { export const validate = /* @__PURE__ */ Object.assign( async >( value: unknown, - constraints: CompatibleConstraints + constraints: C & CompatibleConstraints ): Promise, InferViolations>> => { const violations = await collectViolations(value, constraints as CompatibleConstraints>) @@ -257,7 +257,7 @@ export const validate = /* @__PURE__ */ Object.assign( { sync>( value: unknown, - constraints: CompatibleConstraints + constraints: C & CompatibleConstraints ): ValidationResult, InferViolations> { const violations = collectViolationsSync(value, constraints as CompatibleConstraints>) diff --git a/tests/assertions.test.ts b/tests/assertions.test.ts index c657e93..fa06502 100644 --- a/tests/assertions.test.ts +++ b/tests/assertions.test.ts @@ -124,7 +124,7 @@ describe('oneOf', () => { { value: 1 }, { value: 2 }, { value: 3 }, - ], { equalTo: (a, b) => typeof b === 'object' && 'value' in b && a.value === b.value }).check + ], { equalTo: (a, b) => typeof b === 'object' && b !== null && 'value' in b && a.value === b.value }).check expect(isCorrect({ value: 1 })).toBe(true) expect(isCorrect({ value: 2 })).toBe(true) diff --git a/tests/builtin-guards.test-d.ts b/tests/builtin-guards.test-d.ts new file mode 100644 index 0000000..44eda35 --- /dev/null +++ b/tests/builtin-guards.test-d.ts @@ -0,0 +1,69 @@ +import type { + InferConstraint, + InferViolations, + ValidationResult, +} from '@/index' + +import { + describe, + expectTypeOf, + test, +} from 'vitest' + +import { + hasValue, + isError, + isFiniteNumber, + isInteger, + isPromiseLike, + isRegExp, + isSafeInteger, + isValidDate, + multipleOf, + validate, +} from '@/index' +import { isPromiseLike as isPromiseLikePredicate } from '@/predicates' + +describe('built-in guard types', () => { + test('preserves the narrowed domain and dedicated violation code of every new guard', () => { + expectTypeOf>().toEqualTypeOf() + expectTypeOf>().toEqualTypeOf() + expectTypeOf>().toEqualTypeOf() + expectTypeOf>().toEqualTypeOf>() + expectTypeOf>().toEqualTypeOf() + expectTypeOf>().toEqualTypeOf() + expectTypeOf>().toEqualTypeOf() + + expectTypeOf['violates']['code']>().toEqualTypeOf<'type.error'>() + expectTypeOf['violates']['code']>().toEqualTypeOf<'number.finite'>() + expectTypeOf['violates']['code']>().toEqualTypeOf<'number.integer'>() + expectTypeOf['violates']['code']>().toEqualTypeOf<'type.promise-like'>() + expectTypeOf['violates']['code']>().toEqualTypeOf<'type.regexp'>() + expectTypeOf['violates']['code']>().toEqualTypeOf<'number.safe-integer'>() + expectTypeOf['violates']['code']>().toEqualTypeOf<'date.valid'>() + expectTypeOf['violates']['args']>().toMatchTypeOf() + }) + + test('numeric guards establish domains for bounded and multiple-of refinements', () => { + const finite = validate.sync(4, [isFiniteNumber, hasValue({ min: 1 })]) + const integer = validate.sync(4, [isInteger, multipleOf(2)]) + const safeInteger = validate.sync(4, [isSafeInteger, hasValue({ max: 10 })]) + + expectTypeOf(finite).toMatchTypeOf>() + expectTypeOf(integer).toMatchTypeOf>() + expectTypeOf(safeInteger).toMatchTypeOf>() + expectTypeOf<(typeof integer)[2][number]['violates']['code']>().toEqualTypeOf<'number.integer' | 'number.multiple-of'>() + }) + + test('promise-like predicates narrow to unknown unless a value type is provided', () => { + const value: unknown = {} + + if (isPromiseLikePredicate(value)) { + expectTypeOf(value).toEqualTypeOf>() + } + + if (isPromiseLikePredicate(value)) { + expectTypeOf(value).toEqualTypeOf>() + } + }) +}) diff --git a/tests/custom-assertions.test-d.ts b/tests/custom-assertions.test-d.ts new file mode 100644 index 0000000..879e39d --- /dev/null +++ b/tests/custom-assertions.test-d.ts @@ -0,0 +1,62 @@ +import { + describe, + expectTypeOf, + test, +} from 'vitest' + +import { + assert, + isString, + refine, + validate, +} from '@/index' + +describe('custom assertion checker types', () => { + test('connects extractor values and checker arguments', () => { + const minLength = refine({ name: 'minLength', bail: false }, [[ + (value: string) => value.length, + (length: number, min: number) => length >= min, + 'custom.min-length', + 3, + ]]) + const result = validate.sync('abcd', [isString, minLength]) + + if (result[0]) expectTypeOf(result[1]).toEqualTypeOf() + + assert(isString.check, { name: 'wrongValue', bail: true }, [ + // @ts-expect-error checker cannot consume the extractor result + [(value: string) => value.length, (value: string) => value.startsWith('x'), 'custom.wrong'], + ]) + refine({ name: 'wrongArgs', bail: false }, [ + // @ts-expect-error checker arguments must accept the constraint args + [(value: string) => value.length, (length: number, min: number) => length >= min, 'custom.wrong', 'three'], + ]) + assert(isString.check, { name: 'wrongInput', bail: true }, [ + // @ts-expect-error extractor must accept the guarded input + [(value: number) => value, (value: number) => value > 0, 'custom.wrong'], + ]) + }) + + test('requires a domain accepted by every extractor in a refinement', () => { + const incompatible = refine({ name: 'incompatible', bail: false }, [ + [(value: string) => value.toUpperCase(), (value: string) => value.length > 0, 'custom.string'], + [(value: number) => value.toFixed(), (value: string) => value.length > 0, 'custom.number'], + ]) + + // @ts-expect-error string is not accepted by the numeric extractor + validate.sync('abc', [isString, incompatible]) + }) + + test('supports generic custom refinement factories', () => { + const minLength = (minimum: Minimum) => refine({ name: 'minLength', bail: false }, [[ + (value: string) => value.length, + (length: number, min: number) => length >= min, + 'custom.min-length', + minimum, + ]]) + const result = validate.sync('abcd', [isString, minLength(3)]) + + if (result[0]) expectTypeOf(result[1]).toEqualTypeOf() + expectTypeOf<(typeof result)[2][number]['violates']['code']>().toEqualTypeOf<'type.string' | 'custom.min-length'>() + }) +}) diff --git a/tests/match.test.ts b/tests/match.test.ts index af8044c..decf7c3 100644 --- a/tests/match.test.ts +++ b/tests/match.test.ts @@ -6,6 +6,7 @@ import { import { hasLength, + each, isDefined, isString, matches, @@ -42,4 +43,11 @@ describe('matches.sync', () => { expect(matchesConstraints('neo', [isString, hasLength({ min: 2 })])).toBe(true) expect(matchesConstraints('n', [isString, hasLength({ min: 2 })])).toBe(false) }) + + test('requires a new guard after a structural validator in dynamic sequences', () => { + const constraints = [each(isString), hasLength({ min: 2 })] + + expect(matchesConstraints(['a', 'b'], constraints as never)).toBe(false) + expect(matchesConstraints(['a', 'b'], [each(isString), isDefined, hasLength({ min: 2 })])).toBe(true) + }) }) diff --git a/tests/predicates.test.ts b/tests/predicates.test.ts index 624316e..a89e279 100644 --- a/tests/predicates.test.ts +++ b/tests/predicates.test.ts @@ -503,17 +503,17 @@ describe('isUndefined', () => { describe('And', () => { test('returns true when a value satisfies all of the predicates', () => { - expect(And(isNumber, (value: number): value is number => isFinite(value))(1)).toBe(true) + expect(And(isNumber, (value: unknown): value is number => isNumber(value) && isFinite(value))(1)).toBe(true) expect(And( isString, - (value: string): value is string => value.length > 3, - (value: string): value is string => value.length < 10 + (value: unknown): value is string => isString(value) && value.length > 3, + (value: unknown): value is string => isString(value) && value.length < 10 )('NickName')).toBe(true) }) test('returns false when a value does not satisfy at least one of the predicates', () => { - expect(And(isNumber, (value: number): value is number => value > 10)(5)).toBe(false) - expect(And(isString, (value: string): value is string => value.length > 0)('')).toBe(false) + expect(And(isNumber, (value: unknown): value is number => isNumber(value) && value > 10)(5)).toBe(false) + expect(And(isString, (value: unknown): value is string => isString(value) && value.length > 0)('')).toBe(false) }) }) diff --git a/tests/validate.test.ts b/tests/validate.test.ts index 022ba5a..6b2dc1c 100644 --- a/tests/validate.test.ts +++ b/tests/validate.test.ts @@ -226,13 +226,15 @@ describe('validate', () => { }) test('awaits async assertions with bail and stops further checks on failure', async () => { - expect(await validate('', [ + const constraints: Assertion[] = [ createAsyncAssertion('asyncBail', async (value: unknown) => ({ value, violates: assertionSubject('asyncBail', 'asyncBail'), }), true), hasLength({ min: 2 }), - ])).toEqual(invalid('', [{ + ] + + expect(await validate('', constraints)).toEqual(invalid('', [{ value: '', path: [], violates: assertionSubject('asyncBail', 'asyncBail'), diff --git a/tests/validate.tuple.test-d.ts b/tests/validate.tuple.test-d.ts index 1627002..45e1af0 100644 --- a/tests/validate.tuple.test-d.ts +++ b/tests/validate.tuple.test-d.ts @@ -19,6 +19,7 @@ import { shape, isDefined, isString, + matches, validate, } from '@/index' @@ -143,6 +144,16 @@ describe('validate tuple types', () => { assertType>(result) }) + test('intersects stages without flattening unions inside a guard domain', () => { + const constraints = [isDefined, isString] + const result = validate.sync('abc', constraints) + + if (result[0]) { + result[1].toUpperCase() + assertType(result[1]) + } + }) + test('rejects incompatible clarifying assertions in tuples', () => { // @ts-expect-error incompatible clarifying assertion validate.sync(12, [isNumber, hasLength({ min: 3 })] as const) @@ -157,4 +168,20 @@ describe('validate tuple types', () => { // @ts-expect-error clarifying assertions are not standalone validation constraints validate.sync('abc', hasLength({ min: 3 })) }) + + test('requires a new guard after a structural validator', () => { + const constraints = [each(isString), hasLength({ min: 2 })] as const + + // @ts-expect-error validators do not establish an assertion domain + validate.sync(['a', 'b'], constraints) + // @ts-expect-error validators do not establish an assertion domain + validate(['a', 'b'], constraints) + // @ts-expect-error validators do not establish an assertion domain + matches.sync(['a', 'b'], constraints) + // @ts-expect-error nested fields obey the same stage rules + shape({ tags: constraints }) + + const result = validate.sync(['a', 'b'], [each(isString), isDefined, hasLength({ min: 2 })]) + assertType>(result) + }) }) diff --git a/tsconfig.json b/tsconfig.json index 738796a..6c98536 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,5 +1,6 @@ { "compilerOptions": { + "strict": true, "esModuleInterop": true, "module": "ESNext", "moduleResolution": "Bundler", @@ -15,6 +16,8 @@ "src/**/*.ts", "src/shims-*.d.ts", "tests/**/*.ts", + "vite.config*.ts", + "vitest.config.ts", "rollup.config.ts" ] } diff --git a/types/index.d.ts b/types/index.d.ts index dcd2c5c..f79dbda 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -386,9 +386,13 @@ export type ConstraintDescriptor = BuiltInConstraintDescriptor | CustomConstrain * `type LengthConstraint = AssertionConstraint` */ export type AssertionConstraint< - T = unknown, - V = unknown, - A extends readonly unknown[] = readonly unknown[], + // Defaults erase heterogeneous checker signatures; concrete tuples retain their types. + // eslint-disable-next-line @typescript-eslint/no-explicit-any + T = any, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + V = any, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + A extends readonly unknown[] = any[], C extends string = string > = readonly [ Extractor, @@ -399,13 +403,13 @@ export type AssertionConstraint< /** Maps an assertion checker tuple into its public descriptor entry. */ export type DescribeAssertionConstraint = - C extends AssertionConstraint + C extends readonly [unknown, unknown, infer Code extends string, ...infer A] ? AssertionConstraintDescriptor : never /** Maps an assertion checker tuple into the violation subject it can produce. */ export type AssertionConstraintSubject = - C extends AssertionConstraint + C extends readonly [unknown, unknown, infer Code extends string, ...infer A] ? { kind: 'assertion'; name: N; @@ -497,9 +501,11 @@ type ApplyConstraintStep< ? ConstraintSequenceState : never : never - : Step extends Constraint - ? ConstraintSequenceState - : never + : Step extends Validator + ? ConstraintSequenceState + : Step extends Assertion + ? ConstraintSequenceState + : never type CompatibleConstraintTupleState< T extends readonly Constraint[], @@ -543,6 +549,11 @@ export type InferConstraint = ? T : never +type InferConstraintIntersection = + (C extends unknown ? (value: InferConstraint) => void : never) extends (value: infer Output) => void + ? Output + : never + /** * Extracts the validated TypeScript type from one or many constraints. * @@ -558,7 +569,9 @@ export type InferConstraints = C extends readonly [] ? unknown : C extends readonly unknown[] - ? UnionToIntersection> + ? number extends C['length'] + ? InferConstraintIntersection + : Intersect<{ [K in keyof C]: InferConstraint }> : InferConstraint /** Internal async runner signature used by composed validators. */ From 6c4765b689d1c56d6b67579463f927b96d58cb94 Mon Sep 17 00:00:00 2001 From: Zaitsev Kirill Date: Sat, 26 Sep 2026 13:07:19 +0400 Subject: [PATCH 09/15] fix(build): Packed ESM and CommonJS consumers were supported --- .github/workflows/release.yml | 2 + .github/workflows/tests.yml | 3 + package.json | 58 +++++++++++----- scripts/check-package.mjs | 126 ++++++++++++++++++++++++++++++++++ vite.config.ts | 44 ++++++++++-- 5 files changed, 212 insertions(+), 21 deletions(-) create mode 100644 scripts/check-package.mjs diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a8c2365..59589ad 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -72,6 +72,8 @@ jobs: yarn lint yarn typecheck yarn test + yarn test:d + yarn test:package - name: Run release script env: diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 7fc541d..4a1f548 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -64,6 +64,9 @@ jobs: - name: Run type tests run: yarn test:d + - name: Check packed package consumers + run: yarn test:package + - name: Upload coverage to GitHub Artifacts uses: actions/upload-artifact@v4 with: diff --git a/package.json b/package.json index 9d31505..0a8da5d 100644 --- a/package.json +++ b/package.json @@ -6,33 +6,58 @@ "version": "0.2.1", "exports": { ".": { - "types": "./dist/index.d.ts", - "import": "./dist/index.mjs", - "require": "./dist/index.cjs", + "import": { + "types": "./dist/index.d.mts", + "default": "./dist/index.mjs" + }, + "require": { + "types": "./dist/index.d.cts", + "default": "./dist/index.cjs" + }, "default": "./dist/index.mjs" }, "./assertions": { - "types": "./dist/assertions.d.ts", - "import": "./dist/assertions.mjs", - "require": "./dist/assertions.cjs", + "import": { + "types": "./dist/assertions.d.mts", + "default": "./dist/assertions.mjs" + }, + "require": { + "types": "./dist/assertions.d.cts", + "default": "./dist/assertions.cjs" + }, "default": "./dist/assertions.mjs" }, "./combinators": { - "types": "./dist/combinators.d.ts", - "import": "./dist/combinators.mjs", - "require": "./dist/combinators.cjs", + "import": { + "types": "./dist/combinators.d.mts", + "default": "./dist/combinators.mjs" + }, + "require": { + "types": "./dist/combinators.d.cts", + "default": "./dist/combinators.cjs" + }, "default": "./dist/combinators.mjs" }, "./json-schema": { - "types": "./dist/json-schema.d.ts", - "import": "./dist/json-schema.mjs", - "require": "./dist/json-schema.cjs", + "import": { + "types": "./dist/json-schema.d.mts", + "default": "./dist/json-schema.mjs" + }, + "require": { + "types": "./dist/json-schema.d.cts", + "default": "./dist/json-schema.cjs" + }, "default": "./dist/json-schema.mjs" }, "./predicates": { - "types": "./dist/predicates.d.ts", - "import": "./dist/predicates.mjs", - "require": "./dist/predicates.cjs", + "import": { + "types": "./dist/predicates.d.mts", + "default": "./dist/predicates.mjs" + }, + "require": { + "types": "./dist/predicates.d.cts", + "default": "./dist/predicates.cjs" + }, "default": "./dist/predicates.mjs" } }, @@ -56,7 +81,7 @@ }, "scripts": { "build": "vite build", - "lint": "eslint src tests types", + "lint": "eslint src tests types scripts vite.config.ts", "prepare": "husky", "release": "npx --yes standard-version", "release:minor": "npx --yes standard-version --release-as minor", @@ -67,6 +92,7 @@ "test:d": "vitest run --typecheck.only", "test:coverage": "vitest run --coverage", "test:coverage:html": "vitest run --coverage --reporter=html --outputFile.html=./reports/html/report.html", + "test:package": "yarn build && node scripts/check-package.mjs", "typecheck": "tsc --noEmit" }, "devDependencies": { diff --git a/scripts/check-package.mjs b/scripts/check-package.mjs new file mode 100644 index 0000000..f84ce9b --- /dev/null +++ b/scripts/check-package.mjs @@ -0,0 +1,126 @@ +import assert from 'node:assert/strict' +import { execFileSync } from 'node:child_process' +import { mkdtemp, readFile, rm, writeFile } from 'node:fs/promises' +import { tmpdir } from 'node:os' +import { dirname, join } from 'node:path' +import { fileURLToPath } from 'node:url' +import { build } from 'vite' + +const projectRoot = dirname(dirname(fileURLToPath(import.meta.url))) +const consumerRoot = await mkdtemp(join(tmpdir(), 'validator-consumer-')) +const run = (command, args, cwd = consumerRoot) => execFileSync(command, args, { + cwd, + encoding: 'utf8', + stdio: ['ignore', 'pipe', 'pipe'], +}) + +const runtimeChecks = ` +assert.equal(root.isInteger, assertions.isInteger) +assert.equal(typeof combinators.shape, 'function') +assert.equal(predicates.isShape({ name: [predicates.isString, false] })({}), true) +assert.equal(predicates.isShape({ name: [predicates.isString, false] })({ name: 2 }), false) +assert.equal(root.isFiniteNumber.check(Infinity), false) +assert.equal(root.isInteger.check(1.5), false) +assert.equal(root.isSafeInteger.check(Number.MAX_SAFE_INTEGER + 1), false) +assert.equal(root.isValidDate.check(new Date('invalid')), false) +assert.equal(root.isError.check(new TypeError('error')), true) +assert.equal(root.isRegExp.check(/pattern/), true) +assert.equal(root.isPromiseLike.check({ then() {} }), true) +assert.equal(root.validate.sync(4, [root.isInteger, root.multipleOf(2)])[0], true) +assert.throws(() => root.validate.sync(['a'], [root.each(root.isString), root.hasLength({ min: 1 })]), /preceding guard/) +assert.deepEqual(jsonSchema.toJsonSchema(root.isSafeInteger), { + type: 'integer', minimum: Number.MIN_SAFE_INTEGER, maximum: Number.MAX_SAFE_INTEGER, +}) +assert.throws(() => jsonSchema.toJsonSchema(root.isRegExp, { mode: 'strict' }), jsonSchema.JsonSchemaExportError) +` + +const typeChecks = ` +import { each, hasLength, hasValue, isDefined, isInteger, isPromiseLike, isString, matches, multipleOf, shape, validate } from '@modulify/validator' +import type { InferConstraint, KnownViolationSubject, ValidationResult, ViolationCodeEntry } from '@modulify/validator' +import { isSafeInteger } from '@modulify/validator/assertions' +import { optional } from '@modulify/validator/combinators' +import { isShape, isString as isStringPredicate } from '@modulify/validator/predicates' +import { toJsonSchema } from '@modulify/validator/json-schema' + +declare module '@modulify/validator' { + interface ViolationCodeRegistry { + 'consumer.named': ViolationCodeEntry<'assertion', 'consumer', readonly [name: string]> + } +} +const named: KnownViolationSubject<'consumer.named'> = { kind: 'assertion', name: 'consumer', code: 'consumer.named', args: ['value'] } +// @ts-expect-error registry augmentation preserves the argument type +const invalidNamed: KnownViolationSubject<'consumer.named'> = { kind: 'assertion', name: 'consumer', code: 'consumer.named', args: [2] } + +const result: ValidationResult = validate.sync(4, [isInteger, multipleOf(2)]) +const safeResult: ValidationResult = validate.sync(4, [isSafeInteger, hasValue({ min: 0 })]) +const profile = shape({ name: [isDefined, isString], nickname: optional(isString) }) +const profileResult = validate.sync({}, profile) +if (profileResult[0]) profileResult[1].name.toUpperCase() +toJsonSchema(profile) +const predicate = isShape({ name: isStringPredicate, nickname: [isStringPredicate, false] }) +const value: unknown = {} +if (predicate(value)) { value.name.toUpperCase(); value.nickname?.toUpperCase() } +const promised: InferConstraint = Promise.resolve('value') +// @ts-expect-error validator does not establish an assertion domain +validate.sync(['a'], [each(isString), hasLength({ min: 1 })]) +// @ts-expect-error refinement input must match the preceding guard +matches.sync(4, [isInteger, hasLength({ min: 1 })]) +void result; void safeResult; void promised; void named; void invalidNamed +` + +try { + const packed = JSON.parse(run('npm', ['pack', '--ignore-scripts', '--json', '--cache', join(consumerRoot, 'cache'), '--pack-destination', consumerRoot], projectRoot)) + const archive = join(consumerRoot, packed[0].filename) + await writeFile(join(consumerRoot, 'package.json'), JSON.stringify({ private: true, type: 'module' })) + run('npm', ['install', '--offline', '--ignore-scripts', '--no-audit', '--no-fund', '--package-lock=false', '--cache', join(consumerRoot, 'cache'), archive]) + + for (const format of ['mjs', 'cjs']) { + const imports = format === 'mjs' + ? `import assert from 'node:assert/strict' +import * as root from '@modulify/validator' +import * as assertions from '@modulify/validator/assertions' +import * as combinators from '@modulify/validator/combinators' +import * as predicates from '@modulify/validator/predicates' +import * as jsonSchema from '@modulify/validator/json-schema'` + : `const assert = require('node:assert/strict') +const root = require('@modulify/validator') +const assertions = require('@modulify/validator/assertions') +const combinators = require('@modulify/validator/combinators') +const predicates = require('@modulify/validator/predicates') +const jsonSchema = require('@modulify/validator/json-schema')` + const file = join(consumerRoot, `runtime.${format}`) + await writeFile(file, imports + runtimeChecks) + run(process.execPath, [file]) + } + + const tsc = join(projectRoot, 'node_modules/typescript/bin/tsc') + for (const format of ['mts', 'cts']) { + const file = join(consumerRoot, `consumer.${format}`) + await writeFile(file, typeChecks) + run(process.execPath, [tsc, '--noEmit', '--strict', '--target', 'ES2022', '--module', 'NodeNext', '--moduleResolution', 'NodeNext', file]) + } + run(process.execPath, [tsc, '--noEmit', '--strict', '--target', 'ES2022', '--module', 'ESNext', '--moduleResolution', 'Bundler', join(consumerRoot, 'consumer.mts')]) + + await writeFile(join(consumerRoot, 'entry.mjs'), 'import { isInteger } from \'@modulify/validator\'; export const check = value => isInteger.check(value)\n') + await build({ + configFile: false, + root: consumerRoot, + logLevel: 'silent', + build: { + minify: false, + lib: { entry: join(consumerRoot, 'entry.mjs'), formats: ['es'], fileName: () => 'consumer.mjs' }, + }, + }) + const bundled = await readFile(join(consumerRoot, 'dist/consumer.mjs'), 'utf8') + for (const unused of ['isPromiseLike', 'collectViolations', 'shape.fields.mismatch', 'type.error']) { + assert.equal(bundled.includes(unused), false, `Unused ${unused} remained in the consumer bundle`) + } + await writeFile(join(consumerRoot, 'bundle-check.mjs'), 'import assert from \'node:assert/strict\'; import { check } from \'./dist/consumer.mjs\'; assert.equal(check(1), true); assert.equal(check(1.5), false)\n') + run(process.execPath, [join(consumerRoot, 'bundle-check.mjs')]) + console.log(`Packed consumer checks passed: ESM, CJS, strict NodeNext/Bundler, tree shaking (${Buffer.byteLength(bundled)} bytes).`) +} catch (error) { + console.error([error.message, error.stdout?.toString(), error.stderr?.toString()].filter(Boolean).join('\n')) + process.exitCode = 1 +} finally { + await rm(consumerRoot, { recursive: true, force: true }) +} diff --git a/vite.config.ts b/vite.config.ts index 7e6ea68..eaa72bb 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -9,16 +9,46 @@ import { join, resolve, } from 'node:path' +import { + cp, + readFile, + readdir, + writeFile, +} from 'node:fs/promises' import { name } from './package.json' import basic from './vite.config.basic' +const writeModuleDeclarations = async () => { + const outputDirectory = resolve(__dirname, 'dist') + await cp(resolve(__dirname, 'types'), join(outputDirectory, 'types'), { recursive: true }) + const files = (await readdir(outputDirectory, { recursive: true })).filter(file => file.endsWith('.d.ts')) + + for (const file of files) { + const source = await readFile(join(outputDirectory, file), 'utf8') + + for (const [declarationExtension, moduleExtension] of [ + ['.d.ts', '.js'], + ['.d.mts', '.mjs'], + ['.d.cts', '.cjs'], + ]) { + const content = source.replace(/(from\s+|import\()(['"])(\.[^'"]+)\2/g, (_, prefix, quote, specifier: string) => { + const target = specifier === '../types' + ? './types/index' + : specifier.replace(/^\.\.\/types\//, './types/') + + return `${prefix}${quote}${target.replace(/\.(?:js|mjs|cjs)$/, '')}${moduleExtension}${quote}` + }) + await writeFile(join(outputDirectory, file.replace(/\.d\.ts$/, declarationExtension)), content) + } + } +} + export default mergeConfig(basic, defineConfig({ build: { lib: { name, - formats: ['es', 'cjs'], entry: { assert: resolve(__dirname, './src/assert.ts'), assertions: resolve(__dirname, './src/assertions.ts'), @@ -35,11 +65,12 @@ export default mergeConfig(basic, defineConfig({ }, minify: false, rollupOptions: { - output: { + output: (['es', 'cjs'] as const).map(format => ({ + format, exports: 'named', - dir: join(__dirname, '/dist'), - chunkFileNames: '[name].[format].js', - }, + dir: resolve(__dirname, 'dist'), + chunkFileNames: `[name]-[hash].${format === 'es' ? 'mjs' : 'cjs'}`, + })), }, }, @@ -48,8 +79,11 @@ export default mergeConfig(basic, defineConfig({ exclude: [ 'scripts/**/*.*', 'tests/**/*.*', + 'vite.config*.ts', + 'vitest.config.ts', ], insertTypesEntry: true, staticImport: true, + afterBuild: writeModuleDeclarations, })], })) From 7f531c7d3e81660afd1ed2f5532be963e80f9340 Mon Sep 17 00:00:00 2001 From: Zaitsev Kirill Date: Sat, 26 Sep 2026 13:08:09 +0400 Subject: [PATCH 10/15] docs: Guard contracts and migration steps were documented --- CHANGELOG.md | 2 +- README.md | 2 + docs/en/00-index.md | 2 + docs/en/04-json-schema-export.md | 5 +++ docs/en/05-public-api.md | 18 ++++++++ docs/en/09-migration.md | 75 +++++++++++++++++++++++++++++++ docs/ru/00-index.md | 2 + docs/ru/04-json-schema-export.md | 5 +++ docs/ru/05-public-api.md | 18 ++++++++ docs/ru/09-migration.md | 76 ++++++++++++++++++++++++++++++++ docs/ru/README.md | 2 + 11 files changed, 206 insertions(+), 1 deletion(-) create mode 100644 docs/en/09-migration.md create mode 100644 docs/ru/09-migration.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 5dafbb6..33aaf98 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. +All notable changes to this project will be documented in this file. Release notes are generated by [@modulify/conventional-release](https://github.com/modulify/conventional/tree/main/packages/conventional-release) from Conventional Commits. ### [0.2.1](https://github.com/modulify/validator/compare/v0.2.0...v0.2.1) (2026-03-09) diff --git a/README.md b/README.md index 55e4a4f..bada69b 100644 --- a/README.md +++ b/README.md @@ -360,6 +360,8 @@ Two additional guides are useful when you want faster practical navigation inste - [Common Recipes](./docs/en/06-common-recipes.md) - task-oriented examples for payload validation, wrapper choice, reusable shapes, form error mapping, and JSON Schema export. - [AI Reference](./docs/en/07-ai-reference.md) - compact contract summary for agents, tooling, and quick semantic lookup. +- [Migration From 0.2.1](./docs/en/09-migration.md) + ## Notes - Assertions return structured metadata instead of messages. diff --git a/docs/en/00-index.md b/docs/en/00-index.md index 7b3eb21..da6d042 100644 --- a/docs/en/00-index.md +++ b/docs/en/00-index.md @@ -9,6 +9,8 @@ - [AI Reference](./07-ai-reference.md) - Compact contract summary for AI agents, tooling, and quick lookup of stable library semantics. - [Violation Code Types](./08-violation-code-types.md) - Detailed guide to `ViolationCodeRegistry`, `ViolationCode`, literal code preservation in descriptors, and external registry augmentation. +- [Migration From 0.2.1](./09-migration.md) - Breaking changes, renamed types, and updated validation contracts. + ## Translations - [Russian](../ru/00-index.md) diff --git a/docs/en/04-json-schema-export.md b/docs/en/04-json-schema-export.md index 97137a1..0ccdf02 100644 --- a/docs/en/04-json-schema-export.md +++ b/docs/en/04-json-schema-export.md @@ -69,6 +69,9 @@ Supported practical mappings include: - `isString` -> `type: 'string'` - `isNumber` -> `type: 'number'` +- `isFiniteNumber` -> `type: 'number'` +- `isInteger` -> `type: 'integer'` +- `isSafeInteger` -> `type: 'integer'`, `minimum: Number.MIN_SAFE_INTEGER`, `maximum: Number.MAX_SAFE_INTEGER` - `isBoolean` -> `type: 'boolean'` - `isNull` -> `type: 'null'` - `isEmail` -> `type: 'string'` plus `format: 'email'` @@ -202,6 +205,8 @@ Important examples: The exporter keeps these boundaries explicit instead of guessing. +`isValidDate`, `isError`, `isRegExp`, and `isPromiseLike` describe runtime values without a faithful JSON Schema representation. Strict mode throws `JsonSchemaExportError`; best-effort mode emits an unconstrained node. JSON numeric values are finite, so `isFiniteNumber` needs no additional schema keyword. + ## Relationship To `describe(...)` `toJsonSchema(...)` is downstream from `describe(...)`. diff --git a/docs/en/05-public-api.md b/docs/en/05-public-api.md index 34d324f..51fa069 100644 --- a/docs/en/05-public-api.md +++ b/docs/en/05-public-api.md @@ -48,6 +48,24 @@ This is the main runtime-facing API surface of the library. Sequential assertion arrays are stage-aware: a compatible tuple such as `[isString, hasLength({ min: 3 })]` is supported directly, while incompatible combinations are rejected by the type system. Refinement assertions are staged helpers, so `validate(...)` and `matches.sync(...)` expect them to appear after a compatible guard instead of being passed on their own. +Structural validators such as `each(...)` reset the assertion stage. Use a new compatible guard before subsequent refinements, for example `[each(isString), isDefined, hasLength({ min: 2 })]`. + +### Built-in value guards + +These checks are available as assertions from the root and `./assertions`, and as boolean type guards from `./predicates`. + +| Check | Accepted values | Violation code | +| --- | --- | --- | +| `isFiniteNumber` | Numbers excluding `NaN` and infinities | `number.finite` | +| `isInteger` | Integer numbers | `number.integer` | +| `isSafeInteger` | Integers within JavaScript's safe integer bounds | `number.safe-integer` | +| `isValidDate` | `Date` instances with a valid timestamp | `date.valid` | +| `isError` | `Error` instances, including subclasses | `type.error` | +| `isRegExp` | `RegExp` instances | `type.regexp` | +| `isPromiseLike` | Objects or functions with a callable `then` property | `type.promise-like` | + +`isNumber` still accepts infinities; `isDate` still accepts invalid `Date` instances. Use the stricter checks when those values must be rejected. `isPromiseLike` checks the presence of a callable `then`, without executing it or inspecting the resolved value. + ## Metadata And Introspection The same root package also includes: diff --git a/docs/en/09-migration.md b/docs/en/09-migration.md new file mode 100644 index 0000000..791094c --- /dev/null +++ b/docs/en/09-migration.md @@ -0,0 +1,75 @@ +# Migrating From 0.2.1 + +[Documentation index](./00-index.md) +[Russian translation](../ru/09-migration.md) + +This guide describes the unreleased changes after `0.2.1`. + +## Object-Level Refinements + +`shape(...).refine(...)` is async-first even when its callback returns a plain value. +Use `shape(...).refine.sync(...)` for rules used by `validate.sync(...)`, +`matches.sync(...)`, or `shape.check(...)`. + +```typescript +const profile = shape({ password: isString, confirmation: isString }) + .refine.sync(value => value.password === value.confirmation ? null : { + code: 'profile.password.mismatch', + }) +``` + +Use `.refine(...)` with `await validate(...)` for asynchronous rules. + +## Staged Assertions + +Guard assertions establish a domain; refinements check properties inside it: + +```typescript +validate.sync('name', [isString, hasLength({ min: 3 })]) +validate.sync(4, [isInteger, multipleOf(2)]) +``` + +Replace standalone refinements such as `validate.sync(value, hasLength(...))` +with a compatible guard followed by the refinement. Incompatible combinations, +such as `[isNumber, hasLength(...)]`, are rejected by TypeScript. + +Structural validators reset the assertion stage. To refine the outer array after +checking its elements, use `[each(isString), isDefined, hasLength({ min: 2 })]`. + +## Public Type Names + +The old names have no compatibility aliases. Update imports and annotations: + +| Old name | Current name | +| --- | --- | +| `ValidationTuple` | `ValidationResult` | +| `InferMaybeManyViolations` | `InferViolations` | +| `ObjectDescriptor` | `ShapeDescriptor` | +| `InferObjectDescriptor` | `InferShape` | +| `PartialObjectDescriptor` | `PartialShapeDescriptor` | +| `MergeObjectDescriptors` | `MergeShapeDescriptors` | +| `ObjectShapeFieldSelector` | `ShapeFieldSelector` | +| `ObjectShapeRefinement` / `ObjectShapeAsyncRefinement` | `ShapeRefinement` | +| `ObjectShapeRefinementSync` / `ObjectShapeSyncRefinement` | `SyncShapeRefinement` | +| `ObjectShapeRefinementIssue` | `ShapeRefinementViolationInput` | +| `ObjectShapeRefineMethod` | `ShapeRefineMethod` | +| `ObjectShapeRefineMethodSync` | `ShapeRefineMethodSync` | +| `DescribeMaybeMany` | `DescribeConstraints` | +| `DescribeObjectDescriptor` | `DescribeShapeDescriptor` | +| `AssertionDescriptorConstraint` | `AssertionConstraintDescriptor` | +| `ConstraintDescriptorBase` | `BaseConstraintDescriptor` | +| `ValidatorDescriptor` | `OpaqueValidatorDescriptor` | +| `GenericObjectShapeRuleDescriptor` | `SyncObjectShapeRuleDescriptor` | + +## Optional Predicate Fields + +`isShape({ name: [isString, false] })` permits an absent `name`, but rejects +`{ name: 2 }` and `{ name: undefined }`. To allow explicit `undefined`, use +`[Or(isString, isUndefined), false]`. Required fields must be present even when +their predicate accepts `undefined`. + +## Package Consumers + +The public root and subpath exports support ESM, CommonJS, and strict TypeScript +with NodeNext or Bundler resolution. Import from package entrypoints rather than +internal files in `dist/`. diff --git a/docs/ru/00-index.md b/docs/ru/00-index.md index 5dabe3f..f8855b2 100644 --- a/docs/ru/00-index.md +++ b/docs/ru/00-index.md @@ -10,6 +10,8 @@ - [Справка для AI](./07-ai-reference.md) - Краткое описание контракта для AI agents, инструментов и быстрого поиска стабильной семантики библиотеки. - [Типы кодов нарушений](./08-violation-code-types.md) - Подробное руководство по `ViolationCodeRegistry`, `ViolationCode`, сохранению literal-кодов в descriptors и внешнему расширению реестра. +- [Миграция с 0.2.1](./09-migration.md) - Breaking changes, переименованные типы и обновлённые контракты валидации. + ## Переводы - [English](../en/00-index.md) diff --git a/docs/ru/04-json-schema-export.md b/docs/ru/04-json-schema-export.md index 0d47d5f..f2a3d96 100644 --- a/docs/ru/04-json-schema-export.md +++ b/docs/ru/04-json-schema-export.md @@ -69,6 +69,9 @@ Exporter покрывает built-in descriptor set, который уже уч - `isString` -> `type: 'string'` - `isNumber` -> `type: 'number'` +- `isFiniteNumber` -> `type: 'number'` +- `isInteger` -> `type: 'integer'` +- `isSafeInteger` -> `type: 'integer'`, `minimum: Number.MIN_SAFE_INTEGER`, `maximum: Number.MAX_SAFE_INTEGER` - `isBoolean` -> `type: 'boolean'` - `isNull` -> `type: 'null'` - `isEmail` -> `type: 'string'` и `format: 'email'` @@ -202,6 +205,8 @@ Strict режим полезен, когда silent fallback был бы вво Exporter фиксирует эти границы явно и не пытается гадать. +`isValidDate`, `isError`, `isRegExp` и `isPromiseLike` описывают runtime-значения без точного представления в JSON Schema. Strict mode бросает `JsonSchemaExportError`, а best-effort mode выдаёт узел без ограничений. Числа JSON конечны, поэтому для `isFiniteNumber` дополнительный schema keyword не нужен. + ## Связь с `describe(...)` `toJsonSchema(...)` находится downstream от `describe(...)`. diff --git a/docs/ru/05-public-api.md b/docs/ru/05-public-api.md index f27df06..c77fed6 100644 --- a/docs/ru/05-public-api.md +++ b/docs/ru/05-public-api.md @@ -48,6 +48,24 @@ Root package включает: Последовательные массивы assertions теперь stage-aware: совместимый кортеж вроде `[isString, hasLength({ min: 3 })]` поддерживается напрямую, а несовместимые комбинации отсекаются типовой системой. Refinement assertions в этой модели являются staged-helper'ами, поэтому `validate(...)` и `matches.sync(...)` ожидают их после совместимого guard-а, а не в одиночку. +Структурные validators вроде `each(...)` сбрасывают assertion stage. Перед следующим refinement нужен новый совместимый guard, например `[each(isString), isDefined, hasLength({ min: 2 })]`. + +### Встроенные проверки значений + +Эти проверки доступны как assertions из root и `./assertions`, а как boolean type guards — из `./predicates`. + +| Проверка | Допустимые значения | Код нарушения | +| --- | --- | --- | +| `isFiniteNumber` | Числа без `NaN` и бесконечностей | `number.finite` | +| `isInteger` | Целые числа | `number.integer` | +| `isSafeInteger` | Целые числа в безопасном диапазоне JavaScript | `number.safe-integer` | +| `isValidDate` | Экземпляры `Date` с корректным timestamp | `date.valid` | +| `isError` | Экземпляры `Error`, включая подклассы | `type.error` | +| `isRegExp` | Экземпляры `RegExp` | `type.regexp` | +| `isPromiseLike` | Объекты или функции с вызываемым свойством `then` | `type.promise-like` | + +`isNumber` по-прежнему принимает бесконечности, а `isDate` — невалидные экземпляры `Date`. Для их отклонения используйте более строгие проверки. `isPromiseLike` проверяет наличие вызываемого `then`, не вызывая его и не проверяя тип результата. + ## Метаданные и интроспекция Тот же root package также включает: diff --git a/docs/ru/09-migration.md b/docs/ru/09-migration.md new file mode 100644 index 0000000..4be6e3b --- /dev/null +++ b/docs/ru/09-migration.md @@ -0,0 +1,76 @@ +# Миграция с 0.2.1 + +[Индекс документации](./00-index.md) +[English](../en/09-migration.md) + +Здесь описаны ещё не выпущенные изменения после `0.2.1`. + +## Правила уровня объекта + +`shape(...).refine(...)` стал async-first, даже если callback возвращает обычное +значение. Для правил, используемых через `validate.sync(...)`, `matches.sync(...)` +или `shape.check(...)`, нужен `shape(...).refine.sync(...)`. + +```typescript +const profile = shape({ password: isString, confirmation: isString }) + .refine.sync(value => value.password === value.confirmation ? null : { + code: 'profile.password.mismatch', + }) +``` + +Асинхронные правила `.refine(...)` используются с `await validate(...)`. + +## Последовательные assertions + +Guard задаёт область допустимых значений, refinement проверяет свойства внутри неё: + +```typescript +validate.sync('name', [isString, hasLength({ min: 3 })]) +validate.sync(4, [isInteger, multipleOf(2)]) +``` + +Замените самостоятельные refinements вроде `validate.sync(value, hasLength(...))` +на совместимый guard и refinement после него. Несовместимые сочетания, например +`[isNumber, hasLength(...)]`, отклоняются TypeScript. + +Структурные validators сбрасывают assertion stage. Для проверки длины внешнего +массива после проверки элементов используйте +`[each(isString), isDefined, hasLength({ min: 2 })]`. + +## Имена публичных типов + +Совместимых aliases для прежних имён нет. Обновите imports и аннотации: + +| Прежнее имя | Новое имя | +| --- | --- | +| `ValidationTuple` | `ValidationResult` | +| `InferMaybeManyViolations` | `InferViolations` | +| `ObjectDescriptor` | `ShapeDescriptor` | +| `InferObjectDescriptor` | `InferShape` | +| `PartialObjectDescriptor` | `PartialShapeDescriptor` | +| `MergeObjectDescriptors` | `MergeShapeDescriptors` | +| `ObjectShapeFieldSelector` | `ShapeFieldSelector` | +| `ObjectShapeRefinement` / `ObjectShapeAsyncRefinement` | `ShapeRefinement` | +| `ObjectShapeRefinementSync` / `ObjectShapeSyncRefinement` | `SyncShapeRefinement` | +| `ObjectShapeRefinementIssue` | `ShapeRefinementViolationInput` | +| `ObjectShapeRefineMethod` | `ShapeRefineMethod` | +| `ObjectShapeRefineMethodSync` | `ShapeRefineMethodSync` | +| `DescribeMaybeMany` | `DescribeConstraints` | +| `DescribeObjectDescriptor` | `DescribeShapeDescriptor` | +| `AssertionDescriptorConstraint` | `AssertionConstraintDescriptor` | +| `ConstraintDescriptorBase` | `BaseConstraintDescriptor` | +| `ValidatorDescriptor` | `OpaqueValidatorDescriptor` | +| `GenericObjectShapeRuleDescriptor` | `SyncObjectShapeRuleDescriptor` | + +## Optional-поля в predicates + +`isShape({ name: [isString, false] })` разрешает отсутствие `name`, но отклоняет +`{ name: 2 }` и `{ name: undefined }`. Для явного `undefined` используйте +`[Or(isString, isUndefined), false]`. Обязательное поле должно присутствовать, +даже если его предикат принимает `undefined`. + +## Потребители пакета + +Публичные root и subpath exports поддерживают ESM, CommonJS и strict TypeScript +с разрешением модулей NodeNext или Bundler. Используйте package entrypoints, +а не внутренние файлы `dist/`. diff --git a/docs/ru/README.md b/docs/ru/README.md index a3d0531..c22e254 100644 --- a/docs/ru/README.md +++ b/docs/ru/README.md @@ -363,6 +363,8 @@ const jsonSchema = toJsonSchema(profile) - [Практические рецепты](./06-common-recipes.md) - практические примеры для валидации payload, выбора wrappers, переиспользуемых shape, сопоставления ошибок формы и экспорта JSON Schema. - [Справка для AI](./07-ai-reference.md) - компактное описание контракта для agents, инструментов и быстрого поиска семантики библиотеки. +- [Миграция с 0.2.1](./09-migration.md) + ## Заметки - Assertions возвращают структурированные метаданные вместо сообщений. From 4224927a3fdd7e5d2360704e1002b414ea6efb59 Mon Sep 17 00:00:00 2001 From: Zaitsev Kirill Date: Sat, 26 Sep 2026 13:28:53 +0400 Subject: [PATCH 11/15] build: Development and release tooling were updated --- .versionrc.json | 12 ------------ .yarnrc.yml | 11 +++++++++++ package.json | 37 +++++++++++++++++++------------------ release.config.mjs | 8 ++++++++ src/assert.ts | 2 ++ src/combinators.ts | 2 +- src/constraints.ts | 3 ++- tests/combinators.test.ts | 1 + tests/validate.test.ts | 1 + tsconfig.json | 3 ++- vite.config.basic.ts | 4 ++-- vite.config.ts | 30 +++++++++++++++--------------- vitest.config.ts | 2 +- 13 files changed, 65 insertions(+), 51 deletions(-) delete mode 100644 .versionrc.json create mode 100644 release.config.mjs diff --git a/.versionrc.json b/.versionrc.json deleted file mode 100644 index e2dffca..0000000 --- a/.versionrc.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "types": [ - {"type": "feat", "section": "Features"}, - {"type": "fix", "section": "Fixes"}, - {"type": "chore", "hidden": true}, - {"type": "docs", "hidden": true}, - {"type": "style", "hidden": true}, - {"type": "refactor", "hidden": true}, - {"type": "perf", "hidden": true}, - {"type": "test", "hidden": true} - ] -} diff --git a/.yarnrc.yml b/.yarnrc.yml index 7b11830..5ef5b89 100644 --- a/.yarnrc.yml +++ b/.yarnrc.yml @@ -3,3 +3,14 @@ enableGlobalCache: false nodeLinker: node-modules yarnPath: .yarn/releases/yarn-4.12.0.cjs + +# The TypeScript config loader needs peers forwarded through @commitlint/load. +packageExtensions: + "@commitlint/load@21.2.3": + peerDependencies: + "@types/node": "*" + typescript: ">=5" + "@commitlint/cli@21.2.3": + peerDependencies: + "@types/node": "*" + typescript: ">=5" diff --git a/package.json b/package.json index 0a8da5d..3ef7457 100644 --- a/package.json +++ b/package.json @@ -81,12 +81,13 @@ }, "scripts": { "build": "vite build", - "lint": "eslint src tests types scripts vite.config.ts", + "lint": "eslint src tests types scripts vite.config.ts release.config.mjs", "prepare": "husky", - "release": "npx --yes standard-version", - "release:minor": "npx --yes standard-version --release-as minor", - "release:patch": "npx --yes standard-version --release-as patch", - "release:major": "npx --yes standard-version --release-as major", + "release": "conventional-release --tags", + "release:dry": "conventional-release --dry --verbose --tags", + "release:minor": "conventional-release --tags --release-as minor", + "release:patch": "conventional-release --tags --release-as patch", + "release:major": "conventional-release --tags --release-as major", "stats": "gzip -c ./dist/index.mjs | wc -c", "test": "vitest run", "test:d": "vitest run --typecheck.only", @@ -96,22 +97,22 @@ "typecheck": "tsc --noEmit" }, "devDependencies": { - "@commitlint/cli": "^20.4.3", - "@commitlint/config-conventional": "^20.4.3", + "@commitlint/cli": "^21.2.3", + "@commitlint/config-conventional": "^21.2.3", "@eslint/js": "^10.0.1", - "@types/node": "^25.3.5", - "@vitest/coverage-v8": "4.0.18", - "@vitest/ui": "4.0.18", - "eslint": "^10.0.3", - "globals": "^17.4.0", + "@modulify/conventional-release": "^0.1.3", + "@types/node": "^24.19.0", + "@vitest/coverage-v8": "5.0.2", + "@vitest/ui": "5.0.2", + "eslint": "^10.11.0", + "globals": "^17.12.0", "husky": "^9.1.7", - "ts-node": "^10.9.2", "tslib": "^2.8.1", - "typescript": "^5.5.4", - "typescript-eslint": "^8.56.1", - "vite": "^7.3.1", - "vite-plugin-dts": "^4.5.4", - "vitest": "^4.0.18" + "typescript": "~6.0.3", + "typescript-eslint": "^8.70.1", + "unplugin-dts": "^1.1.1", + "vite": "^8.3.1", + "vitest": "5.0.2" }, "resolutions": { "minimatch@npm:10.2.1": "npm:10.2.4", diff --git a/release.config.mjs b/release.config.mjs new file mode 100644 index 0000000..6bab202 --- /dev/null +++ b/release.config.mjs @@ -0,0 +1,8 @@ +/** @type {import('@modulify/conventional-release').Options} */ +export default { + mode: 'sync', + tagPrefix: 'v', + commitMessage: ({ tag }) => `chore: Released ${tag}`, + // Only the root version changes; the dependency graph and lockfile stay intact. + install: false, +} diff --git a/src/assert.ts b/src/assert.ts index abd437a..dcc71bc 100644 --- a/src/assert.ts +++ b/src/assert.ts @@ -222,6 +222,8 @@ export const createRefinement = < }, constraints: C = [] as unknown as C ): Refinement, C, ResolveAssertionCode, ResolveAssertionArgs, Name> => { + // Only refinement constraints inspect values; this predicate preserves the input type. + // eslint-disable-next-line @typescript-eslint/no-unused-vars return createAssertion('refinement', ((value: unknown): value is ConstraintInput => true), meta, constraints) as Refinement< ConstraintInput, C, diff --git a/src/combinators.ts b/src/combinators.ts index d4166d4..3429af8 100644 --- a/src/combinators.ts +++ b/src/combinators.ts @@ -221,7 +221,7 @@ const getPathValue = (value: unknown, path: readonly PropertyKey[]) => path.redu return Reflect.get(Object(current), key) }, value) -const toPath = (selector: ShapeFieldSelector): PropertyKey[] => Array.isArray(selector) +const toPath = (selector: ShapeFieldSelector): PropertyKey[] => typeof selector === 'object' ? [...selector] : [selector] diff --git a/src/constraints.ts b/src/constraints.ts index 9f8cdb0..7f1bc3e 100644 --- a/src/constraints.ts +++ b/src/constraints.ts @@ -15,9 +15,10 @@ import { export const isValidator = (constraint: Constraint): constraint is Validator => 'run' in constraint export function arrayify(value: MaybeMany): T[] { + // Array.isArray does not exclude readonly arrays from the scalar branch. return Array.isArray(value) ? [...value] - : [value] + : [value as T] } export function matchesConstraints>( diff --git a/tests/combinators.test.ts b/tests/combinators.test.ts index df221ad..a26e662 100644 --- a/tests/combinators.test.ts +++ b/tests/combinators.test.ts @@ -69,6 +69,7 @@ const createAsyncAssertion = ( }, check: { enumerable: true, + // eslint-disable-next-line @typescript-eslint/no-unused-vars value: (_value: unknown): _value is unknown => true, }, }) diff --git a/tests/validate.test.ts b/tests/validate.test.ts index 6b2dc1c..940abd7 100644 --- a/tests/validate.test.ts +++ b/tests/validate.test.ts @@ -77,6 +77,7 @@ const createAsyncAssertion = ( }, check: { enumerable: true, + // eslint-disable-next-line @typescript-eslint/no-unused-vars value: (_value: unknown): _value is unknown => true, }, }) diff --git a/tsconfig.json b/tsconfig.json index 6c98536..c12b406 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -10,7 +10,8 @@ "~types/*": ["./types/*"] }, "resolveJsonModule": true, - "target": "ES2017" + "target": "ES2017", + "allowImportingTsExtensions": true }, "include": [ "src/**/*.ts", diff --git a/vite.config.basic.ts b/vite.config.basic.ts index 978c0c2..c9382c2 100644 --- a/vite.config.basic.ts +++ b/vite.config.basic.ts @@ -6,8 +6,8 @@ export default defineConfig({ resolve: { alias: { - '@': resolve(__dirname, './src'), - '~types': resolve(__dirname, './types/'), + '@': resolve(import.meta.dirname, './src'), + '~types': resolve(import.meta.dirname, './types/'), }, }, }) \ No newline at end of file diff --git a/vite.config.ts b/vite.config.ts index eaa72bb..81c4eff 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -3,7 +3,7 @@ import { mergeConfig, } from 'vite' -import dts from 'vite-plugin-dts' +import dts from 'unplugin-dts/vite' import { join, @@ -16,13 +16,13 @@ import { writeFile, } from 'node:fs/promises' -import { name } from './package.json' +import packageJson from './package.json' with { type: 'json' } -import basic from './vite.config.basic' +import basic from './vite.config.basic.ts' const writeModuleDeclarations = async () => { - const outputDirectory = resolve(__dirname, 'dist') - await cp(resolve(__dirname, 'types'), join(outputDirectory, 'types'), { recursive: true }) + const outputDirectory = resolve(import.meta.dirname, 'dist') + await cp(resolve(import.meta.dirname, 'types'), join(outputDirectory, 'types'), { recursive: true }) const files = (await readdir(outputDirectory, { recursive: true })).filter(file => file.endsWith('.d.ts')) for (const file of files) { @@ -48,15 +48,15 @@ const writeModuleDeclarations = async () => { export default mergeConfig(basic, defineConfig({ build: { lib: { - name, + name: packageJson.name, entry: { - assert: resolve(__dirname, './src/assert.ts'), - assertions: resolve(__dirname, './src/assertions.ts'), - combinators: resolve(__dirname, './src/combinators.ts'), - 'json-schema': resolve(__dirname, './src/json-schema.ts'), - metadata: resolve(__dirname, './src/metadata.ts'), - predicates: resolve(__dirname, './src/predicates.ts'), - index: resolve(__dirname, './src/index.ts'), + assert: resolve(import.meta.dirname, './src/assert.ts'), + assertions: resolve(import.meta.dirname, './src/assertions.ts'), + combinators: resolve(import.meta.dirname, './src/combinators.ts'), + 'json-schema': resolve(import.meta.dirname, './src/json-schema.ts'), + metadata: resolve(import.meta.dirname, './src/metadata.ts'), + predicates: resolve(import.meta.dirname, './src/predicates.ts'), + index: resolve(import.meta.dirname, './src/index.ts'), }, fileName: (format, entryName) => `${entryName}.${{ cjs: 'cjs', @@ -64,11 +64,11 @@ export default mergeConfig(basic, defineConfig({ }[format as 'es' | 'cjs']}`, }, minify: false, - rollupOptions: { + rolldownOptions: { output: (['es', 'cjs'] as const).map(format => ({ format, exports: 'named', - dir: resolve(__dirname, 'dist'), + dir: resolve(import.meta.dirname, 'dist'), chunkFileNames: `[name]-[hash].${format === 'es' ? 'mjs' : 'cjs'}`, })), }, diff --git a/vitest.config.ts b/vitest.config.ts index 896b4a9..1297d8f 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -3,7 +3,7 @@ import { mergeConfig, } from 'vitest/config' -import basic from './vite.config.basic' +import basic from './vite.config.basic.ts' export default mergeConfig(basic, defineConfig({ test: { From 6bae5abecdd5c827da733a53b17a536f8c72d839 Mon Sep 17 00:00:00 2001 From: Zaitsev Kirill Date: Sat, 26 Sep 2026 13:29:12 +0400 Subject: [PATCH 12/15] chore: Updated yarn.lock --- yarn.lock | 3182 ++++++++++++++++++++++------------------------------- 1 file changed, 1298 insertions(+), 1884 deletions(-) diff --git a/yarn.lock b/yarn.lock index 7296622..59dde7a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -22,17 +22,10 @@ __metadata: languageName: node linkType: hard -"@babel/helper-string-parser@npm:^7.25.7": - version: 7.25.7 - resolution: "@babel/helper-string-parser@npm:7.25.7" - checksum: 10c0/73ef2ceb81f8294678a0afe8ab0103729c0370cac2e830e0d5128b03be5f6a2635838af31d391d763e3c5a4460ed96f42fd7c9b552130670d525be665913bc4c - languageName: node - linkType: hard - -"@babel/helper-string-parser@npm:^7.27.1": - version: 7.27.1 - resolution: "@babel/helper-string-parser@npm:7.27.1" - checksum: 10c0/8bda3448e07b5583727c103560bcf9c4c24b3c1051a4c516d4050ef69df37bb9a4734a585fe12725b8c2763de0a265aa1e909b485a4e3270b7cfd3e4dbe4b602 +"@babel/helper-string-parser@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/helper-string-parser@npm:7.29.7" + checksum: 10c0/194bc0f1716e396d5ffde56ad6119745fb9557662c98611590e5e454906783a4ccb21ce93056b8eb69a4909044834e45d96e50ac695bbe9e3221648fe033c06c languageName: node linkType: hard @@ -43,17 +36,10 @@ __metadata: languageName: node linkType: hard -"@babel/helper-validator-identifier@npm:^7.25.7": - version: 7.25.7 - resolution: "@babel/helper-validator-identifier@npm:7.25.7" - checksum: 10c0/07438e5bf01ab2882a15027fdf39ac3b0ba1b251774a5130917907014684e2f70fef8fd620137ca062c4c4eedc388508d2ea7a3a7d9936a32785f4fe116c68c0 - languageName: node - linkType: hard - -"@babel/helper-validator-identifier@npm:^7.28.5": - version: 7.28.5 - resolution: "@babel/helper-validator-identifier@npm:7.28.5" - checksum: 10c0/42aaebed91f739a41f3d80b72752d1f95fd7c72394e8e4bd7cdd88817e0774d80a432451bcba17c2c642c257c483bf1d409dd4548883429ea9493a3bc4ab0847 +"@babel/helper-validator-identifier@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/helper-validator-identifier@npm:7.29.7" + checksum: 10c0/4795354e7ae0dcafa72de1cd04ec51252dc1498517170beaf019e03effc5b7bf13c6b21a3949a77e07b8125be7f106ed1131350d8ebd4566ae874094a726d62b languageName: node linkType: hard @@ -68,434 +54,264 @@ __metadata: languageName: node linkType: hard -"@babel/parser@npm:^7.25.3": - version: 7.25.8 - resolution: "@babel/parser@npm:7.25.8" +"@babel/parser@npm:^7.29.7": + version: 7.29.9 + resolution: "@babel/parser@npm:7.29.9" dependencies: - "@babel/types": "npm:^7.25.8" + "@babel/types": "npm:^7.29.8" bin: parser: ./bin/babel-parser.js - checksum: 10c0/a1a13845b7e8dda4c970791814a4bbf60004969882f18f470e260ad822d2e1f8941948f851e9335895563610f240fa6c98481ce8019865e469502bbf21daafa4 + checksum: 10c0/171b16eb4527cdbbb72cb98710f91acb22356e2c40196ba3b7a1b84efa8c55d0b0992f399c8c675c3e17f67fd133676c5c4118e5f20db243f427c8b29b0c3f40 languageName: node linkType: hard -"@babel/parser@npm:^7.29.0": - version: 7.29.0 - resolution: "@babel/parser@npm:7.29.0" +"@babel/types@npm:^7.29.7, @babel/types@npm:^7.29.8": + version: 7.29.8 + resolution: "@babel/types@npm:7.29.8" dependencies: - "@babel/types": "npm:^7.29.0" - bin: - parser: ./bin/babel-parser.js - checksum: 10c0/333b2aa761264b91577a74bee86141ef733f9f9f6d4fc52548e4847dc35dfbf821f58c46832c637bfa761a6d9909d6a68f7d1ed59e17e4ffbb958dc510c17b62 + "@babel/helper-string-parser": "npm:^7.29.7" + "@babel/helper-validator-identifier": "npm:^7.29.7" + checksum: 10c0/be7c279f0abf2a086c633e21b49c7ca80275d05283cc5a268b67a708c9914bd0c944f1422b3eb3cb37682a2af5d560abf520ccf9b01b53ecbfe6b71fbc3fdde6 languageName: node linkType: hard -"@babel/types@npm:^7.25.8": - version: 7.25.8 - resolution: "@babel/types@npm:7.25.8" - dependencies: - "@babel/helper-string-parser": "npm:^7.25.7" - "@babel/helper-validator-identifier": "npm:^7.25.7" - to-fast-properties: "npm:^2.0.0" - checksum: 10c0/55ca2d6df6426c98db2769ce884ce5e9de83a512ea2dd7bcf56c811984dc14351cacf42932a723630c5afcff2455809323decd645820762182f10b7b5252b59f +"@bcoe/v8-coverage@npm:^1.0.2": + version: 1.0.2 + resolution: "@bcoe/v8-coverage@npm:1.0.2" + checksum: 10c0/1eb1dc93cc17fb7abdcef21a6e7b867d6aa99a7ec88ec8207402b23d9083ab22a8011213f04b2cf26d535f1d22dc26139b7929e6c2134c254bd1e14ba5e678c3 languageName: node linkType: hard -"@babel/types@npm:^7.29.0": - version: 7.29.0 - resolution: "@babel/types@npm:7.29.0" +"@cacheable/memory@npm:^2.2.0": + version: 2.2.0 + resolution: "@cacheable/memory@npm:2.2.0" dependencies: - "@babel/helper-string-parser": "npm:^7.27.1" - "@babel/helper-validator-identifier": "npm:^7.28.5" - checksum: 10c0/23cc3466e83bcbfab8b9bd0edaafdb5d4efdb88b82b3be6728bbade5ba2f0996f84f63b1c5f7a8c0d67efded28300898a5f930b171bb40b311bca2029c4e9b4f + "@cacheable/utils": "npm:^2.5.0" + "@keyv/bigmap": "npm:^1.3.1" + hookified: "npm:^1.15.1" + keyv: "npm:^5.6.0" + checksum: 10c0/c74889eb09871be0696065778fff013a498ddc73b538d113c2cb6ece361746f2959fbddfa4ce03ce53c9259998be05949e393212e72413dbd07616744eabd261 languageName: node linkType: hard -"@bcoe/v8-coverage@npm:^1.0.2": - version: 1.0.2 - resolution: "@bcoe/v8-coverage@npm:1.0.2" - checksum: 10c0/1eb1dc93cc17fb7abdcef21a6e7b867d6aa99a7ec88ec8207402b23d9083ab22a8011213f04b2cf26d535f1d22dc26139b7929e6c2134c254bd1e14ba5e678c3 +"@cacheable/utils@npm:^2.5.0": + version: 2.5.0 + resolution: "@cacheable/utils@npm:2.5.0" + dependencies: + hashery: "npm:^1.5.1" + keyv: "npm:^5.6.0" + checksum: 10c0/99e901accd71eeb0bce91484d6c66d7ef2c731b541561eb5dc9ea7fed78bdc0b65e5dd2a80edde50191143bcfc5f238fc4742475b32e82d43b43eea224038e35 languageName: node linkType: hard -"@commitlint/cli@npm:^20.4.3": - version: 20.4.3 - resolution: "@commitlint/cli@npm:20.4.3" +"@commitlint/cli@npm:^21.2.3": + version: 21.2.3 + resolution: "@commitlint/cli@npm:21.2.3" dependencies: - "@commitlint/format": "npm:^20.4.3" - "@commitlint/lint": "npm:^20.4.3" - "@commitlint/load": "npm:^20.4.3" - "@commitlint/read": "npm:^20.4.3" - "@commitlint/types": "npm:^20.4.3" + "@commitlint/config-conventional": "npm:^21.2.3" + "@commitlint/format": "npm:^21.2.3" + "@commitlint/lint": "npm:^21.2.3" + "@commitlint/load": "npm:^21.2.3" + "@commitlint/read": "npm:^21.2.3" + "@commitlint/types": "npm:^21.2.3" tinyexec: "npm:^1.0.0" - yargs: "npm:^17.0.0" + yargs: "npm:^18.0.0" bin: - commitlint: ./cli.js - checksum: 10c0/1f54b6870a16548a6af189336c479ed49ab8da615fbaec4c8ff3124a115db0a38bdbd07d49afe27e0d1ac35ba723640532db2b4f3c96720515eca0aa9265747d + commitlint: cli.js + checksum: 10c0/544e86b5e5af0cbae5a55b5643dd9a8565b7f00e435378b46eb372a0b46adf330577b7d5c25abddbb30d4aa3ad78b114b2514ca936aeff3b310eb6e5f13dfc9c languageName: node linkType: hard -"@commitlint/config-conventional@npm:^20.4.3": - version: 20.4.3 - resolution: "@commitlint/config-conventional@npm:20.4.3" +"@commitlint/config-conventional@npm:^21.2.3": + version: 21.2.3 + resolution: "@commitlint/config-conventional@npm:21.2.3" dependencies: - "@commitlint/types": "npm:^20.4.3" - conventional-changelog-conventionalcommits: "npm:^9.2.0" - checksum: 10c0/c7c6fb0b3201b17fae3a3e488b20795e7facbda511017d96939029f83d320567c310576ec9074f9536a3c811ea5d8038b3ddc92905c198e24c00b1a2759a8c84 + "@commitlint/types": "npm:^21.2.3" + conventional-changelog-conventionalcommits: "npm:^10.0.0" + checksum: 10c0/584a7b436d6f2aace31f9a35c48da213aa5eed01f7d21ca6db3800992e59c0f52894e54d2006f1c42739b8edce509387bd2cba783a5b18088db58da3b2afbf44 languageName: node linkType: hard -"@commitlint/config-validator@npm:^20.4.3": - version: 20.4.3 - resolution: "@commitlint/config-validator@npm:20.4.3" +"@commitlint/config-validator@npm:^21.2.3": + version: 21.2.3 + resolution: "@commitlint/config-validator@npm:21.2.3" dependencies: - "@commitlint/types": "npm:^20.4.3" + "@commitlint/types": "npm:^21.2.3" ajv: "npm:^8.11.0" - checksum: 10c0/e65f2bd3cc00b0b8b46c9bf830f78c90c60eb0d864d8e3cf02badf83196637ce7c0c9febabd91bb66d9b77138bc3e741416fff1fb4ffa4054a877cac75f2bae7 + checksum: 10c0/54641610bf9863ba085be7e806a34324e67b8ee8e3f4c12fe7c3f1116a385729eabd0d16f42823d60128bc6de99742e31112bc54c70ef45b5c439cad6fd95d8d languageName: node linkType: hard -"@commitlint/ensure@npm:^20.4.3": - version: 20.4.3 - resolution: "@commitlint/ensure@npm:20.4.3" +"@commitlint/ensure@npm:^21.2.3": + version: 21.2.3 + resolution: "@commitlint/ensure@npm:21.2.3" dependencies: - "@commitlint/types": "npm:^20.4.3" - lodash.camelcase: "npm:^4.3.0" - lodash.kebabcase: "npm:^4.1.1" - lodash.snakecase: "npm:^4.1.1" - lodash.startcase: "npm:^4.4.0" - lodash.upperfirst: "npm:^4.3.1" - checksum: 10c0/aa1f42317af156f8af1314311f9195f6d8150807e5d5971432bd086b8ead39c8c6166bf426d413790bb105e015f9c556de4d69083cb0a897293bed56b884d6a4 + "@commitlint/types": "npm:^21.2.3" + es-toolkit: "npm:^1.46.0" + checksum: 10c0/55314de93bc297624c62e10f68622928fde9b3a8e9522d0f8c1b54e31c86d5b1e98518682673651e7e5894be821d683099b23eb26aae300b0e7958ee62b340bc languageName: node linkType: hard -"@commitlint/execute-rule@npm:^20.0.0": - version: 20.0.0 - resolution: "@commitlint/execute-rule@npm:20.0.0" - checksum: 10c0/a1035ae9d6842489e617f18b244e6e53ac44b051b54501b9544019d33da924c877d7b893bfa2a66ad325a9c2ff65b11137d5383743e31d3e0b606decc1619584 +"@commitlint/execute-rule@npm:^21.0.1": + version: 21.0.1 + resolution: "@commitlint/execute-rule@npm:21.0.1" + checksum: 10c0/84fd03f20e808b9f4b68193410d9be90c54b3599f81e82ac23847e90ccba4b52768cfd1ee637931a6ca90575eaefa6ed5fc43a5398d52a4e2d6bef6dd354227e languageName: node linkType: hard -"@commitlint/format@npm:^20.4.3": - version: 20.4.3 - resolution: "@commitlint/format@npm:20.4.3" +"@commitlint/format@npm:^21.2.3": + version: 21.2.3 + resolution: "@commitlint/format@npm:21.2.3" dependencies: - "@commitlint/types": "npm:^20.4.3" + "@commitlint/types": "npm:^21.2.3" picocolors: "npm:^1.1.1" - checksum: 10c0/e221f61a07eaf917185b8f30ff6405f642495a4b199cbfd4a0f130a11068ed5afceb938790fb35383aea9fed101fd4777dd169962a9a721e50851cdadf8044a2 + checksum: 10c0/d87685e1e10d3338d4d1cab929af03d938070b1bfed79c16ef6fb54a95e1f5e11d56ed7ac188e332b0db8ab25302426087b2152a58334c09c6764497fd2b3465 languageName: node linkType: hard -"@commitlint/is-ignored@npm:^20.4.3": - version: 20.4.3 - resolution: "@commitlint/is-ignored@npm:20.4.3" +"@commitlint/is-ignored@npm:^21.2.3": + version: 21.2.3 + resolution: "@commitlint/is-ignored@npm:21.2.3" dependencies: - "@commitlint/types": "npm:^20.4.3" + "@commitlint/types": "npm:^21.2.3" semver: "npm:^7.6.0" - checksum: 10c0/bc118de0aabc63dc82b85e96119489e3b008c5db63c1d9dfccd6c465ba422734a0b80f465382ac38a9fb07b9a80995e4eeb529214ad3c2fe93d80e505fb43fbd + checksum: 10c0/bf2b8a47149c537cb90bacc9dec12cfeae891597490e3919813deacaec3b64821268c2595c84327641b9b1e5988e2668362ecca4cdf011141deac1ad3956f56b languageName: node linkType: hard -"@commitlint/lint@npm:^20.4.3": - version: 20.4.3 - resolution: "@commitlint/lint@npm:20.4.3" +"@commitlint/lint@npm:^21.2.3": + version: 21.2.3 + resolution: "@commitlint/lint@npm:21.2.3" dependencies: - "@commitlint/is-ignored": "npm:^20.4.3" - "@commitlint/parse": "npm:^20.4.3" - "@commitlint/rules": "npm:^20.4.3" - "@commitlint/types": "npm:^20.4.3" - checksum: 10c0/e6af7cbbf935a393a970352674f88ec3e05f08c9f88f638b81279febf77c1795d75112e27248f7788a14e0747ec06fa2ab3efca5d0836070d35b0283fa515817 + "@commitlint/is-ignored": "npm:^21.2.3" + "@commitlint/parse": "npm:^21.2.3" + "@commitlint/rules": "npm:^21.2.3" + "@commitlint/types": "npm:^21.2.3" + checksum: 10c0/fe572e141f84b2a289e7c33356b8adaecc75d09e5e4cb07f173e0d80ea5f1070897536729fab5f1e0123e77a06982c97c3a417ae84b46877d7bd0913e382b647 languageName: node linkType: hard -"@commitlint/load@npm:^20.4.3": - version: 20.4.3 - resolution: "@commitlint/load@npm:20.4.3" +"@commitlint/load@npm:^21.2.3": + version: 21.2.3 + resolution: "@commitlint/load@npm:21.2.3" dependencies: - "@commitlint/config-validator": "npm:^20.4.3" - "@commitlint/execute-rule": "npm:^20.0.0" - "@commitlint/resolve-extends": "npm:^20.4.3" - "@commitlint/types": "npm:^20.4.3" + "@commitlint/config-validator": "npm:^21.2.3" + "@commitlint/execute-rule": "npm:^21.0.1" + "@commitlint/resolve-extends": "npm:^21.2.3" + "@commitlint/types": "npm:^21.2.3" cosmiconfig: "npm:^9.0.1" cosmiconfig-typescript-loader: "npm:^6.1.0" + es-toolkit: "npm:^1.46.0" is-plain-obj: "npm:^4.1.0" - lodash.mergewith: "npm:^4.6.2" picocolors: "npm:^1.1.1" - checksum: 10c0/80fb52e259492a130701cfe637e9c06a6151a980bd51f0d2c011f7e973f7188d8c3d52d078302b75e4e767d618ebdca3a49935268a38e807298bf08fa7f5e49d + checksum: 10c0/b79e17711857fe6a14459e1acb63f233d39bcfacdab8ebbbc60bca0f87843768e9c6a40ccea5e8ab6e095c3eb79d74c61d910e620eef33fe673a034494b74e50 languageName: node linkType: hard -"@commitlint/message@npm:^20.4.3": - version: 20.4.3 - resolution: "@commitlint/message@npm:20.4.3" - checksum: 10c0/16f7a67d17df916830a9d6b2cdbaef0d680afcc47e9de53b18c7580bcbb50d8241b86c0e26a4ae71bd1d4a1025b318eb6c920e75fbc97aee7341744d4ef5dcbc +"@commitlint/message@npm:^21.2.0": + version: 21.2.0 + resolution: "@commitlint/message@npm:21.2.0" + checksum: 10c0/f3f97efd382970316aac689545b7f7bc278e114cce0540469ba56539f2ab78089b84bd0137c82be0908d1e251890e035348acd369c24c4011abf6445d8ec928f languageName: node linkType: hard -"@commitlint/parse@npm:^20.4.3": - version: 20.4.3 - resolution: "@commitlint/parse@npm:20.4.3" +"@commitlint/parse@npm:^21.2.3": + version: 21.2.3 + resolution: "@commitlint/parse@npm:21.2.3" dependencies: - "@commitlint/types": "npm:^20.4.3" - conventional-changelog-angular: "npm:^8.2.0" - conventional-commits-parser: "npm:^6.3.0" - checksum: 10c0/404fecad36b1586a611f1a56e8f7b5c39482c5f010949029020e23103464ab03bece04083557443ec3055917e632118c8ba52cc2c3ab76bac95b704d9338a272 + "@commitlint/types": "npm:^21.2.3" + conventional-changelog-angular: "npm:^9.0.0" + conventional-commits-parser: "npm:^7.0.0" + checksum: 10c0/5692b5298bf9668d45cde1a33087bf40c17ccf9c6421dbe314740b5a9779386261532386c4dfae421d82c80dbc1742ca5a78150dfeb6891421570b4206bb343b languageName: node linkType: hard -"@commitlint/read@npm:^20.4.3": - version: 20.4.3 - resolution: "@commitlint/read@npm:20.4.3" +"@commitlint/read@npm:^21.2.3": + version: 21.2.3 + resolution: "@commitlint/read@npm:21.2.3" dependencies: - "@commitlint/top-level": "npm:^20.4.3" - "@commitlint/types": "npm:^20.4.3" - git-raw-commits: "npm:^4.0.0" - minimist: "npm:^1.2.8" + "@commitlint/top-level": "npm:^21.2.0" + "@commitlint/types": "npm:^21.2.3" + "@conventional-changelog/git-client": "npm:^3.0.0" tinyexec: "npm:^1.0.0" - checksum: 10c0/3e7f88ad9bd780f6b0ec6511d00d222aa61d1fd8fad98d7dfdf9d98d112b655c5ae0c2d575709008f01ceb049e134ccfc2fb21ea5948a93ad60c65fada289c49 + checksum: 10c0/7a0421c9135dbef584ecf8e4aa8d3eee2536cf575dab8dbcbf7ea8d79753fd31df5701cb877f413189763f1d2e0e6fbc7b6e2061b35b15acca1ed602a0b28ca3 languageName: node linkType: hard -"@commitlint/resolve-extends@npm:^20.4.3": - version: 20.4.3 - resolution: "@commitlint/resolve-extends@npm:20.4.3" +"@commitlint/resolve-extends@npm:^21.2.3": + version: 21.2.3 + resolution: "@commitlint/resolve-extends@npm:21.2.3" dependencies: - "@commitlint/config-validator": "npm:^20.4.3" - "@commitlint/types": "npm:^20.4.3" - global-directory: "npm:^4.0.1" - import-meta-resolve: "npm:^4.0.0" - lodash.mergewith: "npm:^4.6.2" + "@commitlint/config-validator": "npm:^21.2.3" + "@commitlint/types": "npm:^21.2.3" + es-toolkit: "npm:^1.46.0" + global-directory: "npm:^5.0.0" resolve-from: "npm:^5.0.0" - checksum: 10c0/09220329bacdcb0c90bb93d18e9d57bb832a64a2683bd39d183e07b19131aa19999c61abf30159989432755997852e3fb78227333b46ad662dd62b7c91c01a78 + checksum: 10c0/527fb212ced6b5a1009f2a7ee972e58369196030d846d23f59a141c0f22e1b22fc19bfec0ff2739d63f72fa764d62dd78c0204d435f1134d62e0b88ea1defcd4 languageName: node linkType: hard -"@commitlint/rules@npm:^20.4.3": - version: 20.4.3 - resolution: "@commitlint/rules@npm:20.4.3" +"@commitlint/rules@npm:^21.2.3": + version: 21.2.3 + resolution: "@commitlint/rules@npm:21.2.3" dependencies: - "@commitlint/ensure": "npm:^20.4.3" - "@commitlint/message": "npm:^20.4.3" - "@commitlint/to-lines": "npm:^20.0.0" - "@commitlint/types": "npm:^20.4.3" - checksum: 10c0/03348731a9d01acd65a7e5a19c6a24d1fb5418bad09c9d4bde40081a1c75d7e06157049a8f8973ecdfa640f3c1dd8f70b8ce678e6a3456b855198dcaa51ca667 + "@commitlint/ensure": "npm:^21.2.3" + "@commitlint/message": "npm:^21.2.0" + "@commitlint/to-lines": "npm:^21.0.1" + "@commitlint/types": "npm:^21.2.3" + checksum: 10c0/cb15fe727d0a0a8845b472eef1dc61885862ee23e1afd154e55a288a486fbc2a228ec87f0152a58a4d27646cc0a19eddee0855c15a3f528653b5c19512f0e115 languageName: node linkType: hard -"@commitlint/to-lines@npm:^20.0.0": - version: 20.0.0 - resolution: "@commitlint/to-lines@npm:20.0.0" - checksum: 10c0/49bc05eb0649adc6f4740a4f3976cc43402080bd9d90567c654180f90c0b6deb9a922b0efbde38567ac1def8f63cc506589124cc7f862e3914d30e13f29997c0 +"@commitlint/to-lines@npm:^21.0.1": + version: 21.0.1 + resolution: "@commitlint/to-lines@npm:21.0.1" + checksum: 10c0/bdf0640e260e11aeb6c6450095477ff123d2792c19307aba11ffaf35170ec8f05244d106edfecfb979fe5c1559b821db77a9d844261ef443895cee1d847e15ca languageName: node linkType: hard -"@commitlint/top-level@npm:^20.4.3": - version: 20.4.3 - resolution: "@commitlint/top-level@npm:20.4.3" +"@commitlint/top-level@npm:^21.2.0": + version: 21.2.0 + resolution: "@commitlint/top-level@npm:21.2.0" dependencies: escalade: "npm:^3.2.0" - checksum: 10c0/52a1f59677d3d2e4bdd998bbde6e7e3848ce52372c7bd394ad24555820cadbf6820886bc33bf42a8c73878c6f0b211334ce93c4f4fbf6d8e8022b72c5c39ebd5 + checksum: 10c0/2358c836db34a0405b8e7e4eda38ca120d80dafa0b6ca84b12460131649d7fbdad35e5b68de19db51cf3a4725ae8a721f8f1f6909dcd161d60e3953d96f775f3 languageName: node linkType: hard -"@commitlint/types@npm:^20.4.3": - version: 20.4.3 - resolution: "@commitlint/types@npm:20.4.3" +"@commitlint/types@npm:^21.2.3": + version: 21.2.3 + resolution: "@commitlint/types@npm:21.2.3" dependencies: - conventional-commits-parser: "npm:^6.3.0" + conventional-commits-parser: "npm:^7.0.0" picocolors: "npm:^1.1.1" - checksum: 10c0/2181f9696f95b828551521e576fe786f81de5b5669e0ac876808e86811587011f7b78644f97c1d6623cc2bad68e0592416327080f14f153deccc20c5d295367e + checksum: 10c0/64a2dd651b6c54b0e332a77d5e39fac823cf5750f0f9d8f6f562b97143ee2b76b689537a2d2bf98e64258b5d951fb0e21bc83bcf7969ce4b084f8a588d933740 languageName: node linkType: hard -"@cspotcode/source-map-support@npm:^0.8.0": - version: 0.8.1 - resolution: "@cspotcode/source-map-support@npm:0.8.1" +"@conventional-changelog/git-client@npm:^3.0.0": + version: 3.1.2 + resolution: "@conventional-changelog/git-client@npm:3.1.2" dependencies: - "@jridgewell/trace-mapping": "npm:0.3.9" - checksum: 10c0/05c5368c13b662ee4c122c7bfbe5dc0b613416672a829f3e78bc49a357a197e0218d6e74e7c66cfcd04e15a179acab080bd3c69658c9fbefd0e1ccd950a07fc6 - languageName: node - linkType: hard - -"@esbuild/aix-ppc64@npm:0.27.3": - version: 0.27.3 - resolution: "@esbuild/aix-ppc64@npm:0.27.3" - conditions: os=aix & cpu=ppc64 - languageName: node - linkType: hard - -"@esbuild/android-arm64@npm:0.27.3": - version: 0.27.3 - resolution: "@esbuild/android-arm64@npm:0.27.3" - conditions: os=android & cpu=arm64 - languageName: node - linkType: hard - -"@esbuild/android-arm@npm:0.27.3": - version: 0.27.3 - resolution: "@esbuild/android-arm@npm:0.27.3" - conditions: os=android & cpu=arm - languageName: node - linkType: hard - -"@esbuild/android-x64@npm:0.27.3": - version: 0.27.3 - resolution: "@esbuild/android-x64@npm:0.27.3" - conditions: os=android & cpu=x64 - languageName: node - linkType: hard - -"@esbuild/darwin-arm64@npm:0.27.3": - version: 0.27.3 - resolution: "@esbuild/darwin-arm64@npm:0.27.3" - conditions: os=darwin & cpu=arm64 - languageName: node - linkType: hard - -"@esbuild/darwin-x64@npm:0.27.3": - version: 0.27.3 - resolution: "@esbuild/darwin-x64@npm:0.27.3" - conditions: os=darwin & cpu=x64 - languageName: node - linkType: hard - -"@esbuild/freebsd-arm64@npm:0.27.3": - version: 0.27.3 - resolution: "@esbuild/freebsd-arm64@npm:0.27.3" - conditions: os=freebsd & cpu=arm64 - languageName: node - linkType: hard - -"@esbuild/freebsd-x64@npm:0.27.3": - version: 0.27.3 - resolution: "@esbuild/freebsd-x64@npm:0.27.3" - conditions: os=freebsd & cpu=x64 - languageName: node - linkType: hard - -"@esbuild/linux-arm64@npm:0.27.3": - version: 0.27.3 - resolution: "@esbuild/linux-arm64@npm:0.27.3" - conditions: os=linux & cpu=arm64 - languageName: node - linkType: hard - -"@esbuild/linux-arm@npm:0.27.3": - version: 0.27.3 - resolution: "@esbuild/linux-arm@npm:0.27.3" - conditions: os=linux & cpu=arm - languageName: node - linkType: hard - -"@esbuild/linux-ia32@npm:0.27.3": - version: 0.27.3 - resolution: "@esbuild/linux-ia32@npm:0.27.3" - conditions: os=linux & cpu=ia32 - languageName: node - linkType: hard - -"@esbuild/linux-loong64@npm:0.27.3": - version: 0.27.3 - resolution: "@esbuild/linux-loong64@npm:0.27.3" - conditions: os=linux & cpu=loong64 - languageName: node - linkType: hard - -"@esbuild/linux-mips64el@npm:0.27.3": - version: 0.27.3 - resolution: "@esbuild/linux-mips64el@npm:0.27.3" - conditions: os=linux & cpu=mips64el - languageName: node - linkType: hard - -"@esbuild/linux-ppc64@npm:0.27.3": - version: 0.27.3 - resolution: "@esbuild/linux-ppc64@npm:0.27.3" - conditions: os=linux & cpu=ppc64 - languageName: node - linkType: hard - -"@esbuild/linux-riscv64@npm:0.27.3": - version: 0.27.3 - resolution: "@esbuild/linux-riscv64@npm:0.27.3" - conditions: os=linux & cpu=riscv64 - languageName: node - linkType: hard - -"@esbuild/linux-s390x@npm:0.27.3": - version: 0.27.3 - resolution: "@esbuild/linux-s390x@npm:0.27.3" - conditions: os=linux & cpu=s390x - languageName: node - linkType: hard - -"@esbuild/linux-x64@npm:0.27.3": - version: 0.27.3 - resolution: "@esbuild/linux-x64@npm:0.27.3" - conditions: os=linux & cpu=x64 - languageName: node - linkType: hard - -"@esbuild/netbsd-arm64@npm:0.27.3": - version: 0.27.3 - resolution: "@esbuild/netbsd-arm64@npm:0.27.3" - conditions: os=netbsd & cpu=arm64 - languageName: node - linkType: hard - -"@esbuild/netbsd-x64@npm:0.27.3": - version: 0.27.3 - resolution: "@esbuild/netbsd-x64@npm:0.27.3" - conditions: os=netbsd & cpu=x64 - languageName: node - linkType: hard - -"@esbuild/openbsd-arm64@npm:0.27.3": - version: 0.27.3 - resolution: "@esbuild/openbsd-arm64@npm:0.27.3" - conditions: os=openbsd & cpu=arm64 - languageName: node - linkType: hard - -"@esbuild/openbsd-x64@npm:0.27.3": - version: 0.27.3 - resolution: "@esbuild/openbsd-x64@npm:0.27.3" - conditions: os=openbsd & cpu=x64 - languageName: node - linkType: hard - -"@esbuild/openharmony-arm64@npm:0.27.3": - version: 0.27.3 - resolution: "@esbuild/openharmony-arm64@npm:0.27.3" - conditions: os=openharmony & cpu=arm64 - languageName: node - linkType: hard - -"@esbuild/sunos-x64@npm:0.27.3": - version: 0.27.3 - resolution: "@esbuild/sunos-x64@npm:0.27.3" - conditions: os=sunos & cpu=x64 - languageName: node - linkType: hard - -"@esbuild/win32-arm64@npm:0.27.3": - version: 0.27.3 - resolution: "@esbuild/win32-arm64@npm:0.27.3" - conditions: os=win32 & cpu=arm64 - languageName: node - linkType: hard - -"@esbuild/win32-ia32@npm:0.27.3": - version: 0.27.3 - resolution: "@esbuild/win32-ia32@npm:0.27.3" - conditions: os=win32 & cpu=ia32 + "@simple-libs/child-process-utils": "npm:^2.0.0" + "@simple-libs/stream-utils": "npm:^2.0.0" + semver: "npm:^7.5.2" + peerDependencies: + conventional-commits-filter: ^6.0.1 + conventional-commits-parser: ^7.1.2 + peerDependenciesMeta: + conventional-commits-filter: + optional: true + conventional-commits-parser: + optional: true + checksum: 10c0/ee07d87638e5a2ded3d14498fc9530f6023651573dd56ee729b587def16d13f94ae362c5b0c2a17acda538bad0787f7c4df5d36ee7164a92b9eb79cc0e039145 languageName: node linkType: hard -"@esbuild/win32-x64@npm:0.27.3": - version: 0.27.3 - resolution: "@esbuild/win32-x64@npm:0.27.3" - conditions: os=win32 & cpu=x64 +"@conventional-changelog/template@npm:^1.4.0": + version: 1.4.0 + resolution: "@conventional-changelog/template@npm:1.4.0" + checksum: 10c0/052ac38b43f67b2572d101ced1dee2691a2ac3828cbc1041738cf1646b956ff0fb3e87258ec59f46eb2c3fcc9a68bb89f90b8dcb1e354880f2c71d8f5ef3a021 languageName: node linkType: hard @@ -517,32 +333,32 @@ __metadata: languageName: node linkType: hard -"@eslint/config-array@npm:^0.23.3": - version: 0.23.3 - resolution: "@eslint/config-array@npm:0.23.3" +"@eslint/config-array@npm:^0.23.5": + version: 0.23.5 + resolution: "@eslint/config-array@npm:0.23.5" dependencies: - "@eslint/object-schema": "npm:^3.0.3" + "@eslint/object-schema": "npm:^3.0.5" debug: "npm:^4.3.1" minimatch: "npm:^10.2.4" - checksum: 10c0/7c19027acf9110cc542513ff9f3ca73a61d127e900c24f0e8e4d5e18aa22baf08d1d5bc386563d2f9311095f3b7898fe9b627b590fe9232b745ef60d4443cf9f + checksum: 10c0/b24833c4c76e78ee075d306cd3f095db46b2db0f90cc13a6ee6e4275f9889731c05bf5403ab5fefb79c756e07ac9184ed0e04570341382f9eccbccc80e6d1a0c languageName: node linkType: hard -"@eslint/config-helpers@npm:^0.5.2": - version: 0.5.3 - resolution: "@eslint/config-helpers@npm:0.5.3" +"@eslint/config-helpers@npm:^0.7.0": + version: 0.7.0 + resolution: "@eslint/config-helpers@npm:0.7.0" dependencies: - "@eslint/core": "npm:^1.1.1" - checksum: 10c0/c836476e839a79dcdc9f7e0013057cfe0341162180d50e5a08668edb4b4b6c520a3174011469f6ef02efd2affd092263c020e89d0a3452c801427b0ac003549a + "@eslint/core": "npm:^1.2.1" + checksum: 10c0/fd40d57d6f1db49f7b647048b88a433dc7f6522ef3edf855a43cb526ef4fc40622ceed0dc8de2e03d254f30f8e035370570de1d4bd8e7c2b1200131451e0d331 languageName: node linkType: hard -"@eslint/core@npm:^1.1.1": - version: 1.1.1 - resolution: "@eslint/core@npm:1.1.1" +"@eslint/core@npm:^1.2.1": + version: 1.2.1 + resolution: "@eslint/core@npm:1.2.1" dependencies: "@types/json-schema": "npm:^7.0.15" - checksum: 10c0/129c654c78afc1f6d61dccb0ce841be667f09f052f7d5642614b6ba5eeebd579ca6cc336d7b750d88625e61f7aad22fdd62bf83847fbfc10cc3e58cfe6c5072e + checksum: 10c0/10979b40588ecfef771fcb5013a542a35fb30692cc95a65f3481b0b36fbd89f5679efeb30d57f4eed35203d859aabace2a620177d6c536f71b299a1af2f3398f languageName: node linkType: hard @@ -558,20 +374,20 @@ __metadata: languageName: node linkType: hard -"@eslint/object-schema@npm:^3.0.3": - version: 3.0.3 - resolution: "@eslint/object-schema@npm:3.0.3" - checksum: 10c0/4abbb7cba5419dce46ae8aa8e979fa190f2e906a8e1b5a8e22e4489f62a68dea3967679f66acbc0c3ef89f33252a7460e39fc2d6f2b4f616a137f3514eda4784 +"@eslint/object-schema@npm:^3.0.5": + version: 3.0.5 + resolution: "@eslint/object-schema@npm:3.0.5" + checksum: 10c0/1db337431f520b99e9edda64ef5fafd7ec6a029843eeb608753025125b6649d861d843cffafafd3c4e37926d7d5f9ec0c6a8e3665c13c3da2144e8132892e92e languageName: node linkType: hard -"@eslint/plugin-kit@npm:^0.6.1": - version: 0.6.1 - resolution: "@eslint/plugin-kit@npm:0.6.1" +"@eslint/plugin-kit@npm:^0.7.3": + version: 0.7.3 + resolution: "@eslint/plugin-kit@npm:0.7.3" dependencies: - "@eslint/core": "npm:^1.1.1" + "@eslint/core": "npm:^1.2.1" levn: "npm:^0.4.1" - checksum: 10c0/f8354a7b92cc41e7a55d51986d192134be84f9dc0c91b5e649d075d733b56981c4ca8bf4460d54120c4c87b47984167bad2cb9bceb303f11b0a3bad22b3ed06a + checksum: 10c0/2daebaedcfbd261478e8b87dfb46d3118f48277c19eb298483ebc6f3b438e9688b86ddca13b3aeb08dfb06be00fb6073506852386aa124645c1170e7e33d140c languageName: node linkType: hard @@ -622,6 +438,13 @@ __metadata: languageName: node linkType: hard +"@isaacs/cliui@npm:^9.0.0": + version: 9.0.0 + resolution: "@isaacs/cliui@npm:9.0.0" + checksum: 10c0/971063b7296419f85053dacd0a0285dcadaa3dfc139228b23e016c1a9848121ad4aa5e7fcca7522062014e1eb6239a7424188b9f2cba893a79c90aae5710319c + languageName: node + linkType: hard + "@isaacs/fs-minipass@npm:^4.0.0": version: 4.0.1 resolution: "@isaacs/fs-minipass@npm:4.0.1" @@ -631,10 +454,23 @@ __metadata: languageName: node linkType: hard -"@jridgewell/resolve-uri@npm:^3.0.3": - version: 3.1.1 - resolution: "@jridgewell/resolve-uri@npm:3.1.1" - checksum: 10c0/0dbc9e29bc640bbbdc5b9876d2859c69042bfcf1423c1e6421bcca53e826660bff4e41c7d4bcb8dbea696404231a6f902f76ba41835d049e20f2dd6cffb713bf +"@jridgewell/gen-mapping@npm:^0.3.5": + version: 0.3.13 + resolution: "@jridgewell/gen-mapping@npm:0.3.13" + dependencies: + "@jridgewell/sourcemap-codec": "npm:^1.5.0" + "@jridgewell/trace-mapping": "npm:^0.3.24" + checksum: 10c0/9a7d65fb13bd9aec1fbab74cda08496839b7e2ceb31f5ab922b323e94d7c481ce0fc4fd7e12e2610915ed8af51178bdc61e168e92a8c8b8303b030b03489b13b + languageName: node + linkType: hard + +"@jridgewell/remapping@npm:^2.3.5": + version: 2.3.5 + resolution: "@jridgewell/remapping@npm:2.3.5" + dependencies: + "@jridgewell/gen-mapping": "npm:^0.3.5" + "@jridgewell/trace-mapping": "npm:^0.3.24" + checksum: 10c0/3de494219ffeb2c5c38711d0d7bb128097edf91893090a2dbc8ee0b55d092bb7347b1fd0f478486c5eab010e855c73927b1666f2107516d472d24a73017d1194 languageName: node linkType: hard @@ -645,13 +481,6 @@ __metadata: languageName: node linkType: hard -"@jridgewell/sourcemap-codec@npm:^1.4.10": - version: 1.4.15 - resolution: "@jridgewell/sourcemap-codec@npm:1.4.15" - checksum: 10c0/0c6b5ae663087558039052a626d2d7ed5208da36cfd707dcc5cea4a07cfc918248403dcb5989a8f7afaf245ce0573b7cc6fd94c4a30453bd10e44d9363940ba5 - languageName: node - linkType: hard - "@jridgewell/sourcemap-codec@npm:^1.4.14, @jridgewell/sourcemap-codec@npm:^1.5.0": version: 1.5.0 resolution: "@jridgewell/sourcemap-codec@npm:1.5.0" @@ -659,24 +488,14 @@ __metadata: languageName: node linkType: hard -"@jridgewell/sourcemap-codec@npm:^1.5.5": - version: 1.5.5 - resolution: "@jridgewell/sourcemap-codec@npm:1.5.5" - checksum: 10c0/f9e538f302b63c0ebc06eecb1dd9918dd4289ed36147a0ddce35d6ea4d7ebbda243cda7b2213b6a5e1d8087a298d5cf630fb2bd39329cdecb82017023f6081a0 +"@jridgewell/sourcemap-codec@npm:^1.6.0": + version: 1.6.0 + resolution: "@jridgewell/sourcemap-codec@npm:1.6.0" + checksum: 10c0/b5be700e45a775f218589c3466c4ffea630582b4988657652da464e5ab8a7d18bf928ee4fd2363fb346ede4bea9c6ff0bae05a538358c4565ece6199531b5f72 languageName: node linkType: hard -"@jridgewell/trace-mapping@npm:0.3.9": - version: 0.3.9 - resolution: "@jridgewell/trace-mapping@npm:0.3.9" - dependencies: - "@jridgewell/resolve-uri": "npm:^3.0.3" - "@jridgewell/sourcemap-codec": "npm:^1.4.10" - checksum: 10c0/fa425b606d7c7ee5bfa6a31a7b050dd5814b4082f318e0e4190f991902181b4330f43f4805db1dd4f2433fd0ed9cc7a7b9c2683f1deeab1df1b0a98b1e24055b - languageName: node - linkType: hard - -"@jridgewell/trace-mapping@npm:^0.3.31": +"@jridgewell/trace-mapping@npm:0.3.31, @jridgewell/trace-mapping@npm:^0.3.24, @jridgewell/trace-mapping@npm:^0.3.31": version: 0.3.31 resolution: "@jridgewell/trace-mapping@npm:0.3.31" dependencies: @@ -686,57 +505,122 @@ __metadata: languageName: node linkType: hard -"@microsoft/api-extractor-model@npm:7.33.4": - version: 7.33.4 - resolution: "@microsoft/api-extractor-model@npm:7.33.4" +"@keyv/bigmap@npm:^1.3.1": + version: 1.3.1 + resolution: "@keyv/bigmap@npm:1.3.1" dependencies: - "@microsoft/tsdoc": "npm:~0.16.0" - "@microsoft/tsdoc-config": "npm:~0.18.1" - "@rushstack/node-core-library": "npm:5.20.3" - checksum: 10c0/c71569e59a5f876c600f38240ed8d12c1e4c908a2a6af9cd75f3b22334d7c950b54f367622205930253fc03474c0aa7d017adfce571feff3011780f2502c0391 + hashery: "npm:^1.4.0" + hookified: "npm:^1.15.0" + peerDependencies: + keyv: ^5.6.0 + checksum: 10c0/acc6a4a5edf462ce23e95672ab4bfaf7cd1941dff6bf3a2f671ce467961ace1fac9d3eb75a9ed9a8e92012e00a7d8b16ad1677bc539a52c3ad0cec31473e2349 languageName: node linkType: hard -"@microsoft/api-extractor@npm:^7.50.1": - version: 7.57.6 - resolution: "@microsoft/api-extractor@npm:7.57.6" +"@keyv/serialize@npm:^1.1.1": + version: 1.1.1 + resolution: "@keyv/serialize@npm:1.1.1" + checksum: 10c0/b0008cae4a54400c3abf587b8cc2474c6f528ee58969ce6cf9cb07a04006f80c73c85971d6be6544408318a2bc40108236a19a82aea0a6de95aae49533317374 + languageName: node + linkType: hard + +"@modulify/conventional-bump@npm:^0.1.3": + version: 0.1.3 + resolution: "@modulify/conventional-bump@npm:0.1.3" dependencies: - "@microsoft/api-extractor-model": "npm:7.33.4" - "@microsoft/tsdoc": "npm:~0.16.0" - "@microsoft/tsdoc-config": "npm:~0.18.1" - "@rushstack/node-core-library": "npm:5.20.3" - "@rushstack/rig-package": "npm:0.7.2" - "@rushstack/terminal": "npm:0.22.3" - "@rushstack/ts-command-line": "npm:5.3.3" - diff: "npm:~8.0.2" - lodash: "npm:~4.17.23" - minimatch: "npm:10.2.1" - resolve: "npm:~1.22.1" - semver: "npm:~7.5.4" - source-map: "npm:~0.6.1" - typescript: "npm:5.8.2" - bin: - api-extractor: bin/api-extractor - checksum: 10c0/2f4598cc6b7cb6acc65e2029dabb7c8679495a95ed36708264088eed4a7cacb62e7f69662f71ab61e273a6441c66ae3c392f605a512fb9a4e7f9211f702287ac + "@modulify/conventional-git": "npm:^0.1.3" + semver: "npm:^7.7.4" + peerDependencies: + "@types/node": ">=20" + "@types/semver": ^7.7.1 + peerDependenciesMeta: + "@types/node": + optional: true + "@types/semver": + optional: true + checksum: 10c0/f03c067fd7173de699b986a4261a71d777ce448cbbc9157a63aa0bbe0db4e9a71ff91cae927d654103e6bc344693c4c809262663c83df87fce7a332ef67149cb + languageName: node + linkType: hard + +"@modulify/conventional-changelog@npm:^0.1.3": + version: 0.1.3 + resolution: "@modulify/conventional-changelog@npm:0.1.3" + dependencies: + "@modulify/conventional-bump": "npm:^0.1.3" + "@modulify/conventional-git": "npm:^0.1.3" + "@types/nunjucks": "npm:^3.2.6" + nunjucks: "npm:^3.2.4" + peerDependencies: + "@types/node": ">=20" + peerDependenciesMeta: + "@types/node": + optional: true + checksum: 10c0/2a48c6baa566f1be514baed4db069a289738945c081be0b42701507cbe3965d95d4f7db62c7b4cebccff9a424553a21af3a25992b87adce23e442473a7565dd9 languageName: node linkType: hard -"@microsoft/tsdoc-config@npm:~0.18.1": - version: 0.18.1 - resolution: "@microsoft/tsdoc-config@npm:0.18.1" +"@modulify/conventional-git@npm:^0.1.3": + version: 0.1.3 + resolution: "@modulify/conventional-git@npm:0.1.3" dependencies: - "@microsoft/tsdoc": "npm:0.16.0" - ajv: "npm:~8.18.0" - jju: "npm:~1.4.0" - resolve: "npm:~1.22.2" - checksum: 10c0/06507f7ced4fadf3e68368c60810c1e057403581f720e6cf96b4d6b6bc7a927232510da40425ffd67d5d918ec7cfba8baec56406687330f233f67eb11b9d8d65 + "@modulify/git-toolkit": "npm:^0.0.3" + semver: "npm:^7.7.4" + peerDependencies: + "@types/node": ">=20" + peerDependenciesMeta: + "@types/node": + optional: true + checksum: 10c0/2816827f0693b3eafa7b08f4b166840418f553f06e8dfdaad9dab7f2bdee164cdf3e8af80f75308a2482aef02d7b2ea893589cea5fe955474a6bf19864abd925 + languageName: node + linkType: hard + +"@modulify/conventional-release@npm:^0.1.3": + version: 0.1.3 + resolution: "@modulify/conventional-release@npm:0.1.3" + dependencies: + "@modulify/conventional-bump": "npm:^0.1.3" + "@modulify/conventional-changelog": "npm:^0.1.3" + "@modulify/conventional-git": "npm:^0.1.3" + "@modulify/git-toolkit": "npm:^0.0.3" + "@modulify/pkg": "npm:^1.0.1" + chalk: "npm:^5.6.2" + figures: "npm:^6.1.0" + semver: "npm:^7.7.4" + yargs: "npm:^17.7.2" + peerDependencies: + "@types/node": ">=20" + "@types/semver": ^7.7.1 + peerDependenciesMeta: + "@types/node": + optional: true + "@types/semver": + optional: true + bin: + conventional-release: bin/index.js + checksum: 10c0/8265285c6a8324bc349f8891a1a014d95109491cef915a111f85a10e6c5df833ae36551de64c77ef07fb6c65772e6c509c92883ca8923fff39dada03809ca687 + languageName: node + linkType: hard + +"@modulify/git-toolkit@npm:^0.0.3": + version: 0.0.3 + resolution: "@modulify/git-toolkit@npm:0.0.3" + peerDependencies: + "@types/node": ">=20" + peerDependenciesMeta: + "@types/node": + optional: true + checksum: 10c0/47579452bb3722eca9c54ddbc296e4106a6e1014f034b3dd8c602ab281d41d3879899d388ce34825de2947e9864158c9685959ab2b764db95ca9ecbd4b3895a4 languageName: node linkType: hard -"@microsoft/tsdoc@npm:0.16.0, @microsoft/tsdoc@npm:~0.16.0": - version: 0.16.0 - resolution: "@microsoft/tsdoc@npm:0.16.0" - checksum: 10c0/8883bb0ed22753af7360e9222687fda4eb448f0a574ea34b4596c11e320148b3ae0d24e00f8923df8ba7bc62a46a6f53b9343243a348640d923dfd55d52cd6bb +"@modulify/pkg@npm:^1.0.1": + version: 1.0.1 + resolution: "@modulify/pkg@npm:1.0.1" + dependencies: + detect-indent: "npm:^7.0.1" + detect-newline: "npm:^4.0.1" + glob: "npm:^11.0.0" + checksum: 10c0/09b3fac8bee717bbb987e5c41f0eac90990077acdd1b4e197e28765c1768162a13b241bb15d0f1630f82e9aa354ca124035e10f3d3790cb4cf1a730aa70b2c70 languageName: node linkType: hard @@ -744,22 +628,22 @@ __metadata: version: 0.0.0-use.local resolution: "@modulify/validator@workspace:." dependencies: - "@commitlint/cli": "npm:^20.4.3" - "@commitlint/config-conventional": "npm:^20.4.3" + "@commitlint/cli": "npm:^21.2.3" + "@commitlint/config-conventional": "npm:^21.2.3" "@eslint/js": "npm:^10.0.1" - "@types/node": "npm:^25.3.5" - "@vitest/coverage-v8": "npm:4.0.18" - "@vitest/ui": "npm:4.0.18" - eslint: "npm:^10.0.3" - globals: "npm:^17.4.0" + "@modulify/conventional-release": "npm:^0.1.3" + "@types/node": "npm:^24.19.0" + "@vitest/coverage-v8": "npm:5.0.2" + "@vitest/ui": "npm:5.0.2" + eslint: "npm:^10.11.0" + globals: "npm:^17.12.0" husky: "npm:^9.1.7" - ts-node: "npm:^10.9.2" tslib: "npm:^2.8.1" - typescript: "npm:^5.5.4" - typescript-eslint: "npm:^8.56.1" - vite: "npm:^7.3.1" - vite-plugin-dts: "npm:^4.5.4" - vitest: "npm:^4.0.18" + typescript: "npm:~6.0.3" + typescript-eslint: "npm:^8.70.1" + unplugin-dts: "npm:^1.1.1" + vite: "npm:^8.3.1" + vitest: "npm:5.0.2" languageName: unknown linkType: soft @@ -785,6 +669,13 @@ __metadata: languageName: node linkType: hard +"@oxc-project/types@npm:=0.151.0": + version: 0.151.0 + resolution: "@oxc-project/types@npm:0.151.0" + checksum: 10c0/7dac5399345abf368449735c31c218439e6c3a2acf7b8cbc715a88b45989390208f61bea0f3eb28bee5a034a509a7f4142ae60069e6611b07c70a30867c8ca84 + languageName: node + linkType: hard + "@polka/url@npm:^1.0.0-next.24": version: 1.0.0-next.28 resolution: "@polka/url@npm:1.0.0-next.28" @@ -792,314 +683,147 @@ __metadata: languageName: node linkType: hard -"@rollup/pluginutils@npm:^5.1.4": - version: 5.1.4 - resolution: "@rollup/pluginutils@npm:5.1.4" - dependencies: - "@types/estree": "npm:^1.0.0" - estree-walker: "npm:^2.0.2" - picomatch: "npm:^4.0.2" - peerDependencies: - rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 - peerDependenciesMeta: - rollup: - optional: true - checksum: 10c0/6d58fbc6f1024eb4b087bc9bf59a1d655a8056a60c0b4021d3beaeec3f0743503f52467fd89d2cf0e7eccf2831feb40a05ad541a17637ea21ba10b21c2004deb - languageName: node - linkType: hard - -"@rollup/rollup-android-arm-eabi@npm:4.59.0": - version: 4.59.0 - resolution: "@rollup/rollup-android-arm-eabi@npm:4.59.0" +"@rolldown/binding-android-arm-eabi@npm:1.2.11": + version: 1.2.11 + resolution: "@rolldown/binding-android-arm-eabi@npm:1.2.11" conditions: os=android & cpu=arm languageName: node linkType: hard -"@rollup/rollup-android-arm64@npm:4.59.0": - version: 4.59.0 - resolution: "@rollup/rollup-android-arm64@npm:4.59.0" +"@rolldown/binding-android-arm64@npm:1.2.11": + version: 1.2.11 + resolution: "@rolldown/binding-android-arm64@npm:1.2.11" conditions: os=android & cpu=arm64 languageName: node linkType: hard -"@rollup/rollup-darwin-arm64@npm:4.59.0": - version: 4.59.0 - resolution: "@rollup/rollup-darwin-arm64@npm:4.59.0" +"@rolldown/binding-darwin-arm64@npm:1.2.11": + version: 1.2.11 + resolution: "@rolldown/binding-darwin-arm64@npm:1.2.11" conditions: os=darwin & cpu=arm64 languageName: node linkType: hard -"@rollup/rollup-darwin-x64@npm:4.59.0": - version: 4.59.0 - resolution: "@rollup/rollup-darwin-x64@npm:4.59.0" +"@rolldown/binding-darwin-x64@npm:1.2.11": + version: 1.2.11 + resolution: "@rolldown/binding-darwin-x64@npm:1.2.11" conditions: os=darwin & cpu=x64 languageName: node linkType: hard -"@rollup/rollup-freebsd-arm64@npm:4.59.0": - version: 4.59.0 - resolution: "@rollup/rollup-freebsd-arm64@npm:4.59.0" - conditions: os=freebsd & cpu=arm64 - languageName: node - linkType: hard - -"@rollup/rollup-freebsd-x64@npm:4.59.0": - version: 4.59.0 - resolution: "@rollup/rollup-freebsd-x64@npm:4.59.0" +"@rolldown/binding-freebsd-x64@npm:1.2.11": + version: 1.2.11 + resolution: "@rolldown/binding-freebsd-x64@npm:1.2.11" conditions: os=freebsd & cpu=x64 languageName: node linkType: hard -"@rollup/rollup-linux-arm-gnueabihf@npm:4.59.0": - version: 4.59.0 - resolution: "@rollup/rollup-linux-arm-gnueabihf@npm:4.59.0" - conditions: os=linux & cpu=arm & libc=glibc - languageName: node - linkType: hard - -"@rollup/rollup-linux-arm-musleabihf@npm:4.59.0": - version: 4.59.0 - resolution: "@rollup/rollup-linux-arm-musleabihf@npm:4.59.0" - conditions: os=linux & cpu=arm & libc=musl +"@rolldown/binding-linux-arm-gnueabihf@npm:1.2.11": + version: 1.2.11 + resolution: "@rolldown/binding-linux-arm-gnueabihf@npm:1.2.11" + conditions: os=linux & cpu=arm languageName: node linkType: hard -"@rollup/rollup-linux-arm64-gnu@npm:4.59.0": - version: 4.59.0 - resolution: "@rollup/rollup-linux-arm64-gnu@npm:4.59.0" +"@rolldown/binding-linux-arm64-gnu@npm:1.2.11": + version: 1.2.11 + resolution: "@rolldown/binding-linux-arm64-gnu@npm:1.2.11" conditions: os=linux & cpu=arm64 & libc=glibc languageName: node linkType: hard -"@rollup/rollup-linux-arm64-musl@npm:4.59.0": - version: 4.59.0 - resolution: "@rollup/rollup-linux-arm64-musl@npm:4.59.0" +"@rolldown/binding-linux-arm64-musl@npm:1.2.11": + version: 1.2.11 + resolution: "@rolldown/binding-linux-arm64-musl@npm:1.2.11" conditions: os=linux & cpu=arm64 & libc=musl languageName: node linkType: hard -"@rollup/rollup-linux-loong64-gnu@npm:4.59.0": - version: 4.59.0 - resolution: "@rollup/rollup-linux-loong64-gnu@npm:4.59.0" - conditions: os=linux & cpu=loong64 & libc=glibc - languageName: node - linkType: hard - -"@rollup/rollup-linux-loong64-musl@npm:4.59.0": - version: 4.59.0 - resolution: "@rollup/rollup-linux-loong64-musl@npm:4.59.0" - conditions: os=linux & cpu=loong64 & libc=musl - languageName: node - linkType: hard - -"@rollup/rollup-linux-ppc64-gnu@npm:4.59.0": - version: 4.59.0 - resolution: "@rollup/rollup-linux-ppc64-gnu@npm:4.59.0" +"@rolldown/binding-linux-ppc64-gnu@npm:1.2.11": + version: 1.2.11 + resolution: "@rolldown/binding-linux-ppc64-gnu@npm:1.2.11" conditions: os=linux & cpu=ppc64 & libc=glibc languageName: node linkType: hard -"@rollup/rollup-linux-ppc64-musl@npm:4.59.0": - version: 4.59.0 - resolution: "@rollup/rollup-linux-ppc64-musl@npm:4.59.0" - conditions: os=linux & cpu=ppc64 & libc=musl - languageName: node - linkType: hard - -"@rollup/rollup-linux-riscv64-gnu@npm:4.59.0": - version: 4.59.0 - resolution: "@rollup/rollup-linux-riscv64-gnu@npm:4.59.0" - conditions: os=linux & cpu=riscv64 & libc=glibc - languageName: node - linkType: hard - -"@rollup/rollup-linux-riscv64-musl@npm:4.59.0": - version: 4.59.0 - resolution: "@rollup/rollup-linux-riscv64-musl@npm:4.59.0" - conditions: os=linux & cpu=riscv64 & libc=musl - languageName: node - linkType: hard - -"@rollup/rollup-linux-s390x-gnu@npm:4.59.0": - version: 4.59.0 - resolution: "@rollup/rollup-linux-s390x-gnu@npm:4.59.0" +"@rolldown/binding-linux-s390x-gnu@npm:1.2.11": + version: 1.2.11 + resolution: "@rolldown/binding-linux-s390x-gnu@npm:1.2.11" conditions: os=linux & cpu=s390x & libc=glibc languageName: node linkType: hard -"@rollup/rollup-linux-x64-gnu@npm:4.59.0": - version: 4.59.0 - resolution: "@rollup/rollup-linux-x64-gnu@npm:4.59.0" +"@rolldown/binding-linux-x64-gnu@npm:1.2.11": + version: 1.2.11 + resolution: "@rolldown/binding-linux-x64-gnu@npm:1.2.11" conditions: os=linux & cpu=x64 & libc=glibc languageName: node linkType: hard -"@rollup/rollup-linux-x64-musl@npm:4.59.0": - version: 4.59.0 - resolution: "@rollup/rollup-linux-x64-musl@npm:4.59.0" +"@rolldown/binding-linux-x64-musl@npm:1.2.11": + version: 1.2.11 + resolution: "@rolldown/binding-linux-x64-musl@npm:1.2.11" conditions: os=linux & cpu=x64 & libc=musl languageName: node linkType: hard -"@rollup/rollup-openbsd-x64@npm:4.59.0": - version: 4.59.0 - resolution: "@rollup/rollup-openbsd-x64@npm:4.59.0" - conditions: os=openbsd & cpu=x64 - languageName: node - linkType: hard - -"@rollup/rollup-openharmony-arm64@npm:4.59.0": - version: 4.59.0 - resolution: "@rollup/rollup-openharmony-arm64@npm:4.59.0" +"@rolldown/binding-openharmony-arm64@npm:1.2.11": + version: 1.2.11 + resolution: "@rolldown/binding-openharmony-arm64@npm:1.2.11" conditions: os=openharmony & cpu=arm64 languageName: node linkType: hard -"@rollup/rollup-win32-arm64-msvc@npm:4.59.0": - version: 4.59.0 - resolution: "@rollup/rollup-win32-arm64-msvc@npm:4.59.0" +"@rolldown/binding-win32-arm64-msvc@npm:1.2.11": + version: 1.2.11 + resolution: "@rolldown/binding-win32-arm64-msvc@npm:1.2.11" conditions: os=win32 & cpu=arm64 languageName: node linkType: hard -"@rollup/rollup-win32-ia32-msvc@npm:4.59.0": - version: 4.59.0 - resolution: "@rollup/rollup-win32-ia32-msvc@npm:4.59.0" - conditions: os=win32 & cpu=ia32 - languageName: node - linkType: hard - -"@rollup/rollup-win32-x64-gnu@npm:4.59.0": - version: 4.59.0 - resolution: "@rollup/rollup-win32-x64-gnu@npm:4.59.0" +"@rolldown/binding-win32-x64-msvc@npm:1.2.11": + version: 1.2.11 + resolution: "@rolldown/binding-win32-x64-msvc@npm:1.2.11" conditions: os=win32 & cpu=x64 languageName: node linkType: hard -"@rollup/rollup-win32-x64-msvc@npm:4.59.0": - version: 4.59.0 - resolution: "@rollup/rollup-win32-x64-msvc@npm:4.59.0" - conditions: os=win32 & cpu=x64 - languageName: node - linkType: hard - -"@rushstack/node-core-library@npm:5.20.3": - version: 5.20.3 - resolution: "@rushstack/node-core-library@npm:5.20.3" - dependencies: - ajv: "npm:~8.18.0" - ajv-draft-04: "npm:~1.0.0" - ajv-formats: "npm:~3.0.1" - fs-extra: "npm:~11.3.0" - import-lazy: "npm:~4.0.0" - jju: "npm:~1.4.0" - resolve: "npm:~1.22.1" - semver: "npm:~7.5.4" - peerDependencies: - "@types/node": "*" - peerDependenciesMeta: - "@types/node": - optional: true - checksum: 10c0/bc9b33c6bef033ae6b33efb400b446d8fbaf98777ce085d03c505821f699e0a703ebe987ec05998c37aae3c33bd0dd7a4eb5e6b831c61977be9faac10f8c1c77 - languageName: node - linkType: hard - -"@rushstack/problem-matcher@npm:0.2.1": - version: 0.2.1 - resolution: "@rushstack/problem-matcher@npm:0.2.1" - peerDependencies: - "@types/node": "*" - peerDependenciesMeta: - "@types/node": - optional: true - checksum: 10c0/d6cf27f6bfcdc00763e6d51e582d4faef7109ba8906e6bb3bc375edae551c54a589ed61b7e01e9ae1dbdd4a7075fd82f2da541918b52f2233d6c86393beeaaa7 - languageName: node - linkType: hard - -"@rushstack/rig-package@npm:0.7.2": - version: 0.7.2 - resolution: "@rushstack/rig-package@npm:0.7.2" - dependencies: - resolve: "npm:~1.22.1" - strip-json-comments: "npm:~3.1.1" - checksum: 10c0/2e2839fa9a3984d4b6433d6e5d48130ba0be88fc1d80e1d832272a1e939d3bfed532e8b7560ef70f8b4ebc62593b8684f2ae1cc8aecd5595661066f53527253c +"@rolldown/pluginutils@npm:^1.0.0": + version: 1.0.1 + resolution: "@rolldown/pluginutils@npm:1.0.1" + checksum: 10c0/99d9b06d90196823e4d8c841f258db7a16e5dbba5824a2962b05d907b79f1ba929d56f22dd744fd530936e568c865ee56a719dc31e57e13bc0a8eb4764a8d8dd languageName: node linkType: hard -"@rushstack/terminal@npm:0.22.3": - version: 0.22.3 - resolution: "@rushstack/terminal@npm:0.22.3" +"@rollup/pluginutils@npm:^5.1.4": + version: 5.1.4 + resolution: "@rollup/pluginutils@npm:5.1.4" dependencies: - "@rushstack/node-core-library": "npm:5.20.3" - "@rushstack/problem-matcher": "npm:0.2.1" - supports-color: "npm:~8.1.1" + "@types/estree": "npm:^1.0.0" + estree-walker: "npm:^2.0.2" + picomatch: "npm:^4.0.2" peerDependencies: - "@types/node": "*" + rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 peerDependenciesMeta: - "@types/node": + rollup: optional: true - checksum: 10c0/0e81fe7543b5365d776c2d8f68252ccd543be2723fc9f640ebce5c6b84eec9c7fc63f385c517dfed9325ba4a8daf62175ca4df299fb1eea33561155412cf4667 + checksum: 10c0/6d58fbc6f1024eb4b087bc9bf59a1d655a8056a60c0b4021d3beaeec3f0743503f52467fd89d2cf0e7eccf2831feb40a05ad541a17637ea21ba10b21c2004deb languageName: node linkType: hard -"@rushstack/ts-command-line@npm:5.3.3": - version: 5.3.3 - resolution: "@rushstack/ts-command-line@npm:5.3.3" +"@simple-libs/child-process-utils@npm:^2.0.0": + version: 2.0.0 + resolution: "@simple-libs/child-process-utils@npm:2.0.0" dependencies: - "@rushstack/terminal": "npm:0.22.3" - "@types/argparse": "npm:1.0.38" - argparse: "npm:~1.0.9" - string-argv: "npm:~0.3.1" - checksum: 10c0/03d9e9989b2979884b952eefa519a3abfe1b218516c5468bc162aeae3ddca003e09574019d38018b4ca86736f0807084226d60253e3c6fb63b7f40aacfde2f45 - languageName: node - linkType: hard - -"@simple-libs/stream-utils@npm:^1.2.0": - version: 1.2.0 - resolution: "@simple-libs/stream-utils@npm:1.2.0" - checksum: 10c0/2788ac7b167d1b6c81b8c6fae2f5d9688b1f02ab31e9e15b33c9dc2ae920cf7de87869de10679be8957f9adb645c91c8919e271f3e34b6b4ec56daf725522dc7 - languageName: node - linkType: hard - -"@standard-schema/spec@npm:^1.0.0": - version: 1.1.0 - resolution: "@standard-schema/spec@npm:1.1.0" - checksum: 10c0/d90f55acde4b2deb983529c87e8025fa693de1a5e8b49ecc6eb84d1fd96328add0e03d7d551442156c7432fd78165b2c26ff561b970a9a881f046abb78d6a526 - languageName: node - linkType: hard - -"@tsconfig/node10@npm:^1.0.7": - version: 1.0.9 - resolution: "@tsconfig/node10@npm:1.0.9" - checksum: 10c0/c176a2c1e1b16be120c328300ea910df15fb9a5277010116d26818272341a11483c5a80059389d04edacf6fd2d03d4687ad3660870fdd1cc0b7109e160adb220 + "@simple-libs/stream-utils": "npm:^2.0.0" + checksum: 10c0/8bd2ad6fe0eb7ed45e7f656b8fb2ea249673dad807ba69b950a91319747fd871c386010c766cbf8ac7242aeef76d4a03f83765c068ebb6267e2bf7e6a042e2f2 languageName: node linkType: hard -"@tsconfig/node12@npm:^1.0.7": - version: 1.0.11 - resolution: "@tsconfig/node12@npm:1.0.11" - checksum: 10c0/dddca2b553e2bee1308a056705103fc8304e42bb2d2cbd797b84403a223b25c78f2c683ec3e24a095e82cd435387c877239bffcb15a590ba817cd3f6b9a99fd9 - languageName: node - linkType: hard - -"@tsconfig/node14@npm:^1.0.0": - version: 1.0.3 - resolution: "@tsconfig/node14@npm:1.0.3" - checksum: 10c0/67c1316d065fdaa32525bc9449ff82c197c4c19092b9663b23213c8cbbf8d88b6ed6a17898e0cbc2711950fbfaf40388938c1c748a2ee89f7234fc9e7fe2bf44 - languageName: node - linkType: hard - -"@tsconfig/node16@npm:^1.0.2": - version: 1.0.4 - resolution: "@tsconfig/node16@npm:1.0.4" - checksum: 10c0/05f8f2734e266fb1839eb1d57290df1664fe2aa3b0fdd685a9035806daa635f7519bf6d5d9b33f6e69dd545b8c46bd6e2b5c79acb2b1f146e885f7f11a42a5bb - languageName: node - linkType: hard - -"@types/argparse@npm:1.0.38": - version: 1.0.38 - resolution: "@types/argparse@npm:1.0.38" - checksum: 10c0/4fc892da5df16923f48180da2d1f4562fa8b0507cf636b24780444fa0a1d7321d4dc0c0ecbee6152968823f5a2ae0d321b4f8c705a489bf1ae1245bdeb0868fd +"@simple-libs/stream-utils@npm:^2.0.0": + version: 2.0.0 + resolution: "@simple-libs/stream-utils@npm:2.0.0" + checksum: 10c0/27fa4a9eef3d652a80b970bee18589a618f66884cd15845a0c57d7b73f005ea4d9acda7fe580851af229d07de749419715d06dcedf33bcb1a671e7a27106fdda languageName: node linkType: hard @@ -1127,13 +851,6 @@ __metadata: languageName: node linkType: hard -"@types/estree@npm:1.0.8, @types/estree@npm:^1.0.8": - version: 1.0.8 - resolution: "@types/estree@npm:1.0.8" - checksum: 10c0/39d34d1afaa338ab9763f37ad6066e3f349444f9052b9676a7cc0252ef9485a41c6d81c9c4e0d26e9077993354edf25efc853f3224dd4b447175ef62bdcc86a5 - languageName: node - linkType: hard - "@types/estree@npm:^1.0.0": version: 1.0.5 resolution: "@types/estree@npm:1.0.5" @@ -1148,6 +865,13 @@ __metadata: languageName: node linkType: hard +"@types/estree@npm:^1.0.8": + version: 1.0.8 + resolution: "@types/estree@npm:1.0.8" + checksum: 10c0/39d34d1afaa338ab9763f37ad6066e3f349444f9052b9676a7cc0252ef9485a41c6d81c9c4e0d26e9077993354edf25efc853f3224dd4b447175ef62bdcc86a5 + languageName: node + linkType: hard + "@types/json-schema@npm:^7.0.15": version: 7.0.15 resolution: "@types/json-schema@npm:7.0.15" @@ -1155,281 +879,259 @@ __metadata: languageName: node linkType: hard -"@types/node@npm:^25.3.5": - version: 25.3.5 - resolution: "@types/node@npm:25.3.5" +"@types/node@npm:^24.19.0": + version: 24.19.0 + resolution: "@types/node@npm:24.19.0" dependencies: - undici-types: "npm:~7.18.0" - checksum: 10c0/4cf0834a6f6933bf0aca6afead117ae3db3b8f02a5f7187a24f871db0fb9344e5e46573ba387bc53b7505e1e219c4c535cbe67221ced95bb5ad98573223b19d0 + undici-types: "npm:>=7.24.0 <7.24.7" + checksum: 10c0/f38afca05eb323a01243aa06a4cb8e69ef3423e1115e4464a211c6556156c024bb80fadeb7e3691802a247e7e653b70bad8c528b291153e4f48fccecc6159735 languageName: node linkType: hard -"@typescript-eslint/eslint-plugin@npm:8.56.1": - version: 8.56.1 - resolution: "@typescript-eslint/eslint-plugin@npm:8.56.1" +"@types/nunjucks@npm:^3.2.6": + version: 3.2.6 + resolution: "@types/nunjucks@npm:3.2.6" + checksum: 10c0/bc62c5c9305c0c63a49a55c3553393fdc84d123b4831668212380077ee6646c3ba52bc49e3c651257addf3a47d775707dcefeaf5e1b76cf7f680096939894142 + languageName: node + linkType: hard + +"@typescript-eslint/eslint-plugin@npm:8.70.1": + version: 8.70.1 + resolution: "@typescript-eslint/eslint-plugin@npm:8.70.1" dependencies: "@eslint-community/regexpp": "npm:^4.12.2" - "@typescript-eslint/scope-manager": "npm:8.56.1" - "@typescript-eslint/type-utils": "npm:8.56.1" - "@typescript-eslint/utils": "npm:8.56.1" - "@typescript-eslint/visitor-keys": "npm:8.56.1" + "@typescript-eslint/scope-manager": "npm:8.70.1" + "@typescript-eslint/type-utils": "npm:8.70.1" + "@typescript-eslint/utils": "npm:8.70.1" + "@typescript-eslint/visitor-keys": "npm:8.70.1" ignore: "npm:^7.0.5" natural-compare: "npm:^1.4.0" - ts-api-utils: "npm:^2.4.0" + ts-api-utils: "npm:^2.5.0" peerDependencies: - "@typescript-eslint/parser": ^8.56.1 + "@typescript-eslint/parser": ^8.70.1 eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 - typescript: ">=4.8.4 <6.0.0" - checksum: 10c0/8a97e777792ee3e25078884ba0a04f6732367779c9487abcdc5a2d65b224515fa6a0cf1fac1aafc52fb30f3af97f2e1c9949aadbd6ca74a0165691f95494a721 + typescript: ">=4.8.4 <6.1.0" + checksum: 10c0/6d703bd93dd65ad8bea35dfc7d79a69c0ecbcb7725e289028446673978fc2064993e1c6479dc70c06e1b776cea1a79fce17092cca337183cdd9f93c44f79f27a languageName: node linkType: hard -"@typescript-eslint/parser@npm:8.56.1": - version: 8.56.1 - resolution: "@typescript-eslint/parser@npm:8.56.1" +"@typescript-eslint/parser@npm:8.70.1": + version: 8.70.1 + resolution: "@typescript-eslint/parser@npm:8.70.1" dependencies: - "@typescript-eslint/scope-manager": "npm:8.56.1" - "@typescript-eslint/types": "npm:8.56.1" - "@typescript-eslint/typescript-estree": "npm:8.56.1" - "@typescript-eslint/visitor-keys": "npm:8.56.1" + "@typescript-eslint/scope-manager": "npm:8.70.1" + "@typescript-eslint/types": "npm:8.70.1" + "@typescript-eslint/typescript-estree": "npm:8.70.1" + "@typescript-eslint/visitor-keys": "npm:8.70.1" debug: "npm:^4.4.3" peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 - typescript: ">=4.8.4 <6.0.0" - checksum: 10c0/61c9dab481e795b01835c00c9c7c845f1d7ea7faf3b8657fccee0f8658a65390cb5fe2b5230ae8c4241bd6e0c32aa9455a91989a492bd3bd6fec7c7d9339377a + typescript: ">=4.8.4 <6.1.0" + checksum: 10c0/892025824f25e64ced093dc25a4ae1b6a03b1cdf8bbb00637914bd4fd640887bdc3fcf585f2eadb260677d329690c7e837aedc3680d5532f8277fd30e451bc30 languageName: node linkType: hard -"@typescript-eslint/project-service@npm:8.56.1": - version: 8.56.1 - resolution: "@typescript-eslint/project-service@npm:8.56.1" +"@typescript-eslint/project-service@npm:8.70.1": + version: 8.70.1 + resolution: "@typescript-eslint/project-service@npm:8.70.1" dependencies: - "@typescript-eslint/tsconfig-utils": "npm:^8.56.1" - "@typescript-eslint/types": "npm:^8.56.1" + "@typescript-eslint/tsconfig-utils": "npm:^8.70.1" + "@typescript-eslint/types": "npm:^8.70.1" debug: "npm:^4.4.3" peerDependencies: - typescript: ">=4.8.4 <6.0.0" - checksum: 10c0/ca61cde575233bc79046d73ddd330d183fb3cbb941fddc31919336317cda39885c59296e2e5401b03d9325a64a629e842fd66865705ff0d85d83ee3ee40871e8 + typescript: ">=4.8.4 <6.1.0" + checksum: 10c0/3f168d3dd18fd63718e0758c6e4f28741640c2dc38f18a46c9f2276bdc8a6e0927bfcd1f0cf40e01b6c2e3c2eac179ff6ed916c5ad539e284fb66511066e1425 languageName: node linkType: hard -"@typescript-eslint/scope-manager@npm:8.56.1": - version: 8.56.1 - resolution: "@typescript-eslint/scope-manager@npm:8.56.1" +"@typescript-eslint/scope-manager@npm:8.70.1": + version: 8.70.1 + resolution: "@typescript-eslint/scope-manager@npm:8.70.1" dependencies: - "@typescript-eslint/types": "npm:8.56.1" - "@typescript-eslint/visitor-keys": "npm:8.56.1" - checksum: 10c0/89cc1af2635eee23f2aa2ff87c08f88f3ad972ebf67eaacdc604a4ef4178535682bad73fd086e6f3c542e4e5d874253349af10d58291d079cc29c6c7e9831de4 + "@typescript-eslint/types": "npm:8.70.1" + "@typescript-eslint/visitor-keys": "npm:8.70.1" + checksum: 10c0/979ef0a6119ffd7045dc0e7e14c9e9a12c135a83554962ff4b33acab7bfdabda3631e8c6818ef5fc6c9b1d21d381f61e593a1bd8c92019333926447a27aaca21 languageName: node linkType: hard -"@typescript-eslint/tsconfig-utils@npm:8.56.1, @typescript-eslint/tsconfig-utils@npm:^8.56.1": - version: 8.56.1 - resolution: "@typescript-eslint/tsconfig-utils@npm:8.56.1" +"@typescript-eslint/tsconfig-utils@npm:8.70.1, @typescript-eslint/tsconfig-utils@npm:^8.70.1": + version: 8.70.1 + resolution: "@typescript-eslint/tsconfig-utils@npm:8.70.1" peerDependencies: - typescript: ">=4.8.4 <6.0.0" - checksum: 10c0/d03b64d7ff19020beeefa493ae667c2e67a4547d25a3ecb9210a3a52afe980c093d772a91014bae699ee148bfb60cc659479e02bfc2946ea06954a8478ef1fe1 + typescript: ">=4.8.4 <6.1.0" + checksum: 10c0/c1b11fd209a547f637403a4143a5bfc72bfa6eae2f6771b62131392964f5012056608f10ce9156a5316f8b16cbe98f41de156e3f83f72870a875c80c0a8b9248 languageName: node linkType: hard -"@typescript-eslint/type-utils@npm:8.56.1": - version: 8.56.1 - resolution: "@typescript-eslint/type-utils@npm:8.56.1" +"@typescript-eslint/type-utils@npm:8.70.1": + version: 8.70.1 + resolution: "@typescript-eslint/type-utils@npm:8.70.1" dependencies: - "@typescript-eslint/types": "npm:8.56.1" - "@typescript-eslint/typescript-estree": "npm:8.56.1" - "@typescript-eslint/utils": "npm:8.56.1" + "@typescript-eslint/types": "npm:8.70.1" + "@typescript-eslint/typescript-estree": "npm:8.70.1" + "@typescript-eslint/utils": "npm:8.70.1" debug: "npm:^4.4.3" - ts-api-utils: "npm:^2.4.0" + ts-api-utils: "npm:^2.5.0" peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 - typescript: ">=4.8.4 <6.0.0" - checksum: 10c0/66517aed5059ef4a29605d06a510582f934d5789ae40ad673f1f0421f8aa13ec9ba7b8caab57ae9f270afacbf13ec5359cedfe74f21ae77e9a2364929f7e7cee + typescript: ">=4.8.4 <6.1.0" + checksum: 10c0/b195d46e7f9a4aece9bb7643db698a640c61a1dff846ae5a076c1b5e55b182f21fa2f57e6708438dcba067a61fccde61b2743c37bedc321768f2b6b3967dfa25 languageName: node linkType: hard -"@typescript-eslint/types@npm:8.56.1, @typescript-eslint/types@npm:^8.56.1": - version: 8.56.1 - resolution: "@typescript-eslint/types@npm:8.56.1" - checksum: 10c0/e5a0318abddf0c4f98da3039cb10b3c0601c8601f7a9f7043630f0d622dabfe83a4cd833545ad3531fc846e46ca2874377277b392c2490dffec279d9242d827b +"@typescript-eslint/types@npm:8.70.1, @typescript-eslint/types@npm:^8.70.1": + version: 8.70.1 + resolution: "@typescript-eslint/types@npm:8.70.1" + checksum: 10c0/8bbc4806c5d9a7350b0cba1e60e93b235a3935d33972efba8f127688cc29db49efef2e42472bb5127f211daf2fbfd9f05be4e9bf8cd4cb8e7fdebb7be543ce16 languageName: node linkType: hard -"@typescript-eslint/typescript-estree@npm:8.56.1": - version: 8.56.1 - resolution: "@typescript-eslint/typescript-estree@npm:8.56.1" +"@typescript-eslint/typescript-estree@npm:8.70.1": + version: 8.70.1 + resolution: "@typescript-eslint/typescript-estree@npm:8.70.1" dependencies: - "@typescript-eslint/project-service": "npm:8.56.1" - "@typescript-eslint/tsconfig-utils": "npm:8.56.1" - "@typescript-eslint/types": "npm:8.56.1" - "@typescript-eslint/visitor-keys": "npm:8.56.1" + "@typescript-eslint/project-service": "npm:8.70.1" + "@typescript-eslint/tsconfig-utils": "npm:8.70.1" + "@typescript-eslint/types": "npm:8.70.1" + "@typescript-eslint/visitor-keys": "npm:8.70.1" debug: "npm:^4.4.3" minimatch: "npm:^10.2.2" semver: "npm:^7.7.3" tinyglobby: "npm:^0.2.15" - ts-api-utils: "npm:^2.4.0" + ts-api-utils: "npm:^2.5.0" peerDependencies: - typescript: ">=4.8.4 <6.0.0" - checksum: 10c0/92f4421dac41be289761200dc2ed85974fa451deacb09490ae1870a25b71b97218e609a90d4addba9ded5b2abdebc265c9db7f6e9ce6d29ed20e89b8487e9618 + typescript: ">=4.8.4 <6.1.0" + checksum: 10c0/d0444d74a6136c92bc83d89dfb0515b1f9a9809ed6913941c41104d163eb0f40fb8b13741f93b8d0def9c41d200f6fa691507723a1433d9f25ffe6857f16bb3a languageName: node linkType: hard -"@typescript-eslint/utils@npm:8.56.1": - version: 8.56.1 - resolution: "@typescript-eslint/utils@npm:8.56.1" +"@typescript-eslint/utils@npm:8.70.1": + version: 8.70.1 + resolution: "@typescript-eslint/utils@npm:8.70.1" dependencies: "@eslint-community/eslint-utils": "npm:^4.9.1" - "@typescript-eslint/scope-manager": "npm:8.56.1" - "@typescript-eslint/types": "npm:8.56.1" - "@typescript-eslint/typescript-estree": "npm:8.56.1" + "@typescript-eslint/scope-manager": "npm:8.70.1" + "@typescript-eslint/types": "npm:8.70.1" + "@typescript-eslint/typescript-estree": "npm:8.70.1" peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 - typescript: ">=4.8.4 <6.0.0" - checksum: 10c0/d9ffd9b2944a2c425e0532f71dc61e61d0a923d1a17733cf2777c2a4ae638307d12d44f63b33b6b3dc62f02f47db93ec49344ecefe17b76ee3e4fb0833325be3 + typescript: ">=4.8.4 <6.1.0" + checksum: 10c0/c946cf4ea68651ae25352c43fbc1cdb108079b70bed8a3bc86f0e3b35f166e468ab2c8b5d8137078622c1b22fc5599d55d8b88d530c2674df2d41f9e8bd3b305 languageName: node linkType: hard -"@typescript-eslint/visitor-keys@npm:8.56.1": - version: 8.56.1 - resolution: "@typescript-eslint/visitor-keys@npm:8.56.1" +"@typescript-eslint/visitor-keys@npm:8.70.1": + version: 8.70.1 + resolution: "@typescript-eslint/visitor-keys@npm:8.70.1" dependencies: - "@typescript-eslint/types": "npm:8.56.1" + "@typescript-eslint/types": "npm:8.70.1" eslint-visitor-keys: "npm:^5.0.0" - checksum: 10c0/86d97905dec1af964cc177c185933d040449acf6006096497f2e0093c6a53eb92b3ac1db9eb40a5a2e8d91160f558c9734331a9280797f09f284c38978b22190 + checksum: 10c0/b5faf6ec7ef8c39bcc127401a2197315fa784a30bcc0e97c5e0dda3c2f574b64e097d6ca9d886accccd3a67f78a77e2b53520735a733d6e15dcd0fa666bb657c languageName: node linkType: hard -"@vitest/coverage-v8@npm:4.0.18": - version: 4.0.18 - resolution: "@vitest/coverage-v8@npm:4.0.18" +"@vitest/coverage-v8@npm:5.0.2": + version: 5.0.2 + resolution: "@vitest/coverage-v8@npm:5.0.2" dependencies: "@bcoe/v8-coverage": "npm:^1.0.2" - "@vitest/utils": "npm:4.0.18" - ast-v8-to-istanbul: "npm:^0.3.10" - istanbul-lib-coverage: "npm:^3.2.2" - istanbul-lib-report: "npm:^3.0.1" - istanbul-reports: "npm:^3.2.0" - magicast: "npm:^0.5.1" - obug: "npm:^2.1.1" - std-env: "npm:^3.10.0" - tinyrainbow: "npm:^3.0.3" + "@vitest/istanbul-lib-coverage": "npm:^1.0.0" + "@vitest/istanbul-lib-report": "npm:^1.0.0" + ast-v8-to-istanbul: "npm:^1.0.5" + magicast: "npm:^0.5.4" + obug: "npm:^2.1.4" + std-env: "npm:^4.2.0" + tinyrainbow: "npm:^3.1.1" peerDependencies: - "@vitest/browser": 4.0.18 - vitest: 4.0.18 + "@vitest/browser": 5.0.2 + vitest: 5.0.2 peerDependenciesMeta: "@vitest/browser": optional: true - checksum: 10c0/e23e0da86f0b2a020c51562bc40ebdc7fc7553c24f8071dfb39a6df0161badbd5eaf2eebbf8ceaef18933a18c1934ff52d1c0c4bde77bb87e0c1feb0c8cbee4d + checksum: 10c0/291db720aa27ee9ba609104fbf0c6442ea7db595e8e415be63614e68885fab8b267dedabe85eff24f93f247d058b9467e8cdd28ec30a9224fedbf498b763691d languageName: node linkType: hard -"@vitest/expect@npm:4.0.18": - version: 4.0.18 - resolution: "@vitest/expect@npm:4.0.18" +"@vitest/istanbul-lib-coverage@npm:1.0.1, @vitest/istanbul-lib-coverage@npm:^1.0.0": + version: 1.0.1 + resolution: "@vitest/istanbul-lib-coverage@npm:1.0.1" + checksum: 10c0/5259767db6c748a018c39554951e19818268fb6f72f74b74d2370471c7d83df990c3371247caea7d38f8e315df6dd461427dc242a1e7f5071e4b28863524f354 + languageName: node + linkType: hard + +"@vitest/istanbul-lib-report@npm:^1.0.0": + version: 1.0.1 + resolution: "@vitest/istanbul-lib-report@npm:1.0.1" dependencies: - "@standard-schema/spec": "npm:^1.0.0" - "@types/chai": "npm:^5.2.2" - "@vitest/spy": "npm:4.0.18" - "@vitest/utils": "npm:4.0.18" - chai: "npm:^6.2.1" - tinyrainbow: "npm:^3.0.3" - checksum: 10c0/123b0aa111682e82ec5289186df18037b1a1768700e468ee0f9879709aaa320cf790463c15c0d8ee10df92b402f4394baf5d27797e604d78e674766d87bcaadc + "@vitest/istanbul-lib-coverage": "npm:1.0.1" + checksum: 10c0/1a76d4cd4c6284da0b897230d672769a5191235b3a0df41a3c861e3e12659d1790012705df6bad0f540e3f14b7b017f3611f99c9b9eb96972f8b55a3fb9270fe languageName: node linkType: hard -"@vitest/mocker@npm:4.0.18": - version: 4.0.18 - resolution: "@vitest/mocker@npm:4.0.18" +"@vitest/mocker@npm:5.0.2": + version: 5.0.2 + resolution: "@vitest/mocker@npm:5.0.2" dependencies: - "@vitest/spy": "npm:4.0.18" + "@jridgewell/trace-mapping": "npm:0.3.31" + "@vitest/spy": "npm:5.0.2" estree-walker: "npm:^3.0.3" - magic-string: "npm:^0.30.21" + magic-string: "npm:^1.2.3" peerDependencies: msw: ^2.4.9 - vite: ^6.0.0 || ^7.0.0-0 + vite: ^6.0.0 || ^7.0.0 || ^8.0.0 peerDependenciesMeta: msw: optional: true vite: optional: true - checksum: 10c0/fb0a257e7e167759d4ad228d53fa7bad2267586459c4a62188f2043dd7163b4b02e1e496dc3c227837f776e7d73d6c4343613e89e7da379d9d30de8260f1ee4b - languageName: node - linkType: hard - -"@vitest/pretty-format@npm:4.0.18": - version: 4.0.18 - resolution: "@vitest/pretty-format@npm:4.0.18" - dependencies: - tinyrainbow: "npm:^3.0.3" - checksum: 10c0/0086b8c88eeca896d8e4b98fcdef452c8041a1b63eb9e85d3e0bcc96c8aa76d8e9e0b6990ebb0bb0a697c4ebab347e7735888b24f507dbff2742ddce7723fd94 - languageName: node - linkType: hard - -"@vitest/runner@npm:4.0.18": - version: 4.0.18 - resolution: "@vitest/runner@npm:4.0.18" - dependencies: - "@vitest/utils": "npm:4.0.18" - pathe: "npm:^2.0.3" - checksum: 10c0/fdb4afa411475133c05ba266c8092eaf1e56cbd5fb601f92ec6ccb9bab7ca52e06733ee8626599355cba4ee71cb3a8f28c84d3b69dc972e41047edc50229bc01 + checksum: 10c0/e2d847563d47b61e9f3e5ed2d86af42b753311cd52c94ef7226398fd6f810b96ca4737855bcc4a59d78a0300859db50693d831ab29ca6334b159cc8e870f8149 languageName: node linkType: hard -"@vitest/snapshot@npm:4.0.18": - version: 4.0.18 - resolution: "@vitest/snapshot@npm:4.0.18" +"@vitest/pretty-format@npm:5.0.2": + version: 5.0.2 + resolution: "@vitest/pretty-format@npm:5.0.2" dependencies: - "@vitest/pretty-format": "npm:4.0.18" - magic-string: "npm:^0.30.21" - pathe: "npm:^2.0.3" - checksum: 10c0/d3bfefa558db9a69a66886ace6575eb96903a5ba59f4d9a5d0fecb4acc2bb8dbb443ef409f5ac1475f2e1add30bd1d71280f98912da35e89c75829df9e84ea43 + tinyrainbow: "npm:^3.1.1" + checksum: 10c0/fd3a6c7ab541ca1cf9b21cc8f44e141f50e5d07eb456edb85f1f9eacfd3a276a9ab5a442cbc35c023c3fc0f05cf2bfa022a7960d33538a2872db2043bf207616 languageName: node linkType: hard -"@vitest/spy@npm:4.0.18": - version: 4.0.18 - resolution: "@vitest/spy@npm:4.0.18" - checksum: 10c0/6de537890b3994fcadb8e8d8ac05942320ae184f071ec395d978a5fba7fa928cbb0c5de85af86a1c165706c466e840de8779eaff8c93450c511c7abaeb9b8a4e +"@vitest/spy@npm:5.0.2": + version: 5.0.2 + resolution: "@vitest/spy@npm:5.0.2" + checksum: 10c0/4a52231cc7b5b8959bb15a700a761464051a6d0cc35a231fc92fd4f58d10a01012671a20ea37ebc79e98bdd5dc257a3c52db10d6a592944560fb69651f6f1296 languageName: node linkType: hard -"@vitest/ui@npm:4.0.18": - version: 4.0.18 - resolution: "@vitest/ui@npm:4.0.18" +"@vitest/ui@npm:5.0.2": + version: 5.0.2 + resolution: "@vitest/ui@npm:5.0.2" dependencies: - "@vitest/utils": "npm:4.0.18" - fflate: "npm:^0.8.2" - flatted: "npm:^3.3.3" + "@vitest/utils": "npm:5.0.2" + fflate: "npm:^0.8.3" + flatted: "npm:^3.4.4" pathe: "npm:^2.0.3" sirv: "npm:^3.0.2" - tinyglobby: "npm:^0.2.15" - tinyrainbow: "npm:^3.0.3" + tinyrainbow: "npm:^3.1.1" peerDependencies: - vitest: 4.0.18 - checksum: 10c0/25ed45b52948101ba5c641aeb84126ce695d067984b8430f797498f7e3aeee53c5392e9e14290744a68b5114cfe933e84675b85340204cdbd118079217985157 - languageName: node - linkType: hard - -"@vitest/utils@npm:4.0.18": - version: 4.0.18 - resolution: "@vitest/utils@npm:4.0.18" - dependencies: - "@vitest/pretty-format": "npm:4.0.18" - tinyrainbow: "npm:^3.0.3" - checksum: 10c0/4a3c43c1421eb90f38576926496f6c80056167ba111e63f77cf118983902673737a1a38880b890d7c06ec0a12475024587344ee502b3c43093781533022f2aeb + vitest: 5.0.2 + checksum: 10c0/f232eae237a794b211a1c23ce10e98d125233070af23caaa92e7b89222a6118030df05525c094f493d650bfdcc190636763da1d5e3ed35976a59e6eefa0db5de languageName: node linkType: hard -"@volar/language-core@npm:2.4.11": - version: 2.4.11 - resolution: "@volar/language-core@npm:2.4.11" +"@vitest/utils@npm:5.0.2": + version: 5.0.2 + resolution: "@vitest/utils@npm:5.0.2" dependencies: - "@volar/source-map": "npm:2.4.11" - checksum: 10c0/ccc5de0c28b4186dc99ff9856b2ac2318ee1818480af3ca406f3c09d42b19b6df8698b525f6cf0fed368332fc76659cd4433fb38e6a55a85c7cefc97d665ccf8 + "@vitest/pretty-format": "npm:5.0.2" + convert-source-map: "npm:^2.0.0" + tinyrainbow: "npm:^3.1.1" + checksum: 10c0/5711e43595ce7afe83d0989f301ad6bc00605786fc39242953d2885fbdee2956e51d4ee32ad005234b4d81887538c385d480fe635d609a1646eb530d5b0802b6 languageName: node linkType: hard -"@volar/language-core@npm:~2.4.11": +"@volar/language-core@npm:2.4.28": version: 2.4.28 resolution: "@volar/language-core@npm:2.4.28" dependencies: @@ -1438,13 +1140,6 @@ __metadata: languageName: node linkType: hard -"@volar/source-map@npm:2.4.11": - version: 2.4.11 - resolution: "@volar/source-map@npm:2.4.11" - checksum: 10c0/8e5badf9f67d669679c48fe32258e082d823523ca2807438d38c0aac6c52b84d43580c8921b559fc5a27c0c7457ffcba569e60de7a597851690dec04ed77c5fc - languageName: node - linkType: hard - "@volar/source-map@npm:2.4.28": version: 2.4.28 resolution: "@volar/source-map@npm:2.4.28" @@ -1452,75 +1147,21 @@ __metadata: languageName: node linkType: hard -"@volar/typescript@npm:^2.4.11": - version: 2.4.11 - resolution: "@volar/typescript@npm:2.4.11" +"@volar/typescript@npm:^2.4.26": + version: 2.4.28 + resolution: "@volar/typescript@npm:2.4.28" dependencies: - "@volar/language-core": "npm:2.4.11" + "@volar/language-core": "npm:2.4.28" path-browserify: "npm:^1.0.1" vscode-uri: "npm:^3.0.8" - checksum: 10c0/bca9bda9c8c95fd06672b834d1804810fdad496e15ee8e2099f76de74fa529d835f342afb6b976e6e3bc4599a3bbbfd007a842694fe1300cf6286783b827f917 - languageName: node - linkType: hard - -"@vue/compiler-core@npm:3.5.13": - version: 3.5.13 - resolution: "@vue/compiler-core@npm:3.5.13" - dependencies: - "@babel/parser": "npm:^7.25.3" - "@vue/shared": "npm:3.5.13" - entities: "npm:^4.5.0" - estree-walker: "npm:^2.0.2" - source-map-js: "npm:^1.2.0" - checksum: 10c0/b89f3e3ca92c3177ae449ada1480df13d99b5b3b2cdcf3202fd37dc30f294a1db1f473209f8bae9233e2d338632219d39b2bfa6941d158cea55255e4b0b30f90 - languageName: node - linkType: hard - -"@vue/compiler-dom@npm:^3.5.0": - version: 3.5.13 - resolution: "@vue/compiler-dom@npm:3.5.13" - dependencies: - "@vue/compiler-core": "npm:3.5.13" - "@vue/shared": "npm:3.5.13" - checksum: 10c0/8f424a71883c9ef4abdd125d2be8d12dd8cf94ba56089245c88734b1f87c65e10597816070ba2ea0a297a2f66dc579f39275a9a53ef5664c143a12409612cd72 - languageName: node - linkType: hard - -"@vue/compiler-vue2@npm:^2.7.16": - version: 2.7.16 - resolution: "@vue/compiler-vue2@npm:2.7.16" - dependencies: - de-indent: "npm:^1.0.2" - he: "npm:^1.2.0" - checksum: 10c0/c76c3fad770b9a7da40b314116cc9da173da20e5fd68785c8ed8dd8a87d02f239545fa296e16552e040ec86b47bfb18283b39447b250c2e76e479bd6ae475bb3 + checksum: 10c0/075c890b9ec1cb17f17e38aaed035f8ee7d507439e87270d8e3c394356fc9387fd0bda9ec1069b36ea4c378d9375a08f5bc64c063a83427010ddd86d472124fc languageName: node linkType: hard -"@vue/language-core@npm:2.2.0": - version: 2.2.0 - resolution: "@vue/language-core@npm:2.2.0" - dependencies: - "@volar/language-core": "npm:~2.4.11" - "@vue/compiler-dom": "npm:^3.5.0" - "@vue/compiler-vue2": "npm:^2.7.16" - "@vue/shared": "npm:^3.5.0" - alien-signals: "npm:^0.4.9" - minimatch: "npm:^9.0.3" - muggle-string: "npm:^0.4.1" - path-browserify: "npm:^1.0.1" - peerDependencies: - typescript: "*" - peerDependenciesMeta: - typescript: - optional: true - checksum: 10c0/1c44cc4067266bbc825af358a867aed455963a08c160cd9df9a47571fd917a87d9de9bdea6149877e0c8309a6cf39f263e7cf2fbadeceba47a5a158f392151b2 - languageName: node - linkType: hard - -"@vue/shared@npm:3.5.13, @vue/shared@npm:^3.5.0": - version: 3.5.13 - resolution: "@vue/shared@npm:3.5.13" - checksum: 10c0/2c940ef907116f1c2583ca1d7733984e5705983ab07054c4e72f1d95eb0f7bdf4d01efbdaee1776c2008f79595963f44e98fced057f5957d86d57b70028f5025 +"a-sync-waterfall@npm:^1.0.0": + version: 1.0.1 + resolution: "a-sync-waterfall@npm:1.0.1" + checksum: 10c0/1c7b258da2c77eb1447dcc683afb10ca3dc8880de990562ccbb7b282538aba01e910345ce9e8500c1458272c7866b85fcfa5ca8159e33550b011ab5c586ec5a4 languageName: node linkType: hard @@ -1540,10 +1181,12 @@ __metadata: languageName: node linkType: hard -"acorn-walk@npm:^8.1.1": - version: 8.3.2 - resolution: "acorn-walk@npm:8.3.2" - checksum: 10c0/7e2a8dad5480df7f872569b9dccff2f3da7e65f5353686b1d6032ab9f4ddf6e3a2cb83a9b52cf50b1497fd522154dda92f0abf7153290cc79cd14721ff121e52 +"acorn@npm:^8.15.0": + version: 8.18.0 + resolution: "acorn@npm:8.18.0" + bin: + acorn: bin/acorn + checksum: 10c0/be771be2135cc07910cf76f444ad514d7dcfd6d4a8026e597e93155275abc8ef61eee12211d52146e9d962874269b634f397464942087be013c89d0c54c5f8e5 languageName: node linkType: hard @@ -1556,45 +1199,10 @@ __metadata: languageName: node linkType: hard -"acorn@npm:^8.4.1": - version: 8.11.3 - resolution: "acorn@npm:8.11.3" - bin: - acorn: bin/acorn - checksum: 10c0/3ff155f8812e4a746fee8ecff1f227d527c4c45655bb1fad6347c3cb58e46190598217551b1500f18542d2bbe5c87120cb6927f5a074a59166fbdd9468f0a299 - languageName: node - linkType: hard - "agent-base@npm:^7.1.0, agent-base@npm:^7.1.2": version: 7.1.4 resolution: "agent-base@npm:7.1.4" - checksum: 10c0/c2c9ab7599692d594b6a161559ada307b7a624fa4c7b03e3afdb5a5e31cd0e53269115b620fcab024c5ac6a6f37fa5eb2e004f076ad30f5f7e6b8b671f7b35fe - languageName: node - linkType: hard - -"ajv-draft-04@npm:~1.0.0": - version: 1.0.0 - resolution: "ajv-draft-04@npm:1.0.0" - peerDependencies: - ajv: ^8.5.0 - peerDependenciesMeta: - ajv: - optional: true - checksum: 10c0/6044310bd38c17d77549fd326bd40ce1506fa10b0794540aa130180808bf94117fac8c9b448c621512bea60e4a947278f6a978e87f10d342950c15b33ddd9271 - languageName: node - linkType: hard - -"ajv-formats@npm:~3.0.1": - version: 3.0.1 - resolution: "ajv-formats@npm:3.0.1" - dependencies: - ajv: "npm:^8.0.0" - peerDependencies: - ajv: ^8.0.0 - peerDependenciesMeta: - ajv: - optional: true - checksum: 10c0/168d6bca1ea9f163b41c8147bae537e67bd963357a5488a1eaf3abe8baa8eec806d4e45f15b10767e6020679315c7e1e5e6803088dfb84efa2b4e9353b83dd0a + checksum: 10c0/c2c9ab7599692d594b6a161559ada307b7a624fa4c7b03e3afdb5a5e31cd0e53269115b620fcab024c5ac6a6f37fa5eb2e004f076ad30f5f7e6b8b671f7b35fe languageName: node linkType: hard @@ -1610,18 +1218,6 @@ __metadata: languageName: node linkType: hard -"ajv@npm:^8.0.0": - version: 8.17.1 - resolution: "ajv@npm:8.17.1" - dependencies: - fast-deep-equal: "npm:^3.1.3" - fast-uri: "npm:^3.0.1" - json-schema-traverse: "npm:^1.0.0" - require-from-string: "npm:^2.0.2" - checksum: 10c0/ec3ba10a573c6b60f94639ffc53526275917a2df6810e4ab5a6b959d87459f9ef3f00d5e7865b82677cb7d21590355b34da14d1d0b9c32d75f95a187e76fff35 - languageName: node - linkType: hard - "ajv@npm:^8.11.0": version: 8.12.0 resolution: "ajv@npm:8.12.0" @@ -1634,25 +1230,6 @@ __metadata: languageName: node linkType: hard -"ajv@npm:~8.18.0": - version: 8.18.0 - resolution: "ajv@npm:8.18.0" - dependencies: - fast-deep-equal: "npm:^3.1.3" - fast-uri: "npm:^3.0.1" - json-schema-traverse: "npm:^1.0.0" - require-from-string: "npm:^2.0.2" - checksum: 10c0/e7517c426173513a07391be951879932bdf3348feaebd2199f5b901c20f99d60db8cd1591502d4d551dc82f594e82a05c4fe1c70139b15b8937f7afeaed9532f - languageName: node - linkType: hard - -"alien-signals@npm:^0.4.9": - version: 0.4.14 - resolution: "alien-signals@npm:0.4.14" - checksum: 10c0/5abb3377bcaf6b3819e950084b3ebd022ad90210105afb450c89dc347e80e28da441bf34858a57ea122abe7603e552ddbad80dc597c8f02a0a5206c5fb9c20cb - languageName: node - linkType: hard - "ansi-regex@npm:^5.0.1": version: 5.0.1 resolution: "ansi-regex@npm:5.0.1" @@ -1660,6 +1237,13 @@ __metadata: languageName: node linkType: hard +"ansi-regex@npm:^6.2.2": + version: 6.3.0 + resolution: "ansi-regex@npm:6.3.0" + checksum: 10c0/bc047786d0531f1f22d1e22e9863e8554488a399f1ee5270b753efa6de938a788d27456f2b256770f11fdd8b1e847483bc1b55ddc2b3ffe82ec45c875292c651 + languageName: node + linkType: hard + "ansi-styles@npm:^3.2.1": version: 3.2.1 resolution: "ansi-styles@npm:3.2.1" @@ -1678,10 +1262,10 @@ __metadata: languageName: node linkType: hard -"arg@npm:^4.1.0": - version: 4.1.3 - resolution: "arg@npm:4.1.3" - checksum: 10c0/070ff801a9d236a6caa647507bdcc7034530604844d64408149a26b9e87c2f97650055c0f049abd1efc024b334635c01f29e0b632b371ac3f26130f4cf65997a +"ansi-styles@npm:^6.2.1": + version: 6.2.3 + resolution: "ansi-styles@npm:6.2.3" + checksum: 10c0/23b8a4ce14e18fb854693b95351e286b771d23d8844057ed2e7d083cd3e708376c3323707ec6a24365f7d7eda3ca00327fe04092e29e551499ec4c8b7bfac868 languageName: node linkType: hard @@ -1692,19 +1276,17 @@ __metadata: languageName: node linkType: hard -"argparse@npm:~1.0.9": - version: 1.0.10 - resolution: "argparse@npm:1.0.10" - dependencies: - sprintf-js: "npm:~1.0.2" - checksum: 10c0/b2972c5c23c63df66bca144dbc65d180efa74f25f8fd9b7d9a0a6c88ae839db32df3d54770dcb6460cf840d232b60695d1a6b1053f599d84e73f7437087712de +"argue-cli@npm:^3.1.0": + version: 3.2.0 + resolution: "argue-cli@npm:3.2.0" + checksum: 10c0/1b3df0c0e0a7a6199b45124aa2e664d9ec467b484cc9e9263ecf3062cc30725548fd840688ceee8bf5d67955cad8d155111e80ec6626d60b884b6c66264e9940 languageName: node linkType: hard -"array-ify@npm:^1.0.0": - version: 1.0.0 - resolution: "array-ify@npm:1.0.0" - checksum: 10c0/75c9c072faac47bd61779c0c595e912fe660d338504ac70d10e39e1b8a4a0c9c87658703d619b9d1b70d324177ae29dc8d07dda0d0a15d005597bc4c5a59c70c +"asap@npm:^2.0.3": + version: 2.0.6 + resolution: "asap@npm:2.0.6" + checksum: 10c0/c6d5e39fe1f15e4b87677460bd66b66050cd14c772269cee6688824c1410a08ab20254bb6784f9afb75af9144a9f9a7692d49547f4d19d715aeb7c0318f3136d languageName: node linkType: hard @@ -1715,14 +1297,14 @@ __metadata: languageName: node linkType: hard -"ast-v8-to-istanbul@npm:^0.3.10": - version: 0.3.12 - resolution: "ast-v8-to-istanbul@npm:0.3.12" +"ast-v8-to-istanbul@npm:^1.0.5": + version: 1.0.7 + resolution: "ast-v8-to-istanbul@npm:1.0.7" dependencies: "@jridgewell/trace-mapping": "npm:^0.3.31" estree-walker: "npm:^3.0.3" js-tokens: "npm:^10.0.0" - checksum: 10c0/bad6ba222b1073c165c8d65dbf366193d4a90536dabe37f93a3df162269b1c9473975756e4c048f708c235efccc26f8e5321c547b7e9563b64b21b2e0f27cbc9 + checksum: 10c0/c6bbfbf8b8adc8d793f8d10fbf9bcb25b0c65f2faa73ec1e41d2937a9c18327167b9395134943a4814dd3d095d2453b2f0f71a5d33aecd808ac4725c23e69918 languageName: node linkType: hard @@ -1742,6 +1324,15 @@ __metadata: languageName: node linkType: hard +"brace-expansion@npm:^5.0.8": + version: 5.0.12 + resolution: "brace-expansion@npm:5.0.12" + dependencies: + balanced-match: "npm:^4.0.2" + checksum: 10c0/a40ac64d3bdb5bb222456514d3633c1f627f03b844f509c69e70f98b78be0ce85e21b44bbfecdd0ddd4cc0855ca4242b41ea96ddccf88c7c3c7a84405474bb3a + languageName: node + linkType: hard + "cacache@npm:^20.0.1": version: 20.0.3 resolution: "cacache@npm:20.0.3" @@ -1761,6 +1352,19 @@ __metadata: languageName: node linkType: hard +"cacheable@npm:^2.5.0": + version: 2.5.0 + resolution: "cacheable@npm:2.5.0" + dependencies: + "@cacheable/memory": "npm:^2.2.0" + "@cacheable/utils": "npm:^2.5.0" + hookified: "npm:^1.15.0" + keyv: "npm:^5.6.0" + qified: "npm:^0.10.1" + checksum: 10c0/ad4086a60ed327196901c16360312705e35bfde5118457449cfabaf4ece0fecad046985ea8b66b3ece46dd469cb33798d87e1dac3ed2c00746578054bccbf74d + languageName: node + linkType: hard + "callsites@npm:^3.0.0": version: 3.1.0 resolution: "callsites@npm:3.1.0" @@ -1768,7 +1372,7 @@ __metadata: languageName: node linkType: hard -"chai@npm:^6.2.1": +"chai@npm:^6.2.2": version: 6.2.2 resolution: "chai@npm:6.2.2" checksum: 10c0/e6c69e5f0c11dffe6ea13d0290936ebb68fcc1ad688b8e952e131df6a6d5797d5e860bc55cef1aca2e950c3e1f96daf79e9d5a70fb7dbaab4e46355e2635ed53 @@ -1786,6 +1390,13 @@ __metadata: languageName: node linkType: hard +"chalk@npm:^5.6.2": + version: 5.6.2 + resolution: "chalk@npm:5.6.2" + checksum: 10c0/99a4b0f0e7991796b1e7e3f52dceb9137cae2a9dfc8fc0784a550dc4c558e15ab32ed70b14b21b52beb2679b4892b41a0aa44249bcb996f01e125d58477c6976 + languageName: node + linkType: hard + "chownr@npm:^3.0.0": version: 3.0.0 resolution: "chownr@npm:3.0.0" @@ -1804,6 +1415,17 @@ __metadata: languageName: node linkType: hard +"cliui@npm:^9.0.1": + version: 9.0.1 + resolution: "cliui@npm:9.0.1" + dependencies: + string-width: "npm:^7.2.0" + strip-ansi: "npm:^7.1.0" + wrap-ansi: "npm:^9.0.0" + checksum: 10c0/13441832e9efe7c7a76bd2b8e683555c478d461a9f249dc5db9b17fe8d4b47fa9277b503914b90bd00e4a151abb6b9b02b2288972ffe2e5e3ca40bcb1c2330d3 + languageName: node + linkType: hard + "color-convert@npm:^1.9.0": version: 1.9.3 resolution: "color-convert@npm:1.9.3" @@ -1836,13 +1458,10 @@ __metadata: languageName: node linkType: hard -"compare-func@npm:^2.0.0": - version: 2.0.0 - resolution: "compare-func@npm:2.0.0" - dependencies: - array-ify: "npm:^1.0.0" - dot-prop: "npm:^5.1.0" - checksum: 10c0/78bd4dd4ed311a79bd264c9e13c36ed564cde657f1390e699e0f04b8eee1fc06ffb8698ce2dfb5fbe7342d509579c82d4e248f08915b708f77f7b72234086cc3 +"commander@npm:^5.1.0": + version: 5.1.0 + resolution: "commander@npm:5.1.0" + checksum: 10c0/da9d71dbe4ce039faf1fe9eac3771dca8c11d66963341f62602f7b66e36d2a3f8883407af4f9a37b1db1a55c59c0c1325f186425764c2e963dc1d67aec2a4b6d languageName: node linkType: hard @@ -1867,33 +1486,40 @@ __metadata: languageName: node linkType: hard -"conventional-changelog-angular@npm:^8.2.0": - version: 8.3.0 - resolution: "conventional-changelog-angular@npm:8.3.0" +"conventional-changelog-angular@npm:^9.0.0": + version: 9.4.0 + resolution: "conventional-changelog-angular@npm:9.4.0" dependencies: - compare-func: "npm:^2.0.0" - checksum: 10c0/bab87fa741a25e4fb623e2629912a5e592de5ed616398bee0cd9779dc950aae2a78ac48a6f4268cbb5f5544bb33644e01c7b40cea378bb9763ae5304cc22efc2 + "@conventional-changelog/template": "npm:^1.4.0" + checksum: 10c0/79368f0bb9a0b10cc38b5eb0af64b7a69c815f3e5fed853af9f80b56b8f09b1578d0fb47d8a0cf13e8030c6dedb81a0f507d1998cd064d2ade3f517c82fbdcde languageName: node linkType: hard -"conventional-changelog-conventionalcommits@npm:^9.2.0": - version: 9.3.0 - resolution: "conventional-changelog-conventionalcommits@npm:9.3.0" +"conventional-changelog-conventionalcommits@npm:^10.0.0": + version: 10.4.0 + resolution: "conventional-changelog-conventionalcommits@npm:10.4.0" dependencies: - compare-func: "npm:^2.0.0" - checksum: 10c0/36be9435bb1f6e97bc729a1e69471851b6621054980617dc9a82c03221de5f9c21cd2369308c364f89b2383bd596071f90c8c71efdbe7a2908f91a935685fc76 + "@conventional-changelog/template": "npm:^1.4.0" + checksum: 10c0/60d99d74e6fdba134fc4785c13f87df82b9cabd980b4390dc958a8abf75d6fe1fdad921f52fb75e02f01a376cfd194b9f506f1e7718041e522b5260b53273882 languageName: node linkType: hard -"conventional-commits-parser@npm:^6.3.0": - version: 6.3.0 - resolution: "conventional-commits-parser@npm:6.3.0" +"conventional-commits-parser@npm:^7.0.0": + version: 7.1.2 + resolution: "conventional-commits-parser@npm:7.1.2" dependencies: - "@simple-libs/stream-utils": "npm:^1.2.0" - meow: "npm:^13.0.0" + "@simple-libs/stream-utils": "npm:^2.0.0" + argue-cli: "npm:^3.1.0" bin: - conventional-commits-parser: dist/cli/index.js - checksum: 10c0/7b152db0b63617fb5f993c3422942c05f48ff42fef4350d7e73b1d8a9f24489050b126478f2aabee5e45f205dbd02cb0b486e4bb865f9c0b18c35b4d13952b25 + conventional-commits-parser: ./dist/cli/index.js + checksum: 10c0/dd1478a5ef4fddd52cdd67fa4a97d1f47908a20d22287ccfa3f5e09b8298a3bcf3eb49ed117744805cc07ca31edf8835ce0913e96b1bcf7c385cf9bcefb3cf54 + languageName: node + linkType: hard + +"convert-source-map@npm:^2.0.0": + version: 2.0.0 + resolution: "convert-source-map@npm:2.0.0" + checksum: 10c0/8f2f7a27a1a011cc6cc88cc4da2d7d0cfa5ee0369508baae3d98c260bb3ac520691464e5bbe4ae7cdf09860c1d69ecc6f70c63c6e7c7f7e3f18ec08484dc7d9b languageName: node linkType: hard @@ -1927,13 +1553,6 @@ __metadata: languageName: node linkType: hard -"create-require@npm:^1.1.0": - version: 1.1.1 - resolution: "create-require@npm:1.1.1" - checksum: 10c0/157cbc59b2430ae9a90034a5f3a1b398b6738bf510f713edc4d4e45e169bc514d3d99dd34d8d01ca7ae7830b5b8b537e46ae8f3c8f932371b0875c0151d7ec91 - languageName: node - linkType: hard - "cross-spawn@npm:^7.0.6": version: 7.0.6 resolution: "cross-spawn@npm:7.0.6" @@ -1945,20 +1564,6 @@ __metadata: languageName: node linkType: hard -"dargs@npm:^8.0.0": - version: 8.1.0 - resolution: "dargs@npm:8.1.0" - checksum: 10c0/08cbd1ee4ac1a16fb7700e761af2e3e22d1bdc04ac4f851926f552dde8f9e57714c0d04013c2cca1cda0cba8fb637e0f93ad15d5285547a939dd1989ee06a82d - languageName: node - linkType: hard - -"de-indent@npm:^1.0.2": - version: 1.0.2 - resolution: "de-indent@npm:1.0.2" - checksum: 10c0/7058ce58abd6dfc123dd204e36be3797abd419b59482a634605420f47ae97639d0c183ec5d1b904f308a01033f473673897afc2bd59bc620ebf1658763ef4291 - languageName: node - linkType: hard - "debug@npm:4, debug@npm:^4.4.3": version: 4.4.3 resolution: "debug@npm:4.4.3" @@ -2002,26 +1607,31 @@ __metadata: languageName: node linkType: hard -"diff@npm:^4.0.1": - version: 4.0.2 - resolution: "diff@npm:4.0.2" - checksum: 10c0/81b91f9d39c4eaca068eb0c1eb0e4afbdc5bb2941d197f513dd596b820b956fef43485876226d65d497bebc15666aa2aa82c679e84f65d5f2bfbf14ee46e32c1 +"detect-indent@npm:^7.0.1": + version: 7.0.2 + resolution: "detect-indent@npm:7.0.2" + checksum: 10c0/adb1334ca3fe516dc6817aff0a777540b88643ab92fe13a72d0f5d12721ca796ffdd0e5fedb7b45e6e82657156c6ad44f5d5758157f0439532ae7d07b595146b languageName: node linkType: hard -"diff@npm:~8.0.2": - version: 8.0.3 - resolution: "diff@npm:8.0.3" - checksum: 10c0/d29321c70d3545fdcb56c5fdd76028c3f04c012462779e062303d4c3c531af80d2c360c26b871e6e2b9a971d2422d47e1779a859106c4cac4b5d2d143df70e20 +"detect-libc@npm:^2.0.3": + version: 2.1.2 + resolution: "detect-libc@npm:2.1.2" + checksum: 10c0/acc675c29a5649fa1fb6e255f993b8ee829e510b6b56b0910666949c80c364738833417d0edb5f90e4e46be17228b0f2b66a010513984e18b15deeeac49369c4 languageName: node linkType: hard -"dot-prop@npm:^5.1.0": - version: 5.3.0 - resolution: "dot-prop@npm:5.3.0" - dependencies: - is-obj: "npm:^2.0.0" - checksum: 10c0/93f0d343ef87fe8869320e62f2459f7e70f49c6098d948cc47e060f4a3f827d0ad61e83cb82f2bd90cd5b9571b8d334289978a43c0f98fea4f0e99ee8faa0599 +"detect-newline@npm:^4.0.1": + version: 4.0.1 + resolution: "detect-newline@npm:4.0.1" + checksum: 10c0/1cc1082e88ad477f30703ae9f23bd3e33816ea2db6a35333057e087d72d466f5a777809b71f560118ecff935d2c712f5b59e1008a8b56a900909d8fd4621c603 + languageName: node + linkType: hard + +"emoji-regex@npm:^10.3.0": + version: 10.6.0 + resolution: "emoji-regex@npm:10.6.0" + checksum: 10c0/1e4aa097bb007301c3b4b1913879ae27327fdc48e93eeefefe3b87e495eb33c5af155300be951b4349ff6ac084f4403dc9eff970acba7c1c572d89396a9a32d7 languageName: node linkType: hard @@ -2032,13 +1642,6 @@ __metadata: languageName: node linkType: hard -"entities@npm:^4.5.0": - version: 4.5.0 - resolution: "entities@npm:4.5.0" - checksum: 10c0/5b039739f7621f5d1ad996715e53d964035f75ad3b9a4d38c6b3804bb226e282ffeae2443624d8fdd9c47d8e926ae9ac009c54671243f0c3294c26af7cc85250 - languageName: node - linkType: hard - "env-paths@npm:^2.2.0, env-paths@npm:^2.2.1": version: 2.2.1 resolution: "env-paths@npm:2.2.1" @@ -2055,99 +1658,24 @@ __metadata: languageName: node linkType: hard -"es-module-lexer@npm:^1.7.0": - version: 1.7.0 - resolution: "es-module-lexer@npm:1.7.0" - checksum: 10c0/4c935affcbfeba7fb4533e1da10fa8568043df1e3574b869385980de9e2d475ddc36769891936dbb07036edb3c3786a8b78ccf44964cd130dedc1f2c984b6c7b - languageName: node - linkType: hard - -"esbuild@npm:^0.27.0": - version: 0.27.3 - resolution: "esbuild@npm:0.27.3" - dependencies: - "@esbuild/aix-ppc64": "npm:0.27.3" - "@esbuild/android-arm": "npm:0.27.3" - "@esbuild/android-arm64": "npm:0.27.3" - "@esbuild/android-x64": "npm:0.27.3" - "@esbuild/darwin-arm64": "npm:0.27.3" - "@esbuild/darwin-x64": "npm:0.27.3" - "@esbuild/freebsd-arm64": "npm:0.27.3" - "@esbuild/freebsd-x64": "npm:0.27.3" - "@esbuild/linux-arm": "npm:0.27.3" - "@esbuild/linux-arm64": "npm:0.27.3" - "@esbuild/linux-ia32": "npm:0.27.3" - "@esbuild/linux-loong64": "npm:0.27.3" - "@esbuild/linux-mips64el": "npm:0.27.3" - "@esbuild/linux-ppc64": "npm:0.27.3" - "@esbuild/linux-riscv64": "npm:0.27.3" - "@esbuild/linux-s390x": "npm:0.27.3" - "@esbuild/linux-x64": "npm:0.27.3" - "@esbuild/netbsd-arm64": "npm:0.27.3" - "@esbuild/netbsd-x64": "npm:0.27.3" - "@esbuild/openbsd-arm64": "npm:0.27.3" - "@esbuild/openbsd-x64": "npm:0.27.3" - "@esbuild/openharmony-arm64": "npm:0.27.3" - "@esbuild/sunos-x64": "npm:0.27.3" - "@esbuild/win32-arm64": "npm:0.27.3" - "@esbuild/win32-ia32": "npm:0.27.3" - "@esbuild/win32-x64": "npm:0.27.3" +"es-module-lexer@npm:^2.3.2": + version: 2.3.2 + resolution: "es-module-lexer@npm:2.3.2" + checksum: 10c0/5e7389424c43478439f12f9a6aca1750f6f99afa384fc3de329f4a45f152ab156671055008adaafc74960877abf8cc338aeebf6bed8c21297b146fd6eb7a22f8 + languageName: node + linkType: hard + +"es-toolkit@npm:^1.46.0": + version: 1.52.0 + resolution: "es-toolkit@npm:1.52.0" dependenciesMeta: - "@esbuild/aix-ppc64": - optional: true - "@esbuild/android-arm": - optional: true - "@esbuild/android-arm64": - optional: true - "@esbuild/android-x64": - optional: true - "@esbuild/darwin-arm64": - optional: true - "@esbuild/darwin-x64": - optional: true - "@esbuild/freebsd-arm64": - optional: true - "@esbuild/freebsd-x64": - optional: true - "@esbuild/linux-arm": - optional: true - "@esbuild/linux-arm64": - optional: true - "@esbuild/linux-ia32": - optional: true - "@esbuild/linux-loong64": - optional: true - "@esbuild/linux-mips64el": - optional: true - "@esbuild/linux-ppc64": - optional: true - "@esbuild/linux-riscv64": - optional: true - "@esbuild/linux-s390x": - optional: true - "@esbuild/linux-x64": - optional: true - "@esbuild/netbsd-arm64": - optional: true - "@esbuild/netbsd-x64": - optional: true - "@esbuild/openbsd-arm64": - optional: true - "@esbuild/openbsd-x64": - optional: true - "@esbuild/openharmony-arm64": - optional: true - "@esbuild/sunos-x64": - optional: true - "@esbuild/win32-arm64": - optional: true - "@esbuild/win32-ia32": - optional: true - "@esbuild/win32-x64": - optional: true - bin: - esbuild: bin/esbuild - checksum: 10c0/fdc3f87a3f08b3ef98362f37377136c389a0d180fda4b8d073b26ba930cf245521db0a368f119cc7624bc619248fff1439f5811f062d853576f8ffa3df8ee5f1 + "@trivago/prettier-plugin-sort-imports@4.3.0": + unplugged: true + prettier-plugin-sort-re-exports@0.0.1: + unplugged: true + vitepress-plugin-sandpack@1.1.4: + unplugged: true + checksum: 10c0/465988c0a6b84b6d1d171287cbbe40c3247a1f94acea210ae14bc66b7d90c93e6839bd6937cbaf2a1d206669f3179eb26141a89d9125969a4b8e58cb7d86a2ca languageName: node linkType: hard @@ -2205,16 +1733,16 @@ __metadata: languageName: node linkType: hard -"eslint@npm:^10.0.3": - version: 10.0.3 - resolution: "eslint@npm:10.0.3" +"eslint@npm:^10.11.0": + version: 10.11.0 + resolution: "eslint@npm:10.11.0" dependencies: "@eslint-community/eslint-utils": "npm:^4.8.0" "@eslint-community/regexpp": "npm:^4.12.2" - "@eslint/config-array": "npm:^0.23.3" - "@eslint/config-helpers": "npm:^0.5.2" - "@eslint/core": "npm:^1.1.1" - "@eslint/plugin-kit": "npm:^0.6.1" + "@eslint/config-array": "npm:^0.23.5" + "@eslint/config-helpers": "npm:^0.7.0" + "@eslint/core": "npm:^1.2.1" + "@eslint/plugin-kit": "npm:^0.7.3" "@humanfs/node": "npm:^0.16.6" "@humanwhocodes/module-importer": "npm:^1.0.1" "@humanwhocodes/retry": "npm:^0.4.2" @@ -2225,18 +1753,18 @@ __metadata: escape-string-regexp: "npm:^4.0.0" eslint-scope: "npm:^9.1.2" eslint-visitor-keys: "npm:^5.0.1" - espree: "npm:^11.1.1" + espree: "npm:^11.2.0" esquery: "npm:^1.7.0" esutils: "npm:^2.0.2" fast-deep-equal: "npm:^3.1.3" - file-entry-cache: "npm:^8.0.0" + file-entry-cache: "npm:11.1.5 || >11.1.6 <12" find-up: "npm:^5.0.0" glob-parent: "npm:^6.0.2" ignore: "npm:^5.2.0" imurmurhash: "npm:^0.1.4" is-glob: "npm:^4.0.0" json-stable-stringify-without-jsonify: "npm:^1.0.1" - minimatch: "npm:^10.2.4" + minimatch: "npm:^10.2.5" natural-compare: "npm:^1.4.0" optionator: "npm:^0.9.3" peerDependencies: @@ -2246,11 +1774,11 @@ __metadata: optional: true bin: eslint: bin/eslint.js - checksum: 10c0/fbbb4d99cb6af5c30b163b7898241dbac1cd1cee0e6746d5732a95e3b1e68b5bea0bc27cb78e8440a39cf4cc98c7f52cf5ed8d7c2bbdf2232662476d113c41fc + checksum: 10c0/c767f45f21d1921b41d70e30b1c8fa7ec4e65c0f43a8d80cc4990ca3d923127fa7b9b3416b8f5be6e877d9286f97d3c51a04ed341bc6aa73c0dba0f463e1f861 languageName: node linkType: hard -"espree@npm:^11.1.1": +"espree@npm:^11.2.0": version: 11.2.0 resolution: "espree@npm:11.2.0" dependencies: @@ -2309,10 +1837,10 @@ __metadata: languageName: node linkType: hard -"expect-type@npm:^1.2.2": - version: 1.3.0 - resolution: "expect-type@npm:1.3.0" - checksum: 10c0/8412b3fe4f392c420ab41dae220b09700e4e47c639a29ba7ba2e83cc6cffd2b4926f7ac9e47d7e277e8f4f02acda76fd6931cb81fd2b382fa9477ef9ada953fd +"expect-type@npm:^1.4.0": + version: 1.4.0 + resolution: "expect-type@npm:1.4.0" + checksum: 10c0/d40d76b8570695d36587beb3cc28494da2ca3ec8f04e67f5622ed2d372d850e401a9adef19c6835e1a8173903f157c79540b34c7b3fbd7cd8ce726cc903c57b7 languageName: node linkType: hard @@ -2351,13 +1879,6 @@ __metadata: languageName: node linkType: hard -"fast-uri@npm:^3.0.1": - version: 3.0.3 - resolution: "fast-uri@npm:3.0.3" - checksum: 10c0/4b2c5ce681a062425eae4f15cdc8fc151fd310b2f69b1f96680677820a8b49c3cd6e80661a406e19d50f0c40a3f8bffdd458791baf66f4a879d80be28e10a320 - languageName: node - linkType: hard - "fdir@npm:^6.5.0": version: 6.5.0 resolution: "fdir@npm:6.5.0" @@ -2370,19 +1891,28 @@ __metadata: languageName: node linkType: hard -"fflate@npm:^0.8.2": - version: 0.8.2 - resolution: "fflate@npm:0.8.2" - checksum: 10c0/03448d630c0a583abea594835a9fdb2aaf7d67787055a761515bf4ed862913cfd693b4c4ffd5c3f3b355a70cf1e19033e9ae5aedcca103188aaff91b8bd6e293 +"fflate@npm:^0.8.3": + version: 0.8.3 + resolution: "fflate@npm:0.8.3" + checksum: 10c0/eab181ca37f5348ae76d4b6f840e0026e30220e33153289ac942222d8b9638237d486507dbcc09878d724095bd354993a2ee48bbee99c8f2c6440d4448719aa7 languageName: node linkType: hard -"file-entry-cache@npm:^8.0.0": - version: 8.0.0 - resolution: "file-entry-cache@npm:8.0.0" +"figures@npm:^6.1.0": + version: 6.1.0 + resolution: "figures@npm:6.1.0" + dependencies: + is-unicode-supported: "npm:^2.0.0" + checksum: 10c0/9159df4264d62ef447a3931537de92f5012210cf5135c35c010df50a2169377581378149abfe1eb238bd6acbba1c0d547b1f18e0af6eee49e30363cedaffcfe4 + languageName: node + linkType: hard + +"file-entry-cache@npm:11.1.5 || >11.1.6 <12": + version: 11.1.5 + resolution: "file-entry-cache@npm:11.1.5" dependencies: - flat-cache: "npm:^4.0.0" - checksum: 10c0/9e2b5938b1cd9b6d7e3612bdc533afd4ac17b2fc646569e9a8abbf2eb48e5eb8e316bc38815a3ef6a1b456f4107f0d0f055a614ca613e75db6bf9ff4d72c1638 + flat-cache: "npm:^6.1.23" + checksum: 10c0/d05eca8794b27824d79a0f1a0bc76b81ca7232b6c9634bc086f365a6d9ddac749c4826b2b2d36bb7ef9ed84fdf0097f34979527daeb9255b4f17673a4ae50fc5 languageName: node linkType: hard @@ -2396,38 +1926,31 @@ __metadata: languageName: node linkType: hard -"flat-cache@npm:^4.0.0": - version: 4.0.1 - resolution: "flat-cache@npm:4.0.1" +"flat-cache@npm:^6.1.23": + version: 6.1.23 + resolution: "flat-cache@npm:6.1.23" dependencies: - flatted: "npm:^3.2.9" - keyv: "npm:^4.5.4" - checksum: 10c0/2c59d93e9faa2523e4fda6b4ada749bed432cfa28c8e251f33b25795e426a1c6dbada777afb1f74fcfff33934fdbdea921ee738fcc33e71adc9d6eca984a1cfc + cacheable: "npm:^2.5.0" + flatted: "npm:^3.4.2" + hookified: "npm:^1.15.0" + checksum: 10c0/c757c24f51334e1917958cd9d62d4ed3d520a3815d5a1ec50d993ac3a93fa81b6de7e28264231914bb0d990babd426e4d713d3e2f3d782dae65c42a2ded1e651 languageName: node linkType: hard -"flatted@npm:^3.2.9": - version: 3.2.9 - resolution: "flatted@npm:3.2.9" - checksum: 10c0/5c91c5a0a21bbc0b07b272231e5b4efe6b822bcb4ad317caf6bb06984be4042a9e9045026307da0fdb4583f1f545e317a67ef1231a59e71f7fced3cc429cfc53 +"flatted@npm:^3.4.2, flatted@npm:^3.4.4": + version: 3.4.4 + resolution: "flatted@npm:3.4.4" + checksum: 10c0/a3a52a88ea5a4c333e5a1f097dcd87a037fa31c236a77cf46222c2aa4036ef895ab9bc76138a66d9dc22bdb3652128f9598cd26e7439561bf19f67276e2bc37e languageName: node linkType: hard -"flatted@npm:^3.3.3": - version: 3.3.4 - resolution: "flatted@npm:3.3.4" - checksum: 10c0/d1f33426e9714063a65a90940acdb2897eceb810230a58e496d90334dcecfa81a90135bbc660df6827939865d57cb4a2afab14dcd3d505e16a8484fd73bf9642 - languageName: node - linkType: hard - -"fs-extra@npm:~11.3.0": - version: 11.3.4 - resolution: "fs-extra@npm:11.3.4" +"foreground-child@npm:^3.3.1": + version: 3.3.1 + resolution: "foreground-child@npm:3.3.1" dependencies: - graceful-fs: "npm:^4.2.0" - jsonfile: "npm:^6.0.1" - universalify: "npm:^2.0.0" - checksum: 10c0/e08276f767a62496ae97d711aaa692c6a478177f24a85979b6a2881c9db9c68b8c2ad5da0bcf92c0b2a474cea6e935ec245656441527958fd8372cb647087df0 + cross-spawn: "npm:^7.0.6" + signal-exit: "npm:^4.0.1" + checksum: 10c0/8986e4af2430896e65bc2788d6679067294d6aee9545daefc84923a0a4b399ad9c7a3ea7bd8c0b2b80fdf4a92de4c69df3f628233ff3224260e9c1541a9e9ed3 languageName: node linkType: hard @@ -2440,7 +1963,7 @@ __metadata: languageName: node linkType: hard -"fsevents@npm:~2.3.2, fsevents@npm:~2.3.3": +"fsevents@npm:~2.3.3": version: 2.3.3 resolution: "fsevents@npm:2.3.3" dependencies: @@ -2450,7 +1973,7 @@ __metadata: languageName: node linkType: hard -"fsevents@patch:fsevents@npm%3A~2.3.2#optional!builtin, fsevents@patch:fsevents@npm%3A~2.3.3#optional!builtin": +"fsevents@patch:fsevents@npm%3A~2.3.3#optional!builtin": version: 2.3.3 resolution: "fsevents@patch:fsevents@npm%3A2.3.3#optional!builtin::version=2.3.3&hash=df0bf1" dependencies: @@ -2459,13 +1982,6 @@ __metadata: languageName: node linkType: hard -"function-bind@npm:^1.1.2": - version: 1.1.2 - resolution: "function-bind@npm:1.1.2" - checksum: 10c0/d8680ee1e5fcd4c197e4ac33b2b4dce03c71f4d91717292785703db200f5c21f977c568d28061226f9b5900cbcd2c84463646134fd5337e7925e0942bc3f46d5 - languageName: node - linkType: hard - "get-caller-file@npm:^2.0.5": version: 2.0.5 resolution: "get-caller-file@npm:2.0.5" @@ -2473,16 +1989,10 @@ __metadata: languageName: node linkType: hard -"git-raw-commits@npm:^4.0.0": - version: 4.0.0 - resolution: "git-raw-commits@npm:4.0.0" - dependencies: - dargs: "npm:^8.0.0" - meow: "npm:^12.0.1" - split2: "npm:^4.0.0" - bin: - git-raw-commits: cli.mjs - checksum: 10c0/ab51335d9e55692fce8e42788013dba7a7e7bf9f5bf0622c8cd7ddc9206489e66bb939563fca4edb3aa87477e2118f052702aad1933b13c6fa738af7f29884f0 +"get-east-asian-width@npm:^1.0.0, get-east-asian-width@npm:^1.5.0": + version: 1.7.0 + resolution: "get-east-asian-width@npm:1.7.0" + checksum: 10c0/680a5f611a186e4a065291f31821f7e4ad7742625be814e05b8ab5535c6fa53562bc0333a83138fca5f2e1030ed960a561b2fd4629e0cff0dc68b19cf46d7c53 languageName: node linkType: hard @@ -2495,6 +2005,29 @@ __metadata: languageName: node linkType: hard +"glob-to-regexp@npm:0.4.1": + version: 0.4.1 + resolution: "glob-to-regexp@npm:0.4.1" + checksum: 10c0/0486925072d7a916f052842772b61c3e86247f0a80cc0deb9b5a3e8a1a9faad5b04fb6f58986a09f34d3e96cd2a22a24b7e9882fb1cf904c31e9a310de96c429 + languageName: node + linkType: hard + +"glob@npm:^11.0.0": + version: 11.1.0 + resolution: "glob@npm:11.1.0" + dependencies: + foreground-child: "npm:^3.3.1" + jackspeak: "npm:^4.1.1" + minimatch: "npm:^10.1.1" + minipass: "npm:^7.1.2" + package-json-from-dist: "npm:^1.0.0" + path-scurry: "npm:^2.0.0" + bin: + glob: dist/esm/bin.mjs + checksum: 10c0/1ceae07f23e316a6fa74581d9a74be6e8c2e590d2f7205034dd5c0435c53f5f7b712c2be00c3b65bf0a49294a1c6f4b98cd84c7637e29453b5aa13b79f1763a2 + languageName: node + linkType: hard + "glob@npm:^13.0.0": version: 13.0.6 resolution: "glob@npm:13.0.6" @@ -2506,23 +2039,23 @@ __metadata: languageName: node linkType: hard -"global-directory@npm:^4.0.1": - version: 4.0.1 - resolution: "global-directory@npm:4.0.1" +"global-directory@npm:^5.0.0": + version: 5.0.0 + resolution: "global-directory@npm:5.0.0" dependencies: - ini: "npm:4.1.1" - checksum: 10c0/f9cbeef41db4876f94dd0bac1c1b4282a7de9c16350ecaaf83e7b2dd777b32704cc25beeb1170b5a63c42a2c9abfade74d46357fe0133e933218bc89e613d4b2 + ini: "npm:6.0.0" + checksum: 10c0/2b90eea8975bb332db7e8c9096ff310f0deb617d17e47e93b921d1c69f7677c1759a07009f2581f050d3ea08a3e079bf4ffdb9c34c94d48dc369c6577b3d45f4 languageName: node linkType: hard -"globals@npm:^17.4.0": - version: 17.4.0 - resolution: "globals@npm:17.4.0" - checksum: 10c0/2be9e8c2b9035836f13d420b22f0247a328db82967d3bebfc01126d888ed609305f06c05895914e969653af5c6ba35fd7a0920f3e6c869afa60666c810630feb +"globals@npm:^17.12.0": + version: 17.12.0 + resolution: "globals@npm:17.12.0" + checksum: 10c0/8325cf8818c848871d17c32974c2c74e9c5f09c1b0e04421886f64f21e77c3e95f3ba25586743895a73acd860ada151132481d87d44a1f12993ae7758a694d03 languageName: node linkType: hard -"graceful-fs@npm:^4.1.6, graceful-fs@npm:^4.2.0, graceful-fs@npm:^4.2.6": +"graceful-fs@npm:^4.2.6": version: 4.2.11 resolution: "graceful-fs@npm:4.2.11" checksum: 10c0/386d011a553e02bc594ac2ca0bd6d9e4c22d7fa8cfbfc448a6d148c59ea881b092db9dbe3547ae4b88e55f1b01f7c4a2ecc53b310c042793e63aa44cf6c257f2 @@ -2536,35 +2069,26 @@ __metadata: languageName: node linkType: hard -"has-flag@npm:^4.0.0": - version: 4.0.0 - resolution: "has-flag@npm:4.0.0" - checksum: 10c0/2e789c61b7888d66993e14e8331449e525ef42aac53c627cc53d1c3334e768bcb6abdc4f5f0de1478a25beec6f0bd62c7549058b7ac53e924040d4f301f02fd1 - languageName: node - linkType: hard - -"hasown@npm:^2.0.0": - version: 2.0.0 - resolution: "hasown@npm:2.0.0" +"hashery@npm:^1.4.0, hashery@npm:^1.5.1": + version: 1.5.1 + resolution: "hashery@npm:1.5.1" dependencies: - function-bind: "npm:^1.1.2" - checksum: 10c0/5d415b114f410661208c95e7ab4879f1cc2765b8daceff4dc8718317d1cb7b9ffa7c5d1eafd9a4389c9aab7445d6ea88e05f3096cb1e529618b55304956b87fc + hookified: "npm:^1.15.0" + checksum: 10c0/ab4225b655a7b0d05df99b1a59d5b3a51fe433f82422ca25e6f3f4c4ddd30adb49ebd38e0047ef9bded93319c1e9fc857e16aa382e554929c871cb77d39fc463 languageName: node linkType: hard -"he@npm:^1.2.0": - version: 1.2.0 - resolution: "he@npm:1.2.0" - bin: - he: bin/he - checksum: 10c0/a27d478befe3c8192f006cdd0639a66798979dfa6e2125c6ac582a19a5ebfec62ad83e8382e6036170d873f46e4536a7e795bf8b95bf7c247f4cc0825ccc8c17 +"hookified@npm:^1.15.0, hookified@npm:^1.15.1": + version: 1.15.1 + resolution: "hookified@npm:1.15.1" + checksum: 10c0/6b691374fa97ae57169fb29f90e723499fda5e85494654fbe55c4768b3ccbf3e14c0adc8d0f365f32c503b60d7c06f907781f5966c03d41c423575eb5e16860c languageName: node linkType: hard -"html-escaper@npm:^2.0.0": - version: 2.0.2 - resolution: "html-escaper@npm:2.0.2" - checksum: 10c0/208e8a12de1a6569edbb14544f4567e6ce8ecc30b9394fcaa4e7bb1e60c12a7c9a1ed27e31290817157e8626f3a4f29e76c8747030822eb84a6abb15c255f0a0 +"hookified@npm:^2.1.1": + version: 2.2.0 + resolution: "hookified@npm:2.2.0" + checksum: 10c0/7017d2b66945490293a5aba239e7b39f39071dd940fa019348c7ffea92b91b8c267853c4c51680ed0f3687b33352582fa4d2cff6dd4c5a1c5c44b037276f07aa languageName: node linkType: hard @@ -2637,20 +2161,6 @@ __metadata: languageName: node linkType: hard -"import-lazy@npm:~4.0.0": - version: 4.0.0 - resolution: "import-lazy@npm:4.0.0" - checksum: 10c0/a3520313e2c31f25c0b06aa66d167f329832b68a4f957d7c9daf6e0fa41822b6e84948191648b9b9d8ca82f94740cdf15eecf2401a5b42cd1c33fd84f2225cca - languageName: node - linkType: hard - -"import-meta-resolve@npm:^4.0.0": - version: 4.2.0 - resolution: "import-meta-resolve@npm:4.2.0" - checksum: 10c0/3ee8aeecb61d19b49d2703987f977e9d1c7d4ba47db615a570eaa02fe414f40dfa63f7b953e842cbe8470d26df6371332bfcf21b2fd92b0112f9fea80dde2c4c - languageName: node - linkType: hard - "imurmurhash@npm:^0.1.4": version: 0.1.4 resolution: "imurmurhash@npm:0.1.4" @@ -2658,10 +2168,10 @@ __metadata: languageName: node linkType: hard -"ini@npm:4.1.1": - version: 4.1.1 - resolution: "ini@npm:4.1.1" - checksum: 10c0/7fddc8dfd3e63567d4fdd5d999d1bf8a8487f1479d0b34a1d01f28d391a9228d261e19abc38e1a6a1ceb3400c727204fce05725d5eb598dfcf2077a1e3afe211 +"ini@npm:6.0.0": + version: 6.0.0 + resolution: "ini@npm:6.0.0" + checksum: 10c0/9a7f55f306e2b25b41ae67c8b526e8f4673f057b70852b9025816ef4f15f07bf1ba35ed68ea4471ff7b31718f7ef1bc50d709f8d03cb012e10a3135eb99c7206 languageName: node linkType: hard @@ -2679,15 +2189,6 @@ __metadata: languageName: node linkType: hard -"is-core-module@npm:^2.13.0": - version: 2.13.1 - resolution: "is-core-module@npm:2.13.1" - dependencies: - hasown: "npm:^2.0.0" - checksum: 10c0/2cba9903aaa52718f11c4896dabc189bab980870aae86a62dc0d5cedb546896770ee946fb14c84b7adf0735f5eaea4277243f1b95f5cefa90054f92fbcac2518 - languageName: node - linkType: hard - "is-extglob@npm:^2.1.1": version: 2.1.1 resolution: "is-extglob@npm:2.1.1" @@ -2711,13 +2212,6 @@ __metadata: languageName: node linkType: hard -"is-obj@npm:^2.0.0": - version: 2.0.0 - resolution: "is-obj@npm:2.0.0" - checksum: 10c0/85044ed7ba8bd169e2c2af3a178cacb92a97aa75de9569d02efef7f443a824b5e153eba72b9ae3aca6f8ce81955271aa2dc7da67a8b720575d3e38104208cb4e - languageName: node - linkType: hard - "is-plain-obj@npm:^4.1.0": version: 4.1.0 resolution: "is-plain-obj@npm:4.1.0" @@ -2725,6 +2219,13 @@ __metadata: languageName: node linkType: hard +"is-unicode-supported@npm:^2.0.0": + version: 2.1.0 + resolution: "is-unicode-supported@npm:2.1.0" + checksum: 10c0/a0f53e9a7c1fdbcf2d2ef6e40d4736fdffff1c9f8944c75e15425118ff3610172c87bf7bc6c34d3903b04be59790bb2212ddbe21ee65b5a97030fc50370545a5 + languageName: node + linkType: hard + "isexe@npm:^2.0.0": version: 2.0.0 resolution: "isexe@npm:2.0.0" @@ -2739,31 +2240,12 @@ __metadata: languageName: node linkType: hard -"istanbul-lib-coverage@npm:^3.0.0, istanbul-lib-coverage@npm:^3.2.2": - version: 3.2.2 - resolution: "istanbul-lib-coverage@npm:3.2.2" - checksum: 10c0/6c7ff2106769e5f592ded1fb418f9f73b4411fd5a084387a5410538332b6567cd1763ff6b6cadca9b9eb2c443cce2f7ea7d7f1b8d315f9ce58539793b1e0922b - languageName: node - linkType: hard - -"istanbul-lib-report@npm:^3.0.0, istanbul-lib-report@npm:^3.0.1": - version: 3.0.1 - resolution: "istanbul-lib-report@npm:3.0.1" - dependencies: - istanbul-lib-coverage: "npm:^3.0.0" - make-dir: "npm:^4.0.0" - supports-color: "npm:^7.1.0" - checksum: 10c0/84323afb14392de8b6a5714bd7e9af845cfbd56cfe71ed276cda2f5f1201aea673c7111901227ee33e68e4364e288d73861eb2ed48f6679d1e69a43b6d9b3ba7 - languageName: node - linkType: hard - -"istanbul-reports@npm:^3.2.0": - version: 3.2.0 - resolution: "istanbul-reports@npm:3.2.0" +"jackspeak@npm:^4.1.1": + version: 4.2.3 + resolution: "jackspeak@npm:4.2.3" dependencies: - html-escaper: "npm:^2.0.0" - istanbul-lib-report: "npm:^3.0.0" - checksum: 10c0/d596317cfd9c22e1394f22a8d8ba0303d2074fe2e971887b32d870e4b33f8464b10f8ccbe6847808f7db485f084eba09e6c2ed706b3a978e4b52f07085b8f9bc + "@isaacs/cliui": "npm:^9.0.0" + checksum: 10c0/b5c0c414f1607c2aa0597f4bf2c03b8443897fccd5fd3c2b3e4f77d556b2bc7c3d3413828ba91e0789f6fb40ad90242f7f89fb20aee9e9d705bc1681f7564f67 languageName: node linkType: hard @@ -2776,13 +2258,6 @@ __metadata: languageName: node linkType: hard -"jju@npm:~1.4.0": - version: 1.4.0 - resolution: "jju@npm:1.4.0" - checksum: 10c0/f3f444557e4364cfc06b1abf8331bf3778b26c0c8552ca54429bc0092652172fdea26cbffe33e1017b303d5aa506f7ede8571857400efe459cb7439180e2acad - languageName: node - linkType: hard - "js-tokens@npm:^10.0.0": version: 10.0.0 resolution: "js-tokens@npm:10.0.0" @@ -2808,13 +2283,6 @@ __metadata: languageName: node linkType: hard -"json-buffer@npm:3.0.1": - version: 3.0.1 - resolution: "json-buffer@npm:3.0.1" - checksum: 10c0/0d1c91569d9588e7eef2b49b59851f297f3ab93c7b35c7c221e288099322be6b562767d11e4821da500f3219542b9afd2e54c5dc573107c1126ed1080f8e96d7 - languageName: node - linkType: hard - "json-parse-even-better-errors@npm:^2.3.0": version: 2.3.1 resolution: "json-parse-even-better-errors@npm:2.3.1" @@ -2843,25 +2311,12 @@ __metadata: languageName: node linkType: hard -"jsonfile@npm:^6.0.1": - version: 6.1.0 - resolution: "jsonfile@npm:6.1.0" - dependencies: - graceful-fs: "npm:^4.1.6" - universalify: "npm:^2.0.0" - dependenciesMeta: - graceful-fs: - optional: true - checksum: 10c0/4f95b5e8a5622b1e9e8f33c96b7ef3158122f595998114d1e7f03985649ea99cb3cd99ce1ed1831ae94c8c8543ab45ebd044207612f31a56fd08462140e46865 - languageName: node - linkType: hard - -"keyv@npm:^4.5.4": - version: 4.5.4 - resolution: "keyv@npm:4.5.4" +"keyv@npm:^5.6.0": + version: 5.6.0 + resolution: "keyv@npm:5.6.0" dependencies: - json-buffer: "npm:3.0.1" - checksum: 10c0/aa52f3c5e18e16bb6324876bb8b59dd02acf782a4b789c7b2ae21107fab95fab3890ed448d4f8dba80ce05391eeac4bfabb4f02a20221342982f806fa2cf271e + "@keyv/serialize": "npm:^1.1.1" + checksum: 10c0/c3ea795b6e03593ca57c8f70928a69bad14c13389a7fb75649a115ff55615244b04d8902798d841c17f0bb4a8a8866c97133b543b93f151b440170bba09176db languageName: node linkType: hard @@ -2882,6 +2337,126 @@ __metadata: languageName: node linkType: hard +"lightningcss-android-arm64@npm:1.33.0": + version: 1.33.0 + resolution: "lightningcss-android-arm64@npm:1.33.0" + conditions: os=android & cpu=arm64 + languageName: node + linkType: hard + +"lightningcss-darwin-arm64@npm:1.33.0": + version: 1.33.0 + resolution: "lightningcss-darwin-arm64@npm:1.33.0" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"lightningcss-darwin-x64@npm:1.33.0": + version: 1.33.0 + resolution: "lightningcss-darwin-x64@npm:1.33.0" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"lightningcss-freebsd-x64@npm:1.33.0": + version: 1.33.0 + resolution: "lightningcss-freebsd-x64@npm:1.33.0" + conditions: os=freebsd & cpu=x64 + languageName: node + linkType: hard + +"lightningcss-linux-arm-gnueabihf@npm:1.33.0": + version: 1.33.0 + resolution: "lightningcss-linux-arm-gnueabihf@npm:1.33.0" + conditions: os=linux & cpu=arm + languageName: node + linkType: hard + +"lightningcss-linux-arm64-gnu@npm:1.33.0": + version: 1.33.0 + resolution: "lightningcss-linux-arm64-gnu@npm:1.33.0" + conditions: os=linux & cpu=arm64 & libc=glibc + languageName: node + linkType: hard + +"lightningcss-linux-arm64-musl@npm:1.33.0": + version: 1.33.0 + resolution: "lightningcss-linux-arm64-musl@npm:1.33.0" + conditions: os=linux & cpu=arm64 & libc=musl + languageName: node + linkType: hard + +"lightningcss-linux-x64-gnu@npm:1.33.0": + version: 1.33.0 + resolution: "lightningcss-linux-x64-gnu@npm:1.33.0" + conditions: os=linux & cpu=x64 & libc=glibc + languageName: node + linkType: hard + +"lightningcss-linux-x64-musl@npm:1.33.0": + version: 1.33.0 + resolution: "lightningcss-linux-x64-musl@npm:1.33.0" + conditions: os=linux & cpu=x64 & libc=musl + languageName: node + linkType: hard + +"lightningcss-win32-arm64-msvc@npm:1.33.0": + version: 1.33.0 + resolution: "lightningcss-win32-arm64-msvc@npm:1.33.0" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + +"lightningcss-win32-x64-msvc@npm:1.33.0": + version: 1.33.0 + resolution: "lightningcss-win32-x64-msvc@npm:1.33.0" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + +"lightningcss@npm:^1.33.0": + version: 1.33.0 + resolution: "lightningcss@npm:1.33.0" + dependencies: + detect-libc: "npm:^2.0.3" + lightningcss-android-arm64: "npm:1.33.0" + lightningcss-darwin-arm64: "npm:1.33.0" + lightningcss-darwin-x64: "npm:1.33.0" + lightningcss-freebsd-x64: "npm:1.33.0" + lightningcss-linux-arm-gnueabihf: "npm:1.33.0" + lightningcss-linux-arm64-gnu: "npm:1.33.0" + lightningcss-linux-arm64-musl: "npm:1.33.0" + lightningcss-linux-x64-gnu: "npm:1.33.0" + lightningcss-linux-x64-musl: "npm:1.33.0" + lightningcss-win32-arm64-msvc: "npm:1.33.0" + lightningcss-win32-x64-msvc: "npm:1.33.0" + dependenciesMeta: + lightningcss-android-arm64: + optional: true + lightningcss-darwin-arm64: + optional: true + lightningcss-darwin-x64: + optional: true + lightningcss-freebsd-x64: + optional: true + lightningcss-linux-arm-gnueabihf: + optional: true + lightningcss-linux-arm64-gnu: + optional: true + lightningcss-linux-arm64-musl: + optional: true + lightningcss-linux-x64-gnu: + optional: true + lightningcss-linux-x64-musl: + optional: true + lightningcss-win32-arm64-msvc: + optional: true + lightningcss-win32-x64-msvc: + optional: true + checksum: 10c0/ce1f8279fbae636dbf37fa6e7385d5f98ed881d72af3362f24afbd4685e19c1fcdfecf17e5dd77f2ebee3d0c23ade276230d85842d07292229a2cffba8ff20a3 + languageName: node + linkType: hard + "lines-and-columns@npm:^1.1.6": version: 1.2.4 resolution: "lines-and-columns@npm:1.2.4" @@ -2889,14 +2464,14 @@ __metadata: languageName: node linkType: hard -"local-pkg@npm:^1.0.0": - version: 1.1.2 - resolution: "local-pkg@npm:1.1.2" +"local-pkg@npm:^1.1.1": + version: 1.2.1 + resolution: "local-pkg@npm:1.2.1" dependencies: mlly: "npm:^1.7.4" pkg-types: "npm:^2.3.0" quansync: "npm:^0.2.11" - checksum: 10c0/1bcfcc5528dea95cba3caa478126a348d3985aad9f69ecf7802c13efef90897e1c5ff7851974332c5e6d4a4698efe610fef758a068c8bc3feb5322aeb35d5993 + checksum: 10c0/d21ccfc2595b2eddf7369f19797533573feb06820d8b5766467902a7a54e36b3a1f2b8cc2aa4481a2ad432c8e798576eabfac2f1ec0646a8e11a68694c2ef9d4 languageName: node linkType: hard @@ -2909,55 +2484,6 @@ __metadata: languageName: node linkType: hard -"lodash.camelcase@npm:^4.3.0": - version: 4.3.0 - resolution: "lodash.camelcase@npm:4.3.0" - checksum: 10c0/fcba15d21a458076dd309fce6b1b4bf611d84a0ec252cb92447c948c533ac250b95d2e00955801ebc367e5af5ed288b996d75d37d2035260a937008e14eaf432 - languageName: node - linkType: hard - -"lodash.kebabcase@npm:^4.1.1": - version: 4.1.1 - resolution: "lodash.kebabcase@npm:4.1.1" - checksum: 10c0/da5d8f41dbb5bc723d4bf9203d5096ca8da804d6aec3d2b56457156ba6c8d999ff448d347ebd97490da853cb36696ea4da09a431499f1ee8deb17b094ecf4e33 - languageName: node - linkType: hard - -"lodash.mergewith@npm:^4.6.2": - version: 4.6.2 - resolution: "lodash.mergewith@npm:4.6.2" - checksum: 10c0/4adbed65ff96fd65b0b3861f6899f98304f90fd71e7f1eb36c1270e05d500ee7f5ec44c02ef979b5ddbf75c0a0b9b99c35f0ad58f4011934c4d4e99e5200b3b5 - languageName: node - linkType: hard - -"lodash.snakecase@npm:^4.1.1": - version: 4.1.1 - resolution: "lodash.snakecase@npm:4.1.1" - checksum: 10c0/f0b3f2497eb20eea1a1cfc22d645ecaeb78ac14593eb0a40057977606d2f35f7aaff0913a06553c783b535aafc55b718f523f9eb78f8d5293f492af41002eaf9 - languageName: node - linkType: hard - -"lodash.startcase@npm:^4.4.0": - version: 4.4.0 - resolution: "lodash.startcase@npm:4.4.0" - checksum: 10c0/bd82aa87a45de8080e1c5ee61128c7aee77bf7f1d86f4ff94f4a6d7438fc9e15e5f03374b947be577a93804c8ad6241f0251beaf1452bf716064eeb657b3a9f0 - languageName: node - linkType: hard - -"lodash.upperfirst@npm:^4.3.1": - version: 4.3.1 - resolution: "lodash.upperfirst@npm:4.3.1" - checksum: 10c0/435625da4b3ee74e7a1367a780d9107ab0b13ef4359fc074b2a1a40458eb8d91b655af62f6795b7138d493303a98c0285340160341561d6896e4947e077fa975 - languageName: node - linkType: hard - -"lodash@npm:~4.17.23": - version: 4.17.23 - resolution: "lodash@npm:4.17.23" - checksum: 10c0/1264a90469f5bb95d4739c43eb6277d15b6d9e186df4ac68c3620443160fc669e2f14c11e7d8b2ccf078b81d06147c01a8ccced9aab9f9f63d50dcf8cace6bf6 - languageName: node - linkType: hard - "lru-cache@npm:^11.0.0, lru-cache@npm:^11.1.0, lru-cache@npm:^11.2.1": version: 11.2.6 resolution: "lru-cache@npm:11.2.6" @@ -2965,15 +2491,6 @@ __metadata: languageName: node linkType: hard -"lru-cache@npm:^6.0.0": - version: 6.0.0 - resolution: "lru-cache@npm:6.0.0" - dependencies: - yallist: "npm:^4.0.0" - checksum: 10c0/cb53e582785c48187d7a188d3379c181b5ca2a9c78d2bce3e7dee36f32761d1c42983da3fe12b55cb74e1779fa94cdc2e5367c028a9b35317184ede0c07a30a9 - languageName: node - linkType: hard - "magic-string@npm:^0.30.17": version: 0.30.17 resolution: "magic-string@npm:0.30.17" @@ -2983,39 +2500,23 @@ __metadata: languageName: node linkType: hard -"magic-string@npm:^0.30.21": - version: 0.30.21 - resolution: "magic-string@npm:0.30.21" +"magic-string@npm:^1.2.3": + version: 1.4.2 + resolution: "magic-string@npm:1.4.2" dependencies: - "@jridgewell/sourcemap-codec": "npm:^1.5.5" - checksum: 10c0/299378e38f9a270069fc62358522ddfb44e94244baa0d6a8980ab2a9b2490a1d03b236b447eee309e17eb3bddfa482c61259d47960eb018a904f0ded52780c4a + "@jridgewell/sourcemap-codec": "npm:^1.6.0" + checksum: 10c0/d3e222c202f99ec4861e7316f473f9b1cfe335ebdf38d4f67335f842f3fe248f01ee00fd3e1a8206bcbe9855e0c41515d7c210300d8fcf6eefbb982d79e423c6 languageName: node linkType: hard -"magicast@npm:^0.5.1": - version: 0.5.2 - resolution: "magicast@npm:0.5.2" +"magicast@npm:^0.5.4": + version: 0.5.5 + resolution: "magicast@npm:0.5.5" dependencies: - "@babel/parser": "npm:^7.29.0" - "@babel/types": "npm:^7.29.0" + "@babel/parser": "npm:^7.29.7" + "@babel/types": "npm:^7.29.7" source-map-js: "npm:^1.2.1" - checksum: 10c0/924af677643c5a0a7d6cdb3247c0eb96fa7611b2ba6a5e720d35d81c503d3d9f5948eb5227f80f90f82ea3e7d38cffd10bb988f3fc09020db428e14f26e960d7 - languageName: node - linkType: hard - -"make-dir@npm:^4.0.0": - version: 4.0.0 - resolution: "make-dir@npm:4.0.0" - dependencies: - semver: "npm:^7.5.3" - checksum: 10c0/69b98a6c0b8e5c4fe9acb61608a9fbcfca1756d910f51e5dbe7a9e5cfb74fca9b8a0c8a0ffdf1294a740826c1ab4871d5bf3f62f72a3049e5eac6541ddffed68 - languageName: node - linkType: hard - -"make-error@npm:^1.1.1": - version: 1.3.6 - resolution: "make-error@npm:1.3.6" - checksum: 10c0/171e458d86854c6b3fc46610cfacf0b45149ba043782558c6875d9f42f222124384ad0b468c92e996d815a8a2003817a710c0a160e49c1c394626f76fa45396f + checksum: 10c0/d27e94cd1e85f1736f97ab0c6e542170a818c7e573261722d056b3ec4a520e16d836ce4c6b0cd3dd215b1171d83592d48a3736f3da5997a55629a0de05f66e7d languageName: node linkType: hard @@ -3038,21 +2539,16 @@ __metadata: languageName: node linkType: hard -"meow@npm:^12.0.1": - version: 12.1.1 - resolution: "meow@npm:12.1.1" - checksum: 10c0/a125ca99a32e2306e2f4cbe651a0d27f6eb67918d43a075f6e80b35e9bf372ebf0fc3a9fbc201cbbc9516444b6265fb3c9f80c5b7ebd32f548aa93eb7c28e088 - languageName: node - linkType: hard - -"meow@npm:^13.0.0": - version: 13.2.0 - resolution: "meow@npm:13.2.0" - checksum: 10c0/d5b339ae314715bcd0b619dd2f8a266891928e21526b4800d49b4fba1cc3fff7e2c1ff5edd3344149fac841bc2306157f858e8c4d5eaee4d52ce52ad925664ce +"minimatch@npm:^10.1.1, minimatch@npm:^10.2.5": + version: 10.2.6 + resolution: "minimatch@npm:10.2.6" + dependencies: + brace-expansion: "npm:^5.0.8" + checksum: 10c0/4559a836243b98bd4d17ea9f7edae698717c76399eea7be374f3737f33164e4907f19e9726891ddeb122f750a5a7fa80d2ac43e851d6e5984dc4ff42ec127d3a languageName: node linkType: hard -"minimatch@npm:10.2.4, minimatch@npm:^10.2.2, minimatch@npm:^10.2.4": +"minimatch@npm:^10.2.2, minimatch@npm:^10.2.4": version: 10.2.4 resolution: "minimatch@npm:10.2.4" dependencies: @@ -3061,22 +2557,6 @@ __metadata: languageName: node linkType: hard -"minimatch@npm:9.0.7": - version: 9.0.7 - resolution: "minimatch@npm:9.0.7" - dependencies: - brace-expansion: "npm:^5.0.2" - checksum: 10c0/e90f8d8dd1dd3e1257b29c8cb31f554e74e2b89d52391b3903b566a28a287b59e2077a25552bc90a31bbbb79113a59e7422c7783ed9f968fe21c2ccb3c67bdef - languageName: node - linkType: hard - -"minimist@npm:^1.2.8": - version: 1.2.8 - resolution: "minimist@npm:1.2.8" - checksum: 10c0/19d3fcdca050087b84c2029841a093691a91259a47def2f18222f41e7645a0b7c44ef4b40e88a1e58a40c84d2ef0ee6047c55594d298146d0eb3f6b737c20ce6 - languageName: node - linkType: hard - "minipass-collect@npm:^2.0.1": version: 2.0.1 resolution: "minipass-collect@npm:2.0.1" @@ -3186,19 +2666,12 @@ __metadata: languageName: node linkType: hard -"muggle-string@npm:^0.4.1": - version: 0.4.1 - resolution: "muggle-string@npm:0.4.1" - checksum: 10c0/e914b63e24cd23f97e18376ec47e4ba3aa24365e4776212b666add2e47bb158003212980d732c49abf3719568900af7861873844a6e2d3a7ca7e86952c0e99e9 - languageName: node - linkType: hard - -"nanoid@npm:^3.3.11": - version: 3.3.11 - resolution: "nanoid@npm:3.3.11" +"nanoid@npm:^3.3.18": + version: 3.3.19 + resolution: "nanoid@npm:3.3.19" bin: nanoid: bin/nanoid.cjs - checksum: 10c0/40e7f70b3d15f725ca072dfc4f74e81fcf1fbb02e491cf58ac0c79093adc9b0a73b152bcde57df4b79cd097e13023d7504acb38404a4da7bc1cd8e887b82fe0b + checksum: 10c0/ce7afa6258bfaa00bdc8de291cfe9913280eaa403b7d9cff3db4e4200c1913e7163577df74897958da144db473a96c67f74f57db0f542fbfe409df677bae3255 languageName: node linkType: hard @@ -3247,10 +2720,28 @@ __metadata: languageName: node linkType: hard -"obug@npm:^2.1.1": - version: 2.1.1 - resolution: "obug@npm:2.1.1" - checksum: 10c0/59dccd7de72a047e08f8649e94c1015ec72f94eefb6ddb57fb4812c4b425a813bc7e7cd30c9aca20db3c59abc3c85cc7a62bb656a968741d770f4e8e02bc2e78 +"nunjucks@npm:^3.2.4": + version: 3.2.4 + resolution: "nunjucks@npm:3.2.4" + dependencies: + a-sync-waterfall: "npm:^1.0.0" + asap: "npm:^2.0.3" + commander: "npm:^5.1.0" + peerDependencies: + chokidar: ^3.3.0 + peerDependenciesMeta: + chokidar: + optional: true + bin: + nunjucks-precompile: bin/precompile + checksum: 10c0/7fe5197559b7c09972c79e2a86f9c093459b9075bc9b41134cd2bc599ae93567b53bd09d472a748edc736192d9ccd2998aa8c20cfcbe6a3fffd281f91897c888 + languageName: node + linkType: hard + +"obug@npm:^2.1.4": + version: 2.2.1 + resolution: "obug@npm:2.2.1" + checksum: 10c0/8a7c001847d880fa6ba83c7bbee6a80fd271ee628439cd508c8647d53b217abb1759e2d5e4aaeb09a35d0a274bdb225a0a5a78eded3213d63e656153cf411b63 languageName: node linkType: hard @@ -3293,6 +2784,13 @@ __metadata: languageName: node linkType: hard +"package-json-from-dist@npm:^1.0.0": + version: 1.0.1 + resolution: "package-json-from-dist@npm:1.0.1" + checksum: 10c0/62ba2785eb655fec084a257af34dbe24292ab74516d6aecef97ef72d4897310bc6898f6c85b5cd22770eaa1ce60d55a0230e150fb6a966e3ecd6c511e23d164b + languageName: node + linkType: hard + "parent-module@npm:^1.0.0": version: 1.0.1 resolution: "parent-module@npm:1.0.1" @@ -3335,14 +2833,7 @@ __metadata: languageName: node linkType: hard -"path-parse@npm:^1.0.7": - version: 1.0.7 - resolution: "path-parse@npm:1.0.7" - checksum: 10c0/11ce261f9d294cc7a58d6a574b7f1b935842355ec66fba3c3fd79e0f036462eaf07d0aa95bb74ff432f9afef97ce1926c720988c6a7451d8a584930ae7de86e1 - languageName: node - linkType: hard - -"path-scurry@npm:^2.0.2": +"path-scurry@npm:^2.0.0, path-scurry@npm:^2.0.2": version: 2.0.2 resolution: "path-scurry@npm:2.0.2" dependencies: @@ -3380,6 +2871,13 @@ __metadata: languageName: node linkType: hard +"picomatch@npm:^4.0.4, picomatch@npm:^4.0.7": + version: 4.0.7 + resolution: "picomatch@npm:4.0.7" + checksum: 10c0/beb6ae02c43ae44e84883b90830196d9046b1726ead292adcf7f57945e0bb0d992d68563d87e03b484b6f3c9a5c6defda7523477f047d7f0e663f126cc01787f + languageName: node + linkType: hard + "pkg-types@npm:^1.3.1": version: 1.3.1 resolution: "pkg-types@npm:1.3.1" @@ -3402,14 +2900,14 @@ __metadata: languageName: node linkType: hard -"postcss@npm:^8.5.6": - version: 8.5.8 - resolution: "postcss@npm:8.5.8" +"postcss@npm:^8.5.28": + version: 8.5.28 + resolution: "postcss@npm:8.5.28" dependencies: - nanoid: "npm:^3.3.11" + nanoid: "npm:^3.3.18" picocolors: "npm:^1.1.1" source-map-js: "npm:^1.2.1" - checksum: 10c0/dd918f7127ee7c60a0295bae2e72b3787892296e1d1c3c564d7a2a00c68d8df83cadc3178491259daa19ccc54804fb71ed8c937c6787e08d8bd4bedf8d17044c + checksum: 10c0/9fe44215a6628d89c8a75184b92f5b2f77e16f9e5b13b39b9009bb7e8e6eccddf82c8854bae922db1f17ace6ef3445073fe38abff513caeb03e5285b1b55620a languageName: node linkType: hard @@ -3434,6 +2932,15 @@ __metadata: languageName: node linkType: hard +"qified@npm:^0.10.1": + version: 0.10.1 + resolution: "qified@npm:0.10.1" + dependencies: + hookified: "npm:^2.1.1" + checksum: 10c0/4a39d45492c65a4b9795381acede1bd566028ce9764ead5b41d776b391a898bf0855dd2b59d724a6711fe253ba3cacf7c9a8832887cf9dc6d48a2c5e4997c82d + languageName: node + linkType: hard + "quansync@npm:^0.2.11": version: 0.2.11 resolution: "quansync@npm:0.2.11" @@ -3469,32 +2976,6 @@ __metadata: languageName: node linkType: hard -"resolve@npm:~1.22.1, resolve@npm:~1.22.2": - version: 1.22.8 - resolution: "resolve@npm:1.22.8" - dependencies: - is-core-module: "npm:^2.13.0" - path-parse: "npm:^1.0.7" - supports-preserve-symlinks-flag: "npm:^1.0.0" - bin: - resolve: bin/resolve - checksum: 10c0/07e179f4375e1fd072cfb72ad66d78547f86e6196c4014b31cb0b8bb1db5f7ca871f922d08da0fbc05b94e9fd42206f819648fa3b5b873ebbc8e1dc68fec433a - languageName: node - linkType: hard - -"resolve@patch:resolve@npm%3A~1.22.1#optional!builtin, resolve@patch:resolve@npm%3A~1.22.2#optional!builtin": - version: 1.22.8 - resolution: "resolve@patch:resolve@npm%3A1.22.8#optional!builtin::version=1.22.8&hash=c3c19d" - dependencies: - is-core-module: "npm:^2.13.0" - path-parse: "npm:^1.0.7" - supports-preserve-symlinks-flag: "npm:^1.0.0" - bin: - resolve: bin/resolve - checksum: 10c0/0446f024439cd2e50c6c8fa8ba77eaa8370b4180f401a96abf3d1ebc770ac51c1955e12764cde449fde3fff480a61f84388e3505ecdbab778f4bef5f8212c729 - languageName: node - linkType: hard - "retry@npm:^0.13.1": version: 0.13.1 resolution: "retry@npm:0.13.1" @@ -3502,93 +2983,61 @@ __metadata: languageName: node linkType: hard -"rollup@npm:^4.43.0": - version: 4.59.0 - resolution: "rollup@npm:4.59.0" - dependencies: - "@rollup/rollup-android-arm-eabi": "npm:4.59.0" - "@rollup/rollup-android-arm64": "npm:4.59.0" - "@rollup/rollup-darwin-arm64": "npm:4.59.0" - "@rollup/rollup-darwin-x64": "npm:4.59.0" - "@rollup/rollup-freebsd-arm64": "npm:4.59.0" - "@rollup/rollup-freebsd-x64": "npm:4.59.0" - "@rollup/rollup-linux-arm-gnueabihf": "npm:4.59.0" - "@rollup/rollup-linux-arm-musleabihf": "npm:4.59.0" - "@rollup/rollup-linux-arm64-gnu": "npm:4.59.0" - "@rollup/rollup-linux-arm64-musl": "npm:4.59.0" - "@rollup/rollup-linux-loong64-gnu": "npm:4.59.0" - "@rollup/rollup-linux-loong64-musl": "npm:4.59.0" - "@rollup/rollup-linux-ppc64-gnu": "npm:4.59.0" - "@rollup/rollup-linux-ppc64-musl": "npm:4.59.0" - "@rollup/rollup-linux-riscv64-gnu": "npm:4.59.0" - "@rollup/rollup-linux-riscv64-musl": "npm:4.59.0" - "@rollup/rollup-linux-s390x-gnu": "npm:4.59.0" - "@rollup/rollup-linux-x64-gnu": "npm:4.59.0" - "@rollup/rollup-linux-x64-musl": "npm:4.59.0" - "@rollup/rollup-openbsd-x64": "npm:4.59.0" - "@rollup/rollup-openharmony-arm64": "npm:4.59.0" - "@rollup/rollup-win32-arm64-msvc": "npm:4.59.0" - "@rollup/rollup-win32-ia32-msvc": "npm:4.59.0" - "@rollup/rollup-win32-x64-gnu": "npm:4.59.0" - "@rollup/rollup-win32-x64-msvc": "npm:4.59.0" - "@types/estree": "npm:1.0.8" - fsevents: "npm:~2.3.2" +"rolldown@npm:~1.2.9": + version: 1.2.11 + resolution: "rolldown@npm:1.2.11" + dependencies: + "@oxc-project/types": "npm:=0.151.0" + "@rolldown/binding-android-arm-eabi": "npm:1.2.11" + "@rolldown/binding-android-arm64": "npm:1.2.11" + "@rolldown/binding-darwin-arm64": "npm:1.2.11" + "@rolldown/binding-darwin-x64": "npm:1.2.11" + "@rolldown/binding-freebsd-x64": "npm:1.2.11" + "@rolldown/binding-linux-arm-gnueabihf": "npm:1.2.11" + "@rolldown/binding-linux-arm64-gnu": "npm:1.2.11" + "@rolldown/binding-linux-arm64-musl": "npm:1.2.11" + "@rolldown/binding-linux-ppc64-gnu": "npm:1.2.11" + "@rolldown/binding-linux-s390x-gnu": "npm:1.2.11" + "@rolldown/binding-linux-x64-gnu": "npm:1.2.11" + "@rolldown/binding-linux-x64-musl": "npm:1.2.11" + "@rolldown/binding-openharmony-arm64": "npm:1.2.11" + "@rolldown/binding-win32-arm64-msvc": "npm:1.2.11" + "@rolldown/binding-win32-x64-msvc": "npm:1.2.11" + "@rolldown/pluginutils": "npm:^1.0.0" dependenciesMeta: - "@rollup/rollup-android-arm-eabi": - optional: true - "@rollup/rollup-android-arm64": - optional: true - "@rollup/rollup-darwin-arm64": - optional: true - "@rollup/rollup-darwin-x64": - optional: true - "@rollup/rollup-freebsd-arm64": + "@rolldown/binding-android-arm-eabi": optional: true - "@rollup/rollup-freebsd-x64": + "@rolldown/binding-android-arm64": optional: true - "@rollup/rollup-linux-arm-gnueabihf": + "@rolldown/binding-darwin-arm64": optional: true - "@rollup/rollup-linux-arm-musleabihf": + "@rolldown/binding-darwin-x64": optional: true - "@rollup/rollup-linux-arm64-gnu": + "@rolldown/binding-freebsd-x64": optional: true - "@rollup/rollup-linux-arm64-musl": + "@rolldown/binding-linux-arm-gnueabihf": optional: true - "@rollup/rollup-linux-loong64-gnu": + "@rolldown/binding-linux-arm64-gnu": optional: true - "@rollup/rollup-linux-loong64-musl": + "@rolldown/binding-linux-arm64-musl": optional: true - "@rollup/rollup-linux-ppc64-gnu": + "@rolldown/binding-linux-ppc64-gnu": optional: true - "@rollup/rollup-linux-ppc64-musl": + "@rolldown/binding-linux-s390x-gnu": optional: true - "@rollup/rollup-linux-riscv64-gnu": + "@rolldown/binding-linux-x64-gnu": optional: true - "@rollup/rollup-linux-riscv64-musl": + "@rolldown/binding-linux-x64-musl": optional: true - "@rollup/rollup-linux-s390x-gnu": + "@rolldown/binding-openharmony-arm64": optional: true - "@rollup/rollup-linux-x64-gnu": + "@rolldown/binding-win32-arm64-msvc": optional: true - "@rollup/rollup-linux-x64-musl": - optional: true - "@rollup/rollup-openbsd-x64": - optional: true - "@rollup/rollup-openharmony-arm64": - optional: true - "@rollup/rollup-win32-arm64-msvc": - optional: true - "@rollup/rollup-win32-ia32-msvc": - optional: true - "@rollup/rollup-win32-x64-gnu": - optional: true - "@rollup/rollup-win32-x64-msvc": - optional: true - fsevents: + "@rolldown/binding-win32-x64-msvc": optional: true bin: - rollup: dist/bin/rollup - checksum: 10c0/f38742da34cfee5e899302615fa157aa77cb6a2a1495e5e3ce4cc9c540d3262e235bbe60caa31562bbfe492b01fdb3e7a8c43c39d842d3293bcf843123b766fc + rolldown: ./bin/cli.mjs + checksum: 10c0/43dfd083fb625a254c039bddfe04f3dd1d7cf8b84291ead835e3702e8050b7fee5e702c8280d3000b2541c0e24aaa3110bac4251bc2ca50a85fe1c8bbb48d2f5 languageName: node linkType: hard @@ -3608,23 +3057,21 @@ __metadata: languageName: node linkType: hard -"semver@npm:^7.5.3, semver@npm:^7.6.0": - version: 7.6.3 - resolution: "semver@npm:7.6.3" +"semver@npm:^7.5.2, semver@npm:^7.7.4": + version: 7.8.5 + resolution: "semver@npm:7.8.5" bin: semver: bin/semver.js - checksum: 10c0/88f33e148b210c153873cb08cfe1e281d518aaa9a666d4d148add6560db5cd3c582f3a08ccb91f38d5f379ead256da9931234ed122057f40bb5766e65e58adaf + checksum: 10c0/b1f3127a5be8125a94f37188b361c212466c292c6910adce3ec106cff5dc211ccaedc4739c11bb70fda59d6fc1f040a9bca289f4e093451521a2372e5231fe0c languageName: node linkType: hard -"semver@npm:~7.5.4": - version: 7.5.4 - resolution: "semver@npm:7.5.4" - dependencies: - lru-cache: "npm:^6.0.0" +"semver@npm:^7.6.0": + version: 7.6.3 + resolution: "semver@npm:7.6.3" bin: semver: bin/semver.js - checksum: 10c0/5160b06975a38b11c1ab55950cb5b8a23db78df88275d3d8a42ccf1f29e55112ac995b3a26a522c36e3b5f76b0445f1eef70d696b8c7862a2b4303d7b0e7609e + checksum: 10c0/88f33e148b210c153873cb08cfe1e281d518aaa9a666d4d148add6560db5cd3c582f3a08ccb91f38d5f379ead256da9931234ed122057f40bb5766e65e58adaf languageName: node linkType: hard @@ -3644,10 +3091,10 @@ __metadata: languageName: node linkType: hard -"siginfo@npm:^2.0.0": - version: 2.0.0 - resolution: "siginfo@npm:2.0.0" - checksum: 10c0/3def8f8e516fbb34cb6ae415b07ccc5d9c018d85b4b8611e3dc6f8be6d1899f693a4382913c9ed51a06babb5201639d76453ab297d1c54a456544acf5c892e34 +"signal-exit@npm:^4.0.1": + version: 4.1.0 + resolution: "signal-exit@npm:4.1.0" + checksum: 10c0/41602dce540e46d599edba9d9860193398d135f7ff72cab629db5171516cfae628d21e7bfccde1bbfdf11c48726bc2a6d1a8fb8701125852fbfda7cf19c6aa83 languageName: node linkType: hard @@ -3690,34 +3137,13 @@ __metadata: languageName: node linkType: hard -"source-map-js@npm:^1.2.0, source-map-js@npm:^1.2.1": +"source-map-js@npm:^1.2.1": version: 1.2.1 resolution: "source-map-js@npm:1.2.1" checksum: 10c0/7bda1fc4c197e3c6ff17de1b8b2c20e60af81b63a52cb32ec5a5d67a20a7d42651e2cb34ebe93833c5a2a084377e17455854fee3e21e7925c64a51b6a52b0faf languageName: node linkType: hard -"source-map@npm:~0.6.1": - version: 0.6.1 - resolution: "source-map@npm:0.6.1" - checksum: 10c0/ab55398007c5e5532957cb0beee2368529618ac0ab372d789806f5718123cc4367d57de3904b4e6a4170eb5a0b0f41373066d02ca0735a0c4d75c7d328d3e011 - languageName: node - linkType: hard - -"split2@npm:^4.0.0": - version: 4.2.0 - resolution: "split2@npm:4.2.0" - checksum: 10c0/b292beb8ce9215f8c642bb68be6249c5a4c7f332fc8ecadae7be5cbdf1ea95addc95f0459ef2e7ad9d45fd1064698a097e4eb211c83e772b49bc0ee423e91534 - languageName: node - linkType: hard - -"sprintf-js@npm:~1.0.2": - version: 1.0.3 - resolution: "sprintf-js@npm:1.0.3" - checksum: 10c0/ecadcfe4c771890140da5023d43e190b7566d9cf8b2d238600f31bec0fc653f328da4450eb04bd59a431771a8e9cc0e118f0aa3974b683a4981b4e07abc2a5bb - languageName: node - linkType: hard - "ssri@npm:^13.0.0": version: 13.0.1 resolution: "ssri@npm:13.0.1" @@ -3727,24 +3153,10 @@ __metadata: languageName: node linkType: hard -"stackback@npm:0.0.2": - version: 0.0.2 - resolution: "stackback@npm:0.0.2" - checksum: 10c0/89a1416668f950236dd5ac9f9a6b2588e1b9b62b1b6ad8dff1bfc5d1a15dbf0aafc9b52d2226d00c28dffff212da464eaeebfc6b7578b9d180cef3e3782c5983 - languageName: node - linkType: hard - -"std-env@npm:^3.10.0": - version: 3.10.0 - resolution: "std-env@npm:3.10.0" - checksum: 10c0/1814927a45004d36dde6707eaf17552a546769bc79a6421be2c16ce77d238158dfe5de30910b78ec30d95135cc1c59ea73ee22d2ca170f8b9753f84da34c427f - languageName: node - linkType: hard - -"string-argv@npm:~0.3.1": - version: 0.3.2 - resolution: "string-argv@npm:0.3.2" - checksum: 10c0/75c02a83759ad1722e040b86823909d9a2fc75d15dd71ec4b537c3560746e33b5f5a07f7332d1e3f88319909f82190843aa2f0a0d8c8d591ec08e93d5b8dec82 +"std-env@npm:^4.2.0": + version: 4.2.0 + resolution: "std-env@npm:4.2.0" + checksum: 10c0/40ac525ce7b7c556abc332a7376f14356eeb1a7f17f6ff9a003eb9f52326ff1f3745d3e1b43452675b1ec6fcc319f1b1d6f3b0d386cf3f91058479ad883cff69 languageName: node linkType: hard @@ -3759,6 +3171,27 @@ __metadata: languageName: node linkType: hard +"string-width@npm:^7.0.0, string-width@npm:^7.2.0": + version: 7.2.0 + resolution: "string-width@npm:7.2.0" + dependencies: + emoji-regex: "npm:^10.3.0" + get-east-asian-width: "npm:^1.0.0" + strip-ansi: "npm:^7.1.0" + checksum: 10c0/eb0430dd43f3199c7a46dcbf7a0b34539c76fe3aa62763d0b0655acdcbdf360b3f66f3d58ca25ba0205f42ea3491fa00f09426d3b7d3040e506878fc7664c9b9 + languageName: node + linkType: hard + +"string-width@npm:^8.2.1": + version: 8.3.0 + resolution: "string-width@npm:8.3.0" + dependencies: + get-east-asian-width: "npm:^1.5.0" + strip-ansi: "npm:^7.1.2" + checksum: 10c0/bc354b27772777c314e6792106f01586e959f36abb3d4b8ad46c4d4ee92d5e906acb10c1f3be97a85ae0ba1b11389094bc5672999fc5f03f12d22a4725fa30fe + languageName: node + linkType: hard + "strip-ansi@npm:^6.0.0, strip-ansi@npm:^6.0.1": version: 6.0.1 resolution: "strip-ansi@npm:6.0.1" @@ -3768,10 +3201,12 @@ __metadata: languageName: node linkType: hard -"strip-json-comments@npm:~3.1.1": - version: 3.1.1 - resolution: "strip-json-comments@npm:3.1.1" - checksum: 10c0/9681a6257b925a7fa0f285851c0e613cc934a50661fa7bb41ca9cbbff89686bb4a0ee366e6ecedc4daafd01e83eee0720111ab294366fe7c185e935475ebcecd +"strip-ansi@npm:^7.1.0, strip-ansi@npm:^7.1.2": + version: 7.2.0 + resolution: "strip-ansi@npm:7.2.0" + dependencies: + ansi-regex: "npm:^6.2.2" + checksum: 10c0/544d13b7582f8254811ea97db202f519e189e59d35740c46095897e254e4f1aa9fe1524a83ad6bc5ad67d4dd6c0281d2e0219ed62b880a6238a16a17d375f221 languageName: node linkType: hard @@ -3784,31 +3219,6 @@ __metadata: languageName: node linkType: hard -"supports-color@npm:^7.1.0": - version: 7.2.0 - resolution: "supports-color@npm:7.2.0" - dependencies: - has-flag: "npm:^4.0.0" - checksum: 10c0/afb4c88521b8b136b5f5f95160c98dee7243dc79d5432db7efc27efb219385bbc7d9427398e43dd6cc730a0f87d5085ce1652af7efbe391327bc0a7d0f7fc124 - languageName: node - linkType: hard - -"supports-color@npm:~8.1.1": - version: 8.1.1 - resolution: "supports-color@npm:8.1.1" - dependencies: - has-flag: "npm:^4.0.0" - checksum: 10c0/ea1d3c275dd604c974670f63943ed9bd83623edc102430c05adb8efc56ba492746b6e95386e7831b872ec3807fd89dd8eb43f735195f37b5ec343e4234cc7e89 - languageName: node - linkType: hard - -"supports-preserve-symlinks-flag@npm:^1.0.0": - version: 1.0.0 - resolution: "supports-preserve-symlinks-flag@npm:1.0.0" - checksum: 10c0/6c4032340701a9950865f7ae8ef38578d8d7053f5e10518076e6554a9381fa91bd9c6850193695c141f32b21f979c985db07265a758867bac95de05f7d8aeb39 - languageName: node - linkType: hard - "tar@npm:^7.5.4": version: 7.5.10 resolution: "tar@npm:7.5.10" @@ -3822,20 +3232,27 @@ __metadata: languageName: node linkType: hard -"tinybench@npm:^2.9.0": - version: 2.9.0 - resolution: "tinybench@npm:2.9.0" - checksum: 10c0/c3500b0f60d2eb8db65250afe750b66d51623057ee88720b7f064894a6cb7eb93360ca824a60a31ab16dab30c7b1f06efe0795b352e37914a9d4bad86386a20c +"tinybench@npm:^6.1.4": + version: 6.2.0 + resolution: "tinybench@npm:6.2.0" + checksum: 10c0/ae821428f0412324611d449290f8497c886c94f04bad8e19aa9b3a5060b4802e738c49f9eb493e4fd17e4d5e792290565e5079be647df1995aea731c82363815 languageName: node linkType: hard -"tinyexec@npm:^1.0.0, tinyexec@npm:^1.0.2": +"tinyexec@npm:^1.0.0": version: 1.0.2 resolution: "tinyexec@npm:1.0.2" checksum: 10c0/1261a8e34c9b539a9aae3b7f0bb5372045ff28ee1eba035a2a059e532198fe1a182ec61ac60fa0b4a4129f0c4c4b1d2d57355b5cb9aa2d17ac9454ecace502ee languageName: node linkType: hard +"tinyexec@npm:^1.3.0": + version: 1.3.1 + resolution: "tinyexec@npm:1.3.1" + checksum: 10c0/c590369cb3fa73cc18a3998caec8a00f9712d91309c881be3ac52b160fb2f80c7b1fbf3591858ab6abab47ad55385b0c86ec19982e32441a01f55298dab9ccd2 + languageName: node + linkType: hard + "tinyglobby@npm:^0.2.12, tinyglobby@npm:^0.2.15": version: 0.2.15 resolution: "tinyglobby@npm:0.2.15" @@ -3846,17 +3263,20 @@ __metadata: languageName: node linkType: hard -"tinyrainbow@npm:^3.0.3": - version: 3.0.3 - resolution: "tinyrainbow@npm:3.0.3" - checksum: 10c0/1e799d35cd23cabe02e22550985a3051dc88814a979be02dc632a159c393a998628eacfc558e4c746b3006606d54b00bcdea0c39301133956d10a27aa27e988c +"tinyglobby@npm:^0.2.17": + version: 0.2.17 + resolution: "tinyglobby@npm:0.2.17" + dependencies: + fdir: "npm:^6.5.0" + picomatch: "npm:^4.0.4" + checksum: 10c0/7f7bb0f197c88bc4b20c231e0deca4240ca3bf313a88f5a7fee93a872b84966a4d50220947c0455ad07a60b3b360961c5b7fd979222aeb716a9f99b412002e4c languageName: node linkType: hard -"to-fast-properties@npm:^2.0.0": - version: 2.0.0 - resolution: "to-fast-properties@npm:2.0.0" - checksum: 10c0/b214d21dbfb4bce3452b6244b336806ffea9c05297148d32ebb428d5c43ce7545bdfc65a1ceb58c9ef4376a65c0cb2854d645f33961658b3e3b4f84910ddcdd7 +"tinyrainbow@npm:^3.1.1": + version: 3.1.1 + resolution: "tinyrainbow@npm:3.1.1" + checksum: 10c0/f9d2743832c6191f753408f36224fe817620b8abcef572b2e570204c673a901d753ff84ca8e7b88f9c79e934295b3ffc6fcbc56a06f126e24e1ec6186dcad40d languageName: node linkType: hard @@ -3867,50 +3287,12 @@ __metadata: languageName: node linkType: hard -"ts-api-utils@npm:^2.4.0": - version: 2.4.0 - resolution: "ts-api-utils@npm:2.4.0" +"ts-api-utils@npm:^2.5.0": + version: 2.5.0 + resolution: "ts-api-utils@npm:2.5.0" peerDependencies: typescript: ">=4.8.4" - checksum: 10c0/ed185861aef4e7124366a3f6561113557a57504267d4d452a51e0ba516a9b6e713b56b4aeaab9fa13de9db9ab755c65c8c13a777dba9133c214632cb7b65c083 - languageName: node - linkType: hard - -"ts-node@npm:^10.9.2": - version: 10.9.2 - resolution: "ts-node@npm:10.9.2" - dependencies: - "@cspotcode/source-map-support": "npm:^0.8.0" - "@tsconfig/node10": "npm:^1.0.7" - "@tsconfig/node12": "npm:^1.0.7" - "@tsconfig/node14": "npm:^1.0.0" - "@tsconfig/node16": "npm:^1.0.2" - acorn: "npm:^8.4.1" - acorn-walk: "npm:^8.1.1" - arg: "npm:^4.1.0" - create-require: "npm:^1.1.0" - diff: "npm:^4.0.1" - make-error: "npm:^1.1.1" - v8-compile-cache-lib: "npm:^3.0.1" - yn: "npm:3.1.1" - peerDependencies: - "@swc/core": ">=1.2.50" - "@swc/wasm": ">=1.2.50" - "@types/node": "*" - typescript: ">=2.7" - peerDependenciesMeta: - "@swc/core": - optional: true - "@swc/wasm": - optional: true - bin: - ts-node: dist/bin.js - ts-node-cwd: dist/bin-cwd.js - ts-node-esm: dist/bin-esm.js - ts-node-script: dist/bin-script.js - ts-node-transpile-only: dist/bin-transpile.js - ts-script: dist/bin-script-deprecated.js - checksum: 10c0/5f29938489f96982a25ba650b64218e83a3357d76f7bede80195c65ab44ad279c8357264639b7abdd5d7e75fc269a83daa0e9c62fd8637a3def67254ecc9ddc2 + checksum: 10c0/767849383c114e7f1971fa976b20e73ac28fd0c70d8d65c0004790bf4d8f89888c7e4cf6d5949f9c1beae9bc3c64835bef77bbe27fddf45a3c7b60cebcf85c8c languageName: node linkType: hard @@ -3930,58 +3312,38 @@ __metadata: languageName: node linkType: hard -"typescript-eslint@npm:^8.56.1": - version: 8.56.1 - resolution: "typescript-eslint@npm:8.56.1" +"typescript-eslint@npm:^8.70.1": + version: 8.70.1 + resolution: "typescript-eslint@npm:8.70.1" dependencies: - "@typescript-eslint/eslint-plugin": "npm:8.56.1" - "@typescript-eslint/parser": "npm:8.56.1" - "@typescript-eslint/typescript-estree": "npm:8.56.1" - "@typescript-eslint/utils": "npm:8.56.1" + "@typescript-eslint/eslint-plugin": "npm:8.70.1" + "@typescript-eslint/parser": "npm:8.70.1" + "@typescript-eslint/typescript-estree": "npm:8.70.1" + "@typescript-eslint/utils": "npm:8.70.1" peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 - typescript: ">=4.8.4 <6.0.0" - checksum: 10c0/c33aeb9a8beab54308412dcd460ab60f845fee30eaed1fdc1083ff53c430a4dcbdfeac862136a21fb3a639538f8712d933fc410680c2a650e67b992720a0d9f6 - languageName: node - linkType: hard - -"typescript@npm:5.8.2": - version: 5.8.2 - resolution: "typescript@npm:5.8.2" - bin: - tsc: bin/tsc - tsserver: bin/tsserver - checksum: 10c0/5c4f6fbf1c6389b6928fe7b8fcd5dc73bb2d58cd4e3883f1d774ed5bd83b151cbac6b7ecf11723de56d4676daeba8713894b1e9af56174f2f9780ae7848ec3c6 - languageName: node - linkType: hard - -"typescript@npm:^5.5.4": - version: 5.7.2 - resolution: "typescript@npm:5.7.2" - bin: - tsc: bin/tsc - tsserver: bin/tsserver - checksum: 10c0/a873118b5201b2ef332127ef5c63fb9d9c155e6fdbe211cbd9d8e65877283797cca76546bad742eea36ed7efbe3424a30376818f79c7318512064e8625d61622 + typescript: ">=4.8.4 <6.1.0" + checksum: 10c0/9d9a01dca7f0663b7dbe496c2e1697f0cf6a44570f7dd53926bf5ea80a54bb0f7ca1c88771d82eabc6083f7983a9dec24f6b4ed1957d0810d9e9e68a250cbc4d languageName: node linkType: hard -"typescript@patch:typescript@npm%3A5.8.2#optional!builtin": - version: 5.8.2 - resolution: "typescript@patch:typescript@npm%3A5.8.2#optional!builtin::version=5.8.2&hash=5786d5" +"typescript@npm:~6.0.3": + version: 6.0.3 + resolution: "typescript@npm:6.0.3" bin: tsc: bin/tsc tsserver: bin/tsserver - checksum: 10c0/5448a08e595cc558ab321e49d4cac64fb43d1fa106584f6ff9a8d8e592111b373a995a1d5c7f3046211c8a37201eb6d0f1566f15cdb7a62a5e3be01d087848e2 + checksum: 10c0/4a25ff5045b984370f48f196b3a0120779b1b343d40b9a68d114ea5e5fff099809b2bb777576991a63a5cd59cf7bffd96ff6fe10afcefbcb8bd6fb96ad4b6606 languageName: node linkType: hard -"typescript@patch:typescript@npm%3A^5.5.4#optional!builtin": - version: 5.7.2 - resolution: "typescript@patch:typescript@npm%3A5.7.2#optional!builtin::version=5.7.2&hash=5786d5" +"typescript@patch:typescript@npm%3A~6.0.3#optional!builtin": + version: 6.0.3 + resolution: "typescript@patch:typescript@npm%3A6.0.3#optional!builtin::version=6.0.3&hash=5786d5" bin: tsc: bin/tsc tsserver: bin/tsserver - checksum: 10c0/f3b8082c9d1d1629a215245c9087df56cb784f9fb6f27b5d55577a20e68afe2a889c040aacff6d27e35be165ecf9dca66e694c42eb9a50b3b2c451b36b5675cb + checksum: 10c0/2f25c74e65663c248fa1ade2b8459d9ce5372ff9dad07067310f132966ebec1d93f6c42f0baf77a6b6a7a91460463f708e6887013aaade22111037457c6b25df languageName: node linkType: hard @@ -3992,10 +3354,10 @@ __metadata: languageName: node linkType: hard -"undici-types@npm:~7.18.0": - version: 7.18.2 - resolution: "undici-types@npm:7.18.2" - checksum: 10c0/85a79189113a238959d7a647368e4f7c5559c3a404ebdb8fc4488145ce9426fcd82252a844a302798dfc0e37e6fb178ff481ed03bc4caf634c5757d9ef43521d +"undici-types@npm:>=7.24.0 <7.24.7": + version: 7.24.6 + resolution: "undici-types@npm:7.24.6" + checksum: 10c0/d9cd8befb643ac904615c280a095ba4240531f6bb4a5e75a22a7483630ca8d3f1016d2ab6ace6ceda1f63b3a2db2fe037fafe121d6917a0187573aa548ff78ca languageName: node linkType: hard @@ -4017,68 +3379,87 @@ __metadata: languageName: node linkType: hard -"universalify@npm:^2.0.0": - version: 2.0.1 - resolution: "universalify@npm:2.0.1" - checksum: 10c0/73e8ee3809041ca8b818efb141801a1004e3fc0002727f1531f4de613ea281b494a40909596dae4a042a4fb6cd385af5d4db2e137b1362e0e91384b828effd3a - languageName: node - linkType: hard - -"uri-js@npm:^4.2.2": - version: 4.4.1 - resolution: "uri-js@npm:4.4.1" - dependencies: - punycode: "npm:^2.1.0" - checksum: 10c0/4ef57b45aa820d7ac6496e9208559986c665e49447cb072744c13b66925a362d96dd5a46c4530a6b8e203e5db5fe849369444440cb22ecfc26c679359e5dfa3c - languageName: node - linkType: hard - -"v8-compile-cache-lib@npm:^3.0.1": - version: 3.0.1 - resolution: "v8-compile-cache-lib@npm:3.0.1" - checksum: 10c0/bdc36fb8095d3b41df197f5fb6f11e3a26adf4059df3213e3baa93810d8f0cc76f9a74aaefc18b73e91fe7e19154ed6f134eda6fded2e0f1c8d2272ed2d2d391 - languageName: node - linkType: hard - -"vite-plugin-dts@npm:^4.5.4": - version: 4.5.4 - resolution: "vite-plugin-dts@npm:4.5.4" +"unplugin-dts@npm:^1.1.1": + version: 1.1.1 + resolution: "unplugin-dts@npm:1.1.1" dependencies: - "@microsoft/api-extractor": "npm:^7.50.1" "@rollup/pluginutils": "npm:^5.1.4" - "@volar/typescript": "npm:^2.4.11" - "@vue/language-core": "npm:2.2.0" + "@volar/typescript": "npm:^2.4.26" compare-versions: "npm:^6.1.1" debug: "npm:^4.4.0" + glob-to-regexp: "npm:0.4.1" kolorist: "npm:^1.8.0" - local-pkg: "npm:^1.0.0" + local-pkg: "npm:^1.1.1" magic-string: "npm:^0.30.17" + unplugin: "npm:^2.3.2" peerDependencies: - typescript: "*" - vite: "*" + "@microsoft/api-extractor": ">=7" + "@rspack/core": ^1 + "@vue/language-core": ^3.1.5 + esbuild: "*" + rolldown: "*" + rollup: ">=3" + typescript: ">=4" + vite: ">=3" + webpack: ^4 || ^5 peerDependenciesMeta: + "@microsoft/api-extractor": + optional: true + "@rspack/core": + optional: true + "@vue/language-core": + optional: true + esbuild: + optional: true + rolldown: + optional: true + rollup: + optional: true vite: optional: true - checksum: 10c0/5fcb7f3739d115f36195a692c0e9f9fca4e504bbbbabe29e71ee06630dd05ea2920169371e80e548eb4779d2eca14107277497838d7df588d53e1fadf84be861 + webpack: + optional: true + checksum: 10c0/21759ef53f4dd99b2d87af672509ae2c791454cb136bf843d890c7773a9e5a5a13af15c62caaa1020f622da07c23de94ee2a0e0bd23539a9fedb9b461158f975 languageName: node linkType: hard -"vite@npm:^6.0.0 || ^7.0.0, vite@npm:^7.3.1": - version: 7.3.1 - resolution: "vite@npm:7.3.1" +"unplugin@npm:^2.3.2": + version: 2.3.11 + resolution: "unplugin@npm:2.3.11" dependencies: - esbuild: "npm:^0.27.0" - fdir: "npm:^6.5.0" - fsevents: "npm:~2.3.3" + "@jridgewell/remapping": "npm:^2.3.5" + acorn: "npm:^8.15.0" picomatch: "npm:^4.0.3" - postcss: "npm:^8.5.6" - rollup: "npm:^4.43.0" - tinyglobby: "npm:^0.2.15" + webpack-virtual-modules: "npm:^0.6.2" + checksum: 10c0/273c1eab0eca4470c7317428689295c31dbe8ab0b306504de9f03cd20c156debb4131bef24b27ac615862958c5dd950a3951d26c0723ea774652ab3624149cff + languageName: node + linkType: hard + +"uri-js@npm:^4.2.2": + version: 4.4.1 + resolution: "uri-js@npm:4.4.1" + dependencies: + punycode: "npm:^2.1.0" + checksum: 10c0/4ef57b45aa820d7ac6496e9208559986c665e49447cb072744c13b66925a362d96dd5a46c4530a6b8e203e5db5fe849369444440cb22ecfc26c679359e5dfa3c + languageName: node + linkType: hard + +"vite@npm:^8.3.1": + version: 8.3.1 + resolution: "vite@npm:8.3.1" + dependencies: + fsevents: "npm:~2.3.3" + lightningcss: "npm:^1.33.0" + picomatch: "npm:^4.0.7" + postcss: "npm:^8.5.28" + rolldown: "npm:~1.2.9" + tinyglobby: "npm:^0.2.17" peerDependencies: "@types/node": ^20.19.0 || >=22.12.0 + "@vitejs/devtools": ^0.7.1 + esbuild: ^0.27.0 || ^0.28.0 jiti: ">=1.21.0" less: ^4.0.0 - lightningcss: ^1.21.0 sass: ^1.70.0 sass-embedded: ^1.70.0 stylus: ">=0.54.8" @@ -4092,12 +3473,14 @@ __metadata: peerDependenciesMeta: "@types/node": optional: true + "@vitejs/devtools": + optional: true + esbuild: + optional: true jiti: optional: true less: optional: true - lightningcss: - optional: true sass: optional: true sass-embedded: @@ -4114,44 +3497,40 @@ __metadata: optional: true bin: vite: bin/vite.js - checksum: 10c0/5c7548f5f43a23533e53324304db4ad85f1896b1bfd3ee32ae9b866bac2933782c77b350eb2b52a02c625c8ad1ddd4c000df077419410650c982cd97fde8d014 + checksum: 10c0/a91f4fee1cd331912ee3ac45f49900044846e28b8632ae734fe1978630e643e503d04181b2aeac59f70556fc51137dd49179fd39cbeb72a3e5f0bce88d2bcd30 languageName: node linkType: hard -"vitest@npm:^4.0.18": - version: 4.0.18 - resolution: "vitest@npm:4.0.18" +"vitest@npm:5.0.2": + version: 5.0.2 + resolution: "vitest@npm:5.0.2" dependencies: - "@vitest/expect": "npm:4.0.18" - "@vitest/mocker": "npm:4.0.18" - "@vitest/pretty-format": "npm:4.0.18" - "@vitest/runner": "npm:4.0.18" - "@vitest/snapshot": "npm:4.0.18" - "@vitest/spy": "npm:4.0.18" - "@vitest/utils": "npm:4.0.18" - es-module-lexer: "npm:^1.7.0" - expect-type: "npm:^1.2.2" - magic-string: "npm:^0.30.21" - obug: "npm:^2.1.1" - pathe: "npm:^2.0.3" - picomatch: "npm:^4.0.3" - std-env: "npm:^3.10.0" - tinybench: "npm:^2.9.0" - tinyexec: "npm:^1.0.2" - tinyglobby: "npm:^0.2.15" - tinyrainbow: "npm:^3.0.3" - vite: "npm:^6.0.0 || ^7.0.0" - why-is-node-running: "npm:^2.3.0" + "@types/chai": "npm:^5.2.2" + "@vitest/mocker": "npm:5.0.2" + chai: "npm:^6.2.2" + es-module-lexer: "npm:^2.3.2" + expect-type: "npm:^1.4.0" + magic-string: "npm:^1.2.3" + obug: "npm:^2.1.4" + picomatch: "npm:^4.0.7" + std-env: "npm:^4.2.0" + tinybench: "npm:^6.1.4" + tinyexec: "npm:^1.3.0" + tinyglobby: "npm:^0.2.17" + why-is-node-running: "npm:^3.2.1" peerDependencies: "@edge-runtime/vm": "*" "@opentelemetry/api": ^1.9.0 - "@types/node": ^20.0.0 || ^22.0.0 || >=24.0.0 - "@vitest/browser-playwright": 4.0.18 - "@vitest/browser-preview": 4.0.18 - "@vitest/browser-webdriverio": 4.0.18 - "@vitest/ui": 4.0.18 + "@types/node": ^22.0.0 || >=24.0.0 + "@vitest/browser-playwright": 5.0.2 + "@vitest/browser-preview": 5.0.2 + "@vitest/browser-webdriverio": ^5.0.0-beta.5 || >=5.0.0 + "@vitest/coverage-istanbul": 5.0.2 + "@vitest/coverage-v8": 5.0.2 + "@vitest/ui": 5.0.2 happy-dom: "*" jsdom: "*" + vite: ^6.4.0 || ^7.0.0 || ^8.0.0 peerDependenciesMeta: "@edge-runtime/vm": optional: true @@ -4165,15 +3544,21 @@ __metadata: optional: true "@vitest/browser-webdriverio": optional: true + "@vitest/coverage-istanbul": + optional: true + "@vitest/coverage-v8": + optional: true "@vitest/ui": optional: true happy-dom: optional: true jsdom: optional: true + vite: + optional: false bin: - vitest: vitest.mjs - checksum: 10c0/b913cd32032c95f29ff08c931f4b4c6fd6d2da498908d6770952c561a1b8d75c62499a1f04cadf82fb89cc0f9a33f29fb5dfdb899f6dbb27686a9d91571be5fa + vitest: ./vitest.mjs + checksum: 10c0/e86919375db85ddecc6308076341f66a9d51c3b28aa7966ea6bcb0f45234b5e2883602c17e509abfa6d729aaf8113b3adf7b59a8316079c2c9a48dbb1b248897 languageName: node linkType: hard @@ -4184,6 +3569,13 @@ __metadata: languageName: node linkType: hard +"webpack-virtual-modules@npm:^0.6.2": + version: 0.6.2 + resolution: "webpack-virtual-modules@npm:0.6.2" + checksum: 10c0/5ffbddf0e84bf1562ff86cf6fcf039c74edf09d78358a6904a09bbd4484e8bb6812dc385fe14330b715031892dcd8423f7a88278b57c9f5002c84c2860179add + languageName: node + linkType: hard + "which@npm:^2.0.1": version: 2.0.2 resolution: "which@npm:2.0.2" @@ -4206,15 +3598,12 @@ __metadata: languageName: node linkType: hard -"why-is-node-running@npm:^2.3.0": - version: 2.3.0 - resolution: "why-is-node-running@npm:2.3.0" - dependencies: - siginfo: "npm:^2.0.0" - stackback: "npm:0.0.2" +"why-is-node-running@npm:^3.2.1": + version: 3.2.2 + resolution: "why-is-node-running@npm:3.2.2" bin: why-is-node-running: cli.js - checksum: 10c0/1cde0b01b827d2cf4cb11db962f3958b9175d5d9e7ac7361d1a7b0e2dc6069a263e69118bd974c4f6d0a890ef4eedfe34cf3d5167ec14203dbc9a18620537054 + checksum: 10c0/47307bfc2f48dd5228a92103dc43a7cadafbc6b6791c114fd27e03ac271a5082849a54f7e5f5cb261ea68f0d4ad40d9cb503b55812a6b62e67de0c57039fb95b languageName: node linkType: hard @@ -4229,6 +3618,17 @@ __metadata: languageName: node linkType: hard +"wrap-ansi@npm:^9.0.0": + version: 9.0.2 + resolution: "wrap-ansi@npm:9.0.2" + dependencies: + ansi-styles: "npm:^6.2.1" + string-width: "npm:^7.0.0" + strip-ansi: "npm:^7.1.0" + checksum: 10c0/3305839b9a0d6fb930cb63a52f34d3936013d8b0682ff3ec133c9826512620f213800ffa19ea22904876d5b7e9a3c1f40682f03597d986a4ca881fa7b033688c + languageName: node + linkType: hard + "y18n@npm:^5.0.5": version: 5.0.8 resolution: "y18n@npm:5.0.8" @@ -4257,9 +3657,16 @@ __metadata: languageName: node linkType: hard -"yargs@npm:^17.0.0": - version: 17.7.2 - resolution: "yargs@npm:17.7.2" +"yargs-parser@npm:^22.0.0": + version: 22.0.0 + resolution: "yargs-parser@npm:22.0.0" + checksum: 10c0/cb7ef81759c4271cb1d96b9351dbbc9a9ce35d3e1122d2b739bf6c432603824fa02c67cc12dcef6ea80283379d63495686e8f41cc7b06c6576e792aba4d33e1c + languageName: node + linkType: hard + +"yargs@npm:^17.7.2": + version: 17.7.3 + resolution: "yargs@npm:17.7.3" dependencies: cliui: "npm:^8.0.1" escalade: "npm:^3.1.1" @@ -4268,14 +3675,21 @@ __metadata: string-width: "npm:^4.2.3" y18n: "npm:^5.0.5" yargs-parser: "npm:^21.1.1" - checksum: 10c0/ccd7e723e61ad5965fffbb791366db689572b80cca80e0f96aad968dfff4156cd7cd1ad18607afe1046d8241e6fb2d6c08bf7fa7bfb5eaec818735d8feac8f05 + checksum: 10c0/7a28572f7e785a57886e34fdbddb9b28756dec552e1453d5f6e7cdd00ad8721a4e8c4321d33683f5e61cacb36ad43258adbb48396b71ec4ed14abee0fc0d0c1f languageName: node linkType: hard -"yn@npm:3.1.1": - version: 3.1.1 - resolution: "yn@npm:3.1.1" - checksum: 10c0/0732468dd7622ed8a274f640f191f3eaf1f39d5349a1b72836df484998d7d9807fbea094e2f5486d6b0cd2414aad5775972df0e68f8604db89a239f0f4bf7443 +"yargs@npm:^18.0.0": + version: 18.2.0 + resolution: "yargs@npm:18.2.0" + dependencies: + cliui: "npm:^9.0.1" + escalade: "npm:^3.1.1" + get-caller-file: "npm:^2.0.5" + string-width: "npm:^8.2.1" + y18n: "npm:^5.0.5" + yargs-parser: "npm:^22.0.0" + checksum: 10c0/7419338c3c00f9143ec6b6ab168335061c6f5342d161642aec23ff119e28cac1c417e0eca1d7dc82fd084f07207cc5d34c7d8315f605c0e3dbf12c350e405f3f languageName: node linkType: hard From 90d8da061044aa8dac2c5f3c1b1af98b319b04e5 Mon Sep 17 00:00:00 2001 From: Zaitsev Kirill Date: Sat, 26 Sep 2026 13:29:19 +0400 Subject: [PATCH 13/15] ci: Release preparation and publication were separated --- .github/actions/setup-node-deps/action.yml | 8 +- .github/workflows/release.yml | 172 +++++++++++++++------ .github/workflows/tests.yml | 11 +- README.md | 2 + docs/RELEASING.md | 66 ++++++++ 5 files changed, 209 insertions(+), 50 deletions(-) create mode 100644 docs/RELEASING.md diff --git a/.github/actions/setup-node-deps/action.yml b/.github/actions/setup-node-deps/action.yml index d12fa61..d40a7b3 100644 --- a/.github/actions/setup-node-deps/action.yml +++ b/.github/actions/setup-node-deps/action.yml @@ -18,9 +18,13 @@ inputs: runs: using: composite steps: + - name: Enable Corepack + shell: bash + run: corepack enable + - name: Setup Node.js if: ${{ inputs.registry-url == '' }} - uses: actions/setup-node@v4 + uses: actions/setup-node@v6 with: node-version: ${{ inputs.node-version }} cache: yarn @@ -28,7 +32,7 @@ runs: - name: Setup Node.js with registry if: ${{ inputs.registry-url != '' }} - uses: actions/setup-node@v4 + uses: actions/setup-node@v6 with: node-version: ${{ inputs.node-version }} registry-url: ${{ inputs.registry-url }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 59589ad..c229807 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -4,9 +4,9 @@ on: workflow_dispatch: inputs: release: - description: "Release type" + description: Release type required: true - default: "auto" + default: auto type: choice options: - auto @@ -14,9 +14,9 @@ on: - minor - major prerelease: - description: "Prerelease channel" + description: Prerelease channel required: true - default: "none" + default: none type: choice options: - none @@ -24,43 +24,69 @@ on: - beta - rc npm_tag: - description: "npm dist-tag for publication" + description: npm dist-tag (auto selects latest or the prerelease channel) required: true - default: "latest" + default: auto type: string permissions: - contents: write - id-token: write + contents: read concurrency: - group: release-${{ github.ref }} + group: release cancel-in-progress: false jobs: release: - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 + timeout-minutes: 15 + permissions: + contents: write + outputs: + tag: ${{ steps.version.outputs.tag }} + npm_tag: ${{ steps.validate.outputs.npm_tag }} steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: fetch-depth: 0 - name: Validate release branch and npm tag + id: validate env: NPM_TAG: ${{ inputs.npm_tag }} + PRERELEASE: ${{ inputs.prerelease }} run: | - if [ "$NPM_TAG" = "latest" ] && [ "$GITHUB_REF" != "refs/heads/main" ]; then - echo "Release to npm tag 'latest' must be started from the main branch." + if [ "$GITHUB_REF_TYPE" != branch ]; then + echo "Release must run from a branch." + exit 1 + fi + if [ "$PRERELEASE" = none ] && [ "$GITHUB_REF_NAME" != main ]; then + echo "Stable releases must run from main." + exit 1 + fi + if [ "$NPM_TAG" = auto ]; then + if [ "$PRERELEASE" = none ]; then + NPM_TAG=latest + else + NPM_TAG="$PRERELEASE" + fi + fi + if [[ ! "$NPM_TAG" =~ ^[a-z][a-z0-9._-]*$ ]]; then + echo "Invalid npm dist-tag: $NPM_TAG" exit 1 fi + if [ "$PRERELEASE" != none ] && [ "$NPM_TAG" = latest ]; then + echo "Prereleases cannot be published to latest." + exit 1 + fi + echo "npm_tag=$NPM_TAG" >> "$GITHUB_OUTPUT" - name: Setup Node.js and dependencies uses: ./.github/actions/setup-node-deps with: node-version: 24.x - registry-url: https://registry.npmjs.org - name: Configure git user run: | @@ -75,44 +101,102 @@ jobs: yarn test:d yarn test:package - - name: Run release script + - name: Create release commit and tag + id: version env: RELEASE_TYPE: ${{ inputs.release }} PRERELEASE: ${{ inputs.prerelease }} + YARN_ENABLE_IMMUTABLE_INSTALLS: 'false' run: | - case "$RELEASE_TYPE" in - auto) - RELEASE_SCRIPT="release" - ;; - patch) - RELEASE_SCRIPT="release:patch" - ;; - minor) - RELEASE_SCRIPT="release:minor" - ;; - major) - RELEASE_SCRIPT="release:major" - ;; - *) - echo "Unknown release type: $RELEASE_TYPE" - exit 1 - ;; - esac - - CMD=(yarn run "$RELEASE_SCRIPT") - if [ "$PRERELEASE" != "none" ]; then + PREVIOUS_VERSION=$(node -p "require('./package.json').version") + CMD=(yarn release) + if [ "$RELEASE_TYPE" != auto ]; then + CMD+=(--release-as "$RELEASE_TYPE") + fi + if [ "$PRERELEASE" != none ]; then CMD+=(--prerelease "$PRERELEASE") fi - "${CMD[@]}" - - name: Build package - run: yarn build + VERSION=$(node -p "require('./package.json').version") + if [ "$VERSION" = "$PREVIOUS_VERSION" ]; then + echo "No new release was created." + exit 1 + fi + TAG="v$VERSION" + test "$(git rev-parse "refs/tags/$TAG^{commit}")" = "$(git rev-parse HEAD)" + echo "tag=$TAG" >> "$GITHUB_OUTPUT" + + - name: Push release commit and tag + run: git push --atomic origin "HEAD:$GITHUB_REF_NAME" "refs/tags/${{ steps.version.outputs.tag }}" + + publish: + needs: release + runs-on: ubuntu-24.04 + timeout-minutes: 15 + permissions: + contents: read + id-token: write + + steps: + - name: Checkout release tag + uses: actions/checkout@v6 + with: + ref: refs/tags/${{ needs.release.outputs.tag }} + persist-credentials: false + + - name: Setup Node.js and dependencies + uses: ./.github/actions/setup-node-deps + with: + node-version: 24.x + registry-url: https://registry.npmjs.org + + - name: Verify npm supports trusted publishing + run: | + node -e "const [major, minor, patch] = process.argv[1].split('.').map(Number); if (major < 11 || (major === 11 && (minor < 5 || (minor === 5 && patch < 1)))) process.exit(1)" "$(npm --version)" + + - name: Check released package + run: | + yarn lint + yarn typecheck + yarn test + yarn test:d + yarn test:package + + - name: Check published version + id: published + run: | + NAME=$(node -p "require('./package.json').name") + VERSION=$(node -p "require('./package.json').version") + npm view "$NAME" versions --json > "$RUNNER_TEMP/published-versions.json" + PUBLISHED=$(node -e "const fs = require('node:fs'); const versions = JSON.parse(fs.readFileSync(process.argv[1], 'utf8')); console.log([].concat(versions).includes(process.argv[2]))" "$RUNNER_TEMP/published-versions.json" "$VERSION") + echo "published=$PUBLISHED" >> "$GITHUB_OUTPUT" - - name: Push release commit and tags - run: git push origin "HEAD:${GITHUB_REF_NAME}" --follow-tags + - name: Publish to npm with trusted publishing + if: steps.published.outputs.published == 'false' + env: + NPM_TAG: ${{ needs.release.outputs.npm_tag }} + run: | + unset NODE_AUTH_TOKEN NPM_TOKEN + npm publish --access public --tag "$NPM_TAG" - - name: Publish to npm + github-release: + needs: [release, publish] + runs-on: ubuntu-24.04 + permissions: + contents: write + steps: + - name: Create GitHub Release env: - NPM_TAG: ${{ inputs.npm_tag }} - run: npm publish --access public --tag "$NPM_TAG" + GH_TOKEN: ${{ github.token }} + TAG: ${{ needs.release.outputs.tag }} + PRERELEASE: ${{ inputs.prerelease }} + run: | + if gh release view "$TAG" --repo "$GITHUB_REPOSITORY" > /dev/null 2>&1; then + exit 0 + fi + CMD=(gh release create "$TAG" --repo "$GITHUB_REPOSITORY" --verify-tag --generate-notes --title "$TAG") + if [ "$PRERELEASE" != none ]; then + CMD+=(--prerelease --latest=false) + fi + "${CMD[@]}" diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 4a1f548..bc91ea7 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -16,7 +16,7 @@ jobs: steps: - name: Using branch ${{ github.ref }} for repository ${{ github.repository }}. - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Run actionlint run: make actionlint @@ -30,7 +30,7 @@ jobs: steps: - name: Using branch ${{ github.ref }} for repository ${{ github.repository }}. - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Setup Node.js and dependencies uses: ./.github/actions/setup-node-deps @@ -51,13 +51,16 @@ jobs: steps: - name: Using branch ${{ github.ref }} for repository ${{ github.repository }}. - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Setup Node.js and dependencies uses: ./.github/actions/setup-node-deps with: node-version: ${{ matrix.node-version }} + - name: Check TypeScript + run: yarn typecheck + - name: Run tests run: yarn test:coverage @@ -68,7 +71,7 @@ jobs: run: yarn test:package - name: Upload coverage to GitHub Artifacts - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v6 with: name: coverage-${{ matrix.node-version }} path: coverage/ diff --git a/README.md b/README.md index bada69b..f191d71 100644 --- a/README.md +++ b/README.md @@ -374,3 +374,5 @@ Two additional guides are useful when you want faster practical navigation inste ## Translations - [Russian](./docs/ru/README.md) + +Release workflow and npm OIDC setup: [Releasing](docs/RELEASING.md). diff --git a/docs/RELEASING.md b/docs/RELEASING.md new file mode 100644 index 0000000..9c2d03f --- /dev/null +++ b/docs/RELEASING.md @@ -0,0 +1,66 @@ +# Releasing @modulify/validator + +Use Node.js 22.14+ or 24+ and the Yarn version pinned in `package.json`. +Install dependencies with `yarn install --immutable`. + +The repository uses `@modulify/conventional-release` to calculate the version, +update `package.json` and `CHANGELOG.md`, and create a release commit with an +annotated `v` tag. npm publication and GitHub Releases belong to CI. + +Preview a release without changing files, commits, or tags: + +```sh +yarn release:dry +yarn release:dry --prerelease rc +yarn release:dry --release-as minor +``` + +Automatic version recommendations below 1.0 follow pre-major semantics: +breaking changes advance the minor version; features and fixes advance the patch. +Explicit `--release-as major` produces 1.0.0. Release configuration lives in +`release.config.mjs`; dependencies are installed before releasing, so the version +bump does not rerun installation or update the lockfile. + +## GitHub Actions + +Start the `Release` workflow manually: + +- `release`: `auto`, `patch`, `minor`, or `major`; +- `prerelease`: `none`, `alpha`, `beta`, or `rc`; +- `npm_tag`: `auto` selects `latest` for stable releases and the prerelease channel + otherwise; an explicit dist-tag can override it. + +Stable releases run only from `main`. Prereleases may run from other branches, +but cannot publish to `latest`. Releases are serialized across branches. + +CI runs lint, strict type checks, runtime and type tests, and packed consumer +checks before creating the release. It atomically pushes the release commit and +its tag. A separate job checks out that exact tag, repeats the checks, builds the +package, and publishes through npm OIDC. A GitHub Release is created after npm +publication succeeds; prereleases are marked accordingly. + +If publication or GitHub Release creation fails after the tag was pushed, use +GitHub Actions **Re-run failed jobs** on the original run. Publication checks the +registry for the exact version and skips a version already published. Registry +lookup errors stop the job. Starting a new workflow run prepares another release. + +## npm Trusted Publisher + +The package already has this trusted publisher configured (verified 2026-09-26): + +| Field | Value | +| --- | --- | +| Provider | GitHub Actions | +| Organization | `modulify` | +| Repository | `validator` | +| Workflow filename | `release.yml` | +| Allowed actions | `npm publish`, `npm stage publish` | + +Keep the workflow filename aligned with this package-level configuration. +The publishing job uses a GitHub-hosted runner, Node.js 24, npm 11.5.1 or newer, +and `id-token: write`. No `NPM_TOKEN` or `NODE_AUTH_TOKEN` secret is used for +publication. npm generates provenance automatically for this public package +and repository. See [npm Trusted Publishing](https://docs.npmjs.com/trusted-publishers/). + +A local authenticated npm session is separate from CI OIDC. Release previews +and repository-local versioning do not require npm authentication. From 80c6514ec0a916127bf2af43102d9da1dc08ecb0 Mon Sep 17 00:00:00 2001 From: Zaitsev Kirill Date: Sat, 26 Sep 2026 13:36:21 +0400 Subject: [PATCH 14/15] chore: Local skills were moved to .agents/skills --- {skills => .agents/skills}/commit-workflow/SKILL.md | 0 {skills => .agents/skills}/coverage-recovery/SKILL.md | 0 .../skills}/yarn-lock-conflict-resolution/SKILL.md | 0 AGENTS.md | 8 ++++---- 4 files changed, 4 insertions(+), 4 deletions(-) rename {skills => .agents/skills}/commit-workflow/SKILL.md (100%) rename {skills => .agents/skills}/coverage-recovery/SKILL.md (100%) rename {skills => .agents/skills}/yarn-lock-conflict-resolution/SKILL.md (100%) diff --git a/skills/commit-workflow/SKILL.md b/.agents/skills/commit-workflow/SKILL.md similarity index 100% rename from skills/commit-workflow/SKILL.md rename to .agents/skills/commit-workflow/SKILL.md diff --git a/skills/coverage-recovery/SKILL.md b/.agents/skills/coverage-recovery/SKILL.md similarity index 100% rename from skills/coverage-recovery/SKILL.md rename to .agents/skills/coverage-recovery/SKILL.md diff --git a/skills/yarn-lock-conflict-resolution/SKILL.md b/.agents/skills/yarn-lock-conflict-resolution/SKILL.md similarity index 100% rename from skills/yarn-lock-conflict-resolution/SKILL.md rename to .agents/skills/yarn-lock-conflict-resolution/SKILL.md diff --git a/AGENTS.md b/AGENTS.md index 39eb7fc..8a18a66 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -73,10 +73,10 @@ yarn build ## Important Project Rules - Commit messages follow Conventional Commits. -- Before creating any commit, always reread `skills/commit-workflow/SKILL.md` and follow it as the source of truth for commit splitting, wording, scopes, and lockfile policy. +- Before creating any commit, always reread `.agents/skills/commit-workflow/SKILL.md` and follow it as the source of truth for commit splitting, wording, scopes, and lockfile policy. - Getter/helper functions must be free of side effects. Side effects are allowed only by prior agreement and only when there are strong, explicit reasons. ## Local Skills -- `skills/commit-workflow/SKILL.md` - rules for splitting changes into commits and writing changelog-friendly Conventional Commit messages. -- `skills/coverage-recovery/SKILL.md` - workflow for analyzing uncovered code paths and improving test coverage without adding artificial tests. -- `skills/yarn-lock-conflict-resolution/SKILL.md` - safe procedure for resolving `yarn.lock` conflicts during merge or rebase. +- `.agents/skills/commit-workflow/SKILL.md` - rules for splitting changes into commits and writing changelog-friendly Conventional Commit messages. +- `.agents/skills/coverage-recovery/SKILL.md` - workflow for analyzing uncovered code paths and improving test coverage without adding artificial tests. +- `.agents/skills/yarn-lock-conflict-resolution/SKILL.md` - safe procedure for resolving `yarn.lock` conflicts during merge or rebase. From f0703f7c4ee102f38c6120de8a914e15aa9542cd Mon Sep 17 00:00:00 2001 From: Zaitsev Kirill Date: Sat, 26 Sep 2026 13:36:41 +0400 Subject: [PATCH 15/15] test: Coverage was enabled by default --- .agents/skills/coverage-recovery/SKILL.md | 4 ++-- .github/workflows/tests.yml | 2 +- AGENTS.md | 6 +----- Makefile | 5 ----- package.json | 4 +--- vitest.config.ts | 1 + 6 files changed, 6 insertions(+), 16 deletions(-) diff --git a/.agents/skills/coverage-recovery/SKILL.md b/.agents/skills/coverage-recovery/SKILL.md index 9130be1..926f9fe 100644 --- a/.agents/skills/coverage-recovery/SKILL.md +++ b/.agents/skills/coverage-recovery/SKILL.md @@ -28,7 +28,7 @@ Use this skill when the user asks to: ## Workflow 1. Collect facts: ```bash -yarn test:coverage +yarn test ``` 2. Read uncovered details, not only percentages. Use: @@ -48,7 +48,7 @@ Use: ```bash yarn lint yarn typecheck -yarn test:coverage +yarn test ``` ## Controlled Failure Patterns diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index bc91ea7..ad9f341 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -62,7 +62,7 @@ jobs: run: yarn typecheck - name: Run tests - run: yarn test:coverage + run: yarn test - name: Run type tests run: yarn test:d diff --git a/AGENTS.md b/AGENTS.md index 8a18a66..ad79e96 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -50,14 +50,10 @@ yarn lint ```bash yarn typecheck ``` -- Test: +- Test (coverage is enabled by default; reports are written to `coverage/`): ```bash yarn test ``` -- Coverage: -```bash -yarn test:coverage -``` - Build: ```bash yarn build diff --git a/Makefile b/Makefile index bae922a..b9d14ec 100644 --- a/Makefile +++ b/Makefile @@ -32,11 +32,6 @@ test: node_modules ## Runs autotests $(TARGET_HEADER) $(YARN) test -.PHONY: test-coverage -test-coverage: node_modules ## Runs autotests with --coverage - $(TARGET_HEADER) - $(YARN) test:coverage - .PHONY: release release: ## Bumps version and creates tag $(TARGET_HEADER) diff --git a/package.json b/package.json index 3ef7457..bf52b02 100644 --- a/package.json +++ b/package.json @@ -90,9 +90,7 @@ "release:major": "conventional-release --tags --release-as major", "stats": "gzip -c ./dist/index.mjs | wc -c", "test": "vitest run", - "test:d": "vitest run --typecheck.only", - "test:coverage": "vitest run --coverage", - "test:coverage:html": "vitest run --coverage --reporter=html --outputFile.html=./reports/html/report.html", + "test:d": "vitest run --typecheck.only --coverage.enabled=false", "test:package": "yarn build && node scripts/check-package.mjs", "typecheck": "tsc --noEmit" }, diff --git a/vitest.config.ts b/vitest.config.ts index 1297d8f..04e2db8 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -11,6 +11,7 @@ export default mergeConfig(basic, defineConfig({ '**/*.{test,spec}.?(c|m)[jt]s?(x)', ], coverage: { + enabled: true, provider: 'v8', include: ['src/**'], reporter: [