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.
Files changed (159) hide show
  1. package/CHANGELOG.md +40 -0
  2. package/README.md +104 -41
  3. package/SECURITY.md +52 -0
  4. package/dist/aot/index.d.ts +1 -1
  5. package/dist/aot/index.d.ts.map +1 -1
  6. package/dist/aot/index.js +22 -3
  7. package/dist/builders/composite.d.ts +6 -3
  8. package/dist/builders/composite.d.ts.map +1 -1
  9. package/dist/builders/composite.js +22 -13
  10. package/dist/builders/index.d.ts +6 -5
  11. package/dist/builders/index.d.ts.map +1 -1
  12. package/dist/builders/index.js +5 -4
  13. package/dist/builders/modifier.d.ts +6 -0
  14. package/dist/builders/modifier.d.ts.map +1 -1
  15. package/dist/builders/modifier.js +14 -0
  16. package/dist/builders/object/guard.d.ts +54 -2
  17. package/dist/builders/object/guard.d.ts.map +1 -1
  18. package/dist/builders/object/guard.js +117 -7
  19. package/dist/builders/object/index.d.ts +2 -2
  20. package/dist/builders/object/index.d.ts.map +1 -1
  21. package/dist/builders/object/index.js +1 -1
  22. package/dist/builders/object/schema.d.ts +33 -2
  23. package/dist/builders/object/schema.d.ts.map +1 -1
  24. package/dist/builders/object/schema.js +198 -8
  25. package/dist/builders/object/types.d.ts +15 -0
  26. package/dist/builders/object/types.d.ts.map +1 -1
  27. package/dist/builders/runtime.d.ts +40 -0
  28. package/dist/builders/runtime.d.ts.map +1 -0
  29. package/dist/builders/runtime.js +150 -0
  30. package/dist/builders/scalar.d.ts +20 -1
  31. package/dist/builders/scalar.d.ts.map +1 -1
  32. package/dist/builders/scalar.js +54 -1
  33. package/dist/builders/table.d.ts +31 -5
  34. package/dist/builders/table.d.ts.map +1 -1
  35. package/dist/builders/table.js +31 -5
  36. package/dist/builders/types.d.ts +6 -0
  37. package/dist/builders/types.d.ts.map +1 -1
  38. package/dist/compile/check-composite.d.ts +3 -1
  39. package/dist/compile/check-composite.d.ts.map +1 -1
  40. package/dist/compile/check-composite.js +143 -11
  41. package/dist/compile/check-scalar.d.ts +10 -0
  42. package/dist/compile/check-scalar.d.ts.map +1 -1
  43. package/dist/compile/check-scalar.js +138 -2
  44. package/dist/compile/check.d.ts.map +1 -1
  45. package/dist/compile/check.js +25 -3
  46. package/dist/compile/context.d.ts.map +1 -1
  47. package/dist/compile/context.js +2 -0
  48. package/dist/compile/first.d.ts +26 -0
  49. package/dist/compile/first.d.ts.map +1 -0
  50. package/dist/compile/first.js +850 -0
  51. package/dist/compile/graph-predicate.d.ts.map +1 -1
  52. package/dist/compile/graph-predicate.js +389 -18
  53. package/dist/compile/guard.d.ts +2 -1
  54. package/dist/compile/guard.d.ts.map +1 -1
  55. package/dist/compile/guard.js +54 -8
  56. package/dist/compile/predicate.d.ts.map +1 -1
  57. package/dist/compile/predicate.js +156 -5
  58. package/dist/compile/runtime.d.ts +20 -1
  59. package/dist/compile/runtime.d.ts.map +1 -1
  60. package/dist/compile/runtime.js +31 -0
  61. package/dist/compile/source.d.ts.map +1 -1
  62. package/dist/compile/source.js +27 -3
  63. package/dist/compile/types.d.ts +2 -0
  64. package/dist/compile/types.d.ts.map +1 -1
  65. package/dist/decoder/index.d.ts +60 -0
  66. package/dist/decoder/index.d.ts.map +1 -1
  67. package/dist/decoder/index.js +164 -1
  68. package/dist/evaluate/check-composite.d.ts +52 -2
  69. package/dist/evaluate/check-composite.d.ts.map +1 -1
  70. package/dist/evaluate/check-composite.js +193 -6
  71. package/dist/evaluate/check-scalar.d.ts +9 -0
  72. package/dist/evaluate/check-scalar.d.ts.map +1 -1
  73. package/dist/evaluate/check-scalar.js +92 -3
  74. package/dist/evaluate/check.d.ts.map +1 -1
  75. package/dist/evaluate/check.js +19 -4
  76. package/dist/evaluate/shared.d.ts +19 -0
  77. package/dist/evaluate/shared.d.ts.map +1 -1
  78. package/dist/evaluate/shared.js +35 -0
  79. package/dist/guard/array.d.ts +48 -0
  80. package/dist/guard/array.d.ts.map +1 -0
  81. package/dist/guard/array.js +84 -0
  82. package/dist/guard/base.d.ts +32 -2
  83. package/dist/guard/base.d.ts.map +1 -1
  84. package/dist/guard/base.js +74 -3
  85. package/dist/guard/date.d.ts +34 -0
  86. package/dist/guard/date.d.ts.map +1 -0
  87. package/dist/guard/date.js +60 -0
  88. package/dist/guard/index.d.ts +2 -0
  89. package/dist/guard/index.d.ts.map +1 -1
  90. package/dist/guard/index.js +2 -0
  91. package/dist/guard/number.d.ts +60 -0
  92. package/dist/guard/number.d.ts.map +1 -1
  93. package/dist/guard/number.js +129 -0
  94. package/dist/guard/read.d.ts +53 -1
  95. package/dist/guard/read.d.ts.map +1 -1
  96. package/dist/guard/read.js +102 -0
  97. package/dist/guard/string.d.ts +82 -0
  98. package/dist/guard/string.d.ts.map +1 -1
  99. package/dist/guard/string.js +213 -0
  100. package/dist/guard/types.d.ts +18 -0
  101. package/dist/guard/types.d.ts.map +1 -1
  102. package/dist/index.d.ts +4 -4
  103. package/dist/index.d.ts.map +1 -1
  104. package/dist/index.js +4 -4
  105. package/dist/ir/builder.d.ts +3 -3
  106. package/dist/ir/builder.d.ts.map +1 -1
  107. package/dist/ir/builder.js +5 -2
  108. package/dist/ir/freeze.js +7 -0
  109. package/dist/ir/types.d.ts +4 -1
  110. package/dist/ir/types.d.ts.map +1 -1
  111. package/dist/ir/validate.d.ts.map +1 -1
  112. package/dist/ir/validate.js +35 -1
  113. package/dist/issue/index.d.ts +1 -1
  114. package/dist/issue/index.d.ts.map +1 -1
  115. package/dist/issue/index.js +4 -0
  116. package/dist/json-schema/emit-composite.d.ts +6 -2
  117. package/dist/json-schema/emit-composite.d.ts.map +1 -1
  118. package/dist/json-schema/emit-composite.js +66 -12
  119. package/dist/json-schema/emit-scalar.d.ts.map +1 -1
  120. package/dist/json-schema/emit-scalar.js +54 -1
  121. package/dist/json-schema/emit.d.ts.map +1 -1
  122. package/dist/json-schema/emit.js +11 -2
  123. package/dist/json-schema/types.d.ts +7 -1
  124. package/dist/json-schema/types.d.ts.map +1 -1
  125. package/dist/kind/index.d.ts +25 -0
  126. package/dist/kind/index.d.ts.map +1 -1
  127. package/dist/kind/index.js +26 -3
  128. package/dist/lower/index.d.ts.map +1 -1
  129. package/dist/lower/index.js +52 -3
  130. package/dist/message/index.d.ts +18 -0
  131. package/dist/message/index.d.ts.map +1 -1
  132. package/dist/message/index.js +67 -0
  133. package/dist/optimize/domain.js +6 -2
  134. package/dist/optimize/map-node.d.ts.map +1 -1
  135. package/dist/optimize/map-node.js +3 -0
  136. package/dist/plan/cache.js +13 -1
  137. package/dist/plan/predicate.d.ts.map +1 -1
  138. package/dist/plan/predicate.js +33 -3
  139. package/dist/plan/schema-predicate.d.ts.map +1 -1
  140. package/dist/plan/schema-predicate.js +267 -8
  141. package/dist/schema/freeze.js +22 -0
  142. package/dist/schema/index.d.ts +2 -2
  143. package/dist/schema/index.d.ts.map +1 -1
  144. package/dist/schema/index.js +1 -1
  145. package/dist/schema/types.d.ts +89 -4
  146. package/dist/schema/types.d.ts.map +1 -1
  147. package/dist/schema/types.js +8 -1
  148. package/dist/schema/undefined.d.ts.map +1 -1
  149. package/dist/schema/undefined.js +5 -0
  150. package/dist/schema/validate.d.ts.map +1 -1
  151. package/dist/schema/validate.js +111 -4
  152. package/docs/api.md +79 -10
  153. package/docs/assets/benchmark-headline.svg +33 -33
  154. package/docs/engine-notes.md +9 -5
  155. package/docs/index.html +1366 -722
  156. package/docs/ko/api.md +383 -0
  157. package/docs/ko/engine-notes.md +156 -0
  158. package/docs/ko/readme.md +404 -0
  159. package/package.json +6 -2
