@lovrozagar/oat 0.9.1 → 0.11.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 (283) hide show
  1. package/README.md +26 -1936
  2. package/dist/cli.js +326 -60
  3. package/dist/config/define-config.d.ts +15 -1
  4. package/dist/config/define-config.d.ts.map +1 -1
  5. package/dist/config/define-config.js.map +1 -1
  6. package/dist/config/validate.d.ts +12 -0
  7. package/dist/config/validate.d.ts.map +1 -0
  8. package/dist/config/validate.js +86 -0
  9. package/dist/config/validate.js.map +1 -0
  10. package/dist/conformance/effects.d.ts +9 -0
  11. package/dist/conformance/effects.d.ts.map +1 -0
  12. package/dist/conformance/effects.js +276 -0
  13. package/dist/conformance/effects.js.map +1 -0
  14. package/dist/conformance/expected-failures.d.ts +18 -0
  15. package/dist/conformance/expected-failures.d.ts.map +1 -0
  16. package/dist/conformance/expected-failures.js +48 -0
  17. package/dist/conformance/expected-failures.js.map +1 -0
  18. package/dist/conformance/feature-gate.d.ts +7 -0
  19. package/dist/conformance/feature-gate.d.ts.map +1 -0
  20. package/dist/conformance/feature-gate.js +428 -0
  21. package/dist/conformance/feature-gate.js.map +1 -0
  22. package/dist/conformance/fuzz.d.ts +108 -0
  23. package/dist/conformance/fuzz.d.ts.map +1 -0
  24. package/dist/conformance/fuzz.js +246 -0
  25. package/dist/conformance/fuzz.js.map +1 -0
  26. package/dist/conformance/leaks.d.ts +10 -0
  27. package/dist/conformance/leaks.d.ts.map +1 -0
  28. package/dist/conformance/leaks.js +23 -0
  29. package/dist/conformance/leaks.js.map +1 -0
  30. package/dist/conformance/pool.d.ts +49 -0
  31. package/dist/conformance/pool.d.ts.map +1 -0
  32. package/dist/conformance/pool.js +75 -0
  33. package/dist/conformance/pool.js.map +1 -0
  34. package/dist/conformance/rate-limit.d.ts +7 -0
  35. package/dist/conformance/rate-limit.d.ts.map +1 -0
  36. package/dist/conformance/rate-limit.js +372 -0
  37. package/dist/conformance/rate-limit.js.map +1 -0
  38. package/dist/conformance/scope.d.ts +15 -0
  39. package/dist/conformance/scope.d.ts.map +1 -0
  40. package/dist/conformance/scope.js +78 -0
  41. package/dist/conformance/scope.js.map +1 -0
  42. package/dist/conformance/seed-contract.d.ts +11 -0
  43. package/dist/conformance/seed-contract.d.ts.map +1 -0
  44. package/dist/conformance/seed-contract.js +680 -0
  45. package/dist/conformance/seed-contract.js.map +1 -0
  46. package/dist/conformance/shapes.d.ts +35 -0
  47. package/dist/conformance/shapes.d.ts.map +1 -0
  48. package/dist/conformance/shapes.js +162 -0
  49. package/dist/conformance/shapes.js.map +1 -0
  50. package/dist/conformance/specs.d.ts +21 -0
  51. package/dist/conformance/specs.d.ts.map +1 -0
  52. package/dist/conformance/specs.js +306 -0
  53. package/dist/conformance/specs.js.map +1 -0
  54. package/dist/conformance/suite.d.ts +209 -0
  55. package/dist/conformance/suite.d.ts.map +1 -0
  56. package/dist/conformance/suite.js +1538 -0
  57. package/dist/conformance/suite.js.map +1 -0
  58. package/dist/conformance/unique.d.ts +7 -0
  59. package/dist/conformance/unique.d.ts.map +1 -0
  60. package/dist/conformance/unique.js +586 -0
  61. package/dist/conformance/unique.js.map +1 -0
  62. package/dist/conformance/worker.d.ts +3 -0
  63. package/dist/conformance/worker.d.ts.map +1 -0
  64. package/dist/conformance/worker.js +27 -0
  65. package/dist/conformance/worker.js.map +1 -0
  66. package/dist/index.d.ts +10 -3
  67. package/dist/index.js +6 -2
  68. package/dist/reference/defects.d.ts +71 -1
  69. package/dist/reference/defects.d.ts.map +1 -1
  70. package/dist/reference/defects.js +74 -8
  71. package/dist/reference/defects.js.map +1 -1
  72. package/dist/reference/dialect.d.ts +6 -4
  73. package/dist/reference/dialect.d.ts.map +1 -1
  74. package/dist/reference/dialect.js +1 -0
  75. package/dist/reference/dialect.js.map +1 -1
  76. package/dist/reference/http.d.ts +17 -25
  77. package/dist/reference/http.d.ts.map +1 -1
  78. package/dist/reference/http.js +574 -224
  79. package/dist/reference/http.js.map +1 -1
  80. package/dist/reference/listing.d.ts +51 -0
  81. package/dist/reference/listing.d.ts.map +1 -0
  82. package/dist/reference/listing.js +256 -0
  83. package/dist/reference/listing.js.map +1 -0
  84. package/dist/reference/model.d.ts +35 -1
  85. package/dist/reference/model.d.ts.map +1 -1
  86. package/dist/reference/model.js +42 -0
  87. package/dist/reference/model.js.map +1 -1
  88. package/dist/reference/query.d.ts +132 -43
  89. package/dist/reference/query.d.ts.map +1 -1
  90. package/dist/reference/query.js +397 -315
  91. package/dist/reference/query.js.map +1 -1
  92. package/dist/reference/shapes.d.ts +60 -0
  93. package/dist/reference/shapes.d.ts.map +1 -0
  94. package/dist/reference/shapes.js +142 -0
  95. package/dist/reference/shapes.js.map +1 -0
  96. package/dist/reference/spec.d.ts +9 -2
  97. package/dist/reference/spec.d.ts.map +1 -1
  98. package/dist/reference/spec.js +291 -71
  99. package/dist/reference/spec.js.map +1 -1
  100. package/dist/reference/store-api.d.ts +14 -43
  101. package/dist/reference/store-api.d.ts.map +1 -1
  102. package/dist/reference/store-api.js +8 -58
  103. package/dist/reference/store-api.js.map +1 -1
  104. package/dist/reference/stores/memory.d.ts +12 -15
  105. package/dist/reference/stores/memory.d.ts.map +1 -1
  106. package/dist/reference/stores/memory.js +22 -70
  107. package/dist/reference/stores/memory.js.map +1 -1
  108. package/dist/reference/stores/postgres.d.ts +11 -23
  109. package/dist/reference/stores/postgres.d.ts.map +1 -1
  110. package/dist/reference/stores/postgres.js +53 -334
  111. package/dist/reference/stores/postgres.js.map +1 -1
  112. package/dist/reference/stores/sql-compile.d.ts +36 -0
  113. package/dist/reference/stores/sql-compile.d.ts.map +1 -0
  114. package/dist/reference/stores/sql-compile.js +95 -0
  115. package/dist/reference/stores/sql-compile.js.map +1 -0
  116. package/dist/reference/stores/sqlite.d.ts +12 -27
  117. package/dist/reference/stores/sqlite.d.ts.map +1 -1
  118. package/dist/reference/stores/sqlite.js +54 -355
  119. package/dist/reference/stores/sqlite.js.map +1 -1
  120. package/dist/report/console.d.ts +2 -1
  121. package/dist/report/console.d.ts.map +1 -1
  122. package/dist/report/console.js +56 -10
  123. package/dist/report/console.js.map +1 -1
  124. package/dist/report/matrix.d.ts +10 -13
  125. package/dist/report/matrix.d.ts.map +1 -1
  126. package/dist/report/matrix.js +82 -18
  127. package/dist/report/matrix.js.map +1 -1
  128. package/dist/report/render.d.ts +18 -1
  129. package/dist/report/render.d.ts.map +1 -1
  130. package/dist/report/render.js +169 -14
  131. package/dist/report/render.js.map +1 -1
  132. package/dist/runtime/async.d.ts +16 -3
  133. package/dist/runtime/async.d.ts.map +1 -1
  134. package/dist/runtime/async.js +24 -19
  135. package/dist/runtime/async.js.map +1 -1
  136. package/dist/runtime/auth.d.ts +2 -9
  137. package/dist/runtime/auth.d.ts.map +1 -1
  138. package/dist/runtime/auth.js +38 -62
  139. package/dist/runtime/auth.js.map +1 -1
  140. package/dist/runtime/body.d.ts +15 -3
  141. package/dist/runtime/body.d.ts.map +1 -1
  142. package/dist/runtime/body.js +61 -12
  143. package/dist/runtime/body.js.map +1 -1
  144. package/dist/runtime/check-costs.d.ts +15 -0
  145. package/dist/runtime/check-costs.d.ts.map +1 -0
  146. package/dist/runtime/check-costs.js +114 -0
  147. package/dist/runtime/check-costs.js.map +1 -0
  148. package/dist/runtime/check-ids.d.ts +15 -0
  149. package/dist/runtime/check-ids.d.ts.map +1 -0
  150. package/dist/runtime/check-ids.js +140 -0
  151. package/dist/runtime/check-ids.js.map +1 -0
  152. package/dist/runtime/checks.d.ts +122 -18
  153. package/dist/runtime/checks.d.ts.map +1 -1
  154. package/dist/runtime/checks.js +3437 -1933
  155. package/dist/runtime/checks.js.map +1 -1
  156. package/dist/runtime/client.d.ts +112 -0
  157. package/dist/runtime/client.d.ts.map +1 -1
  158. package/dist/runtime/client.js +271 -39
  159. package/dist/runtime/client.js.map +1 -1
  160. package/dist/runtime/cookies.d.ts +16 -2
  161. package/dist/runtime/cookies.d.ts.map +1 -1
  162. package/dist/runtime/cookies.js +83 -22
  163. package/dist/runtime/cookies.js.map +1 -1
  164. package/dist/runtime/exchanges.d.ts +4 -5
  165. package/dist/runtime/exchanges.d.ts.map +1 -1
  166. package/dist/runtime/exchanges.js +33 -55
  167. package/dist/runtime/exchanges.js.map +1 -1
  168. package/dist/runtime/exit.d.ts +40 -0
  169. package/dist/runtime/exit.d.ts.map +1 -0
  170. package/dist/runtime/exit.js +48 -0
  171. package/dist/runtime/exit.js.map +1 -0
  172. package/dist/runtime/feature-gate.d.ts +0 -9
  173. package/dist/runtime/feature-gate.d.ts.map +1 -1
  174. package/dist/runtime/feature-gate.js +0 -21
  175. package/dist/runtime/feature-gate.js.map +1 -1
  176. package/dist/runtime/finding.d.ts +61 -7
  177. package/dist/runtime/finding.d.ts.map +1 -1
  178. package/dist/runtime/finding.js +74 -12
  179. package/dist/runtime/finding.js.map +1 -1
  180. package/dist/runtime/fixture.d.ts +35 -28
  181. package/dist/runtime/fixture.d.ts.map +1 -1
  182. package/dist/runtime/fixture.js +92 -731
  183. package/dist/runtime/fixture.js.map +1 -1
  184. package/dist/runtime/generate.d.ts +63 -0
  185. package/dist/runtime/generate.d.ts.map +1 -0
  186. package/dist/runtime/generate.js +635 -0
  187. package/dist/runtime/generate.js.map +1 -0
  188. package/dist/runtime/headers.d.ts +6 -0
  189. package/dist/runtime/headers.d.ts.map +1 -0
  190. package/dist/runtime/headers.js +16 -0
  191. package/dist/runtime/headers.js.map +1 -0
  192. package/dist/runtime/path.d.ts +7 -0
  193. package/dist/runtime/path.d.ts.map +1 -0
  194. package/dist/runtime/path.js +44 -0
  195. package/dist/runtime/path.js.map +1 -0
  196. package/dist/runtime/payloads.d.ts +2 -0
  197. package/dist/runtime/payloads.d.ts.map +1 -1
  198. package/dist/runtime/payloads.js +2 -1
  199. package/dist/runtime/payloads.js.map +1 -1
  200. package/dist/runtime/rate-limit.d.ts +6 -4
  201. package/dist/runtime/rate-limit.d.ts.map +1 -1
  202. package/dist/runtime/rate-limit.js +22 -33
  203. package/dist/runtime/rate-limit.js.map +1 -1
  204. package/dist/runtime/redact.d.ts +24 -0
  205. package/dist/runtime/redact.d.ts.map +1 -0
  206. package/dist/runtime/redact.js +98 -0
  207. package/dist/runtime/redact.js.map +1 -0
  208. package/dist/runtime/run.d.ts +51 -2
  209. package/dist/runtime/run.d.ts.map +1 -1
  210. package/dist/runtime/run.js +463 -195
  211. package/dist/runtime/run.js.map +1 -1
  212. package/dist/runtime/runs.d.ts +5 -0
  213. package/dist/runtime/runs.d.ts.map +1 -1
  214. package/dist/runtime/runs.js +7 -1
  215. package/dist/runtime/runs.js.map +1 -1
  216. package/dist/runtime/scope.d.ts +163 -0
  217. package/dist/runtime/scope.d.ts.map +1 -0
  218. package/dist/runtime/scope.js +423 -0
  219. package/dist/runtime/scope.js.map +1 -0
  220. package/dist/runtime/teardown.d.ts +17 -4
  221. package/dist/runtime/teardown.d.ts.map +1 -1
  222. package/dist/runtime/teardown.js +49 -16
  223. package/dist/runtime/teardown.js.map +1 -1
  224. package/dist/runtime/transcript.d.ts +34 -2
  225. package/dist/runtime/transcript.d.ts.map +1 -1
  226. package/dist/runtime/transcript.js +119 -24
  227. package/dist/runtime/transcript.js.map +1 -1
  228. package/dist/runtime/unique.d.ts +2 -11
  229. package/dist/runtime/unique.d.ts.map +1 -1
  230. package/dist/runtime/unique.js +27 -53
  231. package/dist/runtime/unique.js.map +1 -1
  232. package/dist/runtime/validate.d.ts +13 -2
  233. package/dist/runtime/validate.d.ts.map +1 -1
  234. package/dist/runtime/validate.js +51 -31
  235. package/dist/runtime/validate.js.map +1 -1
  236. package/dist/runtime/wait.d.ts +2 -1
  237. package/dist/runtime/wait.d.ts.map +1 -1
  238. package/dist/runtime/wait.js +2 -36
  239. package/dist/runtime/wait.js.map +1 -1
  240. package/dist/runtime/world.d.ts +31 -3
  241. package/dist/runtime/world.d.ts.map +1 -1
  242. package/dist/runtime/world.js +113 -32
  243. package/dist/runtime/world.js.map +1 -1
  244. package/dist/spec/collection.d.ts +8 -2
  245. package/dist/spec/collection.d.ts.map +1 -1
  246. package/dist/spec/collection.js +47 -7
  247. package/dist/spec/collection.js.map +1 -1
  248. package/dist/spec/extensions.d.ts +5 -1
  249. package/dist/spec/extensions.d.ts.map +1 -1
  250. package/dist/spec/extensions.js +9 -2
  251. package/dist/spec/extensions.js.map +1 -1
  252. package/dist/spec/graph.d.ts +46 -0
  253. package/dist/spec/graph.d.ts.map +1 -1
  254. package/dist/spec/graph.js +175 -11
  255. package/dist/spec/graph.js.map +1 -1
  256. package/dist/spec/load.d.ts +25 -5
  257. package/dist/spec/load.d.ts.map +1 -1
  258. package/dist/spec/load.js +160 -59
  259. package/dist/spec/load.js.map +1 -1
  260. package/dist/spec/query-capabilities.d.ts +4 -0
  261. package/dist/spec/query-capabilities.d.ts.map +1 -1
  262. package/dist/spec/query-capabilities.js +6 -0
  263. package/dist/spec/query-capabilities.js.map +1 -1
  264. package/dist/spec/schema.d.ts +20 -0
  265. package/dist/spec/schema.d.ts.map +1 -0
  266. package/dist/spec/schema.js +118 -0
  267. package/dist/spec/schema.js.map +1 -0
  268. package/dist/spec/tag-schema.d.ts +12 -0
  269. package/dist/spec/tag-schema.d.ts.map +1 -0
  270. package/dist/spec/tag-schema.js +143 -0
  271. package/dist/spec/tag-schema.js.map +1 -0
  272. package/dist/spec/types.js +17 -1
  273. package/dist/spec/types.js.map +1 -1
  274. package/docs/api.md +59 -0
  275. package/docs/assumptions.md +99 -0
  276. package/docs/checks.md +183 -0
  277. package/docs/commands.md +214 -0
  278. package/docs/configuration.md +650 -0
  279. package/docs/model.md +184 -0
  280. package/docs/reference-defects.md +94 -0
  281. package/docs/reports.md +211 -0
  282. package/docs/tags.md +400 -0
  283. package/package.json +3 -1
