@evolu/common 7.4.1 → 8.0.0-next.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/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 +160 -0
- package/dist/src/Lookup.d.ts.map +1 -0
- package/dist/src/Lookup.js +192 -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 +27 -0
- package/dist/src/Polyfills.d.ts.map +1 -0
- package/dist/src/Polyfills.js +299 -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 +2263 -423
- package/dist/src/Task.d.ts.map +1 -1
- package/dist/src/Task.js +1488 -348
- package/dist/src/Test.d.ts +122 -0
- package/dist/src/Test.d.ts.map +1 -0
- package/dist/src/Test.js +66 -0
- package/dist/src/Time.d.ts +201 -48
- package/dist/src/Time.d.ts.map +1 -1
- package/dist/src/Time.js +190 -86
- 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 +582 -209
- package/dist/src/Type.d.ts.map +1 -1
- package/dist/src/Type.js +362 -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 +113 -58
- package/dist/src/WebSocket.d.ts.map +1 -1
- package/dist/src/WebSocket.js +210 -118
- 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 +622 -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 +18 -38
- package/dist/src/local-first/Relay.d.ts.map +1 -1
- package/dist/src/local-first/Relay.js +59 -171
- 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 +50 -53
- package/dist/src/local-first/Storage.d.ts.map +1 -1
- package/dist/src/local-first/Storage.js +121 -173
- 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 +36 -26
- 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 +415 -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 +465 -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 +3685 -740
- package/src/Test.ts +162 -0
- package/src/Time.ts +348 -145
- package/src/Tracer.ts +54 -0
- package/src/Type.ts +851 -430
- package/src/Types.ts +202 -7
- package/src/WebSocket.ts +311 -178
- package/src/Worker.ts +527 -147
- package/src/index.ts +74 -3
- package/src/local-first/Db.ts +871 -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 +440 -303
- package/src/local-first/Query.ts +243 -194
- package/src/local-first/Relay.ts +91 -252
- package/src/local-first/Schema.ts +284 -394
- package/src/local-first/Shared.ts +932 -0
- package/src/local-first/Storage.ts +206 -277
- package/src/local-first/Timestamp.ts +38 -45
- package/src/local-first/index.ts +2 -16
- package/LICENSE +0 -21
- 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,19 @@ 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>();
|
|
758
839
|
|
|
759
840
|
/**
|
|
760
841
|
* `instanceof` {@link Type}.
|
|
@@ -774,7 +855,7 @@ export const formatUint8ArrayError =
|
|
|
774
855
|
* const error = UserInstance.from({}); // err
|
|
775
856
|
* ```
|
|
776
857
|
*
|
|
777
|
-
* @
|
|
858
|
+
* @group Base factories
|
|
778
859
|
*/
|
|
779
860
|
export const instanceOf = <T extends abstract new (...args: any) => any>(
|
|
780
861
|
ctor: T,
|
|
@@ -802,16 +883,17 @@ export interface InstanceOfType<
|
|
|
802
883
|
ctor: T;
|
|
803
884
|
}
|
|
804
885
|
|
|
805
|
-
export const formatInstanceOfError =
|
|
806
|
-
(
|
|
807
|
-
)
|
|
886
|
+
export const formatInstanceOfError =
|
|
887
|
+
/*#__PURE__*/ createTypeErrorFormatter<InstanceOfError>(
|
|
888
|
+
(error) => `The value ${error.value} is not an instance of ${error.ctor}.`,
|
|
889
|
+
);
|
|
808
890
|
|
|
809
891
|
/**
|
|
810
892
|
* JavaScript Date.
|
|
811
893
|
*
|
|
812
|
-
* @
|
|
894
|
+
* @group Base Types
|
|
813
895
|
*/
|
|
814
|
-
export const Date = instanceOf(globalThis.Date);
|
|
896
|
+
export const Date = /*#__PURE__*/ instanceOf(globalThis.Date);
|
|
815
897
|
|
|
816
898
|
/**
|
|
817
899
|
* Validates that an unknown value is an Evolu {@link Type} (i.e., satisfies
|
|
@@ -825,7 +907,7 @@ export const Date = instanceOf(globalThis.Date);
|
|
|
825
907
|
* ```
|
|
826
908
|
*/
|
|
827
909
|
// TODO: Rename to TypeInstance or something like that.
|
|
828
|
-
export const EvoluType = base("EvoluType", (value) =>
|
|
910
|
+
export const EvoluType = /*#__PURE__*/ base("EvoluType", (value) =>
|
|
829
911
|
isType(value)
|
|
830
912
|
? ok(value)
|
|
831
913
|
: err<EvoluTypeError>({
|
|
@@ -836,19 +918,24 @@ export const EvoluType = base("EvoluType", (value) =>
|
|
|
836
918
|
|
|
837
919
|
export interface EvoluTypeError extends TypeError<"EvoluType"> {}
|
|
838
920
|
|
|
839
|
-
export const formatIsTypeError =
|
|
840
|
-
(
|
|
841
|
-
)
|
|
921
|
+
export const formatIsTypeError =
|
|
922
|
+
/*#__PURE__*/ createTypeErrorFormatter<EvoluTypeError>(
|
|
923
|
+
(error) => `Value ${error.value} is not a valid Evolu Type.`,
|
|
924
|
+
);
|
|
842
925
|
|
|
843
926
|
/**
|
|
844
927
|
* Branded {@link Type}.
|
|
845
928
|
*
|
|
929
|
+
* Branding is the recommended way to define types in Evolu. Instead of using
|
|
930
|
+
* primitive types like `string` or `number` directly, wrap them with `brand` to
|
|
931
|
+
* create semantically meaningful types. See {@link Brand} for why this matters.
|
|
932
|
+
*
|
|
846
933
|
* The `brand` Type Factory takes the name of a new {@link Brand}, a parent Type
|
|
847
934
|
* to be branded, and the optional `refine` function for additional constraint.
|
|
848
935
|
*
|
|
849
936
|
* The `refine` function can be omitted if we only want to add a brand.
|
|
850
937
|
*
|
|
851
|
-
* ###
|
|
938
|
+
* ### Example
|
|
852
939
|
*
|
|
853
940
|
* A simple `CurrencyCode` Type:
|
|
854
941
|
*
|
|
@@ -967,7 +1054,7 @@ export const formatIsTypeError = createTypeErrorFormatter<EvoluTypeError>(
|
|
|
967
1054
|
* );
|
|
968
1055
|
* ```
|
|
969
1056
|
*
|
|
970
|
-
* @
|
|
1057
|
+
* @group Base factories
|
|
971
1058
|
*/
|
|
972
1059
|
export function brand<
|
|
973
1060
|
Name extends TypeName,
|
|
@@ -980,6 +1067,7 @@ export function brand<
|
|
|
980
1067
|
refine: (value: Parent) => Result<Parent, RefineError>,
|
|
981
1068
|
): BrandType<ParentType, Name, RefineError, InferErrors<ParentType>>;
|
|
982
1069
|
|
|
1070
|
+
/** Without refine function. */
|
|
983
1071
|
export function brand<Name extends TypeName, ParentType extends AnyType>(
|
|
984
1072
|
name: Name,
|
|
985
1073
|
parent: ParentType,
|
|
@@ -1003,15 +1091,15 @@ export function brand<
|
|
|
1003
1091
|
? (value: unknown) => {
|
|
1004
1092
|
const parentResult = parent.fromUnknown(value);
|
|
1005
1093
|
if (!parentResult.ok) return parentResult;
|
|
1006
|
-
return refine(parentResult.value as
|
|
1094
|
+
return refine(parentResult.value as never);
|
|
1007
1095
|
}
|
|
1008
1096
|
: (value: unknown) => {
|
|
1009
1097
|
const parentResult = parent.fromUnknown(value);
|
|
1010
1098
|
if (!parentResult.ok)
|
|
1011
|
-
return err<BrandWithoutRefineError<Name,
|
|
1099
|
+
return err<BrandWithoutRefineError<Name, never>>({
|
|
1012
1100
|
type: name,
|
|
1013
1101
|
value,
|
|
1014
|
-
parentError: parentResult.error as
|
|
1102
|
+
parentError: parentResult.error as never,
|
|
1015
1103
|
});
|
|
1016
1104
|
return ok(parentResult.value);
|
|
1017
1105
|
};
|
|
@@ -1019,7 +1107,7 @@ export function brand<
|
|
|
1019
1107
|
return {
|
|
1020
1108
|
...createType("Brand", {
|
|
1021
1109
|
fromUnknown,
|
|
1022
|
-
fromParent: (refine ?? ok) as
|
|
1110
|
+
fromParent: (refine ?? ok) as never,
|
|
1023
1111
|
}),
|
|
1024
1112
|
brand: name,
|
|
1025
1113
|
parentType: parent,
|
|
@@ -1053,12 +1141,15 @@ export interface BrandWithoutRefineError<
|
|
|
1053
1141
|
/**
|
|
1054
1142
|
* A three-letter ISO 4217 currency code (e.g., USD, EUR).
|
|
1055
1143
|
*
|
|
1056
|
-
* @
|
|
1144
|
+
* @group String
|
|
1057
1145
|
*/
|
|
1058
|
-
export const CurrencyCode = brand(
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1146
|
+
export const CurrencyCode = /*#__PURE__*/ brand(
|
|
1147
|
+
"CurrencyCode",
|
|
1148
|
+
String,
|
|
1149
|
+
(value) =>
|
|
1150
|
+
/^[A-Z]{3}$/.test(value)
|
|
1151
|
+
? ok(value)
|
|
1152
|
+
: err<CurrencyCodeError>({ type: "CurrencyCode", value }),
|
|
1062
1153
|
);
|
|
1063
1154
|
|
|
1064
1155
|
export type CurrencyCode = typeof CurrencyCode.Type;
|
|
@@ -1066,7 +1157,7 @@ export type CurrencyCode = typeof CurrencyCode.Type;
|
|
|
1066
1157
|
export interface CurrencyCodeError extends TypeError<"CurrencyCode"> {}
|
|
1067
1158
|
|
|
1068
1159
|
export const formatCurrencyCodeError =
|
|
1069
|
-
createTypeErrorFormatter<CurrencyCodeError>(
|
|
1160
|
+
/*#__PURE__*/ createTypeErrorFormatter<CurrencyCodeError>(
|
|
1070
1161
|
(error) => `Invalid currency code: ${error.value}.`,
|
|
1071
1162
|
);
|
|
1072
1163
|
|
|
@@ -1089,9 +1180,9 @@ export const formatCurrencyCodeError =
|
|
|
1089
1180
|
* const error = DateIso.from("10000-01-01T00:00:00.000Z"); // err
|
|
1090
1181
|
* ```
|
|
1091
1182
|
*
|
|
1092
|
-
* @
|
|
1183
|
+
* @group String
|
|
1093
1184
|
*/
|
|
1094
|
-
export const DateIso = brand("DateIso", String, (value) => {
|
|
1185
|
+
export const DateIso = /*#__PURE__*/ brand("DateIso", String, (value) => {
|
|
1095
1186
|
if (value.length !== 24) {
|
|
1096
1187
|
return err<DateIsoError>({ type: "DateIso", value });
|
|
1097
1188
|
}
|
|
@@ -1111,9 +1202,10 @@ export type DateIso = typeof DateIso.Type;
|
|
|
1111
1202
|
|
|
1112
1203
|
export interface DateIsoError extends TypeError<"DateIso"> {}
|
|
1113
1204
|
|
|
1114
|
-
export const formatDateIsoError =
|
|
1115
|
-
(
|
|
1116
|
-
)
|
|
1205
|
+
export const formatDateIsoError =
|
|
1206
|
+
/*#__PURE__*/ createTypeErrorFormatter<DateIsoError>(
|
|
1207
|
+
(error) => `The value ${error.value} is not a valid ISO 8601 date string.`,
|
|
1208
|
+
);
|
|
1117
1209
|
|
|
1118
1210
|
export const dateToDateIso = (value: Date): Result<DateIso, DateIsoError> =>
|
|
1119
1211
|
DateIso.fromParent(value.toISOString());
|
|
@@ -1137,7 +1229,18 @@ export const dateIsoToDate = (value: DateIso): Date =>
|
|
|
1137
1229
|
* );
|
|
1138
1230
|
* ```
|
|
1139
1231
|
*
|
|
1140
|
-
*
|
|
1232
|
+
* ### Numeric literal inference
|
|
1233
|
+
*
|
|
1234
|
+
* When using factories with numeric parameters (like `lessThan`, `maxLength`,
|
|
1235
|
+
* `between`), use numeric literals instead of expressions. TypeScript widens
|
|
1236
|
+
* expressions to `number`, losing the literal type in the brand name:
|
|
1237
|
+
*
|
|
1238
|
+
* ```ts
|
|
1239
|
+
* lessThan(100)(Number); // Brand<"LessThan100"> ✓
|
|
1240
|
+
* lessThan(100 - 1)(Number); // Brand<"LessThan" + number> ✗
|
|
1241
|
+
* ```
|
|
1242
|
+
*
|
|
1243
|
+
* @group Utilities
|
|
1141
1244
|
*/
|
|
1142
1245
|
export type BrandFactory<
|
|
1143
1246
|
Name extends TypeName,
|
|
@@ -1173,7 +1276,7 @@ export type BrandFactory<
|
|
|
1173
1276
|
* type TrimmedNonEmptyString = typeof TrimmedNonEmptyString.Type;
|
|
1174
1277
|
* ```
|
|
1175
1278
|
*
|
|
1176
|
-
* @
|
|
1279
|
+
* @group String
|
|
1177
1280
|
*/
|
|
1178
1281
|
export const trimmed: BrandFactory<"Trimmed", string, TrimmedError> = (
|
|
1179
1282
|
parent,
|
|
@@ -1186,9 +1289,10 @@ export const trimmed: BrandFactory<"Trimmed", string, TrimmedError> = (
|
|
|
1186
1289
|
|
|
1187
1290
|
export interface TrimmedError extends TypeError<"Trimmed"> {}
|
|
1188
1291
|
|
|
1189
|
-
export const formatTrimmedError =
|
|
1190
|
-
(
|
|
1191
|
-
)
|
|
1292
|
+
export const formatTrimmedError =
|
|
1293
|
+
/*#__PURE__*/ createTypeErrorFormatter<TrimmedError>(
|
|
1294
|
+
(error) => `The value ${error.value} must be trimmed.`,
|
|
1295
|
+
);
|
|
1192
1296
|
|
|
1193
1297
|
/**
|
|
1194
1298
|
* Trimmed string
|
|
@@ -1196,9 +1300,9 @@ export const formatTrimmedError = createTypeErrorFormatter<TrimmedError>(
|
|
|
1196
1300
|
* - Use `TrimmedString.is` to check if an unknown value is trimmed.
|
|
1197
1301
|
* - Use `TrimmedString.from` to check if a string is trimmed.
|
|
1198
1302
|
*
|
|
1199
|
-
* @
|
|
1303
|
+
* @group String
|
|
1200
1304
|
*/
|
|
1201
|
-
export const TrimmedString = trimmed(String);
|
|
1305
|
+
export const TrimmedString = /*#__PURE__*/ trimmed(String);
|
|
1202
1306
|
export type TrimmedString = typeof TrimmedString.Type;
|
|
1203
1307
|
|
|
1204
1308
|
export const trim = (value: string): TrimmedString =>
|
|
@@ -1207,6 +1311,8 @@ export const trim = (value: string): TrimmedString =>
|
|
|
1207
1311
|
/**
|
|
1208
1312
|
* Minimum length.
|
|
1209
1313
|
*
|
|
1314
|
+
* Use numeric literal, not expression. See {@link BrandFactory}.
|
|
1315
|
+
*
|
|
1210
1316
|
* ### Example
|
|
1211
1317
|
*
|
|
1212
1318
|
* ```ts
|
|
@@ -1214,8 +1320,8 @@ export const trim = (value: string): TrimmedString =>
|
|
|
1214
1320
|
* const NonEmptyString = minLength(1)(String);
|
|
1215
1321
|
* ```
|
|
1216
1322
|
*
|
|
1217
|
-
* @
|
|
1218
|
-
* @
|
|
1323
|
+
* @group String
|
|
1324
|
+
* @group Array
|
|
1219
1325
|
*/
|
|
1220
1326
|
export const minLength: <Min extends number>(
|
|
1221
1327
|
min: Min,
|
|
@@ -1231,14 +1337,17 @@ export interface MinLengthError<
|
|
|
1231
1337
|
readonly min: Min;
|
|
1232
1338
|
}
|
|
1233
1339
|
|
|
1234
|
-
export const formatMinLengthError =
|
|
1235
|
-
(
|
|
1236
|
-
|
|
1237
|
-
|
|
1340
|
+
export const formatMinLengthError =
|
|
1341
|
+
/*#__PURE__*/ createTypeErrorFormatter<MinLengthError>(
|
|
1342
|
+
(error) =>
|
|
1343
|
+
`The value ${error.value} does not meet the minimum length of ${error.min}.`,
|
|
1344
|
+
);
|
|
1238
1345
|
|
|
1239
1346
|
/**
|
|
1240
1347
|
* Maximum length.
|
|
1241
1348
|
*
|
|
1349
|
+
* Use numeric literal, not expression. See {@link BrandFactory}.
|
|
1350
|
+
*
|
|
1242
1351
|
* ### Example
|
|
1243
1352
|
*
|
|
1244
1353
|
* ```ts
|
|
@@ -1246,8 +1355,8 @@ export const formatMinLengthError = createTypeErrorFormatter<MinLengthError>(
|
|
|
1246
1355
|
* const String100 = maxLength(100)(String);
|
|
1247
1356
|
* ```
|
|
1248
1357
|
*
|
|
1249
|
-
* @
|
|
1250
|
-
* @
|
|
1358
|
+
* @group String
|
|
1359
|
+
* @group Array
|
|
1251
1360
|
*/
|
|
1252
1361
|
export const maxLength: <Max extends number>(
|
|
1253
1362
|
max: Max,
|
|
@@ -1263,14 +1372,17 @@ export interface MaxLengthError<
|
|
|
1263
1372
|
readonly max: Max;
|
|
1264
1373
|
}
|
|
1265
1374
|
|
|
1266
|
-
export const formatMaxLengthError =
|
|
1267
|
-
(
|
|
1268
|
-
|
|
1269
|
-
|
|
1375
|
+
export const formatMaxLengthError =
|
|
1376
|
+
/*#__PURE__*/ createTypeErrorFormatter<MaxLengthError>(
|
|
1377
|
+
(error) =>
|
|
1378
|
+
`The value ${error.value} exceeds the maximum length of ${error.max}.`,
|
|
1379
|
+
);
|
|
1270
1380
|
|
|
1271
1381
|
/**
|
|
1272
1382
|
* Exact length.
|
|
1273
1383
|
*
|
|
1384
|
+
* Use numeric literal, not expression. See {@link BrandFactory}.
|
|
1385
|
+
*
|
|
1274
1386
|
* ### Example
|
|
1275
1387
|
*
|
|
1276
1388
|
* ```ts
|
|
@@ -1278,8 +1390,8 @@ export const formatMaxLengthError = createTypeErrorFormatter<MaxLengthError>(
|
|
|
1278
1390
|
* const Length1String = length(1)(String);
|
|
1279
1391
|
* ```
|
|
1280
1392
|
*
|
|
1281
|
-
* @
|
|
1282
|
-
* @
|
|
1393
|
+
* @group String
|
|
1394
|
+
* @group Array
|
|
1283
1395
|
*/
|
|
1284
1396
|
export const length: <Exact extends number>(
|
|
1285
1397
|
exact: Exact,
|
|
@@ -1297,49 +1409,52 @@ export interface LengthError<
|
|
|
1297
1409
|
readonly exact: Exact;
|
|
1298
1410
|
}
|
|
1299
1411
|
|
|
1300
|
-
export const formatLengthError =
|
|
1301
|
-
(
|
|
1302
|
-
|
|
1303
|
-
|
|
1412
|
+
export const formatLengthError =
|
|
1413
|
+
/*#__PURE__*/ createTypeErrorFormatter<LengthError>(
|
|
1414
|
+
(error) =>
|
|
1415
|
+
`The value ${error.value} does not have the required length of ${error.exact}.`,
|
|
1416
|
+
);
|
|
1304
1417
|
|
|
1305
|
-
/** @
|
|
1306
|
-
export const NonEmptyString = minLength(1)(String);
|
|
1418
|
+
/** @group String */
|
|
1419
|
+
export const NonEmptyString = /*#__PURE__*/ minLength(1)(String);
|
|
1307
1420
|
export type NonEmptyString = typeof NonEmptyString.Type;
|
|
1308
1421
|
|
|
1309
|
-
/** @
|
|
1310
|
-
export const String100 = maxLength(100)(String);
|
|
1422
|
+
/** @group String */
|
|
1423
|
+
export const String100 = /*#__PURE__*/ maxLength(100)(String);
|
|
1311
1424
|
export type String100 = typeof String100.Type;
|
|
1312
1425
|
|
|
1313
|
-
/** @
|
|
1314
|
-
export const String1000 = maxLength(1000)(String);
|
|
1426
|
+
/** @group String */
|
|
1427
|
+
export const String1000 = /*#__PURE__*/ maxLength(1000)(String);
|
|
1315
1428
|
export type String1000 = typeof String1000.Type;
|
|
1316
1429
|
|
|
1317
|
-
/** @
|
|
1318
|
-
export const NonEmptyString100 = minLength(1)(String100);
|
|
1430
|
+
/** @group String */
|
|
1431
|
+
export const NonEmptyString100 = /*#__PURE__*/ minLength(1)(String100);
|
|
1319
1432
|
export type NonEmptyString100 = typeof NonEmptyString100.Type;
|
|
1320
1433
|
|
|
1321
|
-
/** @
|
|
1322
|
-
export const NonEmptyString1000 = minLength(1)(String1000);
|
|
1434
|
+
/** @group String */
|
|
1435
|
+
export const NonEmptyString1000 = /*#__PURE__*/ minLength(1)(String1000);
|
|
1323
1436
|
export type NonEmptyString1000 = typeof NonEmptyString1000.Type;
|
|
1324
1437
|
|
|
1325
|
-
/** @
|
|
1326
|
-
export const NonEmptyTrimmedString = minLength(1)(TrimmedString);
|
|
1438
|
+
/** @group String */
|
|
1439
|
+
export const NonEmptyTrimmedString = /*#__PURE__*/ minLength(1)(TrimmedString);
|
|
1327
1440
|
export type NonEmptyTrimmedString = typeof NonEmptyTrimmedString.Type;
|
|
1328
1441
|
|
|
1329
|
-
/** @
|
|
1330
|
-
export const TrimmedString100 = maxLength(100)(TrimmedString);
|
|
1442
|
+
/** @group String */
|
|
1443
|
+
export const TrimmedString100 = /*#__PURE__*/ maxLength(100)(TrimmedString);
|
|
1331
1444
|
export type TrimmedString100 = typeof TrimmedString100.Type;
|
|
1332
1445
|
|
|
1333
|
-
/** @
|
|
1334
|
-
export const TrimmedString1000 = maxLength(1000)(TrimmedString);
|
|
1446
|
+
/** @group String */
|
|
1447
|
+
export const TrimmedString1000 = /*#__PURE__*/ maxLength(1000)(TrimmedString);
|
|
1335
1448
|
export type TrimmedString1000 = typeof TrimmedString1000.Type;
|
|
1336
1449
|
|
|
1337
|
-
/** @
|
|
1338
|
-
export const NonEmptyTrimmedString100 =
|
|
1450
|
+
/** @group String */
|
|
1451
|
+
export const NonEmptyTrimmedString100 =
|
|
1452
|
+
/*#__PURE__*/ minLength(1)(TrimmedString100);
|
|
1339
1453
|
export type NonEmptyTrimmedString100 = typeof NonEmptyTrimmedString100.Type;
|
|
1340
1454
|
|
|
1341
|
-
/** @
|
|
1342
|
-
export const NonEmptyTrimmedString1000 =
|
|
1455
|
+
/** @group String */
|
|
1456
|
+
export const NonEmptyTrimmedString1000 =
|
|
1457
|
+
/*#__PURE__*/ minLength(1)(TrimmedString1000);
|
|
1343
1458
|
export type NonEmptyTrimmedString1000 = typeof NonEmptyTrimmedString1000.Type;
|
|
1344
1459
|
|
|
1345
1460
|
/**
|
|
@@ -1349,20 +1464,24 @@ export type NonEmptyTrimmedString1000 = typeof NonEmptyTrimmedString1000.Type;
|
|
|
1349
1464
|
* safely on the user's device using cryptographically secure random number
|
|
1350
1465
|
* generation, ensuring it remains private and unique.
|
|
1351
1466
|
*
|
|
1352
|
-
* @
|
|
1467
|
+
* @group String
|
|
1353
1468
|
*/
|
|
1354
|
-
export const Mnemonic = brand(
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1469
|
+
export const Mnemonic = /*#__PURE__*/ brand(
|
|
1470
|
+
"Mnemonic",
|
|
1471
|
+
NonEmptyTrimmedString,
|
|
1472
|
+
(value) =>
|
|
1473
|
+
bip39.validateMnemonic(value, wordlist)
|
|
1474
|
+
? ok(value)
|
|
1475
|
+
: err<MnemonicError>({ type: "Mnemonic", value }),
|
|
1358
1476
|
);
|
|
1359
1477
|
export type Mnemonic = typeof Mnemonic.Type;
|
|
1360
1478
|
|
|
1361
1479
|
export interface MnemonicError extends TypeError<"Mnemonic"> {}
|
|
1362
1480
|
|
|
1363
|
-
export const formatMnemonicError =
|
|
1364
|
-
(
|
|
1365
|
-
)
|
|
1481
|
+
export const formatMnemonicError =
|
|
1482
|
+
/*#__PURE__*/ createTypeErrorFormatter<MnemonicError>(
|
|
1483
|
+
(error) => `Invalid BIP39 mnemonic: ${error.value}.`,
|
|
1484
|
+
);
|
|
1366
1485
|
|
|
1367
1486
|
/**
|
|
1368
1487
|
* String matching a regular expression.
|
|
@@ -1373,7 +1492,7 @@ export const formatMnemonicError = createTypeErrorFormatter<MnemonicError>(
|
|
|
1373
1492
|
* const Alphanumeric = regex("Alphanumeric", /^[a-z0-9]+$/i)(String);
|
|
1374
1493
|
* ```
|
|
1375
1494
|
*
|
|
1376
|
-
* @
|
|
1495
|
+
* @group String
|
|
1377
1496
|
*/
|
|
1378
1497
|
export const regex: <Name extends TypeName>(
|
|
1379
1498
|
name: Name,
|
|
@@ -1399,10 +1518,11 @@ export interface RegexError<
|
|
|
1399
1518
|
readonly pattern: RegExp;
|
|
1400
1519
|
}
|
|
1401
1520
|
|
|
1402
|
-
export const formatRegexError =
|
|
1403
|
-
(
|
|
1404
|
-
|
|
1405
|
-
|
|
1521
|
+
export const formatRegexError =
|
|
1522
|
+
/*#__PURE__*/ createTypeErrorFormatter<RegexError>(
|
|
1523
|
+
(error) =>
|
|
1524
|
+
`The value ${error.value} does not match the pattern for ${error.name}: ${error.pattern}.`,
|
|
1525
|
+
);
|
|
1406
1526
|
|
|
1407
1527
|
/**
|
|
1408
1528
|
* URL-safe string.
|
|
@@ -1429,9 +1549,12 @@ export const formatRegexError = createTypeErrorFormatter<RegexError>(
|
|
|
1429
1549
|
* }
|
|
1430
1550
|
* ```
|
|
1431
1551
|
*
|
|
1432
|
-
* @
|
|
1552
|
+
* @group String
|
|
1433
1553
|
*/
|
|
1434
|
-
export const UrlSafeString = regex(
|
|
1554
|
+
export const UrlSafeString = /*#__PURE__*/ regex(
|
|
1555
|
+
"UrlSafeString",
|
|
1556
|
+
/^[A-Za-z0-9_-]+$/,
|
|
1557
|
+
)(String);
|
|
1435
1558
|
export type UrlSafeString = typeof UrlSafeString.Type;
|
|
1436
1559
|
export type UrlSafeStringError = typeof UrlSafeString.Error;
|
|
1437
1560
|
|
|
@@ -1441,9 +1564,9 @@ export type UrlSafeStringError = typeof UrlSafeString.Error;
|
|
|
1441
1564
|
* Encode with {@link uint8ArrayToBase64Url}, decode with
|
|
1442
1565
|
* {@link base64UrlToUint8Array}.
|
|
1443
1566
|
*
|
|
1444
|
-
* @
|
|
1567
|
+
* @group String
|
|
1445
1568
|
*/
|
|
1446
|
-
export const Base64Url = brand(
|
|
1569
|
+
export const Base64Url = /*#__PURE__*/ brand(
|
|
1447
1570
|
"Base64Url",
|
|
1448
1571
|
String,
|
|
1449
1572
|
(value: string): Result<string, Base64UrlError> => {
|
|
@@ -1466,9 +1589,10 @@ export const Base64Url = brand(
|
|
|
1466
1589
|
export type Base64Url = typeof Base64Url.Type;
|
|
1467
1590
|
export interface Base64UrlError extends TypeError<"Base64Url"> {}
|
|
1468
1591
|
|
|
1469
|
-
export const formatBase64UrlError =
|
|
1470
|
-
(
|
|
1471
|
-
)
|
|
1592
|
+
export const formatBase64UrlError =
|
|
1593
|
+
/*#__PURE__*/ createTypeErrorFormatter<Base64UrlError>(
|
|
1594
|
+
(error) => `The value ${error.value} is not a valid Base64Url string.`,
|
|
1595
|
+
);
|
|
1472
1596
|
|
|
1473
1597
|
const base64UrlOptions = { alphabet: "base64url", omitPadding: true };
|
|
1474
1598
|
|
|
@@ -1520,33 +1644,35 @@ export const base64UrlToUint8Array: (str: Base64Url) => Uint8Array =
|
|
|
1520
1644
|
};
|
|
1521
1645
|
|
|
1522
1646
|
/**
|
|
1523
|
-
*
|
|
1647
|
+
* Alphanumeric string for naming in file systems, URLs, and identifiers.
|
|
1524
1648
|
*
|
|
1525
1649
|
* Uses the same safe alphabet as {@link UrlSafeString} (letters, digits, `-`,
|
|
1526
|
-
* `_`).
|
|
1650
|
+
* `_`).
|
|
1527
1651
|
*
|
|
1528
1652
|
* The string must be between 1 and 64 characters.
|
|
1529
1653
|
*
|
|
1530
1654
|
* ### Example
|
|
1531
1655
|
*
|
|
1532
1656
|
* ```ts
|
|
1533
|
-
* const result =
|
|
1657
|
+
* const result = Name.from("data-report-123");
|
|
1534
1658
|
* if (result.ok) {
|
|
1535
|
-
* console.log("Valid
|
|
1659
|
+
* console.log("Valid Name string:", result.value);
|
|
1536
1660
|
* } else {
|
|
1537
|
-
* console.error("Invalid
|
|
1661
|
+
* console.error("Invalid Name string:", result.error);
|
|
1538
1662
|
* }
|
|
1539
1663
|
* ```
|
|
1540
1664
|
*
|
|
1541
|
-
* @
|
|
1665
|
+
* @group String
|
|
1542
1666
|
*/
|
|
1543
|
-
export const
|
|
1667
|
+
export const Name = /*#__PURE__*/ brand("Name", UrlSafeString, (value) =>
|
|
1544
1668
|
value.length >= 1 && value.length <= 64
|
|
1545
1669
|
? ok(value)
|
|
1546
|
-
: err<
|
|
1670
|
+
: err<NameError>({ type: "Name", value }),
|
|
1547
1671
|
);
|
|
1548
|
-
export type
|
|
1549
|
-
export interface
|
|
1672
|
+
export type Name = typeof Name.Type;
|
|
1673
|
+
export interface NameError extends TypeError<"Name"> {}
|
|
1674
|
+
|
|
1675
|
+
export const testName = /*#__PURE__*/ Name.orThrow("Name");
|
|
1550
1676
|
|
|
1551
1677
|
/**
|
|
1552
1678
|
* Trimmed string between 8 and 64 characters, branded as `SimplePassword`.
|
|
@@ -1570,11 +1696,11 @@ export interface SimpleNameError extends TypeError<"SimpleName"> {}
|
|
|
1570
1696
|
* // |> brand("SimplePassword", %)
|
|
1571
1697
|
* ```
|
|
1572
1698
|
*
|
|
1573
|
-
* @
|
|
1699
|
+
* @group String
|
|
1574
1700
|
*/
|
|
1575
|
-
export const SimplePassword = brand(
|
|
1701
|
+
export const SimplePassword = /*#__PURE__*/ brand(
|
|
1576
1702
|
"SimplePassword",
|
|
1577
|
-
minLength(8)(maxLength(64)(TrimmedString)),
|
|
1703
|
+
/*#__PURE__*/ minLength(8)(/*#__PURE__*/ maxLength(64)(TrimmedString)),
|
|
1578
1704
|
);
|
|
1579
1705
|
export type SimplePassword = typeof SimplePassword.Type;
|
|
1580
1706
|
|
|
@@ -1605,16 +1731,16 @@ export const formatSimplePasswordError = (
|
|
|
1605
1731
|
* copied (logs, URLs, exports); only use it when you explicitly want insertion
|
|
1606
1732
|
* locality for very large write‑heavy tables and accept timestamp exposure.
|
|
1607
1733
|
*
|
|
1608
|
-
*
|
|
1734
|
+
* ## Future
|
|
1609
1735
|
*
|
|
1610
1736
|
* A possible hybrid masked‑time approach (`timestamp ^ H(cluster_id, timestamp
|
|
1611
1737
|
*
|
|
1612
1738
|
* > > N)`) could provide locality without exposing raw creation time. See
|
|
1613
1739
|
* > > https://brooker.co.za/blog/2025/10/22/uuidv7.html
|
|
1614
1740
|
*
|
|
1615
|
-
* @
|
|
1741
|
+
* @group String
|
|
1616
1742
|
*/
|
|
1617
|
-
export const Id = brand("Id", String, (value) =>
|
|
1743
|
+
export const Id = /*#__PURE__*/ brand("Id", String, (value) =>
|
|
1618
1744
|
value.length === 22 && Base64Url.fromParent(value).ok
|
|
1619
1745
|
? ok(value)
|
|
1620
1746
|
: err<IdError>({ type: "Id", value }),
|
|
@@ -1623,7 +1749,7 @@ export type Id = typeof Id.Type;
|
|
|
1623
1749
|
|
|
1624
1750
|
export interface IdError extends TypeError<"Id"> {}
|
|
1625
1751
|
|
|
1626
|
-
export const formatIdError = createTypeErrorFormatter<IdError>(
|
|
1752
|
+
export const formatIdError = /*#__PURE__*/ createTypeErrorFormatter<IdError>(
|
|
1627
1753
|
(error) => `The value ${error.value} is not a valid Id.`,
|
|
1628
1754
|
);
|
|
1629
1755
|
|
|
@@ -1678,7 +1804,7 @@ export const createId = <B extends string = never>(
|
|
|
1678
1804
|
* external string from the generated {@link Id}. If you need to preserve the
|
|
1679
1805
|
* original external ID, store it in a separate column.
|
|
1680
1806
|
*
|
|
1681
|
-
* @
|
|
1807
|
+
* @group String
|
|
1682
1808
|
*/
|
|
1683
1809
|
export const createIdFromString = <B extends string = never>(
|
|
1684
1810
|
value: string,
|
|
@@ -1723,7 +1849,9 @@ export const createIdAsUuidv7 = <B extends string = never>(
|
|
|
1723
1849
|
id[6] = (id[6] & 0x0f) | 0x70;
|
|
1724
1850
|
id[8] = (id[8] & 0x3f) | 0x80;
|
|
1725
1851
|
|
|
1726
|
-
return id as unknown as [B] extends [never]
|
|
1852
|
+
return uint8ArrayToBase64Url(id) as unknown as [B] extends [never]
|
|
1853
|
+
? Id
|
|
1854
|
+
: Id & Brand<B>;
|
|
1727
1855
|
};
|
|
1728
1856
|
|
|
1729
1857
|
/**
|
|
@@ -1739,7 +1867,7 @@ export const createIdAsUuidv7 = <B extends string = never>(
|
|
|
1739
1867
|
* type TodoId = typeof TodoId.Type;
|
|
1740
1868
|
* ```
|
|
1741
1869
|
*
|
|
1742
|
-
* @
|
|
1870
|
+
* @group String
|
|
1743
1871
|
*/
|
|
1744
1872
|
export const id = <Table extends TypeName>(table: Table): TableId<Table> => {
|
|
1745
1873
|
const fromUnknown = (value: unknown) => {
|
|
@@ -1779,12 +1907,16 @@ export interface TableIdError<
|
|
|
1779
1907
|
readonly table: Table;
|
|
1780
1908
|
}
|
|
1781
1909
|
|
|
1782
|
-
export const formatTableIdError =
|
|
1783
|
-
(
|
|
1784
|
-
)
|
|
1910
|
+
export const formatTableIdError =
|
|
1911
|
+
/*#__PURE__*/ createTypeErrorFormatter<TableIdError>(
|
|
1912
|
+
(error) => `Invalid Id for table ${error.table}: ${error.value}.`,
|
|
1913
|
+
);
|
|
1785
1914
|
|
|
1786
1915
|
/** Binary representation of an {@link Id}. */
|
|
1787
|
-
export const IdBytes = brand(
|
|
1916
|
+
export const IdBytes = /*#__PURE__*/ brand(
|
|
1917
|
+
"IdBytes",
|
|
1918
|
+
/*#__PURE__*/ length(16)(Uint8Array),
|
|
1919
|
+
);
|
|
1788
1920
|
export type IdBytes = typeof IdBytes.Type;
|
|
1789
1921
|
|
|
1790
1922
|
export const idBytesTypeValueLength = 16 as NonNegativeInt;
|
|
@@ -1809,7 +1941,7 @@ export const idBytesToId = (idBytes: IdBytes): Id =>
|
|
|
1809
1941
|
* const errorResult = PositiveNumber.from(-5); // err
|
|
1810
1942
|
* ```
|
|
1811
1943
|
*
|
|
1812
|
-
* @
|
|
1944
|
+
* @group Number
|
|
1813
1945
|
*/
|
|
1814
1946
|
export const positive: BrandFactory<"Positive", number, PositiveError> = (
|
|
1815
1947
|
parent,
|
|
@@ -1820,9 +1952,10 @@ export const positive: BrandFactory<"Positive", number, PositiveError> = (
|
|
|
1820
1952
|
|
|
1821
1953
|
export interface PositiveError extends TypeError<"Positive"> {}
|
|
1822
1954
|
|
|
1823
|
-
export const formatPositiveError =
|
|
1824
|
-
|
|
1825
|
-
)
|
|
1955
|
+
export const formatPositiveError =
|
|
1956
|
+
/*#__PURE__*/ createTypeErrorFormatter<PositiveError>(
|
|
1957
|
+
(error) => `The value ${error.value} must be positive (> 0).`,
|
|
1958
|
+
);
|
|
1826
1959
|
|
|
1827
1960
|
/**
|
|
1828
1961
|
* Negative number (< 0).
|
|
@@ -1833,7 +1966,7 @@ export const formatPositiveError = createTypeErrorFormatter<PositiveError>(
|
|
|
1833
1966
|
* const NegativeNumber = negative(Number);
|
|
1834
1967
|
* ```
|
|
1835
1968
|
*
|
|
1836
|
-
* @
|
|
1969
|
+
* @group Number
|
|
1837
1970
|
*/
|
|
1838
1971
|
export const negative: BrandFactory<"Negative", number, NegativeError> = (
|
|
1839
1972
|
parent,
|
|
@@ -1844,9 +1977,10 @@ export const negative: BrandFactory<"Negative", number, NegativeError> = (
|
|
|
1844
1977
|
|
|
1845
1978
|
export interface NegativeError extends TypeError<"Negative"> {}
|
|
1846
1979
|
|
|
1847
|
-
export const formatNegativeError =
|
|
1848
|
-
|
|
1849
|
-
)
|
|
1980
|
+
export const formatNegativeError =
|
|
1981
|
+
/*#__PURE__*/ createTypeErrorFormatter<NegativeError>(
|
|
1982
|
+
(error) => `The value ${error.value} must be negative (< 0).`,
|
|
1983
|
+
);
|
|
1850
1984
|
|
|
1851
1985
|
/**
|
|
1852
1986
|
* Non-positive number (≤ 0).
|
|
@@ -1857,7 +1991,7 @@ export const formatNegativeError = createTypeErrorFormatter<NegativeError>(
|
|
|
1857
1991
|
* const NonPositiveNumber = nonPositive(Number);
|
|
1858
1992
|
* ```
|
|
1859
1993
|
*
|
|
1860
|
-
* @
|
|
1994
|
+
* @group Number
|
|
1861
1995
|
*/
|
|
1862
1996
|
export const nonPositive: BrandFactory<
|
|
1863
1997
|
"NonPositive",
|
|
@@ -1873,7 +2007,7 @@ export const nonPositive: BrandFactory<
|
|
|
1873
2007
|
export interface NonPositiveError extends TypeError<"NonPositive"> {}
|
|
1874
2008
|
|
|
1875
2009
|
export const formatNonPositiveError =
|
|
1876
|
-
createTypeErrorFormatter<NonPositiveError>(
|
|
2010
|
+
/*#__PURE__*/ createTypeErrorFormatter<NonPositiveError>(
|
|
1877
2011
|
(error) => `The value ${error.value} must be non-positive (≤ 0).`,
|
|
1878
2012
|
);
|
|
1879
2013
|
|
|
@@ -1886,7 +2020,7 @@ export const formatNonPositiveError =
|
|
|
1886
2020
|
* const NonNegativeNumber = nonNegative(Number);
|
|
1887
2021
|
* ```
|
|
1888
2022
|
*
|
|
1889
|
-
* @
|
|
2023
|
+
* @group Number
|
|
1890
2024
|
*/
|
|
1891
2025
|
export const nonNegative: BrandFactory<
|
|
1892
2026
|
"NonNegative",
|
|
@@ -1902,40 +2036,40 @@ export const nonNegative: BrandFactory<
|
|
|
1902
2036
|
export interface NonNegativeError extends TypeError<"NonNegative"> {}
|
|
1903
2037
|
|
|
1904
2038
|
export const formatNonNegativeError =
|
|
1905
|
-
createTypeErrorFormatter<NonNegativeError>(
|
|
2039
|
+
/*#__PURE__*/ createTypeErrorFormatter<NonNegativeError>(
|
|
1906
2040
|
(error) => `The value ${error.value} must be non-negative (≥ 0).`,
|
|
1907
2041
|
);
|
|
1908
2042
|
|
|
1909
2043
|
/**
|
|
1910
2044
|
* Non-negative number (≥ 0).
|
|
1911
2045
|
*
|
|
1912
|
-
* @
|
|
2046
|
+
* @group Number
|
|
1913
2047
|
*/
|
|
1914
|
-
export const NonNegativeNumber = nonNegative(Number);
|
|
2048
|
+
export const NonNegativeNumber = /*#__PURE__*/ nonNegative(Number);
|
|
1915
2049
|
export type NonNegativeNumber = typeof NonNegativeNumber.Type;
|
|
1916
2050
|
|
|
1917
2051
|
/**
|
|
1918
2052
|
* Positive number (> 0).
|
|
1919
2053
|
*
|
|
1920
|
-
* @
|
|
2054
|
+
* @group Number
|
|
1921
2055
|
*/
|
|
1922
|
-
export const PositiveNumber = positive(NonNegativeNumber);
|
|
2056
|
+
export const PositiveNumber = /*#__PURE__*/ positive(NonNegativeNumber);
|
|
1923
2057
|
export type PositiveNumber = typeof PositiveNumber.Type;
|
|
1924
2058
|
|
|
1925
2059
|
/**
|
|
1926
2060
|
* Non-positive number (≤ 0).
|
|
1927
2061
|
*
|
|
1928
|
-
* @
|
|
2062
|
+
* @group Number
|
|
1929
2063
|
*/
|
|
1930
|
-
export const NonPositiveNumber = nonPositive(Number);
|
|
2064
|
+
export const NonPositiveNumber = /*#__PURE__*/ nonPositive(Number);
|
|
1931
2065
|
export type NonPositiveNumber = typeof NonPositiveNumber.Type;
|
|
1932
2066
|
|
|
1933
2067
|
/**
|
|
1934
2068
|
* Negative number (< 0).
|
|
1935
2069
|
*
|
|
1936
|
-
* @
|
|
2070
|
+
* @group Number
|
|
1937
2071
|
*/
|
|
1938
|
-
export const NegativeNumber = negative(NonPositiveNumber);
|
|
2072
|
+
export const NegativeNumber = /*#__PURE__*/ negative(NonPositiveNumber);
|
|
1939
2073
|
export type NegativeNumber = typeof NegativeNumber.Type;
|
|
1940
2074
|
|
|
1941
2075
|
/**
|
|
@@ -1944,12 +2078,17 @@ export type NegativeNumber = typeof NegativeNumber.Type;
|
|
|
1944
2078
|
* ### Example
|
|
1945
2079
|
*
|
|
1946
2080
|
* ```ts
|
|
1947
|
-
* const Int =
|
|
2081
|
+
* const Int = safeInt(Number);
|
|
1948
2082
|
* ```
|
|
1949
2083
|
*
|
|
1950
|
-
*
|
|
2084
|
+
* Note: This helper was previously named `int`. A Next.js regression produced
|
|
2085
|
+
* runtime `int is not defined`, so we keep the implementation under the
|
|
2086
|
+
* `safeInt` alias.
|
|
2087
|
+
*
|
|
2088
|
+
* @group Number
|
|
1951
2089
|
*/
|
|
1952
|
-
|
|
2090
|
+
|
|
2091
|
+
const safeInt: BrandFactory<"Int", number, IntError> = (parent) =>
|
|
1953
2092
|
brand("Int", parent, (value) =>
|
|
1954
2093
|
globalThis.Number.isSafeInteger(value)
|
|
1955
2094
|
? ok(value)
|
|
@@ -1958,59 +2097,67 @@ export const int: BrandFactory<"Int", number, IntError> = (parent) =>
|
|
|
1958
2097
|
|
|
1959
2098
|
export interface IntError extends TypeError<"Int"> {}
|
|
1960
2099
|
|
|
1961
|
-
export const formatIntError = createTypeErrorFormatter<IntError>(
|
|
2100
|
+
export const formatIntError = /*#__PURE__*/ createTypeErrorFormatter<IntError>(
|
|
1962
2101
|
(error) => `The value ${error.value} must be an integer.`,
|
|
1963
2102
|
);
|
|
1964
2103
|
|
|
1965
2104
|
/**
|
|
1966
2105
|
* Integer within the safe range of JavaScript numbers.
|
|
1967
2106
|
*
|
|
1968
|
-
* @
|
|
2107
|
+
* @group Number
|
|
1969
2108
|
*/
|
|
1970
|
-
export const Int =
|
|
2109
|
+
export const Int = /*#__PURE__*/ safeInt(Number);
|
|
1971
2110
|
export type Int = typeof Int.Type;
|
|
1972
2111
|
|
|
1973
2112
|
/**
|
|
1974
2113
|
* Non-negative integer (≥ 0).
|
|
1975
2114
|
*
|
|
1976
|
-
* @
|
|
2115
|
+
* @group Number
|
|
1977
2116
|
*/
|
|
1978
|
-
export const NonNegativeInt = nonNegative(Int);
|
|
2117
|
+
export const NonNegativeInt = /*#__PURE__*/ nonNegative(Int);
|
|
1979
2118
|
export type NonNegativeInt = typeof NonNegativeInt.Type;
|
|
1980
2119
|
|
|
2120
|
+
/** Minimum {@link NonNegativeInt} value (0). */
|
|
2121
|
+
export const zeroNonNegativeInt = /*#__PURE__*/ NonNegativeInt.orThrow(0);
|
|
2122
|
+
|
|
1981
2123
|
/**
|
|
1982
2124
|
* Positive integer (> 0).
|
|
1983
2125
|
*
|
|
1984
|
-
* @
|
|
2126
|
+
* @group Number
|
|
1985
2127
|
*/
|
|
1986
|
-
export const PositiveInt = positive(NonNegativeInt);
|
|
2128
|
+
export const PositiveInt = /*#__PURE__*/ positive(NonNegativeInt);
|
|
1987
2129
|
export type PositiveInt = typeof PositiveInt.Type;
|
|
1988
2130
|
|
|
1989
|
-
/**
|
|
1990
|
-
export const
|
|
2131
|
+
/** {@link PositiveInt} value 1. */
|
|
2132
|
+
export const onePositiveInt = /*#__PURE__*/ PositiveInt.orThrow(1);
|
|
2133
|
+
|
|
2134
|
+
/** Maximum {@link PositiveInt} value (MAX_SAFE_INTEGER). */
|
|
2135
|
+
export const maxPositiveInt = /*#__PURE__*/ PositiveInt.orThrow(
|
|
1991
2136
|
globalThis.Number.MAX_SAFE_INTEGER,
|
|
1992
2137
|
);
|
|
1993
2138
|
|
|
1994
2139
|
/**
|
|
1995
2140
|
* Non-positive integer (≤ 0).
|
|
1996
2141
|
*
|
|
1997
|
-
* @
|
|
2142
|
+
* @group Number
|
|
1998
2143
|
*/
|
|
1999
|
-
export const NonPositiveInt = nonPositive(Int);
|
|
2144
|
+
export const NonPositiveInt = /*#__PURE__*/ nonPositive(Int);
|
|
2000
2145
|
export type NonPositiveInt = typeof NonPositiveInt.Type;
|
|
2001
2146
|
|
|
2002
2147
|
/**
|
|
2003
2148
|
* Negative integer (< 0).
|
|
2004
2149
|
*
|
|
2005
|
-
* @
|
|
2150
|
+
* @group Number
|
|
2006
2151
|
*/
|
|
2007
|
-
export const NegativeInt = negative(NonPositiveInt);
|
|
2152
|
+
export const NegativeInt = /*#__PURE__*/ negative(NonPositiveInt);
|
|
2008
2153
|
export type NegativeInt = typeof NegativeInt.Type;
|
|
2009
2154
|
|
|
2010
2155
|
/**
|
|
2011
2156
|
* Number greater than a specified value.
|
|
2012
2157
|
*
|
|
2013
|
-
* @
|
|
2158
|
+
* Use numeric literal, not expression. See {@link BrandFactory}.
|
|
2159
|
+
*
|
|
2160
|
+
* @group Number
|
|
2014
2161
|
*/
|
|
2015
2162
|
export const greaterThan: <Min extends number>(
|
|
2016
2163
|
min: Min,
|
|
@@ -2027,14 +2174,16 @@ export interface GreaterThanError<
|
|
|
2027
2174
|
}
|
|
2028
2175
|
|
|
2029
2176
|
export const formatGreaterThanError =
|
|
2030
|
-
createTypeErrorFormatter<GreaterThanError>(
|
|
2177
|
+
/*#__PURE__*/ createTypeErrorFormatter<GreaterThanError>(
|
|
2031
2178
|
(error) => `The value ${error.value} is not > ${error.min}.`,
|
|
2032
2179
|
);
|
|
2033
2180
|
|
|
2034
2181
|
/**
|
|
2035
2182
|
* Number less than a specified value.
|
|
2036
2183
|
*
|
|
2037
|
-
* @
|
|
2184
|
+
* Use numeric literal, not expression. See {@link BrandFactory}.
|
|
2185
|
+
*
|
|
2186
|
+
* @group Number
|
|
2038
2187
|
*/
|
|
2039
2188
|
export const lessThan: <Max extends number>(
|
|
2040
2189
|
max: Max,
|
|
@@ -2050,14 +2199,17 @@ export interface LessThanError<
|
|
|
2050
2199
|
readonly max: Max;
|
|
2051
2200
|
}
|
|
2052
2201
|
|
|
2053
|
-
export const formatLessThanError =
|
|
2054
|
-
|
|
2055
|
-
)
|
|
2202
|
+
export const formatLessThanError =
|
|
2203
|
+
/*#__PURE__*/ createTypeErrorFormatter<LessThanError>(
|
|
2204
|
+
(error) => `The value ${error.value} is not < ${error.max}.`,
|
|
2205
|
+
);
|
|
2056
2206
|
|
|
2057
2207
|
/**
|
|
2058
2208
|
* Number ≥ a specified value.
|
|
2059
2209
|
*
|
|
2060
|
-
* @
|
|
2210
|
+
* Use numeric literal, not expression. See {@link BrandFactory}.
|
|
2211
|
+
*
|
|
2212
|
+
* @group Number
|
|
2061
2213
|
*/
|
|
2062
2214
|
export const greaterThanOrEqualTo: <Min extends number>(
|
|
2063
2215
|
min: Min,
|
|
@@ -2079,14 +2231,16 @@ export interface GreaterThanOrEqualToError<
|
|
|
2079
2231
|
}
|
|
2080
2232
|
|
|
2081
2233
|
export const formatGreaterThanOrEqualToError =
|
|
2082
|
-
createTypeErrorFormatter<GreaterThanOrEqualToError>(
|
|
2234
|
+
/*#__PURE__*/ createTypeErrorFormatter<GreaterThanOrEqualToError>(
|
|
2083
2235
|
(error) => `The value ${error.value} is not >= ${error.min}.`,
|
|
2084
2236
|
);
|
|
2085
2237
|
|
|
2086
2238
|
/**
|
|
2087
2239
|
* Number ≤ a specified value.
|
|
2088
2240
|
*
|
|
2089
|
-
* @
|
|
2241
|
+
* Use numeric literal, not expression. See {@link BrandFactory}.
|
|
2242
|
+
*
|
|
2243
|
+
* @group Number
|
|
2090
2244
|
*/
|
|
2091
2245
|
export const lessThanOrEqualTo: <Max extends number>(
|
|
2092
2246
|
max: Max,
|
|
@@ -2106,14 +2260,14 @@ export interface LessThanOrEqualToError<
|
|
|
2106
2260
|
}
|
|
2107
2261
|
|
|
2108
2262
|
export const formatLessThanOrEqualToError =
|
|
2109
|
-
createTypeErrorFormatter<LessThanOrEqualToError>(
|
|
2263
|
+
/*#__PURE__*/ createTypeErrorFormatter<LessThanOrEqualToError>(
|
|
2110
2264
|
(error) => `The value ${error.value} is not <= ${error.max}.`,
|
|
2111
2265
|
);
|
|
2112
2266
|
|
|
2113
2267
|
/**
|
|
2114
2268
|
* Number that is not NaN.
|
|
2115
2269
|
*
|
|
2116
|
-
* @
|
|
2270
|
+
* @group Number
|
|
2117
2271
|
*/
|
|
2118
2272
|
export const nonNaN: BrandFactory<"NonNaN", number, NonNaNError> = (parent) =>
|
|
2119
2273
|
brand("NonNaN", parent, (value) =>
|
|
@@ -2124,18 +2278,19 @@ export const nonNaN: BrandFactory<"NonNaN", number, NonNaNError> = (parent) =>
|
|
|
2124
2278
|
|
|
2125
2279
|
export interface NonNaNError extends TypeError<"NonNaN"> {}
|
|
2126
2280
|
|
|
2127
|
-
export const formatNonNaNError =
|
|
2128
|
-
(
|
|
2129
|
-
)
|
|
2281
|
+
export const formatNonNaNError =
|
|
2282
|
+
/*#__PURE__*/ createTypeErrorFormatter<NonNaNError>(
|
|
2283
|
+
() => `The value must not be NaN.`,
|
|
2284
|
+
);
|
|
2130
2285
|
|
|
2131
|
-
/** @
|
|
2132
|
-
export const NonNaNNumber = nonNaN(Number);
|
|
2286
|
+
/** @group Number */
|
|
2287
|
+
export const NonNaNNumber = /*#__PURE__*/ nonNaN(Number);
|
|
2133
2288
|
export type NonNaNNumber = typeof NonNaNNumber.Type;
|
|
2134
2289
|
|
|
2135
2290
|
/**
|
|
2136
2291
|
* Finite number.
|
|
2137
2292
|
*
|
|
2138
|
-
* @
|
|
2293
|
+
* @group Number
|
|
2139
2294
|
*/
|
|
2140
2295
|
export const finite: BrandFactory<"Finite", number, FiniteError> = (parent) =>
|
|
2141
2296
|
brand("Finite", parent, (value) =>
|
|
@@ -2146,9 +2301,10 @@ export const finite: BrandFactory<"Finite", number, FiniteError> = (parent) =>
|
|
|
2146
2301
|
|
|
2147
2302
|
export interface FiniteError extends TypeError<"Finite"> {}
|
|
2148
2303
|
|
|
2149
|
-
export const formatFiniteError =
|
|
2150
|
-
(
|
|
2151
|
-
)
|
|
2304
|
+
export const formatFiniteError =
|
|
2305
|
+
/*#__PURE__*/ createTypeErrorFormatter<FiniteError>(
|
|
2306
|
+
(error) => `The value ${error.value} must be finite.`,
|
|
2307
|
+
);
|
|
2152
2308
|
|
|
2153
2309
|
/**
|
|
2154
2310
|
* Finite number.
|
|
@@ -2161,15 +2317,17 @@ export const formatFiniteError = createTypeErrorFormatter<FiniteError>(
|
|
|
2161
2317
|
* finite (e.g., `Infinity`, `-Infinity`, or `NaN`). Using `FiniteNumber` helps
|
|
2162
2318
|
* prevent these unexpected behaviors when working with JSON serialization.
|
|
2163
2319
|
*
|
|
2164
|
-
* @
|
|
2320
|
+
* @group Number
|
|
2165
2321
|
*/
|
|
2166
|
-
export const FiniteNumber = finite(Number);
|
|
2322
|
+
export const FiniteNumber = /*#__PURE__*/ finite(Number);
|
|
2167
2323
|
export type FiniteNumber = typeof FiniteNumber.Type;
|
|
2168
2324
|
|
|
2169
2325
|
/**
|
|
2170
2326
|
* Number that is a multiple of a divisor.
|
|
2171
2327
|
*
|
|
2172
|
-
* @
|
|
2328
|
+
* Use numeric literal, not expression. See {@link BrandFactory}.
|
|
2329
|
+
*
|
|
2330
|
+
* @group Number
|
|
2173
2331
|
*/
|
|
2174
2332
|
export const multipleOf: <Divisor extends number>(
|
|
2175
2333
|
divisor: Divisor,
|
|
@@ -2187,13 +2345,17 @@ export interface MultipleOfError<
|
|
|
2187
2345
|
readonly divisor: Divisor;
|
|
2188
2346
|
}
|
|
2189
2347
|
|
|
2190
|
-
export const formatMultipleOfError =
|
|
2191
|
-
(
|
|
2192
|
-
)
|
|
2348
|
+
export const formatMultipleOfError =
|
|
2349
|
+
/*#__PURE__*/ createTypeErrorFormatter<MultipleOfError>(
|
|
2350
|
+
(error) =>
|
|
2351
|
+
`The value ${error.value} is not a multiple of ${error.divisor}.`,
|
|
2352
|
+
);
|
|
2193
2353
|
|
|
2194
2354
|
/**
|
|
2195
2355
|
* Number within a range, inclusive.
|
|
2196
2356
|
*
|
|
2357
|
+
* Use numeric literal, not expression. See {@link BrandFactory}.
|
|
2358
|
+
*
|
|
2197
2359
|
* ### Example
|
|
2198
2360
|
*
|
|
2199
2361
|
* ```ts
|
|
@@ -2202,7 +2364,7 @@ export const formatMultipleOfError = createTypeErrorFormatter<MultipleOfError>(
|
|
|
2202
2364
|
* const errorResult = Between1And10.from(11); // err
|
|
2203
2365
|
* ```
|
|
2204
2366
|
*
|
|
2205
|
-
* @
|
|
2367
|
+
* @group Number
|
|
2206
2368
|
*/
|
|
2207
2369
|
export const between: <Min extends number, Max extends number>(
|
|
2208
2370
|
min: Min,
|
|
@@ -2223,10 +2385,11 @@ export interface BetweenError<
|
|
|
2223
2385
|
readonly max: Max;
|
|
2224
2386
|
}
|
|
2225
2387
|
|
|
2226
|
-
export const formatBetweenError =
|
|
2227
|
-
(
|
|
2228
|
-
|
|
2229
|
-
|
|
2388
|
+
export const formatBetweenError =
|
|
2389
|
+
/*#__PURE__*/ createTypeErrorFormatter<BetweenError>(
|
|
2390
|
+
(error) =>
|
|
2391
|
+
`The value ${error.value} is not between ${error.min} and ${error.max}, inclusive.`,
|
|
2392
|
+
);
|
|
2230
2393
|
|
|
2231
2394
|
/**
|
|
2232
2395
|
* Literal {@link Type}.
|
|
@@ -2243,7 +2406,7 @@ export const formatBetweenError = createTypeErrorFormatter<BetweenError>(
|
|
|
2243
2406
|
*
|
|
2244
2407
|
* TODO: Add JsonValue
|
|
2245
2408
|
*
|
|
2246
|
-
* @
|
|
2409
|
+
* @group Base factories
|
|
2247
2410
|
*/
|
|
2248
2411
|
export const literal = <T extends Literal>(expected: T): LiteralType<T> => {
|
|
2249
2412
|
const fromUnknown = (value: unknown): Result<T, LiteralError<T>> =>
|
|
@@ -2275,17 +2438,18 @@ export interface LiteralError<
|
|
|
2275
2438
|
readonly expected: T;
|
|
2276
2439
|
}
|
|
2277
2440
|
|
|
2278
|
-
export const formatLiteralError =
|
|
2279
|
-
(
|
|
2280
|
-
|
|
2281
|
-
error.expected
|
|
2282
|
-
|
|
2283
|
-
)
|
|
2441
|
+
export const formatLiteralError =
|
|
2442
|
+
/*#__PURE__*/ createTypeErrorFormatter<LiteralError>(
|
|
2443
|
+
(error) =>
|
|
2444
|
+
`The value ${error.value} is not strictly equal to the expected literal: ${globalThis.String(
|
|
2445
|
+
error.expected,
|
|
2446
|
+
)}.`,
|
|
2447
|
+
);
|
|
2284
2448
|
|
|
2285
2449
|
/**
|
|
2286
2450
|
* Array of a specific {@link Type}.
|
|
2287
2451
|
*
|
|
2288
|
-
*
|
|
2452
|
+
* ### Example
|
|
2289
2453
|
*
|
|
2290
2454
|
* ```ts
|
|
2291
2455
|
* const NumberArray = array(Number);
|
|
@@ -2294,8 +2458,8 @@ export const formatLiteralError = createTypeErrorFormatter<LiteralError>(
|
|
|
2294
2458
|
* const result2 = NumberArray.from(["a", "b"]); // err(...)
|
|
2295
2459
|
* ```
|
|
2296
2460
|
*
|
|
2297
|
-
* @
|
|
2298
|
-
* @
|
|
2461
|
+
* @group Base factories
|
|
2462
|
+
* @group Array
|
|
2299
2463
|
*/
|
|
2300
2464
|
export const array = <ElementType extends AnyType>(
|
|
2301
2465
|
element: ElementType,
|
|
@@ -2413,7 +2577,7 @@ export const formatArrayError = <Error extends TypeError>(
|
|
|
2413
2577
|
* const result2 = NumberSet.from(new Set(["a", "b"])); // err(...)
|
|
2414
2578
|
* ```
|
|
2415
2579
|
*
|
|
2416
|
-
* @
|
|
2580
|
+
* @group Base factories
|
|
2417
2581
|
*/
|
|
2418
2582
|
export const set = <ElementType extends AnyType>(
|
|
2419
2583
|
element: ElementType,
|
|
@@ -2543,8 +2707,8 @@ export const formatSetError = <Error extends TypeError>(
|
|
|
2543
2707
|
* StringToNumberRecord.from({ a: "x", b: 2 });
|
|
2544
2708
|
* ```
|
|
2545
2709
|
*
|
|
2546
|
-
* @
|
|
2547
|
-
* @
|
|
2710
|
+
* @group Base factories
|
|
2711
|
+
* @group Object
|
|
2548
2712
|
*/
|
|
2549
2713
|
export const record = <
|
|
2550
2714
|
KeyName extends TypeName,
|
|
@@ -2591,7 +2755,7 @@ export const record = <
|
|
|
2591
2755
|
type: "Record",
|
|
2592
2756
|
value,
|
|
2593
2757
|
reason: { kind: "Key", key: rawKey, error: keyResult.error },
|
|
2594
|
-
} as
|
|
2758
|
+
} as never);
|
|
2595
2759
|
}
|
|
2596
2760
|
|
|
2597
2761
|
const valueResult = valueType.fromUnknown(rawValue);
|
|
@@ -2602,12 +2766,12 @@ export const record = <
|
|
|
2602
2766
|
reason: {
|
|
2603
2767
|
kind: "Value",
|
|
2604
2768
|
key: rawKey,
|
|
2605
|
-
error: valueResult.error as
|
|
2769
|
+
error: valueResult.error as never,
|
|
2606
2770
|
},
|
|
2607
2771
|
});
|
|
2608
2772
|
}
|
|
2609
2773
|
|
|
2610
|
-
result[keyResult.value] = valueResult.value as
|
|
2774
|
+
result[keyResult.value] = valueResult.value as never;
|
|
2611
2775
|
}
|
|
2612
2776
|
|
|
2613
2777
|
return ok(result);
|
|
@@ -2664,7 +2828,7 @@ export const record = <
|
|
|
2664
2828
|
}),
|
|
2665
2829
|
key: keyType,
|
|
2666
2830
|
value: valueType,
|
|
2667
|
-
} as
|
|
2831
|
+
} as never;
|
|
2668
2832
|
};
|
|
2669
2833
|
|
|
2670
2834
|
/**
|
|
@@ -2748,22 +2912,22 @@ export const formatRecordError = <Error extends TypeError>(
|
|
|
2748
2912
|
*
|
|
2749
2913
|
* https://www.typescriptlang.org/docs/handbook/2/objects.html#index-signatures
|
|
2750
2914
|
*
|
|
2751
|
-
* ###
|
|
2915
|
+
* ### Example
|
|
2752
2916
|
*
|
|
2753
|
-
*
|
|
2917
|
+
* ## Basic Object Validation
|
|
2754
2918
|
*
|
|
2755
2919
|
* ```ts
|
|
2756
2920
|
* const User = object({
|
|
2757
2921
|
* name: NonEmptyTrimmedString,
|
|
2758
2922
|
* age: PositiveNumber,
|
|
2759
2923
|
* });
|
|
2760
|
-
*
|
|
2924
|
+
* interface User extends InferType<typeof User> {}
|
|
2761
2925
|
*
|
|
2762
2926
|
* const result = User.from({ name: "John", age: 30 }); // ok({ name: "John", age: 30 })
|
|
2763
2927
|
* const error = User.from({ name: "John", age: -5 }); // err
|
|
2764
2928
|
* ```
|
|
2765
2929
|
*
|
|
2766
|
-
*
|
|
2930
|
+
* ## Optional Properties
|
|
2767
2931
|
*
|
|
2768
2932
|
* In this example the `age` property is marked as optional using
|
|
2769
2933
|
* {@link optional}.
|
|
@@ -2773,10 +2937,10 @@ export const formatRecordError = <Error extends TypeError>(
|
|
|
2773
2937
|
* name: NonEmptyString, // Required
|
|
2774
2938
|
* age: optional(PositiveNumber), // Optional
|
|
2775
2939
|
* });
|
|
2776
|
-
*
|
|
2940
|
+
* interface User extends InferType<typeof User> {}
|
|
2777
2941
|
* ```
|
|
2778
2942
|
*
|
|
2779
|
-
*
|
|
2943
|
+
* ## Allowing Additional Properties
|
|
2780
2944
|
*
|
|
2781
2945
|
* ```ts
|
|
2782
2946
|
* const UserWithAnyExtraProperties = object(
|
|
@@ -2795,7 +2959,7 @@ export const formatRecordError = <Error extends TypeError>(
|
|
|
2795
2959
|
* });
|
|
2796
2960
|
* ```
|
|
2797
2961
|
*
|
|
2798
|
-
*
|
|
2962
|
+
* ## Combining Fixed and Flexible Properties
|
|
2799
2963
|
*
|
|
2800
2964
|
* ```ts
|
|
2801
2965
|
* const NumberDictionary = object(
|
|
@@ -2832,13 +2996,14 @@ export const formatRecordError = <Error extends TypeError>(
|
|
|
2832
2996
|
* );
|
|
2833
2997
|
* ```
|
|
2834
2998
|
*
|
|
2835
|
-
* @
|
|
2836
|
-
* @
|
|
2999
|
+
* @group Base factories
|
|
3000
|
+
* @group Object
|
|
2837
3001
|
*/
|
|
2838
3002
|
export function object<Props extends Record<string, AnyType>>(
|
|
2839
3003
|
props: Props,
|
|
2840
3004
|
): ObjectType<Props>;
|
|
2841
3005
|
|
|
3006
|
+
/** With additional record properties. */
|
|
2842
3007
|
export function object<
|
|
2843
3008
|
Props extends Record<string, AnyType>,
|
|
2844
3009
|
KeyName extends TypeName,
|
|
@@ -2901,9 +3066,9 @@ export function object(
|
|
|
2901
3066
|
|
|
2902
3067
|
const propResult = props[key].fromUnknown(value[key]);
|
|
2903
3068
|
if (!propResult.ok) {
|
|
2904
|
-
errors[key] = propResult.error as
|
|
3069
|
+
errors[key] = propResult.error as never;
|
|
2905
3070
|
} else {
|
|
2906
|
-
result[key] = propResult.value as
|
|
3071
|
+
result[key] = propResult.value as never;
|
|
2907
3072
|
}
|
|
2908
3073
|
}
|
|
2909
3074
|
|
|
@@ -2921,7 +3086,7 @@ export function object(
|
|
|
2921
3086
|
reason: {
|
|
2922
3087
|
kind: "IndexKey",
|
|
2923
3088
|
key,
|
|
2924
|
-
error: keyResult.error as
|
|
3089
|
+
error: keyResult.error as never,
|
|
2925
3090
|
},
|
|
2926
3091
|
});
|
|
2927
3092
|
}
|
|
@@ -2934,12 +3099,12 @@ export function object(
|
|
|
2934
3099
|
reason: {
|
|
2935
3100
|
kind: "IndexValue",
|
|
2936
3101
|
key,
|
|
2937
|
-
error: valueResult.error as
|
|
3102
|
+
error: valueResult.error as never,
|
|
2938
3103
|
},
|
|
2939
3104
|
});
|
|
2940
3105
|
}
|
|
2941
3106
|
|
|
2942
|
-
result[keyResult.value] = valueResult.value as
|
|
3107
|
+
result[keyResult.value] = valueResult.value as never;
|
|
2943
3108
|
}
|
|
2944
3109
|
} else if (extraKeys.length > 0) {
|
|
2945
3110
|
return err({
|
|
@@ -3092,7 +3257,7 @@ export interface ObjectError<
|
|
|
3092
3257
|
* Merge Error and ParentError into one ObjectError so tooltips and error
|
|
3093
3258
|
* messages are easier to read.
|
|
3094
3259
|
*
|
|
3095
|
-
* @
|
|
3260
|
+
* @group Utilities
|
|
3096
3261
|
*/
|
|
3097
3262
|
export type MergeObjectTypeErrors<T extends ObjectType<any>> =
|
|
3098
3263
|
T extends ObjectType<infer Props>
|
|
@@ -3202,6 +3367,187 @@ export const formatObjectWithRecordError = <Error extends TypeError>(
|
|
|
3202
3367
|
}
|
|
3203
3368
|
});
|
|
3204
3369
|
|
|
3370
|
+
/**
|
|
3371
|
+
* Base interface for objects with a discriminant `type` property.
|
|
3372
|
+
*
|
|
3373
|
+
* This enables
|
|
3374
|
+
* {@link https://www.typescriptlang.org/docs/handbook/2/narrowing.html#discriminated-unions | discriminated unions}
|
|
3375
|
+
* (also known as tagged unions) — a pattern where TypeScript uses a literal
|
|
3376
|
+
* `type` field to narrow union types automatically.
|
|
3377
|
+
*
|
|
3378
|
+
* ## Why Discriminated Unions?
|
|
3379
|
+
*
|
|
3380
|
+
* Discriminated unions model states that are **mutually exclusive**. Instead of
|
|
3381
|
+
* optional fields and boolean flags that can combine into invalid
|
|
3382
|
+
* configurations, each variant is a distinct type. This makes illegal states
|
|
3383
|
+
* unrepresentable — invalid combinations cannot exist, so bugs cannot create
|
|
3384
|
+
* them.
|
|
3385
|
+
*
|
|
3386
|
+
* Benefits:
|
|
3387
|
+
*
|
|
3388
|
+
* - **Self-documenting** — Union cases immediately show all possible states
|
|
3389
|
+
* - **Compile-time safety** — TypeScript enforces handling all cases
|
|
3390
|
+
* - **Refactoring-friendly** — Adding a new state breaks code that doesn't handle
|
|
3391
|
+
* it
|
|
3392
|
+
*
|
|
3393
|
+
* ### Example
|
|
3394
|
+
*
|
|
3395
|
+
* ```ts
|
|
3396
|
+
* // Bad: optional fields allow invalid states (no contact info at all)
|
|
3397
|
+
* interface Contact {
|
|
3398
|
+
* readonly email?: Email;
|
|
3399
|
+
* readonly phone?: Phone;
|
|
3400
|
+
* }
|
|
3401
|
+
*
|
|
3402
|
+
* // Good: discriminated union makes "at least one" explicit
|
|
3403
|
+
* interface EmailOnly extends Typed<"EmailOnly"> {
|
|
3404
|
+
* readonly email: Email;
|
|
3405
|
+
* }
|
|
3406
|
+
* interface PhoneOnly extends Typed<"PhoneOnly"> {
|
|
3407
|
+
* readonly phone: Phone;
|
|
3408
|
+
* }
|
|
3409
|
+
* interface EmailAndPhone extends Typed<"EmailAndPhone"> {
|
|
3410
|
+
* readonly email: Email;
|
|
3411
|
+
* readonly phone: Phone;
|
|
3412
|
+
* }
|
|
3413
|
+
*
|
|
3414
|
+
* type ContactInfo = EmailOnly | PhoneOnly | EmailAndPhone;
|
|
3415
|
+
* ```
|
|
3416
|
+
*
|
|
3417
|
+
* ```ts
|
|
3418
|
+
* interface Pending extends Typed<"Pending"> {
|
|
3419
|
+
* readonly createdAt: DateIso;
|
|
3420
|
+
* }
|
|
3421
|
+
* interface Shipped extends Typed<"Shipped"> {
|
|
3422
|
+
* readonly trackingNumber: TrackingNumber;
|
|
3423
|
+
* }
|
|
3424
|
+
* interface Delivered extends Typed<"Delivered"> {
|
|
3425
|
+
* readonly deliveredAt: DateIso;
|
|
3426
|
+
* }
|
|
3427
|
+
* interface Cancelled extends Typed<"Cancelled"> {
|
|
3428
|
+
* readonly reason: CancellationReason;
|
|
3429
|
+
* }
|
|
3430
|
+
*
|
|
3431
|
+
* type OrderState = Pending | Shipped | Delivered | Cancelled;
|
|
3432
|
+
*
|
|
3433
|
+
* // TypeScript enforces exhaustiveness via return type
|
|
3434
|
+
* const getStatusMessage = (state: OrderState): string => {
|
|
3435
|
+
* switch (state.type) {
|
|
3436
|
+
* case "Pending":
|
|
3437
|
+
* return "Order placed";
|
|
3438
|
+
* case "Shipped":
|
|
3439
|
+
* return `Shipped: ${state.trackingNumber}`;
|
|
3440
|
+
* case "Delivered":
|
|
3441
|
+
* return `Delivered on ${state.deliveredAt.toLocaleDateString()}`;
|
|
3442
|
+
* case "Cancelled":
|
|
3443
|
+
* return `Cancelled: ${state.reason}`;
|
|
3444
|
+
* }
|
|
3445
|
+
* };
|
|
3446
|
+
*
|
|
3447
|
+
* // For void functions, use exhaustiveCheck to ensure all cases are handled
|
|
3448
|
+
* const logState = (state: OrderState): void => {
|
|
3449
|
+
* switch (state.type) {
|
|
3450
|
+
* case "Pending":
|
|
3451
|
+
* console.log("Order placed");
|
|
3452
|
+
* break;
|
|
3453
|
+
* case "Shipped":
|
|
3454
|
+
* console.log(`Shipped: ${state.trackingNumber}`);
|
|
3455
|
+
* break;
|
|
3456
|
+
* case "Delivered":
|
|
3457
|
+
* console.log(
|
|
3458
|
+
* `Delivered on ${state.deliveredAt.toLocaleDateString()}`,
|
|
3459
|
+
* );
|
|
3460
|
+
* break;
|
|
3461
|
+
* case "Cancelled":
|
|
3462
|
+
* console.log(`Cancelled: ${state.reason}`);
|
|
3463
|
+
* break;
|
|
3464
|
+
* default:
|
|
3465
|
+
* exhaustiveCheck(state);
|
|
3466
|
+
* }
|
|
3467
|
+
* };
|
|
3468
|
+
* ```
|
|
3469
|
+
*
|
|
3470
|
+
* ## Why `type` (and not e.g. `_tag`)?
|
|
3471
|
+
*
|
|
3472
|
+
* Underscore-prefixing is meant to avoid clashing with domain properties, but
|
|
3473
|
+
* proper discriminated union design means the discriminant IS the domain
|
|
3474
|
+
* concept — there's no clash to avoid. The `type` prop name also aligns with
|
|
3475
|
+
* {@link Type}'s name. If an entity has a meaningful "type" (like product
|
|
3476
|
+
* category), model it as the discriminant itself:
|
|
3477
|
+
*
|
|
3478
|
+
* ```ts
|
|
3479
|
+
* interface Electronics extends Typed<"Electronics"> {
|
|
3480
|
+
* voltage: Voltage;
|
|
3481
|
+
* }
|
|
3482
|
+
* interface Clothing extends Typed<"Clothing"> {
|
|
3483
|
+
* size: Size;
|
|
3484
|
+
* }
|
|
3485
|
+
* type Product = Electronics | Clothing;
|
|
3486
|
+
* ```
|
|
3487
|
+
*
|
|
3488
|
+
* @see {@link exhaustiveCheck} to ensure all cases are handled in void functions.
|
|
3489
|
+
* @see {@link typed} for runtime-validated typed objects.
|
|
3490
|
+
*/
|
|
3491
|
+
export interface Typed<T extends TypeName> {
|
|
3492
|
+
readonly type: T;
|
|
3493
|
+
}
|
|
3494
|
+
|
|
3495
|
+
/**
|
|
3496
|
+
* Creates a runtime-validated typed object with a `type` discriminant.
|
|
3497
|
+
*
|
|
3498
|
+
* ### Example
|
|
3499
|
+
*
|
|
3500
|
+
* ```ts
|
|
3501
|
+
* const Card = typed("Card", {
|
|
3502
|
+
* cardNumber: CardNumber,
|
|
3503
|
+
* expiry: DateIso,
|
|
3504
|
+
* });
|
|
3505
|
+
*
|
|
3506
|
+
* const Cash = typed("Cash", {
|
|
3507
|
+
* currency: NonEmptyTrimmedString,
|
|
3508
|
+
* });
|
|
3509
|
+
*
|
|
3510
|
+
* const Payment = union(Card, Cash);
|
|
3511
|
+
* type Payment = typeof Payment.Type;
|
|
3512
|
+
*
|
|
3513
|
+
* const result = Payment.fromUnknown(data);
|
|
3514
|
+
* if (result.ok) {
|
|
3515
|
+
* switch (result.value.type) {
|
|
3516
|
+
* case "Card":
|
|
3517
|
+
* console.log(result.value.cardNumber);
|
|
3518
|
+
* break;
|
|
3519
|
+
* case "Cash":
|
|
3520
|
+
* console.log(result.value.currency);
|
|
3521
|
+
* break;
|
|
3522
|
+
* }
|
|
3523
|
+
* }
|
|
3524
|
+
* ```
|
|
3525
|
+
*
|
|
3526
|
+
* @see {@link Typed} for type-only discrimination.
|
|
3527
|
+
*/
|
|
3528
|
+
export function typed<Tag extends TypeName>(tag: Tag): TypedType<Tag>;
|
|
3529
|
+
/** With additional properties. */
|
|
3530
|
+
export function typed<
|
|
3531
|
+
Tag extends TypeName,
|
|
3532
|
+
Props extends Record<string, AnyType>,
|
|
3533
|
+
>(tag: Tag, props: Props): TypedType<Tag, Props>;
|
|
3534
|
+
export function typed<
|
|
3535
|
+
Tag extends TypeName,
|
|
3536
|
+
Props extends Record<string, AnyType>,
|
|
3537
|
+
>(tag: Tag, props?: Props): ObjectType<{ type: LiteralType<Tag> } & Props> {
|
|
3538
|
+
return object({ type: literal(tag), ...props } as {
|
|
3539
|
+
type: LiteralType<Tag>;
|
|
3540
|
+
} & Props);
|
|
3541
|
+
}
|
|
3542
|
+
|
|
3543
|
+
/** Return type of {@link typed}. */
|
|
3544
|
+
export type TypedType<
|
|
3545
|
+
Tag extends TypeName,
|
|
3546
|
+
Props extends Record<string, AnyType> = Record<never, never>,
|
|
3547
|
+
> = ObjectType<{ type: LiteralType<Tag> } & Props>;
|
|
3548
|
+
|
|
3549
|
+
// TODO: TypeError with "Error" suffix.
|
|
3550
|
+
|
|
3205
3551
|
/**
|
|
3206
3552
|
* Union {@link Type}.
|
|
3207
3553
|
*
|
|
@@ -3224,12 +3570,13 @@ export const formatObjectWithRecordError = <Error extends TypeError>(
|
|
|
3224
3570
|
* const result3 = StringOrNumber.from(42); // ok(42)
|
|
3225
3571
|
* ```
|
|
3226
3572
|
*
|
|
3227
|
-
* @
|
|
3573
|
+
* @group Base factories
|
|
3228
3574
|
*/
|
|
3229
3575
|
export function union<
|
|
3230
3576
|
Members extends [AnyType, AnyType, ...ReadonlyArray<AnyType>],
|
|
3231
3577
|
>(...members: Members): UnionType<Members>;
|
|
3232
3578
|
|
|
3579
|
+
/** With literal values. */
|
|
3233
3580
|
export function union<
|
|
3234
3581
|
Literals extends [Literal, Literal, ...ReadonlyArray<Literal>],
|
|
3235
3582
|
>(
|
|
@@ -3319,6 +3666,110 @@ export const isUnionType = (
|
|
|
3319
3666
|
): t is UnionType<[AnyType, ...ReadonlyArray<AnyType>]> =>
|
|
3320
3667
|
t.name === "Union" && Array.isArray((t as { members?: unknown }).members);
|
|
3321
3668
|
|
|
3669
|
+
/**
|
|
3670
|
+
* Creates a {@link Type} for {@link Result} values.
|
|
3671
|
+
*
|
|
3672
|
+
* Use for validating serialized Results from storage, APIs, or message passing.
|
|
3673
|
+
*
|
|
3674
|
+
* ### Example
|
|
3675
|
+
*
|
|
3676
|
+
* ```ts
|
|
3677
|
+
* const SyncResponse = result(
|
|
3678
|
+
* object({ timestamp: NonNegativeInt }),
|
|
3679
|
+
* typed("SyncError", { message: String }),
|
|
3680
|
+
* );
|
|
3681
|
+
*
|
|
3682
|
+
* // Validate response from worker or API
|
|
3683
|
+
* const validated = SyncResponse.from(JSON.parse(message));
|
|
3684
|
+
* if (!validated.ok) return validated; // validation error
|
|
3685
|
+
* // validated.value is Result<{ timestamp }, SyncError>
|
|
3686
|
+
* ```
|
|
3687
|
+
*
|
|
3688
|
+
* @group Composite factories
|
|
3689
|
+
*/
|
|
3690
|
+
export const result = <OkType extends AnyType, ErrType extends AnyType>(
|
|
3691
|
+
okType: OkType,
|
|
3692
|
+
errType: ErrType,
|
|
3693
|
+
): UnionType<
|
|
3694
|
+
[
|
|
3695
|
+
ObjectType<{ ok: LiteralType<true>; value: OkType }>,
|
|
3696
|
+
ObjectType<{ ok: LiteralType<false>; error: ErrType }>,
|
|
3697
|
+
]
|
|
3698
|
+
> =>
|
|
3699
|
+
union(
|
|
3700
|
+
object({ ok: literal(true), value: okType }),
|
|
3701
|
+
object({ ok: literal(false), error: errType }),
|
|
3702
|
+
);
|
|
3703
|
+
|
|
3704
|
+
/**
|
|
3705
|
+
* A {@link result} type for `Result<unknown, unknown>`.
|
|
3706
|
+
*
|
|
3707
|
+
* Useful for serializing Results where the value and error types are unknown.
|
|
3708
|
+
*
|
|
3709
|
+
* @group Composite factories
|
|
3710
|
+
*/
|
|
3711
|
+
export const UnknownResult = /*#__PURE__*/ result(Unknown, Unknown);
|
|
3712
|
+
export type UnknownResult = typeof UnknownResult.Type;
|
|
3713
|
+
|
|
3714
|
+
/**
|
|
3715
|
+
* Creates a {@link Type} for {@link NextResult} with three outcomes.
|
|
3716
|
+
*
|
|
3717
|
+
* Validates results where the producer responds with:
|
|
3718
|
+
*
|
|
3719
|
+
* - `Ok<A>` — produced a value
|
|
3720
|
+
* - `Err<Done<D>>` — completed normally with a done value
|
|
3721
|
+
* - `Err<E>` — failed with an error
|
|
3722
|
+
*
|
|
3723
|
+
* ### Example
|
|
3724
|
+
*
|
|
3725
|
+
* ```ts
|
|
3726
|
+
* const MyNextResult = nextResult(Item, MyError, Summary);
|
|
3727
|
+
*
|
|
3728
|
+
* const validated = MyNextResult.fromUnknown(data);
|
|
3729
|
+
* if (!validated.ok) return validated;
|
|
3730
|
+
*
|
|
3731
|
+
* const result = validated.value;
|
|
3732
|
+
* if (result.ok) {
|
|
3733
|
+
* console.log(result.value);
|
|
3734
|
+
* } else if (result.error.type === "Done") {
|
|
3735
|
+
* console.log("Done:", result.error.done);
|
|
3736
|
+
* } else {
|
|
3737
|
+
* console.error(result.error);
|
|
3738
|
+
* }
|
|
3739
|
+
* ```
|
|
3740
|
+
*
|
|
3741
|
+
* @group Composite factories
|
|
3742
|
+
*/
|
|
3743
|
+
export const nextResult = <
|
|
3744
|
+
ValueType extends AnyType,
|
|
3745
|
+
ErrorType extends AnyType,
|
|
3746
|
+
DoneType extends AnyType,
|
|
3747
|
+
>(
|
|
3748
|
+
valueType: ValueType,
|
|
3749
|
+
errorType: ErrorType,
|
|
3750
|
+
doneType: DoneType,
|
|
3751
|
+
): ReturnType<
|
|
3752
|
+
typeof result<
|
|
3753
|
+
ValueType,
|
|
3754
|
+
UnionType<[ErrorType, TypedType<"Done", { done: DoneType }>]>
|
|
3755
|
+
>
|
|
3756
|
+
> => result(valueType, union(errorType, typed("Done", { done: doneType })));
|
|
3757
|
+
|
|
3758
|
+
/**
|
|
3759
|
+
* A {@link nextResult} type for `NextResult<unknown, unknown, unknown>`.
|
|
3760
|
+
*
|
|
3761
|
+
* Useful for checking if a value is a {@link NextResult} via
|
|
3762
|
+
* `UnknownNextResult.is(value)`.
|
|
3763
|
+
*
|
|
3764
|
+
* @group Composite factories
|
|
3765
|
+
*/
|
|
3766
|
+
export const UnknownNextResult = /*#__PURE__*/ nextResult(
|
|
3767
|
+
Unknown,
|
|
3768
|
+
Unknown,
|
|
3769
|
+
Unknown,
|
|
3770
|
+
);
|
|
3771
|
+
export type UnknownNextResult = typeof UnknownNextResult.Type;
|
|
3772
|
+
|
|
3322
3773
|
/**
|
|
3323
3774
|
* Recursive {@link Type}.
|
|
3324
3775
|
*
|
|
@@ -3352,7 +3803,7 @@ export const isUnionType = (
|
|
|
3352
3803
|
* );
|
|
3353
3804
|
* ```
|
|
3354
3805
|
*
|
|
3355
|
-
* @
|
|
3806
|
+
* @group Base factories
|
|
3356
3807
|
*/
|
|
3357
3808
|
export const recursive = <ParentType extends AnyType>(
|
|
3358
3809
|
create: () => ParentType,
|
|
@@ -3412,7 +3863,7 @@ export interface RecursiveType<ParentType extends AnyType> extends Type<
|
|
|
3412
3863
|
* NullOrString.from(42); // err(...)
|
|
3413
3864
|
* ```
|
|
3414
3865
|
*
|
|
3415
|
-
* @
|
|
3866
|
+
* @group Base factories
|
|
3416
3867
|
*/
|
|
3417
3868
|
export const nullOr = <T extends AnyType>(
|
|
3418
3869
|
type: T,
|
|
@@ -3430,7 +3881,7 @@ export const nullOr = <T extends AnyType>(
|
|
|
3430
3881
|
* UndefinedOrString.from(42); // err(...)
|
|
3431
3882
|
* ```
|
|
3432
3883
|
*
|
|
3433
|
-
* @
|
|
3884
|
+
* @group Base factories
|
|
3434
3885
|
*/
|
|
3435
3886
|
export const undefinedOr = <T extends AnyType>(
|
|
3436
3887
|
type: T,
|
|
@@ -3452,7 +3903,7 @@ export const undefinedOr = <T extends AnyType>(
|
|
|
3452
3903
|
* NullishOrString.from(42); // err(...)
|
|
3453
3904
|
* ```
|
|
3454
3905
|
*
|
|
3455
|
-
* @
|
|
3906
|
+
* @group Base factories
|
|
3456
3907
|
*/
|
|
3457
3908
|
export const nullishOr = <T extends AnyType>(
|
|
3458
3909
|
type: T,
|
|
@@ -3473,7 +3924,7 @@ export const nullishOr = <T extends AnyType>(
|
|
|
3473
3924
|
* const error = NameAndAge.from(["Alice", -10]); // err
|
|
3474
3925
|
* ```
|
|
3475
3926
|
*
|
|
3476
|
-
* @
|
|
3927
|
+
* @group Base factories
|
|
3477
3928
|
*/
|
|
3478
3929
|
export const tuple = <Elements extends [AnyType, ...ReadonlyArray<AnyType>]>(
|
|
3479
3930
|
...elements: Elements
|
|
@@ -3503,7 +3954,7 @@ export const tuple = <Elements extends [AnyType, ...ReadonlyArray<AnyType>]>(
|
|
|
3503
3954
|
reason: {
|
|
3504
3955
|
kind: "Element",
|
|
3505
3956
|
index: i,
|
|
3506
|
-
error: elementResult.error as
|
|
3957
|
+
error: elementResult.error as never,
|
|
3507
3958
|
},
|
|
3508
3959
|
});
|
|
3509
3960
|
}
|
|
@@ -3534,7 +3985,7 @@ export const tuple = <Elements extends [AnyType, ...ReadonlyArray<AnyType>]>(
|
|
|
3534
3985
|
reason: {
|
|
3535
3986
|
kind: "Element",
|
|
3536
3987
|
index: i,
|
|
3537
|
-
error: elementResult.error as
|
|
3988
|
+
error: elementResult.error as never,
|
|
3538
3989
|
},
|
|
3539
3990
|
});
|
|
3540
3991
|
}
|
|
@@ -3550,7 +4001,7 @@ export const tuple = <Elements extends [AnyType, ...ReadonlyArray<AnyType>]>(
|
|
|
3550
4001
|
fromParent,
|
|
3551
4002
|
}),
|
|
3552
4003
|
elements,
|
|
3553
|
-
} as
|
|
4004
|
+
} as never;
|
|
3554
4005
|
};
|
|
3555
4006
|
|
|
3556
4007
|
/**
|
|
@@ -3609,9 +4060,9 @@ export const formatTupleError = <Error extends TypeError>(
|
|
|
3609
4060
|
*
|
|
3610
4061
|
* https://www.sqlite.org/c3ref/int64.html
|
|
3611
4062
|
*
|
|
3612
|
-
* @
|
|
4063
|
+
* @group Number
|
|
3613
4064
|
*/
|
|
3614
|
-
export const Int64 = brand("Int64", BigInt, (value) =>
|
|
4065
|
+
export const Int64 = /*#__PURE__*/ brand("Int64", BigInt, (value) =>
|
|
3615
4066
|
value >= -9223372036854775808n && value <= 9223372036854775807n
|
|
3616
4067
|
? ok(value)
|
|
3617
4068
|
: err<Int64Error>({ type: "Int64", value }),
|
|
@@ -3619,25 +4070,29 @@ export const Int64 = brand("Int64", BigInt, (value) =>
|
|
|
3619
4070
|
export type Int64 = typeof Int64.Type;
|
|
3620
4071
|
export interface Int64Error extends TypeError<"Int64"> {}
|
|
3621
4072
|
|
|
3622
|
-
export const formatInt64Error =
|
|
3623
|
-
(
|
|
3624
|
-
|
|
3625
|
-
)
|
|
4073
|
+
export const formatInt64Error =
|
|
4074
|
+
/*#__PURE__*/ createTypeErrorFormatter<Int64Error>(
|
|
4075
|
+
(error) =>
|
|
4076
|
+
`The value ${error.value} is not a valid 64-bit signed integer (Int64).`,
|
|
4077
|
+
);
|
|
3626
4078
|
|
|
3627
4079
|
/**
|
|
3628
4080
|
* Stringified {@link Int64}.
|
|
3629
4081
|
*
|
|
3630
|
-
* @
|
|
4082
|
+
* @group String
|
|
3631
4083
|
*/
|
|
3632
|
-
export const Int64String = brand(
|
|
3633
|
-
|
|
3634
|
-
|
|
3635
|
-
|
|
3636
|
-
|
|
3637
|
-
|
|
3638
|
-
|
|
3639
|
-
|
|
3640
|
-
|
|
4084
|
+
export const Int64String = /*#__PURE__*/ brand(
|
|
4085
|
+
"Int64",
|
|
4086
|
+
NonEmptyTrimmedString,
|
|
4087
|
+
(value) =>
|
|
4088
|
+
trySync(
|
|
4089
|
+
() => {
|
|
4090
|
+
const maybeInt = globalThis.BigInt(value);
|
|
4091
|
+
Int64.orThrow(maybeInt);
|
|
4092
|
+
return value;
|
|
4093
|
+
},
|
|
4094
|
+
(): Int64StringError => ({ type: "Int64String", value }),
|
|
4095
|
+
),
|
|
3641
4096
|
);
|
|
3642
4097
|
|
|
3643
4098
|
export type Int64String = typeof Int64String.Type;
|
|
@@ -3645,10 +4100,20 @@ export type Int64String = typeof Int64String.Type;
|
|
|
3645
4100
|
export interface Int64StringError extends TypeError<"Int64String"> {}
|
|
3646
4101
|
|
|
3647
4102
|
export const formatInt64StringError =
|
|
3648
|
-
createTypeErrorFormatter<Int64StringError>(
|
|
4103
|
+
/*#__PURE__*/ createTypeErrorFormatter<Int64StringError>(
|
|
3649
4104
|
(error) => `The value ${error.value} is not a valid Int64 string.`,
|
|
3650
4105
|
);
|
|
3651
4106
|
|
|
4107
|
+
/**
|
|
4108
|
+
* Validated JSON-compatible value.
|
|
4109
|
+
*
|
|
4110
|
+
* This is the output side of JSON data in Evolu. It uses {@link FiniteNumber}
|
|
4111
|
+
* instead of `number` because JSON numbers are expected to be finite once the
|
|
4112
|
+
* value has been parsed or validated.
|
|
4113
|
+
*
|
|
4114
|
+
* Compare with {@link JsonValueInput}, which represents caller-provided input
|
|
4115
|
+
* before validation.
|
|
4116
|
+
*/
|
|
3652
4117
|
export type JsonValue =
|
|
3653
4118
|
| string
|
|
3654
4119
|
| FiniteNumber
|
|
@@ -3657,6 +4122,19 @@ export type JsonValue =
|
|
|
3657
4122
|
| JsonArray
|
|
3658
4123
|
| JsonObject;
|
|
3659
4124
|
|
|
4125
|
+
/**
|
|
4126
|
+
* JSON-compatible input value before validation.
|
|
4127
|
+
*
|
|
4128
|
+
* This is broader than {@link JsonValue} because inputs arrive as ordinary
|
|
4129
|
+
* JavaScript values, so numbers are typed as `number` before validation can
|
|
4130
|
+
* narrow them to {@link FiniteNumber}.
|
|
4131
|
+
*
|
|
4132
|
+
* That means `JsonValueInput` can temporarily contain numbers that are lossy in
|
|
4133
|
+
* JSON serialization. For example, `JSON.stringify(NaN)` and
|
|
4134
|
+
* `JSON.stringify(Infinity)` produce `null`, and `JSON.stringify(-0)` produces
|
|
4135
|
+
* `0`. Use {@link JsonValue} when the value must already satisfy JSON numeric
|
|
4136
|
+
* constraints.
|
|
4137
|
+
*/
|
|
3660
4138
|
export type JsonValueInput =
|
|
3661
4139
|
| string
|
|
3662
4140
|
| number
|
|
@@ -3690,9 +4168,9 @@ export type JsonArrayInput = ReadonlyArray<JsonValueInput>;
|
|
|
3690
4168
|
* JSON-compatible value: string, {@link FiniteNumber}, boolean, null,
|
|
3691
4169
|
* {@link JsonArray}, or {@link JsonObject}.
|
|
3692
4170
|
*
|
|
3693
|
-
* @
|
|
4171
|
+
* @group Base Types
|
|
3694
4172
|
*/
|
|
3695
|
-
export const JsonValue = recursive(
|
|
4173
|
+
export const JsonValue = /*#__PURE__*/ recursive(
|
|
3696
4174
|
(): UnionType<
|
|
3697
4175
|
[
|
|
3698
4176
|
typeof String,
|
|
@@ -3732,16 +4210,16 @@ export const JsonValue = recursive(
|
|
|
3732
4210
|
/**
|
|
3733
4211
|
* JSON-compatible array of {@link JsonValue} elements.
|
|
3734
4212
|
*
|
|
3735
|
-
* @
|
|
4213
|
+
* @group Array
|
|
3736
4214
|
*/
|
|
3737
|
-
export const JsonArray = array(JsonValue);
|
|
4215
|
+
export const JsonArray = /*#__PURE__*/ array(JsonValue);
|
|
3738
4216
|
|
|
3739
4217
|
/**
|
|
3740
4218
|
* JSON-compatible object with string keys and {@link JsonValue} values.
|
|
3741
4219
|
*
|
|
3742
|
-
* @
|
|
4220
|
+
* @group Object
|
|
3743
4221
|
*/
|
|
3744
|
-
export const JsonObject = record(String, JsonValue);
|
|
4222
|
+
export const JsonObject = /*#__PURE__*/ record(String, JsonValue);
|
|
3745
4223
|
|
|
3746
4224
|
export const parseJson = (value: string): Result<JsonValue, JsonError> =>
|
|
3747
4225
|
trySync(
|
|
@@ -3763,9 +4241,9 @@ export const parseJson = (value: string): Result<JsonValue, JsonError> =>
|
|
|
3763
4241
|
* const error = Json.from("invalid json"); // err
|
|
3764
4242
|
* ```
|
|
3765
4243
|
*
|
|
3766
|
-
* @
|
|
4244
|
+
* @group String
|
|
3767
4245
|
*/
|
|
3768
|
-
export const Json = brand("Json", String, (value) => {
|
|
4246
|
+
export const Json = /*#__PURE__*/ brand("Json", String, (value) => {
|
|
3769
4247
|
const result = parseJson(value);
|
|
3770
4248
|
if (!result.ok) return result;
|
|
3771
4249
|
return ok(value);
|
|
@@ -3777,9 +4255,10 @@ export interface JsonError extends TypeError<"Json"> {
|
|
|
3777
4255
|
readonly message: string;
|
|
3778
4256
|
}
|
|
3779
4257
|
|
|
3780
|
-
export const formatJsonError =
|
|
3781
|
-
(
|
|
3782
|
-
)
|
|
4258
|
+
export const formatJsonError =
|
|
4259
|
+
/*#__PURE__*/ createTypeErrorFormatter<JsonError>(
|
|
4260
|
+
(error) => `Invalid JSON: ${error.value}. Error: ${error.message}`,
|
|
4261
|
+
);
|
|
3783
4262
|
|
|
3784
4263
|
export const jsonValueToJson = (value: JsonValue): Json =>
|
|
3785
4264
|
JSON.stringify(value) as Json;
|
|
@@ -3807,7 +4286,7 @@ export const jsonToJsonValue = (value: Json): JsonValue =>
|
|
|
3807
4286
|
* name: NonEmptyString100,
|
|
3808
4287
|
* age: FiniteNumber,
|
|
3809
4288
|
* });
|
|
3810
|
-
*
|
|
4289
|
+
* interface Person extends InferType<typeof Person> {}
|
|
3811
4290
|
*
|
|
3812
4291
|
* const [PersonJson, personToPersonJson, personJsonToPerson] = json(
|
|
3813
4292
|
* Person,
|
|
@@ -3848,11 +4327,7 @@ export const json = <T extends AnyType, Name extends TypeName>(
|
|
|
3848
4327
|
return ok(value);
|
|
3849
4328
|
}) as BrandType<typeof String, Name, JsonError | InferErrors<T>, StringError>;
|
|
3850
4329
|
|
|
3851
|
-
return [
|
|
3852
|
-
BrandedJsonType,
|
|
3853
|
-
jsonValueToJson as IntentionalNever,
|
|
3854
|
-
jsonToJsonValue as IntentionalNever,
|
|
3855
|
-
];
|
|
4330
|
+
return [BrandedJsonType, jsonValueToJson as never, jsonToJsonValue as never];
|
|
3856
4331
|
};
|
|
3857
4332
|
|
|
3858
4333
|
/**
|
|
@@ -3924,7 +4399,7 @@ export const isOptionalType = (x: unknown): x is OptionalType<any> =>
|
|
|
3924
4399
|
* PartialUser.from({ age: -5 });
|
|
3925
4400
|
* ```
|
|
3926
4401
|
*
|
|
3927
|
-
* @
|
|
4402
|
+
* @group Object
|
|
3928
4403
|
*/
|
|
3929
4404
|
export const partial = <Props extends Record<string, AnyType>>(
|
|
3930
4405
|
props: Props,
|
|
@@ -3944,7 +4419,7 @@ export const partial = <Props extends Record<string, AnyType>>(
|
|
|
3944
4419
|
* entirely, or set it to `null`, or set it to the non-null member of the
|
|
3945
4420
|
* union.
|
|
3946
4421
|
*
|
|
3947
|
-
* @
|
|
4422
|
+
* @group Object
|
|
3948
4423
|
*/
|
|
3949
4424
|
export const nullableToOptional = <Props extends Record<string, AnyType>>(
|
|
3950
4425
|
props: Props,
|
|
@@ -3990,12 +4465,12 @@ export type NullTypeInMembers<Members extends [AnyType, ...Array<AnyType>]> =
|
|
|
3990
4465
|
/**
|
|
3991
4466
|
* Create a new `object` {@link Type} by omitting some keys.
|
|
3992
4467
|
*
|
|
3993
|
-
* @
|
|
4468
|
+
* @group Object
|
|
3994
4469
|
*/
|
|
3995
|
-
export
|
|
4470
|
+
export const omit = <T extends ObjectType<any>, Keys extends keyof T["props"]>(
|
|
3996
4471
|
objectType: T,
|
|
3997
4472
|
...keys: ReadonlyArray<Keys>
|
|
3998
|
-
): ObjectType<Omit<T["props"], Keys>> {
|
|
4473
|
+
): ObjectType<Omit<T["props"], Keys>> => {
|
|
3999
4474
|
const newProps = {} as Omit<T["props"], Keys>;
|
|
4000
4475
|
|
|
4001
4476
|
for (const key in objectType.props) {
|
|
@@ -4005,40 +4480,7 @@ export function omit<T extends ObjectType<any>, Keys extends keyof T["props"]>(
|
|
|
4005
4480
|
}
|
|
4006
4481
|
}
|
|
4007
4482
|
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
|
-
>;
|
|
4483
|
+
};
|
|
4042
4484
|
|
|
4043
4485
|
/**
|
|
4044
4486
|
* Union of all `TypeError`s defined in the `Type.ts` file, including base type
|
|
@@ -4051,7 +4493,7 @@ export type ValidMutationSize<Props extends Record<string, AnyType>> =
|
|
|
4051
4493
|
* Used by {@link createFormatTypeError} to generate human-readable error
|
|
4052
4494
|
* messages.
|
|
4053
4495
|
*
|
|
4054
|
-
* @
|
|
4496
|
+
* @group Utilities
|
|
4055
4497
|
*/
|
|
4056
4498
|
export type TypeErrors<ExtraErrors extends TypeError = never> =
|
|
4057
4499
|
| StringError
|
|
@@ -4092,7 +4534,6 @@ export type TypeErrors<ExtraErrors extends TypeError = never> =
|
|
|
4092
4534
|
| Int64Error
|
|
4093
4535
|
| Int64StringError
|
|
4094
4536
|
| JsonError
|
|
4095
|
-
| ValidMutationSizeError
|
|
4096
4537
|
| ExtraErrors
|
|
4097
4538
|
// Composite errors
|
|
4098
4539
|
| ArrayError<TypeErrors<ExtraErrors>>
|
|
@@ -4185,7 +4626,7 @@ export type TypeErrors<ExtraErrors extends TypeError = never> =
|
|
|
4185
4626
|
* };
|
|
4186
4627
|
* ```
|
|
4187
4628
|
*
|
|
4188
|
-
* @
|
|
4629
|
+
* @group Utilities
|
|
4189
4630
|
*/
|
|
4190
4631
|
export const createFormatTypeError = <ExtraErrors extends TypeError = never>(
|
|
4191
4632
|
extraFormatter?: TypeErrorFormatter<ExtraErrors>,
|
|
@@ -4274,8 +4715,6 @@ export const createFormatTypeError = <ExtraErrors extends TypeError = never>(
|
|
|
4274
4715
|
return formatInt64StringError(error);
|
|
4275
4716
|
case "Json":
|
|
4276
4717
|
return formatJsonError(error);
|
|
4277
|
-
case "ValidMutationSize":
|
|
4278
|
-
return formatValidMutationSizeError(error);
|
|
4279
4718
|
// Composite Types
|
|
4280
4719
|
case "SimplePassword":
|
|
4281
4720
|
return formatSimplePasswordError(formatTypeError)(error);
|
|
@@ -4308,25 +4747,25 @@ export const createFormatTypeError = <ExtraErrors extends TypeError = never>(
|
|
|
4308
4747
|
* Converts an Evolu {@link TypeError} to Standard Schema V1 issues format.
|
|
4309
4748
|
*
|
|
4310
4749
|
* This function recursively converts Evolu's typed errors into the Standard
|
|
4311
|
-
* Schema issue format with proper path tracking for nested structures.
|
|
4750
|
+
* Schema issue format with proper path tracking for nested structures. The
|
|
4751
|
+
* `message` field contains the JSON-serialized typed error, which users can
|
|
4752
|
+
* deserialize and format using appropriate {@link TypeErrorFormatter}s.
|
|
4312
4753
|
*
|
|
4313
|
-
* @
|
|
4754
|
+
* @group Utilities
|
|
4314
4755
|
*/
|
|
4315
4756
|
export const typeErrorToStandardSchemaIssues = <
|
|
4316
4757
|
ExtraErrors extends TypeError = never,
|
|
4317
4758
|
>(
|
|
4318
4759
|
error: TypeErrors<ExtraErrors>,
|
|
4319
|
-
formatTypeError: TypeErrorFormatter<TypeErrors<ExtraErrors>>,
|
|
4320
4760
|
path: ReadonlyArray<PropertyKey> = [],
|
|
4321
4761
|
): ReadonlyArray<StandardSchemaV1.Issue> => {
|
|
4322
4762
|
if (error.type === "Array") {
|
|
4323
4763
|
const arrayError = error as ArrayError;
|
|
4324
4764
|
if (arrayError.reason.kind === "NotArray") {
|
|
4325
|
-
return [{ message:
|
|
4765
|
+
return [{ message: JSON.stringify(error), path }];
|
|
4326
4766
|
}
|
|
4327
4767
|
return typeErrorToStandardSchemaIssues(
|
|
4328
4768
|
arrayError.reason.error as TypeErrors<ExtraErrors>,
|
|
4329
|
-
formatTypeError,
|
|
4330
4769
|
[...path, arrayError.reason.index],
|
|
4331
4770
|
);
|
|
4332
4771
|
}
|
|
@@ -4334,11 +4773,10 @@ export const typeErrorToStandardSchemaIssues = <
|
|
|
4334
4773
|
if (error.type === "Set") {
|
|
4335
4774
|
const setError = error as SetError;
|
|
4336
4775
|
if (setError.reason.kind === "NotSet") {
|
|
4337
|
-
return [{ message:
|
|
4776
|
+
return [{ message: JSON.stringify(error), path }];
|
|
4338
4777
|
}
|
|
4339
4778
|
return typeErrorToStandardSchemaIssues(
|
|
4340
4779
|
setError.reason.error as TypeErrors<ExtraErrors>,
|
|
4341
|
-
formatTypeError,
|
|
4342
4780
|
[...path, setError.reason.index],
|
|
4343
4781
|
);
|
|
4344
4782
|
}
|
|
@@ -4349,14 +4787,13 @@ export const typeErrorToStandardSchemaIssues = <
|
|
|
4349
4787
|
objectError.reason.kind === "NotObject" ||
|
|
4350
4788
|
objectError.reason.kind === "ExtraKeys"
|
|
4351
4789
|
) {
|
|
4352
|
-
return [{ message:
|
|
4790
|
+
return [{ message: JSON.stringify(error), path }];
|
|
4353
4791
|
}
|
|
4354
4792
|
const issues: Array<StandardSchemaV1.Issue> = [];
|
|
4355
4793
|
for (const [key, propError] of Object.entries(objectError.reason.errors)) {
|
|
4356
4794
|
issues.push(
|
|
4357
4795
|
...typeErrorToStandardSchemaIssues(
|
|
4358
4796
|
propError as TypeErrors<ExtraErrors>,
|
|
4359
|
-
formatTypeError,
|
|
4360
4797
|
[...path, key],
|
|
4361
4798
|
),
|
|
4362
4799
|
);
|
|
@@ -4367,7 +4804,7 @@ export const typeErrorToStandardSchemaIssues = <
|
|
|
4367
4804
|
if (error.type === "ObjectWithRecord") {
|
|
4368
4805
|
const objectWithRecordError = error as ObjectWithRecordError;
|
|
4369
4806
|
if (objectWithRecordError.reason.kind === "NotObject") {
|
|
4370
|
-
return [{ message:
|
|
4807
|
+
return [{ message: JSON.stringify(error), path }];
|
|
4371
4808
|
}
|
|
4372
4809
|
if (
|
|
4373
4810
|
objectWithRecordError.reason.kind === "IndexKey" ||
|
|
@@ -4375,7 +4812,6 @@ export const typeErrorToStandardSchemaIssues = <
|
|
|
4375
4812
|
) {
|
|
4376
4813
|
return typeErrorToStandardSchemaIssues(
|
|
4377
4814
|
objectWithRecordError.reason.error as TypeErrors<ExtraErrors>,
|
|
4378
|
-
formatTypeError,
|
|
4379
4815
|
[...path, objectWithRecordError.reason.key as PropertyKey],
|
|
4380
4816
|
);
|
|
4381
4817
|
}
|
|
@@ -4386,7 +4822,6 @@ export const typeErrorToStandardSchemaIssues = <
|
|
|
4386
4822
|
issues.push(
|
|
4387
4823
|
...typeErrorToStandardSchemaIssues(
|
|
4388
4824
|
propError as TypeErrors<ExtraErrors>,
|
|
4389
|
-
formatTypeError,
|
|
4390
4825
|
[...path, key],
|
|
4391
4826
|
),
|
|
4392
4827
|
);
|
|
@@ -4397,11 +4832,10 @@ export const typeErrorToStandardSchemaIssues = <
|
|
|
4397
4832
|
if (error.type === "Record") {
|
|
4398
4833
|
const recordError = error as RecordError;
|
|
4399
4834
|
if (recordError.reason.kind === "NotRecord") {
|
|
4400
|
-
return [{ message:
|
|
4835
|
+
return [{ message: JSON.stringify(error), path }];
|
|
4401
4836
|
}
|
|
4402
4837
|
return typeErrorToStandardSchemaIssues(
|
|
4403
4838
|
recordError.reason.error as TypeErrors<ExtraErrors>,
|
|
4404
|
-
formatTypeError,
|
|
4405
4839
|
[...path, recordError.reason.key as PropertyKey],
|
|
4406
4840
|
);
|
|
4407
4841
|
}
|
|
@@ -4409,11 +4843,10 @@ export const typeErrorToStandardSchemaIssues = <
|
|
|
4409
4843
|
if (error.type === "Tuple") {
|
|
4410
4844
|
const tupleError = error as TupleError;
|
|
4411
4845
|
if (tupleError.reason.kind === "InvalidLength") {
|
|
4412
|
-
return [{ message:
|
|
4846
|
+
return [{ message: JSON.stringify(error), path }];
|
|
4413
4847
|
}
|
|
4414
4848
|
return typeErrorToStandardSchemaIssues(
|
|
4415
4849
|
tupleError.reason.error as TypeErrors<ExtraErrors>,
|
|
4416
|
-
formatTypeError,
|
|
4417
4850
|
[...path, tupleError.reason.index],
|
|
4418
4851
|
);
|
|
4419
4852
|
}
|
|
@@ -4421,11 +4854,7 @@ export const typeErrorToStandardSchemaIssues = <
|
|
|
4421
4854
|
if (error.type === "Union") {
|
|
4422
4855
|
const unionError = error as UnionError;
|
|
4423
4856
|
return unionError.errors.flatMap((err) =>
|
|
4424
|
-
typeErrorToStandardSchemaIssues(
|
|
4425
|
-
err as TypeErrors<ExtraErrors>,
|
|
4426
|
-
formatTypeError,
|
|
4427
|
-
path,
|
|
4428
|
-
),
|
|
4857
|
+
typeErrorToStandardSchemaIssues(err as TypeErrors<ExtraErrors>, path),
|
|
4429
4858
|
);
|
|
4430
4859
|
}
|
|
4431
4860
|
|
|
@@ -4434,14 +4863,13 @@ export const typeErrorToStandardSchemaIssues = <
|
|
|
4434
4863
|
if ("parentError" in brandError) {
|
|
4435
4864
|
return typeErrorToStandardSchemaIssues(
|
|
4436
4865
|
brandError.parentError as TypeErrors<ExtraErrors>,
|
|
4437
|
-
formatTypeError,
|
|
4438
4866
|
path,
|
|
4439
4867
|
);
|
|
4440
4868
|
}
|
|
4441
|
-
return [{ message:
|
|
4869
|
+
return [{ message: JSON.stringify(error), path }];
|
|
4442
4870
|
}
|
|
4443
4871
|
|
|
4444
|
-
return [{ message:
|
|
4872
|
+
return [{ message: JSON.stringify(error), path }];
|
|
4445
4873
|
};
|
|
4446
4874
|
|
|
4447
4875
|
/** The Standard Schema interface. */
|
|
@@ -4515,10 +4943,3 @@ export declare namespace StandardSchemaV1 {
|
|
|
4515
4943
|
Schema["~standard"]["types"]
|
|
4516
4944
|
>["output"];
|
|
4517
4945
|
}
|
|
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;
|