@aventara/core 1.0.0-rc.0 → 1.0.0-rc.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (242) hide show
  1. package/CHANGELOG.md +1140 -0
  2. package/README.md +224 -57
  3. package/dist/adapters/adapter-discovery-diagnostic.d.ts +4 -4
  4. package/dist/adapters/adapter-field.d.ts +11 -11
  5. package/dist/adapters/adapter-identifier.d.ts +6 -6
  6. package/dist/adapters/adapter-model.d.ts +9 -9
  7. package/dist/adapters/adapter-operation-executor.interface.d.ts +4 -4
  8. package/dist/adapters/adapter-operation.d.ts +3 -3
  9. package/dist/adapters/adapter-resource.d.ts +18 -9
  10. package/dist/adapters/adapter-sql-statement.d.ts +19 -0
  11. package/dist/adapters/adapter-transaction-executor.interface.d.ts +19 -0
  12. package/dist/adapters/adapter.interface.d.ts +49 -29
  13. package/dist/avz/avz-check.factory.d.ts +83 -0
  14. package/dist/avz/avz-check.factory.js +116 -0
  15. package/dist/avz/avz-check.registry.d.ts +35 -0
  16. package/dist/avz/avz-check.registry.js +9 -0
  17. package/dist/avz/avz.d.ts +203 -0
  18. package/dist/{json → avz}/avz.js +150 -8
  19. package/dist/{json → avz}/index.d.ts +2 -1
  20. package/dist/{json → avz}/json-shape.registry.d.ts +7 -7
  21. package/dist/{json → avz}/zod-issue.translator.d.ts +8 -2
  22. package/dist/{json → avz}/zod-issue.translator.js +7 -0
  23. package/dist/compiler/contract-compiler-diagnostic.d.ts +31 -5
  24. package/dist/compiler/contract.compiler.d.ts +18 -18
  25. package/dist/compiler/contract.compiler.js +548 -441
  26. package/dist/compiler/create-requirements.projector.d.ts +49 -0
  27. package/dist/compiler/create-requirements.projector.js +423 -0
  28. package/dist/compiler/field-validator.compiler.d.ts +47 -0
  29. package/dist/compiler/field-validator.compiler.js +238 -0
  30. package/dist/config/any-framework-config.d.ts +19 -12
  31. package/dist/config/config-diagnostic.d.ts +11 -4
  32. package/dist/config/config-layer.d.ts +15 -6
  33. package/dist/config/config.resolver.d.ts +74 -31
  34. package/dist/config/config.resolver.js +108 -44
  35. package/dist/config/config.validator.d.ts +7 -7
  36. package/dist/config/config.validator.js +581 -72
  37. package/dist/config/define-resource.d.ts +57 -0
  38. package/dist/config/define-resource.js +14 -0
  39. package/dist/config/effective-fields.d.ts +19 -23
  40. package/dist/config/entrypoint.normalizer.d.ts +1 -1
  41. package/dist/config/exact-framework-config.d.ts +280 -0
  42. package/dist/config/field-compute.resolver.d.ts +19 -0
  43. package/dist/config/field-compute.resolver.js +115 -0
  44. package/dist/config/field-pipeline-config.d.ts +47 -0
  45. package/dist/config/forced-operation.validator.d.ts +10 -0
  46. package/dist/config/forced-operation.validator.js +66 -0
  47. package/dist/config/framework-config.d.ts +110 -39
  48. package/dist/config/framework-exemptions-config.d.ts +91 -0
  49. package/dist/config/framework-exemptions-config.js +1 -0
  50. package/dist/config/framework-field-definition.d.ts +78 -0
  51. package/dist/config/framework-field-definition.js +28 -0
  52. package/dist/config/framework-resources-config.d.ts +116 -0
  53. package/dist/config/framework-resources-config.js +1 -0
  54. package/dist/config/pipeline-config.d.ts +56 -20
  55. package/dist/config/pipeline-tree.resolver.d.ts +79 -0
  56. package/dist/config/pipeline-tree.resolver.js +220 -0
  57. package/dist/config/stage-type.eraser.d.ts +65 -0
  58. package/dist/config/stage-type.eraser.js +1 -0
  59. package/dist/contracts/capability.d.ts +11 -11
  60. package/dist/contracts/client-contract-structure.validator.d.ts +323 -28
  61. package/dist/contracts/client-contract-structure.validator.js +152 -8
  62. package/dist/contracts/contract-canonicalizer.d.ts +19 -3
  63. package/dist/contracts/contract-canonicalizer.js +141 -28
  64. package/dist/contracts/contract-hash.d.ts +12 -7
  65. package/dist/contracts/contract-hash.js +3 -2
  66. package/dist/contracts/contract-limits.d.ts +4 -1
  67. package/dist/contracts/contract-limits.js +1 -0
  68. package/dist/contracts/contract-lookup.d.ts +5 -5
  69. package/dist/contracts/contract-protocol.d.ts +4 -4
  70. package/dist/contracts/contract.d.ts +24 -16
  71. package/dist/contracts/create-requirements.d.ts +35 -0
  72. package/dist/contracts/create-requirements.js +1 -0
  73. package/dist/contracts/enum-definition.d.ts +1 -1
  74. package/dist/contracts/field-check.d.ts +101 -0
  75. package/dist/contracts/field-check.js +1 -0
  76. package/dist/contracts/field.d.ts +52 -42
  77. package/dist/contracts/identifier.d.ts +4 -4
  78. package/dist/contracts/json-shape.d.ts +12 -1
  79. package/dist/contracts/lifecycle-trait.d.ts +2 -2
  80. package/dist/contracts/resource-operations.d.ts +28 -28
  81. package/dist/contracts/resource.d.ts +15 -9
  82. package/dist/contracts/scalar-value-type.d.ts +15 -15
  83. package/dist/contracts/scalar.d.ts +3 -3
  84. package/dist/contracts/transaction-capability.d.ts +1 -1
  85. package/dist/contracts/wire-scalar-format.d.ts +4 -4
  86. package/dist/framework/contract-view.d.ts +213 -147
  87. package/dist/framework/framework-construction.error.d.ts +10 -10
  88. package/dist/framework/framework-construction.error.js +2 -2
  89. package/dist/framework/framework.d.ts +47 -23
  90. package/dist/framework/framework.factory.d.ts +8 -6
  91. package/dist/framework/framework.factory.js +40 -20
  92. package/dist/framework/operation-facade.d.ts +123 -52
  93. package/dist/framework/operation-facade.js +9 -1
  94. package/dist/index.d.ts +55 -50
  95. package/dist/index.js +6 -6
  96. package/dist/internal.d.ts +15 -10
  97. package/dist/internal.js +3 -1
  98. package/dist/operations/operation-arguments.d.ts +267 -202
  99. package/dist/operations/operation-call.d.ts +27 -28
  100. package/dist/operations/operation-identity.d.ts +10 -10
  101. package/dist/operations/operation-result.d.ts +21 -21
  102. package/dist/operations/operation-validator-core.d.ts +6 -6
  103. package/dist/operations/operation-validator-core.js +13 -2
  104. package/dist/operations/operation-validator.d.ts +9 -9
  105. package/dist/operations/projection-scope.resolver.d.ts +6 -6
  106. package/dist/operations/structural-limits.walker.d.ts +6 -6
  107. package/dist/operations/structural-limits.walker.js +145 -0
  108. package/dist/operations/validation-code.d.ts +11 -6
  109. package/dist/operations/validation-code.js +4 -0
  110. package/dist/operations/write-scope.resolver.d.ts +6 -6
  111. package/dist/protocol/absent-route.responder.d.ts +13 -12
  112. package/dist/protocol/absent-route.responder.js +19 -19
  113. package/dist/protocol/contract-document.responder.d.ts +3 -3
  114. package/dist/protocol/contract-document.responder.js +2 -2
  115. package/dist/protocol/http-request.decoder.d.ts +17 -13
  116. package/dist/protocol/http-request.decoder.js +10 -10
  117. package/dist/protocol/http-request.reader.d.ts +3 -3
  118. package/dist/protocol/http-response.encoder.d.ts +4 -4
  119. package/dist/protocol/http-response.encoder.js +94 -55
  120. package/dist/protocol/http-status.table.d.ts +2 -2
  121. package/dist/protocol/operation-response.guard.d.ts +2 -2
  122. package/dist/protocol/protocol-surface.d.ts +4 -4
  123. package/dist/protocol/protocol.binder.d.ts +50 -27
  124. package/dist/protocol/protocol.binder.js +12 -9
  125. package/dist/protocol/protocol.handler.d.ts +22 -13
  126. package/dist/protocol/protocol.handler.js +44 -9
  127. package/dist/protocol/unmounted-route.error.d.ts +4 -4
  128. package/dist/protocol/unmounted-route.error.js +2 -2
  129. package/dist/runtime/caller-data.copier.d.ts +5 -5
  130. package/dist/runtime/caller-data.copier.js +13 -13
  131. package/dist/runtime/caller-data.door.d.ts +6 -6
  132. package/dist/runtime/caller-data.door.js +6 -4
  133. package/dist/runtime/computed-read-runtime.d.ts +7 -6
  134. package/dist/runtime/computed-read-runtime.js +61 -48
  135. package/dist/runtime/computed-value.error.d.ts +6 -6
  136. package/dist/runtime/computed-value.error.js +3 -3
  137. package/dist/runtime/context-executor.runtime.d.ts +59 -0
  138. package/dist/runtime/context-executor.runtime.js +275 -0
  139. package/dist/runtime/create-requirement.d.ts +10 -81
  140. package/dist/runtime/create-requirement.js +80 -57
  141. package/dist/runtime/decimal.d.ts +2 -2
  142. package/dist/runtime/decimal.js +1 -1
  143. package/dist/runtime/diagnostics-reporter.d.ts +53 -27
  144. package/dist/runtime/diagnostics-reporter.js +24 -0
  145. package/dist/runtime/error-boundary.d.ts +65 -8
  146. package/dist/runtime/error-boundary.js +45 -19
  147. package/dist/runtime/execution-options.d.ts +10 -2
  148. package/dist/runtime/field-check.evaluator.d.ts +29 -0
  149. package/dist/runtime/field-check.evaluator.js +521 -0
  150. package/dist/runtime/field-compute.d.ts +64 -0
  151. package/dist/runtime/field-compute.js +1 -0
  152. package/dist/runtime/field-stage.plan.d.ts +15 -0
  153. package/dist/runtime/field-stage.plan.js +27 -0
  154. package/dist/runtime/field-stage.runtime.d.ts +35 -0
  155. package/dist/runtime/field-stage.runtime.js +143 -0
  156. package/dist/runtime/field-validator.runtime.d.ts +11 -0
  157. package/dist/runtime/field-validator.runtime.js +87 -0
  158. package/dist/runtime/framework-error-class.table.d.ts +33 -0
  159. package/dist/runtime/framework-error-class.table.js +25 -0
  160. package/dist/runtime/framework-error.d.ts +68 -8
  161. package/dist/runtime/framework-error.factory.d.ts +4 -0
  162. package/dist/runtime/framework-error.factory.js +15 -0
  163. package/dist/runtime/framework-error.js +23 -2
  164. package/dist/runtime/interceptor-next.error.d.ts +5 -0
  165. package/dist/runtime/interceptor-next.error.js +6 -0
  166. package/dist/runtime/json-value.d.ts +3 -3
  167. package/dist/runtime/level-lifecycle.runtime.d.ts +25 -0
  168. package/dist/runtime/level-lifecycle.runtime.js +42 -0
  169. package/dist/runtime/misuse-reporter.d.ts +12 -12
  170. package/dist/runtime/mutation-planner.d.ts +19 -10
  171. package/dist/runtime/mutation-planner.js +54 -30
  172. package/dist/runtime/nested-write.walker.d.ts +32 -0
  173. package/dist/runtime/nested-write.walker.js +73 -0
  174. package/dist/runtime/operation-request.d.ts +33 -6
  175. package/dist/runtime/operation-response.d.ts +17 -17
  176. package/dist/runtime/operation-runtime.d.ts +113 -25
  177. package/dist/runtime/operation-runtime.js +395 -74
  178. package/dist/runtime/outcome-mapper.d.ts +16 -5
  179. package/dist/runtime/outcome-mapper.js +23 -6
  180. package/dist/runtime/output-validator.d.ts +10 -10
  181. package/dist/runtime/output-validator.js +3 -3
  182. package/dist/runtime/owned-execution.registry.d.ts +6 -6
  183. package/dist/runtime/pipe-output.validator.d.ts +33 -18
  184. package/dist/runtime/pipe-output.validator.js +12 -12
  185. package/dist/runtime/pipeline.d.ts +196 -34
  186. package/dist/runtime/readonly-value.d.ts +32 -0
  187. package/dist/runtime/readonly-value.js +141 -0
  188. package/dist/runtime/request-envelope.validator.d.ts +3 -3
  189. package/dist/runtime/request-envelope.validator.js +12 -7
  190. package/dist/runtime/request-id.d.ts +1 -1
  191. package/dist/runtime/request-state.d.ts +14 -0
  192. package/dist/runtime/request-state.js +1 -0
  193. package/dist/runtime/request-state.resolver.d.ts +12 -0
  194. package/dist/runtime/request-state.resolver.js +20 -0
  195. package/dist/runtime/scalar-runtime.d.ts +31 -16
  196. package/dist/runtime/scalar-runtime.js +16 -18
  197. package/dist/transactions/deferred-operation.d.ts +35 -20
  198. package/dist/transactions/operation-arguments-encoder.d.ts +3 -3
  199. package/dist/transactions/operation-arguments-encoder.js +2 -2
  200. package/dist/transactions/sql-statement.d.ts +77 -0
  201. package/dist/transactions/sql-statement.js +47 -0
  202. package/dist/transactions/sql-statement.validator.d.ts +44 -0
  203. package/dist/transactions/sql-statement.validator.js +134 -0
  204. package/dist/transactions/transaction-envelope.d.ts +4 -4
  205. package/dist/transactions/transaction-integrity-error.d.ts +6 -6
  206. package/dist/transactions/transaction-integrity-error.js +5 -2
  207. package/dist/transactions/transaction-plan-assembler.d.ts +21 -11
  208. package/dist/transactions/transaction-plan-assembler.js +148 -34
  209. package/dist/transactions/transaction-plan-validator.d.ts +29 -20
  210. package/dist/transactions/transaction-plan-validator.js +70 -38
  211. package/dist/transactions/transaction-reference-collector.d.ts +5 -5
  212. package/dist/transactions/transaction-reference-resolver.d.ts +19 -2
  213. package/dist/transactions/transaction-reference-resolver.js +26 -14
  214. package/dist/transactions/transaction-reference-validator.d.ts +34 -4
  215. package/dist/transactions/transaction-reference-validator.js +58 -26
  216. package/dist/transactions/transaction-reference.d.ts +13 -13
  217. package/dist/transactions/transaction-request.d.ts +18 -5
  218. package/dist/transactions/transaction-response.d.ts +6 -6
  219. package/dist/transactions/transaction-runtime.d.ts +8 -7
  220. package/dist/transactions/transaction-runtime.js +166 -50
  221. package/dist/transactions/transaction-settlement.runtime.d.ts +51 -0
  222. package/dist/transactions/transaction-settlement.runtime.js +120 -0
  223. package/dist/transactions/transaction-slot-path-error.d.ts +4 -4
  224. package/dist/transactions/transaction-slot-path-error.js +2 -2
  225. package/dist/transactions/transaction-slot-writer.js +2 -2
  226. package/dist/transactions/transaction-stage.runtime.d.ts +81 -0
  227. package/dist/transactions/transaction-stage.runtime.js +123 -0
  228. package/package.json +6 -5
  229. package/src/runtime/field-check.evaluator.ts +720 -0
  230. package/dist/config/define-fields.d.ts +0 -42
  231. package/dist/config/define-fields.js +0 -3
  232. package/dist/config/framework-behaviors-config.d.ts +0 -30
  233. package/dist/config/framework-fields-config.d.ts +0 -42
  234. package/dist/config/framework-restrictions-config.d.ts +0 -142
  235. package/dist/json/avz.d.ts +0 -96
  236. package/dist/runtime/field-behavior.d.ts +0 -48
  237. /package/dist/{config/framework-behaviors-config.js → adapters/adapter-sql-statement.js} +0 -0
  238. /package/dist/{config/framework-fields-config.js → adapters/adapter-transaction-executor.interface.js} +0 -0
  239. /package/dist/{json → avz}/index.js +0 -0
  240. /package/dist/{json → avz}/json-shape.registry.js +0 -0
  241. /package/dist/config/{framework-restrictions-config.js → exact-framework-config.js} +0 -0
  242. /package/dist/{runtime/field-behavior.js → config/field-pipeline-config.js} +0 -0
