@voyant-travel/hono 0.118.1 → 0.118.3
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.map +1 -1
- package/dist/app.js +5 -7
- package/dist/lib/request-outbox-store.d.ts +17 -0
- package/dist/lib/request-outbox-store.d.ts.map +1 -0
- package/dist/lib/request-outbox-store.js +39 -0
- package/dist/middleware/require-actor.d.ts.map +1 -1
- package/dist/middleware/require-actor.js +24 -3
- package/package.json +5 -5
package/dist/app.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"app.d.ts","sourceRoot":"","sources":["../src/app.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"app.d.ts","sourceRoot":"","sources":["../src/app.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,MAAM,CAAA;AAQhC,OAAO,EAAE,KAAK,gBAAgB,EAA0C,MAAM,kBAAkB,CAAA;AA+BhG,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAY,eAAe,EAAE,MAAM,YAAY,CAAA;AAI5F,4EAA4E;AAC5E,KAAK,QAAQ,CAAC,SAAS,SAAS,cAAc,IAAI;IAChD,QAAQ,EAAE,SAAS,CAAA;IACnB,SAAS,EAAE,eAAe,CAAA;CAC3B,CAAA;AAuBD;;;;;GAKG;AACH,MAAM,WAAW,SAAS;IACxB,yEAAyE;IACzE,MAAM,EAAE,MAAM,CAAA;IACd,IAAI,EAAE,gBAAgB,CAAA;CACvB;AAED;;;;;;;;;;GAUG;AACH,MAAM,WAAW,mBAAmB,CAAC,SAAS,GAAG,OAAO;IACtD;;;;;;;;;;;OAWG;IACH,KAAK,CAAC,QAAQ,CAAC,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAC1C;;;;;;;;OAQG;IACH,QAAQ,EAAE,OAAO,qBAAqB,EAAE,QAAQ,CAAA;IAChD;;;;;OAKG;IACH,UAAU,EAAE,SAAS,EAAE,CAAA;CACxB;AAED;;;;;;GAMG;AACH,wBAAgB,QAAQ,CAAC,SAAS,SAAS,cAAc,EACvD,MAAM,EAAE,eAAe,CAAC,SAAS,CAAC,GACjC,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,GAAG,mBAAmB,CAAC,SAAS,CAAC,CAue5D"}
|
package/dist/app.js
CHANGED
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
// splitting it is intentional follow-up work, not part of voyant#2114.
|
|
3
3
|
import { OpenAPIHono } from "@hono/zod-openapi";
|
|
4
4
|
import { createContainer, createEventBus, createQueryRunner, } from "@voyant-travel/core";
|
|
5
|
-
import { createOutboxEventStore } from "@voyant-travel/db/outbox";
|
|
6
5
|
import { assembleAnonymousPaths } from "./anonymous-paths.js";
|
|
7
6
|
import { containerToServiceResolver, makeFrameworkLogger, wireWorkflowRuntime, } from "./app-workflows.js";
|
|
8
7
|
import { mountLazyRoutePaths, mountLazyRoutesAt } from "./lazy-routes.js";
|
|
@@ -11,6 +10,7 @@ import { createPathDbSelector } from "./lib/db-selector.js";
|
|
|
11
10
|
import { tryGetExecutionCtx } from "./lib/execution-ctx.js";
|
|
12
11
|
import { matchesPublicPath, normalizePathname } from "./lib/public-paths.js";
|
|
13
12
|
import { requestScopedEventBus } from "./lib/request-event-bus.js";
|
|
13
|
+
import { createRequestOutboxStore } from "./lib/request-outbox-store.js";
|
|
14
14
|
import { requireAuth } from "./middleware/auth.js";
|
|
15
15
|
import { DEFAULT_REQUEST_BODY_LIMIT_BYTES, MAX_GLOBAL_REQUEST_BODY_BYTES, requestBodyLimit, } from "./middleware/body-size.js";
|
|
16
16
|
import { cors } from "./middleware/cors.js";
|
|
@@ -272,12 +272,10 @@ export function mountApp(config) {
|
|
|
272
272
|
// lazily at capture time. Outbox writes are single statements — the
|
|
273
273
|
// cheap http client on non-transactional surfaces handles them fine.
|
|
274
274
|
const buildOutboxStore = config.outbox
|
|
275
|
-
? (c) =>
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
}
|
|
280
|
-
return requestDb;
|
|
275
|
+
? (c) => createRequestOutboxStore({
|
|
276
|
+
env: c.env,
|
|
277
|
+
operationDbFactory: config.db,
|
|
278
|
+
requestDb: () => c.get("db"),
|
|
281
279
|
})
|
|
282
280
|
: undefined;
|
|
283
281
|
app.use("*", async (c, next) => {
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { OutboxEventStore } from "@voyant-travel/core";
|
|
2
|
+
import { type DbFactory, type VoyantBindings, type VoyantDb } from "../types.js";
|
|
3
|
+
export interface RequestOutboxStoreOptions<TBindings extends VoyantBindings> {
|
|
4
|
+
env: TBindings;
|
|
5
|
+
requestDb: () => VoyantDb | undefined;
|
|
6
|
+
operationDbFactory: DbFactory<TBindings>;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Outbox store for request-scoped emits.
|
|
10
|
+
*
|
|
11
|
+
* Capture uses the current request DB so the outbox row is written before
|
|
12
|
+
* subscriber delivery starts. Settlement uses a fresh default DB client because
|
|
13
|
+
* scheduled subscribers may finish after the request-owned client has been
|
|
14
|
+
* disposed by middleware cleanup.
|
|
15
|
+
*/
|
|
16
|
+
export declare function createRequestOutboxStore<TBindings extends VoyantBindings>(options: RequestOutboxStoreOptions<TBindings>): OutboxEventStore;
|
|
17
|
+
//# sourceMappingURL=request-outbox-store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"request-outbox-store.d.ts","sourceRoot":"","sources":["../../src/lib/request-outbox-store.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAiB,gBAAgB,EAAE,MAAM,qBAAqB,CAAA;AAG1E,OAAO,EACL,KAAK,SAAS,EAEd,KAAK,cAAc,EACnB,KAAK,QAAQ,EACd,MAAM,aAAa,CAAA;AAEpB,MAAM,WAAW,yBAAyB,CAAC,SAAS,SAAS,cAAc;IACzE,GAAG,EAAE,SAAS,CAAA;IACd,SAAS,EAAE,MAAM,QAAQ,GAAG,SAAS,CAAA;IACrC,kBAAkB,EAAE,SAAS,CAAC,SAAS,CAAC,CAAA;CACzC;AAED;;;;;;;GAOG;AACH,wBAAgB,wBAAwB,CAAC,SAAS,SAAS,cAAc,EACvE,OAAO,EAAE,yBAAyB,CAAC,SAAS,CAAC,GAC5C,gBAAgB,CA6BlB"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { completeOutboxEvent, failOutboxEvent, insertOutboxEvents } from "@voyant-travel/db/outbox";
|
|
2
|
+
import { resolveDbFactoryResult, } from "../types.js";
|
|
3
|
+
/**
|
|
4
|
+
* Outbox store for request-scoped emits.
|
|
5
|
+
*
|
|
6
|
+
* Capture uses the current request DB so the outbox row is written before
|
|
7
|
+
* subscriber delivery starts. Settlement uses a fresh default DB client because
|
|
8
|
+
* scheduled subscribers may finish after the request-owned client has been
|
|
9
|
+
* disposed by middleware cleanup.
|
|
10
|
+
*/
|
|
11
|
+
export function createRequestOutboxStore(options) {
|
|
12
|
+
async function withOperationDb(fn) {
|
|
13
|
+
const { db, dispose } = resolveDbFactoryResult(options.operationDbFactory(options.env));
|
|
14
|
+
try {
|
|
15
|
+
return await fn(db);
|
|
16
|
+
}
|
|
17
|
+
finally {
|
|
18
|
+
if (dispose)
|
|
19
|
+
await dispose();
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return {
|
|
23
|
+
async insert(envelope) {
|
|
24
|
+
const requestDb = options.requestDb();
|
|
25
|
+
if (!requestDb) {
|
|
26
|
+
throw new Error("[voyant] outbox capture needs the per-request db — emit ran before the db middleware");
|
|
27
|
+
}
|
|
28
|
+
const rows = await insertOutboxEvents(requestDb, [envelope]);
|
|
29
|
+
const row = rows[0];
|
|
30
|
+
return row ? { id: row.id } : null;
|
|
31
|
+
},
|
|
32
|
+
async complete(id) {
|
|
33
|
+
await withOperationDb((db) => completeOutboxEvent(db, id));
|
|
34
|
+
},
|
|
35
|
+
async fail(id, error) {
|
|
36
|
+
await withOperationDb((db) => failOutboxEvent(db, id, error));
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"require-actor.d.ts","sourceRoot":"","sources":["../../src/middleware/require-actor.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAA;AAEhD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,MAAM,CAAA;AAG7C,OAAO,KAAK,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;
|
|
1
|
+
{"version":3,"file":"require-actor.d.ts","sourceRoot":"","sources":["../../src/middleware/require-actor.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAA;AAEhD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,MAAM,CAAA;AAG7C,OAAO,KAAK,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;AA8DlE;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAIzD;AAED,MAAM,WAAW,mBAAmB;IAClC;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB;AAMD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,YAAY,CAAC,SAAS,SAAS,cAAc,GAAG,cAAc,EAC5E,GAAG,OAAO,EAAE,KAAK,EAAE,GAClB,iBAAiB,CAAC;IACnB,QAAQ,EAAE,SAAS,CAAA;IACnB,SAAS,EAAE,eAAe,CAAA;CAC3B,CAAC,CAAA;AACF,wBAAgB,YAAY,CAAC,SAAS,SAAS,cAAc,GAAG,cAAc,EAC5E,OAAO,EAAE,mBAAmB,EAC5B,GAAG,OAAO,EAAE,KAAK,EAAE,GAClB,iBAAiB,CAAC;IACnB,QAAQ,EAAE,SAAS,CAAA;IACnB,SAAS,EAAE,eAAe,CAAA;CAC3B,CAAC,CAAA"}
|
|
@@ -10,7 +10,7 @@ function apiKeyResourceFromPath(pathname) {
|
|
|
10
10
|
}
|
|
11
11
|
return null;
|
|
12
12
|
}
|
|
13
|
-
function
|
|
13
|
+
function baseApiKeyPermissionActionsForMethod(method) {
|
|
14
14
|
switch (method.toUpperCase()) {
|
|
15
15
|
case "GET":
|
|
16
16
|
case "HEAD":
|
|
@@ -26,6 +26,27 @@ function apiKeyPermissionActionsForMethod(method) {
|
|
|
26
26
|
return [];
|
|
27
27
|
}
|
|
28
28
|
}
|
|
29
|
+
/**
|
|
30
|
+
* Search endpoints accept complex request bodies, so they're exposed as `POST`
|
|
31
|
+
* even though they're read-family operations. Without this, `POST
|
|
32
|
+
* /v1/<surface>/catalog/search` would only accept `write`/`trigger`/`relay`,
|
|
33
|
+
* and a `catalog:search`/`catalog:read`-scoped token would be rejected
|
|
34
|
+
* (voyant#2649). Matching a `search` / `*-search` path segment (e.g.
|
|
35
|
+
* `/catalog/search`, `/catalog/package-search`) keeps every other POST route
|
|
36
|
+
* (product writes, bookings, pricing, …) gated on its normal write actions.
|
|
37
|
+
*/
|
|
38
|
+
function isSearchPath(pathname) {
|
|
39
|
+
// A segment that is `search` or ends in `-search` (e.g. `package-search`),
|
|
40
|
+
// bounded so `/searchable` or `/researcher` don't count.
|
|
41
|
+
return /(?:\/|-)search(?:\/|$)/.test(pathname);
|
|
42
|
+
}
|
|
43
|
+
function apiKeyPermissionActionsForMethod(method, pathname) {
|
|
44
|
+
const actions = baseApiKeyPermissionActionsForMethod(method);
|
|
45
|
+
if (isSearchPath(pathname)) {
|
|
46
|
+
return Array.from(new Set([...actions, "search", "read"]));
|
|
47
|
+
}
|
|
48
|
+
return actions;
|
|
49
|
+
}
|
|
29
50
|
function hasAnyApiKeyPermission(scopes, resource, actions) {
|
|
30
51
|
if (!scopes || scopes.length === 0)
|
|
31
52
|
return false;
|
|
@@ -69,7 +90,7 @@ export function requireActor(...args) {
|
|
|
69
90
|
// is a reserved namespace (no module is named `_meta`).
|
|
70
91
|
if (resource === "_meta")
|
|
71
92
|
return next();
|
|
72
|
-
const actions = apiKeyPermissionActionsForMethod(c.req.method);
|
|
93
|
+
const actions = apiKeyPermissionActionsForMethod(c.req.method, pathname);
|
|
73
94
|
if (resource && hasAnyApiKeyPermission(c.get("scopes"), resource, actions)) {
|
|
74
95
|
return next();
|
|
75
96
|
}
|
|
@@ -99,7 +120,7 @@ export function requireActor(...args) {
|
|
|
99
120
|
});
|
|
100
121
|
const resource = apiKeyResourceFromPath(pathname);
|
|
101
122
|
if (resource && resource !== "_meta") {
|
|
102
|
-
const actions = apiKeyPermissionActionsForMethod(c.req.method);
|
|
123
|
+
const actions = apiKeyPermissionActionsForMethod(c.req.method, pathname);
|
|
103
124
|
if (!hasAnyApiKeyPermission(scopes, resource, actions)) {
|
|
104
125
|
return c.json({ error: "Forbidden: missing required permission" }, 403);
|
|
105
126
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@voyant-travel/hono",
|
|
3
|
-
"version": "0.118.
|
|
3
|
+
"version": "0.118.3",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -138,16 +138,16 @@
|
|
|
138
138
|
"@voyant-travel/core": "^0.111.0",
|
|
139
139
|
"@voyant-travel/db": "^0.109.4",
|
|
140
140
|
"@voyant-travel/storage": "^0.106.0",
|
|
141
|
-
"@voyant-travel/types": "^0.106.
|
|
142
|
-
"@voyant-travel/utils": "^0.105.
|
|
143
|
-
"@voyant-travel/workflows": "^0.111.
|
|
141
|
+
"@voyant-travel/types": "^0.106.1",
|
|
142
|
+
"@voyant-travel/utils": "^0.105.5",
|
|
143
|
+
"@voyant-travel/workflows": "^0.111.12"
|
|
144
144
|
},
|
|
145
145
|
"devDependencies": {
|
|
146
146
|
"@cloudflare/workers-types": "^4.20260628.1",
|
|
147
147
|
"typescript": "^6.0.3",
|
|
148
148
|
"vitest": "^4.1.9",
|
|
149
149
|
"@voyant-travel/voyant-typescript-config": "^0.1.0",
|
|
150
|
-
"@voyant-travel/workflows-orchestrator": "^0.111.
|
|
150
|
+
"@voyant-travel/workflows-orchestrator": "^0.111.12"
|
|
151
151
|
},
|
|
152
152
|
"files": [
|
|
153
153
|
"dist"
|