typesea 0.1.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 (271) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/LICENSE +21 -0
  3. package/README.md +320 -0
  4. package/dist/adapters/index.d.ts +152 -0
  5. package/dist/adapters/index.d.ts.map +1 -0
  6. package/dist/adapters/index.js +396 -0
  7. package/dist/aot/index.d.ts +33 -0
  8. package/dist/aot/index.d.ts.map +1 -0
  9. package/dist/aot/index.js +295 -0
  10. package/dist/async/index.d.ts +111 -0
  11. package/dist/async/index.d.ts.map +1 -0
  12. package/dist/async/index.js +221 -0
  13. package/dist/builders/composite.d.ts +31 -0
  14. package/dist/builders/composite.d.ts.map +1 -0
  15. package/dist/builders/composite.js +165 -0
  16. package/dist/builders/index.d.ts +11 -0
  17. package/dist/builders/index.d.ts.map +1 -0
  18. package/dist/builders/index.js +9 -0
  19. package/dist/builders/modifier.d.ts +26 -0
  20. package/dist/builders/modifier.d.ts.map +1 -0
  21. package/dist/builders/modifier.js +67 -0
  22. package/dist/builders/object/guard.d.ts +62 -0
  23. package/dist/builders/object/guard.d.ts.map +1 -0
  24. package/dist/builders/object/guard.js +113 -0
  25. package/dist/builders/object/index.d.ts +7 -0
  26. package/dist/builders/object/index.d.ts.map +1 -0
  27. package/dist/builders/object/index.js +5 -0
  28. package/dist/builders/object/schema.d.ts +44 -0
  29. package/dist/builders/object/schema.d.ts.map +1 -0
  30. package/dist/builders/object/schema.js +257 -0
  31. package/dist/builders/object/types.d.ts +63 -0
  32. package/dist/builders/object/types.d.ts.map +1 -0
  33. package/dist/builders/object/types.js +5 -0
  34. package/dist/builders/scalar.d.ts +39 -0
  35. package/dist/builders/scalar.d.ts.map +1 -0
  36. package/dist/builders/scalar.js +63 -0
  37. package/dist/builders/table.d.ts +53 -0
  38. package/dist/builders/table.d.ts.map +1 -0
  39. package/dist/builders/table.js +48 -0
  40. package/dist/builders/types.d.ts +26 -0
  41. package/dist/builders/types.d.ts.map +1 -0
  42. package/dist/builders/types.js +5 -0
  43. package/dist/compile/check-composite.d.ts +34 -0
  44. package/dist/compile/check-composite.d.ts.map +1 -0
  45. package/dist/compile/check-composite.js +117 -0
  46. package/dist/compile/check-scalar.d.ts +24 -0
  47. package/dist/compile/check-scalar.d.ts.map +1 -0
  48. package/dist/compile/check-scalar.js +73 -0
  49. package/dist/compile/check.d.ts +15 -0
  50. package/dist/compile/check.d.ts.map +1 -0
  51. package/dist/compile/check.js +98 -0
  52. package/dist/compile/context.d.ts +35 -0
  53. package/dist/compile/context.d.ts.map +1 -0
  54. package/dist/compile/context.js +72 -0
  55. package/dist/compile/graph-predicate.d.ts +19 -0
  56. package/dist/compile/graph-predicate.d.ts.map +1 -0
  57. package/dist/compile/graph-predicate.js +460 -0
  58. package/dist/compile/guard.d.ts +41 -0
  59. package/dist/compile/guard.d.ts.map +1 -0
  60. package/dist/compile/guard.js +180 -0
  61. package/dist/compile/index.d.ts +8 -0
  62. package/dist/compile/index.d.ts.map +1 -0
  63. package/dist/compile/index.js +6 -0
  64. package/dist/compile/issue.d.ts +18 -0
  65. package/dist/compile/issue.d.ts.map +1 -0
  66. package/dist/compile/issue.js +28 -0
  67. package/dist/compile/names.d.ts +16 -0
  68. package/dist/compile/names.d.ts.map +1 -0
  69. package/dist/compile/names.js +82 -0
  70. package/dist/compile/predicate.d.ts +23 -0
  71. package/dist/compile/predicate.d.ts.map +1 -0
  72. package/dist/compile/predicate.js +317 -0
  73. package/dist/compile/runtime.d.ts +55 -0
  74. package/dist/compile/runtime.d.ts.map +1 -0
  75. package/dist/compile/runtime.js +63 -0
  76. package/dist/compile/source.d.ts +11 -0
  77. package/dist/compile/source.d.ts.map +1 -0
  78. package/dist/compile/source.js +51 -0
  79. package/dist/compile/types.d.ts +52 -0
  80. package/dist/compile/types.d.ts.map +1 -0
  81. package/dist/compile/types.js +5 -0
  82. package/dist/decoder/index.d.ts +106 -0
  83. package/dist/decoder/index.d.ts.map +1 -0
  84. package/dist/decoder/index.js +262 -0
  85. package/dist/evaluate/check-composite.d.ts +39 -0
  86. package/dist/evaluate/check-composite.d.ts.map +1 -0
  87. package/dist/evaluate/check-composite.js +184 -0
  88. package/dist/evaluate/check-scalar.d.ts +20 -0
  89. package/dist/evaluate/check-scalar.d.ts.map +1 -0
  90. package/dist/evaluate/check-scalar.js +81 -0
  91. package/dist/evaluate/check.d.ts +11 -0
  92. package/dist/evaluate/check.d.ts.map +1 -0
  93. package/dist/evaluate/check.js +126 -0
  94. package/dist/evaluate/index.d.ts +7 -0
  95. package/dist/evaluate/index.d.ts.map +1 -0
  96. package/dist/evaluate/index.js +6 -0
  97. package/dist/evaluate/issue.d.ts +10 -0
  98. package/dist/evaluate/issue.d.ts.map +1 -0
  99. package/dist/evaluate/issue.js +11 -0
  100. package/dist/evaluate/predicate.d.ts +26 -0
  101. package/dist/evaluate/predicate.d.ts.map +1 -0
  102. package/dist/evaluate/predicate.js +37 -0
  103. package/dist/evaluate/shared.d.ts +59 -0
  104. package/dist/evaluate/shared.d.ts.map +1 -0
  105. package/dist/evaluate/shared.js +96 -0
  106. package/dist/evaluate/state.d.ts +65 -0
  107. package/dist/evaluate/state.d.ts.map +1 -0
  108. package/dist/evaluate/state.js +66 -0
  109. package/dist/guard/base.d.ts +72 -0
  110. package/dist/guard/base.d.ts.map +1 -0
  111. package/dist/guard/base.js +136 -0
  112. package/dist/guard/error.d.ts +19 -0
  113. package/dist/guard/error.d.ts.map +1 -0
  114. package/dist/guard/error.js +22 -0
  115. package/dist/guard/index.d.ts +10 -0
  116. package/dist/guard/index.d.ts.map +1 -0
  117. package/dist/guard/index.js +8 -0
  118. package/dist/guard/number.d.ts +32 -0
  119. package/dist/guard/number.d.ts.map +1 -0
  120. package/dist/guard/number.js +71 -0
  121. package/dist/guard/props.d.ts +18 -0
  122. package/dist/guard/props.d.ts.map +1 -0
  123. package/dist/guard/props.js +35 -0
  124. package/dist/guard/read.d.ts +42 -0
  125. package/dist/guard/read.d.ts.map +1 -0
  126. package/dist/guard/read.js +114 -0
  127. package/dist/guard/registry.d.ts +15 -0
  128. package/dist/guard/registry.d.ts.map +1 -0
  129. package/dist/guard/registry.js +21 -0
  130. package/dist/guard/string.d.ts +36 -0
  131. package/dist/guard/string.d.ts.map +1 -0
  132. package/dist/guard/string.js +95 -0
  133. package/dist/guard/types.d.ts +103 -0
  134. package/dist/guard/types.d.ts.map +1 -0
  135. package/dist/guard/types.js +5 -0
  136. package/dist/index.d.ts +14 -0
  137. package/dist/index.d.ts.map +1 -0
  138. package/dist/index.js +10 -0
  139. package/dist/internal/index.d.ts +34 -0
  140. package/dist/internal/index.d.ts.map +1 -0
  141. package/dist/internal/index.js +56 -0
  142. package/dist/ir/builder.d.ts +173 -0
  143. package/dist/ir/builder.d.ts.map +1 -0
  144. package/dist/ir/builder.js +481 -0
  145. package/dist/ir/freeze.d.ts +10 -0
  146. package/dist/ir/freeze.d.ts.map +1 -0
  147. package/dist/ir/freeze.js +102 -0
  148. package/dist/ir/index.d.ts +9 -0
  149. package/dist/ir/index.d.ts.map +1 -0
  150. package/dist/ir/index.js +7 -0
  151. package/dist/ir/regexp.d.ts +6 -0
  152. package/dist/ir/regexp.d.ts.map +1 -0
  153. package/dist/ir/regexp.js +12 -0
  154. package/dist/ir/types.d.ts +215 -0
  155. package/dist/ir/types.d.ts.map +1 -0
  156. package/dist/ir/types.js +5 -0
  157. package/dist/ir/validate.d.ts +10 -0
  158. package/dist/ir/validate.d.ts.map +1 -0
  159. package/dist/ir/validate.js +271 -0
  160. package/dist/issue/index.d.ts +44 -0
  161. package/dist/issue/index.d.ts.map +1 -0
  162. package/dist/issue/index.js +152 -0
  163. package/dist/json-schema/emit-combinator.d.ts +28 -0
  164. package/dist/json-schema/emit-combinator.d.ts.map +1 -0
  165. package/dist/json-schema/emit-combinator.js +96 -0
  166. package/dist/json-schema/emit-composite.d.ts +28 -0
  167. package/dist/json-schema/emit-composite.d.ts.map +1 -0
  168. package/dist/json-schema/emit-composite.js +127 -0
  169. package/dist/json-schema/emit-scalar.d.ts +25 -0
  170. package/dist/json-schema/emit-scalar.d.ts.map +1 -0
  171. package/dist/json-schema/emit-scalar.js +104 -0
  172. package/dist/json-schema/emit-types.d.ts +12 -0
  173. package/dist/json-schema/emit-types.d.ts.map +1 -0
  174. package/dist/json-schema/emit-types.js +5 -0
  175. package/dist/json-schema/emit.d.ts +12 -0
  176. package/dist/json-schema/emit.d.ts.map +1 -0
  177. package/dist/json-schema/emit.js +62 -0
  178. package/dist/json-schema/freeze.d.ts +14 -0
  179. package/dist/json-schema/freeze.d.ts.map +1 -0
  180. package/dist/json-schema/freeze.js +114 -0
  181. package/dist/json-schema/index.d.ts +20 -0
  182. package/dist/json-schema/index.d.ts.map +1 -0
  183. package/dist/json-schema/index.js +76 -0
  184. package/dist/json-schema/issue.d.ts +11 -0
  185. package/dist/json-schema/issue.d.ts.map +1 -0
  186. package/dist/json-schema/issue.js +14 -0
  187. package/dist/json-schema/read.d.ts +29 -0
  188. package/dist/json-schema/read.d.ts.map +1 -0
  189. package/dist/json-schema/read.js +87 -0
  190. package/dist/json-schema/types.d.ts +106 -0
  191. package/dist/json-schema/types.d.ts.map +1 -0
  192. package/dist/json-schema/types.js +5 -0
  193. package/dist/kind/index.d.ts +119 -0
  194. package/dist/kind/index.d.ts.map +1 -0
  195. package/dist/kind/index.js +94 -0
  196. package/dist/lower/index.d.ts +7 -0
  197. package/dist/lower/index.d.ts.map +1 -0
  198. package/dist/lower/index.js +199 -0
  199. package/dist/message/index.d.ts +51 -0
  200. package/dist/message/index.d.ts.map +1 -0
  201. package/dist/message/index.js +269 -0
  202. package/dist/optimize/compact.d.ts +10 -0
  203. package/dist/optimize/compact.d.ts.map +1 -0
  204. package/dist/optimize/compact.js +60 -0
  205. package/dist/optimize/fold-boolean.d.ts +15 -0
  206. package/dist/optimize/fold-boolean.d.ts.map +1 -0
  207. package/dist/optimize/fold-boolean.js +75 -0
  208. package/dist/optimize/fold-common.d.ts +45 -0
  209. package/dist/optimize/fold-common.d.ts.map +1 -0
  210. package/dist/optimize/fold-common.js +71 -0
  211. package/dist/optimize/fold-scalar.d.ts +59 -0
  212. package/dist/optimize/fold-scalar.d.ts.map +1 -0
  213. package/dist/optimize/fold-scalar.js +174 -0
  214. package/dist/optimize/fold.d.ts +10 -0
  215. package/dist/optimize/fold.d.ts.map +1 -0
  216. package/dist/optimize/fold.js +103 -0
  217. package/dist/optimize/index.d.ts +10 -0
  218. package/dist/optimize/index.d.ts.map +1 -0
  219. package/dist/optimize/index.js +23 -0
  220. package/dist/optimize/map-node.d.ts +21 -0
  221. package/dist/optimize/map-node.d.ts.map +1 -0
  222. package/dist/optimize/map-node.js +222 -0
  223. package/dist/optimize/remap.d.ts +30 -0
  224. package/dist/optimize/remap.d.ts.map +1 -0
  225. package/dist/optimize/remap.js +46 -0
  226. package/dist/optimize/rewrite.d.ts +22 -0
  227. package/dist/optimize/rewrite.d.ts.map +1 -0
  228. package/dist/optimize/rewrite.js +34 -0
  229. package/dist/plan/cache.d.ts +20 -0
  230. package/dist/plan/cache.d.ts.map +1 -0
  231. package/dist/plan/cache.js +122 -0
  232. package/dist/plan/index.d.ts +8 -0
  233. package/dist/plan/index.d.ts.map +1 -0
  234. package/dist/plan/index.js +6 -0
  235. package/dist/plan/predicate.d.ts +27 -0
  236. package/dist/plan/predicate.d.ts.map +1 -0
  237. package/dist/plan/predicate.js +415 -0
  238. package/dist/plan/schema-predicate.d.ts +15 -0
  239. package/dist/plan/schema-predicate.d.ts.map +1 -0
  240. package/dist/plan/schema-predicate.js +277 -0
  241. package/dist/plan/types.d.ts +18 -0
  242. package/dist/plan/types.d.ts.map +1 -0
  243. package/dist/plan/types.js +5 -0
  244. package/dist/result/index.d.ts +27 -0
  245. package/dist/result/index.d.ts.map +1 -0
  246. package/dist/result/index.js +20 -0
  247. package/dist/schema/common.d.ts +30 -0
  248. package/dist/schema/common.d.ts.map +1 -0
  249. package/dist/schema/common.js +102 -0
  250. package/dist/schema/freeze.d.ts +10 -0
  251. package/dist/schema/freeze.d.ts.map +1 -0
  252. package/dist/schema/freeze.js +163 -0
  253. package/dist/schema/index.d.ts +11 -0
  254. package/dist/schema/index.d.ts.map +1 -0
  255. package/dist/schema/index.js +9 -0
  256. package/dist/schema/lazy.d.ts +10 -0
  257. package/dist/schema/lazy.d.ts.map +1 -0
  258. package/dist/schema/lazy.js +25 -0
  259. package/dist/schema/literal.d.ts +10 -0
  260. package/dist/schema/literal.d.ts.map +1 -0
  261. package/dist/schema/literal.js +17 -0
  262. package/dist/schema/types.d.ts +243 -0
  263. package/dist/schema/types.d.ts.map +1 -0
  264. package/dist/schema/types.js +9 -0
  265. package/dist/schema/validate.d.ts +10 -0
  266. package/dist/schema/validate.d.ts.map +1 -0
  267. package/dist/schema/validate.js +268 -0
  268. package/docs/api.md +301 -0
  269. package/docs/engine-notes.md +153 -0
  270. package/docs/index.html +1242 -0
  271. package/package.json +68 -0
