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.
- package/CHANGELOG.md +85 -6
- package/README.md +143 -28
- package/dist/adapters/index.d.ts +50 -8
- package/dist/adapters/index.d.ts.map +1 -1
- package/dist/adapters/index.js +169 -48
- package/dist/aot/index.d.ts +19 -3
- package/dist/aot/index.d.ts.map +1 -1
- package/dist/aot/index.js +115 -17
- package/dist/async/index.d.ts +28 -56
- package/dist/async/index.d.ts.map +1 -1
- package/dist/async/index.js +94 -37
- package/dist/builders/composite.d.ts +43 -9
- package/dist/builders/composite.d.ts.map +1 -1
- package/dist/builders/composite.js +100 -17
- package/dist/builders/index.d.ts +8 -5
- package/dist/builders/index.d.ts.map +1 -1
- package/dist/builders/index.js +7 -4
- package/dist/builders/modifier.d.ts +36 -5
- package/dist/builders/modifier.d.ts.map +1 -1
- package/dist/builders/modifier.js +52 -5
- package/dist/builders/object/guard.d.ts +72 -24
- package/dist/builders/object/guard.d.ts.map +1 -1
- package/dist/builders/object/guard.js +139 -29
- package/dist/builders/object/index.d.ts +4 -2
- package/dist/builders/object/index.d.ts.map +1 -1
- package/dist/builders/object/index.js +3 -1
- package/dist/builders/object/schema.d.ts +88 -11
- package/dist/builders/object/schema.d.ts.map +1 -1
- package/dist/builders/object/schema.js +290 -23
- package/dist/builders/object/types.d.ts +20 -31
- package/dist/builders/object/types.d.ts.map +1 -1
- package/dist/builders/object/types.js +2 -0
- package/dist/builders/runtime.d.ts +40 -0
- package/dist/builders/runtime.d.ts.map +1 -0
- package/dist/builders/runtime.js +150 -0
- package/dist/builders/scalar.d.ts +49 -9
- package/dist/builders/scalar.d.ts.map +1 -1
- package/dist/builders/scalar.js +87 -9
- package/dist/builders/table.d.ts +35 -5
- package/dist/builders/table.d.ts.map +1 -1
- package/dist/builders/table.js +35 -5
- package/dist/builders/types.d.ts +20 -4
- package/dist/builders/types.d.ts.map +1 -1
- package/dist/builders/types.js +2 -0
- package/dist/compile/check-composite.d.ts +25 -2
- package/dist/compile/check-composite.d.ts.map +1 -1
- package/dist/compile/check-composite.js +699 -27
- package/dist/compile/check-scalar.d.ts +88 -0
- package/dist/compile/check-scalar.d.ts.map +1 -1
- package/dist/compile/check-scalar.js +570 -3
- package/dist/compile/check.d.ts +12 -0
- package/dist/compile/check.d.ts.map +1 -1
- package/dist/compile/check.js +62 -3
- package/dist/compile/context.d.ts +47 -9
- package/dist/compile/context.d.ts.map +1 -1
- package/dist/compile/context.js +53 -8
- package/dist/compile/first.d.ts +26 -0
- package/dist/compile/first.d.ts.map +1 -0
- package/dist/compile/first.js +850 -0
- package/dist/compile/graph-predicate.d.ts +4 -2
- package/dist/compile/graph-predicate.d.ts.map +1 -1
- package/dist/compile/graph-predicate.js +2272 -165
- package/dist/compile/guard.d.ts +16 -24
- package/dist/compile/guard.d.ts.map +1 -1
- package/dist/compile/guard.js +202 -72
- package/dist/compile/index.d.ts +3 -1
- package/dist/compile/index.d.ts.map +1 -1
- package/dist/compile/index.js +2 -0
- package/dist/compile/issue.d.ts +110 -0
- package/dist/compile/issue.d.ts.map +1 -1
- package/dist/compile/issue.js +184 -1
- package/dist/compile/names.d.ts +12 -2
- package/dist/compile/names.d.ts.map +1 -1
- package/dist/compile/names.js +19 -3
- package/dist/compile/predicate.d.ts +24 -0
- package/dist/compile/predicate.d.ts.map +1 -1
- package/dist/compile/predicate.js +287 -10
- package/dist/compile/runtime.d.ts +100 -13
- package/dist/compile/runtime.d.ts.map +1 -1
- package/dist/compile/runtime.js +56 -6
- package/dist/compile/source.d.ts +10 -2
- package/dist/compile/source.d.ts.map +1 -1
- package/dist/compile/source.js +385 -26
- package/dist/compile/types.d.ts +22 -0
- package/dist/compile/types.d.ts.map +1 -1
- package/dist/compile/types.js +2 -0
- package/dist/decoder/index.d.ts +92 -46
- package/dist/decoder/index.d.ts.map +1 -1
- package/dist/decoder/index.js +266 -39
- package/dist/evaluate/check-composite.d.ts +111 -2
- package/dist/evaluate/check-composite.d.ts.map +1 -1
- package/dist/evaluate/check-composite.js +343 -8
- package/dist/evaluate/check-scalar.d.ts +25 -0
- package/dist/evaluate/check-scalar.d.ts.map +1 -1
- package/dist/evaluate/check-scalar.js +124 -3
- package/dist/evaluate/check.d.ts +7 -0
- package/dist/evaluate/check.d.ts.map +1 -1
- package/dist/evaluate/check.js +62 -4
- package/dist/evaluate/index.d.ts +2 -0
- package/dist/evaluate/index.d.ts.map +1 -1
- package/dist/evaluate/index.js +2 -0
- package/dist/evaluate/issue.d.ts +11 -1
- package/dist/evaluate/issue.d.ts.map +1 -1
- package/dist/evaluate/issue.js +15 -1
- package/dist/evaluate/predicate.d.ts +16 -5
- package/dist/evaluate/predicate.d.ts.map +1 -1
- package/dist/evaluate/predicate.js +20 -5
- package/dist/evaluate/shared.d.ts +78 -13
- package/dist/evaluate/shared.d.ts.map +1 -1
- package/dist/evaluate/shared.js +101 -8
- package/dist/evaluate/state.d.ts +35 -13
- package/dist/evaluate/state.d.ts.map +1 -1
- package/dist/evaluate/state.js +35 -2
- package/dist/guard/array.d.ts +48 -0
- package/dist/guard/array.d.ts.map +1 -0
- package/dist/guard/array.js +84 -0
- package/dist/guard/base.d.ts +111 -31
- package/dist/guard/base.d.ts.map +1 -1
- package/dist/guard/base.js +165 -32
- package/dist/guard/date.d.ts +34 -0
- package/dist/guard/date.d.ts.map +1 -0
- package/dist/guard/date.js +60 -0
- package/dist/guard/error.d.ts +10 -5
- package/dist/guard/error.d.ts.map +1 -1
- package/dist/guard/error.js +10 -5
- package/dist/guard/index.d.ts +4 -0
- package/dist/guard/index.d.ts.map +1 -1
- package/dist/guard/index.js +4 -0
- package/dist/guard/number.d.ts +86 -11
- package/dist/guard/number.d.ts.map +1 -1
- package/dist/guard/number.js +159 -11
- package/dist/guard/props.d.ts +27 -3
- package/dist/guard/props.d.ts.map +1 -1
- package/dist/guard/props.js +27 -3
- package/dist/guard/read.d.ts +115 -10
- package/dist/guard/read.d.ts.map +1 -1
- package/dist/guard/read.js +185 -10
- package/dist/guard/registry.d.ts +12 -2
- package/dist/guard/registry.d.ts.map +1 -1
- package/dist/guard/registry.js +15 -3
- package/dist/guard/string.d.ts +115 -13
- package/dist/guard/string.d.ts.map +1 -1
- package/dist/guard/string.js +250 -13
- package/dist/guard/types.d.ts +110 -40
- package/dist/guard/types.d.ts.map +1 -1
- package/dist/guard/types.js +2 -0
- package/dist/index.d.ts +5 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -4
- package/dist/internal/index.d.ts +42 -6
- package/dist/internal/index.d.ts.map +1 -1
- package/dist/internal/index.js +51 -8
- package/dist/ir/builder.d.ts +17 -127
- package/dist/ir/builder.d.ts.map +1 -1
- package/dist/ir/builder.js +80 -137
- package/dist/ir/freeze.d.ts +4 -0
- package/dist/ir/freeze.d.ts.map +1 -1
- package/dist/ir/freeze.js +66 -0
- package/dist/ir/index.d.ts +3 -1
- package/dist/ir/index.d.ts.map +1 -1
- package/dist/ir/index.js +2 -0
- package/dist/ir/regexp.d.ts +2 -0
- package/dist/ir/regexp.d.ts.map +1 -1
- package/dist/ir/regexp.js +2 -0
- package/dist/ir/types.d.ts +94 -56
- package/dist/ir/types.d.ts.map +1 -1
- package/dist/ir/types.js +2 -0
- package/dist/ir/validate.d.ts +8 -1
- package/dist/ir/validate.d.ts.map +1 -1
- package/dist/ir/validate.js +511 -61
- package/dist/issue/index.d.ts +42 -10
- package/dist/issue/index.d.ts.map +1 -1
- package/dist/issue/index.js +65 -11
- package/dist/json-schema/emit-combinator.d.ts +44 -4
- package/dist/json-schema/emit-combinator.d.ts.map +1 -1
- package/dist/json-schema/emit-combinator.js +44 -4
- package/dist/json-schema/emit-composite.d.ts +16 -2
- package/dist/json-schema/emit-composite.d.ts.map +1 -1
- package/dist/json-schema/emit-composite.js +81 -13
- package/dist/json-schema/emit-scalar.d.ts +26 -3
- package/dist/json-schema/emit-scalar.d.ts.map +1 -1
- package/dist/json-schema/emit-scalar.js +124 -10
- package/dist/json-schema/emit-types.d.ts +11 -1
- package/dist/json-schema/emit-types.d.ts.map +1 -1
- package/dist/json-schema/emit-types.js +2 -0
- package/dist/json-schema/emit.d.ts +12 -1
- package/dist/json-schema/emit.d.ts.map +1 -1
- package/dist/json-schema/emit.js +23 -3
- package/dist/json-schema/freeze.d.ts +13 -2
- package/dist/json-schema/freeze.d.ts.map +1 -1
- package/dist/json-schema/freeze.js +41 -8
- package/dist/json-schema/index.d.ts +16 -2
- package/dist/json-schema/index.d.ts.map +1 -1
- package/dist/json-schema/index.js +23 -3
- package/dist/json-schema/issue.d.ts +4 -1
- package/dist/json-schema/issue.d.ts.map +1 -1
- package/dist/json-schema/issue.js +4 -1
- package/dist/json-schema/read.d.ts +24 -3
- package/dist/json-schema/read.d.ts.map +1 -1
- package/dist/json-schema/read.js +59 -12
- package/dist/json-schema/types.d.ts +45 -16
- package/dist/json-schema/types.d.ts.map +1 -1
- package/dist/json-schema/types.js +2 -0
- package/dist/kind/index.d.ts +40 -28
- package/dist/kind/index.d.ts.map +1 -1
- package/dist/kind/index.js +41 -13
- package/dist/lower/index.d.ts +6 -1
- package/dist/lower/index.d.ts.map +1 -1
- package/dist/lower/index.js +462 -46
- package/dist/message/index.d.ts +64 -10
- package/dist/message/index.d.ts.map +1 -1
- package/dist/message/index.js +155 -17
- package/dist/optimize/algebraic.d.ts +54 -0
- package/dist/optimize/algebraic.d.ts.map +1 -0
- package/dist/optimize/algebraic.js +314 -0
- package/dist/optimize/compact.d.ts +8 -1
- package/dist/optimize/compact.d.ts.map +1 -1
- package/dist/optimize/compact.js +13 -2
- package/dist/optimize/domain.d.ts +16 -0
- package/dist/optimize/domain.d.ts.map +1 -0
- package/dist/optimize/domain.js +619 -0
- package/dist/optimize/fold-boolean.d.ts +17 -2
- package/dist/optimize/fold-boolean.d.ts.map +1 -1
- package/dist/optimize/fold-boolean.js +59 -14
- package/dist/optimize/fold-common.d.ts +43 -8
- package/dist/optimize/fold-common.d.ts.map +1 -1
- package/dist/optimize/fold-common.js +37 -6
- package/dist/optimize/fold-constraints.d.ts +33 -0
- package/dist/optimize/fold-constraints.d.ts.map +1 -0
- package/dist/optimize/fold-constraints.js +484 -0
- package/dist/optimize/fold-scalar.d.ts +98 -13
- package/dist/optimize/fold-scalar.d.ts.map +1 -1
- package/dist/optimize/fold-scalar.js +98 -13
- package/dist/optimize/fold.d.ts +8 -1
- package/dist/optimize/fold.d.ts.map +1 -1
- package/dist/optimize/fold.js +22 -2
- package/dist/optimize/index.d.ts +9 -1
- package/dist/optimize/index.d.ts.map +1 -1
- package/dist/optimize/index.js +18 -3
- package/dist/optimize/map-node.d.ts +3 -1
- package/dist/optimize/map-node.d.ts.map +1 -1
- package/dist/optimize/map-node.js +48 -3
- package/dist/optimize/peephole.d.ts +16 -0
- package/dist/optimize/peephole.d.ts.map +1 -0
- package/dist/optimize/peephole.js +254 -0
- package/dist/optimize/remap.d.ts +2 -0
- package/dist/optimize/remap.d.ts.map +1 -1
- package/dist/optimize/remap.js +2 -0
- package/dist/optimize/rewrite.d.ts +13 -8
- package/dist/optimize/rewrite.d.ts.map +1 -1
- package/dist/optimize/rewrite.js +13 -8
- package/dist/plan/cache.d.ts +9 -3
- package/dist/plan/cache.d.ts.map +1 -1
- package/dist/plan/cache.js +34 -6
- package/dist/plan/index.d.ts +2 -0
- package/dist/plan/index.d.ts.map +1 -1
- package/dist/plan/index.js +2 -0
- package/dist/plan/predicate.d.ts +2 -0
- package/dist/plan/predicate.d.ts.map +1 -1
- package/dist/plan/predicate.js +298 -29
- package/dist/plan/schema-predicate.d.ts +6 -0
- package/dist/plan/schema-predicate.d.ts.map +1 -1
- package/dist/plan/schema-predicate.js +382 -19
- package/dist/plan/types.d.ts +2 -0
- package/dist/plan/types.d.ts.map +1 -1
- package/dist/plan/types.js +2 -0
- package/dist/result/index.d.ts +19 -5
- package/dist/result/index.d.ts.map +1 -1
- package/dist/result/index.js +10 -2
- package/dist/schema/common.d.ts +69 -6
- package/dist/schema/common.d.ts.map +1 -1
- package/dist/schema/common.js +104 -10
- package/dist/schema/freeze.d.ts +4 -0
- package/dist/schema/freeze.d.ts.map +1 -1
- package/dist/schema/freeze.js +40 -0
- package/dist/schema/index.d.ts +5 -2
- package/dist/schema/index.d.ts.map +1 -1
- package/dist/schema/index.js +4 -1
- package/dist/schema/lazy.d.ts +4 -0
- package/dist/schema/lazy.d.ts.map +1 -1
- package/dist/schema/lazy.js +4 -0
- package/dist/schema/literal.d.ts +7 -1
- package/dist/schema/literal.d.ts.map +1 -1
- package/dist/schema/literal.js +7 -1
- package/dist/schema/types.d.ts +109 -100
- package/dist/schema/types.d.ts.map +1 -1
- package/dist/schema/types.js +13 -2
- package/dist/schema/undefined.d.ts +17 -0
- package/dist/schema/undefined.d.ts.map +1 -0
- package/dist/schema/undefined.js +77 -0
- package/dist/schema/validate.d.ts +8 -1
- package/dist/schema/validate.d.ts.map +1 -1
- package/dist/schema/validate.js +255 -57
- package/docs/api.md +128 -8
- package/docs/assets/benchmark-headline.svg +163 -0
- package/docs/engine-notes.md +62 -15
- package/docs/index.html +1340 -702
- package/docs/ko/api.md +375 -0
- package/docs/ko/engine-notes.md +156 -0
- package/docs/ko/readme.md +378 -0
- 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
|
-
|
|
|
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
|
|
156
|
-
validation has finished. This keeps `is()` and ordinary
|
|
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>
|
package/docs/engine-notes.md
CHANGED
|
@@ -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
|
|
95
|
-
|
|
96
|
-
`Object.getOwnPropertyNames(value).length` with the declared key
|
|
97
|
-
require `Object.getOwnPropertySymbols(value).length === 0`.
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
emit the full own-key membership scan because a
|
|
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
|
|
145
|
-
|
|
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
|
|
150
|
-
|
|
|
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.
|