@voyant-travel/hono 0.133.0 → 0.134.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.
package/dist/app.d.ts CHANGED
@@ -34,9 +34,8 @@ export interface ModuleMount {
34
34
  /**
35
35
  * App handle returned alongside the Hono instance. Carries `ready()` for
36
36
  * headless / sibling-process deployments that need to fire the lazy
37
- * bootstrap before the first HTTP request workflow runtimes (node
38
- * sibling-process pattern, tests) call this so the time wheel and
39
- * manifest registration kick off without traffic.
37
+ * bootstrap before the first HTTP request. Resident hosts and tests call this
38
+ * so selected runtime registration completes without waiting for traffic.
40
39
  *
41
40
  * Returned via the augmented Hono instance: `app.ready` is attached
42
41
  * directly so the existing call sites (which destructure / pass `app`
package/dist/app.js CHANGED
@@ -1,10 +1,9 @@
1
1
  // agent-quality: file-size exception — app.ts is the framework composition root;
2
2
  // splitting it is intentional follow-up work, not part of voyant#2114.
3
3
  import { OpenAPIHono } from "@hono/zod-openapi";
4
- import { createContainer, createEventBus, createQueryRunner, } from "@voyant-travel/core";
4
+ import { createContainer, createEventBus, createQueryRunner } from "@voyant-travel/core";
5
5
  import { createLinkServiceFactory } from "@voyant-travel/db/links";
6
6
  import { assembleAnonymousPaths, assembleOptionalCustomerAuthPaths } from "./anonymous-paths.js";
7
- import { containerToServiceResolver, makeFrameworkLogger, wireWorkflowRuntime, } from "./app-workflows.js";
8
7
  import { composeAuthAugmentations } from "./auth-augmentation.js";
9
8
  import { expandApiBundles, isLazyApiBundle, } from "./bundle.js";
10
9
  import { assembleClientAuthenticatedRoutes, matchesClientAuthenticatedRoute, } from "./client-authenticated-routes.js";
@@ -137,48 +136,6 @@ export function mountApp(config) {
137
136
  for (const sub of expanded?.subscribers ?? []) {
138
137
  eventBus.subscribe(sub.event, sub.handler, { inline: sub.inline ?? false });
139
138
  }
140
- // ---- Workflow runtime wiring (synchronous setup; manifest registration
141
- // + EventBus forwarder run inside the lazy bootstrap below) ----
142
- //
143
- // We collect `workflows` + `eventFilters` from every module and plugin
144
- // here so the failure mode for "duplicate workflow id across modules"
145
- // surfaces at construction time (per architecture doc §18, the workflow
146
- // runtime is fail-closed).
147
- const collectedWorkflows = [];
148
- const collectedFilters = [];
149
- function collectModuleRuntimeDescriptors(modules) {
150
- for (const mod of modules) {
151
- if (mod.module.workflows)
152
- collectedWorkflows.push(...mod.module.workflows);
153
- if (mod.module.eventFilters)
154
- collectedFilters.push(...mod.module.eventFilters);
155
- }
156
- }
157
- function collectPluginRuntimeDescriptors(plugins) {
158
- for (const plugin of plugins) {
159
- if (plugin.workflows)
160
- collectedWorkflows.push(...plugin.workflows);
161
- if (plugin.eventFilters)
162
- collectedFilters.push(...plugin.eventFilters);
163
- }
164
- }
165
- function assertUniqueWorkflowIds() {
166
- if (!config.workflows || collectedWorkflows.length === 0)
167
- return;
168
- const seen = new Map();
169
- for (const wf of collectedWorkflows) {
170
- const existing = seen.get(wf.id);
171
- if (existing && existing !== wf) {
172
- throw new Error(`[voyant] duplicate workflow id "${wf.id}" registered by multiple modules/plugins. ` +
173
- `Workflow ids must be unique across the app — use a module-scoped prefix ` +
174
- `(e.g. "${wf.id.includes(".") ? wf.id : `<module>.${wf.id}`}").`);
175
- }
176
- seen.set(wf.id, wf);
177
- }
178
- }
179
- collectModuleRuntimeDescriptors(allModules);
180
- collectPluginRuntimeDescriptors(eagerPlugins);
181
- assertUniqueWorkflowIds();
182
139
  const txModuleNames = new Set();
183
140
  const txRequiringModules = [];
184
141
  const txPrefixes = [...(config.dbTransactionalPaths ?? [])];
@@ -273,8 +230,6 @@ export function mountApp(config) {
273
230
  for (const sub of lazyExpanded.subscribers) {
274
231
  eventBus.subscribe(sub.event, sub.handler, { inline: sub.inline ?? false });
275
232
  }
276
- collectModuleRuntimeDescriptors(lazyExpanded.modules);
277
- collectPluginRuntimeDescriptors(pending);
278
233
  addTransactionalSurfaces(lazyExpanded.modules, lazyExpanded.extensions);
279
234
  }
280
235
  async function ensureBootstrapLazyPluginsExpanded() {
@@ -293,47 +248,12 @@ export function mountApp(config) {
293
248
  }
294
249
  await lazyPluginExpansionPromise;
295
250
  }
296
- // Workflow driver construction is **deferred** to the lazy bootstrap
297
- // path so callers whose driver options come from `env.*` bindings can
298
- // pass a function-of-bindings shape. Node / InMemory users usually
299
- // return a direct factory from that function.
300
- let workflowDriver;
301
251
  let bootstrapPromise = null;
302
252
  function ensureRuntimeBootstrapped(bindings) {
303
253
  if (!bootstrapPromise) {
304
254
  bootstrapPromise = (async () => {
305
255
  const ctx = { bindings, container, eventBus };
306
256
  await ensureBootstrapLazyPluginsExpanded();
307
- assertUniqueWorkflowIds();
308
- // ---- Workflow runtime FIRST — fail-closed manifest registration
309
- // and EventBus forwarder must be in place before any module
310
- // bootstrap can emit. Otherwise a `module.bootstrap` that
311
- // emits an event during its own bootstrap would route through
312
- // a bus with no workflow forwarder yet, silently losing the
313
- // event. Per architecture doc §21.22 + reviewer feedback P2.3.
314
- if (config.workflows) {
315
- // `driver` is always a function-of-bindings (per
316
- // VoyantWorkflowsConfig — see types.ts + reviewer feedback P2.1).
317
- // Node / InMemory users wrap with `() => createXxxDriver({...})`.
318
- // Managed-cloud users can derive a forwarding driver from `env`.
319
- // We invoke with bindings, then the resulting DriverFactory
320
- // with framework deps.
321
- const factoryDeps = {
322
- services: containerToServiceResolver(container),
323
- logger: makeFrameworkLogger(config.logger),
324
- };
325
- const factory = config.workflows.driver(bindings);
326
- workflowDriver = factory(factoryDeps);
327
- await wireWorkflowRuntime({
328
- modules: allModules.map((m) => m.module),
329
- collectedWorkflows,
330
- collectedFilters,
331
- driver: workflowDriver,
332
- environment: config.workflows.environment ?? "development",
333
- projectId: config.workflows.projectId ?? "default",
334
- eventBus,
335
- });
336
- }
337
257
  // Run each bootstrap in isolation — a single failing plugin/module/extension
338
258
  // must not poison the cached promise and kill the whole app's request pipeline.
339
259
  const runIsolated = async (label, fn) => {
@@ -521,10 +441,8 @@ export function mountApp(config) {
521
441
  app.use("*", db(dbSource, { requiresTransactionalDb: txRequiringModules, basePath: config.basePath }));
522
442
  app.use("*", async (c, next) => {
523
443
  // Bootstrap only after auth has admitted the request. Rejected requests
524
- // must not initialize package runtimes or contact workflow infrastructure.
444
+ // must not initialize package runtimes.
525
445
  await ensureRuntimeBootstrapped(c.env);
526
- if (workflowDriver)
527
- c.set("workflowDriver", workflowDriver);
528
446
  return next();
529
447
  });
530
448
  if (createRequestLinkService) {
package/dist/bundle.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { BootstrapHandler, EventFilterDescriptor, LinkDefinition, Subscriber, WorkflowDescriptor } from "@voyant-travel/core";
1
+ import type { BootstrapHandler, LinkDefinition, Subscriber } from "@voyant-travel/core";
2
2
  import type { ApiExtension, ApiModule } from "./module.js";
3
3
  /**
4
4
  * Reusable server API contribution surface.
@@ -37,17 +37,6 @@ export interface ApiBundle {
37
37
  * these into the assembled anonymous allow-list.
38
38
  */
39
39
  anonymous?: string[];
40
- /**
41
- * Workflows contributed by the plugin. Mirrors the `Plugin.workflows`
42
- * field in `@voyant-travel/core` — collected at `createApp()` boot and
43
- * registered with the configured workflow driver.
44
- */
45
- workflows?: readonly WorkflowDescriptor[];
46
- /**
47
- * Event filters contributed by the plugin. Mirrors
48
- * `Plugin.eventFilters` in `@voyant-travel/core`.
49
- */
50
- eventFilters?: readonly EventFilterDescriptor[];
51
40
  }
