@voyant-travel/hono 0.119.0 → 0.120.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/openapi.d.ts +44 -14
- package/dist/openapi.d.ts.map +1 -1
- package/dist/openapi.js +169 -24
- package/package.json +5 -5
package/dist/openapi.d.ts
CHANGED
|
@@ -124,30 +124,60 @@ export declare function mergeLazyOpenApiPaths(base: OpenApiDocument, mounts: rea
|
|
|
124
124
|
* (`@asteasolutions/zod-to-openapi` must stay out of the Worker bundle).
|
|
125
125
|
*/
|
|
126
126
|
export declare function generateModuleOpenApiDocuments(mounts: readonly ModuleMount[], options: GenerateOpenApiOptions): Promise<Map<string, OpenApiDocument>>;
|
|
127
|
+
/**
|
|
128
|
+
* Build the authoritative path → module ownership map from the mount manifest.
|
|
129
|
+
*
|
|
130
|
+
* Generates each module's isolated doc (see `generateModuleOpenApiDocuments`)
|
|
131
|
+
* only to learn which real absolute paths it owns — so `publicPath` overrides
|
|
132
|
+
* (whose prefix isn't the module name, e.g. `/v1/public/booking-engine`) map to
|
|
133
|
+
* the right module. Routes the manifest doesn't record (`additionalRoutes`,
|
|
134
|
+
* directly-mounted routes) are absent here and fall back to their path segment.
|
|
135
|
+
*
|
|
136
|
+
* Build-time only.
|
|
137
|
+
*/
|
|
138
|
+
export declare function buildModulePathOwnership(mounts: readonly ModuleMount[], options: GenerateOpenApiOptions): Promise<Map<string, string>>;
|
|
127
139
|
/**
|
|
128
140
|
* Partition a composed document into one document per module, covering EVERY
|
|
129
141
|
* admin/storefront path (voyant#2733).
|
|
130
142
|
*
|
|
131
|
-
*
|
|
132
|
-
*
|
|
133
|
-
*
|
|
134
|
-
*
|
|
135
|
-
*
|
|
136
|
-
*
|
|
137
|
-
* This uses the manifest as the *authoritative* owner for the routes it knows
|
|
138
|
-
* about — which is what makes `publicPath` overrides (whose prefix isn't the
|
|
139
|
-
* module name, e.g. `/v1/public/booking-engine`) land under the right module —
|
|
140
|
-
* and falls back to the path's own second segment for anything the manifest
|
|
141
|
-
* doesn't claim. The result therefore partitions the full surface exactly: every
|
|
142
|
-
* `/v1/admin/*` and `/v1/public/*` path lands in exactly one module document.
|
|
143
|
-
* Non-surface routes (`/v1/<name>` webhooks, legacy `/v1/*`) live only in the
|
|
144
|
-
* aggregate, as before.
|
|
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).
|
|
146
|
+
* The full surface is therefore partitioned exactly: every `/v1/admin/*` and
|
|
147
|
+
* `/v1/public/*` path lands in exactly one module document. Non-surface routes
|
|
148
|
+
* (`/v1/<name>` webhooks, legacy `/v1/*`) live only in the aggregate, as before.
|
|
145
149
|
*
|
|
146
150
|
* Each per-module document carries the aggregate's shared `components` verbatim
|
|
147
151
|
* (there is ~one), so it stays a valid, self-contained OpenAPI document.
|
|
152
|
+
*/
|
|
153
|
+
export declare function partitionByModule(full: OpenApiDocument, owner: ReadonlyMap<string, string>): Map<string, OpenApiDocument>;
|
|
154
|
+
/**
|
|
155
|
+
* Convenience: build the ownership map and partition in one call. Prefer the
|
|
156
|
+
* two-step `buildModulePathOwnership` + `partitionByModule` when you also want
|
|
157
|
+
* to `stampModuleMetadata` the aggregate from the same map (so it's built once).
|
|
148
158
|
*
|
|
149
159
|
* Build-time only.
|
|
150
160
|
*/
|
|
151
161
|
export declare function splitDocumentByModule(full: OpenApiDocument, mounts: readonly ModuleMount[], options: GenerateOpenApiOptions): Promise<Map<string, OpenApiDocument>>;
|
|
162
|
+
/**
|
|
163
|
+
* Stamp every operation with the metadata standard OpenAPI tooling expects
|
|
164
|
+
* (voyant#2733 / voyant#2729). All fields are non-destructive — a value a route
|
|
165
|
+
* already declares is never overwritten:
|
|
166
|
+
* - `operationId` — stable camelCase id from method + path, for readable
|
|
167
|
+
* generated client method names.
|
|
168
|
+
* - `summary` — the method + path signature, so viewers/linters have a title
|
|
169
|
+
* for every operation.
|
|
170
|
+
* - `tags: [module]` — so Swagger/Scalar group the sidebar by module (they
|
|
171
|
+
* key grouping off `tags` and ignore `x-*`).
|
|
172
|
+
* - `x-voyant-module` / `x-voyant-surface` — machine-readable owner + surface
|
|
173
|
+
* for custom tooling that shouldn't re-derive them from path prefixes.
|
|
174
|
+
*
|
|
175
|
+
* The module is the authoritative owner from the manifest, so `publicPath`
|
|
176
|
+
* overrides are labelled with their real owning module rather than their mount
|
|
177
|
+
* prefix. Applied to the aggregate before it's split, so the per-module and
|
|
178
|
+
* surface documents (all derived from it) inherit the stamps. Keys are appended,
|
|
179
|
+
* keeping order deterministic for the drift gate.
|
|
180
|
+
*/
|
|
181
|
+
export declare function stampModuleMetadata(doc: OpenApiDocument, owner: ReadonlyMap<string, string>): OpenApiDocument;
|
|
152
182
|
export {};
|
|
153
183
|
//# sourceMappingURL=openapi.d.ts.map
|
package/dist/openapi.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"openapi.d.ts","sourceRoot":"","sources":["../src/openapi.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAC/C,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,MAAM,CAAA;AAEhC,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAA;AAExD;;;;;;;;;;;;GAYG;AAEH,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,MAAM,CAAA;IACb,OAAO,EAAE,MAAM,CAAA;IACf,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,8EAA8E;IAC9E,CAAC,SAAS,EAAE,KAAK,MAAM,EAAE,GAAG,OAAO,CAAA;CACpC;AAED,MAAM,WAAW,aAAa;IAC5B,GAAG,EAAE,MAAM,CAAA;IACX,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,CAAC,SAAS,EAAE,KAAK,MAAM,EAAE,GAAG,OAAO,CAAA;CACpC;AAED,MAAM,WAAW,sBAAsB;IACrC,IAAI,EAAE,WAAW,CAAA;IACjB,OAAO,CAAC,EAAE,aAAa,EAAE,CAAA;CAC1B;AAGD,KAAK,MAAM,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,CAAA;AAEjC;;;GAGG;AACH,MAAM,MAAM,eAAe,GAAG,UAAU,CAAC,WAAW,CAAC,sBAAsB,CAAC,CAAC,CAAA;AAE7E;;;;GAIG;AACH,wBAAgB,uBAAuB,CACrC,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,sBAAsB,GAC9B,eAAe,CASjB;AAED;;;;GAIG;AACH,MAAM,MAAM,UAAU,GAAG,OAAO,GAAG,YAAY,CAAA;AAO/C;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,eAAe,EAAE,OAAO,EAAE,UAAU,GAAG,eAAe,CAMxF;AAED;;;;;;GAMG;AACH,MAAM,WAAW,SAAS;IACxB,MAAM,EAAE,MAAM,CAAA;IACd,IAAI,EAAE,gBAAgB,CAAA;CACvB;AAED;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,WAAW;IAC1B,UAAU,EAAE,MAAM,CAAA;IAClB,MAAM,EAAE,MAAM,CAAA;IACd,IAAI,EAAE,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAA;CACrC;AAYD;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAsB,qBAAqB,CACzC,IAAI,EAAE,eAAe,EACrB,MAAM,EAAE,SAAS,SAAS,EAAE,EAC5B,OAAO,EAAE,sBAAsB,GAC9B,OAAO,CAAC,eAAe,CAAC,CA8D1B;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAsB,8BAA8B,CAClD,MAAM,EAAE,SAAS,WAAW,EAAE,EAC9B,OAAO,EAAE,sBAAsB,GAC9B,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC,CAsCvC;
|
|
1
|
+
{"version":3,"file":"openapi.d.ts","sourceRoot":"","sources":["../src/openapi.ts"],"names":[],"mappings":"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"}
|
package/dist/openapi.js
CHANGED
|
@@ -173,46 +173,68 @@ export async function generateModuleOpenApiDocuments(mounts, options) {
|
|
|
173
173
|
}
|
|
174
174
|
return result;
|
|
175
175
|
}
|
|
176
|
-
/**
|
|
177
|
-
|
|
178
|
-
|
|
176
|
+
/**
|
|
177
|
+
* The module a path belongs to, given the authoritative owner map: the manifest
|
|
178
|
+
* owner if known (which is what keeps `publicPath` overrides correct), else the
|
|
179
|
+
* path's own module segment — `<seg>` in `/v1/admin/<seg>/...`,
|
|
180
|
+
* `/v1/public/<seg>/...`, or `/v1/<seg>/...` (webhooks/legacy).
|
|
181
|
+
*/
|
|
182
|
+
function moduleNameForPath(path, owner) {
|
|
183
|
+
const known = owner.get(path);
|
|
184
|
+
if (known)
|
|
185
|
+
return known;
|
|
186
|
+
const parts = path.split("/").filter(Boolean); // ["v1","admin","bookings",...]
|
|
187
|
+
const seg = parts[1] === "admin" || parts[1] === "public" ? parts[2] : parts[1];
|
|
188
|
+
return seg ?? "misc";
|
|
189
|
+
}
|
|
190
|
+
/** The API surface a path is served on, or `null` for non-surface routes. */
|
|
191
|
+
function surfaceForPath(path) {
|
|
192
|
+
if (path.startsWith("/v1/admin/"))
|
|
193
|
+
return "admin";
|
|
194
|
+
if (path.startsWith("/v1/public/"))
|
|
195
|
+
return "storefront";
|
|
196
|
+
return null;
|
|
179
197
|
}
|
|
180
198
|
/**
|
|
181
|
-
*
|
|
182
|
-
* admin/storefront path (voyant#2733).
|
|
183
|
-
*
|
|
184
|
-
* `generateModuleOpenApiDocuments` alone only sees routes recorded in the module
|
|
185
|
-
* mount manifest — it misses `additionalRoutes` and any route mounted directly
|
|
186
|
-
* on the composed app (e.g. the operator's workflow-runs admin surface, the
|
|
187
|
-
* `_meta/capabilities` route). Those were present in the old committed aggregate
|
|
188
|
-
* and must not silently vanish from the committed per-module specs.
|
|
199
|
+
* Build the authoritative path → module ownership map from the mount manifest.
|
|
189
200
|
*
|
|
190
|
-
*
|
|
191
|
-
*
|
|
192
|
-
* module name, e.g. `/v1/public/booking-engine`)
|
|
193
|
-
*
|
|
194
|
-
*
|
|
195
|
-
* `/v1/admin/*` and `/v1/public/*` path lands in exactly one module document.
|
|
196
|
-
* Non-surface routes (`/v1/<name>` webhooks, legacy `/v1/*`) live only in the
|
|
197
|
-
* aggregate, as before.
|
|
198
|
-
*
|
|
199
|
-
* Each per-module document carries the aggregate's shared `components` verbatim
|
|
200
|
-
* (there is ~one), so it stays a valid, self-contained OpenAPI document.
|
|
201
|
+
* Generates each module's isolated doc (see `generateModuleOpenApiDocuments`)
|
|
202
|
+
* only to learn which real absolute paths it owns — so `publicPath` overrides
|
|
203
|
+
* (whose prefix isn't the module name, e.g. `/v1/public/booking-engine`) map to
|
|
204
|
+
* the right module. Routes the manifest doesn't record (`additionalRoutes`,
|
|
205
|
+
* directly-mounted routes) are absent here and fall back to their path segment.
|
|
201
206
|
*
|
|
202
207
|
* Build-time only.
|
|
203
208
|
*/
|
|
204
|
-
export async function
|
|
209
|
+
export async function buildModulePathOwnership(mounts, options) {
|
|
205
210
|
const moduleDocs = await generateModuleOpenApiDocuments(mounts, options);
|
|
206
211
|
const owner = new Map();
|
|
207
212
|
for (const [moduleName, doc] of moduleDocs) {
|
|
208
213
|
for (const path of Object.keys(doc.paths ?? {}))
|
|
209
214
|
owner.set(path, moduleName);
|
|
210
215
|
}
|
|
216
|
+
return owner;
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* Partition a composed document into one document per module, covering EVERY
|
|
220
|
+
* admin/storefront path (voyant#2733).
|
|
221
|
+
*
|
|
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).
|
|
225
|
+
* The full surface is therefore partitioned exactly: every `/v1/admin/*` and
|
|
226
|
+
* `/v1/public/*` path lands in exactly one module document. Non-surface routes
|
|
227
|
+
* (`/v1/<name>` webhooks, legacy `/v1/*`) live only in the aggregate, as before.
|
|
228
|
+
*
|
|
229
|
+
* Each per-module document carries the aggregate's shared `components` verbatim
|
|
230
|
+
* (there is ~one), so it stays a valid, self-contained OpenAPI document.
|
|
231
|
+
*/
|
|
232
|
+
export function partitionByModule(full, owner) {
|
|
211
233
|
const buckets = new Map();
|
|
212
234
|
for (const [path, item] of Object.entries(full.paths ?? {})) {
|
|
213
235
|
if (!path.startsWith("/v1/admin/") && !path.startsWith("/v1/public/"))
|
|
214
236
|
continue;
|
|
215
|
-
const moduleName =
|
|
237
|
+
const moduleName = moduleNameForPath(path, owner);
|
|
216
238
|
let bucket = buckets.get(moduleName);
|
|
217
239
|
if (!bucket) {
|
|
218
240
|
bucket = {};
|
|
@@ -230,3 +252,126 @@ export async function splitDocumentByModule(full, mounts, options) {
|
|
|
230
252
|
}
|
|
231
253
|
return result;
|
|
232
254
|
}
|
|
255
|
+
/**
|
|
256
|
+
* Convenience: build the ownership map and partition in one call. Prefer the
|
|
257
|
+
* two-step `buildModulePathOwnership` + `partitionByModule` when you also want
|
|
258
|
+
* to `stampModuleMetadata` the aggregate from the same map (so it's built once).
|
|
259
|
+
*
|
|
260
|
+
* Build-time only.
|
|
261
|
+
*/
|
|
262
|
+
export async function splitDocumentByModule(full, mounts, options) {
|
|
263
|
+
return partitionByModule(full, await buildModulePathOwnership(mounts, options));
|
|
264
|
+
}
|
|
265
|
+
const HTTP_METHODS = ["get", "put", "post", "delete", "options", "head", "patch", "trace"];
|
|
266
|
+
/** PascalCase a path segment, splitting on non-alphanumerics (kebab, etc.). */
|
|
267
|
+
function pascalCase(segment) {
|
|
268
|
+
return segment
|
|
269
|
+
.split(/[^a-zA-Z0-9]+/)
|
|
270
|
+
.filter(Boolean)
|
|
271
|
+
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
|
|
272
|
+
.join("");
|
|
273
|
+
}
|
|
274
|
+
/**
|
|
275
|
+
* Stable camelCase operationId derived from method + path (voyant#2729) — gives
|
|
276
|
+
* client generators readable, deterministic method names instead of guessing.
|
|
277
|
+
* Path params render as `ByX`, the `v1` prefix is dropped:
|
|
278
|
+
* `GET /v1/admin/bookings/{id}` → `getAdminBookingsById`. Method + path is unique
|
|
279
|
+
* per OpenAPI, so the derived id is too (a numeric suffix guards edge cases).
|
|
280
|
+
*/
|
|
281
|
+
function deriveOperationId(method, path) {
|
|
282
|
+
const parts = path
|
|
283
|
+
.split("/")
|
|
284
|
+
.filter(Boolean)
|
|
285
|
+
.filter((segment) => segment !== "v1")
|
|
286
|
+
.map((segment) => {
|
|
287
|
+
const param = /^\{(.+)\}$/.exec(segment);
|
|
288
|
+
return param?.[1] ? `By${pascalCase(param[1])}` : pascalCase(segment);
|
|
289
|
+
});
|
|
290
|
+
return `${method}${parts.join("")}`;
|
|
291
|
+
}
|
|
292
|
+
/**
|
|
293
|
+
* A readable, always-correct operation summary (voyant#2729): the method + path
|
|
294
|
+
* signature. Deliberately mechanical rather than guessed prose — a hand-authored
|
|
295
|
+
* per-route `summary` overrides it (forward-only).
|
|
296
|
+
*/
|
|
297
|
+
function deriveSummary(method, path) {
|
|
298
|
+
return `${method.toUpperCase()} ${path}`;
|
|
299
|
+
}
|
|
300
|
+
/**
|
|
301
|
+
* Stamp every operation with the metadata standard OpenAPI tooling expects
|
|
302
|
+
* (voyant#2733 / voyant#2729). All fields are non-destructive — a value a route
|
|
303
|
+
* already declares is never overwritten:
|
|
304
|
+
* - `operationId` — stable camelCase id from method + path, for readable
|
|
305
|
+
* generated client method names.
|
|
306
|
+
* - `summary` — the method + path signature, so viewers/linters have a title
|
|
307
|
+
* for every operation.
|
|
308
|
+
* - `tags: [module]` — so Swagger/Scalar group the sidebar by module (they
|
|
309
|
+
* key grouping off `tags` and ignore `x-*`).
|
|
310
|
+
* - `x-voyant-module` / `x-voyant-surface` — machine-readable owner + surface
|
|
311
|
+
* for custom tooling that shouldn't re-derive them from path prefixes.
|
|
312
|
+
*
|
|
313
|
+
* The module is the authoritative owner from the manifest, so `publicPath`
|
|
314
|
+
* overrides are labelled with their real owning module rather than their mount
|
|
315
|
+
* prefix. Applied to the aggregate before it's split, so the per-module and
|
|
316
|
+
* surface documents (all derived from it) inherit the stamps. Keys are appended,
|
|
317
|
+
* keeping order deterministic for the drift gate.
|
|
318
|
+
*/
|
|
319
|
+
export function stampModuleMetadata(doc, owner) {
|
|
320
|
+
const paths = {};
|
|
321
|
+
// operationId must be unique across the document; track what we've assigned so
|
|
322
|
+
// a derived id never collides. Pre-seed EVERY route-declared id up front —
|
|
323
|
+
// otherwise a hand-authored id appearing later in iteration order than an
|
|
324
|
+
// earlier op that derived the same string would slip through un-suffixed,
|
|
325
|
+
// leaving duplicate ids in the output (declared ids always win; derived yield).
|
|
326
|
+
const usedOperationIds = new Set();
|
|
327
|
+
for (const item of Object.values(doc.paths ?? {})) {
|
|
328
|
+
if (!item || typeof item !== "object")
|
|
329
|
+
continue;
|
|
330
|
+
for (const method of HTTP_METHODS) {
|
|
331
|
+
const op = item[method];
|
|
332
|
+
const declared = op && typeof op === "object" ? op.operationId : null;
|
|
333
|
+
if (typeof declared === "string" && declared.length > 0)
|
|
334
|
+
usedOperationIds.add(declared);
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
for (const [path, item] of Object.entries(doc.paths ?? {})) {
|
|
338
|
+
if (!item || typeof item !== "object") {
|
|
339
|
+
paths[path] = item;
|
|
340
|
+
continue;
|
|
341
|
+
}
|
|
342
|
+
const moduleName = moduleNameForPath(path, owner);
|
|
343
|
+
const surface = surfaceForPath(path);
|
|
344
|
+
const nextItem = { ...item };
|
|
345
|
+
for (const method of HTTP_METHODS) {
|
|
346
|
+
const op = nextItem[method];
|
|
347
|
+
if (!op || typeof op !== "object")
|
|
348
|
+
continue;
|
|
349
|
+
const operation = op;
|
|
350
|
+
const declaredId = typeof operation.operationId === "string" && operation.operationId.length > 0
|
|
351
|
+
? operation.operationId
|
|
352
|
+
: null;
|
|
353
|
+
let operationId = declaredId ?? deriveOperationId(method, path);
|
|
354
|
+
if (!declaredId) {
|
|
355
|
+
let suffix = 2;
|
|
356
|
+
while (usedOperationIds.has(operationId)) {
|
|
357
|
+
operationId = `${deriveOperationId(method, path)}_${suffix++}`;
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
usedOperationIds.add(operationId);
|
|
361
|
+
const hasSummary = typeof operation.summary === "string" && operation.summary.length > 0;
|
|
362
|
+
// Swagger/Scalar group by `tags` (and ignore `x-*`) — without one a
|
|
363
|
+
// whole-surface document collapses under a single "default" group (#2733).
|
|
364
|
+
const hasTags = Array.isArray(operation.tags) && operation.tags.length > 0;
|
|
365
|
+
nextItem[method] = {
|
|
366
|
+
...operation,
|
|
367
|
+
operationId,
|
|
368
|
+
...(hasSummary ? {} : { summary: deriveSummary(method, path) }),
|
|
369
|
+
...(hasTags ? {} : { tags: [moduleName] }),
|
|
370
|
+
"x-voyant-module": moduleName,
|
|
371
|
+
...(surface ? { "x-voyant-surface": surface } : {}),
|
|
372
|
+
};
|
|
373
|
+
}
|
|
374
|
+
paths[path] = nextItem;
|
|
375
|
+
}
|
|
376
|
+
return { ...doc, paths };
|
|
377
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@voyant-travel/hono",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.120.1",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -136,18 +136,18 @@
|
|
|
136
136
|
"hono": "^4.12.25",
|
|
137
137
|
"zod": "^4.4.3",
|
|
138
138
|
"@voyant-travel/core": "^0.111.1",
|
|
139
|
-
"@voyant-travel/db": "^0.109.4",
|
|
140
|
-
"@voyant-travel/storage": "^0.106.0",
|
|
141
139
|
"@voyant-travel/types": "^0.106.1",
|
|
140
|
+
"@voyant-travel/storage": "^0.106.0",
|
|
141
|
+
"@voyant-travel/db": "^0.109.4",
|
|
142
142
|
"@voyant-travel/utils": "^0.105.5",
|
|
143
|
-
"@voyant-travel/workflows": "^0.111.
|
|
143
|
+
"@voyant-travel/workflows": "^0.111.14"
|
|
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.14"
|
|
151
151
|
},
|
|
152
152
|
"files": [
|
|
153
153
|
"dist"
|