@scalar/validation 0.5.0 → 0.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +26 -0
- package/README.md +14 -0
- package/dist/coerce.d.ts +28 -1
- package/dist/coerce.d.ts.map +1 -1
- package/dist/coerce.js +172 -92
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/schema.d.ts +79 -7
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +12 -0
- package/dist/types.d.ts +17 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/validate.d.ts +5 -1
- package/dist/validate.d.ts.map +1 -1
- package/dist/validate.js +115 -72
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,31 @@
|
|
|
1
1
|
# @scalar/validation
|
|
2
2
|
|
|
3
|
+
## 0.6.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [#9710](https://github.com/scalar/scalar/pull/9710): Republish so the updated README (with the Scalar platform overview) reaches npm. Also renames the README generator metadata in package.json from `readme` to `scalarReadme`: npm treats a `readme` field as the readme text itself, so affected packages were published with a literal `[object Object]` readme on the registry instead of README.md.
|
|
8
|
+
|
|
9
|
+
## 0.6.0
|
|
10
|
+
|
|
11
|
+
### Minor Changes
|
|
12
|
+
|
|
13
|
+
- [#9262](https://github.com/scalar/scalar/pull/9262): feat(validation): support recursive schemas in validate and coerce
|
|
14
|
+
|
|
15
|
+
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.
|
|
16
|
+
|
|
17
|
+
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.
|
|
18
|
+
|
|
19
|
+
### Patch Changes
|
|
20
|
+
|
|
21
|
+
- [#9262](https://github.com/scalar/scalar/pull/9262): fix(validation): detect cycles when scoring union branches
|
|
22
|
+
|
|
23
|
+
`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.
|
|
24
|
+
|
|
25
|
+
- [#9262](https://github.com/scalar/scalar/pull/9262): fix(validation): do not leak cycle-detection cache across union branches
|
|
26
|
+
|
|
27
|
+
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.
|
|
28
|
+
|
|
3
29
|
## 0.5.0
|
|
4
30
|
|
|
5
31
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -2,6 +2,20 @@
|
|
|
2
2
|
|
|
3
3
|
Small, schema-first helpers to **check** unknown data and **coerce** it into predictable shapes. Schemas are plain JavaScript objects (easy to serialize or log), and TypeScript can infer output types with `Static`.
|
|
4
4
|
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
Scalar is an open-source API platform for teams who want beautiful developer interfaces without vendor lock-in.
|
|
8
|
+
|
|
9
|
+
- **[API References](https://scalar.com/products/api-references/getting-started)** — Interactive API documentation from OpenAPI and AsyncAPI specs.
|
|
10
|
+
- **[Docs](https://scalar.com/products/docs/getting-started)** — Write in Markdown/MDX, generate API references, sync with two-way Git.
|
|
11
|
+
- **[SDKs](https://scalar.com/products/sdks/getting-started)** — Type-safe client libraries in TypeScript, Python, Go, PHP, Java, and Ruby.
|
|
12
|
+
- **[MCP Servers](https://scalar.com/products/agent/getting-started)** — Generate secure MCP servers from your API spec.
|
|
13
|
+
- **[API Client](https://scalar.com/products/api-client/getting-started)** — Open-source, offline-first Postman alternative built on OpenAPI.
|
|
14
|
+
|
|
15
|
+
20M+ monthly npm installs · 15,500+ GitHub stars · MIT licensed · [scalar.com](https://scalar.com)
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
5
19
|
## Install
|
|
6
20
|
|
|
7
21
|
This package lives in the Scalar monorepo. In workspace consumers:
|
package/dist/coerce.d.ts
CHANGED
|
@@ -1,5 +1,31 @@
|
|
|
1
1
|
import type { Schema } from './schema.js';
|
|
2
2
|
import type { Static } from './types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Memoizes `schema.schema()` per lazy schema so that recursive definitions
|
|
5
|
+
* such as `lazy(() => object({ child: lazy(() => T) }))` resolve to the same
|
|
6
|
+
* inner schema reference across calls. Without this, every traversal would
|
|
7
|
+
* synthesize a fresh inner schema, defeating the `(value, schema)` cycle
|
|
8
|
+
* cache and producing infinite recursion on self-referential values.
|
|
9
|
+
*
|
|
10
|
+
* The cache is supplied by the top-level `coerce` call so it never leaks
|
|
11
|
+
* resolved schemas between unrelated invocations.
|
|
12
|
+
*/
|
|
13
|
+
type LazyCache = WeakMap<object, Schema>;
|
|
14
|
+
/**
|
|
15
|
+
* Falls back to `any` when `S` widens all the way to the full `Schema` union and
|
|
16
|
+
* returns the precise `Static<S>` otherwise. Computing `Static<Schema>` forces
|
|
17
|
+
* TypeScript to expand every variant of the recursive `Schema` definition and
|
|
18
|
+
* exhausts the depth limit, surfacing at call sites as
|
|
19
|
+
* `TS2589: Type instantiation is excessively deep and possibly infinite`.
|
|
20
|
+
* Degrading to `any` in that single case keeps the type tractable; callers that
|
|
21
|
+
* pass a specific schema (for example an `intersection(...)` literal) still get
|
|
22
|
+
* the precise static type.
|
|
23
|
+
*
|
|
24
|
+
* `[Schema] extends [S]` is wrapped in tuples to prevent distribution over union
|
|
25
|
+
* members — we want a single check that the whole `Schema` union is assignable
|
|
26
|
+
* to `S`, not a check that runs once per variant.
|
|
27
|
+
*/
|
|
28
|
+
type SafeStatic<S extends Schema> = [Schema] extends [S] ? any : Static<S>;
|
|
3
29
|
/**
|
|
4
30
|
* Coerces an unknown value toward the static type implied by `schema`. Values that
|
|
5
31
|
* pass {@link validate} for that branch are kept; otherwise primitives default to
|
|
@@ -18,5 +44,6 @@ import type { Static } from './types.js';
|
|
|
18
44
|
* The optional `cache` argument tracks visited object–schema pairs to stop infinite recursion
|
|
19
45
|
* on cyclic graphs; callers normally omit it.
|
|
20
46
|
*/
|
|
21
|
-
export declare const coerce: <S extends Schema>(schema: S, value: unknown, cache?: WeakMap<object,
|
|
47
|
+
export declare const coerce: <S extends Schema>(schema: S, value: unknown, cache?: WeakMap<object, Map<Schema, unknown>>, lazyCache?: LazyCache) => SafeStatic<S>;
|
|
48
|
+
export {};
|
|
22
49
|
//# sourceMappingURL=coerce.d.ts.map
|
package/dist/coerce.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
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;
|
|
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;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"}
|
package/dist/coerce.js
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
import { isObject } from './helpers/is-object.js';
|
|
2
2
|
import { validate } from './validate.js';
|
|
3
|
+
const resolveLazy = (schema, lazyCache) => {
|
|
4
|
+
const cached = lazyCache.get(schema);
|
|
5
|
+
if (cached) {
|
|
6
|
+
return cached;
|
|
7
|
+
}
|
|
8
|
+
const resolved = schema.schema();
|
|
9
|
+
lazyCache.set(schema, resolved);
|
|
10
|
+
return resolved;
|
|
11
|
+
};
|
|
3
12
|
/**
|
|
4
13
|
* True when this property schema is only used to discriminate union branches
|
|
5
14
|
* (single literal, or a union of literals). No presence bonus when the value
|
|
@@ -24,92 +33,127 @@ const isDiscriminatorProperty = (schema) => {
|
|
|
24
33
|
* Higher score means a closer match. Literals and matching object shapes
|
|
25
34
|
* are weighted more heavily. Objects are scored by shape/literals;
|
|
26
35
|
* arrays/records by structural type; primitives by validation; unions try all branches.
|
|
36
|
+
*
|
|
37
|
+
* The `scoringCache` tracks `(value, schema)` pairs that are currently being
|
|
38
|
+
* scored higher up the call stack. Without it, a recursive lazy schema such as
|
|
39
|
+
* `lazy(() => union([object({ child: optional(lazy(() => T)) }), …]))` scored
|
|
40
|
+
* against a self-referential value would recurse forever through
|
|
41
|
+
* `lazy → union → object → property → lazy → …` and overflow the stack.
|
|
42
|
+
*
|
|
43
|
+
* On re-entry of a pair we return `1` rather than `0` — a neutral positive
|
|
44
|
+
* score consistent with `validateInner` short-circuiting cycles to `true`.
|
|
45
|
+
* Markers are removed in `finally` so sibling union branches that share a
|
|
46
|
+
* schema reference are scored independently rather than inheriting a stale
|
|
47
|
+
* "in cycle" marker.
|
|
27
48
|
*/
|
|
28
|
-
const scoreUnion = (schema, value) => {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
// Discriminator properties (`literal` or `union` of literals): recurse with scoreUnion;
|
|
35
|
-
// matching values get a high weight (×10) so `type: literal('A')` beats unrelated fields
|
|
36
|
-
// on another branch; mismatches score 0 (no "key present" tie-break).
|
|
37
|
-
// Other properties: scoreUnion plus +1 when the value fails validation so `{ a: null }`
|
|
38
|
-
// can still prefer the branch that declares `a`.
|
|
39
|
-
return Object.keys(schema.properties).reduce((acc, key) => {
|
|
40
|
-
if (!(key in value)) {
|
|
41
|
-
return acc;
|
|
42
|
-
}
|
|
43
|
-
const propSchema = schema.properties[key];
|
|
44
|
-
const raw = value[key];
|
|
45
|
-
const base = scoreUnion(propSchema, raw);
|
|
46
|
-
if (isDiscriminatorProperty(propSchema)) {
|
|
47
|
-
return acc + (base > 0 ? base * 10 : 0);
|
|
48
|
-
}
|
|
49
|
-
return acc + (base > 0 ? base : 1);
|
|
50
|
-
}, 0);
|
|
49
|
+
const scoreUnion = (schema, value, lazyCache, scoringCache = new WeakMap()) => {
|
|
50
|
+
// Short-circuit on cycles: this exact (value, schema) pair is already being
|
|
51
|
+
// scored higher up the call stack. The enclosing call's score subsumes any
|
|
52
|
+
// contribution we could compute here, so return a neutral positive score.
|
|
53
|
+
if (isObject(value) && scoringCache.get(value)?.has(schema)) {
|
|
54
|
+
return 1;
|
|
51
55
|
}
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
// TODO: implement smarter scoring for records (just a placeholder for now)
|
|
58
|
-
return isObject(value) ? 1 : 0;
|
|
59
|
-
}
|
|
60
|
-
if (schema.type === 'optional') {
|
|
61
|
-
return value === undefined ? 1 : scoreUnion(schema.schema, value);
|
|
62
|
-
}
|
|
63
|
-
if (schema.type === 'union') {
|
|
64
|
-
// For a union, use the highest score among all sub-schemas
|
|
65
|
-
return Math.max(...schema.schemas.map((schema) => scoreUnion(schema, value)));
|
|
56
|
+
const trackable = isObject(value);
|
|
57
|
+
if (trackable) {
|
|
58
|
+
const schemas = scoringCache.get(value) ?? new Set();
|
|
59
|
+
schemas.add(schema);
|
|
60
|
+
scoringCache.set(value, schemas);
|
|
66
61
|
}
|
|
67
|
-
|
|
68
|
-
if (schema.
|
|
69
|
-
|
|
62
|
+
try {
|
|
63
|
+
if (schema.type === 'object') {
|
|
64
|
+
if (!isObject(value)) {
|
|
65
|
+
return 0;
|
|
66
|
+
}
|
|
67
|
+
const keys = Object.keys(schema.properties);
|
|
68
|
+
// If there are no properties, we want to score 1 since we want to outscore if there are inline primitives
|
|
69
|
+
if (keys.length === 0) {
|
|
70
|
+
return 1;
|
|
71
|
+
}
|
|
72
|
+
// Missing keys contribute 0 (including optional keys — matches prior union heuristics).
|
|
73
|
+
// Discriminator properties (`literal` or `union` of literals): recurse with scoreUnion;
|
|
74
|
+
// matching values get a high weight (×10) so `type: literal('A')` beats unrelated fields
|
|
75
|
+
// on another branch; mismatches score 0 (no "key present" tie-break).
|
|
76
|
+
// Other properties: scoreUnion plus +1 when the value fails validation so `{ a: null }`
|
|
77
|
+
// can still prefer the branch that declares `a`.
|
|
78
|
+
return keys.reduce((acc, key) => {
|
|
79
|
+
if (!(key in value)) {
|
|
80
|
+
return acc;
|
|
81
|
+
}
|
|
82
|
+
const propSchema = schema.properties[key];
|
|
83
|
+
const raw = value[key];
|
|
84
|
+
const base = scoreUnion(propSchema, raw, lazyCache, scoringCache);
|
|
85
|
+
if (isDiscriminatorProperty(propSchema)) {
|
|
86
|
+
return acc + (base > 0 ? base * 10 : 0);
|
|
87
|
+
}
|
|
88
|
+
return acc + (base > 0 ? base : 1);
|
|
89
|
+
}, 0);
|
|
70
90
|
}
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
91
|
+
if (schema.type === 'array') {
|
|
92
|
+
// Score 1 if value is an array, otherwise 0
|
|
93
|
+
return Array.isArray(value) ? 1 : 0;
|
|
94
|
+
}
|
|
95
|
+
if (schema.type === 'record') {
|
|
96
|
+
// TODO: implement smarter scoring for records (just a placeholder for now)
|
|
97
|
+
return isObject(value) ? 1 : 0;
|
|
98
|
+
}
|
|
99
|
+
if (schema.type === 'optional') {
|
|
100
|
+
return value === undefined ? 1 : scoreUnion(schema.schema, value, lazyCache, scoringCache);
|
|
101
|
+
}
|
|
102
|
+
if (schema.type === 'union') {
|
|
103
|
+
// For a union, use the highest score among all sub-schemas
|
|
104
|
+
return Math.max(...schema.schemas.map((branch) => scoreUnion(branch, value, lazyCache, scoringCache)));
|
|
105
|
+
}
|
|
106
|
+
if (schema.type === 'intersection') {
|
|
107
|
+
if (schema.schemas.length === 0) {
|
|
108
|
+
return 1;
|
|
109
|
+
}
|
|
110
|
+
return schema.schemas.reduce((acc, sub) => acc + scoreUnion(sub, value, lazyCache, scoringCache), 0);
|
|
111
|
+
}
|
|
112
|
+
if (schema.type === 'lazy') {
|
|
113
|
+
// For a lazy schema, evaluate the inner schema and recurse
|
|
114
|
+
return scoreUnion(resolveLazy(schema, lazyCache), value, lazyCache, scoringCache);
|
|
115
|
+
}
|
|
116
|
+
if (schema.type === 'evaluate') {
|
|
117
|
+
// For an evaluate schema, evaluate the expression and recurse
|
|
118
|
+
return scoreUnion(schema.schema, schema.expression(value), lazyCache, scoringCache);
|
|
119
|
+
}
|
|
120
|
+
// For primitives and any other type, return 1 if valid, otherwise 0
|
|
121
|
+
return validate(schema, value) ? 1 : 0;
|
|
76
122
|
}
|
|
77
|
-
|
|
78
|
-
//
|
|
79
|
-
|
|
123
|
+
finally {
|
|
124
|
+
// Clear the in-progress marker so sibling union branches that reference
|
|
125
|
+
// the same schema are scored independently rather than short-circuiting
|
|
126
|
+
// to the cycle-neutral score.
|
|
127
|
+
if (trackable) {
|
|
128
|
+
scoringCache.get(value)?.delete(schema);
|
|
129
|
+
}
|
|
80
130
|
}
|
|
81
|
-
// For primitives and any other type, return 1 if valid, otherwise 0
|
|
82
|
-
return validate(schema, value) ? 1 : 0;
|
|
83
131
|
};
|
|
84
132
|
/**
|
|
85
|
-
*
|
|
86
|
-
*
|
|
87
|
-
*
|
|
88
|
-
*
|
|
89
|
-
*
|
|
90
|
-
* @example
|
|
91
|
-
* ```ts
|
|
92
|
-
* import { coerce, number, object, string } from '@scalar/validation'
|
|
93
|
-
*
|
|
94
|
-
* coerce(number(), 42) // 42
|
|
95
|
-
* coerce(number(), 'nope') // 0 — invalid number uses default
|
|
96
|
-
* coerce(object({ id: number(), name: string() }), { id: '1', name: 'Ada' }) // { id: 0, name: 'Ada' }
|
|
97
|
-
* ```
|
|
98
|
-
*
|
|
99
|
-
* The optional `cache` argument tracks visited object–schema pairs to stop infinite recursion
|
|
100
|
-
* on cyclic graphs; callers normally omit it.
|
|
133
|
+
* Records the in-progress `result` for a given `(value, schema)` pair so that
|
|
134
|
+
* recursive calls hitting the same pair return the already-allocated result
|
|
135
|
+
* instead of recursing forever. Plain objects and arrays are both tracked;
|
|
136
|
+
* other values cannot form cycles and are ignored.
|
|
101
137
|
*/
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
}
|
|
107
|
-
// Track visited schemas to prevent infinite recursion
|
|
108
|
-
if (isObject(value)) {
|
|
109
|
-
const schemas = cache.get(value) || new Set();
|
|
110
|
-
schemas.add(schema);
|
|
138
|
+
const trackCycle = (value, schema, result, cache) => {
|
|
139
|
+
if (isObject(value) || Array.isArray(value)) {
|
|
140
|
+
const schemas = cache.get(value) || new Map();
|
|
141
|
+
schemas.set(schema, result);
|
|
111
142
|
cache.set(value, schemas);
|
|
112
143
|
}
|
|
144
|
+
};
|
|
145
|
+
/**
|
|
146
|
+
* Internal coercion implementation. Takes the wide `Schema` union and returns `unknown` so that
|
|
147
|
+
* recursive calls do not pay the cost of relating two generic `Static<S>` instantiations, which
|
|
148
|
+
* can overflow the type checker now that `LazyStatic` resolves recursive schemas without a depth
|
|
149
|
+
* cap. The public `coerce` wrapper preserves the typed surface.
|
|
150
|
+
*/
|
|
151
|
+
const coerceInner = (schema, value, cache, lazyCache) => {
|
|
152
|
+
// Prevent infinite recursion by returning the in-progress result that was
|
|
153
|
+
// staged by an enclosing call via trackCycle.
|
|
154
|
+
if ((isObject(value) || Array.isArray(value)) && cache.get(value)?.has(schema)) {
|
|
155
|
+
return cache.get(value)?.get(schema);
|
|
156
|
+
}
|
|
113
157
|
// If no schema is provided, return the value as is
|
|
114
158
|
if (!schema) {
|
|
115
159
|
return value;
|
|
@@ -121,25 +165,25 @@ export const coerce = (schema, value, cache = new WeakMap()) => {
|
|
|
121
165
|
if (typeof value === 'function') {
|
|
122
166
|
return value;
|
|
123
167
|
}
|
|
124
|
-
return (
|
|
168
|
+
return () => undefined;
|
|
125
169
|
}
|
|
126
170
|
if (schema.type === 'number') {
|
|
127
171
|
if (validate(schema, value)) {
|
|
128
172
|
return value;
|
|
129
173
|
}
|
|
130
|
-
return
|
|
174
|
+
return schema.default ?? 0;
|
|
131
175
|
}
|
|
132
176
|
if (schema.type === 'string') {
|
|
133
177
|
if (validate(schema, value)) {
|
|
134
178
|
return value;
|
|
135
179
|
}
|
|
136
|
-
return
|
|
180
|
+
return schema.default ?? '';
|
|
137
181
|
}
|
|
138
182
|
if (schema.type === 'boolean') {
|
|
139
183
|
if (validate(schema, value)) {
|
|
140
184
|
return value;
|
|
141
185
|
}
|
|
142
|
-
return
|
|
186
|
+
return schema.default ?? false;
|
|
143
187
|
}
|
|
144
188
|
if (schema.type === 'nullable') {
|
|
145
189
|
return null;
|
|
@@ -151,56 +195,92 @@ export const coerce = (schema, value, cache = new WeakMap()) => {
|
|
|
151
195
|
if (value === undefined) {
|
|
152
196
|
return undefined;
|
|
153
197
|
}
|
|
154
|
-
return
|
|
198
|
+
return coerceInner(schema.schema, value, cache, lazyCache);
|
|
155
199
|
}
|
|
156
200
|
if (schema.type === 'array') {
|
|
157
201
|
if (!Array.isArray(value)) {
|
|
158
202
|
return [];
|
|
159
203
|
}
|
|
160
|
-
|
|
204
|
+
// Pre-allocate so a self-referential array can be cached before we
|
|
205
|
+
// recurse into its items, breaking otherwise-infinite cycles.
|
|
206
|
+
const result = new Array(value.length);
|
|
207
|
+
trackCycle(value, schema, result, cache);
|
|
208
|
+
for (let i = 0; i < value.length; i++) {
|
|
209
|
+
result[i] = coerceInner(schema.items, value[i], cache, lazyCache);
|
|
210
|
+
}
|
|
211
|
+
return result;
|
|
161
212
|
}
|
|
162
213
|
if (schema.type === 'record') {
|
|
163
214
|
if (!isObject(value)) {
|
|
164
215
|
return {};
|
|
165
216
|
}
|
|
166
|
-
|
|
217
|
+
// Pre-allocate so a self-referential record can be cached before we
|
|
218
|
+
// recurse into its entries, breaking otherwise-infinite cycles.
|
|
219
|
+
const result = {};
|
|
220
|
+
trackCycle(value, schema, result, cache);
|
|
221
|
+
for (const key of Object.keys(value)) {
|
|
222
|
+
result[key] = coerceInner(schema.value, value[key], cache, lazyCache);
|
|
223
|
+
}
|
|
224
|
+
return result;
|
|
167
225
|
}
|
|
168
226
|
if (schema.type === 'object') {
|
|
169
227
|
const keys = Object.keys(schema.properties);
|
|
170
228
|
const target = isObject(value) ? value : null;
|
|
171
|
-
|
|
229
|
+
// Pre-allocate so a self-referential object can be cached before we
|
|
230
|
+
// recurse into its properties, breaking otherwise-infinite cycles.
|
|
231
|
+
const result = {};
|
|
232
|
+
trackCycle(value, schema, result, cache);
|
|
172
233
|
for (const key of keys) {
|
|
173
234
|
const propSchema = schema.properties[key];
|
|
174
235
|
const raw = target?.[key];
|
|
175
236
|
if (propSchema.type === 'optional' && raw === undefined) {
|
|
176
237
|
continue;
|
|
177
238
|
}
|
|
178
|
-
|
|
239
|
+
result[key] = coerceInner(propSchema, raw, cache, lazyCache);
|
|
179
240
|
}
|
|
180
|
-
return
|
|
241
|
+
return result;
|
|
181
242
|
}
|
|
182
243
|
if (schema.type === 'union') {
|
|
183
|
-
const branch = schema.schemas.reduce((acc,
|
|
184
|
-
const score = scoreUnion(
|
|
185
|
-
return score > acc.score ? { schema, score } : acc;
|
|
244
|
+
const branch = schema.schemas.reduce((acc, branchSchema) => {
|
|
245
|
+
const score = scoreUnion(branchSchema, value, lazyCache);
|
|
246
|
+
return score > acc.score ? { schema: branchSchema, score } : acc;
|
|
186
247
|
}, { schema: schema.schemas[0], score: 0 });
|
|
187
248
|
// We need some way to pick one of the union values
|
|
188
|
-
return
|
|
249
|
+
return coerceInner(branch.schema, value, cache, lazyCache);
|
|
189
250
|
}
|
|
190
251
|
if (schema.type === 'intersection') {
|
|
191
|
-
return schema.schemas.reduce((acc, subSchema) => Object.assign(acc,
|
|
252
|
+
return schema.schemas.reduce((acc, subSchema) => Object.assign(acc, coerceInner(subSchema, value, cache, lazyCache)), {});
|
|
192
253
|
}
|
|
193
254
|
if (schema.type === 'literal') {
|
|
194
255
|
return schema.value;
|
|
195
256
|
}
|
|
196
257
|
if (schema.type === 'lazy') {
|
|
197
|
-
return
|
|
258
|
+
return coerceInner(resolveLazy(schema, lazyCache), value, cache, lazyCache);
|
|
198
259
|
}
|
|
199
260
|
if (schema.type === 'evaluate') {
|
|
200
|
-
return
|
|
261
|
+
return coerceInner(schema.schema, schema.expression(value), cache, lazyCache);
|
|
201
262
|
}
|
|
202
263
|
// We need to assert here that schema has the type never so we know we handle all cases
|
|
203
264
|
const _exhaustive = schema;
|
|
204
265
|
console.warn('Unknown schema type:', _exhaustive);
|
|
205
266
|
return value;
|
|
206
267
|
};
|
|
268
|
+
/**
|
|
269
|
+
* Coerces an unknown value toward the static type implied by `schema`. Values that
|
|
270
|
+
* pass {@link validate} for that branch are kept; otherwise primitives default to
|
|
271
|
+
* `0`, `''`, or `false`, and arrays, records, and objects are built recursively.
|
|
272
|
+
* Unions pick the best-matching branch; `evaluate` runs `expression` before the inner schema.
|
|
273
|
+
*
|
|
274
|
+
* @example
|
|
275
|
+
* ```ts
|
|
276
|
+
* import { coerce, number, object, string } from '@scalar/validation'
|
|
277
|
+
*
|
|
278
|
+
* coerce(number(), 42) // 42
|
|
279
|
+
* coerce(number(), 'nope') // 0 — invalid number uses default
|
|
280
|
+
* coerce(object({ id: number(), name: string() }), { id: '1', name: 'Ada' }) // { id: 0, name: 'Ada' }
|
|
281
|
+
* ```
|
|
282
|
+
*
|
|
283
|
+
* The optional `cache` argument tracks visited object–schema pairs to stop infinite recursion
|
|
284
|
+
* on cyclic graphs; callers normally omit it.
|
|
285
|
+
*/
|
|
286
|
+
export const coerce = (schema, value, cache = new WeakMap(), lazyCache = new WeakMap()) => coerceInner(schema, value, cache, lazyCache);
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { coerce } from './coerce.js';
|
|
2
|
-
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
|
|
2
|
+
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';
|
|
3
3
|
export { type GenerateTypesOptions, generateTypes } from './typegen.js';
|
|
4
4
|
export type { Static } from './types.js';
|
|
5
5
|
export { validate } from './validate.js';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
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,
|
|
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,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"}
|
package/dist/schema.d.ts
CHANGED
|
@@ -67,10 +67,52 @@ export type ObjectSchema<Properties extends Record<string, Schema>> = {
|
|
|
67
67
|
properties: Properties;
|
|
68
68
|
} & Documentation;
|
|
69
69
|
/** Schema that matches if any member schema matches (discriminated union when literals or object tags differ). */
|
|
70
|
-
export type UnionSchema<Schemas extends Schema[]> = {
|
|
70
|
+
export type UnionSchema<Schemas extends readonly Schema[]> = {
|
|
71
71
|
type: 'union';
|
|
72
72
|
schemas: Schemas;
|
|
73
73
|
} & Documentation;
|
|
74
|
+
/**
|
|
75
|
+
* Members that may appear inside a `union([...])`.
|
|
76
|
+
*
|
|
77
|
+
* Discriminant-only for the same reason as {@link IntersectionMember}: a full `Schema` constraint
|
|
78
|
+
* at the call site forces TypeScript to eagerly evaluate tuple elements and breaks circular
|
|
79
|
+
* inference when members use `lazy(() => self)` (for example recursive navigation trees).
|
|
80
|
+
*/
|
|
81
|
+
export type UnionMember = {
|
|
82
|
+
type: 'number';
|
|
83
|
+
} | {
|
|
84
|
+
type: 'string';
|
|
85
|
+
} | {
|
|
86
|
+
type: 'boolean';
|
|
87
|
+
} | {
|
|
88
|
+
type: 'nullable';
|
|
89
|
+
} | {
|
|
90
|
+
type: 'notDefined';
|
|
91
|
+
} | {
|
|
92
|
+
type: 'any';
|
|
93
|
+
} | {
|
|
94
|
+
type: 'unknown';
|
|
95
|
+
} | {
|
|
96
|
+
type: 'function';
|
|
97
|
+
} | {
|
|
98
|
+
type: 'array';
|
|
99
|
+
} | {
|
|
100
|
+
type: 'record';
|
|
101
|
+
} | {
|
|
102
|
+
type: 'object';
|
|
103
|
+
} | {
|
|
104
|
+
type: 'union';
|
|
105
|
+
} | {
|
|
106
|
+
type: 'optional';
|
|
107
|
+
} | {
|
|
108
|
+
type: 'intersection';
|
|
109
|
+
} | {
|
|
110
|
+
type: 'literal';
|
|
111
|
+
} | {
|
|
112
|
+
type: 'lazy';
|
|
113
|
+
} | {
|
|
114
|
+
type: 'evaluate';
|
|
115
|
+
};
|
|
74
116
|
/**
|
|
75
117
|
* Schema that accepts `undefined` or a value matching the inner schema.
|
|
76
118
|
* In {@link Static} and type generation, object properties use `key?:` instead of `T | undefined`.
|
|
@@ -80,10 +122,28 @@ export type OptionalSchema<S extends Schema> = {
|
|
|
80
122
|
schema: S;
|
|
81
123
|
} & Documentation;
|
|
82
124
|
/**
|
|
83
|
-
*
|
|
84
|
-
*
|
|
125
|
+
* Members that may appear inside an `intersection([...])`.
|
|
126
|
+
*
|
|
127
|
+
* This is intentionally a discriminant-only structural type rather than a union of the full
|
|
128
|
+
* schema types. If we use the full schema types here, the constraint check at the call site
|
|
129
|
+
* of `intersection` forces TypeScript to *eagerly* evaluate each tuple element, which breaks
|
|
130
|
+
* circular type inference when a member transitively contains a `lazy(() => self)` reference.
|
|
131
|
+
*
|
|
132
|
+
* The narrower discriminant shape only requires TypeScript to verify the `type` literal, which
|
|
133
|
+
* is cheap and does not trigger evaluation of nested schemas. Every `ObjectSchema`, `UnionSchema`,
|
|
134
|
+
* `IntersectionSchema`, and `LazySchema` is still assignable to this type because each carries
|
|
135
|
+
* the appropriate `type` field, so the public API and compile-time safety are preserved.
|
|
85
136
|
*/
|
|
86
|
-
export type
|
|
137
|
+
export type IntersectionMember = {
|
|
138
|
+
type: 'object';
|
|
139
|
+
} | {
|
|
140
|
+
type: 'union';
|
|
141
|
+
} | {
|
|
142
|
+
type: 'intersection';
|
|
143
|
+
} | {
|
|
144
|
+
type: 'lazy';
|
|
145
|
+
};
|
|
146
|
+
export type IntersectionSchema<Schemas extends readonly Schema[]> = {
|
|
87
147
|
type: 'intersection';
|
|
88
148
|
schemas: Schemas;
|
|
89
149
|
} & Documentation;
|
|
@@ -110,7 +170,7 @@ export type EvaluateSchema<S extends Schema> = {
|
|
|
110
170
|
expression: (value: unknown) => unknown;
|
|
111
171
|
schema: S;
|
|
112
172
|
};
|
|
113
|
-
export type Schema = NumberSchema | StringSchema | BooleanSchema | NullableSchema | NotDefinedSchema | AnySchema | UnknownSchema | FunctionSchema<any> | ArraySchema<any> | RecordSchema<any, any> | ObjectSchema<Record<string, any>> | UnionSchema<
|
|
173
|
+
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>;
|
|
114
174
|
declare const number: (options?: Documentation & {
|
|
115
175
|
default?: number;
|
|
116
176
|
}) => NumberSchema;
|
|
@@ -128,8 +188,20 @@ declare const fn: <T extends (...args: any[]) => any = (...args: unknown[]) => u
|
|
|
128
188
|
declare const array: <Item extends Schema>(items: Item, options?: Documentation) => ArraySchema<Item>;
|
|
129
189
|
declare const record: <Key extends StringSchema | AnySchema, Value extends Schema>(key: Key, value: Value, options?: Documentation) => RecordSchema<Key, Value>;
|
|
130
190
|
declare const object: <Properties extends Record<string, Schema>>(properties: Properties, options?: Documentation) => ObjectSchema<Properties>;
|
|
131
|
-
|
|
132
|
-
|
|
191
|
+
/**
|
|
192
|
+
* The conditional return type mirrors {@link intersection}: lightweight input constraint,
|
|
193
|
+
* precise `UnionSchema<Schemas>` output without re-triggering eager evaluation.
|
|
194
|
+
*/
|
|
195
|
+
declare const union: <const Schemas extends readonly UnionMember[]>(schemas: Schemas, options?: Documentation) => Schemas extends readonly Schema[] ? UnionSchema<Schemas> : never;
|
|
196
|
+
/**
|
|
197
|
+
* The conditional return type is what unlocks circular `intersection([... lazy(() => self) ...])`.
|
|
198
|
+
*
|
|
199
|
+
* The input constraint is the lightweight `IntersectionMember` (discriminant-only) so the call-site
|
|
200
|
+
* constraint check does not force TypeScript to eagerly evaluate each tuple element. The conditional
|
|
201
|
+
* `Schemas extends readonly Schema[]` is always true in practice (every passed value is a real schema)
|
|
202
|
+
* and lets us produce a precise `IntersectionSchema<Schemas>` without re-introducing the heavy check.
|
|
203
|
+
*/
|
|
204
|
+
declare const intersection: <const Schemas extends readonly IntersectionMember[]>(schemas: Schemas, options?: Documentation) => Schemas extends readonly Schema[] ? IntersectionSchema<Schemas> : never;
|
|
133
205
|
declare const optional: <S extends Schema>(schema: S, options?: Documentation) => OptionalSchema<S>;
|
|
134
206
|
declare const literal: <Value extends string | number | boolean | bigint>(value: Value) => LiteralSchema<Value>;
|
|
135
207
|
declare const lazy: <S extends () => Schema>(schema: S) => LazySchema<S>;
|
package/dist/schema.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,KAAK,aAAa,GAAG,OAAO,CAAC;IAC3B,8DAA8D;IAC9D,WAAW,EAAE,MAAM,CAAA;IACnB,0DAA0D;IAC1D,QAAQ,EAAE,MAAM,CAAA;CACjB,CAAC,CAAA;AAEF,6EAA6E;AAC7E,MAAM,MAAM,YAAY,GAAG;IACzB,IAAI,EAAE,QAAQ,CAAA;IACd,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB,GAAG,aAAa,CAAA;AAEjB,qEAAqE;AACrE,MAAM,MAAM,YAAY,GAAG;IACzB,IAAI,EAAE,QAAQ,CAAA;IACd,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB,GAAG,aAAa,CAAA;AAEjB,uEAAuE;AACvE,MAAM,MAAM,aAAa,GAAG;IAC1B,IAAI,EAAE,SAAS,CAAA;IACf,OAAO,CAAC,EAAE,OAAO,CAAA;CAClB,GAAG,aAAa,CAAA;AAEjB,4DAA4D;AAC5D,MAAM,MAAM,cAAc,GAAG;IAC3B,IAAI,EAAE,UAAU,CAAA;CACjB,GAAG,aAAa,CAAA;AAEjB,qFAAqF;AACrF,MAAM,MAAM,gBAAgB,GAAG;IAC7B,IAAI,EAAE,YAAY,CAAA;CACnB,GAAG,aAAa,CAAA;AAEjB,yFAAyF;AACzF,MAAM,MAAM,SAAS,GAAG;IACtB,IAAI,EAAE,KAAK,CAAA;CACZ,GAAG,aAAa,CAAA;AAEjB,4FAA4F;AAC5F,MAAM,MAAM,aAAa,GAAG;IAC1B,IAAI,EAAE,SAAS,CAAA;CAChB,GAAG,aAAa,CAAA;AAEjB;;;;GAIG;AACH,MAAM,MAAM,cAAc,CAAC,CAAC,SAAS,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,GAAG,GAAG,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,OAAO,IAAI;IAChG,IAAI,EAAE,UAAU,CAAA;IAChB,yFAAyF;IACzF,GAAG,CAAC,EAAE,CAAC,CAAA;CACR,GAAG,aAAa,CAAA;AAEjB,iGAAiG;AACjG,MAAM,MAAM,WAAW,CAAC,IAAI,SAAS,MAAM,IAAI;IAC7C,IAAI,EAAE,OAAO,CAAA;IACb,KAAK,EAAE,IAAI,CAAA;CACZ,GAAG,aAAa,CAAA;AAEjB,4GAA4G;AAC5G,MAAM,MAAM,YAAY,CAAC,GAAG,SAAS,YAAY,GAAG,YAAY,GAAG,SAAS,EAAE,KAAK,SAAS,MAAM,IAAI;IACpG,IAAI,EAAE,QAAQ,CAAA;IACd,GAAG,EAAE,GAAG,CAAA;IACR,KAAK,EAAE,KAAK,CAAA;CACb,GAAG,aAAa,CAAA;AAEjB,yFAAyF;AACzF,MAAM,MAAM,YAAY,CAAC,UAAU,SAAS,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,IAAI;IACpE,IAAI,EAAE,QAAQ,CAAA;IACd,UAAU,EAAE,UAAU,CAAA;CACvB,GAAG,aAAa,CAAA;AAEjB,kHAAkH;AAClH,MAAM,MAAM,WAAW,CAAC,OAAO,SAAS,MAAM,EAAE,IAAI;
|
|
1
|
+
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"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"}
|
package/dist/schema.js
CHANGED
|
@@ -60,12 +60,24 @@ const object = (properties, options) => ({
|
|
|
60
60
|
typeName: options?.typeName,
|
|
61
61
|
typeComment: options?.typeComment,
|
|
62
62
|
});
|
|
63
|
+
/**
|
|
64
|
+
* The conditional return type mirrors {@link intersection}: lightweight input constraint,
|
|
65
|
+
* precise `UnionSchema<Schemas>` output without re-triggering eager evaluation.
|
|
66
|
+
*/
|
|
63
67
|
const union = (schemas, options) => ({
|
|
64
68
|
type: 'union',
|
|
65
69
|
schemas,
|
|
66
70
|
typeName: options?.typeName,
|
|
67
71
|
typeComment: options?.typeComment,
|
|
68
72
|
});
|
|
73
|
+
/**
|
|
74
|
+
* The conditional return type is what unlocks circular `intersection([... lazy(() => self) ...])`.
|
|
75
|
+
*
|
|
76
|
+
* The input constraint is the lightweight `IntersectionMember` (discriminant-only) so the call-site
|
|
77
|
+
* constraint check does not force TypeScript to eagerly evaluate each tuple element. The conditional
|
|
78
|
+
* `Schemas extends readonly Schema[]` is always true in practice (every passed value is a real schema)
|
|
79
|
+
* and lets us produce a precise `IntersectionSchema<Schemas>` without re-introducing the heavy check.
|
|
80
|
+
*/
|
|
69
81
|
const intersection = (schemas, options) => ({
|
|
70
82
|
type: 'intersection',
|
|
71
83
|
schemas,
|
package/dist/types.d.ts
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
import type { AnySchema, ArraySchema, BooleanSchema, EvaluateSchema, FunctionSchema, IntersectionSchema, LazySchema, LiteralSchema, NotDefinedSchema, NullableSchema, NumberSchema, ObjectSchema, OptionalSchema, RecordSchema, Schema, StringSchema, UnionSchema, UnknownSchema } from './schema.js';
|
|
2
2
|
export type Static<T> = _Static<T, 10>;
|
|
3
|
+
/**
|
|
4
|
+
* Indirection through a named generic alias for `LazySchema`.
|
|
5
|
+
*
|
|
6
|
+
* TypeScript caches and lazily expands named generic type aliases, so referencing
|
|
7
|
+
* `LazyStatic<F>` instead of inlining `_Static<ReturnType<F>>` lets the resolved
|
|
8
|
+
* type be re-entered for circular schemas (`lazy(() => self)`) without hitting the
|
|
9
|
+
* depth limit. Accessing properties on the resulting type expands one level at a
|
|
10
|
+
* time on demand, instead of materialising the whole structure up front.
|
|
11
|
+
*/
|
|
12
|
+
type LazyStatic<F extends () => Schema> = F extends () => infer S ? (S extends Schema ? _Static<S, 10> : never) : never;
|
|
13
|
+
/**
|
|
14
|
+
* Folds union member schemas into a union of their static types.
|
|
15
|
+
* Uses `Schema` for tuple positions (not a narrower alias) so `infer First extends …` does not
|
|
16
|
+
* reject valid tuple elements and collapse to `never`.
|
|
17
|
+
*/
|
|
18
|
+
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>;
|
|
3
19
|
/**
|
|
4
20
|
* Folds intersection member schemas into an intersection of their static types.
|
|
5
21
|
* Uses `Schema` for tuple positions (not a narrower alias) so `infer First extends …` does not
|
|
@@ -22,7 +38,7 @@ type ObjectStatics<Properties, Depth extends number> = [keyof Properties] extend
|
|
|
22
38
|
} & {
|
|
23
39
|
[K in OptionalPropertyKeys<Properties>]?: _Static<OptionalSchemaInner<Properties[K]>, Prev<Depth>>;
|
|
24
40
|
};
|
|
25
|
-
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> ?
|
|
41
|
+
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;
|
|
26
42
|
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;
|
|
27
43
|
export {};
|
|
28
44
|
//# sourceMappingURL=types.d.ts.map
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
package/dist/validate.d.ts
CHANGED
|
@@ -31,8 +31,12 @@ import type { Schema } from './schema.js';
|
|
|
31
31
|
* validate(schema, { id: 1, name: 2 }) // false
|
|
32
32
|
* ```
|
|
33
33
|
*
|
|
34
|
+
* The optional `cache` argument tracks visited object–schema pairs to stop
|
|
35
|
+
* infinite recursion on cyclic value graphs (for example a node whose child
|
|
36
|
+
* points back at itself paired with a `lazy` schema). Callers normally omit it.
|
|
37
|
+
*
|
|
34
38
|
* If schema is `undefined`, validation fails.
|
|
35
39
|
* Returns true if the value matches the schema, false otherwise.
|
|
36
40
|
*/
|
|
37
|
-
export declare const validate: (schema: Schema | undefined, value: unknown) => boolean;
|
|
41
|
+
export declare const validate: (schema: Schema | undefined, value: unknown, cache?: WeakMap<object, Set<Schema>>) => boolean;
|
|
38
42
|
//# sourceMappingURL=validate.d.ts.map
|
package/dist/validate.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../src/validate.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,UAAU,CAAA;
|
|
1
|
+
{"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"}
|
package/dist/validate.js
CHANGED
|
@@ -1,4 +1,114 @@
|
|
|
1
1
|
import { isObject } from './helpers/is-object.js';
|
|
2
|
+
/**
|
|
3
|
+
* Internal validation implementation. Threads a `cache` of `(object, schema)`
|
|
4
|
+
* pairs that are *currently in flight on the call stack* so cyclic graphs
|
|
5
|
+
* terminate instead of recursing forever. Re-entering a pair that is already
|
|
6
|
+
* being validated higher up the stack short-circuits to `true`: any concrete
|
|
7
|
+
* mismatch would surface at that enclosing call rather than via the cycle.
|
|
8
|
+
*
|
|
9
|
+
* Crucially the marker is removed before this call returns, so the cache only
|
|
10
|
+
* ever describes the live call stack and not "ever visited in this run".
|
|
11
|
+
* Without that removal a failed branch (for example the first member of a
|
|
12
|
+
* `union`) would leave stale entries that later sibling branches sharing the
|
|
13
|
+
* same schema reference would mistake for a successful cycle short-circuit.
|
|
14
|
+
*/
|
|
15
|
+
const validateInner = (schema, value, cache) => {
|
|
16
|
+
if (!schema) {
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
19
|
+
// Short-circuit on cycles: this exact `(value, schema)` pair is already
|
|
20
|
+
// being validated higher up the call stack.
|
|
21
|
+
const trackable = isObject(value) || Array.isArray(value);
|
|
22
|
+
if (trackable && cache.get(value)?.has(schema)) {
|
|
23
|
+
return true;
|
|
24
|
+
}
|
|
25
|
+
// Mark this `(value, schema)` pair as in-progress for the duration of this
|
|
26
|
+
// call. Plain objects and arrays can form cycles; primitives and other
|
|
27
|
+
// non-plain objects do not need (or get) an entry.
|
|
28
|
+
if (trackable) {
|
|
29
|
+
const schemas = cache.get(value) ?? new Set();
|
|
30
|
+
schemas.add(schema);
|
|
31
|
+
cache.set(value, schemas);
|
|
32
|
+
}
|
|
33
|
+
try {
|
|
34
|
+
if (schema.type === 'any' || schema.type === 'unknown') {
|
|
35
|
+
return true;
|
|
36
|
+
}
|
|
37
|
+
if (schema.type === 'function') {
|
|
38
|
+
return typeof value === 'function';
|
|
39
|
+
}
|
|
40
|
+
if (schema.type === 'number') {
|
|
41
|
+
return typeof value === 'number' && !Number.isNaN(value) && Number.isFinite(value);
|
|
42
|
+
}
|
|
43
|
+
if (schema.type === 'string') {
|
|
44
|
+
return typeof value === 'string';
|
|
45
|
+
}
|
|
46
|
+
if (schema.type === 'boolean') {
|
|
47
|
+
return typeof value === 'boolean';
|
|
48
|
+
}
|
|
49
|
+
if (schema.type === 'nullable') {
|
|
50
|
+
return value === null;
|
|
51
|
+
}
|
|
52
|
+
if (schema.type === 'notDefined') {
|
|
53
|
+
return value === undefined;
|
|
54
|
+
}
|
|
55
|
+
if (schema.type === 'array') {
|
|
56
|
+
return Array.isArray(value) && value.every((item) => validateInner(schema.items, item, cache));
|
|
57
|
+
}
|
|
58
|
+
if (schema.type === 'record') {
|
|
59
|
+
if (!isObject(value)) {
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
const keys = Object.keys(value);
|
|
63
|
+
return keys.every((key) => validateInner(schema.key, key, cache) && validateInner(schema.value, value[key], cache));
|
|
64
|
+
}
|
|
65
|
+
if (schema.type === 'object') {
|
|
66
|
+
if (!isObject(value)) {
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
const schemaKeys = Object.keys(schema.properties);
|
|
70
|
+
return schemaKeys.every((key) => validateInner(schema.properties[key], value[key], cache));
|
|
71
|
+
}
|
|
72
|
+
if (schema.type === 'optional') {
|
|
73
|
+
return value === undefined || validateInner(schema.schema, value, cache);
|
|
74
|
+
}
|
|
75
|
+
if (schema.type === 'union') {
|
|
76
|
+
return schema.schemas.some((branch) => validateInner(branch, value, cache));
|
|
77
|
+
}
|
|
78
|
+
if (schema.type === 'intersection') {
|
|
79
|
+
if (schema.schemas.length === 0) {
|
|
80
|
+
// Vacuous: no constraints (matches `Array.prototype.every` on an empty list).
|
|
81
|
+
return true;
|
|
82
|
+
}
|
|
83
|
+
if (!isObject(value)) {
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
86
|
+
return schema.schemas.every((subSchema) => validateInner(subSchema, value, cache));
|
|
87
|
+
}
|
|
88
|
+
if (schema.type === 'literal') {
|
|
89
|
+
return value === schema.value;
|
|
90
|
+
}
|
|
91
|
+
if (schema.type === 'lazy') {
|
|
92
|
+
return validateInner(schema.schema(), value, cache);
|
|
93
|
+
}
|
|
94
|
+
if (schema.type === 'evaluate') {
|
|
95
|
+
return validateInner(schema.schema, schema.expression(value), cache);
|
|
96
|
+
}
|
|
97
|
+
// We need to assert here that schema has the type never so we know we handle all cases
|
|
98
|
+
const _exhaustive = schema;
|
|
99
|
+
console.warn('Unknown schema type:', _exhaustive);
|
|
100
|
+
return false;
|
|
101
|
+
}
|
|
102
|
+
finally {
|
|
103
|
+
// Always clear the in-progress marker, even when a sub-call throws. This
|
|
104
|
+
// keeps the cache scoped to the live call stack so sibling branches (for
|
|
105
|
+
// example other `union` members or earlier-failed `intersection` members)
|
|
106
|
+
// re-validate the shared schema instead of inheriting a stale `true`.
|
|
107
|
+
if (trackable) {
|
|
108
|
+
cache.get(value)?.delete(schema);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
};
|
|
2
112
|
/**
|
|
3
113
|
* Validates that a given value matches the specified schema.
|
|
4
114
|
*
|
|
@@ -31,78 +141,11 @@ import { isObject } from './helpers/is-object.js';
|
|
|
31
141
|
* validate(schema, { id: 1, name: 2 }) // false
|
|
32
142
|
* ```
|
|
33
143
|
*
|
|
144
|
+
* The optional `cache` argument tracks visited object–schema pairs to stop
|
|
145
|
+
* infinite recursion on cyclic value graphs (for example a node whose child
|
|
146
|
+
* points back at itself paired with a `lazy` schema). Callers normally omit it.
|
|
147
|
+
*
|
|
34
148
|
* If schema is `undefined`, validation fails.
|
|
35
149
|
* Returns true if the value matches the schema, false otherwise.
|
|
36
150
|
*/
|
|
37
|
-
export const validate = (schema, value) =>
|
|
38
|
-
if (!schema) {
|
|
39
|
-
return false;
|
|
40
|
-
}
|
|
41
|
-
if (schema.type === 'any' || schema.type === 'unknown') {
|
|
42
|
-
return true;
|
|
43
|
-
}
|
|
44
|
-
if (schema.type === 'function') {
|
|
45
|
-
return typeof value === 'function';
|
|
46
|
-
}
|
|
47
|
-
if (schema.type === 'number') {
|
|
48
|
-
return typeof value === 'number' && !Number.isNaN(value) && Number.isFinite(value);
|
|
49
|
-
}
|
|
50
|
-
if (schema.type === 'string') {
|
|
51
|
-
return typeof value === 'string';
|
|
52
|
-
}
|
|
53
|
-
if (schema.type === 'boolean') {
|
|
54
|
-
return typeof value === 'boolean';
|
|
55
|
-
}
|
|
56
|
-
if (schema.type === 'nullable') {
|
|
57
|
-
return value === null;
|
|
58
|
-
}
|
|
59
|
-
if (schema.type === 'notDefined') {
|
|
60
|
-
return value === undefined;
|
|
61
|
-
}
|
|
62
|
-
if (schema.type === 'array') {
|
|
63
|
-
return Array.isArray(value) && value.every((item) => validate(schema.items, item));
|
|
64
|
-
}
|
|
65
|
-
if (schema.type === 'record') {
|
|
66
|
-
if (!isObject(value)) {
|
|
67
|
-
return false;
|
|
68
|
-
}
|
|
69
|
-
const keys = Object.keys(value);
|
|
70
|
-
return keys.every((key) => validate(schema.key, key) && validate(schema.value, value[key]));
|
|
71
|
-
}
|
|
72
|
-
if (schema.type === 'object') {
|
|
73
|
-
if (!isObject(value)) {
|
|
74
|
-
return false;
|
|
75
|
-
}
|
|
76
|
-
const schemaKeys = Object.keys(schema.properties);
|
|
77
|
-
return schemaKeys.every((key) => validate(schema.properties[key], value[key]));
|
|
78
|
-
}
|
|
79
|
-
if (schema.type === 'optional') {
|
|
80
|
-
return value === undefined || validate(schema.schema, value);
|
|
81
|
-
}
|
|
82
|
-
if (schema.type === 'union') {
|
|
83
|
-
return schema.schemas.some((schema) => validate(schema, value));
|
|
84
|
-
}
|
|
85
|
-
if (schema.type === 'intersection') {
|
|
86
|
-
if (schema.schemas.length === 0) {
|
|
87
|
-
// Vacuous: no constraints (matches `Array.prototype.every` on an empty list).
|
|
88
|
-
return true;
|
|
89
|
-
}
|
|
90
|
-
if (!isObject(value)) {
|
|
91
|
-
return false;
|
|
92
|
-
}
|
|
93
|
-
return schema.schemas.every((subSchema) => validate(subSchema, value));
|
|
94
|
-
}
|
|
95
|
-
if (schema.type === 'literal') {
|
|
96
|
-
return value === schema.value;
|
|
97
|
-
}
|
|
98
|
-
if (schema.type === 'lazy') {
|
|
99
|
-
return validate(schema.schema(), value);
|
|
100
|
-
}
|
|
101
|
-
if (schema.type === 'evaluate') {
|
|
102
|
-
return validate(schema.schema, schema.expression(value));
|
|
103
|
-
}
|
|
104
|
-
// We need to assert here that schema has the type never so we know we handle all cases
|
|
105
|
-
const _exhaustive = schema;
|
|
106
|
-
console.warn('Unknown schema type:', _exhaustive);
|
|
107
|
-
return false;
|
|
108
|
-
};
|
|
151
|
+
export const validate = (schema, value, cache = new WeakMap()) => validateInner(schema, value, cache);
|