@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/src/Type.ts
CHANGED
|
@@ -1,175 +1,100 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* ## Intro
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
import
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
* assertion messages could make it even smaller, but Evolu keeps actionable
|
|
68
|
-
* messages as a deliberate tradeoff for developer experience.
|
|
69
|
-
*
|
|
70
|
-
* The main properties of Evolu Type are:
|
|
71
|
-
*
|
|
72
|
-
* - **Result-based error handling** – expected failures are explicit values.
|
|
73
|
-
* - **Typed errors with decoupled formatters** – validation logic stays
|
|
74
|
-
* independent of user-facing messages, and errors can be handled
|
|
75
|
-
* exhaustively.
|
|
76
|
-
* - **Type-safe, tree-shakeable localization** – formatter requirements are
|
|
77
|
-
* inferred from selected Types, while apps bundle exactly the locales they
|
|
78
|
-
* support so users can change language offline.
|
|
79
|
-
* - **Consistent constraints through {@link Brand}** – every refinement constraint
|
|
80
|
-
* is represented in the TypeScript type, so an unconstrained parent value
|
|
81
|
-
* cannot be used where the constrained value is required.
|
|
82
|
-
* - **Typed inputs** – prefer `from` and its `.parent` entry points to connect
|
|
83
|
-
* precise producer and consumer contracts while preserving typed remaining
|
|
84
|
-
* errors; reserve `fromUnknown` for genuinely unknown values.
|
|
85
|
-
* - **Lawful codecs** – Types partially decode `Input` to `Output` and totally
|
|
86
|
-
* encode every legitimate `Output` to `CanonicalInput`, the statically known
|
|
87
|
-
* subtype of `Input` returned by complete encoding.
|
|
88
|
-
* - **A top-down implementation** – the source is intended to be read from
|
|
89
|
-
* beginning to end.
|
|
90
|
-
*
|
|
91
|
-
* Evolu Type supports [Standard Schema](https://standardschema.dev/) for
|
|
92
|
-
* interoperability with compatible tools and frameworks while preserving each
|
|
93
|
-
* Type's exact Input and Output.
|
|
94
|
-
*
|
|
95
|
-
* Evolu Type assumes that all executing code, including third-party
|
|
96
|
-
* dependencies, has been audited and is trusted. It validates data contracts
|
|
97
|
-
* under that assumption. Trusting code does not require trusting every value it
|
|
98
|
-
* returns, so uncertain values from legacy code or another realm can still be
|
|
99
|
-
* decoded at an explicit boundary. It does not protect against hostile
|
|
100
|
-
* executable behavior such as sabotaged Proxies or throwing traps; Type
|
|
101
|
-
* validation is not a security boundary for untrusted JavaScript.
|
|
102
|
-
*
|
|
103
|
-
* Type declarations and their callbacks are trusted construction code. Evolu
|
|
104
|
-
* Type leverages that trust for better developer experience and does not try to
|
|
105
|
-
* recover from code that defeats the type system with `any` or casts, including
|
|
106
|
-
* fabricating an `Err` for `Result<_, never>`. Runtime assertions still enforce
|
|
107
|
-
* contracts TypeScript cannot express, and every Type declaration must be
|
|
108
|
-
* tested for its expected successes and failures.
|
|
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
|
|
109
67
|
*
|
|
110
68
|
* `fromUnknown` validates untyped input through the complete pipeline. `from`
|
|
111
|
-
* and its `.parent` operations
|
|
112
|
-
*
|
|
113
|
-
*
|
|
114
|
-
*
|
|
115
|
-
*
|
|
116
|
-
* an expected validation failure. Its message identifies the expected Type and
|
|
117
|
-
* its cause preserves the exact structured Output validation error.
|
|
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.
|
|
118
74
|
*
|
|
119
75
|
* Prefer the most precise typed boundary available. A value is not unknown
|
|
120
76
|
* merely because it originated outside the application: forms, components, and
|
|
121
77
|
* other producers often expose a `string` or a branded value that can connect
|
|
122
78
|
* directly to a matching `from` boundary. Reserve `fromUnknown` for values
|
|
123
79
|
* whose TypeScript type is genuinely `unknown`. `is` means exact membership in
|
|
124
|
-
* the
|
|
125
|
-
* successful `fromUnknown` result always satisfies `is`.
|
|
126
|
-
*
|
|
127
|
-
* Decoding accepts a representation outside the Output domain only when the
|
|
128
|
-
* Type explicitly declares that representation, such as a transformation Input.
|
|
129
|
-
* Structural Types do not implicitly repair another JavaScript representation.
|
|
130
|
-
* In particular, {@link array} and {@link tuple} require dense own data elements,
|
|
131
|
-
* while the predefined {@link Object}, {@link object}, and {@link record} require
|
|
132
|
-
* plain objects with own enumerable data properties. They do not invoke
|
|
133
|
-
* accessors or materialize inherited and non-enumerable properties.
|
|
134
|
-
*
|
|
135
|
-
* TypeScript object types are structural and do not encode JavaScript realm
|
|
136
|
-
* identity. Structural Types therefore accept legitimate representations from
|
|
137
|
-
* other realms. Prototype checks remain when the prototype defines the semantic
|
|
138
|
-
* domain. Plain-object Types accept a `null` prototype or an immediate root
|
|
139
|
-
* prototype whose own prototype is `null`; ordinary class instances and deeper
|
|
140
|
-
* prototype chains are rejected. When Record decoding must construct a
|
|
141
|
-
* normalized value, it uses a `null` prototype so every string key remains
|
|
142
|
-
* ordinary data.
|
|
143
|
-
*
|
|
144
|
-
* Evolu Type requires TypeScript 7 or newer and expects the
|
|
145
|
-
* `exactOptionalPropertyTypes` compiler option to be enabled.
|
|
146
|
-
*
|
|
147
|
-
* Predefined Types intentionally use the names of corresponding JavaScript
|
|
148
|
-
* built-ins because they represent those familiar value categories. If an
|
|
149
|
-
* imported Type shadows a built-in in the same scope, access the built-in
|
|
150
|
-
* through `globalThis`, JavaScript's standard cross-environment global object,
|
|
151
|
-
* such as `globalThis.String` or `globalThis.Date`.
|
|
152
|
-
*
|
|
153
|
-
* ### Example
|
|
80
|
+
* the Output domain, not merely that an encoded Input can be decoded.
|
|
154
81
|
*
|
|
155
|
-
*
|
|
156
|
-
*
|
|
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.
|
|
157
90
|
*
|
|
158
|
-
*
|
|
159
|
-
*
|
|
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.
|
|
160
94
|
*
|
|
161
|
-
*
|
|
162
|
-
*
|
|
163
|
-
*
|
|
164
|
-
* {
|
|
165
|
-
* readonly type: "TypeOf";
|
|
166
|
-
* readonly expected: "String";
|
|
167
|
-
* readonly value: unknown;
|
|
168
|
-
* }
|
|
169
|
-
* >
|
|
170
|
-
* >();
|
|
171
|
-
* expectOk(result, "hello");
|
|
172
|
-
* ```
|
|
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.
|
|
173
98
|
*
|
|
174
99
|
* ## FAQ
|
|
175
100
|
*
|
|
@@ -287,10 +212,10 @@ import {
|
|
|
287
212
|
* is own, enumerable, or a data property. It also permits a wider object with
|
|
288
213
|
* excess properties where a narrower object type is expected.
|
|
289
214
|
*
|
|
290
|
-
* `fromUnknown` treats such invalid external values as
|
|
291
|
-
*
|
|
292
|
-
*
|
|
293
|
-
*
|
|
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
|
|
294
219
|
* because the application contract is broken. `orThrow` and `orNull` preserve
|
|
295
220
|
* the assertion at their typed `Input` boundary, then apply {@link getOrThrow}
|
|
296
221
|
* or {@link getOrNull} only to validation failures returned by the remaining
|
|
@@ -325,10 +250,12 @@ import {
|
|
|
325
250
|
* without requiring conversion merely because its built-ins belong to another
|
|
326
251
|
* realm.
|
|
327
252
|
*
|
|
328
|
-
* When an application trusts both the producer and its return contract,
|
|
329
|
-
*
|
|
330
|
-
*
|
|
331
|
-
*
|
|
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.
|
|
332
259
|
*
|
|
333
260
|
* All executing JavaScript remains trusted. Deliberately forged built-ins,
|
|
334
261
|
* hostile Proxies, throwing traps, or sabotaged executable behavior can throw;
|
|
@@ -384,8 +311,8 @@ import {
|
|
|
384
311
|
* value to a Task. A pure synchronous conversion that can fail can be an
|
|
385
312
|
* ordinary function returning Result.
|
|
386
313
|
*
|
|
387
|
-
* Keeping those responsibilities separate prevents
|
|
388
|
-
*
|
|
314
|
+
* Keeping those responsibilities separate prevents Evolu Type from becoming a
|
|
315
|
+
* hidden application workflow. It also keeps validation deterministic,
|
|
389
316
|
* dependency-free, immediately composable, and straightforward to test.
|
|
390
317
|
*
|
|
391
318
|
* ### What if only decoding is needed?
|
|
@@ -397,7 +324,55 @@ import {
|
|
|
397
324
|
* irreversible operation is a separate function or Task, not a Type
|
|
398
325
|
* transformation.
|
|
399
326
|
*
|
|
400
|
-
* @
|
|
327
|
+
* @module
|
|
328
|
+
*/
|
|
329
|
+
import { utf8ToBytes } from "@noble/ciphers/utils.js";
|
|
330
|
+
import { sha256 } from "@noble/hashes/sha2.js";
|
|
331
|
+
import * as bip39 from "@scure/bip39";
|
|
332
|
+
import { wordlist } from "@scure/bip39/wordlists/english.js";
|
|
333
|
+
import type { StandardSchemaV1 } from "@standard-schema/spec";
|
|
334
|
+
import type {
|
|
335
|
+
AtLeastTwoReadonlyArray,
|
|
336
|
+
NonEmptyReadonlyArray,
|
|
337
|
+
} from "./Array.ts";
|
|
338
|
+
import { assert, assertNonNullable } from "./Assert.ts";
|
|
339
|
+
import type { Brand } from "./Brand.ts";
|
|
340
|
+
import type { RandomBytesDep } from "./Crypto.ts";
|
|
341
|
+
import { identity, type Thunk } from "./Function.ts";
|
|
342
|
+
import { createMutableRecord } from "./Object.ts";
|
|
343
|
+
import { hasNodeBuffer } from "./Platform.ts";
|
|
344
|
+
import {
|
|
345
|
+
err,
|
|
346
|
+
flatMapResult,
|
|
347
|
+
getOk,
|
|
348
|
+
getOrNull,
|
|
349
|
+
getOrThrow,
|
|
350
|
+
ok,
|
|
351
|
+
trySync,
|
|
352
|
+
type Result,
|
|
353
|
+
} from "./Result.ts";
|
|
354
|
+
import { safelyStringifyUnknownValue } from "./String.ts";
|
|
355
|
+
import type { Task } from "./Task.ts";
|
|
356
|
+
import type { TimeDep } from "./Time.ts";
|
|
357
|
+
import {
|
|
358
|
+
instance,
|
|
359
|
+
isInstance,
|
|
360
|
+
type CompileTimeError,
|
|
361
|
+
type Instance,
|
|
362
|
+
type IsUnion,
|
|
363
|
+
type Literal,
|
|
364
|
+
type Simplify,
|
|
365
|
+
type ValueWithLength,
|
|
366
|
+
type WidenLiteral,
|
|
367
|
+
} from "./Types.ts";
|
|
368
|
+
|
|
369
|
+
/**
|
|
370
|
+
* A runtime representation of a TypeScript type, including its encoded input,
|
|
371
|
+
* semantic output, structured errors, and canonical encoding.
|
|
372
|
+
*
|
|
373
|
+
* See the {@link @evolu/common!Type | Type overview}.
|
|
374
|
+
*
|
|
375
|
+
* @group Core
|
|
401
376
|
*/
|
|
402
377
|
export interface Type<
|
|
403
378
|
Name extends TypeName,
|
|
@@ -489,7 +464,20 @@ export interface Type<
|
|
|
489
464
|
/** The one preceding Type node, or `null` for a root Type. */
|
|
490
465
|
readonly parent: Parent;
|
|
491
466
|
|
|
492
|
-
/**
|
|
467
|
+
/**
|
|
468
|
+
* Decodes an unknown value through the complete Type pipeline.
|
|
469
|
+
*
|
|
470
|
+
* ### Example
|
|
471
|
+
*
|
|
472
|
+
* ```ts
|
|
473
|
+
* import { PositiveInt } from "@evolu/common";
|
|
474
|
+
*
|
|
475
|
+
* const value: unknown = 42;
|
|
476
|
+
* const result = PositiveInt.fromUnknown(value);
|
|
477
|
+
*
|
|
478
|
+
* expectOk(result, 42);
|
|
479
|
+
* ```
|
|
480
|
+
*/
|
|
493
481
|
readonly fromUnknown: (
|
|
494
482
|
value: unknown,
|
|
495
483
|
options?: ValidationOptions,
|
|
@@ -503,6 +491,19 @@ export interface Type<
|
|
|
503
491
|
* Structural errors retain nested errors and their locations in the typed
|
|
504
492
|
* error value. This formatter does not encode paths or enumerate nested
|
|
505
493
|
* errors in its message.
|
|
494
|
+
*
|
|
495
|
+
* ### Example
|
|
496
|
+
*
|
|
497
|
+
* ```ts
|
|
498
|
+
* import { String } from "@evolu/common";
|
|
499
|
+
*
|
|
500
|
+
* const result = String.fromUnknown(42);
|
|
501
|
+
*
|
|
502
|
+
* expectErr(result, { type: "TypeOf", expected: "String", value: 42 });
|
|
503
|
+
* expect(String.formatError(result.error)).toBe(
|
|
504
|
+
* "A value 42 is not a string.",
|
|
505
|
+
* );
|
|
506
|
+
* ```
|
|
506
507
|
*/
|
|
507
508
|
readonly formatError: TypeErrorFormatter<Errors>;
|
|
508
509
|
|
|
@@ -540,6 +541,52 @@ export interface Type<
|
|
|
540
541
|
* developer error. The Error message identifies the expected boundary Type,
|
|
541
542
|
* and its cause preserves the structured validation error. Only failures
|
|
542
543
|
* introduced after that boundary are returned through `Result`.
|
|
544
|
+
*
|
|
545
|
+
* ### Example
|
|
546
|
+
*
|
|
547
|
+
* A form already guarantees every constraint on a title, while a new note
|
|
548
|
+
* input guarantees only trimmed text. The note's parent boundary validates
|
|
549
|
+
* only the constraints that the form does not guarantee:
|
|
550
|
+
*
|
|
551
|
+
* ```ts
|
|
552
|
+
* import {
|
|
553
|
+
* flatMapResult,
|
|
554
|
+
* NonEmptyTrimmedString100,
|
|
555
|
+
* object,
|
|
556
|
+
* TrimmedString,
|
|
557
|
+
* type MaxLengthError,
|
|
558
|
+
* type MinLengthError,
|
|
559
|
+
* type Result,
|
|
560
|
+
* } from "@evolu/common";
|
|
561
|
+
*
|
|
562
|
+
* const Todo = object({
|
|
563
|
+
* title: NonEmptyTrimmedString100,
|
|
564
|
+
* note: NonEmptyTrimmedString100,
|
|
565
|
+
* });
|
|
566
|
+
*
|
|
567
|
+
* const saveTodo = (
|
|
568
|
+
* title: NonEmptyTrimmedString100,
|
|
569
|
+
* note: TrimmedString,
|
|
570
|
+
* ) => {
|
|
571
|
+
* // @ts-expect-error TrimmedString does not guarantee a non-empty value
|
|
572
|
+
* // with at most 100 characters.
|
|
573
|
+
* Todo.from({ title, note });
|
|
574
|
+
*
|
|
575
|
+
* return flatMapResult(
|
|
576
|
+
* Todo.props.note.from.parent.parent(note),
|
|
577
|
+
* (note) => Todo.from({ title, note }),
|
|
578
|
+
* );
|
|
579
|
+
* };
|
|
580
|
+
*
|
|
581
|
+
* const title = NonEmptyTrimmedString100.orThrow("Buy milk");
|
|
582
|
+
* const note = TrimmedString.orThrow("Remember oat milk");
|
|
583
|
+
* const result = saveTodo(title, note);
|
|
584
|
+
*
|
|
585
|
+
* expectTypeOf(result).toEqualTypeOf<
|
|
586
|
+
* Result<typeof Todo.Output, MaxLengthError<100> | MinLengthError<1>>
|
|
587
|
+
* >();
|
|
588
|
+
* expectOk(result, { title, note });
|
|
589
|
+
* ```
|
|
543
590
|
*/
|
|
544
591
|
readonly from: [CustomFrom] extends [never]
|
|
545
592
|
? [Parent] extends [infer P extends TypeNode]
|
|
@@ -554,6 +601,16 @@ export interface Type<
|
|
|
554
601
|
* `to` runs the complete encoding pipeline. Its first `.parent` stops at the
|
|
555
602
|
* immediate parent Output, and each additional suffix stops one Type closer
|
|
556
603
|
* to the root. Every entry point accepts this Type's `Output`.
|
|
604
|
+
*
|
|
605
|
+
* ### Example
|
|
606
|
+
*
|
|
607
|
+
* ```ts
|
|
608
|
+
* import { Int64, Int64FromInt64String } from "@evolu/common";
|
|
609
|
+
*
|
|
610
|
+
* const value = Int64.orThrow(42n);
|
|
611
|
+
*
|
|
612
|
+
* expect(Int64FromInt64String.to(value)).toBe("42");
|
|
613
|
+
* ```
|
|
557
614
|
*/
|
|
558
615
|
readonly to: [Parent] extends [infer P extends TypeNode]
|
|
559
616
|
? ToOperation<Output, CanonicalInput, P>
|
|
@@ -586,6 +643,9 @@ export interface Type<
|
|
|
586
643
|
*
|
|
587
644
|
* // Equivalent because `from.parent` is this Type's deepest `from` operation:
|
|
588
645
|
* const sameValue = getOrThrow(NonEmptyString.from.parent("Evolu"));
|
|
646
|
+
*
|
|
647
|
+
* expect(value).toBe("Evolu");
|
|
648
|
+
* expect(sameValue).toBe(value);
|
|
589
649
|
* ```
|
|
590
650
|
*/
|
|
591
651
|
readonly orThrow: TypeOperationFn<"orThrow", Input, Output, never>;
|
|
@@ -617,12 +677,20 @@ export interface Type<
|
|
|
617
677
|
*
|
|
618
678
|
* // Equivalent because `from.parent` is this Type's deepest `from` operation:
|
|
619
679
|
* const sameValue = getOrNull(NonEmptyString.from.parent("Evolu"));
|
|
680
|
+
*
|
|
681
|
+
* expect(value).toBe("Evolu");
|
|
682
|
+
* expect(sameValue).toBe(value);
|
|
683
|
+
* expect(NonEmptyString.orNull("")).toBeNull();
|
|
620
684
|
* ```
|
|
621
685
|
*/
|
|
622
686
|
readonly orNull: TypeOperationFn<"orNull", Input, Output, never>;
|
|
623
687
|
}
|
|
624
688
|
|
|
625
|
-
/**
|
|
689
|
+
/**
|
|
690
|
+
* A capitalized name identifying a {@link Type} node.
|
|
691
|
+
*
|
|
692
|
+
* @group Core
|
|
693
|
+
*/
|
|
626
694
|
export type TypeName = Capitalize<string>;
|
|
627
695
|
|
|
628
696
|
/**
|
|
@@ -635,7 +703,7 @@ export type TypeName = Capitalize<string>;
|
|
|
635
703
|
* only when it intentionally represents the same error contract. Accidental
|
|
636
704
|
* reuse prevents reliable discriminated-union narrowing.
|
|
637
705
|
*
|
|
638
|
-
* @group Core
|
|
706
|
+
* @group Core
|
|
639
707
|
*/
|
|
640
708
|
export interface TypeError<Name extends TypeName = TypeName> {
|
|
641
709
|
readonly type: Name;
|
|
@@ -653,7 +721,7 @@ interface TransparentTypeError {
|
|
|
653
721
|
* Structural errors such as Array and Union errors extend {@link TypeError}
|
|
654
722
|
* instead because they locate nested errors rather than own one value.
|
|
655
723
|
*
|
|
656
|
-
* @group Core
|
|
724
|
+
* @group Core
|
|
657
725
|
*/
|
|
658
726
|
// Built-in errors intentionally repeat narrower `value` properties. Making
|
|
659
727
|
// `value` generic here and sharing this base regresses `pnpm bench:type`.
|
|
@@ -666,13 +734,18 @@ export interface TypeValueError<
|
|
|
666
734
|
/**
|
|
667
735
|
* Formats a structured {@link TypeError} as a human-readable message.
|
|
668
736
|
*
|
|
669
|
-
* @group Core
|
|
737
|
+
* @group Core
|
|
670
738
|
*/
|
|
671
739
|
export type TypeErrorFormatter<Error extends TypeError> = (
|
|
672
740
|
error: Error,
|
|
673
741
|
) => string;
|
|
674
742
|
|
|
675
|
-
/**
|
|
743
|
+
/**
|
|
744
|
+
* The common structural shape of every {@link Type}, with its specific type
|
|
745
|
+
* parameters erased.
|
|
746
|
+
*
|
|
747
|
+
* @group Core
|
|
748
|
+
*/
|
|
676
749
|
export interface TypeNode {
|
|
677
750
|
readonly name: TypeName;
|
|
678
751
|
readonly "~standard": StandardSchemaV1.Props<unknown, unknown>;
|
|
@@ -804,7 +877,7 @@ const formatDefaultRuntimeTypeIssue: RuntimeFormatTypeIssue = (issue) =>
|
|
|
804
877
|
* Asserts that a value belongs to a {@link Type} Output domain.
|
|
805
878
|
*
|
|
806
879
|
* Use this for internal invariants, not external input. Validate external input
|
|
807
|
-
* with `Type.fromUnknown` so
|
|
880
|
+
* with `Type.fromUnknown` so validation failures remain typed values. A failed
|
|
808
881
|
* assertion uses the Type name for its message and preserves the exact Output
|
|
809
882
|
* validation error as the thrown Error's cause.
|
|
810
883
|
*
|
|
@@ -824,7 +897,7 @@ const formatDefaultRuntimeTypeIssue: RuntimeFormatTypeIssue = (issue) =>
|
|
|
824
897
|
* >();
|
|
825
898
|
* ```
|
|
826
899
|
*
|
|
827
|
-
* @group Core
|
|
900
|
+
* @group Core
|
|
828
901
|
*/
|
|
829
902
|
export const assertType: <T extends TypeNode>(
|
|
830
903
|
type: T,
|
|
@@ -860,34 +933,32 @@ const assertTypeOutput = <Error extends TypeError>(
|
|
|
860
933
|
};
|
|
861
934
|
|
|
862
935
|
/**
|
|
863
|
-
*
|
|
936
|
+
* Creates localized copies of selected {@link Type} declarations.
|
|
864
937
|
*
|
|
865
|
-
*
|
|
866
|
-
*
|
|
867
|
-
*
|
|
868
|
-
*
|
|
869
|
-
*
|
|
870
|
-
* Lazy error interfaces.
|
|
938
|
+
* Pass the Types used together in one localization scope and formatter maps
|
|
939
|
+
* keyed by locale. TypeScript infers every formatter required by the selected
|
|
940
|
+
* Types, including errors from nested structural Types and recursive Lazy
|
|
941
|
+
* Types. Every locale must provide the complete inferred formatter set;
|
|
942
|
+
* missing and unrelated formatters are compile-time errors.
|
|
871
943
|
*
|
|
872
|
-
*
|
|
873
|
-
*
|
|
874
|
-
*
|
|
944
|
+
* The result preserves the locale names, selected Type names, and exact
|
|
945
|
+
* TypeScript types. A localized Type validates exactly like its source Type;
|
|
946
|
+
* only its human-readable `formatError` and Standard Schema messages change.
|
|
947
|
+
* The source Types remain unchanged.
|
|
875
948
|
*
|
|
876
|
-
*
|
|
877
|
-
*
|
|
949
|
+
* Parents and reflected child Types are localized with the same formatter set.
|
|
950
|
+
* Structural Types retain error paths and delegate nested messages to the Type
|
|
951
|
+
* that produced them. Different localized Type sets can coexist in separate
|
|
952
|
+
* application or dependency-injection scopes.
|
|
878
953
|
*
|
|
879
|
-
*
|
|
880
|
-
*
|
|
881
|
-
*
|
|
882
|
-
*
|
|
954
|
+
* Localization is scoped to the selected Types instead of a package-wide
|
|
955
|
+
* translation registry. Static imports give bundlers an explicit dependency
|
|
956
|
+
* graph, so unrelated Types, locales, and formatters can be removed. Bundling
|
|
957
|
+
* every locale an app supports also allows language changes without a network
|
|
958
|
+
* connection.
|
|
883
959
|
*
|
|
884
|
-
*
|
|
885
|
-
*
|
|
886
|
-
* dependency graph from those Types to their formatters, so unrelated Type and
|
|
887
|
-
* localization code can be removed. An app supplies all locales it supports in
|
|
888
|
-
* the same self-contained bundle, allowing users to change language without a
|
|
889
|
-
* network connection. Different localized Type sets can coexist on one page or
|
|
890
|
-
* in separate dependency-injection scopes.
|
|
960
|
+
* The selected Type map, locale map, and formatter maps must be plain objects
|
|
961
|
+
* with own enumerable string-keyed data properties.
|
|
891
962
|
*
|
|
892
963
|
* ### Example
|
|
893
964
|
*
|
|
@@ -897,7 +968,7 @@ const assertTypeOutput = <Error extends TypeError>(
|
|
|
897
968
|
*
|
|
898
969
|
* const Label = minLength(1)(String);
|
|
899
970
|
*
|
|
900
|
-
* const
|
|
971
|
+
* const typesByLocale = localizeTypes(
|
|
901
972
|
* { Label },
|
|
902
973
|
* {
|
|
903
974
|
* cs: {
|
|
@@ -907,11 +978,66 @@ const assertTypeOutput = <Error extends TypeError>(
|
|
|
907
978
|
* },
|
|
908
979
|
* );
|
|
909
980
|
*
|
|
910
|
-
* expectTypeOf<typeof
|
|
981
|
+
* expectTypeOf<typeof typesByLocale.cs.Label>().toEqualTypeOf<
|
|
911
982
|
* typeof Label
|
|
912
983
|
* >();
|
|
984
|
+
*
|
|
985
|
+
* const result = typesByLocale.cs.Label.fromUnknown("");
|
|
986
|
+
* expectErr(result, { type: "MinLength1", min: 1, value: "" });
|
|
987
|
+
* expect(typesByLocale.cs.Label.formatError(result.error)).toBe(
|
|
988
|
+
* "Text nesmí být prázdný.",
|
|
989
|
+
* );
|
|
913
990
|
* ```
|
|
914
991
|
*
|
|
992
|
+
* ### Supported locales
|
|
993
|
+
*
|
|
994
|
+
* English is built in; use {@link Type} directly for its default formatters.
|
|
995
|
+
* The following additional locales are available:
|
|
996
|
+
*
|
|
997
|
+
* - Arabic (`ar`)
|
|
998
|
+
* - Bengali (`bn`)
|
|
999
|
+
* - Catalan (`ca`)
|
|
1000
|
+
* - Chinese, Simplified (`zhCN`)
|
|
1001
|
+
* - Chinese, Traditional (`zhTW`)
|
|
1002
|
+
* - Croatian (`hr`)
|
|
1003
|
+
* - Czech (`cs`)
|
|
1004
|
+
* - Danish (`da`)
|
|
1005
|
+
* - Dutch (`nl`)
|
|
1006
|
+
* - Filipino (`fil`)
|
|
1007
|
+
* - Finnish (`fi`)
|
|
1008
|
+
* - French (`fr`)
|
|
1009
|
+
* - German (`de`)
|
|
1010
|
+
* - Greek (`el`)
|
|
1011
|
+
* - Hebrew (`he`)
|
|
1012
|
+
* - Hindi (`hi`)
|
|
1013
|
+
* - Hungarian (`hu`)
|
|
1014
|
+
* - Indonesian (`id`)
|
|
1015
|
+
* - Italian (`it`)
|
|
1016
|
+
* - Japanese (`ja`)
|
|
1017
|
+
* - Korean (`ko`)
|
|
1018
|
+
* - Malay (`ms`)
|
|
1019
|
+
* - Malayalam (`ml`)
|
|
1020
|
+
* - Marathi (`mr`)
|
|
1021
|
+
* - Norwegian Bokmål (`nb`)
|
|
1022
|
+
* - Persian (`fa`)
|
|
1023
|
+
* - Polish (`pl`)
|
|
1024
|
+
* - Portuguese (`pt`)
|
|
1025
|
+
* - Portuguese, Brazilian (`ptBR`)
|
|
1026
|
+
* - Punjabi (`pa`)
|
|
1027
|
+
* - Romanian (`ro`)
|
|
1028
|
+
* - Slovak (`sk`)
|
|
1029
|
+
* - Slovenian (`sl`)
|
|
1030
|
+
* - Spanish (`es`)
|
|
1031
|
+
* - Swahili (`sw`)
|
|
1032
|
+
* - Swedish (`sv`)
|
|
1033
|
+
* - Tamil (`ta`)
|
|
1034
|
+
* - Telugu (`te`)
|
|
1035
|
+
* - Thai (`th`)
|
|
1036
|
+
* - Turkish (`tr`)
|
|
1037
|
+
* - Ukrainian (`uk`)
|
|
1038
|
+
* - Urdu (`ur`)
|
|
1039
|
+
* - Vietnamese (`vi`)
|
|
1040
|
+
*
|
|
915
1041
|
* @group Localization
|
|
916
1042
|
*/
|
|
917
1043
|
export const localizeTypes = ((
|
|
@@ -1277,16 +1403,21 @@ declare const identityEncodingSymbol: unique symbol;
|
|
|
1277
1403
|
* age: optional(PositiveInt),
|
|
1278
1404
|
* });
|
|
1279
1405
|
* interface User extends InferType<typeof User> {}
|
|
1406
|
+
*
|
|
1407
|
+
* const user = User.orThrow({ name: "Ada", age: 37 });
|
|
1408
|
+
*
|
|
1409
|
+
* expectTypeOf(user).toExtend<User>();
|
|
1410
|
+
* expect(user.name).toBe("Ada");
|
|
1280
1411
|
* ```
|
|
1281
1412
|
*
|
|
1282
|
-
* @group
|
|
1413
|
+
* @group Core
|
|
1283
1414
|
*/
|
|
1284
1415
|
export type InferType<T extends TypeNode> = T["Output"];
|
|
1285
1416
|
|
|
1286
1417
|
/**
|
|
1287
1418
|
* The union of errors a {@link Type} can return from `fromUnknown`.
|
|
1288
1419
|
*
|
|
1289
|
-
* @group
|
|
1420
|
+
* @group Core
|
|
1290
1421
|
*/
|
|
1291
1422
|
export type InferErrors<T extends TypeNode> = T[typeof errorsSymbol];
|
|
1292
1423
|
|
|
@@ -1501,7 +1632,7 @@ type TypeOperationFn<
|
|
|
1501
1632
|
/**
|
|
1502
1633
|
* Configures how container {@link Type} operations report errors.
|
|
1503
1634
|
*
|
|
1504
|
-
* @group Core
|
|
1635
|
+
* @group Core
|
|
1505
1636
|
*/
|
|
1506
1637
|
export interface ValidationOptions {
|
|
1507
1638
|
/** Controls whether container {@link Type} operations return one or all errors. */
|
|
@@ -1637,7 +1768,7 @@ type ConcreteChildTypeNameError = CompileTimeError<
|
|
|
1637
1768
|
* expectErr(Text.fromUnknown(42), { type: "Text", value: 42 });
|
|
1638
1769
|
* ```
|
|
1639
1770
|
*
|
|
1640
|
-
* @group
|
|
1771
|
+
* @group Construction
|
|
1641
1772
|
*/
|
|
1642
1773
|
export function createType<
|
|
1643
1774
|
Name extends TypeName,
|
|
@@ -1649,6 +1780,8 @@ export function createType<
|
|
|
1649
1780
|
// Validation alone determines Error; broad formatters must not widen it.
|
|
1650
1781
|
formatError: TypeErrorFormatter<NoInfer<Error>>,
|
|
1651
1782
|
): Type<Name, Output, Output, Error, null, Error, never, Output>;
|
|
1783
|
+
|
|
1784
|
+
/** Creates an infallible child Type that preserves its parent's Output. */
|
|
1652
1785
|
export function createType<
|
|
1653
1786
|
Name extends TypeName,
|
|
1654
1787
|
ParentType extends ConcreteTypeNode,
|
|
@@ -1669,6 +1802,8 @@ export function createType<
|
|
|
1669
1802
|
CanonicalInputForChild<ParentType, ParentType["Output"]>,
|
|
1670
1803
|
IdentityEncodingOf<ParentType>
|
|
1671
1804
|
>;
|
|
1805
|
+
|
|
1806
|
+
/** Creates a fallible child Type that narrows its parent's Output. */
|
|
1672
1807
|
export function createType<
|
|
1673
1808
|
Name extends TypeName,
|
|
1674
1809
|
ParentType extends ConcreteTypeNode,
|
|
@@ -1887,12 +2022,19 @@ const createChildType = <
|
|
|
1887
2022
|
* `from` accepts the semantic Output. `from.parent` converts the parent Output
|
|
1888
2023
|
* to the output Type Input, then runs the complete output Type pipeline. `to`
|
|
1889
2024
|
* canonically encodes every output Type value before converting it back through
|
|
1890
|
-
* the parent Type.
|
|
1891
|
-
*
|
|
1892
|
-
*
|
|
1893
|
-
*
|
|
1894
|
-
*
|
|
1895
|
-
*
|
|
2025
|
+
* the parent Type.
|
|
2026
|
+
*
|
|
2027
|
+
* The callbacks must form a lawful codec: decoding `to(output)` must reproduce
|
|
2028
|
+
* the same semantic Output for every valid output value. Encoding may
|
|
2029
|
+
* canonicalize multiple parent representations, but it must be total and must
|
|
2030
|
+
* not lose distinctions present in the Output domain.
|
|
2031
|
+
*
|
|
2032
|
+
* Transformation callbacks are Type construction code. Their successful
|
|
2033
|
+
* results are asserted against the declared boundary so a broken callback fails
|
|
2034
|
+
* as a developer error rather than becoming a validation error. Like all
|
|
2035
|
+
* Type-construction callbacks, they are trusted to follow their declared
|
|
2036
|
+
* TypeScript types. A `Result<_, never>` callback is therefore trusted never to
|
|
2037
|
+
* return an `Err`.
|
|
1896
2038
|
*
|
|
1897
2039
|
* Errors from the parent and the forward callback remain unchanged. A forward
|
|
1898
2040
|
* callback error must use the transformation name as its type. Errors from the
|
|
@@ -1905,18 +2047,30 @@ const createChildType = <
|
|
|
1905
2047
|
* ### Example
|
|
1906
2048
|
*
|
|
1907
2049
|
* ```ts
|
|
1908
|
-
* import {
|
|
2050
|
+
* import {
|
|
2051
|
+
* Boolean,
|
|
2052
|
+
* literal,
|
|
2053
|
+
* ok,
|
|
2054
|
+
* transform,
|
|
2055
|
+
* union,
|
|
2056
|
+
* } from "@evolu/common";
|
|
1909
2057
|
*
|
|
1910
|
-
* const
|
|
1911
|
-
*
|
|
1912
|
-
*
|
|
1913
|
-
*
|
|
2058
|
+
* const BooleanString = union(literal("false"), literal("true"));
|
|
2059
|
+
* const BooleanFromString = transform(
|
|
2060
|
+
* "BooleanFromString",
|
|
2061
|
+
* BooleanString,
|
|
2062
|
+
* Boolean,
|
|
2063
|
+
* {
|
|
2064
|
+
* from: (value) => ok(value === "true"),
|
|
2065
|
+
* to: (value) => (value ? "true" : "false"),
|
|
2066
|
+
* },
|
|
2067
|
+
* );
|
|
1914
2068
|
*
|
|
1915
|
-
* expectOk(
|
|
1916
|
-
*
|
|
2069
|
+
* expectOk(BooleanFromString.from.parent("true"), true);
|
|
2070
|
+
* expect(BooleanFromString.to(false)).toBe("false");
|
|
1917
2071
|
* ```
|
|
1918
2072
|
*
|
|
1919
|
-
* @group
|
|
2073
|
+
* @group Construction
|
|
1920
2074
|
*/
|
|
1921
2075
|
export function transform<
|
|
1922
2076
|
Name extends TypeName,
|
|
@@ -1934,6 +2088,8 @@ export function transform<
|
|
|
1934
2088
|
readonly to: (value: CanonicalInputOf<OutputType>) => ToOutput;
|
|
1935
2089
|
},
|
|
1936
2090
|
): TransformType<ParentType, OutputType, Name, never, ToOutput>;
|
|
2091
|
+
|
|
2092
|
+
/** Creates a fallible transformed Type with its own error formatter. */
|
|
1937
2093
|
export function transform<
|
|
1938
2094
|
Name extends TypeName,
|
|
1939
2095
|
ParentType extends ConcreteTypeNode,
|
|
@@ -2038,7 +2194,11 @@ export function transform(
|
|
|
2038
2194
|
);
|
|
2039
2195
|
}
|
|
2040
2196
|
|
|
2041
|
-
/**
|
|
2197
|
+
/**
|
|
2198
|
+
* The {@link Type} returned by {@link transform}.
|
|
2199
|
+
*
|
|
2200
|
+
* @group Construction
|
|
2201
|
+
*/
|
|
2042
2202
|
export interface TransformType<
|
|
2043
2203
|
ParentType extends TypeNode,
|
|
2044
2204
|
OutputType extends TypeNode,
|
|
@@ -2065,7 +2225,12 @@ export interface TransformType<
|
|
|
2065
2225
|
readonly output: OutputType;
|
|
2066
2226
|
}
|
|
2067
2227
|
|
|
2068
|
-
/**
|
|
2228
|
+
/**
|
|
2229
|
+
* An error produced by {@link transform} while decoding or validating its
|
|
2230
|
+
* output.
|
|
2231
|
+
*
|
|
2232
|
+
* @group Construction
|
|
2233
|
+
*/
|
|
2069
2234
|
export type TransformError<
|
|
2070
2235
|
Name extends TypeName,
|
|
2071
2236
|
OwnError extends TypeError<Name>,
|
|
@@ -2079,7 +2244,7 @@ export type TransformError<
|
|
|
2079
2244
|
/**
|
|
2080
2245
|
* Wraps an error produced by the output {@link Type} of {@link transform}.
|
|
2081
2246
|
*
|
|
2082
|
-
* @group
|
|
2247
|
+
* @group Construction
|
|
2083
2248
|
*/
|
|
2084
2249
|
export interface TransformOutputError<
|
|
2085
2250
|
Name extends TypeName,
|
|
@@ -2345,14 +2510,22 @@ const addRuntimeAssertions = (
|
|
|
2345
2510
|
return asserted;
|
|
2346
2511
|
};
|
|
2347
2512
|
|
|
2348
|
-
/**
|
|
2513
|
+
/**
|
|
2514
|
+
* An infallible {@link Type} accepting every value.
|
|
2515
|
+
*
|
|
2516
|
+
* @group Base
|
|
2517
|
+
*/
|
|
2349
2518
|
export const Unknown = /*#__PURE__*/ createRootType<"Unknown", unknown, never>(
|
|
2350
2519
|
"Unknown",
|
|
2351
2520
|
ok,
|
|
2352
2521
|
identity,
|
|
2353
2522
|
);
|
|
2354
2523
|
|
|
2355
|
-
/**
|
|
2524
|
+
/**
|
|
2525
|
+
* A {@link Type} rejecting every value.
|
|
2526
|
+
*
|
|
2527
|
+
* @group Base
|
|
2528
|
+
*/
|
|
2356
2529
|
export const Never = /*#__PURE__*/ createRootType(
|
|
2357
2530
|
"Never",
|
|
2358
2531
|
(value): Result<never, NeverError> => err({ type: "Never", value }),
|
|
@@ -2360,6 +2533,11 @@ export const Never = /*#__PURE__*/ createRootType(
|
|
|
2360
2533
|
`A value ${safelyStringifyUnknownValue(error.value)} is not valid for type Never.`,
|
|
2361
2534
|
);
|
|
2362
2535
|
|
|
2536
|
+
/**
|
|
2537
|
+
* Error returned by {@link Never} for every value.
|
|
2538
|
+
*
|
|
2539
|
+
* @group Base
|
|
2540
|
+
*/
|
|
2363
2541
|
export interface NeverError extends TypeError<"Never"> {
|
|
2364
2542
|
readonly value: unknown;
|
|
2365
2543
|
}
|
|
@@ -2398,7 +2576,11 @@ interface TypeOfOutputByName {
|
|
|
2398
2576
|
readonly Function: globalThis.Function;
|
|
2399
2577
|
}
|
|
2400
2578
|
|
|
2401
|
-
/**
|
|
2579
|
+
/**
|
|
2580
|
+
* Error returned when `typeof` does not match the expected JavaScript type.
|
|
2581
|
+
*
|
|
2582
|
+
* @group Base
|
|
2583
|
+
*/
|
|
2402
2584
|
export interface TypeOfError<
|
|
2403
2585
|
Name extends keyof TypeOfOutputByName,
|
|
2404
2586
|
> extends TypeError<"TypeOf"> {
|
|
@@ -2429,6 +2611,8 @@ export interface TypeOfError<
|
|
|
2429
2611
|
* expectTypeOf<WireValue100>().toEqualTypeOf<
|
|
2430
2612
|
* string & Brand<"MaxLength100">
|
|
2431
2613
|
* >();
|
|
2614
|
+
* expectOk(WireValue100.fromUnknown(""), "");
|
|
2615
|
+
* expectOk(WireValue100.fromUnknown(" value "), " value ");
|
|
2432
2616
|
* ```
|
|
2433
2617
|
*
|
|
2434
2618
|
* @group String
|
|
@@ -2505,25 +2689,41 @@ export const String = /*#__PURE__*/ createTypeOfType("String");
|
|
|
2505
2689
|
*/
|
|
2506
2690
|
export const Number = /*#__PURE__*/ createTypeOfType("Number");
|
|
2507
2691
|
|
|
2508
|
-
/**
|
|
2692
|
+
/**
|
|
2693
|
+
* A JavaScript bigint {@link Type}.
|
|
2694
|
+
*
|
|
2695
|
+
* @group Base
|
|
2696
|
+
*/
|
|
2509
2697
|
export const BigInt = /*#__PURE__*/ createTypeOfType("BigInt");
|
|
2510
2698
|
|
|
2511
|
-
/**
|
|
2699
|
+
/**
|
|
2700
|
+
* A JavaScript boolean {@link Type}.
|
|
2701
|
+
*
|
|
2702
|
+
* @group Base
|
|
2703
|
+
*/
|
|
2512
2704
|
export const Boolean = /*#__PURE__*/ createTypeOfType("Boolean");
|
|
2513
2705
|
|
|
2514
|
-
/**
|
|
2706
|
+
/**
|
|
2707
|
+
* A JavaScript symbol {@link Type}.
|
|
2708
|
+
*
|
|
2709
|
+
* @group Base
|
|
2710
|
+
*/
|
|
2515
2711
|
export const Symbol = /*#__PURE__*/ createTypeOfType("Symbol");
|
|
2516
2712
|
|
|
2517
|
-
/**
|
|
2713
|
+
/**
|
|
2714
|
+
* A JavaScript function {@link Type}.
|
|
2715
|
+
*
|
|
2716
|
+
* @group Base
|
|
2717
|
+
*/
|
|
2518
2718
|
export const Function = /*#__PURE__*/ createTypeOfType("Function");
|
|
2519
2719
|
|
|
2520
2720
|
/**
|
|
2521
|
-
*
|
|
2721
|
+
* A {@link Type} validating Evolu Type declarations.
|
|
2522
2722
|
*
|
|
2523
2723
|
* This is useful when a Type itself crosses an unknown boundary or must be
|
|
2524
2724
|
* asserted with {@link assertType}.
|
|
2525
2725
|
*
|
|
2526
|
-
* @group Core
|
|
2726
|
+
* @group Core
|
|
2527
2727
|
*/
|
|
2528
2728
|
export const EvoluType = /*#__PURE__*/ createType(
|
|
2529
2729
|
"EvoluType",
|
|
@@ -2545,23 +2745,23 @@ export const EvoluType = /*#__PURE__*/ createType(
|
|
|
2545
2745
|
* the recursive `TypeNode` shape so composing Types does not repeatedly add its
|
|
2546
2746
|
* compiler cost; {@link EvoluType} bridges that runtime evidence to this type.
|
|
2547
2747
|
*
|
|
2548
|
-
* @group Core
|
|
2748
|
+
* @group Core
|
|
2549
2749
|
*/
|
|
2550
2750
|
export interface AnyType extends TypeNode {
|
|
2551
2751
|
readonly [concreteTypeSymbol]: true;
|
|
2552
2752
|
}
|
|
2553
2753
|
|
|
2554
2754
|
/**
|
|
2555
|
-
* Error returned when
|
|
2755
|
+
* Error returned when {@link EvoluType} rejects a value.
|
|
2556
2756
|
*
|
|
2557
|
-
* @group Core
|
|
2757
|
+
* @group Core
|
|
2558
2758
|
*/
|
|
2559
2759
|
export interface EvoluTypeError extends TypeValueError<"EvoluType"> {}
|
|
2560
2760
|
|
|
2561
2761
|
/**
|
|
2562
2762
|
* Nominal evidence that a value has one object tag.
|
|
2563
2763
|
*
|
|
2564
|
-
* @group Base
|
|
2764
|
+
* @group Base
|
|
2565
2765
|
*/
|
|
2566
2766
|
export interface ObjectTag<Name extends TypeName> {
|
|
2567
2767
|
readonly [objectTagSymbol]: Name;
|
|
@@ -2570,7 +2770,7 @@ export interface ObjectTag<Name extends TypeName> {
|
|
|
2570
2770
|
/**
|
|
2571
2771
|
* The {@link Type} returned by {@link objectTag}.
|
|
2572
2772
|
*
|
|
2573
|
-
* @group Base
|
|
2773
|
+
* @group Base
|
|
2574
2774
|
*/
|
|
2575
2775
|
export interface ObjectTagType<
|
|
2576
2776
|
Name extends TypeName,
|
|
@@ -2597,7 +2797,7 @@ export interface ObjectTagType<
|
|
|
2597
2797
|
/**
|
|
2598
2798
|
* An error returned when an object does not report the expected tag.
|
|
2599
2799
|
*
|
|
2600
|
-
* @group Base
|
|
2800
|
+
* @group Base
|
|
2601
2801
|
*/
|
|
2602
2802
|
export interface ObjectTagError<
|
|
2603
2803
|
Expected extends TypeName = TypeName,
|
|
@@ -2628,7 +2828,18 @@ interface ObjectTagOutputByName {
|
|
|
2628
2828
|
* assumption of the predefined Type. Primitive Outputs are rejected at compile
|
|
2629
2829
|
* time.
|
|
2630
2830
|
*
|
|
2631
|
-
*
|
|
2831
|
+
* ### Example
|
|
2832
|
+
*
|
|
2833
|
+
* ```ts
|
|
2834
|
+
* import { objectTag } from "@evolu/common";
|
|
2835
|
+
*
|
|
2836
|
+
* const DateType = objectTag("Date");
|
|
2837
|
+
* const date = new globalThis.Date("2025-01-01T00:00:00.000Z");
|
|
2838
|
+
*
|
|
2839
|
+
* expectOk(DateType.fromUnknown(date), date);
|
|
2840
|
+
* ```
|
|
2841
|
+
*
|
|
2842
|
+
* @group Base
|
|
2632
2843
|
*/
|
|
2633
2844
|
export function objectTag<Name extends keyof ObjectTagOutputByName>(
|
|
2634
2845
|
name: ValidateConcreteTypeName<Name>,
|
|
@@ -2642,6 +2853,8 @@ export function objectTag<Name extends keyof ObjectTagOutputByName>(
|
|
|
2642
2853
|
never,
|
|
2643
2854
|
ObjectTagOutputByName[Name]
|
|
2644
2855
|
>;
|
|
2856
|
+
|
|
2857
|
+
/** Creates an object-tag Type by refining an existing object Type. */
|
|
2645
2858
|
export function objectTag<
|
|
2646
2859
|
Name extends TypeName,
|
|
2647
2860
|
OutputType extends ConcreteTypeNode & { readonly Output: object },
|
|
@@ -2656,6 +2869,9 @@ export function objectTag(
|
|
|
2656
2869
|
name: TypeName,
|
|
2657
2870
|
outputType?: ConcreteTypeNode & { readonly Output: object },
|
|
2658
2871
|
): TypeNode {
|
|
2872
|
+
const formatError: TypeErrorFormatter<ObjectTagError> = (error) =>
|
|
2873
|
+
`A value ${safelyStringifyUnknownValue(error.value)} does not have the expected object tag ${safelyStringifyUnknownValue(error.expected)}.`;
|
|
2874
|
+
|
|
2659
2875
|
if (outputType === undefined) {
|
|
2660
2876
|
return createRootType(
|
|
2661
2877
|
name,
|
|
@@ -2663,7 +2879,7 @@ export function objectTag(
|
|
|
2663
2879
|
hasObjectTag(value, name)
|
|
2664
2880
|
? ok(value as object)
|
|
2665
2881
|
: err({ type: "ObjectTag", expected: name, value }),
|
|
2666
|
-
|
|
2882
|
+
formatError,
|
|
2667
2883
|
);
|
|
2668
2884
|
}
|
|
2669
2885
|
|
|
@@ -2675,7 +2891,7 @@ export function objectTag(
|
|
|
2675
2891
|
hasObjectTag(value, name)
|
|
2676
2892
|
? ok(value as object & ObjectTag<TypeName>)
|
|
2677
2893
|
: err({ type: "ObjectTag", expected: name, value }),
|
|
2678
|
-
|
|
2894
|
+
formatError,
|
|
2679
2895
|
),
|
|
2680
2896
|
{ expected: name },
|
|
2681
2897
|
);
|
|
@@ -2683,9 +2899,6 @@ export function objectTag(
|
|
|
2683
2899
|
|
|
2684
2900
|
declare const objectTagSymbol: unique symbol;
|
|
2685
2901
|
|
|
2686
|
-
const formatObjectTagError: TypeErrorFormatter<ObjectTagError> = (error) =>
|
|
2687
|
-
`A value ${safelyStringifyUnknownValue(error.value)} does not have the object tag ${safelyStringifyUnknownValue(error.expected)}.`;
|
|
2688
|
-
|
|
2689
2902
|
const hasObjectTag = (value: unknown, expected: string): boolean =>
|
|
2690
2903
|
value !== null &&
|
|
2691
2904
|
(typeof value === "object" || typeof value === "function") &&
|
|
@@ -2696,7 +2909,7 @@ const hasObjectTag = (value: unknown, expected: string): boolean =>
|
|
|
2696
2909
|
*
|
|
2697
2910
|
* It trusts the reported object tag and does not verify Date internal slots.
|
|
2698
2911
|
*
|
|
2699
|
-
* @group Base
|
|
2912
|
+
* @group Base
|
|
2700
2913
|
*/
|
|
2701
2914
|
export const Date = /*#__PURE__*/ objectTag("Date");
|
|
2702
2915
|
|
|
@@ -2706,7 +2919,7 @@ export const Date = /*#__PURE__*/ objectTag("Date");
|
|
|
2706
2919
|
* It trusts the reported object tag and does not verify Uint8Array internal
|
|
2707
2920
|
* slots.
|
|
2708
2921
|
*
|
|
2709
|
-
* @group Base
|
|
2922
|
+
* @group Base
|
|
2710
2923
|
*/
|
|
2711
2924
|
export const Uint8Array = /*#__PURE__*/ objectTag("Uint8Array");
|
|
2712
2925
|
|
|
@@ -2716,7 +2929,7 @@ export const Uint8Array = /*#__PURE__*/ objectTag("Uint8Array");
|
|
|
2716
2929
|
* It trusts the reported object tag and does not verify ArrayBuffer internal
|
|
2717
2930
|
* slots.
|
|
2718
2931
|
*
|
|
2719
|
-
* @group Base
|
|
2932
|
+
* @group Base
|
|
2720
2933
|
*/
|
|
2721
2934
|
export const ArrayBuffer = /*#__PURE__*/ objectTag("ArrayBuffer");
|
|
2722
2935
|
|
|
@@ -2748,7 +2961,7 @@ export const ArrayBuffer = /*#__PURE__*/ objectTag("ArrayBuffer");
|
|
|
2748
2961
|
* assert(!UserInstance.is({ name: "Ada" }));
|
|
2749
2962
|
* ```
|
|
2750
2963
|
*
|
|
2751
|
-
* @group Base
|
|
2964
|
+
* @group Base
|
|
2752
2965
|
*/
|
|
2753
2966
|
export const instanceOf = <Constructor extends InstanceConstructor>(
|
|
2754
2967
|
constructor: ValidateInstanceConstructor<Constructor>,
|
|
@@ -2779,14 +2992,18 @@ export const instanceOf = <Constructor extends InstanceConstructor>(
|
|
|
2779
2992
|
/**
|
|
2780
2993
|
* A JavaScript class constructor accepted by {@link instanceOf}.
|
|
2781
2994
|
*
|
|
2782
|
-
* @group Base
|
|
2995
|
+
* @group Base
|
|
2783
2996
|
*/
|
|
2784
2997
|
export type InstanceConstructor<Instance extends object = object> =
|
|
2785
2998
|
(abstract new (...args: ReadonlyArray<never>) => Instance) & {
|
|
2786
2999
|
readonly name: string;
|
|
2787
3000
|
};
|
|
2788
3001
|
|
|
2789
|
-
/**
|
|
3002
|
+
/**
|
|
3003
|
+
* The {@link Type} returned by {@link instanceOf}.
|
|
3004
|
+
*
|
|
3005
|
+
* @group Base
|
|
3006
|
+
*/
|
|
2790
3007
|
export interface InstanceOfType<
|
|
2791
3008
|
Constructor extends InstanceConstructor,
|
|
2792
3009
|
> extends Type<
|
|
@@ -2807,7 +3024,11 @@ type InstanceOfOutput<Constructor extends InstanceConstructor> =
|
|
|
2807
3024
|
? Output
|
|
2808
3025
|
: InstanceType<Constructor>;
|
|
2809
3026
|
|
|
2810
|
-
/**
|
|
3027
|
+
/**
|
|
3028
|
+
* Error returned when a value is not an instance of the expected constructor.
|
|
3029
|
+
*
|
|
3030
|
+
* @group Base
|
|
3031
|
+
*/
|
|
2811
3032
|
export interface InstanceOfError extends TypeValueError<"InstanceOf"> {
|
|
2812
3033
|
readonly constructorName: string;
|
|
2813
3034
|
}
|
|
@@ -2895,7 +3116,11 @@ export const literal = <const Expected extends Literal>(
|
|
|
2895
3116
|
) as unknown as LiteralType<Expected>;
|
|
2896
3117
|
};
|
|
2897
3118
|
|
|
2898
|
-
/**
|
|
3119
|
+
/**
|
|
3120
|
+
* The {@link Type} returned by {@link literal}.
|
|
3121
|
+
*
|
|
3122
|
+
* @group Unions
|
|
3123
|
+
*/
|
|
2899
3124
|
export interface LiteralType<Expected extends Literal> extends Type<
|
|
2900
3125
|
"Literal",
|
|
2901
3126
|
WidenLiteral<Expected>,
|
|
@@ -2941,7 +3166,11 @@ type LiteralCompileTimeError = CompileTimeError<
|
|
|
2941
3166
|
"Expected must be one concrete literal value."
|
|
2942
3167
|
>;
|
|
2943
3168
|
|
|
2944
|
-
/**
|
|
3169
|
+
/**
|
|
3170
|
+
* Error returned when a value does not equal the expected literal.
|
|
3171
|
+
*
|
|
3172
|
+
* @group Unions
|
|
3173
|
+
*/
|
|
2945
3174
|
export interface LiteralError<
|
|
2946
3175
|
Expected extends Literal = Literal,
|
|
2947
3176
|
> extends TypeError<"Literal"> {
|
|
@@ -2949,10 +3178,18 @@ export interface LiteralError<
|
|
|
2949
3178
|
readonly value: unknown;
|
|
2950
3179
|
}
|
|
2951
3180
|
|
|
2952
|
-
/**
|
|
3181
|
+
/**
|
|
3182
|
+
* Literal {@link Type} accepting only `undefined`.
|
|
3183
|
+
*
|
|
3184
|
+
* @group Unions
|
|
3185
|
+
*/
|
|
2953
3186
|
export const Undefined = /*#__PURE__*/ literal(undefined);
|
|
2954
3187
|
|
|
2955
|
-
/**
|
|
3188
|
+
/**
|
|
3189
|
+
* Literal {@link Type} accepting only `null`.
|
|
3190
|
+
*
|
|
3191
|
+
* @group Unions
|
|
3192
|
+
*/
|
|
2956
3193
|
export const Null = /*#__PURE__*/ literal(null);
|
|
2957
3194
|
|
|
2958
3195
|
/**
|
|
@@ -2978,7 +3215,10 @@ export const Null = /*#__PURE__*/ literal(null);
|
|
|
2978
3215
|
* within each member.
|
|
2979
3216
|
*
|
|
2980
3217
|
* Member order matters when multiple members accept the same value: validation
|
|
2981
|
-
* and encoding use the first matching member.
|
|
3218
|
+
* and encoding use the first matching member. When member Inputs overlap,
|
|
3219
|
+
* decoding the value emitted by the first member selected for an Output must
|
|
3220
|
+
* reproduce that semantic Output; otherwise the Union violates the round-trip
|
|
3221
|
+
* law.
|
|
2982
3222
|
*
|
|
2983
3223
|
* ### Example
|
|
2984
3224
|
*
|
|
@@ -3017,11 +3257,15 @@ export function union<const Expected extends AtLeastTwoReadonlyArray<Literal>>(
|
|
|
3017
3257
|
): UnionType<{
|
|
3018
3258
|
readonly [Index in keyof Expected]: LiteralType<Expected[Index]>;
|
|
3019
3259
|
}>;
|
|
3260
|
+
|
|
3261
|
+
/** Creates a Union Type from Type members. */
|
|
3020
3262
|
export function union<const Members extends AtLeastTwoReadonlyArray<TypeNode>>(
|
|
3021
3263
|
...members: {
|
|
3022
3264
|
readonly [Index in keyof Members]: ValidateUnionTypeMember<Members[Index]>;
|
|
3023
3265
|
}
|
|
3024
3266
|
): UnionType<Members>;
|
|
3267
|
+
|
|
3268
|
+
/** Creates a Union Type from Type and literal members. */
|
|
3025
3269
|
export function union<
|
|
3026
3270
|
const Members extends AtLeastTwoReadonlyArray<TypeNode | Literal>,
|
|
3027
3271
|
>(
|
|
@@ -3046,7 +3290,8 @@ export function union(
|
|
|
3046
3290
|
inputMembers,
|
|
3047
3291
|
(member, value, options) => member[outputValidationSymbol](value, options),
|
|
3048
3292
|
);
|
|
3049
|
-
const defaultFormatter =
|
|
3293
|
+
const defaultFormatter = (() =>
|
|
3294
|
+
"A value does not match any allowed variant.") as TypeErrorFormatter<TypeError>;
|
|
3050
3295
|
const getTypeIssues: RuntimeGetTypeIssues = (error) =>
|
|
3051
3296
|
singleRuntimeTypeIssue("Union", error, defaultFormatter);
|
|
3052
3297
|
const input = createTypeNode<
|
|
@@ -3101,9 +3346,6 @@ export function union(
|
|
|
3101
3346
|
);
|
|
3102
3347
|
}
|
|
3103
3348
|
|
|
3104
|
-
const formatUnionError: TypeErrorFormatter<UnionErrorValue<TypeError>> = () =>
|
|
3105
|
-
"A value does not match any union member.";
|
|
3106
|
-
|
|
3107
3349
|
const createUnionValidation =
|
|
3108
3350
|
(
|
|
3109
3351
|
members: ReadonlyArray<RuntimeTypeNode>,
|
|
@@ -3146,6 +3388,16 @@ const createUnionValidation =
|
|
|
3146
3388
|
* This does not make an object property optional. It changes only the values
|
|
3147
3389
|
* accepted when the property is present.
|
|
3148
3390
|
*
|
|
3391
|
+
* ### Example
|
|
3392
|
+
*
|
|
3393
|
+
* ```ts
|
|
3394
|
+
* import { String, undefinedOr } from "@evolu/common";
|
|
3395
|
+
*
|
|
3396
|
+
* const StringOrUndefined = undefinedOr(String);
|
|
3397
|
+
*
|
|
3398
|
+
* expectOk(StringOrUndefined.fromUnknown(undefined), undefined);
|
|
3399
|
+
* ```
|
|
3400
|
+
*
|
|
3149
3401
|
* @group Unions
|
|
3150
3402
|
*/
|
|
3151
3403
|
export const undefinedOr = <ValueType extends TypeNode>(
|
|
@@ -3155,6 +3407,16 @@ export const undefinedOr = <ValueType extends TypeNode>(
|
|
|
3155
3407
|
/**
|
|
3156
3408
|
* Union {@link Type} containing the supplied Type and `null`.
|
|
3157
3409
|
*
|
|
3410
|
+
* ### Example
|
|
3411
|
+
*
|
|
3412
|
+
* ```ts
|
|
3413
|
+
* import { String, nullOr } from "@evolu/common";
|
|
3414
|
+
*
|
|
3415
|
+
* const NullableString = nullOr(String);
|
|
3416
|
+
*
|
|
3417
|
+
* expectOk(NullableString.fromUnknown(null), null);
|
|
3418
|
+
* ```
|
|
3419
|
+
*
|
|
3158
3420
|
* @group Unions
|
|
3159
3421
|
*/
|
|
3160
3422
|
export const nullOr = <ValueType extends TypeNode>(
|
|
@@ -3164,9 +3426,20 @@ export const nullOr = <ValueType extends TypeNode>(
|
|
|
3164
3426
|
/**
|
|
3165
3427
|
* Union {@link Type} containing the supplied Type, `null`, and `undefined`.
|
|
3166
3428
|
*
|
|
3167
|
-
*
|
|
3168
|
-
|
|
3169
|
-
|
|
3429
|
+
* ### Example
|
|
3430
|
+
*
|
|
3431
|
+
* ```ts
|
|
3432
|
+
* import { String, nullishOr } from "@evolu/common";
|
|
3433
|
+
*
|
|
3434
|
+
* const NullishString = nullishOr(String);
|
|
3435
|
+
*
|
|
3436
|
+
* expectOk(NullishString.fromUnknown(undefined), undefined);
|
|
3437
|
+
* expectOk(NullishString.fromUnknown(null), null);
|
|
3438
|
+
* ```
|
|
3439
|
+
*
|
|
3440
|
+
* @group Unions
|
|
3441
|
+
*/
|
|
3442
|
+
export const nullishOr = <ValueType extends TypeNode>(
|
|
3170
3443
|
type: ValidateUnionTypeMember<ValueType>,
|
|
3171
3444
|
): UnionType<readonly [ValueType, typeof Null, typeof Undefined]> =>
|
|
3172
3445
|
union(type, Null, Undefined);
|
|
@@ -3205,7 +3478,11 @@ type NormalizeUnionMembers<
|
|
|
3205
3478
|
? Normalized
|
|
3206
3479
|
: never;
|
|
3207
3480
|
|
|
3208
|
-
/**
|
|
3481
|
+
/**
|
|
3482
|
+
* The {@link Type} returned by {@link union}.
|
|
3483
|
+
*
|
|
3484
|
+
* @group Unions
|
|
3485
|
+
*/
|
|
3209
3486
|
export interface UnionType<
|
|
3210
3487
|
Members extends AtLeastTwoReadonlyArray<TypeNode>,
|
|
3211
3488
|
> extends Type<
|
|
@@ -3283,13 +3560,21 @@ type RootUnionMembers<Members extends AtLeastTwoReadonlyArray<TypeNode>> = {
|
|
|
3283
3560
|
? RootMembers
|
|
3284
3561
|
: never;
|
|
3285
3562
|
|
|
3286
|
-
/**
|
|
3563
|
+
/**
|
|
3564
|
+
* Error returned when every member of a {@link union} rejects an input.
|
|
3565
|
+
*
|
|
3566
|
+
* @group Unions
|
|
3567
|
+
*/
|
|
3287
3568
|
export type UnionError<
|
|
3288
3569
|
Error extends TypeError = TypeError,
|
|
3289
3570
|
MemberError extends UnionMemberError<Error> = UnionMemberError<Error>,
|
|
3290
3571
|
> = [Error] extends [never] ? never : UnionErrorValue<Error, MemberError>;
|
|
3291
3572
|
|
|
3292
|
-
/**
|
|
3573
|
+
/**
|
|
3574
|
+
* An error returned by one {@link union} member and its index.
|
|
3575
|
+
*
|
|
3576
|
+
* @group Unions
|
|
3577
|
+
*/
|
|
3293
3578
|
export interface UnionMemberError<
|
|
3294
3579
|
Error extends TypeError,
|
|
3295
3580
|
Index extends number = number,
|
|
@@ -3353,9 +3638,7 @@ interface UnionErrorValue<
|
|
|
3353
3638
|
}
|
|
3354
3639
|
|
|
3355
3640
|
/**
|
|
3356
|
-
* Template literal {@link Type}
|
|
3357
|
-
*
|
|
3358
|
-
* Parses and creates structured strings.
|
|
3641
|
+
* Template literal {@link Type} that parses canonical strings into Tuples.
|
|
3359
3642
|
*
|
|
3360
3643
|
* Accepts the same template parts as {@link templateLiteral}: fixed string
|
|
3361
3644
|
* literals and Types canonically encoded as strings. Instead of keeping Output
|
|
@@ -3371,10 +3654,10 @@ interface UnionErrorValue<
|
|
|
3371
3654
|
* reversibility and keeps capture boundaries unambiguous. Different capture
|
|
3372
3655
|
* Tuples must never encode to the same string. The parser provides predictable
|
|
3373
3656
|
* parsing without pathological backtracking and decodes each capture once, so
|
|
3374
|
-
* adversarial input cannot trigger exponential parser work. Fixed-width
|
|
3375
|
-
* may be adjacent, but only one variable-width capture is allowed.
|
|
3376
|
-
* that could join UTF-16 surrogate halves across parts are
|
|
3377
|
-
* construction.
|
|
3657
|
+
* adversarial input cannot trigger exponential parser work. Fixed-width
|
|
3658
|
+
* captures may be adjacent, but only one variable-width capture is allowed.
|
|
3659
|
+
* Declarations that could join UTF-16 surrogate halves across parts are
|
|
3660
|
+
* rejected during construction.
|
|
3378
3661
|
*
|
|
3379
3662
|
* Keep capture unions reasonably small to avoid excessive compiler work.
|
|
3380
3663
|
*
|
|
@@ -3411,10 +3694,9 @@ interface UnionErrorValue<
|
|
|
3411
3694
|
*
|
|
3412
3695
|
* // Parse an unknown string into structured data.
|
|
3413
3696
|
* const result = SupportedLocale.fromUnknown("cs-CZ");
|
|
3414
|
-
*
|
|
3697
|
+
* expectOk(result, ["cs", "CZ"]);
|
|
3415
3698
|
* const locale = result.value;
|
|
3416
3699
|
* expectTypeOf(locale).toEqualTypeOf<SupportedLocale>();
|
|
3417
|
-
* expect(locale).toEqual(["cs", "CZ"]);
|
|
3418
3700
|
* expectErr(SupportedLocale.fromUnknown("cs/CZ"), {
|
|
3419
3701
|
* type: "TemplateLiteral",
|
|
3420
3702
|
* value: "cs/CZ",
|
|
@@ -3473,10 +3755,9 @@ interface UnionErrorValue<
|
|
|
3473
3755
|
*
|
|
3474
3756
|
* // Decode the string into structured data.
|
|
3475
3757
|
* const result = ItemId.fromUnknown("item-42");
|
|
3476
|
-
*
|
|
3758
|
+
* expectOk(result, [42n]);
|
|
3477
3759
|
* const itemId = result.value;
|
|
3478
3760
|
* expectTypeOf(itemId).toEqualTypeOf<ItemId>();
|
|
3479
|
-
* expect(itemId).toEqual([42n]);
|
|
3480
3761
|
*
|
|
3481
3762
|
* // Encode the structured data into its canonical string.
|
|
3482
3763
|
* const itemIdLiteral = ItemId.to(itemId);
|
|
@@ -3569,12 +3850,14 @@ const createTemplateLiteralParserType = <
|
|
|
3569
3850
|
options,
|
|
3570
3851
|
false,
|
|
3571
3852
|
);
|
|
3572
|
-
return (
|
|
3573
|
-
|
|
3574
|
-
|
|
3575
|
-
|
|
3576
|
-
|
|
3577
|
-
|
|
3853
|
+
return (
|
|
3854
|
+
outputResult.ok
|
|
3855
|
+
? outputResult
|
|
3856
|
+
: err({
|
|
3857
|
+
type: "TemplateLiteral",
|
|
3858
|
+
outputError: outputResult.error,
|
|
3859
|
+
})
|
|
3860
|
+
) as Result<
|
|
3578
3861
|
TemplateLiteralCaptureTuple<Parts>["Output"],
|
|
3579
3862
|
TemplateLiteralRuntimeParseError<Parts>
|
|
3580
3863
|
>;
|
|
@@ -3635,7 +3918,8 @@ const createTemplateLiteralParserType = <
|
|
|
3635
3918
|
return singleRuntimeTypeIssue(
|
|
3636
3919
|
"TemplateLiteral",
|
|
3637
3920
|
error,
|
|
3638
|
-
|
|
3921
|
+
((error: TemplateLiteralError) =>
|
|
3922
|
+
`The value ${safelyStringifyUnknownValue(error.value)} does not match the template literal.`) as TypeErrorFormatter<TypeError>,
|
|
3639
3923
|
);
|
|
3640
3924
|
};
|
|
3641
3925
|
const canonicalStringFromUnknown = (
|
|
@@ -3726,7 +4010,11 @@ const createTemplateLiteralParserType = <
|
|
|
3726
4010
|
return type;
|
|
3727
4011
|
};
|
|
3728
4012
|
|
|
3729
|
-
/**
|
|
4013
|
+
/**
|
|
4014
|
+
* The parsing {@link Type} returned by {@link templateLiteralParser}.
|
|
4015
|
+
*
|
|
4016
|
+
* @group Template literals
|
|
4017
|
+
*/
|
|
3730
4018
|
export interface TemplateLiteralParserType<
|
|
3731
4019
|
Parts extends TemplateLiteralParts,
|
|
3732
4020
|
> extends Type<
|
|
@@ -3746,7 +4034,11 @@ export interface TemplateLiteralParserType<
|
|
|
3746
4034
|
readonly parts: Parts;
|
|
3747
4035
|
}
|
|
3748
4036
|
|
|
3749
|
-
/**
|
|
4037
|
+
/**
|
|
4038
|
+
* The validating string {@link Type} returned by {@link templateLiteral}.
|
|
4039
|
+
*
|
|
4040
|
+
* @group Template literals
|
|
4041
|
+
*/
|
|
3750
4042
|
export interface TemplateLiteralType<
|
|
3751
4043
|
Parts extends TemplateLiteralParts,
|
|
3752
4044
|
> extends Type<
|
|
@@ -3772,8 +4064,8 @@ export interface TemplateLiteralType<
|
|
|
3772
4064
|
* Creates a canonical string Type from fixed strings and string-encoded Types.
|
|
3773
4065
|
*
|
|
3774
4066
|
* Use this factory when Output should remain a string. Switch to
|
|
3775
|
-
* {@link templateLiteralParser} when the individual Type parts should be
|
|
3776
|
-
*
|
|
4067
|
+
* {@link templateLiteralParser} when the individual Type parts should be decoded
|
|
4068
|
+
* into a Tuple.
|
|
3777
4069
|
*
|
|
3778
4070
|
* ### Example
|
|
3779
4071
|
*
|
|
@@ -3830,15 +4122,15 @@ type TemplateLiteralCaptureFromStringError<T extends TypeNode> =
|
|
|
3830
4122
|
: InferErrors<T>
|
|
3831
4123
|
: never;
|
|
3832
4124
|
|
|
3833
|
-
/**
|
|
4125
|
+
/**
|
|
4126
|
+
* Error returned when a string does not match a template literal declaration.
|
|
4127
|
+
*
|
|
4128
|
+
* @group Template literals
|
|
4129
|
+
*/
|
|
3834
4130
|
export interface TemplateLiteralError extends TypeError<"TemplateLiteral"> {
|
|
3835
4131
|
readonly value: string;
|
|
3836
4132
|
}
|
|
3837
4133
|
|
|
3838
|
-
const formatTemplateLiteralError: TypeErrorFormatter<TemplateLiteralError> =
|
|
3839
|
-
(error) =>
|
|
3840
|
-
`The value ${safelyStringifyUnknownValue(error.value)} does not match the template literal.`;
|
|
3841
|
-
|
|
3842
4134
|
declare const templateLiteralStringBrandSymbol: unique symbol;
|
|
3843
4135
|
|
|
3844
4136
|
interface TemplateLiteralStringBrand<Parts extends TemplateLiteralParts> {
|
|
@@ -4084,9 +4376,7 @@ const compileTemplateLiteralParser = <Parts extends TemplateLiteralParts>(
|
|
|
4084
4376
|
}
|
|
4085
4377
|
|
|
4086
4378
|
return position === inputCodePoints.length
|
|
4087
|
-
? ok(
|
|
4088
|
-
captures as unknown as TemplateLiteralCaptureTuple<Parts>["Input"],
|
|
4089
|
-
)
|
|
4379
|
+
? ok(captures as unknown as TemplateLiteralCaptureTuple<Parts>["Input"])
|
|
4090
4380
|
: err({ type: "TemplateLiteral", value: input });
|
|
4091
4381
|
};
|
|
4092
4382
|
};
|
|
@@ -4254,68 +4544,10 @@ const getTemplateLiteralPartFraming = (
|
|
|
4254
4544
|
* });
|
|
4255
4545
|
* ```
|
|
4256
4546
|
*
|
|
4257
|
-
* To reuse a Brand constraint with different parent Types, define a
|
|
4258
|
-
* {@link BrandFactory}.
|
|
4259
|
-
*
|
|
4260
|
-
* ```ts
|
|
4261
|
-
* import {
|
|
4262
|
-
* String,
|
|
4263
|
-
* brand,
|
|
4264
|
-
* err,
|
|
4265
|
-
* minLength,
|
|
4266
|
-
* ok,
|
|
4267
|
-
* type Brand,
|
|
4268
|
-
* type BrandFactory,
|
|
4269
|
-
* type TypeError,
|
|
4270
|
-
* } from "@evolu/common";
|
|
4271
|
-
*
|
|
4272
|
-
* const trimmed: BrandFactory<"Trimmed", string, TrimmedError> = (
|
|
4273
|
-
* parent,
|
|
4274
|
-
* ) =>
|
|
4275
|
-
* brand(
|
|
4276
|
-
* "Trimmed",
|
|
4277
|
-
* parent,
|
|
4278
|
-
* (value) =>
|
|
4279
|
-
* value === value.trim()
|
|
4280
|
-
* ? ok()
|
|
4281
|
-
* : err<TrimmedError>({ type: "Trimmed", value }),
|
|
4282
|
-
* () => "Expected a string without surrounding whitespace.",
|
|
4283
|
-
* );
|
|
4284
|
-
*
|
|
4285
|
-
* const TrimmedString = trimmed(String);
|
|
4286
|
-
* type TrimmedString = typeof TrimmedString.Output;
|
|
4287
|
-
*
|
|
4288
|
-
* expectTypeOf<TrimmedString>().toEqualTypeOf<string & Brand<"Trimmed">>();
|
|
4289
|
-
*
|
|
4290
|
-
* const NonEmptyString = minLength(1)(String);
|
|
4291
|
-
* type NonEmptyString = typeof NonEmptyString.Output;
|
|
4292
|
-
*
|
|
4293
|
-
* expectTypeOf<NonEmptyString>().toEqualTypeOf<
|
|
4294
|
-
* string & Brand<"MinLength1">
|
|
4295
|
-
* >();
|
|
4296
|
-
*
|
|
4297
|
-
* const NonEmptyTrimmedString = minLength(1)(TrimmedString);
|
|
4298
|
-
* type NonEmptyTrimmedString = typeof NonEmptyTrimmedString.Output;
|
|
4299
|
-
*
|
|
4300
|
-
* expectTypeOf<NonEmptyTrimmedString>().toEqualTypeOf<
|
|
4301
|
-
* string & Brand<"Trimmed"> & Brand<"MinLength1">
|
|
4302
|
-
* >();
|
|
4303
|
-
*
|
|
4304
|
-
* interface TrimmedError extends TypeError<"Trimmed"> {
|
|
4305
|
-
* readonly value: string;
|
|
4306
|
-
* }
|
|
4307
|
-
*
|
|
4308
|
-
* // Validation from unknown.
|
|
4309
|
-
* expectOk(NonEmptyTrimmedString.fromUnknown("Evolu"), "Evolu");
|
|
4547
|
+
* To reuse and compose a Brand constraint with different parent Types, define a
|
|
4548
|
+
* {@link BrandFactory}.
|
|
4310
4549
|
*
|
|
4311
|
-
*
|
|
4312
|
-
* expectOk(
|
|
4313
|
-
* NonEmptyTrimmedString.from.parent(TrimmedString.orThrow("Evolu")),
|
|
4314
|
-
* "Evolu",
|
|
4315
|
-
* );
|
|
4316
|
-
* ```
|
|
4317
|
-
*
|
|
4318
|
-
* @group Type construction
|
|
4550
|
+
* @group Construction
|
|
4319
4551
|
*/
|
|
4320
4552
|
export function brand<
|
|
4321
4553
|
Name extends TypeName,
|
|
@@ -4325,6 +4557,8 @@ export function brand<
|
|
|
4325
4557
|
parent: ValidateParent<ParentType>,
|
|
4326
4558
|
validate?: (value: ParentType["Output"]) => Result<void, never>,
|
|
4327
4559
|
): BrandType<ParentType, Name, never>;
|
|
4560
|
+
|
|
4561
|
+
/** Creates a validated Brand Type with its own error formatter. */
|
|
4328
4562
|
export function brand<
|
|
4329
4563
|
Name extends TypeName,
|
|
4330
4564
|
ParentType extends ConcreteTypeNode,
|
|
@@ -4352,7 +4586,11 @@ export function brand(
|
|
|
4352
4586
|
);
|
|
4353
4587
|
}
|
|
4354
4588
|
|
|
4355
|
-
/**
|
|
4589
|
+
/**
|
|
4590
|
+
* The {@link Type} returned by {@link brand}.
|
|
4591
|
+
*
|
|
4592
|
+
* @group Construction
|
|
4593
|
+
*/
|
|
4356
4594
|
export interface BrandType<
|
|
4357
4595
|
ParentType extends TypeNode,
|
|
4358
4596
|
Name extends TypeName,
|
|
@@ -4409,7 +4647,11 @@ export const DateIso = /*#__PURE__*/ brand(
|
|
|
4409
4647
|
);
|
|
4410
4648
|
export type DateIso = typeof DateIso.Output;
|
|
4411
4649
|
|
|
4412
|
-
/**
|
|
4650
|
+
/**
|
|
4651
|
+
* Error returned when a string is not a canonical {@link DateIso}.
|
|
4652
|
+
*
|
|
4653
|
+
* @group String
|
|
4654
|
+
*/
|
|
4413
4655
|
export interface DateIsoError extends TypeError<"DateIso"> {
|
|
4414
4656
|
readonly value: string;
|
|
4415
4657
|
}
|
|
@@ -4417,6 +4659,18 @@ export interface DateIsoError extends TypeError<"DateIso"> {
|
|
|
4417
4659
|
/**
|
|
4418
4660
|
* Safely transforms a {@link Date} into a canonical {@link DateIso}.
|
|
4419
4661
|
*
|
|
4662
|
+
* ### Example
|
|
4663
|
+
*
|
|
4664
|
+
* ```ts
|
|
4665
|
+
* import { DateIsoFromDate } from "@evolu/common";
|
|
4666
|
+
*
|
|
4667
|
+
* const date = new globalThis.Date("2025-01-01T12:00:00.000Z");
|
|
4668
|
+
* const result = DateIsoFromDate.fromUnknown(date);
|
|
4669
|
+
*
|
|
4670
|
+
* expectOk(result, "2025-01-01T12:00:00.000Z");
|
|
4671
|
+
* expect(DateIsoFromDate.to(result.value)).toEqual(date);
|
|
4672
|
+
* ```
|
|
4673
|
+
*
|
|
4420
4674
|
* @group String
|
|
4421
4675
|
*/
|
|
4422
4676
|
export const DateIsoFromDate = /*#__PURE__*/ transform(
|
|
@@ -4434,7 +4688,11 @@ export const DateIsoFromDate = /*#__PURE__*/ transform(
|
|
|
4434
4688
|
() => "The Date cannot be represented as DateIso.",
|
|
4435
4689
|
);
|
|
4436
4690
|
|
|
4437
|
-
/**
|
|
4691
|
+
/**
|
|
4692
|
+
* Error returned when a {@link Date} cannot be represented as {@link DateIso}.
|
|
4693
|
+
*
|
|
4694
|
+
* @group String
|
|
4695
|
+
*/
|
|
4438
4696
|
export interface DateIsoFromDateError extends TypeError<"DateIsoFromDate"> {
|
|
4439
4697
|
readonly value: globalThis.Date;
|
|
4440
4698
|
}
|
|
@@ -4456,7 +4714,11 @@ export const Int64 = /*#__PURE__*/ brand(
|
|
|
4456
4714
|
);
|
|
4457
4715
|
export type Int64 = typeof Int64.Output;
|
|
4458
4716
|
|
|
4459
|
-
/**
|
|
4717
|
+
/**
|
|
4718
|
+
* Error returned when a bigint is outside the signed 64-bit {@link Int64} range.
|
|
4719
|
+
*
|
|
4720
|
+
* @group Number
|
|
4721
|
+
*/
|
|
4460
4722
|
export interface Int64Error extends TypeError<"Int64"> {
|
|
4461
4723
|
readonly value: bigint;
|
|
4462
4724
|
}
|
|
@@ -4478,7 +4740,12 @@ export const UInt64 = /*#__PURE__*/ brand(
|
|
|
4478
4740
|
);
|
|
4479
4741
|
export type UInt64 = typeof UInt64.Output;
|
|
4480
4742
|
|
|
4481
|
-
/**
|
|
4743
|
+
/**
|
|
4744
|
+
* Error returned when a bigint is outside the unsigned 64-bit {@link UInt64}
|
|
4745
|
+
* range.
|
|
4746
|
+
*
|
|
4747
|
+
* @group Number
|
|
4748
|
+
*/
|
|
4482
4749
|
export interface UInt64Error extends TypeError<"UInt64"> {
|
|
4483
4750
|
readonly value: bigint;
|
|
4484
4751
|
}
|
|
@@ -4535,7 +4802,7 @@ export interface UInt64Error extends TypeError<"UInt64"> {
|
|
|
4535
4802
|
* For numeric parameters encoded in a Brand name, use
|
|
4536
4803
|
* {@link ValidateBrandFactoryNumber}.
|
|
4537
4804
|
*
|
|
4538
|
-
* @group
|
|
4805
|
+
* @group Construction
|
|
4539
4806
|
*/
|
|
4540
4807
|
export type BrandFactory<
|
|
4541
4808
|
Name extends TypeName,
|
|
@@ -4605,7 +4872,7 @@ export type BrandFactory<
|
|
|
4605
4872
|
* lessThan(100 - 1)(Number);
|
|
4606
4873
|
* ```
|
|
4607
4874
|
*
|
|
4608
|
-
* @group
|
|
4875
|
+
* @group Construction
|
|
4609
4876
|
*/
|
|
4610
4877
|
export type ValidateBrandFactoryNumber<Value extends number> =
|
|
4611
4878
|
IsUnion<Value> extends false
|
|
@@ -4664,7 +4931,11 @@ export const capitalized: BrandFactory<
|
|
|
4664
4931
|
`The value ${safelyStringifyUnknownValue(error.value)} must be capitalized.`,
|
|
4665
4932
|
);
|
|
4666
4933
|
|
|
4667
|
-
/**
|
|
4934
|
+
/**
|
|
4935
|
+
* Error returned when {@link capitalized} rejects a string.
|
|
4936
|
+
*
|
|
4937
|
+
* @group String
|
|
4938
|
+
*/
|
|
4668
4939
|
export interface CapitalizedError extends TypeError<"Capitalized"> {
|
|
4669
4940
|
readonly value: string;
|
|
4670
4941
|
}
|
|
@@ -4680,6 +4951,16 @@ export type CapitalizedString = typeof CapitalizedString.Output;
|
|
|
4680
4951
|
/**
|
|
4681
4952
|
* String {@link Brand} without surrounding whitespace.
|
|
4682
4953
|
*
|
|
4954
|
+
* ### Example
|
|
4955
|
+
*
|
|
4956
|
+
* ```ts
|
|
4957
|
+
* import { String, trimmed } from "@evolu/common";
|
|
4958
|
+
*
|
|
4959
|
+
* const Trimmed = trimmed(String);
|
|
4960
|
+
*
|
|
4961
|
+
* expectOk(Trimmed.fromUnknown("Evolu"), "Evolu");
|
|
4962
|
+
* ```
|
|
4963
|
+
*
|
|
4683
4964
|
* @group String
|
|
4684
4965
|
*/
|
|
4685
4966
|
export const trimmed: BrandFactory<"Trimmed", string, TrimmedError> = (
|
|
@@ -4696,7 +4977,11 @@ export const trimmed: BrandFactory<"Trimmed", string, TrimmedError> = (
|
|
|
4696
4977
|
`The value ${safelyStringifyUnknownValue(error.value)} must be trimmed.`,
|
|
4697
4978
|
);
|
|
4698
4979
|
|
|
4699
|
-
/**
|
|
4980
|
+
/**
|
|
4981
|
+
* Error returned when {@link trimmed} rejects a string.
|
|
4982
|
+
*
|
|
4983
|
+
* @group String
|
|
4984
|
+
*/
|
|
4700
4985
|
export interface TrimmedError extends TypeError<"Trimmed"> {
|
|
4701
4986
|
readonly value: string;
|
|
4702
4987
|
}
|
|
@@ -4718,13 +5003,33 @@ export type TrimmedString = typeof TrimmedString.Output;
|
|
|
4718
5003
|
/**
|
|
4719
5004
|
* Trims a string and returns a {@link TrimmedString}.
|
|
4720
5005
|
*
|
|
5006
|
+
* ### Example
|
|
5007
|
+
*
|
|
5008
|
+
* ```ts
|
|
5009
|
+
* import { trim } from "@evolu/common";
|
|
5010
|
+
*
|
|
5011
|
+
* expect(trim(" Evolu ")).toBe("Evolu");
|
|
5012
|
+
* ```
|
|
5013
|
+
*
|
|
4721
5014
|
* @group String
|
|
4722
5015
|
*/
|
|
4723
5016
|
export const trim = (value: string): TrimmedString =>
|
|
4724
5017
|
value.trim() as TrimmedString;
|
|
4725
5018
|
|
|
4726
5019
|
/**
|
|
4727
|
-
* Minimum-length {@link Brand}
|
|
5020
|
+
* Minimum-length {@link Brand} for values whose `length` is at least `min`.
|
|
5021
|
+
*
|
|
5022
|
+
* ### Example
|
|
5023
|
+
*
|
|
5024
|
+
* ```ts
|
|
5025
|
+
* import { String, array, minLength } from "@evolu/common";
|
|
5026
|
+
*
|
|
5027
|
+
* const AtLeastThreeCharacters = minLength(3)(String);
|
|
5028
|
+
* const AtLeastTwoItems = minLength(2)(array(String));
|
|
5029
|
+
*
|
|
5030
|
+
* expectOk(AtLeastThreeCharacters.fromUnknown("abc"), "abc");
|
|
5031
|
+
* expectOk(AtLeastTwoItems.fromUnknown(["a", "b"]), ["a", "b"]);
|
|
5032
|
+
* ```
|
|
4728
5033
|
*
|
|
4729
5034
|
* @group String
|
|
4730
5035
|
* @group Collection
|
|
@@ -4748,7 +5053,11 @@ export const minLength =
|
|
|
4748
5053
|
);
|
|
4749
5054
|
};
|
|
4750
5055
|
|
|
4751
|
-
/**
|
|
5056
|
+
/**
|
|
5057
|
+
* Error returned when {@link minLength} rejects a value.
|
|
5058
|
+
*
|
|
5059
|
+
* @group Collection
|
|
5060
|
+
*/
|
|
4752
5061
|
export interface MinLengthError<
|
|
4753
5062
|
Min extends number = number,
|
|
4754
5063
|
> extends TypeError<`MinLength${Min}`> {
|
|
@@ -4774,7 +5083,19 @@ export const NonEmptyTrimmedString = /*#__PURE__*/ minLength(1)(TrimmedString);
|
|
|
4774
5083
|
export type NonEmptyTrimmedString = typeof NonEmptyTrimmedString.Output;
|
|
4775
5084
|
|
|
4776
5085
|
/**
|
|
4777
|
-
* Maximum-length {@link Brand}
|
|
5086
|
+
* Maximum-length {@link Brand} for values whose `length` is at most `max`.
|
|
5087
|
+
*
|
|
5088
|
+
* ### Example
|
|
5089
|
+
*
|
|
5090
|
+
* ```ts
|
|
5091
|
+
* import { String, array, maxLength } from "@evolu/common";
|
|
5092
|
+
*
|
|
5093
|
+
* const AtMostThreeCharacters = maxLength(3)(String);
|
|
5094
|
+
* const AtMostTwoItems = maxLength(2)(array(String));
|
|
5095
|
+
*
|
|
5096
|
+
* expectOk(AtMostThreeCharacters.fromUnknown("abc"), "abc");
|
|
5097
|
+
* expectOk(AtMostTwoItems.fromUnknown(["a", "b"]), ["a", "b"]);
|
|
5098
|
+
* ```
|
|
4778
5099
|
*
|
|
4779
5100
|
* @group String
|
|
4780
5101
|
* @group Collection
|
|
@@ -4798,7 +5119,11 @@ export const maxLength =
|
|
|
4798
5119
|
);
|
|
4799
5120
|
};
|
|
4800
5121
|
|
|
4801
|
-
/**
|
|
5122
|
+
/**
|
|
5123
|
+
* Error returned when {@link maxLength} rejects a value.
|
|
5124
|
+
*
|
|
5125
|
+
* @group Collection
|
|
5126
|
+
*/
|
|
4802
5127
|
export interface MaxLengthError<
|
|
4803
5128
|
Max extends number = number,
|
|
4804
5129
|
> extends TypeError<`MaxLength${Max}`> {
|
|
@@ -4827,7 +5152,19 @@ export const NonEmptyTrimmedString1000 = /*#__PURE__*/ maxLength(1000)(
|
|
|
4827
5152
|
export type NonEmptyTrimmedString1000 = typeof NonEmptyTrimmedString1000.Output;
|
|
4828
5153
|
|
|
4829
5154
|
/**
|
|
4830
|
-
* Exact-length {@link Brand}
|
|
5155
|
+
* Exact-length {@link Brand} for values whose `length` equals `exact`.
|
|
5156
|
+
*
|
|
5157
|
+
* ### Example
|
|
5158
|
+
*
|
|
5159
|
+
* ```ts
|
|
5160
|
+
* import { String, array, length } from "@evolu/common";
|
|
5161
|
+
*
|
|
5162
|
+
* const ThreeCharacters = length(3)(String);
|
|
5163
|
+
* const TwoItems = length(2)(array(String));
|
|
5164
|
+
*
|
|
5165
|
+
* expectOk(ThreeCharacters.fromUnknown("abc"), "abc");
|
|
5166
|
+
* expectOk(TwoItems.fromUnknown(["a", "b"]), ["a", "b"]);
|
|
5167
|
+
* ```
|
|
4831
5168
|
*
|
|
4832
5169
|
* @group String
|
|
4833
5170
|
* @group Collection
|
|
@@ -4851,7 +5188,11 @@ export const length =
|
|
|
4851
5188
|
);
|
|
4852
5189
|
};
|
|
4853
5190
|
|
|
4854
|
-
/**
|
|
5191
|
+
/**
|
|
5192
|
+
* Error returned when {@link length} rejects a value.
|
|
5193
|
+
*
|
|
5194
|
+
* @group Collection
|
|
5195
|
+
*/
|
|
4855
5196
|
export interface LengthError<
|
|
4856
5197
|
Exact extends number = number,
|
|
4857
5198
|
> extends TypeError<`Length${Exact}`> {
|
|
@@ -4917,7 +5258,12 @@ export const regex = <const Name extends TypeName>(
|
|
|
4917
5258
|
);
|
|
4918
5259
|
};
|
|
4919
5260
|
|
|
4920
|
-
/**
|
|
5261
|
+
/**
|
|
5262
|
+
* Error returned when a string does not match the regular expression supplied
|
|
5263
|
+
* to {@link regex}.
|
|
5264
|
+
*
|
|
5265
|
+
* @group String
|
|
5266
|
+
*/
|
|
4921
5267
|
export interface RegexError<
|
|
4922
5268
|
Name extends TypeName = TypeName,
|
|
4923
5269
|
> extends TypeError<Name> {
|
|
@@ -5005,7 +5351,11 @@ export const Base64Url = /*#__PURE__*/ brand(
|
|
|
5005
5351
|
);
|
|
5006
5352
|
export type Base64Url = typeof Base64Url.Output;
|
|
5007
5353
|
|
|
5008
|
-
/**
|
|
5354
|
+
/**
|
|
5355
|
+
* Error returned when a string is not valid {@link Base64Url} text.
|
|
5356
|
+
*
|
|
5357
|
+
* @group String
|
|
5358
|
+
*/
|
|
5009
5359
|
export interface Base64UrlError extends TypeError<"Base64Url"> {
|
|
5010
5360
|
readonly value: string;
|
|
5011
5361
|
}
|
|
@@ -5013,6 +5363,16 @@ export interface Base64UrlError extends TypeError<"Base64Url"> {
|
|
|
5013
5363
|
/**
|
|
5014
5364
|
* Encodes bytes as {@link Base64Url}.
|
|
5015
5365
|
*
|
|
5366
|
+
* ### Example
|
|
5367
|
+
*
|
|
5368
|
+
* ```ts
|
|
5369
|
+
* import { uint8ArrayToBase64Url } from "@evolu/common";
|
|
5370
|
+
*
|
|
5371
|
+
* expect(uint8ArrayToBase64Url(new Uint8Array([0, 1, 2, 255]))).toBe(
|
|
5372
|
+
* "AAEC_w",
|
|
5373
|
+
* );
|
|
5374
|
+
* ```
|
|
5375
|
+
*
|
|
5016
5376
|
* @group String
|
|
5017
5377
|
*/
|
|
5018
5378
|
export const uint8ArrayToBase64Url = (bytes: Uint8Array): Base64Url =>
|
|
@@ -5021,6 +5381,18 @@ export const uint8ArrayToBase64Url = (bytes: Uint8Array): Base64Url =>
|
|
|
5021
5381
|
/**
|
|
5022
5382
|
* Decodes {@link Base64Url} as bytes.
|
|
5023
5383
|
*
|
|
5384
|
+
* ### Example
|
|
5385
|
+
*
|
|
5386
|
+
* ```ts
|
|
5387
|
+
* import { Base64Url, base64UrlToUint8Array } from "@evolu/common";
|
|
5388
|
+
*
|
|
5389
|
+
* const value = Base64Url.orThrow("AAEC_w");
|
|
5390
|
+
*
|
|
5391
|
+
* expect(base64UrlToUint8Array(value)).toEqual(
|
|
5392
|
+
* new Uint8Array([0, 1, 2, 255]),
|
|
5393
|
+
* );
|
|
5394
|
+
* ```
|
|
5395
|
+
*
|
|
5024
5396
|
* @group String
|
|
5025
5397
|
*/
|
|
5026
5398
|
export const base64UrlToUint8Array = (value: Base64Url): Uint8Array =>
|
|
@@ -5041,7 +5413,11 @@ export const Name = /*#__PURE__*/ brand(
|
|
|
5041
5413
|
);
|
|
5042
5414
|
export type Name = typeof Name.Output;
|
|
5043
5415
|
|
|
5044
|
-
/**
|
|
5416
|
+
/**
|
|
5417
|
+
* Error returned when a string is not a valid {@link Name}.
|
|
5418
|
+
*
|
|
5419
|
+
* @group String
|
|
5420
|
+
*/
|
|
5045
5421
|
export interface NameError extends TypeError<"Name"> {
|
|
5046
5422
|
readonly value: string;
|
|
5047
5423
|
}
|
|
@@ -5077,11 +5453,15 @@ export const Mnemonic = /*#__PURE__*/ brand(
|
|
|
5077
5453
|
? ok()
|
|
5078
5454
|
: err<MnemonicError>({ type: "Mnemonic", value }),
|
|
5079
5455
|
(error) =>
|
|
5080
|
-
`The value ${safelyStringifyUnknownValue(error.value)} is not a valid BIP39 mnemonic.`,
|
|
5456
|
+
`The value ${safelyStringifyUnknownValue(error.value)} is not a valid English BIP39 mnemonic.`,
|
|
5081
5457
|
);
|
|
5082
5458
|
export type Mnemonic = typeof Mnemonic.Output;
|
|
5083
5459
|
|
|
5084
|
-
/**
|
|
5460
|
+
/**
|
|
5461
|
+
* Error returned when a string is not a valid English BIP39 {@link Mnemonic}.
|
|
5462
|
+
*
|
|
5463
|
+
* @group String
|
|
5464
|
+
*/
|
|
5085
5465
|
export interface MnemonicError extends TypeError<"Mnemonic"> {
|
|
5086
5466
|
readonly value: string;
|
|
5087
5467
|
}
|
|
@@ -5108,7 +5488,11 @@ export const Id = /*#__PURE__*/ brand(
|
|
|
5108
5488
|
);
|
|
5109
5489
|
export type Id = typeof Id.Output;
|
|
5110
5490
|
|
|
5111
|
-
/**
|
|
5491
|
+
/**
|
|
5492
|
+
* Error returned when a string is not a valid {@link Id}.
|
|
5493
|
+
*
|
|
5494
|
+
* @group String
|
|
5495
|
+
*/
|
|
5112
5496
|
export interface IdError extends TypeError<"Id"> {
|
|
5113
5497
|
readonly value: string;
|
|
5114
5498
|
}
|
|
@@ -5119,6 +5503,22 @@ export interface IdError extends TypeError<"Id"> {
|
|
|
5119
5503
|
* This is the recommended default because it does not encode creation time.
|
|
5120
5504
|
* Pass a Brand name when the returned Id belongs to one domain entity.
|
|
5121
5505
|
*
|
|
5506
|
+
* ### Example
|
|
5507
|
+
*
|
|
5508
|
+
* ```ts
|
|
5509
|
+
* import {
|
|
5510
|
+
* Id,
|
|
5511
|
+
* createId,
|
|
5512
|
+
* createRandomBytes,
|
|
5513
|
+
* type Brand,
|
|
5514
|
+
* } from "@evolu/common";
|
|
5515
|
+
*
|
|
5516
|
+
* const userId = createId<"User">({ randomBytes: createRandomBytes() });
|
|
5517
|
+
*
|
|
5518
|
+
* expect(Id.is(userId)).toBe(true);
|
|
5519
|
+
* expectTypeOf(userId).toEqualTypeOf<Id & Brand<"User">>();
|
|
5520
|
+
* ```
|
|
5521
|
+
*
|
|
5122
5522
|
* @group String
|
|
5123
5523
|
*/
|
|
5124
5524
|
export const createId = <B extends string = never>(
|
|
@@ -5165,6 +5565,26 @@ export const createIdFromString = <B extends string = never>(
|
|
|
5165
5565
|
* logs, URLs, and exports. Prefer {@link createId} unless that tradeoff is
|
|
5166
5566
|
* deliberate.
|
|
5167
5567
|
*
|
|
5568
|
+
* ### Example
|
|
5569
|
+
*
|
|
5570
|
+
* ```ts
|
|
5571
|
+
* import {
|
|
5572
|
+
* createIdAsUuidv7,
|
|
5573
|
+
* createRandomBytes,
|
|
5574
|
+
* createTime,
|
|
5575
|
+
* idToIdBytes,
|
|
5576
|
+
* } from "@evolu/common";
|
|
5577
|
+
*
|
|
5578
|
+
* const value = createIdAsUuidv7({
|
|
5579
|
+
* randomBytes: createRandomBytes(),
|
|
5580
|
+
* time: createTime(),
|
|
5581
|
+
* });
|
|
5582
|
+
* const bytes = idToIdBytes(value);
|
|
5583
|
+
*
|
|
5584
|
+
* expect(bytes[6] >> 4).toBe(0x7);
|
|
5585
|
+
* expect(bytes[8] & 0xc0).toBe(0x80);
|
|
5586
|
+
* ```
|
|
5587
|
+
*
|
|
5168
5588
|
* @group String
|
|
5169
5589
|
*/
|
|
5170
5590
|
export const createIdAsUuidv7 = <B extends string = never>(
|
|
@@ -5189,6 +5609,22 @@ export const createIdAsUuidv7 = <B extends string = never>(
|
|
|
5189
5609
|
/**
|
|
5190
5610
|
* Table-specific {@link Id} Type.
|
|
5191
5611
|
*
|
|
5612
|
+
* ### Example
|
|
5613
|
+
*
|
|
5614
|
+
* ```ts
|
|
5615
|
+
* import {
|
|
5616
|
+
* createIdFromString,
|
|
5617
|
+
* id,
|
|
5618
|
+
* type Brand,
|
|
5619
|
+
* type Id,
|
|
5620
|
+
* } from "@evolu/common";
|
|
5621
|
+
*
|
|
5622
|
+
* const TodoId = id("Todo");
|
|
5623
|
+
* const todoId = TodoId.orThrow(createIdFromString("todo"));
|
|
5624
|
+
*
|
|
5625
|
+
* expectTypeOf(todoId).toEqualTypeOf<Id & Brand<"Todo">>();
|
|
5626
|
+
* ```
|
|
5627
|
+
*
|
|
5192
5628
|
* @group String
|
|
5193
5629
|
*/
|
|
5194
5630
|
export const id = <Table extends TypeName>(
|
|
@@ -5211,7 +5647,11 @@ export const id = <Table extends TypeName>(
|
|
|
5211
5647
|
);
|
|
5212
5648
|
};
|
|
5213
5649
|
|
|
5214
|
-
/**
|
|
5650
|
+
/**
|
|
5651
|
+
* The {@link Type} returned by {@link id} for one table.
|
|
5652
|
+
*
|
|
5653
|
+
* @group String
|
|
5654
|
+
*/
|
|
5215
5655
|
export interface TableId<Table extends TypeName> extends Type<
|
|
5216
5656
|
"TableId",
|
|
5217
5657
|
string,
|
|
@@ -5226,7 +5666,11 @@ export interface TableId<Table extends TypeName> extends Type<
|
|
|
5226
5666
|
readonly table: Table;
|
|
5227
5667
|
}
|
|
5228
5668
|
|
|
5229
|
-
/**
|
|
5669
|
+
/**
|
|
5670
|
+
* Error returned when a string is not a valid {@link Id} for the expected table.
|
|
5671
|
+
*
|
|
5672
|
+
* @group String
|
|
5673
|
+
*/
|
|
5230
5674
|
export interface TableIdError<
|
|
5231
5675
|
Table extends TypeName = TypeName,
|
|
5232
5676
|
> extends TypeError<"TableId"> {
|
|
@@ -5278,15 +5722,51 @@ export const IdBytes = /*#__PURE__*/ brand(
|
|
|
5278
5722
|
);
|
|
5279
5723
|
export type IdBytes = typeof IdBytes.Output;
|
|
5280
5724
|
|
|
5281
|
-
/**
|
|
5725
|
+
/**
|
|
5726
|
+
* Byte length of an {@link IdBytes} value.
|
|
5727
|
+
*
|
|
5728
|
+
* @group String
|
|
5729
|
+
*/
|
|
5282
5730
|
export const idBytesTypeValueLength = 16 as NonNegativeInt;
|
|
5283
5731
|
|
|
5284
|
-
/**
|
|
5732
|
+
/**
|
|
5733
|
+
* Converts an {@link Id} to {@link IdBytes}.
|
|
5734
|
+
*
|
|
5735
|
+
* ### Example
|
|
5736
|
+
*
|
|
5737
|
+
* ```ts
|
|
5738
|
+
* import { createIdFromString, idToIdBytes } from "@evolu/common";
|
|
5739
|
+
*
|
|
5740
|
+
* const bytes = idToIdBytes(createIdFromString("todo"));
|
|
5741
|
+
*
|
|
5742
|
+
* expect(bytes).toHaveLength(16);
|
|
5743
|
+
* ```
|
|
5744
|
+
*
|
|
5745
|
+
* @group String
|
|
5746
|
+
*/
|
|
5285
5747
|
export const idToIdBytes = (value: Id): IdBytes =>
|
|
5286
5748
|
base64UrlToUint8Array(value as unknown as Base64Url) as IdBytes;
|
|
5287
5749
|
|
|
5288
|
-
/**
|
|
5289
|
-
|
|
5750
|
+
/**
|
|
5751
|
+
* Converts {@link IdBytes} to an {@link Id}.
|
|
5752
|
+
*
|
|
5753
|
+
* ### Example
|
|
5754
|
+
*
|
|
5755
|
+
* ```ts
|
|
5756
|
+
* import {
|
|
5757
|
+
* createIdFromString,
|
|
5758
|
+
* idBytesToId,
|
|
5759
|
+
* idToIdBytes,
|
|
5760
|
+
* } from "@evolu/common";
|
|
5761
|
+
*
|
|
5762
|
+
* const value = createIdFromString("todo");
|
|
5763
|
+
*
|
|
5764
|
+
* expect(idBytesToId(idToIdBytes(value))).toBe(value);
|
|
5765
|
+
* ```
|
|
5766
|
+
*
|
|
5767
|
+
* @group String
|
|
5768
|
+
*/
|
|
5769
|
+
export const idBytesToId = (value: IdBytes): Id =>
|
|
5290
5770
|
uint8ArrayToBase64Url(value) as unknown as Id;
|
|
5291
5771
|
|
|
5292
5772
|
/**
|
|
@@ -5319,7 +5799,11 @@ export const Int64String = /*#__PURE__*/ brand(
|
|
|
5319
5799
|
);
|
|
5320
5800
|
export type Int64String = typeof Int64String.Output;
|
|
5321
5801
|
|
|
5322
|
-
/**
|
|
5802
|
+
/**
|
|
5803
|
+
* Error returned when a string is not a canonical {@link Int64String}.
|
|
5804
|
+
*
|
|
5805
|
+
* @group Number
|
|
5806
|
+
*/
|
|
5323
5807
|
export interface Int64StringError extends TypeError<"Int64String"> {
|
|
5324
5808
|
readonly value: string;
|
|
5325
5809
|
}
|
|
@@ -5331,6 +5815,17 @@ export interface Int64StringError extends TypeError<"Int64String"> {
|
|
|
5331
5815
|
* text, including SQLite queries that cast INTEGER values to TEXT to avoid a
|
|
5332
5816
|
* lossy JavaScript number conversion.
|
|
5333
5817
|
*
|
|
5818
|
+
* ### Example
|
|
5819
|
+
*
|
|
5820
|
+
* ```ts
|
|
5821
|
+
* import { Int64FromInt64String } from "@evolu/common";
|
|
5822
|
+
*
|
|
5823
|
+
* const result = Int64FromInt64String.fromUnknown("9223372036854775807");
|
|
5824
|
+
*
|
|
5825
|
+
* expectOk(result, 9223372036854775807n);
|
|
5826
|
+
* expect(Int64FromInt64String.to(result.value)).toBe("9223372036854775807");
|
|
5827
|
+
* ```
|
|
5828
|
+
*
|
|
5334
5829
|
* @group Number
|
|
5335
5830
|
*/
|
|
5336
5831
|
export const Int64FromInt64String = /*#__PURE__*/ transform(
|
|
@@ -5346,6 +5841,16 @@ export const Int64FromInt64String = /*#__PURE__*/ transform(
|
|
|
5346
5841
|
/**
|
|
5347
5842
|
* Number {@link Brand} requiring a value greater than or equal to zero.
|
|
5348
5843
|
*
|
|
5844
|
+
* ### Example
|
|
5845
|
+
*
|
|
5846
|
+
* ```ts
|
|
5847
|
+
* import { Number, nonNegative } from "@evolu/common";
|
|
5848
|
+
*
|
|
5849
|
+
* const NonNegative = nonNegative(Number);
|
|
5850
|
+
*
|
|
5851
|
+
* expectOk(NonNegative.fromUnknown(0), 0);
|
|
5852
|
+
* ```
|
|
5853
|
+
*
|
|
5349
5854
|
* @group Number
|
|
5350
5855
|
*/
|
|
5351
5856
|
export const nonNegative: BrandFactory<
|
|
@@ -5362,7 +5867,11 @@ export const nonNegative: BrandFactory<
|
|
|
5362
5867
|
`The value ${safelyStringifyUnknownValue(error.value)} must be non-negative (>= 0).`,
|
|
5363
5868
|
);
|
|
5364
5869
|
|
|
5365
|
-
/**
|
|
5870
|
+
/**
|
|
5871
|
+
* Error returned when {@link nonNegative} rejects a number.
|
|
5872
|
+
*
|
|
5873
|
+
* @group Number
|
|
5874
|
+
*/
|
|
5366
5875
|
export interface NonNegativeError extends TypeError<"NonNegative"> {
|
|
5367
5876
|
readonly value: number;
|
|
5368
5877
|
}
|
|
@@ -5378,6 +5887,16 @@ export type NonNegativeNumber = typeof NonNegativeNumber.Output;
|
|
|
5378
5887
|
/**
|
|
5379
5888
|
* Number {@link Brand} requiring a value greater than zero.
|
|
5380
5889
|
*
|
|
5890
|
+
* ### Example
|
|
5891
|
+
*
|
|
5892
|
+
* ```ts
|
|
5893
|
+
* import { Number, positive } from "@evolu/common";
|
|
5894
|
+
*
|
|
5895
|
+
* const Positive = positive(Number);
|
|
5896
|
+
*
|
|
5897
|
+
* expectOk(Positive.fromUnknown(1), 1);
|
|
5898
|
+
* ```
|
|
5899
|
+
*
|
|
5381
5900
|
* @group Number
|
|
5382
5901
|
*/
|
|
5383
5902
|
export const positive: BrandFactory<"Positive", number, PositiveError> = (
|
|
@@ -5392,7 +5911,11 @@ export const positive: BrandFactory<"Positive", number, PositiveError> = (
|
|
|
5392
5911
|
`The value ${safelyStringifyUnknownValue(error.value)} must be positive (> 0).`,
|
|
5393
5912
|
);
|
|
5394
5913
|
|
|
5395
|
-
/**
|
|
5914
|
+
/**
|
|
5915
|
+
* Error returned when {@link positive} rejects a number.
|
|
5916
|
+
*
|
|
5917
|
+
* @group Number
|
|
5918
|
+
*/
|
|
5396
5919
|
export interface PositiveError extends TypeError<"Positive"> {
|
|
5397
5920
|
readonly value: number;
|
|
5398
5921
|
}
|
|
@@ -5411,6 +5934,16 @@ export type PositiveNumber = typeof PositiveNumber.Output;
|
|
|
5411
5934
|
/**
|
|
5412
5935
|
* Number {@link Brand} requiring a value less than or equal to zero.
|
|
5413
5936
|
*
|
|
5937
|
+
* ### Example
|
|
5938
|
+
*
|
|
5939
|
+
* ```ts
|
|
5940
|
+
* import { Number, nonPositive } from "@evolu/common";
|
|
5941
|
+
*
|
|
5942
|
+
* const NonPositive = nonPositive(Number);
|
|
5943
|
+
*
|
|
5944
|
+
* expectOk(NonPositive.fromUnknown(0), 0);
|
|
5945
|
+
* ```
|
|
5946
|
+
*
|
|
5414
5947
|
* @group Number
|
|
5415
5948
|
*/
|
|
5416
5949
|
export const nonPositive: BrandFactory<
|
|
@@ -5427,7 +5960,11 @@ export const nonPositive: BrandFactory<
|
|
|
5427
5960
|
`The value ${safelyStringifyUnknownValue(error.value)} must be non-positive (<= 0).`,
|
|
5428
5961
|
);
|
|
5429
5962
|
|
|
5430
|
-
/**
|
|
5963
|
+
/**
|
|
5964
|
+
* Error returned when {@link nonPositive} rejects a number.
|
|
5965
|
+
*
|
|
5966
|
+
* @group Number
|
|
5967
|
+
*/
|
|
5431
5968
|
export interface NonPositiveError extends TypeError<"NonPositive"> {
|
|
5432
5969
|
readonly value: number;
|
|
5433
5970
|
}
|
|
@@ -5443,6 +5980,16 @@ export type NonPositiveNumber = typeof NonPositiveNumber.Output;
|
|
|
5443
5980
|
/**
|
|
5444
5981
|
* Number {@link Brand} requiring a value less than zero.
|
|
5445
5982
|
*
|
|
5983
|
+
* ### Example
|
|
5984
|
+
*
|
|
5985
|
+
* ```ts
|
|
5986
|
+
* import { Number, negative } from "@evolu/common";
|
|
5987
|
+
*
|
|
5988
|
+
* const Negative = negative(Number);
|
|
5989
|
+
*
|
|
5990
|
+
* expectOk(Negative.fromUnknown(-1), -1);
|
|
5991
|
+
* ```
|
|
5992
|
+
*
|
|
5446
5993
|
* @group Number
|
|
5447
5994
|
*/
|
|
5448
5995
|
export const negative: BrandFactory<"Negative", number, NegativeError> = (
|
|
@@ -5457,7 +6004,11 @@ export const negative: BrandFactory<"Negative", number, NegativeError> = (
|
|
|
5457
6004
|
`The value ${safelyStringifyUnknownValue(error.value)} must be negative (< 0).`,
|
|
5458
6005
|
);
|
|
5459
6006
|
|
|
5460
|
-
/**
|
|
6007
|
+
/**
|
|
6008
|
+
* Error returned when {@link negative} rejects a number.
|
|
6009
|
+
*
|
|
6010
|
+
* @group Number
|
|
6011
|
+
*/
|
|
5461
6012
|
export interface NegativeError extends TypeError<"Negative"> {
|
|
5462
6013
|
readonly value: number;
|
|
5463
6014
|
}
|
|
@@ -5476,6 +6027,16 @@ export type NegativeNumber = typeof NegativeNumber.Output;
|
|
|
5476
6027
|
/**
|
|
5477
6028
|
* Number {@link Brand} requiring a value other than `NaN`.
|
|
5478
6029
|
*
|
|
6030
|
+
* ### Example
|
|
6031
|
+
*
|
|
6032
|
+
* ```ts
|
|
6033
|
+
* import { Number, nonNaN } from "@evolu/common";
|
|
6034
|
+
*
|
|
6035
|
+
* const NonNaN = nonNaN(Number);
|
|
6036
|
+
*
|
|
6037
|
+
* expectOk(NonNaN.fromUnknown(Infinity), Infinity);
|
|
6038
|
+
* ```
|
|
6039
|
+
*
|
|
5479
6040
|
* @group Number
|
|
5480
6041
|
*/
|
|
5481
6042
|
export const nonNaN: BrandFactory<"NonNaN", number, NonNaNError> = (parent) =>
|
|
@@ -5489,7 +6050,11 @@ export const nonNaN: BrandFactory<"NonNaN", number, NonNaNError> = (parent) =>
|
|
|
5489
6050
|
() => "The value must not be NaN.",
|
|
5490
6051
|
);
|
|
5491
6052
|
|
|
5492
|
-
/**
|
|
6053
|
+
/**
|
|
6054
|
+
* Error returned when {@link nonNaN} rejects `NaN`.
|
|
6055
|
+
*
|
|
6056
|
+
* @group Number
|
|
6057
|
+
*/
|
|
5493
6058
|
export interface NonNaNError extends TypeError<"NonNaN"> {
|
|
5494
6059
|
readonly value: number;
|
|
5495
6060
|
}
|
|
@@ -5510,6 +6075,16 @@ export type NonNaNNumber = typeof NonNaNNumber.Output;
|
|
|
5510
6075
|
/**
|
|
5511
6076
|
* Number {@link Brand} requiring a finite value.
|
|
5512
6077
|
*
|
|
6078
|
+
* ### Example
|
|
6079
|
+
*
|
|
6080
|
+
* ```ts
|
|
6081
|
+
* import { Number, finite } from "@evolu/common";
|
|
6082
|
+
*
|
|
6083
|
+
* const Finite = finite(Number);
|
|
6084
|
+
*
|
|
6085
|
+
* expectOk(Finite.fromUnknown(42), 42);
|
|
6086
|
+
* ```
|
|
6087
|
+
*
|
|
5513
6088
|
* @group Number
|
|
5514
6089
|
*/
|
|
5515
6090
|
export const finite: BrandFactory<"Finite", number, FiniteError> = (parent) =>
|
|
@@ -5524,7 +6099,11 @@ export const finite: BrandFactory<"Finite", number, FiniteError> = (parent) =>
|
|
|
5524
6099
|
`The value ${safelyStringifyUnknownValue(error.value)} must be finite.`,
|
|
5525
6100
|
);
|
|
5526
6101
|
|
|
5527
|
-
/**
|
|
6102
|
+
/**
|
|
6103
|
+
* Error returned when {@link finite} rejects a non-finite number.
|
|
6104
|
+
*
|
|
6105
|
+
* @group Number
|
|
6106
|
+
*/
|
|
5528
6107
|
export interface FiniteError extends TypeError<"Finite"> {
|
|
5529
6108
|
readonly value: number;
|
|
5530
6109
|
}
|
|
@@ -5589,7 +6168,11 @@ export const int: BrandFactory<"Int", number, IntError> = (parent) =>
|
|
|
5589
6168
|
`The value ${safelyStringifyUnknownValue(error.value)} must be a safe integer.`,
|
|
5590
6169
|
);
|
|
5591
6170
|
|
|
5592
|
-
/**
|
|
6171
|
+
/**
|
|
6172
|
+
* Error returned when {@link int} rejects a number that is not a safe integer.
|
|
6173
|
+
*
|
|
6174
|
+
* @group Number
|
|
6175
|
+
*/
|
|
5593
6176
|
export interface IntError extends TypeError<"Int"> {
|
|
5594
6177
|
readonly value: number;
|
|
5595
6178
|
}
|
|
@@ -5665,6 +6248,16 @@ export type NegativeInt = typeof NegativeInt.Output;
|
|
|
5665
6248
|
/**
|
|
5666
6249
|
* Number {@link Brand} requiring a value greater than `min`.
|
|
5667
6250
|
*
|
|
6251
|
+
* ### Example
|
|
6252
|
+
*
|
|
6253
|
+
* ```ts
|
|
6254
|
+
* import { Number, greaterThan } from "@evolu/common";
|
|
6255
|
+
*
|
|
6256
|
+
* const GreaterThanTen = greaterThan(10)(Number);
|
|
6257
|
+
*
|
|
6258
|
+
* expectOk(GreaterThanTen.fromUnknown(11), 11);
|
|
6259
|
+
* ```
|
|
6260
|
+
*
|
|
5668
6261
|
* @group Number
|
|
5669
6262
|
*/
|
|
5670
6263
|
export const greaterThan =
|
|
@@ -5686,7 +6279,11 @@ export const greaterThan =
|
|
|
5686
6279
|
);
|
|
5687
6280
|
};
|
|
5688
6281
|
|
|
5689
|
-
/**
|
|
6282
|
+
/**
|
|
6283
|
+
* Error returned when {@link greaterThan} rejects a number.
|
|
6284
|
+
*
|
|
6285
|
+
* @group Number
|
|
6286
|
+
*/
|
|
5690
6287
|
export interface GreaterThanError<
|
|
5691
6288
|
Min extends number = number,
|
|
5692
6289
|
> extends TypeError<`GreaterThan${Min}`> {
|
|
@@ -5697,6 +6294,16 @@ export interface GreaterThanError<
|
|
|
5697
6294
|
/**
|
|
5698
6295
|
* Number {@link Brand} requiring a value greater than or equal to `min`.
|
|
5699
6296
|
*
|
|
6297
|
+
* ### Example
|
|
6298
|
+
*
|
|
6299
|
+
* ```ts
|
|
6300
|
+
* import { Number, greaterThanOrEqualTo } from "@evolu/common";
|
|
6301
|
+
*
|
|
6302
|
+
* const AtLeastTen = greaterThanOrEqualTo(10)(Number);
|
|
6303
|
+
*
|
|
6304
|
+
* expectOk(AtLeastTen.fromUnknown(10), 10);
|
|
6305
|
+
* ```
|
|
6306
|
+
*
|
|
5700
6307
|
* @group Number
|
|
5701
6308
|
*/
|
|
5702
6309
|
export const greaterThanOrEqualTo =
|
|
@@ -5726,7 +6333,11 @@ export const greaterThanOrEqualTo =
|
|
|
5726
6333
|
);
|
|
5727
6334
|
};
|
|
5728
6335
|
|
|
5729
|
-
/**
|
|
6336
|
+
/**
|
|
6337
|
+
* Error returned when {@link greaterThanOrEqualTo} rejects a number.
|
|
6338
|
+
*
|
|
6339
|
+
* @group Number
|
|
6340
|
+
*/
|
|
5730
6341
|
export interface GreaterThanOrEqualToError<
|
|
5731
6342
|
Min extends number = number,
|
|
5732
6343
|
> extends TypeError<`GreaterThanOrEqualTo${Min}`> {
|
|
@@ -5737,6 +6348,16 @@ export interface GreaterThanOrEqualToError<
|
|
|
5737
6348
|
/**
|
|
5738
6349
|
* Number {@link Brand} requiring a value less than `max`.
|
|
5739
6350
|
*
|
|
6351
|
+
* ### Example
|
|
6352
|
+
*
|
|
6353
|
+
* ```ts
|
|
6354
|
+
* import { Number, lessThan } from "@evolu/common";
|
|
6355
|
+
*
|
|
6356
|
+
* const LessThanTen = lessThan(10)(Number);
|
|
6357
|
+
*
|
|
6358
|
+
* expectOk(LessThanTen.fromUnknown(9), 9);
|
|
6359
|
+
* ```
|
|
6360
|
+
*
|
|
5740
6361
|
* @group Number
|
|
5741
6362
|
*/
|
|
5742
6363
|
export const lessThan =
|
|
@@ -5758,7 +6379,11 @@ export const lessThan =
|
|
|
5758
6379
|
);
|
|
5759
6380
|
};
|
|
5760
6381
|
|
|
5761
|
-
/**
|
|
6382
|
+
/**
|
|
6383
|
+
* Error returned when {@link lessThan} rejects a number.
|
|
6384
|
+
*
|
|
6385
|
+
* @group Number
|
|
6386
|
+
*/
|
|
5762
6387
|
export interface LessThanError<
|
|
5763
6388
|
Max extends number = number,
|
|
5764
6389
|
> extends TypeError<`LessThan${Max}`> {
|
|
@@ -5780,6 +6405,16 @@ export type Age = typeof Age.Output;
|
|
|
5780
6405
|
/**
|
|
5781
6406
|
* Number {@link Brand} requiring a value less than or equal to `max`.
|
|
5782
6407
|
*
|
|
6408
|
+
* ### Example
|
|
6409
|
+
*
|
|
6410
|
+
* ```ts
|
|
6411
|
+
* import { Number, lessThanOrEqualTo } from "@evolu/common";
|
|
6412
|
+
*
|
|
6413
|
+
* const AtMostTen = lessThanOrEqualTo(10)(Number);
|
|
6414
|
+
*
|
|
6415
|
+
* expectOk(AtMostTen.fromUnknown(10), 10);
|
|
6416
|
+
* ```
|
|
6417
|
+
*
|
|
5783
6418
|
* @group Number
|
|
5784
6419
|
*/
|
|
5785
6420
|
export const lessThanOrEqualTo =
|
|
@@ -5805,7 +6440,11 @@ export const lessThanOrEqualTo =
|
|
|
5805
6440
|
);
|
|
5806
6441
|
};
|
|
5807
6442
|
|
|
5808
|
-
/**
|
|
6443
|
+
/**
|
|
6444
|
+
* Error returned when {@link lessThanOrEqualTo} rejects a number.
|
|
6445
|
+
*
|
|
6446
|
+
* @group Number
|
|
6447
|
+
*/
|
|
5809
6448
|
export interface LessThanOrEqualToError<
|
|
5810
6449
|
Max extends number = number,
|
|
5811
6450
|
> extends TypeError<`LessThanOrEqualTo${Max}`> {
|
|
@@ -5879,7 +6518,11 @@ export const DecimalString = /*#__PURE__*/ brand(
|
|
|
5879
6518
|
);
|
|
5880
6519
|
export type DecimalString = typeof DecimalString.Output;
|
|
5881
6520
|
|
|
5882
|
-
/**
|
|
6521
|
+
/**
|
|
6522
|
+
* Error returned when a string is not a canonical {@link DecimalString}.
|
|
6523
|
+
*
|
|
6524
|
+
* @group Number
|
|
6525
|
+
*/
|
|
5883
6526
|
export interface DecimalStringError extends TypeError<"DecimalString"> {
|
|
5884
6527
|
readonly value: string;
|
|
5885
6528
|
}
|
|
@@ -5887,6 +6530,16 @@ export interface DecimalStringError extends TypeError<"DecimalString"> {
|
|
|
5887
6530
|
/**
|
|
5888
6531
|
* {@link DecimalString} Brand requiring a value greater than or equal to zero.
|
|
5889
6532
|
*
|
|
6533
|
+
* ### Example
|
|
6534
|
+
*
|
|
6535
|
+
* ```ts
|
|
6536
|
+
* import { DecimalString, nonNegativeDecimalString } from "@evolu/common";
|
|
6537
|
+
*
|
|
6538
|
+
* const NonNegative = nonNegativeDecimalString(DecimalString);
|
|
6539
|
+
*
|
|
6540
|
+
* expectOk(NonNegative.fromUnknown("0.5"), "0.5");
|
|
6541
|
+
* ```
|
|
6542
|
+
*
|
|
5890
6543
|
* @group Number
|
|
5891
6544
|
*/
|
|
5892
6545
|
export const nonNegativeDecimalString: BrandFactory<
|
|
@@ -5908,7 +6561,11 @@ export const nonNegativeDecimalString: BrandFactory<
|
|
|
5908
6561
|
`The value ${safelyStringifyUnknownValue(error.value)} must be a non-negative decimal string.`,
|
|
5909
6562
|
);
|
|
5910
6563
|
|
|
5911
|
-
/**
|
|
6564
|
+
/**
|
|
6565
|
+
* Error returned when {@link nonNegativeDecimalString} rejects a decimal string.
|
|
6566
|
+
*
|
|
6567
|
+
* @group Number
|
|
6568
|
+
*/
|
|
5912
6569
|
export interface NonNegativeDecimalStringError extends TypeError<"NonNegativeDecimalString"> {
|
|
5913
6570
|
readonly value: string;
|
|
5914
6571
|
}
|
|
@@ -5925,6 +6582,16 @@ export type NonNegativeDecimalString = typeof NonNegativeDecimalString.Output;
|
|
|
5925
6582
|
/**
|
|
5926
6583
|
* {@link DecimalString} Brand requiring a value greater than zero.
|
|
5927
6584
|
*
|
|
6585
|
+
* ### Example
|
|
6586
|
+
*
|
|
6587
|
+
* ```ts
|
|
6588
|
+
* import { DecimalString, positiveDecimalString } from "@evolu/common";
|
|
6589
|
+
*
|
|
6590
|
+
* const Positive = positiveDecimalString(DecimalString);
|
|
6591
|
+
*
|
|
6592
|
+
* expectOk(Positive.fromUnknown("0.5"), "0.5");
|
|
6593
|
+
* ```
|
|
6594
|
+
*
|
|
5928
6595
|
* @group Number
|
|
5929
6596
|
*/
|
|
5930
6597
|
export const positiveDecimalString: BrandFactory<
|
|
@@ -5946,7 +6613,11 @@ export const positiveDecimalString: BrandFactory<
|
|
|
5946
6613
|
`The value ${safelyStringifyUnknownValue(error.value)} must be a positive decimal string.`,
|
|
5947
6614
|
);
|
|
5948
6615
|
|
|
5949
|
-
/**
|
|
6616
|
+
/**
|
|
6617
|
+
* Error returned when {@link positiveDecimalString} rejects a decimal string.
|
|
6618
|
+
*
|
|
6619
|
+
* @group Number
|
|
6620
|
+
*/
|
|
5950
6621
|
export interface PositiveDecimalStringError extends TypeError<"PositiveDecimalString"> {
|
|
5951
6622
|
readonly value: string;
|
|
5952
6623
|
}
|
|
@@ -5967,6 +6638,16 @@ export type PositiveDecimalString = typeof PositiveDecimalString.Output;
|
|
|
5967
6638
|
/**
|
|
5968
6639
|
* {@link DecimalString} Brand requiring a value less than or equal to zero.
|
|
5969
6640
|
*
|
|
6641
|
+
* ### Example
|
|
6642
|
+
*
|
|
6643
|
+
* ```ts
|
|
6644
|
+
* import { DecimalString, nonPositiveDecimalString } from "@evolu/common";
|
|
6645
|
+
*
|
|
6646
|
+
* const NonPositive = nonPositiveDecimalString(DecimalString);
|
|
6647
|
+
*
|
|
6648
|
+
* expectOk(NonPositive.fromUnknown("-0.5"), "-0.5");
|
|
6649
|
+
* ```
|
|
6650
|
+
*
|
|
5970
6651
|
* @group Number
|
|
5971
6652
|
*/
|
|
5972
6653
|
export const nonPositiveDecimalString: BrandFactory<
|
|
@@ -5988,7 +6669,11 @@ export const nonPositiveDecimalString: BrandFactory<
|
|
|
5988
6669
|
`The value ${safelyStringifyUnknownValue(error.value)} must be a non-positive decimal string.`,
|
|
5989
6670
|
);
|
|
5990
6671
|
|
|
5991
|
-
/**
|
|
6672
|
+
/**
|
|
6673
|
+
* Error returned when {@link nonPositiveDecimalString} rejects a decimal string.
|
|
6674
|
+
*
|
|
6675
|
+
* @group Number
|
|
6676
|
+
*/
|
|
5992
6677
|
export interface NonPositiveDecimalStringError extends TypeError<"NonPositiveDecimalString"> {
|
|
5993
6678
|
readonly value: string;
|
|
5994
6679
|
}
|
|
@@ -6005,6 +6690,16 @@ export type NonPositiveDecimalString = typeof NonPositiveDecimalString.Output;
|
|
|
6005
6690
|
/**
|
|
6006
6691
|
* {@link DecimalString} Brand requiring a value less than zero.
|
|
6007
6692
|
*
|
|
6693
|
+
* ### Example
|
|
6694
|
+
*
|
|
6695
|
+
* ```ts
|
|
6696
|
+
* import { DecimalString, negativeDecimalString } from "@evolu/common";
|
|
6697
|
+
*
|
|
6698
|
+
* const Negative = negativeDecimalString(DecimalString);
|
|
6699
|
+
*
|
|
6700
|
+
* expectOk(Negative.fromUnknown("-0.5"), "-0.5");
|
|
6701
|
+
* ```
|
|
6702
|
+
*
|
|
6008
6703
|
* @group Number
|
|
6009
6704
|
*/
|
|
6010
6705
|
export const negativeDecimalString: BrandFactory<
|
|
@@ -6026,7 +6721,11 @@ export const negativeDecimalString: BrandFactory<
|
|
|
6026
6721
|
`The value ${safelyStringifyUnknownValue(error.value)} must be a negative decimal string.`,
|
|
6027
6722
|
);
|
|
6028
6723
|
|
|
6029
|
-
/**
|
|
6724
|
+
/**
|
|
6725
|
+
* Error returned when {@link negativeDecimalString} rejects a decimal string.
|
|
6726
|
+
*
|
|
6727
|
+
* @group Number
|
|
6728
|
+
*/
|
|
6030
6729
|
export interface NegativeDecimalStringError extends TypeError<"NegativeDecimalString"> {
|
|
6031
6730
|
readonly value: string;
|
|
6032
6731
|
}
|
|
@@ -6118,7 +6817,11 @@ export const multipleOf = <const Divisor extends string>(
|
|
|
6118
6817
|
);
|
|
6119
6818
|
};
|
|
6120
6819
|
|
|
6121
|
-
/**
|
|
6820
|
+
/**
|
|
6821
|
+
* Error returned when {@link multipleOf} rejects a number.
|
|
6822
|
+
*
|
|
6823
|
+
* @group Number
|
|
6824
|
+
*/
|
|
6122
6825
|
export interface MultipleOfError<
|
|
6123
6826
|
Divisor extends string = string,
|
|
6124
6827
|
> extends TypeError<`MultipleOf${Divisor}`> {
|
|
@@ -6197,6 +6900,16 @@ const decimalStringToParts = (value: string): DecimalParts => {
|
|
|
6197
6900
|
/**
|
|
6198
6901
|
* Number {@link Brand} requiring a value within an inclusive range.
|
|
6199
6902
|
*
|
|
6903
|
+
* ### Example
|
|
6904
|
+
*
|
|
6905
|
+
* ```ts
|
|
6906
|
+
* import { Number, between } from "@evolu/common";
|
|
6907
|
+
*
|
|
6908
|
+
* const Percentage = between(0, 100)(Number);
|
|
6909
|
+
*
|
|
6910
|
+
* expectOk(Percentage.fromUnknown(75), 75);
|
|
6911
|
+
* ```
|
|
6912
|
+
*
|
|
6200
6913
|
* @group Number
|
|
6201
6914
|
*/
|
|
6202
6915
|
export const between =
|
|
@@ -6224,7 +6937,11 @@ export const between =
|
|
|
6224
6937
|
);
|
|
6225
6938
|
};
|
|
6226
6939
|
|
|
6227
|
-
/**
|
|
6940
|
+
/**
|
|
6941
|
+
* Error returned when {@link between} rejects a number.
|
|
6942
|
+
*
|
|
6943
|
+
* @group Number
|
|
6944
|
+
*/
|
|
6228
6945
|
export interface BetweenError<
|
|
6229
6946
|
Min extends number = number,
|
|
6230
6947
|
Max extends number = number,
|
|
@@ -6292,7 +7009,11 @@ export const array = <ElementType extends ConcreteTypeNode>(
|
|
|
6292
7009
|
arrayRuntimeConfig,
|
|
6293
7010
|
) as ArrayType<ElementType>;
|
|
6294
7011
|
|
|
6295
|
-
/**
|
|
7012
|
+
/**
|
|
7013
|
+
* The homogeneous readonly-array {@link Type} returned by {@link array}.
|
|
7014
|
+
*
|
|
7015
|
+
* @group Collection
|
|
7016
|
+
*/
|
|
6296
7017
|
export interface ArrayType<ElementType extends TypeNode> extends Type<
|
|
6297
7018
|
"Array",
|
|
6298
7019
|
ReadonlyArray<ElementType["Input"]>,
|
|
@@ -6371,11 +7092,19 @@ type ArrayNodeError<ElementType extends TypeNode> = [
|
|
|
6371
7092
|
? ArrayElementsError<ElementType["Error"]>
|
|
6372
7093
|
: ArrayError<ElementType["Error"]>;
|
|
6373
7094
|
|
|
6374
|
-
/**
|
|
7095
|
+
/**
|
|
7096
|
+
* Error returned by {@link array} for a non-array value or invalid array items.
|
|
7097
|
+
*
|
|
7098
|
+
* @group Collection
|
|
7099
|
+
*/
|
|
6375
7100
|
export type ArrayError<Error extends TypeError = TypeError> =
|
|
6376
7101
|
ArrayNotArrayError | ArrayItemsErrorValue<Error, true>;
|
|
6377
7102
|
|
|
6378
|
-
/**
|
|
7103
|
+
/**
|
|
7104
|
+
* Error returned when an {@link array} input is not an array.
|
|
7105
|
+
*
|
|
7106
|
+
* @group Collection
|
|
7107
|
+
*/
|
|
6379
7108
|
export interface ArrayNotArrayError extends TypeError<"Array"> {
|
|
6380
7109
|
readonly reason: {
|
|
6381
7110
|
readonly kind: "NotArray";
|
|
@@ -6383,29 +7112,49 @@ export interface ArrayNotArrayError extends TypeError<"Array"> {
|
|
|
6383
7112
|
};
|
|
6384
7113
|
}
|
|
6385
7114
|
|
|
6386
|
-
/**
|
|
7115
|
+
/**
|
|
7116
|
+
* An {@link array} error containing structural or element issues.
|
|
7117
|
+
*
|
|
7118
|
+
* @group Collection
|
|
7119
|
+
*/
|
|
6387
7120
|
export type ArrayItemsError<Error extends TypeError> = ArrayItemsErrorValue<
|
|
6388
7121
|
Error,
|
|
6389
7122
|
true
|
|
6390
7123
|
>;
|
|
6391
7124
|
|
|
6392
|
-
/**
|
|
7125
|
+
/**
|
|
7126
|
+
* One structural or element issue found by {@link array}.
|
|
7127
|
+
*
|
|
7128
|
+
* @group Collection
|
|
7129
|
+
*/
|
|
6393
7130
|
export type ArrayIssue<Error extends TypeError> =
|
|
6394
7131
|
ArrayStructuralIssue | ArrayElementIssue<Error>;
|
|
6395
7132
|
|
|
6396
|
-
/**
|
|
7133
|
+
/**
|
|
7134
|
+
* A missing array element.
|
|
7135
|
+
*
|
|
7136
|
+
* @group Collection
|
|
7137
|
+
*/
|
|
6397
7138
|
export interface ArrayHoleIssue {
|
|
6398
7139
|
readonly kind: "Hole";
|
|
6399
7140
|
readonly index: number;
|
|
6400
7141
|
}
|
|
6401
7142
|
|
|
6402
|
-
/**
|
|
7143
|
+
/**
|
|
7144
|
+
* An array element defined by an accessor instead of a data property.
|
|
7145
|
+
*
|
|
7146
|
+
* @group Collection
|
|
7147
|
+
*/
|
|
6403
7148
|
export interface ArrayAccessorIssue {
|
|
6404
7149
|
readonly kind: "Accessor";
|
|
6405
7150
|
readonly index: number;
|
|
6406
7151
|
}
|
|
6407
7152
|
|
|
6408
|
-
/**
|
|
7153
|
+
/**
|
|
7154
|
+
* An own array property other than `length` or an indexed element.
|
|
7155
|
+
*
|
|
7156
|
+
* @group Collection
|
|
7157
|
+
*/
|
|
6409
7158
|
export interface ArrayExcessPropertyIssue {
|
|
6410
7159
|
readonly kind: "ExcessProperty";
|
|
6411
7160
|
readonly key: string | symbol;
|
|
@@ -6414,7 +7163,11 @@ export interface ArrayExcessPropertyIssue {
|
|
|
6414
7163
|
type ArrayStructuralIssue =
|
|
6415
7164
|
ArrayHoleIssue | ArrayAccessorIssue | ArrayExcessPropertyIssue;
|
|
6416
7165
|
|
|
6417
|
-
/**
|
|
7166
|
+
/**
|
|
7167
|
+
* An invalid array element and its index.
|
|
7168
|
+
*
|
|
7169
|
+
* @group Collection
|
|
7170
|
+
*/
|
|
6418
7171
|
export type ArrayElementIssue<Error extends TypeError> = Error extends TypeError
|
|
6419
7172
|
? {
|
|
6420
7173
|
readonly kind: "Element";
|
|
@@ -6423,7 +7176,11 @@ export type ArrayElementIssue<Error extends TypeError> = Error extends TypeError
|
|
|
6423
7176
|
}
|
|
6424
7177
|
: never;
|
|
6425
7178
|
|
|
6426
|
-
/**
|
|
7179
|
+
/**
|
|
7180
|
+
* An {@link array} error containing element errors from a typed boundary.
|
|
7181
|
+
*
|
|
7182
|
+
* @group Collection
|
|
7183
|
+
*/
|
|
6427
7184
|
export type ArrayElementsError<Error extends TypeError> = [Error] extends [
|
|
6428
7185
|
never,
|
|
6429
7186
|
]
|
|
@@ -6494,23 +7251,6 @@ const isArrayCollection = (
|
|
|
6494
7251
|
return true;
|
|
6495
7252
|
};
|
|
6496
7253
|
|
|
6497
|
-
const formatArrayError: TypeErrorFormatter<ArrayError> = (error) => {
|
|
6498
|
-
if (error.reason.kind === "NotArray") {
|
|
6499
|
-
return `A value ${safelyStringifyUnknownValue(error.reason.value)} is not an array.`;
|
|
6500
|
-
}
|
|
6501
|
-
|
|
6502
|
-
const issue = error.reason.issues[0] as ArrayStructuralIssue;
|
|
6503
|
-
|
|
6504
|
-
switch (issue.kind) {
|
|
6505
|
-
case "Hole":
|
|
6506
|
-
return `An array element at index ${issue.index} is missing.`;
|
|
6507
|
-
case "Accessor":
|
|
6508
|
-
return `An array element at index ${issue.index} must be a data property.`;
|
|
6509
|
-
case "ExcessProperty":
|
|
6510
|
-
return "An excess Array property is not allowed. Remove it or use a different Type.";
|
|
6511
|
-
}
|
|
6512
|
-
};
|
|
6513
|
-
|
|
6514
7254
|
const arrayRuntimeConfig: HomogeneousCollectionRuntimeConfig<
|
|
6515
7255
|
ReadonlyArray<unknown>
|
|
6516
7256
|
> = {
|
|
@@ -6521,7 +7261,23 @@ const arrayRuntimeConfig: HomogeneousCollectionRuntimeConfig<
|
|
|
6521
7261
|
validateArrayItems(value, validateElement, options, false),
|
|
6522
7262
|
encode: encodeArrayCollection,
|
|
6523
7263
|
is: isArrayCollection,
|
|
6524
|
-
formatError:
|
|
7264
|
+
formatError: ((error: ArrayError) => {
|
|
7265
|
+
if (error.reason.kind === "NotArray") {
|
|
7266
|
+
return `A value ${safelyStringifyUnknownValue(error.reason.value)} is not an array.`;
|
|
7267
|
+
}
|
|
7268
|
+
const issue = error.reason.issues[0];
|
|
7269
|
+
|
|
7270
|
+
switch (issue.kind) {
|
|
7271
|
+
case "Hole":
|
|
7272
|
+
return `An array element at index ${issue.index} is missing.`;
|
|
7273
|
+
case "Accessor":
|
|
7274
|
+
return `An array element at index ${issue.index} must be a data property.`;
|
|
7275
|
+
case "ExcessProperty":
|
|
7276
|
+
return "An excess Array property is not allowed. Remove it or use a different Type.";
|
|
7277
|
+
case "Element":
|
|
7278
|
+
return `An array element at index ${issue.index} is invalid.`;
|
|
7279
|
+
}
|
|
7280
|
+
}) as TypeErrorFormatter<TypeError>,
|
|
6525
7281
|
};
|
|
6526
7282
|
|
|
6527
7283
|
const validateArrayItems = (
|
|
@@ -6658,6 +7414,17 @@ const copyArrayPrefix = (
|
|
|
6658
7414
|
* iteration order. Classification uses the realm-neutral object tag and
|
|
6659
7415
|
* prototype structure under Evolu Type's trusted JavaScript policy.
|
|
6660
7416
|
*
|
|
7417
|
+
* ### Example
|
|
7418
|
+
*
|
|
7419
|
+
* ```ts
|
|
7420
|
+
* import { String, set } from "@evolu/common";
|
|
7421
|
+
*
|
|
7422
|
+
* const Tags = set(String);
|
|
7423
|
+
* const tags = new Set(["local-first", "offline"]);
|
|
7424
|
+
*
|
|
7425
|
+
* expectOk(Tags.fromUnknown(tags), tags);
|
|
7426
|
+
* ```
|
|
7427
|
+
*
|
|
6661
7428
|
* @group Collection
|
|
6662
7429
|
*/
|
|
6663
7430
|
export const set = <ElementType extends ConcreteTypeNode>(
|
|
@@ -6668,7 +7435,11 @@ export const set = <ElementType extends ConcreteTypeNode>(
|
|
|
6668
7435
|
setRuntimeConfig,
|
|
6669
7436
|
) as SetType<ElementType>;
|
|
6670
7437
|
|
|
6671
|
-
/**
|
|
7438
|
+
/**
|
|
7439
|
+
* The homogeneous readonly-set {@link Type} returned by {@link set}.
|
|
7440
|
+
*
|
|
7441
|
+
* @group Collection
|
|
7442
|
+
*/
|
|
6672
7443
|
export interface SetType<ElementType extends TypeNode> extends Type<
|
|
6673
7444
|
"Set",
|
|
6674
7445
|
ReadonlySet<ElementType["Input"]>,
|
|
@@ -6739,13 +7510,22 @@ type SetNodeError<ElementType extends TypeNode> = [
|
|
|
6739
7510
|
? SetElementsError<ElementType["Error"]>
|
|
6740
7511
|
: SetError<ElementType["Error"]>;
|
|
6741
7512
|
|
|
6742
|
-
/**
|
|
7513
|
+
/**
|
|
7514
|
+
* Error returned by {@link set} for a non-Set value, an invalid Set prototype,
|
|
7515
|
+
* or invalid Set items.
|
|
7516
|
+
*
|
|
7517
|
+
* @group Collection
|
|
7518
|
+
*/
|
|
6743
7519
|
export type SetError<Error extends TypeError = TypeError> =
|
|
6744
7520
|
| SetNotSetError
|
|
6745
7521
|
| SetUnexpectedPrototypeError
|
|
6746
7522
|
| SetItemsErrorValue<Error, true>;
|
|
6747
7523
|
|
|
6748
|
-
/**
|
|
7524
|
+
/**
|
|
7525
|
+
* Error returned when a {@link set} input is not a Set.
|
|
7526
|
+
*
|
|
7527
|
+
* @group Collection
|
|
7528
|
+
*/
|
|
6749
7529
|
export interface SetNotSetError extends TypeError<"Set"> {
|
|
6750
7530
|
readonly reason: {
|
|
6751
7531
|
readonly kind: "NotSet";
|
|
@@ -6765,7 +7545,11 @@ export interface SetUnexpectedPrototypeError extends TypeError<"Set"> {
|
|
|
6765
7545
|
};
|
|
6766
7546
|
}
|
|
6767
7547
|
|
|
6768
|
-
/**
|
|
7548
|
+
/**
|
|
7549
|
+
* An own property found on a Set value.
|
|
7550
|
+
*
|
|
7551
|
+
* @group Collection
|
|
7552
|
+
*/
|
|
6769
7553
|
export interface SetExcessPropertyIssue {
|
|
6770
7554
|
readonly kind: "ExcessProperty";
|
|
6771
7555
|
readonly key: string | symbol;
|
|
@@ -6773,7 +7557,11 @@ export interface SetExcessPropertyIssue {
|
|
|
6773
7557
|
|
|
6774
7558
|
type SetStructuralIssue = SetExcessPropertyIssue;
|
|
6775
7559
|
|
|
6776
|
-
/**
|
|
7560
|
+
/**
|
|
7561
|
+
* An invalid Set element and its iteration index.
|
|
7562
|
+
*
|
|
7563
|
+
* @group Collection
|
|
7564
|
+
*/
|
|
6777
7565
|
export type SetElementIssue<Error extends TypeError> = Error extends TypeError
|
|
6778
7566
|
? {
|
|
6779
7567
|
readonly kind: "Element";
|
|
@@ -6782,13 +7570,21 @@ export type SetElementIssue<Error extends TypeError> = Error extends TypeError
|
|
|
6782
7570
|
}
|
|
6783
7571
|
: never;
|
|
6784
7572
|
|
|
6785
|
-
/**
|
|
7573
|
+
/**
|
|
7574
|
+
* A {@link set} error containing structural or element issues.
|
|
7575
|
+
*
|
|
7576
|
+
* @group Collection
|
|
7577
|
+
*/
|
|
6786
7578
|
export type SetItemsError<Error extends TypeError> = SetItemsErrorValue<
|
|
6787
7579
|
Error,
|
|
6788
7580
|
true
|
|
6789
7581
|
>;
|
|
6790
7582
|
|
|
6791
|
-
/**
|
|
7583
|
+
/**
|
|
7584
|
+
* A {@link set} error containing element errors from a typed boundary.
|
|
7585
|
+
*
|
|
7586
|
+
* @group Collection
|
|
7587
|
+
*/
|
|
6792
7588
|
export type SetElementsError<Error extends TypeError> = [Error] extends [never]
|
|
6793
7589
|
? never
|
|
6794
7590
|
: SetItemsErrorValue<Error, false>;
|
|
@@ -6960,18 +7756,6 @@ const isSetCollection = (
|
|
|
6960
7756
|
return true;
|
|
6961
7757
|
};
|
|
6962
7758
|
|
|
6963
|
-
const formatSetError: TypeErrorFormatter<SetError> = (error) => {
|
|
6964
|
-
if (error.reason.kind === "NotSet") {
|
|
6965
|
-
return `A value ${safelyStringifyUnknownValue(error.reason.value)} is not a Set.`;
|
|
6966
|
-
}
|
|
6967
|
-
if (error.reason.kind === "UnexpectedPrototype") {
|
|
6968
|
-
return "The value is a Set subclass, but a Set Output must be a direct Set.";
|
|
6969
|
-
}
|
|
6970
|
-
|
|
6971
|
-
const issue = error.reason.issues[0] as SetStructuralIssue;
|
|
6972
|
-
return `An excess Set property ${safelyStringifyUnknownValue(issue.key)} is not allowed.`;
|
|
6973
|
-
};
|
|
6974
|
-
|
|
6975
7759
|
const hasDirectSetPrototype = (value: object): boolean => {
|
|
6976
7760
|
const prototype: unknown = globalThis.Object.getPrototypeOf(value);
|
|
6977
7761
|
if (prototype === null) return false;
|
|
@@ -6994,7 +7778,19 @@ const setRuntimeConfig: HomogeneousCollectionRuntimeConfig<
|
|
|
6994
7778
|
validateSetItems(value, validateElement, options, false),
|
|
6995
7779
|
encode: encodeSetCollection,
|
|
6996
7780
|
is: isSetCollection,
|
|
6997
|
-
formatError:
|
|
7781
|
+
formatError: ((error: SetError) => {
|
|
7782
|
+
if (error.reason.kind === "NotSet")
|
|
7783
|
+
return `A value ${safelyStringifyUnknownValue(error.reason.value)} is not a Set.`;
|
|
7784
|
+
if (error.reason.kind === "UnexpectedPrototype")
|
|
7785
|
+
return "The value is an instance of a Set subclass, but a Set Output must be a direct Set instance.";
|
|
7786
|
+
const issue = error.reason.issues[0];
|
|
7787
|
+
switch (issue.kind) {
|
|
7788
|
+
case "ExcessProperty":
|
|
7789
|
+
return `An excess Set property ${safelyStringifyUnknownValue(issue.key)} is not allowed.`;
|
|
7790
|
+
case "Element":
|
|
7791
|
+
return `A Set element at index ${issue.index} is invalid.`;
|
|
7792
|
+
}
|
|
7793
|
+
}) as TypeErrorFormatter<TypeError>,
|
|
6998
7794
|
};
|
|
6999
7795
|
|
|
7000
7796
|
const validateSetItems = (
|
|
@@ -7067,17 +7863,12 @@ const validateSetItems = (
|
|
|
7067
7863
|
* ### Example
|
|
7068
7864
|
*
|
|
7069
7865
|
* ```ts
|
|
7070
|
-
* import {
|
|
7866
|
+
* import { Int64FromInt64String, String, tuple } from "@evolu/common";
|
|
7071
7867
|
*
|
|
7072
|
-
* const
|
|
7073
|
-
* from: (value) => ok(globalThis.Number(value)),
|
|
7074
|
-
* to: globalThis.String,
|
|
7075
|
-
* });
|
|
7076
|
-
*
|
|
7077
|
-
* const Entry = tuple(String, NumberFromString);
|
|
7868
|
+
* const Entry = tuple(String, Int64FromInt64String);
|
|
7078
7869
|
*
|
|
7079
|
-
* expectOk(Entry.fromUnknown(["count", "1"]), ["count",
|
|
7080
|
-
* expectOk(Entry.from.parent(["count", "1"]), ["count",
|
|
7870
|
+
* expectOk(Entry.fromUnknown(["count", "1"]), ["count", 1n]);
|
|
7871
|
+
* expectOk(Entry.from.parent(["count", "1"]), ["count", 1n]);
|
|
7081
7872
|
* ```
|
|
7082
7873
|
*
|
|
7083
7874
|
* @group Collection
|
|
@@ -7148,14 +7939,11 @@ const createTupleType = (
|
|
|
7148
7939
|
options,
|
|
7149
7940
|
);
|
|
7150
7941
|
const formatError: TypeErrorFormatter<TupleError> = (error) => {
|
|
7151
|
-
if (error.reason.kind === "NotArray")
|
|
7942
|
+
if (error.reason.kind === "NotArray")
|
|
7152
7943
|
return `A value ${safelyStringifyUnknownValue(error.reason.value)} is not a tuple.`;
|
|
7153
|
-
|
|
7154
|
-
if (error.reason.kind === "InvalidLength") {
|
|
7944
|
+
if (error.reason.kind === "InvalidLength")
|
|
7155
7945
|
return `A Tuple must contain exactly ${error.reason.expected} elements, but the value contains ${error.reason.actual}.`;
|
|
7156
|
-
|
|
7157
|
-
const issue = error.reason.issues[0] as TupleStructuralIssue;
|
|
7158
|
-
|
|
7946
|
+
const issue = error.reason.issues[0];
|
|
7159
7947
|
switch (issue.kind) {
|
|
7160
7948
|
case "Hole":
|
|
7161
7949
|
return `A Tuple element at index ${issue.index} is missing.`;
|
|
@@ -7163,6 +7951,8 @@ const createTupleType = (
|
|
|
7163
7951
|
return `A Tuple element at index ${issue.index} must be a data property.`;
|
|
7164
7952
|
case "ExcessProperty":
|
|
7165
7953
|
return "An excess Tuple property is not allowed. Remove it or use a different Type.";
|
|
7954
|
+
case "Element":
|
|
7955
|
+
return `A Tuple element at index ${issue.index} is invalid.`;
|
|
7166
7956
|
}
|
|
7167
7957
|
};
|
|
7168
7958
|
const rootElements = typeElements.map(
|
|
@@ -7538,10 +8328,7 @@ export type Digit1To6 = typeof Digit1To6.Output;
|
|
|
7538
8328
|
export const Digit1To23 = /*#__PURE__*/ union(
|
|
7539
8329
|
Digit1To9,
|
|
7540
8330
|
/*#__PURE__*/ templateLiteral("1", Digit),
|
|
7541
|
-
/*#__PURE__*/ templateLiteral(
|
|
7542
|
-
"2",
|
|
7543
|
-
/*#__PURE__*/ union("0", "1", "2", "3"),
|
|
7544
|
-
),
|
|
8331
|
+
/*#__PURE__*/ templateLiteral("2", /*#__PURE__*/ union("0", "1", "2", "3")),
|
|
7545
8332
|
);
|
|
7546
8333
|
export type Digit1To23 = typeof Digit1To23.Output;
|
|
7547
8334
|
|
|
@@ -7552,10 +8339,7 @@ export type Digit1To23 = typeof Digit1To23.Output;
|
|
|
7552
8339
|
*/
|
|
7553
8340
|
export const Digit1To51 = /*#__PURE__*/ union(
|
|
7554
8341
|
Digit1To9,
|
|
7555
|
-
/*#__PURE__*/ templateLiteral(
|
|
7556
|
-
/*#__PURE__*/ union("1", "2", "3", "4"),
|
|
7557
|
-
Digit,
|
|
7558
|
-
),
|
|
8342
|
+
/*#__PURE__*/ templateLiteral(/*#__PURE__*/ union("1", "2", "3", "4"), Digit),
|
|
7559
8343
|
/*#__PURE__*/ templateLiteral("5", /*#__PURE__*/ union("0", "1")),
|
|
7560
8344
|
);
|
|
7561
8345
|
export type Digit1To51 = typeof Digit1To51.Output;
|
|
@@ -7654,31 +8438,19 @@ const createObjectRuntimeTypeIssues =
|
|
|
7654
8438
|
}) as unknown as NonEmptyReadonlyArray<RuntimeTypeIssue>;
|
|
7655
8439
|
};
|
|
7656
8440
|
|
|
8441
|
+
const formatPlainObjectRootError = (
|
|
8442
|
+
reason:
|
|
8443
|
+
ObjectNotObjectError["reason"] | ObjectUnexpectedPrototypeError["reason"],
|
|
8444
|
+
): string =>
|
|
8445
|
+
reason.kind === "NotObject"
|
|
8446
|
+
? `A value ${safelyStringifyUnknownValue(reason.value)} is not an object.`
|
|
8447
|
+
: "The value is an object, but an Object Output must be a plain object or have a null prototype.";
|
|
8448
|
+
|
|
7657
8449
|
type PlainObjectError = ObjectError<
|
|
7658
8450
|
Readonly<Record<never, never>>,
|
|
7659
8451
|
ObjectPropertyAccessError | ObjectExcessPropertyError
|
|
7660
8452
|
>;
|
|
7661
8453
|
|
|
7662
|
-
const formatPlainObjectError: TypeErrorFormatter<PlainObjectError> = (
|
|
7663
|
-
error,
|
|
7664
|
-
) => {
|
|
7665
|
-
if (error.reason.kind !== "Properties") {
|
|
7666
|
-
return formatPlainObjectRootError(error.reason);
|
|
7667
|
-
}
|
|
7668
|
-
|
|
7669
|
-
const key = Reflect.ownKeys(error.reason.errors).at(0);
|
|
7670
|
-
assertNonNullable(key);
|
|
7671
|
-
const propertyError = error.reason.errors[key];
|
|
7672
|
-
assertNonNullable(propertyError);
|
|
7673
|
-
|
|
7674
|
-
if (propertyError.type === "ObjectPropertyAccess") {
|
|
7675
|
-
return propertyError.reason === "Accessor"
|
|
7676
|
-
? "An Object property must be a data property. Materialize accessor values into plain data before using this Type or use a different Type."
|
|
7677
|
-
: "An Object property must be enumerable. Make it enumerable or use a different Type.";
|
|
7678
|
-
}
|
|
7679
|
-
return "An Object property key must be a string. Remove it or use a different Type.";
|
|
7680
|
-
};
|
|
7681
|
-
|
|
7682
8454
|
/**
|
|
7683
8455
|
* A {@link Type} for readonly plain objects with unknown property values.
|
|
7684
8456
|
*
|
|
@@ -7693,7 +8465,7 @@ const formatPlainObjectError: TypeErrorFormatter<PlainObjectError> = (
|
|
|
7693
8465
|
* values have their own Types, and {@link instanceOf} when an instance belongs
|
|
7694
8466
|
* to the domain.
|
|
7695
8467
|
*
|
|
7696
|
-
* @group Base
|
|
8468
|
+
* @group Base
|
|
7697
8469
|
*/
|
|
7698
8470
|
export const Object: Type<
|
|
7699
8471
|
"Object",
|
|
@@ -7768,10 +8540,52 @@ export const Object: Type<
|
|
|
7768
8540
|
reason: { kind: "Properties", errors },
|
|
7769
8541
|
} as PlainObjectError);
|
|
7770
8542
|
},
|
|
7771
|
-
|
|
7772
|
-
|
|
7773
|
-
|
|
7774
|
-
|
|
8543
|
+
(error: ObjectError) => {
|
|
8544
|
+
if (error.reason.kind !== "Properties")
|
|
8545
|
+
return formatPlainObjectRootError(error.reason);
|
|
8546
|
+
const key = Reflect.ownKeys(error.reason.errors).at(0);
|
|
8547
|
+
assertNonNullable(key);
|
|
8548
|
+
const propertyError = error.reason.errors[key];
|
|
8549
|
+
assertNonNullable(propertyError);
|
|
8550
|
+
if (propertyError.type === "ObjectPropertyAccess") {
|
|
8551
|
+
switch ((propertyError as ObjectPropertyAccessError).reason) {
|
|
8552
|
+
case "Accessor":
|
|
8553
|
+
return "An Object property must be a data property. Materialize accessor values into plain data before using this Type or use a different Type.";
|
|
8554
|
+
case "NonEnumerable":
|
|
8555
|
+
return "An Object property must be enumerable. Make it enumerable or use a different Type.";
|
|
8556
|
+
}
|
|
8557
|
+
}
|
|
8558
|
+
if (propertyError.type === "ObjectMissingProperty")
|
|
8559
|
+
return `The required property ${safelyStringifyUnknownValue(key)} is missing.`;
|
|
8560
|
+
if (typeof key === "symbol")
|
|
8561
|
+
return "An Object property key must be a string. Remove the symbol property or use a different Type.";
|
|
8562
|
+
if (propertyError.type === "ObjectExcessProperty")
|
|
8563
|
+
return `The property ${safelyStringifyUnknownValue(key)} is not allowed. Remove it or use a different Type.`;
|
|
8564
|
+
return `The property ${safelyStringifyUnknownValue(key)} is invalid.`;
|
|
8565
|
+
},
|
|
8566
|
+
/*#__PURE__*/ createObjectRuntimeTypeIssues(((error: ObjectError) => {
|
|
8567
|
+
if (error.reason.kind !== "Properties")
|
|
8568
|
+
return formatPlainObjectRootError(error.reason);
|
|
8569
|
+
const key = Reflect.ownKeys(error.reason.errors).at(0);
|
|
8570
|
+
assertNonNullable(key);
|
|
8571
|
+
const propertyError = error.reason.errors[key];
|
|
8572
|
+
assertNonNullable(propertyError);
|
|
8573
|
+
if (propertyError.type === "ObjectPropertyAccess") {
|
|
8574
|
+
switch ((propertyError as ObjectPropertyAccessError).reason) {
|
|
8575
|
+
case "Accessor":
|
|
8576
|
+
return "An Object property must be a data property. Materialize accessor values into plain data before using this Type or use a different Type.";
|
|
8577
|
+
case "NonEnumerable":
|
|
8578
|
+
return "An Object property must be enumerable. Make it enumerable or use a different Type.";
|
|
8579
|
+
}
|
|
8580
|
+
}
|
|
8581
|
+
if (propertyError.type === "ObjectMissingProperty")
|
|
8582
|
+
return `The required property ${safelyStringifyUnknownValue(key)} is missing.`;
|
|
8583
|
+
if (typeof key === "symbol")
|
|
8584
|
+
return "An Object property key must be a string. Remove the symbol property or use a different Type.";
|
|
8585
|
+
if (propertyError.type === "ObjectExcessProperty")
|
|
8586
|
+
return `The property ${safelyStringifyUnknownValue(key)} is not allowed. Remove it or use a different Type.`;
|
|
8587
|
+
return `The property ${safelyStringifyUnknownValue(key)} is invalid.`;
|
|
8588
|
+
}) as TypeErrorFormatter<TypeError>),
|
|
7775
8589
|
);
|
|
7776
8590
|
|
|
7777
8591
|
const isPlainObject = (value: object): boolean => {
|
|
@@ -7781,14 +8595,6 @@ const isPlainObject = (value: object): boolean => {
|
|
|
7781
8595
|
);
|
|
7782
8596
|
};
|
|
7783
8597
|
|
|
7784
|
-
const formatPlainObjectRootError = (
|
|
7785
|
-
reason:
|
|
7786
|
-
ObjectNotObjectError["reason"] | ObjectUnexpectedPrototypeError["reason"],
|
|
7787
|
-
): string =>
|
|
7788
|
-
reason.kind === "NotObject"
|
|
7789
|
-
? `A value ${safelyStringifyUnknownValue(reason.value)} is not an object.`
|
|
7790
|
-
: "The value is an object, but an Object Output must be a plain object or have a null prototype.";
|
|
7791
|
-
|
|
7792
8598
|
/**
|
|
7793
8599
|
* Record {@link Type}.
|
|
7794
8600
|
*
|
|
@@ -7819,20 +8625,13 @@ const formatPlainObjectRootError = (
|
|
|
7819
8625
|
*
|
|
7820
8626
|
* ```ts
|
|
7821
8627
|
* import {
|
|
7822
|
-
*
|
|
8628
|
+
* Int64FromInt64String,
|
|
7823
8629
|
* String,
|
|
7824
|
-
* ok,
|
|
7825
8630
|
* record,
|
|
7826
|
-
*
|
|
7827
|
-
* type Result,
|
|
8631
|
+
* type Int64,
|
|
7828
8632
|
* } from "@evolu/common";
|
|
7829
8633
|
*
|
|
7830
|
-
* const
|
|
7831
|
-
* from: (value) => ok(globalThis.Number(value)),
|
|
7832
|
-
* to: globalThis.String,
|
|
7833
|
-
* });
|
|
7834
|
-
*
|
|
7835
|
-
* const ScoresByUser = record(String, ScoreFromString);
|
|
8634
|
+
* const ScoresByUser = record(String, Int64FromInt64String);
|
|
7836
8635
|
* type ScoresByUser = typeof ScoresByUser.Output;
|
|
7837
8636
|
*
|
|
7838
8637
|
* // Validate an unknown value.
|
|
@@ -7841,7 +8640,7 @@ const formatPlainObjectRootError = (
|
|
|
7841
8640
|
* grace: "20",
|
|
7842
8641
|
* });
|
|
7843
8642
|
*
|
|
7844
|
-
* expectOk(scoresFromUnknown, { ada:
|
|
8643
|
+
* expectOk(scoresFromUnknown, { ada: 10n, grace: 20n });
|
|
7845
8644
|
*
|
|
7846
8645
|
* // Validate keys and values with their root Types.
|
|
7847
8646
|
* const scoresInput = ScoresByUser.parent.fromUnknown({
|
|
@@ -7853,11 +8652,10 @@ const formatPlainObjectRootError = (
|
|
|
7853
8652
|
* // Run the remaining key and value stages.
|
|
7854
8653
|
* const scoresFromInput = ScoresByUser.from.parent(scoresInput.value);
|
|
7855
8654
|
*
|
|
7856
|
-
*
|
|
7857
|
-
*
|
|
8655
|
+
* expectOk(scoresFromInput, { ada: 10n, grace: 20n });
|
|
8656
|
+
* expectTypeOf(scoresFromInput.value).toEqualTypeOf<
|
|
8657
|
+
* Readonly<Partial<Record<string, Int64>>>
|
|
7858
8658
|
* >();
|
|
7859
|
-
*
|
|
7860
|
-
* expectOk(scoresFromInput, { ada: 10, grace: 20 });
|
|
7861
8659
|
* ```
|
|
7862
8660
|
*
|
|
7863
8661
|
* Note that TypeScript does not model an object's runtime prototype. This can
|
|
@@ -7954,20 +8752,20 @@ export const record = <
|
|
|
7954
8752
|
options,
|
|
7955
8753
|
);
|
|
7956
8754
|
const formatError: TypeErrorFormatter<RecordError> = (error) => {
|
|
7957
|
-
if (error.reason.kind === "NotRecord")
|
|
7958
|
-
return `A value ${safelyStringifyUnknownValue(error.reason.value)} is not a
|
|
7959
|
-
|
|
7960
|
-
if (error.reason.kind === "NotPlainRecord") {
|
|
8755
|
+
if (error.reason.kind === "NotRecord")
|
|
8756
|
+
return `A value ${safelyStringifyUnknownValue(error.reason.value)} is not a Record.`;
|
|
8757
|
+
if (error.reason.kind === "NotPlainRecord")
|
|
7961
8758
|
return "The value is an object, but a Record Output must be a plain object or have a null prototype.";
|
|
7962
|
-
|
|
7963
|
-
|
|
7964
|
-
const issue = error.reason.issues[0] as RecordStructuralIssue;
|
|
7965
|
-
|
|
8759
|
+
const issue = error.reason.issues[0];
|
|
7966
8760
|
switch (issue.kind) {
|
|
8761
|
+
case "Key":
|
|
8762
|
+
return `Property key ${safelyStringifyUnknownValue(issue.key)} is invalid.`;
|
|
8763
|
+
case "Value":
|
|
8764
|
+
return `The value of property ${safelyStringifyUnknownValue(issue.key)} is invalid.`;
|
|
7967
8765
|
case "Accessor":
|
|
7968
|
-
return `A
|
|
8766
|
+
return `A Record property ${safelyStringifyUnknownValue(issue.key)} must be a data property.`;
|
|
7969
8767
|
case "NonEnumerable":
|
|
7970
|
-
return `A
|
|
8768
|
+
return `A Record property ${safelyStringifyUnknownValue(issue.key)} must be enumerable.`;
|
|
7971
8769
|
case "Collision":
|
|
7972
8770
|
return `Record keys ${safelyStringifyUnknownValue(issue.previousKey)} and ${safelyStringifyUnknownValue(issue.key)} decode to the same key ${safelyStringifyUnknownValue(issue.outputKey)}.`;
|
|
7973
8771
|
}
|
|
@@ -8073,7 +8871,11 @@ export const record = <
|
|
|
8073
8871
|
);
|
|
8074
8872
|
};
|
|
8075
8873
|
|
|
8076
|
-
/**
|
|
8874
|
+
/**
|
|
8875
|
+
* The {@link Type} returned by {@link record}.
|
|
8876
|
+
*
|
|
8877
|
+
* @group Objects
|
|
8878
|
+
*/
|
|
8077
8879
|
export interface RecordType<
|
|
8078
8880
|
KeyType extends TypeNode,
|
|
8079
8881
|
ValueType extends TypeNode,
|
|
@@ -8197,7 +8999,11 @@ type RecordKeyStringTypeError = CompileTimeError<
|
|
|
8197
8999
|
"Record key Type Input and Output must extend string."
|
|
8198
9000
|
>;
|
|
8199
9001
|
|
|
8200
|
-
/**
|
|
9002
|
+
/**
|
|
9003
|
+
* Error returned while validating a {@link record} and its entries.
|
|
9004
|
+
*
|
|
9005
|
+
* @group Objects
|
|
9006
|
+
*/
|
|
8201
9007
|
export type RecordError<
|
|
8202
9008
|
KeyError extends TypeError = TypeError,
|
|
8203
9009
|
ValueError extends TypeError = TypeError,
|
|
@@ -8211,7 +9017,11 @@ export type RecordError<
|
|
|
8211
9017
|
Collision | RecordAccessorIssue | RecordNonEnumerableIssue
|
|
8212
9018
|
>;
|
|
8213
9019
|
|
|
8214
|
-
/**
|
|
9020
|
+
/**
|
|
9021
|
+
* Error returned when a {@link record} input is not an object.
|
|
9022
|
+
*
|
|
9023
|
+
* @group Objects
|
|
9024
|
+
*/
|
|
8215
9025
|
export interface RecordNotRecordError extends TypeError<"Record"> {
|
|
8216
9026
|
readonly reason: {
|
|
8217
9027
|
readonly kind: "NotRecord";
|
|
@@ -8219,7 +9029,11 @@ export interface RecordNotRecordError extends TypeError<"Record"> {
|
|
|
8219
9029
|
};
|
|
8220
9030
|
}
|
|
8221
9031
|
|
|
8222
|
-
/**
|
|
9032
|
+
/**
|
|
9033
|
+
* Error returned when a {@link record} input is not a plain object.
|
|
9034
|
+
*
|
|
9035
|
+
* @group Objects
|
|
9036
|
+
*/
|
|
8223
9037
|
export interface RecordNotPlainRecordError extends TypeError<"Record"> {
|
|
8224
9038
|
readonly reason: {
|
|
8225
9039
|
readonly kind: "NotPlainRecord";
|
|
@@ -8244,14 +9058,22 @@ export type RecordEntriesError<
|
|
|
8244
9058
|
? never
|
|
8245
9059
|
: RecordEntriesErrorValue<KeyError, ValueError, StructuralIssue>;
|
|
8246
9060
|
|
|
8247
|
-
/**
|
|
9061
|
+
/**
|
|
9062
|
+
* An invalid key, value, or property structure in a {@link record}.
|
|
9063
|
+
*
|
|
9064
|
+
* @group Objects
|
|
9065
|
+
*/
|
|
8248
9066
|
export type RecordIssue<
|
|
8249
9067
|
KeyError extends TypeError,
|
|
8250
9068
|
ValueError extends TypeError,
|
|
8251
9069
|
StructuralIssue extends RecordStructuralIssue = RecordCollisionIssue,
|
|
8252
9070
|
> = RecordKeyIssue<KeyError> | RecordValueIssue<ValueError> | StructuralIssue;
|
|
8253
9071
|
|
|
8254
|
-
/**
|
|
9072
|
+
/**
|
|
9073
|
+
* A property-structure issue returned by {@link record}.
|
|
9074
|
+
*
|
|
9075
|
+
* @group Objects
|
|
9076
|
+
*/
|
|
8255
9077
|
export type RecordStructuralIssue =
|
|
8256
9078
|
RecordAccessorIssue | RecordCollisionIssue | RecordNonEnumerableIssue;
|
|
8257
9079
|
|
|
@@ -8275,7 +9097,11 @@ export interface RecordNonEnumerableIssue {
|
|
|
8275
9097
|
readonly key: string | symbol;
|
|
8276
9098
|
}
|
|
8277
9099
|
|
|
8278
|
-
/**
|
|
9100
|
+
/**
|
|
9101
|
+
* An invalid key and its source property key in a {@link record}.
|
|
9102
|
+
*
|
|
9103
|
+
* @group Objects
|
|
9104
|
+
*/
|
|
8279
9105
|
export type RecordKeyIssue<Error extends TypeError> = Error extends TypeError
|
|
8280
9106
|
? {
|
|
8281
9107
|
readonly kind: "Key";
|
|
@@ -8284,7 +9110,11 @@ export type RecordKeyIssue<Error extends TypeError> = Error extends TypeError
|
|
|
8284
9110
|
}
|
|
8285
9111
|
: never;
|
|
8286
9112
|
|
|
8287
|
-
/**
|
|
9113
|
+
/**
|
|
9114
|
+
* An invalid value and its property key in a {@link record}.
|
|
9115
|
+
*
|
|
9116
|
+
* @group Objects
|
|
9117
|
+
*/
|
|
8288
9118
|
export type RecordValueIssue<Error extends TypeError> = Error extends TypeError
|
|
8289
9119
|
? {
|
|
8290
9120
|
readonly kind: "Value";
|
|
@@ -8293,7 +9123,11 @@ export type RecordValueIssue<Error extends TypeError> = Error extends TypeError
|
|
|
8293
9123
|
}
|
|
8294
9124
|
: never;
|
|
8295
9125
|
|
|
8296
|
-
/**
|
|
9126
|
+
/**
|
|
9127
|
+
* Two {@link record} keys that decode to the same output key.
|
|
9128
|
+
*
|
|
9129
|
+
* @group Objects
|
|
9130
|
+
*/
|
|
8297
9131
|
export interface RecordCollisionIssue {
|
|
8298
9132
|
readonly kind: "Collision";
|
|
8299
9133
|
readonly key: string | symbol;
|
|
@@ -8520,48 +9354,38 @@ type ObjectProperty = ObjectProps[string];
|
|
|
8520
9354
|
*
|
|
8521
9355
|
* ```ts
|
|
8522
9356
|
* import {
|
|
8523
|
-
*
|
|
9357
|
+
* Int64FromInt64String,
|
|
8524
9358
|
* String,
|
|
8525
9359
|
* object,
|
|
8526
|
-
* ok,
|
|
8527
|
-
* transform,
|
|
8528
9360
|
* type InferType,
|
|
8529
|
-
* type Result,
|
|
8530
9361
|
* } from "@evolu/common";
|
|
8531
9362
|
*
|
|
8532
|
-
* const AgeFromString = transform("AgeFromString", String, Number, {
|
|
8533
|
-
* from: (value) => ok(globalThis.Number(value)),
|
|
8534
|
-
* to: globalThis.String,
|
|
8535
|
-
* });
|
|
8536
|
-
*
|
|
8537
9363
|
* const User = object({
|
|
8538
9364
|
* name: String,
|
|
8539
|
-
*
|
|
9365
|
+
* loginCount: Int64FromInt64String,
|
|
8540
9366
|
* });
|
|
8541
9367
|
* interface User extends InferType<typeof User> {}
|
|
8542
9368
|
*
|
|
8543
9369
|
* // Validate an unknown value.
|
|
8544
|
-
* const userFromUnknown = User.fromUnknown({
|
|
9370
|
+
* const userFromUnknown = User.fromUnknown({
|
|
9371
|
+
* name: "Ada",
|
|
9372
|
+
* loginCount: "42",
|
|
9373
|
+
* });
|
|
8545
9374
|
*
|
|
8546
|
-
* expectOk(userFromUnknown, { name: "Ada",
|
|
9375
|
+
* expectOk(userFromUnknown, { name: "Ada", loginCount: 42n });
|
|
8547
9376
|
*
|
|
8548
9377
|
* // Validate the object and root property Types.
|
|
8549
9378
|
* const userInput = User.parent.fromUnknown({
|
|
8550
9379
|
* name: "Ada",
|
|
8551
|
-
*
|
|
9380
|
+
* loginCount: "42",
|
|
8552
9381
|
* });
|
|
8553
|
-
* expectOk(userInput, { name: "Ada",
|
|
9382
|
+
* expectOk(userInput, { name: "Ada", loginCount: "42" });
|
|
8554
9383
|
*
|
|
8555
9384
|
* // Run the remaining property stages.
|
|
8556
|
-
* const userFromInput
|
|
8557
|
-
* { readonly name: string; readonly age: number },
|
|
8558
|
-
* never
|
|
8559
|
-
* > = User.from.parent(userInput.value);
|
|
9385
|
+
* const userFromInput = User.from.parent(userInput.value);
|
|
8560
9386
|
*
|
|
8561
|
-
*
|
|
8562
|
-
*
|
|
8563
|
-
* >();
|
|
8564
|
-
* expectOk(userFromInput, { name: "Ada", age: 42 });
|
|
9387
|
+
* expectOk(userFromInput, { name: "Ada", loginCount: 42n });
|
|
9388
|
+
* expectTypeOf(userFromInput.value).toExtend<User>();
|
|
8565
9389
|
* ```
|
|
8566
9390
|
*
|
|
8567
9391
|
* Note that TypeScript does not model an object's runtime prototype. This can
|
|
@@ -8614,6 +9438,8 @@ export function object<const Props extends ObjectProps>(
|
|
|
8614
9438
|
? []
|
|
8615
9439
|
: [ValidationFailure<ObjectValidationError<Props>>]
|
|
8616
9440
|
): StrictObjectType<Props>;
|
|
9441
|
+
|
|
9442
|
+
/** Creates an Object Type with additional record properties. */
|
|
8617
9443
|
export function object<
|
|
8618
9444
|
const Props extends ObjectProps,
|
|
8619
9445
|
const Rest extends RecordTypeNode & ConcreteTypeNode,
|
|
@@ -8818,31 +9644,27 @@ const createObjectType = (
|
|
|
8818
9644
|
options: ValidationOptions = firstValidationOptions,
|
|
8819
9645
|
) => validate(value, options, true);
|
|
8820
9646
|
const formatError: TypeErrorFormatter<ObjectError> = (error) => {
|
|
8821
|
-
if (error.reason.kind !== "Properties")
|
|
9647
|
+
if (error.reason.kind !== "Properties")
|
|
8822
9648
|
return formatPlainObjectRootError(error.reason);
|
|
8823
|
-
}
|
|
8824
|
-
|
|
8825
9649
|
const key = Reflect.ownKeys(error.reason.errors).at(0);
|
|
8826
9650
|
assertNonNullable(key);
|
|
8827
|
-
const propertyError =
|
|
8828
|
-
|
|
8829
|
-
)[key]!;
|
|
8830
|
-
|
|
9651
|
+
const propertyError = error.reason.errors[key];
|
|
9652
|
+
assertNonNullable(propertyError);
|
|
8831
9653
|
if (propertyError.type === "ObjectPropertyAccess") {
|
|
8832
9654
|
switch ((propertyError as ObjectPropertyAccessError).reason) {
|
|
8833
9655
|
case "Accessor":
|
|
8834
9656
|
return "An Object property must be a data property. Materialize accessor values into plain data before using this Type or use a different Type.";
|
|
8835
9657
|
case "NonEnumerable":
|
|
8836
|
-
return "An Object property must be enumerable.";
|
|
9658
|
+
return "An Object property must be enumerable. Make it enumerable or use a different Type.";
|
|
8837
9659
|
}
|
|
8838
9660
|
}
|
|
8839
|
-
if (
|
|
8840
|
-
|
|
8841
|
-
|
|
8842
|
-
|
|
8843
|
-
|
|
8844
|
-
|
|
8845
|
-
return
|
|
9661
|
+
if (propertyError.type === "ObjectMissingProperty")
|
|
9662
|
+
return `The required property ${safelyStringifyUnknownValue(key)} is missing.`;
|
|
9663
|
+
if (typeof key === "symbol")
|
|
9664
|
+
return "An Object property key must be a string. Remove the symbol property or use a different Type.";
|
|
9665
|
+
if (propertyError.type === "ObjectExcessProperty")
|
|
9666
|
+
return `The property ${safelyStringifyUnknownValue(key)} is not allowed. Remove it or use a different Type.`;
|
|
9667
|
+
return `The property ${safelyStringifyUnknownValue(key)} is invalid.`;
|
|
8846
9668
|
};
|
|
8847
9669
|
const rootProps = createMutableRecord<string, RuntimeObjectProperty>();
|
|
8848
9670
|
let hasNonRootType = false;
|
|
@@ -9214,7 +10036,11 @@ type ObjectRecordCanonicalInputTypeError = CompileTimeError<
|
|
|
9214
10036
|
"Every declared property Type CanonicalInput must extend the Object Record value Type CanonicalInput."
|
|
9215
10037
|
>;
|
|
9216
10038
|
|
|
9217
|
-
/**
|
|
10039
|
+
/**
|
|
10040
|
+
* The {@link Type} returned by {@link object}.
|
|
10041
|
+
*
|
|
10042
|
+
* @group Objects
|
|
10043
|
+
*/
|
|
9218
10044
|
export type ObjectType<
|
|
9219
10045
|
Props extends ObjectProps,
|
|
9220
10046
|
Rest extends ObjectRecordTypeNode | undefined = undefined,
|
|
@@ -9649,7 +10475,11 @@ export const partial = <const Props extends ObjectProps>(
|
|
|
9649
10475
|
>;
|
|
9650
10476
|
};
|
|
9651
10477
|
|
|
9652
|
-
/**
|
|
10478
|
+
/**
|
|
10479
|
+
* Maps every required object property Type to an optional property.
|
|
10480
|
+
*
|
|
10481
|
+
* @group Objects
|
|
10482
|
+
*/
|
|
9653
10483
|
export type PartialObjectProps<Props extends ObjectProps> = {
|
|
9654
10484
|
readonly [Key in keyof Props]: Props[Key] extends OptionalProperty<TypeNode>
|
|
9655
10485
|
? Props[Key]
|
|
@@ -9719,7 +10549,12 @@ export const nullableToOptional = <const Props extends ObjectProps>(
|
|
|
9719
10549
|
>;
|
|
9720
10550
|
};
|
|
9721
10551
|
|
|
9722
|
-
/**
|
|
10552
|
+
/**
|
|
10553
|
+
* Maps object properties whose Union Type includes {@link Null} to optional
|
|
10554
|
+
* properties.
|
|
10555
|
+
*
|
|
10556
|
+
* @group Objects
|
|
10557
|
+
*/
|
|
9723
10558
|
export type NullableToOptionalProps<Props extends ObjectProps> = {
|
|
9724
10559
|
readonly [Key in keyof Props]: Props[Key] extends OptionalProperty<TypeNode>
|
|
9725
10560
|
? Props[Key]
|
|
@@ -9733,6 +10568,17 @@ export type NullableToOptionalProps<Props extends ObjectProps> = {
|
|
|
9733
10568
|
/**
|
|
9734
10569
|
* Object {@link Type} without the selected declared properties.
|
|
9735
10570
|
*
|
|
10571
|
+
* ### Example
|
|
10572
|
+
*
|
|
10573
|
+
* ```ts
|
|
10574
|
+
* import { String, object, omit } from "@evolu/common";
|
|
10575
|
+
*
|
|
10576
|
+
* const User = object({ name: String, password: String });
|
|
10577
|
+
* const PublicUser = omit(User, "password");
|
|
10578
|
+
*
|
|
10579
|
+
* expectOk(PublicUser.fromUnknown({ name: "Ada" }), { name: "Ada" });
|
|
10580
|
+
* ```
|
|
10581
|
+
*
|
|
9736
10582
|
* @group Objects
|
|
9737
10583
|
*/
|
|
9738
10584
|
export const omit = <
|
|
@@ -9785,11 +10631,11 @@ type OmitKeyConcreteTypeError = CompileTimeError<
|
|
|
9785
10631
|
>;
|
|
9786
10632
|
|
|
9787
10633
|
/**
|
|
9788
|
-
*
|
|
10634
|
+
* Creates a {@link Type} for {@link Result} values.
|
|
9789
10635
|
*
|
|
9790
10636
|
* Use this to validate Results crossing a storage, worker, API, or other
|
|
9791
|
-
* serialization boundary.
|
|
9792
|
-
* successful value is the inner domain Result described by `okType` and
|
|
10637
|
+
* serialization boundary. `fromUnknown` returns an outer validation Result.
|
|
10638
|
+
* Its successful value is the inner domain Result described by `okType` and
|
|
9793
10639
|
* `errorType`.
|
|
9794
10640
|
*
|
|
9795
10641
|
* ### Example
|
|
@@ -9805,34 +10651,16 @@ type OmitKeyConcreteTypeError = CompileTimeError<
|
|
|
9805
10651
|
*
|
|
9806
10652
|
* const SyncResponse = result(
|
|
9807
10653
|
* object({ timestamp: NonNegativeInt }),
|
|
9808
|
-
* typed("
|
|
10654
|
+
* typed("SyncFailed", { message: String }),
|
|
9809
10655
|
* );
|
|
9810
10656
|
*
|
|
9811
|
-
* const
|
|
9812
|
-
*
|
|
9813
|
-
*
|
|
9814
|
-
*
|
|
9815
|
-
* const response = validated.value;
|
|
9816
|
-
* return response.ok
|
|
9817
|
-
* ? `Synced at ${response.value.timestamp}`
|
|
9818
|
-
* : response.error.message;
|
|
9819
|
-
* };
|
|
10657
|
+
* const validated = SyncResponse.fromUnknown({
|
|
10658
|
+
* ok: true,
|
|
10659
|
+
* value: { timestamp: 42 },
|
|
10660
|
+
* });
|
|
9820
10661
|
*
|
|
9821
|
-
*
|
|
9822
|
-
*
|
|
9823
|
-
* ok: true,
|
|
9824
|
-
* value: { timestamp: 42 },
|
|
9825
|
-
* }),
|
|
9826
|
-
* ).toBe("Synced at 42");
|
|
9827
|
-
* expect(
|
|
9828
|
-
* describeResponse({
|
|
9829
|
-
* ok: false,
|
|
9830
|
-
* error: { type: "SyncError", message: "Offline" },
|
|
9831
|
-
* }),
|
|
9832
|
-
* ).toBe("Offline");
|
|
9833
|
-
* expect(describeResponse({ ok: true, value: { timestamp: -1 } })).toBe(
|
|
9834
|
-
* "Invalid response",
|
|
9835
|
-
* );
|
|
10662
|
+
* expectOk(validated, { ok: true, value: { timestamp: 42 } });
|
|
10663
|
+
* expectOk(validated.value, { timestamp: 42 });
|
|
9836
10664
|
* ```
|
|
9837
10665
|
*
|
|
9838
10666
|
* @group Results
|
|
@@ -9887,7 +10715,7 @@ export const UnknownResult = /*#__PURE__*/ result(Unknown, Unknown);
|
|
|
9887
10715
|
export type UnknownResult = typeof UnknownResult.Output;
|
|
9888
10716
|
|
|
9889
10717
|
/**
|
|
9890
|
-
*
|
|
10718
|
+
* Creates an {@link ObjectType} with a literal `type` property.
|
|
9891
10719
|
*
|
|
9892
10720
|
* The discriminator belongs to `typed`, so additional properties cannot declare
|
|
9893
10721
|
* `type`. The discriminator Input is `string`, inherited from {@link String},
|
|
@@ -9899,22 +10727,16 @@ export type UnknownResult = typeof UnknownResult.Output;
|
|
|
9899
10727
|
* ### Example
|
|
9900
10728
|
*
|
|
9901
10729
|
* ```ts
|
|
9902
|
-
* import { String,
|
|
10730
|
+
* import { String, typed } from "@evolu/common";
|
|
9903
10731
|
*
|
|
9904
10732
|
* const Pending = typed("Pending", {
|
|
9905
10733
|
* label: String,
|
|
9906
10734
|
* });
|
|
9907
10735
|
*
|
|
9908
|
-
*
|
|
9909
|
-
* const Status = discriminatedUnion(Pending, Completed);
|
|
9910
|
-
*
|
|
9911
|
-
* expectOk(Status.fromUnknown({ type: "Pending", label: "Waiting" }), {
|
|
10736
|
+
* expectOk(Pending.fromUnknown({ type: "Pending", label: "Waiting" }), {
|
|
9912
10737
|
* type: "Pending",
|
|
9913
10738
|
* label: "Waiting",
|
|
9914
10739
|
* });
|
|
9915
|
-
* expectOk(Status.fromUnknown({ type: "Completed" }), {
|
|
9916
|
-
* type: "Completed",
|
|
9917
|
-
* });
|
|
9918
10740
|
* ```
|
|
9919
10741
|
*
|
|
9920
10742
|
* @group Discriminated unions
|
|
@@ -9922,6 +10744,8 @@ export type UnknownResult = typeof UnknownResult.Output;
|
|
|
9922
10744
|
export function typed<const Tag extends TypeName>(
|
|
9923
10745
|
tag: ValidateTypedTag<Tag>,
|
|
9924
10746
|
): TypedType<Tag>;
|
|
10747
|
+
|
|
10748
|
+
/** Creates a Tagged Object Type with declared properties. */
|
|
9925
10749
|
export function typed<
|
|
9926
10750
|
const Tag extends TypeName,
|
|
9927
10751
|
const Props extends ObjectProps,
|
|
@@ -9932,6 +10756,8 @@ export function typed<
|
|
|
9932
10756
|
? []
|
|
9933
10757
|
: [ValidationFailure<TypedValidationError<Props>>]
|
|
9934
10758
|
): TypedType<Tag, Props>;
|
|
10759
|
+
|
|
10760
|
+
/** Creates a Tagged Object Type with additional record properties. */
|
|
9935
10761
|
export function typed<
|
|
9936
10762
|
const Tag extends TypeName,
|
|
9937
10763
|
const Props extends ObjectProps,
|
|
@@ -9971,7 +10797,14 @@ export function typed(
|
|
|
9971
10797
|
}
|
|
9972
10798
|
|
|
9973
10799
|
/**
|
|
9974
|
-
* A
|
|
10800
|
+
* A TypeScript interface with a literal `type` property.
|
|
10801
|
+
*
|
|
10802
|
+
* Use `Typed` for both domain objects in discriminated unions and plain domain
|
|
10803
|
+
* errors returned by {@link Result}. Name a domain error interface `XError`.
|
|
10804
|
+
* When `X` already describes a failure, use `X` for its `type` discriminant
|
|
10805
|
+
* because `Error` describes the interface's role rather than the runtime error
|
|
10806
|
+
* kind. Keep `Error` when it is needed to make the discriminant unambiguous,
|
|
10807
|
+
* such as `TimeoutError`.
|
|
9975
10808
|
*
|
|
9976
10809
|
* Typed unions model mutually exclusive states as separate variants instead of
|
|
9977
10810
|
* combinations of flags and optional properties. TypeScript narrows a union by
|
|
@@ -9982,20 +10815,23 @@ export function typed(
|
|
|
9982
10815
|
* ### Example
|
|
9983
10816
|
*
|
|
9984
10817
|
* ```ts
|
|
9985
|
-
* import { type Typed } from "@evolu/common";
|
|
10818
|
+
* import { err, ok, type Result, type Typed } from "@evolu/common";
|
|
9986
10819
|
*
|
|
9987
|
-
*
|
|
10820
|
+
* interface User extends Typed<"User"> {
|
|
10821
|
+
* readonly id: string;
|
|
10822
|
+
* }
|
|
9988
10823
|
*
|
|
9989
|
-
* const
|
|
9990
|
-
*
|
|
9991
|
-
*
|
|
9992
|
-
*
|
|
9993
|
-
*
|
|
9994
|
-
*
|
|
9995
|
-
*
|
|
9996
|
-
* }
|
|
10824
|
+
* const getUser = (id: string): Result<User, UserNotFoundError> =>
|
|
10825
|
+
* id === "user-1"
|
|
10826
|
+
* ? ok({ type: "User", id })
|
|
10827
|
+
* : err({ type: "UserNotFound", id });
|
|
10828
|
+
*
|
|
10829
|
+
* interface UserNotFoundError extends Typed<"UserNotFound"> {
|
|
10830
|
+
* readonly id: string;
|
|
10831
|
+
* }
|
|
9997
10832
|
*
|
|
9998
|
-
*
|
|
10833
|
+
* expectOk(getUser("user-1"), { type: "User", id: "user-1" });
|
|
10834
|
+
* expectErr(getUser("missing"), { type: "UserNotFound", id: "missing" });
|
|
9999
10835
|
* ```
|
|
10000
10836
|
*
|
|
10001
10837
|
* @group Discriminated unions
|
|
@@ -10007,9 +10843,8 @@ export interface Typed<Tag extends TypeName> {
|
|
|
10007
10843
|
/**
|
|
10008
10844
|
* Extracts members of a {@link Typed} Output union by their `type` literal.
|
|
10009
10845
|
*
|
|
10010
|
-
* The requested
|
|
10011
|
-
* so a misspelling is a TypeScript error instead of silently producing
|
|
10012
|
-
* `never`.
|
|
10846
|
+
* The requested tag is constrained to the union's actual discriminator values,
|
|
10847
|
+
* so a misspelling is a TypeScript error instead of silently producing `never`.
|
|
10013
10848
|
*
|
|
10014
10849
|
* ### Example
|
|
10015
10850
|
*
|
|
@@ -10079,7 +10914,8 @@ type TypedTypePropertyError = CompileTimeError<
|
|
|
10079
10914
|
>;
|
|
10080
10915
|
|
|
10081
10916
|
/**
|
|
10082
|
-
*
|
|
10917
|
+
* Creates a {@link Type} for producer Results with value, error, or done
|
|
10918
|
+
* outcomes.
|
|
10083
10919
|
*
|
|
10084
10920
|
* The three outcomes are `Ok<Value>`, `Err<Error>`, and `Err<Typed<"Done"> & {
|
|
10085
10921
|
* done: Done }>`. This keeps normal completion distinct from failure while
|
|
@@ -10092,7 +10928,7 @@ type TypedTypePropertyError = CompileTimeError<
|
|
|
10092
10928
|
*
|
|
10093
10929
|
* const StringNextResult = nextResult(
|
|
10094
10930
|
* String,
|
|
10095
|
-
* typed("
|
|
10931
|
+
* typed("ReadFailed", { message: String }),
|
|
10096
10932
|
* String,
|
|
10097
10933
|
* );
|
|
10098
10934
|
*
|
|
@@ -10116,7 +10952,7 @@ type TypedTypePropertyError = CompileTimeError<
|
|
|
10116
10952
|
* expect(
|
|
10117
10953
|
* describeNext({
|
|
10118
10954
|
* ok: false,
|
|
10119
|
-
* error: { type: "
|
|
10955
|
+
* error: { type: "ReadFailed", message: "Offline" },
|
|
10120
10956
|
* }),
|
|
10121
10957
|
* ).toBe("Error: Offline");
|
|
10122
10958
|
* ```
|
|
@@ -10178,7 +11014,7 @@ export const UnknownNextResult = /*#__PURE__*/ nextResult(
|
|
|
10178
11014
|
export type UnknownNextResult = typeof UnknownNextResult.Output;
|
|
10179
11015
|
|
|
10180
11016
|
/**
|
|
10181
|
-
* Discriminated
|
|
11017
|
+
* Discriminated union {@link Type}.
|
|
10182
11018
|
*
|
|
10183
11019
|
* With no explicit key, the conventional `type` property created by
|
|
10184
11020
|
* {@link typed} is used. Pass a key first to discriminate
|
|
@@ -10213,6 +11049,35 @@ export function discriminatedUnion<
|
|
|
10213
11049
|
>(
|
|
10214
11050
|
...members: Members & DiscriminatedUnionValidation<"type", Members>
|
|
10215
11051
|
): DiscriminatedUnionType<"type", Members>;
|
|
11052
|
+
|
|
11053
|
+
/**
|
|
11054
|
+
* Creates a Discriminated Union Type with an explicit discriminator key.
|
|
11055
|
+
*
|
|
11056
|
+
* ### Example
|
|
11057
|
+
*
|
|
11058
|
+
* ```ts
|
|
11059
|
+
* import {
|
|
11060
|
+
* Number,
|
|
11061
|
+
* String,
|
|
11062
|
+
* discriminatedUnion,
|
|
11063
|
+
* literal,
|
|
11064
|
+
* object,
|
|
11065
|
+
* } from "@evolu/common";
|
|
11066
|
+
*
|
|
11067
|
+
* const Added = object({ kind: literal("added"), value: String });
|
|
11068
|
+
* const Removed = object({ kind: literal("removed"), id: Number });
|
|
11069
|
+
* const Event = discriminatedUnion("kind", Added, Removed);
|
|
11070
|
+
*
|
|
11071
|
+
* expectOk(Event.fromUnknown({ kind: "added", value: "Evolu" }), {
|
|
11072
|
+
* kind: "added",
|
|
11073
|
+
* value: "Evolu",
|
|
11074
|
+
* });
|
|
11075
|
+
* expectOk(Event.fromUnknown({ kind: "removed", id: 1 }), {
|
|
11076
|
+
* kind: "removed",
|
|
11077
|
+
* id: 1,
|
|
11078
|
+
* });
|
|
11079
|
+
* ```
|
|
11080
|
+
*/
|
|
10216
11081
|
export function discriminatedUnion<
|
|
10217
11082
|
const Key extends string,
|
|
10218
11083
|
const Members extends DiscriminatedUnionMembers,
|
|
@@ -10354,26 +11219,21 @@ export function discriminatedUnion(
|
|
|
10354
11219
|
Reflect.get(value as Readonly<Record<string, unknown>>, key),
|
|
10355
11220
|
)!;
|
|
10356
11221
|
const formatError: TypeErrorFormatter<DiscriminatedUnionError> = (error) => {
|
|
10357
|
-
|
|
10358
|
-
DiscriminatedUnionError["reason"],
|
|
10359
|
-
DiscriminatedUnionMemberIssue
|
|
10360
|
-
>;
|
|
10361
|
-
|
|
10362
|
-
switch (reason.kind) {
|
|
11222
|
+
switch (error.reason.kind) {
|
|
10363
11223
|
case "Object":
|
|
10364
|
-
return formatPlainObjectRootError(reason.error.reason);
|
|
11224
|
+
return formatPlainObjectRootError(error.reason.error.reason);
|
|
10365
11225
|
case "PropertyAccess": {
|
|
10366
|
-
const property = `The discriminator property ${safelyStringifyUnknownValue(reason.key)}`;
|
|
10367
|
-
if (reason.reason === "Accessor")
|
|
11226
|
+
const property = `The discriminator property ${safelyStringifyUnknownValue(error.reason.key)}`;
|
|
11227
|
+
if (error.reason.reason === "Accessor")
|
|
10368
11228
|
return `${property} must be a data property.`;
|
|
10369
|
-
|
|
10370
|
-
if (reason.reason === "Inherited") {
|
|
11229
|
+
if (error.reason.reason === "Inherited")
|
|
10371
11230
|
return `${property} must be an own property.`;
|
|
10372
|
-
}
|
|
10373
11231
|
return `${property} must be enumerable.`;
|
|
10374
11232
|
}
|
|
10375
11233
|
case "Discriminator":
|
|
10376
|
-
return `The discriminator property ${safelyStringifyUnknownValue(reason.key)} has an unexpected value ${safelyStringifyUnknownValue(reason.value)}.`;
|
|
11234
|
+
return `The discriminator property ${safelyStringifyUnknownValue(error.reason.key)} has an unexpected value ${safelyStringifyUnknownValue(error.reason.value)}.`;
|
|
11235
|
+
case "Member":
|
|
11236
|
+
return `The selected variant ${safelyStringifyUnknownValue(error.reason.discriminator)} is invalid.`;
|
|
10377
11237
|
}
|
|
10378
11238
|
};
|
|
10379
11239
|
const defaultFormatter = formatError as TypeErrorFormatter<TypeError>;
|
|
@@ -10799,10 +11659,10 @@ type RuntimeDiscriminatedUnionMember = RuntimeObjectTypeNode & {
|
|
|
10799
11659
|
};
|
|
10800
11660
|
|
|
10801
11661
|
/**
|
|
10802
|
-
*
|
|
11662
|
+
* Creates a lazy {@link Type} for recursive definitions.
|
|
10803
11663
|
*
|
|
10804
|
-
*
|
|
10805
|
-
* trees and mutually recursive models.
|
|
11664
|
+
* The definition is evaluated on first use and then cached, allowing recursive
|
|
11665
|
+
* data such as trees and mutually recursive models.
|
|
10806
11666
|
*
|
|
10807
11667
|
* A recursive declaration refers to its own variable while that variable is
|
|
10808
11668
|
* being initialized, so TypeScript cannot infer it reliably. Getter-based
|
|
@@ -10822,12 +11682,10 @@ type RuntimeDiscriminatedUnionMember = RuntimeObjectTypeNode & {
|
|
|
10822
11682
|
*
|
|
10823
11683
|
* ```ts
|
|
10824
11684
|
* import {
|
|
10825
|
-
* Number,
|
|
10826
11685
|
* String,
|
|
10827
11686
|
* array,
|
|
10828
11687
|
* lazy,
|
|
10829
11688
|
* object,
|
|
10830
|
-
* optional,
|
|
10831
11689
|
* type ArrayError,
|
|
10832
11690
|
* type LazyType,
|
|
10833
11691
|
* type ObjectError,
|
|
@@ -10848,35 +11706,19 @@ type RuntimeDiscriminatedUnionMember = RuntimeObjectTypeNode & {
|
|
|
10848
11706
|
* () => object({ value: String, children: array(Tree) }),
|
|
10849
11707
|
* );
|
|
10850
11708
|
*
|
|
10851
|
-
*
|
|
10852
|
-
*
|
|
10853
|
-
*
|
|
10854
|
-
* }
|
|
10855
|
-
*
|
|
10856
|
-
* interface Right {
|
|
10857
|
-
* readonly count: number;
|
|
10858
|
-
* readonly left?: Left;
|
|
10859
|
-
* }
|
|
10860
|
-
*
|
|
10861
|
-
* interface LeftError extends ObjectError<{
|
|
10862
|
-
* readonly label: TypeOfError<"String">;
|
|
10863
|
-
* readonly right?: RightError;
|
|
10864
|
-
* }> {}
|
|
10865
|
-
*
|
|
10866
|
-
* interface RightError extends ObjectError<{
|
|
10867
|
-
* readonly count: TypeOfError<"Number">;
|
|
10868
|
-
* readonly left?: LeftError;
|
|
10869
|
-
* }> {}
|
|
10870
|
-
*
|
|
10871
|
-
* const Left: LazyType<Left, Left, never, LeftError, LeftError> = lazy(
|
|
10872
|
-
* () => object({ label: String, right: optional(Right) }),
|
|
10873
|
-
* );
|
|
11709
|
+
* const result = Tree.fromUnknown({
|
|
11710
|
+
* value: "root",
|
|
11711
|
+
* children: [{ value: "leaf", children: [] }],
|
|
11712
|
+
* });
|
|
10874
11713
|
*
|
|
10875
|
-
*
|
|
10876
|
-
*
|
|
11714
|
+
* expectOk(result, {
|
|
11715
|
+
* value: "root",
|
|
11716
|
+
* children: [{ value: "leaf", children: [] }],
|
|
11717
|
+
* });
|
|
11718
|
+
* expectTypeOf(result.value).toEqualTypeOf<Tree>();
|
|
10877
11719
|
* ```
|
|
10878
11720
|
*
|
|
10879
|
-
* @group Recursive
|
|
11721
|
+
* @group Recursive
|
|
10880
11722
|
*/
|
|
10881
11723
|
export function lazy<Target extends ConcreteTypeNode>(
|
|
10882
11724
|
getType: Thunk<ValidateLazyTarget<Target>>,
|
|
@@ -10995,7 +11837,7 @@ export function lazy(getType: Thunk<TypeNode>): TypeNode {
|
|
|
10995
11837
|
* recursive declaration finite for TypeScript while preserving structured
|
|
10996
11838
|
* errors at every boundary.
|
|
10997
11839
|
*
|
|
10998
|
-
* @group Recursive
|
|
11840
|
+
* @group Recursive
|
|
10999
11841
|
*/
|
|
11000
11842
|
export interface LazyType<
|
|
11001
11843
|
// Explicit invariance prevents recursive comparisons from repeatedly
|
|
@@ -11123,7 +11965,7 @@ export interface JsonObject {
|
|
|
11123
11965
|
}
|
|
11124
11966
|
|
|
11125
11967
|
/**
|
|
11126
|
-
* One issue found while validating an exact {@link JsonValue}.
|
|
11968
|
+
* One issue found while validating a candidate as an exact {@link JsonValue}.
|
|
11127
11969
|
*
|
|
11128
11970
|
* @group JSON
|
|
11129
11971
|
*/
|
|
@@ -11171,7 +12013,8 @@ export type JsonValueIssue =
|
|
|
11171
12013
|
};
|
|
11172
12014
|
|
|
11173
12015
|
/**
|
|
11174
|
-
* An error containing one or more issues found
|
|
12016
|
+
* An error containing one or more issues found while validating a candidate as
|
|
12017
|
+
* an exact {@link JsonValue}.
|
|
11175
12018
|
*
|
|
11176
12019
|
* @group JSON
|
|
11177
12020
|
*/
|
|
@@ -11500,31 +12343,6 @@ const validateJsonValue = (
|
|
|
11500
12343
|
});
|
|
11501
12344
|
};
|
|
11502
12345
|
|
|
11503
|
-
const formatJsonValueError: TypeErrorFormatter<JsonValueError> = (error) => {
|
|
11504
|
-
const issue = error.reason.issues[0];
|
|
11505
|
-
|
|
11506
|
-
switch (issue.kind) {
|
|
11507
|
-
case "InvalidType":
|
|
11508
|
-
return `A value ${safelyStringifyUnknownValue(issue.value)} is not a JSON value.`;
|
|
11509
|
-
case "NonFiniteNumber":
|
|
11510
|
-
return "A JSON number must be finite.";
|
|
11511
|
-
case "UnexpectedPrototype":
|
|
11512
|
-
return "The value is an object, but a JsonValue Object must be a plain object or have a null prototype.";
|
|
11513
|
-
case "Accessor":
|
|
11514
|
-
return "A JSON property must be a data property. Materialize accessor values into plain data before using this Type or use a different Type.";
|
|
11515
|
-
case "NonEnumerable":
|
|
11516
|
-
return "A JSON Object property must be enumerable. Remove it or use a different Type.";
|
|
11517
|
-
case "SymbolProperty":
|
|
11518
|
-
return "A JSON Object property key must be a string. Remove the symbol property or use a different Type.";
|
|
11519
|
-
case "Hole":
|
|
11520
|
-
return "A JSON Array element is missing.";
|
|
11521
|
-
case "ExcessProperty":
|
|
11522
|
-
return "An excess JSON Array property is not allowed. Remove it or use a different Type.";
|
|
11523
|
-
case "CircularReference":
|
|
11524
|
-
return "A JsonValue must not contain circular references.";
|
|
11525
|
-
}
|
|
11526
|
-
};
|
|
11527
|
-
|
|
11528
12346
|
const getJsonValueRuntimeTypeIssues: RuntimeGetTypeIssues = (error, mode) => {
|
|
11529
12347
|
const jsonValueError = error as JsonValueError;
|
|
11530
12348
|
const issues =
|
|
@@ -11542,7 +12360,29 @@ const getJsonValueRuntimeTypeIssues: RuntimeGetTypeIssues = (error, mode) => {
|
|
|
11542
12360
|
reason: { kind: "Issues", issues: [issue] },
|
|
11543
12361
|
},
|
|
11544
12362
|
path: issue.path,
|
|
11545
|
-
formatError:
|
|
12363
|
+
formatError: ((error: JsonValueError) => {
|
|
12364
|
+
const issue = error.reason.issues[0];
|
|
12365
|
+
switch (issue.kind) {
|
|
12366
|
+
case "InvalidType":
|
|
12367
|
+
return `A value ${safelyStringifyUnknownValue(issue.value)} is not a JSON value.`;
|
|
12368
|
+
case "NonFiniteNumber":
|
|
12369
|
+
return "A JSON number must be finite.";
|
|
12370
|
+
case "UnexpectedPrototype":
|
|
12371
|
+
return "The value is an object, but a JsonValue object must be a plain object or have a null prototype.";
|
|
12372
|
+
case "Accessor":
|
|
12373
|
+
return "A JSON property must be a data property. Materialize accessor values into plain data before using this Type or use a different Type.";
|
|
12374
|
+
case "NonEnumerable":
|
|
12375
|
+
return "A JSON object property must be enumerable. Remove it or use a different Type.";
|
|
12376
|
+
case "SymbolProperty":
|
|
12377
|
+
return "A JSON object property key must be a string. Remove the symbol property or use a different Type.";
|
|
12378
|
+
case "Hole":
|
|
12379
|
+
return "A JSON array element is missing.";
|
|
12380
|
+
case "ExcessProperty":
|
|
12381
|
+
return "An excess JSON array property is not allowed. Remove it or use a different Type.";
|
|
12382
|
+
case "CircularReference":
|
|
12383
|
+
return "A JsonValue must not contain circular references.";
|
|
12384
|
+
}
|
|
12385
|
+
}) as TypeErrorFormatter<TypeError>,
|
|
11546
12386
|
})) as unknown as NonEmptyReadonlyArray<RuntimeTypeIssue>;
|
|
11547
12387
|
};
|
|
11548
12388
|
|
|
@@ -11627,6 +12467,21 @@ const stringifyJsonValue = (value: JsonValue): Json => {
|
|
|
11627
12467
|
/**
|
|
11628
12468
|
* Exact root Type for {@link JsonValue} data trees.
|
|
11629
12469
|
*
|
|
12470
|
+
* ### Example
|
|
12471
|
+
*
|
|
12472
|
+
* ```ts
|
|
12473
|
+
* import { JsonValue, type JsonValueInput } from "@evolu/common";
|
|
12474
|
+
*
|
|
12475
|
+
* const input: JsonValueInput = {
|
|
12476
|
+
* name: "Ada",
|
|
12477
|
+
* scores: [10, 20],
|
|
12478
|
+
* };
|
|
12479
|
+
* const result = JsonValue.fromUnknown(input);
|
|
12480
|
+
*
|
|
12481
|
+
* expectOk(result, input);
|
|
12482
|
+
* expectTypeOf(result.value).toEqualTypeOf<JsonValue>();
|
|
12483
|
+
* ```
|
|
12484
|
+
*
|
|
11630
12485
|
* @group JSON
|
|
11631
12486
|
*/
|
|
11632
12487
|
export const JsonValue: JsonValueType =
|
|
@@ -11683,6 +12538,16 @@ export type Json = typeof Json.Output;
|
|
|
11683
12538
|
/**
|
|
11684
12539
|
* Totally parses proven {@link Json} text into an exact {@link JsonValue}.
|
|
11685
12540
|
*
|
|
12541
|
+
* ### Example
|
|
12542
|
+
*
|
|
12543
|
+
* ```ts
|
|
12544
|
+
* import { Json, jsonToJsonValue } from "@evolu/common";
|
|
12545
|
+
*
|
|
12546
|
+
* const value = jsonToJsonValue(Json.orThrow('{"name":"Ada"}'));
|
|
12547
|
+
*
|
|
12548
|
+
* expect(value).toEqual({ name: "Ada" });
|
|
12549
|
+
* ```
|
|
12550
|
+
*
|
|
11686
12551
|
* @group JSON
|
|
11687
12552
|
*/
|
|
11688
12553
|
export const jsonToJsonValue = (value: Json): JsonValue => parseJson(value);
|
|
@@ -11690,17 +12555,38 @@ export const jsonToJsonValue = (value: Json): JsonValue => parseJson(value);
|
|
|
11690
12555
|
/**
|
|
11691
12556
|
* Totally encodes an exact {@link JsonValue} as canonical {@link Json} text.
|
|
11692
12557
|
*
|
|
12558
|
+
* ### Example
|
|
12559
|
+
*
|
|
12560
|
+
* ```ts
|
|
12561
|
+
* import { jsonValueToJson, type JsonValue } from "@evolu/common";
|
|
12562
|
+
*
|
|
12563
|
+
* const value: JsonValue = { name: "Ada" };
|
|
12564
|
+
*
|
|
12565
|
+
* expect(jsonValueToJson(value)).toBe('{"name":"Ada"}');
|
|
12566
|
+
* ```
|
|
12567
|
+
*
|
|
11693
12568
|
* @group JSON
|
|
11694
12569
|
*/
|
|
11695
12570
|
export const jsonValueToJson = (value: JsonValue): Json =>
|
|
11696
12571
|
stringifyJsonValue(JsonValue.to(value));
|
|
11697
12572
|
|
|
11698
12573
|
/**
|
|
11699
|
-
* {@link Json}
|
|
12574
|
+
* Transformation {@link Type} that parses {@link Json} into {@link JsonValue}.
|
|
11700
12575
|
*
|
|
11701
12576
|
* Decoding unknown input first validates the Json Brand. Starting from the
|
|
11702
12577
|
* typed Json parent is infallible. Encoding canonicalizes JSON text.
|
|
11703
12578
|
*
|
|
12579
|
+
* ### Example
|
|
12580
|
+
*
|
|
12581
|
+
* ```ts
|
|
12582
|
+
* import { JsonValueFromJson } from "@evolu/common";
|
|
12583
|
+
*
|
|
12584
|
+
* const result = JsonValueFromJson.fromUnknown('{ "name": "Ada" }');
|
|
12585
|
+
*
|
|
12586
|
+
* expectOk(result, { name: "Ada" });
|
|
12587
|
+
* expect(JsonValueFromJson.to(result.value)).toBe('{"name":"Ada"}');
|
|
12588
|
+
* ```
|
|
12589
|
+
*
|
|
11704
12590
|
* @group JSON
|
|
11705
12591
|
*/
|
|
11706
12592
|
export const JsonValueFromJson = /*#__PURE__*/ transform(
|