typesea 0.1.0 → 0.3.0

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 (301) hide show
  1. package/CHANGELOG.md +85 -6
  2. package/README.md +143 -28
  3. package/dist/adapters/index.d.ts +50 -8
  4. package/dist/adapters/index.d.ts.map +1 -1
  5. package/dist/adapters/index.js +169 -48
  6. package/dist/aot/index.d.ts +19 -3
  7. package/dist/aot/index.d.ts.map +1 -1
  8. package/dist/aot/index.js +115 -17
  9. package/dist/async/index.d.ts +28 -56
  10. package/dist/async/index.d.ts.map +1 -1
  11. package/dist/async/index.js +94 -37
  12. package/dist/builders/composite.d.ts +43 -9
  13. package/dist/builders/composite.d.ts.map +1 -1
  14. package/dist/builders/composite.js +100 -17
  15. package/dist/builders/index.d.ts +8 -5
  16. package/dist/builders/index.d.ts.map +1 -1
  17. package/dist/builders/index.js +7 -4
  18. package/dist/builders/modifier.d.ts +36 -5
  19. package/dist/builders/modifier.d.ts.map +1 -1
  20. package/dist/builders/modifier.js +52 -5
  21. package/dist/builders/object/guard.d.ts +72 -24
  22. package/dist/builders/object/guard.d.ts.map +1 -1
  23. package/dist/builders/object/guard.js +139 -29
  24. package/dist/builders/object/index.d.ts +4 -2
  25. package/dist/builders/object/index.d.ts.map +1 -1
  26. package/dist/builders/object/index.js +3 -1
  27. package/dist/builders/object/schema.d.ts +88 -11
  28. package/dist/builders/object/schema.d.ts.map +1 -1
  29. package/dist/builders/object/schema.js +290 -23
  30. package/dist/builders/object/types.d.ts +20 -31
  31. package/dist/builders/object/types.d.ts.map +1 -1
  32. package/dist/builders/object/types.js +2 -0
  33. package/dist/builders/runtime.d.ts +40 -0
  34. package/dist/builders/runtime.d.ts.map +1 -0
  35. package/dist/builders/runtime.js +150 -0
  36. package/dist/builders/scalar.d.ts +49 -9
  37. package/dist/builders/scalar.d.ts.map +1 -1
  38. package/dist/builders/scalar.js +87 -9
  39. package/dist/builders/table.d.ts +35 -5
  40. package/dist/builders/table.d.ts.map +1 -1
  41. package/dist/builders/table.js +35 -5
  42. package/dist/builders/types.d.ts +20 -4
  43. package/dist/builders/types.d.ts.map +1 -1
  44. package/dist/builders/types.js +2 -0
  45. package/dist/compile/check-composite.d.ts +25 -2
  46. package/dist/compile/check-composite.d.ts.map +1 -1
  47. package/dist/compile/check-composite.js +699 -27
  48. package/dist/compile/check-scalar.d.ts +88 -0
  49. package/dist/compile/check-scalar.d.ts.map +1 -1
  50. package/dist/compile/check-scalar.js +570 -3
  51. package/dist/compile/check.d.ts +12 -0
  52. package/dist/compile/check.d.ts.map +1 -1
  53. package/dist/compile/check.js +62 -3
  54. package/dist/compile/context.d.ts +47 -9
  55. package/dist/compile/context.d.ts.map +1 -1
  56. package/dist/compile/context.js +53 -8
  57. package/dist/compile/first.d.ts +26 -0
  58. package/dist/compile/first.d.ts.map +1 -0
  59. package/dist/compile/first.js +850 -0
  60. package/dist/compile/graph-predicate.d.ts +4 -2
  61. package/dist/compile/graph-predicate.d.ts.map +1 -1
  62. package/dist/compile/graph-predicate.js +2272 -165
  63. package/dist/compile/guard.d.ts +16 -24
  64. package/dist/compile/guard.d.ts.map +1 -1
  65. package/dist/compile/guard.js +202 -72
  66. package/dist/compile/index.d.ts +3 -1
  67. package/dist/compile/index.d.ts.map +1 -1
  68. package/dist/compile/index.js +2 -0
  69. package/dist/compile/issue.d.ts +110 -0
  70. package/dist/compile/issue.d.ts.map +1 -1
  71. package/dist/compile/issue.js +184 -1
  72. package/dist/compile/names.d.ts +12 -2
  73. package/dist/compile/names.d.ts.map +1 -1
  74. package/dist/compile/names.js +19 -3
  75. package/dist/compile/predicate.d.ts +24 -0
  76. package/dist/compile/predicate.d.ts.map +1 -1
  77. package/dist/compile/predicate.js +287 -10
  78. package/dist/compile/runtime.d.ts +100 -13
  79. package/dist/compile/runtime.d.ts.map +1 -1
  80. package/dist/compile/runtime.js +56 -6
  81. package/dist/compile/source.d.ts +10 -2
  82. package/dist/compile/source.d.ts.map +1 -1
  83. package/dist/compile/source.js +385 -26
  84. package/dist/compile/types.d.ts +22 -0
  85. package/dist/compile/types.d.ts.map +1 -1
  86. package/dist/compile/types.js +2 -0
  87. package/dist/decoder/index.d.ts +92 -46
  88. package/dist/decoder/index.d.ts.map +1 -1
  89. package/dist/decoder/index.js +266 -39
  90. package/dist/evaluate/check-composite.d.ts +111 -2
  91. package/dist/evaluate/check-composite.d.ts.map +1 -1
  92. package/dist/evaluate/check-composite.js +343 -8
  93. package/dist/evaluate/check-scalar.d.ts +25 -0
  94. package/dist/evaluate/check-scalar.d.ts.map +1 -1
  95. package/dist/evaluate/check-scalar.js +124 -3
  96. package/dist/evaluate/check.d.ts +7 -0
  97. package/dist/evaluate/check.d.ts.map +1 -1
  98. package/dist/evaluate/check.js +62 -4
  99. package/dist/evaluate/index.d.ts +2 -0
  100. package/dist/evaluate/index.d.ts.map +1 -1
  101. package/dist/evaluate/index.js +2 -0
  102. package/dist/evaluate/issue.d.ts +11 -1
  103. package/dist/evaluate/issue.d.ts.map +1 -1
  104. package/dist/evaluate/issue.js +15 -1
  105. package/dist/evaluate/predicate.d.ts +16 -5
  106. package/dist/evaluate/predicate.d.ts.map +1 -1
  107. package/dist/evaluate/predicate.js +20 -5
  108. package/dist/evaluate/shared.d.ts +78 -13
  109. package/dist/evaluate/shared.d.ts.map +1 -1
  110. package/dist/evaluate/shared.js +101 -8
  111. package/dist/evaluate/state.d.ts +35 -13
  112. package/dist/evaluate/state.d.ts.map +1 -1
  113. package/dist/evaluate/state.js +35 -2
  114. package/dist/guard/array.d.ts +48 -0
  115. package/dist/guard/array.d.ts.map +1 -0
  116. package/dist/guard/array.js +84 -0
  117. package/dist/guard/base.d.ts +111 -31
  118. package/dist/guard/base.d.ts.map +1 -1
  119. package/dist/guard/base.js +165 -32
  120. package/dist/guard/date.d.ts +34 -0
  121. package/dist/guard/date.d.ts.map +1 -0
  122. package/dist/guard/date.js +60 -0
  123. package/dist/guard/error.d.ts +10 -5
  124. package/dist/guard/error.d.ts.map +1 -1
  125. package/dist/guard/error.js +10 -5
  126. package/dist/guard/index.d.ts +4 -0
  127. package/dist/guard/index.d.ts.map +1 -1
  128. package/dist/guard/index.js +4 -0
  129. package/dist/guard/number.d.ts +86 -11
  130. package/dist/guard/number.d.ts.map +1 -1
  131. package/dist/guard/number.js +159 -11
  132. package/dist/guard/props.d.ts +27 -3
  133. package/dist/guard/props.d.ts.map +1 -1
  134. package/dist/guard/props.js +27 -3
  135. package/dist/guard/read.d.ts +115 -10
  136. package/dist/guard/read.d.ts.map +1 -1
  137. package/dist/guard/read.js +185 -10
  138. package/dist/guard/registry.d.ts +12 -2
  139. package/dist/guard/registry.d.ts.map +1 -1
  140. package/dist/guard/registry.js +15 -3
  141. package/dist/guard/string.d.ts +115 -13
  142. package/dist/guard/string.d.ts.map +1 -1
  143. package/dist/guard/string.js +250 -13
  144. package/dist/guard/types.d.ts +110 -40
  145. package/dist/guard/types.d.ts.map +1 -1
  146. package/dist/guard/types.js +2 -0
  147. package/dist/index.d.ts +5 -5
  148. package/dist/index.d.ts.map +1 -1
  149. package/dist/index.js +4 -4
  150. package/dist/internal/index.d.ts +42 -6
  151. package/dist/internal/index.d.ts.map +1 -1
  152. package/dist/internal/index.js +51 -8
  153. package/dist/ir/builder.d.ts +17 -127
  154. package/dist/ir/builder.d.ts.map +1 -1
  155. package/dist/ir/builder.js +80 -137
  156. package/dist/ir/freeze.d.ts +4 -0
  157. package/dist/ir/freeze.d.ts.map +1 -1
  158. package/dist/ir/freeze.js +66 -0
  159. package/dist/ir/index.d.ts +3 -1
  160. package/dist/ir/index.d.ts.map +1 -1
  161. package/dist/ir/index.js +2 -0
  162. package/dist/ir/regexp.d.ts +2 -0
  163. package/dist/ir/regexp.d.ts.map +1 -1
  164. package/dist/ir/regexp.js +2 -0
  165. package/dist/ir/types.d.ts +94 -56
  166. package/dist/ir/types.d.ts.map +1 -1
  167. package/dist/ir/types.js +2 -0
  168. package/dist/ir/validate.d.ts +8 -1
  169. package/dist/ir/validate.d.ts.map +1 -1
  170. package/dist/ir/validate.js +511 -61
  171. package/dist/issue/index.d.ts +42 -10
  172. package/dist/issue/index.d.ts.map +1 -1
  173. package/dist/issue/index.js +65 -11
  174. package/dist/json-schema/emit-combinator.d.ts +44 -4
  175. package/dist/json-schema/emit-combinator.d.ts.map +1 -1
  176. package/dist/json-schema/emit-combinator.js +44 -4
  177. package/dist/json-schema/emit-composite.d.ts +16 -2
  178. package/dist/json-schema/emit-composite.d.ts.map +1 -1
  179. package/dist/json-schema/emit-composite.js +81 -13
  180. package/dist/json-schema/emit-scalar.d.ts +26 -3
  181. package/dist/json-schema/emit-scalar.d.ts.map +1 -1
  182. package/dist/json-schema/emit-scalar.js +124 -10
  183. package/dist/json-schema/emit-types.d.ts +11 -1
  184. package/dist/json-schema/emit-types.d.ts.map +1 -1
  185. package/dist/json-schema/emit-types.js +2 -0
  186. package/dist/json-schema/emit.d.ts +12 -1
  187. package/dist/json-schema/emit.d.ts.map +1 -1
  188. package/dist/json-schema/emit.js +23 -3
  189. package/dist/json-schema/freeze.d.ts +13 -2
  190. package/dist/json-schema/freeze.d.ts.map +1 -1
  191. package/dist/json-schema/freeze.js +41 -8
  192. package/dist/json-schema/index.d.ts +16 -2
  193. package/dist/json-schema/index.d.ts.map +1 -1
  194. package/dist/json-schema/index.js +23 -3
  195. package/dist/json-schema/issue.d.ts +4 -1
  196. package/dist/json-schema/issue.d.ts.map +1 -1
  197. package/dist/json-schema/issue.js +4 -1
  198. package/dist/json-schema/read.d.ts +24 -3
  199. package/dist/json-schema/read.d.ts.map +1 -1
  200. package/dist/json-schema/read.js +59 -12
  201. package/dist/json-schema/types.d.ts +45 -16
  202. package/dist/json-schema/types.d.ts.map +1 -1
  203. package/dist/json-schema/types.js +2 -0
  204. package/dist/kind/index.d.ts +40 -28
  205. package/dist/kind/index.d.ts.map +1 -1
  206. package/dist/kind/index.js +41 -13
  207. package/dist/lower/index.d.ts +6 -1
  208. package/dist/lower/index.d.ts.map +1 -1
  209. package/dist/lower/index.js +462 -46
  210. package/dist/message/index.d.ts +64 -10
  211. package/dist/message/index.d.ts.map +1 -1
  212. package/dist/message/index.js +155 -17
  213. package/dist/optimize/algebraic.d.ts +54 -0
  214. package/dist/optimize/algebraic.d.ts.map +1 -0
  215. package/dist/optimize/algebraic.js +314 -0
  216. package/dist/optimize/compact.d.ts +8 -1
  217. package/dist/optimize/compact.d.ts.map +1 -1
  218. package/dist/optimize/compact.js +13 -2
  219. package/dist/optimize/domain.d.ts +16 -0
  220. package/dist/optimize/domain.d.ts.map +1 -0
  221. package/dist/optimize/domain.js +619 -0
  222. package/dist/optimize/fold-boolean.d.ts +17 -2
  223. package/dist/optimize/fold-boolean.d.ts.map +1 -1
  224. package/dist/optimize/fold-boolean.js +59 -14
  225. package/dist/optimize/fold-common.d.ts +43 -8
  226. package/dist/optimize/fold-common.d.ts.map +1 -1
  227. package/dist/optimize/fold-common.js +37 -6
  228. package/dist/optimize/fold-constraints.d.ts +33 -0
  229. package/dist/optimize/fold-constraints.d.ts.map +1 -0
  230. package/dist/optimize/fold-constraints.js +484 -0
  231. package/dist/optimize/fold-scalar.d.ts +98 -13
  232. package/dist/optimize/fold-scalar.d.ts.map +1 -1
  233. package/dist/optimize/fold-scalar.js +98 -13
  234. package/dist/optimize/fold.d.ts +8 -1
  235. package/dist/optimize/fold.d.ts.map +1 -1
  236. package/dist/optimize/fold.js +22 -2
  237. package/dist/optimize/index.d.ts +9 -1
  238. package/dist/optimize/index.d.ts.map +1 -1
  239. package/dist/optimize/index.js +18 -3
  240. package/dist/optimize/map-node.d.ts +3 -1
  241. package/dist/optimize/map-node.d.ts.map +1 -1
  242. package/dist/optimize/map-node.js +48 -3
  243. package/dist/optimize/peephole.d.ts +16 -0
  244. package/dist/optimize/peephole.d.ts.map +1 -0
  245. package/dist/optimize/peephole.js +254 -0
  246. package/dist/optimize/remap.d.ts +2 -0
  247. package/dist/optimize/remap.d.ts.map +1 -1
  248. package/dist/optimize/remap.js +2 -0
  249. package/dist/optimize/rewrite.d.ts +13 -8
  250. package/dist/optimize/rewrite.d.ts.map +1 -1
  251. package/dist/optimize/rewrite.js +13 -8
  252. package/dist/plan/cache.d.ts +9 -3
  253. package/dist/plan/cache.d.ts.map +1 -1
  254. package/dist/plan/cache.js +34 -6
  255. package/dist/plan/index.d.ts +2 -0
  256. package/dist/plan/index.d.ts.map +1 -1
  257. package/dist/plan/index.js +2 -0
  258. package/dist/plan/predicate.d.ts +2 -0
  259. package/dist/plan/predicate.d.ts.map +1 -1
  260. package/dist/plan/predicate.js +298 -29
  261. package/dist/plan/schema-predicate.d.ts +6 -0
  262. package/dist/plan/schema-predicate.d.ts.map +1 -1
  263. package/dist/plan/schema-predicate.js +382 -19
  264. package/dist/plan/types.d.ts +2 -0
  265. package/dist/plan/types.d.ts.map +1 -1
  266. package/dist/plan/types.js +2 -0
  267. package/dist/result/index.d.ts +19 -5
  268. package/dist/result/index.d.ts.map +1 -1
  269. package/dist/result/index.js +10 -2
  270. package/dist/schema/common.d.ts +69 -6
  271. package/dist/schema/common.d.ts.map +1 -1
  272. package/dist/schema/common.js +104 -10
  273. package/dist/schema/freeze.d.ts +4 -0
  274. package/dist/schema/freeze.d.ts.map +1 -1
  275. package/dist/schema/freeze.js +40 -0
  276. package/dist/schema/index.d.ts +5 -2
  277. package/dist/schema/index.d.ts.map +1 -1
  278. package/dist/schema/index.js +4 -1
  279. package/dist/schema/lazy.d.ts +4 -0
  280. package/dist/schema/lazy.d.ts.map +1 -1
  281. package/dist/schema/lazy.js +4 -0
  282. package/dist/schema/literal.d.ts +7 -1
  283. package/dist/schema/literal.d.ts.map +1 -1
  284. package/dist/schema/literal.js +7 -1
  285. package/dist/schema/types.d.ts +109 -100
  286. package/dist/schema/types.d.ts.map +1 -1
  287. package/dist/schema/types.js +13 -2
  288. package/dist/schema/undefined.d.ts +17 -0
  289. package/dist/schema/undefined.d.ts.map +1 -0
  290. package/dist/schema/undefined.js +77 -0
  291. package/dist/schema/validate.d.ts +8 -1
  292. package/dist/schema/validate.d.ts.map +1 -1
  293. package/dist/schema/validate.js +255 -57
  294. package/docs/api.md +128 -8
  295. package/docs/assets/benchmark-headline.svg +163 -0
  296. package/docs/engine-notes.md +62 -15
  297. package/docs/index.html +1340 -702
  298. package/docs/ko/api.md +375 -0
  299. package/docs/ko/engine-notes.md +156 -0
  300. package/docs/ko/readme.md +378 -0
  301. package/package.json +66 -65
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,16 @@ 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 NumberText = t.codec(
152
+ t.string.regex(/^\d+$/u, "digits"),
153
+ t.number.int().nonnegative(),
154
+ {
155
+ decode: (value) => Number(value),
156
+ encode: (value) => String(value)
157
+ }
158
+ );
128
159
  ```
129
160
 
130
161
  Decoders are for output-producing operations. They return `Result` from
@@ -134,8 +165,15 @@ not be the same runtime value as the input.
134
165
  - `t.transform(source, mapper)` decodes `source`, then maps the decoded value.
135
166
  - `t.pipe(source, next)` feeds a successful decoded value into the next guard or
136
167
  decoder.
168
+ - `t.default(source, value)` returns a fallback output for `undefined` input.
169
+ - `t.prefault(source, value)` feeds a fallback input through the source.
170
+ - `t.codec(input, output, mapping)` validates both sides of a bidirectional
171
+ decode/encode pair.
137
172
  - `t.coerce.string`, `t.coerce.number`, and `t.coerce.boolean` provide explicit
138
173
  primitive coercion.
174
+ - `t.string.trim()`, `t.string.toLowerCase()`, and `t.string.toUpperCase()`
175
+ are decoder helpers. They validate the string first, then return transformed
176
+ output from `decode()`.
139
177
  - `t.asyncRefine`, `t.asyncTransform`, and `t.asyncPipe` return
140
178
  `Promise<Result<T, Issue[]>>` from `decodeAsync()`.
141
179
 
@@ -152,14 +190,16 @@ const checked = withMessages(User.check(input), {
152
190
  });
153
191
  ```
