@evolu/common 8.2.0 → 8.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/dist/src/Function.d.ts +1 -0
- package/dist/src/Function.d.ts.map +1 -1
- package/dist/src/Http.d.ts +9 -0
- package/dist/src/Http.d.ts.map +1 -1
- package/dist/src/Object.d.ts +1 -0
- package/dist/src/Object.d.ts.map +1 -1
- package/dist/src/RefCount.d.ts +1 -0
- package/dist/src/RefCount.d.ts.map +1 -1
- package/dist/src/Relation.d.ts +2 -0
- package/dist/src/Relation.d.ts.map +1 -1
- package/dist/src/Resource.d.ts +1 -0
- package/dist/src/Resource.d.ts.map +1 -1
- package/dist/src/Result.d.ts +383 -548
- package/dist/src/Result.d.ts.map +1 -1
- package/dist/src/Result.js +296 -46
- package/dist/src/StackTrace.d.ts +1 -0
- package/dist/src/StackTrace.d.ts.map +1 -1
- package/dist/src/Task.d.ts +985 -1069
- package/dist/src/Task.d.ts.map +1 -1
- package/dist/src/Task.js +1007 -156
- package/dist/src/Time.d.ts +1 -0
- package/dist/src/Time.d.ts.map +1 -1
- package/dist/src/Type.d.ts +1285 -734
- package/dist/src/Type.d.ts.map +1 -1
- package/dist/src/Type.js +1057 -193
- package/dist/src/intl/_en.d.ts +126 -0
- package/dist/src/intl/_en.d.ts.map +1 -0
- package/dist/src/intl/_en.js +267 -0
- package/dist/src/intl/ar.d.ts +117 -0
- package/dist/src/intl/ar.d.ts.map +1 -0
- package/dist/src/intl/ar.js +245 -0
- package/dist/src/intl/bn.d.ts +117 -0
- package/dist/src/intl/bn.d.ts.map +1 -0
- package/dist/src/intl/bn.js +258 -0
- package/dist/src/intl/ca.d.ts +117 -0
- package/dist/src/intl/ca.d.ts.map +1 -0
- package/dist/src/intl/ca.js +258 -0
- package/dist/src/intl/cs.d.ts +20 -7
- package/dist/src/intl/cs.d.ts.map +1 -1
- package/dist/src/intl/cs.js +54 -27
- package/dist/src/intl/da.d.ts +117 -0
- package/dist/src/intl/da.d.ts.map +1 -0
- package/dist/src/intl/da.js +258 -0
- package/dist/src/intl/de.d.ts +117 -0
- package/dist/src/intl/de.d.ts.map +1 -0
- package/dist/src/intl/de.js +258 -0
- package/dist/src/intl/el.d.ts +117 -0
- package/dist/src/intl/el.d.ts.map +1 -0
- package/dist/src/intl/el.js +265 -0
- package/dist/src/intl/es.d.ts +117 -0
- package/dist/src/intl/es.d.ts.map +1 -0
- package/dist/src/intl/es.js +258 -0
- package/dist/src/intl/fa.d.ts +117 -0
- package/dist/src/intl/fa.d.ts.map +1 -0
- package/dist/src/intl/fa.js +258 -0
- package/dist/src/intl/fi.d.ts +117 -0
- package/dist/src/intl/fi.d.ts.map +1 -0
- package/dist/src/intl/fi.js +258 -0
- package/dist/src/intl/fil.d.ts +117 -0
- package/dist/src/intl/fil.d.ts.map +1 -0
- package/dist/src/intl/fil.js +258 -0
- package/dist/src/intl/fr.d.ts +117 -0
- package/dist/src/intl/fr.d.ts.map +1 -0
- package/dist/src/intl/fr.js +258 -0
- package/dist/src/intl/he.d.ts +117 -0
- package/dist/src/intl/he.d.ts.map +1 -0
- package/dist/src/intl/he.js +258 -0
- package/dist/src/intl/hi.d.ts +117 -0
- package/dist/src/intl/hi.d.ts.map +1 -0
- package/dist/src/intl/hi.js +258 -0
- package/dist/src/intl/hr.d.ts +117 -0
- package/dist/src/intl/hr.d.ts.map +1 -0
- package/dist/src/intl/hr.js +265 -0
- package/dist/src/intl/hu.d.ts +63 -0
- package/dist/src/intl/hu.d.ts.map +1 -0
- package/dist/src/intl/hu.js +204 -0
- package/dist/src/intl/id.d.ts +117 -0
- package/dist/src/intl/id.d.ts.map +1 -0
- package/dist/src/intl/id.js +258 -0
- package/dist/src/intl/index.d.ts +42 -0
- package/dist/src/intl/index.d.ts.map +1 -1
- package/dist/src/intl/index.js +42 -0
- package/dist/src/intl/it.d.ts +117 -0
- package/dist/src/intl/it.d.ts.map +1 -0
- package/dist/src/intl/it.js +258 -0
- package/dist/src/intl/ja.d.ts +117 -0
- package/dist/src/intl/ja.d.ts.map +1 -0
- package/dist/src/intl/ja.js +258 -0
- package/dist/src/intl/ko.d.ts +117 -0
- package/dist/src/intl/ko.d.ts.map +1 -0
- package/dist/src/intl/ko.js +258 -0
- package/dist/src/intl/ml.d.ts +117 -0
- package/dist/src/intl/ml.d.ts.map +1 -0
- package/dist/src/intl/ml.js +258 -0
- package/dist/src/intl/mr.d.ts +117 -0
- package/dist/src/intl/mr.d.ts.map +1 -0
- package/dist/src/intl/mr.js +258 -0
- package/dist/src/intl/ms.d.ts +117 -0
- package/dist/src/intl/ms.d.ts.map +1 -0
- package/dist/src/intl/ms.js +245 -0
- package/dist/src/intl/nb.d.ts +63 -0
- package/dist/src/intl/nb.d.ts.map +1 -0
- package/dist/src/intl/nb.js +191 -0
- package/dist/src/intl/nl.d.ts +117 -0
- package/dist/src/intl/nl.d.ts.map +1 -0
- package/dist/src/intl/nl.js +258 -0
- package/dist/src/intl/pa.d.ts +117 -0
- package/dist/src/intl/pa.d.ts.map +1 -0
- package/dist/src/intl/pa.js +258 -0
- package/dist/src/intl/pl.d.ts +117 -0
- package/dist/src/intl/pl.d.ts.map +1 -0
- package/dist/src/intl/pl.js +242 -0
- package/dist/src/intl/pt-BR.d.ts +117 -0
- package/dist/src/intl/pt-BR.d.ts.map +1 -0
- package/dist/src/intl/pt-BR.js +258 -0
- package/dist/src/intl/pt.d.ts +117 -0
- package/dist/src/intl/pt.d.ts.map +1 -0
- package/dist/src/intl/pt.js +246 -0
- package/dist/src/intl/ro.d.ts +117 -0
- package/dist/src/intl/ro.d.ts.map +1 -0
- package/dist/src/intl/ro.js +258 -0
- package/dist/src/intl/sk.d.ts +117 -0
- package/dist/src/intl/sk.d.ts.map +1 -0
- package/dist/src/intl/sk.js +244 -0
- package/dist/src/intl/sl.d.ts +117 -0
- package/dist/src/intl/sl.d.ts.map +1 -0
- package/dist/src/intl/sl.js +258 -0
- package/dist/src/intl/sv.d.ts +117 -0
- package/dist/src/intl/sv.d.ts.map +1 -0
- package/dist/src/intl/sv.js +258 -0
- package/dist/src/intl/sw.d.ts +63 -0
- package/dist/src/intl/sw.d.ts.map +1 -0
- package/dist/src/intl/sw.js +191 -0
- package/dist/src/intl/ta.d.ts +117 -0
- package/dist/src/intl/ta.d.ts.map +1 -0
- package/dist/src/intl/ta.js +258 -0
- package/dist/src/intl/te.d.ts +117 -0
- package/dist/src/intl/te.d.ts.map +1 -0
- package/dist/src/intl/te.js +258 -0
- package/dist/src/intl/th.d.ts +117 -0
- package/dist/src/intl/th.d.ts.map +1 -0
- package/dist/src/intl/th.js +258 -0
- package/dist/src/intl/tr.d.ts +117 -0
- package/dist/src/intl/tr.d.ts.map +1 -0
- package/dist/src/intl/tr.js +255 -0
- package/dist/src/intl/uk.d.ts +117 -0
- package/dist/src/intl/uk.d.ts.map +1 -0
- package/dist/src/intl/uk.js +258 -0
- package/dist/src/intl/ur.d.ts +117 -0
- package/dist/src/intl/ur.d.ts.map +1 -0
- package/dist/src/intl/ur.js +245 -0
- package/dist/src/intl/vi.d.ts +117 -0
- package/dist/src/intl/vi.d.ts.map +1 -0
- package/dist/src/intl/vi.js +255 -0
- package/dist/src/intl/zh-CN.d.ts +117 -0
- package/dist/src/intl/zh-CN.d.ts.map +1 -0
- package/dist/src/intl/zh-CN.js +258 -0
- package/dist/src/intl/zh-TW.d.ts +117 -0
- package/dist/src/intl/zh-TW.d.ts.map +1 -0
- package/dist/src/intl/zh-TW.js +258 -0
- package/dist/src/local-first/Owner.d.ts +156 -62
- package/dist/src/local-first/Owner.d.ts.map +1 -1
- package/dist/src/local-first/Owner.js +119 -20
- package/package.json +1 -1
- package/src/Function.ts +2 -0
- package/src/Http.ts +9 -0
- package/src/Object.ts +2 -0
- package/src/RefCount.ts +2 -0
- package/src/Relation.ts +4 -0
- package/src/Resource.ts +2 -0
- package/src/Result.ts +397 -560
- package/src/StackTrace.ts +2 -0
- package/src/Task.ts +1143 -1222
- package/src/Time.ts +2 -0
- package/src/Type.ts +1598 -712
- package/src/intl/_en.ts +497 -0
- package/src/intl/ar.ts +417 -0
- package/src/intl/bn.ts +486 -0
- package/src/intl/ca.ts +488 -0
- package/src/intl/cs.ts +95 -31
- package/src/intl/da.ts +488 -0
- package/src/intl/de.ts +488 -0
- package/src/intl/el.ts +494 -0
- package/src/intl/es.ts +488 -0
- package/src/intl/fa.ts +487 -0
- package/src/intl/fi.ts +487 -0
- package/src/intl/fil.ts +488 -0
- package/src/intl/fr.ts +488 -0
- package/src/intl/he.ts +486 -0
- package/src/intl/hi.ts +487 -0
- package/src/intl/hr.ts +494 -0
- package/src/intl/hu.ts +387 -0
- package/src/intl/id.ts +487 -0
- package/src/intl/index.ts +42 -0
- package/src/intl/it.ts +488 -0
- package/src/intl/ja.ts +488 -0
- package/src/intl/ko.ts +487 -0
- package/src/intl/ml.ts +487 -0
- package/src/intl/mr.ts +487 -0
- package/src/intl/ms.ts +410 -0
- package/src/intl/nb.ts +364 -0
- package/src/intl/nl.ts +488 -0
- package/src/intl/pa.ts +487 -0
- package/src/intl/pl.ts +450 -0
- package/src/intl/pt-BR.ts +487 -0
- package/src/intl/pt.ts +424 -0
- package/src/intl/ro.ts +488 -0
- package/src/intl/sk.ts +412 -0
- package/src/intl/sl.ts +487 -0
- package/src/intl/sv.ts +488 -0
- package/src/intl/sw.ts +324 -0
- package/src/intl/ta.ts +487 -0
- package/src/intl/te.ts +487 -0
- package/src/intl/th.ts +484 -0
- package/src/intl/tr.ts +487 -0
- package/src/intl/uk.ts +488 -0
- package/src/intl/ur.ts +410 -0
- package/src/intl/vi.ts +435 -0
- package/src/intl/zh-CN.ts +482 -0
- package/src/intl/zh-TW.ts +482 -0
- package/src/local-first/Owner.ts +156 -62
package/dist/src/Type.js
CHANGED
|
@@ -1,5 +1,328 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* ## Intro
|
|
3
|
+
*
|
|
4
|
+
* Runtime validation with precise TypeScript types and structured errors.
|
|
5
|
+
*
|
|
6
|
+
* Evolu {@link Type} is a pure, synchronous codec for defining semantic domains.
|
|
7
|
+
* It partially decodes an `Input` into an `Output` and totally encodes every
|
|
8
|
+
* valid `Output` into a `CanonicalInput`. Types can validate, refine,
|
|
9
|
+
* transform, and compose without losing the contracts TypeScript can express.
|
|
10
|
+
*
|
|
11
|
+
* ```ts
|
|
12
|
+
* import {
|
|
13
|
+
* NonEmptyTrimmedString100,
|
|
14
|
+
* PositiveInt,
|
|
15
|
+
* object,
|
|
16
|
+
* type InferType,
|
|
17
|
+
* } from "@evolu/common";
|
|
18
|
+
*
|
|
19
|
+
* const User = object({
|
|
20
|
+
* name: NonEmptyTrimmedString100,
|
|
21
|
+
* age: PositiveInt,
|
|
22
|
+
* });
|
|
23
|
+
* interface User extends InferType<typeof User> {}
|
|
24
|
+
*
|
|
25
|
+
* const value: unknown = { name: "Ada", age: 37 };
|
|
26
|
+
* const user = User.fromUnknown(value);
|
|
27
|
+
*
|
|
28
|
+
* expectOk(user, { name: "Ada", age: 37 });
|
|
29
|
+
* expectTypeOf(user.value).toExtend<User>();
|
|
30
|
+
* ```
|
|
31
|
+
*
|
|
32
|
+
* Decoding failures are explicit {@link Result} values. Error formatters are
|
|
33
|
+
* separate from validation, so structured errors remain exhaustively typed and
|
|
34
|
+
* can be localized without changing the Type.
|
|
35
|
+
*
|
|
36
|
+
* Evolu Type is designed to make correct code the easiest code to write:
|
|
37
|
+
*
|
|
38
|
+
* - {@link Brand} carries every refinement constraint into TypeScript.
|
|
39
|
+
* - Invalid declarations produce readable {@link CompileTimeError} types when the
|
|
40
|
+
* compiler can detect them; runtime assertions enforce construction contracts
|
|
41
|
+
* it cannot prove.
|
|
42
|
+
* - Typed `from` boundaries allow connecting value producers to domain fields
|
|
43
|
+
* through their exact TypeScript types, so incompatible contract changes are
|
|
44
|
+
* compile-time errors rather than runtime validation errors.
|
|
45
|
+
* - Lawful codecs compose without creating unencodable values: every valid
|
|
46
|
+
* Output has a canonical Input representation and round-trips to the same
|
|
47
|
+
* semantic value.
|
|
48
|
+
* - Type-safe localization infers the required error formatters from selected
|
|
49
|
+
* Types, so missing validation messages are compile-time errors.
|
|
50
|
+
*
|
|
51
|
+
* Correctness is especially important for local-first data: application authors
|
|
52
|
+
* cannot inspect or repair a user's data on a server. Type declarations reject
|
|
53
|
+
* invalid data at system boundaries, then preserve those guarantees wherever
|
|
54
|
+
* the data travels.
|
|
55
|
+
*
|
|
56
|
+
* Evolu Type supports [Standard Schema](https://standardschema.dev/) while
|
|
57
|
+
* preserving each Type's exact Input and Output. Its implementation is
|
|
58
|
+
* optimized for small bundles, but keeps actionable assertion messages as a
|
|
59
|
+
* deliberate developer-experience tradeoff.
|
|
60
|
+
*
|
|
61
|
+
* Evolu Type requires TypeScript 7 or newer and `exactOptionalPropertyTypes`.
|
|
62
|
+
* Predefined Types use the names of corresponding JavaScript built-ins. When
|
|
63
|
+
* one shadows a built-in, access the built-in through `globalThis`, such as
|
|
64
|
+
* `globalThis.String` or `globalThis.Date`.
|
|
65
|
+
*
|
|
66
|
+
* ## Boundaries
|
|
67
|
+
*
|
|
68
|
+
* `fromUnknown` validates untyped input through the complete pipeline. `from`
|
|
69
|
+
* and its `.parent` operations assert their declared boundary, then return only
|
|
70
|
+
* errors from the remaining stages. `orThrow` and `orNull` reuse the deepest
|
|
71
|
+
* `from` operation accepting `Input`, while `to` asserts its Output boundary. A
|
|
72
|
+
* failed assertion is a developer error; its cause preserves the exact
|
|
73
|
+
* structured Output validation error.
|
|
74
|
+
*
|
|
75
|
+
* Prefer the most precise typed boundary available. A value is not unknown
|
|
76
|
+
* merely because it originated outside the application: forms, components, and
|
|
77
|
+
* other producers often expose a `string` or a branded value that can connect
|
|
78
|
+
* directly to a matching `from` boundary. Reserve `fromUnknown` for values
|
|
79
|
+
* whose TypeScript type is genuinely `unknown`. `is` means exact membership in
|
|
80
|
+
* the Output domain, not merely that an encoded Input can be decoded.
|
|
81
|
+
*
|
|
82
|
+
* Typed boundaries also connect producer and domain constraints at compile
|
|
83
|
+
* time. Suppose a form and its domain field both use
|
|
84
|
+
* {@link NonEmptyTrimmedString100}. If the form is later relaxed to
|
|
85
|
+
* {@link NonEmptyTrimmedString1000}, the domain Type will still reject longer
|
|
86
|
+
* values, so invalid data cannot be stored. But a boundary accepting only
|
|
87
|
+
* `unknown` or `string` cannot reveal that the producer contract changed. The
|
|
88
|
+
* application still compiles, and users discover the incompatibility only when
|
|
89
|
+
* a valid form value fails to save.
|
|
90
|
+
*
|
|
91
|
+
* Passing the precise branded value to `from` makes that incompatibility a
|
|
92
|
+
* compile-time error. The developer must preserve the original form limit or
|
|
93
|
+
* introduce a new field for the wider domain instead of shipping a broken form.
|
|
94
|
+
*
|
|
95
|
+
* A weaker producer is sometimes intentional. In that case, a matching
|
|
96
|
+
* `.parent` boundary validates only the constraints the producer does not
|
|
97
|
+
* already guarantee, while preserving its existing guarantees in the type.
|
|
98
|
+
*
|
|
99
|
+
* ## FAQ
|
|
100
|
+
*
|
|
101
|
+
* ### What does a Type represent?
|
|
102
|
+
*
|
|
103
|
+
* A Type is a lawful, pure codec for an exact semantic domain:
|
|
104
|
+
*
|
|
105
|
+
* ```text
|
|
106
|
+
* Input ── partial decode ──▶ Output
|
|
107
|
+
* CanonicalInput ◀─── total encode ─── Output
|
|
108
|
+
*
|
|
109
|
+
* CanonicalInput ⊆ Input
|
|
110
|
+
* ```
|
|
111
|
+
*
|
|
112
|
+
* Read each line in the direction of its arrowhead. `Input` is the complete
|
|
113
|
+
* typed decoding boundary, including candidates that validation can reject and
|
|
114
|
+
* noncanonical representations that decoding can normalize. `Output` is the
|
|
115
|
+
* validated semantic value. `CanonicalInput` is the statically known subtype of
|
|
116
|
+
* `Input` returned by the complete `to` operation. It can be wider than the
|
|
117
|
+
* values actually emitted when a refinement follows an arbitrary transformation
|
|
118
|
+
* because TypeScript cannot determine which values its encoder returns for the
|
|
119
|
+
* narrowed Output. `fromUnknown` and the `from` operations decode; `to`
|
|
120
|
+
* encodes.
|
|
121
|
+
*
|
|
122
|
+
* A lawful Type round-trips every Output:
|
|
123
|
+
*
|
|
124
|
+
* ```text
|
|
125
|
+
* fromUnknown(to(output)) ≈ ok(output)
|
|
126
|
+
* ```
|
|
127
|
+
*
|
|
128
|
+
* Encoding can canonicalize a valid Input:
|
|
129
|
+
*
|
|
130
|
+
* ```text
|
|
131
|
+
* "0042" ──decode──▶ 42 ──encode──▶ "42"
|
|
132
|
+
* ```
|
|
133
|
+
*
|
|
134
|
+
* Once canonicalized, repeating the decode-encode cycle must preserve that
|
|
135
|
+
* representation:
|
|
136
|
+
*
|
|
137
|
+
* ```text
|
|
138
|
+
* "42" ──decode──▶ 42 ──encode──▶ "42"
|
|
139
|
+
* ```
|
|
140
|
+
*
|
|
141
|
+
* Here `decode` means running the complete decoding pipeline, as `fromUnknown`
|
|
142
|
+
* does, and `≈` means equality appropriate for the semantic domain. Validation
|
|
143
|
+
* refinements, {@link ArrayType | Array Types}, and
|
|
144
|
+
* {@link ObjectType | Object Types} preserve these laws when their contained
|
|
145
|
+
* Types do. A {@link union} additionally requires compatible dispatch: it
|
|
146
|
+
* encodes through the first member matching the Output and decodes through the
|
|
147
|
+
* first member accepting the Input. Member ordering is lawful only when those
|
|
148
|
+
* choices agree semantically. Encoded representations can overlap even when
|
|
149
|
+
* member Output types are disjoint.
|
|
150
|
+
*
|
|
151
|
+
* When encoding returns a refined value unchanged, the refinement can narrow
|
|
152
|
+
* `CanonicalInput` without changing its JavaScript representation. For example,
|
|
153
|
+
* {@link FiniteNumber} has `number` as its Input, while its Output and
|
|
154
|
+
* CanonicalInput are `FiniteNumber`: decoding can reject non-finite number
|
|
155
|
+
* candidates, and encoding only receives validated finite Outputs. A
|
|
156
|
+
* transformation can change the representation entirely. For
|
|
157
|
+
* {@link Int64FromInt64String}, Input is `string`, Output is `Int64`, and
|
|
158
|
+
* CanonicalInput is `Int64String`. Structural Type factories derive their
|
|
159
|
+
* CanonicalInput recursively from their contained Types.
|
|
160
|
+
*
|
|
161
|
+
* ### Why is to total?
|
|
162
|
+
*
|
|
163
|
+
* Suppose a Type accepts only strings containing decimal digits and decodes
|
|
164
|
+
* them to JavaScript numbers. Parsing `"42"` is possible, but the Type cannot
|
|
165
|
+
* lawfully declare its Output as `number`:
|
|
166
|
+
*
|
|
167
|
+
* ```text
|
|
168
|
+
* digits-only string ──partial decode──▶ number
|
|
169
|
+
* digits-only string ◀─── total encode ── number // impossible
|
|
170
|
+
* ```
|
|
171
|
+
*
|
|
172
|
+
* `number` also contains negative and fractional numbers, `NaN`, positive and
|
|
173
|
+
* negative infinity, and `-0`. None of those values has a digits-only
|
|
174
|
+
* representation, so `to` could not encode every valid Output.
|
|
175
|
+
*
|
|
176
|
+
* One lawful design narrows the Output to the exact representable domain:
|
|
177
|
+
*
|
|
178
|
+
* ```text
|
|
179
|
+
* digits-only string ──partial decode──▶ NonNegativeSafeInteger
|
|
180
|
+
* digits-only string ◀─── total encode ── NonNegativeSafeInteger
|
|
181
|
+
*
|
|
182
|
+
* "0042" ──decode──▶ 42 ──encode──▶ "42"
|
|
183
|
+
* ```
|
|
184
|
+
*
|
|
185
|
+
* Another lawful design keeps `number` as the Output but expands the Input
|
|
186
|
+
* representation to include a canonical string for every number, including
|
|
187
|
+
* `"NaN"`, `"Infinity"`, `"-Infinity"`, and `"-0"`, as well as negative and
|
|
188
|
+
* fractional numbers.
|
|
189
|
+
*
|
|
190
|
+
* The same principle applies when converting between two representations. Give
|
|
191
|
+
* each representation its own Type with the same exact Output. For example, a
|
|
192
|
+
* string representation and a number representation can both decode to the
|
|
193
|
+
* shared `SafeInteger` domain:
|
|
194
|
+
*
|
|
195
|
+
* ```text
|
|
196
|
+
* string ──partial decode──▶ SafeInteger
|
|
197
|
+
* string ◀─── total encode ── SafeInteger
|
|
198
|
+
*
|
|
199
|
+
* number ──partial decode──▶ SafeInteger
|
|
200
|
+
* number ◀─── total encode ── SafeInteger
|
|
201
|
+
* ```
|
|
202
|
+
*
|
|
203
|
+
* Conversion decodes the source representation, then total-encodes the shared
|
|
204
|
+
* Output into the target representation. If no lossless shared domain exists,
|
|
205
|
+
* the operation is a partial conversion, migration, or policy decision and
|
|
206
|
+
* should be an explicit function returning Result, not a Type transformation.
|
|
207
|
+
*
|
|
208
|
+
* ### Why can a typed operation throw?
|
|
209
|
+
*
|
|
210
|
+
* TypeScript proves structural assignability, but it cannot describe every
|
|
211
|
+
* runtime invariant. For example, it cannot express whether an object property
|
|
212
|
+
* is own, enumerable, or a data property. It also permits a wider object with
|
|
213
|
+
* excess properties where a narrower object type is expected.
|
|
214
|
+
*
|
|
215
|
+
* `fromUnknown` treats such invalid external values as input data and returns a
|
|
216
|
+
* typed error. Typed boundaries instead assert the domain promised by their
|
|
217
|
+
* parameter type. If application code claims an accessor-backed object or an
|
|
218
|
+
* object with excess properties is an Object Output, the assertion throws
|
|
219
|
+
* because the application contract is broken. `orThrow` and `orNull` preserve
|
|
220
|
+
* the assertion at their typed `Input` boundary, then apply {@link getOrThrow}
|
|
221
|
+
* or {@link getOrNull} only to validation failures returned by the remaining
|
|
222
|
+
* pipeline.
|
|
223
|
+
*
|
|
224
|
+
* Consequently, structural representation errors such as sparse Arrays,
|
|
225
|
+
* accessors, and excess properties normally do not enter user-facing validation
|
|
226
|
+
* in typed application flows. They violate the producer's declared contract and
|
|
227
|
+
* throw as developer errors. At a genuinely unknown boundary, such as a
|
|
228
|
+
* schema-authoring tool, import, or external protocol, the same issues are
|
|
229
|
+
* legitimate typed validation errors and their formatter messages are useful.
|
|
230
|
+
*
|
|
231
|
+
* This distinction applies to data failures. Any Type operation, including
|
|
232
|
+
* `fromUnknown`, can throw when trusted Type-declaration code, such as a
|
|
233
|
+
* successful transformation callback, violates its declared contract.
|
|
234
|
+
*
|
|
235
|
+
* Materialize accessor values into plain data, remove properties the Type does
|
|
236
|
+
* not represent, or use a different Type. Silently discarding excess data would
|
|
237
|
+
* make the code constructing it dead while appearing to encode it successfully.
|
|
238
|
+
* One exact Object policy also keeps Output membership independent of parsing
|
|
239
|
+
* configuration. Exact structural policies also keep Output membership
|
|
240
|
+
* independent of whether a transformation happens to allocate a new value.
|
|
241
|
+
* Evolu Type therefore does not invoke accessors, discard excess properties, or
|
|
242
|
+
* make `to` fallible. This keeps `to` total for every legitimate Output and
|
|
243
|
+
* lets transformations compose without an encoding-error channel.
|
|
244
|
+
*
|
|
245
|
+
* ### How should values from another realm be handled?
|
|
246
|
+
*
|
|
247
|
+
* Code trust and data validation are separate decisions. Values returned by
|
|
248
|
+
* trusted legacy code or another realm can still be uncertain and should be
|
|
249
|
+
* validated. Realm-neutral Types accept an otherwise legitimate representation
|
|
250
|
+
* without requiring conversion merely because its built-ins belong to another
|
|
251
|
+
* realm.
|
|
252
|
+
*
|
|
253
|
+
* When an application trusts both the producer and its return contract, expose
|
|
254
|
+
* that contract as an accurate TypeScript type and use the typed value directly.
|
|
255
|
+
* If the boundary returns `unknown`, validate it instead of bypassing the
|
|
256
|
+
* boundary with a cast. Use a specialized Type or explicit transformation when
|
|
257
|
+
* the producer uses a different representation that needs adaptation or
|
|
258
|
+
* normalization.
|
|
259
|
+
*
|
|
260
|
+
* All executing JavaScript remains trusted. Deliberately forged built-ins,
|
|
261
|
+
* hostile Proxies, throwing traps, or sabotaged executable behavior can throw;
|
|
262
|
+
* Evolu Type does not selectively contain them or claim to be a security
|
|
263
|
+
* boundary for untrusted code.
|
|
264
|
+
*
|
|
265
|
+
* ### Why doesn't Evolu Type extract data from rich objects?
|
|
266
|
+
*
|
|
267
|
+
* Some validation libraries parse an object's data projection. An imaginary
|
|
268
|
+
* validation library can enumerate own enumerable string properties and decode
|
|
269
|
+
* them into a fresh plain object. That lets a class instance decode as plain
|
|
270
|
+
* data while its prototype and methods are ignored. The same general policy can
|
|
271
|
+
* treat a `Date` or `Map` as an empty Record and can invoke enumerable getters.
|
|
272
|
+
* This is a coherent but intentionally forgiving normalization model.
|
|
273
|
+
*
|
|
274
|
+
* Evolu Type validates exactly the runtime representation defined by each Type;
|
|
275
|
+
* it does not implicitly project one representation into another. The
|
|
276
|
+
* predefined {@link Object} defines an open plain-object representation with
|
|
277
|
+
* unknown values, {@link object} defines a closed plain-object representation,
|
|
278
|
+
* and {@link record} defines a plain-dictionary representation whose complete
|
|
279
|
+
* set of own properties are its entries. Their realm-neutral plain-object rule
|
|
280
|
+
* accepts a `null` prototype or an immediate root prototype whose own prototype
|
|
281
|
+
* is `null`; ordinary class instances and deeper prototype chains are rejected.
|
|
282
|
+
* Every property must be an enumerable data property; inherited members are not
|
|
283
|
+
* entries, while accessors and hidden properties are invalid instead of being
|
|
284
|
+
* invoked or ignored. {@link array} similarly defines a dense sequence whose
|
|
285
|
+
* only own properties are `length` and its indexed data properties;
|
|
286
|
+
* {@link tuple} applies the same representation rules with a fixed length and a
|
|
287
|
+
* distinct Type for each position. Only an explicit {@link transform} changes
|
|
288
|
+
* the representation. Consequently, `is` tests exact Output membership and `to`
|
|
289
|
+
* stays total for valid Outputs.
|
|
290
|
+
*
|
|
291
|
+
* ### Why is JsonValue stricter than JSON.stringify?
|
|
292
|
+
*
|
|
293
|
+
* `JSON.stringify` is a forgiving data projection. It can invoke `toJSON` and
|
|
294
|
+
* accessors, discard object properties, replace unsupported array elements and
|
|
295
|
+
* non-finite numbers with `null`, and normalize `-0` to `0`. Those rules are
|
|
296
|
+
* useful for ordinary serialization, but they do not preserve an exact value.
|
|
297
|
+
*
|
|
298
|
+
* {@link JsonValue} instead defines data that is already represented as data.
|
|
299
|
+
* Invalid runtime behavior and values are rejected rather than interpreted or
|
|
300
|
+
* silently discarded. Its encoder is total and stack-safe for every valid
|
|
301
|
+
* Output, and {@link JsonValueFromJson} preserves the semantic value when it is
|
|
302
|
+
* encoded and decoded, including JavaScript's distinction between `-0` and `0`.
|
|
303
|
+
* Use an explicit transformation before this boundary when a projection or
|
|
304
|
+
* other normalization is desired.
|
|
305
|
+
*
|
|
306
|
+
* ### Why are Types pure and synchronous?
|
|
307
|
+
*
|
|
308
|
+
* A Type describes data meaning, not work. Time, I/O, dependencies, external
|
|
309
|
+
* state, authorization, and other contextual decisions belong in a {@link Task}.
|
|
310
|
+
* Use a Type to decode the data required by that work, then pass the decoded
|
|
311
|
+
* value to a Task. A pure synchronous conversion that can fail can be an
|
|
312
|
+
* ordinary function returning Result.
|
|
313
|
+
*
|
|
314
|
+
* Keeping those responsibilities separate prevents Evolu Type from becoming a
|
|
315
|
+
* hidden application workflow. It also keeps validation deterministic,
|
|
316
|
+
* dependency-free, immediately composable, and straightforward to test.
|
|
317
|
+
*
|
|
318
|
+
* ### What if only decoding is needed?
|
|
319
|
+
*
|
|
320
|
+
* Use `fromUnknown` for unknown data. For typed application data, call `from`
|
|
321
|
+
* at the boundary its input type proves, or use `orThrow` or `orNull` for a
|
|
322
|
+
* flat conversion from `Input`. The canonical `to` encoder still keeps the Type
|
|
323
|
+
* lawful and composable with transformations and structural Types. A genuinely
|
|
324
|
+
* irreversible operation is a separate function or Task, not a Type
|
|
325
|
+
* transformation.
|
|
3
326
|
*
|
|
4
327
|
* @module
|
|
5
328
|
*/
|
|
@@ -50,7 +373,7 @@ const formatDefaultRuntimeTypeIssue = (issue) => issue.formatError(issue.error);
|
|
|
50
373
|
* Asserts that a value belongs to a {@link Type} Output domain.
|
|
51
374
|
*
|
|
52
375
|
* Use this for internal invariants, not external input. Validate external input
|
|
53
|
-
* with `Type.fromUnknown` so
|
|
376
|
+
* with `Type.fromUnknown` so validation failures remain typed values. A failed
|
|
54
377
|
* assertion uses the Type name for its message and preserves the exact Output
|
|
55
378
|
* validation error as the thrown Error's cause.
|
|
56
379
|
*
|
|
@@ -70,7 +393,7 @@ const formatDefaultRuntimeTypeIssue = (issue) => issue.formatError(issue.error);
|
|
|
70
393
|
* >();
|
|
71
394
|
* ```
|
|
72
395
|
*
|
|
73
|
-
* @group Core
|
|
396
|
+
* @group Core
|
|
74
397
|
*/
|
|
75
398
|
export const assertType = (type, value) => {
|
|
76
399
|
// TODO: Make assert prepend "Expected " and accept an optional third cause
|
|
@@ -90,34 +413,32 @@ const assertTypeOutput = (name, is, validateOutput, value, options = firstValida
|
|
|
90
413
|
throw new Error(`Expected ${name}.`, { cause: error });
|
|
91
414
|
};
|
|
92
415
|
/**
|
|
93
|
-
*
|
|
94
|
-
*
|
|
95
|
-
*
|
|
96
|
-
*
|
|
97
|
-
*
|
|
98
|
-
*
|
|
99
|
-
*
|
|
100
|
-
*
|
|
101
|
-
*
|
|
102
|
-
*
|
|
103
|
-
*
|
|
104
|
-
*
|
|
105
|
-
*
|
|
106
|
-
*
|
|
107
|
-
*
|
|
108
|
-
*
|
|
109
|
-
*
|
|
110
|
-
*
|
|
111
|
-
*
|
|
112
|
-
*
|
|
113
|
-
*
|
|
114
|
-
*
|
|
115
|
-
*
|
|
116
|
-
*
|
|
117
|
-
*
|
|
118
|
-
*
|
|
119
|
-
* network connection. Different localized Type sets can coexist on one page or
|
|
120
|
-
* in separate dependency-injection scopes.
|
|
416
|
+
* Creates localized copies of selected {@link Type} declarations.
|
|
417
|
+
*
|
|
418
|
+
* Pass the Types used together in one localization scope and formatter maps
|
|
419
|
+
* keyed by locale. TypeScript infers every formatter required by the selected
|
|
420
|
+
* Types, including errors from nested structural Types and recursive Lazy
|
|
421
|
+
* Types. Every locale must provide the complete inferred formatter set;
|
|
422
|
+
* missing and unrelated formatters are compile-time errors.
|
|
423
|
+
*
|
|
424
|
+
* The result preserves the locale names, selected Type names, and exact
|
|
425
|
+
* TypeScript types. A localized Type validates exactly like its source Type;
|
|
426
|
+
* only its human-readable `formatError` and Standard Schema messages change.
|
|
427
|
+
* The source Types remain unchanged.
|
|
428
|
+
*
|
|
429
|
+
* Parents and reflected child Types are localized with the same formatter set.
|
|
430
|
+
* Structural Types retain error paths and delegate nested messages to the Type
|
|
431
|
+
* that produced them. Different localized Type sets can coexist in separate
|
|
432
|
+
* application or dependency-injection scopes.
|
|
433
|
+
*
|
|
434
|
+
* Localization is scoped to the selected Types instead of a package-wide
|
|
435
|
+
* translation registry. Static imports give bundlers an explicit dependency
|
|
436
|
+
* graph, so unrelated Types, locales, and formatters can be removed. Bundling
|
|
437
|
+
* every locale an app supports also allows language changes without a network
|
|
438
|
+
* connection.
|
|
439
|
+
*
|
|
440
|
+
* The selected Type map, locale map, and formatter maps must be plain objects
|
|
441
|
+
* with own enumerable string-keyed data properties.
|
|
121
442
|
*
|
|
122
443
|
* ### Example
|
|
123
444
|
*
|
|
@@ -127,7 +448,7 @@ const assertTypeOutput = (name, is, validateOutput, value, options = firstValida
|
|
|
127
448
|
*
|
|
128
449
|
* const Label = minLength(1)(String);
|
|
129
450
|
*
|
|
130
|
-
* const
|
|
451
|
+
* const typesByLocale = localizeTypes(
|
|
131
452
|
* { Label },
|
|
132
453
|
* {
|
|
133
454
|
* cs: {
|
|
@@ -137,11 +458,66 @@ const assertTypeOutput = (name, is, validateOutput, value, options = firstValida
|
|
|
137
458
|
* },
|
|
138
459
|
* );
|
|
139
460
|
*
|
|
140
|
-
* expectTypeOf<typeof
|
|
461
|
+
* expectTypeOf<typeof typesByLocale.cs.Label>().toEqualTypeOf<
|
|
141
462
|
* typeof Label
|
|
142
463
|
* >();
|
|
464
|
+
*
|
|
465
|
+
* const result = typesByLocale.cs.Label.fromUnknown("");
|
|
466
|
+
* expectErr(result, { type: "MinLength1", min: 1, value: "" });
|
|
467
|
+
* expect(typesByLocale.cs.Label.formatError(result.error)).toBe(
|
|
468
|
+
* "Text nesmí být prázdný.",
|
|
469
|
+
* );
|
|
143
470
|
* ```
|
|
144
471
|
*
|
|
472
|
+
* ### Supported locales
|
|
473
|
+
*
|
|
474
|
+
* English is built in; use {@link Type} directly for its default formatters.
|
|
475
|
+
* The following additional locales are available:
|
|
476
|
+
*
|
|
477
|
+
* - Arabic (`ar`)
|
|
478
|
+
* - Bengali (`bn`)
|
|
479
|
+
* - Catalan (`ca`)
|
|
480
|
+
* - Chinese, Simplified (`zhCN`)
|
|
481
|
+
* - Chinese, Traditional (`zhTW`)
|
|
482
|
+
* - Croatian (`hr`)
|
|
483
|
+
* - Czech (`cs`)
|
|
484
|
+
* - Danish (`da`)
|
|
485
|
+
* - Dutch (`nl`)
|
|
486
|
+
* - Filipino (`fil`)
|
|
487
|
+
* - Finnish (`fi`)
|
|
488
|
+
* - French (`fr`)
|
|
489
|
+
* - German (`de`)
|
|
490
|
+
* - Greek (`el`)
|
|
491
|
+
* - Hebrew (`he`)
|
|
492
|
+
* - Hindi (`hi`)
|
|
493
|
+
* - Hungarian (`hu`)
|
|
494
|
+
* - Indonesian (`id`)
|
|
495
|
+
* - Italian (`it`)
|
|
496
|
+
* - Japanese (`ja`)
|
|
497
|
+
* - Korean (`ko`)
|
|
498
|
+
* - Malay (`ms`)
|
|
499
|
+
* - Malayalam (`ml`)
|
|
500
|
+
* - Marathi (`mr`)
|
|
501
|
+
* - Norwegian Bokmål (`nb`)
|
|
502
|
+
* - Persian (`fa`)
|
|
503
|
+
* - Polish (`pl`)
|
|
504
|
+
* - Portuguese (`pt`)
|
|
505
|
+
* - Portuguese, Brazilian (`ptBR`)
|
|
506
|
+
* - Punjabi (`pa`)
|
|
507
|
+
* - Romanian (`ro`)
|
|
508
|
+
* - Slovak (`sk`)
|
|
509
|
+
* - Slovenian (`sl`)
|
|
510
|
+
* - Spanish (`es`)
|
|
511
|
+
* - Swahili (`sw`)
|
|
512
|
+
* - Swedish (`sv`)
|
|
513
|
+
* - Tamil (`ta`)
|
|
514
|
+
* - Telugu (`te`)
|
|
515
|
+
* - Thai (`th`)
|
|
516
|
+
* - Turkish (`tr`)
|
|
517
|
+
* - Ukrainian (`uk`)
|
|
518
|
+
* - Urdu (`ur`)
|
|
519
|
+
* - Vietnamese (`vi`)
|
|
520
|
+
*
|
|
145
521
|
* @group Localization
|
|
146
522
|
*/
|
|
147
523
|
export const localizeTypes = ((typesByName, formatErrorByTypeByLocale) => {
|
|
@@ -435,9 +811,17 @@ const addRuntimeAssertions = (name, is, validateOutput, parent, operation) => {
|
|
|
435
811
|
}
|
|
436
812
|
return asserted;
|
|
437
813
|
};
|
|
438
|
-
/**
|
|
814
|
+
/**
|
|
815
|
+
* An infallible {@link Type} accepting every value.
|
|
816
|
+
*
|
|
817
|
+
* @group Base
|
|
818
|
+
*/
|
|
439
819
|
export const Unknown = /*#__PURE__*/ createRootType("Unknown", ok, identity);
|
|
440
|
-
/**
|
|
820
|
+
/**
|
|
821
|
+
* A {@link Type} rejecting every value.
|
|
822
|
+
*
|
|
823
|
+
* @group Base
|
|
824
|
+
*/
|
|
441
825
|
export const Never = /*#__PURE__*/ createRootType("Never", (value) => err({ type: "Never", value }), (error) => `A value ${safelyStringifyUnknownValue(error.value)} is not valid for type Never.`);
|
|
442
826
|
const createTypeOfType = (name) => {
|
|
443
827
|
const typeOf = name.toLowerCase();
|
|
@@ -468,6 +852,8 @@ const createTypeOfType = (name) => {
|
|
|
468
852
|
* expectTypeOf<WireValue100>().toEqualTypeOf<
|
|
469
853
|
* string & Brand<"MaxLength100">
|
|
470
854
|
* >();
|
|
855
|
+
* expectOk(WireValue100.fromUnknown(""), "");
|
|
856
|
+
* expectOk(WireValue100.fromUnknown(" value "), " value ");
|
|
471
857
|
* ```
|
|
472
858
|
*
|
|
473
859
|
* @group String
|
|
@@ -542,36 +928,52 @@ export const String = /*#__PURE__*/ createTypeOfType("String");
|
|
|
542
928
|
* @group Number
|
|
543
929
|
*/
|
|
544
930
|
export const Number = /*#__PURE__*/ createTypeOfType("Number");
|
|
545
|
-
/**
|
|
931
|
+
/**
|
|
932
|
+
* A JavaScript bigint {@link Type}.
|
|
933
|
+
*
|
|
934
|
+
* @group Base
|
|
935
|
+
*/
|
|
546
936
|
export const BigInt = /*#__PURE__*/ createTypeOfType("BigInt");
|
|
547
|
-
/**
|
|
937
|
+
/**
|
|
938
|
+
* A JavaScript boolean {@link Type}.
|
|
939
|
+
*
|
|
940
|
+
* @group Base
|
|
941
|
+
*/
|
|
548
942
|
export const Boolean = /*#__PURE__*/ createTypeOfType("Boolean");
|
|
549
|
-
/**
|
|
943
|
+
/**
|
|
944
|
+
* A JavaScript symbol {@link Type}.
|
|
945
|
+
*
|
|
946
|
+
* @group Base
|
|
947
|
+
*/
|
|
550
948
|
export const Symbol = /*#__PURE__*/ createTypeOfType("Symbol");
|
|
551
|
-
/**
|
|
949
|
+
/**
|
|
950
|
+
* A JavaScript function {@link Type}.
|
|
951
|
+
*
|
|
952
|
+
* @group Base
|
|
953
|
+
*/
|
|
552
954
|
export const Function = /*#__PURE__*/ createTypeOfType("Function");
|
|
553
955
|
/**
|
|
554
|
-
*
|
|
956
|
+
* A {@link Type} validating Evolu Type declarations.
|
|
555
957
|
*
|
|
556
958
|
* This is useful when a Type itself crosses an unknown boundary or must be
|
|
557
959
|
* asserted with {@link assertType}.
|
|
558
960
|
*
|
|
559
|
-
* @group Core
|
|
961
|
+
* @group Core
|
|
560
962
|
*/
|
|
561
963
|
export const EvoluType = /*#__PURE__*/ createType("EvoluType", (value) => isInstance("Type")(value)
|
|
562
964
|
? ok(value)
|
|
563
965
|
: err({ type: "EvoluType", value }), (error) => `A value ${safelyStringifyUnknownValue(error.value)} is not an Evolu Type.`);
|
|
564
966
|
export function objectTag(name, outputType) {
|
|
967
|
+
const formatError = (error) => `A value ${safelyStringifyUnknownValue(error.value)} does not have the expected object tag ${safelyStringifyUnknownValue(error.expected)}.`;
|
|
565
968
|
if (outputType === undefined) {
|
|
566
969
|
return createRootType(name, (value) => hasObjectTag(value, name)
|
|
567
970
|
? ok(value)
|
|
568
|
-
: err({ type: "ObjectTag", expected: name, value }),
|
|
971
|
+
: err({ type: "ObjectTag", expected: name, value }), formatError);
|
|
569
972
|
}
|
|
570
973
|
return globalThis.Object.assign(createChildType("ObjectTag", outputType, (value) => hasObjectTag(value, name)
|
|
571
974
|
? ok(value)
|
|
572
|
-
: err({ type: "ObjectTag", expected: name, value }),
|
|
975
|
+
: err({ type: "ObjectTag", expected: name, value }), formatError), { expected: name });
|
|
573
976
|
}
|
|
574
|
-
const formatObjectTagError = (error) => `A value ${safelyStringifyUnknownValue(error.value)} does not have the object tag ${safelyStringifyUnknownValue(error.expected)}.`;
|
|
575
977
|
const hasObjectTag = (value, expected) => value !== null &&
|
|
576
978
|
(typeof value === "object" || typeof value === "function") &&
|
|
577
979
|
globalThis.Object.prototype.toString.call(value) === `[object ${expected}]`;
|
|
@@ -580,7 +982,7 @@ const hasObjectTag = (value, expected) => value !== null &&
|
|
|
580
982
|
*
|
|
581
983
|
* It trusts the reported object tag and does not verify Date internal slots.
|
|
582
984
|
*
|
|
583
|
-
* @group Base
|
|
985
|
+
* @group Base
|
|
584
986
|
*/
|
|
585
987
|
export const Date = /*#__PURE__*/ objectTag("Date");
|
|
586
988
|
/**
|
|
@@ -589,7 +991,7 @@ export const Date = /*#__PURE__*/ objectTag("Date");
|
|
|
589
991
|
* It trusts the reported object tag and does not verify Uint8Array internal
|
|
590
992
|
* slots.
|
|
591
993
|
*
|
|
592
|
-
* @group Base
|
|
994
|
+
* @group Base
|
|
593
995
|
*/
|
|
594
996
|
export const Uint8Array = /*#__PURE__*/ objectTag("Uint8Array");
|
|
595
997
|
/**
|
|
@@ -598,7 +1000,7 @@ export const Uint8Array = /*#__PURE__*/ objectTag("Uint8Array");
|
|
|
598
1000
|
* It trusts the reported object tag and does not verify ArrayBuffer internal
|
|
599
1001
|
* slots.
|
|
600
1002
|
*
|
|
601
|
-
* @group Base
|
|
1003
|
+
* @group Base
|
|
602
1004
|
*/
|
|
603
1005
|
export const ArrayBuffer = /*#__PURE__*/ objectTag("ArrayBuffer");
|
|
604
1006
|
/**
|
|
@@ -629,7 +1031,7 @@ export const ArrayBuffer = /*#__PURE__*/ objectTag("ArrayBuffer");
|
|
|
629
1031
|
* assert(!UserInstance.is({ name: "Ada" }));
|
|
630
1032
|
* ```
|
|
631
1033
|
*
|
|
632
|
-
* @group Base
|
|
1034
|
+
* @group Base
|
|
633
1035
|
*/
|
|
634
1036
|
export const instanceOf = (constructor) => {
|
|
635
1037
|
const concreteConstructor = constructor;
|
|
@@ -690,9 +1092,17 @@ export const literal = (expected) => {
|
|
|
690
1092
|
[templateLiteralSyntaxSymbol]: true,
|
|
691
1093
|
});
|
|
692
1094
|
};
|
|
693
|
-
/**
|
|
1095
|
+
/**
|
|
1096
|
+
* Literal {@link Type} accepting only `undefined`.
|
|
1097
|
+
*
|
|
1098
|
+
* @group Unions
|
|
1099
|
+
*/
|
|
694
1100
|
export const Undefined = /*#__PURE__*/ literal(undefined);
|
|
695
|
-
/**
|
|
1101
|
+
/**
|
|
1102
|
+
* Literal {@link Type} accepting only `null`.
|
|
1103
|
+
*
|
|
1104
|
+
* @group Unions
|
|
1105
|
+
*/
|
|
696
1106
|
export const Null = /*#__PURE__*/ literal(null);
|
|
697
1107
|
export function union(...typesOrLiterals) {
|
|
698
1108
|
const members = typesOrLiterals.map((typeOrLiteral) => typeOrLiteral !== null && typeof typeOrLiteral === "object"
|
|
@@ -701,7 +1111,7 @@ export function union(...typesOrLiterals) {
|
|
|
701
1111
|
const inputMembers = members.map(getTerminalRuntimeNode);
|
|
702
1112
|
const inputFrom = createUnionValidation(inputMembers, (member, value, options) => member.fromUnknown(value, options));
|
|
703
1113
|
const inputValidateOutput = createUnionValidation(inputMembers, (member, value, options) => member[outputValidationSymbol](value, options));
|
|
704
|
-
const defaultFormatter =
|
|
1114
|
+
const defaultFormatter = (() => "A value does not match any allowed variant.");
|
|
705
1115
|
const getTypeIssues = (error) => singleRuntimeTypeIssue("Union", error, defaultFormatter);
|
|
706
1116
|
const input = createTypeNode("Union", null, inputFrom, (value) => inputMembers.some((member) => member.is(value)), inputValidateOutput, ok, identity, getTypeIssues);
|
|
707
1117
|
const fromUnknown = createUnionValidation(members, (member, value, options) => member.fromUnknown(value, options));
|
|
@@ -719,7 +1129,6 @@ export function union(...typesOrLiterals) {
|
|
|
719
1129
|
};
|
|
720
1130
|
return createTypeNode("Union", input, fromUnknown, (value) => members.some((member) => member.is(value)), validateOutput, from, to, getTypeIssues, { members, [templateLiteralSyntaxSymbol]: true });
|
|
721
1131
|
}
|
|
722
|
-
const formatUnionError = () => "A value does not match any union member.";
|
|
723
1132
|
const createUnionValidation = (members, validateMember) => (value, options = firstValidationOptions) => {
|
|
724
1133
|
let errors;
|
|
725
1134
|
for (let index = 0; index < members.length; index++) {
|
|
@@ -744,18 +1153,49 @@ const createUnionValidation = (members, validateMember) => (value, options = fir
|
|
|
744
1153
|
* This does not make an object property optional. It changes only the values
|
|
745
1154
|
* accepted when the property is present.
|
|
746
1155
|
*
|
|
1156
|
+
* ### Example
|
|
1157
|
+
*
|
|
1158
|
+
* ```ts
|
|
1159
|
+
* import { String, undefinedOr } from "@evolu/common";
|
|
1160
|
+
*
|
|
1161
|
+
* const StringOrUndefined = undefinedOr(String);
|
|
1162
|
+
*
|
|
1163
|
+
* expectOk(StringOrUndefined.fromUnknown(undefined), undefined);
|
|
1164
|
+
* ```
|
|
1165
|
+
*
|
|
747
1166
|
* @group Unions
|
|
748
1167
|
*/
|
|
749
1168
|
export const undefinedOr = (type) => union(type, Undefined);
|
|
750
1169
|
/**
|
|
751
1170
|
* Union {@link Type} containing the supplied Type and `null`.
|
|
752
1171
|
*
|
|
1172
|
+
* ### Example
|
|
1173
|
+
*
|
|
1174
|
+
* ```ts
|
|
1175
|
+
* import { String, nullOr } from "@evolu/common";
|
|
1176
|
+
*
|
|
1177
|
+
* const NullableString = nullOr(String);
|
|
1178
|
+
*
|
|
1179
|
+
* expectOk(NullableString.fromUnknown(null), null);
|
|
1180
|
+
* ```
|
|
1181
|
+
*
|
|
753
1182
|
* @group Unions
|
|
754
1183
|
*/
|
|
755
1184
|
export const nullOr = (type) => union(type, Null);
|
|
756
1185
|
/**
|
|
757
1186
|
* Union {@link Type} containing the supplied Type, `null`, and `undefined`.
|
|
758
1187
|
*
|
|
1188
|
+
* ### Example
|
|
1189
|
+
*
|
|
1190
|
+
* ```ts
|
|
1191
|
+
* import { String, nullishOr } from "@evolu/common";
|
|
1192
|
+
*
|
|
1193
|
+
* const NullishString = nullishOr(String);
|
|
1194
|
+
*
|
|
1195
|
+
* expectOk(NullishString.fromUnknown(undefined), undefined);
|
|
1196
|
+
* expectOk(NullishString.fromUnknown(null), null);
|
|
1197
|
+
* ```
|
|
1198
|
+
*
|
|
759
1199
|
* @group Unions
|
|
760
1200
|
*/
|
|
761
1201
|
export const nullishOr = (type) => union(type, Null, Undefined);
|
|
@@ -763,9 +1203,7 @@ const isRuntimeUnionTypeNode = (type) => type.name === "Union" &&
|
|
|
763
1203
|
"members" in type &&
|
|
764
1204
|
globalThis.Array.isArray(type.members);
|
|
765
1205
|
/**
|
|
766
|
-
* Template literal {@link Type}
|
|
767
|
-
*
|
|
768
|
-
* Parses and creates structured strings.
|
|
1206
|
+
* Template literal {@link Type} that parses canonical strings into Tuples.
|
|
769
1207
|
*
|
|
770
1208
|
* Accepts the same template parts as {@link templateLiteral}: fixed string
|
|
771
1209
|
* literals and Types canonically encoded as strings. Instead of keeping Output
|
|
@@ -781,10 +1219,10 @@ const isRuntimeUnionTypeNode = (type) => type.name === "Union" &&
|
|
|
781
1219
|
* reversibility and keeps capture boundaries unambiguous. Different capture
|
|
782
1220
|
* Tuples must never encode to the same string. The parser provides predictable
|
|
783
1221
|
* parsing without pathological backtracking and decodes each capture once, so
|
|
784
|
-
* adversarial input cannot trigger exponential parser work. Fixed-width
|
|
785
|
-
* may be adjacent, but only one variable-width capture is allowed.
|
|
786
|
-
* that could join UTF-16 surrogate halves across parts are
|
|
787
|
-
* construction.
|
|
1222
|
+
* adversarial input cannot trigger exponential parser work. Fixed-width
|
|
1223
|
+
* captures may be adjacent, but only one variable-width capture is allowed.
|
|
1224
|
+
* Declarations that could join UTF-16 surrogate halves across parts are
|
|
1225
|
+
* rejected during construction.
|
|
788
1226
|
*
|
|
789
1227
|
* Keep capture unions reasonably small to avoid excessive compiler work.
|
|
790
1228
|
*
|
|
@@ -821,10 +1259,9 @@ const isRuntimeUnionTypeNode = (type) => type.name === "Union" &&
|
|
|
821
1259
|
*
|
|
822
1260
|
* // Parse an unknown string into structured data.
|
|
823
1261
|
* const result = SupportedLocale.fromUnknown("cs-CZ");
|
|
824
|
-
*
|
|
1262
|
+
* expectOk(result, ["cs", "CZ"]);
|
|
825
1263
|
* const locale = result.value;
|
|
826
1264
|
* expectTypeOf(locale).toEqualTypeOf<SupportedLocale>();
|
|
827
|
-
* expect(locale).toEqual(["cs", "CZ"]);
|
|
828
1265
|
* expectErr(SupportedLocale.fromUnknown("cs/CZ"), {
|
|
829
1266
|
* type: "TemplateLiteral",
|
|
830
1267
|
* value: "cs/CZ",
|
|
@@ -883,10 +1320,9 @@ const isRuntimeUnionTypeNode = (type) => type.name === "Union" &&
|
|
|
883
1320
|
*
|
|
884
1321
|
* // Decode the string into structured data.
|
|
885
1322
|
* const result = ItemId.fromUnknown("item-42");
|
|
886
|
-
*
|
|
1323
|
+
* expectOk(result, [42n]);
|
|
887
1324
|
* const itemId = result.value;
|
|
888
1325
|
* expectTypeOf(itemId).toEqualTypeOf<ItemId>();
|
|
889
|
-
* expect(itemId).toEqual([42n]);
|
|
890
1326
|
*
|
|
891
1327
|
* // Encode the structured data into its canonical string.
|
|
892
1328
|
* const itemIdLiteral = ItemId.to(itemId);
|
|
@@ -989,7 +1425,7 @@ const createTemplateLiteralParserType = (templateParts) => {
|
|
|
989
1425
|
if ("outputError" in error) {
|
|
990
1426
|
return runtimeOutput[getRuntimeTypeIssuesSymbol](error.outputError, mode);
|
|
991
1427
|
}
|
|
992
|
-
return singleRuntimeTypeIssue("TemplateLiteral", error,
|
|
1428
|
+
return singleRuntimeTypeIssue("TemplateLiteral", error, ((error) => `The value ${safelyStringifyUnknownValue(error.value)} does not match the template literal.`));
|
|
993
1429
|
};
|
|
994
1430
|
const canonicalStringFromUnknown = (value, options = firstValidationOptions) => {
|
|
995
1431
|
const stringResult = String.fromUnknown(value, options);
|
|
@@ -1039,8 +1475,8 @@ const createTemplateLiteralParserType = (templateParts) => {
|
|
|
1039
1475
|
* Creates a canonical string Type from fixed strings and string-encoded Types.
|
|
1040
1476
|
*
|
|
1041
1477
|
* Use this factory when Output should remain a string. Switch to
|
|
1042
|
-
* {@link templateLiteralParser} when the individual Type parts should be
|
|
1043
|
-
*
|
|
1478
|
+
* {@link templateLiteralParser} when the individual Type parts should be decoded
|
|
1479
|
+
* into a Tuple.
|
|
1044
1480
|
*
|
|
1045
1481
|
* ### Example
|
|
1046
1482
|
*
|
|
@@ -1061,7 +1497,6 @@ const createTemplateLiteralParserType = (templateParts) => {
|
|
|
1061
1497
|
* @group Template literals
|
|
1062
1498
|
*/
|
|
1063
1499
|
export const templateLiteral = (...parts) => createTemplateLiteralParserType(parts).parent;
|
|
1064
|
-
const formatTemplateLiteralError = (error) => `The value ${safelyStringifyUnknownValue(error.value)} does not match the template literal.`;
|
|
1065
1500
|
const compileTemplateLiteralParser = (parts) => {
|
|
1066
1501
|
let framing = emptyTemplateLiteralFraming;
|
|
1067
1502
|
let fixedPartsWidth = 0;
|
|
@@ -1202,6 +1637,18 @@ export const DateIso = /*#__PURE__*/ brand("DateIso", String, (value) => value.l
|
|
|
1202
1637
|
/**
|
|
1203
1638
|
* Safely transforms a {@link Date} into a canonical {@link DateIso}.
|
|
1204
1639
|
*
|
|
1640
|
+
* ### Example
|
|
1641
|
+
*
|
|
1642
|
+
* ```ts
|
|
1643
|
+
* import { DateIsoFromDate } from "@evolu/common";
|
|
1644
|
+
*
|
|
1645
|
+
* const date = new globalThis.Date("2025-01-01T12:00:00.000Z");
|
|
1646
|
+
* const result = DateIsoFromDate.fromUnknown(date);
|
|
1647
|
+
*
|
|
1648
|
+
* expectOk(result, "2025-01-01T12:00:00.000Z");
|
|
1649
|
+
* expect(DateIsoFromDate.to(result.value)).toEqual(date);
|
|
1650
|
+
* ```
|
|
1651
|
+
*
|
|
1205
1652
|
* @group String
|
|
1206
1653
|
*/
|
|
1207
1654
|
export const DateIsoFromDate = /*#__PURE__*/ transform("DateIsoFromDate", Date, DateIso, {
|
|
@@ -1265,6 +1712,16 @@ export const CapitalizedString = /*#__PURE__*/ capitalized(String);
|
|
|
1265
1712
|
/**
|
|
1266
1713
|
* String {@link Brand} without surrounding whitespace.
|
|
1267
1714
|
*
|
|
1715
|
+
* ### Example
|
|
1716
|
+
*
|
|
1717
|
+
* ```ts
|
|
1718
|
+
* import { String, trimmed } from "@evolu/common";
|
|
1719
|
+
*
|
|
1720
|
+
* const Trimmed = trimmed(String);
|
|
1721
|
+
*
|
|
1722
|
+
* expectOk(Trimmed.fromUnknown("Evolu"), "Evolu");
|
|
1723
|
+
* ```
|
|
1724
|
+
*
|
|
1268
1725
|
* @group String
|
|
1269
1726
|
*/
|
|
1270
1727
|
export const trimmed = (parent) => brand("Trimmed", parent, (value) => value === value.trim()
|
|
@@ -1285,11 +1742,31 @@ export const TrimmedString = /*#__PURE__*/ trimmed(String);
|
|
|
1285
1742
|
/**
|
|
1286
1743
|
* Trims a string and returns a {@link TrimmedString}.
|
|
1287
1744
|
*
|
|
1745
|
+
* ### Example
|
|
1746
|
+
*
|
|
1747
|
+
* ```ts
|
|
1748
|
+
* import { trim } from "@evolu/common";
|
|
1749
|
+
*
|
|
1750
|
+
* expect(trim(" Evolu ")).toBe("Evolu");
|
|
1751
|
+
* ```
|
|
1752
|
+
*
|
|
1288
1753
|
* @group String
|
|
1289
1754
|
*/
|
|
1290
1755
|
export const trim = (value) => value.trim();
|
|
1291
1756
|
/**
|
|
1292
|
-
* Minimum-length {@link Brand}
|
|
1757
|
+
* Minimum-length {@link Brand} for values whose `length` is at least `min`.
|
|
1758
|
+
*
|
|
1759
|
+
* ### Example
|
|
1760
|
+
*
|
|
1761
|
+
* ```ts
|
|
1762
|
+
* import { String, array, minLength } from "@evolu/common";
|
|
1763
|
+
*
|
|
1764
|
+
* const AtLeastThreeCharacters = minLength(3)(String);
|
|
1765
|
+
* const AtLeastTwoItems = minLength(2)(array(String));
|
|
1766
|
+
*
|
|
1767
|
+
* expectOk(AtLeastThreeCharacters.fromUnknown("abc"), "abc");
|
|
1768
|
+
* expectOk(AtLeastTwoItems.fromUnknown(["a", "b"]), ["a", "b"]);
|
|
1769
|
+
* ```
|
|
1293
1770
|
*
|
|
1294
1771
|
* @group String
|
|
1295
1772
|
* @group Collection
|
|
@@ -1316,7 +1793,19 @@ export const minLength = (min) => (parent) => {
|
|
|
1316
1793
|
*/
|
|
1317
1794
|
export const NonEmptyTrimmedString = /*#__PURE__*/ minLength(1)(TrimmedString);
|
|
1318
1795
|
/**
|
|
1319
|
-
* Maximum-length {@link Brand}
|
|
1796
|
+
* Maximum-length {@link Brand} for values whose `length` is at most `max`.
|
|
1797
|
+
*
|
|
1798
|
+
* ### Example
|
|
1799
|
+
*
|
|
1800
|
+
* ```ts
|
|
1801
|
+
* import { String, array, maxLength } from "@evolu/common";
|
|
1802
|
+
*
|
|
1803
|
+
* const AtMostThreeCharacters = maxLength(3)(String);
|
|
1804
|
+
* const AtMostTwoItems = maxLength(2)(array(String));
|
|
1805
|
+
*
|
|
1806
|
+
* expectOk(AtMostThreeCharacters.fromUnknown("abc"), "abc");
|
|
1807
|
+
* expectOk(AtMostTwoItems.fromUnknown(["a", "b"]), ["a", "b"]);
|
|
1808
|
+
* ```
|
|
1320
1809
|
*
|
|
1321
1810
|
* @group String
|
|
1322
1811
|
* @group Collection
|
|
@@ -1340,7 +1829,19 @@ export const NonEmptyTrimmedString100 = /*#__PURE__*/ maxLength(100)(NonEmptyTri
|
|
|
1340
1829
|
*/
|
|
1341
1830
|
export const NonEmptyTrimmedString1000 = /*#__PURE__*/ maxLength(1000)(NonEmptyTrimmedString);
|
|
1342
1831
|
/**
|
|
1343
|
-
* Exact-length {@link Brand}
|
|
1832
|
+
* Exact-length {@link Brand} for values whose `length` equals `exact`.
|
|
1833
|
+
*
|
|
1834
|
+
* ### Example
|
|
1835
|
+
*
|
|
1836
|
+
* ```ts
|
|
1837
|
+
* import { String, array, length } from "@evolu/common";
|
|
1838
|
+
*
|
|
1839
|
+
* const ThreeCharacters = length(3)(String);
|
|
1840
|
+
* const TwoItems = length(2)(array(String));
|
|
1841
|
+
*
|
|
1842
|
+
* expectOk(ThreeCharacters.fromUnknown("abc"), "abc");
|
|
1843
|
+
* expectOk(TwoItems.fromUnknown(["a", "b"]), ["a", "b"]);
|
|
1844
|
+
* ```
|
|
1344
1845
|
*
|
|
1345
1846
|
* @group String
|
|
1346
1847
|
* @group Collection
|
|
@@ -1454,12 +1955,34 @@ export const Base64Url = /*#__PURE__*/ brand("Base64Url", String, (value) => {
|
|
|
1454
1955
|
/**
|
|
1455
1956
|
* Encodes bytes as {@link Base64Url}.
|
|
1456
1957
|
*
|
|
1958
|
+
* ### Example
|
|
1959
|
+
*
|
|
1960
|
+
* ```ts
|
|
1961
|
+
* import { uint8ArrayToBase64Url } from "@evolu/common";
|
|
1962
|
+
*
|
|
1963
|
+
* expect(uint8ArrayToBase64Url(new Uint8Array([0, 1, 2, 255]))).toBe(
|
|
1964
|
+
* "AAEC_w",
|
|
1965
|
+
* );
|
|
1966
|
+
* ```
|
|
1967
|
+
*
|
|
1457
1968
|
* @group String
|
|
1458
1969
|
*/
|
|
1459
1970
|
export const uint8ArrayToBase64Url = (bytes) => uint8ArrayToBase64UrlString(bytes);
|
|
1460
1971
|
/**
|
|
1461
1972
|
* Decodes {@link Base64Url} as bytes.
|
|
1462
1973
|
*
|
|
1974
|
+
* ### Example
|
|
1975
|
+
*
|
|
1976
|
+
* ```ts
|
|
1977
|
+
* import { Base64Url, base64UrlToUint8Array } from "@evolu/common";
|
|
1978
|
+
*
|
|
1979
|
+
* const value = Base64Url.orThrow("AAEC_w");
|
|
1980
|
+
*
|
|
1981
|
+
* expect(base64UrlToUint8Array(value)).toEqual(
|
|
1982
|
+
* new Uint8Array([0, 1, 2, 255]),
|
|
1983
|
+
* );
|
|
1984
|
+
* ```
|
|
1985
|
+
*
|
|
1463
1986
|
* @group String
|
|
1464
1987
|
*/
|
|
1465
1988
|
export const base64UrlToUint8Array = (value) => base64UrlStringToUint8Array(value);
|
|
@@ -1489,7 +2012,7 @@ export const SimplePassword = /*#__PURE__*/ brand("SimplePassword",
|
|
|
1489
2012
|
*/
|
|
1490
2013
|
export const Mnemonic = /*#__PURE__*/ brand("Mnemonic", NonEmptyTrimmedString, (value) => bip39.validateMnemonic(value, wordlist)
|
|
1491
2014
|
? ok()
|
|
1492
|
-
: err({ type: "Mnemonic", value }), (error) => `The value ${safelyStringifyUnknownValue(error.value)} is not a valid BIP39 mnemonic.`);
|
|
2015
|
+
: err({ type: "Mnemonic", value }), (error) => `The value ${safelyStringifyUnknownValue(error.value)} is not a valid English BIP39 mnemonic.`);
|
|
1493
2016
|
/**
|
|
1494
2017
|
* Evolu Id: 16 bytes encoded as a 22-character {@link Base64Url}.
|
|
1495
2018
|
*
|
|
@@ -1509,6 +2032,22 @@ export const Id = /*#__PURE__*/ brand("Id", String, (value) => value.length ===
|
|
|
1509
2032
|
* This is the recommended default because it does not encode creation time.
|
|
1510
2033
|
* Pass a Brand name when the returned Id belongs to one domain entity.
|
|
1511
2034
|
*
|
|
2035
|
+
* ### Example
|
|
2036
|
+
*
|
|
2037
|
+
* ```ts
|
|
2038
|
+
* import {
|
|
2039
|
+
* Id,
|
|
2040
|
+
* createId,
|
|
2041
|
+
* createRandomBytes,
|
|
2042
|
+
* type Brand,
|
|
2043
|
+
* } from "@evolu/common";
|
|
2044
|
+
*
|
|
2045
|
+
* const userId = createId<"User">({ randomBytes: createRandomBytes() });
|
|
2046
|
+
*
|
|
2047
|
+
* expect(Id.is(userId)).toBe(true);
|
|
2048
|
+
* expectTypeOf(userId).toEqualTypeOf<Id & Brand<"User">>();
|
|
2049
|
+
* ```
|
|
2050
|
+
*
|
|
1512
2051
|
* @group String
|
|
1513
2052
|
*/
|
|
1514
2053
|
export const createId = (deps, ..._validation) => uint8ArrayToBase64Url(deps.randomBytes.create(16));
|
|
@@ -1543,6 +2082,26 @@ export const createIdFromString = (value, ..._validation) => idBytesToId(sha256(
|
|
|
1543
2082
|
* logs, URLs, and exports. Prefer {@link createId} unless that tradeoff is
|
|
1544
2083
|
* deliberate.
|
|
1545
2084
|
*
|
|
2085
|
+
* ### Example
|
|
2086
|
+
*
|
|
2087
|
+
* ```ts
|
|
2088
|
+
* import {
|
|
2089
|
+
* createIdAsUuidv7,
|
|
2090
|
+
* createRandomBytes,
|
|
2091
|
+
* createTime,
|
|
2092
|
+
* idToIdBytes,
|
|
2093
|
+
* } from "@evolu/common";
|
|
2094
|
+
*
|
|
2095
|
+
* const value = createIdAsUuidv7({
|
|
2096
|
+
* randomBytes: createRandomBytes(),
|
|
2097
|
+
* time: createTime(),
|
|
2098
|
+
* });
|
|
2099
|
+
* const bytes = idToIdBytes(value);
|
|
2100
|
+
*
|
|
2101
|
+
* expect(bytes[6] >> 4).toBe(0x7);
|
|
2102
|
+
* expect(bytes[8] & 0xc0).toBe(0x80);
|
|
2103
|
+
* ```
|
|
2104
|
+
*
|
|
1546
2105
|
* @group String
|
|
1547
2106
|
*/
|
|
1548
2107
|
export const createIdAsUuidv7 = (deps, ..._validation) => {
|
|
@@ -1561,6 +2120,22 @@ export const createIdAsUuidv7 = (deps, ..._validation) => {
|
|
|
1561
2120
|
/**
|
|
1562
2121
|
* Table-specific {@link Id} Type.
|
|
1563
2122
|
*
|
|
2123
|
+
* ### Example
|
|
2124
|
+
*
|
|
2125
|
+
* ```ts
|
|
2126
|
+
* import {
|
|
2127
|
+
* createIdFromString,
|
|
2128
|
+
* id,
|
|
2129
|
+
* type Brand,
|
|
2130
|
+
* type Id,
|
|
2131
|
+
* } from "@evolu/common";
|
|
2132
|
+
*
|
|
2133
|
+
* const TodoId = id("Todo");
|
|
2134
|
+
* const todoId = TodoId.orThrow(createIdFromString("todo"));
|
|
2135
|
+
*
|
|
2136
|
+
* expectTypeOf(todoId).toEqualTypeOf<Id & Brand<"Todo">>();
|
|
2137
|
+
* ```
|
|
2138
|
+
*
|
|
1564
2139
|
* @group String
|
|
1565
2140
|
*/
|
|
1566
2141
|
export const id = (table) => {
|
|
@@ -1576,11 +2151,47 @@ export const id = (table) => {
|
|
|
1576
2151
|
*/
|
|
1577
2152
|
export const IdBytes = /*#__PURE__*/ brand("IdBytes",
|
|
1578
2153
|
/*#__PURE__*/ length(16)(Uint8Array));
|
|
1579
|
-
/**
|
|
2154
|
+
/**
|
|
2155
|
+
* Byte length of an {@link IdBytes} value.
|
|
2156
|
+
*
|
|
2157
|
+
* @group String
|
|
2158
|
+
*/
|
|
1580
2159
|
export const idBytesTypeValueLength = 16;
|
|
1581
|
-
/**
|
|
2160
|
+
/**
|
|
2161
|
+
* Converts an {@link Id} to {@link IdBytes}.
|
|
2162
|
+
*
|
|
2163
|
+
* ### Example
|
|
2164
|
+
*
|
|
2165
|
+
* ```ts
|
|
2166
|
+
* import { createIdFromString, idToIdBytes } from "@evolu/common";
|
|
2167
|
+
*
|
|
2168
|
+
* const bytes = idToIdBytes(createIdFromString("todo"));
|
|
2169
|
+
*
|
|
2170
|
+
* expect(bytes).toHaveLength(16);
|
|
2171
|
+
* ```
|
|
2172
|
+
*
|
|
2173
|
+
* @group String
|
|
2174
|
+
*/
|
|
1582
2175
|
export const idToIdBytes = (value) => base64UrlToUint8Array(value);
|
|
1583
|
-
/**
|
|
2176
|
+
/**
|
|
2177
|
+
* Converts {@link IdBytes} to an {@link Id}.
|
|
2178
|
+
*
|
|
2179
|
+
* ### Example
|
|
2180
|
+
*
|
|
2181
|
+
* ```ts
|
|
2182
|
+
* import {
|
|
2183
|
+
* createIdFromString,
|
|
2184
|
+
* idBytesToId,
|
|
2185
|
+
* idToIdBytes,
|
|
2186
|
+
* } from "@evolu/common";
|
|
2187
|
+
*
|
|
2188
|
+
* const value = createIdFromString("todo");
|
|
2189
|
+
*
|
|
2190
|
+
* expect(idBytesToId(idToIdBytes(value))).toBe(value);
|
|
2191
|
+
* ```
|
|
2192
|
+
*
|
|
2193
|
+
* @group String
|
|
2194
|
+
*/
|
|
1584
2195
|
export const idBytesToId = (value) => uint8ArrayToBase64Url(value);
|
|
1585
2196
|
/**
|
|
1586
2197
|
* Decimal string representation of a signed {@link Int64}.
|
|
@@ -1606,6 +2217,17 @@ export const Int64String = /*#__PURE__*/ brand("Int64String", NonEmptyTrimmedStr
|
|
|
1606
2217
|
* text, including SQLite queries that cast INTEGER values to TEXT to avoid a
|
|
1607
2218
|
* lossy JavaScript number conversion.
|
|
1608
2219
|
*
|
|
2220
|
+
* ### Example
|
|
2221
|
+
*
|
|
2222
|
+
* ```ts
|
|
2223
|
+
* import { Int64FromInt64String } from "@evolu/common";
|
|
2224
|
+
*
|
|
2225
|
+
* const result = Int64FromInt64String.fromUnknown("9223372036854775807");
|
|
2226
|
+
*
|
|
2227
|
+
* expectOk(result, 9223372036854775807n);
|
|
2228
|
+
* expect(Int64FromInt64String.to(result.value)).toBe("9223372036854775807");
|
|
2229
|
+
* ```
|
|
2230
|
+
*
|
|
1609
2231
|
* @group Number
|
|
1610
2232
|
*/
|
|
1611
2233
|
export const Int64FromInt64String = /*#__PURE__*/ transform("Int64FromInt64String", Int64String, Int64, {
|
|
@@ -1615,6 +2237,16 @@ export const Int64FromInt64String = /*#__PURE__*/ transform("Int64FromInt64Strin
|
|
|
1615
2237
|
/**
|
|
1616
2238
|
* Number {@link Brand} requiring a value greater than or equal to zero.
|
|
1617
2239
|
*
|
|
2240
|
+
* ### Example
|
|
2241
|
+
*
|
|
2242
|
+
* ```ts
|
|
2243
|
+
* import { Number, nonNegative } from "@evolu/common";
|
|
2244
|
+
*
|
|
2245
|
+
* const NonNegative = nonNegative(Number);
|
|
2246
|
+
*
|
|
2247
|
+
* expectOk(NonNegative.fromUnknown(0), 0);
|
|
2248
|
+
* ```
|
|
2249
|
+
*
|
|
1618
2250
|
* @group Number
|
|
1619
2251
|
*/
|
|
1620
2252
|
export const nonNegative = (parent) => brand("NonNegative", parent, (value) => value >= 0 ? ok() : err({ type: "NonNegative", value }), (error) => `The value ${safelyStringifyUnknownValue(error.value)} must be non-negative (>= 0).`);
|
|
@@ -1627,6 +2259,16 @@ export const NonNegativeNumber = /*#__PURE__*/ nonNegative(Number);
|
|
|
1627
2259
|
/**
|
|
1628
2260
|
* Number {@link Brand} requiring a value greater than zero.
|
|
1629
2261
|
*
|
|
2262
|
+
* ### Example
|
|
2263
|
+
*
|
|
2264
|
+
* ```ts
|
|
2265
|
+
* import { Number, positive } from "@evolu/common";
|
|
2266
|
+
*
|
|
2267
|
+
* const Positive = positive(Number);
|
|
2268
|
+
*
|
|
2269
|
+
* expectOk(Positive.fromUnknown(1), 1);
|
|
2270
|
+
* ```
|
|
2271
|
+
*
|
|
1630
2272
|
* @group Number
|
|
1631
2273
|
*/
|
|
1632
2274
|
export const positive = (parent) => brand("Positive", parent, (value) => value > 0 ? ok() : err({ type: "Positive", value }), (error) => `The value ${safelyStringifyUnknownValue(error.value)} must be positive (> 0).`);
|
|
@@ -1642,6 +2284,16 @@ export const PositiveNumber = /*#__PURE__*/ positive(NonNegativeNumber);
|
|
|
1642
2284
|
/**
|
|
1643
2285
|
* Number {@link Brand} requiring a value less than or equal to zero.
|
|
1644
2286
|
*
|
|
2287
|
+
* ### Example
|
|
2288
|
+
*
|
|
2289
|
+
* ```ts
|
|
2290
|
+
* import { Number, nonPositive } from "@evolu/common";
|
|
2291
|
+
*
|
|
2292
|
+
* const NonPositive = nonPositive(Number);
|
|
2293
|
+
*
|
|
2294
|
+
* expectOk(NonPositive.fromUnknown(0), 0);
|
|
2295
|
+
* ```
|
|
2296
|
+
*
|
|
1645
2297
|
* @group Number
|
|
1646
2298
|
*/
|
|
1647
2299
|
export const nonPositive = (parent) => brand("NonPositive", parent, (value) => value <= 0 ? ok() : err({ type: "NonPositive", value }), (error) => `The value ${safelyStringifyUnknownValue(error.value)} must be non-positive (<= 0).`);
|
|
@@ -1654,6 +2306,16 @@ export const NonPositiveNumber = /*#__PURE__*/ nonPositive(Number);
|
|
|
1654
2306
|
/**
|
|
1655
2307
|
* Number {@link Brand} requiring a value less than zero.
|
|
1656
2308
|
*
|
|
2309
|
+
* ### Example
|
|
2310
|
+
*
|
|
2311
|
+
* ```ts
|
|
2312
|
+
* import { Number, negative } from "@evolu/common";
|
|
2313
|
+
*
|
|
2314
|
+
* const Negative = negative(Number);
|
|
2315
|
+
*
|
|
2316
|
+
* expectOk(Negative.fromUnknown(-1), -1);
|
|
2317
|
+
* ```
|
|
2318
|
+
*
|
|
1657
2319
|
* @group Number
|
|
1658
2320
|
*/
|
|
1659
2321
|
export const negative = (parent) => brand("Negative", parent, (value) => value < 0 ? ok() : err({ type: "Negative", value }), (error) => `The value ${safelyStringifyUnknownValue(error.value)} must be negative (< 0).`);
|
|
@@ -1669,6 +2331,16 @@ export const NegativeNumber = /*#__PURE__*/ negative(NonPositiveNumber);
|
|
|
1669
2331
|
/**
|
|
1670
2332
|
* Number {@link Brand} requiring a value other than `NaN`.
|
|
1671
2333
|
*
|
|
2334
|
+
* ### Example
|
|
2335
|
+
*
|
|
2336
|
+
* ```ts
|
|
2337
|
+
* import { Number, nonNaN } from "@evolu/common";
|
|
2338
|
+
*
|
|
2339
|
+
* const NonNaN = nonNaN(Number);
|
|
2340
|
+
*
|
|
2341
|
+
* expectOk(NonNaN.fromUnknown(Infinity), Infinity);
|
|
2342
|
+
* ```
|
|
2343
|
+
*
|
|
1672
2344
|
* @group Number
|
|
1673
2345
|
*/
|
|
1674
2346
|
export const nonNaN = (parent) => brand("NonNaN", parent, (value) => globalThis.Number.isNaN(value)
|
|
@@ -1688,6 +2360,16 @@ export const NonNaNNumber = /*#__PURE__*/ nonNaN(Number);
|
|
|
1688
2360
|
/**
|
|
1689
2361
|
* Number {@link Brand} requiring a finite value.
|
|
1690
2362
|
*
|
|
2363
|
+
* ### Example
|
|
2364
|
+
*
|
|
2365
|
+
* ```ts
|
|
2366
|
+
* import { Number, finite } from "@evolu/common";
|
|
2367
|
+
*
|
|
2368
|
+
* const Finite = finite(Number);
|
|
2369
|
+
*
|
|
2370
|
+
* expectOk(Finite.fromUnknown(42), 42);
|
|
2371
|
+
* ```
|
|
2372
|
+
*
|
|
1691
2373
|
* @group Number
|
|
1692
2374
|
*/
|
|
1693
2375
|
export const finite = (parent) => brand("Finite", parent, (value) => globalThis.Number.isFinite(value)
|
|
@@ -1794,6 +2476,16 @@ export const NegativeInt = /*#__PURE__*/ negative(NonPositiveInt);
|
|
|
1794
2476
|
/**
|
|
1795
2477
|
* Number {@link Brand} requiring a value greater than `min`.
|
|
1796
2478
|
*
|
|
2479
|
+
* ### Example
|
|
2480
|
+
*
|
|
2481
|
+
* ```ts
|
|
2482
|
+
* import { Number, greaterThan } from "@evolu/common";
|
|
2483
|
+
*
|
|
2484
|
+
* const GreaterThanTen = greaterThan(10)(Number);
|
|
2485
|
+
*
|
|
2486
|
+
* expectOk(GreaterThanTen.fromUnknown(11), 11);
|
|
2487
|
+
* ```
|
|
2488
|
+
*
|
|
1797
2489
|
* @group Number
|
|
1798
2490
|
*/
|
|
1799
2491
|
export const greaterThan = (min) => (parent) => {
|
|
@@ -1805,6 +2497,16 @@ export const greaterThan = (min) => (parent) => {
|
|
|
1805
2497
|
/**
|
|
1806
2498
|
* Number {@link Brand} requiring a value greater than or equal to `min`.
|
|
1807
2499
|
*
|
|
2500
|
+
* ### Example
|
|
2501
|
+
*
|
|
2502
|
+
* ```ts
|
|
2503
|
+
* import { Number, greaterThanOrEqualTo } from "@evolu/common";
|
|
2504
|
+
*
|
|
2505
|
+
* const AtLeastTen = greaterThanOrEqualTo(10)(Number);
|
|
2506
|
+
*
|
|
2507
|
+
* expectOk(AtLeastTen.fromUnknown(10), 10);
|
|
2508
|
+
* ```
|
|
2509
|
+
*
|
|
1808
2510
|
* @group Number
|
|
1809
2511
|
*/
|
|
1810
2512
|
export const greaterThanOrEqualTo = (min) => (parent) => {
|
|
@@ -1820,6 +2522,16 @@ export const greaterThanOrEqualTo = (min) => (parent) => {
|
|
|
1820
2522
|
/**
|
|
1821
2523
|
* Number {@link Brand} requiring a value less than `max`.
|
|
1822
2524
|
*
|
|
2525
|
+
* ### Example
|
|
2526
|
+
*
|
|
2527
|
+
* ```ts
|
|
2528
|
+
* import { Number, lessThan } from "@evolu/common";
|
|
2529
|
+
*
|
|
2530
|
+
* const LessThanTen = lessThan(10)(Number);
|
|
2531
|
+
*
|
|
2532
|
+
* expectOk(LessThanTen.fromUnknown(9), 9);
|
|
2533
|
+
* ```
|
|
2534
|
+
*
|
|
1823
2535
|
* @group Number
|
|
1824
2536
|
*/
|
|
1825
2537
|
export const lessThan = (max) => (parent) => {
|
|
@@ -1838,6 +2550,16 @@ export const Age = /*#__PURE__*/ brand("Age",
|
|
|
1838
2550
|
/**
|
|
1839
2551
|
* Number {@link Brand} requiring a value less than or equal to `max`.
|
|
1840
2552
|
*
|
|
2553
|
+
* ### Example
|
|
2554
|
+
*
|
|
2555
|
+
* ```ts
|
|
2556
|
+
* import { Number, lessThanOrEqualTo } from "@evolu/common";
|
|
2557
|
+
*
|
|
2558
|
+
* const AtMostTen = lessThanOrEqualTo(10)(Number);
|
|
2559
|
+
*
|
|
2560
|
+
* expectOk(AtMostTen.fromUnknown(10), 10);
|
|
2561
|
+
* ```
|
|
2562
|
+
*
|
|
1841
2563
|
* @group Number
|
|
1842
2564
|
*/
|
|
1843
2565
|
export const lessThanOrEqualTo = (max) => (parent) => {
|
|
@@ -1902,6 +2624,16 @@ export const DecimalString = /*#__PURE__*/ brand("DecimalString", String, (value
|
|
|
1902
2624
|
/**
|
|
1903
2625
|
* {@link DecimalString} Brand requiring a value greater than or equal to zero.
|
|
1904
2626
|
*
|
|
2627
|
+
* ### Example
|
|
2628
|
+
*
|
|
2629
|
+
* ```ts
|
|
2630
|
+
* import { DecimalString, nonNegativeDecimalString } from "@evolu/common";
|
|
2631
|
+
*
|
|
2632
|
+
* const NonNegative = nonNegativeDecimalString(DecimalString);
|
|
2633
|
+
*
|
|
2634
|
+
* expectOk(NonNegative.fromUnknown("0.5"), "0.5");
|
|
2635
|
+
* ```
|
|
2636
|
+
*
|
|
1905
2637
|
* @group Number
|
|
1906
2638
|
*/
|
|
1907
2639
|
export const nonNegativeDecimalString = (parent) => brand("NonNegativeDecimalString", parent, (value) => value[0] !== "-"
|
|
@@ -1920,6 +2652,16 @@ export const NonNegativeDecimalString =
|
|
|
1920
2652
|
/**
|
|
1921
2653
|
* {@link DecimalString} Brand requiring a value greater than zero.
|
|
1922
2654
|
*
|
|
2655
|
+
* ### Example
|
|
2656
|
+
*
|
|
2657
|
+
* ```ts
|
|
2658
|
+
* import { DecimalString, positiveDecimalString } from "@evolu/common";
|
|
2659
|
+
*
|
|
2660
|
+
* const Positive = positiveDecimalString(DecimalString);
|
|
2661
|
+
*
|
|
2662
|
+
* expectOk(Positive.fromUnknown("0.5"), "0.5");
|
|
2663
|
+
* ```
|
|
2664
|
+
*
|
|
1923
2665
|
* @group Number
|
|
1924
2666
|
*/
|
|
1925
2667
|
export const positiveDecimalString = (parent) => brand("PositiveDecimalString", parent, (value) => value !== "0" && value[0] !== "-"
|
|
@@ -1940,6 +2682,16 @@ export const PositiveDecimalString = /*#__PURE__*/ positiveDecimalString(NonNega
|
|
|
1940
2682
|
/**
|
|
1941
2683
|
* {@link DecimalString} Brand requiring a value less than or equal to zero.
|
|
1942
2684
|
*
|
|
2685
|
+
* ### Example
|
|
2686
|
+
*
|
|
2687
|
+
* ```ts
|
|
2688
|
+
* import { DecimalString, nonPositiveDecimalString } from "@evolu/common";
|
|
2689
|
+
*
|
|
2690
|
+
* const NonPositive = nonPositiveDecimalString(DecimalString);
|
|
2691
|
+
*
|
|
2692
|
+
* expectOk(NonPositive.fromUnknown("-0.5"), "-0.5");
|
|
2693
|
+
* ```
|
|
2694
|
+
*
|
|
1943
2695
|
* @group Number
|
|
1944
2696
|
*/
|
|
1945
2697
|
export const nonPositiveDecimalString = (parent) => brand("NonPositiveDecimalString", parent, (value) => value === "0" || value[0] === "-"
|
|
@@ -1958,6 +2710,16 @@ export const NonPositiveDecimalString =
|
|
|
1958
2710
|
/**
|
|
1959
2711
|
* {@link DecimalString} Brand requiring a value less than zero.
|
|
1960
2712
|
*
|
|
2713
|
+
* ### Example
|
|
2714
|
+
*
|
|
2715
|
+
* ```ts
|
|
2716
|
+
* import { DecimalString, negativeDecimalString } from "@evolu/common";
|
|
2717
|
+
*
|
|
2718
|
+
* const Negative = negativeDecimalString(DecimalString);
|
|
2719
|
+
*
|
|
2720
|
+
* expectOk(Negative.fromUnknown("-0.5"), "-0.5");
|
|
2721
|
+
* ```
|
|
2722
|
+
*
|
|
1961
2723
|
* @group Number
|
|
1962
2724
|
*/
|
|
1963
2725
|
export const negativeDecimalString = (parent) => brand("NegativeDecimalString", parent, (value) => value[0] === "-"
|
|
@@ -2051,6 +2813,16 @@ const decimalStringToParts = (value) => {
|
|
|
2051
2813
|
/**
|
|
2052
2814
|
* Number {@link Brand} requiring a value within an inclusive range.
|
|
2053
2815
|
*
|
|
2816
|
+
* ### Example
|
|
2817
|
+
*
|
|
2818
|
+
* ```ts
|
|
2819
|
+
* import { Number, between } from "@evolu/common";
|
|
2820
|
+
*
|
|
2821
|
+
* const Percentage = between(0, 100)(Number);
|
|
2822
|
+
*
|
|
2823
|
+
* expectOk(Percentage.fromUnknown(75), 75);
|
|
2824
|
+
* ```
|
|
2825
|
+
*
|
|
2054
2826
|
* @group Number
|
|
2055
2827
|
*/
|
|
2056
2828
|
export const between = (min, max) => (parent) => {
|
|
@@ -2152,20 +2924,6 @@ const isArrayCollection = (value, isElement) => {
|
|
|
2152
2924
|
}
|
|
2153
2925
|
return true;
|
|
2154
2926
|
};
|
|
2155
|
-
const formatArrayError = (error) => {
|
|
2156
|
-
if (error.reason.kind === "NotArray") {
|
|
2157
|
-
return `A value ${safelyStringifyUnknownValue(error.reason.value)} is not an array.`;
|
|
2158
|
-
}
|
|
2159
|
-
const issue = error.reason.issues[0];
|
|
2160
|
-
switch (issue.kind) {
|
|
2161
|
-
case "Hole":
|
|
2162
|
-
return `An array element at index ${issue.index} is missing.`;
|
|
2163
|
-
case "Accessor":
|
|
2164
|
-
return `An array element at index ${issue.index} must be a data property.`;
|
|
2165
|
-
case "ExcessProperty":
|
|
2166
|
-
return "An excess Array property is not allowed. Remove it or use a different Type.";
|
|
2167
|
-
}
|
|
2168
|
-
};
|
|
2169
2927
|
const arrayRuntimeConfig = {
|
|
2170
2928
|
name: "Array",
|
|
2171
2929
|
typeByElement: arrayTypeByElement,
|
|
@@ -2173,7 +2931,22 @@ const arrayRuntimeConfig = {
|
|
|
2173
2931
|
validateItems: (value, validateElement, options) => validateArrayItems(value, validateElement, options, false),
|
|
2174
2932
|
encode: encodeArrayCollection,
|
|
2175
2933
|
is: isArrayCollection,
|
|
2176
|
-
formatError:
|
|
2934
|
+
formatError: ((error) => {
|
|
2935
|
+
if (error.reason.kind === "NotArray") {
|
|
2936
|
+
return `A value ${safelyStringifyUnknownValue(error.reason.value)} is not an array.`;
|
|
2937
|
+
}
|
|
2938
|
+
const issue = error.reason.issues[0];
|
|
2939
|
+
switch (issue.kind) {
|
|
2940
|
+
case "Hole":
|
|
2941
|
+
return `An array element at index ${issue.index} is missing.`;
|
|
2942
|
+
case "Accessor":
|
|
2943
|
+
return `An array element at index ${issue.index} must be a data property.`;
|
|
2944
|
+
case "ExcessProperty":
|
|
2945
|
+
return "An excess Array property is not allowed. Remove it or use a different Type.";
|
|
2946
|
+
case "Element":
|
|
2947
|
+
return `An array element at index ${issue.index} is invalid.`;
|
|
2948
|
+
}
|
|
2949
|
+
}),
|
|
2177
2950
|
};
|
|
2178
2951
|
const validateArrayItems = (value, validate, options, checkStructure) => validateIndexedArrayItems("Array", value, validate, options, checkStructure);
|
|
2179
2952
|
// Array and Tuple share their structural and element validation traversal.
|
|
@@ -2258,6 +3031,17 @@ const copyArrayPrefix = (value, endIndex) => {
|
|
|
2258
3031
|
* iteration order. Classification uses the realm-neutral object tag and
|
|
2259
3032
|
* prototype structure under Evolu Type's trusted JavaScript policy.
|
|
2260
3033
|
*
|
|
3034
|
+
* ### Example
|
|
3035
|
+
*
|
|
3036
|
+
* ```ts
|
|
3037
|
+
* import { String, set } from "@evolu/common";
|
|
3038
|
+
*
|
|
3039
|
+
* const Tags = set(String);
|
|
3040
|
+
* const tags = new Set(["local-first", "offline"]);
|
|
3041
|
+
*
|
|
3042
|
+
* expectOk(Tags.fromUnknown(tags), tags);
|
|
3043
|
+
* ```
|
|
3044
|
+
*
|
|
2261
3045
|
* @group Collection
|
|
2262
3046
|
*/
|
|
2263
3047
|
export const set = (element) => createHomogeneousCollectionType(element, setRuntimeConfig);
|
|
@@ -2331,16 +3115,6 @@ const isSetCollection = (value, isElement) => {
|
|
|
2331
3115
|
}
|
|
2332
3116
|
return true;
|
|
2333
3117
|
};
|
|
2334
|
-
const formatSetError = (error) => {
|
|
2335
|
-
if (error.reason.kind === "NotSet") {
|
|
2336
|
-
return `A value ${safelyStringifyUnknownValue(error.reason.value)} is not a Set.`;
|
|
2337
|
-
}
|
|
2338
|
-
if (error.reason.kind === "UnexpectedPrototype") {
|
|
2339
|
-
return "The value is a Set subclass, but a Set Output must be a direct Set.";
|
|
2340
|
-
}
|
|
2341
|
-
const issue = error.reason.issues[0];
|
|
2342
|
-
return `An excess Set property ${safelyStringifyUnknownValue(issue.key)} is not allowed.`;
|
|
2343
|
-
};
|
|
2344
3118
|
const hasDirectSetPrototype = (value) => {
|
|
2345
3119
|
const prototype = globalThis.Object.getPrototypeOf(value);
|
|
2346
3120
|
if (prototype === null)
|
|
@@ -2356,7 +3130,19 @@ const setRuntimeConfig = {
|
|
|
2356
3130
|
validateItems: (value, validateElement, options) => validateSetItems(value, validateElement, options, false),
|
|
2357
3131
|
encode: encodeSetCollection,
|
|
2358
3132
|
is: isSetCollection,
|
|
2359
|
-
formatError:
|
|
3133
|
+
formatError: ((error) => {
|
|
3134
|
+
if (error.reason.kind === "NotSet")
|
|
3135
|
+
return `A value ${safelyStringifyUnknownValue(error.reason.value)} is not a Set.`;
|
|
3136
|
+
if (error.reason.kind === "UnexpectedPrototype")
|
|
3137
|
+
return "The value is an instance of a Set subclass, but a Set Output must be a direct Set instance.";
|
|
3138
|
+
const issue = error.reason.issues[0];
|
|
3139
|
+
switch (issue.kind) {
|
|
3140
|
+
case "ExcessProperty":
|
|
3141
|
+
return `An excess Set property ${safelyStringifyUnknownValue(issue.key)} is not allowed.`;
|
|
3142
|
+
case "Element":
|
|
3143
|
+
return `A Set element at index ${issue.index} is invalid.`;
|
|
3144
|
+
}
|
|
3145
|
+
}),
|
|
2360
3146
|
};
|
|
2361
3147
|
const validateSetItems = (value, validate, options, checkStructure) => {
|
|
2362
3148
|
let issues;
|
|
@@ -2421,12 +3207,10 @@ const createTupleType = (typeElements) => {
|
|
|
2421
3207
|
const fromUnknown = (value, options = firstValidationOptions) => validate(value, (element, item, elementOptions) => element.fromUnknown(item, elementOptions), options);
|
|
2422
3208
|
const validateOutput = (value, options = firstValidationOptions) => validate(value, (element, item, elementOptions) => element[outputValidationSymbol](item, elementOptions), options);
|
|
2423
3209
|
const formatError = (error) => {
|
|
2424
|
-
if (error.reason.kind === "NotArray")
|
|
3210
|
+
if (error.reason.kind === "NotArray")
|
|
2425
3211
|
return `A value ${safelyStringifyUnknownValue(error.reason.value)} is not a tuple.`;
|
|
2426
|
-
|
|
2427
|
-
if (error.reason.kind === "InvalidLength") {
|
|
3212
|
+
if (error.reason.kind === "InvalidLength")
|
|
2428
3213
|
return `A Tuple must contain exactly ${error.reason.expected} elements, but the value contains ${error.reason.actual}.`;
|
|
2429
|
-
}
|
|
2430
3214
|
const issue = error.reason.issues[0];
|
|
2431
3215
|
switch (issue.kind) {
|
|
2432
3216
|
case "Hole":
|
|
@@ -2435,6 +3219,8 @@ const createTupleType = (typeElements) => {
|
|
|
2435
3219
|
return `A Tuple element at index ${issue.index} must be a data property.`;
|
|
2436
3220
|
case "ExcessProperty":
|
|
2437
3221
|
return "An excess Tuple property is not allowed. Remove it or use a different Type.";
|
|
3222
|
+
case "Element":
|
|
3223
|
+
return `A Tuple element at index ${issue.index} is invalid.`;
|
|
2438
3224
|
}
|
|
2439
3225
|
};
|
|
2440
3226
|
const rootElements = typeElements.map(getTerminalRuntimeNode);
|
|
@@ -2506,16 +3292,14 @@ export const Digit1To6 = /*#__PURE__*/ union("1", "2", "3", "4", "5", "6");
|
|
|
2506
3292
|
*/
|
|
2507
3293
|
export const Digit1To23 = /*#__PURE__*/ union(Digit1To9,
|
|
2508
3294
|
/*#__PURE__*/ templateLiteral("1", Digit),
|
|
2509
|
-
/*#__PURE__*/ templateLiteral("2",
|
|
2510
|
-
/*#__PURE__*/ union("0", "1", "2", "3")));
|
|
3295
|
+
/*#__PURE__*/ templateLiteral("2", /*#__PURE__*/ union("0", "1", "2", "3")));
|
|
2511
3296
|
/**
|
|
2512
3297
|
* Decimal string from `"1"` to `"51"`.
|
|
2513
3298
|
*
|
|
2514
3299
|
* @group String
|
|
2515
3300
|
*/
|
|
2516
3301
|
export const Digit1To51 = /*#__PURE__*/ union(Digit1To9,
|
|
2517
|
-
/*#__PURE__*/ templateLiteral(
|
|
2518
|
-
/*#__PURE__*/ union("1", "2", "3", "4"), Digit),
|
|
3302
|
+
/*#__PURE__*/ templateLiteral(/*#__PURE__*/ union("1", "2", "3", "4"), Digit),
|
|
2519
3303
|
/*#__PURE__*/ templateLiteral("5", /*#__PURE__*/ union("0", "1")));
|
|
2520
3304
|
/**
|
|
2521
3305
|
* Decimal string from `"1"` to `"99"`.
|
|
@@ -2572,21 +3356,9 @@ const createObjectRuntimeTypeIssues = (defaultFormatter, props, recordType) => (
|
|
|
2572
3356
|
return recordType[getRuntimeTypeIssuesSymbol](propertyError, mode);
|
|
2573
3357
|
});
|
|
2574
3358
|
};
|
|
2575
|
-
const
|
|
2576
|
-
|
|
2577
|
-
|
|
2578
|
-
}
|
|
2579
|
-
const key = Reflect.ownKeys(error.reason.errors).at(0);
|
|
2580
|
-
assertNonNullable(key);
|
|
2581
|
-
const propertyError = error.reason.errors[key];
|
|
2582
|
-
assertNonNullable(propertyError);
|
|
2583
|
-
if (propertyError.type === "ObjectPropertyAccess") {
|
|
2584
|
-
return propertyError.reason === "Accessor"
|
|
2585
|
-
? "An Object property must be a data property. Materialize accessor values into plain data before using this Type or use a different Type."
|
|
2586
|
-
: "An Object property must be enumerable. Make it enumerable or use a different Type.";
|
|
2587
|
-
}
|
|
2588
|
-
return "An Object property key must be a string. Remove it or use a different Type.";
|
|
2589
|
-
};
|
|
3359
|
+
const formatPlainObjectRootError = (reason) => reason.kind === "NotObject"
|
|
3360
|
+
? `A value ${safelyStringifyUnknownValue(reason.value)} is not an object.`
|
|
3361
|
+
: "The value is an object, but an Object Output must be a plain object or have a null prototype.";
|
|
2590
3362
|
/**
|
|
2591
3363
|
* A {@link Type} for readonly plain objects with unknown property values.
|
|
2592
3364
|
*
|
|
@@ -2601,7 +3373,7 @@ const formatPlainObjectError = (error) => {
|
|
|
2601
3373
|
* values have their own Types, and {@link instanceOf} when an instance belongs
|
|
2602
3374
|
* to the domain.
|
|
2603
3375
|
*
|
|
2604
|
-
* @group Base
|
|
3376
|
+
* @group Base
|
|
2605
3377
|
*/
|
|
2606
3378
|
export const Object = /*#__PURE__*/ createRootType("Object", (value, options = firstValidationOptions) => {
|
|
2607
3379
|
if (value === null || typeof value !== "object") {
|
|
@@ -2651,15 +3423,56 @@ export const Object = /*#__PURE__*/ createRootType("Object", (value, options = f
|
|
|
2651
3423
|
type: "Object",
|
|
2652
3424
|
reason: { kind: "Properties", errors },
|
|
2653
3425
|
});
|
|
2654
|
-
},
|
|
2655
|
-
|
|
3426
|
+
}, (error) => {
|
|
3427
|
+
if (error.reason.kind !== "Properties")
|
|
3428
|
+
return formatPlainObjectRootError(error.reason);
|
|
3429
|
+
const key = Reflect.ownKeys(error.reason.errors).at(0);
|
|
3430
|
+
assertNonNullable(key);
|
|
3431
|
+
const propertyError = error.reason.errors[key];
|
|
3432
|
+
assertNonNullable(propertyError);
|
|
3433
|
+
if (propertyError.type === "ObjectPropertyAccess") {
|
|
3434
|
+
switch (propertyError.reason) {
|
|
3435
|
+
case "Accessor":
|
|
3436
|
+
return "An Object property must be a data property. Materialize accessor values into plain data before using this Type or use a different Type.";
|
|
3437
|
+
case "NonEnumerable":
|
|
3438
|
+
return "An Object property must be enumerable. Make it enumerable or use a different Type.";
|
|
3439
|
+
}
|
|
3440
|
+
}
|
|
3441
|
+
if (propertyError.type === "ObjectMissingProperty")
|
|
3442
|
+
return `The required property ${safelyStringifyUnknownValue(key)} is missing.`;
|
|
3443
|
+
if (typeof key === "symbol")
|
|
3444
|
+
return "An Object property key must be a string. Remove the symbol property or use a different Type.";
|
|
3445
|
+
if (propertyError.type === "ObjectExcessProperty")
|
|
3446
|
+
return `The property ${safelyStringifyUnknownValue(key)} is not allowed. Remove it or use a different Type.`;
|
|
3447
|
+
return `The property ${safelyStringifyUnknownValue(key)} is invalid.`;
|
|
3448
|
+
},
|
|
3449
|
+
/*#__PURE__*/ createObjectRuntimeTypeIssues(((error) => {
|
|
3450
|
+
if (error.reason.kind !== "Properties")
|
|
3451
|
+
return formatPlainObjectRootError(error.reason);
|
|
3452
|
+
const key = Reflect.ownKeys(error.reason.errors).at(0);
|
|
3453
|
+
assertNonNullable(key);
|
|
3454
|
+
const propertyError = error.reason.errors[key];
|
|
3455
|
+
assertNonNullable(propertyError);
|
|
3456
|
+
if (propertyError.type === "ObjectPropertyAccess") {
|
|
3457
|
+
switch (propertyError.reason) {
|
|
3458
|
+
case "Accessor":
|
|
3459
|
+
return "An Object property must be a data property. Materialize accessor values into plain data before using this Type or use a different Type.";
|
|
3460
|
+
case "NonEnumerable":
|
|
3461
|
+
return "An Object property must be enumerable. Make it enumerable or use a different Type.";
|
|
3462
|
+
}
|
|
3463
|
+
}
|
|
3464
|
+
if (propertyError.type === "ObjectMissingProperty")
|
|
3465
|
+
return `The required property ${safelyStringifyUnknownValue(key)} is missing.`;
|
|
3466
|
+
if (typeof key === "symbol")
|
|
3467
|
+
return "An Object property key must be a string. Remove the symbol property or use a different Type.";
|
|
3468
|
+
if (propertyError.type === "ObjectExcessProperty")
|
|
3469
|
+
return `The property ${safelyStringifyUnknownValue(key)} is not allowed. Remove it or use a different Type.`;
|
|
3470
|
+
return `The property ${safelyStringifyUnknownValue(key)} is invalid.`;
|
|
3471
|
+
})));
|
|
2656
3472
|
const isPlainObject = (value) => {
|
|
2657
3473
|
const prototype = globalThis.Object.getPrototypeOf(value);
|
|
2658
3474
|
return (prototype === null || globalThis.Object.getPrototypeOf(prototype) === null);
|
|
2659
3475
|
};
|
|
2660
|
-
const formatPlainObjectRootError = (reason) => reason.kind === "NotObject"
|
|
2661
|
-
? `A value ${safelyStringifyUnknownValue(reason.value)} is not an object.`
|
|
2662
|
-
: "The value is an object, but an Object Output must be a plain object or have a null prototype.";
|
|
2663
3476
|
/**
|
|
2664
3477
|
* Record {@link Type}.
|
|
2665
3478
|
*
|
|
@@ -2690,20 +3503,13 @@ const formatPlainObjectRootError = (reason) => reason.kind === "NotObject"
|
|
|
2690
3503
|
*
|
|
2691
3504
|
* ```ts
|
|
2692
3505
|
* import {
|
|
2693
|
-
*
|
|
3506
|
+
* Int64FromInt64String,
|
|
2694
3507
|
* String,
|
|
2695
|
-
* ok,
|
|
2696
3508
|
* record,
|
|
2697
|
-
*
|
|
2698
|
-
* type Result,
|
|
3509
|
+
* type Int64,
|
|
2699
3510
|
* } from "@evolu/common";
|
|
2700
3511
|
*
|
|
2701
|
-
* const
|
|
2702
|
-
* from: (value) => ok(globalThis.Number(value)),
|
|
2703
|
-
* to: globalThis.String,
|
|
2704
|
-
* });
|
|
2705
|
-
*
|
|
2706
|
-
* const ScoresByUser = record(String, ScoreFromString);
|
|
3512
|
+
* const ScoresByUser = record(String, Int64FromInt64String);
|
|
2707
3513
|
* type ScoresByUser = typeof ScoresByUser.Output;
|
|
2708
3514
|
*
|
|
2709
3515
|
* // Validate an unknown value.
|
|
@@ -2712,7 +3518,7 @@ const formatPlainObjectRootError = (reason) => reason.kind === "NotObject"
|
|
|
2712
3518
|
* grace: "20",
|
|
2713
3519
|
* });
|
|
2714
3520
|
*
|
|
2715
|
-
* expectOk(scoresFromUnknown, { ada:
|
|
3521
|
+
* expectOk(scoresFromUnknown, { ada: 10n, grace: 20n });
|
|
2716
3522
|
*
|
|
2717
3523
|
* // Validate keys and values with their root Types.
|
|
2718
3524
|
* const scoresInput = ScoresByUser.parent.fromUnknown({
|
|
@@ -2724,11 +3530,10 @@ const formatPlainObjectRootError = (reason) => reason.kind === "NotObject"
|
|
|
2724
3530
|
* // Run the remaining key and value stages.
|
|
2725
3531
|
* const scoresFromInput = ScoresByUser.from.parent(scoresInput.value);
|
|
2726
3532
|
*
|
|
2727
|
-
*
|
|
2728
|
-
*
|
|
3533
|
+
* expectOk(scoresFromInput, { ada: 10n, grace: 20n });
|
|
3534
|
+
* expectTypeOf(scoresFromInput.value).toEqualTypeOf<
|
|
3535
|
+
* Readonly<Partial<Record<string, Int64>>>
|
|
2729
3536
|
* >();
|
|
2730
|
-
*
|
|
2731
|
-
* expectOk(scoresFromInput, { ada: 10, grace: 20 });
|
|
2732
3537
|
* ```
|
|
2733
3538
|
*
|
|
2734
3539
|
* Note that TypeScript does not model an object's runtime prototype. This can
|
|
@@ -2792,18 +3597,20 @@ export const record = (key, value) => {
|
|
|
2792
3597
|
const fromUnknown = (input, options = firstValidationOptions) => validate(input, typeKey.fromUnknown, typeValue.fromUnknown, options);
|
|
2793
3598
|
const validateOutput = (input, options = firstValidationOptions) => validate(input, typeKey[outputValidationSymbol], typeValue[outputValidationSymbol], options);
|
|
2794
3599
|
const formatError = (error) => {
|
|
2795
|
-
if (error.reason.kind === "NotRecord")
|
|
2796
|
-
return `A value ${safelyStringifyUnknownValue(error.reason.value)} is not a
|
|
2797
|
-
|
|
2798
|
-
if (error.reason.kind === "NotPlainRecord") {
|
|
3600
|
+
if (error.reason.kind === "NotRecord")
|
|
3601
|
+
return `A value ${safelyStringifyUnknownValue(error.reason.value)} is not a Record.`;
|
|
3602
|
+
if (error.reason.kind === "NotPlainRecord")
|
|
2799
3603
|
return "The value is an object, but a Record Output must be a plain object or have a null prototype.";
|
|
2800
|
-
}
|
|
2801
3604
|
const issue = error.reason.issues[0];
|
|
2802
3605
|
switch (issue.kind) {
|
|
3606
|
+
case "Key":
|
|
3607
|
+
return `Property key ${safelyStringifyUnknownValue(issue.key)} is invalid.`;
|
|
3608
|
+
case "Value":
|
|
3609
|
+
return `The value of property ${safelyStringifyUnknownValue(issue.key)} is invalid.`;
|
|
2803
3610
|
case "Accessor":
|
|
2804
|
-
return `A
|
|
3611
|
+
return `A Record property ${safelyStringifyUnknownValue(issue.key)} must be a data property.`;
|
|
2805
3612
|
case "NonEnumerable":
|
|
2806
|
-
return `A
|
|
3613
|
+
return `A Record property ${safelyStringifyUnknownValue(issue.key)} must be enumerable.`;
|
|
2807
3614
|
case "Collision":
|
|
2808
3615
|
return `Record keys ${safelyStringifyUnknownValue(issue.previousKey)} and ${safelyStringifyUnknownValue(issue.key)} decode to the same key ${safelyStringifyUnknownValue(issue.outputKey)}.`;
|
|
2809
3616
|
}
|
|
@@ -3115,25 +3922,27 @@ const createObjectType = (props, recordType) => {
|
|
|
3115
3922
|
const fromUnknown = (value, options = firstValidationOptions) => validate(value, options, false);
|
|
3116
3923
|
const validateOutput = (value, options = firstValidationOptions) => validate(value, options, true);
|
|
3117
3924
|
const formatError = (error) => {
|
|
3118
|
-
if (error.reason.kind !== "Properties")
|
|
3925
|
+
if (error.reason.kind !== "Properties")
|
|
3119
3926
|
return formatPlainObjectRootError(error.reason);
|
|
3120
|
-
}
|
|
3121
3927
|
const key = Reflect.ownKeys(error.reason.errors).at(0);
|
|
3122
3928
|
assertNonNullable(key);
|
|
3123
3929
|
const propertyError = error.reason.errors[key];
|
|
3930
|
+
assertNonNullable(propertyError);
|
|
3124
3931
|
if (propertyError.type === "ObjectPropertyAccess") {
|
|
3125
3932
|
switch (propertyError.reason) {
|
|
3126
3933
|
case "Accessor":
|
|
3127
3934
|
return "An Object property must be a data property. Materialize accessor values into plain data before using this Type or use a different Type.";
|
|
3128
3935
|
case "NonEnumerable":
|
|
3129
|
-
return "An Object property must be enumerable.";
|
|
3936
|
+
return "An Object property must be enumerable. Make it enumerable or use a different Type.";
|
|
3130
3937
|
}
|
|
3131
3938
|
}
|
|
3132
|
-
if (
|
|
3133
|
-
|
|
3134
|
-
|
|
3135
|
-
|
|
3136
|
-
|
|
3939
|
+
if (propertyError.type === "ObjectMissingProperty")
|
|
3940
|
+
return `The required property ${safelyStringifyUnknownValue(key)} is missing.`;
|
|
3941
|
+
if (typeof key === "symbol")
|
|
3942
|
+
return "An Object property key must be a string. Remove the symbol property or use a different Type.";
|
|
3943
|
+
if (propertyError.type === "ObjectExcessProperty")
|
|
3944
|
+
return `The property ${safelyStringifyUnknownValue(key)} is not allowed. Remove it or use a different Type.`;
|
|
3945
|
+
return `The property ${safelyStringifyUnknownValue(key)} is invalid.`;
|
|
3137
3946
|
};
|
|
3138
3947
|
const rootProps = createMutableRecord();
|
|
3139
3948
|
let hasNonRootType = false;
|
|
@@ -3383,6 +4192,17 @@ export const nullableToOptional = (props, ..._validation) => {
|
|
|
3383
4192
|
/**
|
|
3384
4193
|
* Object {@link Type} without the selected declared properties.
|
|
3385
4194
|
*
|
|
4195
|
+
* ### Example
|
|
4196
|
+
*
|
|
4197
|
+
* ```ts
|
|
4198
|
+
* import { String, object, omit } from "@evolu/common";
|
|
4199
|
+
*
|
|
4200
|
+
* const User = object({ name: String, password: String });
|
|
4201
|
+
* const PublicUser = omit(User, "password");
|
|
4202
|
+
*
|
|
4203
|
+
* expectOk(PublicUser.fromUnknown({ name: "Ada" }), { name: "Ada" });
|
|
4204
|
+
* ```
|
|
4205
|
+
*
|
|
3386
4206
|
* @group Objects
|
|
3387
4207
|
*/
|
|
3388
4208
|
export const omit = (objectType, ...keys) => {
|
|
@@ -3535,22 +4355,21 @@ export function discriminatedUnion(...keyOrMembers) {
|
|
|
3535
4355
|
};
|
|
3536
4356
|
const route = (value) => membersByDiscriminator.get(Reflect.get(value, key));
|
|
3537
4357
|
const formatError = (error) => {
|
|
3538
|
-
|
|
3539
|
-
switch (reason.kind) {
|
|
4358
|
+
switch (error.reason.kind) {
|
|
3540
4359
|
case "Object":
|
|
3541
|
-
return formatPlainObjectRootError(reason.error.reason);
|
|
4360
|
+
return formatPlainObjectRootError(error.reason.error.reason);
|
|
3542
4361
|
case "PropertyAccess": {
|
|
3543
|
-
const property = `The discriminator property ${safelyStringifyUnknownValue(reason.key)}`;
|
|
3544
|
-
if (reason.reason === "Accessor")
|
|
4362
|
+
const property = `The discriminator property ${safelyStringifyUnknownValue(error.reason.key)}`;
|
|
4363
|
+
if (error.reason.reason === "Accessor")
|
|
3545
4364
|
return `${property} must be a data property.`;
|
|
3546
|
-
|
|
3547
|
-
if (reason.reason === "Inherited") {
|
|
4365
|
+
if (error.reason.reason === "Inherited")
|
|
3548
4366
|
return `${property} must be an own property.`;
|
|
3549
|
-
}
|
|
3550
4367
|
return `${property} must be enumerable.`;
|
|
3551
4368
|
}
|
|
3552
4369
|
case "Discriminator":
|
|
3553
|
-
return `The discriminator property ${safelyStringifyUnknownValue(reason.key)} has an unexpected value ${safelyStringifyUnknownValue(reason.value)}.`;
|
|
4370
|
+
return `The discriminator property ${safelyStringifyUnknownValue(error.reason.key)} has an unexpected value ${safelyStringifyUnknownValue(error.reason.value)}.`;
|
|
4371
|
+
case "Member":
|
|
4372
|
+
return `The selected variant ${safelyStringifyUnknownValue(error.reason.discriminator)} is invalid.`;
|
|
3554
4373
|
}
|
|
3555
4374
|
};
|
|
3556
4375
|
const defaultFormatter = formatError;
|
|
@@ -3831,29 +4650,6 @@ const validateJsonValue = (value, options = firstValidationOptions) => {
|
|
|
3831
4650
|
},
|
|
3832
4651
|
});
|
|
3833
4652
|
};
|
|
3834
|
-
const formatJsonValueError = (error) => {
|
|
3835
|
-
const issue = error.reason.issues[0];
|
|
3836
|
-
switch (issue.kind) {
|
|
3837
|
-
case "InvalidType":
|
|
3838
|
-
return `A value ${safelyStringifyUnknownValue(issue.value)} is not a JSON value.`;
|
|
3839
|
-
case "NonFiniteNumber":
|
|
3840
|
-
return "A JSON number must be finite.";
|
|
3841
|
-
case "UnexpectedPrototype":
|
|
3842
|
-
return "The value is an object, but a JsonValue Object must be a plain object or have a null prototype.";
|
|
3843
|
-
case "Accessor":
|
|
3844
|
-
return "A JSON property must be a data property. Materialize accessor values into plain data before using this Type or use a different Type.";
|
|
3845
|
-
case "NonEnumerable":
|
|
3846
|
-
return "A JSON Object property must be enumerable. Remove it or use a different Type.";
|
|
3847
|
-
case "SymbolProperty":
|
|
3848
|
-
return "A JSON Object property key must be a string. Remove the symbol property or use a different Type.";
|
|
3849
|
-
case "Hole":
|
|
3850
|
-
return "A JSON Array element is missing.";
|
|
3851
|
-
case "ExcessProperty":
|
|
3852
|
-
return "An excess JSON Array property is not allowed. Remove it or use a different Type.";
|
|
3853
|
-
case "CircularReference":
|
|
3854
|
-
return "A JsonValue must not contain circular references.";
|
|
3855
|
-
}
|
|
3856
|
-
};
|
|
3857
4653
|
const getJsonValueRuntimeTypeIssues = (error, mode) => {
|
|
3858
4654
|
const jsonValueError = error;
|
|
3859
4655
|
const issues = mode === "first"
|
|
@@ -3868,7 +4664,29 @@ const getJsonValueRuntimeTypeIssues = (error, mode) => {
|
|
|
3868
4664
|
reason: { kind: "Issues", issues: [issue] },
|
|
3869
4665
|
},
|
|
3870
4666
|
path: issue.path,
|
|
3871
|
-
formatError:
|
|
4667
|
+
formatError: ((error) => {
|
|
4668
|
+
const issue = error.reason.issues[0];
|
|
4669
|
+
switch (issue.kind) {
|
|
4670
|
+
case "InvalidType":
|
|
4671
|
+
return `A value ${safelyStringifyUnknownValue(issue.value)} is not a JSON value.`;
|
|
4672
|
+
case "NonFiniteNumber":
|
|
4673
|
+
return "A JSON number must be finite.";
|
|
4674
|
+
case "UnexpectedPrototype":
|
|
4675
|
+
return "The value is an object, but a JsonValue object must be a plain object or have a null prototype.";
|
|
4676
|
+
case "Accessor":
|
|
4677
|
+
return "A JSON property must be a data property. Materialize accessor values into plain data before using this Type or use a different Type.";
|
|
4678
|
+
case "NonEnumerable":
|
|
4679
|
+
return "A JSON object property must be enumerable. Remove it or use a different Type.";
|
|
4680
|
+
case "SymbolProperty":
|
|
4681
|
+
return "A JSON object property key must be a string. Remove the symbol property or use a different Type.";
|
|
4682
|
+
case "Hole":
|
|
4683
|
+
return "A JSON array element is missing.";
|
|
4684
|
+
case "ExcessProperty":
|
|
4685
|
+
return "An excess JSON array property is not allowed. Remove it or use a different Type.";
|
|
4686
|
+
case "CircularReference":
|
|
4687
|
+
return "A JsonValue must not contain circular references.";
|
|
4688
|
+
}
|
|
4689
|
+
}),
|
|
3872
4690
|
}));
|
|
3873
4691
|
};
|
|
3874
4692
|
const parseJson = (value) => globalThis.JSON.parse(value);
|
|
@@ -3938,6 +4756,21 @@ const stringifyJsonValue = (value) => {
|
|
|
3938
4756
|
/**
|
|
3939
4757
|
* Exact root Type for {@link JsonValue} data trees.
|
|
3940
4758
|
*
|
|
4759
|
+
* ### Example
|
|
4760
|
+
*
|
|
4761
|
+
* ```ts
|
|
4762
|
+
* import { JsonValue, type JsonValueInput } from "@evolu/common";
|
|
4763
|
+
*
|
|
4764
|
+
* const input: JsonValueInput = {
|
|
4765
|
+
* name: "Ada",
|
|
4766
|
+
* scores: [10, 20],
|
|
4767
|
+
* };
|
|
4768
|
+
* const result = JsonValue.fromUnknown(input);
|
|
4769
|
+
*
|
|
4770
|
+
* expectOk(result, input);
|
|
4771
|
+
* expectTypeOf(result.value).toEqualTypeOf<JsonValue>();
|
|
4772
|
+
* ```
|
|
4773
|
+
*
|
|
3941
4774
|
* @group JSON
|
|
3942
4775
|
*/
|
|
3943
4776
|
export const JsonValue =
|
|
@@ -3970,21 +4803,52 @@ export const Json = /*#__PURE__*/ brand("Json", String, (value) => {
|
|
|
3970
4803
|
/**
|
|
3971
4804
|
* Totally parses proven {@link Json} text into an exact {@link JsonValue}.
|
|
3972
4805
|
*
|
|
4806
|
+
* ### Example
|
|
4807
|
+
*
|
|
4808
|
+
* ```ts
|
|
4809
|
+
* import { Json, jsonToJsonValue } from "@evolu/common";
|
|
4810
|
+
*
|
|
4811
|
+
* const value = jsonToJsonValue(Json.orThrow('{"name":"Ada"}'));
|
|
4812
|
+
*
|
|
4813
|
+
* expect(value).toEqual({ name: "Ada" });
|
|
4814
|
+
* ```
|
|
4815
|
+
*
|
|
3973
4816
|
* @group JSON
|
|
3974
4817
|
*/
|
|
3975
4818
|
export const jsonToJsonValue = (value) => parseJson(value);
|
|
3976
4819
|
/**
|
|
3977
4820
|
* Totally encodes an exact {@link JsonValue} as canonical {@link Json} text.
|
|
3978
4821
|
*
|
|
4822
|
+
* ### Example
|
|
4823
|
+
*
|
|
4824
|
+
* ```ts
|
|
4825
|
+
* import { jsonValueToJson, type JsonValue } from "@evolu/common";
|
|
4826
|
+
*
|
|
4827
|
+
* const value: JsonValue = { name: "Ada" };
|
|
4828
|
+
*
|
|
4829
|
+
* expect(jsonValueToJson(value)).toBe('{"name":"Ada"}');
|
|
4830
|
+
* ```
|
|
4831
|
+
*
|
|
3979
4832
|
* @group JSON
|
|
3980
4833
|
*/
|
|
3981
4834
|
export const jsonValueToJson = (value) => stringifyJsonValue(JsonValue.to(value));
|
|
3982
4835
|
/**
|
|
3983
|
-
* {@link Json}
|
|
4836
|
+
* Transformation {@link Type} that parses {@link Json} into {@link JsonValue}.
|
|
3984
4837
|
*
|
|
3985
4838
|
* Decoding unknown input first validates the Json Brand. Starting from the
|
|
3986
4839
|
* typed Json parent is infallible. Encoding canonicalizes JSON text.
|
|
3987
4840
|
*
|
|
4841
|
+
* ### Example
|
|
4842
|
+
*
|
|
4843
|
+
* ```ts
|
|
4844
|
+
* import { JsonValueFromJson } from "@evolu/common";
|
|
4845
|
+
*
|
|
4846
|
+
* const result = JsonValueFromJson.fromUnknown('{ "name": "Ada" }');
|
|
4847
|
+
*
|
|
4848
|
+
* expectOk(result, { name: "Ada" });
|
|
4849
|
+
* expect(JsonValueFromJson.to(result.value)).toBe('{"name":"Ada"}');
|
|
4850
|
+
* ```
|
|
4851
|
+
*
|
|
3988
4852
|
* @group JSON
|
|
3989
4853
|
*/
|
|
3990
4854
|
export const JsonValueFromJson = /*#__PURE__*/ transform("JsonValueFromJson", Json, JsonValue, {
|