functionalscript 0.46.0 → 0.47.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/fjs/basen/cbase32/module.f.mjs +18 -10
- package/fjs/basen/cbase32/proof.f.d.mts +3 -0
- package/fjs/basen/cbase32/proof.f.mjs +24 -2
- package/fjs/basen/proof.f.mjs +8 -16
- package/fjs/bnf/ll1/proof.f.d.mts +2 -0
- package/fjs/bnf/ll1/proof.f.mjs +33 -18
- package/fjs/bnf/module.f.d.mts +2 -2
- package/fjs/bnf/module.f.mjs +2 -2
- package/fjs/cas/proof.f.mjs +4 -26
- package/fjs/ci/common/module.f.mjs +6 -0
- package/fjs/ci/config/module.f.d.mts +7 -7
- package/fjs/ci/config/module.f.mjs +7 -7
- package/fjs/cli/module.f.mjs +14 -10
- package/fjs/crypto/vdf/module.f.d.mts +0 -1
- package/fjs/crypto/vdf/module.f.mjs +3 -14
- package/fjs/dev/module.f.mjs +10 -20
- package/fjs/djs/parser/module.f.d.mts +101 -53
- package/fjs/djs/parser/module.f.mjs +732 -525
- package/fjs/djs/parser/proof.f.d.mts +6 -0
- package/fjs/djs/parser/proof.f.mjs +315 -11
- package/fjs/djs/parser/types.d.ts +29 -2
- package/fjs/djs/tokenizer/module.f.mjs +122 -105
- package/fjs/djs/tokenizer/proof.f.mjs +17 -0
- package/fjs/djs/transpiler/module.f.mjs +4 -5
- package/fjs/djs/transpiler/proof.f.d.mts +2 -0
- package/fjs/djs/transpiler/proof.f.mjs +25 -0
- package/fjs/edag/amnesia/module.f.d.mts +20 -0
- package/fjs/edag/amnesia/module.f.mjs +303 -0
- package/fjs/edag/amnesia/proof.f.d.mts +79 -0
- package/fjs/edag/amnesia/proof.f.mjs +573 -0
- package/fjs/edag/amnesia/types.d.ts +15 -0
- package/fjs/edag/module.f.d.mts +410 -0
- package/fjs/edag/module.f.mjs +559 -0
- package/fjs/edag/proof.f.d.mts +116 -0
- package/fjs/edag/proof.f.mjs +686 -0
- package/fjs/edag/types.d.ts +55 -0
- package/fjs/effects/node/memory/module.d.mts +37 -3
- package/fjs/effects/node/memory/module.mjs +38 -4
- package/fjs/effects/node/memory/proof.d.mts +5 -2
- package/fjs/effects/node/memory/proof.mjs +36 -6
- package/fjs/effects/node/module.d.mts +9 -1
- package/fjs/effects/node/module.f.d.mts +39 -0
- package/fjs/effects/node/module.f.mjs +46 -0
- package/fjs/effects/node/module.mjs +234 -32
- package/fjs/effects/node/types.d.ts +28 -2
- package/fjs/effects/node/virtual/module.f.d.mts +19 -10
- package/fjs/effects/node/virtual/module.f.mjs +246 -32
- package/fjs/effects/node/virtual/proof.f.d.mts +17 -2
- package/fjs/effects/node/virtual/proof.f.mjs +232 -8
- package/fjs/effects/node/virtual/types.d.ts +57 -1
- package/fjs/emergent_testing/browser/proof.d.mts +55 -0
- package/fjs/emergent_testing/browser/proof.mjs +352 -0
- package/fjs/emergent_testing/browser/species.proof.d.mts +4 -0
- package/fjs/emergent_testing/browser/species.proof.mjs +45 -0
- package/fjs/emergent_testing/browser.d.mts +79 -0
- package/fjs/emergent_testing/browser.mjs +432 -0
- package/fjs/emergent_testing/proof.f.d.mts +1 -1
- package/fjs/emergent_testing/proof.f.mjs +2 -2
- package/fjs/js/string_escape/module.f.d.mts +47 -0
- package/fjs/js/string_escape/module.f.mjs +95 -0
- package/fjs/js/string_escape/proof.f.d.mts +8 -0
- package/fjs/js/string_escape/proof.f.mjs +68 -0
- package/fjs/js/tokenizer/module.f.mjs +14 -12
- package/fjs/mcp/cas/module.f.d.mts +1 -7
- package/fjs/mcp/cas/module.f.mjs +24 -15
- package/fjs/mcp/evo/module.f.mjs +9 -24
- package/fjs/mcp/module.f.d.mts +3 -1
- package/fjs/mcp/module.f.mjs +4 -2
- package/fjs/media/json/rtti/module.f.d.mts +2 -2
- package/fjs/media/json/rtti/module.f.mjs +1 -1
- package/fjs/media/json/schema/module.f.d.mts +15 -5
- package/fjs/media/json/schema/module.f.mjs +42 -15
- package/fjs/media/json/schema/proof.f.d.mts +10 -1
- package/fjs/media/json/schema/proof.f.mjs +88 -16
- package/fjs/media/json/serializer/module.f.mjs +14 -26
- package/fjs/media/json/types.d.ts +1 -1
- package/fjs/media/lock/module.f.d.mts +8 -3
- package/fjs/media/lock/module.f.mjs +9 -3
- package/fjs/media/lock/proof.f.d.mts +1 -1
- package/fjs/media/lock/proof.f.mjs +2 -2
- package/fjs/media/lock/types.d.ts +1 -3
- package/fjs/media/module.f.d.mts +20 -13
- package/fjs/media/module.f.mjs +29 -16
- package/fjs/media/note/module.f.d.mts +14 -7
- package/fjs/media/note/module.f.mjs +14 -7
- package/fjs/media/note/proof.f.mjs +1 -1
- package/fjs/media/proof.f.mjs +5 -5
- package/fjs/media/revision/module.f.d.mts +8 -2
- package/fjs/media/revision/module.f.mjs +9 -3
- package/fjs/media/revision/proof.f.mjs +1 -1
- package/fjs/media/revision/types.d.ts +1 -1
- package/fjs/media/type/module.f.d.mts +20 -0
- package/fjs/media/type/module.f.mjs +74 -0
- package/fjs/media/type/proof.f.d.mts +5 -0
- package/fjs/media/type/proof.f.mjs +24 -1
- package/fjs/module.f.mjs +6 -0
- package/fjs/path/module.f.d.mts +60 -3
- package/fjs/path/module.f.mjs +138 -14
- package/fjs/path/proof.f.d.mts +3 -0
- package/fjs/path/proof.f.mjs +298 -2
- package/fjs/protocol/json_rpc/module.f.d.mts +65 -28
- package/fjs/protocol/json_rpc/module.f.mjs +49 -13
- package/fjs/protocol/json_rpc/proof.f.d.mts +4 -0
- package/fjs/protocol/json_rpc/proof.f.mjs +29 -0
- package/fjs/protocol/json_rpc/types.d.ts +12 -2
- package/fjs/protocol/mcp/module.f.d.mts +89 -54
- package/fjs/protocol/mcp/module.f.mjs +110 -56
- package/fjs/protocol/mcp/proof.f.d.mts +13 -0
- package/fjs/protocol/mcp/proof.f.mjs +107 -7
- package/fjs/protocol/mcp/stdio/module.f.mjs +3 -4
- package/fjs/protocol/mcp/stdio/proof.f.mjs +2 -2
- package/fjs/protocol/mcp/types.d.ts +14 -2
- package/fjs/text/utf8/module.f.mjs +27 -1
- package/fjs/text/utf8/proof.f.mjs +37 -0
- package/fjs/types/array/module.f.mjs +18 -11
- package/fjs/types/array/proof.f.mjs +27 -1
- package/fjs/types/bigfloat/module.f.d.mts +48 -9
- package/fjs/types/bigfloat/module.f.mjs +147 -59
- package/fjs/types/bigfloat/proof.f.d.mts +2 -0
- package/fjs/types/bigfloat/proof.f.mjs +136 -4
- package/fjs/types/bigfloat/types.d.ts +17 -0
- package/fjs/types/function/module.f.d.mts +11 -2
- package/fjs/types/function/module.f.mjs +20 -2
- package/fjs/types/function/proof.f.mjs +5 -1
- package/fjs/types/phantom/types.d.ts +21 -0
- package/fjs/types/result/module.f.d.mts +4 -4
- package/fjs/types/result/module.f.mjs +2 -2
- package/fjs/types/result/proof.f.d.mts +6 -2
- package/fjs/types/result/proof.f.mjs +17 -1
- package/fjs/types/rtti/common/module.f.d.mts +78 -5
- package/fjs/types/rtti/common/module.f.mjs +168 -3
- package/fjs/types/rtti/common/proof.f.d.mts +14 -0
- package/fjs/types/rtti/common/proof.f.mjs +61 -2
- package/fjs/types/rtti/common/types.d.ts +23 -1
- package/fjs/types/rtti/data/module.f.d.mts +57 -1
- package/fjs/types/rtti/data/module.f.mjs +241 -47
- package/fjs/types/rtti/data/proof.f.d.mts +10 -0
- package/fjs/types/rtti/data/proof.f.mjs +272 -39
- package/fjs/types/rtti/data/types.d.ts +14 -7
- package/fjs/types/rtti/host.proof.d.mts +33 -0
- package/fjs/types/rtti/host.proof.mjs +170 -0
- package/fjs/types/rtti/module.f.d.mts +51 -6
- package/fjs/types/rtti/module.f.mjs +50 -3
- package/fjs/types/rtti/parse/module.f.d.mts +31 -24
- package/fjs/types/rtti/parse/module.f.mjs +197 -59
- package/fjs/types/rtti/parse/proof.f.d.mts +37 -2
- package/fjs/types/rtti/parse/proof.f.mjs +171 -47
- package/fjs/types/rtti/proof.f.d.mts +6 -0
- package/fjs/types/rtti/proof.f.mjs +42 -1
- package/fjs/types/rtti/ts/module.f.d.mts +9 -3
- package/fjs/types/rtti/ts/module.f.mjs +68 -15
- package/fjs/types/rtti/ts/proof.f.d.mts +42 -2
- package/fjs/types/rtti/ts/proof.f.mjs +139 -6
- package/fjs/types/rtti/ts/types.d.ts +164 -16
- package/fjs/types/rtti/types.d.ts +31 -3
- package/fjs/types/rtti/validate/module.f.d.mts +120 -0
- package/fjs/types/rtti/validate/module.f.mjs +351 -0
- package/fjs/types/rtti/validate/proof.f.d.mts +154 -0
- package/fjs/types/rtti/validate/proof.f.mjs +781 -0
- package/fjs/types/ts/types.d.ts +4 -0
- package/fjs/web/module.f.d.mts +115 -0
- package/fjs/web/module.f.mjs +581 -0
- package/fjs/web/proof.f.d.mts +39 -0
- package/fjs/web/proof.f.mjs +413 -0
- package/fjs/web/types.d.ts +39 -0
- package/fjs/website/browser-prepare.d.mts +5 -0
- package/fjs/website/browser-prepare.mjs +115 -0
- package/fjs/website/browser-source.d.mts +37 -0
- package/fjs/website/browser-source.mjs +202 -0
- package/fjs/website/browser-source.proof.d.mts +31 -0
- package/fjs/website/browser-source.proof.mjs +162 -0
- package/fjs/website/browser.d.mts +5 -0
- package/fjs/website/browser.mjs +8 -0
- package/fjs/website/module.f.d.mts +3 -3
- package/fjs/website/module.f.mjs +62 -9
- package/fjs/website/proof.f.d.mts +4 -0
- package/fjs/website/proof.f.mjs +18 -3
- package/package.json +2 -3
|
@@ -1,14 +1,84 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @import { Type } from '../types.ts'
|
|
2
|
+
* @import { Or, Type } from '../types.ts'
|
|
3
3
|
* @import { Data } from '../data/types.ts'
|
|
4
|
+
* @import { Ts, TupleTs } from './types.ts'
|
|
5
|
+
* @import { Assert } from '../../../asserts/types.ts'
|
|
6
|
+
* @import { Equal } from '../../ts/types.ts'
|
|
4
7
|
*/
|
|
5
8
|
|
|
6
9
|
import { assertEq } from '../../../asserts/module.f.mjs'
|
|
7
10
|
import { toData, unitBit } from '../data/module.f.mjs'
|
|
8
|
-
import { boolean, number, string, bigint, unknown, array, record, or, option, never } from '../module.f.mjs'
|
|
11
|
+
import { boolean, number, string, bigint, unknown, array, open, record, or, option, rest, never } from '../module.f.mjs'
|
|
9
12
|
import { dataToTs, printer } from './module.f.mjs'
|
|
10
13
|
|
|
14
|
+
// ── `Ts<T>` over a tuple schema ─────────────────────────────────────────────
|
|
15
|
+
//
|
|
16
|
+
// Spelled as schema *types* rather than `typeof` a value: these are type-level
|
|
17
|
+
// facts, and a value existing only to be pointed at is an unused one.
|
|
18
|
+
//
|
|
19
|
+
// `TupleTs` splits off the trailing run of positions admitting `undefined` and
|
|
20
|
+
// renders it optional, which needs a known length. A schema array of non-fixed
|
|
21
|
+
// length — what `.map()` produces — has no trailing position to split off, so
|
|
22
|
+
// it keeps its element type instead, the homomorphic mapping's answer. Pinned
|
|
23
|
+
// because a split that falls back to the empty tuple silently renders such a
|
|
24
|
+
// schema `readonly []`, and nothing else here would have caught it.
|
|
25
|
+
/** @typedef {Assert<Equal<Ts<readonly (typeof number | typeof bigint)[]>, readonly (number | bigint)[]>>} _NonFixedLength */
|
|
26
|
+
|
|
27
|
+
// `option(t)` is `or(t, undefined)`; these are the schema types it produces.
|
|
28
|
+
/** @typedef {Or<readonly [typeof boolean, undefined]>} _OptionBoolean */
|
|
29
|
+
/** @typedef {Or<readonly [typeof string, undefined]>} _OptionString */
|
|
30
|
+
|
|
31
|
+
// A variadic tuple is the shape the `length` guard exists for, and the only
|
|
32
|
+
// one: its peel *succeeds*, binding the unknown-length prefix to `I`, so
|
|
33
|
+
// without the guard the reconstruction flattens it. The others below reach the
|
|
34
|
+
// fallback because their peel fails, and are held by that alone.
|
|
35
|
+
//
|
|
36
|
+
// Asserted as assignability rather than with `Equal<>`. `Equal<>` reports this
|
|
37
|
+
// shape as unchanged whether or not the guard is in place — it cannot see the
|
|
38
|
+
// difference — so an `Equal<>` pin here passes over the bug it is meant to
|
|
39
|
+
// catch. What the flattening actually costs is a string admitted in the number
|
|
40
|
+
// prefix, so that is what these state.
|
|
41
|
+
/** @typedef {readonly [...(typeof number)[], _OptionString]} _VariadicSchema */
|
|
42
|
+
/** @typedef {Assert<readonly [1, 'x', 2] extends Ts<_VariadicSchema> ? false : true>} _VariadicPrefixRejectsMixedPrefix */
|
|
43
|
+
/** @typedef {Assert<readonly [1, 2, 'x'] extends Ts<_VariadicSchema> ? true : false>} _VariadicPrefixAdmitsItsOwnShape */
|
|
44
|
+
|
|
45
|
+
// A rest element after a fixed prefix is the same shape from the other side,
|
|
46
|
+
// and is held for the same reason: `length` is `number`, so the mapping stands.
|
|
47
|
+
//
|
|
48
|
+
// This row and `_NonFixedLength` document intent rather than discriminate a
|
|
49
|
+
// mechanism. The guard and the fallback both answer `M` for these two shapes,
|
|
50
|
+
// so neither single mutation moves them — only removing both at once does.
|
|
51
|
+
// The rows that pin one mechanism each are `_VariadicPrefixRejectsMixedPrefix`
|
|
52
|
+
// (the guard), `_OptionalMember` (the fallback) and
|
|
53
|
+
// `_UnionKeepsBranchCorrelation` (the distribution).
|
|
54
|
+
/** @typedef {Assert<Equal<Ts<readonly [typeof number, ...(typeof string)[]]>, readonly [number, ...string[]]>>} _RestTuple */
|
|
55
|
+
|
|
56
|
+
// A schema whose own tuple type already marks a member optional is held by the
|
|
57
|
+
// *fallback* rather than the length guard: its length is `1 | 2`, not `number`,
|
|
58
|
+
// so it reaches the split, where the peel needs a required last element and
|
|
59
|
+
// finds none. An optional position is what this transform produces, so one the
|
|
60
|
+
// caller wrote is already in the target form and the mapping stands.
|
|
61
|
+
/** @typedef {Assert<Equal<Ts<readonly [typeof number, (typeof string)?]>, readonly [number, string?]>>} _OptionalMember */
|
|
62
|
+
|
|
63
|
+
// A union of tuple schemas is split per member, not once across the union.
|
|
64
|
+
// Splitting the union lets the two halves distribute independently and the
|
|
65
|
+
// spread then pairs every prefix with every suffix, so `[number, boolean]` —
|
|
66
|
+
// A's prefix with B's suffix — would pass. Assignability again: this is a
|
|
67
|
+
// statement about which values the union admits.
|
|
68
|
+
/** @typedef {readonly [typeof number, _OptionString]} _BranchA */
|
|
69
|
+
/** @typedef {readonly [typeof string, _OptionBoolean, _OptionNumber]} _BranchB */
|
|
70
|
+
/** @typedef {Or<readonly [typeof number, undefined]>} _OptionNumber */
|
|
71
|
+
/** @typedef {Assert<readonly [1, true] extends TupleTs<_BranchA | _BranchB> ? false : true>} _UnionKeepsBranchCorrelation */
|
|
72
|
+
/** @typedef {Assert<readonly [1, 'x'] extends TupleTs<_BranchA | _BranchB> ? true : false>} _UnionAdmitsItsOwnBranches */
|
|
73
|
+
|
|
74
|
+
/** @typedef {Assert<Equal<Ts<readonly [typeof number, typeof bigint, _OptionBoolean, _OptionString]>, readonly [number, bigint, (boolean | undefined)?, (string | undefined)?]>>} _OptionalTail */
|
|
75
|
+
|
|
76
|
+
// Only the *trailing* run: TypeScript forbids a required element after an
|
|
77
|
+
// optional one, so an interior position that admits `undefined` stays required.
|
|
78
|
+
/** @typedef {Assert<Equal<Ts<readonly [_OptionString, typeof number]>, readonly [string | undefined, number]>>} _InteriorStaysRequired */
|
|
79
|
+
|
|
11
80
|
const toTs = printer()
|
|
81
|
+
|
|
12
82
|
const toTsMut = printer(true)
|
|
13
83
|
|
|
14
84
|
/** @type {(rtti: Type, expected: string) => void} */
|
|
@@ -96,10 +166,24 @@ export const proof = {
|
|
|
96
166
|
negInf: () => eq(-Infinity, 'number'),
|
|
97
167
|
string: () => eq('hello', '"hello"'),
|
|
98
168
|
bigint: () => eq(7n, '7n'),
|
|
169
|
+
// a bare tuple is closed, so it prints exactly — the same rendering
|
|
170
|
+
// `Ts<>` gives it, which is what makes that cast sound
|
|
99
171
|
emptyTuple: () => eq([], 'readonly[]'),
|
|
100
172
|
tuple: () => eq([12, true], 'readonly[12,true]'),
|
|
101
|
-
//
|
|
102
|
-
|
|
173
|
+
// a position the array may end before prints optional, as the key it
|
|
174
|
+
// is the array counterpart of does
|
|
175
|
+
optionalTuplePosition: () => eq(
|
|
176
|
+
[number, option(string)],
|
|
177
|
+
'readonly[number,(undefined|string)?]',
|
|
178
|
+
),
|
|
179
|
+
allOptionalTuple: () => eq(
|
|
180
|
+
[option(number)],
|
|
181
|
+
'readonly[(undefined|number)?]',
|
|
182
|
+
),
|
|
183
|
+
// a declared `unknown` key is a key the container has, so it is not
|
|
184
|
+
// dropped the way an `open` struct's is
|
|
185
|
+
emptyStruct: () => eq({}, '{}'),
|
|
186
|
+
unknownProp: () => eq({ a: unknown }, '{readonly"a"?:unknown}'),
|
|
103
187
|
struct: () => eq(
|
|
104
188
|
{ a: number, b: string },
|
|
105
189
|
'{readonly"a":number,readonly"b":string}',
|
|
@@ -128,7 +212,45 @@ export const proof = {
|
|
|
128
212
|
thunks: () => eq(or(number, string), 'number|string'),
|
|
129
213
|
mixed: () => eq(or(42, string), '42|string'),
|
|
130
214
|
},
|
|
215
|
+
// The open and stated-rest forms. An object type is structurally open in
|
|
216
|
+
// TypeScript, so an `open` struct prints as wide as it can be printed;
|
|
217
|
+
// a tuple has a rest element, so this printer says exactly what the schema
|
|
218
|
+
// says — `Ts<>` renders the same tail, for the same reason.
|
|
219
|
+
open: {
|
|
220
|
+
// an unconstrained tuple, or struct, is the whole kind
|
|
221
|
+
emptyTuple: () => eq(open([]), 'readonly(unknown)[]'),
|
|
222
|
+
unconstrainedTuple: () => eq(open([unknown]), 'readonly(unknown)[]'),
|
|
223
|
+
tuple: () => eq(open([12, true]), 'readonly[12,true,...readonly(unknown)[]]'),
|
|
224
|
+
emptyStruct: () => eq(open({}), '{readonly[k in string]?:unknown}'),
|
|
225
|
+
struct: () => eq(open({ a: number }), '{readonly"a":number}'),
|
|
226
|
+
// an unconstrained key *is* dropped once the container is open
|
|
227
|
+
unknownProp: () => eq(open({ a: unknown }), '{readonly[k in string]?:unknown}'),
|
|
228
|
+
// a stated rest prints as the rest element / index signature it is.
|
|
229
|
+
// The tail admits `undefined` because a hole past the prefix is no
|
|
230
|
+
// member, so a reader skips it and the index reads `undefined`.
|
|
231
|
+
tupleRest: () => eq(rest([number], string), 'readonly[number,...readonly(undefined|string)[]]'),
|
|
232
|
+
structRest: () => eq(
|
|
233
|
+
rest({ a: number }, string),
|
|
234
|
+
'{readonly"a":number}&{readonly[k in string]?:number|string}',
|
|
235
|
+
),
|
|
236
|
+
// `unknown` already admits `undefined`, so the open tail is unchanged
|
|
237
|
+
openIsAnUnconstrainedRest: () => {
|
|
238
|
+
assertEq(toTs(rest([number], unknown)), toTs(open([number])))
|
|
239
|
+
assertEq(toTs(rest({ a: number }, unknown)), toTs(open({ a: number })))
|
|
240
|
+
},
|
|
241
|
+
// an empty rest is the bare form, recognized here through the data
|
|
242
|
+
// form — including the `[or()]` spelling, which `RestTs` cannot see
|
|
243
|
+
// and so renders with a (wider, still sound) tail
|
|
244
|
+
emptyRestIsTheBareForm: () => {
|
|
245
|
+
assertEq(toTs(rest([number], never)), toTs([number]))
|
|
246
|
+
assertEq(toTs(rest([number], [never])), toTs([number]))
|
|
247
|
+
},
|
|
248
|
+
mut: () => eqMut(rest([number], string), '[number,...(undefined|string)[]]'),
|
|
249
|
+
},
|
|
131
250
|
never: () => eq(never, 'never'),
|
|
251
|
+
// an array with no admissible element is the empty array, and nothing
|
|
252
|
+
// past a prefix is what prints as an exact-length tuple
|
|
253
|
+
arrayOfNever: () => eq(array(never), 'readonly[]'),
|
|
132
254
|
// union members follow the canonical kind order, `undefined` first
|
|
133
255
|
option: () => eq(option(number), 'undefined|number'),
|
|
134
256
|
normalization: {
|
|
@@ -198,9 +320,20 @@ export const proof = {
|
|
|
198
320
|
},
|
|
199
321
|
},
|
|
200
322
|
data: {
|
|
323
|
+
// The tail admits `undefined` on top of the rest — a hole past the
|
|
324
|
+
// prefix is no member, so a reader skips it and the index reads
|
|
325
|
+
// `undefined`. A rest that already admits it is printed as it is.
|
|
201
326
|
tupleWithRest: () => {
|
|
202
327
|
eqData([{}, { array: [{ prefix: [{ number: true }], rest: { string: true } }] }],
|
|
203
|
-
[[], 'readonly[number,...readonly(string)[]]'])
|
|
328
|
+
[[], 'readonly[number,...readonly(undefined|string)[]]'])
|
|
329
|
+
eqData([{}, { array: [{ prefix: [{ number: true }], rest: { unit: unitBit(undefined), string: true } }] }],
|
|
330
|
+
[[], 'readonly[number,...readonly(undefined|string)[]]'])
|
|
331
|
+
},
|
|
332
|
+
// an exact-length pattern is the one that prints without a rest
|
|
333
|
+
// element — what a bare, closed tuple converts to
|
|
334
|
+
exactLengthTuple: () => {
|
|
335
|
+
eqData([{}, { array: [{ prefix: [{ number: true }, { string: true }] }] }],
|
|
336
|
+
[[], 'readonly[number,string]'])
|
|
204
337
|
},
|
|
205
338
|
structWithRest: () => {
|
|
206
339
|
// the index signature must cover the declared keys too, so the
|
|
@@ -226,7 +359,7 @@ export const proof = {
|
|
|
226
359
|
array: () => eqMut(array(number), '(number)[]'),
|
|
227
360
|
nestedArray: () => eqMut(array(array(boolean)), '((boolean)[])[]'),
|
|
228
361
|
record: () => eqMut(record(string), '{[k in string]?:string}'),
|
|
229
|
-
tuple: () => eqMut([12, true], '[12,true]'),
|
|
362
|
+
tuple: () => eqMut(open([12, true]), '[12,true,...(unknown)[]]'),
|
|
230
363
|
struct: () => eqMut({ a: number, b: string }, '{"a":number,"b":string}'),
|
|
231
364
|
},
|
|
232
365
|
throw: {
|
|
@@ -9,7 +9,8 @@
|
|
|
9
9
|
*
|
|
10
10
|
* @module
|
|
11
11
|
*/
|
|
12
|
-
import type {
|
|
12
|
+
import type { And, Equal } from '../../ts/types.ts';
|
|
13
|
+
import type { Tag0, Tag1, Or, Struct, Tuple, Type, ConstObject } from '../types.ts';
|
|
13
14
|
import type { phantomKey } from '../../phantom/types.ts';
|
|
14
15
|
import type { StringMap } from '../../object/types.ts';
|
|
15
16
|
/**
|
|
@@ -52,28 +53,132 @@ export type RecordTs<T extends Type> = {
|
|
|
52
53
|
readonly [K in string]?: Ts<T>;
|
|
53
54
|
};
|
|
54
55
|
/**
|
|
55
|
-
* Maps a tuple schema to a readonly tuple of resolved types
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
*
|
|
56
|
+
* Maps a tuple schema to a readonly tuple of resolved types, with the
|
|
57
|
+
* **trailing** positions whose sets admit `undefined` rendered optional:
|
|
58
|
+
* `[number, bigint, option(boolean), option(string)]` becomes
|
|
59
|
+
* `readonly[number, bigint, (boolean|undefined)?, (string|undefined)?]`.
|
|
60
|
+
*
|
|
61
|
+
* That is the same rule {@link StructTs} applies per key — a member is
|
|
62
|
+
* required exactly when its set excludes `undefined` — so an array may stop
|
|
63
|
+
* at the last required position, which is what `../parse/module.f.mjs` and
|
|
64
|
+
* `../validate/module.f.mjs` accept. Only the trailing run: TypeScript
|
|
65
|
+
* forbids a required element after an optional one, so a position that admits
|
|
66
|
+
* `undefined` with a required one after it stays required with `undefined` in
|
|
67
|
+
* its type (see {@link _tupleInteriorOption}).
|
|
68
|
+
*
|
|
69
|
+
* **Deriving this generically took three specific moves**, each defeating an
|
|
70
|
+
* error that sank the obvious spellings — do not simplify it back:
|
|
71
|
+
*
|
|
72
|
+
* - `MappedTs` resolves `Ts<>` **once per position**, and the split then walks
|
|
73
|
+
* the mapped tuple rather than the schema. Testing `undefined extends
|
|
74
|
+
* Ts<Last>` during the walk evaluates `Ts<>` twice per position and raises
|
|
75
|
+
* TS2589 (excessively deep).
|
|
76
|
+
* - `Extract<…, readonly unknown[]>` is what makes a mapped type spreadable.
|
|
77
|
+
* Spreading it directly raises TS2574 ("a rest element type must be an array
|
|
78
|
+
* type") — TypeScript cannot prove a mapped type over a generic `keyof T` is
|
|
79
|
+
* array-shaped. An `& readonly unknown[]` intersection silences that too,
|
|
80
|
+
* but leaves the intersection in the rendered type, so `Equal<>` against a
|
|
81
|
+
* hand-written tuple fails; `Extract` resolves clean.
|
|
82
|
+
* - `extends infer M extends …` forces each intermediate to resolve before it
|
|
83
|
+
* is used in rest position. Without it the concrete instantiation works and
|
|
84
|
+
* the generic one does not.
|
|
85
|
+
*
|
|
86
|
+
* A recursive form that prepends onto an all-optional tail (rather than
|
|
87
|
+
* splitting) crashes the compiler outright — a Go stack overflow in tsgo
|
|
88
|
+
* 7.0.2, not a diagnostic.
|
|
89
|
+
*
|
|
90
|
+
* **The exact length is the model, not an approximation of it.** A bare tuple
|
|
91
|
+
* schema is *closed* — the positions it declares and no others (see "Structs
|
|
92
|
+
* and tuples are closed" in `../README.md`) — which is what a TypeScript tuple
|
|
93
|
+
* already means, so this rendering and the schema denote the same set and
|
|
94
|
+
* `validate`'s success cast is sound. A schema admitting more says so, and
|
|
95
|
+
* {@link RestTs} renders the tail it says it with.
|
|
67
96
|
*/
|
|
68
|
-
|
|
97
|
+
type MappedTs<T extends Tuple> = Extract<{
|
|
69
98
|
readonly [K in keyof T]: Ts<T[K]>;
|
|
70
|
-
}
|
|
99
|
+
}, readonly unknown[]>;
|
|
100
|
+
type RequiredPart<M extends readonly unknown[]> = M extends readonly [...infer I extends readonly unknown[], infer L] ? undefined extends L ? RequiredPart<I> : M : M;
|
|
101
|
+
type OmittablePart<M extends readonly unknown[], Acc extends readonly unknown[] = readonly []> = M extends readonly [...infer I extends readonly unknown[], infer L] ? undefined extends L ? OmittablePart<I, readonly [L, ...Acc]> : Acc : Acc;
|
|
102
|
+
type AsOptional<O extends readonly unknown[]> = Extract<{
|
|
103
|
+
readonly [K in keyof O]+?: O[K];
|
|
104
|
+
}, readonly unknown[]>;
|
|
105
|
+
export type TupleTs<T extends Tuple> = MappedTs<T> extends infer M extends readonly unknown[] ? SplitTs<M> : never;
|
|
106
|
+
/**
|
|
107
|
+
* Splits one mapped tuple. `M` is naked in the first conditional on purpose:
|
|
108
|
+
* that distributes over a union of tuples, so each member is split and rebuilt
|
|
109
|
+
* whole. Splitting the union instead lets `RequiredPart` and `OmittablePart`
|
|
110
|
+
* distribute separately, and the spread then recombines every prefix with
|
|
111
|
+
* every suffix — a union of `[number, option(string)]` and
|
|
112
|
+
* `[string, option(boolean), option(number)]` would admit `[number, boolean]`.
|
|
113
|
+
*
|
|
114
|
+
* Splitting a trailing run off also needs a *fixed* length. A schema array of
|
|
115
|
+
* non-fixed length (what `.map()` produces) and a variadic tuple
|
|
116
|
+
* (`[...(typeof number)[], option(string)]`) both have `length: number` and no
|
|
117
|
+
* last position to peel, so they keep the mapping as it is — splitting them
|
|
118
|
+
* would drop the element type and the prefix's shape respectively, and widen
|
|
119
|
+
* what `Ts<T>` admits.
|
|
120
|
+
*/
|
|
121
|
+
type SplitTs<M extends readonly unknown[]> = M extends readonly unknown[] ? number extends M['length'] ? M : RequiredPart<M> extends infer R extends readonly unknown[] ? OmittablePart<M> extends infer O extends readonly unknown[] ? readonly [...R, ...AsOptional<O>] : never : never : never;
|
|
71
122
|
type OptionalFields<T extends Struct> = {
|
|
72
123
|
readonly [K in keyof T as undefined extends Ts<T[K]> ? K : never]?: Ts<T[K]>;
|
|
73
124
|
};
|
|
74
125
|
type RequiredFields<T extends Struct> = {
|
|
75
126
|
readonly [K in keyof T as undefined extends Ts<T[K]> ? never : K]: Ts<T[K]>;
|
|
76
127
|
};
|
|
128
|
+
/**
|
|
129
|
+
* Maps a container with a stated rest to the resolved type of its declared
|
|
130
|
+
* members, **plus the tail the rest states**.
|
|
131
|
+
*
|
|
132
|
+
* The struct kind needs no tail: an object type is width-open in TypeScript,
|
|
133
|
+
* so `ConstTs<C>` already admits the undeclared keys — as wide as TypeScript
|
|
134
|
+
* can render either way.
|
|
135
|
+
*
|
|
136
|
+
* The tuple kind does, and a rename of the old exact-only rendering would have
|
|
137
|
+
* relocated the unsound cast rather than removing it: `Ts<typeof open([42])>`
|
|
138
|
+
* would be the exact `readonly[42]` while `validate(open([42]))([42, 'x'])`
|
|
139
|
+
* accepts and hands back two elements.
|
|
140
|
+
*
|
|
141
|
+
* **The tail admits `undefined`.** Both readers check an undeclared member as
|
|
142
|
+
* a member, and a hole past the prefix is no member, so
|
|
143
|
+
* `validate(rest([42], string))([42, , ])` is `ok` and index 1 reads
|
|
144
|
+
* `undefined`. `...(Ts<R> | undefined)[]` is what "a rest never sees an absent
|
|
145
|
+
* member" says on the type side. The common case pays nothing: `open(c)`'s
|
|
146
|
+
* rest is `unknown`, which already admits `undefined`.
|
|
147
|
+
*
|
|
148
|
+
* **An empty rest renders no tail**, since `rest(c, or())` is the bare `c` —
|
|
149
|
+
* one set, so one rendering, and `readonly[42, ...undefined[]]` would admit
|
|
150
|
+
* the `[42, undefined]` both readers reject. What counts as empty is
|
|
151
|
+
* `emptyRest`'s question in `../data/module.f.mjs`, and that is a `toData`
|
|
152
|
+
* conversion plus `subset` both ways, which `types.ts` cannot invoke. Nor is
|
|
153
|
+
* `Ts<R> extends never` a substitute — `Ts<readonly[Or<readonly[]>]>` is
|
|
154
|
+
* `readonly[never]`, whose `length` is `1`. So this recognizes the one
|
|
155
|
+
* directly spellable empty rest, `or()`, and **keeps the tail whenever it
|
|
156
|
+
* cannot tell**. The conservatism has a direction: a kept tail is wider than
|
|
157
|
+
* the schema but sound — every accepted value still has the rendered type,
|
|
158
|
+
* which is the only direction a success cast needs — while a wrongly dropped
|
|
159
|
+
* one is the unsound cast. Where that leaves this and the runtime printer
|
|
160
|
+
* disagreeing (`rest([42], readonly[Or<readonly[]>])`, which the data form
|
|
161
|
+
* recognizes as empty and this does not) the printer is the narrower of the
|
|
162
|
+
* two; see {@link _restEmptyIndirect}.
|
|
163
|
+
*
|
|
164
|
+
* The other place the two differ is a rest with **no prefix**, which is the
|
|
165
|
+
* uniform array: `rest([], string)` and `array(string)` are one set, so the
|
|
166
|
+
* printer — which goes through the data form and sees one node — prints
|
|
167
|
+
* `ArrayTs`'s `readonly(string)[]` for both, while this renders the tail and
|
|
168
|
+
* so answers `readonly(string|undefined)[]` for the first. The tail is the
|
|
169
|
+
* sound one of the two, a hole being no member on either spelling; `ArrayTs`
|
|
170
|
+
* carries that gap already and closing it is its own change, not this one's.
|
|
171
|
+
* See {@link _restNoPrefix}.
|
|
172
|
+
*/
|
|
173
|
+
export type RestTs<C extends ConstObject, R extends Type> = C extends Tuple ? TupleRestTs<C, R> : ConstTs<C>;
|
|
174
|
+
/**
|
|
175
|
+
* `[R] extends [...]` rather than a naked `R`: a naked one distributes, and a
|
|
176
|
+
* union rest would then render a union of tuples rather than a tuple whose
|
|
177
|
+
* tail is a union.
|
|
178
|
+
*/
|
|
179
|
+
type TupleRestTs<C extends Tuple, R extends Type> = [
|
|
180
|
+
R
|
|
181
|
+
] extends [Or<readonly []>] ? TupleTs<C> : TupleTs<C> extends infer M extends readonly unknown[] ? readonly [...M, ...ReadonlyArray<Ts<R> | undefined>] : never;
|
|
77
182
|
/** Maps a struct schema to a readonly object of resolved types, with optional fields for schemas that include `undefined`. */
|
|
78
183
|
export type StructTs<T extends Struct> = (keyof OptionalFields<T> extends never ? unknown : OptionalFields<T>) & (keyof RequiredFields<T> extends never ? unknown : RequiredFields<T>);
|
|
79
184
|
/**
|
|
@@ -96,6 +201,33 @@ export type StructTs<T extends Struct> = (keyof OptionalFields<T> extends never
|
|
|
96
201
|
* // Ts<typeof my> → MyType
|
|
97
202
|
* ```
|
|
98
203
|
*
|
|
204
|
+
* `MyType` here is an unchecked annotation — nothing derives it, so a typo
|
|
205
|
+
* (the wrong type, or one that has drifted from `myConst`) is trusted
|
|
206
|
+
* silently. Pin it down with two asserts, one against the un-annotated
|
|
207
|
+
* `myThunk` (forces the real structural walk) and one against the
|
|
208
|
+
* phantom-wrapped `my` (catches the two drifting apart):
|
|
209
|
+
*
|
|
210
|
+
* ```ts
|
|
211
|
+
* type _Check0 = Assert<Check<MyType, typeof myThunk>>
|
|
212
|
+
* type _Check1 = Assert<Check<MyType, typeof my>>
|
|
213
|
+
* ```
|
|
214
|
+
*
|
|
215
|
+
* {@link Check3} is the same pair written once:
|
|
216
|
+
*
|
|
217
|
+
* ```ts
|
|
218
|
+
* type _Check = Assert<Check3<MyType, typeof myThunk, typeof my>>
|
|
219
|
+
* ```
|
|
220
|
+
*
|
|
221
|
+
* See `fjs/edag/module.f.mjs` (`_exp`/`exp`) for this in practice. Note also
|
|
222
|
+
* that the phantom branch below does `Exclude<O, undefined>`, so a `MyType`
|
|
223
|
+
* that includes bare `undefined` at its top level will never satisfy
|
|
224
|
+
* `_Check1`. That is a constraint on the wrapped type, not on how wide it
|
|
225
|
+
* is: a union is fine when no member contributes a top-level `undefined` —
|
|
226
|
+
* `fjs/edag` wraps `Exp`, the whole node union, because its `undefined` is
|
|
227
|
+
* the tagged `['undefined']` rather than the bare value. Wrap one type per
|
|
228
|
+
* recursive cycle, and if the natural one does carry a top-level
|
|
229
|
+
* `undefined`, wrap a narrower node type inside the cycle instead.
|
|
230
|
+
*
|
|
99
231
|
* @example
|
|
100
232
|
* ```ts
|
|
101
233
|
* type A = Ts<typeof string> // string
|
|
@@ -108,5 +240,21 @@ export type Ts<T extends Type> = unknown extends T ? Unknown : T extends {
|
|
|
108
240
|
readonly [phantomKey]?: infer O;
|
|
109
241
|
} ? Exclude<O, undefined> : T extends () => infer I ? (I extends readonly ['const', infer C] ? ConstTs<C> : I extends readonly ['boolean'] ? boolean : I extends readonly ['number'] ? number : I extends readonly ['string'] ? string : I extends readonly ['bigint'] ? bigint : I extends readonly ['unknown'] ? Unknown : I extends readonly ['array', infer E extends Type] ? readonly Ts<E>[] : I extends readonly ['record', infer E extends Type] ? {
|
|
110
242
|
readonly [k in string]?: Ts<E>;
|
|
111
|
-
} : I extends readonly ['or', ...infer A extends readonly Type[]] ? Ts<A[number]> : never) : ConstTs<T>;
|
|
243
|
+
} : I extends readonly ['or', ...infer A extends readonly Type[]] ? Ts<A[number]> : I extends readonly ['rest', infer C extends ConstObject, infer R extends Type] ? RestTs<C, R> : never) : ConstTs<T>;
|
|
244
|
+
/**
|
|
245
|
+
* Pins a hand-written TypeScript type `A` against the type an rtti schema `B`
|
|
246
|
+
* actually derives to — `Assert<Check<A, B>>` reads as "`A` is `Ts<B>`".
|
|
247
|
+
* This is the assertion the {@link Ts} doc above uses for the two-Phantom-check
|
|
248
|
+
* pattern, and the one every hand-written recursive type in this codebase (a
|
|
249
|
+
* `Tree`, a `LockMap`, …) pins against its rtti schema with.
|
|
250
|
+
*/
|
|
251
|
+
export type Check<A, B extends Type> = Equal<A, Ts<B>>;
|
|
252
|
+
/**
|
|
253
|
+
* The two-assert `Phantom` pattern from the {@link Ts} doc, in one
|
|
254
|
+
* assert: `T` is both `Ts<R0>` (the raw thunk, forcing the structural walk)
|
|
255
|
+
* and `Ts<R1>` (the phantom-wrapped export). Checking only the wrapped
|
|
256
|
+
* export is a tautology — `Ts<>` short-circuits to the annotation — so the
|
|
257
|
+
* `R0` half is what gives this teeth.
|
|
258
|
+
*/
|
|
259
|
+
export type Check3<T, R0 extends Type, R1 extends Type> = And<Equal<T, Ts<R0>>, Equal<T, Ts<R1>>>;
|
|
112
260
|
export {};
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* ```
|
|
12
12
|
* Type = Const | Thunk
|
|
13
13
|
* Thunk = () => Info
|
|
14
|
-
* Info = ['const', Const] | Info0<Tag0> | Info1<Tag1, Type>
|
|
14
|
+
* Info = ['const', Const] | Info0<Tag0> | Info1<Tag1, Type> | InfoRest<ConstObject, Type>
|
|
15
15
|
* ```
|
|
16
16
|
*
|
|
17
17
|
* ## Nullary schemas (no type parameter)
|
|
@@ -32,6 +32,13 @@
|
|
|
32
32
|
* Inside a recursive `Thunk`-based definition, wrap consts with `() => ['const', c]`
|
|
33
33
|
* to keep the schema uniform.
|
|
34
34
|
*
|
|
35
|
+
* ## Undeclared members
|
|
36
|
+
*
|
|
37
|
+
* A `Struct` or a `Tuple` on its own is **closed** — it admits the members it
|
|
38
|
+
* declares and no others. `rest(c, r)` states the set every undeclared member
|
|
39
|
+
* belongs to, and `open(c)` is `rest(c, unknown)`: any undeclared member at
|
|
40
|
+
* all. See "Structs and tuples are closed" in `./README.md`.
|
|
41
|
+
*
|
|
35
42
|
* ## Converting to TypeScript types
|
|
36
43
|
*
|
|
37
44
|
* See `./ts/module.f.ts` for `Ts<T>` and the `*Ts` transformer types.
|
|
@@ -55,7 +62,7 @@ export type Tag0 = typeof tag0List[number];
|
|
|
55
62
|
/** Info tuple for a nullary tag: `readonly[tag]`. */
|
|
56
63
|
export type Info0<T extends Tag0> = T extends Tag0 ? readonly [T] : never;
|
|
57
64
|
/** Any schema: a `Const` used directly, or a `Thunk` for tag-based/recursive schemas. */
|
|
58
|
-
export type Type = (() => (readonly ['const', Const] | readonly ['bigint'] | readonly ['boolean'] | readonly ['number'] | readonly ['string'] | readonly ['unknown'] | readonly ['array', Type] | readonly ['record', Type] | readonly ['or', ...readonly Type[]])) | Const;
|
|
65
|
+
export type Type = (() => (readonly ['const', Const] | readonly ['bigint'] | readonly ['boolean'] | readonly ['number'] | readonly ['string'] | readonly ['unknown'] | readonly ['array', Type] | readonly ['record', Type] | readonly ['or', ...readonly Type[]] | readonly ['rest', ConstObject, Type])) | Const;
|
|
59
66
|
/** The type of a nullary thunk for `Tag0`. */
|
|
60
67
|
export type _Type0<T extends Tag0> = () => Info0<T>;
|
|
61
68
|
/** Schema type for `boolean`. */
|
|
@@ -74,10 +81,31 @@ export type Tag1 = 'array' | 'record';
|
|
|
74
81
|
export type Info1<K extends Tag1, T extends Type> = K extends Tag1 ? readonly [K, T] : never;
|
|
75
82
|
/** The type of a unary thunk for `Tag1` with inner type `T`. */
|
|
76
83
|
export type Type1<K extends Tag1, T extends Type> = () => Info1<K, T>;
|
|
77
|
-
export type _MakeType1<K extends Tag1> = <T extends Type>(t: T) => Type1<K, T>;
|
|
84
|
+
export type _MakeType1<K extends Tag1> = <const T extends Type>(t: T) => Type1<K, T>;
|
|
78
85
|
/** Schema type for a readonly array with element type `T`. */
|
|
79
86
|
export type Array<T extends Type> = Type1<'array', T>;
|
|
80
87
|
/** Schema type for a record (index signature) with value type `T`. */
|
|
81
88
|
export type Record<T extends Type> = Type1<'record', T>;
|
|
82
89
|
/** Schema type for a union of types `T`. */
|
|
83
90
|
export type Or<T extends readonly Type[]> = () => readonly ['or', ...T];
|
|
91
|
+
/**
|
|
92
|
+
* Info tuple for a container with a stated rest: `readonly[tag, container,
|
|
93
|
+
* rest]`, where `rest` is the set every member the container does not declare
|
|
94
|
+
* belongs to. A bare `C` is the same set with a rest of `never`, and needs no
|
|
95
|
+
* spelling of its own.
|
|
96
|
+
*/
|
|
97
|
+
export type InfoRest<C extends ConstObject, R extends Type> = readonly ['rest', C, R];
|
|
98
|
+
/**
|
|
99
|
+
* Schema type for a container `C` whose undeclared members are `R`. Both
|
|
100
|
+
* parameters are required: `never` — the bare `C` — is the identity a schema
|
|
101
|
+
* writes by leaving the wrapper off, so there is no default to state.
|
|
102
|
+
*/
|
|
103
|
+
export type Rest<C extends ConstObject, R extends Type> = () => InfoRest<C, R>;
|
|
104
|
+
export type _MakeRest = <const C extends ConstObject, const R extends Type>(c: C, r: R) => Rest<C, R>;
|
|
105
|
+
/**
|
|
106
|
+
* `open`'s signature. It is not `_MakeRest` partially applied: the `const`
|
|
107
|
+
* modifier is what keeps `open([42])` a literal tuple rather than widening it
|
|
108
|
+
* to `Type[]`, so the modifier has to be restated here. `../proof.f.mjs` pins
|
|
109
|
+
* that, as it does for every other `const`-taking constructor.
|
|
110
|
+
*/
|
|
111
|
+
export type _MakeOpen = <const C extends ConstObject>(c: C) => Rest<C, Unknown>;
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runtime validation of unknown values against RTTI schemas — the verbatim
|
|
3
|
+
* reader.
|
|
4
|
+
*
|
|
5
|
+
* The main entry point is `validate(rtti)`, which takes a schema `Type` and
|
|
6
|
+
* returns a `Validate<T>` function. When called with an unknown value, it
|
|
7
|
+
* returns a `Result` that is either `['ok', value]` — **the value it was
|
|
8
|
+
* given** — or `['error', { path, message }]`.
|
|
9
|
+
*
|
|
10
|
+
* ## What distinguishes it from `parse`
|
|
11
|
+
*
|
|
12
|
+
* `../parse/module.f.mjs` answers "read this value as `T`" and builds a fresh
|
|
13
|
+
* value holding exactly what the schema declares. `validate` answers "is this
|
|
14
|
+
* value a `T`?" about the value itself, so on success the caller keeps the
|
|
15
|
+
* object it passed in — same reference, same members, same serialization:
|
|
16
|
+
*
|
|
17
|
+
* ```js
|
|
18
|
+
* const schema = { a: number, b: option(string) }
|
|
19
|
+
* parse(schema)({ a: 1, extra: 'x' }) // ['ok', { a: 1, b: undefined }]
|
|
20
|
+
* validate(schema)({ a: 1, extra: 'x' }) // ['ok', { a: 1, extra: 'x' }]
|
|
21
|
+
* ```
|
|
22
|
+
*
|
|
23
|
+
* The two agree on **acceptance**: every value one accepts the other accepts,
|
|
24
|
+
* with the same error `path` and `message`. They differ only in what a success
|
|
25
|
+
* carries. `./proof.f.mjs` pins that agreement as a table rather than leaving
|
|
26
|
+
* it to convention. Which reader a caller wants, and why both exist, is in
|
|
27
|
+
* "The two schema-form readers" in `../README.md`.
|
|
28
|
+
*
|
|
29
|
+
* ## Structs and tuples are closed
|
|
30
|
+
*
|
|
31
|
+
* Closedness is the shared rule, not a `parse` detail — see "Structs and
|
|
32
|
+
* tuples are closed" in `../README.md`. A bare `Struct` or `Tuple` admits the
|
|
33
|
+
* members it declares and no others, so an undeclared key or an index past the
|
|
34
|
+
* prefix rejects the value. A tuple answers by **length** as well as by
|
|
35
|
+
* member, because a hole past the prefix is no member and would slip through
|
|
36
|
+
* the member check alone.
|
|
37
|
+
*
|
|
38
|
+
* Closedness is about *undeclared* members and leaves the required/optional
|
|
39
|
+
* rule alone: an absent member reads as `undefined`, so a member is required
|
|
40
|
+
* exactly when its set excludes `undefined`, and a schema whose trailing
|
|
41
|
+
* position admits it still accepts a shorter array. A tuple schema declares by
|
|
42
|
+
* length, so a hole in the *schema* is a position whose schema is `undefined`
|
|
43
|
+
* — see "A hole is a declared position" in `../README.md`.
|
|
44
|
+
*
|
|
45
|
+
* The length check is the model rather than an inference from `Ts<>`. #1622
|
|
46
|
+
* added one by reading `Ts<readonly[42]>`'s exact tuple as the value model
|
|
47
|
+
* while the model said open, and it was reverted for that reason; what has
|
|
48
|
+
* changed since is the model, not the reading.
|
|
49
|
+
*
|
|
50
|
+
* ## Stated rests
|
|
51
|
+
*
|
|
52
|
+
* `rest(c, r)` admits the declared members plus any number of members
|
|
53
|
+
* belonging to `r`, and `open(c)` — `rest(c, unknown)` — admits anything else
|
|
54
|
+
* besides. This widens what is accepted and changes nothing else: a success
|
|
55
|
+
* still carries the very value it was given, undeclared members included.
|
|
56
|
+
*
|
|
57
|
+
* ## Dispatch strategy
|
|
58
|
+
*
|
|
59
|
+
* Schema recognition is delegated to `visit` in `../common/module.f.mjs`,
|
|
60
|
+
* which routes each `Type` variant to a handler in the `Visitor` record
|
|
61
|
+
* defined below; nothing here walks the `Type` ADT itself. The container
|
|
62
|
+
* handlers drive `eachEntry` in its no-accumulator mode — the mode its JSDoc
|
|
63
|
+
* describes for "a caller whose whole question is 'did every entry
|
|
64
|
+
* succeed?'" — so a validation allocates nothing per entry. The data form's
|
|
65
|
+
* `validate` (`../data/module.f.mjs`) is the same shape over `Data`.
|
|
66
|
+
*
|
|
67
|
+
* ## Recursion safety
|
|
68
|
+
*
|
|
69
|
+
* The inner validator of an `array`, a `record` or a `rest` is instantiated
|
|
70
|
+
* lazily — only after confirming there is a member for it to read. This
|
|
71
|
+
* prevents infinite recursion when validating recursive schemas like
|
|
72
|
+
* `const list = () => ['array', list]`.
|
|
73
|
+
*
|
|
74
|
+
* See `./types.ts` for the `Path`/`Result`/`Validate`/`ValidationError`
|
|
75
|
+
* type-level API.
|
|
76
|
+
*
|
|
77
|
+
* @module
|
|
78
|
+
*
|
|
79
|
+
* @import { Unknown } from '../ts/types.ts'
|
|
80
|
+
* @import { ConstObject, Info1, Tag1, Type } from '../types.ts'
|
|
81
|
+
* @import { Container, Fits, IsContainer, SchemaEntries, Validate, ValidateE, Visitor } from '../common/types.ts'
|
|
82
|
+
* @import { StringMap } from '../../object/types.ts'
|
|
83
|
+
*/
|
|
84
|
+
import type { Type } from '../types.ts';
|
|
85
|
+
import type { Validate } from '../common/types.ts';
|
|
86
|
+
/**
|
|
87
|
+
* Creates a validator function for the given RTTI schema: a `Thunk` for
|
|
88
|
+
* tag-based schemas, or a `Const` (primitive literal, tuple, or struct) for
|
|
89
|
+
* exact-value schemas.
|
|
90
|
+
*
|
|
91
|
+
* The returned function takes an unknown value and returns either
|
|
92
|
+
* `['ok', value]` — the very value it was given, not a reconstruction — or
|
|
93
|
+
* `['error', { path, message }]` describing the failure location.
|
|
94
|
+
*
|
|
95
|
+
* Use it when the question is "is this value of this shape?" and the value has
|
|
96
|
+
* to survive the question intact. Use `../parse/module.f.mjs` when the answer
|
|
97
|
+
* wanted is a value built to the schema.
|
|
98
|
+
*
|
|
99
|
+
* @example
|
|
100
|
+
* ```js
|
|
101
|
+
* const v = validate(array(number))
|
|
102
|
+
* const input = [1, 2, 3]
|
|
103
|
+
* v(input) // ['ok', input] — the same array, not a copy
|
|
104
|
+
* v([1, 'two']) // ['error', { path: ['1'], message: 'unexpected value' }]
|
|
105
|
+
*
|
|
106
|
+
* // closed, so a member the schema does not name rejects the value
|
|
107
|
+
* validate([number, number])([1, 2, 3]) // ['error', …]
|
|
108
|
+
* validate({ a: number })({ a: 1, b: 2 }) // ['error', …]
|
|
109
|
+
*
|
|
110
|
+
* // an absent optional member stays absent
|
|
111
|
+
* validate({ a: number, b: option(string) })({ a: 1 }) // ['ok', { a: 1 }]
|
|
112
|
+
*
|
|
113
|
+
* // a stated rest says what the undeclared members may be; `open` says anything
|
|
114
|
+
* validate(rest({ a: number }, number))({ a: 1, b: 2 }) // ['ok', { a: 1, b: 2 }]
|
|
115
|
+
* validate(open({ a: number }))({ a: 1, b: 'x' }) // ['ok', { a: 1, b: 'x' }]
|
|
116
|
+
* ```
|
|
117
|
+
*
|
|
118
|
+
* @type {<const T extends Type>(rtti: T) => Validate<T>}
|
|
119
|
+
*/
|
|
120
|
+
export declare const validate: <const T extends Type>(rtti: T) => Validate<T>;
|