154
192
 
155
- `formatIssue`, `formatIssues`, and `withMessages` render diagnostics after
156
- validation has finished. This keeps `is()` and ordinary `check()` paths free from
157
- message allocation.
193
+ `formatIssue`, `formatIssues`, `flattenIssues`, and `withMessages` render
194
+ diagnostics after validation has finished. This keeps `is()` and ordinary
195
+ `check()` paths free from message allocation.
158
196
 
159
197
  Built-in locales are `en` and `ko`. Custom catalogs can use string templates
160
198
  with `{path}`, `{code}`, `{expected}`, and `{actual}`, or formatter callbacks.
161
199
  `withMessages(result, options)` preserves successful results and returns a new
162
200
  failed `Result` with copied, frozen issues whose `message` fields are populated.
201
+ `flattenIssues(issues, options)` groups rendered messages into `formErrors` and
202
+ top-level `fieldErrors` buckets.
163
203
 
164
204
  ## Runtime Compile
165
205
 
@@ -187,10 +227,67 @@ Generated source never interpolates user-controlled values directly. Literals,
187
227
  regexps, property keys, keysets, and dynamic schema fallbacks are captured in
188
228
  side tables and referenced by numeric index.
189
229
 
230
+ ### Unsafe Compile Mode
231
+
232
+ ```ts
233
+ const FastButLooseUser = compile(User, {
234
+ name: "isUserFast",
235
+ mode: "unsafe"
236
+ });
237
+ ```
238
+
239
+ `CompileOptions["mode"]` and `AotCompileOptions["mode"]` are
240
+ `"safe" | "unsafe" | "unchecked" | undefined`; omitted options default to
241
+ `"safe"`. Safe mode keeps TypeSea's hostile-input contract: descriptor-based
242
+ property reads, no getter execution, and strict-object rejection for symbol and
243
+ non-enumerable extras.
244
+
245
+ Unsafe mode is an explicit performance escape hatch for trusted, normalized
246
+ plain data:
247
+
248
+ - Required object fields read with `value[key]` when the field schema rejects
249
+ `undefined`.
250
+ - Discriminant dispatch reads the tag with direct bracket access.
251
+ - Arrays and tuples read items with direct indexed loads.
252
+ - Strict-object extra-key rejection uses an allocation-free own-enumerable
253
+ `for...in` loop.
254
+
255
+ This may execute getters, may accept prototype-backed values, and does not
256
+ reject symbol or non-enumerable extras on strict objects. Because compiled
257
+ `check()` first trusts the generated predicate verdict, an unsafe predicate
258
+ that returns `true` also returns a successful `check()` result. Use unsafe mode
259
+ only after the input has crossed a trusted normalization boundary.
260
+
261
+ Unsafe mode may embed escaped static property keys directly into generated
262
+ predicate source so V8 can attach ordinary property-load inline caches. Safe
263
+ mode keeps property keys in side tables.
264
+
265
+ Unchecked mode uses the unsafe direct-read shape and also skips strict-object
266
+ extra-key loops. It is only for input whose object shape has already been
267
+ trusted or normalized; strict objects no longer reject extra keys in this mode.
268
+ Unsafe and unchecked compiled `check()` calls also return raw successful Result
269
+ objects without `Object.freeze()`. Failure diagnostics remain frozen. Safe mode
270
+ keeps frozen success and failure Result objects.
271
+ FastMode diagnostic collectors use direct field reads and FastMode strict-key
272
+ rules for object diagnostics where possible, so missing/accessor issue codes
273
+ are not guaranteed to match safe mode. Array and tuple diagnostics also use
274
+ direct indexed reads in fast modes, so sparse slots are diagnosed from the
275
+ loaded `undefined` value. Record diagnostics use direct `record[key]` reads;
276
+ unchecked mode also visits inherited enumerable keys. Discriminant diagnostics
277
+ read the tag directly and compare string cases with `===`.
278
+
190
279
  ## AOT Emit
