@evolu/common 8.4.0 → 8.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/Array.d.ts +191 -94
- package/dist/src/Array.d.ts.map +1 -1
- package/dist/src/Array.js +91 -40
- package/dist/src/Assert.d.ts +233 -30
- package/dist/src/Assert.d.ts.map +1 -1
- package/dist/src/Assert.js +151 -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/LeakDetector.d.ts.map +1 -1
- package/dist/src/LeakDetector.js +4 -1
- 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 +5 -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/Platform.js +1 -0
- 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/Relation.js +3 -3
- package/dist/src/Resource.d.ts +27 -12
- package/dist/src/Resource.d.ts.map +1 -1
- package/dist/src/Resource.js +9 -3
- package/dist/src/Result.d.ts +258 -114
- package/dist/src/Result.d.ts.map +1 -1
- package/dist/src/Result.js +74 -39
- 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 +18 -15
- package/dist/src/Store.d.ts.map +1 -1
- package/dist/src/Store.js +3 -1
- package/dist/src/String.d.ts.map +1 -1
- package/dist/src/String.js +1 -1
- package/dist/src/Task.d.ts +555 -264
- package/dist/src/Task.d.ts.map +1 -1
- package/dist/src/Task.js +278 -168
- package/dist/src/Test.d.ts +10 -4
- package/dist/src/Test.d.ts.map +1 -1
- package/dist/src/Test.js +11 -4
- package/dist/src/Time.d.ts +27 -17
- package/dist/src/Time.d.ts.map +1 -1
- package/dist/src/Time.js +25 -8
- package/dist/src/Type.d.ts +1218 -372
- package/dist/src/Type.d.ts.map +1 -1
- package/dist/src/Type.js +1056 -379
- 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/WebSocket.js +8 -0
- package/dist/src/Worker.d.ts +10 -6
- package/dist/src/Worker.d.ts.map +1 -1
- package/dist/src/Worker.js +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/Db.js +3 -2
- 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/Evolu.js +1 -0
- package/dist/src/local-first/Owner.d.ts +18 -14
- package/dist/src/local-first/Owner.d.ts.map +1 -1
- package/dist/src/local-first/Owner.js +14 -11
- package/dist/src/local-first/Protocol.d.ts +5 -5
- package/dist/src/local-first/Protocol.d.ts.map +1 -1
- package/dist/src/local-first/Protocol.js +35 -29
- 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 +27 -14
- 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/Relay.js +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/Shared.d.ts.map +1 -1
- package/dist/src/local-first/Shared.js +4 -2
- package/dist/src/local-first/Storage.d.ts +4 -2
- package/dist/src/local-first/Storage.d.ts.map +1 -1
- package/dist/src/local-first/Timestamp.d.ts.map +1 -1
- package/dist/src/local-first/Timestamp.js +1 -1
- package/package.json +3 -4
- package/src/Array.ts +195 -95
- package/src/Assert.ts +337 -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/LeakDetector.ts +4 -3
- package/src/LockManager.ts +12 -10
- package/src/Lookup.ts +5 -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 +3 -2
- package/src/Random.ts +7 -4
- package/src/Redacted.ts +13 -6
- package/src/Ref.ts +4 -4
- package/src/Relation.ts +8 -10
- package/src/Resource.ts +36 -15
- package/src/Result.ts +263 -119
- package/src/Schedule.ts +233 -116
- package/src/Set.ts +43 -24
- package/src/Sqlite.ts +18 -15
- package/src/Store.ts +3 -1
- package/src/String.ts +1 -1
- package/src/Task.ts +566 -286
- package/src/Test.ts +11 -4
- package/src/Time.ts +36 -19
- package/src/Type.ts +2191 -590
- package/src/Types.ts +108 -40
- package/src/WebSocket.ts +22 -6
- package/src/Worker.ts +11 -7
- 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/Db.ts +2 -1
- package/src/local-first/Evolu.ts +48 -24
- package/src/local-first/Owner.ts +18 -14
- package/src/local-first/Protocol.ts +45 -35
- package/src/local-first/Query.ts +40 -24
- package/src/local-first/Relay.ts +13 -6
- package/src/local-first/Schema.ts +19 -11
- package/src/local-first/Shared.ts +4 -2
- package/src/local-first/Storage.ts +4 -2
- package/src/local-first/Timestamp.ts +1 -1
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
|
*
|
|
@@ -431,9 +453,10 @@ import { utf8ToBytes } from "@noble/ciphers/utils.js";
|
|
|
431
453
|
import { sha256 } from "@noble/hashes/sha2.js";
|
|
432
454
|
import * as bip39 from "@scure/bip39";
|
|
433
455
|
import { wordlist } from "@scure/bip39/wordlists/english.js";
|
|
456
|
+
import { createMutableArray, } from "./Array.js";
|
|
434
457
|
import { assert, assertNonNullable } from "./Assert.js";
|
|
435
458
|
import { identity } from "./Function.js";
|
|
436
|
-
import { createMutableRecord } from "./Object.js";
|
|
459
|
+
import { createMutableRecord, getObjectKind, isPlainObject } from "./Object.js";
|
|
437
460
|
import { hasNodeBuffer } from "./Platform.js";
|
|
438
461
|
import { err, flatMapResult, getOk, getOrNull, getOrThrow, ok, trySync, } from "./Result.js";
|
|
439
462
|
import { safelyStringifyUnknownValue } from "./String.js";
|
|
@@ -470,33 +493,9 @@ const createCollectionRuntimeTypeIssues = (name, issuesKind, defaultFormatter, g
|
|
|
470
493
|
});
|
|
471
494
|
};
|
|
472
495
|
const formatDefaultRuntimeTypeIssue = (issue) => issue.formatError(issue.error);
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
* Use this for internal invariants, not external input. Validate external input
|
|
477
|
-
* with `Type.fromUnknown` so validation failures remain typed values. A failed
|
|
478
|
-
* assertion uses the Type name for its message and preserves the exact Output
|
|
479
|
-
* validation error as the thrown Error's cause.
|
|
480
|
-
*
|
|
481
|
-
* ### Example
|
|
482
|
-
*
|
|
483
|
-
* ```ts
|
|
484
|
-
* import {
|
|
485
|
-
* NonEmptyTrimmedString100,
|
|
486
|
-
* assertType,
|
|
487
|
-
* type Brand,
|
|
488
|
-
* } from "@evolu/common";
|
|
489
|
-
*
|
|
490
|
-
* const value: unknown = "Evolu";
|
|
491
|
-
* assertType(NonEmptyTrimmedString100, value);
|
|
492
|
-
* expectTypeOf(value).toEqualTypeOf<
|
|
493
|
-
* string & Brand<"Trimmed"> & Brand<"MinLength1"> & Brand<"MaxLength100">
|
|
494
|
-
* >();
|
|
495
|
-
* ```
|
|
496
|
-
*
|
|
497
|
-
* @group Core
|
|
498
|
-
*/
|
|
499
|
-
export const assertType = (type, value) => {
|
|
496
|
+
export function assertType(type, value) {
|
|
497
|
+
if (type === undefined)
|
|
498
|
+
return;
|
|
500
499
|
// TODO: Make assert prepend "Expected " and accept an optional third cause
|
|
501
500
|
// argument. Then use it here and migrate every other assertion to pass an
|
|
502
501
|
// expectation fragment.
|
|
@@ -505,7 +504,7 @@ export const assertType = (type, value) => {
|
|
|
505
504
|
if (!result.ok) {
|
|
506
505
|
throw new Error(`Expected ${runtimeType.name}.`, { cause: result.error });
|
|
507
506
|
}
|
|
508
|
-
}
|
|
507
|
+
}
|
|
509
508
|
const assertTypeOutput = (name, is, validateOutput, value, options = firstValidationOptions) => {
|
|
510
509
|
if (is(value))
|
|
511
510
|
return;
|
|
@@ -538,13 +537,22 @@ const assertTypeOutput = (name, is, validateOutput, value, options = firstValida
|
|
|
538
537
|
* every locale an app supports also allows language changes without a network
|
|
539
538
|
* connection.
|
|
540
539
|
*
|
|
541
|
-
* The selected Type map, locale map, and formatter maps must
|
|
542
|
-
*
|
|
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.
|
|
543
543
|
*
|
|
544
544
|
* ### Example
|
|
545
545
|
*
|
|
546
546
|
* ```ts
|
|
547
|
-
* import {
|
|
547
|
+
* import {
|
|
548
|
+
* assertEqual,
|
|
549
|
+
* Data,
|
|
550
|
+
* assertErr,
|
|
551
|
+
* assertType,
|
|
552
|
+
* String,
|
|
553
|
+
* localizeTypes,
|
|
554
|
+
* minLength,
|
|
555
|
+
* } from "@evolu/common";
|
|
548
556
|
* import { cs } from "@evolu/common/intl";
|
|
549
557
|
*
|
|
550
558
|
* const Label = minLength(1)(String);
|
|
@@ -559,13 +567,14 @@ const assertTypeOutput = (name, is, validateOutput, value, options = firstValida
|
|
|
559
567
|
* },
|
|
560
568
|
* );
|
|
561
569
|
*
|
|
562
|
-
*
|
|
563
|
-
* typeof Label
|
|
564
|
-
* >();
|
|
570
|
+
* assertType<typeof Label, typeof typesByLocale.cs.Label>();
|
|
565
571
|
*
|
|
566
572
|
* const result = typesByLocale.cs.Label.fromUnknown("");
|
|
567
|
-
*
|
|
568
|
-
*
|
|
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),
|
|
569
578
|
* "Text nesmí být prázdný.",
|
|
570
579
|
* );
|
|
571
580
|
* ```
|
|
@@ -647,9 +656,7 @@ export const localizeTypes = ((typesByName, formatErrorByTypeByLocale) => {
|
|
|
647
656
|
});
|
|
648
657
|
const getLocalizationMapKeys = (value) => {
|
|
649
658
|
const errorMessage = "localizeTypes maps must be plain objects with own enumerable string-keyed data properties.";
|
|
650
|
-
assert(value
|
|
651
|
-
const prototype = globalThis.Object.getPrototypeOf(value);
|
|
652
|
-
assert(prototype === null || globalThis.Object.getPrototypeOf(prototype) === null, errorMessage);
|
|
659
|
+
assert(isPlainObject(value), errorMessage);
|
|
653
660
|
const keys = Reflect.ownKeys(value);
|
|
654
661
|
for (const key of keys) {
|
|
655
662
|
const descriptor = globalThis.Object.getOwnPropertyDescriptor(value, key);
|
|
@@ -699,12 +706,11 @@ const localizeTypeReflection = (value, formatIssue, localizedTypeBySource) => {
|
|
|
699
706
|
value["~evolu/instance"] === "Type") {
|
|
700
707
|
return withFormatError(value, formatIssue, localizedTypeBySource);
|
|
701
708
|
}
|
|
702
|
-
if (
|
|
709
|
+
if (Array.isArray(value)) {
|
|
703
710
|
return value.map((value) => localizeTypeReflection(value, formatIssue, localizedTypeBySource));
|
|
704
711
|
}
|
|
705
|
-
if (
|
|
712
|
+
if (!isPlainObject(value))
|
|
706
713
|
return value;
|
|
707
|
-
}
|
|
708
714
|
const localized = globalThis.Object.create(globalThis.Object.getPrototypeOf(value));
|
|
709
715
|
for (const key of Reflect.ownKeys(value)) {
|
|
710
716
|
localized[key] = localizeTypeReflection(value[key], formatIssue, localizedTypeBySource);
|
|
@@ -847,7 +853,7 @@ const createToOperation = (parent, own) => {
|
|
|
847
853
|
return to;
|
|
848
854
|
};
|
|
849
855
|
function getTerminalRuntimeNode(node) {
|
|
850
|
-
while (node.parent)
|
|
856
|
+
while (node.parent != null)
|
|
851
857
|
node = node.parent;
|
|
852
858
|
return node;
|
|
853
859
|
}
|
|
@@ -945,16 +951,20 @@ const createTypeOfType = (name) => {
|
|
|
945
951
|
* and significant whitespace:
|
|
946
952
|
*
|
|
947
953
|
* ```ts
|
|
948
|
-
* import {
|
|
954
|
+
* import {
|
|
955
|
+
* assertOk,
|
|
956
|
+
* assertType,
|
|
957
|
+
* String,
|
|
958
|
+
* maxLength,
|
|
959
|
+
* type Brand,
|
|
960
|
+
* } from "@evolu/common";
|
|
949
961
|
*
|
|
950
962
|
* const WireValue100 = maxLength(100)(String);
|
|
951
963
|
* type WireValue100 = typeof WireValue100.Output;
|
|
952
964
|
*
|
|
953
|
-
*
|
|
954
|
-
*
|
|
955
|
-
*
|
|
956
|
-
* expectOk(WireValue100.fromUnknown(""), "");
|
|
957
|
-
* expectOk(WireValue100.fromUnknown(" value "), " value ");
|
|
965
|
+
* assertType<string & Brand<"MaxLength100">, WireValue100>();
|
|
966
|
+
* assertOk(WireValue100.fromUnknown(""), "");
|
|
967
|
+
* assertOk(WireValue100.fromUnknown(" value "), " value ");
|
|
958
968
|
* ```
|
|
959
969
|
*
|
|
960
970
|
* @group String
|
|
@@ -980,7 +990,12 @@ export const String = /*#__PURE__*/ createTypeOfType("String");
|
|
|
980
990
|
*
|
|
981
991
|
* ```ts
|
|
982
992
|
* import {
|
|
993
|
+
* assertEqual,
|
|
994
|
+
* assertErr,
|
|
995
|
+
* assertOk,
|
|
996
|
+
* assertType,
|
|
983
997
|
* Age,
|
|
998
|
+
* Data,
|
|
984
999
|
* FiniteNumber,
|
|
985
1000
|
* Int,
|
|
986
1001
|
* NonNaNNumber,
|
|
@@ -990,36 +1005,41 @@ export const String = /*#__PURE__*/ createTypeOfType("String");
|
|
|
990
1005
|
* } from "@evolu/common";
|
|
991
1006
|
*
|
|
992
1007
|
* // Note how every additional constraint accumulates its Brand.
|
|
993
|
-
*
|
|
994
|
-
*
|
|
995
|
-
*
|
|
996
|
-
* >
|
|
997
|
-
*
|
|
998
|
-
* 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
|
|
999
1013
|
* >();
|
|
1000
|
-
*
|
|
1001
|
-
* number & Brand<"NonNaN"> & Brand<"Finite"> & Brand<"Int"
|
|
1014
|
+
* assertType<
|
|
1015
|
+
* number & Brand<"NonNaN"> & Brand<"Finite"> & Brand<"Int">,
|
|
1016
|
+
* typeof Int.Output
|
|
1002
1017
|
* >();
|
|
1003
|
-
*
|
|
1018
|
+
* assertType<
|
|
1004
1019
|
* number &
|
|
1005
1020
|
* Brand<"NonNaN"> &
|
|
1006
1021
|
* Brand<"Finite"> &
|
|
1007
1022
|
* Brand<"Int"> &
|
|
1008
|
-
* Brand<"NonNegative"
|
|
1023
|
+
* Brand<"NonNegative">,
|
|
1024
|
+
* typeof NonNegativeInt.Output
|
|
1009
1025
|
* >();
|
|
1010
1026
|
*
|
|
1011
|
-
*
|
|
1027
|
+
* assertType<
|
|
1012
1028
|
* number &
|
|
1013
1029
|
* Brand<"NonNaN"> &
|
|
1014
1030
|
* Brand<"Finite"> &
|
|
1015
1031
|
* Brand<"Int"> &
|
|
1016
1032
|
* Brand<"NonNegative"> &
|
|
1017
1033
|
* Brand<"LessThan200"> &
|
|
1018
|
-
* Brand<"Age"
|
|
1034
|
+
* Brand<"Age">,
|
|
1035
|
+
* typeof Age.Output
|
|
1019
1036
|
* >();
|
|
1020
1037
|
*
|
|
1021
|
-
*
|
|
1022
|
-
*
|
|
1038
|
+
* assertOk(Age.fromUnknown(122), 122);
|
|
1039
|
+
* const invalid = Age.fromUnknown(200);
|
|
1040
|
+
* assertErr(invalid);
|
|
1041
|
+
* assertType(Data, invalid.error);
|
|
1042
|
+
* assertEqual(invalid.error, {
|
|
1023
1043
|
* type: "LessThan200",
|
|
1024
1044
|
* value: 200,
|
|
1025
1045
|
* max: 200,
|
|
@@ -1116,7 +1136,7 @@ export const ArrayBuffer = /*#__PURE__*/ objectTag("ArrayBuffer");
|
|
|
1116
1136
|
* ### Example
|
|
1117
1137
|
*
|
|
1118
1138
|
* ```ts
|
|
1119
|
-
* import { instanceOf } from "@evolu/common";
|
|
1139
|
+
* import { assertFalse, assertTrue, instanceOf } from "@evolu/common";
|
|
1120
1140
|
*
|
|
1121
1141
|
* class User {
|
|
1122
1142
|
* readonly name: string;
|
|
@@ -1128,8 +1148,8 @@ export const ArrayBuffer = /*#__PURE__*/ objectTag("ArrayBuffer");
|
|
|
1128
1148
|
*
|
|
1129
1149
|
* const UserInstance = instanceOf(User);
|
|
1130
1150
|
*
|
|
1131
|
-
*
|
|
1132
|
-
*
|
|
1151
|
+
* assertTrue(UserInstance.is(new User("Ada")));
|
|
1152
|
+
* assertFalse(UserInstance.is({ name: "Ada" }));
|
|
1133
1153
|
* ```
|
|
1134
1154
|
*
|
|
1135
1155
|
* @group Base
|
|
@@ -1156,13 +1176,23 @@ export const instanceOf = (constructor) => {
|
|
|
1156
1176
|
* ### Example
|
|
1157
1177
|
*
|
|
1158
1178
|
* ```ts
|
|
1159
|
-
* import {
|
|
1179
|
+
* import {
|
|
1180
|
+
* assertEqual,
|
|
1181
|
+
* assertErr,
|
|
1182
|
+
* assertOk,
|
|
1183
|
+
* assertType,
|
|
1184
|
+
* Data,
|
|
1185
|
+
* literal,
|
|
1186
|
+
* } from "@evolu/common";
|
|
1160
1187
|
*
|
|
1161
1188
|
* const Ready = literal("ready");
|
|
1162
1189
|
*
|
|
1163
|
-
*
|
|
1164
|
-
*
|
|
1165
|
-
*
|
|
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, {
|
|
1166
1196
|
* type: "Literal",
|
|
1167
1197
|
* expected: "ready",
|
|
1168
1198
|
* value: "pending",
|
|
@@ -1257,11 +1287,11 @@ const createUnionValidation = (members, validateMember) => (value, options = fir
|
|
|
1257
1287
|
* ### Example
|
|
1258
1288
|
*
|
|
1259
1289
|
* ```ts
|
|
1260
|
-
* import { String, undefinedOr } from "@evolu/common";
|
|
1290
|
+
* import { assertOk, String, undefinedOr } from "@evolu/common";
|
|
1261
1291
|
*
|
|
1262
1292
|
* const StringOrUndefined = undefinedOr(String);
|
|
1263
1293
|
*
|
|
1264
|
-
*
|
|
1294
|
+
* assertOk(StringOrUndefined.fromUnknown(undefined), undefined);
|
|
1265
1295
|
* ```
|
|
1266
1296
|
*
|
|
1267
1297
|
* @group Unions
|
|
@@ -1273,11 +1303,11 @@ export const undefinedOr = (type) => union(type, Undefined);
|
|
|
1273
1303
|
* ### Example
|
|
1274
1304
|
*
|
|
1275
1305
|
* ```ts
|
|
1276
|
-
* import { String, nullOr } from "@evolu/common";
|
|
1306
|
+
* import { assertOk, String, nullOr } from "@evolu/common";
|
|
1277
1307
|
*
|
|
1278
1308
|
* const NullableString = nullOr(String);
|
|
1279
1309
|
*
|
|
1280
|
-
*
|
|
1310
|
+
* assertOk(NullableString.fromUnknown(null), null);
|
|
1281
1311
|
* ```
|
|
1282
1312
|
*
|
|
1283
1313
|
* @group Unions
|
|
@@ -1289,20 +1319,18 @@ export const nullOr = (type) => union(type, Null);
|
|
|
1289
1319
|
* ### Example
|
|
1290
1320
|
*
|
|
1291
1321
|
* ```ts
|
|
1292
|
-
* import { String, nullishOr } from "@evolu/common";
|
|
1322
|
+
* import { assertOk, String, nullishOr } from "@evolu/common";
|
|
1293
1323
|
*
|
|
1294
1324
|
* const NullishString = nullishOr(String);
|
|
1295
1325
|
*
|
|
1296
|
-
*
|
|
1297
|
-
*
|
|
1326
|
+
* assertOk(NullishString.fromUnknown(undefined), undefined);
|
|
1327
|
+
* assertOk(NullishString.fromUnknown(null), null);
|
|
1298
1328
|
* ```
|
|
1299
1329
|
*
|
|
1300
1330
|
* @group Unions
|
|
1301
1331
|
*/
|
|
1302
1332
|
export const nullishOr = (type) => union(type, Null, Undefined);
|
|
1303
|
-
const isRuntimeUnionTypeNode = (type) => type.name === "Union" &&
|
|
1304
|
-
"members" in type &&
|
|
1305
|
-
globalThis.Array.isArray(type.members);
|
|
1333
|
+
const isRuntimeUnionTypeNode = (type) => type.name === "Union" && "members" in type && Array.isArray(type.members);
|
|
1306
1334
|
/**
|
|
1307
1335
|
* Template literal {@link Type} that parses canonical strings into Tuples.
|
|
1308
1336
|
*
|
|
@@ -1338,7 +1366,16 @@ const isRuntimeUnionTypeNode = (type) => type.name === "Union" &&
|
|
|
1338
1366
|
* the structured data decoded from it:
|
|
1339
1367
|
*
|
|
1340
1368
|
* ```ts
|
|
1341
|
-
* import {
|
|
1369
|
+
* import {
|
|
1370
|
+
* assertFalse,
|
|
1371
|
+
* assertEqual,
|
|
1372
|
+
* assertErr,
|
|
1373
|
+
* assertOk,
|
|
1374
|
+
* assertType,
|
|
1375
|
+
* Data,
|
|
1376
|
+
* templateLiteralParser,
|
|
1377
|
+
* union,
|
|
1378
|
+
* } from "@evolu/common";
|
|
1342
1379
|
*
|
|
1343
1380
|
* const Language = union("en", "cs");
|
|
1344
1381
|
* const Region = union("US", "CZ");
|
|
@@ -1348,36 +1385,39 @@ const isRuntimeUnionTypeNode = (type) => type.name === "Union" &&
|
|
|
1348
1385
|
*
|
|
1349
1386
|
* // Output is the decoded language and region.
|
|
1350
1387
|
* type SupportedLocale = typeof SupportedLocale.Output;
|
|
1351
|
-
*
|
|
1352
|
-
* readonly ["en" | "cs", "US" | "CZ"]
|
|
1353
|
-
* >();
|
|
1388
|
+
* assertType<readonly ["en" | "cs", "US" | "CZ"], SupportedLocale>();
|
|
1354
1389
|
*
|
|
1355
1390
|
* // The parent Output is the canonical locale string.
|
|
1356
1391
|
* type SupportedLocaleLiteral = typeof SupportedLocale.parent.Output;
|
|
1357
|
-
*
|
|
1358
|
-
* "en-US" | "en-CZ" | "cs-US" | "cs-CZ"
|
|
1392
|
+
* assertType<
|
|
1393
|
+
* "en-US" | "en-CZ" | "cs-US" | "cs-CZ",
|
|
1394
|
+
* SupportedLocaleLiteral
|
|
1359
1395
|
* >();
|
|
1360
1396
|
*
|
|
1361
1397
|
* // Parse an unknown string into structured data.
|
|
1362
1398
|
* const result = SupportedLocale.fromUnknown("cs-CZ");
|
|
1363
|
-
*
|
|
1399
|
+
* assertOk(result, ["cs", "CZ"]);
|
|
1364
1400
|
* const locale = result.value;
|
|
1365
|
-
*
|
|
1366
|
-
*
|
|
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, {
|
|
1367
1407
|
* type: "TemplateLiteral",
|
|
1368
1408
|
* value: "cs/CZ",
|
|
1369
1409
|
* });
|
|
1370
1410
|
*
|
|
1371
1411
|
* // Encode structured data into its canonical string.
|
|
1372
1412
|
* const localeLiteral = SupportedLocale.to(locale);
|
|
1373
|
-
*
|
|
1374
|
-
*
|
|
1413
|
+
* assertType<SupportedLocaleLiteral, typeof localeLiteral>();
|
|
1414
|
+
* assertEqual(localeLiteral, "cs-CZ");
|
|
1375
1415
|
*
|
|
1376
1416
|
* // Validate a string configuration value.
|
|
1377
1417
|
* const configValue: unknown = "cs-CZ";
|
|
1378
|
-
*
|
|
1379
|
-
*
|
|
1380
|
-
*
|
|
1418
|
+
* assertType(SupportedLocale.parent, configValue);
|
|
1419
|
+
* assertType<SupportedLocaleLiteral, typeof configValue>();
|
|
1420
|
+
* assertFalse(SupportedLocale.parent.is("fr-CZ"));
|
|
1381
1421
|
* ```
|
|
1382
1422
|
*
|
|
1383
1423
|
* `SupportedLocale` is structured data for application code.
|
|
@@ -1390,6 +1430,8 @@ const isRuntimeUnionTypeNode = (type) => type.name === "Union" &&
|
|
|
1390
1430
|
*
|
|
1391
1431
|
* ```ts
|
|
1392
1432
|
* import {
|
|
1433
|
+
* assertEqual,
|
|
1434
|
+
* assertOk,
|
|
1393
1435
|
* NonNegativeDecimalString,
|
|
1394
1436
|
* templateLiteralParser,
|
|
1395
1437
|
* } from "@evolu/common";
|
|
@@ -1402,8 +1444,8 @@ const isRuntimeUnionTypeNode = (type) => type.name === "Union" &&
|
|
|
1402
1444
|
* // DecimalText.to requires a validated NonNegativeDecimalString.
|
|
1403
1445
|
* const zero = NonNegativeDecimalString.orThrow("0");
|
|
1404
1446
|
*
|
|
1405
|
-
*
|
|
1406
|
-
*
|
|
1447
|
+
* assertOk(DecimalText.fromUnknown("decimal:0"), [zero]);
|
|
1448
|
+
* assertEqual(DecimalText.to([zero]), "decimal:0");
|
|
1407
1449
|
* ```
|
|
1408
1450
|
*
|
|
1409
1451
|
* Capture Types (the Type arguments passed to `templateLiteralParser`) can use
|
|
@@ -1411,6 +1453,9 @@ const isRuntimeUnionTypeNode = (type) => type.name === "Union" &&
|
|
|
1411
1453
|
*
|
|
1412
1454
|
* ```ts
|
|
1413
1455
|
* import {
|
|
1456
|
+
* assertEqual,
|
|
1457
|
+
* assertOk,
|
|
1458
|
+
* assertType,
|
|
1414
1459
|
* Int64FromInt64String,
|
|
1415
1460
|
* templateLiteralParser,
|
|
1416
1461
|
* } from "@evolu/common";
|
|
@@ -1421,24 +1466,29 @@ const isRuntimeUnionTypeNode = (type) => type.name === "Union" &&
|
|
|
1421
1466
|
*
|
|
1422
1467
|
* // Decode the string into structured data.
|
|
1423
1468
|
* const result = ItemId.fromUnknown("item-42");
|
|
1424
|
-
*
|
|
1469
|
+
* assertOk(result, [42n]);
|
|
1425
1470
|
* const itemId = result.value;
|
|
1426
|
-
*
|
|
1471
|
+
* assertType<ItemId, typeof itemId>();
|
|
1427
1472
|
*
|
|
1428
1473
|
* // Encode the structured data into its canonical string.
|
|
1429
1474
|
* const itemIdLiteral = ItemId.to(itemId);
|
|
1430
|
-
*
|
|
1431
|
-
*
|
|
1475
|
+
* assertType<ItemIdLiteral, typeof itemIdLiteral>();
|
|
1476
|
+
* assertEqual(itemIdLiteral, "item-42");
|
|
1432
1477
|
*
|
|
1433
1478
|
* // TypeScript cannot prove from the literal alone that "42" is a valid Int64 encoding.
|
|
1434
1479
|
* // @ts-expect-error Validate it with ItemId.parent or create it with ItemId.to.
|
|
1435
|
-
* const
|
|
1480
|
+
* const _invalidItemIdLiteral: ItemIdLiteral = "item-42";
|
|
1436
1481
|
* ```
|
|
1437
1482
|
*
|
|
1438
1483
|
* Fixed-width captures can be adjacent:
|
|
1439
1484
|
*
|
|
1440
1485
|
* ```ts
|
|
1441
|
-
* import {
|
|
1486
|
+
* import {
|
|
1487
|
+
* assertEqual,
|
|
1488
|
+
* assertOk,
|
|
1489
|
+
* templateLiteralParser,
|
|
1490
|
+
* union,
|
|
1491
|
+
* } from "@evolu/common";
|
|
1442
1492
|
*
|
|
1443
1493
|
* const Digit = union("0", "1", "2", "3", "4", "5", "6", "7", "8", "9");
|
|
1444
1494
|
* const TwoDigits = templateLiteralParser(Digit, Digit);
|
|
@@ -1448,10 +1498,10 @@ const isRuntimeUnionTypeNode = (type) => type.name === "Union" &&
|
|
|
1448
1498
|
* const twoDigits: TwoDigits = ["4", "2"];
|
|
1449
1499
|
* const twoDigitsLiteral: TwoDigitsLiteral = "42";
|
|
1450
1500
|
* // @ts-expect-error TwoDigitsLiteral requires exactly two digits.
|
|
1451
|
-
* const
|
|
1501
|
+
* const _threeDigitsLiteral: TwoDigitsLiteral = "123";
|
|
1452
1502
|
*
|
|
1453
|
-
*
|
|
1454
|
-
*
|
|
1503
|
+
* assertOk(TwoDigits.from.parent(twoDigitsLiteral), twoDigits);
|
|
1504
|
+
* assertEqual(TwoDigits.to(twoDigits), twoDigitsLiteral);
|
|
1455
1505
|
* ```
|
|
1456
1506
|
*
|
|
1457
1507
|
* TypeScript rejects multiple variable-width captures because their encoded
|
|
@@ -1582,17 +1632,24 @@ const createTemplateLiteralParserType = (templateParts) => {
|
|
|
1582
1632
|
* ### Example
|
|
1583
1633
|
*
|
|
1584
1634
|
* ```ts
|
|
1585
|
-
* import {
|
|
1635
|
+
* import {
|
|
1636
|
+
* assertFalse,
|
|
1637
|
+
* assertOk,
|
|
1638
|
+
* assertType,
|
|
1639
|
+
* templateLiteral,
|
|
1640
|
+
* union,
|
|
1641
|
+
* } from "@evolu/common";
|
|
1586
1642
|
*
|
|
1587
1643
|
* const Language = union("en", "cs");
|
|
1588
1644
|
* const Region = union("US", "CZ");
|
|
1589
1645
|
* const Locale = templateLiteral(Language, "-", Region);
|
|
1590
1646
|
*
|
|
1591
|
-
*
|
|
1592
|
-
* "en-US" | "en-CZ" | "cs-US" | "cs-CZ"
|
|
1647
|
+
* assertType<
|
|
1648
|
+
* "en-US" | "en-CZ" | "cs-US" | "cs-CZ",
|
|
1649
|
+
* typeof Locale.Output
|
|
1593
1650
|
* >();
|
|
1594
|
-
*
|
|
1595
|
-
*
|
|
1651
|
+
* assertOk(Locale.fromUnknown("cs-CZ"), "cs-CZ");
|
|
1652
|
+
* assertFalse(Locale.is("fr-CZ"));
|
|
1596
1653
|
* ```
|
|
1597
1654
|
*
|
|
1598
1655
|
* @group Template literals
|
|
@@ -1608,7 +1665,7 @@ const compileTemplateLiteralParser = (parts) => {
|
|
|
1608
1665
|
return [part, partFraming.width];
|
|
1609
1666
|
});
|
|
1610
1667
|
return (input) => {
|
|
1611
|
-
const inputCodePoints =
|
|
1668
|
+
const inputCodePoints = Array.from(input);
|
|
1612
1669
|
const variableWidth = inputCodePoints.length - fixedPartsWidth;
|
|
1613
1670
|
if (variableWidth < 0) {
|
|
1614
1671
|
return err({ type: "TemplateLiteral", value: input });
|
|
@@ -1664,7 +1721,7 @@ const getStringTemplateLiteralFraming = (value) => {
|
|
|
1664
1721
|
const firstCodeUnit = value.charCodeAt(0);
|
|
1665
1722
|
const lastCodeUnit = value.charCodeAt(value.length - 1);
|
|
1666
1723
|
return {
|
|
1667
|
-
width:
|
|
1724
|
+
width: Array.from(value).length,
|
|
1668
1725
|
canBeEmpty: value.length === 0,
|
|
1669
1726
|
canStartWithLowSurrogate: firstCodeUnit >= 0xdc00 && firstCodeUnit <= 0xdfff,
|
|
1670
1727
|
canEndWithHighSurrogate: lastCodeUnit >= 0xd800 && lastCodeUnit <= 0xdbff,
|
|
@@ -1718,13 +1775,21 @@ export function brand(name, parent, validate, formatError) {
|
|
|
1718
1775
|
* ### Example
|
|
1719
1776
|
*
|
|
1720
1777
|
* ```ts
|
|
1721
|
-
* import {
|
|
1778
|
+
* import {
|
|
1779
|
+
* assertEqual,
|
|
1780
|
+
* assertErr,
|
|
1781
|
+
* assertOk,
|
|
1782
|
+
* assertType,
|
|
1783
|
+
* Data,
|
|
1784
|
+
* DateIso,
|
|
1785
|
+
* } from "@evolu/common";
|
|
1722
1786
|
*
|
|
1723
|
-
*
|
|
1724
|
-
*
|
|
1725
|
-
*
|
|
1726
|
-
* );
|
|
1727
|
-
*
|
|
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, {
|
|
1728
1793
|
* type: "DateIso",
|
|
1729
1794
|
* value: "2023-01-01",
|
|
1730
1795
|
* });
|
|
@@ -1741,13 +1806,13 @@ export const DateIso = /*#__PURE__*/ brand("DateIso", String, (value) => value.l
|
|
|
1741
1806
|
* ### Example
|
|
1742
1807
|
*
|
|
1743
1808
|
* ```ts
|
|
1744
|
-
* import { DateIsoFromDate } from "@evolu/common";
|
|
1809
|
+
* import { assertEqual, assertOk, DateIsoFromDate } from "@evolu/common";
|
|
1745
1810
|
*
|
|
1746
|
-
* const date = new
|
|
1811
|
+
* const date = new Date("2025-01-01T12:00:00.000Z");
|
|
1747
1812
|
* const result = DateIsoFromDate.fromUnknown(date);
|
|
1748
1813
|
*
|
|
1749
|
-
*
|
|
1750
|
-
*
|
|
1814
|
+
* assertOk(result, "2025-01-01T12:00:00.000Z");
|
|
1815
|
+
* assertEqual(DateIsoFromDate.to(result.value), date);
|
|
1751
1816
|
* ```
|
|
1752
1817
|
*
|
|
1753
1818
|
* @group String
|
|
@@ -1780,17 +1845,27 @@ export const UInt64 = /*#__PURE__*/ brand("UInt64", BigInt, (value) => globalThi
|
|
|
1780
1845
|
* ### Example
|
|
1781
1846
|
*
|
|
1782
1847
|
* ```ts
|
|
1783
|
-
* import {
|
|
1848
|
+
* import {
|
|
1849
|
+
* assertEqual,
|
|
1850
|
+
* assertErr,
|
|
1851
|
+
* assertOk,
|
|
1852
|
+
* assertType,
|
|
1853
|
+
* Data,
|
|
1854
|
+
* String,
|
|
1855
|
+
* capitalized,
|
|
1856
|
+
* type Brand,
|
|
1857
|
+
* } from "@evolu/common";
|
|
1784
1858
|
*
|
|
1785
1859
|
* const CapitalizedString = capitalized(String);
|
|
1786
1860
|
* type CapitalizedString = typeof CapitalizedString.Output;
|
|
1787
1861
|
*
|
|
1788
|
-
*
|
|
1789
|
-
* string & Brand<"Capitalized">
|
|
1790
|
-
* >();
|
|
1862
|
+
* assertType<string & Brand<"Capitalized">, CapitalizedString>();
|
|
1791
1863
|
*
|
|
1792
|
-
*
|
|
1793
|
-
*
|
|
1864
|
+
* assertOk(CapitalizedString.fromUnknown("Evolu"), "Evolu");
|
|
1865
|
+
* const invalid = CapitalizedString.fromUnknown("evolu");
|
|
1866
|
+
* assertErr(invalid);
|
|
1867
|
+
* assertType(Data, invalid.error);
|
|
1868
|
+
* assertEqual(invalid.error, {
|
|
1794
1869
|
* type: "Capitalized",
|
|
1795
1870
|
* value: "evolu",
|
|
1796
1871
|
* });
|
|
@@ -1816,11 +1891,11 @@ export const CapitalizedString = /*#__PURE__*/ capitalized(String);
|
|
|
1816
1891
|
* ### Example
|
|
1817
1892
|
*
|
|
1818
1893
|
* ```ts
|
|
1819
|
-
* import { String, trimmed } from "@evolu/common";
|
|
1894
|
+
* import { assertOk, String, trimmed } from "@evolu/common";
|
|
1820
1895
|
*
|
|
1821
1896
|
* const Trimmed = trimmed(String);
|
|
1822
1897
|
*
|
|
1823
|
-
*
|
|
1898
|
+
* assertOk(Trimmed.fromUnknown("Evolu"), "Evolu");
|
|
1824
1899
|
* ```
|
|
1825
1900
|
*
|
|
1826
1901
|
* @group String
|
|
@@ -1846,9 +1921,9 @@ export const TrimmedString = /*#__PURE__*/ trimmed(String);
|
|
|
1846
1921
|
* ### Example
|
|
1847
1922
|
*
|
|
1848
1923
|
* ```ts
|
|
1849
|
-
* import { trim } from "@evolu/common";
|
|
1924
|
+
* import { assertEqual, trim } from "@evolu/common";
|
|
1850
1925
|
*
|
|
1851
|
-
*
|
|
1926
|
+
* assertEqual(trim(" Evolu "), "Evolu");
|
|
1852
1927
|
* ```
|
|
1853
1928
|
*
|
|
1854
1929
|
* @group String
|
|
@@ -1860,13 +1935,13 @@ export const trim = (value) => value.trim();
|
|
|
1860
1935
|
* ### Example
|
|
1861
1936
|
*
|
|
1862
1937
|
* ```ts
|
|
1863
|
-
* import { String, array, minLength } from "@evolu/common";
|
|
1938
|
+
* import { assertOk, String, array, minLength } from "@evolu/common";
|
|
1864
1939
|
*
|
|
1865
1940
|
* const AtLeastThreeCharacters = minLength(3)(String);
|
|
1866
1941
|
* const AtLeastTwoItems = minLength(2)(array(String));
|
|
1867
1942
|
*
|
|
1868
|
-
*
|
|
1869
|
-
*
|
|
1943
|
+
* assertOk(AtLeastThreeCharacters.fromUnknown("abc"), "abc");
|
|
1944
|
+
* assertOk(AtLeastTwoItems.fromUnknown(["a", "b"]), ["a", "b"]);
|
|
1870
1945
|
* ```
|
|
1871
1946
|
*
|
|
1872
1947
|
* @group String
|
|
@@ -1899,13 +1974,13 @@ export const NonEmptyTrimmedString = /*#__PURE__*/ minLength(1)(TrimmedString);
|
|
|
1899
1974
|
* ### Example
|
|
1900
1975
|
*
|
|
1901
1976
|
* ```ts
|
|
1902
|
-
* import { String, array, maxLength } from "@evolu/common";
|
|
1977
|
+
* import { assertOk, String, array, maxLength } from "@evolu/common";
|
|
1903
1978
|
*
|
|
1904
1979
|
* const AtMostThreeCharacters = maxLength(3)(String);
|
|
1905
1980
|
* const AtMostTwoItems = maxLength(2)(array(String));
|
|
1906
1981
|
*
|
|
1907
|
-
*
|
|
1908
|
-
*
|
|
1982
|
+
* assertOk(AtMostThreeCharacters.fromUnknown("abc"), "abc");
|
|
1983
|
+
* assertOk(AtMostTwoItems.fromUnknown(["a", "b"]), ["a", "b"]);
|
|
1909
1984
|
* ```
|
|
1910
1985
|
*
|
|
1911
1986
|
* @group String
|
|
@@ -1935,13 +2010,13 @@ export const NonEmptyTrimmedString1000 = /*#__PURE__*/ maxLength(1000)(NonEmptyT
|
|
|
1935
2010
|
* ### Example
|
|
1936
2011
|
*
|
|
1937
2012
|
* ```ts
|
|
1938
|
-
* import { String, array, length } from "@evolu/common";
|
|
2013
|
+
* import { assertOk, String, array, length } from "@evolu/common";
|
|
1939
2014
|
*
|
|
1940
2015
|
* const ThreeCharacters = length(3)(String);
|
|
1941
2016
|
* const TwoItems = length(2)(array(String));
|
|
1942
2017
|
*
|
|
1943
|
-
*
|
|
1944
|
-
*
|
|
2018
|
+
* assertOk(ThreeCharacters.fromUnknown("abc"), "abc");
|
|
2019
|
+
* assertOk(TwoItems.fromUnknown(["a", "b"]), ["a", "b"]);
|
|
1945
2020
|
* ```
|
|
1946
2021
|
*
|
|
1947
2022
|
* @group String
|
|
@@ -1961,21 +2036,34 @@ export const length = (exact) => (parent) => {
|
|
|
1961
2036
|
* A non-empty string using the URL-safe alphabet:
|
|
1962
2037
|
*
|
|
1963
2038
|
* ```ts
|
|
1964
|
-
* import {
|
|
2039
|
+
* import {
|
|
2040
|
+
* assertEqual,
|
|
2041
|
+
* assertErr,
|
|
2042
|
+
* assertOk,
|
|
2043
|
+
* assertType,
|
|
2044
|
+
* Data,
|
|
2045
|
+
* String,
|
|
2046
|
+
* regex,
|
|
2047
|
+
* type Brand,
|
|
2048
|
+
* } from "@evolu/common";
|
|
1965
2049
|
*
|
|
1966
|
-
* const UrlSafeString = regex(
|
|
2050
|
+
* const UrlSafeString = regex(
|
|
2051
|
+
* "UrlSafeString",
|
|
2052
|
+
* /^[A-Za-z0-9_-]+$/u,
|
|
2053
|
+
* )(String);
|
|
1967
2054
|
* type UrlSafeString = typeof UrlSafeString.Output;
|
|
1968
2055
|
*
|
|
1969
|
-
*
|
|
1970
|
-
* string & Brand<"UrlSafeString">
|
|
1971
|
-
* >();
|
|
2056
|
+
* assertType<string & Brand<"UrlSafeString">, UrlSafeString>();
|
|
1972
2057
|
*
|
|
1973
|
-
*
|
|
1974
|
-
*
|
|
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, {
|
|
1975
2063
|
* type: "UrlSafeString",
|
|
1976
2064
|
* value: "not safe",
|
|
1977
2065
|
* source: "^[A-Za-z0-9_-]+$",
|
|
1978
|
-
* flags: "",
|
|
2066
|
+
* flags: "u",
|
|
1979
2067
|
* });
|
|
1980
2068
|
* ```
|
|
1981
2069
|
*
|
|
@@ -2007,7 +2095,7 @@ export const regex = (name, pattern) => {
|
|
|
2007
2095
|
*
|
|
2008
2096
|
* @group String
|
|
2009
2097
|
*/
|
|
2010
|
-
export const UrlSafeString = /*#__PURE__*/ regex("UrlSafeString", /^[A-Za-z0-9_-]+$/)(String);
|
|
2098
|
+
export const UrlSafeString = /*#__PURE__*/ regex("UrlSafeString", /^[A-Za-z0-9_-]+$/u)(String);
|
|
2011
2099
|
const base64UrlOptions = {
|
|
2012
2100
|
alphabet: "base64url",
|
|
2013
2101
|
omitPadding: true,
|
|
@@ -2020,9 +2108,9 @@ const uint8ArrayToBase64UrlString = (bytes) => {
|
|
|
2020
2108
|
if ("toBase64" in uint8ArrayPrototype) {
|
|
2021
2109
|
return bytes.toBase64(base64UrlOptions);
|
|
2022
2110
|
}
|
|
2023
|
-
const binaryString =
|
|
2024
|
-
const base64 =
|
|
2025
|
-
return base64.
|
|
2111
|
+
const binaryString = Array.from(bytes, (byte) => globalThis.String.fromCodePoint(byte)).join("");
|
|
2112
|
+
const base64 = btoa(binaryString);
|
|
2113
|
+
return base64.replaceAll("+", "-").replaceAll("/", "_").replaceAll("=", "");
|
|
2026
2114
|
};
|
|
2027
2115
|
const base64UrlStringToUint8Array = (value) => {
|
|
2028
2116
|
if (hasNodeBuffer) {
|
|
@@ -2033,10 +2121,10 @@ const base64UrlStringToUint8Array = (value) => {
|
|
|
2033
2121
|
if ("fromBase64" in uint8ArrayConstructor) {
|
|
2034
2122
|
return globalThis.Uint8Array.fromBase64(value, base64UrlOptions);
|
|
2035
2123
|
}
|
|
2036
|
-
let base64 = value.
|
|
2124
|
+
let base64 = value.replaceAll("-", "+").replaceAll("_", "/");
|
|
2037
2125
|
while (base64.length % 4 !== 0)
|
|
2038
2126
|
base64 += "=";
|
|
2039
|
-
const binaryString =
|
|
2127
|
+
const binaryString = atob(base64);
|
|
2040
2128
|
return globalThis.Uint8Array.from(binaryString, (character) => character.charCodeAt(0));
|
|
2041
2129
|
};
|
|
2042
2130
|
/**
|
|
@@ -2059,9 +2147,10 @@ export const Base64Url = /*#__PURE__*/ brand("Base64Url", String, (value) => {
|
|
|
2059
2147
|
* ### Example
|
|
2060
2148
|
*
|
|
2061
2149
|
* ```ts
|
|
2062
|
-
* import { uint8ArrayToBase64Url } from "@evolu/common";
|
|
2150
|
+
* import { assertEqual, uint8ArrayToBase64Url } from "@evolu/common";
|
|
2063
2151
|
*
|
|
2064
|
-
*
|
|
2152
|
+
* assertEqual(
|
|
2153
|
+
* uint8ArrayToBase64Url(new Uint8Array([0, 1, 2, 255])),
|
|
2065
2154
|
* "AAEC_w",
|
|
2066
2155
|
* );
|
|
2067
2156
|
* ```
|
|
@@ -2075,11 +2164,16 @@ export const uint8ArrayToBase64Url = (bytes) => uint8ArrayToBase64UrlString(byte
|
|
|
2075
2164
|
* ### Example
|
|
2076
2165
|
*
|
|
2077
2166
|
* ```ts
|
|
2078
|
-
* import {
|
|
2167
|
+
* import {
|
|
2168
|
+
* assertEqual,
|
|
2169
|
+
* Base64Url,
|
|
2170
|
+
* base64UrlToUint8Array,
|
|
2171
|
+
* } from "@evolu/common";
|
|
2079
2172
|
*
|
|
2080
2173
|
* const value = Base64Url.orThrow("AAEC_w");
|
|
2081
2174
|
*
|
|
2082
|
-
*
|
|
2175
|
+
* assertEqual(
|
|
2176
|
+
* base64UrlToUint8Array(value),
|
|
2083
2177
|
* new Uint8Array([0, 1, 2, 255]),
|
|
2084
2178
|
* );
|
|
2085
2179
|
* ```
|
|
@@ -2137,6 +2231,8 @@ export const Id = /*#__PURE__*/ brand("Id", String, (value) => value.length ===
|
|
|
2137
2231
|
*
|
|
2138
2232
|
* ```ts
|
|
2139
2233
|
* import {
|
|
2234
|
+
* assertTrue,
|
|
2235
|
+
* assertType,
|
|
2140
2236
|
* Id,
|
|
2141
2237
|
* createId,
|
|
2142
2238
|
* createRandomBytes,
|
|
@@ -2145,8 +2241,8 @@ export const Id = /*#__PURE__*/ brand("Id", String, (value) => value.length ===
|
|
|
2145
2241
|
*
|
|
2146
2242
|
* const userId = createId<"User">({ randomBytes: createRandomBytes() });
|
|
2147
2243
|
*
|
|
2148
|
-
*
|
|
2149
|
-
*
|
|
2244
|
+
* assertTrue(Id.is(userId));
|
|
2245
|
+
* assertType<Id & Brand<"User">, typeof userId>();
|
|
2150
2246
|
* ```
|
|
2151
2247
|
*
|
|
2152
2248
|
* @group String
|
|
@@ -2162,14 +2258,20 @@ export const createId = (deps, ..._validation) => uint8ArrayToBase64Url(deps.ran
|
|
|
2162
2258
|
* ### Example
|
|
2163
2259
|
*
|
|
2164
2260
|
* ```ts
|
|
2165
|
-
* import {
|
|
2261
|
+
* import {
|
|
2262
|
+
* assertEqual,
|
|
2263
|
+
* assertType,
|
|
2264
|
+
* createIdFromString,
|
|
2265
|
+
* type Brand,
|
|
2266
|
+
* type Id,
|
|
2267
|
+
* } from "@evolu/common";
|
|
2166
2268
|
*
|
|
2167
2269
|
* const first = createIdFromString("external-user-123");
|
|
2168
2270
|
* const second = createIdFromString("external-user-123");
|
|
2169
2271
|
* const todoId = createIdFromString<"Todo">("external-todo-456");
|
|
2170
2272
|
*
|
|
2171
|
-
*
|
|
2172
|
-
*
|
|
2273
|
+
* assertEqual(first, second);
|
|
2274
|
+
* assertType<Id & Brand<"Todo">, typeof todoId>();
|
|
2173
2275
|
* ```
|
|
2174
2276
|
*
|
|
2175
2277
|
* @group String
|
|
@@ -2187,6 +2289,7 @@ export const createIdFromString = (value, ..._validation) => idBytesToId(sha256(
|
|
|
2187
2289
|
*
|
|
2188
2290
|
* ```ts
|
|
2189
2291
|
* import {
|
|
2292
|
+
* assertEqual,
|
|
2190
2293
|
* createIdAsUuidv7,
|
|
2191
2294
|
* createRandomBytes,
|
|
2192
2295
|
* createTime,
|
|
@@ -2199,8 +2302,8 @@ export const createIdFromString = (value, ..._validation) => idBytesToId(sha256(
|
|
|
2199
2302
|
* });
|
|
2200
2303
|
* const bytes = idToIdBytes(value);
|
|
2201
2304
|
*
|
|
2202
|
-
*
|
|
2203
|
-
*
|
|
2305
|
+
* assertEqual(bytes[6] >> 4, 0x7);
|
|
2306
|
+
* assertEqual(bytes[8] & 0xc0, 0x80);
|
|
2204
2307
|
* ```
|
|
2205
2308
|
*
|
|
2206
2309
|
* @group String
|
|
@@ -2225,6 +2328,7 @@ export const createIdAsUuidv7 = (deps, ..._validation) => {
|
|
|
2225
2328
|
*
|
|
2226
2329
|
* ```ts
|
|
2227
2330
|
* import {
|
|
2331
|
+
* assertType,
|
|
2228
2332
|
* createIdFromString,
|
|
2229
2333
|
* id,
|
|
2230
2334
|
* type Brand,
|
|
@@ -2234,7 +2338,7 @@ export const createIdAsUuidv7 = (deps, ..._validation) => {
|
|
|
2234
2338
|
* const TodoId = id("Todo");
|
|
2235
2339
|
* const todoId = TodoId.orThrow(createIdFromString("todo"));
|
|
2236
2340
|
*
|
|
2237
|
-
*
|
|
2341
|
+
* assertType<Id & Brand<"Todo">, typeof todoId>();
|
|
2238
2342
|
* ```
|
|
2239
2343
|
*
|
|
2240
2344
|
* @group String
|
|
@@ -2264,11 +2368,15 @@ export const idBytesTypeValueLength = 16;
|
|
|
2264
2368
|
* ### Example
|
|
2265
2369
|
*
|
|
2266
2370
|
* ```ts
|
|
2267
|
-
* import {
|
|
2371
|
+
* import {
|
|
2372
|
+
* assertEqual,
|
|
2373
|
+
* createIdFromString,
|
|
2374
|
+
* idToIdBytes,
|
|
2375
|
+
* } from "@evolu/common";
|
|
2268
2376
|
*
|
|
2269
2377
|
* const bytes = idToIdBytes(createIdFromString("todo"));
|
|
2270
2378
|
*
|
|
2271
|
-
*
|
|
2379
|
+
* assertEqual(bytes.length, 16);
|
|
2272
2380
|
* ```
|
|
2273
2381
|
*
|
|
2274
2382
|
* @group String
|
|
@@ -2281,6 +2389,7 @@ export const idToIdBytes = (value) => base64UrlToUint8Array(value);
|
|
|
2281
2389
|
*
|
|
2282
2390
|
* ```ts
|
|
2283
2391
|
* import {
|
|
2392
|
+
* assertEqual,
|
|
2284
2393
|
* createIdFromString,
|
|
2285
2394
|
* idBytesToId,
|
|
2286
2395
|
* idToIdBytes,
|
|
@@ -2288,7 +2397,7 @@ export const idToIdBytes = (value) => base64UrlToUint8Array(value);
|
|
|
2288
2397
|
*
|
|
2289
2398
|
* const value = createIdFromString("todo");
|
|
2290
2399
|
*
|
|
2291
|
-
*
|
|
2400
|
+
* assertEqual(idBytesToId(idToIdBytes(value)), value);
|
|
2292
2401
|
* ```
|
|
2293
2402
|
*
|
|
2294
2403
|
* @group String
|
|
@@ -2302,7 +2411,7 @@ export const idBytesToId = (value) => uint8ArrayToBase64Url(value);
|
|
|
2302
2411
|
export const Int64String = /*#__PURE__*/ brand("Int64String", NonEmptyTrimmedString, (value) => {
|
|
2303
2412
|
const negative = value.startsWith("-");
|
|
2304
2413
|
if (value.length > (negative ? 20 : 19) ||
|
|
2305
|
-
!/^(?:0|-?[1-9]\d*)
|
|
2414
|
+
!/^(?:0|-?[1-9]\d*)$/u.test(value)) {
|
|
2306
2415
|
return err({ type: "Int64String", value });
|
|
2307
2416
|
}
|
|
2308
2417
|
const digits = negative ? value.slice(1) : value;
|
|
@@ -2321,12 +2430,17 @@ export const Int64String = /*#__PURE__*/ brand("Int64String", NonEmptyTrimmedStr
|
|
|
2321
2430
|
* ### Example
|
|
2322
2431
|
*
|
|
2323
2432
|
* ```ts
|
|
2324
|
-
* import {
|
|
2433
|
+
* import {
|
|
2434
|
+
* assertEqual,
|
|
2435
|
+
* assertOk,
|
|
2436
|
+
* Int64FromInt64String,
|
|
2437
|
+
* } from "@evolu/common";
|
|
2325
2438
|
*
|
|
2326
2439
|
* const result = Int64FromInt64String.fromUnknown("9223372036854775807");
|
|
2327
2440
|
*
|
|
2328
|
-
*
|
|
2329
|
-
*
|
|
2441
|
+
* assertOk(result, 9223372036854775807n);
|
|
2442
|
+
* assertEqual(
|
|
2443
|
+
* Int64FromInt64String.to(result.value),
|
|
2330
2444
|
* "9223372036854775807",
|
|
2331
2445
|
* );
|
|
2332
2446
|
* ```
|
|
@@ -2343,11 +2457,11 @@ export const Int64FromInt64String = /*#__PURE__*/ transform("Int64FromInt64Strin
|
|
|
2343
2457
|
* ### Example
|
|
2344
2458
|
*
|
|
2345
2459
|
* ```ts
|
|
2346
|
-
* import { Number, nonNegative } from "@evolu/common";
|
|
2460
|
+
* import { assertOk, Number, nonNegative } from "@evolu/common";
|
|
2347
2461
|
*
|
|
2348
2462
|
* const NonNegative = nonNegative(Number);
|
|
2349
2463
|
*
|
|
2350
|
-
*
|
|
2464
|
+
* assertOk(NonNegative.fromUnknown(0), 0);
|
|
2351
2465
|
* ```
|
|
2352
2466
|
*
|
|
2353
2467
|
* @group Number
|
|
@@ -2365,11 +2479,11 @@ export const NonNegativeNumber = /*#__PURE__*/ nonNegative(Number);
|
|
|
2365
2479
|
* ### Example
|
|
2366
2480
|
*
|
|
2367
2481
|
* ```ts
|
|
2368
|
-
* import { Number, positive } from "@evolu/common";
|
|
2482
|
+
* import { assertOk, Number, positive } from "@evolu/common";
|
|
2369
2483
|
*
|
|
2370
2484
|
* const Positive = positive(Number);
|
|
2371
2485
|
*
|
|
2372
|
-
*
|
|
2486
|
+
* assertOk(Positive.fromUnknown(1), 1);
|
|
2373
2487
|
* ```
|
|
2374
2488
|
*
|
|
2375
2489
|
* @group Number
|
|
@@ -2390,11 +2504,11 @@ export const PositiveNumber = /*#__PURE__*/ positive(NonNegativeNumber);
|
|
|
2390
2504
|
* ### Example
|
|
2391
2505
|
*
|
|
2392
2506
|
* ```ts
|
|
2393
|
-
* import { Number, nonPositive } from "@evolu/common";
|
|
2507
|
+
* import { assertOk, Number, nonPositive } from "@evolu/common";
|
|
2394
2508
|
*
|
|
2395
2509
|
* const NonPositive = nonPositive(Number);
|
|
2396
2510
|
*
|
|
2397
|
-
*
|
|
2511
|
+
* assertOk(NonPositive.fromUnknown(0), 0);
|
|
2398
2512
|
* ```
|
|
2399
2513
|
*
|
|
2400
2514
|
* @group Number
|
|
@@ -2412,11 +2526,11 @@ export const NonPositiveNumber = /*#__PURE__*/ nonPositive(Number);
|
|
|
2412
2526
|
* ### Example
|
|
2413
2527
|
*
|
|
2414
2528
|
* ```ts
|
|
2415
|
-
* import { Number, negative } from "@evolu/common";
|
|
2529
|
+
* import { assertOk, Number, negative } from "@evolu/common";
|
|
2416
2530
|
*
|
|
2417
2531
|
* const Negative = negative(Number);
|
|
2418
2532
|
*
|
|
2419
|
-
*
|
|
2533
|
+
* assertOk(Negative.fromUnknown(-1), -1);
|
|
2420
2534
|
* ```
|
|
2421
2535
|
*
|
|
2422
2536
|
* @group Number
|
|
@@ -2437,11 +2551,11 @@ export const NegativeNumber = /*#__PURE__*/ negative(NonPositiveNumber);
|
|
|
2437
2551
|
* ### Example
|
|
2438
2552
|
*
|
|
2439
2553
|
* ```ts
|
|
2440
|
-
* import { Number, nonNaN } from "@evolu/common";
|
|
2554
|
+
* import { assertOk, Number, nonNaN } from "@evolu/common";
|
|
2441
2555
|
*
|
|
2442
2556
|
* const NonNaN = nonNaN(Number);
|
|
2443
2557
|
*
|
|
2444
|
-
*
|
|
2558
|
+
* assertOk(NonNaN.fromUnknown(Infinity), Infinity);
|
|
2445
2559
|
* ```
|
|
2446
2560
|
*
|
|
2447
2561
|
* @group Number
|
|
@@ -2466,11 +2580,11 @@ export const NonNaNNumber = /*#__PURE__*/ nonNaN(Number);
|
|
|
2466
2580
|
* ### Example
|
|
2467
2581
|
*
|
|
2468
2582
|
* ```ts
|
|
2469
|
-
* import { Number, finite } from "@evolu/common";
|
|
2583
|
+
* import { assertOk, Number, finite } from "@evolu/common";
|
|
2470
2584
|
*
|
|
2471
2585
|
* const Finite = finite(Number);
|
|
2472
2586
|
*
|
|
2473
|
-
*
|
|
2587
|
+
* assertOk(Finite.fromUnknown(42), 42);
|
|
2474
2588
|
* ```
|
|
2475
2589
|
*
|
|
2476
2590
|
* @group Number
|
|
@@ -2505,15 +2619,28 @@ export const PositiveFiniteNumber = /*#__PURE__*/ positive(NonNegativeFiniteNumb
|
|
|
2505
2619
|
* ### Example
|
|
2506
2620
|
*
|
|
2507
2621
|
* ```ts
|
|
2508
|
-
* import {
|
|
2622
|
+
* import {
|
|
2623
|
+
* assertEqual,
|
|
2624
|
+
* assertErr,
|
|
2625
|
+
* assertOk,
|
|
2626
|
+
* assertType,
|
|
2627
|
+
* Data,
|
|
2628
|
+
* Number,
|
|
2629
|
+
* int,
|
|
2630
|
+
* type Brand,
|
|
2631
|
+
* } from "@evolu/common";
|
|
2509
2632
|
*
|
|
2510
2633
|
* const Int = int(Number);
|
|
2511
2634
|
* type Int = typeof Int.Output;
|
|
2512
2635
|
*
|
|
2513
|
-
*
|
|
2636
|
+
* assertType<number & Brand<"Int">, Int>();
|
|
2514
2637
|
*
|
|
2515
|
-
*
|
|
2516
|
-
*
|
|
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 });
|
|
2517
2644
|
* ```
|
|
2518
2645
|
*
|
|
2519
2646
|
* @group Number
|
|
@@ -2582,11 +2709,11 @@ export const NegativeInt = /*#__PURE__*/ negative(NonPositiveInt);
|
|
|
2582
2709
|
* ### Example
|
|
2583
2710
|
*
|
|
2584
2711
|
* ```ts
|
|
2585
|
-
* import { Number, greaterThan } from "@evolu/common";
|
|
2712
|
+
* import { assertOk, Number, greaterThan } from "@evolu/common";
|
|
2586
2713
|
*
|
|
2587
2714
|
* const GreaterThanTen = greaterThan(10)(Number);
|
|
2588
2715
|
*
|
|
2589
|
-
*
|
|
2716
|
+
* assertOk(GreaterThanTen.fromUnknown(11), 11);
|
|
2590
2717
|
* ```
|
|
2591
2718
|
*
|
|
2592
2719
|
* @group Number
|
|
@@ -2603,11 +2730,11 @@ export const greaterThan = (min) => (parent) => {
|
|
|
2603
2730
|
* ### Example
|
|
2604
2731
|
*
|
|
2605
2732
|
* ```ts
|
|
2606
|
-
* import { Number, greaterThanOrEqualTo } from "@evolu/common";
|
|
2733
|
+
* import { assertOk, Number, greaterThanOrEqualTo } from "@evolu/common";
|
|
2607
2734
|
*
|
|
2608
2735
|
* const AtLeastTen = greaterThanOrEqualTo(10)(Number);
|
|
2609
2736
|
*
|
|
2610
|
-
*
|
|
2737
|
+
* assertOk(AtLeastTen.fromUnknown(10), 10);
|
|
2611
2738
|
* ```
|
|
2612
2739
|
*
|
|
2613
2740
|
* @group Number
|
|
@@ -2628,11 +2755,11 @@ export const greaterThanOrEqualTo = (min) => (parent) => {
|
|
|
2628
2755
|
* ### Example
|
|
2629
2756
|
*
|
|
2630
2757
|
* ```ts
|
|
2631
|
-
* import { Number, lessThan } from "@evolu/common";
|
|
2758
|
+
* import { assertOk, Number, lessThan } from "@evolu/common";
|
|
2632
2759
|
*
|
|
2633
2760
|
* const LessThanTen = lessThan(10)(Number);
|
|
2634
2761
|
*
|
|
2635
|
-
*
|
|
2762
|
+
* assertOk(LessThanTen.fromUnknown(9), 9);
|
|
2636
2763
|
* ```
|
|
2637
2764
|
*
|
|
2638
2765
|
* @group Number
|
|
@@ -2656,11 +2783,11 @@ export const Age = /*#__PURE__*/ brand("Age",
|
|
|
2656
2783
|
* ### Example
|
|
2657
2784
|
*
|
|
2658
2785
|
* ```ts
|
|
2659
|
-
* import { Number, lessThanOrEqualTo } from "@evolu/common";
|
|
2786
|
+
* import { assertOk, Number, lessThanOrEqualTo } from "@evolu/common";
|
|
2660
2787
|
*
|
|
2661
2788
|
* const AtMostTen = lessThanOrEqualTo(10)(Number);
|
|
2662
2789
|
*
|
|
2663
|
-
*
|
|
2790
|
+
* assertOk(AtMostTen.fromUnknown(10), 10);
|
|
2664
2791
|
* ```
|
|
2665
2792
|
*
|
|
2666
2793
|
* @group Number
|
|
@@ -2707,13 +2834,23 @@ export const Ratio = /*#__PURE__*/ brand("Ratio",
|
|
|
2707
2834
|
* ### Example
|
|
2708
2835
|
*
|
|
2709
2836
|
* ```ts
|
|
2710
|
-
* import {
|
|
2837
|
+
* import {
|
|
2838
|
+
* assertEqual,
|
|
2839
|
+
* assertErr,
|
|
2840
|
+
* assertOk,
|
|
2841
|
+
* assertType,
|
|
2842
|
+
* Data,
|
|
2843
|
+
* DecimalString,
|
|
2844
|
+
* } from "@evolu/common";
|
|
2711
2845
|
*
|
|
2712
|
-
*
|
|
2713
|
-
*
|
|
2714
|
-
*
|
|
2846
|
+
* assertOk(DecimalString.fromUnknown("-10.25"), "-10.25");
|
|
2847
|
+
* assertOk(DecimalString.fromUnknown("0"), "0");
|
|
2848
|
+
* assertOk(DecimalString.fromUnknown("10.25"), "10.25");
|
|
2715
2849
|
*
|
|
2716
|
-
*
|
|
2850
|
+
* const invalid = DecimalString.fromUnknown("10.250");
|
|
2851
|
+
* assertErr(invalid);
|
|
2852
|
+
* assertType(Data, invalid.error);
|
|
2853
|
+
* assertEqual(invalid.error, {
|
|
2717
2854
|
* type: "DecimalString",
|
|
2718
2855
|
* value: "10.250",
|
|
2719
2856
|
* });
|
|
@@ -2721,7 +2858,7 @@ export const Ratio = /*#__PURE__*/ brand("Ratio",
|
|
|
2721
2858
|
*
|
|
2722
2859
|
* @group Number
|
|
2723
2860
|
*/
|
|
2724
|
-
export const DecimalString = /*#__PURE__*/ brand("DecimalString", String, (value) => /^(?:0|-?(?:[1-9]\d*|(?:0|[1-9]\d*)\.\d*[1-9]))
|
|
2861
|
+
export const DecimalString = /*#__PURE__*/ brand("DecimalString", String, (value) => /^(?:0|-?(?:[1-9]\d*|(?:0|[1-9]\d*)\.\d*[1-9]))$/u.test(value)
|
|
2725
2862
|
? ok()
|
|
2726
2863
|
: err({ type: "DecimalString", value }), (error) => `The value ${safelyStringifyUnknownValue(error.value)} must be a canonical decimal string.`);
|
|
2727
2864
|
/**
|
|
@@ -2730,11 +2867,15 @@ export const DecimalString = /*#__PURE__*/ brand("DecimalString", String, (value
|
|
|
2730
2867
|
* ### Example
|
|
2731
2868
|
*
|
|
2732
2869
|
* ```ts
|
|
2733
|
-
* import {
|
|
2870
|
+
* import {
|
|
2871
|
+
* assertOk,
|
|
2872
|
+
* DecimalString,
|
|
2873
|
+
* nonNegativeDecimalString,
|
|
2874
|
+
* } from "@evolu/common";
|
|
2734
2875
|
*
|
|
2735
2876
|
* const NonNegative = nonNegativeDecimalString(DecimalString);
|
|
2736
2877
|
*
|
|
2737
|
-
*
|
|
2878
|
+
* assertOk(NonNegative.fromUnknown("0.5"), "0.5");
|
|
2738
2879
|
* ```
|
|
2739
2880
|
*
|
|
2740
2881
|
* @group Number
|
|
@@ -2758,11 +2899,15 @@ export const NonNegativeDecimalString =
|
|
|
2758
2899
|
* ### Example
|
|
2759
2900
|
*
|
|
2760
2901
|
* ```ts
|
|
2761
|
-
* import {
|
|
2902
|
+
* import {
|
|
2903
|
+
* assertOk,
|
|
2904
|
+
* DecimalString,
|
|
2905
|
+
* positiveDecimalString,
|
|
2906
|
+
* } from "@evolu/common";
|
|
2762
2907
|
*
|
|
2763
2908
|
* const Positive = positiveDecimalString(DecimalString);
|
|
2764
2909
|
*
|
|
2765
|
-
*
|
|
2910
|
+
* assertOk(Positive.fromUnknown("0.5"), "0.5");
|
|
2766
2911
|
* ```
|
|
2767
2912
|
*
|
|
2768
2913
|
* @group Number
|
|
@@ -2788,11 +2933,15 @@ export const PositiveDecimalString = /*#__PURE__*/ positiveDecimalString(NonNega
|
|
|
2788
2933
|
* ### Example
|
|
2789
2934
|
*
|
|
2790
2935
|
* ```ts
|
|
2791
|
-
* import {
|
|
2936
|
+
* import {
|
|
2937
|
+
* assertOk,
|
|
2938
|
+
* DecimalString,
|
|
2939
|
+
* nonPositiveDecimalString,
|
|
2940
|
+
* } from "@evolu/common";
|
|
2792
2941
|
*
|
|
2793
2942
|
* const NonPositive = nonPositiveDecimalString(DecimalString);
|
|
2794
2943
|
*
|
|
2795
|
-
*
|
|
2944
|
+
* assertOk(NonPositive.fromUnknown("-0.5"), "-0.5");
|
|
2796
2945
|
* ```
|
|
2797
2946
|
*
|
|
2798
2947
|
* @group Number
|
|
@@ -2816,11 +2965,15 @@ export const NonPositiveDecimalString =
|
|
|
2816
2965
|
* ### Example
|
|
2817
2966
|
*
|
|
2818
2967
|
* ```ts
|
|
2819
|
-
* import {
|
|
2968
|
+
* import {
|
|
2969
|
+
* assertOk,
|
|
2970
|
+
* DecimalString,
|
|
2971
|
+
* negativeDecimalString,
|
|
2972
|
+
* } from "@evolu/common";
|
|
2820
2973
|
*
|
|
2821
2974
|
* const Negative = negativeDecimalString(DecimalString);
|
|
2822
2975
|
*
|
|
2823
|
-
*
|
|
2976
|
+
* assertOk(Negative.fromUnknown("-0.5"), "-0.5");
|
|
2824
2977
|
* ```
|
|
2825
2978
|
*
|
|
2826
2979
|
* @group Number
|
|
@@ -2854,16 +3007,29 @@ export const NegativeDecimalString = /*#__PURE__*/ negativeDecimalString(NonPosi
|
|
|
2854
3007
|
* ### Example
|
|
2855
3008
|
*
|
|
2856
3009
|
* ```ts
|
|
2857
|
-
* import {
|
|
3010
|
+
* import {
|
|
3011
|
+
* assertEqual,
|
|
3012
|
+
* assertErr,
|
|
3013
|
+
* assertOk,
|
|
3014
|
+
* assertType,
|
|
3015
|
+
* Data,
|
|
3016
|
+
* FiniteNumber,
|
|
3017
|
+
* multipleOf,
|
|
3018
|
+
* type Brand,
|
|
3019
|
+
* } from "@evolu/common";
|
|
2858
3020
|
*
|
|
2859
3021
|
* const Tenths = multipleOf("0.1")(FiniteNumber);
|
|
2860
3022
|
*
|
|
2861
|
-
*
|
|
2862
|
-
* FiniteNumber & Brand<"MultipleOf0.1"
|
|
3023
|
+
* assertType<
|
|
3024
|
+
* FiniteNumber & Brand<"MultipleOf0.1">,
|
|
3025
|
+
* typeof Tenths.Output
|
|
2863
3026
|
* >();
|
|
2864
3027
|
*
|
|
2865
|
-
*
|
|
2866
|
-
*
|
|
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, {
|
|
2867
3033
|
* type: "MultipleOf0.1",
|
|
2868
3034
|
* value: 0.31,
|
|
2869
3035
|
* divisor: "0.1",
|
|
@@ -2919,11 +3085,11 @@ const decimalStringToParts = (value) => {
|
|
|
2919
3085
|
* ### Example
|
|
2920
3086
|
*
|
|
2921
3087
|
* ```ts
|
|
2922
|
-
* import { Number, between } from "@evolu/common";
|
|
3088
|
+
* import { assertOk, Number, between } from "@evolu/common";
|
|
2923
3089
|
*
|
|
2924
3090
|
* const Percentage = between(0, 100)(Number);
|
|
2925
3091
|
*
|
|
2926
|
-
*
|
|
3092
|
+
* assertOk(Percentage.fromUnknown(75), 75);
|
|
2927
3093
|
* ```
|
|
2928
3094
|
*
|
|
2929
3095
|
* @group Number
|
|
@@ -2965,6 +3131,11 @@ export const between = (min, max) => (parent) => {
|
|
|
2965
3131
|
*
|
|
2966
3132
|
* ```ts
|
|
2967
3133
|
* import {
|
|
3134
|
+
* assertEqual,
|
|
3135
|
+
* assertErr,
|
|
3136
|
+
* assertOk,
|
|
3137
|
+
* assertType,
|
|
3138
|
+
* Data,
|
|
2968
3139
|
* String,
|
|
2969
3140
|
* array,
|
|
2970
3141
|
* brand,
|
|
@@ -2976,12 +3147,16 @@ export const between = (min, max) => (parent) => {
|
|
|
2976
3147
|
* const UserIds = array(UserId);
|
|
2977
3148
|
* const result = UserIds.from.parent(["ada", "grace"]);
|
|
2978
3149
|
*
|
|
2979
|
-
*
|
|
2980
|
-
* Result<ReadonlyArray<string & Brand<"UserId"
|
|
3150
|
+
* assertType<
|
|
3151
|
+
* Result<ReadonlyArray<string & Brand<"UserId">>>,
|
|
3152
|
+
* typeof result
|
|
2981
3153
|
* >();
|
|
2982
|
-
*
|
|
2983
|
-
*
|
|
2984
|
-
*
|
|
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, {
|
|
2985
3160
|
* type: "Array",
|
|
2986
3161
|
* reason: { kind: "NotArray", value: "ada" },
|
|
2987
3162
|
* });
|
|
@@ -3120,7 +3295,7 @@ const validateIndexedArrayItems = (name, value, validate, options, checkStructur
|
|
|
3120
3295
|
});
|
|
3121
3296
|
};
|
|
3122
3297
|
const copyArrayPrefix = (value, endIndex) => {
|
|
3123
|
-
const output =
|
|
3298
|
+
const output = createMutableArray(value.length);
|
|
3124
3299
|
for (let index = 0; index < endIndex; index++) {
|
|
3125
3300
|
output[index] = value[index];
|
|
3126
3301
|
}
|
|
@@ -3129,20 +3304,20 @@ const copyArrayPrefix = (value, endIndex) => {
|
|
|
3129
3304
|
/**
|
|
3130
3305
|
* Set {@link Type} whose every element must match one Type.
|
|
3131
3306
|
*
|
|
3132
|
-
*
|
|
3133
|
-
*
|
|
3134
|
-
*
|
|
3135
|
-
*
|
|
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.
|
|
3136
3311
|
*
|
|
3137
3312
|
* ### Example
|
|
3138
3313
|
*
|
|
3139
3314
|
* ```ts
|
|
3140
|
-
* import { String, set } from "@evolu/common";
|
|
3315
|
+
* import { assertOk, String, set } from "@evolu/common";
|
|
3141
3316
|
*
|
|
3142
3317
|
* const Tags = set(String);
|
|
3143
3318
|
* const tags = new Set(["local-first", "offline"]);
|
|
3144
3319
|
*
|
|
3145
|
-
*
|
|
3320
|
+
* assertOk(Tags.fromUnknown(tags), tags);
|
|
3146
3321
|
* ```
|
|
3147
3322
|
*
|
|
3148
3323
|
* @group Collection
|
|
@@ -3176,68 +3351,44 @@ const createHomogeneousCollectionType = (typeElement, config) => {
|
|
|
3176
3351
|
return type;
|
|
3177
3352
|
};
|
|
3178
3353
|
const setTypeByElement = /*#__PURE__*/ new WeakMap();
|
|
3179
|
-
const validateSetCollection = (value, validateElement, options) => {
|
|
3180
|
-
if (!hasObjectTag(value, "Set")) {
|
|
3181
|
-
return err({
|
|
3182
|
-
type: "Set",
|
|
3183
|
-
reason: { kind: "NotSet", value },
|
|
3184
|
-
});
|
|
3185
|
-
}
|
|
3186
|
-
if (!hasDirectSetPrototype(value)) {
|
|
3187
|
-
return err({
|
|
3188
|
-
type: "Set",
|
|
3189
|
-
reason: {
|
|
3190
|
-
kind: "UnexpectedPrototype",
|
|
3191
|
-
value: value,
|
|
3192
|
-
},
|
|
3193
|
-
});
|
|
3194
|
-
}
|
|
3195
|
-
return validateSetItems(value, validateElement, options, true);
|
|
3196
|
-
};
|
|
3197
|
-
const encodeSetCollection = (value, encodeElement) => {
|
|
3198
|
-
let changed = false;
|
|
3199
|
-
const output = new globalThis.Set();
|
|
3200
|
-
for (const item of value) {
|
|
3201
|
-
const encoded = encodeElement(item);
|
|
3202
|
-
if (!globalThis.Object.is(encoded, item))
|
|
3203
|
-
changed = true;
|
|
3204
|
-
output.add(encoded);
|
|
3205
|
-
}
|
|
3206
|
-
return changed ? output : value;
|
|
3207
|
-
};
|
|
3208
|
-
const isSetCollection = (value, isElement) => {
|
|
3209
|
-
if (!hasObjectTag(value, "Set"))
|
|
3210
|
-
return false;
|
|
3211
|
-
if (!hasDirectSetPrototype(value))
|
|
3212
|
-
return false;
|
|
3213
|
-
if (Reflect.ownKeys(value).length !== 0)
|
|
3214
|
-
return false;
|
|
3215
|
-
for (const item of value) {
|
|
3216
|
-
if (!isElement(item))
|
|
3217
|
-
return false;
|
|
3218
|
-
}
|
|
3219
|
-
return true;
|
|
3220
|
-
};
|
|
3221
|
-
const hasDirectSetPrototype = (value) => {
|
|
3222
|
-
const prototype = globalThis.Object.getPrototypeOf(value);
|
|
3223
|
-
if (prototype === null)
|
|
3224
|
-
return false;
|
|
3225
|
-
const objectPrototype = globalThis.Object.getPrototypeOf(prototype);
|
|
3226
|
-
return (objectPrototype !== null &&
|
|
3227
|
-
globalThis.Object.getPrototypeOf(objectPrototype) === null);
|
|
3228
|
-
};
|
|
3229
3354
|
const setRuntimeConfig = {
|
|
3230
3355
|
name: "Set",
|
|
3231
3356
|
typeByElement: setTypeByElement,
|
|
3232
|
-
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
|
+
},
|
|
3233
3366
|
validateItems: (value, validateElement, options) => validateSetItems(value, validateElement, options, false),
|
|
3234
|
-
encode:
|
|
3235
|
-
|
|
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
|
+
},
|
|
3236
3389
|
formatError: ((error) => {
|
|
3237
3390
|
if (error.reason.kind === "NotSet")
|
|
3238
3391
|
return `A value ${safelyStringifyUnknownValue(error.reason.value)} is not a Set.`;
|
|
3239
|
-
if (error.reason.kind === "UnexpectedPrototype")
|
|
3240
|
-
return "The value is an instance of a Set subclass, but a Set Output must be a direct Set instance.";
|
|
3241
3392
|
const issue = error.reason.issues[0];
|
|
3242
3393
|
switch (issue.kind) {
|
|
3243
3394
|
case "ExcessProperty":
|
|
@@ -3250,7 +3401,7 @@ const setRuntimeConfig = {
|
|
|
3250
3401
|
const validateSetItems = (value, validate, options, checkStructure) => {
|
|
3251
3402
|
let issues;
|
|
3252
3403
|
let changed = false;
|
|
3253
|
-
const output = new
|
|
3404
|
+
const output = new Set();
|
|
3254
3405
|
if (checkStructure) {
|
|
3255
3406
|
for (const key of Reflect.ownKeys(value)) {
|
|
3256
3407
|
(issues ??= []).push({ kind: "ExcessProperty", key });
|
|
@@ -3283,6 +3434,208 @@ const validateSetItems = (value, validate, options, checkStructure) => {
|
|
|
3283
3434
|
},
|
|
3284
3435
|
});
|
|
3285
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
|
+
};
|
|
3286
3639
|
export function tuple(...elements) {
|
|
3287
3640
|
return createTupleType(elements);
|
|
3288
3641
|
}
|
|
@@ -3466,11 +3819,12 @@ const formatPlainObjectRootError = (reason) => reason.kind === "NotObject"
|
|
|
3466
3819
|
* A {@link Type} for readonly plain objects with unknown property values.
|
|
3467
3820
|
*
|
|
3468
3821
|
* `Object` is the runtime counterpart of a `Readonly<Record<string, unknown>>`
|
|
3469
|
-
* data boundary. Its
|
|
3470
|
-
*
|
|
3471
|
-
*
|
|
3472
|
-
*
|
|
3473
|
-
* 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.
|
|
3474
3828
|
*
|
|
3475
3829
|
* Use {@link object} when property names are fixed, {@link record} when keys and
|
|
3476
3830
|
* values have their own Types, and {@link instanceOf} when an instance belongs
|
|
@@ -3578,10 +3932,6 @@ const _Object = /*#__PURE__*/ createRootType("Object", (value, options = firstVa
|
|
|
3578
3932
|
// https://github.com/react/metro/issues/1331
|
|
3579
3933
|
// https://github.com/expo/expo/issues/31167
|
|
3580
3934
|
export { _Object as Object };
|
|
3581
|
-
const isPlainObject = (value) => {
|
|
3582
|
-
const prototype = globalThis.Object.getPrototypeOf(value);
|
|
3583
|
-
return (prototype === null || globalThis.Object.getPrototypeOf(prototype) === null);
|
|
3584
|
-
};
|
|
3585
3935
|
/**
|
|
3586
3936
|
* Record {@link Type}.
|
|
3587
3937
|
*
|
|
@@ -3598,12 +3948,13 @@ const isPlainObject = (value) => {
|
|
|
3598
3948
|
* By default, validation returns the first issue. Pass `{ errors: "all" }` to
|
|
3599
3949
|
* collect issues across the whole Record.
|
|
3600
3950
|
*
|
|
3601
|
-
* A Record must
|
|
3602
|
-
*
|
|
3603
|
-
*
|
|
3604
|
-
*
|
|
3605
|
-
*
|
|
3606
|
-
* `__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.
|
|
3607
3958
|
*
|
|
3608
3959
|
* If transformed keys collide, validation fails instead of overwriting an
|
|
3609
3960
|
* entry.
|
|
@@ -3612,6 +3963,8 @@ const isPlainObject = (value) => {
|
|
|
3612
3963
|
*
|
|
3613
3964
|
* ```ts
|
|
3614
3965
|
* import {
|
|
3966
|
+
* assertOk,
|
|
3967
|
+
* assertType,
|
|
3615
3968
|
* Int64FromInt64String,
|
|
3616
3969
|
* String,
|
|
3617
3970
|
* record,
|
|
@@ -3627,21 +3980,22 @@ const isPlainObject = (value) => {
|
|
|
3627
3980
|
* grace: "20",
|
|
3628
3981
|
* });
|
|
3629
3982
|
*
|
|
3630
|
-
*
|
|
3983
|
+
* assertOk(scoresFromUnknown, { ada: 10n, grace: 20n });
|
|
3631
3984
|
*
|
|
3632
3985
|
* // Validate keys and values with their root Types.
|
|
3633
3986
|
* const scoresInput = ScoresByUser.parent.fromUnknown({
|
|
3634
3987
|
* ada: "10",
|
|
3635
3988
|
* grace: "20",
|
|
3636
3989
|
* });
|
|
3637
|
-
*
|
|
3990
|
+
* assertOk(scoresInput, { ada: "10", grace: "20" });
|
|
3638
3991
|
*
|
|
3639
3992
|
* // Run the remaining key and value stages.
|
|
3640
3993
|
* const scoresFromInput = ScoresByUser.from.parent(scoresInput.value);
|
|
3641
3994
|
*
|
|
3642
|
-
*
|
|
3643
|
-
*
|
|
3644
|
-
* Readonly<Partial<Record<string, Int64
|
|
3995
|
+
* assertOk(scoresFromInput, { ada: 10n, grace: 20n });
|
|
3996
|
+
* assertType<
|
|
3997
|
+
* Readonly<Partial<Record<string, Int64>>>,
|
|
3998
|
+
* typeof scoresFromInput.value
|
|
3645
3999
|
* >();
|
|
3646
4000
|
* ```
|
|
3647
4001
|
*
|
|
@@ -3650,34 +4004,64 @@ const isPlainObject = (value) => {
|
|
|
3650
4004
|
* as a Record entry:
|
|
3651
4005
|
*
|
|
3652
4006
|
* ```ts
|
|
4007
|
+
* import {
|
|
4008
|
+
* assertEqual,
|
|
4009
|
+
* assertErr,
|
|
4010
|
+
* assertTrue,
|
|
4011
|
+
* trySync,
|
|
4012
|
+
* } from "@evolu/common";
|
|
4013
|
+
*
|
|
3653
4014
|
* type Values = Partial<Record<"toString", number>>;
|
|
3654
4015
|
* const values: Values = {};
|
|
3655
4016
|
*
|
|
3656
4017
|
* // TypeScript treats the inherited function as `number | undefined`.
|
|
3657
4018
|
* const value: number | undefined = values.toString;
|
|
4019
|
+
* const valueType = typeof value;
|
|
3658
4020
|
*
|
|
3659
|
-
*
|
|
3660
|
-
*
|
|
3661
|
-
*
|
|
3662
|
-
*
|
|
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);
|
|
3663
4030
|
* ```
|
|
3664
4031
|
*
|
|
3665
4032
|
* Evolu Record Outputs use the same TypeScript Record representation.
|
|
3666
4033
|
*
|
|
3667
4034
|
* ```ts
|
|
3668
|
-
* 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";
|
|
3669
4046
|
*
|
|
3670
4047
|
* const Values = record(literal("toString"), Number);
|
|
3671
4048
|
* const result = Values.fromUnknown({});
|
|
4049
|
+
* const emptyValues: Data = {};
|
|
3672
4050
|
*
|
|
3673
|
-
*
|
|
4051
|
+
* assertOk(result, emptyValues);
|
|
3674
4052
|
*
|
|
3675
4053
|
* const value: number | undefined = result.value.toString;
|
|
4054
|
+
* const valueType = typeof value;
|
|
3676
4055
|
*
|
|
3677
|
-
*
|
|
3678
|
-
*
|
|
3679
|
-
*
|
|
3680
|
-
*
|
|
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);
|
|
3681
4065
|
* ```
|
|
3682
4066
|
*
|
|
3683
4067
|
* In other words, treat Record Outputs as string-keyed data rather than calling
|
|
@@ -3864,7 +4248,13 @@ const validateRecordEntries = (input, validateKey, validateValue, options) => {
|
|
|
3864
4248
|
* ### Example
|
|
3865
4249
|
*
|
|
3866
4250
|
* ```ts
|
|
3867
|
-
* import {
|
|
4251
|
+
* import {
|
|
4252
|
+
* assertOk,
|
|
4253
|
+
* String,
|
|
4254
|
+
* object,
|
|
4255
|
+
* optional,
|
|
4256
|
+
* undefinedOr,
|
|
4257
|
+
* } from "@evolu/common";
|
|
3868
4258
|
*
|
|
3869
4259
|
* const User = object({
|
|
3870
4260
|
* name: String,
|
|
@@ -3872,8 +4262,8 @@ const validateRecordEntries = (input, validateKey, validateValue, options) => {
|
|
|
3872
4262
|
* preferredName: optional(undefinedOr(String)),
|
|
3873
4263
|
* });
|
|
3874
4264
|
*
|
|
3875
|
-
*
|
|
3876
|
-
*
|
|
4265
|
+
* assertOk(User.fromUnknown({ name: "Ada" }), { name: "Ada" });
|
|
4266
|
+
* assertOk(User.fromUnknown({ name: "Ada", preferredName: undefined }), {
|
|
3877
4267
|
* name: "Ada",
|
|
3878
4268
|
* preferredName: undefined,
|
|
3879
4269
|
* });
|
|
@@ -4230,13 +4620,20 @@ const createRecordPropertyError = (issue) => ({
|
|
|
4230
4620
|
* ### Example
|
|
4231
4621
|
*
|
|
4232
4622
|
* ```ts
|
|
4233
|
-
* import {
|
|
4623
|
+
* import {
|
|
4624
|
+
* assertFalse,
|
|
4625
|
+
* assertOk,
|
|
4626
|
+
* PositiveInt,
|
|
4627
|
+
* String,
|
|
4628
|
+
* partial,
|
|
4629
|
+
* } from "@evolu/common";
|
|
4234
4630
|
*
|
|
4235
4631
|
* const PartialUser = partial({ name: String, age: PositiveInt });
|
|
4632
|
+
* const emptyPartialUser: typeof PartialUser.Output = {};
|
|
4236
4633
|
*
|
|
4237
|
-
*
|
|
4238
|
-
*
|
|
4239
|
-
*
|
|
4634
|
+
* assertOk(PartialUser.fromUnknown({}), emptyPartialUser);
|
|
4635
|
+
* assertOk(PartialUser.fromUnknown({ name: "Ada" }), { name: "Ada" });
|
|
4636
|
+
* assertFalse(PartialUser.fromUnknown({ age: -1 }).ok);
|
|
4240
4637
|
* ```
|
|
4241
4638
|
*
|
|
4242
4639
|
* @group Objects
|
|
@@ -4263,15 +4660,20 @@ export const partial = (props, ..._validation) => {
|
|
|
4263
4660
|
* ### Example
|
|
4264
4661
|
*
|
|
4265
4662
|
* ```ts
|
|
4266
|
-
* import {
|
|
4663
|
+
* import {
|
|
4664
|
+
* assertOk,
|
|
4665
|
+
* String,
|
|
4666
|
+
* nullOr,
|
|
4667
|
+
* nullableToOptional,
|
|
4668
|
+
* } from "@evolu/common";
|
|
4267
4669
|
*
|
|
4268
4670
|
* const User = nullableToOptional({
|
|
4269
4671
|
* name: String,
|
|
4270
4672
|
* nickname: nullOr(String),
|
|
4271
4673
|
* });
|
|
4272
4674
|
*
|
|
4273
|
-
*
|
|
4274
|
-
*
|
|
4675
|
+
* assertOk(User.fromUnknown({ name: "Ada" }), { name: "Ada" });
|
|
4676
|
+
* assertOk(User.fromUnknown({ name: "Ada", nickname: null }), {
|
|
4275
4677
|
* name: "Ada",
|
|
4276
4678
|
* nickname: null,
|
|
4277
4679
|
* });
|
|
@@ -4304,12 +4706,12 @@ export const nullableToOptional = (props, ..._validation) => {
|
|
|
4304
4706
|
* ### Example
|
|
4305
4707
|
*
|
|
4306
4708
|
* ```ts
|
|
4307
|
-
* import { String, object, omit } from "@evolu/common";
|
|
4709
|
+
* import { assertOk, String, object, omit } from "@evolu/common";
|
|
4308
4710
|
*
|
|
4309
4711
|
* const User = object({ name: String, password: String });
|
|
4310
4712
|
* const PublicUser = omit(User, "password");
|
|
4311
4713
|
*
|
|
4312
|
-
*
|
|
4714
|
+
* assertOk(PublicUser.fromUnknown({ name: "Ada" }), { name: "Ada" });
|
|
4313
4715
|
* ```
|
|
4314
4716
|
*
|
|
4315
4717
|
* @group Objects
|
|
@@ -4564,7 +4966,7 @@ const lazyTypeNodes = /*#__PURE__*/ new WeakSet();
|
|
|
4564
4966
|
const assertLazyReferencesAreGuarded = (type) => {
|
|
4565
4967
|
for (;;) {
|
|
4566
4968
|
assert(!lazyTypeNodes.has(type), "A Lazy Type definition must place every Lazy Type behind a structural boundary.");
|
|
4567
|
-
if ("members" in type &&
|
|
4969
|
+
if ("members" in type && Array.isArray(type.members)) {
|
|
4568
4970
|
for (const member of type.members)
|
|
4569
4971
|
assertLazyReferencesAreGuarded(member);
|
|
4570
4972
|
}
|
|
@@ -4576,6 +4978,266 @@ const assertLazyReferencesAreGuarded = (type) => {
|
|
|
4576
4978
|
type = type.parent;
|
|
4577
4979
|
}
|
|
4578
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);
|
|
4579
5241
|
const emptyJsonValuePath =
|
|
4580
5242
|
/*#__PURE__*/ globalThis.Object.freeze([]);
|
|
4581
5243
|
const jsonValuePathToArray = (path) => {
|
|
@@ -4613,6 +5275,7 @@ const validateJsonValue = (value, options = firstValidationOptions) => {
|
|
|
4613
5275
|
}
|
|
4614
5276
|
if (typeof value === "number") {
|
|
4615
5277
|
if (!globalThis.Number.isFinite(value)) {
|
|
5278
|
+
// oxlint-disable-next-line unicorn/no-lonely-if -- Keep issue construction separate from the finite-number guard.
|
|
4616
5279
|
if (addIssue({
|
|
4617
5280
|
kind: "NonFiniteNumber",
|
|
4618
5281
|
path: jsonValuePathToArray(path),
|
|
@@ -4633,7 +5296,7 @@ const validateJsonValue = (value, options = firstValidationOptions) => {
|
|
|
4633
5296
|
}
|
|
4634
5297
|
continue;
|
|
4635
5298
|
}
|
|
4636
|
-
const isArray =
|
|
5299
|
+
const isArray = Array.isArray(value);
|
|
4637
5300
|
if (!isArray && !isPlainObject(value)) {
|
|
4638
5301
|
if (addIssue({
|
|
4639
5302
|
kind: "UnexpectedPrototype",
|
|
@@ -4798,7 +5461,7 @@ const getJsonValueRuntimeTypeIssues = (error, mode) => {
|
|
|
4798
5461
|
}),
|
|
4799
5462
|
}));
|
|
4800
5463
|
};
|
|
4801
|
-
const parseJson = (value) =>
|
|
5464
|
+
const parseJson = (value) => JSON.parse(value);
|
|
4802
5465
|
const jsonToJsonValueResult = (value) => {
|
|
4803
5466
|
const result = trySync(() => parseJson(value));
|
|
4804
5467
|
return result.ok && validateJsonValue(result.value).ok
|
|
@@ -4819,9 +5482,10 @@ const stringifyJsonValue = (value) => {
|
|
|
4819
5482
|
chunks.push("null");
|
|
4820
5483
|
continue;
|
|
4821
5484
|
}
|
|
5485
|
+
// oxlint-disable-next-line typescript/switch-exhaustiveness-check -- JsonValue excludes the additional runtime types reported by tsgolint.
|
|
4822
5486
|
switch (typeof value) {
|
|
4823
5487
|
case "string":
|
|
4824
|
-
chunks.push(
|
|
5488
|
+
chunks.push(JSON.stringify(value));
|
|
4825
5489
|
break;
|
|
4826
5490
|
case "number":
|
|
4827
5491
|
chunks.push(globalThis.Object.is(value, -0) ? "-0" : globalThis.String(value));
|
|
@@ -4830,7 +5494,7 @@ const stringifyJsonValue = (value) => {
|
|
|
4830
5494
|
chunks.push(value ? "true" : "false");
|
|
4831
5495
|
break;
|
|
4832
5496
|
case "object": {
|
|
4833
|
-
if (
|
|
5497
|
+
if (Array.isArray(value)) {
|
|
4834
5498
|
const array = value;
|
|
4835
5499
|
chunks.push("[");
|
|
4836
5500
|
work.push({ kind: "Text", value: "]" });
|
|
@@ -4851,7 +5515,7 @@ const stringifyJsonValue = (value) => {
|
|
|
4851
5515
|
work.push({ kind: "Text", value: ":" });
|
|
4852
5516
|
work.push({
|
|
4853
5517
|
kind: "Text",
|
|
4854
|
-
value:
|
|
5518
|
+
value: JSON.stringify(key),
|
|
4855
5519
|
});
|
|
4856
5520
|
if (index > 0)
|
|
4857
5521
|
work.push({ kind: "Text", value: "," });
|
|
@@ -4868,7 +5532,12 @@ const stringifyJsonValue = (value) => {
|
|
|
4868
5532
|
* ### Example
|
|
4869
5533
|
*
|
|
4870
5534
|
* ```ts
|
|
4871
|
-
* import {
|
|
5535
|
+
* import {
|
|
5536
|
+
* assertOk,
|
|
5537
|
+
* assertType,
|
|
5538
|
+
* JsonValue,
|
|
5539
|
+
* type JsonValueInput,
|
|
5540
|
+
* } from "@evolu/common";
|
|
4872
5541
|
*
|
|
4873
5542
|
* const input: JsonValueInput = {
|
|
4874
5543
|
* name: "Ada",
|
|
@@ -4876,8 +5545,8 @@ const stringifyJsonValue = (value) => {
|
|
|
4876
5545
|
* };
|
|
4877
5546
|
* const result = JsonValue.fromUnknown(input);
|
|
4878
5547
|
*
|
|
4879
|
-
*
|
|
4880
|
-
*
|
|
5548
|
+
* assertOk(result, input);
|
|
5549
|
+
* assertType<JsonValue, typeof result.value>();
|
|
4881
5550
|
* ```
|
|
4882
5551
|
*
|
|
4883
5552
|
* @group JSON
|
|
@@ -4915,11 +5584,11 @@ export const Json = /*#__PURE__*/ brand("Json", String, (value) => {
|
|
|
4915
5584
|
* ### Example
|
|
4916
5585
|
*
|
|
4917
5586
|
* ```ts
|
|
4918
|
-
* import { Json, jsonToJsonValue } from "@evolu/common";
|
|
5587
|
+
* import { assertEqual, Json, jsonToJsonValue } from "@evolu/common";
|
|
4919
5588
|
*
|
|
4920
5589
|
* const value = jsonToJsonValue(Json.orThrow('{"name":"Ada"}'));
|
|
4921
5590
|
*
|
|
4922
|
-
*
|
|
5591
|
+
* assertEqual(value, { name: "Ada" });
|
|
4923
5592
|
* ```
|
|
4924
5593
|
*
|
|
4925
5594
|
* @group JSON
|
|
@@ -4931,11 +5600,15 @@ export const jsonToJsonValue = (value) => parseJson(value);
|
|
|
4931
5600
|
* ### Example
|
|
4932
5601
|
*
|
|
4933
5602
|
* ```ts
|
|
4934
|
-
* import {
|
|
5603
|
+
* import {
|
|
5604
|
+
* assertEqual,
|
|
5605
|
+
* jsonValueToJson,
|
|
5606
|
+
* type JsonValue,
|
|
5607
|
+
* } from "@evolu/common";
|
|
4935
5608
|
*
|
|
4936
5609
|
* const value: JsonValue = { name: "Ada" };
|
|
4937
5610
|
*
|
|
4938
|
-
*
|
|
5611
|
+
* assertEqual(jsonValueToJson(value), '{"name":"Ada"}');
|
|
4939
5612
|
* ```
|
|
4940
5613
|
*
|
|
4941
5614
|
* @group JSON
|
|
@@ -4950,12 +5623,12 @@ export const jsonValueToJson = (value) => stringifyJsonValue(JsonValue.to(value)
|
|
|
4950
5623
|
* ### Example
|
|
4951
5624
|
*
|
|
4952
5625
|
* ```ts
|
|
4953
|
-
* import { JsonValueFromJson } from "@evolu/common";
|
|
5626
|
+
* import { assertEqual, assertOk, JsonValueFromJson } from "@evolu/common";
|
|
4954
5627
|
*
|
|
4955
5628
|
* const result = JsonValueFromJson.fromUnknown('{ "name": "Ada" }');
|
|
4956
5629
|
*
|
|
4957
|
-
*
|
|
4958
|
-
*
|
|
5630
|
+
* assertOk(result, { name: "Ada" });
|
|
5631
|
+
* assertEqual(JsonValueFromJson.to(result.value), '{"name":"Ada"}');
|
|
4959
5632
|
* ```
|
|
4960
5633
|
*
|
|
4961
5634
|
* @group JSON
|
|
@@ -4976,6 +5649,8 @@ export const JsonValueFromJson = /*#__PURE__*/ transform("JsonValueFromJson", Js
|
|
|
4976
5649
|
*
|
|
4977
5650
|
* ```ts
|
|
4978
5651
|
* import {
|
|
5652
|
+
* assertEqual,
|
|
5653
|
+
* assertType,
|
|
4979
5654
|
* Age,
|
|
4980
5655
|
* NonEmptyTrimmedString100,
|
|
4981
5656
|
* json,
|
|
@@ -4996,11 +5671,13 @@ export const JsonValueFromJson = /*#__PURE__*/ transform("JsonValueFromJson", Js
|
|
|
4996
5671
|
* const user = User.orThrow({ name: "Ada", age: 37 });
|
|
4997
5672
|
* const userJson = userToUserJson(user);
|
|
4998
5673
|
*
|
|
4999
|
-
*
|
|
5000
|
-
* string & Brand<"Json"> & Brand<"UserJson"
|
|
5674
|
+
* assertType<
|
|
5675
|
+
* string & Brand<"Json"> & Brand<"UserJson">,
|
|
5676
|
+
* typeof userJson
|
|
5001
5677
|
* >();
|
|
5002
|
-
*
|
|
5003
|
-
*
|
|
5678
|
+
* assertEqual(userJson, '{"name":"Ada","age":37}');
|
|
5679
|
+
* assertEqual(UserJson.orThrow(userJson), userJson);
|
|
5680
|
+
* assertEqual(userJsonToUser(userJson), user);
|
|
5004
5681
|
* ```
|
|
5005
5682
|
*
|
|
5006
5683
|
* The supplied Type must have a JSON-compatible `CanonicalInput`. The branded
|