@voyant-travel/hono 0.118.4 → 0.119.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/dist/app.d.ts +22 -0
- package/dist/app.d.ts.map +1 -1
- package/dist/app.js +34 -10
- package/dist/openapi.d.ts +63 -0
- package/dist/openapi.d.ts.map +1 -1
- package/dist/openapi.js +118 -0
- package/package.json +3 -3
package/dist/app.d.ts
CHANGED
|
@@ -17,6 +17,20 @@ export interface LazyMount {
|
|
|
17
17
|
prefix: string;
|
|
18
18
|
load: LazyRoutesLoader;
|
|
19
19
|
}
|
|
20
|
+
/**
|
|
21
|
+
* A single module route mount recorded at mount time so a build-time generator
|
|
22
|
+
* can produce one self-contained OpenAPI document per module (voyant#2733).
|
|
23
|
+
* Mirrors `ModuleMount` in `./openapi.ts` (structurally identical, declared here
|
|
24
|
+
* to keep the build-time-only openapi module out of the runtime path). `load`
|
|
25
|
+
* returns the sub-app without serving it — eager mounts wrap the already-built
|
|
26
|
+
* routes as `() => routes`; lazy mounts pass their loader.
|
|
27
|
+
*/
|
|
28
|
+
export interface ModuleMount {
|
|
29
|
+
moduleName: string;
|
|
30
|
+
/** Absolute surface mount prefix, or `"/"` for absolute `lazyRoutes`. */
|
|
31
|
+
prefix: string;
|
|
32
|
+
load: () => Hono<any, any, any> | Promise<Hono<any, any, any>>;
|
|
33
|
+
}
|
|
20
34
|
/**
|
|
21
35
|
* App handle returned alongside the Hono instance. Carries `ready()` for
|
|
22
36
|
* headless / sibling-process deployments that need to fire the lazy
|
|
@@ -59,6 +73,14 @@ export interface VoyantAppExtensions<TBindings = unknown> {
|
|
|
59
73
|
* `.openapi()` operations via `mergeLazyOpenApiPaths`. Never read at runtime.
|
|
60
74
|
*/
|
|
61
75
|
lazyMounts: LazyMount[];
|
|
76
|
+
/**
|
|
77
|
+
* Every module route mount (admin + public, eager + lazy), tagged with its
|
|
78
|
+
* owning module name (voyant#2733). A build-time generator reads this to
|
|
79
|
+
* produce one self-contained OpenAPI document per module via
|
|
80
|
+
* `generateModuleOpenApiDocuments`, keeping the module boundary authoritative
|
|
81
|
+
* rather than guessed from path prefixes. Never read at runtime.
|
|
82
|
+
*/
|
|
83
|
+
moduleMounts: ModuleMount[];
|
|
62
84
|
}
|
|
63
85
|
/**
|
|
64
86
|
* Low-level app factory: given an already-resolved `modules`/`extensions` set
|
package/dist/app.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"app.d.ts","sourceRoot":"","sources":["../src/app.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,MAAM,CAAA;AAQhC,OAAO,EAAE,KAAK,gBAAgB,EAA0C,MAAM,kBAAkB,CAAA;AA+BhG,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAY,eAAe,EAAE,MAAM,YAAY,CAAA;AAI5F,4EAA4E;AAC5E,KAAK,QAAQ,CAAC,SAAS,SAAS,cAAc,IAAI;IAChD,QAAQ,EAAE,SAAS,CAAA;IACnB,SAAS,EAAE,eAAe,CAAA;CAC3B,CAAA;AAuBD;;;;;GAKG;AACH,MAAM,WAAW,SAAS;IACxB,yEAAyE;IACzE,MAAM,EAAE,MAAM,CAAA;IACd,IAAI,EAAE,gBAAgB,CAAA;CACvB;AAED;;;;;;;;;;GAUG;AACH,MAAM,WAAW,mBAAmB,CAAC,SAAS,GAAG,OAAO;IACtD;;;;;;;;;;;OAWG;IACH,KAAK,CAAC,QAAQ,CAAC,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAC1C;;;;;;;;OAQG;IACH,QAAQ,EAAE,OAAO,qBAAqB,EAAE,QAAQ,CAAA;IAChD;;;;;OAKG;IACH,UAAU,EAAE,SAAS,EAAE,CAAA;
|
|
1
|
+
{"version":3,"file":"app.d.ts","sourceRoot":"","sources":["../src/app.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,MAAM,CAAA;AAQhC,OAAO,EAAE,KAAK,gBAAgB,EAA0C,MAAM,kBAAkB,CAAA;AA+BhG,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAY,eAAe,EAAE,MAAM,YAAY,CAAA;AAI5F,4EAA4E;AAC5E,KAAK,QAAQ,CAAC,SAAS,SAAS,cAAc,IAAI;IAChD,QAAQ,EAAE,SAAS,CAAA;IACnB,SAAS,EAAE,eAAe,CAAA;CAC3B,CAAA;AAuBD;;;;;GAKG;AACH,MAAM,WAAW,SAAS;IACxB,yEAAyE;IACzE,MAAM,EAAE,MAAM,CAAA;IACd,IAAI,EAAE,gBAAgB,CAAA;CACvB;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,WAAW;IAC1B,UAAU,EAAE,MAAM,CAAA;IAClB,yEAAyE;IACzE,MAAM,EAAE,MAAM,CAAA;IAEd,IAAI,EAAE,MAAM,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC,CAAA;CAC/D;AAED;;;;;;;;;;GAUG;AACH,MAAM,WAAW,mBAAmB,CAAC,SAAS,GAAG,OAAO;IACtD;;;;;;;;;;;OAWG;IACH,KAAK,CAAC,QAAQ,CAAC,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAC1C;;;;;;;;OAQG;IACH,QAAQ,EAAE,OAAO,qBAAqB,EAAE,QAAQ,CAAA;IAChD;;;;;OAKG;IACH,UAAU,EAAE,SAAS,EAAE,CAAA;IACvB;;;;;;OAMG;IACH,YAAY,EAAE,WAAW,EAAE,CAAA;CAC5B;AAED;;;;;;GAMG;AACH,wBAAgB,QAAQ,CAAC,SAAS,SAAS,cAAc,EACvD,MAAM,EAAE,eAAe,CAAC,SAAS,CAAC,GACjC,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,GAAG,mBAAmB,CAAC,SAAS,CAAC,CA+f5D"}
|
package/dist/app.js
CHANGED
|
@@ -410,56 +410,79 @@ export function mountApp(config) {
|
|
|
410
410
|
// them so the prefix logic is single-sourced here. Cheap array pushes — no
|
|
411
411
|
// eager `import()` and no runtime read.
|
|
412
412
|
const lazyMounts = [];
|
|
413
|
+
// Per-module OpenAPI generation manifest (voyant#2733). Recorded alongside the
|
|
414
|
+
// mount so the module name + real prefix are single-sourced here; `publicPath`
|
|
415
|
+
// overrides are captured with their actual mount, not re-derived. Eager routes
|
|
416
|
+
// are wrapped as `() => routes` so the generator's loader-based path is
|
|
417
|
+
// uniform across eager + lazy. Webhook routes are intentionally excluded — the
|
|
418
|
+
// per-module docs cover the admin/storefront surfaces only.
|
|
419
|
+
const moduleMounts = [];
|
|
413
420
|
// Mount module routes
|
|
414
421
|
for (const mod of allModules) {
|
|
415
|
-
const
|
|
416
|
-
const
|
|
422
|
+
const moduleName = mod.module.name;
|
|
423
|
+
const adminPrefix = `/v1/admin/${moduleName}`;
|
|
424
|
+
const publicPrefix = resolveSurfaceMountPath("/v1/public", mod.publicPath, moduleName);
|
|
417
425
|
if (mod.adminRoutes) {
|
|
418
|
-
|
|
426
|
+
const adminRoutes = mod.adminRoutes;
|
|
427
|
+
app.route(adminPrefix, adminRoutes);
|
|
428
|
+
moduleMounts.push({ moduleName, prefix: adminPrefix, load: () => adminRoutes });
|
|
419
429
|
}
|
|
420
430
|
if (mod.publicRoutes) {
|
|
421
|
-
|
|
431
|
+
const publicRoutes = mod.publicRoutes;
|
|
432
|
+
app.route(publicPrefix, publicRoutes);
|
|
433
|
+
moduleMounts.push({ moduleName, prefix: publicPrefix, load: () => publicRoutes });
|
|
422
434
|
}
|
|
423
435
|
if (mod.lazyAdminRoutes) {
|
|
424
436
|
mountLazyRoutesAt(app, adminPrefix, mod.lazyAdminRoutes);
|
|
425
437
|
lazyMounts.push({ prefix: adminPrefix, load: mod.lazyAdminRoutes });
|
|
438
|
+
moduleMounts.push({ moduleName, prefix: adminPrefix, load: mod.lazyAdminRoutes });
|
|
426
439
|
}
|
|
427
440
|
if (mod.lazyPublicRoutes) {
|
|
428
441
|
mountLazyRoutesAt(app, publicPrefix, mod.lazyPublicRoutes);
|
|
429
442
|
lazyMounts.push({ prefix: publicPrefix, load: mod.lazyPublicRoutes });
|
|
443
|
+
moduleMounts.push({ moduleName, prefix: publicPrefix, load: mod.lazyPublicRoutes });
|
|
430
444
|
}
|
|
431
445
|
if (mod.lazyRoutes) {
|
|
432
446
|
mountLazyRoutePaths(app, mod.lazyRoutes.paths, mod.lazyRoutes.load);
|
|
433
447
|
lazyMounts.push({ prefix: "/", load: mod.lazyRoutes.load });
|
|
448
|
+
moduleMounts.push({ moduleName, prefix: "/", load: mod.lazyRoutes.load });
|
|
434
449
|
}
|
|
435
450
|
if (mod.webhookRoutes) {
|
|
436
|
-
app.route(`/v1/${
|
|
451
|
+
app.route(`/v1/${moduleName}`, mod.webhookRoutes);
|
|
437
452
|
}
|
|
438
453
|
}
|
|
439
454
|
// Mount extension routes
|
|
440
455
|
for (const ext of allExtensions) {
|
|
441
|
-
const
|
|
442
|
-
const
|
|
456
|
+
const moduleName = ext.extension.module;
|
|
457
|
+
const adminPrefix = `/v1/admin/${moduleName}`;
|
|
458
|
+
const publicPrefix = resolveSurfaceMountPath("/v1/public", ext.publicPath, moduleName);
|
|
443
459
|
if (ext.adminRoutes) {
|
|
444
|
-
|
|
460
|
+
const adminRoutes = ext.adminRoutes;
|
|
461
|
+
app.route(adminPrefix, adminRoutes);
|
|
462
|
+
moduleMounts.push({ moduleName, prefix: adminPrefix, load: () => adminRoutes });
|
|
445
463
|
}
|
|
446
464
|
if (ext.publicRoutes) {
|
|
447
|
-
|
|
465
|
+
const publicRoutes = ext.publicRoutes;
|
|
466
|
+
app.route(publicPrefix, publicRoutes);
|
|
467
|
+
moduleMounts.push({ moduleName, prefix: publicPrefix, load: () => publicRoutes });
|
|
448
468
|
}
|
|
449
469
|
if (ext.lazyAdminRoutes) {
|
|
450
470
|
mountLazyRoutesAt(app, adminPrefix, ext.lazyAdminRoutes);
|
|
451
471
|
lazyMounts.push({ prefix: adminPrefix, load: ext.lazyAdminRoutes });
|
|
472
|
+
moduleMounts.push({ moduleName, prefix: adminPrefix, load: ext.lazyAdminRoutes });
|
|
452
473
|
}
|
|
453
474
|
if (ext.lazyPublicRoutes) {
|
|
454
475
|
mountLazyRoutesAt(app, publicPrefix, ext.lazyPublicRoutes);
|
|
455
476
|
lazyMounts.push({ prefix: publicPrefix, load: ext.lazyPublicRoutes });
|
|
477
|
+
moduleMounts.push({ moduleName, prefix: publicPrefix, load: ext.lazyPublicRoutes });
|
|
456
478
|
}
|
|
457
479
|
if (ext.lazyRoutes) {
|
|
458
480
|
mountLazyRoutePaths(app, ext.lazyRoutes.paths, ext.lazyRoutes.load);
|
|
459
481
|
lazyMounts.push({ prefix: "/", load: ext.lazyRoutes.load });
|
|
482
|
+
moduleMounts.push({ moduleName, prefix: "/", load: ext.lazyRoutes.load });
|
|
460
483
|
}
|
|
461
484
|
if (ext.webhookRoutes) {
|
|
462
|
-
app.route(`/v1/${
|
|
485
|
+
app.route(`/v1/${moduleName}`, ext.webhookRoutes);
|
|
463
486
|
}
|
|
464
487
|
}
|
|
465
488
|
// Additional routes
|
|
@@ -477,6 +500,7 @@ export function mountApp(config) {
|
|
|
477
500
|
const augmented = app;
|
|
478
501
|
augmented.eventBus = eventBus;
|
|
479
502
|
augmented.lazyMounts = lazyMounts;
|
|
503
|
+
augmented.moduleMounts = moduleMounts;
|
|
480
504
|
augmented.ready = (bindings) => ensureRuntimeBootstrapped(bindings ?? {});
|
|
481
505
|
return augmented;
|
|
482
506
|
}
|
package/dist/openapi.d.ts
CHANGED
|
@@ -66,6 +66,23 @@ export interface LazyMount {
|
|
|
66
66
|
prefix: string;
|
|
67
67
|
load: LazyRoutesLoader;
|
|
68
68
|
}
|
|
69
|
+
/**
|
|
70
|
+
* A single module route mount recorded by `mountApp` for build-time per-module
|
|
71
|
+
* spec generation (voyant#2733). One module contributes several mounts (admin +
|
|
72
|
+
* public, eager + lazy), all tagged with the same `moduleName` — so the module
|
|
73
|
+
* boundary is the authoritative one from the registration, not a path-prefix
|
|
74
|
+
* guess. `prefix` is the real absolute mount (base path included), so the
|
|
75
|
+
* generated per-module doc carries correct absolute paths — including
|
|
76
|
+
* `publicPath` overrides whose prefix isn't the module name (e.g. a module that
|
|
77
|
+
* mounts its storefront routes under `/v1/public/booking-engine`). `load`
|
|
78
|
+
* returns the sub-app without serving it (eager mounts wrap the already-built
|
|
79
|
+
* app as `() => routes`; lazy mounts pass their loader).
|
|
80
|
+
*/
|
|
81
|
+
export interface ModuleMount {
|
|
82
|
+
moduleName: string;
|
|
83
|
+
prefix: string;
|
|
84
|
+
load: () => AnyApp | Promise<AnyApp>;
|
|
85
|
+
}
|
|
69
86
|
/**
|
|
70
87
|
* Eager-merge lazy route families into a generated base document (voyant#2114).
|
|
71
88
|
*
|
|
@@ -86,5 +103,51 @@ export interface LazyMount {
|
|
|
86
103
|
* (`@asteasolutions/zod-to-openapi` stays out of the Worker bundle).
|
|
87
104
|
*/
|
|
88
105
|
export declare function mergeLazyOpenApiPaths(base: OpenApiDocument, mounts: readonly LazyMount[], options: GenerateOpenApiOptions): Promise<OpenApiDocument>;
|
|
106
|
+
/**
|
|
107
|
+
* Generate one self-contained OpenAPI document per module from its recorded
|
|
108
|
+
* mounts (voyant#2733).
|
|
109
|
+
*
|
|
110
|
+
* Instead of building one giant composed document and splitting it by path
|
|
111
|
+
* prefix, this generates each module's spec directly from the routes the module
|
|
112
|
+
* registered — the authoritative module boundary. Each module's admin + public
|
|
113
|
+
* (+ lazy) sub-apps are re-mounted into a throwaway `OpenAPIHono` at their real
|
|
114
|
+
* absolute prefix (reusing the exact prefix-merge `OpenAPIHono.route(...)`
|
|
115
|
+
* applies), then its `getOpenAPI31Document()` is read. The returned docs are
|
|
116
|
+
* self-contained (each carries its own referenced components), so they render
|
|
117
|
+
* and diff cleanly on their own — unlike a 7 MB aggregate.
|
|
118
|
+
*
|
|
119
|
+
* A mount whose loader throws, or that returns a plain `Hono` with no
|
|
120
|
+
* `.openapi()` registry, is skipped without failing the module. Modules that
|
|
121
|
+
* contribute no documented operation are omitted from the result entirely.
|
|
122
|
+
*
|
|
123
|
+
* Build-time only — same constraint as the rest of this module
|
|
124
|
+
* (`@asteasolutions/zod-to-openapi` must stay out of the Worker bundle).
|
|
125
|
+
*/
|
|
126
|
+
export declare function generateModuleOpenApiDocuments(mounts: readonly ModuleMount[], options: GenerateOpenApiOptions): Promise<Map<string, OpenApiDocument>>;
|
|
127
|
+
/**
|
|
128
|
+
* Partition a composed document into one document per module, covering EVERY
|
|
129
|
+
* admin/storefront path (voyant#2733).
|
|
130
|
+
*
|
|
131
|
+
* `generateModuleOpenApiDocuments` alone only sees routes recorded in the module
|
|
132
|
+
* mount manifest — it misses `additionalRoutes` and any route mounted directly
|
|
133
|
+
* on the composed app (e.g. the operator's workflow-runs admin surface, the
|
|
134
|
+
* `_meta/capabilities` route). Those were present in the old committed aggregate
|
|
135
|
+
* and must not silently vanish from the committed per-module specs.
|
|
136
|
+
*
|
|
137
|
+
* This uses the manifest as the *authoritative* owner for the routes it knows
|
|
138
|
+
* about — which is what makes `publicPath` overrides (whose prefix isn't the
|
|
139
|
+
* module name, e.g. `/v1/public/booking-engine`) land under the right module —
|
|
140
|
+
* and falls back to the path's own second segment for anything the manifest
|
|
141
|
+
* doesn't claim. The result therefore partitions the full surface exactly: every
|
|
142
|
+
* `/v1/admin/*` and `/v1/public/*` path lands in exactly one module document.
|
|
143
|
+
* Non-surface routes (`/v1/<name>` webhooks, legacy `/v1/*`) live only in the
|
|
144
|
+
* aggregate, as before.
|
|
145
|
+
*
|
|
146
|
+
* Each per-module document carries the aggregate's shared `components` verbatim
|
|
147
|
+
* (there is ~one), so it stays a valid, self-contained OpenAPI document.
|
|
148
|
+
*
|
|
149
|
+
* Build-time only.
|
|
150
|
+
*/
|
|
151
|
+
export declare function splitDocumentByModule(full: OpenApiDocument, mounts: readonly ModuleMount[], options: GenerateOpenApiOptions): Promise<Map<string, OpenApiDocument>>;
|
|
89
152
|
export {};
|
|
90
153
|
//# sourceMappingURL=openapi.d.ts.map
|
package/dist/openapi.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"openapi.d.ts","sourceRoot":"","sources":["../src/openapi.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAC/C,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,MAAM,CAAA;AAEhC,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAA;AAExD;;;;;;;;;;;;GAYG;AAEH,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,MAAM,CAAA;IACb,OAAO,EAAE,MAAM,CAAA;IACf,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,8EAA8E;IAC9E,CAAC,SAAS,EAAE,KAAK,MAAM,EAAE,GAAG,OAAO,CAAA;CACpC;AAED,MAAM,WAAW,aAAa;IAC5B,GAAG,EAAE,MAAM,CAAA;IACX,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,CAAC,SAAS,EAAE,KAAK,MAAM,EAAE,GAAG,OAAO,CAAA;CACpC;AAED,MAAM,WAAW,sBAAsB;IACrC,IAAI,EAAE,WAAW,CAAA;IACjB,OAAO,CAAC,EAAE,aAAa,EAAE,CAAA;CAC1B;AAGD,KAAK,MAAM,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,CAAA;AAEjC;;;GAGG;AACH,MAAM,MAAM,eAAe,GAAG,UAAU,CAAC,WAAW,CAAC,sBAAsB,CAAC,CAAC,CAAA;AAE7E;;;;GAIG;AACH,wBAAgB,uBAAuB,CACrC,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,sBAAsB,GAC9B,eAAe,CASjB;AAED;;;;GAIG;AACH,MAAM,MAAM,UAAU,GAAG,OAAO,GAAG,YAAY,CAAA;AAO/C;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,eAAe,EAAE,OAAO,EAAE,UAAU,GAAG,eAAe,CAMxF;AAED;;;;;;GAMG;AACH,MAAM,WAAW,SAAS;IACxB,MAAM,EAAE,MAAM,CAAA;IACd,IAAI,EAAE,gBAAgB,CAAA;CACvB;AAYD;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAsB,qBAAqB,CACzC,IAAI,EAAE,eAAe,EACrB,MAAM,EAAE,SAAS,SAAS,EAAE,EAC5B,OAAO,EAAE,sBAAsB,GAC9B,OAAO,CAAC,eAAe,CAAC,CA8D1B"}
|
|
1
|
+
{"version":3,"file":"openapi.d.ts","sourceRoot":"","sources":["../src/openapi.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAC/C,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,MAAM,CAAA;AAEhC,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAA;AAExD;;;;;;;;;;;;GAYG;AAEH,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,MAAM,CAAA;IACb,OAAO,EAAE,MAAM,CAAA;IACf,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,8EAA8E;IAC9E,CAAC,SAAS,EAAE,KAAK,MAAM,EAAE,GAAG,OAAO,CAAA;CACpC;AAED,MAAM,WAAW,aAAa;IAC5B,GAAG,EAAE,MAAM,CAAA;IACX,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,CAAC,SAAS,EAAE,KAAK,MAAM,EAAE,GAAG,OAAO,CAAA;CACpC;AAED,MAAM,WAAW,sBAAsB;IACrC,IAAI,EAAE,WAAW,CAAA;IACjB,OAAO,CAAC,EAAE,aAAa,EAAE,CAAA;CAC1B;AAGD,KAAK,MAAM,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,CAAA;AAEjC;;;GAGG;AACH,MAAM,MAAM,eAAe,GAAG,UAAU,CAAC,WAAW,CAAC,sBAAsB,CAAC,CAAC,CAAA;AAE7E;;;;GAIG;AACH,wBAAgB,uBAAuB,CACrC,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,sBAAsB,GAC9B,eAAe,CASjB;AAED;;;;GAIG;AACH,MAAM,MAAM,UAAU,GAAG,OAAO,GAAG,YAAY,CAAA;AAO/C;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,eAAe,EAAE,OAAO,EAAE,UAAU,GAAG,eAAe,CAMxF;AAED;;;;;;GAMG;AACH,MAAM,WAAW,SAAS;IACxB,MAAM,EAAE,MAAM,CAAA;IACd,IAAI,EAAE,gBAAgB,CAAA;CACvB;AAED;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,WAAW;IAC1B,UAAU,EAAE,MAAM,CAAA;IAClB,MAAM,EAAE,MAAM,CAAA;IACd,IAAI,EAAE,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAA;CACrC;AAYD;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAsB,qBAAqB,CACzC,IAAI,EAAE,eAAe,EACrB,MAAM,EAAE,SAAS,SAAS,EAAE,EAC5B,OAAO,EAAE,sBAAsB,GAC9B,OAAO,CAAC,eAAe,CAAC,CA8D1B;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAsB,8BAA8B,CAClD,MAAM,EAAE,SAAS,WAAW,EAAE,EAC9B,OAAO,EAAE,sBAAsB,GAC9B,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC,CAsCvC;AAOD;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAsB,qBAAqB,CACzC,IAAI,EAAE,eAAe,EACrB,MAAM,EAAE,SAAS,WAAW,EAAE,EAC9B,OAAO,EAAE,sBAAsB,GAC9B,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC,CA4BvC"}
|
package/dist/openapi.js
CHANGED
|
@@ -112,3 +112,121 @@ export async function mergeLazyOpenApiPaths(base, mounts, options) {
|
|
|
112
112
|
}
|
|
113
113
|
return merged;
|
|
114
114
|
}
|
|
115
|
+
/**
|
|
116
|
+
* Generate one self-contained OpenAPI document per module from its recorded
|
|
117
|
+
* mounts (voyant#2733).
|
|
118
|
+
*
|
|
119
|
+
* Instead of building one giant composed document and splitting it by path
|
|
120
|
+
* prefix, this generates each module's spec directly from the routes the module
|
|
121
|
+
* registered — the authoritative module boundary. Each module's admin + public
|
|
122
|
+
* (+ lazy) sub-apps are re-mounted into a throwaway `OpenAPIHono` at their real
|
|
123
|
+
* absolute prefix (reusing the exact prefix-merge `OpenAPIHono.route(...)`
|
|
124
|
+
* applies), then its `getOpenAPI31Document()` is read. The returned docs are
|
|
125
|
+
* self-contained (each carries its own referenced components), so they render
|
|
126
|
+
* and diff cleanly on their own — unlike a 7 MB aggregate.
|
|
127
|
+
*
|
|
128
|
+
* A mount whose loader throws, or that returns a plain `Hono` with no
|
|
129
|
+
* `.openapi()` registry, is skipped without failing the module. Modules that
|
|
130
|
+
* contribute no documented operation are omitted from the result entirely.
|
|
131
|
+
*
|
|
132
|
+
* Build-time only — same constraint as the rest of this module
|
|
133
|
+
* (`@asteasolutions/zod-to-openapi` must stay out of the Worker bundle).
|
|
134
|
+
*/
|
|
135
|
+
export async function generateModuleOpenApiDocuments(mounts, options) {
|
|
136
|
+
const byModule = new Map();
|
|
137
|
+
for (const mount of mounts) {
|
|
138
|
+
const list = byModule.get(mount.moduleName);
|
|
139
|
+
if (list)
|
|
140
|
+
list.push(mount);
|
|
141
|
+
else
|
|
142
|
+
byModule.set(mount.moduleName, [mount]);
|
|
143
|
+
}
|
|
144
|
+
const result = new Map();
|
|
145
|
+
for (const [moduleName, moduleMounts] of byModule) {
|
|
146
|
+
const throwaway = new OpenAPIHono();
|
|
147
|
+
let mountedAny = false;
|
|
148
|
+
for (const { prefix, load } of moduleMounts) {
|
|
149
|
+
let subApp;
|
|
150
|
+
try {
|
|
151
|
+
subApp = await load();
|
|
152
|
+
}
|
|
153
|
+
catch (error) {
|
|
154
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
155
|
+
console.warn(`[voyant] openapi: module "${moduleName}" mount at "${prefix}" failed to load, skipping: ${message}`);
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
158
|
+
if (!isOpenApiHono(subApp))
|
|
159
|
+
continue;
|
|
160
|
+
throwaway.route(prefix === "/" ? "/" : prefix, subApp);
|
|
161
|
+
mountedAny = true;
|
|
162
|
+
}
|
|
163
|
+
if (!mountedAny)
|
|
164
|
+
continue;
|
|
165
|
+
const doc = throwaway.getOpenAPI31Document({
|
|
166
|
+
openapi: "3.1.0",
|
|
167
|
+
info: options.info,
|
|
168
|
+
...(options.servers ? { servers: options.servers } : {}),
|
|
169
|
+
});
|
|
170
|
+
if (!doc.paths || Object.keys(doc.paths).length === 0)
|
|
171
|
+
continue;
|
|
172
|
+
result.set(moduleName, doc);
|
|
173
|
+
}
|
|
174
|
+
return result;
|
|
175
|
+
}
|
|
176
|
+
/** Second path segment after the surface prefix — `/v1/admin/<seg>/...`. */
|
|
177
|
+
function moduleSegment(path) {
|
|
178
|
+
return path.split("/")[3] ?? "misc";
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Partition a composed document into one document per module, covering EVERY
|
|
182
|
+
* admin/storefront path (voyant#2733).
|
|
183
|
+
*
|
|
184
|
+
* `generateModuleOpenApiDocuments` alone only sees routes recorded in the module
|
|
185
|
+
* mount manifest — it misses `additionalRoutes` and any route mounted directly
|
|
186
|
+
* on the composed app (e.g. the operator's workflow-runs admin surface, the
|
|
187
|
+
* `_meta/capabilities` route). Those were present in the old committed aggregate
|
|
188
|
+
* and must not silently vanish from the committed per-module specs.
|
|
189
|
+
*
|
|
190
|
+
* This uses the manifest as the *authoritative* owner for the routes it knows
|
|
191
|
+
* about — which is what makes `publicPath` overrides (whose prefix isn't the
|
|
192
|
+
* module name, e.g. `/v1/public/booking-engine`) land under the right module —
|
|
193
|
+
* and falls back to the path's own second segment for anything the manifest
|
|
194
|
+
* doesn't claim. The result therefore partitions the full surface exactly: every
|
|
195
|
+
* `/v1/admin/*` and `/v1/public/*` path lands in exactly one module document.
|
|
196
|
+
* Non-surface routes (`/v1/<name>` webhooks, legacy `/v1/*`) live only in the
|
|
197
|
+
* aggregate, as before.
|
|
198
|
+
*
|
|
199
|
+
* Each per-module document carries the aggregate's shared `components` verbatim
|
|
200
|
+
* (there is ~one), so it stays a valid, self-contained OpenAPI document.
|
|
201
|
+
*
|
|
202
|
+
* Build-time only.
|
|
203
|
+
*/
|
|
204
|
+
export async function splitDocumentByModule(full, mounts, options) {
|
|
205
|
+
const moduleDocs = await generateModuleOpenApiDocuments(mounts, options);
|
|
206
|
+
const owner = new Map();
|
|
207
|
+
for (const [moduleName, doc] of moduleDocs) {
|
|
208
|
+
for (const path of Object.keys(doc.paths ?? {}))
|
|
209
|
+
owner.set(path, moduleName);
|
|
210
|
+
}
|
|
211
|
+
const buckets = new Map();
|
|
212
|
+
for (const [path, item] of Object.entries(full.paths ?? {})) {
|
|
213
|
+
if (!path.startsWith("/v1/admin/") && !path.startsWith("/v1/public/"))
|
|
214
|
+
continue;
|
|
215
|
+
const moduleName = owner.get(path) ?? moduleSegment(path);
|
|
216
|
+
let bucket = buckets.get(moduleName);
|
|
217
|
+
if (!bucket) {
|
|
218
|
+
bucket = {};
|
|
219
|
+
buckets.set(moduleName, bucket);
|
|
220
|
+
}
|
|
221
|
+
bucket[path] = item;
|
|
222
|
+
}
|
|
223
|
+
const result = new Map();
|
|
224
|
+
for (const [moduleName, paths] of buckets) {
|
|
225
|
+
result.set(moduleName, {
|
|
226
|
+
...full,
|
|
227
|
+
paths,
|
|
228
|
+
...(full.components ? { components: full.components } : {}),
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
return result;
|
|
232
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@voyant-travel/hono",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.119.0",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -140,14 +140,14 @@
|
|
|
140
140
|
"@voyant-travel/storage": "^0.106.0",
|
|
141
141
|
"@voyant-travel/types": "^0.106.1",
|
|
142
142
|
"@voyant-travel/utils": "^0.105.5",
|
|
143
|
-
"@voyant-travel/workflows": "^0.111.
|
|
143
|
+
"@voyant-travel/workflows": "^0.111.13"
|
|
144
144
|
},
|
|
145
145
|
"devDependencies": {
|
|
146
146
|
"@cloudflare/workers-types": "^4.20260628.1",
|
|
147
147
|
"typescript": "^6.0.3",
|
|
148
148
|
"vitest": "^4.1.9",
|
|
149
149
|
"@voyant-travel/voyant-typescript-config": "^0.1.0",
|
|
150
|
-
"@voyant-travel/workflows-orchestrator": "^0.111.
|
|
150
|
+
"@voyant-travel/workflows-orchestrator": "^0.111.13"
|
|
151
151
|
},
|
|
152
152
|
"files": [
|
|
153
153
|
"dist"
|