@lostgradient/weft 0.16.0 → 0.17.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 +1 -1
  2. package/dist/alerting/alert-manager.d.ts +5 -5
  3. package/dist/alerting/alert-manager.js +24 -6
  4. package/dist/alerting/index.d.ts +1 -1
  5. package/dist/alerting/types.d.ts +17 -13
  6. package/dist/alerting/types.js +5 -0
  7. package/dist/core/engine/disposal.js +7 -0
  8. package/dist/core/engine/engine-runtime-helpers.d.ts +3 -1
  9. package/dist/core/engine/engine-runtime-helpers.js +2 -2
  10. package/dist/core/engine/errors.d.ts +20 -0
  11. package/dist/core/engine/errors.js +8 -0
  12. package/dist/core/engine/index.d.ts +39 -17
  13. package/dist/core/engine/index.js +81 -9
  14. package/dist/core/engine/inline-launch-queue.d.ts +6 -2
  15. package/dist/core/engine/inline-launch-queue.js +41 -6
  16. package/dist/core/engine/internals.d.ts +2 -0
  17. package/dist/core/engine/lease-manager.d.ts +3 -2
  18. package/dist/core/engine/lease-manager.js +7 -3
  19. package/dist/core/engine/operations-time.js +6 -0
  20. package/dist/core/execution-strategy.d.ts +8 -0
  21. package/dist/core/inline-execution-strategy.d.ts +1 -0
  22. package/dist/core/inline-execution-strategy.js +33 -20
  23. package/dist/core/weft-error.d.ts +1 -1
  24. package/dist/core/weft-error.js +1 -0
  25. package/dist/core/worker-execution-strategy.d.ts +2 -0
  26. package/dist/core/worker-execution-strategy.js +7 -2
  27. package/dist/core/worker-turn-watchdog.d.ts +4 -1
  28. package/dist/core/worker-turn-watchdog.js +9 -4
  29. package/dist/index.d.ts +2 -2
  30. package/dist/index.js +1 -0
  31. package/dist/indexeddb.js +1 -1
  32. package/dist/json-schema.js +1 -1
  33. package/dist/mcp/tools.js +1 -3
  34. package/dist/server/dashboard-assets.d.ts +12 -1
  35. package/dist/server/dashboard-assets.js +145 -31
  36. package/dist/server/operations/aggregate-workflows.js +0 -7
  37. package/dist/server/operations/async-activity.js +5 -24
  38. package/dist/server/operations/bulk-cancel-workflows.js +3 -8
  39. package/dist/server/operations/bulk-delete-workflows.js +3 -8
  40. package/dist/server/operations/bulk-mutate-workflow-tags.js +3 -8
  41. package/dist/server/operations/bulk-operation-helpers.d.ts +2 -10
  42. package/dist/server/operations/bulk-operation-helpers.js +0 -6
  43. package/dist/server/operations/bulk-retry-failed-workflows.js +3 -8
  44. package/dist/server/operations/bulk-signal-workflows.js +3 -8
  45. package/dist/server/operations/cancel-schedule.js +1 -3
  46. package/dist/server/operations/cancel-workflow.js +1 -3
  47. package/dist/server/operations/create-schedule.js +2 -3
  48. package/dist/server/operations/fork-workflow.js +2 -3
  49. package/dist/server/operations/get-checkpoint-at.js +1 -6
  50. package/dist/server/operations/get-registry.js +1 -7
  51. package/dist/server/operations/get-retention-overview.js +2 -15
  52. package/dist/server/operations/get-review.js +2 -15
  53. package/dist/server/operations/get-schedule.js +2 -8
  54. package/dist/server/operations/get-stream-chunks.js +2 -6
  55. package/dist/server/operations/get-system-lease.js +1 -7
  56. package/dist/server/operations/get-system-metrics.js +0 -4
  57. package/dist/server/operations/get-task-diagnostics.js +2 -13
  58. package/dist/server/operations/get-update-result.js +1 -6
  59. package/dist/server/operations/get-workflow-attributes.js +2 -15
  60. package/dist/server/operations/get-workflow-events.js +2 -15
  61. package/dist/server/operations/get-workflow-observability.js +2 -5
  62. package/dist/server/operations/get-workflow-result.js +0 -7
  63. package/dist/server/operations/get-workflow-timeline.js +1 -6
  64. package/dist/server/operations/get-workflow.d.ts +3 -6
  65. package/dist/server/operations/get-workflow.js +2 -12
  66. package/dist/server/operations/list-alerts.d.ts +5 -2
  67. package/dist/server/operations/list-alerts.js +2 -9
  68. package/dist/server/operations/list-checkpoints.js +1 -6
  69. package/dist/server/operations/list-reviews.js +0 -7
  70. package/dist/server/operations/list-schedules.js +3 -8
  71. package/dist/server/operations/list-task-queues.js +0 -4
  72. package/dist/server/operations/list-workers.js +0 -4
  73. package/dist/server/operations/list-workflows.js +0 -7
  74. package/dist/server/operations/pause-schedule.js +1 -3
  75. package/dist/server/operations/purge-workflows.js +2 -5
  76. package/dist/server/operations/query-workflow.js +4 -15
  77. package/dist/server/operations/replay-workflow.js +2 -6
  78. package/dist/server/operations/resume-schedule.js +1 -3
  79. package/dist/server/operations/resume-workflow.js +1 -3
  80. package/dist/server/operations/set-workflow-attributes.js +3 -15
  81. package/dist/server/operations/signal-workflow.js +3 -12
  82. package/dist/server/operations/single-workflow-tag-mutation.js +0 -7
  83. package/dist/server/operations/start-or-signal-workflow.js +2 -3
  84. package/dist/server/operations/start-workflow.js +2 -3
  85. package/dist/server/operations/storage-capabilities.js +1 -3
  86. package/dist/server/operations/storage.js +7 -13
  87. package/dist/server/operations/submit-review-decision.js +3 -12
  88. package/dist/server/operations/suspend-workflow.js +1 -3
  89. package/dist/server/operations/timeout-workflow.js +1 -3
  90. package/dist/server/operations/update-schedule.js +1 -3
  91. package/dist/server/operations/update-workflow.js +2 -3
  92. package/dist/server/operations/worker-drain.js +0 -8
  93. package/dist/server/rest-binding.d.ts +3 -2
  94. package/dist/server/route-model.d.ts +2 -0
  95. package/dist/server/route-model.js +1 -1
  96. package/dist/server/runtime/task-reconciliation.d.ts +10 -1
  97. package/dist/server/runtime/task-reconciliation.js +26 -2
  98. package/dist/server/serve-internals.js +12 -6
  99. package/dist/storage/indexeddb.js +28 -48
  100. package/dist/storage/testing.d.ts +6 -3
  101. package/dist/storage/testing.js +1 -1
  102. package/dist/version.d.ts +1 -1
  103. package/dist/version.js +1 -1
  104. package/dist/worker/protocol-schemas.d.ts +2 -2
  105. package/dist/worker/protocol-schemas.js +2 -2
  106. package/dist/worker/protocol.js +1 -1
  107. package/package.json +1 -1