52
41
  declare const LAZY_API_BUNDLE: unique symbol;
53
42
  /**
@@ -89,8 +78,7 @@ export interface LazyApiBundle {
89
78
  /**
90
79
  * Load this bundle during app bootstrap instead of waiting for a declared
91
80
  * route matcher. Set this for lazy bundles that contribute subscribers,
92
- * workflow metadata, container services, or bootstraps needed before a
93
- * bundle-owned route is hit.
81
+ * container services, or bootstraps needed before a bundle-owned route is hit.
94
82
  */
95
83
  loadOnBootstrap?: boolean;
96
84
  /** Loads and constructs the real bundle. Memoized by `mountApp`. */
package/dist/openapi.d.ts CHANGED
@@ -141,8 +141,7 @@ export declare function buildModulePathOwnership(mounts: readonly ModuleMount[],
141
141
  * admin/storefront path (voyant#2733).
142
142
  *
143
143
  * Uses the ownership map as the authoritative module owner, falling back to the
144
- * path's own segment for anything the manifest doesn't claim (e.g.
145
- * `additionalRoutes` mounts like the operator's workflow-runs admin surface).
144
+ * path's own segment for anything the manifest doesn't claim.
146
145
  * The full surface is therefore partitioned exactly: every `/v1/admin/*` and
147
146
  * `/v1/public/*` path lands in exactly one module document. Non-surface routes
148
147
  * (`/v1/<name>` webhooks, legacy `/v1/*`) live only in the aggregate, as before.
package/dist/openapi.js CHANGED
@@ -220,8 +220,7 @@ export async function buildModulePathOwnership(mounts, options) {
220
220
  * admin/storefront path (voyant#2733).
221
221
  *
222
222
  * Uses the ownership map as the authoritative module owner, falling back to the
223
- * path's own segment for anything the manifest doesn't claim (e.g.
224
- * `additionalRoutes` mounts like the operator's workflow-runs admin surface).
223
+ * path's own segment for anything the manifest doesn't claim.
225
224
  * The full surface is therefore partitioned exactly: every `/v1/admin/*` and
226
225
  * `/v1/public/*` path lands in exactly one module document. Non-surface routes
227
226
  * (`/v1/<name>` webhooks, legacy `/v1/*`) live only in the aggregate, as before.
package/dist/types.d.ts CHANGED
@@ -2,7 +2,6 @@ import type { Actor, VoyantVariables as CoreVoyantVariables, EventBus, LinkDefin
2
2
  import type { SelectApikey } from "@voyant-travel/db/schema/iam";
3
3
  import type { AccessCatalog } from "@voyant-travel/types/api-keys";
4
4
  import type { KVStore } from "@voyant-travel/utils/cache";
5
- import type { DriverFactory } from "@voyant-travel/workflows/driver";
6
5
  import type { NeonHttpDatabase } from "drizzle-orm/neon-http";
7
6
  import type { NeonDatabase as NeonWsDatabase } from "drizzle-orm/neon-serverless";
8
7
  import type { PostgresJsDatabase } from "drizzle-orm/postgres-js";
@@ -47,8 +46,6 @@ export type VoyantVariables = CoreVoyantVariables & {
47
46
  link?: LinkService;
48
47
  /** Shared cross-module query runtime, when the app wires one in. */
49
48
  query?: VoyantQueryRuntime;
50
- /** Optional workflow driver surfaced to HTTP routes after lazy app bootstrap. */
51
- workflowDriver?: import("@voyant-travel/workflows/driver").WorkflowDriver;
52
49
  };
53
50
  /** Handler contract for application-authored Hono API routes. */
54
51
  export type VoyantRouteHandler<TBindings extends VoyantBindings = VoyantBindings> = Handler<{
@@ -291,17 +288,6 @@ export interface VoyantAppConfig<TBindings extends VoyantBindings = VoyantBindin
291
288
  * individual policies.
292
289
  */
293
290
  rateLimit?: false | import("./middleware/rate-limit.js").RateLimitConfig;
294
- /**
295
- * Workflow runtime configuration. When set, `createApp()` collects
296
- * `module.workflows` + `module.eventFilters` (plus the same fields
297
- * from plugins), invokes `workflows.driver` with framework deps, and
298
- * — inside the lazy bootstrap path — registers the manifest with the
299
- * driver and installs an EventBus forwarder that routes emitted
300
- * events to `driver.ingestEvent(...)`.
301
- *
302
- * See `docs/architecture/workflows-runtime-architecture.md` §6, §18.
303
- */
304
- workflows?: VoyantWorkflowsConfig;
305
291
  /**
306
292
  * Admin API capability metadata, served at `GET /v1/admin/_meta/capabilities`
307
293
  * so clients (the admin SDK) can discover what this deployment supports —
@@ -325,58 +311,3 @@ export interface VoyantAppConfig<TBindings extends VoyantBindings = VoyantBindin
325
311
  };
326
312
  additionalRoutes?: (app: Hono<any>) => void;
327
313
  }
328
- /**
329
- * Workflow runtime configuration block. The driver is resolved at boot
330
- * time (inside the lazy bootstrap path), after framework deps and —
331
- * crucially — after runtime bindings are available.
332
- *
333
- * `driver` is **always** a function-of-bindings: `(env) => DriverFactory`.
334
- * This unambiguous shape works for local node runtimes and managed-cloud
335
- * forwarding drivers:
336
- *
337
- * **Node / InMemory** — wrap your direct factory:
338
- *
339
- * workflows: {
340
- * driver: () => createStandaloneDriver({ db }),
341
- * }
342
- *
343
- * **Managed Cloud forwarding** — pull credentials off `env`:
344
- *
345
- * workflows: {
346
- * driver: (env) => () => createCloudWorkflowDriver({
347
- * baseUrl: env.VOYANT_CLOUD_WORKFLOWS_URL,
348
- * triggerToken: env.VOYANT_CLOUD_WORKFLOW_TRIGGER_TOKEN,
349
- * appSlug: env.VOYANT_CLOUD_APP_SLUG,
350
- * environment: env.VOYANT_CLOUD_ENVIRONMENT,
351
- * }),
352
- * }
353
- *
354
- * The single shape avoids ambiguous "is this a factory or a
355
- * factory-of-factories?" heuristics. See architecture doc §6.3 +
356
- * reviewer feedback P2.1.
357
- */
358
- export interface VoyantWorkflowsConfig<TBindings = unknown> {
359
- /**
360
- * Function-of-bindings that returns a `DriverFactory`. Resolved
361
- * lazily with `c.env` once bindings are available, then invoked
362
- * with `{ services, logger }` to produce the driver.
363
- */
364
- driver: (bindings: TBindings) => DriverFactory;
365
- /**
366
- * Environment the manifest registers under. Defaults to `"development"`.
367
- * Workflow filters are environment-scoped (production manifests don't
368
- * see preview events and vice versa) per architecture doc §21.10.
369
- */
370
- environment?: "production" | "preview" | "development";
371
- /**
372
- * Project / tenant identifier baked into the manifest. Single-tenant
373
- * runtimes leave this unset (defaults to `"default"`). Multi-tenant
374
- * deployments override per-app via voyant-cloud's wrapper layer.
375
- */
376
- projectId?: string;
377
- }
378
- /**
379
- * Structural shape of a `DriverFactory` from `@voyant-travel/workflows/driver`.
380
- * The SDK package's concrete `DriverFactory` satisfies this via TS
381
- * structural compat (architecture doc §21.19).
382
- */
package/dist/types.js CHANGED
@@ -22,8 +22,3 @@ export function resolveDbFactoryResult(value) {
22
22
  const dispose = dbClientDispose(value);
23
23
  return dispose ? { db: value, dispose } : { db: value };
24
24
  }
25
- /**
26
- * Structural shape of a `DriverFactory` from `@voyant-travel/workflows/driver`.
27
- * The SDK package's concrete `DriverFactory` satisfies this via TS
28
- * structural compat (architecture doc §21.19).
29
- */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@voyant-travel/hono",
3
- "version": "0.133.0",
3
+ "version": "0.134.1",
4
4
  "license": "Apache-2.0",
5
5
  "type": "module",
6
6
  "exports": {
@@ -130,17 +130,15 @@
130
130
  "drizzle-orm": "^0.45.2",
131
131
  "hono": "^4.12.27",
132
132
  "zod": "^4.4.3",
133
- "@voyant-travel/core": "^0.130.0",
134
- "@voyant-travel/db": "^0.117.1",
135
- "@voyant-travel/types": "^0.109.8",
133
+ "@voyant-travel/core": "^0.132.0",
134
+ "@voyant-travel/db": "^0.118.1",
136
135
  "@voyant-travel/utils": "^0.109.0",
137
- "@voyant-travel/workflows": "^0.122.18"
136
+ "@voyant-travel/types": "^0.109.9"
138
137
  },
139
138
  "devDependencies": {
140
139
  "typescript": "^6.0.3",
141
140
  "vitest": "^4.1.9",
142
- "@voyant-travel/voyant-typescript-config": "^0.1.0",
143
- "@voyant-travel/workflows-orchestrator": "^0.122.18"
141
+ "@voyant-travel/voyant-typescript-config": "^0.1.0"
144
142
  },
145
143
  "files": [
146
144
  "dist"
@@ -1,30 +0,0 @@
1
- import type { EventEnvelope, EventFilterDescriptor, Module, ModuleContainer, WorkflowDescriptor } from "@voyant-travel/core";
2
- import type { WorkflowDriver } from "@voyant-travel/workflows/driver";
3
- import type { VoyantAppConfig } from "./types.js";
4
- export interface WireWorkflowRuntimeArgs {
5
- modules: ReadonlyArray<Module>;
6
- collectedWorkflows: ReadonlyArray<WorkflowDescriptor>;
7
- collectedFilters: ReadonlyArray<EventFilterDescriptor>;
8
- driver: WorkflowDriver;
9
- environment: "production" | "preview" | "development";
10
- projectId: string;
11
- eventBus: {
12
- subscribe(event: string, handler: (e: EventEnvelope) => Promise<void> | void): unknown;
13
- };
14
- }
15
- /**
16
- * Build the manifest, register it with the driver, and install a single
17
- * EventBus subscriber per unique eventType seen across the manifest's filters.
18
- */
19
- export declare function wireWorkflowRuntime(args: WireWorkflowRuntimeArgs): Promise<void>;
20
- /**
21
- * Adapt the framework's `ModuleContainer` to a read-only `ServiceResolver`.
22
- */
23
- export declare function containerToServiceResolver(container: ModuleContainer): {
24
- resolve<T>(name: string): T;
25
- has(name: string): boolean;
26
- };
27
- /**
28
- * Adapt the framework's optional `LoggerProvider` to the workflow driver logger.
29
- */
30
- export declare function makeFrameworkLogger(loggerProvider: VoyantAppConfig["logger"] | undefined): (level: "debug" | "info" | "warn" | "error", msg: string, data?: object) => void;
@@ -1,115 +0,0 @@
1
- function toManifestWorkflowDescriptor(wf) {
2
- const candidate = wf;
3
- return candidate.config ? { id: wf.id, config: candidate.config } : { id: wf.id };
4
- }
5
- /**
6
- * Build the manifest, register it with the driver, and install a single
7
- * EventBus subscriber per unique eventType seen across the manifest's filters.
8
- */
9
- export async function wireWorkflowRuntime(args) {
10
- // Keep app composition importable before workspace packages are built. The
11
- // workflow implementation is needed only when a configured app boots.
12
- const { buildManifest } = await import("@voyant-travel/workflows/events");
13
- // The descriptors collected from modules + plugins use core's structural
14
- // types (`{ id, eventType }` only - see `EventFilterDescriptor` in core);
15
- // the manifest builder needs the runtime shape with `.manifest` populated.
16
- // Validate before casting so a contract-violating plugin fails loudly here
17
- // instead of crashing on `entry.manifest.id` deep inside the sort.
18
- const filterEntries = [];
19
- for (const entry of args.collectedFilters) {
20
- const candidate = entry;
21
- if (!candidate.manifest || typeof candidate.manifest.id !== "string") {
22
- throw new Error(`[voyant] event filter "${entry.id}" (event "${entry.eventType}") is missing the runtime ` +
23
- `\`manifest\` field. Filters must be produced via \`trigger.on(eventName, { ... })\` from ` +
24
- `@voyant-travel/workflows - the public EventFilterDescriptor is the structural minimum, but ` +
25
- `createApp() needs the manifest payload to register with the driver.`);
26
- }
27
- filterEntries.push({
28
- id: candidate.manifest.id,
29
- eventType: candidate.manifest.eventType,
30
- manifest: candidate.manifest,
31
- declaration: { target: { id: candidate.manifest.targetWorkflowId } },
32
- targetWorkflowId: candidate.manifest.targetWorkflowId,
33
- });
34
- }
35
- const manifest = await buildManifest({
36
- projectId: args.projectId,
37
- environment: args.environment,
38
- workflows: args.collectedWorkflows.map(toManifestWorkflowDescriptor),
39
- eventFilters: filterEntries,
40
- });
41
- await args.driver.registerManifest({
42
- environment: args.environment,
43
- manifest,
44
- });
45
- // Install one EventBus subscriber per unique eventType. Each subscriber
46
- // forwards the envelope through `driver.ingestEvent(...)`, which routes
47
- // through the same predicate/mapper machinery the HTTP ingest path uses.
48
- const eventTypes = new Set(filterEntries.map((f) => f.manifest.eventType));
49
- for (const eventType of eventTypes) {
50
- args.eventBus.subscribe(eventType, async (envelope) => {
51
- const stamped = ensureMetadataEventId(envelope);
52
- // Let ingest failures propagate: the EventBus catches every subscriber
53
- // throw (the emitter and sibling handlers stay unaffected per its
54
- // fire-and-forget contract) AND routes it through `onSubscriberError`, so
55
- // a misbehaving driver / network glitch is both logged and reported via
56
- // the framework reporter — instead of being swallowed here (RFC #1553).
57
- await args.driver.ingestEvent({
58
- environment: args.environment,
59
- envelope: stamped,
60
- });
61
- });
62
- }
63
- }
64
- /**
65
- * Adapt the framework's `ModuleContainer` to a read-only `ServiceResolver`.
66
- */
67
- export function containerToServiceResolver(container) {
68
- return {
69
- resolve(name) {
70
- return container.resolve(name);
71
- },
72
- has(name) {
73
- return container.has(name);
74
- },
75
- };
76
- }
77
- /**
78
- * Adapt the framework's optional `LoggerProvider` to the workflow driver logger.
79
- */
80
- export function makeFrameworkLogger(loggerProvider) {
81
- void loggerProvider;
82
- return (level, msg, data) => {
83
- const fn = level === "error"
84
- ? console.error
85
- : level === "warn"
86
- ? console.warn
87
- : level === "debug"
88
- ? console.debug
89
- : console.log;
90
- if (data !== undefined)
91
- fn(`[voyant] ${msg}`, data);
92
- else
93
- fn(`[voyant] ${msg}`);
94
- };
95
- }
96
- function ensureMetadataEventId(envelope) {
97
- const metadata = envelope.metadata;
98
- if (metadata !== undefined &&
99
- metadata !== null &&
100
- typeof metadata === "object" &&
101
- typeof metadata.eventId === "string" &&
102
- (metadata.eventId.length ?? 0) > 0) {
103
- return envelope;
104
- }
105
- const eventId = `evt_${Date.now().toString(36)}_${Math.floor(Math.random() * 1_000_000)
106
- .toString(36)
107
- .padStart(4, "0")}`;
108
- return {
109
- ...envelope,
110
- metadata: {
111
- ...(metadata ?? {}),
112
- eventId,
113
- },
114
- };
115
- }