@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.
- package/README.md +525 -0
- package/dist/bundler-B1qy9b-j.cjs +112 -0
- package/dist/bundler-B1qy9b-j.cjs.map +1 -0
- package/dist/bundler-DskIqW2t.mjs +111 -0
- package/dist/bundler-DskIqW2t.mjs.map +1 -0
- package/dist/{config-C9aXOHBe.cjs → config-AmInkU7k.cjs} +8 -8
- package/dist/config-AmInkU7k.cjs.map +1 -0
- package/dist/{config-BrkUalUh.mjs → config-DYULeEv8.mjs} +3 -3
- package/dist/config-DYULeEv8.mjs.map +1 -0
- package/dist/config.cjs +1 -1
- package/dist/config.d.cts +1 -1
- package/dist/config.d.mts +1 -1
- package/dist/config.mjs +1 -1
- package/dist/encryption-C8H-38Yy.mjs +42 -0
- package/dist/encryption-C8H-38Yy.mjs.map +1 -0
- package/dist/encryption-Dyf_r1h-.cjs +44 -0
- package/dist/encryption-Dyf_r1h-.cjs.map +1 -0
- package/dist/index.cjs +2123 -179
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +2141 -192
- package/dist/index.mjs.map +1 -1
- package/dist/{openapi-CZLI4QTr.mjs → openapi-BfFlOBCG.mjs} +801 -38
- package/dist/openapi-BfFlOBCG.mjs.map +1 -0
- package/dist/{openapi-BeHLKcwP.cjs → openapi-Bt_1FDpT.cjs} +794 -31
- package/dist/openapi-Bt_1FDpT.cjs.map +1 -0
- package/dist/{openapi-react-query-o5iMi8tz.cjs → openapi-react-query-B-sNWHFU.cjs} +5 -5
- package/dist/openapi-react-query-B-sNWHFU.cjs.map +1 -0
- package/dist/{openapi-react-query-CcciaVu5.mjs → openapi-react-query-B6XTeGqS.mjs} +5 -5
- package/dist/openapi-react-query-B6XTeGqS.mjs.map +1 -0
- package/dist/openapi-react-query.cjs +1 -1
- package/dist/openapi-react-query.d.cts.map +1 -1
- package/dist/openapi-react-query.d.mts.map +1 -1
- package/dist/openapi-react-query.mjs +1 -1
- package/dist/openapi.cjs +2 -2
- package/dist/openapi.d.cts +1 -1
- package/dist/openapi.d.cts.map +1 -1
- package/dist/openapi.d.mts +1 -1
- package/dist/openapi.d.mts.map +1 -1
- package/dist/openapi.mjs +2 -2
- package/dist/storage-BOOpAF8N.cjs +5 -0
- package/dist/storage-Bj1E26lU.cjs +187 -0
- package/dist/storage-Bj1E26lU.cjs.map +1 -0
- package/dist/storage-kSxTjkNb.mjs +133 -0
- package/dist/storage-kSxTjkNb.mjs.map +1 -0
- package/dist/storage-tgZSUnKl.mjs +3 -0
- package/dist/{types-b-vwGpqc.d.cts → types-BR0M2v_c.d.mts} +100 -1
- package/dist/types-BR0M2v_c.d.mts.map +1 -0
- package/dist/{types-DXgiA1sF.d.mts → types-BhkZc-vm.d.cts} +100 -1
- package/dist/types-BhkZc-vm.d.cts.map +1 -0
- package/examples/cron-example.ts +27 -27
- package/examples/env.ts +27 -27
- package/examples/function-example.ts +31 -31
- package/examples/gkm.config.json +20 -20
- package/examples/gkm.config.ts +8 -8
- package/examples/gkm.minimal.config.json +5 -5
- package/examples/gkm.production.config.json +25 -25
- package/examples/logger.ts +2 -2
- package/package.json +6 -6
- package/src/__tests__/EndpointGenerator.hooks.spec.ts +191 -191
- package/src/__tests__/config.spec.ts +55 -55
- package/src/__tests__/loadEnvFiles.spec.ts +93 -93
- package/src/__tests__/normalizeHooksConfig.spec.ts +58 -58
- package/src/__tests__/openapi-react-query.spec.ts +497 -497
- package/src/__tests__/openapi.spec.ts +428 -428
- package/src/__tests__/test-helpers.ts +76 -76
- package/src/auth/__tests__/credentials.spec.ts +204 -0
- package/src/auth/__tests__/index.spec.ts +168 -0
- package/src/auth/credentials.ts +187 -0
- package/src/auth/index.ts +226 -0
- package/src/build/__tests__/bundler.spec.ts +444 -0
- package/src/build/__tests__/index-new.spec.ts +474 -474
- package/src/build/__tests__/manifests.spec.ts +333 -333
- package/src/build/bundler.ts +210 -0
- package/src/build/endpoint-analyzer.ts +236 -0
- package/src/build/handler-templates.ts +1253 -0
- package/src/build/index.ts +260 -179
- package/src/build/manifests.ts +52 -52
- package/src/build/providerResolver.ts +145 -145
- package/src/build/types.ts +64 -43
- package/src/config.ts +39 -39
- package/src/deploy/__tests__/docker.spec.ts +111 -0
- package/src/deploy/__tests__/dokploy.spec.ts +245 -0
- package/src/deploy/__tests__/init.spec.ts +662 -0
- package/src/deploy/docker.ts +128 -0
- package/src/deploy/dokploy.ts +204 -0
- package/src/deploy/index.ts +136 -0
- package/src/deploy/init.ts +484 -0
- package/src/deploy/types.ts +48 -0
- package/src/dev/__tests__/index.spec.ts +266 -266
- package/src/dev/index.ts +647 -601
- package/src/docker/__tests__/compose.spec.ts +531 -0
- package/src/docker/__tests__/templates.spec.ts +280 -0
- package/src/docker/compose.ts +273 -0
- package/src/docker/index.ts +230 -0
- package/src/docker/templates.ts +446 -0
- package/src/generators/CronGenerator.ts +72 -72
- package/src/generators/EndpointGenerator.ts +699 -398
- package/src/generators/FunctionGenerator.ts +84 -84
- package/src/generators/Generator.ts +72 -72
- package/src/generators/OpenApiTsGenerator.ts +577 -577
- package/src/generators/SubscriberGenerator.ts +124 -124
- package/src/generators/__tests__/CronGenerator.spec.ts +433 -433
- package/src/generators/__tests__/EndpointGenerator.spec.ts +532 -382
- package/src/generators/__tests__/FunctionGenerator.spec.ts +244 -244
- package/src/generators/__tests__/SubscriberGenerator.spec.ts +397 -382
- package/src/generators/index.ts +4 -4
- package/src/index.ts +623 -201
- package/src/init/__tests__/generators.spec.ts +334 -334
- package/src/init/__tests__/init.spec.ts +332 -332
- package/src/init/__tests__/utils.spec.ts +89 -89
- package/src/init/generators/config.ts +175 -175
- package/src/init/generators/docker.ts +41 -41
- package/src/init/generators/env.ts +72 -72
- package/src/init/generators/index.ts +1 -1
- package/src/init/generators/models.ts +64 -64
- package/src/init/generators/monorepo.ts +161 -161
- package/src/init/generators/package.ts +71 -71
- package/src/init/generators/source.ts +6 -6
- package/src/init/index.ts +203 -208
- package/src/init/templates/api.ts +115 -115
- package/src/init/templates/index.ts +75 -75
- package/src/init/templates/minimal.ts +98 -98
- package/src/init/templates/serverless.ts +89 -89
- package/src/init/templates/worker.ts +98 -98
- package/src/init/utils.ts +54 -56
- package/src/openapi-react-query.ts +194 -194
- package/src/openapi.ts +63 -63
- package/src/secrets/__tests__/encryption.spec.ts +226 -0
- package/src/secrets/__tests__/generator.spec.ts +319 -0
- package/src/secrets/__tests__/index.spec.ts +91 -0
- package/src/secrets/__tests__/storage.spec.ts +611 -0
- package/src/secrets/encryption.ts +91 -0
- package/src/secrets/generator.ts +164 -0
- package/src/secrets/index.ts +383 -0
- package/src/secrets/storage.ts +192 -0
- package/src/secrets/types.ts +53 -0
- package/src/types.ts +295 -176
- package/tsdown.config.ts +11 -8
- package/dist/config-BrkUalUh.mjs.map +0 -1
- package/dist/config-C9aXOHBe.cjs.map +0 -1
- package/dist/openapi-BeHLKcwP.cjs.map +0 -1
- package/dist/openapi-CZLI4QTr.mjs.map +0 -1
- package/dist/openapi-react-query-CcciaVu5.mjs.map +0 -1
- package/dist/openapi-react-query-o5iMi8tz.cjs.map +0 -1
- package/dist/types-DXgiA1sF.d.mts.map +0 -1
- package/dist/types-b-vwGpqc.d.cts.map +0 -1
|
@@ -0,0 +1,1253 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Handler Templates for Build-Time Code Generation
|
|
3
|
+
*
|
|
4
|
+
* Generates optimized handler code based on endpoint tier:
|
|
5
|
+
* - minimal: Near-raw-Hono performance for simple endpoints
|
|
6
|
+
* - standard: Middleware composition for auth/services
|
|
7
|
+
* - full: Complete handler chain for complex endpoints
|
|
8
|
+
*
|
|
9
|
+
* Output structure (split by tier):
|
|
10
|
+
* - endpoints/validators.ts - Shared validator factories
|
|
11
|
+
* - endpoints/minimal.ts - Minimal tier handlers
|
|
12
|
+
* - endpoints/standard.ts - Standard tier handlers
|
|
13
|
+
* - endpoints/full.ts - Full tier handlers
|
|
14
|
+
* - endpoints/index.ts - Main entry point
|
|
15
|
+
*/
|
|
16
|
+
import type { EndpointAnalysis, EndpointFeatures } from './endpoint-analyzer';
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Multi-file output structure (flat by tier)
|
|
20
|
+
*/
|
|
21
|
+
export interface GeneratedEndpointFiles {
|
|
22
|
+
'validators.ts': string;
|
|
23
|
+
'minimal.ts': string;
|
|
24
|
+
'standard.ts': string;
|
|
25
|
+
'full.ts': string;
|
|
26
|
+
'index.ts': string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Nested folder structure with per-endpoint files
|
|
31
|
+
*
|
|
32
|
+
* Structure:
|
|
33
|
+
* - endpoints/validators.ts
|
|
34
|
+
* - endpoints/minimal/index.ts
|
|
35
|
+
* - endpoints/minimal/[endpointName].ts
|
|
36
|
+
* - endpoints/standard/index.ts
|
|
37
|
+
* - endpoints/standard/[endpointName].ts
|
|
38
|
+
* - endpoints/full/index.ts
|
|
39
|
+
* - endpoints/full/[endpointName].ts
|
|
40
|
+
* - endpoints/index.ts
|
|
41
|
+
*/
|
|
42
|
+
export interface GeneratedEndpointFilesNested {
|
|
43
|
+
'validators.ts': string;
|
|
44
|
+
'minimal/index.ts': string;
|
|
45
|
+
'standard/index.ts': string;
|
|
46
|
+
'full/index.ts': string;
|
|
47
|
+
'index.ts': string;
|
|
48
|
+
[path: string]: string; // e.g., 'minimal/healthEndpoint.ts'
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Endpoint import info for generating import statements
|
|
53
|
+
*/
|
|
54
|
+
export interface EndpointImportInfo {
|
|
55
|
+
exportName: string;
|
|
56
|
+
importPath: string;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Generate imports needed for optimized endpoints file
|
|
61
|
+
*/
|
|
62
|
+
export function generateOptimizedImports(analyses: EndpointAnalysis[]): string {
|
|
63
|
+
const needsValidator = analyses.some(
|
|
64
|
+
(a) =>
|
|
65
|
+
a.features.hasBodyValidation ||
|
|
66
|
+
a.features.hasQueryValidation ||
|
|
67
|
+
a.features.hasParamValidation,
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
const needsResponseBuilder = analyses.some(
|
|
71
|
+
(a) => a.tier === 'standard' || a.tier === 'full',
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
const needsServiceDiscovery = analyses.some(
|
|
75
|
+
(a) => a.features.hasServices || a.features.hasDatabase,
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
const needsEvents = analyses.some((a) => a.features.hasEvents);
|
|
79
|
+
|
|
80
|
+
const needsAudits = analyses.some((a) => a.features.hasAudits);
|
|
81
|
+
|
|
82
|
+
const needsRateLimit = analyses.some((a) => a.features.hasRateLimit);
|
|
83
|
+
|
|
84
|
+
const needsRls = analyses.some((a) => a.features.hasRls);
|
|
85
|
+
|
|
86
|
+
const imports: string[] = [
|
|
87
|
+
`import type { EnvironmentParser } from '@geekmidas/envkit';`,
|
|
88
|
+
`import type { Logger } from '@geekmidas/logger';`,
|
|
89
|
+
`import type { Hono } from 'hono';`,
|
|
90
|
+
];
|
|
91
|
+
|
|
92
|
+
if (needsValidator) {
|
|
93
|
+
imports.push(`import { validator } from 'hono/validator';`);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
imports.push(`import { Endpoint } from '@geekmidas/constructs/endpoints';`);
|
|
97
|
+
|
|
98
|
+
if (needsResponseBuilder) {
|
|
99
|
+
imports.push(
|
|
100
|
+
`import { ResponseBuilder } from '@geekmidas/constructs/endpoints';`,
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (needsServiceDiscovery) {
|
|
105
|
+
imports.push(`import { ServiceDiscovery } from '@geekmidas/services';`);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (needsEvents) {
|
|
109
|
+
imports.push(
|
|
110
|
+
`import { publishConstructEvents } from '@geekmidas/constructs/endpoints';`,
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (needsAudits) {
|
|
115
|
+
imports.push(
|
|
116
|
+
`import { createAuditContext, withAuditableEndpointTransaction } from '@geekmidas/constructs/endpoints';`,
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (needsRateLimit) {
|
|
121
|
+
imports.push(`import { createError } from '@geekmidas/errors';`);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (needsRls) {
|
|
125
|
+
imports.push(
|
|
126
|
+
`import { withRlsContext, extractRlsContext } from '@geekmidas/constructs/endpoints';`,
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return imports.join('\n');
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Generate reusable validator middleware factories
|
|
135
|
+
*/
|
|
136
|
+
export function generateValidatorFactories(
|
|
137
|
+
analyses: EndpointAnalysis[],
|
|
138
|
+
): string {
|
|
139
|
+
const needsBody = analyses.some((a) => a.features.hasBodyValidation);
|
|
140
|
+
const needsQuery = analyses.some((a) => a.features.hasQueryValidation);
|
|
141
|
+
const needsParams = analyses.some((a) => a.features.hasParamValidation);
|
|
142
|
+
|
|
143
|
+
if (!needsBody && !needsQuery && !needsParams) {
|
|
144
|
+
return '';
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const factories: string[] = [];
|
|
148
|
+
|
|
149
|
+
if (needsBody) {
|
|
150
|
+
factories.push(`
|
|
151
|
+
const validateBody = (endpoint: any) =>
|
|
152
|
+
validator('json', async (value, c) => {
|
|
153
|
+
if (!endpoint.input?.body) return undefined;
|
|
154
|
+
const parsed = await Endpoint.validate(endpoint.input.body, value);
|
|
155
|
+
if (parsed.issues) return c.json(parsed.issues, 422);
|
|
156
|
+
return parsed.value;
|
|
157
|
+
});`);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (needsQuery) {
|
|
161
|
+
factories.push(`
|
|
162
|
+
const validateQuery = (endpoint: any) =>
|
|
163
|
+
validator('query', async (_, c) => {
|
|
164
|
+
if (!endpoint.input?.query) return undefined;
|
|
165
|
+
const rawQuery = Object.fromEntries(new URL(c.req.url).searchParams);
|
|
166
|
+
const parsed = await Endpoint.validate(endpoint.input.query, rawQuery);
|
|
167
|
+
if (parsed.issues) return c.json(parsed.issues, 422);
|
|
168
|
+
return parsed.value;
|
|
169
|
+
});`);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
if (needsParams) {
|
|
173
|
+
factories.push(`
|
|
174
|
+
const validateParams = (endpoint: any) =>
|
|
175
|
+
validator('param', async (params, c) => {
|
|
176
|
+
if (!endpoint.input?.params) return undefined;
|
|
177
|
+
const parsed = await Endpoint.validate(endpoint.input.params, params);
|
|
178
|
+
if (parsed.issues) return c.json(parsed.issues, 422);
|
|
179
|
+
return parsed.value;
|
|
180
|
+
});`);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
return factories.join('\n');
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Generate validator references for an endpoint
|
|
188
|
+
*/
|
|
189
|
+
function generateValidators(
|
|
190
|
+
exportName: string,
|
|
191
|
+
features: EndpointFeatures,
|
|
192
|
+
): string {
|
|
193
|
+
const validators: string[] = [];
|
|
194
|
+
|
|
195
|
+
if (features.hasBodyValidation) {
|
|
196
|
+
validators.push(`validateBody(${exportName})`);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
if (features.hasQueryValidation) {
|
|
200
|
+
validators.push(`validateQuery(${exportName})`);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
if (features.hasParamValidation) {
|
|
204
|
+
validators.push(`validateParams(${exportName})`);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Add trailing comma if there are validators (needed before the handler function)
|
|
208
|
+
return validators.length > 0 ? `\n ${validators.join(',\n ')},` : '';
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Generate a minimal handler (near-raw-Hono performance)
|
|
213
|
+
*
|
|
214
|
+
* Used for: Health checks, public endpoints with no services
|
|
215
|
+
*/
|
|
216
|
+
export function generateMinimalHandler(analysis: EndpointAnalysis): string {
|
|
217
|
+
const { exportName, features } = analysis;
|
|
218
|
+
const method = analysis.method.toLowerCase();
|
|
219
|
+
|
|
220
|
+
const validators = generateValidators(exportName, features);
|
|
221
|
+
const hasValidators = validators.length > 0;
|
|
222
|
+
|
|
223
|
+
// For truly minimal endpoints (no validation), generate inline handler
|
|
224
|
+
if (!hasValidators && !features.hasOutputValidation) {
|
|
225
|
+
return `
|
|
226
|
+
// Minimal handler: ${analysis.route} (${analysis.method})
|
|
227
|
+
app.${method}('${analysis.route}', async (c) => {
|
|
228
|
+
const result = await ${exportName}.handler(
|
|
229
|
+
{
|
|
230
|
+
services: {},
|
|
231
|
+
logger,
|
|
232
|
+
body: undefined,
|
|
233
|
+
query: undefined,
|
|
234
|
+
params: undefined,
|
|
235
|
+
session: undefined,
|
|
236
|
+
header: Endpoint.createHeaders(c.req.header()),
|
|
237
|
+
cookie: Endpoint.createCookies(c.req.header().cookie),
|
|
238
|
+
auditor: undefined,
|
|
239
|
+
db: undefined,
|
|
240
|
+
} as any,
|
|
241
|
+
{ getMetadata: () => ({}) } as any,
|
|
242
|
+
);
|
|
243
|
+
return c.json(result, ${exportName}.status as any);
|
|
244
|
+
});`;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// With validation but still minimal
|
|
248
|
+
return `
|
|
249
|
+
// Minimal handler with validation: ${analysis.route} (${analysis.method})
|
|
250
|
+
app.${method}('${analysis.route}',${validators}
|
|
251
|
+
async (c) => {
|
|
252
|
+
const result = await ${exportName}.handler(
|
|
253
|
+
{
|
|
254
|
+
services: {},
|
|
255
|
+
logger,
|
|
256
|
+
body: ${features.hasBodyValidation ? "(c.req.valid as any)('json')" : 'undefined'},
|
|
257
|
+
query: ${features.hasQueryValidation ? "(c.req.valid as any)('query')" : 'undefined'},
|
|
258
|
+
params: ${features.hasParamValidation ? "(c.req.valid as any)('param')" : 'undefined'},
|
|
259
|
+
session: undefined,
|
|
260
|
+
header: Endpoint.createHeaders(c.req.header()),
|
|
261
|
+
cookie: Endpoint.createCookies(c.req.header().cookie),
|
|
262
|
+
auditor: undefined,
|
|
263
|
+
db: undefined,
|
|
264
|
+
} as any,
|
|
265
|
+
{ getMetadata: () => ({}) } as any,
|
|
266
|
+
);
|
|
267
|
+
${
|
|
268
|
+
features.hasOutputValidation
|
|
269
|
+
? `const output = await ${exportName}.parseOutput(result);
|
|
270
|
+
return c.json(output, ${exportName}.status as any);`
|
|
271
|
+
: `return c.json(result, ${exportName}.status as any);`
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
);`;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Generate a standard handler (auth and/or services)
|
|
279
|
+
*
|
|
280
|
+
* Used for: Authenticated endpoints, endpoints with services
|
|
281
|
+
*/
|
|
282
|
+
export function generateStandardHandler(analysis: EndpointAnalysis): string {
|
|
283
|
+
const { exportName, features } = analysis;
|
|
284
|
+
const method = analysis.method.toLowerCase();
|
|
285
|
+
|
|
286
|
+
const validators = generateValidators(exportName, features);
|
|
287
|
+
|
|
288
|
+
// Build service resolution code
|
|
289
|
+
let serviceResolution = '';
|
|
290
|
+
if (features.hasServices || features.hasDatabase) {
|
|
291
|
+
serviceResolution = `
|
|
292
|
+
const services = await serviceDiscovery.register(${exportName}.services);
|
|
293
|
+
${
|
|
294
|
+
features.hasDatabase
|
|
295
|
+
? `const db = ${exportName}.databaseService
|
|
296
|
+
? (await serviceDiscovery.register([${exportName}.databaseService]) as any)[${exportName}.databaseService.serviceName]
|
|
297
|
+
: undefined;`
|
|
298
|
+
: 'const db = undefined;'
|
|
299
|
+
}`;
|
|
300
|
+
} else {
|
|
301
|
+
serviceResolution = `
|
|
302
|
+
const services = {};
|
|
303
|
+
const db = undefined;`;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// Build auth code
|
|
307
|
+
let authCode = '';
|
|
308
|
+
if (features.hasAuth) {
|
|
309
|
+
authCode = `
|
|
310
|
+
// Authentication
|
|
311
|
+
const session = await ${exportName}.getSession({
|
|
312
|
+
services,
|
|
313
|
+
logger,
|
|
314
|
+
header,
|
|
315
|
+
cookie,
|
|
316
|
+
...(db !== undefined && { db }),
|
|
317
|
+
} as any);
|
|
318
|
+
|
|
319
|
+
const isAuthorized = await ${exportName}.authorize({
|
|
320
|
+
header,
|
|
321
|
+
cookie,
|
|
322
|
+
services,
|
|
323
|
+
logger,
|
|
324
|
+
session,
|
|
325
|
+
} as any);
|
|
326
|
+
|
|
327
|
+
if (!isAuthorized) {
|
|
328
|
+
return c.json({ error: 'Unauthorized' }, 401);
|
|
329
|
+
}`;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// Build event publishing code
|
|
333
|
+
let eventCode = '';
|
|
334
|
+
if (features.hasEvents) {
|
|
335
|
+
eventCode = `
|
|
336
|
+
// Publish events on success
|
|
337
|
+
if (Endpoint.isSuccessStatus(${exportName}.status)) {
|
|
338
|
+
await (publishConstructEvents as any)(
|
|
339
|
+
${exportName},
|
|
340
|
+
result,
|
|
341
|
+
serviceDiscovery,
|
|
342
|
+
logger,
|
|
343
|
+
);
|
|
344
|
+
}`;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
return `
|
|
348
|
+
// Standard handler: ${analysis.route} (${analysis.method})
|
|
349
|
+
app.${method}('${analysis.route}',${validators}
|
|
350
|
+
async (c) => {
|
|
351
|
+
const headerValues = c.req.header();
|
|
352
|
+
const header = Endpoint.createHeaders(headerValues);
|
|
353
|
+
const cookie = Endpoint.createCookies(headerValues.cookie);
|
|
354
|
+
${serviceResolution}
|
|
355
|
+
${authCode}
|
|
356
|
+
|
|
357
|
+
const responseBuilder = new ResponseBuilder();
|
|
358
|
+
const result = await ${exportName}.handler(
|
|
359
|
+
{
|
|
360
|
+
services,
|
|
361
|
+
logger,
|
|
362
|
+
body: ${features.hasBodyValidation ? "(c.req.valid as any)('json')" : 'undefined'},
|
|
363
|
+
query: ${features.hasQueryValidation ? "(c.req.valid as any)('query')" : 'undefined'},
|
|
364
|
+
params: ${features.hasParamValidation ? "(c.req.valid as any)('param')" : 'undefined'},
|
|
365
|
+
session: ${features.hasAuth ? 'session' : 'undefined'},
|
|
366
|
+
header,
|
|
367
|
+
cookie,
|
|
368
|
+
auditor: undefined,
|
|
369
|
+
db,
|
|
370
|
+
} as any,
|
|
371
|
+
responseBuilder,
|
|
372
|
+
);
|
|
373
|
+
|
|
374
|
+
let data = result;
|
|
375
|
+
let metadata = responseBuilder.getMetadata();
|
|
376
|
+
|
|
377
|
+
if (Endpoint.hasMetadata(result)) {
|
|
378
|
+
data = result.data;
|
|
379
|
+
metadata = result.metadata;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
${
|
|
383
|
+
features.hasOutputValidation
|
|
384
|
+
? `const output = ${exportName}.outputSchema
|
|
385
|
+
? await ${exportName}.parseOutput(data)
|
|
386
|
+
: data;`
|
|
387
|
+
: 'const output = data;'
|
|
388
|
+
}
|
|
389
|
+
${eventCode}
|
|
390
|
+
|
|
391
|
+
const status = (metadata.status ?? ${exportName}.status) as any;
|
|
392
|
+
return c.json(output, status);
|
|
393
|
+
}
|
|
394
|
+
);`;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
/**
|
|
398
|
+
* Generate setup function that uses HonoEndpoint.addRoutes for full-featured endpoints
|
|
399
|
+
* but generates optimized inline handlers for minimal/standard endpoints
|
|
400
|
+
*/
|
|
401
|
+
export function generateOptimizedSetupFunction(
|
|
402
|
+
analyses: EndpointAnalysis[],
|
|
403
|
+
_allExportNames: string[],
|
|
404
|
+
): string {
|
|
405
|
+
const minimalEndpoints = analyses.filter((a) => a.tier === 'minimal');
|
|
406
|
+
const standardEndpoints = analyses.filter((a) => a.tier === 'standard');
|
|
407
|
+
const fullEndpoints = analyses.filter((a) => a.tier === 'full');
|
|
408
|
+
|
|
409
|
+
// Generate inline handlers for minimal and standard endpoints
|
|
410
|
+
const minimalHandlers = minimalEndpoints
|
|
411
|
+
.map((a) => generateMinimalHandler(a))
|
|
412
|
+
.join('\n');
|
|
413
|
+
|
|
414
|
+
const standardHandlers = standardEndpoints
|
|
415
|
+
.map((a) => generateStandardHandler(a))
|
|
416
|
+
.join('\n');
|
|
417
|
+
|
|
418
|
+
// Full endpoints use HonoEndpoint.addRoutes
|
|
419
|
+
const fullEndpointNames = fullEndpoints.map((a) => a.exportName);
|
|
420
|
+
|
|
421
|
+
const fullEndpointsSetup =
|
|
422
|
+
fullEndpointNames.length > 0
|
|
423
|
+
? `
|
|
424
|
+
// Full-featured endpoints use HonoEndpoint.addRoutes
|
|
425
|
+
const fullEndpoints = [${fullEndpointNames.join(', ')}];
|
|
426
|
+
HonoEndpoint.addRoutes(fullEndpoints, serviceDiscovery, app, openApiOptions);`
|
|
427
|
+
: '';
|
|
428
|
+
|
|
429
|
+
// Add HonoEndpoint import only if needed
|
|
430
|
+
const honoEndpointImport =
|
|
431
|
+
fullEndpointNames.length > 0
|
|
432
|
+
? `import { HonoEndpoint } from '@geekmidas/constructs/hono';`
|
|
433
|
+
: '';
|
|
434
|
+
|
|
435
|
+
// Only generate openApiOptions if we have full endpoints that need it
|
|
436
|
+
const openApiOptionsDecl =
|
|
437
|
+
fullEndpointNames.length > 0
|
|
438
|
+
? `
|
|
439
|
+
const openApiOptions: any = enableOpenApi ? {
|
|
440
|
+
docsPath: '/__docs',
|
|
441
|
+
openApiOptions: {
|
|
442
|
+
title: 'API Documentation',
|
|
443
|
+
version: '1.0.0',
|
|
444
|
+
description: 'Generated API documentation'
|
|
445
|
+
}
|
|
446
|
+
} : { docsPath: false };`
|
|
447
|
+
: '';
|
|
448
|
+
|
|
449
|
+
return `${honoEndpointImport}
|
|
450
|
+
|
|
451
|
+
export async function setupEndpoints(
|
|
452
|
+
app: Hono,
|
|
453
|
+
envParser: EnvironmentParser<any>,
|
|
454
|
+
logger: Logger,
|
|
455
|
+
enableOpenApi: boolean = false,
|
|
456
|
+
): Promise<void> {
|
|
457
|
+
const serviceDiscovery = ServiceDiscovery.getInstance(envParser);
|
|
458
|
+
${openApiOptionsDecl}
|
|
459
|
+
|
|
460
|
+
// ============================================
|
|
461
|
+
// Minimal handlers (${minimalEndpoints.length} endpoints)
|
|
462
|
+
// Near-raw-Hono performance
|
|
463
|
+
// ============================================
|
|
464
|
+
${minimalHandlers}
|
|
465
|
+
|
|
466
|
+
// ============================================
|
|
467
|
+
// Standard handlers (${standardEndpoints.length} endpoints)
|
|
468
|
+
// Auth and/or services
|
|
469
|
+
// ============================================
|
|
470
|
+
${standardHandlers}
|
|
471
|
+
${fullEndpointsSetup}
|
|
472
|
+
|
|
473
|
+
// Add Swagger UI if OpenAPI is enabled
|
|
474
|
+
if (enableOpenApi) {
|
|
475
|
+
try {
|
|
476
|
+
const { swaggerUI } = await import('@hono/swagger-ui');
|
|
477
|
+
app.get('/__docs/ui', swaggerUI({ url: '/__docs' }));
|
|
478
|
+
} catch {
|
|
479
|
+
// @hono/swagger-ui not installed, skip Swagger UI
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
}`;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
/**
|
|
486
|
+
* Generate complete optimized endpoints file
|
|
487
|
+
*/
|
|
488
|
+
export function generateOptimizedEndpointsFile(
|
|
489
|
+
analyses: EndpointAnalysis[],
|
|
490
|
+
endpointImports: string,
|
|
491
|
+
allExportNames: string[],
|
|
492
|
+
): string {
|
|
493
|
+
const imports = generateOptimizedImports(analyses);
|
|
494
|
+
const validatorFactories = generateValidatorFactories(analyses);
|
|
495
|
+
const setupFunction = generateOptimizedSetupFunction(
|
|
496
|
+
analyses,
|
|
497
|
+
allExportNames,
|
|
498
|
+
);
|
|
499
|
+
|
|
500
|
+
return `/**
|
|
501
|
+
* Generated optimized endpoints file
|
|
502
|
+
*
|
|
503
|
+
* Build-time optimization tiers:
|
|
504
|
+
* - minimal: ${analyses.filter((a) => a.tier === 'minimal').length} endpoints (near-raw-Hono)
|
|
505
|
+
* - standard: ${analyses.filter((a) => a.tier === 'standard').length} endpoints (auth/services)
|
|
506
|
+
* - full: ${analyses.filter((a) => a.tier === 'full').length} endpoints (audits/rls/rate-limit)
|
|
507
|
+
*/
|
|
508
|
+
${imports}
|
|
509
|
+
${endpointImports}
|
|
510
|
+
${validatorFactories}
|
|
511
|
+
|
|
512
|
+
${setupFunction}
|
|
513
|
+
`;
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
// ============================================================================
|
|
517
|
+
// Multi-File Generation (Split by Tier)
|
|
518
|
+
// ============================================================================
|
|
519
|
+
|
|
520
|
+
/**
|
|
521
|
+
* Generate validators.ts - Shared validator middleware factories
|
|
522
|
+
*/
|
|
523
|
+
function generateValidatorsFile(analyses: EndpointAnalysis[]): string {
|
|
524
|
+
const needsBody = analyses.some((a) => a.features.hasBodyValidation);
|
|
525
|
+
const needsQuery = analyses.some((a) => a.features.hasQueryValidation);
|
|
526
|
+
const needsParams = analyses.some((a) => a.features.hasParamValidation);
|
|
527
|
+
|
|
528
|
+
if (!needsBody && !needsQuery && !needsParams) {
|
|
529
|
+
return `// No validators needed for this build\nexport {};\n`;
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
const exports: string[] = [];
|
|
533
|
+
const factories: string[] = [];
|
|
534
|
+
|
|
535
|
+
if (needsBody) {
|
|
536
|
+
exports.push('validateBody');
|
|
537
|
+
factories.push(`
|
|
538
|
+
export const validateBody = (endpoint: any) =>
|
|
539
|
+
validator('json', async (value, c) => {
|
|
540
|
+
if (!endpoint.input?.body) return undefined;
|
|
541
|
+
const parsed = await Endpoint.validate(endpoint.input.body, value);
|
|
542
|
+
if (parsed.issues) return c.json(parsed.issues, 422);
|
|
543
|
+
return parsed.value;
|
|
544
|
+
});`);
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
if (needsQuery) {
|
|
548
|
+
exports.push('validateQuery');
|
|
549
|
+
factories.push(`
|
|
550
|
+
export const validateQuery = (endpoint: any) =>
|
|
551
|
+
validator('query', async (_, c) => {
|
|
552
|
+
if (!endpoint.input?.query) return undefined;
|
|
553
|
+
const rawQuery = Object.fromEntries(new URL(c.req.url).searchParams);
|
|
554
|
+
const parsed = await Endpoint.validate(endpoint.input.query, rawQuery);
|
|
555
|
+
if (parsed.issues) return c.json(parsed.issues, 422);
|
|
556
|
+
return parsed.value;
|
|
557
|
+
});`);
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
if (needsParams) {
|
|
561
|
+
exports.push('validateParams');
|
|
562
|
+
factories.push(`
|
|
563
|
+
export const validateParams = (endpoint: any) =>
|
|
564
|
+
validator('param', async (params, c) => {
|
|
565
|
+
if (!endpoint.input?.params) return undefined;
|
|
566
|
+
const parsed = await Endpoint.validate(endpoint.input.params, params);
|
|
567
|
+
if (parsed.issues) return c.json(parsed.issues, 422);
|
|
568
|
+
return parsed.value;
|
|
569
|
+
});`);
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
return `/**
|
|
573
|
+
* Generated validator middleware factories
|
|
574
|
+
* Shared across all endpoint tiers that need validation
|
|
575
|
+
*/
|
|
576
|
+
import { validator } from 'hono/validator';
|
|
577
|
+
import { Endpoint } from '@geekmidas/constructs/endpoints';
|
|
578
|
+
${factories.join('\n')}
|
|
579
|
+
`;
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
/**
|
|
583
|
+
* Generate minimal.ts - Minimal tier handlers
|
|
584
|
+
*/
|
|
585
|
+
function generateMinimalFile(
|
|
586
|
+
analyses: EndpointAnalysis[],
|
|
587
|
+
endpointImports: EndpointImportInfo[],
|
|
588
|
+
): string {
|
|
589
|
+
const minimalEndpoints = analyses.filter((a) => a.tier === 'minimal');
|
|
590
|
+
|
|
591
|
+
if (minimalEndpoints.length === 0) {
|
|
592
|
+
return `// No minimal-tier endpoints in this build
|
|
593
|
+
import type { Hono } from 'hono';
|
|
594
|
+
import type { Logger } from '@geekmidas/logger';
|
|
595
|
+
|
|
596
|
+
export function setupMinimalEndpoints(
|
|
597
|
+
_app: Hono,
|
|
598
|
+
_logger: Logger,
|
|
599
|
+
): void {
|
|
600
|
+
// No minimal endpoints
|
|
601
|
+
}
|
|
602
|
+
`;
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
const minimalExportNames = minimalEndpoints.map((a) => a.exportName);
|
|
606
|
+
const relevantImports = endpointImports.filter((i) =>
|
|
607
|
+
minimalExportNames.includes(i.exportName),
|
|
608
|
+
);
|
|
609
|
+
|
|
610
|
+
const importStatements = relevantImports
|
|
611
|
+
.map((i) => `import { ${i.exportName} } from '${i.importPath}';`)
|
|
612
|
+
.join('\n');
|
|
613
|
+
|
|
614
|
+
const needsValidators = minimalEndpoints.some(
|
|
615
|
+
(a) =>
|
|
616
|
+
a.features.hasBodyValidation ||
|
|
617
|
+
a.features.hasQueryValidation ||
|
|
618
|
+
a.features.hasParamValidation,
|
|
619
|
+
);
|
|
620
|
+
|
|
621
|
+
const validatorImport = needsValidators
|
|
622
|
+
? generateValidatorImports(minimalEndpoints)
|
|
623
|
+
: '';
|
|
624
|
+
|
|
625
|
+
const handlers = minimalEndpoints
|
|
626
|
+
.map((a) => generateMinimalHandler(a))
|
|
627
|
+
.join('\n');
|
|
628
|
+
|
|
629
|
+
return `/**
|
|
630
|
+
* Minimal-tier endpoint handlers (${minimalEndpoints.length} endpoints)
|
|
631
|
+
* Near-raw-Hono performance - no auth, no services, no complex features
|
|
632
|
+
*/
|
|
633
|
+
import type { Hono } from 'hono';
|
|
634
|
+
import type { Logger } from '@geekmidas/logger';
|
|
635
|
+
import { Endpoint } from '@geekmidas/constructs/endpoints';
|
|
636
|
+
${validatorImport}
|
|
637
|
+
${importStatements}
|
|
638
|
+
|
|
639
|
+
export function setupMinimalEndpoints(
|
|
640
|
+
app: Hono,
|
|
641
|
+
logger: Logger,
|
|
642
|
+
): void {
|
|
643
|
+
${handlers}
|
|
644
|
+
}
|
|
645
|
+
`;
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
/**
|
|
649
|
+
* Generate standard.ts - Standard tier handlers
|
|
650
|
+
*/
|
|
651
|
+
function generateStandardFile(
|
|
652
|
+
analyses: EndpointAnalysis[],
|
|
653
|
+
endpointImports: EndpointImportInfo[],
|
|
654
|
+
): string {
|
|
655
|
+
const standardEndpoints = analyses.filter((a) => a.tier === 'standard');
|
|
656
|
+
|
|
657
|
+
if (standardEndpoints.length === 0) {
|
|
658
|
+
return `// No standard-tier endpoints in this build
|
|
659
|
+
import type { Hono } from 'hono';
|
|
660
|
+
import type { Logger } from '@geekmidas/logger';
|
|
661
|
+
import type { ServiceDiscovery } from '@geekmidas/services';
|
|
662
|
+
|
|
663
|
+
export function setupStandardEndpoints(
|
|
664
|
+
_app: Hono,
|
|
665
|
+
_serviceDiscovery: ServiceDiscovery<any, any>,
|
|
666
|
+
_logger: Logger,
|
|
667
|
+
): void {
|
|
668
|
+
// No standard endpoints
|
|
669
|
+
}
|
|
670
|
+
`;
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
const standardExportNames = standardEndpoints.map((a) => a.exportName);
|
|
674
|
+
const relevantImports = endpointImports.filter((i) =>
|
|
675
|
+
standardExportNames.includes(i.exportName),
|
|
676
|
+
);
|
|
677
|
+
|
|
678
|
+
const importStatements = relevantImports
|
|
679
|
+
.map((i) => `import { ${i.exportName} } from '${i.importPath}';`)
|
|
680
|
+
.join('\n');
|
|
681
|
+
|
|
682
|
+
const needsValidators = standardEndpoints.some(
|
|
683
|
+
(a) =>
|
|
684
|
+
a.features.hasBodyValidation ||
|
|
685
|
+
a.features.hasQueryValidation ||
|
|
686
|
+
a.features.hasParamValidation,
|
|
687
|
+
);
|
|
688
|
+
|
|
689
|
+
const validatorImport = needsValidators
|
|
690
|
+
? generateValidatorImports(standardEndpoints)
|
|
691
|
+
: '';
|
|
692
|
+
|
|
693
|
+
const needsEvents = standardEndpoints.some((a) => a.features.hasEvents);
|
|
694
|
+
const eventsImport = needsEvents
|
|
695
|
+
? `import { publishConstructEvents } from '@geekmidas/constructs/endpoints';`
|
|
696
|
+
: '';
|
|
697
|
+
|
|
698
|
+
const handlers = standardEndpoints
|
|
699
|
+
.map((a) => generateStandardHandler(a))
|
|
700
|
+
.join('\n');
|
|
701
|
+
|
|
702
|
+
return `/**
|
|
703
|
+
* Standard-tier endpoint handlers (${standardEndpoints.length} endpoints)
|
|
704
|
+
* Auth and/or services enabled, but no complex features like audits/RLS
|
|
705
|
+
*/
|
|
706
|
+
import type { Hono } from 'hono';
|
|
707
|
+
import type { Logger } from '@geekmidas/logger';
|
|
708
|
+
import type { ServiceDiscovery } from '@geekmidas/services';
|
|
709
|
+
import { Endpoint, ResponseBuilder } from '@geekmidas/constructs/endpoints';
|
|
710
|
+
${eventsImport}
|
|
711
|
+
${validatorImport}
|
|
712
|
+
${importStatements}
|
|
713
|
+
|
|
714
|
+
export function setupStandardEndpoints(
|
|
715
|
+
app: Hono,
|
|
716
|
+
serviceDiscovery: ServiceDiscovery<any, any>,
|
|
717
|
+
logger: Logger,
|
|
718
|
+
): void {
|
|
719
|
+
${handlers}
|
|
720
|
+
}
|
|
721
|
+
`;
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
/**
|
|
725
|
+
* Generate full.ts - Full tier handlers (uses HonoEndpoint.addRoutes)
|
|
726
|
+
*/
|
|
727
|
+
function generateFullFile(
|
|
728
|
+
analyses: EndpointAnalysis[],
|
|
729
|
+
endpointImports: EndpointImportInfo[],
|
|
730
|
+
): string {
|
|
731
|
+
const fullEndpoints = analyses.filter((a) => a.tier === 'full');
|
|
732
|
+
|
|
733
|
+
if (fullEndpoints.length === 0) {
|
|
734
|
+
return `// No full-tier endpoints in this build
|
|
735
|
+
import type { Hono } from 'hono';
|
|
736
|
+
import type { ServiceDiscovery } from '@geekmidas/services';
|
|
737
|
+
|
|
738
|
+
export function setupFullEndpoints(
|
|
739
|
+
_app: Hono,
|
|
740
|
+
_serviceDiscovery: ServiceDiscovery<any, any>,
|
|
741
|
+
_enableOpenApi: boolean,
|
|
742
|
+
): void {
|
|
743
|
+
// No full endpoints
|
|
744
|
+
}
|
|
745
|
+
`;
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
const fullExportNames = fullEndpoints.map((a) => a.exportName);
|
|
749
|
+
const relevantImports = endpointImports.filter((i) =>
|
|
750
|
+
fullExportNames.includes(i.exportName),
|
|
751
|
+
);
|
|
752
|
+
|
|
753
|
+
const importStatements = relevantImports
|
|
754
|
+
.map((i) => `import { ${i.exportName} } from '${i.importPath}';`)
|
|
755
|
+
.join('\n');
|
|
756
|
+
|
|
757
|
+
return `/**
|
|
758
|
+
* Full-tier endpoint handlers (${fullEndpoints.length} endpoints)
|
|
759
|
+
* Complex features: audits, RLS, rate limiting
|
|
760
|
+
* Uses HonoEndpoint.addRoutes for full feature support
|
|
761
|
+
*/
|
|
762
|
+
import type { Hono } from 'hono';
|
|
763
|
+
import type { ServiceDiscovery } from '@geekmidas/services';
|
|
764
|
+
import { HonoEndpoint } from '@geekmidas/constructs/hono';
|
|
765
|
+
${importStatements}
|
|
766
|
+
|
|
767
|
+
const fullEndpoints = [${fullExportNames.join(', ')}];
|
|
768
|
+
|
|
769
|
+
export function setupFullEndpoints(
|
|
770
|
+
app: Hono,
|
|
771
|
+
serviceDiscovery: ServiceDiscovery<any, any>,
|
|
772
|
+
enableOpenApi: boolean,
|
|
773
|
+
): void {
|
|
774
|
+
const openApiOptions: any = enableOpenApi ? {
|
|
775
|
+
docsPath: '/__docs',
|
|
776
|
+
openApiOptions: {
|
|
777
|
+
title: 'API Documentation',
|
|
778
|
+
version: '1.0.0',
|
|
779
|
+
description: 'Generated API documentation'
|
|
780
|
+
}
|
|
781
|
+
} : { docsPath: false };
|
|
782
|
+
|
|
783
|
+
HonoEndpoint.addRoutes(fullEndpoints as any, serviceDiscovery, app, openApiOptions);
|
|
784
|
+
}
|
|
785
|
+
`;
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
/**
|
|
789
|
+
* Generate index.ts - Main entry point
|
|
790
|
+
*/
|
|
791
|
+
function generateIndexFile(analyses: EndpointAnalysis[]): string {
|
|
792
|
+
const minimalCount = analyses.filter((a) => a.tier === 'minimal').length;
|
|
793
|
+
const standardCount = analyses.filter((a) => a.tier === 'standard').length;
|
|
794
|
+
const fullCount = analyses.filter((a) => a.tier === 'full').length;
|
|
795
|
+
|
|
796
|
+
return `/**
|
|
797
|
+
* Generated optimized endpoints
|
|
798
|
+
*
|
|
799
|
+
* Build-time optimization tiers:
|
|
800
|
+
* - minimal: ${minimalCount} endpoints (near-raw-Hono)
|
|
801
|
+
* - standard: ${standardCount} endpoints (auth/services)
|
|
802
|
+
* - full: ${fullCount} endpoints (audits/rls/rate-limit)
|
|
803
|
+
*/
|
|
804
|
+
import type { EnvironmentParser } from '@geekmidas/envkit';
|
|
805
|
+
import type { Logger } from '@geekmidas/logger';
|
|
806
|
+
import type { Hono } from 'hono';
|
|
807
|
+
import { ServiceDiscovery } from '@geekmidas/services';
|
|
808
|
+
import { setupMinimalEndpoints } from './minimal.js';
|
|
809
|
+
import { setupStandardEndpoints } from './standard.js';
|
|
810
|
+
import { setupFullEndpoints } from './full.js';
|
|
811
|
+
|
|
812
|
+
export async function setupEndpoints(
|
|
813
|
+
app: Hono,
|
|
814
|
+
envParser: EnvironmentParser<any>,
|
|
815
|
+
logger: Logger,
|
|
816
|
+
enableOpenApi: boolean = false,
|
|
817
|
+
): Promise<void> {
|
|
818
|
+
const serviceDiscovery = ServiceDiscovery.getInstance(envParser);
|
|
819
|
+
|
|
820
|
+
// Minimal handlers (${minimalCount} endpoints) - near-raw-Hono performance
|
|
821
|
+
setupMinimalEndpoints(app, logger);
|
|
822
|
+
|
|
823
|
+
// Standard handlers (${standardCount} endpoints) - auth/services
|
|
824
|
+
setupStandardEndpoints(app, serviceDiscovery, logger);
|
|
825
|
+
|
|
826
|
+
// Full handlers (${fullCount} endpoints) - audits/rls/rate-limit
|
|
827
|
+
setupFullEndpoints(app, serviceDiscovery, enableOpenApi);
|
|
828
|
+
|
|
829
|
+
// Add Swagger UI if OpenAPI is enabled
|
|
830
|
+
if (enableOpenApi) {
|
|
831
|
+
try {
|
|
832
|
+
const { swaggerUI } = await import('@hono/swagger-ui');
|
|
833
|
+
app.get('/__docs/ui', swaggerUI({ url: '/__docs' }));
|
|
834
|
+
} catch {
|
|
835
|
+
// @hono/swagger-ui not installed, skip Swagger UI
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
`;
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
/**
|
|
843
|
+
* Generate validator imports based on what's needed
|
|
844
|
+
*/
|
|
845
|
+
function generateValidatorImports(analyses: EndpointAnalysis[]): string {
|
|
846
|
+
const needsBody = analyses.some((a) => a.features.hasBodyValidation);
|
|
847
|
+
const needsQuery = analyses.some((a) => a.features.hasQueryValidation);
|
|
848
|
+
const needsParams = analyses.some((a) => a.features.hasParamValidation);
|
|
849
|
+
|
|
850
|
+
const imports: string[] = [];
|
|
851
|
+
if (needsBody) imports.push('validateBody');
|
|
852
|
+
if (needsQuery) imports.push('validateQuery');
|
|
853
|
+
if (needsParams) imports.push('validateParams');
|
|
854
|
+
|
|
855
|
+
if (imports.length === 0) return '';
|
|
856
|
+
return `import { ${imports.join(', ')} } from './validators.js';`;
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
/**
|
|
860
|
+
* Generate all endpoint files split by tier (flat structure)
|
|
861
|
+
*/
|
|
862
|
+
export function generateEndpointFilesByTier(
|
|
863
|
+
analyses: EndpointAnalysis[],
|
|
864
|
+
endpointImports: EndpointImportInfo[],
|
|
865
|
+
): GeneratedEndpointFiles {
|
|
866
|
+
return {
|
|
867
|
+
'validators.ts': generateValidatorsFile(analyses),
|
|
868
|
+
'minimal.ts': generateMinimalFile(analyses, endpointImports),
|
|
869
|
+
'standard.ts': generateStandardFile(analyses, endpointImports),
|
|
870
|
+
'full.ts': generateFullFile(analyses, endpointImports),
|
|
871
|
+
'index.ts': generateIndexFile(analyses),
|
|
872
|
+
};
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
// ============================================================================
|
|
876
|
+
// Per-Endpoint File Generation (Nested Folder Structure)
|
|
877
|
+
// ============================================================================
|
|
878
|
+
|
|
879
|
+
/**
|
|
880
|
+
* Generate a standalone minimal endpoint file
|
|
881
|
+
*/
|
|
882
|
+
function generateMinimalEndpointFile(
|
|
883
|
+
analysis: EndpointAnalysis,
|
|
884
|
+
endpointImport: EndpointImportInfo,
|
|
885
|
+
): string {
|
|
886
|
+
const { exportName, features } = analysis;
|
|
887
|
+
|
|
888
|
+
const needsValidators =
|
|
889
|
+
features.hasBodyValidation ||
|
|
890
|
+
features.hasQueryValidation ||
|
|
891
|
+
features.hasParamValidation;
|
|
892
|
+
|
|
893
|
+
const validatorImport = needsValidators
|
|
894
|
+
? generateValidatorImportsForEndpoint(analysis)
|
|
895
|
+
: '';
|
|
896
|
+
|
|
897
|
+
const handler = generateMinimalHandler(analysis);
|
|
898
|
+
|
|
899
|
+
return `/**
|
|
900
|
+
* Minimal endpoint: ${analysis.route} (${analysis.method})
|
|
901
|
+
* Near-raw-Hono performance
|
|
902
|
+
*/
|
|
903
|
+
import type { Hono } from 'hono';
|
|
904
|
+
import type { Logger } from '@geekmidas/logger';
|
|
905
|
+
import { Endpoint } from '@geekmidas/constructs/endpoints';
|
|
906
|
+
${validatorImport}
|
|
907
|
+
import { ${exportName} } from '${endpointImport.importPath}';
|
|
908
|
+
|
|
909
|
+
export function setup${capitalize(exportName)}(
|
|
910
|
+
app: Hono,
|
|
911
|
+
logger: Logger,
|
|
912
|
+
): void {
|
|
913
|
+
${handler}
|
|
914
|
+
}
|
|
915
|
+
`;
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
/**
|
|
919
|
+
* Generate a standalone standard endpoint file
|
|
920
|
+
*/
|
|
921
|
+
function generateStandardEndpointFile(
|
|
922
|
+
analysis: EndpointAnalysis,
|
|
923
|
+
endpointImport: EndpointImportInfo,
|
|
924
|
+
): string {
|
|
925
|
+
const { exportName, features } = analysis;
|
|
926
|
+
|
|
927
|
+
const needsValidators =
|
|
928
|
+
features.hasBodyValidation ||
|
|
929
|
+
features.hasQueryValidation ||
|
|
930
|
+
features.hasParamValidation;
|
|
931
|
+
|
|
932
|
+
const validatorImport = needsValidators
|
|
933
|
+
? generateValidatorImportsForEndpoint(analysis)
|
|
934
|
+
: '';
|
|
935
|
+
|
|
936
|
+
const eventsImport = features.hasEvents
|
|
937
|
+
? `import { publishConstructEvents } from '@geekmidas/constructs/endpoints';`
|
|
938
|
+
: '';
|
|
939
|
+
|
|
940
|
+
const handler = generateStandardHandler(analysis);
|
|
941
|
+
|
|
942
|
+
return `/**
|
|
943
|
+
* Standard endpoint: ${analysis.route} (${analysis.method})
|
|
944
|
+
* Auth and/or services enabled
|
|
945
|
+
*/
|
|
946
|
+
import type { Hono } from 'hono';
|
|
947
|
+
import type { Logger } from '@geekmidas/logger';
|
|
948
|
+
import type { ServiceDiscovery } from '@geekmidas/services';
|
|
949
|
+
import { Endpoint, ResponseBuilder } from '@geekmidas/constructs/endpoints';
|
|
950
|
+
${eventsImport}
|
|
951
|
+
${validatorImport}
|
|
952
|
+
import { ${exportName} } from '${endpointImport.importPath}';
|
|
953
|
+
|
|
954
|
+
export function setup${capitalize(exportName)}(
|
|
955
|
+
app: Hono,
|
|
956
|
+
serviceDiscovery: ServiceDiscovery<any, any>,
|
|
957
|
+
logger: Logger,
|
|
958
|
+
): void {
|
|
959
|
+
${handler}
|
|
960
|
+
}
|
|
961
|
+
`;
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
/**
|
|
965
|
+
* Generate a standalone full endpoint file
|
|
966
|
+
*/
|
|
967
|
+
function generateFullEndpointFile(
|
|
968
|
+
analysis: EndpointAnalysis,
|
|
969
|
+
endpointImport: EndpointImportInfo,
|
|
970
|
+
): string {
|
|
971
|
+
const { exportName } = analysis;
|
|
972
|
+
|
|
973
|
+
return `/**
|
|
974
|
+
* Full endpoint: ${analysis.route} (${analysis.method})
|
|
975
|
+
* Complex features: audits, RLS, rate limiting
|
|
976
|
+
*/
|
|
977
|
+
import type { Hono } from 'hono';
|
|
978
|
+
import type { ServiceDiscovery } from '@geekmidas/services';
|
|
979
|
+
import { HonoEndpoint } from '@geekmidas/constructs/hono';
|
|
980
|
+
import { ${exportName} } from '${endpointImport.importPath}';
|
|
981
|
+
|
|
982
|
+
export function setup${capitalize(exportName)}(
|
|
983
|
+
app: Hono,
|
|
984
|
+
serviceDiscovery: ServiceDiscovery<any, any>,
|
|
985
|
+
openApiOptions: any,
|
|
986
|
+
): void {
|
|
987
|
+
HonoEndpoint.addRoutes([${exportName}] as any, serviceDiscovery, app, openApiOptions);
|
|
988
|
+
}
|
|
989
|
+
`;
|
|
990
|
+
}
|
|
991
|
+
|
|
992
|
+
/**
|
|
993
|
+
* Generate tier index file that imports and calls all endpoint setup functions
|
|
994
|
+
*/
|
|
995
|
+
function generateTierIndexFile(
|
|
996
|
+
tier: 'minimal' | 'standard' | 'full',
|
|
997
|
+
analyses: EndpointAnalysis[],
|
|
998
|
+
): string {
|
|
999
|
+
const tierEndpoints = analyses.filter((a) => a.tier === tier);
|
|
1000
|
+
|
|
1001
|
+
if (tierEndpoints.length === 0) {
|
|
1002
|
+
if (tier === 'minimal') {
|
|
1003
|
+
return `// No minimal-tier endpoints in this build
|
|
1004
|
+
import type { Hono } from 'hono';
|
|
1005
|
+
import type { Logger } from '@geekmidas/logger';
|
|
1006
|
+
|
|
1007
|
+
export function setupMinimalEndpoints(
|
|
1008
|
+
_app: Hono,
|
|
1009
|
+
_logger: Logger,
|
|
1010
|
+
): void {
|
|
1011
|
+
// No minimal endpoints
|
|
1012
|
+
}
|
|
1013
|
+
`;
|
|
1014
|
+
}
|
|
1015
|
+
if (tier === 'standard') {
|
|
1016
|
+
return `// No standard-tier endpoints in this build
|
|
1017
|
+
import type { Hono } from 'hono';
|
|
1018
|
+
import type { Logger } from '@geekmidas/logger';
|
|
1019
|
+
import type { ServiceDiscovery } from '@geekmidas/services';
|
|
1020
|
+
|
|
1021
|
+
export function setupStandardEndpoints(
|
|
1022
|
+
_app: Hono,
|
|
1023
|
+
_serviceDiscovery: ServiceDiscovery<any, any>,
|
|
1024
|
+
_logger: Logger,
|
|
1025
|
+
): void {
|
|
1026
|
+
// No standard endpoints
|
|
1027
|
+
}
|
|
1028
|
+
`;
|
|
1029
|
+
}
|
|
1030
|
+
// full
|
|
1031
|
+
return `// No full-tier endpoints in this build
|
|
1032
|
+
import type { Hono } from 'hono';
|
|
1033
|
+
import type { ServiceDiscovery } from '@geekmidas/services';
|
|
1034
|
+
|
|
1035
|
+
export function setupFullEndpoints(
|
|
1036
|
+
_app: Hono,
|
|
1037
|
+
_serviceDiscovery: ServiceDiscovery<any, any>,
|
|
1038
|
+
_enableOpenApi: boolean,
|
|
1039
|
+
): void {
|
|
1040
|
+
// No full endpoints
|
|
1041
|
+
}
|
|
1042
|
+
`;
|
|
1043
|
+
}
|
|
1044
|
+
|
|
1045
|
+
const imports = tierEndpoints
|
|
1046
|
+
.map(
|
|
1047
|
+
(a) =>
|
|
1048
|
+
`import { setup${capitalize(a.exportName)} } from './${a.exportName}.js';`,
|
|
1049
|
+
)
|
|
1050
|
+
.join('\n');
|
|
1051
|
+
|
|
1052
|
+
if (tier === 'minimal') {
|
|
1053
|
+
const calls = tierEndpoints
|
|
1054
|
+
.map((a) => ` setup${capitalize(a.exportName)}(app, logger);`)
|
|
1055
|
+
.join('\n');
|
|
1056
|
+
|
|
1057
|
+
return `/**
|
|
1058
|
+
* Minimal-tier endpoint index (${tierEndpoints.length} endpoints)
|
|
1059
|
+
* Near-raw-Hono performance
|
|
1060
|
+
*/
|
|
1061
|
+
import type { Hono } from 'hono';
|
|
1062
|
+
import type { Logger } from '@geekmidas/logger';
|
|
1063
|
+
${imports}
|
|
1064
|
+
|
|
1065
|
+
export function setupMinimalEndpoints(
|
|
1066
|
+
app: Hono,
|
|
1067
|
+
logger: Logger,
|
|
1068
|
+
): void {
|
|
1069
|
+
${calls}
|
|
1070
|
+
}
|
|
1071
|
+
`;
|
|
1072
|
+
}
|
|
1073
|
+
|
|
1074
|
+
if (tier === 'standard') {
|
|
1075
|
+
const calls = tierEndpoints
|
|
1076
|
+
.map(
|
|
1077
|
+
(a) =>
|
|
1078
|
+
` setup${capitalize(a.exportName)}(app, serviceDiscovery, logger);`,
|
|
1079
|
+
)
|
|
1080
|
+
.join('\n');
|
|
1081
|
+
|
|
1082
|
+
return `/**
|
|
1083
|
+
* Standard-tier endpoint index (${tierEndpoints.length} endpoints)
|
|
1084
|
+
* Auth and/or services enabled
|
|
1085
|
+
*/
|
|
1086
|
+
import type { Hono } from 'hono';
|
|
1087
|
+
import type { Logger } from '@geekmidas/logger';
|
|
1088
|
+
import type { ServiceDiscovery } from '@geekmidas/services';
|
|
1089
|
+
${imports}
|
|
1090
|
+
|
|
1091
|
+
export function setupStandardEndpoints(
|
|
1092
|
+
app: Hono,
|
|
1093
|
+
serviceDiscovery: ServiceDiscovery<any, any>,
|
|
1094
|
+
logger: Logger,
|
|
1095
|
+
): void {
|
|
1096
|
+
${calls}
|
|
1097
|
+
}
|
|
1098
|
+
`;
|
|
1099
|
+
}
|
|
1100
|
+
|
|
1101
|
+
// full
|
|
1102
|
+
const calls = tierEndpoints
|
|
1103
|
+
.map(
|
|
1104
|
+
(a) =>
|
|
1105
|
+
` setup${capitalize(a.exportName)}(app, serviceDiscovery, openApiOptions);`,
|
|
1106
|
+
)
|
|
1107
|
+
.join('\n');
|
|
1108
|
+
|
|
1109
|
+
return `/**
|
|
1110
|
+
* Full-tier endpoint index (${tierEndpoints.length} endpoints)
|
|
1111
|
+
* Complex features: audits, RLS, rate limiting
|
|
1112
|
+
*/
|
|
1113
|
+
import type { Hono } from 'hono';
|
|
1114
|
+
import type { ServiceDiscovery } from '@geekmidas/services';
|
|
1115
|
+
${imports}
|
|
1116
|
+
|
|
1117
|
+
export function setupFullEndpoints(
|
|
1118
|
+
app: Hono,
|
|
1119
|
+
serviceDiscovery: ServiceDiscovery<any, any>,
|
|
1120
|
+
enableOpenApi: boolean,
|
|
1121
|
+
): void {
|
|
1122
|
+
const openApiOptions: any = enableOpenApi ? {
|
|
1123
|
+
docsPath: '/__docs',
|
|
1124
|
+
openApiOptions: {
|
|
1125
|
+
title: 'API Documentation',
|
|
1126
|
+
version: '1.0.0',
|
|
1127
|
+
description: 'Generated API documentation'
|
|
1128
|
+
}
|
|
1129
|
+
} : { docsPath: false };
|
|
1130
|
+
|
|
1131
|
+
${calls}
|
|
1132
|
+
}
|
|
1133
|
+
`;
|
|
1134
|
+
}
|
|
1135
|
+
|
|
1136
|
+
/**
|
|
1137
|
+
* Generate validator imports for a single endpoint
|
|
1138
|
+
*/
|
|
1139
|
+
function generateValidatorImportsForEndpoint(
|
|
1140
|
+
analysis: EndpointAnalysis,
|
|
1141
|
+
): string {
|
|
1142
|
+
const imports: string[] = [];
|
|
1143
|
+
if (analysis.features.hasBodyValidation) imports.push('validateBody');
|
|
1144
|
+
if (analysis.features.hasQueryValidation) imports.push('validateQuery');
|
|
1145
|
+
if (analysis.features.hasParamValidation) imports.push('validateParams');
|
|
1146
|
+
|
|
1147
|
+
if (imports.length === 0) return '';
|
|
1148
|
+
return `import { ${imports.join(', ')} } from '../validators.js';`;
|
|
1149
|
+
}
|
|
1150
|
+
|
|
1151
|
+
/**
|
|
1152
|
+
* Capitalize first letter
|
|
1153
|
+
*/
|
|
1154
|
+
function capitalize(str: string): string {
|
|
1155
|
+
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
1156
|
+
}
|
|
1157
|
+
|
|
1158
|
+
/**
|
|
1159
|
+
* Generate all endpoint files with nested folder structure (per-endpoint files)
|
|
1160
|
+
*/
|
|
1161
|
+
export function generateEndpointFilesNested(
|
|
1162
|
+
analyses: EndpointAnalysis[],
|
|
1163
|
+
endpointImports: EndpointImportInfo[],
|
|
1164
|
+
): GeneratedEndpointFilesNested {
|
|
1165
|
+
const files: GeneratedEndpointFilesNested = {
|
|
1166
|
+
'validators.ts': generateValidatorsFile(analyses),
|
|
1167
|
+
'minimal/index.ts': generateTierIndexFile('minimal', analyses),
|
|
1168
|
+
'standard/index.ts': generateTierIndexFile('standard', analyses),
|
|
1169
|
+
'full/index.ts': generateTierIndexFile('full', analyses),
|
|
1170
|
+
'index.ts': generateNestedIndexFile(analyses),
|
|
1171
|
+
};
|
|
1172
|
+
|
|
1173
|
+
// Generate individual endpoint files
|
|
1174
|
+
for (const analysis of analyses) {
|
|
1175
|
+
const endpointImport = endpointImports.find(
|
|
1176
|
+
(i) => i.exportName === analysis.exportName,
|
|
1177
|
+
);
|
|
1178
|
+
if (!endpointImport) continue;
|
|
1179
|
+
|
|
1180
|
+
const fileName = `${analysis.tier}/${analysis.exportName}.ts`;
|
|
1181
|
+
|
|
1182
|
+
switch (analysis.tier) {
|
|
1183
|
+
case 'minimal':
|
|
1184
|
+
files[fileName] = generateMinimalEndpointFile(analysis, endpointImport);
|
|
1185
|
+
break;
|
|
1186
|
+
case 'standard':
|
|
1187
|
+
files[fileName] = generateStandardEndpointFile(
|
|
1188
|
+
analysis,
|
|
1189
|
+
endpointImport,
|
|
1190
|
+
);
|
|
1191
|
+
break;
|
|
1192
|
+
case 'full':
|
|
1193
|
+
files[fileName] = generateFullEndpointFile(analysis, endpointImport);
|
|
1194
|
+
break;
|
|
1195
|
+
}
|
|
1196
|
+
}
|
|
1197
|
+
|
|
1198
|
+
return files;
|
|
1199
|
+
}
|
|
1200
|
+
|
|
1201
|
+
/**
|
|
1202
|
+
* Generate index.ts for nested structure
|
|
1203
|
+
*/
|
|
1204
|
+
function generateNestedIndexFile(analyses: EndpointAnalysis[]): string {
|
|
1205
|
+
const minimalCount = analyses.filter((a) => a.tier === 'minimal').length;
|
|
1206
|
+
const standardCount = analyses.filter((a) => a.tier === 'standard').length;
|
|
1207
|
+
const fullCount = analyses.filter((a) => a.tier === 'full').length;
|
|
1208
|
+
|
|
1209
|
+
return `/**
|
|
1210
|
+
* Generated optimized endpoints
|
|
1211
|
+
*
|
|
1212
|
+
* Build-time optimization tiers:
|
|
1213
|
+
* - minimal: ${minimalCount} endpoints (near-raw-Hono)
|
|
1214
|
+
* - standard: ${standardCount} endpoints (auth/services)
|
|
1215
|
+
* - full: ${fullCount} endpoints (audits/rls/rate-limit)
|
|
1216
|
+
*/
|
|
1217
|
+
import type { EnvironmentParser } from '@geekmidas/envkit';
|
|
1218
|
+
import type { Logger } from '@geekmidas/logger';
|
|
1219
|
+
import type { Hono } from 'hono';
|
|
1220
|
+
import { ServiceDiscovery } from '@geekmidas/services';
|
|
1221
|
+
import { setupMinimalEndpoints } from './minimal/index.js';
|
|
1222
|
+
import { setupStandardEndpoints } from './standard/index.js';
|
|
1223
|
+
import { setupFullEndpoints } from './full/index.js';
|
|
1224
|
+
|
|
1225
|
+
export async function setupEndpoints(
|
|
1226
|
+
app: Hono,
|
|
1227
|
+
envParser: EnvironmentParser<any>,
|
|
1228
|
+
logger: Logger,
|
|
1229
|
+
enableOpenApi: boolean = false,
|
|
1230
|
+
): Promise<void> {
|
|
1231
|
+
const serviceDiscovery = ServiceDiscovery.getInstance(envParser);
|
|
1232
|
+
|
|
1233
|
+
// Minimal handlers (${minimalCount} endpoints) - near-raw-Hono performance
|
|
1234
|
+
setupMinimalEndpoints(app, logger);
|
|
1235
|
+
|
|
1236
|
+
// Standard handlers (${standardCount} endpoints) - auth/services
|
|
1237
|
+
setupStandardEndpoints(app, serviceDiscovery, logger);
|
|
1238
|
+
|
|
1239
|
+
// Full handlers (${fullCount} endpoints) - audits/rls/rate-limit
|
|
1240
|
+
setupFullEndpoints(app, serviceDiscovery, enableOpenApi);
|
|
1241
|
+
|
|
1242
|
+
// Add Swagger UI if OpenAPI is enabled
|
|
1243
|
+
if (enableOpenApi) {
|
|
1244
|
+
try {
|
|
1245
|
+
const { swaggerUI } = await import('@hono/swagger-ui');
|
|
1246
|
+
app.get('/__docs/ui', swaggerUI({ url: '/__docs' }));
|
|
1247
|
+
} catch {
|
|
1248
|
+
// @hono/swagger-ui not installed, skip Swagger UI
|
|
1249
|
+
}
|
|
1250
|
+
}
|
|
1251
|
+
}
|
|
1252
|
+
`;
|
|
1253
|
+
}
|