@geekmidas/cli 0.9.0 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (146) hide show
  1. package/README.md +525 -0
  2. package/dist/bundler-DRXCw_YR.mjs +70 -0
  3. package/dist/bundler-DRXCw_YR.mjs.map +1 -0
  4. package/dist/bundler-WsEvH_b2.cjs +71 -0
  5. package/dist/bundler-WsEvH_b2.cjs.map +1 -0
  6. package/dist/{config-CFls09Ey.cjs → config-AmInkU7k.cjs} +10 -8
  7. package/dist/config-AmInkU7k.cjs.map +1 -0
  8. package/dist/{config-Bq72aj8e.mjs → config-DYULeEv8.mjs} +6 -4
  9. package/dist/config-DYULeEv8.mjs.map +1 -0
  10. package/dist/config.cjs +1 -1
  11. package/dist/config.d.cts +2 -1
  12. package/dist/config.d.cts.map +1 -0
  13. package/dist/config.d.mts +2 -1
  14. package/dist/config.d.mts.map +1 -0
  15. package/dist/config.mjs +1 -1
  16. package/dist/encryption-C8H-38Yy.mjs +42 -0
  17. package/dist/encryption-C8H-38Yy.mjs.map +1 -0
  18. package/dist/encryption-Dyf_r1h-.cjs +44 -0
  19. package/dist/encryption-Dyf_r1h-.cjs.map +1 -0
  20. package/dist/index.cjs +2125 -184
  21. package/dist/index.cjs.map +1 -1
  22. package/dist/index.mjs +2143 -197
  23. package/dist/index.mjs.map +1 -1
  24. package/dist/{openapi--vOy9mo4.mjs → openapi-BfFlOBCG.mjs} +812 -49
  25. package/dist/openapi-BfFlOBCG.mjs.map +1 -0
  26. package/dist/{openapi-CHhTPief.cjs → openapi-Bt_1FDpT.cjs} +805 -42
  27. package/dist/openapi-Bt_1FDpT.cjs.map +1 -0
  28. package/dist/{openapi-react-query-o5iMi8tz.cjs → openapi-react-query-B-sNWHFU.cjs} +5 -5
  29. package/dist/openapi-react-query-B-sNWHFU.cjs.map +1 -0
  30. package/dist/{openapi-react-query-CcciaVu5.mjs → openapi-react-query-B6XTeGqS.mjs} +5 -5
  31. package/dist/openapi-react-query-B6XTeGqS.mjs.map +1 -0
  32. package/dist/openapi-react-query.cjs +1 -1
  33. package/dist/openapi-react-query.d.cts.map +1 -0
  34. package/dist/openapi-react-query.d.mts.map +1 -0
  35. package/dist/openapi-react-query.mjs +1 -1
  36. package/dist/openapi.cjs +2 -2
  37. package/dist/openapi.d.cts +1 -1
  38. package/dist/openapi.d.cts.map +1 -0
  39. package/dist/openapi.d.mts +1 -1
  40. package/dist/openapi.d.mts.map +1 -0
  41. package/dist/openapi.mjs +2 -2
  42. package/dist/storage-BUYQJgz7.cjs +4 -0
  43. package/dist/storage-BXoJvmv2.cjs +149 -0
  44. package/dist/storage-BXoJvmv2.cjs.map +1 -0
  45. package/dist/storage-C9PU_30f.mjs +101 -0
  46. package/dist/storage-C9PU_30f.mjs.map +1 -0
  47. package/dist/storage-DLJAYxzJ.mjs +3 -0
  48. package/dist/{types-b-vwGpqc.d.cts → types-BR0M2v_c.d.mts} +100 -1
  49. package/dist/types-BR0M2v_c.d.mts.map +1 -0
  50. package/dist/{types-DXgiA1sF.d.mts → types-BhkZc-vm.d.cts} +100 -1
  51. package/dist/types-BhkZc-vm.d.cts.map +1 -0
  52. package/examples/cron-example.ts +27 -27
  53. package/examples/env.ts +27 -27
  54. package/examples/function-example.ts +31 -31
  55. package/examples/gkm.config.json +20 -20
  56. package/examples/gkm.config.ts +8 -8
  57. package/examples/gkm.minimal.config.json +5 -5
  58. package/examples/gkm.production.config.json +25 -25
  59. package/examples/logger.ts +2 -2
  60. package/package.json +6 -6
  61. package/src/__tests__/EndpointGenerator.hooks.spec.ts +191 -191
  62. package/src/__tests__/config.spec.ts +55 -55
  63. package/src/__tests__/loadEnvFiles.spec.ts +93 -93
  64. package/src/__tests__/normalizeHooksConfig.spec.ts +58 -58
  65. package/src/__tests__/openapi-react-query.spec.ts +497 -497
  66. package/src/__tests__/openapi.spec.ts +428 -428
  67. package/src/__tests__/test-helpers.ts +77 -76
  68. package/src/auth/__tests__/credentials.spec.ts +204 -0
  69. package/src/auth/__tests__/index.spec.ts +168 -0
  70. package/src/auth/credentials.ts +187 -0
  71. package/src/auth/index.ts +226 -0
  72. package/src/build/__tests__/index-new.spec.ts +474 -474
  73. package/src/build/__tests__/manifests.spec.ts +333 -333
  74. package/src/build/bundler.ts +141 -0
  75. package/src/build/endpoint-analyzer.ts +236 -0
  76. package/src/build/handler-templates.ts +1253 -0
  77. package/src/build/index.ts +250 -179
  78. package/src/build/manifests.ts +52 -52
  79. package/src/build/providerResolver.ts +145 -145
  80. package/src/build/types.ts +64 -43
  81. package/src/config.ts +39 -37
  82. package/src/deploy/__tests__/docker.spec.ts +111 -0
  83. package/src/deploy/__tests__/dokploy.spec.ts +245 -0
  84. package/src/deploy/__tests__/init.spec.ts +662 -0
  85. package/src/deploy/docker.ts +128 -0
  86. package/src/deploy/dokploy.ts +204 -0
  87. package/src/deploy/index.ts +136 -0
  88. package/src/deploy/init.ts +484 -0
  89. package/src/deploy/types.ts +48 -0
  90. package/src/dev/__tests__/index.spec.ts +266 -266
  91. package/src/dev/index.ts +647 -593
  92. package/src/docker/__tests__/compose.spec.ts +531 -0
  93. package/src/docker/__tests__/templates.spec.ts +280 -0
  94. package/src/docker/compose.ts +273 -0
  95. package/src/docker/index.ts +230 -0
  96. package/src/docker/templates.ts +446 -0
  97. package/src/generators/CronGenerator.ts +72 -72
  98. package/src/generators/EndpointGenerator.ts +699 -398
  99. package/src/generators/FunctionGenerator.ts +84 -84
  100. package/src/generators/Generator.ts +72 -72
  101. package/src/generators/OpenApiTsGenerator.ts +589 -589
  102. package/src/generators/SubscriberGenerator.ts +124 -124
  103. package/src/generators/__tests__/CronGenerator.spec.ts +433 -433
  104. package/src/generators/__tests__/EndpointGenerator.spec.ts +532 -382
  105. package/src/generators/__tests__/FunctionGenerator.spec.ts +244 -244
  106. package/src/generators/__tests__/SubscriberGenerator.spec.ts +397 -382
  107. package/src/generators/index.ts +4 -4
  108. package/src/index.ts +628 -206
  109. package/src/init/__tests__/generators.spec.ts +334 -334
  110. package/src/init/__tests__/init.spec.ts +332 -332
  111. package/src/init/__tests__/utils.spec.ts +89 -89
  112. package/src/init/generators/config.ts +175 -175
  113. package/src/init/generators/docker.ts +41 -41
  114. package/src/init/generators/env.ts +72 -72
  115. package/src/init/generators/index.ts +1 -1
  116. package/src/init/generators/models.ts +64 -64
  117. package/src/init/generators/monorepo.ts +161 -161
  118. package/src/init/generators/package.ts +71 -71
  119. package/src/init/generators/source.ts +6 -6
  120. package/src/init/index.ts +203 -208
  121. package/src/init/templates/api.ts +115 -115
  122. package/src/init/templates/index.ts +75 -75
  123. package/src/init/templates/minimal.ts +98 -98
  124. package/src/init/templates/serverless.ts +89 -89
  125. package/src/init/templates/worker.ts +98 -98
  126. package/src/init/utils.ts +54 -56
  127. package/src/openapi-react-query.ts +194 -194
  128. package/src/openapi.ts +63 -63
  129. package/src/secrets/__tests__/encryption.spec.ts +226 -0
  130. package/src/secrets/__tests__/generator.spec.ts +319 -0
  131. package/src/secrets/__tests__/index.spec.ts +91 -0
  132. package/src/secrets/__tests__/storage.spec.ts +403 -0
  133. package/src/secrets/encryption.ts +91 -0
  134. package/src/secrets/generator.ts +164 -0
  135. package/src/secrets/index.ts +383 -0
  136. package/src/secrets/storage.ts +134 -0
  137. package/src/secrets/types.ts +53 -0
  138. package/src/types.ts +295 -176
  139. package/tsconfig.json +9 -0
  140. package/tsdown.config.ts +11 -8
  141. package/dist/config-Bq72aj8e.mjs.map +0 -1
  142. package/dist/config-CFls09Ey.cjs.map +0 -1
  143. package/dist/openapi--vOy9mo4.mjs.map +0 -1
  144. package/dist/openapi-CHhTPief.cjs.map +0 -1
  145. package/dist/openapi-react-query-CcciaVu5.mjs.map +0 -1
  146. package/dist/openapi-react-query-o5iMi8tz.cjs.map +0 -1
