@moinax/orc 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js ADDED
@@ -0,0 +1,1491 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/cli.ts
4
+ import { Command } from "commander";
5
+ import path5 from "path";
6
+ import fs5 from "fs";
7
+ import { createJiti } from "jiti";
8
+
9
+ // src/generator/client-generator.ts
10
+ import fs3 from "fs";
11
+ import path3 from "path";
12
+
13
+ // src/generator/enum-registry.ts
14
+ import pluralizeLib2 from "pluralize";
15
+
16
+ // src/generator/utils.ts
17
+ import pluralizeLib from "pluralize";
18
+ function capitalize(str) {
19
+ return str.charAt(0).toUpperCase() + str.slice(1);
20
+ }
21
+ function camelCase(str) {
22
+ const result = str.replace(/[-_]+/g, "_").replace(/_([a-zA-Z])/g, (_, char) => char.toUpperCase()).replace(/_+/g, "");
23
+ return result.charAt(0).toLowerCase() + result.slice(1);
24
+ }
25
+ function pascalCase(str) {
26
+ return capitalize(camelCase(str));
27
+ }
28
+ function schemaConstToTypeName(schemaConstName) {
29
+ const withoutSuffix = schemaConstName.replace(/Schema$/, "");
30
+ return pascalCase(withoutSuffix);
31
+ }
32
+ function singularize(str) {
33
+ const match = str.match(/^(.*)([A-Z][a-z]+s)$/);
34
+ if (match) {
35
+ const prefix = match[1];
36
+ const lastWord = match[2];
37
+ return prefix + pluralizeLib.singular(lastWord);
38
+ }
39
+ return pluralizeLib.singular(str);
40
+ }
41
+ function isBooleanLikeEnum(values) {
42
+ if (!Array.isArray(values) || values.length !== 2) return false;
43
+ const sorted = [...values].sort();
44
+ return sorted[0] === "false" && sorted[1] === "true";
45
+ }
46
+ function getResourcePrefixedParamNames(methodName, resourceClassName) {
47
+ const singularResource = singularize(resourceClassName);
48
+ if (methodName.startsWith("get")) {
49
+ const rest = methodName.slice(3);
50
+ return {
51
+ schemaConstName: `get${singularResource}${rest}ParamsSchema`,
52
+ typeName: `Get${singularResource}${rest}Params`
53
+ };
54
+ }
55
+ return {
56
+ schemaConstName: `${camelCase(methodName)}${singularResource}ParamsSchema`,
57
+ typeName: `${pascalCase(methodName)}${singularResource}Params`
58
+ };
59
+ }
60
+ function validateFileName(name, context) {
61
+ if (!name || typeof name !== "string") {
62
+ throw new Error(`Invalid ${context}: must be a non-empty string`);
63
+ }
64
+ if (name.includes("..") || name.includes("/") || name.includes("\\")) {
65
+ throw new Error(`Invalid ${context}: contains path traversal characters`);
66
+ }
67
+ if (!/^[a-zA-Z][a-zA-Z0-9]*$/.test(name)) {
68
+ throw new Error(`Invalid ${context}: must be alphanumeric starting with a letter`);
69
+ }
70
+ }
71
+ function validateOutputPath(outputPath) {
72
+ if (!outputPath || typeof outputPath !== "string") {
73
+ throw new Error("Invalid output path: must be a non-empty string");
74
+ }
75
+ if (outputPath.includes("..")) {
76
+ throw new Error("Invalid output path: contains path traversal characters");
77
+ }
78
+ }
79
+ function isListResponse(responseSchema) {
80
+ if (!responseSchema) return false;
81
+ if (responseSchema.type === "object" && responseSchema.properties?.pagination) {
82
+ return true;
83
+ }
84
+ if (responseSchema.type === "array") {
85
+ return true;
86
+ }
87
+ if (responseSchema.type === "object" && responseSchema.properties) {
88
+ for (const arrayProp of ["data", "items", "results"]) {
89
+ const prop = responseSchema.properties[arrayProp];
90
+ if (prop?.type === "array") return true;
91
+ }
92
+ }
93
+ if (responseSchema.$ref) {
94
+ const refName = responseSchema.$ref.split("/").pop();
95
+ if (refName.includes("Paginated") || refName.includes("List")) {
96
+ return true;
97
+ }
98
+ }
99
+ return false;
100
+ }
101
+ function operationIdToMethodName(operationId, httpMethod, pathPattern, resourceName, responseSchema) {
102
+ const isList = isListResponse(responseSchema);
103
+ switch (httpMethod) {
104
+ case "get":
105
+ return isList ? "getList" : "getDetail";
106
+ case "post":
107
+ return "create";
108
+ case "put":
109
+ return "replace";
110
+ case "patch":
111
+ return "update";
112
+ case "delete":
113
+ return "delete";
114
+ default:
115
+ return operationId ? camelCase(operationId) : httpMethod;
116
+ }
117
+ }
118
+ function parsePathSegments(pathPattern) {
119
+ return pathPattern.split("/").filter((seg) => seg).map((seg) => ({
120
+ name: seg.startsWith("{") ? seg.slice(1, -1) : seg,
121
+ isParam: seg.startsWith("{"),
122
+ raw: seg
123
+ }));
124
+ }
125
+ function getResourcePath(pathPattern) {
126
+ const segments = parsePathSegments(pathPattern);
127
+ const resourcePath = [];
128
+ for (const seg of segments) {
129
+ if (!seg.isParam) {
130
+ resourcePath.push(seg.name);
131
+ }
132
+ }
133
+ return resourcePath;
134
+ }
135
+ function buildPathTree(paths) {
136
+ const tree = {
137
+ name: "root",
138
+ children: /* @__PURE__ */ new Map(),
139
+ operations: []
140
+ };
141
+ for (const [pathPattern, methods] of Object.entries(paths)) {
142
+ for (const [httpMethod, operation] of Object.entries(methods)) {
143
+ if (httpMethod === "parameters") continue;
144
+ const resourcePath = getResourcePath(pathPattern);
145
+ let current = tree;
146
+ for (const segment of resourcePath) {
147
+ if (!current.children.has(segment)) {
148
+ current.children.set(segment, {
149
+ name: segment,
150
+ children: /* @__PURE__ */ new Map(),
151
+ operations: []
152
+ });
153
+ }
154
+ current = current.children.get(segment);
155
+ }
156
+ current.operations.push({
157
+ pathPattern,
158
+ httpMethod,
159
+ operation
160
+ });
161
+ }
162
+ }
163
+ return tree;
164
+ }
165
+ function cleanSchemaName(name) {
166
+ if (name.endsWith("SchemaInput")) {
167
+ name = name.replace("SchemaInput", "Input");
168
+ } else if (name.endsWith("Schema")) {
169
+ name = name.replace("Schema", "");
170
+ }
171
+ if (name.includes("__")) {
172
+ const parts = name.split("__");
173
+ name = parts[parts.length - 1];
174
+ }
175
+ name = name.replace(/_+$/, "");
176
+ name = name.replace(/_([A-Za-z])/g, (_, char) => char.toUpperCase());
177
+ return name;
178
+ }
179
+
180
+ // src/generator/enum-registry.ts
181
+ var EnumRegistry = class _EnumRegistry {
182
+ enums = /* @__PURE__ */ new Map();
183
+ enumContexts = /* @__PURE__ */ new Map();
184
+ usedNames = /* @__PURE__ */ new Set();
185
+ static fingerprint(values) {
186
+ return JSON.stringify([...values].sort());
187
+ }
188
+ generateEnumNames(values, context) {
189
+ const { source, schemaName, propertyPath, paramName, resourceName } = context;
190
+ let baseName;
191
+ if (source === "schema" && schemaName && propertyPath) {
192
+ const cleanSchemaName2 = schemaName.replace(/Schema$/, "").replace(/SchemaInput$/, "").replace(/Input$/, "");
193
+ const pathParts = propertyPath.split(".");
194
+ const contextFromPath = pathParts.map((p) => pascalCase(singularize(p))).join("");
195
+ baseName = camelCase(cleanSchemaName2) + contextFromPath;
196
+ } else if (source === "queryParam" && resourceName && paramName) {
197
+ const singularResource = singularize(resourceName);
198
+ baseName = camelCase(singularResource) + pascalCase(paramName);
199
+ } else {
200
+ baseName = camelCase(values[0].toLowerCase());
201
+ }
202
+ const valuesConstName = pluralizeLib2.isPlural(baseName) ? baseName : pluralizeLib2.plural(baseName);
203
+ return {
204
+ valuesConstName,
205
+ schemaConstName: `${singularize(baseName)}Schema`,
206
+ typeName: pascalCase(singularize(baseName)),
207
+ values
208
+ };
209
+ }
210
+ register(values, context) {
211
+ const fingerprint = _EnumRegistry.fingerprint(values);
212
+ if (!this.enumContexts.has(fingerprint)) {
213
+ this.enumContexts.set(fingerprint, []);
214
+ }
215
+ this.enumContexts.get(fingerprint).push(context);
216
+ if (this.enums.has(fingerprint)) {
217
+ return this.enums.get(fingerprint);
218
+ }
219
+ let enumInfo = this.generateEnumNames(values, context);
220
+ let counter = 1;
221
+ const hasCollision = () => this.usedNames.has(enumInfo.valuesConstName) || this.usedNames.has(enumInfo.schemaConstName) || this.usedNames.has(enumInfo.typeName);
222
+ while (hasCollision()) {
223
+ const baseInfo = this.generateEnumNames(values, context);
224
+ enumInfo = {
225
+ valuesConstName: `${baseInfo.valuesConstName}${counter}`,
226
+ schemaConstName: `${baseInfo.schemaConstName.replace(/Schema$/, "")}${counter}Schema`,
227
+ typeName: `${baseInfo.typeName}${counter}`,
228
+ values: baseInfo.values
229
+ };
230
+ counter++;
231
+ }
232
+ this.usedNames.add(enumInfo.valuesConstName);
233
+ this.usedNames.add(enumInfo.schemaConstName);
234
+ this.usedNames.add(enumInfo.typeName);
235
+ this.enums.set(fingerprint, enumInfo);
236
+ return enumInfo;
237
+ }
238
+ has(values) {
239
+ return this.enums.has(_EnumRegistry.fingerprint(values));
240
+ }
241
+ get(values) {
242
+ return this.enums.get(_EnumRegistry.fingerprint(values));
243
+ }
244
+ getAll() {
245
+ return Array.from(this.enums.values());
246
+ }
247
+ generateEnumExports() {
248
+ const lines = [];
249
+ if (this.enums.size === 0) {
250
+ return "";
251
+ }
252
+ lines.push("// ============================================================================");
253
+ lines.push("// Extracted Enums");
254
+ lines.push("// ============================================================================");
255
+ lines.push("");
256
+ for (const enumInfo of this.enums.values()) {
257
+ const { valuesConstName, schemaConstName, typeName, values } = enumInfo;
258
+ const valuesStr = values.map((v) => `'${v}'`).join(", ");
259
+ lines.push(`export const ${valuesConstName} = [${valuesStr}] as const;`);
260
+ lines.push(`export const ${schemaConstName} = z.enum(${valuesConstName});`);
261
+ lines.push(`export type ${typeName} = z.output<typeof ${schemaConstName}>;`);
262
+ lines.push("");
263
+ }
264
+ return lines.join("\n");
265
+ }
266
+ };
267
+
268
+ // src/generator/resource-generator.ts
269
+ var ResourceGenerator = class {
270
+ schemas;
271
+ clientClassName;
272
+ stripPathPrefix;
273
+ enumRegistry;
274
+ paths;
275
+ pathTree;
276
+ generatedResources = /* @__PURE__ */ new Map();
277
+ collectedInlineSchemas = /* @__PURE__ */ new Map();
278
+ currentResourceName = null;
279
+ runtimePackage;
280
+ constructor(paths, schemas, clientClassName, options = {}) {
281
+ this.schemas = schemas || {};
282
+ this.clientClassName = clientClassName;
283
+ this.stripPathPrefix = options.stripPathPrefix || null;
284
+ this.enumRegistry = options.enumRegistry || new EnumRegistry();
285
+ this.runtimePackage = options.runtimePackage || "@moinax/orc";
286
+ this.paths = this.stripPathPrefix ? this.stripPrefixFromPaths(paths || {}) : paths || {};
287
+ this.pathTree = buildPathTree(this.paths);
288
+ }
289
+ collectAllInlineSchemas() {
290
+ this._collectInlineSchemasFromNode(this.pathTree, []);
291
+ return this.collectedInlineSchemas;
292
+ }
293
+ _collectInlineSchemasFromNode(node, parentPath) {
294
+ const nodeName = node.name === "root" ? "" : node.name;
295
+ const currentPath = [...parentPath, nodeName].filter(Boolean);
296
+ const operationMethodNames = /* @__PURE__ */ new Map();
297
+ for (const { pathPattern, httpMethod, operation } of node.operations) {
298
+ const responseSchemaObj = (operation.responses?.["200"] || operation.responses?.["201"])?.content?.["application/json"]?.schema;
299
+ const methodName = operationIdToMethodName(
300
+ operation.operationId,
301
+ httpMethod,
302
+ pathPattern,
303
+ node.name,
304
+ responseSchemaObj
305
+ );
306
+ const opKey = this.getOperationKey(pathPattern, httpMethod);
307
+ operationMethodNames.set(opKey, methodName);
308
+ }
309
+ const resourceClassName = this.getResourceClassName(currentPath);
310
+ for (const { pathPattern, httpMethod, operation } of node.operations) {
311
+ if (this.hasInlineRequestBody(operation)) {
312
+ const requestSchema = this.getRequestSchemaName(operation, pathPattern);
313
+ if (!requestSchema) {
314
+ const opKey = this.getOperationKey(pathPattern, httpMethod);
315
+ const methodName = operationMethodNames.get(opKey);
316
+ const schemaConstName = this.generateInlineSchemaName(resourceClassName, methodName, "body");
317
+ const bodySchema = operation.requestBody.content["application/json"].schema;
318
+ this.collectedInlineSchemas.set(schemaConstName, {
319
+ schema: bodySchema,
320
+ isInput: true,
321
+ typeName: `${pascalCase(resourceClassName)}${pascalCase(methodName)}Body`
322
+ });
323
+ }
324
+ }
325
+ }
326
+ for (const { pathPattern, httpMethod, operation } of node.operations) {
327
+ if (this.hasInlineResponseSchema(operation)) {
328
+ const responseSchema = this.getResponseSchemaName(operation, pathPattern);
329
+ if (!responseSchema) {
330
+ const opKey = this.getOperationKey(pathPattern, httpMethod);
331
+ const methodName = operationMethodNames.get(opKey);
332
+ const schemaConstName = this.generateInlineSchemaName(resourceClassName, methodName, "response");
333
+ const successResponse = operation.responses?.["200"] || operation.responses?.["201"];
334
+ const inlineSchema = successResponse.content["application/json"].schema;
335
+ this.collectedInlineSchemas.set(schemaConstName, {
336
+ schema: inlineSchema,
337
+ isInput: false,
338
+ typeName: `${pascalCase(resourceClassName)}${pascalCase(methodName)}Response`
339
+ });
340
+ }
341
+ }
342
+ }
343
+ for (const [, childNode] of node.children) {
344
+ this._collectInlineSchemasFromNode(childNode, currentPath);
345
+ }
346
+ }
347
+ stripPrefixFromPaths(paths) {
348
+ const result = {};
349
+ for (const [pathPattern, methods] of Object.entries(paths)) {
350
+ let newPath = pathPattern;
351
+ if (this.stripPathPrefix && pathPattern.startsWith(this.stripPathPrefix)) {
352
+ newPath = pathPattern.slice(this.stripPathPrefix.length) || "/";
353
+ }
354
+ result[newPath] = methods;
355
+ }
356
+ return result;
357
+ }
358
+ getResponseSchemaName(operation, pathPattern = null) {
359
+ const successResponse = operation.responses?.["200"] || operation.responses?.["201"];
360
+ if (!successResponse) return null;
361
+ const content = successResponse.content?.["application/json"];
362
+ if (!content?.schema) return null;
363
+ const schema = content.schema;
364
+ if (schema.$ref) {
365
+ const rawName = schema.$ref.split("/").pop();
366
+ return cleanSchemaName(rawName);
367
+ }
368
+ if (schema.type === "object") {
369
+ if (operation.operationId) {
370
+ const opId = operation.operationId;
371
+ const patterns = [
372
+ /^create[_-]?(.+)$/i,
373
+ /^update[_-]?(.+)$/i,
374
+ /^get[_-]?(.+)$/i,
375
+ /^(.+)[_-]?create$/i,
376
+ /^(.+)[_-]?update$/i,
377
+ /^(.+)[_-]?get$/i
378
+ ];
379
+ for (const pattern of patterns) {
380
+ const match = opId.match(pattern);
381
+ if (match) {
382
+ const entityName = pascalCase(match[1].replace(/[_-]/g, " "));
383
+ const schemaSchemaName = `${entityName}Schema`;
384
+ if (this.schemas[entityName] || this.schemas[schemaSchemaName]) {
385
+ return entityName;
386
+ }
387
+ }
388
+ }
389
+ }
390
+ if (pathPattern) {
391
+ const resourcePath = getResourcePath(pathPattern);
392
+ if (resourcePath.length > 0) {
393
+ const lastSegment = resourcePath[resourcePath.length - 1];
394
+ const entityName = pascalCase(singularize(lastSegment));
395
+ const schemaSchemaName = `${entityName}Schema`;
396
+ if (this.schemas[entityName] || this.schemas[schemaSchemaName]) {
397
+ return entityName;
398
+ }
399
+ }
400
+ }
401
+ }
402
+ if (schema.type === "object" && schema.properties) {
403
+ return null;
404
+ }
405
+ return null;
406
+ }
407
+ getRequestSchemaName(operation, pathPattern = null) {
408
+ const content = operation.requestBody?.content?.["application/json"];
409
+ if (!content?.schema) return null;
410
+ const schema = content.schema;
411
+ if (schema.$ref) {
412
+ const rawName = schema.$ref.split("/").pop();
413
+ return cleanSchemaName(rawName);
414
+ }
415
+ return null;
416
+ }
417
+ hasInlineRequestBody(operation) {
418
+ const content = operation.requestBody?.content?.["application/json"];
419
+ if (!content?.schema) return false;
420
+ return !content.schema.$ref && content.schema.type === "object";
421
+ }
422
+ hasInlineResponseSchema(operation) {
423
+ const successResponse = operation.responses?.["200"] || operation.responses?.["201"];
424
+ if (!successResponse) return false;
425
+ const content = successResponse.content?.["application/json"];
426
+ if (!content?.schema) return false;
427
+ const schema = content.schema;
428
+ if (schema.$ref) return false;
429
+ if (schema.type === "object" && schema.properties?.pagination) return false;
430
+ return schema.type === "object" && !!schema.properties;
431
+ }
432
+ generateInlineSchemaName(resourceName, methodName, purpose) {
433
+ const baseName = `${camelCase(resourceName)}${capitalize(methodName)}${capitalize(purpose)}Schema`;
434
+ return camelCase(baseName);
435
+ }
436
+ getPathParams(operation) {
437
+ return (operation.parameters || []).filter((p) => "in" in p && p.in === "path").map((p) => ({
438
+ name: p.name,
439
+ type: this.paramTypeToTs(p.schema),
440
+ required: p.required !== false
441
+ }));
442
+ }
443
+ getQueryParams(operation) {
444
+ return (operation.parameters || []).filter((p) => "in" in p && p.in === "query").map((p) => ({
445
+ name: p.name,
446
+ type: this.paramTypeToTs(p.schema),
447
+ required: p.required === true,
448
+ schema: p.schema
449
+ }));
450
+ }
451
+ paramTypeToTs(schema) {
452
+ if (!schema) return "string";
453
+ if (schema.enum) return schema.enum.map((v) => `'${v}'`).join(" | ");
454
+ switch (schema.type) {
455
+ case "integer":
456
+ case "number":
457
+ return "number";
458
+ case "boolean":
459
+ return "boolean";
460
+ default:
461
+ return "string";
462
+ }
463
+ }
464
+ paramToZod(param, resourceName) {
465
+ const { schema, name: paramName } = param;
466
+ if (!schema) return "z.string()";
467
+ let zodType;
468
+ if (schema.enum) {
469
+ if (isBooleanLikeEnum(schema.enum)) {
470
+ zodType = "z.boolean()";
471
+ } else {
472
+ const context = {
473
+ source: "queryParam",
474
+ resourceName: resourceName || this.currentResourceName || void 0,
475
+ paramName
476
+ };
477
+ const enumInfo = this.enumRegistry.register(schema.enum, context);
478
+ zodType = enumInfo.schemaConstName;
479
+ }
480
+ } else {
481
+ switch (schema.type) {
482
+ case "integer":
483
+ zodType = "z.number().int()";
484
+ break;
485
+ case "number":
486
+ zodType = "z.number()";
487
+ break;
488
+ case "boolean":
489
+ zodType = "z.boolean()";
490
+ break;
491
+ default:
492
+ zodType = "z.string()";
493
+ }
494
+ }
495
+ if (!param.required) {
496
+ zodType = `${zodType}.optional()`;
497
+ }
498
+ return zodType;
499
+ }
500
+ isPaginationParam(paramName) {
501
+ return ["page", "limit", "orderBy", "ordering"].includes(paramName);
502
+ }
503
+ getUniqueMethodName(operationId, httpMethod, pathPattern, usedNames, responseSchema) {
504
+ let methodName = operationIdToMethodName(operationId, httpMethod, pathPattern, "", responseSchema);
505
+ if (usedNames.has(methodName) && operationId) {
506
+ methodName = camelCase(operationId);
507
+ }
508
+ let finalName = methodName;
509
+ let counter = 1;
510
+ while (usedNames.has(finalName)) {
511
+ finalName = `${methodName}${counter}`;
512
+ counter++;
513
+ }
514
+ usedNames.add(finalName);
515
+ return finalName;
516
+ }
517
+ getOperationKey(pathPattern, httpMethod) {
518
+ return `${httpMethod}:${pathPattern}`;
519
+ }
520
+ getResourceClassName(pathSegments) {
521
+ return pathSegments.map((seg) => pascalCase(seg)).join("");
522
+ }
523
+ generateResourceNode(node, pathSegments) {
524
+ const parentImportPath = "../";
525
+ const resourceClassName = this.getResourceClassName(pathSegments);
526
+ this.currentResourceName = resourceClassName;
527
+ const lines = [];
528
+ const usedMethodNames = /* @__PURE__ */ new Set();
529
+ const schemaImports = /* @__PURE__ */ new Set();
530
+ let hasQueryParams = false;
531
+ let hasPaginatedResponse = false;
532
+ const childResources = [];
533
+ for (const [childName, childNode] of node.children) {
534
+ const childPath = [...pathSegments, childName];
535
+ const childClassName = this.getResourceClassName(childPath);
536
+ childResources.push({
537
+ propertyName: singularize(camelCase(childName)),
538
+ className: childClassName,
539
+ fileName: `${childClassName}.resource`
540
+ });
541
+ }
542
+ const operationMethodNames = /* @__PURE__ */ new Map();
543
+ let usesInlineZod = false;
544
+ for (const { pathPattern, httpMethod, operation } of node.operations) {
545
+ const successResponse = operation.responses?.["200"] || operation.responses?.["201"];
546
+ const responseContent = successResponse?.content?.["application/json"];
547
+ const responseSchemaObj = responseContent?.schema;
548
+ const methodName = this.getUniqueMethodName(
549
+ operation.operationId,
550
+ httpMethod,
551
+ pathPattern,
552
+ usedMethodNames,
553
+ responseSchemaObj
554
+ );
555
+ const opKey = this.getOperationKey(pathPattern, httpMethod);
556
+ operationMethodNames.set(opKey, methodName);
557
+ const queryParams = this.getQueryParams(operation);
558
+ if (queryParams.length > 0) {
559
+ hasQueryParams = true;
560
+ }
561
+ if (responseSchemaObj?.type === "object" && responseSchemaObj?.properties?.pagination) {
562
+ hasPaginatedResponse = true;
563
+ }
564
+ if (responseSchemaObj?.type === "object" && responseSchemaObj?.properties?.pagination && !responseSchemaObj?.properties?.data?.items?.$ref) {
565
+ usesInlineZod = true;
566
+ }
567
+ }
568
+ const typeImports = /* @__PURE__ */ new Map();
569
+ if (hasQueryParams) {
570
+ schemaImports.add("paginationParamsSchema");
571
+ }
572
+ if (hasPaginatedResponse) {
573
+ schemaImports.add("paginationResponseSchema");
574
+ typeImports.set("paginationResponseSchema", "PaginationResponse");
575
+ }
576
+ for (const { pathPattern, operation } of node.operations) {
577
+ const responseSchema = this.getResponseSchemaName(operation, pathPattern);
578
+ if (responseSchema) {
579
+ const schemaConst = `${camelCase(responseSchema)}Schema`;
580
+ schemaImports.add(schemaConst);
581
+ typeImports.set(schemaConst, pascalCase(responseSchema));
582
+ }
583
+ const requestSchema = this.getRequestSchemaName(operation, pathPattern);
584
+ if (requestSchema) {
585
+ const schemaConst = `${camelCase(requestSchema)}Schema`;
586
+ schemaImports.add(schemaConst);
587
+ typeImports.set(schemaConst, pascalCase(requestSchema));
588
+ }
589
+ }
590
+ const inlineBodySchemas = /* @__PURE__ */ new Map();
591
+ for (const { pathPattern, httpMethod, operation } of node.operations) {
592
+ if (this.hasInlineRequestBody(operation)) {
593
+ const requestSchema = this.getRequestSchemaName(operation, pathPattern);
594
+ if (!requestSchema) {
595
+ const opKey = this.getOperationKey(pathPattern, httpMethod);
596
+ const methodName = operationMethodNames.get(opKey);
597
+ const schemaConstName = this.generateInlineSchemaName(resourceClassName, methodName, "body");
598
+ const typeName = schemaConstToTypeName(schemaConstName);
599
+ inlineBodySchemas.set(opKey, { schemaConst: schemaConstName, typeName });
600
+ schemaImports.add(schemaConstName);
601
+ typeImports.set(schemaConstName, typeName);
602
+ }
603
+ }
604
+ }
605
+ const inlineResponseSchemas = /* @__PURE__ */ new Map();
606
+ for (const { pathPattern, httpMethod, operation } of node.operations) {
607
+ if (this.hasInlineResponseSchema(operation)) {
608
+ const responseSchema = this.getResponseSchemaName(operation, pathPattern);
609
+ if (!responseSchema) {
610
+ const opKey = this.getOperationKey(pathPattern, httpMethod);
611
+ const methodName = operationMethodNames.get(opKey);
612
+ const schemaConstName = this.generateInlineSchemaName(resourceClassName, methodName, "response");
613
+ const typeName = schemaConstToTypeName(schemaConstName);
614
+ inlineResponseSchemas.set(opKey, { schemaConst: schemaConstName, typeName });
615
+ schemaImports.add(schemaConstName);
616
+ typeImports.set(schemaConstName, typeName);
617
+ }
618
+ }
619
+ }
620
+ const needsZod = hasQueryParams || usesInlineZod;
621
+ if (needsZod) {
622
+ lines.push("import { z } from 'zod';");
623
+ lines.push("");
624
+ }
625
+ const hasParsing = node.operations.some(({ httpMethod, operation }) => {
626
+ const successResponse = operation.responses?.["200"] || operation.responses?.["201"];
627
+ const hasResponseParsing = !!successResponse?.content?.["application/json"]?.schema;
628
+ const hasBodyParsing = ["post", "put", "patch"].includes(httpMethod) && !!operation.requestBody;
629
+ const hasParamsParsing = this.getQueryParams(operation).length > 0;
630
+ return hasResponseParsing || hasBodyParsing || hasParamsParsing;
631
+ });
632
+ if (hasParsing) {
633
+ lines.push(`import { parseSchema } from '${this.runtimePackage}';`);
634
+ }
635
+ lines.push(`import { Resource } from '${parentImportPath}Resource';`);
636
+ const schemaImportPlaceholderIndex = lines.length;
637
+ lines.push("__SCHEMA_IMPORTS_PLACEHOLDER__");
638
+ for (const child of childResources) {
639
+ lines.push(`import { ${child.className}Resource } from './${child.fileName}';`);
640
+ }
641
+ lines.push("");
642
+ const queryParamsSchemas = [];
643
+ for (const { pathPattern, httpMethod, operation } of node.operations) {
644
+ const queryParams = this.getQueryParams(operation);
645
+ if (queryParams.length > 0) {
646
+ const opKey = this.getOperationKey(pathPattern, httpMethod);
647
+ const methodName = operationMethodNames.get(opKey);
648
+ const { schemaConstName, typeName } = getResourcePrefixedParamNames(methodName, resourceClassName);
649
+ const specificParams = queryParams.filter((p) => !this.isPaginationParam(p.name));
650
+ if (specificParams.length > 0) {
651
+ const props = specificParams.map((p) => {
652
+ const zodCode = this.paramToZod(p, resourceClassName);
653
+ if (p.schema?.enum) {
654
+ const enumInfo = this.enumRegistry.get(p.schema.enum);
655
+ if (enumInfo) {
656
+ schemaImports.add(enumInfo.schemaConstName);
657
+ }
658
+ }
659
+ return ` ${p.name}: ${zodCode}`;
660
+ });
661
+ queryParamsSchemas.push(
662
+ `export const ${schemaConstName} = paginationParamsSchema.extend({
663
+ ${props.join(",\n")},
664
+ });`
665
+ );
666
+ } else {
667
+ queryParamsSchemas.push(`export const ${schemaConstName} = paginationParamsSchema;`);
668
+ }
669
+ queryParamsSchemas.push(`export type ${typeName} = z.input<typeof ${schemaConstName}>;`);
670
+ queryParamsSchemas.push("");
671
+ }
672
+ }
673
+ if (queryParamsSchemas.length > 0) {
674
+ lines.push("// Query parameter schemas");
675
+ lines.push(queryParamsSchemas.join("\n"));
676
+ }
677
+ lines.push(`export class ${resourceClassName}Resource extends Resource {`);
678
+ for (const child of childResources) {
679
+ lines.push(` public ${child.propertyName}: ${child.className}Resource;`);
680
+ }
681
+ if (childResources.length > 0) {
682
+ lines.push("");
683
+ lines.push(
684
+ ` constructor(client: InstanceType<typeof import('${parentImportPath}${this.clientClassName}').default>) {`
685
+ );
686
+ lines.push(" super(client);");
687
+ for (const child of childResources) {
688
+ lines.push(` this.${child.propertyName} = new ${child.className}Resource(client);`);
689
+ }
690
+ lines.push(" }");
691
+ }
692
+ for (const { pathPattern, httpMethod, operation } of node.operations) {
693
+ const opKey = this.getOperationKey(pathPattern, httpMethod);
694
+ const methodName = operationMethodNames.get(opKey);
695
+ const pathParams = this.getPathParams(operation);
696
+ const queryParams = this.getQueryParams(operation);
697
+ const responseSchema = this.getResponseSchemaName(operation, pathPattern);
698
+ const requestSchema = this.getRequestSchemaName(operation, pathPattern);
699
+ const params = [];
700
+ for (const p of pathParams) {
701
+ params.push(`${p.name}: ${p.type}`);
702
+ }
703
+ if (queryParams.length > 0) {
704
+ const { typeName } = getResourcePrefixedParamNames(methodName, resourceClassName);
705
+ const hasRequired = queryParams.some((p) => p.required);
706
+ params.push(`params${hasRequired ? "" : "?"}: ${typeName}`);
707
+ }
708
+ const inlineBodySchema = inlineBodySchemas.get(opKey);
709
+ if (["post", "put", "patch"].includes(httpMethod) && operation.requestBody) {
710
+ if (requestSchema) {
711
+ const schemaConst = `${camelCase(requestSchema)}Schema`;
712
+ const typeName = typeImports.get(schemaConst);
713
+ params.push(`body: ${typeName}`);
714
+ } else if (inlineBodySchema) {
715
+ params.push(`body: ${inlineBodySchema.typeName}`);
716
+ } else {
717
+ params.push("body: Record<string, unknown>");
718
+ }
719
+ }
720
+ const fullPath = this.stripPathPrefix ? this.stripPathPrefix + pathPattern : pathPattern;
721
+ let pathTemplate = fullPath.replace(/\{(\w+)\}/g, "${$1}");
722
+ pathTemplate = "`" + pathTemplate + "`";
723
+ const successResponse = operation.responses?.["200"] || operation.responses?.["201"];
724
+ const responseContent = successResponse?.content?.["application/json"];
725
+ const responseSchemaObj = responseContent?.schema;
726
+ let returnType = "void";
727
+ let parseLogic = "";
728
+ if (responseSchemaObj) {
729
+ if (responseSchemaObj.type === "object" && responseSchemaObj.properties?.pagination) {
730
+ const dataRef = responseSchemaObj.properties.data?.items?.$ref;
731
+ if (dataRef) {
732
+ const rawItemSchema = dataRef.split("/").pop();
733
+ const itemSchema = cleanSchemaName(rawItemSchema);
734
+ const itemSchemaConst = `${camelCase(itemSchema)}Schema`;
735
+ const itemTypeName = pascalCase(itemSchema);
736
+ schemaImports.add(itemSchemaConst);
737
+ typeImports.set(itemSchemaConst, itemTypeName);
738
+ returnType = `{ pagination: PaginationResponse; data: ${itemTypeName}[] }`;
739
+ parseLogic = `const schema = z.object({ pagination: paginationResponseSchema, data: z.array(${itemSchemaConst}) }).describe('Paginated${itemTypeName}List');
740
+ return parseSchema(schema, response);`;
741
+ } else {
742
+ if (responseSchema) {
743
+ const itemSchemaConst = `${camelCase(responseSchema)}Schema`;
744
+ const itemTypeName = pascalCase(responseSchema);
745
+ schemaImports.add(itemSchemaConst);
746
+ typeImports.set(itemSchemaConst, itemTypeName);
747
+ returnType = `{ pagination: PaginationResponse; data: ${itemTypeName}[] }`;
748
+ parseLogic = `const schema = z.object({ pagination: paginationResponseSchema, data: z.array(${itemSchemaConst}) }).describe('Paginated${itemTypeName}List');
749
+ return parseSchema(schema, response);`;
750
+ } else {
751
+ returnType = `{ pagination: PaginationResponse; data: unknown[] }`;
752
+ parseLogic = `const schema = z.object({ pagination: paginationResponseSchema, data: z.array(z.unknown()) }).describe('PaginatedList');
753
+ return parseSchema(schema, response);`;
754
+ }
755
+ }
756
+ } else if (responseSchema) {
757
+ const schemaConstName = `${camelCase(responseSchema)}Schema`;
758
+ const typeName = pascalCase(responseSchema);
759
+ returnType = typeName;
760
+ parseLogic = `return parseSchema(${schemaConstName}, response);`;
761
+ schemaImports.add(schemaConstName);
762
+ typeImports.set(schemaConstName, typeName);
763
+ } else if (inlineResponseSchemas.get(opKey)) {
764
+ const inlineSchema = inlineResponseSchemas.get(opKey);
765
+ returnType = inlineSchema.typeName;
766
+ parseLogic = `return parseSchema(${inlineSchema.schemaConst}, response);`;
767
+ } else {
768
+ returnType = "unknown";
769
+ parseLogic = "return response;";
770
+ }
771
+ }
772
+ lines.push("");
773
+ lines.push(` async ${methodName}(${params.join(", ")}): Promise<${returnType}> {`);
774
+ if (queryParams.length > 0) {
775
+ const { schemaConstName } = getResourcePrefixedParamNames(methodName, resourceClassName);
776
+ lines.push(` const searchParams = new URLSearchParams();`);
777
+ lines.push(` if (params) {`);
778
+ lines.push(` const validated = parseSchema(${schemaConstName}, params);`);
779
+ lines.push(` Object.entries(validated).forEach(([key, value]) => {`);
780
+ lines.push(` if (value !== undefined) searchParams.set(key, String(value));`);
781
+ lines.push(` });`);
782
+ lines.push(` }`);
783
+ lines.push(` const query = searchParams.toString();`);
784
+ lines.push(` const url = query ? \`\${${pathTemplate}}?\${query}\` : ${pathTemplate};`);
785
+ }
786
+ const urlVar = queryParams.length > 0 ? "url" : pathTemplate;
787
+ const needsResponse = returnType !== "void";
788
+ const responsePrefix = needsResponse ? "const response = " : "";
789
+ const hasBodySchema = requestSchema || inlineBodySchema;
790
+ const hasBodyValidation = ["post", "put", "patch"].includes(httpMethod) && hasBodySchema;
791
+ const bodyVar = hasBodyValidation ? "validatedBody" : "body";
792
+ if (hasBodyValidation) {
793
+ const bodySchemaConst = requestSchema ? `${camelCase(requestSchema)}Schema` : inlineBodySchema.schemaConst;
794
+ lines.push(` const validatedBody = parseSchema(${bodySchemaConst}, body);`);
795
+ }
796
+ switch (httpMethod) {
797
+ case "get":
798
+ lines.push(` ${responsePrefix}await this.client.get(${urlVar});`);
799
+ break;
800
+ case "post":
801
+ lines.push(` ${responsePrefix}await this.client.post(${urlVar}, ${bodyVar});`);
802
+ break;
803
+ case "put":
804
+ lines.push(` ${responsePrefix}await this.client.put(${urlVar}, ${bodyVar});`);
805
+ break;
806
+ case "patch":
807
+ lines.push(` ${responsePrefix}await this.client.patch(${urlVar}, ${bodyVar});`);
808
+ break;
809
+ case "delete":
810
+ lines.push(` ${responsePrefix}await this.client.delete(${urlVar});`);
811
+ break;
812
+ }
813
+ if (parseLogic) {
814
+ lines.push(` ${parseLogic}`);
815
+ }
816
+ lines.push(" }");
817
+ }
818
+ lines.push("}");
819
+ const placeholderIndex = lines.findIndex((l) => l === "__SCHEMA_IMPORTS_PLACEHOLDER__");
820
+ if (placeholderIndex >= 0) {
821
+ if (schemaImports.size > 0 || typeImports.size > 0) {
822
+ const allImports = /* @__PURE__ */ new Set([...schemaImports, ...typeImports.values()]);
823
+ lines[placeholderIndex] = `import { ${Array.from(allImports).join(", ")} } from '${parentImportPath}schemas';`;
824
+ } else {
825
+ lines.splice(placeholderIndex, 1);
826
+ }
827
+ }
828
+ return {
829
+ className: resourceClassName,
830
+ code: lines.join("\n"),
831
+ children: childResources
832
+ };
833
+ }
834
+ generateFromTree(node, pathSegments = [], depth = 0) {
835
+ const resources = [];
836
+ for (const [childName, childNode] of node.children) {
837
+ const childPath = [...pathSegments, childName];
838
+ const childResources = this.generateFromTree(childNode, childPath, depth + 1);
839
+ resources.push(...childResources);
840
+ }
841
+ if (pathSegments.length > 0 && (node.operations.length > 0 || node.children.size > 0)) {
842
+ const resource = this.generateResourceNode(node, pathSegments);
843
+ resources.push({
844
+ pathSegments,
845
+ ...resource
846
+ });
847
+ }
848
+ return resources;
849
+ }
850
+ generateAll() {
851
+ this.collectAllInlineSchemas();
852
+ const resources = this.generateFromTree(this.pathTree);
853
+ const result = {};
854
+ for (const resource of resources) {
855
+ result[resource.className] = resource.code;
856
+ }
857
+ return {
858
+ resources: result,
859
+ tree: this.pathTree,
860
+ inlineSchemas: this.collectedInlineSchemas
861
+ };
862
+ }
863
+ };
864
+
865
+ // src/generator/zod-generator.ts
866
+ var ZodGenerator = class {
867
+ schemas;
868
+ generatedSchemas = /* @__PURE__ */ new Map();
869
+ schemaOrder = [];
870
+ inlineSchemas = /* @__PURE__ */ new Map();
871
+ enumRegistry;
872
+ usedDateSchemas = {
873
+ stringToDateSchema: false,
874
+ stringToDaySchema: false,
875
+ dateToStringSchema: false,
876
+ dayToStringSchema: false
877
+ };
878
+ currentSchemaName = null;
879
+ currentPropertyPath = null;
880
+ constructor(schemas, enumRegistry) {
881
+ this.schemas = schemas || {};
882
+ this.enumRegistry = enumRegistry || new EnumRegistry();
883
+ }
884
+ addInlineSchemas(inlineSchemas) {
885
+ this.inlineSchemas = inlineSchemas;
886
+ }
887
+ convertSchema(schema, schemaName = null, isTopLevel = false) {
888
+ if (!schema) return "z.unknown()";
889
+ if (schema.$ref) {
890
+ const refName = schema.$ref.split("/").pop();
891
+ const cleanedName = cleanSchemaName(refName);
892
+ return `${camelCase(cleanedName)}Schema`;
893
+ }
894
+ if (schema.anyOf) {
895
+ const options = schema.anyOf.map((s) => this.convertSchema(s, schemaName));
896
+ if (options.length === 2 && options.includes("z.null()")) {
897
+ const nonNull = options.find((o) => o !== "z.null()");
898
+ const isInputSchema = schemaName && (schemaName.includes("Input") || schemaName.includes("Create") || schemaName.includes("Update"));
899
+ return isInputSchema ? `${nonNull}.nullish()` : `${nonNull}.nullable()`;
900
+ }
901
+ return `z.union([${options.join(", ")}])`;
902
+ }
903
+ if (schema.oneOf) {
904
+ const options = schema.oneOf.map((s) => this.convertSchema(s, schemaName));
905
+ return `z.union([${options.join(", ")}])`;
906
+ }
907
+ if (schema.allOf) {
908
+ const schemas = schema.allOf.map((s) => this.convertSchema(s, schemaName));
909
+ if (schemas.length === 1) return schemas[0];
910
+ return schemas.reduce((acc, s) => `${acc}.merge(${s})`);
911
+ }
912
+ if (schema.const !== void 0) {
913
+ if (typeof schema.const === "string") {
914
+ return `z.literal('${schema.const}')`;
915
+ }
916
+ return `z.literal(${JSON.stringify(schema.const)})`;
917
+ }
918
+ const isNullable = schema.nullable === true;
919
+ let zodSchema;
920
+ switch (schema.type) {
921
+ case "string":
922
+ zodSchema = this.convertStringSchema(schema, schemaName);
923
+ break;
924
+ case "number":
925
+ case "integer":
926
+ zodSchema = this.convertNumberSchema(schema, schemaName);
927
+ break;
928
+ case "boolean":
929
+ zodSchema = "z.boolean()";
930
+ break;
931
+ case "array":
932
+ zodSchema = this.convertArraySchema(schema, schemaName);
933
+ break;
934
+ case "object":
935
+ zodSchema = this.convertObjectSchema(schema, schemaName);
936
+ break;
937
+ case "null":
938
+ return "z.null()";
939
+ default:
940
+ if (schema.enum) {
941
+ zodSchema = this.convertEnumSchema(schema, schemaName);
942
+ } else if (schema.properties) {
943
+ zodSchema = this.convertObjectSchema(schema, schemaName);
944
+ } else {
945
+ zodSchema = "z.unknown()";
946
+ }
947
+ }
948
+ if (isNullable) {
949
+ const isInputSchema = schemaName && (schemaName.includes("Input") || schemaName.includes("Create") || schemaName.includes("Update"));
950
+ zodSchema = isInputSchema ? `${zodSchema}.nullish()` : `${zodSchema}.nullable()`;
951
+ }
952
+ return zodSchema;
953
+ }
954
+ convertStringSchema(schema, schemaName = null) {
955
+ if (schema.enum) {
956
+ return this.convertEnumSchema(schema, schemaName);
957
+ }
958
+ if (schema.format === "date-time" || schema.format === "date") {
959
+ const isInputSchema = schemaName && (schemaName.includes("Input") || schemaName.includes("Create") || schemaName.includes("Update"));
960
+ const isDateTime = schema.format === "date-time";
961
+ if (isInputSchema) {
962
+ if (isDateTime) {
963
+ this.usedDateSchemas.dateToStringSchema = true;
964
+ return "dateToStringSchema";
965
+ } else {
966
+ this.usedDateSchemas.dayToStringSchema = true;
967
+ return "dayToStringSchema";
968
+ }
969
+ }
970
+ if (isDateTime) {
971
+ this.usedDateSchemas.stringToDateSchema = true;
972
+ return "stringToDateSchema";
973
+ } else {
974
+ this.usedDateSchemas.stringToDaySchema = true;
975
+ return "stringToDaySchema";
976
+ }
977
+ }
978
+ let zod = "z.string()";
979
+ if (schema.format === "uuid") {
980
+ zod = "z.string().uuid()";
981
+ } else if (schema.format === "email") {
982
+ zod = "z.string().email()";
983
+ } else if (schema.format === "uri") {
984
+ zod = "z.string().url()";
985
+ }
986
+ if (schema.minLength !== void 0) {
987
+ zod = `${zod}.min(${schema.minLength})`;
988
+ }
989
+ if (schema.maxLength !== void 0) {
990
+ zod = `${zod}.max(${schema.maxLength})`;
991
+ }
992
+ if (schema.pattern && !schema.format) {
993
+ zod = `${zod}.regex(/${schema.pattern}/)`;
994
+ }
995
+ return zod;
996
+ }
997
+ convertNumberSchema(schema, schemaName = null) {
998
+ const isInputSchema = schemaName && (schemaName.includes("Input") || schemaName.includes("Body") || schemaName.includes("Params") || schemaName === "InlineInput");
999
+ const baseType = isInputSchema ? "z.coerce.number()" : "z.number()";
1000
+ let zod = schema.type === "integer" ? `${baseType}.int()` : baseType;
1001
+ if (schema.minimum !== void 0) {
1002
+ zod = `${zod}.min(${schema.minimum})`;
1003
+ }
1004
+ if (schema.maximum !== void 0) {
1005
+ zod = `${zod}.max(${schema.maximum})`;
1006
+ }
1007
+ return zod;
1008
+ }
1009
+ convertArraySchema(schema, schemaName = null) {
1010
+ const itemSchema = schema.items ? this.convertSchema(schema.items, schemaName) : "z.unknown()";
1011
+ let zod = `z.array(${itemSchema})`;
1012
+ if (schema.minItems !== void 0) {
1013
+ zod = `${zod}.min(${schema.minItems})`;
1014
+ }
1015
+ if (schema.maxItems !== void 0) {
1016
+ zod = `${zod}.max(${schema.maxItems})`;
1017
+ }
1018
+ return zod;
1019
+ }
1020
+ convertObjectSchema(schema, schemaName = null) {
1021
+ if (!schema.properties || Object.keys(schema.properties).length === 0) {
1022
+ if (schema.additionalProperties && typeof schema.additionalProperties === "object") {
1023
+ const valueSchema = this.convertSchema(schema.additionalProperties, schemaName);
1024
+ return `z.record(z.string(), ${valueSchema})`;
1025
+ }
1026
+ return "z.object({})";
1027
+ }
1028
+ const required = new Set(schema.required || []);
1029
+ const properties = [];
1030
+ for (const [propName, propSchema] of Object.entries(schema.properties)) {
1031
+ const previousPropertyPath = this.currentPropertyPath;
1032
+ this.currentPropertyPath = previousPropertyPath ? `${previousPropertyPath}.${propName}` : propName;
1033
+ const propZod = this.convertSchema(propSchema, schemaName);
1034
+ const isRequired = required.has(propName);
1035
+ const finalProp = isRequired ? propZod : `${propZod}.optional()`;
1036
+ properties.push(` ${propName}: ${finalProp}`);
1037
+ this.currentPropertyPath = previousPropertyPath;
1038
+ }
1039
+ let zod = `z.object({
1040
+ ${properties.join(",\n")}
1041
+ })`;
1042
+ if (schema.additionalProperties === false) {
1043
+ zod = `${zod}.strict()`;
1044
+ }
1045
+ return zod;
1046
+ }
1047
+ convertEnumSchema(schema, schemaName = null) {
1048
+ const values = schema.enum;
1049
+ if (isBooleanLikeEnum(values)) {
1050
+ return "z.boolean()";
1051
+ }
1052
+ const context = {
1053
+ source: "schema",
1054
+ schemaName: this.currentSchemaName || schemaName || void 0,
1055
+ propertyPath: this.currentPropertyPath || void 0
1056
+ };
1057
+ const enumInfo = this.enumRegistry.register(values, context);
1058
+ return enumInfo.schemaConstName;
1059
+ }
1060
+ extractDependencies(schema) {
1061
+ const deps = /* @__PURE__ */ new Set();
1062
+ if (!schema) return deps;
1063
+ if (schema.$ref) {
1064
+ const refName = schema.$ref.split("/").pop();
1065
+ deps.add(refName);
1066
+ return deps;
1067
+ }
1068
+ if (schema.anyOf) {
1069
+ for (const s of schema.anyOf) {
1070
+ for (const dep of this.extractDependencies(s)) {
1071
+ deps.add(dep);
1072
+ }
1073
+ }
1074
+ }
1075
+ if (schema.oneOf) {
1076
+ for (const s of schema.oneOf) {
1077
+ for (const dep of this.extractDependencies(s)) {
1078
+ deps.add(dep);
1079
+ }
1080
+ }
1081
+ }
1082
+ if (schema.allOf) {
1083
+ for (const s of schema.allOf) {
1084
+ for (const dep of this.extractDependencies(s)) {
1085
+ deps.add(dep);
1086
+ }
1087
+ }
1088
+ }
1089
+ if (schema.items) {
1090
+ for (const dep of this.extractDependencies(schema.items)) {
1091
+ deps.add(dep);
1092
+ }
1093
+ }
1094
+ if (schema.properties) {
1095
+ for (const propSchema of Object.values(schema.properties)) {
1096
+ for (const dep of this.extractDependencies(propSchema)) {
1097
+ deps.add(dep);
1098
+ }
1099
+ }
1100
+ }
1101
+ if (schema.additionalProperties && typeof schema.additionalProperties === "object") {
1102
+ for (const dep of this.extractDependencies(schema.additionalProperties)) {
1103
+ deps.add(dep);
1104
+ }
1105
+ }
1106
+ return deps;
1107
+ }
1108
+ topologicalSort(schemaNames) {
1109
+ const visited = /* @__PURE__ */ new Set();
1110
+ const result = [];
1111
+ const visiting = /* @__PURE__ */ new Set();
1112
+ const visit = (name) => {
1113
+ if (visited.has(name)) return;
1114
+ if (visiting.has(name)) return;
1115
+ visiting.add(name);
1116
+ const schema = this.schemas[name];
1117
+ if (schema) {
1118
+ const deps = this.extractDependencies(schema);
1119
+ for (const dep of deps) {
1120
+ if (schemaNames.includes(dep)) {
1121
+ visit(dep);
1122
+ }
1123
+ }
1124
+ }
1125
+ visiting.delete(name);
1126
+ visited.add(name);
1127
+ result.push(name);
1128
+ };
1129
+ for (const name of schemaNames) {
1130
+ visit(name);
1131
+ }
1132
+ return result;
1133
+ }
1134
+ generateSchemas(runtimePackage) {
1135
+ const schemaOutput = [];
1136
+ for (const name of Object.keys(this.schemas)) {
1137
+ this.schemaOrder.push(name);
1138
+ }
1139
+ this.schemaOrder = this.topologicalSort(this.schemaOrder);
1140
+ const usedNames = /* @__PURE__ */ new Set();
1141
+ for (const name of this.schemaOrder) {
1142
+ const schema = this.schemas[name];
1143
+ this.currentSchemaName = name;
1144
+ this.currentPropertyPath = null;
1145
+ const zodSchema = this.convertSchema(schema, name, true);
1146
+ this.currentSchemaName = null;
1147
+ let cleanName = name;
1148
+ if (cleanName.endsWith("SchemaInput")) {
1149
+ cleanName = cleanName.replace("SchemaInput", "Input");
1150
+ } else if (cleanName.endsWith("Schema")) {
1151
+ cleanName = cleanName.replace("Schema", "");
1152
+ }
1153
+ const schemaConstName = camelCase(cleanName) + "Schema";
1154
+ const typeName = pascalCase(cleanName);
1155
+ if (usedNames.has(schemaConstName) || usedNames.has(typeName)) {
1156
+ continue;
1157
+ }
1158
+ usedNames.add(schemaConstName);
1159
+ usedNames.add(typeName);
1160
+ schemaOutput.push(`export const ${schemaConstName} = ${zodSchema}.describe('${typeName}');`);
1161
+ const isInputType = /(?:Body|Input|Params)$/.test(cleanName);
1162
+ const inferType = isInputType ? "z.input" : "z.output";
1163
+ schemaOutput.push(`export type ${typeName} = ${inferType}<typeof ${schemaConstName}>;`);
1164
+ schemaOutput.push("");
1165
+ }
1166
+ this.enumRegistry.register(["desc", "asc"], {
1167
+ source: "schema",
1168
+ schemaName: "PaginationParams",
1169
+ propertyPath: "ordering"
1170
+ });
1171
+ const orderingEnumInfo = this.enumRegistry.get(["desc", "asc"]);
1172
+ schemaOutput.push("// Common pagination schemas");
1173
+ schemaOutput.push(`export const paginationParamsSchema = z.object({
1174
+ page: z.number().optional(),
1175
+ limit: z.number().optional(),
1176
+ orderBy: z.string().optional(),
1177
+ ordering: ${orderingEnumInfo.schemaConstName}.optional(),
1178
+ }).describe('PaginationParams');`);
1179
+ schemaOutput.push("export type PaginationParams = z.input<typeof paginationParamsSchema>;");
1180
+ schemaOutput.push("");
1181
+ schemaOutput.push(`export const paginationResponseSchema = z.object({
1182
+ page: z.number(),
1183
+ pages: z.number(),
1184
+ limit: z.number(),
1185
+ total: z.number(),
1186
+ }).describe('PaginationResponse');`);
1187
+ schemaOutput.push("export type PaginationResponse = z.output<typeof paginationResponseSchema>;");
1188
+ schemaOutput.push("");
1189
+ if (this.inlineSchemas.size > 0) {
1190
+ schemaOutput.push("// Inline request/response schemas");
1191
+ for (const [schemaName, schemaInfo] of this.inlineSchemas) {
1192
+ const { schema, isInput, typeName } = schemaInfo;
1193
+ const contextName = isInput ? "InlineInput" : "InlineOutput";
1194
+ const zodSchema = this.convertSchema(schema, contextName, true);
1195
+ schemaOutput.push(`export const ${schemaName} = ${zodSchema}.describe('${typeName}');`);
1196
+ const inferType = isInput ? "z.input" : "z.output";
1197
+ schemaOutput.push(`export type ${typeName} = ${inferType}<typeof ${schemaName}>;`);
1198
+ schemaOutput.push("");
1199
+ }
1200
+ }
1201
+ const output = [];
1202
+ output.push("import { z } from 'zod';");
1203
+ const usedDateSchemasList = Object.entries(this.usedDateSchemas).filter(([_, used]) => used).map(([name]) => name);
1204
+ if (usedDateSchemasList.length > 0) {
1205
+ output.push(`import { ${usedDateSchemasList.join(", ")} } from '${runtimePackage}';`);
1206
+ }
1207
+ output.push("");
1208
+ const enumExports = this.enumRegistry.generateEnumExports();
1209
+ if (enumExports) {
1210
+ output.push(enumExports);
1211
+ }
1212
+ output.push(...schemaOutput);
1213
+ return output.join("\n");
1214
+ }
1215
+ };
1216
+
1217
+ // src/generator/file-writer.ts
1218
+ import fs from "fs";
1219
+ import path from "path";
1220
+ import prettier from "prettier";
1221
+ async function formatCode(code, filepath) {
1222
+ try {
1223
+ const options = await prettier.resolveConfig(filepath);
1224
+ return prettier.format(code, { ...options, parser: "typescript" });
1225
+ } catch (error) {
1226
+ console.warn(`Warning: Could not format ${filepath}:`, error.message);
1227
+ return code;
1228
+ }
1229
+ }
1230
+ async function writeFile(filepath, content) {
1231
+ const dir = path.dirname(filepath);
1232
+ if (!fs.existsSync(dir)) {
1233
+ fs.mkdirSync(dir, { recursive: true });
1234
+ }
1235
+ const formatted = await formatCode(content, filepath);
1236
+ fs.writeFileSync(filepath, formatted);
1237
+ console.log(`Generated: ${filepath}`);
1238
+ }
1239
+
1240
+ // src/generator/spec-loader.ts
1241
+ import fs2 from "fs";
1242
+ import path2 from "path";
1243
+ async function loadSpec(specPath) {
1244
+ if (specPath.startsWith("http://") || specPath.startsWith("https://")) {
1245
+ console.log(`Fetching OpenAPI spec from ${specPath}...`);
1246
+ const response = await fetch(specPath);
1247
+ if (!response.ok) {
1248
+ throw new Error(`Failed to fetch OpenAPI spec: ${response.statusText}`);
1249
+ }
1250
+ const spec2 = await response.json();
1251
+ console.log(`OpenAPI spec version: ${spec2.openapi}`);
1252
+ console.log(`API title: ${spec2.info.title}`);
1253
+ return spec2;
1254
+ }
1255
+ const resolvedPath = path2.resolve(specPath);
1256
+ console.log(`Reading OpenAPI spec from ${resolvedPath}...`);
1257
+ if (!fs2.existsSync(resolvedPath)) {
1258
+ throw new Error(`OpenAPI spec file not found: ${resolvedPath}`);
1259
+ }
1260
+ const content = fs2.readFileSync(resolvedPath, "utf-8");
1261
+ let spec;
1262
+ if (resolvedPath.endsWith(".yaml") || resolvedPath.endsWith(".yml")) {
1263
+ throw new Error("YAML specs are not supported yet. Please use JSON format.");
1264
+ } else {
1265
+ spec = JSON.parse(content);
1266
+ }
1267
+ console.log(`OpenAPI spec version: ${spec.openapi}`);
1268
+ console.log(`API title: ${spec.info.title}`);
1269
+ return spec;
1270
+ }
1271
+
1272
+ // src/generator/client-generator.ts
1273
+ async function generateClient(config, options = {}) {
1274
+ const { name, output } = config;
1275
+ const specUrl = options.specOverride || config.spec;
1276
+ const shouldWrite = options.write !== false;
1277
+ const runtimePackage = options.runtimePackage || "@moinax/orc";
1278
+ validateOutputPath(output);
1279
+ const outputDir = path3.join(output, "generated");
1280
+ const clientClassName = `${name}Client`;
1281
+ validateFileName(clientClassName, "client class name");
1282
+ console.log(`
1283
+ ${"=".repeat(60)}`);
1284
+ console.log(`Generating ${clientClassName}...`);
1285
+ console.log(`${"=".repeat(60)}`);
1286
+ if (shouldWrite && fs3.existsSync(outputDir)) {
1287
+ fs3.rmSync(outputDir, { recursive: true });
1288
+ console.log(`
1289
+ Cleaned up ${outputDir}`);
1290
+ }
1291
+ const spec = await loadSpec(specUrl);
1292
+ const enumRegistry = new EnumRegistry();
1293
+ const resourceGenerator = new ResourceGenerator(spec.paths, spec.components?.schemas, clientClassName, {
1294
+ stripPathPrefix: config.stripPathPrefix,
1295
+ enumRegistry,
1296
+ runtimePackage
1297
+ });
1298
+ console.log("\nGenerating resource classes...");
1299
+ const { resources, tree, inlineSchemas } = resourceGenerator.generateAll();
1300
+ console.log("\nGenerating Zod schemas...");
1301
+ const zodGenerator = new ZodGenerator(spec.components?.schemas, enumRegistry);
1302
+ zodGenerator.addInlineSchemas(inlineSchemas);
1303
+ const schemasCode = zodGenerator.generateSchemas(runtimePackage);
1304
+ const generatedFiles = [];
1305
+ generatedFiles.push({
1306
+ path: path3.join(outputDir, "schemas.ts"),
1307
+ content: schemasCode
1308
+ });
1309
+ const resourceNames = [];
1310
+ for (const [resourceName, code] of Object.entries(resources)) {
1311
+ validateFileName(resourceName, "resource name");
1312
+ generatedFiles.push({
1313
+ path: path3.join(outputDir, "resources", `${resourceName}.resource.ts`),
1314
+ content: code
1315
+ });
1316
+ resourceNames.push(resourceName);
1317
+ }
1318
+ const resourceIndexCode = resourceNames.map((resourceName) => `export { ${resourceName}Resource } from './${resourceName}.resource';`).join("\n");
1319
+ generatedFiles.push({
1320
+ path: path3.join(outputDir, "resources", "index.ts"),
1321
+ content: resourceIndexCode
1322
+ });
1323
+ const resourceBaseCode = `import type ${clientClassName} from './${clientClassName}';
1324
+ import { Resource as BaseResource } from '${runtimePackage}';
1325
+
1326
+ export class Resource extends BaseResource {
1327
+ protected declare client: ${clientClassName};
1328
+
1329
+ constructor(client: ${clientClassName}) {
1330
+ super(client);
1331
+ }
1332
+ }
1333
+ `;
1334
+ generatedFiles.push({
1335
+ path: path3.join(outputDir, "Resource.ts"),
1336
+ content: resourceBaseCode
1337
+ });
1338
+ const topLevelResources = [];
1339
+ for (const [childName] of tree.children) {
1340
+ const className = pascalCase(childName);
1341
+ topLevelResources.push({
1342
+ propertyName: singularize(camelCase(childName)),
1343
+ className
1344
+ });
1345
+ }
1346
+ console.log(`
1347
+ Generating ${clientClassName}...`);
1348
+ const resourceImports = topLevelResources.map((r) => `${r.className}Resource`).join(",\n ");
1349
+ const resourceProperties = topLevelResources.map((r) => `public ${r.propertyName}: ${r.className}Resource;`).join("\n ");
1350
+ const resourceInstantiations = topLevelResources.map((r) => `this.${r.propertyName} = new ${r.className}Resource(this);`).join("\n ");
1351
+ const clientCode = `import { Client, ClientOptions } from '${runtimePackage}';
1352
+
1353
+ import {
1354
+ ${resourceImports},
1355
+ } from './resources';
1356
+
1357
+ export default class ${clientClassName} extends Client {
1358
+ ${resourceProperties}
1359
+
1360
+ constructor(baseUrl: string, options: ClientOptions = {}) {
1361
+ super(baseUrl, options);
1362
+ ${resourceInstantiations}
1363
+ }
1364
+ }
1365
+ `;
1366
+ generatedFiles.push({
1367
+ path: path3.join(outputDir, `${clientClassName}.ts`),
1368
+ content: clientCode
1369
+ });
1370
+ const mainIndexCode = `export { default as ${clientClassName} } from './${clientClassName}';
1371
+ export * from './schemas';
1372
+ export * from './resources';
1373
+ `;
1374
+ generatedFiles.push({
1375
+ path: path3.join(outputDir, "index.ts"),
1376
+ content: mainIndexCode
1377
+ });
1378
+ if (shouldWrite) {
1379
+ for (const file of generatedFiles) {
1380
+ await writeFile(file.path, file.content);
1381
+ }
1382
+ }
1383
+ console.log(`
1384
+ ${clientClassName} generation complete!`);
1385
+ return {
1386
+ name,
1387
+ resourceNames: topLevelResources.map((r) => r.className),
1388
+ files: shouldWrite ? void 0 : generatedFiles
1389
+ };
1390
+ }
1391
+
1392
+ // src/cli/commands/generate.ts
1393
+ async function runGenerate(config, options) {
1394
+ const runtimePackage = config.runtimePackage || "@moinax/orc";
1395
+ let clients = config.clients;
1396
+ if (options.client) {
1397
+ clients = clients.filter((c) => c.name.toLowerCase() === options.client.toLowerCase());
1398
+ if (clients.length === 0) {
1399
+ console.error(`Error: Client "${options.client}" not found in configuration.`);
1400
+ console.log("Available clients:", config.clients.map((c) => c.name).join(", "));
1401
+ process.exit(1);
1402
+ }
1403
+ }
1404
+ if (options.spec && !options.client) {
1405
+ console.error("Error: --spec requires --client to be specified");
1406
+ process.exit(1);
1407
+ }
1408
+ console.log("ORC - OpenAPI Rest Client Generator");
1409
+ console.log("====================================");
1410
+ console.log(`Generating ${clients.length} client(s): ${clients.map((c) => c.name).join(", ")}`);
1411
+ if (options.spec) {
1412
+ console.log(`Using custom spec: ${options.spec}`);
1413
+ }
1414
+ const results = [];
1415
+ for (const clientConfig of clients) {
1416
+ try {
1417
+ const result = await generateClient(clientConfig, {
1418
+ specOverride: options.spec,
1419
+ runtimePackage
1420
+ });
1421
+ results.push(result);
1422
+ } catch (error) {
1423
+ console.error(`
1424
+ Error generating ${clientConfig.name}Client:`, error.message);
1425
+ process.exit(1);
1426
+ }
1427
+ }
1428
+ console.log("\n" + "=".repeat(60));
1429
+ console.log("All clients generated successfully!");
1430
+ console.log("=".repeat(60));
1431
+ for (const result of results) {
1432
+ console.log(`
1433
+ ${result.name}Client resources: ${result.resourceNames.join(", ")}`);
1434
+ }
1435
+ }
1436
+
1437
+ // src/cli/commands/init.ts
1438
+ import fs4 from "fs";
1439
+ import path4 from "path";
1440
+ var CONFIG_TEMPLATE = `import { defineConfig } from '@moinax/orc/config';
1441
+
1442
+ export default defineConfig({
1443
+ clients: [
1444
+ {
1445
+ name: 'MyApi',
1446
+ spec: 'https://api.example.com/openapi.json',
1447
+ output: 'src/lib/api/my-api-client',
1448
+ },
1449
+ ],
1450
+ });
1451
+ `;
1452
+ async function runInit() {
1453
+ const configPath = path4.join(process.cwd(), "orc.config.ts");
1454
+ if (fs4.existsSync(configPath)) {
1455
+ console.error(`Config file already exists: ${configPath}`);
1456
+ process.exit(1);
1457
+ }
1458
+ fs4.writeFileSync(configPath, CONFIG_TEMPLATE);
1459
+ console.log(`Created config file: ${configPath}`);
1460
+ console.log("\nEdit the file to configure your API clients, then run:");
1461
+ console.log(" orc generate");
1462
+ }
1463
+
1464
+ // src/cli.ts
1465
+ var CONFIG_NAMES = ["orc.config.ts", "orc.config.js", "orc.config.mjs"];
1466
+ async function loadConfig() {
1467
+ const cwd = process.cwd();
1468
+ for (const configName of CONFIG_NAMES) {
1469
+ const configPath = path5.join(cwd, configName);
1470
+ if (fs5.existsSync(configPath)) {
1471
+ console.log(`Using config: ${configPath}`);
1472
+ const jiti = createJiti(cwd, { interopDefault: true });
1473
+ const config = await jiti.import(configPath);
1474
+ return config;
1475
+ }
1476
+ }
1477
+ throw new Error(
1478
+ `No config file found. Create one with 'orc init' or add one of: ${CONFIG_NAMES.join(", ")}`
1479
+ );
1480
+ }
1481
+ var program = new Command();
1482
+ program.name("orc").description("ORC - OpenAPI Rest Client Generator").version("0.1.0");
1483
+ program.command("generate").description("Generate TypeScript API clients from OpenAPI specs").option("-c, --client <name>", "Generate a specific client only").option("-s, --spec <url>", "Override spec URL (requires --client)").action(async (options) => {
1484
+ const config = await loadConfig();
1485
+ await runGenerate(config, options);
1486
+ });
1487
+ program.command("init").description("Scaffold a new orc.config.ts file").action(async () => {
1488
+ await runInit();
1489
+ });
1490
+ program.parse();
1491
+ //# sourceMappingURL=cli.js.map