@@ -1 +1 @@
1
- {"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../../src/schema/validate.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAoBH,OAAO,KAAK,EAER,MAAM,EAET,MAAM,YAAY,CAAC;AAEpB;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,MAAM,CAK7D"}
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"}
@@ -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
- | Literals and containers | `t.literal(value)`, `t.array(item)`, `t.tuple([a, b])`, `t.record(value)` |
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
- decoder.
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 diagnostics after
156
- validation has finished. This keeps `is()` and ordinary `check()` paths free from
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-04 KST, strict-object contract, ops/sec. Higher is better.</text>
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.58M</text>
67
- <text x="66" y="210" class="metric-sub">31.7x Zod, 10.0x Ajv</text>
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">42.08M</text>
72
- <text x="421" y="210" class="metric-sub">1.51x Ajv, 499x Zod</text>
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">4.30M</text>
77
- <text x="776" y="210" class="metric-sub">Ajv-class while staying safe</text>
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.58M</text>
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.58M</text>
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="631" height="15" rx="7.5" fill="url(#unsafe)"/>
92
- <text x="915" y="350" class="bar-value">36.30M</text>
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="75" height="15" rx="7.5" fill="url(#safe)"/>
96
- <rect x="354" y="360" width="74" height="15" rx="7.5" fill="url(#ajv)"/>
97
- <rect x="437" y="360" width="24" height="15" rx="7.5" fill="url(#valibot)"/>
98
- <rect x="470" y="360" width="23" height="15" rx="7.5" fill="url(#zod)"/>
99
- <text x="506" y="374" class="bar-value">safe 4.30M, Ajv 4.28M, Valibot 1.41M, Zod 1.34M</text>
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.48M</text>
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.48M</text>
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="728" height="15" rx="7.5" fill="url(#unsafe)"/>
114
- <text x="1024" y="506" class="bar-value">49.65M</text>
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="617" height="15" rx="7.5" fill="url(#safe)"/>
118
- <text x="902" y="530" class="bar-value">42.08M</text>
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="408" height="15" rx="7.5" fill="url(#ajv)"/>
122
- <rect x="690" y="540" width="13" height="15" rx="7.5" fill="url(#valibot)"/>
123
- <rect x="714" y="540" width="3" height="15" rx="1.5" fill="url(#zod)"/>
124
- <text x="734" y="554" class="bar-value">Ajv 27.82M, Valibot 0.88M, Zod 0.08M</text>
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 28.71M; Ajv is a boolean baseline</text>
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">28.71M</text>
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="95" height="15" rx="7.5" fill="url(#unchecked)"/>
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="54" height="15" rx="7.5" fill="url(#safe)"/>
141
- <text x="532" y="666" class="bar-value">unchecked 3.67M, unsafe 3.08M, safe 2.09M</text>
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="23" height="15" rx="7.5" fill="url(#valibot)"/>
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.89M, Zod 0.08M</text>
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)">
@@ -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-04 KST reported these ecosystem paths over the
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()` | 513,701 hz | 4,297,306 hz | 36,297,653 hz | 42,581,174 hz | 4,275,389 hz |
190
- | Valid `check()` | 503,232 hz | 3,903,929 hz | 35,568,425 hz | 40,084,605 hz | 4,278,587 hz |
191
- | Invalid `is()` | 3,636,369 hz | 42,080,241 hz | 49,654,076 hz | 50,482,732 hz | 27,820,643 hz |
192
- | Invalid `check()` | 420,446 hz | 2,086,129 hz | 3,077,367 hz | 3,673,508 hz | 28,713,035 hz |
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