@@ -1,68 +1,68 @@
1
1
  import type { Endpoint } from '@geekmidas/constructs/endpoints';
2
2
  import {
3
- StandardSchemaJsonSchema,
4
- getSchemaMetadata,
3
+ getSchemaMetadata,
4
+ StandardSchemaJsonSchema,
5
5
  } from '@geekmidas/schema/conversion';
6
6
  import type { StandardSchemaV1 } from '@standard-schema/spec';
7
7
 
8
8
  interface OpenApiTsOptions {
9
- title?: string;
10
- version?: string;
11
- description?: string;
9
+ title?: string;
10
+ version?: string;
11
+ description?: string;
12
12
  }
13
13
 
14
14
  // JSON Schema type definition
15
15
  interface JsonSchema {
16
- type?: string;
17
- properties?: Record<string, JsonSchema>;
18
- items?: JsonSchema;
19
- required?: string[];
20
- enum?: string[];
21
- $ref?: string;
22
- anyOf?: JsonSchema[];
23
- oneOf?: JsonSchema[];
24
- allOf?: JsonSchema[];
25
- additionalProperties?: boolean | JsonSchema;
26
- [key: string]: unknown;
16
+ type?: string;
17
+ properties?: Record<string, JsonSchema>;
18
+ items?: JsonSchema;
19
+ required?: string[];
20
+ enum?: string[];
21
+ $ref?: string;
22
+ anyOf?: JsonSchema[];
23
+ oneOf?: JsonSchema[];
24
+ allOf?: JsonSchema[];
25
+ additionalProperties?: boolean | JsonSchema;
26
+ [key: string]: unknown;
27
27
  }
28
28
 
29
29
  // Security scheme type (OpenAPI 3.1)
30
30
  interface SecuritySchemeObject {
31
- type: 'apiKey' | 'http' | 'mutualTLS' | 'oauth2' | 'openIdConnect';
32
- description?: string;
33
- name?: string;
34
- in?: 'query' | 'header' | 'cookie';
35
- scheme?: string;
36
- bearerFormat?: string;
37
- flows?: Record<string, unknown>;
38
- openIdConnectUrl?: string;
39
- [key: string]: unknown;
31
+ type: 'apiKey' | 'http' | 'mutualTLS' | 'oauth2' | 'openIdConnect';
32
+ description?: string;
33
+ name?: string;
34
+ in?: 'query' | 'header' | 'cookie';
35
+ scheme?: string;
36
+ bearerFormat?: string;
37
+ flows?: Record<string, unknown>;
38
+ openIdConnectUrl?: string;
39
+ [key: string]: unknown;
40
40
  }
41
41
 
42
42
  interface EndpointInfo {
43
- endpoint: string;
44
- route: string;
45
- method: string;
46
- authorizerName: string | null;
47
- /** @deprecated Use securityScheme instead */
48
- authorizerType: string | null;
49
- /** The OpenAPI security scheme definition for this endpoint's authorizer */
50
- securityScheme: SecuritySchemeObject | null;
51
- input?: {
52
- body?: StandardSchemaV1;
53
- query?: StandardSchemaV1;
54
- params?: StandardSchemaV1;
55
- };
56
- output?: StandardSchemaV1;
57
- description?: string;
58
- tags?: string[];
59
- operationId?: string;
43
+ endpoint: string;
44
+ route: string;
45
+ method: string;
46
+ authorizerName: string | null;
47
+ /** @deprecated Use securityScheme instead */
48
+ authorizerType: string | null;
49
+ /** The OpenAPI security scheme definition for this endpoint's authorizer */
50
+ securityScheme: SecuritySchemeObject | null;
51
+ input?: {
52
+ body?: StandardSchemaV1;
53
+ query?: StandardSchemaV1;
54
+ params?: StandardSchemaV1;
55
+ };
56
+ output?: StandardSchemaV1;
57
+ description?: string;
58
+ tags?: string[];
59
+ operationId?: string;
60
60
  }
