@geekmidas/cli 0.2.4 → 0.4.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 (82) hide show
  1. package/README.md +488 -71
  2. package/dist/{EndpointGenerator-C73wNoih.cjs → EndpointGenerator-BxNCkus4.cjs} +60 -6
  3. package/dist/EndpointGenerator-BxNCkus4.cjs.map +1 -0
  4. package/dist/{EndpointGenerator-CWh18d92.mjs → EndpointGenerator-CzDhG7Or.mjs} +60 -6
  5. package/dist/EndpointGenerator-CzDhG7Or.mjs.map +1 -0
  6. package/dist/OpenApiTsGenerator-NBNEoaeO.cjs +501 -0
  7. package/dist/OpenApiTsGenerator-NBNEoaeO.cjs.map +1 -0
  8. package/dist/OpenApiTsGenerator-q3aWNkuM.mjs +495 -0
  9. package/dist/OpenApiTsGenerator-q3aWNkuM.mjs.map +1 -0
  10. package/dist/build/index.cjs +4 -4
  11. package/dist/build/index.mjs +4 -4
  12. package/dist/build/manifests.cjs +3 -2
  13. package/dist/build/manifests.mjs +2 -2
  14. package/dist/{build-BVng9MQX.cjs → build-CWtHnJMQ.cjs} +19 -17
  15. package/dist/build-CWtHnJMQ.cjs.map +1 -0
  16. package/dist/{build-BqexeI-W.mjs → build-DyDgu_D1.mjs} +20 -18
  17. package/dist/build-DyDgu_D1.mjs.map +1 -0
  18. package/dist/{config-U-mdW-7Y.mjs → config-AFmFKmU0.mjs} +3 -3
  19. package/dist/config-AFmFKmU0.mjs.map +1 -0
  20. package/dist/{config-D1EpSGk6.cjs → config-BVIJpAsa.cjs} +3 -3
  21. package/dist/config-BVIJpAsa.cjs.map +1 -0
  22. package/dist/config.cjs +1 -1
  23. package/dist/config.mjs +1 -1
  24. package/dist/dev/index.cjs +5 -4
  25. package/dist/dev/index.mjs +4 -4
  26. package/dist/{dev-DbtyToc7.cjs → dev-CgDYC4o8.cjs} +95 -31
  27. package/dist/dev-CgDYC4o8.cjs.map +1 -0
  28. package/dist/{dev-DnGYXuMn.mjs → dev-CpA8AQPX.mjs} +90 -32
  29. package/dist/dev-CpA8AQPX.mjs.map +1 -0
  30. package/dist/generators/EndpointGenerator.cjs +1 -1
  31. package/dist/generators/EndpointGenerator.mjs +1 -1
  32. package/dist/generators/OpenApiTsGenerator.cjs +3 -0
  33. package/dist/generators/OpenApiTsGenerator.mjs +3 -0
  34. package/dist/generators/index.cjs +1 -1
  35. package/dist/generators/index.mjs +1 -1
  36. package/dist/index.cjs +16 -10
  37. package/dist/index.cjs.map +1 -1
  38. package/dist/index.mjs +16 -10
  39. package/dist/index.mjs.map +1 -1
  40. package/dist/manifests-C2eMoMUm.mjs +68 -0
  41. package/dist/manifests-C2eMoMUm.mjs.map +1 -0
  42. package/dist/manifests-CK1VV_pM.cjs +80 -0
  43. package/dist/manifests-CK1VV_pM.cjs.map +1 -0
  44. package/dist/openapi-DRTRGhTt.mjs +50 -0
  45. package/dist/openapi-DRTRGhTt.mjs.map +1 -0
  46. package/dist/openapi-DhK4b0lB.cjs +56 -0
  47. package/dist/openapi-DhK4b0lB.cjs.map +1 -0
  48. package/dist/openapi.cjs +4 -3
  49. package/dist/openapi.mjs +4 -3
  50. package/docs/OPENAPI_TYPESCRIPT_DESIGN.md +408 -0
  51. package/docs/manifest-refactor-design.md +287 -0
  52. package/package.json +10 -3
  53. package/src/__tests__/openapi.spec.ts +78 -63
  54. package/src/build/__tests__/index-new.spec.ts +43 -72
  55. package/src/build/__tests__/manifests.spec.ts +346 -0
  56. package/src/build/index.ts +59 -62
  57. package/src/build/manifests.ts +85 -13
  58. package/src/build/types.ts +13 -2
  59. package/src/config.ts +4 -2
  60. package/src/dev/__tests__/index.spec.ts +98 -1
  61. package/src/dev/index.ts +152 -25
  62. package/src/generators/EndpointGenerator.ts +69 -5
  63. package/src/generators/OpenApiTsGenerator.ts +798 -0
  64. package/src/index.ts +6 -3
  65. package/src/openapi.ts +36 -15
  66. package/src/types.ts +23 -7
  67. package/dist/EndpointGenerator-C73wNoih.cjs.map +0 -1
  68. package/dist/EndpointGenerator-CWh18d92.mjs.map +0 -1
  69. package/dist/build-BVng9MQX.cjs.map +0 -1
  70. package/dist/build-BqexeI-W.mjs.map +0 -1
  71. package/dist/config-D1EpSGk6.cjs.map +0 -1
  72. package/dist/config-U-mdW-7Y.mjs.map +0 -1
  73. package/dist/dev-DbtyToc7.cjs.map +0 -1
  74. package/dist/dev-DnGYXuMn.mjs.map +0 -1
  75. package/dist/manifests-BrJXpHrf.mjs +0 -21
  76. package/dist/manifests-BrJXpHrf.mjs.map +0 -1
  77. package/dist/manifests-D0saShvH.cjs +0 -27
  78. package/dist/manifests-D0saShvH.cjs.map +0 -1
  79. package/dist/openapi-BTHbPrxS.mjs +0 -36
  80. package/dist/openapi-BTHbPrxS.mjs.map +0 -1
  81. package/dist/openapi-CewcfoRH.cjs +0 -42
  82. package/dist/openapi-CewcfoRH.cjs.map +0 -1
