auto-api-hooks 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js ADDED
@@ -0,0 +1,3308 @@
1
+ #!/usr/bin/env node
2
+ import { Command } from 'commander';
3
+ import pc2 from 'picocolors';
4
+ import { mkdir, writeFile, readFile } from 'fs/promises';
5
+ import { join, dirname, extname } from 'path';
6
+ import { parse } from 'yaml';
7
+ import SwaggerParser from '@apidevtools/swagger-parser';
8
+ import { buildClientSchema, buildSchema, isNonNullType, isScalarType, isObjectType, isInputObjectType, isEnumType, isUnionType, isListType } from 'graphql';
9
+
10
+ var _verbose = false;
11
+ function setVerbose(v) {
12
+ _verbose = v;
13
+ }
14
+ var logger = {
15
+ info(message) {
16
+ console.log(pc2.cyan("\u2139"), message);
17
+ },
18
+ success(message) {
19
+ console.log(pc2.green("\u2714"), message);
20
+ },
21
+ warn(message) {
22
+ console.log(pc2.yellow("\u26A0"), message);
23
+ },
24
+ error(message) {
25
+ console.error(pc2.red("\u2716"), message);
26
+ },
27
+ verbose(message) {
28
+ if (_verbose) {
29
+ console.log(pc2.gray(" \u25B8"), pc2.gray(message));
30
+ }
31
+ }
32
+ };
33
+
34
+ // src/ir/helpers.ts
35
+ var CURSOR_PARAM_NAMES = /* @__PURE__ */ new Set([
36
+ "cursor",
37
+ "after",
38
+ "before",
39
+ "page_token",
40
+ "pageToken",
41
+ "next_token",
42
+ "nextToken",
43
+ "starting_after",
44
+ "startingAfter",
45
+ "ending_before",
46
+ "endingBefore"
47
+ ]);
48
+ var OFFSET_PARAM_NAMES = /* @__PURE__ */ new Set([
49
+ "offset",
50
+ "skip"
51
+ ]);
52
+ var LIMIT_PARAM_NAMES = /* @__PURE__ */ new Set([
53
+ "limit",
54
+ "count",
55
+ "size",
56
+ "per_page",
57
+ "perPage",
58
+ "page_size",
59
+ "pageSize"
60
+ ]);
61
+ var PAGE_NUMBER_PARAM_NAMES = /* @__PURE__ */ new Set([
62
+ "page",
63
+ "page_number",
64
+ "pageNumber",
65
+ "p"
66
+ ]);
67
+ var CURSOR_RESPONSE_FIELDS = /* @__PURE__ */ new Set([
68
+ "next_cursor",
69
+ "nextCursor",
70
+ "cursor",
71
+ "next_page_token",
72
+ "nextPageToken",
73
+ "next_token",
74
+ "nextToken",
75
+ "endCursor",
76
+ "end_cursor",
77
+ "has_more",
78
+ "hasMore"
79
+ ]);
80
+ var ITEMS_FIELDS = /* @__PURE__ */ new Set([
81
+ "items",
82
+ "data",
83
+ "results",
84
+ "records",
85
+ "edges",
86
+ "nodes",
87
+ "entries",
88
+ "list",
89
+ "rows",
90
+ "content",
91
+ "hits"
92
+ ]);
93
+ function findItemsPath(type) {
94
+ if (type.kind !== "object") return void 0;
95
+ for (const prop of type.properties) {
96
+ if (ITEMS_FIELDS.has(prop.name) && prop.type.kind === "array") {
97
+ return [prop.name];
98
+ }
99
+ }
100
+ return void 0;
101
+ }
102
+ function findNextPagePath(type) {
103
+ if (type.kind !== "object") return void 0;
104
+ for (const prop of type.properties) {
105
+ if (CURSOR_RESPONSE_FIELDS.has(prop.name)) {
106
+ return [prop.name];
107
+ }
108
+ if ((prop.name === "pagination" || prop.name === "meta" || prop.name === "page_info" || prop.name === "pageInfo") && prop.type.kind === "object") {
109
+ for (const nested of prop.type.properties) {
110
+ if (CURSOR_RESPONSE_FIELDS.has(nested.name)) {
111
+ return [prop.name, nested.name];
112
+ }
113
+ }
114
+ }
115
+ }
116
+ return void 0;
117
+ }
118
+ function findPageCountPath(type) {
119
+ if (type.kind !== "object") return void 0;
120
+ const PAGE_COUNT_FIELDS = /* @__PURE__ */ new Set([
121
+ "total_pages",
122
+ "totalPages",
123
+ "total_count",
124
+ "totalCount",
125
+ "total",
126
+ "page_count",
127
+ "pageCount",
128
+ "last_page",
129
+ "lastPage"
130
+ ]);
131
+ for (const prop of type.properties) {
132
+ if (PAGE_COUNT_FIELDS.has(prop.name)) {
133
+ return [prop.name];
134
+ }
135
+ if ((prop.name === "pagination" || prop.name === "meta") && prop.type.kind === "object") {
136
+ for (const nested of prop.type.properties) {
137
+ if (PAGE_COUNT_FIELDS.has(nested.name)) {
138
+ return [prop.name, nested.name];
139
+ }
140
+ }
141
+ }
142
+ }
143
+ return void 0;
144
+ }
145
+ function detectPagination(op) {
146
+ const queryParamNames = new Set(op.queryParams.map((p) => p.name));
147
+ const responseType = op.response.type;
148
+ for (const name of CURSOR_PARAM_NAMES) {
149
+ if (queryParamNames.has(name)) {
150
+ const nextPagePath = findNextPagePath(responseType) ?? [name];
151
+ const itemsPath = findItemsPath(responseType) ?? [];
152
+ return {
153
+ strategy: "cursor",
154
+ pageParam: name,
155
+ nextPagePath,
156
+ itemsPath
157
+ };
158
+ }
159
+ }
160
+ for (const offsetName of OFFSET_PARAM_NAMES) {
161
+ if (queryParamNames.has(offsetName)) {
162
+ const hasLimit = [...LIMIT_PARAM_NAMES].some((n) => queryParamNames.has(n));
163
+ if (hasLimit) {
164
+ const itemsPath = findItemsPath(responseType) ?? [];
165
+ return {
166
+ strategy: "offset-limit",
167
+ pageParam: offsetName,
168
+ nextPagePath: [offsetName],
169
+ itemsPath
170
+ };
171
+ }
172
+ }
173
+ }
174
+ for (const pageName of PAGE_NUMBER_PARAM_NAMES) {
175
+ if (queryParamNames.has(pageName)) {
176
+ const itemsPath = findItemsPath(responseType) ?? [];
177
+ const nextPagePath = findPageCountPath(responseType) ?? [pageName];
178
+ return {
179
+ strategy: "page-number",
180
+ pageParam: pageName,
181
+ nextPagePath,
182
+ itemsPath
183
+ };
184
+ }
185
+ }
186
+ if (responseType.kind === "object") {
187
+ const cursorPath = findNextPagePath(responseType);
188
+ const items = findItemsPath(responseType);
189
+ if (cursorPath && items) {
190
+ const likelyCursorParam = [...CURSOR_PARAM_NAMES].find((n) => queryParamNames.has(n)) ?? [...PAGE_NUMBER_PARAM_NAMES].find((n) => queryParamNames.has(n));
191
+ if (likelyCursorParam) {
192
+ return {
193
+ strategy: "cursor",
194
+ pageParam: likelyCursorParam,
195
+ nextPagePath: cursorPath,
196
+ itemsPath: items
197
+ };
198
+ }
199
+ }
200
+ }
201
+ return void 0;
202
+ }
203
+ function applyPaginationDetection(spec) {
204
+ const operations = spec.operations.map((op) => {
205
+ if (op.method !== "GET" && op.method !== "QUERY") {
206
+ return op;
207
+ }
208
+ if (op.pagination) {
209
+ return op;
210
+ }
211
+ const pagination = detectPagination(op);
212
+ if (pagination) {
213
+ return { ...op, pagination };
214
+ }
215
+ return op;
216
+ });
217
+ return { ...spec, operations };
218
+ }
219
+ function isRef(obj) {
220
+ return typeof obj === "object" && obj !== null && "$ref" in obj;
221
+ }
222
+ var HTTP_METHODS = /* @__PURE__ */ new Set([
223
+ "get",
224
+ "post",
225
+ "put",
226
+ "delete",
227
+ "patch",
228
+ "head",
229
+ "options"
230
+ ]);
231
+ function convertSchema(schema) {
232
+ if (!schema) {
233
+ return { kind: "primitive", type: "unknown" };
234
+ }
235
+ if (schema.oneOf && schema.oneOf.length > 0) {
236
+ const variants = schema.oneOf.filter((s) => !isRef(s)).map((s) => convertSchema(s));
237
+ return { kind: "union", variants, description: schema.description };
238
+ }
239
+ if (schema.anyOf && schema.anyOf.length > 0) {
240
+ const variants = schema.anyOf.filter((s) => !isRef(s)).map((s) => convertSchema(s));
241
+ return { kind: "union", variants, description: schema.description };
242
+ }
243
+ if (schema.allOf && schema.allOf.length > 0) {
244
+ const merged = mergeAllOf(schema.allOf.filter((s) => !isRef(s)));
245
+ const result = convertSchema(merged);
246
+ if (schema.description && result.kind === "object") {
247
+ return { ...result, description: schema.description };
248
+ }
249
+ return result;
250
+ }
251
+ if (schema.enum && schema.enum.length > 0) {
252
+ return {
253
+ kind: "enum",
254
+ values: schema.enum,
255
+ name: schema.title,
256
+ description: schema.description
257
+ };
258
+ }
259
+ if (schema.type === "array") {
260
+ const arraySchema = schema;
261
+ const items = isRef(arraySchema.items) ? { kind: "primitive", type: "unknown" } : convertSchema(arraySchema.items);
262
+ return { kind: "array", items, description: schema.description };
263
+ }
264
+ if (schema.type === "object" || schema.properties || !schema.type && hasObjectShape(schema)) {
265
+ return convertObjectSchema(schema);
266
+ }
267
+ if (schema.type === "string" || schema.type === "number" || schema.type === "integer" || schema.type === "boolean") {
268
+ const result = {
269
+ kind: "primitive",
270
+ type: schema.type,
271
+ description: schema.description
272
+ };
273
+ if (schema.format) {
274
+ result.format = schema.format;
275
+ }
276
+ return result;
277
+ }
278
+ if (schema.nullable && schema.type) {
279
+ const inner = convertSchema({ ...schema, nullable: void 0 });
280
+ return {
281
+ kind: "union",
282
+ variants: [inner, { kind: "primitive", type: "null" }],
283
+ description: schema.description
284
+ };
285
+ }
286
+ return { kind: "primitive", type: "unknown", description: schema.description };
287
+ }
288
+ function hasObjectShape(schema) {
289
+ return !!(schema.properties || schema.additionalProperties);
290
+ }
291
+ function convertObjectSchema(schema) {
292
+ const requiredSet = new Set(schema.required ?? []);
293
+ const properties = [];
294
+ if (schema.properties) {
295
+ for (const [name, propSchema] of Object.entries(schema.properties)) {
296
+ if (isRef(propSchema)) continue;
297
+ properties.push({
298
+ name,
299
+ type: convertSchema(propSchema),
300
+ required: requiredSet.has(name),
301
+ description: propSchema.description
302
+ });
303
+ }
304
+ }
305
+ const result = {
306
+ kind: "object",
307
+ properties,
308
+ description: schema.description
309
+ };
310
+ if (schema.title) {
311
+ result.name = schema.title;
312
+ }
313
+ if (schema.additionalProperties !== void 0) {
314
+ if (typeof schema.additionalProperties === "boolean") {
315
+ result.additionalProperties = schema.additionalProperties;
316
+ } else if (!isRef(schema.additionalProperties)) {
317
+ result.additionalProperties = convertSchema(schema.additionalProperties);
318
+ }
319
+ }
320
+ return result;
321
+ }
322
+ function mergeAllOf(schemas) {
323
+ const merged = {
324
+ type: "object",
325
+ properties: {},
326
+ required: []
327
+ };
328
+ for (const schema of schemas) {
329
+ if (schema.properties) {
330
+ Object.assign(merged.properties, schema.properties);
331
+ }
332
+ if (schema.required) {
333
+ merged.required.push(...schema.required);
334
+ }
335
+ if (schema.description && !merged.description) {
336
+ merged.description = schema.description;
337
+ }
338
+ }
339
+ return merged;
340
+ }
341
+ function convertParameter(param) {
342
+ const schema = param.schema && !isRef(param.schema) ? param.schema : void 0;
343
+ return {
344
+ name: param.name,
345
+ required: param.required ?? param.in === "path",
346
+ type: convertSchema(schema),
347
+ description: param.description,
348
+ in: param.in
349
+ };
350
+ }
351
+ function convertRequestBody(body) {
352
+ const jsonContent = body.content["application/json"] ?? body.content["*/*"];
353
+ if (!jsonContent) {
354
+ const firstKey = Object.keys(body.content)[0];
355
+ if (!firstKey) return void 0;
356
+ const mediaType = body.content[firstKey];
357
+ return convertMediaTypeToBody(mediaType, firstKey, body);
358
+ }
359
+ return convertMediaTypeToBody(jsonContent, "application/json", body);
360
+ }
361
+ function convertMediaTypeToBody(media, contentType, body) {
362
+ const schema = media.schema && !isRef(media.schema) ? media.schema : void 0;
363
+ return {
364
+ required: body.required ?? false,
365
+ contentType,
366
+ type: convertSchema(schema),
367
+ description: body.description
368
+ };
369
+ }
370
+ function convertResponse(responses) {
371
+ const successCodes = ["200", "201"];
372
+ let responseObj;
373
+ let statusCode = 200;
374
+ for (const code of successCodes) {
375
+ const candidate = responses[code];
376
+ if (candidate && !isRef(candidate)) {
377
+ responseObj = candidate;
378
+ statusCode = parseInt(code, 10);
379
+ break;
380
+ }
381
+ }
382
+ if (!responseObj) {
383
+ for (const [code, value] of Object.entries(responses)) {
384
+ if (code.startsWith("2") && !isRef(value)) {
385
+ responseObj = value;
386
+ statusCode = parseInt(code, 10);
387
+ break;
388
+ }
389
+ }
390
+ }
391
+ if (!responseObj) {
392
+ const defaultResp = responses["default"];
393
+ if (defaultResp && !isRef(defaultResp)) {
394
+ responseObj = defaultResp;
395
+ statusCode = "default";
396
+ }
397
+ }
398
+ if (!responseObj) {
399
+ return {
400
+ statusCode: 200,
401
+ contentType: "application/json",
402
+ type: { kind: "primitive", type: "unknown" }
403
+ };
404
+ }
405
+ const content = responseObj.content;
406
+ if (!content) {
407
+ return {
408
+ statusCode,
409
+ contentType: "application/json",
410
+ type: { kind: "primitive", type: "unknown" },
411
+ description: responseObj.description
412
+ };
413
+ }
414
+ const jsonMedia = content["application/json"] ?? content["*/*"];
415
+ if (jsonMedia) {
416
+ const schema = jsonMedia.schema && !isRef(jsonMedia.schema) ? jsonMedia.schema : void 0;
417
+ return {
418
+ statusCode,
419
+ contentType: "application/json",
420
+ type: convertSchema(schema),
421
+ description: responseObj.description
422
+ };
423
+ }
424
+ const firstKey = Object.keys(content)[0];
425
+ if (firstKey) {
426
+ const media = content[firstKey];
427
+ const schema = media.schema && !isRef(media.schema) ? media.schema : void 0;
428
+ return {
429
+ statusCode,
430
+ contentType: firstKey,
431
+ type: convertSchema(schema),
432
+ description: responseObj.description
433
+ };
434
+ }
435
+ return {
436
+ statusCode,
437
+ contentType: "application/json",
438
+ type: { kind: "primitive", type: "unknown" },
439
+ description: responseObj.description
440
+ };
441
+ }
442
+ function generateOperationId(method, path) {
443
+ const segments = path.split("/").filter(Boolean).map((seg) => {
444
+ const clean = seg.replace(/[{}]/g, "");
445
+ return clean.charAt(0).toUpperCase() + clean.slice(1);
446
+ });
447
+ return method.toLowerCase() + segments.join("");
448
+ }
449
+ var openApiParser = {
450
+ canParse(input) {
451
+ if (typeof input !== "object" || input === null) return false;
452
+ const doc = input;
453
+ return typeof doc.openapi === "string" && doc.openapi.startsWith("3");
454
+ },
455
+ async parse(input, options) {
456
+ const derefed = await SwaggerParser.dereference(
457
+ input,
458
+ { dereference: { circular: "ignore" } }
459
+ );
460
+ const doc = derefed;
461
+ const types = /* @__PURE__ */ new Map();
462
+ if (doc.components?.schemas) {
463
+ for (const [name, schema] of Object.entries(doc.components.schemas)) {
464
+ if (!isRef(schema)) {
465
+ const converted = convertSchema(schema);
466
+ if (converted.kind === "object" && !converted.name) {
467
+ converted.name = name;
468
+ } else if (converted.kind === "enum" && !converted.name) {
469
+ converted.name = name;
470
+ }
471
+ types.set(name, converted);
472
+ }
473
+ }
474
+ }
475
+ const baseUrl = options?.baseUrl ?? (doc.servers && doc.servers.length > 0 ? doc.servers[0].url : "");
476
+ const operations = [];
477
+ if (doc.paths) {
478
+ for (const [path, pathItem] of Object.entries(doc.paths)) {
479
+ if (!pathItem) continue;
480
+ const pathLevelParams = (pathItem.parameters ?? []).filter((p) => !isRef(p));
481
+ for (const method of HTTP_METHODS) {
482
+ const operationObj = pathItem[method];
483
+ if (!operationObj) continue;
484
+ const opParams = (operationObj.parameters ?? []).filter((p) => !isRef(p));
485
+ const mergedParams = mergeParameters(pathLevelParams, opParams);
486
+ const pathParams = [];
487
+ const queryParams = [];
488
+ const headerParams = [];
489
+ for (const param of mergedParams) {
490
+ const converted = convertParameter(param);
491
+ switch (param.in) {
492
+ case "path":
493
+ pathParams.push(converted);
494
+ break;
495
+ case "query":
496
+ queryParams.push(converted);
497
+ break;
498
+ case "header":
499
+ headerParams.push(converted);
500
+ break;
501
+ }
502
+ }
503
+ let requestBody;
504
+ if (operationObj.requestBody && !isRef(operationObj.requestBody)) {
505
+ requestBody = convertRequestBody(operationObj.requestBody);
506
+ }
507
+ const response = convertResponse(
508
+ operationObj.responses
509
+ );
510
+ const tags = operationObj.tags && operationObj.tags.length > 0 ? operationObj.tags : ["default"];
511
+ const operationId = operationObj.operationId ?? generateOperationId(method, path);
512
+ operations.push({
513
+ operationId,
514
+ summary: operationObj.summary,
515
+ method: method.toUpperCase(),
516
+ path,
517
+ tags,
518
+ pathParams,
519
+ queryParams,
520
+ headerParams,
521
+ requestBody,
522
+ response,
523
+ deprecated: operationObj.deprecated ?? false
524
+ });
525
+ }
526
+ }
527
+ }
528
+ return {
529
+ title: doc.info?.title ?? "Untitled API",
530
+ baseUrl,
531
+ version: doc.info?.version ?? "0.0.0",
532
+ operations,
533
+ types
534
+ };
535
+ }
536
+ };
537
+ function mergeParameters(pathParams, opParams) {
538
+ const opParamKeys = new Set(opParams.map((p) => `${p.in}:${p.name}`));
539
+ const filtered = pathParams.filter((p) => !opParamKeys.has(`${p.in}:${p.name}`));
540
+ return [...filtered, ...opParams];
541
+ }
542
+ function isRef2(obj) {
543
+ return typeof obj === "object" && obj !== null && "$ref" in obj;
544
+ }
545
+ function isBodyParam(param) {
546
+ return param.in === "body";
547
+ }
548
+ var HTTP_METHODS2 = /* @__PURE__ */ new Set([
549
+ "get",
550
+ "post",
551
+ "put",
552
+ "delete",
553
+ "patch",
554
+ "head",
555
+ "options"
556
+ ]);
557
+ function convertSchema2(schema) {
558
+ if (!schema) {
559
+ return { kind: "primitive", type: "unknown" };
560
+ }
561
+ if (schema.oneOf && schema.oneOf.length > 0) {
562
+ const variants = schema.oneOf.filter((s) => !isRef2(s)).map((s) => convertSchema2(s));
563
+ return { kind: "union", variants, description: schema.description };
564
+ }
565
+ if (schema.anyOf && schema.anyOf.length > 0) {
566
+ const variants = schema.anyOf.filter((s) => !isRef2(s)).map((s) => convertSchema2(s));
567
+ return { kind: "union", variants, description: schema.description };
568
+ }
569
+ if (schema.allOf && schema.allOf.length > 0) {
570
+ const merged = mergeAllOf2(
571
+ schema.allOf.filter((s) => !isRef2(s))
572
+ );
573
+ const result = convertSchema2(merged);
574
+ if (schema.description && result.kind === "object") {
575
+ return { ...result, description: schema.description };
576
+ }
577
+ return result;
578
+ }
579
+ if (schema.enum && schema.enum.length > 0) {
580
+ return {
581
+ kind: "enum",
582
+ values: schema.enum,
583
+ name: schema.title,
584
+ description: schema.description
585
+ };
586
+ }
587
+ const schemaType = typeof schema.type === "string" ? schema.type : void 0;
588
+ if (schemaType === "array") {
589
+ let items = { kind: "primitive", type: "unknown" };
590
+ if (schema.items && !isRef2(schema.items)) {
591
+ items = convertSchema2(schema.items);
592
+ }
593
+ return { kind: "array", items, description: schema.description };
594
+ }
595
+ if (schemaType === "object" || schema.properties || !schemaType && hasObjectShape2(schema)) {
596
+ return convertObjectSchema2(schema);
597
+ }
598
+ if (schemaType === "string" || schemaType === "number" || schemaType === "integer" || schemaType === "boolean") {
599
+ const result = {
600
+ kind: "primitive",
601
+ type: schemaType,
602
+ description: schema.description
603
+ };
604
+ if (schema.format) {
605
+ result.format = schema.format;
606
+ }
607
+ return result;
608
+ }
609
+ return { kind: "primitive", type: "unknown", description: schema.description };
610
+ }
611
+ function hasObjectShape2(schema) {
612
+ return !!(schema.properties || schema.additionalProperties);
613
+ }
614
+ function convertObjectSchema2(schema) {
615
+ const requiredSet = new Set(schema.required ?? []);
616
+ const properties = [];
617
+ if (schema.properties) {
618
+ for (const [name, propSchema] of Object.entries(schema.properties)) {
619
+ if (isRef2(propSchema)) continue;
620
+ properties.push({
621
+ name,
622
+ type: convertSchema2(propSchema),
623
+ required: requiredSet.has(name),
624
+ description: propSchema.description
625
+ });
626
+ }
627
+ }
628
+ const result = {
629
+ kind: "object",
630
+ properties,
631
+ description: schema.description
632
+ };
633
+ if (schema.title) {
634
+ result.name = schema.title;
635
+ }
636
+ if (schema.additionalProperties !== void 0) {
637
+ if (typeof schema.additionalProperties === "boolean") {
638
+ result.additionalProperties = schema.additionalProperties;
639
+ } else if (!isRef2(schema.additionalProperties)) {
640
+ result.additionalProperties = convertSchema2(schema.additionalProperties);
641
+ }
642
+ }
643
+ return result;
644
+ }
645
+ function mergeAllOf2(schemas) {
646
+ const merged = {
647
+ type: "object",
648
+ properties: {},
649
+ required: []
650
+ };
651
+ for (const schema of schemas) {
652
+ if (schema.properties) {
653
+ Object.assign(merged.properties, schema.properties);
654
+ }
655
+ if (schema.required) {
656
+ merged.required.push(...schema.required);
657
+ }
658
+ if (schema.description && !merged.description) {
659
+ merged.description = schema.description;
660
+ }
661
+ }
662
+ return merged;
663
+ }
664
+ function convertGeneralParam(param) {
665
+ const paramType = param.type;
666
+ let type;
667
+ if (param.enum && param.enum.length > 0) {
668
+ type = {
669
+ kind: "enum",
670
+ values: param.enum
671
+ };
672
+ } else if (paramType === "array" && param.items) {
673
+ const itemSchema = isRef2(param.items) ? { kind: "primitive", type: "unknown" } : convertItemsToType(param.items);
674
+ type = { kind: "array", items: itemSchema };
675
+ } else {
676
+ type = {
677
+ kind: "primitive",
678
+ type: mapSwagger2Type(paramType),
679
+ format: param.format
680
+ };
681
+ }
682
+ return {
683
+ name: param.name,
684
+ required: param.required ?? param.in === "path",
685
+ type,
686
+ description: param.description,
687
+ in: param.in
688
+ };
689
+ }
690
+ function convertItemsToType(items) {
691
+ if (items.enum && items.enum.length > 0) {
692
+ return { kind: "enum", values: items.enum };
693
+ }
694
+ if (items.type === "array" && items.items && !isRef2(items.items)) {
695
+ return {
696
+ kind: "array",
697
+ items: convertItemsToType(items.items)
698
+ };
699
+ }
700
+ return {
701
+ kind: "primitive",
702
+ type: mapSwagger2Type(items.type),
703
+ format: items.format
704
+ };
705
+ }
706
+ function mapSwagger2Type(t) {
707
+ switch (t) {
708
+ case "string":
709
+ return "string";
710
+ case "number":
711
+ case "float":
712
+ case "double":
713
+ return "number";
714
+ case "integer":
715
+ case "long":
716
+ return "integer";
717
+ case "boolean":
718
+ return "boolean";
719
+ default:
720
+ return "unknown";
721
+ }
722
+ }
723
+ function convertBodyParam(param, produces) {
724
+ const schema = isRef2(param.schema) ? void 0 : param.schema;
725
+ return {
726
+ required: param.required ?? false,
727
+ contentType: produces.includes("application/json") ? "application/json" : produces[0] ?? "application/json",
728
+ type: convertSchema2(schema),
729
+ description: param.description
730
+ };
731
+ }
732
+ function convertResponse2(responses, produces) {
733
+ const successCodes = ["200", "201"];
734
+ let responseObj;
735
+ let statusCode = 200;
736
+ for (const code of successCodes) {
737
+ const candidate = responses[code];
738
+ if (candidate && !isRef2(candidate)) {
739
+ responseObj = candidate;
740
+ statusCode = parseInt(code, 10);
741
+ break;
742
+ }
743
+ }
744
+ if (!responseObj) {
745
+ for (const [code, value] of Object.entries(responses)) {
746
+ if (code.startsWith("2") && value && !isRef2(value)) {
747
+ responseObj = value;
748
+ statusCode = parseInt(code, 10);
749
+ break;
750
+ }
751
+ }
752
+ }
753
+ if (!responseObj) {
754
+ const defaultResp = responses["default"];
755
+ if (defaultResp && !isRef2(defaultResp)) {
756
+ responseObj = defaultResp;
757
+ statusCode = "default";
758
+ }
759
+ }
760
+ if (!responseObj || !responseObj.schema) {
761
+ return {
762
+ statusCode,
763
+ contentType: produces[0] ?? "application/json",
764
+ type: { kind: "primitive", type: "unknown" },
765
+ description: responseObj?.description
766
+ };
767
+ }
768
+ const schema = isRef2(responseObj.schema) ? void 0 : responseObj.schema;
769
+ const contentType = produces.includes("application/json") ? "application/json" : produces[0] ?? "application/json";
770
+ return {
771
+ statusCode,
772
+ contentType,
773
+ type: convertSchema2(schema),
774
+ description: responseObj.description
775
+ };
776
+ }
777
+ function generateOperationId2(method, path) {
778
+ const segments = path.split("/").filter(Boolean).map((seg) => {
779
+ const clean = seg.replace(/[{}]/g, "");
780
+ return clean.charAt(0).toUpperCase() + clean.slice(1);
781
+ });
782
+ return method.toLowerCase() + segments.join("");
783
+ }
784
+ var swaggerParser = {
785
+ canParse(input) {
786
+ if (typeof input !== "object" || input === null) return false;
787
+ const doc = input;
788
+ return doc.swagger === "2.0";
789
+ },
790
+ async parse(input, options) {
791
+ const derefed = await SwaggerParser.dereference(
792
+ input,
793
+ { dereference: { circular: "ignore" } }
794
+ );
795
+ const doc = derefed;
796
+ const types = /* @__PURE__ */ new Map();
797
+ if (doc.definitions) {
798
+ for (const [name, schema] of Object.entries(doc.definitions)) {
799
+ if (!isRef2(schema)) {
800
+ const converted = convertSchema2(schema);
801
+ if (converted.kind === "object" && !converted.name) {
802
+ converted.name = name;
803
+ } else if (converted.kind === "enum" && !converted.name) {
804
+ converted.name = name;
805
+ }
806
+ types.set(name, converted);
807
+ }
808
+ }
809
+ }
810
+ let baseUrl = options?.baseUrl ?? "";
811
+ if (!baseUrl) {
812
+ const scheme = doc.schemes && doc.schemes.length > 0 ? doc.schemes[0] : "https";
813
+ const host = doc.host ?? "";
814
+ const basePath = doc.basePath ?? "";
815
+ if (host) {
816
+ baseUrl = `${scheme}://${host}${basePath}`;
817
+ } else {
818
+ baseUrl = basePath;
819
+ }
820
+ }
821
+ const globalProduces = doc.produces ?? ["application/json"];
822
+ const operations = [];
823
+ for (const [path, pathItem] of Object.entries(doc.paths)) {
824
+ if (!pathItem) continue;
825
+ const pathLevelParams = (pathItem.parameters ?? []).filter((p) => !isRef2(p));
826
+ for (const method of HTTP_METHODS2) {
827
+ const operationObj = pathItem[method];
828
+ if (!operationObj) continue;
829
+ const opRawParams = (operationObj.parameters ?? []).filter((p) => !isRef2(p));
830
+ const mergedParams = mergeParameters2(pathLevelParams, opRawParams);
831
+ const produces = operationObj.produces ?? globalProduces;
832
+ const pathParams = [];
833
+ const queryParams = [];
834
+ const headerParams = [];
835
+ let requestBody;
836
+ for (const param of mergedParams) {
837
+ if (isBodyParam(param)) {
838
+ requestBody = convertBodyParam(param, produces);
839
+ } else {
840
+ const generalParam = param;
841
+ const converted = convertGeneralParam(generalParam);
842
+ switch (param.in) {
843
+ case "path":
844
+ pathParams.push(converted);
845
+ break;
846
+ case "query":
847
+ queryParams.push(converted);
848
+ break;
849
+ case "header":
850
+ headerParams.push(converted);
851
+ break;
852
+ }
853
+ }
854
+ }
855
+ const response = convertResponse2(operationObj.responses, produces);
856
+ const tags = operationObj.tags && operationObj.tags.length > 0 ? operationObj.tags : ["default"];
857
+ const operationId = operationObj.operationId ?? generateOperationId2(method, path);
858
+ operations.push({
859
+ operationId,
860
+ summary: operationObj.summary,
861
+ method: method.toUpperCase(),
862
+ path,
863
+ tags,
864
+ pathParams,
865
+ queryParams,
866
+ headerParams,
867
+ requestBody,
868
+ response,
869
+ deprecated: operationObj.deprecated ?? false
870
+ });
871
+ }
872
+ }
873
+ return {
874
+ title: doc.info?.title ?? "Untitled API",
875
+ baseUrl,
876
+ version: doc.info?.version ?? "0.0.0",
877
+ operations,
878
+ types
879
+ };
880
+ }
881
+ };
882
+ function mergeParameters2(pathParams, opParams) {
883
+ const opParamKeys = new Set(opParams.map((p) => `${p.in}:${p.name}`));
884
+ const filtered = pathParams.filter((p) => !opParamKeys.has(`${p.in}:${p.name}`));
885
+ return [...filtered, ...opParams];
886
+ }
887
+ function isIntrospectionResult(input) {
888
+ return typeof input === "object" && input !== null && "__schema" in input;
889
+ }
890
+ function isIntrospectionWrapper(input) {
891
+ return typeof input === "object" && input !== null && "data" in input && typeof input.data === "object" && input.data !== null && "__schema" in input.data;
892
+ }
893
+ var SDL_KEYWORDS = /^\s*(type |schema |input |enum |union |interface |scalar |directive |extend )/;
894
+ function isSDLString(input) {
895
+ return typeof input === "string" && SDL_KEYWORDS.test(input);
896
+ }
897
+ function mapScalarType(name) {
898
+ switch (name) {
899
+ case "String":
900
+ return { type: "string" };
901
+ case "Int":
902
+ return { type: "number", format: "int32" };
903
+ case "Float":
904
+ return { type: "number", format: "float" };
905
+ case "Boolean":
906
+ return { type: "boolean" };
907
+ case "ID":
908
+ return { type: "string", format: "id" };
909
+ case "DateTime":
910
+ return { type: "string", format: "date-time" };
911
+ case "Date":
912
+ return { type: "string", format: "date" };
913
+ case "JSON":
914
+ case "JSONObject":
915
+ return { type: "unknown" };
916
+ default:
917
+ return { type: "string" };
918
+ }
919
+ }
920
+ var visitedTypes = /* @__PURE__ */ new Set();
921
+ function convertGraphQLType(graphqlType) {
922
+ let required = false;
923
+ let unwrapped = graphqlType;
924
+ if (isNonNullType(unwrapped)) {
925
+ required = true;
926
+ unwrapped = unwrapped.ofType;
927
+ }
928
+ const apiType = convertInnerType(unwrapped);
929
+ return { type: apiType, required };
930
+ }
931
+ function convertInnerType(graphqlType) {
932
+ if (isListType(graphqlType)) {
933
+ const inner = isNonNullType(graphqlType.ofType) ? convertInnerType(graphqlType.ofType.ofType) : convertInnerType(graphqlType.ofType);
934
+ return { kind: "array", items: inner };
935
+ }
936
+ if (isScalarType(graphqlType)) {
937
+ const mapped = mapScalarType(graphqlType.name);
938
+ return {
939
+ kind: "primitive",
940
+ type: mapped.type,
941
+ format: mapped.format,
942
+ description: graphqlType.description ?? void 0
943
+ };
944
+ }
945
+ if (isEnumType(graphqlType)) {
946
+ return {
947
+ kind: "enum",
948
+ name: graphqlType.name,
949
+ values: graphqlType.getValues().map((v) => v.value),
950
+ description: graphqlType.description ?? void 0
951
+ };
952
+ }
953
+ if (isUnionType(graphqlType)) {
954
+ const variants = graphqlType.getTypes().map((t) => convertInnerType(t));
955
+ return {
956
+ kind: "union",
957
+ variants,
958
+ description: graphqlType.description ?? void 0
959
+ };
960
+ }
961
+ if (isObjectType(graphqlType)) {
962
+ return convertObjectType(graphqlType);
963
+ }
964
+ if (isInputObjectType(graphqlType)) {
965
+ return convertInputObjectType(graphqlType);
966
+ }
967
+ return { kind: "primitive", type: "unknown" };
968
+ }
969
+ function convertObjectType(objType) {
970
+ const name = objType.name;
971
+ if (visitedTypes.has(name)) {
972
+ return { kind: "ref", name };
973
+ }
974
+ visitedTypes.add(name);
975
+ try {
976
+ const fields = objType.getFields();
977
+ const properties = [];
978
+ for (const [fieldName, field] of Object.entries(fields)) {
979
+ const { type, required } = convertGraphQLType(field.type);
980
+ properties.push({
981
+ name: fieldName,
982
+ type,
983
+ required,
984
+ description: field.description ?? void 0
985
+ });
986
+ }
987
+ return {
988
+ kind: "object",
989
+ name,
990
+ properties,
991
+ description: objType.description ?? void 0
992
+ };
993
+ } finally {
994
+ visitedTypes.delete(name);
995
+ }
996
+ }
997
+ function convertInputObjectType(inputType) {
998
+ const name = inputType.name;
999
+ if (visitedTypes.has(name)) {
1000
+ return { kind: "ref", name };
1001
+ }
1002
+ visitedTypes.add(name);
1003
+ try {
1004
+ const fields = inputType.getFields();
1005
+ const properties = [];
1006
+ for (const [fieldName, field] of Object.entries(fields)) {
1007
+ const { type, required } = convertGraphQLType(field.type);
1008
+ properties.push({
1009
+ name: fieldName,
1010
+ type,
1011
+ required,
1012
+ description: field.description ?? void 0
1013
+ });
1014
+ }
1015
+ return {
1016
+ kind: "object",
1017
+ name,
1018
+ properties,
1019
+ description: inputType.description ?? void 0
1020
+ };
1021
+ } finally {
1022
+ visitedTypes.delete(name);
1023
+ }
1024
+ }
1025
+ function convertArgument(arg) {
1026
+ const { type, required } = convertGraphQLType(arg.type);
1027
+ return {
1028
+ name: arg.name,
1029
+ required,
1030
+ type,
1031
+ description: arg.description ?? void 0,
1032
+ in: "query"
1033
+ // GraphQL args are conceptually similar to query params
1034
+ };
1035
+ }
1036
+ function buildOperationsFromType(rootType, method, tag) {
1037
+ if (!rootType) return [];
1038
+ const fields = rootType.getFields();
1039
+ const operations = [];
1040
+ for (const [fieldName, field] of Object.entries(fields)) {
1041
+ const args = field.args ?? [];
1042
+ const queryParams = args.map(convertArgument);
1043
+ let requestBody;
1044
+ if (args.length > 0) {
1045
+ const argProperties = args.map((arg) => {
1046
+ const { type, required } = convertGraphQLType(arg.type);
1047
+ return {
1048
+ name: arg.name,
1049
+ type,
1050
+ required,
1051
+ description: arg.description ?? void 0
1052
+ };
1053
+ });
1054
+ requestBody = {
1055
+ required: args.some((arg) => isNonNullType(arg.type)),
1056
+ contentType: "application/json",
1057
+ type: {
1058
+ kind: "object",
1059
+ properties: argProperties
1060
+ }
1061
+ };
1062
+ }
1063
+ const { type: responseType } = convertGraphQLType(field.type);
1064
+ const response = {
1065
+ statusCode: 200,
1066
+ contentType: "application/json",
1067
+ type: responseType,
1068
+ description: field.description ?? void 0
1069
+ };
1070
+ operations.push({
1071
+ operationId: fieldName,
1072
+ summary: field.description ?? void 0,
1073
+ method,
1074
+ path: fieldName,
1075
+ tags: [tag],
1076
+ pathParams: [],
1077
+ queryParams,
1078
+ headerParams: [],
1079
+ requestBody,
1080
+ response,
1081
+ deprecated: field.deprecationReason != null
1082
+ });
1083
+ }
1084
+ return operations;
1085
+ }
1086
+ function extractNamedTypes(schema) {
1087
+ const types = /* @__PURE__ */ new Map();
1088
+ const typeMap = schema.getTypeMap();
1089
+ for (const [name, graphqlType] of Object.entries(typeMap)) {
1090
+ if (name.startsWith("__")) continue;
1091
+ if (isScalarType(graphqlType)) {
1092
+ const builtins = /* @__PURE__ */ new Set(["String", "Int", "Float", "Boolean", "ID"]);
1093
+ if (builtins.has(name)) continue;
1094
+ }
1095
+ if (isObjectType(graphqlType)) {
1096
+ const queryType = schema.getQueryType();
1097
+ const mutationType = schema.getMutationType();
1098
+ const subscriptionType = schema.getSubscriptionType();
1099
+ if (graphqlType === queryType || graphqlType === mutationType || graphqlType === subscriptionType) {
1100
+ continue;
1101
+ }
1102
+ types.set(name, convertObjectType(graphqlType));
1103
+ } else if (isInputObjectType(graphqlType)) {
1104
+ types.set(name, convertInputObjectType(graphqlType));
1105
+ } else if (isEnumType(graphqlType)) {
1106
+ types.set(name, {
1107
+ kind: "enum",
1108
+ name,
1109
+ values: graphqlType.getValues().map((v) => v.value),
1110
+ description: graphqlType.description ?? void 0
1111
+ });
1112
+ } else if (isUnionType(graphqlType)) {
1113
+ types.set(name, {
1114
+ kind: "union",
1115
+ variants: graphqlType.getTypes().map((t) => convertInnerType(t)),
1116
+ description: graphqlType.description ?? void 0
1117
+ });
1118
+ }
1119
+ }
1120
+ return types;
1121
+ }
1122
+ var graphqlParser = {
1123
+ canParse(input) {
1124
+ if (isIntrospectionResult(input)) return true;
1125
+ if (isIntrospectionWrapper(input)) return true;
1126
+ if (isSDLString(input)) return true;
1127
+ return false;
1128
+ },
1129
+ async parse(input, options) {
1130
+ let schema;
1131
+ if (isIntrospectionWrapper(input)) {
1132
+ schema = buildClientSchema(input.data);
1133
+ } else if (isIntrospectionResult(input)) {
1134
+ schema = buildClientSchema(input);
1135
+ } else if (typeof input === "string") {
1136
+ schema = buildSchema(input);
1137
+ } else {
1138
+ throw new Error("GraphQL parser: unsupported input format");
1139
+ }
1140
+ visitedTypes.clear();
1141
+ const operations = [
1142
+ ...buildOperationsFromType(schema.getQueryType(), "QUERY", "queries"),
1143
+ ...buildOperationsFromType(schema.getMutationType(), "MUTATION", "mutations"),
1144
+ ...buildOperationsFromType(schema.getSubscriptionType(), "SUBSCRIPTION", "subscriptions")
1145
+ ];
1146
+ const types = extractNamedTypes(schema);
1147
+ visitedTypes.clear();
1148
+ return {
1149
+ title: "GraphQL API",
1150
+ baseUrl: options?.baseUrl ?? "/graphql",
1151
+ version: "0.0.0",
1152
+ operations,
1153
+ types
1154
+ };
1155
+ }
1156
+ };
1157
+
1158
+ // src/parsers/index.ts
1159
+ var ParseError = class extends Error {
1160
+ constructor(message) {
1161
+ super(message);
1162
+ this.name = "ParseError";
1163
+ }
1164
+ };
1165
+ var parsers = [openApiParser, swaggerParser, graphqlParser];
1166
+ var YAML_EXTENSIONS = /* @__PURE__ */ new Set([".yaml", ".yml"]);
1167
+ var JSON_EXTENSIONS = /* @__PURE__ */ new Set([".json"]);
1168
+ var GRAPHQL_EXTENSIONS = /* @__PURE__ */ new Set([".graphql", ".gql"]);
1169
+ async function loadFile(filePath) {
1170
+ const content = await readFile(filePath, "utf-8");
1171
+ const ext = extname(filePath).toLowerCase();
1172
+ if (YAML_EXTENSIONS.has(ext)) {
1173
+ return parse(content);
1174
+ }
1175
+ if (JSON_EXTENSIONS.has(ext)) {
1176
+ return JSON.parse(content);
1177
+ }
1178
+ if (GRAPHQL_EXTENSIONS.has(ext)) {
1179
+ return content;
1180
+ }
1181
+ try {
1182
+ return JSON.parse(content);
1183
+ } catch {
1184
+ try {
1185
+ return parse(content);
1186
+ } catch {
1187
+ return content;
1188
+ }
1189
+ }
1190
+ }
1191
+ async function parseSpec(input, options) {
1192
+ let resolved;
1193
+ if (typeof input === "string") {
1194
+ const looksLikeFile = /\.[a-z]{2,10}$/i.test(input) && !input.includes("\n");
1195
+ if (looksLikeFile) {
1196
+ try {
1197
+ resolved = await loadFile(input);
1198
+ } catch (err) {
1199
+ throw new ParseError(
1200
+ `Failed to read spec file "${input}": ${err instanceof Error ? err.message : String(err)}`
1201
+ );
1202
+ }
1203
+ } else {
1204
+ try {
1205
+ resolved = JSON.parse(input);
1206
+ } catch {
1207
+ try {
1208
+ const parsed = parse(input);
1209
+ resolved = typeof parsed === "string" ? input : parsed;
1210
+ } catch {
1211
+ resolved = input;
1212
+ }
1213
+ }
1214
+ }
1215
+ } else {
1216
+ resolved = input;
1217
+ }
1218
+ for (const parser of parsers) {
1219
+ if (parser.canParse(resolved)) {
1220
+ const spec = await parser.parse(resolved, options);
1221
+ return applyPaginationDetection(spec);
1222
+ }
1223
+ }
1224
+ const inputType = typeof resolved;
1225
+ let hint = "";
1226
+ if (inputType === "object" && resolved !== null) {
1227
+ const keys = Object.keys(resolved).slice(0, 5);
1228
+ hint = ` Object keys: [${keys.join(", ")}].`;
1229
+ } else if (inputType === "string") {
1230
+ const preview = resolved.slice(0, 80);
1231
+ hint = ` String starts with: "${preview}...".`;
1232
+ }
1233
+ throw new ParseError(
1234
+ `Unable to detect API specification format.${hint} Expected an OpenAPI 3.x document (with "openapi" field starting with "3"), a Swagger 2.0 document (with "swagger": "2.0"), or a GraphQL schema (introspection JSON with "__schema", or SDL string).`
1235
+ );
1236
+ }
1237
+
1238
+ // src/utils/naming.ts
1239
+ var IRREGULAR_PLURALS = {
1240
+ person: "people",
1241
+ child: "children",
1242
+ man: "men",
1243
+ woman: "women",
1244
+ mouse: "mice",
1245
+ goose: "geese",
1246
+ tooth: "teeth",
1247
+ foot: "feet",
1248
+ datum: "data",
1249
+ medium: "media",
1250
+ criterion: "criteria",
1251
+ analysis: "analyses",
1252
+ status: "statuses"
1253
+ };
1254
+ var IRREGULAR_SINGULARS = Object.fromEntries(
1255
+ Object.entries(IRREGULAR_PLURALS).map(([s, p]) => [p, s])
1256
+ );
1257
+ function singularize(word) {
1258
+ const lower = word.toLowerCase();
1259
+ if (IRREGULAR_SINGULARS[lower]) {
1260
+ return preserveCase(word, IRREGULAR_SINGULARS[lower]);
1261
+ }
1262
+ if (lower.endsWith("ies") && lower.length > 4) {
1263
+ return word.slice(0, -3) + "y";
1264
+ }
1265
+ if (lower.endsWith("ses") || lower.endsWith("xes") || lower.endsWith("zes") || lower.endsWith("ches") || lower.endsWith("shes")) {
1266
+ return word.slice(0, -2);
1267
+ }
1268
+ if (lower.endsWith("s") && !lower.endsWith("ss") && !lower.endsWith("us") && lower.length > 2) {
1269
+ return word.slice(0, -1);
1270
+ }
1271
+ return word;
1272
+ }
1273
+ function preserveCase(original, replacement) {
1274
+ if (original[0] === original[0].toUpperCase()) {
1275
+ return replacement[0].toUpperCase() + replacement.slice(1);
1276
+ }
1277
+ return replacement;
1278
+ }
1279
+ function toPascalCase(str) {
1280
+ return str.replace(/[^a-zA-Z0-9]+(.)/g, (_, char) => char.toUpperCase()).replace(/^[a-z]/, (char) => char.toUpperCase()).replace(/[^a-zA-Z0-9]/g, "");
1281
+ }
1282
+ function toCamelCase(str) {
1283
+ const pascal = toPascalCase(str);
1284
+ return pascal[0].toLowerCase() + pascal.slice(1);
1285
+ }
1286
+ function extractResource(path) {
1287
+ const segments = path.split("/").filter((s) => s && !s.startsWith("{"));
1288
+ const meaningful = segments.filter((s) => !/^(api|v\d+)$/i.test(s));
1289
+ return meaningful[meaningful.length - 1] || segments[segments.length - 1] || "unknown";
1290
+ }
1291
+ function isDetailEndpoint(path) {
1292
+ const segments = path.split("/").filter(Boolean);
1293
+ const last = segments[segments.length - 1];
1294
+ return !!last && last.startsWith("{");
1295
+ }
1296
+ function getHookName(operationId, method, path) {
1297
+ if (operationId) {
1298
+ const name = toPascalCase(operationId);
1299
+ return `use${name}`;
1300
+ }
1301
+ const resource = extractResource(path);
1302
+ const isDetail = isDetailEndpoint(path);
1303
+ const resourceName = isDetail ? singularize(resource) : resource;
1304
+ const methodMap = {
1305
+ GET: isDetail ? "Get" : "Get",
1306
+ POST: "Create",
1307
+ PUT: "Update",
1308
+ PATCH: "Patch",
1309
+ DELETE: "Delete",
1310
+ QUERY: "Get",
1311
+ MUTATION: "",
1312
+ SUBSCRIPTION: "Subscribe"
1313
+ };
1314
+ const prefix = methodMap[method.toUpperCase()] || method;
1315
+ return `use${prefix}${toPascalCase(resourceName)}`;
1316
+ }
1317
+ function toKebabCase(str) {
1318
+ return str.replace(/([a-z])([A-Z])/g, "$1-$2").replace(/[^a-zA-Z0-9]+/g, "-").replace(/^-+|-+$/g, "").toLowerCase();
1319
+ }
1320
+
1321
+ // src/type-gen/typescript-emitter.ts
1322
+ function emitTypeScriptTypes(spec) {
1323
+ const chunks = [];
1324
+ chunks.push(fileHeader(spec));
1325
+ for (const [name, type] of spec.types) {
1326
+ chunks.push(emitNamedType(name, type));
1327
+ }
1328
+ for (const op of spec.operations) {
1329
+ const params = emitParamsInterface(op);
1330
+ if (params) {
1331
+ chunks.push(params);
1332
+ }
1333
+ const body = emitRequestBodyType(op);
1334
+ if (body) {
1335
+ chunks.push(body);
1336
+ }
1337
+ chunks.push(emitResponseType(op));
1338
+ }
1339
+ return chunks.join("\n");
1340
+ }
1341
+ function emitTypeString(type) {
1342
+ switch (type.kind) {
1343
+ case "primitive":
1344
+ return emitPrimitive(type);
1345
+ case "object":
1346
+ return emitObjectInline(type);
1347
+ case "array":
1348
+ return emitArray(type);
1349
+ case "enum":
1350
+ return emitEnum(type);
1351
+ case "union":
1352
+ return emitUnion(type);
1353
+ case "ref":
1354
+ return emitRef(type);
1355
+ }
1356
+ }
1357
+ function emitParamsInterface(op) {
1358
+ const allParams = [
1359
+ ...op.pathParams,
1360
+ ...op.queryParams,
1361
+ ...op.headerParams
1362
+ ];
1363
+ if (allParams.length === 0) {
1364
+ return null;
1365
+ }
1366
+ const name = `${toPascalCase(op.operationId)}Params`;
1367
+ const lines = [];
1368
+ if (op.summary) {
1369
+ lines.push(jsdoc(op.summary));
1370
+ }
1371
+ lines.push(`export interface ${name} {`);
1372
+ for (const param of allParams) {
1373
+ if (param.description) {
1374
+ lines.push(indent(jsdoc(param.description)));
1375
+ }
1376
+ const optional = param.required ? "" : "?";
1377
+ lines.push(indent(`${safePropName(param.name)}${optional}: ${emitTypeString(param.type)}`));
1378
+ }
1379
+ lines.push("}");
1380
+ lines.push("");
1381
+ return lines.join("\n");
1382
+ }
1383
+ function emitRequestBodyType(op) {
1384
+ if (!op.requestBody) {
1385
+ return null;
1386
+ }
1387
+ const name = `${toPascalCase(op.operationId)}Body`;
1388
+ const lines = [];
1389
+ if (op.requestBody.description) {
1390
+ lines.push(jsdoc(op.requestBody.description));
1391
+ }
1392
+ lines.push(`export type ${name} = ${emitTypeString(op.requestBody.type)}`);
1393
+ lines.push("");
1394
+ return lines.join("\n");
1395
+ }
1396
+ function emitResponseType(op) {
1397
+ const name = `${toPascalCase(op.operationId)}Response`;
1398
+ const lines = [];
1399
+ if (op.response.description) {
1400
+ lines.push(jsdoc(op.response.description));
1401
+ }
1402
+ lines.push(`export type ${name} = ${emitTypeString(op.response.type)}`);
1403
+ lines.push("");
1404
+ return lines.join("\n");
1405
+ }
1406
+ function fileHeader(spec) {
1407
+ const lines = [
1408
+ "/* eslint-disable */",
1409
+ "/* tslint:disable */",
1410
+ `/**`,
1411
+ ` * Auto-generated TypeScript types for ${spec.title} (v${spec.version}).`,
1412
+ ` * DO NOT EDIT \u2014 this file is regenerated on every run.`,
1413
+ ` */`,
1414
+ ""
1415
+ ];
1416
+ return lines.join("\n");
1417
+ }
1418
+ function emitPrimitive(type) {
1419
+ switch (type.type) {
1420
+ case "string":
1421
+ return "string";
1422
+ case "number":
1423
+ case "integer":
1424
+ return "number";
1425
+ case "boolean":
1426
+ return "boolean";
1427
+ case "null":
1428
+ return "null";
1429
+ case "unknown":
1430
+ return "unknown";
1431
+ }
1432
+ }
1433
+ function emitObjectInline(type) {
1434
+ if (type.properties.length === 0 && !type.additionalProperties) {
1435
+ return "Record<string, unknown>";
1436
+ }
1437
+ const members = [];
1438
+ for (const prop of type.properties) {
1439
+ const optional = prop.required ? "" : "?";
1440
+ const comment = prop.description ? ` ${inlineComment(prop.description)}` : "";
1441
+ members.push(
1442
+ `${safePropName(prop.name)}${optional}: ${emitTypeString(prop.type)}${comment}`
1443
+ );
1444
+ }
1445
+ if (type.additionalProperties !== void 0) {
1446
+ if (type.additionalProperties === true) {
1447
+ members.push("[key: string]: unknown");
1448
+ } else if (type.additionalProperties !== false) {
1449
+ members.push(`[key: string]: ${emitTypeString(type.additionalProperties)}`);
1450
+ }
1451
+ }
1452
+ return `{
1453
+ ${members.map((m) => indent(m)).join("\n")}
1454
+ }`;
1455
+ }
1456
+ function emitArray(type) {
1457
+ const inner = emitTypeString(type.items);
1458
+ if (inner.includes("|") || inner.includes("{")) {
1459
+ return `Array<${inner}>`;
1460
+ }
1461
+ return `${inner}[]`;
1462
+ }
1463
+ function emitEnum(type) {
1464
+ if (type.values.length === 0) {
1465
+ return "never";
1466
+ }
1467
+ return type.values.map((v) => typeof v === "string" ? `'${escapeString(v)}'` : String(v)).join(" | ");
1468
+ }
1469
+ function emitUnion(type) {
1470
+ if (type.variants.length === 0) {
1471
+ return "never";
1472
+ }
1473
+ if (type.variants.length === 1) {
1474
+ return emitTypeString(type.variants[0]);
1475
+ }
1476
+ return type.variants.map((v) => emitTypeString(v)).join(" | ");
1477
+ }
1478
+ function emitRef(type) {
1479
+ return toPascalCase(type.name);
1480
+ }
1481
+ function emitNamedType(name, type) {
1482
+ const tsName = toPascalCase(name);
1483
+ const lines = [];
1484
+ if (type.kind !== "ref" && type.description) {
1485
+ lines.push(jsdoc(type.description));
1486
+ }
1487
+ if (type.kind === "object") {
1488
+ lines.push(...emitInterface(tsName, type));
1489
+ } else {
1490
+ lines.push(`export type ${tsName} = ${emitTypeString(type)}`);
1491
+ }
1492
+ lines.push("");
1493
+ return lines.join("\n");
1494
+ }
1495
+ function emitInterface(name, type) {
1496
+ const lines = [];
1497
+ lines.push(`export interface ${name} {`);
1498
+ for (const prop of type.properties) {
1499
+ if (prop.description) {
1500
+ lines.push(indent(jsdoc(prop.description)));
1501
+ }
1502
+ const optional = prop.required ? "" : "?";
1503
+ lines.push(indent(`${safePropName(prop.name)}${optional}: ${emitTypeString(prop.type)}`));
1504
+ }
1505
+ if (type.additionalProperties !== void 0) {
1506
+ if (type.additionalProperties === true) {
1507
+ lines.push(indent("[key: string]: unknown"));
1508
+ } else if (type.additionalProperties !== false) {
1509
+ lines.push(indent(`[key: string]: ${emitTypeString(type.additionalProperties)}`));
1510
+ }
1511
+ }
1512
+ lines.push("}");
1513
+ return lines;
1514
+ }
1515
+ function jsdoc(text) {
1516
+ const trimmed = text.trim();
1517
+ if (!trimmed.includes("\n")) {
1518
+ return `/** ${trimmed} */`;
1519
+ }
1520
+ const lines = trimmed.split("\n");
1521
+ return ["/**", ...lines.map((l) => ` * ${l.trimEnd()}`), " */"].join("\n");
1522
+ }
1523
+ function inlineComment(text) {
1524
+ const single = text.replace(/\n/g, " ").trim();
1525
+ return `/** ${single} */`;
1526
+ }
1527
+ function indent(str) {
1528
+ return str.split("\n").map((line) => ` ${line}`).join("\n");
1529
+ }
1530
+ function safePropName(name) {
1531
+ if (/^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(name)) {
1532
+ return name;
1533
+ }
1534
+ return `'${escapeString(name)}'`;
1535
+ }
1536
+ function escapeString(value) {
1537
+ return value.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
1538
+ }
1539
+
1540
+ // src/type-gen/zod-emitter.ts
1541
+ function emitZodSchemas(spec) {
1542
+ const chunks = [];
1543
+ chunks.push(fileHeader2(spec));
1544
+ chunks.push("import { z } from 'zod'");
1545
+ chunks.push("");
1546
+ for (const [name, type] of spec.types) {
1547
+ chunks.push(emitNamedSchema(name, type));
1548
+ }
1549
+ for (const op of spec.operations) {
1550
+ chunks.push(emitOperationResponseSchema(op));
1551
+ }
1552
+ return chunks.join("\n");
1553
+ }
1554
+ function emitZodType(type) {
1555
+ switch (type.kind) {
1556
+ case "primitive":
1557
+ return emitZodPrimitive(type);
1558
+ case "object":
1559
+ return emitZodObject(type);
1560
+ case "array":
1561
+ return emitZodArray(type);
1562
+ case "enum":
1563
+ return emitZodEnum(type);
1564
+ case "union":
1565
+ return emitZodUnion(type);
1566
+ case "ref":
1567
+ return emitZodRef(type);
1568
+ }
1569
+ }
1570
+ function emitZodPrimitive(type) {
1571
+ switch (type.type) {
1572
+ case "string":
1573
+ return applyStringFormat("z.string()", type.format);
1574
+ case "number":
1575
+ return "z.number()";
1576
+ case "integer":
1577
+ return "z.number().int()";
1578
+ case "boolean":
1579
+ return "z.boolean()";
1580
+ case "null":
1581
+ return "z.null()";
1582
+ case "unknown":
1583
+ return "z.unknown()";
1584
+ }
1585
+ }
1586
+ function applyStringFormat(base, format) {
1587
+ if (!format) {
1588
+ return base;
1589
+ }
1590
+ switch (format) {
1591
+ case "date-time":
1592
+ return `${base}.datetime()`;
1593
+ case "email":
1594
+ return `${base}.email()`;
1595
+ case "uuid":
1596
+ return `${base}.uuid()`;
1597
+ case "uri":
1598
+ case "url":
1599
+ return `${base}.url()`;
1600
+ default:
1601
+ return base;
1602
+ }
1603
+ }
1604
+ function emitZodObject(type) {
1605
+ if (type.properties.length === 0 && !type.additionalProperties) {
1606
+ return "z.record(z.string(), z.unknown())";
1607
+ }
1608
+ const members = type.properties.map((prop) => emitZodProperty(prop));
1609
+ let suffix = "";
1610
+ if (type.additionalProperties !== void 0) {
1611
+ if (type.additionalProperties === true) {
1612
+ suffix = ".catchall(z.unknown())";
1613
+ } else if (type.additionalProperties !== false) {
1614
+ suffix = `.catchall(${emitZodType(type.additionalProperties)})`;
1615
+ }
1616
+ }
1617
+ if (members.length === 0) {
1618
+ return `z.object({})${suffix}`;
1619
+ }
1620
+ const body = members.map((m) => indent2(m)).join("\n");
1621
+ return `z.object({
1622
+ ${body}
1623
+ })${suffix}`;
1624
+ }
1625
+ function emitZodProperty(prop) {
1626
+ const schema = emitZodType(prop.type);
1627
+ const optionalSuffix = prop.required ? "" : ".optional()";
1628
+ const comment = prop.description ? ` ${inlineComment2(prop.description)}` : "";
1629
+ return `${safePropName2(prop.name)}: ${schema}${optionalSuffix},${comment}`;
1630
+ }
1631
+ function emitZodArray(type) {
1632
+ return `z.array(${emitZodType(type.items)})`;
1633
+ }
1634
+ function emitZodEnum(type) {
1635
+ if (type.values.length === 0) {
1636
+ return "z.never()";
1637
+ }
1638
+ const allStrings = type.values.every((v) => typeof v === "string");
1639
+ if (allStrings) {
1640
+ const items = type.values.map((v) => `'${escapeString2(String(v))}'`).join(", ");
1641
+ return `z.enum([${items}])`;
1642
+ }
1643
+ const literals = type.values.map(
1644
+ (v) => typeof v === "string" ? `z.literal('${escapeString2(v)}')` : `z.literal(${v})`
1645
+ ).join(", ");
1646
+ return `z.union([${literals}])`;
1647
+ }
1648
+ function emitZodUnion(type) {
1649
+ if (type.variants.length === 0) {
1650
+ return "z.never()";
1651
+ }
1652
+ if (type.variants.length === 1) {
1653
+ return emitZodType(type.variants[0]);
1654
+ }
1655
+ const members = type.variants.map((v) => emitZodType(v)).join(", ");
1656
+ return `z.union([${members}])`;
1657
+ }
1658
+ function emitZodRef(type) {
1659
+ return schemaVarName(type.name);
1660
+ }
1661
+ function emitNamedSchema(name, type) {
1662
+ const varName = schemaVarName(name);
1663
+ const lines = [];
1664
+ if (type.kind !== "ref" && type.description) {
1665
+ lines.push(jsdoc2(type.description));
1666
+ }
1667
+ lines.push(`export const ${varName} = ${emitZodType(type)}`);
1668
+ lines.push("");
1669
+ return lines.join("\n");
1670
+ }
1671
+ function emitOperationResponseSchema(op) {
1672
+ const varName = `${toCamelCase(op.operationId)}ResponseSchema`;
1673
+ const lines = [];
1674
+ if (op.response.description) {
1675
+ lines.push(jsdoc2(op.response.description));
1676
+ }
1677
+ lines.push(`export const ${varName} = ${emitZodType(op.response.type)}`);
1678
+ lines.push("");
1679
+ return lines.join("\n");
1680
+ }
1681
+ function schemaVarName(name) {
1682
+ return `${toCamelCase(name)}Schema`;
1683
+ }
1684
+ function fileHeader2(spec) {
1685
+ const lines = [
1686
+ "/* eslint-disable */",
1687
+ "/* tslint:disable */",
1688
+ `/**`,
1689
+ ` * Auto-generated Zod schemas for ${spec.title} (v${spec.version}).`,
1690
+ ` * DO NOT EDIT \u2014 this file is regenerated on every run.`,
1691
+ ` */`,
1692
+ ""
1693
+ ];
1694
+ return lines.join("\n");
1695
+ }
1696
+ function jsdoc2(text) {
1697
+ const trimmed = text.trim();
1698
+ if (!trimmed.includes("\n")) {
1699
+ return `/** ${trimmed} */`;
1700
+ }
1701
+ const lines = trimmed.split("\n");
1702
+ return ["/**", ...lines.map((l) => ` * ${l.trimEnd()}`), " */"].join("\n");
1703
+ }
1704
+ function inlineComment2(text) {
1705
+ const single = text.replace(/\n/g, " ").trim();
1706
+ return `/** ${single} */`;
1707
+ }
1708
+ function indent2(str) {
1709
+ return str.split("\n").map((line) => ` ${line}`).join("\n");
1710
+ }
1711
+ function safePropName2(name) {
1712
+ if (/^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(name)) {
1713
+ return name;
1714
+ }
1715
+ return `'${escapeString2(name)}'`;
1716
+ }
1717
+ function escapeString2(value) {
1718
+ return value.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
1719
+ }
1720
+
1721
+ // src/generators/base-generator.ts
1722
+ var BaseHookGenerator = class {
1723
+ /**
1724
+ * Generate all files for the given spec.
1725
+ */
1726
+ generate(spec, options) {
1727
+ const files = [];
1728
+ files.push({
1729
+ path: "types.ts",
1730
+ content: emitTypeScriptTypes(spec)
1731
+ });
1732
+ if (options.zod) {
1733
+ files.push({
1734
+ path: "schemas.ts",
1735
+ content: emitZodSchemas(spec)
1736
+ });
1737
+ }
1738
+ files.push(this.generateClientConfig(spec, options));
1739
+ const groups = this.groupByTag(spec.operations);
1740
+ const hookExports = [];
1741
+ for (const [tag, operations] of groups) {
1742
+ const groupFiles = this.generateHookGroup(tag, operations, spec, options);
1743
+ files.push(...groupFiles);
1744
+ hookExports.push(tag);
1745
+ }
1746
+ files.push(this.generateBarrelIndex(hookExports, options));
1747
+ return files;
1748
+ }
1749
+ /**
1750
+ * Generate hooks for a single tag group.
1751
+ * Subclasses implement generateHookFile for each operation.
1752
+ */
1753
+ generateHookGroup(tag, operations, spec, options) {
1754
+ const files = [];
1755
+ const tagDir = toKebabCase(tag);
1756
+ const exportNames = [];
1757
+ for (const op of operations) {
1758
+ const hookName = getHookName(op.operationId, op.method, op.path);
1759
+ const fileName = toKebabCase(hookName.replace(/^use/, ""));
1760
+ const hookFile = this.generateHookFile(op, hookName, spec, options);
1761
+ files.push({
1762
+ path: `${tagDir}/${fileName}.ts`,
1763
+ content: hookFile
1764
+ });
1765
+ exportNames.push(hookName);
1766
+ if (options.infiniteQueries && op.pagination && this.supportsInfiniteQueries()) {
1767
+ const infiniteName = `${hookName}Infinite`;
1768
+ const infiniteFile = this.generateInfiniteHookFile(op, infiniteName, spec, options);
1769
+ if (infiniteFile) {
1770
+ files.push({
1771
+ path: `${tagDir}/${fileName}-infinite.ts`,
1772
+ content: infiniteFile
1773
+ });
1774
+ exportNames.push(infiniteName);
1775
+ }
1776
+ }
1777
+ }
1778
+ const groupExports = exportNames.map((name) => {
1779
+ const fileName = toKebabCase(name.replace(/^use/, ""));
1780
+ return `export { ${name} } from './${fileName}'`;
1781
+ }).join("\n");
1782
+ files.push({
1783
+ path: `${tagDir}/index.ts`,
1784
+ content: `${groupExports}
1785
+ `
1786
+ });
1787
+ return files;
1788
+ }
1789
+ /**
1790
+ * Generate the base API client configuration file.
1791
+ */
1792
+ generateClientConfig(spec, options) {
1793
+ const baseUrl = options.baseUrl || spec.baseUrl || "";
1794
+ return {
1795
+ path: "client.ts",
1796
+ content: [
1797
+ `/**`,
1798
+ ` * API client configuration.`,
1799
+ ` * Generated by auto-api-hooks \u2014 do not edit manually.`,
1800
+ ` */`,
1801
+ ``,
1802
+ `export const API_BASE_URL = '${baseUrl}'`,
1803
+ ``,
1804
+ `export interface ClientConfig {`,
1805
+ ` baseUrl: string`,
1806
+ ` headers?: Record<string, string>`,
1807
+ `}`,
1808
+ ``,
1809
+ `let _config: ClientConfig = {`,
1810
+ ` baseUrl: API_BASE_URL,`,
1811
+ `}`,
1812
+ ``,
1813
+ `/**`,
1814
+ ` * Configure the API client.`,
1815
+ ` */`,
1816
+ `export function configureClient(config: Partial<ClientConfig>): void {`,
1817
+ ` _config = { ..._config, ...config }`,
1818
+ `}`,
1819
+ ``,
1820
+ `/**`,
1821
+ ` * Get the current client configuration.`,
1822
+ ` */`,
1823
+ `export function getClientConfig(): ClientConfig {`,
1824
+ ` return _config`,
1825
+ `}`,
1826
+ ``
1827
+ ].join("\n")
1828
+ };
1829
+ }
1830
+ /**
1831
+ * Generate the barrel index file.
1832
+ */
1833
+ generateBarrelIndex(tagGroups, options) {
1834
+ const lines = [
1835
+ `/**`,
1836
+ ` * Auto-generated API hooks.`,
1837
+ ` * Generated by auto-api-hooks \u2014 do not edit manually.`,
1838
+ ` */`,
1839
+ ``,
1840
+ `export * from './types'`,
1841
+ `export * from './client'`
1842
+ ];
1843
+ if (options.zod) {
1844
+ lines.push(`export * from './schemas'`);
1845
+ }
1846
+ for (const tag of tagGroups) {
1847
+ lines.push(`export * from './${toKebabCase(tag)}'`);
1848
+ }
1849
+ lines.push(``);
1850
+ return {
1851
+ path: "index.ts",
1852
+ content: lines.join("\n")
1853
+ };
1854
+ }
1855
+ /**
1856
+ * Group operations by their first tag.
1857
+ */
1858
+ groupByTag(operations) {
1859
+ const groups = /* @__PURE__ */ new Map();
1860
+ for (const op of operations) {
1861
+ const tag = op.tags[0] || "default";
1862
+ const group = groups.get(tag) || [];
1863
+ group.push(op);
1864
+ groups.set(tag, group);
1865
+ }
1866
+ return groups;
1867
+ }
1868
+ /**
1869
+ * Get the params type name for an operation.
1870
+ */
1871
+ getParamsTypeName(operationId) {
1872
+ return `${toPascalCase(operationId)}Params`;
1873
+ }
1874
+ /**
1875
+ * Get the request body type name for an operation.
1876
+ */
1877
+ getBodyTypeName(operationId) {
1878
+ return `${toPascalCase(operationId)}Body`;
1879
+ }
1880
+ /**
1881
+ * Get the response type name for an operation.
1882
+ */
1883
+ getResponseTypeName(operationId) {
1884
+ return `${toPascalCase(operationId)}Response`;
1885
+ }
1886
+ /**
1887
+ * Check if an operation is a "read" operation (GET or QUERY).
1888
+ */
1889
+ isReadOperation(op) {
1890
+ return op.method === "GET" || op.method === "QUERY";
1891
+ }
1892
+ /**
1893
+ * Check if an operation is a "write" operation (POST, PUT, PATCH, DELETE, MUTATION).
1894
+ */
1895
+ isWriteOperation(op) {
1896
+ return !this.isReadOperation(op);
1897
+ }
1898
+ /**
1899
+ * Whether this generator supports infinite queries.
1900
+ * Override in subclasses that support them (react-query, swr).
1901
+ */
1902
+ supportsInfiniteQueries() {
1903
+ return false;
1904
+ }
1905
+ /**
1906
+ * Generate an infinite query hook file for a paginated operation.
1907
+ * Override in subclasses that support infinite queries.
1908
+ */
1909
+ generateInfiniteHookFile(_op, _hookName, _spec, _options) {
1910
+ return null;
1911
+ }
1912
+ };
1913
+
1914
+ // src/generators/fetch-generator.ts
1915
+ var FetchGenerator = class extends BaseHookGenerator {
1916
+ generateHookFile(op, hookName, _spec, options) {
1917
+ const paramsType = this.getParamsTypeName(op.operationId);
1918
+ const responseType = this.getResponseTypeName(op.operationId);
1919
+ const bodyType = op.requestBody ? this.getBodyTypeName(op.operationId) : null;
1920
+ const hasParams = op.pathParams.length > 0 || op.queryParams.length > 0;
1921
+ const isRead = this.isReadOperation(op);
1922
+ const lines = [
1923
+ `/**`,
1924
+ ` * ${op.summary || `${op.method} ${op.path}`}`,
1925
+ ` *`,
1926
+ ` * \`${op.method} ${op.path}\``,
1927
+ op.deprecated ? ` * @deprecated` : "",
1928
+ ` * Generated by auto-api-hooks \u2014 do not edit manually.`,
1929
+ ` */`,
1930
+ `import { useState, useEffect, useCallback, useRef } from 'react'`,
1931
+ `import { getClientConfig } from '../client'`,
1932
+ `import type { ${[paramsType, responseType, bodyType].filter(Boolean).join(", ")} } from '../types'`
1933
+ ].filter(Boolean);
1934
+ if (options.zod) {
1935
+ const schemaName = `${toPascalCase(op.operationId)}ResponseSchema`;
1936
+ lines.push(`import { ${schemaName.charAt(0).toLowerCase() + schemaName.slice(1)} } from '../schemas'`);
1937
+ }
1938
+ lines.push(``);
1939
+ if (isRead) {
1940
+ lines.push(...this.generateReadHook(op, hookName, paramsType, responseType, hasParams, options));
1941
+ } else {
1942
+ lines.push(...this.generateWriteHook(op, hookName, paramsType, responseType, bodyType, hasParams, options));
1943
+ }
1944
+ return lines.join("\n") + "\n";
1945
+ }
1946
+ generateReadHook(op, hookName, paramsType, responseType, hasParams, options) {
1947
+ const paramArg = hasParams ? `params: ${paramsType}` : "";
1948
+ const enabledArg = `options?: { enabled?: boolean }`;
1949
+ const args = [paramArg, enabledArg].filter(Boolean).join(", ");
1950
+ return [
1951
+ `export interface ${hookName}Result {`,
1952
+ ` data: ${responseType} | null`,
1953
+ ` error: Error | null`,
1954
+ ` isLoading: boolean`,
1955
+ ` refetch: () => void`,
1956
+ `}`,
1957
+ ``,
1958
+ `export function ${hookName}(${args}): ${hookName}Result {`,
1959
+ ` const [data, setData] = useState<${responseType} | null>(null)`,
1960
+ ` const [error, setError] = useState<Error | null>(null)`,
1961
+ ` const [isLoading, setIsLoading] = useState(false)`,
1962
+ ` const abortRef = useRef<AbortController | null>(null)`,
1963
+ ``,
1964
+ ` const fetchData = useCallback(async () => {`,
1965
+ ` if (options?.enabled === false) return`,
1966
+ ` abortRef.current?.abort()`,
1967
+ ` const controller = new AbortController()`,
1968
+ ` abortRef.current = controller`,
1969
+ ``,
1970
+ ` setIsLoading(true)`,
1971
+ ` setError(null)`,
1972
+ ``,
1973
+ ` try {`,
1974
+ ` const config = getClientConfig()`,
1975
+ ` const url = buildUrl(config.baseUrl, ${hasParams ? "params" : "undefined"})`,
1976
+ ` const res = await fetch(url, {`,
1977
+ ` method: '${op.method}',`,
1978
+ ` headers: { 'Content-Type': 'application/json', ...config.headers },`,
1979
+ ` signal: controller.signal,`,
1980
+ ` })`,
1981
+ ``,
1982
+ ` if (!res.ok) throw new Error(\`HTTP \${res.status}: \${res.statusText}\`)`,
1983
+ ``,
1984
+ options.zod ? ` const json = await res.json()
1985
+ const parsed = ${toPascalCase(op.operationId).charAt(0).toLowerCase() + toPascalCase(op.operationId).slice(1)}ResponseSchema.parse(json)
1986
+ setData(parsed as ${responseType})` : ` const json = await res.json()
1987
+ setData(json as ${responseType})`,
1988
+ ` } catch (err) {`,
1989
+ ` if (err instanceof Error && err.name !== 'AbortError') {`,
1990
+ ` setError(err)`,
1991
+ ` }`,
1992
+ ` } finally {`,
1993
+ ` setIsLoading(false)`,
1994
+ ` }`,
1995
+ ` // eslint-disable-next-line react-hooks/exhaustive-deps`,
1996
+ ` }, [${hasParams ? "JSON.stringify(params)" : ""}])`,
1997
+ ``,
1998
+ ` useEffect(() => {`,
1999
+ ` fetchData()`,
2000
+ ` return () => { abortRef.current?.abort() }`,
2001
+ ` }, [fetchData])`,
2002
+ ``,
2003
+ ` return { data, error, isLoading, refetch: fetchData }`,
2004
+ `}`,
2005
+ ``,
2006
+ ...this.generateBuildUrl(op)
2007
+ ];
2008
+ }
2009
+ generateWriteHook(op, hookName, paramsType, responseType, bodyType, hasParams, options) {
2010
+ const mutateArgs = [];
2011
+ if (hasParams) mutateArgs.push(`params: ${paramsType}`);
2012
+ if (bodyType) mutateArgs.push(`body: ${bodyType}`);
2013
+ const mutateArgStr = mutateArgs.join(", ");
2014
+ return [
2015
+ `export interface ${hookName}Result {`,
2016
+ ` data: ${responseType} | null`,
2017
+ ` error: Error | null`,
2018
+ ` isLoading: boolean`,
2019
+ ` mutate: (${mutateArgStr}) => Promise<${responseType}>`,
2020
+ ` reset: () => void`,
2021
+ `}`,
2022
+ ``,
2023
+ `export function ${hookName}(): ${hookName}Result {`,
2024
+ ` const [data, setData] = useState<${responseType} | null>(null)`,
2025
+ ` const [error, setError] = useState<Error | null>(null)`,
2026
+ ` const [isLoading, setIsLoading] = useState(false)`,
2027
+ ``,
2028
+ ` const mutate = useCallback(async (${mutateArgStr}): Promise<${responseType}> => {`,
2029
+ ` setIsLoading(true)`,
2030
+ ` setError(null)`,
2031
+ ``,
2032
+ ` try {`,
2033
+ ` const config = getClientConfig()`,
2034
+ ` const url = buildUrl(config.baseUrl, ${hasParams ? "params" : "undefined"})`,
2035
+ ` const res = await fetch(url, {`,
2036
+ ` method: '${op.method}',`,
2037
+ ` headers: { 'Content-Type': 'application/json', ...config.headers },`,
2038
+ bodyType ? ` body: JSON.stringify(body),` : "",
2039
+ ` })`,
2040
+ ``,
2041
+ ` if (!res.ok) throw new Error(\`HTTP \${res.status}: \${res.statusText}\`)`,
2042
+ ``,
2043
+ options.zod ? ` const json = await res.json()
2044
+ const parsed = ${toPascalCase(op.operationId).charAt(0).toLowerCase() + toPascalCase(op.operationId).slice(1)}ResponseSchema.parse(json)
2045
+ setData(parsed as ${responseType})
2046
+ return parsed as ${responseType}` : ` const json = await res.json()
2047
+ setData(json as ${responseType})
2048
+ return json as ${responseType}`,
2049
+ ` } catch (err) {`,
2050
+ ` const error = err instanceof Error ? err : new Error(String(err))`,
2051
+ ` setError(error)`,
2052
+ ` throw error`,
2053
+ ` } finally {`,
2054
+ ` setIsLoading(false)`,
2055
+ ` }`,
2056
+ ` }, [])`,
2057
+ ``,
2058
+ ` const reset = useCallback(() => {`,
2059
+ ` setData(null)`,
2060
+ ` setError(null)`,
2061
+ ` setIsLoading(false)`,
2062
+ ` }, [])`,
2063
+ ``,
2064
+ ` return { data, error, isLoading, mutate, reset }`,
2065
+ `}`,
2066
+ ``,
2067
+ ...this.generateBuildUrl(op)
2068
+ ].filter(Boolean);
2069
+ }
2070
+ generateBuildUrl(op) {
2071
+ const hasPathParams = op.pathParams.length > 0;
2072
+ const hasQueryParams = op.queryParams.length > 0;
2073
+ const paramsType = this.getParamsTypeName(op.operationId);
2074
+ const hasAnyParams = hasPathParams || hasQueryParams;
2075
+ if (!hasAnyParams) {
2076
+ return [
2077
+ `function buildUrl(baseUrl: string, _params: undefined): string {`,
2078
+ ` return \`\${baseUrl}${op.path}\``,
2079
+ `}`
2080
+ ];
2081
+ }
2082
+ let pathTemplate = op.path;
2083
+ for (const p of op.pathParams) {
2084
+ pathTemplate = pathTemplate.replace(`{${p.name}}`, `\${params.${p.name}}`);
2085
+ }
2086
+ const lines = [
2087
+ `function buildUrl(baseUrl: string, params: ${paramsType}): string {`,
2088
+ ` let url = \`\${baseUrl}${pathTemplate}\``
2089
+ ];
2090
+ if (hasQueryParams) {
2091
+ lines.push(
2092
+ ` const query = new URLSearchParams()`
2093
+ );
2094
+ for (const p of op.queryParams) {
2095
+ lines.push(
2096
+ ` if (params.${p.name} !== undefined) query.set('${p.name}', String(params.${p.name}))`
2097
+ );
2098
+ }
2099
+ lines.push(
2100
+ ` const qs = query.toString()`,
2101
+ ` if (qs) url += \`?\${qs}\``
2102
+ );
2103
+ }
2104
+ lines.push(
2105
+ ` return url`,
2106
+ `}`
2107
+ );
2108
+ return lines;
2109
+ }
2110
+ };
2111
+
2112
+ // src/generators/axios-generator.ts
2113
+ var AxiosGenerator = class extends BaseHookGenerator {
2114
+ generateClientConfig(spec, options) {
2115
+ const baseUrl = options.baseUrl || spec.baseUrl || "";
2116
+ return {
2117
+ path: "client.ts",
2118
+ content: [
2119
+ `/**`,
2120
+ ` * Axios API client configuration.`,
2121
+ ` * Generated by auto-api-hooks \u2014 do not edit manually.`,
2122
+ ` */`,
2123
+ `import axios from 'axios'`,
2124
+ ``,
2125
+ `export const API_BASE_URL = '${baseUrl}'`,
2126
+ ``,
2127
+ `export const apiClient = axios.create({`,
2128
+ ` baseURL: API_BASE_URL,`,
2129
+ ` headers: { 'Content-Type': 'application/json' },`,
2130
+ `})`,
2131
+ ``,
2132
+ `/**`,
2133
+ ` * Configure the API client.`,
2134
+ ` */`,
2135
+ `export function configureClient(config: { baseUrl?: string; headers?: Record<string, string> }): void {`,
2136
+ ` if (config.baseUrl) apiClient.defaults.baseURL = config.baseUrl`,
2137
+ ` if (config.headers) Object.assign(apiClient.defaults.headers.common, config.headers)`,
2138
+ `}`,
2139
+ ``
2140
+ ].join("\n")
2141
+ };
2142
+ }
2143
+ generateHookFile(op, hookName, _spec, options) {
2144
+ const paramsType = this.getParamsTypeName(op.operationId);
2145
+ const responseType = this.getResponseTypeName(op.operationId);
2146
+ const bodyType = op.requestBody ? this.getBodyTypeName(op.operationId) : null;
2147
+ const hasParams = op.pathParams.length > 0 || op.queryParams.length > 0;
2148
+ const isRead = this.isReadOperation(op);
2149
+ const imports = [
2150
+ `import type { ${[paramsType, responseType, bodyType].filter(Boolean).join(", ")} } from '../types'`,
2151
+ `import { apiClient } from '../client'`
2152
+ ];
2153
+ if (isRead) {
2154
+ imports.unshift(`import { useState, useEffect, useCallback, useRef } from 'react'`);
2155
+ } else {
2156
+ imports.unshift(`import { useState, useCallback } from 'react'`);
2157
+ }
2158
+ if (options.zod) {
2159
+ const schemaVar = `${toPascalCase(op.operationId).charAt(0).toLowerCase() + toPascalCase(op.operationId).slice(1)}ResponseSchema`;
2160
+ imports.push(`import { ${schemaVar} } from '../schemas'`);
2161
+ }
2162
+ const lines = [
2163
+ `/**`,
2164
+ ` * ${op.summary || `${op.method} ${op.path}`}`,
2165
+ ` *`,
2166
+ ` * \`${op.method} ${op.path}\``,
2167
+ op.deprecated ? ` * @deprecated` : "",
2168
+ ` * Generated by auto-api-hooks \u2014 do not edit manually.`,
2169
+ ` */`,
2170
+ ...imports,
2171
+ ``
2172
+ ].filter(Boolean);
2173
+ if (isRead) {
2174
+ lines.push(...this.generateReadHook(op, hookName, paramsType, responseType, hasParams, options));
2175
+ } else {
2176
+ lines.push(...this.generateWriteHook(op, hookName, paramsType, responseType, bodyType, hasParams, options));
2177
+ }
2178
+ return lines.join("\n") + "\n";
2179
+ }
2180
+ generateReadHook(op, hookName, paramsType, responseType, hasParams, options) {
2181
+ const paramArg = hasParams ? `params: ${paramsType}` : "";
2182
+ const args = [paramArg, `options?: { enabled?: boolean }`].filter(Boolean).join(", ");
2183
+ const schemaVar = `${toPascalCase(op.operationId).charAt(0).toLowerCase() + toPascalCase(op.operationId).slice(1)}ResponseSchema`;
2184
+ let pathExpr = `'${op.path}'`;
2185
+ if (op.pathParams.length > 0) {
2186
+ let tmpl = op.path;
2187
+ for (const p of op.pathParams) {
2188
+ tmpl = tmpl.replace(`{${p.name}}`, `\${params.${p.name}}`);
2189
+ }
2190
+ pathExpr = `\`${tmpl}\``;
2191
+ }
2192
+ const queryParams = op.queryParams.length > 0 ? `params: { ${op.queryParams.map((p) => `${p.name}: params.${p.name}`).join(", ")} },` : "";
2193
+ return [
2194
+ `export interface ${hookName}Result {`,
2195
+ ` data: ${responseType} | null`,
2196
+ ` error: Error | null`,
2197
+ ` isLoading: boolean`,
2198
+ ` refetch: () => void`,
2199
+ `}`,
2200
+ ``,
2201
+ `export function ${hookName}(${args}): ${hookName}Result {`,
2202
+ ` const [data, setData] = useState<${responseType} | null>(null)`,
2203
+ ` const [error, setError] = useState<Error | null>(null)`,
2204
+ ` const [isLoading, setIsLoading] = useState(false)`,
2205
+ ` const cancelRef = useRef<(() => void) | null>(null)`,
2206
+ ``,
2207
+ ` const fetchData = useCallback(async () => {`,
2208
+ ` if (options?.enabled === false) return`,
2209
+ ` cancelRef.current?.()`,
2210
+ ` const source = new AbortController()`,
2211
+ ` cancelRef.current = () => source.abort()`,
2212
+ ``,
2213
+ ` setIsLoading(true)`,
2214
+ ` setError(null)`,
2215
+ ``,
2216
+ ` try {`,
2217
+ ` const res = await apiClient.get<${responseType}>(${pathExpr}, {`,
2218
+ queryParams ? ` ${queryParams}` : "",
2219
+ ` signal: source.signal,`,
2220
+ ` })`,
2221
+ options.zod ? ` const parsed = ${schemaVar}.parse(res.data)
2222
+ setData(parsed as ${responseType})` : ` setData(res.data)`,
2223
+ ` } catch (err) {`,
2224
+ ` if (!source.signal.aborted) {`,
2225
+ ` setError(err instanceof Error ? err : new Error(String(err)))`,
2226
+ ` }`,
2227
+ ` } finally {`,
2228
+ ` setIsLoading(false)`,
2229
+ ` }`,
2230
+ ` // eslint-disable-next-line react-hooks/exhaustive-deps`,
2231
+ ` }, [${hasParams ? "JSON.stringify(params)" : ""}])`,
2232
+ ``,
2233
+ ` useEffect(() => {`,
2234
+ ` fetchData()`,
2235
+ ` return () => { cancelRef.current?.() }`,
2236
+ ` }, [fetchData])`,
2237
+ ``,
2238
+ ` return { data, error, isLoading, refetch: fetchData }`,
2239
+ `}`
2240
+ ].filter(Boolean);
2241
+ }
2242
+ generateWriteHook(op, hookName, paramsType, responseType, bodyType, hasParams, options) {
2243
+ const mutateArgs = [];
2244
+ if (hasParams) mutateArgs.push(`params: ${paramsType}`);
2245
+ if (bodyType) mutateArgs.push(`body: ${bodyType}`);
2246
+ const mutateArgStr = mutateArgs.join(", ");
2247
+ const schemaVar = `${toPascalCase(op.operationId).charAt(0).toLowerCase() + toPascalCase(op.operationId).slice(1)}ResponseSchema`;
2248
+ let pathExpr = `'${op.path}'`;
2249
+ if (op.pathParams.length > 0) {
2250
+ let tmpl = op.path;
2251
+ for (const p of op.pathParams) {
2252
+ tmpl = tmpl.replace(`{${p.name}}`, `\${params.${p.name}}`);
2253
+ }
2254
+ pathExpr = `\`${tmpl}\``;
2255
+ }
2256
+ const methodLower = op.method.toLowerCase();
2257
+ const axiosCall = bodyType ? `apiClient.${methodLower}<${responseType}>(${pathExpr}, body)` : `apiClient.${methodLower}<${responseType}>(${pathExpr})`;
2258
+ return [
2259
+ `export interface ${hookName}Result {`,
2260
+ ` data: ${responseType} | null`,
2261
+ ` error: Error | null`,
2262
+ ` isLoading: boolean`,
2263
+ ` mutate: (${mutateArgStr}) => Promise<${responseType}>`,
2264
+ ` reset: () => void`,
2265
+ `}`,
2266
+ ``,
2267
+ `export function ${hookName}(): ${hookName}Result {`,
2268
+ ` const [data, setData] = useState<${responseType} | null>(null)`,
2269
+ ` const [error, setError] = useState<Error | null>(null)`,
2270
+ ` const [isLoading, setIsLoading] = useState(false)`,
2271
+ ``,
2272
+ ` const mutate = useCallback(async (${mutateArgStr}): Promise<${responseType}> => {`,
2273
+ ` setIsLoading(true)`,
2274
+ ` setError(null)`,
2275
+ ``,
2276
+ ` try {`,
2277
+ ` const res = await ${axiosCall}`,
2278
+ options.zod ? ` const parsed = ${schemaVar}.parse(res.data)
2279
+ setData(parsed as ${responseType})
2280
+ return parsed as ${responseType}` : ` setData(res.data)
2281
+ return res.data`,
2282
+ ` } catch (err) {`,
2283
+ ` const error = err instanceof Error ? err : new Error(String(err))`,
2284
+ ` setError(error)`,
2285
+ ` throw error`,
2286
+ ` } finally {`,
2287
+ ` setIsLoading(false)`,
2288
+ ` }`,
2289
+ ` }, [])`,
2290
+ ``,
2291
+ ` const reset = useCallback(() => {`,
2292
+ ` setData(null)`,
2293
+ ` setError(null)`,
2294
+ ` setIsLoading(false)`,
2295
+ ` }, [])`,
2296
+ ``,
2297
+ ` return { data, error, isLoading, mutate, reset }`,
2298
+ `}`
2299
+ ];
2300
+ }
2301
+ };
2302
+
2303
+ // src/utils/cache-keys.ts
2304
+ function deriveCacheKeyFactories(operations) {
2305
+ const resourceMap = /* @__PURE__ */ new Map();
2306
+ for (const op of operations) {
2307
+ if (op.method !== "GET" && op.method !== "QUERY") continue;
2308
+ const resource = extractResource(op.path);
2309
+ const existing = resourceMap.get(resource) || { hasList: false, hasDetail: false };
2310
+ if (isDetailEndpoint(op.path)) {
2311
+ existing.hasDetail = true;
2312
+ } else {
2313
+ existing.hasList = true;
2314
+ }
2315
+ resourceMap.set(resource, existing);
2316
+ }
2317
+ const factories = [];
2318
+ for (const [resource, info] of resourceMap) {
2319
+ const singular = toCamelCase(resource);
2320
+ factories.push({
2321
+ resource: singular,
2322
+ variableName: `${singular}Keys`,
2323
+ rootKey: [resource],
2324
+ hasList: info.hasList,
2325
+ hasDetail: info.hasDetail
2326
+ });
2327
+ }
2328
+ return factories;
2329
+ }
2330
+ function getSwrKey(op) {
2331
+ const pathParams = op.pathParams.map((p) => p.name);
2332
+ let key = op.path;
2333
+ for (const param of pathParams) {
2334
+ key = key.replace(`{${param}}`, `\${${param}}`);
2335
+ }
2336
+ if (op.queryParams.length > 0) {
2337
+ return `\`${key}?\${new URLSearchParams(params as Record<string, string>).toString()}\``;
2338
+ }
2339
+ if (pathParams.length > 0) {
2340
+ return `\`${key}\``;
2341
+ }
2342
+ return `'${op.path}'`;
2343
+ }
2344
+ function getQueryKey(op) {
2345
+ const resource = extractResource(op.path);
2346
+ const parts = [`'${resource}'`];
2347
+ if (isDetailEndpoint(op.path)) {
2348
+ parts.push(`'detail'`);
2349
+ for (const p of op.pathParams) {
2350
+ parts.push(p.name);
2351
+ }
2352
+ } else {
2353
+ parts.push(`'list'`);
2354
+ if (op.queryParams.length > 0) {
2355
+ parts.push("params");
2356
+ }
2357
+ }
2358
+ return `[${parts.join(", ")}] as const`;
2359
+ }
2360
+
2361
+ // src/generators/react-query-generator.ts
2362
+ var ReactQueryGenerator = class extends BaseHookGenerator {
2363
+ supportsInfiniteQueries() {
2364
+ return true;
2365
+ }
2366
+ generate(spec, options) {
2367
+ const files = super.generate(spec, options);
2368
+ files.push(this.generateQueryKeysFile(spec));
2369
+ return files;
2370
+ }
2371
+ /**
2372
+ * Generate the query-keys.ts file with cache key factories.
2373
+ */
2374
+ generateQueryKeysFile(spec) {
2375
+ const factories = deriveCacheKeyFactories(spec.operations);
2376
+ const lines = [
2377
+ `/**`,
2378
+ ` * Query key factories for TanStack React Query.`,
2379
+ ` * Generated by auto-api-hooks \u2014 do not edit manually.`,
2380
+ ` */`,
2381
+ ``
2382
+ ];
2383
+ for (const factory of factories) {
2384
+ lines.push(
2385
+ `export const ${factory.variableName} = {`,
2386
+ ` all: [${factory.rootKey.map((k) => `'${k}'`).join(", ")}] as const,`
2387
+ );
2388
+ if (factory.hasList) {
2389
+ lines.push(
2390
+ ` lists: () => [...${factory.variableName}.all, 'list'] as const,`,
2391
+ ` list: (params?: Record<string, unknown>) => [...${factory.variableName}.lists(), params] as const,`
2392
+ );
2393
+ }
2394
+ if (factory.hasDetail) {
2395
+ lines.push(
2396
+ ` details: () => [...${factory.variableName}.all, 'detail'] as const,`,
2397
+ ` detail: (id: string | number) => [...${factory.variableName}.details(), id] as const,`
2398
+ );
2399
+ }
2400
+ lines.push(`} as const`, ``);
2401
+ }
2402
+ return {
2403
+ path: "query-keys.ts",
2404
+ content: lines.join("\n")
2405
+ };
2406
+ }
2407
+ generateBarrelIndex(tagGroups, options) {
2408
+ const base = super.generateBarrelIndex(tagGroups, options);
2409
+ const content = base.content.replace(
2410
+ `export * from './client'`,
2411
+ `export * from './client'
2412
+ export * from './query-keys'`
2413
+ );
2414
+ return { ...base, content };
2415
+ }
2416
+ generateHookFile(op, hookName, _spec, options) {
2417
+ const isRead = this.isReadOperation(op);
2418
+ return isRead ? this.generateQueryHook(op, hookName, options) : this.generateMutationHook(op, hookName, options);
2419
+ }
2420
+ generateInfiniteHookFile(op, hookName, _spec, options) {
2421
+ if (!op.pagination) return null;
2422
+ return this.generateInfiniteQueryHook(op, hookName, op.pagination, options);
2423
+ }
2424
+ // ---------------------------------------------------------------------------
2425
+ // useQuery
2426
+ // ---------------------------------------------------------------------------
2427
+ generateQueryHook(op, hookName, options) {
2428
+ const paramsType = this.getParamsTypeName(op.operationId);
2429
+ const responseType = this.getResponseTypeName(op.operationId);
2430
+ const hasParams = op.pathParams.length > 0 || op.queryParams.length > 0;
2431
+ const queryKey = getQueryKey(op);
2432
+ const schemaVar = `${toPascalCase(op.operationId).charAt(0).toLowerCase() + toPascalCase(op.operationId).slice(1)}ResponseSchema`;
2433
+ const paramArg = hasParams ? `params: ${paramsType}` : "";
2434
+ const optionsArg = `options?: Partial<UseQueryOptions<${responseType}, Error>>`;
2435
+ const args = [paramArg, optionsArg].filter(Boolean).join(", ");
2436
+ const fetcherBody = this.generateFetcherBody(op, options);
2437
+ const lines = [
2438
+ `/**`,
2439
+ ` * ${op.summary || `${op.method} ${op.path}`}`,
2440
+ ` *`,
2441
+ ` * \`${op.method} ${op.path}\``,
2442
+ op.deprecated ? ` * @deprecated` : "",
2443
+ ` * Generated by auto-api-hooks \u2014 do not edit manually.`,
2444
+ ` */`,
2445
+ `import { useQuery } from '@tanstack/react-query'`,
2446
+ `import type { UseQueryOptions } from '@tanstack/react-query'`,
2447
+ `import { getClientConfig } from '../client'`,
2448
+ `import type { ${[paramsType, responseType].filter(Boolean).join(", ")} } from '../types'`
2449
+ ].filter(Boolean);
2450
+ if (options.zod) {
2451
+ lines.push(`import { ${schemaVar} } from '../schemas'`);
2452
+ }
2453
+ lines.push(
2454
+ ``,
2455
+ `export function ${hookName}(${args}) {`,
2456
+ ` return useQuery<${responseType}, Error>({`,
2457
+ ` queryKey: ${queryKey},`,
2458
+ ` queryFn: async () => {`,
2459
+ ...fetcherBody.map((l) => ` ${l}`),
2460
+ ` },`,
2461
+ ` ...options,`,
2462
+ ` })`,
2463
+ `}`,
2464
+ ``
2465
+ );
2466
+ return lines.join("\n");
2467
+ }
2468
+ // ---------------------------------------------------------------------------
2469
+ // useMutation
2470
+ // ---------------------------------------------------------------------------
2471
+ generateMutationHook(op, hookName, options) {
2472
+ const paramsType = this.getParamsTypeName(op.operationId);
2473
+ const responseType = this.getResponseTypeName(op.operationId);
2474
+ const bodyType = op.requestBody ? this.getBodyTypeName(op.operationId) : null;
2475
+ const hasParams = op.pathParams.length > 0 || op.queryParams.length > 0;
2476
+ const schemaVar = `${toPascalCase(op.operationId).charAt(0).toLowerCase() + toPascalCase(op.operationId).slice(1)}ResponseSchema`;
2477
+ const varFields = [];
2478
+ if (hasParams) varFields.push(`params: ${paramsType}`);
2479
+ if (bodyType) varFields.push(`body: ${bodyType}`);
2480
+ const varsType = varFields.length > 0 ? `{ ${varFields.join("; ")} }` : "void";
2481
+ const fetcherBody = this.generateMutationFetcherBody(op, options);
2482
+ const typeImports = [paramsType, responseType, bodyType].filter(Boolean).join(", ");
2483
+ const lines = [
2484
+ `/**`,
2485
+ ` * ${op.summary || `${op.method} ${op.path}`}`,
2486
+ ` *`,
2487
+ ` * \`${op.method} ${op.path}\``,
2488
+ op.deprecated ? ` * @deprecated` : "",
2489
+ ` * Generated by auto-api-hooks \u2014 do not edit manually.`,
2490
+ ` */`,
2491
+ `import { useMutation } from '@tanstack/react-query'`,
2492
+ `import type { UseMutationOptions } from '@tanstack/react-query'`,
2493
+ `import { getClientConfig } from '../client'`,
2494
+ `import type { ${typeImports} } from '../types'`
2495
+ ].filter(Boolean);
2496
+ if (options.zod) {
2497
+ lines.push(`import { ${schemaVar} } from '../schemas'`);
2498
+ }
2499
+ lines.push(
2500
+ ``,
2501
+ `export function ${hookName}(options?: UseMutationOptions<${responseType}, Error, ${varsType}>) {`,
2502
+ ` return useMutation<${responseType}, Error, ${varsType}>({`,
2503
+ ` mutationFn: async (${varFields.length > 0 ? "vars" : ""}) => {`,
2504
+ ...fetcherBody.map((l) => ` ${l}`),
2505
+ ` },`,
2506
+ ` ...options,`,
2507
+ ` })`,
2508
+ `}`,
2509
+ ``
2510
+ );
2511
+ return lines.join("\n");
2512
+ }
2513
+ // ---------------------------------------------------------------------------
2514
+ // useInfiniteQuery
2515
+ // ---------------------------------------------------------------------------
2516
+ generateInfiniteQueryHook(op, hookName, pagination, options) {
2517
+ const paramsType = this.getParamsTypeName(op.operationId);
2518
+ const responseType = this.getResponseTypeName(op.operationId);
2519
+ const hasParams = op.queryParams.length > 1 || op.pathParams.length > 0;
2520
+ const queryKey = getQueryKey(op);
2521
+ const schemaVar = `${toPascalCase(op.operationId).charAt(0).toLowerCase() + toPascalCase(op.operationId).slice(1)}ResponseSchema`;
2522
+ const pageParamType = pagination.strategy === "cursor" ? "string | undefined" : "number";
2523
+ const initialPageParam = pagination.strategy === "cursor" ? "undefined" : pagination.strategy === "offset-limit" ? "0" : "1";
2524
+ const nextPagePath = pagination.nextPagePath.join("?.");
2525
+ const paramArg = hasParams ? `params: Omit<${paramsType}, '${pagination.pageParam}'>` : "";
2526
+ const optionsArg = `options?: Partial<UseInfiniteQueryOptions<${responseType}, Error>>`;
2527
+ const args = [paramArg, optionsArg].filter(Boolean).join(", ");
2528
+ const lines = [
2529
+ `/**`,
2530
+ ` * ${op.summary || `${op.method} ${op.path}`} (infinite)`,
2531
+ ` *`,
2532
+ ` * \`${op.method} ${op.path}\``,
2533
+ op.deprecated ? ` * @deprecated` : "",
2534
+ ` * Generated by auto-api-hooks \u2014 do not edit manually.`,
2535
+ ` */`,
2536
+ `import { useInfiniteQuery } from '@tanstack/react-query'`,
2537
+ `import type { UseInfiniteQueryOptions } from '@tanstack/react-query'`,
2538
+ `import { getClientConfig } from '../client'`,
2539
+ `import type { ${[paramsType, responseType].filter(Boolean).join(", ")} } from '../types'`
2540
+ ].filter(Boolean);
2541
+ if (options.zod) {
2542
+ lines.push(`import { ${schemaVar} } from '../schemas'`);
2543
+ }
2544
+ let pathExpr = `'${op.path}'`;
2545
+ if (op.pathParams.length > 0) {
2546
+ let tmpl = op.path;
2547
+ for (const p of op.pathParams) {
2548
+ tmpl = tmpl.replace(`{${p.name}}`, `\${params.${p.name}}`);
2549
+ }
2550
+ pathExpr = `\`${tmpl}\``;
2551
+ }
2552
+ lines.push(
2553
+ ``,
2554
+ `export function ${hookName}(${args}) {`,
2555
+ ` return useInfiniteQuery<${responseType}, Error>({`,
2556
+ ` queryKey: ${queryKey},`,
2557
+ ` queryFn: async ({ pageParam }) => {`,
2558
+ ` const config = getClientConfig()`,
2559
+ ` const url = new URL(${pathExpr}, config.baseUrl)`
2560
+ );
2561
+ for (const p of op.queryParams) {
2562
+ if (p.name === pagination.pageParam) {
2563
+ lines.push(` if (pageParam !== undefined) url.searchParams.set('${p.name}', String(pageParam))`);
2564
+ } else {
2565
+ lines.push(` if (${hasParams ? `params.${p.name}` : "undefined"} !== undefined) url.searchParams.set('${p.name}', String(${hasParams ? `params.${p.name}` : ""}))`);
2566
+ }
2567
+ }
2568
+ lines.push(
2569
+ ` const res = await fetch(url.toString(), {`,
2570
+ ` headers: { 'Content-Type': 'application/json', ...config.headers },`,
2571
+ ` })`,
2572
+ ` if (!res.ok) throw new Error(\`HTTP \${res.status}: \${res.statusText}\`)`
2573
+ );
2574
+ if (options.zod) {
2575
+ lines.push(
2576
+ ` const json = await res.json()`,
2577
+ ` return ${schemaVar}.parse(json) as ${responseType}`
2578
+ );
2579
+ } else {
2580
+ lines.push(` return (await res.json()) as ${responseType}`);
2581
+ }
2582
+ lines.push(
2583
+ ` },`,
2584
+ ` initialPageParam: ${initialPageParam} as ${pageParamType},`,
2585
+ ` getNextPageParam: (lastPage) => (lastPage as Record<string, unknown>)?.${nextPagePath} as ${pageParamType},`,
2586
+ ` ...options,`,
2587
+ ` })`,
2588
+ `}`,
2589
+ ``
2590
+ );
2591
+ return lines.join("\n");
2592
+ }
2593
+ // ---------------------------------------------------------------------------
2594
+ // Shared fetcher body generators
2595
+ // ---------------------------------------------------------------------------
2596
+ generateFetcherBody(op, options) {
2597
+ const responseType = this.getResponseTypeName(op.operationId);
2598
+ const schemaVar = `${toPascalCase(op.operationId).charAt(0).toLowerCase() + toPascalCase(op.operationId).slice(1)}ResponseSchema`;
2599
+ let pathExpr = `'${op.path}'`;
2600
+ if (op.pathParams.length > 0) {
2601
+ let tmpl = op.path;
2602
+ for (const p of op.pathParams) {
2603
+ tmpl = tmpl.replace(`{${p.name}}`, `\${params.${p.name}}`);
2604
+ }
2605
+ pathExpr = `\`${tmpl}\``;
2606
+ }
2607
+ const lines = [
2608
+ `const config = getClientConfig()`,
2609
+ `const url = new URL(${pathExpr}, config.baseUrl)`
2610
+ ];
2611
+ for (const p of op.queryParams) {
2612
+ lines.push(`if (params.${p.name} !== undefined) url.searchParams.set('${p.name}', String(params.${p.name}))`);
2613
+ }
2614
+ lines.push(
2615
+ `const res = await fetch(url.toString(), {`,
2616
+ ` headers: { 'Content-Type': 'application/json', ...config.headers },`,
2617
+ `})`,
2618
+ `if (!res.ok) throw new Error(\`HTTP \${res.status}: \${res.statusText}\`)`
2619
+ );
2620
+ if (options.zod) {
2621
+ lines.push(
2622
+ `const json = await res.json()`,
2623
+ `return ${schemaVar}.parse(json) as ${responseType}`
2624
+ );
2625
+ } else {
2626
+ lines.push(`return (await res.json()) as ${responseType}`);
2627
+ }
2628
+ return lines;
2629
+ }
2630
+ generateMutationFetcherBody(op, options) {
2631
+ const responseType = this.getResponseTypeName(op.operationId);
2632
+ const hasParams = op.pathParams.length > 0 || op.queryParams.length > 0;
2633
+ const hasBody = !!op.requestBody;
2634
+ const schemaVar = `${toPascalCase(op.operationId).charAt(0).toLowerCase() + toPascalCase(op.operationId).slice(1)}ResponseSchema`;
2635
+ let pathExpr = `'${op.path}'`;
2636
+ if (op.pathParams.length > 0) {
2637
+ let tmpl = op.path;
2638
+ for (const p of op.pathParams) {
2639
+ tmpl = tmpl.replace(`{${p.name}}`, `\${vars.params.${p.name}}`);
2640
+ }
2641
+ pathExpr = `\`${tmpl}\``;
2642
+ }
2643
+ const lines = [
2644
+ `const config = getClientConfig()`,
2645
+ `const url = new URL(${pathExpr}, config.baseUrl)`
2646
+ ];
2647
+ if (hasParams) {
2648
+ for (const p of op.queryParams) {
2649
+ lines.push(`if (vars.params.${p.name} !== undefined) url.searchParams.set('${p.name}', String(vars.params.${p.name}))`);
2650
+ }
2651
+ }
2652
+ lines.push(
2653
+ `const res = await fetch(url.toString(), {`,
2654
+ ` method: '${op.method}',`,
2655
+ ` headers: { 'Content-Type': 'application/json', ...config.headers },`
2656
+ );
2657
+ if (hasBody) {
2658
+ lines.push(` body: JSON.stringify(vars.body),`);
2659
+ }
2660
+ lines.push(
2661
+ `})`,
2662
+ `if (!res.ok) throw new Error(\`HTTP \${res.status}: \${res.statusText}\`)`
2663
+ );
2664
+ if (options.zod) {
2665
+ lines.push(
2666
+ `const json = await res.json()`,
2667
+ `return ${schemaVar}.parse(json) as ${responseType}`
2668
+ );
2669
+ } else {
2670
+ lines.push(`return (await res.json()) as ${responseType}`);
2671
+ }
2672
+ return lines;
2673
+ }
2674
+ };
2675
+
2676
+ // src/generators/swr-generator.ts
2677
+ var SwrGenerator = class extends BaseHookGenerator {
2678
+ supportsInfiniteQueries() {
2679
+ return true;
2680
+ }
2681
+ generateHookFile(op, hookName, _spec, options) {
2682
+ const isRead = this.isReadOperation(op);
2683
+ return isRead ? this.generateSwrHook(op, hookName, options) : this.generateSwrMutationHook(op, hookName, options);
2684
+ }
2685
+ generateInfiniteHookFile(op, hookName, _spec, options) {
2686
+ if (!op.pagination) return null;
2687
+ return this.generateSwrInfiniteHook(op, hookName, op.pagination, options);
2688
+ }
2689
+ // ---------------------------------------------------------------------------
2690
+ // useSWR
2691
+ // ---------------------------------------------------------------------------
2692
+ generateSwrHook(op, hookName, options) {
2693
+ const paramsType = this.getParamsTypeName(op.operationId);
2694
+ const responseType = this.getResponseTypeName(op.operationId);
2695
+ const hasParams = op.pathParams.length > 0 || op.queryParams.length > 0;
2696
+ const swrKey = getSwrKey(op);
2697
+ const schemaVar = `${toPascalCase(op.operationId).charAt(0).toLowerCase() + toPascalCase(op.operationId).slice(1)}ResponseSchema`;
2698
+ const paramArg = hasParams ? `params: ${paramsType}` : "";
2699
+ const optionsArg = `options?: { enabled?: boolean }`;
2700
+ const args = [paramArg, optionsArg].filter(Boolean).join(", ");
2701
+ const fetcherBody = this.generateFetcherBody(op, options);
2702
+ const lines = [
2703
+ `/**`,
2704
+ ` * ${op.summary || `${op.method} ${op.path}`}`,
2705
+ ` *`,
2706
+ ` * \`${op.method} ${op.path}\``,
2707
+ op.deprecated ? ` * @deprecated` : "",
2708
+ ` * Generated by auto-api-hooks \u2014 do not edit manually.`,
2709
+ ` */`,
2710
+ `import useSWR from 'swr'`,
2711
+ `import type { SWRConfiguration } from 'swr'`,
2712
+ `import { getClientConfig } from '../client'`,
2713
+ `import type { ${[paramsType, responseType].filter(Boolean).join(", ")} } from '../types'`
2714
+ ].filter(Boolean);
2715
+ if (options.zod) {
2716
+ lines.push(`import { ${schemaVar} } from '../schemas'`);
2717
+ }
2718
+ lines.push(
2719
+ ``,
2720
+ `export function ${hookName}(${args}) {`,
2721
+ ` const key = options?.enabled === false ? null : ${swrKey}`,
2722
+ ``,
2723
+ ` return useSWR<${responseType}, Error>(key, async () => {`,
2724
+ ...fetcherBody.map((l) => ` ${l}`),
2725
+ ` })`,
2726
+ `}`,
2727
+ ``
2728
+ );
2729
+ return lines.join("\n");
2730
+ }
2731
+ // ---------------------------------------------------------------------------
2732
+ // useSWRMutation
2733
+ // ---------------------------------------------------------------------------
2734
+ generateSwrMutationHook(op, hookName, options) {
2735
+ const paramsType = this.getParamsTypeName(op.operationId);
2736
+ const responseType = this.getResponseTypeName(op.operationId);
2737
+ const bodyType = op.requestBody ? this.getBodyTypeName(op.operationId) : null;
2738
+ const hasParams = op.pathParams.length > 0 || op.queryParams.length > 0;
2739
+ const schemaVar = `${toPascalCase(op.operationId).charAt(0).toLowerCase() + toPascalCase(op.operationId).slice(1)}ResponseSchema`;
2740
+ const argFields = [];
2741
+ if (hasParams) argFields.push(`params: ${paramsType}`);
2742
+ if (bodyType) argFields.push(`body: ${bodyType}`);
2743
+ const argType = argFields.length > 0 ? `{ ${argFields.join("; ")} }` : "void";
2744
+ const typeImports = [paramsType, responseType, bodyType].filter(Boolean).join(", ");
2745
+ const fetcherBody = this.generateMutationFetcherBody(op, options);
2746
+ const lines = [
2747
+ `/**`,
2748
+ ` * ${op.summary || `${op.method} ${op.path}`}`,
2749
+ ` *`,
2750
+ ` * \`${op.method} ${op.path}\``,
2751
+ op.deprecated ? ` * @deprecated` : "",
2752
+ ` * Generated by auto-api-hooks \u2014 do not edit manually.`,
2753
+ ` */`,
2754
+ `import useSWRMutation from 'swr/mutation'`,
2755
+ `import { getClientConfig } from '../client'`,
2756
+ `import type { ${typeImports} } from '../types'`
2757
+ ].filter(Boolean);
2758
+ if (options.zod) {
2759
+ lines.push(`import { ${schemaVar} } from '../schemas'`);
2760
+ }
2761
+ lines.push(
2762
+ ``,
2763
+ `export function ${hookName}() {`,
2764
+ ` return useSWRMutation<${responseType}, Error, string, ${argType}>(`,
2765
+ ` '${op.method} ${op.path}',`,
2766
+ ` async (_key: string, { arg }: { arg: ${argType} }) => {`,
2767
+ ...fetcherBody.map((l) => ` ${l}`),
2768
+ ` },`,
2769
+ ` )`,
2770
+ `}`,
2771
+ ``
2772
+ );
2773
+ return lines.join("\n");
2774
+ }
2775
+ // ---------------------------------------------------------------------------
2776
+ // useSWRInfinite
2777
+ // ---------------------------------------------------------------------------
2778
+ generateSwrInfiniteHook(op, hookName, pagination, options) {
2779
+ const paramsType = this.getParamsTypeName(op.operationId);
2780
+ const responseType = this.getResponseTypeName(op.operationId);
2781
+ const hasParams = op.queryParams.length > 1 || op.pathParams.length > 0;
2782
+ const schemaVar = `${toPascalCase(op.operationId).charAt(0).toLowerCase() + toPascalCase(op.operationId).slice(1)}ResponseSchema`;
2783
+ const nextPagePath = pagination.nextPagePath.join("?.");
2784
+ const paramArg = hasParams ? `params: Omit<${paramsType}, '${pagination.pageParam}'>` : "";
2785
+ const args = [paramArg].filter(Boolean).join(", ");
2786
+ let pathExpr = `'${op.path}'`;
2787
+ if (op.pathParams.length > 0) {
2788
+ let tmpl = op.path;
2789
+ for (const p of op.pathParams) {
2790
+ tmpl = tmpl.replace(`{${p.name}}`, `\${params.${p.name}}`);
2791
+ }
2792
+ pathExpr = `\`${tmpl}\``;
2793
+ }
2794
+ const lines = [
2795
+ `/**`,
2796
+ ` * ${op.summary || `${op.method} ${op.path}`} (infinite)`,
2797
+ ` *`,
2798
+ ` * \`${op.method} ${op.path}\``,
2799
+ op.deprecated ? ` * @deprecated` : "",
2800
+ ` * Generated by auto-api-hooks \u2014 do not edit manually.`,
2801
+ ` */`,
2802
+ `import useSWRInfinite from 'swr/infinite'`,
2803
+ `import { getClientConfig } from '../client'`,
2804
+ `import type { ${[paramsType, responseType].filter(Boolean).join(", ")} } from '../types'`
2805
+ ].filter(Boolean);
2806
+ if (options.zod) {
2807
+ lines.push(`import { ${schemaVar} } from '../schemas'`);
2808
+ }
2809
+ lines.push(
2810
+ ``,
2811
+ `export function ${hookName}(${args}) {`,
2812
+ ` const getKey = (pageIndex: number, previousPageData: ${responseType} | null) => {`,
2813
+ ` if (previousPageData && !(previousPageData as Record<string, unknown>)?.${nextPagePath}) return null`,
2814
+ ` const pageParam = pageIndex === 0`,
2815
+ ` ? undefined`,
2816
+ ` : (previousPageData as Record<string, unknown>)?.${nextPagePath}`,
2817
+ ` return [${pathExpr}, pageParam${hasParams ? ", params" : ""}] as const`,
2818
+ ` }`,
2819
+ ``,
2820
+ ` return useSWRInfinite<${responseType}, Error>(getKey, async ([_path, pageParam]) => {`,
2821
+ ` const config = getClientConfig()`,
2822
+ ` const url = new URL(${pathExpr}, config.baseUrl)`
2823
+ );
2824
+ for (const p of op.queryParams) {
2825
+ if (p.name === pagination.pageParam) {
2826
+ lines.push(` if (pageParam !== undefined) url.searchParams.set('${p.name}', String(pageParam))`);
2827
+ } else {
2828
+ lines.push(` if (${hasParams ? `params.${p.name}` : "undefined"} !== undefined) url.searchParams.set('${p.name}', String(${hasParams ? `params.${p.name}` : ""}))`);
2829
+ }
2830
+ }
2831
+ lines.push(
2832
+ ` const res = await fetch(url.toString(), {`,
2833
+ ` headers: { 'Content-Type': 'application/json', ...config.headers },`,
2834
+ ` })`,
2835
+ ` if (!res.ok) throw new Error(\`HTTP \${res.status}: \${res.statusText}\`)`
2836
+ );
2837
+ if (options.zod) {
2838
+ lines.push(
2839
+ ` const json = await res.json()`,
2840
+ ` return ${schemaVar}.parse(json) as ${responseType}`
2841
+ );
2842
+ } else {
2843
+ lines.push(` return (await res.json()) as ${responseType}`);
2844
+ }
2845
+ lines.push(
2846
+ ` })`,
2847
+ `}`,
2848
+ ``
2849
+ );
2850
+ return lines.join("\n");
2851
+ }
2852
+ // ---------------------------------------------------------------------------
2853
+ // Shared fetcher body generators
2854
+ // ---------------------------------------------------------------------------
2855
+ generateFetcherBody(op, options) {
2856
+ const responseType = this.getResponseTypeName(op.operationId);
2857
+ const schemaVar = `${toPascalCase(op.operationId).charAt(0).toLowerCase() + toPascalCase(op.operationId).slice(1)}ResponseSchema`;
2858
+ let pathExpr = `'${op.path}'`;
2859
+ if (op.pathParams.length > 0) {
2860
+ let tmpl = op.path;
2861
+ for (const p of op.pathParams) {
2862
+ tmpl = tmpl.replace(`{${p.name}}`, `\${params.${p.name}}`);
2863
+ }
2864
+ pathExpr = `\`${tmpl}\``;
2865
+ }
2866
+ const lines = [
2867
+ `const config = getClientConfig()`,
2868
+ `const url = new URL(${pathExpr}, config.baseUrl)`
2869
+ ];
2870
+ for (const p of op.queryParams) {
2871
+ lines.push(`if (params.${p.name} !== undefined) url.searchParams.set('${p.name}', String(params.${p.name}))`);
2872
+ }
2873
+ lines.push(
2874
+ `const res = await fetch(url.toString(), {`,
2875
+ ` headers: { 'Content-Type': 'application/json', ...config.headers },`,
2876
+ `})`,
2877
+ `if (!res.ok) throw new Error(\`HTTP \${res.status}: \${res.statusText}\`)`
2878
+ );
2879
+ if (options.zod) {
2880
+ lines.push(
2881
+ `const json = await res.json()`,
2882
+ `return ${schemaVar}.parse(json) as ${responseType}`
2883
+ );
2884
+ } else {
2885
+ lines.push(`return (await res.json()) as ${responseType}`);
2886
+ }
2887
+ return lines;
2888
+ }
2889
+ generateMutationFetcherBody(op, options) {
2890
+ const responseType = this.getResponseTypeName(op.operationId);
2891
+ const hasParams = op.pathParams.length > 0 || op.queryParams.length > 0;
2892
+ const hasBody = !!op.requestBody;
2893
+ const schemaVar = `${toPascalCase(op.operationId).charAt(0).toLowerCase() + toPascalCase(op.operationId).slice(1)}ResponseSchema`;
2894
+ let pathExpr = `'${op.path}'`;
2895
+ if (op.pathParams.length > 0) {
2896
+ let tmpl = op.path;
2897
+ for (const p of op.pathParams) {
2898
+ tmpl = tmpl.replace(`{${p.name}}`, `\${arg.params.${p.name}}`);
2899
+ }
2900
+ pathExpr = `\`${tmpl}\``;
2901
+ }
2902
+ const lines = [
2903
+ `const config = getClientConfig()`,
2904
+ `const url = new URL(${pathExpr}, config.baseUrl)`
2905
+ ];
2906
+ if (hasParams) {
2907
+ for (const p of op.queryParams) {
2908
+ lines.push(`if (arg.params.${p.name} !== undefined) url.searchParams.set('${p.name}', String(arg.params.${p.name}))`);
2909
+ }
2910
+ }
2911
+ lines.push(
2912
+ `const res = await fetch(url.toString(), {`,
2913
+ ` method: '${op.method}',`,
2914
+ ` headers: { 'Content-Type': 'application/json', ...config.headers },`
2915
+ );
2916
+ if (hasBody) {
2917
+ lines.push(` body: JSON.stringify(arg.body),`);
2918
+ }
2919
+ lines.push(
2920
+ `})`,
2921
+ `if (!res.ok) throw new Error(\`HTTP \${res.status}: \${res.statusText}\`)`
2922
+ );
2923
+ if (options.zod) {
2924
+ lines.push(
2925
+ `const json = await res.json()`,
2926
+ `return ${schemaVar}.parse(json) as ${responseType}`
2927
+ );
2928
+ } else {
2929
+ lines.push(`return (await res.json()) as ${responseType}`);
2930
+ }
2931
+ return lines;
2932
+ }
2933
+ };
2934
+
2935
+ // src/utils/errors.ts
2936
+ var AutoApiHooksError = class extends Error {
2937
+ constructor(message) {
2938
+ super(message);
2939
+ this.name = "AutoApiHooksError";
2940
+ }
2941
+ };
2942
+ var GeneratorError = class extends AutoApiHooksError {
2943
+ constructor(message) {
2944
+ super(message);
2945
+ this.name = "GeneratorError";
2946
+ }
2947
+ };
2948
+ var FileWriteError = class extends AutoApiHooksError {
2949
+ constructor(message) {
2950
+ super(message);
2951
+ this.name = "FileWriteError";
2952
+ }
2953
+ };
2954
+
2955
+ // src/generators/index.ts
2956
+ function createGenerator(strategy) {
2957
+ switch (strategy) {
2958
+ case "fetch":
2959
+ return new FetchGenerator();
2960
+ case "axios":
2961
+ return new AxiosGenerator();
2962
+ case "react-query":
2963
+ return new ReactQueryGenerator();
2964
+ case "swr":
2965
+ return new SwrGenerator();
2966
+ default:
2967
+ throw new GeneratorError(`Unknown fetcher strategy: ${strategy}`);
2968
+ }
2969
+ }
2970
+ function generateHooks(spec, options) {
2971
+ const generator = createGenerator(options.fetcher);
2972
+ return generator.generate(spec, options);
2973
+ }
2974
+
2975
+ // src/mock-gen/faker-helpers.ts
2976
+ var counter = 0;
2977
+ function resetCounter() {
2978
+ counter = 0;
2979
+ }
2980
+ function emitMockValue(type, spec, depth = 0) {
2981
+ if (depth > 5) return `null`;
2982
+ switch (type.kind) {
2983
+ case "primitive":
2984
+ return emitPrimitiveMock(type.type, type.format);
2985
+ case "object":
2986
+ return emitObjectMock(type, spec, depth);
2987
+ case "array":
2988
+ return `[${emitMockValue(type.items, spec, depth + 1)}]`;
2989
+ case "enum":
2990
+ if (type.values.length === 0) return `null`;
2991
+ const val = type.values[0];
2992
+ return typeof val === "string" ? `'${val}'` : String(val);
2993
+ case "union":
2994
+ if (type.variants.length === 0) return `null`;
2995
+ return emitMockValue(type.variants[0], spec, depth + 1);
2996
+ case "ref": {
2997
+ const resolved = spec.types.get(type.name);
2998
+ if (resolved) return emitMockValue(resolved, spec, depth + 1);
2999
+ return `{}`;
3000
+ }
3001
+ default:
3002
+ return `null`;
3003
+ }
3004
+ }
3005
+ function emitPrimitiveMock(type, format) {
3006
+ switch (type) {
3007
+ case "string":
3008
+ return emitStringMock(format);
3009
+ case "number":
3010
+ case "integer":
3011
+ return String(counter++);
3012
+ case "boolean":
3013
+ return "true";
3014
+ case "null":
3015
+ return "null";
3016
+ default:
3017
+ return `'unknown'`;
3018
+ }
3019
+ }
3020
+ function emitStringMock(format) {
3021
+ switch (format) {
3022
+ case "date-time":
3023
+ return `'2024-01-01T00:00:00Z'`;
3024
+ case "date":
3025
+ return `'2024-01-01'`;
3026
+ case "email":
3027
+ return `'user@example.com'`;
3028
+ case "uuid":
3029
+ return `'00000000-0000-0000-0000-000000000001'`;
3030
+ case "uri":
3031
+ case "url":
3032
+ return `'https://example.com'`;
3033
+ case "hostname":
3034
+ return `'example.com'`;
3035
+ case "ipv4":
3036
+ return `'127.0.0.1'`;
3037
+ case "ipv6":
3038
+ return `'::1'`;
3039
+ default:
3040
+ return `'string-value-${counter++}'`;
3041
+ }
3042
+ }
3043
+ function emitObjectMock(type, spec, depth) {
3044
+ if (type.properties.length === 0) {
3045
+ return `{}`;
3046
+ }
3047
+ const props = type.properties.map((p) => ` ${safeKey(p.name)}: ${emitMockValue(p.type, spec, depth + 1)}`).join(",\n");
3048
+ return `{
3049
+ ${props}
3050
+ }`;
3051
+ }
3052
+ function safeKey(name) {
3053
+ return /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(name) ? name : `'${name}'`;
3054
+ }
3055
+ function emitMockDataFunction(op, spec) {
3056
+ resetCounter();
3057
+ const fnName = `generate${op.operationId.charAt(0).toUpperCase() + op.operationId.slice(1)}Mock`;
3058
+ const mockValue = emitMockValue(op.response.type, spec);
3059
+ return [
3060
+ `export function ${fnName}() {`,
3061
+ ` return ${mockValue}`,
3062
+ `}`
3063
+ ].join("\n");
3064
+ }
3065
+ function emitMockDataFile(spec) {
3066
+ const lines = [
3067
+ `/**`,
3068
+ ` * Mock data generators.`,
3069
+ ` * Generated by auto-api-hooks \u2014 do not edit manually.`,
3070
+ ` */`,
3071
+ ``
3072
+ ];
3073
+ for (const op of spec.operations) {
3074
+ resetCounter();
3075
+ lines.push(emitMockDataFunction(op, spec));
3076
+ lines.push(``);
3077
+ }
3078
+ return lines.join("\n");
3079
+ }
3080
+
3081
+ // src/mock-gen/handler-emitter.ts
3082
+ function emitMswHandlers(spec) {
3083
+ const lines = [
3084
+ `/**`,
3085
+ ` * MSW request handlers.`,
3086
+ ` * Generated by auto-api-hooks \u2014 do not edit manually.`,
3087
+ ` */`,
3088
+ `import { http, HttpResponse } from 'msw'`,
3089
+ `import * as mocks from './data'`,
3090
+ ``,
3091
+ `export const handlers = [`
3092
+ ];
3093
+ for (const op of spec.operations) {
3094
+ if (!isHttpMethod(op.method)) continue;
3095
+ const mswMethod = op.method.toLowerCase();
3096
+ const mswPath = convertPathToMsw(op.path, spec.baseUrl);
3097
+ const mockFnName = `generate${op.operationId.charAt(0).toUpperCase() + op.operationId.slice(1)}Mock`;
3098
+ const statusCode = getStatusCode(op);
3099
+ lines.push(
3100
+ ` http.${mswMethod}('${mswPath}', () => {`,
3101
+ ` return HttpResponse.json(mocks.${mockFnName}(), { status: ${statusCode} })`,
3102
+ ` }),`
3103
+ );
3104
+ }
3105
+ lines.push(
3106
+ `]`,
3107
+ ``
3108
+ );
3109
+ return lines.join("\n");
3110
+ }
3111
+ function emitMswServerSetup() {
3112
+ return [
3113
+ `/**`,
3114
+ ` * MSW server setup for Node.js (tests, SSR).`,
3115
+ ` * Generated by auto-api-hooks \u2014 do not edit manually.`,
3116
+ ` */`,
3117
+ `import { setupServer } from 'msw/node'`,
3118
+ `import { handlers } from './handlers'`,
3119
+ ``,
3120
+ `export const server = setupServer(...handlers)`,
3121
+ ``
3122
+ ].join("\n");
3123
+ }
3124
+ function emitMswBrowserSetup() {
3125
+ return [
3126
+ `/**`,
3127
+ ` * MSW browser setup for development.`,
3128
+ ` * Generated by auto-api-hooks \u2014 do not edit manually.`,
3129
+ ` */`,
3130
+ `import { setupWorker } from 'msw/browser'`,
3131
+ `import { handlers } from './handlers'`,
3132
+ ``,
3133
+ `export const worker = setupWorker(...handlers)`,
3134
+ ``
3135
+ ].join("\n");
3136
+ }
3137
+ function emitMockIndex() {
3138
+ return [
3139
+ `export { handlers } from './handlers'`,
3140
+ `export * from './data'`,
3141
+ ``
3142
+ ].join("\n");
3143
+ }
3144
+ function convertPathToMsw(path, baseUrl) {
3145
+ const mswPath = path.replace(/\{(\w+)\}/g, ":$1");
3146
+ if (baseUrl) {
3147
+ return `${baseUrl}${mswPath}`;
3148
+ }
3149
+ return mswPath;
3150
+ }
3151
+ function isHttpMethod(method) {
3152
+ return ["GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS"].includes(method);
3153
+ }
3154
+ function getStatusCode(op) {
3155
+ if (op.response.statusCode !== "default" && typeof op.response.statusCode === "number") {
3156
+ return op.response.statusCode;
3157
+ }
3158
+ switch (op.method) {
3159
+ case "POST":
3160
+ return 201;
3161
+ case "DELETE":
3162
+ return 204;
3163
+ default:
3164
+ return 200;
3165
+ }
3166
+ }
3167
+
3168
+ // src/mock-gen/index.ts
3169
+ function generateMockFiles(spec) {
3170
+ return [
3171
+ { path: "mocks/data.ts", content: emitMockDataFile(spec) },
3172
+ { path: "mocks/handlers.ts", content: emitMswHandlers(spec) },
3173
+ { path: "mocks/server.ts", content: emitMswServerSetup() },
3174
+ { path: "mocks/browser.ts", content: emitMswBrowserSetup() },
3175
+ { path: "mocks/index.ts", content: emitMockIndex() }
3176
+ ];
3177
+ }
3178
+ async function writeFiles(outputDir, files) {
3179
+ for (const file of files) {
3180
+ const fullPath = join(outputDir, file.path);
3181
+ const dir = dirname(fullPath);
3182
+ try {
3183
+ await mkdir(dir, { recursive: true });
3184
+ await writeFile(fullPath, file.content, "utf-8");
3185
+ logger.verbose(`Wrote ${file.path}`);
3186
+ } catch (err) {
3187
+ throw new FileWriteError(
3188
+ `Failed to write ${fullPath}: ${err instanceof Error ? err.message : String(err)}`
3189
+ );
3190
+ }
3191
+ }
3192
+ }
3193
+
3194
+ // src/cli.ts
3195
+ var program = new Command();
3196
+ program.name("auto-api-hooks").description("Auto-generate type-safe React hooks from OpenAPI, Swagger, or GraphQL specs.").version("1.0.0");
3197
+ program.command("generate").description("Generate React hooks from an API specification").requiredOption("--spec <path>", "Path to API spec file (OpenAPI, Swagger, or GraphQL)").option("--fetcher <strategy>", "Fetching strategy: fetch | axios | react-query | swr", "fetch").option("--output <dir>", "Output directory", "./src/hooks").option("--base-url <url>", "Override base URL from spec").option("--zod", "Generate Zod validation schemas", false).option("--mock", "Generate MSW mock server", false).option("--watch", "Watch spec file and regenerate on change", false).option("--no-infinite", "Disable infinite query generation for paginated endpoints").option("--tag <tags...>", "Only generate hooks for specific tags").option("--verbose", "Enable verbose logging", false).action(async (opts) => {
3198
+ const {
3199
+ spec: specPath,
3200
+ fetcher,
3201
+ output,
3202
+ baseUrl,
3203
+ zod,
3204
+ mock,
3205
+ watch,
3206
+ infinite,
3207
+ tag: tags,
3208
+ verbose
3209
+ } = opts;
3210
+ if (verbose) setVerbose(true);
3211
+ const validStrategies = ["fetch", "axios", "react-query", "swr"];
3212
+ if (!validStrategies.includes(fetcher)) {
3213
+ logger.error(`Invalid fetcher strategy: ${pc2.bold(fetcher)}`);
3214
+ logger.info(`Valid options: ${validStrategies.join(", ")}`);
3215
+ process.exit(1);
3216
+ }
3217
+ try {
3218
+ await runGenerate({
3219
+ specPath,
3220
+ fetcher,
3221
+ outputDir: output,
3222
+ baseUrl,
3223
+ zod: !!zod,
3224
+ mock: !!mock,
3225
+ infiniteQueries: infinite !== false,
3226
+ tags
3227
+ });
3228
+ if (watch) {
3229
+ await startWatchMode(specPath, {
3230
+ specPath,
3231
+ fetcher,
3232
+ outputDir: output,
3233
+ baseUrl,
3234
+ zod: !!zod,
3235
+ mock: !!mock,
3236
+ infiniteQueries: infinite !== false,
3237
+ tags
3238
+ });
3239
+ }
3240
+ } catch (err) {
3241
+ logger.error(err instanceof Error ? err.message : String(err));
3242
+ process.exit(1);
3243
+ }
3244
+ });
3245
+ async function runGenerate(config) {
3246
+ const startTime = Date.now();
3247
+ logger.info(`Parsing ${pc2.bold(config.specPath)}...`);
3248
+ const spec = await parseSpec(config.specPath, { baseUrl: config.baseUrl });
3249
+ if (config.tags && config.tags.length > 0) {
3250
+ const tagSet = new Set(config.tags);
3251
+ spec.operations = spec.operations.filter(
3252
+ (op) => op.tags.some((t) => tagSet.has(t))
3253
+ );
3254
+ }
3255
+ logger.verbose(`Found ${spec.operations.length} operations, ${spec.types.size} types`);
3256
+ logger.info(`Generating ${pc2.bold(config.fetcher)} hooks...`);
3257
+ const hookFiles = generateHooks(spec, {
3258
+ fetcher: config.fetcher,
3259
+ zod: config.zod,
3260
+ mock: false,
3261
+ // Mock files are generated separately
3262
+ outputDir: config.outputDir,
3263
+ baseUrl: config.baseUrl,
3264
+ infiniteQueries: config.infiniteQueries
3265
+ });
3266
+ let mockFiles = [];
3267
+ if (config.mock) {
3268
+ logger.info("Generating MSW mock server...");
3269
+ mockFiles = generateMockFiles(spec);
3270
+ }
3271
+ const allFiles = [...hookFiles, ...mockFiles];
3272
+ await writeFiles(config.outputDir, allFiles);
3273
+ const duration = Date.now() - startTime;
3274
+ logger.success(
3275
+ `Generated ${pc2.bold(String(allFiles.length))} files in ${pc2.bold(config.outputDir)} (${duration}ms)`
3276
+ );
3277
+ const hookCount = hookFiles.filter((f) => f.path.includes("use")).length;
3278
+ logger.info(
3279
+ ` ${pc2.cyan("Hooks:")} ${hookCount} | ${pc2.cyan("Types:")} ${spec.types.size} | ${pc2.cyan("Strategy:")} ${config.fetcher}${config.zod ? ` | ${pc2.cyan("Zod:")} \u2714` : ""}${config.mock ? ` | ${pc2.cyan("Mocks:")} \u2714` : ""}`
3280
+ );
3281
+ }
3282
+ async function startWatchMode(specPath, config) {
3283
+ const { watch } = await import('chokidar');
3284
+ logger.info(`
3285
+ ${pc2.yellow("\u{1F440} Watching")} ${pc2.bold(specPath)} for changes...
3286
+ `);
3287
+ const watcher = watch(specPath, {
3288
+ ignoreInitial: true,
3289
+ awaitWriteFinish: { stabilityThreshold: 300, pollInterval: 100 }
3290
+ });
3291
+ watcher.on("change", async () => {
3292
+ logger.info(`
3293
+ ${pc2.yellow("\u21BB")} Spec changed, regenerating...`);
3294
+ try {
3295
+ await runGenerate(config);
3296
+ } catch (err) {
3297
+ logger.error(err instanceof Error ? err.message : String(err));
3298
+ }
3299
+ });
3300
+ process.on("SIGINT", () => {
3301
+ logger.info("\nStopping watch mode...");
3302
+ watcher.close();
3303
+ process.exit(0);
3304
+ });
3305
+ }
3306
+ program.parse();
3307
+ //# sourceMappingURL=cli.js.map
3308
+ //# sourceMappingURL=cli.js.map