61
61
 
62
62
  interface SecuritySchemeInfo {
63
- name: string;
64
- type: string;
65
- scheme: SecuritySchemeObject;
63
+ name: string;
64
+ type: string;
65
+ scheme: SecuritySchemeObject;
66
66
  }
67
67
 
68
68
  /**
@@ -74,549 +74,549 @@ interface SecuritySchemeInfo {
74
74
  * - schema interfaces: reusable TypeScript types from Zod/Valibot schemas
75
75
  */
76
76
  export class OpenApiTsGenerator {
77
- async generate(
78
- endpoints: Endpoint<any, any, any, any, any, any>[],
79
- options: OpenApiTsOptions = {},
80
- ): Promise<string> {
81
- const { title = 'API', version = '1.0.0', description } = options;
82
-
83
- // Extract endpoint info
84
- const endpointInfos = await this.extractEndpointInfos(endpoints);
85
-
86
- // Collect unique security schemes
87
- const securitySchemes = this.collectSecuritySchemes(endpointInfos);
88
-
89
- // Build endpoint auth map
90
- const endpointAuth = this.buildEndpointAuthMap(endpointInfos);
91
-
92
- // Generate schema interfaces
93
- const schemaInterfaces = await this.generateSchemaInterfaces(endpointInfos);
94
-
95
- // Generate paths interface
96
- const pathsInterface = await this.generatePathsInterface(endpointInfos);
97
-
98
- // Build the final TypeScript module
99
- return this.buildModule({
100
- title,
101
- version,
102
- description,
103
- securitySchemes,
104
- endpointAuth,
105
- schemaInterfaces,
106
- pathsInterface,
107
- });
108
- }
109
-
110
- private async extractEndpointInfos(
111
- endpoints: Endpoint<any, any, any, any, any, any>[],
112
- ): Promise<EndpointInfo[]> {
113
- return endpoints.map((ep) => {
114
- const route = ep.route.replace(/:(\w+)/g, '{$1}');
115
- const method = ep.method.toUpperCase();
116
-
117
- // Get security scheme from authorizer (if available)
118
- // This is the preferred way - the scheme is stored directly on the authorizer
119
- const securityScheme = ep.authorizer?.securityScheme as
120
- | SecuritySchemeObject
121
- | undefined;
122
-
123
- return {
124
- endpoint: `${method} ${route}`,
125
- route,
126
- method,
127
- authorizerName: ep.authorizer?.name ?? null,
128
- authorizerType: ep.authorizer?.type ?? null,
129
- securityScheme: securityScheme ?? null,
130
- input: ep.input,
131
- output: ep.outputSchema,
132
- description: ep.description,
133
- tags: ep.tags,
134
- operationId: ep.operationId,
135
- };
136
- });
137
- }
138
-
139
- private collectSecuritySchemes(
140
- endpointInfos: EndpointInfo[],
141
- ): SecuritySchemeInfo[] {
142
- const schemes = new Map<string, SecuritySchemeInfo>();
143
-
144
- for (const info of endpointInfos) {
145
- if (info.authorizerName && !schemes.has(info.authorizerName)) {
146
- // Prefer the stored security scheme (from .securitySchemes() or built-ins)
147
- // Fall back to inference from authorizerType for backward compatibility
148
- const scheme =
149
- info.securityScheme ??
150
- (info.authorizerType
151
- ? this.mapAuthorizerToSecurityScheme(
152
- info.authorizerType,
153
- info.authorizerName,
154
- )
155
- : null);
156
-
157
- if (scheme) {
158
- schemes.set(info.authorizerName, {
159
- name: info.authorizerName,
160
- type: scheme.type,
161
- scheme,
162
- });
163
- }
164
- }
165
- }
166
-
167
- return Array.from(schemes.values());
168
- }
169
-
170
- private mapAuthorizerToSecurityScheme(
171
- type: string,
172
- _name: string,
173
- ): SecuritySchemeObject {
174
- switch (type.toLowerCase()) {
175
- case 'jwt':
176
- case 'bearer':
177
- return {
178
- type: 'http',
179
- scheme: 'bearer',
180
- bearerFormat: 'JWT',
181
- };
182
- case 'iam':
183
- case 'aws-sigv4':
184
- case 'sigv4':
185
- return {
186
- type: 'apiKey',
187
- in: 'header',
188
- name: 'Authorization',
189
- 'x-amazon-apigateway-authtype': 'awsSigv4',
190
- };
191
- case 'apikey':
192
- case 'api-key':
193
- return {
194
- type: 'apiKey',
195
- in: 'header',
196
- name: 'X-API-Key',
197
- };
198
- case 'oauth2':
199
- return {
200
- type: 'oauth2',
201
- flows: {},
202
- };
203
- case 'oidc':
204
- case 'openidconnect':
205
- return {
206
- type: 'openIdConnect',
207
- openIdConnectUrl: '',
208
- };
209
- default:
210
- return {
211
- type: 'http',
212
- scheme: 'bearer',
213
- };
214
- }
215
- }
216
-
217
- private buildEndpointAuthMap(
218
- endpointInfos: EndpointInfo[],
219
- ): Record<string, string | null> {
220
- const authMap: Record<string, string | null> = {};
221
-
222
- for (const info of endpointInfos) {
223
- authMap[info.endpoint] = info.authorizerName;
224
- }
225
-
226
- return authMap;
227
- }
228
-
229
- private async generateSchemaInterfaces(
230
- endpointInfos: EndpointInfo[],
231
- ): Promise<string> {
232
- const interfaces: string[] = [];
233
- const generatedNames = new Set<string>();
234
- // Collect nested schemas with $defs (from .meta({ id: 'X' }))
235
- const collectedDefs = new Map<string, JsonSchema>();
236
-
237
- for (const info of endpointInfos) {
238
- const baseName = this.getSchemaBaseName(info);
239
-
240
- // Input body schema
241
- if (info.input?.body) {
242
- const name = await this.getSchemaName(
243
- info.input.body,
244
- `${baseName}Input`,
245
- );
246
- if (!generatedNames.has(name)) {
247
- const schema = await this.schemaToInterfaceWithDefs(
248
- info.input.body,
249
- name,
250
- collectedDefs,
251
- );
252
- if (schema) {
253
- interfaces.push(schema);
254
- generatedNames.add(name);
255
- }
256
- }
257
- }
258
-
259
- // Input params schema
260
- if (info.input?.params) {
261
- const name = await this.getSchemaName(
262
- info.input.params,
263
- `${baseName}Params`,
264
- );
265
- if (!generatedNames.has(name)) {
266
- const schema = await this.schemaToInterfaceWithDefs(
267
- info.input.params,
268
- name,
269
- collectedDefs,
270
- );
271
- if (schema) {
272
- interfaces.push(schema);
273
- generatedNames.add(name);
274
- }
275
- }
276
- }
277
-
278
- // Input query schema
279
- if (info.input?.query) {
280
- const name = await this.getSchemaName(
281
- info.input.query,
282
- `${baseName}Query`,
283
- );
284
- if (!generatedNames.has(name)) {
285
- const schema = await this.schemaToInterfaceWithDefs(
286
- info.input.query,
287
- name,
288
- collectedDefs,
289
- );
290
- if (schema) {
291
- interfaces.push(schema);
292
- generatedNames.add(name);
293
- }
294
- }
295
- }
296
-
297
- // Output schema
298
- if (info.output) {
299
- const name = await this.getSchemaName(info.output, `${baseName}Output`);
300
- if (!generatedNames.has(name)) {
301
- const schema = await this.schemaToInterfaceWithDefs(
302
- info.output,
303
- name,
304
- collectedDefs,
305
- );
306
- if (schema) {
307
- interfaces.push(schema);
308
- generatedNames.add(name);
309
- }
310
- }
311
- }
312
- }
313
-
314
- // Generate interfaces for collected $defs (nested schemas with .meta({ id: 'X' }))
315
- for (const [defName, defSchema] of collectedDefs) {
316
- if (!generatedNames.has(defName)) {
317
- const interfaceStr = this.jsonSchemaToInterface(defSchema, defName);
318
- interfaces.push(interfaceStr);
319
- generatedNames.add(defName);
320
- }
321
- }
322
-
323
- return interfaces.join('\n\n');
324
- }
325
-
326
- /**
327
- * Get the name for a schema, using metadata `id` if available,
328
- * otherwise falling back to the provided default name.
329
- */
330
- private async getSchemaName(
331
- schema: StandardSchemaV1,
332
- defaultName: string,
333
- ): Promise<string> {
334
- try {
335
- const metadata = await getSchemaMetadata(schema);
336
- if (metadata?.id) {
337
- return this.pascalCase(metadata.id);
338
- }
339
- } catch {
340
- // Ignore metadata extraction errors
341
- }
342
- return defaultName;
343
- }
344
-
345
- private getSchemaBaseName(info: EndpointInfo): string {
346
- if (info.operationId) {
347
- return this.pascalCase(info.operationId);
348
- }
349
-
350
- // Generate name from method + route
351
- const routeParts = info.route
352
- .replace(/[{}]/g, '')
353
- .split('/')
354
- .filter(Boolean)
355
- .map((part) => this.pascalCase(part));
356
-
357
- return `${this.pascalCase(info.method.toLowerCase())}${routeParts.join('')}`;
358
- }
359
-
360
- private pascalCase(str: string): string {
361
- return str
362
- .replace(/[-_](.)/g, (_, c) => c.toUpperCase())
363
- .replace(/^./, (c) => c.toUpperCase());
364
- }
365
-
366
- /**
367
- * Convert schema to interface while collecting $defs for nested schemas
368
- * with .meta({ id: 'X' }).
369
- */
370
- private async schemaToInterfaceWithDefs(
371
- schema: StandardSchemaV1,
372
- name: string,
373
- collectedDefs: Map<string, JsonSchema>,
374
- ): Promise<string | null> {
375
- try {
376
- // Get raw JSON schema with $defs intact (don't use convertStandardSchemaToJsonSchema
377
- // which strips $defs)
378
- const vendor = schema['~standard']?.vendor;
379
- if (!vendor || !(vendor in StandardSchemaJsonSchema)) {
380
- return null;
381
- }
382
-
383
- const toJsonSchema =
384
- StandardSchemaJsonSchema[
385
- vendor as keyof typeof StandardSchemaJsonSchema
386
- ];
387
- const jsonSchema = await toJsonSchema(schema);
388
- if (!jsonSchema) return null;
389
-
390
- // Extract $defs from the JSON schema (these come from .meta({ id: 'X' }))
391
- if (jsonSchema.$defs && typeof jsonSchema.$defs === 'object') {
392
- for (const [defName, defSchema] of Object.entries(jsonSchema.$defs)) {
393
- if (!collectedDefs.has(defName)) {
394
- // Remove the 'id' field from the schema as it's just metadata
395
- const { id, ...schemaWithoutId } = defSchema as JsonSchema & {
396
- id?: string;
397
- };
398
- collectedDefs.set(defName, schemaWithoutId as JsonSchema);
399
- }
400
- }
401
- }
402
-
403
- // Remove $defs from the schema before converting to interface
404
- const { $defs, ...schemaWithoutDefs } = jsonSchema;
405
- return this.jsonSchemaToInterface(schemaWithoutDefs, name);
406
- } catch {
407
- return null;
408
- }
409
- }
410
-
411
- private jsonSchemaToInterface(schema: JsonSchema, name: string): string {
412
- if (schema.type !== 'object' || !schema.properties) {
413
- // For non-object types, create a type alias
414
- const typeStr = this.jsonSchemaTypeToTs(schema);
415
- return `export type ${name} = ${typeStr};`;
416
- }
417
-
418
- const props: string[] = [];
419
- const required = new Set(schema.required || []);
420
-
421
- for (const [propName, propSchema] of Object.entries(schema.properties)) {
422
- const isRequired = required.has(propName);
423
- const typeStr = this.jsonSchemaTypeToTs(propSchema as JsonSchema);
424
- const optionalMark = isRequired ? '' : '?';
425
- props.push(` ${propName}${optionalMark}: ${typeStr};`);
426
- }
427
-
428
- return `export interface ${name} {\n${props.join('\n')}\n}`;
429
- }
430
-
431
- private jsonSchemaTypeToTs(schema: JsonSchema): string {
432
- if (!schema) return 'unknown';
433
-
434
- if (schema.$ref) {
435
- // Extract name from $ref
436
- const refName = schema.$ref.split('/').pop() || 'unknown';
437
- return refName;
438
- }
439
-
440
- if (schema.anyOf) {
441
- return schema.anyOf
442
- .map((s: JsonSchema) => this.jsonSchemaTypeToTs(s))
443
- .join(' | ');
444
- }
445
-
446
- if (schema.oneOf) {
447
- return schema.oneOf
448
- .map((s: JsonSchema) => this.jsonSchemaTypeToTs(s))
449
- .join(' | ');
450
- }
451
-
452
- if (schema.allOf) {
453
- return schema.allOf
454
- .map((s: JsonSchema) => this.jsonSchemaTypeToTs(s))
455
- .join(' & ');
456
- }
457
-
458
- switch (schema.type) {
459
- case 'string':
460
- if (schema.enum) {
461
- return schema.enum.map((e: string) => `'${e}'`).join(' | ');
462
- }
463
- return 'string';
464
- case 'number':
465
- case 'integer':
466
- return 'number';
467
- case 'boolean':
468
- return 'boolean';
469
- case 'null':
470
- return 'null';
471
- case 'array':
472
- if (schema.items) {
473
- return `Array<${this.jsonSchemaTypeToTs(schema.items as JsonSchema)}>`;
474
- }
475
- return 'Array<unknown>';
476
- case 'object':
477
- if (schema.properties) {
478
- const props: string[] = [];
479
- const required = new Set(schema.required || []);
480
- for (const [propName, propSchema] of Object.entries(
481
- schema.properties,
482
- )) {
483
- const isRequired = required.has(propName);
484
- const typeStr = this.jsonSchemaTypeToTs(propSchema as JsonSchema);
485
- const optionalMark = isRequired ? '' : '?';
486
- props.push(`${propName}${optionalMark}: ${typeStr}`);
487
- }
488
- return `{ ${props.join('; ')} }`;
489
- }
490
- if (schema.additionalProperties) {
491
- const valueType = this.jsonSchemaTypeToTs(
492
- schema.additionalProperties as JsonSchema,
493
- );
494
- return `Record<string, ${valueType}>`;
495
- }
496
- return 'Record<string, unknown>';
497
- default:
498
- return 'unknown';
499
- }
500
- }
501
-
502
- private async generatePathsInterface(
503
- endpointInfos: EndpointInfo[],
504
- ): Promise<string> {
505
- const pathGroups = new Map<string, EndpointInfo[]>();
506
-
507
- // Group endpoints by route
508
- for (const info of endpointInfos) {
509
- const existing = pathGroups.get(info.route) || [];
510
- existing.push(info);
511
- pathGroups.set(info.route, existing);
512
- }
513
-
514
- const pathEntries: string[] = [];
515
-
516
- for (const [route, infos] of pathGroups) {
517
- const methodEntries: string[] = [];
518
-
519
- for (const info of infos) {
520
- const methodDef = await this.generateMethodDefinition(info);
521
- methodEntries.push(` ${info.method.toLowerCase()}: ${methodDef};`);
522
- }
523
-
524
- // Add path parameters if present
525
- const firstWithParams = infos.find((i) => i.input?.params);
526
- let paramsEntry = '';
527
- if (firstWithParams?.input?.params) {
528
- const paramsName = await this.getSchemaName(
529
- firstWithParams.input.params,
530
- `${this.getSchemaBaseName(firstWithParams)}Params`,
531
- );
532
- paramsEntry = `\n parameters: {\n path: ${paramsName};\n };`;
533
- }
534
-
535
- pathEntries.push(
536
- ` '${route}': {${paramsEntry}\n${methodEntries.join('\n')}\n };`,
537
- );
538
- }
539
-
540
- return `export interface paths {\n${pathEntries.join('\n')}\n}`;
541
- }
542
-
543
- private async generateMethodDefinition(info: EndpointInfo): Promise<string> {
544
- const parts: string[] = [];
545
- const baseName = this.getSchemaBaseName(info);
546
-
547
- // Request body
548
- if (info.input?.body) {
549
- const bodyName = await this.getSchemaName(
550
- info.input.body,
551
- `${baseName}Input`,
552
- );
553
- parts.push(`requestBody: {
554
- content: {
555
- 'application/json': ${bodyName};
556
- };
557
- }`);
558
- }
559
-
560
- // Query parameters
561
- if (info.input?.query) {
562
- const queryName = await this.getSchemaName(
563
- info.input.query,
564
- `${baseName}Query`,
565
- );
566
- parts.push(`parameters: {
567
- query: ${queryName};
568
- }`);
569
- }
570
-
571
- // Responses
572
- const outputName = info.output
573
- ? await this.getSchemaName(info.output, `${baseName}Output`)
574
- : 'unknown';
575
- parts.push(`responses: {
576
- 200: {
77
+ async generate(
78
+ endpoints: Endpoint<any, any, any, any, any, any>[],
79
+ options: OpenApiTsOptions = {},
80
+ ): Promise<string> {
81
+ const { title = 'API', version = '1.0.0', description } = options;
82
+
83
+ // Extract endpoint info
84
+ const endpointInfos = await this.extractEndpointInfos(endpoints);
85
+
86
+ // Collect unique security schemes
87
+ const securitySchemes = this.collectSecuritySchemes(endpointInfos);
88
+
89
+ // Build endpoint auth map
90
+ const endpointAuth = this.buildEndpointAuthMap(endpointInfos);
91
+
92
+ // Generate schema interfaces
93
+ const schemaInterfaces = await this.generateSchemaInterfaces(endpointInfos);
94
+
95
+ // Generate paths interface
96
+ const pathsInterface = await this.generatePathsInterface(endpointInfos);
97
+
98
+ // Build the final TypeScript module
99
+ return this.buildModule({
100
+ title,
101
+ version,
102
+ description,
103
+ securitySchemes,
104
+ endpointAuth,
105
+ schemaInterfaces,
106
+ pathsInterface,
107
+ });
108
+ }
109
+
110
+ private async extractEndpointInfos(
111
+ endpoints: Endpoint<any, any, any, any, any, any>[],
112
+ ): Promise<EndpointInfo[]> {
113
+ return endpoints.map((ep) => {
114
+ const route = ep.route.replace(/:(\w+)/g, '{$1}');
115
+ const method = ep.method.toUpperCase();
116
+
117
+ // Get security scheme from authorizer (if available)
118
+ // This is the preferred way - the scheme is stored directly on the authorizer
119
+ const securityScheme = ep.authorizer?.securityScheme as
120
+ | SecuritySchemeObject
121
+ | undefined;
122
+
123
+ return {
124
+ endpoint: `${method} ${route}`,
125
+ route,
126
+ method,
127
+ authorizerName: ep.authorizer?.name ?? null,
128
+ authorizerType: ep.authorizer?.type ?? null,
129
+ securityScheme: securityScheme ?? null,
130
+ input: ep.input,
131
+ output: ep.outputSchema,
132
+ description: ep.description,
133
+ tags: ep.tags,
134
+ operationId: ep.operationId,
135
+ };
136
+ });
137
+ }
138
+
139
+ private collectSecuritySchemes(
140
+ endpointInfos: EndpointInfo[],
141
+ ): SecuritySchemeInfo[] {
142
+ const schemes = new Map<string, SecuritySchemeInfo>();
143
+
144
+ for (const info of endpointInfos) {
145
+ if (info.authorizerName && !schemes.has(info.authorizerName)) {
146
+ // Prefer the stored security scheme (from .securitySchemes() or built-ins)
147
+ // Fall back to inference from authorizerType for backward compatibility
148
+ const scheme =
149
+ info.securityScheme ??
150
+ (info.authorizerType
151
+ ? this.mapAuthorizerToSecurityScheme(
152
+ info.authorizerType,
153
+ info.authorizerName,
154
+ )
155
+ : null);
156
+
157
+ if (scheme) {
158
+ schemes.set(info.authorizerName, {
159
+ name: info.authorizerName,
160
+ type: scheme.type,
161
+ scheme,
162
+ });
163
+ }
164
+ }
165
+ }
166
+
167
+ return Array.from(schemes.values());
168
+ }
169
+
170
+ private mapAuthorizerToSecurityScheme(
171
+ type: string,
172
+ _name: string,
173
+ ): SecuritySchemeObject {
174
+ switch (type.toLowerCase()) {
175
+ case 'jwt':
176
+ case 'bearer':
177
+ return {
178
+ type: 'http',
179
+ scheme: 'bearer',
180
+ bearerFormat: 'JWT',
181
+ };
182
+ case 'iam':
183
+ case 'aws-sigv4':
184
+ case 'sigv4':
185
+ return {
186
+ type: 'apiKey',
187
+ in: 'header',
188
+ name: 'Authorization',
189
+ 'x-amazon-apigateway-authtype': 'awsSigv4',
190
+ };
191
+ case 'apikey':
192
+ case 'api-key':
193
+ return {
194
+ type: 'apiKey',
195
+ in: 'header',
196
+ name: 'X-API-Key',
197
+ };
198
+ case 'oauth2':
199
+ return {
200
+ type: 'oauth2',
201
+ flows: {},
202
+ };
203
+ case 'oidc':
204
+ case 'openidconnect':
205
+ return {
206
+ type: 'openIdConnect',
207
+ openIdConnectUrl: '',
208
+ };
209
+ default:
210
+ return {
211
+ type: 'http',
212
+ scheme: 'bearer',
213
+ };
214
+ }
215
+ }
216
+
217
+ private buildEndpointAuthMap(
218
+ endpointInfos: EndpointInfo[],
219
+ ): Record<string, string | null> {
220
+ const authMap: Record<string, string | null> = {};
221
+
222
+ for (const info of endpointInfos) {
223
+ authMap[info.endpoint] = info.authorizerName;
224
+ }
225
+
226
+ return authMap;
227
+ }
228
+
229
+ private async generateSchemaInterfaces(
230
+ endpointInfos: EndpointInfo[],
231
+ ): Promise<string> {
232
+ const interfaces: string[] = [];
233
+ const generatedNames = new Set<string>();
234
+ // Collect nested schemas with $defs (from .meta({ id: 'X' }))
235
+ const collectedDefs = new Map<string, JsonSchema>();
236
+
237
+ for (const info of endpointInfos) {
238
+ const baseName = this.getSchemaBaseName(info);
239
+
240
+ // Input body schema
241
+ if (info.input?.body) {
242
+ const name = await this.getSchemaName(
243
+ info.input.body,
244
+ `${baseName}Input`,
245
+ );
246
+ if (!generatedNames.has(name)) {
247
+ const schema = await this.schemaToInterfaceWithDefs(
248
+ info.input.body,
249
+ name,
250
+ collectedDefs,
251
+ );
252
+ if (schema) {
253
+ interfaces.push(schema);
254
+ generatedNames.add(name);
255
+ }
256
+ }
257
+ }
258
+
259
+ // Input params schema
260
+ if (info.input?.params) {
261
+ const name = await this.getSchemaName(
262
+ info.input.params,
263
+ `${baseName}Params`,
264
+ );
265
+ if (!generatedNames.has(name)) {
266
+ const schema = await this.schemaToInterfaceWithDefs(
267
+ info.input.params,
268
+ name,
269
+ collectedDefs,
270
+ );
271
+ if (schema) {
272
+ interfaces.push(schema);
273
+ generatedNames.add(name);
274
+ }
275
+ }
276
+ }
277
+
278
+ // Input query schema
279
+ if (info.input?.query) {
280
+ const name = await this.getSchemaName(
281
+ info.input.query,
282
+ `${baseName}Query`,
283
+ );
284
+ if (!generatedNames.has(name)) {
285
+ const schema = await this.schemaToInterfaceWithDefs(
286
+ info.input.query,
287
+ name,
288
+ collectedDefs,
289
+ );
290
+ if (schema) {
291
+ interfaces.push(schema);
292
+ generatedNames.add(name);
293
+ }
294
+ }
295
+ }
296
+
297
+ // Output schema
298
+ if (info.output) {
299
+ const name = await this.getSchemaName(info.output, `${baseName}Output`);
300
+ if (!generatedNames.has(name)) {
301
+ const schema = await this.schemaToInterfaceWithDefs(
302
+ info.output,
303
+ name,
304
+ collectedDefs,
305
+ );
306
+ if (schema) {
307
+ interfaces.push(schema);
308
+ generatedNames.add(name);
309
+ }
310
+ }
311
+ }
312
+ }
313
+
314
+ // Generate interfaces for collected $defs (nested schemas with .meta({ id: 'X' }))
315
+ for (const [defName, defSchema] of collectedDefs) {
316
+ if (!generatedNames.has(defName)) {
317
+ const interfaceStr = this.jsonSchemaToInterface(defSchema, defName);
318
+ interfaces.push(interfaceStr);
319
+ generatedNames.add(defName);
320
+ }
321
+ }
322
+
323
+ return interfaces.join('\n\n');
324
+ }
325
+
326
+ /**
327
+ * Get the name for a schema, using metadata `id` if available,
328
+ * otherwise falling back to the provided default name.
329
+ */
330
+ private async getSchemaName(
331
+ schema: StandardSchemaV1,
332
+ defaultName: string,
333
+ ): Promise<string> {
334
+ try {
335
+ const metadata = await getSchemaMetadata(schema);
336
+ if (metadata?.id) {
337
+ return this.pascalCase(metadata.id);
338
+ }
339
+ } catch {
340
+ // Ignore metadata extraction errors
341
+ }
342
+ return defaultName;
343
+ }
344
+
345
+ private getSchemaBaseName(info: EndpointInfo): string {
346
+ if (info.operationId) {
347
+ return this.pascalCase(info.operationId);
348
+ }
349
+
350
+ // Generate name from method + route
351
+ const routeParts = info.route
352
+ .replace(/[{}]/g, '')
353
+ .split('/')
354
+ .filter(Boolean)
355
+ .map((part) => this.pascalCase(part));
356
+
357
+ return `${this.pascalCase(info.method.toLowerCase())}${routeParts.join('')}`;
358
+ }
359
+
360
+ private pascalCase(str: string): string {
361
+ return str
362
+ .replace(/[-_](.)/g, (_, c) => c.toUpperCase())
363
+ .replace(/^./, (c) => c.toUpperCase());
364
+ }
365
+
366
+ /**
367
+ * Convert schema to interface while collecting $defs for nested schemas
368
+ * with .meta({ id: 'X' }).
369
+ */
370
+ private async schemaToInterfaceWithDefs(
371
+ schema: StandardSchemaV1,
372
+ name: string,
373
+ collectedDefs: Map<string, JsonSchema>,
374
+ ): Promise<string | null> {
375
+ try {
376
+ // Get raw JSON schema with $defs intact (don't use convertStandardSchemaToJsonSchema
377
+ // which strips $defs)
378
+ const vendor = schema['~standard']?.vendor;
379
+ if (!vendor || !(vendor in StandardSchemaJsonSchema)) {
380
+ return null;
381
+ }
382
+
383
+ const toJsonSchema =
384
+ StandardSchemaJsonSchema[
385
+ vendor as keyof typeof StandardSchemaJsonSchema
386
+ ];
387
+ const jsonSchema = await toJsonSchema(schema);
388
+ if (!jsonSchema) return null;
389
+
390
+ // Extract $defs from the JSON schema (these come from .meta({ id: 'X' }))
391
+ if (jsonSchema.$defs && typeof jsonSchema.$defs === 'object') {
392
+ for (const [defName, defSchema] of Object.entries(jsonSchema.$defs)) {
393
+ if (!collectedDefs.has(defName)) {
394
+ // Remove the 'id' field from the schema as it's just metadata
395
+ const { id, ...schemaWithoutId } = defSchema as JsonSchema & {
396
+ id?: string;
397
+ };
398
+ collectedDefs.set(defName, schemaWithoutId as JsonSchema);
399
+ }
400
+ }
401
+ }
402
+
403
+ // Remove $defs from the schema before converting to interface
404
+ const { $defs, ...schemaWithoutDefs } = jsonSchema;
405
+ return this.jsonSchemaToInterface(schemaWithoutDefs, name);
406
+ } catch {
407
+ return null;
408
+ }
409
+ }
410
+
411
+ private jsonSchemaToInterface(schema: JsonSchema, name: string): string {
412
+ if (schema.type !== 'object' || !schema.properties) {
413
+ // For non-object types, create a type alias
414
+ const typeStr = this.jsonSchemaTypeToTs(schema);
415
+ return `export type ${name} = ${typeStr};`;
416
+ }
417
+
418
+ const props: string[] = [];
419
+ const required = new Set(schema.required || []);
420
+
421
+ for (const [propName, propSchema] of Object.entries(schema.properties)) {
422
+ const isRequired = required.has(propName);
423
+ const typeStr = this.jsonSchemaTypeToTs(propSchema as JsonSchema);
424
+ const optionalMark = isRequired ? '' : '?';
425
+ props.push(` ${propName}${optionalMark}: ${typeStr};`);
426
+ }
427
+
428
+ return `export interface ${name} {\n${props.join('\n')}\n}`;
429
+ }
430
+
431
+ private jsonSchemaTypeToTs(schema: JsonSchema): string {
432
+ if (!schema) return 'unknown';
433
+
434
+ if (schema.$ref) {
435
+ // Extract name from $ref
436
+ const refName = schema.$ref.split('/').pop() || 'unknown';
437
+ return refName;
438
+ }
439
+
440
+ if (schema.anyOf) {
441
+ return schema.anyOf
442
+ .map((s: JsonSchema) => this.jsonSchemaTypeToTs(s))
443
+ .join(' | ');
444
+ }
445
+
446
+ if (schema.oneOf) {
447
+ return schema.oneOf
448
+ .map((s: JsonSchema) => this.jsonSchemaTypeToTs(s))
449
+ .join(' | ');
450
+ }
451
+
452
+ if (schema.allOf) {
453
+ return schema.allOf
454
+ .map((s: JsonSchema) => this.jsonSchemaTypeToTs(s))
455
+ .join(' & ');
456
+ }
457
+
458
+ switch (schema.type) {
459
+ case 'string':
460
+ if (schema.enum) {
461
+ return schema.enum.map((e: string) => `'${e}'`).join(' | ');
462
+ }
463
+ return 'string';
464
+ case 'number':
465
+ case 'integer':
466
+ return 'number';
467
+ case 'boolean':
468
+ return 'boolean';
469
+ case 'null':
470
+ return 'null';
471
+ case 'array':
472
+ if (schema.items) {
473
+ return `Array<${this.jsonSchemaTypeToTs(schema.items as JsonSchema)}>`;
474
+ }
475
+ return 'Array<unknown>';
476
+ case 'object':
477
+ if (schema.properties) {
478
+ const props: string[] = [];
479
+ const required = new Set(schema.required || []);
480
+ for (const [propName, propSchema] of Object.entries(
481
+ schema.properties,
482
+ )) {
483
+ const isRequired = required.has(propName);
484
+ const typeStr = this.jsonSchemaTypeToTs(propSchema as JsonSchema);
485
+ const optionalMark = isRequired ? '' : '?';
486
+ props.push(`${propName}${optionalMark}: ${typeStr}`);
487
+ }
488
+ return `{ ${props.join('; ')} }`;
489
+ }
490
+ if (schema.additionalProperties) {
491
+ const valueType = this.jsonSchemaTypeToTs(
492
+ schema.additionalProperties as JsonSchema,
493
+ );
494
+ return `Record<string, ${valueType}>`;
495
+ }
496
+ return 'Record<string, unknown>';
497
+ default:
498
+ return 'unknown';
499
+ }
500
+ }
501
+
502
+ private async generatePathsInterface(
503
+ endpointInfos: EndpointInfo[],
504
+ ): Promise<string> {
505
+ const pathGroups = new Map<string, EndpointInfo[]>();
506
+
507
+ // Group endpoints by route
508
+ for (const info of endpointInfos) {
509
+ const existing = pathGroups.get(info.route) || [];
510
+ existing.push(info);
511
+ pathGroups.set(info.route, existing);
512
+ }
513
+
514
+ const pathEntries: string[] = [];
515
+
516
+ for (const [route, infos] of pathGroups) {
517
+ const methodEntries: string[] = [];
518
+
519
+ for (const info of infos) {
520
+ const methodDef = await this.generateMethodDefinition(info);
521
+ methodEntries.push(` ${info.method.toLowerCase()}: ${methodDef};`);
522
+ }
523
+
524
+ // Add path parameters if present
525
+ const firstWithParams = infos.find((i) => i.input?.params);
526
+ let paramsEntry = '';
527
+ if (firstWithParams?.input?.params) {
528
+ const paramsName = await this.getSchemaName(
529
+ firstWithParams.input.params,
530
+ `${this.getSchemaBaseName(firstWithParams)}Params`,
531
+ );
532
+ paramsEntry = `\n parameters: {\n path: ${paramsName};\n };`;
533
+ }
534
+
535
+ pathEntries.push(
536
+ ` '${route}': {${paramsEntry}\n${methodEntries.join('\n')}\n };`,
537
+ );
538
+ }
539
+
540
+ return `export interface paths {\n${pathEntries.join('\n')}\n}`;
541
+ }
542
+
543
+ private async generateMethodDefinition(info: EndpointInfo): Promise<string> {
544
+ const parts: string[] = [];
545
+ const baseName = this.getSchemaBaseName(info);
546
+
547
+ // Request body
548
+ if (info.input?.body) {
549
+ const bodyName = await this.getSchemaName(
550
+ info.input.body,
551
+ `${baseName}Input`,
552
+ );
553
+ parts.push(`requestBody: {
577
554
  content: {
578
- 'application/json': ${outputName};
555
+ 'application/json': ${bodyName};
556
+ };
557
+ }`);
558
+ }
559
+
560
+ // Query parameters
561
+ if (info.input?.query) {
562
+ const queryName = await this.getSchemaName(
563
+ info.input.query,
564
+ `${baseName}Query`,
565
+ );
566
+ parts.push(`parameters: {
567
+ query: ${queryName};
568
+ }`);
569
+ }
570
+
571
+ // Responses
572
+ const outputName = info.output
573
+ ? await this.getSchemaName(info.output, `${baseName}Output`)
574
+ : 'unknown';
575
+ parts.push(`responses: {
576
+ 200: {
577
+ content: {
578
+ 'application/json': ${outputName};
579
+ };
579
580
  };
580
- };
581
- }`);
582
-
583
- return `{\n ${parts.join(';\n ')};\n }`;
584
- }
585
-
586
- private buildModule(params: {
587
- title: string;
588
- version: string;
589
- description?: string;
590
- securitySchemes: SecuritySchemeInfo[];
591
- endpointAuth: Record<string, string | null>;
592
- schemaInterfaces: string;
593
- pathsInterface: string;
594
- }): string {
595
- const {
596
- title,
597
- version,
598
- description,
599
- securitySchemes,
600
- endpointAuth,
601
- schemaInterfaces,
602
- pathsInterface,
603
- } = params;
604
-
605
- const securitySchemesObj = securitySchemes.reduce(
606
- (acc, s) => {
607
- acc[s.name] = s.scheme;
608
- return acc;
609
- },
610
- {} as Record<string, SecuritySchemeObject>,
611
- );
612
-
613
- const schemeNames = securitySchemes.map((s) => `'${s.name}'`).join(' | ');
614
-
615
- // Generate createApi only if there are security schemes
616
- const hasSecuritySchemes = schemeNames.length > 0;
617
-
618
- const createApiSection = hasSecuritySchemes
619
- ? `
581
+ }`);
582
+
583
+ return `{\n ${parts.join(';\n ')};\n }`;
584
+ }
585
+
586
+ private buildModule(params: {
587
+ title: string;
588
+ version: string;
589
+ description?: string;
590
+ securitySchemes: SecuritySchemeInfo[];
591
+ endpointAuth: Record<string, string | null>;
592
+ schemaInterfaces: string;
593
+ pathsInterface: string;
594
+ }): string {
595
+ const {
596
+ title,
597
+ version,
598
+ description,
599
+ securitySchemes,
600
+ endpointAuth,
601
+ schemaInterfaces,
602
+ pathsInterface,
603
+ } = params;
604
+
605
+ const securitySchemesObj = securitySchemes.reduce(
606
+ (acc, s) => {
607
+ acc[s.name] = s.scheme;
608
+ return acc;
609
+ },
610
+ {} as Record<string, SecuritySchemeObject>,
611
+ );
612
+
613
+ const schemeNames = securitySchemes.map((s) => `'${s.name}'`).join(' | ');
614
+
615
+ // Generate createApi only if there are security schemes
616
+ const hasSecuritySchemes = schemeNames.length > 0;
617
+
618
+ const createApiSection = hasSecuritySchemes
619
+ ? `
620
620
  // ============================================================
621
621
  // API Client Factory
622
622
  // ============================================================
@@ -676,7 +676,7 @@ export function createApi(options: CreateApiOptions) {
676
676
  return Object.assign(fetcher, hooks);
677
677
  }
678
678
  `
679
- : `
679
+ : `
680
680
  // ============================================================
681
681
  // API Client Factory
682
682
  // ============================================================
@@ -721,7 +721,7 @@ export function createApi(options: CreateApiOptions) {
721
721
  }
722
722
  `;
723
723
 
724
- return `// Auto-generated by @geekmidas/cli - DO NOT EDIT
724
+ return `// Auto-generated by @geekmidas/cli - DO NOT EDIT
725
725
  // Generated: ${new Date().toISOString()}
726
726
 
727
727
  // ============================================================
@@ -794,5 +794,5 @@ ${schemaInterfaces}
794
794
  ${pathsInterface}
795
795
  ${createApiSection}
796
796
  `;
797
- }
797
+ }
798
798
  }