@earendil-works/pi-schema 0.0.0-stage → 0.0.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 (176) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/LICENSES/typebox-LICENSE +23 -0
  3. package/README.md +295 -2
  4. package/dist/annotations.d.ts +44 -0
  5. package/dist/annotations.d.ts.map +1 -0
  6. package/dist/annotations.js +193 -0
  7. package/dist/annotations.js.map +1 -0
  8. package/dist/builders.d.ts +114 -0
  9. package/dist/builders.d.ts.map +1 -0
  10. package/dist/builders.js +98 -0
  11. package/dist/builders.js.map +1 -0
  12. package/dist/checks.d.ts +87 -0
  13. package/dist/checks.d.ts.map +1 -0
  14. package/dist/checks.js +447 -0
  15. package/dist/checks.js.map +1 -0
  16. package/dist/coerce.d.ts +53 -0
  17. package/dist/coerce.d.ts.map +1 -0
  18. package/dist/coerce.js +585 -0
  19. package/dist/coerce.js.map +1 -0
  20. package/dist/compiler.d.ts +53 -0
  21. package/dist/compiler.d.ts.map +1 -0
  22. package/dist/compiler.js +532 -0
  23. package/dist/compiler.js.map +1 -0
  24. package/dist/explain.d.ts +29 -0
  25. package/dist/explain.d.ts.map +1 -0
  26. package/dist/explain.js +381 -0
  27. package/dist/explain.js.map +1 -0
  28. package/dist/formats/index.d.ts +7 -0
  29. package/dist/formats/index.d.ts.map +1 -0
  30. package/dist/formats/index.js +51 -0
  31. package/dist/formats/index.js.map +1 -0
  32. package/dist/formats/typebox/date.d.ts +7 -0
  33. package/dist/formats/typebox/date.d.ts.map +1 -0
  34. package/dist/formats/typebox/date.js +47 -0
  35. package/dist/formats/typebox/date.js.map +1 -0
  36. package/dist/formats/typebox/date_time.d.ts +7 -0
  37. package/dist/formats/typebox/date_time.d.ts.map +1 -0
  38. package/dist/formats/typebox/date_time.js +39 -0
  39. package/dist/formats/typebox/date_time.js.map +1 -0
  40. package/dist/formats/typebox/duration.d.ts +6 -0
  41. package/dist/formats/typebox/duration.d.ts.map +1 -0
  42. package/dist/formats/typebox/duration.js +36 -0
  43. package/dist/formats/typebox/duration.js.map +1 -0
  44. package/dist/formats/typebox/email.d.ts +6 -0
  45. package/dist/formats/typebox/email.d.ts.map +1 -0
  46. package/dist/formats/typebox/email.js +36 -0
  47. package/dist/formats/typebox/email.js.map +1 -0
  48. package/dist/formats/typebox/hostname.d.ts +8 -0
  49. package/dist/formats/typebox/hostname.d.ts.map +1 -0
  50. package/dist/formats/typebox/hostname.js +38 -0
  51. package/dist/formats/typebox/hostname.js.map +1 -0
  52. package/dist/formats/typebox/idn_email.d.ts +6 -0
  53. package/dist/formats/typebox/idn_email.d.ts.map +1 -0
  54. package/dist/formats/typebox/idn_email.js +36 -0
  55. package/dist/formats/typebox/idn_email.js.map +1 -0
  56. package/dist/formats/typebox/idn_hostname.d.ts +9 -0
  57. package/dist/formats/typebox/idn_hostname.d.ts.map +1 -0
  58. package/dist/formats/typebox/idn_hostname.js +39 -0
  59. package/dist/formats/typebox/idn_hostname.js.map +1 -0
  60. package/dist/formats/typebox/idna/format/bidi.d.ts +4 -0
  61. package/dist/formats/typebox/idna/format/bidi.d.ts.map +1 -0
  62. package/dist/formats/typebox/idna/format/bidi.js +132 -0
  63. package/dist/formats/typebox/idna/format/bidi.js.map +1 -0
  64. package/dist/formats/typebox/idna/format/puny.d.ts +4 -0
  65. package/dist/formats/typebox/idna/format/puny.d.ts.map +1 -0
  66. package/dist/formats/typebox/idna/format/puny.js +172 -0
  67. package/dist/formats/typebox/idna/format/puny.js.map +1 -0
  68. package/dist/formats/typebox/idna/hostname.d.ts +2 -0
  69. package/dist/formats/typebox/idna/hostname.d.ts.map +1 -0
  70. package/dist/formats/typebox/idna/hostname.js +43 -0
  71. package/dist/formats/typebox/idna/hostname.js.map +1 -0
  72. package/dist/formats/typebox/idna/idn-hostname.d.ts +2 -0
  73. package/dist/formats/typebox/idna/idn-hostname.d.ts.map +1 -0
  74. package/dist/formats/typebox/idna/idn-hostname.js +71 -0
  75. package/dist/formats/typebox/idna/idn-hostname.js.map +1 -0
  76. package/dist/formats/typebox/idna/index.d.ts +3 -0
  77. package/dist/formats/typebox/idna/index.d.ts.map +1 -0
  78. package/dist/formats/typebox/idna/index.js +30 -0
  79. package/dist/formats/typebox/idna/index.js.map +1 -0
  80. package/dist/formats/typebox/idna/label/ascii.d.ts +2 -0
  81. package/dist/formats/typebox/idna/label/ascii.d.ts.map +1 -0
  82. package/dist/formats/typebox/idna/label/ascii.js +37 -0
  83. package/dist/formats/typebox/idna/label/ascii.js.map +1 -0
  84. package/dist/formats/typebox/idna/label/puny.d.ts +2 -0
  85. package/dist/formats/typebox/idna/label/puny.d.ts.map +1 -0
  86. package/dist/formats/typebox/idna/label/puny.js +56 -0
  87. package/dist/formats/typebox/idna/label/puny.js.map +1 -0
  88. package/dist/formats/typebox/idna/label/unicode.d.ts +2 -0
  89. package/dist/formats/typebox/idna/label/unicode.d.ts.map +1 -0
  90. package/dist/formats/typebox/idna/label/unicode.js +134 -0
  91. package/dist/formats/typebox/idna/label/unicode.js.map +1 -0
  92. package/dist/formats/typebox/idna/pattern/pattern.d.ts +31 -0
  93. package/dist/formats/typebox/idna/pattern/pattern.d.ts.map +1 -0
  94. package/dist/formats/typebox/idna/pattern/pattern.js +98 -0
  95. package/dist/formats/typebox/idna/pattern/pattern.js.map +1 -0
  96. package/dist/formats/typebox/ipv4.d.ts +6 -0
  97. package/dist/formats/typebox/ipv4.d.ts.map +1 -0
  98. package/dist/formats/typebox/ipv4.js +36 -0
  99. package/dist/formats/typebox/ipv4.js.map +1 -0
  100. package/dist/formats/typebox/ipv6.d.ts +6 -0
  101. package/dist/formats/typebox/ipv6.d.ts.map +1 -0
  102. package/dist/formats/typebox/ipv6.js +36 -0
  103. package/dist/formats/typebox/ipv6.js.map +1 -0
  104. package/dist/formats/typebox/iri.d.ts +6 -0
  105. package/dist/formats/typebox/iri.d.ts.map +1 -0
  106. package/dist/formats/typebox/iri.js +63 -0
  107. package/dist/formats/typebox/iri.js.map +1 -0
  108. package/dist/formats/typebox/iri_reference.d.ts +6 -0
  109. package/dist/formats/typebox/iri_reference.d.ts.map +1 -0
  110. package/dist/formats/typebox/iri_reference.js +40 -0
  111. package/dist/formats/typebox/iri_reference.js.map +1 -0
  112. package/dist/formats/typebox/json_pointer.d.ts +7 -0
  113. package/dist/formats/typebox/json_pointer.d.ts.map +1 -0
  114. package/dist/formats/typebox/json_pointer.js +37 -0
  115. package/dist/formats/typebox/json_pointer.js.map +1 -0
  116. package/dist/formats/typebox/json_pointer_uri_fragment.d.ts +7 -0
  117. package/dist/formats/typebox/json_pointer_uri_fragment.d.ts.map +1 -0
  118. package/dist/formats/typebox/json_pointer_uri_fragment.js +37 -0
  119. package/dist/formats/typebox/json_pointer_uri_fragment.js.map +1 -0
  120. package/dist/formats/typebox/regex.d.ts +6 -0
  121. package/dist/formats/typebox/regex.d.ts.map +1 -0
  122. package/dist/formats/typebox/regex.js +41 -0
  123. package/dist/formats/typebox/regex.js.map +1 -0
  124. package/dist/formats/typebox/relative_json_pointer.d.ts +7 -0
  125. package/dist/formats/typebox/relative_json_pointer.d.ts.map +1 -0
  126. package/dist/formats/typebox/relative_json_pointer.js +37 -0
  127. package/dist/formats/typebox/relative_json_pointer.js.map +1 -0
  128. package/dist/formats/typebox/time.d.ts +6 -0
  129. package/dist/formats/typebox/time.d.ts.map +1 -0
  130. package/dist/formats/typebox/time.js +60 -0
  131. package/dist/formats/typebox/time.js.map +1 -0
  132. package/dist/formats/typebox/uri.d.ts +6 -0
  133. package/dist/formats/typebox/uri.d.ts.map +1 -0
  134. package/dist/formats/typebox/uri.js +36 -0
  135. package/dist/formats/typebox/uri.js.map +1 -0
  136. package/dist/formats/typebox/uri_reference.d.ts +6 -0
  137. package/dist/formats/typebox/uri_reference.d.ts.map +1 -0
  138. package/dist/formats/typebox/uri_reference.js +36 -0
  139. package/dist/formats/typebox/uri_reference.js.map +1 -0
  140. package/dist/formats/typebox/uri_template.d.ts +6 -0
  141. package/dist/formats/typebox/uri_template.d.ts.map +1 -0
  142. package/dist/formats/typebox/uri_template.js +37 -0
  143. package/dist/formats/typebox/uri_template.js.map +1 -0
  144. package/dist/formats/typebox/url.d.ts +7 -0
  145. package/dist/formats/typebox/url.d.ts.map +1 -0
  146. package/dist/formats/typebox/url.js +36 -0
  147. package/dist/formats/typebox/url.js.map +1 -0
  148. package/dist/formats/typebox/uuid.d.ts +7 -0
  149. package/dist/formats/typebox/uuid.d.ts.map +1 -0
  150. package/dist/formats/typebox/uuid.js +37 -0
  151. package/dist/formats/typebox/uuid.js.map +1 -0
  152. package/dist/index.d.ts +4 -0
  153. package/dist/index.d.ts.map +1 -0
  154. package/dist/index.js +5 -0
  155. package/dist/index.js.map +1 -0
  156. package/dist/resolver.d.ts +57 -0
  157. package/dist/resolver.d.ts.map +1 -0
  158. package/dist/resolver.js +202 -0
  159. package/dist/resolver.js.map +1 -0
  160. package/dist/shared.d.ts +9 -0
  161. package/dist/shared.d.ts.map +1 -0
  162. package/dist/shared.js +33 -0
  163. package/dist/shared.js.map +1 -0
  164. package/dist/typebox-static.d.ts +48 -0
  165. package/dist/typebox-static.d.ts.map +1 -0
  166. package/dist/typebox-static.js +5 -0
  167. package/dist/typebox-static.js.map +1 -0
  168. package/dist/types.d.ts +48 -0
  169. package/dist/types.d.ts.map +1 -0
  170. package/dist/types.js +3 -0
  171. package/dist/types.js.map +1 -0
  172. package/dist/validator.d.ts +39 -0
  173. package/dist/validator.d.ts.map +1 -0
  174. package/dist/validator.js +71 -0
  175. package/dist/validator.js.map +1 -0
  176. package/package.json +59 -5
