@evolu/common 8.3.0 → 8.3.2

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/src/Type.ts 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
- * PositiveInt,
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: PositiveInt,
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
- * Decoding failures are explicit {@link Result} values. Error formatters are
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
- * Evolu Type is designed to make correct code the easiest code to write:
118
+ * ```ts
119
+ * import { Age } from "@evolu/common";
37
120
  *
38
- * - {@link Brand} carries every refinement constraint into TypeScript.
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
- * Correctness is especially important for local-first data: application authors
52
- * cannot inspect or repair a user's data on a server. Type declarations reject
53
- * invalid data at system boundaries, then preserve those guarantees wherever
54
- * the data travels.
55
- *
56
- * Evolu Type supports [Standard Schema](https://standardschema.dev/) while
57
- * preserving each Type's exact Input and Output. Its implementation is
58
- * optimized for small bundles, but keeps actionable assertion messages as a
59
- * deliberate developer-experience tradeoff.
60
- *
61
- * Evolu Type requires TypeScript 7 or newer and `exactOptionalPropertyTypes`.
62
- * Predefined Types use the names of corresponding JavaScript built-ins. When
63
- * one shadows a built-in, access the built-in through `globalThis`, such as
64
- * `globalThis.String` or `globalThis.Date`.
65
- *
66
- * ## Boundaries
67
- *
68
- * `fromUnknown` validates untyped input through the complete pipeline. `from`
69
- * and its `.parent` operations assert their declared boundary, then return only
70
- * errors from the remaining stages. `orThrow` and `orNull` reuse the deepest
71
- * `from` operation accepting `Input`, while `to` asserts its Output boundary. A
72
- * failed assertion is a developer error; its cause preserves the exact
73
- * structured Output validation error.
74
- *
75
- * Prefer the most precise typed boundary available. A value is not unknown
76
- * merely because it originated outside the application: forms, components, and
77
- * other producers often expose a `string` or a branded value that can connect
78
- * directly to a matching `from` boundary. Reserve `fromUnknown` for values
79
- * whose TypeScript type is genuinely `unknown`. `is` means exact membership in
80
- * the Output domain, not merely that an encoded Input can be decoded.
81
- *
82
- * Typed boundaries also connect producer and domain constraints at compile
83
- * time. Suppose a form and its domain field both use
84
- * {@link NonEmptyTrimmedString100}. If the form is later relaxed to
85
- * {@link NonEmptyTrimmedString1000}, the domain Type will still reject longer
86
- * values, so invalid data cannot be stored. But a boundary accepting only
87
- * `unknown` or `string` cannot reveal that the producer contract changed. The
88
- * application still compiles, and users discover the incompatibility only when
89
- * a valid form value fails to save.
90
- *
91
- * Passing the precise branded value to `from` makes that incompatibility a
92
- * compile-time error. The developer must preserve the original form limit or
93
- * introduce a new field for the wider domain instead of shipping a broken form.
94
- *
95
- * A weaker producer is sometimes intentional. In that case, a matching
96
- * `.parent` boundary validates only the constraints the producer does not
97
- * already guarantee, while preserving its existing guarantees in the type.
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
- * Code trust and data validation are separate decisions. Values returned by
248
- * trusted legacy code or another realm can still be uncertain and should be
249
- * validated. Realm-neutral Types accept an otherwise legitimate representation
250
- * without requiring conversion merely because its built-ins belong to another
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 directly.
255
- * If the boundary returns `unknown`, validate it instead of bypassing the
256
- * boundary with a cast. Use a specialized Type or explicit transformation when
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
@@ -485,7 +586,7 @@ export interface Type<
485
586
 
486
587
  /**
487
588
  * Formats an error returned by `fromUnknown` or `from` as one human-readable
488
- * message. Built-in Types use English; {@link localizeTypes} derives Types
589
+ * message. Predefined Types use English; {@link localizeTypes} derives Types
489
590
  * with localized formatters.
490
591
  *
491
592
  * Structural errors retain nested errors and their locations in the typed
@@ -723,7 +824,7 @@ interface TransparentTypeError {
723
824
  *
724
825
  * @group Core
725
826
  */
726
- // Built-in errors intentionally repeat narrower `value` properties. Making
827
+ // Predefined errors intentionally repeat narrower `value` properties. Making
727
828
  // `value` generic here and sharing this base regresses `pnpm bench:type`.
728
829
  export interface TypeValueError<
729
830
  Name extends TypeName = TypeName,
@@ -938,8 +1039,8 @@ const assertTypeOutput = <Error extends TypeError>(
938
1039
  * Pass the Types used together in one localization scope and formatter maps
939
1040
  * keyed by locale. TypeScript infers every formatter required by the selected
940
1041
  * Types, including errors from nested structural Types and recursive Lazy
941
- * Types. Every locale must provide the complete inferred formatter set;
942
- * missing and unrelated formatters are compile-time errors.
1042
+ * Types. Every locale must provide the complete inferred formatter set; missing
1043
+ * and unrelated formatters are compile-time errors.
943
1044
  *
944
1045
  * The result preserves the locale names, selected Type names, and exact
945
1046
  * TypeScript types. A localized Type validates exactly like its source Type;
@@ -991,8 +1092,8 @@ const assertTypeOutput = <Error extends TypeError>(
991
1092
  *
992
1093
  * ### Supported locales
993
1094
  *
994
- * English is built in; use {@link Type} directly for its default formatters.
995
- * The following additional locales are available:
1095
+ * English is built in; use {@link Type} directly for its default formatters. The
1096
+ * following additional locales are available:
996
1097
  *
997
1098
  * - Arabic (`ar`)
998
1099
  * - Bengali (`bn`)
@@ -2029,9 +2130,9 @@ const createChildType = <
2029
2130
  * canonicalize multiple parent representations, but it must be total and must
2030
2131
  * not lose distinctions present in the Output domain.
2031
2132
  *
2032
- * Transformation callbacks are Type construction code. Their successful
2033
- * results are asserted against the declared boundary so a broken callback fails
2034
- * as a developer error rather than becoming a validation error. Like all
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
2035
2136
  * Type-construction callbacks, they are trusted to follow their declared
2036
2137
  * TypeScript types. A `Result<_, never>` callback is therefore trusted never to
2037
2138
  * return an `Err`.
@@ -2047,13 +2148,7 @@ const createChildType = <
2047
2148
  * ### Example
2048
2149
  *
2049
2150
  * ```ts
