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.
- package/CHANGELOG.md +9 -0
- package/LICENSE +21 -0
- package/README.md +320 -0
- package/dist/adapters/index.d.ts +152 -0
- package/dist/adapters/index.d.ts.map +1 -0
- package/dist/adapters/index.js +396 -0
- package/dist/aot/index.d.ts +33 -0
- package/dist/aot/index.d.ts.map +1 -0
- package/dist/aot/index.js +295 -0
- package/dist/async/index.d.ts +111 -0
- package/dist/async/index.d.ts.map +1 -0
- package/dist/async/index.js +221 -0
- package/dist/builders/composite.d.ts +31 -0
- package/dist/builders/composite.d.ts.map +1 -0
- package/dist/builders/composite.js +165 -0
- package/dist/builders/index.d.ts +11 -0
- package/dist/builders/index.d.ts.map +1 -0
- package/dist/builders/index.js +9 -0
- package/dist/builders/modifier.d.ts +26 -0
- package/dist/builders/modifier.d.ts.map +1 -0
- package/dist/builders/modifier.js +67 -0
- package/dist/builders/object/guard.d.ts +62 -0
- package/dist/builders/object/guard.d.ts.map +1 -0
- package/dist/builders/object/guard.js +113 -0
- package/dist/builders/object/index.d.ts +7 -0
- package/dist/builders/object/index.d.ts.map +1 -0
- package/dist/builders/object/index.js +5 -0
- package/dist/builders/object/schema.d.ts +44 -0
- package/dist/builders/object/schema.d.ts.map +1 -0
- package/dist/builders/object/schema.js +257 -0
- package/dist/builders/object/types.d.ts +63 -0
- package/dist/builders/object/types.d.ts.map +1 -0
- package/dist/builders/object/types.js +5 -0
- package/dist/builders/scalar.d.ts +39 -0
- package/dist/builders/scalar.d.ts.map +1 -0
- package/dist/builders/scalar.js +63 -0
- package/dist/builders/table.d.ts +53 -0
- package/dist/builders/table.d.ts.map +1 -0
- package/dist/builders/table.js +48 -0
- package/dist/builders/types.d.ts +26 -0
- package/dist/builders/types.d.ts.map +1 -0
- package/dist/builders/types.js +5 -0
- package/dist/compile/check-composite.d.ts +34 -0
- package/dist/compile/check-composite.d.ts.map +1 -0
- package/dist/compile/check-composite.js +117 -0
- package/dist/compile/check-scalar.d.ts +24 -0
- package/dist/compile/check-scalar.d.ts.map +1 -0
- package/dist/compile/check-scalar.js +73 -0
- package/dist/compile/check.d.ts +15 -0
- package/dist/compile/check.d.ts.map +1 -0
- package/dist/compile/check.js +98 -0
- package/dist/compile/context.d.ts +35 -0
- package/dist/compile/context.d.ts.map +1 -0
- package/dist/compile/context.js +72 -0
- package/dist/compile/graph-predicate.d.ts +19 -0
- package/dist/compile/graph-predicate.d.ts.map +1 -0
- package/dist/compile/graph-predicate.js +460 -0
- package/dist/compile/guard.d.ts +41 -0
- package/dist/compile/guard.d.ts.map +1 -0
- package/dist/compile/guard.js +180 -0
- package/dist/compile/index.d.ts +8 -0
- package/dist/compile/index.d.ts.map +1 -0
- package/dist/compile/index.js +6 -0
- package/dist/compile/issue.d.ts +18 -0
- package/dist/compile/issue.d.ts.map +1 -0
- package/dist/compile/issue.js +28 -0
- package/dist/compile/names.d.ts +16 -0
- package/dist/compile/names.d.ts.map +1 -0
- package/dist/compile/names.js +82 -0
- package/dist/compile/predicate.d.ts +23 -0
- package/dist/compile/predicate.d.ts.map +1 -0
- package/dist/compile/predicate.js +317 -0
- package/dist/compile/runtime.d.ts +55 -0
- package/dist/compile/runtime.d.ts.map +1 -0
- package/dist/compile/runtime.js +63 -0
- package/dist/compile/source.d.ts +11 -0
- package/dist/compile/source.d.ts.map +1 -0
- package/dist/compile/source.js +51 -0
- package/dist/compile/types.d.ts +52 -0
- package/dist/compile/types.d.ts.map +1 -0
- package/dist/compile/types.js +5 -0
- package/dist/decoder/index.d.ts +106 -0
- package/dist/decoder/index.d.ts.map +1 -0
- package/dist/decoder/index.js +262 -0
- package/dist/evaluate/check-composite.d.ts +39 -0
- package/dist/evaluate/check-composite.d.ts.map +1 -0
- package/dist/evaluate/check-composite.js +184 -0
- package/dist/evaluate/check-scalar.d.ts +20 -0
- package/dist/evaluate/check-scalar.d.ts.map +1 -0
- package/dist/evaluate/check-scalar.js +81 -0
- package/dist/evaluate/check.d.ts +11 -0
- package/dist/evaluate/check.d.ts.map +1 -0
- package/dist/evaluate/check.js +126 -0
- package/dist/evaluate/index.d.ts +7 -0
- package/dist/evaluate/index.d.ts.map +1 -0
- package/dist/evaluate/index.js +6 -0
- package/dist/evaluate/issue.d.ts +10 -0
- package/dist/evaluate/issue.d.ts.map +1 -0
- package/dist/evaluate/issue.js +11 -0
- package/dist/evaluate/predicate.d.ts +26 -0
- package/dist/evaluate/predicate.d.ts.map +1 -0
- package/dist/evaluate/predicate.js +37 -0
- package/dist/evaluate/shared.d.ts +59 -0
- package/dist/evaluate/shared.d.ts.map +1 -0
- package/dist/evaluate/shared.js +96 -0
- package/dist/evaluate/state.d.ts +65 -0
- package/dist/evaluate/state.d.ts.map +1 -0
- package/dist/evaluate/state.js +66 -0
- package/dist/guard/base.d.ts +72 -0
- package/dist/guard/base.d.ts.map +1 -0
- package/dist/guard/base.js +136 -0
- package/dist/guard/error.d.ts +19 -0
- package/dist/guard/error.d.ts.map +1 -0
- package/dist/guard/error.js +22 -0
- package/dist/guard/index.d.ts +10 -0
- package/dist/guard/index.d.ts.map +1 -0
- package/dist/guard/index.js +8 -0
- package/dist/guard/number.d.ts +32 -0
- package/dist/guard/number.d.ts.map +1 -0
- package/dist/guard/number.js +71 -0
- package/dist/guard/props.d.ts +18 -0
- package/dist/guard/props.d.ts.map +1 -0
- package/dist/guard/props.js +35 -0
- package/dist/guard/read.d.ts +42 -0
- package/dist/guard/read.d.ts.map +1 -0
- package/dist/guard/read.js +114 -0
- package/dist/guard/registry.d.ts +15 -0
- package/dist/guard/registry.d.ts.map +1 -0
- package/dist/guard/registry.js +21 -0
- package/dist/guard/string.d.ts +36 -0
- package/dist/guard/string.d.ts.map +1 -0
- package/dist/guard/string.js +95 -0
- package/dist/guard/types.d.ts +103 -0
- package/dist/guard/types.d.ts.map +1 -0
- package/dist/guard/types.js +5 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +10 -0
- package/dist/internal/index.d.ts +34 -0
- package/dist/internal/index.d.ts.map +1 -0
- package/dist/internal/index.js +56 -0
- package/dist/ir/builder.d.ts +173 -0
- package/dist/ir/builder.d.ts.map +1 -0
- package/dist/ir/builder.js +481 -0
- package/dist/ir/freeze.d.ts +10 -0
- package/dist/ir/freeze.d.ts.map +1 -0
- package/dist/ir/freeze.js +102 -0
- package/dist/ir/index.d.ts +9 -0
- package/dist/ir/index.d.ts.map +1 -0
- package/dist/ir/index.js +7 -0
- package/dist/ir/regexp.d.ts +6 -0
- package/dist/ir/regexp.d.ts.map +1 -0
- package/dist/ir/regexp.js +12 -0
- package/dist/ir/types.d.ts +215 -0
- package/dist/ir/types.d.ts.map +1 -0
- package/dist/ir/types.js +5 -0
- package/dist/ir/validate.d.ts +10 -0
- package/dist/ir/validate.d.ts.map +1 -0
- package/dist/ir/validate.js +271 -0
- package/dist/issue/index.d.ts +44 -0
- package/dist/issue/index.d.ts.map +1 -0
- package/dist/issue/index.js +152 -0
- package/dist/json-schema/emit-combinator.d.ts +28 -0
- package/dist/json-schema/emit-combinator.d.ts.map +1 -0
- package/dist/json-schema/emit-combinator.js +96 -0
- package/dist/json-schema/emit-composite.d.ts +28 -0
- package/dist/json-schema/emit-composite.d.ts.map +1 -0
- package/dist/json-schema/emit-composite.js +127 -0
- package/dist/json-schema/emit-scalar.d.ts +25 -0
- package/dist/json-schema/emit-scalar.d.ts.map +1 -0
- package/dist/json-schema/emit-scalar.js +104 -0
- package/dist/json-schema/emit-types.d.ts +12 -0
- package/dist/json-schema/emit-types.d.ts.map +1 -0
- package/dist/json-schema/emit-types.js +5 -0
- package/dist/json-schema/emit.d.ts +12 -0
- package/dist/json-schema/emit.d.ts.map +1 -0
- package/dist/json-schema/emit.js +62 -0
- package/dist/json-schema/freeze.d.ts +14 -0
- package/dist/json-schema/freeze.d.ts.map +1 -0
- package/dist/json-schema/freeze.js +114 -0
- package/dist/json-schema/index.d.ts +20 -0
- package/dist/json-schema/index.d.ts.map +1 -0
- package/dist/json-schema/index.js +76 -0
- package/dist/json-schema/issue.d.ts +11 -0
- package/dist/json-schema/issue.d.ts.map +1 -0
- package/dist/json-schema/issue.js +14 -0
- package/dist/json-schema/read.d.ts +29 -0
- package/dist/json-schema/read.d.ts.map +1 -0
- package/dist/json-schema/read.js +87 -0
- package/dist/json-schema/types.d.ts +106 -0
- package/dist/json-schema/types.d.ts.map +1 -0
- package/dist/json-schema/types.js +5 -0
- package/dist/kind/index.d.ts +119 -0
- package/dist/kind/index.d.ts.map +1 -0
- package/dist/kind/index.js +94 -0
- package/dist/lower/index.d.ts +7 -0
- package/dist/lower/index.d.ts.map +1 -0
- package/dist/lower/index.js +199 -0
- package/dist/message/index.d.ts +51 -0
- package/dist/message/index.d.ts.map +1 -0
- package/dist/message/index.js +269 -0
- package/dist/optimize/compact.d.ts +10 -0
- package/dist/optimize/compact.d.ts.map +1 -0
- package/dist/optimize/compact.js +60 -0
- package/dist/optimize/fold-boolean.d.ts +15 -0
- package/dist/optimize/fold-boolean.d.ts.map +1 -0
- package/dist/optimize/fold-boolean.js +75 -0
- package/dist/optimize/fold-common.d.ts +45 -0
- package/dist/optimize/fold-common.d.ts.map +1 -0
- package/dist/optimize/fold-common.js +71 -0
- package/dist/optimize/fold-scalar.d.ts +59 -0
- package/dist/optimize/fold-scalar.d.ts.map +1 -0
- package/dist/optimize/fold-scalar.js +174 -0
- package/dist/optimize/fold.d.ts +10 -0
- package/dist/optimize/fold.d.ts.map +1 -0
- package/dist/optimize/fold.js +103 -0
- package/dist/optimize/index.d.ts +10 -0
- package/dist/optimize/index.d.ts.map +1 -0
- package/dist/optimize/index.js +23 -0
- package/dist/optimize/map-node.d.ts +21 -0
- package/dist/optimize/map-node.d.ts.map +1 -0
- package/dist/optimize/map-node.js +222 -0
- package/dist/optimize/remap.d.ts +30 -0
- package/dist/optimize/remap.d.ts.map +1 -0
- package/dist/optimize/remap.js +46 -0
- package/dist/optimize/rewrite.d.ts +22 -0
- package/dist/optimize/rewrite.d.ts.map +1 -0
- package/dist/optimize/rewrite.js +34 -0
- package/dist/plan/cache.d.ts +20 -0
- package/dist/plan/cache.d.ts.map +1 -0
- package/dist/plan/cache.js +122 -0
- package/dist/plan/index.d.ts +8 -0
- package/dist/plan/index.d.ts.map +1 -0
- package/dist/plan/index.js +6 -0
- package/dist/plan/predicate.d.ts +27 -0
- package/dist/plan/predicate.d.ts.map +1 -0
- package/dist/plan/predicate.js +415 -0
- package/dist/plan/schema-predicate.d.ts +15 -0
- package/dist/plan/schema-predicate.d.ts.map +1 -0
- package/dist/plan/schema-predicate.js +277 -0
- package/dist/plan/types.d.ts +18 -0
- package/dist/plan/types.d.ts.map +1 -0
- package/dist/plan/types.js +5 -0
- package/dist/result/index.d.ts +27 -0
- package/dist/result/index.d.ts.map +1 -0
- package/dist/result/index.js +20 -0
- package/dist/schema/common.d.ts +30 -0
- package/dist/schema/common.d.ts.map +1 -0
- package/dist/schema/common.js +102 -0
- package/dist/schema/freeze.d.ts +10 -0
- package/dist/schema/freeze.d.ts.map +1 -0
- package/dist/schema/freeze.js +163 -0
- package/dist/schema/index.d.ts +11 -0
- package/dist/schema/index.d.ts.map +1 -0
- package/dist/schema/index.js +9 -0
- package/dist/schema/lazy.d.ts +10 -0
- package/dist/schema/lazy.d.ts.map +1 -0
- package/dist/schema/lazy.js +25 -0
- package/dist/schema/literal.d.ts +10 -0
- package/dist/schema/literal.d.ts.map +1 -0
- package/dist/schema/literal.js +17 -0
- package/dist/schema/types.d.ts +243 -0
- package/dist/schema/types.d.ts.map +1 -0
- package/dist/schema/types.js +9 -0
- package/dist/schema/validate.d.ts +10 -0
- package/dist/schema/validate.d.ts.map +1 -0
- package/dist/schema/validate.js +268 -0
- package/docs/api.md +301 -0
- package/docs/engine-notes.md +153 -0
- package/docs/index.html +1242 -0
- 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.
|