package/dist/coerce.js ADDED
@@ -0,0 +1,585 @@
1
+ // Coercion: turns a loosely typed value (a model's tool call arguments, form input, query strings) into the value a
2
+ // schema most likely meant, before it is checked. Only validators compiled with `{ coerce: true }` have it, and they
3
+ // build it on their first coerce() or parse(); the check path never runs it.
4
+ //
5
+ // Three passes, in this order:
6
+ // 1. Optional nulls: a `null` in an optional property whose schema rejects null is dropped, as if omitted.
7
+ // 2. Conversion rules ported from TypeBox 1.3.27's Value.Convert (MIT), keyed on JSON Schema structure (`$ref`, `const`,
8
+ // `enum`, `anyOf`/`oneOf`, `allOf`, `type`, `items`/`prefixItems`, `properties`/`patternProperties`) instead of
9
+ // TypeBox's kind marks: `null` becomes "null" for a string, `"3.5"` becomes 3 for an integer, a single value becomes
10
+ // a one-item array, and a union takes the first branch whose conversion passes.
11
+ // 3. Primitive coercion by `type` (also type arrays), as in Ajv's `coerceTypes`: what pass 2 left, for example a type
12
+ // array `["integer", "null"]`, or `""` for `null`.
13
+ // A value that cannot be coerced is left as it is; the check then reports it.
14
+ //
15
+ // Every pass is copy-on-write: an object or array is copied only when one of its members changes, so an unchanged
16
+ // value comes back as the same reference and the input is never modified. All passes walk the same keywords for
17
+ // members: `properties`, then `patternProperties`, then `additionalProperties` for keys neither names; tuple items by
18
+ // `prefixItems` (or a draft-7 `items` array), the rest by `items`. Draft 7's `additionalItems` is not coerced.
19
+ import { isSchemaObject } from "./resolver.js";
20
+ import { sharedRegex } from "./shared.js";
21
+ /** Coerces values for one compiled schema. */
22
+ export class Coercer {
23
+ c;
24
+ schema;
25
+ /**
26
+ * The (schema node, value) pairs pass 2 is converting through a re-entry point, as a stack. Conversion can enter
27
+ * a node again with the same value only through a `$ref` or by wrapping a single value into an array (also for
28
+ * schemas that cycle through shared JS objects): a recursive schema that meets a value no branch accepts would
29
+ * convert it without end. A pair already on the stack (primitives by value, objects by reference) leaves the value
30
+ * unchanged. Only those two points push, so schemas without references or wraps pay nothing.
31
+ */
32
+ entered = [];
33
+ /** How often a re-entry was cut short in this coerce() call; results computed across one are not memoized. */
34
+ cuts = 0;
35
+ /**
36
+ * Results of `$ref` targets by (node, value) within one coerce() call: a recursive union whose branches all
37
+ * convert the same children (and all fail) would convert them once per branch, exponentially in the depth.
38
+ */
39
+ memo = undefined;
40
+ constructor(compiler, schema) {
41
+ this.c = compiler;
42
+ this.schema = schema;
43
+ }
44
+ /** `value` coerced: a copy along the changed paths only, or `value` itself when nothing changes. */
45
+ coerce(value) {
46
+ const base = this.c.r.rootBase;
47
+ const withoutNulls = this.dropOptionalNulls(value, this.schema, base);
48
+ return this.coerceTypes(this.convertOnly(withoutNulls), this.schema, base);
49
+ }
50
+ /** Pass 2 alone, as TypeBox's `Value.Convert` (the TypeBox conformance tests run it). */
51
+ convertOnly(value) {
52
+ try {
53
+ return this.convert(this.schema, this.c.r.rootBase, value);
54
+ }
55
+ finally {
56
+ this.memo = undefined;
57
+ this.cuts = 0;
58
+ }
59
+ }
60
+ /** `node` converted for `value` at a re-entry point: unchanged when that pair is already being converted. */
61
+ reenter(node, base, value, memoize) {
62
+ const entered = this.entered;
63
+ for (let i = 0; i < entered.length; i += 2) {
64
+ if (entered[i] === node && Object.is(entered[i + 1], value)) {
65
+ this.cuts++;
66
+ return value;
67
+ }
68
+ }
69
+ const memo = memoize ? this.memo?.get(node) : undefined;
70
+ if (memo?.has(value))
71
+ return memo.get(value);
72
+ const cuts = this.cuts;
73
+ entered.push(node, value);
74
+ let result;
75
+ try {
76
+ result = this.convert(node, base, value);
77
+ }
78
+ finally {
79
+ entered.length -= 2;
80
+ }
81
+ if (memoize && this.cuts === cuts) {
82
+ this.memo ??= new Map();
83
+ let values = this.memo.get(node);
84
+ if (values === undefined) {
85
+ values = new Map();
86
+ this.memo.set(node, values);
87
+ }
88
+ values.set(value, result);
89
+ }
90
+ return result;
91
+ }
92
+ /** Whether `value` passes the schema `node`; undefined when `node` cannot be compiled. */
93
+ passes(node, base, value) {
94
+ try {
95
+ return this.c.check(node, this.baseOf(node, base))(value);
96
+ }
97
+ catch {
98
+ return undefined;
99
+ }
100
+ }
101
+ baseOf(node, base) {
102
+ return isSchemaObject(node) ? (this.c.r.baseOf.get(node) ?? base) : base;
103
+ }
104
+ // ---------------------------------------------------------------------------------------------------------------
105
+ // Pass 1: optional nulls
106
+ // ---------------------------------------------------------------------------------------------------------------
107
+ dropOptionalNulls(value, schema, base) {
108
+ if (!isSchemaObject(schema))
109
+ return value;
110
+ const b = this.baseOf(schema, base);
111
+ if (Array.isArray(value)) {
112
+ return mapItems(value, (item, i) => {
113
+ const itemSchema = itemSchemaAt(schema, i);
114
+ return itemSchema === undefined ? item : this.dropOptionalNulls(item, itemSchema, b);
115
+ });
116
+ }
117
+ if (!isPlainObject(value))
118
+ return value;
119
+ const properties = isSchemaObject(schema.properties) ? schema.properties : {};
120
+ const patterns = isSchemaObject(schema.patternProperties)
121
+ ? Object.entries(schema.patternProperties).map(([pattern, item]) => [sharedRegex(pattern), item])
122
+ : [];
123
+ const additional = isSchemaObject(schema.additionalProperties) ? schema.additionalProperties : undefined;
124
+ if (patterns.length === 0 && additional === undefined && !isSchemaObject(schema.properties))
125
+ return value;
126
+ const required = new Set(Array.isArray(schema.required) ? schema.required : []);
127
+ let out;
128
+ const set = (key, next) => {
129
+ if (next === (out ?? value)[key])
130
+ return;
131
+ out ??= { ...value };
132
+ out[key] = next;
133
+ };
134
+ for (const [key, property] of Object.entries(properties)) {
135
+ if (!Object.hasOwn(value, key))
136
+ continue;
137
+ const isRef = isSchemaObject(property) && typeof property.$ref === "string";
138
+ if (value[key] === null && !required.has(key) && !isRef && this.passes(property, b, null) === false) {
139
+ out ??= { ...value };
140
+ delete out[key];
141
+ continue;
142
+ }
143
+ set(key, this.dropOptionalNulls(value[key], property, b));
144
+ }
145
+ // Values of pattern and additional properties are walked for their own optional nulls; the keys themselves
146
+ // are not optional properties, so they stay.
147
+ if (patterns.length > 0 || additional !== undefined) {
148
+ for (const key of Object.keys(value)) {
149
+ let matched = Object.hasOwn(properties, key);
150
+ for (const [pattern, item] of patterns) {
151
+ if (!pattern.test(key))
152
+ continue;
153
+ matched = true;
154
+ set(key, this.dropOptionalNulls((out ?? value)[key], item, b));
155
+ }
156
+ if (!matched && additional !== undefined)
157
+ set(key, this.dropOptionalNulls(value[key], additional, b));
158
+ }
159
+ }
160
+ return out ?? value;
161
+ }
162
+ // ---------------------------------------------------------------------------------------------------------------
163
+ // Pass 2: TypeBox's conversion rules
164
+ // ---------------------------------------------------------------------------------------------------------------
165
+ convert(node, base, value) {
166
+ if (!isSchemaObject(node))
167
+ return value;
168
+ const b = this.baseOf(node, base);
169
+ if (typeof node.$ref === "string") {
170
+ let target;
171
+ try {
172
+ target = this.c.r.resolve(node.$ref, b);
173
+ }
174
+ catch {
175
+ return value;
176
+ }
177
+ return isSchemaObject(target.node) ? this.reenter(target.node, target.base, value, true) : value;
178
+ }
179
+ return this.convertNode(node, b, value);
180
+ }
181
+ /** One schema node's conversion: its first keyword among `const`, `enum`, the unions, `allOf` and `type`. */
182
+ convertNode(node, b, value) {
183
+ if (Object.hasOwn(node, "const"))
184
+ return convertLiteral(node.const, value);
185
+ if (Array.isArray(node.enum))
186
+ return this.convertEnum(node, node.enum, b, value);
187
+ // A union or intersection node may also declare members (plain JSON Schema; TypeBox's never do): they convert
188
+ // after the branches.
189
+ if (Array.isArray(node.anyOf))
190
+ return this.convertUnion(node, node.anyOf, b, value);
191
+ if (Array.isArray(node.oneOf))
192
+ return this.convertUnion(node, node.oneOf, b, value);
193
+ if (Array.isArray(node.allOf)) {
194
+ let next = value;
195
+ for (const member of node.allOf)
196
+ next = this.convert(member, b, next);
197
+ return this.convertMembers(node, b, next);
198
+ }
199
+ switch (node.type) {
200
+ case "string":
201
+ return orValue(tryString(value), value);
202
+ case "number":
203
+ return orValue(tryNumber(value), value);
204
+ case "integer": {
205
+ const result = tryNumber(value);
206
+ return result === undefined ? value : Math.trunc(result.value);
207
+ }
208
+ case "boolean":
209
+ return orValue(tryBoolean(value), value);
210
+ case "null":
211
+ return orValue(tryNull(value), value);
212
+ case "array":
213
+ return this.convertArray(node, b, value);
214
+ case "object":
215
+ return this.convertObject(node, b, value);
216
+ case undefined:
217
+ if (node.properties !== undefined || node.patternProperties !== undefined) {
218
+ return this.convertObject(node, b, value);
219
+ }
220
+ if (node.items !== undefined || node.prefixItems !== undefined)
221
+ return this.convertArray(node, b, value);
222
+ return value;
223
+ default:
224
+ return value;
225
+ }
226
+ }
227
+ /** An enum is a union of its values: a member stays, else the first value the input converts to. */
228
+ convertEnum(node, values, base, value) {
229
+ if (values.some((member) => member === value))
230
+ return value;
231
+ for (const member of values) {
232
+ const candidate = convertLiteral(member, value);
233
+ if (candidate !== value && this.passes(node, base, candidate) === true)
234
+ return candidate;
235
+ }
236
+ return value;
237
+ }
238
+ /**
239
+ * A value one branch accepts stays; else the first branch's conversion that the whole union accepts. Branches are
240
+ * converted in order and only until one is accepted, so a later recursive branch is not entered needlessly. A node
241
+ * that also declares members (plain JSON Schema; TypeBox's never do) converts them into each candidate before it is
242
+ * checked, so a branch is not discarded for a sibling property that is converted after it.
243
+ */
244
+ convertUnion(node, branches, base, value) {
245
+ if (branches.some((branch) => this.passes(branch, base, value) === true))
246
+ return this.convertMembers(node, base, value);
247
+ for (const branch of branches) {
248
+ const candidate = this.convertMembers(node, base, this.convert(branch, base, value));
249
+ if (this.passes(node, base, candidate) === true)
250
+ return candidate;
251
+ }
252
+ return this.convertMembers(node, base, value);
253
+ }
254
+ /** The object members of a union or intersection node, when it declares any. */
255
+ convertMembers(node, base, value) {
256
+ const declares = node.properties !== undefined ||
257
+ node.patternProperties !== undefined ||
258
+ node.additionalProperties !== undefined;
259
+ return declares ? this.convertObject(node, base, value) : value;
260
+ }
261
+ convertArray(node, base, value) {
262
+ const prefix = Array.isArray(node.items)
263
+ ? node.items
264
+ : Array.isArray(node.prefixItems)
265
+ ? node.prefixItems
266
+ : undefined;
267
+ if (prefix !== undefined) {
268
+ // A tuple converts its items and never wraps a single value.
269
+ if (!Array.isArray(value))
270
+ return value;
271
+ const rest = Array.isArray(node.items) ? undefined : node.items;
272
+ return mapItems(value, (item, i) => {
273
+ const schema = i < prefix.length ? prefix[i] : rest;
274
+ return schema === undefined ? item : this.convert(schema, base, item);
275
+ });
276
+ }
277
+ if (Array.isArray(value)) {
278
+ return node.items === undefined ? value : mapItems(value, (item) => this.convert(node.items, base, item));
279
+ }
280
+ // The wrapped single value is converted by the item schema with the same value: a re-entry point.
281
+ const item = node.items;
282
+ return [isSchemaObject(item) ? this.reenter(item, base, value, false) : value];
283
+ }
284
+ convertObject(node, base, value) {
285
+ if (!isPlainObject(value))
286
+ return value;
287
+ const properties = isSchemaObject(node.properties) ? node.properties : {};
288
+ const patterns = isSchemaObject(node.patternProperties)
289
+ ? Object.entries(node.patternProperties).map(([pattern, schema]) => [sharedRegex(pattern), schema])
290
+ : [];
291
+ const additional = isSchemaObject(node.additionalProperties) ? node.additionalProperties : undefined;
292
+ let out;
293
+ for (const key of Object.keys(value)) {
294
+ // An undefined property counts as absent.
295
+ if (value[key] === undefined)
296
+ continue;
297
+ let next = value[key];
298
+ let matched = Object.hasOwn(properties, key);
299
+ if (matched)
300
+ next = this.convert(properties[key], base, next);
301
+ for (const [pattern, schema] of patterns) {
302
+ if (!pattern.test(key))
303
+ continue;
304
+ matched = true;
305
+ next = this.convert(schema, base, next);
306
+ }
307
+ if (!matched && additional !== undefined)
308
+ next = this.convert(additional, base, next);
309
+ if (next !== value[key]) {
310
+ out ??= { ...value };
311
+ out[key] = next;
312
+ }
313
+ }
314
+ return out ?? value;
315
+ }
316
+ // ---------------------------------------------------------------------------------------------------------------
317
+ // Pass 3: primitive coercion by type
318
+ // ---------------------------------------------------------------------------------------------------------------
319
+ coerceTypes(value, schema, base) {
320
+ if (!isSchemaObject(schema))
321
+ return value;
322
+ const b = this.baseOf(schema, base);
323
+ let next = value;
324
+ if (Array.isArray(schema.allOf))
325
+ for (const member of schema.allOf)
326
+ next = this.coerceTypes(next, member, b);
327
+ if (Array.isArray(schema.anyOf))
328
+ next = this.coerceUnion(next, schema.anyOf, b);
329
+ if (Array.isArray(schema.oneOf))
330
+ next = this.coerceUnion(next, schema.oneOf, b);
331
+ const types = typesOf(schema);
332
+ const matchesUnionMember = types.length > 1 && types.some((type) => matchesType(next, type));
333
+ if (types.length > 0 && !matchesUnionMember) {
334
+ for (const type of types) {
335
+ const candidate = coercePrimitive(next, type);
336
+ if (candidate !== next) {
337
+ next = candidate;
338
+ break;
339
+ }
340
+ }
341
+ }
342
+ if (types.includes("object") && isPlainObject(next))
343
+ next = this.coerceProperties(next, schema, b);
344
+ if (types.includes("array") && Array.isArray(next))
345
+ next = this.coerceItems(next, schema, b);
346
+ return next;
347
+ }
348
+ coerceUnion(value, branches, base) {
349
+ for (const branch of branches)
350
+ if (this.passes(branch, base, value) === true)
351
+ return value;
352
+ for (const branch of branches) {
353
+ const coerced = this.coerceTypes(value, branch, base);
354
+ if (this.passes(branch, base, coerced) === true)
355
+ return coerced;
356
+ }
357
+ return value;
358
+ }
359
+ coerceProperties(value, schema, base) {
360
+ const properties = isSchemaObject(schema.properties) ? schema.properties : {};
361
+ const patterns = isSchemaObject(schema.patternProperties)
362
+ ? Object.entries(schema.patternProperties).map(([pattern, item]) => [sharedRegex(pattern), item])
363
+ : [];
364
+ const additional = isSchemaObject(schema.additionalProperties) ? schema.additionalProperties : undefined;
365
+ let out;
366
+ const set = (key, next) => {
367
+ if (next === (out ?? value)[key])
368
+ return;
369
+ out ??= { ...value };
370
+ out[key] = next;
371
+ };
372
+ for (const [key, property] of Object.entries(properties)) {
373
+ if (key in value)
374
+ set(key, this.coerceTypes(value[key], property, base));
375
+ }
376
+ for (const key of Object.keys(value)) {
377
+ let matched = Object.hasOwn(properties, key);
378
+ for (const [pattern, item] of patterns) {
379
+ if (!pattern.test(key))
380
+ continue;
381
+ matched = true;
382
+ set(key, this.coerceTypes((out ?? value)[key], item, base));
383
+ }
384
+ if (!matched && additional !== undefined)
385
+ set(key, this.coerceTypes(value[key], additional, base));
386
+ }
387
+ return out ?? value;
388
+ }
389
+ coerceItems(value, schema, base) {
390
+ return mapItems(value, (item, i) => {
391
+ const itemSchema = itemSchemaAt(schema, i);
392
+ return isSchemaObject(itemSchema) ? this.coerceTypes(item, itemSchema, base) : item;
393
+ });
394
+ }
395
+ }
396
+ /**
397
+ * The schema of an array's item at `index`: a tuple's (`prefixItems`, or a draft-7 `items` array) for its prefix,
398
+ * `items` after `prefixItems`, or `items` for every item. Undefined when nothing applies.
399
+ */
400
+ function itemSchemaAt(schema, index) {
401
+ if (Array.isArray(schema.items))
402
+ return schema.items[index];
403
+ if (Array.isArray(schema.prefixItems) && index < schema.prefixItems.length)
404
+ return schema.prefixItems[index];
405
+ return schema.items;
406
+ }
407
+ /** `items` mapped by `map`; the same array when every item maps to itself. */
408
+ function mapItems(items, map) {
409
+ let out;
410
+ for (let i = 0; i < items.length; i++) {
411
+ const next = map(items[i], i);
412
+ if (next === items[i])
413
+ continue;
414
+ out ??= items.slice();
415
+ out[i] = next;
416
+ }
417
+ return out ?? items;
418
+ }
419
+ function orValue(result, value) {
420
+ return result === undefined ? value : result.value;
421
+ }
422
+ /** A literal (`const`, an enum value) taken from a value of another type that converts to exactly it. */
423
+ function convertLiteral(literal, value) {
424
+ if (literal === value)
425
+ return value;
426
+ const result = typeof literal === "string"
427
+ ? tryString(value)
428
+ : typeof literal === "number"
429
+ ? tryNumber(value)
430
+ : typeof literal === "boolean"
431
+ ? tryBoolean(value)
432
+ : undefined;
433
+ return result !== undefined && result.value === literal ? result.value : value;
434
+ }
435
+ function tryString(value) {
436
+ if (typeof value === "bigint" || typeof value === "boolean" || isFiniteNumber(value))
437
+ return { value: String(value) };
438
+ if (value === null)
439
+ return { value: "null" };
440
+ if (typeof value === "string")
441
+ return { value };
442
+ if (value === undefined)
443
+ return { value: "" };
444
+ return undefined;
445
+ }
446
+ const MAX_BIGINT = BigInt(Number.MAX_SAFE_INTEGER);
447
+ const MIN_BIGINT = BigInt(Number.MIN_SAFE_INTEGER);
448
+ function tryNumber(value) {
449
+ if (typeof value === "bigint")
450
+ return value <= MAX_BIGINT && value >= MIN_BIGINT ? { value: Number(value) } : undefined;
451
+ if (typeof value === "boolean")
452
+ return { value: value ? 1 : 0 };
453
+ if (isFiniteNumber(value))
454
+ return { value };
455
+ if (value === null || value === undefined)
456
+ return { value: 0 };
457
+ if (typeof value !== "string")
458
+ return undefined;
459
+ const coerced = +value;
460
+ if (Number.isFinite(coerced))
461
+ return { value: coerced };
462
+ const lowercase = value.toLowerCase();
463
+ if (lowercase === "false")
464
+ return { value: 0 };
465
+ if (lowercase === "true")
466
+ return { value: 1 };
467
+ const big = bigIntOfString(value);
468
+ return big !== undefined && big <= MAX_BIGINT && big >= MIN_BIGINT ? { value: Number(big) } : undefined;
469
+ }
470
+ /** TypeBox's TryBigInt of a string: `12n`, `12.5` (truncated) or `12`; true and false are not reached from tryNumber. */
471
+ function bigIntOfString(value) {
472
+ if (/^-?(0|[1-9]\d*)n$/.test(value))
473
+ return BigInt(value.slice(0, -1));
474
+ if (/^-?(0|[1-9]\d*)\.\d+$/.test(value))
475
+ return BigInt(value.split(".")[0]);
476
+ if (/^-?(0|[1-9]\d*)$/.test(value))
477
+ return BigInt(value);
478
+ return undefined;
479
+ }
480
+ function tryBoolean(value) {
481
+ if (typeof value === "bigint")
482
+ return value === 0n ? { value: false } : value === 1n ? { value: true } : undefined;
483
+ if (typeof value === "boolean")
484
+ return { value };
485
+ if (isFiniteNumber(value))
486
+ return value === 0 ? { value: false } : value === 1 ? { value: true } : undefined;
487
+ if (value === null || value === undefined)
488
+ return { value: false };
489
+ if (typeof value !== "string")
490
+ return undefined;
491
+ const lowercase = value.toLowerCase();
492
+ if (lowercase === "false" || value === "0")
493
+ return { value: false };
494
+ if (lowercase === "true" || value === "1")
495
+ return { value: true };
496
+ return undefined;
497
+ }
498
+ function tryNull(value) {
499
+ if (typeof value === "bigint")
500
+ return value === 0n ? { value: null } : undefined;
501
+ if (typeof value === "boolean")
502
+ return value === false ? { value: null } : undefined;
503
+ if (isFiniteNumber(value))
504
+ return value === 0 ? { value: null } : undefined;
505
+ if (value === null || value === undefined)
506
+ return { value: null };
507
+ if (typeof value !== "string")
508
+ return undefined;
509
+ const lowercase = value.toLowerCase();
510
+ return lowercase === "undefined" || lowercase === "null" || value === "" || value === "0"
511
+ ? { value: null }
512
+ : undefined;
513
+ }
514
+ function isFiniteNumber(value) {
515
+ return typeof value === "number" && Number.isFinite(value);
516
+ }
517
+ // -------------------------------------------------------------------------------------------------------------------
518
+ // Primitive coercion by type (pass 3)
519
+ // -------------------------------------------------------------------------------------------------------------------
520
+ function typesOf(schema) {
521
+ if (typeof schema.type === "string")
522
+ return [schema.type];
523
+ if (Array.isArray(schema.type))
524
+ return schema.type.filter((type) => typeof type === "string");
525
+ return [];
526
+ }
527
+ function matchesType(value, type) {
528
+ switch (type) {
529
+ case "number":
530
+ return typeof value === "number";
531
+ case "integer":
532
+ return typeof value === "number" && Number.isInteger(value);
533
+ case "boolean":
534
+ return typeof value === "boolean";
535
+ case "string":
536
+ return typeof value === "string";
537
+ case "null":
538
+ return value === null;
539
+ case "array":
540
+ return Array.isArray(value);
541
+ case "object":
542
+ return isPlainObject(value);
543
+ default:
544
+ return false;
545
+ }
546
+ }
547
+ function coercePrimitive(value, type) {
548
+ switch (type) {
549
+ case "number":
550
+ case "integer": {
551
+ if (value === null)
552
+ return 0;
553
+ if (typeof value === "string" && value.trim() !== "") {
554
+ const parsed = Number(value);
555
+ if (type === "number" ? Number.isFinite(parsed) : Number.isInteger(parsed))
556
+ return parsed;
557
+ }
558
+ if (typeof value === "boolean")
559
+ return value ? 1 : 0;
560
+ return value;
561
+ }
562
+ case "boolean":
563
+ if (value === null)
564
+ return false;
565
+ if (value === "true" || value === 1)
566
+ return true;
567
+ if (value === "false" || value === 0)
568
+ return false;
569
+ return value;
570
+ case "string":
571
+ if (value === null)
572
+ return "";
573
+ if (typeof value === "number" || typeof value === "boolean")
574
+ return String(value);
575
+ return value;
576
+ case "null":
577
+ return value === "" || value === 0 || value === false ? null : value;
578
+ default:
579
+ return value;
580
+ }
581
+ }
582
+ function isPlainObject(value) {
583
+ return typeof value === "object" && value !== null && !Array.isArray(value);
584
+ }
585
+ //# sourceMappingURL=coerce.js.map