@evolu/common 8.9.0 → 8.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (233) hide show
  1. package/dist/src/Bytes.d.ts +647 -0
  2. package/dist/src/Bytes.d.ts.map +1 -0
  3. package/dist/src/{Binary.js → Bytes.js} +266 -16
  4. package/dist/src/Config.d.ts +142 -0
  5. package/dist/src/Config.d.ts.map +1 -0
  6. package/dist/src/Config.js +181 -0
  7. package/dist/src/Fs.d.ts +302 -0
  8. package/dist/src/Fs.d.ts.map +1 -0
  9. package/dist/src/Fs.js +111 -0
  10. package/dist/src/Number.d.ts +50 -7
  11. package/dist/src/Number.d.ts.map +1 -1
  12. package/dist/src/Number.js +47 -8
  13. package/dist/src/Object.d.ts +32 -0
  14. package/dist/src/Object.d.ts.map +1 -1
  15. package/dist/src/Object.js +41 -0
  16. package/dist/src/Sqlite.js +1 -1
  17. package/dist/src/Time.d.ts +74 -12
  18. package/dist/src/Time.d.ts.map +1 -1
  19. package/dist/src/Time.js +40 -2
  20. package/dist/src/Type.d.ts +1451 -79
  21. package/dist/src/Type.d.ts.map +1 -1
  22. package/dist/src/Type.js +1303 -96
  23. package/dist/src/index.d.ts +3 -1
  24. package/dist/src/index.d.ts.map +1 -1
  25. package/dist/src/index.js +3 -1
  26. package/dist/src/intl/_en.d.ts +24 -1
  27. package/dist/src/intl/_en.d.ts.map +1 -1
  28. package/dist/src/intl/_en.js +20 -0
  29. package/dist/src/intl/ar.d.ts +24 -1
  30. package/dist/src/intl/ar.d.ts.map +1 -1
  31. package/dist/src/intl/ar.js +20 -0
  32. package/dist/src/intl/bn.d.ts +24 -1
  33. package/dist/src/intl/bn.d.ts.map +1 -1
  34. package/dist/src/intl/bn.js +20 -0
  35. package/dist/src/intl/ca.d.ts +24 -1
  36. package/dist/src/intl/ca.d.ts.map +1 -1
  37. package/dist/src/intl/ca.js +20 -0
  38. package/dist/src/intl/cs.d.ts +24 -1
  39. package/dist/src/intl/cs.d.ts.map +1 -1
  40. package/dist/src/intl/cs.js +20 -0
  41. package/dist/src/intl/da.d.ts +24 -1
  42. package/dist/src/intl/da.d.ts.map +1 -1
  43. package/dist/src/intl/da.js +20 -0
  44. package/dist/src/intl/de.d.ts +24 -1
  45. package/dist/src/intl/de.d.ts.map +1 -1
  46. package/dist/src/intl/de.js +20 -0
  47. package/dist/src/intl/el.d.ts +24 -1
  48. package/dist/src/intl/el.d.ts.map +1 -1
  49. package/dist/src/intl/el.js +20 -0
  50. package/dist/src/intl/es.d.ts +24 -1
  51. package/dist/src/intl/es.d.ts.map +1 -1
  52. package/dist/src/intl/es.js +20 -0
  53. package/dist/src/intl/fa.d.ts +24 -1
  54. package/dist/src/intl/fa.d.ts.map +1 -1
  55. package/dist/src/intl/fa.js +20 -0
  56. package/dist/src/intl/fi.d.ts +24 -1
  57. package/dist/src/intl/fi.d.ts.map +1 -1
  58. package/dist/src/intl/fi.js +20 -0
  59. package/dist/src/intl/fil.d.ts +24 -1
  60. package/dist/src/intl/fil.d.ts.map +1 -1
  61. package/dist/src/intl/fil.js +20 -0
  62. package/dist/src/intl/fr.d.ts +24 -1
  63. package/dist/src/intl/fr.d.ts.map +1 -1
  64. package/dist/src/intl/fr.js +20 -0
  65. package/dist/src/intl/he.d.ts +24 -1
  66. package/dist/src/intl/he.d.ts.map +1 -1
  67. package/dist/src/intl/he.js +20 -0
  68. package/dist/src/intl/hi.d.ts +24 -1
  69. package/dist/src/intl/hi.d.ts.map +1 -1
  70. package/dist/src/intl/hi.js +20 -0
  71. package/dist/src/intl/hr.d.ts +24 -1
  72. package/dist/src/intl/hr.d.ts.map +1 -1
  73. package/dist/src/intl/hr.js +20 -0
  74. package/dist/src/intl/hu.d.ts +22 -1
  75. package/dist/src/intl/hu.d.ts.map +1 -1
  76. package/dist/src/intl/hu.js +18 -0
  77. package/dist/src/intl/id.d.ts +24 -1
  78. package/dist/src/intl/id.d.ts.map +1 -1
  79. package/dist/src/intl/id.js +20 -0
  80. package/dist/src/intl/it.d.ts +24 -1
  81. package/dist/src/intl/it.d.ts.map +1 -1
  82. package/dist/src/intl/it.js +20 -0
  83. package/dist/src/intl/ja.d.ts +24 -1
  84. package/dist/src/intl/ja.d.ts.map +1 -1
  85. package/dist/src/intl/ja.js +20 -0
  86. package/dist/src/intl/ko.d.ts +24 -1
  87. package/dist/src/intl/ko.d.ts.map +1 -1
  88. package/dist/src/intl/ko.js +20 -0
  89. package/dist/src/intl/ml.d.ts +24 -1
  90. package/dist/src/intl/ml.d.ts.map +1 -1
  91. package/dist/src/intl/ml.js +20 -0
  92. package/dist/src/intl/mr.d.ts +24 -1
  93. package/dist/src/intl/mr.d.ts.map +1 -1
  94. package/dist/src/intl/mr.js +20 -0
  95. package/dist/src/intl/ms.d.ts +24 -1
  96. package/dist/src/intl/ms.d.ts.map +1 -1
  97. package/dist/src/intl/ms.js +20 -0
  98. package/dist/src/intl/nb.d.ts +22 -1
  99. package/dist/src/intl/nb.d.ts.map +1 -1
  100. package/dist/src/intl/nb.js +18 -0
  101. package/dist/src/intl/nl.d.ts +24 -1
  102. package/dist/src/intl/nl.d.ts.map +1 -1
  103. package/dist/src/intl/nl.js +20 -0
  104. package/dist/src/intl/pa.d.ts +24 -1
  105. package/dist/src/intl/pa.d.ts.map +1 -1
  106. package/dist/src/intl/pa.js +20 -0
  107. package/dist/src/intl/pl.d.ts +23 -0
  108. package/dist/src/intl/pl.d.ts.map +1 -1
  109. package/dist/src/intl/pl.js +20 -0
  110. package/dist/src/intl/pt-BR.d.ts +24 -1
  111. package/dist/src/intl/pt-BR.d.ts.map +1 -1
  112. package/dist/src/intl/pt-BR.js +20 -0
  113. package/dist/src/intl/pt.d.ts +24 -1
  114. package/dist/src/intl/pt.d.ts.map +1 -1
  115. package/dist/src/intl/pt.js +20 -0
  116. package/dist/src/intl/ro.d.ts +24 -1
  117. package/dist/src/intl/ro.d.ts.map +1 -1
  118. package/dist/src/intl/ro.js +20 -0
  119. package/dist/src/intl/sk.d.ts +24 -1
  120. package/dist/src/intl/sk.d.ts.map +1 -1
  121. package/dist/src/intl/sk.js +20 -0
  122. package/dist/src/intl/sl.d.ts +24 -1
  123. package/dist/src/intl/sl.d.ts.map +1 -1
  124. package/dist/src/intl/sl.js +20 -0
  125. package/dist/src/intl/sv.d.ts +24 -1
  126. package/dist/src/intl/sv.d.ts.map +1 -1
  127. package/dist/src/intl/sv.js +20 -0
  128. package/dist/src/intl/sw.d.ts +21 -0
  129. package/dist/src/intl/sw.d.ts.map +1 -1
  130. package/dist/src/intl/sw.js +18 -0
  131. package/dist/src/intl/ta.d.ts +24 -1
  132. package/dist/src/intl/ta.d.ts.map +1 -1
  133. package/dist/src/intl/ta.js +20 -0
  134. package/dist/src/intl/te.d.ts +24 -1
  135. package/dist/src/intl/te.d.ts.map +1 -1
  136. package/dist/src/intl/te.js +20 -0
  137. package/dist/src/intl/th.d.ts +24 -1
  138. package/dist/src/intl/th.d.ts.map +1 -1
  139. package/dist/src/intl/th.js +20 -0
  140. package/dist/src/intl/tr.d.ts +24 -1
  141. package/dist/src/intl/tr.d.ts.map +1 -1
  142. package/dist/src/intl/tr.js +20 -0
  143. package/dist/src/intl/uk.d.ts +80 -57
  144. package/dist/src/intl/uk.d.ts.map +1 -1
  145. package/dist/src/intl/uk.js +174 -149
  146. package/dist/src/intl/ur.d.ts +24 -1
  147. package/dist/src/intl/ur.d.ts.map +1 -1
  148. package/dist/src/intl/ur.js +20 -0
  149. package/dist/src/intl/vi.d.ts +24 -1
  150. package/dist/src/intl/vi.d.ts.map +1 -1
  151. package/dist/src/intl/vi.js +20 -0
  152. package/dist/src/intl/zh-CN.d.ts +24 -1
  153. package/dist/src/intl/zh-CN.d.ts.map +1 -1
  154. package/dist/src/intl/zh-CN.js +20 -0
  155. package/dist/src/intl/zh-TW.d.ts +24 -1
  156. package/dist/src/intl/zh-TW.d.ts.map +1 -1
  157. package/dist/src/intl/zh-TW.js +20 -0
  158. package/dist/src/local-first/Protocol.d.ts +1 -1
  159. package/dist/src/local-first/Protocol.d.ts.map +1 -1
  160. package/dist/src/local-first/Protocol.js +1 -1
  161. package/dist/src/local-first/Schema.d.ts +10 -0
  162. package/dist/src/local-first/Schema.d.ts.map +1 -1
  163. package/dist/src/local-first/Storage.js +1 -1
  164. package/dist/src/local-first/Timestamp.js +1 -1
  165. package/package.json +1 -1
  166. package/src/{Binary.test.ts → Bytes.test.ts} +286 -1
  167. package/src/{Binary.ts → Bytes.ts} +652 -21
  168. package/src/Config.test.ts +672 -0
  169. package/src/Config.ts +410 -0
  170. package/src/Fs.test.ts +105 -0
  171. package/src/Fs.ts +414 -0
  172. package/src/Number.test.ts +82 -18
  173. package/src/Number.ts +76 -8
  174. package/src/Object.test.ts +115 -1
  175. package/src/Object.ts +44 -0
  176. package/src/Sqlite.ts +1 -1
  177. package/src/Time.test.ts +82 -11
  178. package/src/Time.ts +141 -16
  179. package/src/Type.test.ts +3114 -157
  180. package/src/Type.ts +2553 -196
  181. package/src/index.ts +3 -1
  182. package/src/intl/_en.ts +70 -0
  183. package/src/intl/ar.ts +71 -0
  184. package/src/intl/bn.ts +70 -0
  185. package/src/intl/ca.ts +70 -0
  186. package/src/intl/cs.ts +70 -0
  187. package/src/intl/da.ts +70 -0
  188. package/src/intl/de.ts +70 -0
  189. package/src/intl/el.ts +70 -0
  190. package/src/intl/es.ts +70 -0
  191. package/src/intl/fa.ts +70 -0
  192. package/src/intl/fi.ts +70 -0
  193. package/src/intl/fil.ts +70 -0
  194. package/src/intl/fr.ts +70 -0
  195. package/src/intl/he.ts +70 -0
  196. package/src/intl/hi.ts +70 -0
  197. package/src/intl/hr.ts +70 -0
  198. package/src/intl/hu.ts +69 -0
  199. package/src/intl/id.ts +70 -0
  200. package/src/intl/intl.test.ts +819 -1
  201. package/src/intl/it.ts +70 -0
  202. package/src/intl/ja.ts +70 -0
  203. package/src/intl/ko.ts +68 -0
  204. package/src/intl/ml.ts +70 -0
  205. package/src/intl/mr.ts +70 -0
  206. package/src/intl/ms.ts +71 -0
  207. package/src/intl/nb.ts +69 -0
  208. package/src/intl/nl.ts +70 -0
  209. package/src/intl/pa.ts +70 -0
  210. package/src/intl/pl.ts +63 -0
  211. package/src/intl/pt-BR.ts +70 -0
  212. package/src/intl/pt.ts +71 -0
  213. package/src/intl/ro.ts +70 -0
  214. package/src/intl/sk.ts +71 -0
  215. package/src/intl/sl.ts +70 -0
  216. package/src/intl/sv.ts +70 -0
  217. package/src/intl/sw.ts +62 -0
  218. package/src/intl/ta.ts +70 -0
  219. package/src/intl/te.ts +70 -0
  220. package/src/intl/th.ts +68 -0
  221. package/src/intl/tr.ts +70 -0
  222. package/src/intl/uk.ts +228 -155
  223. package/src/intl/ur.ts +70 -0
  224. package/src/intl/vi.ts +70 -0
  225. package/src/intl/zh-CN.ts +68 -0
  226. package/src/intl/zh-TW.ts +68 -0
  227. package/src/local-first/Protocol.test.ts +1 -1
  228. package/src/local-first/Protocol.ts +1 -1
  229. package/src/local-first/Schema.ts +11 -0
  230. package/src/local-first/Storage.ts +1 -1
  231. package/src/local-first/Timestamp.ts +1 -1
  232. package/dist/src/Binary.d.ts +0 -254
  233. package/dist/src/Binary.d.ts.map +0 -1
@@ -236,15 +236,22 @@ export interface Type<Name extends TypeName, in out Input, in out Output, Error
236
236
  */
237
237
  readonly to: [Parent] extends [infer P extends TypeNode] ? ToOperation<Output, CanonicalInput, P> : (value: Output) => CanonicalInput;
238
238
  /**
239
- * Shorthand for calling {@link getOrThrow} with the result of the deepest
240
- * `from` operation, which accepts this Type's `Input`.
239
+ * Runs the deepest `from` operation, which accepts this Type's `Input`, and
240
+ * returns its decoded value or throws an Error.
241
+ *
242
+ * Returned validation errors use this Type's {@link Type.formatError} as the
243
+ * Error message and preserve the original validation error as `cause`. With
244
+ * `{ errors: "all" }`, the cause retains all collected errors; the message
245
+ * still follows `formatError`, which describes the first issue.
241
246
  *
242
247
  * The typed `Input` boundary is asserted before the remaining pipeline runs.
243
- * A boundary violation is a bug, so it throws directly; `getOrThrow` maps
244
- * only a validation error returned after that boundary.
248
+ * A boundary violation is a bug, so it throws directly with its assertion
249
+ * message instead of formatting a returned validation error.
245
250
  *
246
251
  * `Type.orThrow.parent(value)` does not exist. To throw after starting from a
247
- * typed boundary, call `getOrThrow` with the corresponding `from` operation.
252
+ * typed boundary, call {@link getOrThrow} with the corresponding `from`
253
+ * operation. Its generic Error message differs from this operation's
254
+ * message.
248
255
  *
249
256
  * Use `orThrow` for startup and configuration, module constants, test
250
257
  * fixtures, and internal invariants where failure must stop the current flow.
@@ -256,20 +263,31 @@ export interface Type<Name extends TypeName, in out Input, in out Output, Error
256
263
  * ```ts
257
264
  * import {
258
265
  * assertEqual,
259
- * getOrThrow,
266
+ * assertInstanceOf,
267
+ * assertErr,
260
268
  * minLength,
261
269
  * String,
270
+ * trySync,
262
271
  * } from "@evolu/common";
263
272
  *
264
273
  * const NonEmptyString = minLength(1)(String);
265
274
  *
266
275
  * const value = NonEmptyString.orThrow("Evolu");
267
276
  *
268
- * // Equivalent because `from.parent` is this Type's deepest `from` operation:
269
- * const sameValue = getOrThrow(NonEmptyString.from.parent("Evolu"));
270
- *
271
277
  * assertEqual(value, "Evolu");
272
- * assertEqual(sameValue, value);
278
+ *
279
+ * const failed = trySync(() => NonEmptyString.orThrow(""));
280
+ * assertErr(failed);
281
+ * assertInstanceOf(failed.error, Error);
282
+ * assertEqual(
283
+ * failed.error.message,
284
+ * 'The value "" does not meet the minimum length of 1.',
285
+ * );
286
+ * assertEqual(failed.error.cause, {
287
+ * type: "MinLength1",
288
+ * value: "",
289
+ * min: 1,
290
+ * });
273
291
  * ```
274
292
  */
275
293
  readonly orThrow: TypeOperationFn<"orThrow", Input, Output, never>;
@@ -355,6 +373,50 @@ export interface TypeValueError<Name extends TypeName = TypeName> extends TypeEr
355
373
  * @group Core
356
374
  */
357
375
  export type TypeErrorFormatter<Error extends TypeError> = (error: Error) => string;
