@scalar/validation
Advanced tools
+20
-0
| # @scalar/validation | ||
| ## 0.6.0 | ||
| ### Minor Changes | ||
| - [#9262](https://github.com/scalar/scalar/pull/9262): feat(validation): support recursive schemas in validate and coerce | ||
| Add recursive schema support for `validate` and `coerce`: both functions now track visited object–schema pairs so cyclic values (self-referential nodes, mutual `lazy` graphs, cyclic records) terminate instead of overflowing the stack. Nested `intersection` members are validated and coerced recursively, and union branch scoring prefers property-less object schemas over primitives for empty objects. | ||
| Improve `Static` inference for circular schemas via `LazyStatic` and tuple-folding union statics; `union` and `intersection` use lightweight `UnionMember` / `IntersectionMember` constraints so `lazy(() => self)` call sites type-check without hitting depth limits. `coerce` returns `SafeStatic<S>` (precise `Static<S>` for concrete schemas, `any` when `S` is the full `Schema` union). Export `UnionMember` and `IntersectionMember` from the package entry point. | ||
| ### Patch Changes | ||
| - [#9262](https://github.com/scalar/scalar/pull/9262): fix(validation): detect cycles when scoring union branches | ||
| `scoreUnion` (used by `coerce` to pick the best matching `union` branch) now tracks the `(value, schema)` pairs that are live on its call stack and returns a neutral positive score on re-entry. A recursive lazy schema such as `lazy(() => union([object({ child: optional(lazy(() => T)) }), …]))` evaluated against a self-referential value previously caused `scoreUnion` to recurse through `lazy → union → object → property → lazy → …` indefinitely and overflow the stack, contradicting the rest of the cycle-handling work. The marker is cleared in `finally` so sibling union branches that share a schema reference are scored independently. | ||
| - [#9262](https://github.com/scalar/scalar/pull/9262): fix(validation): do not leak cycle-detection cache across union branches | ||
| Scope the in-progress `(value, schema)` cache used by `validate` to the live call stack instead of treating it as run-wide memoization. The marker for each pair is now cleared before the call returns, so a shared schema reference that failed in one `union` branch (for example the common `base` in `union([intersection([base, objA]), intersection([base, objB])])`) is re-validated in the next branch rather than short-circuiting to `true` from a stale entry. Cycle detection on self-referential and mutually recursive lazy graphs is unaffected because the marker is still present during recursive descent into the same value. | ||
| ## 0.5.0 | ||
@@ -4,0 +24,0 @@ |
+28
-1
| import type { Schema } from './schema.js'; | ||
| import type { Static } from './types.js'; | ||
| /** | ||
| * Memoizes `schema.schema()` per lazy schema so that recursive definitions | ||
| * such as `lazy(() => object({ child: lazy(() => T) }))` resolve to the same | ||
| * inner schema reference across calls. Without this, every traversal would | ||
| * synthesize a fresh inner schema, defeating the `(value, schema)` cycle | ||
| * cache and producing infinite recursion on self-referential values. | ||
| * | ||
| * The cache is supplied by the top-level `coerce` call so it never leaks | ||
| * resolved schemas between unrelated invocations. | ||
| */ | ||
| type LazyCache = WeakMap<object, Schema>; | ||
| /** | ||
| * Falls back to `any` when `S` widens all the way to the full `Schema` union and | ||
| * returns the precise `Static<S>` otherwise. Computing `Static<Schema>` forces | ||
| * TypeScript to expand every variant of the recursive `Schema` definition and | ||
| * exhausts the depth limit, surfacing at call sites as | ||
| * `TS2589: Type instantiation is excessively deep and possibly infinite`. | ||
| * Degrading to `any` in that single case keeps the type tractable; callers that | ||
| * pass a specific schema (for example an `intersection(...)` literal) still get | ||
| * the precise static type. | ||
| * | ||
| * `[Schema] extends [S]` is wrapped in tuples to prevent distribution over union | ||
| * members — we want a single check that the whole `Schema` union is assignable | ||
| * to `S`, not a check that runs once per variant. | ||
| */ | ||
| type SafeStatic<S extends Schema> = [Schema] extends [S] ? any : Static<S>; | ||
| /** | ||
| * Coerces an unknown value toward the static type implied by `schema`. Values that | ||
@@ -21,3 +47,4 @@ * pass {@link validate} for that branch are kept; otherwise primitives default to | ||
| */ | ||
| export declare const coerce: <S extends Schema>(schema: S, value: unknown, cache?: WeakMap<object, Set<Schema>>) => Static<S>; | ||
| export declare const coerce: <S extends Schema>(schema: S, value: unknown, cache?: WeakMap<object, Map<Schema, unknown>>, lazyCache?: LazyCache) => SafeStatic<S>; | ||
| export {}; | ||
| //# sourceMappingURL=coerce.d.ts.map |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"coerce.d.ts","sourceRoot":"","sources":["../src/coerce.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,UAAU,CAAA;AACtC,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,SAAS,CAAA;AA0FrC;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,MAAM,GAAI,CAAC,SAAS,MAAM,EACrC,QAAQ,CAAC,EACT,OAAO,OAAO,EACd,QAAO,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,MAAM,CAAC,CAAiB,KAClD,MAAM,CAAC,CAAC,CAmHV,CAAA"} | ||
| {"version":3,"file":"coerce.d.ts","sourceRoot":"","sources":["../src/coerce.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,UAAU,CAAA;AACtC,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,SAAS,CAAA;AAGrC;;;;;;;;;GASG;AACH,KAAK,SAAS,GAAG,OAAO,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;AA+SxC;;;;;;;;;;;;;GAaG;AACH,KAAK,UAAU,CAAC,CAAC,SAAS,MAAM,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,GAAG,GAAG,GAAG,MAAM,CAAC,CAAC,CAAC,CAAA;AAE1E;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,MAAM,GAAI,CAAC,SAAS,MAAM,EACrC,QAAQ,CAAC,EACT,OAAO,OAAO,EACd,QAAO,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,CAAiB,EAC5D,YAAW,SAAyB,KACnC,UAAU,CAAC,CAAC,CAAkE,CAAA"} |
+172
-92
| import { isObject } from './helpers/is-object.js'; | ||
| import { validate } from './validate.js'; | ||
| const resolveLazy = (schema, lazyCache) => { | ||
| const cached = lazyCache.get(schema); | ||
| if (cached) { | ||
| return cached; | ||
| } | ||
| const resolved = schema.schema(); | ||
| lazyCache.set(schema, resolved); | ||
| return resolved; | ||
| }; | ||
| /** | ||
@@ -27,88 +36,123 @@ * True when this property schema is only used to discriminate union branches | ||
| * arrays/records by structural type; primitives by validation; unions try all branches. | ||
| * | ||
| * The `scoringCache` tracks `(value, schema)` pairs that are currently being | ||
| * scored higher up the call stack. Without it, a recursive lazy schema such as | ||
| * `lazy(() => union([object({ child: optional(lazy(() => T)) }), …]))` scored | ||
| * against a self-referential value would recurse forever through | ||
| * `lazy → union → object → property → lazy → …` and overflow the stack. | ||
| * | ||
| * On re-entry of a pair we return `1` rather than `0` — a neutral positive | ||
| * score consistent with `validateInner` short-circuiting cycles to `true`. | ||
| * Markers are removed in `finally` so sibling union branches that share a | ||
| * schema reference are scored independently rather than inheriting a stale | ||
| * "in cycle" marker. | ||
| */ | ||
| const scoreUnion = (schema, value) => { | ||
| if (schema.type === 'object') { | ||
| if (!isObject(value)) { | ||
| return 0; | ||
| const scoreUnion = (schema, value, lazyCache, scoringCache = new WeakMap()) => { | ||
| // Short-circuit on cycles: this exact (value, schema) pair is already being | ||
| // scored higher up the call stack. The enclosing call's score subsumes any | ||
| // contribution we could compute here, so return a neutral positive score. | ||
| if (isObject(value) && scoringCache.get(value)?.has(schema)) { | ||
| return 1; | ||
| } | ||
| const trackable = isObject(value); | ||
| if (trackable) { | ||
| const schemas = scoringCache.get(value) ?? new Set(); | ||
| schemas.add(schema); | ||
| scoringCache.set(value, schemas); | ||
| } | ||
| try { | ||
| if (schema.type === 'object') { | ||
| if (!isObject(value)) { | ||
| return 0; | ||
| } | ||
| const keys = Object.keys(schema.properties); | ||
| // If there are no properties, we want to score 1 since we want to outscore if there are inline primitives | ||
| if (keys.length === 0) { | ||
| return 1; | ||
| } | ||
| // Missing keys contribute 0 (including optional keys — matches prior union heuristics). | ||
| // Discriminator properties (`literal` or `union` of literals): recurse with scoreUnion; | ||
| // matching values get a high weight (×10) so `type: literal('A')` beats unrelated fields | ||
| // on another branch; mismatches score 0 (no "key present" tie-break). | ||
| // Other properties: scoreUnion plus +1 when the value fails validation so `{ a: null }` | ||
| // can still prefer the branch that declares `a`. | ||
| return keys.reduce((acc, key) => { | ||
| if (!(key in value)) { | ||
| return acc; | ||
| } | ||
| const propSchema = schema.properties[key]; | ||
| const raw = value[key]; | ||
| const base = scoreUnion(propSchema, raw, lazyCache, scoringCache); | ||
| if (isDiscriminatorProperty(propSchema)) { | ||
| return acc + (base > 0 ? base * 10 : 0); | ||
| } | ||
| return acc + (base > 0 ? base : 1); | ||
| }, 0); | ||
| } | ||
| // Missing keys contribute 0 (including optional keys — matches prior union heuristics). | ||
| // Discriminator properties (`literal` or `union` of literals): recurse with scoreUnion; | ||
| // matching values get a high weight (×10) so `type: literal('A')` beats unrelated fields | ||
| // on another branch; mismatches score 0 (no "key present" tie-break). | ||
| // Other properties: scoreUnion plus +1 when the value fails validation so `{ a: null }` | ||
| // can still prefer the branch that declares `a`. | ||
| return Object.keys(schema.properties).reduce((acc, key) => { | ||
| if (!(key in value)) { | ||
| return acc; | ||
| if (schema.type === 'array') { | ||
| // Score 1 if value is an array, otherwise 0 | ||
| return Array.isArray(value) ? 1 : 0; | ||
| } | ||
| if (schema.type === 'record') { | ||
| // TODO: implement smarter scoring for records (just a placeholder for now) | ||
| return isObject(value) ? 1 : 0; | ||
| } | ||
| if (schema.type === 'optional') { | ||
| return value === undefined ? 1 : scoreUnion(schema.schema, value, lazyCache, scoringCache); | ||
| } | ||
| if (schema.type === 'union') { | ||
| // For a union, use the highest score among all sub-schemas | ||
| return Math.max(...schema.schemas.map((branch) => scoreUnion(branch, value, lazyCache, scoringCache))); | ||
| } | ||
| if (schema.type === 'intersection') { | ||
| if (schema.schemas.length === 0) { | ||
| return 1; | ||
| } | ||
| const propSchema = schema.properties[key]; | ||
| const raw = value[key]; | ||
| const base = scoreUnion(propSchema, raw); | ||
| if (isDiscriminatorProperty(propSchema)) { | ||
| return acc + (base > 0 ? base * 10 : 0); | ||
| } | ||
| return acc + (base > 0 ? base : 1); | ||
| }, 0); | ||
| return schema.schemas.reduce((acc, sub) => acc + scoreUnion(sub, value, lazyCache, scoringCache), 0); | ||
| } | ||
| if (schema.type === 'lazy') { | ||
| // For a lazy schema, evaluate the inner schema and recurse | ||
| return scoreUnion(resolveLazy(schema, lazyCache), value, lazyCache, scoringCache); | ||
| } | ||
| if (schema.type === 'evaluate') { | ||
| // For an evaluate schema, evaluate the expression and recurse | ||
| return scoreUnion(schema.schema, schema.expression(value), lazyCache, scoringCache); | ||
| } | ||
| // For primitives and any other type, return 1 if valid, otherwise 0 | ||
| return validate(schema, value) ? 1 : 0; | ||
| } | ||
| if (schema.type === 'array') { | ||
| // Score 1 if value is an array, otherwise 0 | ||
| return Array.isArray(value) ? 1 : 0; | ||
| } | ||
| if (schema.type === 'record') { | ||
| // TODO: implement smarter scoring for records (just a placeholder for now) | ||
| return isObject(value) ? 1 : 0; | ||
| } | ||
| if (schema.type === 'optional') { | ||
| return value === undefined ? 1 : scoreUnion(schema.schema, value); | ||
| } | ||
| if (schema.type === 'union') { | ||
| // For a union, use the highest score among all sub-schemas | ||
| return Math.max(...schema.schemas.map((schema) => scoreUnion(schema, value))); | ||
| } | ||
| if (schema.type === 'intersection') { | ||
| if (schema.schemas.length === 0) { | ||
| return 1; | ||
| finally { | ||
| // Clear the in-progress marker so sibling union branches that reference | ||
| // the same schema are scored independently rather than short-circuiting | ||
| // to the cycle-neutral score. | ||
| if (trackable) { | ||
| scoringCache.get(value)?.delete(schema); | ||
| } | ||
| return schema.schemas.reduce((acc, sub) => acc + scoreUnion(sub, value), 0); | ||
| } | ||
| if (schema.type === 'lazy') { | ||
| // For a lazy schema, evaluate the inner schema and recurse | ||
| return scoreUnion(schema.schema(), value); | ||
| }; | ||
| /** | ||
| * Records the in-progress `result` for a given `(value, schema)` pair so that | ||
| * recursive calls hitting the same pair return the already-allocated result | ||
| * instead of recursing forever. Plain objects and arrays are both tracked; | ||
| * other values cannot form cycles and are ignored. | ||
| */ | ||
| const trackCycle = (value, schema, result, cache) => { | ||
| if (isObject(value) || Array.isArray(value)) { | ||
| const schemas = cache.get(value) || new Map(); | ||
| schemas.set(schema, result); | ||
| cache.set(value, schemas); | ||
| } | ||
| if (schema.type === 'evaluate') { | ||
| // For an evaluate schema, evaluate the expression and recurse | ||
| return scoreUnion(schema.schema, schema.expression(value)); | ||
| } | ||
| // For primitives and any other type, return 1 if valid, otherwise 0 | ||
| return validate(schema, value) ? 1 : 0; | ||
| }; | ||
| /** | ||
| * Coerces an unknown value toward the static type implied by `schema`. Values that | ||
| * pass {@link validate} for that branch are kept; otherwise primitives default to | ||
| * `0`, `''`, or `false`, and arrays, records, and objects are built recursively. | ||
| * Unions pick the best-matching branch; `evaluate` runs `expression` before the inner schema. | ||
| * | ||
| * @example | ||
| * ```ts | ||
| * import { coerce, number, object, string } from '@scalar/validation' | ||
| * | ||
| * coerce(number(), 42) // 42 | ||
| * coerce(number(), 'nope') // 0 — invalid number uses default | ||
| * coerce(object({ id: number(), name: string() }), { id: '1', name: 'Ada' }) // { id: 0, name: 'Ada' } | ||
| * ``` | ||
| * | ||
| * The optional `cache` argument tracks visited object–schema pairs to stop infinite recursion | ||
| * on cyclic graphs; callers normally omit it. | ||
| * Internal coercion implementation. Takes the wide `Schema` union and returns `unknown` so that | ||
| * recursive calls do not pay the cost of relating two generic `Static<S>` instantiations, which | ||
| * can overflow the type checker now that `LazyStatic` resolves recursive schemas without a depth | ||
| * cap. The public `coerce` wrapper preserves the typed surface. | ||
| */ | ||
| export const coerce = (schema, value, cache = new WeakMap()) => { | ||
| // Prevent infinite recursion | ||
| if (isObject(value) && cache.get(value)?.has(schema)) { | ||
| return value; | ||
| const coerceInner = (schema, value, cache, lazyCache) => { | ||
| // Prevent infinite recursion by returning the in-progress result that was | ||
| // staged by an enclosing call via trackCycle. | ||
| if ((isObject(value) || Array.isArray(value)) && cache.get(value)?.has(schema)) { | ||
| return cache.get(value)?.get(schema); | ||
| } | ||
| // Track visited schemas to prevent infinite recursion | ||
| if (isObject(value)) { | ||
| const schemas = cache.get(value) || new Set(); | ||
| schemas.add(schema); | ||
| cache.set(value, schemas); | ||
| } | ||
| // If no schema is provided, return the value as is | ||
@@ -125,3 +169,3 @@ if (!schema) { | ||
| } | ||
| return (() => undefined); | ||
| return () => undefined; | ||
| } | ||
@@ -132,3 +176,3 @@ if (schema.type === 'number') { | ||
| } | ||
| return (schema.default ?? 0); | ||
| return schema.default ?? 0; | ||
| } | ||
@@ -139,3 +183,3 @@ if (schema.type === 'string') { | ||
| } | ||
| return (schema.default ?? ''); | ||
| return schema.default ?? ''; | ||
| } | ||
@@ -146,3 +190,3 @@ if (schema.type === 'boolean') { | ||
| } | ||
| return (schema.default ?? false); | ||
| return schema.default ?? false; | ||
| } | ||
@@ -159,3 +203,3 @@ if (schema.type === 'nullable') { | ||
| } | ||
| return coerce(schema.schema, value, cache); | ||
| return coerceInner(schema.schema, value, cache, lazyCache); | ||
| } | ||
@@ -166,3 +210,10 @@ if (schema.type === 'array') { | ||
| } | ||
| return value.map((item) => coerce(schema.items, item, cache)); | ||
| // Pre-allocate so a self-referential array can be cached before we | ||
| // recurse into its items, breaking otherwise-infinite cycles. | ||
| const result = new Array(value.length); | ||
| trackCycle(value, schema, result, cache); | ||
| for (let i = 0; i < value.length; i++) { | ||
| result[i] = coerceInner(schema.items, value[i], cache, lazyCache); | ||
| } | ||
| return result; | ||
| } | ||
@@ -173,3 +224,10 @@ if (schema.type === 'record') { | ||
| } | ||
| return Object.fromEntries(Object.entries(value).map(([key, value]) => [key, coerce(schema.value, value, cache)])); | ||
| // Pre-allocate so a self-referential record can be cached before we | ||
| // recurse into its entries, breaking otherwise-infinite cycles. | ||
| const result = {}; | ||
| trackCycle(value, schema, result, cache); | ||
| for (const key of Object.keys(value)) { | ||
| result[key] = coerceInner(schema.value, value[key], cache, lazyCache); | ||
| } | ||
| return result; | ||
| } | ||
@@ -179,3 +237,6 @@ if (schema.type === 'object') { | ||
| const target = isObject(value) ? value : null; | ||
| const entries = []; | ||
| // Pre-allocate so a self-referential object can be cached before we | ||
| // recurse into its properties, breaking otherwise-infinite cycles. | ||
| const result = {}; | ||
| trackCycle(value, schema, result, cache); | ||
| for (const key of keys) { | ||
@@ -187,16 +248,16 @@ const propSchema = schema.properties[key]; | ||
| } | ||
| entries.push([key, coerce(propSchema, raw, cache)]); | ||
| result[key] = coerceInner(propSchema, raw, cache, lazyCache); | ||
| } | ||
| return Object.fromEntries(entries); | ||
| return result; | ||
| } | ||
| if (schema.type === 'union') { | ||
| const branch = schema.schemas.reduce((acc, schema) => { | ||
| const score = scoreUnion(schema, value); | ||
| return score > acc.score ? { schema, score } : acc; | ||
| const branch = schema.schemas.reduce((acc, branchSchema) => { | ||
| const score = scoreUnion(branchSchema, value, lazyCache); | ||
| return score > acc.score ? { schema: branchSchema, score } : acc; | ||
| }, { schema: schema.schemas[0], score: 0 }); | ||
| // We need some way to pick one of the union values | ||
| return coerce(branch.schema, value, cache); | ||
| return coerceInner(branch.schema, value, cache, lazyCache); | ||
| } | ||
| if (schema.type === 'intersection') { | ||
| return schema.schemas.reduce((acc, subSchema) => Object.assign(acc, coerce(subSchema, value, cache)), {}); | ||
| return schema.schemas.reduce((acc, subSchema) => Object.assign(acc, coerceInner(subSchema, value, cache, lazyCache)), {}); | ||
| } | ||
@@ -207,6 +268,6 @@ if (schema.type === 'literal') { | ||
| if (schema.type === 'lazy') { | ||
| return coerce(schema.schema(), value, cache); | ||
| return coerceInner(resolveLazy(schema, lazyCache), value, cache, lazyCache); | ||
| } | ||
| if (schema.type === 'evaluate') { | ||
| return coerce(schema.schema, schema.expression(value), cache); | ||
| return coerceInner(schema.schema, schema.expression(value), cache, lazyCache); | ||
| } | ||
@@ -218,1 +279,20 @@ // We need to assert here that schema has the type never so we know we handle all cases | ||
| }; | ||
| /** | ||
| * Coerces an unknown value toward the static type implied by `schema`. Values that | ||
| * pass {@link validate} for that branch are kept; otherwise primitives default to | ||
| * `0`, `''`, or `false`, and arrays, records, and objects are built recursively. | ||
| * Unions pick the best-matching branch; `evaluate` runs `expression` before the inner schema. | ||
| * | ||
| * @example | ||
| * ```ts | ||
| * import { coerce, number, object, string } from '@scalar/validation' | ||
| * | ||
| * coerce(number(), 42) // 42 | ||
| * coerce(number(), 'nope') // 0 — invalid number uses default | ||
| * coerce(object({ id: number(), name: string() }), { id: '1', name: 'Ada' }) // { id: 0, name: 'Ada' } | ||
| * ``` | ||
| * | ||
| * The optional `cache` argument tracks visited object–schema pairs to stop infinite recursion | ||
| * on cyclic graphs; callers normally omit it. | ||
| */ | ||
| export const coerce = (schema, value, cache = new WeakMap(), lazyCache = new WeakMap()) => coerceInner(schema, value, cache, lazyCache); |
+1
-1
| export { coerce } from './coerce.js'; | ||
| export { type AnySchema, type ArraySchema, type BooleanSchema, type EvaluateSchema, type IntersectionSchema, type LazySchema, type LiteralSchema, type NotDefinedSchema, type NullableSchema, type NumberSchema, type ObjectSchema, type OptionalSchema, type RecordSchema, type Schema, type StringSchema, type UnionSchema, type UnknownSchema, type FunctionSchema, any, array, boolean, evaluate, fn, intersection, lazy, literal, notDefined, nullable, number, object, optional, record, string, union, unknown, } from './schema.js'; | ||
| export { type AnySchema, type ArraySchema, type BooleanSchema, type EvaluateSchema, type FunctionSchema, type IntersectionMember, type IntersectionSchema, type LazySchema, type LiteralSchema, type NotDefinedSchema, type NullableSchema, type NumberSchema, type ObjectSchema, type OptionalSchema, type RecordSchema, type Schema, type StringSchema, type UnionMember, type UnionSchema, type UnknownSchema, any, array, boolean, evaluate, fn, intersection, lazy, literal, notDefined, nullable, number, object, optional, record, string, union, unknown, } from './schema.js'; | ||
| export { type GenerateTypesOptions, generateTypes } from './typegen.js'; | ||
@@ -4,0 +4,0 @@ export type { Static } from './types.js'; |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAA;AACjC,OAAO,EACL,KAAK,SAAS,EACd,KAAK,WAAW,EAChB,KAAK,aAAa,EAClB,KAAK,cAAc,EACnB,KAAK,kBAAkB,EACvB,KAAK,UAAU,EACf,KAAK,aAAa,EAClB,KAAK,gBAAgB,EACrB,KAAK,cAAc,EACnB,KAAK,YAAY,EACjB,KAAK,YAAY,EACjB,KAAK,cAAc,EACnB,KAAK,YAAY,EACjB,KAAK,MAAM,EACX,KAAK,YAAY,EACjB,KAAK,WAAW,EAChB,KAAK,aAAa,EAClB,KAAK,cAAc,EACnB,GAAG,EACH,KAAK,EACL,OAAO,EACP,QAAQ,EACR,EAAE,EACF,YAAY,EACZ,IAAI,EACJ,OAAO,EACP,UAAU,EACV,QAAQ,EACR,MAAM,EACN,MAAM,EACN,QAAQ,EACR,MAAM,EACN,MAAM,EACN,KAAK,EACL,OAAO,GACR,MAAM,UAAU,CAAA;AACjB,OAAO,EAAE,KAAK,oBAAoB,EAAE,aAAa,EAAE,MAAM,WAAW,CAAA;AACpE,YAAY,EAAE,MAAM,EAAE,MAAM,SAAS,CAAA;AACrC,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAA"} | ||
| {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAA;AACjC,OAAO,EACL,KAAK,SAAS,EACd,KAAK,WAAW,EAChB,KAAK,aAAa,EAClB,KAAK,cAAc,EACnB,KAAK,cAAc,EACnB,KAAK,kBAAkB,EACvB,KAAK,kBAAkB,EACvB,KAAK,UAAU,EACf,KAAK,aAAa,EAClB,KAAK,gBAAgB,EACrB,KAAK,cAAc,EACnB,KAAK,YAAY,EACjB,KAAK,YAAY,EACjB,KAAK,cAAc,EACnB,KAAK,YAAY,EACjB,KAAK,MAAM,EACX,KAAK,YAAY,EACjB,KAAK,WAAW,EAChB,KAAK,WAAW,EAChB,KAAK,aAAa,EAClB,GAAG,EACH,KAAK,EACL,OAAO,EACP,QAAQ,EACR,EAAE,EACF,YAAY,EACZ,IAAI,EACJ,OAAO,EACP,UAAU,EACV,QAAQ,EACR,MAAM,EACN,MAAM,EACN,QAAQ,EACR,MAAM,EACN,MAAM,EACN,KAAK,EACL,OAAO,GACR,MAAM,UAAU,CAAA;AACjB,OAAO,EAAE,KAAK,oBAAoB,EAAE,aAAa,EAAE,MAAM,WAAW,CAAA;AACpE,YAAY,EAAE,MAAM,EAAE,MAAM,SAAS,CAAA;AACrC,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAA"} |
+79
-7
@@ -70,3 +70,3 @@ /** | ||
| /** Schema that matches if any member schema matches (discriminated union when literals or object tags differ). */ | ||
| export type UnionSchema<Schemas extends Schema[]> = { | ||
| export type UnionSchema<Schemas extends readonly Schema[]> = { | ||
| type: 'union'; | ||
@@ -76,2 +76,44 @@ schemas: Schemas; | ||
| /** | ||
| * Members that may appear inside a `union([...])`. | ||
| * | ||
| * Discriminant-only for the same reason as {@link IntersectionMember}: a full `Schema` constraint | ||
| * at the call site forces TypeScript to eagerly evaluate tuple elements and breaks circular | ||
| * inference when members use `lazy(() => self)` (for example recursive navigation trees). | ||
| */ | ||
| export type UnionMember = { | ||
| type: 'number'; | ||
| } | { | ||
| type: 'string'; | ||
| } | { | ||
| type: 'boolean'; | ||
| } | { | ||
| type: 'nullable'; | ||
| } | { | ||
| type: 'notDefined'; | ||
| } | { | ||
| type: 'any'; | ||
| } | { | ||
| type: 'unknown'; | ||
| } | { | ||
| type: 'function'; | ||
| } | { | ||
| type: 'array'; | ||
| } | { | ||
| type: 'record'; | ||
| } | { | ||
| type: 'object'; | ||
| } | { | ||
| type: 'union'; | ||
| } | { | ||
| type: 'optional'; | ||
| } | { | ||
| type: 'intersection'; | ||
| } | { | ||
| type: 'literal'; | ||
| } | { | ||
| type: 'lazy'; | ||
| } | { | ||
| type: 'evaluate'; | ||
| }; | ||
| /** | ||
| * Schema that accepts `undefined` or a value matching the inner schema. | ||
@@ -85,7 +127,25 @@ * In {@link Static} and type generation, object properties use `key?:` instead of `T | undefined`. | ||
| /** | ||
| * `UnionSchema<any>` avoids a variance pitfall: `UnionSchema<[A, B]>` is not assignable to | ||
| * `UnionSchema<ObjectSchema<any>[]>`, which breaks `infer` when resolving `Static`. | ||
| * Members that may appear inside an `intersection([...])`. | ||
| * | ||
| * This is intentionally a discriminant-only structural type rather than a union of the full | ||
| * schema types. If we use the full schema types here, the constraint check at the call site | ||
| * of `intersection` forces TypeScript to *eagerly* evaluate each tuple element, which breaks | ||
| * circular type inference when a member transitively contains a `lazy(() => self)` reference. | ||
| * | ||
| * The narrower discriminant shape only requires TypeScript to verify the `type` literal, which | ||
| * is cheap and does not trigger evaluation of nested schemas. Every `ObjectSchema`, `UnionSchema`, | ||
| * `IntersectionSchema`, and `LazySchema` is still assignable to this type because each carries | ||
| * the appropriate `type` field, so the public API and compile-time safety are preserved. | ||
| */ | ||
| export type IntersectionSchema<Schemas extends readonly (ObjectSchema<any> | UnionSchema<any>)[]> = { | ||
| export type IntersectionMember = { | ||
| type: 'object'; | ||
| } | { | ||
| type: 'union'; | ||
| } | { | ||
| type: 'intersection'; | ||
| } | { | ||
| type: 'lazy'; | ||
| }; | ||
| export type IntersectionSchema<Schemas extends readonly Schema[]> = { | ||
| type: 'intersection'; | ||
| schemas: Schemas; | ||
@@ -116,3 +176,3 @@ } & Documentation; | ||
| }; | ||
| export type Schema = NumberSchema | StringSchema | BooleanSchema | NullableSchema | NotDefinedSchema | AnySchema | UnknownSchema | FunctionSchema<any> | ArraySchema<any> | RecordSchema<any, any> | ObjectSchema<Record<string, any>> | UnionSchema<any[]> | OptionalSchema<any> | IntersectionSchema<readonly (ObjectSchema<any> | UnionSchema<ObjectSchema<any>[]>)[]> | LiteralSchema<any> | LazySchema<any> | EvaluateSchema<any>; | ||
| export type Schema = NumberSchema | StringSchema | BooleanSchema | NullableSchema | NotDefinedSchema | AnySchema | UnknownSchema | FunctionSchema<any> | ArraySchema<any> | RecordSchema<any, any> | ObjectSchema<Record<string, any>> | UnionSchema<readonly Schema[]> | OptionalSchema<any> | IntersectionSchema<readonly Schema[]> | LiteralSchema<any> | LazySchema<any> | EvaluateSchema<any>; | ||
| declare const number: (options?: Documentation & { | ||
@@ -135,4 +195,16 @@ default?: number; | ||
| declare const object: <Properties extends Record<string, Schema>>(properties: Properties, options?: Documentation) => ObjectSchema<Properties>; | ||
| declare const union: <Schemas extends Schema[]>(schemas: Schemas, options?: Documentation) => UnionSchema<Schemas>; | ||
| declare const intersection: <const Schemas extends readonly (ObjectSchema<any> | UnionSchema<ObjectSchema<any>[]>)[]>(schemas: Schemas, options?: Documentation) => IntersectionSchema<Schemas>; | ||
| /** | ||
| * The conditional return type mirrors {@link intersection}: lightweight input constraint, | ||
| * precise `UnionSchema<Schemas>` output without re-triggering eager evaluation. | ||
| */ | ||
| declare const union: <const Schemas extends readonly UnionMember[]>(schemas: Schemas, options?: Documentation) => Schemas extends readonly Schema[] ? UnionSchema<Schemas> : never; | ||
| /** | ||
| * The conditional return type is what unlocks circular `intersection([... lazy(() => self) ...])`. | ||
| * | ||
| * The input constraint is the lightweight `IntersectionMember` (discriminant-only) so the call-site | ||
| * constraint check does not force TypeScript to eagerly evaluate each tuple element. The conditional | ||
| * `Schemas extends readonly Schema[]` is always true in practice (every passed value is a real schema) | ||
| * and lets us produce a precise `IntersectionSchema<Schemas>` without re-introducing the heavy check. | ||
| */ | ||
| declare const intersection: <const Schemas extends readonly IntersectionMember[]>(schemas: Schemas, options?: Documentation) => Schemas extends readonly Schema[] ? IntersectionSchema<Schemas> : never; | ||
| declare const optional: <S extends Schema>(schema: S, options?: Documentation) => OptionalSchema<S>; | ||
@@ -139,0 +211,0 @@ declare const literal: <Value extends string | number | boolean | bigint>(value: Value) => LiteralSchema<Value>; |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"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"} | ||
| {"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"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"} |
+12
-0
@@ -63,2 +63,6 @@ const number = (options) => ({ | ||
| }); | ||
| /** | ||
| * The conditional return type mirrors {@link intersection}: lightweight input constraint, | ||
| * precise `UnionSchema<Schemas>` output without re-triggering eager evaluation. | ||
| */ | ||
| const union = (schemas, options) => ({ | ||
@@ -70,2 +74,10 @@ type: 'union', | ||
| }); | ||
| /** | ||
| * The conditional return type is what unlocks circular `intersection([... lazy(() => self) ...])`. | ||
| * | ||
| * The input constraint is the lightweight `IntersectionMember` (discriminant-only) so the call-site | ||
| * constraint check does not force TypeScript to eagerly evaluate each tuple element. The conditional | ||
| * `Schemas extends readonly Schema[]` is always true in practice (every passed value is a real schema) | ||
| * and lets us produce a precise `IntersectionSchema<Schemas>` without re-introducing the heavy check. | ||
| */ | ||
| const intersection = (schemas, options) => ({ | ||
@@ -72,0 +84,0 @@ type: 'intersection', |
+17
-1
| import type { AnySchema, ArraySchema, BooleanSchema, EvaluateSchema, FunctionSchema, IntersectionSchema, LazySchema, LiteralSchema, NotDefinedSchema, NullableSchema, NumberSchema, ObjectSchema, OptionalSchema, RecordSchema, Schema, StringSchema, UnionSchema, UnknownSchema } from './schema.js'; | ||
| export type Static<T> = _Static<T, 10>; | ||
| /** | ||
| * Indirection through a named generic alias for `LazySchema`. | ||
| * | ||
| * TypeScript caches and lazily expands named generic type aliases, so referencing | ||
| * `LazyStatic<F>` instead of inlining `_Static<ReturnType<F>>` lets the resolved | ||
| * type be re-entered for circular schemas (`lazy(() => self)`) without hitting the | ||
| * depth limit. Accessing properties on the resulting type expands one level at a | ||
| * time on demand, instead of materialising the whole structure up front. | ||
| */ | ||
| type LazyStatic<F extends () => Schema> = F extends () => infer S ? (S extends Schema ? _Static<S, 10> : never) : never; | ||
| /** | ||
| * Folds union member schemas into a union of their static types. | ||
| * Uses `Schema` for tuple positions (not a narrower alias) so `infer First extends …` does not | ||
| * reject valid tuple elements and collapse to `never`. | ||
| */ | ||
| type UnionObjectStatics<Schemas extends readonly Schema[], Depth extends number> = Schemas extends readonly [] ? never : Schemas extends readonly [infer First extends Schema, ...infer Rest extends readonly Schema[]] ? _Static<First, Depth> | UnionObjectStatics<Rest, Depth> : _Static<Schemas[number], Depth>; | ||
| /** | ||
| * Folds intersection member schemas into an intersection of their static types. | ||
@@ -25,5 +41,5 @@ * Uses `Schema` for tuple positions (not a narrower alias) so `infer First extends …` does not | ||
| }; | ||
| type _Static<T, Depth extends number = 10> = Depth extends 0 ? any : T extends LiteralSchema<infer Value> ? Value : T extends NumberSchema ? number : T extends StringSchema ? string : T extends BooleanSchema ? boolean : T extends NullableSchema ? null : T extends NotDefinedSchema ? undefined : T extends AnySchema ? any : T extends UnknownSchema ? unknown : T extends FunctionSchema<infer F> ? F : T extends ArraySchema<infer Item> ? Array<_Static<Item, Prev<Depth>>> : T extends RecordSchema<infer Key, infer Value> ? Record<_Static<Key, Prev<Depth>> & PropertyKey, _Static<Value, Prev<Depth>>> : T extends ObjectSchema<infer Properties> ? ObjectStatics<Properties, Depth> : T extends OptionalSchema<infer S> ? _Static<S, Prev<Depth>> | undefined : T extends IntersectionSchema<infer Schemas> ? IntersectObjectStatics<Schemas, Prev<Depth>> : T extends UnionSchema<infer Schemas> ? _Static<Schemas[number], Prev<Depth>> : T extends EvaluateSchema<infer S> ? _Static<S, Prev<Depth>> : T extends LazySchema<infer S> ? _Static<ReturnType<S>, Prev<Depth>> : never; | ||
| type _Static<T, Depth extends number = 10> = Depth extends 0 ? any : T extends LiteralSchema<infer Value> ? Value : T extends NumberSchema ? number : T extends StringSchema ? string : T extends BooleanSchema ? boolean : T extends NullableSchema ? null : T extends NotDefinedSchema ? undefined : T extends AnySchema ? any : T extends UnknownSchema ? unknown : T extends FunctionSchema<infer F> ? F : T extends ArraySchema<infer Item> ? Array<_Static<Item, Prev<Depth>>> : T extends RecordSchema<infer Key, infer Value> ? Record<_Static<Key, Prev<Depth>> & PropertyKey, _Static<Value, Prev<Depth>>> : T extends ObjectSchema<infer Properties> ? ObjectStatics<Properties, Depth> : T extends OptionalSchema<infer S> ? _Static<S, Prev<Depth>> | undefined : T extends IntersectionSchema<infer Schemas> ? IntersectObjectStatics<Schemas, Prev<Depth>> : T extends UnionSchema<infer Schemas> ? UnionObjectStatics<Schemas, Prev<Depth>> : T extends EvaluateSchema<infer S> ? _Static<S, Prev<Depth>> : T extends LazySchema<infer S> ? LazyStatic<S> : never; | ||
| type Prev<T extends number> = T extends 10 ? 9 : T extends 9 ? 8 : T extends 8 ? 7 : T extends 7 ? 6 : T extends 6 ? 5 : T extends 5 ? 4 : T extends 4 ? 3 : T extends 3 ? 2 : T extends 2 ? 1 : T extends 1 ? 0 : 0; | ||
| export {}; | ||
| //# sourceMappingURL=types.d.ts.map |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"} | ||
| {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"} |
@@ -34,6 +34,10 @@ import type { Schema } from './schema.js'; | ||
| * | ||
| * The optional `cache` argument tracks visited object–schema pairs to stop | ||
| * infinite recursion on cyclic value graphs (for example a node whose child | ||
| * points back at itself paired with a `lazy` schema). Callers normally omit it. | ||
| * | ||
| * If schema is `undefined`, validation fails. | ||
| * Returns true if the value matches the schema, false otherwise. | ||
| */ | ||
| export declare const validate: (schema: Schema | undefined, value: unknown) => boolean; | ||
| export declare const validate: (schema: Schema | undefined, value: unknown, cache?: WeakMap<object, Set<Schema>>) => boolean; | ||
| //# sourceMappingURL=validate.d.ts.map |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../src/validate.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,UAAU,CAAA;AAEtC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AACH,eAAO,MAAM,QAAQ,GAAI,QAAQ,MAAM,GAAG,SAAS,EAAE,OAAO,OAAO,KAAG,OAwErE,CAAA"} | ||
| {"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../src/validate.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,UAAU,CAAA;AAsHtC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AACH,eAAO,MAAM,QAAQ,GACnB,QAAQ,MAAM,GAAG,SAAS,EAC1B,OAAO,OAAO,EACd,QAAO,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,MAAM,CAAC,CAAiB,KAClD,OAA8C,CAAA"} |
+115
-72
| import { isObject } from './helpers/is-object.js'; | ||
| /** | ||
| * Internal validation implementation. Threads a `cache` of `(object, schema)` | ||
| * pairs that are *currently in flight on the call stack* so cyclic graphs | ||
| * terminate instead of recursing forever. Re-entering a pair that is already | ||
| * being validated higher up the stack short-circuits to `true`: any concrete | ||
| * mismatch would surface at that enclosing call rather than via the cycle. | ||
| * | ||
| * Crucially the marker is removed before this call returns, so the cache only | ||
| * ever describes the live call stack and not "ever visited in this run". | ||
| * Without that removal a failed branch (for example the first member of a | ||
| * `union`) would leave stale entries that later sibling branches sharing the | ||
| * same schema reference would mistake for a successful cycle short-circuit. | ||
| */ | ||
| const validateInner = (schema, value, cache) => { | ||
| if (!schema) { | ||
| return false; | ||
| } | ||
| // Short-circuit on cycles: this exact `(value, schema)` pair is already | ||
| // being validated higher up the call stack. | ||
| const trackable = isObject(value) || Array.isArray(value); | ||
| if (trackable && cache.get(value)?.has(schema)) { | ||
| return true; | ||
| } | ||
| // Mark this `(value, schema)` pair as in-progress for the duration of this | ||
| // call. Plain objects and arrays can form cycles; primitives and other | ||
| // non-plain objects do not need (or get) an entry. | ||
| if (trackable) { | ||
| const schemas = cache.get(value) ?? new Set(); | ||
| schemas.add(schema); | ||
| cache.set(value, schemas); | ||
| } | ||
| try { | ||
| if (schema.type === 'any' || schema.type === 'unknown') { | ||
| return true; | ||
| } | ||
| if (schema.type === 'function') { | ||
| return typeof value === 'function'; | ||
| } | ||
| if (schema.type === 'number') { | ||
| return typeof value === 'number' && !Number.isNaN(value) && Number.isFinite(value); | ||
| } | ||
| if (schema.type === 'string') { | ||
| return typeof value === 'string'; | ||
| } | ||
| if (schema.type === 'boolean') { | ||
| return typeof value === 'boolean'; | ||
| } | ||
| if (schema.type === 'nullable') { | ||
| return value === null; | ||
| } | ||
| if (schema.type === 'notDefined') { | ||
| return value === undefined; | ||
| } | ||
| if (schema.type === 'array') { | ||
| return Array.isArray(value) && value.every((item) => validateInner(schema.items, item, cache)); | ||
| } | ||
| if (schema.type === 'record') { | ||
| if (!isObject(value)) { | ||
| return false; | ||
| } | ||
| const keys = Object.keys(value); | ||
| return keys.every((key) => validateInner(schema.key, key, cache) && validateInner(schema.value, value[key], cache)); | ||
| } | ||
| if (schema.type === 'object') { | ||
| if (!isObject(value)) { | ||
| return false; | ||
| } | ||
| const schemaKeys = Object.keys(schema.properties); | ||
| return schemaKeys.every((key) => validateInner(schema.properties[key], value[key], cache)); | ||
| } | ||
| if (schema.type === 'optional') { | ||
| return value === undefined || validateInner(schema.schema, value, cache); | ||
| } | ||
| if (schema.type === 'union') { | ||
| return schema.schemas.some((branch) => validateInner(branch, value, cache)); | ||
| } | ||
| if (schema.type === 'intersection') { | ||
| if (schema.schemas.length === 0) { | ||
| // Vacuous: no constraints (matches `Array.prototype.every` on an empty list). | ||
| return true; | ||
| } | ||
| if (!isObject(value)) { | ||
| return false; | ||
| } | ||
| return schema.schemas.every((subSchema) => validateInner(subSchema, value, cache)); | ||
| } | ||
| if (schema.type === 'literal') { | ||
| return value === schema.value; | ||
| } | ||
| if (schema.type === 'lazy') { | ||
| return validateInner(schema.schema(), value, cache); | ||
| } | ||
| if (schema.type === 'evaluate') { | ||
| return validateInner(schema.schema, schema.expression(value), cache); | ||
| } | ||
| // We need to assert here that schema has the type never so we know we handle all cases | ||
| const _exhaustive = schema; | ||
| console.warn('Unknown schema type:', _exhaustive); | ||
| return false; | ||
| } | ||
| finally { | ||
| // Always clear the in-progress marker, even when a sub-call throws. This | ||
| // keeps the cache scoped to the live call stack so sibling branches (for | ||
| // example other `union` members or earlier-failed `intersection` members) | ||
| // re-validate the shared schema instead of inheriting a stale `true`. | ||
| if (trackable) { | ||
| cache.get(value)?.delete(schema); | ||
| } | ||
| } | ||
| }; | ||
| /** | ||
| * Validates that a given value matches the specified schema. | ||
@@ -34,76 +144,9 @@ * | ||
| * | ||
| * The optional `cache` argument tracks visited object–schema pairs to stop | ||
| * infinite recursion on cyclic value graphs (for example a node whose child | ||
| * points back at itself paired with a `lazy` schema). Callers normally omit it. | ||
| * | ||
| * If schema is `undefined`, validation fails. | ||
| * Returns true if the value matches the schema, false otherwise. | ||
| */ | ||
| export const validate = (schema, value) => { | ||
| if (!schema) { | ||
| return false; | ||
| } | ||
| if (schema.type === 'any' || schema.type === 'unknown') { | ||
| return true; | ||
| } | ||
| if (schema.type === 'function') { | ||
| return typeof value === 'function'; | ||
| } | ||
| if (schema.type === 'number') { | ||
| return typeof value === 'number' && !Number.isNaN(value) && Number.isFinite(value); | ||
| } | ||
| if (schema.type === 'string') { | ||
| return typeof value === 'string'; | ||
| } | ||
| if (schema.type === 'boolean') { | ||
| return typeof value === 'boolean'; | ||
| } | ||
| if (schema.type === 'nullable') { | ||
| return value === null; | ||
| } | ||
| if (schema.type === 'notDefined') { | ||
| return value === undefined; | ||
| } | ||
| if (schema.type === 'array') { | ||
| return Array.isArray(value) && value.every((item) => validate(schema.items, item)); | ||
| } | ||
| if (schema.type === 'record') { | ||
| if (!isObject(value)) { | ||
| return false; | ||
| } | ||
| const keys = Object.keys(value); | ||
| return keys.every((key) => validate(schema.key, key) && validate(schema.value, value[key])); | ||
| } | ||
| if (schema.type === 'object') { | ||
| if (!isObject(value)) { | ||
| return false; | ||
| } | ||
| const schemaKeys = Object.keys(schema.properties); | ||
| return schemaKeys.every((key) => validate(schema.properties[key], value[key])); | ||
| } | ||
| if (schema.type === 'optional') { | ||
| return value === undefined || validate(schema.schema, value); | ||
| } | ||
| if (schema.type === 'union') { | ||
| return schema.schemas.some((schema) => validate(schema, value)); | ||
| } | ||
| if (schema.type === 'intersection') { | ||
| if (schema.schemas.length === 0) { | ||
| // Vacuous: no constraints (matches `Array.prototype.every` on an empty list). | ||
| return true; | ||
| } | ||
| if (!isObject(value)) { | ||
| return false; | ||
| } | ||
| return schema.schemas.every((subSchema) => validate(subSchema, value)); | ||
| } | ||
| if (schema.type === 'literal') { | ||
| return value === schema.value; | ||
| } | ||
| if (schema.type === 'lazy') { | ||
| return validate(schema.schema(), value); | ||
| } | ||
| if (schema.type === 'evaluate') { | ||
| return validate(schema.schema, schema.expression(value)); | ||
| } | ||
| // We need to assert here that schema has the type never so we know we handle all cases | ||
| const _exhaustive = schema; | ||
| console.warn('Unknown schema type:', _exhaustive); | ||
| return false; | ||
| }; | ||
| export const validate = (schema, value, cache = new WeakMap()) => validateInner(schema, value, cache); |
+1
-1
@@ -18,3 +18,3 @@ { | ||
| ], | ||
| "version": "0.5.0", | ||
| "version": "0.6.0", | ||
| "engines": { | ||
@@ -21,0 +21,0 @@ "node": ">=20" |
76992
28.31%1240
25.89%