@@ -1,7 +1,7 @@
1
1
  import { z } from "zod";
2
2
  import { defineOperation } from "../operation-registry.js";
3
3
  import { readRestJsonBody } from "../rest-body.js";
4
- import { isOperationFault, shapeRestFault } from "./operation-helpers.js";
4
+ import { isOperationFault } from "./operation-helpers.js";
5
5
  const setWorkflowAttributesInput = z.object({
6
6
  workflowId: z.string().min(1),
7
7
  attributes: z.unknown().optional()
@@ -32,17 +32,7 @@ export const setWorkflowAttributesOperation = defineOperation({
32
32
  };
33
33
  }
34
34
  }
35
- });
36
- function shapeSetWorkflowAttributesSuccess(output) {
37
- return new Response(JSON.stringify(output), {
38
- status: 200,
39
- headers: { "Content-Type": "application/json" }
40
- });
41
- }
42
- function shapeSetWorkflowAttributesFault(fault) {
43
- return shapeRestFault(fault);
44
- }
45
- export const setWorkflowAttributesRestBinding = {
35
+ }), setWorkflowAttributesRestBinding = {
46
36
  method: "PATCH",
47
37
  path: "/v1/workflows/:id/attributes",
48
38
  pathParamNames: ["id"],
@@ -64,7 +54,5 @@ export const setWorkflowAttributesRestBinding = {
64
54
  attributes: typeof body === "object" ? body.attributes : void 0
65
55
  };
66
56
  },
67
- success: { kind: "json", status: 200 },
68
- shapeSuccess: (output) => shapeSetWorkflowAttributesSuccess(output),
69
- shapeFault: shapeSetWorkflowAttributesFault
57
+ success: { kind: "json", status: 200 }
70
58
  };
@@ -1,7 +1,7 @@
1
1
  import { z } from "zod";
2
2
  import { isSignalIdWithinByteLimit } from "../../core/signal-id.js";
3
3
  import { readRestJsonBody } from "../rest-body.js";
4
- import { isOperationFault, shapeRestFault } from "./operation-helpers.js";
4
+ import { isOperationFault } from "./operation-helpers.js";
5
5
  import {
6
6
  createSingleWorkflowControlOperation,
7
7
  extractWorkflowIdFromPath
@@ -27,14 +27,7 @@ export const signalWorkflowOperation = createSingleWorkflowControlOperation({
27
27
  await engine.signal(input.workflowId, input.signalName, input.payload, input.signalId === void 0 ? void 0 : { signalId: input.signalId });
28
28
  return { ok: !0 };
29
29
  }
30
- });
31
- function shapeSignalWorkflowSuccess(output) {
32
- return new Response(JSON.stringify(output), {
33
- status: 200,
34
- headers: { "Content-Type": "application/json" }
35
- });
36
- }
37
- export const signalWorkflowRestBinding = {
30
+ }), signalWorkflowRestBinding = {
38
31
  method: "POST",
39
32
  path: "/v1/workflows/:id/signal/:name",
40
33
  pathParamNames: ["id", "name"],
@@ -57,7 +50,5 @@ export const signalWorkflowRestBinding = {
57
50
  ...signalId === void 0 ? {} : { signalId }
58
51
  };
59
52
  },