376
+ /**
377
+ * A formatted validation issue located by its path from the root value.
378
+ *
379
+ * @group Core
380
+ */
381
+ export interface TypeIssue {
382
+ readonly path: ReadonlyArray<PropertyKey>;
383
+ readonly message: string;
384
+ }
385
+ /**
386
+ * Converts an error from a {@link Type} into formatted issues with paths.
387
+ *
388
+ * Pass the Type that produced the error. This uses its nested and localized
389
+ * formatters without validating the input again. To retain every issue, decode
390
+ * with `{ errors: "all" }`; this function cannot recover errors omitted during
391
+ * validation. A root issue has an empty path.
392
+ *
393
+ * ### Example
394
+ *
395
+ * ```ts
396
+ * import {
397
+ * assertEqual,
398
+ * assertErr,
399
+ * IntFromString,
400
+ * object,
401
+ * typeErrorToIssues,
402
+ * } from "@evolu/common";
403
+ *
404
+ * const Settings = object({ port: IntFromString });
405
+ *
406
+ * const result = Settings.fromUnknown({ port: "http" }, { errors: "all" });
407
+ * assertErr(result);
408
+ *
409
+ * assertEqual(typeErrorToIssues(Settings, result.error), [
410
+ * {
411
+ * path: ["port"],
412
+ * message: 'The value "http" is not a decimal integer.',
413
+ * },
414
+ * ]);
415
+ * ```
416
+ *
417
+ * @group Core
418
+ */
419
+ export declare const typeErrorToIssues: <T extends TypeNode>(type: T, error: InferErrors<NoInfer<T>>) => NonEmptyReadonlyArray<TypeIssue>;
358
420
  /**
359
421
  * The common structural shape of every {@link Type}, with its specific type
360
422
  * parameters erased.
@@ -379,6 +441,21 @@ export interface TypeNode {
379
441
  readonly fromUnknown: (value: unknown, options?: ValidationOptions) => Result<unknown, TypeError>;
380
442
  readonly is: (value: unknown) => boolean;
381
443
  }
444
+ declare const outputValidationSymbolType: unique symbol;
445
+ declare const getRuntimeTypeIssuesSymbolType: unique symbol;
446
+ type RuntimeOutputValidation = (value: unknown, options?: ValidationOptions) => Result<unknown, TypeError>;
447
+ interface RuntimeTypeIssue {
448
+ readonly name: TypeName;
449
+ readonly error: TypeError;
450
+ readonly path: ReadonlyArray<PropertyKey>;
451
+ readonly formatError: TypeErrorFormatter<TypeError>;
452
+ readonly alternatives?: ReadonlyArray<{
453
+ readonly index: number;
454
+ readonly name: TypeName;
455
+ readonly issues: NonEmptyReadonlyArray<RuntimeTypeIssue>;
456
+ }>;
457
+ }
458
+ type RuntimeGetTypeIssues = (error: TypeError, mode: ValidationOptions["errors"]) => NonEmptyReadonlyArray<RuntimeTypeIssue>;
382
459
  /**
383
460
  * Asserts type equality or validates a value with a {@link Type}.
384
461
  *
@@ -435,6 +512,11 @@ export declare function assertType<T extends TypeNode>(type: T, value: unknown):
435
512
  * that produced them. Different localized Type sets can coexist in separate
436
513
  * application or dependency-injection scopes.
437
514
  *
515
+ * A Union formatter supplies the summary of the failure. Retained member
516
+ * failures are appended using their own localized formatters, with member
517
+ * indexes, Type names, and paths identifying each alternative. The Union still
518
+ * produces one issue at its enclosing path.
519
+ *
438
520
  * Localization is scoped to the selected Types instead of a package-wide
439
521
  * translation registry. Static imports give bundlers an explicit dependency
440
522
  * graph, so unrelated Types, locales, and formatters can be removed. Bundling
@@ -655,7 +737,7 @@ type ReflectedTypes<T extends TypeNode> = typeof reflectedTypesSymbol extends ke
655
737
  readonly name: "Object";
656
738
  readonly props: infer Props extends ObjectProps;
657
739
  readonly record?: infer Rest;
658
- } ? ObjectPropertyType<Props[keyof Props]> | Extract<Rest, TypeNode> : never;
740
+ } ? DefaultableObjectPropertyType<Props[keyof Props]> | Extract<Rest, TypeNode> : never;
659
741
  type RootType<T extends TypeNode> = T extends {
660
742
  readonly parent: infer Parent extends TypeNode;
661
743
  } ? RootType<Parent> : T;
@@ -716,7 +798,42 @@ interface ValidationFailure<Error> {
716
798
  readonly [validationFailureSymbol]: Error;
717
799
  }
718
800
  type ValidateParent<T extends ConcreteTypeNode> = IsUnion<T> extends false ? T : CompileTimeError<"Type", "Parent must be one concrete Type node. Pass a Union Type node instead of a union of Type nodes.">;
719
- type ValidateOutput<T extends ConcreteTypeNode> = IsUnion<T> extends false ? T : CompileTimeError<"Type", "Output Type must be one concrete Type node. Pass a Union Type node instead of a union of Type nodes.">;
801
+ /**
802
+ * Requires one concrete output {@link Type} node in a factory parameter.
803
+ *
804
+ * Rejects TypeScript unions of Type nodes. A {@link union} Type is one concrete
805
+ * node and remains valid. Use an intersection with the inferred parameter type
806
+ * to preserve its specific Type. This guard performs no runtime validation.
807
+ *
808
+ * ### Example
809
+ *
810
+ * ```ts
811
+ * import {
812
+ * assertSame,
813
+ * Number,
814
+ * String,
815
+ * union,
816
+ * type AnyType,
817
+ * type ValidateOutput,
818
+ * } from "@evolu/common";
819
+ *
820
+ * const defineOutput = <T extends AnyType>(
821
+ * type: T & ValidateOutput<T>,
822
+ * ): T => type;
823
+ *
824
+ * assertSame(defineOutput(String), String);
825
+ *
826
+ * const Value = union(String, Number);
827
+ * assertSame(defineOutput(Value), Value);
828
+ *
829
+ * const uncertain = String as typeof String | typeof Number;
830
+ * // @ts-expect-error Output Type must be one concrete Type node. Pass a Union Type node instead of a union of Type nodes.
831
+ * defineOutput(uncertain);
832
+ * ```
833
+ *
834
+ * @group Construction
835
+ */
836
+ export type ValidateOutput<T extends AnyType> = IsUnion<T> extends false ? T : CompileTimeError<"Type", "Output Type must be one concrete Type node. Pass a Union Type node instead of a union of Type nodes.">;
720
837
  type ValidateConcreteTypeName<Name extends TypeName> = IsTypeNameUnion<Name> extends false ? {} extends Readonly<Record<Name, never>> ? ConcreteChildTypeNameError : Name : ConcreteChildTypeNameError;
721
838
  type ValidateChildTypeName<Name extends TypeName, ParentType extends TypeNode> = [ChildTypeNameValidationError<Name, ParentType>] extends [never] ? Name : ChildTypeNameValidationError<Name, ParentType>;
722
839
  type ChildTypeNameValidationError<Name extends TypeName, ParentType extends TypeNode> = IsTypeNameUnion<Name> extends true ? ConcreteChildTypeNameError : {} extends Readonly<Record<Name, never>> ? ConcreteChildTypeNameError : Name extends InferErrors<ParentType>["type"] ? ChildTypeNameCollisionError : never;
@@ -748,6 +865,8 @@ type ConcreteChildTypeNameError = CompileTimeError<"Type", "Name must be one con
748
865
  * its error's `type` must equal that name and must not duplicate an inherited
749
866
  * error type. An infallible child has no own error to format.
750
867
  *
868
+ * Use {@link createTypeWithError} to wrap an existing validator's errors.
869
+ *
751
870
  * ### Example
752
871
  *
753
872
  * A root Type for a custom external value category:
@@ -778,6 +897,7 @@ type ConcreteChildTypeNameError = CompileTimeError<"Type", "Name must be one con
778
897
  * );
779
898
  *
780
899
  * assertOk(Text.fromUnknown("Evolu"), "Evolu");
900
+ *
781
901
  * const invalid = Text.fromUnknown(42);
782
902
  * assertErr(invalid);
783
903
  * assertType(Data, invalid.error);
@@ -791,6 +911,48 @@ export declare function createType<Name extends TypeName, Output, Error extends
791
911
  export declare function createType<Name extends TypeName, ParentType extends ConcreteTypeNode>(name: ValidateConcreteTypeName<Name>, parent: ValidateParent<ParentType>, fromParent: (value: ParentType["Output"]) => Result<ParentType["Output"], never>): Type<Name, ParentType["Input"], ParentType["Output"], never, ParentType, InferErrors<ParentType>, ChildCustomFrom<ParentType, ParentType["Output"], never>, CanonicalInputForChild<ParentType, ParentType["Output"]>, IdentityEncodingOf<ParentType>>;
792
912
  /** Creates a fallible child Type that narrows its parent's Output. */
793
913
  export declare function createType<Name extends TypeName, ParentType extends ConcreteTypeNode, Output extends ParentType["Output"], Error extends TypeError<Name>>(name: Name, parent: ValidateBrandParent<Name, ParentType>, fromParent: (value: ParentType["Output"]) => Result<Output, Error>, formatError: [Error] extends [never] ? never : TypeErrorFormatter<NoInfer<Error>>): Type<Name, ParentType["Input"], Output, Error, ParentType, Error | InferErrors<ParentType>, ChildCustomFrom<ParentType, Output, Error>, CanonicalInputForChild<ParentType, Output>, IdentityEncodingOf<ParentType>>;
914
+ /**
915
+ * Creates a root {@link Type} with a custom error for an existing validator.
916
+ *
917
+ * The source must use identity encoding. The new Type accepts its Output as
918
+ * Input and hides its parent boundaries. Validation and error collection are
919
+ * delegated automatically; the mapper receives the failure and original value.
920
+ * The formatter presents the mapped error as one issue.
921
+ *
922
+ * ### Example
923
+ *
924
+ * ```ts
925
+ * import {
926
+ * assertEqual,
927
+ * assertErr,
928
+ * createTypeWithError,
929
+ * Number,
930
+ * String,
931
+ * union,
932
+ * type TypeError,
933
+ * type UnionError,
934
+ * } from "@evolu/common";
935
+ *
936
+ * interface ValueError extends TypeError<"Value"> {
937
+ * readonly cause: UnionError;
938
+ * }
939
+ *
940
+ * const Value = createTypeWithError(
941
+ * "Value",
942
+ * union(String, Number),
943
+ * (cause): ValueError => ({ type: "Value", cause }),
944
+ * () => "Enter text or a number.",
945
+ * );
946
+ *
947
+ * const result = Value.fromUnknown(false, { errors: "all" });
948
+ * assertErr(result);
949
+ * assertEqual(result.error.cause.errors.length, 2);
950
+ * assertEqual(Value.formatError(result.error), "Enter text or a number.");
951
+ * ```
952
+ *
953
+ * @group Construction
954
+ */
955
+ export declare const createTypeWithError: <Name extends TypeName, T extends ConcreteTypeNode, Error extends TypeError<Name>>(name: Name & ValidateConcreteTypeName<Name>, type: T & ValidateOutput<T> & (IdentityEncodingOf<T> extends true ? unknown : CompileTimeError<"Type", "Source Type must use identity encoding.">), mapError: (error: InferErrors<T>, value: unknown) => Error, formatError: TypeErrorFormatter<NoInfer<Error>>) => Type<Name, T["Output"], T["Output"], Error>;
794
956
  /**
795
957
  * Transform {@link Type}.
796
958
  *
@@ -943,8 +1105,33 @@ type DeepestFromError<Operation> = DeepestFromOperation<Operation> extends (...a
943
1105
  readonly error: infer Error extends TypeError;
944
1106
  } ? Error : never : never;
945
1107
  type TypeFromError<T extends TypeNode> = unknown extends T[typeof customFromSymbol] ? DeepestFromError<TypeFromOperation<T>> : [T[typeof customFromSymbol]] extends [never] ? NonRootErrors<T> : DeepestFromError<T[typeof customFromSymbol]>;
1108
+ type RuntimeOperationFn<Value> = (value: never, options?: ValidationOptions) => Value;
1109
+ type RuntimeOperation<Value> = RuntimeOperationFn<Value> & {
1110
+ parent?: RuntimeOperation<Value>;
1111
+ };
1112
+ type RuntimeEncoder = RuntimeOperation<unknown>;
1113
+ declare const encoderSymbolType: unique symbol;
1114
+ declare const fromSymbolType: unique symbol;
946
1115
  declare const templateLiteralSyntaxSymbolType: unique symbol;
947
1116
  declare const templateLiteralSyntaxSymbol: typeof templateLiteralSyntaxSymbolType;
1117
+ /**
1118
+ * Type-erased {@link Type} used to traverse and invoke heterogeneous Type nodes
1119
+ * at runtime.
1120
+ */
1121
+ type RuntimeTypeNode = Omit<TypeNode, typeof customFromSymbol> & {
1122
+ readonly [customFromSymbol]: never;
1123
+ readonly fromUnknown: (value: unknown, options: ValidationOptions) => Result<unknown, TypeError>;
1124
+ readonly formatError: TypeErrorFormatter<TypeError>;
1125
+ readonly from: RuntimeOperation<Result<unknown, TypeError>>;
1126
+ readonly to: RuntimeEncoder;
1127
+ readonly orThrow: RuntimeOperation<unknown>;
1128
+ readonly orNull: RuntimeOperation<unknown>;
1129
+ readonly [outputValidationSymbolType]: RuntimeOutputValidation;
1130
+ readonly [fromSymbolType]: RuntimeOperation<Result<unknown, TypeError>>;
1131
+ readonly [encoderSymbolType]: RuntimeEncoder;
1132
+ readonly [getRuntimeTypeIssuesSymbolType]: RuntimeGetTypeIssues;
1133
+ readonly [templateLiteralSyntaxSymbolType]?: true;
1134
+ };
948
1135
  /**
949
1136
  * An infallible {@link Type} accepting every value.
950
1137
  *
@@ -1082,6 +1269,7 @@ export declare const String: Type<"String", string, string, TypeOfError<"String"
1082
1269
  * >();
1083
1270
  *
1084
1271
  * assertOk(Age.fromUnknown(122), 122);
1272
+ *
1085
1273
  * const invalid = Age.fromUnknown(200);
1086
1274
  * assertErr(invalid);
1087
1275
  * assertType(Data, invalid.error);
@@ -1107,6 +1295,47 @@ export declare const BigInt: Type<"BigInt", bigint, bigint, TypeOfError<"BigInt"
1107
1295
  * @group Base
1108
1296
  */
1109
1297
  export declare const Boolean: Type<"Boolean", boolean, boolean, TypeOfError<"Boolean">, null, TypeOfError<"Boolean">, never, boolean, true>;
