@voyant-travel/framework 0.1.0 → 0.2.1

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.
@@ -0,0 +1,404 @@
1
+ /**
2
+ * The standard Voyant runtime composition registry — the package-owned half of
3
+ * `OPERATOR_RUNTIME_MANIFEST`'s factories.
4
+ *
5
+ * agent-quality: file-size exception -- this is the framework's single
6
+ * composition source of truth (the FrameworkProviders contract + one factory
7
+ * entry per standard module/extension). Keeping the provider interface + the
8
+ * registry in one file is intentional; the length scales with the standard
9
+ * module count, not with logic complexity — mirroring the deployment's
10
+ * `composition.ts` rationale.
11
+ *
12
+ * Workstream B of the consolidated-deployments RFC relocates the deployment's
13
+ * `operatorComposition` registry here, one classified-as-standard family group
14
+ * at a time (see `operator-registry-classification.md`). A deployment spreads
15
+ * this into its own registry:
16
+ *
17
+ * export const operatorComposition: CompositionRegistry<OperatorCapabilities> = {
18
+ * modules: { ...frameworkComposition.modules, ...deploymentLocal },
19
+ * extensions: { ...frameworkComposition.extensions, ...deploymentLocal },
20
+ * }
21
+ *
22
+ * so `composeFromManifest` always sees one complete registry while the
23
+ * deployment object shrinks PR by PR.
24
+ *
25
+ * The factories read only the framework-owned {@link FrameworkProviders} surface
26
+ * off `ctx.capabilities` — never a deployment file, never a baked provider
27
+ * choice (Netopia et al. stay injected). Because `OperatorCapabilities extends
28
+ * FrameworkProviders` and factory params are contravariant, a
29
+ * `ModuleFactory<FrameworkProviders>` slots cleanly into the deployment's wider
30
+ * `CompositionRegistry<OperatorCapabilities>`.
31
+ *
32
+ * Provider field types are anchored to the package option types they feed
33
+ * (`NonNullable<XOptions["field"]>`) or to a package service (`typeof
34
+ * relationshipsService`), so they can't drift from the contracts the factories
35
+ * pass them into.
36
+ */
37
+ import { actionLedgerHonoModule } from "@voyant-travel/action-ledger";
38
+ import { bookingsSupplierExtension, createBookingsHonoModule, } from "@voyant-travel/bookings";
39
+ import { bookingsExtrasRoutes } from "@voyant-travel/bookings/extras";
40
+ import { createBookingRequirementsHonoModule, } from "@voyant-travel/bookings/requirements";
41
+ import { createCatalogSearchHonoModule, executeSemanticSearch, } from "@voyant-travel/catalog";
42
+ import { createCommerceHonoModules, createCommerceStorefrontOfferResolvers, } from "@voyant-travel/commerce";
43
+ import { distributionBookingExtension, distributionHonoModule, externalRefsHonoModule, suppliersHonoModule, } from "@voyant-travel/distribution";
44
+ import { bookingsCreateExtension, createBookingTaxHonoExtension, createFinanceHonoModule, } from "@voyant-travel/finance";
45
+ import { createPublicDocumentDeliveryHonoModule } from "@voyant-travel/hono";
46
+ import { identityHonoModule } from "@voyant-travel/identity";
47
+ import { inventoryBookingExtension, inventoryHonoModule } from "@voyant-travel/inventory";
48
+ import { inventoryAuthoringExtension } from "@voyant-travel/inventory/authoring/extension";
49
+ import { inventoryExtrasRoutes } from "@voyant-travel/inventory/extras";
50
+ import { CONTRACT_DOCUMENT_ROUTE_PATHS, createLegalHonoModule, } from "@voyant-travel/legal";
51
+ import { createDefaultBookingDocumentAttachment, createNotificationService, createNotificationsHonoModule, notificationsService, } from "@voyant-travel/notifications";
52
+ import { operationsHonoModule } from "@voyant-travel/operations";
53
+ import { resolveBookingTaxSettings, updateBookingTaxSettings, } from "@voyant-travel/operator-settings";
54
+ import { createOperatorSettingsHonoModule } from "@voyant-travel/operator-settings/hono-module";
55
+ import { createQuotesHonoModule, quotesBookingExtension } from "@voyant-travel/quotes";
56
+ import { createRelationshipsHonoModule, } from "@voyant-travel/relationships";
57
+ import { createStorefrontHonoModule, } from "@voyant-travel/storefront";
58
+ import { createCustomerPortalHonoModule } from "@voyant-travel/storefront/customer-portal";
59
+ import { createStorefrontVerificationHonoModule, } from "@voyant-travel/storefront/verification";
60
+ import { createTripsHonoModule } from "@voyant-travel/trips";
61
+ import { Hono } from "hono";
62
+ /**
63
+ * Combined "extras" surface — inventory + bookings package extras routes mounted
64
+ * on one module. Pure composition of package route sets (no providers); the
65
+ * deployment used to build this inline.
66
+ */
67
+ const extrasHonoModule = {
68
+ module: { name: "extras" },
69
+ routes: new Hono().route("/", inventoryExtrasRoutes).route("/", bookingsExtrasRoutes),
70
+ };
71
+ // Stable absolute route matchers for the lazy `operator/*` standard families
72
+ // (Tier 4). The framework owns the URL contract; the deployment injects only
73
+ // the `load` closure that wires its providers into the route bundle.
74
+ const CATALOG_BOOKING_ROUTE_PATHS = [
75
+ "/v1/admin/catalog/quote",
76
+ "/v1/admin/catalog/book",
77
+ "/v1/admin/catalog/drafts/:id",
78
+ "/v1/admin/catalog/holds/place",
79
+ "/v1/admin/catalog/holds/release",
80
+ "/v1/admin/catalog/slots",
81
+ "/v1/admin/catalog/orders",
82
+ "/v1/admin/catalog/orders/:id",
83
+ "/v1/admin/catalog/orders/:id/cancel",
84
+ "/v1/admin/bookings/:id/catalog-snapshot",
85
+ "/v1/public/catalog/quote",
86
+ "/v1/public/catalog/book",
87
+ "/v1/public/catalog/drafts/:id",
88
+ "/v1/public/catalog/holds/place",
89
+ "/v1/public/catalog/holds/release",
90
+ "/v1/public/catalog/slots",
91
+ ];
92
+ const CATALOG_CONTENT_ROUTE_PATHS = [
93
+ "/v1/admin/products/:id/content",
94
+ "/v1/public/products/:id/content",
95
+ "/v1/admin/cruises/:id/content",
96
+ "/v1/public/cruises/:id/content",
97
+ "/v1/admin/accommodations/:id/content",
98
+ "/v1/public/accommodations/:id/content",
99
+ ];
100
+ const MEDIA_ROUTE_PATHS = [
101
+ "/v1/admin/products/:id/brochure/generate",
102
+ "/v1/uploads",
103
+ "/v1/admin/uploads",
104
+ "/v1/uploads/video",
105
+ "/v1/admin/uploads/video",
106
+ "/v1/media/*",
107
+ "/v1/admin/media/*",
108
+ ];
109
+ const PAYMENT_LINK_ROUTE_PATHS = [
110
+ "/v1/public/payment-link-config",
111
+ "/v1/public/payment-link/:sessionId/retry",
112
+ "/v1/public/payment-link/resolve",
113
+ "/v1/public/payment-link/:sessionId/start-card",
114
+ "/v1/public/payment-link/:sessionId/trip-summary",
115
+ "/v1/public/payment-link/:sessionId/booking-summary",
116
+ "/v1/public/bookings/:bookingId/checkout-status",
117
+ ];
118
+ function optionalDateTime(value) {
119
+ if (!value)
120
+ return null;
121
+ return value instanceof Date ? value.toISOString() : value;
122
+ }
123
+ function toCheckoutNotificationDelivery(delivery) {
124
+ if (!delivery)
125
+ return null;
126
+ return {
127
+ id: delivery.id,
128
+ templateSlug: delivery.templateSlug,
129
+ channel: delivery.channel,
130
+ provider: delivery.provider,
131
+ status: delivery.status,
132
+ toAddress: delivery.toAddress,
133
+ subject: delivery.subject,
134
+ sentAt: optionalDateTime(delivery.sentAt),
135
+ failedAt: optionalDateTime(delivery.failedAt),
136
+ errorMessage: delivery.errorMessage,
137
+ };
138
+ }
139
+ function toCheckoutReminderRun(run) {
140
+ return {
141
+ id: run.id,
142
+ reminderRuleId: run.reminderRuleId,
143
+ reminderRuleSlug: run.reminderRule.slug,
144
+ reminderRuleName: run.reminderRule.name,
145
+ targetType: run.targetType,
146
+ targetId: run.targetId,
147
+ bookingId: run.links.bookingId,
148
+ paymentSessionId: run.links.paymentSessionId,
149
+ notificationDeliveryId: run.links.notificationDeliveryId,
150
+ status: run.status,
151
+ deliveryStatus: run.delivery?.status ?? null,
152
+ channel: run.delivery?.channel ?? run.reminderRule.channel,
153
+ provider: run.delivery?.provider ?? run.reminderRule.provider ?? null,
154
+ recipient: run.recipient,
155
+ scheduledFor: run.scheduledFor,
156
+ processedAt: run.processedAt,
157
+ errorMessage: run.errorMessage,
158
+ relativeDaysFromDueDate: null,
159
+ createdAt: run.createdAt,
160
+ };
161
+ }
162
+ /**
163
+ * Standard module/extension factories owned by the framework. Keyed by the same
164
+ * manifest specifiers as `FRAMEWORK_RUNTIME_MANIFEST`; a deployment spreads this
165
+ * into its registry (see file header).
166
+ *
167
+ * - Tier 1: the pure singleton modules — no providers, no deployment imports.
168
+ * - Tier 2: capability-shaped `@voyant-travel/*` modules — read injected
169
+ * providers off `ctx.capabilities`.
170
+ */
171
+ export const frameworkComposition = {
172
+ modules: {
173
+ // Tier 1 — pure singletons.
174
+ "@voyant-travel/action-ledger": () => actionLedgerHonoModule,
175
+ "@voyant-travel/relationships": ({ capabilities }) => createRelationshipsHonoModule({ customFields: capabilities.customFields }),
176
+ "@voyant-travel/quotes": () => createQuotesHonoModule(),
177
+ "@voyant-travel/operations": () => operationsHonoModule,
178
+ "@voyant-travel/identity": () => identityHonoModule,
179
+ "@voyant-travel/distribution": () => [
180
+ externalRefsHonoModule,
181
+ distributionHonoModule,
182
+ suppliersHonoModule,
183
+ ],
184
+ "@voyant-travel/commerce": () => createCommerceHonoModules(),
185
+ "@voyant-travel/inventory": () => inventoryHonoModule,
186
+ "@voyant-travel/inventory/extras": () => extrasHonoModule,
187
+ "@voyant-travel/bookings/requirements": ({ capabilities }) => createBookingRequirementsHonoModule({
188
+ publicRoutes: {
189
+ resolveProductSnapshot: capabilities.resolveBookingRequirementsProductSnapshot,
190
+ },
191
+ }),
192
+ // Tier 2 — capability-shaped modules (providers injected via ctx).
193
+ "@voyant-travel/catalog": ({ capabilities }) => createCatalogSearchHonoModule({
194
+ resolveRuntime: capabilities.resolveCatalogRuntime,
195
+ executeSearch: ({ adapter, embeddings, slice, request }) => executeSemanticSearch({
196
+ adapter,
197
+ embeddings: embeddings,
198
+ slice,
199
+ request,
200
+ }),
201
+ }),
202
+ "@voyant-travel/storefront": ({ capabilities }) => createStorefrontHonoModule({
203
+ offers: createCommerceStorefrontOfferResolvers(),
204
+ // Async booking-bootstrap intents (queued write pipeline, RFC
205
+ // voyant#1687 §3.2) — the handler runs on the app bus with
206
+ // outbox-grade retries; the */2min cron sweeps stale intents.
207
+ bookingIntents: { resolveDb: capabilities.resolveDb },
208
+ intake: { persistence: capabilities.storefrontIntakePersistence },
209
+ }),
210
+ "@voyant-travel/finance": ({ capabilities }) => createFinanceHonoModule({
211
+ resolveDocumentDownloadUrl: (bindings, storageKey) => capabilities.resolveDocumentDownloadUrl(bindings, storageKey),
212
+ resolveInvoiceExchangeRateResolver: capabilities.createInvoiceExchangeRateResolver,
213
+ resolveInvoiceSettlementPollers: capabilities.createInvoiceSettlementPollers,
214
+ invoiceDueDateResolver: ({ issueDate, dueDate, bookingPaymentSchedule }) => bookingPaymentSchedule && dueDate < issueDate ? issueDate : dueDate,
215
+ resolveNotificationDispatcher: (bindings) => {
216
+ const providers = capabilities.resolveNotificationProviders(bindings);
217
+ if (providers.length === 0)
218
+ return null;
219
+ const dispatcher = createNotificationService(providers);
220
+ return {
221
+ sendInvoiceNotification: async (db, invoiceId, input) => toCheckoutNotificationDelivery(await notificationsService.sendInvoiceNotification(db, dispatcher, invoiceId, input)),
222
+ sendPaymentSessionNotification: async (db, paymentSessionId, input) => toCheckoutNotificationDelivery(await notificationsService.sendPaymentSessionNotification(db, dispatcher, paymentSessionId, input)),
223
+ };
224
+ },
225
+ resolvePaymentStarters: () => ({
226
+ netopia: capabilities.netopiaCheckoutStarter,
227
+ }),
228
+ resolveBankTransferDetails: capabilities.resolveBankTransferDetails,
229
+ resolvePublicCheckoutBaseUrl: capabilities.resolvePublicCheckoutBaseUrl,
230
+ listBookingReminderRuns: async (db, bookingId, query) => {
231
+ const result = await notificationsService.listReminderRuns(db, {
232
+ bookingId,
233
+ status: query.status,
234
+ limit: query.limit,
235
+ offset: query.offset,
236
+ });
237
+ return {
238
+ data: result.data.map(toCheckoutReminderRun),
239
+ total: result.total,
240
+ limit: result.limit,
241
+ offset: result.offset,
242
+ };
243
+ },
244
+ }),
245
+ "@voyant-travel/bookings": ({ capabilities }) => createBookingsHonoModule({
246
+ resolveTravelSnapshot: (db, personId, { kms }) => capabilities.relationshipsService.loadPersonTravelSnapshot(db, personId, { kms }),
247
+ resolveBillingPerson: async (db, contact, ctx) => {
248
+ const person = await capabilities.relationshipsService.upsertPersonFromContact(db, contact, {
249
+ source: ctx.source,
250
+ sourceRef: ctx.sourceRef,
251
+ });
252
+ return person?.id ?? null;
253
+ },
254
+ resolveTravelerPerson: async (db, contact, ctx) => {
255
+ const person = await capabilities.relationshipsService.upsertPersonFromContact(db, contact, {
256
+ source: ctx.source,
257
+ sourceRef: ctx.sourceRef,
258
+ requireContactPoint: true,
259
+ });
260
+ return person?.id ?? null;
261
+ },
262
+ resolveBillingPersonById: async (db, personId) => (await capabilities.relationshipsService.getPersonById(db, personId)) != null,
263
+ resolveBillingOrganizationById: async (db, organizationId) => (await capabilities.relationshipsService.getOrganizationById(db, organizationId)) != null,
264
+ closePaymentSchedulesForBooking: capabilities.closePaymentSchedulesForBooking,
265
+ customFields: capabilities.customFields,
266
+ }),
267
+ "@voyant-travel/public-document-delivery": ({ capabilities }) => createPublicDocumentDeliveryHonoModule({
268
+ // Same storage backend as legal documents; the unknown-bindings
269
+ // adapter keeps the provider contract uniform (the narrow-bindings
270
+ // `createDocumentStorage` is retired in the deployment).
271
+ resolveStorage: capabilities.createOperatorDocumentStorage,
272
+ }),
273
+ "@voyant-travel/notifications": ({ capabilities }) => createNotificationsHonoModule({
274
+ resolveProviders: capabilities.resolveNotificationProviders,
275
+ resolvePublicCheckoutBaseUrl: capabilities.resolvePublicCheckoutBaseUrl,
276
+ resolveDocumentAttachmentResolver: (bindings) => async (document) => {
277
+ if (document.storageKey) {
278
+ const contentBase64 = await capabilities.readDocumentContentBase64(bindings, document.storageKey);
279
+ if (contentBase64) {
280
+ return {
281
+ filename: document.name,
282
+ contentBase64,
283
+ contentType: document.mimeType ?? undefined,
284
+ };
285
+ }
286
+ const path = await capabilities.resolveDocumentDownloadUrl(bindings, document.storageKey);
287
+ if (path) {
288
+ return {
289
+ filename: document.name,
290
+ path,
291
+ contentType: document.mimeType ?? undefined,
292
+ };
293
+ }
294
+ }
295
+ return createDefaultBookingDocumentAttachment(document);
296
+ },
297
+ resolveDb: capabilities.resolveDb,
298
+ autoConfirmAndDispatch: { enabled: true, templateSlug: "booking-confirmation" },
299
+ }),
300
+ "@voyant-travel/legal": ({ capabilities }) => createLegalHonoModule({
301
+ resolveDb: capabilities.resolveDb,
302
+ resolveDocumentDownloadUrl: (bindings, storageKey) => capabilities.resolveDocumentDownloadUrl(bindings, storageKey),
303
+ resolveDocumentStorage: capabilities.createOperatorDocumentStorage,
304
+ resolveDocumentGenerator: capabilities.resolveContractDocumentGenerator,
305
+ resolveBookingPiiService: capabilities.createBookingPiiService,
306
+ autoGenerateContractOnConfirmed: capabilities.autoGenerateContractOnConfirmed,
307
+ }),
308
+ "@voyant-travel/storefront/customer-portal": ({ capabilities }) => createCustomerPortalHonoModule({
309
+ resolveDocumentDownloadUrl: (bindings, storageKey) => capabilities.resolveDocumentDownloadUrl(bindings, storageKey),
310
+ }),
311
+ "@voyant-travel/storefront/verification": ({ capabilities }) => createStorefrontVerificationHonoModule({
312
+ resolveProviders: capabilities.resolveNotificationProviders,
313
+ email: { subject: "Your verification code" },
314
+ }),
315
+ "@voyant-travel/trips": ({ capabilities }) => createTripsHonoModule({
316
+ ...capabilities.createTripsRoutesOptions(),
317
+ publicRoutes: true,
318
+ }),
319
+ // Standard settings module — schema-owning + lazy absolute-path routes, no
320
+ // providers (reads its own tables). Stage 2 of the operator-settings
321
+ // extraction; previously a deployment-local module.
322
+ "@voyant-travel/operator-settings": () => createOperatorSettingsHonoModule(),
323
+ // Tier 4 — lazy `operator/*` standard families. The framework owns the
324
+ // manifest entry + path matchers; the deployment injects the `load` closure
325
+ // that wires its providers into the package-owned route bundle.
326
+ "@voyant-travel/flights": ({ capabilities }) => ({
327
+ module: { name: "flights" },
328
+ lazyAdminRoutes: capabilities.loadFlightAdminRoutes,
329
+ }),
330
+ "operator/mcp": ({ capabilities }) => ({
331
+ module: { name: "mcp" },
332
+ lazyAdminRoutes: capabilities.loadMcpAdminRoutes,
333
+ }),
334
+ "operator/catalog-booking": ({ capabilities }) => ({
335
+ module: { name: "catalog-booking" },
336
+ lazyRoutes: {
337
+ paths: CATALOG_BOOKING_ROUTE_PATHS,
338
+ load: capabilities.loadCatalogBookingRoutes,
339
+ },
340
+ }),
341
+ "operator/catalog-content": ({ capabilities }) => ({
342
+ module: { name: "catalog-content" },
343
+ lazyRoutes: {
344
+ paths: CATALOG_CONTENT_ROUTE_PATHS,
345
+ load: capabilities.loadCatalogContentRoutes,
346
+ },
347
+ }),
348
+ "operator/media": ({ capabilities }) => ({
349
+ module: { name: "media" },
350
+ lazyRoutes: { paths: MEDIA_ROUTE_PATHS, load: capabilities.loadMediaRoutes },
351
+ }),
352
+ "operator/payment-link": ({ capabilities }) => ({
353
+ module: { name: "payment-link" },
354
+ lazyRoutes: { paths: PAYMENT_LINK_ROUTE_PATHS, load: capabilities.loadPaymentLinkRoutes },
355
+ }),
356
+ "operator/contract-document": ({ capabilities }) => ({
357
+ module: { name: "contract-document" },
358
+ lazyRoutes: {
359
+ paths: CONTRACT_DOCUMENT_ROUTE_PATHS,
360
+ load: capabilities.loadContractDocumentRoutes,
361
+ },
362
+ }),
363
+ },
364
+ extensions: {
365
+ // Tier 3 — pure singleton extensions (no providers).
366
+ "@voyant-travel/bookings/booking-supplier-extension": () => bookingsSupplierExtension,
367
+ "@voyant-travel/finance/bookings-create-extension": () => bookingsCreateExtension,
368
+ "@voyant-travel/inventory/booking-extension": () => inventoryBookingExtension,
369
+ "@voyant-travel/inventory/authoring/extension": () => inventoryAuthoringExtension,
370
+ "@voyant-travel/quotes/booking-extension": () => quotesBookingExtension,
371
+ "@voyant-travel/distribution": () => distributionBookingExtension,
372
+ // Injection-shaped extensions — deployment-specific builders/readers via ctx.
373
+ "@voyant-travel/distribution/channel-push-extension": ({ capabilities }) => capabilities.createChannelPushExtension(),
374
+ // Booking-tax settings are read straight from the standard
375
+ // @voyant-travel/operator-settings package — no per-deployment injection.
376
+ "@voyant-travel/finance/booking-tax-extension": () => createBookingTaxHonoExtension({ resolveBookingTaxSettings, updateBookingTaxSettings }),
377
+ // Tier 4 — lazy `operator/*` standard extensions. The framework owns the
378
+ // `extension` metadata + publicPath; the deployment injects builders/loaders.
379
+ "operator/booking-schedule-extension": ({ capabilities }) => capabilities.createBookingScheduleExtension(),
380
+ "operator/quote-version-snapshot-extension": ({ capabilities }) => capabilities.createQuoteVersionSnapshotExtension(),
381
+ "operator/booking-maintenance-extension": ({ capabilities }) => ({
382
+ extension: { name: "booking-maintenance", module: "bookings" },
383
+ lazyAdminRoutes: capabilities.loadBookingMaintenanceRoutes,
384
+ }),
385
+ "operator/action-ledger-health-extension": ({ capabilities }) => ({
386
+ extension: { name: "action-ledger-health", module: "action-ledger" },
387
+ lazyAdminRoutes: capabilities.loadActionLedgerHealthRoutes,
388
+ }),
389
+ "operator/proposal-extension": ({ capabilities }) => ({
390
+ extension: { name: "proposal", module: "quote-versions" },
391
+ publicPath: "proposals",
392
+ lazyAdminRoutes: capabilities.loadProposalAdminRoutes,
393
+ lazyPublicRoutes: capabilities.loadProposalPublicRoutes,
394
+ }),
395
+ "operator/catalog-offers-extension": ({ capabilities }) => ({
396
+ extension: { name: "catalog-offers", module: "catalog" },
397
+ lazyAdminRoutes: capabilities.loadCatalogOffersRoutes,
398
+ }),
399
+ "operator/catalog-checkout-extension": ({ capabilities }) => ({
400
+ extension: { name: "catalog-checkout", module: "catalog" },
401
+ lazyPublicRoutes: capabilities.loadCatalogCheckoutRoutes,
402
+ }),
403
+ },
404
+ };
@@ -0,0 +1,52 @@
1
+ /**
2
+ * `createVoyantApp` — the framework's config-driven front door (Workstream B
3
+ * convergence).
4
+ *
5
+ * A standard deployment no longer hand-assembles a manifest + registry. It calls
6
+ * this with its injected {@link FrameworkProviders} and any deployment-local
7
+ * module/extension additions; the framework merges them onto the standard set
8
+ * (`FRAMEWORK_RUNTIME_MANIFEST` + `frameworkComposition`) and delegates to the
9
+ * lower-level `createApp` in `@voyant-travel/hono`.
10
+ *
11
+ * export const app = createVoyantApp<CloudflareBindings, OperatorProviders>({
12
+ * providers: buildOperatorProviders(),
13
+ * modules: deploymentLocalModules, // e.g. invitations, operator-settings
14
+ * db, workflows, outbox, publicPaths, // unchanged VoyantApp config
15
+ * })
16
+ *
17
+ * This is NOT the rejected "assembly kit": providers stay injected (never baked),
18
+ * and the assembly is the framework-owned standard set — not a bottom-up file dump.
19
+ *
20
+ * Scope (D.1): the standard profile is FIXED. This always mounts the full
21
+ * `FRAMEWORK_RUNTIME_MANIFEST` and only APPENDS the deployment's local
22
+ * `modules`/`extensions`; it does not consume `voyant.config` to remove standard
23
+ * modules. Subsetting the standard set is a later workstream — see `manifest.ts`.
24
+ */
25
+ import { type CreateAppConfig, type VoyantBindings } from "@voyant-travel/hono";
26
+ import type { ExtensionFactory, ModuleFactory } from "@voyant-travel/hono/composition";
27
+ import { type FrameworkProviders } from "./composition.js";
28
+ /**
29
+ * Config for {@link createVoyantApp}: the injected providers + deployment-local
30
+ * additions, plus everything else `createApp` takes (db, auth, workflows,
31
+ * outbox, publicPaths, …) — minus the `manifest`/`registry`/`capabilities` the
32
+ * framework now assembles for you.
33
+ */
34
+ export interface CreateVoyantAppConfig<TBindings extends VoyantBindings, TProviders extends FrameworkProviders> extends Omit<CreateAppConfig<TBindings, TProviders>, "manifest" | "registry" | "capabilities"> {
35
+ /** The deployment-injected provider surface (a `FrameworkProviders` superset). */
36
+ providers: TProviders;
37
+ /** Deployment-local module factories, appended after the standard set. */
38
+ modules?: Record<string, ModuleFactory<TProviders>>;
39
+ /** Deployment-local extension factories, appended after the standard set. */
40
+ extensions?: Record<string, ExtensionFactory<TProviders>>;
41
+ }
42
+ /**
43
+ * Assemble the standard manifest + registry (framework-owned) with the
44
+ * deployment's local additions, then build the app via `@voyant-travel/hono`'s
45
+ * `createApp`. Standard families keep their `FRAMEWORK_RUNTIME_MANIFEST` order;
46
+ * local additions mount after them.
47
+ */
48
+ export declare function createVoyantApp<TBindings extends VoyantBindings, TProviders extends FrameworkProviders>(config: CreateVoyantAppConfig<TBindings, TProviders>): import("hono").Hono<{
49
+ Bindings: TBindings;
50
+ Variables: import("@voyant-travel/hono").VoyantVariables;
51
+ }, import("hono/types").BlankSchema, "/"> & import("@voyant-travel/hono/app").VoyantAppExtensions<TBindings>;
52
+ //# sourceMappingURL=create-app.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"create-app.d.ts","sourceRoot":"","sources":["../src/create-app.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,EAAE,KAAK,eAAe,EAAa,KAAK,cAAc,EAAE,MAAM,qBAAqB,CAAA;AAC1F,OAAO,KAAK,EAEV,gBAAgB,EAChB,aAAa,EACd,MAAM,iCAAiC,CAAA;AACxC,OAAO,EAAE,KAAK,kBAAkB,EAAwB,MAAM,kBAAkB,CAAA;AAGhF;;;;;GAKG;AACH,MAAM,WAAW,qBAAqB,CACpC,SAAS,SAAS,cAAc,EAChC,UAAU,SAAS,kBAAkB,CACrC,SAAQ,IAAI,CAAC,eAAe,CAAC,SAAS,EAAE,UAAU,CAAC,EAAE,UAAU,GAAG,UAAU,GAAG,cAAc,CAAC;IAC9F,kFAAkF;IAClF,SAAS,EAAE,UAAU,CAAA;IACrB,0EAA0E;IAC1E,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,UAAU,CAAC,CAAC,CAAA;IACnD,6EAA6E;IAC7E,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,UAAU,CAAC,CAAC,CAAA;CAC1D;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAC7B,SAAS,SAAS,cAAc,EAChC,UAAU,SAAS,kBAAkB,EACrC,MAAM,EAAE,qBAAqB,CAAC,SAAS,EAAE,UAAU,CAAC;;;6GA2BrD"}
@@ -0,0 +1,59 @@
1
+ /**
2
+ * `createVoyantApp` — the framework's config-driven front door (Workstream B
3
+ * convergence).
4
+ *
5
+ * A standard deployment no longer hand-assembles a manifest + registry. It calls
6
+ * this with its injected {@link FrameworkProviders} and any deployment-local
7
+ * module/extension additions; the framework merges them onto the standard set
8
+ * (`FRAMEWORK_RUNTIME_MANIFEST` + `frameworkComposition`) and delegates to the
9
+ * lower-level `createApp` in `@voyant-travel/hono`.
10
+ *
11
+ * export const app = createVoyantApp<CloudflareBindings, OperatorProviders>({
12
+ * providers: buildOperatorProviders(),
13
+ * modules: deploymentLocalModules, // e.g. invitations, operator-settings
14
+ * db, workflows, outbox, publicPaths, // unchanged VoyantApp config
15
+ * })
16
+ *
17
+ * This is NOT the rejected "assembly kit": providers stay injected (never baked),
18
+ * and the assembly is the framework-owned standard set — not a bottom-up file dump.
19
+ *
20
+ * Scope (D.1): the standard profile is FIXED. This always mounts the full
21
+ * `FRAMEWORK_RUNTIME_MANIFEST` and only APPENDS the deployment's local
22
+ * `modules`/`extensions`; it does not consume `voyant.config` to remove standard
23
+ * modules. Subsetting the standard set is a later workstream — see `manifest.ts`.
24
+ */
25
+ import { createApp } from "@voyant-travel/hono";
26
+ import { frameworkComposition } from "./composition.js";
27
+ import { FRAMEWORK_RUNTIME_MANIFEST } from "./manifest.js";
28
+ /**
29
+ * Assemble the standard manifest + registry (framework-owned) with the
30
+ * deployment's local additions, then build the app via `@voyant-travel/hono`'s
31
+ * `createApp`. Standard families keep their `FRAMEWORK_RUNTIME_MANIFEST` order;
32
+ * local additions mount after them.
33
+ */
34
+ export function createVoyantApp(config) {
35
+ const { providers, modules = {}, extensions = {}, ...rest } = config;
36
+ const registry = {
37
+ // The framework factories read only the `FrameworkProviders` slice; a
38
+ // `ModuleFactory<FrameworkProviders>` is assignable to
39
+ // `ModuleFactory<TProviders>` (factory params are contravariant), so the
40
+ // standard set composes against the deployment's wider providers.
41
+ modules: {
42
+ ...frameworkComposition.modules,
43
+ ...modules,
44
+ },
45
+ extensions: {
46
+ ...frameworkComposition.extensions,
47
+ ...extensions,
48
+ },
49
+ };
50
+ return createApp({
51
+ ...rest,
52
+ manifest: {
53
+ modules: [...FRAMEWORK_RUNTIME_MANIFEST.modules, ...Object.keys(modules)],
54
+ extensions: [...FRAMEWORK_RUNTIME_MANIFEST.extensions, ...Object.keys(extensions)],
55
+ },
56
+ registry,
57
+ capabilities: providers,
58
+ });
59
+ }
@@ -0,0 +1,80 @@
1
+ /**
2
+ * Deployment-local module + extension discovery (consolidated-deployments RFC —
3
+ * the "20%" extension seams: *custom module* and *custom route on an existing
4
+ * module*, neither requiring a fork).
5
+ *
6
+ * A deployment drops a unit into a conventional directory and it is auto-mounted
7
+ * — no edit to a framework-owned file, no hand-wiring in the deployment's
8
+ * composition:
9
+ *
10
+ * - `src/modules/<name>/index.ts` → a new module (own routes + schema)
11
+ * - `src/extensions/<name>/index.ts` → a {@link HonoExtension} adding routes to
12
+ * an EXISTING module's surface
13
+ *
14
+ * Discovery is **build-time**: the deployment feeds a Vite
15
+ * `import.meta.glob("./modules/*\/index.ts", { eager: true })` (statically
16
+ * compiled to static imports — Workers-safe) into {@link modulesFromGlob} /
17
+ * {@link extensionsFromGlob}, which key each unit by its `<name>` directory
18
+ * segment.
19
+ *
20
+ * // src/api/composition.ts (deployment-owned)
21
+ * const modules = modulesFromGlob<OperatorCapabilities>(
22
+ * import.meta.glob("../modules/*\/index.ts", { eager: true }),
23
+ * )
24
+ * const extensions = extensionsFromGlob<OperatorCapabilities>(
25
+ * import.meta.glob("../extensions/*\/index.ts", { eager: true }),
26
+ * )
27
+ *
28
+ * // src/modules/loyalty/index.ts
29
+ * export default defineDeploymentModule({ module: { name: "loyalty" }, adminRoutes })
30
+ * // src/extensions/booking-notes/index.ts
31
+ * export default defineDeploymentExtension({
32
+ * extension: { module: "bookings" }, adminRoutes, // → /v1/admin/bookings/*
33
+ * })
34
+ *
35
+ * Schema owned by a custom module/extension (`<dir>/<name>/schema.ts`) is picked
36
+ * up by the deployment's drizzle config glob (a *deployment* migration source,
37
+ * applied after the framework bundle by the D.1 collector).
38
+ */
39
+ import type { ExtensionFactory, ModuleFactory } from "@voyant-travel/hono/composition";
40
+ import type { HonoExtension, HonoModule } from "@voyant-travel/hono/module";
41
+ import type { FrameworkProviders } from "./composition.js";
42
+ /** A deployment module declaration: a ready unit, or a factory that builds one. */
43
+ export type DeploymentModuleDeclaration<TProviders> = HonoModule | HonoModule[] | ModuleFactory<TProviders>;
44
+ /** A deployment extension declaration: a ready unit, or a factory that builds one. */
45
+ export type DeploymentExtensionDeclaration<TProviders> = HonoExtension | ExtensionFactory<TProviders>;
46
+ /**
47
+ * Normalize a deployment-local module declaration into a {@link ModuleFactory}.
48
+ * Accepts a ready {@link HonoModule} (or array) or a factory; returns a factory
49
+ * either way. Use it as the `export default` of a `src/modules/<name>/index.ts`.
50
+ */
51
+ export declare function defineDeploymentModule<TProviders = FrameworkProviders>(declaration: DeploymentModuleDeclaration<TProviders>): ModuleFactory<TProviders>;
52
+ /**
53
+ * Normalize a deployment-local extension declaration into an
54
+ * {@link ExtensionFactory}. Accepts a ready {@link HonoExtension} or a factory.
55
+ * Use it as the `export default` of a `src/extensions/<name>/index.ts`.
56
+ */
57
+ export declare function defineDeploymentExtension<TProviders = FrameworkProviders>(declaration: DeploymentExtensionDeclaration<TProviders>): ExtensionFactory<TProviders>;
58
+ /** A Vite `import.meta.glob(..., { eager: true })` result: path → module namespace. */
59
+ export type EagerModuleGlob = Record<string, unknown>;
60
+ /**
61
+ * Build a deployment-local **module** registry from a Vite `import.meta.glob`
62
+ * (eager) of `src/modules/<name>/index.ts` files. Each entry's `default` export
63
+ * is a {@link HonoModule} or {@link ModuleFactory} (wrap with
64
+ * {@link defineDeploymentModule}); the registry key is the `<name>` directory
65
+ * segment, which becomes the module's composition specifier.
66
+ *
67
+ * @throws if a matched file has no default export.
68
+ */
69
+ export declare function modulesFromGlob<TProviders = FrameworkProviders>(glob: EagerModuleGlob): Record<string, ModuleFactory<TProviders>>;
70
+ /**
71
+ * Build a deployment-local **extension** registry from a Vite `import.meta.glob`
72
+ * (eager) of `src/extensions/<name>/index.ts` files. Each entry's `default`
73
+ * export is a {@link HonoExtension} or {@link ExtensionFactory} (wrap with
74
+ * {@link defineDeploymentExtension}); the registry key is the `<name>` directory
75
+ * segment.
76
+ *
77
+ * @throws if a matched file has no default export.
78
+ */
79
+ export declare function extensionsFromGlob<TProviders = FrameworkProviders>(glob: EagerModuleGlob): Record<string, ExtensionFactory<TProviders>>;
80
+ //# sourceMappingURL=discover-modules.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"discover-modules.d.ts","sourceRoot":"","sources":["../src/discover-modules.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AAEH,OAAO,KAAK,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,iCAAiC,CAAA;AACtF,OAAO,KAAK,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,4BAA4B,CAAA;AAC3E,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAA;AAE1D,mFAAmF;AACnF,MAAM,MAAM,2BAA2B,CAAC,UAAU,IAC9C,UAAU,GACV,UAAU,EAAE,GACZ,aAAa,CAAC,UAAU,CAAC,CAAA;AAE7B,sFAAsF;AACtF,MAAM,MAAM,8BAA8B,CAAC,UAAU,IACjD,aAAa,GACb,gBAAgB,CAAC,UAAU,CAAC,CAAA;AAEhC;;;;GAIG;AACH,wBAAgB,sBAAsB,CAAC,UAAU,GAAG,kBAAkB,EACpE,WAAW,EAAE,2BAA2B,CAAC,UAAU,CAAC,GACnD,aAAa,CAAC,UAAU,CAAC,CAE3B;AAED;;;;GAIG;AACH,wBAAgB,yBAAyB,CAAC,UAAU,GAAG,kBAAkB,EACvE,WAAW,EAAE,8BAA8B,CAAC,UAAU,CAAC,GACtD,gBAAgB,CAAC,UAAU,CAAC,CAE9B;AAED,uFAAuF;AACvF,MAAM,MAAM,eAAe,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;AAErD;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAAC,UAAU,GAAG,kBAAkB,EAC7D,IAAI,EAAE,eAAe,GACpB,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,UAAU,CAAC,CAAC,CAI3C;AAED;;;;;;;;GAQG;AACH,wBAAgB,kBAAkB,CAAC,UAAU,GAAG,kBAAkB,EAChE,IAAI,EAAE,eAAe,GACpB,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,UAAU,CAAC,CAAC,CAO9C"}