@evolu/common 8.5.0 → 8.6.1
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/Array.d.ts +170 -97
- package/dist/src/Array.d.ts.map +1 -1
- package/dist/src/Array.js +67 -42
- package/dist/src/Assert.d.ts +233 -30
- package/dist/src/Assert.d.ts.map +1 -1
- package/dist/src/Assert.js +150 -30
- package/dist/src/BigInt.d.ts +3 -3
- package/dist/src/BigInt.js +3 -3
- package/dist/src/Brand.d.ts +7 -7
- package/dist/src/Buffer.d.ts +12 -4
- package/dist/src/Buffer.d.ts.map +1 -1
- package/dist/src/Cache.d.ts +8 -3
- package/dist/src/Cache.d.ts.map +1 -1
- package/dist/src/Cache.js +8 -3
- package/dist/src/Callbacks.d.ts +8 -4
- package/dist/src/Callbacks.d.ts.map +1 -1
- package/dist/src/Console.d.ts +45 -30
- package/dist/src/Console.d.ts.map +1 -1
- package/dist/src/Console.js +32 -21
- package/dist/src/Crypto.d.ts +13 -8
- package/dist/src/Crypto.d.ts.map +1 -1
- package/dist/src/Crypto.js +6 -3
- package/dist/src/Eq.d.ts +107 -58
- package/dist/src/Eq.d.ts.map +1 -1
- package/dist/src/Eq.js +308 -112
- package/dist/src/Function.d.ts +54 -24
- package/dist/src/Function.d.ts.map +1 -1
- package/dist/src/Function.js +34 -17
- package/dist/src/Http.d.ts +52 -19
- package/dist/src/Http.d.ts.map +1 -1
- package/dist/src/Identicon.d.ts +9 -4
- package/dist/src/Identicon.d.ts.map +1 -1
- package/dist/src/Identicon.js +9 -4
- package/dist/src/LockManager.d.ts +12 -10
- package/dist/src/LockManager.d.ts.map +1 -1
- package/dist/src/Lookup.d.ts +4 -2
- package/dist/src/Lookup.d.ts.map +1 -1
- package/dist/src/Lookup.js +4 -2
- package/dist/src/Number.d.ts +28 -24
- package/dist/src/Number.d.ts.map +1 -1
- package/dist/src/Number.js +16 -17
- package/dist/src/Object.d.ts +89 -47
- package/dist/src/Object.d.ts.map +1 -1
- package/dist/src/Object.js +88 -41
- package/dist/src/Option.d.ts +14 -5
- package/dist/src/Option.d.ts.map +1 -1
- package/dist/src/Option.js +14 -5
- package/dist/src/Order.d.ts +12 -12
- package/dist/src/Order.js +12 -12
- package/dist/src/Platform.d.ts +2 -2
- package/dist/src/Random.d.ts +7 -4
- package/dist/src/Random.d.ts.map +1 -1
- package/dist/src/Redacted.d.ts +13 -6
- package/dist/src/Redacted.d.ts.map +1 -1
- package/dist/src/Redacted.js +4 -2
- package/dist/src/Ref.d.ts +4 -4
- package/dist/src/Relation.d.ts +5 -7
- package/dist/src/Relation.d.ts.map +1 -1
- package/dist/src/Resource.d.ts +27 -12
- package/dist/src/Resource.d.ts.map +1 -1
- package/dist/src/Result.d.ts +258 -114
- package/dist/src/Result.d.ts.map +1 -1
- package/dist/src/Result.js +71 -34
- package/dist/src/Schedule.d.ts +233 -116
- package/dist/src/Schedule.d.ts.map +1 -1
- package/dist/src/Schedule.js +204 -110
- package/dist/src/Set.d.ts +43 -24
- package/dist/src/Set.d.ts.map +1 -1
- package/dist/src/Set.js +25 -13
- package/dist/src/Sqlite.d.ts +4 -4
- package/dist/src/Sqlite.d.ts.map +1 -1
- package/dist/src/Sqlite.js +9 -8
- package/dist/src/Task.d.ts +441 -212
- package/dist/src/Task.d.ts.map +1 -1
- package/dist/src/Task.js +238 -115
- package/dist/src/Test.d.ts +10 -4
- package/dist/src/Test.d.ts.map +1 -1
- package/dist/src/Test.js +10 -4
- package/dist/src/Time.d.ts +27 -17
- package/dist/src/Time.d.ts.map +1 -1
- package/dist/src/Time.js +16 -6
- package/dist/src/Type.d.ts +1096 -387
- package/dist/src/Type.d.ts.map +1 -1
- package/dist/src/Type.js +1046 -372
- package/dist/src/Types.d.ts +95 -40
- package/dist/src/Types.d.ts.map +1 -1
- package/dist/src/Types.js +13 -4
- package/dist/src/WebSocket.d.ts +14 -6
- package/dist/src/WebSocket.d.ts.map +1 -1
- package/dist/src/Worker.d.ts +10 -6
- package/dist/src/Worker.d.ts.map +1 -1
- package/dist/src/intl/_en.d.ts +3 -1
- package/dist/src/intl/_en.d.ts.map +1 -1
- package/dist/src/intl/_en.js +17 -3
- package/dist/src/intl/ar.d.ts +3 -1
- package/dist/src/intl/ar.d.ts.map +1 -1
- package/dist/src/intl/ar.js +15 -2
- package/dist/src/intl/bn.d.ts +3 -1
- package/dist/src/intl/bn.d.ts.map +1 -1
- package/dist/src/intl/bn.js +16 -3
- package/dist/src/intl/ca.d.ts +3 -1
- package/dist/src/intl/ca.d.ts.map +1 -1
- package/dist/src/intl/ca.js +16 -3
- package/dist/src/intl/cs.d.ts +3 -1
- package/dist/src/intl/cs.d.ts.map +1 -1
- package/dist/src/intl/cs.js +16 -3
- package/dist/src/intl/da.d.ts +3 -1
- package/dist/src/intl/da.d.ts.map +1 -1
- package/dist/src/intl/da.js +16 -3
- package/dist/src/intl/de.d.ts +3 -1
- package/dist/src/intl/de.d.ts.map +1 -1
- package/dist/src/intl/de.js +16 -3
- package/dist/src/intl/el.d.ts +3 -1
- package/dist/src/intl/el.d.ts.map +1 -1
- package/dist/src/intl/el.js +16 -3
- package/dist/src/intl/es.d.ts +3 -1
- package/dist/src/intl/es.d.ts.map +1 -1
- package/dist/src/intl/es.js +16 -3
- package/dist/src/intl/fa.d.ts +3 -1
- package/dist/src/intl/fa.d.ts.map +1 -1
- package/dist/src/intl/fa.js +16 -3
- package/dist/src/intl/fi.d.ts +3 -1
- package/dist/src/intl/fi.d.ts.map +1 -1
- package/dist/src/intl/fi.js +16 -3
- package/dist/src/intl/fil.d.ts +3 -1
- package/dist/src/intl/fil.d.ts.map +1 -1
- package/dist/src/intl/fil.js +16 -3
- package/dist/src/intl/fr.d.ts +3 -1
- package/dist/src/intl/fr.d.ts.map +1 -1
- package/dist/src/intl/fr.js +16 -3
- package/dist/src/intl/he.d.ts +3 -1
- package/dist/src/intl/he.d.ts.map +1 -1
- package/dist/src/intl/he.js +16 -3
- package/dist/src/intl/hi.d.ts +3 -1
- package/dist/src/intl/hi.d.ts.map +1 -1
- package/dist/src/intl/hi.js +16 -3
- package/dist/src/intl/hr.d.ts +3 -1
- package/dist/src/intl/hr.d.ts.map +1 -1
- package/dist/src/intl/hr.js +16 -3
- package/dist/src/intl/hu.d.ts +2 -1
- package/dist/src/intl/hu.d.ts.map +1 -1
- package/dist/src/intl/hu.js +15 -3
- package/dist/src/intl/id.d.ts +3 -1
- package/dist/src/intl/id.d.ts.map +1 -1
- package/dist/src/intl/id.js +16 -3
- package/dist/src/intl/it.d.ts +3 -1
- package/dist/src/intl/it.d.ts.map +1 -1
- package/dist/src/intl/it.js +16 -3
- package/dist/src/intl/ja.d.ts +3 -1
- package/dist/src/intl/ja.d.ts.map +1 -1
- package/dist/src/intl/ja.js +16 -3
- package/dist/src/intl/ko.d.ts +3 -1
- package/dist/src/intl/ko.d.ts.map +1 -1
- package/dist/src/intl/ko.js +16 -3
- package/dist/src/intl/ml.d.ts +3 -1
- package/dist/src/intl/ml.d.ts.map +1 -1
- package/dist/src/intl/ml.js +16 -3
- package/dist/src/intl/mr.d.ts +3 -1
- package/dist/src/intl/mr.d.ts.map +1 -1
- package/dist/src/intl/mr.js +16 -3
- package/dist/src/intl/ms.d.ts +3 -1
- package/dist/src/intl/ms.d.ts.map +1 -1
- package/dist/src/intl/ms.js +15 -2
- package/dist/src/intl/nb.d.ts +2 -1
- package/dist/src/intl/nb.d.ts.map +1 -1
- package/dist/src/intl/nb.js +14 -2
- package/dist/src/intl/nl.d.ts +3 -1
- package/dist/src/intl/nl.d.ts.map +1 -1
- package/dist/src/intl/nl.js +16 -3
- package/dist/src/intl/pa.d.ts +3 -1
- package/dist/src/intl/pa.d.ts.map +1 -1
- package/dist/src/intl/pa.js +16 -3
- package/dist/src/intl/pl.d.ts +2 -0
- package/dist/src/intl/pl.d.ts.map +1 -1
- package/dist/src/intl/pl.js +15 -2
- package/dist/src/intl/pt-BR.d.ts +3 -1
- package/dist/src/intl/pt-BR.d.ts.map +1 -1
- package/dist/src/intl/pt-BR.js +16 -3
- package/dist/src/intl/pt.d.ts +3 -1
- package/dist/src/intl/pt.d.ts.map +1 -1
- package/dist/src/intl/pt.js +15 -2
- package/dist/src/intl/ro.d.ts +3 -1
- package/dist/src/intl/ro.d.ts.map +1 -1
- package/dist/src/intl/ro.js +16 -3
- package/dist/src/intl/sk.d.ts +3 -1
- package/dist/src/intl/sk.d.ts.map +1 -1
- package/dist/src/intl/sk.js +15 -2
- package/dist/src/intl/sl.d.ts +3 -1
- package/dist/src/intl/sl.d.ts.map +1 -1
- package/dist/src/intl/sl.js +16 -3
- package/dist/src/intl/sv.d.ts +3 -1
- package/dist/src/intl/sv.d.ts.map +1 -1
- package/dist/src/intl/sv.js +16 -3
- package/dist/src/intl/sw.d.ts +1 -0
- package/dist/src/intl/sw.d.ts.map +1 -1
- package/dist/src/intl/sw.js +14 -2
- package/dist/src/intl/ta.d.ts +3 -1
- package/dist/src/intl/ta.d.ts.map +1 -1
- package/dist/src/intl/ta.js +16 -3
- package/dist/src/intl/te.d.ts +3 -1
- package/dist/src/intl/te.d.ts.map +1 -1
- package/dist/src/intl/te.js +16 -3
- package/dist/src/intl/th.d.ts +3 -1
- package/dist/src/intl/th.d.ts.map +1 -1
- package/dist/src/intl/th.js +16 -3
- package/dist/src/intl/tr.d.ts +3 -1
- package/dist/src/intl/tr.d.ts.map +1 -1
- package/dist/src/intl/tr.js +16 -3
- package/dist/src/intl/uk.d.ts +3 -1
- package/dist/src/intl/uk.d.ts.map +1 -1
- package/dist/src/intl/uk.js +16 -3
- package/dist/src/intl/ur.d.ts +3 -1
- package/dist/src/intl/ur.d.ts.map +1 -1
- package/dist/src/intl/ur.js +15 -2
- package/dist/src/intl/vi.d.ts +3 -1
- package/dist/src/intl/vi.d.ts.map +1 -1
- package/dist/src/intl/vi.js +16 -3
- package/dist/src/intl/zh-CN.d.ts +3 -1
- package/dist/src/intl/zh-CN.d.ts.map +1 -1
- package/dist/src/intl/zh-CN.js +16 -3
- package/dist/src/intl/zh-TW.d.ts +3 -1
- package/dist/src/intl/zh-TW.d.ts.map +1 -1
- package/dist/src/intl/zh-TW.js +16 -3
- package/dist/src/local-first/Evolu.d.ts +47 -24
- package/dist/src/local-first/Evolu.d.ts.map +1 -1
- package/dist/src/local-first/Owner.d.ts +10 -7
- package/dist/src/local-first/Owner.d.ts.map +1 -1
- package/dist/src/local-first/Owner.js +10 -7
- package/dist/src/local-first/Protocol.d.ts +5 -5
- package/dist/src/local-first/Protocol.js +7 -7
- package/dist/src/local-first/Query.d.ts +34 -21
- package/dist/src/local-first/Query.d.ts.map +1 -1
- package/dist/src/local-first/Query.js +25 -13
- package/dist/src/local-first/Relay.d.ts +12 -5
- package/dist/src/local-first/Relay.d.ts.map +1 -1
- package/dist/src/local-first/Schema.d.ts +19 -11
- package/dist/src/local-first/Schema.d.ts.map +1 -1
- package/dist/src/local-first/Schema.js +11 -6
- package/dist/src/local-first/Storage.d.ts +4 -2
- package/dist/src/local-first/Storage.d.ts.map +1 -1
- package/package.json +3 -4
- package/src/Array.ts +170 -97
- package/src/Assert.ts +336 -31
- package/src/BigInt.ts +3 -3
- package/src/Brand.ts +7 -7
- package/src/Buffer.ts +12 -4
- package/src/Cache.ts +8 -3
- package/src/Callbacks.ts +8 -4
- package/src/Console.ts +45 -30
- package/src/Crypto.ts +13 -8
- package/src/Eq.ts +450 -122
- package/src/Function.ts +54 -24
- package/src/Http.ts +52 -19
- package/src/Identicon.ts +9 -4
- package/src/LockManager.ts +12 -10
- package/src/Lookup.ts +4 -2
- package/src/Number.ts +28 -24
- package/src/Object.ts +115 -49
- package/src/Option.ts +14 -5
- package/src/Order.ts +12 -12
- package/src/Platform.ts +2 -2
- package/src/Random.ts +7 -4
- package/src/Redacted.ts +13 -6
- package/src/Ref.ts +4 -4
- package/src/Relation.ts +5 -7
- package/src/Resource.ts +27 -12
- package/src/Result.ts +258 -114
- package/src/Schedule.ts +233 -116
- package/src/Set.ts +43 -24
- package/src/Sqlite.ts +9 -8
- package/src/Task.ts +441 -212
- package/src/Test.ts +10 -4
- package/src/Time.ts +27 -17
- package/src/Type.ts +2077 -620
- package/src/Types.ts +108 -40
- package/src/WebSocket.ts +14 -6
- package/src/Worker.ts +10 -6
- package/src/intl/_en.ts +20 -4
- package/src/intl/ar.ts +17 -2
- package/src/intl/bn.ts +19 -4
- package/src/intl/ca.ts +19 -4
- package/src/intl/cs.ts +19 -4
- package/src/intl/da.ts +19 -4
- package/src/intl/de.ts +19 -4
- package/src/intl/el.ts +19 -4
- package/src/intl/es.ts +19 -4
- package/src/intl/fa.ts +19 -4
- package/src/intl/fi.ts +19 -4
- package/src/intl/fil.ts +19 -4
- package/src/intl/fr.ts +19 -4
- package/src/intl/he.ts +19 -4
- package/src/intl/hi.ts +19 -4
- package/src/intl/hr.ts +19 -4
- package/src/intl/hu.ts +18 -4
- package/src/intl/id.ts +19 -4
- package/src/intl/it.ts +19 -4
- package/src/intl/ja.ts +19 -4
- package/src/intl/ko.ts +19 -4
- package/src/intl/ml.ts +19 -4
- package/src/intl/mr.ts +19 -4
- package/src/intl/ms.ts +17 -2
- package/src/intl/nb.ts +16 -2
- package/src/intl/nl.ts +19 -4
- package/src/intl/pa.ts +19 -4
- package/src/intl/pl.ts +18 -2
- package/src/intl/pt-BR.ts +19 -4
- package/src/intl/pt.ts +17 -2
- package/src/intl/ro.ts +19 -4
- package/src/intl/sk.ts +17 -2
- package/src/intl/sl.ts +19 -4
- package/src/intl/sv.ts +19 -4
- package/src/intl/sw.ts +15 -2
- package/src/intl/ta.ts +19 -4
- package/src/intl/te.ts +19 -4
- package/src/intl/th.ts +19 -4
- package/src/intl/tr.ts +19 -4
- package/src/intl/uk.ts +19 -4
- package/src/intl/ur.ts +17 -2
- package/src/intl/vi.ts +19 -3
- package/src/intl/zh-CN.ts +19 -4
- package/src/intl/zh-TW.ts +19 -4
- package/src/local-first/Evolu.ts +47 -24
- package/src/local-first/Owner.ts +10 -7
- package/src/local-first/Protocol.ts +7 -7
- package/src/local-first/Query.ts +34 -21
- package/src/local-first/Relay.ts +12 -5
- package/src/local-first/Schema.ts +19 -11
- package/src/local-first/Storage.ts +4 -2
package/dist/src/Type.js
CHANGED
|
@@ -1,6 +1,4 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* ## Intro
|
|
3
|
-
*
|
|
4
2
|
* Runtime validation with precise TypeScript types and structured errors.
|
|
5
3
|
*
|
|
6
4
|
* Evolu {@link Type} is a pure, synchronous codec for defining semantic domains.
|
|
@@ -49,6 +47,11 @@
|
|
|
49
47
|
*
|
|
50
48
|
* ```ts
|
|
51
49
|
* import {
|
|
50
|
+
* assertEqual,
|
|
51
|
+
* Data,
|
|
52
|
+
* assertErr,
|
|
53
|
+
* assertOk,
|
|
54
|
+
* assertType,
|
|
52
55
|
* Number,
|
|
53
56
|
* NonEmptyTrimmedString100,
|
|
54
57
|
* brand,
|
|
@@ -73,14 +76,15 @@
|
|
|
73
76
|
* const Age = brand("Age", lessThan(200)(NonNegativeInt));
|
|
74
77
|
* type Age = typeof Age.Output;
|
|
75
78
|
*
|
|
76
|
-
*
|
|
79
|
+
* assertType<
|
|
77
80
|
* number &
|
|
78
81
|
* Brand<"NonNaN"> &
|
|
79
82
|
* Brand<"Finite"> &
|
|
80
83
|
* Brand<"Int"> &
|
|
81
84
|
* Brand<"NonNegative"> &
|
|
82
85
|
* Brand<"LessThan200"> &
|
|
83
|
-
* Brand<"Age"
|
|
86
|
+
* Brand<"Age">,
|
|
87
|
+
* Age
|
|
84
88
|
* >();
|
|
85
89
|
*
|
|
86
90
|
* const User = object({
|
|
@@ -92,12 +96,16 @@
|
|
|
92
96
|
* const value: unknown = { name: "Ada", age: 37 };
|
|
93
97
|
* const user = User.fromUnknown(value);
|
|
94
98
|
*
|
|
95
|
-
*
|
|
96
|
-
*
|
|
99
|
+
* assertOk(user, { name: "Ada", age: 37 });
|
|
100
|
+
* assertType<typeof User.Output, typeof user.value>();
|
|
97
101
|
*
|
|
98
102
|
* const invalidUser = User.fromUnknown({ name: "Ada", age: 37.5 });
|
|
99
103
|
*
|
|
100
|
-
*
|
|
104
|
+
* assertErr(invalidUser);
|
|
105
|
+
* // InferErrors includes every structured error User.fromUnknown can return.
|
|
106
|
+
* assertType<InferErrors<typeof User>, typeof invalidUser.error>();
|
|
107
|
+
* assertType(Data, invalidUser.error);
|
|
108
|
+
* assertEqual(invalidUser.error, {
|
|
101
109
|
* type: "Object",
|
|
102
110
|
* reason: {
|
|
103
111
|
* kind: "Properties",
|
|
@@ -106,22 +114,26 @@
|
|
|
106
114
|
* },
|
|
107
115
|
* },
|
|
108
116
|
* });
|
|
109
|
-
*
|
|
110
|
-
* // InferErrors includes every structured error User.fromUnknown can return.
|
|
111
|
-
* expectTypeOf(invalidUser.error).toEqualTypeOf<
|
|
112
|
-
* InferErrors<typeof User>
|
|
113
|
-
* >();
|
|
114
117
|
* ```
|
|
115
118
|
*
|
|
116
119
|
* A Type can format its structured errors into user-facing messages:
|
|
117
120
|
*
|
|
118
121
|
* ```ts
|
|
119
|
-
* import {
|
|
122
|
+
* import {
|
|
123
|
+
* assertEqual,
|
|
124
|
+
* assertType,
|
|
125
|
+
* Data,
|
|
126
|
+
* assertErr,
|
|
127
|
+
* Age,
|
|
128
|
+
* } from "@evolu/common";
|
|
120
129
|
*
|
|
121
130
|
* const age = Age.fromUnknown(37.5);
|
|
122
131
|
*
|
|
123
|
-
*
|
|
124
|
-
*
|
|
132
|
+
* assertErr(age);
|
|
133
|
+
* assertType(Data, age.error);
|
|
134
|
+
* assertEqual(age.error, { type: "Int", value: 37.5 });
|
|
135
|
+
* assertEqual(
|
|
136
|
+
* Age.formatError(age.error),
|
|
125
137
|
* "The value 37.5 must be a safe integer.",
|
|
126
138
|
* );
|
|
127
139
|
* ```
|
|
@@ -138,6 +150,8 @@
|
|
|
138
150
|
*
|
|
139
151
|
* ```ts
|
|
140
152
|
* import {
|
|
153
|
+
* assertOk,
|
|
154
|
+
* assertType,
|
|
141
155
|
* NonEmptyTrimmedString100,
|
|
142
156
|
* NonEmptyTrimmedString1000,
|
|
143
157
|
* object,
|
|
@@ -152,7 +166,7 @@
|
|
|
152
166
|
*
|
|
153
167
|
* // This is type-checked: Todo.from expects NonEmptyTrimmedString100.
|
|
154
168
|
* const title = NonEmptyTrimmedString100.orThrow("Buy milk");
|
|
155
|
-
*
|
|
169
|
+
* assertOk(Todo.from({ title }), { title });
|
|
156
170
|
*
|
|
157
171
|
* // Imagine the UI input component is changed to allow longer titles.
|
|
158
172
|
* // TypeScript rejects the mismatch, so users never see a save error
|
|
@@ -170,13 +184,14 @@
|
|
|
170
184
|
* );
|
|
171
185
|
*
|
|
172
186
|
* // No "not a string" or "not trimmed" errors: the input guarantees both.
|
|
173
|
-
*
|
|
187
|
+
* assertType<
|
|
174
188
|
* Result<
|
|
175
189
|
* NonEmptyTrimmedString100,
|
|
176
190
|
* MaxLengthError<100> | MinLengthError<1>
|
|
177
|
-
*
|
|
191
|
+
* >,
|
|
192
|
+
* typeof validatedTitle
|
|
178
193
|
* >();
|
|
179
|
-
*
|
|
194
|
+
* assertOk(validatedTitle, "Buy milk");
|
|
180
195
|
* ```
|
|
181
196
|
*
|
|
182
197
|
* Evolu includes dozens of predefined Types and Type factories. Use Types such
|
|
@@ -203,6 +218,10 @@
|
|
|
203
218
|
* cannot express. They are correctness checks, not defenses against malicious
|
|
204
219
|
* code.
|
|
205
220
|
*
|
|
221
|
+
* Evolu does not support subclassing native JavaScript objects. Such subclasses
|
|
222
|
+
* can be classified as their reported built-in representation, but their
|
|
223
|
+
* behavior is unspecified.
|
|
224
|
+
*
|
|
206
225
|
* ## FAQ
|
|
207
226
|
*
|
|
208
227
|
* ### What does a Type represent?
|
|
@@ -378,16 +397,19 @@
|
|
|
378
397
|
* unknown values, {@link object} defines a closed plain-object representation,
|
|
379
398
|
* and {@link record} defines a plain-dictionary representation whose complete
|
|
380
399
|
* set of own properties are its entries. Their realm-neutral plain-object rule
|
|
381
|
-
* accepts a `null` prototype or an immediate root
|
|
382
|
-
*
|
|
383
|
-
*
|
|
384
|
-
*
|
|
385
|
-
*
|
|
386
|
-
*
|
|
387
|
-
*
|
|
388
|
-
*
|
|
389
|
-
*
|
|
390
|
-
*
|
|
400
|
+
* uses {@link isPlainObject}: it accepts a `null` prototype or an immediate root
|
|
401
|
+
* prototype with own `hasOwnProperty` and `isPrototypeOf` properties. A custom
|
|
402
|
+
* root prototype with the same shape can therefore be classified as plain;
|
|
403
|
+
* other custom prototypes and class instances are rejected. This heuristic
|
|
404
|
+
* assumes trusted JavaScript and is not a security boundary. Every property
|
|
405
|
+
* must be an enumerable data property; inherited members are not entries, while
|
|
406
|
+
* accessors and hidden properties are invalid instead of being invoked or
|
|
407
|
+
* ignored. {@link array} similarly defines a dense sequence whose only own
|
|
408
|
+
* properties are `length` and its indexed data properties; {@link tuple} applies
|
|
409
|
+
* the same representation rules with a fixed length and a distinct Type for
|
|
410
|
+
* each position. Only an explicit {@link transform} changes the representation.
|
|
411
|
+
* Consequently, `is` tests exact Output membership and `to` stays total for
|
|
412
|
+
* valid Outputs.
|
|
391
413
|
*
|
|
392
414
|
* ### Why is JsonValue stricter than JSON.stringify?
|
|
393
415
|
*
|
|
@@ -434,7 +456,7 @@ import { wordlist } from "@scure/bip39/wordlists/english.js";
|
|
|
434
456
|
import { createMutableArray, } from "./Array.js";
|
|
435
457
|
import { assert, assertNonNullable } from "./Assert.js";
|
|
436
458
|
import { identity } from "./Function.js";
|
|
437
|
-
import { createMutableRecord } from "./Object.js";
|
|
459
|
+
import { createMutableRecord, getObjectKind, isPlainObject } from "./Object.js";
|
|
438
460
|
import { hasNodeBuffer } from "./Platform.js";
|
|
439
461
|
import { err, flatMapResult, getOk, getOrNull, getOrThrow, ok, trySync, } from "./Result.js";
|
|
440
462
|
import { safelyStringifyUnknownValue } from "./String.js";
|
|
@@ -471,33 +493,9 @@ const createCollectionRuntimeTypeIssues = (name, issuesKind, defaultFormatter, g
|
|
|
471
493
|
});
|
|
472
494
|
};
|
|
473
495
|
const formatDefaultRuntimeTypeIssue = (issue) => issue.formatError(issue.error);
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
* Use this for internal invariants, not external input. Validate external input
|
|
478
|
-
* with `Type.fromUnknown` so validation failures remain typed values. A failed
|
|
479
|
-
* assertion uses the Type name for its message and preserves the exact Output
|
|
480
|
-
* validation error as the thrown Error's cause.
|
|
481
|
-
*
|
|
482
|
-
* ### Example
|
|
483
|
-
*
|
|
484
|
-
* ```ts
|
|
485
|
-
* import {
|
|
486
|
-
* NonEmptyTrimmedString100,
|
|
487
|
-
* assertType,
|
|
488
|
-
* type Brand,
|
|
489
|
-
* } from "@evolu/common";
|
|
490
|
-
*
|
|
491
|
-
* const value: unknown = "Evolu";
|
|
492
|
-
* assertType(NonEmptyTrimmedString100, value);
|
|
493
|
-
* expectTypeOf(value).toEqualTypeOf<
|
|
494
|
-
* string & Brand<"Trimmed"> & Brand<"MinLength1"> & Brand<"MaxLength100">
|
|
495
|
-
* >();
|
|
496
|
-
* ```
|
|
497
|
-
*
|
|
498
|
-
* @group Core
|
|
499
|
-
*/
|
|
500
|
-
export const assertType = (type, value) => {
|
|
496
|
+
export function assertType(type, value) {
|
|
497
|
+
if (type === undefined)
|
|
498
|
+
return;
|
|
501
499
|
// TODO: Make assert prepend "Expected " and accept an optional third cause
|
|
502
500
|
// argument. Then use it here and migrate every other assertion to pass an
|
|
503
501
|
// expectation fragment.
|
|
@@ -506,7 +504,7 @@ export const assertType = (type, value) => {
|
|
|
506
504
|
if (!result.ok) {
|
|
507
505
|
throw new Error(`Expected ${runtimeType.name}.`, { cause: result.error });
|
|
508
506
|
}
|
|
509
|
-
}
|
|
507
|
+
}
|
|
510
508
|
const assertTypeOutput = (name, is, validateOutput, value, options = firstValidationOptions) => {
|
|
511
509
|
if (is(value))
|
|
512
510
|
return;
|
|
@@ -539,13 +537,22 @@ const assertTypeOutput = (name, is, validateOutput, value, options = firstValida
|
|
|
539
537
|
* every locale an app supports also allows language changes without a network
|
|
540
538
|
* connection.
|
|
541
539
|
*
|
|
542
|
-
* The selected Type map, locale map, and formatter maps must
|
|
543
|
-
*
|
|
540
|
+
* The selected Type map, locale map, and formatter maps must satisfy the
|
|
541
|
+
* realm-neutral structural heuristic described by {@link isPlainObject} and have
|
|
542
|
+
* own enumerable string-keyed data properties.
|
|
544
543
|
*
|
|
545
544
|
* ### Example
|
|
546
545
|
*
|
|
547
546
|
* ```ts
|
|
548
|
-
* import {
|
|
547
|
+
* import {
|
|
548
|
+
* assertEqual,
|
|
549
|
+
* Data,
|
|
550
|
+
* assertErr,
|
|
551
|
+
* assertType,
|
|
552
|
+
* String,
|
|
553
|
+
* localizeTypes,
|
|
554
|
+
* minLength,
|
|
555
|
+
* } from "@evolu/common";
|
|
549
556
|
* import { cs } from "@evolu/common/intl";
|
|
550
557
|
*
|
|
551
558
|
* const Label = minLength(1)(String);
|
|
@@ -560,13 +567,14 @@ const assertTypeOutput = (name, is, validateOutput, value, options = firstValida
|
|
|
560
567
|
* },
|
|
561
568
|
* );
|
|
562
569
|
*
|
|
563
|
-
*
|
|
564
|
-
* typeof Label
|
|
565
|
-
* >();
|
|
570
|
+
* assertType<typeof Label, typeof typesByLocale.cs.Label>();
|
|
566
571
|
*
|
|
567
572
|
* const result = typesByLocale.cs.Label.fromUnknown("");
|
|
568
|
-
*
|
|
569
|
-
*
|
|
573
|
+
* assertErr(result);
|
|
574
|
+
* assertType(Data, result.error);
|
|
575
|
+
* assertEqual(result.error, { type: "MinLength1", min: 1, value: "" });
|
|
576
|
+
* assertEqual(
|
|
577
|
+
* typesByLocale.cs.Label.formatError(result.error),
|
|
570
578
|
* "Text nesmí být prázdný.",
|
|
571
579
|
* );
|
|
572
580
|
* ```
|
|
@@ -648,9 +656,7 @@ export const localizeTypes = ((typesByName, formatErrorByTypeByLocale) => {
|
|
|
648
656
|
});
|
|
649
657
|
const getLocalizationMapKeys = (value) => {
|
|
650
658
|
const errorMessage = "localizeTypes maps must be plain objects with own enumerable string-keyed data properties.";
|
|
651
|
-
assert(value
|
|
652
|
-
const prototype = globalThis.Object.getPrototypeOf(value);
|
|
653
|
-
assert(prototype === null || globalThis.Object.getPrototypeOf(prototype) === null, errorMessage);
|
|
659
|
+
assert(isPlainObject(value), errorMessage);
|
|
654
660
|
const keys = Reflect.ownKeys(value);
|
|
655
661
|
for (const key of keys) {
|
|
656
662
|
const descriptor = globalThis.Object.getOwnPropertyDescriptor(value, key);
|
|
@@ -700,12 +706,11 @@ const localizeTypeReflection = (value, formatIssue, localizedTypeBySource) => {
|
|
|
700
706
|
value["~evolu/instance"] === "Type") {
|
|
701
707
|
return withFormatError(value, formatIssue, localizedTypeBySource);
|
|
702
708
|
}
|
|
703
|
-
if (
|
|
709
|
+
if (Array.isArray(value)) {
|
|
704
710
|
return value.map((value) => localizeTypeReflection(value, formatIssue, localizedTypeBySource));
|
|
705
711
|
}
|
|
706
|
-
if (
|
|
712
|
+
if (!isPlainObject(value))
|
|
707
713
|
return value;
|
|
708
|
-
}
|
|
709
714
|
const localized = globalThis.Object.create(globalThis.Object.getPrototypeOf(value));
|
|
710
715
|
for (const key of Reflect.ownKeys(value)) {
|
|
711
716
|
localized[key] = localizeTypeReflection(value[key], formatIssue, localizedTypeBySource);
|
|
@@ -946,16 +951,20 @@ const createTypeOfType = (name) => {
|
|
|
946
951
|
* and significant whitespace:
|
|
947
952
|
*
|
|
948
953
|
* ```ts
|
|
949
|
-
* import {
|
|
954
|
+
* import {
|
|
955
|
+
* assertOk,
|
|
956
|
+
* assertType,
|
|
957
|
+
* String,
|
|
958
|
+
* maxLength,
|
|
959
|
+
* type Brand,
|
|
960
|
+
* } from "@evolu/common";
|
|
950
961
|
*
|
|
951
962
|
* const WireValue100 = maxLength(100)(String);
|
|
952
963
|
* type WireValue100 = typeof WireValue100.Output;
|
|
953
964
|
*
|
|
954
|
-
*
|
|
955
|
-
*
|
|
956
|
-
*
|
|
957
|
-
* expectOk(WireValue100.fromUnknown(""), "");
|
|
958
|
-
* expectOk(WireValue100.fromUnknown(" value "), " value ");
|
|
965
|
+
* assertType<string & Brand<"MaxLength100">, WireValue100>();
|
|
966
|
+
* assertOk(WireValue100.fromUnknown(""), "");
|
|
967
|
+
* assertOk(WireValue100.fromUnknown(" value "), " value ");
|
|
959
968
|
* ```
|
|
960
969
|
*
|
|
961
970
|
* @group String
|
|
@@ -981,7 +990,12 @@ export const String = /*#__PURE__*/ createTypeOfType("String");
|
|
|
981
990
|
*
|
|
982
991
|
* ```ts
|
|
983
992
|
* import {
|
|
993
|
+
* assertEqual,
|
|
994
|
+
* assertErr,
|
|
995
|
+
* assertOk,
|
|
996
|
+
* assertType,
|
|
984
997
|
* Age,
|
|
998
|
+
* Data,
|
|
985
999
|
* FiniteNumber,
|
|
986
1000
|
* Int,
|
|
987
1001
|
* NonNaNNumber,
|
|
@@ -991,36 +1005,41 @@ export const String = /*#__PURE__*/ createTypeOfType("String");
|
|
|
991
1005
|
* } from "@evolu/common";
|
|
992
1006
|
*
|
|
993
1007
|
* // Note how every additional constraint accumulates its Brand.
|
|
994
|
-
*
|
|
995
|
-
*
|
|
996
|
-
*
|
|
997
|
-
* >
|
|
998
|
-
*
|
|
999
|
-
* number & Brand<"NonNaN"> & Brand<"Finite">
|
|
1008
|
+
* assertType<number, typeof Number.Output>();
|
|
1009
|
+
* assertType<number & Brand<"NonNaN">, typeof NonNaNNumber.Output>();
|
|
1010
|
+
* assertType<
|
|
1011
|
+
* number & Brand<"NonNaN"> & Brand<"Finite">,
|
|
1012
|
+
* typeof FiniteNumber.Output
|
|
1000
1013
|
* >();
|
|
1001
|
-
*
|
|
1002
|
-
* number & Brand<"NonNaN"> & Brand<"Finite"> & Brand<"Int"
|
|
1014
|
+
* assertType<
|
|
1015
|
+
* number & Brand<"NonNaN"> & Brand<"Finite"> & Brand<"Int">,
|
|
1016
|
+
* typeof Int.Output
|
|
1003
1017
|
* >();
|
|
1004
|
-
*
|
|
1018
|
+
* assertType<
|
|
1005
1019
|
* number &
|
|
1006
1020
|
* Brand<"NonNaN"> &
|
|
1007
1021
|
* Brand<"Finite"> &
|
|
1008
1022
|
* Brand<"Int"> &
|
|
1009
|
-
* Brand<"NonNegative"
|
|
1023
|
+
* Brand<"NonNegative">,
|
|
1024
|
+
* typeof NonNegativeInt.Output
|
|
1010
1025
|
* >();
|
|
1011
1026
|
*
|
|
1012
|
-
*
|
|
1027
|
+
* assertType<
|
|
1013
1028
|
* number &
|
|
1014
1029
|
* Brand<"NonNaN"> &
|
|
1015
1030
|
* Brand<"Finite"> &
|
|
1016
1031
|
* Brand<"Int"> &
|
|
1017
1032
|
* Brand<"NonNegative"> &
|
|
1018
1033
|
* Brand<"LessThan200"> &
|
|
1019
|
-
* Brand<"Age"
|
|
1034
|
+
* Brand<"Age">,
|
|
1035
|
+
* typeof Age.Output
|
|
1020
1036
|
* >();
|
|
1021
1037
|
*
|
|
1022
|
-
*
|
|
1023
|
-
*
|
|
1038
|
+
* assertOk(Age.fromUnknown(122), 122);
|
|
1039
|
+
* const invalid = Age.fromUnknown(200);
|
|
1040
|
+
* assertErr(invalid);
|
|
1041
|
+
* assertType(Data, invalid.error);
|
|
1042
|
+
* assertEqual(invalid.error, {
|
|
1024
1043
|
* type: "LessThan200",
|
|
1025
1044
|
* value: 200,
|
|
1026
1045
|
* max: 200,
|
|
@@ -1117,7 +1136,7 @@ export const ArrayBuffer = /*#__PURE__*/ objectTag("ArrayBuffer");
|
|
|
1117
1136
|
* ### Example
|
|
1118
1137
|
*
|
|
1119
1138
|
* ```ts
|
|
1120
|
-
* import { instanceOf } from "@evolu/common";
|
|
1139
|
+
* import { assertFalse, assertTrue, instanceOf } from "@evolu/common";
|
|
1121
1140
|
*
|
|
1122
1141
|
* class User {
|
|
1123
1142
|
* readonly name: string;
|
|
@@ -1129,8 +1148,8 @@ export const ArrayBuffer = /*#__PURE__*/ objectTag("ArrayBuffer");
|
|
|
1129
1148
|
*
|
|
1130
1149
|
* const UserInstance = instanceOf(User);
|
|
1131
1150
|
*
|
|
1132
|
-
*
|
|
1133
|
-
*
|
|
1151
|
+
* assertTrue(UserInstance.is(new User("Ada")));
|
|
1152
|
+
* assertFalse(UserInstance.is({ name: "Ada" }));
|
|
1134
1153
|
* ```
|
|
1135
1154
|
*
|
|
1136
1155
|
* @group Base
|
|
@@ -1157,13 +1176,23 @@ export const instanceOf = (constructor) => {
|
|
|
1157
1176
|
* ### Example
|
|
1158
1177
|
*
|
|
1159
1178
|
* ```ts
|
|
1160
|
-
* import {
|
|
1179
|
+
* import {
|
|
1180
|
+
* assertEqual,
|
|
1181
|
+
* assertErr,
|
|
1182
|
+
* assertOk,
|
|
1183
|
+
* assertType,
|
|
1184
|
+
* Data,
|
|
1185
|
+
* literal,
|
|
1186
|
+
* } from "@evolu/common";
|
|
1161
1187
|
*
|
|
1162
1188
|
* const Ready = literal("ready");
|
|
1163
1189
|
*
|
|
1164
|
-
*
|
|
1165
|
-
*
|
|
1166
|
-
*
|
|
1190
|
+
* assertType<"ready", typeof Ready.Output>();
|
|
1191
|
+
* assertOk(Ready.fromUnknown("ready"), "ready");
|
|
1192
|
+
* const invalid = Ready.fromUnknown("pending");
|
|
1193
|
+
* assertErr(invalid);
|
|
1194
|
+
* assertType(Data, invalid.error);
|
|
1195
|
+
* assertEqual(invalid.error, {
|
|
1167
1196
|
* type: "Literal",
|
|
1168
1197
|
* expected: "ready",
|
|
1169
1198
|
* value: "pending",
|
|
@@ -1258,11 +1287,11 @@ const createUnionValidation = (members, validateMember) => (value, options = fir
|
|
|
1258
1287
|
* ### Example
|
|
1259
1288
|
*
|
|
1260
1289
|
* ```ts
|
|
1261
|
-
* import { String, undefinedOr } from "@evolu/common";
|
|
1290
|
+
* import { assertOk, String, undefinedOr } from "@evolu/common";
|
|
1262
1291
|
*
|
|
1263
1292
|
* const StringOrUndefined = undefinedOr(String);
|
|
1264
1293
|
*
|
|
1265
|
-
*
|
|
1294
|
+
* assertOk(StringOrUndefined.fromUnknown(undefined), undefined);
|
|
1266
1295
|
* ```
|
|
1267
1296
|
*
|
|
1268
1297
|
* @group Unions
|
|
@@ -1274,11 +1303,11 @@ export const undefinedOr = (type) => union(type, Undefined);
|
|
|
1274
1303
|
* ### Example
|
|
1275
1304
|
*
|
|
1276
1305
|
* ```ts
|
|
1277
|
-
* import { String, nullOr } from "@evolu/common";
|
|
1306
|
+
* import { assertOk, String, nullOr } from "@evolu/common";
|
|
1278
1307
|
*
|
|
1279
1308
|
* const NullableString = nullOr(String);
|
|
1280
1309
|
*
|
|
1281
|
-
*
|
|
1310
|
+
* assertOk(NullableString.fromUnknown(null), null);
|
|
1282
1311
|
* ```
|
|
1283
1312
|
*
|
|
1284
1313
|
* @group Unions
|
|
@@ -1290,20 +1319,18 @@ export const nullOr = (type) => union(type, Null);
|
|
|
1290
1319
|
* ### Example
|
|
1291
1320
|
*
|
|
1292
1321
|
* ```ts
|
|
1293
|
-
* import { String, nullishOr } from "@evolu/common";
|
|
1322
|
+
* import { assertOk, String, nullishOr } from "@evolu/common";
|
|
1294
1323
|
*
|
|
1295
1324
|
* const NullishString = nullishOr(String);
|
|
1296
1325
|
*
|
|
1297
|
-
*
|
|
1298
|
-
*
|
|
1326
|
+
* assertOk(NullishString.fromUnknown(undefined), undefined);
|
|
1327
|
+
* assertOk(NullishString.fromUnknown(null), null);
|
|
1299
1328
|
* ```
|
|
1300
1329
|
*
|
|
1301
1330
|
* @group Unions
|
|
1302
1331
|
*/
|
|
1303
1332
|
export const nullishOr = (type) => union(type, Null, Undefined);
|
|
1304
|
-
const isRuntimeUnionTypeNode = (type) => type.name === "Union" &&
|
|
1305
|
-
"members" in type &&
|
|
1306
|
-
globalThis.Array.isArray(type.members);
|
|
1333
|
+
const isRuntimeUnionTypeNode = (type) => type.name === "Union" && "members" in type && Array.isArray(type.members);
|
|
1307
1334
|
/**
|
|
1308
1335
|
* Template literal {@link Type} that parses canonical strings into Tuples.
|
|
1309
1336
|
*
|
|
@@ -1339,7 +1366,16 @@ const isRuntimeUnionTypeNode = (type) => type.name === "Union" &&
|
|
|
1339
1366
|
* the structured data decoded from it:
|
|
1340
1367
|
*
|
|
1341
1368
|
* ```ts
|
|
1342
|
-
* import {
|
|
1369
|
+
* import {
|
|
1370
|
+
* assertFalse,
|
|
1371
|
+
* assertEqual,
|
|
1372
|
+
* assertErr,
|
|
1373
|
+
* assertOk,
|
|
1374
|
+
* assertType,
|
|
1375
|
+
* Data,
|
|
1376
|
+
* templateLiteralParser,
|
|
1377
|
+
* union,
|
|
1378
|
+
* } from "@evolu/common";
|
|
1343
1379
|
*
|
|
1344
1380
|
* const Language = union("en", "cs");
|
|
1345
1381
|
* const Region = union("US", "CZ");
|
|
@@ -1349,36 +1385,39 @@ const isRuntimeUnionTypeNode = (type) => type.name === "Union" &&
|
|
|
1349
1385
|
*
|
|
1350
1386
|
* // Output is the decoded language and region.
|
|
1351
1387
|
* type SupportedLocale = typeof SupportedLocale.Output;
|
|
1352
|
-
*
|
|
1353
|
-
* readonly ["en" | "cs", "US" | "CZ"]
|
|
1354
|
-
* >();
|
|
1388
|
+
* assertType<readonly ["en" | "cs", "US" | "CZ"], SupportedLocale>();
|
|
1355
1389
|
*
|
|
1356
1390
|
* // The parent Output is the canonical locale string.
|
|
1357
1391
|
* type SupportedLocaleLiteral = typeof SupportedLocale.parent.Output;
|
|
1358
|
-
*
|
|
1359
|
-
* "en-US" | "en-CZ" | "cs-US" | "cs-CZ"
|
|
1392
|
+
* assertType<
|
|
1393
|
+
* "en-US" | "en-CZ" | "cs-US" | "cs-CZ",
|
|
1394
|
+
* SupportedLocaleLiteral
|
|
1360
1395
|
* >();
|
|
1361
1396
|
*
|
|
1362
1397
|
* // Parse an unknown string into structured data.
|
|
1363
1398
|
* const result = SupportedLocale.fromUnknown("cs-CZ");
|
|
1364
|
-
*
|
|
1399
|
+
* assertOk(result, ["cs", "CZ"]);
|
|
1365
1400
|
* const locale = result.value;
|
|
1366
|
-
*
|
|
1367
|
-
*
|
|
1401
|
+
* assertType<SupportedLocale, typeof locale>();
|
|
1402
|
+
* const invalid = SupportedLocale.fromUnknown("cs/CZ");
|
|
1403
|
+
* assertErr(invalid);
|
|
1404
|
+
* assertType(Data, invalid.error);
|
|
1405
|
+
* const error: Data = invalid.error;
|
|
1406
|
+
* assertEqual(error, {
|
|
1368
1407
|
* type: "TemplateLiteral",
|
|
1369
1408
|
* value: "cs/CZ",
|
|
1370
1409
|
* });
|
|
1371
1410
|
*
|
|
1372
1411
|
* // Encode structured data into its canonical string.
|
|
1373
1412
|
* const localeLiteral = SupportedLocale.to(locale);
|
|
1374
|
-
*
|
|
1375
|
-
*
|
|
1413
|
+
* assertType<SupportedLocaleLiteral, typeof localeLiteral>();
|
|
1414
|
+
* assertEqual(localeLiteral, "cs-CZ");
|
|
1376
1415
|
*
|
|
1377
1416
|
* // Validate a string configuration value.
|
|
1378
1417
|
* const configValue: unknown = "cs-CZ";
|
|
1379
|
-
*
|
|
1380
|
-
*
|
|
1381
|
-
*
|
|
1418
|
+
* assertType(SupportedLocale.parent, configValue);
|
|
1419
|
+
* assertType<SupportedLocaleLiteral, typeof configValue>();
|
|
1420
|
+
* assertFalse(SupportedLocale.parent.is("fr-CZ"));
|
|
1382
1421
|
* ```
|
|
1383
1422
|
*
|
|
1384
1423
|
* `SupportedLocale` is structured data for application code.
|
|
@@ -1391,6 +1430,8 @@ const isRuntimeUnionTypeNode = (type) => type.name === "Union" &&
|
|
|
1391
1430
|
*
|
|
1392
1431
|
* ```ts
|
|
1393
1432
|
* import {
|
|
1433
|
+
* assertEqual,
|
|
1434
|
+
* assertOk,
|
|
1394
1435
|
* NonNegativeDecimalString,
|
|
1395
1436
|
* templateLiteralParser,
|
|
1396
1437
|
* } from "@evolu/common";
|
|
@@ -1403,8 +1444,8 @@ const isRuntimeUnionTypeNode = (type) => type.name === "Union" &&
|
|
|
1403
1444
|
* // DecimalText.to requires a validated NonNegativeDecimalString.
|
|
1404
1445
|
* const zero = NonNegativeDecimalString.orThrow("0");
|
|
1405
1446
|
*
|
|
1406
|
-
*
|
|
1407
|
-
*
|
|
1447
|
+
* assertOk(DecimalText.fromUnknown("decimal:0"), [zero]);
|
|
1448
|
+
* assertEqual(DecimalText.to([zero]), "decimal:0");
|
|
1408
1449
|
* ```
|
|
1409
1450
|
*
|
|
1410
1451
|
* Capture Types (the Type arguments passed to `templateLiteralParser`) can use
|
|
@@ -1412,6 +1453,9 @@ const isRuntimeUnionTypeNode = (type) => type.name === "Union" &&
|
|
|
1412
1453
|
*
|
|
1413
1454
|
* ```ts
|
|
1414
1455
|
* import {
|
|
1456
|
+
* assertEqual,
|
|
1457
|
+
* assertOk,
|
|
1458
|
+
* assertType,
|
|
1415
1459
|
* Int64FromInt64String,
|
|
1416
1460
|
* templateLiteralParser,
|
|
1417
1461
|
* } from "@evolu/common";
|
|
@@ -1422,24 +1466,29 @@ const isRuntimeUnionTypeNode = (type) => type.name === "Union" &&
|
|
|
1422
1466
|
*
|
|
1423
1467
|
* // Decode the string into structured data.
|
|
1424
1468
|
* const result = ItemId.fromUnknown("item-42");
|
|
1425
|
-
*
|
|
1469
|
+
* assertOk(result, [42n]);
|
|
1426
1470
|
* const itemId = result.value;
|
|
1427
|
-
*
|
|
1471
|
+
* assertType<ItemId, typeof itemId>();
|
|
1428
1472
|
*
|
|
1429
1473
|
* // Encode the structured data into its canonical string.
|
|
1430
1474
|
* const itemIdLiteral = ItemId.to(itemId);
|
|
1431
|
-
*
|
|
1432
|
-
*
|
|
1475
|
+
* assertType<ItemIdLiteral, typeof itemIdLiteral>();
|
|
1476
|
+
* assertEqual(itemIdLiteral, "item-42");
|
|
1433
1477
|
*
|
|
1434
1478
|
* // TypeScript cannot prove from the literal alone that "42" is a valid Int64 encoding.
|
|
1435
1479
|
* // @ts-expect-error Validate it with ItemId.parent or create it with ItemId.to.
|
|
1436
|
-
* const
|
|
1480
|
+
* const _invalidItemIdLiteral: ItemIdLiteral = "item-42";
|
|
1437
1481
|
* ```
|
|
1438
1482
|
*
|
|
1439
1483
|
* Fixed-width captures can be adjacent:
|
|
1440
1484
|
*
|
|
1441
1485
|
* ```ts
|
|
1442
|
-
* import {
|
|
1486
|
+
* import {
|
|
1487
|
+
* assertEqual,
|
|
1488
|
+
* assertOk,
|
|
1489
|
+
* templateLiteralParser,
|
|
1490
|
+
* union,
|
|
1491
|
+
* } from "@evolu/common";
|
|
1443
1492
|
*
|
|
1444
1493
|
* const Digit = union("0", "1", "2", "3", "4", "5", "6", "7", "8", "9");
|
|
1445
1494
|
* const TwoDigits = templateLiteralParser(Digit, Digit);
|
|
@@ -1449,10 +1498,10 @@ const isRuntimeUnionTypeNode = (type) => type.name === "Union" &&
|
|
|
1449
1498
|
* const twoDigits: TwoDigits = ["4", "2"];
|
|
1450
1499
|
* const twoDigitsLiteral: TwoDigitsLiteral = "42";
|
|
1451
1500
|
* // @ts-expect-error TwoDigitsLiteral requires exactly two digits.
|
|
1452
|
-
* const
|
|
1501
|
+
* const _threeDigitsLiteral: TwoDigitsLiteral = "123";
|
|
1453
1502
|
*
|
|
1454
|
-
*
|
|
1455
|
-
*
|
|
1503
|
+
* assertOk(TwoDigits.from.parent(twoDigitsLiteral), twoDigits);
|
|
1504
|
+
* assertEqual(TwoDigits.to(twoDigits), twoDigitsLiteral);
|
|
1456
1505
|
* ```
|
|
1457
1506
|
*
|
|
1458
1507
|
* TypeScript rejects multiple variable-width captures because their encoded
|
|
@@ -1583,17 +1632,24 @@ const createTemplateLiteralParserType = (templateParts) => {
|
|
|
1583
1632
|
* ### Example
|
|
1584
1633
|
*
|
|
1585
1634
|
* ```ts
|
|
1586
|
-
* import {
|
|
1635
|
+
* import {
|
|
1636
|
+
* assertFalse,
|
|
1637
|
+
* assertOk,
|
|
1638
|
+
* assertType,
|
|
1639
|
+
* templateLiteral,
|
|
1640
|
+
* union,
|
|
1641
|
+
* } from "@evolu/common";
|
|
1587
1642
|
*
|
|
1588
1643
|
* const Language = union("en", "cs");
|
|
1589
1644
|
* const Region = union("US", "CZ");
|
|
1590
1645
|
* const Locale = templateLiteral(Language, "-", Region);
|
|
1591
1646
|
*
|
|
1592
|
-
*
|
|
1593
|
-
* "en-US" | "en-CZ" | "cs-US" | "cs-CZ"
|
|
1647
|
+
* assertType<
|
|
1648
|
+
* "en-US" | "en-CZ" | "cs-US" | "cs-CZ",
|
|
1649
|
+
* typeof Locale.Output
|
|
1594
1650
|
* >();
|
|
1595
|
-
*
|
|
1596
|
-
*
|
|
1651
|
+
* assertOk(Locale.fromUnknown("cs-CZ"), "cs-CZ");
|
|
1652
|
+
* assertFalse(Locale.is("fr-CZ"));
|
|
1597
1653
|
* ```
|
|
1598
1654
|
*
|
|
1599
1655
|
* @group Template literals
|
|
@@ -1609,7 +1665,7 @@ const compileTemplateLiteralParser = (parts) => {
|
|
|
1609
1665
|
return [part, partFraming.width];
|
|
1610
1666
|
});
|
|
1611
1667
|
return (input) => {
|
|
1612
|
-
const inputCodePoints =
|
|
1668
|
+
const inputCodePoints = Array.from(input);
|
|
1613
1669
|
const variableWidth = inputCodePoints.length - fixedPartsWidth;
|
|
1614
1670
|
if (variableWidth < 0) {
|
|
1615
1671
|
return err({ type: "TemplateLiteral", value: input });
|
|
@@ -1665,7 +1721,7 @@ const getStringTemplateLiteralFraming = (value) => {
|
|
|
1665
1721
|
const firstCodeUnit = value.charCodeAt(0);
|
|
1666
1722
|
const lastCodeUnit = value.charCodeAt(value.length - 1);
|
|
1667
1723
|
return {
|
|
1668
|
-
width:
|
|
1724
|
+
width: Array.from(value).length,
|
|
1669
1725
|
canBeEmpty: value.length === 0,
|
|
1670
1726
|
canStartWithLowSurrogate: firstCodeUnit >= 0xdc00 && firstCodeUnit <= 0xdfff,
|
|
1671
1727
|
canEndWithHighSurrogate: lastCodeUnit >= 0xd800 && lastCodeUnit <= 0xdbff,
|
|
@@ -1719,13 +1775,21 @@ export function brand(name, parent, validate, formatError) {
|
|
|
1719
1775
|
* ### Example
|
|
1720
1776
|
*
|
|
1721
1777
|
* ```ts
|
|
1722
|
-
* import {
|
|
1778
|
+
* import {
|
|
1779
|
+
* assertEqual,
|
|
1780
|
+
* assertErr,
|
|
1781
|
+
* assertOk,
|
|
1782
|
+
* assertType,
|
|
1783
|
+
* Data,
|
|
1784
|
+
* DateIso,
|
|
1785
|
+
* } from "@evolu/common";
|
|
1723
1786
|
*
|
|
1724
|
-
*
|
|
1725
|
-
*
|
|
1726
|
-
*
|
|
1727
|
-
* );
|
|
1728
|
-
*
|
|
1787
|
+
* const value = "2023-01-01T12:00:00.000Z";
|
|
1788
|
+
* assertOk(DateIso.fromUnknown(value), value);
|
|
1789
|
+
* const invalid = DateIso.fromUnknown("2023-01-01");
|
|
1790
|
+
* assertErr(invalid);
|
|
1791
|
+
* assertType(Data, invalid.error);
|
|
1792
|
+
* assertEqual(invalid.error, {
|
|
1729
1793
|
* type: "DateIso",
|
|
1730
1794
|
* value: "2023-01-01",
|
|
1731
1795
|
* });
|
|
@@ -1742,13 +1806,13 @@ export const DateIso = /*#__PURE__*/ brand("DateIso", String, (value) => value.l
|
|
|
1742
1806
|
* ### Example
|
|
1743
1807
|
*
|
|
1744
1808
|
* ```ts
|
|
1745
|
-
* import { DateIsoFromDate } from "@evolu/common";
|
|
1809
|
+
* import { assertEqual, assertOk, DateIsoFromDate } from "@evolu/common";
|
|
1746
1810
|
*
|
|
1747
|
-
* const date = new
|
|
1811
|
+
* const date = new Date("2025-01-01T12:00:00.000Z");
|
|
1748
1812
|
* const result = DateIsoFromDate.fromUnknown(date);
|
|
1749
1813
|
*
|
|
1750
|
-
*
|
|
1751
|
-
*
|
|
1814
|
+
* assertOk(result, "2025-01-01T12:00:00.000Z");
|
|
1815
|
+
* assertEqual(DateIsoFromDate.to(result.value), date);
|
|
1752
1816
|
* ```
|
|
1753
1817
|
*
|
|
1754
1818
|
* @group String
|
|
@@ -1781,17 +1845,27 @@ export const UInt64 = /*#__PURE__*/ brand("UInt64", BigInt, (value) => globalThi
|
|
|
1781
1845
|
* ### Example
|
|
1782
1846
|
*
|
|
1783
1847
|
* ```ts
|
|
1784
|
-
* import {
|
|
1848
|
+
* import {
|
|
1849
|
+
* assertEqual,
|
|
1850
|
+
* assertErr,
|
|
1851
|
+
* assertOk,
|
|
1852
|
+
* assertType,
|
|
1853
|
+
* Data,
|
|
1854
|
+
* String,
|
|
1855
|
+
* capitalized,
|
|
1856
|
+
* type Brand,
|
|
1857
|
+
* } from "@evolu/common";
|
|
1785
1858
|
*
|
|
1786
1859
|
* const CapitalizedString = capitalized(String);
|
|
1787
1860
|
* type CapitalizedString = typeof CapitalizedString.Output;
|
|
1788
1861
|
*
|
|
1789
|
-
*
|
|
1790
|
-
* string & Brand<"Capitalized">
|
|
1791
|
-
* >();
|
|
1862
|
+
* assertType<string & Brand<"Capitalized">, CapitalizedString>();
|
|
1792
1863
|
*
|
|
1793
|
-
*
|
|
1794
|
-
*
|
|
1864
|
+
* assertOk(CapitalizedString.fromUnknown("Evolu"), "Evolu");
|
|
1865
|
+
* const invalid = CapitalizedString.fromUnknown("evolu");
|
|
1866
|
+
* assertErr(invalid);
|
|
1867
|
+
* assertType(Data, invalid.error);
|
|
1868
|
+
* assertEqual(invalid.error, {
|
|
1795
1869
|
* type: "Capitalized",
|
|
1796
1870
|
* value: "evolu",
|
|
1797
1871
|
* });
|
|
@@ -1817,11 +1891,11 @@ export const CapitalizedString = /*#__PURE__*/ capitalized(String);
|
|
|
1817
1891
|
* ### Example
|
|
1818
1892
|
*
|
|
1819
1893
|
* ```ts
|
|
1820
|
-
* import { String, trimmed } from "@evolu/common";
|
|
1894
|
+
* import { assertOk, String, trimmed } from "@evolu/common";
|
|
1821
1895
|
*
|
|
1822
1896
|
* const Trimmed = trimmed(String);
|
|
1823
1897
|
*
|
|
1824
|
-
*
|
|
1898
|
+
* assertOk(Trimmed.fromUnknown("Evolu"), "Evolu");
|
|
1825
1899
|
* ```
|
|
1826
1900
|
*
|
|
1827
1901
|
* @group String
|
|
@@ -1847,9 +1921,9 @@ export const TrimmedString = /*#__PURE__*/ trimmed(String);
|
|
|
1847
1921
|
* ### Example
|
|
1848
1922
|
*
|
|
1849
1923
|
* ```ts
|
|
1850
|
-
* import { trim } from "@evolu/common";
|
|
1924
|
+
* import { assertEqual, trim } from "@evolu/common";
|
|
1851
1925
|
*
|
|
1852
|
-
*
|
|
1926
|
+
* assertEqual(trim(" Evolu "), "Evolu");
|
|
1853
1927
|
* ```
|
|
1854
1928
|
*
|
|
1855
1929
|
* @group String
|
|
@@ -1861,13 +1935,13 @@ export const trim = (value) => value.trim();
|
|
|
1861
1935
|
* ### Example
|
|
1862
1936
|
*
|
|
1863
1937
|
* ```ts
|
|
1864
|
-
* import { String, array, minLength } from "@evolu/common";
|
|
1938
|
+
* import { assertOk, String, array, minLength } from "@evolu/common";
|
|
1865
1939
|
*
|
|
1866
1940
|
* const AtLeastThreeCharacters = minLength(3)(String);
|
|
1867
1941
|
* const AtLeastTwoItems = minLength(2)(array(String));
|
|
1868
1942
|
*
|
|
1869
|
-
*
|
|
1870
|
-
*
|
|
1943
|
+
* assertOk(AtLeastThreeCharacters.fromUnknown("abc"), "abc");
|
|
1944
|
+
* assertOk(AtLeastTwoItems.fromUnknown(["a", "b"]), ["a", "b"]);
|
|
1871
1945
|
* ```
|
|
1872
1946
|
*
|
|
1873
1947
|
* @group String
|
|
@@ -1900,13 +1974,13 @@ export const NonEmptyTrimmedString = /*#__PURE__*/ minLength(1)(TrimmedString);
|
|
|
1900
1974
|
* ### Example
|
|
1901
1975
|
*
|
|
1902
1976
|
* ```ts
|
|
1903
|
-
* import { String, array, maxLength } from "@evolu/common";
|
|
1977
|
+
* import { assertOk, String, array, maxLength } from "@evolu/common";
|
|
1904
1978
|
*
|
|
1905
1979
|
* const AtMostThreeCharacters = maxLength(3)(String);
|
|
1906
1980
|
* const AtMostTwoItems = maxLength(2)(array(String));
|
|
1907
1981
|
*
|
|
1908
|
-
*
|
|
1909
|
-
*
|
|
1982
|
+
* assertOk(AtMostThreeCharacters.fromUnknown("abc"), "abc");
|
|
1983
|
+
* assertOk(AtMostTwoItems.fromUnknown(["a", "b"]), ["a", "b"]);
|
|
1910
1984
|
* ```
|
|
1911
1985
|
*
|
|
1912
1986
|
* @group String
|
|
@@ -1936,13 +2010,13 @@ export const NonEmptyTrimmedString1000 = /*#__PURE__*/ maxLength(1000)(NonEmptyT
|
|
|
1936
2010
|
* ### Example
|
|
1937
2011
|
*
|
|
1938
2012
|
* ```ts
|
|
1939
|
-
* import { String, array, length } from "@evolu/common";
|
|
2013
|
+
* import { assertOk, String, array, length } from "@evolu/common";
|
|
1940
2014
|
*
|
|
1941
2015
|
* const ThreeCharacters = length(3)(String);
|
|
1942
2016
|
* const TwoItems = length(2)(array(String));
|
|
1943
2017
|
*
|
|
1944
|
-
*
|
|
1945
|
-
*
|
|
2018
|
+
* assertOk(ThreeCharacters.fromUnknown("abc"), "abc");
|
|
2019
|
+
* assertOk(TwoItems.fromUnknown(["a", "b"]), ["a", "b"]);
|
|
1946
2020
|
* ```
|
|
1947
2021
|
*
|
|
1948
2022
|
* @group String
|
|
@@ -1962,21 +2036,34 @@ export const length = (exact) => (parent) => {
|
|
|
1962
2036
|
* A non-empty string using the URL-safe alphabet:
|
|
1963
2037
|
*
|
|
1964
2038
|
* ```ts
|
|
1965
|
-
* import {
|
|
2039
|
+
* import {
|
|
2040
|
+
* assertEqual,
|
|
2041
|
+
* assertErr,
|
|
2042
|
+
* assertOk,
|
|
2043
|
+
* assertType,
|
|
2044
|
+
* Data,
|
|
2045
|
+
* String,
|
|
2046
|
+
* regex,
|
|
2047
|
+
* type Brand,
|
|
2048
|
+
* } from "@evolu/common";
|
|
1966
2049
|
*
|
|
1967
|
-
* const UrlSafeString = regex(
|
|
2050
|
+
* const UrlSafeString = regex(
|
|
2051
|
+
* "UrlSafeString",
|
|
2052
|
+
* /^[A-Za-z0-9_-]+$/u,
|
|
2053
|
+
* )(String);
|
|
1968
2054
|
* type UrlSafeString = typeof UrlSafeString.Output;
|
|
1969
2055
|
*
|
|
1970
|
-
*
|
|
1971
|
-
* string & Brand<"UrlSafeString">
|
|
1972
|
-
* >();
|
|
2056
|
+
* assertType<string & Brand<"UrlSafeString">, UrlSafeString>();
|
|
1973
2057
|
*
|
|
1974
|
-
*
|
|
1975
|
-
*
|
|
2058
|
+
* assertOk(UrlSafeString.fromUnknown("abc-123_DEF"), "abc-123_DEF");
|
|
2059
|
+
* const invalid = UrlSafeString.fromUnknown("not safe");
|
|
2060
|
+
* assertErr(invalid);
|
|
2061
|
+
* assertType(Data, invalid.error);
|
|
2062
|
+
* assertEqual(invalid.error, {
|
|
1976
2063
|
* type: "UrlSafeString",
|
|
1977
2064
|
* value: "not safe",
|
|
1978
2065
|
* source: "^[A-Za-z0-9_-]+$",
|
|
1979
|
-
* flags: "",
|
|
2066
|
+
* flags: "u",
|
|
1980
2067
|
* });
|
|
1981
2068
|
* ```
|
|
1982
2069
|
*
|
|
@@ -2021,8 +2108,8 @@ const uint8ArrayToBase64UrlString = (bytes) => {
|
|
|
2021
2108
|
if ("toBase64" in uint8ArrayPrototype) {
|
|
2022
2109
|
return bytes.toBase64(base64UrlOptions);
|
|
2023
2110
|
}
|
|
2024
|
-
const binaryString =
|
|
2025
|
-
const base64 =
|
|
2111
|
+
const binaryString = Array.from(bytes, (byte) => globalThis.String.fromCodePoint(byte)).join("");
|
|
2112
|
+
const base64 = btoa(binaryString);
|
|
2026
2113
|
return base64.replaceAll("+", "-").replaceAll("/", "_").replaceAll("=", "");
|
|
2027
2114
|
};
|
|
2028
2115
|
const base64UrlStringToUint8Array = (value) => {
|
|
@@ -2037,7 +2124,7 @@ const base64UrlStringToUint8Array = (value) => {
|
|
|
2037
2124
|
let base64 = value.replaceAll("-", "+").replaceAll("_", "/");
|
|
2038
2125
|
while (base64.length % 4 !== 0)
|
|
2039
2126
|
base64 += "=";
|
|
2040
|
-
const binaryString =
|
|
2127
|
+
const binaryString = atob(base64);
|
|
2041
2128
|
return globalThis.Uint8Array.from(binaryString, (character) => character.charCodeAt(0));
|
|
2042
2129
|
};
|
|
2043
2130
|
/**
|
|
@@ -2060,9 +2147,10 @@ export const Base64Url = /*#__PURE__*/ brand("Base64Url", String, (value) => {
|
|
|
2060
2147
|
* ### Example
|
|
2061
2148
|
*
|
|
2062
2149
|
* ```ts
|
|
2063
|
-
* import { uint8ArrayToBase64Url } from "@evolu/common";
|
|
2150
|
+
* import { assertEqual, uint8ArrayToBase64Url } from "@evolu/common";
|
|
2064
2151
|
*
|
|
2065
|
-
*
|
|
2152
|
+
* assertEqual(
|
|
2153
|
+
* uint8ArrayToBase64Url(new Uint8Array([0, 1, 2, 255])),
|
|
2066
2154
|
* "AAEC_w",
|
|
2067
2155
|
* );
|
|
2068
2156
|
* ```
|
|
@@ -2076,11 +2164,16 @@ export const uint8ArrayToBase64Url = (bytes) => uint8ArrayToBase64UrlString(byte
|
|
|
2076
2164
|
* ### Example
|
|
2077
2165
|
*
|
|
2078
2166
|
* ```ts
|
|
2079
|
-
* import {
|
|
2167
|
+
* import {
|
|
2168
|
+
* assertEqual,
|
|
2169
|
+
* Base64Url,
|
|
2170
|
+
* base64UrlToUint8Array,
|
|
2171
|
+
* } from "@evolu/common";
|
|
2080
2172
|
*
|
|
2081
2173
|
* const value = Base64Url.orThrow("AAEC_w");
|
|
2082
2174
|
*
|
|
2083
|
-
*
|
|
2175
|
+
* assertEqual(
|
|
2176
|
+
* base64UrlToUint8Array(value),
|
|
2084
2177
|
* new Uint8Array([0, 1, 2, 255]),
|
|
2085
2178
|
* );
|
|
2086
2179
|
* ```
|
|
@@ -2138,6 +2231,8 @@ export const Id = /*#__PURE__*/ brand("Id", String, (value) => value.length ===
|
|
|
2138
2231
|
*
|
|
2139
2232
|
* ```ts
|
|
2140
2233
|
* import {
|
|
2234
|
+
* assertTrue,
|
|
2235
|
+
* assertType,
|
|
2141
2236
|
* Id,
|
|
2142
2237
|
* createId,
|
|
2143
2238
|
* createRandomBytes,
|
|
@@ -2146,8 +2241,8 @@ export const Id = /*#__PURE__*/ brand("Id", String, (value) => value.length ===
|
|
|
2146
2241
|
*
|
|
2147
2242
|
* const userId = createId<"User">({ randomBytes: createRandomBytes() });
|
|
2148
2243
|
*
|
|
2149
|
-
*
|
|
2150
|
-
*
|
|
2244
|
+
* assertTrue(Id.is(userId));
|
|
2245
|
+
* assertType<Id & Brand<"User">, typeof userId>();
|
|
2151
2246
|
* ```
|
|
2152
2247
|
*
|
|
2153
2248
|
* @group String
|
|
@@ -2163,14 +2258,20 @@ export const createId = (deps, ..._validation) => uint8ArrayToBase64Url(deps.ran
|
|
|
2163
2258
|
* ### Example
|
|
2164
2259
|
*
|
|
2165
2260
|
* ```ts
|
|
2166
|
-
* import {
|
|
2261
|
+
* import {
|
|
2262
|
+
* assertEqual,
|
|
2263
|
+
* assertType,
|
|
2264
|
+
* createIdFromString,
|
|
2265
|
+
* type Brand,
|
|
2266
|
+
* type Id,
|
|
2267
|
+
* } from "@evolu/common";
|
|
2167
2268
|
*
|
|
2168
2269
|
* const first = createIdFromString("external-user-123");
|
|
2169
2270
|
* const second = createIdFromString("external-user-123");
|
|
2170
2271
|
* const todoId = createIdFromString<"Todo">("external-todo-456");
|
|
2171
2272
|
*
|
|
2172
|
-
*
|
|
2173
|
-
*
|
|
2273
|
+
* assertEqual(first, second);
|
|
2274
|
+
* assertType<Id & Brand<"Todo">, typeof todoId>();
|
|
2174
2275
|
* ```
|
|
2175
2276
|
*
|
|
2176
2277
|
* @group String
|
|
@@ -2188,6 +2289,7 @@ export const createIdFromString = (value, ..._validation) => idBytesToId(sha256(
|
|
|
2188
2289
|
*
|
|
2189
2290
|
* ```ts
|
|
2190
2291
|
* import {
|
|
2292
|
+
* assertEqual,
|
|
2191
2293
|
* createIdAsUuidv7,
|
|
2192
2294
|
* createRandomBytes,
|
|
2193
2295
|
* createTime,
|
|
@@ -2200,8 +2302,8 @@ export const createIdFromString = (value, ..._validation) => idBytesToId(sha256(
|
|
|
2200
2302
|
* });
|
|
2201
2303
|
* const bytes = idToIdBytes(value);
|
|
2202
2304
|
*
|
|
2203
|
-
*
|
|
2204
|
-
*
|
|
2305
|
+
* assertEqual(bytes[6] >> 4, 0x7);
|
|
2306
|
+
* assertEqual(bytes[8] & 0xc0, 0x80);
|
|
2205
2307
|
* ```
|
|
2206
2308
|
*
|
|
2207
2309
|
* @group String
|
|
@@ -2226,6 +2328,7 @@ export const createIdAsUuidv7 = (deps, ..._validation) => {
|
|
|
2226
2328
|
*
|
|
2227
2329
|
* ```ts
|
|
2228
2330
|
* import {
|
|
2331
|
+
* assertType,
|
|
2229
2332
|
* createIdFromString,
|
|
2230
2333
|
* id,
|
|
2231
2334
|
* type Brand,
|
|
@@ -2235,7 +2338,7 @@ export const createIdAsUuidv7 = (deps, ..._validation) => {
|
|
|
2235
2338
|
* const TodoId = id("Todo");
|
|
2236
2339
|
* const todoId = TodoId.orThrow(createIdFromString("todo"));
|
|
2237
2340
|
*
|
|
2238
|
-
*
|
|
2341
|
+
* assertType<Id & Brand<"Todo">, typeof todoId>();
|
|
2239
2342
|
* ```
|
|
2240
2343
|
*
|
|
2241
2344
|
* @group String
|
|
@@ -2265,11 +2368,15 @@ export const idBytesTypeValueLength = 16;
|
|
|
2265
2368
|
* ### Example
|
|
2266
2369
|
*
|
|
2267
2370
|
* ```ts
|
|
2268
|
-
* import {
|
|
2371
|
+
* import {
|
|
2372
|
+
* assertEqual,
|
|
2373
|
+
* createIdFromString,
|
|
2374
|
+
* idToIdBytes,
|
|
2375
|
+
* } from "@evolu/common";
|
|
2269
2376
|
*
|
|
2270
2377
|
* const bytes = idToIdBytes(createIdFromString("todo"));
|
|
2271
2378
|
*
|
|
2272
|
-
*
|
|
2379
|
+
* assertEqual(bytes.length, 16);
|
|
2273
2380
|
* ```
|
|
2274
2381
|
*
|
|
2275
2382
|
* @group String
|
|
@@ -2282,6 +2389,7 @@ export const idToIdBytes = (value) => base64UrlToUint8Array(value);
|
|
|
2282
2389
|
*
|
|
2283
2390
|
* ```ts
|
|
2284
2391
|
* import {
|
|
2392
|
+
* assertEqual,
|
|
2285
2393
|
* createIdFromString,
|
|
2286
2394
|
* idBytesToId,
|
|
2287
2395
|
* idToIdBytes,
|
|
@@ -2289,7 +2397,7 @@ export const idToIdBytes = (value) => base64UrlToUint8Array(value);
|
|
|
2289
2397
|
*
|
|
2290
2398
|
* const value = createIdFromString("todo");
|
|
2291
2399
|
*
|
|
2292
|
-
*
|
|
2400
|
+
* assertEqual(idBytesToId(idToIdBytes(value)), value);
|
|
2293
2401
|
* ```
|
|
2294
2402
|
*
|
|
2295
2403
|
* @group String
|
|
@@ -2322,12 +2430,17 @@ export const Int64String = /*#__PURE__*/ brand("Int64String", NonEmptyTrimmedStr
|
|
|
2322
2430
|
* ### Example
|
|
2323
2431
|
*
|
|
2324
2432
|
* ```ts
|
|
2325
|
-
* import {
|
|
2433
|
+
* import {
|
|
2434
|
+
* assertEqual,
|
|
2435
|
+
* assertOk,
|
|
2436
|
+
* Int64FromInt64String,
|
|
2437
|
+
* } from "@evolu/common";
|
|
2326
2438
|
*
|
|
2327
2439
|
* const result = Int64FromInt64String.fromUnknown("9223372036854775807");
|
|
2328
2440
|
*
|
|
2329
|
-
*
|
|
2330
|
-
*
|
|
2441
|
+
* assertOk(result, 9223372036854775807n);
|
|
2442
|
+
* assertEqual(
|
|
2443
|
+
* Int64FromInt64String.to(result.value),
|
|
2331
2444
|
* "9223372036854775807",
|
|
2332
2445
|
* );
|
|
2333
2446
|
* ```
|
|
@@ -2344,11 +2457,11 @@ export const Int64FromInt64String = /*#__PURE__*/ transform("Int64FromInt64Strin
|
|
|
2344
2457
|
* ### Example
|
|
2345
2458
|
*
|
|
2346
2459
|
* ```ts
|
|
2347
|
-
* import { Number, nonNegative } from "@evolu/common";
|
|
2460
|
+
* import { assertOk, Number, nonNegative } from "@evolu/common";
|
|
2348
2461
|
*
|
|
2349
2462
|
* const NonNegative = nonNegative(Number);
|
|
2350
2463
|
*
|
|
2351
|
-
*
|
|
2464
|
+
* assertOk(NonNegative.fromUnknown(0), 0);
|
|
2352
2465
|
* ```
|
|
2353
2466
|
*
|
|
2354
2467
|
* @group Number
|
|
@@ -2366,11 +2479,11 @@ export const NonNegativeNumber = /*#__PURE__*/ nonNegative(Number);
|
|
|
2366
2479
|
* ### Example
|
|
2367
2480
|
*
|
|
2368
2481
|
* ```ts
|
|
2369
|
-
* import { Number, positive } from "@evolu/common";
|
|
2482
|
+
* import { assertOk, Number, positive } from "@evolu/common";
|
|
2370
2483
|
*
|
|
2371
2484
|
* const Positive = positive(Number);
|
|
2372
2485
|
*
|
|
2373
|
-
*
|
|
2486
|
+
* assertOk(Positive.fromUnknown(1), 1);
|
|
2374
2487
|
* ```
|
|
2375
2488
|
*
|
|
2376
2489
|
* @group Number
|
|
@@ -2391,11 +2504,11 @@ export const PositiveNumber = /*#__PURE__*/ positive(NonNegativeNumber);
|
|
|
2391
2504
|
* ### Example
|
|
2392
2505
|
*
|
|
2393
2506
|
* ```ts
|
|
2394
|
-
* import { Number, nonPositive } from "@evolu/common";
|
|
2507
|
+
* import { assertOk, Number, nonPositive } from "@evolu/common";
|
|
2395
2508
|
*
|
|
2396
2509
|
* const NonPositive = nonPositive(Number);
|
|
2397
2510
|
*
|
|
2398
|
-
*
|
|
2511
|
+
* assertOk(NonPositive.fromUnknown(0), 0);
|
|
2399
2512
|
* ```
|
|
2400
2513
|
*
|
|
2401
2514
|
* @group Number
|
|
@@ -2413,11 +2526,11 @@ export const NonPositiveNumber = /*#__PURE__*/ nonPositive(Number);
|
|
|
2413
2526
|
* ### Example
|
|
2414
2527
|
*
|
|
2415
2528
|
* ```ts
|
|
2416
|
-
* import { Number, negative } from "@evolu/common";
|
|
2529
|
+
* import { assertOk, Number, negative } from "@evolu/common";
|
|
2417
2530
|
*
|
|
2418
2531
|
* const Negative = negative(Number);
|
|
2419
2532
|
*
|
|
2420
|
-
*
|
|
2533
|
+
* assertOk(Negative.fromUnknown(-1), -1);
|
|
2421
2534
|
* ```
|
|
2422
2535
|
*
|
|
2423
2536
|
* @group Number
|
|
@@ -2438,11 +2551,11 @@ export const NegativeNumber = /*#__PURE__*/ negative(NonPositiveNumber);
|
|
|
2438
2551
|
* ### Example
|
|
2439
2552
|
*
|
|
2440
2553
|
* ```ts
|
|
2441
|
-
* import { Number, nonNaN } from "@evolu/common";
|
|
2554
|
+
* import { assertOk, Number, nonNaN } from "@evolu/common";
|
|
2442
2555
|
*
|
|
2443
2556
|
* const NonNaN = nonNaN(Number);
|
|
2444
2557
|
*
|
|
2445
|
-
*
|
|
2558
|
+
* assertOk(NonNaN.fromUnknown(Infinity), Infinity);
|
|
2446
2559
|
* ```
|
|
2447
2560
|
*
|
|
2448
2561
|
* @group Number
|
|
@@ -2467,11 +2580,11 @@ export const NonNaNNumber = /*#__PURE__*/ nonNaN(Number);
|
|
|
2467
2580
|
* ### Example
|
|
2468
2581
|
*
|
|
2469
2582
|
* ```ts
|
|
2470
|
-
* import { Number, finite } from "@evolu/common";
|
|
2583
|
+
* import { assertOk, Number, finite } from "@evolu/common";
|
|
2471
2584
|
*
|
|
2472
2585
|
* const Finite = finite(Number);
|
|
2473
2586
|
*
|
|
2474
|
-
*
|
|
2587
|
+
* assertOk(Finite.fromUnknown(42), 42);
|
|
2475
2588
|
* ```
|
|
2476
2589
|
*
|
|
2477
2590
|
* @group Number
|
|
@@ -2506,15 +2619,28 @@ export const PositiveFiniteNumber = /*#__PURE__*/ positive(NonNegativeFiniteNumb
|
|
|
2506
2619
|
* ### Example
|
|
2507
2620
|
*
|
|
2508
2621
|
* ```ts
|
|
2509
|
-
* import {
|
|
2622
|
+
* import {
|
|
2623
|
+
* assertEqual,
|
|
2624
|
+
* assertErr,
|
|
2625
|
+
* assertOk,
|
|
2626
|
+
* assertType,
|
|
2627
|
+
* Data,
|
|
2628
|
+
* Number,
|
|
2629
|
+
* int,
|
|
2630
|
+
* type Brand,
|
|
2631
|
+
* } from "@evolu/common";
|
|
2510
2632
|
*
|
|
2511
2633
|
* const Int = int(Number);
|
|
2512
2634
|
* type Int = typeof Int.Output;
|
|
2513
2635
|
*
|
|
2514
|
-
*
|
|
2636
|
+
* assertType<number & Brand<"Int">, Int>();
|
|
2515
2637
|
*
|
|
2516
|
-
*
|
|
2517
|
-
*
|
|
2638
|
+
* assertOk(Int.fromUnknown(42), 42);
|
|
2639
|
+
* const invalid = Int.fromUnknown(1.5);
|
|
2640
|
+
* assertErr(invalid);
|
|
2641
|
+
* assertType(Data, invalid.error);
|
|
2642
|
+
* const error: Data = invalid.error;
|
|
2643
|
+
* assertEqual(error, { type: "Int", value: 1.5 });
|
|
2518
2644
|
* ```
|
|
2519
2645
|
*
|
|
2520
2646
|
* @group Number
|
|
@@ -2583,11 +2709,11 @@ export const NegativeInt = /*#__PURE__*/ negative(NonPositiveInt);
|
|
|
2583
2709
|
* ### Example
|
|
2584
2710
|
*
|
|
2585
2711
|
* ```ts
|
|
2586
|
-
* import { Number, greaterThan } from "@evolu/common";
|
|
2712
|
+
* import { assertOk, Number, greaterThan } from "@evolu/common";
|
|
2587
2713
|
*
|
|
2588
2714
|
* const GreaterThanTen = greaterThan(10)(Number);
|
|
2589
2715
|
*
|
|
2590
|
-
*
|
|
2716
|
+
* assertOk(GreaterThanTen.fromUnknown(11), 11);
|
|
2591
2717
|
* ```
|
|
2592
2718
|
*
|
|
2593
2719
|
* @group Number
|
|
@@ -2604,11 +2730,11 @@ export const greaterThan = (min) => (parent) => {
|
|
|
2604
2730
|
* ### Example
|
|
2605
2731
|
*
|
|
2606
2732
|
* ```ts
|
|
2607
|
-
* import { Number, greaterThanOrEqualTo } from "@evolu/common";
|
|
2733
|
+
* import { assertOk, Number, greaterThanOrEqualTo } from "@evolu/common";
|
|
2608
2734
|
*
|
|
2609
2735
|
* const AtLeastTen = greaterThanOrEqualTo(10)(Number);
|
|
2610
2736
|
*
|
|
2611
|
-
*
|
|
2737
|
+
* assertOk(AtLeastTen.fromUnknown(10), 10);
|
|
2612
2738
|
* ```
|
|
2613
2739
|
*
|
|
2614
2740
|
* @group Number
|
|
@@ -2629,11 +2755,11 @@ export const greaterThanOrEqualTo = (min) => (parent) => {
|
|
|
2629
2755
|
* ### Example
|
|
2630
2756
|
*
|
|
2631
2757
|
* ```ts
|
|
2632
|
-
* import { Number, lessThan } from "@evolu/common";
|
|
2758
|
+
* import { assertOk, Number, lessThan } from "@evolu/common";
|
|
2633
2759
|
*
|
|
2634
2760
|
* const LessThanTen = lessThan(10)(Number);
|
|
2635
2761
|
*
|
|
2636
|
-
*
|
|
2762
|
+
* assertOk(LessThanTen.fromUnknown(9), 9);
|
|
2637
2763
|
* ```
|
|
2638
2764
|
*
|
|
2639
2765
|
* @group Number
|
|
@@ -2657,11 +2783,11 @@ export const Age = /*#__PURE__*/ brand("Age",
|
|
|
2657
2783
|
* ### Example
|
|
2658
2784
|
*
|
|
2659
2785
|
* ```ts
|
|
2660
|
-
* import { Number, lessThanOrEqualTo } from "@evolu/common";
|
|
2786
|
+
* import { assertOk, Number, lessThanOrEqualTo } from "@evolu/common";
|
|
2661
2787
|
*
|
|
2662
2788
|
* const AtMostTen = lessThanOrEqualTo(10)(Number);
|
|
2663
2789
|
*
|
|
2664
|
-
*
|
|
2790
|
+
* assertOk(AtMostTen.fromUnknown(10), 10);
|
|
2665
2791
|
* ```
|
|
2666
2792
|
*
|
|
2667
2793
|
* @group Number
|
|
@@ -2708,13 +2834,23 @@ export const Ratio = /*#__PURE__*/ brand("Ratio",
|
|
|
2708
2834
|
* ### Example
|
|
2709
2835
|
*
|
|
2710
2836
|
* ```ts
|
|
2711
|
-
* import {
|
|
2837
|
+
* import {
|
|
2838
|
+
* assertEqual,
|
|
2839
|
+
* assertErr,
|
|
2840
|
+
* assertOk,
|
|
2841
|
+
* assertType,
|
|
2842
|
+
* Data,
|
|
2843
|
+
* DecimalString,
|
|
2844
|
+
* } from "@evolu/common";
|
|
2712
2845
|
*
|
|
2713
|
-
*
|
|
2714
|
-
*
|
|
2715
|
-
*
|
|
2846
|
+
* assertOk(DecimalString.fromUnknown("-10.25"), "-10.25");
|
|
2847
|
+
* assertOk(DecimalString.fromUnknown("0"), "0");
|
|
2848
|
+
* assertOk(DecimalString.fromUnknown("10.25"), "10.25");
|
|
2716
2849
|
*
|
|
2717
|
-
*
|
|
2850
|
+
* const invalid = DecimalString.fromUnknown("10.250");
|
|
2851
|
+
* assertErr(invalid);
|
|
2852
|
+
* assertType(Data, invalid.error);
|
|
2853
|
+
* assertEqual(invalid.error, {
|
|
2718
2854
|
* type: "DecimalString",
|
|
2719
2855
|
* value: "10.250",
|
|
2720
2856
|
* });
|
|
@@ -2731,11 +2867,15 @@ export const DecimalString = /*#__PURE__*/ brand("DecimalString", String, (value
|
|
|
2731
2867
|
* ### Example
|
|
2732
2868
|
*
|
|
2733
2869
|
* ```ts
|
|
2734
|
-
* import {
|
|
2870
|
+
* import {
|
|
2871
|
+
* assertOk,
|
|
2872
|
+
* DecimalString,
|
|
2873
|
+
* nonNegativeDecimalString,
|
|
2874
|
+
* } from "@evolu/common";
|
|
2735
2875
|
*
|
|
2736
2876
|
* const NonNegative = nonNegativeDecimalString(DecimalString);
|
|
2737
2877
|
*
|
|
2738
|
-
*
|
|
2878
|
+
* assertOk(NonNegative.fromUnknown("0.5"), "0.5");
|
|
2739
2879
|
* ```
|
|
2740
2880
|
*
|
|
2741
2881
|
* @group Number
|
|
@@ -2759,11 +2899,15 @@ export const NonNegativeDecimalString =
|
|
|
2759
2899
|
* ### Example
|
|
2760
2900
|
*
|
|
2761
2901
|
* ```ts
|
|
2762
|
-
* import {
|
|
2902
|
+
* import {
|
|
2903
|
+
* assertOk,
|
|
2904
|
+
* DecimalString,
|
|
2905
|
+
* positiveDecimalString,
|
|
2906
|
+
* } from "@evolu/common";
|
|
2763
2907
|
*
|
|
2764
2908
|
* const Positive = positiveDecimalString(DecimalString);
|
|
2765
2909
|
*
|
|
2766
|
-
*
|
|
2910
|
+
* assertOk(Positive.fromUnknown("0.5"), "0.5");
|
|
2767
2911
|
* ```
|
|
2768
2912
|
*
|
|
2769
2913
|
* @group Number
|
|
@@ -2789,11 +2933,15 @@ export const PositiveDecimalString = /*#__PURE__*/ positiveDecimalString(NonNega
|
|
|
2789
2933
|
* ### Example
|
|
2790
2934
|
*
|
|
2791
2935
|
* ```ts
|
|
2792
|
-
* import {
|
|
2936
|
+
* import {
|
|
2937
|
+
* assertOk,
|
|
2938
|
+
* DecimalString,
|
|
2939
|
+
* nonPositiveDecimalString,
|
|
2940
|
+
* } from "@evolu/common";
|
|
2793
2941
|
*
|
|
2794
2942
|
* const NonPositive = nonPositiveDecimalString(DecimalString);
|
|
2795
2943
|
*
|
|
2796
|
-
*
|
|
2944
|
+
* assertOk(NonPositive.fromUnknown("-0.5"), "-0.5");
|
|
2797
2945
|
* ```
|
|
2798
2946
|
*
|
|
2799
2947
|
* @group Number
|
|
@@ -2817,11 +2965,15 @@ export const NonPositiveDecimalString =
|
|
|
2817
2965
|
* ### Example
|
|
2818
2966
|
*
|
|
2819
2967
|
* ```ts
|
|
2820
|
-
* import {
|
|
2968
|
+
* import {
|
|
2969
|
+
* assertOk,
|
|
2970
|
+
* DecimalString,
|
|
2971
|
+
* negativeDecimalString,
|
|
2972
|
+
* } from "@evolu/common";
|
|
2821
2973
|
*
|
|
2822
2974
|
* const Negative = negativeDecimalString(DecimalString);
|
|
2823
2975
|
*
|
|
2824
|
-
*
|
|
2976
|
+
* assertOk(Negative.fromUnknown("-0.5"), "-0.5");
|
|
2825
2977
|
* ```
|
|
2826
2978
|
*
|
|
2827
2979
|
* @group Number
|
|
@@ -2855,16 +3007,29 @@ export const NegativeDecimalString = /*#__PURE__*/ negativeDecimalString(NonPosi
|
|
|
2855
3007
|
* ### Example
|
|
2856
3008
|
*
|
|
2857
3009
|
* ```ts
|
|
2858
|
-
* import {
|
|
3010
|
+
* import {
|
|
3011
|
+
* assertEqual,
|
|
3012
|
+
* assertErr,
|
|
3013
|
+
* assertOk,
|
|
3014
|
+
* assertType,
|
|
3015
|
+
* Data,
|
|
3016
|
+
* FiniteNumber,
|
|
3017
|
+
* multipleOf,
|
|
3018
|
+
* type Brand,
|
|
3019
|
+
* } from "@evolu/common";
|
|
2859
3020
|
*
|
|
2860
3021
|
* const Tenths = multipleOf("0.1")(FiniteNumber);
|
|
2861
3022
|
*
|
|
2862
|
-
*
|
|
2863
|
-
* FiniteNumber & Brand<"MultipleOf0.1"
|
|
3023
|
+
* assertType<
|
|
3024
|
+
* FiniteNumber & Brand<"MultipleOf0.1">,
|
|
3025
|
+
* typeof Tenths.Output
|
|
2864
3026
|
* >();
|
|
2865
3027
|
*
|
|
2866
|
-
*
|
|
2867
|
-
*
|
|
3028
|
+
* assertOk(Tenths.fromUnknown(0.3), 0.3);
|
|
3029
|
+
* const invalid = Tenths.fromUnknown(0.31);
|
|
3030
|
+
* assertErr(invalid);
|
|
3031
|
+
* assertType(Data, invalid.error);
|
|
3032
|
+
* assertEqual(invalid.error, {
|
|
2868
3033
|
* type: "MultipleOf0.1",
|
|
2869
3034
|
* value: 0.31,
|
|
2870
3035
|
* divisor: "0.1",
|
|
@@ -2920,11 +3085,11 @@ const decimalStringToParts = (value) => {
|
|
|
2920
3085
|
* ### Example
|
|
2921
3086
|
*
|
|
2922
3087
|
* ```ts
|
|
2923
|
-
* import { Number, between } from "@evolu/common";
|
|
3088
|
+
* import { assertOk, Number, between } from "@evolu/common";
|
|
2924
3089
|
*
|
|
2925
3090
|
* const Percentage = between(0, 100)(Number);
|
|
2926
3091
|
*
|
|
2927
|
-
*
|
|
3092
|
+
* assertOk(Percentage.fromUnknown(75), 75);
|
|
2928
3093
|
* ```
|
|
2929
3094
|
*
|
|
2930
3095
|
* @group Number
|
|
@@ -2966,6 +3131,11 @@ export const between = (min, max) => (parent) => {
|
|
|
2966
3131
|
*
|
|
2967
3132
|
* ```ts
|
|
2968
3133
|
* import {
|
|
3134
|
+
* assertEqual,
|
|
3135
|
+
* assertErr,
|
|
3136
|
+
* assertOk,
|
|
3137
|
+
* assertType,
|
|
3138
|
+
* Data,
|
|
2969
3139
|
* String,
|
|
2970
3140
|
* array,
|
|
2971
3141
|
* brand,
|
|
@@ -2977,12 +3147,16 @@ export const between = (min, max) => (parent) => {
|
|
|
2977
3147
|
* const UserIds = array(UserId);
|
|
2978
3148
|
* const result = UserIds.from.parent(["ada", "grace"]);
|
|
2979
3149
|
*
|
|
2980
|
-
*
|
|
2981
|
-
* Result<ReadonlyArray<string & Brand<"UserId"
|
|
3150
|
+
* assertType<
|
|
3151
|
+
* Result<ReadonlyArray<string & Brand<"UserId">>>,
|
|
3152
|
+
* typeof result
|
|
2982
3153
|
* >();
|
|
2983
|
-
*
|
|
2984
|
-
*
|
|
2985
|
-
*
|
|
3154
|
+
* assertOk(result, ["ada", "grace"]);
|
|
3155
|
+
* assertOk(UserIds.fromUnknown(["ada", "grace"]), ["ada", "grace"]);
|
|
3156
|
+
* const invalid = UserIds.fromUnknown("ada");
|
|
3157
|
+
* assertErr(invalid);
|
|
3158
|
+
* assertType(Data, invalid.error);
|
|
3159
|
+
* assertEqual(invalid.error, {
|
|
2986
3160
|
* type: "Array",
|
|
2987
3161
|
* reason: { kind: "NotArray", value: "ada" },
|
|
2988
3162
|
* });
|
|
@@ -3130,20 +3304,20 @@ const copyArrayPrefix = (value, endIndex) => {
|
|
|
3130
3304
|
/**
|
|
3131
3305
|
* Set {@link Type} whose every element must match one Type.
|
|
3132
3306
|
*
|
|
3133
|
-
*
|
|
3134
|
-
*
|
|
3135
|
-
*
|
|
3136
|
-
*
|
|
3307
|
+
* Sets from this or another realm are accepted. A Set must have no own
|
|
3308
|
+
* properties; its elements are validated in iteration order. Classification
|
|
3309
|
+
* uses the realm-neutral object tag under Evolu Type's trusted JavaScript
|
|
3310
|
+
* policy.
|
|
3137
3311
|
*
|
|
3138
3312
|
* ### Example
|
|
3139
3313
|
*
|
|
3140
3314
|
* ```ts
|
|
3141
|
-
* import { String, set } from "@evolu/common";
|
|
3315
|
+
* import { assertOk, String, set } from "@evolu/common";
|
|
3142
3316
|
*
|
|
3143
3317
|
* const Tags = set(String);
|
|
3144
3318
|
* const tags = new Set(["local-first", "offline"]);
|
|
3145
3319
|
*
|
|
3146
|
-
*
|
|
3320
|
+
* assertOk(Tags.fromUnknown(tags), tags);
|
|
3147
3321
|
* ```
|
|
3148
3322
|
*
|
|
3149
3323
|
* @group Collection
|
|
@@ -3177,68 +3351,44 @@ const createHomogeneousCollectionType = (typeElement, config) => {
|
|
|
3177
3351
|
return type;
|
|
3178
3352
|
};
|
|
3179
3353
|
const setTypeByElement = /*#__PURE__*/ new WeakMap();
|
|
3180
|
-
const validateSetCollection = (value, validateElement, options) => {
|
|
3181
|
-
if (!hasObjectTag(value, "Set")) {
|
|
3182
|
-
return err({
|
|
3183
|
-
type: "Set",
|
|
3184
|
-
reason: { kind: "NotSet", value },
|
|
3185
|
-
});
|
|
3186
|
-
}
|
|
3187
|
-
if (!hasDirectSetPrototype(value)) {
|
|
3188
|
-
return err({
|
|
3189
|
-
type: "Set",
|
|
3190
|
-
reason: {
|
|
3191
|
-
kind: "UnexpectedPrototype",
|
|
3192
|
-
value: value,
|
|
3193
|
-
},
|
|
3194
|
-
});
|
|
3195
|
-
}
|
|
3196
|
-
return validateSetItems(value, validateElement, options, true);
|
|
3197
|
-
};
|
|
3198
|
-
const encodeSetCollection = (value, encodeElement) => {
|
|
3199
|
-
let changed = false;
|
|
3200
|
-
const output = new globalThis.Set();
|
|
3201
|
-
for (const item of value) {
|
|
3202
|
-
const encoded = encodeElement(item);
|
|
3203
|
-
if (!globalThis.Object.is(encoded, item))
|
|
3204
|
-
changed = true;
|
|
3205
|
-
output.add(encoded);
|
|
3206
|
-
}
|
|
3207
|
-
return changed ? output : value;
|
|
3208
|
-
};
|
|
3209
|
-
const isSetCollection = (value, isElement) => {
|
|
3210
|
-
if (!hasObjectTag(value, "Set"))
|
|
3211
|
-
return false;
|
|
3212
|
-
if (!hasDirectSetPrototype(value))
|
|
3213
|
-
return false;
|
|
3214
|
-
if (Reflect.ownKeys(value).length !== 0)
|
|
3215
|
-
return false;
|
|
3216
|
-
for (const item of value) {
|
|
3217
|
-
if (!isElement(item))
|
|
3218
|
-
return false;
|
|
3219
|
-
}
|
|
3220
|
-
return true;
|
|
3221
|
-
};
|
|
3222
|
-
const hasDirectSetPrototype = (value) => {
|
|
3223
|
-
const prototype = globalThis.Object.getPrototypeOf(value);
|
|
3224
|
-
if (prototype === null)
|
|
3225
|
-
return false;
|
|
3226
|
-
const objectPrototype = globalThis.Object.getPrototypeOf(prototype);
|
|
3227
|
-
return (objectPrototype !== null &&
|
|
3228
|
-
globalThis.Object.getPrototypeOf(objectPrototype) === null);
|
|
3229
|
-
};
|
|
3230
3354
|
const setRuntimeConfig = {
|
|
3231
3355
|
name: "Set",
|
|
3232
3356
|
typeByElement: setTypeByElement,
|
|
3233
|
-
validate:
|
|
3357
|
+
validate: (value, validateElement, options) => {
|
|
3358
|
+
if (!hasObjectTag(value, "Set")) {
|
|
3359
|
+
return err({
|
|
3360
|
+
type: "Set",
|
|
3361
|
+
reason: { kind: "NotSet", value },
|
|
3362
|
+
});
|
|
3363
|
+
}
|
|
3364
|
+
return validateSetItems(value, validateElement, options, true);
|
|
3365
|
+
},
|
|
3234
3366
|
validateItems: (value, validateElement, options) => validateSetItems(value, validateElement, options, false),
|
|
3235
|
-
encode:
|
|
3236
|
-
|
|
3367
|
+
encode: (value, encodeElement) => {
|
|
3368
|
+
let changed = false;
|
|
3369
|
+
const output = new Set();
|
|
3370
|
+
for (const item of value) {
|
|
3371
|
+
const encoded = encodeElement(item);
|
|
3372
|
+
if (!globalThis.Object.is(encoded, item))
|
|
3373
|
+
changed = true;
|
|
3374
|
+
output.add(encoded);
|
|
3375
|
+
}
|
|
3376
|
+
return changed ? output : value;
|
|
3377
|
+
},
|
|
3378
|
+
is: (value, isElement) => {
|
|
3379
|
+
if (!hasObjectTag(value, "Set") ||
|
|
3380
|
+
Reflect.ownKeys(value).length !== 0) {
|
|
3381
|
+
return false;
|
|
3382
|
+
}
|
|
3383
|
+
for (const item of value) {
|
|
3384
|
+
if (!isElement(item))
|
|
3385
|
+
return false;
|
|
3386
|
+
}
|
|
3387
|
+
return true;
|
|
3388
|
+
},
|
|
3237
3389
|
formatError: ((error) => {
|
|
3238
3390
|
if (error.reason.kind === "NotSet")
|
|
3239
3391
|
return `A value ${safelyStringifyUnknownValue(error.reason.value)} is not a Set.`;
|
|
3240
|
-
if (error.reason.kind === "UnexpectedPrototype")
|
|
3241
|
-
return "The value is an instance of a Set subclass, but a Set Output must be a direct Set instance.";
|
|
3242
3392
|
const issue = error.reason.issues[0];
|
|
3243
3393
|
switch (issue.kind) {
|
|
3244
3394
|
case "ExcessProperty":
|
|
@@ -3251,7 +3401,7 @@ const setRuntimeConfig = {
|
|
|
3251
3401
|
const validateSetItems = (value, validate, options, checkStructure) => {
|
|
3252
3402
|
let issues;
|
|
3253
3403
|
let changed = false;
|
|
3254
|
-
const output = new
|
|
3404
|
+
const output = new Set();
|
|
3255
3405
|
if (checkStructure) {
|
|
3256
3406
|
for (const key of Reflect.ownKeys(value)) {
|
|
3257
3407
|
(issues ??= []).push({ kind: "ExcessProperty", key });
|
|
@@ -3284,6 +3434,208 @@ const validateSetItems = (value, validate, options, checkStructure) => {
|
|
|
3284
3434
|
},
|
|
3285
3435
|
});
|
|
3286
3436
|
};
|
|
3437
|
+
/**
|
|
3438
|
+
* Map {@link Type} whose keys and values must match their respective Types.
|
|
3439
|
+
*
|
|
3440
|
+
* Maps from this or another realm are accepted. A Map must have no own
|
|
3441
|
+
* properties; its entries are validated in iteration order. When distinct input
|
|
3442
|
+
* keys decode to the same output key, validation fails instead of discarding
|
|
3443
|
+
* one associated value. Classification uses the realm-neutral object tag under
|
|
3444
|
+
* Evolu Type's trusted JavaScript policy.
|
|
3445
|
+
*
|
|
3446
|
+
* ### Example
|
|
3447
|
+
*
|
|
3448
|
+
* ```ts
|
|
3449
|
+
* import { assertOk, PositiveInt, String, map } from "@evolu/common";
|
|
3450
|
+
*
|
|
3451
|
+
* const Scores = map(String, PositiveInt);
|
|
3452
|
+
* const scores = new Map([
|
|
3453
|
+
* ["Ada", 10],
|
|
3454
|
+
* ["Grace", 20],
|
|
3455
|
+
* ]);
|
|
3456
|
+
*
|
|
3457
|
+
* assertOk(Scores.fromUnknown(scores), scores);
|
|
3458
|
+
* ```
|
|
3459
|
+
*
|
|
3460
|
+
* @group Collection
|
|
3461
|
+
*/
|
|
3462
|
+
export const map = (key, value) => {
|
|
3463
|
+
const typeKey = key;
|
|
3464
|
+
const typeValue = value;
|
|
3465
|
+
let typeByValue = mapTypeByValueByKey.get(typeKey);
|
|
3466
|
+
const cached = typeByValue?.get(typeValue);
|
|
3467
|
+
if (cached)
|
|
3468
|
+
return cached;
|
|
3469
|
+
const validate = (input, validateKey, validateValue, options) => {
|
|
3470
|
+
if (!hasObjectTag(input, "Map")) {
|
|
3471
|
+
return err({
|
|
3472
|
+
type: "Map",
|
|
3473
|
+
reason: { kind: "NotMap", value: input },
|
|
3474
|
+
});
|
|
3475
|
+
}
|
|
3476
|
+
return validateMapEntries(input, validateKey, validateValue, options, true);
|
|
3477
|
+
};
|
|
3478
|
+
const fromUnknown = (input, options = firstValidationOptions) => validate(input, typeKey.fromUnknown, typeValue.fromUnknown, options);
|
|
3479
|
+
const validateOutput = (input, options = firstValidationOptions) => validate(input, typeKey[outputValidationSymbol], typeValue[outputValidationSymbol], options);
|
|
3480
|
+
const formatError = (error) => {
|
|
3481
|
+
if (error.reason.kind === "NotMap")
|
|
3482
|
+
return `A value ${safelyStringifyUnknownValue(error.reason.value)} is not a Map.`;
|
|
3483
|
+
const issue = error.reason.issues[0];
|
|
3484
|
+
switch (issue.kind) {
|
|
3485
|
+
case "ExcessProperty":
|
|
3486
|
+
return `An excess Map property ${safelyStringifyUnknownValue(issue.key)} is not allowed.`;
|
|
3487
|
+
case "Collision":
|
|
3488
|
+
return `Map keys at indexes ${issue.previousIndex} and ${issue.index} decode to the same key ${safelyStringifyUnknownValue(issue.outputKey)}.`;
|
|
3489
|
+
}
|
|
3490
|
+
};
|
|
3491
|
+
const rootKey = getTerminalRuntimeNode(typeKey);
|
|
3492
|
+
const rootValue = getTerminalRuntimeNode(typeValue);
|
|
3493
|
+
const parent = rootKey !== typeKey || rootValue !== typeValue
|
|
3494
|
+
? map(rootKey, rootValue)
|
|
3495
|
+
: null;
|
|
3496
|
+
const fromParent = parent
|
|
3497
|
+
? (input, options = firstValidationOptions) => validateMapEntries(input, getTerminalRuntimeNode(typeKey[fromSymbol]), getTerminalRuntimeNode(typeValue[fromSymbol]), options, false)
|
|
3498
|
+
: undefined;
|
|
3499
|
+
const from = createFromOperation(fromParent);
|
|
3500
|
+
const encodeKey = typeKey[encoderSymbol];
|
|
3501
|
+
const encodeValue = typeValue[encoderSymbol];
|
|
3502
|
+
const to = encodeKey === identity && encodeValue === identity
|
|
3503
|
+
? identity
|
|
3504
|
+
: (input) => {
|
|
3505
|
+
const output = new Map();
|
|
3506
|
+
let changed = false;
|
|
3507
|
+
for (const [inputKey, inputValue] of input) {
|
|
3508
|
+
const outputKey = encodeKey(inputKey);
|
|
3509
|
+
const outputValue = encodeValue(inputValue);
|
|
3510
|
+
assert(!output.has(outputKey), "Map key Type encoding must not produce duplicate keys.");
|
|
3511
|
+
output.set(outputKey, outputValue);
|
|
3512
|
+
if (!globalThis.Object.is(inputKey, outputKey) ||
|
|
3513
|
+
!globalThis.Object.is(inputValue, outputValue)) {
|
|
3514
|
+
changed = true;
|
|
3515
|
+
}
|
|
3516
|
+
}
|
|
3517
|
+
return changed ? output : input;
|
|
3518
|
+
};
|
|
3519
|
+
const is = (input) => {
|
|
3520
|
+
if (!hasObjectTag(input, "Map")) {
|
|
3521
|
+
return false;
|
|
3522
|
+
}
|
|
3523
|
+
if (Reflect.ownKeys(input).length !== 0)
|
|
3524
|
+
return false;
|
|
3525
|
+
for (const [inputKey, inputValue] of input) {
|
|
3526
|
+
if (!typeKey.is(inputKey) || !typeValue.is(inputValue))
|
|
3527
|
+
return false;
|
|
3528
|
+
}
|
|
3529
|
+
return true;
|
|
3530
|
+
};
|
|
3531
|
+
const getTypeIssues = (error, mode) => {
|
|
3532
|
+
const mapError = error;
|
|
3533
|
+
if (mapError.reason.kind !== "Entries") {
|
|
3534
|
+
return singleRuntimeTypeIssue("Map", error, formatError);
|
|
3535
|
+
}
|
|
3536
|
+
const allIssues = mapError.reason.issues;
|
|
3537
|
+
const issues = mode === "first" ? [allIssues[0]] : allIssues;
|
|
3538
|
+
return issues.flatMap((issue) => {
|
|
3539
|
+
if (issue.kind === "Key" || issue.kind === "Value") {
|
|
3540
|
+
return prependRuntimeTypeIssuePath(issue.index, prependRuntimeTypeIssuePath(issue.kind === "Key" ? "key" : "value", (issue.kind === "Key" ? typeKey : typeValue)[getRuntimeTypeIssuesSymbol](issue.error, mode)));
|
|
3541
|
+
}
|
|
3542
|
+
return singleRuntimeTypeIssue("Map", mode === "first"
|
|
3543
|
+
? error
|
|
3544
|
+
: {
|
|
3545
|
+
type: "Map",
|
|
3546
|
+
reason: { kind: "Entries", issues: [issue] },
|
|
3547
|
+
}, formatError, [issue.kind === "ExcessProperty" ? issue.key : issue.index]);
|
|
3548
|
+
});
|
|
3549
|
+
};
|
|
3550
|
+
const type = createTypeNode("Map", parent, fromUnknown, is, validateOutput, from, to, getTypeIssues, { key: typeKey, value: typeValue });
|
|
3551
|
+
if (typeByValue === undefined) {
|
|
3552
|
+
typeByValue = new WeakMap();
|
|
3553
|
+
mapTypeByValueByKey.set(typeKey, typeByValue);
|
|
3554
|
+
}
|
|
3555
|
+
typeByValue.set(typeValue, type);
|
|
3556
|
+
return type;
|
|
3557
|
+
};
|
|
3558
|
+
const mapTypeByValueByKey = /*#__PURE__*/ new WeakMap();
|
|
3559
|
+
const validateMapEntries = (input, validateKey, validateValue, options, checkStructure) => {
|
|
3560
|
+
let issues;
|
|
3561
|
+
const output = new Map();
|
|
3562
|
+
const inputByOutputKey = new Map();
|
|
3563
|
+
let changed = false;
|
|
3564
|
+
if (checkStructure) {
|
|
3565
|
+
for (const key of Reflect.ownKeys(input)) {
|
|
3566
|
+
(issues ??= []).push({ kind: "ExcessProperty", key });
|
|
3567
|
+
if (options.errors === "first")
|
|
3568
|
+
break;
|
|
3569
|
+
}
|
|
3570
|
+
}
|
|
3571
|
+
let index = 0;
|
|
3572
|
+
for (const [inputKey, inputValue] of input) {
|
|
3573
|
+
if (issues !== undefined && options.errors === "first")
|
|
3574
|
+
break;
|
|
3575
|
+
const keyResult = validateKey(inputKey, options);
|
|
3576
|
+
if (!keyResult.ok) {
|
|
3577
|
+
(issues ??= []).push({
|
|
3578
|
+
kind: "Key",
|
|
3579
|
+
index,
|
|
3580
|
+
key: inputKey,
|
|
3581
|
+
error: keyResult.error,
|
|
3582
|
+
});
|
|
3583
|
+
if (options.errors === "first")
|
|
3584
|
+
break;
|
|
3585
|
+
}
|
|
3586
|
+
const valueResult = validateValue(inputValue, options);
|
|
3587
|
+
if (!valueResult.ok) {
|
|
3588
|
+
(issues ??= []).push({
|
|
3589
|
+
kind: "Value",
|
|
3590
|
+
index,
|
|
3591
|
+
key: inputKey,
|
|
3592
|
+
error: valueResult.error,
|
|
3593
|
+
});
|
|
3594
|
+
if (options.errors === "first")
|
|
3595
|
+
break;
|
|
3596
|
+
}
|
|
3597
|
+
if (!keyResult.ok) {
|
|
3598
|
+
index++;
|
|
3599
|
+
continue;
|
|
3600
|
+
}
|
|
3601
|
+
const outputKey = keyResult.value;
|
|
3602
|
+
const previous = inputByOutputKey.get(outputKey);
|
|
3603
|
+
if (previous !== undefined) {
|
|
3604
|
+
(issues ??= []).push({
|
|
3605
|
+
kind: "Collision",
|
|
3606
|
+
index,
|
|
3607
|
+
key: inputKey,
|
|
3608
|
+
previousIndex: previous.index,
|
|
3609
|
+
previousKey: previous.key,
|
|
3610
|
+
outputKey,
|
|
3611
|
+
});
|
|
3612
|
+
if (options.errors === "first")
|
|
3613
|
+
break;
|
|
3614
|
+
index++;
|
|
3615
|
+
continue;
|
|
3616
|
+
}
|
|
3617
|
+
inputByOutputKey.set(outputKey, { index, key: inputKey });
|
|
3618
|
+
if (!valueResult.ok) {
|
|
3619
|
+
index++;
|
|
3620
|
+
continue;
|
|
3621
|
+
}
|
|
3622
|
+
output.set(outputKey, valueResult.value);
|
|
3623
|
+
if (!globalThis.Object.is(inputKey, outputKey) ||
|
|
3624
|
+
!globalThis.Object.is(inputValue, valueResult.value)) {
|
|
3625
|
+
changed = true;
|
|
3626
|
+
}
|
|
3627
|
+
index++;
|
|
3628
|
+
}
|
|
3629
|
+
return issues === undefined
|
|
3630
|
+
? ok(changed ? output : input)
|
|
3631
|
+
: err({
|
|
3632
|
+
type: "Map",
|
|
3633
|
+
reason: {
|
|
3634
|
+
kind: "Entries",
|
|
3635
|
+
issues: issues,
|
|
3636
|
+
},
|
|
3637
|
+
});
|
|
3638
|
+
};
|
|
3287
3639
|
export function tuple(...elements) {
|
|
3288
3640
|
return createTupleType(elements);
|
|
3289
3641
|
}
|
|
@@ -3467,11 +3819,12 @@ const formatPlainObjectRootError = (reason) => reason.kind === "NotObject"
|
|
|
3467
3819
|
* A {@link Type} for readonly plain objects with unknown property values.
|
|
3468
3820
|
*
|
|
3469
3821
|
* `Object` is the runtime counterpart of a `Readonly<Record<string, unknown>>`
|
|
3470
|
-
* data boundary. Its
|
|
3471
|
-
*
|
|
3472
|
-
*
|
|
3473
|
-
*
|
|
3474
|
-
* properties, and symbol properties
|
|
3822
|
+
* data boundary. Its prototype rule uses the realm-neutral structural heuristic
|
|
3823
|
+
* described by {@link isPlainObject}. A matching custom root prototype can be
|
|
3824
|
+
* classified as plain; other custom prototypes and class instances are
|
|
3825
|
+
* rejected. Every own property must have a string key and be an enumerable data
|
|
3826
|
+
* property. Accessors, non-enumerable properties, and symbol properties are
|
|
3827
|
+
* rejected without reading their values.
|
|
3475
3828
|
*
|
|
3476
3829
|
* Use {@link object} when property names are fixed, {@link record} when keys and
|
|
3477
3830
|
* values have their own Types, and {@link instanceOf} when an instance belongs
|
|
@@ -3579,10 +3932,6 @@ const _Object = /*#__PURE__*/ createRootType("Object", (value, options = firstVa
|
|
|
3579
3932
|
// https://github.com/react/metro/issues/1331
|
|
3580
3933
|
// https://github.com/expo/expo/issues/31167
|
|
3581
3934
|
export { _Object as Object };
|
|
3582
|
-
const isPlainObject = (value) => {
|
|
3583
|
-
const prototype = globalThis.Object.getPrototypeOf(value);
|
|
3584
|
-
return (prototype === null || globalThis.Object.getPrototypeOf(prototype) === null);
|
|
3585
|
-
};
|
|
3586
3935
|
/**
|
|
3587
3936
|
* Record {@link Type}.
|
|
3588
3937
|
*
|
|
@@ -3599,12 +3948,13 @@ const isPlainObject = (value) => {
|
|
|
3599
3948
|
* By default, validation returns the first issue. Pass `{ errors: "all" }` to
|
|
3600
3949
|
* collect issues across the whole Record.
|
|
3601
3950
|
*
|
|
3602
|
-
* A Record must
|
|
3603
|
-
*
|
|
3604
|
-
*
|
|
3605
|
-
*
|
|
3606
|
-
*
|
|
3607
|
-
* `__proto__` remain ordinary
|
|
3951
|
+
* A Record must satisfy the realm-neutral structural heuristic described by
|
|
3952
|
+
* {@link isPlainObject}. A matching custom root prototype can be classified as
|
|
3953
|
+
* plain; other custom prototypes and class instances are rejected. Every own
|
|
3954
|
+
* property must be an enumerable data property whose key and value satisfy
|
|
3955
|
+
* their Types. When decoding or encoding changes an entry, the constructed
|
|
3956
|
+
* Record uses a `null` prototype so keys such as `__proto__` remain ordinary
|
|
3957
|
+
* data.
|
|
3608
3958
|
*
|
|
3609
3959
|
* If transformed keys collide, validation fails instead of overwriting an
|
|
3610
3960
|
* entry.
|
|
@@ -3613,6 +3963,8 @@ const isPlainObject = (value) => {
|
|
|
3613
3963
|
*
|
|
3614
3964
|
* ```ts
|
|
3615
3965
|
* import {
|
|
3966
|
+
* assertOk,
|
|
3967
|
+
* assertType,
|
|
3616
3968
|
* Int64FromInt64String,
|
|
3617
3969
|
* String,
|
|
3618
3970
|
* record,
|
|
@@ -3628,21 +3980,22 @@ const isPlainObject = (value) => {
|
|
|
3628
3980
|
* grace: "20",
|
|
3629
3981
|
* });
|
|
3630
3982
|
*
|
|
3631
|
-
*
|
|
3983
|
+
* assertOk(scoresFromUnknown, { ada: 10n, grace: 20n });
|
|
3632
3984
|
*
|
|
3633
3985
|
* // Validate keys and values with their root Types.
|
|
3634
3986
|
* const scoresInput = ScoresByUser.parent.fromUnknown({
|
|
3635
3987
|
* ada: "10",
|
|
3636
3988
|
* grace: "20",
|
|
3637
3989
|
* });
|
|
3638
|
-
*
|
|
3990
|
+
* assertOk(scoresInput, { ada: "10", grace: "20" });
|
|
3639
3991
|
*
|
|
3640
3992
|
* // Run the remaining key and value stages.
|
|
3641
3993
|
* const scoresFromInput = ScoresByUser.from.parent(scoresInput.value);
|
|
3642
3994
|
*
|
|
3643
|
-
*
|
|
3644
|
-
*
|
|
3645
|
-
* Readonly<Partial<Record<string, Int64
|
|
3995
|
+
* assertOk(scoresFromInput, { ada: 10n, grace: 20n });
|
|
3996
|
+
* assertType<
|
|
3997
|
+
* Readonly<Partial<Record<string, Int64>>>,
|
|
3998
|
+
* typeof scoresFromInput.value
|
|
3646
3999
|
* >();
|
|
3647
4000
|
* ```
|
|
3648
4001
|
*
|
|
@@ -3651,34 +4004,64 @@ const isPlainObject = (value) => {
|
|
|
3651
4004
|
* as a Record entry:
|
|
3652
4005
|
*
|
|
3653
4006
|
* ```ts
|
|
4007
|
+
* import {
|
|
4008
|
+
* assertEqual,
|
|
4009
|
+
* assertErr,
|
|
4010
|
+
* assertTrue,
|
|
4011
|
+
* trySync,
|
|
4012
|
+
* } from "@evolu/common";
|
|
4013
|
+
*
|
|
3654
4014
|
* type Values = Partial<Record<"toString", number>>;
|
|
3655
4015
|
* const values: Values = {};
|
|
3656
4016
|
*
|
|
3657
4017
|
* // TypeScript treats the inherited function as `number | undefined`.
|
|
3658
4018
|
* const value: number | undefined = values.toString;
|
|
4019
|
+
* const valueType = typeof value;
|
|
3659
4020
|
*
|
|
3660
|
-
*
|
|
3661
|
-
*
|
|
3662
|
-
*
|
|
3663
|
-
*
|
|
4021
|
+
* assertEqual(valueType, "function");
|
|
4022
|
+
* const called = trySync(
|
|
4023
|
+
* () => {
|
|
4024
|
+
* if (value !== undefined) value.toFixed(0);
|
|
4025
|
+
* },
|
|
4026
|
+
* (error) => error,
|
|
4027
|
+
* );
|
|
4028
|
+
* assertErr(called);
|
|
4029
|
+
* assertTrue(called.error instanceof TypeError);
|
|
3664
4030
|
* ```
|
|
3665
4031
|
*
|
|
3666
4032
|
* Evolu Record Outputs use the same TypeScript Record representation.
|
|
3667
4033
|
*
|
|
3668
4034
|
* ```ts
|
|
3669
|
-
* import {
|
|
4035
|
+
* import {
|
|
4036
|
+
* assertEqual,
|
|
4037
|
+
* assertErr,
|
|
4038
|
+
* assertOk,
|
|
4039
|
+
* assertTrue,
|
|
4040
|
+
* trySync,
|
|
4041
|
+
* Number,
|
|
4042
|
+
* literal,
|
|
4043
|
+
* record,
|
|
4044
|
+
* type Data,
|
|
4045
|
+
* } from "@evolu/common";
|
|
3670
4046
|
*
|
|
3671
4047
|
* const Values = record(literal("toString"), Number);
|
|
3672
4048
|
* const result = Values.fromUnknown({});
|
|
4049
|
+
* const emptyValues: Data = {};
|
|
3673
4050
|
*
|
|
3674
|
-
*
|
|
4051
|
+
* assertOk(result, emptyValues);
|
|
3675
4052
|
*
|
|
3676
4053
|
* const value: number | undefined = result.value.toString;
|
|
4054
|
+
* const valueType = typeof value;
|
|
3677
4055
|
*
|
|
3678
|
-
*
|
|
3679
|
-
*
|
|
3680
|
-
*
|
|
3681
|
-
*
|
|
4056
|
+
* assertEqual(valueType, "function");
|
|
4057
|
+
* const called = trySync(
|
|
4058
|
+
* () => {
|
|
4059
|
+
* if (value !== undefined) value.toFixed(0);
|
|
4060
|
+
* },
|
|
4061
|
+
* (error) => error,
|
|
4062
|
+
* );
|
|
4063
|
+
* assertErr(called);
|
|
4064
|
+
* assertTrue(called.error instanceof TypeError);
|
|
3682
4065
|
* ```
|
|
3683
4066
|
*
|
|
3684
4067
|
* In other words, treat Record Outputs as string-keyed data rather than calling
|
|
@@ -3865,7 +4248,13 @@ const validateRecordEntries = (input, validateKey, validateValue, options) => {
|
|
|
3865
4248
|
* ### Example
|
|
3866
4249
|
*
|
|
3867
4250
|
* ```ts
|
|
3868
|
-
* import {
|
|
4251
|
+
* import {
|
|
4252
|
+
* assertOk,
|
|
4253
|
+
* String,
|
|
4254
|
+
* object,
|
|
4255
|
+
* optional,
|
|
4256
|
+
* undefinedOr,
|
|
4257
|
+
* } from "@evolu/common";
|
|
3869
4258
|
*
|
|
3870
4259
|
* const User = object({
|
|
3871
4260
|
* name: String,
|
|
@@ -3873,8 +4262,8 @@ const validateRecordEntries = (input, validateKey, validateValue, options) => {
|
|
|
3873
4262
|
* preferredName: optional(undefinedOr(String)),
|
|
3874
4263
|
* });
|
|
3875
4264
|
*
|
|
3876
|
-
*
|
|
3877
|
-
*
|
|
4265
|
+
* assertOk(User.fromUnknown({ name: "Ada" }), { name: "Ada" });
|
|
4266
|
+
* assertOk(User.fromUnknown({ name: "Ada", preferredName: undefined }), {
|
|
3878
4267
|
* name: "Ada",
|
|
3879
4268
|
* preferredName: undefined,
|
|
3880
4269
|
* });
|
|
@@ -4231,13 +4620,20 @@ const createRecordPropertyError = (issue) => ({
|
|
|
4231
4620
|
* ### Example
|
|
4232
4621
|
*
|
|
4233
4622
|
* ```ts
|
|
4234
|
-
* import {
|
|
4623
|
+
* import {
|
|
4624
|
+
* assertFalse,
|
|
4625
|
+
* assertOk,
|
|
4626
|
+
* PositiveInt,
|
|
4627
|
+
* String,
|
|
4628
|
+
* partial,
|
|
4629
|
+
* } from "@evolu/common";
|
|
4235
4630
|
*
|
|
4236
4631
|
* const PartialUser = partial({ name: String, age: PositiveInt });
|
|
4632
|
+
* const emptyPartialUser: typeof PartialUser.Output = {};
|
|
4237
4633
|
*
|
|
4238
|
-
*
|
|
4239
|
-
*
|
|
4240
|
-
*
|
|
4634
|
+
* assertOk(PartialUser.fromUnknown({}), emptyPartialUser);
|
|
4635
|
+
* assertOk(PartialUser.fromUnknown({ name: "Ada" }), { name: "Ada" });
|
|
4636
|
+
* assertFalse(PartialUser.fromUnknown({ age: -1 }).ok);
|
|
4241
4637
|
* ```
|
|
4242
4638
|
*
|
|
4243
4639
|
* @group Objects
|
|
@@ -4264,15 +4660,20 @@ export const partial = (props, ..._validation) => {
|
|
|
4264
4660
|
* ### Example
|
|
4265
4661
|
*
|
|
4266
4662
|
* ```ts
|
|
4267
|
-
* import {
|
|
4663
|
+
* import {
|
|
4664
|
+
* assertOk,
|
|
4665
|
+
* String,
|
|
4666
|
+
* nullOr,
|
|
4667
|
+
* nullableToOptional,
|
|
4668
|
+
* } from "@evolu/common";
|
|
4268
4669
|
*
|
|
4269
4670
|
* const User = nullableToOptional({
|
|
4270
4671
|
* name: String,
|
|
4271
4672
|
* nickname: nullOr(String),
|
|
4272
4673
|
* });
|
|
4273
4674
|
*
|
|
4274
|
-
*
|
|
4275
|
-
*
|
|
4675
|
+
* assertOk(User.fromUnknown({ name: "Ada" }), { name: "Ada" });
|
|
4676
|
+
* assertOk(User.fromUnknown({ name: "Ada", nickname: null }), {
|
|
4276
4677
|
* name: "Ada",
|
|
4277
4678
|
* nickname: null,
|
|
4278
4679
|
* });
|
|
@@ -4305,12 +4706,12 @@ export const nullableToOptional = (props, ..._validation) => {
|
|
|
4305
4706
|
* ### Example
|
|
4306
4707
|
*
|
|
4307
4708
|
* ```ts
|
|
4308
|
-
* import { String, object, omit } from "@evolu/common";
|
|
4709
|
+
* import { assertOk, String, object, omit } from "@evolu/common";
|
|
4309
4710
|
*
|
|
4310
4711
|
* const User = object({ name: String, password: String });
|
|
4311
4712
|
* const PublicUser = omit(User, "password");
|
|
4312
4713
|
*
|
|
4313
|
-
*
|
|
4714
|
+
* assertOk(PublicUser.fromUnknown({ name: "Ada" }), { name: "Ada" });
|
|
4314
4715
|
* ```
|
|
4315
4716
|
*
|
|
4316
4717
|
* @group Objects
|
|
@@ -4565,7 +4966,7 @@ const lazyTypeNodes = /*#__PURE__*/ new WeakSet();
|
|
|
4565
4966
|
const assertLazyReferencesAreGuarded = (type) => {
|
|
4566
4967
|
for (;;) {
|
|
4567
4968
|
assert(!lazyTypeNodes.has(type), "A Lazy Type definition must place every Lazy Type behind a structural boundary.");
|
|
4568
|
-
if ("members" in type &&
|
|
4969
|
+
if ("members" in type && Array.isArray(type.members)) {
|
|
4569
4970
|
for (const member of type.members)
|
|
4570
4971
|
assertLazyReferencesAreGuarded(member);
|
|
4571
4972
|
}
|
|
@@ -4577,6 +4978,266 @@ const assertLazyReferencesAreGuarded = (type) => {
|
|
|
4577
4978
|
type = type.parent;
|
|
4578
4979
|
}
|
|
4579
4980
|
};
|
|
4981
|
+
const emptyDataPath =
|
|
4982
|
+
/*#__PURE__*/ globalThis.Object.freeze([]);
|
|
4983
|
+
const dataPathToArray = (path) => {
|
|
4984
|
+
if (path === null)
|
|
4985
|
+
return emptyDataPath;
|
|
4986
|
+
const keys = [];
|
|
4987
|
+
let node = path;
|
|
4988
|
+
while (node !== null) {
|
|
4989
|
+
keys.push(node.key);
|
|
4990
|
+
node = node.parent;
|
|
4991
|
+
}
|
|
4992
|
+
keys.reverse();
|
|
4993
|
+
return globalThis.Object.freeze(keys);
|
|
4994
|
+
};
|
|
4995
|
+
const dataChildPath = (parent, key) => ({ parent, key });
|
|
4996
|
+
const validateData = (value, options = firstValidationOptions) => {
|
|
4997
|
+
const work = [{ value, path: null }];
|
|
4998
|
+
const visited = new WeakSet();
|
|
4999
|
+
let issues;
|
|
5000
|
+
const addIssue = (issue) => {
|
|
5001
|
+
(issues ??= []).push(issue);
|
|
5002
|
+
return options.errors === "first";
|
|
5003
|
+
};
|
|
5004
|
+
while (work.length > 0) {
|
|
5005
|
+
const current = work.pop();
|
|
5006
|
+
const { path, value } = current;
|
|
5007
|
+
if (value === null ||
|
|
5008
|
+
value === undefined ||
|
|
5009
|
+
typeof value === "string" ||
|
|
5010
|
+
typeof value === "number" ||
|
|
5011
|
+
typeof value === "bigint" ||
|
|
5012
|
+
typeof value === "boolean") {
|
|
5013
|
+
continue;
|
|
5014
|
+
}
|
|
5015
|
+
if (typeof value !== "object") {
|
|
5016
|
+
if (addIssue({
|
|
5017
|
+
kind: "InvalidType",
|
|
5018
|
+
path: dataPathToArray(path),
|
|
5019
|
+
value,
|
|
5020
|
+
})) {
|
|
5021
|
+
break;
|
|
5022
|
+
}
|
|
5023
|
+
continue;
|
|
5024
|
+
}
|
|
5025
|
+
if (visited.has(value))
|
|
5026
|
+
continue;
|
|
5027
|
+
const children = [];
|
|
5028
|
+
const kind = getObjectKind(value);
|
|
5029
|
+
if (kind === "Array") {
|
|
5030
|
+
const array = value;
|
|
5031
|
+
for (const key of Reflect.ownKeys(array)) {
|
|
5032
|
+
if (key === "length")
|
|
5033
|
+
continue;
|
|
5034
|
+
if (typeof key === "string") {
|
|
5035
|
+
const index = globalThis.Number(key) >>> 0;
|
|
5036
|
+
if (index < array.length && globalThis.String(index) === key) {
|
|
5037
|
+
continue;
|
|
5038
|
+
}
|
|
5039
|
+
}
|
|
5040
|
+
if (addIssue({
|
|
5041
|
+
kind: "ExcessProperty",
|
|
5042
|
+
path: dataPathToArray(dataChildPath(path, key)),
|
|
5043
|
+
container: "Array",
|
|
5044
|
+
})) {
|
|
5045
|
+
break;
|
|
5046
|
+
}
|
|
5047
|
+
}
|
|
5048
|
+
if (issues !== undefined && options.errors === "first")
|
|
5049
|
+
break;
|
|
5050
|
+
for (let index = 0; index < array.length; index++) {
|
|
5051
|
+
const childPath = dataChildPath(path, index);
|
|
5052
|
+
const descriptor = globalThis.Object.getOwnPropertyDescriptor(array, index);
|
|
5053
|
+
if (descriptor === undefined) {
|
|
5054
|
+
if (addIssue({ kind: "Hole", path: dataPathToArray(childPath) })) {
|
|
5055
|
+
break;
|
|
5056
|
+
}
|
|
5057
|
+
continue;
|
|
5058
|
+
}
|
|
5059
|
+
if (!("value" in descriptor)) {
|
|
5060
|
+
if (addIssue({ kind: "Accessor", path: dataPathToArray(childPath) })) {
|
|
5061
|
+
break;
|
|
5062
|
+
}
|
|
5063
|
+
continue;
|
|
5064
|
+
}
|
|
5065
|
+
children.push({ value: descriptor.value, path: childPath });
|
|
5066
|
+
}
|
|
5067
|
+
}
|
|
5068
|
+
else if (kind === "Object") {
|
|
5069
|
+
for (const key of Reflect.ownKeys(value)) {
|
|
5070
|
+
const childPath = dataChildPath(path, key);
|
|
5071
|
+
if (typeof key === "symbol") {
|
|
5072
|
+
if (addIssue({
|
|
5073
|
+
kind: "SymbolProperty",
|
|
5074
|
+
path: dataPathToArray(childPath),
|
|
5075
|
+
})) {
|
|
5076
|
+
break;
|
|
5077
|
+
}
|
|
5078
|
+
continue;
|
|
5079
|
+
}
|
|
5080
|
+
const descriptor = globalThis.Object.getOwnPropertyDescriptor(value, key);
|
|
5081
|
+
if (!("value" in descriptor)) {
|
|
5082
|
+
if (addIssue({ kind: "Accessor", path: dataPathToArray(childPath) })) {
|
|
5083
|
+
break;
|
|
5084
|
+
}
|
|
5085
|
+
continue;
|
|
5086
|
+
}
|
|
5087
|
+
if (!descriptor.enumerable) {
|
|
5088
|
+
if (addIssue({
|
|
5089
|
+
kind: "NonEnumerable",
|
|
5090
|
+
path: dataPathToArray(childPath),
|
|
5091
|
+
})) {
|
|
5092
|
+
break;
|
|
5093
|
+
}
|
|
5094
|
+
continue;
|
|
5095
|
+
}
|
|
5096
|
+
children.push({ value: descriptor.value, path: childPath });
|
|
5097
|
+
}
|
|
5098
|
+
}
|
|
5099
|
+
else if (kind === "Date") {
|
|
5100
|
+
visited.add(value);
|
|
5101
|
+
continue;
|
|
5102
|
+
}
|
|
5103
|
+
else if (kind === "Uint8Array") {
|
|
5104
|
+
try {
|
|
5105
|
+
void globalThis.Uint8Array.prototype.values.call(value);
|
|
5106
|
+
}
|
|
5107
|
+
catch {
|
|
5108
|
+
if (addIssue({
|
|
5109
|
+
kind: "InvalidUint8Array",
|
|
5110
|
+
path: dataPathToArray(path),
|
|
5111
|
+
value: value,
|
|
5112
|
+
})) {
|
|
5113
|
+
break;
|
|
5114
|
+
}
|
|
5115
|
+
}
|
|
5116
|
+
visited.add(value);
|
|
5117
|
+
continue;
|
|
5118
|
+
}
|
|
5119
|
+
else if (kind === "Set") {
|
|
5120
|
+
for (const key of Reflect.ownKeys(value)) {
|
|
5121
|
+
if (addIssue({
|
|
5122
|
+
kind: "ExcessProperty",
|
|
5123
|
+
path: dataPathToArray(dataChildPath(path, key)),
|
|
5124
|
+
container: "Set",
|
|
5125
|
+
})) {
|
|
5126
|
+
break;
|
|
5127
|
+
}
|
|
5128
|
+
}
|
|
5129
|
+
if (issues !== undefined && options.errors === "first")
|
|
5130
|
+
break;
|
|
5131
|
+
let index = 0;
|
|
5132
|
+
for (const item of value) {
|
|
5133
|
+
children.push({ value: item, path: dataChildPath(path, index++) });
|
|
5134
|
+
}
|
|
5135
|
+
}
|
|
5136
|
+
else if (kind === "Map") {
|
|
5137
|
+
for (const key of Reflect.ownKeys(value)) {
|
|
5138
|
+
if (addIssue({
|
|
5139
|
+
kind: "ExcessProperty",
|
|
5140
|
+
path: dataPathToArray(dataChildPath(path, key)),
|
|
5141
|
+
container: "Map",
|
|
5142
|
+
})) {
|
|
5143
|
+
break;
|
|
5144
|
+
}
|
|
5145
|
+
}
|
|
5146
|
+
if (issues !== undefined && options.errors === "first")
|
|
5147
|
+
break;
|
|
5148
|
+
let index = 0;
|
|
5149
|
+
for (const [key, item] of value) {
|
|
5150
|
+
const entryPath = dataChildPath(path, index++);
|
|
5151
|
+
children.push({ value: key, path: dataChildPath(entryPath, "key") });
|
|
5152
|
+
children.push({
|
|
5153
|
+
value: item,
|
|
5154
|
+
path: dataChildPath(entryPath, "value"),
|
|
5155
|
+
});
|
|
5156
|
+
}
|
|
5157
|
+
}
|
|
5158
|
+
else if (addIssue({
|
|
5159
|
+
kind: "UnexpectedPrototype",
|
|
5160
|
+
path: dataPathToArray(path),
|
|
5161
|
+
container: "Object",
|
|
5162
|
+
value,
|
|
5163
|
+
})) {
|
|
5164
|
+
break;
|
|
5165
|
+
}
|
|
5166
|
+
if (issues !== undefined && options.errors === "first")
|
|
5167
|
+
break;
|
|
5168
|
+
visited.add(value);
|
|
5169
|
+
for (let index = children.length - 1; index >= 0; index--) {
|
|
5170
|
+
work.push(children[index]);
|
|
5171
|
+
}
|
|
5172
|
+
}
|
|
5173
|
+
return issues === undefined
|
|
5174
|
+
? ok(value)
|
|
5175
|
+
: err({
|
|
5176
|
+
type: "Data",
|
|
5177
|
+
reason: {
|
|
5178
|
+
kind: "Issues",
|
|
5179
|
+
issues: globalThis.Object.freeze(issues),
|
|
5180
|
+
},
|
|
5181
|
+
});
|
|
5182
|
+
};
|
|
5183
|
+
const getDataRuntimeTypeIssues = (error, mode) => {
|
|
5184
|
+
const dataError = error;
|
|
5185
|
+
const issues = mode === "first"
|
|
5186
|
+
? [dataError.reason.issues[0]]
|
|
5187
|
+
: dataError.reason.issues;
|
|
5188
|
+
return issues.map((issue) => ({
|
|
5189
|
+
name: "Data",
|
|
5190
|
+
error: mode === "first"
|
|
5191
|
+
? error
|
|
5192
|
+
: {
|
|
5193
|
+
type: "Data",
|
|
5194
|
+
reason: { kind: "Issues", issues: [issue] },
|
|
5195
|
+
},
|
|
5196
|
+
path: issue.path,
|
|
5197
|
+
formatError: ((error) => {
|
|
5198
|
+
const issue = error.reason.issues[0];
|
|
5199
|
+
switch (issue.kind) {
|
|
5200
|
+
case "InvalidType":
|
|
5201
|
+
return `A value ${safelyStringifyUnknownValue(issue.value)} is not Data.`;
|
|
5202
|
+
case "UnexpectedPrototype":
|
|
5203
|
+
return `A Data ${issue.container} has an unexpected prototype.`;
|
|
5204
|
+
case "Accessor":
|
|
5205
|
+
return "A Data property must be a data property. Materialize accessor values into plain data before using this Type or use a different Type.";
|
|
5206
|
+
case "NonEnumerable":
|
|
5207
|
+
return "A Data Object property must be enumerable. Remove it or use a different Type.";
|
|
5208
|
+
case "SymbolProperty":
|
|
5209
|
+
return "A Data Object property key must be a string. Remove the symbol property or use a different Type.";
|
|
5210
|
+
case "Hole":
|
|
5211
|
+
return "A Data Array element is missing.";
|
|
5212
|
+
case "InvalidUint8Array":
|
|
5213
|
+
return "A Data Uint8Array must have an attached, in-bounds ArrayBuffer.";
|
|
5214
|
+
case "ExcessProperty":
|
|
5215
|
+
return `A Data ${issue.container} must not have excess own properties. Remove the property or use a different Type.`;
|
|
5216
|
+
}
|
|
5217
|
+
}),
|
|
5218
|
+
}));
|
|
5219
|
+
};
|
|
5220
|
+
/**
|
|
5221
|
+
* Root Type for {@link Data} values.
|
|
5222
|
+
*
|
|
5223
|
+
* Validation is iterative and preserves the input identity. Cyclic and shared
|
|
5224
|
+
* data graphs are valid.
|
|
5225
|
+
*
|
|
5226
|
+
* ### Example
|
|
5227
|
+
*
|
|
5228
|
+
* ```ts
|
|
5229
|
+
* import { assertOk, assertTrue, Data } from "@evolu/common";
|
|
5230
|
+
*
|
|
5231
|
+
* const value = new Map([["count", 1]]);
|
|
5232
|
+
* const result = Data.fromUnknown(value);
|
|
5233
|
+
*
|
|
5234
|
+
* assertOk(result, value);
|
|
5235
|
+
* assertTrue(Data.is(result.value));
|
|
5236
|
+
* ```
|
|
5237
|
+
*
|
|
5238
|
+
* @group Base
|
|
5239
|
+
*/
|
|
5240
|
+
export const Data = /*#__PURE__*/ createTypeNode("Data", null, validateData, (value) => validateData(value).ok, validateData, ok, identity, getDataRuntimeTypeIssues);
|
|
4580
5241
|
const emptyJsonValuePath =
|
|
4581
5242
|
/*#__PURE__*/ globalThis.Object.freeze([]);
|
|
4582
5243
|
const jsonValuePathToArray = (path) => {
|
|
@@ -4635,7 +5296,7 @@ const validateJsonValue = (value, options = firstValidationOptions) => {
|
|
|
4635
5296
|
}
|
|
4636
5297
|
continue;
|
|
4637
5298
|
}
|
|
4638
|
-
const isArray =
|
|
5299
|
+
const isArray = Array.isArray(value);
|
|
4639
5300
|
if (!isArray && !isPlainObject(value)) {
|
|
4640
5301
|
if (addIssue({
|
|
4641
5302
|
kind: "UnexpectedPrototype",
|
|
@@ -4800,7 +5461,7 @@ const getJsonValueRuntimeTypeIssues = (error, mode) => {
|
|
|
4800
5461
|
}),
|
|
4801
5462
|
}));
|
|
4802
5463
|
};
|
|
4803
|
-
const parseJson = (value) =>
|
|
5464
|
+
const parseJson = (value) => JSON.parse(value);
|
|
4804
5465
|
const jsonToJsonValueResult = (value) => {
|
|
4805
5466
|
const result = trySync(() => parseJson(value));
|
|
4806
5467
|
return result.ok && validateJsonValue(result.value).ok
|
|
@@ -4824,7 +5485,7 @@ const stringifyJsonValue = (value) => {
|
|
|
4824
5485
|
// oxlint-disable-next-line typescript/switch-exhaustiveness-check -- JsonValue excludes the additional runtime types reported by tsgolint.
|
|
4825
5486
|
switch (typeof value) {
|
|
4826
5487
|
case "string":
|
|
4827
|
-
chunks.push(
|
|
5488
|
+
chunks.push(JSON.stringify(value));
|
|
4828
5489
|
break;
|
|
4829
5490
|
case "number":
|
|
4830
5491
|
chunks.push(globalThis.Object.is(value, -0) ? "-0" : globalThis.String(value));
|
|
@@ -4833,7 +5494,7 @@ const stringifyJsonValue = (value) => {
|
|
|
4833
5494
|
chunks.push(value ? "true" : "false");
|
|
4834
5495
|
break;
|
|
4835
5496
|
case "object": {
|
|
4836
|
-
if (
|
|
5497
|
+
if (Array.isArray(value)) {
|
|
4837
5498
|
const array = value;
|
|
4838
5499
|
chunks.push("[");
|
|
4839
5500
|
work.push({ kind: "Text", value: "]" });
|
|
@@ -4854,7 +5515,7 @@ const stringifyJsonValue = (value) => {
|
|
|
4854
5515
|
work.push({ kind: "Text", value: ":" });
|
|
4855
5516
|
work.push({
|
|
4856
5517
|
kind: "Text",
|
|
4857
|
-
value:
|
|
5518
|
+
value: JSON.stringify(key),
|
|
4858
5519
|
});
|
|
4859
5520
|
if (index > 0)
|
|
4860
5521
|
work.push({ kind: "Text", value: "," });
|
|
@@ -4871,7 +5532,12 @@ const stringifyJsonValue = (value) => {
|
|
|
4871
5532
|
* ### Example
|
|
4872
5533
|
*
|
|
4873
5534
|
* ```ts
|
|
4874
|
-
* import {
|
|
5535
|
+
* import {
|
|
5536
|
+
* assertOk,
|
|
5537
|
+
* assertType,
|
|
5538
|
+
* JsonValue,
|
|
5539
|
+
* type JsonValueInput,
|
|
5540
|
+
* } from "@evolu/common";
|
|
4875
5541
|
*
|
|
4876
5542
|
* const input: JsonValueInput = {
|
|
4877
5543
|
* name: "Ada",
|
|
@@ -4879,8 +5545,8 @@ const stringifyJsonValue = (value) => {
|
|
|
4879
5545
|
* };
|
|
4880
5546
|
* const result = JsonValue.fromUnknown(input);
|
|
4881
5547
|
*
|
|
4882
|
-
*
|
|
4883
|
-
*
|
|
5548
|
+
* assertOk(result, input);
|
|
5549
|
+
* assertType<JsonValue, typeof result.value>();
|
|
4884
5550
|
* ```
|
|
4885
5551
|
*
|
|
4886
5552
|
* @group JSON
|
|
@@ -4918,11 +5584,11 @@ export const Json = /*#__PURE__*/ brand("Json", String, (value) => {
|
|
|
4918
5584
|
* ### Example
|
|
4919
5585
|
*
|
|
4920
5586
|
* ```ts
|
|
4921
|
-
* import { Json, jsonToJsonValue } from "@evolu/common";
|
|
5587
|
+
* import { assertEqual, Json, jsonToJsonValue } from "@evolu/common";
|
|
4922
5588
|
*
|
|
4923
5589
|
* const value = jsonToJsonValue(Json.orThrow('{"name":"Ada"}'));
|
|
4924
5590
|
*
|
|
4925
|
-
*
|
|
5591
|
+
* assertEqual(value, { name: "Ada" });
|
|
4926
5592
|
* ```
|
|
4927
5593
|
*
|
|
4928
5594
|
* @group JSON
|
|
@@ -4934,11 +5600,15 @@ export const jsonToJsonValue = (value) => parseJson(value);
|
|
|
4934
5600
|
* ### Example
|
|
4935
5601
|
*
|
|
4936
5602
|
* ```ts
|
|
4937
|
-
* import {
|
|
5603
|
+
* import {
|
|
5604
|
+
* assertEqual,
|
|
5605
|
+
* jsonValueToJson,
|
|
5606
|
+
* type JsonValue,
|
|
5607
|
+
* } from "@evolu/common";
|
|
4938
5608
|
*
|
|
4939
5609
|
* const value: JsonValue = { name: "Ada" };
|
|
4940
5610
|
*
|
|
4941
|
-
*
|
|
5611
|
+
* assertEqual(jsonValueToJson(value), '{"name":"Ada"}');
|
|
4942
5612
|
* ```
|
|
4943
5613
|
*
|
|
4944
5614
|
* @group JSON
|
|
@@ -4953,12 +5623,12 @@ export const jsonValueToJson = (value) => stringifyJsonValue(JsonValue.to(value)
|
|
|
4953
5623
|
* ### Example
|
|
4954
5624
|
*
|
|
4955
5625
|
* ```ts
|
|
4956
|
-
* import { JsonValueFromJson } from "@evolu/common";
|
|
5626
|
+
* import { assertEqual, assertOk, JsonValueFromJson } from "@evolu/common";
|
|
4957
5627
|
*
|
|
4958
5628
|
* const result = JsonValueFromJson.fromUnknown('{ "name": "Ada" }');
|
|
4959
5629
|
*
|
|
4960
|
-
*
|
|
4961
|
-
*
|
|
5630
|
+
* assertOk(result, { name: "Ada" });
|
|
5631
|
+
* assertEqual(JsonValueFromJson.to(result.value), '{"name":"Ada"}');
|
|
4962
5632
|
* ```
|
|
4963
5633
|
*
|
|
4964
5634
|
* @group JSON
|
|
@@ -4979,6 +5649,8 @@ export const JsonValueFromJson = /*#__PURE__*/ transform("JsonValueFromJson", Js
|
|
|
4979
5649
|
*
|
|
4980
5650
|
* ```ts
|
|
4981
5651
|
* import {
|
|
5652
|
+
* assertEqual,
|
|
5653
|
+
* assertType,
|
|
4982
5654
|
* Age,
|
|
4983
5655
|
* NonEmptyTrimmedString100,
|
|
4984
5656
|
* json,
|
|
@@ -4999,11 +5671,13 @@ export const JsonValueFromJson = /*#__PURE__*/ transform("JsonValueFromJson", Js
|
|
|
4999
5671
|
* const user = User.orThrow({ name: "Ada", age: 37 });
|
|
5000
5672
|
* const userJson = userToUserJson(user);
|
|
5001
5673
|
*
|
|
5002
|
-
*
|
|
5003
|
-
* string & Brand<"Json"> & Brand<"UserJson"
|
|
5674
|
+
* assertType<
|
|
5675
|
+
* string & Brand<"Json"> & Brand<"UserJson">,
|
|
5676
|
+
* typeof userJson
|
|
5004
5677
|
* >();
|
|
5005
|
-
*
|
|
5006
|
-
*
|
|
5678
|
+
* assertEqual(userJson, '{"name":"Ada","age":37}');
|
|
5679
|
+
* assertEqual(UserJson.orThrow(userJson), userJson);
|
|
5680
|
+
* assertEqual(userJsonToUser(userJson), user);
|
|
5007
5681
|
* ```
|
|
5008
5682
|
*
|
|
5009
5683
|
* The supplied Type must have a JSON-compatible `CanonicalInput`. The branded
|