191
280
 
192
281
  ```ts
193
282
  const emitted = emitAotModule(User, { name: "aotUser" });
283
+ const unsafeEmitted = emitAotModule(User, {
284
+ name: "aotUserFast",
285
+ mode: "unsafe"
286
+ });
287
+ const uncheckedEmitted = emitAotModule(User, {
288
+ name: "aotUserTrustedShape",
289
+ mode: "unchecked"
290
+ });
194
291
  ```
195
292
 
196
293
  `emitAotModule` returns `Result<AotModule, AotIssue[]>`. A successful result
@@ -213,6 +310,28 @@ const resolver = toReactHookFormResolver(User);
213
310
  Adapters are structural and zero-dependency. TypeSea does not import tRPC,
214
311
  Fastify, or React Hook Form.
215
312
 
313
+ Compiled guards can be passed to the same adapters. This is the preferred shape
314
+ for hot request paths: compile once during startup, then let the adapter reuse
315
+ the generated predicate.
316
+
317
+ ```ts
318
+ const FastUser = compile(User);
319
+ const fastParser = toTrpcParser(FastUser);
320
+ const fastValidatorCompiler = toFastifyValidatorCompiler(FastUser);
321
+ ```
322
+
323
+ Use the default compiled mode at public input boundaries. For trusted,
324
+ already-normalized internal data, the faster modes can be wired through adapters
325
+ the same way.
326
+
327
+ ```ts
328
+ const UnsafeUser = compile(User, { mode: "unsafe" });
329
+ const internalParser = toTrpcParser(UnsafeUser);
330
+
331
+ const TrustedShapeUser = compile(User, { mode: "unchecked" });
332
+ const internalValidatorCompiler = toFastifyValidatorCompiler(TrustedShapeUser);
333
+ ```
334
+
216
335
  | Adapter | Export | Behavior |