2050
- * import {
2051
- * Boolean,
2052
- * literal,
2053
- * ok,
2054
- * transform,
2055
- * union,
2056
- * } from "@evolu/common";
2151
+ * import { Boolean, literal, ok, transform, union } from "@evolu/common";
2057
2152
  *
2058
2153
  * const BooleanString = union(literal("false"), literal("true"));
2059
2154
  * const BooleanFromString = transform(
@@ -2815,18 +2910,18 @@ interface ObjectTagOutputByName {
2815
2910
  /**
2816
2911
  * Realm-neutral {@link Type} trusting an object's reported tag.
2817
2912
  *
2818
- * Predefined built-in tags expose their native Output type under the assumption
2819
- * that trusted code does not forge their tags. They do not verify native
2820
- * internal slots. A custom tag refines the supplied Type and adds nominal
2821
- * evidence to its Output, so only a value validated by the resulting Type is
2822
- * 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.
2823
2918
  *
2824
- * `Object.prototype.toString` recognizes legitimate built-ins from another
2825
- * realm, but any object can customize the result with `Symbol.toStringTag`.
2826
- * Types returned by this factory therefore classify trusted values; they are
2827
- * not security boundaries. Passing a forged built-in tag violates the trust
2828
- * assumption of the predefined Type. Primitive Outputs are rejected at compile
2829
- * time.
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.
2830
2925
  *
2831
2926
  * ### Example
2832
2927
  *
@@ -5331,8 +5426,8 @@ const base64UrlStringToUint8Array = (value: string): Uint8Array => {
5331
5426
  /**
5332
5427
  * Base64Url text without padding.
5333
5428
  *
5334
- * Encode bytes with {@link uint8ArrayToBase64Url} and decode them with
5335
- * {@link base64UrlToUint8Array}.
5429
+ * Convert bytes to Base64Url with {@link uint8ArrayToBase64Url} and convert
5430
+ * Base64Url to bytes with {@link base64UrlToUint8Array}.
5336
5431
  *
5337
5432
  * @group String
5338
5433
  */
@@ -5361,7 +5456,7 @@ export interface Base64UrlError extends TypeError<"Base64Url"> {
5361
5456
  }
5362
5457
 
5363
5458
  /**
5364
- * Encodes bytes as {@link Base64Url}.
5459
+ * Converts bytes to {@link Base64Url}.
5365
5460
  *
5366
5461
  * ### Example
5367
5462
  *
@@ -5379,7 +5474,7 @@ export const uint8ArrayToBase64Url = (bytes: Uint8Array): Base64Url =>
5379
5474
  uint8ArrayToBase64UrlString(bytes) as Base64Url;
5380
5475
 
5381
5476
  /**
5382
- * Decodes {@link Base64Url} as bytes.
5477
+ * Converts {@link Base64Url} to bytes.
5383
5478
  *
5384
5479
  * ### Example
5385
5480
  *
@@ -5823,7 +5918,9 @@ export interface Int64StringError extends TypeError<"Int64String"> {
5823
5918
  * const result = Int64FromInt64String.fromUnknown("9223372036854775807");
5824
5919
  *
5825
5920
  * expectOk(result, 9223372036854775807n);
5826
- * expect(Int64FromInt64String.to(result.value)).toBe("9223372036854775807");
5921
+ * expect(Int64FromInt64String.to(result.value)).toBe(
5922
+ * "9223372036854775807",
5923
+ * );
5827
5924
  * ```
5828
5925
  *
5829
5926
  * @group Number
@@ -8467,7 +8564,7 @@ type PlainObjectError = ObjectError<
8467
8564
  *
8468
8565
  * @group Base
8469
8566
  */
8470
- export const Object: Type<
8567
+ const _Object: Type<
8471
8568
  "Object",
8472
8569
  Readonly<Record<string, unknown>>,
8473
8570
  Readonly<Record<string, unknown>>,
@@ -8588,6 +8685,13 @@ export const Object: Type<
8588
8685
  }) as TypeErrorFormatter<TypeError>),
