asphodelos 0.0.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.
@@ -0,0 +1,4018 @@
1
+ import { t as __exportAll } from "./rolldown-runtime-wcPFST8Q.mjs";
2
+ import { n as fmt } from "./format-B0W7HIAg.mjs";
3
+ import { r as pathEntries } from "./openapi-B4aAnx4P.mjs";
4
+ import { Data, Effect, FileSystem } from "effect";
5
+ import path from "node:path";
6
+ import * as NodeFileSystem from "@effect/platform-node/NodeFileSystem";
7
+ import { Node, Project } from "ts-morph";
8
+ //#region src/fsp/index.ts
9
+ /**
10
+ * Node's `FileSystem` implementation.
11
+ *
12
+ * Every function below reads the service out of the environment, so a program that uses them
13
+ * provides this once at its boundary — the CLI folds it in through `NodeServices.layer`, the Vite
14
+ * plugin provides it directly.
15
+ */
16
+ const fileSystemLayer = NodeFileSystem.layer;
17
+ /** Whether a platform failure is "the path is not there", the one case worth absorbing. */
18
+ function isNotFound(e) {
19
+ return e.reason._tag === "NotFound";
20
+ }
21
+ /** Removes a file. A path that is already gone is not an error. */
22
+ function unlink(path) {
23
+ return Effect.gen(function* () {
24
+ yield* (yield* FileSystem.FileSystem).remove(path, { force: true });
25
+ });
26
+ }
27
+ /** Creates `dir` and every missing parent. */
28
+ function mkdir(dir) {
29
+ return Effect.gen(function* () {
30
+ yield* (yield* FileSystem.FileSystem).makeDirectory(dir, { recursive: true });
31
+ });
32
+ }
33
+ /** Lists `dir`. A directory that does not exist reads as empty. */
34
+ function readdir(dir) {
35
+ return Effect.gen(function* () {
36
+ return yield* (yield* FileSystem.FileSystem).readDirectory(dir);
37
+ }).pipe(Effect.catchIf(isNotFound, () => Effect.succeed([])));
38
+ }
39
+ /** Reads `path` as UTF-8, answering `null` when there is nothing there yet. */
40
+ function readFile(path) {
41
+ return Effect.gen(function* () {
42
+ return yield* (yield* FileSystem.FileSystem).readFileString(path);
43
+ }).pipe(Effect.catchIf(isNotFound, () => Effect.succeed(null)));
44
+ }
45
+ /**
46
+ * Writes `data` to `path`, skipping the write when the bytes already match.
47
+ *
48
+ * That skip is contract, not optimization: the Vite plugin watches its own output, so rewriting
49
+ * an unchanged file would feed a change event straight back into the generator.
50
+ */
51
+ function writeFile(path, data) {
52
+ return Effect.gen(function* () {
53
+ const fs = yield* FileSystem.FileSystem;
54
+ if ((yield* fs.readFileString(path).pipe(Effect.orElseSucceed(() => null))) === data) return;
55
+ yield* fs.writeFileString(path, data);
56
+ });
57
+ }
58
+ //#endregion
59
+ //#region src/emit/index.ts
60
+ /** Formats generated source and writes it to `output`, creating `dir` on the way. */
61
+ function emit(code, dir, output) {
62
+ return Effect.gen(function* () {
63
+ const [formatted] = yield* Effect.all([fmt(code), mkdir(dir)], { concurrency: "unbounded" });
64
+ yield* writeFile(output, formatted);
65
+ });
66
+ }
67
+ //#endregion
68
+ //#region src/guard/index.ts
69
+ function isReference(v) {
70
+ return typeof v === "object" && v !== null && "$ref" in v && typeof v.$ref === "string" && !!v.$ref;
71
+ }
72
+ function isStringRef(v) {
73
+ return "$ref" in v && typeof v.$ref === "string";
74
+ }
75
+ function isRecord(v) {
76
+ return typeof v === "object" && v !== null && !Array.isArray(v);
77
+ }
78
+ function isMediaWithSchema(v) {
79
+ return typeof v === "object" && v !== null && !Array.isArray(v) && "schema" in v;
80
+ }
81
+ function isSecurityArray(v) {
82
+ return Array.isArray(v) && v.every((x) => typeof x === "object" && x !== null);
83
+ }
84
+ function isSchemaArray(v) {
85
+ return Array.isArray(v);
86
+ }
87
+ function isTypeArray(v) {
88
+ return Array.isArray(v);
89
+ }
90
+ //#endregion
91
+ //#region src/openapi/vendor-ext.ts
92
+ const VENDOR_EXT_KEYS = [
93
+ "x-error-message",
94
+ "x-required-message",
95
+ "x-const-message",
96
+ "x-enum-message",
97
+ "x-minimum-message",
98
+ "x-maximum-message",
99
+ "x-exclusiveMinimum-message",
100
+ "x-exclusiveMaximum-message",
101
+ "x-multipleOf-message",
102
+ "x-minLength-message",
103
+ "x-maxLength-message",
104
+ "x-pattern-message",
105
+ "x-length-message",
106
+ "x-minItems-message",
107
+ "x-maxItems-message",
108
+ "x-uniqueItems-message",
109
+ "x-contains-message",
110
+ "x-minContains-message",
111
+ "x-maxContains-message",
112
+ "x-prefixItems-message",
113
+ "x-items-message",
114
+ "x-minProperties-message",
115
+ "x-maxProperties-message",
116
+ "x-additionalProperties-message",
117
+ "x-propertyNames-message",
118
+ "x-patternProperties-message",
119
+ "x-dependentRequired-message",
120
+ "x-dependentSchemas-message",
121
+ "x-properties-message",
122
+ "x-unevaluatedProperties-message",
123
+ "x-unevaluatedItems-message",
124
+ "x-if-message",
125
+ "x-then-message",
126
+ "x-else-message",
127
+ "x-allOf-message",
128
+ "x-anyOf-message",
129
+ "x-oneOf-message",
130
+ "x-not-message",
131
+ "x-implication-message",
132
+ "x-brand"
133
+ ];
134
+ //#endregion
135
+ //#region src/utils/index.ts
136
+ function capitalize(s) {
137
+ return s.charAt(0).toUpperCase() + s.slice(1);
138
+ }
139
+ function uncapitalize(s) {
140
+ return s.charAt(0).toLowerCase() + s.slice(1);
141
+ }
142
+ function encodeNonAscii(name) {
143
+ return [...name].map((ch) => {
144
+ const cp = ch.codePointAt(0) ?? 0;
145
+ return cp > 127 ? `u${cp.toString(16)}` : ch;
146
+ }).join("");
147
+ }
148
+ function pascalCase(s) {
149
+ const parts = encodeNonAscii(s).split(/[^A-Za-z0-9]+/u).filter(Boolean);
150
+ if (parts.length === 0) return "Schema";
151
+ const result = parts.map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join("");
152
+ return /^[0-9]/u.test(result) ? `_${result}` : result;
153
+ }
154
+ function filterDefined(arr) {
155
+ return arr.filter((e) => e !== null && e !== void 0);
156
+ }
157
+ const JS_RESERVED = new Set([
158
+ "break",
159
+ "case",
160
+ "catch",
161
+ "class",
162
+ "const",
163
+ "continue",
164
+ "debugger",
165
+ "default",
166
+ "delete",
167
+ "do",
168
+ "else",
169
+ "enum",
170
+ "export",
171
+ "extends",
172
+ "false",
173
+ "finally",
174
+ "for",
175
+ "function",
176
+ "if",
177
+ "import",
178
+ "in",
179
+ "instanceof",
180
+ "new",
181
+ "null",
182
+ "return",
183
+ "super",
184
+ "switch",
185
+ "this",
186
+ "throw",
187
+ "true",
188
+ "try",
189
+ "typeof",
190
+ "var",
191
+ "void",
192
+ "while",
193
+ "with",
194
+ "let",
195
+ "static",
196
+ "yield",
197
+ "await",
198
+ "implements",
199
+ "interface",
200
+ "package",
201
+ "private",
202
+ "protected",
203
+ "public"
204
+ ]);
205
+ /**
206
+ * Produce a valid lower-camel JavaScript identifier for a module variable.
207
+ * Already-valid, non-reserved names pass through unchanged; reserved words get
208
+ * a `Module` suffix (`class` → `classModule`) and non-identifier characters /
209
+ * leading digits are normalized so `export const <name>` always parses.
210
+ */
211
+ function toSafeIdentifier(name) {
212
+ const cleaned = /^[A-Za-z_$][A-Za-z0-9_$]*$/u.test(name) ? name : (() => {
213
+ const joined = name.split(/[^A-Za-z0-9]+/u).filter(Boolean).map((p, i) => i === 0 ? p : p.charAt(0).toUpperCase() + p.slice(1)).join("");
214
+ if (joined.length === 0) return "_";
215
+ return /^[0-9]/u.test(joined) ? `_${joined}` : joined;
216
+ })();
217
+ return JS_RESERVED.has(cleaned) ? `${cleaned}Module` : cleaned;
218
+ }
219
+ /**
220
+ * Encode an HTTP response status as an object key: pure-integer codes stay
221
+ * unquoted (`200`), ranges and `default` (`2XX`, `default`) are quoted so they
222
+ * are not parsed as malformed numeric literals.
223
+ */
224
+ function safeStatusKey(status) {
225
+ return /^[0-9]+$/u.test(status) ? status : JSON.stringify(status);
226
+ }
227
+ /**
228
+ * The top-level resource segment of a path, used as the first element of every
229
+ * cache-key tuple (`/posts/{id}` → `posts`). An empty path falls back to `''`.
230
+ */
231
+ function resourcePrefix(pathStr) {
232
+ return pathStr.replace(/^\//u, "").split("/")[0] ?? "";
233
+ }
234
+ //#endregion
235
+ //#region src/helper/schema.ts
236
+ function pickVendorExtensions(source) {
237
+ if (!source) return {};
238
+ const picked = {};
239
+ for (const key of VENDOR_EXT_KEYS) {
240
+ const value = source[key];
241
+ if (typeof value === "string") picked[key] = value;
242
+ }
243
+ return picked;
244
+ }
245
+ function jsonSchema(content) {
246
+ return content?.["application/json"]?.schema;
247
+ }
248
+ const BODY_MEDIA_PRIORITY = [
249
+ "application/json",
250
+ "multipart/form-data",
251
+ "application/octet-stream"
252
+ ];
253
+ function pickBodyMedia(content) {
254
+ if (!content) return void 0;
255
+ for (const m of BODY_MEDIA_PRIORITY) {
256
+ const entry = content[m];
257
+ if (entry?.schema) return {
258
+ mediaType: m,
259
+ schema: entry.schema
260
+ };
261
+ }
262
+ }
263
+ function refSchemaName(s) {
264
+ if (!s?.$ref) return null;
265
+ const m = /^#\/components\/schemas\/([^/]+)$/u.exec(s.$ref);
266
+ return m?.[1] ? decodeURIComponent(m[1]) : null;
267
+ }
268
+ function bodyInfo(operation) {
269
+ const body = operation.requestBody;
270
+ if (!body || isReference(body)) return {};
271
+ const picked = pickBodyMedia(body.content);
272
+ if (!picked) return {};
273
+ const { mediaType, schema } = picked;
274
+ const ref = refSchemaName(schema);
275
+ if (ref) return { ref };
276
+ const media = body.content?.[mediaType];
277
+ const mediaExt = media && !isReference(media) ? pickVendorExtensions(media) : {};
278
+ const bodyExt = pickVendorExtensions(body);
279
+ const schemaExt = pickVendorExtensions(schema);
280
+ const inline = {
281
+ ...mediaExt,
282
+ ...bodyExt,
283
+ ...schema,
284
+ ...schemaExt
285
+ };
286
+ if (mediaType === "application/json") return { inline };
287
+ return {
288
+ inline,
289
+ ctx: { mediaType }
290
+ };
291
+ }
292
+ function responseInfo(res) {
293
+ const schema = jsonSchema(res.content);
294
+ if (!schema) return { void: true };
295
+ const ref = refSchemaName(schema);
296
+ return ref ? { ref } : { inline: schema };
297
+ }
298
+ function makeParamsSchema(params, where) {
299
+ const filtered = params.filter((p) => !isReference(p) && p.in === where);
300
+ if (filtered.length === 0) return void 0;
301
+ const withSchema = filtered.filter((p) => {
302
+ if (!p.schema) {
303
+ if (p.content) console.warn(`asphodelos: Parameter '${p.name}' uses 'content' field (OpenAPI 3.1 alternative to 'schema'). This is not yet supported; the parameter will be omitted from the generated route.`);
304
+ return false;
305
+ }
306
+ return true;
307
+ });
308
+ if (withSchema.length === 0) return void 0;
309
+ const properties = Object.fromEntries(withSchema.map((p) => [p.name, p.schema]));
310
+ const required = withSchema.filter((p) => p.required).map((p) => p.name);
311
+ const hoisted = {};
312
+ for (const p of withSchema) {
313
+ const fromParam = pickVendorExtensions(p);
314
+ const fromSchema = pickVendorExtensions(p.schema);
315
+ for (const key of VENDOR_EXT_KEYS) {
316
+ if (hoisted[key] !== void 0) continue;
317
+ const value = fromParam[key] ?? fromSchema[key];
318
+ if (value !== void 0) hoisted[key] = value;
319
+ }
320
+ }
321
+ const schema = {
322
+ type: "object",
323
+ properties,
324
+ ...required.length > 0 ? { required } : {},
325
+ ...hoisted
326
+ };
327
+ if (where === "cookie") return {
328
+ schema,
329
+ ctx: { elysiaKind: "cookie" }
330
+ };
331
+ if (where === "query") return {
332
+ schema,
333
+ ctx: { parameterLocation: "query" }
334
+ };
335
+ return { schema };
336
+ }
337
+ function collectSchemaRefs$1(schema, acc = /* @__PURE__ */ new Set()) {
338
+ if (schema.properties) for (const v of Object.values(schema.properties)) collectSchemaRefs$1(v, acc);
339
+ if (schema.items) {
340
+ const items = schema.items;
341
+ if (isSchemaArray(items)) for (const i of items) collectSchemaRefs$1(i, acc);
342
+ else collectSchemaRefs$1(items, acc);
343
+ }
344
+ if (schema.prefixItems) for (const i of schema.prefixItems) collectSchemaRefs$1(i, acc);
345
+ if (schema.additionalProperties && typeof schema.additionalProperties === "object") collectSchemaRefs$1(schema.additionalProperties, acc);
346
+ if (schema.$ref) {
347
+ const m = /^#\/components\/schemas\/([^/]+)$/u.exec(schema.$ref);
348
+ if (m?.[1]) acc.add(decodeURIComponent(m[1]));
349
+ }
350
+ if (schema.oneOf) for (const s of schema.oneOf) collectSchemaRefs$1(s, acc);
351
+ if (schema.allOf) for (const s of schema.allOf) collectSchemaRefs$1(s, acc);
352
+ if (schema.anyOf) for (const s of schema.anyOf) collectSchemaRefs$1(s, acc);
353
+ if (schema.not) collectSchemaRefs$1(schema.not, acc);
354
+ if (schema.patternProperties) for (const v of Object.values(schema.patternProperties)) collectSchemaRefs$1(v, acc);
355
+ if (schema.propertyNames) collectSchemaRefs$1(schema.propertyNames, acc);
356
+ if (schema.dependentSchemas) for (const v of Object.values(schema.dependentSchemas)) collectSchemaRefs$1(v, acc);
357
+ if (schema.contains) collectSchemaRefs$1(schema.contains, acc);
358
+ return acc;
359
+ }
360
+ function topoSortSchemas(schemas) {
361
+ const byName = new Map(schemas.map((s) => [s.name, s]));
362
+ const visited = /* @__PURE__ */ new Set();
363
+ const result = [];
364
+ const visit = (name, stack) => {
365
+ if (visited.has(name) || stack.has(name)) return;
366
+ const def = byName.get(name);
367
+ if (!def) return;
368
+ stack.add(name);
369
+ for (const dep of collectSchemaRefs$1(def.schema)) visit(dep, stack);
370
+ stack.delete(name);
371
+ visited.add(name);
372
+ result.push(def);
373
+ };
374
+ for (const { name } of schemas) visit(name, /* @__PURE__ */ new Set());
375
+ return result;
376
+ }
377
+ function sccSchemas(schemas) {
378
+ const byName = new Map(schemas.map((s) => [s.name, s]));
379
+ const refsOf = (name) => {
380
+ const def = byName.get(name);
381
+ if (!def) return [];
382
+ return [...collectSchemaRefs$1(def.schema)].filter((ref) => byName.has(ref));
383
+ };
384
+ const index = /* @__PURE__ */ new Map();
385
+ const lowlink = /* @__PURE__ */ new Map();
386
+ const onStack = /* @__PURE__ */ new Set();
387
+ const stack = [];
388
+ const sccs = [];
389
+ const counter = { value: 0 };
390
+ const strongconnect = (v) => {
391
+ index.set(v, counter.value);
392
+ lowlink.set(v, counter.value);
393
+ counter.value += 1;
394
+ stack.push(v);
395
+ onStack.add(v);
396
+ for (const w of refsOf(v)) if (!index.has(w)) {
397
+ strongconnect(w);
398
+ lowlink.set(v, Math.min(lowlink.get(v) ?? 0, lowlink.get(w) ?? 0));
399
+ } else if (onStack.has(w)) lowlink.set(v, Math.min(lowlink.get(v) ?? 0, index.get(w) ?? 0));
400
+ if ((lowlink.get(v) ?? 0) === (index.get(v) ?? 0)) {
401
+ const group = [];
402
+ while (true) {
403
+ const w = stack.pop();
404
+ if (w === void 0) break;
405
+ onStack.delete(w);
406
+ const def = byName.get(w);
407
+ if (def) group.push(def);
408
+ if (w === v) break;
409
+ }
410
+ sccs.push(group);
411
+ }
412
+ };
413
+ for (const { name } of schemas) if (!index.has(name)) strongconnect(name);
414
+ return sccs;
415
+ }
416
+ const REF_SUFFIX = {
417
+ schemas: "Schema",
418
+ parameters: "ParamsSchema",
419
+ headers: "HeaderSchema",
420
+ securitySchemes: "SecurityScheme",
421
+ requestBodies: "RequestBodySchema",
422
+ responses: "ResponseSchema",
423
+ examples: "Example",
424
+ links: "Link",
425
+ callbacks: "Callback",
426
+ pathItems: "PathItem",
427
+ mediaTypes: "MediaTypeSchema"
428
+ };
429
+ const REF_PATTERN = new RegExp(`^#/components/(${Object.keys(REF_SUFFIX).join("|")})/(.+)$`, "u");
430
+ function refIdent(ref) {
431
+ const m = REF_PATTERN.exec(ref);
432
+ if (!m?.[1] || !m[2]) return void 0;
433
+ const suffix = REF_SUFFIX[m[1]];
434
+ if (!suffix) return void 0;
435
+ return `${pascalCase(decodeURIComponent(m[2]))}${suffix}`;
436
+ }
437
+ function stringifyRefs(value) {
438
+ if (value === null) return "null";
439
+ if (value === void 0) return "undefined";
440
+ if (typeof value === "string") return JSON.stringify(value);
441
+ if (typeof value === "number" || typeof value === "boolean") return String(value);
442
+ if (Array.isArray(value)) return `[${value.map(stringifyRefs).join(",")}]`;
443
+ if (typeof value === "object") {
444
+ if (isReference(value) && value.$ref) {
445
+ const ident = refIdent(value.$ref);
446
+ if (ident) return ident;
447
+ }
448
+ return `{${Object.entries(value).map(([k, v]) => `${JSON.stringify(k)}:${stringifyRefs(v)}`).join(",")}}`;
449
+ }
450
+ return JSON.stringify(value);
451
+ }
452
+ function moduleInlineSchemas(inline) {
453
+ return topoSortSchemas(inline);
454
+ }
455
+ function moduleComponentRefs(routes, inline, componentNames, componentSchemas = {}) {
456
+ const direct = /* @__PURE__ */ new Set();
457
+ for (const r of routes) {
458
+ if (r.bodyRef && componentNames.has(r.bodyRef)) direct.add(r.bodyRef);
459
+ for (const res of r.responses) if (res.schema.kind === "ref" && componentNames.has(res.schema.name)) direct.add(res.schema.name);
460
+ }
461
+ for (const { schema } of inline) for (const n of collectSchemaRefs$1(schema)) if (componentNames.has(n)) direct.add(n);
462
+ const closure = new Set(direct);
463
+ const queue = [...direct];
464
+ while (queue.length > 0) {
465
+ const name = queue.shift();
466
+ if (name === void 0) break;
467
+ const def = componentSchemas[name];
468
+ if (!def) continue;
469
+ for (const dep of collectSchemaRefs$1(def)) if (componentNames.has(dep) && !closure.has(dep)) {
470
+ closure.add(dep);
471
+ queue.push(dep);
472
+ }
473
+ }
474
+ return closure;
475
+ }
476
+ //#endregion
477
+ //#region src/helper/typebox.ts
478
+ const RAW = Symbol.for("asphodelos.options.raw");
479
+ function raw(expr) {
480
+ return {
481
+ [RAW]: true,
482
+ expr
483
+ };
484
+ }
485
+ function isRaw(v) {
486
+ return typeof v === "object" && v !== null && RAW in v;
487
+ }
488
+ function formatKey(k) {
489
+ return /^[a-zA-Z_$][a-zA-Z_$0-9]*$/u.test(k) ? k : JSON.stringify(k);
490
+ }
491
+ function options(pairs) {
492
+ const filtered = pairs.filter(([, v]) => v !== void 0);
493
+ if (filtered.length === 0) return "";
494
+ return `{${filtered.map(([k, v]) => `${formatKey(k)}:${isRaw(v) ? v.expr : JSON.stringify(v)}`).join(",")}}`;
495
+ }
496
+ function exclusiveBound(flag, inclusive) {
497
+ if (flag === true) return inclusive;
498
+ if (typeof flag === "number") return flag;
499
+ }
500
+ /**
501
+ * Normalize OpenAPI 3.0's boolean `exclusiveMinimum`/`exclusiveMaximum` to the
502
+ * JSON Schema 2020-12 numeric form TypeBox expects. In 3.0 a boolean `true`
503
+ * makes the sibling `minimum`/`maximum` exclusive; 2020-12 (and TypeBox) carry
504
+ * the bound value on `exclusiveMinimum`/`exclusiveMaximum` itself. `false`
505
+ * collapses back to the inclusive bound.
506
+ * https://spec.openapis.org/oas/v3.0.3#schema-object
507
+ */
508
+ function normalizeBounds(schema) {
509
+ return {
510
+ minimum: schema.exclusiveMinimum === true ? void 0 : schema.minimum,
511
+ maximum: schema.exclusiveMaximum === true ? void 0 : schema.maximum,
512
+ exclusiveMinimum: exclusiveBound(schema.exclusiveMinimum, schema.minimum),
513
+ exclusiveMaximum: exclusiveBound(schema.exclusiveMaximum, schema.maximum)
514
+ };
515
+ }
516
+ function commonOpts(schema) {
517
+ return [
518
+ ["externalDocs", schema.externalDocs],
519
+ ["example", schema.example],
520
+ ["examples", schema.examples],
521
+ ["title", schema.title],
522
+ ["description", schema.description],
523
+ ["default", schema.default],
524
+ ["readOnly", schema.readOnly],
525
+ ["writeOnly", schema.writeOnly],
526
+ ["deprecated", schema.deprecated],
527
+ ["x-error-message", schema["x-error-message"]],
528
+ ["x-required-message", schema["x-required-message"]],
529
+ ["x-const-message", schema["x-const-message"]],
530
+ ["x-enum-message", schema["x-enum-message"]],
531
+ ["x-minimum-message", schema["x-minimum-message"]],
532
+ ["x-maximum-message", schema["x-maximum-message"]],
533
+ ["x-exclusiveMinimum-message", schema["x-exclusiveMinimum-message"]],
534
+ ["x-exclusiveMaximum-message", schema["x-exclusiveMaximum-message"]],
535
+ ["x-multipleOf-message", schema["x-multipleOf-message"]],
536
+ ["x-minLength-message", schema["x-minLength-message"]],
537
+ ["x-maxLength-message", schema["x-maxLength-message"]],
538
+ ["x-pattern-message", schema["x-pattern-message"]],
539
+ ["x-length-message", schema["x-length-message"]],
540
+ ["x-minItems-message", schema["x-minItems-message"]],
541
+ ["x-maxItems-message", schema["x-maxItems-message"]],
542
+ ["x-uniqueItems-message", schema["x-uniqueItems-message"]],
543
+ ["x-contains-message", schema["x-contains-message"]],
544
+ ["x-minContains-message", schema["x-minContains-message"]],
545
+ ["x-maxContains-message", schema["x-maxContains-message"]],
546
+ ["x-prefixItems-message", schema["x-prefixItems-message"]],
547
+ ["x-items-message", schema["x-items-message"]],
548
+ ["x-minProperties-message", schema["x-minProperties-message"]],
549
+ ["x-maxProperties-message", schema["x-maxProperties-message"]],
550
+ ["x-additionalProperties-message", schema["x-additionalProperties-message"]],
551
+ ["x-propertyNames-message", schema["x-propertyNames-message"]],
552
+ ["x-patternProperties-message", schema["x-patternProperties-message"]],
553
+ ["x-dependentRequired-message", schema["x-dependentRequired-message"]],
554
+ ["x-dependentSchemas-message", schema["x-dependentSchemas-message"]],
555
+ ["x-properties-message", schema["x-properties-message"]],
556
+ ["x-unevaluatedProperties-message", schema["x-unevaluatedProperties-message"]],
557
+ ["x-unevaluatedItems-message", schema["x-unevaluatedItems-message"]],
558
+ ["x-if-message", schema["x-if-message"]],
559
+ ["x-then-message", schema["x-then-message"]],
560
+ ["x-else-message", schema["x-else-message"]],
561
+ ["x-allOf-message", schema["x-allOf-message"]],
562
+ ["x-anyOf-message", schema["x-anyOf-message"]],
563
+ ["x-oneOf-message", schema["x-oneOf-message"]],
564
+ ["x-not-message", schema["x-not-message"]],
565
+ ["x-implication-message", schema["x-implication-message"]],
566
+ ["x-brand", schema["x-brand"]]
567
+ ];
568
+ }
569
+ function branch(message, hasConstraint, type, note) {
570
+ return message !== void 0 && hasConstraint ? [{
571
+ type,
572
+ note,
573
+ message
574
+ }] : [];
575
+ }
576
+ function branches(schema, kind) {
577
+ const requiredMessage = schema["x-required-message"];
578
+ const constMessage = schema["x-const-message"];
579
+ const enumMessage = schema["x-enum-message"];
580
+ const minLength = schema["x-minLength-message"];
581
+ const maxLength = schema["x-maxLength-message"];
582
+ const length = schema["x-length-message"];
583
+ const pattern = schema["x-pattern-message"];
584
+ const minimum = schema["x-minimum-message"];
585
+ const maximum = schema["x-maximum-message"];
586
+ const exclusiveMinimum = schema["x-exclusiveMinimum-message"];
587
+ const exclusiveMaximum = schema["x-exclusiveMaximum-message"];
588
+ const multipleOf = schema["x-multipleOf-message"];
589
+ const minItems = schema["x-minItems-message"];
590
+ const maxItems = schema["x-maxItems-message"];
591
+ const uniqueItems = schema["x-uniqueItems-message"];
592
+ const contains = schema["x-contains-message"];
593
+ const minContains = schema["x-minContains-message"];
594
+ const maxContains = schema["x-maxContains-message"];
595
+ const minProperties = schema["x-minProperties-message"];
596
+ const maxProperties = schema["x-maxProperties-message"];
597
+ const additionalPropertiesMessage = schema["x-additionalProperties-message"];
598
+ const oneOf = schema["x-oneOf-message"];
599
+ const anyOf = schema["x-anyOf-message"];
600
+ const allOf = schema["x-allOf-message"];
601
+ const not = schema["x-not-message"];
602
+ const implication = schema["x-implication-message"];
603
+ if (kind === "string") return [
604
+ ...length !== void 0 && schema.minLength !== void 0 && schema.minLength === schema.maxLength ? [{
605
+ type: 52,
606
+ note: "StringMinLength",
607
+ message: length
608
+ }, {
609
+ type: 51,
610
+ note: "StringMaxLength",
611
+ message: length
612
+ }] : [...branch(minLength, schema.minLength !== void 0, 52, "StringMinLength"), ...branch(maxLength, schema.maxLength !== void 0, 51, "StringMaxLength")],
613
+ ...branch(pattern, schema.pattern !== void 0, 53, "StringPattern"),
614
+ ...branch(pattern, schema.pattern !== void 0, 48, "RegExp")
615
+ ];
616
+ if (kind === "number") return [
617
+ ...branch(minimum, schema.minimum !== void 0, 39, "NumberMinimum"),
618
+ ...branch(maximum, schema.maximum !== void 0, 38, "NumberMaximum"),
619
+ ...branch(exclusiveMinimum, schema.exclusiveMinimum !== void 0, 37, "NumberExclusiveMinimum"),
620
+ ...branch(exclusiveMaximum, schema.exclusiveMaximum !== void 0, 36, "NumberExclusiveMaximum"),
621
+ ...branch(multipleOf, schema.multipleOf !== void 0, 40, "NumberMultipleOf")
622
+ ];
623
+ if (kind === "integer") return [
624
+ ...branch(minimum, schema.minimum !== void 0, 25, "IntegerMinimum"),
625
+ ...branch(maximum, schema.maximum !== void 0, 24, "IntegerMaximum"),
626
+ ...branch(exclusiveMinimum, schema.exclusiveMinimum !== void 0, 23, "IntegerExclusiveMinimum"),
627
+ ...branch(exclusiveMaximum, schema.exclusiveMaximum !== void 0, 22, "IntegerExclusiveMaximum"),
628
+ ...branch(multipleOf, schema.multipleOf !== void 0, 26, "IntegerMultipleOf")
629
+ ];
630
+ if (kind === "boolean") return [];
631
+ if (kind === "array") return [
632
+ ...length !== void 0 && schema.minItems !== void 0 && schema.minItems === schema.maxItems ? [{
633
+ type: 4,
634
+ note: "ArrayMinItems",
635
+ message: length
636
+ }, {
637
+ type: 2,
638
+ note: "ArrayMaxItems",
639
+ message: length
640
+ }] : [...branch(minItems, schema.minItems !== void 0, 4, "ArrayMinItems"), ...branch(maxItems, schema.maxItems !== void 0, 2, "ArrayMaxItems")],
641
+ ...branch(uniqueItems, schema.uniqueItems === true, 5, "ArrayUniqueItems"),
642
+ ...branch(contains, schema.contains !== void 0, 0, "ArrayContains"),
643
+ ...branch(minContains, schema.minContains !== void 0, 3, "ArrayMinContains"),
644
+ ...branch(maxContains, schema.maxContains !== void 0, 1, "ArrayMaxContains")
645
+ ];
646
+ if (kind === "object") {
647
+ const requiredArr = Array.isArray(schema.required) ? schema.required : [];
648
+ return [
649
+ ...branch(minProperties, schema.minProperties !== void 0, 44, "ObjectMinProperties"),
650
+ ...branch(maxProperties, schema.maxProperties !== void 0, 43, "ObjectMaxProperties"),
651
+ ...branch(requiredMessage, requiredArr.length > 0, 45, "ObjectRequiredProperty"),
652
+ ...branch(additionalPropertiesMessage, schema.additionalProperties === false, 42, "ObjectAdditionalProperties")
653
+ ];
654
+ }
655
+ return [
656
+ ...branch((schema.anyOf ? implication : void 0) ?? oneOf ?? anyOf ?? enumMessage, Boolean(schema.oneOf ?? schema.anyOf ?? schema.enum), 62, "Union"),
657
+ ...branch(allOf, Boolean(schema.allOf), 29, "Intersect"),
658
+ ...branch(not, Boolean(schema.not), 34, "Not"),
659
+ ...branch(constMessage, schema.const !== void 0, 32, "Literal")
660
+ ];
661
+ }
662
+ function errorCallback(schema, kind) {
663
+ const fallback = schema["x-error-message"];
664
+ const branchList = branches(schema, kind);
665
+ if (branchList.length === 0) return fallback !== void 0 ? ["error", fallback] : void 0;
666
+ const fallbackExpr = fallback !== void 0 ? JSON.stringify(fallback) : "undefined";
667
+ return ["error", raw(`(error)=>{const type=error?.errors?.[0]?.type;${branchList.reduce((acc, b) => {
668
+ const last = acc.at(-1);
669
+ if (last && last.message === b.message) return [...acc.slice(0, -1), {
670
+ message: last.message,
671
+ types: [...last.types, b.type]
672
+ }];
673
+ return [...acc, {
674
+ message: b.message,
675
+ types: [b.type]
676
+ }];
677
+ }, []).map((g) => {
678
+ return `if(${[...g.types].toSorted((a, b) => a - b).map((t) => `type===${t}`).join("||")})return ${JSON.stringify(g.message)}`;
679
+ }).join(";")};return ${fallbackExpr}}`)];
680
+ }
681
+ function enumErrorCallback(schema) {
682
+ const enumMessage = schema["x-enum-message"];
683
+ const fallback = schema["x-error-message"];
684
+ if (enumMessage === void 0) return fallback !== void 0 ? ["error", fallback] : void 0;
685
+ if (fallback === void 0) return ["error", enumMessage];
686
+ return ["error", raw(`(error)=>{const type=error?.errors?.[0]?.type;if(type===62)return ${JSON.stringify(enumMessage)};return ${JSON.stringify(fallback)}}`)];
687
+ }
688
+ function transformWrap(expr, schema, renderSubSchema) {
689
+ const active = [
690
+ propertyNamesSection(schema),
691
+ patternPropertiesSection(schema),
692
+ dependentRequiredSection(schema),
693
+ dependentSchemasSection(schema, renderSubSchema)
694
+ ].filter((s) => s !== null);
695
+ if (active.length === 0) return expr;
696
+ const prelude = active.flatMap((s) => s.prelude);
697
+ const checks = active.map((s) => s.check);
698
+ return `t.Transform(${expr}).Decode(${`(v)=>{${prelude.length > 0 ? `${prelude.join(";")};` : ""}if(v&&typeof v==='object'){${checks.join(";")}}return v}`}).Encode((v)=>v)`;
699
+ }
700
+ function propertyNamesSection(schema) {
701
+ const message = schema["x-propertyNames-message"];
702
+ if (message === void 0) return null;
703
+ const pattern = schema.propertyNames?.pattern;
704
+ if (typeof pattern === "string") return {
705
+ prelude: [`const propNamesRe=new RegExp(${JSON.stringify(pattern)})`],
706
+ check: `for(const k of Object.keys(v)){if(!propNamesRe.test(k))throw new Error(${JSON.stringify(message)})}`
707
+ };
708
+ if (schema.propertyNames !== void 0) {
709
+ const unsupported = Object.keys(schema.propertyNames).filter((k) => k !== "pattern");
710
+ if (unsupported.length > 0) console.warn(`[asphodelos] x-propertyNames-message: only \`pattern\` is enforced via transform; ignoring ${unsupported.map((k) => `\`${k}\``).join(", ")} on propertyNames`);
711
+ }
712
+ return null;
713
+ }
714
+ function patternPropertiesSection(schema) {
715
+ const message = schema["x-patternProperties-message"];
716
+ const map = schema.patternProperties;
717
+ if (message === void 0 || !map || Object.keys(map).length === 0 || schema.additionalProperties !== false) return null;
718
+ const arrLit = Object.keys(map).map((p) => `new RegExp(${JSON.stringify(p)})`).join(",");
719
+ const declaredKeys = Object.keys(schema.properties ?? {});
720
+ return {
721
+ prelude: [`const patternPropsRes=[${arrLit}]`, `const declaredProps=new Set<string>(${JSON.stringify(declaredKeys)})`],
722
+ check: `for(const k of Object.keys(v)){if(declaredProps.has(k))continue;if(!patternPropsRes.some(r=>r.test(k)))throw new Error(${JSON.stringify(message)})}`
723
+ };
724
+ }
725
+ function dependentRequiredSection(schema) {
726
+ const message = schema["x-dependentRequired-message"];
727
+ const map = schema.dependentRequired;
728
+ if (message === void 0 || !map || Object.keys(map).length === 0) return null;
729
+ const emptyEntries = Object.entries(map).filter(([, requiredKeys]) => !requiredKeys || requiredKeys.length === 0).map(([trigger]) => trigger);
730
+ if (emptyEntries.length > 0) console.warn(`[asphodelos] x-dependentRequired-message: dependentRequired entries with empty arrays are silently skipped — ${emptyEntries.map((t) => `\`${t}\``).join(", ")} produce no runtime check. Add at least one required key or remove the entry.`);
731
+ const check = Object.entries(map).map(([trigger, requiredKeys]) => {
732
+ const triggerJson = JSON.stringify(trigger);
733
+ const missing = (requiredKeys ?? []).map((k) => `!(${JSON.stringify(k)} in v)`).join("||");
734
+ return missing ? `if(${triggerJson} in v&&(${missing}))throw new Error(${JSON.stringify(message)})` : null;
735
+ }).filter((s) => s !== null).join(";");
736
+ return check ? {
737
+ prelude: [],
738
+ check
739
+ } : null;
740
+ }
741
+ function dependentSchemasSection(schema, renderSubSchema) {
742
+ const message = schema["x-dependentSchemas-message"];
743
+ const map = schema.dependentSchemas;
744
+ if (message === void 0 || !map || Object.keys(map).length === 0) return null;
745
+ if (renderSubSchema) {
746
+ for (const [trigger, sub] of Object.entries(map)) {
747
+ if (sub?.additionalProperties === false) {
748
+ if (!Object.keys(sub.properties ?? {}).includes(trigger)) console.warn(`[asphodelos] x-dependentSchemas-message: sub-schema for \`${trigger}\` has \`additionalProperties: false\` but does not list \`${trigger}\` in its properties. Per JSON Schema spec the dependent schema applies to the whole instance, so the trigger key itself is rejected as unexpected — every payload (including conforming ones) will fail. Add \`${trigger}\` to the sub-schema's properties, drop \`additionalProperties: false\`, or restructure the dependency.`);
749
+ }
750
+ const subType = sub?.type;
751
+ if (subType !== void 0 && subType !== "object") console.warn(`[asphodelos] x-dependentSchemas-message: sub-schema for \`${trigger}\` has \`type: ${typeof subType === "string" ? subType : JSON.stringify(subType)}\` — non-object sub-schemas can't match the parent object at runtime so every payload will fail. dependentSchemas applies to the whole instance; the sub-schema must be \`type: 'object'\` (or omit \`type\`).`);
752
+ }
753
+ const subsLit = Object.entries(map).map(([trigger, sub]) => `${JSON.stringify(trigger)}:${renderSubSchema(sub)}`).join(",");
754
+ const check = Object.keys(map).map((trigger) => `if(${JSON.stringify(trigger)} in v&&!Value.Check(depSubs[${JSON.stringify(trigger)}],v))throw new Error(${JSON.stringify(message)})`).join(";");
755
+ return {
756
+ prelude: [`const depSubs={${subsLit}}`],
757
+ check
758
+ };
759
+ }
760
+ console.warn("[asphodelos] x-dependentSchemas-message: deep enforcement requires a renderSubSchema callback. Falling back to shallow `required[]`-only check; nested constraints in dependent sub-schemas (pattern, enum, …) will not be enforced at runtime.");
761
+ const check = Object.entries(map).map(([trigger, sub]) => {
762
+ const triggerJson = JSON.stringify(trigger);
763
+ const missing = (Array.isArray(sub?.required) ? sub.required : []).map((k) => `!(${JSON.stringify(k)} in v)`).join("||");
764
+ return missing ? `if(${triggerJson} in v&&(${missing}))throw new Error(${JSON.stringify(message)})` : null;
765
+ }).filter((s) => s !== null).join(";");
766
+ return check ? {
767
+ prelude: [],
768
+ check
769
+ } : null;
770
+ }
771
+ function regexEscape(s) {
772
+ return s.replaceAll(/[.*+?^${}()|[\]\\]/gu, "\\$&");
773
+ }
774
+ function stringPatternFrom(schema) {
775
+ if (schema.format === "email" && typeof schema["x-emailRegex"] === "string") return schema["x-emailRegex"];
776
+ const start = schema["x-startsWith"];
777
+ const incl = schema["x-includes"];
778
+ const end = schema["x-endsWith"];
779
+ if (start === void 0 && incl === void 0 && end === void 0) return schema.pattern;
780
+ return [
781
+ start !== void 0 ? `^${regexEscape(start)}` : "",
782
+ incl !== void 0 ? regexEscape(incl) : "",
783
+ end !== void 0 ? `${regexEscape(end)}$` : ""
784
+ ].filter((p) => p !== "").join(".*");
785
+ }
786
+ function stringTransformWrap(expr, schema) {
787
+ const steps = [];
788
+ if (schema["x-trim"] === true) steps.push("v.trim()");
789
+ if (schema["x-toLowerCase"] === true || schema["x-lowercase"] === true) steps.push("v.toLowerCase()");
790
+ if (schema["x-toUpperCase"] === true || schema["x-uppercase"] === true) steps.push("v.toUpperCase()");
791
+ const norm = schema["x-normalize"];
792
+ if (norm === "NFC" || norm === "NFD" || norm === "NFKC" || norm === "NFKD") steps.push(`v.normalize(${JSON.stringify(norm)})`);
793
+ if (steps.length === 0) return expr;
794
+ return `t.Transform(${expr}).Decode((v)=>{${steps.map((s) => `v=${s}`).join(";")};return v}).Encode((v)=>v)`;
795
+ }
796
+ function wrap(expr, schema) {
797
+ return schema.nullable === true || Array.isArray(schema.type) && schema.type.includes("null") ? `t.Union([${expr},t.Null()])` : expr;
798
+ }
799
+ function readonly(expr, enabled) {
800
+ return enabled ? `t.Readonly(${expr})` : expr;
801
+ }
802
+ function brand(expr, schema) {
803
+ const name = schema["x-brand"];
804
+ if (!name) return expr;
805
+ const TS_BASE_TYPE = {
806
+ string: "string",
807
+ integer: "number",
808
+ number: "number",
809
+ boolean: "boolean"
810
+ };
811
+ const primary = typeof schema.type === "string" ? schema.type : Array.isArray(schema.type) ? schema.type.find((t) => t !== "null") : void 0;
812
+ const base = primary ? TS_BASE_TYPE[primary] : void 0;
813
+ if (!base) return expr;
814
+ return `t.Unsafe<${base} & { readonly __brand: '${name}' }>(${expr})`;
815
+ }
816
+ //#endregion
817
+ //#region src/generator/typebox/t/array.ts
818
+ function isMultipartBinaryArray(items, ctx) {
819
+ return items.format === "binary" && ctx.mediaType === "multipart/form-data";
820
+ }
821
+ function array(schema, ctx = {}) {
822
+ const childCtx = ctx.mediaType ? { mediaType: ctx.mediaType } : {};
823
+ if (schema.prefixItems) return `t.Tuple([${schema.prefixItems.map((s) => typebox(s, childCtx)).join(",")}])`;
824
+ const items = schema.items;
825
+ if (items && isSchemaArray(items)) return `t.Tuple([${items.map((s) => typebox(s, childCtx)).join(",")}])`;
826
+ if (items && !isSchemaArray(items) && isMultipartBinaryArray(items, ctx)) {
827
+ const filesOpts = options([
828
+ ["type", items.contentMediaType],
829
+ ["minItems", schema.minItems],
830
+ ["maxItems", schema.maxItems],
831
+ ["minSize", items.minLength],
832
+ ["maxSize", items.maxLength],
833
+ ...filterDefined([errorCallback(schema, "array")]),
834
+ ...commonOpts(schema)
835
+ ]);
836
+ return filesOpts ? `t.Files(${filesOpts})` : "t.Files()";
837
+ }
838
+ const itemExpr = items ? typebox(items, childCtx) : "t.Unknown()";
839
+ const containsExpr = schema.contains ? raw(typebox(schema.contains, childCtx)) : void 0;
840
+ const opts = options([
841
+ ["minItems", schema.minItems],
842
+ ["maxItems", schema.maxItems],
843
+ ["uniqueItems", schema.uniqueItems],
844
+ ["contains", containsExpr],
845
+ ["minContains", schema.minContains],
846
+ ["maxContains", schema.maxContains],
847
+ ...filterDefined([errorCallback(schema, "array")]),
848
+ ...commonOpts(schema)
849
+ ]);
850
+ return opts ? `t.Array(${itemExpr},${opts})` : `t.Array(${itemExpr})`;
851
+ }
852
+ //#endregion
853
+ //#region src/generator/typebox/t/boolean.ts
854
+ function boolean(schema) {
855
+ const opts = options([
856
+ ["format", schema.format],
857
+ ...filterDefined([errorCallback(schema, "boolean")]),
858
+ ...commonOpts(schema)
859
+ ]);
860
+ return `${schema.format === "boolean-string" ? "t.BooleanString" : "t.Boolean"}(${opts})`;
861
+ }
862
+ //#endregion
863
+ //#region src/generator/typebox/t/enum.ts
864
+ function isUnionEnumValue(v) {
865
+ return typeof v === "string" || typeof v === "number";
866
+ }
867
+ function literalSchema(value) {
868
+ if (value === null) return "t.Null()";
869
+ if (Array.isArray(value)) return `t.Tuple([${value.map(literalSchema).join(",")}])`;
870
+ if (isRecord(value)) return `t.Object({${Object.entries(value).map(([key, v]) => `${JSON.stringify(key)}:${literalSchema(v)}`).join(",")}})`;
871
+ return `t.Literal(${JSON.stringify(value)})`;
872
+ }
873
+ function _enum(schema) {
874
+ const values = schema.enum ?? [];
875
+ const opts = options([...filterDefined([enumErrorCallback(schema)]), ...commonOpts(schema)]);
876
+ if (values.length === 0) return "t.Never()";
877
+ if (values.length === 1) {
878
+ const v = values[0];
879
+ if (v === null) return opts ? `t.Null(${opts})` : "t.Null()";
880
+ if (opts && typeof v !== "object") return `t.Literal(${JSON.stringify(v)},${opts})`;
881
+ return literalSchema(v);
882
+ }
883
+ if (values.every(isUnionEnumValue)) {
884
+ const list = JSON.stringify(values);
885
+ return opts ? `t.UnionEnum(${list},${opts})` : `t.UnionEnum(${list})`;
886
+ }
887
+ return opts ? `t.Union([${values.map(literalSchema).join(",")}],${opts})` : `t.Union([${values.map(literalSchema).join(",")}])`;
888
+ }
889
+ //#endregion
890
+ //#region src/generator/typebox/t/integer.ts
891
+ function autoMaximum(format, maximum, exclusiveMaximum) {
892
+ if (maximum !== void 0) return maximum;
893
+ if (exclusiveMaximum !== void 0) return void 0;
894
+ if (format === "int32") return 2147483647;
895
+ return Number.MAX_SAFE_INTEGER;
896
+ }
897
+ function integer(schema) {
898
+ const bounds = normalizeBounds(schema);
899
+ const opts = options([
900
+ ["format", schema.format],
901
+ ["minimum", bounds.minimum],
902
+ ["maximum", autoMaximum(schema.format, bounds.maximum, bounds.exclusiveMaximum)],
903
+ ["exclusiveMinimum", bounds.exclusiveMinimum],
904
+ ["exclusiveMaximum", bounds.exclusiveMaximum],
905
+ ["multipleOf", schema.multipleOf],
906
+ ...filterDefined([errorCallback(schema, "integer")]),
907
+ ...commonOpts(schema)
908
+ ]);
909
+ return `${schema.format === "numeric" ? "t.Numeric" : "t.Integer"}(${opts})`;
910
+ }
911
+ //#endregion
912
+ //#region src/generator/typebox/t/intersect.ts
913
+ function intersect(schemas, parent) {
914
+ if (schemas.length === 0) return "t.Unknown()";
915
+ if (schemas.length === 1 && schemas[0]) return typebox(schemas[0]);
916
+ const opts = parent ? options([...filterDefined([errorCallback(parent, "composition")]), ...commonOpts(parent)]) : "";
917
+ return opts ? `t.Intersect([${schemas.map((s) => typebox(s)).join(",")}],${opts})` : `t.Intersect([${schemas.map((s) => typebox(s)).join(",")}])`;
918
+ }
919
+ //#endregion
920
+ //#region src/generator/typebox/t/not.ts
921
+ function notType(inner, parent) {
922
+ const innerExpr = typebox(inner);
923
+ const opts = options([...filterDefined([errorCallback(parent, "composition")]), ...commonOpts(parent)]);
924
+ return opts ? `t.Not(${innerExpr},${opts})` : `t.Not(${innerExpr})`;
925
+ }
926
+ //#endregion
927
+ //#region src/generator/typebox/t/null.ts
928
+ function nullType() {
929
+ return "t.Null()";
930
+ }
931
+ //#endregion
932
+ //#region src/generator/typebox/t/number.ts
933
+ function number(schema) {
934
+ const bounds = normalizeBounds(schema);
935
+ const opts = options([
936
+ ["format", schema.format],
937
+ ["minimum", bounds.minimum],
938
+ ["maximum", bounds.maximum],
939
+ ["exclusiveMinimum", bounds.exclusiveMinimum],
940
+ ["exclusiveMaximum", bounds.exclusiveMaximum],
941
+ ["multipleOf", schema.multipleOf],
942
+ ...filterDefined([errorCallback(schema, "number")]),
943
+ ...commonOpts(schema)
944
+ ]);
945
+ return `${schema.format === "numeric" ? "t.Numeric" : "t.Number"}(${opts})`;
946
+ }
947
+ //#endregion
948
+ //#region src/generator/typebox/t/object.ts
949
+ function pickObjectFactory(ctx) {
950
+ if (ctx.elysiaKind === "object-string") return "ObjectString";
951
+ if (ctx.elysiaKind === "cookie") return "Cookie";
952
+ if (ctx.mediaType === "multipart/form-data") return "Form";
953
+ return "Object";
954
+ }
955
+ function childCtxFor(parentCtx, propSchema) {
956
+ const inheritedMedia = parentCtx.mediaType ? { mediaType: parentCtx.mediaType } : {};
957
+ if (parentCtx.parameterLocation === "query" && propSchema.type === "object") return {
958
+ ...inheritedMedia,
959
+ elysiaKind: "object-string"
960
+ };
961
+ return inheritedMedia;
962
+ }
963
+ function object(schema, ctx = {}) {
964
+ const properties = schema.properties ?? {};
965
+ const required = new Set(schema.required);
966
+ const propEntries = Object.entries(properties).map(([key, propSchema]) => {
967
+ const inner = typebox(propSchema, childCtxFor(ctx, propSchema));
968
+ const isRequired = required.has(key);
969
+ const wrapped = propSchema.readOnly === true ? isRequired ? `t.Readonly(${inner})` : `t.ReadonlyOptional(${inner})` : isRequired ? inner : `t.Optional(${inner})`;
970
+ return `${JSON.stringify(key)}:${wrapped}`;
971
+ });
972
+ const additional = schema.additionalProperties;
973
+ if (Object.keys(properties).length === 0 && additional && typeof additional === "object") {
974
+ const additionalCtx = childCtxFor(ctx, additional);
975
+ const valueExpr = typebox(additional, additionalCtx);
976
+ const opts = options([
977
+ ["minProperties", schema.minProperties],
978
+ ["maxProperties", schema.maxProperties],
979
+ ...filterDefined([errorCallback(schema, "object")]),
980
+ ...commonOpts(schema)
981
+ ]);
982
+ return transformWrap(opts ? `t.Record(t.String(),${valueExpr},${opts})` : `t.Record(t.String(),${valueExpr})`, schema, (s) => typebox(s, additionalCtx));
983
+ }
984
+ const opts = options([
985
+ ["minProperties", schema.minProperties],
986
+ ["maxProperties", schema.maxProperties],
987
+ ["additionalProperties", additional === false ? false : void 0],
988
+ ...filterDefined([errorCallback(schema, "object")]),
989
+ ...commonOpts(schema)
990
+ ]);
991
+ const body = `{${propEntries.join(",")}}`;
992
+ const factory = pickObjectFactory(ctx);
993
+ const objectExpr = opts ? `t.${factory}(${body},${opts})` : `t.${factory}(${body})`;
994
+ const subCtx = ctx.mediaType ? { mediaType: ctx.mediaType } : {};
995
+ return transformWrap(objectExpr, schema, (s) => typebox(s, subCtx));
996
+ }
997
+ //#endregion
998
+ //#region src/generator/typebox/t/ref.ts
999
+ function ref(schema) {
1000
+ if (!schema.$ref) return "t.Unknown()";
1001
+ const m = /^#\/components\/schemas\/([^/]+)$/u.exec(schema.$ref);
1002
+ if (m?.[1]) return `${pascalCase(decodeURIComponent(m[1]))}Schema`;
1003
+ return "t.Unknown()";
1004
+ }
1005
+ //#endregion
1006
+ //#region src/generator/typebox/t/string.ts
1007
+ function string(schema, ctx = {}) {
1008
+ if (schema.format === "date" || schema.format === "date-time") return `t.Date(${options([...commonOpts(schema)])})`;
1009
+ if (schema.format === "binary") {
1010
+ if (ctx.mediaType === "application/octet-stream") return `t.Uint8Array(${options([
1011
+ ["minByteLength", schema.minLength],
1012
+ ["maxByteLength", schema.maxLength],
1013
+ ...commonOpts(schema)
1014
+ ])})`;
1015
+ return `t.File(${options([
1016
+ ["type", schema.contentMediaType],
1017
+ ["minSize", schema.minLength],
1018
+ ["maxSize", schema.maxLength],
1019
+ ...commonOpts(schema)
1020
+ ])})`;
1021
+ }
1022
+ return stringTransformWrap(`t.String(${options([
1023
+ ["format", schema.format],
1024
+ ["pattern", stringPatternFrom(schema)],
1025
+ ["minLength", schema.minLength],
1026
+ ["maxLength", schema.maxLength],
1027
+ ...filterDefined([errorCallback(schema, "string")]),
1028
+ ...commonOpts(schema)
1029
+ ])})`, schema);
1030
+ }
1031
+ //#endregion
1032
+ //#region src/generator/typebox/t/union.ts
1033
+ function union(schemas, parent) {
1034
+ if (schemas.length === 0) return "t.Never()";
1035
+ if (schemas.length === 1 && schemas[0]) return typebox(schemas[0]);
1036
+ const opts = parent ? options([...filterDefined([errorCallback(parent, "composition")]), ...commonOpts(parent)]) : "";
1037
+ return opts ? `t.Union([${schemas.map((s) => typebox(s)).join(",")}],${opts})` : `t.Union([${schemas.map((s) => typebox(s)).join(",")}])`;
1038
+ }
1039
+ //#endregion
1040
+ //#region src/generator/typebox/index.ts
1041
+ function typebox(schema, ctx = {}) {
1042
+ const enabled = schema["x-readonly"] === true;
1043
+ const rawTransform = schema["x-transform"];
1044
+ if (typeof rawTransform === "string" && rawTransform.length > 0) return readonly(wrap(brand(rawTransform, schema), schema), enabled);
1045
+ if (schema.$ref) return ref(schema);
1046
+ if (schema.allOf) return readonly(wrap(intersect(schema.allOf, schema), schema), enabled);
1047
+ if (schema.oneOf) return readonly(wrap(union(schema.oneOf, schema), schema), enabled);
1048
+ if (schema.anyOf) return readonly(wrap(union(schema.anyOf, schema), schema), enabled);
1049
+ if (schema.not) return readonly(wrap(notType(schema.not, schema), schema), enabled);
1050
+ if (schema.enum) return readonly(wrap(brand(_enum(schema), schema), schema), enabled);
1051
+ if (schema.const !== void 0) {
1052
+ const opts = options([...filterDefined([errorCallback(schema, "composition")]), ...commonOpts(schema)]);
1053
+ const optsPart = opts ? `,${opts}` : "";
1054
+ return readonly(wrap(brand(schema.const === null ? `t.Null(${opts})` : typeof schema.const === "object" ? literalSchema(schema.const) : `t.Literal(${JSON.stringify(schema.const)}${optsPart})`, schema), schema), enabled);
1055
+ }
1056
+ const primaryType = () => {
1057
+ if (typeof schema.type === "string") return schema.type;
1058
+ if (isTypeArray(schema.type)) return schema.type.find((t) => t !== "null");
1059
+ if (schema.properties ?? schema.additionalProperties !== void 0) return "object";
1060
+ if (schema.items ?? schema.prefixItems) return "array";
1061
+ };
1062
+ switch (primaryType()) {
1063
+ case "string": return readonly(wrap(brand(string(schema, ctx), schema), schema), enabled);
1064
+ case "integer": return readonly(wrap(brand(integer(schema), schema), schema), enabled);
1065
+ case "number": return readonly(wrap(brand(number(schema), schema), schema), enabled);
1066
+ case "boolean": return readonly(wrap(brand(boolean(schema), schema), schema), enabled);
1067
+ case "array": return readonly(wrap(array(schema, ctx), schema), enabled);
1068
+ case "object": return readonly(wrap(object(schema, ctx), schema), enabled);
1069
+ case "null": return nullType();
1070
+ case "date":
1071
+ case void 0: return "t.Unknown()";
1072
+ default: return "t.Unknown()";
1073
+ }
1074
+ }
1075
+ //#endregion
1076
+ //#region src/generator/components/schemas.ts
1077
+ function sub(code, from, to) {
1078
+ return code.replaceAll(new RegExp(`\\b${from}\\b`, "gu"), to);
1079
+ }
1080
+ function findFree(base, i, used) {
1081
+ const candidate = `${base}${i}`;
1082
+ return used.has(candidate) ? findFree(base, i + 1, used) : candidate;
1083
+ }
1084
+ /**
1085
+ * OpenAPI schema keys are case-sensitive, but `pascalCase` upper-cases the first
1086
+ * letter, so `User` and `user` both emit `const UserSchema` / `type User`
1087
+ * (TS2451 / TS2300). Map each colliding key to a deterministic suffixed exported
1088
+ * name (`User`, `User2`, …) so every declaration is unique. The map is global
1089
+ * across SCC groups; TypeBox refs (`t.Ref('user')`, recursive `Self`) keep using
1090
+ * the original key, so only the exported binding names change.
1091
+ */
1092
+ function resolveUniqueNames(keys) {
1093
+ return keys.reduce((acc, key) => {
1094
+ const base = pascalCase(key);
1095
+ const unique = acc.used.has(base) ? findFree(base, 2, acc.used) : base;
1096
+ acc.used.add(unique);
1097
+ acc.map.set(key, unique);
1098
+ return acc;
1099
+ }, {
1100
+ used: /* @__PURE__ */ new Set(),
1101
+ map: /* @__PURE__ */ new Map()
1102
+ }).map;
1103
+ }
1104
+ function schemasCode(schemas, readonlyMode, exportTypes, exported = true) {
1105
+ if (!schemas || Object.keys(schemas).length === 0) return "";
1106
+ const names = resolveUniqueNames(Object.keys(schemas));
1107
+ return sccSchemas(Object.entries(schemas).map(([name, schema]) => ({
1108
+ name,
1109
+ schema
1110
+ }))).map((group) => emitGroup(group, readonlyMode, exportTypes, exported, names)).filter((s) => s.length > 0).join("\n\n");
1111
+ }
1112
+ function refsWithin(schema, memberNames) {
1113
+ return new Set([...collectSchemaRefs$1(schema)].filter((r) => memberNames.has(r)));
1114
+ }
1115
+ function findStar(group, exportedName) {
1116
+ if (group.some((g) => exportedName(g.name) !== pascalCase(g.name))) return null;
1117
+ const memberNames = new Set(group.map((g) => g.name));
1118
+ const refs = new Map(group.map((g) => [g.name, refsWithin(g.schema, memberNames)]));
1119
+ const candidates = group.filter((hub) => {
1120
+ const spokes = group.filter((g) => g.name !== hub.name);
1121
+ const hubRefs = refs.get(hub.name) ?? /* @__PURE__ */ new Set();
1122
+ return spokes.every((s) => {
1123
+ const r = refs.get(s.name) ?? /* @__PURE__ */ new Set();
1124
+ return r.size === 1 && r.has(hub.name);
1125
+ }) && spokes.every((s) => hubRefs.has(s.name));
1126
+ });
1127
+ if (candidates.length === 0) return null;
1128
+ const hub = candidates.reduce((a, b) => {
1129
+ const ra = (refs.get(a.name) ?? /* @__PURE__ */ new Set()).size;
1130
+ const rb = (refs.get(b.name) ?? /* @__PURE__ */ new Set()).size;
1131
+ if (rb > ra) return b;
1132
+ if (rb < ra) return a;
1133
+ return b.name < a.name ? b : a;
1134
+ });
1135
+ return {
1136
+ hub,
1137
+ spokes: group.filter((g) => g.name !== hub.name)
1138
+ };
1139
+ }
1140
+ function emitStar(star, exportKw, readonlyMode, exportTypes) {
1141
+ const { hub, spokes } = star;
1142
+ const hubRef = `${pascalCase(hub.name)}Schema`;
1143
+ const hubExpr = `t.Recursive((Self)=>${sub(spokes.reduce((acc, s) => sub(acc, `${pascalCase(s.name)}Schema`, `(${sub(typebox(s.schema), hubRef, "Self")})`), typebox(hub.schema)), hubRef, "Self")})`;
1144
+ const decl = (name, expr) => {
1145
+ const ident = `${pascalCase(name)}Schema`;
1146
+ const d = `${exportKw}const ${ident}=${readonly(expr, readonlyMode)}`;
1147
+ return exportTypes ? `${d}\n\nexport type ${pascalCase(name)}=Static<typeof ${ident}>` : d;
1148
+ };
1149
+ return [decl(hub.name, hubExpr), ...spokes.map((s) => decl(s.name, typebox(s.schema)))].join("\n\n");
1150
+ }
1151
+ function emitGroup(group, readonlyMode, exportTypes, exported, names) {
1152
+ const exportKw = exported ? "export " : "";
1153
+ const head = group[0];
1154
+ const exportedName = (name) => names.get(name) ?? pascalCase(name);
1155
+ if (group.length === 1 && head) {
1156
+ const { name, schema } = head;
1157
+ const ident = `${exportedName(name)}Schema`;
1158
+ const selfRef = `${pascalCase(name)}Schema`;
1159
+ const refs = collectSchemaRefs$1(schema);
1160
+ const inner = typebox(schema);
1161
+ const decl = `${exportKw}const ${ident}=${readonly(refs.has(name) ? `t.Recursive((Self)=>${inner.replaceAll(new RegExp(`\\b${selfRef}\\b`, "gu"), "Self")})` : inner, readonlyMode)}`;
1162
+ return exportTypes ? `${decl}\n\nexport type ${exportedName(name)}=Static<typeof ${ident}>` : decl;
1163
+ }
1164
+ const star = findStar(group, exportedName);
1165
+ if (star) return emitStar(star, exportKw, readonlyMode, exportTypes);
1166
+ const memberNames = new Set(group.map((g) => g.name));
1167
+ const moduleEntries = group.map(({ name, schema }) => {
1168
+ const inner = typebox(schema);
1169
+ return `${name}:${[...memberNames].reduce((acc, m) => {
1170
+ const ident = `${pascalCase(m)}Schema`;
1171
+ return acc.replaceAll(new RegExp(`\\b${ident}\\b`, "gu"), `t.Ref('${m}')`);
1172
+ }, inner)}`;
1173
+ }).join(",");
1174
+ const moduleId = head ? `${pascalCase(head.name)}Module` : "Module";
1175
+ return `const ${moduleId}=t.Module({${moduleEntries}})\n\n${group.map(({ name }) => {
1176
+ const ident = `${exportedName(name)}Schema`;
1177
+ const decl = `${exportKw}const ${ident}=${readonly(`${moduleId}.Import('${name}')`, readonlyMode)}`;
1178
+ return exportTypes ? `${decl}\n\nexport type ${exportedName(name)}=Static<typeof ${ident}>` : decl;
1179
+ }).join("\n\n")}`;
1180
+ }
1181
+ //#endregion
1182
+ //#region src/generator/components/typeboxKinds.ts
1183
+ function makeCode(suffix, extract) {
1184
+ return (section, readonlyMode, exportTypes, exported = true) => {
1185
+ if (!section) return "";
1186
+ const entries = extract(section);
1187
+ if (entries.length === 0) return "";
1188
+ const exportKw = exported ? "export " : "";
1189
+ return entries.map(([name, expr]) => {
1190
+ const ident = `${pascalCase(name)}${suffix}`;
1191
+ const decl = `${exportKw}const ${ident}=${readonly(expr, readonlyMode)}`;
1192
+ const typeStripped = pascalCase(name);
1193
+ return exportTypes ? `${decl}\n\nexport type ${typeStripped}${suffix.replace(/Schema$/u, "")}=Static<typeof ${ident}>` : decl;
1194
+ }).join("\n\n");
1195
+ };
1196
+ }
1197
+ const responsesCode = makeCode("ResponseSchema", (s) => Object.entries(s).map(([name, res]) => {
1198
+ const schema = jsonSchema(res.content);
1199
+ return [name, schema ? typebox(schema) : "t.Void()"];
1200
+ }));
1201
+ const parametersCode = makeCode("ParamsSchema", (s) => Object.entries(s).map(([name, p]) => {
1202
+ if (!p.schema) {
1203
+ if (p.content) console.warn(`asphodelos: Component parameter '${name}' uses 'content' field (OpenAPI 3.1 alternative to 'schema'). This is not yet supported; emitting t.Unknown() placeholder.`);
1204
+ return [name, "t.Unknown()"];
1205
+ }
1206
+ return [name, typebox(p.schema)];
1207
+ }));
1208
+ const requestBodiesCode = makeCode("RequestBodySchema", (s) => Object.entries(s).map(([name, body]) => {
1209
+ const schema = jsonSchema(body.content);
1210
+ return [name, schema ? typebox(schema) : "t.Unknown()"];
1211
+ }));
1212
+ const headersCode = makeCode("HeaderSchema", (s) => Object.entries(s).map(([name, h]) => {
1213
+ if (isReference(h) && h.$ref) return [name, refIdent(h.$ref) ?? "t.Unknown()"];
1214
+ const schema = "schema" in h ? h.schema : void 0;
1215
+ return [name, schema ? typebox(schema) : "t.Unknown()"];
1216
+ }));
1217
+ const mediaTypesCode = makeCode("MediaTypeSchema", (s) => Object.entries(s).map(([name, mt]) => {
1218
+ if (isReference(mt) && mt.$ref) return [name, refIdent(mt.$ref) ?? "t.Unknown()"];
1219
+ const schema = !isReference(mt) && "schema" in mt ? mt.schema : void 0;
1220
+ return [name, schema ? typebox(schema) : "t.Unknown()"];
1221
+ }));
1222
+ //#endregion
1223
+ //#region src/generator/components/metadataKinds.ts
1224
+ function makeMetadataCode(suffix) {
1225
+ return (section, exported = true) => {
1226
+ if (!section) return "";
1227
+ const entries = Object.entries(section);
1228
+ if (entries.length === 0) return "";
1229
+ const exportKw = exported ? "export " : "";
1230
+ return entries.map(([name, v]) => {
1231
+ const ident = `${pascalCase(name)}${suffix}`;
1232
+ if (isReference(v) && v.$ref) return `${exportKw}const ${ident}=${refIdent(v.$ref) ?? `${stringifyRefs(v)} as const`}`;
1233
+ return `${exportKw}const ${ident}=${stringifyRefs(v)} as const`;
1234
+ }).join("\n\n");
1235
+ };
1236
+ }
1237
+ const examplesCode = makeMetadataCode("Example");
1238
+ const securitySchemesCode = makeMetadataCode("SecurityScheme");
1239
+ const linksCode = makeMetadataCode("Link");
1240
+ const callbacksCode = makeMetadataCode("Callback");
1241
+ const pathItemsCode = makeMetadataCode("PathItem");
1242
+ //#endregion
1243
+ //#region src/helper/code.ts
1244
+ function makeModuleSpec(fromFile, target) {
1245
+ const stripped = path.relative(path.dirname(fromFile), target.output).replaceAll("\\", "/").replace(/\.ts$/u, "").replace(/(^|\/)index$/u, "");
1246
+ return stripped === "" ? "." : stripped.startsWith(".") ? stripped : `./${stripped}`;
1247
+ }
1248
+ function makeBarrel(value) {
1249
+ return `${Object.keys(value).toSorted().map((k) => `export * from './${k.charAt(0).toLowerCase() + k.slice(1)}'`).join("\n")}\n`;
1250
+ }
1251
+ const JS_IDENT = "[A-Za-z_$][A-Za-z0-9_$]*";
1252
+ const COMPONENT_SUFFIXES = [
1253
+ ["schemas", "Schema"],
1254
+ ["responses", "ResponseSchema"],
1255
+ ["parameters", "ParamsSchema"],
1256
+ ["examples", "Example"],
1257
+ ["requestBodies", "RequestBodySchema"],
1258
+ ["headers", "HeaderSchema"],
1259
+ ["securitySchemes", "SecurityScheme"],
1260
+ ["links", "Link"],
1261
+ ["callbacks", "Callback"],
1262
+ ["pathItems", "PathItem"],
1263
+ ["mediaTypes", "MediaTypeSchema"]
1264
+ ];
1265
+ const SCAN = new RegExp([
1266
+ String.raw`"(?:\\.|[^"\\])*"`,
1267
+ String.raw`'(?:\\.|[^'\\])*'`,
1268
+ "`(?:\\\\.|[^`\\\\])*`",
1269
+ String.raw`//[^\n]*`,
1270
+ String.raw`/\*[\s\S]*?\*/`,
1271
+ `\\b(${JS_IDENT}(?:${COMPONENT_SUFFIXES.map(([, suf]) => suf).join("|")}))\\b`
1272
+ ].join("|"), "gu");
1273
+ const CONST_PATTERN = new RegExp(`(?:export\\s+)?const\\s+(${JS_IDENT})\\s*=`, "gu");
1274
+ const EXPORT_TYPE_PATTERN = new RegExp(`export\\s+type\\s+(${JS_IDENT})\\s*=`, "gu");
1275
+ function classifyRef(name) {
1276
+ return COMPONENT_SUFFIXES.reduce((best, entry) => name.endsWith(entry[1]) && (!best || entry[1].length > best[1].length) ? entry : best, void 0)?.[0];
1277
+ }
1278
+ function makeImports(code, fromFile, components, split = false, excludeKinds = /* @__PURE__ */ new Set()) {
1279
+ const fallbackPrefix = split ? ".." : ".";
1280
+ const resolvePath = (k) => {
1281
+ const target = components?.[k];
1282
+ return target?.import ?? (target ? makeModuleSpec(fromFile, target) : `${fallbackPrefix}/${k}`);
1283
+ };
1284
+ const definedConsts = new Set(Array.from(code.matchAll(CONST_PATTERN), (m) => m[1]).filter(Boolean));
1285
+ const definedTypes = new Set(Array.from(code.matchAll(EXPORT_TYPE_PATTERN), (m) => m[1]).filter(Boolean));
1286
+ const grouped = /* @__PURE__ */ new Map();
1287
+ for (const match of code.matchAll(SCAN)) {
1288
+ const name = match[1];
1289
+ if (!name || definedConsts.has(name) || definedTypes.has(name)) continue;
1290
+ const kind = classifyRef(name);
1291
+ if (!kind || excludeKinds.has(kind)) continue;
1292
+ const bucket = grouped.get(kind) ?? /* @__PURE__ */ new Set();
1293
+ bucket.add(name);
1294
+ grouped.set(kind, bucket);
1295
+ }
1296
+ const needsT = /\bt\.[A-Z]/u.test(code);
1297
+ const needsStatic = /\bStatic\s*</u.test(code) && !definedTypes.has("Static");
1298
+ const needsUnwrap = /\bUnwrapSchema\s*</u.test(code) && !definedTypes.has("UnwrapSchema");
1299
+ const elysiaParts = [
1300
+ needsT ? "t" : "",
1301
+ needsStatic ? "type Static" : "",
1302
+ needsUnwrap ? "type UnwrapSchema" : ""
1303
+ ].filter(Boolean);
1304
+ const headerLines = [
1305
+ elysiaParts.length > 0 ? `import {${elysiaParts.join(",")}} from 'elysia'` : "",
1306
+ /\bValue\.Check\b/u.test(code) ? `import {Value} from '@sinclair/typebox/value'` : "",
1307
+ ...COMPONENT_SUFFIXES.flatMap(([kind]) => {
1308
+ const names = grouped.get(kind);
1309
+ if (!names) return [];
1310
+ return [`import {${[...names].toSorted().join(",")}} from '${resolvePath(kind)}'`];
1311
+ })
1312
+ ].filter(Boolean);
1313
+ if (headerLines.length === 0) return code;
1314
+ return `${headerLines.join("\n")}\n\n${code}`;
1315
+ }
1316
+ //#endregion
1317
+ //#region src/helper/eden.ts
1318
+ function isIdentifierSegment(seg) {
1319
+ return /^[A-Za-z_$][A-Za-z0-9_$]*$/u.test(seg);
1320
+ }
1321
+ function bracketKey(seg) {
1322
+ return `['${seg.replaceAll("\\", "\\\\").replaceAll("'", "\\'")}']`;
1323
+ }
1324
+ function accessSegment(seg) {
1325
+ return isIdentifierSegment(seg) ? `.${seg}` : bracketKey(seg);
1326
+ }
1327
+ function edenChain(pathStr, client, method) {
1328
+ const { callChain, typeChain, typeIsValue, typeChainHasBracket, paramArgs } = pathStr.split("/").filter(Boolean).reduce((acc, seg) => {
1329
+ if (/^\{([^}]+)\}$/u.exec(seg)) {
1330
+ const t = acc.typeIsValue ? `typeof ${acc.typeChain}` : acc.typeChain;
1331
+ const name = acc.paramArgs.length === 0 ? "params" : `params${acc.paramArgs.length + 1}`;
1332
+ return {
1333
+ callChain: `${acc.callChain}(${name})`,
1334
+ typeChain: `ReturnType<${t}>`,
1335
+ typeIsValue: false,
1336
+ typeChainHasBracket: acc.typeChainHasBracket,
1337
+ paramArgs: [...acc.paramArgs, {
1338
+ name,
1339
+ typeExpr: `Parameters<${t}>[0]`
1340
+ }]
1341
+ };
1342
+ }
1343
+ const isIdent = isIdentifierSegment(seg);
1344
+ const valueTypeAccess = acc.typeChainHasBracket || !isIdent ? bracketKey(seg) : `.${seg}`;
1345
+ return {
1346
+ callChain: `${acc.callChain}${accessSegment(seg)}`,
1347
+ typeChain: acc.typeIsValue ? `${acc.typeChain}${valueTypeAccess}` : `${acc.typeChain}${bracketKey(seg)}`,
1348
+ typeIsValue: acc.typeIsValue,
1349
+ typeChainHasBracket: acc.typeChainHasBracket || acc.typeIsValue && !isIdent,
1350
+ paramArgs: acc.paramArgs
1351
+ };
1352
+ }, {
1353
+ callChain: client,
1354
+ typeChain: client,
1355
+ typeIsValue: true,
1356
+ typeChainHasBracket: false,
1357
+ paramArgs: []
1358
+ });
1359
+ const methodHostTypeExpr = typeIsValue ? typeChainHasBracket ? `typeof ${typeChain}${bracketKey(method)}` : `typeof ${typeChain}.${method}` : `${typeChain}['${method}']`;
1360
+ return {
1361
+ callExpr: `${callChain}.${method}`,
1362
+ methodHostTypeExpr,
1363
+ paramArgs
1364
+ };
1365
+ }
1366
+ //#endregion
1367
+ //#region src/helper/path-params.ts
1368
+ function isIdentifier(name) {
1369
+ return /^[A-Za-z_$][A-Za-z0-9_$]*$/u.test(name);
1370
+ }
1371
+ function paramToken(segment) {
1372
+ return /^\{(.+)\}$/u.exec(segment)?.[1] ?? null;
1373
+ }
1374
+ function canonicalParamNames(paths) {
1375
+ const namesByPosition = /* @__PURE__ */ new Map();
1376
+ for (const path of paths) {
1377
+ const norm = [];
1378
+ for (const segment of path.split("/").filter(Boolean)) {
1379
+ const token = paramToken(segment);
1380
+ if (token === null) {
1381
+ norm.push(segment);
1382
+ continue;
1383
+ }
1384
+ const key = [...norm, "{}"].join("/");
1385
+ const names = namesByPosition.get(key) ?? /* @__PURE__ */ new Set();
1386
+ names.add(token);
1387
+ namesByPosition.set(key, names);
1388
+ norm.push("{}");
1389
+ }
1390
+ }
1391
+ const canonical = /* @__PURE__ */ new Map();
1392
+ for (const [key, names] of namesByPosition) {
1393
+ if (names.size < 2) continue;
1394
+ const spellings = [...names];
1395
+ if (!spellings.every(isIdentifier)) continue;
1396
+ const representative = spellings.reduce((a, b) => b.length > a.length || b.length === a.length && b < a ? b : a);
1397
+ canonical.set(key, representative);
1398
+ }
1399
+ return canonical;
1400
+ }
1401
+ function normalizePath(path, canonical) {
1402
+ const norm = [];
1403
+ const renames = /* @__PURE__ */ new Map();
1404
+ return {
1405
+ path: `/${path.split("/").filter(Boolean).map((segment) => {
1406
+ const token = paramToken(segment);
1407
+ if (token === null) {
1408
+ norm.push(segment);
1409
+ return segment;
1410
+ }
1411
+ const key = [...norm, "{}"].join("/");
1412
+ norm.push("{}");
1413
+ const representative = canonical.get(key);
1414
+ if (representative && representative !== token) {
1415
+ renames.set(token, representative);
1416
+ return `{${representative}}`;
1417
+ }
1418
+ return segment;
1419
+ }).join("/")}`,
1420
+ renames
1421
+ };
1422
+ }
1423
+ function renameOperationParams(operation, renames) {
1424
+ if (renames.size === 0 || !operation.parameters) return operation;
1425
+ const parameters = operation.parameters.map((p) => !isReference(p) && p.in === "path" && renames.has(p.name) ? {
1426
+ ...p,
1427
+ name: renames.get(p.name) ?? p.name
1428
+ } : p);
1429
+ return {
1430
+ ...operation,
1431
+ parameters
1432
+ };
1433
+ }
1434
+ //#endregion
1435
+ //#region src/helper/openapi.ts
1436
+ /**
1437
+ * Generates prefix-only query-key getters (`get<Prefix>Key() => ['<prefix>']`)
1438
+ * for each unique first path segment, mirroring hono-takibi. Used for
1439
+ * per-resource cache invalidation.
1440
+ */
1441
+ function makePrefixKeyCode(prefix) {
1442
+ return `export function get${pascalCase(prefix)}Key() {\n return ['${prefix}'] as const\n}`;
1443
+ }
1444
+ function makePrefixKeyCodes(openAPI, basePath) {
1445
+ const prefixes = /* @__PURE__ */ new Set();
1446
+ for (const pathStr of Object.keys(openAPI.paths ?? {})) {
1447
+ const seg = resourcePrefix(`${basePath}${pathStr}`);
1448
+ if (seg) prefixes.add(seg);
1449
+ }
1450
+ return [...prefixes].toSorted().map(makePrefixKeyCode);
1451
+ }
1452
+ function tagName(tag) {
1453
+ const cleaned = tag.replaceAll(/[^a-zA-Z0-9]+/gu, " ").trim();
1454
+ if (!cleaned) return "default";
1455
+ return cleaned.split(/\s+/u).map((p, i) => i === 0 ? p.toLowerCase() : p.charAt(0).toUpperCase() + p.slice(1).toLowerCase()).join("");
1456
+ }
1457
+ function resourceName(pathStr) {
1458
+ const first = pathStr.split("/").find((s) => s.length > 0 && !s.startsWith("{"));
1459
+ return first ? tagName(first) : "root";
1460
+ }
1461
+ function makeOperationId(method, path) {
1462
+ const camel = path.replaceAll(/\{([^}]+)\}/gu, "By-$1").split("/").filter(Boolean).map((p, i) => i === 0 ? p : p.charAt(0).toUpperCase() + p.slice(1)).join("").replaceAll(/-([a-z])/giu, (_, c) => c.toUpperCase());
1463
+ return camel ? `${method}${camel.charAt(0).toUpperCase()}${camel.slice(1)}` : method;
1464
+ }
1465
+ function resolveOperationId(operation, method, path) {
1466
+ const id = operation.operationId;
1467
+ return typeof id === "string" && id.length > 0 ? id : makeOperationId(method, path);
1468
+ }
1469
+ function lookup$1(ref, section, table) {
1470
+ const m = new RegExp(`^#/components/${section}/(.+)$`, "u").exec(ref);
1471
+ return m?.[1] && table ? table[decodeURIComponent(m[1])] : void 0;
1472
+ }
1473
+ function resolveParameter$1(p, components) {
1474
+ if (isReference(p) && p.$ref) return lookup$1(p.$ref, "parameters", components?.parameters);
1475
+ return "name" in p && "in" in p ? p : void 0;
1476
+ }
1477
+ function resolveRequestBody(b, components) {
1478
+ if (!b) return void 0;
1479
+ if (isReference(b) && b.$ref) return lookup$1(b.$ref, "requestBodies", components?.requestBodies);
1480
+ return b;
1481
+ }
1482
+ function resolveResponse(r, components) {
1483
+ if (isReference(r) && r.$ref) return lookup$1(r.$ref, "responses", components?.responses) ?? {};
1484
+ return r;
1485
+ }
1486
+ function resolveOperation(operation, components) {
1487
+ return {
1488
+ ...operation,
1489
+ parameters: (operation.parameters ?? []).map((p) => resolveParameter$1(p, components)).filter((p) => p !== void 0),
1490
+ requestBody: resolveRequestBody(operation.requestBody, components),
1491
+ responses: Object.fromEntries(Object.entries(operation.responses ?? {}).map(([status, res]) => [status, resolveResponse(res, components)]))
1492
+ };
1493
+ }
1494
+ const HTTP_METHODS = [
1495
+ "get",
1496
+ "put",
1497
+ "post",
1498
+ "delete",
1499
+ "options",
1500
+ "head",
1501
+ "patch"
1502
+ ];
1503
+ function makeRoute(method, path, operation) {
1504
+ const operationId = resolveOperationId(operation, method, path);
1505
+ const tags = operation.tags ?? [];
1506
+ const params = makeParamsSchema(operation.parameters ?? [], "path");
1507
+ const query = makeParamsSchema(operation.parameters ?? [], "query");
1508
+ const headers = makeParamsSchema(operation.parameters ?? [], "header");
1509
+ const cookie = makeParamsSchema(operation.parameters ?? [], "cookie");
1510
+ const body = bodyInfo(operation);
1511
+ const synth = (kind) => `${toSafeIdentifier(operationId)}${kind}`;
1512
+ const responsesAndInline = Object.entries(operation.responses ?? {}).map(([status, res]) => {
1513
+ const info = responseInfo(res);
1514
+ if (info.ref) return { entry: {
1515
+ status,
1516
+ schema: {
1517
+ kind: "ref",
1518
+ name: info.ref
1519
+ }
1520
+ } };
1521
+ if (info.inline) {
1522
+ const name = synth(`Response${status}`);
1523
+ return {
1524
+ entry: {
1525
+ status,
1526
+ schema: {
1527
+ kind: "ref",
1528
+ name
1529
+ }
1530
+ },
1531
+ inline: {
1532
+ name,
1533
+ schema: info.inline
1534
+ }
1535
+ };
1536
+ }
1537
+ return { entry: {
1538
+ status,
1539
+ schema: { kind: "void" }
1540
+ } };
1541
+ });
1542
+ const inlineSchemas = [
1543
+ ...params ? [{
1544
+ name: synth("Params"),
1545
+ schema: params.schema,
1546
+ ctx: params.ctx
1547
+ }] : [],
1548
+ ...query ? [{
1549
+ name: synth("Query"),
1550
+ schema: query.schema,
1551
+ ctx: query.ctx
1552
+ }] : [],
1553
+ ...headers ? [{
1554
+ name: synth("Headers"),
1555
+ schema: headers.schema,
1556
+ ctx: headers.ctx
1557
+ }] : [],
1558
+ ...cookie ? [{
1559
+ name: synth("Cookie"),
1560
+ schema: cookie.schema,
1561
+ ctx: cookie.ctx
1562
+ }] : [],
1563
+ ...body.inline ? [{
1564
+ name: synth("Body"),
1565
+ schema: body.inline,
1566
+ ctx: body.ctx
1567
+ }] : [],
1568
+ ...responsesAndInline.flatMap((item) => "inline" in item && item.inline ? [item.inline] : [])
1569
+ ];
1570
+ const security = isSecurityArray(operation.security) ? operation.security : [];
1571
+ return {
1572
+ route: {
1573
+ method,
1574
+ path,
1575
+ operationId,
1576
+ summary: operation.summary,
1577
+ description: operation.description,
1578
+ tags,
1579
+ paramsRef: params ? synth("Params") : void 0,
1580
+ queryRef: query ? synth("Query") : void 0,
1581
+ headersRef: headers ? synth("Headers") : void 0,
1582
+ cookieRef: cookie ? synth("Cookie") : void 0,
1583
+ bodyRef: body.ref ?? (body.inline ? synth("Body") : void 0),
1584
+ responses: responsesAndInline.map((item) => item.entry),
1585
+ security,
1586
+ callbacks: operation.callbacks && Object.keys(operation.callbacks).length > 0 ? operation.callbacks : void 0
1587
+ },
1588
+ inlineSchemas
1589
+ };
1590
+ }
1591
+ function resolvePathItem(pathItem, api) {
1592
+ if (!pathItem.$ref) return pathItem;
1593
+ const m = /^#\/components\/pathItems\/(.+)$/u.exec(pathItem.$ref);
1594
+ return (m?.[1] ? api.components?.pathItems?.[decodeURIComponent(m[1])] : void 0) ?? pathItem;
1595
+ }
1596
+ function walk(path, resource, pathItem, api, canonical) {
1597
+ const resolved = resolvePathItem(pathItem, api);
1598
+ const { path: normalizedPath, renames } = normalizePath(path, canonical);
1599
+ return HTTP_METHODS.flatMap((method) => {
1600
+ const operation = resolved[method];
1601
+ if (!operation) return [];
1602
+ return [[resource, makeRoute(method, normalizedPath, renameOperationParams(resolveOperation(operation, api.components), renames))]];
1603
+ });
1604
+ }
1605
+ function operationsByResource(api) {
1606
+ const canonical = canonicalParamNames([...Object.keys(api.paths), ...Object.keys(api.webhooks ?? {}).map((event) => `/webhooks/${event}`)]);
1607
+ const grouped = /* @__PURE__ */ new Map();
1608
+ const collect = (resource, item) => {
1609
+ const bucket = grouped.get(resource) ?? [];
1610
+ bucket.push(item);
1611
+ grouped.set(resource, bucket);
1612
+ };
1613
+ for (const [path, pathItem] of pathEntries(api)) {
1614
+ if (!pathItem) continue;
1615
+ for (const [resource, route] of walk(path, resourceName(path), pathItem, api, canonical)) collect(resource, route);
1616
+ }
1617
+ for (const [event, pathItem] of Object.entries(api.webhooks ?? {})) {
1618
+ if (!pathItem) continue;
1619
+ for (const [resource, route] of walk(`/webhooks/${event}`, "webhooks", pathItem, api, canonical)) collect(resource, route);
1620
+ }
1621
+ return grouped;
1622
+ }
1623
+ //#endregion
1624
+ //#region src/core/components/output.ts
1625
+ /**
1626
+ * Emits every component kind into a single file (`components.output` mode).
1627
+ * Cross-component references resolve within the same file — `makeImports`
1628
+ * skips them since they are locally-defined consts — so it only injects the
1629
+ * elysia / typebox runtime imports the combined body needs.
1630
+ */
1631
+ function components(section, output, readonly) {
1632
+ return Effect.gen(function* () {
1633
+ if (!section) return "No components found";
1634
+ const blocks = [
1635
+ schemasCode(section.schemas, readonly),
1636
+ responsesCode(section.responses, readonly),
1637
+ parametersCode(section.parameters, readonly),
1638
+ examplesCode(section.examples),
1639
+ requestBodiesCode(section.requestBodies, readonly),
1640
+ headersCode(section.headers, readonly),
1641
+ securitySchemesCode(section.securitySchemes),
1642
+ linksCode(section.links),
1643
+ callbacksCode(section.callbacks),
1644
+ pathItemsCode(section.pathItems),
1645
+ mediaTypesCode(section.mediaTypes, readonly)
1646
+ ].filter((block) => block !== "");
1647
+ if (blocks.length === 0) return "No components found";
1648
+ const abs = path.resolve(process.cwd(), output);
1649
+ yield* emit(makeImports(blocks.join("\n\n"), abs, void 0, false), path.dirname(abs), abs);
1650
+ return `Generated components code written to ${output}`;
1651
+ });
1652
+ }
1653
+ //#endregion
1654
+ //#region src/error/index.ts
1655
+ /**
1656
+ * A generator refused the work it was given — an empty section, an output path that does not fit
1657
+ * the mode, a document that says nothing to write.
1658
+ *
1659
+ * Distinct from `PlatformError` (the filesystem said no) and `FormatError` (oxfmt rejected the
1660
+ * source): this one is always the caller's input, and the CLI renders its message as-is.
1661
+ */
1662
+ var GenerateError = class extends Data.TaggedError("GenerateError") {};
1663
+ //#endregion
1664
+ //#region src/core/components/schemas.ts
1665
+ function fileNameOf(group) {
1666
+ return uncapitalize(group.map((g) => g.name).toSorted()[0] ?? "schema");
1667
+ }
1668
+ function splitSchemaFiles(section, readonly, exportTypes) {
1669
+ const sccs = sccSchemas(Object.entries(section).map(([name, schema]) => ({
1670
+ name,
1671
+ schema
1672
+ })));
1673
+ const nameToFile = new Map(sccs.flatMap((g) => g.map((s) => [s.name, fileNameOf(g)])));
1674
+ return sccs.map((group) => {
1675
+ const fileName = fileNameOf(group);
1676
+ const body = schemasCode(Object.fromEntries(group.map((g) => [g.name, g.schema])), readonly, exportTypes);
1677
+ if (body === "") return {
1678
+ fileName,
1679
+ code: ""
1680
+ };
1681
+ const memberNames = new Set(group.map((g) => g.name));
1682
+ const bySibling = /* @__PURE__ */ new Map();
1683
+ for (const { schema } of group) for (const refName of collectSchemaRefs$1(schema)) {
1684
+ const sibling = nameToFile.get(refName);
1685
+ if (!sibling || sibling === fileName || memberNames.has(refName)) continue;
1686
+ const bucket = bySibling.get(sibling) ?? /* @__PURE__ */ new Set();
1687
+ bucket.add(`${pascalCase(refName)}Schema`);
1688
+ bySibling.set(sibling, bucket);
1689
+ }
1690
+ const siblingImports = [...bySibling.entries()].toSorted(([a], [b]) => a.localeCompare(b)).map(([sibling, idents]) => `import {${[...idents].toSorted().join(",")}} from './${sibling}'`).join("\n");
1691
+ return {
1692
+ fileName,
1693
+ code: siblingImports ? `${siblingImports}\n\n${body}` : body
1694
+ };
1695
+ });
1696
+ }
1697
+ function schemas(section, output, split, exportTypes, components, readonly) {
1698
+ return Effect.gen(function* () {
1699
+ if (!section) return yield* new GenerateError({ message: "No schemas found" });
1700
+ if (Object.keys(section).length === 0) return "No schemas found";
1701
+ const abs = path.resolve(process.cwd(), output);
1702
+ if (split) {
1703
+ const exclude = new Set(["schemas"]);
1704
+ const outDir = path.join(path.dirname(abs), path.basename(abs, ".ts"));
1705
+ const files = splitSchemaFiles(section, readonly, exportTypes);
1706
+ const barrelLines = files.map((f) => `export * from './${f.fileName}'`).toSorted();
1707
+ yield* Effect.all([...files.map((f) => {
1708
+ const filePath = path.join(outDir, `${f.fileName}.ts`);
1709
+ return emit(f.code === "" ? "" : makeImports(f.code, filePath, components, true, exclude), path.dirname(filePath), filePath);
1710
+ }), emit(`${barrelLines.join("\n")}\n`, outDir, path.join(outDir, "index.ts"))], { concurrency: "unbounded" });
1711
+ return `Generated schemas code written to ${outDir}/*.ts (index.ts included)`;
1712
+ }
1713
+ const code = schemasCode(section, readonly, exportTypes);
1714
+ if (code === "") return "No schemas found";
1715
+ yield* emit(makeImports(code, abs, components, false), path.dirname(abs), abs);
1716
+ return `Generated schemas code written to ${output}`;
1717
+ });
1718
+ }
1719
+ //#endregion
1720
+ //#region src/core/components/responses.ts
1721
+ function responses(section, output, split, exportTypes, components, readonly) {
1722
+ return Effect.gen(function* () {
1723
+ if (!section) return yield* new GenerateError({ message: "No responses found" });
1724
+ const entries = Object.entries(section);
1725
+ if (entries.length === 0) return "No responses found";
1726
+ if (split) {
1727
+ const outDir = path.join(path.dirname(output), path.basename(output, ".ts"));
1728
+ yield* Effect.all([...entries.map(([name, value]) => {
1729
+ const code = responsesCode({ [name]: value }, readonly, exportTypes);
1730
+ const filePath = path.join(outDir, `${uncapitalize(name)}.ts`);
1731
+ return emit(makeImports(code, filePath, components, split), path.dirname(filePath), filePath);
1732
+ }), emit(makeBarrel(section), outDir, path.join(outDir, "index.ts"))], { concurrency: "unbounded" });
1733
+ return `Generated responses code written to ${outDir}/*.ts (index.ts included)`;
1734
+ }
1735
+ yield* emit(makeImports(responsesCode(section, readonly, exportTypes), output, components, split), path.dirname(output), output);
1736
+ return `Generated responses code written to ${output}`;
1737
+ });
1738
+ }
1739
+ //#endregion
1740
+ //#region src/core/components/parameters.ts
1741
+ function parameters(section, output, split, exportTypes, components, readonly) {
1742
+ return Effect.gen(function* () {
1743
+ if (!section) return yield* new GenerateError({ message: "No parameters found" });
1744
+ const entries = Object.entries(section);
1745
+ if (entries.length === 0) return "No parameters found";
1746
+ if (split) {
1747
+ const outDir = path.join(path.dirname(output), path.basename(output, ".ts"));
1748
+ yield* Effect.all([...entries.map(([name, value]) => {
1749
+ const code = parametersCode({ [name]: value }, readonly, exportTypes);
1750
+ const filePath = path.join(outDir, `${uncapitalize(name)}.ts`);
1751
+ return emit(makeImports(code, filePath, components, split), path.dirname(filePath), filePath);
1752
+ }), emit(makeBarrel(section), outDir, path.join(outDir, "index.ts"))], { concurrency: "unbounded" });
1753
+ return `Generated parameters code written to ${outDir}/*.ts (index.ts included)`;
1754
+ }
1755
+ yield* emit(makeImports(parametersCode(section, readonly, exportTypes), output, components, split), path.dirname(output), output);
1756
+ return `Generated parameters code written to ${output}`;
1757
+ });
1758
+ }
1759
+ //#endregion
1760
+ //#region src/core/components/examples.ts
1761
+ function examples(section, output, split, components) {
1762
+ return Effect.gen(function* () {
1763
+ if (!section) return yield* new GenerateError({ message: "No examples found" });
1764
+ const entries = Object.entries(section);
1765
+ if (entries.length === 0) return "No examples found";
1766
+ if (split) {
1767
+ const outDir = path.join(path.dirname(output), path.basename(output, ".ts"));
1768
+ yield* Effect.all([...entries.map(([name, value]) => {
1769
+ const code = examplesCode({ [name]: value });
1770
+ const filePath = path.join(outDir, `${uncapitalize(name)}.ts`);
1771
+ return emit(makeImports(code, filePath, components, split), path.dirname(filePath), filePath);
1772
+ }), emit(makeBarrel(section), outDir, path.join(outDir, "index.ts"))], { concurrency: "unbounded" });
1773
+ return `Generated examples code written to ${outDir}/*.ts (index.ts included)`;
1774
+ }
1775
+ yield* emit(makeImports(examplesCode(section), output, components, split), path.dirname(output), output);
1776
+ return `Generated examples code written to ${output}`;
1777
+ });
1778
+ }
1779
+ //#endregion
1780
+ //#region src/core/components/requestBodies.ts
1781
+ function requestBodies(section, output, split, exportTypes, components, readonly) {
1782
+ return Effect.gen(function* () {
1783
+ if (!section) return yield* new GenerateError({ message: "No requestBodies found" });
1784
+ const entries = Object.entries(section);
1785
+ if (entries.length === 0) return "No requestBodies found";
1786
+ if (split) {
1787
+ const outDir = path.join(path.dirname(output), path.basename(output, ".ts"));
1788
+ yield* Effect.all([...entries.map(([name, value]) => {
1789
+ const code = requestBodiesCode({ [name]: value }, readonly, exportTypes);
1790
+ const filePath = path.join(outDir, `${uncapitalize(name)}.ts`);
1791
+ return emit(makeImports(code, filePath, components, split), path.dirname(filePath), filePath);
1792
+ }), emit(makeBarrel(section), outDir, path.join(outDir, "index.ts"))], { concurrency: "unbounded" });
1793
+ return `Generated requestBodies code written to ${outDir}/*.ts (index.ts included)`;
1794
+ }
1795
+ yield* emit(makeImports(requestBodiesCode(section, readonly, exportTypes), output, components, split), path.dirname(output), output);
1796
+ return `Generated requestBodies code written to ${output}`;
1797
+ });
1798
+ }
1799
+ //#endregion
1800
+ //#region src/core/components/headers.ts
1801
+ function headers(section, output, split, exportTypes, components, readonly) {
1802
+ return Effect.gen(function* () {
1803
+ if (!section) return yield* new GenerateError({ message: "No headers found" });
1804
+ const entries = Object.entries(section);
1805
+ if (entries.length === 0) return "No headers found";
1806
+ if (split) {
1807
+ const outDir = path.join(path.dirname(output), path.basename(output, ".ts"));
1808
+ yield* Effect.all([...entries.map(([name, value]) => {
1809
+ const code = headersCode({ [name]: value }, readonly, exportTypes);
1810
+ const filePath = path.join(outDir, `${uncapitalize(name)}.ts`);
1811
+ return emit(makeImports(code, filePath, components, split), path.dirname(filePath), filePath);
1812
+ }), emit(makeBarrel(section), outDir, path.join(outDir, "index.ts"))], { concurrency: "unbounded" });
1813
+ return `Generated headers code written to ${outDir}/*.ts (index.ts included)`;
1814
+ }
1815
+ yield* emit(makeImports(headersCode(section, readonly, exportTypes), output, components, split), path.dirname(output), output);
1816
+ return `Generated headers code written to ${output}`;
1817
+ });
1818
+ }
1819
+ //#endregion
1820
+ //#region src/core/components/securitySchemes.ts
1821
+ function securitySchemes(section, output, split, components) {
1822
+ return Effect.gen(function* () {
1823
+ if (!section) return yield* new GenerateError({ message: "No securitySchemes found" });
1824
+ const entries = Object.entries(section);
1825
+ if (entries.length === 0) return "No securitySchemes found";
1826
+ if (split) {
1827
+ const outDir = path.join(path.dirname(output), path.basename(output, ".ts"));
1828
+ yield* Effect.all([...entries.map(([name, value]) => {
1829
+ const code = securitySchemesCode({ [name]: value });
1830
+ const filePath = path.join(outDir, `${uncapitalize(name)}.ts`);
1831
+ return emit(makeImports(code, filePath, components, split), path.dirname(filePath), filePath);
1832
+ }), emit(makeBarrel(section), outDir, path.join(outDir, "index.ts"))], { concurrency: "unbounded" });
1833
+ return `Generated securitySchemes code written to ${outDir}/*.ts (index.ts included)`;
1834
+ }
1835
+ yield* emit(makeImports(securitySchemesCode(section), output, components, split), path.dirname(output), output);
1836
+ return `Generated securitySchemes code written to ${output}`;
1837
+ });
1838
+ }
1839
+ //#endregion
1840
+ //#region src/core/components/links.ts
1841
+ function links(section, output, split, components) {
1842
+ return Effect.gen(function* () {
1843
+ if (!section) return yield* new GenerateError({ message: "No links found" });
1844
+ const entries = Object.entries(section);
1845
+ if (entries.length === 0) return "No links found";
1846
+ if (split) {
1847
+ const outDir = path.join(path.dirname(output), path.basename(output, ".ts"));
1848
+ yield* Effect.all([...entries.map(([name, value]) => {
1849
+ const code = linksCode({ [name]: value });
1850
+ const filePath = path.join(outDir, `${uncapitalize(name)}.ts`);
1851
+ return emit(makeImports(code, filePath, components, split), path.dirname(filePath), filePath);
1852
+ }), emit(makeBarrel(section), outDir, path.join(outDir, "index.ts"))], { concurrency: "unbounded" });
1853
+ return `Generated links code written to ${outDir}/*.ts (index.ts included)`;
1854
+ }
1855
+ yield* emit(makeImports(linksCode(section), output, components, split), path.dirname(output), output);
1856
+ return `Generated links code written to ${output}`;
1857
+ });
1858
+ }
1859
+ //#endregion
1860
+ //#region src/core/components/callbacks.ts
1861
+ function callbacks(section, output, split, components) {
1862
+ return Effect.gen(function* () {
1863
+ if (!section) return yield* new GenerateError({ message: "No callbacks found" });
1864
+ const entries = Object.entries(section);
1865
+ if (entries.length === 0) return "No callbacks found";
1866
+ if (split) {
1867
+ const outDir = path.join(path.dirname(output), path.basename(output, ".ts"));
1868
+ yield* Effect.all([...entries.map(([name, value]) => {
1869
+ const code = callbacksCode({ [name]: value });
1870
+ const filePath = path.join(outDir, `${uncapitalize(name)}.ts`);
1871
+ return emit(makeImports(code, filePath, components, split), path.dirname(filePath), filePath);
1872
+ }), emit(makeBarrel(section), outDir, path.join(outDir, "index.ts"))], { concurrency: "unbounded" });
1873
+ return `Generated callbacks code written to ${outDir}/*.ts (index.ts included)`;
1874
+ }
1875
+ yield* emit(makeImports(callbacksCode(section), output, components, split), path.dirname(output), output);
1876
+ return `Generated callbacks code written to ${output}`;
1877
+ });
1878
+ }
1879
+ //#endregion
1880
+ //#region src/core/components/pathItems.ts
1881
+ function pathItems(section, output, split, components) {
1882
+ return Effect.gen(function* () {
1883
+ if (!section) return yield* new GenerateError({ message: "No pathItems found" });
1884
+ const entries = Object.entries(section);
1885
+ if (entries.length === 0) return "No pathItems found";
1886
+ if (split) {
1887
+ const outDir = path.join(path.dirname(output), path.basename(output, ".ts"));
1888
+ yield* Effect.all([...entries.map(([name, value]) => {
1889
+ const code = pathItemsCode({ [name]: value });
1890
+ const filePath = path.join(outDir, `${uncapitalize(name)}.ts`);
1891
+ return emit(makeImports(code, filePath, components, split), path.dirname(filePath), filePath);
1892
+ }), emit(makeBarrel(section), outDir, path.join(outDir, "index.ts"))], { concurrency: "unbounded" });
1893
+ return `Generated pathItems code written to ${outDir}/*.ts (index.ts included)`;
1894
+ }
1895
+ yield* emit(makeImports(pathItemsCode(section), output, components, split), path.dirname(output), output);
1896
+ return `Generated pathItems code written to ${output}`;
1897
+ });
1898
+ }
1899
+ //#endregion
1900
+ //#region src/core/components/mediaTypes.ts
1901
+ function mediaTypes(section, output, split, exportTypes, components, readonly) {
1902
+ return Effect.gen(function* () {
1903
+ if (!section) return yield* new GenerateError({ message: "No mediaTypes found" });
1904
+ const entries = Object.entries(section);
1905
+ if (entries.length === 0) return "No mediaTypes found";
1906
+ if (split) {
1907
+ const outDir = path.join(path.dirname(output), path.basename(output, ".ts"));
1908
+ yield* Effect.all([...entries.map(([name, value]) => {
1909
+ const code = mediaTypesCode({ [name]: value }, readonly, exportTypes);
1910
+ const filePath = path.join(outDir, `${uncapitalize(name)}.ts`);
1911
+ return emit(makeImports(code, filePath, components, split), path.dirname(filePath), filePath);
1912
+ }), emit(makeBarrel(section), outDir, path.join(outDir, "index.ts"))], { concurrency: "unbounded" });
1913
+ return `Generated mediaTypes code written to ${outDir}/*.ts (index.ts included)`;
1914
+ }
1915
+ yield* emit(makeImports(mediaTypesCode(section, readonly, exportTypes), output, components, split), path.dirname(output), output);
1916
+ return `Generated mediaTypes code written to ${output}`;
1917
+ });
1918
+ }
1919
+ //#endregion
1920
+ //#region src/core/eden/index.ts
1921
+ function makeJsDocs(method, pathStr, operation) {
1922
+ const blocks = [];
1923
+ if (operation.summary) blocks.push([operation.summary]);
1924
+ if (operation.description) blocks.push(operation.description.split("\n"));
1925
+ blocks.push([`${method.toUpperCase()} ${pathStr}`]);
1926
+ const tagLines = [];
1927
+ if (operation.deprecated) tagLines.push("@deprecated");
1928
+ if (tagLines.length > 0) blocks.push(tagLines);
1929
+ return `/**\n${blocks.map((lines) => lines.map((l) => l === "" ? " *" : ` * ${l}`).join("\n")).join("\n *\n")}\n */`;
1930
+ }
1931
+ function makeOperation$1(pathStr, method, operation, client, docs) {
1932
+ const funcName = toSafeIdentifier(resolveOperationId(operation, method, pathStr));
1933
+ const { callExpr, methodHostTypeExpr, paramArgs } = edenChain(pathStr, client, method);
1934
+ const fn = `export async function ${funcName}(${[...paramArgs.map((p) => `${p.name}: ${p.typeExpr}`), `...args: Parameters<${methodHostTypeExpr}>`].join(", ")}) {\n return ${callExpr}(...args)\n}`;
1935
+ return docs ? `${makeJsDocs(method, pathStr, operation)}\n${fn}` : fn;
1936
+ }
1937
+ function eden(openAPI, output, importPath, client, basePath, docs = false) {
1938
+ return Effect.gen(function* () {
1939
+ const prefix = basePath && basePath !== "/" ? basePath : "";
1940
+ const operations = [];
1941
+ for (const [pathStr, pathItem] of pathEntries(openAPI)) {
1942
+ if (!pathItem) continue;
1943
+ for (const method of HTTP_METHODS) {
1944
+ const operation = pathItem[method];
1945
+ if (!operation) continue;
1946
+ operations.push(makeOperation$1(`${prefix}${pathStr}`, method, operation, client, docs));
1947
+ }
1948
+ }
1949
+ if (operations.length === 0) return "No operations found";
1950
+ yield* emit(`${`import { ${client} } from '${importPath}'\n\n`}${`${operations.join("\n\n")}\n`}`, path.dirname(output), output);
1951
+ return `Generated eden code written to ${output}`;
1952
+ });
1953
+ }
1954
+ //#endregion
1955
+ //#region src/generator/app/index.ts
1956
+ function exportName(name) {
1957
+ return toSafeIdentifier(name);
1958
+ }
1959
+ function appFile(resources, { prefix, integration, port = "3000" } = {}) {
1960
+ const ROOT_NAME = "app";
1961
+ const localAlias = (name) => exportName(name) === ROOT_NAME ? `${ROOT_NAME}Module` : exportName(name);
1962
+ const moduleImports = resources.map((name) => {
1963
+ const exported = exportName(name);
1964
+ const alias = localAlias(name);
1965
+ return alias === exported ? `import {${exported}} from './modules/${name}'` : `import {${exported} as ${alias}} from './modules/${name}'`;
1966
+ }).join("\n");
1967
+ const moduleUses = resources.map((name) => `.use(${localAlias(name)})`).join("");
1968
+ return `import {Elysia} from 'elysia'
1969
+ ${moduleImports}
1970
+
1971
+ export const app=new Elysia(${prefix ? `{prefix:${JSON.stringify(prefix)}}` : ""})${moduleUses}${integration ? "" : `
1972
+
1973
+ if (import.meta.main) {
1974
+ app.listen(${port})\n console.log(\`🦊 Elysia is running at \${app.server?.hostname}:\${app.server?.port}\`)
1975
+ }`}
1976
+ `;
1977
+ }
1978
+ //#endregion
1979
+ //#region src/generator/controller/index.ts
1980
+ function schemaIdent(name) {
1981
+ return `${pascalCase(name)}Schema`;
1982
+ }
1983
+ function join(entries, sep) {
1984
+ return filterDefined(entries).join(sep);
1985
+ }
1986
+ function toElysiaPath(p) {
1987
+ return p.replaceAll(/\{([^}]+)\}/gu, ":$1");
1988
+ }
1989
+ function componentKey(name) {
1990
+ return `'${pascalCase(name)}'`;
1991
+ }
1992
+ function controllerFile(tag, routes, componentImports = [], componentNames = /* @__PURE__ */ new Set(), schemasImportPath = "../../components") {
1993
+ const className = `${pascalCase(tag)}Model`;
1994
+ const isComponent = (name) => componentNames.has(name);
1995
+ const inlineRef = (name) => `${className}.${name}`;
1996
+ const refValue = (name) => isComponent(name) ? componentKey(name) : inlineRef(name);
1997
+ const renderRoute = (route) => {
1998
+ const renderResponseValue = (schema) => schema.kind === "ref" ? refValue(schema.name) : "t.Void()";
1999
+ const argKeys = filterDefined([
2000
+ route.paramsRef ? "params" : null,
2001
+ route.queryRef ? "query" : null,
2002
+ route.headersRef ? "headers" : null,
2003
+ route.cookieRef ? "cookie" : null,
2004
+ route.bodyRef ? "body" : null
2005
+ ]);
2006
+ const handler = `${argKeys.length > 0 ? `({${argKeys.join(",")}})` : "()"}=>{}`;
2007
+ const responseEntries = route.responses.map((res) => `${safeStatusKey(res.status)}:${renderResponseValue(res.schema)}`).join(",");
2008
+ const detail = join([
2009
+ `tags:${JSON.stringify(route.tags)}`,
2010
+ route.summary ? `summary:${JSON.stringify(route.summary)}` : null,
2011
+ route.description ? `description:${JSON.stringify(route.description)}` : null,
2012
+ `operationId:${JSON.stringify(route.operationId)}`,
2013
+ route.callbacks ? `callbacks:${JSON.stringify(route.callbacks)}` : null,
2014
+ route.security.length > 0 ? `security:${JSON.stringify(route.security)}` : null
2015
+ ], ",");
2016
+ const opts = join([
2017
+ route.paramsRef ? `params:${refValue(route.paramsRef)}` : null,
2018
+ route.queryRef ? `query:${refValue(route.queryRef)}` : null,
2019
+ route.headersRef ? `headers:${refValue(route.headersRef)}` : null,
2020
+ route.cookieRef ? `cookie:${refValue(route.cookieRef)}` : null,
2021
+ route.bodyRef ? `body:${refValue(route.bodyRef)}` : null,
2022
+ responseEntries ? `response:{${responseEntries}}` : null,
2023
+ `detail:{${detail}}`
2024
+ ], ",");
2025
+ return `.${route.method}(${JSON.stringify(toElysiaPath(route.path))},${handler},{${opts}})`;
2026
+ };
2027
+ const chain = routes.map(renderRoute).join("");
2028
+ const importedSchemaIdents = componentImports.map(schemaIdent);
2029
+ const componentImport = componentImports.length > 0 ? `import {${importedSchemaIdents.join(",")}} from '${schemasImportPath}'\n` : "";
2030
+ const componentRegister = componentImports.length > 0 ? `.model({${componentImports.map((n) => `${pascalCase(n)}:${schemaIdent(n)}`).join(",")}})` : "";
2031
+ return `${/\bt\.[A-Z]\w*\(/u.test(chain) ? `import {Elysia,t} from 'elysia'` : `import {Elysia} from 'elysia'`}
2032
+ ${componentImport}import {${className}} from './model'
2033
+
2034
+ export const ${toSafeIdentifier(tag)}=new Elysia()${componentRegister}${chain}
2035
+ `;
2036
+ }
2037
+ //#endregion
2038
+ //#region src/generator/model/index.ts
2039
+ function modelFile(tag, schemas, componentNames = /* @__PURE__ */ new Set(), schemasImportPath = "../../components", readonlyMode) {
2040
+ const className = `${pascalCase(tag)}Model`;
2041
+ const referenced = /* @__PURE__ */ new Set();
2042
+ for (const { schema } of schemas) for (const r of collectSchemaRefs$1(schema)) if (componentNames.has(r)) referenced.add(r);
2043
+ const importedIdents = [...referenced].toSorted().map((n) => `${pascalCase(n)}Schema`);
2044
+ const componentImport = importedIdents.length > 0 ? `import {${importedIdents.join(",")}} from '${schemasImportPath}'\n` : "";
2045
+ const entries = schemas.map(({ name, schema, ctx }) => `${name}:${readonly(typebox(schema, ctx), readonlyMode)}`).join(",");
2046
+ if (entries === "") return `${componentImport}
2047
+ export const ${className}={} as const
2048
+
2049
+ export type ${className}=typeof ${className}
2050
+ `;
2051
+ return `import {t,type UnwrapSchema} from 'elysia'
2052
+ ${componentImport}
2053
+ export const ${className}={${entries}} as const
2054
+
2055
+ export type ${className}={[k in keyof typeof ${className}]:UnwrapSchema<(typeof ${className})[k]>}
2056
+ `;
2057
+ }
2058
+ //#endregion
2059
+ //#region src/generator/service/index.ts
2060
+ function serviceFile(tag) {
2061
+ return `export abstract class ${pascalCase(tag)}{}
2062
+ `;
2063
+ }
2064
+ //#endregion
2065
+ //#region src/merge/statement-key.ts
2066
+ function statementKey(stmt) {
2067
+ if (Node.isClassDeclaration(stmt)) {
2068
+ const name = stmt.getName();
2069
+ return name ? `class:${name}` : null;
2070
+ }
2071
+ if (Node.isFunctionDeclaration(stmt)) {
2072
+ const name = stmt.getName();
2073
+ return name ? `fn:${name}` : null;
2074
+ }
2075
+ if (Node.isVariableStatement(stmt)) {
2076
+ const first = stmt.getDeclarations()[0];
2077
+ return first ? `var:${first.getName()}` : null;
2078
+ }
2079
+ if (Node.isTypeAliasDeclaration(stmt)) {
2080
+ const name = stmt.getName();
2081
+ return name ? `type:${name}` : null;
2082
+ }
2083
+ if (Node.isInterfaceDeclaration(stmt)) {
2084
+ const name = stmt.getName();
2085
+ return name ? `interface:${name}` : null;
2086
+ }
2087
+ if (Node.isIfStatement(stmt)) return `if:${stmt.getExpression().getText().trim()}`;
2088
+ if (Node.isExpressionStatement(stmt)) {
2089
+ const expr = stmt.getExpression();
2090
+ if (Node.isCallExpression(expr)) {
2091
+ const callee = expr.getExpression();
2092
+ if (Node.isIdentifier(callee) && callee.getText() === "describe") {
2093
+ const first = expr.getArguments()[0];
2094
+ if (first && Node.isStringLiteral(first)) return `describe:${first.getLiteralText()}`;
2095
+ }
2096
+ const argSig = expr.getArguments().map((a) => a.getText().trim().replace(/;$/u, "")).join(",");
2097
+ return `expr:${callee.getText().trim()}(${argSig})`;
2098
+ }
2099
+ }
2100
+ return null;
2101
+ }
2102
+ //#endregion
2103
+ //#region src/merge/index.ts
2104
+ function stmtText(s) {
2105
+ if (Node.isJSDocable(s)) {
2106
+ const docs = s.getJsDocs().map((d) => d.getText()).join("\n");
2107
+ if (docs.length > 0) return `${docs}\n${s.getText()}`;
2108
+ }
2109
+ return s.getText();
2110
+ }
2111
+ function collectNames(imp) {
2112
+ const names = [];
2113
+ const def = imp.getDefaultImport();
2114
+ if (def) names.push(def.getText());
2115
+ const ns = imp.getNamespaceImport();
2116
+ if (ns) names.push(ns.getText());
2117
+ for (const b of imp.getNamedImports()) names.push(b.getNameNode().getText());
2118
+ return names;
2119
+ }
2120
+ function mergeSource(existing, generated) {
2121
+ const project = new Project({ useInMemoryFileSystem: true });
2122
+ const existingFile = project.createSourceFile("existing.ts", existing);
2123
+ const generatedFile = project.createSourceFile("generated.ts", generated);
2124
+ const claimedByGenerated = /* @__PURE__ */ new Set();
2125
+ for (const imp of generatedFile.getImportDeclarations()) for (const n of collectNames(imp)) claimedByGenerated.add(n);
2126
+ const importBySpec = /* @__PURE__ */ new Map();
2127
+ for (const imp of generatedFile.getImportDeclarations()) importBySpec.set(imp.getModuleSpecifierValue(), imp.getText());
2128
+ for (const imp of existingFile.getImportDeclarations()) {
2129
+ const spec = imp.getModuleSpecifierValue();
2130
+ if (importBySpec.has(spec)) {
2131
+ importBySpec.set(spec, imp.getText());
2132
+ continue;
2133
+ }
2134
+ const named = imp.getNamedImports();
2135
+ const def = imp.getDefaultImport();
2136
+ const ns = imp.getNamespaceImport();
2137
+ const survivingNamed = named.filter((b) => !claimedByGenerated.has(b.getNameNode().getText()));
2138
+ const defClaimed = def ? claimedByGenerated.has(def.getText()) : false;
2139
+ const nsClaimed = ns ? claimedByGenerated.has(ns.getText()) : false;
2140
+ if (survivingNamed.length === named.length && !defClaimed && !nsClaimed) {
2141
+ importBySpec.set(spec, imp.getText());
2142
+ continue;
2143
+ }
2144
+ const clauseParts = [
2145
+ def && !defClaimed ? def.getText() : "",
2146
+ ns && !nsClaimed ? `* as ${ns.getText()}` : "",
2147
+ survivingNamed.length > 0 ? `{${survivingNamed.map((b) => b.getText()).join(",")}}` : ""
2148
+ ].filter(Boolean);
2149
+ if (clauseParts.length === 0) continue;
2150
+ importBySpec.set(spec, `import ${clauseParts.join(",")} from '${spec}'`);
2151
+ }
2152
+ const existingStmts = existingFile.getStatements().filter((s) => !Node.isImportDeclaration(s));
2153
+ const generatedStmts = generatedFile.getStatements().filter((s) => !Node.isImportDeclaration(s));
2154
+ const existingByKey = /* @__PURE__ */ new Map();
2155
+ for (const s of existingStmts) {
2156
+ const k = statementKey(s);
2157
+ if (k) existingByKey.set(k, s);
2158
+ }
2159
+ const usedExistingKeys = /* @__PURE__ */ new Set();
2160
+ const isFirstEmit = existingStmts.length === 0;
2161
+ const fromGenerated = generatedStmts.flatMap((s) => {
2162
+ const k = statementKey(s);
2163
+ if (k && existingByKey.has(k)) {
2164
+ usedExistingKeys.add(k);
2165
+ const existingStmt = existingByKey.get(k);
2166
+ return [existingStmt ? stmtText(existingStmt) : stmtText(s)];
2167
+ }
2168
+ if (!isFirstEmit && k?.startsWith("expr:")) return [];
2169
+ return [stmtText(s)];
2170
+ });
2171
+ const fromExistingOnly = existingStmts.filter((s) => {
2172
+ const k = statementKey(s);
2173
+ return k ? !usedExistingKeys.has(k) : true;
2174
+ }).map(stmtText);
2175
+ return [
2176
+ [...importBySpec.values()].join("\n"),
2177
+ "",
2178
+ [...fromGenerated, ...fromExistingOnly].join("\n\n"),
2179
+ ""
2180
+ ].join("\n");
2181
+ }
2182
+ //#endregion
2183
+ //#region src/core/elysia/index.ts
2184
+ /** Keeps whatever the user already wrote in `output` when regenerating over it. */
2185
+ function merged(code, output) {
2186
+ return Effect.gen(function* () {
2187
+ const existing = yield* readFile(output);
2188
+ return existing === null ? code : mergeSource(existing, code);
2189
+ });
2190
+ }
2191
+ function elysia(api, options = {}) {
2192
+ return Effect.gen(function* () {
2193
+ const byResource = operationsByResource(api);
2194
+ const resources = [...byResource.keys()];
2195
+ if (resources.length === 0) return yield* new GenerateError({ message: "no operations found in OpenAPI document" });
2196
+ const componentSchemas = (api.components ?? {}).schemas ?? {};
2197
+ const componentNames = new Set(Object.keys(componentSchemas));
2198
+ const appOutput = options.output ?? "src/index.ts";
2199
+ const appAbs = path.resolve(process.cwd(), appOutput);
2200
+ const baseDir = path.dirname(appAbs);
2201
+ const modulesDir = path.join(baseDir, "modules");
2202
+ const schemasImportFromModule = (() => {
2203
+ const fromFile = path.join(modulesDir, "_", "index.ts");
2204
+ if (options.componentsOutput) return makeModuleSpec(fromFile, {
2205
+ output: path.resolve(process.cwd(), options.componentsOutput),
2206
+ split: false
2207
+ });
2208
+ const cfg = options.components?.schemas;
2209
+ if (cfg?.import) return cfg.import;
2210
+ if (cfg?.output) return makeModuleSpec(fromFile, {
2211
+ output: path.resolve(process.cwd(), cfg.output),
2212
+ split: cfg.split
2213
+ });
2214
+ return options.pathAlias ? `${options.pathAlias}/schemas` : "../../components/schemas";
2215
+ })();
2216
+ function writeResource(resource) {
2217
+ return Effect.gen(function* () {
2218
+ const items = byResource.get(resource) ?? [];
2219
+ const routes = items.map((i) => i.route);
2220
+ const inline = items.flatMap((i) => i.inlineSchemas);
2221
+ const inlineSchemas = moduleInlineSchemas(inline);
2222
+ const refs = [...moduleComponentRefs(routes, inline, componentNames, componentSchemas)].toSorted();
2223
+ const dir = path.join(modulesDir, resource);
2224
+ const servicePath = path.join(dir, "service.ts");
2225
+ const controllerPath = path.join(dir, "index.ts");
2226
+ const [serviceCode, controllerCode] = yield* Effect.all([merged(serviceFile(resource), servicePath), merged(controllerFile(resource, routes, refs, componentNames, schemasImportFromModule), controllerPath)], { concurrency: "unbounded" });
2227
+ return [
2228
+ emit(modelFile(resource, inlineSchemas, componentNames, schemasImportFromModule, options.readonly), dir, path.join(dir, "model.ts")),
2229
+ emit(serviceCode, dir, servicePath),
2230
+ emit(controllerCode, dir, controllerPath)
2231
+ ];
2232
+ });
2233
+ }
2234
+ const appCode = yield* merged(appFile(resources, {
2235
+ prefix: options.prefix,
2236
+ integration: options.integration,
2237
+ port: options.port
2238
+ }), appAbs);
2239
+ const moduleWrites = yield* Effect.all(resources.map(writeResource), { concurrency: "unbounded" });
2240
+ yield* Effect.all([emit(appCode, baseDir, appAbs), ...moduleWrites.flat()], { concurrency: "unbounded" });
2241
+ return `Generated ${resources.length} module(s) (${resources.join(", ")}) → ${path.posix.dirname(appOutput)}/`;
2242
+ });
2243
+ }
2244
+ //#endregion
2245
+ //#region src/helper/query.ts
2246
+ function optionsObjectType(config, optionsType) {
2247
+ return config.unwrapOptionsAccessor ? `ReturnType<${optionsType}>` : optionsType;
2248
+ }
2249
+ const QUERY_OMIT_KEYS = `'queryKey' | 'queryFn'`;
2250
+ const INFINITE_OMIT_KEYS = `'queryKey' | 'queryFn' | 'initialPageParam' | 'getNextPageParam'`;
2251
+ function omitInjectedKeys(config, optionsType, keys) {
2252
+ return `Omit<${config.maybeRefOptions ? `Extract<${optionsType}, { queryKey: unknown }>` : optionsObjectType(config, optionsType)}, ${keys}>`;
2253
+ }
2254
+ function makeGetQueryOptionsParam(config, dataT, keyName) {
2255
+ const optType = config.queryOptionsType;
2256
+ const omit = (optionsType) => omitInjectedKeys(config, optionsType, QUERY_OMIT_KEYS);
2257
+ if (config.useQueryGenerics) return `queryOptions?: ${omit(`${optType}<${dataT}, TError, TData>`)}`;
2258
+ if (config.maybeRefOptions) return `queryOptions?: ${omit(`${optType}<${dataT}, TError, TData, ${dataT}, ReturnType<typeof ${keyName}>>`)}`;
2259
+ if (config.thunkOptionsCall) return `queryOptions?: () => ${omit(`${optType}<${dataT}, TError, TData, ReturnType<typeof ${keyName}>>`)}`;
2260
+ return `queryOptions?: ${omit(`${optType}<${dataT}, TError, TData>`)}`;
2261
+ }
2262
+ function makeMutationOptionsParam(config, dataT, errorT, variablesType) {
2263
+ const optType = `${config.mutationOptionsType}<${dataT}, ${errorT}, ${variablesType}>`;
2264
+ return config.thunkOptionsCall ? `mutationOptions?: () => ${optionsObjectType(config, optType)}` : `mutationOptions?: ${optType}`;
2265
+ }
2266
+ function makeGetHookBody(config, dataT, keyCall, queryFnBlock) {
2267
+ const inner = `${config.thunkOptionsCall ? "...queryOptions?.()" : "...queryOptions"},queryKey:${keyCall},${queryFnBlock}`;
2268
+ if (config.useQueryGenerics) return `${config.queryFn}<${dataT},TError,TData>({${inner}})`;
2269
+ if (config.useThunk) return `${config.queryFn}(()=>({${inner}}))`;
2270
+ return `${config.queryFn}({${inner}})`;
2271
+ }
2272
+ function makeMutationFactoryCode(name, keySig, keyCall, dataT, errorT, variablesType, mutationFnBlock) {
2273
+ return `export function ${name}<TError = ${errorT}>(${keySig}){return mutationOptions<${dataT},TError,${variablesType}>({mutationKey:${keyCall},${mutationFnBlock}})}`;
2274
+ }
2275
+ function makeMutationHookBody(config, dataT, variablesType, factoryCall) {
2276
+ const inner = `${config.thunkOptionsCall ? "...mutationOptions?.()" : "...mutationOptions"},...${factoryCall}`;
2277
+ if (config.useQueryGenerics) return `${config.mutationFn}<${dataT},TError,${variablesType}>({${inner}})`;
2278
+ if (config.useThunk) return `${config.mutationFn}(()=>({${inner}}))`;
2279
+ return `${config.mutationFn}({${inner}})`;
2280
+ }
2281
+ function makeTanstackInfiniteParts(config, args) {
2282
+ const { funcName, hookName, suspenseHookName, keyPrefix, pathStr } = args;
2283
+ const { paramSig, paramPass, optionsType, dataT, errorT, callExpr } = args;
2284
+ const infiniteKeyName = `${funcName}InfiniteQueryKey`;
2285
+ const infiniteKeyCode = `export function ${infiniteKeyName}(${[...paramSig, `options?: ${optionsType}`].join(", ")}){const{headers:_h,fetch:_f,throwHttpError:_t,...keyArgs}=options??{};return[${[
2286
+ `'${keyPrefix}'`,
2287
+ `'${pathStr}'`,
2288
+ `'infinite'`,
2289
+ ...paramPass,
2290
+ "keyArgs"
2291
+ ].join(", ")}]as const}`;
2292
+ const infiniteKeyCall = `${infiniteKeyName}(${[...paramPass, "options"].join(", ")})`;
2293
+ const infiniteHookName = `${hookName}Infinite`;
2294
+ const suspenseInfiniteHookName = `${suspenseHookName}Infinite`;
2295
+ const queryKeyType = `ReturnType<typeof ${infiniteKeyName}>`;
2296
+ const paginationParam = `pagination: { initialPageParam: TPageParam; getNextPageParam: (lastPage: ${dataT}, allPages: ${dataT}[], lastPageParam: TPageParam, allPageParams: TPageParam[]) => TPageParam | undefined | null; buildInit: (pageParam: unknown) => ${optionsType} }`;
2297
+ const infiniteSig = [
2298
+ ...paramSig,
2299
+ `options: ${optionsType} | undefined`,
2300
+ paginationParam
2301
+ ].join(", ");
2302
+ const infiniteQueryFnBody = `queryFn:async({pageParam,signal})=>{const overlay=pagination.buildInit(pageParam);const{data,error}=await ${callExpr}({...options,...overlay,query:{...options?.query,...overlay?.query},headers:{...options?.headers,...overlay?.headers},fetch:{...options?.fetch,...overlay?.fetch,signal}});if(error)throw error;return data},`;
2303
+ const infiniteOptionsCode = `export function ${`${funcName}InfiniteQueryOptions`}<TPageParam>(${infiniteSig}){return infiniteQueryOptions<${dataT},${errorT},InfiniteData<${dataT},TPageParam>,${queryKeyType},TPageParam>({queryKey:${infiniteKeyCall},${infiniteQueryFnBody}initialPageParam:pagination.initialPageParam,getNextPageParam:pagination.getNextPageParam})}`;
2304
+ const infiniteGenerics = `<TPageParam = unknown, TData = InfiniteData<${dataT}, TPageParam>, TError = ${errorT}>`;
2305
+ const hookQueryFnBody = `queryFn:async({pageParam,signal}:QueryFunctionContext<${queryKeyType},TPageParam>)=>{const overlay=pagination.buildInit(pageParam);const{data,error}=await ${callExpr}({...options,...overlay,query:{...options?.query,...overlay?.query},headers:{...options?.headers,...overlay?.headers},fetch:{...options?.fetch,...overlay?.fetch,signal}});if(error)throw error;return data},`;
2306
+ const inner = `${config.thunkOptionsCall ? "...queryOptions?.()" : "...queryOptions"},queryKey:${infiniteKeyCall},${hookQueryFnBody}initialPageParam:pagination.initialPageParam,getNextPageParam:pagination.getNextPageParam`;
2307
+ const hookBody = (queryFn) => config.useThunk ? `${queryFn}(()=>({${inner}}))` : `${queryFn}({${inner}})`;
2308
+ const hookSig = (libOptionsType) => {
2309
+ const fullType = omitInjectedKeys(config, `${libOptionsType}<${dataT}, TError, TData, ${queryKeyType}, TPageParam>`, INFINITE_OMIT_KEYS);
2310
+ return [infiniteSig, config.thunkOptionsCall ? `queryOptions?: () => ${fullType}` : `queryOptions?: ${fullType}`].join(", ");
2311
+ };
2312
+ const infiniteCode = `export function ${infiniteHookName}${infiniteGenerics}(${hookSig(config.infiniteOptionsType)}){return ${hookBody(config.infiniteQueryFn)}}`;
2313
+ if (!config.suspenseInfiniteQueryFn) return [
2314
+ infiniteKeyCode,
2315
+ infiniteOptionsCode,
2316
+ infiniteCode
2317
+ ];
2318
+ return [
2319
+ infiniteKeyCode,
2320
+ infiniteOptionsCode,
2321
+ infiniteCode,
2322
+ `export function ${suspenseInfiniteHookName}${infiniteGenerics}(${hookSig(config.suspenseInfiniteOptionsType)}){return ${hookBody(config.suspenseInfiniteQueryFn)}}`
2323
+ ];
2324
+ }
2325
+ function makeInfiniteParts(config, args) {
2326
+ if (config.hasInfiniteQueryOptionsHelper) return makeTanstackInfiniteParts(config, args);
2327
+ const { funcName, hookName, keyPrefix, pathStr } = args;
2328
+ const { paramSig, paramPass, optionsType, dataT, errorT, callExpr } = args;
2329
+ const infiniteKeyName = `${funcName}InfiniteQueryKey`;
2330
+ const infiniteKeyCode = `export function ${infiniteKeyName}(${[...paramSig, `options?: ${optionsType}`].join(", ")}){const{headers:_h,fetch:_f,throwHttpError:_t,...keyArgs}=options??{};return[${[
2331
+ `'${keyPrefix}'`,
2332
+ `'${pathStr}'`,
2333
+ `'infinite'`,
2334
+ ...paramPass,
2335
+ "keyArgs"
2336
+ ].join(", ")}]as const}`;
2337
+ const infiniteKeyCall = `${infiniteKeyName}(${[...paramPass, "options"].join(", ")})`;
2338
+ const infiniteHookName = `${hookName}Infinite`;
2339
+ const infiniteGenerics = `<TPageParam = unknown, TData = InfiniteData<${dataT}, TPageParam>, TError = ${errorT}>`;
2340
+ const infiniteQueryFnBlock = `queryFn:async({pageParam,signal}:QueryFunctionContext)=>{const overlay=pagination.buildInit(pageParam);const{data,error}=await ${callExpr}({...options,...overlay,query:{...options?.query,...overlay?.query},headers:{...options?.headers,...overlay?.headers},fetch:{...options?.fetch,...overlay?.fetch,signal}});if(error)throw error;return data},`;
2341
+ const fullOptionsType = `${config.infiniteOptionsType}<${dataT}, TError, TData, ReturnType<typeof ${infiniteKeyName}>, TPageParam>`;
2342
+ const infiniteHookSig = [
2343
+ ...paramSig,
2344
+ `options: ${optionsType} | undefined`,
2345
+ `pagination: { buildInit: (pageParam: unknown) => ${optionsType} }`,
2346
+ `queryOptions: ${omitInjectedKeys(config, fullOptionsType, QUERY_OMIT_KEYS)}`
2347
+ ].join(", ");
2348
+ const inner = `...queryOptions,queryKey:${infiniteKeyCall},${infiniteQueryFnBlock}`;
2349
+ return [infiniteKeyCode, `export function ${infiniteHookName}${infiniteGenerics}(${infiniteHookSig}){return ${config.infiniteQueryFn}({${inner}})}`];
2350
+ }
2351
+ function makeSwrOperation(config, args) {
2352
+ const { hookName, immutableHookName, keyName, keyPrefix, pathStr, method } = args;
2353
+ const { paramSig, paramPass } = args;
2354
+ const { argsType, optionsType, dataT, errorT, callExpr } = args;
2355
+ const { isQuery, isBodyMethod, isPaginated, hasKeyArgs, optMark } = args;
2356
+ if (isQuery) {
2357
+ const keySig = (hasKeyArgs ? [...paramSig, `options?: ${optionsType}`] : paramSig).join(", ");
2358
+ const keyTuple = [
2359
+ `'${keyPrefix}'`,
2360
+ `'${pathStr}'`,
2361
+ ...paramPass,
2362
+ ...hasKeyArgs ? ["keyArgs"] : []
2363
+ ].join(", ");
2364
+ const keyCode = hasKeyArgs ? `export function ${keyName}(${keySig}){const{headers:_h,fetch:_f,throwHttpError:_t,...keyArgs}=options??{};return[${keyTuple}]as const}` : `export function ${keyName}(${keySig}){return[${keyTuple}]as const}`;
2365
+ const keyCall = `${keyName}(${[...paramPass, ...hasKeyArgs ? ["options"] : []].join(", ")})`;
2366
+ const generics = `<TError = ${errorT}>`;
2367
+ const hookSig = [
2368
+ ...paramSig,
2369
+ `options${optMark}: ${optionsType}`,
2370
+ `config?: SWRConfiguration<${dataT}, TError>`
2371
+ ].join(", ");
2372
+ const swrHook = (name, swrFn) => `export function ${name}${generics}(${hookSig}){return ${swrFn}<${dataT},TError>(${keyCall},async()=>{const{data,error}=await ${callExpr}(options);if(error)throw error;return data},config)}`;
2373
+ const hookCode = config.immutableQueryFn ? `${swrHook(hookName, config.queryFn)}\n\n${swrHook(immutableHookName, config.immutableQueryFn)}` : swrHook(hookName, config.queryFn);
2374
+ if (isPaginated) {
2375
+ const infiniteHookName = `${hookName}Infinite`;
2376
+ const infiniteHookSig = [
2377
+ ...paramSig,
2378
+ `buildInit: (pageIndex: number, previousPage: ${dataT} | null) => ${optionsType} | null`,
2379
+ `config?: SWRInfiniteConfiguration<${dataT}, TError>`
2380
+ ].join(", ");
2381
+ const infiniteKeyTuple = [
2382
+ `'${keyPrefix}'`,
2383
+ `'${pathStr}'`,
2384
+ `'infinite'`,
2385
+ "pageIndex",
2386
+ ...paramPass,
2387
+ "keyArgs"
2388
+ ].join(", ");
2389
+ const fetcherDestructure = [
2390
+ "_resource",
2391
+ "_opId",
2392
+ "_infinite",
2393
+ "_pageIndex",
2394
+ ...paramPass.map((_, i) => `_key${i}`),
2395
+ "keyArgs"
2396
+ ].join(", ");
2397
+ return `${keyCode}\n\n${hookCode}\n\n${`export function ${infiniteHookName}${generics}(${infiniteHookSig}){${`const getKey=(pageIndex:number,previousPage:${dataT}|null)=>{const options=buildInit(pageIndex,previousPage);if(options===null)return null;const{headers:_h,fetch:_f,throwHttpError:_t,...keyArgs}=options??{};return[${infiniteKeyTuple}]as const}`};return useSWRInfinite<${dataT},TError,typeof getKey>(getKey,async([${fetcherDestructure}])=>{const{data,error}=await ${callExpr}(keyArgs);if(error)throw error;return data},config)}`}`;
2398
+ }
2399
+ return `${keyCode}\n\n${hookCode}`;
2400
+ }
2401
+ const keyCode = `export function ${keyName}(${paramSig.join(", ")}){return ${`[${[
2402
+ `'${keyPrefix}'`,
2403
+ `'${pathStr}'`,
2404
+ `'${method.toUpperCase()}'`,
2405
+ ...paramPass
2406
+ ].join(", ")}] as const`}}`;
2407
+ const keyCall = `${keyName}(${paramPass.join(", ")})`;
2408
+ const variablesType = isBodyMethod ? `{ body: ${argsType}[0]; options${optMark}: ${argsType}[1] }` : `{ options${optMark}: ${argsType}[0] }`;
2409
+ const triggerCallExpr = isBodyMethod ? `${callExpr}(arg.body, arg.options)` : `${callExpr}(arg.options)`;
2410
+ return `${keyCode}\n\n${`export function ${hookName}${`<TError = ${errorT}>`}(${[...paramSig, `config?: SWRMutationConfiguration<${dataT}, TError, ReturnType<typeof ${keyName}>, ${variablesType}>`].join(", ")}){return useSWRMutation<${dataT},TError,ReturnType<typeof ${keyName}>,${variablesType}>(${keyCall},${`async(_key:ReturnType<typeof ${keyName}>,{arg}:{arg:${variablesType}})=>{const{data,error}=await ${triggerCallExpr};if(error)throw error;return data}`},config)}`}`;
2411
+ }
2412
+ function needsFetchHook(pathStr) {
2413
+ return pathStr.split("/").filter(Boolean).some((seg) => seg.includes(".") || seg.includes("{") && !/^\{[^}]+\}$/u.test(seg));
2414
+ }
2415
+ function pathParamNames(pathStr) {
2416
+ return [...pathStr.matchAll(/\{([^}]+)\}/gu)].map((m) => m[1] ?? "");
2417
+ }
2418
+ function successType(operation) {
2419
+ const ok = Object.entries(operation.responses ?? {}).find(([status]) => status.startsWith("2"));
2420
+ if (!ok) return { dataT: "void" };
2421
+ const info = responseInfo(ok[1]);
2422
+ if (info.ref) return {
2423
+ dataT: pascalCase(info.ref),
2424
+ importName: pascalCase(info.ref)
2425
+ };
2426
+ if (info.void) return { dataT: "void" };
2427
+ return { dataT: "unknown" };
2428
+ }
2429
+ function fetchTypeImport(pathStr, operation) {
2430
+ return needsFetchHook(pathStr) ? successType(operation).importName : void 0;
2431
+ }
2432
+ function makeFetchOperation(pathStr, method, operation, config) {
2433
+ const funcName = toSafeIdentifier(resolveOperationId(operation, method, pathStr));
2434
+ const Op = capitalize(funcName);
2435
+ const hookName = `${config.hookPrefix}${Op}`;
2436
+ const suspenseHookName = `${config.hookPrefix}Suspense${Op}`;
2437
+ const immutableHookName = `${config.hookPrefix}Immutable${Op}`;
2438
+ const keyPrefix = resourcePrefix(pathStr);
2439
+ const isQuery = method === "get" || method === "head";
2440
+ const params = pathParamNames(pathStr);
2441
+ const paramSig = params.map((p) => `${p}: string`);
2442
+ const paramPass = [...params];
2443
+ const hasQuery = (operation.parameters ?? []).some((p) => "in" in p && p.in === "query");
2444
+ const optionsType = hasQuery ? "{ headers?: Record<string, string>; query?: Record<string, unknown> }" : "{ headers?: Record<string, string> }";
2445
+ const { dataT } = successType(operation);
2446
+ const errorT = "unknown";
2447
+ const urlInner = pathStr.replaceAll(/\{([^}]+)\}/gu, (_, n) => `\${encodeURIComponent(${n})}`);
2448
+ const urlExpr = hasQuery ? `\`${urlInner}\${search.size ? \`?\${search}\` : ''}\`` : `\`${urlInner}\``;
2449
+ const searchCode = (opt) => hasQuery ? `const search=new URLSearchParams();for(const[k,v]of Object.entries(${opt}?.query??{})){if(v===undefined)continue;if(Array.isArray(v)){for(const x of v){search.append(k,String(x))}}else{search.set(k,String(v))}}` : "";
2450
+ const getBody = (returnAnnotation, opt, signal) => `${searchCode(opt)}const res=await fetch(${urlExpr},{headers:${opt}?.headers${signal ? ",signal" : ""}});if(!res.ok)throw await res.json();return JSON.parse(await res.text())`;
2451
+ const keyArgsDecl = hasQuery ? "const{headers:_h,...keyArgs}=options??{};" : "";
2452
+ const keyTuple = [
2453
+ `'${keyPrefix}'`,
2454
+ `'${pathStr}'`,
2455
+ ...paramPass,
2456
+ ...hasQuery ? ["keyArgs"] : []
2457
+ ].join(", ");
2458
+ if (config.isSWR && isQuery) {
2459
+ const keyName = `${funcName}QueryKey`;
2460
+ const keyCode = `export function ${keyName}(${[...paramSig, `options?: ${optionsType}`].join(", ")}){${keyArgsDecl}return[${keyTuple}]as const}`;
2461
+ const keyCall = `${keyName}(${[...paramPass, "options"].join(", ")})`;
2462
+ const fetcher = `async():Promise<${dataT}>=>{${getBody(dataT, "options", false)}}`;
2463
+ const hookSig = [
2464
+ ...paramSig,
2465
+ `options?: ${optionsType}`,
2466
+ `config?: SWRConfiguration<${dataT}, TError>`
2467
+ ].join(", ");
2468
+ const swrHook = (name, swrFn) => `export function ${name}<TError=${errorT}>(${hookSig}){return ${swrFn}<${dataT},TError>(${keyCall},${fetcher},config)}`;
2469
+ return `${keyCode}\n\n${config.immutableQueryFn ? `${swrHook(hookName, config.queryFn)}\n\n${swrHook(immutableHookName, config.immutableQueryFn)}` : swrHook(hookName, config.queryFn)}`;
2470
+ }
2471
+ const mutationBody = (opt, body) => `${searchCode(opt)}const res=await fetch(${urlExpr},{method:'${method.toUpperCase()}',headers:{'content-type':'application/json',...${opt}?.headers},body:${body}===undefined?undefined:JSON.stringify(${body})});if(!res.ok)throw await res.json();return JSON.parse(await res.text())`;
2472
+ const variablesType = `{ body?: unknown; options?: ${optionsType} }`;
2473
+ if (config.isSWR) {
2474
+ const keyName = `${funcName}MutationKey`;
2475
+ const keyExpr = `[${[
2476
+ `'${keyPrefix}'`,
2477
+ `'${pathStr}'`,
2478
+ `'${method.toUpperCase()}'`,
2479
+ ...paramPass
2480
+ ].join(", ")}] as const`;
2481
+ const keyCode = `export function ${keyName}(${paramSig.join(", ")}){return ${keyExpr}}`;
2482
+ const keyCall = `${keyName}(${paramPass.join(", ")})`;
2483
+ const triggerCb = `async(_key:ReturnType<typeof ${keyName}>,{arg}:{arg:${variablesType}}):Promise<${dataT}>=>{${mutationBody("arg.options", "arg.body")}}`;
2484
+ return `${keyCode}\n\n${`export function ${hookName}<TError=${errorT}>(${[...paramSig, `config?: SWRMutationConfiguration<${dataT}, TError, ReturnType<typeof ${keyName}>, ${variablesType}>`].join(", ")}){return useSWRMutation<${dataT},TError,ReturnType<typeof ${keyName}>,${variablesType}>(${keyCall},${triggerCb},config)}`}`;
2485
+ }
2486
+ if (isQuery) {
2487
+ const keyName = `${funcName}QueryKey`;
2488
+ const keyCode = `export function ${keyName}(${[...paramSig, `options?: ${optionsType}`].join(", ")}){${keyArgsDecl}return[${keyTuple}]as const}`;
2489
+ const keyCall = `${keyName}(${[...paramPass, "options"].join(", ")})`;
2490
+ const queryFnBlock = `queryFn:async(${config.queryFnContext ? "{signal}:QueryFunctionContext" : "{signal}"}):Promise<${dataT}>=>{${getBody(dataT, "options", true)}},`;
2491
+ const queryOptionsCode = `export function ${`${funcName}QueryOptions`}(${[...paramSig, `options?: ${optionsType}`].join(", ")}){return queryOptions({queryKey:${keyCall},queryFn:async({signal}):Promise<${dataT}>=>{${getBody(dataT, "options", true)}}})}`;
2492
+ const generics = `<TData = ${dataT}, TError = ${errorT}>`;
2493
+ const queryOptionsParam = makeGetQueryOptionsParam(config, dataT, keyName);
2494
+ const parts = [
2495
+ keyCode,
2496
+ queryOptionsCode,
2497
+ `export function ${hookName}${generics}(${[
2498
+ ...paramSig,
2499
+ `options?: ${optionsType}`,
2500
+ queryOptionsParam
2501
+ ].join(", ")}){return ${makeGetHookBody(config, dataT, keyCall, queryFnBlock)}}`
2502
+ ];
2503
+ if (config.suspenseQueryFn) {
2504
+ const suspenseHookCode = `export function ${suspenseHookName}${generics}(${[
2505
+ ...paramSig,
2506
+ `options?: ${optionsType}`,
2507
+ `queryOptions?: ${omitInjectedKeys(config, `${config.suspenseQueryOptionsType}<${dataT}, TError, TData>`, QUERY_OMIT_KEYS)}`
2508
+ ].join(", ")}){return ${config.suspenseQueryFn}<${dataT},TError,TData>({...queryOptions,queryKey:${keyCall},${queryFnBlock}})}`;
2509
+ parts.push(suspenseHookCode);
2510
+ }
2511
+ return parts.join("\n\n");
2512
+ }
2513
+ const keyName = `${funcName}MutationKey`;
2514
+ const keyExpr = `[${[
2515
+ `'${keyPrefix}'`,
2516
+ `'${pathStr}'`,
2517
+ `'${method.toUpperCase()}'`,
2518
+ ...paramPass
2519
+ ].join(", ")}] as const`;
2520
+ const keyCode = `export function ${keyName}(${paramSig.join(", ")}){return ${keyExpr}}`;
2521
+ const keyCall = `${keyName}(${paramPass.join(", ")})`;
2522
+ const mutationFnBlock = `mutationFn:async({body,options}):Promise<${dataT}>=>{${mutationBody("options", "body")}},`;
2523
+ const mutationOptionsName = `${funcName}MutationOptions`;
2524
+ const factoryCode = makeMutationFactoryCode(mutationOptionsName, paramSig.join(", "), keyCall, dataT, errorT, variablesType, mutationFnBlock);
2525
+ const factoryCall = `${mutationOptionsName}<TError>(${paramPass.join(", ")})`;
2526
+ const mutationOptionsParam = makeMutationOptionsParam(config, dataT, "TError", variablesType);
2527
+ return `${keyCode}\n\n${factoryCode}\n\n${`export function ${hookName}<TError=${errorT}>(${[...paramSig, mutationOptionsParam].join(", ")}){return ${makeMutationHookBody(config, dataT, variablesType, factoryCall)}}`}`;
2528
+ }
2529
+ function makeOperation(pathStr, method, operation, client, config) {
2530
+ if (needsFetchHook(pathStr)) return makeFetchOperation(pathStr, method, operation, config);
2531
+ const funcName = toSafeIdentifier(resolveOperationId(operation, method, pathStr));
2532
+ const Op = capitalize(funcName);
2533
+ const hookName = `${config.hookPrefix}${Op}`;
2534
+ const suspenseHookName = `${config.hookPrefix}Suspense${Op}`;
2535
+ const immutableHookName = `${config.hookPrefix}Immutable${Op}`;
2536
+ const isQuery = method === "get" || method === "head";
2537
+ const keyName = `${funcName}${isQuery ? "QueryKey" : "MutationKey"}`;
2538
+ const keyPrefix = resourcePrefix(pathStr);
2539
+ const { callExpr, methodHostTypeExpr, paramArgs } = edenChain(pathStr, client, method);
2540
+ const paramSig = paramArgs.map((p) => `${p.name}: ${p.typeExpr}`);
2541
+ const paramPass = paramArgs.map((p) => p.name);
2542
+ const argsType = `Parameters<${methodHostTypeExpr}>`;
2543
+ const optionsType = `${argsType}[0]`;
2544
+ const response = `Awaited<ReturnType<${methodHostTypeExpr}>>`;
2545
+ const dataT = `Extract<${response}, { error: null }>['data']`;
2546
+ const errorT = `Exclude<${response}['error'], null>`;
2547
+ const isBodyMethod = method === "post" || method === "put" || method === "patch" || method === "delete";
2548
+ const isPaginated = operation["x-pagination"] === true;
2549
+ const hasKeyArgs = (operation.parameters ?? []).some((p) => "in" in p ? p.in === "query" : true);
2550
+ const requiredOptions = (operation.parameters ?? []).some((p) => "in" in p && (p.in === "query" || p.in === "header") && p.required === true);
2551
+ const optMark = requiredOptions ? "" : "?";
2552
+ const optFetch = requiredOptions ? "options.fetch" : "options?.fetch";
2553
+ if (config.isSWR) return makeSwrOperation(config, {
2554
+ hookName,
2555
+ immutableHookName,
2556
+ keyName,
2557
+ keyPrefix,
2558
+ pathStr,
2559
+ method,
2560
+ paramSig,
2561
+ paramPass,
2562
+ argsType,
2563
+ optionsType,
2564
+ dataT,
2565
+ errorT,
2566
+ callExpr,
2567
+ isQuery,
2568
+ isBodyMethod,
2569
+ isPaginated,
2570
+ hasKeyArgs,
2571
+ optMark
2572
+ });
2573
+ if (isQuery) {
2574
+ const keySig = (hasKeyArgs ? [...paramSig, `options?: ${optionsType}`] : paramSig).join(", ");
2575
+ const keyTuple = [
2576
+ `'${keyPrefix}'`,
2577
+ `'${pathStr}'`,
2578
+ ...paramPass,
2579
+ ...hasKeyArgs ? ["keyArgs"] : []
2580
+ ].join(", ");
2581
+ const keyCode = hasKeyArgs ? `export function ${keyName}(${keySig}){const{headers:_h,fetch:_f,throwHttpError:_t,...keyArgs}=options??{};return[${keyTuple}]as const}` : `export function ${keyName}(${keySig}){return[${keyTuple}]as const}`;
2582
+ const keyCall = `${keyName}(${[...paramPass, ...hasKeyArgs ? ["options"] : []].join(", ")})`;
2583
+ const queryOptionsCode = `export function ${`${funcName}QueryOptions`}(${[...paramSig, `options${optMark}: ${optionsType}`].join(", ")}){return queryOptions({queryKey:${keyCall},queryFn:async({signal})=>{const{data,error}=await ${callExpr}({...options,fetch:{...${optFetch},signal}});if(error)throw error;return data}})}`;
2584
+ const queryFnBlock = `queryFn:async(${config.queryFnContext ? "{signal}:QueryFunctionContext" : "{signal}"})=>{const{data,error}=await ${callExpr}({...options,fetch:{...${optFetch},signal}});if(error)throw error;return data},`;
2585
+ const generics = `<TData = ${dataT}, TError = ${errorT}>`;
2586
+ const queryOptionsParam = makeGetQueryOptionsParam(config, dataT, keyName);
2587
+ const parts = [
2588
+ keyCode,
2589
+ queryOptionsCode,
2590
+ `export function ${hookName}${generics}(${[
2591
+ ...paramSig,
2592
+ `options${optMark}: ${optionsType}`,
2593
+ queryOptionsParam
2594
+ ].join(", ")}){return ${makeGetHookBody(config, dataT, keyCall, queryFnBlock)}}`
2595
+ ];
2596
+ if (config.suspenseQueryFn) {
2597
+ const suspenseHookCode = `export function ${suspenseHookName}${generics}(${[
2598
+ ...paramSig,
2599
+ `options${optMark}: ${optionsType}`,
2600
+ `queryOptions?: ${omitInjectedKeys(config, `${config.suspenseQueryOptionsType}<${dataT}, TError, TData>`, QUERY_OMIT_KEYS)}`
2601
+ ].join(", ")}){return ${config.suspenseQueryFn}<${dataT},TError,TData>({...queryOptions,queryKey:${keyCall},${queryFnBlock}})}`;
2602
+ parts.push(suspenseHookCode);
2603
+ }
2604
+ if (isPaginated) parts.push(...makeInfiniteParts(config, {
2605
+ funcName,
2606
+ hookName,
2607
+ suspenseHookName,
2608
+ keyPrefix,
2609
+ pathStr,
2610
+ paramSig,
2611
+ paramPass,
2612
+ optionsType,
2613
+ dataT,
2614
+ errorT,
2615
+ callExpr
2616
+ }));
2617
+ return parts.join("\n\n");
2618
+ }
2619
+ const keySig = paramSig.join(", ");
2620
+ const keyCode = `export function ${keyName}(${keySig}){return ${`[${[
2621
+ `'${keyPrefix}'`,
2622
+ `'${pathStr}'`,
2623
+ `'${method.toUpperCase()}'`,
2624
+ ...paramPass
2625
+ ].join(", ")}] as const`}}`;
2626
+ const keyCall = `${keyName}(${paramPass.join(", ")})`;
2627
+ const variablesType = isBodyMethod ? `{ body: ${argsType}[0]; options${optMark}: ${argsType}[1] }` : `{ options${optMark}: ${argsType}[0] }`;
2628
+ const mutationFnBlock = isBodyMethod ? `mutationFn:async({body,options})=>{const{data,error}=await ${callExpr}(body,options);if(error)throw error;return data},` : `mutationFn:async({options})=>{const{data,error}=await ${callExpr}(options);if(error)throw error;return data},`;
2629
+ const mutationOptionsName = `${funcName}MutationOptions`;
2630
+ const factoryCode = makeMutationFactoryCode(mutationOptionsName, keySig, keyCall, dataT, errorT, variablesType, mutationFnBlock);
2631
+ const factoryCall = `${mutationOptionsName}<TError>(${paramPass.join(", ")})`;
2632
+ const mutationGenerics = `<TError = ${errorT}>`;
2633
+ const mutationOptionsParam = makeMutationOptionsParam(config, dataT, "TError", variablesType);
2634
+ return `${keyCode}\n\n${factoryCode}\n\n${`export function ${hookName}${mutationGenerics}(${[...paramSig, mutationOptionsParam].join(", ")}){return ${makeMutationHookBody(config, dataT, variablesType, factoryCall)}}`}`;
2635
+ }
2636
+ function makeSwrHeader(config, client, importPath, deps) {
2637
+ return `${deps.isQuery ? `import useSWR from 'swr'\nimport type { SWRConfiguration } from 'swr'\n` : ""}${deps.isQuery && config.immutableQueryFn ? `import ${config.immutableQueryFn} from 'swr/immutable'\n` : ""}${deps.isInfinite ? `import useSWRInfinite from 'swr/infinite'\nimport type { SWRInfiniteConfiguration } from 'swr/infinite'\n` : ""}${deps.isMutation ? `import useSWRMutation from 'swr/mutation'\nimport type { SWRMutationConfiguration } from 'swr/mutation'\n` : ""}import { ${client} } from '${importPath}'\n\n`;
2638
+ }
2639
+ function makeHeader(config, client, importPath, deps) {
2640
+ if (config.isSWR) return makeSwrHeader(config, client, importPath, deps);
2641
+ const pkg = config.packageName;
2642
+ const suspenseValue = config.suspenseQueryFn ? `, ${config.suspenseQueryFn}` : "";
2643
+ const suspenseType = config.suspenseQueryOptionsType ? `, ${config.suspenseQueryOptionsType}` : "";
2644
+ const suspenseInfiniteValue = config.suspenseInfiniteQueryFn ? `, ${config.suspenseInfiniteQueryFn}` : "";
2645
+ const suspenseInfiniteType = config.suspenseInfiniteOptionsType ? `, ${config.suspenseInfiniteOptionsType}` : "";
2646
+ return `${deps.isQuery ? `import { ${config.queryFn}${suspenseValue}, queryOptions } from '${pkg}'\n` : ""}${deps.isQuery ? `import type { ${config.queryOptionsType}${suspenseType} } from '${pkg}'\n` : ""}${deps.isInfinite ? config.hasInfiniteQueryOptionsHelper ? `import { ${config.infiniteQueryFn}${suspenseInfiniteValue}, infiniteQueryOptions } from '${pkg}'\n` : `import { ${config.infiniteQueryFn}${suspenseInfiniteValue} } from '${pkg}'\n` : ""}${deps.isInfinite ? `import type { ${config.infiniteOptionsType}, InfiniteData${suspenseInfiniteType} } from '${pkg}'\n` : ""}${deps.isMutation ? `import { ${config.mutationFn}, mutationOptions } from '${pkg}'\n` : ""}${deps.isMutation ? `import type { ${config.mutationOptionsType} } from '${pkg}'\n` : ""}${config.queryFnContext && deps.isQuery || deps.isInfinite ? `import type { QueryFunctionContext } from '${pkg}'\n` : ""}import { ${client} } from '${importPath}'\n\n`;
2647
+ }
2648
+ function makeQueryHooks(openAPI, output, importPath, config, client, basePath, split = false) {
2649
+ return Effect.gen(function* () {
2650
+ const prefix = basePath && basePath !== "/" ? basePath : "";
2651
+ const ops = [];
2652
+ for (const [pathStr, pathItem] of pathEntries(openAPI)) {
2653
+ if (!pathItem) continue;
2654
+ for (const method of HTTP_METHODS) {
2655
+ const rawOperation = pathItem[method];
2656
+ if (!rawOperation) continue;
2657
+ const operation = resolveOperation(rawOperation, openAPI.components);
2658
+ const funcName = toSafeIdentifier(resolveOperationId(operation, method, `${prefix}${pathStr}`));
2659
+ const code = makeOperation(`${prefix}${pathStr}`, method, operation, client, config);
2660
+ const keyPrefix = resourcePrefix(`${prefix}${pathStr}`);
2661
+ const isQuery = method === "get" || method === "head";
2662
+ ops.push({
2663
+ funcName,
2664
+ code,
2665
+ prefix: keyPrefix,
2666
+ deps: {
2667
+ isQuery,
2668
+ isInfinite: isQuery && operation["x-pagination"] === true,
2669
+ isMutation: !isQuery
2670
+ },
2671
+ fetchType: fetchTypeImport(`${prefix}${pathStr}`, operation)
2672
+ });
2673
+ }
2674
+ }
2675
+ if (ops.length === 0) return "No operations found";
2676
+ if (split) {
2677
+ const prefixKeys = makePrefixKeyCodes(openAPI, prefix);
2678
+ const keysCode = prefixKeys.length > 0 ? `${prefixKeys.join("\n\n")}\n` : "";
2679
+ if (keysCode && ops.some((op) => op.funcName === "keys")) return yield* new GenerateError({ message: "Operation file name 'keys.ts' collides with the aggregated cache-key file. Rename the operation (operationId) that resolves to 'keys'." });
2680
+ for (const op of ops) {
2681
+ const header = makeHeader(config, client, importPath, op.deps);
2682
+ const typeImport = op.fetchType ? `import type { ${op.fetchType} } from '../components/schemas'\n` : "";
2683
+ const file = path.join(output, `${op.funcName}.ts`);
2684
+ yield* emit(`${typeImport}${header}${op.code}\n`, output, file);
2685
+ }
2686
+ if (keysCode) yield* emit(keysCode, output, path.join(output, "keys.ts"));
2687
+ yield* emit(`${[...keysCode ? [`export * from './keys'`] : [], ...ops.map((op) => `export * from './${op.funcName}'`)].join("\n")}\n`, output, path.join(output, "index.ts"));
2688
+ return `Generated split ${config.label} code → ${output}/`;
2689
+ }
2690
+ yield* emit(`${makeHeader(config, client, importPath, ops.reduce((acc, op) => ({
2691
+ isQuery: acc.isQuery || op.deps.isQuery,
2692
+ isInfinite: acc.isInfinite || op.deps.isInfinite,
2693
+ isMutation: acc.isMutation || op.deps.isMutation
2694
+ }), {
2695
+ isQuery: false,
2696
+ isInfinite: false,
2697
+ isMutation: false
2698
+ }))}${`${[...makePrefixKeyCodes(openAPI, prefix), ...ops.map((op) => op.code)].join("\n\n")}\n`}`, path.dirname(output), output);
2699
+ return `Generated ${config.label} code written to ${output}`;
2700
+ });
2701
+ }
2702
+ //#endregion
2703
+ //#region src/core/hooks/index.ts
2704
+ const TANSTACK = {
2705
+ label: "tanstack-query",
2706
+ packageName: "@tanstack/react-query",
2707
+ hookPrefix: "use",
2708
+ queryFn: "useQuery",
2709
+ mutationFn: "useMutation",
2710
+ infiniteQueryFn: "useInfiniteQuery",
2711
+ useQueryGenerics: true,
2712
+ hasInfiniteQueryOptionsHelper: true,
2713
+ suspenseQueryFn: "useSuspenseQuery",
2714
+ suspenseInfiniteQueryFn: "useSuspenseInfiniteQuery",
2715
+ queryOptionsType: "UseQueryOptions",
2716
+ suspenseQueryOptionsType: "UseSuspenseQueryOptions",
2717
+ mutationOptionsType: "UseMutationOptions",
2718
+ infiniteOptionsType: "UseInfiniteQueryOptions",
2719
+ suspenseInfiniteOptionsType: "UseSuspenseInfiniteQueryOptions"
2720
+ };
2721
+ const HOOK_CONFIGS = {
2722
+ swr: {
2723
+ label: "swr",
2724
+ packageName: "swr",
2725
+ hookPrefix: "use",
2726
+ queryFn: "useSWR",
2727
+ mutationFn: "useSWRMutation",
2728
+ infiniteQueryFn: "useSWRInfinite",
2729
+ immutableQueryFn: "useSWRImmutable",
2730
+ isSWR: true
2731
+ },
2732
+ "tanstack-query": TANSTACK,
2733
+ "preact-query": {
2734
+ ...TANSTACK,
2735
+ label: "preact-query",
2736
+ packageName: "@tanstack/preact-query"
2737
+ },
2738
+ "vue-query": {
2739
+ label: "vue-query",
2740
+ packageName: "@tanstack/vue-query",
2741
+ hookPrefix: "use",
2742
+ queryFn: "useQuery",
2743
+ mutationFn: "useMutation",
2744
+ infiniteQueryFn: "useInfiniteQuery",
2745
+ queryFnContext: true,
2746
+ maybeRefOptions: true,
2747
+ queryOptionsType: "UseQueryOptions",
2748
+ mutationOptionsType: "UseMutationOptions",
2749
+ infiniteOptionsType: "UseInfiniteQueryOptions"
2750
+ },
2751
+ "solid-query": {
2752
+ label: "solid-query",
2753
+ packageName: "@tanstack/solid-query",
2754
+ hookPrefix: "create",
2755
+ queryFn: "createQuery",
2756
+ mutationFn: "createMutation",
2757
+ infiniteQueryFn: "createInfiniteQuery",
2758
+ queryFnContext: true,
2759
+ useThunk: true,
2760
+ thunkOptionsCall: true,
2761
+ unwrapOptionsAccessor: true,
2762
+ hasInfiniteQueryOptionsHelper: true,
2763
+ queryOptionsType: "UndefinedInitialDataOptions",
2764
+ mutationOptionsType: "CreateMutationOptions",
2765
+ infiniteOptionsType: "UndefinedInitialDataInfiniteOptions"
2766
+ },
2767
+ "svelte-query": {
2768
+ label: "svelte-query",
2769
+ packageName: "@tanstack/svelte-query",
2770
+ hookPrefix: "create",
2771
+ queryFn: "createQuery",
2772
+ mutationFn: "createMutation",
2773
+ infiniteQueryFn: "createInfiniteQuery",
2774
+ queryFnContext: true,
2775
+ useThunk: true,
2776
+ hasInfiniteQueryOptionsHelper: true,
2777
+ queryOptionsType: "CreateQueryOptions",
2778
+ mutationOptionsType: "CreateMutationOptions",
2779
+ infiniteOptionsType: "CreateInfiniteQueryOptions"
2780
+ },
2781
+ "angular-query": {
2782
+ label: "angular-query",
2783
+ packageName: "@tanstack/angular-query-experimental",
2784
+ hookPrefix: "inject",
2785
+ queryFn: "injectQuery",
2786
+ mutationFn: "injectMutation",
2787
+ infiniteQueryFn: "injectInfiniteQuery",
2788
+ queryFnContext: true,
2789
+ useThunk: true,
2790
+ hasInfiniteQueryOptionsHelper: true,
2791
+ queryOptionsType: "CreateQueryOptions",
2792
+ mutationOptionsType: "CreateMutationOptions",
2793
+ infiniteOptionsType: "CreateInfiniteQueryOptions"
2794
+ }
2795
+ };
2796
+ function hooks(openAPI, output, importPath, library, options) {
2797
+ return makeQueryHooks(openAPI, output, importPath, HOOK_CONFIGS[library], options.client, options.basePath, options.split);
2798
+ }
2799
+ //#endregion
2800
+ //#region src/helper/faker.ts
2801
+ function hasNumericConstraint(schema) {
2802
+ return schema.minimum !== void 0 || schema.maximum !== void 0 || schema.exclusiveMinimum !== void 0 || schema.exclusiveMaximum !== void 0 || schema.multipleOf !== void 0;
2803
+ }
2804
+ function hasStringConstraint(schema) {
2805
+ return schema.minLength !== void 0 || schema.maxLength !== void 0 || schema.pattern !== void 0;
2806
+ }
2807
+ function numericFakerExpr(schema, isInt) {
2808
+ const bounds = normalizeBounds(schema);
2809
+ const epsilon = isInt ? 1 : .01;
2810
+ const lo = bounds.exclusiveMinimum !== void 0 ? bounds.exclusiveMinimum + epsilon : bounds.minimum ?? 1;
2811
+ const hi = Math.max(lo, bounds.exclusiveMaximum !== void 0 ? bounds.exclusiveMaximum - epsilon : bounds.maximum ?? 1e3);
2812
+ const multipleOf = schema.multipleOf;
2813
+ if (multipleOf !== void 0 && multipleOf > 0) {
2814
+ const kMin = Math.ceil(lo / multipleOf);
2815
+ return `faker.number.int({ min: ${kMin}, max: ${Math.max(kMin, Math.floor(hi / multipleOf))} }) * ${multipleOf}`;
2816
+ }
2817
+ if (isInt) return `faker.number.int({ min: ${lo}, max: ${hi} })`;
2818
+ return `faker.number.float({ min: ${lo}, max: ${hi}, fractionDigits: 2 })`;
2819
+ }
2820
+ /**
2821
+ * Rewrites a `pattern` into the string `faker.helpers.fromRegExp` samples.
2822
+ *
2823
+ * faker strips `^`/`$` only from a `RegExp` argument; given a string it copies them into the
2824
+ * value (`^abc$`), which then fails the very pattern it was drawn from. The anchors are removed
2825
+ * here instead, together with the no-op `\/` escape that faker would copy verbatim as well.
2826
+ */
2827
+ function fakerPattern(pattern) {
2828
+ return pattern.replace(/^\^+/u, "").replace(/(?<!\\)(?:\\\\)*\$+$/u, (anchor) => anchor.replaceAll("$", "")).replaceAll(/\\([\s\S])/gu, (match, escaped) => escaped === "/" ? "/" : match);
2829
+ }
2830
+ function stringFakerExpr(schema) {
2831
+ if (schema.pattern !== void 0) return `faker.helpers.fromRegExp(${JSON.stringify(fakerPattern(schema.pattern))})`;
2832
+ const lower = schema.minLength ?? 5;
2833
+ const max = schema.maxLength ?? Math.max(lower, 20);
2834
+ return `faker.string.alpha({ length: { min: ${Math.min(lower, max)}, max: ${max} } })`;
2835
+ }
2836
+ function arrayLengthExpr(schema, options = {}) {
2837
+ const min = schema.minItems ?? Math.min(options.arrayMin ?? 1, schema.maxItems ?? Infinity);
2838
+ return `faker.number.int({ min: ${min}, max: ${Math.max(schema.maxItems ?? options.arrayMax ?? 5, min)} })`;
2839
+ }
2840
+ const NON_EXISTENT_CEIL = 2147483647;
2841
+ function primaryType$2(schema) {
2842
+ const type = schema.type;
2843
+ if (Array.isArray(type)) return type.find((t) => t !== "null") ?? type[0];
2844
+ return type;
2845
+ }
2846
+ function nonExistentNumber(schema, isInt) {
2847
+ const bounds = normalizeBounds(schema);
2848
+ const epsilon = isInt ? 1 : .01;
2849
+ const lo = bounds.exclusiveMinimum !== void 0 ? bounds.exclusiveMinimum + epsilon : bounds.minimum ?? Number.NEGATIVE_INFINITY;
2850
+ const hiRaw = bounds.exclusiveMaximum !== void 0 ? bounds.exclusiveMaximum - epsilon : bounds.maximum;
2851
+ const hi = hiRaw ?? Math.max(lo === Number.NEGATIVE_INFINITY ? NON_EXISTENT_CEIL : lo, NON_EXISTENT_CEIL);
2852
+ const multipleOf = schema.multipleOf;
2853
+ if (multipleOf !== void 0 && multipleOf > 0) {
2854
+ const value = lo === Number.NEGATIVE_INFINITY ? Math.floor(NON_EXISTENT_CEIL / multipleOf) * multipleOf : Math.ceil(lo / multipleOf) * multipleOf;
2855
+ if (value < lo || hiRaw !== void 0 && value > hi) return void 0;
2856
+ return String(value);
2857
+ }
2858
+ const value = isInt ? Math.floor(hi) : hi;
2859
+ if (value < lo) return void 0;
2860
+ return String(value);
2861
+ }
2862
+ function nonExistentStringLiteral(schema) {
2863
+ const base = "__non_existent__";
2864
+ const min = schema.minLength ?? 0;
2865
+ const max = schema.maxLength ?? Math.max(16, min);
2866
+ if (16 > max) return "x".repeat(max);
2867
+ if (16 < min) return base + "x".repeat(min - 16);
2868
+ return base;
2869
+ }
2870
+ function nonExistentPathValue(schema) {
2871
+ if (schema.enum !== void 0 || schema.const !== void 0) return void 0;
2872
+ const type = primaryType$2(schema);
2873
+ if (type === "integer" || type === "number") {
2874
+ const value = nonExistentNumber(schema, type === "integer");
2875
+ return value === void 0 ? void 0 : {
2876
+ kind: "literal",
2877
+ value
2878
+ };
2879
+ }
2880
+ if (schema.format === "uuid") return {
2881
+ kind: "literal",
2882
+ value: "00000000-0000-0000-0000-000000000000"
2883
+ };
2884
+ if (schema.pattern !== void 0) return {
2885
+ kind: "expr",
2886
+ code: `faker.helpers.fromRegExp(${JSON.stringify(fakerPattern(schema.pattern))})`
2887
+ };
2888
+ return {
2889
+ kind: "literal",
2890
+ value: nonExistentStringLiteral(schema)
2891
+ };
2892
+ }
2893
+ //#endregion
2894
+ //#region src/generator/faker/index.ts
2895
+ function safeObjectKey(key) {
2896
+ return /^[A-Za-z_$][A-Za-z0-9_$]*$/u.test(key) ? key : JSON.stringify(key);
2897
+ }
2898
+ function mockName(schemaName) {
2899
+ return `mock${schemaName.replaceAll(".", "").replaceAll(/[^A-Za-z0-9_$]/gu, "_")}`;
2900
+ }
2901
+ const FORMAT_TO_FAKER = {
2902
+ date: "faker.date.past().toISOString().slice(0, 10)",
2903
+ "date-time": "faker.date.past().toISOString()",
2904
+ time: "faker.date.past().toISOString().slice(11)",
2905
+ uri: "faker.internet.url()",
2906
+ url: "faker.internet.url()",
2907
+ email: "faker.internet.email()",
2908
+ ipv4: "faker.internet.ipv4()",
2909
+ ipv6: "faker.internet.ipv6()",
2910
+ hostname: "faker.internet.domainName()",
2911
+ uuid: "faker.string.uuid()",
2912
+ password: "faker.internet.password()",
2913
+ binary: "new Blob([faker.string.alphanumeric(100)])",
2914
+ byte: "btoa(faker.string.alphanumeric(10))",
2915
+ int32: "faker.number.int({ min: -2147483648, max: 2147483647 })",
2916
+ int64: "faker.number.int({ min: Number.MIN_SAFE_INTEGER, max: Number.MAX_SAFE_INTEGER })",
2917
+ float: "faker.number.float({ min: 0, max: 1000, fractionDigits: 2 })",
2918
+ double: "faker.number.float({ min: 0, max: 1000000, fractionDigits: 4 })",
2919
+ "iso-time": "faker.date.past().toISOString().slice(11, 19)",
2920
+ "iso-date-time": "faker.date.past().toISOString()",
2921
+ duration: "`P${faker.number.int({ min: 1, max: 30 })}D`",
2922
+ "uri-reference": "faker.internet.url()",
2923
+ "uri-template": "faker.internet.url()",
2924
+ "json-pointer": "`/${faker.string.alpha(8)}`",
2925
+ "json-pointer-uri-fragment": "`#/${faker.string.alpha(8)}`",
2926
+ "relative-json-pointer": "`0/${faker.string.alpha(8)}`",
2927
+ iri: "faker.internet.url()",
2928
+ "iri-reference": "faker.internet.url()",
2929
+ "idn-email": "faker.internet.email()",
2930
+ "idn-hostname": "faker.internet.domainName()",
2931
+ uuidv4: "faker.string.uuid({ version: 4 })",
2932
+ uuidv7: "faker.string.uuid({ version: 7 })",
2933
+ ulid: "faker.string.ulid()",
2934
+ nanoid: "faker.string.nanoid()",
2935
+ jwt: "faker.internet.jwt()",
2936
+ emoji: "faker.internet.emoji()",
2937
+ mac: "faker.internet.mac()",
2938
+ e164: "faker.phone.number({ style: 'international' })",
2939
+ decimal: "faker.commerce.price()"
2940
+ };
2941
+ const TYPE_TO_FAKER = {
2942
+ string: "faker.string.alpha({ length: { min: 5, max: 20 } })",
2943
+ number: "faker.number.float({ min: 0, max: 1000, fractionDigits: 2 })",
2944
+ integer: "faker.number.int({ min: 1, max: 1000 })",
2945
+ boolean: "faker.datatype.boolean()",
2946
+ date: "faker.date.past().toISOString()",
2947
+ null: "null"
2948
+ };
2949
+ const PROPERTY_NAME_TO_FAKER = {
2950
+ id: "faker.number.int({ min: 1, max: 99999 })",
2951
+ uuid: "faker.string.uuid()",
2952
+ email: "faker.internet.email()",
2953
+ name: "faker.person.fullName()",
2954
+ firstName: "faker.person.firstName()",
2955
+ lastName: "faker.person.lastName()",
2956
+ username: "faker.internet.username()",
2957
+ password: "faker.internet.password()",
2958
+ phone: "faker.phone.number()",
2959
+ address: "faker.location.streetAddress()",
2960
+ city: "faker.location.city()",
2961
+ state: "faker.location.state()",
2962
+ country: "faker.location.country()",
2963
+ zip: "faker.location.zipCode()",
2964
+ zipCode: "faker.location.zipCode()",
2965
+ url: "faker.internet.url()",
2966
+ website: "faker.internet.url()",
2967
+ createdAt: "faker.date.past().toISOString()",
2968
+ updatedAt: "faker.date.recent().toISOString()",
2969
+ deletedAt: "faker.date.past().toISOString()",
2970
+ title: "faker.lorem.sentence()",
2971
+ description: "faker.lorem.paragraph()",
2972
+ content: "faker.lorem.paragraphs(2)",
2973
+ status: "faker.helpers.arrayElement(['active', 'inactive', 'pending'])",
2974
+ type: "faker.helpers.arrayElement(['A', 'B', 'C'])",
2975
+ price: "faker.number.float({ min: 1, max: 10000, fractionDigits: 2 })",
2976
+ quantity: "faker.number.int({ min: 1, max: 100 })",
2977
+ count: "faker.number.int({ min: 0, max: 1000 })",
2978
+ age: "faker.number.int({ min: 1, max: 120 })"
2979
+ };
2980
+ function lookup(table, key) {
2981
+ return Object.hasOwn(table, key) ? table[key] : void 0;
2982
+ }
2983
+ function isHintCompatible(type, expr) {
2984
+ if (type === void 0) return true;
2985
+ const isInt = expr.startsWith("faker.number.int(");
2986
+ if (type === "integer") return isInt;
2987
+ if (type === "number") return isInt || expr.startsWith("faker.number.float(");
2988
+ if (type === "string") return !expr.startsWith("faker.number.");
2989
+ return false;
2990
+ }
2991
+ function propertyNameHint(propertyName) {
2992
+ return lookup(PROPERTY_NAME_TO_FAKER, propertyName) ?? lookup(PROPERTY_NAME_TO_FAKER, propertyName.replaceAll(/[_-]+([a-zA-Z0-9])/gu, (_, c) => c.toUpperCase()));
2993
+ }
2994
+ function primaryType$1(type) {
2995
+ if (isTypeArray(type)) return type.find((t) => t !== "null") ?? type[0];
2996
+ return type;
2997
+ }
2998
+ function isNullType(type) {
2999
+ return isTypeArray(type) ? type.includes("null") : type === "null";
3000
+ }
3001
+ function schemaExample(schema) {
3002
+ if (schema.example !== void 0) return schema.example;
3003
+ return Array.isArray(schema.examples) ? schema.examples[0] : void 0;
3004
+ }
3005
+ function exampleLiteral(schema) {
3006
+ const example = schemaExample(schema);
3007
+ if (example === void 0 || schema.$ref !== void 0) return void 0;
3008
+ const types = isTypeArray(schema.type) ? schema.type : schema.type ? [schema.type] : [];
3009
+ if (types.length === 0 && !schema.enum) return void 0;
3010
+ const isAccepted = (type) => types.length === 0 || types.includes(type);
3011
+ if (example === null) return isAccepted("null") || schema.nullable ? "null" : void 0;
3012
+ if (schema.enum && !schema.enum.some((member) => member === example)) return void 0;
3013
+ const asConst = schema.enum ? " as const" : "";
3014
+ if (typeof example === "string") return isAccepted("string") && schema.format !== "binary" ? `${JSON.stringify(example)}${asConst}` : void 0;
3015
+ if (typeof example === "number") {
3016
+ if (!(isAccepted("number") || isAccepted("integer") && Number.isInteger(example))) return;
3017
+ return `${JSON.stringify(example)}${asConst}`;
3018
+ }
3019
+ if (typeof example === "boolean") return isAccepted("boolean") ? `${String(example)}${asConst}` : void 0;
3020
+ }
3021
+ function schemaToFaker(schema, propertyName, options = {}) {
3022
+ if (schema.const !== void 0) return `${JSON.stringify(schema.const)} as const`;
3023
+ if (options.useExamples) {
3024
+ const example = exampleLiteral(schema);
3025
+ if (example !== void 0) return example;
3026
+ }
3027
+ if (schema.enum && schema.enum.length > 0) return `faker.helpers.arrayElement([${schema.enum.map((v) => JSON.stringify(v)).join(", ")}] as const)`;
3028
+ if (schema.$ref) return `${mockName(schema.$ref.split("/").pop() || "unknown")}()`;
3029
+ if (isTypeArray(schema.type)) {
3030
+ const type = primaryType$1(schema.type);
3031
+ if (type === void 0 || type === "null") return "null";
3032
+ const value = schemaToFaker({
3033
+ ...schema,
3034
+ type
3035
+ }, propertyName, options);
3036
+ return schema.type.includes("null") ? `faker.helpers.arrayElement([${value}, null])` : value;
3037
+ }
3038
+ const type = primaryType$1(schema.type);
3039
+ if (type === "array" && schema.items) {
3040
+ const itemSchema = isSchemaArray(schema.items) ? schema.items[0] : schema.items;
3041
+ if (!itemSchema) return "[]";
3042
+ const itemFaker = schemaToFaker(itemSchema, void 0, options);
3043
+ return `Array.from({ length: ${arrayLengthExpr(schema, options)} }, () => (${itemFaker}))`;
3044
+ }
3045
+ const renderProps = (properties, required) => {
3046
+ const requiredSet = new Set(required);
3047
+ return Object.entries(properties).map(([k, v]) => {
3048
+ const key = safeObjectKey(k);
3049
+ const value = schemaToFaker(v, k, options);
3050
+ const isNullable = v.nullable === true || isNullType(v.type);
3051
+ if (!(requiredSet.has(k) || isNullable)) return `${key}: faker.helpers.arrayElement([${value}, undefined])`;
3052
+ if (v.nullable) return `${key}: faker.helpers.arrayElement([${value}, null])`;
3053
+ return `${key}: ${value}`;
3054
+ }).join(", ");
3055
+ };
3056
+ if (type === "object" && schema.properties) return `{ ${renderProps(schema.properties, schema.required)} }`;
3057
+ if (type === "object" && !schema.properties && schema.additionalProperties) {
3058
+ if (typeof schema.additionalProperties === "boolean" || schema.propertyNames !== void 0 || schema.patternProperties !== void 0) return "{}";
3059
+ const value = schemaToFaker(schema.additionalProperties, void 0, options);
3060
+ const max = schema.maxProperties ?? Math.max(schema.minProperties ?? 1, 3);
3061
+ return `Object.fromEntries(Array.from({ length: faker.number.int({ min: ${schema.minProperties ?? Math.min(1, max)}, max: ${max} }) }, () => [faker.string.alpha(8), ${value}] satisfies [string, unknown]))`;
3062
+ }
3063
+ if (schema.allOf && schema.allOf.length > 0) {
3064
+ const merged = schema.allOf.map((s) => schemaToFaker(s, propertyName, options)).map((m) => `...${m}`).join(", ");
3065
+ if (schema.properties) return `{ ${merged}, ${renderProps(schema.properties, schema.required)} }`;
3066
+ return `{ ${merged} }`;
3067
+ }
3068
+ const union = schema.oneOf && schema.oneOf.length > 0 ? schema.oneOf : schema.anyOf && schema.anyOf.length > 0 ? schema.anyOf : void 0;
3069
+ if (union) return `faker.helpers.arrayElement([${union.map((s) => schemaToFaker(s, propertyName, options)).join(", ")}])`;
3070
+ const constrainedNumeric = (type === "integer" || type === "number") && hasNumericConstraint(schema);
3071
+ const constrainedString = type === "string" && hasStringConstraint(schema);
3072
+ const formatFaker = schema.format ? lookup(FORMAT_TO_FAKER, schema.format) : void 0;
3073
+ if (formatFaker && !constrainedNumeric && isHintCompatible(type, formatFaker)) return formatFaker;
3074
+ const nameHint = propertyName ? propertyNameHint(propertyName) : void 0;
3075
+ if (nameHint && !constrainedNumeric && !constrainedString && isHintCompatible(type, nameHint)) return nameHint;
3076
+ if (type === "string") return stringFakerExpr(schema);
3077
+ if (type === "integer") return numericFakerExpr(schema, true);
3078
+ if (type === "number") return numericFakerExpr(schema, false);
3079
+ if (type === "object") return "{}";
3080
+ if (type === "array") return "[]";
3081
+ return (type ? lookup(TYPE_TO_FAKER, type) : void 0) ?? "undefined";
3082
+ }
3083
+ function collectSchemaRefs(schema, schemas, visited = /* @__PURE__ */ new Set()) {
3084
+ if (schema.$ref) {
3085
+ const refName = schema.$ref.replace("#/components/schemas/", "");
3086
+ if (visited.has(refName)) return [];
3087
+ visited.add(refName);
3088
+ const referenced = schemas?.[refName];
3089
+ return [refName, ...referenced ? collectSchemaRefs(referenced, schemas, visited) : []];
3090
+ }
3091
+ const propRefs = schema.properties ? Object.values(schema.properties).flatMap((p) => collectSchemaRefs(p, schemas, visited)) : [];
3092
+ const itemRefs = (schema.items ? isSchemaArray(schema.items) ? schema.items : [schema.items] : []).flatMap((item) => collectSchemaRefs(item, schemas, visited));
3093
+ const compositeRefs = [
3094
+ "allOf",
3095
+ "oneOf",
3096
+ "anyOf"
3097
+ ].flatMap((k) => {
3098
+ const composite = schema[k];
3099
+ return composite ? composite.flatMap((sub) => collectSchemaRefs(sub, schemas, visited)) : [];
3100
+ });
3101
+ return [
3102
+ ...propRefs,
3103
+ ...itemRefs,
3104
+ ...compositeRefs
3105
+ ];
3106
+ }
3107
+ function shallowRefs(schema) {
3108
+ const selfRef = schema.$ref ? [schema.$ref.replace("#/components/schemas/", "")] : [];
3109
+ const propRefs = schema.properties ? Object.values(schema.properties).flatMap(shallowRefs) : [];
3110
+ const itemRefs = (schema.items ? Array.isArray(schema.items) ? schema.items : [schema.items] : []).flatMap(shallowRefs);
3111
+ const compositeRefs = [
3112
+ "allOf",
3113
+ "oneOf",
3114
+ "anyOf"
3115
+ ].flatMap((k) => {
3116
+ const composite = schema[k];
3117
+ return composite ? composite.flatMap(shallowRefs) : [];
3118
+ });
3119
+ return [
3120
+ ...selfRef,
3121
+ ...propRefs,
3122
+ ...itemRefs,
3123
+ ...compositeRefs
3124
+ ];
3125
+ }
3126
+ function reachesSelf(start, target, schemas, visited = /* @__PURE__ */ new Set()) {
3127
+ if (start === target) return true;
3128
+ if (visited.has(start)) return false;
3129
+ visited.add(start);
3130
+ const schema = schemas[start];
3131
+ if (!schema) return false;
3132
+ return shallowRefs(schema).some((dep) => reachesSelf(dep, target, schemas, visited));
3133
+ }
3134
+ function detectCircularSchemas(schemas) {
3135
+ return new Set(Object.keys(schemas).filter((name) => {
3136
+ const schema = schemas[name];
3137
+ if (!schema) return false;
3138
+ return shallowRefs(schema).some((dep) => reachesSelf(dep, name, schemas));
3139
+ }));
3140
+ }
3141
+ function topologicalOrder(usedSchemaNames, schemas) {
3142
+ const visit = (name, visited, visiting) => {
3143
+ if (visited.has(name) || !usedSchemaNames.has(name) || visiting.has(name)) return [];
3144
+ const nextVisiting = new Set(visiting).add(name);
3145
+ const schema = schemas[name];
3146
+ const depOrder = schema ? collectSchemaRefs(schema, schemas, new Set([name])).flatMap((dep) => {
3147
+ const subOrder = visit(dep, visited, nextVisiting);
3148
+ for (const n of subOrder) visited.add(n);
3149
+ return subOrder;
3150
+ }) : [];
3151
+ visited.add(name);
3152
+ return [...depOrder, name];
3153
+ };
3154
+ const visited = /* @__PURE__ */ new Set();
3155
+ return [...usedSchemaNames].flatMap((name) => visit(name, visited, /* @__PURE__ */ new Set()));
3156
+ }
3157
+ function makeMockFunctions(spec, usedSchemaNames, options = {}) {
3158
+ if (!spec.components?.schemas || usedSchemaNames.size === 0) return "";
3159
+ const schemas = spec.components.schemas;
3160
+ const circular = detectCircularSchemas(schemas);
3161
+ return topologicalOrder(usedSchemaNames, schemas).map((name) => {
3162
+ const schema = schemas[name];
3163
+ const returnType = circular.has(name) ? ": any" : "";
3164
+ const value = schema ? schemaToFaker(schema, void 0, options) : "undefined";
3165
+ return `function ${mockName(name)}()${returnType} {\n return ${value}\n}`;
3166
+ }).join("\n\n");
3167
+ }
3168
+ //#endregion
3169
+ //#region src/generator/mock/index.ts
3170
+ const SEED_REF_DATE = "2025-01-01T00:00:00.000Z";
3171
+ function normalizeKey(key) {
3172
+ return /^[1-5]xx$/iu.test(key) ? key.toUpperCase() : key;
3173
+ }
3174
+ function pickSuccessResponse(responses) {
3175
+ const keys = Object.keys(responses);
3176
+ const [status] = keys.filter((k) => /^2\d\d$/u.test(k)).map((k) => Number.parseInt(k, 10)).toSorted((a, b) => a - b);
3177
+ if (status !== void 0) return {
3178
+ key: String(status),
3179
+ status,
3180
+ response: responses[String(status)]
3181
+ };
3182
+ const wildcard = [
3183
+ "2XX",
3184
+ "2xx",
3185
+ "default"
3186
+ ].find((k) => k in responses);
3187
+ if (wildcard) return {
3188
+ key: normalizeKey(wildcard),
3189
+ status: 200,
3190
+ response: responses[wildcard]
3191
+ };
3192
+ const first = keys[0];
3193
+ if (first === void 0) return {
3194
+ key: "200",
3195
+ status: 200,
3196
+ response: void 0
3197
+ };
3198
+ const parsed = Number.parseInt(first, 10);
3199
+ return {
3200
+ key: normalizeKey(first),
3201
+ status: Number.isNaN(parsed) ? 200 : parsed,
3202
+ response: responses[first]
3203
+ };
3204
+ }
3205
+ /**
3206
+ * The example a media type declares, if it declares one.
3207
+ *
3208
+ * `example` wins over `examples`, and the first `examples` entry wins over the rest — a document
3209
+ * that lists several is showing variants of one realistic response, and a mock only serves one.
3210
+ * A `$ref` entry resolves against `components.examples`; an entry that only carries
3211
+ * `externalValue` has nothing to serve, so it falls through to faker.
3212
+ */
3213
+ function mediaExample(media, components) {
3214
+ if (!isRecord(media)) return void 0;
3215
+ if (media.example !== void 0) return media.example;
3216
+ if (!isRecord(media.examples)) return void 0;
3217
+ return exampleEntryValue(Object.values(media.examples)[0], components);
3218
+ }
3219
+ function exampleEntryValue(entry, components) {
3220
+ if (!isRecord(entry)) return void 0;
3221
+ const ref = entry.$ref;
3222
+ const resolved = typeof ref === "string" ? components?.examples?.[ref.replace("#/components/examples/", "")] : entry;
3223
+ return isRecord(resolved) ? resolved.value : void 0;
3224
+ }
3225
+ function namedExamples(media, components) {
3226
+ if (!isRecord(media) || !isRecord(media.examples)) return [];
3227
+ return Object.entries(media.examples).flatMap(([name, entry]) => {
3228
+ const value = exampleEntryValue(entry, components);
3229
+ return value === void 0 ? [] : [[name, value]];
3230
+ });
3231
+ }
3232
+ function jsonMediaType(content) {
3233
+ const types = Object.keys(content ?? {});
3234
+ return types.includes("application/json") ? "application/json" : types.find((type) => /^application\/(?:[\w.-]+\+)?json(?:;|$)/u.test(type));
3235
+ }
3236
+ function responseBody(response, example, fakerOptions) {
3237
+ const content = response?.content;
3238
+ if (!content) return {
3239
+ kind: "empty",
3240
+ expr: ""
3241
+ };
3242
+ const jsonType = jsonMediaType(content);
3243
+ const json = jsonType ? content[jsonType] : void 0;
3244
+ if (jsonType && json) {
3245
+ if (example !== void 0) return {
3246
+ kind: "json",
3247
+ expr: JSON.stringify(example),
3248
+ mediaType: jsonType
3249
+ };
3250
+ if (isMediaWithSchema(json)) return {
3251
+ kind: "json",
3252
+ expr: schemaToFaker(json.schema, void 0, fakerOptions),
3253
+ mediaType: jsonType
3254
+ };
3255
+ }
3256
+ const text = content["text/plain"];
3257
+ if (text) {
3258
+ if (typeof example === "string") return {
3259
+ kind: "text",
3260
+ expr: JSON.stringify(example)
3261
+ };
3262
+ return {
3263
+ kind: "text",
3264
+ expr: isMediaWithSchema(text) ? schemaToFaker(text.schema, void 0, fakerOptions) : "faker.lorem.sentence()"
3265
+ };
3266
+ }
3267
+ return {
3268
+ kind: "empty",
3269
+ expr: ""
3270
+ };
3271
+ }
3272
+ function defaultExample(response, components, useExamples) {
3273
+ const content = response?.content;
3274
+ if (!useExamples || !content) return void 0;
3275
+ const jsonType = jsonMediaType(content);
3276
+ return jsonType ? mediaExample(content[jsonType], components) : mediaExample(content["text/plain"], components);
3277
+ }
3278
+ /**
3279
+ * The expression that reads the credential a security scheme expects, or `undefined` when the
3280
+ * scheme is one a mock cannot check for.
3281
+ *
3282
+ * Only presence is checked: a mock that validated credentials would need a user store, and the
3283
+ * point of the 401 branch is that a client can exercise the unauthorized path at all.
3284
+ */
3285
+ function credentialExpr(scheme) {
3286
+ if (scheme.type === "http" || scheme.type === "oauth2" || scheme.type === "openIdConnect") return "c.headers.authorization";
3287
+ if (scheme.type !== "apiKey") return void 0;
3288
+ const name = scheme.name ?? "X-API-Key";
3289
+ if (scheme.in === "query") return `c.query[${quoteSingle$1(name)}]`;
3290
+ if (scheme.in === "cookie") return `c.cookie[${quoteSingle$1(name)}]?.value`;
3291
+ return `c.headers[${quoteSingle$1(name.toLowerCase())}]`;
3292
+ }
3293
+ /**
3294
+ * The guard a secured operation answers 401 with, or `''` when there is nothing to guard.
3295
+ *
3296
+ * Emitted only when the operation declares a 401: without that in the contract a 401 would be a
3297
+ * response the client was never told about. Alternatives are OR'd, because OpenAPI's security
3298
+ * array is a list of ways to satisfy the requirement.
3299
+ */
3300
+ function authGuard(operation, spec, responses) {
3301
+ if (!("401" in responses)) return "";
3302
+ const requirements = operation.security ?? spec.security;
3303
+ if (!requirements || requirements.length === 0) return "";
3304
+ const schemes = spec.components?.securitySchemes ?? {};
3305
+ const checks = requirements.flatMap((requirement) => Object.keys(requirement)).map((name) => schemes[name]).flatMap((scheme) => {
3306
+ if (!scheme || isReference(scheme)) return [];
3307
+ const expr = credentialExpr(scheme);
3308
+ return expr === void 0 ? [] : [expr];
3309
+ }).filter((expr, index, all) => all.indexOf(expr) === index);
3310
+ if (checks.length === 0) return "";
3311
+ return `if (!(${checks.join(" || ")})) return c.status(401)\n `;
3312
+ }
3313
+ /**
3314
+ * The guard a path parameter answers 404 with, or `''` when there is nothing to guard.
3315
+ *
3316
+ * Emitted only when the operation declares a 404, for the same reason as the 401: a status the
3317
+ * document never mentioned is one the client was never told to expect. The sentinel value is
3318
+ * shared with the test generator, so a generated 404 test and the mock agree on which value
3319
+ * means "not there".
3320
+ */
3321
+ function notFoundGuard(operation, components, responses) {
3322
+ if (!("404" in responses)) return "";
3323
+ const guards = (operation.parameters ?? []).map((parameter) => parameter.$ref ? components?.parameters?.[parameter.$ref.replace("#/components/parameters/", "")] : parameter).flatMap((parameter) => {
3324
+ if (!parameter || !("in" in parameter) || parameter.in !== "path") return [];
3325
+ const schema = parameter.schema;
3326
+ if (!schema) return [];
3327
+ const sentinel = nonExistentPathValue(schema);
3328
+ if (!sentinel) return [];
3329
+ const value = sentinel.kind === "literal" ? quoteSingle$1(sentinel.value) : `String(${sentinel.code})`;
3330
+ return [`if (prefer.key === undefined && c.params[${quoteSingle$1(parameter.name)}] === ${value}) return c.status(404)`];
3331
+ });
3332
+ return guards.length === 0 ? "" : `${guards.join("\n ")}\n `;
3333
+ }
3334
+ /**
3335
+ * The return statement for one answer. 200 returns the value directly (Elysia defaults to 200);
3336
+ * other statuses use the `status(code, body)` helper so the code reaches the wire. A `+json`
3337
+ * media type other than `application/json` is set on the response so it survives serialization.
3338
+ */
3339
+ function returnStatement(status, body) {
3340
+ if (body.kind === "empty") return `return c.status(${status})`;
3341
+ const header = body.kind === "json" && body.mediaType !== "application/json" ? `c.set.headers['content-type'] = ${quoteSingle$1(body.mediaType)}\n ` : "";
3342
+ if (status === "200") return `${header}return ${body.kind === "json" ? `(${body.expr})` : body.expr}`;
3343
+ return `${header}return c.status(${status}, ${body.expr})`;
3344
+ }
3345
+ /**
3346
+ * The handler: guards first, in this order — a request without credentials never reaches the 404
3347
+ * check, which matches what a real server tells an unauthenticated client about what exists —
3348
+ * then the `Prefer` selection, then the declared success response.
3349
+ */
3350
+ function makeHandler(parts) {
3351
+ return `(c) => {\n ${parts.seed}${parts.auth}${parts.prefer}${parts.notFound}${parts.success}\n }`;
3352
+ }
3353
+ /**
3354
+ * Module-level helpers for Prism-compatible response selection, emitted once per mock file. The
3355
+ * names carry no `mock` prefix, so they can never collide with a component factory
3356
+ * (`mock<Name>`).
3357
+ */
3358
+ const PREFER_HELPERS = `// Reads Prism's \`Prefer: code=<status>, example=<name>\` header (or the \`__code\` /
3359
+ // \`__example\` query) and resolves it against the responses the operation declares: the exact
3360
+ // status, then its \`NXX\` range, then \`default\`. Without a code the example is looked up in
3361
+ // the success response. Anything the operation does not declare answers 500 problem+json, as
3362
+ // Prism does.
3363
+ function resolvePrefer(
3364
+ header: string | undefined,
3365
+ query: { readonly [k: string]: string | undefined },
3366
+ responses: { readonly [key: string]: readonly string[] },
3367
+ success: string,
3368
+ ) {
3369
+ let code = query.__code
3370
+ let example = query.__example
3371
+ for (const [, name = '', quoted, bare] of (header ?? '').matchAll(
3372
+ /([A-Za-z]+)\\s*=\\s*(?:"([^"]*)"|([^\\s,;]*))/gu,
3373
+ )) {
3374
+ if (name.toLowerCase() === 'code') code ??= quoted ?? bare
3375
+ if (name.toLowerCase() === 'example') example ??= quoted ?? bare
3376
+ }
3377
+ if (code === undefined && example === undefined) return {}
3378
+ if (code !== undefined && !/^[2-5]\\d\\d$/u.test(code)) {
3379
+ return { problem: preferProblem(\`Prefer code=\${code} is not a status code between 200 and 599.\`) }
3380
+ }
3381
+ const key =
3382
+ code === undefined
3383
+ ? success
3384
+ : [code, \`\${code.slice(0, 1)}XX\`, 'default'].find((k) => Object.hasOwn(responses, k))
3385
+ if (key === undefined) {
3386
+ return { problem: preferProblem(\`No \${code} response is declared for this operation.\`) }
3387
+ }
3388
+ if (example !== undefined && !responses[key]?.includes(example)) {
3389
+ return {
3390
+ problem: preferProblem(
3391
+ \`No example named "\${example}" is declared for the \${key} response.\`,
3392
+ ),
3393
+ }
3394
+ }
3395
+ return { key, status: code === undefined ? undefined : Number(code), example }
3396
+ }
3397
+
3398
+ function preferProblem(detail: string) {
3399
+ return new Response(
3400
+ JSON.stringify({ type: 'about:blank', title: 'Mock response unavailable', status: 500, detail }),
3401
+ { status: 500, headers: { 'content-type': 'application/problem+json' } },
3402
+ )
3403
+ }`;
3404
+ function indent(statement) {
3405
+ return statement.replaceAll("\n ", "\n ");
3406
+ }
3407
+ function quoteSingle$1(s) {
3408
+ return `'${s.replaceAll("\\", "\\\\").replaceAll("'", "\\'").replaceAll("\n", "\\n").replaceAll("\r", "\\r").replaceAll(" ", "\\t")}'`;
3409
+ }
3410
+ function routePath(path) {
3411
+ return quoteSingle$1(path.replaceAll(/\{([^}]+)\}/gu, ":$1"));
3412
+ }
3413
+ /**
3414
+ * The middleware every response passes through when `delay` is configured.
3415
+ *
3416
+ * Cross-cutting rather than woven into each handler, so the handlers stay exactly what they would
3417
+ * be without it and the output is byte-identical when no delay is asked for.
3418
+ */
3419
+ function delayMiddleware(delay) {
3420
+ if (delay === void 0 || delay === false) return "";
3421
+ return `\n .onBeforeHandle(() => new Promise((resolve) => setTimeout(resolve, ${typeof delay === "number" ? String(delay) : `faker.number.int({ min: ${String(delay.min)}, max: ${String(delay.max)} })`})))`;
3422
+ }
3423
+ /**
3424
+ * One self-contained Elysia file that stands up a mock server: a faker factory per referenced
3425
+ * component schema, then every operation wired to a handler that returns a mock of its declared
3426
+ * success response. `import.meta.main` guards `listen()` so importing the module (e.g. from a
3427
+ * test) starts no server.
3428
+ *
3429
+ * A secured operation that declares a 401 checks for the credential first, and a path parameter
3430
+ * with a recognisable "missing" value answers 404 — so a client can drive the unhappy paths the
3431
+ * document promises, not just the happy one.
3432
+ */
3433
+ function makeMock(spec, options = {}) {
3434
+ const { prefix, port = "3000", useExamples = true, locale, delay, arrayMin, arrayMax, seed } = options;
3435
+ const fakerOptions = {
3436
+ ...arrayMin !== void 0 ? { arrayMin } : {},
3437
+ ...arrayMax !== void 0 ? { arrayMax } : {},
3438
+ ...useExamples === "all" ? { useExamples: true } : {}
3439
+ };
3440
+ const components = spec.components;
3441
+ const routes = pathEntries(spec).flatMap(([path, pathItem]) => {
3442
+ if (!pathItem) return [];
3443
+ return HTTP_METHODS.flatMap((method) => {
3444
+ const operation = pathItem[method];
3445
+ if (!operation) return [];
3446
+ const resolved = resolveOperation(operation, components);
3447
+ const success = pickSuccessResponse(resolved.responses);
3448
+ const responses = Object.entries(resolved.responses).map(([rawKey, declared]) => {
3449
+ const response = declared;
3450
+ const content = response.content;
3451
+ const jsonType = jsonMediaType(content);
3452
+ return {
3453
+ key: normalizeKey(rawKey),
3454
+ response,
3455
+ example: defaultExample(response, components, useExamples !== false),
3456
+ named: jsonType ? namedExamples(content?.[jsonType], components) : [],
3457
+ jsonSchema: jsonType ? content?.[jsonType] : void 0
3458
+ };
3459
+ });
3460
+ const refs = responses.flatMap(({ jsonSchema }) => jsonSchema && isMediaWithSchema(jsonSchema) ? collectSchemaRefs(jsonSchema.schema, components?.schemas) : []);
3461
+ const preferTable = Object.fromEntries(responses.map((r) => [r.key, r.named.map(([name]) => name)]));
3462
+ const render = (r, example) => {
3463
+ return indent(returnStatement(/^\d{3}$/u.test(r.key) ? r.key : `prefer.status ?? ${/^[1-5]XX$/u.test(r.key) ? `${r.key.slice(0, 1)}00` : "200"}`, responseBody(r.response, example, fakerOptions)));
3464
+ };
3465
+ const branches = [responses.flatMap((r) => r.named.filter(([, value]) => value !== r.example).map(([name, value]) => `if (prefer.key === ${quoteSingle$1(r.key)} && prefer.example === ${quoteSingle$1(name)}) {\n ${render(r, value)}\n }\n `)), responses.filter((r) => !(r.key === success.key && /^\d{3}$/u.test(r.key))).map((r) => `if (prefer.key === ${quoteSingle$1(r.key)}) {\n ${render(r, r.example)}\n }\n `)].flat();
3466
+ const prefer = `const prefer = resolvePrefer(c.headers.prefer, c.query, ${JSON.stringify(preferTable)}, ${quoteSingle$1(success.key)})\n if (prefer.problem) return prefer.problem\n ${branches.join("")}`;
3467
+ const successBody = responseBody(success.response, defaultExample(success.response, components, useExamples !== false), fakerOptions);
3468
+ const auth = authGuard(resolved, spec, resolved.responses);
3469
+ const notFound = notFoundGuard(resolved, components, resolved.responses);
3470
+ const successReturn = returnStatement(String(success.status), successBody);
3471
+ const usesFaker = /\bfaker\.|\bmock[A-Za-z0-9_$]*\(/u.test(`${branches.join("")}${notFound}${successReturn}`);
3472
+ const handler = makeHandler({
3473
+ seed: seed !== void 0 && usesFaker ? `faker.seed(${JSON.stringify(seed)})\n faker.setDefaultRefDate('${SEED_REF_DATE}')\n ` : "",
3474
+ auth,
3475
+ prefer,
3476
+ notFound,
3477
+ success: successReturn
3478
+ });
3479
+ return [{
3480
+ method,
3481
+ path,
3482
+ code: `.${method}(${routePath(path)}, ${handler})`,
3483
+ refs
3484
+ }];
3485
+ });
3486
+ });
3487
+ const mockFunctions = makeMockFunctions(spec, new Set(routes.flatMap((r) => r.refs)), fakerOptions);
3488
+ const ctorArgs = prefix ? `{ prefix: ${quoteSingle$1(prefix)} }` : "";
3489
+ const routeChain = routes.map((r) => ` ${r.code}`).join("\n");
3490
+ const listenBlock = `
3491
+
3492
+ if (import.meta.main) {
3493
+ app.listen(${port})\n console.log(\`🦊 Elysia is running at \${app.server?.hostname}:\${app.server?.port}\`)
3494
+ }`;
3495
+ const appCode = `export const app = new Elysia(${ctorArgs})${delayMiddleware(delay)}\n${routeChain}${listenBlock}`;
3496
+ const helpers = routes.length > 0 ? `${PREFER_HELPERS}\n\n` : "";
3497
+ const body = mockFunctions ? `${mockFunctions}\n\n${helpers}${appCode}` : `${helpers}${appCode}`;
3498
+ const fakerModule = locale === void 0 ? "@faker-js/faker" : `@faker-js/faker/locale/${locale}`;
3499
+ return `import { Elysia } from 'elysia'${body.includes("faker.") ? `\nimport { faker } from '${fakerModule}'` : ""}\n\n${body}\n`;
3500
+ }
3501
+ //#endregion
3502
+ //#region src/core/mock/index.ts
3503
+ function mock(openAPI, output, options = {}) {
3504
+ return Effect.gen(function* () {
3505
+ yield* emit(makeMock(openAPI, options), path.dirname(output), output);
3506
+ return `Generated mock server written to ${output}`;
3507
+ });
3508
+ }
3509
+ //#endregion
3510
+ //#region src/generator/test/test-generator.ts
3511
+ function extractSecurity(opSecurity, globalSecurity, securitySchemes) {
3512
+ return (opSecurity ?? globalSecurity ?? []).flatMap((secDef) => Object.keys(secDef).flatMap((schemeName) => {
3513
+ const scheme = securitySchemes?.[schemeName];
3514
+ if (!scheme || typeof scheme.type !== "string") return [];
3515
+ if (scheme.type === "http" && scheme.scheme === "bearer") return [{
3516
+ type: "bearer",
3517
+ name: "Authorization"
3518
+ }];
3519
+ if (scheme.type === "http" && scheme.scheme === "basic") return [{
3520
+ type: "basic",
3521
+ name: "Authorization"
3522
+ }];
3523
+ if (scheme.type === "apiKey") {
3524
+ const inLocation = scheme.in === "header" || scheme.in === "query" || scheme.in === "cookie" ? scheme.in : "header";
3525
+ return [{
3526
+ type: "apiKey",
3527
+ name: scheme.name || "X-API-Key",
3528
+ in: inLocation
3529
+ }];
3530
+ }
3531
+ if (scheme.type === "oauth2") return [{
3532
+ type: "oauth2",
3533
+ name: "Authorization"
3534
+ }];
3535
+ return [];
3536
+ }));
3537
+ }
3538
+ function extractTestCases(spec) {
3539
+ const components = spec.components;
3540
+ return pathEntries(spec).flatMap(([path, pathItem]) => {
3541
+ if (!pathItem) return [];
3542
+ return HTTP_METHODS.flatMap((method) => {
3543
+ const operation = pathItem[method];
3544
+ if (!operation) return [];
3545
+ const resolved = resolveOperation(operation, components);
3546
+ const resolvedParams = resolved.parameters.map((param) => {
3547
+ const schema = param.schema ?? { type: "string" };
3548
+ return {
3549
+ param,
3550
+ schema,
3551
+ fakerCode: schemaToFaker(schema, param.name)
3552
+ };
3553
+ });
3554
+ const pathParams = resolvedParams.filter((p) => p.param.in === "path").map((p) => ({
3555
+ name: p.param.name,
3556
+ fakerCode: p.fakerCode,
3557
+ schema: p.schema
3558
+ }));
3559
+ const queryParams = resolvedParams.filter((p) => p.param.in === "query").map((p) => ({
3560
+ name: p.param.name,
3561
+ fakerCode: p.fakerCode,
3562
+ required: p.param.required ?? false
3563
+ }));
3564
+ const headerParams = resolvedParams.filter((p) => p.param.in === "header").map((p) => ({
3565
+ name: p.param.name,
3566
+ fakerCode: p.fakerCode,
3567
+ required: p.param.required ?? false
3568
+ }));
3569
+ const requestBodyDef = resolved.requestBody;
3570
+ const jsonMedia = requestBodyDef && "content" in requestBodyDef ? requestBodyDef.content?.["application/json"] : void 0;
3571
+ const jsonBodySchema = jsonMedia && isMediaWithSchema(jsonMedia) ? jsonMedia.schema : void 0;
3572
+ const requestBody = jsonBodySchema ? { fakerCode: schemaToFaker(jsonBodySchema) } : void 0;
3573
+ const bodyRefs = jsonBodySchema ? collectSchemaRefs(jsonBodySchema, components?.schemas) : [];
3574
+ const paramRefs = resolvedParams.flatMap((p) => collectSchemaRefs(p.schema, components?.schemas));
3575
+ const usedSchemaRefs = [...new Set([...bodyRefs, ...paramRefs])];
3576
+ const responseKeys = Object.keys(operation.responses ?? {});
3577
+ const successStatus = responseKeys.filter((s) => s.startsWith("2")).map((s) => Number.parseInt(s, 10)).toSorted((a, b) => a - b)[0] ?? 200;
3578
+ const errorStatuses = responseKeys.filter((s) => (s.startsWith("4") || s.startsWith("5")) && s !== "default").map((s) => Number.parseInt(s, 10)).toSorted((a, b) => a - b);
3579
+ const security = extractSecurity(operation.security, spec.security, components?.securitySchemes);
3580
+ return [{
3581
+ operationId: resolveOperationId(operation, method, path),
3582
+ method: method.toUpperCase(),
3583
+ path,
3584
+ summary: operation.summary || "",
3585
+ tag: operation.tags?.[0],
3586
+ pathParams,
3587
+ queryParams,
3588
+ headerParams,
3589
+ requestBody,
3590
+ successStatus,
3591
+ errorStatuses,
3592
+ security,
3593
+ usedSchemaRefs
3594
+ }];
3595
+ });
3596
+ });
3597
+ }
3598
+ function quoteSingle(s) {
3599
+ return `'${s.replaceAll("\\", "\\\\").replaceAll("'", "\\'").replaceAll("\n", "\\n").replaceAll("\r", "\\r").replaceAll(" ", "\\t")}'`;
3600
+ }
3601
+ function escapeTemplateLiteral(s) {
3602
+ return s.replaceAll("\\", "\\\\").replaceAll("`", "\\`").replaceAll("${", "\\${");
3603
+ }
3604
+ function resolveNonExistent(schema, schemas) {
3605
+ return nonExistentPathValue(schema.$ref && schemas ? schemas[schema.$ref.replace("#/components/schemas/", "")] ?? schema : schema);
3606
+ }
3607
+ function makeAuthHeader(sec) {
3608
+ switch (sec.type) {
3609
+ case "bearer":
3610
+ case "oauth2": return "'Authorization':`Bearer ${faker.string.alphanumeric(32)}`";
3611
+ case "basic": return "'Authorization':`Basic ${btoa(`${faker.internet.username()}:${faker.internet.password()}`)}`";
3612
+ case "apiKey":
3613
+ if (sec.in === "header") return `${quoteSingle(sec.name)}:faker.string.alphanumeric(32)`;
3614
+ if (sec.in === "cookie") return `'Cookie':\`${escapeTemplateLiteral(sec.name)}=\${faker.string.alphanumeric(32)}\``;
3615
+ return "";
3616
+ default: return "";
3617
+ }
3618
+ }
3619
+ function makeTestCase(tc, prefix = "", schemas) {
3620
+ const fullPath = `${prefix && prefix !== "/" ? prefix : ""}${tc.path}`;
3621
+ const escapedFullPath = escapeTemplateLiteral(fullPath);
3622
+ const testPath = tc.pathParams.reduce((path, param) => path.replace(`{${param.name}}`, `\${${toSafeIdentifier(param.name)}}`), escapedFullPath);
3623
+ const pathSetup = tc.pathParams.map((param) => `const ${toSafeIdentifier(param.name)}=${param.fakerCode}`);
3624
+ const querySetup = tc.queryParams.map((param) => `const ${toSafeIdentifier(param.name)}=${param.fakerCode}`);
3625
+ const queryParts = tc.queryParams.map((param) => `${escapeTemplateLiteral(param.name)}=\${encodeURIComponent(String(${toSafeIdentifier(param.name)}))}`);
3626
+ const queryString = queryParts.length > 0 ? `?${queryParts.join("&")}` : "";
3627
+ const authQueryParts = tc.security.filter((sec) => sec.type === "apiKey" && sec.in === "query").map((sec) => `${escapeTemplateLiteral(sec.name)}=\${faker.string.alphanumeric(32)}`);
3628
+ const authQueryString = authQueryParts.length > 0 ? queryString ? `&${authQueryParts.join("&")}` : `?${authQueryParts.join("&")}` : "";
3629
+ const requiredHeaderParams = tc.headerParams.filter((p) => p.required);
3630
+ const headerSetup = requiredHeaderParams.map((param) => `const ${toSafeIdentifier(param.name)}=${param.fakerCode}`);
3631
+ const headerEntries = requiredHeaderParams.map((param) => `${quoteSingle(param.name)}:String(${toSafeIdentifier(param.name)})`);
3632
+ const authHeaders = tc.security.map(makeAuthHeader).filter(Boolean);
3633
+ const { bodySetup, bodyOption, contentTypeHeader } = tc.requestBody ? {
3634
+ bodySetup: `const body=${tc.requestBody.fakerCode}`,
3635
+ bodyOption: ",body:JSON.stringify(body)",
3636
+ contentTypeHeader: "'Content-Type':'application/json'"
3637
+ } : {
3638
+ bodySetup: "",
3639
+ bodyOption: "",
3640
+ contentTypeHeader: ""
3641
+ };
3642
+ const headers = [...headerEntries, ...contentTypeHeader ? [contentTypeHeader] : []];
3643
+ const allHeaders = [...headers, ...authHeaders];
3644
+ const headersOption = allHeaders.length > 0 ? `,headers:{${allHeaders.join(",")}}` : "";
3645
+ const headersWithoutAuth = headers.length > 0 ? `,headers:{${headers.join(",")}}` : "";
3646
+ const summaryPart = tc.summary ? ` - ${tc.summary}` : "";
3647
+ const setupCode = [
3648
+ ...pathSetup,
3649
+ ...querySetup,
3650
+ ...headerSetup,
3651
+ bodySetup
3652
+ ].filter(Boolean).join("\n");
3653
+ const describeTitle = quoteSingle(`${tc.method} ${fullPath}`);
3654
+ const methodLiteral = quoteSingle(tc.method);
3655
+ return `${`describe(${describeTitle},()=>{it(${quoteSingle(`should return ${tc.successStatus}${summaryPart}`)},async()=>{${setupCode}\nconst res=await app.handle(new Request(\`http://localhost${testPath}${queryString}${authQueryString}\`,{method:${methodLiteral}${headersOption}${bodyOption}}))\nexpect(res.status).toBe(${tc.successStatus})})`}${tc.security.length > 0 ? `\nit('should return 401 without auth',async()=>{${setupCode}\nconst res=await app.handle(new Request(\`http://localhost${testPath}${queryString}\`,{method:${methodLiteral}${headersWithoutAuth}${bodyOption}}))\nexpect(res.status).toBe(401)})` : ""}${tc.pathParams.length > 0 && tc.errorStatuses.includes(404) ? (() => {
3656
+ const probes = tc.pathParams.map((param) => ({
3657
+ param,
3658
+ probe: resolveNonExistent(param.schema, schemas)
3659
+ }));
3660
+ if (probes.some(({ probe }) => probe === void 0)) return "";
3661
+ const probeSetup = probes.flatMap(({ param, probe }) => probe?.kind === "expr" ? [`const ${toSafeIdentifier(param.name)}=${probe.code}`] : []);
3662
+ const notFoundPath = probes.reduce((path, { param, probe }) => {
3663
+ const value = probe?.kind === "expr" ? `\${${toSafeIdentifier(param.name)}}` : probe?.value ?? "";
3664
+ return path.replace(`{${param.name}}`, value);
3665
+ }, escapedFullPath);
3666
+ return `\nit('should return 404 for non-existent resource',async()=>{${[
3667
+ ...probeSetup,
3668
+ ...querySetup,
3669
+ ...headerSetup,
3670
+ bodySetup
3671
+ ].filter(Boolean).join("\n")}\nconst res=await app.handle(new Request(\`http://localhost${notFoundPath}${queryString}\`,{method:${methodLiteral}${headersOption}${bodyOption}}))\nexpect(res.status).toBe(404)})`;
3672
+ })() : ""}})\n`;
3673
+ }
3674
+ function makeTagDescribes(testCases, spec, prefix) {
3675
+ return [...testCases.reduce((acc, tc) => {
3676
+ const tag = tc.tag || "default";
3677
+ return acc.set(tag, [...acc.get(tag) ?? [], tc]);
3678
+ }, /* @__PURE__ */ new Map()).entries()].map(([tag, cases]) => {
3679
+ const tagDescription = (spec.tags?.find((t) => t.name === tag))?.description || tag;
3680
+ const testCasesCode = cases.map((tc) => makeTestCase(tc, prefix, spec.components?.schemas)).join("");
3681
+ return `describe(${quoteSingle(tagDescription)},()=>{${testCasesCode}})\n`;
3682
+ }).join("");
3683
+ }
3684
+ function makeAppTestFile(spec, appImport, prefix, resourceFilter) {
3685
+ const testCases = resourceFilter ? extractTestCases(spec).filter((tc) => resourceFilter.has(resourceName(tc.path))) : extractTestCases(spec);
3686
+ const apiTitle = spec.info?.title || "API";
3687
+ const mockFunctions = makeMockFunctions(spec, new Set(testCases.flatMap((tc) => tc.usedSchemaRefs)));
3688
+ const tagDescribes = makeTagDescribes(testCases, spec, prefix);
3689
+ const body = `${mockFunctions ? `${mockFunctions}\n\n` : ""}describe(${quoteSingle(apiTitle)},()=>{${tagDescribes}})\n`;
3690
+ return `${`import{describe,it,expect}from'bun:test'${body.includes("faker.") ? `\nimport{faker}from'@faker-js/faker'` : ""}\nimport{app}from'${appImport}'\n`}\n${body}`;
3691
+ }
3692
+ function makeTestFile(spec, appImport = "..", prefix) {
3693
+ return makeAppTestFile(spec, appImport, prefix);
3694
+ }
3695
+ function makeColocatedTestEntries(spec, appImport = "../../index", prefix) {
3696
+ const resources = [...operationsByResource(spec).keys()];
3697
+ const resourcesWithCases = new Set(extractTestCases(spec).map((tc) => resourceName(tc.path)));
3698
+ return resources.filter((name) => resourcesWithCases.has(name)).map((name) => ({
3699
+ name,
3700
+ code: makeAppTestFile(spec, appImport, prefix, new Set([name]))
3701
+ }));
3702
+ }
3703
+ //#endregion
3704
+ //#region src/core/test/index.ts
3705
+ /** Writes `code`, keeping whatever the user already wrote in `output`. */
3706
+ function writeMerged(code, output) {
3707
+ return Effect.gen(function* () {
3708
+ const existing = yield* readFile(output);
3709
+ yield* emit(existing === null ? code : mergeSource(existing, code), path.dirname(output), output);
3710
+ });
3711
+ }
3712
+ function test(openAPI, output, options = {}) {
3713
+ return Effect.gen(function* () {
3714
+ const { appOutput = "src/index.ts", split = false, pathAlias, prefix } = options;
3715
+ const appOutputAbs = path.resolve(process.cwd(), appOutput);
3716
+ const appBase = path.basename(appOutput).replace(/\.ts$/u, "");
3717
+ const appImportFor = (fromFileAbs) => {
3718
+ if (pathAlias) return `${pathAlias.replace(/\/$/u, "")}/${appBase}`;
3719
+ const rel = path.relative(path.dirname(fromFileAbs), appOutputAbs).replaceAll("\\", "/").replace(/\.ts$/u, "");
3720
+ return rel.startsWith(".") ? rel : `./${rel}`;
3721
+ };
3722
+ if (split) {
3723
+ const modulesDir = path.join(path.dirname(appOutputAbs), "modules");
3724
+ const entries = makeColocatedTestEntries(openAPI, appImportFor(path.join(modulesDir, "resource", "index.test.ts")), prefix);
3725
+ yield* Effect.all(entries.map((entry) => writeMerged(entry.code, path.join(modulesDir, entry.name, "index.test.ts"))), { concurrency: "unbounded" });
3726
+ return `Generated ${entries.length} co-located test file(s) under ${path.posix.dirname(appOutput)}/modules/`;
3727
+ }
3728
+ if (output === void 0) return yield* new GenerateError({ message: "test.output is required when split is false" });
3729
+ yield* writeMerged(makeTestFile(openAPI, appImportFor(path.resolve(process.cwd(), output)), prefix), output);
3730
+ return `Generated test file written to ${output}`;
3731
+ });
3732
+ }
3733
+ //#endregion
3734
+ //#region src/core/type/index.ts
3735
+ function primitiveTypeToTs(t) {
3736
+ if (t === "string") return "string";
3737
+ if (t === "number" || t === "integer") return "number";
3738
+ if (t === "boolean") return "boolean";
3739
+ if (t === "null") return "null";
3740
+ return "unknown";
3741
+ }
3742
+ function tsName(name) {
3743
+ if (/^[A-Za-z_][A-Za-z0-9_]*$/u.test(name)) return name;
3744
+ return pascalCase(name);
3745
+ }
3746
+ function literalToTs(v) {
3747
+ if (v === null) return "null";
3748
+ if (typeof v === "string") return JSON.stringify(v);
3749
+ if (typeof v === "number" || typeof v === "boolean") return String(v);
3750
+ return JSON.stringify(v);
3751
+ }
3752
+ function isNullable(s) {
3753
+ if (s.nullable === true) return true;
3754
+ if (Array.isArray(s.type)) return s.type.some((t) => t === "null");
3755
+ return false;
3756
+ }
3757
+ function primaryType(s) {
3758
+ if (typeof s.type === "string") return s.type;
3759
+ if (Array.isArray(s.type)) return s.type.find((t) => t !== "null");
3760
+ }
3761
+ function schemaToTs(schema) {
3762
+ if (!schema) return "unknown";
3763
+ if (schema.$ref) {
3764
+ const raw = schema.$ref.split("/").pop();
3765
+ return raw ? tsName(decodeURIComponent(raw)) : "unknown";
3766
+ }
3767
+ const wrap = (inner) => isNullable(schema) ? `(${inner} | null)` : inner;
3768
+ if ("const" in schema && schema.const !== void 0) return wrap(literalToTs(schema.const));
3769
+ if (schema.enum && schema.enum.length > 0) return wrap(schema.enum.map(literalToTs).join(" | "));
3770
+ if (schema.oneOf) return wrap(schema.oneOf.map(schemaToTs).join(" | ") || "unknown");
3771
+ if (schema.anyOf) return wrap(schema.anyOf.map(schemaToTs).join(" | ") || "unknown");
3772
+ if (schema.allOf) return wrap(schema.allOf.map(schemaToTs).join(" & ") || "unknown");
3773
+ if (Array.isArray(schema.type)) {
3774
+ const nonNull = schema.type.filter((type) => type !== "null");
3775
+ const isObj = schema.properties !== void 0 || schema.additionalProperties !== void 0;
3776
+ const isArr = nonNull.includes("array");
3777
+ if (nonNull.length > 1 && !isObj && !isArr) return wrap(nonNull.map(primitiveTypeToTs).join(" | "));
3778
+ }
3779
+ const primary = primaryType(schema);
3780
+ if (primary === "array") {
3781
+ if (Array.isArray(schema.prefixItems) && schema.prefixItems.length > 0) return wrap(`[${schema.prefixItems.map(schemaToTs).join(", ")}]`);
3782
+ return wrap(`${schemaToTs(isSchemaArray(schema.items) ? schema.items[0] : schema.items)}[]`);
3783
+ }
3784
+ if (primary === "object" || schema.properties || schema.additionalProperties !== void 0) {
3785
+ const props = schema.properties ?? {};
3786
+ const required = new Set(schema.required);
3787
+ const entries = Object.entries(props).map(([key, value]) => {
3788
+ const opt = required.has(key) ? "" : "?";
3789
+ return `${JSON.stringify(key)}${opt}: ${schemaToTs(value)}`;
3790
+ });
3791
+ const additional = schema.additionalProperties;
3792
+ const propsTs = entries.length === 0 ? "" : `{ ${entries.join("; ")} }`;
3793
+ if (additional && typeof additional === "object") {
3794
+ const recordTs = `Record<string, ${schemaToTs(additional)}>`;
3795
+ return wrap(propsTs === "" ? recordTs : `${propsTs} & ${recordTs}`);
3796
+ }
3797
+ if (additional === true) {
3798
+ const recordTs = "Record<string, unknown>";
3799
+ return wrap(propsTs === "" ? recordTs : `${propsTs} & ${recordTs}`);
3800
+ }
3801
+ return wrap(propsTs === "" ? "{}" : propsTs);
3802
+ }
3803
+ if (primary === "null") return "null";
3804
+ if (primary !== void 0) return wrap(primitiveTypeToTs(primary));
3805
+ return wrap("unknown");
3806
+ }
3807
+ function pathToKeys(pathStr) {
3808
+ return pathStr.split("/").filter((s) => s.length > 0).map((seg) => seg.startsWith("{") && seg.endsWith("}") ? `:${seg.slice(1, -1)}` : seg);
3809
+ }
3810
+ function responseContentTs(resp, components) {
3811
+ if (isStringRef(resp) && resp.$ref) {
3812
+ const name = resp.$ref.split("/").pop();
3813
+ const resolved = name ? components?.responses?.[decodeURIComponent(name)] : void 0;
3814
+ if (!resolved) return "unknown";
3815
+ return responseContentTs(resolved, components);
3816
+ }
3817
+ const json = resp.content?.["application/json"];
3818
+ if (!json) return "unknown";
3819
+ return schemaToTs(json.schema);
3820
+ }
3821
+ function isMedia(entry) {
3822
+ return entry !== void 0 && !isStringRef(entry);
3823
+ }
3824
+ function requestBodyTs(body, components) {
3825
+ if (!body) return "unknown";
3826
+ if (isStringRef(body) && body.$ref) {
3827
+ const name = body.$ref.split("/").pop();
3828
+ const resolved = name ? components?.requestBodies?.[decodeURIComponent(name)] : void 0;
3829
+ if (!resolved) return "unknown";
3830
+ return requestBodyTs(resolved, components);
3831
+ }
3832
+ const content = "content" in body ? body.content : void 0;
3833
+ if (!content) return "unknown";
3834
+ for (const mediaType of [
3835
+ "application/json",
3836
+ "multipart/form-data",
3837
+ "application/x-www-form-urlencoded",
3838
+ "application/octet-stream"
3839
+ ]) {
3840
+ const entry = content[mediaType];
3841
+ if (!isMedia(entry)) continue;
3842
+ if (!entry.schema) continue;
3843
+ if (mediaType === "application/octet-stream") {
3844
+ const schema = entry.schema;
3845
+ if (!schema.$ref && (schema.format === "binary" || schema.type === "string")) return "Blob | File | ArrayBuffer";
3846
+ return schemaToTs(schema);
3847
+ }
3848
+ return schemaToTs(entry.schema);
3849
+ }
3850
+ for (const [, entry] of Object.entries(content)) {
3851
+ if (!isMedia(entry) || !entry.schema) continue;
3852
+ return schemaToTs(entry.schema);
3853
+ }
3854
+ return "unknown";
3855
+ }
3856
+ function resolveParameter(p, components) {
3857
+ if (isStringRef(p) && p.$ref) {
3858
+ const name = p.$ref.split("/").pop();
3859
+ if (!name) return void 0;
3860
+ return components?.parameters?.[decodeURIComponent(name)];
3861
+ }
3862
+ return "name" in p && "in" in p ? p : void 0;
3863
+ }
3864
+ function paramsTs(params, where, components) {
3865
+ const resolved = params.map((p) => resolveParameter(p, components)).filter((p) => p?.in === where);
3866
+ if (resolved.length === 0) return "{}";
3867
+ return `{ ${resolved.map((p) => {
3868
+ const opt = p.required ? "" : "?";
3869
+ return `${JSON.stringify(p.name)}${opt}: ${schemaToTs(p.schema)}`;
3870
+ }).join("; ")} }`;
3871
+ }
3872
+ const VALIDATION_ERROR_TS = `{ type: "validation"; on: string; summary?: string; message?: string; found?: unknown; property?: string; expected?: string }`;
3873
+ function makeResponseTs(operation, components) {
3874
+ const declared = Object.entries(operation.responses ?? {}).filter(([status]) => status !== "default");
3875
+ const entries = declared.map(([status, resp]) => {
3876
+ return `${status}: ${responseContentTs(resp, components)}`;
3877
+ });
3878
+ if (!declared.some(([status]) => status === "422")) entries.push(`422: ${VALIDATION_ERROR_TS}`);
3879
+ return `{ ${entries.join("; ")} }`;
3880
+ }
3881
+ function makeOperationTs(operation, components) {
3882
+ const params = operation.parameters ?? [];
3883
+ const body = requestBodyTs(operation.requestBody, components);
3884
+ const pathParams = paramsTs(params, "path", components);
3885
+ const queryParams = paramsTs(params, "query", components);
3886
+ const headerParams = paramsTs(params, "header", components);
3887
+ const headers = headerParams === "{}" ? "unknown" : headerParams;
3888
+ const response = makeResponseTs(operation, components);
3889
+ return `{ body: ${body}; params: ${pathParams}; query: ${queryParams === "{}" ? "unknown" : queryParams}; headers: ${headers}; response: ${response} }`;
3890
+ }
3891
+ function nestRoute(keys, method, opTs) {
3892
+ const leaf = `{ ${method}: ${opTs} }`;
3893
+ return keys.reduceRight((acc, key) => `{ ${JSON.stringify(key)}: ${acc} }`, leaf);
3894
+ }
3895
+ function makeRoutes(openAPI, prefix) {
3896
+ const out = [];
3897
+ const prefixKeys = prefix && prefix !== "/" ? pathToKeys(prefix) : [];
3898
+ for (const [pathStr, pathItem] of pathEntries(openAPI)) {
3899
+ if (!pathItem) continue;
3900
+ const keys = [...prefixKeys, ...pathToKeys(pathStr)];
3901
+ for (const method of HTTP_METHODS) {
3902
+ const operation = pathItem[method];
3903
+ if (!operation) continue;
3904
+ out.push(nestRoute(keys, method, makeOperationTs(operation, openAPI.components)));
3905
+ }
3906
+ }
3907
+ return out;
3908
+ }
3909
+ function collectRefs(openAPI) {
3910
+ const seen = /* @__PURE__ */ new Set();
3911
+ const queue = [];
3912
+ const components = openAPI.components;
3913
+ const enqueue = (s) => {
3914
+ if (!s) return;
3915
+ for (const name of collectSchemaRefs$1(s)) if (!seen.has(name)) {
3916
+ seen.add(name);
3917
+ queue.push(name);
3918
+ }
3919
+ };
3920
+ const walkContentMap = (content) => {
3921
+ for (const media of Object.values(content)) if (isMediaWithSchema(media)) enqueue(media.schema);
3922
+ };
3923
+ const walkRequestBody = (rb) => {
3924
+ if (!rb) return;
3925
+ if (isStringRef(rb) && rb.$ref) {
3926
+ const name = rb.$ref.split("/").pop();
3927
+ const resolved = name ? components?.requestBodies?.[decodeURIComponent(name)] : void 0;
3928
+ if (resolved?.content) walkContentMap(resolved.content);
3929
+ return;
3930
+ }
3931
+ if ("content" in rb && rb.content) walkContentMap(rb.content);
3932
+ };
3933
+ const walkResponse = (resp) => {
3934
+ if (isStringRef(resp) && resp.$ref) {
3935
+ const name = resp.$ref.split("/").pop();
3936
+ const resolved = name ? components?.responses?.[decodeURIComponent(name)] : void 0;
3937
+ if (resolved?.content) walkContentMap(resolved.content);
3938
+ return;
3939
+ }
3940
+ if (resp.content) walkContentMap(resp.content);
3941
+ };
3942
+ for (const [, pathItem] of pathEntries(openAPI)) {
3943
+ if (!pathItem) continue;
3944
+ for (const method of HTTP_METHODS) {
3945
+ const op = pathItem[method];
3946
+ if (!op) continue;
3947
+ walkRequestBody(op.requestBody);
3948
+ for (const p of op.parameters ?? []) if (isStringRef(p) && p.$ref) {
3949
+ const pname = p.$ref.split("/").pop();
3950
+ const resolved = pname ? components?.parameters?.[decodeURIComponent(pname)] : void 0;
3951
+ if (resolved) enqueue(resolved.schema);
3952
+ } else if ("schema" in p) enqueue(p.schema);
3953
+ for (const resp of Object.values(op.responses ?? {})) if (resp) walkResponse(resp);
3954
+ }
3955
+ }
3956
+ while (queue.length > 0) {
3957
+ const name = queue.shift();
3958
+ if (!name) continue;
3959
+ enqueue(components?.schemas?.[name]);
3960
+ }
3961
+ return [...seen];
3962
+ }
3963
+ function makeAppType(openAPI, prefix) {
3964
+ const refAliases = collectRefs(openAPI).map((name) => {
3965
+ const schema = openAPI.components?.schemas?.[name];
3966
+ return `type ${tsName(name)} = ${schemaToTs(schema)}`;
3967
+ });
3968
+ const routes = makeRoutes(openAPI, prefix);
3969
+ const routesTs = routes.length === 0 ? "{}" : routes.join(" & ");
3970
+ const SINGLETON = `{ decorator: {}; store: {}; derive: {}; resolve: {} }`;
3971
+ const EPHEMERAL = `{ typebox: {}; error: {} }`;
3972
+ const VOLATILE = `{ schema: {}; standaloneSchema: {}; macro: {}; macroFn: {}; parser: {}; response: {} }`;
3973
+ const TAIL = `{ derive: {}; resolve: {}; schema: {}; standaloneSchema: {}; response: {} }`;
3974
+ return {
3975
+ refAliases,
3976
+ appType: `export type App = Elysia<"", ${SINGLETON}, ${EPHEMERAL}, ${VOLATILE}, ${routesTs}, ${TAIL}, ${TAIL}>`,
3977
+ routes
3978
+ };
3979
+ }
3980
+ function types(openAPI, output, prefix) {
3981
+ return Effect.gen(function* () {
3982
+ const { refAliases, appType } = makeAppType(openAPI, prefix);
3983
+ yield* emit(`${[
3984
+ `import type { Elysia } from "elysia"`,
3985
+ refAliases.join("\n"),
3986
+ appType
3987
+ ].filter((s) => s.length > 0).join("\n\n")}\n`, path.dirname(output), output);
3988
+ return `Generated app type written to ${output}`;
3989
+ });
3990
+ }
3991
+ //#endregion
3992
+ //#region src/core/index.ts
3993
+ var core_exports = /* @__PURE__ */ __exportAll({
3994
+ HOOK_CONFIGS: () => HOOK_CONFIGS,
3995
+ callbacks: () => callbacks,
3996
+ components: () => components,
3997
+ eden: () => eden,
3998
+ elysia: () => elysia,
3999
+ examples: () => examples,
4000
+ headers: () => headers,
4001
+ hooks: () => hooks,
4002
+ links: () => links,
4003
+ makeAppType: () => makeAppType,
4004
+ makeJsDocs: () => makeJsDocs,
4005
+ mediaTypes: () => mediaTypes,
4006
+ mock: () => mock,
4007
+ parameters: () => parameters,
4008
+ pathItems: () => pathItems,
4009
+ requestBodies: () => requestBodies,
4010
+ responses: () => responses,
4011
+ schemaToTs: () => schemaToTs,
4012
+ schemas: () => schemas,
4013
+ securitySchemes: () => securitySchemes,
4014
+ test: () => test,
4015
+ types: () => types
4016
+ });
4017
+ //#endregion
4018
+ export { unlink as S, responses as _, hooks as a, fileSystemLayer as b, mediaTypes as c, links as d, securitySchemes as f, parameters as g, examples as h, mock as i, pathItems as l, requestBodies as m, types as n, elysia as o, headers as p, test as r, eden as s, core_exports as t, callbacks as u, schemas as v, readdir as x, components as y };