60
- success: { kind: "json", status: 200 },
61
- shapeSuccess: (output) => shapeSignalWorkflowSuccess(output),
62
- shapeFault: shapeRestFault
53
+ success: { kind: "json", status: 200 }
63
54
  };
@@ -66,16 +66,9 @@ export function createSingleWorkflowTagMutationRestBinding(configuration) {
66
66
  };
67
67
  },
68
68
  success: { kind: "json", status: 200 },
69
- shapeSuccess: shapeSingleWorkflowTagMutationSuccess,
70
69
  shapeFault: shapeSingleWorkflowTagMutationFault
71
70
  };
72
71
  }
73
- function shapeSingleWorkflowTagMutationSuccess(output) {
74
- return new Response(JSON.stringify(output), {
75
- status: 200,
76
- headers: { "Content-Type": "application/json" }
77
- });
78
- }
79
72
  function shapeSingleWorkflowTagMutationFault(fault) {
80
73
  if (fault.code === "Unprocessable")
81
74
  return shapeRestFault(fault, { status: 400 });
@@ -9,7 +9,7 @@ import { runtimeWorkflowEngine } from "../../core/runtime-workflow-engine.js";
9
9
  import { isSignalIdWithinByteLimit } from "../../core/signal-id.js";
10
10
  import { StartWorkflowValidationError } from "../../core/start-workflow-validation.js";
11
11
  import { defineOperation } from "../operation-registry.js";
12
- import { invalidParamsFault, shapeRestFault } from "./operation-helpers.js";
12
+ import { invalidParamsFault } from "./operation-helpers.js";
13
13
  import { buildStartOrSignalWorkflowOptions } from "./start-workflow-options.js";
14
14
  import {
15
15
  extractSharedStartWorkflowRestFields,
@@ -136,6 +136,5 @@ export const startOrSignalWorkflowOperation = defineOperation({
136
136
  onTerminalConflict: record.onTerminalConflict
137
137
  };
138
138
  },
139
- success: { kind: "json", status: 201 },
140
- shapeFault: shapeRestFault
139
+ success: { kind: "json", status: 201 }
141
140
  };
@@ -7,7 +7,7 @@ import {
7
7
  import { runtimeWorkflowEngine } from "../../core/runtime-workflow-engine.js";
8
8
  import { StartWorkflowValidationError } from "../../core/start-workflow-validation.js";
9
9
  import { defineOperation } from "../operation-registry.js";
10
- import { invalidParamsFault, shapeRestFault } from "./operation-helpers.js";
10
+ import { invalidParamsFault } from "./operation-helpers.js";
11
11
  import { buildSharedStartWorkflowOptions } from "./start-workflow-options.js";
12
12
  import {
13
13
  extractSharedStartWorkflowRestFields,
@@ -99,6 +99,5 @@ export const startWorkflowOperation = defineOperation({
99
99
  extractInput: async (request, _pathParams, context) => {
100
100
  return extractSharedStartWorkflowRestFields(await parseStartWorkflowRequestRecord(request, context));
101
101
  },
102
- success: { kind: "json", status: 201 },
103
- shapeFault: shapeRestFault
102
+ success: { kind: "json", status: 201 }
104
103
  };
@@ -1,6 +1,5 @@
1
1
  import { z } from "zod";
2
2
  import { defineOperation } from "../operation-registry.js";
3
- import { shapeRestFault } from "./operation-helpers.js";
4
3
  const storageCapabilitiesInput = z.object({}), storageCapabilitiesOutput = z.object({
5
4
  persistence: z.enum(["ephemeral", "local", "remote"]),
6
5
  readAfterWrite: z.enum(["linearizable", "session", "eventual"]),
@@ -33,6 +32,5 @@ export const storageCapabilitiesOperation = defineOperation({
33
32
  inputSources: {},
34
33
  extractInput: async () => ({}),
35
34
  success: { kind: "json", status: 200 },
36
- shapeSuccess: (output) => Response.json(output),
37
- shapeFault: shapeRestFault
35
+ shapeSuccess: (output) => Response.json(output)
38
36
  };
@@ -11,7 +11,7 @@ import { raiseFault } from "../operation-catalog.js";
11
11
  import { defineOperation } from "../operation-registry.js";
12
12
  import { isAuthenticated } from "../principal.js";
13
13
  import { readRestBodyBounded, readRestJsonBody } from "../rest-body.js";
14
- import { invalidParamsFault, isOperationFault, shapeRestFault } from "./operation-helpers.js";
14
+ import { invalidParamsFault, isOperationFault } from "./operation-helpers.js";
15
15
  const rawStorageAccess = {
16
16
  kind: "scoped",
17
17
  scopes: { kind: "anyOf", scopes: ["storage:admin"] }
@@ -283,8 +283,7 @@ export const storageGetOperation = defineOperation({
283
283
  },
284
284
  extractInput: async (_request, pathParams) => ({ key: storageKeyFromPath(pathParams) }),
285
285
  success: { kind: "streaming", mediaType: "application/octet-stream" },
286
- shapeSuccess: (output) => createBinaryResponse(output),
287
- shapeFault: shapeRestFault
286
+ shapeSuccess: (output) => createBinaryResponse(output)
288
287
  }, storagePutRestBinding = {
289
288
  method: "PUT",
290
289
  path: "/v1/storage/:key",
@@ -299,8 +298,7 @@ export const storageGetOperation = defineOperation({
299
298
  value: await readRestBodyBounded(request, context)
300
299
  }),
301
300
  success: { kind: "empty", status: 204 },
302
- shapeSuccess: createNoContentResponse,
303
- shapeFault: shapeRestFault
301
+ shapeSuccess: createNoContentResponse
304
302
  }, storageDeleteRestBinding = {
305
303
  method: "DELETE",
306
304
  path: "/v1/storage/:key",
@@ -311,8 +309,7 @@ export const storageGetOperation = defineOperation({
311
309
  },
312
310
  extractInput: async (_request, pathParams) => ({ key: storageKeyFromPath(pathParams) }),
313
311
  success: { kind: "empty", status: 204 },
314
- shapeSuccess: createNoContentResponse,
315
- shapeFault: shapeRestFault
312
+ shapeSuccess: createNoContentResponse
316
313
  }, storageScanRestBinding = {
317
314
  method: "GET",
318
315
  path: "/v1/storage",
@@ -329,8 +326,7 @@ export const storageGetOperation = defineOperation({
329
326
  },
330
327
  extractInput: async (request) => extractStorageScanInput(request),
331
328
  success: { kind: "streaming", mediaType: "application/x-ndjson" },
332
- shapeSuccess: (output) => createNdjsonResponse(output),
333
- shapeFault: shapeRestFault
329
+ shapeSuccess: (output) => createNdjsonResponse(output)
334
330
  }, storageBatchRestBinding = {
335
331
  method: "POST",
336
332
  path: "/v1/storage/-/batch",
@@ -341,8 +337,7 @@ export const storageGetOperation = defineOperation({
341
337
  },
342
338
  extractInput: async (request, _pathParams, context) => readJsonRequestBody(request, context),
343
339
  success: { kind: "empty", status: 204 },
344
- shapeSuccess: createNoContentResponse,
345
- shapeFault: shapeRestFault
340
+ shapeSuccess: createNoContentResponse
346
341
  }, storageConditionalBatchRestBinding = {
347
342
  method: "POST",
348
343
  path: "/v1/storage/-/conditional-batch",
@@ -354,6 +349,5 @@ export const storageGetOperation = defineOperation({
354
349
  },
355
350
  extractInput: async (request, _pathParams, context) => readJsonRequestBody(request, context),
356
351
  success: { kind: "json", status: 200 },
357
- shapeSuccess: (output) => Response.json(output),
358
- shapeFault: shapeRestFault
352
+ shapeSuccess: (output) => Response.json(output)
359
353
  };
@@ -1,7 +1,7 @@
1
1
  import { z } from "zod";
2
2
  import { defineOperation } from "../operation-registry.js";
3
3
  import { readRestJsonBody } from "../rest-body.js";
4
- import { isOperationFault, shapeRestFault } from "./operation-helpers.js";
4
+ import { isOperationFault } from "./operation-helpers.js";
5
5
  const VALID_DECISIONS = [
6
6
  "approved",
7
7
  "rejected",
@@ -92,14 +92,7 @@ export const submitReviewDecisionOperation = defineOperation({
92
92
  mapReviewDecisionError(error, input.reviewId);
93
93
  }
94
94
  }
95
- });
96
- function shapeSubmitReviewDecisionSuccess(output) {
97
- return new Response(JSON.stringify(output), {
98
- status: 200,
99
- headers: { "Content-Type": "application/json" }
100
- });
101
- }
102
- export const submitReviewDecisionRestBinding = {
95
+ }), submitReviewDecisionRestBinding = {
103
96
  method: "POST",
104
97
  path: "/v1/reviews/:reviewId/decision",
105
98
  pathParamNames: ["reviewId"],
@@ -127,7 +120,5 @@ export const submitReviewDecisionRestBinding = {
127
120
  workflowId: record.workflowId
128
121
  };
129
122
  },
130
- success: { kind: "json", status: 200 },
131
- shapeSuccess: (output) => shapeSubmitReviewDecisionSuccess(output),
132
- shapeFault: shapeRestFault
123
+ success: { kind: "json", status: 200 }
133
124
  };
@@ -1,6 +1,5 @@
1
1
  import { z } from "zod";
2
2
  import { WorkflowSuspendNotSupportedError } from "../../core/engine/errors.js";
3
- import { shapeRestFault } from "./operation-helpers.js";
4
3
  import {
5
4
  createSingleWorkflowControlOperation,
6
5
  extractWorkflowIdFromPath
@@ -31,6 +30,5 @@ export const suspendWorkflowOperation = createSingleWorkflowControlOperation({
31
30
  workflowId: { kind: "path", pathParam: "id" }
32
31
  },
33
32
  extractInput: async (_request, pathParams) => extractWorkflowIdFromPath(pathParams),
34
- success: { kind: "empty", status: 204 },
35
- shapeFault: shapeRestFault
33
+ success: { kind: "empty", status: 204 }
36
34
  };
@@ -1,5 +1,4 @@
1
1
  import { z } from "zod";
2
- import { shapeRestFault } from "./operation-helpers.js";
3
2
  import {
4
3
  createSingleWorkflowControlOperation,
5
4
  extractWorkflowIdFromPath
@@ -29,6 +28,5 @@ export const timeoutWorkflowOperation = createSingleWorkflowControlOperation({
29
28
  workflowId: { kind: "path", pathParam: "id" }
30
29
  },
31
30
  extractInput: async (_request, pathParams) => extractWorkflowIdFromPath(pathParams),
32
- success: { kind: "empty", status: 204 },
33
- shapeFault: shapeRestFault
31
+ success: { kind: "empty", status: 204 }
34
32
  };
@@ -1,6 +1,5 @@
1
1
  import { z } from "zod";
2
2
  import { defineOperation } from "../operation-registry.js";
3
- import { shapeRestFault } from "./operation-helpers.js";
4
3
  import {
5
4
  mapScheduleErrorToFault,
6
5
  validateScheduleInputCadence,
@@ -62,6 +61,5 @@ export const updateScheduleOperation = defineOperation({
62
61
  ...extractSharedScheduleRestFields(record)
63
62
  };
64
63
  },
65
- success: { kind: "empty", status: 204 },
66
- shapeFault: shapeRestFault
64
+ success: { kind: "empty", status: 204 }
67
65
  };
@@ -6,7 +6,7 @@ import {
6
6
  } from "../../core/updates.js";
7
7
  import { defineOperation } from "../operation-registry.js";
8
8
  import { readRestJsonBody } from "../rest-body.js";
9
- import { isOperationFault, shapeRestFault } from "./operation-helpers.js";
9
+ import { isOperationFault } from "./operation-helpers.js";
10
10
  const DEFAULT_UPDATE_TIMEOUT_MS = 30000, updateWorkflowInput = z.object({
11
11
  workflowId: z.string().min(1),
12
12
  updateName: z.string().min(1),
@@ -108,6 +108,5 @@ export const updateWorkflowOperation = defineOperation({
108
108
  ...idempotencyKey !== void 0 ? { idempotencyKey } : {}
109
109
  };
110
110
  },
111
- success: { kind: "json", status: 200 },
112
- shapeFault: shapeRestFault
111
+ success: { kind: "json", status: 200 }
113
112
  };
@@ -159,7 +159,6 @@ function createWorkerDrainRestBinding(method, operationName) {
159
159
  ...method === "POST" ? await readDrainBody(request, context) : {}
160
160
  }),
161
161
  success: { kind: "json", status: 200 },
162
- shapeSuccess: shapeDrainSuccess,
163
162
  shapeFault: shapeOperationFaultAsJson
164
163
  };
165
164
  }
@@ -178,16 +177,9 @@ function createDeploymentDrainRestBinding(method, operationName) {
178
177
  ...method === "POST" ? await readDrainBody(request, context) : {}
179
178
  }),
180
179
  success: { kind: "json", status: 200 },
181
- shapeSuccess: shapeDrainSuccess,
182
180
  shapeFault: shapeOperationFaultAsJson
183
181
  };
184
182
  }
185
- function shapeDrainSuccess(output) {
186
- return new Response(JSON.stringify(output), {
187
- status: 200,
188
- headers: { "Content-Type": "application/json" }
189
- });
190
- }
191
183
  async function readDrainBody(request, context) {
192
184
  const text = await readRestTextBody(request, context);
193
185
  if (text.trim().length === 0)
@@ -98,8 +98,9 @@ export type RestBinding<Input, Output> = {
98
98
  readonly success: ResponseShape;
99
99
  /**
100
100
  * Optional override for response construction. When absent, transport
101
- * adapters default to `Response.json(output, { status: success.status })`
102
- * (or `new Response(null, { status })` for `empty`). Provide this
101
+ * adapters default to `jsonResponse(output, success.status)`, which emits
102
+ * `JSON.stringify(output)` with `Content-Type: application/json` (or
103
+ * `new Response(null, { status })` for `empty`). Provide this
103
104
  * when the wire representation differs from `output` verbatim.
104
105
  *
105
106
  * Receives the original `Request` so REST-only response shaping —
@@ -34,6 +34,8 @@
34
34
  * RFC 9264 / discovery convention.
35
35
  */
36
36
  export declare const API_PREFIX = "/api";
37
+ /** Root-relative namespace for the functional REST and WebSocket API. */
38
+ export declare const ROOT_API_PREFIX = "/v1";
37
39
  /**
38
40
  * Map a canonical, root-relative server path to its external (`/api`-prefixed)
39
41
  * form for emission in specs and discovery documents.
@@ -1,4 +1,4 @@
1
- export const API_PREFIX = "/api";
1
+ export const API_PREFIX = "/api", ROOT_API_PREFIX = "/v1";
2
2
  export function externalApiPath(canonicalPath) {
3
3
  if (!canonicalPath.startsWith("/"))
4
4
  throw Error(`externalApiPath requires a leading slash, received: ${canonicalPath}`);
@@ -1,6 +1,14 @@
1
1
  import type { ServeOptions } from '../index.ts';
2
2
  import type { InflightRecord, TaskRequeueReason } from '../task-state.ts';
3
3
  import type { ServerContext } from './context.ts';
4
+ interface ManualTaskReconciliationForTesting {
5
+ readonly options: ServeOptions;
6
+ scanAt(operationId: string, trackedDeadline: number, now: number): Promise<void>;
7
+ }
8
+ /** @internal Restricts manual reconciliation to explicitly marked test options. */
9
+ export declare function useManualTaskReconciliationForTesting(options: ServeOptions): ManualTaskReconciliationForTesting;
10
+ /** @internal Installs the narrow one-shot test controller before a server starts. */
11
+ export declare function consumeManualTaskReconciliationForTesting(options: ServeOptions, context: ServerContext, cleanupWorkflowIndex: (operationId: string) => void): boolean;
4
12
  /**
5
13
  * Given a persisted inflight record, either permanently fail the task (if
6
14
  * retry attempts are exhausted) or transition it back to queued and
@@ -13,5 +21,6 @@ export declare function reassignOrExpireTask(context: ServerContext, options: Se
13
21
  * their tasks. Only touches storage for the specific operations whose
14
22
  * deadlines have actually passed — no full `op:inflight:*` scan.
15
23
  */
16
- export declare function scanExpiredTasks(context: ServerContext, options: ServeOptions, cleanupWorkflowIndex: (operationId: string) => void): Promise<void>;
24
+ export declare function scanExpiredTasks(context: ServerContext, options: ServeOptions, cleanupWorkflowIndex: (operationId: string) => void, now?: number): Promise<void>;
17
25
  export declare function reconcileOrphanedRecords(context: ServerContext, options: ServeOptions, cleanupWorkflowIndex: (operationId: string) => void): Promise<void>;
26
+ export {};
@@ -17,6 +17,30 @@ import {
17
17
  recordWorkerCapacitySaturationMetric
18
18
  } from "./task-metrics.js";
19
19
  import { isInflightRecord } from "./websocket-worker.js";
20
+ const manualTaskReconciliationRegistrations = new WeakMap;
21
+ export function useManualTaskReconciliationForTesting(options) {
22
+ const registration = {};
23
+ manualTaskReconciliationRegistrations.set(options, registration);
24
+ return {
25
+ options,
26
+ scanAt(operationId, trackedDeadline, now) {
27
+ if (registration.scanAt === void 0)
28
+ throw Error("Manual task reconciliation requires a running test server");
29
+ return registration.scanAt(operationId, trackedDeadline, now);
30
+ }
31
+ };
32
+ }
33
+ export function consumeManualTaskReconciliationForTesting(options, context, cleanupWorkflowIndex) {
34
+ const registration = manualTaskReconciliationRegistrations.get(options);
35
+ manualTaskReconciliationRegistrations.delete(options);
36
+ if (registration === void 0)
37
+ return !1;
38
+ registration.scanAt = async (operationId, trackedDeadline, now) => {
39
+ context.deadlineTracker.add({ operationId, deadline: trackedDeadline });
40
+ await scanExpiredTasks(context, options, cleanupWorkflowIndex, now);
41
+ };
42
+ return !0;
43
+ }
20
44
  export async function reassignOrExpireTask(context, options, operationId, record, reason = "visibility-timeout") {
21
45
  const nextAttempt = (record.attempt ?? 1) + 1, policy = record.retryPolicy, nextRetryCount = Math.max(record.retryCount ?? 0, nextAttempt - 1);
22
46
  if (hasExceededMaxAttempts(policy, nextAttempt)) {
@@ -82,12 +106,12 @@ function scheduleTaskRedispatch(context, options, taskDispatch, policy) {
82
106
  } else
83
107
  dispatchTaskImpl(context, options, taskDispatch).catch((err) => console.error(`[weft] Redispatch failed for "${taskDispatch.operationId}":`, err));
84
108
  }
85
- export async function scanExpiredTasks(context, options, cleanupWorkflowIndex) {
109
+ export async function scanExpiredTasks(context, options, cleanupWorkflowIndex, now = Date.now()) {
86
110
  if (context.scanRunning)
87
111
  return;
88
112
  context.scanRunning = !0;
89
113
  try {
90
- const now = Date.now(), expired = context.deadlineTracker.drainExpired(now);
114
+ const expired = context.deadlineTracker.drainExpired(now);
91
115
  for (const { operationId, deadline } of expired) {
92
116
  if (context.processingOperations.has(operationId)) {
93
117
  context.deadlineTracker.add({ operationId, deadline });
@@ -25,7 +25,11 @@ import {
25
25
  registerWorkflowEventLifecycle
26
26
  } from "./runtime/event-broadcasting.js";
27
27
  import { shutdownAllWorkers } from "./runtime/shutdown.js";
28
- import { reconcileOrphanedRecords, scanExpiredTasks } from "./runtime/task-reconciliation.js";
28
+ import {
29
+ consumeManualTaskReconciliationForTesting as consumeManual,
30
+ reconcileOrphanedRecords,
31
+ scanExpiredTasks
32
+ } from "./runtime/task-reconciliation.js";
29
33
  import { DEFAULT_MAX_STREAM_CONNECTIONS_PER_WORKFLOW } from "./runtime/websocket-stream.js";
30
34
  import { isInflightRecord, withRetry } from "./runtime/websocket-worker.js";
31
35
  import { TaskQueue } from "./task-queue.js";
@@ -166,14 +170,16 @@ export function registerStackDisposers(stack, context, options, broadcastingHand
166
170
  stack.defer(() => context.mcpSessionManager[Symbol.asyncDispose]());
167
171
  stack.defer(registerWorkflowEventLifecycle(options.engine, context, broadcastingHandle));
168
172
  stack.defer(() => shutdownAllWorkers(context, options.workerShutdownTimeoutMs === void 0 ? void 0 : { timeoutMs: options.workerShutdownTimeoutMs, stopWaitingWhenIdle: !0 }));
169
- const visibilityPollHandle = setInterval(() => {
173
+ const schedulesTaskReconciliation = !consumeManual(options, context, onOperationCleanup), visibilityPollHandle = schedulesTaskReconciliation ? setInterval(() => {
170
174
  scanExpiredTasks(context, options, onOperationCleanup);
171
- }, context.visibilityPollMs), reconciliationIntervalMs = context.visibilityPollMs * RECONCILIATION_MULTIPLIER, reconciliationHandle = setInterval(() => {
175
+ }, context.visibilityPollMs) : void 0, reconciliationIntervalMs = context.visibilityPollMs * RECONCILIATION_MULTIPLIER, reconciliationHandle = schedulesTaskReconciliation ? setInterval(() => {
172
176
  reconcileOrphanedRecords(context, options, onOperationCleanup);
173
- }, reconciliationIntervalMs);
177
+ }, reconciliationIntervalMs) : void 0;
174
178
  stack.defer(() => {
175
- clearInterval(visibilityPollHandle);
176
- clearInterval(reconciliationHandle);
179
+ if (visibilityPollHandle !== void 0)
180
+ clearInterval(visibilityPollHandle);
181
+ if (reconciliationHandle !== void 0)
182
+ clearInterval(reconciliationHandle);
177
183
  context.deadlineTracker.clear();
178
184
  for (const timer of context.pendingTimers)
179
185
  clearTimeout(timer);
@@ -74,6 +74,30 @@ function createCursorRequestAwaiter(request, transaction) {
74
74
  });
75
75
  };
76
76
  }
77
+ async function* iterateCursor(database, keyRangeFactory, prefix, options, openCursor, project) {
78
+ const { limit, reverse } = options, prefixEnd = resolvePrefixRangeEnd(prefix), range = keyRangeFactory.bound(prefix, prefixEnd, !1, !0), direction = reverse ? "prev" : "next", transaction = database.transaction(STORE_NAME, "readonly"), store = transaction.objectStore(STORE_NAME), request = openCursor(store, range, direction), nextCursor = createCursorRequestAwaiter(request, transaction);
79
+ let count = 0, completed = !1;
80
+ try {
81
+ let cursor = await nextCursor();
82
+ while (cursor) {
83
+ if (limit !== void 0 && count >= limit)
84
+ break;
85
+ const key = cursor.key;
86
+ if (matchesScanOptions(key, options)) {
87
+ yield project(cursor);
88
+ count++;
89
+ }
90
+ cursor.continue();
91
+ cursor = await nextCursor();
92
+ }
93
+ completed = !0;
94
+ } finally {
95
+ if (!completed)
96
+ try {
97
+ transaction.abort();
98
+ } catch {}
99
+ }
100
+ }
77
101
 
78
102
  export class IndexedDBStorage {
79
103
  #databaseName;
@@ -168,30 +192,8 @@ export class IndexedDBStorage {
168
192
  });
169
193
  }
170
194
  async* scan(prefix, options = {}) {
171
- const { limit, reverse } = options, database = await this.#databasePromise, prefixEnd = resolvePrefixRangeEnd(prefix), range = this.#runtime.IDBKeyRange.bound(prefix, prefixEnd, !1, !0), direction = reverse ? "prev" : "next", transaction = database.transaction(STORE_NAME, "readonly"), request = transaction.objectStore(STORE_NAME).openCursor(range, direction);
172
- let count = 0;
173
- const nextCursor = createCursorRequestAwaiter(request, transaction);
174
- let completed = !1;
175
- try {
176
- let cursor = await nextCursor();
177
- while (cursor) {
178
- if (limit !== void 0 && count >= limit)
179
- break;
180
- const key = cursor.key;
181
- if (matchesScanOptions(key, options)) {
182
- yield [key, new Uint8Array(cursor.value)];
183
- count++;
184
- }
185
- cursor.continue();
186
- cursor = await nextCursor();
187
- }
188
- completed = !0;
189
- } finally {
190
- if (!completed)
191
- try {
192
- transaction.abort();
193
- } catch {}
194
- }
195
+ const database = await this.#databasePromise;
196
+ yield* iterateCursor(database, this.#runtime.IDBKeyRange, prefix, options, (store, range, direction) => store.openCursor(range, direction), (cursor) => [cursor.key, new Uint8Array(cursor.value)]);
195
197
  }
196
198
  async batch(operations) {
197
199
  assertStorageBatchOperationCount("batch operations", operations.length);
@@ -270,30 +272,8 @@ export class IndexedDBStorage {
270
272
  });
271
273
  }
272
274
  async* keys(prefix, options = {}) {
273
- const { limit, reverse } = options, database = await this.#databasePromise, prefixEnd = resolvePrefixRangeEnd(prefix), range = this.#runtime.IDBKeyRange.bound(prefix, prefixEnd, !1, !0), direction = reverse ? "prev" : "next", transaction = database.transaction(STORE_NAME, "readonly"), request = transaction.objectStore(STORE_NAME).openKeyCursor(range, direction);
274
- let count = 0;
275
- const nextCursor = createCursorRequestAwaiter(request, transaction);
276
- let completed = !1;
277
- try {
278
- let cursor = await nextCursor();
279
- while (cursor) {
280
- if (limit !== void 0 && count >= limit)
281
- break;
282
- const key = cursor.key;
283
- if (matchesScanOptions(key, options)) {
284
- yield key;
285
- count++;
286
- }
287
- cursor.continue();
288
- cursor = await nextCursor();
289
- }
290
- completed = !0;
291
- } finally {
292
- if (!completed)
293
- try {
294
- transaction.abort();
295
- } catch {}
296
- }
275
+ const database = await this.#databasePromise;
276
+ yield* iterateCursor(database, this.#runtime.IDBKeyRange, prefix, options, (store, range, direction) => store.openKeyCursor(range, direction), (cursor) => cursor.key);
297
277
  }
298
278
  async count(prefix) {
299
279
  const database = await this.#databasePromise, prefixEnd = resolvePrefixRangeEnd(prefix), store = database.transaction(STORE_NAME, "readonly").objectStore(STORE_NAME);
@@ -70,6 +70,8 @@ export type ConcurrentConditionalBatchConformanceOptions = {
70
70
  export type BinaryAndLargeScanConformanceOptions = {
71
71
  /** Construct a fresh, empty adapter for each conformance case. */
72
72
  readonly create: () => Storage | Promise<Storage>;
73
+ /** Optional timeout for adapters whose local large-scan test needs more time. */
74
+ readonly largeScanTimeoutMs?: number;
73
75
  };
74
76
  /**
75
77
  * Construct a fresh, empty adapter for each basic-contract case.
@@ -151,9 +153,10 @@ export declare function runStorageCapabilityConformance(name: string, options: C
151
153
  */
152
154
  export declare function runConcurrentConditionalBatchConformance(name: string, options: ConcurrentConditionalBatchConformanceOptions): void;
153
155
  /**
154
- * Register the basic key/value and scan contract that every storage adapter
155
- * must satisfy: get, put, overwrite, delete, prefix scans, limits, reverse
156
- * ordering, range bounds, and empty-prefix scans.
156
+ * Register the complete basic key/value and scan contract that every storage
157
+ * adapter must satisfy: get, put, overwrite, delete (including missing keys),
158
+ * prefix scans, limits, reverse ordering, exclusive and inclusive range
159
+ * bounds, no-match scans, empty-prefix scans, and put/delete batches.
157
160
  *
158
161
  * @example
159
162
  * ```ts