@evolu/common 8.3.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/Type.d.ts +63 -85
- package/dist/src/Type.d.ts.map +1 -1
- package/dist/src/Type.js +219 -135
- package/package.json +1 -1
- package/src/Type.ts +244 -165
package/dist/src/Type.js
CHANGED
|
@@ -8,17 +8,84 @@
|
|
|
8
8
|
* valid `Output` into a `CanonicalInput`. Types can validate, refine,
|
|
9
9
|
* transform, and compose without losing the contracts TypeScript can express.
|
|
10
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:
|
|
49
|
+
*
|
|
11
50
|
* ```ts
|
|
12
51
|
* import {
|
|
52
|
+
* Number,
|
|
13
53
|
* NonEmptyTrimmedString100,
|
|
14
|
-
*
|
|
54
|
+
* brand,
|
|
55
|
+
* finite,
|
|
56
|
+
* int,
|
|
57
|
+
* lessThan,
|
|
58
|
+
* nonNaN,
|
|
59
|
+
* nonNegative,
|
|
15
60
|
* object,
|
|
61
|
+
* type Brand,
|
|
62
|
+
* type InferErrors,
|
|
16
63
|
* type InferType,
|
|
17
64
|
* } from "@evolu/common";
|
|
18
65
|
*
|
|
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);
|
|
72
|
+
*
|
|
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
|
+
*
|
|
19
86
|
* const User = object({
|
|
20
87
|
* name: NonEmptyTrimmedString100,
|
|
21
|
-
* age:
|
|
88
|
+
* age: Age,
|
|
22
89
|
* });
|
|
23
90
|
* interface User extends InferType<typeof User> {}
|
|
24
91
|
*
|
|
@@ -27,74 +94,114 @@
|
|
|
27
94
|
*
|
|
28
95
|
* expectOk(user, { name: "Ada", age: 37 });
|
|
29
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
|
+
* >();
|
|
30
114
|
* ```
|
|
31
115
|
*
|
|
32
|
-
*
|
|
33
|
-
* separate from validation, so structured errors remain exhaustively typed and
|
|
34
|
-
* can be localized without changing the Type.
|
|
116
|
+
* A Type can format its structured errors into user-facing messages:
|
|
35
117
|
*
|
|
36
|
-
*
|
|
118
|
+
* ```ts
|
|
119
|
+
* import { Age } from "@evolu/common";
|
|
37
120
|
*
|
|
38
|
-
*
|
|
39
|
-
* - Invalid declarations produce readable {@link CompileTimeError} types when the
|
|
40
|
-
* compiler can detect them; runtime assertions enforce construction contracts
|
|
41
|
-
* it cannot prove.
|
|
42
|
-
* - Typed `from` boundaries allow connecting value producers to domain fields
|
|
43
|
-
* through their exact TypeScript types, so incompatible contract changes are
|
|
44
|
-
* compile-time errors rather than runtime validation errors.
|
|
45
|
-
* - Lawful codecs compose without creating unencodable values: every valid
|
|
46
|
-
* Output has a canonical Input representation and round-trips to the same
|
|
47
|
-
* semantic value.
|
|
48
|
-
* - Type-safe localization infers the required error formatters from selected
|
|
49
|
-
* Types, so missing validation messages are compile-time errors.
|
|
121
|
+
* const age = Age.fromUnknown(37.5);
|
|
50
122
|
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
*
|
|
70
|
-
*
|
|
71
|
-
*
|
|
72
|
-
*
|
|
73
|
-
*
|
|
74
|
-
*
|
|
75
|
-
*
|
|
76
|
-
*
|
|
77
|
-
*
|
|
78
|
-
*
|
|
79
|
-
*
|
|
80
|
-
*
|
|
81
|
-
*
|
|
82
|
-
*
|
|
83
|
-
*
|
|
84
|
-
*
|
|
85
|
-
*
|
|
86
|
-
*
|
|
87
|
-
*
|
|
88
|
-
*
|
|
89
|
-
*
|
|
90
|
-
*
|
|
91
|
-
*
|
|
92
|
-
*
|
|
93
|
-
*
|
|
94
|
-
*
|
|
95
|
-
*
|
|
96
|
-
*
|
|
97
|
-
*
|
|
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<
|
|
174
|
+
* Result<
|
|
175
|
+
* NonEmptyTrimmedString100,
|
|
176
|
+
* MaxLengthError<100> | MinLengthError<1>
|
|
177
|
+
* >
|
|
178
|
+
* >();
|
|
179
|
+
* expectOk(validatedTitle, "Buy milk");
|
|
180
|
+
* ```
|
|
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.
|
|
98
205
|
*
|
|
99
206
|
* ## FAQ
|
|
100
207
|
*
|
|
@@ -244,24 +351,18 @@
|
|
|
244
351
|
*
|
|
245
352
|
* ### How should values from another realm be handled?
|
|
246
353
|
*
|
|
247
|
-
*
|
|
248
|
-
*
|
|
249
|
-
*
|
|
250
|
-
*
|
|
251
|
-
* 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.
|
|
252
358
|
*
|
|
253
359
|
* When an application trusts both the producer and its return contract, expose
|
|
254
|
-
* that contract as an accurate TypeScript type and use the typed value
|
|
255
|
-
* If the boundary returns `unknown`, validate it instead of bypassing
|
|
256
|
-
* boundary with a cast. Use a specialized Type or explicit transformation
|
|
257
|
-
* the producer uses a different representation that needs adaptation or
|
|
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
|
|
258
364
|
* normalization.
|
|
259
365
|
*
|
|
260
|
-
* All executing JavaScript remains trusted. Deliberately forged built-ins,
|
|
261
|
-
* hostile Proxies, throwing traps, or sabotaged executable behavior can throw;
|
|
262
|
-
* Evolu Type does not selectively contain them or claim to be a security
|
|
263
|
-
* boundary for untrusted code.
|
|
264
|
-
*
|
|
265
366
|
* ### Why doesn't Evolu Type extract data from rich objects?
|
|
266
367
|
*
|
|
267
368
|
* Some validation libraries parse an object's data projection. An imaginary
|
|
@@ -418,8 +519,8 @@ const assertTypeOutput = (name, is, validateOutput, value, options = firstValida
|
|
|
418
519
|
* Pass the Types used together in one localization scope and formatter maps
|
|
419
520
|
* keyed by locale. TypeScript infers every formatter required by the selected
|
|
420
521
|
* Types, including errors from nested structural Types and recursive Lazy
|
|
421
|
-
* Types. Every locale must provide the complete inferred formatter set;
|
|
422
|
-
*
|
|
522
|
+
* Types. Every locale must provide the complete inferred formatter set; missing
|
|
523
|
+
* and unrelated formatters are compile-time errors.
|
|
423
524
|
*
|
|
424
525
|
* The result preserves the locale names, selected Type names, and exact
|
|
425
526
|
* TypeScript types. A localized Type validates exactly like its source Type;
|
|
@@ -471,8 +572,8 @@ const assertTypeOutput = (name, is, validateOutput, value, options = firstValida
|
|
|
471
572
|
*
|
|
472
573
|
* ### Supported locales
|
|
473
574
|
*
|
|
474
|
-
* English is built in; use {@link Type} directly for its default formatters.
|
|
475
|
-
*
|
|
575
|
+
* English is built in; use {@link Type} directly for its default formatters. The
|
|
576
|
+
* following additional locales are available:
|
|
476
577
|
*
|
|
477
578
|
* - Arabic (`ar`)
|
|
478
579
|
* - Bengali (`bn`)
|
|
@@ -1941,8 +2042,8 @@ const base64UrlStringToUint8Array = (value) => {
|
|
|
1941
2042
|
/**
|
|
1942
2043
|
* Base64Url text without padding.
|
|
1943
2044
|
*
|
|
1944
|
-
*
|
|
1945
|
-
* {@link base64UrlToUint8Array}.
|
|
2045
|
+
* Convert bytes to Base64Url with {@link uint8ArrayToBase64Url} and convert
|
|
2046
|
+
* Base64Url to bytes with {@link base64UrlToUint8Array}.
|
|
1946
2047
|
*
|
|
1947
2048
|
* @group String
|
|
1948
2049
|
*/
|
|
@@ -1953,7 +2054,7 @@ export const Base64Url = /*#__PURE__*/ brand("Base64Url", String, (value) => {
|
|
|
1953
2054
|
: err({ type: "Base64Url", value });
|
|
1954
2055
|
}, (error) => `The value ${safelyStringifyUnknownValue(error.value)} is not a valid Base64Url string.`);
|
|
1955
2056
|
/**
|
|
1956
|
-
*
|
|
2057
|
+
* Converts bytes to {@link Base64Url}.
|
|
1957
2058
|
*
|
|
1958
2059
|
* ### Example
|
|
1959
2060
|
*
|
|
@@ -1969,7 +2070,7 @@ export const Base64Url = /*#__PURE__*/ brand("Base64Url", String, (value) => {
|
|
|
1969
2070
|
*/
|
|
1970
2071
|
export const uint8ArrayToBase64Url = (bytes) => uint8ArrayToBase64UrlString(bytes);
|
|
1971
2072
|
/**
|
|
1972
|
-
*
|
|
2073
|
+
* Converts {@link Base64Url} to bytes.
|
|
1973
2074
|
*
|
|
1974
2075
|
* ### Example
|
|
1975
2076
|
*
|
|
@@ -2225,7 +2326,9 @@ export const Int64String = /*#__PURE__*/ brand("Int64String", NonEmptyTrimmedStr
|
|
|
2225
2326
|
* const result = Int64FromInt64String.fromUnknown("9223372036854775807");
|
|
2226
2327
|
*
|
|
2227
2328
|
* expectOk(result, 9223372036854775807n);
|
|
2228
|
-
* expect(Int64FromInt64String.to(result.value)).toBe(
|
|
2329
|
+
* expect(Int64FromInt64String.to(result.value)).toBe(
|
|
2330
|
+
* "9223372036854775807",
|
|
2331
|
+
* );
|
|
2229
2332
|
* ```
|
|
2230
2333
|
*
|
|
2231
2334
|
* @group Number
|
|
@@ -4791,7 +4894,7 @@ export const JsonObject = /*#__PURE__*/ record(String, JsonValue);
|
|
|
4791
4894
|
* A {@link String} Brand proving that its exact text parses to {@link JsonValue}.
|
|
4792
4895
|
*
|
|
4793
4896
|
* The Brand preserves whitespace, property order, and number spelling. Convert
|
|
4794
|
-
* it
|
|
4897
|
+
* it to {@link JsonValue} through {@link JsonValueFromJson} or
|
|
4795
4898
|
* {@link jsonToJsonValue}.
|
|
4796
4899
|
*
|
|
4797
4900
|
* @group JSON
|
|
@@ -4801,7 +4904,7 @@ export const Json = /*#__PURE__*/ brand("Json", String, (value) => {
|
|
|
4801
4904
|
return result.ok ? ok() : result;
|
|
4802
4905
|
}, (error) => `The value ${safelyStringifyUnknownValue(error.value)} cannot be parsed into a JsonValue.`);
|
|
4803
4906
|
/**
|
|
4804
|
-
*
|
|
4907
|
+
* Converts proven {@link Json} text to an exact {@link JsonValue}.
|
|
4805
4908
|
*
|
|
4806
4909
|
* ### Example
|
|
4807
4910
|
*
|
|
@@ -4817,7 +4920,7 @@ export const Json = /*#__PURE__*/ brand("Json", String, (value) => {
|
|
|
4817
4920
|
*/
|
|
4818
4921
|
export const jsonToJsonValue = (value) => parseJson(value);
|
|
4819
4922
|
/**
|
|
4820
|
-
*
|
|
4923
|
+
* Converts an exact {@link JsonValue} to canonical {@link Json} text.
|
|
4821
4924
|
*
|
|
4822
4925
|
* ### Example
|
|
4823
4926
|
*
|
|
@@ -4856,67 +4959,48 @@ export const JsonValueFromJson = /*#__PURE__*/ transform("JsonValueFromJson", Js
|
|
|
4856
4959
|
to: stringifyJsonValue,
|
|
4857
4960
|
});
|
|
4858
4961
|
/**
|
|
4859
|
-
* Branded {@link Json} Type and
|
|
4860
|
-
*
|
|
4861
|
-
* Use this
|
|
4862
|
-
*
|
|
4863
|
-
*
|
|
4864
|
-
*
|
|
4865
|
-
* Output.
|
|
4866
|
-
*
|
|
4867
|
-
* The supplied Type's `CanonicalInput` must be JSON-compatible. The encoder
|
|
4868
|
-
* first uses the Type's canonical `to` operation, then encodes that
|
|
4869
|
-
* representation as canonical Json. Runtime representation constraints
|
|
4870
|
-
* TypeScript cannot prove, such as dense Arrays and enumerable data properties,
|
|
4871
|
-
* are asserted as developer errors.
|
|
4872
|
-
*
|
|
4873
|
-
* The branded Json Type is the validation boundary for unknown JSON text. It
|
|
4874
|
-
* grants its {@link Brand} only when the text is valid Json and decoding it
|
|
4875
|
-
* through the supplied Type succeeds. The supplied Type is responsible for
|
|
4876
|
-
* preserving semantic Outputs across canonical JSON encoding and decoding. This
|
|
4877
|
-
* law cannot be checked generically because Types do not define semantic
|
|
4878
|
-
* equality. Before granting the Brand, the encoder asserts the weaker runtime
|
|
4879
|
-
* guarantee that the final Json successfully decodes through the supplied Type.
|
|
4880
|
-
* Failed decodability therefore throws as a developer error.
|
|
4881
|
-
*
|
|
4882
|
-
* Consequently, the two typed conversions return their values directly without
|
|
4883
|
-
* exposing a validation {@link Result}: an Output satisfying the JSON
|
|
4884
|
-
* representation contract of a correctly declared Type can always be encoded,
|
|
4885
|
-
* and the branded Json proves decoding will succeed. Decoding still runs the
|
|
4886
|
-
* Type pipeline because transformations may need to construct different Output
|
|
4887
|
-
* values.
|
|
4962
|
+
* Branded {@link Json} Type and conversions for another {@link Type}.
|
|
4963
|
+
*
|
|
4964
|
+
* Use this when a value must be stored as JSON text, such as in a JSON column
|
|
4965
|
+
* in an Evolu Schema. It returns a branded Json Type and functions for
|
|
4966
|
+
* converting the supplied Type's Output to and from that branded JSON
|
|
4967
|
+
* representation.
|
|
4888
4968
|
*
|
|
4889
4969
|
* ### Example
|
|
4890
4970
|
*
|
|
4891
4971
|
* ```ts
|
|
4892
4972
|
* import {
|
|
4893
4973
|
* Age,
|
|
4974
|
+
* NonEmptyTrimmedString100,
|
|
4894
4975
|
* json,
|
|
4895
4976
|
* object,
|
|
4896
|
-
* String,
|
|
4897
4977
|
* type Brand,
|
|
4898
|
-
* type InferType,
|
|
4899
|
-
* type Json,
|
|
4900
4978
|
* } from "@evolu/common";
|
|
4901
4979
|
*
|
|
4902
|
-
* const
|
|
4903
|
-
*
|
|
4980
|
+
* const User = object({
|
|
4981
|
+
* name: NonEmptyTrimmedString100,
|
|
4982
|
+
* age: Age,
|
|
4983
|
+
* });
|
|
4904
4984
|
*
|
|
4905
|
-
* const [
|
|
4906
|
-
*
|
|
4907
|
-
* "
|
|
4985
|
+
* const [UserJson, userToUserJson, userJsonToUser] = json(
|
|
4986
|
+
* User,
|
|
4987
|
+
* "UserJson",
|
|
4908
4988
|
* );
|
|
4909
|
-
* type PersonJson = typeof PersonJson.Output;
|
|
4910
|
-
*
|
|
4911
|
-
* expectTypeOf<PersonJson>().toEqualTypeOf<Json & Brand<"PersonJson">>();
|
|
4912
4989
|
*
|
|
4913
|
-
* const
|
|
4914
|
-
* const
|
|
4915
|
-
* const decodedPerson = personJsonToPerson(personJson);
|
|
4990
|
+
* const user = User.orThrow({ name: "Ada", age: 37 });
|
|
4991
|
+
* const userJson = userToUserJson(user);
|
|
4916
4992
|
*
|
|
4917
|
-
*
|
|
4993
|
+
* expectTypeOf(userJson).toEqualTypeOf<
|
|
4994
|
+
* string & Brand<"Json"> & Brand<"UserJson">
|
|
4995
|
+
* >();
|
|
4996
|
+
* expect(userJson).toBe('{"name":"Ada","age":37}');
|
|
4997
|
+
* expect(userJsonToUser(userJson)).toEqual(user);
|
|
4918
4998
|
* ```
|
|
4919
4999
|
*
|
|
5000
|
+
* The supplied Type must have a JSON-compatible `CanonicalInput`. The branded
|
|
5001
|
+
* Json Type accepts only valid JSON text whose parsed value can be decoded by
|
|
5002
|
+
* the supplied Type.
|
|
5003
|
+
*
|
|
4920
5004
|
* @group JSON
|
|
4921
5005
|
*/
|
|
4922
5006
|
export const json = (type, name, ..._validation) => {
|