1298
+ /**
1299
+ * Error returned when a string is neither `true` nor `false`.
1300
+ *
1301
+ * @group Base
1302
+ */
1303
+ export interface BooleanFromStringError extends TypeError<"BooleanFromString"> {
1304
+ readonly value: string;
1305
+ }
1306
+ /**
1307
+ * Transforms a boolean spelled as text into a {@link Boolean}.
1308
+ *
1309
+ * This is useful for inputs that carry booleans as text, such as environment
1310
+ * variables, URL query parameters, and form fields. Exactly `true` and `false`
1311
+ * are accepted, the spellings JSON and JavaScript use, so a boolean has one
1312
+ * representation in every source.
1313
+ *
1314
+ * ### Example
1315
+ *
1316
+ * ```ts
1317
+ * import {
1318
+ * assertEqual,
1319
+ * assertErr,
1320
+ * assertOk,
1321
+ * BooleanFromString,
1322
+ * } from "@evolu/common";
1323
+ *
1324
+ * assertOk(BooleanFromString.fromUnknown("true"), true);
1325
+ * assertOk(BooleanFromString.fromUnknown("false"), false);
1326
+ * assertEqual(BooleanFromString.to(true), "true");
1327
+ *
1328
+ * const invalid = BooleanFromString.fromUnknown("yes");
1329
+ * assertErr(invalid, { type: "BooleanFromString", value: "yes" });
1330
+ * assertEqual(
1331
+ * BooleanFromString.formatError(invalid.error),
1332
+ * 'The value "yes" is not a boolean. Use true or false.',
1333
+ * );
1334
+ * ```
1335
+ *
1336
+ * @group Base
1337
+ */
1338
+ export declare const BooleanFromString: TransformType<Type<"String", string, string, TypeOfError<"String">, null, TypeOfError<"String">, never, string, true>, Type<"Boolean", boolean, boolean, TypeOfError<"Boolean">, null, TypeOfError<"Boolean">, never, boolean, true>, "BooleanFromString", BooleanFromStringError, "false" | "true">;
1110
1339
  /**
1111
1340
  * A JavaScript symbol {@link Type}.
1112
1341
  *
@@ -1236,6 +1465,7 @@ export declare function objectTag<Name extends keyof ObjectTagOutputByName>(name
1236
1465
  * "TaggedValue",
1237
1466
  * instanceOf(TaggedValue),
1238
1467
  * );
1468
+ *
1239
1469
  * const value = new TaggedValue();
1240
1470
  * const result = TaggedValueType.fromUnknown(value);
1241
1471
  *
@@ -1338,6 +1568,34 @@ export interface InstanceOfError extends TypeValueError<"InstanceOf"> {
1338
1568
  }
1339
1569
  type ValidateInstanceConstructor<Constructor extends InstanceConstructor> = IsUnion<Constructor> extends false ? "prototype" extends keyof Constructor ? Constructor : InstanceConstructorCompileTimeError : InstanceConstructorCompileTimeError;
1340
1570
  type InstanceConstructorCompileTimeError = CompileTimeError<"Type", "Constructor must preserve one concrete constructor. Create a Union Type from separate Instance Types instead of passing a union or erased constructor.">;
1571
+ /**
1572
+ * Requires one concrete literal type in a factory parameter.
1573
+ *
1574
+ * Preserves an exact literal and produces a compile-time error for widened,
1575
+ * union, branded, or open template literal types. Use an intersection with the
1576
+ * inferred parameter type, as {@link prefixed} does. This guard performs no
1577
+ * runtime validation.
1578
+ *
1579
+ * ### Example
1580
+ *
1581
+ * ```ts
1582
+ * import { assertType, type ValidateLiteral } from "@evolu/common";
1583
+ *
1584
+ * const definePrefix = <Prefix extends string>(
1585
+ * prefix: Prefix & ValidateLiteral<Prefix>,
1586
+ * ): Prefix => prefix;
1587
+ *
1588
+ * const prefix = definePrefix("APP_");
1589
+ * assertType<typeof prefix, "APP_">();
1590
+ *
1591
+ * const widened: string = "APP_";
1592
+ * // @ts-expect-error Expected must be one concrete literal value.
1593
+ * definePrefix(widened);
1594
+ * ```
1595
+ *
1596
+ * @group Unions
1597
+ */
1598
+ export type ValidateLiteral<Expected extends Literal> = IsUnion<Expected> extends false ? {} extends Readonly<Record<`${Expected}`, never>> ? LiteralCompileTimeError : Expected : LiteralCompileTimeError;
1341
1599
  /**
1342
1600
  * Literal {@link Type}.
1343
1601
  *
@@ -1366,6 +1624,7 @@ type InstanceConstructorCompileTimeError = CompileTimeError<"Type", "Constructor
1366
1624
  *
1367
1625
  * assertType<typeof Ready.Output, "ready">();
1368
1626
  * assertOk(Ready.fromUnknown("ready"), "ready");
1627
+ *
1369
1628
  * const invalid = Ready.fromUnknown("pending");
1370
1629
  * assertErr(invalid);
1371
1630
  * assertType(Data, invalid.error);
@@ -1390,7 +1649,6 @@ export interface LiteralType<Expected extends Literal> extends Type<"Literal", W
1390
1649
  }
1391
1650
  type LiteralParent<Expected extends Literal> = Expected extends string ? typeof String : Expected extends number ? typeof Number : Expected extends bigint ? typeof BigInt : Expected extends boolean ? typeof Boolean : null;
1392
1651
  type LiteralParentErrors<Expected extends Literal> = LiteralParent<Expected> extends infer Parent extends TypeNode ? InferErrors<Parent> : never;
1393
- type ValidateLiteral<Expected extends Literal> = IsUnion<Expected> extends false ? {} extends Readonly<Record<`${Expected}`, never>> ? LiteralCompileTimeError : Expected : LiteralCompileTimeError;
1394
1652
  type LiteralCompileTimeError = CompileTimeError<"Type", "Expected must be one concrete literal value.">;
1395
1653
  /**
1396
1654
  * Error returned when a value does not equal the expected literal.
@@ -1435,6 +1693,11 @@ export declare const Null: LiteralType<null>;
1435
1693
  * errors: "all" }` to retain every member failure and collect nested errors
1436
1694
  * within each member.
1437
1695
  *
1696
+ * Formatting keeps one Union issue at the enclosing path and appends the
1697
+ * retained failures below its summary. Member indexes identify alternatives,
1698
+ * not positions in the input. Nested member paths appear in the message.
1699
+ * Formatting never retries validation or recovers discarded failures.
1700
+ *
1438
1701
  * Member order matters when multiple members accept the same value: validation
1439
1702
  * and encoding use the first matching member. When member Inputs overlap,
1440
1703
  * decoding the value emitted by the first member selected for an Output must
@@ -1683,6 +1946,7 @@ interface UnionErrorValue<Error extends TypeError, MemberError extends UnionMemb
1683
1946
  * assertOk(result, ["cs", "CZ"]);
1684
1947
  * const locale = result.value;
1685
1948
  * assertType<typeof locale, SupportedLocale>();
1949
+ *
1686
1950
  * const invalid = SupportedLocale.fromUnknown("cs/CZ");
1687
1951
  * assertErr(invalid);
1688
1952
  * assertType(Data, invalid.error);
@@ -1960,6 +2224,7 @@ type TemplateLiteralStringWidth<Value extends string, Width extends ReadonlyArra
1960
2224
  * }
1961
2225
  *
1962
2226
  * assertOk(Int64.fromUnknown(42n), 42n);
2227
+ *
1963
2228
  * const invalid = Int64.fromUnknown(2n ** 63n);
1964
2229
  * assertErr(invalid);
1965
2230
  * assertType(Data, invalid.error);
@@ -2009,6 +2274,7 @@ export interface BrandType<ParentType extends TypeNode, Name extends TypeName, E
2009
2274
  *
2010
2275
  * const value = "2023-01-01T12:00:00.000Z";
2011
2276
  * assertOk(DateIso.fromUnknown(value), value);
2277
+ *
2012
2278
  * const invalid = DateIso.fromUnknown("2023-01-01");
2013
2279
  * assertErr(invalid);
2014
2280
  * assertType(Data, invalid.error);
@@ -2135,6 +2401,7 @@ export interface UInt64Error extends TypeError<"UInt64"> {
2135
2401
  * }
2136
2402
  *
2137
2403
  * assertOk(TrimmedString.fromUnknown("Evolu"), "Evolu");
2404
+ *
2138
2405
  * const invalid = TrimmedString.fromUnknown(" Evolu");
2139
2406
  * assertErr(invalid);
2140
2407
  * assertType(Data, invalid.error);
@@ -2216,57 +2483,643 @@ export type BrandFactory<Name extends TypeName, Value, Error extends TypeError<N
2216
2483
  export type ValidateBrandFactoryNumber<Value extends number> = IsUnion<Value> extends false ? {} extends Record<`${Value}`, never> ? Value & Readonly<Record<BrandFactoryNumberError, never>> : Value : Value & Readonly<Record<BrandFactoryNumberError, never>>;
2217
2484
  type BrandFactoryNumberError = CompileTimeError<"Brand Factory", "Parameter must be one concrete numeric literal instead of a widened, union, or branded number.">;
2218
2485
  /**
2219
- * Capitalized {@link Brand}.
2486
+ * Naming conventions accepted by {@link identifier}.
2220
2487
  *
2221
- * Requires the first character of a string to be uppercase.
2488
+ * @group String
2489
+ */
2490
+ export type IdentifierCasing = "camelCase" | "PascalCase" | "snake_case" | "kebab-case" | "CONSTANT_CASE";
2491
+ /**
2492
+ * Brand names of the Types that {@link identifier} creates, by casing.
2493
+ *
2494
+ * @group String
2495
+ */
2496
+ export interface IdentifierBrandByCasing {
2497
+ readonly camelCase: "CamelCaseIdentifier";
2498
+ readonly PascalCase: "PascalCaseIdentifier";
2499
+ readonly snake_case: "SnakeCaseIdentifier";
2500
+ readonly "kebab-case": "KebabCaseIdentifier";
2501
+ readonly CONSTANT_CASE: "ConstantCaseIdentifier";
2502
+ }
2503
+ /**
2504
+ * Adds identifier validation in a naming convention to an existing string Type.
2505
+ *
2506
+ * An identifier is one or more ASCII words, each starting with a letter and
2507
+ * continuing with letters or digits. camelCase and PascalCase start every word
2508
+ * after the first with an uppercase letter, so `httpUrl` has two words and
2509
+ * `httpURL` has four. snake_case, kebab-case, and CONSTANT_CASE put exactly one
2510
+ * separator between words. Validation keeps the spelling and rejects empty
2511
+ * strings, whitespace, punctuation, and non-ASCII characters.
2512
+ *
2513
+ * Words never start with a digit. Identifier grammars in most languages forbid
2514
+ * a leading digit, and camelCase cannot mark a word boundary before one, so the
2515
+ * rule applies to every word and keeps every conversion exact. Join an
2516
+ * abbreviation such as `2FA` to the previous word, as in `MAX2FA_ATTEMPTS` and
2517
+ * `max2faAttempts`, or spell the number out, as in `TWO_FACTOR_SECRET`.
2518
+ *
2519
+ * Convert validated identifiers with functions such as
2520
+ * {@link camelCaseToSnakeCase}. Conversions preserve word boundaries, and
2521
+ * converting back restores the original spelling. They return only the
2522
+ * destination brand, dropping unrelated constraints such as input length.
2222
2523
  *
2223
2524
  * ### Example
2224
2525
  *
2225
2526
  * ```ts
2226
2527
  * import {
2227
- * assertEqual,
2228
2528
  * assertErr,
2229
2529
  * assertOk,
2230
- * assertType,
2231
- * Data,
2530
+ * identifier,
2531
+ * maxLength,
2232
2532
  * String,
2533
+ * } from "@evolu/common";
2534
+ *
2535
+ * const EnvName = identifier("CONSTANT_CASE")(maxLength(16)(String));
2536
+ *
2537
+ * assertOk(EnvName.fromUnknown("HTTP2_PORT"), "HTTP2_PORT");
2538
+ * assertErr(EnvName.fromUnknown("HTTP_2_PORT"));
2539
+ * ```
2540
+ *
2541
+ * @group String
2542
+ */
2543
+ export declare const identifier: <Casing extends IdentifierCasing>(casing: Casing) => BrandFactory<IdentifierBrandByCasing[Casing], string, IdentifierError<Casing>>;
2544
+ /**
2545
+ * Error returned when {@link identifier} rejects a string.
2546
+ *
2547
+ * @group String
2548
+ */
2549
+ export interface IdentifierError<Casing extends IdentifierCasing = IdentifierCasing> extends TypeError<IdentifierBrandByCasing[Casing]> {
2550
+ readonly value: string;
2551
+ readonly casing: Casing;
2552
+ }
2553
+ /**
2554
+ * A validated camelCase identifier, such as `http2Port`.
2555
+ *
2556
+ * See {@link identifier} for the grammar and the conversion functions.
2557
+ *
2558
+ * @group String
2559
+ */
2560
+ export declare const CamelCaseIdentifier: BrandType<Type<"String", string, string, TypeOfError<"String">, null, TypeOfError<"String">, never, string, true>, "CamelCaseIdentifier", IdentifierError<"camelCase">>;
2561
+ export type CamelCaseIdentifier = typeof CamelCaseIdentifier.Output;
2562
+ /**
2563
+ * A validated PascalCase identifier, such as `Http2Port`.
2564
+ *
2565
+ * See {@link identifier} for the grammar and the conversion functions.
2566
+ *
2567
+ * @group String
2568
+ */
2569
+ export declare const PascalCaseIdentifier: BrandType<Type<"String", string, string, TypeOfError<"String">, null, TypeOfError<"String">, never, string, true>, "PascalCaseIdentifier", IdentifierError<"PascalCase">>;
2570
+ export type PascalCaseIdentifier = typeof PascalCaseIdentifier.Output;
2571
+ /**
2572
+ * A validated snake_case identifier, such as `http2_port`.
2573
+ *
2574
+ * See {@link identifier} for the grammar and the conversion functions.
2575
+ *
2576
+ * @group String
2577
+ */
2578
+ export declare const SnakeCaseIdentifier: BrandType<Type<"String", string, string, TypeOfError<"String">, null, TypeOfError<"String">, never, string, true>, "SnakeCaseIdentifier", IdentifierError<"snake_case">>;
2579
+ export type SnakeCaseIdentifier = typeof SnakeCaseIdentifier.Output;
2580
+ /**
2581
+ * A validated kebab-case identifier, such as `http2-port`.
2582
+ *
2583
+ * See {@link identifier} for the grammar and the conversion functions.
2584
+ *
2585
+ * @group String
2586
+ */
2587
+ export declare const KebabCaseIdentifier: BrandType<Type<"String", string, string, TypeOfError<"String">, null, TypeOfError<"String">, never, string, true>, "KebabCaseIdentifier", IdentifierError<"kebab-case">>;
2588
+ export type KebabCaseIdentifier = typeof KebabCaseIdentifier.Output;
2589
+ /**
2590
+ * A validated CONSTANT_CASE identifier, such as `HTTP2_PORT`.
2591
+ *
2592
+ * See {@link identifier} for the grammar and the conversion functions.
2593
+ *
2594
+ * @group String
2595
+ */
2596
+ export declare const ConstantCaseIdentifier: BrandType<Type<"String", string, string, TypeOfError<"String">, null, TypeOfError<"String">, never, string, true>, "ConstantCaseIdentifier", IdentifierError<"CONSTANT_CASE">>;
2597
+ export type ConstantCaseIdentifier = typeof ConstantCaseIdentifier.Output;
2598
+ /**
2599
+ * Converts a {@link CamelCaseIdentifier} to a {@link PascalCaseIdentifier}.
2600
+ *
2601
+ * Converts `http2Port` to `Http2Port`. Use {@link pascalCaseToCamelCase} to
2602
+ * recover the original spelling.
2603
+ *
2604
+ * @group String
2605
+ */
2606
+ export declare const camelCaseToPascalCase: (value: CamelCaseIdentifier) => PascalCaseIdentifier;
2607
+ /**
2608
+ * Converts a {@link CamelCaseIdentifier} to a {@link SnakeCaseIdentifier}.
2609
+ *
2610
+ * Converts `http2Port` to `http2_port`. Use {@link snakeCaseToCamelCase} to
2611
+ * recover the original spelling.
2612
+ *
2613
+ * @group String
2614
+ */
2615
+ export declare const camelCaseToSnakeCase: (value: CamelCaseIdentifier) => SnakeCaseIdentifier;
2616
+ /**
2617
+ * Converts a {@link CamelCaseIdentifier} to a {@link KebabCaseIdentifier}.
2618
+ *
2619
+ * Converts `http2Port` to `http2-port`. Use {@link kebabCaseToCamelCase} to
2620
+ * recover the original spelling.
2621
+ *
2622
+ * @group String
2623
+ */
2624
+ export declare const camelCaseToKebabCase: (value: CamelCaseIdentifier) => KebabCaseIdentifier;
2625
+ /**
2626
+ * Converts a {@link CamelCaseIdentifier} to a {@link ConstantCaseIdentifier}.
2627
+ *
2628
+ * Converts `http2Port` to `HTTP2_PORT`. Use {@link constantCaseToCamelCase} to
2629
+ * recover the original spelling.
2630
+ *
2631
+ * @group String
2632
+ */
2633
+ export declare const camelCaseToConstantCase: (value: CamelCaseIdentifier) => ConstantCaseIdentifier;
2634
+ /**
2635
+ * Converts a {@link PascalCaseIdentifier} to a {@link CamelCaseIdentifier}.
2636
+ *
2637
+ * Converts `Http2Port` to `http2Port`. Use {@link camelCaseToPascalCase} to
2638
+ * recover the original spelling.
2639
+ *
2640
+ * @group String
2641
+ */
2642
+ export declare const pascalCaseToCamelCase: (value: PascalCaseIdentifier) => CamelCaseIdentifier;
2643
+ /**
2644
+ * Converts a {@link PascalCaseIdentifier} to a {@link SnakeCaseIdentifier}.
2645
+ *
2646
+ * Converts `Http2Port` to `http2_port`. Use {@link snakeCaseToPascalCase} to
2647
+ * recover the original spelling.
2648
+ *
2649
+ * @group String
2650
+ */
2651
+ export declare const pascalCaseToSnakeCase: (value: PascalCaseIdentifier) => SnakeCaseIdentifier;
2652
+ /**
2653
+ * Converts a {@link PascalCaseIdentifier} to a {@link KebabCaseIdentifier}.
2654
+ *
2655
+ * Converts `Http2Port` to `http2-port`. Use {@link kebabCaseToPascalCase} to
2656
+ * recover the original spelling.
2657
+ *
2658
+ * @group String
2659
+ */
2660
+ export declare const pascalCaseToKebabCase: (value: PascalCaseIdentifier) => KebabCaseIdentifier;
2661
+ /**
2662
+ * Converts a {@link PascalCaseIdentifier} to a {@link ConstantCaseIdentifier}.
2663
+ *
2664
+ * Converts `Http2Port` to `HTTP2_PORT`. Use {@link constantCaseToPascalCase} to
2665
+ * recover the original spelling.
2666
+ *
2667
+ * @group String
2668
+ */
2669
+ export declare const pascalCaseToConstantCase: (value: PascalCaseIdentifier) => ConstantCaseIdentifier;
2670
+ /**
2671
+ * Converts a {@link SnakeCaseIdentifier} to a {@link CamelCaseIdentifier}.
2672
+ *
2673
+ * Converts `http2_port` to `http2Port`. Use {@link camelCaseToSnakeCase} to
2674
+ * recover the original spelling.
2675
+ *
2676
+ * @group String
2677
+ */
2678
+ export declare const snakeCaseToCamelCase: (value: SnakeCaseIdentifier) => CamelCaseIdentifier;
2679
+ /**
2680
+ * Converts a {@link SnakeCaseIdentifier} to a {@link PascalCaseIdentifier}.
2681
+ *
2682
+ * Converts `http2_port` to `Http2Port`. Use {@link pascalCaseToSnakeCase} to
2683
+ * recover the original spelling.
2684
+ *
2685
+ * @group String
2686
+ */
2687
+ export declare const snakeCaseToPascalCase: (value: SnakeCaseIdentifier) => PascalCaseIdentifier;
2688
+ /**
2689
+ * Converts a {@link SnakeCaseIdentifier} to a {@link KebabCaseIdentifier}.
2690
+ *
2691
+ * Converts `http2_port` to `http2-port`. Use {@link kebabCaseToSnakeCase} to
2692
+ * recover the original spelling.
2693
+ *
2694
+ * @group String
2695
+ */
2696
+ export declare const snakeCaseToKebabCase: (value: SnakeCaseIdentifier) => KebabCaseIdentifier;
2697
+ /**
2698
+ * Converts a {@link SnakeCaseIdentifier} to a {@link ConstantCaseIdentifier}.
2699
+ *
2700
+ * Converts `http2_port` to `HTTP2_PORT`. Use {@link constantCaseToSnakeCase} to
2701
+ * recover the original spelling.
2702
+ *
2703
+ * @group String
2704
+ */
2705
+ export declare const snakeCaseToConstantCase: (value: SnakeCaseIdentifier) => ConstantCaseIdentifier;
2706
+ /**
2707
+ * Converts a {@link KebabCaseIdentifier} to a {@link CamelCaseIdentifier}.
2708
+ *
2709
+ * Converts `http2-port` to `http2Port`. Use {@link camelCaseToKebabCase} to
2710
+ * recover the original spelling.
2711
+ *
2712
+ * @group String
2713
+ */
2714
+ export declare const kebabCaseToCamelCase: (value: KebabCaseIdentifier) => CamelCaseIdentifier;
2715
+ /**
2716
+ * Converts a {@link KebabCaseIdentifier} to a {@link PascalCaseIdentifier}.
2717
+ *
2718
+ * Converts `http2-port` to `Http2Port`. Use {@link pascalCaseToKebabCase} to
2719
+ * recover the original spelling.
2720
+ *
2721
+ * @group String
2722
+ */
2723
+ export declare const kebabCaseToPascalCase: (value: KebabCaseIdentifier) => PascalCaseIdentifier;
2724
+ /**
2725
+ * Converts a {@link KebabCaseIdentifier} to a {@link SnakeCaseIdentifier}.
2726
+ *
2727
+ * Converts `http2-port` to `http2_port`. Use {@link snakeCaseToKebabCase} to
2728
+ * recover the original spelling.
2729
+ *
2730
+ * @group String
2731
+ */
2732
+ export declare const kebabCaseToSnakeCase: (value: KebabCaseIdentifier) => SnakeCaseIdentifier;
2733
+ /**
2734
+ * Converts a {@link KebabCaseIdentifier} to a {@link ConstantCaseIdentifier}.
2735
+ *
2736
+ * Converts `http2-port` to `HTTP2_PORT`. Use {@link constantCaseToKebabCase} to
2737
+ * recover the original spelling.
2738
+ *
2739
+ * @group String
2740
+ */
2741
+ export declare const kebabCaseToConstantCase: (value: KebabCaseIdentifier) => ConstantCaseIdentifier;
2742
+ /**
2743
+ * Converts a {@link ConstantCaseIdentifier} to a {@link CamelCaseIdentifier}.
2744
+ *
2745
+ * Converts `HTTP2_PORT` to `http2Port`. Use {@link camelCaseToConstantCase} to
2746
+ * recover the original spelling.
2747
+ *
2748
+ * @group String
2749
+ */
2750
+ export declare const constantCaseToCamelCase: (value: ConstantCaseIdentifier) => CamelCaseIdentifier;
2751
+ /**
2752
+ * Decodes a {@link ConstantCaseIdentifier} to a {@link CamelCaseIdentifier} and
2753
+ * restores the original spelling when encoding.
2754
+ *
2755
+ * ### Example
2756
+ *
2757
+ * ```ts
2758
+ * import {
2759
+ * assertEqual,
2760
+ * assertOk,
2761
+ * CamelCaseIdentifierFromConstantCaseIdentifier,
2762
+ * } from "@evolu/common";
2763
+ *
2764
+ * const Key = CamelCaseIdentifierFromConstantCaseIdentifier;
2765
+ * const result = Key.fromUnknown("HTTP2_PORT");
2766
+ * assertOk(result, "http2Port");
2767
+ * assertEqual(Key.to(result.value), "HTTP2_PORT");
2768
+ * ```
2769
+ *
2770
+ * @group String
2771
+ */
2772
+ export declare const CamelCaseIdentifierFromConstantCaseIdentifier: TransformType<BrandType<Type<"String", string, string, TypeOfError<"String">, null, TypeOfError<"String">, never, string, true>, "ConstantCaseIdentifier", IdentifierError<"CONSTANT_CASE">>, BrandType<Type<"String", string, string, TypeOfError<"String">, null, TypeOfError<"String">, never, string, true>, "CamelCaseIdentifier", IdentifierError<"camelCase">>, "CamelCaseIdentifierFromConstantCaseIdentifier", never, string & Brand<"ConstantCaseIdentifier">>;
2773
+ /**
2774
+ * Converts a {@link ConstantCaseIdentifier} to a {@link PascalCaseIdentifier}.
2775
+ *
2776
+ * Converts `HTTP2_PORT` to `Http2Port`. Use {@link pascalCaseToConstantCase} to
2777
+ * recover the original spelling.
2778
+ *
2779
+ * @group String
2780
+ */
2781
+ export declare const constantCaseToPascalCase: (value: ConstantCaseIdentifier) => PascalCaseIdentifier;
2782
+ /**
2783
+ * Converts a {@link ConstantCaseIdentifier} to a {@link SnakeCaseIdentifier}.
2784
+ *
2785
+ * Converts `HTTP2_PORT` to `http2_port`. Use {@link snakeCaseToConstantCase} to
2786
+ * recover the original spelling.
2787
+ *
2788
+ * @group String
2789
+ */
2790
+ export declare const constantCaseToSnakeCase: (value: ConstantCaseIdentifier) => SnakeCaseIdentifier;
2791
+ /**
2792
+ * Converts a {@link ConstantCaseIdentifier} to a {@link KebabCaseIdentifier}.
2793
+ *
2794
+ * Converts `HTTP2_PORT` to `http2-port`. Use {@link kebabCaseToConstantCase} to
2795
+ * recover the original spelling.
2796
+ *
2797
+ * @group String
2798
+ */
2799
+ export declare const constantCaseToKebabCase: (value: ConstantCaseIdentifier) => KebabCaseIdentifier;
2800
+ /**
2801
+ * Adds capitalized text validation to an existing string Type.
2802
+ *
2803
+ * Narrows the output to TypeScript's `Capitalize<string>` while preserving the
2804
+ * parent Type's constraints. Validation leaves the text unchanged. Use
2805
+ * {@link capitalize} to change its casing.
2806
+ *
2807
+ * ### Example
2808
+ *
2809
+ * ```ts
2810
+ * import {
2811
+ * assertErr,
2812
+ * assertOk,
2233
2813
  * capitalized,
2234
- * type Brand,
2814
+ * maxLength,
2815
+ * String,
2235
2816
  * } from "@evolu/common";
2236
2817
  *
2237
- * const CapitalizedString = capitalized(String);
2238
- * type CapitalizedString = typeof CapitalizedString.Output;
2818
+ * const Label = capitalized(maxLength(50)(String));
2819
+ * assertOk(Label.fromUnknown("Hello world"), "Hello world");
2820
+ * assertErr(Label.fromUnknown("hello world"));
2821
+ * ```
2239
2822
  *
2240
- * assertType<CapitalizedString, string & Brand<"Capitalized">>();
2823
+ * @group String
2824
+ */
2825
+ export declare const capitalized: <ParentType extends ConcreteTypeNode & {
2826
+ readonly Output: string;
2827
+ }>(parent: ValidateBrandParent<"Capitalized", ParentType>) => ReturnType<typeof createType<"Capitalized", ParentType, ParentType["Output"] & Capitalize<string>, CapitalizedError>>;
2828
+ /**
2829
+ * Error returned when {@link capitalized} rejects a string.
2241
2830
  *
2242
- * assertOk(CapitalizedString.fromUnknown("Evolu"), "Evolu");
2243
- * const invalid = CapitalizedString.fromUnknown("evolu");
2244
- * assertErr(invalid);
2245
- * assertType(Data, invalid.error);
2246
- * assertEqual(invalid.error, {
2247
- * type: "Capitalized",
2248
- * value: "evolu",
2249
- * });
2831
+ * @group String
2832
+ */
2833
+ export interface CapitalizedError extends TypeError<"Capitalized"> {
2834
+ readonly value: string;
2835
+ }
2836
+ /**
2837
+ * Validates capitalized text as TypeScript's `Capitalize<string>`.
2838
+ *
2839
+ * The rest of the text can use any casing. Empty strings and text starting with
2840
+ * an uncased character, such as a digit or emoji, are valid. Use
2841
+ * {@link capitalize} to produce a capitalized value from any string.
2842
+ *
2843
+ * Capitalization applies to general text, including spaces and punctuation.
2844
+ * Both `hello` and `Hello` become `Hello`, so the original initial casing
2845
+ * cannot be recovered. Capitalization does not identify words or turn text into
2846
+ * an identifier.
2847
+ *
2848
+ * ### Example
2849
+ *
2850
+ * ```ts
2851
+ * import { assertErr, assertOk, CapitalizedString } from "@evolu/common";
2852
+ *
2853
+ * const text: CapitalizedString = "Hello world";
2854
+ * assertOk(CapitalizedString.fromUnknown(text), text);
2855
+ * assertErr(CapitalizedString.fromUnknown("hello world"));
2856
+ * assertOk(CapitalizedString.fromUnknown(""), "");
2857
+ * ```
2858
+ *
2859
+ * @group String
2860
+ */
2861
+ export declare const CapitalizedString: Type<"Capitalized", string, Capitalize<string>, CapitalizedError, Type<"String", string, string, TypeOfError<"String">, null, TypeOfError<"String">, never, string, true>, CapitalizedError | TypeOfError<"String">, never, Capitalize<string>, true>;
2862
+ export type CapitalizedString = typeof CapitalizedString.Output;
2863
+ /**
2864
+ * Uppercases the first Unicode code point and returns a
2865
+ * {@link CapitalizedString}.
2866
+ *
2867
+ * Preserves the remainder of the string and leaves an empty string unchanged.
2868
+ * Uses JavaScript's default Unicode casing without locale-specific rules.
2869
+ * Changing case can change the length, so input brands are not retained.
2870
+ *
2871
+ * ### Example
2872
+ *
2873
+ * ```ts
2874
+ * import { assertEqual, assertType, capitalize } from "@evolu/common";
2875
+ *
2876
+ * const text = capitalize("hello world");
2877
+ * assertEqual(text, "Hello world");
2878
+ * assertType<typeof text, "Hello world">();
2879
+ * assertEqual(capitalize(text), text);
2880
+ * ```
2881
+ *
2882
+ * @group String
2883
+ */
2884
+ export declare const capitalize: <S extends string>(value: S) => Capitalize<S>;
2885
+ /**
2886
+ * Adds uncapitalized text validation to an existing string Type.
2887
+ *
2888
+ * Narrows the output to TypeScript's `Uncapitalize<string>` while preserving
2889
+ * the parent Type's constraints. Validation leaves the text unchanged. Use
2890
+ * {@link uncapitalize} to change its casing.
2891
+ *
2892
+ * ### Example
2893
+ *
2894
+ * ```ts
2895
+ * import {
2896
+ * assertErr,
2897
+ * assertOk,
2898
+ * uncapitalized,
2899
+ * maxLength,
2900
+ * String,
2901
+ * } from "@evolu/common";
2902
+ *
2903
+ * const Label = uncapitalized(maxLength(50)(String));
2904
+ * assertOk(Label.fromUnknown("hello WORLD"), "hello WORLD");
2905
+ * assertErr(Label.fromUnknown("Hello WORLD"));
2906
+ * ```
2907
+ *
2908
+ * @group String
2909
+ */
2910
+ export declare const uncapitalized: <ParentType extends ConcreteTypeNode & {
2911
+ readonly Output: string;
2912
+ }>(parent: ValidateBrandParent<"Uncapitalized", ParentType>) => ReturnType<typeof createType<"Uncapitalized", ParentType, ParentType["Output"] & Uncapitalize<string>, UncapitalizedError>>;
2913
+ /**
2914
+ * Error returned when {@link uncapitalized} rejects a string.
2915
+ *
2916
+ * @group String
2917
+ */
2918
+ export interface UncapitalizedError extends TypeError<"Uncapitalized"> {
2919
+ readonly value: string;
2920
+ }
2921
+ /**
2922
+ * Validates uncapitalized text as TypeScript's `Uncapitalize<string>`.
2923
+ *
2924
+ * The rest of the text can use any casing. Empty strings and text starting with
2925
+ * an uncased character, such as a digit or emoji, are valid. Use
2926
+ * {@link uncapitalize} to produce an uncapitalized value from any string.
2927
+ *
2928
+ * ### Example
2929
+ *
2930
+ * ```ts
2931
+ * import { assertErr, assertOk, UncapitalizedString } from "@evolu/common";
2932
+ *
2933
+ * const text: UncapitalizedString = "hello WORLD";
2934
+ * assertOk(UncapitalizedString.fromUnknown(text), text);
2935
+ * assertErr(UncapitalizedString.fromUnknown("Hello WORLD"));
2936
+ * assertOk(UncapitalizedString.fromUnknown(""), "");
2937
+ * ```
2938
+ *
2939
+ * @group String
2940
+ */
2941
+ export declare const UncapitalizedString: Type<"Uncapitalized", string, Uncapitalize<string>, UncapitalizedError, Type<"String", string, string, TypeOfError<"String">, null, TypeOfError<"String">, never, string, true>, TypeOfError<"String"> | UncapitalizedError, never, Uncapitalize<string>, true>;
2942
+ export type UncapitalizedString = typeof UncapitalizedString.Output;
2943
+ /**
2944
+ * Lowercases the first Unicode code point and returns a
2945
+ * {@link UncapitalizedString}.
2946
+ *
2947
+ * Preserves the remainder of the string and leaves an empty string unchanged.
2948
+ * Uses JavaScript's default Unicode casing without locale-specific rules.
2949
+ * Changing case can change the length, so input brands are not retained.
2950
+ *
2951
+ * ### Example
2952
+ *
2953
+ * ```ts
2954
+ * import { assertEqual, assertType, uncapitalize } from "@evolu/common";
2955
+ *
2956
+ * const text = uncapitalize("Hello WORLD");
2957
+ * assertEqual(text, "hello WORLD");
2958
+ * assertType<typeof text, "hello WORLD">();
2959
+ * assertEqual(uncapitalize(text), text);
2960
+ * ```
2961
+ *
2962
+ * @group String
2963
+ */
2964
+ export declare const uncapitalize: <S extends string>(value: S) => Uncapitalize<S>;
2965
+ /**
2966
+ * Adds uppercased text validation to an existing string Type.
2967
+ *
2968
+ * Narrows the output to TypeScript's `Uppercase<string>` while preserving the
2969
+ * parent Type's constraints. Validation leaves the text unchanged. Use
2970
+ * {@link uppercase} to change its casing.
2971
+ *
2972
+ * ### Example
2973
+ *
2974
+ * ```ts
2975
+ * import {
2976
+ * assertErr,
2977
+ * assertOk,
2978
+ * uppercased,
2979
+ * maxLength,
2980
+ * String,
2981
+ * } from "@evolu/common";
2982
+ *
2983
+ * const Label = uppercased(maxLength(50)(String));
2984
+ * assertOk(Label.fromUnknown("HELLO WORLD"), "HELLO WORLD");
2985
+ * assertErr(Label.fromUnknown("Hello world"));
2986
+ * ```
2987
+ *
2988
+ * @group String
2989
+ */
2990
+ export declare const uppercased: <ParentType extends ConcreteTypeNode & {
2991
+ readonly Output: string;
2992
+ }>(parent: ValidateBrandParent<"Uppercased", ParentType>) => ReturnType<typeof createType<"Uppercased", ParentType, ParentType["Output"] & Uppercase<string>, UppercasedError>>;
2993
+ /**
2994
+ * Error returned when {@link uppercased} rejects a string.
2995
+ *
2996
+ * @group String
2997
+ */
2998
+ export interface UppercasedError extends TypeError<"Uppercased"> {
2999
+ readonly value: string;
3000
+ }
3001
+ /**
3002
+ * Validates uppercased text as TypeScript's `Uppercase<string>`.
3003
+ *
3004
+ * Checks the whole string using JavaScript's Unicode uppercase mapping. Empty
3005
+ * strings and uncased characters, such as digits and emoji, are valid. Use
3006
+ * {@link uppercase} to produce an uppercased value from any string.
3007
+ *
3008
+ * ### Example
3009
+ *
3010
+ * ```ts
3011
+ * import { assertErr, assertOk, UppercasedString } from "@evolu/common";
3012
+ *
3013
+ * const text: UppercasedString = "HELLO WORLD";
3014
+ * assertOk(UppercasedString.fromUnknown(text), text);
3015
+ * assertErr(UppercasedString.fromUnknown("Hello world"));
3016
+ * assertOk(UppercasedString.fromUnknown(""), "");
3017
+ * ```
3018
+ *
3019
+ * @group String
3020
+ */
3021
+ export declare const UppercasedString: Type<"Uppercased", string, Uppercase<string>, UppercasedError, Type<"String", string, string, TypeOfError<"String">, null, TypeOfError<"String">, never, string, true>, TypeOfError<"String"> | UppercasedError, never, Uppercase<string>, true>;
3022
+ export type UppercasedString = typeof UppercasedString.Output;
3023
+ /**
3024
+ * Uppercases the whole string and returns a {@link UppercasedString}.
3025
+ *
3026
+ * Leaves an empty string unchanged. Uses JavaScript's default Unicode casing
3027
+ * without locale-specific rules. Changing case can change the length, so input
3028
+ * brands are not retained.
3029
+ *
3030
+ * ### Example
3031
+ *
3032
+ * ```ts
3033
+ * import { assertEqual, assertType, uppercase } from "@evolu/common";
3034
+ *
3035
+ * const text = uppercase("Hello world");
3036
+ * assertEqual(text, "HELLO WORLD");
3037
+ * assertType<typeof text, "HELLO WORLD">();
3038
+ * assertEqual(uppercase(text), text);
3039
+ * ```
3040
+ *
3041
+ * @group String
3042
+ */
3043
+ export declare const uppercase: <S extends string>(value: S) => Uppercase<S>;
3044
+ /**
3045
+ * Adds lowercased text validation to an existing string Type.
3046
+ *
3047
+ * Narrows the output to TypeScript's `Lowercase<string>` while preserving the
3048
+ * parent Type's constraints. Validation leaves the text unchanged. Use
3049
+ * {@link lowercase} to change its casing.
3050
+ *
3051
+ * ### Example
3052
+ *
3053
+ * ```ts
3054
+ * import {
3055
+ * assertErr,
3056
+ * assertOk,
3057
+ * lowercased,
3058
+ * maxLength,
3059
+ * String,
3060
+ * } from "@evolu/common";
3061
+ *
3062
+ * const Label = lowercased(maxLength(50)(String));
3063
+ * assertOk(Label.fromUnknown("hello world"), "hello world");
3064
+ * assertErr(Label.fromUnknown("Hello WORLD"));
2250
3065
  * ```
2251
3066
  *
2252
3067
  * @group String
2253
3068
  */
