@voyant-travel/hono 0.127.2 → 0.128.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/README.md +5 -4
- package/dist/anonymous-paths.d.ts +2 -2
- package/dist/anonymous-paths.js +1 -1
- package/dist/app.js +5 -11
- package/dist/bundle.d.ts +23 -23
- package/dist/bundle.js +8 -8
- package/dist/composition.d.ts +5 -5
- package/dist/index.d.ts +4 -4
- package/dist/index.js +1 -1
- package/dist/lazy-routes.d.ts +1 -1
- package/dist/middleware/index.d.ts +1 -2
- package/dist/middleware/index.js +1 -2
- package/dist/middleware/metrics.d.ts +8 -19
- package/dist/middleware/metrics.js +3 -8
- package/dist/middleware/rate-limit.d.ts +7 -50
- package/dist/middleware/rate-limit.js +3 -75
- package/dist/module.d.ts +10 -10
- package/dist/types.d.ts +5 -22
- package/package.json +5 -6
package/README.md
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
# @voyant-travel/hono
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
helpers, and
|
|
3
|
+
Voyant's sole server API runtime implementation. Provides `createApp()`,
|
|
4
|
+
middleware, auth helpers, and API bundle expansion for mounting Voyant modules
|
|
5
|
+
behind a Hono app.
|
|
5
6
|
|
|
6
7
|
## Install
|
|
7
8
|
|
|
@@ -52,8 +53,8 @@ The middleware chain is: container → requestId → logger → errorBoundary
|
|
|
52
53
|
| --- | --- |
|
|
53
54
|
| `.` | Barrel re-exports |
|
|
54
55
|
| `./app` | `createApp` factory |
|
|
55
|
-
| `./module` | `
|
|
56
|
-
| `./bundle` | `
|
|
56
|
+
| `./module` | `ApiModule`, `ApiExtension` contracts |
|
|
57
|
+
| `./bundle` | `ApiBundle`, `defineApiBundle`, `expandApiBundles` |
|
|
57
58
|
| `./middleware` | All middleware re-exports |
|
|
58
59
|
| `./middleware/auth` | `requireAuth` session/API-key/JWT auth |
|
|
59
60
|
| `./middleware/cors` | CORS configuration |
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { ApiExtension, ApiModule } from "./module.js";
|
|
2
2
|
/**
|
|
3
3
|
* Assemble the anonymous-access allow-list (ADR-0008) from module/extension
|
|
4
4
|
* `anonymous` declarations, unioned with any explicit `publicPaths` the
|
|
@@ -11,4 +11,4 @@ import type { HonoExtension, HonoModule } from "./module.js";
|
|
|
11
11
|
* deterministic, snapshot-auditable result; the global list is what `requireAuth`
|
|
12
12
|
* matches to skip auth (and stamp `actor: "customer"`).
|
|
13
13
|
*/
|
|
14
|
-
export declare function assembleAnonymousPaths(modules: readonly
|
|
14
|
+
export declare function assembleAnonymousPaths(modules: readonly ApiModule[], extensions: readonly ApiExtension[], explicit?: readonly string[]): string[];
|
package/dist/anonymous-paths.js
CHANGED
|
@@ -31,7 +31,7 @@ export function assembleAnonymousPaths(modules, extensions, explicit = []) {
|
|
|
31
31
|
// at `/v1/{name}`, matching the mount in `app.ts`. Parameterized/wildcard paths
|
|
32
32
|
// are skipped (the literal `matchesPublicPath` matcher can't match them) and
|
|
33
33
|
// must be declared via `anonymous` if ever needed.
|
|
34
|
-
// biome-ignore lint/suspicious/noExplicitAny: Hono sub-apps have varied env generics -- owner: hono; mirrors the
|
|
34
|
+
// biome-ignore lint/suspicious/noExplicitAny: Hono sub-apps have varied env generics -- owner: hono; mirrors the ApiModule.webhookRoutes suppression.
|
|
35
35
|
const addWebhooks = (name, routes) => {
|
|
36
36
|
if (!routes)
|
|
37
37
|
return;
|
package/dist/app.js
CHANGED
|
@@ -5,7 +5,7 @@ import { createContainer, createEventBus, createQueryRunner, } from "@voyant-tra
|
|
|
5
5
|
import { createLinkServiceFactory } from "@voyant-travel/db/links";
|
|
6
6
|
import { assembleAnonymousPaths } from "./anonymous-paths.js";
|
|
7
7
|
import { containerToServiceResolver, makeFrameworkLogger, wireWorkflowRuntime, } from "./app-workflows.js";
|
|
8
|
-
import {
|
|
8
|
+
import { expandApiBundles, isLazyApiBundle, } from "./bundle.js";
|
|
9
9
|
import { mountLazyRoutePaths, mountLazyRoutesAt } from "./lazy-routes.js";
|
|
10
10
|
import { mountAuthForwarding } from "./lib/auth-forward.js";
|
|
11
11
|
import { createPathDbSelector } from "./lib/db-selector.js";
|
|
@@ -19,7 +19,6 @@ import { cors } from "./middleware/cors.js";
|
|
|
19
19
|
import { db } from "./middleware/db.js";
|
|
20
20
|
import { handleApiError, requestId } from "./middleware/error-boundary.js";
|
|
21
21
|
import { logger } from "./middleware/logger.js";
|
|
22
|
-
import { metrics } from "./middleware/metrics.js";
|
|
23
22
|
import { publicResponseCache } from "./middleware/public-cache.js";
|
|
24
23
|
import { rateLimit, resolveRateLimitStore, } from "./middleware/rate-limit.js";
|
|
25
24
|
import { requireActor } from "./middleware/require-actor.js";
|
|
@@ -80,14 +79,14 @@ export function mountApp(config) {
|
|
|
80
79
|
throw new Error(`Duplicate bundle name: "${plugin.name}"`);
|
|
81
80
|
}
|
|
82
81
|
pluginNames.add(plugin.name);
|
|
83
|
-
if (
|
|
82
|
+
if (isLazyApiBundle(plugin))
|
|
84
83
|
lazyPlugins.push(plugin);
|
|
85
84
|
else
|
|
86
85
|
eagerPlugins.push(plugin);
|
|
87
86
|
}
|
|
88
87
|
// Expand eager plugins into their constituent modules/extensions before
|
|
89
88
|
// mounting. Lazy plugins keep only their static metadata in the eager closure.
|
|
90
|
-
const expanded = eagerPlugins.length > 0 ?
|
|
89
|
+
const expanded = eagerPlugins.length > 0 ? expandApiBundles(eagerPlugins) : null;
|
|
91
90
|
const allModules = [...(config.modules ?? []), ...(expanded?.modules ?? [])];
|
|
92
91
|
const allExtensions = [...(config.extensions ?? []), ...(expanded?.extensions ?? [])];
|
|
93
92
|
const linkDefinitions = [...(config.linkDefinitions ?? []), ...(expanded?.links ?? [])];
|
|
@@ -252,7 +251,7 @@ export function mountApp(config) {
|
|
|
252
251
|
const pending = bundles.filter((bundle) => !expandedLazyBundleNames.has(bundle.name));
|
|
253
252
|
if (pending.length === 0)
|
|
254
253
|
return;
|
|
255
|
-
const lazyExpanded =
|
|
254
|
+
const lazyExpanded = expandApiBundles(pending);
|
|
256
255
|
for (const bundle of pending)
|
|
257
256
|
expandedLazyBundleNames.add(bundle.name);
|
|
258
257
|
allModules.push(...lazyExpanded.modules);
|
|
@@ -372,11 +371,6 @@ export function mountApp(config) {
|
|
|
372
371
|
app.use("*", requestId);
|
|
373
372
|
// Structured logger
|
|
374
373
|
app.use("*", logger(config.logger));
|
|
375
|
-
// Per-request metrics → Analytics Engine (no-op without the binding).
|
|
376
|
-
// Mounted before the cache middleware so cache hits are measured too.
|
|
377
|
-
if (config.metrics !== false) {
|
|
378
|
-
app.use("*", metrics());
|
|
379
|
-
}
|
|
380
374
|
// CORS (allowlist via env CORS_ALLOWLIST)
|
|
381
375
|
app.use("*", cors());
|
|
382
376
|
if (config.securityHeaders !== false) {
|
|
@@ -604,7 +598,7 @@ export function mountApp(config) {
|
|
|
604
598
|
}
|
|
605
599
|
function buildBundleRouteApp(bundle) {
|
|
606
600
|
const pluginRoutes = new OpenAPIHono();
|
|
607
|
-
const bundleExpanded =
|
|
601
|
+
const bundleExpanded = expandApiBundles([bundle]);
|
|
608
602
|
for (const mod of bundleExpanded.modules)
|
|
609
603
|
mountModuleRoutesInto(pluginRoutes, mod);
|
|
610
604
|
for (const ext of bundleExpanded.extensions)
|
package/dist/bundle.d.ts
CHANGED
|
@@ -1,27 +1,27 @@
|
|
|
1
1
|
import type { BootstrapHandler, EventFilterDescriptor, LinkDefinition, Subscriber, WorkflowDescriptor } from "@voyant-travel/core";
|
|
2
|
-
import type {
|
|
2
|
+
import type { ApiExtension, ApiModule } from "./module.js";
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
4
|
+
* Reusable server API contribution surface.
|
|
5
5
|
*
|
|
6
|
-
* `@voyant-travel/hono` is
|
|
7
|
-
* `
|
|
8
|
-
*
|
|
6
|
+
* `@voyant-travel/hono` is Voyant's sole server API runtime implementation.
|
|
7
|
+
* `ApiBundle` describes packages that contribute {@link ApiModule} /
|
|
8
|
+
* {@link ApiExtension} wrappers carrying HTTP routes.
|
|
9
9
|
*
|
|
10
10
|
* Registered via `createApp({ plugins: [...] })` — the app factory expands
|
|
11
11
|
* each bundle into the underlying modules, extensions, subscribers, and link
|
|
12
12
|
* definitions before mounting them.
|
|
13
13
|
*/
|
|
14
|
-
export interface
|
|
14
|
+
export interface ApiBundle {
|
|
15
15
|
/** Unique bundle identifier (e.g. "payload-cms", "bokun"). */
|
|
16
16
|
name: string;
|
|
17
17
|
/** Optional version tag for diagnostics. */
|
|
18
18
|
version?: string;
|
|
19
19
|
/** Optional lazy runtime bootstrap executed once per app/isolate. */
|
|
20
20
|
bootstrap?: BootstrapHandler;
|
|
21
|
-
/**
|
|
22
|
-
modules?:
|
|
23
|
-
/**
|
|
24
|
-
extensions?:
|
|
21
|
+
/** API modules (module + routes) contributed by the plugin. */
|
|
22
|
+
modules?: ApiModule[];
|
|
23
|
+
/** API extensions (extension + routes) contributed by the plugin. */
|
|
24
|
+
extensions?: ApiExtension[];
|
|
25
25
|
/** Event subscribers wired to the caller's event bus, when provided. */
|
|
26
26
|
subscribers?: Subscriber[];
|
|
27
27
|
/** Link definitions contributed by the plugin. */
|
|
@@ -49,15 +49,15 @@ export interface HonoBundle {
|
|
|
49
49
|
*/
|
|
50
50
|
eventFilters?: readonly EventFilterDescriptor[];
|
|
51
51
|
}
|
|
52
|
-
declare const
|
|
52
|
+
declare const LAZY_API_BUNDLE: unique symbol;
|
|
53
53
|
/**
|
|
54
54
|
* Lazy bundle declaration. The bundle's heavy runtime graph is imported only
|
|
55
55
|
* when a declared route matcher is hit, unless `loadOnBootstrap` asks the app
|
|
56
56
|
* to load it during request/headless bootstrap. `name` and `anonymous` remain
|
|
57
57
|
* eager metadata so duplicate checks and auth allow-lists stay fail-closed.
|
|
58
58
|
*/
|
|
59
|
-
export interface
|
|
60
|
-
readonly [
|
|
59
|
+
export interface LazyApiBundle {
|
|
60
|
+
readonly [LAZY_API_BUNDLE]: true;
|
|
61
61
|
/** Unique bundle identifier matching the loaded bundle's `name`. */
|
|
62
62
|
name: string;
|
|
63
63
|
/** Optional version tag for diagnostics. */
|
|
@@ -94,27 +94,27 @@ export interface LazyHonoBundle {
|
|
|
94
94
|
*/
|
|
95
95
|
loadOnBootstrap?: boolean;
|
|
96
96
|
/** Loads and constructs the real bundle. Memoized by `mountApp`. */
|
|
97
|
-
load: () => Promise<
|
|
97
|
+
load: () => Promise<ApiBundle>;
|
|
98
98
|
}
|
|
99
|
-
export type
|
|
99
|
+
export type ApiBundleInput = ApiBundle | LazyApiBundle;
|
|
100
100
|
/**
|
|
101
101
|
* Identity helper — returns the bundle unchanged, purely for IDE inference.
|
|
102
102
|
*/
|
|
103
|
-
export declare function
|
|
104
|
-
export declare function
|
|
105
|
-
export declare function
|
|
106
|
-
export interface
|
|
107
|
-
modules:
|
|
108
|
-
extensions:
|
|
103
|
+
export declare function defineApiBundle<P extends ApiBundle>(bundle: P): P;
|
|
104
|
+
export declare function defineLazyApiBundle<P extends Omit<LazyApiBundle, typeof LAZY_API_BUNDLE>>(bundle: P): P & LazyApiBundle;
|
|
105
|
+
export declare function isLazyApiBundle(bundle: ApiBundleInput): bundle is LazyApiBundle;
|
|
106
|
+
export interface ExpandedApiBundles {
|
|
107
|
+
modules: ApiModule[];
|
|
108
|
+
extensions: ApiExtension[];
|
|
109
109
|
subscribers: Subscriber[];
|
|
110
110
|
links: LinkDefinition[];
|
|
111
111
|
/** Absolute anonymous-access paths declared by bundles (ADR-0008). */
|
|
112
112
|
anonymousPaths: string[];
|
|
113
113
|
}
|
|
114
114
|
/**
|
|
115
|
-
* Flatten a list of {@link
|
|
115
|
+
* Flatten a list of {@link ApiBundle} values into their constituent pieces.
|
|
116
116
|
*
|
|
117
117
|
* Throws if two bundles declare the same `name`.
|
|
118
118
|
*/
|
|
119
|
-
export declare function
|
|
119
|
+
export declare function expandApiBundles(bundles: ReadonlyArray<ApiBundle>): ExpandedApiBundles;
|
|
120
120
|
export {};
|
package/dist/bundle.js
CHANGED
|
@@ -1,22 +1,22 @@
|
|
|
1
|
-
const
|
|
1
|
+
const LAZY_API_BUNDLE = Symbol.for("voyant.api.lazyBundle");
|
|
2
2
|
/**
|
|
3
3
|
* Identity helper — returns the bundle unchanged, purely for IDE inference.
|
|
4
4
|
*/
|
|
5
|
-
export function
|
|
5
|
+
export function defineApiBundle(bundle) {
|
|
6
6
|
return bundle;
|
|
7
7
|
}
|
|
8
|
-
export function
|
|
9
|
-
return { ...bundle, [
|
|
8
|
+
export function defineLazyApiBundle(bundle) {
|
|
9
|
+
return { ...bundle, [LAZY_API_BUNDLE]: true };
|
|
10
10
|
}
|
|
11
|
-
export function
|
|
12
|
-
return bundle[
|
|
11
|
+
export function isLazyApiBundle(bundle) {
|
|
12
|
+
return bundle[LAZY_API_BUNDLE] === true;
|
|
13
13
|
}
|
|
14
14
|
/**
|
|
15
|
-
* Flatten a list of {@link
|
|
15
|
+
* Flatten a list of {@link ApiBundle} values into their constituent pieces.
|
|
16
16
|
*
|
|
17
17
|
* Throws if two bundles declare the same `name`.
|
|
18
18
|
*/
|
|
19
|
-
export function
|
|
19
|
+
export function expandApiBundles(bundles) {
|
|
20
20
|
const seen = new Set();
|
|
21
21
|
const modules = [];
|
|
22
22
|
const extensions = [];
|
package/dist/composition.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { ApiExtension, ApiModule } from "./module.js";
|
|
2
2
|
/**
|
|
3
3
|
* Manifest-driven runtime composition.
|
|
4
4
|
*
|
|
@@ -30,8 +30,8 @@ export interface CompositionContext<TCapabilities> {
|
|
|
30
30
|
capabilities: TCapabilities;
|
|
31
31
|
options: Record<string, unknown>;
|
|
32
32
|
}
|
|
33
|
-
export type ModuleFactory<TCapabilities> = (ctx: CompositionContext<TCapabilities>) =>
|
|
34
|
-
export type ExtensionFactory<TCapabilities> = (ctx: CompositionContext<TCapabilities>) =>
|
|
33
|
+
export type ModuleFactory<TCapabilities> = (ctx: CompositionContext<TCapabilities>) => ApiModule | ApiModule[];
|
|
34
|
+
export type ExtensionFactory<TCapabilities> = (ctx: CompositionContext<TCapabilities>) => ApiExtension;
|
|
35
35
|
/**
|
|
36
36
|
* Maps manifest specifiers to the factory that builds the runtime unit. Keys
|
|
37
37
|
* MUST match the `voyant.config.ts` `modules` / `extensions` specifiers.
|
|
@@ -41,8 +41,8 @@ export interface CompositionRegistry<TCapabilities> {
|
|
|
41
41
|
extensions?: Record<string, ExtensionFactory<TCapabilities>>;
|
|
42
42
|
}
|
|
43
43
|
export interface ComposedApp {
|
|
44
|
-
modules:
|
|
45
|
-
extensions:
|
|
44
|
+
modules: ApiModule[];
|
|
45
|
+
extensions: ApiExtension[];
|
|
46
46
|
}
|
|
47
47
|
/**
|
|
48
48
|
* Derive the `createApp({ modules, extensions })` arrays from a manifest by
|
package/dist/index.d.ts
CHANGED
|
@@ -3,17 +3,17 @@ export { assembleAnonymousPaths } from "./anonymous-paths.js";
|
|
|
3
3
|
export { mountApp } from "./app.js";
|
|
4
4
|
export type { SessionAuthContext } from "./auth/index.js";
|
|
5
5
|
export { constantTimeEqual, extractBearerToken, generateNumericCode, randomBytesHex, requireUserId, sha256Base64Url, sha256Hex, unsignCookie, verifySession, } from "./auth/index.js";
|
|
6
|
-
export type {
|
|
7
|
-
export {
|
|
6
|
+
export type { ApiBundle, ApiBundleInput, ExpandedApiBundles, LazyApiBundle, } from "./bundle.js";
|
|
7
|
+
export { defineApiBundle, defineLazyApiBundle, expandApiBundles, isLazyApiBundle, } from "./bundle.js";
|
|
8
8
|
export { type CreateAppConfig, createApp } from "./create-app.js";
|
|
9
9
|
export type { AuthenticatedDocumentDownloadResolver, AuthenticatedDocumentDownloadResolverOptions, DocumentDownloadEnvelope, DocumentDownloadResolution, DocumentDownloadResolver, DocumentDownloadResolverResult, StoredDocumentReference, } from "./document-download.js";
|
|
10
10
|
export { createAuthenticatedDocumentDownloadResolver, encodeStorageKeyPath, resolveStoredDocumentDownload, } from "./document-download.js";
|
|
11
11
|
export { type AsyncMethodProvider, lazyProvider } from "./lazy-provider.js";
|
|
12
|
-
export { createLazyRouteHandler, type
|
|
12
|
+
export { createLazyRouteHandler, type LazyApiRoutes, type LazyRoutesLoader, mountLazyRoutePaths, mountLazyRoutesAt, } from "./lazy-routes.js";
|
|
13
13
|
export { createPathDbSelector, type PathDbSelectorOptions } from "./lib/db-selector.js";
|
|
14
14
|
export type { RateLimitStore } from "./middleware/index.js";
|
|
15
15
|
export { clientIpKey, consoleLoggerProvider, cors, createMemoryRateLimitStore, createRedisRateLimitStore, DEFAULT_IDEMPOTENCY_TTL_MS, db, enforceRateLimit, errorBoundary, handleApiError, type IdempotencyKeyOptions, idempotencyKey, isStaffRbacEnforced, LIVE_LIMITS, logger, purgeExpiredIdempotencyKeys, rateLimit, requestId, requireActor, requireAuth, requirePermission, } from "./middleware/index.js";
|
|
16
|
-
export type {
|
|
16
|
+
export type { ApiExtension, ApiModule } from "./module.js";
|
|
17
17
|
export type { ErrorEvent, Reporter } from "./observability/index.js";
|
|
18
18
|
export { consoleReporter, getRequestId, noopReporter, runWithRequestId, safeCaptureException, } from "./observability/index.js";
|
|
19
19
|
export { stampOpenApiRegistryApiId } from "./openapi-ownership.js";
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export { assembleAnonymousPaths } from "./anonymous-paths.js";
|
|
2
2
|
export { mountApp } from "./app.js";
|
|
3
3
|
export { constantTimeEqual, extractBearerToken, generateNumericCode, randomBytesHex, requireUserId, sha256Base64Url, sha256Hex, unsignCookie, verifySession, } from "./auth/index.js";
|
|
4
|
-
export {
|
|
4
|
+
export { defineApiBundle, defineLazyApiBundle, expandApiBundles, isLazyApiBundle, } from "./bundle.js";
|
|
5
5
|
export { createApp } from "./create-app.js";
|
|
6
6
|
export { createAuthenticatedDocumentDownloadResolver, encodeStorageKeyPath, resolveStoredDocumentDownload, } from "./document-download.js";
|
|
7
7
|
export { lazyProvider } from "./lazy-provider.js";
|
package/dist/lazy-routes.d.ts
CHANGED
|
@@ -38,7 +38,7 @@ export type LazyRoutesLoader = () => Promise<AnyHono>;
|
|
|
38
38
|
* matchers the framework installs up front (no bundle import until a request
|
|
39
39
|
* matches).
|
|
40
40
|
*/
|
|
41
|
-
export interface
|
|
41
|
+
export interface LazyApiRoutes {
|
|
42
42
|
paths: readonly string[];
|
|
43
43
|
load: LazyRoutesLoader;
|
|
44
44
|
}
|
|
@@ -5,9 +5,8 @@ export { db } from "./db.js";
|
|
|
5
5
|
export { errorBoundary, type HandleApiErrorOptions, handleApiError, requestId, } from "./error-boundary.js";
|
|
6
6
|
export { DEFAULT_IDEMPOTENCY_TTL_MS, type IdempotencyKeyOptions, idempotencyKey, purgeExpiredIdempotencyKeys, } from "./idempotency-key.js";
|
|
7
7
|
export { consoleLoggerProvider, logger } from "./logger.js";
|
|
8
|
-
export { type AnalyticsEngineDatasetLike, DB_METRICS_CONTEXT_KEY, type MetricsMiddlewareOptions, metrics, type RequestDbMetrics, withQueryCounting, } from "./metrics.js";
|
|
9
8
|
export { type PublicCacheOptions, publicResponseCache, resetPublicCacheStateForTests, } from "./public-cache.js";
|
|
10
|
-
export {
|
|
9
|
+
export { clientIpKey, createMemoryRateLimitStore, createRedisRateLimitStore, enforceRateLimit, LIVE_LIMITS, type RateLimitConfig, type RateLimitPolicy, type RateLimitRequestContext, type RateLimitResult, type RateLimitRule, type RateLimitStore, type RedisRateLimitStoreOptions, rateLimit, resolveRateLimitStore, } from "./rate-limit.js";
|
|
11
10
|
export { isStaffRbacEnforced, requireActor } from "./require-actor.js";
|
|
12
11
|
export { requirePermission } from "./require-permission.js";
|
|
13
12
|
export { type SecurityHeadersOptions, securityHeaders } from "./security-headers.js";
|
package/dist/middleware/index.js
CHANGED
|
@@ -5,9 +5,8 @@ export { db } from "./db.js";
|
|
|
5
5
|
export { errorBoundary, handleApiError, requestId, } from "./error-boundary.js";
|
|
6
6
|
export { DEFAULT_IDEMPOTENCY_TTL_MS, idempotencyKey, purgeExpiredIdempotencyKeys, } from "./idempotency-key.js";
|
|
7
7
|
export { consoleLoggerProvider, logger } from "./logger.js";
|
|
8
|
-
export { DB_METRICS_CONTEXT_KEY, metrics, withQueryCounting, } from "./metrics.js";
|
|
9
8
|
export { publicResponseCache, resetPublicCacheStateForTests, } from "./public-cache.js";
|
|
10
|
-
export { clientIpKey,
|
|
9
|
+
export { clientIpKey, createMemoryRateLimitStore, createRedisRateLimitStore, enforceRateLimit, LIVE_LIMITS, rateLimit, resolveRateLimitStore, } from "./rate-limit.js";
|
|
11
10
|
export { isStaffRbacEnforced, requireActor } from "./require-actor.js";
|
|
12
11
|
export { requirePermission } from "./require-permission.js";
|
|
13
12
|
export { securityHeaders } from "./security-headers.js";
|
|
@@ -1,10 +1,5 @@
|
|
|
1
1
|
import type { MiddlewareHandler } from "hono";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Structural shape of a Workers Analytics Engine dataset binding —
|
|
5
|
-
* declared locally so `@voyant-travel/hono` needs no `@cloudflare/workers-types`
|
|
6
|
-
* dependency.
|
|
7
|
-
*/
|
|
2
|
+
/** Structural sink contract for request metrics. */
|
|
8
3
|
export interface AnalyticsEngineDatasetLike {
|
|
9
4
|
writeDataPoint(point: {
|
|
10
5
|
blobs?: string[];
|
|
@@ -18,19 +13,11 @@ export interface RequestDbMetrics {
|
|
|
18
13
|
}
|
|
19
14
|
export declare const DB_METRICS_CONTEXT_KEY = "__voyantDbMetrics";
|
|
20
15
|
export interface MetricsMiddlewareOptions {
|
|
21
|
-
/**
|
|
22
|
-
|
|
23
|
-
* `env.METRICS`. Returning undefined makes the middleware a no-op for
|
|
24
|
-
* that request (deployments without the binding pay ~nothing).
|
|
25
|
-
*/
|
|
26
|
-
dataset?: (env: unknown) => AnalyticsEngineDatasetLike | undefined;
|
|
16
|
+
/** Resolve the metrics sink for a request. Returning undefined is a no-op. */
|
|
17
|
+
dataset: (env: unknown) => AnalyticsEngineDatasetLike | undefined;
|
|
27
18
|
}
|
|
28
19
|
/**
|
|
29
|
-
* Per-request metrics
|
|
30
|
-
* 3.4, the in-worker half — the platform dispatcher's DISPATCH_METRICS
|
|
31
|
-
* dataset already records hostname/plan/cache/duration per dispatch;
|
|
32
|
-
* this adds what only the worker can see: the matched route pattern,
|
|
33
|
-
* the db query count, and in-worker cache hits).
|
|
20
|
+
* Per-request metrics for an explicitly supplied deployment sink.
|
|
34
21
|
*
|
|
35
22
|
* One data point per request:
|
|
36
23
|
* - blobs: [method, routePattern, surface, cacheStatus]
|
|
@@ -40,8 +27,10 @@ export interface MetricsMiddlewareOptions {
|
|
|
40
27
|
* `writeDataPoint` is fire-and-forget and never throws into the
|
|
41
28
|
* request; a missing binding short-circuits before timing overhead.
|
|
42
29
|
*/
|
|
43
|
-
export declare function metrics(options
|
|
44
|
-
Variables:
|
|
30
|
+
export declare function metrics(options: MetricsMiddlewareOptions): MiddlewareHandler<{
|
|
31
|
+
Variables: {
|
|
32
|
+
[DB_METRICS_CONTEXT_KEY]?: RequestDbMetrics;
|
|
33
|
+
};
|
|
45
34
|
}>;
|
|
46
35
|
/**
|
|
47
36
|
* Wrap a drizzle client so top-level query-initiating calls increment
|
|
@@ -9,11 +9,7 @@ function surfaceOf(path) {
|
|
|
9
9
|
return "other";
|
|
10
10
|
}
|
|
11
11
|
/**
|
|
12
|
-
* Per-request metrics
|
|
13
|
-
* 3.4, the in-worker half — the platform dispatcher's DISPATCH_METRICS
|
|
14
|
-
* dataset already records hostname/plan/cache/duration per dispatch;
|
|
15
|
-
* this adds what only the worker can see: the matched route pattern,
|
|
16
|
-
* the db query count, and in-worker cache hits).
|
|
12
|
+
* Per-request metrics for an explicitly supplied deployment sink.
|
|
17
13
|
*
|
|
18
14
|
* One data point per request:
|
|
19
15
|
* - blobs: [method, routePattern, surface, cacheStatus]
|
|
@@ -23,9 +19,8 @@ function surfaceOf(path) {
|
|
|
23
19
|
* `writeDataPoint` is fire-and-forget and never throws into the
|
|
24
20
|
* request; a missing binding short-circuits before timing overhead.
|
|
25
21
|
*/
|
|
26
|
-
export function metrics(options
|
|
27
|
-
const resolveDataset = options.dataset
|
|
28
|
-
((env) => env?.METRICS);
|
|
22
|
+
export function metrics(options) {
|
|
23
|
+
const resolveDataset = options.dataset;
|
|
29
24
|
return async (c, next) => {
|
|
30
25
|
const dataset = resolveDataset(c.env);
|
|
31
26
|
if (!dataset || typeof dataset.writeDataPoint !== "function") {
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import type { KVStore } from "@voyant-travel/utils/cache";
|
|
2
1
|
import { type LazyRedisClient } from "@voyant-travel/utils/redis-client";
|
|
3
2
|
import type { MiddlewareHandler } from "hono";
|
|
4
3
|
/**
|
|
@@ -6,11 +5,9 @@ import type { MiddlewareHandler } from "hono";
|
|
|
6
5
|
*
|
|
7
6
|
* The limiter is split into two halves:
|
|
8
7
|
*
|
|
9
|
-
* - a {@link RateLimitStore} — the counting backend.
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* fixed-window counter (best-effort — see {@link createKvRateLimitStore}),
|
|
13
|
-
* and an in-memory Map for Node/dev/tests (per-isolate).
|
|
8
|
+
* - a {@link RateLimitStore} — the counting backend. Composition injects the
|
|
9
|
+
* selected provider; an in-memory Map remains the zero-configuration
|
|
10
|
+
* fallback for Node/dev/tests.
|
|
14
11
|
* - the enforcement surface — the {@link rateLimit} Hono middleware and the
|
|
15
12
|
* imperative {@link enforceRateLimit} helper that route packages
|
|
16
13
|
* (storefront, bookings, …) call directly inside handlers.
|
|
@@ -73,9 +70,8 @@ export interface RateLimitPolicy {
|
|
|
73
70
|
export interface RateLimitConfig {
|
|
74
71
|
/**
|
|
75
72
|
* Explicit store, or a function-of-bindings for stores built from env
|
|
76
|
-
* bindings
|
|
77
|
-
*
|
|
78
|
-
* to `c.env.RATE_LIMITER` (CF binding) → `c.env.RATE_LIMIT` (KV) →
|
|
73
|
+
* bindings. When omitted — or when the function returns `undefined` —
|
|
74
|
+
* resolution falls through to the injected `RATE_LIMIT_STORE`, then
|
|
79
75
|
* in-memory.
|
|
80
76
|
*/
|
|
81
77
|
store?: RateLimitStore | ((env: unknown) => RateLimitStore | undefined);
|
|
@@ -92,21 +88,6 @@ export interface RateLimitRule {
|
|
|
92
88
|
max: number;
|
|
93
89
|
windowSeconds: number;
|
|
94
90
|
}
|
|
95
|
-
/**
|
|
96
|
-
* The Cloudflare native Rate Limiting binding shape (wrangler
|
|
97
|
-
* `[[ratelimits]]`). The binding's `limit`/`period` are fixed in wrangler
|
|
98
|
-
* config — the policy's `max`/`windowSeconds` are advisory for headers
|
|
99
|
-
* only. One shared binding works across policies because the bucket is
|
|
100
|
-
* part of the key; deployments wanting different limits per policy bind
|
|
101
|
-
* one ratelimiter per policy and pass it via `policy.store`.
|
|
102
|
-
*/
|
|
103
|
-
export interface CloudflareRateLimiterBinding {
|
|
104
|
-
limit(opts: {
|
|
105
|
-
key: string;
|
|
106
|
-
}): Promise<{
|
|
107
|
-
success: boolean;
|
|
108
|
-
}>;
|
|
109
|
-
}
|
|
110
91
|
/**
|
|
111
92
|
* Minimal structural view of a Hono context — enough for key derivation,
|
|
112
93
|
* store resolution, and response headers — so route packages can call
|
|
@@ -142,30 +123,6 @@ export declare function clientIpKey(c: {
|
|
|
142
123
|
export declare function createMemoryRateLimitStore(options?: {
|
|
143
124
|
maxEntries?: number;
|
|
144
125
|
}): RateLimitStore;
|
|
145
|
-
/**
|
|
146
|
-
* KV-backed fixed-window store.
|
|
147
|
-
*
|
|
148
|
-
* **Best-effort by construction**: KV is eventually consistent and the
|
|
149
|
-
* counter is a non-atomic read-modify-write, so concurrent requests
|
|
150
|
-
* across PoPs undercount — a determined attacker gets somewhat more than
|
|
151
|
-
* `max` through before the window converges. With per-client keys this
|
|
152
|
-
* is still a real brake on brute force and write floods; deployments
|
|
153
|
-
* needing exact distributed limits should bind the Cloudflare Rate
|
|
154
|
-
* Limiting binding (`RATE_LIMITER`) instead.
|
|
155
|
-
*
|
|
156
|
-
* Stored keys are `lim:<bucket>:<clientKey>:<windowKey>` (the window
|
|
157
|
-
* suffix is appended here) with a TTL of `max(60, windowSeconds * 2)` —
|
|
158
|
-
* KV's TTL floor is 60s.
|
|
159
|
-
*/
|
|
160
|
-
export declare function createKvRateLimitStore(kv: KVStore): RateLimitStore;
|
|
161
|
-
/**
|
|
162
|
-
* Adapter for the Cloudflare native Rate Limiting binding. Truly
|
|
163
|
-
* distributed and atomic; the actual limit/period are fixed in wrangler
|
|
164
|
-
* config, so the policy's `max`/`windowSeconds` only inform the
|
|
165
|
-
* `Retry-After` hint. `remaining` is never reported (the binding does
|
|
166
|
-
* not expose it).
|
|
167
|
-
*/
|
|
168
|
-
export declare function createCloudflareRateLimitStore(binding: CloudflareRateLimiterBinding): RateLimitStore;
|
|
169
126
|
export interface RedisRateLimitStoreOptions {
|
|
170
127
|
client?: LazyRedisClient;
|
|
171
128
|
}
|
|
@@ -174,8 +131,8 @@ export declare function createRedisRateLimitStore(redisUrl: string, options?: Re
|
|
|
174
131
|
export declare function resetRateLimitWarningsForTests(): void;
|
|
175
132
|
/**
|
|
176
133
|
* Resolve the best available store from the environment:
|
|
177
|
-
* `c.env.
|
|
178
|
-
*
|
|
134
|
+
* Injected `c.env.RATE_LIMIT_STORE` → in-memory fallback. **Fails open into
|
|
135
|
+
* the memory store** —
|
|
179
136
|
* rate limiting must never break Node/headless deployments that bind
|
|
180
137
|
* neither — but warns once per isolate outside dev/test so a
|
|
181
138
|
* production deploy without a distributed backend is visible in logs.
|
|
@@ -67,58 +67,6 @@ export function createMemoryRateLimitStore(options) {
|
|
|
67
67
|
},
|
|
68
68
|
};
|
|
69
69
|
}
|
|
70
|
-
/**
|
|
71
|
-
* KV-backed fixed-window store.
|
|
72
|
-
*
|
|
73
|
-
* **Best-effort by construction**: KV is eventually consistent and the
|
|
74
|
-
* counter is a non-atomic read-modify-write, so concurrent requests
|
|
75
|
-
* across PoPs undercount — a determined attacker gets somewhat more than
|
|
76
|
-
* `max` through before the window converges. With per-client keys this
|
|
77
|
-
* is still a real brake on brute force and write floods; deployments
|
|
78
|
-
* needing exact distributed limits should bind the Cloudflare Rate
|
|
79
|
-
* Limiting binding (`RATE_LIMITER`) instead.
|
|
80
|
-
*
|
|
81
|
-
* Stored keys are `lim:<bucket>:<clientKey>:<windowKey>` (the window
|
|
82
|
-
* suffix is appended here) with a TTL of `max(60, windowSeconds * 2)` —
|
|
83
|
-
* KV's TTL floor is 60s.
|
|
84
|
-
*/
|
|
85
|
-
export function createKvRateLimitStore(kv) {
|
|
86
|
-
return {
|
|
87
|
-
async limit(key, { max, windowSeconds }) {
|
|
88
|
-
const nowSeconds = Math.floor(Date.now() / 1000);
|
|
89
|
-
const windowKey = Math.floor(nowSeconds / windowSeconds);
|
|
90
|
-
const storageKey = `${key}:${windowKey}`;
|
|
91
|
-
const raw = await kv.get(storageKey);
|
|
92
|
-
const current = raw ? Number(raw) || 0 : 0;
|
|
93
|
-
const next = current + 1;
|
|
94
|
-
await kv.put(storageKey, String(next), {
|
|
95
|
-
expirationTtl: Math.max(60, windowSeconds * 2),
|
|
96
|
-
});
|
|
97
|
-
return {
|
|
98
|
-
allowed: next <= max,
|
|
99
|
-
remaining: Math.max(0, max - next),
|
|
100
|
-
retryAfterSeconds: Math.max(1, windowSeconds - (nowSeconds % windowSeconds)),
|
|
101
|
-
};
|
|
102
|
-
},
|
|
103
|
-
};
|
|
104
|
-
}
|
|
105
|
-
/**
|
|
106
|
-
* Adapter for the Cloudflare native Rate Limiting binding. Truly
|
|
107
|
-
* distributed and atomic; the actual limit/period are fixed in wrangler
|
|
108
|
-
* config, so the policy's `max`/`windowSeconds` only inform the
|
|
109
|
-
* `Retry-After` hint. `remaining` is never reported (the binding does
|
|
110
|
-
* not expose it).
|
|
111
|
-
*/
|
|
112
|
-
export function createCloudflareRateLimitStore(binding) {
|
|
113
|
-
return {
|
|
114
|
-
async limit(key, { windowSeconds }) {
|
|
115
|
-
const { success } = await binding.limit({ key });
|
|
116
|
-
return success
|
|
117
|
-
? { allowed: true }
|
|
118
|
-
: { allowed: false, retryAfterSeconds: Math.max(1, windowSeconds) };
|
|
119
|
-
},
|
|
120
|
-
};
|
|
121
|
-
}
|
|
122
70
|
export function createRedisRateLimitStore(redisUrl, options = {}) {
|
|
123
71
|
const lazyClient = options.client ?? createLazyRedisClient(redisUrl);
|
|
124
72
|
return {
|
|
@@ -140,9 +88,7 @@ export function createRedisRateLimitStore(redisUrl, options = {}) {
|
|
|
140
88
|
};
|
|
141
89
|
}
|
|
142
90
|
// ---- Store resolution ----
|
|
143
|
-
const bindingStoreCache = new WeakMap();
|
|
144
91
|
const explicitStoreCache = new WeakMap();
|
|
145
|
-
const kvStoreCache = new WeakMap();
|
|
146
92
|
const sharedMemoryStore = createMemoryRateLimitStore();
|
|
147
93
|
let warnedNoDistributedStore = false;
|
|
148
94
|
function isDevLikeEnv() {
|
|
@@ -156,23 +102,14 @@ export function resetRateLimitWarningsForTests() {
|
|
|
156
102
|
}
|
|
157
103
|
/**
|
|
158
104
|
* Resolve the best available store from the environment:
|
|
159
|
-
* `c.env.
|
|
160
|
-
*
|
|
105
|
+
* Injected `c.env.RATE_LIMIT_STORE` → in-memory fallback. **Fails open into
|
|
106
|
+
* the memory store** —
|
|
161
107
|
* rate limiting must never break Node/headless deployments that bind
|
|
162
108
|
* neither — but warns once per isolate outside dev/test so a
|
|
163
109
|
* production deploy without a distributed backend is visible in logs.
|
|
164
110
|
*/
|
|
165
111
|
export function resolveRateLimitStore(c, memoryFallback = sharedMemoryStore) {
|
|
166
112
|
const env = (c.env ?? {});
|
|
167
|
-
const binding = env.RATE_LIMITER;
|
|
168
|
-
if (binding && typeof binding.limit === "function") {
|
|
169
|
-
let store = bindingStoreCache.get(binding);
|
|
170
|
-
if (!store) {
|
|
171
|
-
store = createCloudflareRateLimitStore(binding);
|
|
172
|
-
bindingStoreCache.set(binding, store);
|
|
173
|
-
}
|
|
174
|
-
return store;
|
|
175
|
-
}
|
|
176
113
|
const explicit = env.RATE_LIMIT_STORE;
|
|
177
114
|
if (explicit && typeof explicit.limit === "function") {
|
|
178
115
|
let store = explicitStoreCache.get(explicit);
|
|
@@ -182,19 +119,10 @@ export function resolveRateLimitStore(c, memoryFallback = sharedMemoryStore) {
|
|
|
182
119
|
}
|
|
183
120
|
return store;
|
|
184
121
|
}
|
|
185
|
-
const kv = env.RATE_LIMIT;
|
|
186
|
-
if (kv && typeof kv.get === "function" && typeof kv.put === "function") {
|
|
187
|
-
let store = kvStoreCache.get(kv);
|
|
188
|
-
if (!store) {
|
|
189
|
-
store = createKvRateLimitStore(kv);
|
|
190
|
-
kvStoreCache.set(kv, store);
|
|
191
|
-
}
|
|
192
|
-
return store;
|
|
193
|
-
}
|
|
194
122
|
if (!warnedNoDistributedStore && !isDevLikeEnv()) {
|
|
195
123
|
warnedNoDistributedStore = true;
|
|
196
124
|
console.warn("[voyant] rate-limit: no distributed store available (inject RATE_LIMIT_STORE " +
|
|
197
|
-
"
|
|
125
|
+
"). " +
|
|
198
126
|
"Falling back to a per-isolate in-memory limiter — limits apply " +
|
|
199
127
|
"per instance, not fleet-wide.");
|
|
200
128
|
}
|
package/dist/module.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { Extension, Module } from "@voyant-travel/core";
|
|
2
2
|
import type { Hono } from "hono";
|
|
3
|
-
import type {
|
|
4
|
-
export interface
|
|
3
|
+
import type { LazyApiRoutes, LazyRoutesLoader } from "./lazy-routes.js";
|
|
4
|
+
export interface ApiModule {
|
|
5
5
|
module: Module;
|
|
6
6
|
/** Staff-facing routes — mounted at `/v1/admin/{module.name}`. */
|
|
7
7
|
adminRoutes?: Hono<any>;
|
|
@@ -35,7 +35,7 @@ export interface HonoModule {
|
|
|
35
35
|
* returns ABSOLUTE routes; the framework mounts + caches them with the request
|
|
36
36
|
* context bridged in. Context-preserving replacement for `mountLazyRouteApp`.
|
|
37
37
|
*/
|
|
38
|
-
lazyRoutes?:
|
|
38
|
+
lazyRoutes?: LazyApiRoutes;
|
|
39
39
|
/**
|
|
40
40
|
* Optional override for the public mount path relative to `/v1/public`.
|
|
41
41
|
*
|
|
@@ -69,7 +69,7 @@ export interface HonoModule {
|
|
|
69
69
|
*/
|
|
70
70
|
transactionalPaths?: readonly string[];
|
|
71
71
|
}
|
|
72
|
-
export interface
|
|
72
|
+
export interface ApiExtension {
|
|
73
73
|
extension: Extension;
|
|
74
74
|
/** Staff-facing routes — mounted at `/v1/admin/{extension.module}`. */
|
|
75
75
|
adminRoutes?: Hono<any>;
|
|
@@ -77,15 +77,15 @@ export interface HonoExtension {
|
|
|
77
77
|
publicRoutes?: Hono<any>;
|
|
78
78
|
/**
|
|
79
79
|
* Inbound webhook routes — mounted at `/v1/{extension.module}`, concrete paths
|
|
80
|
-
* auto-added to the anonymous allow-list (ADR-0008). See `
|
|
80
|
+
* auto-added to the anonymous allow-list (ADR-0008). See `ApiModule.webhookRoutes`.
|
|
81
81
|
*/
|
|
82
82
|
webhookRoutes?: Hono<any>;
|
|
83
|
-
/** Lazy variant of `adminRoutes` — mounted at `/v1/admin/{extension.module}` (see
|
|
83
|
+
/** Lazy variant of `adminRoutes` — mounted at `/v1/admin/{extension.module}` (see ApiModule). */
|
|
84
84
|
lazyAdminRoutes?: LazyRoutesLoader;
|
|
85
85
|
/** Lazy variant of `publicRoutes` — mounted at `/v1/public/{publicPath ?? extension.module}`. */
|
|
86
86
|
lazyPublicRoutes?: LazyRoutesLoader;
|
|
87
|
-
/** Deployment-local lazy family at explicit absolute paths (see
|
|
88
|
-
lazyRoutes?:
|
|
87
|
+
/** Deployment-local lazy family at explicit absolute paths (see ApiModule). */
|
|
88
|
+
lazyRoutes?: LazyApiRoutes;
|
|
89
89
|
/**
|
|
90
90
|
* Optional override for the public mount path relative to `/v1/public`.
|
|
91
91
|
*
|
|
@@ -95,13 +95,13 @@ export interface HonoExtension {
|
|
|
95
95
|
publicPath?: string;
|
|
96
96
|
/**
|
|
97
97
|
* Declares which of this extension's PUBLIC routes are reachable without a
|
|
98
|
-
* session (ADR-0008). Same semantics as {@link
|
|
98
|
+
* session (ADR-0008). Same semantics as {@link ApiModule.anonymous}, relative
|
|
99
99
|
* to the extension's public mount.
|
|
100
100
|
*/
|
|
101
101
|
anonymous?: boolean | readonly string[];
|
|
102
102
|
/**
|
|
103
103
|
* Absolute transactional path prefixes — same semantics as
|
|
104
|
-
* {@link
|
|
104
|
+
* {@link ApiModule.transactionalPaths}.
|
|
105
105
|
*/
|
|
106
106
|
transactionalPaths?: readonly string[];
|
|
107
107
|
}
|
package/dist/types.d.ts
CHANGED
|
@@ -7,8 +7,8 @@ import type { NeonHttpDatabase } from "drizzle-orm/neon-http";
|
|
|
7
7
|
import type { NeonDatabase as NeonWsDatabase } from "drizzle-orm/neon-serverless";
|
|
8
8
|
import type { PostgresJsDatabase } from "drizzle-orm/postgres-js";
|
|
9
9
|
import type { Handler, Hono } from "hono";
|
|
10
|
-
import type {
|
|
11
|
-
import type {
|
|
10
|
+
import type { ApiBundleInput } from "./bundle.js";
|
|
11
|
+
import type { ApiExtension, ApiModule } from "./module.js";
|
|
12
12
|
import type { Reporter } from "./observability/reporter.js";
|
|
13
13
|
export interface VoyantExecutionContext {
|
|
14
14
|
waitUntil?: (promise: Promise<unknown>) => void;
|
|
@@ -24,17 +24,9 @@ export interface VoyantBindings {
|
|
|
24
24
|
APP_URL?: string;
|
|
25
25
|
DASH_BASE_URL?: string;
|
|
26
26
|
API_BASE_URL?: string;
|
|
27
|
-
RATE_LIMIT?: KVStore;
|
|
28
27
|
RATE_LIMIT_STORE?: import("./middleware/rate-limit.js").RateLimitStore;
|
|
29
28
|
CACHE?: KVStore;
|
|
30
29
|
SHARED_STATE?: KVStore;
|
|
31
|
-
RATE_LIMITER?: import("./middleware/rate-limit.js").CloudflareRateLimiterBinding;
|
|
32
|
-
/**
|
|
33
|
-
* Workers Analytics Engine dataset receiving per-request metrics
|
|
34
|
-
* (see the `metrics` middleware). Optional — without it the
|
|
35
|
-
* middleware is a no-op.
|
|
36
|
-
*/
|
|
37
|
-
METRICS?: import("./middleware/metrics.js").AnalyticsEngineDatasetLike;
|
|
38
30
|
}
|
|
39
31
|
export type VoyantDb = PostgresJsDatabase | NeonHttpDatabase | NeonWsDatabase;
|
|
40
32
|
export type VoyantQueryRuntime = QueryRunner;
|
|
@@ -55,8 +47,6 @@ export type VoyantVariables = CoreVoyantVariables & {
|
|
|
55
47
|
query?: VoyantQueryRuntime;
|
|
56
48
|
/** Optional workflow driver surfaced to HTTP routes after lazy app bootstrap. */
|
|
57
49
|
workflowDriver?: import("@voyant-travel/workflows/driver").WorkflowDriver;
|
|
58
|
-
/** Per-request db metrics counter populated by the metrics middleware. */
|
|
59
|
-
__voyantDbMetrics?: import("./middleware/metrics.js").RequestDbMetrics;
|
|
60
50
|
};
|
|
61
51
|
/** Handler contract for application-authored Hono API routes. */
|
|
62
52
|
export type VoyantRouteHandler<TBindings extends VoyantBindings = VoyantBindings> = Handler<{
|
|
@@ -193,9 +183,9 @@ export interface VoyantAppConfig<TBindings extends VoyantBindings = VoyantBindin
|
|
|
193
183
|
* together with `dbTransactional`.
|
|
194
184
|
*/
|
|
195
185
|
dbTransactionalPaths?: string[];
|
|
196
|
-
modules?:
|
|
197
|
-
extensions?:
|
|
198
|
-
plugins?:
|
|
186
|
+
modules?: ApiModule[];
|
|
187
|
+
extensions?: ApiExtension[];
|
|
188
|
+
plugins?: ApiBundleInput[];
|
|
199
189
|
eventBus?: EventBus;
|
|
200
190
|
/**
|
|
201
191
|
* Link definitions activated against each request's resolved database.
|
|
@@ -260,13 +250,6 @@ export interface VoyantAppConfig<TBindings extends VoyantBindings = VoyantBindin
|
|
|
260
250
|
* `insertOutboxEvents(tx, ...)`. Default off.
|
|
261
251
|
*/
|
|
262
252
|
outbox?: boolean;
|
|
263
|
-
/**
|
|
264
|
-
* Per-request metrics to the `env.METRICS` Analytics Engine dataset
|
|
265
|
-
* (method, route pattern, surface, cache status, duration, status,
|
|
266
|
-
* db query count). Enabled by default and inert without the binding;
|
|
267
|
-
* set `false` to disable entirely.
|
|
268
|
-
*/
|
|
269
|
-
metrics?: boolean;
|
|
270
253
|
/**
|
|
271
254
|
* Default request body limit enforced before route handlers parse
|
|
272
255
|
* JSON/form data. Content-type-aware: JSON bodies are capped at 10 MiB
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@voyant-travel/hono",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.128.1",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -130,18 +130,17 @@
|
|
|
130
130
|
"drizzle-orm": "^0.45.2",
|
|
131
131
|
"hono": "^4.12.27",
|
|
132
132
|
"zod": "^4.4.3",
|
|
133
|
-
"@voyant-travel/core": "^0.
|
|
134
|
-
"@voyant-travel/db": "^0.114.
|
|
133
|
+
"@voyant-travel/core": "^0.125.0",
|
|
134
|
+
"@voyant-travel/db": "^0.114.9",
|
|
135
135
|
"@voyant-travel/types": "^0.109.2",
|
|
136
136
|
"@voyant-travel/utils": "^0.107.1",
|
|
137
|
-
"@voyant-travel/workflows": "^0.
|
|
137
|
+
"@voyant-travel/workflows": "^0.122.2"
|
|
138
138
|
},
|
|
139
139
|
"devDependencies": {
|
|
140
|
-
"@cloudflare/workers-types": "^4.20260702.1",
|
|
141
140
|
"typescript": "^6.0.3",
|
|
142
141
|
"vitest": "^4.1.9",
|
|
143
142
|
"@voyant-travel/voyant-typescript-config": "^0.1.0",
|
|
144
|
-
"@voyant-travel/workflows-orchestrator": "^0.
|
|
143
|
+
"@voyant-travel/workflows-orchestrator": "^0.122.2"
|
|
145
144
|
},
|
|
146
145
|
"files": [
|
|
147
146
|
"dist"
|