typesea 0.2.0 → 0.3.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 +40 -0
- package/README.md +104 -41
- package/SECURITY.md +52 -0
- package/dist/aot/index.d.ts +1 -1
- package/dist/aot/index.d.ts.map +1 -1
- package/dist/aot/index.js +22 -3
- package/dist/builders/composite.d.ts +6 -3
- package/dist/builders/composite.d.ts.map +1 -1
- package/dist/builders/composite.js +22 -13
- package/dist/builders/index.d.ts +6 -5
- package/dist/builders/index.d.ts.map +1 -1
- package/dist/builders/index.js +5 -4
- package/dist/builders/modifier.d.ts +6 -0
- package/dist/builders/modifier.d.ts.map +1 -1
- package/dist/builders/modifier.js +14 -0
- package/dist/builders/object/guard.d.ts +54 -2
- package/dist/builders/object/guard.d.ts.map +1 -1
- package/dist/builders/object/guard.js +117 -7
- package/dist/builders/object/index.d.ts +2 -2
- package/dist/builders/object/index.d.ts.map +1 -1
- package/dist/builders/object/index.js +1 -1
- package/dist/builders/object/schema.d.ts +33 -2
- package/dist/builders/object/schema.d.ts.map +1 -1
- package/dist/builders/object/schema.js +198 -8
- package/dist/builders/object/types.d.ts +15 -0
- package/dist/builders/object/types.d.ts.map +1 -1
- package/dist/builders/runtime.d.ts +40 -0
- package/dist/builders/runtime.d.ts.map +1 -0
- package/dist/builders/runtime.js +150 -0
- package/dist/builders/scalar.d.ts +20 -1
- package/dist/builders/scalar.d.ts.map +1 -1
- package/dist/builders/scalar.js +54 -1
- package/dist/builders/table.d.ts +31 -5
- package/dist/builders/table.d.ts.map +1 -1
- package/dist/builders/table.js +31 -5
- package/dist/builders/types.d.ts +6 -0
- package/dist/builders/types.d.ts.map +1 -1
- package/dist/compile/check-composite.d.ts +3 -1
- package/dist/compile/check-composite.d.ts.map +1 -1
- package/dist/compile/check-composite.js +143 -11
- package/dist/compile/check-scalar.d.ts +10 -0
- package/dist/compile/check-scalar.d.ts.map +1 -1
- package/dist/compile/check-scalar.js +138 -2
- package/dist/compile/check.d.ts.map +1 -1
- package/dist/compile/check.js +25 -3
- package/dist/compile/context.d.ts.map +1 -1
- package/dist/compile/context.js +2 -0
- package/dist/compile/first.d.ts +26 -0
- package/dist/compile/first.d.ts.map +1 -0
- package/dist/compile/first.js +850 -0
- package/dist/compile/graph-predicate.d.ts.map +1 -1
- package/dist/compile/graph-predicate.js +389 -18
- package/dist/compile/guard.d.ts +2 -1
- package/dist/compile/guard.d.ts.map +1 -1
- package/dist/compile/guard.js +54 -8
- package/dist/compile/predicate.d.ts.map +1 -1
- package/dist/compile/predicate.js +156 -5
- package/dist/compile/runtime.d.ts +20 -1
- package/dist/compile/runtime.d.ts.map +1 -1
- package/dist/compile/runtime.js +31 -0
- package/dist/compile/source.d.ts.map +1 -1
- package/dist/compile/source.js +27 -3
- package/dist/compile/types.d.ts +2 -0
- package/dist/compile/types.d.ts.map +1 -1
- package/dist/decoder/index.d.ts +60 -0
- package/dist/decoder/index.d.ts.map +1 -1
- package/dist/decoder/index.js +164 -1
- package/dist/evaluate/check-composite.d.ts +52 -2
- package/dist/evaluate/check-composite.d.ts.map +1 -1
- package/dist/evaluate/check-composite.js +193 -6
- package/dist/evaluate/check-scalar.d.ts +9 -0
- package/dist/evaluate/check-scalar.d.ts.map +1 -1
- package/dist/evaluate/check-scalar.js +92 -3
- package/dist/evaluate/check.d.ts.map +1 -1
- package/dist/evaluate/check.js +19 -4
- package/dist/evaluate/shared.d.ts +19 -0
- package/dist/evaluate/shared.d.ts.map +1 -1
- package/dist/evaluate/shared.js +35 -0
- package/dist/guard/array.d.ts +48 -0
- package/dist/guard/array.d.ts.map +1 -0
- package/dist/guard/array.js +84 -0
- package/dist/guard/base.d.ts +32 -2
- package/dist/guard/base.d.ts.map +1 -1
- package/dist/guard/base.js +74 -3
- package/dist/guard/date.d.ts +34 -0
- package/dist/guard/date.d.ts.map +1 -0
- package/dist/guard/date.js +60 -0
- package/dist/guard/index.d.ts +2 -0
- package/dist/guard/index.d.ts.map +1 -1
- package/dist/guard/index.js +2 -0
- package/dist/guard/number.d.ts +60 -0
- package/dist/guard/number.d.ts.map +1 -1
- package/dist/guard/number.js +129 -0
- package/dist/guard/read.d.ts +53 -1
- package/dist/guard/read.d.ts.map +1 -1
- package/dist/guard/read.js +102 -0
- package/dist/guard/string.d.ts +82 -0
- package/dist/guard/string.d.ts.map +1 -1
- package/dist/guard/string.js +213 -0
- package/dist/guard/types.d.ts +18 -0
- package/dist/guard/types.d.ts.map +1 -1
- package/dist/index.d.ts +4 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -4
- package/dist/ir/builder.d.ts +3 -3
- package/dist/ir/builder.d.ts.map +1 -1
- package/dist/ir/builder.js +5 -2
- package/dist/ir/freeze.js +7 -0
- package/dist/ir/types.d.ts +4 -1
- package/dist/ir/types.d.ts.map +1 -1
- package/dist/ir/validate.d.ts.map +1 -1
- package/dist/ir/validate.js +35 -1
- package/dist/issue/index.d.ts +1 -1
- package/dist/issue/index.d.ts.map +1 -1
- package/dist/issue/index.js +4 -0
- package/dist/json-schema/emit-composite.d.ts +6 -2
- package/dist/json-schema/emit-composite.d.ts.map +1 -1
- package/dist/json-schema/emit-composite.js +66 -12
- package/dist/json-schema/emit-scalar.d.ts.map +1 -1
- package/dist/json-schema/emit-scalar.js +54 -1
- package/dist/json-schema/emit.d.ts.map +1 -1
- package/dist/json-schema/emit.js +11 -2
- package/dist/json-schema/types.d.ts +7 -1
- package/dist/json-schema/types.d.ts.map +1 -1
- package/dist/kind/index.d.ts +25 -0
- package/dist/kind/index.d.ts.map +1 -1
- package/dist/kind/index.js +26 -3
- package/dist/lower/index.d.ts.map +1 -1
- package/dist/lower/index.js +52 -3
- package/dist/message/index.d.ts +18 -0
- package/dist/message/index.d.ts.map +1 -1
- package/dist/message/index.js +67 -0
- package/dist/optimize/domain.js +6 -2
- package/dist/optimize/map-node.d.ts.map +1 -1
- package/dist/optimize/map-node.js +3 -0
- package/dist/plan/cache.js +13 -1
- package/dist/plan/predicate.d.ts.map +1 -1
- package/dist/plan/predicate.js +33 -3
- package/dist/plan/schema-predicate.d.ts.map +1 -1
- package/dist/plan/schema-predicate.js +267 -8
- package/dist/schema/freeze.js +22 -0
- package/dist/schema/index.d.ts +2 -2
- package/dist/schema/index.d.ts.map +1 -1
- package/dist/schema/index.js +1 -1
- package/dist/schema/types.d.ts +89 -4
- package/dist/schema/types.d.ts.map +1 -1
- package/dist/schema/types.js +8 -1
- package/dist/schema/undefined.d.ts.map +1 -1
- package/dist/schema/undefined.js +5 -0
- package/dist/schema/validate.d.ts.map +1 -1
- package/dist/schema/validate.js +111 -4
- package/docs/api.md +79 -10
- package/docs/assets/benchmark-headline.svg +33 -33
- package/docs/engine-notes.md +9 -5
- package/docs/index.html +1366 -722
- package/docs/ko/api.md +383 -0
- package/docs/ko/engine-notes.md +156 -0
- package/docs/ko/readme.md +404 -0
- package/package.json +6 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../../src/schema/validate.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;
|
|
1
|
+
{"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../../src/schema/validate.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAsBH,OAAO,KAAK,EAIR,MAAM,EAET,MAAM,YAAY,CAAC;AAEpB;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,MAAM,CAK7D"}
|
package/dist/schema/validate.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* @details Schema helpers enforce construction-time invariants before values reach
|
|
5
5
|
* validation, compilation, or export.
|
|
6
6
|
*/
|
|
7
|
-
import { NumberCheckTag, ObjectModeTag, PresenceTag, SchemaTag, StringCheckTag } from "../kind/index.js";
|
|
7
|
+
import { ArrayCheckTag, DateCheckTag, NumberCheckTag, ObjectModeTag, PresenceTag, SchemaTag, StringCheckTag } from "../kind/index.js";
|
|
8
8
|
import { isLiteralValue } from "./literal.js";
|
|
9
9
|
import { includesString, isMissingDataProperty, isObjectKeyLookup, isPlainRegExp, isRecord, isStringArray, isUnknownArray, readOwnDataProperty } from "./common.js";
|
|
10
10
|
/**
|
|
@@ -69,6 +69,8 @@ function isSchemaRecord(value, state) {
|
|
|
69
69
|
case SchemaTag.Symbol:
|
|
70
70
|
case SchemaTag.Boolean:
|
|
71
71
|
return true;
|
|
72
|
+
case SchemaTag.Date:
|
|
73
|
+
return isDateChecks(readOwnDataProperty(value, "checks"));
|
|
72
74
|
case SchemaTag.String:
|
|
73
75
|
return isStringChecks(readOwnDataProperty(value, "checks"));
|
|
74
76
|
case SchemaTag.Number:
|
|
@@ -82,11 +84,25 @@ function isSchemaRecord(value, state) {
|
|
|
82
84
|
return !isMissingDataProperty(literal) && isLiteralValue(literal);
|
|
83
85
|
}
|
|
84
86
|
case SchemaTag.Array:
|
|
85
|
-
return isSchemaValueInner(readOwnDataProperty(value, "item"), state)
|
|
87
|
+
return isSchemaValueInner(readOwnDataProperty(value, "item"), state) &&
|
|
88
|
+
isArrayChecks(readOwnDataProperty(value, "checks"));
|
|
86
89
|
case SchemaTag.Tuple:
|
|
87
|
-
return isSchemaArray(readOwnDataProperty(value, "items"), state)
|
|
90
|
+
return isSchemaArray(readOwnDataProperty(value, "items"), state) &&
|
|
91
|
+
isOptionalSchemaValue(readOwnDataProperty(value, "rest"), state);
|
|
88
92
|
case SchemaTag.Record:
|
|
89
93
|
return isSchemaValueInner(readOwnDataProperty(value, "value"), state);
|
|
94
|
+
case SchemaTag.Map:
|
|
95
|
+
return isSchemaValueInner(readOwnDataProperty(value, "key"), state) &&
|
|
96
|
+
isSchemaValueInner(readOwnDataProperty(value, "value"), state);
|
|
97
|
+
case SchemaTag.Set:
|
|
98
|
+
return isSchemaValueInner(readOwnDataProperty(value, "item"), state);
|
|
99
|
+
case SchemaTag.InstanceOf:
|
|
100
|
+
return typeof readOwnDataProperty(value, "constructor") === "function" &&
|
|
101
|
+
typeof readOwnDataProperty(value, "name") === "string";
|
|
102
|
+
case SchemaTag.Property:
|
|
103
|
+
return typeof readOwnDataProperty(value, "key") === "string" &&
|
|
104
|
+
isSchemaValueInner(readOwnDataProperty(value, "base"), state) &&
|
|
105
|
+
isSchemaValueInner(readOwnDataProperty(value, "value"), state);
|
|
90
106
|
case SchemaTag.Object:
|
|
91
107
|
return isObjectSchemaValue(value, state);
|
|
92
108
|
case SchemaTag.Union:
|
|
@@ -113,6 +129,15 @@ function isSchemaRecord(value, state) {
|
|
|
113
129
|
return false;
|
|
114
130
|
}
|
|
115
131
|
}
|
|
132
|
+
/**
|
|
133
|
+
* @brief Validate an optional schema payload.
|
|
134
|
+
* @param value Candidate child schema or undefined.
|
|
135
|
+
* @param state Recursion state for child schemas.
|
|
136
|
+
* @returns True when the value is absent or a valid schema.
|
|
137
|
+
*/
|
|
138
|
+
function isOptionalSchemaValue(value, state) {
|
|
139
|
+
return value === undefined || isSchemaValueInner(value, state);
|
|
140
|
+
}
|
|
116
141
|
/**
|
|
117
142
|
* @brief Validate the check vector attached to a string schema.
|
|
118
143
|
* @param value Candidate check vector.
|
|
@@ -149,6 +174,13 @@ function isStringChecks(value) {
|
|
|
149
174
|
}
|
|
150
175
|
break;
|
|
151
176
|
case StringCheckTag.Uuid:
|
|
177
|
+
case StringCheckTag.Email:
|
|
178
|
+
case StringCheckTag.Url:
|
|
179
|
+
case StringCheckTag.IsoDate:
|
|
180
|
+
case StringCheckTag.IsoDateTime:
|
|
181
|
+
case StringCheckTag.Ulid:
|
|
182
|
+
case StringCheckTag.Ipv4:
|
|
183
|
+
case StringCheckTag.Ipv6:
|
|
152
184
|
break;
|
|
153
185
|
default:
|
|
154
186
|
return false;
|
|
@@ -176,7 +208,47 @@ function isNumberChecks(value) {
|
|
|
176
208
|
case NumberCheckTag.Integer:
|
|
177
209
|
break;
|
|
178
210
|
case NumberCheckTag.Gte:
|
|
179
|
-
case NumberCheckTag.Lte:
|
|
211
|
+
case NumberCheckTag.Lte:
|
|
212
|
+
case NumberCheckTag.Gt:
|
|
213
|
+
case NumberCheckTag.Lt: {
|
|
214
|
+
const bound = readOwnDataProperty(check, "value");
|
|
215
|
+
if (typeof bound !== "number" || !Number.isFinite(bound)) {
|
|
216
|
+
return false;
|
|
217
|
+
}
|
|
218
|
+
break;
|
|
219
|
+
}
|
|
220
|
+
case NumberCheckTag.MultipleOf: {
|
|
221
|
+
const divisor = readOwnDataProperty(check, "value");
|
|
222
|
+
if (typeof divisor !== "number" ||
|
|
223
|
+
!Number.isFinite(divisor) ||
|
|
224
|
+
divisor <= 0) {
|
|
225
|
+
return false;
|
|
226
|
+
}
|
|
227
|
+
break;
|
|
228
|
+
}
|
|
229
|
+
default:
|
|
230
|
+
return false;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
return true;
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* @brief Validate the check vector attached to a Date schema.
|
|
237
|
+
* @param value Candidate check vector.
|
|
238
|
+
* @returns True when every Date bound is finite epoch milliseconds.
|
|
239
|
+
*/
|
|
240
|
+
function isDateChecks(value) {
|
|
241
|
+
if (!isUnknownArray(value)) {
|
|
242
|
+
return false;
|
|
243
|
+
}
|
|
244
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
245
|
+
const check = value[index];
|
|
246
|
+
if (!isRecord(check)) {
|
|
247
|
+
return false;
|
|
248
|
+
}
|
|
249
|
+
switch (readOwnDataProperty(check, "tag")) {
|
|
250
|
+
case DateCheckTag.Min:
|
|
251
|
+
case DateCheckTag.Max: {
|
|
180
252
|
const bound = readOwnDataProperty(check, "value");
|
|
181
253
|
if (typeof bound !== "number" || !Number.isFinite(bound)) {
|
|
182
254
|
return false;
|
|
@@ -189,6 +261,37 @@ function isNumberChecks(value) {
|
|
|
189
261
|
}
|
|
190
262
|
return true;
|
|
191
263
|
}
|
|
264
|
+
/**
|
|
265
|
+
* @brief Validate the check vector attached to an array schema.
|
|
266
|
+
* @param value Candidate check vector.
|
|
267
|
+
* @returns True when every array length bound is a non-negative integer.
|
|
268
|
+
* @details Array length checks are admitted once at schema construction so
|
|
269
|
+
* interpreters and code generators can emit direct `length` comparisons later.
|
|
270
|
+
*/
|
|
271
|
+
function isArrayChecks(value) {
|
|
272
|
+
if (!isUnknownArray(value)) {
|
|
273
|
+
return false;
|
|
274
|
+
}
|
|
275
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
276
|
+
const check = value[index];
|
|
277
|
+
if (!isRecord(check)) {
|
|
278
|
+
return false;
|
|
279
|
+
}
|
|
280
|
+
switch (readOwnDataProperty(check, "tag")) {
|
|
281
|
+
case ArrayCheckTag.Min:
|
|
282
|
+
case ArrayCheckTag.Max: {
|
|
283
|
+
const bound = readOwnDataProperty(check, "value");
|
|
284
|
+
if (typeof bound !== "number" || !Number.isInteger(bound) || bound < 0) {
|
|
285
|
+
return false;
|
|
286
|
+
}
|
|
287
|
+
break;
|
|
288
|
+
}
|
|
289
|
+
default:
|
|
290
|
+
return false;
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
return true;
|
|
294
|
+
}
|
|
192
295
|
/**
|
|
193
296
|
* @brief Validate a dense vector of child schemas.
|
|
194
297
|
* @details Schema helpers enforce construction-time invariants before values reach
|
|
@@ -226,10 +329,14 @@ function isObjectSchemaValue(value, state) {
|
|
|
226
329
|
const entries = readOwnDataProperty(value, "entries");
|
|
227
330
|
const keys = readOwnDataProperty(value, "keys");
|
|
228
331
|
const keyLookup = readOwnDataProperty(value, "keyLookup");
|
|
332
|
+
const catchall = readOwnDataProperty(value, "catchall");
|
|
229
333
|
if (!isUnknownArray(entries) || !isStringArray(keys) ||
|
|
230
334
|
!isObjectKeyLookup(keyLookup, keys) || entries.length !== keys.length) {
|
|
231
335
|
return false;
|
|
232
336
|
}
|
|
337
|
+
if (catchall !== undefined && !isSchemaValueInner(catchall, state)) {
|
|
338
|
+
return false;
|
|
339
|
+
}
|
|
233
340
|
const seen = [];
|
|
234
341
|
for (let index = 0; index < entries.length; index += 1) {
|
|
235
342
|
const entry = entries[index];
|
package/docs/api.md
CHANGED
|
@@ -27,6 +27,7 @@ condition.
|
|
|
27
27
|
interface Guard<T> {
|
|
28
28
|
is(value: unknown): value is T;
|
|
29
29
|
check(value: unknown): CheckResult<T>;
|
|
30
|
+
checkFirst(value: unknown): CheckResult<T>;
|
|
30
31
|
assert(value: unknown): asserts value is T;
|
|
31
32
|
graph(): Graph;
|
|
32
33
|
}
|
|
@@ -36,6 +37,7 @@ interface Guard<T> {
|
|
|
36
37
|
| --- | --- | --- |
|
|
37
38
|
| `is` | Hot boolean narrowing | Avoids diagnostic allocation on the success path. |
|
|
38
39
|
| `check` | Validation with issues | Returns frozen `Result<T, Issue[]>` containers. |
|
|
40
|
+
| `checkFirst` | Hot rejection diagnostics | Returns the same frozen `Result` shape, but failure contains at most one issue. Compiled and AOT guards use a dedicated first-fault collector. |
|
|
39
41
|
| `assert` | Throwing integration boundaries | Throws `TypeSeaAssertionError` with copied, frozen issues. |
|
|
40
42
|
| `graph` | Runtime plan introspection | Returns the validated, optimized, frozen Sea-of-Nodes graph held by the validation plan. |
|
|
41
43
|
|
|
@@ -47,14 +49,19 @@ cross the API boundary.
|
|
|
47
49
|
|
|
48
50
|
| Family | Builders |
|
|
49
51
|
| --- | --- |
|
|
50
|
-
| Scalars | `t.unknown`, `t.never`, `t.string`, `t.number`, `t.bigint`, `t.symbol`, `t.boolean` |
|
|
51
|
-
|
|
|
52
|
+
| Scalars | `t.unknown`, `t.never`, `t.string`, `t.number`, `t.date`, `t.bigint`, `t.symbol`, `t.boolean`, `t.null`, `t.undefined`, `t.void` |
|
|
53
|
+
| String checks | `.min`, `.max`, `.length`, `.nonempty`, `.regex`, `.startsWith`, `.endsWith`, `.includes`, `.uuid`, `.email`, `.url`, `.isoDate`, `.isoDateTime`, `.ulid`, `.ipv4`, `.ipv6` |
|
|
54
|
+
| Number checks | `.int`, `.finite`, `.safe`, `.gte`, `.lte`, `.min`, `.max`, `.gt`, `.lt`, `.multipleOf`, `.positive`, `.nonnegative`, `.negative`, `.nonpositive` |
|
|
55
|
+
| Date checks | `.min`, `.max` |
|
|
56
|
+
| Literals and containers | `t.literal(value)`, `t.enum(values)`, `t.array(item)`, `t.tuple([a, b])`, `t.tuple([head], rest)`, `t.record(value)`, `t.map(key, value)`, `t.set(item)`, `t.json()` |
|
|
57
|
+
| Array checks | `.min`, `.max`, `.length`, `.nonempty` |
|
|
52
58
|
| Objects | `t.object(shape)`, `t.strictObject(shape)` |
|
|
53
|
-
| Object transforms | `t.extend`, `t.pick`, `t.omit`, `t.partial`, and matching object guard methods |
|
|
59
|
+
| Object transforms | `t.extend`, `t.safeExtend`, `t.merge`, `t.pick`, `t.omit`, `t.partial`, `t.deepPartial`, `t.required`, `t.strict`, `t.passthrough`, `t.strip`, `t.catchall`, and matching object guard methods |
|
|
60
|
+
| Runtime object contracts | `t.instanceOf(Ctor)`, `t.property(base, key, value)`, `guard.property(key, value)` |
|
|
54
61
|
| Composition | `t.union`, `t.discriminatedUnion`, `t.intersect`, `guard.intersect` |
|
|
55
|
-
| Presence | `t.optional`, `t.undefinedable`, `t.nullable` |
|
|
62
|
+
| Presence | `t.optional`, `t.undefinedable`, `t.nullable`, `t.nullish` |
|
|
56
63
|
| Dynamic guards | `t.lazy`, `t.refine` |
|
|
57
|
-
| Decoders | `t.decoder`, `t.transform`, `t.pipe`, `t.coerce` |
|
|
64
|
+
| Decoders | `t.decoder`, `t.transform`, `t.pipe`, `t.default`, `t.defaultValue`, `t.prefault`, `t.catch`, `t.codec`, `t.coerce`, `t.string.trim()`, `t.string.toLowerCase()`, `t.string.toUpperCase()` |
|
|
58
65
|
| Async decoders | `t.asyncDecoder`, `t.asyncRefine`, `t.asyncTransform`, `t.asyncPipe` |
|
|
59
66
|
|
|
60
67
|
Builder functions validate inputs before a schema can enter the validation plan,
|
|
@@ -80,6 +87,8 @@ const Shape = t.object({
|
|
|
80
87
|
- `name` may be absent. If `name` exists, its value must be a string.
|
|
81
88
|
- `nickname` must be present. Its value may be a string or `undefined`.
|
|
82
89
|
- `t.nullable(inner)` adds `null` to the value domain.
|
|
90
|
+
- `t.nullish(inner)` combines nullable value semantics with optional object-key
|
|
91
|
+
presence.
|
|
83
92
|
- Presence-preserving wrappers keep optional-key semantics through `nullable`,
|
|
84
93
|
`undefinedable`, `brand`, and `refine`.
|
|
85
94
|
|
|
@@ -87,6 +96,18 @@ Object combinators preserve object mode. Strict object guards remain strict
|
|
|
87
96
|
after `extend`, `pick`, `omit`, or `partial`; passthrough object guards keep
|
|
88
97
|
allowing unknown keys.
|
|
89
98
|
|
|
99
|
+
`catchall(schema)` validates every undeclared own key with `schema`.
|
|
100
|
+
`strip()` is validation-only in TypeSea: guards return the original value, so it
|
|
101
|
+
has the same validation behavior as `passthrough()`.
|
|
102
|
+
`pick` and `omit` accept either key arrays or Zod-style `{ key: true }` masks.
|
|
103
|
+
`deepPartial()` recursively partializes pure object, array, tuple, tuple rest,
|
|
104
|
+
record, map, set, property, union, intersection, nullable, undefinedable,
|
|
105
|
+
optional, and brand schemas. Lazy and refinement schemas are semantic barriers.
|
|
106
|
+
|
|
107
|
+
`property` validates only own data descriptors. It is useful for class instances
|
|
108
|
+
with stable fields; prototype getters and accessor-backed properties are rejected
|
|
109
|
+
instead of executed.
|
|
110
|
+
|
|
90
111
|
## Composition
|
|
91
112
|
|
|
92
113
|
`t.union(a, b)` accepts a value that satisfies at least one branch.
|
|
@@ -125,6 +146,22 @@ lazy chain must eventually resolve to a concrete non-lazy schema.
|
|
|
125
146
|
```ts
|
|
126
147
|
const Count = t.pipe(t.coerce.number(), t.number.int().gte(0));
|
|
127
148
|
const result = Count.decode("42");
|
|
149
|
+
|
|
150
|
+
const Name = t.default(t.string.min(1), "anonymous");
|
|
151
|
+
const NormalizedName = t.string
|
|
152
|
+
.trim()
|
|
153
|
+
.pipe(t.string.min(1))
|
|
154
|
+
.transform((value) => value.toLowerCase())
|
|
155
|
+
.default("anonymous")
|
|
156
|
+
.catch("anonymous");
|
|
157
|
+
const NumberText = t.codec(
|
|
158
|
+
t.string.regex(/^\d+$/u, "digits"),
|
|
159
|
+
t.number.int().nonnegative(),
|
|
160
|
+
{
|
|
161
|
+
decode: (value) => Number(value),
|
|
162
|
+
encode: (value) => String(value)
|
|
163
|
+
}
|
|
164
|
+
);
|
|
128
165
|
```
|
|
129
166
|
|
|
130
167
|
Decoders are for output-producing operations. They return `Result` from
|
|
@@ -132,10 +169,16 @@ Decoders are for output-producing operations. They return `Result` from
|
|
|
132
169
|
not be the same runtime value as the input.
|
|
133
170
|
|
|
134
171
|
- `t.transform(source, mapper)` decodes `source`, then maps the decoded value.
|
|
135
|
-
- `t.pipe(source, next)` feeds a successful decoded value into the next guard or
|
|
136
|
-
|
|
172
|
+
- `t.pipe(source, next)` feeds a successful decoded value into the next guard or decoder.
|
|
173
|
+
- `t.default(source, value)` returns a fallback output for `undefined` input.
|
|
174
|
+
- `t.prefault(source, value)` feeds a fallback input through the source.
|
|
175
|
+
- `t.catch(source, value)` returns a fallback output after a failed decode.
|
|
176
|
+
- `t.codec(input, output, mapping)` validates both sides of a bidirectional decode/encode pair.
|
|
137
177
|
- `t.coerce.string`, `t.coerce.number`, and `t.coerce.boolean` provide explicit
|
|
138
178
|
primitive coercion.
|
|
179
|
+
- `t.string.trim()`, `t.string.toLowerCase()`, and `t.string.toUpperCase()`
|
|
180
|
+
are decoder helpers. They validate the string first, then return transformed
|
|
181
|
+
output from `decode()`.
|
|
139
182
|
- `t.asyncRefine`, `t.asyncTransform`, and `t.asyncPipe` return
|
|
140
183
|
`Promise<Result<T, Issue[]>>` from `decodeAsync()`.
|
|
141
184
|
|
|
@@ -152,14 +195,16 @@ const checked = withMessages(User.check(input), {
|
|
|
152
195
|
});
|
|
153
196
|
```
|
|
154
197
|
|
|
155
|
-
`formatIssue`, `formatIssues`, and `withMessages` render
|
|
156
|
-
validation has finished. This keeps `is()` and ordinary
|
|
157
|
-
message allocation.
|
|
198
|
+
`formatIssue`, `formatIssues`, `flattenIssues`, and `withMessages` render
|
|
199
|
+
diagnostics after validation has finished. This keeps `is()` and ordinary
|
|
200
|
+
`check()` paths free from message allocation.
|
|
158
201
|
|
|
159
202
|
Built-in locales are `en` and `ko`. Custom catalogs can use string templates
|
|
160
203
|
with `{path}`, `{code}`, `{expected}`, and `{actual}`, or formatter callbacks.
|
|
161
204
|
`withMessages(result, options)` preserves successful results and returns a new
|
|
162
205
|
failed `Result` with copied, frozen issues whose `message` fields are populated.
|
|
206
|
+
`flattenIssues(issues, options)` groups rendered messages into `formErrors` and
|
|
207
|
+
top-level `fieldErrors` buckets.
|
|
163
208
|
|
|
164
209
|
## Runtime Compile
|
|
165
210
|
|
|
@@ -270,6 +315,29 @@ const resolver = toReactHookFormResolver(User);
|
|
|
270
315
|
Adapters are structural and zero-dependency. TypeSea does not import tRPC,
|
|
271
316
|
Fastify, or React Hook Form.
|
|
272
317
|
|
|
318
|
+
Compiled guards can be passed to the same adapters. This is the preferred shape
|
|
319
|
+
for hot request paths: compile once during startup, then let the adapter reuse
|
|
320
|
+
the generated predicate.
|
|
321
|
+
|
|
322
|
+
```ts
|
|
323
|
+
const FastUser = compile(User);
|
|
324
|
+
const fastParser = toTrpcParser(FastUser);
|
|
325
|
+
const fastValidatorCompiler = toFastifyValidatorCompiler(FastUser);
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
Use the default compiled mode at public input boundaries. It keeps the safe
|
|
329
|
+
descriptor-read contract even when an adapter hides the direct `is()` call. For
|
|
330
|
+
trusted, already-normalized internal data, the faster modes can be wired through
|
|
331
|
+
adapters the same way.
|
|
332
|
+
|
|
333
|
+
```ts
|
|
334
|
+
const UnsafeUser = compile(User, { mode: "unsafe" });
|
|
335
|
+
const internalParser = toTrpcParser(UnsafeUser);
|
|
336
|
+
|
|
337
|
+
const TrustedShapeUser = compile(User, { mode: "unchecked" });
|
|
338
|
+
const internalValidatorCompiler = toFastifyValidatorCompiler(TrustedShapeUser);
|
|
339
|
+
```
|
|
340
|
+
|
|
273
341
|
| Adapter | Export | Behavior |
|
|
274
342
|
| --- | --- | --- |
|
|
275
343
|
| tRPC | `toTrpcParser`, `toAsyncTrpcParser` | Return parser objects that emit decoded values or throw `TypeSeaAssertionError`. |
|
|
@@ -314,6 +382,7 @@ Runtime-only concepts return explicit export issues:
|
|
|
314
382
|
- `undefined`
|
|
315
383
|
- `bigint`
|
|
316
384
|
- `symbol`
|
|
385
|
+
- JavaScript `Date`, `Map`, `Set`, `instanceOf`, and `property` contracts
|
|
317
386
|
- `lazy`
|
|
318
387
|
- `refine`
|
|
319
388
|
- decoder transforms
|
|
@@ -58,92 +58,92 @@
|
|
|
58
58
|
<circle cx="84" cy="690" r="220" fill="#38bdf8" opacity="0.07"/>
|
|
59
59
|
|
|
60
60
|
<text id="headline" x="44" y="58" class="title">TypeSea benchmark comparison</text>
|
|
61
|
-
<text x="46" y="88" class="subtitle">Local run on 2026-07-
|
|
61
|
+
<text x="46" y="88" class="subtitle">Local run on 2026-07-05 KST, strict-object contract, ops/sec. Higher is better.</text>
|
|
62
62
|
|
|
63
63
|
<g filter="url(#shadow)">
|
|
64
64
|
<rect x="44" y="118" width="322" height="108" rx="18" fill="url(#panel)" stroke="#1f2937"/>
|
|
65
65
|
<text x="66" y="148" class="metric-label">Unchecked valid hot path</text>
|
|
66
|
-
<text x="66" y="188" class="metric-value">42.
|
|
67
|
-
<text x="66" y="210" class="metric-sub">
|
|
66
|
+
<text x="66" y="188" class="metric-value">42.62M</text>
|
|
67
|
+
<text x="66" y="210" class="metric-sub">30.4x Zod, 10.1x Ajv</text>
|
|
68
68
|
|
|
69
69
|
<rect x="399" y="118" width="322" height="108" rx="18" fill="url(#panel)" stroke="#1f2937"/>
|
|
70
70
|
<text x="421" y="148" class="metric-label">Safe invalid fast-fail</text>
|
|
71
|
-
<text x="421" y="188" class="metric-value">
|
|
72
|
-
<text x="421" y="210" class="metric-sub">1.
|
|
71
|
+
<text x="421" y="188" class="metric-value">43.09M</text>
|
|
72
|
+
<text x="421" y="210" class="metric-sub">1.41x Ajv, 509x Zod</text>
|
|
73
73
|
|
|
74
74
|
<rect x="754" y="118" width="322" height="108" rx="18" fill="url(#panel)" stroke="#1f2937"/>
|
|
75
75
|
<text x="776" y="148" class="metric-label">Safe valid path</text>
|
|
76
|
-
<text x="776" y="188" class="metric-value">
|
|
77
|
-
<text x="776" y="210" class="metric-sub">Ajv
|
|
76
|
+
<text x="776" y="188" class="metric-value">5.11M</text>
|
|
77
|
+
<text x="776" y="210" class="metric-sub">1.21x Ajv while staying safe</text>
|
|
78
78
|
</g>
|
|
79
79
|
|
|
80
80
|
<g filter="url(#shadow)">
|
|
81
81
|
<rect x="44" y="260" width="1032" height="136" rx="18" fill="url(#panel)" stroke="#1f2937"/>
|
|
82
82
|
<text x="66" y="291" class="card-title">Valid object path</text>
|
|
83
|
-
<text x="230" y="291" class="card-note">linear scale to 42.
|
|
83
|
+
<text x="230" y="291" class="card-note">linear scale to 42.62M</text>
|
|
84
84
|
<line x1="270" y1="318" x2="1010" y2="318" class="axis"/>
|
|
85
85
|
|
|
86
86
|
<text x="66" y="326" class="bar-label">TypeSea unchecked</text>
|
|
87
87
|
<rect x="270" y="312" width="740" height="15" rx="7.5" fill="url(#unchecked)"/>
|
|
88
|
-
<text x="1024" y="326" class="bar-value">42.
|
|
88
|
+
<text x="1024" y="326" class="bar-value">42.62M</text>
|
|
89
89
|
|
|
90
90
|
<text x="66" y="350" class="bar-label">TypeSea unsafe</text>
|
|
91
|
-
<rect x="270" y="336" width="
|
|
92
|
-
<text x="
|
|
91
|
+
<rect x="270" y="336" width="639" height="15" rx="7.5" fill="url(#unsafe)"/>
|
|
92
|
+
<text x="923" y="350" class="bar-value">36.78M</text>
|
|
93
93
|
|
|
94
94
|
<text x="66" y="374" class="bar-label">TypeSea safe / Ajv / Zod</text>
|
|
95
|
-
<rect x="270" y="360" width="
|
|
96
|
-
<rect x="
|
|
97
|
-
<rect x="
|
|
98
|
-
<rect x="
|
|
99
|
-
<text x="
|
|
95
|
+
<rect x="270" y="360" width="89" height="15" rx="7.5" fill="url(#safe)"/>
|
|
96
|
+
<rect x="368" y="360" width="74" height="15" rx="7.5" fill="url(#ajv)"/>
|
|
97
|
+
<rect x="451" y="360" width="24" height="15" rx="7.5" fill="url(#valibot)"/>
|
|
98
|
+
<rect x="484" y="360" width="24" height="15" rx="7.5" fill="url(#zod)"/>
|
|
99
|
+
<text x="522" y="374" class="bar-value">safe 5.11M, Ajv 4.24M, Valibot 1.40M, Zod 1.40M</text>
|
|
100
100
|
</g>
|
|
101
101
|
|
|
102
102
|
<g filter="url(#shadow)">
|
|
103
103
|
<rect x="44" y="418" width="1032" height="150" rx="18" fill="url(#panel)" stroke="#1f2937"/>
|
|
104
104
|
<text x="66" y="449" class="card-title">Invalid object fast-fail</text>
|
|
105
|
-
<text x="268" y="449" class="card-note">linear scale to 50.
|
|
105
|
+
<text x="268" y="449" class="card-note">linear scale to 50.90M</text>
|
|
106
106
|
<line x1="270" y1="476" x2="1010" y2="476" class="axis"/>
|
|
107
107
|
|
|
108
108
|
<text x="66" y="482" class="bar-label">TypeSea unchecked</text>
|
|
109
109
|
<rect x="270" y="468" width="740" height="15" rx="7.5" fill="url(#unchecked)"/>
|
|
110
|
-
<text x="1024" y="482" class="bar-value">50.
|
|
110
|
+
<text x="1024" y="482" class="bar-value">50.90M</text>
|
|
111
111
|
|
|
112
112
|
<text x="66" y="506" class="bar-label">TypeSea unsafe</text>
|
|
113
|
-
<rect x="270" y="492" width="
|
|
114
|
-
<text x="1024" y="506" class="bar-value">
|
|
113
|
+
<rect x="270" y="492" width="738" height="15" rx="7.5" fill="url(#unsafe)"/>
|
|
114
|
+
<text x="1024" y="506" class="bar-value">50.74M</text>
|
|
115
115
|
|
|
116
116
|
<text x="66" y="530" class="bar-label">TypeSea safe</text>
|
|
117
|
-
<rect x="270" y="516" width="
|
|
118
|
-
<text x="
|
|
117
|
+
<rect x="270" y="516" width="627" height="15" rx="7.5" fill="url(#safe)"/>
|
|
118
|
+
<text x="912" y="530" class="bar-value">43.09M</text>
|
|
119
119
|
|
|
120
120
|
<text x="66" y="554" class="bar-label">Ajv / Valibot / Zod</text>
|
|
121
|
-
<rect x="270" y="540" width="
|
|
122
|
-
<rect x="
|
|
123
|
-
<rect x="
|
|
124
|
-
<text x="
|
|
121
|
+
<rect x="270" y="540" width="444" height="15" rx="7.5" fill="url(#ajv)"/>
|
|
122
|
+
<rect x="726" y="540" width="13" height="15" rx="7.5" fill="url(#valibot)"/>
|
|
123
|
+
<rect x="750" y="540" width="3" height="15" rx="1.5" fill="url(#zod)"/>
|
|
124
|
+
<text x="770" y="554" class="bar-value">Ajv 30.54M, Valibot 0.87M, Zod 0.08M</text>
|
|
125
125
|
</g>
|
|
126
126
|
|
|
127
127
|
<g filter="url(#shadow)">
|
|
128
128
|
<rect x="44" y="576" width="1032" height="136" rx="18" fill="url(#panel)" stroke="#1f2937"/>
|
|
129
129
|
<text x="66" y="607" class="card-title">Invalid diagnostic path</text>
|
|
130
|
-
<text x="270" y="607" class="card-note">linear scale to
|
|
130
|
+
<text x="270" y="607" class="card-note">linear scale to 29.95M; Ajv is a boolean baseline</text>
|
|
131
131
|
<line x1="270" y1="634" x2="1010" y2="634" class="axis"/>
|
|
132
132
|
|
|
133
133
|
<text x="66" y="642" class="bar-label">Ajv compiled</text>
|
|
134
134
|
<rect x="270" y="628" width="740" height="15" rx="7.5" fill="url(#ajv)"/>
|
|
135
|
-
<text x="1024" y="642" class="bar-value">
|
|
135
|
+
<text x="1024" y="642" class="bar-value">29.95M</text>
|
|
136
136
|
|
|
137
137
|
<text x="66" y="666" class="bar-label">TypeSea modes</text>
|
|
138
|
-
<rect x="270" y="652" width="
|
|
138
|
+
<rect x="270" y="652" width="87" height="15" rx="7.5" fill="url(#unchecked)"/>
|
|
139
139
|
<rect x="374" y="652" width="79" height="15" rx="7.5" fill="url(#unsafe)"/>
|
|
140
|
-
<rect x="462" y="652" width="
|
|
141
|
-
<text x="532" y="666" class="bar-value">unchecked 3.
|
|
140
|
+
<rect x="462" y="652" width="52" height="15" rx="7.5" fill="url(#safe)"/>
|
|
141
|
+
<text x="532" y="666" class="bar-value">unchecked 3.51M, unsafe 3.19M, safe 2.11M</text>
|
|
142
142
|
|
|
143
143
|
<text x="66" y="690" class="bar-label">Valibot / Zod</text>
|
|
144
|
-
<rect x="270" y="676" width="
|
|
144
|
+
<rect x="270" y="676" width="20" height="15" rx="7.5" fill="url(#valibot)"/>
|
|
145
145
|
<rect x="302" y="676" width="3" height="15" rx="1.5" fill="url(#zod)"/>
|
|
146
|
-
<text x="322" y="690" class="bar-value">Valibot 0.
|
|
146
|
+
<text x="322" y="690" class="bar-value">Valibot 0.79M, Zod 0.09M</text>
|
|
147
147
|
</g>
|
|
148
148
|
|
|
149
149
|
<g transform="translate(44 730)">
|
package/docs/engine-notes.md
CHANGED
|
@@ -154,6 +154,10 @@ still checking the original object fields on the outer frame.
|
|
|
154
154
|
Compiled `lazy` and `refine` fallbacks use the same IR-backed runtime path, so
|
|
155
155
|
recursive behavior stays consistent across execution engines.
|
|
156
156
|
|
|
157
|
+
`checkFirst()` has a separate generated collector. It returns one frozen issue
|
|
158
|
+
as soon as the first diagnostic is known, instead of running the full `check()`
|
|
159
|
+
collector and truncating its issue array.
|
|
160
|
+
|
|
157
161
|
## JSON Schema Export
|
|
158
162
|
|
|
159
163
|
JSON Schema export succeeds only when the TypeSea schema can be represented over
|
|
@@ -181,15 +185,15 @@ Zod, Valibot, and Ajv are dev dependencies for measurement only. They are not
|
|
|
181
185
|
imported by `src`, and package policy rejects runtime, peer, optional, or
|
|
182
186
|
bundled dependency fields before release.
|
|
183
187
|
|
|
184
|
-
Last local benchmark on 2026-07-
|
|
188
|
+
Last local benchmark on 2026-07-05 KST reported these ecosystem paths over the
|
|
185
189
|
JSON-compatible strict-object benchmark:
|
|
186
190
|
|
|
187
191
|
| Case | TypeSea runtime plan | TypeSea compiled safe | TypeSea compiled unsafe | TypeSea compiled unchecked | Ajv compiled |
|
|
188
192
|
| --- | ---: | ---: | ---: | ---: | ---: |
|
|
189
|
-
| Valid `is()` |
|
|
190
|
-
| Valid `check()` |
|
|
191
|
-
| Invalid `is()` | 3,
|
|
192
|
-
| Invalid `check()` |
|
|
193
|
+
| Valid `is()` | 478,576 hz | 5,109,602 hz | 36,777,097 hz | 42,620,570 hz | 4,238,036 hz |
|
|
194
|
+
| Valid `check()` | 424,989 hz | 4,642,948 hz | 37,184,199 hz | 42,487,325 hz | 4,338,063 hz |
|
|
195
|
+
| Invalid `is()` | 3,325,603 hz | 43,094,061 hz | 50,738,235 hz | 50,898,012 hz | 30,535,761 hz |
|
|
196
|
+
| Invalid `check()` | 405,590 hz | 2,107,460 hz | 3,186,702 hz | 3,509,673 hz | 29,951,403 hz |
|
|
193
197
|
|
|
194
198
|
Benchmark numbers are machine-local telemetry. They are useful for catching
|
|
195
199
|
regressions, not for promising a fixed throughput floor. Unsafe and unchecked
|