@lostgradient/weft 0.14.0 → 0.16.0

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.
Files changed (107) hide show
  1. package/README.md +3 -8
  2. package/dist/alerting/alert-manager.d.ts +2 -0
  3. package/dist/alerting/alert-manager.js +3 -0
  4. package/dist/cli/api.js +5 -19
  5. package/dist/cli/console.d.ts +12 -0
  6. package/dist/cli/console.js +62 -0
  7. package/dist/cli/generated/operation-catalog.snapshot.json +1061 -90
  8. package/dist/cli/generated/operation-client.generated.d.ts +29 -23
  9. package/dist/cli/generated/operation-client.generated.js +2 -0
  10. package/dist/cli/help-text.d.ts +1 -1
  11. package/dist/cli/help-text.js +1 -0
  12. package/dist/cli/index.d.ts +1 -0
  13. package/dist/cli/index.js +1 -0
  14. package/dist/cli/json-input.d.ts +20 -0
  15. package/dist/cli/json-input.js +33 -0
  16. package/dist/cli/parse-arguments.js +2 -0
  17. package/dist/cli/schedule.js +7 -9
  18. package/dist/cli/types.d.ts +1 -0
  19. package/dist/cli/workflow-commands.js +5 -25
  20. package/dist/cli-main.js +4 -2
  21. package/dist/core/aggregate-validation.d.ts +2 -5
  22. package/dist/core/aggregate-validation.js +2 -10
  23. package/dist/core/atomic-state.js +21 -24
  24. package/dist/core/engine/index.d.ts +3 -0
  25. package/dist/core/engine/index.js +3 -0
  26. package/dist/core/engine/schedules.js +4 -1
  27. package/dist/core/inline-execution-strategy.js +8 -46
  28. package/dist/core/list-filter-validation.d.ts +2 -5
  29. package/dist/core/list-filter-validation.js +2 -10
  30. package/dist/core/types/schedules.d.ts +13 -31
  31. package/dist/core/validation-issues.d.ts +13 -0
  32. package/dist/core/validation-issues.js +9 -0
  33. package/dist/diagnostics/recommendations.js +1 -9
  34. package/dist/http.js +2 -2
  35. package/dist/indexeddb.js +1 -1
  36. package/dist/mcp/session.js +2 -10
  37. package/dist/mcp/tools.js +2 -10
  38. package/dist/mcp/tuple-list-equality.d.ts +1 -0
  39. package/dist/mcp/tuple-list-equality.js +12 -0
  40. package/dist/server/authorization-scope.d.ts +1 -1
  41. package/dist/server/authorization-scope.js +0 -2
  42. package/dist/server/dashboard-assets.d.ts +26 -0
  43. package/dist/server/dashboard-assets.js +95 -0
  44. package/dist/server/index.d.ts +10 -1
  45. package/dist/server/index.js +11 -2
  46. package/dist/server/interactive-operations.js +1 -0
  47. package/dist/server/operation-catalog/types.d.ts +1 -1
  48. package/dist/server/operation-registry.d.ts +2 -31
  49. package/dist/server/operations/bulk-filter-helpers.d.ts +17 -17
  50. package/dist/server/operations/bulk-filter-helpers.js +16 -37
  51. package/dist/server/operations/bulk-mutate-workflow-tags.d.ts +27 -27
  52. package/dist/server/operations/bulk-signal-workflows.d.ts +26 -26
  53. package/dist/server/operations/create-schedule.js +26 -65
  54. package/dist/server/operations/event-stream-contracts.d.ts +20 -0
  55. package/dist/server/operations/event-stream-contracts.js +67 -0
  56. package/dist/server/operations/fleet-events-sse.js +21 -50
  57. package/dist/server/operations/fleet-events-subscription.js +8 -12
  58. package/dist/server/operations/list-alerts.d.ts +26 -0
  59. package/dist/server/operations/list-alerts.js +54 -0
  60. package/dist/server/operations/list-workflows.d.ts +43 -43
  61. package/dist/server/operations/list-workflows.js +9 -30
  62. package/dist/server/operations/purge-workflows.d.ts +11 -11
  63. package/dist/server/operations/schedule-faults.d.ts +9 -1
  64. package/dist/server/operations/schedule-faults.js +51 -0
  65. package/dist/server/operations/schedule-rest-body.d.ts +11 -0
  66. package/dist/server/operations/schedule-rest-body.js +28 -0
  67. package/dist/server/operations/update-schedule.js +13 -72
  68. package/dist/server/operations/workflow-events-sse.js +29 -67
  69. package/dist/server/operations/workflow-events-subscription.js +9 -27
  70. package/dist/server/rest-bindings.js +3 -0
  71. package/dist/server/runtime/task-polling.js +3 -5
  72. package/dist/server/task-state.d.ts +2 -8
  73. package/dist/server/task-state.js +10 -7
  74. package/dist/service-worker/index.d.ts +4 -1
  75. package/dist/service-worker/index.js +2 -2
  76. package/dist/service-worker/setup.d.ts +20 -0
  77. package/dist/service-worker/setup.js +5 -5
  78. package/dist/storage/bun-sql.js +65 -80
  79. package/dist/storage/conditional-batch-body.d.ts +7 -0
  80. package/dist/storage/conditional-batch-body.js +14 -0
  81. package/dist/storage/lmdb.js +1 -1
  82. package/dist/storage/memory.js +1 -1
  83. package/dist/storage/neon.js +2 -2
  84. package/dist/storage/node-sqlite-loader.d.ts +2 -2
  85. package/dist/storage/node-sqlite.js +32 -17
  86. package/dist/storage/postgres.js +2 -2
  87. package/dist/storage/resolve.js +1 -1
  88. package/dist/storage/scoped-storage.js +1 -1
  89. package/dist/storage/testing.js +1 -1
  90. package/dist/storage/turso.js +2 -2
  91. package/dist/version.d.ts +1 -1
  92. package/dist/version.js +1 -1
  93. package/dist/web-extension.js +1 -1
  94. package/dist/worker/execute-with-interceptors.d.ts +1 -1
  95. package/dist/worker/execute-with-interceptors.js +2 -2
  96. package/dist/worker/index.js +5 -5
  97. package/dist/worker/long-poll.js +3 -4
  98. package/dist/worker/protocol-messages.d.ts +11 -19
  99. package/dist/worker/protocol-schemas.d.ts +8 -8
  100. package/dist/worker/protocol-schemas.js +4 -4
  101. package/dist/worker/protocol-task-result.d.ts +1 -0
  102. package/dist/worker/protocol-task-result.js +1 -3
  103. package/dist/worker/protocol.js +1 -1
  104. package/dist/worker/registry/types.d.ts +2 -7
  105. package/dist/worker/registry.d.ts +3 -9
  106. package/dist/worker/registry.js +2 -5
  107. package/package.json +5 -1
@@ -12,6 +12,7 @@ import { z } from 'zod';
12
12
  import { isFailureCategory } from './failure-categories.ts';
13
13
  import type { FailureCategory, WorkflowStatus } from './types/identity.ts';
14
14
  import type { ListFilter } from './types/list-options.ts';
15
+ import { type ValidationIssue } from './validation-issues.ts';
15
16
  import { WeftError } from './weft-error.ts';
16
17
  /**
17
18
  * Concrete object schema for {@link ListFilter}. Supports `.omit()` and
@@ -56,11 +57,7 @@ export declare const listFilterObjectSchema: z.ZodObject<{
56
57
  failureCategory: z.ZodOptional<z.ZodUnion<readonly [z.ZodType<FailureCategory, unknown, z.core.$ZodTypeInternals<FailureCategory, unknown>>, z.ZodArray<z.ZodType<FailureCategory, unknown, z.core.$ZodTypeInternals<FailureCategory, unknown>>>]>>;
57
58
  }, z.core.$strict>;
58
59
  /** A flattened Zod issue suitable for cross-transport serialization. */
59
- export type FilterValidationIssue = {
60
- readonly path: ReadonlyArray<string | number>;
61
- readonly message: string;
62
- readonly code: string;
63
- };
60
+ export type FilterValidationIssue = ValidationIssue;
64
61
  /**
65
62
  * Thrown by {@link normalizeListFilter} when the input fails validation.
66
63
  * Carries flattened Zod issues so transport adapters can map directly to the
@@ -1,5 +1,6 @@
1
1
  import { z } from "zod";
2
2
  import { FAILURE_CATEGORIES, isFailureCategory } from "./failure-categories.js";
3
+ import { flattenZodIssue } from "./validation-issues.js";
3
4
  import { WeftError } from "./weft-error.js";
4
5
  const WORKFLOW_STATUSES = [
5
6
  "pending",
@@ -48,15 +49,6 @@ export const listFilterObjectSchema = z.object({
48
49
  executionDeadline: timeRangeSchema.optional(),
49
50
  failureCategory: z.union([failureCategorySchema, z.array(failureCategorySchema).min(1)]).optional()
50
51
  }).strict();
51
- function flattenIssue(issue) {
52
- const path = [];
53
- for (const segment of issue.path)
54
- if (typeof segment === "string" || typeof segment === "number")
55
- path.push(segment);
56
- else
57
- path.push(String(segment));
58
- return { path, message: issue.message, code: issue.code };
59
- }
60
52
 
61
53
  export class ListFilterValidationError extends WeftError {
62
54
  issues;
@@ -72,7 +64,7 @@ export class ListFilterValidationError extends WeftError {
72
64
  export function normalizeListFilter(input) {
73
65
  const result = listFilterObjectSchema.safeParse(input ?? {});
74
66
  if (!result.success)
75
- throw new ListFilterValidationError(result.error.issues.map(flattenIssue));
67
+ throw new ListFilterValidationError(result.error.issues.map(flattenZodIssue));
76
68
  if (result.data.parentWorkflowExecutionToken !== void 0 && result.data.parentWorkflowId === void 0)
77
69
  throw new ListFilterValidationError([
78
70
  {
@@ -181,14 +181,13 @@ export type ScheduleDefinition<TInput = unknown> = ScheduleSpec & {
181
181
  */
182
182
  export declare function schedule<TInput>(definition: ScheduleDefinition<TInput>): ScheduleDefinition<TInput>;
183
183
  /**
184
- * Full persisted state of a recurring schedule. Returned by `engine.getSchedule(id)`.
185
- * Use {@link ScheduleSummary} (returned by list operations and `engine.getSchedule()`)
186
- * for the lightweight variant it omits `input` to keep payloads small.
184
+ * Metadata shared by persisted schedule state and lightweight schedule summaries.
185
+ * Persisted state and summaries extend this contract so metadata cannot drift
186
+ * between durable records and public lightweight responses.
187
187
  */