package/docs/api.md ADDED
@@ -0,0 +1,301 @@
1
+ # TypeSea API Reference
2
+
3
+ TypeSea accepts untrusted input as `unknown` and narrows it through immutable
4
+ guard values. The public API is small by design; most complexity lives behind
5
+ builder validation, graph introspection, diagnostics, and export checks.
6
+
7
+ ## Import
8
+
9
+ ```ts
10
+ import {
11
+ compile,
12
+ emitAotModule,
13
+ t,
14
+ toJsonSchema,
15
+ type Guard,
16
+ type Infer
17
+ } from "typesea";
18
+ ```
19
+
20
+ The package exposes one root entry point. Subpath imports are intentionally not
21
+ part of the public API. TypeSea is ESM-only and does not publish a CommonJS
22
+ condition.
23
+
24
+ ## Guard Contract
25
+
26
+ ```ts
27
+ interface Guard<T> {
28
+ is(value: unknown): value is T;
29
+ check(value: unknown): CheckResult<T>;
30
+ assert(value: unknown): asserts value is T;
31
+ graph(): Graph;
32
+ }
33
+ ```
34
+
35
+ | Method | Use it for | Contract |
36
+ | --- | --- | --- |
37
+ | `is` | Hot boolean narrowing | Avoids diagnostic allocation on the success path. |
38
+ | `check` | Validation with issues | Returns frozen `Result<T, Issue[]>` containers. |
39
+ | `assert` | Throwing integration boundaries | Throws `TypeSeaAssertionError` with copied, frozen issues. |
40
+ | `graph` | Runtime plan introspection | Returns the validated, optimized, frozen Sea-of-Nodes graph held by the validation plan. |
41
+
42
+ Diagnostic paths contain only object keys and zero-based array or tuple indexes.
43
+ Public diagnostic validators reject malformed path segments before diagnostics
44
+ cross the API boundary.
45
+
46
+ ## Builder Families
47
+
48
+ | Family | Builders |
49
+ | --- | --- |
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
+ | Objects | `t.object(shape)`, `t.strictObject(shape)` |
53
+ | Object transforms | `t.extend`, `t.pick`, `t.omit`, `t.partial`, and matching object guard methods |
54
+ | Composition | `t.union`, `t.discriminatedUnion`, `t.intersect`, `guard.intersect` |
55
+ | Presence | `t.optional`, `t.undefinedable`, `t.nullable` |
56
+ | Dynamic guards | `t.lazy`, `t.refine` |
57
+ | Decoders | `t.decoder`, `t.transform`, `t.pipe`, `t.coerce` |
58
+ | Async decoders | `t.asyncDecoder`, `t.asyncRefine`, `t.asyncTransform`, `t.asyncPipe` |
59
+
60
+ Builder functions validate inputs before a schema can enter the validation plan,
61
+ compiler, AOT emitter, diagnostic collector, or JSON Schema exporter. Forged guard-like values,
62
+ invalid schema tags, invalid predicates, invalid bounds, malformed regexps, and
63
+ invalid discriminated union case sets are rejected during construction.
64
+
65
+ Accepted schemas are frozen before storage. Public schema collection fields use
66
+ frozen arrays and frozen key lookup records instead of mutable collection
67
+ objects.
68
+
69
+ ## Object Presence
70
+
71
+ TypeSea separates key presence from value domain.
72
+
73
+ ```ts
74
+ const Shape = t.object({
75
+ name: t.optional(t.string),
76
+ nickname: t.undefinedable(t.string)
77
+ });
78
+ ```
79
+
80
+ - `name` may be absent. If `name` exists, its value must be a string.
81
+ - `nickname` must be present. Its value may be a string or `undefined`.
82
+ - `t.nullable(inner)` adds `null` to the value domain.
83
+ - Presence-preserving wrappers keep optional-key semantics through `nullable`,
84
+ `undefinedable`, `brand`, and `refine`.
85
+
86
+ Object combinators preserve object mode. Strict object guards remain strict
87
+ after `extend`, `pick`, `omit`, or `partial`; passthrough object guards keep
88
+ allowing unknown keys.
89
+
90
+ ## Composition
91
+
92
+ `t.union(a, b)` accepts a value that satisfies at least one branch.
93
+
94
+ `t.discriminatedUnion("kind", cases)` requires string case keys. Each case must
95
+ be a statically inspectable object case whose dispatch key is a required string
96
+ literal matching the case name.
97
+
98
+ `t.intersect(a, b)` and `guard.intersect(other)` require the same input value to
99
+ satisfy both guards. `check()` collects diagnostics from both sides.
100
+
101
+ ## Recursion
102
+
103
+ Recursive contracts must use `t.lazy`.
104
+
105
+ ```ts
106
+ interface ListNode {
107
+ readonly value: string;
108
+ readonly next?: ListNode;
109
+ }
110
+
111
+ const Node: Guard<ListNode> = t.lazy((): Guard<ListNode> =>
112
+ t.object({
113
+ value: t.string,
114
+ next: t.optional(Node)
115
+ })
116
+ );
117
+ ```
118
+
119
+ Direct cyclic schema objects are rejected at builder boundaries. Lazy guards
120
+ resolve once per guard instance and keep recursive schema identity stable. A
121
+ lazy chain must eventually resolve to a concrete non-lazy schema.
122
+
123
+ ## Decoder Pipelines
124
+
125
+ ```ts
126
+ const Count = t.pipe(t.coerce.number(), t.number.int().gte(0));
127
+ const result = Count.decode("42");
128
+ ```
129
+
130
+ Decoders are for output-producing operations. They return `Result` from
131
+ `decode()` and do not expose `is()` predicates, because the decoded output may
132
+ not be the same runtime value as the input.
133
+
134
+ - `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.
137
+ - `t.coerce.string`, `t.coerce.number`, and `t.coerce.boolean` provide explicit
138
+ primitive coercion.
139
+ - `t.asyncRefine`, `t.asyncTransform`, and `t.asyncPipe` return
140
+ `Promise<Result<T, Issue[]>>` from `decodeAsync()`.
141
+
142
+ Expected async validation failures still return `Result` values.
143
+
144
+ ## Messages
145
+
146
+ ```ts
147
+ const checked = withMessages(User.check(input), {
148
+ locale: "ko",
149
+ catalog: defineMessages({
150
+ expected_string: "{path}: 문자열 필요"
151
+ })
152
+ });
153
+ ```
154
+
155
+ `formatIssue`, `formatIssues`, and `withMessages` render diagnostics after
156
+ validation has finished. This keeps `is()` and ordinary `check()` paths free from
157
+ message allocation.
158
+
159
+ Built-in locales are `en` and `ko`. Custom catalogs can use string templates
160
+ with `{path}`, `{code}`, `{expected}`, and `{actual}`, or formatter callbacks.
161
+ `withMessages(result, options)` preserves successful results and returns a new
162
+ failed `Result` with copied, frozen issues whose `message` fields are populated.
163
+
164
+ ## Runtime Compile
165
+
166
+ ```ts
167
+ const FastUser = compile(User, { name: "isUser" });
168
+
169
+ FastUser.is(input);
170
+ FastUser.check(input);
171
+ ```
172
+
173
+ `compile` emits generated predicate functions from the optimized Sea-of-Nodes
174
+ validation graph plus diagnostics collectors for failed values. Static scalar,
175
+ object, array, record, union, and strict-key nodes lower to straight-line
176
+ JavaScript or indexed loops where possible. Dynamic schema edges such as `lazy`
177
+ and `refine` keep semantics by using the same IR-backed runtime fallback as
178
+ ordinary guards.
179
+
180
+ The optional `name` is a debugging and profiling hint. TypeSea normalizes it
181
+ into a strict-mode-safe JavaScript function name, prefixes reserved names, and
182
+ caps generated name length. Direct compiled guard construction validates the
183
+ predicate, collector, and source arguments. Collector diagnostics are validated,
184
+ copied, and frozen before `check()` returns them.
185
+
186
+ Generated source never interpolates user-controlled values directly. Literals,
187
+ regexps, property keys, keysets, and dynamic schema fallbacks are captured in
188
+ side tables and referenced by numeric index.
189
+
190
+ ## AOT Emit
191
+
192
+ ```ts
193
+ const emitted = emitAotModule(User, { name: "aotUser" });
194
+ ```
195
+
196
+ `emitAotModule` returns `Result<AotModule, AotIssue[]>`. A successful result
197
+ contains standalone ESM validator source plus declaration source. The generated
198
+ module exports `is`, `check`, `assert`, and a default frozen guard-like object,
199
+ without requiring dynamic source compilation at module load time.
200
+
201
+ AOT generation is lossless-only. Schemas that require runtime callbacks or
202
+ identity that cannot be serialized return explicit AOT issues.
203
+
204
+ ## Ecosystem Adapters
205
+
206
+ ```ts
207
+ const parser = toTrpcParser(User);
208
+ const routeSchema = toFastifyRouteSchema(User);
209
+ const validatorCompiler = toFastifyValidatorCompiler(User);
210
+ const resolver = toReactHookFormResolver(User);
211
+ ```
212
+
213
+ Adapters are structural and zero-dependency. TypeSea does not import tRPC,
214
+ Fastify, or React Hook Form.
215
+
216
+ | Adapter | Export | Behavior |
217
+ | --- | --- | --- |
218
+ | tRPC | `toTrpcParser`, `toAsyncTrpcParser` | Return parser objects that emit decoded values or throw `TypeSeaAssertionError`. |
219
+ | Fastify route schema | `toFastifyRouteSchema` | Converts guards to JSON Schema route fragments. |
220
+ | Fastify validator compiler | `toFastifyValidatorCompiler` | Returns compiler-shaped validators that produce `{ value }` or `{ error }`. |
221
+ | React Hook Form | `toReactHookFormResolver` | Returns an async resolver with TypeSea messages mapped to field errors. |
222
+
223
+ ## Graph and IR
224
+
225
+ ```ts
226
+ const graph = User.graph();
227
+ const optimized = optimizeGraph(graph);
228
+ ```
229
+
230
+ `Guard.graph()` returns the optimized Sea-of-Nodes validation graph held by the
231
+ runtime validation plan. The same plan also owns the specialized predicate
232
+ kernel used by `is()`. The graph is the source for `compile()` and
233
+ `emitAotModule()`, while the kernel keeps ordinary guard execution out of a
234
+ generic per-node interpreter. Public graph values are validated,
235
+ dependency-checked, dense, and frozen.
236
+
237
+ `optimizeGraph(graph)` validates direct graph inputs before optimizing them.
238
+ Regex graph nodes accept only plain `RegExp` values and store non-extensible
239
+ regexps, cloning extensible inputs before the graph is frozen.
240
+
241
+ `SchemaCheck` records dynamic runtime schema logic such as `lazy` or `refine`.
242
+ It keeps the IR truthful instead of pretending a callback-backed edge is a
243
+ static primitive.
244
+
245
+ ## JSON Schema
246
+
247
+ ```ts
248
+ const result = toJsonSchema(User);
249
+ ```
250
+
251
+ `toJsonSchema` returns `Result<JsonSchema, JsonSchemaExportIssue[]>`. It
252
+ succeeds only when TypeSea can represent the contract over JSON-compatible input
253
+ values without semantic loss.
254
+
255
+ Runtime-only concepts return explicit export issues:
256
+
257
+ - `undefined`
258
+ - `bigint`
259
+ - `symbol`
260
+ - `lazy`
261
+ - `refine`
262
+ - decoder transforms
263
+ - async validation
264
+ - regexps with flags
265
+ - numeric literals that JSON cannot preserve, such as `NaN`, `Infinity`, and
266
+ `-0`
267
+
268
+ `schemaToJsonSchema(schema)` is the direct schema API. It validates the supplied
269
+ schema and freezes it before export. JSON Schema options are also validated;
270
+ `schemaId`, when present, must be a string.
271
+
272
+ Object `properties` maps are emitted as null-prototype records so special keys
273
+ such as `__proto__`, `constructor`, and `hasOwnProperty` remain ordinary own
274
+ schema properties.
275
+
276
+ ## Edge Semantics
277
+
278
+ - Literal guards use `Object.is`, so `t.literal(Number.NaN)` matches `NaN` and
279
+ `t.literal(-0)` does not match `0`.
280
+ - `t.number` accepts only finite JavaScript numbers. `NaN`, `Infinity`, and
281
+ `-Infinity` are rejected before configured numeric predicates run.
282
+ - String length bounds must be non-negative integers.
283
+ - Numeric comparison bounds must be finite.
284
+ - Predicate callbacks must return strict `true`; truthy non-boolean values do
285
+ not pass validation.
286
+ - `RegExp` checks reset `lastIndex` before each test, so global and sticky
287
+ regexps do not leak state across validations.
288
+ - String regex builders and direct string regex schemas accept only plain
289
+ `RegExp` instances. Accepted regex checks are cloned before storage.
290
+
291
+ ## Result Contract
292
+
293
+ ```ts
294
+ type Result<T, E> =
295
+ | { ok: true; value: T }
296
+ | { ok: false; error: E };
297
+ ```
298
+
299
+ Expected validation failures use `Result`. Result containers are frozen at
300
+ runtime. Successful values are not recursively frozen because they are
301
+ caller-owned data.
@@ -0,0 +1,153 @@
1
+ # Engine Notes
2
+
3
+ TypeSea is written for predictable machine behavior after TypeScript emits
4
+ JavaScript. The goal is not obscurity; the goal is to make object shapes,
5
+ allocation sites, branch behavior, and validation contracts visible in code.
6
+
7
+ ## Hot Path Rules
8
+
9
+ - Use prototype methods instead of per-instance method closures.
10
+ - Use numeric tags for schema, check, issue, and IR node variants.
11
+ - Initialize class fields in one constructor order.
12
+ - Keep successful `is()` validation free of diagnostic allocation.
13
+ - Allocate `Issue` objects and path arrays only when diagnostics are requested.
14
+ - Prefer indexed loops on recursive validation paths.
15
+ - Precompute object-entry arrays during schema construction.
16
+ - After required object fields have proved their data-property descriptors, load
17
+ descriptor values directly instead of rechecking missing-property fallbacks.
18
+ - For all-required strict objects, reject extras by counting own string names
19
+ and own symbols after field validation. Optional strict objects keep the full
20
+ key membership scan.
21
+ - Mark constructed guards out-of-band so normal receivers avoid repeated schema
22
+ validation while forged receivers still fall back to structural checks.
23
+ - Use `Readonly<Record<string, unknown>>` after object guards.
24
+ - Store generated-validator literals, regexps, keysets, and dynamic fallbacks in
25
+ side tables instead of interpolating user-controlled values into source text.
26
+
27
+ ## Type-System Rules
28
+
29
+ - `optional(inner)` means an object key may be absent.
30
+ - `undefinedable(inner)` means the key must exist when used in an object shape,
31
+ but its value may be `undefined`.
32
+ - `nullable(inner)` means the value may be `null`.
33
+ - Presence-preserving wrappers do not erase optional-key semantics.
34
+ - `number` means finite JavaScript number.
35
+ - `unknown` is the only accepted boundary type for untrusted input.
36
+ - Builder validation is the hard barrier before a schema reaches the engine.
37
+
38
+ ## IR Rules
39
+
40
+ The public schema tree is the semantic source used by builders and diagnostic
41
+ collectors. Boolean validation executes a cached `ValidationPlan`: schema
42
+ identity is lowered into Sea-of-Nodes IR, the optimizer runs, and the plan keeps
43
+ both the frozen graph and a schema-specialized predicate kernel.
44
+
45
+ The graph is not decorative. `compile()`, AOT emission, and `Guard.graph()` all
46
+ consume the optimized graph held by the plan. Ordinary `Guard.is()` deliberately
47
+ uses the sibling schema-specialized kernel instead of a generic node interpreter,
48
+ because per-node dispatch and scratch-slot bookkeeping cost more than they buy
49
+ on the most common hot path.
50
+
51
+ Current lowering hash-conses pure value and predicate nodes. Strict object
52
+ schemas lower an explicit keyset check into the IR, so extra-key rejection does
53
+ not depend on out-of-band schema knowledge. Required and optional object fields
54
+ separate key presence from data-property presence, which keeps accessor-backed
55
+ properties from executing getters or being misclassified as valid values.
56
+
57
+ `Guard.graph()` returns the same optimized graph held by the validation plan.
58
+ Public graph values are validated and frozen before leaving the API. The first
59
+ optimizer pass performs reachable node elimination and compacts node ids so every
60
+ dependency points at an existing dense node index. `compile()` and AOT emission
61
+ use this graph as their predicate source.
62
+
63
+ Array, tuple, and record schemas lower to native composite IR nodes whose child
64
+ schemas are executed through child validation plans. `SchemaCheck` is reserved
65
+ for dynamic schemas such as `lazy` and `refine`; the graph records that callback
66
+ or resolver-backed semantics are required instead of pretending they are static
67
+ predicates.
68
+
69
+ ## Runtime Compiler
70
+
71
+ Compiled guards emit boolean predicates from optimized Sea-of-Nodes graphs and
72
+ schema-aware diagnostics collectors for failed values. Runtime `is()` uses the
73
+ plan-owned schema-specialized kernel to avoid recursive node dispatch and scratch
74
+ buffer churn. `check()` first asks the plan predicate for the pass/fail verdict;
75
+ successful values skip diagnostic collection, while failed values replay the
76
+ diagnostic collector to build paths and issue codes.
77
+
78
+ User-controlled literals, regexps, object keys, keysets, dynamic schemas, and
79
+ diagnostic names live in side tables captured by the generated factory. The
80
+ generated source contains numeric side-table indexes, fixed helper strings, and
81
+ sanitized function names.
82
+
83
+ Scalar nodes emit direct JavaScript tests where the semantics are local:
84
+ finite-number checks, integer checks, string length bounds, literal equality,
85
+ and regexp tests all lower without helper calls on the generated hot path.
86
+
87
+ Array and record IR nodes emit indexed loops. Static child schemas are inlined
88
+ into those loops from their optimized graphs, which avoids function-call
89
+ boundaries for small scalar or union element contracts. Tuple nodes preserve
90
+ descriptor-based element access, and dynamic edges use the same IR-backed
91
+ runtime fallback as ordinary guard execution, preserving behavior for `lazy` and
92
+ `refine`.
93
+
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.
102
+
103
+ ## Recursion
104
+
105
+ Lazy schemas resolve their getter once per guard instance. Recursive validation
106
+ therefore sees stable schema identity, and repeated validations do not rebuild
107
+ the recursive schema graph.
108
+
109
+ Recursive validation uses a root-local active pair table keyed by runtime object
110
+ identity and schema identity. Re-entering the same schema/value pair
111
+ short-circuits that edge, which lets cyclic object graphs validate finitely while
112
+ still checking the original object fields on the outer frame.
113
+
114
+ Compiled `lazy` and `refine` fallbacks use the same IR-backed runtime path, so
115
+ recursive behavior stays consistent across execution engines.
116
+
117
+ ## JSON Schema Export
118
+
119
+ JSON Schema export succeeds only when the TypeSea schema can be represented over
120
+ JSON-compatible input values without semantic loss. Runtime-only concepts return
121
+ typed `Result` errors.
122
+
123
+ Export diagnostics keep paths at the failed child slot instead of collapsing
124
+ everything to the parent container. Nested unsupported schemas therefore remain
125
+ actionable without reconstructing the schema tree manually.
126
+
127
+ Literal checks use `Object.is` in runtime-plan and compiled paths. Diagnostics
128
+ use the same literal formatting, including `-0`, so compiled and runtime-plan
129
+ `check()` results stay byte-for-byte comparable in tests.
130
+
131
+ ## Benchmark Scope
132
+
133
+ The benchmark suite keeps two questions separate:
134
+
135
+ - `compile.bench.ts` compares TypeSea runtime-plan and compiled validators over
136
+ the same TypeSea schema.
137
+ - `ecosystem.bench.ts` compares TypeSea runtime-plan, TypeSea compiled, Zod,
138
+ Valibot, and Ajv over one JSON-compatible strict-object contract.
139
+
140
+ Zod, Valibot, and Ajv are dev dependencies for measurement only. They are not
141
+ imported by `src`, and package policy rejects runtime, peer, optional, or
142
+ bundled dependency fields before release.
143
+
144
+ Last local release smoke on 2026-07-04 KST reported this ecosystem boolean
145
+ path over the JSON-compatible strict-object benchmark:
146
+
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 |
151
+
152
+ Benchmark numbers are machine-local telemetry. They are useful for catching
153
+ regressions, not for promising a fixed throughput floor.