@geekmidas/cli 10.0.0-alpha.69 → 10.0.0-alpha.71
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{LegacyStateStore-LS_uTO8D.cjs → LegacyStateStore-Dd5IwiyC.cjs} +2 -2
- package/dist/{LegacyStateStore-LS_uTO8D.cjs.map → LegacyStateStore-Dd5IwiyC.cjs.map} +1 -1
- package/dist/{LegacyStateStore-BE1Et8RS.mjs → LegacyStateStore-NSHEinQi.mjs} +2 -2
- package/dist/{LegacyStateStore-BE1Et8RS.mjs.map → LegacyStateStore-NSHEinQi.mjs.map} +1 -1
- package/dist/{LocalStateStore-CV6phNMZ.cjs → LocalStateStore-DTylG4T4.cjs} +2 -2
- package/dist/{LocalStateStore-CV6phNMZ.cjs.map → LocalStateStore-DTylG4T4.cjs.map} +1 -1
- package/dist/{LocalStateStore-DAnFc708.mjs → LocalStateStore-DUoG2wA_.mjs} +2 -2
- package/dist/{LocalStateStore-DAnFc708.mjs.map → LocalStateStore-DUoG2wA_.mjs.map} +1 -1
- package/dist/{OpenApiTsGenerator-CSaXs-dB.mjs → OpenApiTsGenerator-Bhr_XWWN.mjs} +57 -638
- package/dist/OpenApiTsGenerator-Bhr_XWWN.mjs.map +1 -0
- package/dist/{OpenApiTsGenerator-CxB8mqXz.cjs → OpenApiTsGenerator-DZZwHdDC.cjs} +57 -638
- package/dist/OpenApiTsGenerator-DZZwHdDC.cjs.map +1 -0
- package/dist/{S3StateStore-dA34LAYm.cjs → S3StateStore-D7hrU-sy.cjs} +2 -2
- package/dist/{S3StateStore-dA34LAYm.cjs.map → S3StateStore-D7hrU-sy.cjs.map} +1 -1
- package/dist/{S3StateStore-8kJEGnTh.mjs → S3StateStore-Dxob3PNA.mjs} +2 -2
- package/dist/{S3StateStore-8kJEGnTh.mjs.map → S3StateStore-Dxob3PNA.mjs.map} +1 -1
- package/dist/{SSMStateStore-DBzlfc-S.cjs → SSMStateStore-DGIKJ4NN.cjs} +2 -2
- package/dist/{SSMStateStore-DBzlfc-S.cjs.map → SSMStateStore-DGIKJ4NN.cjs.map} +1 -1
- package/dist/{SSMStateStore-Dkdw-fPL.mjs → SSMStateStore-Dvl4A8Up.mjs} +2 -2
- package/dist/{SSMStateStore-Dkdw-fPL.mjs.map → SSMStateStore-Dvl4A8Up.mjs.map} +1 -1
- package/dist/{add-CoIr2wgv.cjs → add-DeC2Kyyv.cjs} +3 -3
- package/dist/{add-CoIr2wgv.cjs.map → add-DeC2Kyyv.cjs.map} +1 -1
- package/dist/{add-Dzk23476.mjs → add-XTLU19_E.mjs} +3 -3
- package/dist/{add-Dzk23476.mjs.map → add-XTLU19_E.mjs.map} +1 -1
- package/dist/{bundler-CSR2nM4m.cjs → bundler-CEaR6Dpb.cjs} +6 -6
- package/dist/bundler-CEaR6Dpb.cjs.map +1 -0
- package/dist/{bundler-Cn0JF5G0.mjs → bundler-CNTVLCCA.mjs} +6 -6
- package/dist/bundler-CNTVLCCA.mjs.map +1 -0
- package/dist/{cli-ij8Lwlww.mjs → cli-D1arS0p1.mjs} +3 -3
- package/dist/{cli-ij8Lwlww.mjs.map → cli-D1arS0p1.mjs.map} +1 -1
- package/dist/{cli-IoM2-x48.cjs → cli-MMgBrHx7.cjs} +3 -3
- package/dist/{cli-IoM2-x48.cjs.map → cli-MMgBrHx7.cjs.map} +1 -1
- package/dist/{compose-DfIioFX0.cjs → compose-B-MME-4C.cjs} +358 -16
- package/dist/compose-B-MME-4C.cjs.map +1 -0
- package/dist/{compose-CZtinmtw.mjs → compose-T3hnnjKZ.mjs} +353 -17
- package/dist/compose-T3hnnjKZ.mjs.map +1 -0
- package/dist/{config-CxEp_g7j.d.mts → config-BN6iXvyR.d.mts} +3 -3
- package/dist/{config-BnA0kPTY.d.cts.map → config-BN6iXvyR.d.mts.map} +1 -1
- package/dist/{config-BnA0kPTY.d.cts → config-j9zBKaEG.d.cts} +3 -3
- package/dist/{config-CxEp_g7j.d.mts.map → config-j9zBKaEG.d.cts.map} +1 -1
- package/dist/config.cjs +1 -1
- package/dist/config.d.cts +3 -3
- package/dist/config.d.mts +3 -3
- package/dist/config.mjs +1 -1
- package/dist/deploy/public.cjs +6 -6
- package/dist/deploy/public.d.cts +3 -3
- package/dist/deploy/public.d.cts.map +1 -1
- package/dist/deploy/public.d.mts +3 -3
- package/dist/deploy/public.d.mts.map +1 -1
- package/dist/deploy/public.mjs +6 -6
- package/dist/{deploy-Dpz9raPM.cjs → deploy-CZE0m6jd.cjs} +5 -5
- package/dist/{deploy-Dpz9raPM.cjs.map → deploy-CZE0m6jd.cjs.map} +1 -1
- package/dist/{deploy-BzqbkJ3o.mjs → deploy-ClC53rc6.mjs} +5 -5
- package/dist/{deploy-BzqbkJ3o.mjs.map → deploy-ClC53rc6.mjs.map} +1 -1
- package/dist/{dokploy-DkwcORbv.mjs → dokploy-D1UwQKm3.mjs} +2 -2
- package/dist/{dokploy-DkwcORbv.mjs.map → dokploy-D1UwQKm3.mjs.map} +1 -1
- package/dist/{dokploy-B6O-J1fb.cjs → dokploy-lwmH05zr.cjs} +2 -2
- package/dist/{dokploy-B6O-J1fb.cjs.map → dokploy-lwmH05zr.cjs.map} +1 -1
- package/dist/{encryption-C8Ic5t0C.cjs → encryption-D05sNwED.cjs} +3 -3
- package/dist/{encryption-C8Ic5t0C.cjs.map → encryption-D05sNwED.cjs.map} +1 -1
- package/dist/{encryption-7m1mKcRy.mjs → encryption-DcrxWl1s.mjs} +3 -3
- package/dist/{encryption-7m1mKcRy.mjs.map → encryption-DcrxWl1s.mjs.map} +1 -1
- package/dist/{engine-BR9qlS7A.cjs → engine-Cry1vwMz.cjs} +6 -5
- package/dist/engine-Cry1vwMz.cjs.map +1 -0
- package/dist/{engine-62Kl70Pj.mjs → engine-DmQS3XHX.mjs} +6 -5
- package/dist/{engine-62Kl70Pj.mjs.map → engine-DmQS3XHX.mjs.map} +1 -1
- package/dist/{github-CmGIZjhu.cjs → github-BchWoiVx.cjs} +3 -3
- package/dist/{github-CmGIZjhu.cjs.map → github-BchWoiVx.cjs.map} +1 -1
- package/dist/{github-DtymyOcH.mjs → github-CqYOP8cI.mjs} +3 -3
- package/dist/{github-DtymyOcH.mjs.map → github-CqYOP8cI.mjs.map} +1 -1
- package/dist/{index-CkX89m6S.d.mts → index-Bk3NY6DN.d.cts} +2 -2
- package/dist/{index-CkX89m6S.d.mts.map → index-Bk3NY6DN.d.cts.map} +1 -1
- package/dist/{index-EvZBkycu.d.cts → index-DvoxuTqR.d.mts} +2 -2
- package/dist/{index-EvZBkycu.d.cts.map → index-DvoxuTqR.d.mts.map} +1 -1
- package/dist/index.cjs +53 -52
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +53 -52
- package/dist/index.mjs.map +1 -1
- package/dist/{migrate-CEzq6DjL.mjs → migrate-guck0NJK.mjs} +3 -3
- package/dist/{migrate-CEzq6DjL.mjs.map → migrate-guck0NJK.mjs.map} +1 -1
- package/dist/{migrate-CYC-hBOU.cjs → migrate-qKeGYq21.cjs} +3 -3
- package/dist/{migrate-CYC-hBOU.cjs.map → migrate-qKeGYq21.cjs.map} +1 -1
- package/dist/openapi.cjs +1 -1
- package/dist/openapi.d.cts +1 -1
- package/dist/openapi.d.mts +1 -1
- package/dist/openapi.mjs +1 -1
- package/dist/{ready-QGgjmcuN.cjs → ready-Dp6arkI4.cjs} +5 -5
- package/dist/ready-Dp6arkI4.cjs.map +1 -0
- package/dist/{ready-DeYaSoNN.mjs → ready-xpoX11uu.mjs} +5 -5
- package/dist/ready-xpoX11uu.mjs.map +1 -0
- package/dist/reconcile/public.d.cts +1 -1
- package/dist/reconcile/public.d.mts +1 -1
- package/dist/{resolve-Dn5wGf1r.mjs → resolve-BsXEXAUz.mjs} +3 -3
- package/dist/{resolve-Dn5wGf1r.mjs.map → resolve-BsXEXAUz.mjs.map} +1 -1
- package/dist/{resolve-BDsMffvz.cjs → resolve-wEjEvbJ5.cjs} +3 -3
- package/dist/{resolve-BDsMffvz.cjs.map → resolve-wEjEvbJ5.cjs.map} +1 -1
- package/dist/{rollback-DDfaqv7P.mjs → rollback-Bk91csFg.mjs} +5 -5
- package/dist/{rollback-DDfaqv7P.mjs.map → rollback-Bk91csFg.mjs.map} +1 -1
- package/dist/{rollback-5XHaBDxW.cjs → rollback-DmcU-efZ.cjs} +5 -5
- package/dist/{rollback-5XHaBDxW.cjs.map → rollback-DmcU-efZ.cjs.map} +1 -1
- package/dist/{runtime-B3jNgeP-.d.mts → runtime-CYAO4iZG.d.cts} +2 -2
- package/dist/{runtime-B3jNgeP-.d.mts.map → runtime-CYAO4iZG.d.cts.map} +1 -1
- package/dist/{runtime-DWThEP-r.d.cts → runtime-DHJhe4QI.d.mts} +2 -2
- package/dist/{runtime-DWThEP-r.d.cts.map → runtime-DHJhe4QI.d.mts.map} +1 -1
- package/dist/{sniffer-Bc0nNBDF.cjs → sniffer-DpHpFZBD.cjs} +6 -6
- package/dist/sniffer-DpHpFZBD.cjs.map +1 -0
- package/dist/{sniffer-DMqi2b1q.mjs → sniffer-vAXsU_4G.mjs} +6 -6
- package/dist/sniffer-vAXsU_4G.mjs.map +1 -0
- package/dist/{state-DClnPZt5.mjs → state-BJFherj1.mjs} +60 -5
- package/dist/state-BJFherj1.mjs.map +1 -0
- package/dist/{state-8RgXeX5Y.cjs → state-CCuTJNRH.cjs} +71 -4
- package/dist/state-CCuTJNRH.cjs.map +1 -0
- package/dist/target/public.cjs +1 -1
- package/dist/target/public.d.cts +2 -2
- package/dist/target/public.d.mts +2 -2
- package/dist/target/public.mjs +1 -1
- package/dist/{types-CZdCxUCK.d.cts → types-BD1jgA85.d.cts} +70 -2
- package/dist/types-BD1jgA85.d.cts.map +1 -0
- package/dist/{types-Da0E3Qv7.d.mts → types-DkG5WW8l.d.mts} +70 -2
- package/dist/types-DkG5WW8l.d.mts.map +1 -0
- package/dist/vitest.cjs +1 -1
- package/dist/vitest.mjs +1 -1
- package/dist/workspace/index.cjs +1 -1
- package/dist/workspace/index.d.cts +3 -3
- package/dist/workspace/index.d.mts +3 -3
- package/dist/workspace/index.mjs +1 -1
- package/dist/{workspace-B2UK_rVP.mjs → workspace-C7NqCsQF.mjs} +104 -2
- package/dist/workspace-C7NqCsQF.mjs.map +1 -0
- package/dist/{workspace-8NuAjaXJ.cjs → workspace-vdrcRI-m.cjs} +115 -1
- package/dist/workspace-vdrcRI-m.cjs.map +1 -0
- package/package.json +15 -15
- package/dist/OpenApiTsGenerator-CSaXs-dB.mjs.map +0 -1
- package/dist/OpenApiTsGenerator-CxB8mqXz.cjs.map +0 -1
- package/dist/bundler-CSR2nM4m.cjs.map +0 -1
- package/dist/bundler-Cn0JF5G0.mjs.map +0 -1
- package/dist/compose-CZtinmtw.mjs.map +0 -1
- package/dist/compose-DfIioFX0.cjs.map +0 -1
- package/dist/engine-BR9qlS7A.cjs.map +0 -1
- package/dist/ready-DeYaSoNN.mjs.map +0 -1
- package/dist/ready-QGgjmcuN.cjs.map +0 -1
- package/dist/sniffer-Bc0nNBDF.cjs.map +0 -1
- package/dist/sniffer-DMqi2b1q.mjs.map +0 -1
- package/dist/state-8RgXeX5Y.cjs.map +0 -1
- package/dist/state-DClnPZt5.mjs.map +0 -1
- package/dist/types-CZdCxUCK.d.cts.map +0 -1
- package/dist/types-Da0E3Qv7.d.mts.map +0 -1
- package/dist/workspace-8NuAjaXJ.cjs.map +0 -1
- package/dist/workspace-B2UK_rVP.mjs.map +0 -1
|
@@ -4,579 +4,6 @@ import { existsSync, realpathSync } from "node:fs";
|
|
|
4
4
|
import { mkdir, writeFile } from "node:fs/promises";
|
|
5
5
|
import { Endpoint } from "@geekmidas/constructs/endpoints";
|
|
6
6
|
import { StandardSchemaJsonSchema, getRegisteredZodJsonSchemas, getSchemaMetadata } from "@geekmidas/schema/conversion";
|
|
7
|
-
//#region src/build/endpoint-analyzer.ts
|
|
8
|
-
/**
|
|
9
|
-
* Analyze an endpoint to extract its features
|
|
10
|
-
*/
|
|
11
|
-
function analyzeEndpointFeatures(endpoint) {
|
|
12
|
-
return {
|
|
13
|
-
hasAuth: !!endpoint.authorizer || endpoint.hasSession === true,
|
|
14
|
-
hasServices: endpoint.services.length > 0,
|
|
15
|
-
hasDatabase: !!endpoint.databaseService,
|
|
16
|
-
hasBodyValidation: !!endpoint.input?.body,
|
|
17
|
-
hasQueryValidation: !!endpoint.input?.query,
|
|
18
|
-
hasParamValidation: !!endpoint.input?.params,
|
|
19
|
-
hasAudits: (endpoint.audits?.length ?? 0) > 0,
|
|
20
|
-
hasEvents: (endpoint.events?.length ?? 0) > 0,
|
|
21
|
-
hasRateLimit: !!endpoint.rateLimit,
|
|
22
|
-
hasRls: !!endpoint.rlsConfig && !endpoint.rlsBypass,
|
|
23
|
-
hasOutputValidation: !!endpoint.outputSchema
|
|
24
|
-
};
|
|
25
|
-
}
|
|
26
|
-
/**
|
|
27
|
-
* Determine the optimization tier for an endpoint based on its features
|
|
28
|
-
*/
|
|
29
|
-
function determineEndpointTier(features) {
|
|
30
|
-
const { hasAuth, hasServices, hasDatabase, hasAudits, hasEvents, hasRateLimit, hasRls } = features;
|
|
31
|
-
if (!hasAuth && !hasServices && !hasDatabase && !hasAudits && !hasEvents && !hasRateLimit && !hasRls) return "minimal";
|
|
32
|
-
if (hasAudits || hasRls || hasRateLimit) return "full";
|
|
33
|
-
return "standard";
|
|
34
|
-
}
|
|
35
|
-
/**
|
|
36
|
-
* Perform complete analysis of an endpoint
|
|
37
|
-
*/
|
|
38
|
-
function analyzeEndpoint(endpoint, exportName) {
|
|
39
|
-
const features = analyzeEndpointFeatures(endpoint);
|
|
40
|
-
const tier = determineEndpointTier(features);
|
|
41
|
-
return {
|
|
42
|
-
route: endpoint.route,
|
|
43
|
-
method: endpoint.method,
|
|
44
|
-
exportName,
|
|
45
|
-
features,
|
|
46
|
-
tier,
|
|
47
|
-
serviceNames: endpoint.services.map((s) => s.serviceName),
|
|
48
|
-
databaseServiceName: endpoint.databaseService?.serviceName
|
|
49
|
-
};
|
|
50
|
-
}
|
|
51
|
-
/**
|
|
52
|
-
* Generate a summary of endpoint analysis for logging
|
|
53
|
-
*/
|
|
54
|
-
function summarizeAnalysis(analyses) {
|
|
55
|
-
const byTier = {
|
|
56
|
-
minimal: 0,
|
|
57
|
-
standard: 0,
|
|
58
|
-
full: 0
|
|
59
|
-
};
|
|
60
|
-
const byFeature = {
|
|
61
|
-
hasAuth: 0,
|
|
62
|
-
hasServices: 0,
|
|
63
|
-
hasDatabase: 0,
|
|
64
|
-
hasBodyValidation: 0,
|
|
65
|
-
hasQueryValidation: 0,
|
|
66
|
-
hasParamValidation: 0,
|
|
67
|
-
hasAudits: 0,
|
|
68
|
-
hasEvents: 0,
|
|
69
|
-
hasRateLimit: 0,
|
|
70
|
-
hasRls: 0,
|
|
71
|
-
hasOutputValidation: 0
|
|
72
|
-
};
|
|
73
|
-
for (const analysis of analyses) {
|
|
74
|
-
byTier[analysis.tier]++;
|
|
75
|
-
for (const [feature, enabled] of Object.entries(analysis.features)) if (enabled) byFeature[feature]++;
|
|
76
|
-
}
|
|
77
|
-
return {
|
|
78
|
-
total: analyses.length,
|
|
79
|
-
byTier,
|
|
80
|
-
byFeature
|
|
81
|
-
};
|
|
82
|
-
}
|
|
83
|
-
//#endregion
|
|
84
|
-
//#region src/build/handler-templates.ts
|
|
85
|
-
/**
|
|
86
|
-
* Generate validator references for an endpoint
|
|
87
|
-
*/
|
|
88
|
-
function generateValidators(exportName, features) {
|
|
89
|
-
const validators = [];
|
|
90
|
-
if (features.hasBodyValidation) validators.push(`validateBody(${exportName})`);
|
|
91
|
-
if (features.hasQueryValidation) validators.push(`validateQuery(${exportName})`);
|
|
92
|
-
if (features.hasParamValidation) validators.push(`validateParams(${exportName})`);
|
|
93
|
-
return validators.length > 0 ? `\n ${validators.join(",\n ")},` : "";
|
|
94
|
-
}
|
|
95
|
-
/**
|
|
96
|
-
* Generate a minimal handler (near-raw-Hono performance)
|
|
97
|
-
*
|
|
98
|
-
* Used for: Health checks, public endpoints with no services
|
|
99
|
-
*/
|
|
100
|
-
function generateMinimalHandler(analysis) {
|
|
101
|
-
const { exportName, features } = analysis;
|
|
102
|
-
const method = analysis.method.toLowerCase();
|
|
103
|
-
const validators = generateValidators(exportName, features);
|
|
104
|
-
if (!(validators.length > 0) && !features.hasOutputValidation) return `
|
|
105
|
-
// Minimal handler: ${analysis.route} (${analysis.method})
|
|
106
|
-
app.${method}('${analysis.route}', async (c) => {
|
|
107
|
-
const result = await ${exportName}.handler(
|
|
108
|
-
{
|
|
109
|
-
services: {},
|
|
110
|
-
logger,
|
|
111
|
-
body: undefined,
|
|
112
|
-
query: undefined,
|
|
113
|
-
params: undefined,
|
|
114
|
-
session: undefined,
|
|
115
|
-
header: Endpoint.createHeaders(c.req.header()),
|
|
116
|
-
cookie: Endpoint.createCookies(c.req.header().cookie),
|
|
117
|
-
auditor: undefined,
|
|
118
|
-
db: undefined,
|
|
119
|
-
} as any,
|
|
120
|
-
{ getMetadata: () => ({}) } as any,
|
|
121
|
-
);
|
|
122
|
-
return c.json(result, ${exportName}.status as any);
|
|
123
|
-
});`;
|
|
124
|
-
return `
|
|
125
|
-
// Minimal handler with validation: ${analysis.route} (${analysis.method})
|
|
126
|
-
app.${method}('${analysis.route}',${validators}
|
|
127
|
-
async (c) => {
|
|
128
|
-
const result = await ${exportName}.handler(
|
|
129
|
-
{
|
|
130
|
-
services: {},
|
|
131
|
-
logger,
|
|
132
|
-
body: ${features.hasBodyValidation ? "(c.req.valid as any)('json')" : "undefined"},
|
|
133
|
-
query: ${features.hasQueryValidation ? "(c.req.valid as any)('query')" : "undefined"},
|
|
134
|
-
params: ${features.hasParamValidation ? "(c.req.valid as any)('param')" : "undefined"},
|
|
135
|
-
session: undefined,
|
|
136
|
-
header: Endpoint.createHeaders(c.req.header()),
|
|
137
|
-
cookie: Endpoint.createCookies(c.req.header().cookie),
|
|
138
|
-
auditor: undefined,
|
|
139
|
-
db: undefined,
|
|
140
|
-
} as any,
|
|
141
|
-
{ getMetadata: () => ({}) } as any,
|
|
142
|
-
);
|
|
143
|
-
${features.hasOutputValidation ? `const output = await ${exportName}.parseOutput(result);
|
|
144
|
-
return c.json(output, ${exportName}.status as any);` : `return c.json(result, ${exportName}.status as any);`}
|
|
145
|
-
}
|
|
146
|
-
);`;
|
|
147
|
-
}
|
|
148
|
-
/**
|
|
149
|
-
* Generate a standard handler (auth and/or services)
|
|
150
|
-
*
|
|
151
|
-
* Used for: Authenticated endpoints, endpoints with services
|
|
152
|
-
*/
|
|
153
|
-
function generateStandardHandler(analysis) {
|
|
154
|
-
const { exportName, features } = analysis;
|
|
155
|
-
const method = analysis.method.toLowerCase();
|
|
156
|
-
const validators = generateValidators(exportName, features);
|
|
157
|
-
let serviceResolution = "";
|
|
158
|
-
if (features.hasServices || features.hasDatabase) serviceResolution = `
|
|
159
|
-
const services = await serviceDiscovery.register(${exportName}.services);
|
|
160
|
-
${features.hasDatabase ? `const db = ${exportName}.databaseService
|
|
161
|
-
? (await serviceDiscovery.register([${exportName}.databaseService]) as any)[${exportName}.databaseService.serviceName]
|
|
162
|
-
: undefined;` : "const db = undefined;"}`;
|
|
163
|
-
else serviceResolution = `
|
|
164
|
-
const services = {};
|
|
165
|
-
const db = undefined;`;
|
|
166
|
-
let authCode = "";
|
|
167
|
-
if (features.hasAuth) authCode = `
|
|
168
|
-
// Authentication
|
|
169
|
-
const session = await ${exportName}.getSession({
|
|
170
|
-
services,
|
|
171
|
-
logger,
|
|
172
|
-
header,
|
|
173
|
-
cookie,
|
|
174
|
-
...(db !== undefined && { db }),
|
|
175
|
-
} as any);
|
|
176
|
-
|
|
177
|
-
const isAuthorized = await ${exportName}.authorize({
|
|
178
|
-
header,
|
|
179
|
-
cookie,
|
|
180
|
-
services,
|
|
181
|
-
logger,
|
|
182
|
-
session,
|
|
183
|
-
} as any);
|
|
184
|
-
|
|
185
|
-
if (!isAuthorized) {
|
|
186
|
-
return c.json({ error: 'Unauthorized' }, 401);
|
|
187
|
-
}`;
|
|
188
|
-
let eventCode = "";
|
|
189
|
-
if (features.hasEvents) eventCode = `
|
|
190
|
-
// Publish events on success
|
|
191
|
-
if (Endpoint.isSuccessStatus(${exportName}.status)) {
|
|
192
|
-
await (publishConstructEvents as any)(
|
|
193
|
-
${exportName},
|
|
194
|
-
result,
|
|
195
|
-
serviceDiscovery,
|
|
196
|
-
logger,
|
|
197
|
-
);
|
|
198
|
-
}`;
|
|
199
|
-
return `
|
|
200
|
-
// Standard handler: ${analysis.route} (${analysis.method})
|
|
201
|
-
app.${method}('${analysis.route}',${validators}
|
|
202
|
-
async (c) => {
|
|
203
|
-
const headerValues = c.req.header();
|
|
204
|
-
const header = Endpoint.createHeaders(headerValues);
|
|
205
|
-
const cookie = Endpoint.createCookies(headerValues.cookie);
|
|
206
|
-
${serviceResolution}
|
|
207
|
-
${authCode}
|
|
208
|
-
|
|
209
|
-
const responseBuilder = new ResponseBuilder();
|
|
210
|
-
const result = await ${exportName}.handler(
|
|
211
|
-
{
|
|
212
|
-
services,
|
|
213
|
-
logger,
|
|
214
|
-
body: ${features.hasBodyValidation ? "(c.req.valid as any)('json')" : "undefined"},
|
|
215
|
-
query: ${features.hasQueryValidation ? "(c.req.valid as any)('query')" : "undefined"},
|
|
216
|
-
params: ${features.hasParamValidation ? "(c.req.valid as any)('param')" : "undefined"},
|
|
217
|
-
session: ${features.hasAuth ? "session" : "undefined"},
|
|
218
|
-
header,
|
|
219
|
-
cookie,
|
|
220
|
-
auditor: undefined,
|
|
221
|
-
db,
|
|
222
|
-
} as any,
|
|
223
|
-
responseBuilder,
|
|
224
|
-
);
|
|
225
|
-
|
|
226
|
-
let data = result;
|
|
227
|
-
let metadata = responseBuilder.getMetadata();
|
|
228
|
-
|
|
229
|
-
if (Endpoint.hasMetadata(result)) {
|
|
230
|
-
data = result.data;
|
|
231
|
-
metadata = result.metadata;
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
${features.hasOutputValidation ? `const output = ${exportName}.outputSchema
|
|
235
|
-
? await ${exportName}.parseOutput(data)
|
|
236
|
-
: data;` : "const output = data;"}
|
|
237
|
-
${eventCode}
|
|
238
|
-
|
|
239
|
-
const status = (metadata.status ?? ${exportName}.status) as any;
|
|
240
|
-
return c.json(output, status);
|
|
241
|
-
}
|
|
242
|
-
);`;
|
|
243
|
-
}
|
|
244
|
-
/**
|
|
245
|
-
* Generate validators.ts - Shared validator middleware factories
|
|
246
|
-
*/
|
|
247
|
-
function generateValidatorsFile(analyses) {
|
|
248
|
-
const needsBody = analyses.some((a) => a.features.hasBodyValidation);
|
|
249
|
-
const needsQuery = analyses.some((a) => a.features.hasQueryValidation);
|
|
250
|
-
const needsParams = analyses.some((a) => a.features.hasParamValidation);
|
|
251
|
-
if (!needsBody && !needsQuery && !needsParams) return `// No validators needed for this build\nexport {};\n`;
|
|
252
|
-
const exports = [];
|
|
253
|
-
const factories = [];
|
|
254
|
-
if (needsBody) {
|
|
255
|
-
exports.push("validateBody");
|
|
256
|
-
factories.push(`
|
|
257
|
-
export const validateBody = (endpoint: any) =>
|
|
258
|
-
validator('json', async (value, c) => {
|
|
259
|
-
if (!endpoint.input?.body) return undefined;
|
|
260
|
-
const parsed = await Endpoint.validate(endpoint.input.body, value);
|
|
261
|
-
if (parsed.issues) return c.json(parsed.issues, 422);
|
|
262
|
-
return parsed.value;
|
|
263
|
-
});`);
|
|
264
|
-
}
|
|
265
|
-
if (needsQuery) {
|
|
266
|
-
exports.push("validateQuery");
|
|
267
|
-
factories.push(`
|
|
268
|
-
export const validateQuery = (endpoint: any) =>
|
|
269
|
-
validator('query', async (_, c) => {
|
|
270
|
-
if (!endpoint.input?.query) return undefined;
|
|
271
|
-
const rawQuery = Object.fromEntries(new URL(c.req.url).searchParams);
|
|
272
|
-
const parsed = await Endpoint.validate(endpoint.input.query, rawQuery);
|
|
273
|
-
if (parsed.issues) return c.json(parsed.issues, 422);
|
|
274
|
-
return parsed.value;
|
|
275
|
-
});`);
|
|
276
|
-
}
|
|
277
|
-
if (needsParams) {
|
|
278
|
-
exports.push("validateParams");
|
|
279
|
-
factories.push(`
|
|
280
|
-
export const validateParams = (endpoint: any) =>
|
|
281
|
-
validator('param', async (params, c) => {
|
|
282
|
-
if (!endpoint.input?.params) return undefined;
|
|
283
|
-
const parsed = await Endpoint.validate(endpoint.input.params, params);
|
|
284
|
-
if (parsed.issues) return c.json(parsed.issues, 422);
|
|
285
|
-
return parsed.value;
|
|
286
|
-
});`);
|
|
287
|
-
}
|
|
288
|
-
return `/**
|
|
289
|
-
* Generated validator middleware factories
|
|
290
|
-
* Shared across all endpoint tiers that need validation
|
|
291
|
-
*/
|
|
292
|
-
import { validator } from 'hono/validator';
|
|
293
|
-
import { Endpoint } from '@geekmidas/constructs/endpoints';
|
|
294
|
-
${factories.join("\n")}
|
|
295
|
-
`;
|
|
296
|
-
}
|
|
297
|
-
/**
|
|
298
|
-
* Generate a standalone minimal endpoint file
|
|
299
|
-
*/
|
|
300
|
-
function generateMinimalEndpointFile(analysis, endpointImport) {
|
|
301
|
-
const { exportName, features } = analysis;
|
|
302
|
-
const validatorImport = features.hasBodyValidation || features.hasQueryValidation || features.hasParamValidation ? generateValidatorImportsForEndpoint(analysis) : "";
|
|
303
|
-
const handler = generateMinimalHandler(analysis);
|
|
304
|
-
return `/**
|
|
305
|
-
* Minimal endpoint: ${analysis.route} (${analysis.method})
|
|
306
|
-
* Near-raw-Hono performance
|
|
307
|
-
*/
|
|
308
|
-
import type { Hono } from 'hono';
|
|
309
|
-
import type { Logger } from '@geekmidas/logger';
|
|
310
|
-
import { Endpoint } from '@geekmidas/constructs/endpoints';
|
|
311
|
-
${validatorImport}
|
|
312
|
-
import { ${exportName} } from '${endpointImport.importPath}';
|
|
313
|
-
|
|
314
|
-
export function setup${capitalize(exportName)}(
|
|
315
|
-
app: Hono,
|
|
316
|
-
logger: Logger,
|
|
317
|
-
): void {
|
|
318
|
-
${handler}
|
|
319
|
-
}
|
|
320
|
-
`;
|
|
321
|
-
}
|
|
322
|
-
/**
|
|
323
|
-
* Generate a standalone standard endpoint file
|
|
324
|
-
*/
|
|
325
|
-
function generateStandardEndpointFile(analysis, endpointImport) {
|
|
326
|
-
const { exportName, features } = analysis;
|
|
327
|
-
const validatorImport = features.hasBodyValidation || features.hasQueryValidation || features.hasParamValidation ? generateValidatorImportsForEndpoint(analysis) : "";
|
|
328
|
-
const eventsImport = features.hasEvents ? `import { publishConstructEvents } from '@geekmidas/constructs/endpoints';` : "";
|
|
329
|
-
const handler = generateStandardHandler(analysis);
|
|
330
|
-
return `/**
|
|
331
|
-
* Standard endpoint: ${analysis.route} (${analysis.method})
|
|
332
|
-
* Auth and/or services enabled
|
|
333
|
-
*/
|
|
334
|
-
import type { Hono } from 'hono';
|
|
335
|
-
import type { Logger } from '@geekmidas/logger';
|
|
336
|
-
import type { ServiceDiscovery } from '@geekmidas/services';
|
|
337
|
-
import { Endpoint, ResponseBuilder } from '@geekmidas/constructs/endpoints';
|
|
338
|
-
${eventsImport}
|
|
339
|
-
${validatorImport}
|
|
340
|
-
import { ${exportName} } from '${endpointImport.importPath}';
|
|
341
|
-
|
|
342
|
-
export function setup${capitalize(exportName)}(
|
|
343
|
-
app: Hono,
|
|
344
|
-
serviceDiscovery: ServiceDiscovery<any, any>,
|
|
345
|
-
logger: Logger,
|
|
346
|
-
): void {
|
|
347
|
-
${handler}
|
|
348
|
-
}
|
|
349
|
-
`;
|
|
350
|
-
}
|
|
351
|
-
/**
|
|
352
|
-
* Generate a standalone full endpoint file
|
|
353
|
-
*/
|
|
354
|
-
function generateFullEndpointFile(analysis, endpointImport) {
|
|
355
|
-
const { exportName } = analysis;
|
|
356
|
-
return `/**
|
|
357
|
-
* Full endpoint: ${analysis.route} (${analysis.method})
|
|
358
|
-
* Complex features: audits, RLS, rate limiting
|
|
359
|
-
*/
|
|
360
|
-
import type { Hono } from 'hono';
|
|
361
|
-
import type { ServiceDiscovery } from '@geekmidas/services';
|
|
362
|
-
import { HonoEndpoint } from '@geekmidas/constructs/hono';
|
|
363
|
-
import { ${exportName} } from '${endpointImport.importPath}';
|
|
364
|
-
|
|
365
|
-
export function setup${capitalize(exportName)}(
|
|
366
|
-
app: Hono,
|
|
367
|
-
serviceDiscovery: ServiceDiscovery<any, any>,
|
|
368
|
-
openApiOptions: any,
|
|
369
|
-
): void {
|
|
370
|
-
HonoEndpoint.addRoutes([${exportName}] as any, serviceDiscovery, app, openApiOptions);
|
|
371
|
-
}
|
|
372
|
-
`;
|
|
373
|
-
}
|
|
374
|
-
/**
|
|
375
|
-
* Generate tier index file that imports and calls all endpoint setup functions
|
|
376
|
-
*/
|
|
377
|
-
function generateTierIndexFile(tier, analyses) {
|
|
378
|
-
const tierEndpoints = analyses.filter((a) => a.tier === tier);
|
|
379
|
-
if (tierEndpoints.length === 0) {
|
|
380
|
-
if (tier === "minimal") return `// No minimal-tier endpoints in this build
|
|
381
|
-
import type { Hono } from 'hono';
|
|
382
|
-
import type { Logger } from '@geekmidas/logger';
|
|
383
|
-
|
|
384
|
-
export function setupMinimalEndpoints(
|
|
385
|
-
_app: Hono,
|
|
386
|
-
_logger: Logger,
|
|
387
|
-
): void {
|
|
388
|
-
// No minimal endpoints
|
|
389
|
-
}
|
|
390
|
-
`;
|
|
391
|
-
if (tier === "standard") return `// No standard-tier endpoints in this build
|
|
392
|
-
import type { Hono } from 'hono';
|
|
393
|
-
import type { Logger } from '@geekmidas/logger';
|
|
394
|
-
import type { ServiceDiscovery } from '@geekmidas/services';
|
|
395
|
-
|
|
396
|
-
export function setupStandardEndpoints(
|
|
397
|
-
_app: Hono,
|
|
398
|
-
_serviceDiscovery: ServiceDiscovery<any, any>,
|
|
399
|
-
_logger: Logger,
|
|
400
|
-
): void {
|
|
401
|
-
// No standard endpoints
|
|
402
|
-
}
|
|
403
|
-
`;
|
|
404
|
-
return `// No full-tier endpoints in this build
|
|
405
|
-
import type { Hono } from 'hono';
|
|
406
|
-
import type { ServiceDiscovery } from '@geekmidas/services';
|
|
407
|
-
|
|
408
|
-
export function setupFullEndpoints(
|
|
409
|
-
_app: Hono,
|
|
410
|
-
_serviceDiscovery: ServiceDiscovery<any, any>,
|
|
411
|
-
_enableOpenApi: boolean,
|
|
412
|
-
): void {
|
|
413
|
-
// No full endpoints
|
|
414
|
-
}
|
|
415
|
-
`;
|
|
416
|
-
}
|
|
417
|
-
const imports = tierEndpoints.map((a) => `import { setup${capitalize(a.exportName)} } from './${a.exportName}.js';`).join("\n");
|
|
418
|
-
if (tier === "minimal") {
|
|
419
|
-
const calls = tierEndpoints.map((a) => ` setup${capitalize(a.exportName)}(app, logger);`).join("\n");
|
|
420
|
-
return `/**
|
|
421
|
-
* Minimal-tier endpoint index (${tierEndpoints.length} endpoints)
|
|
422
|
-
* Near-raw-Hono performance
|
|
423
|
-
*/
|
|
424
|
-
import type { Hono } from 'hono';
|
|
425
|
-
import type { Logger } from '@geekmidas/logger';
|
|
426
|
-
${imports}
|
|
427
|
-
|
|
428
|
-
export function setupMinimalEndpoints(
|
|
429
|
-
app: Hono,
|
|
430
|
-
logger: Logger,
|
|
431
|
-
): void {
|
|
432
|
-
${calls}
|
|
433
|
-
}
|
|
434
|
-
`;
|
|
435
|
-
}
|
|
436
|
-
if (tier === "standard") {
|
|
437
|
-
const calls = tierEndpoints.map((a) => ` setup${capitalize(a.exportName)}(app, serviceDiscovery, logger);`).join("\n");
|
|
438
|
-
return `/**
|
|
439
|
-
* Standard-tier endpoint index (${tierEndpoints.length} endpoints)
|
|
440
|
-
* Auth and/or services enabled
|
|
441
|
-
*/
|
|
442
|
-
import type { Hono } from 'hono';
|
|
443
|
-
import type { Logger } from '@geekmidas/logger';
|
|
444
|
-
import type { ServiceDiscovery } from '@geekmidas/services';
|
|
445
|
-
${imports}
|
|
446
|
-
|
|
447
|
-
export function setupStandardEndpoints(
|
|
448
|
-
app: Hono,
|
|
449
|
-
serviceDiscovery: ServiceDiscovery<any, any>,
|
|
450
|
-
logger: Logger,
|
|
451
|
-
): void {
|
|
452
|
-
${calls}
|
|
453
|
-
}
|
|
454
|
-
`;
|
|
455
|
-
}
|
|
456
|
-
const calls = tierEndpoints.map((a) => ` setup${capitalize(a.exportName)}(app, serviceDiscovery, openApiOptions);`).join("\n");
|
|
457
|
-
return `/**
|
|
458
|
-
* Full-tier endpoint index (${tierEndpoints.length} endpoints)
|
|
459
|
-
* Complex features: audits, RLS, rate limiting
|
|
460
|
-
*/
|
|
461
|
-
import type { Hono } from 'hono';
|
|
462
|
-
import type { ServiceDiscovery } from '@geekmidas/services';
|
|
463
|
-
${imports}
|
|
464
|
-
|
|
465
|
-
export function setupFullEndpoints(
|
|
466
|
-
app: Hono,
|
|
467
|
-
serviceDiscovery: ServiceDiscovery<any, any>,
|
|
468
|
-
enableOpenApi: boolean,
|
|
469
|
-
): void {
|
|
470
|
-
const openApiOptions: any = enableOpenApi ? {
|
|
471
|
-
docsPath: '/__docs',
|
|
472
|
-
openApiOptions: {
|
|
473
|
-
title: 'API Documentation',
|
|
474
|
-
version: '1.0.0',
|
|
475
|
-
description: 'Generated API documentation'
|
|
476
|
-
}
|
|
477
|
-
} : { docsPath: false };
|
|
478
|
-
|
|
479
|
-
${calls}
|
|
480
|
-
}
|
|
481
|
-
`;
|
|
482
|
-
}
|
|
483
|
-
/**
|
|
484
|
-
* Generate validator imports for a single endpoint
|
|
485
|
-
*/
|
|
486
|
-
function generateValidatorImportsForEndpoint(analysis) {
|
|
487
|
-
const imports = [];
|
|
488
|
-
if (analysis.features.hasBodyValidation) imports.push("validateBody");
|
|
489
|
-
if (analysis.features.hasQueryValidation) imports.push("validateQuery");
|
|
490
|
-
if (analysis.features.hasParamValidation) imports.push("validateParams");
|
|
491
|
-
if (imports.length === 0) return "";
|
|
492
|
-
return `import { ${imports.join(", ")} } from '../validators.js';`;
|
|
493
|
-
}
|
|
494
|
-
/**
|
|
495
|
-
* Capitalize first letter
|
|
496
|
-
*/
|
|
497
|
-
function capitalize(str) {
|
|
498
|
-
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
499
|
-
}
|
|
500
|
-
/**
|
|
501
|
-
* Generate all endpoint files with nested folder structure (per-endpoint files)
|
|
502
|
-
*/
|
|
503
|
-
function generateEndpointFilesNested(analyses, endpointImports) {
|
|
504
|
-
const files = {
|
|
505
|
-
"validators.ts": generateValidatorsFile(analyses),
|
|
506
|
-
"minimal/index.ts": generateTierIndexFile("minimal", analyses),
|
|
507
|
-
"standard/index.ts": generateTierIndexFile("standard", analyses),
|
|
508
|
-
"full/index.ts": generateTierIndexFile("full", analyses),
|
|
509
|
-
"index.ts": generateNestedIndexFile(analyses)
|
|
510
|
-
};
|
|
511
|
-
for (const analysis of analyses) {
|
|
512
|
-
const endpointImport = endpointImports.find((i) => i.exportName === analysis.exportName);
|
|
513
|
-
if (!endpointImport) continue;
|
|
514
|
-
const fileName = `${analysis.tier}/${analysis.exportName}.ts`;
|
|
515
|
-
switch (analysis.tier) {
|
|
516
|
-
case "minimal":
|
|
517
|
-
files[fileName] = generateMinimalEndpointFile(analysis, endpointImport);
|
|
518
|
-
break;
|
|
519
|
-
case "standard":
|
|
520
|
-
files[fileName] = generateStandardEndpointFile(analysis, endpointImport);
|
|
521
|
-
break;
|
|
522
|
-
case "full": files[fileName] = generateFullEndpointFile(analysis, endpointImport);
|
|
523
|
-
}
|
|
524
|
-
}
|
|
525
|
-
return files;
|
|
526
|
-
}
|
|
527
|
-
/**
|
|
528
|
-
* Generate index.ts for nested structure
|
|
529
|
-
*/
|
|
530
|
-
function generateNestedIndexFile(analyses) {
|
|
531
|
-
const minimalCount = analyses.filter((a) => a.tier === "minimal").length;
|
|
532
|
-
const standardCount = analyses.filter((a) => a.tier === "standard").length;
|
|
533
|
-
const fullCount = analyses.filter((a) => a.tier === "full").length;
|
|
534
|
-
return `/**
|
|
535
|
-
* Generated optimized endpoints
|
|
536
|
-
*
|
|
537
|
-
* Build-time optimization tiers:
|
|
538
|
-
* - minimal: ${minimalCount} endpoints (near-raw-Hono)
|
|
539
|
-
* - standard: ${standardCount} endpoints (auth/services)
|
|
540
|
-
* - full: ${fullCount} endpoints (audits/rls/rate-limit)
|
|
541
|
-
*/
|
|
542
|
-
import type { EnvironmentParser } from '@geekmidas/envkit';
|
|
543
|
-
import type { Logger } from '@geekmidas/logger';
|
|
544
|
-
import type { Hono } from 'hono';
|
|
545
|
-
import { ServiceDiscovery } from '@geekmidas/services';
|
|
546
|
-
import { setupMinimalEndpoints } from './minimal/index.js';
|
|
547
|
-
import { setupStandardEndpoints } from './standard/index.js';
|
|
548
|
-
import { setupFullEndpoints } from './full/index.js';
|
|
549
|
-
|
|
550
|
-
export async function setupEndpoints(
|
|
551
|
-
app: Hono,
|
|
552
|
-
envParser: EnvironmentParser<any>,
|
|
553
|
-
logger: Logger,
|
|
554
|
-
enableOpenApi: boolean = false,
|
|
555
|
-
): Promise<void> {
|
|
556
|
-
const serviceDiscovery = ServiceDiscovery.getInstance(envParser);
|
|
557
|
-
|
|
558
|
-
// Minimal handlers (${minimalCount} endpoints) - near-raw-Hono performance
|
|
559
|
-
setupMinimalEndpoints(app, logger);
|
|
560
|
-
|
|
561
|
-
// Standard handlers (${standardCount} endpoints) - auth/services
|
|
562
|
-
setupStandardEndpoints(app, serviceDiscovery, logger);
|
|
563
|
-
|
|
564
|
-
// Full handlers (${fullCount} endpoints) - audits/rls/rate-limit
|
|
565
|
-
setupFullEndpoints(app, serviceDiscovery, enableOpenApi);
|
|
566
|
-
|
|
567
|
-
// Add Swagger UI if OpenAPI is enabled
|
|
568
|
-
if (enableOpenApi) {
|
|
569
|
-
try {
|
|
570
|
-
const { swaggerUI } = await import('@hono/swagger-ui');
|
|
571
|
-
app.get('/__docs/ui', swaggerUI({ url: '/__docs' }));
|
|
572
|
-
} catch {
|
|
573
|
-
// @hono/swagger-ui not installed, skip Swagger UI
|
|
574
|
-
}
|
|
575
|
-
}
|
|
576
|
-
}
|
|
577
|
-
`;
|
|
578
|
-
}
|
|
579
|
-
//#endregion
|
|
580
7
|
//#region src/generators/telemetry.ts
|
|
581
8
|
/**
|
|
582
9
|
* Every package `@geekmidas/telescope/instrumentation` loads at runtime.
|
|
@@ -641,6 +68,13 @@ const HEADER = `/**
|
|
|
641
68
|
* configure the rest.
|
|
642
69
|
*/
|
|
643
70
|
|
|
71
|
+
import type { MiddlewareHandler } from 'hono';
|
|
72
|
+
|
|
73
|
+
/** Requests whose spans would only be noise: health checks, by default. */
|
|
74
|
+
export interface StartTelemetryOptions {
|
|
75
|
+
ignorePaths?: string[];
|
|
76
|
+
}
|
|
77
|
+
|
|
644
78
|
/** Telemetry was asked for and cannot start. The server runs without it. */
|
|
645
79
|
export class TelemetryUnavailable extends Error {
|
|
646
80
|
constructor(readonly reason: string, options?: { cause?: unknown }) {
|
|
@@ -654,7 +88,9 @@ export class TelemetryUnavailable extends Error {
|
|
|
654
88
|
`;
|
|
655
89
|
/**
|
|
656
90
|
* The entry's `telemetry.ts`: a `startTelemetry()` the server awaits before it
|
|
657
|
-
* imports the app, so the libraries the app loads are instrumented.
|
|
91
|
+
* imports the app, so the libraries the app loads are instrumented. It returns
|
|
92
|
+
* the Hono middleware that traces each request, or undefined when telemetry is
|
|
93
|
+
* off — the server mounts it ahead of every route.
|
|
658
94
|
*
|
|
659
95
|
* Failing to start telemetry warns (a named `TelemetryUnavailable`, through
|
|
660
96
|
* `process.emitWarning`) and never stops the server: an outage in observability
|
|
@@ -662,25 +98,32 @@ export class TelemetryUnavailable extends Error {
|
|
|
662
98
|
*/
|
|
663
99
|
function generateTelemetryModule(telemetry) {
|
|
664
100
|
if (!telemetry?.available) return `${HEADER}
|
|
665
|
-
export async function startTelemetry(
|
|
666
|
-
|
|
101
|
+
export async function startTelemetry(
|
|
102
|
+
_options: StartTelemetryOptions = {},
|
|
103
|
+
): Promise<MiddlewareHandler | undefined> {
|
|
104
|
+
if (!process.env.OTEL_EXPORTER_OTLP_ENDPOINT) return undefined;
|
|
667
105
|
|
|
668
106
|
process.emitWarning(
|
|
669
107
|
new TelemetryUnavailable(
|
|
670
108
|
'this server was built without @geekmidas/telescope and the @opentelemetry packages it needs. Add them to the app and rebuild.',
|
|
671
109
|
),
|
|
672
110
|
);
|
|
111
|
+
return undefined;
|
|
673
112
|
}
|
|
674
113
|
`;
|
|
675
114
|
const options = [
|
|
676
115
|
`serviceName: ${JSON.stringify(telemetry.serviceName)},`,
|
|
677
116
|
...telemetry.serviceNamespace ? [`serviceNamespace: ${JSON.stringify(telemetry.serviceNamespace)},`] : [],
|
|
678
117
|
"deploymentEnvironment: process.env.STAGE,",
|
|
679
|
-
"handleSignals: false,"
|
|
118
|
+
"handleSignals: false,",
|
|
119
|
+
"instrumentPino: false,",
|
|
120
|
+
"incomingHttpSpans: false,"
|
|
680
121
|
];
|
|
681
122
|
return `${HEADER}
|
|
682
|
-
export async function startTelemetry(
|
|
683
|
-
|
|
123
|
+
export async function startTelemetry(
|
|
124
|
+
options: StartTelemetryOptions = {},
|
|
125
|
+
): Promise<MiddlewareHandler | undefined> {
|
|
126
|
+
if (!process.env.OTEL_EXPORTER_OTLP_ENDPOINT) return undefined;
|
|
684
127
|
|
|
685
128
|
let instrumentation;
|
|
686
129
|
try {
|
|
@@ -692,7 +135,7 @@ export async function startTelemetry(): Promise<void> {
|
|
|
692
135
|
{ cause },
|
|
693
136
|
),
|
|
694
137
|
);
|
|
695
|
-
return;
|
|
138
|
+
return undefined;
|
|
696
139
|
}
|
|
697
140
|
|
|
698
141
|
instrumentation.setupTelemetry({
|
|
@@ -709,6 +152,13 @@ export async function startTelemetry(): Promise<void> {
|
|
|
709
152
|
});
|
|
710
153
|
});
|
|
711
154
|
}
|
|
155
|
+
|
|
156
|
+
// A SERVER span per request, opened by the app itself rather than by
|
|
157
|
+
// hooking node:http: \`GET /users/:id\`, continuing an incoming traceparent,
|
|
158
|
+
// with the handler — its logs, fetches and queries — running inside it.
|
|
159
|
+
return instrumentation.honoTelemetryMiddleware({
|
|
160
|
+
ignorePaths: options.ignorePaths,
|
|
161
|
+
});
|
|
712
162
|
}
|
|
713
163
|
`;
|
|
714
164
|
}
|
|
@@ -811,7 +261,7 @@ var EndpointGenerator = class extends ConstructGenerator {
|
|
|
811
261
|
const routes = [];
|
|
812
262
|
if (constructs.length === 0) return routes;
|
|
813
263
|
if (target === "server") {
|
|
814
|
-
await this.generateEndpointsFile(outputDir, constructs
|
|
264
|
+
await this.generateEndpointsFile(outputDir, constructs);
|
|
815
265
|
const appFile = await this.generateAppFile(outputDir, context);
|
|
816
266
|
routes.push({
|
|
817
267
|
path: "*",
|
|
@@ -847,7 +297,7 @@ var EndpointGenerator = class extends ConstructGenerator {
|
|
|
847
297
|
await writeFile(handlerPath, this.generateAWSApiGatewayV2Handler(importPath, exportName, context.storageDrivers));
|
|
848
298
|
return handlerPath;
|
|
849
299
|
}
|
|
850
|
-
async generateEndpointsFile(outputDir, endpoints
|
|
300
|
+
async generateEndpointsFile(outputDir, endpoints) {
|
|
851
301
|
const endpointsPath = join(outputDir, "endpoints.ts");
|
|
852
302
|
const importsByFile = /* @__PURE__ */ new Map();
|
|
853
303
|
for (const { path, key } of endpoints) {
|
|
@@ -855,19 +305,16 @@ var EndpointGenerator = class extends ConstructGenerator {
|
|
|
855
305
|
if (!importsByFile.has(importPath)) importsByFile.set(importPath, []);
|
|
856
306
|
importsByFile.get(importPath)?.push(key);
|
|
857
307
|
}
|
|
858
|
-
const endpointImports = Array.from(importsByFile.entries()).map(([importPath, exports]) => `import { ${exports.join(", ")} } from '${importPath}';`).join("\n");
|
|
859
|
-
const allExportNames = endpoints.map(({ key }) => key);
|
|
860
|
-
if (context.production?.enabled && context.production.optimizedHandlers) return this.generateOptimizedEndpointsFile(endpointsPath, endpoints, endpointImports, allExportNames);
|
|
861
308
|
const content = `import type { EnvironmentParser } from '@geekmidas/envkit';
|
|
862
309
|
import type { Logger } from '@geekmidas/logger';
|
|
863
310
|
import { HonoEndpoint } from '@geekmidas/constructs/hono';
|
|
864
311
|
import { Endpoint } from '@geekmidas/constructs/endpoints';
|
|
865
312
|
import { ServiceDiscovery } from '@geekmidas/services';
|
|
866
313
|
import type { Hono } from 'hono';
|
|
867
|
-
${
|
|
314
|
+
${Array.from(importsByFile.entries()).map(([importPath, exports]) => `import { ${exports.join(", ")} } from '${importPath}';`).join("\n")}
|
|
868
315
|
|
|
869
316
|
export const endpoints: Endpoint<any, any, any, any, any, any, any, any, any, any, any, any>[] = [
|
|
870
|
-
${
|
|
317
|
+
${endpoints.map(({ key }) => key).join(",\n ")}
|
|
871
318
|
];
|
|
872
319
|
|
|
873
320
|
export async function setupEndpoints(
|
|
@@ -904,41 +351,6 @@ export async function setupEndpoints(
|
|
|
904
351
|
await writeFile(endpointsPath, content);
|
|
905
352
|
return endpointsPath;
|
|
906
353
|
}
|
|
907
|
-
/**
|
|
908
|
-
* Generate optimized endpoints files with nested folder structure (per-endpoint files)
|
|
909
|
-
*/
|
|
910
|
-
async generateOptimizedEndpointsFile(endpointsPath, endpoints, _endpointImports, _allExportNames) {
|
|
911
|
-
const logger = console;
|
|
912
|
-
const outputDir = dirname(endpointsPath);
|
|
913
|
-
const endpointsDir = join(outputDir, "endpoints");
|
|
914
|
-
await mkdir(join(endpointsDir, "minimal"), { recursive: true });
|
|
915
|
-
await mkdir(join(endpointsDir, "standard"), { recursive: true });
|
|
916
|
-
await mkdir(join(endpointsDir, "full"), { recursive: true });
|
|
917
|
-
const analyses = endpoints.map(({ key, construct }) => analyzeEndpoint(construct, key));
|
|
918
|
-
const endpointImports = endpoints.map(({ key, path }) => {
|
|
919
|
-
const tierDir = join(endpointsDir, "standard");
|
|
920
|
-
return {
|
|
921
|
-
exportName: key,
|
|
922
|
-
importPath: relative(tierDir, path.relative).replace(/\.ts$/, ".js")
|
|
923
|
-
};
|
|
924
|
-
});
|
|
925
|
-
const summary = summarizeAnalysis(analyses);
|
|
926
|
-
logger.log(`\n📊 Endpoint Analysis:`);
|
|
927
|
-
logger.log(` Total: ${summary.total} endpoints`);
|
|
928
|
-
logger.log(` - Minimal (near-raw-Hono): ${summary.byTier.minimal} endpoints`);
|
|
929
|
-
logger.log(` - Standard (auth/services): ${summary.byTier.standard} endpoints`);
|
|
930
|
-
logger.log(` - Full (audits/rls/rate-limit): ${summary.byTier.full} endpoints`);
|
|
931
|
-
const files = generateEndpointFilesNested(analyses, endpointImports);
|
|
932
|
-
for (const [filename, content] of Object.entries(files)) {
|
|
933
|
-
const filePath = join(endpointsDir, filename);
|
|
934
|
-
await mkdir(dirname(filePath), { recursive: true });
|
|
935
|
-
await writeFile(filePath, content);
|
|
936
|
-
}
|
|
937
|
-
const endpointFiles = Object.keys(files).filter((f) => !f.endsWith("index.ts") && !f.endsWith("validators.ts")).length;
|
|
938
|
-
const indexFiles = Object.keys(files).filter((f) => f.endsWith("index.ts")).length;
|
|
939
|
-
logger.log(` Generated ${endpointFiles} endpoint files + ${indexFiles} index files + validators.ts`);
|
|
940
|
-
return join(endpointsDir, "index.ts");
|
|
941
|
-
}
|
|
942
354
|
async generateAppFile(outputDir, context) {
|
|
943
355
|
if (context.production?.enabled) return this.generateProductionAppFile(outputDir, context);
|
|
944
356
|
const appPath = join(outputDir, "app.ts");
|
|
@@ -1009,7 +421,7 @@ import { ${databaseApi.database.exportName} as __introspectedDb } from '${import
|
|
|
1009
421
|
(honoApp as any).__injectWebSocket = injectWebSocket;
|
|
1010
422
|
logger.info('Telescope WebSocket enabled');
|
|
1011
423
|
} catch (e) {
|
|
1012
|
-
logger.warn({
|
|
424
|
+
logger.warn({ err: e }, 'WebSocket support not available - install @hono/node-ws for real-time updates');
|
|
1013
425
|
}
|
|
1014
426
|
` : "";
|
|
1015
427
|
let telescopeSetup = "";
|
|
@@ -1158,19 +570,19 @@ ${afterSetupCall}
|
|
|
1158
570
|
// Mount pushed subscribers' routes and start polled ones. What comes
|
|
1159
571
|
// back subscribes the routes, once the server can answer them.
|
|
1160
572
|
const subscribeForPush = await setupSubscribers(honoApp, envParser, logger).catch((error) => {
|
|
1161
|
-
logger.error({ error }, 'Failed to start subscribers');
|
|
573
|
+
logger.error({ err: error }, 'Failed to start subscribers');
|
|
1162
574
|
return async (_port: number) => {};
|
|
1163
575
|
});
|
|
1164
576
|
|
|
1165
577
|
// Start queue workers in background (non-blocking, local development only)
|
|
1166
578
|
await setupQueues(envParser, logger).catch((error) => {
|
|
1167
|
-
logger.error({ error }, 'Failed to start queue workers');
|
|
579
|
+
logger.error({ err: error }, 'Failed to start queue workers');
|
|
1168
580
|
});
|
|
1169
581
|
|
|
1170
582
|
// Schedule this app's crons. Caught like the others: a scheduler that
|
|
1171
583
|
// cannot start is not a reason for the HTTP server not to.
|
|
1172
584
|
await setupCrons(envParser, logger).catch((error) => {
|
|
1173
|
-
logger.error({ error }, 'Failed to schedule crons');
|
|
585
|
+
logger.error({ err: error }, 'Failed to schedule crons');
|
|
1174
586
|
});
|
|
1175
587
|
|
|
1176
588
|
logger.info({ port }, 'Starting server');
|
|
@@ -1246,19 +658,19 @@ export const handler = adapter.handler;
|
|
|
1246
658
|
// Mount pushed subscribers' routes and start polled ones. What comes
|
|
1247
659
|
// back subscribes the routes, once the server can answer them.
|
|
1248
660
|
const subscribeForPush = await setupSubscribers(honoApp, envParser, logger).catch((error) => {
|
|
1249
|
-
logger.error({ error }, 'Failed to start subscribers');
|
|
661
|
+
logger.error({ err: error }, 'Failed to start subscribers');
|
|
1250
662
|
return async (_port: number) => {};
|
|
1251
663
|
});
|
|
1252
664
|
|
|
1253
665
|
// Start queue workers in background
|
|
1254
666
|
await setupQueues(envParser, logger).catch((error) => {
|
|
1255
|
-
logger.error({ error }, 'Failed to start queue workers');
|
|
667
|
+
logger.error({ err: error }, 'Failed to start queue workers');
|
|
1256
668
|
});
|
|
1257
669
|
|
|
1258
670
|
// Schedule this app's crons. Caught like the others: a scheduler that
|
|
1259
671
|
// cannot start is not a reason for the HTTP server not to.
|
|
1260
672
|
await setupCrons(envParser, logger).catch((error) => {
|
|
1261
|
-
logger.error({ error }, 'Failed to schedule crons');
|
|
673
|
+
logger.error({ err: error }, 'Failed to schedule crons');
|
|
1262
674
|
});
|
|
1263
675
|
` : "";
|
|
1264
676
|
const subscriberImport = includeSubscribers ? `import { setupSubscribers } from './subscribers.js';
|
|
@@ -1286,14 +698,13 @@ import { setupCrons } from './crons.js';` : "";
|
|
|
1286
698
|
if (typeof close === 'function') close.call(server, () => resolve());
|
|
1287
699
|
else resolve();
|
|
1288
700
|
});
|
|
1289
|
-
await runShutdownHooks((error) => logger.error({ error }, 'Shutdown hook failed'));
|
|
701
|
+
await runShutdownHooks((error) => logger.error({ err: error }, 'Shutdown hook failed'));
|
|
1290
702
|
process.exit(0);
|
|
1291
703
|
};
|
|
1292
704
|
|
|
1293
705
|
process.on('SIGTERM', shutdown);
|
|
1294
706
|
process.on('SIGINT', shutdown);
|
|
1295
707
|
` : "";
|
|
1296
|
-
const endpointsImportPath = production.optimizedHandlers ? "./endpoints/index.js" : "./endpoints.js";
|
|
1297
708
|
const content = `/**
|
|
1298
709
|
* Generated production server application
|
|
1299
710
|
*
|
|
@@ -1305,7 +716,7 @@ import { setupCrons } from './crons.js';` : "";
|
|
|
1305
716
|
*/
|
|
1306
717
|
import { Hono } from 'hono';
|
|
1307
718
|
import type { Hono as HonoType } from 'hono';
|
|
1308
|
-
${enableGracefulShutdown ? "import { runShutdownHooks } from '@geekmidas/constructs';\n" : ""}import { setupEndpoints } from '
|
|
719
|
+
${enableGracefulShutdown ? "import { runShutdownHooks } from '@geekmidas/constructs';\n" : ""}import { setupEndpoints } from './endpoints.js';
|
|
1309
720
|
${subscriberImport}
|
|
1310
721
|
${runtime.imports}
|
|
1311
722
|
${runtime.bindings}
|
|
@@ -1362,7 +773,7 @@ export async function createApp(app?: HonoType): Promise<ServerApp> {
|
|
|
1362
773
|
http.statusCode as 500,
|
|
1363
774
|
);
|
|
1364
775
|
}
|
|
1365
|
-
logger.error({ error }, 'Unhandled error');
|
|
776
|
+
logger.error({ err: error }, 'Unhandled error');
|
|
1366
777
|
return c.json({ message: 'Internal Server Error' }, 500);
|
|
1367
778
|
});
|
|
1368
779
|
${cors.setup}${beforeSetupCall}
|
|
@@ -1407,28 +818,36 @@ export default createApp;
|
|
|
1407
818
|
async generateProductionServerEntry(outputDir, context) {
|
|
1408
819
|
const serverPath = join(outputDir, "server.ts");
|
|
1409
820
|
await writeFile(join(outputDir, "telemetry.ts"), generateTelemetryModule(context.telemetry));
|
|
1410
|
-
|
|
821
|
+
const content = `#!/usr/bin/env node
|
|
1411
822
|
/**
|
|
1412
823
|
* Production server entry point
|
|
1413
824
|
* Generated by 'gkm build --production'
|
|
1414
825
|
*/
|
|
1415
826
|
import { serve } from '@hono/node-server';
|
|
827
|
+
import { Hono } from 'hono';
|
|
1416
828
|
import { startTelemetry } from './telemetry.js';
|
|
1417
829
|
|
|
1418
830
|
// Before the app is imported, so the libraries it loads are instrumented.
|
|
1419
|
-
await startTelemetry(
|
|
831
|
+
const requestSpans = await startTelemetry({
|
|
832
|
+
ignorePaths: [${JSON.stringify(context.production?.healthCheck ?? "/health")}, '/ready'],
|
|
833
|
+
});
|
|
1420
834
|
|
|
1421
835
|
const { createApp } = await import('./app.js');
|
|
1422
836
|
|
|
1423
837
|
const port = Number(process.env.PORT) || 3000;
|
|
1424
838
|
|
|
1425
|
-
|
|
839
|
+
// The span middleware goes on before any route, so every request runs in it.
|
|
840
|
+
const app = new Hono();
|
|
841
|
+
if (requestSpans) app.use('*', requestSpans);
|
|
842
|
+
|
|
843
|
+
const { start } = await createApp(app);
|
|
1426
844
|
|
|
1427
845
|
await start({
|
|
1428
846
|
port,
|
|
1429
847
|
serve: (app, port) => serve({ fetch: app.fetch, port }),
|
|
1430
848
|
});
|
|
1431
|
-
|
|
849
|
+
`;
|
|
850
|
+
await writeFile(serverPath, content);
|
|
1432
851
|
}
|
|
1433
852
|
};
|
|
1434
853
|
//#endregion
|
|
@@ -1939,4 +1358,4 @@ function unknownType(name) {
|
|
|
1939
1358
|
//#endregion
|
|
1940
1359
|
export { telemetryFor as i, EndpointGenerator as n, runtimeFor as r, OpenApiTsGenerator as t };
|
|
1941
1360
|
|
|
1942
|
-
//# sourceMappingURL=OpenApiTsGenerator-
|
|
1361
|
+
//# sourceMappingURL=OpenApiTsGenerator-Bhr_XWWN.mjs.map
|