188
- export interface ScheduleState {
188
+ export interface ScheduleMetadata {
189
189
  id: string;
190
190
  workflowType: string;
191
- input: unknown;
192
191
  /** Human-readable operator description stored with this schedule. */
193
192
  description?: string;
194
193
  /**
@@ -220,38 +219,21 @@ export interface ScheduleState {
220
219
  /** Ordered durable occurrences waiting behind `currentWorkflowId`. */
221
220
  queuedRuns: ScheduleQueuedRun[];
222
221
  }
222
+ /**
223
+ * Full persisted state of a recurring schedule. Returned by `engine.getSchedule(id)`.
224
+ * Use {@link ScheduleSummary} (returned by list operations and `engine.getSchedule()`)
225
+ * for the lightweight variant — it omits `input` to keep payloads small.
226
+ */
227
+ export interface ScheduleState extends ScheduleMetadata {
228
+ input: unknown;
229
+ }
223
230
  /**
224
231
  * Lightweight summary of a recurring schedule returned by list operations.
225
232
  * Contains cron expression, status, timing metadata, and the ID of the
226
233
  * currently running workflow (if any). For the full record including the
227
234
  * `input`, use {@link ScheduleState}.
228
235
  */
229
- export interface ScheduleSummary {
230
- id: string;
231
- workflowType: string;
232
- /** Human-readable operator description stored with this schedule. */
233
- description?: string;
234
- /** Cron expression for cron-based schedules; absent for interval schedules. */
235
- cronExpression?: string;
236
- /** Interval period in milliseconds for interval-based schedules; absent for cron schedules. */
237
- intervalMs?: number;
238
- status: ScheduleStatus;
239
- overlap: ScheduleOverlapPolicy;
240
- backfill: boolean;
241
- /** Normalized deterministic jitter window in milliseconds. */
242
- jitterMs?: number;
243
- createdAt: number;
244
- updatedAt: number;
245
- /** Most recent occurrence that started a scheduled workflow. */
246
- lastFireAt?: number;
247
- /** Most recent occurrence skipped because a non-backfill timer was late. */
248
- lastMissedFireAt?: number;
249
- /** Lifetime count of occurrences skipped because a non-backfill timer was late. */
250
- missedFireCount: number;
251
- nextFireAt: number | null;
252
- currentWorkflowId?: string;
253
- /** Ordered durable occurrences waiting behind `currentWorkflowId`. */
254
- queuedRuns: ScheduleQueuedRun[];
236
+ export interface ScheduleSummary extends ScheduleMetadata {
255
237
  }
256
238
  /**
257
239
  * Filter criteria for `engine.listSchedules`. All fields are optional.
@@ -0,0 +1,13 @@
1
+ import type { ZodIssue } from 'zod';
2
+ /** A flattened Zod issue suitable for cross-transport serialization. */
3
+ export type ValidationIssue = {
4
+ readonly path: ReadonlyArray<string | number>;
5
+ readonly message: string;
6
+ readonly code: string;
7
+ };
8
+ /**
9
+ * Convert a Zod issue into the stable diagnostic shape used by core validators.
10
+ * Path segments outside the JSON-friendly string and number forms are rendered
11
+ * with their string representation, preserving the original issue order.
12
+ */
13
+ export declare function flattenZodIssue(issue: ZodIssue): ValidationIssue;
@@ -0,0 +1,9 @@
1
+ export function flattenZodIssue(issue) {
2
+ const path = [];
3
+ for (const segment of issue.path)
4
+ if (typeof segment === "string" || typeof segment === "number")
5
+ path.push(segment);
6
+ else
7
+ path.push(String(segment));
8
+ return { path, message: issue.message, code: issue.code };
9
+ }
@@ -1,3 +1,4 @@
1
+ import { formatBytes } from "./format.js";
1
2
  import { THRESHOLDS } from "./types.js";
2
3
  function integrityFailureRule({ database }) {
3
4
  if (database.integrityOk)
@@ -95,15 +96,6 @@ export function generateRecommendations(report, thresholds) {
95
96
  };
96
97
  return RECOMMENDATION_RULES.flatMap((rule) => rule(context));
97
98
  }
98
- function formatBytes(bytes) {
99
- if (bytes < 1024)
100
- return `${bytes} B`;
101
- if (bytes < 1048576)
102
- return `${(bytes / 1024).toFixed(1)} KB`;
103
- if (bytes < 1073741824)
104
- return `${(bytes / 1048576).toFixed(1)} MB`;
105
- return `${(bytes / 1073741824).toFixed(1)} GB`;
106
- }
107
99
  function formatDuration(milliseconds) {
108
100
  const seconds = Math.floor(milliseconds / 1000), minutes = Math.floor(seconds / 60), hours = Math.floor(minutes / 60), days = Math.floor(hours / 24);
109
101
  if (days > 0)
package/dist/http.js CHANGED
@@ -1,2 +1,2 @@
1
- async function*R(F,G){if(F.body===null)return;let J=F.body.getReader(),Q=new TextDecoder,Z="",$=0,j=!1;try{while(!0){let{done:y,value:z}=await J.read();if(y){j=!0;break}if($+=z.byteLength,$>G.maximumBytes)throw G.sizeLimitError();Z+=Q.decode(z,{stream:!0});let A=Z.split(`
2
- `);Z=A.pop()??"";for(let g of A)yield g}if(Z+=Q.decode(),Z.length>0)yield Z}finally{if(!j)try{await J.cancel()}catch{}J.releaseLock()}}function N(F){let G=[];for(let J=0;J<F.length;J+=512)G.push(String.fromCharCode(...F.subarray(J,J+512)));return btoa(G.join(""))}function B(F){let G=atob(F),J=new Uint8Array(G.length);for(let Q=0;Q<G.length;Q+=1)J[Q]=G.charCodeAt(Q);return J}function q(F){return typeof F==="object"&&F!==null&&!Array.isArray(F)}async function _(F,G){return await F.get(G)!==null}async function*X(F,G,J){for await(let[Q]of F.scan(G,J))yield Q}async function H(F,G){let J=0;for await(let Q of X(F,G))J++;return J}async function L(F,G){let J=[];for await(let Q of X(F,G))J.push({type:"delete",key:Q});if(J.length===0)return 0;return await F.batch(J),J.length}async function U(F,G,J){let Q=[];for await(let Z of X(F,G,J))Q.push({type:"delete",key:Z});if(Q.length===0)return 0;return await F.batch(Q),Q.length}function C(F,G,J){if(!F.capabilities()[G])throw Error(`Feature "${J}" requires storage capability "${G}", but this storage backend does not provide it.`)}var D=1e4;class T extends Error{code="StorageBatchOperationLimitExceededError";cap=D;count;target;constructor(F,G){super(`${F} count ${G} exceeds MAX_BATCH_OPERATIONS (${D}).`);this.name="StorageBatchOperationLimitExceededError",this.target=F,this.count=G}}function M(F,G){if(G>D)throw new T(F,G)}function S(F){return F.length>0?F.slice(0,-1)+String.fromCharCode(F.charCodeAt(F.length-1)+1):"ÿ"}function e(F,G={}){if(G.gt!==void 0&&F<=G.gt)return!1;if(G.gte!==void 0&&F<G.gte)return!1;if(G.lt!==void 0&&F>=G.lt)return!1;if(G.lte!==void 0&&F>G.lte)return!1;return!0}function FF(F,G){if(F===null||G===null)return F===G;if(F.byteLength!==G.byteLength)return!1;for(let J=0;J<F.byteLength;J++)if(F[J]!==G[J])return!1;return!0}async function K(F,G){if(F.has)return F.has(G);return _(F,G)}function P(F,G,J){if(F.keys)return F.keys(G,J);return X(F,G,J)}async function O(F,G){if(F.count)return F.count(G);return H(F,G)}async function b(F,G){if(F.deletePrefix)return F.deletePrefix(G);return L(F,G)}async function v(F,G,J){if(M("conditionalBatch conditions",G.length),M("conditionalBatch operations",J.length),C(F,"conditionalBatch","storageConditionalBatch"),!F.conditionalBatch)throw Error("This storage backend reports conditionalBatch capability but does not implement the conditionalBatch() method.");return F.conditionalBatch(G,J)}function c(F){if(F===void 0)return;if(typeof F!=="number"||!Number.isInteger(F)||F<0)throw Error("deleteRange limit must be a finite non-negative integer");return F===0?0:F}function V(F){let G={},J=!1;for(let Z of["gt","gte","lt","lte"]){let $=F[Z];if($===void 0)continue;if(typeof $!=="string")throw Error("deleteRange bounds must be strings");G[Z]=$,J=!0}if(!J)throw Error("deleteRange requires at least one of gt/gte/lt/lte; use deletePrefix to delete a whole prefix");let Q=c(F.limit);if(Q!==void 0)G.limit=Q;return G}async function h(F,G,J){let Q=V(J);if(F.deleteRange)return F.deleteRange(G,Q);return U(F,G,Q)}function I(F,G){let J={key:F,open:!1};if(G.gte!==void 0&&G.gte>J.key)J.key=G.gte,J.open=!1;if(G.gt!==void 0&&G.gt>=J.key)J.key=G.gt,J.open=!0;return J}function u(F,G){let J={key:S(F),open:!0};if(G.lt!==void 0&&G.lt<=J.key)J.key=G.lt,J.open=!0;if(G.lte!==void 0&&G.lte<J.key)J.key=G.lte,J.open=!1;return J}function ZF(F,G){let J=I(F,G),Q=u(F,G);if(J.key>Q.key||J.key===Q.key&&(J.open||Q.open))return null;return{lower:J,upper:Q}}function E(F){return F.replaceAll(/:+$/g,"")}function k(F,G){let J=E(F),Q=E(G);if(J.length===0)return Q;if(Q.length===0)return J;return`${J}:${Q}`}class W{#G;#J;constructor(F,G){this.#G=F,this.#J=E(G)}#F(F){if(this.#J.length===0)return F;return F.length===0?`${this.#J}:`:`${this.#J}:${F}`}#Q(F){if(this.#J.length===0)return F;return F.slice(this.#J.length+1)}#Z(F={}){let G={};if(F.limit!==void 0)G.limit=F.limit;if(F.reverse!==void 0)G.reverse=F.reverse;if(F.gt!==void 0)G.gt=this.#F(F.gt);if(F.gte!==void 0)G.gte=this.#F(F.gte);if(F.lt!==void 0)G.lt=this.#F(F.lt);if(F.lte!==void 0)G.lte=this.#F(F.lte);return G}#M(F){let G={};if(F.limit!==void 0)G.limit=F.limit;if(F.gt!==void 0)G.gt=this.#F(F.gt);if(F.gte!==void 0)G.gte=this.#F(F.gte);if(F.lt!==void 0)G.lt=this.#F(F.lt);if(F.lte!==void 0)G.lte=this.#F(F.lte);return G}capabilities(){return this.#G.capabilities()}scoped(F){return new W(this.#G,k(this.#J,F))}async get(F){return this.#G.get(this.#F(F))}async put(F,G){await this.#G.put(this.#F(F),G)}async delete(F){await this.#G.delete(this.#F(F))}async*scan(F,G){for await(let[J,Q]of this.#G.scan(this.#F(F),this.#Z(G)))yield[this.#Q(J),Q]}async batch(F){M("batch operations",F.length),await this.#G.batch(F.map((G)=>{if(G.type==="put")return{type:"put",key:this.#F(G.key),value:G.value};return{type:"delete",key:this.#F(G.key)}}))}async conditionalBatch(F,G){return v(this.#G,F.map((J)=>({key:this.#F(J.key),expectedValue:J.expectedValue})),G.map((J)=>{if(J.type==="put")return{type:"put",key:this.#F(J.key),value:J.value};return{type:"delete",key:this.#F(J.key)}}))}async has(F){return K(this.#G,this.#F(F))}async deletePrefix(F){return b(this.#G,this.#F(F))}async deleteRange(F,G){let J=this.#M(V(G));return h(this.#G,this.#F(F),J)}async*keys(F,G){for await(let J of P(this.#G,this.#F(F),this.#Z(G)))yield this.#Q(J)}async count(F){return O(this.#G,this.#F(F))}[Symbol.dispose](){this.#G[Symbol.dispose]()}}function m(F,G){return new W(F,G)}var w=67108864;function x(F){if(F.type==="put")return{type:"put",key:F.key,value:N(F.value)};return{type:"delete",key:F.key}}function f(F){return{key:F.key,expectedValue:F.expectedValue===null?null:N(F.expectedValue)}}function d(F){if(!q(F)||typeof F.key!=="string"||typeof F.value!=="string")throw Error("HTTPStorage scan response contained an invalid NDJSON entry.");return{key:F.key,value:F.value}}function l(F){if(!q(F)||typeof F.applied!=="boolean")throw Error('HTTPStorage conditional batch response must include a boolean "applied" field.');return F.applied}function Y(F,G,J){if(J!==void 0)F.searchParams.set(G,String(J))}function p(F){if(F.trim().length===0)return null;let G=d(JSON.parse(F));return[G.key,B(G.value)]}class a{#G;#J;#F;constructor(F){this.#G=F.baseUrl instanceof URL?F.baseUrl:new URL(F.baseUrl),this.#J={...F.headers},this.#F=F.remoteConditionalBatch??!1}capabilities(){return{persistence:"remote",readAfterWrite:"eventual",scanConsistency:"best-effort",atomicBatch:!0,conditionalBatch:this.#F,boundedRangeDelete:!1}}#Q(F){let G=this.#G.href.endsWith("/")?this.#G.href:`${this.#G.href}/`;return new URL(F.replace(/^\/+/,""),G)}#Z(F){return this.#Q(`/v1/storage/${encodeURIComponent(F)}`)}#M(F,G){let J=this.#Q("/v1/storage");return J.searchParams.set("prefix",F),Y(J,"limit",G.limit),Y(J,"reverse",G.reverse),Y(J,"gt",G.gt),Y(J,"gte",G.gte),Y(J,"lt",G.lt),Y(J,"lte",G.lte),J}async#$(F,G={},J=[]){let Q=new Headers(this.#J);for(let[$,j]of new Headers(G.headers).entries())Q.set($,j);let Z=await fetch(F,{...G,headers:Q});if(!Z.ok&&!J.includes(Z.status))throw Error(`HTTPStorage request failed: ${G.method??"GET"} ${F.pathname} returned ${String(Z.status)}.`);return Z}async get(F){let G=await this.#$(this.#Z(F),{method:"GET"},[404]);if(G.status===404)return null;return new Uint8Array(await G.arrayBuffer())}async put(F,G){await this.#$(this.#Z(F),{method:"PUT",headers:{"content-type":"application/octet-stream"},body:new Blob([G])})}async delete(F){await this.#$(this.#Z(F),{method:"DELETE"})}async*scan(F,G={}){let J=await this.#$(this.#M(F,G),{method:"GET",headers:{accept:"application/x-ndjson"}});for await(let Q of R(J,{maximumBytes:w,sizeLimitError:()=>Error("HTTPStorage scan response exceeded the maximum allowed size.")})){let Z=p(Q);if(Z!==null)yield Z}}async batch(F){M("batch operations",F.length),await this.#$(this.#Q("/v1/storage/-/batch"),{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({operations:F.map(x)})})}async conditionalBatch(F,G){M("conditionalBatch conditions",F.length),M("conditionalBatch operations",G.length);let J=await this.#$(this.#Q("/v1/storage/-/conditional-batch"),{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({conditions:F.map(f),operations:G.map(x)})});return l(await J.json())}has(F){return _(this,F)}async*keys(F,G){yield*X(this,F,G)}count(F){return H(this,F)}deletePrefix(F){return L(this,F)}deleteRange(F,G){return U(this,F,V(G))}scoped(F){return m(this,F)}[Symbol.dispose](){}}export{a as HTTPStorage};
1
+ async function*B(F,G){if(F.body===null)return;let J=F.body.getReader(),Q=new TextDecoder,Z="",$=0,j=!1;try{while(!0){let{done:y,value:z}=await J.read();if(y){j=!0;break}if($+=z.byteLength,$>G.maximumBytes)throw G.sizeLimitError();Z+=Q.decode(z,{stream:!0});let A=Z.split(`
2
+ `);Z=A.pop()??"";for(let g of A)yield g}if(Z+=Q.decode(),Z.length>0)yield Z}finally{if(!j)try{await J.cancel()}catch{}J.releaseLock()}}function N(F){let G=[];for(let J=0;J<F.length;J+=512)G.push(String.fromCharCode(...F.subarray(J,J+512)));return btoa(G.join(""))}function R(F){let G=atob(F),J=new Uint8Array(G.length);for(let Q=0;Q<G.length;Q+=1)J[Q]=G.charCodeAt(Q);return J}function q(F){return typeof F==="object"&&F!==null&&!Array.isArray(F)}async function _(F,G){return await F.get(G)!==null}async function*X(F,G,J){for await(let[Q]of F.scan(G,J))yield Q}async function H(F,G){let J=0;for await(let Q of X(F,G))J++;return J}async function L(F,G){let J=[];for await(let Q of X(F,G))J.push({type:"delete",key:Q});if(J.length===0)return 0;return await F.batch(J),J.length}async function U(F,G,J){let Q=[];for await(let Z of X(F,G,J))Q.push({type:"delete",key:Z});if(Q.length===0)return 0;return await F.batch(Q),Q.length}function C(F,G,J){if(!F.capabilities()[G])throw Error(`Feature "${J}" requires storage capability "${G}", but this storage backend does not provide it.`)}var D=1e4;class T extends Error{code="StorageBatchOperationLimitExceededError";cap=D;count;target;constructor(F,G){super(`${F} count ${G} exceeds MAX_BATCH_OPERATIONS (${D}).`);this.name="StorageBatchOperationLimitExceededError",this.target=F,this.count=G}}function M(F,G){if(G>D)throw new T(F,G)}function K(F){return F.length>0?F.slice(0,-1)+String.fromCharCode(F.charCodeAt(F.length-1)+1):"ÿ"}function e(F,G={}){if(G.gt!==void 0&&F<=G.gt)return!1;if(G.gte!==void 0&&F<G.gte)return!1;if(G.lt!==void 0&&F>=G.lt)return!1;if(G.lte!==void 0&&F>G.lte)return!1;return!0}function FF(F,G){if(F===null||G===null)return F===G;if(F.byteLength!==G.byteLength)return!1;for(let J=0;J<F.byteLength;J++)if(F[J]!==G[J])return!1;return!0}async function S(F,G){if(F.has)return F.has(G);return _(F,G)}function O(F,G,J){if(F.keys)return F.keys(G,J);return X(F,G,J)}async function P(F,G){if(F.count)return F.count(G);return H(F,G)}async function b(F,G){if(F.deletePrefix)return F.deletePrefix(G);return L(F,G)}async function v(F,G,J){if(M("conditionalBatch conditions",G.length),M("conditionalBatch operations",J.length),C(F,"conditionalBatch","storageConditionalBatch"),!F.conditionalBatch)throw Error("This storage backend reports conditionalBatch capability but does not implement the conditionalBatch() method.");return F.conditionalBatch(G,J)}function c(F){if(F===void 0)return;if(typeof F!=="number"||!Number.isInteger(F)||F<0)throw Error("deleteRange limit must be a finite non-negative integer");return F===0?0:F}function V(F){let G={},J=!1;for(let Z of["gt","gte","lt","lte"]){let $=F[Z];if($===void 0)continue;if(typeof $!=="string")throw Error("deleteRange bounds must be strings");G[Z]=$,J=!0}if(!J)throw Error("deleteRange requires at least one of gt/gte/lt/lte; use deletePrefix to delete a whole prefix");let Q=c(F.limit);if(Q!==void 0)G.limit=Q;return G}async function h(F,G,J){let Q=V(J);if(F.deleteRange)return F.deleteRange(G,Q);return U(F,G,Q)}function I(F,G){let J={key:F,open:!1};if(G.gte!==void 0&&G.gte>J.key)J.key=G.gte,J.open=!1;if(G.gt!==void 0&&G.gt>=J.key)J.key=G.gt,J.open=!0;return J}function u(F,G){let J={key:K(F),open:!0};if(G.lt!==void 0&&G.lt<=J.key)J.key=G.lt,J.open=!0;if(G.lte!==void 0&&G.lte<J.key)J.key=G.lte,J.open=!1;return J}function ZF(F,G){let J=I(F,G),Q=u(F,G);if(J.key>Q.key||J.key===Q.key&&(J.open||Q.open))return null;return{lower:J,upper:Q}}function E(F){return F.replaceAll(/:+$/g,"")}function k(F,G){let J=E(F),Q=E(G);if(J.length===0)return Q;if(Q.length===0)return J;return`${J}:${Q}`}class W{#F;#J;constructor(F,G){this.#F=F,this.#J=E(G)}#G(F){if(this.#J.length===0)return F;return F.length===0?`${this.#J}:`:`${this.#J}:${F}`}#Z(F){return F.map((G)=>{if(G.type==="put")return{type:"put",key:this.#G(G.key),value:G.value};return{type:"delete",key:this.#G(G.key)}})}#$(F){if(this.#J.length===0)return F;return F.slice(this.#J.length+1)}#M(F){let G={};if(F.limit!==void 0)G.limit=F.limit;if(F.gt!==void 0)G.gt=this.#G(F.gt);if(F.gte!==void 0)G.gte=this.#G(F.gte);if(F.lt!==void 0)G.lt=this.#G(F.lt);if(F.lte!==void 0)G.lte=this.#G(F.lte);return G}#Q(F={}){let G=this.#M(F);if(F.reverse!==void 0)G.reverse=F.reverse;return G}#X(F){return this.#M(F)}capabilities(){return this.#F.capabilities()}scoped(F){return new W(this.#F,k(this.#J,F))}async get(F){return this.#F.get(this.#G(F))}async put(F,G){await this.#F.put(this.#G(F),G)}async delete(F){await this.#F.delete(this.#G(F))}async*scan(F,G){for await(let[J,Q]of this.#F.scan(this.#G(F),this.#Q(G)))yield[this.#$(J),Q]}async batch(F){M("batch operations",F.length),await this.#F.batch(this.#Z(F))}async conditionalBatch(F,G){return v(this.#F,F.map((J)=>({key:this.#G(J.key),expectedValue:J.expectedValue})),this.#Z(G))}async has(F){return S(this.#F,this.#G(F))}async deletePrefix(F){return b(this.#F,this.#G(F))}async deleteRange(F,G){let J=this.#X(V(G));return h(this.#F,this.#G(F),J)}async*keys(F,G){for await(let J of O(this.#F,this.#G(F),this.#Q(G)))yield this.#$(J)}async count(F){return P(this.#F,this.#G(F))}[Symbol.dispose](){this.#F[Symbol.dispose]()}}function m(F,G){return new W(F,G)}var w=67108864;function x(F){if(F.type==="put")return{type:"put",key:F.key,value:N(F.value)};return{type:"delete",key:F.key}}function f(F){return{key:F.key,expectedValue:F.expectedValue===null?null:N(F.expectedValue)}}function d(F){if(!q(F)||typeof F.key!=="string"||typeof F.value!=="string")throw Error("HTTPStorage scan response contained an invalid NDJSON entry.");return{key:F.key,value:F.value}}function l(F){if(!q(F)||typeof F.applied!=="boolean")throw Error('HTTPStorage conditional batch response must include a boolean "applied" field.');return F.applied}function Y(F,G,J){if(J!==void 0)F.searchParams.set(G,String(J))}function p(F){if(F.trim().length===0)return null;let G=d(JSON.parse(F));return[G.key,R(G.value)]}class s{#F;#J;#G;constructor(F){this.#F=F.baseUrl instanceof URL?F.baseUrl:new URL(F.baseUrl),this.#J={...F.headers},this.#G=F.remoteConditionalBatch??!1}capabilities(){return{persistence:"remote",readAfterWrite:"eventual",scanConsistency:"best-effort",atomicBatch:!0,conditionalBatch:this.#G,boundedRangeDelete:!1}}#Z(F){let G=this.#F.href.endsWith("/")?this.#F.href:`${this.#F.href}/`;return new URL(F.replace(/^\/+/,""),G)}#$(F){return this.#Z(`/v1/storage/${encodeURIComponent(F)}`)}#M(F,G){let J=this.#Z("/v1/storage");return J.searchParams.set("prefix",F),Y(J,"limit",G.limit),Y(J,"reverse",G.reverse),Y(J,"gt",G.gt),Y(J,"gte",G.gte),Y(J,"lt",G.lt),Y(J,"lte",G.lte),J}async#Q(F,G={},J=[]){let Q=new Headers(this.#J);for(let[$,j]of new Headers(G.headers).entries())Q.set($,j);let Z=await fetch(F,{...G,headers:Q});if(!Z.ok&&!J.includes(Z.status))throw Error(`HTTPStorage request failed: ${G.method??"GET"} ${F.pathname} returned ${String(Z.status)}.`);return Z}async get(F){let G=await this.#Q(this.#$(F),{method:"GET"},[404]);if(G.status===404)return null;return new Uint8Array(await G.arrayBuffer())}async put(F,G){await this.#Q(this.#$(F),{method:"PUT",headers:{"content-type":"application/octet-stream"},body:new Blob([G])})}async delete(F){await this.#Q(this.#$(F),{method:"DELETE"})}async*scan(F,G={}){let J=await this.#Q(this.#M(F,G),{method:"GET",headers:{accept:"application/x-ndjson"}});for await(let Q of B(J,{maximumBytes:w,sizeLimitError:()=>Error("HTTPStorage scan response exceeded the maximum allowed size.")})){let Z=p(Q);if(Z!==null)yield Z}}async batch(F){M("batch operations",F.length),await this.#Q(this.#Z("/v1/storage/-/batch"),{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({operations:F.map(x)})})}async conditionalBatch(F,G){M("conditionalBatch conditions",F.length),M("conditionalBatch operations",G.length);let J=await this.#Q(this.#Z("/v1/storage/-/conditional-batch"),{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({conditions:F.map(f),operations:G.map(x)})});return l(await J.json())}has(F){return _(this,F)}async*keys(F,G){yield*X(this,F,G)}count(F){return H(this,F)}deletePrefix(F){return L(this,F)}deleteRange(F,G){return U(this,F,V(G))}scoped(F){return m(this,F)}[Symbol.dispose](){}}export{s as HTTPStorage};
package/dist/indexeddb.js CHANGED
@@ -1 +1 @@
1
- async function q(G,J){return await G.get(J)!==null}async function*N(G,J,Q){for await(let[X]of G.scan(J,Q))yield X}async function O(G,J){let Q=0;for await(let X of N(G,J))Q++;return Q}async function R(G,J){let Q=[];for await(let X of N(G,J))Q.push({type:"delete",key:X});if(Q.length===0)return 0;return await G.batch(Q),Q.length}async function h(G,J,Q){let X=[];for await(let Y of N(G,J,Q))X.push({type:"delete",key:Y});if(X.length===0)return 0;return await G.batch(X),X.length}function v(G,J,Q){if(!G.capabilities()[J])throw Error(`Feature "${Q}" requires storage capability "${J}", but this storage backend does not provide it.`)}var A=1e4;class E extends Error{code="StorageBatchOperationLimitExceededError";cap=A;count;target;constructor(G,J){super(`${G} count ${J} exceeds MAX_BATCH_OPERATIONS (${A}).`);this.name="StorageBatchOperationLimitExceededError",this.target=G,this.count=J}}function M(G,J){if(J>A)throw new E(G,J)}function j(G){return G.length>0?G.slice(0,-1)+String.fromCharCode(G.charCodeAt(G.length-1)+1):"ÿ"}function S(G,J={}){if(J.gt!==void 0&&G<=J.gt)return!1;if(J.gte!==void 0&&G<J.gte)return!1;if(J.lt!==void 0&&G>=J.lt)return!1;if(J.lte!==void 0&&G>J.lte)return!1;return!0}function b(G,J){if(G===null||J===null)return G===J;if(G.byteLength!==J.byteLength)return!1;for(let Q=0;Q<G.byteLength;Q++)if(G[Q]!==J[Q])return!1;return!0}async function m(G,J){if(G.has)return G.has(J);return q(G,J)}function x(G,J,Q){if(G.keys)return G.keys(J,Q);return N(G,J,Q)}async function y(G,J){if(G.count)return G.count(J);return O(G,J)}async function I(G,J){if(G.deletePrefix)return G.deletePrefix(J);return R(G,J)}async function g(G,J,Q){if(M("conditionalBatch conditions",J.length),M("conditionalBatch operations",Q.length),v(G,"conditionalBatch","storageConditionalBatch"),!G.conditionalBatch)throw Error("This storage backend reports conditionalBatch capability but does not implement the conditionalBatch() method.");return G.conditionalBatch(J,Q)}function l(G){if(G===void 0)return;if(typeof G!=="number"||!Number.isInteger(G)||G<0)throw Error("deleteRange limit must be a finite non-negative integer");return G===0?0:G}function T(G){let J={},Q=!1;for(let Y of["gt","gte","lt","lte"]){let $=G[Y];if($===void 0)continue;if(typeof $!=="string")throw Error("deleteRange bounds must be strings");J[Y]=$,Q=!0}if(!Q)throw Error("deleteRange requires at least one of gt/gte/lt/lte; use deletePrefix to delete a whole prefix");let X=l(G.limit);if(X!==void 0)J.limit=X;return J}async function k(G,J,Q){let X=T(Q);if(G.deleteRange)return G.deleteRange(J,X);return h(G,J,X)}function p(G,J){let Q={key:G,open:!1};if(J.gte!==void 0&&J.gte>Q.key)Q.key=J.gte,Q.open=!1;if(J.gt!==void 0&&J.gt>=Q.key)Q.key=J.gt,Q.open=!0;return Q}function d(G,J){let Q={key:j(G),open:!0};if(J.lt!==void 0&&J.lt<=Q.key)Q.key=J.lt,Q.open=!0;if(J.lte!==void 0&&J.lte<Q.key)Q.key=J.lte,Q.open=!1;return Q}function c(G,J){let Q=p(G,J),X=d(G,J);if(Q.key>X.key||Q.key===X.key&&(Q.open||X.open))return null;return{lower:Q,upper:X}}function P(G){return G.replaceAll(/:+$/g,"")}function a(G,J){let Q=P(G),X=P(J);if(Q.length===0)return X;if(X.length===0)return Q;return`${Q}:${X}`}class C{#J;#Q;constructor(G,J){this.#J=G,this.#Q=P(J)}#G(G){if(this.#Q.length===0)return G;return G.length===0?`${this.#Q}:`:`${this.#Q}:${G}`}#X(G){if(this.#Q.length===0)return G;return G.slice(this.#Q.length+1)}#Y(G={}){let J={};if(G.limit!==void 0)J.limit=G.limit;if(G.reverse!==void 0)J.reverse=G.reverse;if(G.gt!==void 0)J.gt=this.#G(G.gt);if(G.gte!==void 0)J.gte=this.#G(G.gte);if(G.lt!==void 0)J.lt=this.#G(G.lt);if(G.lte!==void 0)J.lte=this.#G(G.lte);return J}#Z(G){let J={};if(G.limit!==void 0)J.limit=G.limit;if(G.gt!==void 0)J.gt=this.#G(G.gt);if(G.gte!==void 0)J.gte=this.#G(G.gte);if(G.lt!==void 0)J.lt=this.#G(G.lt);if(G.lte!==void 0)J.lte=this.#G(G.lte);return J}capabilities(){return this.#J.capabilities()}scoped(G){return new C(this.#J,a(this.#Q,G))}async get(G){return this.#J.get(this.#G(G))}async put(G,J){await this.#J.put(this.#G(G),J)}async delete(G){await this.#J.delete(this.#G(G))}async*scan(G,J){for await(let[Q,X]of this.#J.scan(this.#G(G),this.#Y(J)))yield[this.#X(Q),X]}async batch(G){M("batch operations",G.length),await this.#J.batch(G.map((J)=>{if(J.type==="put")return{type:"put",key:this.#G(J.key),value:J.value};return{type:"delete",key:this.#G(J.key)}}))}async conditionalBatch(G,J){return g(this.#J,G.map((Q)=>({key:this.#G(Q.key),expectedValue:Q.expectedValue})),J.map((Q)=>{if(Q.type==="put")return{type:"put",key:this.#G(Q.key),value:Q.value};return{type:"delete",key:this.#G(Q.key)}}))}async has(G){return m(this.#J,this.#G(G))}async deletePrefix(G){return I(this.#J,this.#G(G))}async deleteRange(G,J){let Q=this.#Z(T(J));return k(this.#J,this.#G(G),Q)}async*keys(G,J){for await(let Q of x(this.#J,this.#G(G),this.#Y(J)))yield this.#X(Q)}async count(G){return y(this.#J,this.#G(G))}[Symbol.dispose](){this.#J[Symbol.dispose]()}}function u(G,J){return new C(G,J)}var U="kv";function s(G=globalThis){let{indexedDB:J,IDBKeyRange:Q}=G;if(J===void 0||Q===void 0)throw Error("IndexedDBStorage requires both indexedDB and IDBKeyRange runtime globals.");return{indexedDB:J,IDBKeyRange:Q}}function K(G){return new Promise((J,Q)=>{G.onsuccess=()=>J(G.result),G.onerror=()=>Q(G.error)})}function w(G,J){let Q=null,X=null,Y=null,$=(Z,V)=>{let H=Z??Error(V);if(X){let B=X;Q=null,X=null,B(H);return}Y=H};return G.onsuccess=()=>{if(!Q)return;let Z=Q;Q=null,X=null,Z(G.result)},G.onerror=()=>{$(G.error,"IndexedDB cursor request failed.")},J.onerror=()=>{$(J.error,"IndexedDB transaction failed.")},J.onabort=()=>{$(J.error,"IndexedDB transaction aborted.")},()=>{return new Promise((Z,V)=>{if(Y){let H=Y;Y=null,V(H);return}if(Q=Z,X=V,G.readyState==="done"){let H=Q;Q=null,X=null,H?.(G.result)}})}}class n{#J;#Q=null;#G;#X;constructor(G="weft",J=s()){this.#J=G,this.#X=J,this.#G=this.#Y()}capabilities(){return{persistence:"local",readAfterWrite:"linearizable",scanConsistency:"best-effort",atomicBatch:!0,conditionalBatch:!0,boundedRangeDelete:!0}}#Y(){let G=this.#X.indexedDB.open(this.#J,1);return G.onupgradeneeded=()=>{let J=G.result;if(!J.objectStoreNames.contains(U))J.createObjectStore(U)},K(G).then((J)=>{return this.#Q=J,J})}async get(G){let X=(await this.#G).transaction(U,"readonly").objectStore(U),Y=await K(X.get(G));return Y===void 0?null:new Uint8Array(Y)}async put(G,J){let Y=(await this.#G).transaction(U,"readwrite").objectStore(U);await K(Y.put(J,G))}async delete(G){let X=(await this.#G).transaction(U,"readwrite").objectStore(U);await K(X.delete(G))}async has(G){let X=(await this.#G).transaction(U,"readonly").objectStore(U);return await K(X.count(G))>0}async deletePrefix(G){let J=await this.#G,Q=j(G),X=this.#X.IDBKeyRange.bound(G,Q,!1,!0);return new Promise((Y,$)=>{let Z=J.transaction(U,"readwrite"),V=Z.objectStore(U),H=0,B=V.count(X);B.onsuccess=()=>{H=B.result,V.delete(X)},Z.oncomplete=()=>Y(H),Z.onerror=()=>$(Z.error)})}async deleteRange(G,J){let Q=T(J),X=c(G,Q);if(X===null)return 0;let Y=this.#X.IDBKeyRange.bound(X.lower.key,X.upper.key,X.lower.open,X.upper.open),$=await this.#G,{limit:Z}=Q;return new Promise((V,H)=>{let B=$.transaction(U,"readwrite"),z=B.objectStore(U),D=0;if(Z===void 0){let F=z.count(Y);F.onsuccess=()=>{D=F.result,z.delete(Y)}}else{let F=z.openCursor(Y,"next");F.onsuccess=()=>{let W=F.result;if(W===null||D>=Z)return;if(W.delete(),D++,D<Z)W.continue()}}B.oncomplete=()=>V(D),B.onerror=()=>H(B.error)})}async*scan(G,J={}){let{limit:Q,reverse:X}=J,Y=await this.#G,$=j(G),Z=this.#X.IDBKeyRange.bound(G,$,!1,!0),V=X?"prev":"next",H=Y.transaction(U,"readonly"),z=H.objectStore(U).openCursor(Z,V),D=0,F=w(z,H),W=!1;try{let L=await F();while(L){if(Q!==void 0&&D>=Q)break;let _=L.key;if(S(_,J))yield[_,new Uint8Array(L.value)],D++;L.continue(),L=await F()}W=!0}finally{if(!W)try{H.abort()}catch{}}}async batch(G){if(M("batch operations",G.length),G.length===0)return;let J=await this.#G;return new Promise((Q,X)=>{let Y=J.transaction(U,"readwrite"),$=Y.objectStore(U);for(let Z of G)if(Z.type==="put")$.put(Z.value,Z.key);else $.delete(Z.key);Y.oncomplete=()=>Q(),Y.onerror=()=>X(Y.error)})}async conditionalBatch(G,J){M("conditionalBatch conditions",G.length),M("conditionalBatch operations",J.length);let Q=await this.#G;return new Promise((X,Y)=>{let $=Q.transaction(U,"readwrite"),Z=$.objectStore(U),V=!1,H=!1,B=(F,W)=>{if(V)return;V=!0,Y(F??Error(W))};$.oncomplete=()=>{if(V)return;V=!0,X(!0)},$.onerror=()=>{B($.error,"IndexedDB conditionalBatch transaction failed.")},$.onabort=()=>{if(V)return;if(V=!0,H){X(!1);return}Y($.error??Error("IndexedDB conditionalBatch transaction aborted."))};let z=()=>{for(let F of J)if(F.type==="put")Z.put(F.value,F.key);else Z.delete(F.key)},D=(F)=>{if(F>=G.length){z();return}let W=G[F],L=Z.get(W.key);L.onsuccess=()=>{let _=L.result,f=_===void 0?null:new Uint8Array(_);if(!b(f,W.expectedValue)){H=!0,$.abort();return}D(F+1)},L.onerror=()=>{B(L.error,"IndexedDB conditionalBatch condition check failed.")}};D(0)})}async*keys(G,J={}){let{limit:Q,reverse:X}=J,Y=await this.#G,$=j(G),Z=this.#X.IDBKeyRange.bound(G,$,!1,!0),V=X?"prev":"next",H=Y.transaction(U,"readonly"),z=H.objectStore(U).openKeyCursor(Z,V),D=0,F=w(z,H),W=!1;try{let L=await F();while(L){if(Q!==void 0&&D>=Q)break;let _=L.key;if(S(_,J))yield _,D++;L.continue(),L=await F()}W=!0}finally{if(!W)try{H.abort()}catch{}}}async count(G){let J=await this.#G,Q=j(G),Y=J.transaction(U,"readonly").objectStore(U);return K(Y.count(this.#X.IDBKeyRange.bound(G,Q,!1,!0)))}scoped(G){return u(this,G)}[Symbol.dispose](){if(this.#Q)this.#Q.close(),this.#Q=null}}export{n as IndexedDBStorage};
1
+ async function q(G,J){return await G.get(J)!==null}async function*N(G,J,Q){for await(let[X]of G.scan(J,Q))yield X}async function O(G,J){let Q=0;for await(let X of N(G,J))Q++;return Q}async function R(G,J){let Q=[];for await(let X of N(G,J))Q.push({type:"delete",key:X});if(Q.length===0)return 0;return await G.batch(Q),Q.length}async function h(G,J,Q){let X=[];for await(let Y of N(G,J,Q))X.push({type:"delete",key:Y});if(X.length===0)return 0;return await G.batch(X),X.length}function v(G,J,Q){if(!G.capabilities()[J])throw Error(`Feature "${Q}" requires storage capability "${J}", but this storage backend does not provide it.`)}var A=1e4;class E extends Error{code="StorageBatchOperationLimitExceededError";cap=A;count;target;constructor(G,J){super(`${G} count ${J} exceeds MAX_BATCH_OPERATIONS (${A}).`);this.name="StorageBatchOperationLimitExceededError",this.target=G,this.count=J}}function K(G,J){if(J>A)throw new E(G,J)}function M(G){return G.length>0?G.slice(0,-1)+String.fromCharCode(G.charCodeAt(G.length-1)+1):"ÿ"}function S(G,J={}){if(J.gt!==void 0&&G<=J.gt)return!1;if(J.gte!==void 0&&G<J.gte)return!1;if(J.lt!==void 0&&G>=J.lt)return!1;if(J.lte!==void 0&&G>J.lte)return!1;return!0}function b(G,J){if(G===null||J===null)return G===J;if(G.byteLength!==J.byteLength)return!1;for(let Q=0;Q<G.byteLength;Q++)if(G[Q]!==J[Q])return!1;return!0}async function m(G,J){if(G.has)return G.has(J);return q(G,J)}function x(G,J,Q){if(G.keys)return G.keys(J,Q);return N(G,J,Q)}async function y(G,J){if(G.count)return G.count(J);return O(G,J)}async function I(G,J){if(G.deletePrefix)return G.deletePrefix(J);return R(G,J)}async function g(G,J,Q){if(K("conditionalBatch conditions",J.length),K("conditionalBatch operations",Q.length),v(G,"conditionalBatch","storageConditionalBatch"),!G.conditionalBatch)throw Error("This storage backend reports conditionalBatch capability but does not implement the conditionalBatch() method.");return G.conditionalBatch(J,Q)}function l(G){if(G===void 0)return;if(typeof G!=="number"||!Number.isInteger(G)||G<0)throw Error("deleteRange limit must be a finite non-negative integer");return G===0?0:G}function T(G){let J={},Q=!1;for(let Y of["gt","gte","lt","lte"]){let $=G[Y];if($===void 0)continue;if(typeof $!=="string")throw Error("deleteRange bounds must be strings");J[Y]=$,Q=!0}if(!Q)throw Error("deleteRange requires at least one of gt/gte/lt/lte; use deletePrefix to delete a whole prefix");let X=l(G.limit);if(X!==void 0)J.limit=X;return J}async function k(G,J,Q){let X=T(Q);if(G.deleteRange)return G.deleteRange(J,X);return h(G,J,X)}function p(G,J){let Q={key:G,open:!1};if(J.gte!==void 0&&J.gte>Q.key)Q.key=J.gte,Q.open=!1;if(J.gt!==void 0&&J.gt>=Q.key)Q.key=J.gt,Q.open=!0;return Q}function d(G,J){let Q={key:M(G),open:!0};if(J.lt!==void 0&&J.lt<=Q.key)Q.key=J.lt,Q.open=!0;if(J.lte!==void 0&&J.lte<Q.key)Q.key=J.lte,Q.open=!1;return Q}function c(G,J){let Q=p(G,J),X=d(G,J);if(Q.key>X.key||Q.key===X.key&&(Q.open||X.open))return null;return{lower:Q,upper:X}}function P(G){return G.replaceAll(/:+$/g,"")}function a(G,J){let Q=P(G),X=P(J);if(Q.length===0)return X;if(X.length===0)return Q;return`${Q}:${X}`}class C{#J;#Q;constructor(G,J){this.#J=G,this.#Q=P(J)}#G(G){if(this.#Q.length===0)return G;return G.length===0?`${this.#Q}:`:`${this.#Q}:${G}`}#X(G){return G.map((J)=>{if(J.type==="put")return{type:"put",key:this.#G(J.key),value:J.value};return{type:"delete",key:this.#G(J.key)}})}#Y(G){if(this.#Q.length===0)return G;return G.slice(this.#Q.length+1)}#Z(G){let J={};if(G.limit!==void 0)J.limit=G.limit;if(G.gt!==void 0)J.gt=this.#G(G.gt);if(G.gte!==void 0)J.gte=this.#G(G.gte);if(G.lt!==void 0)J.lt=this.#G(G.lt);if(G.lte!==void 0)J.lte=this.#G(G.lte);return J}#$(G={}){let J=this.#Z(G);if(G.reverse!==void 0)J.reverse=G.reverse;return J}#U(G){return this.#Z(G)}capabilities(){return this.#J.capabilities()}scoped(G){return new C(this.#J,a(this.#Q,G))}async get(G){return this.#J.get(this.#G(G))}async put(G,J){await this.#J.put(this.#G(G),J)}async delete(G){await this.#J.delete(this.#G(G))}async*scan(G,J){for await(let[Q,X]of this.#J.scan(this.#G(G),this.#$(J)))yield[this.#Y(Q),X]}async batch(G){K("batch operations",G.length),await this.#J.batch(this.#X(G))}async conditionalBatch(G,J){return g(this.#J,G.map((Q)=>({key:this.#G(Q.key),expectedValue:Q.expectedValue})),this.#X(J))}async has(G){return m(this.#J,this.#G(G))}async deletePrefix(G){return I(this.#J,this.#G(G))}async deleteRange(G,J){let Q=this.#U(T(J));return k(this.#J,this.#G(G),Q)}async*keys(G,J){for await(let Q of x(this.#J,this.#G(G),this.#$(J)))yield this.#Y(Q)}async count(G){return y(this.#J,this.#G(G))}[Symbol.dispose](){this.#J[Symbol.dispose]()}}function u(G,J){return new C(G,J)}var U="kv";function s(G=globalThis){let{indexedDB:J,IDBKeyRange:Q}=G;if(J===void 0||Q===void 0)throw Error("IndexedDBStorage requires both indexedDB and IDBKeyRange runtime globals.");return{indexedDB:J,IDBKeyRange:Q}}function j(G){return new Promise((J,Q)=>{G.onsuccess=()=>J(G.result),G.onerror=()=>Q(G.error)})}function w(G,J){let Q=null,X=null,Y=null,$=(Z,V)=>{let H=Z??Error(V);if(X){let B=X;Q=null,X=null,B(H);return}Y=H};return G.onsuccess=()=>{if(!Q)return;let Z=Q;Q=null,X=null,Z(G.result)},G.onerror=()=>{$(G.error,"IndexedDB cursor request failed.")},J.onerror=()=>{$(J.error,"IndexedDB transaction failed.")},J.onabort=()=>{$(J.error,"IndexedDB transaction aborted.")},()=>{return new Promise((Z,V)=>{if(Y){let H=Y;Y=null,V(H);return}if(Q=Z,X=V,G.readyState==="done"){let H=Q;Q=null,X=null,H?.(G.result)}})}}class n{#J;#Q=null;#G;#X;constructor(G="weft",J=s()){this.#J=G,this.#X=J,this.#G=this.#Y()}capabilities(){return{persistence:"local",readAfterWrite:"linearizable",scanConsistency:"best-effort",atomicBatch:!0,conditionalBatch:!0,boundedRangeDelete:!0}}#Y(){let G=this.#X.indexedDB.open(this.#J,1);return G.onupgradeneeded=()=>{let J=G.result;if(!J.objectStoreNames.contains(U))J.createObjectStore(U)},j(G).then((J)=>{return this.#Q=J,J})}async get(G){let X=(await this.#G).transaction(U,"readonly").objectStore(U),Y=await j(X.get(G));return Y===void 0?null:new Uint8Array(Y)}async put(G,J){let Y=(await this.#G).transaction(U,"readwrite").objectStore(U);await j(Y.put(J,G))}async delete(G){let X=(await this.#G).transaction(U,"readwrite").objectStore(U);await j(X.delete(G))}async has(G){let X=(await this.#G).transaction(U,"readonly").objectStore(U);return await j(X.count(G))>0}async deletePrefix(G){let J=await this.#G,Q=M(G),X=this.#X.IDBKeyRange.bound(G,Q,!1,!0);return new Promise((Y,$)=>{let Z=J.transaction(U,"readwrite"),V=Z.objectStore(U),H=0,B=V.count(X);B.onsuccess=()=>{H=B.result,V.delete(X)},Z.oncomplete=()=>Y(H),Z.onerror=()=>$(Z.error)})}async deleteRange(G,J){let Q=T(J),X=c(G,Q);if(X===null)return 0;let Y=this.#X.IDBKeyRange.bound(X.lower.key,X.upper.key,X.lower.open,X.upper.open),$=await this.#G,{limit:Z}=Q;return new Promise((V,H)=>{let B=$.transaction(U,"readwrite"),z=B.objectStore(U),D=0;if(Z===void 0){let F=z.count(Y);F.onsuccess=()=>{D=F.result,z.delete(Y)}}else{let F=z.openCursor(Y,"next");F.onsuccess=()=>{let W=F.result;if(W===null||D>=Z)return;if(W.delete(),D++,D<Z)W.continue()}}B.oncomplete=()=>V(D),B.onerror=()=>H(B.error)})}async*scan(G,J={}){let{limit:Q,reverse:X}=J,Y=await this.#G,$=M(G),Z=this.#X.IDBKeyRange.bound(G,$,!1,!0),V=X?"prev":"next",H=Y.transaction(U,"readonly"),z=H.objectStore(U).openCursor(Z,V),D=0,F=w(z,H),W=!1;try{let L=await F();while(L){if(Q!==void 0&&D>=Q)break;let _=L.key;if(S(_,J))yield[_,new Uint8Array(L.value)],D++;L.continue(),L=await F()}W=!0}finally{if(!W)try{H.abort()}catch{}}}async batch(G){if(K("batch operations",G.length),G.length===0)return;let J=await this.#G;return new Promise((Q,X)=>{let Y=J.transaction(U,"readwrite"),$=Y.objectStore(U);for(let Z of G)if(Z.type==="put")$.put(Z.value,Z.key);else $.delete(Z.key);Y.oncomplete=()=>Q(),Y.onerror=()=>X(Y.error)})}async conditionalBatch(G,J){K("conditionalBatch conditions",G.length),K("conditionalBatch operations",J.length);let Q=await this.#G;return new Promise((X,Y)=>{let $=Q.transaction(U,"readwrite"),Z=$.objectStore(U),V=!1,H=!1,B=(F,W)=>{if(V)return;V=!0,Y(F??Error(W))};$.oncomplete=()=>{if(V)return;V=!0,X(!0)},$.onerror=()=>{B($.error,"IndexedDB conditionalBatch transaction failed.")},$.onabort=()=>{if(V)return;if(V=!0,H){X(!1);return}Y($.error??Error("IndexedDB conditionalBatch transaction aborted."))};let z=()=>{for(let F of J)if(F.type==="put")Z.put(F.value,F.key);else Z.delete(F.key)},D=(F)=>{if(F>=G.length){z();return}let W=G[F],L=Z.get(W.key);L.onsuccess=()=>{let _=L.result,f=_===void 0?null:new Uint8Array(_);if(!b(f,W.expectedValue)){H=!0,$.abort();return}D(F+1)},L.onerror=()=>{B(L.error,"IndexedDB conditionalBatch condition check failed.")}};D(0)})}async*keys(G,J={}){let{limit:Q,reverse:X}=J,Y=await this.#G,$=M(G),Z=this.#X.IDBKeyRange.bound(G,$,!1,!0),V=X?"prev":"next",H=Y.transaction(U,"readonly"),z=H.objectStore(U).openKeyCursor(Z,V),D=0,F=w(z,H),W=!1;try{let L=await F();while(L){if(Q!==void 0&&D>=Q)break;let _=L.key;if(S(_,J))yield _,D++;L.continue(),L=await F()}W=!0}finally{if(!W)try{H.abort()}catch{}}}async count(G){let J=await this.#G,Q=M(G),Y=J.transaction(U,"readonly").objectStore(U);return j(Y.count(this.#X.IDBKeyRange.bound(G,Q,!1,!0)))}scoped(G){return u(this,G)}[Symbol.dispose](){if(this.#Q)this.#Q.close(),this.#Q=null}}export{n as IndexedDBStorage};
@@ -3,6 +3,7 @@ import { WeftError } from "../core/weft-error.js";
3
3
  import { MCP_TOOLS_LIST_CHANGED_NOTIFICATION, requestIdKey } from "./protocol.js";
4
4
  import { isWorkflowSearchResourceUri, workflowResourceUri } from "./resources.js";
5
5
  import { listMcpTools } from "./tools.js";
6
+ import { tupleListsEqual } from "./tuple-list-equality.js";
6
7
 
7
8
  export class McpSessionLimitExceededError extends WeftError {
8
9
  constructor() {
@@ -243,16 +244,7 @@ function toolListSignature(engine) {
243
244
  ]);
244
245
  }
245
246
  function toolListSignaturesEqual(left, right) {
246
- if (left.length !== right.length)
247
- return !1;
248
- for (let index = 0;index < left.length; index++) {
249
- const leftEntry = left[index], rightEntry = right[index];
250
- if (leftEntry === void 0 || rightEntry === void 0)
251
- return !1;
252
- if (leftEntry[0] !== rightEntry[0] || leftEntry[1] !== rightEntry[1] || leftEntry[2] !== rightEntry[2] || leftEntry[3] !== rightEntry[3])
253
- return !1;
254
- }
255
- return !0;
247
+ return tupleListsEqual(left, right, (leftEntry, rightEntry) => leftEntry[0] === rightEntry[0] && leftEntry[1] === rightEntry[1] && leftEntry[2] === rightEntry[2] && leftEntry[3] === rightEntry[3]);
256
248
  }
257
249
  export function createMcpSessionManager(engine, options) {
258
250
  return new McpSessionManager(engine, options);
package/dist/mcp/tools.js CHANGED
@@ -8,6 +8,7 @@ import {
8
8
  listVisibleWorkflows
9
9
  } from "./access.js";
10
10
  import { parseMcpListFilter } from "./list-filter.js";
11
+ import { tupleListsEqual } from "./tuple-list-equality.js";
11
12
  const toolRegistryCache = new WeakMap, DEFAULT_WORKFLOW_TOOL_TIMEOUT_MS = 30000, MAX_WORKFLOW_TOOL_TIMEOUT_MS = 2147483647;
12
13
  export function listMcpTools(engine) {
13
14
  return getToolRegistry(engine).tools.map((tool) => tool.definition);
@@ -44,16 +45,7 @@ function toolRegistrySignature(engine) {
44
45
  return engine.listWorkflowDefinitions().toSorted((left, right) => left.type < right.type ? -1 : left.type > right.type ? 1 : 0).map((definition) => [definition.type, definition.description, definition.inputSchema]);
45
46
  }
46
47
  function toolRegistrySignaturesEqual(left, right) {
47
- if (left.length !== right.length)
48
- return !1;
49
- for (let index = 0;index < left.length; index++) {
50
- const leftEntry = left[index], rightEntry = right[index];
51
- if (leftEntry === void 0 || rightEntry === void 0)
52
- return !1;
53
- if (leftEntry[0] !== rightEntry[0] || leftEntry[1] !== rightEntry[1] || leftEntry[2] !== rightEntry[2])
54
- return !1;
55
- }
56
- return !0;
48
+ return tupleListsEqual(left, right, (leftEntry, rightEntry) => leftEntry[0] === rightEntry[0] && leftEntry[1] === rightEntry[1] && leftEntry[2] === rightEntry[2]);
57
49
  }
58
50
  function buildToolImplementations(engine) {
59
51
  const tools = [...builtInTools()], usedNames = new Set(tools.map((tool) => tool.definition.name));
@@ -0,0 +1 @@
1
+ export declare function tupleListsEqual<Tuple extends readonly unknown[]>(left: ReadonlyArray<Tuple>, right: ReadonlyArray<Tuple>, entryEqual: (leftEntry: Tuple, rightEntry: Tuple) => boolean): boolean;
@@ -0,0 +1,12 @@
1
+ export function tupleListsEqual(left, right, entryEqual) {
2
+ if (left.length !== right.length)
3
+ return !1;
4
+ for (let index = 0;index < left.length; index++) {
5
+ const leftEntry = left[index], rightEntry = right[index];
6
+ if (leftEntry === void 0 || rightEntry === void 0)
7
+ return !1;
8
+ if (!entryEqual(leftEntry, rightEntry))
9
+ return !1;
10
+ }
11
+ return !0;
12
+ }
@@ -16,7 +16,7 @@
16
16
  * the exact scope they require. Scope-implication semantics (if any) are a
17
17
  * future track.
18
18
  */
19
- export declare const AUTHORIZATION_SCOPES: readonly ["workflows:read", "workflows:write", "workflows:admin", "schedules:read", "schedules:write", "signals:write", "updates:write", "queries:read", "reviews:read", "reviews:write", "attributes:read", "attributes:write", "tags:write", "streams:read", "events:read", "budget:read", "budget:write", "storage:read", "storage:write", "storage:admin", "workers:write", "system:read", "system:admin"];
19
+ export declare const AUTHORIZATION_SCOPES: readonly ["workflows:read", "workflows:write", "workflows:admin", "schedules:read", "schedules:write", "signals:write", "updates:write", "queries:read", "reviews:read", "reviews:write", "attributes:read", "attributes:write", "tags:write", "streams:read", "events:read", "storage:read", "storage:write", "storage:admin", "workers:write", "system:read", "system:admin"];
20
20
  /** String-literal union of every authorization scope. */
21
21
  export type AuthorizationScope = (typeof AUTHORIZATION_SCOPES)[number];
22
22
  /** Runtime type guard for the `AuthorizationScope` union. */
@@ -14,8 +14,6 @@ export const AUTHORIZATION_SCOPES = [
14
14
  "tags:write",
15
15
  "streams:read",
16
16
  "events:read",
17
- "budget:read",
18
- "budget:write",
19
17
  "storage:read",
20
18
  "storage:write",
21
19
  "storage:admin",
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Static files belonging to an externally supplied dashboard.
3
+ *
4
+ * `prefix` is mounted as `${prefix}/*`; `directory` must exist before
5
+ * `serve()` is called.
6
+ *
7
+ * @example
8
+ * ```ts
9
+ * import type { DashboardAssets } from '@lostgradient/weft/server';
10
+ *
11
+ * const dashboardAssets: DashboardAssets = {
12
+ * prefix: '/assets',
13
+ * directory: './dist/assets',
14
+ * };
15
+ * ```
16
+ */
17
+ export interface DashboardAssets {
18
+ prefix: string;
19
+ directory: string;
20
+ }
21
+ export interface ResolvedDashboardAssets {
22
+ prefix: string;
23
+ directory: string;
24
+ }
25
+ export declare function resolveDashboardAssets(value: unknown, pageRoutes: readonly string[]): ResolvedDashboardAssets;
26
+ export declare function createDashboardAssetRoute(assets: ResolvedDashboardAssets): Partial<Record<'GET' | 'HEAD', (request: Request) => Response>>;
@@ -0,0 +1,95 @@
1
+ import { realpathSync, statSync } from "node:fs";
2
+ import { isAbsolute, join, relative, resolve, sep } from "node:path";
3
+ const ROOT_API_PREFIXES = ["/api", "/v1"], DISCOVERY_DOCUMENTS = [
4
+ "/openapi.json",
5
+ "/openrpc.json",
6
+ "/asyncapi.json",
7
+ "/.well-known/mcp.json"
8
+ ];
9
+ function hasPathPrefix(path, prefix) {
10
+ return path === prefix || path.startsWith(`${prefix}/`);
11
+ }
12
+ function overlapsDashboardPageRoute(prefix, route) {
13
+ if (route.endsWith("/*")) {
14
+ const base = route.slice(0, -2);
15
+ return prefix === base || prefix.startsWith(`${base}/`);
16
+ }
17
+ return prefix === route;
18
+ }
19
+ function hasInvalidPrefixShape(prefix) {
20
+ return prefix.length === 0 || prefix === "/" || !prefix.startsWith("/") || prefix.endsWith("/") || ["?", "#", "%", "*", ":", "\\"].some((character) => prefix.includes(character));
21
+ }
22
+ function validateAssetPrefixShape(prefix) {
23
+ if (hasInvalidPrefixShape(prefix))
24
+ throw Error("dashboardAssets.prefix must be an absolute path prefix without a trailing slash, wildcard, parameter, query, fragment, percent escape, or backslash");
25
+ if (prefix.split("/").slice(1).some((segment) => segment.length === 0 || segment === "." || segment === ".."))
26
+ throw Error("dashboardAssets.prefix must contain only concrete, non-empty path segments");
27
+ }
28
+ function validateAssetPrefixReservations(prefix, pageRoutes) {
29
+ if (ROOT_API_PREFIXES.some((reserved) => hasPathPrefix(prefix, reserved)))
30
+ throw Error("dashboardAssets.prefix must not overlap the /api or /v1 route spaces");
31
+ if (pageRoutes.some((route) => overlapsDashboardPageRoute(prefix, route)))
32
+ throw Error("dashboardAssets.prefix must not overlap dashboard page routes");
33
+ if (DISCOVERY_DOCUMENTS.some((document) => document === prefix))
34
+ throw Error("dashboardAssets.prefix must not overlap a discovery document route");
35
+ }
36
+ function validateAssetPrefix(prefix, pageRoutes) {
37
+ validateAssetPrefixShape(prefix);
38
+ validateAssetPrefixReservations(prefix, pageRoutes);
39
+ }
40
+ export function resolveDashboardAssets(value, pageRoutes) {
41
+ if (typeof value !== "object" || value === null || Array.isArray(value))
42
+ throw Error("dashboardAssets must be an object with prefix and directory strings");
43
+ if (!("prefix" in value) || !("directory" in value))
44
+ throw Error("dashboardAssets.prefix and dashboardAssets.directory must be strings");
45
+ const { prefix, directory } = value;
46
+ if (typeof prefix !== "string" || typeof directory !== "string")
47
+ throw Error("dashboardAssets.prefix and dashboardAssets.directory must be strings");
48
+ validateAssetPrefix(prefix, pageRoutes);
49
+ const resolvedDirectory = resolve(directory);
50
+ let directoryStats;
51
+ try {
52
+ directoryStats = statSync(resolvedDirectory);
53
+ } catch {
54
+ throw Error(`dashboardAssets.directory does not exist: ${directory}`);
55
+ }
56
+ if (!directoryStats.isDirectory())
57
+ throw Error(`dashboardAssets.directory must be a directory: ${directory}`);
58
+ return { prefix, directory: realpathSync(resolvedDirectory) };
59
+ }
60
+ function isWithinDirectory(directory, path) {
61
+ const pathRelativeToDirectory = relative(directory, path);
62
+ return pathRelativeToDirectory.length > 0 && pathRelativeToDirectory !== ".." && !pathRelativeToDirectory.startsWith(`..${sep}`) && !isAbsolute(pathRelativeToDirectory);
63
+ }
64
+ function assetResponse(directory, prefix, request) {
65
+ const rawWildcardPath = new URL(request.url).pathname.slice(prefix.length + 1);
66
+ let wildcardPath;
67
+ try {
68
+ wildcardPath = decodeURIComponent(rawWildcardPath);
69
+ } catch {
70
+ return new Response("Not Found", { status: 404 });
71
+ }
72
+ if (wildcardPath.length === 0)
73
+ return new Response("Not Found", { status: 404 });
74
+ const pathSegments = wildcardPath.split(/[\\/]/);
75
+ if (pathSegments.some((segment) => segment === ".." || segment.length === 0))
76
+ return new Response("Not Found", { status: 404 });
77
+ const assetPath = resolve(join(directory, ...pathSegments));
78
+ if (!isWithinDirectory(directory, assetPath))
79
+ return new Response("Not Found", { status: 404 });
80
+ let realAssetPath;
81
+ try {
82
+ realAssetPath = realpathSync(assetPath);
83
+ if (!statSync(realAssetPath).isFile())
84
+ return new Response("Not Found", { status: 404 });
85
+ } catch {
86
+ return new Response("Not Found", { status: 404 });
87
+ }
88
+ if (!isWithinDirectory(directory, realAssetPath))
89
+ return new Response("Not Found", { status: 404 });
90
+ return new Response(Bun.file(realAssetPath));
91
+ }
92
+ export function createDashboardAssetRoute(assets) {
93
+ const handler = (request) => assetResponse(assets.directory, assets.prefix, request);
94
+ return { GET: handler, HEAD: handler };
95
+ }
@@ -10,11 +10,13 @@ import type { PrometheusExporter } from '../observability/metrics.ts';
10
10
  import type { RoutingPolicy } from '../worker/registry.ts';
11
11
  import { WorkerRegistry } from '../worker/registry.ts';
12
12
  import type { AuthConfig, RateLimitConfig } from './authentication.ts';
13
+ import { type DashboardAssets } from './dashboard-assets.ts';
13
14
  import type { DiscoveryInfo } from './discovery-info.ts';
14
15
  import type { CorsOptions } from './runtime/cors.ts';
15
16
  import type { SchedulingPolicy } from './task-queue-types.ts';
16
17
  import { TaskQueue } from './task-queue.ts';
17
18
  export { wireEventBroadcasting, type EventBroadcastingHandle, } from './runtime/event-broadcasting.ts';
19
+ export type { DashboardAssets } from './dashboard-assets.ts';
18
20
  export type { CorsOptions } from './runtime/cors.ts';
19
21
  export { createRateLimiter, createRotatingApiKeyStore, defaultAuthAuditSink, isSensitiveHeader, redactCredential, redactHeaders, validateRateLimitConfig, type ApiKeyRegistration, type AuthAuditEvent, type AuthAuditSink, type AuthConfig, type RateLimitConfig, type RateLimitDecision, type RateLimiter, type RotatingApiKeyStore, } from './authentication.ts';
20
22
  export type { RetryPolicy } from '../core/types.ts';
@@ -32,6 +34,8 @@ export { TaskQueue } from './task-queue.ts';
32
34
  * They are intentionally specific (no blanket `/*`) so they cannot shadow the
33
35
  * API served under the `/api` prefix or the root-stable discovery endpoints —
34
36
  * those fall through to the `fetch` handler.
37
+ * The supported page routes are `/`, `/workflows`, `/workflows/*`, `/reviews`,
38
+ * `/workers`, `/schedules`, `/storage`, and `/system`.
35
39
  *
36
40
  * @example
37
41
  * ```ts
@@ -42,7 +46,7 @@ export { TaskQueue } from './task-queue.ts';
42
46
  * console.log(DASHBOARD_PAGE_ROUTES.includes('/workflows')); // true
43
47
  * ```
44
48
  */
45
- export declare const DASHBOARD_PAGE_ROUTES: readonly ["/", "/workflows", "/workflows/*", "/reviews", "/workers"];
49
+ export declare const DASHBOARD_PAGE_ROUTES: readonly ["/", "/workflows", "/workflows/*", "/reviews", "/workers", "/schedules", "/storage", "/system"];
46
50
  /**
47
51
  * Route pattern owned by an externally supplied dashboard shell.
48
52
  *
@@ -129,6 +133,11 @@ export interface ServeOptions {
129
133
  development?: boolean;
130
134
  /** Optional external dashboard shell served at {@link DASHBOARD_PAGE_ROUTES}. */
131
135
  dashboard?: DashboardRouteTarget;
136
+ /**
137
+ * Static files served below an explicit prefix for the supplied dashboard.
138
+ * The directory and prefix are validated synchronously before the port binds.
139
+ */
140
+ dashboardAssets?: DashboardAssets;
132
141
  /** Authentication configuration. When provided, all non-public endpoints require valid credentials. */
133
142
  auth?: AuthConfig;
134
143
  /**
@@ -1,4 +1,8 @@
1
1
  import { WorkerRegistry } from "../worker/registry.js";
2
+ import {
3
+ createDashboardAssetRoute,
4
+ resolveDashboardAssets
5
+ } from "./dashboard-assets.js";
2
6
  import { createServerWebSocketHandlers } from "./runtime/authentication-bridge.js";
3
7
  import {
4
8
  wireEventBroadcasting
@@ -39,13 +43,18 @@ export const DASHBOARD_PAGE_ROUTES = [
39
43
  "/workflows",
40
44
  "/workflows/*",
41
45
  "/reviews",
42
- "/workers"
46
+ "/workers",
47
+ "/schedules",
48
+ "/storage",
49
+ "/system"
43
50
  ];
44
51
  export function serve(options) {
45
- const { port, hostname, development, tlsOptions, serverOptions, serverMetricsCollector } = resolveNetworkConfig(options), context = buildServerContext(serverOptions, serverMetricsCollector), boundCleanup = (operationId) => cleanupWorkflowIndex(context, operationId), routes = {};
52
+ const dashboardAssets = options.dashboardAssets === void 0 ? void 0 : resolveDashboardAssets(options.dashboardAssets, DASHBOARD_PAGE_ROUTES), { port, hostname, development, tlsOptions, serverOptions, serverMetricsCollector } = resolveNetworkConfig(options), context = buildServerContext(serverOptions, serverMetricsCollector), boundCleanup = (operationId) => cleanupWorkflowIndex(context, operationId), routes = {};
46
53
  if (options.dashboard != null)
47
54
  for (const path of DASHBOARD_PAGE_ROUTES)
48
55
  routes[path] = options.dashboard;
56
+ if (dashboardAssets !== void 0)
57
+ routes[`${dashboardAssets.prefix}/*`] = createDashboardAssetRoute(dashboardAssets);
49
58
  const serverHolder = { current: null }, server = Bun.serve(buildBunServeConfig(port, hostname, development, routes, tlsOptions, buildFetchHandler(serverHolder, context, serverOptions), createServerWebSocketHandlers(context, serverOptions, boundCleanup)));
50
59
  serverHolder.current = server;
51
60
  const stack = new AsyncDisposableStack;
@@ -1,4 +1,5 @@
1
1
  export const INTERACTIVE_OPERATION_NAMES = [
2
+ "weft.alerts.list",
2
3
  "weft.workflows.start",
3
4
  "weft.workflows.get",
4
5
  "weft.workflows.scheduleprovenance.get",
@@ -99,7 +99,7 @@ export type OperationContext<Input> = {
99
99
  * Common operation fields shared by unary, stream, and subscription kinds.
100
100
  * The discriminated union below adds `kind` and `eventSchema` per kind.
101
101
  */
102
- type OperationDefinitionBase<Input, Output, Element = unknown> = {
102
+ export type OperationDefinitionBase<Input, Output, Element = unknown> = {
103
103
  readonly name: string;
104
104
  readonly mcpExposable: boolean;
105
105
  readonly mcpTool?: McpToolMetadata;
@@ -17,9 +17,7 @@
17
17
  * cast required at the registry boundary.
18
18
  */
19
19
  import type { z } from 'zod';
20
- import type { FaultCode } from '../core/fault-code.ts';
21
- import type { AccessPolicy } from './authorization.ts';
22
- import type { AuthorizationDecision, McpToolMetadata, OperationContext, OperationDefinition, OperationInvocationResult, ParameterizedAccessHint, TransportAvailability, UnknownKeyPolicy } from './operation-catalog/types.ts';
20
+ import type { OperationDefinition, OperationDefinitionBase } from './operation-catalog/types.ts';
23
21
  export { isValidOperationName, validateOperationName } from './operation-catalog.ts';
24
22
  /**
25
23
  * Input shape for `defineOperation`. Mirrors `OperationDefinition` but
@@ -31,35 +29,8 @@ export { isValidOperationName, validateOperationName } from './operation-catalog
31
29
  * passes. When present, BOTH `access` AND `authorize` must permit the
32
30
  * call: the policy runs first, then the parameter-aware hook.
33
31
  */
34
- type OperationDefinitionInputBase<Input, Output, Element = unknown> = {
35
- readonly name: string;
36
- readonly mcpExposable: boolean;
37
- readonly mcpTool?: McpToolMetadata;
38
- readonly summary: string;
39
- /**
40
- * Optional longer-form prose. See {@link OperationDefinition} for the
41
- * contract and the surfaces (OpenAPI, OpenRPC, CLI `--describe`, MCP
42
- * discovery) that read it. `summary` stays mandatory and short.
43
- */
44
- readonly description?: string;
32
+ type OperationDefinitionInputBase<Input, Output, Element = unknown> = Omit<OperationDefinitionBase<Input, Output, Element>, 'tags'> & {
45
33
  readonly tags?: ReadonlyArray<string>;
46
- /**
47
- * Whether invoking this operation irreversibly mutates state. Required —
48
- * there is deliberately no default, so every operation author must make
49
- * the call explicitly. See {@link OperationDefinition} for the contract
50
- * and the consumers that read it.
51
- */
52
- readonly destructive: boolean;
53
- readonly inputSchema: z.ZodType<Input>;
54
- readonly outputSchema: z.ZodType<Output>;
55
- readonly access: AccessPolicy;
56
- readonly parameterizedAccess?: ParameterizedAccessHint;
57
- readonly producibleFaults?: ReadonlyArray<FaultCode>;
58
- readonly discoverable?: boolean;
59
- readonly transports: TransportAvailability;
60
- readonly unknownKeyPolicy: UnknownKeyPolicy;
61
- readonly authorize?: (context: OperationContext<Input>) => Promise<AuthorizationDecision>;
62
- readonly invoke: (context: OperationContext<Input>) => Promise<OperationInvocationResult<Output, Element>>;
63
34
  };
64
35
  /**
65
36
  * Discriminated input for `defineOperation`, mirroring the discriminated