@@ -0,0 +1,635 @@
1
+ /**
2
+ * Values from schemas — the one place oat invents data.
3
+ *
4
+ * Seeding a cohort and probing a check both need values: a record to create, a second value to
5
+ * patch to, an identifier that is well-formed and absent, a value just outside an enum. They used
6
+ * to be invented in two places plus a dozen string literals in the checks (`"oat patched value"`,
7
+ * `"<id>-oat-absent"`), and the literals were wrong exactly where it mattered: not a UUID where the
8
+ * API takes UUIDs, not a date where it takes dates. Everything comes from here now, from the
9
+ * normalized schema (see `spec/schema.ts`), and anything that cannot be constructed says so rather
10
+ * than sending something the document forbids.
11
+ *
12
+ * Generation is deterministic in its inputs. Distinctness across runs comes from a per-run nonce,
13
+ * never from suffix arithmetic that ignores `maximum`, `multipleOf`, `enum` or `format`.
14
+ */
15
+ import { DEFS_ID } from "../spec/load.js";
16
+ import { UNICODE_COHORT_STRING, codePointCount, sliceCodePoints } from "./payloads.js";
17
+ const STRINGS = {
18
+ baseline: "Quarterly Report",
19
+ boundary: "",
20
+ "lexical-first": "aaa first alphabetically",
21
+ "lexical-last": "zzz last alphabetically",
22
+ metacharacter: "100% _off_ *everything*",
23
+ "null-heavy": "null heavy record",
24
+ unicode: UNICODE_COHORT_STRING,
25
+ };
26
+ /** How many times one recursive schema may nest inside itself before generation stops descending. */
27
+ const RECURSION_BOUND = 2;
28
+ const MAX_DEPTH = 12;
29
+ class Unconstructible extends Error {
30
+ reason;
31
+ pointer;
32
+ constructor(reason, pointer) {
33
+ super(reason);
34
+ this.reason = reason;
35
+ this.pointer = pointer;
36
+ }
37
+ }
38
+ /** One value valid for `schema`, or why none can be made. */
39
+ export function generate(schema, options = {}) {
40
+ const ctx = {
41
+ defs: options.defs ?? {},
42
+ depth: 0,
43
+ distinct: options.distinct ?? new Set(),
44
+ index: options.index ?? 0,
45
+ nonce: options.nonce,
46
+ optional: options.optional ?? "all",
47
+ rand: options.rand ?? (() => 0.5),
48
+ refs: [],
49
+ variant: options.variant ?? "baseline",
50
+ };
51
+ try {
52
+ return { ok: true, value: instance(schema, ctx, "", false) };
53
+ }
54
+ catch (error) {
55
+ if (error instanceof Unconstructible)
56
+ return { ok: false, pointer: error.pointer || "/", reason: error.reason };
57
+ if (error instanceof RangeError)
58
+ return { ok: false, pointer: "/", reason: "the schema nests too deeply to generate" };
59
+ throw error;
60
+ }
61
+ }
62
+ /* ------------------------------------------------------------------ schema algebra */
63
+ function isObject(value) {
64
+ return value !== null && typeof value === "object" && !Array.isArray(value);
65
+ }
66
+ const ANNOTATIONS = new Set([
67
+ "description",
68
+ "title",
69
+ "deprecated",
70
+ "examples",
71
+ "readOnly",
72
+ "writeOnly",
73
+ "default",
74
+ "$comment",
75
+ "$schema",
76
+ "$id",
77
+ "$anchor",
78
+ "contentMediaType",
79
+ "contentEncoding",
80
+ ]);
81
+ /** `{}`, `true`, or annotations alone: any value at all. */
82
+ export function isVacuous(schema) {
83
+ if (schema === true)
84
+ return true;
85
+ if (!isObject(schema))
86
+ return false;
87
+ return Object.keys(schema).every((key) => ANNOTATIONS.has(key) || key.startsWith("x-"));
88
+ }
89
+ /** Folds `allOf` branches into one schema: properties merged, bounds intersected. */
90
+ function mergeAll(base, branches) {
91
+ let out = { ...base };
92
+ delete out.allOf;
93
+ for (const branch of branches) {
94
+ if (!isObject(branch))
95
+ continue;
96
+ out = merge(out, branch.allOf === undefined ? branch : mergeAll(branch, branch.allOf));
97
+ }
98
+ return out;
99
+ }
100
+ function merge(a, b) {
101
+ const out = { ...a };
102
+ for (const [key, value] of Object.entries(b)) {
103
+ const existing = out[key];
104
+ if (existing === undefined) {
105
+ out[key] = value;
106
+ continue;
107
+ }
108
+ switch (key) {
109
+ case "properties":
110
+ out.properties = { ...existing, ...value };
111
+ break;
112
+ case "required":
113
+ out.required = [...new Set([...existing, ...value])];
114
+ break;
115
+ case "minimum":
116
+ case "exclusiveMinimum":
117
+ case "minLength":
118
+ case "minItems":
119
+ out[key] = Math.max(Number(existing), Number(value));
120
+ break;
121
+ case "maximum":
122
+ case "exclusiveMaximum":
123
+ case "maxLength":
124
+ case "maxItems":
125
+ out[key] = Math.min(Number(existing), Number(value));
126
+ break;
127
+ case "enum":
128
+ out.enum = existing.filter((item) => value.some((other) => JSON.stringify(other) === JSON.stringify(item)));
129
+ break;
130
+ case "type": {
131
+ const left = Array.isArray(existing) ? existing : [existing];
132
+ const right = Array.isArray(value) ? value : [value];
133
+ const both = left.filter((type) => right.includes(type) || (type === "integer" && right.includes("number")));
134
+ out.type = both.length === 1 ? both[0] : both;
135
+ break;
136
+ }
137
+ default:
138
+ out[key] = isObject(existing) && isObject(value) ? merge(existing, value) : value;
139
+ }
140
+ }
141
+ return out;
142
+ }
143
+ function typesOf(schema) {
144
+ const type = schema.type;
145
+ if (typeof type === "string")
146
+ return [type];
147
+ if (Array.isArray(type))
148
+ return type.filter((item) => typeof item === "string");
149
+ if (schema.properties !== undefined || schema.required !== undefined)
150
+ return ["object"];
151
+ if (schema.items !== undefined || schema.prefixItems !== undefined)
152
+ return ["array"];
153
+ if (["minimum", "maximum", "exclusiveMinimum", "exclusiveMaximum", "multipleOf"].some((k) => k in schema)) {
154
+ return ["number"];
155
+ }
156
+ return ["string"];
157
+ }
158
+ /* ------------------------------------------------------------------ the walk */
159
+ function instance(raw, ctx, pointer, distinct) {
160
+ if (ctx.depth > MAX_DEPTH)
161
+ throw new Unconstructible("the schema nests too deeply to generate", pointer);
162
+ if (raw === false)
163
+ throw new Unconstructible("the schema admits no value (false)", pointer);
164
+ if (raw === true || isVacuous(raw))
165
+ return text(ctx, distinct);
166
+ if (!isObject(raw))
167
+ throw new Unconstructible("the schema is not an object", pointer);
168
+ let schema = raw;
169
+ const ref = schema.$ref;
170
+ if (typeof ref === "string") {
171
+ const name = ref.startsWith(`${DEFS_ID}#/$defs/`) ? ref.slice(`${DEFS_ID}#/$defs/`.length) : undefined;
172
+ const target = name === undefined ? undefined : ctx.defs[name];
173
+ if (name === undefined || target === undefined) {
174
+ throw new Unconstructible(`cannot follow ${ref}`, pointer);
175
+ }
176
+ if (ctx.refs.filter((seen) => seen === name).length >= RECURSION_BOUND) {
177
+ throw new Unconstructible(`recursion through ${name} is bounded at depth ${RECURSION_BOUND}`, pointer);
178
+ }
179
+ const { $ref: _ref, ...siblings } = schema;
180
+ const inner = { ...ctx, refs: [...ctx.refs, name] };
181
+ return instance(Object.keys(siblings).length === 0 ? target : merge(target, siblings), inner, pointer, distinct);
182
+ }
183
+ if (Array.isArray(schema.allOf))
184
+ schema = mergeAll(schema, schema.allOf);
185
+ if ("const" in schema)
186
+ return schema.const;
187
+ /* A union keeps its base: `{properties: {kind}, oneOf: [...]}` needs `kind` whichever branch. */
188
+ const union = (schema.oneOf ?? schema.anyOf);
189
+ if (Array.isArray(union) && union.length > 0) {
190
+ const { oneOf: _o, anyOf: _a, ...base } = schema;
191
+ const isNull = (branch) => isObject(branch) && branch.type === "null";
192
+ if (ctx.variant === "null-heavy" && union.some(isNull))
193
+ return null;
194
+ const failures = [];
195
+ for (const branch of union.filter((candidate) => !isNull(candidate))) {
196
+ const candidate = isObject(branch) ? merge(base, branch) : branch;
197
+ try {
198
+ return instance(candidate, ctx, pointer, distinct);
199
+ }
200
+ catch (error) {
201
+ if (!(error instanceof Unconstructible))
202
+ throw error;
203
+ failures.push(error.reason);
204
+ }
205
+ }
206
+ if (union.some(isNull))
207
+ return null;
208
+ throw new Unconstructible(`no branch of the union can be generated: ${failures.join("; ")}`, pointer);
209
+ }
210
+ if (Array.isArray(schema.enum)) {
211
+ const options = schema.enum.filter((item) => item !== null);
212
+ if (ctx.variant === "null-heavy" && schema.enum.includes(null))
213
+ return null;
214
+ if (options.length === 0) {
215
+ if (schema.enum.includes(null))
216
+ return null;
217
+ throw new Unconstructible("the enum is empty", pointer);
218
+ }
219
+ return options[ctx.index % options.length];
220
+ }
221
+ const types = typesOf(schema);
222
+ if (ctx.variant === "null-heavy" && types.includes("null"))
223
+ return null;
224
+ const type = types.find((item) => item !== "null");
225
+ if (type === undefined)
226
+ return null;
227
+ const next = { ...ctx, depth: ctx.depth + 1 };
228
+ switch (type) {
229
+ case "string":
230
+ return stringFor(schema, ctx, pointer, distinct);
231
+ case "integer":
232
+ case "number":
233
+ return numberFor(schema, ctx, pointer, type === "integer", distinct);
234
+ case "boolean":
235
+ return ctx.index % 2 === 0;
236
+ case "array":
237
+ return arrayFor(schema, next, pointer);
238
+ case "object":
239
+ return objectFor(schema, next, pointer);
240
+ default:
241
+ throw new Unconstructible(`unknown type "${type}"`, pointer);
242
+ }
243
+ }
244
+ function objectFor(schema, ctx, pointer) {
245
+ const properties = isObject(schema.properties) ? schema.properties : {};
246
+ const required = new Set(Array.isArray(schema.required) ? schema.required : []);
247
+ const out = {};
248
+ for (const [name, sub] of Object.entries(properties)) {
249
+ if (!required.has(name) && ctx.optional === "none")
250
+ continue;
251
+ const child = `${pointer}/${name.replace(/~/g, "~0").replace(/\//g, "~1")}`;
252
+ const distinct = pointer === "" && ctx.distinct.has(name);
253
+ try {
254
+ out[name] = instance(sub, ctx, child, distinct);
255
+ }
256
+ catch (error) {
257
+ /* An optional property that cannot be made is left out; a required one cannot be. */
258
+ if (!(error instanceof Unconstructible) || required.has(name))
259
+ throw error;
260
+ }
261
+ }
262
+ for (const name of required) {
263
+ if (Object.hasOwn(out, name))
264
+ continue;
265
+ /* Required but undescribed: any value satisfies the schema. */
266
+ out[name] = text(ctx, false);
267
+ }
268
+ return out;
269
+ }
270
+ function arrayFor(schema, ctx, pointer) {
271
+ const min = typeof schema.minItems === "number" ? schema.minItems : 0;
272
+ const max = typeof schema.maxItems === "number" ? schema.maxItems : Number.POSITIVE_INFINITY;
273
+ if (min > max)
274
+ throw new Unconstructible(`minItems ${min} exceeds maxItems ${max}`, pointer);
275
+ const prefix = Array.isArray(schema.prefixItems) ? schema.prefixItems : [];
276
+ const out = prefix.map((item, position) => instance(item, { ...ctx, index: ctx.index + position }, `${pointer}/${position}`, false));
277
+ /* One element where the schema allows it: an empty array fails `minItems` on exactly the
278
+ * collections most worth testing, and one element is what a cohort needs to filter on. */
279
+ const want = Math.min(Math.max(min, prefix.length, 1), max);
280
+ const items = schema.items;
281
+ if (items === false || out.length >= want)
282
+ return out;
283
+ const seen = new Set(out.map((value) => JSON.stringify(value)));
284
+ for (let attempt = 0; out.length < want && attempt < want + 8; attempt++) {
285
+ let value;
286
+ try {
287
+ value = instance(items ?? true, { ...ctx, index: ctx.index + attempt }, `${pointer}/${out.length}`, false);
288
+ }
289
+ catch (error) {
290
+ if (error instanceof Unconstructible && out.length >= min)
291
+ break;
292
+ throw error;
293
+ }
294
+ const key = JSON.stringify(value);
295
+ if (schema.uniqueItems === true && seen.has(key))
296
+ continue;
297
+ seen.add(key);
298
+ out.push(value);
299
+ }
300
+ if (out.length < min)
301
+ throw new Unconstructible(`cannot make ${min} distinct items`, pointer);
302
+ return out;
303
+ }
304
+ /* ------------------------------------------------------------------ scalars */
305
+ function slug(variant) {
306
+ return variant.replace(/[^a-z0-9]+/gi, "-").toLowerCase();
307
+ }
308
+ function text(ctx, distinct) {
309
+ const base = `${STRINGS[ctx.variant]} ${ctx.index}`;
310
+ return distinct && ctx.nonce !== undefined ? `${base} ${ctx.nonce}` : base;
311
+ }
312
+ /** A deterministic v4-shaped UUID from a salt. */
313
+ export function uuidFrom(salt) {
314
+ let h = 0x811c9dc5;
315
+ const hex = [];
316
+ for (let round = 0; hex.join("").length < 32; round++) {
317
+ for (const ch of `${salt}:${round}`)
318
+ h = Math.imul(h ^ ch.charCodeAt(0), 0x01000193) >>> 0;
319
+ hex.push(h.toString(16).padStart(8, "0"));
320
+ }
321
+ const s = hex.join("").slice(0, 32);
322
+ const variant = "89ab"[Number.parseInt(s[16] ?? "0", 16) % 4];
323
+ return `${s.slice(0, 8)}-${s.slice(8, 12)}-4${s.slice(13, 16)}-${variant}${s.slice(17, 20)}-${s.slice(20, 32)}`;
324
+ }
325
+ function pad(n, width = 2) {
326
+ return String(n).padStart(width, "0");
327
+ }
328
+ /** A value for each `format` oat knows; `undefined` for one it does not. */
329
+ function formatted(format, ctx, distinct) {
330
+ const i = ctx.index;
331
+ const salt = `${ctx.variant}:${i}${distinct ? `:${ctx.nonce ?? ""}` : ""}`;
332
+ const first = ctx.variant === "lexical-first" || ctx.variant === "boundary";
333
+ const last = ctx.variant === "lexical-last";
334
+ const year = first ? 2000 : last ? 2099 : 2024;
335
+ const month = first ? 1 : last ? 12 : (i % 12) + 1;
336
+ const day = first ? 1 : last ? 28 : (i % 28) + 1;
337
+ const minute = (i * 7) % 60;
338
+ switch (format) {
339
+ case "date":
340
+ return `${year}-${pad(month)}-${pad(day)}`;
341
+ case "date-time":
342
+ return `${year}-${pad(month)}-${pad(day)}T${pad(10 + (i % 12))}:${pad(minute)}:00Z`;
343
+ case "time":
344
+ return `${pad(10 + (i % 12))}:${pad(minute)}:00Z`;
345
+ case "uuid":
346
+ return uuidFrom(salt);
347
+ case "email":
348
+ case "idn-email":
349
+ return `oat-${slug(ctx.variant)}-${i}${distinct && ctx.nonce !== undefined ? `-${ctx.nonce}` : ""}@example.test`;
350
+ case "uri":
351
+ case "url":
352
+ case "iri":
353
+ case "uri-reference":
354
+ case "iri-reference":
355
+ return `https://example.test/${slug(ctx.variant)}-${i}${distinct && ctx.nonce !== undefined ? `-${ctx.nonce}` : ""}`;
356
+ case "hostname":
357
+ case "idn-hostname":
358
+ return `host-${i}${distinct && ctx.nonce !== undefined ? `-${ctx.nonce}` : ""}.example.test`;
359
+ case "ipv4":
360
+ return `192.0.2.${(i % 250) + 1}`;
361
+ case "ipv6":
362
+ return `2001:db8::${(i % 250) + 1}`;
363
+ case "byte":
364
+ return Buffer.from(`oat ${i}`).toString("base64");
365
+ default:
366
+ return undefined;
367
+ }
368
+ }
369
+ function stringFor(schema, ctx, pointer, distinct) {
370
+ const min = typeof schema.minLength === "number" ? schema.minLength : 0;
371
+ const max = typeof schema.maxLength === "number" ? schema.maxLength : Number.POSITIVE_INFINITY;
372
+ if (min > max)
373
+ throw new Unconstructible(`minLength ${min} exceeds maxLength ${max}`, pointer);
374
+ const format = typeof schema.format === "string" ? schema.format.toLowerCase() : undefined;
375
+ const pattern = typeof schema.pattern === "string" ? schema.pattern : undefined;
376
+ const unpadded = format === undefined ? undefined : formatted(format, ctx, distinct);
377
+ /* A short address or URL can be lengthened without leaving its format; a date cannot. */
378
+ const known = unpadded === undefined ? undefined : padFormatted(format, unpadded, min);
379
+ if (known !== undefined) {
380
+ if (codePointCount(known) > max || codePointCount(known) < min) {
381
+ throw new Unconstructible(`a ${format} cannot fit length ${min}–${max}`, pointer);
382
+ }
383
+ if (pattern !== undefined && !matches(pattern, known)) {
384
+ throw new Unconstructible(`a ${format} cannot match pattern ${pattern}`, pointer);
385
+ }
386
+ return known;
387
+ }
388
+ if (pattern !== undefined) {
389
+ const candidate = matching(pattern, min, max, ctx, distinct);
390
+ if (candidate === undefined)
391
+ throw new Unconstructible(`no string found for pattern ${pattern}`, pointer);
392
+ return candidate;
393
+ }
394
+ let value = ctx.variant === "boundary" ? "B".repeat(Math.max(1, Math.min(max, 512))) : text(ctx, distinct);
395
+ if (codePointCount(value) > max) {
396
+ /* Keep the distinguishing tail when shortening: the index and nonce are what make it unique. */
397
+ const tail = distinct && ctx.nonce !== undefined ? ` ${ctx.nonce}` : ` ${ctx.index}`;
398
+ value =
399
+ codePointCount(tail) < max
400
+ ? sliceCodePoints(value, max - codePointCount(tail)) + tail
401
+ : sliceCodePoints(value, max);
402
+ }
403
+ if (codePointCount(value) < min)
404
+ value = value + "x".repeat(min - codePointCount(value));
405
+ return value;
406
+ }
407
+ function padFormatted(format, value, min) {
408
+ const short = min - codePointCount(value);
409
+ if (short <= 0)
410
+ return value;
411
+ if (format === "email" || format === "idn-email")
412
+ return value.replace("@", `${"x".repeat(short)}@`);
413
+ if (format.startsWith("uri") || format.startsWith("iri") || format === "url")
414
+ return `${value}${"x".repeat(short)}`;
415
+ if (format === "hostname" || format === "idn-hostname")
416
+ return `${"x".repeat(Math.min(short, 60))}${value}`;
417
+ return value;
418
+ }
419
+ function matches(pattern, value) {
420
+ try {
421
+ return new RegExp(pattern, "u").test(value);
422
+ }
423
+ catch {
424
+ return false;
425
+ }
426
+ }
427
+ /** A bounded search for a string the documented pattern accepts. */
428
+ function matching(pattern, min, max, ctx, distinct) {
429
+ const suffix = distinct && ctx.nonce !== undefined ? ctx.nonce : String(ctx.index);
430
+ const fromPattern = expand(pattern, ctx.index);
431
+ const candidates = [
432
+ fromPattern === undefined ? undefined : `${fromPattern}${suffix}`,
433
+ fromPattern,
434
+ `oat${suffix}`,
435
+ `a${suffix}`,
436
+ suffix,
437
+ `oat-${suffix}`,
438
+ `oat_${suffix}`,
439
+ `OAT${suffix}`,
440
+ "a",
441
+ "abc",
442
+ `oat-${slug(ctx.variant)}-${suffix}@example.test`,
443
+ `https://example.test/${suffix}`,
444
+ uuidFrom(`${ctx.variant}:${suffix}`),
445
+ ];
446
+ for (const candidate of candidates) {
447
+ if (candidate === undefined)
448
+ continue;
449
+ for (const sized of [candidate, candidate.slice(0, max), candidate.padEnd(min, candidate.at(-1) ?? "a")]) {
450
+ const n = codePointCount(sized);
451
+ if (n >= min && n <= max && matches(pattern, sized))
452
+ return sized;
453
+ }
454
+ }
455
+ return undefined;
456
+ }
457
+ /** Expands the common regex subset — classes, escapes, quantifiers, groups — to one example. */
458
+ function expand(pattern, index) {
459
+ let source = pattern.replace(/^\^/, "").replace(/\$$/, "");
460
+ let out = "";
461
+ const quantity = () => {
462
+ const q = source[0];
463
+ if (q === "+" || q === "*" || q === "?") {
464
+ source = source.slice(1);
465
+ return q === "+" ? 2 : q === "*" ? 1 : 1;
466
+ }
467
+ const braces = /^\{(\d+)(,(\d*))?\}/.exec(source);
468
+ if (braces !== null) {
469
+ source = source.slice(braces[0].length);
470
+ return Number(braces[1]);
471
+ }
472
+ return 1;
473
+ };
474
+ let guard = 0;
475
+ while (source.length > 0 && guard++ < 512) {
476
+ const ch = source[0];
477
+ let atom;
478
+ if (ch === "[") {
479
+ const close = source.indexOf("]", 1);
480
+ if (close < 0)
481
+ return undefined;
482
+ const body = source.slice(1, close).replace(/^\^/, "");
483
+ atom = body.startsWith("\\d") ? String(index % 10) : body.startsWith("\\w") ? "a" : (body[0] ?? "a");
484
+ if (body[1] === "-" && body[2] !== undefined)
485
+ atom = body[0] ?? "a";
486
+ source = source.slice(close + 1);
487
+ }
488
+ else if (ch === "\\") {
489
+ const next = source[1] ?? "";
490
+ atom = next === "d" ? String(index % 10) : next === "w" ? "a" : next === "s" ? " " : next;
491
+ source = source.slice(2);
492
+ }
493
+ else if (ch === "(") {
494
+ const close = source.indexOf(")");
495
+ if (close < 0)
496
+ return undefined;
497
+ const inner = source.slice(1, close).replace(/^\?:/, "").split("|")[0] ?? "";
498
+ atom = expand(inner, index) ?? "";
499
+ source = source.slice(close + 1);
500
+ }
501
+ else if (ch === ".") {
502
+ atom = "a";
503
+ source = source.slice(1);
504
+ }
505
+ else if (ch === "|" || ch === ")") {
506
+ break;
507
+ }
508
+ else {
509
+ atom = ch;
510
+ source = source.slice(1);
511
+ }
512
+ out += atom.repeat(quantity());
513
+ }
514
+ return out;
515
+ }
516
+ function numberFor(schema, ctx, pointer, integer, distinct) {
517
+ const step = typeof schema.multipleOf === "number" && schema.multipleOf > 0 ? schema.multipleOf : integer ? 1 : 0;
518
+ const exclusiveLo = typeof schema.exclusiveMinimum === "number" ? schema.exclusiveMinimum : undefined;
519
+ const exclusiveHi = typeof schema.exclusiveMaximum === "number" ? schema.exclusiveMaximum : undefined;
520
+ const lo = Math.max(typeof schema.minimum === "number" ? schema.minimum : Number.NEGATIVE_INFINITY, exclusiveLo ?? Number.NEGATIVE_INFINITY);
521
+ const hi = Math.min(typeof schema.maximum === "number" ? schema.maximum : Number.POSITIVE_INFINITY, exclusiveHi ?? Number.POSITIVE_INFINITY);
522
+ const admits = (value) => value >= lo &&
523
+ value <= hi &&
524
+ (exclusiveLo === undefined || value > exclusiveLo) &&
525
+ (exclusiveHi === undefined || value < exclusiveHi) &&
526
+ (!integer || Number.isInteger(value)) &&
527
+ (step === 0 || Math.abs(value / step - Math.round(value / step)) < 1e-9);
528
+ const snap = (value, direction) => {
529
+ if (step === 0)
530
+ return value;
531
+ const units = direction === "up" ? Math.ceil(value / step - 1e-9) : Math.floor(value / step + 1e-9);
532
+ return Number((units * step).toFixed(10));
533
+ };
534
+ /* A ladder whose lexical order differs from its numeric order (1, 10, 100, 2, 20, 5, 50 as
535
+ * text), so a backend comparing numbers as strings is visibly wrong. */
536
+ const ladder = [1, 2, 5, 10, 20, 50, 100];
537
+ const rung = ladder[ctx.index % ladder.length] ?? 1;
538
+ const origin = Number.isFinite(lo) ? lo : 0;
539
+ const fraction = integer ? 0 : Math.round(ctx.rand() * 9) / 10;
540
+ const offset = distinct && ctx.nonce !== undefined ? nonceNumber(ctx.nonce) % 997 : 0;
541
+ const preferred = ctx.variant === "boundary" && Number.isFinite(hi)
542
+ ? hi
543
+ : ctx.variant === "boundary" && Number.isFinite(lo)
544
+ ? lo
545
+ : origin + rung + fraction + offset * Math.max(step, 1);
546
+ const candidates = [
547
+ snap(Math.min(Math.max(preferred, lo), hi), "up"),
548
+ snap(Math.min(Math.max(preferred, lo), hi), "down"),
549
+ snap(lo, "up"),
550
+ snap(hi, "down"),
551
+ snap((Number.isFinite(lo) ? lo : 0) + (Number.isFinite(hi) ? (hi - (Number.isFinite(lo) ? lo : 0)) / 2 : 1), "up"),
552
+ ];
553
+ for (const candidate of candidates) {
554
+ if (Number.isFinite(candidate) && admits(candidate))
555
+ return candidate;
556
+ if (Number.isFinite(candidate) && admits(candidate + (step || (integer ? 1 : 0.5)))) {
557
+ return candidate + (step || (integer ? 1 : 0.5));
558
+ }
559
+ }
560
+ throw new Unconstructible(`no ${integer ? "integer" : "number"} satisfies the bounds`, pointer);
561
+ }
562
+ function nonceNumber(nonce) {
563
+ let h = 0;
564
+ for (const ch of nonce)
565
+ h = (Math.imul(h, 31) + ch.charCodeAt(0)) >>> 0;
566
+ return h;
567
+ }
568
+ /* ------------------------------------------------------------------ intents */
569
+ /** A valid value that differs from `other` — the value a write probe changes a field to. */
570
+ export function distinctValue(schema, other, options = {}) {
571
+ const base = options.index ?? 0;
572
+ for (let index = base + 1; index < base + 12; index++) {
573
+ const result = generate(schema, { ...options, index });
574
+ if (!result.ok)
575
+ return result;
576
+ if (JSON.stringify(result.value) !== JSON.stringify(other))
577
+ return result;
578
+ }
579
+ return { ok: false, pointer: "/", reason: "the schema admits no value different from the current one" };
580
+ }
581
+ /**
582
+ * An identifier that is well-formed for `schema` and names nothing: a UUID where the API takes
583
+ * UUIDs, an integer where it takes integers. Comparing a malformed probe against a real one
584
+ * compares a 400 with a 404 and reads the difference as a leak.
585
+ */
586
+ export function absentIdentifier(schema, nonce) {
587
+ const s = isObject(schema) ? schema : {};
588
+ const types = typesOf(s);
589
+ if (types.includes("integer") || types.includes("number")) {
590
+ const hi = typeof s.maximum === "number" ? s.maximum : 2_147_483_647;
591
+ const value = Math.max(typeof s.minimum === "number" ? s.minimum : 1, hi - (nonceNumber(nonce) % 1000));
592
+ return { ok: true, value: Number.isInteger(value) ? value : Math.floor(value) };
593
+ }
594
+ const format = typeof s.format === "string" ? s.format : undefined;
595
+ if (format === "uuid")
596
+ return { ok: true, value: uuidFrom(`absent:${nonce}`) };
597
+ return generate({ ...s, format }, { distinct: new Set(), index: 9001, nonce });
598
+ }
599
+ /** A value of the right type outside the declared enum, or why there is none. */
600
+ export function outsideEnum(schema) {
601
+ if (!isObject(schema) || !Array.isArray(schema.enum))
602
+ return { ok: false, pointer: "/", reason: "no enum declared" };
603
+ const values = schema.enum;
604
+ if (values.every((value) => typeof value === "number")) {
605
+ return { ok: true, value: Math.max(...values) + 1 };
606
+ }
607
+ let candidate = "oat-not-in-enum";
608
+ while (values.includes(candidate))
609
+ candidate = `${candidate}-x`;
610
+ return { ok: true, value: candidate };
611
+ }
612
+ /** A string one character longer than `maxLength` allows, or why there is none. */
613
+ export function overMaxLength(schema) {
614
+ if (!isObject(schema) || typeof schema.maxLength !== "number") {
615
+ return { ok: false, pointer: "/", reason: "no maxLength declared" };
616
+ }
617
+ return { ok: true, value: "x".repeat(schema.maxLength + 1) };
618
+ }
619
+ /**
620
+ * A value of the field's own type that no record is likely to hold — the probe for "is this
621
+ * declared filter accepted at all". A string where the field is a string, a number where it is one.
622
+ */
623
+ export function filterSentinel(schema, nonce) {
624
+ const s = isObject(schema) ? schema : {};
625
+ if (Array.isArray(s.enum) && s.enum.length > 0)
626
+ return { ok: true, value: s.enum.at(-1) };
627
+ const types = typesOf(s);
628
+ if (types.includes("boolean"))
629
+ return { ok: true, value: true };
630
+ if (types.includes("integer") || types.includes("number"))
631
+ return absentIdentifier(s, nonce);
632
+ const wrapped = generate({ properties: { value: s }, required: ["value"], type: "object" }, { distinct: new Set(["value"]), index: 4242, nonce, variant: "lexical-last" });
633
+ return wrapped.ok ? { ok: true, value: wrapped.value.value } : wrapped;
634
+ }
635
+ //# sourceMappingURL=generate.js.map