8589
8686
  );
8590
8687
 
8688
+ // Avoid a local `Object` binding because Babel's CommonJS transform injects
8689
+ // `Object.defineProperty` before it is initialized:
8690
+ // https://github.com/babel/babel/issues/16943
8691
+ // https://github.com/react/metro/issues/1331
8692
+ // https://github.com/expo/expo/issues/31167
8693
+ export { _Object as Object };
8694
+
8591
8695
  const isPlainObject = (value: object): boolean => {
8592
8696
  const prototype: unknown = globalThis.Object.getPrototypeOf(value);
8593
8697
  return (
@@ -10280,10 +10384,10 @@ export interface ObjectNotObjectError extends TypeError<"Object"> {
10280
10384
  *
10281
10385
  * Object Types accept a `null` prototype or a prototype whose own prototype is
10282
10386
  * `null`. This includes ordinary and cross-realm plain objects as well as
10283
- * objects created from an immediate root prototype. Arrays, built-in objects,
10284
- * class instances, and objects with deeper custom prototype chains return this
10285
- * error instead of having their prototype or inherited state discarded.
10286
- * `reason.value` is the rejected object.
10387
+ * objects created from an immediate root prototype. Arrays, JavaScript built-in
10388
+ * objects, class instances, and objects with deeper custom prototype chains
10389
+ * return this error instead of having their prototype or inherited state
10390
+ * discarded. `reason.value` is the rejected object.
10287
10391
  *
10288
10392
  * @group Objects
10289
10393
  */
@@ -10634,8 +10738,8 @@ type OmitKeyConcreteTypeError = CompileTimeError<
10634
10738
  * Creates a {@link Type} for {@link Result} values.
10635
10739
  *
10636
10740
  * Use this to validate Results crossing a storage, worker, API, or other
10637
- * serialization boundary. `fromUnknown` returns an outer validation Result.
10638
- * Its successful value is the inner domain Result described by `okType` and
10741
+ * serialization boundary. `fromUnknown` returns an outer validation Result. Its
10742
+ * successful value is the inner domain Result described by `okType` and
10639
10743
  * `errorType`.
10640
10744
  *
10641
10745
  * ### Example
@@ -10844,7 +10948,8 @@ export interface Typed<Tag extends TypeName> {
10844
10948
  * Extracts members of a {@link Typed} Output union by their `type` literal.
10845
10949
  *
10846
10950
  * The requested tag is constrained to the union's actual discriminator values,
10847
- * so a misspelling is a TypeScript error instead of silently producing `never`.
10951
+ * so a misspelling is a TypeScript error instead of silently producing
10952
+ * `never`.
10848
10953
  *
10849
10954
  * ### Example
10850
10955
  *
@@ -12517,7 +12622,7 @@ export const JsonObject = /*#__PURE__*/ record(
12517
12622
  * A {@link String} Brand proving that its exact text parses to {@link JsonValue}.
12518
12623
  *
12519
12624
  * The Brand preserves whitespace, property order, and number spelling. Convert
12520
- * it totally to {@link JsonValue} through {@link JsonValueFromJson} or
12625
+ * it to {@link JsonValue} through {@link JsonValueFromJson} or
12521
12626
  * {@link jsonToJsonValue}.
12522
12627
  *
12523
12628
  * @group JSON
@@ -12536,7 +12641,7 @@ export const Json = /*#__PURE__*/ brand(
12536
12641
  export type Json = typeof Json.Output;
12537
12642
 
12538
12643
  /**
12539
- * Totally parses proven {@link Json} text into an exact {@link JsonValue}.
12644
+ * Converts proven {@link Json} text to an exact {@link JsonValue}.
12540
12645
  *
12541
12646
  * ### Example
12542
12647
  *
@@ -12553,7 +12658,7 @@ export type Json = typeof Json.Output;
12553
12658
  export const jsonToJsonValue = (value: Json): JsonValue => parseJson(value);
12554
12659
 
12555
12660
  /**
12556
- * Totally encodes an exact {@link JsonValue} as canonical {@link Json} text.
12661
+ * Converts an exact {@link JsonValue} to canonical {@link Json} text.
12557
12662
  *
12558
12663
  * ### Example
12559
12664
  *
@@ -12600,67 +12705,48 @@ export const JsonValueFromJson = /*#__PURE__*/ transform(
12600
12705
  );
12601
12706
 
12602
12707
  /**
12603
- * Branded {@link Json} Type and total conversions for another Type.
12604
- *
12605
- * Use this factory when a domain value must be stored as JSON text while its
12606
- * exact Type remains visible to TypeScript, such as a JSON column in an Evolu
12607
- * Schema. The returned tuple contains the branded Json Type, an encoder from
12608
- * the supplied Type's Output to Json, and a decoder from Json back to that
12609
- * Output.
12610
- *
12611
- * The supplied Type's `CanonicalInput` must be JSON-compatible. The encoder
12612
- * first uses the Type's canonical `to` operation, then encodes that
12613
- * representation as canonical Json. Runtime representation constraints
12614
- * TypeScript cannot prove, such as dense Arrays and enumerable data properties,
12615
- * are asserted as developer errors.
12616
- *
12617
- * The branded Json Type is the validation boundary for unknown JSON text. It
12618
- * grants its {@link Brand} only when the text is valid Json and decoding it
12619
- * through the supplied Type succeeds. The supplied Type is responsible for
12620
- * preserving semantic Outputs across canonical JSON encoding and decoding. This
12621
- * law cannot be checked generically because Types do not define semantic
12622
- * equality. Before granting the Brand, the encoder asserts the weaker runtime
12623
- * guarantee that the final Json successfully decodes through the supplied Type.
12624
- * Failed decodability therefore throws as a developer error.
12625
- *
12626
- * Consequently, the two typed conversions return their values directly without
12627
- * exposing a validation {@link Result}: an Output satisfying the JSON
12628
- * representation contract of a correctly declared Type can always be encoded,
12629
- * and the branded Json proves decoding will succeed. Decoding still runs the
12630
- * Type pipeline because transformations may need to construct different Output
12631
- * values.
12708
+ * Branded {@link Json} Type and conversions for another {@link Type}.
12709
+ *
12710
+ * Use this when a value must be stored as JSON text, such as in a JSON column
12711
+ * in an Evolu Schema. It returns a branded Json Type and functions for
12712
+ * converting the supplied Type's Output to and from that branded JSON
12713
+ * representation.
12632
12714
  *
12633
12715
  * ### Example
12634
12716
  *
12635
12717
  * ```ts
12636
12718
  * import {
12637
12719
  * Age,
12720
+ * NonEmptyTrimmedString100,
12638
12721
  * json,
12639
12722
  * object,
12640
- * String,
12641
12723
  * type Brand,
12642
- * type InferType,
12643
- * type Json,
12644
12724
  * } from "@evolu/common";
12645
12725
  *
12646
- * const Person = object({ name: String, age: Age });
12647
- * interface Person extends InferType<typeof Person> {}
12726
+ * const User = object({
12727
+ * name: NonEmptyTrimmedString100,
12728
+ * age: Age,
12729
+ * });
12648
12730
  *
12649
- * const [PersonJson, personToPersonJson, personJsonToPerson] = json(
12650
- * Person,
12651
- * "PersonJson",
12731
+ * const [UserJson, userToUserJson, userJsonToUser] = json(
12732
+ * User,
12733
+ * "UserJson",
12652
12734
  * );
12653
- * type PersonJson = typeof PersonJson.Output;
12654
- *
12655
- * expectTypeOf<PersonJson>().toEqualTypeOf<Json & Brand<"PersonJson">>();
12656
12735
  *
12657
- * const person = Person.orThrow({ name: "Ada", age: 42 });
12658
- * const personJson = personToPersonJson(person);
12659
- * const decodedPerson = personJsonToPerson(personJson);
12736
+ * const user = User.orThrow({ name: "Ada", age: 37 });
12737
+ * const userJson = userToUserJson(user);
12660
12738
  *
12661
- * expect(decodedPerson).toEqual(person);
12739
+ * expectTypeOf(userJson).toEqualTypeOf<
12740
+ * string & Brand<"Json"> & Brand<"UserJson">
12741
+ * >();
12742
+ * expect(userJson).toBe('{"name":"Ada","age":37}');
12743
+ * expect(userJsonToUser(userJson)).toEqual(user);
12662
12744
  * ```
12663
12745
  *
12746
+ * The supplied Type must have a JSON-compatible `CanonicalInput`. The branded
12747
+ * Json Type accepts only valid JSON text whose parsed value can be decoded by
12748
+ * the supplied Type.
12749
+ *
12664
12750
  * @group JSON
12665
12751
  */
12666
12752
  export const json = <T extends ConcreteTypeNode, Name extends TypeName>(