@evolu/common 7.4.1 → 8.0.0-next.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -61
- package/dist/src/Array.d.ts +343 -102
- package/dist/src/Array.d.ts.map +1 -1
- package/dist/src/Array.js +181 -85
- package/dist/src/Assert.d.ts +57 -11
- package/dist/src/Assert.d.ts.map +1 -1
- package/dist/src/Assert.js +47 -11
- package/dist/src/BigInt.d.ts +6 -1
- package/dist/src/BigInt.d.ts.map +1 -1
- package/dist/src/BigInt.js +5 -0
- package/dist/src/Brand.d.ts +40 -12
- package/dist/src/Brand.d.ts.map +1 -1
- package/dist/src/Brand.js +5 -0
- package/dist/src/Buffer.d.ts +6 -1
- package/dist/src/Buffer.d.ts.map +1 -1
- package/dist/src/Buffer.js +7 -2
- package/dist/src/Cache.d.ts +10 -2
- package/dist/src/Cache.d.ts.map +1 -1
- package/dist/src/Cache.js +6 -0
- package/dist/src/Callbacks.d.ts +10 -7
- package/dist/src/Callbacks.d.ts.map +1 -1
- package/dist/src/Callbacks.js +23 -13
- package/dist/src/Console.d.ts +361 -69
- package/dist/src/Console.d.ts.map +1 -1
- package/dist/src/Console.js +217 -117
- package/dist/src/Crypto.d.ts +70 -21
- package/dist/src/Crypto.d.ts.map +1 -1
- package/dist/src/Crypto.js +72 -27
- package/dist/src/Eq.d.ts +22 -2
- package/dist/src/Eq.d.ts.map +1 -1
- package/dist/src/Eq.js +21 -1
- package/dist/src/Error.d.ts +23 -12
- package/dist/src/Error.d.ts.map +1 -1
- package/dist/src/Error.js +27 -11
- package/dist/src/Function.d.ts +128 -56
- package/dist/src/Function.d.ts.map +1 -1
- package/dist/src/Function.js +118 -14
- package/dist/src/Identicon.d.ts +5 -0
- package/dist/src/Identicon.d.ts.map +1 -1
- package/dist/src/Identicon.js +6 -1
- package/dist/src/Lookup.d.ts +162 -0
- package/dist/src/Lookup.d.ts.map +1 -0
- package/dist/src/Lookup.js +200 -0
- package/dist/src/Microtask.d.ts +21 -0
- package/dist/src/Microtask.d.ts.map +1 -0
- package/dist/src/Microtask.js +37 -0
- package/dist/src/Number.d.ts +18 -4
- package/dist/src/Number.d.ts.map +1 -1
- package/dist/src/Number.js +23 -1
- package/dist/src/Object.d.ts +110 -5
- package/dist/src/Object.d.ts.map +1 -1
- package/dist/src/Object.js +108 -5
- package/dist/src/Option.d.ts +58 -0
- package/dist/src/Option.d.ts.map +1 -0
- package/dist/src/Option.js +43 -0
- package/dist/src/Order.d.ts +5 -0
- package/dist/src/Order.d.ts.map +1 -1
- package/dist/src/Order.js +8 -3
- package/dist/src/Platform.d.ts +36 -2
- package/dist/src/Platform.d.ts.map +1 -1
- package/dist/src/Platform.js +10 -6
- package/dist/src/Polyfills.d.ts +29 -0
- package/dist/src/Polyfills.d.ts.map +1 -0
- package/dist/src/Polyfills.js +329 -0
- package/dist/src/Random.d.ts +18 -7
- package/dist/src/Random.d.ts.map +1 -1
- package/dist/src/Random.js +9 -7
- package/dist/src/Redacted.d.ts +7 -4
- package/dist/src/Redacted.d.ts.map +1 -1
- package/dist/src/Redacted.js +5 -0
- package/dist/src/Ref.d.ts +31 -16
- package/dist/src/Ref.d.ts.map +1 -1
- package/dist/src/Ref.js +35 -7
- package/dist/src/RefCount.d.ts +62 -0
- package/dist/src/RefCount.d.ts.map +1 -0
- package/dist/src/RefCount.js +83 -0
- package/dist/src/Relation.d.ts +84 -29
- package/dist/src/Relation.d.ts.map +1 -1
- package/dist/src/Relation.js +83 -105
- package/dist/src/Resource.d.ts +263 -0
- package/dist/src/Resource.d.ts.map +1 -0
- package/dist/src/Resource.js +389 -0
- package/dist/src/Result.d.ts +390 -374
- package/dist/src/Result.d.ts.map +1 -1
- package/dist/src/Result.js +142 -70
- package/dist/src/Schedule.d.ts +953 -0
- package/dist/src/Schedule.d.ts.map +1 -0
- package/dist/src/Schedule.js +1199 -0
- package/dist/src/Set.d.ts +181 -0
- package/dist/src/Set.d.ts.map +1 -0
- package/dist/src/Set.js +137 -0
- package/dist/src/Skiplist.d.ts +6 -1
- package/dist/src/Skiplist.d.ts.map +1 -1
- package/dist/src/Skiplist.js +5 -0
- package/dist/src/Sqlite.d.ts +160 -60
- package/dist/src/Sqlite.d.ts.map +1 -1
- package/dist/src/Sqlite.js +290 -190
- package/dist/src/Store.d.ts +36 -28
- package/dist/src/Store.d.ts.map +1 -1
- package/dist/src/Store.js +55 -15
- package/dist/src/String.d.ts +5 -0
- package/dist/src/String.d.ts.map +1 -1
- package/dist/src/String.js +5 -0
- package/dist/src/Task.d.ts +2274 -423
- package/dist/src/Task.d.ts.map +1 -1
- package/dist/src/Task.js +1499 -348
- package/dist/src/Test.d.ts +150 -0
- package/dist/src/Test.d.ts.map +1 -0
- package/dist/src/Test.js +97 -0
- package/dist/src/Time.d.ts +202 -48
- package/dist/src/Time.d.ts.map +1 -1
- package/dist/src/Time.js +199 -88
- package/dist/src/Tracer.d.ts +48 -0
- package/dist/src/Tracer.d.ts.map +1 -0
- package/dist/src/Tracer.js +6 -0
- package/dist/src/Type.d.ts +587 -209
- package/dist/src/Type.d.ts.map +1 -1
- package/dist/src/Type.js +370 -240
- package/dist/src/Types.d.ts +160 -6
- package/dist/src/Types.d.ts.map +1 -1
- package/dist/src/Types.js +22 -2
- package/dist/src/WebSocket.d.ts +130 -58
- package/dist/src/WebSocket.d.ts.map +1 -1
- package/dist/src/WebSocket.js +274 -113
- package/dist/src/Worker.d.ts +245 -60
- package/dist/src/Worker.d.ts.map +1 -1
- package/dist/src/Worker.js +220 -64
- package/dist/src/index.d.ts +25 -3
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +19 -3
- package/dist/src/local-first/Db.d.ts +24 -213
- package/dist/src/local-first/Db.d.ts.map +1 -1
- package/dist/src/local-first/Db.js +621 -293
- package/dist/src/local-first/Error.d.ts +12 -0
- package/dist/src/local-first/Error.d.ts.map +1 -0
- package/dist/src/local-first/Error.js +6 -0
- package/dist/src/local-first/Evolu.d.ts +330 -285
- package/dist/src/local-first/Evolu.d.ts.map +1 -1
- package/dist/src/local-first/Evolu.js +472 -402
- package/dist/src/local-first/LocalAuth.d.ts +10 -10
- package/dist/src/local-first/LocalAuth.d.ts.map +1 -1
- package/dist/src/local-first/LocalAuth.js +6 -1
- package/dist/src/local-first/Owner.d.ts +70 -27
- package/dist/src/local-first/Owner.d.ts.map +1 -1
- package/dist/src/local-first/Owner.js +27 -14
- package/dist/src/local-first/Protocol.d.ts +58 -50
- package/dist/src/local-first/Protocol.d.ts.map +1 -1
- package/dist/src/local-first/Protocol.js +253 -155
- package/dist/src/local-first/Query.d.ts +133 -43
- package/dist/src/local-first/Query.d.ts.map +1 -1
- package/dist/src/local-first/Query.js +139 -90
- package/dist/src/local-first/Relay.d.ts +34 -49
- package/dist/src/local-first/Relay.d.ts.map +1 -1
- package/dist/src/local-first/Relay.js +59 -177
- package/dist/src/local-first/Schema.d.ts +139 -144
- package/dist/src/local-first/Schema.d.ts.map +1 -1
- package/dist/src/local-first/Schema.js +78 -163
- package/dist/src/local-first/Shared.d.ts +125 -0
- package/dist/src/local-first/Shared.d.ts.map +1 -0
- package/dist/src/local-first/Shared.js +569 -0
- package/dist/src/local-first/Storage.d.ts +54 -58
- package/dist/src/local-first/Storage.d.ts.map +1 -1
- package/dist/src/local-first/Storage.js +127 -179
- package/dist/src/local-first/Timestamp.d.ts +28 -35
- package/dist/src/local-first/Timestamp.d.ts.map +1 -1
- package/dist/src/local-first/Timestamp.js +24 -25
- package/dist/src/local-first/index.d.ts +2 -15
- package/dist/src/local-first/index.d.ts.map +1 -1
- package/dist/src/local-first/index.js +2 -15
- package/package.json +37 -24
- package/src/Array.ts +467 -106
- package/src/Assert.ts +87 -12
- package/src/BigInt.ts +7 -1
- package/src/Brand.ts +41 -12
- package/src/Buffer.ts +10 -4
- package/src/Cache.ts +11 -2
- package/src/Callbacks.ts +33 -23
- package/src/Console.ts +567 -178
- package/src/Crypto.ts +111 -62
- package/src/Eq.ts +30 -3
- package/src/Error.ts +50 -34
- package/src/Function.ts +140 -68
- package/src/Identicon.ts +7 -1
- package/src/Lookup.ts +429 -0
- package/src/Microtask.ts +58 -0
- package/src/Number.ts +40 -7
- package/src/Object.ts +155 -16
- package/src/Option.ts +74 -0
- package/src/Order.ts +11 -3
- package/src/Platform.ts +43 -7
- package/src/Polyfills.ts +520 -0
- package/src/Random.ts +25 -11
- package/src/Redacted.ts +8 -4
- package/src/Ref.ts +72 -21
- package/src/RefCount.ts +170 -0
- package/src/Relation.ts +188 -136
- package/src/Resource.ts +864 -0
- package/src/Result.ts +548 -381
- package/src/Schedule.ts +1471 -0
- package/src/Set.ts +247 -0
- package/src/Skiplist.ts +7 -1
- package/src/Sqlite.ts +409 -304
- package/src/Store.ts +94 -48
- package/src/String.ts +6 -0
- package/src/Task.ts +3696 -740
- package/src/Test.ts +202 -0
- package/src/Time.ts +357 -147
- package/src/Tracer.ts +54 -0
- package/src/Type.ts +866 -430
- package/src/Types.ts +202 -7
- package/src/WebSocket.ts +404 -167
- package/src/Worker.ts +527 -147
- package/src/index.ts +74 -3
- package/src/local-first/Db.ts +867 -597
- package/src/local-first/Error.ts +17 -0
- package/src/local-first/Evolu.ts +884 -824
- package/src/local-first/LocalAuth.ts +10 -10
- package/src/local-first/Owner.ts +94 -42
- package/src/local-first/Protocol.ts +441 -307
- package/src/local-first/Query.ts +243 -194
- package/src/local-first/Relay.ts +119 -273
- package/src/local-first/Schema.ts +284 -394
- package/src/local-first/Shared.ts +932 -0
- package/src/local-first/Storage.ts +211 -283
- package/src/local-first/Timestamp.ts +38 -45
- package/src/local-first/index.ts +2 -16
- package/dist/src/Instances.d.ts +0 -34
- package/dist/src/Instances.d.ts.map +0 -1
- package/dist/src/Instances.js +0 -44
- package/dist/src/Resources.d.ts +0 -118
- package/dist/src/Resources.d.ts.map +0 -1
- package/dist/src/Resources.js +0 -197
- package/dist/src/local-first/Platform.d.ts +0 -25
- package/dist/src/local-first/Platform.d.ts.map +0 -1
- package/dist/src/local-first/Platform.js +0 -1
- package/dist/src/local-first/Public.d.ts +0 -22
- package/dist/src/local-first/Public.d.ts.map +0 -1
- package/dist/src/local-first/Public.js +0 -15
- package/dist/src/local-first/PublicKysely.d.ts +0 -148
- package/dist/src/local-first/PublicKysely.d.ts.map +0 -1
- package/dist/src/local-first/PublicKysely.js +0 -184
- package/dist/src/local-first/Sync.d.ts +0 -112
- package/dist/src/local-first/Sync.d.ts.map +0 -1
- package/dist/src/local-first/Sync.js +0 -526
- package/src/Instances.ts +0 -92
- package/src/Resources.ts +0 -367
- package/src/local-first/Platform.ts +0 -27
- package/src/local-first/Public.ts +0 -42
- package/src/local-first/PublicKysely.ts +0 -239
- package/src/local-first/Sync.ts +0 -949
package/src/Type.ts
CHANGED
|
@@ -1,24 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runtime type validation, parsing, and branded types.
|
|
3
|
+
*
|
|
4
|
+
* @module
|
|
5
|
+
*/
|
|
6
|
+
|
|
1
7
|
import { utf8ToBytes } from "@noble/ciphers/utils.js";
|
|
2
8
|
import { sha256 } from "@noble/hashes/sha2.js";
|
|
3
9
|
import * as bip39 from "@scure/bip39";
|
|
4
10
|
import { wordlist } from "@scure/bip39/wordlists/english.js";
|
|
5
|
-
import { pack } from "msgpackr";
|
|
6
11
|
import type { Brand } from "./Brand.js";
|
|
7
12
|
import type { RandomBytesDep } from "./Crypto.js";
|
|
8
|
-
import {
|
|
13
|
+
import { exhaustiveCheck } from "./Function.js";
|
|
14
|
+
import { isFunction, isPlainObject } from "./Object.js";
|
|
9
15
|
import { hasNodeBuffer } from "./Platform.js";
|
|
10
|
-
import {
|
|
16
|
+
import type { NextResult, Result } from "./Result.js";
|
|
17
|
+
import { err, getOrNull, getOrThrow, ok, trySync } from "./Result.js";
|
|
11
18
|
import { safelyStringifyUnknownValue } from "./String.js";
|
|
12
19
|
import type { TimeDep } from "./Time.js";
|
|
13
20
|
import type { Literal, Refinement, Simplify, WidenLiteral } from "./Types.js";
|
|
14
|
-
import { IntentionalNever } from "./Types.js";
|
|
15
21
|
|
|
16
22
|
/**
|
|
17
23
|
* Evolu {@link Type} is like a type guard that returns typed errors (via
|
|
18
|
-
* {@link Result}) instead of throwing. We either
|
|
19
|
-
* composable typed error
|
|
24
|
+
* {@link Result}) instead of throwing. We either receive a safely typed value or
|
|
25
|
+
* a composable typed error that tells us exactly why the validation failed.
|
|
20
26
|
*
|
|
21
|
-
*
|
|
27
|
+
* The reason why Evolu Type exists is that no other TypeScript validation
|
|
28
|
+
* library met Evolu's requirements. A distinctive feature of Evolu Type
|
|
29
|
+
* compared to other validation libraries is that it returns typed errors rather
|
|
30
|
+
* than string messages. This allows TypeScript to enforce that all validation
|
|
31
|
+
* errors are handled via {@link exhaustiveCheck}, significantly improving the
|
|
32
|
+
* developer experience. Those requirements are:
|
|
22
33
|
*
|
|
23
34
|
* - **Result-based error handling** – no exceptions for normal control flow.
|
|
24
35
|
* - **Typed errors with decoupled formatters** – validation logic ≠ user
|
|
@@ -31,15 +42,10 @@ import { IntentionalNever } from "./Types.js";
|
|
|
31
42
|
* bottom.
|
|
32
43
|
* - **No user-land chaining DSL** – prepared for TC39 Hack pipes.
|
|
33
44
|
*
|
|
34
|
-
* A distinctive feature of Evolu Type compared to other validation libraries is
|
|
35
|
-
* that it returns typed errors rather than string messages. This allows
|
|
36
|
-
* TypeScript to enforce that all validation errors are handled by type
|
|
37
|
-
* checking, significantly improving the developer experience.
|
|
38
|
-
*
|
|
39
45
|
* Evolu Type supports [Standard Schema](https://standardschema.dev/) for
|
|
40
46
|
* interoperability with 40+ validation-compatible tools and frameworks.
|
|
41
47
|
*
|
|
42
|
-
*
|
|
48
|
+
* ## Base Types
|
|
43
49
|
*
|
|
44
50
|
* ```ts
|
|
45
51
|
* // Validate unknown values
|
|
@@ -47,7 +53,7 @@ import { IntentionalNever } from "./Types.js";
|
|
|
47
53
|
* const stringResult = String.fromUnknown(value);
|
|
48
54
|
* if (!stringResult.ok) {
|
|
49
55
|
* // console.error(formatStringError(stringResult.error));
|
|
50
|
-
* return stringResult;
|
|
56
|
+
* return stringResult;
|
|
51
57
|
* }
|
|
52
58
|
* // Safe branch: value is now string
|
|
53
59
|
* const upper = stringResult.value.toUpperCase();
|
|
@@ -66,10 +72,12 @@ import { IntentionalNever } from "./Types.js";
|
|
|
66
72
|
* Point.from({ x: 1, y: "2" }); // err -> nested Number error
|
|
67
73
|
* ```
|
|
68
74
|
*
|
|
69
|
-
*
|
|
75
|
+
* ## Branded types
|
|
70
76
|
*
|
|
71
|
-
* Branding
|
|
72
|
-
*
|
|
77
|
+
* Branding is the recommended way to define types in Evolu. Instead of using
|
|
78
|
+
* primitive types like `string` or `number` directly, wrap them with
|
|
79
|
+
* {@link brand} to create semantically meaningful types. See {@link Brand} for
|
|
80
|
+
* why this matters.
|
|
73
81
|
*
|
|
74
82
|
* ```ts
|
|
75
83
|
* const CurrencyCode = brand("CurrencyCode", String, (value) =>
|
|
@@ -93,20 +101,25 @@ import { IntentionalNever } from "./Types.js";
|
|
|
93
101
|
* See also reusable brand factories like `minLength`, `maxLength`, `trimmed`,
|
|
94
102
|
* `positive`, `between`, etc.
|
|
95
103
|
*
|
|
96
|
-
*
|
|
104
|
+
* ## Object types
|
|
97
105
|
*
|
|
98
106
|
* ```ts
|
|
99
107
|
* const User = object({
|
|
100
108
|
* name: NonEmptyTrimmedString100,
|
|
101
109
|
* age: optional(PositiveInt),
|
|
102
110
|
* });
|
|
103
|
-
*
|
|
111
|
+
*
|
|
112
|
+
* // Use interface for objects. TypeScript displays the interface name
|
|
113
|
+
* // instead of expanding all properties.
|
|
114
|
+
* interface User extends InferType<typeof User> {}
|
|
104
115
|
*
|
|
105
116
|
* User.from({ name: "Alice" }); // ok
|
|
106
117
|
* User.from({ name: "Alice", age: -1 }); // err(PositiveInt)
|
|
118
|
+
*
|
|
119
|
+
* // TODO: Add `record`
|
|
107
120
|
* ```
|
|
108
121
|
*
|
|
109
|
-
*
|
|
122
|
+
* ## JSON type
|
|
110
123
|
*
|
|
111
124
|
* ```ts
|
|
112
125
|
* const Person = object({
|
|
@@ -115,7 +128,7 @@ import { IntentionalNever } from "./Types.js";
|
|
|
115
128
|
* // To prevent this, use FiniteNumber.
|
|
116
129
|
* age: FiniteNumber,
|
|
117
130
|
* });
|
|
118
|
-
*
|
|
131
|
+
* interface Person extends InferType<typeof Person> {}
|
|
119
132
|
*
|
|
120
133
|
* const [PersonJson, personToPersonJson, personJsonToPerson] = json(
|
|
121
134
|
* Person,
|
|
@@ -133,7 +146,7 @@ import { IntentionalNever } from "./Types.js";
|
|
|
133
146
|
* expect(personJsonToPerson(personJson)).toEqual(person);
|
|
134
147
|
* ```
|
|
135
148
|
*
|
|
136
|
-
*
|
|
149
|
+
* ## Error Formatting
|
|
137
150
|
*
|
|
138
151
|
* Evolu separates validation logic from human-readable messages. There are two
|
|
139
152
|
* layers:
|
|
@@ -144,14 +157,14 @@ import { IntentionalNever } from "./Types.js";
|
|
|
144
157
|
* custom errors (including nested composite types) and lets us override
|
|
145
158
|
* selected messages.
|
|
146
159
|
*
|
|
147
|
-
*
|
|
160
|
+
* ### 1. Per-Type formatter
|
|
148
161
|
*
|
|
149
162
|
* ```ts
|
|
150
163
|
* const r = String.fromUnknown(42);
|
|
151
164
|
* if (!r.ok) console.error(formatStringError(r.error));
|
|
152
165
|
* ```
|
|
153
166
|
*
|
|
154
|
-
*
|
|
167
|
+
* ### 2. Unified formatter with overrides
|
|
155
168
|
*
|
|
156
169
|
* ```ts
|
|
157
170
|
* // Override only what we care about; fall back to built-ins for the rest.
|
|
@@ -174,12 +187,14 @@ import { IntentionalNever } from "./Types.js";
|
|
|
174
187
|
* tuple / union) and applies overrides only where specified, greatly reducing
|
|
175
188
|
* boilerplate when formatting complex validation errors.
|
|
176
189
|
*
|
|
177
|
-
*
|
|
190
|
+
* ## Naming
|
|
178
191
|
*
|
|
179
|
-
*
|
|
180
|
-
*
|
|
192
|
+
* Evolu Types intentionally use the same names as native JavaScript types
|
|
193
|
+
* (String, Number, Boolean, etc.). When you need to distinguish between an
|
|
194
|
+
* Evolu Type and the native type, use `globalThis` to reference the native one
|
|
195
|
+
* (e.g., `globalThis.String`, `globalThis.Number`).
|
|
181
196
|
*
|
|
182
|
-
*
|
|
197
|
+
* ## Design decision
|
|
183
198
|
*
|
|
184
199
|
* Evolu Type intentionally does not support bidirectional transformations. It
|
|
185
200
|
* previously did, but supporting that while keeping typed error fidelity added
|
|
@@ -188,28 +203,62 @@ import { IntentionalNever } from "./Types.js";
|
|
|
188
203
|
* reverse transforms would not buy much. We may revisit this if we can design a
|
|
189
204
|
* minimal, 100% safe API that preserves simplicity.
|
|
190
205
|
*
|
|
191
|
-
*
|
|
206
|
+
* ## Composition without pipe
|
|
192
207
|
*
|
|
193
208
|
* Take a look how `SimplePassword` is defined:
|
|
194
209
|
*
|
|
195
210
|
* ```ts
|
|
196
|
-
*
|
|
211
|
+
* const SimplePassword = brand(
|
|
197
212
|
* "SimplePassword",
|
|
198
213
|
* minLength(8)(maxLength(64)(TrimmedString)),
|
|
199
214
|
* );
|
|
200
215
|
* ```
|
|
201
216
|
*
|
|
202
|
-
*
|
|
203
|
-
* TC39 Hack pipes it would be clearer:
|
|
217
|
+
* Shallow nesting often fits one line. If it doesn't, split into named parts:
|
|
204
218
|
*
|
|
205
219
|
* ```ts
|
|
206
|
-
*
|
|
207
|
-
*
|
|
208
|
-
*
|
|
209
|
-
*
|
|
220
|
+
* const Min8TrimmedString64 = minLength(8)(maxLength(64)(TrimmedString));
|
|
221
|
+
* const SimplePassword = brand("SimplePassword", Min8TrimmedString64);
|
|
222
|
+
* ```
|
|
223
|
+
*
|
|
224
|
+
* ## FAQ
|
|
225
|
+
*
|
|
226
|
+
* ### How do I create a generic interface like `FooState<T>`?
|
|
227
|
+
*
|
|
228
|
+
* TypeScript's {@link InferType} extracts a concrete type, not a generic one. We
|
|
229
|
+
* cannot write `interface FooState<T> extends InferType<typeof fooState<T>>`
|
|
230
|
+
* because `InferType` needs a concrete Type instance.
|
|
231
|
+
*
|
|
232
|
+
* The recommended approach is to define the generic interface manually, then
|
|
233
|
+
* create a Type factory that produces structurally compatible Types:
|
|
234
|
+
*
|
|
235
|
+
* ```ts
|
|
236
|
+
* // Define the generic interface manually
|
|
237
|
+
* interface FooState<T> {
|
|
238
|
+
* readonly value: T;
|
|
239
|
+
* readonly loading: boolean;
|
|
240
|
+
* }
|
|
241
|
+
*
|
|
242
|
+
* // Create a Type factory that produces Types matching the interface
|
|
243
|
+
* const fooState = <T extends AnyType>(valueType: T) =>
|
|
244
|
+
* object({
|
|
245
|
+
* value: valueType,
|
|
246
|
+
* loading: Boolean,
|
|
247
|
+
* });
|
|
248
|
+
*
|
|
249
|
+
* // Usage
|
|
250
|
+
* const StringFooState = fooState(String);
|
|
251
|
+
* type StringFooState = InferType<typeof StringFooState>;
|
|
252
|
+
*
|
|
253
|
+
* // The interface and inferred type are structurally compatible
|
|
254
|
+
* const state: FooState<string> = StringFooState.orThrow({
|
|
255
|
+
* value: "hi",
|
|
256
|
+
* loading: false,
|
|
257
|
+
* });
|
|
210
258
|
* ```
|
|
211
259
|
*
|
|
212
|
-
*
|
|
260
|
+
* This keeps the interface generic while having type-safe runtime validation
|
|
261
|
+
* for each concrete use.
|
|
213
262
|
*/
|
|
214
263
|
export interface Type<
|
|
215
264
|
Name extends TypeName,
|
|
@@ -238,6 +287,9 @@ export interface Type<
|
|
|
238
287
|
/**
|
|
239
288
|
* Creates `T` from an `Input` value, throwing an error if validation fails.
|
|
240
289
|
*
|
|
290
|
+
* Use this where failure should crash the current flow instead of being
|
|
291
|
+
* handled locally.
|
|
292
|
+
*
|
|
241
293
|
* Throws an Error with the Type validation error in its `cause` property,
|
|
242
294
|
* making it debuggable while avoiding the need for custom error messages.
|
|
243
295
|
*
|
|
@@ -245,36 +297,41 @@ export interface Type<
|
|
|
245
297
|
*
|
|
246
298
|
* **When to use:**
|
|
247
299
|
*
|
|
248
|
-
* -
|
|
249
|
-
*
|
|
250
|
-
*
|
|
300
|
+
* - Application startup or composition-root setup where errors must stop the
|
|
301
|
+
* program immediately. In Evolu apps, errors are handled by
|
|
302
|
+
* platform-specific `createRun` adapters at the app boundary.
|
|
303
|
+
* - Module-level constants
|
|
304
|
+
* - Test setup with values that are expected to be valid
|
|
251
305
|
* - As an alternative to assertions when the Type error in the thrown Error's
|
|
252
306
|
* `cause` provides sufficient debugging information
|
|
253
|
-
*
|
|
254
|
-
*
|
|
255
|
-
*
|
|
307
|
+
*
|
|
308
|
+
* Prefer `from` in ordinary application logic where the caller can recover,
|
|
309
|
+
* show validation errors, or choose a different flow.
|
|
310
|
+
*
|
|
311
|
+
* For clearer test failure messages on invalid input, use Vitest
|
|
312
|
+
* `schemaMatching` + `assert` with `.is()`.
|
|
256
313
|
*
|
|
257
314
|
* ### Example
|
|
258
315
|
*
|
|
259
316
|
* ```ts
|
|
260
|
-
* //
|
|
317
|
+
* // Good: Known valid constant
|
|
261
318
|
* const maxRetries = PositiveInt.orThrow(3);
|
|
262
319
|
*
|
|
263
|
-
* //
|
|
264
|
-
* const appName =
|
|
320
|
+
* // Good: App configuration that should crash on invalid values
|
|
321
|
+
* const appName = Name.orThrow("MyApp");
|
|
265
322
|
*
|
|
266
|
-
* //
|
|
323
|
+
* // Good: Instead of assert when Type error is clear enough
|
|
267
324
|
* // Context makes it obvious: count increments from non-negative value
|
|
268
325
|
* const currentCount = counts.get(id) ?? 0;
|
|
269
326
|
* const newCount = PositiveInt.orThrow(currentCount + 1);
|
|
270
327
|
*
|
|
271
|
-
* //
|
|
328
|
+
* // Good: Test setup with known valid values
|
|
272
329
|
* const testUser = User.orThrow({ name: "Alice", age: 30 });
|
|
273
330
|
*
|
|
274
|
-
* //
|
|
331
|
+
* // Avoid: User input (use `from` instead)
|
|
275
332
|
* const userAge = PositiveInt.orThrow(userInput); // Could crash!
|
|
276
333
|
*
|
|
277
|
-
* //
|
|
334
|
+
* // Better: Handle user input gracefully
|
|
278
335
|
* const ageResult = PositiveInt.from(userInput);
|
|
279
336
|
* if (!ageResult.ok) {
|
|
280
337
|
* // Handle validation error
|
|
@@ -296,16 +353,16 @@ export interface Type<
|
|
|
296
353
|
* ### Example
|
|
297
354
|
*
|
|
298
355
|
* ```ts
|
|
299
|
-
* //
|
|
356
|
+
* // Good: Optional user input
|
|
300
357
|
* const age = PositiveInt.orNull(userInput);
|
|
301
358
|
* if (age != null) {
|
|
302
359
|
* console.log("Valid age:", age);
|
|
303
360
|
* }
|
|
304
361
|
*
|
|
305
|
-
* //
|
|
362
|
+
* // Good: Default fallback
|
|
306
363
|
* const maxRetries = PositiveInt.orNull(config.retries) ?? 3;
|
|
307
364
|
*
|
|
308
|
-
* //
|
|
365
|
+
* // Avoid: When you need to know why validation failed (use `from` instead)
|
|
309
366
|
* const result = PositiveInt.from(userInput);
|
|
310
367
|
* if (!result.ok) {
|
|
311
368
|
* console.error(formatPositiveError(result.error));
|
|
@@ -420,7 +477,7 @@ export interface Type<
|
|
|
420
477
|
/**
|
|
421
478
|
* Unique identifier for a {@link Type}.
|
|
422
479
|
*
|
|
423
|
-
* @
|
|
480
|
+
* @group Utilities
|
|
424
481
|
*/
|
|
425
482
|
export type TypeName = Capitalize<string>;
|
|
426
483
|
|
|
@@ -444,12 +501,17 @@ export interface TypeErrorWithReason<
|
|
|
444
501
|
readonly reason: Reason;
|
|
445
502
|
}
|
|
446
503
|
|
|
504
|
+
/**
|
|
505
|
+
* A {@link Type} with all type parameters set to `any`.
|
|
506
|
+
*
|
|
507
|
+
* @group Utilities
|
|
508
|
+
*/
|
|
447
509
|
export type AnyType = Type<any, any, any, any, any, any>;
|
|
448
510
|
|
|
449
511
|
/**
|
|
450
512
|
* Extracts the name from a {@link Type}.
|
|
451
513
|
*
|
|
452
|
-
* @
|
|
514
|
+
* @group Utilities
|
|
453
515
|
*/
|
|
454
516
|
export type InferName<A extends AnyType> =
|
|
455
517
|
A extends Type<infer Name, any, any, any, any, any> ? Name : never;
|
|
@@ -457,7 +519,20 @@ export type InferName<A extends AnyType> =
|
|
|
457
519
|
/**
|
|
458
520
|
* Extracts the type from a {@link Type}.
|
|
459
521
|
*
|
|
460
|
-
*
|
|
522
|
+
* ### Example
|
|
523
|
+
*
|
|
524
|
+
* ```ts
|
|
525
|
+
* const User = object({
|
|
526
|
+
* name: NonEmptyTrimmedString100,
|
|
527
|
+
* age: optional(PositiveInt),
|
|
528
|
+
* });
|
|
529
|
+
*
|
|
530
|
+
* // Use interface for objects. TypeScript displays the interface name
|
|
531
|
+
* // instead of expanding all properties.
|
|
532
|
+
* interface User extends InferType<typeof User> {}
|
|
533
|
+
* ```
|
|
534
|
+
*
|
|
535
|
+
* @group Utilities
|
|
461
536
|
*/
|
|
462
537
|
export type InferType<A extends AnyType> =
|
|
463
538
|
A extends Type<any, infer T, any, any, any, any> ? T : never;
|
|
@@ -465,7 +540,7 @@ export type InferType<A extends AnyType> =
|
|
|
465
540
|
/**
|
|
466
541
|
* Extracts the input type from a {@link Type}.
|
|
467
542
|
*
|
|
468
|
-
* @
|
|
543
|
+
* @group Utilities
|
|
469
544
|
*/
|
|
470
545
|
export type InferInput<A extends AnyType> =
|
|
471
546
|
A extends Type<any, any, infer Input, any, any, any> ? Input : never;
|
|
@@ -473,7 +548,7 @@ export type InferInput<A extends AnyType> =
|
|
|
473
548
|
/**
|
|
474
549
|
* Extracts the specific error type from a {@link Type}.
|
|
475
550
|
*
|
|
476
|
-
* @
|
|
551
|
+
* @group Utilities
|
|
477
552
|
*/
|
|
478
553
|
export type InferError<A extends AnyType> =
|
|
479
554
|
A extends Type<any, any, any, infer Error, any, any> ? Error : never;
|
|
@@ -481,7 +556,7 @@ export type InferError<A extends AnyType> =
|
|
|
481
556
|
/**
|
|
482
557
|
* Extracts the parent type from a {@link Type}.
|
|
483
558
|
*
|
|
484
|
-
* @
|
|
559
|
+
* @group Utilities
|
|
485
560
|
*/
|
|
486
561
|
export type InferParent<A extends AnyType> =
|
|
487
562
|
A extends Type<any, any, any, any, infer Parent, any> ? Parent : never;
|
|
@@ -489,7 +564,7 @@ export type InferParent<A extends AnyType> =
|
|
|
489
564
|
/**
|
|
490
565
|
* Extracts the parent error type from a {@link Type}.
|
|
491
566
|
*
|
|
492
|
-
* @
|
|
567
|
+
* @group Utilities
|
|
493
568
|
*/
|
|
494
569
|
export type InferParentError<A extends AnyType> =
|
|
495
570
|
A extends Type<any, any, any, any, any, infer ParentError>
|
|
@@ -499,7 +574,7 @@ export type InferParentError<A extends AnyType> =
|
|
|
499
574
|
/**
|
|
500
575
|
* Extracts all error types from a {@link Type}.
|
|
501
576
|
*
|
|
502
|
-
* @
|
|
577
|
+
* @group Utilities
|
|
503
578
|
*/
|
|
504
579
|
export type InferErrors<T extends AnyType> =
|
|
505
580
|
T extends Type<any, any, any, infer Error, any, infer ParentError>
|
|
@@ -511,7 +586,7 @@ const EvoluTypeSymbol = Symbol("evolu.Type");
|
|
|
511
586
|
/**
|
|
512
587
|
* Checks if the given value is an {@link Type}.
|
|
513
588
|
*
|
|
514
|
-
* @
|
|
589
|
+
* @group Utilities
|
|
515
590
|
*/
|
|
516
591
|
export const isType = (value: unknown): value is AnyType =>
|
|
517
592
|
typeof value === "object" && value !== null && EvoluTypeSymbol in value;
|
|
@@ -563,11 +638,9 @@ const createType = <
|
|
|
563
638
|
if (result.ok) {
|
|
564
639
|
return { value: result.value };
|
|
565
640
|
}
|
|
566
|
-
cachedStandardSchemaFormatTypeError ??= createFormatTypeError();
|
|
567
641
|
return {
|
|
568
642
|
issues: typeErrorToStandardSchemaIssues(
|
|
569
643
|
result.error as TypeErrors<Error>,
|
|
570
|
-
cachedStandardSchemaFormatTypeError,
|
|
571
644
|
),
|
|
572
645
|
};
|
|
573
646
|
},
|
|
@@ -592,7 +665,7 @@ const createType = <
|
|
|
592
665
|
* );
|
|
593
666
|
* ```
|
|
594
667
|
*
|
|
595
|
-
* @
|
|
668
|
+
* @group Utilities
|
|
596
669
|
*/
|
|
597
670
|
export const createTypeErrorFormatter =
|
|
598
671
|
<Error extends TypeError>(
|
|
@@ -628,7 +701,7 @@ export type TypeErrorFormatter<Error extends TypeError> = (
|
|
|
628
701
|
* );
|
|
629
702
|
* ```
|
|
630
703
|
*
|
|
631
|
-
* @
|
|
704
|
+
* @group Base factories
|
|
632
705
|
*/
|
|
633
706
|
export const base = <Name extends TypeName, T, Error extends TypeError>(
|
|
634
707
|
name: Name,
|
|
@@ -652,7 +725,7 @@ export const base = <Name extends TypeName, T, Error extends TypeError>(
|
|
|
652
725
|
* createBaseTypeErrorFormatter<StringError>();
|
|
653
726
|
* ```
|
|
654
727
|
*
|
|
655
|
-
* @
|
|
728
|
+
* @group Utilities
|
|
656
729
|
*/
|
|
657
730
|
export const createBaseTypeErrorFormatter = <
|
|
658
731
|
Error extends TypeError,
|
|
@@ -661,14 +734,17 @@ export const createBaseTypeErrorFormatter = <
|
|
|
661
734
|
(error) => `A value ${error.value} is not a ${error.type.toLowerCase()}.`,
|
|
662
735
|
);
|
|
663
736
|
|
|
664
|
-
/** @
|
|
665
|
-
export const Unknown = base<"Unknown", unknown, never>(
|
|
737
|
+
/** @group Base Types */
|
|
738
|
+
export const Unknown = /*#__PURE__*/ base<"Unknown", unknown, never>(
|
|
739
|
+
"Unknown",
|
|
740
|
+
ok,
|
|
741
|
+
);
|
|
666
742
|
|
|
667
743
|
/**
|
|
668
|
-
* @
|
|
669
|
-
* @
|
|
744
|
+
* @group Base Types
|
|
745
|
+
* @group String
|
|
670
746
|
*/
|
|
671
|
-
export const String = base("String", (value) =>
|
|
747
|
+
export const String = /*#__PURE__*/ base("String", (value) =>
|
|
672
748
|
typeof value === "string"
|
|
673
749
|
? ok(value)
|
|
674
750
|
: err<StringError>({ type: "String", value }),
|
|
@@ -676,10 +752,11 @@ export const String = base("String", (value) =>
|
|
|
676
752
|
|
|
677
753
|
export interface StringError extends TypeError<"String"> {}
|
|
678
754
|
|
|
679
|
-
export const formatStringError =
|
|
755
|
+
export const formatStringError =
|
|
756
|
+
/*#__PURE__*/ createBaseTypeErrorFormatter<StringError>();
|
|
680
757
|
|
|
681
|
-
/** @
|
|
682
|
-
export const Number = base("Number", (value) =>
|
|
758
|
+
/** @group Base Types */
|
|
759
|
+
export const Number = /*#__PURE__*/ base("Number", (value) =>
|
|
683
760
|
typeof value === "number"
|
|
684
761
|
? ok(value)
|
|
685
762
|
: err<NumberError>({ type: "Number", value }),
|
|
@@ -687,10 +764,11 @@ export const Number = base("Number", (value) =>
|
|
|
687
764
|
|
|
688
765
|
export interface NumberError extends TypeError<"Number"> {}
|
|
689
766
|
|
|
690
|
-
export const formatNumberError =
|
|
767
|
+
export const formatNumberError =
|
|
768
|
+
/*#__PURE__*/ createBaseTypeErrorFormatter<NumberError>();
|
|
691
769
|
|
|
692
|
-
/** @
|
|
693
|
-
export const BigInt = base("BigInt", (value) =>
|
|
770
|
+
/** @group Base Types */
|
|
771
|
+
export const BigInt = /*#__PURE__*/ base("BigInt", (value) =>
|
|
694
772
|
typeof value === "bigint"
|
|
695
773
|
? ok(value)
|
|
696
774
|
: err<BigIntError>({ type: "BigInt", value }),
|
|
@@ -698,10 +776,11 @@ export const BigInt = base("BigInt", (value) =>
|
|
|
698
776
|
|
|
699
777
|
export interface BigIntError extends TypeError<"BigInt"> {}
|
|
700
778
|
|
|
701
|
-
export const formatBigIntError =
|
|
779
|
+
export const formatBigIntError =
|
|
780
|
+
/*#__PURE__*/ createBaseTypeErrorFormatter<BigIntError>();
|
|
702
781
|
|
|
703
|
-
/** @
|
|
704
|
-
export const Boolean = base("Boolean", (value) =>
|
|
782
|
+
/** @group Base Types */
|
|
783
|
+
export const Boolean = /*#__PURE__*/ base("Boolean", (value) =>
|
|
705
784
|
typeof value === "boolean"
|
|
706
785
|
? ok(value)
|
|
707
786
|
: err<BooleanError>({ type: "Boolean", value }),
|
|
@@ -709,10 +788,11 @@ export const Boolean = base("Boolean", (value) =>
|
|
|
709
788
|
|
|
710
789
|
export interface BooleanError extends TypeError<"Boolean"> {}
|
|
711
790
|
|
|
712
|
-
export const formatBooleanError =
|
|
791
|
+
export const formatBooleanError =
|
|
792
|
+
/*#__PURE__*/ createBaseTypeErrorFormatter<BooleanError>();
|
|
713
793
|
|
|
714
|
-
/** @
|
|
715
|
-
export const Undefined = base("Undefined", (value) =>
|
|
794
|
+
/** @group Base Types */
|
|
795
|
+
export const Undefined = /*#__PURE__*/ base("Undefined", (value) =>
|
|
716
796
|
value === undefined
|
|
717
797
|
? ok(value)
|
|
718
798
|
: err<UndefinedError>({ type: "Undefined", value }),
|
|
@@ -721,20 +801,21 @@ export const Undefined = base("Undefined", (value) =>
|
|
|
721
801
|
export interface UndefinedError extends TypeError<"Undefined"> {}
|
|
722
802
|
|
|
723
803
|
export const formatUndefinedError =
|
|
724
|
-
createBaseTypeErrorFormatter<UndefinedError>();
|
|
804
|
+
/*#__PURE__*/ createBaseTypeErrorFormatter<UndefinedError>();
|
|
725
805
|
|
|
726
|
-
/** @
|
|
727
|
-
export const Null = base("Null", (value) =>
|
|
806
|
+
/** @group Base Types */
|
|
807
|
+
export const Null = /*#__PURE__*/ base("Null", (value) =>
|
|
728
808
|
value === null ? ok(value) : err<NullError>({ type: "Null", value }),
|
|
729
809
|
);
|
|
730
810
|
|
|
731
811
|
export interface NullError extends TypeError<"Null"> {}
|
|
732
812
|
|
|
733
|
-
export const formatNullError =
|
|
813
|
+
export const formatNullError =
|
|
814
|
+
/*#__PURE__*/ createBaseTypeErrorFormatter<NullError>();
|
|
734
815
|
|
|
735
|
-
/** @
|
|
736
|
-
export const Function = base("Function", (value) =>
|
|
737
|
-
|
|
816
|
+
/** @group Base Types */
|
|
817
|
+
export const Function = /*#__PURE__*/ base("Function", (value) =>
|
|
818
|
+
isFunction(value)
|
|
738
819
|
? ok(value)
|
|
739
820
|
: err<FunctionError>({ type: "Function", value }),
|
|
740
821
|
);
|
|
@@ -742,19 +823,31 @@ export const Function = base("Function", (value) =>
|
|
|
742
823
|
export interface FunctionError extends TypeError<"Function"> {}
|
|
743
824
|
|
|
744
825
|
export const formatFunctionError =
|
|
745
|
-
createBaseTypeErrorFormatter<FunctionError>();
|
|
826
|
+
/*#__PURE__*/ createBaseTypeErrorFormatter<FunctionError>();
|
|
746
827
|
|
|
747
|
-
/** @
|
|
748
|
-
export const Uint8Array = base("Uint8Array", (value) =>
|
|
749
|
-
value
|
|
750
|
-
? ok(value)
|
|
828
|
+
/** @group Base Types */
|
|
829
|
+
export const Uint8Array = /*#__PURE__*/ base("Uint8Array", (value) =>
|
|
830
|
+
Object.prototype.toString.call(value as object) === "[object Uint8Array]"
|
|
831
|
+
? ok(value as globalThis.Uint8Array)
|
|
751
832
|
: err<Uint8ArrayError>({ type: "Uint8Array", value }),
|
|
752
833
|
);
|
|
753
834
|
|
|
754
835
|
export interface Uint8ArrayError extends TypeError<"Uint8Array"> {}
|
|
755
836
|
|
|
756
837
|
export const formatUint8ArrayError =
|
|
757
|
-
createBaseTypeErrorFormatter<Uint8ArrayError>();
|
|
838
|
+
/*#__PURE__*/ createBaseTypeErrorFormatter<Uint8ArrayError>();
|
|
839
|
+
|
|
840
|
+
/** @group Base Types */
|
|
841
|
+
export const ArrayBuffer = /*#__PURE__*/ base("ArrayBuffer", (value) =>
|
|
842
|
+
Object.prototype.toString.call(value as object) === "[object ArrayBuffer]"
|
|
843
|
+
? ok(value as globalThis.ArrayBuffer)
|
|
844
|
+
: err<ArrayBufferError>({ type: "ArrayBuffer", value }),
|
|
845
|
+
);
|
|
846
|
+
|
|
847
|
+
export interface ArrayBufferError extends TypeError<"ArrayBuffer"> {}
|
|
848
|
+
|
|
849
|
+
export const formatArrayBufferError =
|
|
850
|
+
/*#__PURE__*/ createBaseTypeErrorFormatter<ArrayBufferError>();
|
|
758
851
|
|
|
759
852
|
/**
|
|
760
853
|
* `instanceof` {@link Type}.
|
|
@@ -774,7 +867,7 @@ export const formatUint8ArrayError =
|
|
|
774
867
|
* const error = UserInstance.from({}); // err
|
|
775
868
|
* ```
|
|
776
869
|
*
|
|
777
|
-
* @
|
|
870
|
+
* @group Base factories
|
|
778
871
|
*/
|
|
779
872
|
export const instanceOf = <T extends abstract new (...args: any) => any>(
|
|
780
873
|
ctor: T,
|
|
@@ -802,16 +895,17 @@ export interface InstanceOfType<
|
|
|
802
895
|
ctor: T;
|
|
803
896
|
}
|
|
804
897
|
|
|
805
|
-
export const formatInstanceOfError =
|
|
806
|
-
(
|
|
807
|
-
)
|
|
898
|
+
export const formatInstanceOfError =
|
|
899
|
+
/*#__PURE__*/ createTypeErrorFormatter<InstanceOfError>(
|
|
900
|
+
(error) => `The value ${error.value} is not an instance of ${error.ctor}.`,
|
|
901
|
+
);
|
|
808
902
|
|
|
809
903
|
/**
|
|
810
904
|
* JavaScript Date.
|
|
811
905
|
*
|
|
812
|
-
* @
|
|
906
|
+
* @group Base Types
|
|
813
907
|
*/
|
|
814
|
-
export const Date = instanceOf(globalThis.Date);
|
|
908
|
+
export const Date = /*#__PURE__*/ instanceOf(globalThis.Date);
|
|
815
909
|
|
|
816
910
|
/**
|
|
817
911
|
* Validates that an unknown value is an Evolu {@link Type} (i.e., satisfies
|
|
@@ -825,7 +919,7 @@ export const Date = instanceOf(globalThis.Date);
|
|
|
825
919
|
* ```
|
|
826
920
|
*/
|
|
827
921
|
// TODO: Rename to TypeInstance or something like that.
|
|
828
|
-
export const EvoluType = base("EvoluType", (value) =>
|
|
922
|
+
export const EvoluType = /*#__PURE__*/ base("EvoluType", (value) =>
|
|
829
923
|
isType(value)
|
|
830
924
|
? ok(value)
|
|
831
925
|
: err<EvoluTypeError>({
|
|
@@ -836,19 +930,24 @@ export const EvoluType = base("EvoluType", (value) =>
|
|
|
836
930
|
|
|
837
931
|
export interface EvoluTypeError extends TypeError<"EvoluType"> {}
|
|
838
932
|
|
|
839
|
-
export const formatIsTypeError =
|
|
840
|
-
(
|
|
841
|
-
)
|
|
933
|
+
export const formatIsTypeError =
|
|
934
|
+
/*#__PURE__*/ createTypeErrorFormatter<EvoluTypeError>(
|
|
935
|
+
(error) => `Value ${error.value} is not a valid Evolu Type.`,
|
|
936
|
+
);
|
|
842
937
|
|
|
843
938
|
/**
|
|
844
939
|
* Branded {@link Type}.
|
|
845
940
|
*
|
|
941
|
+
* Branding is the recommended way to define types in Evolu. Instead of using
|
|
942
|
+
* primitive types like `string` or `number` directly, wrap them with `brand` to
|
|
943
|
+
* create semantically meaningful types. See {@link Brand} for why this matters.
|
|
944
|
+
*
|
|
846
945
|
* The `brand` Type Factory takes the name of a new {@link Brand}, a parent Type
|
|
847
946
|
* to be branded, and the optional `refine` function for additional constraint.
|
|
848
947
|
*
|
|
849
948
|
* The `refine` function can be omitted if we only want to add a brand.
|
|
850
949
|
*
|
|
851
|
-
* ###
|
|
950
|
+
* ### Example
|
|
852
951
|
*
|
|
853
952
|
* A simple `CurrencyCode` Type:
|
|
854
953
|
*
|
|
@@ -967,7 +1066,7 @@ export const formatIsTypeError = createTypeErrorFormatter<EvoluTypeError>(
|
|
|
967
1066
|
* );
|
|
968
1067
|
* ```
|
|
969
1068
|
*
|
|
970
|
-
* @
|
|
1069
|
+
* @group Base factories
|
|
971
1070
|
*/
|
|
972
1071
|
export function brand<
|
|
973
1072
|
Name extends TypeName,
|
|
@@ -980,6 +1079,7 @@ export function brand<
|
|
|
980
1079
|
refine: (value: Parent) => Result<Parent, RefineError>,
|
|
981
1080
|
): BrandType<ParentType, Name, RefineError, InferErrors<ParentType>>;
|
|
982
1081
|
|
|
1082
|
+
/** Without refine function. */
|
|
983
1083
|
export function brand<Name extends TypeName, ParentType extends AnyType>(
|
|
984
1084
|
name: Name,
|
|
985
1085
|
parent: ParentType,
|
|
@@ -1003,15 +1103,15 @@ export function brand<
|
|
|
1003
1103
|
? (value: unknown) => {
|
|
1004
1104
|
const parentResult = parent.fromUnknown(value);
|
|
1005
1105
|
if (!parentResult.ok) return parentResult;
|
|
1006
|
-
return refine(parentResult.value as
|
|
1106
|
+
return refine(parentResult.value as never);
|
|
1007
1107
|
}
|
|
1008
1108
|
: (value: unknown) => {
|
|
1009
1109
|
const parentResult = parent.fromUnknown(value);
|
|
1010
1110
|
if (!parentResult.ok)
|
|
1011
|
-
return err<BrandWithoutRefineError<Name,
|
|
1111
|
+
return err<BrandWithoutRefineError<Name, never>>({
|
|
1012
1112
|
type: name,
|
|
1013
1113
|
value,
|
|
1014
|
-
parentError: parentResult.error as
|
|
1114
|
+
parentError: parentResult.error as never,
|
|
1015
1115
|
});
|
|
1016
1116
|
return ok(parentResult.value);
|
|
1017
1117
|
};
|
|
@@ -1019,7 +1119,7 @@ export function brand<
|
|
|
1019
1119
|
return {
|
|
1020
1120
|
...createType("Brand", {
|
|
1021
1121
|
fromUnknown,
|
|
1022
|
-
fromParent: (refine ?? ok) as
|
|
1122
|
+
fromParent: (refine ?? ok) as never,
|
|
1023
1123
|
}),
|
|
1024
1124
|
brand: name,
|
|
1025
1125
|
parentType: parent,
|
|
@@ -1053,12 +1153,15 @@ export interface BrandWithoutRefineError<
|
|
|
1053
1153
|
/**
|
|
1054
1154
|
* A three-letter ISO 4217 currency code (e.g., USD, EUR).
|
|
1055
1155
|
*
|
|
1056
|
-
* @
|
|
1156
|
+
* @group String
|
|
1057
1157
|
*/
|
|
1058
|
-
export const CurrencyCode = brand(
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1158
|
+
export const CurrencyCode = /*#__PURE__*/ brand(
|
|
1159
|
+
"CurrencyCode",
|
|
1160
|
+
String,
|
|
1161
|
+
(value) =>
|
|
1162
|
+
/^[A-Z]{3}$/.test(value)
|
|
1163
|
+
? ok(value)
|
|
1164
|
+
: err<CurrencyCodeError>({ type: "CurrencyCode", value }),
|
|
1062
1165
|
);
|
|
1063
1166
|
|
|
1064
1167
|
export type CurrencyCode = typeof CurrencyCode.Type;
|
|
@@ -1066,7 +1169,7 @@ export type CurrencyCode = typeof CurrencyCode.Type;
|
|
|
1066
1169
|
export interface CurrencyCodeError extends TypeError<"CurrencyCode"> {}
|
|
1067
1170
|
|
|
1068
1171
|
export const formatCurrencyCodeError =
|
|
1069
|
-
createTypeErrorFormatter<CurrencyCodeError>(
|
|
1172
|
+
/*#__PURE__*/ createTypeErrorFormatter<CurrencyCodeError>(
|
|
1070
1173
|
(error) => `Invalid currency code: ${error.value}.`,
|
|
1071
1174
|
);
|
|
1072
1175
|
|
|
@@ -1089,9 +1192,9 @@ export const formatCurrencyCodeError =
|
|
|
1089
1192
|
* const error = DateIso.from("10000-01-01T00:00:00.000Z"); // err
|
|
1090
1193
|
* ```
|
|
1091
1194
|
*
|
|
1092
|
-
* @
|
|
1195
|
+
* @group String
|
|
1093
1196
|
*/
|
|
1094
|
-
export const DateIso = brand("DateIso", String, (value) => {
|
|
1197
|
+
export const DateIso = /*#__PURE__*/ brand("DateIso", String, (value) => {
|
|
1095
1198
|
if (value.length !== 24) {
|
|
1096
1199
|
return err<DateIsoError>({ type: "DateIso", value });
|
|
1097
1200
|
}
|
|
@@ -1111,9 +1214,10 @@ export type DateIso = typeof DateIso.Type;
|
|
|
1111
1214
|
|
|
1112
1215
|
export interface DateIsoError extends TypeError<"DateIso"> {}
|
|
1113
1216
|
|
|
1114
|
-
export const formatDateIsoError =
|
|
1115
|
-
(
|
|
1116
|
-
)
|
|
1217
|
+
export const formatDateIsoError =
|
|
1218
|
+
/*#__PURE__*/ createTypeErrorFormatter<DateIsoError>(
|
|
1219
|
+
(error) => `The value ${error.value} is not a valid ISO 8601 date string.`,
|
|
1220
|
+
);
|
|
1117
1221
|
|
|
1118
1222
|
export const dateToDateIso = (value: Date): Result<DateIso, DateIsoError> =>
|
|
1119
1223
|
DateIso.fromParent(value.toISOString());
|
|
@@ -1137,7 +1241,18 @@ export const dateIsoToDate = (value: DateIso): Date =>
|
|
|
1137
1241
|
* );
|
|
1138
1242
|
* ```
|
|
1139
1243
|
*
|
|
1140
|
-
*
|
|
1244
|
+
* ### Numeric literal inference
|
|
1245
|
+
*
|
|
1246
|
+
* When using factories with numeric parameters (like `lessThan`, `maxLength`,
|
|
1247
|
+
* `between`), use numeric literals instead of expressions. TypeScript widens
|
|
1248
|
+
* expressions to `number`, losing the literal type in the brand name:
|
|
1249
|
+
*
|
|
1250
|
+
* ```ts
|
|
1251
|
+
* lessThan(100)(Number); // Brand<"LessThan100"> ✓
|
|
1252
|
+
* lessThan(100 - 1)(Number); // Brand<"LessThan" + number> ✗
|
|
1253
|
+
* ```
|
|
1254
|
+
*
|
|
1255
|
+
* @group Utilities
|
|
1141
1256
|
*/
|
|
1142
1257
|
export type BrandFactory<
|
|
1143
1258
|
Name extends TypeName,
|
|
@@ -1173,7 +1288,7 @@ export type BrandFactory<
|
|
|
1173
1288
|
* type TrimmedNonEmptyString = typeof TrimmedNonEmptyString.Type;
|
|
1174
1289
|
* ```
|
|
1175
1290
|
*
|
|
1176
|
-
* @
|
|
1291
|
+
* @group String
|
|
1177
1292
|
*/
|
|
1178
1293
|
export const trimmed: BrandFactory<"Trimmed", string, TrimmedError> = (
|
|
1179
1294
|
parent,
|
|
@@ -1186,9 +1301,10 @@ export const trimmed: BrandFactory<"Trimmed", string, TrimmedError> = (
|
|
|
1186
1301
|
|
|
1187
1302
|
export interface TrimmedError extends TypeError<"Trimmed"> {}
|
|
1188
1303
|
|
|
1189
|
-
export const formatTrimmedError =
|
|
1190
|
-
(
|
|
1191
|
-
)
|
|
1304
|
+
export const formatTrimmedError =
|
|
1305
|
+
/*#__PURE__*/ createTypeErrorFormatter<TrimmedError>(
|
|
1306
|
+
(error) => `The value ${error.value} must be trimmed.`,
|
|
1307
|
+
);
|
|
1192
1308
|
|
|
1193
1309
|
/**
|
|
1194
1310
|
* Trimmed string
|
|
@@ -1196,9 +1312,9 @@ export const formatTrimmedError = createTypeErrorFormatter<TrimmedError>(
|
|
|
1196
1312
|
* - Use `TrimmedString.is` to check if an unknown value is trimmed.
|
|
1197
1313
|
* - Use `TrimmedString.from` to check if a string is trimmed.
|
|
1198
1314
|
*
|
|
1199
|
-
* @
|
|
1315
|
+
* @group String
|
|
1200
1316
|
*/
|
|
1201
|
-
export const TrimmedString = trimmed(String);
|
|
1317
|
+
export const TrimmedString = /*#__PURE__*/ trimmed(String);
|
|
1202
1318
|
export type TrimmedString = typeof TrimmedString.Type;
|
|
1203
1319
|
|
|
1204
1320
|
export const trim = (value: string): TrimmedString =>
|
|
@@ -1207,6 +1323,8 @@ export const trim = (value: string): TrimmedString =>
|
|
|
1207
1323
|
/**
|
|
1208
1324
|
* Minimum length.
|
|
1209
1325
|
*
|
|
1326
|
+
* Use numeric literal, not expression. See {@link BrandFactory}.
|
|
1327
|
+
*
|
|
1210
1328
|
* ### Example
|
|
1211
1329
|
*
|
|
1212
1330
|
* ```ts
|
|
@@ -1214,8 +1332,8 @@ export const trim = (value: string): TrimmedString =>
|
|
|
1214
1332
|
* const NonEmptyString = minLength(1)(String);
|
|
1215
1333
|
* ```
|
|
1216
1334
|
*
|
|
1217
|
-
* @
|
|
1218
|
-
* @
|
|
1335
|
+
* @group String
|
|
1336
|
+
* @group Array
|
|
1219
1337
|
*/
|
|
1220
1338
|
export const minLength: <Min extends number>(
|
|
1221
1339
|
min: Min,
|
|
@@ -1231,14 +1349,17 @@ export interface MinLengthError<
|
|
|
1231
1349
|
readonly min: Min;
|
|
1232
1350
|
}
|
|
1233
1351
|
|
|
1234
|
-
export const formatMinLengthError =
|
|
1235
|
-
(
|
|
1236
|
-
|
|
1237
|
-
|
|
1352
|
+
export const formatMinLengthError =
|
|
1353
|
+
/*#__PURE__*/ createTypeErrorFormatter<MinLengthError>(
|
|
1354
|
+
(error) =>
|
|
1355
|
+
`The value ${error.value} does not meet the minimum length of ${error.min}.`,
|
|
1356
|
+
);
|
|
1238
1357
|
|
|
1239
1358
|
/**
|
|
1240
1359
|
* Maximum length.
|
|
1241
1360
|
*
|
|
1361
|
+
* Use numeric literal, not expression. See {@link BrandFactory}.
|
|
1362
|
+
*
|
|
1242
1363
|
* ### Example
|
|
1243
1364
|
*
|
|
1244
1365
|
* ```ts
|
|
@@ -1246,8 +1367,8 @@ export const formatMinLengthError = createTypeErrorFormatter<MinLengthError>(
|
|
|
1246
1367
|
* const String100 = maxLength(100)(String);
|
|
1247
1368
|
* ```
|
|
1248
1369
|
*
|
|
1249
|
-
* @
|
|
1250
|
-
* @
|
|
1370
|
+
* @group String
|
|
1371
|
+
* @group Array
|
|
1251
1372
|
*/
|
|
1252
1373
|
export const maxLength: <Max extends number>(
|
|
1253
1374
|
max: Max,
|
|
@@ -1263,14 +1384,17 @@ export interface MaxLengthError<
|
|
|
1263
1384
|
readonly max: Max;
|
|
1264
1385
|
}
|
|
1265
1386
|
|
|
1266
|
-
export const formatMaxLengthError =
|
|
1267
|
-
(
|
|
1268
|
-
|
|
1269
|
-
|
|
1387
|
+
export const formatMaxLengthError =
|
|
1388
|
+
/*#__PURE__*/ createTypeErrorFormatter<MaxLengthError>(
|
|
1389
|
+
(error) =>
|
|
1390
|
+
`The value ${error.value} exceeds the maximum length of ${error.max}.`,
|
|
1391
|
+
);
|
|
1270
1392
|
|
|
1271
1393
|
/**
|
|
1272
1394
|
* Exact length.
|
|
1273
1395
|
*
|
|
1396
|
+
* Use numeric literal, not expression. See {@link BrandFactory}.
|
|
1397
|
+
*
|
|
1274
1398
|
* ### Example
|
|
1275
1399
|
*
|
|
1276
1400
|
* ```ts
|
|
@@ -1278,8 +1402,8 @@ export const formatMaxLengthError = createTypeErrorFormatter<MaxLengthError>(
|
|
|
1278
1402
|
* const Length1String = length(1)(String);
|
|
1279
1403
|
* ```
|
|
1280
1404
|
*
|
|
1281
|
-
* @
|
|
1282
|
-
* @
|
|
1405
|
+
* @group String
|
|
1406
|
+
* @group Array
|
|
1283
1407
|
*/
|
|
1284
1408
|
export const length: <Exact extends number>(
|
|
1285
1409
|
exact: Exact,
|
|
@@ -1297,49 +1421,52 @@ export interface LengthError<
|
|
|
1297
1421
|
readonly exact: Exact;
|
|
1298
1422
|
}
|
|
1299
1423
|
|
|
1300
|
-
export const formatLengthError =
|
|
1301
|
-
(
|
|
1302
|
-
|
|
1303
|
-
|
|
1424
|
+
export const formatLengthError =
|
|
1425
|
+
/*#__PURE__*/ createTypeErrorFormatter<LengthError>(
|
|
1426
|
+
(error) =>
|
|
1427
|
+
`The value ${error.value} does not have the required length of ${error.exact}.`,
|
|
1428
|
+
);
|
|
1304
1429
|
|
|
1305
|
-
/** @
|
|
1306
|
-
export const NonEmptyString = minLength(1)(String);
|
|
1430
|
+
/** @group String */
|
|
1431
|
+
export const NonEmptyString = /*#__PURE__*/ minLength(1)(String);
|
|
1307
1432
|
export type NonEmptyString = typeof NonEmptyString.Type;
|
|
1308
1433
|
|
|
1309
|
-
/** @
|
|
1310
|
-
export const String100 = maxLength(100)(String);
|
|
1434
|
+
/** @group String */
|
|
1435
|
+
export const String100 = /*#__PURE__*/ maxLength(100)(String);
|
|
1311
1436
|
export type String100 = typeof String100.Type;
|
|
1312
1437
|
|
|
1313
|
-
/** @
|
|
1314
|
-
export const String1000 = maxLength(1000)(String);
|
|
1438
|
+
/** @group String */
|
|
1439
|
+
export const String1000 = /*#__PURE__*/ maxLength(1000)(String);
|
|
1315
1440
|
export type String1000 = typeof String1000.Type;
|
|
1316
1441
|
|
|
1317
|
-
/** @
|
|
1318
|
-
export const NonEmptyString100 = minLength(1)(String100);
|
|
1442
|
+
/** @group String */
|
|
1443
|
+
export const NonEmptyString100 = /*#__PURE__*/ minLength(1)(String100);
|
|
1319
1444
|
export type NonEmptyString100 = typeof NonEmptyString100.Type;
|
|
1320
1445
|
|
|
1321
|
-
/** @
|
|
1322
|
-
export const NonEmptyString1000 = minLength(1)(String1000);
|
|
1446
|
+
/** @group String */
|
|
1447
|
+
export const NonEmptyString1000 = /*#__PURE__*/ minLength(1)(String1000);
|
|
1323
1448
|
export type NonEmptyString1000 = typeof NonEmptyString1000.Type;
|
|
1324
1449
|
|
|
1325
|
-
/** @
|
|
1326
|
-
export const NonEmptyTrimmedString = minLength(1)(TrimmedString);
|
|
1450
|
+
/** @group String */
|
|
1451
|
+
export const NonEmptyTrimmedString = /*#__PURE__*/ minLength(1)(TrimmedString);
|
|
1327
1452
|
export type NonEmptyTrimmedString = typeof NonEmptyTrimmedString.Type;
|
|
1328
1453
|
|
|
1329
|
-
/** @
|
|
1330
|
-
export const TrimmedString100 = maxLength(100)(TrimmedString);
|
|
1454
|
+
/** @group String */
|
|
1455
|
+
export const TrimmedString100 = /*#__PURE__*/ maxLength(100)(TrimmedString);
|
|
1331
1456
|
export type TrimmedString100 = typeof TrimmedString100.Type;
|
|
1332
1457
|
|
|
1333
|
-
/** @
|
|
1334
|
-
export const TrimmedString1000 = maxLength(1000)(TrimmedString);
|
|
1458
|
+
/** @group String */
|
|
1459
|
+
export const TrimmedString1000 = /*#__PURE__*/ maxLength(1000)(TrimmedString);
|
|
1335
1460
|
export type TrimmedString1000 = typeof TrimmedString1000.Type;
|
|
1336
1461
|
|
|
1337
|
-
/** @
|
|
1338
|
-
export const NonEmptyTrimmedString100 =
|
|
1462
|
+
/** @group String */
|
|
1463
|
+
export const NonEmptyTrimmedString100 =
|
|
1464
|
+
/*#__PURE__*/ minLength(1)(TrimmedString100);
|
|
1339
1465
|
export type NonEmptyTrimmedString100 = typeof NonEmptyTrimmedString100.Type;
|
|
1340
1466
|
|
|
1341
|
-
/** @
|
|
1342
|
-
export const NonEmptyTrimmedString1000 =
|
|
1467
|
+
/** @group String */
|
|
1468
|
+
export const NonEmptyTrimmedString1000 =
|
|
1469
|
+
/*#__PURE__*/ minLength(1)(TrimmedString1000);
|
|
1343
1470
|
export type NonEmptyTrimmedString1000 = typeof NonEmptyTrimmedString1000.Type;
|
|
1344
1471
|
|
|
1345
1472
|
/**
|
|
@@ -1349,20 +1476,24 @@ export type NonEmptyTrimmedString1000 = typeof NonEmptyTrimmedString1000.Type;
|
|
|
1349
1476
|
* safely on the user's device using cryptographically secure random number
|
|
1350
1477
|
* generation, ensuring it remains private and unique.
|
|
1351
1478
|
*
|
|
1352
|
-
* @
|
|
1479
|
+
* @group String
|
|
1353
1480
|
*/
|
|
1354
|
-
export const Mnemonic = brand(
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1481
|
+
export const Mnemonic = /*#__PURE__*/ brand(
|
|
1482
|
+
"Mnemonic",
|
|
1483
|
+
NonEmptyTrimmedString,
|
|
1484
|
+
(value) =>
|
|
1485
|
+
bip39.validateMnemonic(value, wordlist)
|
|
1486
|
+
? ok(value)
|
|
1487
|
+
: err<MnemonicError>({ type: "Mnemonic", value }),
|
|
1358
1488
|
);
|
|
1359
1489
|
export type Mnemonic = typeof Mnemonic.Type;
|
|
1360
1490
|
|
|
1361
1491
|
export interface MnemonicError extends TypeError<"Mnemonic"> {}
|
|
1362
1492
|
|
|
1363
|
-
export const formatMnemonicError =
|
|
1364
|
-
(
|
|
1365
|
-
)
|
|
1493
|
+
export const formatMnemonicError =
|
|
1494
|
+
/*#__PURE__*/ createTypeErrorFormatter<MnemonicError>(
|
|
1495
|
+
(error) => `Invalid BIP39 mnemonic: ${error.value}.`,
|
|
1496
|
+
);
|
|
1366
1497
|
|
|
1367
1498
|
/**
|
|
1368
1499
|
* String matching a regular expression.
|
|
@@ -1373,7 +1504,7 @@ export const formatMnemonicError = createTypeErrorFormatter<MnemonicError>(
|
|
|
1373
1504
|
* const Alphanumeric = regex("Alphanumeric", /^[a-z0-9]+$/i)(String);
|
|
1374
1505
|
* ```
|
|
1375
1506
|
*
|
|
1376
|
-
* @
|
|
1507
|
+
* @group String
|
|
1377
1508
|
*/
|
|
1378
1509
|
export const regex: <Name extends TypeName>(
|
|
1379
1510
|
name: Name,
|
|
@@ -1399,10 +1530,11 @@ export interface RegexError<
|
|
|
1399
1530
|
readonly pattern: RegExp;
|
|
1400
1531
|
}
|
|
1401
1532
|
|
|
1402
|
-
export const formatRegexError =
|
|
1403
|
-
(
|
|
1404
|
-
|
|
1405
|
-
|
|
1533
|
+
export const formatRegexError =
|
|
1534
|
+
/*#__PURE__*/ createTypeErrorFormatter<RegexError>(
|
|
1535
|
+
(error) =>
|
|
1536
|
+
`The value ${error.value} does not match the pattern for ${error.name}: ${error.pattern}.`,
|
|
1537
|
+
);
|
|
1406
1538
|
|
|
1407
1539
|
/**
|
|
1408
1540
|
* URL-safe string.
|
|
@@ -1429,9 +1561,12 @@ export const formatRegexError = createTypeErrorFormatter<RegexError>(
|
|
|
1429
1561
|
* }
|
|
1430
1562
|
* ```
|
|
1431
1563
|
*
|
|
1432
|
-
* @
|
|
1564
|
+
* @group String
|
|
1433
1565
|
*/
|
|
1434
|
-
export const UrlSafeString = regex(
|
|
1566
|
+
export const UrlSafeString = /*#__PURE__*/ regex(
|
|
1567
|
+
"UrlSafeString",
|
|
1568
|
+
/^[A-Za-z0-9_-]+$/,
|
|
1569
|
+
)(String);
|
|
1435
1570
|
export type UrlSafeString = typeof UrlSafeString.Type;
|
|
1436
1571
|
export type UrlSafeStringError = typeof UrlSafeString.Error;
|
|
1437
1572
|
|
|
@@ -1441,9 +1576,9 @@ export type UrlSafeStringError = typeof UrlSafeString.Error;
|
|
|
1441
1576
|
* Encode with {@link uint8ArrayToBase64Url}, decode with
|
|
1442
1577
|
* {@link base64UrlToUint8Array}.
|
|
1443
1578
|
*
|
|
1444
|
-
* @
|
|
1579
|
+
* @group String
|
|
1445
1580
|
*/
|
|
1446
|
-
export const Base64Url = brand(
|
|
1581
|
+
export const Base64Url = /*#__PURE__*/ brand(
|
|
1447
1582
|
"Base64Url",
|
|
1448
1583
|
String,
|
|
1449
1584
|
(value: string): Result<string, Base64UrlError> => {
|
|
@@ -1466,9 +1601,10 @@ export const Base64Url = brand(
|
|
|
1466
1601
|
export type Base64Url = typeof Base64Url.Type;
|
|
1467
1602
|
export interface Base64UrlError extends TypeError<"Base64Url"> {}
|
|
1468
1603
|
|
|
1469
|
-
export const formatBase64UrlError =
|
|
1470
|
-
(
|
|
1471
|
-
)
|
|
1604
|
+
export const formatBase64UrlError =
|
|
1605
|
+
/*#__PURE__*/ createTypeErrorFormatter<Base64UrlError>(
|
|
1606
|
+
(error) => `The value ${error.value} is not a valid Base64Url string.`,
|
|
1607
|
+
);
|
|
1472
1608
|
|
|
1473
1609
|
const base64UrlOptions = { alphabet: "base64url", omitPadding: true };
|
|
1474
1610
|
|
|
@@ -1520,33 +1656,35 @@ export const base64UrlToUint8Array: (str: Base64Url) => Uint8Array =
|
|
|
1520
1656
|
};
|
|
1521
1657
|
|
|
1522
1658
|
/**
|
|
1523
|
-
*
|
|
1659
|
+
* Alphanumeric string for naming in file systems, URLs, and identifiers.
|
|
1524
1660
|
*
|
|
1525
1661
|
* Uses the same safe alphabet as {@link UrlSafeString} (letters, digits, `-`,
|
|
1526
|
-
* `_`).
|
|
1662
|
+
* `_`).
|
|
1527
1663
|
*
|
|
1528
1664
|
* The string must be between 1 and 64 characters.
|
|
1529
1665
|
*
|
|
1530
1666
|
* ### Example
|
|
1531
1667
|
*
|
|
1532
1668
|
* ```ts
|
|
1533
|
-
* const result =
|
|
1669
|
+
* const result = Name.from("data-report-123");
|
|
1534
1670
|
* if (result.ok) {
|
|
1535
|
-
* console.log("Valid
|
|
1671
|
+
* console.log("Valid Name string:", result.value);
|
|
1536
1672
|
* } else {
|
|
1537
|
-
* console.error("Invalid
|
|
1673
|
+
* console.error("Invalid Name string:", result.error);
|
|
1538
1674
|
* }
|
|
1539
1675
|
* ```
|
|
1540
1676
|
*
|
|
1541
|
-
* @
|
|
1677
|
+
* @group String
|
|
1542
1678
|
*/
|
|
1543
|
-
export const
|
|
1679
|
+
export const Name = /*#__PURE__*/ brand("Name", UrlSafeString, (value) =>
|
|
1544
1680
|
value.length >= 1 && value.length <= 64
|
|
1545
1681
|
? ok(value)
|
|
1546
|
-
: err<
|
|
1682
|
+
: err<NameError>({ type: "Name", value }),
|
|
1547
1683
|
);
|
|
1548
|
-
export type
|
|
1549
|
-
export interface
|
|
1684
|
+
export type Name = typeof Name.Type;
|
|
1685
|
+
export interface NameError extends TypeError<"Name"> {}
|
|
1686
|
+
|
|
1687
|
+
export const testName = /*#__PURE__*/ Name.orThrow("Name");
|
|
1550
1688
|
|
|
1551
1689
|
/**
|
|
1552
1690
|
* Trimmed string between 8 and 64 characters, branded as `SimplePassword`.
|
|
@@ -1570,11 +1708,11 @@ export interface SimpleNameError extends TypeError<"SimpleName"> {}
|
|
|
1570
1708
|
* // |> brand("SimplePassword", %)
|
|
1571
1709
|
* ```
|
|
1572
1710
|
*
|
|
1573
|
-
* @
|
|
1711
|
+
* @group String
|
|
1574
1712
|
*/
|
|
1575
|
-
export const SimplePassword = brand(
|
|
1713
|
+
export const SimplePassword = /*#__PURE__*/ brand(
|
|
1576
1714
|
"SimplePassword",
|
|
1577
|
-
minLength(8)(maxLength(64)(TrimmedString)),
|
|
1715
|
+
/*#__PURE__*/ minLength(8)(/*#__PURE__*/ maxLength(64)(TrimmedString)),
|
|
1578
1716
|
);
|
|
1579
1717
|
export type SimplePassword = typeof SimplePassword.Type;
|
|
1580
1718
|
|
|
@@ -1605,16 +1743,16 @@ export const formatSimplePasswordError = (
|
|
|
1605
1743
|
* copied (logs, URLs, exports); only use it when you explicitly want insertion
|
|
1606
1744
|
* locality for very large write‑heavy tables and accept timestamp exposure.
|
|
1607
1745
|
*
|
|
1608
|
-
*
|
|
1746
|
+
* ## Future
|
|
1609
1747
|
*
|
|
1610
1748
|
* A possible hybrid masked‑time approach (`timestamp ^ H(cluster_id, timestamp
|
|
1611
1749
|
*
|
|
1612
1750
|
* > > N)`) could provide locality without exposing raw creation time. See
|
|
1613
1751
|
* > > https://brooker.co.za/blog/2025/10/22/uuidv7.html
|
|
1614
1752
|
*
|
|
1615
|
-
* @
|
|
1753
|
+
* @group String
|
|
1616
1754
|
*/
|
|
1617
|
-
export const Id = brand("Id", String, (value) =>
|
|
1755
|
+
export const Id = /*#__PURE__*/ brand("Id", String, (value) =>
|
|
1618
1756
|
value.length === 22 && Base64Url.fromParent(value).ok
|
|
1619
1757
|
? ok(value)
|
|
1620
1758
|
: err<IdError>({ type: "Id", value }),
|
|
@@ -1623,7 +1761,7 @@ export type Id = typeof Id.Type;
|
|
|
1623
1761
|
|
|
1624
1762
|
export interface IdError extends TypeError<"Id"> {}
|
|
1625
1763
|
|
|
1626
|
-
export const formatIdError = createTypeErrorFormatter<IdError>(
|
|
1764
|
+
export const formatIdError = /*#__PURE__*/ createTypeErrorFormatter<IdError>(
|
|
1627
1765
|
(error) => `The value ${error.value} is not a valid Id.`,
|
|
1628
1766
|
);
|
|
1629
1767
|
|
|
@@ -1678,7 +1816,7 @@ export const createId = <B extends string = never>(
|
|
|
1678
1816
|
* external string from the generated {@link Id}. If you need to preserve the
|
|
1679
1817
|
* original external ID, store it in a separate column.
|
|
1680
1818
|
*
|
|
1681
|
-
* @
|
|
1819
|
+
* @group String
|
|
1682
1820
|
*/
|
|
1683
1821
|
export const createIdFromString = <B extends string = never>(
|
|
1684
1822
|
value: string,
|
|
@@ -1723,7 +1861,9 @@ export const createIdAsUuidv7 = <B extends string = never>(
|
|
|
1723
1861
|
id[6] = (id[6] & 0x0f) | 0x70;
|
|
1724
1862
|
id[8] = (id[8] & 0x3f) | 0x80;
|
|
1725
1863
|
|
|
1726
|
-
return id as unknown as [B] extends [never]
|
|
1864
|
+
return uint8ArrayToBase64Url(id) as unknown as [B] extends [never]
|
|
1865
|
+
? Id
|
|
1866
|
+
: Id & Brand<B>;
|
|
1727
1867
|
};
|
|
1728
1868
|
|
|
1729
1869
|
/**
|
|
@@ -1739,7 +1879,7 @@ export const createIdAsUuidv7 = <B extends string = never>(
|
|
|
1739
1879
|
* type TodoId = typeof TodoId.Type;
|
|
1740
1880
|
* ```
|
|
1741
1881
|
*
|
|
1742
|
-
* @
|
|
1882
|
+
* @group String
|
|
1743
1883
|
*/
|
|
1744
1884
|
export const id = <Table extends TypeName>(table: Table): TableId<Table> => {
|
|
1745
1885
|
const fromUnknown = (value: unknown) => {
|
|
@@ -1779,12 +1919,16 @@ export interface TableIdError<
|
|
|
1779
1919
|
readonly table: Table;
|
|
1780
1920
|
}
|
|
1781
1921
|
|
|
1782
|
-
export const formatTableIdError =
|
|
1783
|
-
(
|
|
1784
|
-
)
|
|
1922
|
+
export const formatTableIdError =
|
|
1923
|
+
/*#__PURE__*/ createTypeErrorFormatter<TableIdError>(
|
|
1924
|
+
(error) => `Invalid Id for table ${error.table}: ${error.value}.`,
|
|
1925
|
+
);
|
|
1785
1926
|
|
|
1786
1927
|
/** Binary representation of an {@link Id}. */
|
|
1787
|
-
export const IdBytes = brand(
|
|
1928
|
+
export const IdBytes = /*#__PURE__*/ brand(
|
|
1929
|
+
"IdBytes",
|
|
1930
|
+
/*#__PURE__*/ length(16)(Uint8Array),
|
|
1931
|
+
);
|
|
1788
1932
|
export type IdBytes = typeof IdBytes.Type;
|
|
1789
1933
|
|
|
1790
1934
|
export const idBytesTypeValueLength = 16 as NonNegativeInt;
|
|
@@ -1809,7 +1953,7 @@ export const idBytesToId = (idBytes: IdBytes): Id =>
|
|
|
1809
1953
|
* const errorResult = PositiveNumber.from(-5); // err
|
|
1810
1954
|
* ```
|
|
1811
1955
|
*
|
|
1812
|
-
* @
|
|
1956
|
+
* @group Number
|
|
1813
1957
|
*/
|
|
1814
1958
|
export const positive: BrandFactory<"Positive", number, PositiveError> = (
|
|
1815
1959
|
parent,
|
|
@@ -1820,9 +1964,10 @@ export const positive: BrandFactory<"Positive", number, PositiveError> = (
|
|
|
1820
1964
|
|
|
1821
1965
|
export interface PositiveError extends TypeError<"Positive"> {}
|
|
1822
1966
|
|
|
1823
|
-
export const formatPositiveError =
|
|
1824
|
-
|
|
1825
|
-
)
|
|
1967
|
+
export const formatPositiveError =
|
|
1968
|
+
/*#__PURE__*/ createTypeErrorFormatter<PositiveError>(
|
|
1969
|
+
(error) => `The value ${error.value} must be positive (> 0).`,
|
|
1970
|
+
);
|
|
1826
1971
|
|
|
1827
1972
|
/**
|
|
1828
1973
|
* Negative number (< 0).
|
|
@@ -1833,7 +1978,7 @@ export const formatPositiveError = createTypeErrorFormatter<PositiveError>(
|
|
|
1833
1978
|
* const NegativeNumber = negative(Number);
|
|
1834
1979
|
* ```
|
|
1835
1980
|
*
|
|
1836
|
-
* @
|
|
1981
|
+
* @group Number
|
|
1837
1982
|
*/
|
|
1838
1983
|
export const negative: BrandFactory<"Negative", number, NegativeError> = (
|
|
1839
1984
|
parent,
|
|
@@ -1844,9 +1989,10 @@ export const negative: BrandFactory<"Negative", number, NegativeError> = (
|
|
|
1844
1989
|
|
|
1845
1990
|
export interface NegativeError extends TypeError<"Negative"> {}
|
|
1846
1991
|
|
|
1847
|
-
export const formatNegativeError =
|
|
1848
|
-
|
|
1849
|
-
)
|
|
1992
|
+
export const formatNegativeError =
|
|
1993
|
+
/*#__PURE__*/ createTypeErrorFormatter<NegativeError>(
|
|
1994
|
+
(error) => `The value ${error.value} must be negative (< 0).`,
|
|
1995
|
+
);
|
|
1850
1996
|
|
|
1851
1997
|
/**
|
|
1852
1998
|
* Non-positive number (≤ 0).
|
|
@@ -1857,7 +2003,7 @@ export const formatNegativeError = createTypeErrorFormatter<NegativeError>(
|
|
|
1857
2003
|
* const NonPositiveNumber = nonPositive(Number);
|
|
1858
2004
|
* ```
|
|
1859
2005
|
*
|
|
1860
|
-
* @
|
|
2006
|
+
* @group Number
|
|
1861
2007
|
*/
|
|
1862
2008
|
export const nonPositive: BrandFactory<
|
|
1863
2009
|
"NonPositive",
|
|
@@ -1873,7 +2019,7 @@ export const nonPositive: BrandFactory<
|
|
|
1873
2019
|
export interface NonPositiveError extends TypeError<"NonPositive"> {}
|
|
1874
2020
|
|
|
1875
2021
|
export const formatNonPositiveError =
|
|
1876
|
-
createTypeErrorFormatter<NonPositiveError>(
|
|
2022
|
+
/*#__PURE__*/ createTypeErrorFormatter<NonPositiveError>(
|
|
1877
2023
|
(error) => `The value ${error.value} must be non-positive (≤ 0).`,
|
|
1878
2024
|
);
|
|
1879
2025
|
|
|
@@ -1886,7 +2032,7 @@ export const formatNonPositiveError =
|
|
|
1886
2032
|
* const NonNegativeNumber = nonNegative(Number);
|
|
1887
2033
|
* ```
|
|
1888
2034
|
*
|
|
1889
|
-
* @
|
|
2035
|
+
* @group Number
|
|
1890
2036
|
*/
|
|
1891
2037
|
export const nonNegative: BrandFactory<
|
|
1892
2038
|
"NonNegative",
|
|
@@ -1902,40 +2048,40 @@ export const nonNegative: BrandFactory<
|
|
|
1902
2048
|
export interface NonNegativeError extends TypeError<"NonNegative"> {}
|
|
1903
2049
|
|
|
1904
2050
|
export const formatNonNegativeError =
|
|
1905
|
-
createTypeErrorFormatter<NonNegativeError>(
|
|
2051
|
+
/*#__PURE__*/ createTypeErrorFormatter<NonNegativeError>(
|
|
1906
2052
|
(error) => `The value ${error.value} must be non-negative (≥ 0).`,
|
|
1907
2053
|
);
|
|
1908
2054
|
|
|
1909
2055
|
/**
|
|
1910
2056
|
* Non-negative number (≥ 0).
|
|
1911
2057
|
*
|
|
1912
|
-
* @
|
|
2058
|
+
* @group Number
|
|
1913
2059
|
*/
|
|
1914
|
-
export const NonNegativeNumber = nonNegative(Number);
|
|
2060
|
+
export const NonNegativeNumber = /*#__PURE__*/ nonNegative(Number);
|
|
1915
2061
|
export type NonNegativeNumber = typeof NonNegativeNumber.Type;
|
|
1916
2062
|
|
|
1917
2063
|
/**
|
|
1918
2064
|
* Positive number (> 0).
|
|
1919
2065
|
*
|
|
1920
|
-
* @
|
|
2066
|
+
* @group Number
|
|
1921
2067
|
*/
|
|
1922
|
-
export const PositiveNumber = positive(NonNegativeNumber);
|
|
2068
|
+
export const PositiveNumber = /*#__PURE__*/ positive(NonNegativeNumber);
|
|
1923
2069
|
export type PositiveNumber = typeof PositiveNumber.Type;
|
|
1924
2070
|
|
|
1925
2071
|
/**
|
|
1926
2072
|
* Non-positive number (≤ 0).
|
|
1927
2073
|
*
|
|
1928
|
-
* @
|
|
2074
|
+
* @group Number
|
|
1929
2075
|
*/
|
|
1930
|
-
export const NonPositiveNumber = nonPositive(Number);
|
|
2076
|
+
export const NonPositiveNumber = /*#__PURE__*/ nonPositive(Number);
|
|
1931
2077
|
export type NonPositiveNumber = typeof NonPositiveNumber.Type;
|
|
1932
2078
|
|
|
1933
2079
|
/**
|
|
1934
2080
|
* Negative number (< 0).
|
|
1935
2081
|
*
|
|
1936
|
-
* @
|
|
2082
|
+
* @group Number
|
|
1937
2083
|
*/
|
|
1938
|
-
export const NegativeNumber = negative(NonPositiveNumber);
|
|
2084
|
+
export const NegativeNumber = /*#__PURE__*/ negative(NonPositiveNumber);
|
|
1939
2085
|
export type NegativeNumber = typeof NegativeNumber.Type;
|
|
1940
2086
|
|
|
1941
2087
|
/**
|
|
@@ -1944,12 +2090,17 @@ export type NegativeNumber = typeof NegativeNumber.Type;
|
|
|
1944
2090
|
* ### Example
|
|
1945
2091
|
*
|
|
1946
2092
|
* ```ts
|
|
1947
|
-
* const Int =
|
|
2093
|
+
* const Int = safeInt(Number);
|
|
1948
2094
|
* ```
|
|
1949
2095
|
*
|
|
1950
|
-
*
|
|
2096
|
+
* Note: This helper was previously named `int`. A Next.js regression produced
|
|
2097
|
+
* runtime `int is not defined`, so we keep the implementation under the
|
|
2098
|
+
* `safeInt` alias.
|
|
2099
|
+
*
|
|
2100
|
+
* @group Number
|
|
1951
2101
|
*/
|
|
1952
|
-
|
|
2102
|
+
|
|
2103
|
+
const safeInt: BrandFactory<"Int", number, IntError> = (parent) =>
|
|
1953
2104
|
brand("Int", parent, (value) =>
|
|
1954
2105
|
globalThis.Number.isSafeInteger(value)
|
|
1955
2106
|
? ok(value)
|
|
@@ -1958,59 +2109,67 @@ export const int: BrandFactory<"Int", number, IntError> = (parent) =>
|
|
|
1958
2109
|
|
|
1959
2110
|
export interface IntError extends TypeError<"Int"> {}
|
|
1960
2111
|
|
|
1961
|
-
export const formatIntError = createTypeErrorFormatter<IntError>(
|
|
2112
|
+
export const formatIntError = /*#__PURE__*/ createTypeErrorFormatter<IntError>(
|
|
1962
2113
|
(error) => `The value ${error.value} must be an integer.`,
|
|
1963
2114
|
);
|
|
1964
2115
|
|
|
1965
2116
|
/**
|
|
1966
2117
|
* Integer within the safe range of JavaScript numbers.
|
|
1967
2118
|
*
|
|
1968
|
-
* @
|
|
2119
|
+
* @group Number
|
|
1969
2120
|
*/
|
|
1970
|
-
export const Int =
|
|
2121
|
+
export const Int = /*#__PURE__*/ safeInt(Number);
|
|
1971
2122
|
export type Int = typeof Int.Type;
|
|
1972
2123
|
|
|
1973
2124
|
/**
|
|
1974
2125
|
* Non-negative integer (≥ 0).
|
|
1975
2126
|
*
|
|
1976
|
-
* @
|
|
2127
|
+
* @group Number
|
|
1977
2128
|
*/
|
|
1978
|
-
export const NonNegativeInt = nonNegative(Int);
|
|
2129
|
+
export const NonNegativeInt = /*#__PURE__*/ nonNegative(Int);
|
|
1979
2130
|
export type NonNegativeInt = typeof NonNegativeInt.Type;
|
|
1980
2131
|
|
|
2132
|
+
/** Minimum {@link NonNegativeInt} value (0). */
|
|
2133
|
+
export const zeroNonNegativeInt = /*#__PURE__*/ NonNegativeInt.orThrow(0);
|
|
2134
|
+
|
|
1981
2135
|
/**
|
|
1982
2136
|
* Positive integer (> 0).
|
|
1983
2137
|
*
|
|
1984
|
-
* @
|
|
2138
|
+
* @group Number
|
|
1985
2139
|
*/
|
|
1986
|
-
export const PositiveInt = positive(NonNegativeInt);
|
|
2140
|
+
export const PositiveInt = /*#__PURE__*/ positive(NonNegativeInt);
|
|
1987
2141
|
export type PositiveInt = typeof PositiveInt.Type;
|
|
1988
2142
|
|
|
1989
|
-
/**
|
|
1990
|
-
export const
|
|
2143
|
+
/** {@link PositiveInt} value 1. */
|
|
2144
|
+
export const onePositiveInt = /*#__PURE__*/ PositiveInt.orThrow(1);
|
|
2145
|
+
|
|
2146
|
+
/** Maximum {@link PositiveInt} value (MAX_SAFE_INTEGER). */
|
|
2147
|
+
export const maxPositiveInt = /*#__PURE__*/ PositiveInt.orThrow(
|
|
1991
2148
|
globalThis.Number.MAX_SAFE_INTEGER,
|
|
1992
2149
|
);
|
|
1993
2150
|
|
|
1994
2151
|
/**
|
|
1995
2152
|
* Non-positive integer (≤ 0).
|
|
1996
2153
|
*
|
|
1997
|
-
* @
|
|
2154
|
+
* @group Number
|
|
1998
2155
|
*/
|
|
1999
|
-
export const NonPositiveInt = nonPositive(Int);
|
|
2156
|
+
export const NonPositiveInt = /*#__PURE__*/ nonPositive(Int);
|
|
2000
2157
|
export type NonPositiveInt = typeof NonPositiveInt.Type;
|
|
2001
2158
|
|
|
2002
2159
|
/**
|
|
2003
2160
|
* Negative integer (< 0).
|
|
2004
2161
|
*
|
|
2005
|
-
* @
|
|
2162
|
+
* @group Number
|
|
2006
2163
|
*/
|
|
2007
|
-
export const NegativeInt = negative(NonPositiveInt);
|
|
2164
|
+
export const NegativeInt = /*#__PURE__*/ negative(NonPositiveInt);
|
|
2008
2165
|
export type NegativeInt = typeof NegativeInt.Type;
|
|
2009
2166
|
|
|
2010
2167
|
/**
|
|
2011
2168
|
* Number greater than a specified value.
|
|
2012
2169
|
*
|
|
2013
|
-
* @
|
|
2170
|
+
* Use numeric literal, not expression. See {@link BrandFactory}.
|
|
2171
|
+
*
|
|
2172
|
+
* @group Number
|
|
2014
2173
|
*/
|
|
2015
2174
|
export const greaterThan: <Min extends number>(
|
|
2016
2175
|
min: Min,
|
|
@@ -2027,14 +2186,16 @@ export interface GreaterThanError<
|
|
|
2027
2186
|
}
|
|
2028
2187
|
|
|
2029
2188
|
export const formatGreaterThanError =
|
|
2030
|
-
createTypeErrorFormatter<GreaterThanError>(
|
|
2189
|
+
/*#__PURE__*/ createTypeErrorFormatter<GreaterThanError>(
|
|
2031
2190
|
(error) => `The value ${error.value} is not > ${error.min}.`,
|
|
2032
2191
|
);
|
|
2033
2192
|
|
|
2034
2193
|
/**
|
|
2035
2194
|
* Number less than a specified value.
|
|
2036
2195
|
*
|
|
2037
|
-
* @
|
|
2196
|
+
* Use numeric literal, not expression. See {@link BrandFactory}.
|
|
2197
|
+
*
|
|
2198
|
+
* @group Number
|
|
2038
2199
|
*/
|
|
2039
2200
|
export const lessThan: <Max extends number>(
|
|
2040
2201
|
max: Max,
|
|
@@ -2050,14 +2211,17 @@ export interface LessThanError<
|
|
|
2050
2211
|
readonly max: Max;
|
|
2051
2212
|
}
|
|
2052
2213
|
|
|
2053
|
-
export const formatLessThanError =
|
|
2054
|
-
|
|
2055
|
-
)
|
|
2214
|
+
export const formatLessThanError =
|
|
2215
|
+
/*#__PURE__*/ createTypeErrorFormatter<LessThanError>(
|
|
2216
|
+
(error) => `The value ${error.value} is not < ${error.max}.`,
|
|
2217
|
+
);
|
|
2056
2218
|
|
|
2057
2219
|
/**
|
|
2058
2220
|
* Number ≥ a specified value.
|
|
2059
2221
|
*
|
|
2060
|
-
* @
|
|
2222
|
+
* Use numeric literal, not expression. See {@link BrandFactory}.
|
|
2223
|
+
*
|
|
2224
|
+
* @group Number
|
|
2061
2225
|
*/
|
|
2062
2226
|
export const greaterThanOrEqualTo: <Min extends number>(
|
|
2063
2227
|
min: Min,
|
|
@@ -2079,14 +2243,16 @@ export interface GreaterThanOrEqualToError<
|
|
|
2079
2243
|
}
|
|
2080
2244
|
|
|
2081
2245
|
export const formatGreaterThanOrEqualToError =
|
|
2082
|
-
createTypeErrorFormatter<GreaterThanOrEqualToError>(
|
|
2246
|
+
/*#__PURE__*/ createTypeErrorFormatter<GreaterThanOrEqualToError>(
|
|
2083
2247
|
(error) => `The value ${error.value} is not >= ${error.min}.`,
|
|
2084
2248
|
);
|
|
2085
2249
|
|
|
2086
2250
|
/**
|
|
2087
2251
|
* Number ≤ a specified value.
|
|
2088
2252
|
*
|
|
2089
|
-
* @
|
|
2253
|
+
* Use numeric literal, not expression. See {@link BrandFactory}.
|
|
2254
|
+
*
|
|
2255
|
+
* @group Number
|
|
2090
2256
|
*/
|
|
2091
2257
|
export const lessThanOrEqualTo: <Max extends number>(
|
|
2092
2258
|
max: Max,
|
|
@@ -2106,14 +2272,14 @@ export interface LessThanOrEqualToError<
|
|
|
2106
2272
|
}
|
|
2107
2273
|
|
|
2108
2274
|
export const formatLessThanOrEqualToError =
|
|
2109
|
-
createTypeErrorFormatter<LessThanOrEqualToError>(
|
|
2275
|
+
/*#__PURE__*/ createTypeErrorFormatter<LessThanOrEqualToError>(
|
|
2110
2276
|
(error) => `The value ${error.value} is not <= ${error.max}.`,
|
|
2111
2277
|
);
|
|
2112
2278
|
|
|
2113
2279
|
/**
|
|
2114
2280
|
* Number that is not NaN.
|
|
2115
2281
|
*
|
|
2116
|
-
* @
|
|
2282
|
+
* @group Number
|
|
2117
2283
|
*/
|
|
2118
2284
|
export const nonNaN: BrandFactory<"NonNaN", number, NonNaNError> = (parent) =>
|
|
2119
2285
|
brand("NonNaN", parent, (value) =>
|
|
@@ -2124,18 +2290,19 @@ export const nonNaN: BrandFactory<"NonNaN", number, NonNaNError> = (parent) =>
|
|
|
2124
2290
|
|
|
2125
2291
|
export interface NonNaNError extends TypeError<"NonNaN"> {}
|
|
2126
2292
|
|
|
2127
|
-
export const formatNonNaNError =
|
|
2128
|
-
(
|
|
2129
|
-
)
|
|
2293
|
+
export const formatNonNaNError =
|
|
2294
|
+
/*#__PURE__*/ createTypeErrorFormatter<NonNaNError>(
|
|
2295
|
+
() => `The value must not be NaN.`,
|
|
2296
|
+
);
|
|
2130
2297
|
|
|
2131
|
-
/** @
|
|
2132
|
-
export const NonNaNNumber = nonNaN(Number);
|
|
2298
|
+
/** @group Number */
|
|
2299
|
+
export const NonNaNNumber = /*#__PURE__*/ nonNaN(Number);
|
|
2133
2300
|
export type NonNaNNumber = typeof NonNaNNumber.Type;
|
|
2134
2301
|
|
|
2135
2302
|
/**
|
|
2136
2303
|
* Finite number.
|
|
2137
2304
|
*
|
|
2138
|
-
* @
|
|
2305
|
+
* @group Number
|
|
2139
2306
|
*/
|
|
2140
2307
|
export const finite: BrandFactory<"Finite", number, FiniteError> = (parent) =>
|
|
2141
2308
|
brand("Finite", parent, (value) =>
|
|
@@ -2146,9 +2313,10 @@ export const finite: BrandFactory<"Finite", number, FiniteError> = (parent) =>
|
|
|
2146
2313
|
|
|
2147
2314
|
export interface FiniteError extends TypeError<"Finite"> {}
|
|
2148
2315
|
|
|
2149
|
-
export const formatFiniteError =
|
|
2150
|
-
(
|
|
2151
|
-
)
|
|
2316
|
+
export const formatFiniteError =
|
|
2317
|
+
/*#__PURE__*/ createTypeErrorFormatter<FiniteError>(
|
|
2318
|
+
(error) => `The value ${error.value} must be finite.`,
|
|
2319
|
+
);
|
|
2152
2320
|
|
|
2153
2321
|
/**
|
|
2154
2322
|
* Finite number.
|
|
@@ -2161,15 +2329,17 @@ export const formatFiniteError = createTypeErrorFormatter<FiniteError>(
|
|
|
2161
2329
|
* finite (e.g., `Infinity`, `-Infinity`, or `NaN`). Using `FiniteNumber` helps
|
|
2162
2330
|
* prevent these unexpected behaviors when working with JSON serialization.
|
|
2163
2331
|
*
|
|
2164
|
-
* @
|
|
2332
|
+
* @group Number
|
|
2165
2333
|
*/
|
|
2166
|
-
export const FiniteNumber = finite(Number);
|
|
2334
|
+
export const FiniteNumber = /*#__PURE__*/ finite(Number);
|
|
2167
2335
|
export type FiniteNumber = typeof FiniteNumber.Type;
|
|
2168
2336
|
|
|
2169
2337
|
/**
|
|
2170
2338
|
* Number that is a multiple of a divisor.
|
|
2171
2339
|
*
|
|
2172
|
-
* @
|
|
2340
|
+
* Use numeric literal, not expression. See {@link BrandFactory}.
|
|
2341
|
+
*
|
|
2342
|
+
* @group Number
|
|
2173
2343
|
*/
|
|
2174
2344
|
export const multipleOf: <Divisor extends number>(
|
|
2175
2345
|
divisor: Divisor,
|
|
@@ -2187,13 +2357,17 @@ export interface MultipleOfError<
|
|
|
2187
2357
|
readonly divisor: Divisor;
|
|
2188
2358
|
}
|
|
2189
2359
|
|
|
2190
|
-
export const formatMultipleOfError =
|
|
2191
|
-
(
|
|
2192
|
-
)
|
|
2360
|
+
export const formatMultipleOfError =
|
|
2361
|
+
/*#__PURE__*/ createTypeErrorFormatter<MultipleOfError>(
|
|
2362
|
+
(error) =>
|
|
2363
|
+
`The value ${error.value} is not a multiple of ${error.divisor}.`,
|
|
2364
|
+
);
|
|
2193
2365
|
|
|
2194
2366
|
/**
|
|
2195
2367
|
* Number within a range, inclusive.
|
|
2196
2368
|
*
|
|
2369
|
+
* Use numeric literal, not expression. See {@link BrandFactory}.
|
|
2370
|
+
*
|
|
2197
2371
|
* ### Example
|
|
2198
2372
|
*
|
|
2199
2373
|
* ```ts
|
|
@@ -2202,7 +2376,7 @@ export const formatMultipleOfError = createTypeErrorFormatter<MultipleOfError>(
|
|
|
2202
2376
|
* const errorResult = Between1And10.from(11); // err
|
|
2203
2377
|
* ```
|
|
2204
2378
|
*
|
|
2205
|
-
* @
|
|
2379
|
+
* @group Number
|
|
2206
2380
|
*/
|
|
2207
2381
|
export const between: <Min extends number, Max extends number>(
|
|
2208
2382
|
min: Min,
|
|
@@ -2223,10 +2397,11 @@ export interface BetweenError<
|
|
|
2223
2397
|
readonly max: Max;
|
|
2224
2398
|
}
|
|
2225
2399
|
|
|
2226
|
-
export const formatBetweenError =
|
|
2227
|
-
(
|
|
2228
|
-
|
|
2229
|
-
|
|
2400
|
+
export const formatBetweenError =
|
|
2401
|
+
/*#__PURE__*/ createTypeErrorFormatter<BetweenError>(
|
|
2402
|
+
(error) =>
|
|
2403
|
+
`The value ${error.value} is not between ${error.min} and ${error.max}, inclusive.`,
|
|
2404
|
+
);
|
|
2230
2405
|
|
|
2231
2406
|
/**
|
|
2232
2407
|
* Literal {@link Type}.
|
|
@@ -2243,7 +2418,7 @@ export const formatBetweenError = createTypeErrorFormatter<BetweenError>(
|
|
|
2243
2418
|
*
|
|
2244
2419
|
* TODO: Add JsonValue
|
|
2245
2420
|
*
|
|
2246
|
-
* @
|
|
2421
|
+
* @group Base factories
|
|
2247
2422
|
*/
|
|
2248
2423
|
export const literal = <T extends Literal>(expected: T): LiteralType<T> => {
|
|
2249
2424
|
const fromUnknown = (value: unknown): Result<T, LiteralError<T>> =>
|
|
@@ -2275,17 +2450,18 @@ export interface LiteralError<
|
|
|
2275
2450
|
readonly expected: T;
|
|
2276
2451
|
}
|
|
2277
2452
|
|
|
2278
|
-
export const formatLiteralError =
|
|
2279
|
-
(
|
|
2280
|
-
|
|
2281
|
-
error.expected
|
|
2282
|
-
|
|
2283
|
-
)
|
|
2453
|
+
export const formatLiteralError =
|
|
2454
|
+
/*#__PURE__*/ createTypeErrorFormatter<LiteralError>(
|
|
2455
|
+
(error) =>
|
|
2456
|
+
`The value ${error.value} is not strictly equal to the expected literal: ${globalThis.String(
|
|
2457
|
+
error.expected,
|
|
2458
|
+
)}.`,
|
|
2459
|
+
);
|
|
2284
2460
|
|
|
2285
2461
|
/**
|
|
2286
2462
|
* Array of a specific {@link Type}.
|
|
2287
2463
|
*
|
|
2288
|
-
*
|
|
2464
|
+
* ### Example
|
|
2289
2465
|
*
|
|
2290
2466
|
* ```ts
|
|
2291
2467
|
* const NumberArray = array(Number);
|
|
@@ -2294,8 +2470,8 @@ export const formatLiteralError = createTypeErrorFormatter<LiteralError>(
|
|
|
2294
2470
|
* const result2 = NumberArray.from(["a", "b"]); // err(...)
|
|
2295
2471
|
* ```
|
|
2296
2472
|
*
|
|
2297
|
-
* @
|
|
2298
|
-
* @
|
|
2473
|
+
* @group Base factories
|
|
2474
|
+
* @group Array
|
|
2299
2475
|
*/
|
|
2300
2476
|
export const array = <ElementType extends AnyType>(
|
|
2301
2477
|
element: ElementType,
|
|
@@ -2413,7 +2589,7 @@ export const formatArrayError = <Error extends TypeError>(
|
|
|
2413
2589
|
* const result2 = NumberSet.from(new Set(["a", "b"])); // err(...)
|
|
2414
2590
|
* ```
|
|
2415
2591
|
*
|
|
2416
|
-
* @
|
|
2592
|
+
* @group Base factories
|
|
2417
2593
|
*/
|
|
2418
2594
|
export const set = <ElementType extends AnyType>(
|
|
2419
2595
|
element: ElementType,
|
|
@@ -2543,8 +2719,8 @@ export const formatSetError = <Error extends TypeError>(
|
|
|
2543
2719
|
* StringToNumberRecord.from({ a: "x", b: 2 });
|
|
2544
2720
|
* ```
|
|
2545
2721
|
*
|
|
2546
|
-
* @
|
|
2547
|
-
* @
|
|
2722
|
+
* @group Base factories
|
|
2723
|
+
* @group Object
|
|
2548
2724
|
*/
|
|
2549
2725
|
export const record = <
|
|
2550
2726
|
KeyName extends TypeName,
|
|
@@ -2591,7 +2767,7 @@ export const record = <
|
|
|
2591
2767
|
type: "Record",
|
|
2592
2768
|
value,
|
|
2593
2769
|
reason: { kind: "Key", key: rawKey, error: keyResult.error },
|
|
2594
|
-
} as
|
|
2770
|
+
} as never);
|
|
2595
2771
|
}
|
|
2596
2772
|
|
|
2597
2773
|
const valueResult = valueType.fromUnknown(rawValue);
|
|
@@ -2602,12 +2778,12 @@ export const record = <
|
|
|
2602
2778
|
reason: {
|
|
2603
2779
|
kind: "Value",
|
|
2604
2780
|
key: rawKey,
|
|
2605
|
-
error: valueResult.error as
|
|
2781
|
+
error: valueResult.error as never,
|
|
2606
2782
|
},
|
|
2607
2783
|
});
|
|
2608
2784
|
}
|
|
2609
2785
|
|
|
2610
|
-
result[keyResult.value] = valueResult.value as
|
|
2786
|
+
result[keyResult.value] = valueResult.value as never;
|
|
2611
2787
|
}
|
|
2612
2788
|
|
|
2613
2789
|
return ok(result);
|
|
@@ -2664,7 +2840,7 @@ export const record = <
|
|
|
2664
2840
|
}),
|
|
2665
2841
|
key: keyType,
|
|
2666
2842
|
value: valueType,
|
|
2667
|
-
} as
|
|
2843
|
+
} as never;
|
|
2668
2844
|
};
|
|
2669
2845
|
|
|
2670
2846
|
/**
|
|
@@ -2748,22 +2924,22 @@ export const formatRecordError = <Error extends TypeError>(
|
|
|
2748
2924
|
*
|
|
2749
2925
|
* https://www.typescriptlang.org/docs/handbook/2/objects.html#index-signatures
|
|
2750
2926
|
*
|
|
2751
|
-
* ###
|
|
2927
|
+
* ### Example
|
|
2752
2928
|
*
|
|
2753
|
-
*
|
|
2929
|
+
* ## Basic Object Validation
|
|
2754
2930
|
*
|
|
2755
2931
|
* ```ts
|
|
2756
2932
|
* const User = object({
|
|
2757
2933
|
* name: NonEmptyTrimmedString,
|
|
2758
2934
|
* age: PositiveNumber,
|
|
2759
2935
|
* });
|
|
2760
|
-
*
|
|
2936
|
+
* interface User extends InferType<typeof User> {}
|
|
2761
2937
|
*
|
|
2762
2938
|
* const result = User.from({ name: "John", age: 30 }); // ok({ name: "John", age: 30 })
|
|
2763
2939
|
* const error = User.from({ name: "John", age: -5 }); // err
|
|
2764
2940
|
* ```
|
|
2765
2941
|
*
|
|
2766
|
-
*
|
|
2942
|
+
* ## Optional Properties
|
|
2767
2943
|
*
|
|
2768
2944
|
* In this example the `age` property is marked as optional using
|
|
2769
2945
|
* {@link optional}.
|
|
@@ -2773,10 +2949,10 @@ export const formatRecordError = <Error extends TypeError>(
|
|
|
2773
2949
|
* name: NonEmptyString, // Required
|
|
2774
2950
|
* age: optional(PositiveNumber), // Optional
|
|
2775
2951
|
* });
|
|
2776
|
-
*
|
|
2952
|
+
* interface User extends InferType<typeof User> {}
|
|
2777
2953
|
* ```
|
|
2778
2954
|
*
|
|
2779
|
-
*
|
|
2955
|
+
* ## Allowing Additional Properties
|
|
2780
2956
|
*
|
|
2781
2957
|
* ```ts
|
|
2782
2958
|
* const UserWithAnyExtraProperties = object(
|
|
@@ -2795,7 +2971,7 @@ export const formatRecordError = <Error extends TypeError>(
|
|
|
2795
2971
|
* });
|
|
2796
2972
|
* ```
|
|
2797
2973
|
*
|
|
2798
|
-
*
|
|
2974
|
+
* ## Combining Fixed and Flexible Properties
|
|
2799
2975
|
*
|
|
2800
2976
|
* ```ts
|
|
2801
2977
|
* const NumberDictionary = object(
|
|
@@ -2832,13 +3008,14 @@ export const formatRecordError = <Error extends TypeError>(
|
|
|
2832
3008
|
* );
|
|
2833
3009
|
* ```
|
|
2834
3010
|
*
|
|
2835
|
-
* @
|
|
2836
|
-
* @
|
|
3011
|
+
* @group Base factories
|
|
3012
|
+
* @group Object
|
|
2837
3013
|
*/
|
|
2838
3014
|
export function object<Props extends Record<string, AnyType>>(
|
|
2839
3015
|
props: Props,
|
|
2840
3016
|
): ObjectType<Props>;
|
|
2841
3017
|
|
|
3018
|
+
/** With additional record properties. */
|
|
2842
3019
|
export function object<
|
|
2843
3020
|
Props extends Record<string, AnyType>,
|
|
2844
3021
|
KeyName extends TypeName,
|
|
@@ -2901,9 +3078,9 @@ export function object(
|
|
|
2901
3078
|
|
|
2902
3079
|
const propResult = props[key].fromUnknown(value[key]);
|
|
2903
3080
|
if (!propResult.ok) {
|
|
2904
|
-
errors[key] = propResult.error as
|
|
3081
|
+
errors[key] = propResult.error as never;
|
|
2905
3082
|
} else {
|
|
2906
|
-
result[key] = propResult.value as
|
|
3083
|
+
result[key] = propResult.value as never;
|
|
2907
3084
|
}
|
|
2908
3085
|
}
|
|
2909
3086
|
|
|
@@ -2921,7 +3098,7 @@ export function object(
|
|
|
2921
3098
|
reason: {
|
|
2922
3099
|
kind: "IndexKey",
|
|
2923
3100
|
key,
|
|
2924
|
-
error: keyResult.error as
|
|
3101
|
+
error: keyResult.error as never,
|
|
2925
3102
|
},
|
|
2926
3103
|
});
|
|
2927
3104
|
}
|
|
@@ -2934,12 +3111,12 @@ export function object(
|
|
|
2934
3111
|
reason: {
|
|
2935
3112
|
kind: "IndexValue",
|
|
2936
3113
|
key,
|
|
2937
|
-
error: valueResult.error as
|
|
3114
|
+
error: valueResult.error as never,
|
|
2938
3115
|
},
|
|
2939
3116
|
});
|
|
2940
3117
|
}
|
|
2941
3118
|
|
|
2942
|
-
result[keyResult.value] = valueResult.value as
|
|
3119
|
+
result[keyResult.value] = valueResult.value as never;
|
|
2943
3120
|
}
|
|
2944
3121
|
} else if (extraKeys.length > 0) {
|
|
2945
3122
|
return err({
|
|
@@ -3092,7 +3269,7 @@ export interface ObjectError<
|
|
|
3092
3269
|
* Merge Error and ParentError into one ObjectError so tooltips and error
|
|
3093
3270
|
* messages are easier to read.
|
|
3094
3271
|
*
|
|
3095
|
-
* @
|
|
3272
|
+
* @group Utilities
|
|
3096
3273
|
*/
|
|
3097
3274
|
export type MergeObjectTypeErrors<T extends ObjectType<any>> =
|
|
3098
3275
|
T extends ObjectType<infer Props>
|
|
@@ -3202,6 +3379,187 @@ export const formatObjectWithRecordError = <Error extends TypeError>(
|
|
|
3202
3379
|
}
|
|
3203
3380
|
});
|
|
3204
3381
|
|
|
3382
|
+
/**
|
|
3383
|
+
* Base interface for objects with a discriminant `type` property.
|
|
3384
|
+
*
|
|
3385
|
+
* This enables
|
|
3386
|
+
* {@link https://www.typescriptlang.org/docs/handbook/2/narrowing.html#discriminated-unions | discriminated unions}
|
|
3387
|
+
* (also known as tagged unions) — a pattern where TypeScript uses a literal
|
|
3388
|
+
* `type` field to narrow union types automatically.
|
|
3389
|
+
*
|
|
3390
|
+
* ## Why Discriminated Unions?
|
|
3391
|
+
*
|
|
3392
|
+
* Discriminated unions model states that are **mutually exclusive**. Instead of
|
|
3393
|
+
* optional fields and boolean flags that can combine into invalid
|
|
3394
|
+
* configurations, each variant is a distinct type. This makes illegal states
|
|
3395
|
+
* unrepresentable — invalid combinations cannot exist, so bugs cannot create
|
|
3396
|
+
* them.
|
|
3397
|
+
*
|
|
3398
|
+
* Benefits:
|
|
3399
|
+
*
|
|
3400
|
+
* - **Self-documenting** — Union cases immediately show all possible states
|
|
3401
|
+
* - **Compile-time safety** — TypeScript enforces handling all cases
|
|
3402
|
+
* - **Refactoring-friendly** — Adding a new state breaks code that doesn't handle
|
|
3403
|
+
* it
|
|
3404
|
+
*
|
|
3405
|
+
* ### Example
|
|
3406
|
+
*
|
|
3407
|
+
* ```ts
|
|
3408
|
+
* // Bad: optional fields allow invalid states (no contact info at all)
|
|
3409
|
+
* interface Contact {
|
|
3410
|
+
* readonly email?: Email;
|
|
3411
|
+
* readonly phone?: Phone;
|
|
3412
|
+
* }
|
|
3413
|
+
*
|
|
3414
|
+
* // Good: discriminated union makes "at least one" explicit
|
|
3415
|
+
* interface EmailOnly extends Typed<"EmailOnly"> {
|
|
3416
|
+
* readonly email: Email;
|
|
3417
|
+
* }
|
|
3418
|
+
* interface PhoneOnly extends Typed<"PhoneOnly"> {
|
|
3419
|
+
* readonly phone: Phone;
|
|
3420
|
+
* }
|
|
3421
|
+
* interface EmailAndPhone extends Typed<"EmailAndPhone"> {
|
|
3422
|
+
* readonly email: Email;
|
|
3423
|
+
* readonly phone: Phone;
|
|
3424
|
+
* }
|
|
3425
|
+
*
|
|
3426
|
+
* type ContactInfo = EmailOnly | PhoneOnly | EmailAndPhone;
|
|
3427
|
+
* ```
|
|
3428
|
+
*
|
|
3429
|
+
* ```ts
|
|
3430
|
+
* interface Pending extends Typed<"Pending"> {
|
|
3431
|
+
* readonly createdAt: DateIso;
|
|
3432
|
+
* }
|
|
3433
|
+
* interface Shipped extends Typed<"Shipped"> {
|
|
3434
|
+
* readonly trackingNumber: TrackingNumber;
|
|
3435
|
+
* }
|
|
3436
|
+
* interface Delivered extends Typed<"Delivered"> {
|
|
3437
|
+
* readonly deliveredAt: DateIso;
|
|
3438
|
+
* }
|
|
3439
|
+
* interface Cancelled extends Typed<"Cancelled"> {
|
|
3440
|
+
* readonly reason: CancellationReason;
|
|
3441
|
+
* }
|
|
3442
|
+
*
|
|
3443
|
+
* type OrderState = Pending | Shipped | Delivered | Cancelled;
|
|
3444
|
+
*
|
|
3445
|
+
* // TypeScript enforces exhaustiveness via return type
|
|
3446
|
+
* const getStatusMessage = (state: OrderState): string => {
|
|
3447
|
+
* switch (state.type) {
|
|
3448
|
+
* case "Pending":
|
|
3449
|
+
* return "Order placed";
|
|
3450
|
+
* case "Shipped":
|
|
3451
|
+
* return `Shipped: ${state.trackingNumber}`;
|
|
3452
|
+
* case "Delivered":
|
|
3453
|
+
* return `Delivered on ${state.deliveredAt.toLocaleDateString()}`;
|
|
3454
|
+
* case "Cancelled":
|
|
3455
|
+
* return `Cancelled: ${state.reason}`;
|
|
3456
|
+
* }
|
|
3457
|
+
* };
|
|
3458
|
+
*
|
|
3459
|
+
* // For void functions, use exhaustiveCheck to ensure all cases are handled
|
|
3460
|
+
* const logState = (state: OrderState): void => {
|
|
3461
|
+
* switch (state.type) {
|
|
3462
|
+
* case "Pending":
|
|
3463
|
+
* console.log("Order placed");
|
|
3464
|
+
* break;
|
|
3465
|
+
* case "Shipped":
|
|
3466
|
+
* console.log(`Shipped: ${state.trackingNumber}`);
|
|
3467
|
+
* break;
|
|
3468
|
+
* case "Delivered":
|
|
3469
|
+
* console.log(
|
|
3470
|
+
* `Delivered on ${state.deliveredAt.toLocaleDateString()}`,
|
|
3471
|
+
* );
|
|
3472
|
+
* break;
|
|
3473
|
+
* case "Cancelled":
|
|
3474
|
+
* console.log(`Cancelled: ${state.reason}`);
|
|
3475
|
+
* break;
|
|
3476
|
+
* default:
|
|
3477
|
+
* exhaustiveCheck(state);
|
|
3478
|
+
* }
|
|
3479
|
+
* };
|
|
3480
|
+
* ```
|
|
3481
|
+
*
|
|
3482
|
+
* ## Why `type` (and not e.g. `_tag`)?
|
|
3483
|
+
*
|
|
3484
|
+
* Underscore-prefixing is meant to avoid clashing with domain properties, but
|
|
3485
|
+
* proper discriminated union design means the discriminant IS the domain
|
|
3486
|
+
* concept — there's no clash to avoid. The `type` prop name also aligns with
|
|
3487
|
+
* {@link Type}'s name. If an entity has a meaningful "type" (like product
|
|
3488
|
+
* category), model it as the discriminant itself:
|
|
3489
|
+
*
|
|
3490
|
+
* ```ts
|
|
3491
|
+
* interface Electronics extends Typed<"Electronics"> {
|
|
3492
|
+
* voltage: Voltage;
|
|
3493
|
+
* }
|
|
3494
|
+
* interface Clothing extends Typed<"Clothing"> {
|
|
3495
|
+
* size: Size;
|
|
3496
|
+
* }
|
|
3497
|
+
* type Product = Electronics | Clothing;
|
|
3498
|
+
* ```
|
|
3499
|
+
*
|
|
3500
|
+
* @see {@link exhaustiveCheck} to ensure all cases are handled in void functions.
|
|
3501
|
+
* @see {@link typed} for runtime-validated typed objects.
|
|
3502
|
+
*/
|
|
3503
|
+
export interface Typed<T extends TypeName> {
|
|
3504
|
+
readonly type: T;
|
|
3505
|
+
}
|
|
3506
|
+
|
|
3507
|
+
/**
|
|
3508
|
+
* Creates a runtime-validated typed object with a `type` discriminant.
|
|
3509
|
+
*
|
|
3510
|
+
* ### Example
|
|
3511
|
+
*
|
|
3512
|
+
* ```ts
|
|
3513
|
+
* const Card = typed("Card", {
|
|
3514
|
+
* cardNumber: CardNumber,
|
|
3515
|
+
* expiry: DateIso,
|
|
3516
|
+
* });
|
|
3517
|
+
*
|
|
3518
|
+
* const Cash = typed("Cash", {
|
|
3519
|
+
* currency: NonEmptyTrimmedString,
|
|
3520
|
+
* });
|
|
3521
|
+
*
|
|
3522
|
+
* const Payment = union(Card, Cash);
|
|
3523
|
+
* type Payment = typeof Payment.Type;
|
|
3524
|
+
*
|
|
3525
|
+
* const result = Payment.fromUnknown(data);
|
|
3526
|
+
* if (result.ok) {
|
|
3527
|
+
* switch (result.value.type) {
|
|
3528
|
+
* case "Card":
|
|
3529
|
+
* console.log(result.value.cardNumber);
|
|
3530
|
+
* break;
|
|
3531
|
+
* case "Cash":
|
|
3532
|
+
* console.log(result.value.currency);
|
|
3533
|
+
* break;
|
|
3534
|
+
* }
|
|
3535
|
+
* }
|
|
3536
|
+
* ```
|
|
3537
|
+
*
|
|
3538
|
+
* @see {@link Typed} for type-only discrimination.
|
|
3539
|
+
*/
|
|
3540
|
+
export function typed<Tag extends TypeName>(tag: Tag): TypedType<Tag>;
|
|
3541
|
+
/** With additional properties. */
|
|
3542
|
+
export function typed<
|
|
3543
|
+
Tag extends TypeName,
|
|
3544
|
+
Props extends Record<string, AnyType>,
|
|
3545
|
+
>(tag: Tag, props: Props): TypedType<Tag, Props>;
|
|
3546
|
+
export function typed<
|
|
3547
|
+
Tag extends TypeName,
|
|
3548
|
+
Props extends Record<string, AnyType>,
|
|
3549
|
+
>(tag: Tag, props?: Props): ObjectType<{ type: LiteralType<Tag> } & Props> {
|
|
3550
|
+
return object({ type: literal(tag), ...props } as {
|
|
3551
|
+
type: LiteralType<Tag>;
|
|
3552
|
+
} & Props);
|
|
3553
|
+
}
|
|
3554
|
+
|
|
3555
|
+
/** Return type of {@link typed}. */
|
|
3556
|
+
export type TypedType<
|
|
3557
|
+
Tag extends TypeName,
|
|
3558
|
+
Props extends Record<string, AnyType> = Record<never, never>,
|
|
3559
|
+
> = ObjectType<{ type: LiteralType<Tag> } & Props>;
|
|
3560
|
+
|
|
3561
|
+
// TODO: TypeError with "Error" suffix.
|
|
3562
|
+
|
|
3205
3563
|
/**
|
|
3206
3564
|
* Union {@link Type}.
|
|
3207
3565
|
*
|
|
@@ -3224,12 +3582,13 @@ export const formatObjectWithRecordError = <Error extends TypeError>(
|
|
|
3224
3582
|
* const result3 = StringOrNumber.from(42); // ok(42)
|
|
3225
3583
|
* ```
|
|
3226
3584
|
*
|
|
3227
|
-
* @
|
|
3585
|
+
* @group Base factories
|
|
3228
3586
|
*/
|
|
3229
3587
|
export function union<
|
|
3230
3588
|
Members extends [AnyType, AnyType, ...ReadonlyArray<AnyType>],
|
|
3231
3589
|
>(...members: Members): UnionType<Members>;
|
|
3232
3590
|
|
|
3591
|
+
/** With literal values. */
|
|
3233
3592
|
export function union<
|
|
3234
3593
|
Literals extends [Literal, Literal, ...ReadonlyArray<Literal>],
|
|
3235
3594
|
>(
|
|
@@ -3319,6 +3678,110 @@ export const isUnionType = (
|
|
|
3319
3678
|
): t is UnionType<[AnyType, ...ReadonlyArray<AnyType>]> =>
|
|
3320
3679
|
t.name === "Union" && Array.isArray((t as { members?: unknown }).members);
|
|
3321
3680
|
|
|
3681
|
+
/**
|
|
3682
|
+
* Creates a {@link Type} for {@link Result} values.
|
|
3683
|
+
*
|
|
3684
|
+
* Use for validating serialized Results from storage, APIs, or message passing.
|
|
3685
|
+
*
|
|
3686
|
+
* ### Example
|
|
3687
|
+
*
|
|
3688
|
+
* ```ts
|
|
3689
|
+
* const SyncResponse = result(
|
|
3690
|
+
* object({ timestamp: NonNegativeInt }),
|
|
3691
|
+
* typed("SyncError", { message: String }),
|
|
3692
|
+
* );
|
|
3693
|
+
*
|
|
3694
|
+
* // Validate response from worker or API
|
|
3695
|
+
* const validated = SyncResponse.from(JSON.parse(message));
|
|
3696
|
+
* if (!validated.ok) return validated; // validation error
|
|
3697
|
+
* // validated.value is Result<{ timestamp }, SyncError>
|
|
3698
|
+
* ```
|
|
3699
|
+
*
|
|
3700
|
+
* @group Composite factories
|
|
3701
|
+
*/
|
|
3702
|
+
export const result = <OkType extends AnyType, ErrType extends AnyType>(
|
|
3703
|
+
okType: OkType,
|
|
3704
|
+
errType: ErrType,
|
|
3705
|
+
): UnionType<
|
|
3706
|
+
[
|
|
3707
|
+
ObjectType<{ ok: LiteralType<true>; value: OkType }>,
|
|
3708
|
+
ObjectType<{ ok: LiteralType<false>; error: ErrType }>,
|
|
3709
|
+
]
|
|
3710
|
+
> =>
|
|
3711
|
+
union(
|
|
3712
|
+
object({ ok: literal(true), value: okType }),
|
|
3713
|
+
object({ ok: literal(false), error: errType }),
|
|
3714
|
+
);
|
|
3715
|
+
|
|
3716
|
+
/**
|
|
3717
|
+
* A {@link result} type for `Result<unknown, unknown>`.
|
|
3718
|
+
*
|
|
3719
|
+
* Useful for serializing Results where the value and error types are unknown.
|
|
3720
|
+
*
|
|
3721
|
+
* @group Composite factories
|
|
3722
|
+
*/
|
|
3723
|
+
export const UnknownResult = /*#__PURE__*/ result(Unknown, Unknown);
|
|
3724
|
+
export type UnknownResult = typeof UnknownResult.Type;
|
|
3725
|
+
|
|
3726
|
+
/**
|
|
3727
|
+
* Creates a {@link Type} for {@link NextResult} with three outcomes.
|
|
3728
|
+
*
|
|
3729
|
+
* Validates results where the producer responds with:
|
|
3730
|
+
*
|
|
3731
|
+
* - `Ok<A>` — produced a value
|
|
3732
|
+
* - `Err<Done<D>>` — completed normally with a done value
|
|
3733
|
+
* - `Err<E>` — failed with an error
|
|
3734
|
+
*
|
|
3735
|
+
* ### Example
|
|
3736
|
+
*
|
|
3737
|
+
* ```ts
|
|
3738
|
+
* const MyNextResult = nextResult(Item, MyError, Summary);
|
|
3739
|
+
*
|
|
3740
|
+
* const validated = MyNextResult.fromUnknown(data);
|
|
3741
|
+
* if (!validated.ok) return validated;
|
|
3742
|
+
*
|
|
3743
|
+
* const result = validated.value;
|
|
3744
|
+
* if (result.ok) {
|
|
3745
|
+
* console.log(result.value);
|
|
3746
|
+
* } else if (result.error.type === "Done") {
|
|
3747
|
+
* console.log("Done:", result.error.done);
|
|
3748
|
+
* } else {
|
|
3749
|
+
* console.error(result.error);
|
|
3750
|
+
* }
|
|
3751
|
+
* ```
|
|
3752
|
+
*
|
|
3753
|
+
* @group Composite factories
|
|
3754
|
+
*/
|
|
3755
|
+
export const nextResult = <
|
|
3756
|
+
ValueType extends AnyType,
|
|
3757
|
+
ErrorType extends AnyType,
|
|
3758
|
+
DoneType extends AnyType,
|
|
3759
|
+
>(
|
|
3760
|
+
valueType: ValueType,
|
|
3761
|
+
errorType: ErrorType,
|
|
3762
|
+
doneType: DoneType,
|
|
3763
|
+
): ReturnType<
|
|
3764
|
+
typeof result<
|
|
3765
|
+
ValueType,
|
|
3766
|
+
UnionType<[ErrorType, TypedType<"Done", { done: DoneType }>]>
|
|
3767
|
+
>
|
|
3768
|
+
> => result(valueType, union(errorType, typed("Done", { done: doneType })));
|
|
3769
|
+
|
|
3770
|
+
/**
|
|
3771
|
+
* A {@link nextResult} type for `NextResult<unknown, unknown, unknown>`.
|
|
3772
|
+
*
|
|
3773
|
+
* Useful for checking if a value is a {@link NextResult} via
|
|
3774
|
+
* `UnknownNextResult.is(value)`.
|
|
3775
|
+
*
|
|
3776
|
+
* @group Composite factories
|
|
3777
|
+
*/
|
|
3778
|
+
export const UnknownNextResult = /*#__PURE__*/ nextResult(
|
|
3779
|
+
Unknown,
|
|
3780
|
+
Unknown,
|
|
3781
|
+
Unknown,
|
|
3782
|
+
);
|
|
3783
|
+
export type UnknownNextResult = typeof UnknownNextResult.Type;
|
|
3784
|
+
|
|
3322
3785
|
/**
|
|
3323
3786
|
* Recursive {@link Type}.
|
|
3324
3787
|
*
|
|
@@ -3352,7 +3815,7 @@ export const isUnionType = (
|
|
|
3352
3815
|
* );
|
|
3353
3816
|
* ```
|
|
3354
3817
|
*
|
|
3355
|
-
* @
|
|
3818
|
+
* @group Base factories
|
|
3356
3819
|
*/
|
|
3357
3820
|
export const recursive = <ParentType extends AnyType>(
|
|
3358
3821
|
create: () => ParentType,
|
|
@@ -3412,7 +3875,7 @@ export interface RecursiveType<ParentType extends AnyType> extends Type<
|
|
|
3412
3875
|
* NullOrString.from(42); // err(...)
|
|
3413
3876
|
* ```
|
|
3414
3877
|
*
|
|
3415
|
-
* @
|
|
3878
|
+
* @group Base factories
|
|
3416
3879
|
*/
|
|
3417
3880
|
export const nullOr = <T extends AnyType>(
|
|
3418
3881
|
type: T,
|
|
@@ -3430,7 +3893,7 @@ export const nullOr = <T extends AnyType>(
|
|
|
3430
3893
|
* UndefinedOrString.from(42); // err(...)
|
|
3431
3894
|
* ```
|
|
3432
3895
|
*
|
|
3433
|
-
* @
|
|
3896
|
+
* @group Base factories
|
|
3434
3897
|
*/
|
|
3435
3898
|
export const undefinedOr = <T extends AnyType>(
|
|
3436
3899
|
type: T,
|
|
@@ -3452,7 +3915,7 @@ export const undefinedOr = <T extends AnyType>(
|
|
|
3452
3915
|
* NullishOrString.from(42); // err(...)
|
|
3453
3916
|
* ```
|
|
3454
3917
|
*
|
|
3455
|
-
* @
|
|
3918
|
+
* @group Base factories
|
|
3456
3919
|
*/
|
|
3457
3920
|
export const nullishOr = <T extends AnyType>(
|
|
3458
3921
|
type: T,
|
|
@@ -3473,7 +3936,7 @@ export const nullishOr = <T extends AnyType>(
|
|
|
3473
3936
|
* const error = NameAndAge.from(["Alice", -10]); // err
|
|
3474
3937
|
* ```
|
|
3475
3938
|
*
|
|
3476
|
-
* @
|
|
3939
|
+
* @group Base factories
|
|
3477
3940
|
*/
|
|
3478
3941
|
export const tuple = <Elements extends [AnyType, ...ReadonlyArray<AnyType>]>(
|
|
3479
3942
|
...elements: Elements
|
|
@@ -3503,7 +3966,7 @@ export const tuple = <Elements extends [AnyType, ...ReadonlyArray<AnyType>]>(
|
|
|
3503
3966
|
reason: {
|
|
3504
3967
|
kind: "Element",
|
|
3505
3968
|
index: i,
|
|
3506
|
-
error: elementResult.error as
|
|
3969
|
+
error: elementResult.error as never,
|
|
3507
3970
|
},
|
|
3508
3971
|
});
|
|
3509
3972
|
}
|
|
@@ -3534,7 +3997,7 @@ export const tuple = <Elements extends [AnyType, ...ReadonlyArray<AnyType>]>(
|
|
|
3534
3997
|
reason: {
|
|
3535
3998
|
kind: "Element",
|
|
3536
3999
|
index: i,
|
|
3537
|
-
error: elementResult.error as
|
|
4000
|
+
error: elementResult.error as never,
|
|
3538
4001
|
},
|
|
3539
4002
|
});
|
|
3540
4003
|
}
|
|
@@ -3550,7 +4013,7 @@ export const tuple = <Elements extends [AnyType, ...ReadonlyArray<AnyType>]>(
|
|
|
3550
4013
|
fromParent,
|
|
3551
4014
|
}),
|
|
3552
4015
|
elements,
|
|
3553
|
-
} as
|
|
4016
|
+
} as never;
|
|
3554
4017
|
};
|
|
3555
4018
|
|
|
3556
4019
|
/**
|
|
@@ -3609,9 +4072,9 @@ export const formatTupleError = <Error extends TypeError>(
|
|
|
3609
4072
|
*
|
|
3610
4073
|
* https://www.sqlite.org/c3ref/int64.html
|
|
3611
4074
|
*
|
|
3612
|
-
* @
|
|
4075
|
+
* @group Number
|
|
3613
4076
|
*/
|
|
3614
|
-
export const Int64 = brand("Int64", BigInt, (value) =>
|
|
4077
|
+
export const Int64 = /*#__PURE__*/ brand("Int64", BigInt, (value) =>
|
|
3615
4078
|
value >= -9223372036854775808n && value <= 9223372036854775807n
|
|
3616
4079
|
? ok(value)
|
|
3617
4080
|
: err<Int64Error>({ type: "Int64", value }),
|
|
@@ -3619,25 +4082,29 @@ export const Int64 = brand("Int64", BigInt, (value) =>
|
|
|
3619
4082
|
export type Int64 = typeof Int64.Type;
|
|
3620
4083
|
export interface Int64Error extends TypeError<"Int64"> {}
|
|
3621
4084
|
|
|
3622
|
-
export const formatInt64Error =
|
|
3623
|
-
(
|
|
3624
|
-
|
|
3625
|
-
)
|
|
4085
|
+
export const formatInt64Error =
|
|
4086
|
+
/*#__PURE__*/ createTypeErrorFormatter<Int64Error>(
|
|
4087
|
+
(error) =>
|
|
4088
|
+
`The value ${error.value} is not a valid 64-bit signed integer (Int64).`,
|
|
4089
|
+
);
|
|
3626
4090
|
|
|
3627
4091
|
/**
|
|
3628
4092
|
* Stringified {@link Int64}.
|
|
3629
4093
|
*
|
|
3630
|
-
* @
|
|
4094
|
+
* @group String
|
|
3631
4095
|
*/
|
|
3632
|
-
export const Int64String = brand(
|
|
3633
|
-
|
|
3634
|
-
|
|
3635
|
-
|
|
3636
|
-
|
|
3637
|
-
|
|
3638
|
-
|
|
3639
|
-
|
|
3640
|
-
|
|
4096
|
+
export const Int64String = /*#__PURE__*/ brand(
|
|
4097
|
+
"Int64",
|
|
4098
|
+
NonEmptyTrimmedString,
|
|
4099
|
+
(value) =>
|
|
4100
|
+
trySync(
|
|
4101
|
+
() => {
|
|
4102
|
+
const maybeInt = globalThis.BigInt(value);
|
|
4103
|
+
Int64.orThrow(maybeInt);
|
|
4104
|
+
return value;
|
|
4105
|
+
},
|
|
4106
|
+
(): Int64StringError => ({ type: "Int64String", value }),
|
|
4107
|
+
),
|
|
3641
4108
|
);
|
|
3642
4109
|
|
|
3643
4110
|
export type Int64String = typeof Int64String.Type;
|
|
@@ -3645,10 +4112,20 @@ export type Int64String = typeof Int64String.Type;
|
|
|
3645
4112
|
export interface Int64StringError extends TypeError<"Int64String"> {}
|
|
3646
4113
|
|
|
3647
4114
|
export const formatInt64StringError =
|
|
3648
|
-
createTypeErrorFormatter<Int64StringError>(
|
|
4115
|
+
/*#__PURE__*/ createTypeErrorFormatter<Int64StringError>(
|
|
3649
4116
|
(error) => `The value ${error.value} is not a valid Int64 string.`,
|
|
3650
4117
|
);
|
|
3651
4118
|
|
|
4119
|
+
/**
|
|
4120
|
+
* Validated JSON-compatible value.
|
|
4121
|
+
*
|
|
4122
|
+
* This is the output side of JSON data in Evolu. It uses {@link FiniteNumber}
|
|
4123
|
+
* instead of `number` because JSON numbers are expected to be finite once the
|
|
4124
|
+
* value has been parsed or validated.
|
|
4125
|
+
*
|
|
4126
|
+
* Compare with {@link JsonValueInput}, which represents caller-provided input
|
|
4127
|
+
* before validation.
|
|
4128
|
+
*/
|
|
3652
4129
|
export type JsonValue =
|
|
3653
4130
|
| string
|
|
3654
4131
|
| FiniteNumber
|
|
@@ -3657,6 +4134,19 @@ export type JsonValue =
|
|
|
3657
4134
|
| JsonArray
|
|
3658
4135
|
| JsonObject;
|
|
3659
4136
|
|
|
4137
|
+
/**
|
|
4138
|
+
* JSON-compatible input value before validation.
|
|
4139
|
+
*
|
|
4140
|
+
* This is broader than {@link JsonValue} because inputs arrive as ordinary
|
|
4141
|
+
* JavaScript values, so numbers are typed as `number` before validation can
|
|
4142
|
+
* narrow them to {@link FiniteNumber}.
|
|
4143
|
+
*
|
|
4144
|
+
* That means `JsonValueInput` can temporarily contain numbers that are lossy in
|
|
4145
|
+
* JSON serialization. For example, `JSON.stringify(NaN)` and
|
|
4146
|
+
* `JSON.stringify(Infinity)` produce `null`, and `JSON.stringify(-0)` produces
|
|
4147
|
+
* `0`. Use {@link JsonValue} when the value must already satisfy JSON numeric
|
|
4148
|
+
* constraints.
|
|
4149
|
+
*/
|
|
3660
4150
|
export type JsonValueInput =
|
|
3661
4151
|
| string
|
|
3662
4152
|
| number
|
|
@@ -3690,9 +4180,9 @@ export type JsonArrayInput = ReadonlyArray<JsonValueInput>;
|
|
|
3690
4180
|
* JSON-compatible value: string, {@link FiniteNumber}, boolean, null,
|
|
3691
4181
|
* {@link JsonArray}, or {@link JsonObject}.
|
|
3692
4182
|
*
|
|
3693
|
-
* @
|
|
4183
|
+
* @group Base Types
|
|
3694
4184
|
*/
|
|
3695
|
-
export const JsonValue = recursive(
|
|
4185
|
+
export const JsonValue = /*#__PURE__*/ recursive(
|
|
3696
4186
|
(): UnionType<
|
|
3697
4187
|
[
|
|
3698
4188
|
typeof String,
|
|
@@ -3732,16 +4222,16 @@ export const JsonValue = recursive(
|
|
|
3732
4222
|
/**
|
|
3733
4223
|
* JSON-compatible array of {@link JsonValue} elements.
|
|
3734
4224
|
*
|
|
3735
|
-
* @
|
|
4225
|
+
* @group Array
|
|
3736
4226
|
*/
|
|
3737
|
-
export const JsonArray = array(JsonValue);
|
|
4227
|
+
export const JsonArray = /*#__PURE__*/ array(JsonValue);
|
|
3738
4228
|
|
|
3739
4229
|
/**
|
|
3740
4230
|
* JSON-compatible object with string keys and {@link JsonValue} values.
|
|
3741
4231
|
*
|
|
3742
|
-
* @
|
|
4232
|
+
* @group Object
|
|
3743
4233
|
*/
|
|
3744
|
-
export const JsonObject = record(String, JsonValue);
|
|
4234
|
+
export const JsonObject = /*#__PURE__*/ record(String, JsonValue);
|
|
3745
4235
|
|
|
3746
4236
|
export const parseJson = (value: string): Result<JsonValue, JsonError> =>
|
|
3747
4237
|
trySync(
|
|
@@ -3763,9 +4253,9 @@ export const parseJson = (value: string): Result<JsonValue, JsonError> =>
|
|
|
3763
4253
|
* const error = Json.from("invalid json"); // err
|
|
3764
4254
|
* ```
|
|
3765
4255
|
*
|
|
3766
|
-
* @
|
|
4256
|
+
* @group String
|
|
3767
4257
|
*/
|
|
3768
|
-
export const Json = brand("Json", String, (value) => {
|
|
4258
|
+
export const Json = /*#__PURE__*/ brand("Json", String, (value) => {
|
|
3769
4259
|
const result = parseJson(value);
|
|
3770
4260
|
if (!result.ok) return result;
|
|
3771
4261
|
return ok(value);
|
|
@@ -3777,9 +4267,10 @@ export interface JsonError extends TypeError<"Json"> {
|
|
|
3777
4267
|
readonly message: string;
|
|
3778
4268
|
}
|
|
3779
4269
|
|
|
3780
|
-
export const formatJsonError =
|
|
3781
|
-
(
|
|
3782
|
-
)
|
|
4270
|
+
export const formatJsonError =
|
|
4271
|
+
/*#__PURE__*/ createTypeErrorFormatter<JsonError>(
|
|
4272
|
+
(error) => `Invalid JSON: ${error.value}. Error: ${error.message}`,
|
|
4273
|
+
);
|
|
3783
4274
|
|
|
3784
4275
|
export const jsonValueToJson = (value: JsonValue): Json =>
|
|
3785
4276
|
JSON.stringify(value) as Json;
|
|
@@ -3807,7 +4298,7 @@ export const jsonToJsonValue = (value: Json): JsonValue =>
|
|
|
3807
4298
|
* name: NonEmptyString100,
|
|
3808
4299
|
* age: FiniteNumber,
|
|
3809
4300
|
* });
|
|
3810
|
-
*
|
|
4301
|
+
* interface Person extends InferType<typeof Person> {}
|
|
3811
4302
|
*
|
|
3812
4303
|
* const [PersonJson, personToPersonJson, personJsonToPerson] = json(
|
|
3813
4304
|
* Person,
|
|
@@ -3848,11 +4339,7 @@ export const json = <T extends AnyType, Name extends TypeName>(
|
|
|
3848
4339
|
return ok(value);
|
|
3849
4340
|
}) as BrandType<typeof String, Name, JsonError | InferErrors<T>, StringError>;
|
|
3850
4341
|
|
|
3851
|
-
return [
|
|
3852
|
-
BrandedJsonType,
|
|
3853
|
-
jsonValueToJson as IntentionalNever,
|
|
3854
|
-
jsonToJsonValue as IntentionalNever,
|
|
3855
|
-
];
|
|
4342
|
+
return [BrandedJsonType, jsonValueToJson as never, jsonToJsonValue as never];
|
|
3856
4343
|
};
|
|
3857
4344
|
|
|
3858
4345
|
/**
|
|
@@ -3924,7 +4411,7 @@ export const isOptionalType = (x: unknown): x is OptionalType<any> =>
|
|
|
3924
4411
|
* PartialUser.from({ age: -5 });
|
|
3925
4412
|
* ```
|
|
3926
4413
|
*
|
|
3927
|
-
* @
|
|
4414
|
+
* @group Object
|
|
3928
4415
|
*/
|
|
3929
4416
|
export const partial = <Props extends Record<string, AnyType>>(
|
|
3930
4417
|
props: Props,
|
|
@@ -3944,7 +4431,7 @@ export const partial = <Props extends Record<string, AnyType>>(
|
|
|
3944
4431
|
* entirely, or set it to `null`, or set it to the non-null member of the
|
|
3945
4432
|
* union.
|
|
3946
4433
|
*
|
|
3947
|
-
* @
|
|
4434
|
+
* @group Object
|
|
3948
4435
|
*/
|
|
3949
4436
|
export const nullableToOptional = <Props extends Record<string, AnyType>>(
|
|
3950
4437
|
props: Props,
|
|
@@ -3990,12 +4477,12 @@ export type NullTypeInMembers<Members extends [AnyType, ...Array<AnyType>]> =
|
|
|
3990
4477
|
/**
|
|
3991
4478
|
* Create a new `object` {@link Type} by omitting some keys.
|
|
3992
4479
|
*
|
|
3993
|
-
* @
|
|
4480
|
+
* @group Object
|
|
3994
4481
|
*/
|
|
3995
|
-
export
|
|
4482
|
+
export const omit = <T extends ObjectType<any>, Keys extends keyof T["props"]>(
|
|
3996
4483
|
objectType: T,
|
|
3997
4484
|
...keys: ReadonlyArray<Keys>
|
|
3998
|
-
): ObjectType<Omit<T["props"], Keys>> {
|
|
4485
|
+
): ObjectType<Omit<T["props"], Keys>> => {
|
|
3999
4486
|
const newProps = {} as Omit<T["props"], Keys>;
|
|
4000
4487
|
|
|
4001
4488
|
for (const key in objectType.props) {
|
|
@@ -4005,40 +4492,7 @@ export function omit<T extends ObjectType<any>, Keys extends keyof T["props"]>(
|
|
|
4005
4492
|
}
|
|
4006
4493
|
}
|
|
4007
4494
|
return object(newProps);
|
|
4008
|
-
}
|
|
4009
|
-
|
|
4010
|
-
export const maxMutationSize = 655360;
|
|
4011
|
-
|
|
4012
|
-
/**
|
|
4013
|
-
* Evolu has to limit the maximum mutation size. Otherwise, sync couldn't use
|
|
4014
|
-
* the `maxProtocolMessageRangesSize`. The max size is 640KB in bytes, measured
|
|
4015
|
-
* via MessagePack. Evolu Protocol DbChange will be smaller thanks to various
|
|
4016
|
-
* optimizations.
|
|
4017
|
-
*/
|
|
4018
|
-
export const validMutationSize = <T extends AnyType>(
|
|
4019
|
-
type: T,
|
|
4020
|
-
): BrandType<T, "ValidMutationSize", ValidMutationSizeError, InferErrors<T>> =>
|
|
4021
|
-
brand("ValidMutationSize", type, (value) =>
|
|
4022
|
-
pack(value).byteLength <= maxMutationSize
|
|
4023
|
-
? ok(value)
|
|
4024
|
-
: err<ValidMutationSizeError>({ type: "ValidMutationSize", value }),
|
|
4025
|
-
);
|
|
4026
|
-
|
|
4027
|
-
export interface ValidMutationSizeError extends TypeError<"ValidMutationSize"> {}
|
|
4028
|
-
|
|
4029
|
-
export const formatValidMutationSizeError =
|
|
4030
|
-
createTypeErrorFormatter<ValidMutationSizeError>(
|
|
4031
|
-
(error) =>
|
|
4032
|
-
`The mutation size exceeds the maximum limit of ${maxMutationSize} bytes. The provided mutation has a size of ${pack(error.value).byteLength} bytes.`,
|
|
4033
|
-
);
|
|
4034
|
-
|
|
4035
|
-
export type ValidMutationSize<Props extends Record<string, AnyType>> =
|
|
4036
|
-
BrandType<
|
|
4037
|
-
ObjectType<Props>,
|
|
4038
|
-
"ValidMutationSize",
|
|
4039
|
-
ValidMutationSizeError,
|
|
4040
|
-
InferErrors<ObjectType<Props>>
|
|
4041
|
-
>;
|
|
4495
|
+
};
|
|
4042
4496
|
|
|
4043
4497
|
/**
|
|
4044
4498
|
* Union of all `TypeError`s defined in the `Type.ts` file, including base type
|
|
@@ -4051,7 +4505,7 @@ export type ValidMutationSize<Props extends Record<string, AnyType>> =
|
|
|
4051
4505
|
* Used by {@link createFormatTypeError} to generate human-readable error
|
|
4052
4506
|
* messages.
|
|
4053
4507
|
*
|
|
4054
|
-
* @
|
|
4508
|
+
* @group Utilities
|
|
4055
4509
|
*/
|
|
4056
4510
|
export type TypeErrors<ExtraErrors extends TypeError = never> =
|
|
4057
4511
|
| StringError
|
|
@@ -4062,6 +4516,7 @@ export type TypeErrors<ExtraErrors extends TypeError = never> =
|
|
|
4062
4516
|
| NullError
|
|
4063
4517
|
| FunctionError
|
|
4064
4518
|
| Uint8ArrayError
|
|
4519
|
+
| ArrayBufferError
|
|
4065
4520
|
| InstanceOfError
|
|
4066
4521
|
| EvoluTypeError
|
|
4067
4522
|
| CurrencyCodeError
|
|
@@ -4092,7 +4547,6 @@ export type TypeErrors<ExtraErrors extends TypeError = never> =
|
|
|
4092
4547
|
| Int64Error
|
|
4093
4548
|
| Int64StringError
|
|
4094
4549
|
| JsonError
|
|
4095
|
-
| ValidMutationSizeError
|
|
4096
4550
|
| ExtraErrors
|
|
4097
4551
|
// Composite errors
|
|
4098
4552
|
| ArrayError<TypeErrors<ExtraErrors>>
|
|
@@ -4185,7 +4639,7 @@ export type TypeErrors<ExtraErrors extends TypeError = never> =
|
|
|
4185
4639
|
* };
|
|
4186
4640
|
* ```
|
|
4187
4641
|
*
|
|
4188
|
-
* @
|
|
4642
|
+
* @group Utilities
|
|
4189
4643
|
*/
|
|
4190
4644
|
export const createFormatTypeError = <ExtraErrors extends TypeError = never>(
|
|
4191
4645
|
extraFormatter?: TypeErrorFormatter<ExtraErrors>,
|
|
@@ -4216,6 +4670,8 @@ export const createFormatTypeError = <ExtraErrors extends TypeError = never>(
|
|
|
4216
4670
|
return formatFunctionError(error);
|
|
4217
4671
|
case "Uint8Array":
|
|
4218
4672
|
return formatUint8ArrayError(error);
|
|
4673
|
+
case "ArrayBuffer":
|
|
4674
|
+
return formatArrayBufferError(error);
|
|
4219
4675
|
case "InstanceOf":
|
|
4220
4676
|
return formatInstanceOfError(error);
|
|
4221
4677
|
case "EvoluType":
|
|
@@ -4274,8 +4730,6 @@ export const createFormatTypeError = <ExtraErrors extends TypeError = never>(
|
|
|
4274
4730
|
return formatInt64StringError(error);
|
|
4275
4731
|
case "Json":
|
|
4276
4732
|
return formatJsonError(error);
|
|
4277
|
-
case "ValidMutationSize":
|
|
4278
|
-
return formatValidMutationSizeError(error);
|
|
4279
4733
|
// Composite Types
|
|
4280
4734
|
case "SimplePassword":
|
|
4281
4735
|
return formatSimplePasswordError(formatTypeError)(error);
|
|
@@ -4308,25 +4762,25 @@ export const createFormatTypeError = <ExtraErrors extends TypeError = never>(
|
|
|
4308
4762
|
* Converts an Evolu {@link TypeError} to Standard Schema V1 issues format.
|
|
4309
4763
|
*
|
|
4310
4764
|
* This function recursively converts Evolu's typed errors into the Standard
|
|
4311
|
-
* Schema issue format with proper path tracking for nested structures.
|
|
4765
|
+
* Schema issue format with proper path tracking for nested structures. The
|
|
4766
|
+
* `message` field contains the JSON-serialized typed error, which users can
|
|
4767
|
+
* deserialize and format using appropriate {@link TypeErrorFormatter}s.
|
|
4312
4768
|
*
|
|
4313
|
-
* @
|
|
4769
|
+
* @group Utilities
|
|
4314
4770
|
*/
|
|
4315
4771
|
export const typeErrorToStandardSchemaIssues = <
|
|
4316
4772
|
ExtraErrors extends TypeError = never,
|
|
4317
4773
|
>(
|
|
4318
4774
|
error: TypeErrors<ExtraErrors>,
|
|
4319
|
-
formatTypeError: TypeErrorFormatter<TypeErrors<ExtraErrors>>,
|
|
4320
4775
|
path: ReadonlyArray<PropertyKey> = [],
|
|
4321
4776
|
): ReadonlyArray<StandardSchemaV1.Issue> => {
|
|
4322
4777
|
if (error.type === "Array") {
|
|
4323
4778
|
const arrayError = error as ArrayError;
|
|
4324
4779
|
if (arrayError.reason.kind === "NotArray") {
|
|
4325
|
-
return [{ message:
|
|
4780
|
+
return [{ message: JSON.stringify(error), path }];
|
|
4326
4781
|
}
|
|
4327
4782
|
return typeErrorToStandardSchemaIssues(
|
|
4328
4783
|
arrayError.reason.error as TypeErrors<ExtraErrors>,
|
|
4329
|
-
formatTypeError,
|
|
4330
4784
|
[...path, arrayError.reason.index],
|
|
4331
4785
|
);
|
|
4332
4786
|
}
|
|
@@ -4334,11 +4788,10 @@ export const typeErrorToStandardSchemaIssues = <
|
|
|
4334
4788
|
if (error.type === "Set") {
|
|
4335
4789
|
const setError = error as SetError;
|
|
4336
4790
|
if (setError.reason.kind === "NotSet") {
|
|
4337
|
-
return [{ message:
|
|
4791
|
+
return [{ message: JSON.stringify(error), path }];
|
|
4338
4792
|
}
|
|
4339
4793
|
return typeErrorToStandardSchemaIssues(
|
|
4340
4794
|
setError.reason.error as TypeErrors<ExtraErrors>,
|
|
4341
|
-
formatTypeError,
|
|
4342
4795
|
[...path, setError.reason.index],
|
|
4343
4796
|
);
|
|
4344
4797
|
}
|
|
@@ -4349,14 +4802,13 @@ export const typeErrorToStandardSchemaIssues = <
|
|
|
4349
4802
|
objectError.reason.kind === "NotObject" ||
|
|
4350
4803
|
objectError.reason.kind === "ExtraKeys"
|
|
4351
4804
|
) {
|
|
4352
|
-
return [{ message:
|
|
4805
|
+
return [{ message: JSON.stringify(error), path }];
|
|
4353
4806
|
}
|
|
4354
4807
|
const issues: Array<StandardSchemaV1.Issue> = [];
|
|
4355
4808
|
for (const [key, propError] of Object.entries(objectError.reason.errors)) {
|
|
4356
4809
|
issues.push(
|
|
4357
4810
|
...typeErrorToStandardSchemaIssues(
|
|
4358
4811
|
propError as TypeErrors<ExtraErrors>,
|
|
4359
|
-
formatTypeError,
|
|
4360
4812
|
[...path, key],
|
|
4361
4813
|
),
|
|
4362
4814
|
);
|
|
@@ -4367,7 +4819,7 @@ export const typeErrorToStandardSchemaIssues = <
|
|
|
4367
4819
|
if (error.type === "ObjectWithRecord") {
|
|
4368
4820
|
const objectWithRecordError = error as ObjectWithRecordError;
|
|
4369
4821
|
if (objectWithRecordError.reason.kind === "NotObject") {
|
|
4370
|
-
return [{ message:
|
|
4822
|
+
return [{ message: JSON.stringify(error), path }];
|
|
4371
4823
|
}
|
|
4372
4824
|
if (
|
|
4373
4825
|
objectWithRecordError.reason.kind === "IndexKey" ||
|
|
@@ -4375,7 +4827,6 @@ export const typeErrorToStandardSchemaIssues = <
|
|
|
4375
4827
|
) {
|
|
4376
4828
|
return typeErrorToStandardSchemaIssues(
|
|
4377
4829
|
objectWithRecordError.reason.error as TypeErrors<ExtraErrors>,
|
|
4378
|
-
formatTypeError,
|
|
4379
4830
|
[...path, objectWithRecordError.reason.key as PropertyKey],
|
|
4380
4831
|
);
|
|
4381
4832
|
}
|
|
@@ -4386,7 +4837,6 @@ export const typeErrorToStandardSchemaIssues = <
|
|
|
4386
4837
|
issues.push(
|
|
4387
4838
|
...typeErrorToStandardSchemaIssues(
|
|
4388
4839
|
propError as TypeErrors<ExtraErrors>,
|
|
4389
|
-
formatTypeError,
|
|
4390
4840
|
[...path, key],
|
|
4391
4841
|
),
|
|
4392
4842
|
);
|
|
@@ -4397,11 +4847,10 @@ export const typeErrorToStandardSchemaIssues = <
|
|
|
4397
4847
|
if (error.type === "Record") {
|
|
4398
4848
|
const recordError = error as RecordError;
|
|
4399
4849
|
if (recordError.reason.kind === "NotRecord") {
|
|
4400
|
-
return [{ message:
|
|
4850
|
+
return [{ message: JSON.stringify(error), path }];
|
|
4401
4851
|
}
|
|
4402
4852
|
return typeErrorToStandardSchemaIssues(
|
|
4403
4853
|
recordError.reason.error as TypeErrors<ExtraErrors>,
|
|
4404
|
-
formatTypeError,
|
|
4405
4854
|
[...path, recordError.reason.key as PropertyKey],
|
|
4406
4855
|
);
|
|
4407
4856
|
}
|
|
@@ -4409,11 +4858,10 @@ export const typeErrorToStandardSchemaIssues = <
|
|
|
4409
4858
|
if (error.type === "Tuple") {
|
|
4410
4859
|
const tupleError = error as TupleError;
|
|
4411
4860
|
if (tupleError.reason.kind === "InvalidLength") {
|
|
4412
|
-
return [{ message:
|
|
4861
|
+
return [{ message: JSON.stringify(error), path }];
|
|
4413
4862
|
}
|
|
4414
4863
|
return typeErrorToStandardSchemaIssues(
|
|
4415
4864
|
tupleError.reason.error as TypeErrors<ExtraErrors>,
|
|
4416
|
-
formatTypeError,
|
|
4417
4865
|
[...path, tupleError.reason.index],
|
|
4418
4866
|
);
|
|
4419
4867
|
}
|
|
@@ -4421,11 +4869,7 @@ export const typeErrorToStandardSchemaIssues = <
|
|
|
4421
4869
|
if (error.type === "Union") {
|
|
4422
4870
|
const unionError = error as UnionError;
|
|
4423
4871
|
return unionError.errors.flatMap((err) =>
|
|
4424
|
-
typeErrorToStandardSchemaIssues(
|
|
4425
|
-
err as TypeErrors<ExtraErrors>,
|
|
4426
|
-
formatTypeError,
|
|
4427
|
-
path,
|
|
4428
|
-
),
|
|
4872
|
+
typeErrorToStandardSchemaIssues(err as TypeErrors<ExtraErrors>, path),
|
|
4429
4873
|
);
|
|
4430
4874
|
}
|
|
4431
4875
|
|
|
@@ -4434,14 +4878,13 @@ export const typeErrorToStandardSchemaIssues = <
|
|
|
4434
4878
|
if ("parentError" in brandError) {
|
|
4435
4879
|
return typeErrorToStandardSchemaIssues(
|
|
4436
4880
|
brandError.parentError as TypeErrors<ExtraErrors>,
|
|
4437
|
-
formatTypeError,
|
|
4438
4881
|
path,
|
|
4439
4882
|
);
|
|
4440
4883
|
}
|
|
4441
|
-
return [{ message:
|
|
4884
|
+
return [{ message: JSON.stringify(error), path }];
|
|
4442
4885
|
}
|
|
4443
4886
|
|
|
4444
|
-
return [{ message:
|
|
4887
|
+
return [{ message: JSON.stringify(error), path }];
|
|
4445
4888
|
};
|
|
4446
4889
|
|
|
4447
4890
|
/** The Standard Schema interface. */
|
|
@@ -4515,10 +4958,3 @@ export declare namespace StandardSchemaV1 {
|
|
|
4515
4958
|
Schema["~standard"]["types"]
|
|
4516
4959
|
>["output"];
|
|
4517
4960
|
}
|
|
4518
|
-
|
|
4519
|
-
/**
|
|
4520
|
-
* Shared formatter cache for Standard Schema integration - avoids circular
|
|
4521
|
-
* dependency by lazily creating the formatter on first use rather than during
|
|
4522
|
-
* module initialization.
|
|
4523
|
-
*/
|
|
4524
|
-
let cachedStandardSchemaFormatTypeError: TypeErrorFormatter<any> | undefined;
|