@evolu/common 8.2.0 → 8.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/Function.d.ts +1 -0
- package/dist/src/Function.d.ts.map +1 -1
- package/dist/src/Http.d.ts +9 -0
- package/dist/src/Http.d.ts.map +1 -1
- package/dist/src/Object.d.ts +1 -0
- package/dist/src/Object.d.ts.map +1 -1
- package/dist/src/RefCount.d.ts +1 -0
- package/dist/src/RefCount.d.ts.map +1 -1
- package/dist/src/Relation.d.ts +2 -0
- package/dist/src/Relation.d.ts.map +1 -1
- package/dist/src/Resource.d.ts +1 -0
- package/dist/src/Resource.d.ts.map +1 -1
- package/dist/src/Result.d.ts +383 -548
- package/dist/src/Result.d.ts.map +1 -1
- package/dist/src/Result.js +296 -46
- package/dist/src/StackTrace.d.ts +1 -0
- package/dist/src/StackTrace.d.ts.map +1 -1
- package/dist/src/Task.d.ts +985 -1069
- package/dist/src/Task.d.ts.map +1 -1
- package/dist/src/Task.js +1007 -156
- package/dist/src/Time.d.ts +1 -0
- package/dist/src/Time.d.ts.map +1 -1
- package/dist/src/Type.d.ts +1322 -793
- package/dist/src/Type.d.ts.map +1 -1
- package/dist/src/Type.js +1192 -244
- package/dist/src/intl/_en.d.ts +126 -0
- package/dist/src/intl/_en.d.ts.map +1 -0
- package/dist/src/intl/_en.js +267 -0
- package/dist/src/intl/ar.d.ts +117 -0
- package/dist/src/intl/ar.d.ts.map +1 -0
- package/dist/src/intl/ar.js +245 -0
- package/dist/src/intl/bn.d.ts +117 -0
- package/dist/src/intl/bn.d.ts.map +1 -0
- package/dist/src/intl/bn.js +258 -0
- package/dist/src/intl/ca.d.ts +117 -0
- package/dist/src/intl/ca.d.ts.map +1 -0
- package/dist/src/intl/ca.js +258 -0
- package/dist/src/intl/cs.d.ts +20 -7
- package/dist/src/intl/cs.d.ts.map +1 -1
- package/dist/src/intl/cs.js +54 -27
- package/dist/src/intl/da.d.ts +117 -0
- package/dist/src/intl/da.d.ts.map +1 -0
- package/dist/src/intl/da.js +258 -0
- package/dist/src/intl/de.d.ts +117 -0
- package/dist/src/intl/de.d.ts.map +1 -0
- package/dist/src/intl/de.js +258 -0
- package/dist/src/intl/el.d.ts +117 -0
- package/dist/src/intl/el.d.ts.map +1 -0
- package/dist/src/intl/el.js +265 -0
- package/dist/src/intl/es.d.ts +117 -0
- package/dist/src/intl/es.d.ts.map +1 -0
- package/dist/src/intl/es.js +258 -0
- package/dist/src/intl/fa.d.ts +117 -0
- package/dist/src/intl/fa.d.ts.map +1 -0
- package/dist/src/intl/fa.js +258 -0
- package/dist/src/intl/fi.d.ts +117 -0
- package/dist/src/intl/fi.d.ts.map +1 -0
- package/dist/src/intl/fi.js +258 -0
- package/dist/src/intl/fil.d.ts +117 -0
- package/dist/src/intl/fil.d.ts.map +1 -0
- package/dist/src/intl/fil.js +258 -0
- package/dist/src/intl/fr.d.ts +117 -0
- package/dist/src/intl/fr.d.ts.map +1 -0
- package/dist/src/intl/fr.js +258 -0
- package/dist/src/intl/he.d.ts +117 -0
- package/dist/src/intl/he.d.ts.map +1 -0
- package/dist/src/intl/he.js +258 -0
- package/dist/src/intl/hi.d.ts +117 -0
- package/dist/src/intl/hi.d.ts.map +1 -0
- package/dist/src/intl/hi.js +258 -0
- package/dist/src/intl/hr.d.ts +117 -0
- package/dist/src/intl/hr.d.ts.map +1 -0
- package/dist/src/intl/hr.js +265 -0
- package/dist/src/intl/hu.d.ts +63 -0
- package/dist/src/intl/hu.d.ts.map +1 -0
- package/dist/src/intl/hu.js +204 -0
- package/dist/src/intl/id.d.ts +117 -0
- package/dist/src/intl/id.d.ts.map +1 -0
- package/dist/src/intl/id.js +258 -0
- package/dist/src/intl/index.d.ts +42 -0
- package/dist/src/intl/index.d.ts.map +1 -1
- package/dist/src/intl/index.js +42 -0
- package/dist/src/intl/it.d.ts +117 -0
- package/dist/src/intl/it.d.ts.map +1 -0
- package/dist/src/intl/it.js +258 -0
- package/dist/src/intl/ja.d.ts +117 -0
- package/dist/src/intl/ja.d.ts.map +1 -0
- package/dist/src/intl/ja.js +258 -0
- package/dist/src/intl/ko.d.ts +117 -0
- package/dist/src/intl/ko.d.ts.map +1 -0
- package/dist/src/intl/ko.js +258 -0
- package/dist/src/intl/ml.d.ts +117 -0
- package/dist/src/intl/ml.d.ts.map +1 -0
- package/dist/src/intl/ml.js +258 -0
- package/dist/src/intl/mr.d.ts +117 -0
- package/dist/src/intl/mr.d.ts.map +1 -0
- package/dist/src/intl/mr.js +258 -0
- package/dist/src/intl/ms.d.ts +117 -0
- package/dist/src/intl/ms.d.ts.map +1 -0
- package/dist/src/intl/ms.js +245 -0
- package/dist/src/intl/nb.d.ts +63 -0
- package/dist/src/intl/nb.d.ts.map +1 -0
- package/dist/src/intl/nb.js +191 -0
- package/dist/src/intl/nl.d.ts +117 -0
- package/dist/src/intl/nl.d.ts.map +1 -0
- package/dist/src/intl/nl.js +258 -0
- package/dist/src/intl/pa.d.ts +117 -0
- package/dist/src/intl/pa.d.ts.map +1 -0
- package/dist/src/intl/pa.js +258 -0
- package/dist/src/intl/pl.d.ts +117 -0
- package/dist/src/intl/pl.d.ts.map +1 -0
- package/dist/src/intl/pl.js +242 -0
- package/dist/src/intl/pt-BR.d.ts +117 -0
- package/dist/src/intl/pt-BR.d.ts.map +1 -0
- package/dist/src/intl/pt-BR.js +258 -0
- package/dist/src/intl/pt.d.ts +117 -0
- package/dist/src/intl/pt.d.ts.map +1 -0
- package/dist/src/intl/pt.js +246 -0
- package/dist/src/intl/ro.d.ts +117 -0
- package/dist/src/intl/ro.d.ts.map +1 -0
- package/dist/src/intl/ro.js +258 -0
- package/dist/src/intl/sk.d.ts +117 -0
- package/dist/src/intl/sk.d.ts.map +1 -0
- package/dist/src/intl/sk.js +244 -0
- package/dist/src/intl/sl.d.ts +117 -0
- package/dist/src/intl/sl.d.ts.map +1 -0
- package/dist/src/intl/sl.js +258 -0
- package/dist/src/intl/sv.d.ts +117 -0
- package/dist/src/intl/sv.d.ts.map +1 -0
- package/dist/src/intl/sv.js +258 -0
- package/dist/src/intl/sw.d.ts +63 -0
- package/dist/src/intl/sw.d.ts.map +1 -0
- package/dist/src/intl/sw.js +191 -0
- package/dist/src/intl/ta.d.ts +117 -0
- package/dist/src/intl/ta.d.ts.map +1 -0
- package/dist/src/intl/ta.js +258 -0
- package/dist/src/intl/te.d.ts +117 -0
- package/dist/src/intl/te.d.ts.map +1 -0
- package/dist/src/intl/te.js +258 -0
- package/dist/src/intl/th.d.ts +117 -0
- package/dist/src/intl/th.d.ts.map +1 -0
- package/dist/src/intl/th.js +258 -0
- package/dist/src/intl/tr.d.ts +117 -0
- package/dist/src/intl/tr.d.ts.map +1 -0
- package/dist/src/intl/tr.js +255 -0
- package/dist/src/intl/uk.d.ts +117 -0
- package/dist/src/intl/uk.d.ts.map +1 -0
- package/dist/src/intl/uk.js +258 -0
- package/dist/src/intl/ur.d.ts +117 -0
- package/dist/src/intl/ur.d.ts.map +1 -0
- package/dist/src/intl/ur.js +245 -0
- package/dist/src/intl/vi.d.ts +117 -0
- package/dist/src/intl/vi.d.ts.map +1 -0
- package/dist/src/intl/vi.js +255 -0
- package/dist/src/intl/zh-CN.d.ts +117 -0
- package/dist/src/intl/zh-CN.d.ts.map +1 -0
- package/dist/src/intl/zh-CN.js +258 -0
- package/dist/src/intl/zh-TW.d.ts +117 -0
- package/dist/src/intl/zh-TW.d.ts.map +1 -0
- package/dist/src/intl/zh-TW.js +258 -0
- package/dist/src/local-first/Owner.d.ts +156 -62
- package/dist/src/local-first/Owner.d.ts.map +1 -1
- package/dist/src/local-first/Owner.js +119 -20
- package/package.json +1 -1
- package/src/Function.ts +2 -0
- package/src/Http.ts +9 -0
- package/src/Object.ts +2 -0
- package/src/RefCount.ts +2 -0
- package/src/Relation.ts +4 -0
- package/src/Resource.ts +2 -0
- package/src/Result.ts +397 -560
- package/src/StackTrace.ts +2 -0
- package/src/Task.ts +1143 -1222
- package/src/Time.ts +2 -0
- package/src/Type.ts +1759 -794
- package/src/intl/_en.ts +497 -0
- package/src/intl/ar.ts +417 -0
- package/src/intl/bn.ts +486 -0
- package/src/intl/ca.ts +488 -0
- package/src/intl/cs.ts +95 -31
- package/src/intl/da.ts +488 -0
- package/src/intl/de.ts +488 -0
- package/src/intl/el.ts +494 -0
- package/src/intl/es.ts +488 -0
- package/src/intl/fa.ts +487 -0
- package/src/intl/fi.ts +487 -0
- package/src/intl/fil.ts +488 -0
- package/src/intl/fr.ts +488 -0
- package/src/intl/he.ts +486 -0
- package/src/intl/hi.ts +487 -0
- package/src/intl/hr.ts +494 -0
- package/src/intl/hu.ts +387 -0
- package/src/intl/id.ts +487 -0
- package/src/intl/index.ts +42 -0
- package/src/intl/it.ts +488 -0
- package/src/intl/ja.ts +488 -0
- package/src/intl/ko.ts +487 -0
- package/src/intl/ml.ts +487 -0
- package/src/intl/mr.ts +487 -0
- package/src/intl/ms.ts +410 -0
- package/src/intl/nb.ts +364 -0
- package/src/intl/nl.ts +488 -0
- package/src/intl/pa.ts +487 -0
- package/src/intl/pl.ts +450 -0
- package/src/intl/pt-BR.ts +487 -0
- package/src/intl/pt.ts +424 -0
- package/src/intl/ro.ts +488 -0
- package/src/intl/sk.ts +412 -0
- package/src/intl/sl.ts +487 -0
- package/src/intl/sv.ts +488 -0
- package/src/intl/sw.ts +324 -0
- package/src/intl/ta.ts +487 -0
- package/src/intl/te.ts +487 -0
- package/src/intl/th.ts +484 -0
- package/src/intl/tr.ts +487 -0
- package/src/intl/uk.ts +488 -0
- package/src/intl/ur.ts +410 -0
- package/src/intl/vi.ts +435 -0
- package/src/intl/zh-CN.ts +482 -0
- package/src/intl/zh-TW.ts +482 -0
- package/src/local-first/Owner.ts +156 -62
package/src/Type.ts
CHANGED
|
@@ -1,176 +1,208 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* ## Intro
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import { utf8ToBytes } from "@noble/ciphers/utils.js";
|
|
8
|
-
import { sha256 } from "@noble/hashes/sha2.js";
|
|
9
|
-
import * as bip39 from "@scure/bip39";
|
|
10
|
-
import { wordlist } from "@scure/bip39/wordlists/english.js";
|
|
11
|
-
import type { StandardSchemaV1 } from "@standard-schema/spec";
|
|
12
|
-
import type {
|
|
13
|
-
AtLeastTwoReadonlyArray,
|
|
14
|
-
NonEmptyReadonlyArray,
|
|
15
|
-
} from "./Array.ts";
|
|
16
|
-
import { assert, assertNonNullable } from "./Assert.ts";
|
|
17
|
-
import type { Brand } from "./Brand.ts";
|
|
18
|
-
import type { RandomBytesDep } from "./Crypto.ts";
|
|
19
|
-
import { identity, type Thunk } from "./Function.ts";
|
|
20
|
-
import { createMutableRecord } from "./Object.ts";
|
|
21
|
-
import { hasNodeBuffer } from "./Platform.ts";
|
|
22
|
-
import {
|
|
23
|
-
err,
|
|
24
|
-
flatMapResult,
|
|
25
|
-
getOk,
|
|
26
|
-
getOrNull,
|
|
27
|
-
getOrThrow,
|
|
28
|
-
ok,
|
|
29
|
-
trySync,
|
|
30
|
-
type Result,
|
|
31
|
-
} from "./Result.ts";
|
|
32
|
-
import { safelyStringifyUnknownValue } from "./String.ts";
|
|
33
|
-
import type { Task } from "./Task.ts";
|
|
34
|
-
import type { TimeDep } from "./Time.ts";
|
|
35
|
-
import {
|
|
36
|
-
instance,
|
|
37
|
-
isInstance,
|
|
38
|
-
type CompileTimeError,
|
|
39
|
-
type Instance,
|
|
40
|
-
type IsUnion,
|
|
41
|
-
type Literal,
|
|
42
|
-
type Simplify,
|
|
43
|
-
type ValueWithLength,
|
|
44
|
-
type WidenLiteral,
|
|
45
|
-
} from "./Types.ts";
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* A runtime representation of a TypeScript type with typed structured errors.
|
|
49
|
-
*
|
|
50
|
-
* Evolu Type reports expected decoding failures through {@link Result} rather
|
|
51
|
-
* than exceptions. It represents both an encoded `Input` and its semantic
|
|
52
|
-
* `Output`, supporting validation, transformation, and canonical encoding.
|
|
53
|
-
*
|
|
54
|
-
* Evolu Type is designed for correctness and developer experience. Correctness
|
|
55
|
-
* is especially important for local-first data: application authors cannot
|
|
56
|
-
* inspect or repair a user's data on a server because they do not have access
|
|
57
|
-
* to it. Type declarations must reject invalid data at system boundaries, then
|
|
58
|
-
* preserve those guarantees wherever the data travels.
|
|
59
|
-
*
|
|
60
|
-
* To make correct code the easiest code to write, Evolu Type preserves as much
|
|
61
|
-
* information as TypeScript can express. Invalid declarations produce readable
|
|
62
|
-
* {@link CompileTimeError} types when the compiler can detect them, while
|
|
63
|
-
* runtime assertions enforce construction contracts it cannot prove. Together,
|
|
64
|
-
* these choices create a pit of success.
|
|
65
|
-
*
|
|
66
|
-
* The implementation is optimized for minimal bundle size. Less descriptive
|
|
67
|
-
* assertion messages could make it even smaller, but Evolu keeps actionable
|
|
68
|
-
* messages as a deliberate tradeoff for developer experience.
|
|
69
|
-
*
|
|
70
|
-
* The main properties of Evolu Type are:
|
|
71
|
-
*
|
|
72
|
-
* - **Result-based error handling** – expected failures are explicit values.
|
|
73
|
-
* - **Typed errors with decoupled formatters** – validation logic stays
|
|
74
|
-
* independent of user-facing messages, and errors can be handled
|
|
75
|
-
* exhaustively.
|
|
76
|
-
* - **Type-safe, tree-shakeable localization** – formatter requirements are
|
|
77
|
-
* inferred from selected Types, while apps bundle exactly the locales they
|
|
78
|
-
* support so users can change language offline.
|
|
79
|
-
* - **Consistent constraints through {@link Brand}** – every refinement constraint
|
|
80
|
-
* is represented in the TypeScript type, so an unconstrained parent value
|
|
81
|
-
* cannot be used where the constrained value is required.
|
|
82
|
-
* - **Typed inputs** – prefer `from` and its `.parent` entry points to connect
|
|
83
|
-
* precise producer and consumer contracts while preserving typed remaining
|
|
84
|
-
* errors; reserve `fromUnknown` for genuinely unknown values.
|
|
85
|
-
* - **Lawful codecs** – Types partially decode `Input` to `Output` and totally
|
|
86
|
-
* encode every legitimate `Output` to `CanonicalInput`, the statically known
|
|
87
|
-
* subtype of `Input` returned by complete encoding.
|
|
88
|
-
* - **A top-down implementation** – the source is intended to be read from
|
|
89
|
-
* beginning to end.
|
|
90
|
-
*
|
|
91
|
-
* Evolu Type supports [Standard Schema](https://standardschema.dev/) for
|
|
92
|
-
* interoperability with compatible tools and frameworks while preserving each
|
|
93
|
-
* Type's exact Input and Output.
|
|
94
|
-
*
|
|
95
|
-
* Evolu Type assumes that all executing code, including third-party
|
|
96
|
-
* dependencies, has been audited and is trusted. It validates data contracts
|
|
97
|
-
* under that assumption. Trusting code does not require trusting every value it
|
|
98
|
-
* returns, so uncertain values from legacy code or another realm can still be
|
|
99
|
-
* decoded at an explicit boundary. It does not protect against hostile
|
|
100
|
-
* executable behavior such as sabotaged Proxies or throwing traps; Type
|
|
101
|
-
* validation is not a security boundary for untrusted JavaScript.
|
|
102
|
-
*
|
|
103
|
-
* Type declarations and their callbacks are trusted construction code. Evolu
|
|
104
|
-
* Type leverages that trust for better developer experience and does not try to
|
|
105
|
-
* recover from code that defeats the type system with `any` or casts, including
|
|
106
|
-
* fabricating an `Err` for `Result<_, never>`. Runtime assertions still enforce
|
|
107
|
-
* contracts TypeScript cannot express, and every Type declaration must be
|
|
108
|
-
* tested for its expected successes and failures.
|
|
109
|
-
*
|
|
110
|
-
* `fromUnknown` validates untyped input through the complete pipeline. `from`
|
|
111
|
-
* and its `.parent` operations use their declared boundary to determine which
|
|
112
|
-
* remaining stages can return validation errors, but assert that boundary at
|
|
113
|
-
* runtime. `orThrow` and `orNull` reuse the deepest `from` operation accepting
|
|
114
|
-
* `Input`, while `to` asserts its Output boundary. A failed assertion means
|
|
115
|
-
* application code violated its static contract; it is a developer error, not
|
|
116
|
-
* an expected validation failure. Its message identifies the expected Type and
|
|
117
|
-
* its cause preserves the exact structured Output validation error.
|
|
118
|
-
*
|
|
119
|
-
* Prefer the most precise typed boundary available. A value is not unknown
|
|
120
|
-
* merely because it originated outside the application: forms, components, and
|
|
121
|
-
* other producers often expose a `string` or a branded value that can connect
|
|
122
|
-
* directly to a matching `from` boundary. Reserve `fromUnknown` for values
|
|
123
|
-
* whose TypeScript type is genuinely `unknown`. `is` means exact membership in
|
|
124
|
-
* the output domain, not merely that output-side parsing could succeed. A
|
|
125
|
-
* successful `fromUnknown` result always satisfies `is`.
|
|
126
|
-
*
|
|
127
|
-
* Decoding accepts a representation outside the Output domain only when the
|
|
128
|
-
* Type explicitly declares that representation, such as a transformation Input.
|
|
129
|
-
* Structural Types do not implicitly repair another JavaScript representation.
|
|
130
|
-
* In particular, {@link array} and {@link tuple} require dense own data elements,
|
|
131
|
-
* while the predefined {@link Object}, {@link object}, and {@link record} require
|
|
132
|
-
* plain objects with own enumerable data properties. They do not invoke
|
|
133
|
-
* accessors or materialize inherited and non-enumerable properties.
|
|
134
|
-
*
|
|
135
|
-
* TypeScript object types are structural and do not encode JavaScript realm
|
|
136
|
-
* identity. Structural Types therefore accept legitimate representations from
|
|
137
|
-
* other realms. Prototype checks remain when the prototype defines the semantic
|
|
138
|
-
* domain. Plain-object Types accept a `null` prototype or an immediate root
|
|
139
|
-
* prototype whose own prototype is `null`; ordinary class instances and deeper
|
|
140
|
-
* prototype chains are rejected. When Record decoding must construct a
|
|
141
|
-
* normalized value, it uses a `null` prototype so every string key remains
|
|
142
|
-
* ordinary data.
|
|
143
|
-
*
|
|
144
|
-
* Evolu Type requires TypeScript 7 or newer and expects the
|
|
145
|
-
* `exactOptionalPropertyTypes` compiler option to be enabled.
|
|
146
|
-
*
|
|
147
|
-
* Predefined Types intentionally use the names of corresponding JavaScript
|
|
148
|
-
* built-ins because they represent those familiar value categories. If an
|
|
149
|
-
* imported Type shadows a built-in in the same scope, access the built-in
|
|
150
|
-
* through `globalThis`, JavaScript's standard cross-environment global object,
|
|
151
|
-
* such as `globalThis.String` or `globalThis.Date`.
|
|
4
|
+
* Runtime validation with precise TypeScript types and structured errors.
|
|
152
5
|
*
|
|
153
|
-
*
|
|
6
|
+
* Evolu {@link Type} is a pure, synchronous codec for defining semantic domains.
|
|
7
|
+
* It partially decodes an `Input` into an `Output` and totally encodes every
|
|
8
|
+
* valid `Output` into a `CanonicalInput`. Types can validate, refine,
|
|
9
|
+
* transform, and compose without losing the contracts TypeScript can express.
|
|
10
|
+
*
|
|
11
|
+
* Decoding failures are explicit {@link Result} values, and their structured
|
|
12
|
+
* errors preserve the exact error types each Type can return.
|
|
13
|
+
*
|
|
14
|
+
* Evolu Type is designed to make correct code the easiest code to write:
|
|
15
|
+
*
|
|
16
|
+
* - Predefined constraints add a {@link Brand} to their Output.
|
|
17
|
+
* - Invalid declarations produce readable {@link CompileTimeError} types when the
|
|
18
|
+
* compiler can detect them.
|
|
19
|
+
* - Evolu Type uses runtime {@link assert | assertions} to detect developer errors
|
|
20
|
+
* that TypeScript cannot express, such as excess properties and sparse
|
|
21
|
+
* arrays.
|
|
22
|
+
* - Typed `from` boundaries allow connecting value producers to domain fields
|
|
23
|
+
* through their exact TypeScript types, so incompatible contract changes are
|
|
24
|
+
* compile-time errors rather than runtime validation errors.
|
|
25
|
+
* - Lawful codecs compose without creating unencodable values: every valid Output
|
|
26
|
+
* has a canonical Input representation and round-trips to the same semantic
|
|
27
|
+
* value.
|
|
28
|
+
* - Type-safe localization infers the required error formatters from selected
|
|
29
|
+
* Types, so missing validation messages are compile-time errors.
|
|
30
|
+
*
|
|
31
|
+
* Correctness is especially important for local-first data: application authors
|
|
32
|
+
* cannot inspect or repair a user's data.
|
|
33
|
+
*
|
|
34
|
+
* Evolu Type is optimized for small real-world bundles: composed Types share
|
|
35
|
+
* runtime code, while unused validators and formatters are tree-shaken. It
|
|
36
|
+
* could be smaller with less descriptive assertion messages, but Evolu favors
|
|
37
|
+
* actionable diagnostics over micro-optimizing isolated Types.
|
|
38
|
+
*
|
|
39
|
+
* Predefined Types use the names of corresponding JavaScript built-ins. When a
|
|
40
|
+
* Type shadows one, access the JavaScript built-in through `globalThis`, such
|
|
41
|
+
* as `globalThis.String` or `globalThis.Date`.
|
|
42
|
+
*
|
|
43
|
+
* Evolu Type supports [Standard Schema](https://standardschema.dev/) and
|
|
44
|
+
* requires TypeScript 7+ with `exactOptionalPropertyTypes` enabled.
|
|
45
|
+
*
|
|
46
|
+
* ## Examples
|
|
47
|
+
*
|
|
48
|
+
* Define a domain object with a custom `Age` Type, then validate unknown input:
|
|
154
49
|
*
|
|
155
50
|
* ```ts
|
|
156
|
-
* import {
|
|
51
|
+
* import {
|
|
52
|
+
* Number,
|
|
53
|
+
* NonEmptyTrimmedString100,
|
|
54
|
+
* brand,
|
|
55
|
+
* finite,
|
|
56
|
+
* int,
|
|
57
|
+
* lessThan,
|
|
58
|
+
* nonNaN,
|
|
59
|
+
* nonNegative,
|
|
60
|
+
* object,
|
|
61
|
+
* type Brand,
|
|
62
|
+
* type InferErrors,
|
|
63
|
+
* type InferType,
|
|
64
|
+
* } from "@evolu/common";
|
|
157
65
|
*
|
|
158
|
-
*
|
|
159
|
-
*
|
|
66
|
+
* // Age and its parent Types are predefined by Evolu. They are reconstructed
|
|
67
|
+
* // here to reveal every constraint behind a seemingly simple domain value.
|
|
68
|
+
* const NonNaNNumber = nonNaN(Number);
|
|
69
|
+
* const FiniteNumber = finite(NonNaNNumber);
|
|
70
|
+
* const Int = int(FiniteNumber);
|
|
71
|
+
* const NonNegativeInt = nonNegative(Int);
|
|
160
72
|
*
|
|
161
|
-
*
|
|
73
|
+
* const Age = brand("Age", lessThan(200)(NonNegativeInt));
|
|
74
|
+
* type Age = typeof Age.Output;
|
|
75
|
+
*
|
|
76
|
+
* expectTypeOf<Age>().toEqualTypeOf<
|
|
77
|
+
* number &
|
|
78
|
+
* Brand<"NonNaN"> &
|
|
79
|
+
* Brand<"Finite"> &
|
|
80
|
+
* Brand<"Int"> &
|
|
81
|
+
* Brand<"NonNegative"> &
|
|
82
|
+
* Brand<"LessThan200"> &
|
|
83
|
+
* Brand<"Age">
|
|
84
|
+
* >();
|
|
85
|
+
*
|
|
86
|
+
* const User = object({
|
|
87
|
+
* name: NonEmptyTrimmedString100,
|
|
88
|
+
* age: Age,
|
|
89
|
+
* });
|
|
90
|
+
* interface User extends InferType<typeof User> {}
|
|
91
|
+
*
|
|
92
|
+
* const value: unknown = { name: "Ada", age: 37 };
|
|
93
|
+
* const user = User.fromUnknown(value);
|
|
94
|
+
*
|
|
95
|
+
* expectOk(user, { name: "Ada", age: 37 });
|
|
96
|
+
* expectTypeOf(user.value).toExtend<User>();
|
|
97
|
+
*
|
|
98
|
+
* const invalidUser = User.fromUnknown({ name: "Ada", age: 37.5 });
|
|
99
|
+
*
|
|
100
|
+
* expectErr(invalidUser, {
|
|
101
|
+
* type: "Object",
|
|
102
|
+
* reason: {
|
|
103
|
+
* kind: "Properties",
|
|
104
|
+
* errors: {
|
|
105
|
+
* age: { type: "Int", value: 37.5 },
|
|
106
|
+
* },
|
|
107
|
+
* },
|
|
108
|
+
* });
|
|
109
|
+
*
|
|
110
|
+
* // InferErrors includes every structured error User.fromUnknown can return.
|
|
111
|
+
* expectTypeOf(invalidUser.error).toEqualTypeOf<
|
|
112
|
+
* InferErrors<typeof User>
|
|
113
|
+
* >();
|
|
114
|
+
* ```
|
|
115
|
+
*
|
|
116
|
+
* A Type can format its structured errors into user-facing messages:
|
|
117
|
+
*
|
|
118
|
+
* ```ts
|
|
119
|
+
* import { Age } from "@evolu/common";
|
|
120
|
+
*
|
|
121
|
+
* const age = Age.fromUnknown(37.5);
|
|
122
|
+
*
|
|
123
|
+
* expectErr(age, { type: "Int", value: 37.5 });
|
|
124
|
+
* expect(Age.formatError(age.error)).toBe(
|
|
125
|
+
* "The value 37.5 must be a safe integer.",
|
|
126
|
+
* );
|
|
127
|
+
* ```
|
|
128
|
+
*
|
|
129
|
+
* Use {@link localizeTypes} to derive Types with localized messages without
|
|
130
|
+
* changing validation behavior.
|
|
131
|
+
*
|
|
132
|
+
* One of Evolu Type's strongest features is typed `from` boundaries. A value
|
|
133
|
+
* producer, such as a form input, carries the precise constraints it
|
|
134
|
+
* guarantees, and TypeScript checks them against the consuming domain field.
|
|
135
|
+
* Unlike validation from `unknown` or `string`, this checks the contract
|
|
136
|
+
* between the producer and consumer, not merely whether the current value
|
|
137
|
+
* passes:
|
|
138
|
+
*
|
|
139
|
+
* ```ts
|
|
140
|
+
* import {
|
|
141
|
+
* NonEmptyTrimmedString100,
|
|
142
|
+
* NonEmptyTrimmedString1000,
|
|
143
|
+
* object,
|
|
144
|
+
* trim,
|
|
145
|
+
* type MaxLengthError,
|
|
146
|
+
* type MinLengthError,
|
|
147
|
+
* type Result,
|
|
148
|
+
* type TrimmedString,
|
|
149
|
+
* } from "@evolu/common";
|
|
150
|
+
*
|
|
151
|
+
* const Todo = object({ title: NonEmptyTrimmedString100 });
|
|
152
|
+
*
|
|
153
|
+
* // This is type-checked: Todo.from expects NonEmptyTrimmedString100.
|
|
154
|
+
* const title = NonEmptyTrimmedString100.orThrow("Buy milk");
|
|
155
|
+
* expectOk(Todo.from({ title }), { title });
|
|
156
|
+
*
|
|
157
|
+
* // Imagine the UI input component is changed to allow longer titles.
|
|
158
|
+
* // TypeScript rejects the mismatch, so users never see a save error
|
|
159
|
+
* // for a title the UI accepts but the domain cannot save.
|
|
160
|
+
* const longerTitle = NonEmptyTrimmedString1000.orThrow("Buy milk");
|
|
161
|
+
* // @ts-expect-error MaxLength1000 does not guarantee MaxLength100.
|
|
162
|
+
* Todo.from({ title: longerTitle });
|
|
163
|
+
*
|
|
164
|
+
* // Imagine a UI input component that returns TrimmedString.
|
|
165
|
+
* // from.parent.parent connects it to the domain field and validates the
|
|
166
|
+
* // remaining constraints.
|
|
167
|
+
* const titleFromTrimmingInput: TrimmedString = trim(" Buy milk ");
|
|
168
|
+
* const validatedTitle = Todo.props.title.from.parent.parent(
|
|
169
|
+
* titleFromTrimmingInput,
|
|
170
|
+
* );
|
|
171
|
+
*
|
|
172
|
+
* // No "not a string" or "not trimmed" errors: the input guarantees both.
|
|
173
|
+
* expectTypeOf(validatedTitle).toEqualTypeOf<
|
|
162
174
|
* Result<
|
|
163
|
-
*
|
|
164
|
-
*
|
|
165
|
-
* readonly type: "TypeOf";
|
|
166
|
-
* readonly expected: "String";
|
|
167
|
-
* readonly value: unknown;
|
|
168
|
-
* }
|
|
175
|
+
* NonEmptyTrimmedString100,
|
|
176
|
+
* MaxLengthError<100> | MinLengthError<1>
|
|
169
177
|
* >
|
|
170
178
|
* >();
|
|
171
|
-
* expectOk(
|
|
179
|
+
* expectOk(validatedTitle, "Buy milk");
|
|
172
180
|
* ```
|
|
173
181
|
*
|
|
182
|
+
* Evolu includes dozens of predefined Types and Type factories. Use Types such
|
|
183
|
+
* as {@link Age}, {@link PositiveInt}, {@link DateIso},
|
|
184
|
+
* {@link NonEmptyTrimmedString100}, {@link Base64Url}, and {@link Json} directly.
|
|
185
|
+
* Build domain Types with factories such as {@link brand}, {@link typed},
|
|
186
|
+
* {@link minLength}, {@link maxLength}, {@link array}, {@link object},
|
|
187
|
+
* {@link union}, {@link templateLiteral}, {@link transform},
|
|
188
|
+
* {@link discriminatedUnion}, and {@link json}.
|
|
189
|
+
*
|
|
190
|
+
* ## Guarantees
|
|
191
|
+
*
|
|
192
|
+
* Evolu Type validates values; it does not defend against adversarial
|
|
193
|
+
* JavaScript such as malicious Proxies, mutation during validation, throwing
|
|
194
|
+
* traps, forged built-ins, or code deliberately bypassing TypeScript with `any`
|
|
195
|
+
* or casts.
|
|
196
|
+
*
|
|
197
|
+
* Evolu Type trusts application code and audited dependencies. Untrusted code
|
|
198
|
+
* can cause harm far beyond validation and must not run in the application.
|
|
199
|
+
* Defending against it would add complexity without creating a meaningful
|
|
200
|
+
* security boundary.
|
|
201
|
+
*
|
|
202
|
+
* Runtime assertions still detect accidental developer errors that TypeScript
|
|
203
|
+
* cannot express. They are correctness checks, not defenses against malicious
|
|
204
|
+
* code.
|
|
205
|
+
*
|
|
174
206
|
* ## FAQ
|
|
175
207
|
*
|
|
176
208
|
* ### What does a Type represent?
|
|
@@ -287,10 +319,10 @@ import {
|
|
|
287
319
|
* is own, enumerable, or a data property. It also permits a wider object with
|
|
288
320
|
* excess properties where a narrower object type is expected.
|
|
289
321
|
*
|
|
290
|
-
* `fromUnknown` treats such invalid external values as
|
|
291
|
-
*
|
|
292
|
-
*
|
|
293
|
-
*
|
|
322
|
+
* `fromUnknown` treats such invalid external values as input data and returns a
|
|
323
|
+
* typed error. Typed boundaries instead assert the domain promised by their
|
|
324
|
+
* parameter type. If application code claims an accessor-backed object or an
|
|
325
|
+
* object with excess properties is an Object Output, the assertion throws
|
|
294
326
|
* because the application contract is broken. `orThrow` and `orNull` preserve
|
|
295
327
|
* the assertion at their typed `Input` boundary, then apply {@link getOrThrow}
|
|
296
328
|
* or {@link getOrNull} only to validation failures returned by the remaining
|
|
@@ -319,21 +351,17 @@ import {
|
|
|
319
351
|
*
|
|
320
352
|
* ### How should values from another realm be handled?
|
|
321
353
|
*
|
|
322
|
-
*
|
|
323
|
-
*
|
|
324
|
-
*
|
|
325
|
-
*
|
|
326
|
-
* realm.
|
|
354
|
+
* Values returned by legacy code or another realm can still be uncertain and
|
|
355
|
+
* should be validated. Realm-neutral Types accept an otherwise legitimate
|
|
356
|
+
* representation without requiring conversion merely because its JavaScript
|
|
357
|
+
* built-ins belong to another realm.
|
|
327
358
|
*
|
|
328
|
-
* When an application trusts both the producer and its return contract,
|
|
329
|
-
*
|
|
330
|
-
*
|
|
331
|
-
*
|
|
332
|
-
*
|
|
333
|
-
*
|
|
334
|
-
* hostile Proxies, throwing traps, or sabotaged executable behavior can throw;
|
|
335
|
-
* Evolu Type does not selectively contain them or claim to be a security
|
|
336
|
-
* boundary for untrusted code.
|
|
359
|
+
* When an application trusts both the producer and its return contract, expose
|
|
360
|
+
* that contract as an accurate TypeScript type and use the typed value
|
|
361
|
+
* directly. If the boundary returns `unknown`, validate it instead of bypassing
|
|
362
|
+
* the boundary with a cast. Use a specialized Type or explicit transformation
|
|
363
|
+
* when the producer uses a different representation that needs adaptation or
|
|
364
|
+
* normalization.
|
|
337
365
|
*
|
|
338
366
|
* ### Why doesn't Evolu Type extract data from rich objects?
|
|
339
367
|
*
|
|
@@ -384,8 +412,8 @@ import {
|
|
|
384
412
|
* value to a Task. A pure synchronous conversion that can fail can be an
|
|
385
413
|
* ordinary function returning Result.
|
|
386
414
|
*
|
|
387
|
-
* Keeping those responsibilities separate prevents
|
|
388
|
-
*
|
|
415
|
+
* Keeping those responsibilities separate prevents Evolu Type from becoming a
|
|
416
|
+
* hidden application workflow. It also keeps validation deterministic,
|
|
389
417
|
* dependency-free, immediately composable, and straightforward to test.
|
|
390
418
|
*
|
|
391
419
|
* ### What if only decoding is needed?
|
|
@@ -397,7 +425,55 @@ import {
|
|
|
397
425
|
* irreversible operation is a separate function or Task, not a Type
|
|
398
426
|
* transformation.
|
|
399
427
|
*
|
|
400
|
-
* @
|
|
428
|
+
* @module
|
|
429
|
+
*/
|
|
430
|
+
import { utf8ToBytes } from "@noble/ciphers/utils.js";
|
|
431
|
+
import { sha256 } from "@noble/hashes/sha2.js";
|
|
432
|
+
import * as bip39 from "@scure/bip39";
|
|
433
|
+
import { wordlist } from "@scure/bip39/wordlists/english.js";
|
|
434
|
+
import type { StandardSchemaV1 } from "@standard-schema/spec";
|
|
435
|
+
import type {
|
|
436
|
+
AtLeastTwoReadonlyArray,
|
|
437
|
+
NonEmptyReadonlyArray,
|
|
438
|
+
} from "./Array.ts";
|
|
439
|
+
import { assert, assertNonNullable } from "./Assert.ts";
|
|
440
|
+
import type { Brand } from "./Brand.ts";
|
|
441
|
+
import type { RandomBytesDep } from "./Crypto.ts";
|
|
442
|
+
import { identity, type Thunk } from "./Function.ts";
|
|
443
|
+
import { createMutableRecord } from "./Object.ts";
|
|
444
|
+
import { hasNodeBuffer } from "./Platform.ts";
|
|
445
|
+
import {
|
|
446
|
+
err,
|
|
447
|
+
flatMapResult,
|
|
448
|
+
getOk,
|
|
449
|
+
getOrNull,
|
|
450
|
+
getOrThrow,
|
|
451
|
+
ok,
|
|
452
|
+
trySync,
|
|
453
|
+
type Result,
|
|
454
|
+
} from "./Result.ts";
|
|
455
|
+
import { safelyStringifyUnknownValue } from "./String.ts";
|
|
456
|
+
import type { Task } from "./Task.ts";
|
|
457
|
+
import type { TimeDep } from "./Time.ts";
|
|
458
|
+
import {
|
|
459
|
+
instance,
|
|
460
|
+
isInstance,
|
|
461
|
+
type CompileTimeError,
|
|
462
|
+
type Instance,
|
|
463
|
+
type IsUnion,
|
|
464
|
+
type Literal,
|
|
465
|
+
type Simplify,
|
|
466
|
+
type ValueWithLength,
|
|
467
|
+
type WidenLiteral,
|
|
468
|
+
} from "./Types.ts";
|
|
469
|
+
|
|
470
|
+
/**
|
|
471
|
+
* A runtime representation of a TypeScript type, including its encoded input,
|
|
472
|
+
* semantic output, structured errors, and canonical encoding.
|
|
473
|
+
*
|
|
474
|
+
* See the {@link @evolu/common!Type | Type overview}.
|
|
475
|
+
*
|
|
476
|
+
* @group Core
|
|
401
477
|
*/
|
|
402
478
|
export interface Type<
|
|
403
479
|
Name extends TypeName,
|
|
@@ -489,7 +565,20 @@ export interface Type<
|
|
|
489
565
|
/** The one preceding Type node, or `null` for a root Type. */
|
|
490
566
|
readonly parent: Parent;
|
|
491
567
|
|
|
492
|
-
/**
|
|
568
|
+
/**
|
|
569
|
+
* Decodes an unknown value through the complete Type pipeline.
|
|
570
|
+
*
|
|
571
|
+
* ### Example
|
|
572
|
+
*
|
|
573
|
+
* ```ts
|
|
574
|
+
* import { PositiveInt } from "@evolu/common";
|
|
575
|
+
*
|
|
576
|
+
* const value: unknown = 42;
|
|
577
|
+
* const result = PositiveInt.fromUnknown(value);
|
|
578
|
+
*
|
|
579
|
+
* expectOk(result, 42);
|
|
580
|
+
* ```
|
|
581
|
+
*/
|
|
493
582
|
readonly fromUnknown: (
|
|
494
583
|
value: unknown,
|
|
495
584
|
options?: ValidationOptions,
|
|
@@ -497,12 +586,25 @@ export interface Type<
|
|
|
497
586
|
|
|
498
587
|
/**
|
|
499
588
|
* Formats an error returned by `fromUnknown` or `from` as one human-readable
|
|
500
|
-
* message.
|
|
589
|
+
* message. Predefined Types use English; {@link localizeTypes} derives Types
|
|
501
590
|
* with localized formatters.
|
|
502
591
|
*
|
|
503
592
|
* Structural errors retain nested errors and their locations in the typed
|
|
504
593
|
* error value. This formatter does not encode paths or enumerate nested
|
|
505
594
|
* errors in its message.
|
|
595
|
+
*
|
|
596
|
+
* ### Example
|
|
597
|
+
*
|
|
598
|
+
* ```ts
|
|
599
|
+
* import { String } from "@evolu/common";
|
|
600
|
+
*
|
|
601
|
+
* const result = String.fromUnknown(42);
|
|
602
|
+
*
|
|
603
|
+
* expectErr(result, { type: "TypeOf", expected: "String", value: 42 });
|
|
604
|
+
* expect(String.formatError(result.error)).toBe(
|
|
605
|
+
* "A value 42 is not a string.",
|
|
606
|
+
* );
|
|
607
|
+
* ```
|
|
506
608
|
*/
|
|
507
609
|
readonly formatError: TypeErrorFormatter<Errors>;
|
|
508
610
|
|
|
@@ -540,6 +642,52 @@ export interface Type<
|
|
|
540
642
|
* developer error. The Error message identifies the expected boundary Type,
|
|
541
643
|
* and its cause preserves the structured validation error. Only failures
|
|
542
644
|
* introduced after that boundary are returned through `Result`.
|
|
645
|
+
*
|
|
646
|
+
* ### Example
|
|
647
|
+
*
|
|
648
|
+
* A form already guarantees every constraint on a title, while a new note
|
|
649
|
+
* input guarantees only trimmed text. The note's parent boundary validates
|
|
650
|
+
* only the constraints that the form does not guarantee:
|
|
651
|
+
*
|
|
652
|
+
* ```ts
|
|
653
|
+
* import {
|
|
654
|
+
* flatMapResult,
|
|
655
|
+
* NonEmptyTrimmedString100,
|
|
656
|
+
* object,
|
|
657
|
+
* TrimmedString,
|
|
658
|
+
* type MaxLengthError,
|
|
659
|
+
* type MinLengthError,
|
|
660
|
+
* type Result,
|
|
661
|
+
* } from "@evolu/common";
|
|
662
|
+
*
|
|
663
|
+
* const Todo = object({
|
|
664
|
+
* title: NonEmptyTrimmedString100,
|
|
665
|
+
* note: NonEmptyTrimmedString100,
|
|
666
|
+
* });
|
|
667
|
+
*
|
|
668
|
+
* const saveTodo = (
|
|
669
|
+
* title: NonEmptyTrimmedString100,
|
|
670
|
+
* note: TrimmedString,
|
|
671
|
+
* ) => {
|
|
672
|
+
* // @ts-expect-error TrimmedString does not guarantee a non-empty value
|
|
673
|
+
* // with at most 100 characters.
|
|
674
|
+
* Todo.from({ title, note });
|
|
675
|
+
*
|
|
676
|
+
* return flatMapResult(
|
|
677
|
+
* Todo.props.note.from.parent.parent(note),
|
|
678
|
+
* (note) => Todo.from({ title, note }),
|
|
679
|
+
* );
|
|
680
|
+
* };
|
|
681
|
+
*
|
|
682
|
+
* const title = NonEmptyTrimmedString100.orThrow("Buy milk");
|
|
683
|
+
* const note = TrimmedString.orThrow("Remember oat milk");
|
|
684
|
+
* const result = saveTodo(title, note);
|
|
685
|
+
*
|
|
686
|
+
* expectTypeOf(result).toEqualTypeOf<
|
|
687
|
+
* Result<typeof Todo.Output, MaxLengthError<100> | MinLengthError<1>>
|
|
688
|
+
* >();
|
|
689
|
+
* expectOk(result, { title, note });
|
|
690
|
+
* ```
|
|
543
691
|
*/
|
|
544
692
|
readonly from: [CustomFrom] extends [never]
|
|
545
693
|
? [Parent] extends [infer P extends TypeNode]
|
|
@@ -554,6 +702,16 @@ export interface Type<
|
|
|
554
702
|
* `to` runs the complete encoding pipeline. Its first `.parent` stops at the
|
|
555
703
|
* immediate parent Output, and each additional suffix stops one Type closer
|
|
556
704
|
* to the root. Every entry point accepts this Type's `Output`.
|
|
705
|
+
*
|
|
706
|
+
* ### Example
|
|
707
|
+
*
|
|
708
|
+
* ```ts
|
|
709
|
+
* import { Int64, Int64FromInt64String } from "@evolu/common";
|
|
710
|
+
*
|
|
711
|
+
* const value = Int64.orThrow(42n);
|
|
712
|
+
*
|
|
713
|
+
* expect(Int64FromInt64String.to(value)).toBe("42");
|
|
714
|
+
* ```
|
|
557
715
|
*/
|
|
558
716
|
readonly to: [Parent] extends [infer P extends TypeNode]
|
|
559
717
|
? ToOperation<Output, CanonicalInput, P>
|
|
@@ -586,6 +744,9 @@ export interface Type<
|
|
|
586
744
|
*
|
|
587
745
|
* // Equivalent because `from.parent` is this Type's deepest `from` operation:
|
|
588
746
|
* const sameValue = getOrThrow(NonEmptyString.from.parent("Evolu"));
|
|
747
|
+
*
|
|
748
|
+
* expect(value).toBe("Evolu");
|
|
749
|
+
* expect(sameValue).toBe(value);
|
|
589
750
|
* ```
|
|
590
751
|
*/
|
|
591
752
|
readonly orThrow: TypeOperationFn<"orThrow", Input, Output, never>;
|
|
@@ -617,12 +778,20 @@ export interface Type<
|
|
|
617
778
|
*
|
|
618
779
|
* // Equivalent because `from.parent` is this Type's deepest `from` operation:
|
|
619
780
|
* const sameValue = getOrNull(NonEmptyString.from.parent("Evolu"));
|
|
781
|
+
*
|
|
782
|
+
* expect(value).toBe("Evolu");
|
|
783
|
+
* expect(sameValue).toBe(value);
|
|
784
|
+
* expect(NonEmptyString.orNull("")).toBeNull();
|
|
620
785
|
* ```
|
|
621
786
|
*/
|
|
622
787
|
readonly orNull: TypeOperationFn<"orNull", Input, Output, never>;
|
|
623
788
|
}
|
|
624
789
|
|
|
625
|
-
/**
|
|
790
|
+
/**
|
|
791
|
+
* A capitalized name identifying a {@link Type} node.
|
|
792
|
+
*
|
|
793
|
+
* @group Core
|
|
794
|
+
*/
|
|
626
795
|
export type TypeName = Capitalize<string>;
|
|
627
796
|
|
|
628
797
|
/**
|
|
@@ -635,7 +804,7 @@ export type TypeName = Capitalize<string>;
|
|
|
635
804
|
* only when it intentionally represents the same error contract. Accidental
|
|
636
805
|
* reuse prevents reliable discriminated-union narrowing.
|
|
637
806
|
*
|
|
638
|
-
* @group Core
|
|
807
|
+
* @group Core
|
|
639
808
|
*/
|
|
640
809
|
export interface TypeError<Name extends TypeName = TypeName> {
|
|
641
810
|
readonly type: Name;
|
|
@@ -653,9 +822,9 @@ interface TransparentTypeError {
|
|
|
653
822
|
* Structural errors such as Array and Union errors extend {@link TypeError}
|
|
654
823
|
* instead because they locate nested errors rather than own one value.
|
|
655
824
|
*
|
|
656
|
-
* @group Core
|
|
825
|
+
* @group Core
|
|
657
826
|
*/
|
|
658
|
-
//
|
|
827
|
+
// Predefined errors intentionally repeat narrower `value` properties. Making
|
|
659
828
|
// `value` generic here and sharing this base regresses `pnpm bench:type`.
|
|
660
829
|
export interface TypeValueError<
|
|
661
830
|
Name extends TypeName = TypeName,
|
|
@@ -666,13 +835,18 @@ export interface TypeValueError<
|
|
|
666
835
|
/**
|
|
667
836
|
* Formats a structured {@link TypeError} as a human-readable message.
|
|
668
837
|
*
|
|
669
|
-
* @group Core
|
|
838
|
+
* @group Core
|
|
670
839
|
*/
|
|
671
840
|
export type TypeErrorFormatter<Error extends TypeError> = (
|
|
672
841
|
error: Error,
|
|
673
842
|
) => string;
|
|
674
843
|
|
|
675
|
-
/**
|
|
844
|
+
/**
|
|
845
|
+
* The common structural shape of every {@link Type}, with its specific type
|
|
846
|
+
* parameters erased.
|
|
847
|
+
*
|
|
848
|
+
* @group Core
|
|
849
|
+
*/
|
|
676
850
|
export interface TypeNode {
|
|
677
851
|
readonly name: TypeName;
|
|
678
852
|
readonly "~standard": StandardSchemaV1.Props<unknown, unknown>;
|
|
@@ -804,7 +978,7 @@ const formatDefaultRuntimeTypeIssue: RuntimeFormatTypeIssue = (issue) =>
|
|
|
804
978
|
* Asserts that a value belongs to a {@link Type} Output domain.
|
|
805
979
|
*
|
|
806
980
|
* Use this for internal invariants, not external input. Validate external input
|
|
807
|
-
* with `Type.fromUnknown` so
|
|
981
|
+
* with `Type.fromUnknown` so validation failures remain typed values. A failed
|
|
808
982
|
* assertion uses the Type name for its message and preserves the exact Output
|
|
809
983
|
* validation error as the thrown Error's cause.
|
|
810
984
|
*
|
|
@@ -824,7 +998,7 @@ const formatDefaultRuntimeTypeIssue: RuntimeFormatTypeIssue = (issue) =>
|
|
|
824
998
|
* >();
|
|
825
999
|
* ```
|
|
826
1000
|
*
|
|
827
|
-
* @group Core
|
|
1001
|
+
* @group Core
|
|
828
1002
|
*/
|
|
829
1003
|
export const assertType: <T extends TypeNode>(
|
|
830
1004
|
type: T,
|
|
@@ -860,34 +1034,32 @@ const assertTypeOutput = <Error extends TypeError>(
|
|
|
860
1034
|
};
|
|
861
1035
|
|
|
862
1036
|
/**
|
|
863
|
-
*
|
|
1037
|
+
* Creates localized copies of selected {@link Type} declarations.
|
|
864
1038
|
*
|
|
865
|
-
*
|
|
866
|
-
*
|
|
867
|
-
*
|
|
868
|
-
*
|
|
869
|
-
*
|
|
870
|
-
* Lazy error interfaces.
|
|
1039
|
+
* Pass the Types used together in one localization scope and formatter maps
|
|
1040
|
+
* keyed by locale. TypeScript infers every formatter required by the selected
|
|
1041
|
+
* Types, including errors from nested structural Types and recursive Lazy
|
|
1042
|
+
* Types. Every locale must provide the complete inferred formatter set; missing
|
|
1043
|
+
* and unrelated formatters are compile-time errors.
|
|
871
1044
|
*
|
|
872
|
-
*
|
|
873
|
-
*
|
|
874
|
-
*
|
|
1045
|
+
* The result preserves the locale names, selected Type names, and exact
|
|
1046
|
+
* TypeScript types. A localized Type validates exactly like its source Type;
|
|
1047
|
+
* only its human-readable `formatError` and Standard Schema messages change.
|
|
1048
|
+
* The source Types remain unchanged.
|
|
875
1049
|
*
|
|
876
|
-
*
|
|
877
|
-
*
|
|
1050
|
+
* Parents and reflected child Types are localized with the same formatter set.
|
|
1051
|
+
* Structural Types retain error paths and delegate nested messages to the Type
|
|
1052
|
+
* that produced them. Different localized Type sets can coexist in separate
|
|
1053
|
+
* application or dependency-injection scopes.
|
|
878
1054
|
*
|
|
879
|
-
*
|
|
880
|
-
*
|
|
881
|
-
*
|
|
882
|
-
*
|
|
1055
|
+
* Localization is scoped to the selected Types instead of a package-wide
|
|
1056
|
+
* translation registry. Static imports give bundlers an explicit dependency
|
|
1057
|
+
* graph, so unrelated Types, locales, and formatters can be removed. Bundling
|
|
1058
|
+
* every locale an app supports also allows language changes without a network
|
|
1059
|
+
* connection.
|
|
883
1060
|
*
|
|
884
|
-
*
|
|
885
|
-
*
|
|
886
|
-
* dependency graph from those Types to their formatters, so unrelated Type and
|
|
887
|
-
* localization code can be removed. An app supplies all locales it supports in
|
|
888
|
-
* the same self-contained bundle, allowing users to change language without a
|
|
889
|
-
* network connection. Different localized Type sets can coexist on one page or
|
|
890
|
-
* in separate dependency-injection scopes.
|
|
1061
|
+
* The selected Type map, locale map, and formatter maps must be plain objects
|
|
1062
|
+
* with own enumerable string-keyed data properties.
|
|
891
1063
|
*
|
|
892
1064
|
* ### Example
|
|
893
1065
|
*
|
|
@@ -897,7 +1069,7 @@ const assertTypeOutput = <Error extends TypeError>(
|
|
|
897
1069
|
*
|
|
898
1070
|
* const Label = minLength(1)(String);
|
|
899
1071
|
*
|
|
900
|
-
* const
|
|
1072
|
+
* const typesByLocale = localizeTypes(
|
|
901
1073
|
* { Label },
|
|
902
1074
|
* {
|
|
903
1075
|
* cs: {
|
|
@@ -907,11 +1079,66 @@ const assertTypeOutput = <Error extends TypeError>(
|
|
|
907
1079
|
* },
|
|
908
1080
|
* );
|
|
909
1081
|
*
|
|
910
|
-
* expectTypeOf<typeof
|
|
1082
|
+
* expectTypeOf<typeof typesByLocale.cs.Label>().toEqualTypeOf<
|
|
911
1083
|
* typeof Label
|
|
912
1084
|
* >();
|
|
1085
|
+
*
|
|
1086
|
+
* const result = typesByLocale.cs.Label.fromUnknown("");
|
|
1087
|
+
* expectErr(result, { type: "MinLength1", min: 1, value: "" });
|
|
1088
|
+
* expect(typesByLocale.cs.Label.formatError(result.error)).toBe(
|
|
1089
|
+
* "Text nesmí být prázdný.",
|
|
1090
|
+
* );
|
|
913
1091
|
* ```
|
|
914
1092
|
*
|
|
1093
|
+
* ### Supported locales
|
|
1094
|
+
*
|
|
1095
|
+
* English is built in; use {@link Type} directly for its default formatters. The
|
|
1096
|
+
* following additional locales are available:
|
|
1097
|
+
*
|
|
1098
|
+
* - Arabic (`ar`)
|
|
1099
|
+
* - Bengali (`bn`)
|
|
1100
|
+
* - Catalan (`ca`)
|
|
1101
|
+
* - Chinese, Simplified (`zhCN`)
|
|
1102
|
+
* - Chinese, Traditional (`zhTW`)
|
|
1103
|
+
* - Croatian (`hr`)
|
|
1104
|
+
* - Czech (`cs`)
|
|
1105
|
+
* - Danish (`da`)
|
|
1106
|
+
* - Dutch (`nl`)
|
|
1107
|
+
* - Filipino (`fil`)
|
|
1108
|
+
* - Finnish (`fi`)
|
|
1109
|
+
* - French (`fr`)
|
|
1110
|
+
* - German (`de`)
|
|
1111
|
+
* - Greek (`el`)
|
|
1112
|
+
* - Hebrew (`he`)
|
|
1113
|
+
* - Hindi (`hi`)
|
|
1114
|
+
* - Hungarian (`hu`)
|
|
1115
|
+
* - Indonesian (`id`)
|
|
1116
|
+
* - Italian (`it`)
|
|
1117
|
+
* - Japanese (`ja`)
|
|
1118
|
+
* - Korean (`ko`)
|
|
1119
|
+
* - Malay (`ms`)
|
|
1120
|
+
* - Malayalam (`ml`)
|
|
1121
|
+
* - Marathi (`mr`)
|
|
1122
|
+
* - Norwegian Bokmål (`nb`)
|
|
1123
|
+
* - Persian (`fa`)
|
|
1124
|
+
* - Polish (`pl`)
|
|
1125
|
+
* - Portuguese (`pt`)
|
|
1126
|
+
* - Portuguese, Brazilian (`ptBR`)
|
|
1127
|
+
* - Punjabi (`pa`)
|
|
1128
|
+
* - Romanian (`ro`)
|
|
1129
|
+
* - Slovak (`sk`)
|
|
1130
|
+
* - Slovenian (`sl`)
|
|
1131
|
+
* - Spanish (`es`)
|
|
1132
|
+
* - Swahili (`sw`)
|
|
1133
|
+
* - Swedish (`sv`)
|
|
1134
|
+
* - Tamil (`ta`)
|
|
1135
|
+
* - Telugu (`te`)
|
|
1136
|
+
* - Thai (`th`)
|
|
1137
|
+
* - Turkish (`tr`)
|
|
1138
|
+
* - Ukrainian (`uk`)
|
|
1139
|
+
* - Urdu (`ur`)
|
|
1140
|
+
* - Vietnamese (`vi`)
|
|
1141
|
+
*
|
|
915
1142
|
* @group Localization
|
|
916
1143
|
*/
|
|
917
1144
|
export const localizeTypes = ((
|
|
@@ -1277,16 +1504,21 @@ declare const identityEncodingSymbol: unique symbol;
|
|
|
1277
1504
|
* age: optional(PositiveInt),
|
|
1278
1505
|
* });
|
|
1279
1506
|
* interface User extends InferType<typeof User> {}
|
|
1507
|
+
*
|
|
1508
|
+
* const user = User.orThrow({ name: "Ada", age: 37 });
|
|
1509
|
+
*
|
|
1510
|
+
* expectTypeOf(user).toExtend<User>();
|
|
1511
|
+
* expect(user.name).toBe("Ada");
|
|
1280
1512
|
* ```
|
|
1281
1513
|
*
|
|
1282
|
-
* @group
|
|
1514
|
+
* @group Core
|
|
1283
1515
|
*/
|
|
1284
1516
|
export type InferType<T extends TypeNode> = T["Output"];
|
|
1285
1517
|
|
|
1286
1518
|
/**
|
|
1287
1519
|
* The union of errors a {@link Type} can return from `fromUnknown`.
|
|
1288
1520
|
*
|
|
1289
|
-
* @group
|
|
1521
|
+
* @group Core
|
|
1290
1522
|
*/
|
|
1291
1523
|
export type InferErrors<T extends TypeNode> = T[typeof errorsSymbol];
|
|
1292
1524
|
|
|
@@ -1501,7 +1733,7 @@ type TypeOperationFn<
|
|
|
1501
1733
|
/**
|
|
1502
1734
|
* Configures how container {@link Type} operations report errors.
|
|
1503
1735
|
*
|
|
1504
|
-
* @group Core
|
|
1736
|
+
* @group Core
|
|
1505
1737
|
*/
|
|
1506
1738
|
export interface ValidationOptions {
|
|
1507
1739
|
/** Controls whether container {@link Type} operations return one or all errors. */
|
|
@@ -1637,7 +1869,7 @@ type ConcreteChildTypeNameError = CompileTimeError<
|
|
|
1637
1869
|
* expectErr(Text.fromUnknown(42), { type: "Text", value: 42 });
|
|
1638
1870
|
* ```
|
|
1639
1871
|
*
|
|
1640
|
-
* @group
|
|
1872
|
+
* @group Construction
|
|
1641
1873
|
*/
|
|
1642
1874
|
export function createType<
|
|
1643
1875
|
Name extends TypeName,
|
|
@@ -1649,6 +1881,8 @@ export function createType<
|
|
|
1649
1881
|
// Validation alone determines Error; broad formatters must not widen it.
|
|
1650
1882
|
formatError: TypeErrorFormatter<NoInfer<Error>>,
|
|
1651
1883
|
): Type<Name, Output, Output, Error, null, Error, never, Output>;
|
|
1884
|
+
|
|
1885
|
+
/** Creates an infallible child Type that preserves its parent's Output. */
|
|
1652
1886
|
export function createType<
|
|
1653
1887
|
Name extends TypeName,
|
|
1654
1888
|
ParentType extends ConcreteTypeNode,
|
|
@@ -1669,6 +1903,8 @@ export function createType<
|
|
|
1669
1903
|
CanonicalInputForChild<ParentType, ParentType["Output"]>,
|
|
1670
1904
|
IdentityEncodingOf<ParentType>
|
|
1671
1905
|
>;
|
|
1906
|
+
|
|
1907
|
+
/** Creates a fallible child Type that narrows its parent's Output. */
|
|
1672
1908
|
export function createType<
|
|
1673
1909
|
Name extends TypeName,
|
|
1674
1910
|
ParentType extends ConcreteTypeNode,
|
|
@@ -1887,12 +2123,19 @@ const createChildType = <
|
|
|
1887
2123
|
* `from` accepts the semantic Output. `from.parent` converts the parent Output
|
|
1888
2124
|
* to the output Type Input, then runs the complete output Type pipeline. `to`
|
|
1889
2125
|
* canonically encodes every output Type value before converting it back through
|
|
1890
|
-
* the parent Type.
|
|
1891
|
-
*
|
|
1892
|
-
*
|
|
1893
|
-
*
|
|
1894
|
-
*
|
|
1895
|
-
*
|
|
2126
|
+
* the parent Type.
|
|
2127
|
+
*
|
|
2128
|
+
* The callbacks must form a lawful codec: decoding `to(output)` must reproduce
|
|
2129
|
+
* the same semantic Output for every valid output value. Encoding may
|
|
2130
|
+
* canonicalize multiple parent representations, but it must be total and must
|
|
2131
|
+
* not lose distinctions present in the Output domain.
|
|
2132
|
+
*
|
|
2133
|
+
* Transformation callbacks are Type construction code. Their successful results
|
|
2134
|
+
* are asserted against the declared boundary so a broken callback fails as a
|
|
2135
|
+
* developer error rather than becoming a validation error. Like all
|
|
2136
|
+
* Type-construction callbacks, they are trusted to follow their declared
|
|
2137
|
+
* TypeScript types. A `Result<_, never>` callback is therefore trusted never to
|
|
2138
|
+
* return an `Err`.
|
|
1896
2139
|
*
|
|
1897
2140
|
* Errors from the parent and the forward callback remain unchanged. A forward
|
|
1898
2141
|
* callback error must use the transformation name as its type. Errors from the
|
|
@@ -1905,18 +2148,24 @@ const createChildType = <
|
|
|
1905
2148
|
* ### Example
|
|
1906
2149
|
*
|
|
1907
2150
|
* ```ts
|
|
1908
|
-
* import {
|
|
2151
|
+
* import { Boolean, literal, ok, transform, union } from "@evolu/common";
|
|
1909
2152
|
*
|
|
1910
|
-
* const
|
|
1911
|
-
*
|
|
1912
|
-
*
|
|
1913
|
-
*
|
|
2153
|
+
* const BooleanString = union(literal("false"), literal("true"));
|
|
2154
|
+
* const BooleanFromString = transform(
|
|
2155
|
+
* "BooleanFromString",
|
|
2156
|
+
* BooleanString,
|
|
2157
|
+
* Boolean,
|
|
2158
|
+
* {
|
|
2159
|
+
* from: (value) => ok(value === "true"),
|
|
2160
|
+
* to: (value) => (value ? "true" : "false"),
|
|
2161
|
+
* },
|
|
2162
|
+
* );
|
|
1914
2163
|
*
|
|
1915
|
-
* expectOk(
|
|
1916
|
-
*
|
|
2164
|
+
* expectOk(BooleanFromString.from.parent("true"), true);
|
|
2165
|
+
* expect(BooleanFromString.to(false)).toBe("false");
|
|
1917
2166
|
* ```
|
|
1918
2167
|
*
|
|
1919
|
-
* @group
|
|
2168
|
+
* @group Construction
|
|
1920
2169
|
*/
|
|
1921
2170
|
export function transform<
|
|
1922
2171
|
Name extends TypeName,
|
|
@@ -1934,6 +2183,8 @@ export function transform<
|
|
|
1934
2183
|
readonly to: (value: CanonicalInputOf<OutputType>) => ToOutput;
|
|
1935
2184
|
},
|
|
1936
2185
|
): TransformType<ParentType, OutputType, Name, never, ToOutput>;
|
|
2186
|
+
|
|
2187
|
+
/** Creates a fallible transformed Type with its own error formatter. */
|
|
1937
2188
|
export function transform<
|
|
1938
2189
|
Name extends TypeName,
|
|
1939
2190
|
ParentType extends ConcreteTypeNode,
|
|
@@ -2038,7 +2289,11 @@ export function transform(
|
|
|
2038
2289
|
);
|
|
2039
2290
|
}
|
|
2040
2291
|
|
|
2041
|
-
/**
|
|
2292
|
+
/**
|
|
2293
|
+
* The {@link Type} returned by {@link transform}.
|
|
2294
|
+
*
|
|
2295
|
+
* @group Construction
|
|
2296
|
+
*/
|
|
2042
2297
|
export interface TransformType<
|
|
2043
2298
|
ParentType extends TypeNode,
|
|
2044
2299
|
OutputType extends TypeNode,
|
|
@@ -2065,7 +2320,12 @@ export interface TransformType<
|
|
|
2065
2320
|
readonly output: OutputType;
|
|
2066
2321
|
}
|
|
2067
2322
|
|
|
2068
|
-
/**
|
|
2323
|
+
/**
|
|
2324
|
+
* An error produced by {@link transform} while decoding or validating its
|
|
2325
|
+
* output.
|
|
2326
|
+
*
|
|
2327
|
+
* @group Construction
|
|
2328
|
+
*/
|
|
2069
2329
|
export type TransformError<
|
|
2070
2330
|
Name extends TypeName,
|
|
2071
2331
|
OwnError extends TypeError<Name>,
|
|
@@ -2079,7 +2339,7 @@ export type TransformError<
|
|
|
2079
2339
|
/**
|
|
2080
2340
|
* Wraps an error produced by the output {@link Type} of {@link transform}.
|
|
2081
2341
|
*
|
|
2082
|
-
* @group
|
|
2342
|
+
* @group Construction
|
|
2083
2343
|
*/
|
|
2084
2344
|
export interface TransformOutputError<
|
|
2085
2345
|
Name extends TypeName,
|
|
@@ -2345,14 +2605,22 @@ const addRuntimeAssertions = (
|
|
|
2345
2605
|
return asserted;
|
|
2346
2606
|
};
|
|
2347
2607
|
|
|
2348
|
-
/**
|
|
2608
|
+
/**
|
|
2609
|
+
* An infallible {@link Type} accepting every value.
|
|
2610
|
+
*
|
|
2611
|
+
* @group Base
|
|
2612
|
+
*/
|
|
2349
2613
|
export const Unknown = /*#__PURE__*/ createRootType<"Unknown", unknown, never>(
|
|
2350
2614
|
"Unknown",
|
|
2351
2615
|
ok,
|
|
2352
2616
|
identity,
|
|
2353
2617
|
);
|
|
2354
2618
|
|
|
2355
|
-
/**
|
|
2619
|
+
/**
|
|
2620
|
+
* A {@link Type} rejecting every value.
|
|
2621
|
+
*
|
|
2622
|
+
* @group Base
|
|
2623
|
+
*/
|
|
2356
2624
|
export const Never = /*#__PURE__*/ createRootType(
|
|
2357
2625
|
"Never",
|
|
2358
2626
|
(value): Result<never, NeverError> => err({ type: "Never", value }),
|
|
@@ -2360,6 +2628,11 @@ export const Never = /*#__PURE__*/ createRootType(
|
|
|
2360
2628
|
`A value ${safelyStringifyUnknownValue(error.value)} is not valid for type Never.`,
|
|
2361
2629
|
);
|
|
2362
2630
|
|
|
2631
|
+
/**
|
|
2632
|
+
* Error returned by {@link Never} for every value.
|
|
2633
|
+
*
|
|
2634
|
+
* @group Base
|
|
2635
|
+
*/
|
|
2363
2636
|
export interface NeverError extends TypeError<"Never"> {
|
|
2364
2637
|
readonly value: unknown;
|
|
2365
2638
|
}
|
|
@@ -2398,7 +2671,11 @@ interface TypeOfOutputByName {
|
|
|
2398
2671
|
readonly Function: globalThis.Function;
|
|
2399
2672
|
}
|
|
2400
2673
|
|
|
2401
|
-
/**
|
|
2674
|
+
/**
|
|
2675
|
+
* Error returned when `typeof` does not match the expected JavaScript type.
|
|
2676
|
+
*
|
|
2677
|
+
* @group Base
|
|
2678
|
+
*/
|
|
2402
2679
|
export interface TypeOfError<
|
|
2403
2680
|
Name extends keyof TypeOfOutputByName,
|
|
2404
2681
|
> extends TypeError<"TypeOf"> {
|
|
@@ -2429,6 +2706,8 @@ export interface TypeOfError<
|
|
|
2429
2706
|
* expectTypeOf<WireValue100>().toEqualTypeOf<
|
|
2430
2707
|
* string & Brand<"MaxLength100">
|
|
2431
2708
|
* >();
|
|
2709
|
+
* expectOk(WireValue100.fromUnknown(""), "");
|
|
2710
|
+
* expectOk(WireValue100.fromUnknown(" value "), " value ");
|
|
2432
2711
|
* ```
|
|
2433
2712
|
*
|
|
2434
2713
|
* @group String
|
|
@@ -2505,25 +2784,41 @@ export const String = /*#__PURE__*/ createTypeOfType("String");
|
|
|
2505
2784
|
*/
|
|
2506
2785
|
export const Number = /*#__PURE__*/ createTypeOfType("Number");
|
|
2507
2786
|
|
|
2508
|
-
/**
|
|
2787
|
+
/**
|
|
2788
|
+
* A JavaScript bigint {@link Type}.
|
|
2789
|
+
*
|
|
2790
|
+
* @group Base
|
|
2791
|
+
*/
|
|
2509
2792
|
export const BigInt = /*#__PURE__*/ createTypeOfType("BigInt");
|
|
2510
2793
|
|
|
2511
|
-
/**
|
|
2794
|
+
/**
|
|
2795
|
+
* A JavaScript boolean {@link Type}.
|
|
2796
|
+
*
|
|
2797
|
+
* @group Base
|
|
2798
|
+
*/
|
|
2512
2799
|
export const Boolean = /*#__PURE__*/ createTypeOfType("Boolean");
|
|
2513
2800
|
|
|
2514
|
-
/**
|
|
2801
|
+
/**
|
|
2802
|
+
* A JavaScript symbol {@link Type}.
|
|
2803
|
+
*
|
|
2804
|
+
* @group Base
|
|
2805
|
+
*/
|
|
2515
2806
|
export const Symbol = /*#__PURE__*/ createTypeOfType("Symbol");
|
|
2516
2807
|
|
|
2517
|
-
/**
|
|
2808
|
+
/**
|
|
2809
|
+
* A JavaScript function {@link Type}.
|
|
2810
|
+
*
|
|
2811
|
+
* @group Base
|
|
2812
|
+
*/
|
|
2518
2813
|
export const Function = /*#__PURE__*/ createTypeOfType("Function");
|
|
2519
2814
|
|
|
2520
2815
|
/**
|
|
2521
|
-
*
|
|
2816
|
+
* A {@link Type} validating Evolu Type declarations.
|
|
2522
2817
|
*
|
|
2523
2818
|
* This is useful when a Type itself crosses an unknown boundary or must be
|
|
2524
2819
|
* asserted with {@link assertType}.
|
|
2525
2820
|
*
|
|
2526
|
-
* @group Core
|
|
2821
|
+
* @group Core
|
|
2527
2822
|
*/
|
|
2528
2823
|
export const EvoluType = /*#__PURE__*/ createType(
|
|
2529
2824
|
"EvoluType",
|
|
@@ -2545,23 +2840,23 @@ export const EvoluType = /*#__PURE__*/ createType(
|
|
|
2545
2840
|
* the recursive `TypeNode` shape so composing Types does not repeatedly add its
|
|
2546
2841
|
* compiler cost; {@link EvoluType} bridges that runtime evidence to this type.
|
|
2547
2842
|
*
|
|
2548
|
-
* @group Core
|
|
2843
|
+
* @group Core
|
|
2549
2844
|
*/
|
|
2550
2845
|
export interface AnyType extends TypeNode {
|
|
2551
2846
|
readonly [concreteTypeSymbol]: true;
|
|
2552
2847
|
}
|
|
2553
2848
|
|
|
2554
2849
|
/**
|
|
2555
|
-
* Error returned when
|
|
2850
|
+
* Error returned when {@link EvoluType} rejects a value.
|
|
2556
2851
|
*
|
|
2557
|
-
* @group Core
|
|
2852
|
+
* @group Core
|
|
2558
2853
|
*/
|
|
2559
2854
|
export interface EvoluTypeError extends TypeValueError<"EvoluType"> {}
|
|
2560
2855
|
|
|
2561
2856
|
/**
|
|
2562
2857
|
* Nominal evidence that a value has one object tag.
|
|
2563
2858
|
*
|
|
2564
|
-
* @group Base
|
|
2859
|
+
* @group Base
|
|
2565
2860
|
*/
|
|
2566
2861
|
export interface ObjectTag<Name extends TypeName> {
|
|
2567
2862
|
readonly [objectTagSymbol]: Name;
|
|
@@ -2570,7 +2865,7 @@ export interface ObjectTag<Name extends TypeName> {
|
|
|
2570
2865
|
/**
|
|
2571
2866
|
* The {@link Type} returned by {@link objectTag}.
|
|
2572
2867
|
*
|
|
2573
|
-
* @group Base
|
|
2868
|
+
* @group Base
|
|
2574
2869
|
*/
|
|
2575
2870
|
export interface ObjectTagType<
|
|
2576
2871
|
Name extends TypeName,
|
|
@@ -2597,7 +2892,7 @@ export interface ObjectTagType<
|
|
|
2597
2892
|
/**
|
|
2598
2893
|
* An error returned when an object does not report the expected tag.
|
|
2599
2894
|
*
|
|
2600
|
-
* @group Base
|
|
2895
|
+
* @group Base
|
|
2601
2896
|
*/
|
|
2602
2897
|
export interface ObjectTagError<
|
|
2603
2898
|
Expected extends TypeName = TypeName,
|
|
@@ -2615,20 +2910,31 @@ interface ObjectTagOutputByName {
|
|
|
2615
2910
|
/**
|
|
2616
2911
|
* Realm-neutral {@link Type} trusting an object's reported tag.
|
|
2617
2912
|
*
|
|
2618
|
-
* Predefined built-
|
|
2619
|
-
* that trusted code does not forge their tags. They do not
|
|
2620
|
-
* internal slots. A custom tag refines the supplied Type and adds
|
|
2621
|
-
* evidence to its Output, so only a value validated by the resulting
|
|
2622
|
-
* accepted by its typed operations.
|
|
2913
|
+
* Predefined tags for JavaScript built-ins expose their native Output type
|
|
2914
|
+
* under the assumption that trusted code does not forge their tags. They do not
|
|
2915
|
+
* verify native internal slots. A custom tag refines the supplied Type and adds
|
|
2916
|
+
* nominal evidence to its Output, so only a value validated by the resulting
|
|
2917
|
+
* Type is accepted by its typed operations.
|
|
2918
|
+
*
|
|
2919
|
+
* `Object.prototype.toString` recognizes legitimate JavaScript built-ins from
|
|
2920
|
+
* another realm, but any object can customize the result with
|
|
2921
|
+
* `Symbol.toStringTag`. Types returned by this factory therefore classify
|
|
2922
|
+
* trusted values; they are not security boundaries. Passing a forged JavaScript
|
|
2923
|
+
* built-in tag violates the trust assumption of the predefined Type. Primitive
|
|
2924
|
+
* Outputs are rejected at compile time.
|
|
2925
|
+
*
|
|
2926
|
+
* ### Example
|
|
2927
|
+
*
|
|
2928
|
+
* ```ts
|
|
2929
|
+
* import { objectTag } from "@evolu/common";
|
|
2930
|
+
*
|
|
2931
|
+
* const DateType = objectTag("Date");
|
|
2932
|
+
* const date = new globalThis.Date("2025-01-01T00:00:00.000Z");
|
|
2623
2933
|
*
|
|
2624
|
-
*
|
|
2625
|
-
*
|
|
2626
|
-
* Types returned by this factory therefore classify trusted values; they are
|
|
2627
|
-
* not security boundaries. Passing a forged built-in tag violates the trust
|
|
2628
|
-
* assumption of the predefined Type. Primitive Outputs are rejected at compile
|
|
2629
|
-
* time.
|
|
2934
|
+
* expectOk(DateType.fromUnknown(date), date);
|
|
2935
|
+
* ```
|
|
2630
2936
|
*
|
|
2631
|
-
* @group Base
|
|
2937
|
+
* @group Base
|
|
2632
2938
|
*/
|
|
2633
2939
|
export function objectTag<Name extends keyof ObjectTagOutputByName>(
|
|
2634
2940
|
name: ValidateConcreteTypeName<Name>,
|
|
@@ -2642,6 +2948,8 @@ export function objectTag<Name extends keyof ObjectTagOutputByName>(
|
|
|
2642
2948
|
never,
|
|
2643
2949
|
ObjectTagOutputByName[Name]
|
|
2644
2950
|
>;
|
|
2951
|
+
|
|
2952
|
+
/** Creates an object-tag Type by refining an existing object Type. */
|
|
2645
2953
|
export function objectTag<
|
|
2646
2954
|
Name extends TypeName,
|
|
2647
2955
|
OutputType extends ConcreteTypeNode & { readonly Output: object },
|
|
@@ -2656,6 +2964,9 @@ export function objectTag(
|
|
|
2656
2964
|
name: TypeName,
|
|
2657
2965
|
outputType?: ConcreteTypeNode & { readonly Output: object },
|
|
2658
2966
|
): TypeNode {
|
|
2967
|
+
const formatError: TypeErrorFormatter<ObjectTagError> = (error) =>
|
|
2968
|
+
`A value ${safelyStringifyUnknownValue(error.value)} does not have the expected object tag ${safelyStringifyUnknownValue(error.expected)}.`;
|
|
2969
|
+
|
|
2659
2970
|
if (outputType === undefined) {
|
|
2660
2971
|
return createRootType(
|
|
2661
2972
|
name,
|
|
@@ -2663,7 +2974,7 @@ export function objectTag(
|
|
|
2663
2974
|
hasObjectTag(value, name)
|
|
2664
2975
|
? ok(value as object)
|
|
2665
2976
|
: err({ type: "ObjectTag", expected: name, value }),
|
|
2666
|
-
|
|
2977
|
+
formatError,
|
|
2667
2978
|
);
|
|
2668
2979
|
}
|
|
2669
2980
|
|
|
@@ -2675,7 +2986,7 @@ export function objectTag(
|
|
|
2675
2986
|
hasObjectTag(value, name)
|
|
2676
2987
|
? ok(value as object & ObjectTag<TypeName>)
|
|
2677
2988
|
: err({ type: "ObjectTag", expected: name, value }),
|
|
2678
|
-
|
|
2989
|
+
formatError,
|
|
2679
2990
|
),
|
|
2680
2991
|
{ expected: name },
|
|
2681
2992
|
);
|
|
@@ -2683,9 +2994,6 @@ export function objectTag(
|
|
|
2683
2994
|
|
|
2684
2995
|
declare const objectTagSymbol: unique symbol;
|
|
2685
2996
|
|
|
2686
|
-
const formatObjectTagError: TypeErrorFormatter<ObjectTagError> = (error) =>
|
|
2687
|
-
`A value ${safelyStringifyUnknownValue(error.value)} does not have the object tag ${safelyStringifyUnknownValue(error.expected)}.`;
|
|
2688
|
-
|
|
2689
2997
|
const hasObjectTag = (value: unknown, expected: string): boolean =>
|
|
2690
2998
|
value !== null &&
|
|
2691
2999
|
(typeof value === "object" || typeof value === "function") &&
|
|
@@ -2696,7 +3004,7 @@ const hasObjectTag = (value: unknown, expected: string): boolean =>
|
|
|
2696
3004
|
*
|
|
2697
3005
|
* It trusts the reported object tag and does not verify Date internal slots.
|
|
2698
3006
|
*
|
|
2699
|
-
* @group Base
|
|
3007
|
+
* @group Base
|
|
2700
3008
|
*/
|
|
2701
3009
|
export const Date = /*#__PURE__*/ objectTag("Date");
|
|
2702
3010
|
|
|
@@ -2706,7 +3014,7 @@ export const Date = /*#__PURE__*/ objectTag("Date");
|
|
|
2706
3014
|
* It trusts the reported object tag and does not verify Uint8Array internal
|
|
2707
3015
|
* slots.
|
|
2708
3016
|
*
|
|
2709
|
-
* @group Base
|
|
3017
|
+
* @group Base
|
|
2710
3018
|
*/
|
|
2711
3019
|
export const Uint8Array = /*#__PURE__*/ objectTag("Uint8Array");
|
|
2712
3020
|
|
|
@@ -2716,7 +3024,7 @@ export const Uint8Array = /*#__PURE__*/ objectTag("Uint8Array");
|
|
|
2716
3024
|
* It trusts the reported object tag and does not verify ArrayBuffer internal
|
|
2717
3025
|
* slots.
|
|
2718
3026
|
*
|
|
2719
|
-
* @group Base
|
|
3027
|
+
* @group Base
|
|
2720
3028
|
*/
|
|
2721
3029
|
export const ArrayBuffer = /*#__PURE__*/ objectTag("ArrayBuffer");
|
|
2722
3030
|
|
|
@@ -2748,7 +3056,7 @@ export const ArrayBuffer = /*#__PURE__*/ objectTag("ArrayBuffer");
|
|
|
2748
3056
|
* assert(!UserInstance.is({ name: "Ada" }));
|
|
2749
3057
|
* ```
|
|
2750
3058
|
*
|
|
2751
|
-
* @group Base
|
|
3059
|
+
* @group Base
|
|
2752
3060
|
*/
|
|
2753
3061
|
export const instanceOf = <Constructor extends InstanceConstructor>(
|
|
2754
3062
|
constructor: ValidateInstanceConstructor<Constructor>,
|
|
@@ -2779,16 +3087,20 @@ export const instanceOf = <Constructor extends InstanceConstructor>(
|
|
|
2779
3087
|
/**
|
|
2780
3088
|
* A JavaScript class constructor accepted by {@link instanceOf}.
|
|
2781
3089
|
*
|
|
2782
|
-
* @group Base
|
|
3090
|
+
* @group Base
|
|
2783
3091
|
*/
|
|
2784
3092
|
export type InstanceConstructor<Instance extends object = object> =
|
|
2785
3093
|
(abstract new (...args: ReadonlyArray<never>) => Instance) & {
|
|
2786
3094
|
readonly name: string;
|
|
2787
3095
|
};
|
|
2788
3096
|
|
|
2789
|
-
/**
|
|
2790
|
-
|
|
2791
|
-
|
|
3097
|
+
/**
|
|
3098
|
+
* The {@link Type} returned by {@link instanceOf}.
|
|
3099
|
+
*
|
|
3100
|
+
* @group Base
|
|
3101
|
+
*/
|
|
3102
|
+
export interface InstanceOfType<
|
|
3103
|
+
Constructor extends InstanceConstructor,
|
|
2792
3104
|
> extends Type<
|
|
2793
3105
|
"InstanceOf",
|
|
2794
3106
|
InstanceOfOutput<Constructor>,
|
|
@@ -2807,7 +3119,11 @@ type InstanceOfOutput<Constructor extends InstanceConstructor> =
|
|
|
2807
3119
|
? Output
|
|
2808
3120
|
: InstanceType<Constructor>;
|
|
2809
3121
|
|
|
2810
|
-
/**
|
|
3122
|
+
/**
|
|
3123
|
+
* Error returned when a value is not an instance of the expected constructor.
|
|
3124
|
+
*
|
|
3125
|
+
* @group Base
|
|
3126
|
+
*/
|
|
2811
3127
|
export interface InstanceOfError extends TypeValueError<"InstanceOf"> {
|
|
2812
3128
|
readonly constructorName: string;
|
|
2813
3129
|
}
|
|
@@ -2895,7 +3211,11 @@ export const literal = <const Expected extends Literal>(
|
|
|
2895
3211
|
) as unknown as LiteralType<Expected>;
|
|
2896
3212
|
};
|
|
2897
3213
|
|
|
2898
|
-
/**
|
|
3214
|
+
/**
|
|
3215
|
+
* The {@link Type} returned by {@link literal}.
|
|
3216
|
+
*
|
|
3217
|
+
* @group Unions
|
|
3218
|
+
*/
|
|
2899
3219
|
export interface LiteralType<Expected extends Literal> extends Type<
|
|
2900
3220
|
"Literal",
|
|
2901
3221
|
WidenLiteral<Expected>,
|
|
@@ -2941,7 +3261,11 @@ type LiteralCompileTimeError = CompileTimeError<
|
|
|
2941
3261
|
"Expected must be one concrete literal value."
|
|
2942
3262
|
>;
|
|
2943
3263
|
|
|
2944
|
-
/**
|
|
3264
|
+
/**
|
|
3265
|
+
* Error returned when a value does not equal the expected literal.
|
|
3266
|
+
*
|
|
3267
|
+
* @group Unions
|
|
3268
|
+
*/
|
|
2945
3269
|
export interface LiteralError<
|
|
2946
3270
|
Expected extends Literal = Literal,
|
|
2947
3271
|
> extends TypeError<"Literal"> {
|
|
@@ -2949,10 +3273,18 @@ export interface LiteralError<
|
|
|
2949
3273
|
readonly value: unknown;
|
|
2950
3274
|
}
|
|
2951
3275
|
|
|
2952
|
-
/**
|
|
3276
|
+
/**
|
|
3277
|
+
* Literal {@link Type} accepting only `undefined`.
|
|
3278
|
+
*
|
|
3279
|
+
* @group Unions
|
|
3280
|
+
*/
|
|
2953
3281
|
export const Undefined = /*#__PURE__*/ literal(undefined);
|
|
2954
3282
|
|
|
2955
|
-
/**
|
|
3283
|
+
/**
|
|
3284
|
+
* Literal {@link Type} accepting only `null`.
|
|
3285
|
+
*
|
|
3286
|
+
* @group Unions
|
|
3287
|
+
*/
|
|
2956
3288
|
export const Null = /*#__PURE__*/ literal(null);
|
|
2957
3289
|
|
|
2958
3290
|
/**
|
|
@@ -2978,7 +3310,10 @@ export const Null = /*#__PURE__*/ literal(null);
|
|
|
2978
3310
|
* within each member.
|
|
2979
3311
|
*
|
|
2980
3312
|
* Member order matters when multiple members accept the same value: validation
|
|
2981
|
-
* and encoding use the first matching member.
|
|
3313
|
+
* and encoding use the first matching member. When member Inputs overlap,
|
|
3314
|
+
* decoding the value emitted by the first member selected for an Output must
|
|
3315
|
+
* reproduce that semantic Output; otherwise the Union violates the round-trip
|
|
3316
|
+
* law.
|
|
2982
3317
|
*
|
|
2983
3318
|
* ### Example
|
|
2984
3319
|
*
|
|
@@ -3017,11 +3352,15 @@ export function union<const Expected extends AtLeastTwoReadonlyArray<Literal>>(
|
|
|
3017
3352
|
): UnionType<{
|
|
3018
3353
|
readonly [Index in keyof Expected]: LiteralType<Expected[Index]>;
|
|
3019
3354
|
}>;
|
|
3355
|
+
|
|
3356
|
+
/** Creates a Union Type from Type members. */
|
|
3020
3357
|
export function union<const Members extends AtLeastTwoReadonlyArray<TypeNode>>(
|
|
3021
3358
|
...members: {
|
|
3022
3359
|
readonly [Index in keyof Members]: ValidateUnionTypeMember<Members[Index]>;
|
|
3023
3360
|
}
|
|
3024
3361
|
): UnionType<Members>;
|
|
3362
|
+
|
|
3363
|
+
/** Creates a Union Type from Type and literal members. */
|
|
3025
3364
|
export function union<
|
|
3026
3365
|
const Members extends AtLeastTwoReadonlyArray<TypeNode | Literal>,
|
|
3027
3366
|
>(
|
|
@@ -3046,7 +3385,8 @@ export function union(
|
|
|
3046
3385
|
inputMembers,
|
|
3047
3386
|
(member, value, options) => member[outputValidationSymbol](value, options),
|
|
3048
3387
|
);
|
|
3049
|
-
const defaultFormatter =
|
|
3388
|
+
const defaultFormatter = (() =>
|
|
3389
|
+
"A value does not match any allowed variant.") as TypeErrorFormatter<TypeError>;
|
|
3050
3390
|
const getTypeIssues: RuntimeGetTypeIssues = (error) =>
|
|
3051
3391
|
singleRuntimeTypeIssue("Union", error, defaultFormatter);
|
|
3052
3392
|
const input = createTypeNode<
|
|
@@ -3101,9 +3441,6 @@ export function union(
|
|
|
3101
3441
|
);
|
|
3102
3442
|
}
|
|
3103
3443
|
|
|
3104
|
-
const formatUnionError: TypeErrorFormatter<UnionErrorValue<TypeError>> = () =>
|
|
3105
|
-
"A value does not match any union member.";
|
|
3106
|
-
|
|
3107
3444
|
const createUnionValidation =
|
|
3108
3445
|
(
|
|
3109
3446
|
members: ReadonlyArray<RuntimeTypeNode>,
|
|
@@ -3146,6 +3483,16 @@ const createUnionValidation =
|
|
|
3146
3483
|
* This does not make an object property optional. It changes only the values
|
|
3147
3484
|
* accepted when the property is present.
|
|
3148
3485
|
*
|
|
3486
|
+
* ### Example
|
|
3487
|
+
*
|
|
3488
|
+
* ```ts
|
|
3489
|
+
* import { String, undefinedOr } from "@evolu/common";
|
|
3490
|
+
*
|
|
3491
|
+
* const StringOrUndefined = undefinedOr(String);
|
|
3492
|
+
*
|
|
3493
|
+
* expectOk(StringOrUndefined.fromUnknown(undefined), undefined);
|
|
3494
|
+
* ```
|
|
3495
|
+
*
|
|
3149
3496
|
* @group Unions
|
|
3150
3497
|
*/
|
|
3151
3498
|
export const undefinedOr = <ValueType extends TypeNode>(
|
|
@@ -3155,6 +3502,16 @@ export const undefinedOr = <ValueType extends TypeNode>(
|
|
|
3155
3502
|
/**
|
|
3156
3503
|
* Union {@link Type} containing the supplied Type and `null`.
|
|
3157
3504
|
*
|
|
3505
|
+
* ### Example
|
|
3506
|
+
*
|
|
3507
|
+
* ```ts
|
|
3508
|
+
* import { String, nullOr } from "@evolu/common";
|
|
3509
|
+
*
|
|
3510
|
+
* const NullableString = nullOr(String);
|
|
3511
|
+
*
|
|
3512
|
+
* expectOk(NullableString.fromUnknown(null), null);
|
|
3513
|
+
* ```
|
|
3514
|
+
*
|
|
3158
3515
|
* @group Unions
|
|
3159
3516
|
*/
|
|
3160
3517
|
export const nullOr = <ValueType extends TypeNode>(
|
|
@@ -3164,6 +3521,17 @@ export const nullOr = <ValueType extends TypeNode>(
|
|
|
3164
3521
|
/**
|
|
3165
3522
|
* Union {@link Type} containing the supplied Type, `null`, and `undefined`.
|
|
3166
3523
|
*
|
|
3524
|
+
* ### Example
|
|
3525
|
+
*
|
|
3526
|
+
* ```ts
|
|
3527
|
+
* import { String, nullishOr } from "@evolu/common";
|
|
3528
|
+
*
|
|
3529
|
+
* const NullishString = nullishOr(String);
|
|
3530
|
+
*
|
|
3531
|
+
* expectOk(NullishString.fromUnknown(undefined), undefined);
|
|
3532
|
+
* expectOk(NullishString.fromUnknown(null), null);
|
|
3533
|
+
* ```
|
|
3534
|
+
*
|
|
3167
3535
|
* @group Unions
|
|
3168
3536
|
*/
|
|
3169
3537
|
export const nullishOr = <ValueType extends TypeNode>(
|
|
@@ -3205,7 +3573,11 @@ type NormalizeUnionMembers<
|
|
|
3205
3573
|
? Normalized
|
|
3206
3574
|
: never;
|
|
3207
3575
|
|
|
3208
|
-
/**
|
|
3576
|
+
/**
|
|
3577
|
+
* The {@link Type} returned by {@link union}.
|
|
3578
|
+
*
|
|
3579
|
+
* @group Unions
|
|
3580
|
+
*/
|
|
3209
3581
|
export interface UnionType<
|
|
3210
3582
|
Members extends AtLeastTwoReadonlyArray<TypeNode>,
|
|
3211
3583
|
> extends Type<
|
|
@@ -3283,13 +3655,21 @@ type RootUnionMembers<Members extends AtLeastTwoReadonlyArray<TypeNode>> = {
|
|
|
3283
3655
|
? RootMembers
|
|
3284
3656
|
: never;
|
|
3285
3657
|
|
|
3286
|
-
/**
|
|
3658
|
+
/**
|
|
3659
|
+
* Error returned when every member of a {@link union} rejects an input.
|
|
3660
|
+
*
|
|
3661
|
+
* @group Unions
|
|
3662
|
+
*/
|
|
3287
3663
|
export type UnionError<
|
|
3288
3664
|
Error extends TypeError = TypeError,
|
|
3289
3665
|
MemberError extends UnionMemberError<Error> = UnionMemberError<Error>,
|
|
3290
3666
|
> = [Error] extends [never] ? never : UnionErrorValue<Error, MemberError>;
|
|
3291
3667
|
|
|
3292
|
-
/**
|
|
3668
|
+
/**
|
|
3669
|
+
* An error returned by one {@link union} member and its index.
|
|
3670
|
+
*
|
|
3671
|
+
* @group Unions
|
|
3672
|
+
*/
|
|
3293
3673
|
export interface UnionMemberError<
|
|
3294
3674
|
Error extends TypeError,
|
|
3295
3675
|
Index extends number = number,
|
|
@@ -3353,9 +3733,7 @@ interface UnionErrorValue<
|
|
|
3353
3733
|
}
|
|
3354
3734
|
|
|
3355
3735
|
/**
|
|
3356
|
-
* Template literal {@link Type}
|
|
3357
|
-
*
|
|
3358
|
-
* Parses and creates structured strings.
|
|
3736
|
+
* Template literal {@link Type} that parses canonical strings into Tuples.
|
|
3359
3737
|
*
|
|
3360
3738
|
* Accepts the same template parts as {@link templateLiteral}: fixed string
|
|
3361
3739
|
* literals and Types canonically encoded as strings. Instead of keeping Output
|
|
@@ -3371,10 +3749,10 @@ interface UnionErrorValue<
|
|
|
3371
3749
|
* reversibility and keeps capture boundaries unambiguous. Different capture
|
|
3372
3750
|
* Tuples must never encode to the same string. The parser provides predictable
|
|
3373
3751
|
* parsing without pathological backtracking and decodes each capture once, so
|
|
3374
|
-
* adversarial input cannot trigger exponential parser work. Fixed-width
|
|
3375
|
-
* may be adjacent, but only one variable-width capture is allowed.
|
|
3376
|
-
* that could join UTF-16 surrogate halves across parts are
|
|
3377
|
-
* construction.
|
|
3752
|
+
* adversarial input cannot trigger exponential parser work. Fixed-width
|
|
3753
|
+
* captures may be adjacent, but only one variable-width capture is allowed.
|
|
3754
|
+
* Declarations that could join UTF-16 surrogate halves across parts are
|
|
3755
|
+
* rejected during construction.
|
|
3378
3756
|
*
|
|
3379
3757
|
* Keep capture unions reasonably small to avoid excessive compiler work.
|
|
3380
3758
|
*
|
|
@@ -3411,10 +3789,9 @@ interface UnionErrorValue<
|
|
|
3411
3789
|
*
|
|
3412
3790
|
* // Parse an unknown string into structured data.
|
|
3413
3791
|
* const result = SupportedLocale.fromUnknown("cs-CZ");
|
|
3414
|
-
*
|
|
3792
|
+
* expectOk(result, ["cs", "CZ"]);
|
|
3415
3793
|
* const locale = result.value;
|
|
3416
3794
|
* expectTypeOf(locale).toEqualTypeOf<SupportedLocale>();
|
|
3417
|
-
* expect(locale).toEqual(["cs", "CZ"]);
|
|
3418
3795
|
* expectErr(SupportedLocale.fromUnknown("cs/CZ"), {
|
|
3419
3796
|
* type: "TemplateLiteral",
|
|
3420
3797
|
* value: "cs/CZ",
|
|
@@ -3473,10 +3850,9 @@ interface UnionErrorValue<
|
|
|
3473
3850
|
*
|
|
3474
3851
|
* // Decode the string into structured data.
|
|
3475
3852
|
* const result = ItemId.fromUnknown("item-42");
|
|
3476
|
-
*
|
|
3853
|
+
* expectOk(result, [42n]);
|
|
3477
3854
|
* const itemId = result.value;
|
|
3478
3855
|
* expectTypeOf(itemId).toEqualTypeOf<ItemId>();
|
|
3479
|
-
* expect(itemId).toEqual([42n]);
|
|
3480
3856
|
*
|
|
3481
3857
|
* // Encode the structured data into its canonical string.
|
|
3482
3858
|
* const itemIdLiteral = ItemId.to(itemId);
|
|
@@ -3569,12 +3945,14 @@ const createTemplateLiteralParserType = <
|
|
|
3569
3945
|
options,
|
|
3570
3946
|
false,
|
|
3571
3947
|
);
|
|
3572
|
-
return (
|
|
3573
|
-
|
|
3574
|
-
|
|
3575
|
-
|
|
3576
|
-
|
|
3577
|
-
|
|
3948
|
+
return (
|
|
3949
|
+
outputResult.ok
|
|
3950
|
+
? outputResult
|
|
3951
|
+
: err({
|
|
3952
|
+
type: "TemplateLiteral",
|
|
3953
|
+
outputError: outputResult.error,
|
|
3954
|
+
})
|
|
3955
|
+
) as Result<
|
|
3578
3956
|
TemplateLiteralCaptureTuple<Parts>["Output"],
|
|
3579
3957
|
TemplateLiteralRuntimeParseError<Parts>
|
|
3580
3958
|
>;
|
|
@@ -3635,7 +4013,8 @@ const createTemplateLiteralParserType = <
|
|
|
3635
4013
|
return singleRuntimeTypeIssue(
|
|
3636
4014
|
"TemplateLiteral",
|
|
3637
4015
|
error,
|
|
3638
|
-
|
|
4016
|
+
((error: TemplateLiteralError) =>
|
|
4017
|
+
`The value ${safelyStringifyUnknownValue(error.value)} does not match the template literal.`) as TypeErrorFormatter<TypeError>,
|
|
3639
4018
|
);
|
|
3640
4019
|
};
|
|
3641
4020
|
const canonicalStringFromUnknown = (
|
|
@@ -3726,7 +4105,11 @@ const createTemplateLiteralParserType = <
|
|
|
3726
4105
|
return type;
|
|
3727
4106
|
};
|
|
3728
4107
|
|
|
3729
|
-
/**
|
|
4108
|
+
/**
|
|
4109
|
+
* The parsing {@link Type} returned by {@link templateLiteralParser}.
|
|
4110
|
+
*
|
|
4111
|
+
* @group Template literals
|
|
4112
|
+
*/
|
|
3730
4113
|
export interface TemplateLiteralParserType<
|
|
3731
4114
|
Parts extends TemplateLiteralParts,
|
|
3732
4115
|
> extends Type<
|
|
@@ -3746,7 +4129,11 @@ export interface TemplateLiteralParserType<
|
|
|
3746
4129
|
readonly parts: Parts;
|
|
3747
4130
|
}
|
|
3748
4131
|
|
|
3749
|
-
/**
|
|
4132
|
+
/**
|
|
4133
|
+
* The validating string {@link Type} returned by {@link templateLiteral}.
|
|
4134
|
+
*
|
|
4135
|
+
* @group Template literals
|
|
4136
|
+
*/
|
|
3750
4137
|
export interface TemplateLiteralType<
|
|
3751
4138
|
Parts extends TemplateLiteralParts,
|
|
3752
4139
|
> extends Type<
|
|
@@ -3772,8 +4159,8 @@ export interface TemplateLiteralType<
|
|
|
3772
4159
|
* Creates a canonical string Type from fixed strings and string-encoded Types.
|
|
3773
4160
|
*
|
|
3774
4161
|
* Use this factory when Output should remain a string. Switch to
|
|
3775
|
-
* {@link templateLiteralParser} when the individual Type parts should be
|
|
3776
|
-
*
|
|
4162
|
+
* {@link templateLiteralParser} when the individual Type parts should be decoded
|
|
4163
|
+
* into a Tuple.
|
|
3777
4164
|
*
|
|
3778
4165
|
* ### Example
|
|
3779
4166
|
*
|
|
@@ -3830,15 +4217,15 @@ type TemplateLiteralCaptureFromStringError<T extends TypeNode> =
|
|
|
3830
4217
|
: InferErrors<T>
|
|
3831
4218
|
: never;
|
|
3832
4219
|
|
|
3833
|
-
/**
|
|
4220
|
+
/**
|
|
4221
|
+
* Error returned when a string does not match a template literal declaration.
|
|
4222
|
+
*
|
|
4223
|
+
* @group Template literals
|
|
4224
|
+
*/
|
|
3834
4225
|
export interface TemplateLiteralError extends TypeError<"TemplateLiteral"> {
|
|
3835
4226
|
readonly value: string;
|
|
3836
4227
|
}
|
|
3837
4228
|
|
|
3838
|
-
const formatTemplateLiteralError: TypeErrorFormatter<TemplateLiteralError> =
|
|
3839
|
-
(error) =>
|
|
3840
|
-
`The value ${safelyStringifyUnknownValue(error.value)} does not match the template literal.`;
|
|
3841
|
-
|
|
3842
4229
|
declare const templateLiteralStringBrandSymbol: unique symbol;
|
|
3843
4230
|
|
|
3844
4231
|
interface TemplateLiteralStringBrand<Parts extends TemplateLiteralParts> {
|
|
@@ -4084,9 +4471,7 @@ const compileTemplateLiteralParser = <Parts extends TemplateLiteralParts>(
|
|
|
4084
4471
|
}
|
|
4085
4472
|
|
|
4086
4473
|
return position === inputCodePoints.length
|
|
4087
|
-
? ok(
|
|
4088
|
-
captures as unknown as TemplateLiteralCaptureTuple<Parts>["Input"],
|
|
4089
|
-
)
|
|
4474
|
+
? ok(captures as unknown as TemplateLiteralCaptureTuple<Parts>["Input"])
|
|
4090
4475
|
: err({ type: "TemplateLiteral", value: input });
|
|
4091
4476
|
};
|
|
4092
4477
|
};
|
|
@@ -4254,68 +4639,10 @@ const getTemplateLiteralPartFraming = (
|
|
|
4254
4639
|
* });
|
|
4255
4640
|
* ```
|
|
4256
4641
|
*
|
|
4257
|
-
* To reuse a Brand constraint with different parent Types, define a
|
|
4258
|
-
* {@link BrandFactory}.
|
|
4259
|
-
*
|
|
4260
|
-
* ```ts
|
|
4261
|
-
* import {
|
|
4262
|
-
* String,
|
|
4263
|
-
* brand,
|
|
4264
|
-
* err,
|
|
4265
|
-
* minLength,
|
|
4266
|
-
* ok,
|
|
4267
|
-
* type Brand,
|
|
4268
|
-
* type BrandFactory,
|
|
4269
|
-
* type TypeError,
|
|
4270
|
-
* } from "@evolu/common";
|
|
4271
|
-
*
|
|
4272
|
-
* const trimmed: BrandFactory<"Trimmed", string, TrimmedError> = (
|
|
4273
|
-
* parent,
|
|
4274
|
-
* ) =>
|
|
4275
|
-
* brand(
|
|
4276
|
-
* "Trimmed",
|
|
4277
|
-
* parent,
|
|
4278
|
-
* (value) =>
|
|
4279
|
-
* value === value.trim()
|
|
4280
|
-
* ? ok()
|
|
4281
|
-
* : err<TrimmedError>({ type: "Trimmed", value }),
|
|
4282
|
-
* () => "Expected a string without surrounding whitespace.",
|
|
4283
|
-
* );
|
|
4284
|
-
*
|
|
4285
|
-
* const TrimmedString = trimmed(String);
|
|
4286
|
-
* type TrimmedString = typeof TrimmedString.Output;
|
|
4287
|
-
*
|
|
4288
|
-
* expectTypeOf<TrimmedString>().toEqualTypeOf<string & Brand<"Trimmed">>();
|
|
4289
|
-
*
|
|
4290
|
-
* const NonEmptyString = minLength(1)(String);
|
|
4291
|
-
* type NonEmptyString = typeof NonEmptyString.Output;
|
|
4292
|
-
*
|
|
4293
|
-
* expectTypeOf<NonEmptyString>().toEqualTypeOf<
|
|
4294
|
-
* string & Brand<"MinLength1">
|
|
4295
|
-
* >();
|
|
4296
|
-
*
|
|
4297
|
-
* const NonEmptyTrimmedString = minLength(1)(TrimmedString);
|
|
4298
|
-
* type NonEmptyTrimmedString = typeof NonEmptyTrimmedString.Output;
|
|
4299
|
-
*
|
|
4300
|
-
* expectTypeOf<NonEmptyTrimmedString>().toEqualTypeOf<
|
|
4301
|
-
* string & Brand<"Trimmed"> & Brand<"MinLength1">
|
|
4302
|
-
* >();
|
|
4303
|
-
*
|
|
4304
|
-
* interface TrimmedError extends TypeError<"Trimmed"> {
|
|
4305
|
-
* readonly value: string;
|
|
4306
|
-
* }
|
|
4307
|
-
*
|
|
4308
|
-
* // Validation from unknown.
|
|
4309
|
-
* expectOk(NonEmptyTrimmedString.fromUnknown("Evolu"), "Evolu");
|
|
4310
|
-
*
|
|
4311
|
-
* // The typed input selects TrimmedString as the validated boundary.
|
|
4312
|
-
* expectOk(
|
|
4313
|
-
* NonEmptyTrimmedString.from.parent(TrimmedString.orThrow("Evolu")),
|
|
4314
|
-
* "Evolu",
|
|
4315
|
-
* );
|
|
4316
|
-
* ```
|
|
4642
|
+
* To reuse and compose a Brand constraint with different parent Types, define a
|
|
4643
|
+
* {@link BrandFactory}.
|
|
4317
4644
|
*
|
|
4318
|
-
* @group
|
|
4645
|
+
* @group Construction
|
|
4319
4646
|
*/
|
|
4320
4647
|
export function brand<
|
|
4321
4648
|
Name extends TypeName,
|
|
@@ -4325,6 +4652,8 @@ export function brand<
|
|
|
4325
4652
|
parent: ValidateParent<ParentType>,
|
|
4326
4653
|
validate?: (value: ParentType["Output"]) => Result<void, never>,
|
|
4327
4654
|
): BrandType<ParentType, Name, never>;
|
|
4655
|
+
|
|
4656
|
+
/** Creates a validated Brand Type with its own error formatter. */
|
|
4328
4657
|
export function brand<
|
|
4329
4658
|
Name extends TypeName,
|
|
4330
4659
|
ParentType extends ConcreteTypeNode,
|
|
@@ -4352,7 +4681,11 @@ export function brand(
|
|
|
4352
4681
|
);
|
|
4353
4682
|
}
|
|
4354
4683
|
|
|
4355
|
-
/**
|
|
4684
|
+
/**
|
|
4685
|
+
* The {@link Type} returned by {@link brand}.
|
|
4686
|
+
*
|
|
4687
|
+
* @group Construction
|
|
4688
|
+
*/
|
|
4356
4689
|
export interface BrandType<
|
|
4357
4690
|
ParentType extends TypeNode,
|
|
4358
4691
|
Name extends TypeName,
|
|
@@ -4409,7 +4742,11 @@ export const DateIso = /*#__PURE__*/ brand(
|
|
|
4409
4742
|
);
|
|
4410
4743
|
export type DateIso = typeof DateIso.Output;
|
|
4411
4744
|
|
|
4412
|
-
/**
|
|
4745
|
+
/**
|
|
4746
|
+
* Error returned when a string is not a canonical {@link DateIso}.
|
|
4747
|
+
*
|
|
4748
|
+
* @group String
|
|
4749
|
+
*/
|
|
4413
4750
|
export interface DateIsoError extends TypeError<"DateIso"> {
|
|
4414
4751
|
readonly value: string;
|
|
4415
4752
|
}
|
|
@@ -4417,6 +4754,18 @@ export interface DateIsoError extends TypeError<"DateIso"> {
|
|
|
4417
4754
|
/**
|
|
4418
4755
|
* Safely transforms a {@link Date} into a canonical {@link DateIso}.
|
|
4419
4756
|
*
|
|
4757
|
+
* ### Example
|
|
4758
|
+
*
|
|
4759
|
+
* ```ts
|
|
4760
|
+
* import { DateIsoFromDate } from "@evolu/common";
|
|
4761
|
+
*
|
|
4762
|
+
* const date = new globalThis.Date("2025-01-01T12:00:00.000Z");
|
|
4763
|
+
* const result = DateIsoFromDate.fromUnknown(date);
|
|
4764
|
+
*
|
|
4765
|
+
* expectOk(result, "2025-01-01T12:00:00.000Z");
|
|
4766
|
+
* expect(DateIsoFromDate.to(result.value)).toEqual(date);
|
|
4767
|
+
* ```
|
|
4768
|
+
*
|
|
4420
4769
|
* @group String
|
|
4421
4770
|
*/
|
|
4422
4771
|
export const DateIsoFromDate = /*#__PURE__*/ transform(
|
|
@@ -4434,7 +4783,11 @@ export const DateIsoFromDate = /*#__PURE__*/ transform(
|
|
|
4434
4783
|
() => "The Date cannot be represented as DateIso.",
|
|
4435
4784
|
);
|
|
4436
4785
|
|
|
4437
|
-
/**
|
|
4786
|
+
/**
|
|
4787
|
+
* Error returned when a {@link Date} cannot be represented as {@link DateIso}.
|
|
4788
|
+
*
|
|
4789
|
+
* @group String
|
|
4790
|
+
*/
|
|
4438
4791
|
export interface DateIsoFromDateError extends TypeError<"DateIsoFromDate"> {
|
|
4439
4792
|
readonly value: globalThis.Date;
|
|
4440
4793
|
}
|
|
@@ -4456,7 +4809,11 @@ export const Int64 = /*#__PURE__*/ brand(
|
|
|
4456
4809
|
);
|
|
4457
4810
|
export type Int64 = typeof Int64.Output;
|
|
4458
4811
|
|
|
4459
|
-
/**
|
|
4812
|
+
/**
|
|
4813
|
+
* Error returned when a bigint is outside the signed 64-bit {@link Int64} range.
|
|
4814
|
+
*
|
|
4815
|
+
* @group Number
|
|
4816
|
+
*/
|
|
4460
4817
|
export interface Int64Error extends TypeError<"Int64"> {
|
|
4461
4818
|
readonly value: bigint;
|
|
4462
4819
|
}
|
|
@@ -4478,7 +4835,12 @@ export const UInt64 = /*#__PURE__*/ brand(
|
|
|
4478
4835
|
);
|
|
4479
4836
|
export type UInt64 = typeof UInt64.Output;
|
|
4480
4837
|
|
|
4481
|
-
/**
|
|
4838
|
+
/**
|
|
4839
|
+
* Error returned when a bigint is outside the unsigned 64-bit {@link UInt64}
|
|
4840
|
+
* range.
|
|
4841
|
+
*
|
|
4842
|
+
* @group Number
|
|
4843
|
+
*/
|
|
4482
4844
|
export interface UInt64Error extends TypeError<"UInt64"> {
|
|
4483
4845
|
readonly value: bigint;
|
|
4484
4846
|
}
|
|
@@ -4535,7 +4897,7 @@ export interface UInt64Error extends TypeError<"UInt64"> {
|
|
|
4535
4897
|
* For numeric parameters encoded in a Brand name, use
|
|
4536
4898
|
* {@link ValidateBrandFactoryNumber}.
|
|
4537
4899
|
*
|
|
4538
|
-
* @group
|
|
4900
|
+
* @group Construction
|
|
4539
4901
|
*/
|
|
4540
4902
|
export type BrandFactory<
|
|
4541
4903
|
Name extends TypeName,
|
|
@@ -4605,7 +4967,7 @@ export type BrandFactory<
|
|
|
4605
4967
|
* lessThan(100 - 1)(Number);
|
|
4606
4968
|
* ```
|
|
4607
4969
|
*
|
|
4608
|
-
* @group
|
|
4970
|
+
* @group Construction
|
|
4609
4971
|
*/
|
|
4610
4972
|
export type ValidateBrandFactoryNumber<Value extends number> =
|
|
4611
4973
|
IsUnion<Value> extends false
|
|
@@ -4664,7 +5026,11 @@ export const capitalized: BrandFactory<
|
|
|
4664
5026
|
`The value ${safelyStringifyUnknownValue(error.value)} must be capitalized.`,
|
|
4665
5027
|
);
|
|
4666
5028
|
|
|
4667
|
-
/**
|
|
5029
|
+
/**
|
|
5030
|
+
* Error returned when {@link capitalized} rejects a string.
|
|
5031
|
+
*
|
|
5032
|
+
* @group String
|
|
5033
|
+
*/
|
|
4668
5034
|
export interface CapitalizedError extends TypeError<"Capitalized"> {
|
|
4669
5035
|
readonly value: string;
|
|
4670
5036
|
}
|
|
@@ -4680,6 +5046,16 @@ export type CapitalizedString = typeof CapitalizedString.Output;
|
|
|
4680
5046
|
/**
|
|
4681
5047
|
* String {@link Brand} without surrounding whitespace.
|
|
4682
5048
|
*
|
|
5049
|
+
* ### Example
|
|
5050
|
+
*
|
|
5051
|
+
* ```ts
|
|
5052
|
+
* import { String, trimmed } from "@evolu/common";
|
|
5053
|
+
*
|
|
5054
|
+
* const Trimmed = trimmed(String);
|
|
5055
|
+
*
|
|
5056
|
+
* expectOk(Trimmed.fromUnknown("Evolu"), "Evolu");
|
|
5057
|
+
* ```
|
|
5058
|
+
*
|
|
4683
5059
|
* @group String
|
|
4684
5060
|
*/
|
|
4685
5061
|
export const trimmed: BrandFactory<"Trimmed", string, TrimmedError> = (
|
|
@@ -4696,7 +5072,11 @@ export const trimmed: BrandFactory<"Trimmed", string, TrimmedError> = (
|
|
|
4696
5072
|
`The value ${safelyStringifyUnknownValue(error.value)} must be trimmed.`,
|
|
4697
5073
|
);
|
|
4698
5074
|
|
|
4699
|
-
/**
|
|
5075
|
+
/**
|
|
5076
|
+
* Error returned when {@link trimmed} rejects a string.
|
|
5077
|
+
*
|
|
5078
|
+
* @group String
|
|
5079
|
+
*/
|
|
4700
5080
|
export interface TrimmedError extends TypeError<"Trimmed"> {
|
|
4701
5081
|
readonly value: string;
|
|
4702
5082
|
}
|
|
@@ -4718,13 +5098,33 @@ export type TrimmedString = typeof TrimmedString.Output;
|
|
|
4718
5098
|
/**
|
|
4719
5099
|
* Trims a string and returns a {@link TrimmedString}.
|
|
4720
5100
|
*
|
|
5101
|
+
* ### Example
|
|
5102
|
+
*
|
|
5103
|
+
* ```ts
|
|
5104
|
+
* import { trim } from "@evolu/common";
|
|
5105
|
+
*
|
|
5106
|
+
* expect(trim(" Evolu ")).toBe("Evolu");
|
|
5107
|
+
* ```
|
|
5108
|
+
*
|
|
4721
5109
|
* @group String
|
|
4722
5110
|
*/
|
|
4723
5111
|
export const trim = (value: string): TrimmedString =>
|
|
4724
5112
|
value.trim() as TrimmedString;
|
|
4725
5113
|
|
|
4726
5114
|
/**
|
|
4727
|
-
* Minimum-length {@link Brand}
|
|
5115
|
+
* Minimum-length {@link Brand} for values whose `length` is at least `min`.
|
|
5116
|
+
*
|
|
5117
|
+
* ### Example
|
|
5118
|
+
*
|
|
5119
|
+
* ```ts
|
|
5120
|
+
* import { String, array, minLength } from "@evolu/common";
|
|
5121
|
+
*
|
|
5122
|
+
* const AtLeastThreeCharacters = minLength(3)(String);
|
|
5123
|
+
* const AtLeastTwoItems = minLength(2)(array(String));
|
|
5124
|
+
*
|
|
5125
|
+
* expectOk(AtLeastThreeCharacters.fromUnknown("abc"), "abc");
|
|
5126
|
+
* expectOk(AtLeastTwoItems.fromUnknown(["a", "b"]), ["a", "b"]);
|
|
5127
|
+
* ```
|
|
4728
5128
|
*
|
|
4729
5129
|
* @group String
|
|
4730
5130
|
* @group Collection
|
|
@@ -4748,7 +5148,11 @@ export const minLength =
|
|
|
4748
5148
|
);
|
|
4749
5149
|
};
|
|
4750
5150
|
|
|
4751
|
-
/**
|
|
5151
|
+
/**
|
|
5152
|
+
* Error returned when {@link minLength} rejects a value.
|
|
5153
|
+
*
|
|
5154
|
+
* @group Collection
|
|
5155
|
+
*/
|
|
4752
5156
|
export interface MinLengthError<
|
|
4753
5157
|
Min extends number = number,
|
|
4754
5158
|
> extends TypeError<`MinLength${Min}`> {
|
|
@@ -4774,7 +5178,19 @@ export const NonEmptyTrimmedString = /*#__PURE__*/ minLength(1)(TrimmedString);
|
|
|
4774
5178
|
export type NonEmptyTrimmedString = typeof NonEmptyTrimmedString.Output;
|
|
4775
5179
|
|
|
4776
5180
|
/**
|
|
4777
|
-
* Maximum-length {@link Brand}
|
|
5181
|
+
* Maximum-length {@link Brand} for values whose `length` is at most `max`.
|
|
5182
|
+
*
|
|
5183
|
+
* ### Example
|
|
5184
|
+
*
|
|
5185
|
+
* ```ts
|
|
5186
|
+
* import { String, array, maxLength } from "@evolu/common";
|
|
5187
|
+
*
|
|
5188
|
+
* const AtMostThreeCharacters = maxLength(3)(String);
|
|
5189
|
+
* const AtMostTwoItems = maxLength(2)(array(String));
|
|
5190
|
+
*
|
|
5191
|
+
* expectOk(AtMostThreeCharacters.fromUnknown("abc"), "abc");
|
|
5192
|
+
* expectOk(AtMostTwoItems.fromUnknown(["a", "b"]), ["a", "b"]);
|
|
5193
|
+
* ```
|
|
4778
5194
|
*
|
|
4779
5195
|
* @group String
|
|
4780
5196
|
* @group Collection
|
|
@@ -4798,7 +5214,11 @@ export const maxLength =
|
|
|
4798
5214
|
);
|
|
4799
5215
|
};
|
|
4800
5216
|
|
|
4801
|
-
/**
|
|
5217
|
+
/**
|
|
5218
|
+
* Error returned when {@link maxLength} rejects a value.
|
|
5219
|
+
*
|
|
5220
|
+
* @group Collection
|
|
5221
|
+
*/
|
|
4802
5222
|
export interface MaxLengthError<
|
|
4803
5223
|
Max extends number = number,
|
|
4804
5224
|
> extends TypeError<`MaxLength${Max}`> {
|
|
@@ -4827,7 +5247,19 @@ export const NonEmptyTrimmedString1000 = /*#__PURE__*/ maxLength(1000)(
|
|
|
4827
5247
|
export type NonEmptyTrimmedString1000 = typeof NonEmptyTrimmedString1000.Output;
|
|
4828
5248
|
|
|
4829
5249
|
/**
|
|
4830
|
-
* Exact-length {@link Brand}
|
|
5250
|
+
* Exact-length {@link Brand} for values whose `length` equals `exact`.
|
|
5251
|
+
*
|
|
5252
|
+
* ### Example
|
|
5253
|
+
*
|
|
5254
|
+
* ```ts
|
|
5255
|
+
* import { String, array, length } from "@evolu/common";
|
|
5256
|
+
*
|
|
5257
|
+
* const ThreeCharacters = length(3)(String);
|
|
5258
|
+
* const TwoItems = length(2)(array(String));
|
|
5259
|
+
*
|
|
5260
|
+
* expectOk(ThreeCharacters.fromUnknown("abc"), "abc");
|
|
5261
|
+
* expectOk(TwoItems.fromUnknown(["a", "b"]), ["a", "b"]);
|
|
5262
|
+
* ```
|
|
4831
5263
|
*
|
|
4832
5264
|
* @group String
|
|
4833
5265
|
* @group Collection
|
|
@@ -4851,7 +5283,11 @@ export const length =
|
|
|
4851
5283
|
);
|
|
4852
5284
|
};
|
|
4853
5285
|
|
|
4854
|
-
/**
|
|
5286
|
+
/**
|
|
5287
|
+
* Error returned when {@link length} rejects a value.
|
|
5288
|
+
*
|
|
5289
|
+
* @group Collection
|
|
5290
|
+
*/
|
|
4855
5291
|
export interface LengthError<
|
|
4856
5292
|
Exact extends number = number,
|
|
4857
5293
|
> extends TypeError<`Length${Exact}`> {
|
|
@@ -4917,7 +5353,12 @@ export const regex = <const Name extends TypeName>(
|
|
|
4917
5353
|
);
|
|
4918
5354
|
};
|
|
4919
5355
|
|
|
4920
|
-
/**
|
|
5356
|
+
/**
|
|
5357
|
+
* Error returned when a string does not match the regular expression supplied
|
|
5358
|
+
* to {@link regex}.
|
|
5359
|
+
*
|
|
5360
|
+
* @group String
|
|
5361
|
+
*/
|
|
4921
5362
|
export interface RegexError<
|
|
4922
5363
|
Name extends TypeName = TypeName,
|
|
4923
5364
|
> extends TypeError<Name> {
|
|
@@ -4985,8 +5426,8 @@ const base64UrlStringToUint8Array = (value: string): Uint8Array => {
|
|
|
4985
5426
|
/**
|
|
4986
5427
|
* Base64Url text without padding.
|
|
4987
5428
|
*
|
|
4988
|
-
*
|
|
4989
|
-
* {@link base64UrlToUint8Array}.
|
|
5429
|
+
* Convert bytes to Base64Url with {@link uint8ArrayToBase64Url} and convert
|
|
5430
|
+
* Base64Url to bytes with {@link base64UrlToUint8Array}.
|
|
4990
5431
|
*
|
|
4991
5432
|
* @group String
|
|
4992
5433
|
*/
|
|
@@ -5005,13 +5446,27 @@ export const Base64Url = /*#__PURE__*/ brand(
|
|
|
5005
5446
|
);
|
|
5006
5447
|
export type Base64Url = typeof Base64Url.Output;
|
|
5007
5448
|
|
|
5008
|
-
/**
|
|
5449
|
+
/**
|
|
5450
|
+
* Error returned when a string is not valid {@link Base64Url} text.
|
|
5451
|
+
*
|
|
5452
|
+
* @group String
|
|
5453
|
+
*/
|
|
5009
5454
|
export interface Base64UrlError extends TypeError<"Base64Url"> {
|
|
5010
5455
|
readonly value: string;
|
|
5011
5456
|
}
|
|
5012
5457
|
|
|
5013
5458
|
/**
|
|
5014
|
-
*
|
|
5459
|
+
* Converts bytes to {@link Base64Url}.
|
|
5460
|
+
*
|
|
5461
|
+
* ### Example
|
|
5462
|
+
*
|
|
5463
|
+
* ```ts
|
|
5464
|
+
* import { uint8ArrayToBase64Url } from "@evolu/common";
|
|
5465
|
+
*
|
|
5466
|
+
* expect(uint8ArrayToBase64Url(new Uint8Array([0, 1, 2, 255]))).toBe(
|
|
5467
|
+
* "AAEC_w",
|
|
5468
|
+
* );
|
|
5469
|
+
* ```
|
|
5015
5470
|
*
|
|
5016
5471
|
* @group String
|
|
5017
5472
|
*/
|
|
@@ -5019,7 +5474,19 @@ export const uint8ArrayToBase64Url = (bytes: Uint8Array): Base64Url =>
|
|
|
5019
5474
|
uint8ArrayToBase64UrlString(bytes) as Base64Url;
|
|
5020
5475
|
|
|
5021
5476
|
/**
|
|
5022
|
-
*
|
|
5477
|
+
* Converts {@link Base64Url} to bytes.
|
|
5478
|
+
*
|
|
5479
|
+
* ### Example
|
|
5480
|
+
*
|
|
5481
|
+
* ```ts
|
|
5482
|
+
* import { Base64Url, base64UrlToUint8Array } from "@evolu/common";
|
|
5483
|
+
*
|
|
5484
|
+
* const value = Base64Url.orThrow("AAEC_w");
|
|
5485
|
+
*
|
|
5486
|
+
* expect(base64UrlToUint8Array(value)).toEqual(
|
|
5487
|
+
* new Uint8Array([0, 1, 2, 255]),
|
|
5488
|
+
* );
|
|
5489
|
+
* ```
|
|
5023
5490
|
*
|
|
5024
5491
|
* @group String
|
|
5025
5492
|
*/
|
|
@@ -5041,7 +5508,11 @@ export const Name = /*#__PURE__*/ brand(
|
|
|
5041
5508
|
);
|
|
5042
5509
|
export type Name = typeof Name.Output;
|
|
5043
5510
|
|
|
5044
|
-
/**
|
|
5511
|
+
/**
|
|
5512
|
+
* Error returned when a string is not a valid {@link Name}.
|
|
5513
|
+
*
|
|
5514
|
+
* @group String
|
|
5515
|
+
*/
|
|
5045
5516
|
export interface NameError extends TypeError<"Name"> {
|
|
5046
5517
|
readonly value: string;
|
|
5047
5518
|
}
|
|
@@ -5077,11 +5548,15 @@ export const Mnemonic = /*#__PURE__*/ brand(
|
|
|
5077
5548
|
? ok()
|
|
5078
5549
|
: err<MnemonicError>({ type: "Mnemonic", value }),
|
|
5079
5550
|
(error) =>
|
|
5080
|
-
`The value ${safelyStringifyUnknownValue(error.value)} is not a valid BIP39 mnemonic.`,
|
|
5551
|
+
`The value ${safelyStringifyUnknownValue(error.value)} is not a valid English BIP39 mnemonic.`,
|
|
5081
5552
|
);
|
|
5082
5553
|
export type Mnemonic = typeof Mnemonic.Output;
|
|
5083
5554
|
|
|
5084
|
-
/**
|
|
5555
|
+
/**
|
|
5556
|
+
* Error returned when a string is not a valid English BIP39 {@link Mnemonic}.
|
|
5557
|
+
*
|
|
5558
|
+
* @group String
|
|
5559
|
+
*/
|
|
5085
5560
|
export interface MnemonicError extends TypeError<"Mnemonic"> {
|
|
5086
5561
|
readonly value: string;
|
|
5087
5562
|
}
|
|
@@ -5108,7 +5583,11 @@ export const Id = /*#__PURE__*/ brand(
|
|
|
5108
5583
|
);
|
|
5109
5584
|
export type Id = typeof Id.Output;
|
|
5110
5585
|
|
|
5111
|
-
/**
|
|
5586
|
+
/**
|
|
5587
|
+
* Error returned when a string is not a valid {@link Id}.
|
|
5588
|
+
*
|
|
5589
|
+
* @group String
|
|
5590
|
+
*/
|
|
5112
5591
|
export interface IdError extends TypeError<"Id"> {
|
|
5113
5592
|
readonly value: string;
|
|
5114
5593
|
}
|
|
@@ -5119,6 +5598,22 @@ export interface IdError extends TypeError<"Id"> {
|
|
|
5119
5598
|
* This is the recommended default because it does not encode creation time.
|
|
5120
5599
|
* Pass a Brand name when the returned Id belongs to one domain entity.
|
|
5121
5600
|
*
|
|
5601
|
+
* ### Example
|
|
5602
|
+
*
|
|
5603
|
+
* ```ts
|
|
5604
|
+
* import {
|
|
5605
|
+
* Id,
|
|
5606
|
+
* createId,
|
|
5607
|
+
* createRandomBytes,
|
|
5608
|
+
* type Brand,
|
|
5609
|
+
* } from "@evolu/common";
|
|
5610
|
+
*
|
|
5611
|
+
* const userId = createId<"User">({ randomBytes: createRandomBytes() });
|
|
5612
|
+
*
|
|
5613
|
+
* expect(Id.is(userId)).toBe(true);
|
|
5614
|
+
* expectTypeOf(userId).toEqualTypeOf<Id & Brand<"User">>();
|
|
5615
|
+
* ```
|
|
5616
|
+
*
|
|
5122
5617
|
* @group String
|
|
5123
5618
|
*/
|
|
5124
5619
|
export const createId = <B extends string = never>(
|
|
@@ -5165,13 +5660,33 @@ export const createIdFromString = <B extends string = never>(
|
|
|
5165
5660
|
* logs, URLs, and exports. Prefer {@link createId} unless that tradeoff is
|
|
5166
5661
|
* deliberate.
|
|
5167
5662
|
*
|
|
5168
|
-
*
|
|
5169
|
-
|
|
5170
|
-
|
|
5171
|
-
|
|
5172
|
-
|
|
5173
|
-
|
|
5174
|
-
|
|
5663
|
+
* ### Example
|
|
5664
|
+
*
|
|
5665
|
+
* ```ts
|
|
5666
|
+
* import {
|
|
5667
|
+
* createIdAsUuidv7,
|
|
5668
|
+
* createRandomBytes,
|
|
5669
|
+
* createTime,
|
|
5670
|
+
* idToIdBytes,
|
|
5671
|
+
* } from "@evolu/common";
|
|
5672
|
+
*
|
|
5673
|
+
* const value = createIdAsUuidv7({
|
|
5674
|
+
* randomBytes: createRandomBytes(),
|
|
5675
|
+
* time: createTime(),
|
|
5676
|
+
* });
|
|
5677
|
+
* const bytes = idToIdBytes(value);
|
|
5678
|
+
*
|
|
5679
|
+
* expect(bytes[6] >> 4).toBe(0x7);
|
|
5680
|
+
* expect(bytes[8] & 0xc0).toBe(0x80);
|
|
5681
|
+
* ```
|
|
5682
|
+
*
|
|
5683
|
+
* @group String
|
|
5684
|
+
*/
|
|
5685
|
+
export const createIdAsUuidv7 = <B extends string = never>(
|
|
5686
|
+
deps: RandomBytesDep & TimeDep,
|
|
5687
|
+
..._validation: IdBrandValidation<B>
|
|
5688
|
+
): CreatedId<B> => {
|
|
5689
|
+
const bytes = deps.randomBytes.create(16);
|
|
5175
5690
|
const timestamp = globalThis.BigInt(deps.time.now());
|
|
5176
5691
|
|
|
5177
5692
|
bytes[0] = globalThis.Number((timestamp >> 40n) & 0xffn);
|
|
@@ -5189,6 +5704,22 @@ export const createIdAsUuidv7 = <B extends string = never>(
|
|
|
5189
5704
|
/**
|
|
5190
5705
|
* Table-specific {@link Id} Type.
|
|
5191
5706
|
*
|
|
5707
|
+
* ### Example
|
|
5708
|
+
*
|
|
5709
|
+
* ```ts
|
|
5710
|
+
* import {
|
|
5711
|
+
* createIdFromString,
|
|
5712
|
+
* id,
|
|
5713
|
+
* type Brand,
|
|
5714
|
+
* type Id,
|
|
5715
|
+
* } from "@evolu/common";
|
|
5716
|
+
*
|
|
5717
|
+
* const TodoId = id("Todo");
|
|
5718
|
+
* const todoId = TodoId.orThrow(createIdFromString("todo"));
|
|
5719
|
+
*
|
|
5720
|
+
* expectTypeOf(todoId).toEqualTypeOf<Id & Brand<"Todo">>();
|
|
5721
|
+
* ```
|
|
5722
|
+
*
|
|
5192
5723
|
* @group String
|
|
5193
5724
|
*/
|
|
5194
5725
|
export const id = <Table extends TypeName>(
|
|
@@ -5211,7 +5742,11 @@ export const id = <Table extends TypeName>(
|
|
|
5211
5742
|
);
|
|
5212
5743
|
};
|
|
5213
5744
|
|
|
5214
|
-
/**
|
|
5745
|
+
/**
|
|
5746
|
+
* The {@link Type} returned by {@link id} for one table.
|
|
5747
|
+
*
|
|
5748
|
+
* @group String
|
|
5749
|
+
*/
|
|
5215
5750
|
export interface TableId<Table extends TypeName> extends Type<
|
|
5216
5751
|
"TableId",
|
|
5217
5752
|
string,
|
|
@@ -5226,7 +5761,11 @@ export interface TableId<Table extends TypeName> extends Type<
|
|
|
5226
5761
|
readonly table: Table;
|
|
5227
5762
|
}
|
|
5228
5763
|
|
|
5229
|
-
/**
|
|
5764
|
+
/**
|
|
5765
|
+
* Error returned when a string is not a valid {@link Id} for the expected table.
|
|
5766
|
+
*
|
|
5767
|
+
* @group String
|
|
5768
|
+
*/
|
|
5230
5769
|
export interface TableIdError<
|
|
5231
5770
|
Table extends TypeName = TypeName,
|
|
5232
5771
|
> extends TypeError<"TableId"> {
|
|
@@ -5278,14 +5817,50 @@ export const IdBytes = /*#__PURE__*/ brand(
|
|
|
5278
5817
|
);
|
|
5279
5818
|
export type IdBytes = typeof IdBytes.Output;
|
|
5280
5819
|
|
|
5281
|
-
/**
|
|
5820
|
+
/**
|
|
5821
|
+
* Byte length of an {@link IdBytes} value.
|
|
5822
|
+
*
|
|
5823
|
+
* @group String
|
|
5824
|
+
*/
|
|
5282
5825
|
export const idBytesTypeValueLength = 16 as NonNegativeInt;
|
|
5283
5826
|
|
|
5284
|
-
/**
|
|
5827
|
+
/**
|
|
5828
|
+
* Converts an {@link Id} to {@link IdBytes}.
|
|
5829
|
+
*
|
|
5830
|
+
* ### Example
|
|
5831
|
+
*
|
|
5832
|
+
* ```ts
|
|
5833
|
+
* import { createIdFromString, idToIdBytes } from "@evolu/common";
|
|
5834
|
+
*
|
|
5835
|
+
* const bytes = idToIdBytes(createIdFromString("todo"));
|
|
5836
|
+
*
|
|
5837
|
+
* expect(bytes).toHaveLength(16);
|
|
5838
|
+
* ```
|
|
5839
|
+
*
|
|
5840
|
+
* @group String
|
|
5841
|
+
*/
|
|
5285
5842
|
export const idToIdBytes = (value: Id): IdBytes =>
|
|
5286
5843
|
base64UrlToUint8Array(value as unknown as Base64Url) as IdBytes;
|
|
5287
5844
|
|
|
5288
|
-
/**
|
|
5845
|
+
/**
|
|
5846
|
+
* Converts {@link IdBytes} to an {@link Id}.
|
|
5847
|
+
*
|
|
5848
|
+
* ### Example
|
|
5849
|
+
*
|
|
5850
|
+
* ```ts
|
|
5851
|
+
* import {
|
|
5852
|
+
* createIdFromString,
|
|
5853
|
+
* idBytesToId,
|
|
5854
|
+
* idToIdBytes,
|
|
5855
|
+
* } from "@evolu/common";
|
|
5856
|
+
*
|
|
5857
|
+
* const value = createIdFromString("todo");
|
|
5858
|
+
*
|
|
5859
|
+
* expect(idBytesToId(idToIdBytes(value))).toBe(value);
|
|
5860
|
+
* ```
|
|
5861
|
+
*
|
|
5862
|
+
* @group String
|
|
5863
|
+
*/
|
|
5289
5864
|
export const idBytesToId = (value: IdBytes): Id =>
|
|
5290
5865
|
uint8ArrayToBase64Url(value) as unknown as Id;
|
|
5291
5866
|
|
|
@@ -5319,7 +5894,11 @@ export const Int64String = /*#__PURE__*/ brand(
|
|
|
5319
5894
|
);
|
|
5320
5895
|
export type Int64String = typeof Int64String.Output;
|
|
5321
5896
|
|
|
5322
|
-
/**
|
|
5897
|
+
/**
|
|
5898
|
+
* Error returned when a string is not a canonical {@link Int64String}.
|
|
5899
|
+
*
|
|
5900
|
+
* @group Number
|
|
5901
|
+
*/
|
|
5323
5902
|
export interface Int64StringError extends TypeError<"Int64String"> {
|
|
5324
5903
|
readonly value: string;
|
|
5325
5904
|
}
|
|
@@ -5331,6 +5910,19 @@ export interface Int64StringError extends TypeError<"Int64String"> {
|
|
|
5331
5910
|
* text, including SQLite queries that cast INTEGER values to TEXT to avoid a
|
|
5332
5911
|
* lossy JavaScript number conversion.
|
|
5333
5912
|
*
|
|
5913
|
+
* ### Example
|
|
5914
|
+
*
|
|
5915
|
+
* ```ts
|
|
5916
|
+
* import { Int64FromInt64String } from "@evolu/common";
|
|
5917
|
+
*
|
|
5918
|
+
* const result = Int64FromInt64String.fromUnknown("9223372036854775807");
|
|
5919
|
+
*
|
|
5920
|
+
* expectOk(result, 9223372036854775807n);
|
|
5921
|
+
* expect(Int64FromInt64String.to(result.value)).toBe(
|
|
5922
|
+
* "9223372036854775807",
|
|
5923
|
+
* );
|
|
5924
|
+
* ```
|
|
5925
|
+
*
|
|
5334
5926
|
* @group Number
|
|
5335
5927
|
*/
|
|
5336
5928
|
export const Int64FromInt64String = /*#__PURE__*/ transform(
|
|
@@ -5346,6 +5938,16 @@ export const Int64FromInt64String = /*#__PURE__*/ transform(
|
|
|
5346
5938
|
/**
|
|
5347
5939
|
* Number {@link Brand} requiring a value greater than or equal to zero.
|
|
5348
5940
|
*
|
|
5941
|
+
* ### Example
|
|
5942
|
+
*
|
|
5943
|
+
* ```ts
|
|
5944
|
+
* import { Number, nonNegative } from "@evolu/common";
|
|
5945
|
+
*
|
|
5946
|
+
* const NonNegative = nonNegative(Number);
|
|
5947
|
+
*
|
|
5948
|
+
* expectOk(NonNegative.fromUnknown(0), 0);
|
|
5949
|
+
* ```
|
|
5950
|
+
*
|
|
5349
5951
|
* @group Number
|
|
5350
5952
|
*/
|
|
5351
5953
|
export const nonNegative: BrandFactory<
|
|
@@ -5362,7 +5964,11 @@ export const nonNegative: BrandFactory<
|
|
|
5362
5964
|
`The value ${safelyStringifyUnknownValue(error.value)} must be non-negative (>= 0).`,
|
|
5363
5965
|
);
|
|
5364
5966
|
|
|
5365
|
-
/**
|
|
5967
|
+
/**
|
|
5968
|
+
* Error returned when {@link nonNegative} rejects a number.
|
|
5969
|
+
*
|
|
5970
|
+
* @group Number
|
|
5971
|
+
*/
|
|
5366
5972
|
export interface NonNegativeError extends TypeError<"NonNegative"> {
|
|
5367
5973
|
readonly value: number;
|
|
5368
5974
|
}
|
|
@@ -5378,6 +5984,16 @@ export type NonNegativeNumber = typeof NonNegativeNumber.Output;
|
|
|
5378
5984
|
/**
|
|
5379
5985
|
* Number {@link Brand} requiring a value greater than zero.
|
|
5380
5986
|
*
|
|
5987
|
+
* ### Example
|
|
5988
|
+
*
|
|
5989
|
+
* ```ts
|
|
5990
|
+
* import { Number, positive } from "@evolu/common";
|
|
5991
|
+
*
|
|
5992
|
+
* const Positive = positive(Number);
|
|
5993
|
+
*
|
|
5994
|
+
* expectOk(Positive.fromUnknown(1), 1);
|
|
5995
|
+
* ```
|
|
5996
|
+
*
|
|
5381
5997
|
* @group Number
|
|
5382
5998
|
*/
|
|
5383
5999
|
export const positive: BrandFactory<"Positive", number, PositiveError> = (
|
|
@@ -5392,7 +6008,11 @@ export const positive: BrandFactory<"Positive", number, PositiveError> = (
|
|
|
5392
6008
|
`The value ${safelyStringifyUnknownValue(error.value)} must be positive (> 0).`,
|
|
5393
6009
|
);
|
|
5394
6010
|
|
|
5395
|
-
/**
|
|
6011
|
+
/**
|
|
6012
|
+
* Error returned when {@link positive} rejects a number.
|
|
6013
|
+
*
|
|
6014
|
+
* @group Number
|
|
6015
|
+
*/
|
|
5396
6016
|
export interface PositiveError extends TypeError<"Positive"> {
|
|
5397
6017
|
readonly value: number;
|
|
5398
6018
|
}
|
|
@@ -5411,6 +6031,16 @@ export type PositiveNumber = typeof PositiveNumber.Output;
|
|
|
5411
6031
|
/**
|
|
5412
6032
|
* Number {@link Brand} requiring a value less than or equal to zero.
|
|
5413
6033
|
*
|
|
6034
|
+
* ### Example
|
|
6035
|
+
*
|
|
6036
|
+
* ```ts
|
|
6037
|
+
* import { Number, nonPositive } from "@evolu/common";
|
|
6038
|
+
*
|
|
6039
|
+
* const NonPositive = nonPositive(Number);
|
|
6040
|
+
*
|
|
6041
|
+
* expectOk(NonPositive.fromUnknown(0), 0);
|
|
6042
|
+
* ```
|
|
6043
|
+
*
|
|
5414
6044
|
* @group Number
|
|
5415
6045
|
*/
|
|
5416
6046
|
export const nonPositive: BrandFactory<
|
|
@@ -5427,7 +6057,11 @@ export const nonPositive: BrandFactory<
|
|
|
5427
6057
|
`The value ${safelyStringifyUnknownValue(error.value)} must be non-positive (<= 0).`,
|
|
5428
6058
|
);
|
|
5429
6059
|
|
|
5430
|
-
/**
|
|
6060
|
+
/**
|
|
6061
|
+
* Error returned when {@link nonPositive} rejects a number.
|
|
6062
|
+
*
|
|
6063
|
+
* @group Number
|
|
6064
|
+
*/
|
|
5431
6065
|
export interface NonPositiveError extends TypeError<"NonPositive"> {
|
|
5432
6066
|
readonly value: number;
|
|
5433
6067
|
}
|
|
@@ -5443,6 +6077,16 @@ export type NonPositiveNumber = typeof NonPositiveNumber.Output;
|
|
|
5443
6077
|
/**
|
|
5444
6078
|
* Number {@link Brand} requiring a value less than zero.
|
|
5445
6079
|
*
|
|
6080
|
+
* ### Example
|
|
6081
|
+
*
|
|
6082
|
+
* ```ts
|
|
6083
|
+
* import { Number, negative } from "@evolu/common";
|
|
6084
|
+
*
|
|
6085
|
+
* const Negative = negative(Number);
|
|
6086
|
+
*
|
|
6087
|
+
* expectOk(Negative.fromUnknown(-1), -1);
|
|
6088
|
+
* ```
|
|
6089
|
+
*
|
|
5446
6090
|
* @group Number
|
|
5447
6091
|
*/
|
|
5448
6092
|
export const negative: BrandFactory<"Negative", number, NegativeError> = (
|
|
@@ -5457,7 +6101,11 @@ export const negative: BrandFactory<"Negative", number, NegativeError> = (
|
|
|
5457
6101
|
`The value ${safelyStringifyUnknownValue(error.value)} must be negative (< 0).`,
|
|
5458
6102
|
);
|
|
5459
6103
|
|
|
5460
|
-
/**
|
|
6104
|
+
/**
|
|
6105
|
+
* Error returned when {@link negative} rejects a number.
|
|
6106
|
+
*
|
|
6107
|
+
* @group Number
|
|
6108
|
+
*/
|
|
5461
6109
|
export interface NegativeError extends TypeError<"Negative"> {
|
|
5462
6110
|
readonly value: number;
|
|
5463
6111
|
}
|
|
@@ -5476,6 +6124,16 @@ export type NegativeNumber = typeof NegativeNumber.Output;
|
|
|
5476
6124
|
/**
|
|
5477
6125
|
* Number {@link Brand} requiring a value other than `NaN`.
|
|
5478
6126
|
*
|
|
6127
|
+
* ### Example
|
|
6128
|
+
*
|
|
6129
|
+
* ```ts
|
|
6130
|
+
* import { Number, nonNaN } from "@evolu/common";
|
|
6131
|
+
*
|
|
6132
|
+
* const NonNaN = nonNaN(Number);
|
|
6133
|
+
*
|
|
6134
|
+
* expectOk(NonNaN.fromUnknown(Infinity), Infinity);
|
|
6135
|
+
* ```
|
|
6136
|
+
*
|
|
5479
6137
|
* @group Number
|
|
5480
6138
|
*/
|
|
5481
6139
|
export const nonNaN: BrandFactory<"NonNaN", number, NonNaNError> = (parent) =>
|
|
@@ -5489,7 +6147,11 @@ export const nonNaN: BrandFactory<"NonNaN", number, NonNaNError> = (parent) =>
|
|
|
5489
6147
|
() => "The value must not be NaN.",
|
|
5490
6148
|
);
|
|
5491
6149
|
|
|
5492
|
-
/**
|
|
6150
|
+
/**
|
|
6151
|
+
* Error returned when {@link nonNaN} rejects `NaN`.
|
|
6152
|
+
*
|
|
6153
|
+
* @group Number
|
|
6154
|
+
*/
|
|
5493
6155
|
export interface NonNaNError extends TypeError<"NonNaN"> {
|
|
5494
6156
|
readonly value: number;
|
|
5495
6157
|
}
|
|
@@ -5510,6 +6172,16 @@ export type NonNaNNumber = typeof NonNaNNumber.Output;
|
|
|
5510
6172
|
/**
|
|
5511
6173
|
* Number {@link Brand} requiring a finite value.
|
|
5512
6174
|
*
|
|
6175
|
+
* ### Example
|
|
6176
|
+
*
|
|
6177
|
+
* ```ts
|
|
6178
|
+
* import { Number, finite } from "@evolu/common";
|
|
6179
|
+
*
|
|
6180
|
+
* const Finite = finite(Number);
|
|
6181
|
+
*
|
|
6182
|
+
* expectOk(Finite.fromUnknown(42), 42);
|
|
6183
|
+
* ```
|
|
6184
|
+
*
|
|
5513
6185
|
* @group Number
|
|
5514
6186
|
*/
|
|
5515
6187
|
export const finite: BrandFactory<"Finite", number, FiniteError> = (parent) =>
|
|
@@ -5524,7 +6196,11 @@ export const finite: BrandFactory<"Finite", number, FiniteError> = (parent) =>
|
|
|
5524
6196
|
`The value ${safelyStringifyUnknownValue(error.value)} must be finite.`,
|
|
5525
6197
|
);
|
|
5526
6198
|
|
|
5527
|
-
/**
|
|
6199
|
+
/**
|
|
6200
|
+
* Error returned when {@link finite} rejects a non-finite number.
|
|
6201
|
+
*
|
|
6202
|
+
* @group Number
|
|
6203
|
+
*/
|
|
5528
6204
|
export interface FiniteError extends TypeError<"Finite"> {
|
|
5529
6205
|
readonly value: number;
|
|
5530
6206
|
}
|
|
@@ -5589,7 +6265,11 @@ export const int: BrandFactory<"Int", number, IntError> = (parent) =>
|
|
|
5589
6265
|
`The value ${safelyStringifyUnknownValue(error.value)} must be a safe integer.`,
|
|
5590
6266
|
);
|
|
5591
6267
|
|
|
5592
|
-
/**
|
|
6268
|
+
/**
|
|
6269
|
+
* Error returned when {@link int} rejects a number that is not a safe integer.
|
|
6270
|
+
*
|
|
6271
|
+
* @group Number
|
|
6272
|
+
*/
|
|
5593
6273
|
export interface IntError extends TypeError<"Int"> {
|
|
5594
6274
|
readonly value: number;
|
|
5595
6275
|
}
|
|
@@ -5665,6 +6345,16 @@ export type NegativeInt = typeof NegativeInt.Output;
|
|
|
5665
6345
|
/**
|
|
5666
6346
|
* Number {@link Brand} requiring a value greater than `min`.
|
|
5667
6347
|
*
|
|
6348
|
+
* ### Example
|
|
6349
|
+
*
|
|
6350
|
+
* ```ts
|
|
6351
|
+
* import { Number, greaterThan } from "@evolu/common";
|
|
6352
|
+
*
|
|
6353
|
+
* const GreaterThanTen = greaterThan(10)(Number);
|
|
6354
|
+
*
|
|
6355
|
+
* expectOk(GreaterThanTen.fromUnknown(11), 11);
|
|
6356
|
+
* ```
|
|
6357
|
+
*
|
|
5668
6358
|
* @group Number
|
|
5669
6359
|
*/
|
|
5670
6360
|
export const greaterThan =
|
|
@@ -5686,7 +6376,11 @@ export const greaterThan =
|
|
|
5686
6376
|
);
|
|
5687
6377
|
};
|
|
5688
6378
|
|
|
5689
|
-
/**
|
|
6379
|
+
/**
|
|
6380
|
+
* Error returned when {@link greaterThan} rejects a number.
|
|
6381
|
+
*
|
|
6382
|
+
* @group Number
|
|
6383
|
+
*/
|
|
5690
6384
|
export interface GreaterThanError<
|
|
5691
6385
|
Min extends number = number,
|
|
5692
6386
|
> extends TypeError<`GreaterThan${Min}`> {
|
|
@@ -5697,6 +6391,16 @@ export interface GreaterThanError<
|
|
|
5697
6391
|
/**
|
|
5698
6392
|
* Number {@link Brand} requiring a value greater than or equal to `min`.
|
|
5699
6393
|
*
|
|
6394
|
+
* ### Example
|
|
6395
|
+
*
|
|
6396
|
+
* ```ts
|
|
6397
|
+
* import { Number, greaterThanOrEqualTo } from "@evolu/common";
|
|
6398
|
+
*
|
|
6399
|
+
* const AtLeastTen = greaterThanOrEqualTo(10)(Number);
|
|
6400
|
+
*
|
|
6401
|
+
* expectOk(AtLeastTen.fromUnknown(10), 10);
|
|
6402
|
+
* ```
|
|
6403
|
+
*
|
|
5700
6404
|
* @group Number
|
|
5701
6405
|
*/
|
|
5702
6406
|
export const greaterThanOrEqualTo =
|
|
@@ -5726,7 +6430,11 @@ export const greaterThanOrEqualTo =
|
|
|
5726
6430
|
);
|
|
5727
6431
|
};
|
|
5728
6432
|
|
|
5729
|
-
/**
|
|
6433
|
+
/**
|
|
6434
|
+
* Error returned when {@link greaterThanOrEqualTo} rejects a number.
|
|
6435
|
+
*
|
|
6436
|
+
* @group Number
|
|
6437
|
+
*/
|
|
5730
6438
|
export interface GreaterThanOrEqualToError<
|
|
5731
6439
|
Min extends number = number,
|
|
5732
6440
|
> extends TypeError<`GreaterThanOrEqualTo${Min}`> {
|
|
@@ -5737,6 +6445,16 @@ export interface GreaterThanOrEqualToError<
|
|
|
5737
6445
|
/**
|
|
5738
6446
|
* Number {@link Brand} requiring a value less than `max`.
|
|
5739
6447
|
*
|
|
6448
|
+
* ### Example
|
|
6449
|
+
*
|
|
6450
|
+
* ```ts
|
|
6451
|
+
* import { Number, lessThan } from "@evolu/common";
|
|
6452
|
+
*
|
|
6453
|
+
* const LessThanTen = lessThan(10)(Number);
|
|
6454
|
+
*
|
|
6455
|
+
* expectOk(LessThanTen.fromUnknown(9), 9);
|
|
6456
|
+
* ```
|
|
6457
|
+
*
|
|
5740
6458
|
* @group Number
|
|
5741
6459
|
*/
|
|
5742
6460
|
export const lessThan =
|
|
@@ -5758,7 +6476,11 @@ export const lessThan =
|
|
|
5758
6476
|
);
|
|
5759
6477
|
};
|
|
5760
6478
|
|
|
5761
|
-
/**
|
|
6479
|
+
/**
|
|
6480
|
+
* Error returned when {@link lessThan} rejects a number.
|
|
6481
|
+
*
|
|
6482
|
+
* @group Number
|
|
6483
|
+
*/
|
|
5762
6484
|
export interface LessThanError<
|
|
5763
6485
|
Max extends number = number,
|
|
5764
6486
|
> extends TypeError<`LessThan${Max}`> {
|
|
@@ -5780,6 +6502,16 @@ export type Age = typeof Age.Output;
|
|
|
5780
6502
|
/**
|
|
5781
6503
|
* Number {@link Brand} requiring a value less than or equal to `max`.
|
|
5782
6504
|
*
|
|
6505
|
+
* ### Example
|
|
6506
|
+
*
|
|
6507
|
+
* ```ts
|
|
6508
|
+
* import { Number, lessThanOrEqualTo } from "@evolu/common";
|
|
6509
|
+
*
|
|
6510
|
+
* const AtMostTen = lessThanOrEqualTo(10)(Number);
|
|
6511
|
+
*
|
|
6512
|
+
* expectOk(AtMostTen.fromUnknown(10), 10);
|
|
6513
|
+
* ```
|
|
6514
|
+
*
|
|
5783
6515
|
* @group Number
|
|
5784
6516
|
*/
|
|
5785
6517
|
export const lessThanOrEqualTo =
|
|
@@ -5805,7 +6537,11 @@ export const lessThanOrEqualTo =
|
|
|
5805
6537
|
);
|
|
5806
6538
|
};
|
|
5807
6539
|
|
|
5808
|
-
/**
|
|
6540
|
+
/**
|
|
6541
|
+
* Error returned when {@link lessThanOrEqualTo} rejects a number.
|
|
6542
|
+
*
|
|
6543
|
+
* @group Number
|
|
6544
|
+
*/
|
|
5809
6545
|
export interface LessThanOrEqualToError<
|
|
5810
6546
|
Max extends number = number,
|
|
5811
6547
|
> extends TypeError<`LessThanOrEqualTo${Max}`> {
|
|
@@ -5879,7 +6615,11 @@ export const DecimalString = /*#__PURE__*/ brand(
|
|
|
5879
6615
|
);
|
|
5880
6616
|
export type DecimalString = typeof DecimalString.Output;
|
|
5881
6617
|
|
|
5882
|
-
/**
|
|
6618
|
+
/**
|
|
6619
|
+
* Error returned when a string is not a canonical {@link DecimalString}.
|
|
6620
|
+
*
|
|
6621
|
+
* @group Number
|
|
6622
|
+
*/
|
|
5883
6623
|
export interface DecimalStringError extends TypeError<"DecimalString"> {
|
|
5884
6624
|
readonly value: string;
|
|
5885
6625
|
}
|
|
@@ -5887,6 +6627,16 @@ export interface DecimalStringError extends TypeError<"DecimalString"> {
|
|
|
5887
6627
|
/**
|
|
5888
6628
|
* {@link DecimalString} Brand requiring a value greater than or equal to zero.
|
|
5889
6629
|
*
|
|
6630
|
+
* ### Example
|
|
6631
|
+
*
|
|
6632
|
+
* ```ts
|
|
6633
|
+
* import { DecimalString, nonNegativeDecimalString } from "@evolu/common";
|
|
6634
|
+
*
|
|
6635
|
+
* const NonNegative = nonNegativeDecimalString(DecimalString);
|
|
6636
|
+
*
|
|
6637
|
+
* expectOk(NonNegative.fromUnknown("0.5"), "0.5");
|
|
6638
|
+
* ```
|
|
6639
|
+
*
|
|
5890
6640
|
* @group Number
|
|
5891
6641
|
*/
|
|
5892
6642
|
export const nonNegativeDecimalString: BrandFactory<
|
|
@@ -5908,7 +6658,11 @@ export const nonNegativeDecimalString: BrandFactory<
|
|
|
5908
6658
|
`The value ${safelyStringifyUnknownValue(error.value)} must be a non-negative decimal string.`,
|
|
5909
6659
|
);
|
|
5910
6660
|
|
|
5911
|
-
/**
|
|
6661
|
+
/**
|
|
6662
|
+
* Error returned when {@link nonNegativeDecimalString} rejects a decimal string.
|
|
6663
|
+
*
|
|
6664
|
+
* @group Number
|
|
6665
|
+
*/
|
|
5912
6666
|
export interface NonNegativeDecimalStringError extends TypeError<"NonNegativeDecimalString"> {
|
|
5913
6667
|
readonly value: string;
|
|
5914
6668
|
}
|
|
@@ -5925,6 +6679,16 @@ export type NonNegativeDecimalString = typeof NonNegativeDecimalString.Output;
|
|
|
5925
6679
|
/**
|
|
5926
6680
|
* {@link DecimalString} Brand requiring a value greater than zero.
|
|
5927
6681
|
*
|
|
6682
|
+
* ### Example
|
|
6683
|
+
*
|
|
6684
|
+
* ```ts
|
|
6685
|
+
* import { DecimalString, positiveDecimalString } from "@evolu/common";
|
|
6686
|
+
*
|
|
6687
|
+
* const Positive = positiveDecimalString(DecimalString);
|
|
6688
|
+
*
|
|
6689
|
+
* expectOk(Positive.fromUnknown("0.5"), "0.5");
|
|
6690
|
+
* ```
|
|
6691
|
+
*
|
|
5928
6692
|
* @group Number
|
|
5929
6693
|
*/
|
|
5930
6694
|
export const positiveDecimalString: BrandFactory<
|
|
@@ -5946,7 +6710,11 @@ export const positiveDecimalString: BrandFactory<
|
|
|
5946
6710
|
`The value ${safelyStringifyUnknownValue(error.value)} must be a positive decimal string.`,
|
|
5947
6711
|
);
|
|
5948
6712
|
|
|
5949
|
-
/**
|
|
6713
|
+
/**
|
|
6714
|
+
* Error returned when {@link positiveDecimalString} rejects a decimal string.
|
|
6715
|
+
*
|
|
6716
|
+
* @group Number
|
|
6717
|
+
*/
|
|
5950
6718
|
export interface PositiveDecimalStringError extends TypeError<"PositiveDecimalString"> {
|
|
5951
6719
|
readonly value: string;
|
|
5952
6720
|
}
|
|
@@ -5967,6 +6735,16 @@ export type PositiveDecimalString = typeof PositiveDecimalString.Output;
|
|
|
5967
6735
|
/**
|
|
5968
6736
|
* {@link DecimalString} Brand requiring a value less than or equal to zero.
|
|
5969
6737
|
*
|
|
6738
|
+
* ### Example
|
|
6739
|
+
*
|
|
6740
|
+
* ```ts
|
|
6741
|
+
* import { DecimalString, nonPositiveDecimalString } from "@evolu/common";
|
|
6742
|
+
*
|
|
6743
|
+
* const NonPositive = nonPositiveDecimalString(DecimalString);
|
|
6744
|
+
*
|
|
6745
|
+
* expectOk(NonPositive.fromUnknown("-0.5"), "-0.5");
|
|
6746
|
+
* ```
|
|
6747
|
+
*
|
|
5970
6748
|
* @group Number
|
|
5971
6749
|
*/
|
|
5972
6750
|
export const nonPositiveDecimalString: BrandFactory<
|
|
@@ -5988,7 +6766,11 @@ export const nonPositiveDecimalString: BrandFactory<
|
|
|
5988
6766
|
`The value ${safelyStringifyUnknownValue(error.value)} must be a non-positive decimal string.`,
|
|
5989
6767
|
);
|
|
5990
6768
|
|
|
5991
|
-
/**
|
|
6769
|
+
/**
|
|
6770
|
+
* Error returned when {@link nonPositiveDecimalString} rejects a decimal string.
|
|
6771
|
+
*
|
|
6772
|
+
* @group Number
|
|
6773
|
+
*/
|
|
5992
6774
|
export interface NonPositiveDecimalStringError extends TypeError<"NonPositiveDecimalString"> {
|
|
5993
6775
|
readonly value: string;
|
|
5994
6776
|
}
|
|
@@ -6005,6 +6787,16 @@ export type NonPositiveDecimalString = typeof NonPositiveDecimalString.Output;
|
|
|
6005
6787
|
/**
|
|
6006
6788
|
* {@link DecimalString} Brand requiring a value less than zero.
|
|
6007
6789
|
*
|
|
6790
|
+
* ### Example
|
|
6791
|
+
*
|
|
6792
|
+
* ```ts
|
|
6793
|
+
* import { DecimalString, negativeDecimalString } from "@evolu/common";
|
|
6794
|
+
*
|
|
6795
|
+
* const Negative = negativeDecimalString(DecimalString);
|
|
6796
|
+
*
|
|
6797
|
+
* expectOk(Negative.fromUnknown("-0.5"), "-0.5");
|
|
6798
|
+
* ```
|
|
6799
|
+
*
|
|
6008
6800
|
* @group Number
|
|
6009
6801
|
*/
|
|
6010
6802
|
export const negativeDecimalString: BrandFactory<
|
|
@@ -6026,7 +6818,11 @@ export const negativeDecimalString: BrandFactory<
|
|
|
6026
6818
|
`The value ${safelyStringifyUnknownValue(error.value)} must be a negative decimal string.`,
|
|
6027
6819
|
);
|
|
6028
6820
|
|
|
6029
|
-
/**
|
|
6821
|
+
/**
|
|
6822
|
+
* Error returned when {@link negativeDecimalString} rejects a decimal string.
|
|
6823
|
+
*
|
|
6824
|
+
* @group Number
|
|
6825
|
+
*/
|
|
6030
6826
|
export interface NegativeDecimalStringError extends TypeError<"NegativeDecimalString"> {
|
|
6031
6827
|
readonly value: string;
|
|
6032
6828
|
}
|
|
@@ -6118,7 +6914,11 @@ export const multipleOf = <const Divisor extends string>(
|
|
|
6118
6914
|
);
|
|
6119
6915
|
};
|
|
6120
6916
|
|
|
6121
|
-
/**
|
|
6917
|
+
/**
|
|
6918
|
+
* Error returned when {@link multipleOf} rejects a number.
|
|
6919
|
+
*
|
|
6920
|
+
* @group Number
|
|
6921
|
+
*/
|
|
6122
6922
|
export interface MultipleOfError<
|
|
6123
6923
|
Divisor extends string = string,
|
|
6124
6924
|
> extends TypeError<`MultipleOf${Divisor}`> {
|
|
@@ -6197,6 +6997,16 @@ const decimalStringToParts = (value: string): DecimalParts => {
|
|
|
6197
6997
|
/**
|
|
6198
6998
|
* Number {@link Brand} requiring a value within an inclusive range.
|
|
6199
6999
|
*
|
|
7000
|
+
* ### Example
|
|
7001
|
+
*
|
|
7002
|
+
* ```ts
|
|
7003
|
+
* import { Number, between } from "@evolu/common";
|
|
7004
|
+
*
|
|
7005
|
+
* const Percentage = between(0, 100)(Number);
|
|
7006
|
+
*
|
|
7007
|
+
* expectOk(Percentage.fromUnknown(75), 75);
|
|
7008
|
+
* ```
|
|
7009
|
+
*
|
|
6200
7010
|
* @group Number
|
|
6201
7011
|
*/
|
|
6202
7012
|
export const between =
|
|
@@ -6224,7 +7034,11 @@ export const between =
|
|
|
6224
7034
|
);
|
|
6225
7035
|
};
|
|
6226
7036
|
|
|
6227
|
-
/**
|
|
7037
|
+
/**
|
|
7038
|
+
* Error returned when {@link between} rejects a number.
|
|
7039
|
+
*
|
|
7040
|
+
* @group Number
|
|
7041
|
+
*/
|
|
6228
7042
|
export interface BetweenError<
|
|
6229
7043
|
Min extends number = number,
|
|
6230
7044
|
Max extends number = number,
|
|
@@ -6292,7 +7106,11 @@ export const array = <ElementType extends ConcreteTypeNode>(
|
|
|
6292
7106
|
arrayRuntimeConfig,
|
|
6293
7107
|
) as ArrayType<ElementType>;
|
|
6294
7108
|
|
|
6295
|
-
/**
|
|
7109
|
+
/**
|
|
7110
|
+
* The homogeneous readonly-array {@link Type} returned by {@link array}.
|
|
7111
|
+
*
|
|
7112
|
+
* @group Collection
|
|
7113
|
+
*/
|
|
6296
7114
|
export interface ArrayType<ElementType extends TypeNode> extends Type<
|
|
6297
7115
|
"Array",
|
|
6298
7116
|
ReadonlyArray<ElementType["Input"]>,
|
|
@@ -6371,11 +7189,19 @@ type ArrayNodeError<ElementType extends TypeNode> = [
|
|
|
6371
7189
|
? ArrayElementsError<ElementType["Error"]>
|
|
6372
7190
|
: ArrayError<ElementType["Error"]>;
|
|
6373
7191
|
|
|
6374
|
-
/**
|
|
7192
|
+
/**
|
|
7193
|
+
* Error returned by {@link array} for a non-array value or invalid array items.
|
|
7194
|
+
*
|
|
7195
|
+
* @group Collection
|
|
7196
|
+
*/
|
|
6375
7197
|
export type ArrayError<Error extends TypeError = TypeError> =
|
|
6376
7198
|
ArrayNotArrayError | ArrayItemsErrorValue<Error, true>;
|
|
6377
7199
|
|
|
6378
|
-
/**
|
|
7200
|
+
/**
|
|
7201
|
+
* Error returned when an {@link array} input is not an array.
|
|
7202
|
+
*
|
|
7203
|
+
* @group Collection
|
|
7204
|
+
*/
|
|
6379
7205
|
export interface ArrayNotArrayError extends TypeError<"Array"> {
|
|
6380
7206
|
readonly reason: {
|
|
6381
7207
|
readonly kind: "NotArray";
|
|
@@ -6383,29 +7209,49 @@ export interface ArrayNotArrayError extends TypeError<"Array"> {
|
|
|
6383
7209
|
};
|
|
6384
7210
|
}
|
|
6385
7211
|
|
|
6386
|
-
/**
|
|
7212
|
+
/**
|
|
7213
|
+
* An {@link array} error containing structural or element issues.
|
|
7214
|
+
*
|
|
7215
|
+
* @group Collection
|
|
7216
|
+
*/
|
|
6387
7217
|
export type ArrayItemsError<Error extends TypeError> = ArrayItemsErrorValue<
|
|
6388
7218
|
Error,
|
|
6389
7219
|
true
|
|
6390
7220
|
>;
|
|
6391
7221
|
|
|
6392
|
-
/**
|
|
7222
|
+
/**
|
|
7223
|
+
* One structural or element issue found by {@link array}.
|
|
7224
|
+
*
|
|
7225
|
+
* @group Collection
|
|
7226
|
+
*/
|
|
6393
7227
|
export type ArrayIssue<Error extends TypeError> =
|
|
6394
7228
|
ArrayStructuralIssue | ArrayElementIssue<Error>;
|
|
6395
7229
|
|
|
6396
|
-
/**
|
|
7230
|
+
/**
|
|
7231
|
+
* A missing array element.
|
|
7232
|
+
*
|
|
7233
|
+
* @group Collection
|
|
7234
|
+
*/
|
|
6397
7235
|
export interface ArrayHoleIssue {
|
|
6398
7236
|
readonly kind: "Hole";
|
|
6399
7237
|
readonly index: number;
|
|
6400
7238
|
}
|
|
6401
7239
|
|
|
6402
|
-
/**
|
|
7240
|
+
/**
|
|
7241
|
+
* An array element defined by an accessor instead of a data property.
|
|
7242
|
+
*
|
|
7243
|
+
* @group Collection
|
|
7244
|
+
*/
|
|
6403
7245
|
export interface ArrayAccessorIssue {
|
|
6404
7246
|
readonly kind: "Accessor";
|
|
6405
7247
|
readonly index: number;
|
|
6406
7248
|
}
|
|
6407
7249
|
|
|
6408
|
-
/**
|
|
7250
|
+
/**
|
|
7251
|
+
* An own array property other than `length` or an indexed element.
|
|
7252
|
+
*
|
|
7253
|
+
* @group Collection
|
|
7254
|
+
*/
|
|
6409
7255
|
export interface ArrayExcessPropertyIssue {
|
|
6410
7256
|
readonly kind: "ExcessProperty";
|
|
6411
7257
|
readonly key: string | symbol;
|
|
@@ -6414,7 +7260,11 @@ export interface ArrayExcessPropertyIssue {
|
|
|
6414
7260
|
type ArrayStructuralIssue =
|
|
6415
7261
|
ArrayHoleIssue | ArrayAccessorIssue | ArrayExcessPropertyIssue;
|
|
6416
7262
|
|
|
6417
|
-
/**
|
|
7263
|
+
/**
|
|
7264
|
+
* An invalid array element and its index.
|
|
7265
|
+
*
|
|
7266
|
+
* @group Collection
|
|
7267
|
+
*/
|
|
6418
7268
|
export type ArrayElementIssue<Error extends TypeError> = Error extends TypeError
|
|
6419
7269
|
? {
|
|
6420
7270
|
readonly kind: "Element";
|
|
@@ -6423,7 +7273,11 @@ export type ArrayElementIssue<Error extends TypeError> = Error extends TypeError
|
|
|
6423
7273
|
}
|
|
6424
7274
|
: never;
|
|
6425
7275
|
|
|
6426
|
-
/**
|
|
7276
|
+
/**
|
|
7277
|
+
* An {@link array} error containing element errors from a typed boundary.
|
|
7278
|
+
*
|
|
7279
|
+
* @group Collection
|
|
7280
|
+
*/
|
|
6427
7281
|
export type ArrayElementsError<Error extends TypeError> = [Error] extends [
|
|
6428
7282
|
never,
|
|
6429
7283
|
]
|
|
@@ -6494,23 +7348,6 @@ const isArrayCollection = (
|
|
|
6494
7348
|
return true;
|
|
6495
7349
|
};
|
|
6496
7350
|
|
|
6497
|
-
const formatArrayError: TypeErrorFormatter<ArrayError> = (error) => {
|
|
6498
|
-
if (error.reason.kind === "NotArray") {
|
|
6499
|
-
return `A value ${safelyStringifyUnknownValue(error.reason.value)} is not an array.`;
|
|
6500
|
-
}
|
|
6501
|
-
|
|
6502
|
-
const issue = error.reason.issues[0] as ArrayStructuralIssue;
|
|
6503
|
-
|
|
6504
|
-
switch (issue.kind) {
|
|
6505
|
-
case "Hole":
|
|
6506
|
-
return `An array element at index ${issue.index} is missing.`;
|
|
6507
|
-
case "Accessor":
|
|
6508
|
-
return `An array element at index ${issue.index} must be a data property.`;
|
|
6509
|
-
case "ExcessProperty":
|
|
6510
|
-
return "An excess Array property is not allowed. Remove it or use a different Type.";
|
|
6511
|
-
}
|
|
6512
|
-
};
|
|
6513
|
-
|
|
6514
7351
|
const arrayRuntimeConfig: HomogeneousCollectionRuntimeConfig<
|
|
6515
7352
|
ReadonlyArray<unknown>
|
|
6516
7353
|
> = {
|
|
@@ -6521,7 +7358,23 @@ const arrayRuntimeConfig: HomogeneousCollectionRuntimeConfig<
|
|
|
6521
7358
|
validateArrayItems(value, validateElement, options, false),
|
|
6522
7359
|
encode: encodeArrayCollection,
|
|
6523
7360
|
is: isArrayCollection,
|
|
6524
|
-
formatError:
|
|
7361
|
+
formatError: ((error: ArrayError) => {
|
|
7362
|
+
if (error.reason.kind === "NotArray") {
|
|
7363
|
+
return `A value ${safelyStringifyUnknownValue(error.reason.value)} is not an array.`;
|
|
7364
|
+
}
|
|
7365
|
+
const issue = error.reason.issues[0];
|
|
7366
|
+
|
|
7367
|
+
switch (issue.kind) {
|
|
7368
|
+
case "Hole":
|
|
7369
|
+
return `An array element at index ${issue.index} is missing.`;
|
|
7370
|
+
case "Accessor":
|
|
7371
|
+
return `An array element at index ${issue.index} must be a data property.`;
|
|
7372
|
+
case "ExcessProperty":
|
|
7373
|
+
return "An excess Array property is not allowed. Remove it or use a different Type.";
|
|
7374
|
+
case "Element":
|
|
7375
|
+
return `An array element at index ${issue.index} is invalid.`;
|
|
7376
|
+
}
|
|
7377
|
+
}) as TypeErrorFormatter<TypeError>,
|
|
6525
7378
|
};
|
|
6526
7379
|
|
|
6527
7380
|
const validateArrayItems = (
|
|
@@ -6658,6 +7511,17 @@ const copyArrayPrefix = (
|
|
|
6658
7511
|
* iteration order. Classification uses the realm-neutral object tag and
|
|
6659
7512
|
* prototype structure under Evolu Type's trusted JavaScript policy.
|
|
6660
7513
|
*
|
|
7514
|
+
* ### Example
|
|
7515
|
+
*
|
|
7516
|
+
* ```ts
|
|
7517
|
+
* import { String, set } from "@evolu/common";
|
|
7518
|
+
*
|
|
7519
|
+
* const Tags = set(String);
|
|
7520
|
+
* const tags = new Set(["local-first", "offline"]);
|
|
7521
|
+
*
|
|
7522
|
+
* expectOk(Tags.fromUnknown(tags), tags);
|
|
7523
|
+
* ```
|
|
7524
|
+
*
|
|
6661
7525
|
* @group Collection
|
|
6662
7526
|
*/
|
|
6663
7527
|
export const set = <ElementType extends ConcreteTypeNode>(
|
|
@@ -6668,7 +7532,11 @@ export const set = <ElementType extends ConcreteTypeNode>(
|
|
|
6668
7532
|
setRuntimeConfig,
|
|
6669
7533
|
) as SetType<ElementType>;
|
|
6670
7534
|
|
|
6671
|
-
/**
|
|
7535
|
+
/**
|
|
7536
|
+
* The homogeneous readonly-set {@link Type} returned by {@link set}.
|
|
7537
|
+
*
|
|
7538
|
+
* @group Collection
|
|
7539
|
+
*/
|
|
6672
7540
|
export interface SetType<ElementType extends TypeNode> extends Type<
|
|
6673
7541
|
"Set",
|
|
6674
7542
|
ReadonlySet<ElementType["Input"]>,
|
|
@@ -6739,13 +7607,22 @@ type SetNodeError<ElementType extends TypeNode> = [
|
|
|
6739
7607
|
? SetElementsError<ElementType["Error"]>
|
|
6740
7608
|
: SetError<ElementType["Error"]>;
|
|
6741
7609
|
|
|
6742
|
-
/**
|
|
7610
|
+
/**
|
|
7611
|
+
* Error returned by {@link set} for a non-Set value, an invalid Set prototype,
|
|
7612
|
+
* or invalid Set items.
|
|
7613
|
+
*
|
|
7614
|
+
* @group Collection
|
|
7615
|
+
*/
|
|
6743
7616
|
export type SetError<Error extends TypeError = TypeError> =
|
|
6744
7617
|
| SetNotSetError
|
|
6745
7618
|
| SetUnexpectedPrototypeError
|
|
6746
7619
|
| SetItemsErrorValue<Error, true>;
|
|
6747
7620
|
|
|
6748
|
-
/**
|
|
7621
|
+
/**
|
|
7622
|
+
* Error returned when a {@link set} input is not a Set.
|
|
7623
|
+
*
|
|
7624
|
+
* @group Collection
|
|
7625
|
+
*/
|
|
6749
7626
|
export interface SetNotSetError extends TypeError<"Set"> {
|
|
6750
7627
|
readonly reason: {
|
|
6751
7628
|
readonly kind: "NotSet";
|
|
@@ -6765,7 +7642,11 @@ export interface SetUnexpectedPrototypeError extends TypeError<"Set"> {
|
|
|
6765
7642
|
};
|
|
6766
7643
|
}
|
|
6767
7644
|
|
|
6768
|
-
/**
|
|
7645
|
+
/**
|
|
7646
|
+
* An own property found on a Set value.
|
|
7647
|
+
*
|
|
7648
|
+
* @group Collection
|
|
7649
|
+
*/
|
|
6769
7650
|
export interface SetExcessPropertyIssue {
|
|
6770
7651
|
readonly kind: "ExcessProperty";
|
|
6771
7652
|
readonly key: string | symbol;
|
|
@@ -6773,7 +7654,11 @@ export interface SetExcessPropertyIssue {
|
|
|
6773
7654
|
|
|
6774
7655
|
type SetStructuralIssue = SetExcessPropertyIssue;
|
|
6775
7656
|
|
|
6776
|
-
/**
|
|
7657
|
+
/**
|
|
7658
|
+
* An invalid Set element and its iteration index.
|
|
7659
|
+
*
|
|
7660
|
+
* @group Collection
|
|
7661
|
+
*/
|
|
6777
7662
|
export type SetElementIssue<Error extends TypeError> = Error extends TypeError
|
|
6778
7663
|
? {
|
|
6779
7664
|
readonly kind: "Element";
|
|
@@ -6782,13 +7667,21 @@ export type SetElementIssue<Error extends TypeError> = Error extends TypeError
|
|
|
6782
7667
|
}
|
|
6783
7668
|
: never;
|
|
6784
7669
|
|
|
6785
|
-
/**
|
|
7670
|
+
/**
|
|
7671
|
+
* A {@link set} error containing structural or element issues.
|
|
7672
|
+
*
|
|
7673
|
+
* @group Collection
|
|
7674
|
+
*/
|
|
6786
7675
|
export type SetItemsError<Error extends TypeError> = SetItemsErrorValue<
|
|
6787
7676
|
Error,
|
|
6788
7677
|
true
|
|
6789
7678
|
>;
|
|
6790
7679
|
|
|
6791
|
-
/**
|
|
7680
|
+
/**
|
|
7681
|
+
* A {@link set} error containing element errors from a typed boundary.
|
|
7682
|
+
*
|
|
7683
|
+
* @group Collection
|
|
7684
|
+
*/
|
|
6792
7685
|
export type SetElementsError<Error extends TypeError> = [Error] extends [never]
|
|
6793
7686
|
? never
|
|
6794
7687
|
: SetItemsErrorValue<Error, false>;
|
|
@@ -6960,18 +7853,6 @@ const isSetCollection = (
|
|
|
6960
7853
|
return true;
|
|
6961
7854
|
};
|
|
6962
7855
|
|
|
6963
|
-
const formatSetError: TypeErrorFormatter<SetError> = (error) => {
|
|
6964
|
-
if (error.reason.kind === "NotSet") {
|
|
6965
|
-
return `A value ${safelyStringifyUnknownValue(error.reason.value)} is not a Set.`;
|
|
6966
|
-
}
|
|
6967
|
-
if (error.reason.kind === "UnexpectedPrototype") {
|
|
6968
|
-
return "The value is a Set subclass, but a Set Output must be a direct Set.";
|
|
6969
|
-
}
|
|
6970
|
-
|
|
6971
|
-
const issue = error.reason.issues[0] as SetStructuralIssue;
|
|
6972
|
-
return `An excess Set property ${safelyStringifyUnknownValue(issue.key)} is not allowed.`;
|
|
6973
|
-
};
|
|
6974
|
-
|
|
6975
7856
|
const hasDirectSetPrototype = (value: object): boolean => {
|
|
6976
7857
|
const prototype: unknown = globalThis.Object.getPrototypeOf(value);
|
|
6977
7858
|
if (prototype === null) return false;
|
|
@@ -6994,7 +7875,19 @@ const setRuntimeConfig: HomogeneousCollectionRuntimeConfig<
|
|
|
6994
7875
|
validateSetItems(value, validateElement, options, false),
|
|
6995
7876
|
encode: encodeSetCollection,
|
|
6996
7877
|
is: isSetCollection,
|
|
6997
|
-
formatError:
|
|
7878
|
+
formatError: ((error: SetError) => {
|
|
7879
|
+
if (error.reason.kind === "NotSet")
|
|
7880
|
+
return `A value ${safelyStringifyUnknownValue(error.reason.value)} is not a Set.`;
|
|
7881
|
+
if (error.reason.kind === "UnexpectedPrototype")
|
|
7882
|
+
return "The value is an instance of a Set subclass, but a Set Output must be a direct Set instance.";
|
|
7883
|
+
const issue = error.reason.issues[0];
|
|
7884
|
+
switch (issue.kind) {
|
|
7885
|
+
case "ExcessProperty":
|
|
7886
|
+
return `An excess Set property ${safelyStringifyUnknownValue(issue.key)} is not allowed.`;
|
|
7887
|
+
case "Element":
|
|
7888
|
+
return `A Set element at index ${issue.index} is invalid.`;
|
|
7889
|
+
}
|
|
7890
|
+
}) as TypeErrorFormatter<TypeError>,
|
|
6998
7891
|
};
|
|
6999
7892
|
|
|
7000
7893
|
const validateSetItems = (
|
|
@@ -7067,17 +7960,12 @@ const validateSetItems = (
|
|
|
7067
7960
|
* ### Example
|
|
7068
7961
|
*
|
|
7069
7962
|
* ```ts
|
|
7070
|
-
* import {
|
|
7071
|
-
*
|
|
7072
|
-
* const NumberFromString = transform("NumberFromString", String, Number, {
|
|
7073
|
-
* from: (value) => ok(globalThis.Number(value)),
|
|
7074
|
-
* to: globalThis.String,
|
|
7075
|
-
* });
|
|
7963
|
+
* import { Int64FromInt64String, String, tuple } from "@evolu/common";
|
|
7076
7964
|
*
|
|
7077
|
-
* const Entry = tuple(String,
|
|
7965
|
+
* const Entry = tuple(String, Int64FromInt64String);
|
|
7078
7966
|
*
|
|
7079
|
-
* expectOk(Entry.fromUnknown(["count", "1"]), ["count",
|
|
7080
|
-
* expectOk(Entry.from.parent(["count", "1"]), ["count",
|
|
7967
|
+
* expectOk(Entry.fromUnknown(["count", "1"]), ["count", 1n]);
|
|
7968
|
+
* expectOk(Entry.from.parent(["count", "1"]), ["count", 1n]);
|
|
7081
7969
|
* ```
|
|
7082
7970
|
*
|
|
7083
7971
|
* @group Collection
|
|
@@ -7148,14 +8036,11 @@ const createTupleType = (
|
|
|
7148
8036
|
options,
|
|
7149
8037
|
);
|
|
7150
8038
|
const formatError: TypeErrorFormatter<TupleError> = (error) => {
|
|
7151
|
-
if (error.reason.kind === "NotArray")
|
|
8039
|
+
if (error.reason.kind === "NotArray")
|
|
7152
8040
|
return `A value ${safelyStringifyUnknownValue(error.reason.value)} is not a tuple.`;
|
|
7153
|
-
|
|
7154
|
-
if (error.reason.kind === "InvalidLength") {
|
|
8041
|
+
if (error.reason.kind === "InvalidLength")
|
|
7155
8042
|
return `A Tuple must contain exactly ${error.reason.expected} elements, but the value contains ${error.reason.actual}.`;
|
|
7156
|
-
|
|
7157
|
-
const issue = error.reason.issues[0] as TupleStructuralIssue;
|
|
7158
|
-
|
|
8043
|
+
const issue = error.reason.issues[0];
|
|
7159
8044
|
switch (issue.kind) {
|
|
7160
8045
|
case "Hole":
|
|
7161
8046
|
return `A Tuple element at index ${issue.index} is missing.`;
|
|
@@ -7163,6 +8048,8 @@ const createTupleType = (
|
|
|
7163
8048
|
return `A Tuple element at index ${issue.index} must be a data property.`;
|
|
7164
8049
|
case "ExcessProperty":
|
|
7165
8050
|
return "An excess Tuple property is not allowed. Remove it or use a different Type.";
|
|
8051
|
+
case "Element":
|
|
8052
|
+
return `A Tuple element at index ${issue.index} is invalid.`;
|
|
7166
8053
|
}
|
|
7167
8054
|
};
|
|
7168
8055
|
const rootElements = typeElements.map(
|
|
@@ -7538,10 +8425,7 @@ export type Digit1To6 = typeof Digit1To6.Output;
|
|
|
7538
8425
|
export const Digit1To23 = /*#__PURE__*/ union(
|
|
7539
8426
|
Digit1To9,
|
|
7540
8427
|
/*#__PURE__*/ templateLiteral("1", Digit),
|
|
7541
|
-
/*#__PURE__*/ templateLiteral(
|
|
7542
|
-
"2",
|
|
7543
|
-
/*#__PURE__*/ union("0", "1", "2", "3"),
|
|
7544
|
-
),
|
|
8428
|
+
/*#__PURE__*/ templateLiteral("2", /*#__PURE__*/ union("0", "1", "2", "3")),
|
|
7545
8429
|
);
|
|
7546
8430
|
export type Digit1To23 = typeof Digit1To23.Output;
|
|
7547
8431
|
|
|
@@ -7552,10 +8436,7 @@ export type Digit1To23 = typeof Digit1To23.Output;
|
|
|
7552
8436
|
*/
|
|
7553
8437
|
export const Digit1To51 = /*#__PURE__*/ union(
|
|
7554
8438
|
Digit1To9,
|
|
7555
|
-
/*#__PURE__*/ templateLiteral(
|
|
7556
|
-
/*#__PURE__*/ union("1", "2", "3", "4"),
|
|
7557
|
-
Digit,
|
|
7558
|
-
),
|
|
8439
|
+
/*#__PURE__*/ templateLiteral(/*#__PURE__*/ union("1", "2", "3", "4"), Digit),
|
|
7559
8440
|
/*#__PURE__*/ templateLiteral("5", /*#__PURE__*/ union("0", "1")),
|
|
7560
8441
|
);
|
|
7561
8442
|
export type Digit1To51 = typeof Digit1To51.Output;
|
|
@@ -7654,31 +8535,19 @@ const createObjectRuntimeTypeIssues =
|
|
|
7654
8535
|
}) as unknown as NonEmptyReadonlyArray<RuntimeTypeIssue>;
|
|
7655
8536
|
};
|
|
7656
8537
|
|
|
8538
|
+
const formatPlainObjectRootError = (
|
|
8539
|
+
reason:
|
|
8540
|
+
ObjectNotObjectError["reason"] | ObjectUnexpectedPrototypeError["reason"],
|
|
8541
|
+
): string =>
|
|
8542
|
+
reason.kind === "NotObject"
|
|
8543
|
+
? `A value ${safelyStringifyUnknownValue(reason.value)} is not an object.`
|
|
8544
|
+
: "The value is an object, but an Object Output must be a plain object or have a null prototype.";
|
|
8545
|
+
|
|
7657
8546
|
type PlainObjectError = ObjectError<
|
|
7658
8547
|
Readonly<Record<never, never>>,
|
|
7659
8548
|
ObjectPropertyAccessError | ObjectExcessPropertyError
|
|
7660
8549
|
>;
|
|
7661
8550
|
|
|
7662
|
-
const formatPlainObjectError: TypeErrorFormatter<PlainObjectError> = (
|
|
7663
|
-
error,
|
|
7664
|
-
) => {
|
|
7665
|
-
if (error.reason.kind !== "Properties") {
|
|
7666
|
-
return formatPlainObjectRootError(error.reason);
|
|
7667
|
-
}
|
|
7668
|
-
|
|
7669
|
-
const key = Reflect.ownKeys(error.reason.errors).at(0);
|
|
7670
|
-
assertNonNullable(key);
|
|
7671
|
-
const propertyError = error.reason.errors[key];
|
|
7672
|
-
assertNonNullable(propertyError);
|
|
7673
|
-
|
|
7674
|
-
if (propertyError.type === "ObjectPropertyAccess") {
|
|
7675
|
-
return propertyError.reason === "Accessor"
|
|
7676
|
-
? "An Object property must be a data property. Materialize accessor values into plain data before using this Type or use a different Type."
|
|
7677
|
-
: "An Object property must be enumerable. Make it enumerable or use a different Type.";
|
|
7678
|
-
}
|
|
7679
|
-
return "An Object property key must be a string. Remove it or use a different Type.";
|
|
7680
|
-
};
|
|
7681
|
-
|
|
7682
8551
|
/**
|
|
7683
8552
|
* A {@link Type} for readonly plain objects with unknown property values.
|
|
7684
8553
|
*
|
|
@@ -7693,7 +8562,7 @@ const formatPlainObjectError: TypeErrorFormatter<PlainObjectError> = (
|
|
|
7693
8562
|
* values have their own Types, and {@link instanceOf} when an instance belongs
|
|
7694
8563
|
* to the domain.
|
|
7695
8564
|
*
|
|
7696
|
-
* @group Base
|
|
8565
|
+
* @group Base
|
|
7697
8566
|
*/
|
|
7698
8567
|
export const Object: Type<
|
|
7699
8568
|
"Object",
|
|
@@ -7768,10 +8637,52 @@ export const Object: Type<
|
|
|
7768
8637
|
reason: { kind: "Properties", errors },
|
|
7769
8638
|
} as PlainObjectError);
|
|
7770
8639
|
},
|
|
7771
|
-
|
|
7772
|
-
|
|
7773
|
-
|
|
7774
|
-
|
|
8640
|
+
(error: ObjectError) => {
|
|
8641
|
+
if (error.reason.kind !== "Properties")
|
|
8642
|
+
return formatPlainObjectRootError(error.reason);
|
|
8643
|
+
const key = Reflect.ownKeys(error.reason.errors).at(0);
|
|
8644
|
+
assertNonNullable(key);
|
|
8645
|
+
const propertyError = error.reason.errors[key];
|
|
8646
|
+
assertNonNullable(propertyError);
|
|
8647
|
+
if (propertyError.type === "ObjectPropertyAccess") {
|
|
8648
|
+
switch ((propertyError as ObjectPropertyAccessError).reason) {
|
|
8649
|
+
case "Accessor":
|
|
8650
|
+
return "An Object property must be a data property. Materialize accessor values into plain data before using this Type or use a different Type.";
|
|
8651
|
+
case "NonEnumerable":
|
|
8652
|
+
return "An Object property must be enumerable. Make it enumerable or use a different Type.";
|
|
8653
|
+
}
|
|
8654
|
+
}
|
|
8655
|
+
if (propertyError.type === "ObjectMissingProperty")
|
|
8656
|
+
return `The required property ${safelyStringifyUnknownValue(key)} is missing.`;
|
|
8657
|
+
if (typeof key === "symbol")
|
|
8658
|
+
return "An Object property key must be a string. Remove the symbol property or use a different Type.";
|
|
8659
|
+
if (propertyError.type === "ObjectExcessProperty")
|
|
8660
|
+
return `The property ${safelyStringifyUnknownValue(key)} is not allowed. Remove it or use a different Type.`;
|
|
8661
|
+
return `The property ${safelyStringifyUnknownValue(key)} is invalid.`;
|
|
8662
|
+
},
|
|
8663
|
+
/*#__PURE__*/ createObjectRuntimeTypeIssues(((error: ObjectError) => {
|
|
8664
|
+
if (error.reason.kind !== "Properties")
|
|
8665
|
+
return formatPlainObjectRootError(error.reason);
|
|
8666
|
+
const key = Reflect.ownKeys(error.reason.errors).at(0);
|
|
8667
|
+
assertNonNullable(key);
|
|
8668
|
+
const propertyError = error.reason.errors[key];
|
|
8669
|
+
assertNonNullable(propertyError);
|
|
8670
|
+
if (propertyError.type === "ObjectPropertyAccess") {
|
|
8671
|
+
switch ((propertyError as ObjectPropertyAccessError).reason) {
|
|
8672
|
+
case "Accessor":
|
|
8673
|
+
return "An Object property must be a data property. Materialize accessor values into plain data before using this Type or use a different Type.";
|
|
8674
|
+
case "NonEnumerable":
|
|
8675
|
+
return "An Object property must be enumerable. Make it enumerable or use a different Type.";
|
|
8676
|
+
}
|
|
8677
|
+
}
|
|
8678
|
+
if (propertyError.type === "ObjectMissingProperty")
|
|
8679
|
+
return `The required property ${safelyStringifyUnknownValue(key)} is missing.`;
|
|
8680
|
+
if (typeof key === "symbol")
|
|
8681
|
+
return "An Object property key must be a string. Remove the symbol property or use a different Type.";
|
|
8682
|
+
if (propertyError.type === "ObjectExcessProperty")
|
|
8683
|
+
return `The property ${safelyStringifyUnknownValue(key)} is not allowed. Remove it or use a different Type.`;
|
|
8684
|
+
return `The property ${safelyStringifyUnknownValue(key)} is invalid.`;
|
|
8685
|
+
}) as TypeErrorFormatter<TypeError>),
|
|
7775
8686
|
);
|
|
7776
8687
|
|
|
7777
8688
|
const isPlainObject = (value: object): boolean => {
|
|
@@ -7781,14 +8692,6 @@ const isPlainObject = (value: object): boolean => {
|
|
|
7781
8692
|
);
|
|
7782
8693
|
};
|
|
7783
8694
|
|
|
7784
|
-
const formatPlainObjectRootError = (
|
|
7785
|
-
reason:
|
|
7786
|
-
ObjectNotObjectError["reason"] | ObjectUnexpectedPrototypeError["reason"],
|
|
7787
|
-
): string =>
|
|
7788
|
-
reason.kind === "NotObject"
|
|
7789
|
-
? `A value ${safelyStringifyUnknownValue(reason.value)} is not an object.`
|
|
7790
|
-
: "The value is an object, but an Object Output must be a plain object or have a null prototype.";
|
|
7791
|
-
|
|
7792
8695
|
/**
|
|
7793
8696
|
* Record {@link Type}.
|
|
7794
8697
|
*
|
|
@@ -7819,20 +8722,13 @@ const formatPlainObjectRootError = (
|
|
|
7819
8722
|
*
|
|
7820
8723
|
* ```ts
|
|
7821
8724
|
* import {
|
|
7822
|
-
*
|
|
8725
|
+
* Int64FromInt64String,
|
|
7823
8726
|
* String,
|
|
7824
|
-
* ok,
|
|
7825
8727
|
* record,
|
|
7826
|
-
*
|
|
7827
|
-
* type Result,
|
|
8728
|
+
* type Int64,
|
|
7828
8729
|
* } from "@evolu/common";
|
|
7829
8730
|
*
|
|
7830
|
-
* const
|
|
7831
|
-
* from: (value) => ok(globalThis.Number(value)),
|
|
7832
|
-
* to: globalThis.String,
|
|
7833
|
-
* });
|
|
7834
|
-
*
|
|
7835
|
-
* const ScoresByUser = record(String, ScoreFromString);
|
|
8731
|
+
* const ScoresByUser = record(String, Int64FromInt64String);
|
|
7836
8732
|
* type ScoresByUser = typeof ScoresByUser.Output;
|
|
7837
8733
|
*
|
|
7838
8734
|
* // Validate an unknown value.
|
|
@@ -7841,7 +8737,7 @@ const formatPlainObjectRootError = (
|
|
|
7841
8737
|
* grace: "20",
|
|
7842
8738
|
* });
|
|
7843
8739
|
*
|
|
7844
|
-
* expectOk(scoresFromUnknown, { ada:
|
|
8740
|
+
* expectOk(scoresFromUnknown, { ada: 10n, grace: 20n });
|
|
7845
8741
|
*
|
|
7846
8742
|
* // Validate keys and values with their root Types.
|
|
7847
8743
|
* const scoresInput = ScoresByUser.parent.fromUnknown({
|
|
@@ -7853,11 +8749,10 @@ const formatPlainObjectRootError = (
|
|
|
7853
8749
|
* // Run the remaining key and value stages.
|
|
7854
8750
|
* const scoresFromInput = ScoresByUser.from.parent(scoresInput.value);
|
|
7855
8751
|
*
|
|
7856
|
-
*
|
|
7857
|
-
*
|
|
8752
|
+
* expectOk(scoresFromInput, { ada: 10n, grace: 20n });
|
|
8753
|
+
* expectTypeOf(scoresFromInput.value).toEqualTypeOf<
|
|
8754
|
+
* Readonly<Partial<Record<string, Int64>>>
|
|
7858
8755
|
* >();
|
|
7859
|
-
*
|
|
7860
|
-
* expectOk(scoresFromInput, { ada: 10, grace: 20 });
|
|
7861
8756
|
* ```
|
|
7862
8757
|
*
|
|
7863
8758
|
* Note that TypeScript does not model an object's runtime prototype. This can
|
|
@@ -7954,20 +8849,20 @@ export const record = <
|
|
|
7954
8849
|
options,
|
|
7955
8850
|
);
|
|
7956
8851
|
const formatError: TypeErrorFormatter<RecordError> = (error) => {
|
|
7957
|
-
if (error.reason.kind === "NotRecord")
|
|
7958
|
-
return `A value ${safelyStringifyUnknownValue(error.reason.value)} is not a
|
|
7959
|
-
|
|
7960
|
-
if (error.reason.kind === "NotPlainRecord") {
|
|
8852
|
+
if (error.reason.kind === "NotRecord")
|
|
8853
|
+
return `A value ${safelyStringifyUnknownValue(error.reason.value)} is not a Record.`;
|
|
8854
|
+
if (error.reason.kind === "NotPlainRecord")
|
|
7961
8855
|
return "The value is an object, but a Record Output must be a plain object or have a null prototype.";
|
|
7962
|
-
|
|
7963
|
-
|
|
7964
|
-
const issue = error.reason.issues[0] as RecordStructuralIssue;
|
|
7965
|
-
|
|
8856
|
+
const issue = error.reason.issues[0];
|
|
7966
8857
|
switch (issue.kind) {
|
|
8858
|
+
case "Key":
|
|
8859
|
+
return `Property key ${safelyStringifyUnknownValue(issue.key)} is invalid.`;
|
|
8860
|
+
case "Value":
|
|
8861
|
+
return `The value of property ${safelyStringifyUnknownValue(issue.key)} is invalid.`;
|
|
7967
8862
|
case "Accessor":
|
|
7968
|
-
return `A
|
|
8863
|
+
return `A Record property ${safelyStringifyUnknownValue(issue.key)} must be a data property.`;
|
|
7969
8864
|
case "NonEnumerable":
|
|
7970
|
-
return `A
|
|
8865
|
+
return `A Record property ${safelyStringifyUnknownValue(issue.key)} must be enumerable.`;
|
|
7971
8866
|
case "Collision":
|
|
7972
8867
|
return `Record keys ${safelyStringifyUnknownValue(issue.previousKey)} and ${safelyStringifyUnknownValue(issue.key)} decode to the same key ${safelyStringifyUnknownValue(issue.outputKey)}.`;
|
|
7973
8868
|
}
|
|
@@ -8073,7 +8968,11 @@ export const record = <
|
|
|
8073
8968
|
);
|
|
8074
8969
|
};
|
|
8075
8970
|
|
|
8076
|
-
/**
|
|
8971
|
+
/**
|
|
8972
|
+
* The {@link Type} returned by {@link record}.
|
|
8973
|
+
*
|
|
8974
|
+
* @group Objects
|
|
8975
|
+
*/
|
|
8077
8976
|
export interface RecordType<
|
|
8078
8977
|
KeyType extends TypeNode,
|
|
8079
8978
|
ValueType extends TypeNode,
|
|
@@ -8197,7 +9096,11 @@ type RecordKeyStringTypeError = CompileTimeError<
|
|
|
8197
9096
|
"Record key Type Input and Output must extend string."
|
|
8198
9097
|
>;
|
|
8199
9098
|
|
|
8200
|
-
/**
|
|
9099
|
+
/**
|
|
9100
|
+
* Error returned while validating a {@link record} and its entries.
|
|
9101
|
+
*
|
|
9102
|
+
* @group Objects
|
|
9103
|
+
*/
|
|
8201
9104
|
export type RecordError<
|
|
8202
9105
|
KeyError extends TypeError = TypeError,
|
|
8203
9106
|
ValueError extends TypeError = TypeError,
|
|
@@ -8211,7 +9114,11 @@ export type RecordError<
|
|
|
8211
9114
|
Collision | RecordAccessorIssue | RecordNonEnumerableIssue
|
|
8212
9115
|
>;
|
|
8213
9116
|
|
|
8214
|
-
/**
|
|
9117
|
+
/**
|
|
9118
|
+
* Error returned when a {@link record} input is not an object.
|
|
9119
|
+
*
|
|
9120
|
+
* @group Objects
|
|
9121
|
+
*/
|
|
8215
9122
|
export interface RecordNotRecordError extends TypeError<"Record"> {
|
|
8216
9123
|
readonly reason: {
|
|
8217
9124
|
readonly kind: "NotRecord";
|
|
@@ -8219,7 +9126,11 @@ export interface RecordNotRecordError extends TypeError<"Record"> {
|
|
|
8219
9126
|
};
|
|
8220
9127
|
}
|
|
8221
9128
|
|
|
8222
|
-
/**
|
|
9129
|
+
/**
|
|
9130
|
+
* Error returned when a {@link record} input is not a plain object.
|
|
9131
|
+
*
|
|
9132
|
+
* @group Objects
|
|
9133
|
+
*/
|
|
8223
9134
|
export interface RecordNotPlainRecordError extends TypeError<"Record"> {
|
|
8224
9135
|
readonly reason: {
|
|
8225
9136
|
readonly kind: "NotPlainRecord";
|
|
@@ -8244,14 +9155,22 @@ export type RecordEntriesError<
|
|
|
8244
9155
|
? never
|
|
8245
9156
|
: RecordEntriesErrorValue<KeyError, ValueError, StructuralIssue>;
|
|
8246
9157
|
|
|
8247
|
-
/**
|
|
9158
|
+
/**
|
|
9159
|
+
* An invalid key, value, or property structure in a {@link record}.
|
|
9160
|
+
*
|
|
9161
|
+
* @group Objects
|
|
9162
|
+
*/
|
|
8248
9163
|
export type RecordIssue<
|
|
8249
9164
|
KeyError extends TypeError,
|
|
8250
9165
|
ValueError extends TypeError,
|
|
8251
9166
|
StructuralIssue extends RecordStructuralIssue = RecordCollisionIssue,
|
|
8252
9167
|
> = RecordKeyIssue<KeyError> | RecordValueIssue<ValueError> | StructuralIssue;
|
|
8253
9168
|
|
|
8254
|
-
/**
|
|
9169
|
+
/**
|
|
9170
|
+
* A property-structure issue returned by {@link record}.
|
|
9171
|
+
*
|
|
9172
|
+
* @group Objects
|
|
9173
|
+
*/
|
|
8255
9174
|
export type RecordStructuralIssue =
|
|
8256
9175
|
RecordAccessorIssue | RecordCollisionIssue | RecordNonEnumerableIssue;
|
|
8257
9176
|
|
|
@@ -8275,7 +9194,11 @@ export interface RecordNonEnumerableIssue {
|
|
|
8275
9194
|
readonly key: string | symbol;
|
|
8276
9195
|
}
|
|
8277
9196
|
|
|
8278
|
-
/**
|
|
9197
|
+
/**
|
|
9198
|
+
* An invalid key and its source property key in a {@link record}.
|
|
9199
|
+
*
|
|
9200
|
+
* @group Objects
|
|
9201
|
+
*/
|
|
8279
9202
|
export type RecordKeyIssue<Error extends TypeError> = Error extends TypeError
|
|
8280
9203
|
? {
|
|
8281
9204
|
readonly kind: "Key";
|
|
@@ -8284,7 +9207,11 @@ export type RecordKeyIssue<Error extends TypeError> = Error extends TypeError
|
|
|
8284
9207
|
}
|
|
8285
9208
|
: never;
|
|
8286
9209
|
|
|
8287
|
-
/**
|
|
9210
|
+
/**
|
|
9211
|
+
* An invalid value and its property key in a {@link record}.
|
|
9212
|
+
*
|
|
9213
|
+
* @group Objects
|
|
9214
|
+
*/
|
|
8288
9215
|
export type RecordValueIssue<Error extends TypeError> = Error extends TypeError
|
|
8289
9216
|
? {
|
|
8290
9217
|
readonly kind: "Value";
|
|
@@ -8293,7 +9220,11 @@ export type RecordValueIssue<Error extends TypeError> = Error extends TypeError
|
|
|
8293
9220
|
}
|
|
8294
9221
|
: never;
|
|
8295
9222
|
|
|
8296
|
-
/**
|
|
9223
|
+
/**
|
|
9224
|
+
* Two {@link record} keys that decode to the same output key.
|
|
9225
|
+
*
|
|
9226
|
+
* @group Objects
|
|
9227
|
+
*/
|
|
8297
9228
|
export interface RecordCollisionIssue {
|
|
8298
9229
|
readonly kind: "Collision";
|
|
8299
9230
|
readonly key: string | symbol;
|
|
@@ -8520,48 +9451,38 @@ type ObjectProperty = ObjectProps[string];
|
|
|
8520
9451
|
*
|
|
8521
9452
|
* ```ts
|
|
8522
9453
|
* import {
|
|
8523
|
-
*
|
|
9454
|
+
* Int64FromInt64String,
|
|
8524
9455
|
* String,
|
|
8525
9456
|
* object,
|
|
8526
|
-
* ok,
|
|
8527
|
-
* transform,
|
|
8528
9457
|
* type InferType,
|
|
8529
|
-
* type Result,
|
|
8530
9458
|
* } from "@evolu/common";
|
|
8531
9459
|
*
|
|
8532
|
-
* const AgeFromString = transform("AgeFromString", String, Number, {
|
|
8533
|
-
* from: (value) => ok(globalThis.Number(value)),
|
|
8534
|
-
* to: globalThis.String,
|
|
8535
|
-
* });
|
|
8536
|
-
*
|
|
8537
9460
|
* const User = object({
|
|
8538
9461
|
* name: String,
|
|
8539
|
-
*
|
|
9462
|
+
* loginCount: Int64FromInt64String,
|
|
8540
9463
|
* });
|
|
8541
9464
|
* interface User extends InferType<typeof User> {}
|
|
8542
9465
|
*
|
|
8543
9466
|
* // Validate an unknown value.
|
|
8544
|
-
* const userFromUnknown = User.fromUnknown({
|
|
9467
|
+
* const userFromUnknown = User.fromUnknown({
|
|
9468
|
+
* name: "Ada",
|
|
9469
|
+
* loginCount: "42",
|
|
9470
|
+
* });
|
|
8545
9471
|
*
|
|
8546
|
-
* expectOk(userFromUnknown, { name: "Ada",
|
|
9472
|
+
* expectOk(userFromUnknown, { name: "Ada", loginCount: 42n });
|
|
8547
9473
|
*
|
|
8548
9474
|
* // Validate the object and root property Types.
|
|
8549
9475
|
* const userInput = User.parent.fromUnknown({
|
|
8550
9476
|
* name: "Ada",
|
|
8551
|
-
*
|
|
9477
|
+
* loginCount: "42",
|
|
8552
9478
|
* });
|
|
8553
|
-
* expectOk(userInput, { name: "Ada",
|
|
9479
|
+
* expectOk(userInput, { name: "Ada", loginCount: "42" });
|
|
8554
9480
|
*
|
|
8555
9481
|
* // Run the remaining property stages.
|
|
8556
|
-
* const userFromInput
|
|
8557
|
-
* { readonly name: string; readonly age: number },
|
|
8558
|
-
* never
|
|
8559
|
-
* > = User.from.parent(userInput.value);
|
|
9482
|
+
* const userFromInput = User.from.parent(userInput.value);
|
|
8560
9483
|
*
|
|
8561
|
-
*
|
|
8562
|
-
*
|
|
8563
|
-
* >();
|
|
8564
|
-
* expectOk(userFromInput, { name: "Ada", age: 42 });
|
|
9484
|
+
* expectOk(userFromInput, { name: "Ada", loginCount: 42n });
|
|
9485
|
+
* expectTypeOf(userFromInput.value).toExtend<User>();
|
|
8565
9486
|
* ```
|
|
8566
9487
|
*
|
|
8567
9488
|
* Note that TypeScript does not model an object's runtime prototype. This can
|
|
@@ -8614,6 +9535,8 @@ export function object<const Props extends ObjectProps>(
|
|
|
8614
9535
|
? []
|
|
8615
9536
|
: [ValidationFailure<ObjectValidationError<Props>>]
|
|
8616
9537
|
): StrictObjectType<Props>;
|
|
9538
|
+
|
|
9539
|
+
/** Creates an Object Type with additional record properties. */
|
|
8617
9540
|
export function object<
|
|
8618
9541
|
const Props extends ObjectProps,
|
|
8619
9542
|
const Rest extends RecordTypeNode & ConcreteTypeNode,
|
|
@@ -8818,31 +9741,27 @@ const createObjectType = (
|
|
|
8818
9741
|
options: ValidationOptions = firstValidationOptions,
|
|
8819
9742
|
) => validate(value, options, true);
|
|
8820
9743
|
const formatError: TypeErrorFormatter<ObjectError> = (error) => {
|
|
8821
|
-
if (error.reason.kind !== "Properties")
|
|
9744
|
+
if (error.reason.kind !== "Properties")
|
|
8822
9745
|
return formatPlainObjectRootError(error.reason);
|
|
8823
|
-
}
|
|
8824
|
-
|
|
8825
9746
|
const key = Reflect.ownKeys(error.reason.errors).at(0);
|
|
8826
9747
|
assertNonNullable(key);
|
|
8827
|
-
const propertyError =
|
|
8828
|
-
|
|
8829
|
-
)[key]!;
|
|
8830
|
-
|
|
9748
|
+
const propertyError = error.reason.errors[key];
|
|
9749
|
+
assertNonNullable(propertyError);
|
|
8831
9750
|
if (propertyError.type === "ObjectPropertyAccess") {
|
|
8832
9751
|
switch ((propertyError as ObjectPropertyAccessError).reason) {
|
|
8833
9752
|
case "Accessor":
|
|
8834
9753
|
return "An Object property must be a data property. Materialize accessor values into plain data before using this Type or use a different Type.";
|
|
8835
9754
|
case "NonEnumerable":
|
|
8836
|
-
return "An Object property must be enumerable.";
|
|
9755
|
+
return "An Object property must be enumerable. Make it enumerable or use a different Type.";
|
|
8837
9756
|
}
|
|
8838
9757
|
}
|
|
8839
|
-
if (
|
|
8840
|
-
|
|
8841
|
-
|
|
8842
|
-
|
|
8843
|
-
|
|
8844
|
-
|
|
8845
|
-
return
|
|
9758
|
+
if (propertyError.type === "ObjectMissingProperty")
|
|
9759
|
+
return `The required property ${safelyStringifyUnknownValue(key)} is missing.`;
|
|
9760
|
+
if (typeof key === "symbol")
|
|
9761
|
+
return "An Object property key must be a string. Remove the symbol property or use a different Type.";
|
|
9762
|
+
if (propertyError.type === "ObjectExcessProperty")
|
|
9763
|
+
return `The property ${safelyStringifyUnknownValue(key)} is not allowed. Remove it or use a different Type.`;
|
|
9764
|
+
return `The property ${safelyStringifyUnknownValue(key)} is invalid.`;
|
|
8846
9765
|
};
|
|
8847
9766
|
const rootProps = createMutableRecord<string, RuntimeObjectProperty>();
|
|
8848
9767
|
let hasNonRootType = false;
|
|
@@ -9214,7 +10133,11 @@ type ObjectRecordCanonicalInputTypeError = CompileTimeError<
|
|
|
9214
10133
|
"Every declared property Type CanonicalInput must extend the Object Record value Type CanonicalInput."
|
|
9215
10134
|
>;
|
|
9216
10135
|
|
|
9217
|
-
/**
|
|
10136
|
+
/**
|
|
10137
|
+
* The {@link Type} returned by {@link object}.
|
|
10138
|
+
*
|
|
10139
|
+
* @group Objects
|
|
10140
|
+
*/
|
|
9218
10141
|
export type ObjectType<
|
|
9219
10142
|
Props extends ObjectProps,
|
|
9220
10143
|
Rest extends ObjectRecordTypeNode | undefined = undefined,
|
|
@@ -9454,10 +10377,10 @@ export interface ObjectNotObjectError extends TypeError<"Object"> {
|
|
|
9454
10377
|
*
|
|
9455
10378
|
* Object Types accept a `null` prototype or a prototype whose own prototype is
|
|
9456
10379
|
* `null`. This includes ordinary and cross-realm plain objects as well as
|
|
9457
|
-
* objects created from an immediate root prototype. Arrays, built-in
|
|
9458
|
-
* class instances, and objects with deeper custom prototype chains
|
|
9459
|
-
* error instead of having their prototype or inherited state
|
|
9460
|
-
* `reason.value` is the rejected object.
|
|
10380
|
+
* objects created from an immediate root prototype. Arrays, JavaScript built-in
|
|
10381
|
+
* objects, class instances, and objects with deeper custom prototype chains
|
|
10382
|
+
* return this error instead of having their prototype or inherited state
|
|
10383
|
+
* discarded. `reason.value` is the rejected object.
|
|
9461
10384
|
*
|
|
9462
10385
|
* @group Objects
|
|
9463
10386
|
*/
|
|
@@ -9649,7 +10572,11 @@ export const partial = <const Props extends ObjectProps>(
|
|
|
9649
10572
|
>;
|
|
9650
10573
|
};
|
|
9651
10574
|
|
|
9652
|
-
/**
|
|
10575
|
+
/**
|
|
10576
|
+
* Maps every required object property Type to an optional property.
|
|
10577
|
+
*
|
|
10578
|
+
* @group Objects
|
|
10579
|
+
*/
|
|
9653
10580
|
export type PartialObjectProps<Props extends ObjectProps> = {
|
|
9654
10581
|
readonly [Key in keyof Props]: Props[Key] extends OptionalProperty<TypeNode>
|
|
9655
10582
|
? Props[Key]
|
|
@@ -9719,7 +10646,12 @@ export const nullableToOptional = <const Props extends ObjectProps>(
|
|
|
9719
10646
|
>;
|
|
9720
10647
|
};
|
|
9721
10648
|
|
|
9722
|
-
/**
|
|
10649
|
+
/**
|
|
10650
|
+
* Maps object properties whose Union Type includes {@link Null} to optional
|
|
10651
|
+
* properties.
|
|
10652
|
+
*
|
|
10653
|
+
* @group Objects
|
|
10654
|
+
*/
|
|
9723
10655
|
export type NullableToOptionalProps<Props extends ObjectProps> = {
|
|
9724
10656
|
readonly [Key in keyof Props]: Props[Key] extends OptionalProperty<TypeNode>
|
|
9725
10657
|
? Props[Key]
|
|
@@ -9733,6 +10665,17 @@ export type NullableToOptionalProps<Props extends ObjectProps> = {
|
|
|
9733
10665
|
/**
|
|
9734
10666
|
* Object {@link Type} without the selected declared properties.
|
|
9735
10667
|
*
|
|
10668
|
+
* ### Example
|
|
10669
|
+
*
|
|
10670
|
+
* ```ts
|
|
10671
|
+
* import { String, object, omit } from "@evolu/common";
|
|
10672
|
+
*
|
|
10673
|
+
* const User = object({ name: String, password: String });
|
|
10674
|
+
* const PublicUser = omit(User, "password");
|
|
10675
|
+
*
|
|
10676
|
+
* expectOk(PublicUser.fromUnknown({ name: "Ada" }), { name: "Ada" });
|
|
10677
|
+
* ```
|
|
10678
|
+
*
|
|
9736
10679
|
* @group Objects
|
|
9737
10680
|
*/
|
|
9738
10681
|
export const omit = <
|
|
@@ -9785,10 +10728,10 @@ type OmitKeyConcreteTypeError = CompileTimeError<
|
|
|
9785
10728
|
>;
|
|
9786
10729
|
|
|
9787
10730
|
/**
|
|
9788
|
-
*
|
|
10731
|
+
* Creates a {@link Type} for {@link Result} values.
|
|
9789
10732
|
*
|
|
9790
10733
|
* Use this to validate Results crossing a storage, worker, API, or other
|
|
9791
|
-
* serialization boundary.
|
|
10734
|
+
* serialization boundary. `fromUnknown` returns an outer validation Result. Its
|
|
9792
10735
|
* successful value is the inner domain Result described by `okType` and
|
|
9793
10736
|
* `errorType`.
|
|
9794
10737
|
*
|
|
@@ -9805,34 +10748,16 @@ type OmitKeyConcreteTypeError = CompileTimeError<
|
|
|
9805
10748
|
*
|
|
9806
10749
|
* const SyncResponse = result(
|
|
9807
10750
|
* object({ timestamp: NonNegativeInt }),
|
|
9808
|
-
* typed("
|
|
10751
|
+
* typed("SyncFailed", { message: String }),
|
|
9809
10752
|
* );
|
|
9810
10753
|
*
|
|
9811
|
-
* const
|
|
9812
|
-
*
|
|
9813
|
-
*
|
|
9814
|
-
*
|
|
9815
|
-
* const response = validated.value;
|
|
9816
|
-
* return response.ok
|
|
9817
|
-
* ? `Synced at ${response.value.timestamp}`
|
|
9818
|
-
* : response.error.message;
|
|
9819
|
-
* };
|
|
10754
|
+
* const validated = SyncResponse.fromUnknown({
|
|
10755
|
+
* ok: true,
|
|
10756
|
+
* value: { timestamp: 42 },
|
|
10757
|
+
* });
|
|
9820
10758
|
*
|
|
9821
|
-
*
|
|
9822
|
-
*
|
|
9823
|
-
* ok: true,
|
|
9824
|
-
* value: { timestamp: 42 },
|
|
9825
|
-
* }),
|
|
9826
|
-
* ).toBe("Synced at 42");
|
|
9827
|
-
* expect(
|
|
9828
|
-
* describeResponse({
|
|
9829
|
-
* ok: false,
|
|
9830
|
-
* error: { type: "SyncError", message: "Offline" },
|
|
9831
|
-
* }),
|
|
9832
|
-
* ).toBe("Offline");
|
|
9833
|
-
* expect(describeResponse({ ok: true, value: { timestamp: -1 } })).toBe(
|
|
9834
|
-
* "Invalid response",
|
|
9835
|
-
* );
|
|
10759
|
+
* expectOk(validated, { ok: true, value: { timestamp: 42 } });
|
|
10760
|
+
* expectOk(validated.value, { timestamp: 42 });
|
|
9836
10761
|
* ```
|
|
9837
10762
|
*
|
|
9838
10763
|
* @group Results
|
|
@@ -9887,7 +10812,7 @@ export const UnknownResult = /*#__PURE__*/ result(Unknown, Unknown);
|
|
|
9887
10812
|
export type UnknownResult = typeof UnknownResult.Output;
|
|
9888
10813
|
|
|
9889
10814
|
/**
|
|
9890
|
-
*
|
|
10815
|
+
* Creates an {@link ObjectType} with a literal `type` property.
|
|
9891
10816
|
*
|
|
9892
10817
|
* The discriminator belongs to `typed`, so additional properties cannot declare
|
|
9893
10818
|
* `type`. The discriminator Input is `string`, inherited from {@link String},
|
|
@@ -9899,22 +10824,16 @@ export type UnknownResult = typeof UnknownResult.Output;
|
|
|
9899
10824
|
* ### Example
|
|
9900
10825
|
*
|
|
9901
10826
|
* ```ts
|
|
9902
|
-
* import { String,
|
|
10827
|
+
* import { String, typed } from "@evolu/common";
|
|
9903
10828
|
*
|
|
9904
10829
|
* const Pending = typed("Pending", {
|
|
9905
10830
|
* label: String,
|
|
9906
10831
|
* });
|
|
9907
10832
|
*
|
|
9908
|
-
*
|
|
9909
|
-
* const Status = discriminatedUnion(Pending, Completed);
|
|
9910
|
-
*
|
|
9911
|
-
* expectOk(Status.fromUnknown({ type: "Pending", label: "Waiting" }), {
|
|
10833
|
+
* expectOk(Pending.fromUnknown({ type: "Pending", label: "Waiting" }), {
|
|
9912
10834
|
* type: "Pending",
|
|
9913
10835
|
* label: "Waiting",
|
|
9914
10836
|
* });
|
|
9915
|
-
* expectOk(Status.fromUnknown({ type: "Completed" }), {
|
|
9916
|
-
* type: "Completed",
|
|
9917
|
-
* });
|
|
9918
10837
|
* ```
|
|
9919
10838
|
*
|
|
9920
10839
|
* @group Discriminated unions
|
|
@@ -9922,6 +10841,8 @@ export type UnknownResult = typeof UnknownResult.Output;
|
|
|
9922
10841
|
export function typed<const Tag extends TypeName>(
|
|
9923
10842
|
tag: ValidateTypedTag<Tag>,
|
|
9924
10843
|
): TypedType<Tag>;
|
|
10844
|
+
|
|
10845
|
+
/** Creates a Tagged Object Type with declared properties. */
|
|
9925
10846
|
export function typed<
|
|
9926
10847
|
const Tag extends TypeName,
|
|
9927
10848
|
const Props extends ObjectProps,
|
|
@@ -9932,6 +10853,8 @@ export function typed<
|
|
|
9932
10853
|
? []
|
|
9933
10854
|
: [ValidationFailure<TypedValidationError<Props>>]
|
|
9934
10855
|
): TypedType<Tag, Props>;
|
|
10856
|
+
|
|
10857
|
+
/** Creates a Tagged Object Type with additional record properties. */
|
|
9935
10858
|
export function typed<
|
|
9936
10859
|
const Tag extends TypeName,
|
|
9937
10860
|
const Props extends ObjectProps,
|
|
@@ -9971,7 +10894,14 @@ export function typed(
|
|
|
9971
10894
|
}
|
|
9972
10895
|
|
|
9973
10896
|
/**
|
|
9974
|
-
* A
|
|
10897
|
+
* A TypeScript interface with a literal `type` property.
|
|
10898
|
+
*
|
|
10899
|
+
* Use `Typed` for both domain objects in discriminated unions and plain domain
|
|
10900
|
+
* errors returned by {@link Result}. Name a domain error interface `XError`.
|
|
10901
|
+
* When `X` already describes a failure, use `X` for its `type` discriminant
|
|
10902
|
+
* because `Error` describes the interface's role rather than the runtime error
|
|
10903
|
+
* kind. Keep `Error` when it is needed to make the discriminant unambiguous,
|
|
10904
|
+
* such as `TimeoutError`.
|
|
9975
10905
|
*
|
|
9976
10906
|
* Typed unions model mutually exclusive states as separate variants instead of
|
|
9977
10907
|
* combinations of flags and optional properties. TypeScript narrows a union by
|
|
@@ -9982,20 +10912,23 @@ export function typed(
|
|
|
9982
10912
|
* ### Example
|
|
9983
10913
|
*
|
|
9984
10914
|
* ```ts
|
|
9985
|
-
* import { type Typed } from "@evolu/common";
|
|
10915
|
+
* import { err, ok, type Result, type Typed } from "@evolu/common";
|
|
9986
10916
|
*
|
|
9987
|
-
*
|
|
10917
|
+
* interface User extends Typed<"User"> {
|
|
10918
|
+
* readonly id: string;
|
|
10919
|
+
* }
|
|
9988
10920
|
*
|
|
9989
|
-
* const
|
|
9990
|
-
*
|
|
9991
|
-
*
|
|
9992
|
-
*
|
|
9993
|
-
* case "Completed":
|
|
9994
|
-
* return "Done";
|
|
9995
|
-
* }
|
|
9996
|
-
* };
|
|
10921
|
+
* const getUser = (id: string): Result<User, UserNotFoundError> =>
|
|
10922
|
+
* id === "user-1"
|
|
10923
|
+
* ? ok({ type: "User", id })
|
|
10924
|
+
* : err({ type: "UserNotFound", id });
|
|
9997
10925
|
*
|
|
9998
|
-
*
|
|
10926
|
+
* interface UserNotFoundError extends Typed<"UserNotFound"> {
|
|
10927
|
+
* readonly id: string;
|
|
10928
|
+
* }
|
|
10929
|
+
*
|
|
10930
|
+
* expectOk(getUser("user-1"), { type: "User", id: "user-1" });
|
|
10931
|
+
* expectErr(getUser("missing"), { type: "UserNotFound", id: "missing" });
|
|
9999
10932
|
* ```
|
|
10000
10933
|
*
|
|
10001
10934
|
* @group Discriminated unions
|
|
@@ -10007,7 +10940,7 @@ export interface Typed<Tag extends TypeName> {
|
|
|
10007
10940
|
/**
|
|
10008
10941
|
* Extracts members of a {@link Typed} Output union by their `type` literal.
|
|
10009
10942
|
*
|
|
10010
|
-
* The requested
|
|
10943
|
+
* The requested tag is constrained to the union's actual discriminator values,
|
|
10011
10944
|
* so a misspelling is a TypeScript error instead of silently producing
|
|
10012
10945
|
* `never`.
|
|
10013
10946
|
*
|
|
@@ -10079,7 +11012,8 @@ type TypedTypePropertyError = CompileTimeError<
|
|
|
10079
11012
|
>;
|
|
10080
11013
|
|
|
10081
11014
|
/**
|
|
10082
|
-
*
|
|
11015
|
+
* Creates a {@link Type} for producer Results with value, error, or done
|
|
11016
|
+
* outcomes.
|
|
10083
11017
|
*
|
|
10084
11018
|
* The three outcomes are `Ok<Value>`, `Err<Error>`, and `Err<Typed<"Done"> & {
|
|
10085
11019
|
* done: Done }>`. This keeps normal completion distinct from failure while
|
|
@@ -10092,7 +11026,7 @@ type TypedTypePropertyError = CompileTimeError<
|
|
|
10092
11026
|
*
|
|
10093
11027
|
* const StringNextResult = nextResult(
|
|
10094
11028
|
* String,
|
|
10095
|
-
* typed("
|
|
11029
|
+
* typed("ReadFailed", { message: String }),
|
|
10096
11030
|
* String,
|
|
10097
11031
|
* );
|
|
10098
11032
|
*
|
|
@@ -10116,7 +11050,7 @@ type TypedTypePropertyError = CompileTimeError<
|
|
|
10116
11050
|
* expect(
|
|
10117
11051
|
* describeNext({
|
|
10118
11052
|
* ok: false,
|
|
10119
|
-
* error: { type: "
|
|
11053
|
+
* error: { type: "ReadFailed", message: "Offline" },
|
|
10120
11054
|
* }),
|
|
10121
11055
|
* ).toBe("Error: Offline");
|
|
10122
11056
|
* ```
|
|
@@ -10178,7 +11112,7 @@ export const UnknownNextResult = /*#__PURE__*/ nextResult(
|
|
|
10178
11112
|
export type UnknownNextResult = typeof UnknownNextResult.Output;
|
|
10179
11113
|
|
|
10180
11114
|
/**
|
|
10181
|
-
* Discriminated
|
|
11115
|
+
* Discriminated union {@link Type}.
|
|
10182
11116
|
*
|
|
10183
11117
|
* With no explicit key, the conventional `type` property created by
|
|
10184
11118
|
* {@link typed} is used. Pass a key first to discriminate
|
|
@@ -10213,6 +11147,35 @@ export function discriminatedUnion<
|
|
|
10213
11147
|
>(
|
|
10214
11148
|
...members: Members & DiscriminatedUnionValidation<"type", Members>
|
|
10215
11149
|
): DiscriminatedUnionType<"type", Members>;
|
|
11150
|
+
|
|
11151
|
+
/**
|
|
11152
|
+
* Creates a Discriminated Union Type with an explicit discriminator key.
|
|
11153
|
+
*
|
|
11154
|
+
* ### Example
|
|
11155
|
+
*
|
|
11156
|
+
* ```ts
|
|
11157
|
+
* import {
|
|
11158
|
+
* Number,
|
|
11159
|
+
* String,
|
|
11160
|
+
* discriminatedUnion,
|
|
11161
|
+
* literal,
|
|
11162
|
+
* object,
|
|
11163
|
+
* } from "@evolu/common";
|
|
11164
|
+
*
|
|
11165
|
+
* const Added = object({ kind: literal("added"), value: String });
|
|
11166
|
+
* const Removed = object({ kind: literal("removed"), id: Number });
|
|
11167
|
+
* const Event = discriminatedUnion("kind", Added, Removed);
|
|
11168
|
+
*
|
|
11169
|
+
* expectOk(Event.fromUnknown({ kind: "added", value: "Evolu" }), {
|
|
11170
|
+
* kind: "added",
|
|
11171
|
+
* value: "Evolu",
|
|
11172
|
+
* });
|
|
11173
|
+
* expectOk(Event.fromUnknown({ kind: "removed", id: 1 }), {
|
|
11174
|
+
* kind: "removed",
|
|
11175
|
+
* id: 1,
|
|
11176
|
+
* });
|
|
11177
|
+
* ```
|
|
11178
|
+
*/
|
|
10216
11179
|
export function discriminatedUnion<
|
|
10217
11180
|
const Key extends string,
|
|
10218
11181
|
const Members extends DiscriminatedUnionMembers,
|
|
@@ -10354,26 +11317,21 @@ export function discriminatedUnion(
|
|
|
10354
11317
|
Reflect.get(value as Readonly<Record<string, unknown>>, key),
|
|
10355
11318
|
)!;
|
|
10356
11319
|
const formatError: TypeErrorFormatter<DiscriminatedUnionError> = (error) => {
|
|
10357
|
-
|
|
10358
|
-
DiscriminatedUnionError["reason"],
|
|
10359
|
-
DiscriminatedUnionMemberIssue
|
|
10360
|
-
>;
|
|
10361
|
-
|
|
10362
|
-
switch (reason.kind) {
|
|
11320
|
+
switch (error.reason.kind) {
|
|
10363
11321
|
case "Object":
|
|
10364
|
-
return formatPlainObjectRootError(reason.error.reason);
|
|
11322
|
+
return formatPlainObjectRootError(error.reason.error.reason);
|
|
10365
11323
|
case "PropertyAccess": {
|
|
10366
|
-
const property = `The discriminator property ${safelyStringifyUnknownValue(reason.key)}`;
|
|
10367
|
-
if (reason.reason === "Accessor")
|
|
11324
|
+
const property = `The discriminator property ${safelyStringifyUnknownValue(error.reason.key)}`;
|
|
11325
|
+
if (error.reason.reason === "Accessor")
|
|
10368
11326
|
return `${property} must be a data property.`;
|
|
10369
|
-
|
|
10370
|
-
if (reason.reason === "Inherited") {
|
|
11327
|
+
if (error.reason.reason === "Inherited")
|
|
10371
11328
|
return `${property} must be an own property.`;
|
|
10372
|
-
}
|
|
10373
11329
|
return `${property} must be enumerable.`;
|
|
10374
11330
|
}
|
|
10375
11331
|
case "Discriminator":
|
|
10376
|
-
return `The discriminator property ${safelyStringifyUnknownValue(reason.key)} has an unexpected value ${safelyStringifyUnknownValue(reason.value)}.`;
|
|
11332
|
+
return `The discriminator property ${safelyStringifyUnknownValue(error.reason.key)} has an unexpected value ${safelyStringifyUnknownValue(error.reason.value)}.`;
|
|
11333
|
+
case "Member":
|
|
11334
|
+
return `The selected variant ${safelyStringifyUnknownValue(error.reason.discriminator)} is invalid.`;
|
|
10377
11335
|
}
|
|
10378
11336
|
};
|
|
10379
11337
|
const defaultFormatter = formatError as TypeErrorFormatter<TypeError>;
|
|
@@ -10799,10 +11757,10 @@ type RuntimeDiscriminatedUnionMember = RuntimeObjectTypeNode & {
|
|
|
10799
11757
|
};
|
|
10800
11758
|
|
|
10801
11759
|
/**
|
|
10802
|
-
*
|
|
11760
|
+
* Creates a lazy {@link Type} for recursive definitions.
|
|
10803
11761
|
*
|
|
10804
|
-
*
|
|
10805
|
-
* trees and mutually recursive models.
|
|
11762
|
+
* The definition is evaluated on first use and then cached, allowing recursive
|
|
11763
|
+
* data such as trees and mutually recursive models.
|
|
10806
11764
|
*
|
|
10807
11765
|
* A recursive declaration refers to its own variable while that variable is
|
|
10808
11766
|
* being initialized, so TypeScript cannot infer it reliably. Getter-based
|
|
@@ -10822,12 +11780,10 @@ type RuntimeDiscriminatedUnionMember = RuntimeObjectTypeNode & {
|
|
|
10822
11780
|
*
|
|
10823
11781
|
* ```ts
|
|
10824
11782
|
* import {
|
|
10825
|
-
* Number,
|
|
10826
11783
|
* String,
|
|
10827
11784
|
* array,
|
|
10828
11785
|
* lazy,
|
|
10829
11786
|
* object,
|
|
10830
|
-
* optional,
|
|
10831
11787
|
* type ArrayError,
|
|
10832
11788
|
* type LazyType,
|
|
10833
11789
|
* type ObjectError,
|
|
@@ -10848,35 +11804,19 @@ type RuntimeDiscriminatedUnionMember = RuntimeObjectTypeNode & {
|
|
|
10848
11804
|
* () => object({ value: String, children: array(Tree) }),
|
|
10849
11805
|
* );
|
|
10850
11806
|
*
|
|
10851
|
-
*
|
|
10852
|
-
*
|
|
10853
|
-
*
|
|
10854
|
-
* }
|
|
10855
|
-
*
|
|
10856
|
-
* interface Right {
|
|
10857
|
-
* readonly count: number;
|
|
10858
|
-
* readonly left?: Left;
|
|
10859
|
-
* }
|
|
10860
|
-
*
|
|
10861
|
-
* interface LeftError extends ObjectError<{
|
|
10862
|
-
* readonly label: TypeOfError<"String">;
|
|
10863
|
-
* readonly right?: RightError;
|
|
10864
|
-
* }> {}
|
|
10865
|
-
*
|
|
10866
|
-
* interface RightError extends ObjectError<{
|
|
10867
|
-
* readonly count: TypeOfError<"Number">;
|
|
10868
|
-
* readonly left?: LeftError;
|
|
10869
|
-
* }> {}
|
|
10870
|
-
*
|
|
10871
|
-
* const Left: LazyType<Left, Left, never, LeftError, LeftError> = lazy(
|
|
10872
|
-
* () => object({ label: String, right: optional(Right) }),
|
|
10873
|
-
* );
|
|
11807
|
+
* const result = Tree.fromUnknown({
|
|
11808
|
+
* value: "root",
|
|
11809
|
+
* children: [{ value: "leaf", children: [] }],
|
|
11810
|
+
* });
|
|
10874
11811
|
*
|
|
10875
|
-
*
|
|
10876
|
-
*
|
|
11812
|
+
* expectOk(result, {
|
|
11813
|
+
* value: "root",
|
|
11814
|
+
* children: [{ value: "leaf", children: [] }],
|
|
11815
|
+
* });
|
|
11816
|
+
* expectTypeOf(result.value).toEqualTypeOf<Tree>();
|
|
10877
11817
|
* ```
|
|
10878
11818
|
*
|
|
10879
|
-
* @group Recursive
|
|
11819
|
+
* @group Recursive
|
|
10880
11820
|
*/
|
|
10881
11821
|
export function lazy<Target extends ConcreteTypeNode>(
|
|
10882
11822
|
getType: Thunk<ValidateLazyTarget<Target>>,
|
|
@@ -10995,7 +11935,7 @@ export function lazy(getType: Thunk<TypeNode>): TypeNode {
|
|
|
10995
11935
|
* recursive declaration finite for TypeScript while preserving structured
|
|
10996
11936
|
* errors at every boundary.
|
|
10997
11937
|
*
|
|
10998
|
-
* @group Recursive
|
|
11938
|
+
* @group Recursive
|
|
10999
11939
|
*/
|
|
11000
11940
|
export interface LazyType<
|
|
11001
11941
|
// Explicit invariance prevents recursive comparisons from repeatedly
|
|
@@ -11123,7 +12063,7 @@ export interface JsonObject {
|
|
|
11123
12063
|
}
|
|
11124
12064
|
|
|
11125
12065
|
/**
|
|
11126
|
-
* One issue found while validating an exact {@link JsonValue}.
|
|
12066
|
+
* One issue found while validating a candidate as an exact {@link JsonValue}.
|
|
11127
12067
|
*
|
|
11128
12068
|
* @group JSON
|
|
11129
12069
|
*/
|
|
@@ -11171,7 +12111,8 @@ export type JsonValueIssue =
|
|
|
11171
12111
|
};
|
|
11172
12112
|
|
|
11173
12113
|
/**
|
|
11174
|
-
* An error containing one or more issues found
|
|
12114
|
+
* An error containing one or more issues found while validating a candidate as
|
|
12115
|
+
* an exact {@link JsonValue}.
|
|
11175
12116
|
*
|
|
11176
12117
|
* @group JSON
|
|
11177
12118
|
*/
|
|
@@ -11500,31 +12441,6 @@ const validateJsonValue = (
|
|
|
11500
12441
|
});
|
|
11501
12442
|
};
|
|
11502
12443
|
|
|
11503
|
-
const formatJsonValueError: TypeErrorFormatter<JsonValueError> = (error) => {
|
|
11504
|
-
const issue = error.reason.issues[0];
|
|
11505
|
-
|
|
11506
|
-
switch (issue.kind) {
|
|
11507
|
-
case "InvalidType":
|
|
11508
|
-
return `A value ${safelyStringifyUnknownValue(issue.value)} is not a JSON value.`;
|
|
11509
|
-
case "NonFiniteNumber":
|
|
11510
|
-
return "A JSON number must be finite.";
|
|
11511
|
-
case "UnexpectedPrototype":
|
|
11512
|
-
return "The value is an object, but a JsonValue Object must be a plain object or have a null prototype.";
|
|
11513
|
-
case "Accessor":
|
|
11514
|
-
return "A JSON property must be a data property. Materialize accessor values into plain data before using this Type or use a different Type.";
|
|
11515
|
-
case "NonEnumerable":
|
|
11516
|
-
return "A JSON Object property must be enumerable. Remove it or use a different Type.";
|
|
11517
|
-
case "SymbolProperty":
|
|
11518
|
-
return "A JSON Object property key must be a string. Remove the symbol property or use a different Type.";
|
|
11519
|
-
case "Hole":
|
|
11520
|
-
return "A JSON Array element is missing.";
|
|
11521
|
-
case "ExcessProperty":
|
|
11522
|
-
return "An excess JSON Array property is not allowed. Remove it or use a different Type.";
|
|
11523
|
-
case "CircularReference":
|
|
11524
|
-
return "A JsonValue must not contain circular references.";
|
|
11525
|
-
}
|
|
11526
|
-
};
|
|
11527
|
-
|
|
11528
12444
|
const getJsonValueRuntimeTypeIssues: RuntimeGetTypeIssues = (error, mode) => {
|
|
11529
12445
|
const jsonValueError = error as JsonValueError;
|
|
11530
12446
|
const issues =
|
|
@@ -11542,7 +12458,29 @@ const getJsonValueRuntimeTypeIssues: RuntimeGetTypeIssues = (error, mode) => {
|
|
|
11542
12458
|
reason: { kind: "Issues", issues: [issue] },
|
|
11543
12459
|
},
|
|
11544
12460
|
path: issue.path,
|
|
11545
|
-
formatError:
|
|
12461
|
+
formatError: ((error: JsonValueError) => {
|
|
12462
|
+
const issue = error.reason.issues[0];
|
|
12463
|
+
switch (issue.kind) {
|
|
12464
|
+
case "InvalidType":
|
|
12465
|
+
return `A value ${safelyStringifyUnknownValue(issue.value)} is not a JSON value.`;
|
|
12466
|
+
case "NonFiniteNumber":
|
|
12467
|
+
return "A JSON number must be finite.";
|
|
12468
|
+
case "UnexpectedPrototype":
|
|
12469
|
+
return "The value is an object, but a JsonValue object must be a plain object or have a null prototype.";
|
|
12470
|
+
case "Accessor":
|
|
12471
|
+
return "A JSON property must be a data property. Materialize accessor values into plain data before using this Type or use a different Type.";
|
|
12472
|
+
case "NonEnumerable":
|
|
12473
|
+
return "A JSON object property must be enumerable. Remove it or use a different Type.";
|
|
12474
|
+
case "SymbolProperty":
|
|
12475
|
+
return "A JSON object property key must be a string. Remove the symbol property or use a different Type.";
|
|
12476
|
+
case "Hole":
|
|
12477
|
+
return "A JSON array element is missing.";
|
|
12478
|
+
case "ExcessProperty":
|
|
12479
|
+
return "An excess JSON array property is not allowed. Remove it or use a different Type.";
|
|
12480
|
+
case "CircularReference":
|
|
12481
|
+
return "A JsonValue must not contain circular references.";
|
|
12482
|
+
}
|
|
12483
|
+
}) as TypeErrorFormatter<TypeError>,
|
|
11546
12484
|
})) as unknown as NonEmptyReadonlyArray<RuntimeTypeIssue>;
|
|
11547
12485
|
};
|
|
11548
12486
|
|
|
@@ -11627,6 +12565,21 @@ const stringifyJsonValue = (value: JsonValue): Json => {
|
|
|
11627
12565
|
/**
|
|
11628
12566
|
* Exact root Type for {@link JsonValue} data trees.
|
|
11629
12567
|
*
|
|
12568
|
+
* ### Example
|
|
12569
|
+
*
|
|
12570
|
+
* ```ts
|
|
12571
|
+
* import { JsonValue, type JsonValueInput } from "@evolu/common";
|
|
12572
|
+
*
|
|
12573
|
+
* const input: JsonValueInput = {
|
|
12574
|
+
* name: "Ada",
|
|
12575
|
+
* scores: [10, 20],
|
|
12576
|
+
* };
|
|
12577
|
+
* const result = JsonValue.fromUnknown(input);
|
|
12578
|
+
*
|
|
12579
|
+
* expectOk(result, input);
|
|
12580
|
+
* expectTypeOf(result.value).toEqualTypeOf<JsonValue>();
|
|
12581
|
+
* ```
|
|
12582
|
+
*
|
|
11630
12583
|
* @group JSON
|
|
11631
12584
|
*/
|
|
11632
12585
|
export const JsonValue: JsonValueType =
|
|
@@ -11662,7 +12615,7 @@ export const JsonObject = /*#__PURE__*/ record(
|
|
|
11662
12615
|
* A {@link String} Brand proving that its exact text parses to {@link JsonValue}.
|
|
11663
12616
|
*
|
|
11664
12617
|
* The Brand preserves whitespace, property order, and number spelling. Convert
|
|
11665
|
-
* it
|
|
12618
|
+
* it to {@link JsonValue} through {@link JsonValueFromJson} or
|
|
11666
12619
|
* {@link jsonToJsonValue}.
|
|
11667
12620
|
*
|
|
11668
12621
|
* @group JSON
|
|
@@ -11681,14 +12634,34 @@ export const Json = /*#__PURE__*/ brand(
|
|
|
11681
12634
|
export type Json = typeof Json.Output;
|
|
11682
12635
|
|
|
11683
12636
|
/**
|
|
11684
|
-
*
|
|
12637
|
+
* Converts proven {@link Json} text to an exact {@link JsonValue}.
|
|
12638
|
+
*
|
|
12639
|
+
* ### Example
|
|
12640
|
+
*
|
|
12641
|
+
* ```ts
|
|
12642
|
+
* import { Json, jsonToJsonValue } from "@evolu/common";
|
|
12643
|
+
*
|
|
12644
|
+
* const value = jsonToJsonValue(Json.orThrow('{"name":"Ada"}'));
|
|
12645
|
+
*
|
|
12646
|
+
* expect(value).toEqual({ name: "Ada" });
|
|
12647
|
+
* ```
|
|
11685
12648
|
*
|
|
11686
12649
|
* @group JSON
|
|
11687
12650
|
*/
|
|
11688
12651
|
export const jsonToJsonValue = (value: Json): JsonValue => parseJson(value);
|
|
11689
12652
|
|
|
11690
12653
|
/**
|
|
11691
|
-
*
|
|
12654
|
+
* Converts an exact {@link JsonValue} to canonical {@link Json} text.
|
|
12655
|
+
*
|
|
12656
|
+
* ### Example
|
|
12657
|
+
*
|
|
12658
|
+
* ```ts
|
|
12659
|
+
* import { jsonValueToJson, type JsonValue } from "@evolu/common";
|
|
12660
|
+
*
|
|
12661
|
+
* const value: JsonValue = { name: "Ada" };
|
|
12662
|
+
*
|
|
12663
|
+
* expect(jsonValueToJson(value)).toBe('{"name":"Ada"}');
|
|
12664
|
+
* ```
|
|
11692
12665
|
*
|
|
11693
12666
|
* @group JSON
|
|
11694
12667
|
*/
|
|
@@ -11696,11 +12669,22 @@ export const jsonValueToJson = (value: JsonValue): Json =>
|
|
|
11696
12669
|
stringifyJsonValue(JsonValue.to(value));
|
|
11697
12670
|
|
|
11698
12671
|
/**
|
|
11699
|
-
* {@link Json}
|
|
12672
|
+
* Transformation {@link Type} that parses {@link Json} into {@link JsonValue}.
|
|
11700
12673
|
*
|
|
11701
12674
|
* Decoding unknown input first validates the Json Brand. Starting from the
|
|
11702
12675
|
* typed Json parent is infallible. Encoding canonicalizes JSON text.
|
|
11703
12676
|
*
|
|
12677
|
+
* ### Example
|
|
12678
|
+
*
|
|
12679
|
+
* ```ts
|
|
12680
|
+
* import { JsonValueFromJson } from "@evolu/common";
|
|
12681
|
+
*
|
|
12682
|
+
* const result = JsonValueFromJson.fromUnknown('{ "name": "Ada" }');
|
|
12683
|
+
*
|
|
12684
|
+
* expectOk(result, { name: "Ada" });
|
|
12685
|
+
* expect(JsonValueFromJson.to(result.value)).toBe('{"name":"Ada"}');
|
|
12686
|
+
* ```
|
|
12687
|
+
*
|
|
11704
12688
|
* @group JSON
|
|
11705
12689
|
*/
|
|
11706
12690
|
export const JsonValueFromJson = /*#__PURE__*/ transform(
|
|
@@ -11714,67 +12698,48 @@ export const JsonValueFromJson = /*#__PURE__*/ transform(
|
|
|
11714
12698
|
);
|
|
11715
12699
|
|
|
11716
12700
|
/**
|
|
11717
|
-
* Branded {@link Json} Type and
|
|
11718
|
-
*
|
|
11719
|
-
* Use this
|
|
11720
|
-
*
|
|
11721
|
-
*
|
|
11722
|
-
*
|
|
11723
|
-
* Output.
|
|
11724
|
-
*
|
|
11725
|
-
* The supplied Type's `CanonicalInput` must be JSON-compatible. The encoder
|
|
11726
|
-
* first uses the Type's canonical `to` operation, then encodes that
|
|
11727
|
-
* representation as canonical Json. Runtime representation constraints
|
|
11728
|
-
* TypeScript cannot prove, such as dense Arrays and enumerable data properties,
|
|
11729
|
-
* are asserted as developer errors.
|
|
11730
|
-
*
|
|
11731
|
-
* The branded Json Type is the validation boundary for unknown JSON text. It
|
|
11732
|
-
* grants its {@link Brand} only when the text is valid Json and decoding it
|
|
11733
|
-
* through the supplied Type succeeds. The supplied Type is responsible for
|
|
11734
|
-
* preserving semantic Outputs across canonical JSON encoding and decoding. This
|
|
11735
|
-
* law cannot be checked generically because Types do not define semantic
|
|
11736
|
-
* equality. Before granting the Brand, the encoder asserts the weaker runtime
|
|
11737
|
-
* guarantee that the final Json successfully decodes through the supplied Type.
|
|
11738
|
-
* Failed decodability therefore throws as a developer error.
|
|
11739
|
-
*
|
|
11740
|
-
* Consequently, the two typed conversions return their values directly without
|
|
11741
|
-
* exposing a validation {@link Result}: an Output satisfying the JSON
|
|
11742
|
-
* representation contract of a correctly declared Type can always be encoded,
|
|
11743
|
-
* and the branded Json proves decoding will succeed. Decoding still runs the
|
|
11744
|
-
* Type pipeline because transformations may need to construct different Output
|
|
11745
|
-
* values.
|
|
12701
|
+
* Branded {@link Json} Type and conversions for another {@link Type}.
|
|
12702
|
+
*
|
|
12703
|
+
* Use this when a value must be stored as JSON text, such as in a JSON column
|
|
12704
|
+
* in an Evolu Schema. It returns a branded Json Type and functions for
|
|
12705
|
+
* converting the supplied Type's Output to and from that branded JSON
|
|
12706
|
+
* representation.
|
|
11746
12707
|
*
|
|
11747
12708
|
* ### Example
|
|
11748
12709
|
*
|
|
11749
12710
|
* ```ts
|
|
11750
12711
|
* import {
|
|
11751
12712
|
* Age,
|
|
12713
|
+
* NonEmptyTrimmedString100,
|
|
11752
12714
|
* json,
|
|
11753
12715
|
* object,
|
|
11754
|
-
* String,
|
|
11755
12716
|
* type Brand,
|
|
11756
|
-
* type InferType,
|
|
11757
|
-
* type Json,
|
|
11758
12717
|
* } from "@evolu/common";
|
|
11759
12718
|
*
|
|
11760
|
-
* const
|
|
11761
|
-
*
|
|
12719
|
+
* const User = object({
|
|
12720
|
+
* name: NonEmptyTrimmedString100,
|
|
12721
|
+
* age: Age,
|
|
12722
|
+
* });
|
|
11762
12723
|
*
|
|
11763
|
-
* const [
|
|
11764
|
-
*
|
|
11765
|
-
* "
|
|
12724
|
+
* const [UserJson, userToUserJson, userJsonToUser] = json(
|
|
12725
|
+
* User,
|
|
12726
|
+
* "UserJson",
|
|
11766
12727
|
* );
|
|
11767
|
-
* type PersonJson = typeof PersonJson.Output;
|
|
11768
|
-
*
|
|
11769
|
-
* expectTypeOf<PersonJson>().toEqualTypeOf<Json & Brand<"PersonJson">>();
|
|
11770
12728
|
*
|
|
11771
|
-
* const
|
|
11772
|
-
* const
|
|
11773
|
-
* const decodedPerson = personJsonToPerson(personJson);
|
|
12729
|
+
* const user = User.orThrow({ name: "Ada", age: 37 });
|
|
12730
|
+
* const userJson = userToUserJson(user);
|
|
11774
12731
|
*
|
|
11775
|
-
*
|
|
12732
|
+
* expectTypeOf(userJson).toEqualTypeOf<
|
|
12733
|
+
* string & Brand<"Json"> & Brand<"UserJson">
|
|
12734
|
+
* >();
|
|
12735
|
+
* expect(userJson).toBe('{"name":"Ada","age":37}');
|
|
12736
|
+
* expect(userJsonToUser(userJson)).toEqual(user);
|
|
11776
12737
|
* ```
|
|
11777
12738
|
*
|
|
12739
|
+
* The supplied Type must have a JSON-compatible `CanonicalInput`. The branded
|
|
12740
|
+
* Json Type accepts only valid JSON text whose parsed value can be decoded by
|
|
12741
|
+
* the supplied Type.
|
|
12742
|
+
*
|
|
11778
12743
|
* @group JSON
|
|
11779
12744
|
*/
|
|
11780
12745
|
export const json = <T extends ConcreteTypeNode, Name extends TypeName>(
|