@@ -0,0 +1,720 @@
1
+ import type {
2
+ AvFieldCheck,
3
+ AvFieldCheckFormat,
4
+ AvFieldFormatCheck,
5
+ } from "../contracts/field-check.js";
6
+ import type {
7
+ AvValidationCode,
8
+ AvValidationIssue,
9
+ } from "../operations/validation-code.js";
10
+
11
+ // The field checks of a Contract, evaluated: the rules a field's validators and a
12
+ // Json Shape node's checks state, as zod's check name and parameters. The server
13
+ // runs this module on a field's value after its field pipes, the Json Shape
14
+ // checker runs it on a shape node's value, and a generated client runs a copy of
15
+ // it before it sends a request. It reads only the Contract's check data, so it
16
+ // needs no zod and nothing from a configuration, and it imports nothing but
17
+ // types and reads no platform type a project may not declare: a generator copies
18
+ // it whole, and the copy compiles in any TypeScript project.
19
+ //
20
+ // The semantics are zod's (4.6), check by check: a string's length is counted in
21
+ // Unicode code points, a float's `multipleOf` allows zod's rounding tolerance, a
22
+ // bigint's is exact, and every named format is zod's own pattern or test, copied
23
+ // below and held to zod by Aventara's own tests. A `decimal`'s bounds and
24
+ // multiples are exact: compared digit by digit, never through a float, and never
25
+ // by expanding an exponent a caller wrote.
26
+ //
27
+ // A failed check is one issue on an existing validation code: `V1004` for a
28
+ // range, length or size, `V1003` for a pattern or format. The issue carries zod's
29
+ // check name and the check's parameters as the Contract stores them, so a
30
+ // program can read which rule failed, and a message for a person.
31
+
32
+ /** What a check judges: the runtime form of the value it is given. */
33
+ export type FieldCheckSubject =
34
+ | "string"
35
+ | "number"
36
+ | "bigint"
37
+ | "decimal"
38
+ | "datetime"
39
+ | "bytes";
40
+
41
+ // No bigint literal: a project may compile this copy for a target before
42
+ // ES2020, where `0n` is a type error and `BigInt(0)` is not.
43
+
44
+ /**
45
+ * What a check judges on a field of each scalar type that takes checks; a field
46
+ * of any other type (an enum's) holds a string.
47
+ */
48
+ export const FIELD_CHECK_SUBJECTS: Readonly<Record<string, FieldCheckSubject>> =
49
+ {
50
+ string: "string",
51
+ int: "number",
52
+ float: "number",
53
+ bigint: "bigint",
54
+ decimal: "decimal",
55
+ datetime: "datetime",
56
+ bytes: "bytes",
57
+ };
58
+
59
+ type Path = readonly (string | number)[];
60
+
61
+ // ---- named formats, as zod 4.6 checks them -------------------------------
62
+ //
63
+ // Copied from zod (MIT License, Copyright (c) 2025 Colin McDonnell):
64
+ // `v4/core/regexes.js` and the format checks of `v4/core/schemas.js`, with each
65
+ // builder's default options (`z.uuid()` any version, `z.iso.datetime()` UTC
66
+ // only and any precision, `z.iso.time()` with optional seconds).
67
+
68
+ const DATE_SOURCE =
69
+ "(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))";
70
+ const HHMM = "(?:[01]\\d|2[0-3]):[0-5]\\d";
71
+ const IPV6_ALPHABET = /^[0-9a-fA-F:.]+$/;
72
+ const CREDIT_CARD = /^\d(?:[ -]?\d){11,18}$/;
73
+ const IBAN = /^[A-Z]{2}(?!00|01|99)\d{2}[A-Z0-9]{11,30}$/;
74
+
75
+ const FORMAT_PATTERNS: Readonly<Partial<Record<AvFieldCheckFormat, RegExp>>> = {
76
+ email:
77
+ /^(?:[A-Za-z0-9_'+-]+\.)*[A-Za-z0-9_'+-]*[A-Za-z0-9_+-]@(?:[A-Za-z0-9][A-Za-z0-9-]*\.)+[A-Za-z]{2,}$/,
78
+ // zod writes this one as a string source: its class mixes a combining mark
79
+ // with the characters it combines with, on purpose.
80
+ // biome-ignore lint/complexity/useRegexLiterals: a literal trips noMisleadingCharacterClass
81
+ emoji: new RegExp(
82
+ "^(?=[\\s\\S]*[\\p{Extended_Pictographic}\\p{Regional_Indicator}\\u20E3])[\\p{Extended_Pictographic}\\p{Emoji_Component}]+$",
83
+ "u",
84
+ ),
85
+ uuid: /^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$/,
86
+ guid: /^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})$/,
87
+ nanoid: /^[a-zA-Z0-9_-]{21}$/,
88
+ cuid: /^[cC][0-9a-z]{6,}$/,
89
+ cuid2: /^[0-9a-z]+$/,
90
+ ulid: /^[0-7][0-9A-HJKMNP-TV-Za-hjkmnp-tv-z]{25}$/,
91
+ xid: /^[0-9a-vA-V]{20}$/,
92
+ ksuid: /^[A-Za-z0-9]{27}$/,
93
+ datetime: new RegExp(
94
+ `^${DATE_SOURCE}T(?:${HHMM}:[0-5]\\d(?:\\.\\d+)?(?:Z))$`,
95
+ ),
96
+ date: new RegExp(`^${DATE_SOURCE}$`),
97
+ time: new RegExp(`^${HHMM}(?::[0-5]\\d(?:\\.\\d+)?)?$`),
98
+ duration:
99
+ /^P(?:(\d+W)|(?!.*W)(?=\d|T\d)(\d+Y)?(\d+M)?(\d+D)?(T(?=\d)(\d+H)?(\d+M)?(\d+([.,]\d+)?S)?)?)$/,
100
+ ipv4: /^(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\.){3}(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])$/,
101
+ cidrv4:
102
+ /^((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\.){3}(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\/([0-9]|[1-2][0-9]|3[0-2])$/,
103
+ e164: /^\+[1-9]\d{6,14}$/,
104
+ };
105
+
106
+ // `URL` and `atob` are every JavaScript runtime's, but their types are DOM's or
107
+ // Node's, which a project may not declare: they are read off `globalThis`,
108
+ // typed as far as this module calls them.
109
+ /** The platform's `URL` and `atob`, as this module calls them. */
110
+ const platform = globalThis as unknown as {
111
+ readonly URL?: {
112
+ new (input: string): unknown;
113
+ readonly canParse?: (input: string) => boolean;
114
+ };
115
+ readonly atob?: (data: string) => string;
116
+ };
117
+
118
+ function canParseUrl(input: string): boolean {
119
+ const Url = platform.URL;
120
+ if (Url === undefined) {
121
+ return false;
122
+ }
123
+ try {
124
+ if (typeof Url.canParse === "function") {
125
+ return Url.canParse(input);
126
+ }
127
+ new Url(input);
128
+ return true;
129
+ } catch {
130
+ return false;
131
+ }
132
+ }
133
+
134
+ /** `atob`: a base64 string's bytes as a binary string; throws when it is not base64. */
135
+ function decodeBase64(value: string): string {
136
+ if (platform.atob === undefined) {
137
+ throw new TypeError("No atob is available here.");
138
+ }
139
+ return platform.atob(value);
140
+ }
141
+
142
+ function isIpv6(value: string): boolean {
143
+ return IPV6_ALPHABET.test(value) && canParseUrl(`http://[${value}]`);
144
+ }
145
+
146
+ function isCidrv6(value: string): boolean {
147
+ const parts = value.split("/");
148
+ if (parts.length !== 2) {
149
+ return false;
150
+ }
151
+ const [address, prefix] = parts as [string, string];
152
+ const bits = Number(prefix);
153
+ return (
154
+ prefix !== "" &&
155
+ `${bits}` === prefix &&
156
+ bits >= 0 &&
157
+ bits <= 128 &&
158
+ isIpv6(address)
159
+ );
160
+ }
161
+
162
+ function isBase64(value: string): boolean {
163
+ if (value === "") {
164
+ return true;
165
+ }
166
+ if (/\s/.test(value) || value.length % 4 !== 0) {
167
+ return false;
168
+ }
169
+ try {
170
+ decodeBase64(value);
171
+ return true;
172
+ } catch {
173
+ return false;
174
+ }
175
+ }
176
+
177
+ function isBase64Url(value: string): boolean {
178
+ if (!/^[A-Za-z0-9_-]*$/.test(value)) {
179
+ return false;
180
+ }
181
+ const base64 = value.replace(/[-_]/g, (char) => (char === "-" ? "+" : "/"));
182
+ return isBase64(base64.padEnd(Math.ceil(base64.length / 4) * 4, "="));
183
+ }
184
+
185
+ function isLuhn(digits: string): boolean {
186
+ let length = digits.length;
187
+ let bit = 1;
188
+ let sum = 0;
189
+ while (length) {
190
+ const value = digits.charCodeAt(--length) - 48;
191
+ bit ^= 1;
192
+ sum += bit ? ([0, 2, 4, 6, 8, 1, 3, 5, 7, 9][value] ?? 0) : value;
193
+ }
194
+ return sum % 10 === 0;
195
+ }
196
+
197
+ function isIbanChecksum(iban: string): boolean {
198
+ let remainder = 0;
199
+ const step = (code: number): void => {
200
+ remainder =
201
+ (code >= 65
202
+ ? remainder * 100 + (code - 55)
203
+ : remainder * 10 + (code - 48)) % 97;
204
+ };
205
+ for (let index = 4; index < iban.length; index += 1) {
206
+ step(iban.charCodeAt(index));
207
+ }
208
+ for (let index = 0; index < 4; index += 1) {
209
+ step(iban.charCodeAt(index));
210
+ }
211
+ return remainder === 1;
212
+ }
213
+
214
+ function isJwt(token: string): boolean {
215
+ try {
216
+ const parts = token.split(".");
217
+ if (parts.length !== 3 || !parts[0]) {
218
+ return false;
219
+ }
220
+ const header = JSON.parse(decodeBase64(parts[0])) as {
221
+ readonly typ?: unknown;
222
+ readonly alg?: unknown;
223
+ };
224
+ if ("typ" in header && header.typ !== "JWT") {
225
+ return false;
226
+ }
227
+ return Boolean(header.alg);
228
+ } catch {
229
+ return false;
230
+ }
231
+ }
232
+
233
+ function isJsonString(value: string): boolean {
234
+ try {
235
+ JSON.parse(value);
236
+ return true;
237
+ } catch {
238
+ return false;
239
+ }
240
+ }
241
+
242
+ /** Whether `value` is the named format, as zod checks it. */
243
+ function matchesFormat(format: AvFieldCheckFormat, value: string): boolean {
244
+ switch (format) {
245
+ case "url":
246
+ return canParseUrl(value.trim());
247
+ case "ipv6":
248
+ return isIpv6(value);
249
+ case "cidrv6":
250
+ return isCidrv6(value);
251
+ case "base64":
252
+ return isBase64(value);
253
+ case "base64url":
254
+ return isBase64Url(value);
255
+ case "credit_card":
256
+ return CREDIT_CARD.test(value) && isLuhn(value.replace(/[- ]/g, ""));
257
+ case "iban":
258
+ return IBAN.test(value) && isIbanChecksum(value);
259
+ case "jwt":
260
+ return isJwt(value);
261
+ case "json_string":
262
+ return isJsonString(value);
263
+ default: {
264
+ return FORMAT_PATTERNS[format]?.test(value) === true;
265
+ }
266
+ }
267
+ }
268
+
269
+ // ---- exact decimals -------------------------------------------------------
270
+
271
+ /** A decimal as sign, significant digits and the power of ten of the first one. */
272
+ type DecimalParts = {
273
+ readonly negative: boolean;
274
+ /** No leading or trailing zero; `""` for zero. */
275
+ readonly digits: string;
276
+ /** The power of ten of `digits`' first digit. */
277
+ readonly exponent: number;
278
+ };
279
+
280
+ const DECIMAL = /^(-?)(\d+)(?:\.(\d+))?(?:[eE]([+-]?\d+))?$/;
281
+
282
+ function decimalParts(text: string): DecimalParts | undefined {
283
+ const match = DECIMAL.exec(text);
284
+ if (match === null) {
285
+ return undefined;
286
+ }
287
+ const [, sign, whole = "", fraction = "", power = "0"] = match;
288
+ const all = `${whole}${fraction}`;
289
+ const leading = all.length - all.replace(/^0+/, "").length;
290
+ const digits = all.slice(leading).replace(/0+$/, "");
291
+ if (digits === "") {
292
+ return { negative: false, digits: "", exponent: 0 };
293
+ }
294
+ return {
295
+ negative: sign === "-",
296
+ digits,
297
+ exponent: whole.length - 1 - leading + Number(power),
298
+ };
299
+ }
300
+
301
+ /** `-1`, `0` or `1` as `a` is less than, equal to or greater than `b`. */
302
+ function compareDecimals(a: DecimalParts, b: DecimalParts): number {
303
+ const signOf = (parts: DecimalParts) =>
304
+ parts.digits === "" ? 0 : parts.negative ? -1 : 1;
305
+ if (signOf(a) !== signOf(b)) {
306
+ return Math.sign(signOf(a) - signOf(b));
307
+ }
308
+ if (signOf(a) === 0) {
309
+ return 0;
310
+ }
311
+ let magnitude = 0;
312
+ if (a.exponent !== b.exponent) {
313
+ magnitude = a.exponent > b.exponent ? 1 : -1;
314
+ } else {
315
+ const width = Math.max(a.digits.length, b.digits.length);
316
+ const left = a.digits.padEnd(width, "0");
317
+ const right = b.digits.padEnd(width, "0");
318
+ magnitude = left === right ? 0 : left > right ? 1 : -1;
319
+ }
320
+ return a.negative ? -magnitude : magnitude;
321
+ }
322
+
323
+ function modPow(base: bigint, power: number, modulus: bigint): bigint {
324
+ let result = BigInt(1) % modulus;
325
+ let factor = base % modulus;
326
+ let remaining = power;
327
+ while (remaining > 0) {
328
+ if (remaining % 2 === 1) {
329
+ result = (result * factor) % modulus;
330
+ }
331
+ factor = (factor * factor) % modulus;
332
+ remaining = Math.floor(remaining / 2);
333
+ }
334
+ return result;
335
+ }
336
+
337
+ /** Whether `a` is an exact multiple of `b`; nothing is a multiple of zero. */
338
+ function isDecimalMultiple(a: DecimalParts, b: DecimalParts): boolean {
339
+ if (b.digits === "") {
340
+ return false;
341
+ }
342
+ if (a.digits === "") {
343
+ return true;
344
+ }
345
+ // a = A × 10^ea and b = B × 10^eb, with A and B the digit strings as integers.
346
+ const scaleA = a.exponent - (a.digits.length - 1);
347
+ const scaleB = b.exponent - (b.digits.length - 1);
348
+ if (!Number.isSafeInteger(scaleA) || !Number.isSafeInteger(scaleB)) {
349
+ return false;
350
+ }
351
+ const mantissaA = BigInt(a.digits);
352
+ const mantissaB = BigInt(b.digits);
353
+ if (scaleA >= scaleB) {
354
+ return (
355
+ ((mantissaA % mantissaB) *
356
+ modPow(BigInt(10), scaleA - scaleB, mantissaB)) %
357
+ mantissaB ===
358
+ BigInt(0)
359
+ );
360
+ }
361
+ // B × 10^(eb − ea) would have to divide A, whose last digit is not 0: no
362
+ // power of ten does.
363
+ return false;
364
+ }
365
+
366
+ // ---- the checks -----------------------------------------------------------
367
+
368
+ /** zod's float remainder: a true decimal multiple's rounding is not a remainder. */
369
+ function floatRemainder(value: number, step: number): number {
370
+ const ratio = value / step;
371
+ const rounded = Math.round(ratio);
372
+ const tolerance = 4 * Number.EPSILON * Math.max(Math.abs(ratio), 1);
373
+ return Math.abs(ratio - rounded) < tolerance ? 0 : ratio - rounded;
374
+ }
375
+
376
+ /** A string's length in Unicode code points, as zod counts it. */
377
+ function codePoints(value: string): number {
378
+ let count = value.length;
379
+ for (let index = 0; index < value.length - 1; index += 1) {
380
+ if (
381
+ (value.charCodeAt(index) & 0xfc00) === 0xd800 &&
382
+ (value.charCodeAt(index + 1) & 0xfc00) === 0xdc00
383
+ ) {
384
+ count -= 1;
385
+ index += 1;
386
+ }
387
+ }
388
+ return count;
389
+ }
390
+
391
+ const patterns = new WeakMap<object, RegExp>();
392
+
393
+ function patternOf(
394
+ check: Extract<AvFieldFormatCheck, { readonly format: "regex" }>,
395
+ ): RegExp {
396
+ let pattern = patterns.get(check);
397
+ if (pattern === undefined) {
398
+ pattern = new RegExp(check.pattern.source, check.pattern.flags);
399
+ patterns.set(check, pattern);
400
+ }
401
+ // A global or sticky pattern remembers where it stopped; zod resets it too.
402
+ pattern.lastIndex = 0;
403
+ return pattern;
404
+ }
405
+
406
+ function passesFormat(check: AvFieldFormatCheck, value: string): boolean {
407
+ switch (check.format) {
408
+ case "regex":
409
+ return patternOf(check).test(value);
410
+ case "includes":
411
+ return value.includes(check.includes);
412
+ case "starts_with":
413
+ return value.startsWith(check.prefix);
414
+ case "ends_with":
415
+ return value.endsWith(check.suffix);
416
+ case "lowercase":
417
+ return /^[^A-Z]*$/.test(value);
418
+ case "uppercase":
419
+ return /^[^a-z]*$/.test(value);
420
+ default:
421
+ return matchesFormat(check.format, value);
422
+ }
423
+ }
424
+
425
+ /**
426
+ * The order of `value` against a check's bound, or `undefined` when the value is
427
+ * not of the subject's form (the Framework Validation Layer refused it already).
428
+ */
429
+ function compareToBound(
430
+ value: unknown,
431
+ bound: number | string,
432
+ subject: FieldCheckSubject,
433
+ ): number | undefined {
434
+ switch (subject) {
435
+ case "number":
436
+ return typeof value === "number" && typeof bound === "number"
437
+ ? Math.sign(value - bound)
438
+ : undefined;
439
+ case "bigint": {
440
+ if (typeof value !== "bigint") {
441
+ return undefined;
442
+ }
443
+ const limit = BigInt(bound);
444
+ return value === limit ? 0 : value > limit ? 1 : -1;
445
+ }
446
+ case "decimal": {
447
+ const left = decimalParts(String(value));
448
+ const right = decimalParts(String(bound));
449
+ return left === undefined || right === undefined
450
+ ? undefined
451
+ : compareDecimals(left, right);
452
+ }
453
+ case "datetime":
454
+ return value instanceof Date
455
+ ? Math.sign(value.getTime() - Date.parse(String(bound)))
456
+ : undefined;
457
+ default:
458
+ return undefined;
459
+ }
460
+ }
461
+
462
+ function isMultiple(
463
+ value: unknown,
464
+ step: number | string,
465
+ subject: FieldCheckSubject,
466
+ ): boolean | undefined {
467
+ switch (subject) {
468
+ case "number":
469
+ return typeof value === "number" && typeof step === "number"
470
+ ? floatRemainder(value, step) === 0
471
+ : undefined;
472
+ case "bigint": {
473
+ if (typeof value !== "bigint") {
474
+ return undefined;
475
+ }
476
+ const divisor = BigInt(step);
477
+ return divisor !== BigInt(0) && value % divisor === BigInt(0);
478
+ }
479
+ case "decimal": {
480
+ const left = decimalParts(String(value));
481
+ const right = decimalParts(String(step));
482
+ return left === undefined || right === undefined
483
+ ? undefined
484
+ : isDecimalMultiple(left, right);
485
+ }
486
+ default:
487
+ return undefined;
488
+ }
489
+ }
490
+
491
+ /**
492
+ * Whether `value` passes `check`; `true` for a value the check does not judge
493
+ * (a value of another form, which validation refused before the checks run).
494
+ */
495
+ export function passesFieldCheck(
496
+ check: AvFieldCheck,
497
+ value: unknown,
498
+ subject: FieldCheckSubject,
499
+ ): boolean {
500
+ switch (check.check) {
501
+ case "less_than":
502
+ case "greater_than": {
503
+ const order = compareToBound(value, check.value, subject);
504
+ if (order === undefined) {
505
+ return true;
506
+ }
507
+ const signed = check.check === "less_than" ? -order : order;
508
+ return signed > 0 || (check.inclusive && signed === 0);
509
+ }
510
+ case "multiple_of":
511
+ return isMultiple(value, check.value, subject) ?? true;
512
+ case "max_length":
513
+ case "min_length":
514
+ case "length_equals": {
515
+ const length = Array.isArray(value)
516
+ ? value.length
517
+ : typeof value === "string"
518
+ ? codePoints(value)
519
+ : undefined;
520
+ if (length === undefined) {
521
+ return true;
522
+ }
523
+ return check.check === "max_length"
524
+ ? length <= check.maximum
525
+ : check.check === "min_length"
526
+ ? length >= check.minimum
527
+ : length === check.length;
528
+ }
529
+ case "string_format":
530
+ return typeof value === "string" ? passesFormat(check, value) : true;
531
+ case "max_size":
532
+ case "min_size":
533
+ case "size_equals": {
534
+ if (!(value instanceof Uint8Array)) {
535
+ return true;
536
+ }
537
+ const size = value.byteLength;
538
+ return check.check === "max_size"
539
+ ? size <= check.maximum
540
+ : check.check === "min_size"
541
+ ? size >= check.minimum
542
+ : size === check.size;
543
+ }
544
+ }
545
+ }
546
+
547
+ // ---- issues ---------------------------------------------------------------
548
+
549
+ const FORMAT_LABELS: Readonly<Record<AvFieldCheckFormat, string>> = {
550
+ email: "an email address",
551
+ url: "a URL",
552
+ emoji: "emoji",
553
+ uuid: "a UUID",
554
+ guid: "a GUID",
555
+ nanoid: "a Nano ID",
556
+ cuid: "a CUID",
557
+ cuid2: "a CUID2",
558
+ ulid: "a ULID",
559
+ xid: "an XID",
560
+ ksuid: "a KSUID",
561
+ datetime: "an ISO 8601 date-time in UTC",
562
+ date: "an ISO 8601 date",
563
+ time: "an ISO 8601 time",
564
+ duration: "an ISO 8601 duration",
565
+ ipv4: "an IPv4 address",
566
+ ipv6: "an IPv6 address",
567
+ cidrv4: "an IPv4 CIDR block",
568
+ cidrv6: "an IPv6 CIDR block",
569
+ base64: "base64",
570
+ base64url: "base64url",
571
+ json_string: "a JSON string",
572
+ e164: "an E.164 phone number",
573
+ credit_card: "a credit card number",
574
+ iban: "an IBAN",
575
+ jwt: "a JWT",
576
+ };
577
+
578
+ function plural(count: number, one: string, many: string): string {
579
+ return `${count} ${count === 1 ? one : many}`;
580
+ }
581
+
582
+ function formatMessage(check: AvFieldFormatCheck): string {
583
+ switch (check.format) {
584
+ case "regex":
585
+ return `Value must match the pattern /${check.pattern.source}/${check.pattern.flags}.`;
586
+ case "includes":
587
+ return `Value must include ${JSON.stringify(check.includes)}.`;
588
+ case "starts_with":
589
+ return `Value must start with ${JSON.stringify(check.prefix)}.`;
590
+ case "ends_with":
591
+ return `Value must end with ${JSON.stringify(check.suffix)}.`;
592
+ case "lowercase":
593
+ return "Value must be lowercase.";
594
+ case "uppercase":
595
+ return "Value must be uppercase.";
596
+ default:
597
+ return `Value must be ${FORMAT_LABELS[check.format]}.`;
598
+ }
599
+ }
600
+
601
+ function boundMessage(
602
+ check: Extract<AvFieldCheck, { readonly inclusive: boolean }>,
603
+ subject: FieldCheckSubject,
604
+ ): string {
605
+ const bound = String(check.value);
606
+ if (subject === "datetime") {
607
+ return check.check === "less_than"
608
+ ? `Value must be ${check.inclusive ? "on or before" : "before"} ${bound}.`
609
+ : `Value must be ${check.inclusive ? "on or after" : "after"} ${bound}.`;
610
+ }
611
+ return check.check === "less_than"
612
+ ? `Value must be ${check.inclusive ? "at most" : "less than"} ${bound}.`
613
+ : `Value must be ${check.inclusive ? "at least" : "greater than"} ${bound}.`;
614
+ }
615
+
616
+ /** What a person reads about a failed check. */
617
+ function messageOf(check: AvFieldCheck, subject: FieldCheckSubject): string {
618
+ switch (check.check) {
619
+ case "less_than":
620
+ case "greater_than":
621
+ return boundMessage(check, subject);
622
+ case "multiple_of":
623
+ return `Value must be a multiple of ${check.value}.`;
624
+ case "max_length":
625
+ return check.list === true
626
+ ? `List must have at most ${plural(check.maximum, "entry", "entries")}.`
627
+ : `Value must be at most ${plural(check.maximum, "character", "characters")} long.`;
628
+ case "min_length":
629
+ return check.list === true
630
+ ? `List must have at least ${plural(check.minimum, "entry", "entries")}.`
631
+ : `Value must be at least ${plural(check.minimum, "character", "characters")} long.`;
632
+ case "length_equals":
633
+ return `Value must be exactly ${plural(check.length, "character", "characters")} long.`;
634
+ case "string_format":
635
+ return formatMessage(check);
636
+ case "max_size":
637
+ return `Value must be at most ${plural(check.maximum, "byte", "bytes")}.`;
638
+ case "min_size":
639
+ return `Value must be at least ${plural(check.minimum, "byte", "bytes")}.`;
640
+ case "size_equals":
641
+ return `Value must be exactly ${plural(check.size, "byte", "bytes")}.`;
642
+ }
643
+ }
644
+
645
+ /** The issue of one failed check at `path`: its V-code, zod's check and its parameters. */
646
+ export function fieldCheckIssue(
647
+ check: AvFieldCheck,
648
+ subject: FieldCheckSubject,
649
+ path: Path,
650
+ ): AvValidationIssue {
651
+ const { check: name, ...params } = check;
652
+ const code: AvValidationCode = name === "string_format" ? "V1003" : "V1004";
653
+ return {
654
+ code,
655
+ path,
656
+ message: messageOf(check, subject),
657
+ check: name,
658
+ params,
659
+ };
660
+ }
661
+
662
+ /**
663
+ * The issues of the checks `value` breaks, one per broken check, in the checks'
664
+ * order. On a list (`list`), a `list: true` length check judges the list at
665
+ * `path` and every other check judges each entry, at its index; `null` is no
666
+ * value and breaks nothing.
667
+ */
668
+ export function fieldCheckIssues(
669
+ checks: readonly AvFieldCheck[],
670
+ value: unknown,
671
+ subject: FieldCheckSubject,
672
+ path: Path,
673
+ list: boolean,
674
+ ): readonly AvValidationIssue[] {
675
+ if (checks.length === 0 || value === null || value === undefined) {
676
+ return [];
677
+ }
678
+ const issues: AvValidationIssue[] = [];
679
+ if (list && Array.isArray(value)) {
680
+ for (const check of checks) {
681
+ if (isListCheck(check) && !passesFieldCheck(check, value, subject)) {
682
+ issues.push(fieldCheckIssue(check, subject, path));
683
+ }
684
+ }
685
+ for (const [index, entry] of value.entries()) {
686
+ issues.push(
687
+ ...entryCheckIssues(checks, entry, subject, [...path, index]),
688
+ );
689
+ }
690
+ return issues;
691
+ }
692
+ return entryCheckIssues(checks, value, subject, path);
693
+ }
694
+
695
+ /** The issues of the checks one value — a whole value, or a list's entry — breaks. */
696
+ export function entryCheckIssues(
697
+ checks: readonly AvFieldCheck[],
698
+ value: unknown,
699
+ subject: FieldCheckSubject,
700
+ path: Path,
701
+ ): readonly AvValidationIssue[] {
702
+ if (value === null || value === undefined) {
703
+ return [];
704
+ }
705
+ const issues: AvValidationIssue[] = [];
706
+ for (const check of checks) {
707
+ if (!isListCheck(check) && !passesFieldCheck(check, value, subject)) {
708
+ issues.push(fieldCheckIssue(check, subject, path));
709
+ }
710
+ }
711
+ return issues;
712
+ }
713
+
714
+ /** Whether a check judges a list's own length (`AvZ.minItems`/`AvZ.maxItems`). */
715
+ export function isListCheck(check: AvFieldCheck): boolean {
716
+ return (
717
+ (check.check === "max_length" || check.check === "min_length") &&
718
+ check.list === true
719
+ );
720
+ }