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
|
@@ -0,0 +1,351 @@
|
|
|
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
|
+
|
|
85
|
+
import { ok } from '../../result/module.f.mjs'
|
|
86
|
+
import {
|
|
87
|
+
constPrimitiveValidate,
|
|
88
|
+
eachEntry,
|
|
89
|
+
isArray,
|
|
90
|
+
isObject,
|
|
91
|
+
orVisit,
|
|
92
|
+
primitive0Validate,
|
|
93
|
+
structSchemaEntries,
|
|
94
|
+
tupleSchemaEntries,
|
|
95
|
+
undeclaredMembers,
|
|
96
|
+
verror,
|
|
97
|
+
visit,
|
|
98
|
+
} from '../common/module.f.mjs'
|
|
99
|
+
import { emptyRest } from '../data/module.f.mjs'
|
|
100
|
+
|
|
101
|
+
/** `validate` has nothing to collect from a successful entry — only pass/fail matters. */
|
|
102
|
+
const noAccumulate = () => undefined
|
|
103
|
+
|
|
104
|
+
/** A uniform container declares no member by name, so every one is undeclared. */
|
|
105
|
+
/** @type {readonly string[]} */
|
|
106
|
+
const noDeclared = []
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Builds a validator for `array` or `record` schemas.
|
|
110
|
+
* The inner item validator is instantiated lazily (only when the container is
|
|
111
|
+
* non-empty) to avoid infinite recursion with recursive schemas.
|
|
112
|
+
*
|
|
113
|
+
* The members are `undeclaredMembers`', not `Object.entries`': `array(t)` is
|
|
114
|
+
* `rest([], t)`, so the two have to walk a value the same way — an own-entry
|
|
115
|
+
* walk here skipped an index the prototype supplies while the data form's
|
|
116
|
+
* reader found it, which broke the acceptance agreement the three readers are
|
|
117
|
+
* pinned on.
|
|
118
|
+
*
|
|
119
|
+
* `fits` bounds the array kind's length when its element set admits nothing,
|
|
120
|
+
* which is what the data form says by normalizing such a `rest` away: an
|
|
121
|
+
* `array(or())` is the *empty* array and not "any number of holes". It is
|
|
122
|
+
* consulted only where it can change the answer — no member present, and the
|
|
123
|
+
* value reaching further than that — so an ordinary array never asks.
|
|
124
|
+
*/
|
|
125
|
+
const containerValidate =
|
|
126
|
+
/**
|
|
127
|
+
* @template {Tag1} K
|
|
128
|
+
* @param {IsContainer<Container<K>>} isContainer
|
|
129
|
+
* @param {(item: Type) => Fits<Container<K>>} restFits
|
|
130
|
+
* @returns {<I extends Type>(item: I) => Validate<Info1<K, I>>}
|
|
131
|
+
*/
|
|
132
|
+
(isContainer, restFits) =>
|
|
133
|
+
item => {
|
|
134
|
+
// Depends on the schema alone, so it is built once per schema rather
|
|
135
|
+
// than once per validated value.
|
|
136
|
+
const fits = restFits(item)
|
|
137
|
+
return value => {
|
|
138
|
+
if (!isContainer(value)) {
|
|
139
|
+
return verror('unexpected value')
|
|
140
|
+
}
|
|
141
|
+
const e = undeclaredMembers(noDeclared, value)
|
|
142
|
+
if (e.length === 0) {
|
|
143
|
+
return fits(value, 0)
|
|
144
|
+
? /** @type {any} */ (ok(value))
|
|
145
|
+
: verror('unexpected value')
|
|
146
|
+
}
|
|
147
|
+
// Note: we shouldn't instantiate `itemValidate` until we make sure `entries` is not empty.
|
|
148
|
+
// Otherwise, we can get infinite recursion on empty arrays and objects
|
|
149
|
+
const itemValidate = validate(item)
|
|
150
|
+
const r = eachEntry(e, (_k, v) => itemValidate(v), undefined, noAccumulate)
|
|
151
|
+
// `value` is Container<K>, but Ts<Info1<K,I>> = readonly Ts<I>[] | Record<string,Ts<I>>.
|
|
152
|
+
// TypeScript can't narrow the container's element types through the validation loop.
|
|
153
|
+
return r[0] === 'error' ? r : /** @type {any} */ (ok(value))
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const arrayValidate = containerValidate(
|
|
158
|
+
isArray,
|
|
159
|
+
// The cast is the price of one factory over two kinds: `Container<K>` is
|
|
160
|
+
// the union until `K` is bound, and only the array arm has a `length`.
|
|
161
|
+
item => (value, declared) =>
|
|
162
|
+
/** @type {ReadonlyArray<Unknown>} */ (value).length <= declared || !emptyRest([], item),
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
const recordValidate = containerValidate(isObject, () => () => true)
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Builds a validator for `Tuple` or `Struct` const schemas — **closed**: the
|
|
169
|
+
* members the schema declares and no others. It reads each declared member,
|
|
170
|
+
* then answers for every member of the value the schema does not name.
|
|
171
|
+
*
|
|
172
|
+
* `fits` is the one thing the two kinds do not share. An undeclared member is
|
|
173
|
+
* a member on both, but an array is also *as long as it is*: a hole past the
|
|
174
|
+
* prefix is no member and would slip through the member check alone, so the
|
|
175
|
+
* array kind answers with its length as well.
|
|
176
|
+
*/
|
|
177
|
+
const constContainerValidate =
|
|
178
|
+
/**
|
|
179
|
+
* @template {ReadonlyArray<Unknown> | StringMap<Unknown>} C
|
|
180
|
+
* @template {ConstObject} S
|
|
181
|
+
* @param {IsContainer<C>} isContainer
|
|
182
|
+
* @param {SchemaEntries<S>} schemaEntries
|
|
183
|
+
* @param {(value: C, k: string) => Unknown} getItem
|
|
184
|
+
* @param {Fits<C>} fits
|
|
185
|
+
* @returns {<T extends S>(rtti: T) => Validate<T>}
|
|
186
|
+
*/
|
|
187
|
+
(isContainer, schemaEntries, getItem, fits) =>
|
|
188
|
+
rtti => {
|
|
189
|
+
// Depend on `rtti` alone, so they are computed once per schema rather
|
|
190
|
+
// than once per validated value.
|
|
191
|
+
const rttiEntries = schemaEntries(rtti)
|
|
192
|
+
const declared = rttiEntries.map(([k]) => k)
|
|
193
|
+
return value => {
|
|
194
|
+
if (!isContainer(value)) {
|
|
195
|
+
return verror('unexpected value')
|
|
196
|
+
}
|
|
197
|
+
const r = eachEntry(
|
|
198
|
+
rttiEntries,
|
|
199
|
+
(k, v) => /** @type {any} */ (validate(v))(getItem(value, k)),
|
|
200
|
+
undefined,
|
|
201
|
+
noAccumulate,
|
|
202
|
+
)
|
|
203
|
+
if (r[0] === 'error') { return r }
|
|
204
|
+
// `value` is C (Unknown container), but Ts<T> for T extends Tuple|Struct is not
|
|
205
|
+
// structurally equivalent to C — TypeScript can't narrow element types through the loop.
|
|
206
|
+
return undeclaredMembers(declared, value).length === 0 && fits(value, declared.length)
|
|
207
|
+
? /** @type {any} */ (ok(value))
|
|
208
|
+
: verror('unexpected value')
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const tupleValidate = constContainerValidate(
|
|
213
|
+
isArray,
|
|
214
|
+
tupleSchemaEntries,
|
|
215
|
+
(value, k) => value[Number(k)],
|
|
216
|
+
(value, declared) => value.length <= declared,
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
const structValidate = constContainerValidate(
|
|
220
|
+
isObject,
|
|
221
|
+
structSchemaEntries,
|
|
222
|
+
(value, k) => value[k],
|
|
223
|
+
() => true,
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Builds a validator for a container with a stated `rest`. The declared
|
|
228
|
+
* members are read exactly as the bare form reads them, and every member the
|
|
229
|
+
* schema does not name is held to `rest`.
|
|
230
|
+
*
|
|
231
|
+
* `restFits` carries the array kind's length bound, which a `rest` removes
|
|
232
|
+
* only while it admits something. An empty one says what the bare form says,
|
|
233
|
+
* so `rest(c, or())` and `c` stay one set — the criterion for "empty" is
|
|
234
|
+
* `emptyRest`'s, and it is consulted only when nothing is present past the
|
|
235
|
+
* prefix, since a member that got there and passed is itself the proof that
|
|
236
|
+
* the rest admits something.
|
|
237
|
+
*/
|
|
238
|
+
const restContainerValidate =
|
|
239
|
+
/**
|
|
240
|
+
* @template {ReadonlyArray<Unknown> | StringMap<Unknown>} C
|
|
241
|
+
* @template {ConstObject} S
|
|
242
|
+
* @param {IsContainer<C>} isContainer
|
|
243
|
+
* @param {SchemaEntries<S>} schemaEntries
|
|
244
|
+
* @param {(value: C, k: string) => Unknown} getItem
|
|
245
|
+
* @param {(rtti: S, r: Type) => Fits<C>} restFits
|
|
246
|
+
* @returns {(rtti: S, r: Type) => ValidateE}
|
|
247
|
+
*/
|
|
248
|
+
(isContainer, schemaEntries, getItem, restFits) =>
|
|
249
|
+
(rtti, r) => {
|
|
250
|
+
// Depend on the schema alone, so they are computed once per schema.
|
|
251
|
+
const rttiEntries = schemaEntries(rtti)
|
|
252
|
+
const declared = rttiEntries.map(([k]) => k)
|
|
253
|
+
const fits = restFits(rtti, r)
|
|
254
|
+
return value => {
|
|
255
|
+
if (!isContainer(value)) {
|
|
256
|
+
return verror('unexpected value')
|
|
257
|
+
}
|
|
258
|
+
const d = eachEntry(
|
|
259
|
+
rttiEntries,
|
|
260
|
+
(k, v) => /** @type {any} */ (validate(v))(getItem(value, k)),
|
|
261
|
+
undefined,
|
|
262
|
+
noAccumulate,
|
|
263
|
+
)
|
|
264
|
+
if (d[0] === 'error') { return d }
|
|
265
|
+
const extra = undeclaredMembers(declared, value)
|
|
266
|
+
if (extra.length === 0) {
|
|
267
|
+
return fits(value, declared.length) ? ok(value) : verror('unexpected value')
|
|
268
|
+
}
|
|
269
|
+
const restValidate = /** @type {any} */ (validate(r))
|
|
270
|
+
const e = eachEntry(extra, (_k, v) => restValidate(v), undefined, noAccumulate)
|
|
271
|
+
return e[0] === 'error' ? e : ok(value)
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
const restTupleValidate = restContainerValidate(
|
|
276
|
+
isArray,
|
|
277
|
+
tupleSchemaEntries,
|
|
278
|
+
(value, k) => value[Number(k)],
|
|
279
|
+
(rtti, r) => (value, declared) => value.length <= declared || !emptyRest(rtti, r),
|
|
280
|
+
)
|
|
281
|
+
|
|
282
|
+
const restStructValidate = restContainerValidate(
|
|
283
|
+
isObject,
|
|
284
|
+
structSchemaEntries,
|
|
285
|
+
(value, k) => value[k],
|
|
286
|
+
() => () => true,
|
|
287
|
+
)
|
|
288
|
+
|
|
289
|
+
/** @type {(rtti: ConstObject, r: Type) => ValidateE} */
|
|
290
|
+
const restValidate = (rtti, r) =>
|
|
291
|
+
rtti instanceof Array
|
|
292
|
+
? restTupleValidate(rtti, r)
|
|
293
|
+
: restStructValidate(rtti, r)
|
|
294
|
+
|
|
295
|
+
const orValidate =
|
|
296
|
+
/**
|
|
297
|
+
* @template {readonly Type[]} T
|
|
298
|
+
* @param {T} rtti
|
|
299
|
+
* @returns {Validate<() => readonly ['or', ...T]>}
|
|
300
|
+
*/
|
|
301
|
+
rtti =>
|
|
302
|
+
/** @type {any} */ (orVisit(/** @type {any} */ (validate))(rtti))
|
|
303
|
+
|
|
304
|
+
const validateVisitor = /** @type {any} */ ({
|
|
305
|
+
tuple: tupleValidate,
|
|
306
|
+
struct: structValidate,
|
|
307
|
+
rest: restValidate,
|
|
308
|
+
array: arrayValidate,
|
|
309
|
+
record: recordValidate,
|
|
310
|
+
or: orValidate,
|
|
311
|
+
constPrimitive: constPrimitiveValidate,
|
|
312
|
+
primitive0: primitive0Validate,
|
|
313
|
+
unknown: () => ok,
|
|
314
|
+
})
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* Creates a validator function for the given RTTI schema: a `Thunk` for
|
|
318
|
+
* tag-based schemas, or a `Const` (primitive literal, tuple, or struct) for
|
|
319
|
+
* exact-value schemas.
|
|
320
|
+
*
|
|
321
|
+
* The returned function takes an unknown value and returns either
|
|
322
|
+
* `['ok', value]` — the very value it was given, not a reconstruction — or
|
|
323
|
+
* `['error', { path, message }]` describing the failure location.
|
|
324
|
+
*
|
|
325
|
+
* Use it when the question is "is this value of this shape?" and the value has
|
|
326
|
+
* to survive the question intact. Use `../parse/module.f.mjs` when the answer
|
|
327
|
+
* wanted is a value built to the schema.
|
|
328
|
+
*
|
|
329
|
+
* @example
|
|
330
|
+
* ```js
|
|
331
|
+
* const v = validate(array(number))
|
|
332
|
+
* const input = [1, 2, 3]
|
|
333
|
+
* v(input) // ['ok', input] — the same array, not a copy
|
|
334
|
+
* v([1, 'two']) // ['error', { path: ['1'], message: 'unexpected value' }]
|
|
335
|
+
*
|
|
336
|
+
* // closed, so a member the schema does not name rejects the value
|
|
337
|
+
* validate([number, number])([1, 2, 3]) // ['error', …]
|
|
338
|
+
* validate({ a: number })({ a: 1, b: 2 }) // ['error', …]
|
|
339
|
+
*
|
|
340
|
+
* // an absent optional member stays absent
|
|
341
|
+
* validate({ a: number, b: option(string) })({ a: 1 }) // ['ok', { a: 1 }]
|
|
342
|
+
*
|
|
343
|
+
* // a stated rest says what the undeclared members may be; `open` says anything
|
|
344
|
+
* validate(rest({ a: number }, number))({ a: 1, b: 2 }) // ['ok', { a: 1, b: 2 }]
|
|
345
|
+
* validate(open({ a: number }))({ a: 1, b: 'x' }) // ['ok', { a: 1, b: 'x' }]
|
|
346
|
+
* ```
|
|
347
|
+
*
|
|
348
|
+
* @type {<const T extends Type>(rtti: T) => Validate<T>}
|
|
349
|
+
*/
|
|
350
|
+
export const validate = rtti =>
|
|
351
|
+
(visit(validateVisitor)(rtti))
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @import { ValidationError, ValidateE, Validate } from '../common/types.ts'
|
|
3
|
+
* @import { Type } from '../types.ts'
|
|
4
|
+
* @import { Equal } from '../../ts/types.ts'
|
|
5
|
+
* @import { Ts, Unknown } from '../ts/types.ts'
|
|
6
|
+
* @import { Unknown as DjsUnknown } from '../../../djs/types.ts'
|
|
7
|
+
* @import { Assert } from '../../../asserts/types.ts'
|
|
8
|
+
*/
|
|
9
|
+
import { never } from '../module.f.mjs';
|
|
10
|
+
export type _RecursiveRest = () => readonly ['rest', readonly [_RecursiveRest], typeof never];
|
|
11
|
+
export type _OrCycleA = () => readonly ['or', _OrCycleB];
|
|
12
|
+
export type _OrCycleB = () => readonly ['or', _OrCycleA];
|
|
13
|
+
export type _SelfList = () => readonly ['or', undefined, () => readonly ['array', _SelfList]];
|
|
14
|
+
export declare const proof: {
|
|
15
|
+
verbatim: {
|
|
16
|
+
absentOptionalStaysAbsent: () => void;
|
|
17
|
+
undeclaredMemberSurvives: () => void;
|
|
18
|
+
referenceIdentity: () => void;
|
|
19
|
+
};
|
|
20
|
+
sameAcceptanceAsParse: () => void;
|
|
21
|
+
sameAcceptanceInTheDataForm: () => void;
|
|
22
|
+
optionalPositions: () => void;
|
|
23
|
+
interiorOptionBeforeRequired: () => void;
|
|
24
|
+
sparseTuple: {
|
|
25
|
+
holeIsDeclaredUndefined: () => void;
|
|
26
|
+
arityIsTheSchemaLength: () => void;
|
|
27
|
+
nonIndexPropertyIsNotDeclared: () => void;
|
|
28
|
+
};
|
|
29
|
+
boolean: {
|
|
30
|
+
ok: () => void;
|
|
31
|
+
error: () => void;
|
|
32
|
+
};
|
|
33
|
+
number: {
|
|
34
|
+
ok: () => void;
|
|
35
|
+
error: () => void;
|
|
36
|
+
};
|
|
37
|
+
string: {
|
|
38
|
+
ok: () => void;
|
|
39
|
+
error: () => void;
|
|
40
|
+
};
|
|
41
|
+
bigint: {
|
|
42
|
+
ok: () => void;
|
|
43
|
+
error: () => void;
|
|
44
|
+
};
|
|
45
|
+
unknown: {
|
|
46
|
+
ok: () => void;
|
|
47
|
+
};
|
|
48
|
+
const: {
|
|
49
|
+
null: {
|
|
50
|
+
ok: () => void;
|
|
51
|
+
error: () => void;
|
|
52
|
+
};
|
|
53
|
+
undefined: {
|
|
54
|
+
ok: () => void;
|
|
55
|
+
error: () => void;
|
|
56
|
+
};
|
|
57
|
+
number: {
|
|
58
|
+
ok: () => void;
|
|
59
|
+
error: () => void;
|
|
60
|
+
};
|
|
61
|
+
nan: {
|
|
62
|
+
ok: () => void;
|
|
63
|
+
error: () => void;
|
|
64
|
+
};
|
|
65
|
+
infinity: {
|
|
66
|
+
ok: () => void;
|
|
67
|
+
error: () => void;
|
|
68
|
+
};
|
|
69
|
+
signedZero: {
|
|
70
|
+
distinct: () => void;
|
|
71
|
+
self: () => void;
|
|
72
|
+
};
|
|
73
|
+
string: {
|
|
74
|
+
ok: () => void;
|
|
75
|
+
error: () => void;
|
|
76
|
+
};
|
|
77
|
+
bigint: {
|
|
78
|
+
ok: () => void;
|
|
79
|
+
error: () => void;
|
|
80
|
+
};
|
|
81
|
+
boolean: {
|
|
82
|
+
ok: () => void;
|
|
83
|
+
error: () => void;
|
|
84
|
+
};
|
|
85
|
+
tuple: {
|
|
86
|
+
ok: () => void;
|
|
87
|
+
extraItemsRejected: () => void;
|
|
88
|
+
shortArrayKeepsItsLength: () => void;
|
|
89
|
+
empty: () => void;
|
|
90
|
+
error: () => void;
|
|
91
|
+
};
|
|
92
|
+
struct: {
|
|
93
|
+
ok: () => void;
|
|
94
|
+
error: () => void;
|
|
95
|
+
};
|
|
96
|
+
};
|
|
97
|
+
array: {
|
|
98
|
+
empty: () => void;
|
|
99
|
+
ok: () => void;
|
|
100
|
+
error: () => void;
|
|
101
|
+
nested: () => void;
|
|
102
|
+
};
|
|
103
|
+
record: {
|
|
104
|
+
empty: () => void;
|
|
105
|
+
ok: () => void;
|
|
106
|
+
error: () => void;
|
|
107
|
+
};
|
|
108
|
+
constThunk: {
|
|
109
|
+
primitive: () => void;
|
|
110
|
+
};
|
|
111
|
+
or: {
|
|
112
|
+
consts: {
|
|
113
|
+
ok: () => void;
|
|
114
|
+
error: () => void;
|
|
115
|
+
};
|
|
116
|
+
thunks: {
|
|
117
|
+
ok: () => void;
|
|
118
|
+
error: () => void;
|
|
119
|
+
};
|
|
120
|
+
firstMatchWins: () => void;
|
|
121
|
+
};
|
|
122
|
+
option: {
|
|
123
|
+
ok: () => void;
|
|
124
|
+
error: () => void;
|
|
125
|
+
};
|
|
126
|
+
path: {
|
|
127
|
+
rootMismatch: () => void;
|
|
128
|
+
arrayIndex: () => void;
|
|
129
|
+
recordKey: () => void;
|
|
130
|
+
nestedArray: () => void;
|
|
131
|
+
tupleIndex: () => void;
|
|
132
|
+
structKey: () => void;
|
|
133
|
+
deepStruct: () => void;
|
|
134
|
+
recursiveSchema: () => void;
|
|
135
|
+
orRoot: () => void;
|
|
136
|
+
};
|
|
137
|
+
recursive: {
|
|
138
|
+
arrayOfArrays: () => void;
|
|
139
|
+
recordOfRecords: () => void;
|
|
140
|
+
};
|
|
141
|
+
rest: {
|
|
142
|
+
verbatim: () => void;
|
|
143
|
+
absentOptionalStaysAbsent: () => void;
|
|
144
|
+
path: () => void;
|
|
145
|
+
notAContainer: () => void;
|
|
146
|
+
};
|
|
147
|
+
emptyRests: {
|
|
148
|
+
dropped: () => void;
|
|
149
|
+
kept: () => void;
|
|
150
|
+
};
|
|
151
|
+
beyondAClosedPrefix: () => void;
|
|
152
|
+
lengthDoesNotBoundTheWalk: () => void;
|
|
153
|
+
arrayOptional: () => void;
|
|
154
|
+
};
|