@evolu/common 7.4.0 → 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 +1064 -630
- 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 -823
- 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 +209 -282
- 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 -529
- package/src/Instances.ts +0 -90
- 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 -960
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,
|
|
@@ -791,26 +872,28 @@ export interface InstanceOfError extends TypeError<"InstanceOf"> {
|
|
|
791
872
|
readonly ctor: string;
|
|
792
873
|
}
|
|
793
874
|
|
|
794
|
-
export interface InstanceOfType<
|
|
795
|
-
extends
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
875
|
+
export interface InstanceOfType<
|
|
876
|
+
T extends abstract new (...args: any) => any,
|
|
877
|
+
> extends Type<
|
|
878
|
+
"InstanceOf",
|
|
879
|
+
InstanceType<T>,
|
|
880
|
+
InstanceType<T>,
|
|
881
|
+
InstanceOfError
|
|
882
|
+
> {
|
|
801
883
|
ctor: T;
|
|
802
884
|
}
|
|
803
885
|
|
|
804
|
-
export const formatInstanceOfError =
|
|
805
|
-
(
|
|
806
|
-
)
|
|
886
|
+
export const formatInstanceOfError =
|
|
887
|
+
/*#__PURE__*/ createTypeErrorFormatter<InstanceOfError>(
|
|
888
|
+
(error) => `The value ${error.value} is not an instance of ${error.ctor}.`,
|
|
889
|
+
);
|
|
807
890
|
|
|
808
891
|
/**
|
|
809
892
|
* JavaScript Date.
|
|
810
893
|
*
|
|
811
|
-
* @
|
|
894
|
+
* @group Base Types
|
|
812
895
|
*/
|
|
813
|
-
export const Date = instanceOf(globalThis.Date);
|
|
896
|
+
export const Date = /*#__PURE__*/ instanceOf(globalThis.Date);
|
|
814
897
|
|
|
815
898
|
/**
|
|
816
899
|
* Validates that an unknown value is an Evolu {@link Type} (i.e., satisfies
|
|
@@ -824,7 +907,7 @@ export const Date = instanceOf(globalThis.Date);
|
|
|
824
907
|
* ```
|
|
825
908
|
*/
|
|
826
909
|
// TODO: Rename to TypeInstance or something like that.
|
|
827
|
-
export const EvoluType = base("EvoluType", (value) =>
|
|
910
|
+
export const EvoluType = /*#__PURE__*/ base("EvoluType", (value) =>
|
|
828
911
|
isType(value)
|
|
829
912
|
? ok(value)
|
|
830
913
|
: err<EvoluTypeError>({
|
|
@@ -835,19 +918,24 @@ export const EvoluType = base("EvoluType", (value) =>
|
|
|
835
918
|
|
|
836
919
|
export interface EvoluTypeError extends TypeError<"EvoluType"> {}
|
|
837
920
|
|
|
838
|
-
export const formatIsTypeError =
|
|
839
|
-
(
|
|
840
|
-
)
|
|
921
|
+
export const formatIsTypeError =
|
|
922
|
+
/*#__PURE__*/ createTypeErrorFormatter<EvoluTypeError>(
|
|
923
|
+
(error) => `Value ${error.value} is not a valid Evolu Type.`,
|
|
924
|
+
);
|
|
841
925
|
|
|
842
926
|
/**
|
|
843
927
|
* Branded {@link Type}.
|
|
844
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
|
+
*
|
|
845
933
|
* The `brand` Type Factory takes the name of a new {@link Brand}, a parent Type
|
|
846
934
|
* to be branded, and the optional `refine` function for additional constraint.
|
|
847
935
|
*
|
|
848
936
|
* The `refine` function can be omitted if we only want to add a brand.
|
|
849
937
|
*
|
|
850
|
-
* ###
|
|
938
|
+
* ### Example
|
|
851
939
|
*
|
|
852
940
|
* A simple `CurrencyCode` Type:
|
|
853
941
|
*
|
|
@@ -966,7 +1054,7 @@ export const formatIsTypeError = createTypeErrorFormatter<EvoluTypeError>(
|
|
|
966
1054
|
* );
|
|
967
1055
|
* ```
|
|
968
1056
|
*
|
|
969
|
-
* @
|
|
1057
|
+
* @group Base factories
|
|
970
1058
|
*/
|
|
971
1059
|
export function brand<
|
|
972
1060
|
Name extends TypeName,
|
|
@@ -979,6 +1067,7 @@ export function brand<
|
|
|
979
1067
|
refine: (value: Parent) => Result<Parent, RefineError>,
|
|
980
1068
|
): BrandType<ParentType, Name, RefineError, InferErrors<ParentType>>;
|
|
981
1069
|
|
|
1070
|
+
/** Without refine function. */
|
|
982
1071
|
export function brand<Name extends TypeName, ParentType extends AnyType>(
|
|
983
1072
|
name: Name,
|
|
984
1073
|
parent: ParentType,
|
|
@@ -1002,15 +1091,15 @@ export function brand<
|
|
|
1002
1091
|
? (value: unknown) => {
|
|
1003
1092
|
const parentResult = parent.fromUnknown(value);
|
|
1004
1093
|
if (!parentResult.ok) return parentResult;
|
|
1005
|
-
return refine(parentResult.value as
|
|
1094
|
+
return refine(parentResult.value as never);
|
|
1006
1095
|
}
|
|
1007
1096
|
: (value: unknown) => {
|
|
1008
1097
|
const parentResult = parent.fromUnknown(value);
|
|
1009
1098
|
if (!parentResult.ok)
|
|
1010
|
-
return err<BrandWithoutRefineError<Name,
|
|
1099
|
+
return err<BrandWithoutRefineError<Name, never>>({
|
|
1011
1100
|
type: name,
|
|
1012
1101
|
value,
|
|
1013
|
-
parentError: parentResult.error as
|
|
1102
|
+
parentError: parentResult.error as never,
|
|
1014
1103
|
});
|
|
1015
1104
|
return ok(parentResult.value);
|
|
1016
1105
|
};
|
|
@@ -1018,7 +1107,7 @@ export function brand<
|
|
|
1018
1107
|
return {
|
|
1019
1108
|
...createType("Brand", {
|
|
1020
1109
|
fromUnknown,
|
|
1021
|
-
fromParent: (refine ?? ok) as
|
|
1110
|
+
fromParent: (refine ?? ok) as never,
|
|
1022
1111
|
}),
|
|
1023
1112
|
brand: name,
|
|
1024
1113
|
parentType: parent,
|
|
@@ -1031,13 +1120,13 @@ export interface BrandType<
|
|
|
1031
1120
|
Error extends TypeError = never,
|
|
1032
1121
|
ParentError extends TypeError = never,
|
|
1033
1122
|
> extends Type<
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1123
|
+
"Brand",
|
|
1124
|
+
InferType<ParentType> & Brand<Name>,
|
|
1125
|
+
InferInput<ParentType>,
|
|
1126
|
+
Error,
|
|
1127
|
+
InferType<ParentType>,
|
|
1128
|
+
ParentError
|
|
1129
|
+
> {
|
|
1041
1130
|
readonly brand: Name;
|
|
1042
1131
|
readonly parentType: ParentType;
|
|
1043
1132
|
}
|
|
@@ -1052,12 +1141,15 @@ export interface BrandWithoutRefineError<
|
|
|
1052
1141
|
/**
|
|
1053
1142
|
* A three-letter ISO 4217 currency code (e.g., USD, EUR).
|
|
1054
1143
|
*
|
|
1055
|
-
* @
|
|
1144
|
+
* @group String
|
|
1056
1145
|
*/
|
|
1057
|
-
export const CurrencyCode = brand(
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
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 }),
|
|
1061
1153
|
);
|
|
1062
1154
|
|
|
1063
1155
|
export type CurrencyCode = typeof CurrencyCode.Type;
|
|
@@ -1065,7 +1157,7 @@ export type CurrencyCode = typeof CurrencyCode.Type;
|
|
|
1065
1157
|
export interface CurrencyCodeError extends TypeError<"CurrencyCode"> {}
|
|
1066
1158
|
|
|
1067
1159
|
export const formatCurrencyCodeError =
|
|
1068
|
-
createTypeErrorFormatter<CurrencyCodeError>(
|
|
1160
|
+
/*#__PURE__*/ createTypeErrorFormatter<CurrencyCodeError>(
|
|
1069
1161
|
(error) => `Invalid currency code: ${error.value}.`,
|
|
1070
1162
|
);
|
|
1071
1163
|
|
|
@@ -1088,9 +1180,9 @@ export const formatCurrencyCodeError =
|
|
|
1088
1180
|
* const error = DateIso.from("10000-01-01T00:00:00.000Z"); // err
|
|
1089
1181
|
* ```
|
|
1090
1182
|
*
|
|
1091
|
-
* @
|
|
1183
|
+
* @group String
|
|
1092
1184
|
*/
|
|
1093
|
-
export const DateIso = brand("DateIso", String, (value) => {
|
|
1185
|
+
export const DateIso = /*#__PURE__*/ brand("DateIso", String, (value) => {
|
|
1094
1186
|
if (value.length !== 24) {
|
|
1095
1187
|
return err<DateIsoError>({ type: "DateIso", value });
|
|
1096
1188
|
}
|
|
@@ -1110,9 +1202,10 @@ export type DateIso = typeof DateIso.Type;
|
|
|
1110
1202
|
|
|
1111
1203
|
export interface DateIsoError extends TypeError<"DateIso"> {}
|
|
1112
1204
|
|
|
1113
|
-
export const formatDateIsoError =
|
|
1114
|
-
(
|
|
1115
|
-
)
|
|
1205
|
+
export const formatDateIsoError =
|
|
1206
|
+
/*#__PURE__*/ createTypeErrorFormatter<DateIsoError>(
|
|
1207
|
+
(error) => `The value ${error.value} is not a valid ISO 8601 date string.`,
|
|
1208
|
+
);
|
|
1116
1209
|
|
|
1117
1210
|
export const dateToDateIso = (value: Date): Result<DateIso, DateIsoError> =>
|
|
1118
1211
|
DateIso.fromParent(value.toISOString());
|
|
@@ -1136,7 +1229,18 @@ export const dateIsoToDate = (value: DateIso): Date =>
|
|
|
1136
1229
|
* );
|
|
1137
1230
|
* ```
|
|
1138
1231
|
*
|
|
1139
|
-
*
|
|
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
|
|
1140
1244
|
*/
|
|
1141
1245
|
export type BrandFactory<
|
|
1142
1246
|
Name extends TypeName,
|
|
@@ -1172,7 +1276,7 @@ export type BrandFactory<
|
|
|
1172
1276
|
* type TrimmedNonEmptyString = typeof TrimmedNonEmptyString.Type;
|
|
1173
1277
|
* ```
|
|
1174
1278
|
*
|
|
1175
|
-
* @
|
|
1279
|
+
* @group String
|
|
1176
1280
|
*/
|
|
1177
1281
|
export const trimmed: BrandFactory<"Trimmed", string, TrimmedError> = (
|
|
1178
1282
|
parent,
|
|
@@ -1185,9 +1289,10 @@ export const trimmed: BrandFactory<"Trimmed", string, TrimmedError> = (
|
|
|
1185
1289
|
|
|
1186
1290
|
export interface TrimmedError extends TypeError<"Trimmed"> {}
|
|
1187
1291
|
|
|
1188
|
-
export const formatTrimmedError =
|
|
1189
|
-
(
|
|
1190
|
-
)
|
|
1292
|
+
export const formatTrimmedError =
|
|
1293
|
+
/*#__PURE__*/ createTypeErrorFormatter<TrimmedError>(
|
|
1294
|
+
(error) => `The value ${error.value} must be trimmed.`,
|
|
1295
|
+
);
|
|
1191
1296
|
|
|
1192
1297
|
/**
|
|
1193
1298
|
* Trimmed string
|
|
@@ -1195,9 +1300,9 @@ export const formatTrimmedError = createTypeErrorFormatter<TrimmedError>(
|
|
|
1195
1300
|
* - Use `TrimmedString.is` to check if an unknown value is trimmed.
|
|
1196
1301
|
* - Use `TrimmedString.from` to check if a string is trimmed.
|
|
1197
1302
|
*
|
|
1198
|
-
* @
|
|
1303
|
+
* @group String
|
|
1199
1304
|
*/
|
|
1200
|
-
export const TrimmedString = trimmed(String);
|
|
1305
|
+
export const TrimmedString = /*#__PURE__*/ trimmed(String);
|
|
1201
1306
|
export type TrimmedString = typeof TrimmedString.Type;
|
|
1202
1307
|
|
|
1203
1308
|
export const trim = (value: string): TrimmedString =>
|
|
@@ -1206,6 +1311,8 @@ export const trim = (value: string): TrimmedString =>
|
|
|
1206
1311
|
/**
|
|
1207
1312
|
* Minimum length.
|
|
1208
1313
|
*
|
|
1314
|
+
* Use numeric literal, not expression. See {@link BrandFactory}.
|
|
1315
|
+
*
|
|
1209
1316
|
* ### Example
|
|
1210
1317
|
*
|
|
1211
1318
|
* ```ts
|
|
@@ -1213,8 +1320,8 @@ export const trim = (value: string): TrimmedString =>
|
|
|
1213
1320
|
* const NonEmptyString = minLength(1)(String);
|
|
1214
1321
|
* ```
|
|
1215
1322
|
*
|
|
1216
|
-
* @
|
|
1217
|
-
* @
|
|
1323
|
+
* @group String
|
|
1324
|
+
* @group Array
|
|
1218
1325
|
*/
|
|
1219
1326
|
export const minLength: <Min extends number>(
|
|
1220
1327
|
min: Min,
|
|
@@ -1224,19 +1331,23 @@ export const minLength: <Min extends number>(
|
|
|
1224
1331
|
value.length >= min ? ok(value) : err({ type: "MinLength", value, min }),
|
|
1225
1332
|
);
|
|
1226
1333
|
|
|
1227
|
-
export interface MinLengthError<
|
|
1228
|
-
extends
|
|
1334
|
+
export interface MinLengthError<
|
|
1335
|
+
Min extends number = number,
|
|
1336
|
+
> extends TypeError<"MinLength"> {
|
|
1229
1337
|
readonly min: Min;
|
|
1230
1338
|
}
|
|
1231
1339
|
|
|
1232
|
-
export const formatMinLengthError =
|
|
1233
|
-
(
|
|
1234
|
-
|
|
1235
|
-
|
|
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
|
+
);
|
|
1236
1345
|
|
|
1237
1346
|
/**
|
|
1238
1347
|
* Maximum length.
|
|
1239
1348
|
*
|
|
1349
|
+
* Use numeric literal, not expression. See {@link BrandFactory}.
|
|
1350
|
+
*
|
|
1240
1351
|
* ### Example
|
|
1241
1352
|
*
|
|
1242
1353
|
* ```ts
|
|
@@ -1244,8 +1355,8 @@ export const formatMinLengthError = createTypeErrorFormatter<MinLengthError>(
|
|
|
1244
1355
|
* const String100 = maxLength(100)(String);
|
|
1245
1356
|
* ```
|
|
1246
1357
|
*
|
|
1247
|
-
* @
|
|
1248
|
-
* @
|
|
1358
|
+
* @group String
|
|
1359
|
+
* @group Array
|
|
1249
1360
|
*/
|
|
1250
1361
|
export const maxLength: <Max extends number>(
|
|
1251
1362
|
max: Max,
|
|
@@ -1255,19 +1366,23 @@ export const maxLength: <Max extends number>(
|
|
|
1255
1366
|
value.length <= max ? ok(value) : err({ type: "MaxLength", value, max }),
|
|
1256
1367
|
);
|
|
1257
1368
|
|
|
1258
|
-
export interface MaxLengthError<
|
|
1259
|
-
extends
|
|
1369
|
+
export interface MaxLengthError<
|
|
1370
|
+
Max extends number = number,
|
|
1371
|
+
> extends TypeError<"MaxLength"> {
|
|
1260
1372
|
readonly max: Max;
|
|
1261
1373
|
}
|
|
1262
1374
|
|
|
1263
|
-
export const formatMaxLengthError =
|
|
1264
|
-
(
|
|
1265
|
-
|
|
1266
|
-
|
|
1375
|
+
export const formatMaxLengthError =
|
|
1376
|
+
/*#__PURE__*/ createTypeErrorFormatter<MaxLengthError>(
|
|
1377
|
+
(error) =>
|
|
1378
|
+
`The value ${error.value} exceeds the maximum length of ${error.max}.`,
|
|
1379
|
+
);
|
|
1267
1380
|
|
|
1268
1381
|
/**
|
|
1269
1382
|
* Exact length.
|
|
1270
1383
|
*
|
|
1384
|
+
* Use numeric literal, not expression. See {@link BrandFactory}.
|
|
1385
|
+
*
|
|
1271
1386
|
* ### Example
|
|
1272
1387
|
*
|
|
1273
1388
|
* ```ts
|
|
@@ -1275,8 +1390,8 @@ export const formatMaxLengthError = createTypeErrorFormatter<MaxLengthError>(
|
|
|
1275
1390
|
* const Length1String = length(1)(String);
|
|
1276
1391
|
* ```
|
|
1277
1392
|
*
|
|
1278
|
-
* @
|
|
1279
|
-
* @
|
|
1393
|
+
* @group String
|
|
1394
|
+
* @group Array
|
|
1280
1395
|
*/
|
|
1281
1396
|
export const length: <Exact extends number>(
|
|
1282
1397
|
exact: Exact,
|
|
@@ -1288,54 +1403,58 @@ export const length: <Exact extends number>(
|
|
|
1288
1403
|
: err({ type: "Length", value, exact }),
|
|
1289
1404
|
);
|
|
1290
1405
|
|
|
1291
|
-
export interface LengthError<
|
|
1292
|
-
extends
|
|
1406
|
+
export interface LengthError<
|
|
1407
|
+
Exact extends number = number,
|
|
1408
|
+
> extends TypeError<"Length"> {
|
|
1293
1409
|
readonly exact: Exact;
|
|
1294
1410
|
}
|
|
1295
1411
|
|
|
1296
|
-
export const formatLengthError =
|
|
1297
|
-
(
|
|
1298
|
-
|
|
1299
|
-
|
|
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
|
+
);
|
|
1300
1417
|
|
|
1301
|
-
/** @
|
|
1302
|
-
export const NonEmptyString = minLength(1)(String);
|
|
1418
|
+
/** @group String */
|
|
1419
|
+
export const NonEmptyString = /*#__PURE__*/ minLength(1)(String);
|
|
1303
1420
|
export type NonEmptyString = typeof NonEmptyString.Type;
|
|
1304
1421
|
|
|
1305
|
-
/** @
|
|
1306
|
-
export const String100 = maxLength(100)(String);
|
|
1422
|
+
/** @group String */
|
|
1423
|
+
export const String100 = /*#__PURE__*/ maxLength(100)(String);
|
|
1307
1424
|
export type String100 = typeof String100.Type;
|
|
1308
1425
|
|
|
1309
|
-
/** @
|
|
1310
|
-
export const String1000 = maxLength(1000)(String);
|
|
1426
|
+
/** @group String */
|
|
1427
|
+
export const String1000 = /*#__PURE__*/ maxLength(1000)(String);
|
|
1311
1428
|
export type String1000 = typeof String1000.Type;
|
|
1312
1429
|
|
|
1313
|
-
/** @
|
|
1314
|
-
export const NonEmptyString100 = minLength(1)(String100);
|
|
1430
|
+
/** @group String */
|
|
1431
|
+
export const NonEmptyString100 = /*#__PURE__*/ minLength(1)(String100);
|
|
1315
1432
|
export type NonEmptyString100 = typeof NonEmptyString100.Type;
|
|
1316
1433
|
|
|
1317
|
-
/** @
|
|
1318
|
-
export const NonEmptyString1000 = minLength(1)(String1000);
|
|
1434
|
+
/** @group String */
|
|
1435
|
+
export const NonEmptyString1000 = /*#__PURE__*/ minLength(1)(String1000);
|
|
1319
1436
|
export type NonEmptyString1000 = typeof NonEmptyString1000.Type;
|
|
1320
1437
|
|
|
1321
|
-
/** @
|
|
1322
|
-
export const NonEmptyTrimmedString = minLength(1)(TrimmedString);
|
|
1438
|
+
/** @group String */
|
|
1439
|
+
export const NonEmptyTrimmedString = /*#__PURE__*/ minLength(1)(TrimmedString);
|
|
1323
1440
|
export type NonEmptyTrimmedString = typeof NonEmptyTrimmedString.Type;
|
|
1324
1441
|
|
|
1325
|
-
/** @
|
|
1326
|
-
export const TrimmedString100 = maxLength(100)(TrimmedString);
|
|
1442
|
+
/** @group String */
|
|
1443
|
+
export const TrimmedString100 = /*#__PURE__*/ maxLength(100)(TrimmedString);
|
|
1327
1444
|
export type TrimmedString100 = typeof TrimmedString100.Type;
|
|
1328
1445
|
|
|
1329
|
-
/** @
|
|
1330
|
-
export const TrimmedString1000 = maxLength(1000)(TrimmedString);
|
|
1446
|
+
/** @group String */
|
|
1447
|
+
export const TrimmedString1000 = /*#__PURE__*/ maxLength(1000)(TrimmedString);
|
|
1331
1448
|
export type TrimmedString1000 = typeof TrimmedString1000.Type;
|
|
1332
1449
|
|
|
1333
|
-
/** @
|
|
1334
|
-
export const NonEmptyTrimmedString100 =
|
|
1450
|
+
/** @group String */
|
|
1451
|
+
export const NonEmptyTrimmedString100 =
|
|
1452
|
+
/*#__PURE__*/ minLength(1)(TrimmedString100);
|
|
1335
1453
|
export type NonEmptyTrimmedString100 = typeof NonEmptyTrimmedString100.Type;
|
|
1336
1454
|
|
|
1337
|
-
/** @
|
|
1338
|
-
export const NonEmptyTrimmedString1000 =
|
|
1455
|
+
/** @group String */
|
|
1456
|
+
export const NonEmptyTrimmedString1000 =
|
|
1457
|
+
/*#__PURE__*/ minLength(1)(TrimmedString1000);
|
|
1339
1458
|
export type NonEmptyTrimmedString1000 = typeof NonEmptyTrimmedString1000.Type;
|
|
1340
1459
|
|
|
1341
1460
|
/**
|
|
@@ -1345,20 +1464,24 @@ export type NonEmptyTrimmedString1000 = typeof NonEmptyTrimmedString1000.Type;
|
|
|
1345
1464
|
* safely on the user's device using cryptographically secure random number
|
|
1346
1465
|
* generation, ensuring it remains private and unique.
|
|
1347
1466
|
*
|
|
1348
|
-
* @
|
|
1467
|
+
* @group String
|
|
1349
1468
|
*/
|
|
1350
|
-
export const Mnemonic = brand(
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
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 }),
|
|
1354
1476
|
);
|
|
1355
1477
|
export type Mnemonic = typeof Mnemonic.Type;
|
|
1356
1478
|
|
|
1357
1479
|
export interface MnemonicError extends TypeError<"Mnemonic"> {}
|
|
1358
1480
|
|
|
1359
|
-
export const formatMnemonicError =
|
|
1360
|
-
(
|
|
1361
|
-
)
|
|
1481
|
+
export const formatMnemonicError =
|
|
1482
|
+
/*#__PURE__*/ createTypeErrorFormatter<MnemonicError>(
|
|
1483
|
+
(error) => `Invalid BIP39 mnemonic: ${error.value}.`,
|
|
1484
|
+
);
|
|
1362
1485
|
|
|
1363
1486
|
/**
|
|
1364
1487
|
* String matching a regular expression.
|
|
@@ -1369,7 +1492,7 @@ export const formatMnemonicError = createTypeErrorFormatter<MnemonicError>(
|
|
|
1369
1492
|
* const Alphanumeric = regex("Alphanumeric", /^[a-z0-9]+$/i)(String);
|
|
1370
1493
|
* ```
|
|
1371
1494
|
*
|
|
1372
|
-
* @
|
|
1495
|
+
* @group String
|
|
1373
1496
|
*/
|
|
1374
1497
|
export const regex: <Name extends TypeName>(
|
|
1375
1498
|
name: Name,
|
|
@@ -1388,16 +1511,18 @@ export const regex: <Name extends TypeName>(
|
|
|
1388
1511
|
});
|
|
1389
1512
|
};
|
|
1390
1513
|
|
|
1391
|
-
export interface RegexError<
|
|
1392
|
-
extends
|
|
1514
|
+
export interface RegexError<
|
|
1515
|
+
Name extends TypeName = TypeName,
|
|
1516
|
+
> extends TypeError<"Regex"> {
|
|
1393
1517
|
readonly name: Name;
|
|
1394
1518
|
readonly pattern: RegExp;
|
|
1395
1519
|
}
|
|
1396
1520
|
|
|
1397
|
-
export const formatRegexError =
|
|
1398
|
-
(
|
|
1399
|
-
|
|
1400
|
-
|
|
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
|
+
);
|
|
1401
1526
|
|
|
1402
1527
|
/**
|
|
1403
1528
|
* URL-safe string.
|
|
@@ -1424,9 +1549,12 @@ export const formatRegexError = createTypeErrorFormatter<RegexError>(
|
|
|
1424
1549
|
* }
|
|
1425
1550
|
* ```
|
|
1426
1551
|
*
|
|
1427
|
-
* @
|
|
1552
|
+
* @group String
|
|
1428
1553
|
*/
|
|
1429
|
-
export const UrlSafeString = regex(
|
|
1554
|
+
export const UrlSafeString = /*#__PURE__*/ regex(
|
|
1555
|
+
"UrlSafeString",
|
|
1556
|
+
/^[A-Za-z0-9_-]+$/,
|
|
1557
|
+
)(String);
|
|
1430
1558
|
export type UrlSafeString = typeof UrlSafeString.Type;
|
|
1431
1559
|
export type UrlSafeStringError = typeof UrlSafeString.Error;
|
|
1432
1560
|
|
|
@@ -1436,9 +1564,9 @@ export type UrlSafeStringError = typeof UrlSafeString.Error;
|
|
|
1436
1564
|
* Encode with {@link uint8ArrayToBase64Url}, decode with
|
|
1437
1565
|
* {@link base64UrlToUint8Array}.
|
|
1438
1566
|
*
|
|
1439
|
-
* @
|
|
1567
|
+
* @group String
|
|
1440
1568
|
*/
|
|
1441
|
-
export const Base64Url = brand(
|
|
1569
|
+
export const Base64Url = /*#__PURE__*/ brand(
|
|
1442
1570
|
"Base64Url",
|
|
1443
1571
|
String,
|
|
1444
1572
|
(value: string): Result<string, Base64UrlError> => {
|
|
@@ -1461,9 +1589,10 @@ export const Base64Url = brand(
|
|
|
1461
1589
|
export type Base64Url = typeof Base64Url.Type;
|
|
1462
1590
|
export interface Base64UrlError extends TypeError<"Base64Url"> {}
|
|
1463
1591
|
|
|
1464
|
-
export const formatBase64UrlError =
|
|
1465
|
-
(
|
|
1466
|
-
)
|
|
1592
|
+
export const formatBase64UrlError =
|
|
1593
|
+
/*#__PURE__*/ createTypeErrorFormatter<Base64UrlError>(
|
|
1594
|
+
(error) => `The value ${error.value} is not a valid Base64Url string.`,
|
|
1595
|
+
);
|
|
1467
1596
|
|
|
1468
1597
|
const base64UrlOptions = { alphabet: "base64url", omitPadding: true };
|
|
1469
1598
|
|
|
@@ -1515,33 +1644,35 @@ export const base64UrlToUint8Array: (str: Base64Url) => Uint8Array =
|
|
|
1515
1644
|
};
|
|
1516
1645
|
|
|
1517
1646
|
/**
|
|
1518
|
-
*
|
|
1647
|
+
* Alphanumeric string for naming in file systems, URLs, and identifiers.
|
|
1519
1648
|
*
|
|
1520
1649
|
* Uses the same safe alphabet as {@link UrlSafeString} (letters, digits, `-`,
|
|
1521
|
-
* `_`).
|
|
1650
|
+
* `_`).
|
|
1522
1651
|
*
|
|
1523
1652
|
* The string must be between 1 and 64 characters.
|
|
1524
1653
|
*
|
|
1525
1654
|
* ### Example
|
|
1526
1655
|
*
|
|
1527
1656
|
* ```ts
|
|
1528
|
-
* const result =
|
|
1657
|
+
* const result = Name.from("data-report-123");
|
|
1529
1658
|
* if (result.ok) {
|
|
1530
|
-
* console.log("Valid
|
|
1659
|
+
* console.log("Valid Name string:", result.value);
|
|
1531
1660
|
* } else {
|
|
1532
|
-
* console.error("Invalid
|
|
1661
|
+
* console.error("Invalid Name string:", result.error);
|
|
1533
1662
|
* }
|
|
1534
1663
|
* ```
|
|
1535
1664
|
*
|
|
1536
|
-
* @
|
|
1665
|
+
* @group String
|
|
1537
1666
|
*/
|
|
1538
|
-
export const
|
|
1667
|
+
export const Name = /*#__PURE__*/ brand("Name", UrlSafeString, (value) =>
|
|
1539
1668
|
value.length >= 1 && value.length <= 64
|
|
1540
1669
|
? ok(value)
|
|
1541
|
-
: err<
|
|
1670
|
+
: err<NameError>({ type: "Name", value }),
|
|
1542
1671
|
);
|
|
1543
|
-
export type
|
|
1544
|
-
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");
|
|
1545
1676
|
|
|
1546
1677
|
/**
|
|
1547
1678
|
* Trimmed string between 8 and 64 characters, branded as `SimplePassword`.
|
|
@@ -1565,11 +1696,11 @@ export interface SimpleNameError extends TypeError<"SimpleName"> {}
|
|
|
1565
1696
|
* // |> brand("SimplePassword", %)
|
|
1566
1697
|
* ```
|
|
1567
1698
|
*
|
|
1568
|
-
* @
|
|
1699
|
+
* @group String
|
|
1569
1700
|
*/
|
|
1570
|
-
export const SimplePassword = brand(
|
|
1701
|
+
export const SimplePassword = /*#__PURE__*/ brand(
|
|
1571
1702
|
"SimplePassword",
|
|
1572
|
-
minLength(8)(maxLength(64)(TrimmedString)),
|
|
1703
|
+
/*#__PURE__*/ minLength(8)(/*#__PURE__*/ maxLength(64)(TrimmedString)),
|
|
1573
1704
|
);
|
|
1574
1705
|
export type SimplePassword = typeof SimplePassword.Type;
|
|
1575
1706
|
|
|
@@ -1600,16 +1731,16 @@ export const formatSimplePasswordError = (
|
|
|
1600
1731
|
* copied (logs, URLs, exports); only use it when you explicitly want insertion
|
|
1601
1732
|
* locality for very large write‑heavy tables and accept timestamp exposure.
|
|
1602
1733
|
*
|
|
1603
|
-
*
|
|
1734
|
+
* ## Future
|
|
1604
1735
|
*
|
|
1605
1736
|
* A possible hybrid masked‑time approach (`timestamp ^ H(cluster_id, timestamp
|
|
1606
1737
|
*
|
|
1607
1738
|
* > > N)`) could provide locality without exposing raw creation time. See
|
|
1608
1739
|
* > > https://brooker.co.za/blog/2025/10/22/uuidv7.html
|
|
1609
1740
|
*
|
|
1610
|
-
* @
|
|
1741
|
+
* @group String
|
|
1611
1742
|
*/
|
|
1612
|
-
export const Id = brand("Id", String, (value) =>
|
|
1743
|
+
export const Id = /*#__PURE__*/ brand("Id", String, (value) =>
|
|
1613
1744
|
value.length === 22 && Base64Url.fromParent(value).ok
|
|
1614
1745
|
? ok(value)
|
|
1615
1746
|
: err<IdError>({ type: "Id", value }),
|
|
@@ -1618,7 +1749,7 @@ export type Id = typeof Id.Type;
|
|
|
1618
1749
|
|
|
1619
1750
|
export interface IdError extends TypeError<"Id"> {}
|
|
1620
1751
|
|
|
1621
|
-
export const formatIdError = createTypeErrorFormatter<IdError>(
|
|
1752
|
+
export const formatIdError = /*#__PURE__*/ createTypeErrorFormatter<IdError>(
|
|
1622
1753
|
(error) => `The value ${error.value} is not a valid Id.`,
|
|
1623
1754
|
);
|
|
1624
1755
|
|
|
@@ -1673,7 +1804,7 @@ export const createId = <B extends string = never>(
|
|
|
1673
1804
|
* external string from the generated {@link Id}. If you need to preserve the
|
|
1674
1805
|
* original external ID, store it in a separate column.
|
|
1675
1806
|
*
|
|
1676
|
-
* @
|
|
1807
|
+
* @group String
|
|
1677
1808
|
*/
|
|
1678
1809
|
export const createIdFromString = <B extends string = never>(
|
|
1679
1810
|
value: string,
|
|
@@ -1718,7 +1849,9 @@ export const createIdAsUuidv7 = <B extends string = never>(
|
|
|
1718
1849
|
id[6] = (id[6] & 0x0f) | 0x70;
|
|
1719
1850
|
id[8] = (id[8] & 0x3f) | 0x80;
|
|
1720
1851
|
|
|
1721
|
-
return id as unknown as [B] extends [never]
|
|
1852
|
+
return uint8ArrayToBase64Url(id) as unknown as [B] extends [never]
|
|
1853
|
+
? Id
|
|
1854
|
+
: Id & Brand<B>;
|
|
1722
1855
|
};
|
|
1723
1856
|
|
|
1724
1857
|
/**
|
|
@@ -1734,7 +1867,7 @@ export const createIdAsUuidv7 = <B extends string = never>(
|
|
|
1734
1867
|
* type TodoId = typeof TodoId.Type;
|
|
1735
1868
|
* ```
|
|
1736
1869
|
*
|
|
1737
|
-
* @
|
|
1870
|
+
* @group String
|
|
1738
1871
|
*/
|
|
1739
1872
|
export const id = <Table extends TypeName>(table: Table): TableId<Table> => {
|
|
1740
1873
|
const fromUnknown = (value: unknown) => {
|
|
@@ -1757,29 +1890,33 @@ export const id = <Table extends TypeName>(table: Table): TableId<Table> => {
|
|
|
1757
1890
|
};
|
|
1758
1891
|
};
|
|
1759
1892
|
|
|
1760
|
-
export interface TableId<Table extends TypeName>
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
> {
|
|
1893
|
+
export interface TableId<Table extends TypeName> extends Type<
|
|
1894
|
+
"Id",
|
|
1895
|
+
string & Brand<"Id"> & Brand<Table>,
|
|
1896
|
+
string,
|
|
1897
|
+
TableIdError<Table>,
|
|
1898
|
+
string,
|
|
1899
|
+
StringError
|
|
1900
|
+
> {
|
|
1769
1901
|
table: Table;
|
|
1770
1902
|
}
|
|
1771
1903
|
|
|
1772
|
-
export interface TableIdError<
|
|
1773
|
-
extends
|
|
1904
|
+
export interface TableIdError<
|
|
1905
|
+
Table extends TypeName = TypeName,
|
|
1906
|
+
> extends TypeError<"TableId"> {
|
|
1774
1907
|
readonly table: Table;
|
|
1775
1908
|
}
|
|
1776
1909
|
|
|
1777
|
-
export const formatTableIdError =
|
|
1778
|
-
(
|
|
1779
|
-
)
|
|
1910
|
+
export const formatTableIdError =
|
|
1911
|
+
/*#__PURE__*/ createTypeErrorFormatter<TableIdError>(
|
|
1912
|
+
(error) => `Invalid Id for table ${error.table}: ${error.value}.`,
|
|
1913
|
+
);
|
|
1780
1914
|
|
|
1781
1915
|
/** Binary representation of an {@link Id}. */
|
|
1782
|
-
export const IdBytes = brand(
|
|
1916
|
+
export const IdBytes = /*#__PURE__*/ brand(
|
|
1917
|
+
"IdBytes",
|
|
1918
|
+
/*#__PURE__*/ length(16)(Uint8Array),
|
|
1919
|
+
);
|
|
1783
1920
|
export type IdBytes = typeof IdBytes.Type;
|
|
1784
1921
|
|
|
1785
1922
|
export const idBytesTypeValueLength = 16 as NonNegativeInt;
|
|
@@ -1804,7 +1941,7 @@ export const idBytesToId = (idBytes: IdBytes): Id =>
|
|
|
1804
1941
|
* const errorResult = PositiveNumber.from(-5); // err
|
|
1805
1942
|
* ```
|
|
1806
1943
|
*
|
|
1807
|
-
* @
|
|
1944
|
+
* @group Number
|
|
1808
1945
|
*/
|
|
1809
1946
|
export const positive: BrandFactory<"Positive", number, PositiveError> = (
|
|
1810
1947
|
parent,
|
|
@@ -1815,9 +1952,10 @@ export const positive: BrandFactory<"Positive", number, PositiveError> = (
|
|
|
1815
1952
|
|
|
1816
1953
|
export interface PositiveError extends TypeError<"Positive"> {}
|
|
1817
1954
|
|
|
1818
|
-
export const formatPositiveError =
|
|
1819
|
-
|
|
1820
|
-
)
|
|
1955
|
+
export const formatPositiveError =
|
|
1956
|
+
/*#__PURE__*/ createTypeErrorFormatter<PositiveError>(
|
|
1957
|
+
(error) => `The value ${error.value} must be positive (> 0).`,
|
|
1958
|
+
);
|
|
1821
1959
|
|
|
1822
1960
|
/**
|
|
1823
1961
|
* Negative number (< 0).
|
|
@@ -1828,7 +1966,7 @@ export const formatPositiveError = createTypeErrorFormatter<PositiveError>(
|
|
|
1828
1966
|
* const NegativeNumber = negative(Number);
|
|
1829
1967
|
* ```
|
|
1830
1968
|
*
|
|
1831
|
-
* @
|
|
1969
|
+
* @group Number
|
|
1832
1970
|
*/
|
|
1833
1971
|
export const negative: BrandFactory<"Negative", number, NegativeError> = (
|
|
1834
1972
|
parent,
|
|
@@ -1839,9 +1977,10 @@ export const negative: BrandFactory<"Negative", number, NegativeError> = (
|
|
|
1839
1977
|
|
|
1840
1978
|
export interface NegativeError extends TypeError<"Negative"> {}
|
|
1841
1979
|
|
|
1842
|
-
export const formatNegativeError =
|
|
1843
|
-
|
|
1844
|
-
)
|
|
1980
|
+
export const formatNegativeError =
|
|
1981
|
+
/*#__PURE__*/ createTypeErrorFormatter<NegativeError>(
|
|
1982
|
+
(error) => `The value ${error.value} must be negative (< 0).`,
|
|
1983
|
+
);
|
|
1845
1984
|
|
|
1846
1985
|
/**
|
|
1847
1986
|
* Non-positive number (≤ 0).
|
|
@@ -1852,7 +1991,7 @@ export const formatNegativeError = createTypeErrorFormatter<NegativeError>(
|
|
|
1852
1991
|
* const NonPositiveNumber = nonPositive(Number);
|
|
1853
1992
|
* ```
|
|
1854
1993
|
*
|
|
1855
|
-
* @
|
|
1994
|
+
* @group Number
|
|
1856
1995
|
*/
|
|
1857
1996
|
export const nonPositive: BrandFactory<
|
|
1858
1997
|
"NonPositive",
|
|
@@ -1868,7 +2007,7 @@ export const nonPositive: BrandFactory<
|
|
|
1868
2007
|
export interface NonPositiveError extends TypeError<"NonPositive"> {}
|
|
1869
2008
|
|
|
1870
2009
|
export const formatNonPositiveError =
|
|
1871
|
-
createTypeErrorFormatter<NonPositiveError>(
|
|
2010
|
+
/*#__PURE__*/ createTypeErrorFormatter<NonPositiveError>(
|
|
1872
2011
|
(error) => `The value ${error.value} must be non-positive (≤ 0).`,
|
|
1873
2012
|
);
|
|
1874
2013
|
|
|
@@ -1881,7 +2020,7 @@ export const formatNonPositiveError =
|
|
|
1881
2020
|
* const NonNegativeNumber = nonNegative(Number);
|
|
1882
2021
|
* ```
|
|
1883
2022
|
*
|
|
1884
|
-
* @
|
|
2023
|
+
* @group Number
|
|
1885
2024
|
*/
|
|
1886
2025
|
export const nonNegative: BrandFactory<
|
|
1887
2026
|
"NonNegative",
|
|
@@ -1897,40 +2036,40 @@ export const nonNegative: BrandFactory<
|
|
|
1897
2036
|
export interface NonNegativeError extends TypeError<"NonNegative"> {}
|
|
1898
2037
|
|
|
1899
2038
|
export const formatNonNegativeError =
|
|
1900
|
-
createTypeErrorFormatter<NonNegativeError>(
|
|
2039
|
+
/*#__PURE__*/ createTypeErrorFormatter<NonNegativeError>(
|
|
1901
2040
|
(error) => `The value ${error.value} must be non-negative (≥ 0).`,
|
|
1902
2041
|
);
|
|
1903
2042
|
|
|
1904
2043
|
/**
|
|
1905
2044
|
* Non-negative number (≥ 0).
|
|
1906
2045
|
*
|
|
1907
|
-
* @
|
|
2046
|
+
* @group Number
|
|
1908
2047
|
*/
|
|
1909
|
-
export const NonNegativeNumber = nonNegative(Number);
|
|
2048
|
+
export const NonNegativeNumber = /*#__PURE__*/ nonNegative(Number);
|
|
1910
2049
|
export type NonNegativeNumber = typeof NonNegativeNumber.Type;
|
|
1911
2050
|
|
|
1912
2051
|
/**
|
|
1913
2052
|
* Positive number (> 0).
|
|
1914
2053
|
*
|
|
1915
|
-
* @
|
|
2054
|
+
* @group Number
|
|
1916
2055
|
*/
|
|
1917
|
-
export const PositiveNumber = positive(NonNegativeNumber);
|
|
2056
|
+
export const PositiveNumber = /*#__PURE__*/ positive(NonNegativeNumber);
|
|
1918
2057
|
export type PositiveNumber = typeof PositiveNumber.Type;
|
|
1919
2058
|
|
|
1920
2059
|
/**
|
|
1921
2060
|
* Non-positive number (≤ 0).
|
|
1922
2061
|
*
|
|
1923
|
-
* @
|
|
2062
|
+
* @group Number
|
|
1924
2063
|
*/
|
|
1925
|
-
export const NonPositiveNumber = nonPositive(Number);
|
|
2064
|
+
export const NonPositiveNumber = /*#__PURE__*/ nonPositive(Number);
|
|
1926
2065
|
export type NonPositiveNumber = typeof NonPositiveNumber.Type;
|
|
1927
2066
|
|
|
1928
2067
|
/**
|
|
1929
2068
|
* Negative number (< 0).
|
|
1930
2069
|
*
|
|
1931
|
-
* @
|
|
2070
|
+
* @group Number
|
|
1932
2071
|
*/
|
|
1933
|
-
export const NegativeNumber = negative(NonPositiveNumber);
|
|
2072
|
+
export const NegativeNumber = /*#__PURE__*/ negative(NonPositiveNumber);
|
|
1934
2073
|
export type NegativeNumber = typeof NegativeNumber.Type;
|
|
1935
2074
|
|
|
1936
2075
|
/**
|
|
@@ -1939,12 +2078,17 @@ export type NegativeNumber = typeof NegativeNumber.Type;
|
|
|
1939
2078
|
* ### Example
|
|
1940
2079
|
*
|
|
1941
2080
|
* ```ts
|
|
1942
|
-
* const Int =
|
|
2081
|
+
* const Int = safeInt(Number);
|
|
1943
2082
|
* ```
|
|
1944
2083
|
*
|
|
1945
|
-
*
|
|
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
|
|
1946
2089
|
*/
|
|
1947
|
-
|
|
2090
|
+
|
|
2091
|
+
const safeInt: BrandFactory<"Int", number, IntError> = (parent) =>
|
|
1948
2092
|
brand("Int", parent, (value) =>
|
|
1949
2093
|
globalThis.Number.isSafeInteger(value)
|
|
1950
2094
|
? ok(value)
|
|
@@ -1953,59 +2097,67 @@ export const int: BrandFactory<"Int", number, IntError> = (parent) =>
|
|
|
1953
2097
|
|
|
1954
2098
|
export interface IntError extends TypeError<"Int"> {}
|
|
1955
2099
|
|
|
1956
|
-
export const formatIntError = createTypeErrorFormatter<IntError>(
|
|
2100
|
+
export const formatIntError = /*#__PURE__*/ createTypeErrorFormatter<IntError>(
|
|
1957
2101
|
(error) => `The value ${error.value} must be an integer.`,
|
|
1958
2102
|
);
|
|
1959
2103
|
|
|
1960
2104
|
/**
|
|
1961
2105
|
* Integer within the safe range of JavaScript numbers.
|
|
1962
2106
|
*
|
|
1963
|
-
* @
|
|
2107
|
+
* @group Number
|
|
1964
2108
|
*/
|
|
1965
|
-
export const Int =
|
|
2109
|
+
export const Int = /*#__PURE__*/ safeInt(Number);
|
|
1966
2110
|
export type Int = typeof Int.Type;
|
|
1967
2111
|
|
|
1968
2112
|
/**
|
|
1969
2113
|
* Non-negative integer (≥ 0).
|
|
1970
2114
|
*
|
|
1971
|
-
* @
|
|
2115
|
+
* @group Number
|
|
1972
2116
|
*/
|
|
1973
|
-
export const NonNegativeInt = nonNegative(Int);
|
|
2117
|
+
export const NonNegativeInt = /*#__PURE__*/ nonNegative(Int);
|
|
1974
2118
|
export type NonNegativeInt = typeof NonNegativeInt.Type;
|
|
1975
2119
|
|
|
2120
|
+
/** Minimum {@link NonNegativeInt} value (0). */
|
|
2121
|
+
export const zeroNonNegativeInt = /*#__PURE__*/ NonNegativeInt.orThrow(0);
|
|
2122
|
+
|
|
1976
2123
|
/**
|
|
1977
2124
|
* Positive integer (> 0).
|
|
1978
2125
|
*
|
|
1979
|
-
* @
|
|
2126
|
+
* @group Number
|
|
1980
2127
|
*/
|
|
1981
|
-
export const PositiveInt = positive(NonNegativeInt);
|
|
2128
|
+
export const PositiveInt = /*#__PURE__*/ positive(NonNegativeInt);
|
|
1982
2129
|
export type PositiveInt = typeof PositiveInt.Type;
|
|
1983
2130
|
|
|
1984
|
-
/**
|
|
1985
|
-
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(
|
|
1986
2136
|
globalThis.Number.MAX_SAFE_INTEGER,
|
|
1987
2137
|
);
|
|
1988
2138
|
|
|
1989
2139
|
/**
|
|
1990
2140
|
* Non-positive integer (≤ 0).
|
|
1991
2141
|
*
|
|
1992
|
-
* @
|
|
2142
|
+
* @group Number
|
|
1993
2143
|
*/
|
|
1994
|
-
export const NonPositiveInt = nonPositive(Int);
|
|
2144
|
+
export const NonPositiveInt = /*#__PURE__*/ nonPositive(Int);
|
|
1995
2145
|
export type NonPositiveInt = typeof NonPositiveInt.Type;
|
|
1996
2146
|
|
|
1997
2147
|
/**
|
|
1998
2148
|
* Negative integer (< 0).
|
|
1999
2149
|
*
|
|
2000
|
-
* @
|
|
2150
|
+
* @group Number
|
|
2001
2151
|
*/
|
|
2002
|
-
export const NegativeInt = negative(NonPositiveInt);
|
|
2152
|
+
export const NegativeInt = /*#__PURE__*/ negative(NonPositiveInt);
|
|
2003
2153
|
export type NegativeInt = typeof NegativeInt.Type;
|
|
2004
2154
|
|
|
2005
2155
|
/**
|
|
2006
2156
|
* Number greater than a specified value.
|
|
2007
2157
|
*
|
|
2008
|
-
* @
|
|
2158
|
+
* Use numeric literal, not expression. See {@link BrandFactory}.
|
|
2159
|
+
*
|
|
2160
|
+
* @group Number
|
|
2009
2161
|
*/
|
|
2010
2162
|
export const greaterThan: <Min extends number>(
|
|
2011
2163
|
min: Min,
|
|
@@ -2015,20 +2167,23 @@ export const greaterThan: <Min extends number>(
|
|
|
2015
2167
|
value > min ? ok(value) : err({ type: "GreaterThan", value, min }),
|
|
2016
2168
|
);
|
|
2017
2169
|
|
|
2018
|
-
export interface GreaterThanError<
|
|
2019
|
-
extends
|
|
2170
|
+
export interface GreaterThanError<
|
|
2171
|
+
Min extends number = number,
|
|
2172
|
+
> extends TypeError<"GreaterThan"> {
|
|
2020
2173
|
readonly min: Min;
|
|
2021
2174
|
}
|
|
2022
2175
|
|
|
2023
2176
|
export const formatGreaterThanError =
|
|
2024
|
-
createTypeErrorFormatter<GreaterThanError>(
|
|
2177
|
+
/*#__PURE__*/ createTypeErrorFormatter<GreaterThanError>(
|
|
2025
2178
|
(error) => `The value ${error.value} is not > ${error.min}.`,
|
|
2026
2179
|
);
|
|
2027
2180
|
|
|
2028
2181
|
/**
|
|
2029
2182
|
* Number less than a specified value.
|
|
2030
2183
|
*
|
|
2031
|
-
* @
|
|
2184
|
+
* Use numeric literal, not expression. See {@link BrandFactory}.
|
|
2185
|
+
*
|
|
2186
|
+
* @group Number
|
|
2032
2187
|
*/
|
|
2033
2188
|
export const lessThan: <Max extends number>(
|
|
2034
2189
|
max: Max,
|
|
@@ -2038,19 +2193,23 @@ export const lessThan: <Max extends number>(
|
|
|
2038
2193
|
value < max ? ok(value) : err({ type: "LessThan", value, max }),
|
|
2039
2194
|
);
|
|
2040
2195
|
|
|
2041
|
-
export interface LessThanError<
|
|
2042
|
-
extends
|
|
2196
|
+
export interface LessThanError<
|
|
2197
|
+
Max extends number = number,
|
|
2198
|
+
> extends TypeError<"LessThan"> {
|
|
2043
2199
|
readonly max: Max;
|
|
2044
2200
|
}
|
|
2045
2201
|
|
|
2046
|
-
export const formatLessThanError =
|
|
2047
|
-
|
|
2048
|
-
)
|
|
2202
|
+
export const formatLessThanError =
|
|
2203
|
+
/*#__PURE__*/ createTypeErrorFormatter<LessThanError>(
|
|
2204
|
+
(error) => `The value ${error.value} is not < ${error.max}.`,
|
|
2205
|
+
);
|
|
2049
2206
|
|
|
2050
2207
|
/**
|
|
2051
2208
|
* Number ≥ a specified value.
|
|
2052
2209
|
*
|
|
2053
|
-
* @
|
|
2210
|
+
* Use numeric literal, not expression. See {@link BrandFactory}.
|
|
2211
|
+
*
|
|
2212
|
+
* @group Number
|
|
2054
2213
|
*/
|
|
2055
2214
|
export const greaterThanOrEqualTo: <Min extends number>(
|
|
2056
2215
|
min: Min,
|
|
@@ -2065,20 +2224,23 @@ export const greaterThanOrEqualTo: <Min extends number>(
|
|
|
2065
2224
|
: err({ type: "GreaterThanOrEqualTo", value, min }),
|
|
2066
2225
|
);
|
|
2067
2226
|
|
|
2068
|
-
export interface GreaterThanOrEqualToError<
|
|
2069
|
-
extends
|
|
2227
|
+
export interface GreaterThanOrEqualToError<
|
|
2228
|
+
Min extends number = number,
|
|
2229
|
+
> extends TypeError<"GreaterThanOrEqualTo"> {
|
|
2070
2230
|
readonly min: Min;
|
|
2071
2231
|
}
|
|
2072
2232
|
|
|
2073
2233
|
export const formatGreaterThanOrEqualToError =
|
|
2074
|
-
createTypeErrorFormatter<GreaterThanOrEqualToError>(
|
|
2234
|
+
/*#__PURE__*/ createTypeErrorFormatter<GreaterThanOrEqualToError>(
|
|
2075
2235
|
(error) => `The value ${error.value} is not >= ${error.min}.`,
|
|
2076
2236
|
);
|
|
2077
2237
|
|
|
2078
2238
|
/**
|
|
2079
2239
|
* Number ≤ a specified value.
|
|
2080
2240
|
*
|
|
2081
|
-
* @
|
|
2241
|
+
* Use numeric literal, not expression. See {@link BrandFactory}.
|
|
2242
|
+
*
|
|
2243
|
+
* @group Number
|
|
2082
2244
|
*/
|
|
2083
2245
|
export const lessThanOrEqualTo: <Max extends number>(
|
|
2084
2246
|
max: Max,
|
|
@@ -2091,20 +2253,21 @@ export const lessThanOrEqualTo: <Max extends number>(
|
|
|
2091
2253
|
value <= max ? ok(value) : err({ type: "LessThanOrEqualTo", value, max }),
|
|
2092
2254
|
);
|
|
2093
2255
|
|
|
2094
|
-
export interface LessThanOrEqualToError<
|
|
2095
|
-
extends
|
|
2256
|
+
export interface LessThanOrEqualToError<
|
|
2257
|
+
Max extends number = number,
|
|
2258
|
+
> extends TypeError<"LessThanOrEqualTo"> {
|
|
2096
2259
|
readonly max: Max;
|
|
2097
2260
|
}
|
|
2098
2261
|
|
|
2099
2262
|
export const formatLessThanOrEqualToError =
|
|
2100
|
-
createTypeErrorFormatter<LessThanOrEqualToError>(
|
|
2263
|
+
/*#__PURE__*/ createTypeErrorFormatter<LessThanOrEqualToError>(
|
|
2101
2264
|
(error) => `The value ${error.value} is not <= ${error.max}.`,
|
|
2102
2265
|
);
|
|
2103
2266
|
|
|
2104
2267
|
/**
|
|
2105
2268
|
* Number that is not NaN.
|
|
2106
2269
|
*
|
|
2107
|
-
* @
|
|
2270
|
+
* @group Number
|
|
2108
2271
|
*/
|
|
2109
2272
|
export const nonNaN: BrandFactory<"NonNaN", number, NonNaNError> = (parent) =>
|
|
2110
2273
|
brand("NonNaN", parent, (value) =>
|
|
@@ -2115,18 +2278,19 @@ export const nonNaN: BrandFactory<"NonNaN", number, NonNaNError> = (parent) =>
|
|
|
2115
2278
|
|
|
2116
2279
|
export interface NonNaNError extends TypeError<"NonNaN"> {}
|
|
2117
2280
|
|
|
2118
|
-
export const formatNonNaNError =
|
|
2119
|
-
(
|
|
2120
|
-
)
|
|
2281
|
+
export const formatNonNaNError =
|
|
2282
|
+
/*#__PURE__*/ createTypeErrorFormatter<NonNaNError>(
|
|
2283
|
+
() => `The value must not be NaN.`,
|
|
2284
|
+
);
|
|
2121
2285
|
|
|
2122
|
-
/** @
|
|
2123
|
-
export const NonNaNNumber = nonNaN(Number);
|
|
2286
|
+
/** @group Number */
|
|
2287
|
+
export const NonNaNNumber = /*#__PURE__*/ nonNaN(Number);
|
|
2124
2288
|
export type NonNaNNumber = typeof NonNaNNumber.Type;
|
|
2125
2289
|
|
|
2126
2290
|
/**
|
|
2127
2291
|
* Finite number.
|
|
2128
2292
|
*
|
|
2129
|
-
* @
|
|
2293
|
+
* @group Number
|
|
2130
2294
|
*/
|
|
2131
2295
|
export const finite: BrandFactory<"Finite", number, FiniteError> = (parent) =>
|
|
2132
2296
|
brand("Finite", parent, (value) =>
|
|
@@ -2137,9 +2301,10 @@ export const finite: BrandFactory<"Finite", number, FiniteError> = (parent) =>
|
|
|
2137
2301
|
|
|
2138
2302
|
export interface FiniteError extends TypeError<"Finite"> {}
|
|
2139
2303
|
|
|
2140
|
-
export const formatFiniteError =
|
|
2141
|
-
(
|
|
2142
|
-
)
|
|
2304
|
+
export const formatFiniteError =
|
|
2305
|
+
/*#__PURE__*/ createTypeErrorFormatter<FiniteError>(
|
|
2306
|
+
(error) => `The value ${error.value} must be finite.`,
|
|
2307
|
+
);
|
|
2143
2308
|
|
|
2144
2309
|
/**
|
|
2145
2310
|
* Finite number.
|
|
@@ -2152,15 +2317,17 @@ export const formatFiniteError = createTypeErrorFormatter<FiniteError>(
|
|
|
2152
2317
|
* finite (e.g., `Infinity`, `-Infinity`, or `NaN`). Using `FiniteNumber` helps
|
|
2153
2318
|
* prevent these unexpected behaviors when working with JSON serialization.
|
|
2154
2319
|
*
|
|
2155
|
-
* @
|
|
2320
|
+
* @group Number
|
|
2156
2321
|
*/
|
|
2157
|
-
export const FiniteNumber = finite(Number);
|
|
2322
|
+
export const FiniteNumber = /*#__PURE__*/ finite(Number);
|
|
2158
2323
|
export type FiniteNumber = typeof FiniteNumber.Type;
|
|
2159
2324
|
|
|
2160
2325
|
/**
|
|
2161
2326
|
* Number that is a multiple of a divisor.
|
|
2162
2327
|
*
|
|
2163
|
-
* @
|
|
2328
|
+
* Use numeric literal, not expression. See {@link BrandFactory}.
|
|
2329
|
+
*
|
|
2330
|
+
* @group Number
|
|
2164
2331
|
*/
|
|
2165
2332
|
export const multipleOf: <Divisor extends number>(
|
|
2166
2333
|
divisor: Divisor,
|
|
@@ -2172,18 +2339,23 @@ export const multipleOf: <Divisor extends number>(
|
|
|
2172
2339
|
: err({ type: "MultipleOf", value, divisor }),
|
|
2173
2340
|
);
|
|
2174
2341
|
|
|
2175
|
-
export interface MultipleOfError<
|
|
2176
|
-
extends
|
|
2342
|
+
export interface MultipleOfError<
|
|
2343
|
+
Divisor extends number = number,
|
|
2344
|
+
> extends TypeError<"MultipleOf"> {
|
|
2177
2345
|
readonly divisor: Divisor;
|
|
2178
2346
|
}
|
|
2179
2347
|
|
|
2180
|
-
export const formatMultipleOfError =
|
|
2181
|
-
(
|
|
2182
|
-
)
|
|
2348
|
+
export const formatMultipleOfError =
|
|
2349
|
+
/*#__PURE__*/ createTypeErrorFormatter<MultipleOfError>(
|
|
2350
|
+
(error) =>
|
|
2351
|
+
`The value ${error.value} is not a multiple of ${error.divisor}.`,
|
|
2352
|
+
);
|
|
2183
2353
|
|
|
2184
2354
|
/**
|
|
2185
2355
|
* Number within a range, inclusive.
|
|
2186
2356
|
*
|
|
2357
|
+
* Use numeric literal, not expression. See {@link BrandFactory}.
|
|
2358
|
+
*
|
|
2187
2359
|
* ### Example
|
|
2188
2360
|
*
|
|
2189
2361
|
* ```ts
|
|
@@ -2192,7 +2364,7 @@ export const formatMultipleOfError = createTypeErrorFormatter<MultipleOfError>(
|
|
|
2192
2364
|
* const errorResult = Between1And10.from(11); // err
|
|
2193
2365
|
* ```
|
|
2194
2366
|
*
|
|
2195
|
-
* @
|
|
2367
|
+
* @group Number
|
|
2196
2368
|
*/
|
|
2197
2369
|
export const between: <Min extends number, Max extends number>(
|
|
2198
2370
|
min: Min,
|
|
@@ -2213,10 +2385,11 @@ export interface BetweenError<
|
|
|
2213
2385
|
readonly max: Max;
|
|
2214
2386
|
}
|
|
2215
2387
|
|
|
2216
|
-
export const formatBetweenError =
|
|
2217
|
-
(
|
|
2218
|
-
|
|
2219
|
-
|
|
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
|
+
);
|
|
2220
2393
|
|
|
2221
2394
|
/**
|
|
2222
2395
|
* Literal {@link Type}.
|
|
@@ -2233,7 +2406,7 @@ export const formatBetweenError = createTypeErrorFormatter<BetweenError>(
|
|
|
2233
2406
|
*
|
|
2234
2407
|
* TODO: Add JsonValue
|
|
2235
2408
|
*
|
|
2236
|
-
* @
|
|
2409
|
+
* @group Base factories
|
|
2237
2410
|
*/
|
|
2238
2411
|
export const literal = <T extends Literal>(expected: T): LiteralType<T> => {
|
|
2239
2412
|
const fromUnknown = (value: unknown): Result<T, LiteralError<T>> =>
|
|
@@ -2250,27 +2423,33 @@ export const literal = <T extends Literal>(expected: T): LiteralType<T> => {
|
|
|
2250
2423
|
} as LiteralType<T>;
|
|
2251
2424
|
};
|
|
2252
2425
|
|
|
2253
|
-
export interface LiteralType<T extends Literal>
|
|
2254
|
-
|
|
2426
|
+
export interface LiteralType<T extends Literal> extends Type<
|
|
2427
|
+
"Literal",
|
|
2428
|
+
T,
|
|
2429
|
+
WidenLiteral<T>,
|
|
2430
|
+
LiteralError<T>
|
|
2431
|
+
> {
|
|
2255
2432
|
expected: T;
|
|
2256
2433
|
}
|
|
2257
2434
|
|
|
2258
|
-
export interface LiteralError<
|
|
2259
|
-
extends
|
|
2435
|
+
export interface LiteralError<
|
|
2436
|
+
T extends Literal = Literal,
|
|
2437
|
+
> extends TypeError<"Literal"> {
|
|
2260
2438
|
readonly expected: T;
|
|
2261
2439
|
}
|
|
2262
2440
|
|
|
2263
|
-
export const formatLiteralError =
|
|
2264
|
-
(
|
|
2265
|
-
|
|
2266
|
-
error.expected
|
|
2267
|
-
|
|
2268
|
-
)
|
|
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
|
+
);
|
|
2269
2448
|
|
|
2270
2449
|
/**
|
|
2271
2450
|
* Array of a specific {@link Type}.
|
|
2272
2451
|
*
|
|
2273
|
-
*
|
|
2452
|
+
* ### Example
|
|
2274
2453
|
*
|
|
2275
2454
|
* ```ts
|
|
2276
2455
|
* const NumberArray = array(Number);
|
|
@@ -2279,8 +2458,8 @@ export const formatLiteralError = createTypeErrorFormatter<LiteralError>(
|
|
|
2279
2458
|
* const result2 = NumberArray.from(["a", "b"]); // err(...)
|
|
2280
2459
|
* ```
|
|
2281
2460
|
*
|
|
2282
|
-
* @
|
|
2283
|
-
* @
|
|
2461
|
+
* @group Base factories
|
|
2462
|
+
* @group Array
|
|
2284
2463
|
*/
|
|
2285
2464
|
export const array = <ElementType extends AnyType>(
|
|
2286
2465
|
element: ElementType,
|
|
@@ -2351,28 +2530,28 @@ export const array = <ElementType extends AnyType>(
|
|
|
2351
2530
|
};
|
|
2352
2531
|
|
|
2353
2532
|
/** ArrayType extends Type with an additional `element` property for reflection. */
|
|
2354
|
-
export interface ArrayType<ElementType extends AnyType>
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
> {
|
|
2533
|
+
export interface ArrayType<ElementType extends AnyType> extends Type<
|
|
2534
|
+
"Array",
|
|
2535
|
+
ReadonlyArray<InferType<ElementType>>,
|
|
2536
|
+
ReadonlyArray<InferInput<ElementType>>,
|
|
2537
|
+
ArrayError<InferError<ElementType>>,
|
|
2538
|
+
ReadonlyArray<InferParent<ElementType>>,
|
|
2539
|
+
ArrayError<InferParentError<ElementType>>
|
|
2540
|
+
> {
|
|
2363
2541
|
readonly element: ElementType;
|
|
2364
2542
|
}
|
|
2365
2543
|
|
|
2366
|
-
export interface ArrayError<
|
|
2367
|
-
extends
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
|
|
2544
|
+
export interface ArrayError<
|
|
2545
|
+
Error extends TypeError = TypeError,
|
|
2546
|
+
> extends TypeErrorWithReason<
|
|
2547
|
+
"Array",
|
|
2548
|
+
| { readonly kind: "NotArray" }
|
|
2549
|
+
| {
|
|
2550
|
+
readonly kind: "Element";
|
|
2551
|
+
readonly index: number;
|
|
2552
|
+
readonly error: Error;
|
|
2553
|
+
}
|
|
2554
|
+
> {}
|
|
2376
2555
|
|
|
2377
2556
|
export const formatArrayError = <Error extends TypeError>(
|
|
2378
2557
|
formatTypeError: TypeErrorFormatter<Error>,
|
|
@@ -2398,7 +2577,7 @@ export const formatArrayError = <Error extends TypeError>(
|
|
|
2398
2577
|
* const result2 = NumberSet.from(new Set(["a", "b"])); // err(...)
|
|
2399
2578
|
* ```
|
|
2400
2579
|
*
|
|
2401
|
-
* @
|
|
2580
|
+
* @group Base factories
|
|
2402
2581
|
*/
|
|
2403
2582
|
export const set = <ElementType extends AnyType>(
|
|
2404
2583
|
element: ElementType,
|
|
@@ -2469,28 +2648,28 @@ export const set = <ElementType extends AnyType>(
|
|
|
2469
2648
|
};
|
|
2470
2649
|
|
|
2471
2650
|
/** SetType extends Type with an additional `element` property for reflection. */
|
|
2472
|
-
export interface SetType<ElementType extends AnyType>
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
|
|
2478
|
-
|
|
2479
|
-
|
|
2480
|
-
> {
|
|
2651
|
+
export interface SetType<ElementType extends AnyType> extends Type<
|
|
2652
|
+
"Set",
|
|
2653
|
+
ReadonlySet<InferType<ElementType>>,
|
|
2654
|
+
ReadonlySet<InferInput<ElementType>>,
|
|
2655
|
+
SetError<InferError<ElementType>>,
|
|
2656
|
+
ReadonlySet<InferParent<ElementType>>,
|
|
2657
|
+
SetError<InferParentError<ElementType>>
|
|
2658
|
+
> {
|
|
2481
2659
|
readonly element: ElementType;
|
|
2482
2660
|
}
|
|
2483
2661
|
|
|
2484
|
-
export interface SetError<
|
|
2485
|
-
extends
|
|
2486
|
-
|
|
2487
|
-
|
|
2488
|
-
|
|
2489
|
-
|
|
2490
|
-
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
|
|
2662
|
+
export interface SetError<
|
|
2663
|
+
Error extends TypeError = TypeError,
|
|
2664
|
+
> extends TypeErrorWithReason<
|
|
2665
|
+
"Set",
|
|
2666
|
+
| { readonly kind: "NotSet" }
|
|
2667
|
+
| {
|
|
2668
|
+
readonly kind: "Element";
|
|
2669
|
+
readonly index: number;
|
|
2670
|
+
readonly error: Error;
|
|
2671
|
+
}
|
|
2672
|
+
> {}
|
|
2494
2673
|
|
|
2495
2674
|
export const formatSetError = <Error extends TypeError>(
|
|
2496
2675
|
formatTypeError: TypeErrorFormatter<Error>,
|
|
@@ -2528,8 +2707,8 @@ export const formatSetError = <Error extends TypeError>(
|
|
|
2528
2707
|
* StringToNumberRecord.from({ a: "x", b: 2 });
|
|
2529
2708
|
* ```
|
|
2530
2709
|
*
|
|
2531
|
-
* @
|
|
2532
|
-
* @
|
|
2710
|
+
* @group Base factories
|
|
2711
|
+
* @group Object
|
|
2533
2712
|
*/
|
|
2534
2713
|
export const record = <
|
|
2535
2714
|
KeyName extends TypeName,
|
|
@@ -2576,7 +2755,7 @@ export const record = <
|
|
|
2576
2755
|
type: "Record",
|
|
2577
2756
|
value,
|
|
2578
2757
|
reason: { kind: "Key", key: rawKey, error: keyResult.error },
|
|
2579
|
-
} as
|
|
2758
|
+
} as never);
|
|
2580
2759
|
}
|
|
2581
2760
|
|
|
2582
2761
|
const valueResult = valueType.fromUnknown(rawValue);
|
|
@@ -2587,12 +2766,12 @@ export const record = <
|
|
|
2587
2766
|
reason: {
|
|
2588
2767
|
kind: "Value",
|
|
2589
2768
|
key: rawKey,
|
|
2590
|
-
error: valueResult.error as
|
|
2769
|
+
error: valueResult.error as never,
|
|
2591
2770
|
},
|
|
2592
2771
|
});
|
|
2593
2772
|
}
|
|
2594
2773
|
|
|
2595
|
-
result[keyResult.value] = valueResult.value as
|
|
2774
|
+
result[keyResult.value] = valueResult.value as never;
|
|
2596
2775
|
}
|
|
2597
2776
|
|
|
2598
2777
|
return ok(result);
|
|
@@ -2649,7 +2828,7 @@ export const record = <
|
|
|
2649
2828
|
}),
|
|
2650
2829
|
key: keyType,
|
|
2651
2830
|
value: valueType,
|
|
2652
|
-
} as
|
|
2831
|
+
} as never;
|
|
2653
2832
|
};
|
|
2654
2833
|
|
|
2655
2834
|
/**
|
|
@@ -2665,13 +2844,13 @@ export interface RecordType<
|
|
|
2665
2844
|
KeyParentError extends TypeError,
|
|
2666
2845
|
Value extends AnyType = AnyType,
|
|
2667
2846
|
> extends Type<
|
|
2668
|
-
|
|
2669
|
-
|
|
2670
|
-
|
|
2671
|
-
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
|
|
2847
|
+
"Record",
|
|
2848
|
+
Readonly<Record<KeyT, InferType<Value>>>,
|
|
2849
|
+
Readonly<Record<KeyInput, InferInput<Value>>>,
|
|
2850
|
+
RecordError<KeyError, InferError<Value>>,
|
|
2851
|
+
Readonly<Record<KeyParent, InferParent<Value>>>,
|
|
2852
|
+
RecordError<KeyParentError, InferParentError<Value>>
|
|
2853
|
+
> {
|
|
2675
2854
|
readonly key: Type<
|
|
2676
2855
|
KeyName,
|
|
2677
2856
|
KeyT,
|
|
@@ -2687,19 +2866,19 @@ export interface RecordError<
|
|
|
2687
2866
|
KeyError extends TypeError = TypeError,
|
|
2688
2867
|
ValueError extends TypeError = TypeError,
|
|
2689
2868
|
> extends TypeErrorWithReason<
|
|
2690
|
-
|
|
2691
|
-
|
|
2692
|
-
|
|
2693
|
-
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
|
|
2699
|
-
|
|
2700
|
-
|
|
2701
|
-
|
|
2702
|
-
|
|
2869
|
+
"Record",
|
|
2870
|
+
| { readonly kind: "NotRecord" }
|
|
2871
|
+
| {
|
|
2872
|
+
readonly kind: "Key";
|
|
2873
|
+
readonly key: unknown;
|
|
2874
|
+
readonly error: KeyError;
|
|
2875
|
+
}
|
|
2876
|
+
| {
|
|
2877
|
+
readonly kind: "Value";
|
|
2878
|
+
readonly key: unknown;
|
|
2879
|
+
readonly error: ValueError;
|
|
2880
|
+
}
|
|
2881
|
+
> {}
|
|
2703
2882
|
|
|
2704
2883
|
export const formatRecordError = <Error extends TypeError>(
|
|
2705
2884
|
formatTypeError: TypeErrorFormatter<Error>,
|
|
@@ -2733,22 +2912,22 @@ export const formatRecordError = <Error extends TypeError>(
|
|
|
2733
2912
|
*
|
|
2734
2913
|
* https://www.typescriptlang.org/docs/handbook/2/objects.html#index-signatures
|
|
2735
2914
|
*
|
|
2736
|
-
* ###
|
|
2915
|
+
* ### Example
|
|
2737
2916
|
*
|
|
2738
|
-
*
|
|
2917
|
+
* ## Basic Object Validation
|
|
2739
2918
|
*
|
|
2740
2919
|
* ```ts
|
|
2741
2920
|
* const User = object({
|
|
2742
2921
|
* name: NonEmptyTrimmedString,
|
|
2743
2922
|
* age: PositiveNumber,
|
|
2744
2923
|
* });
|
|
2745
|
-
*
|
|
2924
|
+
* interface User extends InferType<typeof User> {}
|
|
2746
2925
|
*
|
|
2747
2926
|
* const result = User.from({ name: "John", age: 30 }); // ok({ name: "John", age: 30 })
|
|
2748
2927
|
* const error = User.from({ name: "John", age: -5 }); // err
|
|
2749
2928
|
* ```
|
|
2750
2929
|
*
|
|
2751
|
-
*
|
|
2930
|
+
* ## Optional Properties
|
|
2752
2931
|
*
|
|
2753
2932
|
* In this example the `age` property is marked as optional using
|
|
2754
2933
|
* {@link optional}.
|
|
@@ -2758,10 +2937,10 @@ export const formatRecordError = <Error extends TypeError>(
|
|
|
2758
2937
|
* name: NonEmptyString, // Required
|
|
2759
2938
|
* age: optional(PositiveNumber), // Optional
|
|
2760
2939
|
* });
|
|
2761
|
-
*
|
|
2940
|
+
* interface User extends InferType<typeof User> {}
|
|
2762
2941
|
* ```
|
|
2763
2942
|
*
|
|
2764
|
-
*
|
|
2943
|
+
* ## Allowing Additional Properties
|
|
2765
2944
|
*
|
|
2766
2945
|
* ```ts
|
|
2767
2946
|
* const UserWithAnyExtraProperties = object(
|
|
@@ -2780,7 +2959,7 @@ export const formatRecordError = <Error extends TypeError>(
|
|
|
2780
2959
|
* });
|
|
2781
2960
|
* ```
|
|
2782
2961
|
*
|
|
2783
|
-
*
|
|
2962
|
+
* ## Combining Fixed and Flexible Properties
|
|
2784
2963
|
*
|
|
2785
2964
|
* ```ts
|
|
2786
2965
|
* const NumberDictionary = object(
|
|
@@ -2817,13 +2996,14 @@ export const formatRecordError = <Error extends TypeError>(
|
|
|
2817
2996
|
* );
|
|
2818
2997
|
* ```
|
|
2819
2998
|
*
|
|
2820
|
-
* @
|
|
2821
|
-
* @
|
|
2999
|
+
* @group Base factories
|
|
3000
|
+
* @group Object
|
|
2822
3001
|
*/
|
|
2823
3002
|
export function object<Props extends Record<string, AnyType>>(
|
|
2824
3003
|
props: Props,
|
|
2825
3004
|
): ObjectType<Props>;
|
|
2826
3005
|
|
|
3006
|
+
/** With additional record properties. */
|
|
2827
3007
|
export function object<
|
|
2828
3008
|
Props extends Record<string, AnyType>,
|
|
2829
3009
|
KeyName extends TypeName,
|
|
@@ -2886,9 +3066,9 @@ export function object(
|
|
|
2886
3066
|
|
|
2887
3067
|
const propResult = props[key].fromUnknown(value[key]);
|
|
2888
3068
|
if (!propResult.ok) {
|
|
2889
|
-
errors[key] = propResult.error as
|
|
3069
|
+
errors[key] = propResult.error as never;
|
|
2890
3070
|
} else {
|
|
2891
|
-
result[key] = propResult.value as
|
|
3071
|
+
result[key] = propResult.value as never;
|
|
2892
3072
|
}
|
|
2893
3073
|
}
|
|
2894
3074
|
|
|
@@ -2906,7 +3086,7 @@ export function object(
|
|
|
2906
3086
|
reason: {
|
|
2907
3087
|
kind: "IndexKey",
|
|
2908
3088
|
key,
|
|
2909
|
-
error: keyResult.error as
|
|
3089
|
+
error: keyResult.error as never,
|
|
2910
3090
|
},
|
|
2911
3091
|
});
|
|
2912
3092
|
}
|
|
@@ -2919,12 +3099,12 @@ export function object(
|
|
|
2919
3099
|
reason: {
|
|
2920
3100
|
kind: "IndexValue",
|
|
2921
3101
|
key,
|
|
2922
|
-
error: valueResult.error as
|
|
3102
|
+
error: valueResult.error as never,
|
|
2923
3103
|
},
|
|
2924
3104
|
});
|
|
2925
3105
|
}
|
|
2926
3106
|
|
|
2927
|
-
result[keyResult.value] = valueResult.value as
|
|
3107
|
+
result[keyResult.value] = valueResult.value as never;
|
|
2928
3108
|
}
|
|
2929
3109
|
} else if (extraKeys.length > 0) {
|
|
2930
3110
|
return err({
|
|
@@ -3011,15 +3191,14 @@ export function object(
|
|
|
3011
3191
|
* ObjectType extends {@link Type} with an additional `props` property for
|
|
3012
3192
|
* reflection.
|
|
3013
3193
|
*/
|
|
3014
|
-
export interface ObjectType<Props extends Record<string, AnyType>>
|
|
3015
|
-
|
|
3016
|
-
|
|
3017
|
-
|
|
3018
|
-
|
|
3019
|
-
|
|
3020
|
-
|
|
3021
|
-
|
|
3022
|
-
> {
|
|
3194
|
+
export interface ObjectType<Props extends Record<string, AnyType>> extends Type<
|
|
3195
|
+
"Object",
|
|
3196
|
+
Readonly<ObjectT<Props>>,
|
|
3197
|
+
Readonly<ObjectInput<Props>>,
|
|
3198
|
+
ObjectError<{ [K in keyof Props]: InferError<Props[K]> }>,
|
|
3199
|
+
Readonly<ObjectParent<Props>>,
|
|
3200
|
+
ObjectError<{ [K in keyof Props]: InferParentError<Props[K]> }>
|
|
3201
|
+
> {
|
|
3023
3202
|
readonly props: Props;
|
|
3024
3203
|
}
|
|
3025
3204
|
|
|
@@ -3065,20 +3244,20 @@ type OptionalKeys<Props extends Record<string, AnyType>> = {
|
|
|
3065
3244
|
export interface ObjectError<
|
|
3066
3245
|
PropsErrors extends Record<string, TypeError> = Record<string, TypeError>,
|
|
3067
3246
|
> extends TypeErrorWithReason<
|
|
3068
|
-
|
|
3069
|
-
|
|
3070
|
-
|
|
3071
|
-
|
|
3072
|
-
|
|
3073
|
-
|
|
3074
|
-
|
|
3075
|
-
|
|
3247
|
+
"Object",
|
|
3248
|
+
| { readonly kind: "NotObject" }
|
|
3249
|
+
| {
|
|
3250
|
+
readonly kind: "Props";
|
|
3251
|
+
readonly errors: Partial<PropsErrors>;
|
|
3252
|
+
}
|
|
3253
|
+
| { readonly kind: "ExtraKeys"; readonly extraKeys: ReadonlyArray<string> }
|
|
3254
|
+
> {}
|
|
3076
3255
|
|
|
3077
3256
|
/**
|
|
3078
3257
|
* Merge Error and ParentError into one ObjectError so tooltips and error
|
|
3079
3258
|
* messages are easier to read.
|
|
3080
3259
|
*
|
|
3081
|
-
* @
|
|
3260
|
+
* @group Utilities
|
|
3082
3261
|
*/
|
|
3083
3262
|
export type MergeObjectTypeErrors<T extends ObjectType<any>> =
|
|
3084
3263
|
T extends ObjectType<infer Props>
|
|
@@ -3118,23 +3297,22 @@ export interface ObjectWithRecordType<
|
|
|
3118
3297
|
KeyParentError extends TypeError,
|
|
3119
3298
|
Value extends AnyType,
|
|
3120
3299
|
> extends Type<
|
|
3121
|
-
|
|
3122
|
-
|
|
3123
|
-
|
|
3124
|
-
|
|
3125
|
-
|
|
3126
|
-
|
|
3127
|
-
|
|
3128
|
-
|
|
3129
|
-
|
|
3130
|
-
Readonly<
|
|
3131
|
-
|
|
3132
|
-
|
|
3133
|
-
|
|
3134
|
-
|
|
3135
|
-
|
|
3136
|
-
|
|
3137
|
-
> {
|
|
3300
|
+
"ObjectWithRecord",
|
|
3301
|
+
Readonly<ObjectT<Props>> & Readonly<Record<KeyT, InferType<Value>>>,
|
|
3302
|
+
Readonly<ObjectInput<Props>> & Readonly<Record<KeyInput, InferInput<Value>>>,
|
|
3303
|
+
ObjectWithRecordError<
|
|
3304
|
+
{ [K in keyof Props]: InferError<Props[K]> },
|
|
3305
|
+
KeyError,
|
|
3306
|
+
InferError<Value>
|
|
3307
|
+
>,
|
|
3308
|
+
Readonly<ObjectParent<Props>> &
|
|
3309
|
+
Readonly<Record<KeyParent, InferParent<Value>>>,
|
|
3310
|
+
ObjectWithRecordError<
|
|
3311
|
+
{ [K in keyof Props]: InferParentError<Props[K]> },
|
|
3312
|
+
KeyParentError,
|
|
3313
|
+
InferParentError<Value>
|
|
3314
|
+
>
|
|
3315
|
+
> {
|
|
3138
3316
|
readonly props: Props;
|
|
3139
3317
|
readonly record: RecordType<
|
|
3140
3318
|
KeyName,
|
|
@@ -3152,20 +3330,20 @@ export interface ObjectWithRecordError<
|
|
|
3152
3330
|
KeyError extends TypeError = TypeError,
|
|
3153
3331
|
ValueError extends TypeError = TypeError,
|
|
3154
3332
|
> extends TypeErrorWithReason<
|
|
3155
|
-
|
|
3156
|
-
|
|
3157
|
-
|
|
3158
|
-
|
|
3159
|
-
|
|
3160
|
-
|
|
3161
|
-
|
|
3162
|
-
|
|
3163
|
-
|
|
3164
|
-
|
|
3165
|
-
|
|
3166
|
-
|
|
3167
|
-
|
|
3168
|
-
|
|
3333
|
+
"ObjectWithRecord",
|
|
3334
|
+
| { readonly kind: "NotObject" }
|
|
3335
|
+
| { readonly kind: "Props"; readonly errors: Partial<PropsErrors> }
|
|
3336
|
+
| {
|
|
3337
|
+
readonly kind: "IndexKey";
|
|
3338
|
+
readonly key: unknown;
|
|
3339
|
+
readonly error: KeyError;
|
|
3340
|
+
}
|
|
3341
|
+
| {
|
|
3342
|
+
readonly kind: "IndexValue";
|
|
3343
|
+
readonly key: string;
|
|
3344
|
+
readonly error: ValueError;
|
|
3345
|
+
}
|
|
3346
|
+
> {}
|
|
3169
3347
|
|
|
3170
3348
|
export const formatObjectWithRecordError = <Error extends TypeError>(
|
|
3171
3349
|
formatTypeError: TypeErrorFormatter<Error>,
|
|
@@ -3189,6 +3367,187 @@ export const formatObjectWithRecordError = <Error extends TypeError>(
|
|
|
3189
3367
|
}
|
|
3190
3368
|
});
|
|
3191
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
|
+
|
|
3192
3551
|
/**
|
|
3193
3552
|
* Union {@link Type}.
|
|
3194
3553
|
*
|
|
@@ -3211,12 +3570,13 @@ export const formatObjectWithRecordError = <Error extends TypeError>(
|
|
|
3211
3570
|
* const result3 = StringOrNumber.from(42); // ok(42)
|
|
3212
3571
|
* ```
|
|
3213
3572
|
*
|
|
3214
|
-
* @
|
|
3573
|
+
* @group Base factories
|
|
3215
3574
|
*/
|
|
3216
3575
|
export function union<
|
|
3217
3576
|
Members extends [AnyType, AnyType, ...ReadonlyArray<AnyType>],
|
|
3218
3577
|
>(...members: Members): UnionType<Members>;
|
|
3219
3578
|
|
|
3579
|
+
/** With literal values. */
|
|
3220
3580
|
export function union<
|
|
3221
3581
|
Literals extends [Literal, Literal, ...ReadonlyArray<Literal>],
|
|
3222
3582
|
>(
|
|
@@ -3271,20 +3631,22 @@ export function union(...args: ReadonlyArray<any>): any {
|
|
|
3271
3631
|
* UnionType extends {@link Type} with an additional `members` property for
|
|
3272
3632
|
* reflection.
|
|
3273
3633
|
*/
|
|
3274
|
-
export interface UnionType<
|
|
3275
|
-
extends
|
|
3276
|
-
|
|
3277
|
-
|
|
3278
|
-
|
|
3279
|
-
|
|
3280
|
-
|
|
3281
|
-
|
|
3282
|
-
|
|
3634
|
+
export interface UnionType<
|
|
3635
|
+
Members extends [AnyType, ...ReadonlyArray<AnyType>],
|
|
3636
|
+
> extends Type<
|
|
3637
|
+
"Union",
|
|
3638
|
+
InferType<Members[number]>,
|
|
3639
|
+
InferInput<Members[number]>,
|
|
3640
|
+
UnionError<InferErrors<Members[number]>>,
|
|
3641
|
+
InferInput<Members[number]>,
|
|
3642
|
+
never
|
|
3643
|
+
> {
|
|
3283
3644
|
readonly members: Members;
|
|
3284
3645
|
}
|
|
3285
3646
|
|
|
3286
|
-
export interface UnionError<
|
|
3287
|
-
extends TypeError
|
|
3647
|
+
export interface UnionError<
|
|
3648
|
+
E extends TypeError = TypeError,
|
|
3649
|
+
> extends TypeError<"Union"> {
|
|
3288
3650
|
readonly errors: Array<E>;
|
|
3289
3651
|
}
|
|
3290
3652
|
|
|
@@ -3304,6 +3666,110 @@ export const isUnionType = (
|
|
|
3304
3666
|
): t is UnionType<[AnyType, ...ReadonlyArray<AnyType>]> =>
|
|
3305
3667
|
t.name === "Union" && Array.isArray((t as { members?: unknown }).members);
|
|
3306
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
|
+
|
|
3307
3773
|
/**
|
|
3308
3774
|
* Recursive {@link Type}.
|
|
3309
3775
|
*
|
|
@@ -3337,7 +3803,7 @@ export const isUnionType = (
|
|
|
3337
3803
|
* );
|
|
3338
3804
|
* ```
|
|
3339
3805
|
*
|
|
3340
|
-
* @
|
|
3806
|
+
* @group Base factories
|
|
3341
3807
|
*/
|
|
3342
3808
|
export const recursive = <ParentType extends AnyType>(
|
|
3343
3809
|
create: () => ParentType,
|
|
@@ -3374,15 +3840,14 @@ export const recursive = <ParentType extends AnyType>(
|
|
|
3374
3840
|
} as RecursiveType<ParentType>;
|
|
3375
3841
|
};
|
|
3376
3842
|
|
|
3377
|
-
export interface RecursiveType<ParentType extends AnyType>
|
|
3378
|
-
|
|
3379
|
-
|
|
3380
|
-
|
|
3381
|
-
|
|
3382
|
-
|
|
3383
|
-
|
|
3384
|
-
|
|
3385
|
-
> {
|
|
3843
|
+
export interface RecursiveType<ParentType extends AnyType> extends Type<
|
|
3844
|
+
"Recursive",
|
|
3845
|
+
InferType<ParentType>,
|
|
3846
|
+
InferInput<ParentType>,
|
|
3847
|
+
InferError<ParentType>,
|
|
3848
|
+
InferParent<ParentType>,
|
|
3849
|
+
InferParentError<ParentType>
|
|
3850
|
+
> {
|
|
3386
3851
|
getParentType(): ParentType;
|
|
3387
3852
|
}
|
|
3388
3853
|
|
|
@@ -3398,7 +3863,7 @@ export interface RecursiveType<ParentType extends AnyType>
|
|
|
3398
3863
|
* NullOrString.from(42); // err(...)
|
|
3399
3864
|
* ```
|
|
3400
3865
|
*
|
|
3401
|
-
* @
|
|
3866
|
+
* @group Base factories
|
|
3402
3867
|
*/
|
|
3403
3868
|
export const nullOr = <T extends AnyType>(
|
|
3404
3869
|
type: T,
|
|
@@ -3416,7 +3881,7 @@ export const nullOr = <T extends AnyType>(
|
|
|
3416
3881
|
* UndefinedOrString.from(42); // err(...)
|
|
3417
3882
|
* ```
|
|
3418
3883
|
*
|
|
3419
|
-
* @
|
|
3884
|
+
* @group Base factories
|
|
3420
3885
|
*/
|
|
3421
3886
|
export const undefinedOr = <T extends AnyType>(
|
|
3422
3887
|
type: T,
|
|
@@ -3438,7 +3903,7 @@ export const undefinedOr = <T extends AnyType>(
|
|
|
3438
3903
|
* NullishOrString.from(42); // err(...)
|
|
3439
3904
|
* ```
|
|
3440
3905
|
*
|
|
3441
|
-
* @
|
|
3906
|
+
* @group Base factories
|
|
3442
3907
|
*/
|
|
3443
3908
|
export const nullishOr = <T extends AnyType>(
|
|
3444
3909
|
type: T,
|
|
@@ -3459,7 +3924,7 @@ export const nullishOr = <T extends AnyType>(
|
|
|
3459
3924
|
* const error = NameAndAge.from(["Alice", -10]); // err
|
|
3460
3925
|
* ```
|
|
3461
3926
|
*
|
|
3462
|
-
* @
|
|
3927
|
+
* @group Base factories
|
|
3463
3928
|
*/
|
|
3464
3929
|
export const tuple = <Elements extends [AnyType, ...ReadonlyArray<AnyType>]>(
|
|
3465
3930
|
...elements: Elements
|
|
@@ -3489,7 +3954,7 @@ export const tuple = <Elements extends [AnyType, ...ReadonlyArray<AnyType>]>(
|
|
|
3489
3954
|
reason: {
|
|
3490
3955
|
kind: "Element",
|
|
3491
3956
|
index: i,
|
|
3492
|
-
error: elementResult.error as
|
|
3957
|
+
error: elementResult.error as never,
|
|
3493
3958
|
},
|
|
3494
3959
|
});
|
|
3495
3960
|
}
|
|
@@ -3520,7 +3985,7 @@ export const tuple = <Elements extends [AnyType, ...ReadonlyArray<AnyType>]>(
|
|
|
3520
3985
|
reason: {
|
|
3521
3986
|
kind: "Element",
|
|
3522
3987
|
index: i,
|
|
3523
|
-
error: elementResult.error as
|
|
3988
|
+
error: elementResult.error as never,
|
|
3524
3989
|
},
|
|
3525
3990
|
});
|
|
3526
3991
|
}
|
|
@@ -3536,7 +4001,7 @@ export const tuple = <Elements extends [AnyType, ...ReadonlyArray<AnyType>]>(
|
|
|
3536
4001
|
fromParent,
|
|
3537
4002
|
}),
|
|
3538
4003
|
elements,
|
|
3539
|
-
} as
|
|
4004
|
+
} as never;
|
|
3540
4005
|
};
|
|
3541
4006
|
|
|
3542
4007
|
/**
|
|
@@ -3546,26 +4011,27 @@ export const tuple = <Elements extends [AnyType, ...ReadonlyArray<AnyType>]>(
|
|
|
3546
4011
|
export interface TupleType<
|
|
3547
4012
|
Elements extends readonly [AnyType, ...ReadonlyArray<AnyType>],
|
|
3548
4013
|
> extends Type<
|
|
3549
|
-
|
|
3550
|
-
|
|
3551
|
-
|
|
3552
|
-
|
|
3553
|
-
|
|
3554
|
-
|
|
3555
|
-
|
|
4014
|
+
"Tuple",
|
|
4015
|
+
readonly [...{ [K in keyof Elements]: InferType<Elements[K]> }],
|
|
4016
|
+
readonly [...{ [K in keyof Elements]: InferInput<Elements[K]> }],
|
|
4017
|
+
TupleError<{ [K in keyof Elements]: InferError<Elements[K]> }[number]>,
|
|
4018
|
+
readonly [...{ [K in keyof Elements]: InferParent<Elements[K]> }],
|
|
4019
|
+
TupleError<{ [K in keyof Elements]: InferParentError<Elements[K]> }[number]>
|
|
4020
|
+
> {
|
|
3556
4021
|
readonly elements: Elements;
|
|
3557
4022
|
}
|
|
3558
4023
|
|
|
3559
|
-
export interface TupleError<
|
|
3560
|
-
extends
|
|
3561
|
-
|
|
3562
|
-
|
|
3563
|
-
|
|
3564
|
-
|
|
3565
|
-
|
|
3566
|
-
|
|
3567
|
-
|
|
3568
|
-
|
|
4024
|
+
export interface TupleError<
|
|
4025
|
+
ElementError extends TypeError = TypeError,
|
|
4026
|
+
> extends TypeErrorWithReason<
|
|
4027
|
+
"Tuple",
|
|
4028
|
+
| { readonly kind: "InvalidLength"; readonly expected: number }
|
|
4029
|
+
| {
|
|
4030
|
+
readonly kind: "Element";
|
|
4031
|
+
readonly index: number;
|
|
4032
|
+
readonly error: ElementError;
|
|
4033
|
+
}
|
|
4034
|
+
> {}
|
|
3569
4035
|
|
|
3570
4036
|
export const formatTupleError = <Error extends TypeError>(
|
|
3571
4037
|
formatTypeError: TypeErrorFormatter<Error>,
|
|
@@ -3594,9 +4060,9 @@ export const formatTupleError = <Error extends TypeError>(
|
|
|
3594
4060
|
*
|
|
3595
4061
|
* https://www.sqlite.org/c3ref/int64.html
|
|
3596
4062
|
*
|
|
3597
|
-
* @
|
|
4063
|
+
* @group Number
|
|
3598
4064
|
*/
|
|
3599
|
-
export const Int64 = brand("Int64", BigInt, (value) =>
|
|
4065
|
+
export const Int64 = /*#__PURE__*/ brand("Int64", BigInt, (value) =>
|
|
3600
4066
|
value >= -9223372036854775808n && value <= 9223372036854775807n
|
|
3601
4067
|
? ok(value)
|
|
3602
4068
|
: err<Int64Error>({ type: "Int64", value }),
|
|
@@ -3604,25 +4070,29 @@ export const Int64 = brand("Int64", BigInt, (value) =>
|
|
|
3604
4070
|
export type Int64 = typeof Int64.Type;
|
|
3605
4071
|
export interface Int64Error extends TypeError<"Int64"> {}
|
|
3606
4072
|
|
|
3607
|
-
export const formatInt64Error =
|
|
3608
|
-
(
|
|
3609
|
-
|
|
3610
|
-
)
|
|
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
|
+
);
|
|
3611
4078
|
|
|
3612
4079
|
/**
|
|
3613
4080
|
* Stringified {@link Int64}.
|
|
3614
4081
|
*
|
|
3615
|
-
* @
|
|
4082
|
+
* @group String
|
|
3616
4083
|
*/
|
|
3617
|
-
export const Int64String = brand(
|
|
3618
|
-
|
|
3619
|
-
|
|
3620
|
-
|
|
3621
|
-
|
|
3622
|
-
|
|
3623
|
-
|
|
3624
|
-
|
|
3625
|
-
|
|
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
|
+
),
|
|
3626
4096
|
);
|
|
3627
4097
|
|
|
3628
4098
|
export type Int64String = typeof Int64String.Type;
|
|
@@ -3630,10 +4100,20 @@ export type Int64String = typeof Int64String.Type;
|
|
|
3630
4100
|
export interface Int64StringError extends TypeError<"Int64String"> {}
|
|
3631
4101
|
|
|
3632
4102
|
export const formatInt64StringError =
|
|
3633
|
-
createTypeErrorFormatter<Int64StringError>(
|
|
4103
|
+
/*#__PURE__*/ createTypeErrorFormatter<Int64StringError>(
|
|
3634
4104
|
(error) => `The value ${error.value} is not a valid Int64 string.`,
|
|
3635
4105
|
);
|
|
3636
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
|
+
*/
|
|
3637
4117
|
export type JsonValue =
|
|
3638
4118
|
| string
|
|
3639
4119
|
| FiniteNumber
|
|
@@ -3642,6 +4122,19 @@ export type JsonValue =
|
|
|
3642
4122
|
| JsonArray
|
|
3643
4123
|
| JsonObject;
|
|
3644
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
|
+
*/
|
|
3645
4138
|
export type JsonValueInput =
|
|
3646
4139
|
| string
|
|
3647
4140
|
| number
|
|
@@ -3675,9 +4168,9 @@ export type JsonArrayInput = ReadonlyArray<JsonValueInput>;
|
|
|
3675
4168
|
* JSON-compatible value: string, {@link FiniteNumber}, boolean, null,
|
|
3676
4169
|
* {@link JsonArray}, or {@link JsonObject}.
|
|
3677
4170
|
*
|
|
3678
|
-
* @
|
|
4171
|
+
* @group Base Types
|
|
3679
4172
|
*/
|
|
3680
|
-
export const JsonValue = recursive(
|
|
4173
|
+
export const JsonValue = /*#__PURE__*/ recursive(
|
|
3681
4174
|
(): UnionType<
|
|
3682
4175
|
[
|
|
3683
4176
|
typeof String,
|
|
@@ -3717,16 +4210,16 @@ export const JsonValue = recursive(
|
|
|
3717
4210
|
/**
|
|
3718
4211
|
* JSON-compatible array of {@link JsonValue} elements.
|
|
3719
4212
|
*
|
|
3720
|
-
* @
|
|
4213
|
+
* @group Array
|
|
3721
4214
|
*/
|
|
3722
|
-
export const JsonArray = array(JsonValue);
|
|
4215
|
+
export const JsonArray = /*#__PURE__*/ array(JsonValue);
|
|
3723
4216
|
|
|
3724
4217
|
/**
|
|
3725
4218
|
* JSON-compatible object with string keys and {@link JsonValue} values.
|
|
3726
4219
|
*
|
|
3727
|
-
* @
|
|
4220
|
+
* @group Object
|
|
3728
4221
|
*/
|
|
3729
|
-
export const JsonObject = record(String, JsonValue);
|
|
4222
|
+
export const JsonObject = /*#__PURE__*/ record(String, JsonValue);
|
|
3730
4223
|
|
|
3731
4224
|
export const parseJson = (value: string): Result<JsonValue, JsonError> =>
|
|
3732
4225
|
trySync(
|
|
@@ -3748,9 +4241,9 @@ export const parseJson = (value: string): Result<JsonValue, JsonError> =>
|
|
|
3748
4241
|
* const error = Json.from("invalid json"); // err
|
|
3749
4242
|
* ```
|
|
3750
4243
|
*
|
|
3751
|
-
* @
|
|
4244
|
+
* @group String
|
|
3752
4245
|
*/
|
|
3753
|
-
export const Json = brand("Json", String, (value) => {
|
|
4246
|
+
export const Json = /*#__PURE__*/ brand("Json", String, (value) => {
|
|
3754
4247
|
const result = parseJson(value);
|
|
3755
4248
|
if (!result.ok) return result;
|
|
3756
4249
|
return ok(value);
|
|
@@ -3762,9 +4255,10 @@ export interface JsonError extends TypeError<"Json"> {
|
|
|
3762
4255
|
readonly message: string;
|
|
3763
4256
|
}
|
|
3764
4257
|
|
|
3765
|
-
export const formatJsonError =
|
|
3766
|
-
(
|
|
3767
|
-
)
|
|
4258
|
+
export const formatJsonError =
|
|
4259
|
+
/*#__PURE__*/ createTypeErrorFormatter<JsonError>(
|
|
4260
|
+
(error) => `Invalid JSON: ${error.value}. Error: ${error.message}`,
|
|
4261
|
+
);
|
|
3768
4262
|
|
|
3769
4263
|
export const jsonValueToJson = (value: JsonValue): Json =>
|
|
3770
4264
|
JSON.stringify(value) as Json;
|
|
@@ -3792,7 +4286,7 @@ export const jsonToJsonValue = (value: Json): JsonValue =>
|
|
|
3792
4286
|
* name: NonEmptyString100,
|
|
3793
4287
|
* age: FiniteNumber,
|
|
3794
4288
|
* });
|
|
3795
|
-
*
|
|
4289
|
+
* interface Person extends InferType<typeof Person> {}
|
|
3796
4290
|
*
|
|
3797
4291
|
* const [PersonJson, personToPersonJson, personJsonToPerson] = json(
|
|
3798
4292
|
* Person,
|
|
@@ -3833,11 +4327,7 @@ export const json = <T extends AnyType, Name extends TypeName>(
|
|
|
3833
4327
|
return ok(value);
|
|
3834
4328
|
}) as BrandType<typeof String, Name, JsonError | InferErrors<T>, StringError>;
|
|
3835
4329
|
|
|
3836
|
-
return [
|
|
3837
|
-
BrandedJsonType,
|
|
3838
|
-
jsonValueToJson as IntentionalNever,
|
|
3839
|
-
jsonToJsonValue as IntentionalNever,
|
|
3840
|
-
];
|
|
4330
|
+
return [BrandedJsonType, jsonValueToJson as never, jsonToJsonValue as never];
|
|
3841
4331
|
};
|
|
3842
4332
|
|
|
3843
4333
|
/**
|
|
@@ -3869,15 +4359,14 @@ export const optional = <T extends AnyType>(type: T): OptionalType<T> => ({
|
|
|
3869
4359
|
parent: type,
|
|
3870
4360
|
});
|
|
3871
4361
|
|
|
3872
|
-
export interface OptionalType<T extends AnyType>
|
|
3873
|
-
|
|
3874
|
-
|
|
3875
|
-
|
|
3876
|
-
|
|
3877
|
-
|
|
3878
|
-
|
|
3879
|
-
|
|
3880
|
-
> {
|
|
4362
|
+
export interface OptionalType<T extends AnyType> extends Type<
|
|
4363
|
+
"Optional",
|
|
4364
|
+
InferType<T>,
|
|
4365
|
+
InferInput<T>,
|
|
4366
|
+
InferError<T>,
|
|
4367
|
+
InferParent<T>,
|
|
4368
|
+
InferParentError<T>
|
|
4369
|
+
> {
|
|
3881
4370
|
readonly parent: T;
|
|
3882
4371
|
}
|
|
3883
4372
|
|
|
@@ -3910,7 +4399,7 @@ export const isOptionalType = (x: unknown): x is OptionalType<any> =>
|
|
|
3910
4399
|
* PartialUser.from({ age: -5 });
|
|
3911
4400
|
* ```
|
|
3912
4401
|
*
|
|
3913
|
-
* @
|
|
4402
|
+
* @group Object
|
|
3914
4403
|
*/
|
|
3915
4404
|
export const partial = <Props extends Record<string, AnyType>>(
|
|
3916
4405
|
props: Props,
|
|
@@ -3930,7 +4419,7 @@ export const partial = <Props extends Record<string, AnyType>>(
|
|
|
3930
4419
|
* entirely, or set it to `null`, or set it to the non-null member of the
|
|
3931
4420
|
* union.
|
|
3932
4421
|
*
|
|
3933
|
-
* @
|
|
4422
|
+
* @group Object
|
|
3934
4423
|
*/
|
|
3935
4424
|
export const nullableToOptional = <Props extends Record<string, AnyType>>(
|
|
3936
4425
|
props: Props,
|
|
@@ -3976,12 +4465,12 @@ export type NullTypeInMembers<Members extends [AnyType, ...Array<AnyType>]> =
|
|
|
3976
4465
|
/**
|
|
3977
4466
|
* Create a new `object` {@link Type} by omitting some keys.
|
|
3978
4467
|
*
|
|
3979
|
-
* @
|
|
4468
|
+
* @group Object
|
|
3980
4469
|
*/
|
|
3981
|
-
export
|
|
4470
|
+
export const omit = <T extends ObjectType<any>, Keys extends keyof T["props"]>(
|
|
3982
4471
|
objectType: T,
|
|
3983
4472
|
...keys: ReadonlyArray<Keys>
|
|
3984
|
-
): ObjectType<Omit<T["props"], Keys>> {
|
|
4473
|
+
): ObjectType<Omit<T["props"], Keys>> => {
|
|
3985
4474
|
const newProps = {} as Omit<T["props"], Keys>;
|
|
3986
4475
|
|
|
3987
4476
|
for (const key in objectType.props) {
|
|
@@ -3991,41 +4480,7 @@ export function omit<T extends ObjectType<any>, Keys extends keyof T["props"]>(
|
|
|
3991
4480
|
}
|
|
3992
4481
|
}
|
|
3993
4482
|
return object(newProps);
|
|
3994
|
-
}
|
|
3995
|
-
|
|
3996
|
-
export const maxMutationSize = 655360;
|
|
3997
|
-
|
|
3998
|
-
/**
|
|
3999
|
-
* Evolu has to limit the maximum mutation size. Otherwise, sync couldn't use
|
|
4000
|
-
* the `maxProtocolMessageRangesSize`. The max size is 640KB in bytes, measured
|
|
4001
|
-
* via MessagePack. Evolu Protocol DbChange will be smaller thanks to various
|
|
4002
|
-
* optimizations.
|
|
4003
|
-
*/
|
|
4004
|
-
export const validMutationSize = <T extends AnyType>(
|
|
4005
|
-
type: T,
|
|
4006
|
-
): BrandType<T, "ValidMutationSize", ValidMutationSizeError, InferErrors<T>> =>
|
|
4007
|
-
brand("ValidMutationSize", type, (value) =>
|
|
4008
|
-
pack(value).byteLength <= maxMutationSize
|
|
4009
|
-
? ok(value)
|
|
4010
|
-
: err<ValidMutationSizeError>({ type: "ValidMutationSize", value }),
|
|
4011
|
-
);
|
|
4012
|
-
|
|
4013
|
-
export interface ValidMutationSizeError
|
|
4014
|
-
extends TypeError<"ValidMutationSize"> {}
|
|
4015
|
-
|
|
4016
|
-
export const formatValidMutationSizeError =
|
|
4017
|
-
createTypeErrorFormatter<ValidMutationSizeError>(
|
|
4018
|
-
(error) =>
|
|
4019
|
-
`The mutation size exceeds the maximum limit of ${maxMutationSize} bytes. The provided mutation has a size of ${pack(error.value).byteLength} bytes.`,
|
|
4020
|
-
);
|
|
4021
|
-
|
|
4022
|
-
export type ValidMutationSize<Props extends Record<string, AnyType>> =
|
|
4023
|
-
BrandType<
|
|
4024
|
-
ObjectType<Props>,
|
|
4025
|
-
"ValidMutationSize",
|
|
4026
|
-
ValidMutationSizeError,
|
|
4027
|
-
InferErrors<ObjectType<Props>>
|
|
4028
|
-
>;
|
|
4483
|
+
};
|
|
4029
4484
|
|
|
4030
4485
|
/**
|
|
4031
4486
|
* Union of all `TypeError`s defined in the `Type.ts` file, including base type
|
|
@@ -4038,7 +4493,7 @@ export type ValidMutationSize<Props extends Record<string, AnyType>> =
|
|
|
4038
4493
|
* Used by {@link createFormatTypeError} to generate human-readable error
|
|
4039
4494
|
* messages.
|
|
4040
4495
|
*
|
|
4041
|
-
* @
|
|
4496
|
+
* @group Utilities
|
|
4042
4497
|
*/
|
|
4043
4498
|
export type TypeErrors<ExtraErrors extends TypeError = never> =
|
|
4044
4499
|
| StringError
|
|
@@ -4079,7 +4534,6 @@ export type TypeErrors<ExtraErrors extends TypeError = never> =
|
|
|
4079
4534
|
| Int64Error
|
|
4080
4535
|
| Int64StringError
|
|
4081
4536
|
| JsonError
|
|
4082
|
-
| ValidMutationSizeError
|
|
4083
4537
|
| ExtraErrors
|
|
4084
4538
|
// Composite errors
|
|
4085
4539
|
| ArrayError<TypeErrors<ExtraErrors>>
|
|
@@ -4172,7 +4626,7 @@ export type TypeErrors<ExtraErrors extends TypeError = never> =
|
|
|
4172
4626
|
* };
|
|
4173
4627
|
* ```
|
|
4174
4628
|
*
|
|
4175
|
-
* @
|
|
4629
|
+
* @group Utilities
|
|
4176
4630
|
*/
|
|
4177
4631
|
export const createFormatTypeError = <ExtraErrors extends TypeError = never>(
|
|
4178
4632
|
extraFormatter?: TypeErrorFormatter<ExtraErrors>,
|
|
@@ -4261,8 +4715,6 @@ export const createFormatTypeError = <ExtraErrors extends TypeError = never>(
|
|
|
4261
4715
|
return formatInt64StringError(error);
|
|
4262
4716
|
case "Json":
|
|
4263
4717
|
return formatJsonError(error);
|
|
4264
|
-
case "ValidMutationSize":
|
|
4265
|
-
return formatValidMutationSizeError(error);
|
|
4266
4718
|
// Composite Types
|
|
4267
4719
|
case "SimplePassword":
|
|
4268
4720
|
return formatSimplePasswordError(formatTypeError)(error);
|
|
@@ -4295,25 +4747,25 @@ export const createFormatTypeError = <ExtraErrors extends TypeError = never>(
|
|
|
4295
4747
|
* Converts an Evolu {@link TypeError} to Standard Schema V1 issues format.
|
|
4296
4748
|
*
|
|
4297
4749
|
* This function recursively converts Evolu's typed errors into the Standard
|
|
4298
|
-
* 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.
|
|
4299
4753
|
*
|
|
4300
|
-
* @
|
|
4754
|
+
* @group Utilities
|
|
4301
4755
|
*/
|
|
4302
4756
|
export const typeErrorToStandardSchemaIssues = <
|
|
4303
4757
|
ExtraErrors extends TypeError = never,
|
|
4304
4758
|
>(
|
|
4305
4759
|
error: TypeErrors<ExtraErrors>,
|
|
4306
|
-
formatTypeError: TypeErrorFormatter<TypeErrors<ExtraErrors>>,
|
|
4307
4760
|
path: ReadonlyArray<PropertyKey> = [],
|
|
4308
4761
|
): ReadonlyArray<StandardSchemaV1.Issue> => {
|
|
4309
4762
|
if (error.type === "Array") {
|
|
4310
4763
|
const arrayError = error as ArrayError;
|
|
4311
4764
|
if (arrayError.reason.kind === "NotArray") {
|
|
4312
|
-
return [{ message:
|
|
4765
|
+
return [{ message: JSON.stringify(error), path }];
|
|
4313
4766
|
}
|
|
4314
4767
|
return typeErrorToStandardSchemaIssues(
|
|
4315
4768
|
arrayError.reason.error as TypeErrors<ExtraErrors>,
|
|
4316
|
-
formatTypeError,
|
|
4317
4769
|
[...path, arrayError.reason.index],
|
|
4318
4770
|
);
|
|
4319
4771
|
}
|
|
@@ -4321,11 +4773,10 @@ export const typeErrorToStandardSchemaIssues = <
|
|
|
4321
4773
|
if (error.type === "Set") {
|
|
4322
4774
|
const setError = error as SetError;
|
|
4323
4775
|
if (setError.reason.kind === "NotSet") {
|
|
4324
|
-
return [{ message:
|
|
4776
|
+
return [{ message: JSON.stringify(error), path }];
|
|
4325
4777
|
}
|
|
4326
4778
|
return typeErrorToStandardSchemaIssues(
|
|
4327
4779
|
setError.reason.error as TypeErrors<ExtraErrors>,
|
|
4328
|
-
formatTypeError,
|
|
4329
4780
|
[...path, setError.reason.index],
|
|
4330
4781
|
);
|
|
4331
4782
|
}
|
|
@@ -4336,14 +4787,13 @@ export const typeErrorToStandardSchemaIssues = <
|
|
|
4336
4787
|
objectError.reason.kind === "NotObject" ||
|
|
4337
4788
|
objectError.reason.kind === "ExtraKeys"
|
|
4338
4789
|
) {
|
|
4339
|
-
return [{ message:
|
|
4790
|
+
return [{ message: JSON.stringify(error), path }];
|
|
4340
4791
|
}
|
|
4341
4792
|
const issues: Array<StandardSchemaV1.Issue> = [];
|
|
4342
4793
|
for (const [key, propError] of Object.entries(objectError.reason.errors)) {
|
|
4343
4794
|
issues.push(
|
|
4344
4795
|
...typeErrorToStandardSchemaIssues(
|
|
4345
4796
|
propError as TypeErrors<ExtraErrors>,
|
|
4346
|
-
formatTypeError,
|
|
4347
4797
|
[...path, key],
|
|
4348
4798
|
),
|
|
4349
4799
|
);
|
|
@@ -4354,7 +4804,7 @@ export const typeErrorToStandardSchemaIssues = <
|
|
|
4354
4804
|
if (error.type === "ObjectWithRecord") {
|
|
4355
4805
|
const objectWithRecordError = error as ObjectWithRecordError;
|
|
4356
4806
|
if (objectWithRecordError.reason.kind === "NotObject") {
|
|
4357
|
-
return [{ message:
|
|
4807
|
+
return [{ message: JSON.stringify(error), path }];
|
|
4358
4808
|
}
|
|
4359
4809
|
if (
|
|
4360
4810
|
objectWithRecordError.reason.kind === "IndexKey" ||
|
|
@@ -4362,7 +4812,6 @@ export const typeErrorToStandardSchemaIssues = <
|
|
|
4362
4812
|
) {
|
|
4363
4813
|
return typeErrorToStandardSchemaIssues(
|
|
4364
4814
|
objectWithRecordError.reason.error as TypeErrors<ExtraErrors>,
|
|
4365
|
-
formatTypeError,
|
|
4366
4815
|
[...path, objectWithRecordError.reason.key as PropertyKey],
|
|
4367
4816
|
);
|
|
4368
4817
|
}
|
|
@@ -4373,7 +4822,6 @@ export const typeErrorToStandardSchemaIssues = <
|
|
|
4373
4822
|
issues.push(
|
|
4374
4823
|
...typeErrorToStandardSchemaIssues(
|
|
4375
4824
|
propError as TypeErrors<ExtraErrors>,
|
|
4376
|
-
formatTypeError,
|
|
4377
4825
|
[...path, key],
|
|
4378
4826
|
),
|
|
4379
4827
|
);
|
|
@@ -4384,11 +4832,10 @@ export const typeErrorToStandardSchemaIssues = <
|
|
|
4384
4832
|
if (error.type === "Record") {
|
|
4385
4833
|
const recordError = error as RecordError;
|
|
4386
4834
|
if (recordError.reason.kind === "NotRecord") {
|
|
4387
|
-
return [{ message:
|
|
4835
|
+
return [{ message: JSON.stringify(error), path }];
|
|
4388
4836
|
}
|
|
4389
4837
|
return typeErrorToStandardSchemaIssues(
|
|
4390
4838
|
recordError.reason.error as TypeErrors<ExtraErrors>,
|
|
4391
|
-
formatTypeError,
|
|
4392
4839
|
[...path, recordError.reason.key as PropertyKey],
|
|
4393
4840
|
);
|
|
4394
4841
|
}
|
|
@@ -4396,11 +4843,10 @@ export const typeErrorToStandardSchemaIssues = <
|
|
|
4396
4843
|
if (error.type === "Tuple") {
|
|
4397
4844
|
const tupleError = error as TupleError;
|
|
4398
4845
|
if (tupleError.reason.kind === "InvalidLength") {
|
|
4399
|
-
return [{ message:
|
|
4846
|
+
return [{ message: JSON.stringify(error), path }];
|
|
4400
4847
|
}
|
|
4401
4848
|
return typeErrorToStandardSchemaIssues(
|
|
4402
4849
|
tupleError.reason.error as TypeErrors<ExtraErrors>,
|
|
4403
|
-
formatTypeError,
|
|
4404
4850
|
[...path, tupleError.reason.index],
|
|
4405
4851
|
);
|
|
4406
4852
|
}
|
|
@@ -4408,11 +4854,7 @@ export const typeErrorToStandardSchemaIssues = <
|
|
|
4408
4854
|
if (error.type === "Union") {
|
|
4409
4855
|
const unionError = error as UnionError;
|
|
4410
4856
|
return unionError.errors.flatMap((err) =>
|
|
4411
|
-
typeErrorToStandardSchemaIssues(
|
|
4412
|
-
err as TypeErrors<ExtraErrors>,
|
|
4413
|
-
formatTypeError,
|
|
4414
|
-
path,
|
|
4415
|
-
),
|
|
4857
|
+
typeErrorToStandardSchemaIssues(err as TypeErrors<ExtraErrors>, path),
|
|
4416
4858
|
);
|
|
4417
4859
|
}
|
|
4418
4860
|
|
|
@@ -4421,14 +4863,13 @@ export const typeErrorToStandardSchemaIssues = <
|
|
|
4421
4863
|
if ("parentError" in brandError) {
|
|
4422
4864
|
return typeErrorToStandardSchemaIssues(
|
|
4423
4865
|
brandError.parentError as TypeErrors<ExtraErrors>,
|
|
4424
|
-
formatTypeError,
|
|
4425
4866
|
path,
|
|
4426
4867
|
);
|
|
4427
4868
|
}
|
|
4428
|
-
return [{ message:
|
|
4869
|
+
return [{ message: JSON.stringify(error), path }];
|
|
4429
4870
|
}
|
|
4430
4871
|
|
|
4431
|
-
return [{ message:
|
|
4872
|
+
return [{ message: JSON.stringify(error), path }];
|
|
4432
4873
|
};
|
|
4433
4874
|
|
|
4434
4875
|
/** The Standard Schema interface. */
|
|
@@ -4502,10 +4943,3 @@ export declare namespace StandardSchemaV1 {
|
|
|
4502
4943
|
Schema["~standard"]["types"]
|
|
4503
4944
|
>["output"];
|
|
4504
4945
|
}
|
|
4505
|
-
|
|
4506
|
-
/**
|
|
4507
|
-
* Shared formatter cache for Standard Schema integration - avoids circular
|
|
4508
|
-
* dependency by lazily creating the formatter on first use rather than during
|
|
4509
|
-
* module initialization.
|
|
4510
|
-
*/
|
|
4511
|
-
let cachedStandardSchemaFormatTypeError: TypeErrorFormatter<any> | undefined;
|