2254
- export declare const capitalized: BrandFactory<"Capitalized", string, CapitalizedError>;
3069
+ export declare const lowercased: <ParentType extends ConcreteTypeNode & {
3070
+ readonly Output: string;
3071
+ }>(parent: ValidateBrandParent<"Lowercased", ParentType>) => ReturnType<typeof createType<"Lowercased", ParentType, ParentType["Output"] & Lowercase<string>, LowercasedError>>;
2255
3072
  /**
2256
- * Error returned when {@link capitalized} rejects a string.
3073
+ * Error returned when {@link lowercased} rejects a string.
2257
3074
  *
2258
3075
  * @group String
2259
3076
  */
2260
- export interface CapitalizedError extends TypeError<"Capitalized"> {
3077
+ export interface LowercasedError extends TypeError<"Lowercased"> {
2261
3078
  readonly value: string;
2262
3079
  }
2263
3080
  /**
2264
- * Capitalized {@link String}.
3081
+ * Validates lowercased text as TypeScript's `Lowercase<string>`.
3082
+ *
3083
+ * Checks the whole string using JavaScript's Unicode lowercase mapping. Empty
3084
+ * strings and uncased characters, such as digits and emoji, are valid. Use
3085
+ * {@link lowercase} to produce a lowercased value from any string.
3086
+ *
3087
+ * ### Example
3088
+ *
3089
+ * ```ts
3090
+ * import { assertErr, assertOk, LowercasedString } from "@evolu/common";
3091
+ *
3092
+ * const text: LowercasedString = "hello world";
3093
+ * assertOk(LowercasedString.fromUnknown(text), text);
3094
+ * assertErr(LowercasedString.fromUnknown("Hello WORLD"));
3095
+ * assertOk(LowercasedString.fromUnknown(""), "");
3096
+ * ```
2265
3097
  *
2266
3098
  * @group String
2267
3099
  */
2268
- export declare const CapitalizedString: BrandType<Type<"String", string, string, TypeOfError<"String">, null, TypeOfError<"String">, never, string, true>, "Capitalized", CapitalizedError>;
2269
- export type CapitalizedString = typeof CapitalizedString.Output;
3100
+ export declare const LowercasedString: Type<"Lowercased", string, Lowercase<string>, LowercasedError, Type<"String", string, string, TypeOfError<"String">, null, TypeOfError<"String">, never, string, true>, LowercasedError | TypeOfError<"String">, never, Lowercase<string>, true>;
3101
+ export type LowercasedString = typeof LowercasedString.Output;
3102
+ /**
3103
+ * Lowercases the whole string and returns a {@link LowercasedString}.
3104
+ *
3105
+ * Leaves an empty string unchanged. Uses JavaScript's default Unicode casing
3106
+ * without locale-specific rules. Changing case can change the length, so input
3107
+ * brands are not retained.
3108
+ *
3109
+ * ### Example
3110
+ *
3111
+ * ```ts
3112
+ * import { assertEqual, assertType, lowercase } from "@evolu/common";
3113
+ *
3114
+ * const text = lowercase("Hello WORLD");
3115
+ * assertEqual(text, "hello world");
3116
+ * assertType<typeof text, "hello world">();
3117
+ * assertEqual(lowercase(text), text);
3118
+ * ```
3119
+ *
3120
+ * @group String
3121
+ */
3122
+ export declare const lowercase: <S extends string>(value: S) => Lowercase<S>;
2270
3123
  /**
2271
3124
  * String {@link Brand} without surrounding whitespace.
2272
3125
  *
@@ -2318,6 +3171,116 @@ export type TrimmedString = typeof TrimmedString.Output;
2318
3171
  * @group String
2319
3172
  */
2320
3173
  export declare const trim: (value: string) => TrimmedString;
3174
+ /**
3175
+ * String {@link Brand} requiring an exact, case-sensitive prefix.
3176
+ *
3177
+ * Validation preserves the complete string, including the prefix. An empty
3178
+ * prefix accepts every string allowed by the parent Type. The prefix must be
3179
+ * one concrete string literal so different prefixes have distinct brands.
3180
+ *
3181
+ * ### Example
3182
+ *
3183
+ * ```ts
3184
+ * import {
3185
+ * assertEqual,
3186
+ * assertErr,
3187
+ * assertOk,
3188
+ * assertType,
3189
+ * maxLength,
3190
+ * startsWith,
3191
+ * String,
3192
+ * type Brand,
3193
+ * } from "@evolu/common";
3194
+ *
3195
+ * const EnvName = startsWith("APP_")(maxLength(64)(String));
3196
+ *
3197
+ * const name = EnvName.fromUnknown("APP_PORT");
3198
+ * assertOk(name, "APP_PORT");
3199
+ * assertType<
3200
+ * typeof name.value,
3201
+ * string & Brand<"MaxLength64"> & Brand<"StartsWithAPP_">
3202
+ * >();
3203
+ *
3204
+ * assertEqual(EnvName.to(name.value), "APP_PORT");
3205
+ *
3206
+ * assertErr(EnvName.fromUnknown("app_PORT"));
3207
+ * assertErr(EnvName.fromUnknown("APP_" + "X".repeat(61)));
3208
+ * ```
3209
+ *
3210
+ * @group String
3211
+ */
3212
+ export declare const startsWith: <Prefix extends string>(prefix: Prefix & ValidateLiteral<Prefix>) => BrandFactory<`StartsWith${Prefix}`, string, StartsWithError<Prefix>>;
3213
+ /**
3214
+ * Error returned when {@link startsWith} rejects a string.
3215
+ *
3216
+ * @group String
3217
+ */
3218
+ export interface StartsWithError<Prefix extends string = string> extends TypeError<`StartsWith${Prefix}`> {
3219
+ readonly value: string;
3220
+ readonly prefix: Prefix;
3221
+ }
3222
+ /**
3223
+ * Decodes a prefixed string with another {@link Type} and restores the prefix
3224
+ * when encoding.
3225
+ *
3226
+ * Uses {@link startsWith} to validate an exact, case-sensitive prefix before
3227
+ * removing one occurrence. The wrapped Type validates and decodes the suffix;
3228
+ * its constraints apply to the suffix, and its Output is preserved. Encoding
3229
+ * prepends the prefix to the wrapped Type's canonical string representation. An
3230
+ * empty prefix leaves that representation unchanged.
3231
+ *
3232
+ * The prefix must be one concrete string literal. The wrapped Type must accept
3233
+ * a string Input and encode to strings; its Output can have another type, as
3234
+ * with {@link PortFromString}.
3235
+ *
3236
+ * ### Example
3237
+ *
3238
+ * ```ts
3239
+ * import {
3240
+ * assertEqual,
3241
+ * assertErr,
3242
+ * assertOk,
3243
+ * assertType,
3244
+ * ConstantCaseIdentifier,
3245
+ * prefixed,
3246
+ * PortFromString,
3247
+ * type Port,
3248
+ * } from "@evolu/common";
3249
+ *
3250
+ * const EnvName = prefixed("APP_")(ConstantCaseIdentifier);
3251
+ *
3252
+ * const name = EnvName.fromUnknown("APP_PORT");
3253
+ * assertOk(name, "PORT");
3254
+ * assertType<typeof name.value, ConstantCaseIdentifier>();
3255
+ *
3256
+ * assertEqual(EnvName.to(name.value), "APP_PORT");
3257
+ *
3258
+ * assertErr(EnvName.fromUnknown("OTHER_PORT"));
3259
+ * assertErr(EnvName.fromUnknown("APP_port"));
3260
+ *
3261
+ * const PortSetting = prefixed("port:")(PortFromString);
3262
+ *
3263
+ * const port = PortSetting.fromUnknown("port:04000");
3264
+ * assertOk(port, 4000);
3265
+ * assertType<typeof port.value, Port>();
3266
+ *
3267
+ * assertEqual(PortSetting.to(port.value), "port:4000");
3268
+ * ```
3269
+ *
3270
+ * @group String
3271
+ */
3272
+ export declare const prefixed: <Prefix extends string>(prefix: Prefix & ValidateLiteral<Prefix>) => <T extends ConcreteTypeNode & {
3273
+ readonly Input: string;
3274
+ readonly CanonicalInput: string;
3275
+ }>(type: T & ValidateOutput<T> & (string extends T["Input"] ? unknown : PrefixedInputTypeError)) => PrefixedType<Prefix, T>;
3276
+ /**
3277
+ * The {@link Type} returned by {@link prefixed}.
3278
+ *
3279
+ * @group String
3280
+ */
3281
+ export interface PrefixedType<Prefix extends string, T extends TypeNode> extends TransformType<BrandType<typeof String, `StartsWith${Prefix}`, StartsWithError<Prefix>>, T, `Prefixed${Prefix}`, never> {
3282
+ }
3283
+ type PrefixedInputTypeError = CompileTimeError<"Type", "Prefixed Type Input must accept every string.">;
2321
3284
  /**
2322
3285
  * Minimum-length {@link Brand} for values whose `length` is at least `min`.
2323
3286
  *
@@ -2460,6 +3423,7 @@ export interface LengthError<Exact extends number = number> extends TypeError<`L
2460
3423
  * assertType<UrlSafeString, string & Brand<"UrlSafeString">>();
2461
3424
  *
2462
3425
  * assertOk(UrlSafeString.fromUnknown("abc-123_DEF"), "abc-123_DEF");
3426
+ *
2463
3427
  * const invalid = UrlSafeString.fromUnknown("not safe");
2464
3428
  * assertErr(invalid);
2465
3429
  * assertType(Data, invalid.error);
@@ -2555,7 +3519,13 @@ export declare const uint8ArrayToBase64Url: (bytes: Uint8Array) => Base64Url;
2555
3519
  */
2556
3520
  export declare const base64UrlToUint8Array: (value: Base64Url) => Uint8Array;
2557
3521
  /**
2558
- * A non-empty URL-safe name containing at most 64 UTF-16 code units.
3522
+ * A non-empty file-system-safe and URL-safe token of at most 64 UTF-16 code
3523
+ * units.
3524
+ *
3525
+ * Evolu uses it for database file names, storage pool names, and log prefixes.
3526
+ * It accepts the {@link UrlSafeString} alphabet in any order, so it may start
3527
+ * with a digit, `-`, or `_`. It is not a language identifier; for
3528
+ * word-structured names, use {@link identifier}.
2559
3529
  *
2560
3530
  * @group String
2561
3531
  */
@@ -3093,6 +4063,7 @@ export type PositiveFiniteNumber = typeof PositiveFiniteNumber.Output;
3093
4063
  * assertType<Int, number & Brand<"Int">>();
3094
4064
  *
3095
4065
  * assertOk(Int.fromUnknown(42), 42);
4066
+ *
3096
4067
  * const invalid = Int.fromUnknown(1.5);
3097
4068
  * assertErr(invalid);
3098
4069
  * assertType(Data, invalid.error);
@@ -3141,6 +4112,52 @@ export declare const zeroNonNegativeInt: number & Brand<"NonNaN"> & Brand<"Finit
3141
4112
  */
3142
4113
  export declare const PositiveInt: BrandType<BrandType<BrandType<BrandType<BrandType<Type<"Number", number, number, TypeOfError<"Number">, null, TypeOfError<"Number">, never, number, true>, "NonNaN", NonNaNError>, "Finite", FiniteError>, "Int", IntError>, "NonNegative", NonNegativeError>, "Positive", PositiveError>;
3143
4114
  export type PositiveInt = typeof PositiveInt.Output;
4115
+ /**
4116
+ * Error returned when a string is not a decimal integer.
4117
+ *
4118
+ * @group Number
4119
+ */
4120
+ export interface IntFromStringError extends TypeError<"IntFromString"> {
4121
+ readonly value: string;
4122
+ }
4123
+ /**
4124
+ * Transforms a decimal integer string into an {@link Int}.
4125
+ *
4126
+ * This is useful for inputs that carry numbers as text, such as environment
4127
+ * variables, URL query parameters, and form fields. The string must consist of
4128
+ * an optional minus sign and digits; the {@link Int} constraint then rejects
4129
+ * values outside the safe integer range.
4130
+ *
4131
+ * ### Example
4132
+ *
4133
+ * ```ts
4134
+ * import {
4135
+ * assertEqual,
4136
+ * assertErr,
4137
+ * assertOk,
4138
+ * assertSame,
4139
+ * IntFromString,
4140
+ * } from "@evolu/common";
4141
+ *
4142
+ * assertOk(IntFromString.fromUnknown("4000"), 4000);
4143
+ * assertOk(IntFromString.fromUnknown("-1"), -1);
4144
+ * assertEqual(IntFromString.to(IntFromString.orThrow("42")), "42");
4145
+ *
4146
+ * const negativeZero = IntFromString.orThrow("-0");
4147
+ * assertSame(negativeZero, -0);
4148
+ * assertEqual(IntFromString.to(negativeZero), "-0");
4149
+ *
4150
+ * const invalid = IntFromString.fromUnknown("4000.5");
4151
+ * assertErr(invalid, { type: "IntFromString", value: "4000.5" });
4152
+ * assertEqual(
4153
+ * IntFromString.formatError(invalid.error),
4154
+ * 'The value "4000.5" is not a decimal integer.',
4155
+ * );
4156
+ * ```
4157
+ *
4158
+ * @group Number
4159
+ */
4160
+ export declare const IntFromString: TransformType<Type<"String", string, string, TypeOfError<"String">, null, TypeOfError<"String">, never, string, true>, BrandType<BrandType<BrandType<Type<"Number", number, number, TypeOfError<"Number">, null, TypeOfError<"Number">, never, number, true>, "NonNaN", NonNaNError>, "Finite", FiniteError>, "Int", IntError>, "IntFromString", IntFromStringError, string>;
3144
4161
  /**
3145
4162
  * Minimum {@link PositiveInt} value.
3146
4163
  *
@@ -3277,6 +4294,55 @@ export interface LessThanOrEqualToError<Max extends number = number> extends Typ
3277
4294
  readonly value: number;
3278
4295
  readonly max: Max;
3279
4296
  }
4297
+ /**
4298
+ * A TCP or UDP port as an integer from zero through 65535, inclusive.
4299
+ *
4300
+ * When binding a server, zero requests an automatically assigned port. Use
4301
+ * {@link PortFromString} for configuration values supplied as text.
4302
+ *
4303
+ * ### Example
4304
+ *
4305
+ * ```ts
4306
+ * import { assertErr, assertOk, Port } from "@evolu/common";
4307
+ *
4308
+ * assertOk(Port.fromUnknown(0), 0);
4309
+ * assertOk(Port.fromUnknown(4000), 4000);
4310
+ * assertOk(Port.fromUnknown(65535), 65535);
4311
+ * assertErr(Port.fromUnknown(-1));
4312
+ * assertErr(Port.fromUnknown(65536));
4313
+ * assertErr(Port.fromUnknown(4000.5));
4314
+ * ```
4315
+ *
4316
+ * @group Number
4317
+ */
4318
+ export declare const Port: BrandType<BrandType<BrandType<BrandType<BrandType<BrandType<Type<"Number", number, number, TypeOfError<"Number">, null, TypeOfError<"Number">, never, number, true>, "NonNaN", NonNaNError>, "Finite", FiniteError>, "Int", IntError>, "NonNegative", NonNegativeError>, "LessThanOrEqualTo65535", LessThanOrEqualToError<65535>>, "Port", never>;
4319
+ export type Port = typeof Port.Output;
4320
+ /**
4321
+ * Parses a decimal integer string and validates it as a {@link Port}.
4322
+ *
4323
+ * Uses {@link IntFromString} for decimal parsing, including its rejection of
4324
+ * whitespace, plus signs, fractions, and exponent notation.
4325
+ *
4326
+ * ### Example
4327
+ *
4328
+ * ```ts
4329
+ * import {
4330
+ * assertEqual,
4331
+ * assertErr,
4332
+ * assertOk,
4333
+ * PortFromString,
4334
+ * } from "@evolu/common";
4335
+ *
4336
+ * assertOk(PortFromString.fromUnknown("0"), 0);
4337
+ * assertOk(PortFromString.fromUnknown("4000"), 4000);
4338
+ * assertErr(PortFromString.fromUnknown("65536"));
4339
+ * assertErr(PortFromString.fromUnknown("http"));
4340
+ * assertEqual(PortFromString.to(PortFromString.orThrow("04000")), "4000");
4341
+ * ```
4342
+ *
4343
+ * @group Number
4344
+ */
4345
+ export declare const PortFromString: TransformType<TransformType<Type<"String", string, string, TypeOfError<"String">, null, TypeOfError<"String">, never, string, true>, BrandType<BrandType<BrandType<Type<"Number", number, number, TypeOfError<"Number">, null, TypeOfError<"Number">, never, number, true>, "NonNaN", NonNaNError>, "Finite", FiniteError>, "Int", IntError>, "IntFromString", IntFromStringError, string>, BrandType<BrandType<BrandType<BrandType<BrandType<BrandType<Type<"Number", number, number, TypeOfError<"Number">, null, TypeOfError<"Number">, never, number, true>, "NonNaN", NonNaNError>, "Finite", FiniteError>, "Int", IntError>, "NonNegative", NonNegativeError>, "LessThanOrEqualTo65535", LessThanOrEqualToError<65535>>, "Port", never>, "PortFromString", never, number & Brand<"NonNaN"> & Brand<"Finite"> & Brand<"Int"> & Brand<"NonNegative"> & Brand<"LessThanOrEqualTo65535"> & Brand<"Port">>;
3280
4346
  /**
3281
4347
  * Finite {@link Number} from zero to one, inclusive.
3282
4348
  *
@@ -3526,6 +4592,7 @@ export type NegativeDecimalString = typeof NegativeDecimalString.Output;
3526
4592
  * >();
3527
4593
  *
3528
4594
  * assertOk(Tenths.fromUnknown(0.3), 0.3);
4595
+ *
3529
4596
  * const invalid = Tenths.fromUnknown(0.31);
3530
4597
  * assertErr(invalid);
3531
4598
  * assertType(Data, invalid.error);
@@ -3622,6 +4689,7 @@ export interface BetweenError<Min extends number = number, Max extends number =
3622
4689
  *
3623
4690
  * const UserId = brand("UserId", String);
3624
4691
  * const UserIds = array(UserId);
4692
+ *
3625
4693
  * const result = UserIds.from.parent(["ada", "grace"]);
3626
4694
  *
3627
4695
  * assertType<
@@ -3630,6 +4698,7 @@ export interface BetweenError<Min extends number = number, Max extends number =
3630
4698
  * >();
3631
4699
  * assertOk(result, ["ada", "grace"]);
3632
4700
  * assertOk(UserIds.fromUnknown(["ada", "grace"]), ["ada", "grace"]);
4701
+ *
3633
4702
  * const invalid = UserIds.fromUnknown("ada");
3634
4703
  * assertErr(invalid);
3635
4704
  * assertType(Data, invalid.error);
@@ -4146,53 +5215,92 @@ interface TupleItemsErrorValue<Error extends TypeError, IncludeStructuralIssues
4146
5215
  };
4147
5216
  }
4148
5217
  /**
4149
- * Decimal digit from `"0"` to `"9"`.
5218
+ * Decimal integer string from `"0"` to `"9"`.
4150
5219
  *
4151
5220
  * @group String
4152
5221
  */
4153
5222
  export declare const Digit: UnionType<readonly [LiteralType<"0">, LiteralType<"1">, LiteralType<"2">, LiteralType<"3">, LiteralType<"4">, LiteralType<"5">, LiteralType<"6">, LiteralType<"7">, LiteralType<"8">, LiteralType<"9">]>;
4154
5223
  export type Digit = typeof Digit.Output;
4155
5224
  /**
4156
- * Decimal digit from `"1"` to `"9"`.
5225
+ * Decimal integer string from `"1"` to `"9"`.
4157
5226
  *
4158
5227
  * @group String
4159
5228
  */
4160
5229
  export declare const Digit1To9: UnionType<readonly [LiteralType<"1">, LiteralType<"2">, LiteralType<"3">, LiteralType<"4">, LiteralType<"5">, LiteralType<"6">, LiteralType<"7">, LiteralType<"8">, LiteralType<"9">]>;
4161
5230
  export type Digit1To9 = typeof Digit1To9.Output;
4162
5231
  /**
4163
- * Decimal string from `"1"` to `"6"`.
5232
+ * Decimal integer string from `"1"` to `"6"`.
4164
5233
  *
4165
5234
  * @group String
4166
5235
  */
4167
5236
  export declare const Digit1To6: UnionType<readonly [LiteralType<"1">, LiteralType<"2">, LiteralType<"3">, LiteralType<"4">, LiteralType<"5">, LiteralType<"6">]>;
4168
5237
  export type Digit1To6 = typeof Digit1To6.Output;
4169
5238
  /**
4170
- * Decimal string from `"1"` to `"23"`.
5239
+ * Decimal integer string from `"1"` to `"23"`, without a leading zero.
4171
5240
  *
4172
5241
  * @group String
4173
5242
  */
4174
- export declare const Digit1To23: UnionType<readonly [UnionType<readonly [LiteralType<"1">, LiteralType<"2">, LiteralType<"3">, LiteralType<"4">, LiteralType<"5">, LiteralType<"6">, LiteralType<"7">, LiteralType<"8">, LiteralType<"9">]>, TemplateLiteralType<readonly ["1", UnionType<readonly [LiteralType<"0">, LiteralType<"1">, LiteralType<"2">, LiteralType<"3">, LiteralType<"4">, LiteralType<"5">, LiteralType<"6">, LiteralType<"7">, LiteralType<"8">, LiteralType<"9">]>]>, TemplateLiteralType<readonly ["2", UnionType<readonly [LiteralType<"0">, LiteralType<"1">, LiteralType<"2">, LiteralType<"3">]>]>]>;
5243
+ export declare const Digit1To23: UnionType<readonly [
5244
+ typeof Digit1To9,
5245
+ TemplateLiteralType<readonly ["1", typeof Digit]>,
5246
+ TemplateLiteralType<readonly [
5247
+ "2",
5248
+ UnionType<readonly [
5249
+ LiteralType<"0">,
5250
+ LiteralType<"1">,
5251
+ LiteralType<"2">,
5252
+ LiteralType<"3">
5253
+ ]>
5254
+ ]>
5255
+ ]>;
4175
5256
  export type Digit1To23 = typeof Digit1To23.Output;
4176
5257
  /**
4177
- * Decimal string from `"1"` to `"51"`.
5258
+ * Decimal integer string from `"1"` to `"51"`, without a leading zero.
4178
5259
  *
4179
5260
  * @group String
4180
5261
  */
4181
- export declare const Digit1To51: UnionType<readonly [UnionType<readonly [LiteralType<"1">, LiteralType<"2">, LiteralType<"3">, LiteralType<"4">, LiteralType<"5">, LiteralType<"6">, LiteralType<"7">, LiteralType<"8">, LiteralType<"9">]>, TemplateLiteralType<readonly [UnionType<readonly [LiteralType<"1">, LiteralType<"2">, LiteralType<"3">, LiteralType<"4">]>, UnionType<readonly [LiteralType<"0">, LiteralType<"1">, LiteralType<"2">, LiteralType<"3">, LiteralType<"4">, LiteralType<"5">, LiteralType<"6">, LiteralType<"7">, LiteralType<"8">, LiteralType<"9">]>]>, TemplateLiteralType<readonly ["5", UnionType<readonly [LiteralType<"0">, LiteralType<"1">]>]>]>;
5262
+ export declare const Digit1To51: UnionType<readonly [
5263
+ typeof Digit1To9,
5264
+ TemplateLiteralType<readonly [
5265
+ UnionType<readonly [
5266
+ LiteralType<"1">,
5267
+ LiteralType<"2">,
5268
+ LiteralType<"3">,
5269
+ LiteralType<"4">
5270
+ ]>,
5271
+ typeof Digit
5272
+ ]>,
5273
+ TemplateLiteralType<readonly ["5", UnionType<readonly [LiteralType<"0">, LiteralType<"1">]>]>
5274
+ ]>;
4182
5275
  export type Digit1To51 = typeof Digit1To51.Output;
4183
5276
  /**
4184
- * Decimal string from `"1"` to `"99"`.
5277
+ * Decimal integer string from `"1"` to `"99"`, without a leading zero.
4185
5278
  *
4186
5279
  * @group String
4187
5280
  */
4188
- export declare const Digit1To99: UnionType<readonly [UnionType<readonly [LiteralType<"1">, LiteralType<"2">, LiteralType<"3">, LiteralType<"4">, LiteralType<"5">, LiteralType<"6">, LiteralType<"7">, LiteralType<"8">, LiteralType<"9">]>, TemplateLiteralType<readonly [UnionType<readonly [LiteralType<"1">, LiteralType<"2">, LiteralType<"3">, LiteralType<"4">, LiteralType<"5">, LiteralType<"6">, LiteralType<"7">, LiteralType<"8">, LiteralType<"9">]>, UnionType<readonly [LiteralType<"0">, LiteralType<"1">, LiteralType<"2">, LiteralType<"3">, LiteralType<"4">, LiteralType<"5">, LiteralType<"6">, LiteralType<"7">, LiteralType<"8">, LiteralType<"9">]>]>]>;
5281
+ export declare const Digit1To99: UnionType<readonly [
5282
+ typeof Digit1To9,
5283
+ TemplateLiteralType<readonly [typeof Digit1To9, typeof Digit]>
5284
+ ]>;
4189
5285
  export type Digit1To99 = typeof Digit1To99.Output;
4190
5286
  /**
4191
- * Decimal string from `"1"` to `"59"`.
5287
+ * Decimal integer string from `"1"` to `"59"`, without a leading zero.
4192
5288
  *
4193
5289
  * @group String
4194
5290
  */
4195
- export declare const Digit1To59: UnionType<readonly [UnionType<readonly [LiteralType<"1">, LiteralType<"2">, LiteralType<"3">, LiteralType<"4">, LiteralType<"5">, LiteralType<"6">, LiteralType<"7">, LiteralType<"8">, LiteralType<"9">]>, TemplateLiteralType<readonly [UnionType<readonly [LiteralType<"1">, LiteralType<"2">, LiteralType<"3">, LiteralType<"4">, LiteralType<"5">]>, UnionType<readonly [LiteralType<"0">, LiteralType<"1">, LiteralType<"2">, LiteralType<"3">, LiteralType<"4">, LiteralType<"5">, LiteralType<"6">, LiteralType<"7">, LiteralType<"8">, LiteralType<"9">]>]>]>;
5291
+ export declare const Digit1To59: UnionType<readonly [
5292
+ typeof Digit1To9,
5293
+ TemplateLiteralType<readonly [
5294
+ UnionType<readonly [
5295
+ LiteralType<"1">,
5296
+ LiteralType<"2">,
5297
+ LiteralType<"3">,
5298
+ LiteralType<"4">,
5299
+ LiteralType<"5">
5300
+ ]>,
5301
+ typeof Digit
5302
+ ]>
5303
+ ]>;
4196
5304
  export type Digit1To59 = typeof Digit1To59.Output;
4197
5305
  type PlainObjectError = ObjectError<Readonly<Record<never, never>>, ObjectPropertyAccessError | ObjectExcessPropertyError>;
4198
5306
  /**
@@ -4301,6 +5409,7 @@ export { _Object as Object };
4301
5409
  * const valueType = typeof value;
4302
5410
  *
4303
5411
  * assertEqual(valueType, "function");
5412
+ *
4304
5413
  * const called = trySync(
4305
5414
  * () => {
4306
5415
  * if (value !== undefined) value.toFixed(0);
@@ -4336,6 +5445,7 @@ export { _Object as Object };
4336
5445
  * const valueType = typeof value;
4337
5446
  *
4338
5447
  * assertEqual(valueType, "function");
5448
+ *
4339
5449
  * const called = trySync(
4340
5450
  * () => {
4341
5451
  * if (value !== undefined) value.toFixed(0);
@@ -4370,6 +5480,16 @@ interface RecordTypeNode extends TypeNode {
4370
5480
  interface ObjectRecordTypeNode extends RecordTypeNode {
4371
5481
  readonly key: typeof String;
4372
5482
  }
5483
+ type RuntimeRecordKeyTypeNode = Omit<RuntimeTypeNode, "fromUnknown" | "from" | "to"> & {
5484
+ readonly fromUnknown: (value: unknown, options: ValidationOptions) => Result<string, TypeError>;
5485
+ readonly from: RuntimeOperation<Result<string, TypeError>>;
5486
+ readonly to: (value: never) => string;
5487
+ };
5488
+ interface RuntimeRecordTypeNode extends RuntimeTypeNode {
5489
+ readonly name: "Record";
5490
+ readonly key: RuntimeRecordKeyTypeNode;
5491
+ readonly value: RuntimeTypeNode;
5492
+ }
4373
5493
  type RecordShape<KeyType extends TypeNode, ValueType extends TypeNode, Field extends "Input" | "Output" | "CanonicalInput"> = [Extract<RecordTypeField<KeyType, Field>, string>] extends [never] ? Readonly<Record<string, never>> : Readonly<Partial<Record<Extract<RecordTypeField<KeyType, Field>, string>, RecordTypeField<ValueType, Field>>>>;
4374
5494
  type RecordTypeField<T extends TypeNode, Field extends "Input" | "Output" | "CanonicalInput"> = Field extends "CanonicalInput" ? CanonicalInputOf<T> : T[Field];
4375
5495
  type RecordParent<KeyType extends TypeNode, ValueType extends TypeNode> = [
@@ -4499,6 +5619,7 @@ interface RecordEntriesErrorValue<KeyError extends TypeError, ValueError extends
4499
5619
  export interface OptionalProperty<T extends TypeNode> {
4500
5620
  readonly type: T;
4501
5621
  readonly [errorsSymbol]: InferErrors<T>;
5622
+ readonly [optionalPropertySymbol]: true;
4502
5623
  }
4503
5624
  /**
4504
5625
  * Optional {@link object} property.
@@ -4534,13 +5655,147 @@ export interface OptionalProperty<T extends TypeNode> {
4534
5655
  * @group Objects
4535
5656
  */
4536
5657
  export declare const optional: <T extends TypeNode>(type: ValidateOptionalPropertyType<T>) => OptionalProperty<T>;
5658
+ declare const optionalPropertySymbolType: unique symbol;
5659
+ declare const optionalPropertySymbol: typeof optionalPropertySymbolType;
4537
5660
  type ValidateOptionalPropertyType<T extends TypeNode> = IsUnion<T> extends false ? T extends ConcreteTypeNode ? T : ObjectPropertyTypeError : ObjectPropertyTypeError;
5661
+ /**
5662
+ * A value decoded by {@link withDefault} with the `preserve` strategy.
5663
+ *
5664
+ * `defaultUsed` distinguishes supplied values from defaults, even when their
5665
+ * values are equal. `original` records the decoded absence, not the raw input
5666
+ * before other transformations.
5667
+ *
5668
+ * @group Construction
5669
+ */
5670
+ export type Defaulted<Value, Default = Value, Original extends "missing" | "null" | "undefined" = "missing" | "null" | "undefined"> = {
5671
+ readonly value: Value;
5672
+ readonly defaultUsed: false;
5673
+ } | {
5674
+ readonly value: Default;
5675
+ readonly defaultUsed: true;
5676
+ readonly original: Original;
5677
+ };
5678
+ /**
5679
+ * The value Type returned by {@link withDefault}.
5680
+ *
5681
+ * @group Construction
5682
+ */
5683
+ export interface WithDefaultType<T extends TypeNode, Value, Strategy extends "replace" | "preserve"> extends Type<"WithDefault", T["Input"], WithDefaultOutput<T, Value, Strategy>, never, T, InferErrors<T>, ChildCustomFrom<T, WithDefaultOutput<T, Value, Strategy>, never>, T["CanonicalInput"], false> {
5684
+ }
5685
+ /**
5686
+ * An optional input property made required in the Output by {@link withDefault}.
5687
+ *
5688
+ * @group Objects
5689
+ */
5690
+ export interface WithDefaultProperty<T extends TypeNode, Value, Strategy extends "replace" | "preserve"> {
5691
+ readonly type: T;
5692
+ readonly value: Value;
5693
+ readonly strategy: Strategy;
5694
+ /** @ignore */
5695
+ readonly [errorsSymbol]: InferErrors<T>;
5696
+ /** @ignore */
5697
+ readonly [defaultPropertySymbol]: RuntimeDefaultOperations;
5698
+ }
5699
+ /**
5700
+ * Supplies a decoded default for absence accepted by another {@link Type}.
5701
+ *
5702
+ * You might not need this: use `??` where a value is consumed if the default
5703
+ * does not need to become part of the decoded data. By default, replacement
5704
+ * loses the distinction between absence and an explicitly supplied value. Set
5705
+ * `strategy: "preserve"` to retain it.
5706
+ *
5707
+ * Defaults apply after decoding, to `null` and `undefined` accepted by the
5708
+ * wrapped Type, and to missing properties when wrapping {@link optional}. Types
5709
+ * whose Output excludes both `null` and `undefined` require `optional`;
5710
+ * otherwise there is no accepted absence to default. Invalid supplied values
5711
+ * still fail. An optional property does not accept an explicit `undefined`
5712
+ * unless its Type does. The default must be an Output of the wrapped Type; it
5713
+ * is checked when constructing the declaration. It may itself be `null` or
5714
+ * `undefined` when the wrapped Output accepts that value.
5715
+ *
5716
+ * Omitting `strategy` returns the effective value and encodes it as supplied
5717
+ * data. `strategy: "preserve"` returns {@link Defaulted} and restores absence
5718
+ * when encoding. A supplied value equal to the default still has `defaultUsed:
5719
+ * false`. Preserved defaults must equal the configured value: {@link Data}
5720
+ * values use structural equality, and other values use reference identity.
5721
+ * Every decoded absence reuses the configured default by reference; defaults
5722
+ * are not cloned. Treat default values as immutable, like other Type
5723
+ * declarations.
5724
+ *
5725
+ * Pass the default value as the second argument. Add `{ strategy: "preserve" }`
5726
+ * as the third argument to retain supplied-input evidence. Explicit `strategy:
5727
+ * "replace"` is not accepted.
5728
+ *
5729
+ * ### Example
5730
+ *
5731
+ * ```ts
5732
+ * import {
5733
+ * assertEqual,
5734
+ * assertErr,
5735
+ * assertOk,
5736
+ * Boolean,
5737
+ * nullOr,
5738
+ * object,
5739
+ * optional,
5740
+ * withDefault,
5741
+ * } from "@evolu/common";
5742
+ *
5743
+ * const Settings = object({
5744
+ * enabled: withDefault(optional(Boolean), true, {
5745
+ * strategy: "preserve",
5746
+ * }),
5747
+ * });
5748
+ *
5749
+ * const missing = Settings.fromUnknown({});
5750
+ * assertOk(missing, {
5751
+ * enabled: { value: true, defaultUsed: true, original: "missing" },
5752
+ * });
5753
+ * assertEqual(Settings.to(missing.value), {});
5754
+ *
5755
+ * assertOk(Settings.fromUnknown({ enabled: true }), {
5756
+ * enabled: { value: true, defaultUsed: false },
5757
+ * });
5758
+ * assertErr(Settings.fromUnknown({ enabled: undefined }));
5759
+ *
5760
+ * const Enabled = withDefault(nullOr(Boolean), true);
5761
+ *
5762
+ * assertOk(Enabled.fromUnknown(null), true);
5763
+ * assertEqual(Enabled.to(true), true);
5764
+ * ```
5765
+ *
5766
+ * @group Construction
5767
+ */
5768
+ export declare function withDefault<T extends TypeNode, const Value extends NoInfer<T["Output"]>>(property: OptionalProperty<T> & {
5769
+ readonly type: ValidateDefaultType<T, true>;
5770
+ }, value: Value): WithDefaultProperty<T, Value, "replace">;
5771
+ export declare function withDefault<T extends TypeNode, const Value extends NoInfer<T["Output"]>>(property: OptionalProperty<T> & {
5772
+ readonly type: ValidateDefaultType<T, true>;
5773
+ }, value: Value, options: {
5774
+ readonly strategy: "preserve";
5775
+ }): WithDefaultProperty<T, Value, "preserve">;
5776
+ export declare function withDefault<T extends TypeNode, const Value extends NoInfer<T["Output"]>>(type: T & ValidateDefaultType<T>, value: Value): WithDefaultType<T, Value, "replace">;
5777
+ export declare function withDefault<T extends TypeNode, const Value extends NoInfer<T["Output"]>>(type: T & ValidateDefaultType<T>, value: Value, options: {
5778
+ readonly strategy: "preserve";
5779
+ }): WithDefaultType<T, Value, "preserve">;
5780
+ type ValidateDefaultType<T extends TypeNode, Optional extends boolean = false> = ValidateOptionalPropertyType<T> & ([ChildTypeNameValidationError<"WithDefault", T>] extends [never] ? unknown : ChildTypeNameValidationError<"WithDefault", T>) & (Optional extends true ? unknown : [DefaultOriginal<T>] extends [never] ? CompileTimeError<"Type", "withDefault requires an optional property or a Type whose Output includes null or undefined."> : unknown);
5781
+ type DefaultOriginal<T extends TypeNode> = (null extends T["Output"] ? "null" : never) | (undefined extends T["Output"] ? "undefined" : never);
5782
+ type WithDefaultOutput<T extends TypeNode, Value, Strategy extends "replace" | "preserve", Original extends "missing" | "null" | "undefined" = DefaultOriginal<T>> = Strategy extends "replace" ? NonNullable<T["Output"]> | Value : Defaulted<NonNullable<T["Output"]>, Value, Original>;
5783
+ declare const defaultPropertySymbolType: unique symbol;
5784
+ declare const defaultPropertySymbol: typeof defaultPropertySymbolType;
5785
+ interface RuntimeDefaultOperations {
5786
+ readonly output: RuntimeTypeNode;
5787
+ readonly decode: (value: unknown) => unknown;
5788
+ readonly encode: (value: never) => unknown;
5789
+ readonly createObject: (props: ObjectProps, record: RuntimeRecordTypeNode | undefined) => ObjectTypeNode;
5790
+ readonly partial: (type: RuntimeTypeNode) => OptionalProperty<TypeNode>;
5791
+ }
4538
5792
  /**
4539
5793
  * Properties used to construct an {@link object} Type.
4540
5794
  *
4541
5795
  * @group Objects
4542
5796
  */
4543
- export type ObjectProps = Readonly<Record<string, TypeNode | OptionalProperty<TypeNode>>>;
5797
+ export type ObjectProps = Readonly<Record<string, TypeNode | OptionalProperty<TypeNode> | WithDefaultProperty<TypeNode, unknown, "replace" | "preserve">>>;
5798
+ type ObjectValueProps = Readonly<Record<string, TypeNode | OptionalProperty<TypeNode>>>;
4544
5799
  type ObjectProperty = ObjectProps[string];
4545
5800
  /**
4546
5801
  * Plain object {@link Type}.
@@ -4548,7 +5803,10 @@ type ObjectProperty = ObjectProps[string];
4548
5803
  * Use `object(props)` for objects with fixed property names. Properties are
4549
5804
  * required unless wrapped with {@link optional}. An optional property may be
4550
5805
  * absent, but a present value is still validated and does not implicitly accept
4551
- * `undefined`.
5806
+ * `undefined`. Wrapping an optional property with {@link withDefault} keeps the
5807
+ * input optional and makes the decoded output required. A default for a
5808
+ * nullable or undefined-accepting Type alone does not make the property
5809
+ * optional.
4552
5810
  *
4553
5811
  * Without a second argument, `fromUnknown` rejects additional properties. Pass
4554
5812
  * a {@link record} with the predefined {@link String} key Type to validate and
@@ -4636,6 +5894,7 @@ type ObjectProperty = ObjectProps[string];
4636
5894
  * const valueType = typeof value;
4637
5895
  *
4638
5896
  * assertEqual(valueType, "function");
5897
+ *
4639
5898
  * const called = trySync(
4640
5899
  * () => {
4641
5900
  * if (value !== undefined) value.toFixed(0);
@@ -4671,6 +5930,7 @@ type ObjectProperty = ObjectProps[string];
4671
5930
  * const valueType = typeof value;
4672
5931
  *
4673
5932
  * assertEqual(valueType, "function");
5933
+ *
4674
5934
  * const called = trySync(
4675
5935
  * () => {
4676
5936
  * if (value !== undefined) value.toFixed(0);
@@ -4686,7 +5946,7 @@ type ObjectProperty = ObjectProps[string];
4686
5946
  *
4687
5947
  * @group Objects
4688
5948
  */
4689
- export declare function object<const Props extends ObjectProps>(props: Props, ...validation: [ObjectValidationError<Props>] extends [never] ? [] : [ValidationFailure<ObjectValidationError<Props>>]): StrictObjectType<Props>;
5949
+ export declare function object<const Props extends ObjectValueProps>(props: Props, ...validation: [ObjectValidationError<Props>] extends [never] ? [] : [ValidationFailure<ObjectValidationError<Props>>]): StrictObjectType<Props>;
4690
5950
  /**
4691
5951
  * Creates an Object Type with additional record properties.
4692
5952
  *
@@ -4705,6 +5965,7 @@ export declare function object<const Props extends ObjectProps>(props: Props, ..
4705
5965
  * { authorization: String },
4706
5966
  * record(String, String),
4707
5967
  * );
5968
+ *
4708
5969
  * const result = RequestHeaders.fromUnknown({
4709
5970
  * authorization: "Bearer token",
4710
5971
  * "x-request-id": "request-1",
@@ -4717,11 +5978,22 @@ export declare function object<const Props extends ObjectProps>(props: Props, ..
4717
5978
  * assertType<(typeof result.value)["x-request-id"], string | undefined>();
4718
5979
  * ```
4719
5980
  */
4720
- export declare function object<const Props extends ObjectProps, const Rest extends RecordTypeNode & ConcreteTypeNode>(props: Props, record: Rest, ...validation: [
5981
+ export declare function object<const Props extends ObjectValueProps, const Rest extends RecordTypeNode & ConcreteTypeNode>(props: Props, record: Rest, ...validation: [
4721
5982
  ObjectValidationError<Props> | ObjectRecordValidationError<Props, Rest>
4722
5983
  ] extends [never] ? [] : [
4723
5984
  ValidationFailure<ObjectValidationError<Props> | ObjectRecordValidationError<Props, Rest>>
4724
5985
  ]): ObjectWithRecordType<Props, Rest extends ObjectRecordTypeNode ? Rest : never>;
5986
+ /** Creates an Object Type with explicit defaults for optional inputs. */
5987
+ export declare function object<const Props extends ObjectProps>(props: Props, ...validation: [ObjectValidationError<Props>] extends [never] ? [] : [ValidationFailure<ObjectValidationError<Props>>]): ObjectType<Props>;
5988
+ /**
5989
+ * Creates an Object Type with explicit defaults and additional record
5990
+ * properties.
5991
+ */
5992
+ export declare function object<const Props extends ObjectProps, const Rest extends ObjectRecordTypeNode & ConcreteTypeNode>(props: Props, record: Rest, ...validation: [
5993
+ ObjectValidationError<Props> | ObjectRecordValidationError<Props, Rest>
5994
+ ] extends [never] ? [] : [
5995
+ ValidationFailure<ObjectValidationError<Props> | ObjectRecordValidationError<Props, Rest>>
5996
+ ]): ObjectType<Props, Rest>;
4725
5997
  interface ObjectTypeNode extends TypeNode {
4726
5998
  readonly name: "Object";
4727
5999
  readonly props: ObjectProps;
@@ -4732,16 +6004,18 @@ type ObjectValidationError<Props extends ObjectProps> = IsUnion<Props> extends f
4732
6004
  }[keyof Props] : ObjectPropsTypeError;
4733
6005
  type ObjectRecordValidationError<Props extends ObjectProps, Rest extends RecordTypeNode & ConcreteTypeNode> = IsUnion<Rest> extends false ? Rest["key"] extends typeof String ? typeof String extends Rest["key"] ? ObjectRecordPropertyValidationError<Props, Rest["value"]> : ObjectRecordKeyTypeError : ObjectRecordKeyTypeError : ObjectRecordTypeError;
4734
6006
  type ObjectRecordPropertyValidationError<Props extends ObjectProps, ValueType extends TypeNode> = [
4735
- ObjectPropertyType<Props[keyof Props]>["Input"],
4736
- ObjectPropertyType<Props[keyof Props]>["Output"],
4737
- ObjectPropertyType<Props[keyof Props]>["CanonicalInput"]
6007
+ DefaultableObjectPropertyType<Props[keyof Props]>["Input"],
6008
+ DefaultableObjectPropertyType<Props[keyof Props]>["Output"],
6009
+ DefaultableObjectPropertyType<Props[keyof Props]>["CanonicalInput"]
4738
6010
  ] extends [ValueType["Input"], ValueType["Output"], ValueType["CanonicalInput"]] ? never : ObjectRecordPropertyTypeError<Props, ValueType>;
4739
- type ObjectRecordPropertyTypeError<Props extends ObjectProps, ValueType extends TypeNode> = [ObjectPropertyType<Props[keyof Props]>["Input"]] extends [
6011
+ type ObjectRecordPropertyTypeError<Props extends ObjectProps, ValueType extends TypeNode> = [DefaultableObjectPropertyType<Props[keyof Props]>["Input"]] extends [
4740
6012
  ValueType["Input"]
4741
- ] ? [ObjectPropertyType<Props[keyof Props]>["Output"]] extends [
6013
+ ] ? [DefaultableObjectPropertyType<Props[keyof Props]>["Output"]] extends [
4742
6014
  ValueType["Output"]
4743
6015
  ] ? ObjectRecordCanonicalInputTypeError : ObjectRecordOutputTypeError : ObjectRecordInputTypeError;
4744
- type ObjectPropertyValidationError<Property extends ObjectProperty> = IsUnion<Property> extends false ? Property extends OptionalProperty<infer T extends TypeNode> ? ObjectPropertyTypeValidationError<T> : Property extends TypeNode ? ObjectPropertyTypeValidationError<Property> : ObjectPropertyTypeError : ObjectPropertyTypeError;
6016
+ type ObjectPropertyValidationError<Property extends ObjectProperty> = IsUnion<Property> extends false ? Property extends TypeNode ? ObjectPropertyTypeValidationError<Property> : Property extends {
6017
+ readonly type: infer T extends TypeNode;
6018
+ } ? ObjectPropertyTypeValidationError<T> : ObjectPropertyTypeError : ObjectPropertyTypeError;
4745
6019
  type ObjectPropertyTypeValidationError<T extends TypeNode> = IsUnion<T> extends false ? T extends ConcreteTypeNode ? [
4746
6020
  Extract<"ObjectMissingProperty" | "ObjectPropertyAccess" | "ObjectExcessProperty", InferErrors<T>["type"]>
4747
6021
  ] extends [never] ? never : CompileTimeError<"Type", "Property Type must not use an error tag reserved for Object structure."> : ObjectPropertyTypeError : ObjectPropertyTypeError;
@@ -4763,60 +6037,73 @@ type ObjectRecordCanonicalInputTypeError = CompileTimeError<"Type", "Every decla
4763
6037
  *
4764
6038
  * @group Objects
4765
6039
  */
4766
- export type ObjectType<Props extends ObjectProps, Rest extends ObjectRecordTypeNode | undefined = undefined> = Rest extends ObjectRecordTypeNode ? ObjectWithRecordType<Props, Rest> : StrictObjectType<Props>;
4767
- type StrictObjectType<Props extends ObjectProps> = Type<"Object", StrictObjectShape<Props, "Input">, StrictObjectShape<Props, "Output">, [
6040
+ export type ObjectType<Props extends ObjectProps, Rest extends ObjectRecordTypeNode | undefined = undefined> = Props extends ObjectValueProps ? Rest extends ObjectRecordTypeNode ? ObjectWithRecordType<Props, Rest> : StrictObjectType<Props> : DefaultObjectType<Props, Rest>;
6041
+ type ObjectValueType<Props extends ObjectValueProps, Rest extends ObjectRecordTypeNode | undefined> = Rest extends ObjectRecordTypeNode ? ObjectWithRecordType<Props, Rest> : StrictObjectType<Props>;
6042
+ type DefaultableObjectPropertyType<Property extends ObjectProperty> = Property extends WithDefaultProperty<infer T, infer Value, infer Strategy> ? Type<"WithDefault", T["Input"], WithDefaultOutput<T, Value, Strategy, "missing" | DefaultOriginal<T>>, never, T, InferErrors<T>, never, T["CanonicalInput"], false> : Property extends TypeNode | OptionalProperty<TypeNode> ? ObjectPropertyType<Property> : never;
6043
+ type DefaultInputProps<Props extends ObjectProps> = {
6044
+ readonly [Key in keyof Props]: Props[Key] extends WithDefaultProperty<infer T, unknown, "replace" | "preserve"> ? OptionalProperty<T> : Extract<Props[Key], TypeNode | OptionalProperty<TypeNode>>;
6045
+ };
6046
+ type DefaultOutputProps<Props extends ObjectProps> = {
6047
+ readonly [Key in keyof Props]: Props[Key] extends WithDefaultProperty<infer T, infer Value, infer Strategy> ? Type<"DefaultValue", WithDefaultOutput<T, Value, Strategy, "missing" | DefaultOriginal<T>>, WithDefaultOutput<T, Value, Strategy, "missing" | DefaultOriginal<T>>, never> : Extract<Props[Key], TypeNode | OptionalProperty<TypeNode>>;
6048
+ };
6049
+ type DefaultObjectType<Props extends ObjectProps, Rest extends ObjectRecordTypeNode | undefined> = Type<"Object", ObjectValueType<DefaultInputProps<Props>, Rest>["Input"], ObjectValueType<DefaultOutputProps<Props>, Rest>["Output"], TypeFromError<ObjectValueType<DefaultInputProps<Props>, Rest>>, RootType<ObjectValueType<DefaultInputProps<Props>, Rest>>, InferErrors<ObjectValueType<DefaultInputProps<Props>, Rest>>, never, ObjectValueType<DefaultInputProps<Props>, Rest>["CanonicalInput"], false> & {
6050
+ readonly props: Readonly<Props>;
6051
+ } & (Rest extends ObjectRecordTypeNode ? {
6052
+ readonly record: Rest;
6053
+ } : {});
6054
+ type StrictObjectType<Props extends ObjectValueProps> = Type<"Object", StrictObjectShape<Props, "Input">, StrictObjectShape<Props, "Output">, [
4768
6055
  StrictObjectParents<Props>
4769
6056
  ] extends [null] ? StrictObjectFromUnknownError<StrictObjectFromUnknownPropertyErrors<Props>> : ObjectFromParentError<ObjectFromParentPropertyErrors<Props>>, [
4770
6057
  StrictObjectParents<Props>
4771
6058
  ] extends [null] ? null : StrictRootObjectType<RootObjectProps<Props>>, StrictObjectFromUnknownError<StrictObjectFromUnknownPropertyErrors<Props>>, never, StrictObjectShape<Props, "CanonicalInput">, AllTypesUseIdentityEncoding<ObjectPropertyType<Props[keyof Props]>>> & {
4772
6059
  readonly props: Readonly<Props>;
4773
6060
  };
4774
- type ObjectWithRecordType<Props extends ObjectProps, Rest extends ObjectRecordTypeNode> = Type<"Object", ObjectWithRecordShape<Props, Rest, "Input">, ObjectWithRecordShape<Props, Rest, "Output">, [
6061
+ type ObjectWithRecordType<Props extends ObjectValueProps, Rest extends ObjectRecordTypeNode> = Type<"Object", ObjectWithRecordShape<Props, Rest, "Input">, ObjectWithRecordShape<Props, Rest, "Output">, [
4775
6062
  ObjectWithRecordParents<Props, Rest>
4776
6063
  ] extends [null] ? ObjectError<ObjectDeclaredErrors<Props>, ObjectRestFromUnknownError<Rest>> : ObjectPropertiesError<ObjectFromParentPropertyErrors<Props>, ObjectRestFromParentError<Rest>>, [
4777
6064
  ObjectWithRecordParents<Props, Rest>
4778
6065
  ] extends [null] ? null : RootObjectWithRecordType<RootObjectProps<Props>, RootObjectRecord<Rest>>, ObjectError<ObjectDeclaredErrors<Props>, ObjectRestFromUnknownError<Rest>>, never, ObjectWithRecordShape<Props, Rest, "CanonicalInput">, AllTypesUseIdentityEncoding<ObjectPropertyType<Props[keyof Props]> | Rest>> & ObjectWithRecordReflection<Props, Rest>;
4779
- type StrictObjectShape<Props extends ObjectProps, Field extends "Input" | "Output" | "CanonicalInput"> = keyof Props extends never ? Readonly<Record<string, never>> : {
6066
+ type StrictObjectShape<Props extends ObjectValueProps, Field extends "Input" | "Output" | "CanonicalInput"> = keyof Props extends never ? Readonly<Record<string, never>> : {
4780
6067
  readonly [Key in RequiredObjectKeys<Props>]: ObjectTypeField<ObjectPropertyType<Props[Key]>, Field>;
4781
6068
  } & {
4782
6069
  readonly [Key in OptionalObjectKeys<Props>]?: ObjectTypeField<ObjectPropertyType<Props[Key]>, Field>;
4783
6070
  };
4784
- type ObjectWithRecordShape<Props extends ObjectProps, Rest extends ObjectRecordTypeNode, Field extends "Input" | "Output" | "CanonicalInput"> = keyof Props extends never ? ObjectRestShape<Rest, Field> : ObjectDeclaredShape<Props, Field> & ObjectRestShape<Rest, Field>;
4785
- type ObjectDeclaredShape<Props extends ObjectProps, Field extends "Input" | "Output" | "CanonicalInput"> = Simplify<{
6071
+ type ObjectWithRecordShape<Props extends ObjectValueProps, Rest extends ObjectRecordTypeNode, Field extends "Input" | "Output" | "CanonicalInput"> = keyof Props extends never ? ObjectRestShape<Rest, Field> : ObjectDeclaredShape<Props, Field> & ObjectRestShape<Rest, Field>;
6072
+ type ObjectDeclaredShape<Props extends ObjectValueProps, Field extends "Input" | "Output" | "CanonicalInput"> = Simplify<{
4786
6073
  readonly [Key in RequiredObjectKeys<Props>]: ObjectTypeField<ObjectPropertyType<Props[Key]>, Field>;
4787
6074
  } & {
4788
6075
  readonly [Key in OptionalObjectKeys<Props>]?: ObjectTypeField<ObjectPropertyType<Props[Key]>, Field>;
4789
6076
  }>;
4790
6077
  type ObjectRestShape<Rest extends ObjectRecordTypeNode, Field extends "Input" | "Output" | "CanonicalInput"> = Readonly<Partial<Record<string, ObjectTypeField<Rest["value"], Field>>>>;
4791
6078
  type ObjectTypeField<T extends TypeNode, Field extends "Input" | "Output" | "CanonicalInput"> = Field extends "CanonicalInput" ? CanonicalInputOf<T> : T[Field];
4792
- type RequiredObjectKeys<Props extends ObjectProps> = Exclude<keyof Props, OptionalObjectKeys<Props>>;
4793
- type OptionalObjectKeys<Props extends ObjectProps> = {
6079
+ type RequiredObjectKeys<Props extends ObjectValueProps> = Exclude<keyof Props, OptionalObjectKeys<Props>>;
6080
+ type OptionalObjectKeys<Props extends ObjectValueProps> = {
4794
6081
  readonly [Key in keyof Props]: Props[Key] extends OptionalProperty<TypeNode> ? Key : never;
4795
6082
  }[keyof Props];
4796
- type ObjectPropertyType<Property extends ObjectProperty> = Property extends OptionalProperty<infer T> ? T : Property;
4797
- type ObjectFromParentPropertyErrors<Props extends ObjectProps> = {
6083
+ type ObjectPropertyType<Property extends ObjectValueProps[string]> = Property extends OptionalProperty<infer T> ? T : Property;
6084
+ type ObjectFromParentPropertyErrors<Props extends ObjectValueProps> = {
4798
6085
  readonly [Key in keyof Props]: TypeFromError<ObjectPropertyType<Props[Key]>>;
4799
6086
  };
4800
- type StrictRootObjectType<Props extends ObjectProps> = Type<"Object", StrictObjectShape<Props, "Input">, StrictObjectShape<Props, "Output">, StrictObjectFromUnknownError<StrictObjectFromUnknownPropertyErrors<Props>>, null, StrictObjectFromUnknownError<StrictObjectFromUnknownPropertyErrors<Props>>, never, StrictObjectShape<Props, "CanonicalInput">, AllTypesUseIdentityEncoding<ObjectPropertyType<Props[keyof Props]>>> & {
6087
+ type StrictRootObjectType<Props extends ObjectValueProps> = Type<"Object", StrictObjectShape<Props, "Input">, StrictObjectShape<Props, "Output">, StrictObjectFromUnknownError<StrictObjectFromUnknownPropertyErrors<Props>>, null, StrictObjectFromUnknownError<StrictObjectFromUnknownPropertyErrors<Props>>, never, StrictObjectShape<Props, "CanonicalInput">, AllTypesUseIdentityEncoding<ObjectPropertyType<Props[keyof Props]>>> & {
4801
6088
  readonly props: Readonly<Props>;
4802
6089
  };
4803
- type RootObjectWithRecordType<Props extends ObjectProps, Rest extends ObjectRecordTypeNode> = Type<"Object", ObjectWithRecordShape<Props, Rest, "Input">, ObjectWithRecordShape<Props, Rest, "Output">, ObjectError<ObjectDeclaredErrors<Props>, ObjectRestFromUnknownError<Rest>>, null, ObjectError<ObjectDeclaredErrors<Props>, ObjectRestFromUnknownError<Rest>>, never, ObjectWithRecordShape<Props, Rest, "CanonicalInput">, AllTypesUseIdentityEncoding<ObjectPropertyType<Props[keyof Props]> | Rest>> & ObjectWithRecordReflection<Props, Rest>;
4804
- type StrictObjectParents<Props extends ObjectProps> = ObjectPropertyType<Props[keyof Props]>["parent"];
4805
- type ObjectWithRecordParents<Props extends ObjectProps, Rest extends ObjectRecordTypeNode> = ObjectPropertyType<Props[keyof Props]>["parent"] | Rest["parent"];
4806
- type RootObjectProps<Props extends ObjectProps> = {
6090
+ type RootObjectWithRecordType<Props extends ObjectValueProps, Rest extends ObjectRecordTypeNode> = Type<"Object", ObjectWithRecordShape<Props, Rest, "Input">, ObjectWithRecordShape<Props, Rest, "Output">, ObjectError<ObjectDeclaredErrors<Props>, ObjectRestFromUnknownError<Rest>>, null, ObjectError<ObjectDeclaredErrors<Props>, ObjectRestFromUnknownError<Rest>>, never, ObjectWithRecordShape<Props, Rest, "CanonicalInput">, AllTypesUseIdentityEncoding<ObjectPropertyType<Props[keyof Props]> | Rest>> & ObjectWithRecordReflection<Props, Rest>;
6091
+ type StrictObjectParents<Props extends ObjectValueProps> = ObjectPropertyType<Props[keyof Props]>["parent"];
6092
+ type ObjectWithRecordParents<Props extends ObjectValueProps, Rest extends ObjectRecordTypeNode> = ObjectPropertyType<Props[keyof Props]>["parent"] | Rest["parent"];
6093
+ type RootObjectProps<Props extends ObjectValueProps> = {
4807
6094
  readonly [Key in keyof Props]: Props[Key] extends OptionalProperty<infer T> ? OptionalProperty<RootType<T>> : Props[Key] extends TypeNode ? RootType<Props[Key]> : never;
4808
6095
  };
4809
6096
  type RootObjectRecord<Rest extends ObjectRecordTypeNode> = RecordType<typeof String, RootType<Rest["value"]>>;
4810
- interface ObjectWithRecordReflection<Props extends ObjectProps, Rest extends ObjectRecordTypeNode> {
6097
+ interface ObjectWithRecordReflection<Props extends ObjectValueProps, Rest extends ObjectRecordTypeNode> {
4811
6098
  readonly props: Readonly<Props>;
4812
6099
  readonly record: Rest;
4813
6100
  }
4814
- type ObjectDeclaredErrors<Props extends ObjectProps> = {
6101
+ type ObjectDeclaredErrors<Props extends ObjectValueProps> = {
4815
6102
  readonly [Key in RequiredObjectKeys<Props>]: Props[Key][typeof errorsSymbol];
4816
6103
  } & {
4817
6104
  readonly [Key in OptionalObjectKeys<Props>]?: Props[Key][typeof errorsSymbol];
4818
6105
  };
4819
- type StrictObjectFromUnknownPropertyErrors<Props extends ObjectProps> = {
6106
+ type StrictObjectFromUnknownPropertyErrors<Props extends ObjectValueProps> = {
4820
6107
  readonly [Key in RequiredObjectKeys<Props>]: Props[Key][typeof errorsSymbol] | ObjectMissingPropertyError | ObjectPropertyAccessError;
4821
6108
  } & {
4822
6109
  readonly [Key in OptionalObjectKeys<Props>]: Props[Key][typeof errorsSymbol] | ObjectPropertyAccessError;
@@ -4933,7 +6220,8 @@ type ObjectRestFromParentError<Rest extends ObjectRecordTypeNode | undefined> =
4933
6220
  * Object {@link Type} with every property optional.
4934
6221
  *
4935
6222
  * No property is required, but every present property must still satisfy its
4936
- * Type.
6223
+ * Type. For {@link withDefault} properties, this disables the missing-property
6224
+ * default; defaults for present `null` or `undefined` values still apply.
4937
6225
  *
4938
6226
  * ### Example
4939
6227
  *
@@ -4963,7 +6251,7 @@ export declare const partial: <const Props extends ObjectProps>(props: Props, ..
4963
6251
  * @group Objects
4964
6252
  */
4965
6253
  export type PartialObjectProps<Props extends ObjectProps> = {
4966
- readonly [Key in keyof Props]: Props[Key] extends OptionalProperty<TypeNode> ? Props[Key] : Props[Key] extends TypeNode ? OptionalProperty<Props[Key]> : never;
6254
+ readonly [Key in keyof Props]: Props[Key] extends WithDefaultProperty<infer T, infer Value, infer Strategy> ? OptionalProperty<WithDefaultType<T, Value, Strategy>> : Props[Key] extends OptionalProperty<TypeNode> ? Props[Key] : Props[Key] extends TypeNode ? OptionalProperty<Props[Key]> : never;
4967
6255
  };
4968
6256
  /**
4969
6257
  * Object {@link Type} making every property whose Union Type includes
@@ -5023,12 +6311,91 @@ export type NullableToOptionalProps<Props extends ObjectProps> = {
5023
6311
  *
5024
6312
  * @group Objects
5025
6313
  */
5026
- export declare const omit: <const Props extends ObjectProps, const Keys extends ReadonlyArray<keyof Props>, Rest extends ObjectRecordTypeNode | undefined = undefined>(objectType: ObjectType<Props, Rest>, ...keys: Keys & ([ValidateOmitKeys<Keys>] extends [never] ? unknown : readonly [ValidationFailure<ValidateOmitKeys<Keys>>])) => ObjectType<Omit<Props, Keys[number]>, Rest>;
6314
+ export declare const omit: <const Props extends ObjectProps, const Keys extends ReadonlyArray<keyof Props>, Rest extends ObjectRecordTypeNode | undefined = undefined>(objectType: {
6315
+ readonly props: Props;
6316
+ readonly record?: Rest;
6317
+ } & NoInfer<ObjectType<Props, Rest>>, ...keys: Keys & ([ValidateOmitKeys<Keys>] extends [never] ? unknown : readonly [ValidationFailure<ValidateOmitKeys<Keys>>])) => ObjectType<Omit<Props, Keys[number]>, Rest>;
5027
6318
  type ValidateOmitKeys<Keys extends ReadonlyArray<PropertyKey>> = number extends Keys["length"] ? OmitKeysTupleError : IsUnion<Keys["length"]> extends false ? {
5028
6319
  readonly [Index in keyof Keys & `${number}`]: IsUnion<Keys[Index]> extends false ? {} extends Readonly<Record<Keys[Index], never>> ? OmitKeyConcreteTypeError : never : OmitKeyConcreteTypeError;
5029
6320
  }[keyof Keys & `${number}`] : OmitKeysTupleError;
5030
6321
  type OmitKeysTupleError = CompileTimeError<"Type", "Omitted keys must use one concrete finite tuple.">;
5031
6322
  type OmitKeyConcreteTypeError = CompileTimeError<"Type", "Each omitted key must be one concrete property key.">;
6323
+ /**
6324
+ * The {@link Type} returned by {@link objectKeys}.
6325
+ *
6326
+ * @group Objects
6327
+ */
6328
+ export interface ObjectKeysType<Key extends TypeNode, T extends TypeNode & {
6329
+ readonly props: ObjectProps;
6330
+ }> extends Type<"ObjectKeys", unknown, T["Output"], ObjectKeysError<T> | TransformOutputError<"ObjectKeys", InferErrors<T>>, typeof Unknown, ObjectKeysError<T> | TransformOutputError<"ObjectKeys", InferErrors<T>>, never, Readonly<Record<string, T["CanonicalInput"][keyof T["CanonicalInput"]]>>, false> {
6331
+ readonly key: Key;
6332
+ readonly output: T;
6333
+ readonly [reflectedTypesSymbol]?: Key | T;
6334
+ }
6335
+ /**
6336
+ * Renames a strict {@link object} Type's encoded keys using a string codec.
6337
+ *
6338
+ * The object declaration uses semantic keys. Each key must be a valid Output of
6339
+ * the key Type. Its canonical encoding becomes the external property name.
6340
+ * Decoding accepts those exact names; aliases and unknown properties are
6341
+ * rejected. Values, optionality, and semantic Output stay with the object Type.
6342
+ * Only the outer keys change. Nested field codecs keep their own behavior.
6343
+ *
6344
+ * Construction rejects invalid schema keys, duplicate encodings, and key codecs
6345
+ * that do not decode their encodings back to the declared keys. These are
6346
+ * schema mistakes; invalid external values return normal Type errors. Error
6347
+ * paths use the external names, including missing required properties.
6348
+ *
6349
+ * ### Example
6350
+ *
6351
+ * ```ts
6352
+ * import {
6353
+ * assertEqual,
6354
+ * assertErr,
6355
+ * assertOk,
6356
+ * CamelCaseIdentifierFromConstantCaseIdentifier,
6357
+ * object,
6358
+ * objectKeys,
6359
+ * optional,
6360
+ * PortFromString,
6361
+ * prefixed,
6362
+ * } from "@evolu/common";
6363
+ *
6364
+ * const Key = prefixed("APP_")(
6365
+ * CamelCaseIdentifierFromConstantCaseIdentifier,
6366
+ * );
6367
+ * const Settings = objectKeys(Key)(
6368
+ * object({ port: optional(PortFromString) }),
6369
+ * );
6370
+ *
6371
+ * const result = Settings.fromUnknown({ APP_PORT: "04000" });
6372
+ * assertOk(result, { port: 4000 });
6373
+ *
6374
+ * assertEqual(Settings.to(result.value), { APP_PORT: "4000" });
6375
+ *
6376
+ * assertErr(Settings.fromUnknown({ APP_POTR: "4000" }));
6377
+ * ```
6378
+ *
6379
+ * @group Objects
6380
+ */
6381
+ export declare const objectKeys: <Key extends ConcreteTypeNode & {
6382
+ readonly Output: string;
6383
+ readonly CanonicalInput: string;
6384
+ }>(key: Key & ValidateOutput<Key>) => <T extends ConcreteTypeNode & {
6385
+ readonly name: "Object";
6386
+ readonly props: ObjectProps;
6387
+ readonly record?: never;
6388
+ }>(type: T & ValidateOutput<T>) => ObjectKeysType<Key, T>;
6389
+ /**
6390
+ * An error from the externally named object decoded by {@link objectKeys}.
6391
+ *
6392
+ * @group Objects
6393
+ */
6394
+ export interface ObjectKeysError<T extends TypeNode & {
6395
+ readonly props: ObjectProps;
6396
+ }> extends TypeError<"ObjectKeys">, TransparentTypeError {
6397
+ readonly error: ObjectError<Readonly<Record<string, T["props"][keyof T["props"]][typeof errorsSymbol] | ObjectMissingPropertyError | ObjectExcessPropertyError>>>;
6398
+ }
5032
6399
  /**
5033
6400
  * Creates a {@link Type} for {@link Result} values.
5034
6401
  *
@@ -5122,7 +6489,9 @@ export type UnknownResult = typeof UnknownResult.Output;
5122
6489
  * type: "Loaded",
5123
6490
  * value: "Evolu",
5124
6491
  * });
6492
+ *
5125
6493
  * assertFalse(Loading.is({ type: "Loading", progress: 1 }));
6494
+ *
5126
6495
  * assertType<
5127
6496
  * typeof Loading.Output extends { readonly type: "Loading" }
5128
6497
  * ? true
@@ -5168,6 +6537,7 @@ export declare function typed<const Tag extends TypeName, const Props extends Ob
5168
6537
  * } from "@evolu/common";
5169
6538
  *
5170
6539
  * const Open = typed("Open", { label: String }, record(String, String));
6540
+ *
5171
6541
  * const result = Open.fromUnknown({
5172
6542
  * type: "Open",
5173
6543
  * label: "Ready",
@@ -5316,6 +6686,7 @@ type TypedTypePropertyError = CompileTimeError<"Type", 'Additional properties mu
5316
6686
  * };
5317
6687
  *
5318
6688
  * assertEqual(describeNext({ ok: true, value: "item" }), "Value: item");
6689
+ *
5319
6690
  * assertEqual(
5320
6691
  * describeNext({
5321
6692
  * ok: false,
@@ -5323,6 +6694,7 @@ type TypedTypePropertyError = CompileTimeError<"Type", 'Additional properties mu
5323
6694
  * }),
5324
6695
  * "Done: complete",
5325
6696
  * );
6697
+ *
5326
6698
  * assertEqual(
5327
6699
  * describeNext({
5328
6700
  * ok: false,