@geekmidas/cli 0.10.0 → 0.13.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-B1qy9b-j.cjs +112 -0
  3. package/dist/bundler-B1qy9b-j.cjs.map +1 -0
  4. package/dist/bundler-DskIqW2t.mjs +111 -0
  5. package/dist/bundler-DskIqW2t.mjs.map +1 -0
  6. package/dist/{config-C9aXOHBe.cjs → config-AmInkU7k.cjs} +8 -8
  7. package/dist/config-AmInkU7k.cjs.map +1 -0
  8. package/dist/{config-BrkUalUh.mjs → config-DYULeEv8.mjs} +3 -3
  9. package/dist/config-DYULeEv8.mjs.map +1 -0
  10. package/dist/config.cjs +1 -1
  11. package/dist/config.d.cts +1 -1
  12. package/dist/config.d.mts +1 -1
  13. package/dist/config.mjs +1 -1
  14. package/dist/encryption-C8H-38Yy.mjs +42 -0
  15. package/dist/encryption-C8H-38Yy.mjs.map +1 -0
  16. package/dist/encryption-Dyf_r1h-.cjs +44 -0
  17. package/dist/encryption-Dyf_r1h-.cjs.map +1 -0
  18. package/dist/index.cjs +2123 -179
  19. package/dist/index.cjs.map +1 -1
  20. package/dist/index.mjs +2141 -192
  21. package/dist/index.mjs.map +1 -1
  22. package/dist/{openapi-CZLI4QTr.mjs → openapi-BfFlOBCG.mjs} +801 -38
  23. package/dist/openapi-BfFlOBCG.mjs.map +1 -0
  24. package/dist/{openapi-BeHLKcwP.cjs → openapi-Bt_1FDpT.cjs} +794 -31
  25. package/dist/openapi-Bt_1FDpT.cjs.map +1 -0
  26. package/dist/{openapi-react-query-o5iMi8tz.cjs → openapi-react-query-B-sNWHFU.cjs} +5 -5
  27. package/dist/openapi-react-query-B-sNWHFU.cjs.map +1 -0
  28. package/dist/{openapi-react-query-CcciaVu5.mjs → openapi-react-query-B6XTeGqS.mjs} +5 -5
  29. package/dist/openapi-react-query-B6XTeGqS.mjs.map +1 -0
  30. package/dist/openapi-react-query.cjs +1 -1
  31. package/dist/openapi-react-query.d.cts.map +1 -1
  32. package/dist/openapi-react-query.d.mts.map +1 -1
  33. package/dist/openapi-react-query.mjs +1 -1
  34. package/dist/openapi.cjs +2 -2
  35. package/dist/openapi.d.cts +1 -1
  36. package/dist/openapi.d.cts.map +1 -1
  37. package/dist/openapi.d.mts +1 -1
  38. package/dist/openapi.d.mts.map +1 -1
  39. package/dist/openapi.mjs +2 -2
  40. package/dist/storage-BOOpAF8N.cjs +5 -0
  41. package/dist/storage-Bj1E26lU.cjs +187 -0
  42. package/dist/storage-Bj1E26lU.cjs.map +1 -0
  43. package/dist/storage-kSxTjkNb.mjs +133 -0
  44. package/dist/storage-kSxTjkNb.mjs.map +1 -0
  45. package/dist/storage-tgZSUnKl.mjs +3 -0
  46. package/dist/{types-b-vwGpqc.d.cts → types-BR0M2v_c.d.mts} +100 -1
  47. package/dist/types-BR0M2v_c.d.mts.map +1 -0
  48. package/dist/{types-DXgiA1sF.d.mts → types-BhkZc-vm.d.cts} +100 -1
  49. package/dist/types-BhkZc-vm.d.cts.map +1 -0
  50. package/examples/cron-example.ts +27 -27
  51. package/examples/env.ts +27 -27
  52. package/examples/function-example.ts +31 -31
  53. package/examples/gkm.config.json +20 -20
  54. package/examples/gkm.config.ts +8 -8
  55. package/examples/gkm.minimal.config.json +5 -5
  56. package/examples/gkm.production.config.json +25 -25
  57. package/examples/logger.ts +2 -2
  58. package/package.json +6 -6
  59. package/src/__tests__/EndpointGenerator.hooks.spec.ts +191 -191
  60. package/src/__tests__/config.spec.ts +55 -55
  61. package/src/__tests__/loadEnvFiles.spec.ts +93 -93
  62. package/src/__tests__/normalizeHooksConfig.spec.ts +58 -58
  63. package/src/__tests__/openapi-react-query.spec.ts +497 -497
  64. package/src/__tests__/openapi.spec.ts +428 -428
  65. package/src/__tests__/test-helpers.ts +76 -76
  66. package/src/auth/__tests__/credentials.spec.ts +204 -0
  67. package/src/auth/__tests__/index.spec.ts +168 -0
  68. package/src/auth/credentials.ts +187 -0
  69. package/src/auth/index.ts +226 -0
  70. package/src/build/__tests__/bundler.spec.ts +444 -0
  71. package/src/build/__tests__/index-new.spec.ts +474 -474
  72. package/src/build/__tests__/manifests.spec.ts +333 -333
  73. package/src/build/bundler.ts +210 -0
  74. package/src/build/endpoint-analyzer.ts +236 -0
  75. package/src/build/handler-templates.ts +1253 -0
  76. package/src/build/index.ts +260 -179
  77. package/src/build/manifests.ts +52 -52
  78. package/src/build/providerResolver.ts +145 -145
  79. package/src/build/types.ts +64 -43
  80. package/src/config.ts +39 -39
  81. package/src/deploy/__tests__/docker.spec.ts +111 -0
  82. package/src/deploy/__tests__/dokploy.spec.ts +245 -0
  83. package/src/deploy/__tests__/init.spec.ts +662 -0
  84. package/src/deploy/docker.ts +128 -0
  85. package/src/deploy/dokploy.ts +204 -0
  86. package/src/deploy/index.ts +136 -0
  87. package/src/deploy/init.ts +484 -0
  88. package/src/deploy/types.ts +48 -0
  89. package/src/dev/__tests__/index.spec.ts +266 -266
  90. package/src/dev/index.ts +647 -601
  91. package/src/docker/__tests__/compose.spec.ts +531 -0
  92. package/src/docker/__tests__/templates.spec.ts +280 -0
  93. package/src/docker/compose.ts +273 -0
  94. package/src/docker/index.ts +230 -0
  95. package/src/docker/templates.ts +446 -0
  96. package/src/generators/CronGenerator.ts +72 -72
  97. package/src/generators/EndpointGenerator.ts +699 -398
  98. package/src/generators/FunctionGenerator.ts +84 -84
  99. package/src/generators/Generator.ts +72 -72
  100. package/src/generators/OpenApiTsGenerator.ts +577 -577
  101. package/src/generators/SubscriberGenerator.ts +124 -124
  102. package/src/generators/__tests__/CronGenerator.spec.ts +433 -433
  103. package/src/generators/__tests__/EndpointGenerator.spec.ts +532 -382
  104. package/src/generators/__tests__/FunctionGenerator.spec.ts +244 -244
  105. package/src/generators/__tests__/SubscriberGenerator.spec.ts +397 -382
  106. package/src/generators/index.ts +4 -4
  107. package/src/index.ts +623 -201
  108. package/src/init/__tests__/generators.spec.ts +334 -334
  109. package/src/init/__tests__/init.spec.ts +332 -332
  110. package/src/init/__tests__/utils.spec.ts +89 -89
  111. package/src/init/generators/config.ts +175 -175
  112. package/src/init/generators/docker.ts +41 -41
  113. package/src/init/generators/env.ts +72 -72
  114. package/src/init/generators/index.ts +1 -1
  115. package/src/init/generators/models.ts +64 -64
  116. package/src/init/generators/monorepo.ts +161 -161
  117. package/src/init/generators/package.ts +71 -71
  118. package/src/init/generators/source.ts +6 -6
  119. package/src/init/index.ts +203 -208
  120. package/src/init/templates/api.ts +115 -115
  121. package/src/init/templates/index.ts +75 -75
  122. package/src/init/templates/minimal.ts +98 -98
  123. package/src/init/templates/serverless.ts +89 -89
  124. package/src/init/templates/worker.ts +98 -98
  125. package/src/init/utils.ts +54 -56
  126. package/src/openapi-react-query.ts +194 -194
  127. package/src/openapi.ts +63 -63
  128. package/src/secrets/__tests__/encryption.spec.ts +226 -0
  129. package/src/secrets/__tests__/generator.spec.ts +319 -0
  130. package/src/secrets/__tests__/index.spec.ts +91 -0
  131. package/src/secrets/__tests__/storage.spec.ts +611 -0
  132. package/src/secrets/encryption.ts +91 -0
  133. package/src/secrets/generator.ts +164 -0
  134. package/src/secrets/index.ts +383 -0
  135. package/src/secrets/storage.ts +192 -0
  136. package/src/secrets/types.ts +53 -0
  137. package/src/types.ts +295 -176
  138. package/tsdown.config.ts +11 -8
  139. package/dist/config-BrkUalUh.mjs.map +0 -1
  140. package/dist/config-C9aXOHBe.cjs.map +0 -1
  141. package/dist/openapi-BeHLKcwP.cjs.map +0 -1
  142. package/dist/openapi-CZLI4QTr.mjs.map +0 -1
  143. package/dist/openapi-react-query-CcciaVu5.mjs.map +0 -1
  144. package/dist/openapi-react-query-o5iMi8tz.cjs.map +0 -1
  145. package/dist/types-DXgiA1sF.d.mts.map +0 -1
  146. package/dist/types-b-vwGpqc.d.cts.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,505 +74,505 @@ 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: {
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
554
  content: {
555
555
  'application/json': ${bodyName};
556
556
  };
557
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: {
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
567
  query: ${queryName};
568
568
  }`);
569
- }
569
+ }
570
570
 
571
- // Responses
572
- const outputName = info.output
573
- ? await this.getSchemaName(info.output, `${baseName}Output`)
574
- : 'unknown';
575
- parts.push(`responses: {
571
+ // Responses
572
+ const outputName = info.output
573
+ ? await this.getSchemaName(info.output, `${baseName}Output`)
574
+ : 'unknown';
575
+ parts.push(`responses: {
576
576
  200: {
577
577
  content: {
578
578
  'application/json': ${outputName};
@@ -580,43 +580,43 @@ export class OpenApiTsGenerator {
580
580
  };
581
581
  }`);
582
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
- ? `
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
  }