217
336
  | --- | --- | --- |
218
337
  | tRPC | `toTrpcParser`, `toAsyncTrpcParser` | Return parser objects that emit decoded values or throw `TypeSeaAssertionError`. |
@@ -257,6 +376,7 @@ Runtime-only concepts return explicit export issues:
257
376
  - `undefined`
258
377
  - `bigint`
259
378
  - `symbol`
379
+ - JavaScript `Date`, `Map`, `Set`, `instanceOf`, and `property` contracts
260
380
  - `lazy`
261
381
  - `refine`
262
382
  - decoder transforms
@@ -0,0 +1,163 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="1120" height="760" viewBox="0 0 1120 760" role="img" aria-labelledby="title desc">
2
+ <title id="title">TypeSea benchmark comparison</title>
3
+ <desc id="desc">Local strict object benchmark comparing TypeSea safe, unsafe, unchecked, Zod, Valibot, and Ajv throughput.</desc>
4
+ <defs>
5
+ <linearGradient id="bg" x1="0" y1="0" x2="1" y2="1">
6
+ <stop offset="0" stop-color="#071014"/>
7
+ <stop offset="0.58" stop-color="#0f172a"/>
8
+ <stop offset="1" stop-color="#13251f"/>
9
+ </linearGradient>
10
+ <linearGradient id="panel" x1="0" y1="0" x2="1" y2="1">
11
+ <stop offset="0" stop-color="#111827"/>
12
+ <stop offset="1" stop-color="#0b1220"/>
13
+ </linearGradient>
14
+ <linearGradient id="safe" x1="0" y1="0" x2="1" y2="0">
15
+ <stop offset="0" stop-color="#14b8a6"/>
16
+ <stop offset="1" stop-color="#2dd4bf"/>
17
+ </linearGradient>
18
+ <linearGradient id="unsafe" x1="0" y1="0" x2="1" y2="0">
19
+ <stop offset="0" stop-color="#f97316"/>
20
+ <stop offset="1" stop-color="#fbbf24"/>
21
+ </linearGradient>
22
+ <linearGradient id="unchecked" x1="0" y1="0" x2="1" y2="0">
23
+ <stop offset="0" stop-color="#22c55e"/>
24
+ <stop offset="1" stop-color="#a3e635"/>
25
+ </linearGradient>
26
+ <linearGradient id="ajv" x1="0" y1="0" x2="1" y2="0">
27
+ <stop offset="0" stop-color="#3b82f6"/>
28
+ <stop offset="1" stop-color="#93c5fd"/>
29
+ </linearGradient>
30
+ <linearGradient id="zod" x1="0" y1="0" x2="1" y2="0">
31
+ <stop offset="0" stop-color="#8b5cf6"/>
32
+ <stop offset="1" stop-color="#c4b5fd"/>
33
+ </linearGradient>
34
+ <linearGradient id="valibot" x1="0" y1="0" x2="1" y2="0">
35
+ <stop offset="0" stop-color="#ec4899"/>
36
+ <stop offset="1" stop-color="#f9a8d4"/>
37
+ </linearGradient>
38
+ <filter id="shadow" x="-20%" y="-20%" width="140%" height="140%">
39
+ <feDropShadow dx="0" dy="14" stdDeviation="18" flood-color="#000000" flood-opacity="0.35"/>
40
+ </filter>
41
+ <style>
42
+ text { font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; }
43
+ .title { fill: #f8fafc; font-size: 42px; font-weight: 800; letter-spacing: 0; }
44
+ .subtitle { fill: #94a3b8; font-size: 16px; font-weight: 500; }
45
+ .card-title { fill: #e5e7eb; font-size: 18px; font-weight: 800; }
46
+ .card-note { fill: #94a3b8; font-size: 12px; font-weight: 600; }
47
+ .metric-label { fill: #94a3b8; font-size: 12px; font-weight: 700; text-transform: uppercase; }
48
+ .metric-value { fill: #f8fafc; font-size: 30px; font-weight: 900; }
49
+ .metric-sub { fill: #cbd5e1; font-size: 13px; font-weight: 600; }
50
+ .bar-label { fill: #cbd5e1; font-size: 13px; font-weight: 700; }
51
+ .bar-value { fill: #f8fafc; font-size: 13px; font-weight: 800; }
52
+ .axis { stroke: #334155; stroke-width: 1; }
53
+ </style>
54
+ </defs>
55
+
56
+ <rect width="1120" height="760" rx="28" fill="url(#bg)"/>
57
+ <circle cx="1028" cy="82" r="180" fill="#22c55e" opacity="0.08"/>
58
+ <circle cx="84" cy="690" r="220" fill="#38bdf8" opacity="0.07"/>
59
+
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>
62
+
63
+ <g filter="url(#shadow)">
64
+ <rect x="44" y="118" width="322" height="108" rx="18" fill="url(#panel)" stroke="#1f2937"/>
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>
68
+
69
+ <rect x="399" y="118" width="322" height="108" rx="18" fill="url(#panel)" stroke="#1f2937"/>
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>
73
+
74
+ <rect x="754" y="118" width="322" height="108" rx="18" fill="url(#panel)" stroke="#1f2937"/>
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>
78
+ </g>
79
+
80
+ <g filter="url(#shadow)">
81
+ <rect x="44" y="260" width="1032" height="136" rx="18" fill="url(#panel)" stroke="#1f2937"/>
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>
84
+ <line x1="270" y1="318" x2="1010" y2="318" class="axis"/>
85
+
86
+ <text x="66" y="326" class="bar-label">TypeSea unchecked</text>
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>
89
+
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>
93
+
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>
100
+ </g>
101
+
102
+ <g filter="url(#shadow)">
103
+ <rect x="44" y="418" width="1032" height="150" rx="18" fill="url(#panel)" stroke="#1f2937"/>
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>
106
+ <line x1="270" y1="476" x2="1010" y2="476" class="axis"/>
107
+
108
+ <text x="66" y="482" class="bar-label">TypeSea unchecked</text>
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>
111
+
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>
115
+
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>
119
+
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>
125
+ </g>
126
+
127
+ <g filter="url(#shadow)">
128
+ <rect x="44" y="576" width="1032" height="136" rx="18" fill="url(#panel)" stroke="#1f2937"/>
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>
131
+ <line x1="270" y1="634" x2="1010" y2="634" class="axis"/>
132
+
133
+ <text x="66" y="642" class="bar-label">Ajv compiled</text>
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>
136
+
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)"/>
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>
142
+
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)"/>
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>
147
+ </g>
148
+
149
+ <g transform="translate(44 730)">
150
+ <rect x="0" y="-11" width="13" height="13" rx="3" fill="url(#safe)"/>
151
+ <text x="20" y="0" class="card-note">TypeSea safe</text>
152
+ <rect x="128" y="-11" width="13" height="13" rx="3" fill="url(#unsafe)"/>
153
+ <text x="148" y="0" class="card-note">unsafe</text>
154
+ <rect x="228" y="-11" width="13" height="13" rx="3" fill="url(#unchecked)"/>
155
+ <text x="248" y="0" class="card-note">unchecked</text>
156
+ <rect x="356" y="-11" width="13" height="13" rx="3" fill="url(#ajv)"/>
157
+ <text x="376" y="0" class="card-note">Ajv</text>
158
+ <rect x="426" y="-11" width="13" height="13" rx="3" fill="url(#valibot)"/>
159
+ <text x="446" y="0" class="card-note">Valibot</text>
160
+ <rect x="536" y="-11" width="13" height="13" rx="3" fill="url(#zod)"/>
161
+ <text x="556" y="0" class="card-note">Zod</text>
162
+ </g>
163
+ </svg>
@@ -18,6 +18,9 @@ allocation sites, branch behavior, and validation contracts visible in code.
18
18
  - For all-required strict objects, reject extras by counting own string names
19
19
  and own symbols after field validation. Optional strict objects keep the full
20
20
  key membership scan.
21
+ - Keep `compile()` and `emitAotModule()` safe by default. Unsafe mode is an
22
+ explicit opt-in that may use direct property/index loads and own-enumerable
23
+ strict-key loops after the caller accepts getter/prototype/symbol-extra risk.
21
24
  - Mark constructed guards out-of-band so normal receivers avoid repeated schema
22
25
  validation while forged receivers still fall back to structural checks.
23
26
  - Use `Readonly<Record<string, unknown>>` after object guards.
@@ -91,14 +94,51 @@ descriptor-based element access, and dynamic edges use the same IR-backed
91
94
  runtime fallback as ordinary guard execution, preserving behavior for `lazy` and
92
95
  `refine`.
93
96
 
94
- Strict object IR emits two shapes. When every declared key is required and has
95
- already passed the data-property descriptor check, generated validators compare
96
- `Object.getOwnPropertyNames(value).length` with the declared key count and
97
- require `Object.getOwnPropertySymbols(value).length === 0`. This keeps
98
- non-enumerable and symbol extras rejected without paying a `Reflect.ownKeys`
99
- membership loop on the common all-required path. Optional strict objects still
100
- emit the full own-key membership scan because a missing optional key cannot be
101
- distinguished by the final key count alone.
97
+ Strict object IR emits two shapes. When every declared key is required,
98
+ generated validators run the strict-key count before field descriptor reads:
99
+ they compare `Object.getOwnPropertyNames(value).length` with the declared key
100
+ count and require `Object.getOwnPropertySymbols(value).length === 0`. V8
101
+ optimizes this count-only path better than a generic `Reflect.ownKeys` count,
102
+ and it rejects obvious extra-key objects before touching field descriptors.
103
+ Optional strict objects still emit the full own-key membership scan because a
104
+ missing optional key cannot be distinguished by the final key count alone.
105
+
106
+ `compile(..., { mode: "unsafe" })` and
107
+ `emitAotModule(..., { mode: "unsafe" })` switch generated predicates to a
108
+ trusted-data code shape. Required object fields whose schemas reject
109
+ `undefined` use direct `value[key]` loads without descriptor or own-key checks.
110
+ Required fields that can accept `undefined` retain an own-key presence guard so
111
+ missing required keys do not collapse into valid `undefined` values. Optional
112
+ fields take the direct-load fast path for present non-`undefined` values and
113
+ fall back to an own-key check only for the ambiguous `undefined` case.
114
+
115
+ Unsafe array, tuple, record, and discriminant paths also prefer direct loads.
116
+ Strict objects use a `for...in` own-enumerable key loop instead of allocating
117
+ own-key arrays.
118
+ Object keys that are ASCII identifier names emit as dot-property loads such as
119
+ `value.id`; other keys emit as escaped string-literal bracket loads. That is
120
+ intentionally not hostile-input equivalent: getters can execute,
121
+ prototype-backed values can be accepted, symbol or non-enumerable strict extras
122
+ are not rejected, and static property names may appear in unsafe generated
123
+ predicate source.
124
+
125
+ `mode: "unchecked"` keeps the unsafe direct-read shape and removes strict
126
+ extra-key loops. It is a trusted-shape path for objects already normalized by
127
+ the caller; strict objects no longer reject any extra keys there.
128
+
129
+ Fast modes also remove `Object.freeze()` from successful compiled `check()`
130
+ results. The returned object keeps the same `{ ok: true, value }` shape, but it
131
+ is intentionally not frozen. Failed diagnostics stay frozen because those
132
+ objects are off the success hot path and are often retained for reporting.
133
+ Object diagnostics in fast modes are generated from the same direct-read
134
+ contract as predicates. Required fields load through `value.key`, optional
135
+ fields use direct load plus an own-key fallback for `undefined`, unsafe strict
136
+ objects scan own enumerable string keys, and unchecked strict objects skip the
137
+ strict-key diagnostic scan. Array and tuple diagnostics in fast modes read
138
+ items through direct indexes instead of descriptor probes. Record diagnostics
139
+ read through `record[key]`; unchecked mode intentionally keeps inherited
140
+ enumerable keys visible. Discriminant diagnostics read the tag directly and
141
+ compare literal string cases with strict equality.
102
142
 
103
143
  ## Recursion
104
144
 
@@ -114,6 +154,10 @@ still checking the original object fields on the outer frame.
114
154
  Compiled `lazy` and `refine` fallbacks use the same IR-backed runtime path, so
115
155
  recursive behavior stays consistent across execution engines.
116
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
+
117
161
  ## JSON Schema Export
118
162
 
119
163
  JSON Schema export succeeds only when the TypeSea schema can be represented over
@@ -141,13 +185,16 @@ Zod, Valibot, and Ajv are dev dependencies for measurement only. They are not
141
185
  imported by `src`, and package policy rejects runtime, peer, optional, or
142
186
  bundled dependency fields before release.
143
187
 
144
- Last local release smoke on 2026-07-04 KST reported this ecosystem boolean
145
- path over the JSON-compatible strict-object benchmark:
188
+ Last local benchmark on 2026-07-04 KST reported these ecosystem paths over the
189
+ JSON-compatible strict-object benchmark:
146
190
 
147
- | Case | TypeSea runtime plan | TypeSea compiled | Ajv compiled |
148
- | --- | ---: | ---: | ---: |
149
- | Valid object | 496,270 hz | 4,237,892 hz | 4,312,174 hz |
150
- | Invalid object | 3,422,416 hz | 27,125,445 hz | 28,953,501 hz |
191
+ | Case | TypeSea runtime plan | TypeSea compiled safe | TypeSea compiled unsafe | TypeSea compiled unchecked | Ajv compiled |
192
+ | --- | ---: | ---: | ---: | ---: | ---: |
193
+ | Valid `is()` | 513,701 hz | 4,297,306 hz | 36,297,653 hz | 42,581,174 hz | 4,275,389 hz |
194
+ | Valid `check()` | 503,232 hz | 3,903,929 hz | 35,568,425 hz | 40,084,605 hz | 4,278,587 hz |
195
+ | Invalid `is()` | 3,636,369 hz | 42,080,241 hz | 49,654,076 hz | 50,482,732 hz | 27,820,643 hz |
196
+ | Invalid `check()` | 420,446 hz | 2,086,129 hz | 3,077,367 hz | 3,673,508 hz | 28,713,035 hz |
151
197
 
152
198
  Benchmark numbers are machine-local telemetry. They are useful for catching
153
- regressions, not for promising a fixed throughput floor.
199
+ regressions, not for promising a fixed throughput floor. Unsafe and unchecked
200
+ numbers are not hostile-input equivalent to safe mode.