@@ -0,0 +1,408 @@
1
+ # OpenAPI TypeScript Generation - Design Document
2
+
3
+ ## Overview
4
+
5
+ This document describes the design for generating TypeScript OpenAPI specifications with integrated authentication support. The feature extends `gkm openapi` with a `--ts` flag that outputs a TypeScript module instead of JSON, enabling:
6
+
7
+ 1. **Type-safe paths** - Full TypeScript interface for API routes
8
+ 2. **Runtime auth map** - Per-endpoint authentication requirements
9
+ 3. **Reusable schemas** - TypeScript interfaces extracted from Zod/Valibot schemas
10
+ 4. **Security scheme definitions** - OpenAPI security schemes as typed constants
11
+
12
+ ## Motivation
13
+
14
+ ### Current Flow (JSON-based)
15
+
16
+ ```
17
+ Endpoints → gkm openapi → openapi.json → openapi-typescript → types.d.ts
18
+
19
+ (no auth info at runtime)
20
+ ```
21
+
22
+ **Problems:**
23
+ - Auth requirements lost at runtime - OpenAPI security info exists but isn't usable by the fetcher
24
+ - Two-step generation - need external tool (`openapi-typescript`) for types
25
+ - No schema reuse - generated types are isolated, can't reference shared interfaces
26
+
27
+ ### Proposed Flow (TypeScript-native)
28
+
29
+ ```
30
+ Endpoints → gkm openapi --ts → openapi.ts
31
+
32
+ Exports:
33
+ ├── paths (type)
34
+ ├── endpointAuth (runtime map)
35
+ ├── securitySchemes (runtime definitions)
36
+ └── schema interfaces (reusable types)
37
+ ```
38
+
39
+ ## Command Interface
40
+
41
+ ```bash
42
+ # Default: generates TypeScript
43
+ gkm openapi --output ./src/api/openapi.ts
44
+
45
+ # Explicit JSON output (legacy)
46
+ gkm openapi --json --output ./openapi.json
47
+ ```
48
+
49
+ ### Options
50
+
51
+ | Flag | Description | Default |
52
+ |------|-------------|---------|
53
+ | `--json` | Generate JSON instead of TypeScript (legacy) | `false` |
54
+ | `--output` | Output file path | `openapi.ts` |
55
+
56
+ ## Generated Output Structure
57
+
58
+ ### File: `openapi.ts`
59
+
60
+ ```typescript
61
+ // Auto-generated by @geekmidas/cli - DO NOT EDIT
62
+ import type { OpenAPIV3_1 } from 'openapi-types';
63
+
64
+ // ============================================================
65
+ // Security Schemes
66
+ // ============================================================
67
+
68
+ /**
69
+ * Available security schemes for this API.
70
+ * Maps authorizer names to OpenAPI security scheme definitions.
71
+ */
72
+ export const securitySchemes = {
73
+ bearer: {
74
+ type: 'http',
75
+ scheme: 'bearer',
76
+ bearerFormat: 'JWT',
77
+ },
78
+ iam: {
79
+ type: 'apiKey',
80
+ in: 'header',
81
+ name: 'Authorization',
82
+ 'x-amazon-apigateway-authtype': 'awsSigv4',
83
+ },
84
+ apiKey: {
85
+ type: 'apiKey',
86
+ in: 'header',
87
+ name: 'X-API-Key',
88
+ },
89
+ } as const satisfies Record<string, OpenAPIV3_1.SecuritySchemeObject>;
90
+
91
+ export type SecuritySchemeId = keyof typeof securitySchemes;
92
+
93
+ // ============================================================
94
+ // Endpoint Authentication Map
95
+ // ============================================================
96
+
97
+ /**
98
+ * Runtime map of endpoints to their required authentication scheme.
99
+ * `null` indicates a public endpoint (no auth required).
100
+ */
101
+ export const endpointAuth = {
102
+ 'POST /tenants': 'iam',
103
+ 'GET /tenants/{id}': 'bearer',
104
+ 'PUT /tenants/{id}': 'bearer',
105
+ 'DELETE /tenants/{id}': 'bearer',
106
+ 'GET /health': null,
107
+ 'GET /docs': null,
108
+ } as const satisfies Record<string, SecuritySchemeId | null>;
109
+
110
+ export type AuthenticatedEndpoint = {
111
+ [K in keyof typeof endpointAuth]: typeof endpointAuth[K] extends null ? never : K;
112
+ }[keyof typeof endpointAuth];
113
+
114
+ export type PublicEndpoint = {
115
+ [K in keyof typeof endpointAuth]: typeof endpointAuth[K] extends null ? K : never;
116
+ }[keyof typeof endpointAuth];
117
+
118
+ // ============================================================
119
+ // Schema Definitions (Reusable Types)
120
+ // ============================================================
121
+
122
+ export interface Tenant {
123
+ id: string;
124
+ name: string;
125
+ createdAt: string;
126
+ updatedAt: string;
127
+ }
128
+
129
+ export interface CreateTenantInput {
130
+ name: string;
131
+ }
132
+
133
+ export interface UpdateTenantInput {
134
+ name?: string;
135
+ }
136
+
137
+ // ============================================================
138
+ // OpenAPI Paths
139
+ // ============================================================
140
+
141
+ export interface paths {
142
+ '/tenants': {
143
+ post: {
144
+ requestBody: {
145
+ content: {
146
+ 'application/json': CreateTenantInput;
147
+ };
148
+ };
149
+ responses: {
150
+ 201: {
151
+ content: {
152
+ 'application/json': Tenant;
153
+ };
154
+ };
155
+ };
156
+ };
157
+ };
158
+ '/tenants/{id}': {
159
+ parameters: {
160
+ path: {
161
+ id: string;
162
+ };
163
+ };
164
+ get: {
165
+ responses: {
166
+ 200: {
167
+ content: {
168
+ 'application/json': Tenant;
169
+ };
170
+ };
171
+ };
172
+ };
173
+ put: {
174
+ requestBody: {
175
+ content: {
176
+ 'application/json': UpdateTenantInput;
177
+ };
178
+ };
179
+ responses: {
180
+ 200: {
181
+ content: {
182
+ 'application/json': Tenant;
183
+ };
184
+ };
185
+ };
186
+ };
187
+ delete: {
188
+ responses: {
189
+ 204: {
190
+ content: never;
191
+ };
192
+ };
193
+ };
194
+ };
195
+ '/health': {
196
+ get: {
197
+ responses: {
198
+ 200: {
199
+ content: {
200
+ 'application/json': { status: string };
201
+ };
202
+ };
203
+ };
204
+ };
205
+ };
206
+ }
207
+
208
+ // ============================================================
209
+ // Full OpenAPI Specification (Optional)
210
+ // ============================================================
211
+
212
+ export const spec = {
213
+ openapi: '3.1.0',
214
+ info: {
215
+ title: 'Tenant API',
216
+ version: '1.0.0',
217
+ },
218
+ paths: { /* ... */ },
219
+ components: {
220
+ securitySchemes,
221
+ schemas: { /* ... */ },
222
+ },
223
+ } as const satisfies OpenAPIV3_1.Document;
224
+ ```
225
+
226
+ ## Implementation Details
227
+
228
+ ### 1. Authorizer to Security Scheme Mapping
229
+
230
+ The `Authorizer.type` field maps to OpenAPI security schemes:
231
+
232
+ | Authorizer Type | OpenAPI Security Scheme |
233
+ |-----------------|------------------------|
234
+ | `jwt`, `bearer` | `{ type: 'http', scheme: 'bearer', bearerFormat: 'JWT' }` |
235
+ | `iam`, `aws-sigv4` | `{ type: 'apiKey', in: 'header', name: 'Authorization', 'x-amazon-apigateway-authtype': 'awsSigv4' }` |
236
+ | `apiKey` | `{ type: 'apiKey', in: metadata.in, name: metadata.name }` |
237
+ | `oauth2` | `{ type: 'oauth2', flows: { ... } }` |
238
+ | `oidc` | `{ type: 'openIdConnect', openIdConnectUrl: metadata.issuer }` |
239
+ | `none` / undefined | `null` (public endpoint) |
240
+
241
+ ### 2. Schema Extraction
242
+
243
+ Schemas are extracted from StandardSchema (Zod/Valibot) definitions:
244
+
245
+ ```typescript
246
+ // From endpoint definition
247
+ const endpoint = e
248
+ .post('/tenants')
249
+ .body(z.object({ name: z.string() }))
250
+ .output(z.object({ id: z.string(), name: z.string() }))
251
+ .handle(async ({ body }) => { ... });
252
+
253
+ // Extracted as
254
+ export interface CreateTenantInput {
255
+ name: string;
256
+ }
257
+
258
+ export interface CreateTenantOutput {
259
+ id: string;
260
+ name: string;
261
+ }
262
+ ```
263
+
264
+ ### 3. Naming Strategy
265
+
266
+ | Schema Location | Generated Name |
267
+ |-----------------|----------------|
268
+ | Body schema | `{OperationId}Input` or `{Method}{Route}Input` |
269
+ | Output schema | `{OperationId}Output` or `{Method}{Route}Output` |
270
+ | Params schema | `{OperationId}Params` |
271
+ | Query schema | `{OperationId}Query` |
272
+
273
+ ### 4. Component Collector Enhancement
274
+
275
+ ```typescript
276
+ // packages/schema/src/openapi.ts
277
+ export interface ComponentCollector {
278
+ schemas: Record<string, OpenAPIV3_1.SchemaObject>;
279
+ securitySchemes: Record<string, OpenAPIV3_1.SecuritySchemeObject>;
280
+ addSchema(id: string, schema: OpenAPIV3_1.SchemaObject): void;
281
+ addSecurityScheme(id: string, scheme: OpenAPIV3_1.SecuritySchemeObject): void;
282
+ getReference(id: string): OpenAPIV3_1.ReferenceObject;
283
+ }
284
+ ```
285
+
286
+ ## Integration with Auth-Aware Fetcher
287
+
288
+ The generated `endpointAuth` map enables automatic auth handling:
289
+
290
+ ```typescript
291
+ // packages/client/src/auth-fetcher.ts
292
+ import { endpointAuth, securitySchemes, type paths } from './openapi';
293
+ import { TokenClient } from '@geekmidas/auth/client';
294
+
295
+ export function createAuthAwareFetcher<Paths>(options: AuthFetcherOptions) {
296
+ const { tokenClient, awsSigner, apiKeyProvider } = options;
297
+
298
+ return async <T extends TypedEndpoint<Paths>>(
299
+ endpoint: T,
300
+ config?: FilteredRequestConfig<Paths, T>,
301
+ ) => {
302
+ const authScheme = endpointAuth[endpoint as keyof typeof endpointAuth];
303
+ let headers: Record<string, string> = {};
304
+
305
+ if (authScheme) {
306
+ const scheme = securitySchemes[authScheme];
307
+
308
+ switch (scheme.type) {
309
+ case 'http':
310
+ if (scheme.scheme === 'bearer') {
311
+ headers = await tokenClient.createValidAuthHeaders();
312
+ }
313
+ break;
314
+ case 'apiKey':
315
+ if (scheme['x-amazon-apigateway-authtype'] === 'awsSigv4') {
316
+ headers = await awsSigner.sign(endpoint, config);
317
+ } else {
318
+ headers[scheme.name] = await apiKeyProvider.getKey();
319
+ }
320
+ break;
321
+ }
322
+ }
323
+
324
+ return baseFetcher.request(endpoint, {
325
+ ...config,
326
+ headers: { ...headers, ...config?.headers },
327
+ });
328
+ };
329
+ }
330
+ ```
331
+
332
+ ## File Structure Changes
333
+
334
+ ```
335
+ packages/
336
+ ├── cli/
337
+ │ └── src/
338
+ │ ├── openapi.ts # Updated: add --ts support
339
+ │ └── generators/
340
+ │ └── OpenApiTsGenerator.ts # New: TypeScript generation
341
+ ├── schema/
342
+ │ └── src/
343
+ │ └── openapi.ts # Updated: security scheme support
344
+ └── client/
345
+ └── src/
346
+ └── auth-fetcher.ts # New: auth-aware fetcher
347
+ ```
348
+
349
+ ## Migration Path
350
+
351
+ ### Breaking Change
352
+
353
+ TypeScript is now the default output format. Existing JSON users must add `--json` flag.
354
+
355
+ ### Existing JSON Users
356
+
357
+ ```bash
358
+ # Before
359
+ gkm openapi --output ./openapi.json
360
+
361
+ # After (explicit JSON)
362
+ gkm openapi --json --output ./openapi.json
363
+ ```
364
+
365
+ ### Adopting TypeScript Output (New Default)
366
+
367
+ 1. Run `gkm openapi --output ./src/api/openapi.ts`
368
+ 2. Update imports from `./openapi-types` to `./openapi`
369
+ 3. Use `createAuthAwareFetcher` instead of `createTypedFetcher`
370
+
371
+ ## Testing Strategy
372
+
373
+ 1. **Unit Tests**
374
+ - Authorizer → security scheme mapping
375
+ - Schema extraction from StandardSchema
376
+ - TypeScript code generation
377
+
378
+ 2. **Integration Tests**
379
+ - Full endpoint → TypeScript output pipeline
380
+ - Generated code compiles without errors
381
+ - Auth map matches endpoint authorizers
382
+
383
+ 3. **E2E Tests**
384
+ - Auth-aware fetcher selects correct auth per endpoint
385
+ - Bearer endpoints get JWT headers
386
+ - IAM endpoints get SigV4 signatures
387
+ - Public endpoints get no auth headers
388
+
389
+ ## Open Questions
390
+
391
+ 1. **Schema naming conflicts** - What if two endpoints have the same operationId?
392
+ - Proposal: Append route hash suffix if conflict detected
393
+
394
+ 2. **Circular schema references** - How to handle `User { friends: User[] }`?
395
+ - Proposal: Use TypeScript `interface` declarations which support self-reference
396
+
397
+ 3. **Optional vs required auth** - Some endpoints allow optional auth (return more data if authenticated)
398
+ - Proposal: Add `optional` auth type: `'bearer' | 'bearer?' | null`
399
+
400
+ ## Timeline
401
+
402
+ | Phase | Description | Estimate |
403
+ |-------|-------------|----------|
404
+ | 1 | Schema package updates (ComponentCollector) | - |
405
+ | 2 | CLI TypeScript generator | - |
406
+ | 3 | Client auth-aware fetcher | - |
407
+ | 4 | Documentation and examples | - |
408
+ | 5 | Testing and refinement | - |
@@ -0,0 +1,287 @@
1
+ # Manifest Refactoring Design
2
+
3
+ ## Current State
4
+
5
+ Currently, the CLI generates a single `manifest.json` file at `.gkm/manifest.json` that aggregates all routes, functions, crons, and subscribers across all providers.
6
+
7
+ ```
8
+ .gkm/
9
+ ├── manifest.json # Single JSON manifest
10
+ ├── aws-apigatewayv2/ # AWS handlers
11
+ │ ├── getUsers.ts
12
+ │ └── createUser.ts
13
+ └── server/ # Server handlers
14
+ ├── app.ts
15
+ └── endpoints.ts
16
+ ```
17
+
18
+ ### Problems
19
+
20
+ 1. **Mixed provider data**: AWS and server routes are combined, causing `method: 'ALL'` to appear in manifests used for AWS infrastructure
21
+ 2. **JSON format**: No TypeScript types available, consumers must define their own types
22
+ 3. **No derived types**: Consumers can't easily extract types like `Authorizer` from the manifest
23
+
24
+ ## Proposed Changes
25
+
26
+ ### 1. Folder Structure
27
+
28
+ Change from single `manifest.json` to a `manifest/` folder with TypeScript files per provider:
29
+
30
+ ```
31
+ .gkm/
32
+ ├── manifest/
33
+ │ ├── aws.ts # AWS-specific manifest
34
+ │ └── server.ts # Server-specific manifest
35
+ ├── aws-apigatewayv2/
36
+ │ ├── getUsers.ts
37
+ │ └── createUser.ts
38
+ └── server/
39
+ ├── app.ts
40
+ └── endpoints.ts
41
+ ```
42
+
43
+ ### 2. AWS Manifest (`manifest/aws.ts`)
44
+
45
+ Contains only AWS routes with actual HTTP methods (no `method: 'ALL'`):
46
+
47
+ ```typescript
48
+ export const manifest = {
49
+ routes: [
50
+ {
51
+ path: '/users',
52
+ method: 'GET',
53
+ handler: '.gkm/aws-apigatewayv2/getUsers.handler',
54
+ timeout: 30,
55
+ memorySize: 256,
56
+ environment: ['DATABASE_URL', 'API_KEY'],
57
+ authorizer: 'jwt',
58
+ },
59
+ {
60
+ path: '/users',
61
+ method: 'POST',
62
+ handler: '.gkm/aws-apigatewayv2/createUser.handler',
63
+ timeout: 30,
64
+ memorySize: 256,
65
+ environment: ['DATABASE_URL'],
66
+ authorizer: 'jwt',
67
+ },
68
+ ],
69
+ functions: [
70
+ {
71
+ name: 'processPayment',
72
+ handler: '.gkm/aws-lambda/processPayment.handler',
73
+ timeout: 60,
74
+ memorySize: 512,
75
+ environment: ['STRIPE_KEY'],
76
+ },
77
+ ],
78
+ crons: [
79
+ {
80
+ name: 'dailyReport',
81
+ handler: '.gkm/aws-lambda/dailyReport.handler',
82
+ schedule: 'rate(1 day)',
83
+ timeout: 300,
84
+ memorySize: 256,
85
+ environment: [],
86
+ },
87
+ ],
88
+ subscribers: [
89
+ {
90
+ name: 'orderCreated',
91
+ handler: '.gkm/aws-lambda/orderCreated.handler',
92
+ subscribedEvents: ['order.created'],
93
+ timeout: 30,
94
+ memorySize: 256,
95
+ environment: [],
96
+ },
97
+ ],
98
+ } as const;
99
+
100
+ // Derived types
101
+ export type Route = (typeof manifest.routes)[number];
102
+ export type Function = (typeof manifest.functions)[number];
103
+ export type Cron = (typeof manifest.crons)[number];
104
+ export type Subscriber = (typeof manifest.subscribers)[number];
105
+
106
+ // Useful union types
107
+ export type Authorizer = Route['authorizer'];
108
+ export type HttpMethod = Route['method'];
109
+ export type RoutePath = Route['path'];
110
+ ```
111
+
112
+ ### 3. Server Manifest (`manifest/server.ts`)
113
+
114
+ Contains server app info and route metadata for documentation/type derivation:
115
+
116
+ ```typescript
117
+ export const manifest = {
118
+ app: {
119
+ handler: '.gkm/server/app.ts',
120
+ endpoints: '.gkm/server/endpoints.ts',
121
+ },
122
+ routes: [
123
+ {
124
+ path: '/users',
125
+ method: 'GET',
126
+ authorizer: 'jwt',
127
+ },
128
+ {
129
+ path: '/users',
130
+ method: 'POST',
131
+ authorizer: 'jwt',
132
+ },
133
+ ],
134
+ subscribers: [
135
+ {
136
+ name: 'orderCreated',
137
+ subscribedEvents: ['order.created'],
138
+ },
139
+ ],
140
+ } as const;
141
+
142
+ // Derived types
143
+ export type Route = (typeof manifest.routes)[number];
144
+ export type Subscriber = (typeof manifest.subscribers)[number];
145
+
146
+ // Useful union types
147
+ export type Authorizer = Route['authorizer'];
148
+ export type HttpMethod = Route['method'];
149
+ export type RoutePath = Route['path'];
150
+ ```
151
+
152
+ ## Implementation Details
153
+
154
+ ### Changes to `manifests.ts`
155
+
156
+ ```typescript
157
+ export async function generateManifests(
158
+ outputDir: string,
159
+ provider: 'aws' | 'server',
160
+ routes: RouteInfo[],
161
+ functions: FunctionInfo[],
162
+ crons: CronInfo[],
163
+ subscribers: SubscriberInfo[],
164
+ ): Promise<void> {
165
+ const manifestDir = join(outputDir, 'manifest');
166
+ await mkdir(manifestDir, { recursive: true });
167
+
168
+ if (provider === 'aws') {
169
+ await generateAwsManifest(manifestDir, routes, functions, crons, subscribers);
170
+ } else {
171
+ await generateServerManifest(manifestDir, routes, subscribers);
172
+ }
173
+ }
174
+
175
+ async function generateAwsManifest(
176
+ manifestDir: string,
177
+ routes: RouteInfo[],
178
+ functions: FunctionInfo[],
179
+ crons: CronInfo[],
180
+ subscribers: SubscriberInfo[],
181
+ ): Promise<void> {
182
+ // Filter out 'ALL' method routes (server-specific)
183
+ const awsRoutes = routes.filter(r => r.method !== 'ALL');
184
+
185
+ const content = `export const manifest = {
186
+ routes: ${JSON.stringify(awsRoutes, null, 2)},
187
+ functions: ${JSON.stringify(functions, null, 2)},
188
+ crons: ${JSON.stringify(crons, null, 2)},
189
+ subscribers: ${JSON.stringify(subscribers, null, 2)},
190
+ } as const;
191
+
192
+ // Derived types
193
+ export type Route = (typeof manifest.routes)[number];
194
+ export type Function = (typeof manifest.functions)[number];
195
+ export type Cron = (typeof manifest.crons)[number];
196
+ export type Subscriber = (typeof manifest.subscribers)[number];
197
+
198
+ // Useful union types
199
+ export type Authorizer = Route['authorizer'];
200
+ export type HttpMethod = Route['method'];
201
+ export type RoutePath = Route['path'];
202
+ `;
203
+
204
+ await writeFile(join(manifestDir, 'aws.ts'), content);
205
+ }
206
+ ```
207
+
208
+ ### Changes to Build Flow
209
+
210
+ Currently `buildCommand` aggregates all providers into one manifest. Change to:
211
+
212
+ 1. Generate per-provider manifests during each provider's build
213
+ 2. Remove aggregation step
214
+ 3. Each provider generates its own TypeScript manifest file
215
+
216
+ ```typescript
217
+ async function buildForProvider(
218
+ provider: LegacyProvider,
219
+ // ...
220
+ ): Promise<BuildResult> {
221
+ // ... existing build logic ...
222
+
223
+ // Generate provider-specific manifest
224
+ const manifestProvider = provider.startsWith('aws') ? 'aws' : 'server';
225
+ await generateManifests(
226
+ rootOutputDir,
227
+ manifestProvider,
228
+ routes,
229
+ functionInfos,
230
+ cronInfos,
231
+ subscriberInfos,
232
+ );
233
+
234
+ return { routes, functions: functionInfos, crons: cronInfos, subscribers: subscriberInfos };
235
+ }
236
+ ```
237
+
238
+ ## Usage Examples
239
+
240
+ ### AWS CDK / SST Integration
241
+
242
+ ```typescript
243
+ import { manifest, type Route, type Authorizer } from './.gkm/manifest/aws';
244
+
245
+ // Type-safe iteration over routes
246
+ for (const route of manifest.routes) {
247
+ new ApiGatewayRoute(this, route.path, {
248
+ method: route.method,
249
+ handler: route.handler,
250
+ authorizer: getAuthorizer(route.authorizer),
251
+ });
252
+ }
253
+
254
+ // Use derived types
255
+ function getAuthorizer(name: Authorizer): IAuthorizer {
256
+ switch (name) {
257
+ case 'jwt':
258
+ return jwtAuthorizer;
259
+ case 'apiKey':
260
+ return apiKeyAuthorizer;
261
+ case 'none':
262
+ return undefined;
263
+ }
264
+ }
265
+ ```
266
+
267
+ ### Type Checking Authorizer Values
268
+
269
+ ```typescript
270
+ import type { Authorizer } from './.gkm/manifest/aws';
271
+
272
+ // Authorizer is a union type of all authorizer values
273
+ // e.g., 'jwt' | 'apiKey' | 'none'
274
+ const authorizer: Authorizer = 'jwt'; // ✓
275
+ const invalid: Authorizer = 'invalid'; // ✗ Type error
276
+ ```
277
+
278
+ ## Migration Notes
279
+
280
+ 1. **Breaking change**: Consumers using `manifest.json` need to switch to TypeScript imports
281
+ 2. **Benefit**: Full TypeScript support with derived types
282
+
283
+ ## Design Decisions
284
+
285
+ 1. **No backward compatibility**: `manifest.json` will not be generated
286
+ 2. **Server manifest includes route metadata**: For documentation and type derivation
287
+ 3. **No re-export index**: Each manifest is imported directly