@lostgradient/weft 0.13.0 → 0.15.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 (114) hide show
  1. package/README.md +3 -8
  2. package/dist/cli/generated/operation-catalog.snapshot.json +977 -77
  3. package/dist/cli/generated/operation-client.generated.d.ts +14 -21
  4. package/dist/cli/help-text.d.ts +3 -3
  5. package/dist/cli/help-text.js +4 -4
  6. package/dist/cli/operation-catalog-snapshot.d.ts +1 -1
  7. package/dist/cli/schedule.js +1 -3
  8. package/dist/cli/serve-registrations.d.ts +3 -3
  9. package/dist/cli/serve-registrations.js +2 -2
  10. package/dist/cli/shutdown.d.ts +11 -0
  11. package/dist/cli/shutdown.js +25 -0
  12. package/dist/cli/validate.d.ts +1 -1
  13. package/dist/cli/version-check.d.ts +1 -1
  14. package/dist/cli/version-check.js +1 -1
  15. package/dist/cli-main.js +10 -29
  16. package/dist/client/http-client-storage.js +7 -35
  17. package/dist/client/http-request.d.ts +8 -9
  18. package/dist/client/http-request.js +3 -25
  19. package/dist/core/aggregate-validation.d.ts +2 -5
  20. package/dist/core/aggregate-validation.js +2 -10
  21. package/dist/core/atomic-state.js +21 -24
  22. package/dist/core/context/parallel-operations.d.ts +0 -2
  23. package/dist/core/context/parallel-operations.js +0 -7
  24. package/dist/core/engine/callback-checkpoint-persistence.d.ts +2 -0
  25. package/dist/core/engine/callback-checkpoint-persistence.js +6 -3
  26. package/dist/core/engine/callback-creators.js +4 -22
  27. package/dist/core/engine/checkpoint-io.d.ts +1 -1
  28. package/dist/core/engine/operations-coordination.js +1 -1
  29. package/dist/core/engine/parallel-dispatch.d.ts +1 -1
  30. package/dist/core/engine/parallel-dispatch.js +1 -1
  31. package/dist/core/engine/review-list-entries.d.ts +1 -1
  32. package/dist/core/engine/review-list-entries.js +6 -30
  33. package/dist/core/engine/schedules.js +4 -1
  34. package/dist/core/engine/timeline-coordinator-detail.d.ts +1 -1
  35. package/dist/core/inline-execution-strategy.js +8 -46
  36. package/dist/core/list-filter-validation.d.ts +2 -5
  37. package/dist/core/list-filter-validation.js +2 -10
  38. package/dist/core/review/index.d.ts +87 -0
  39. package/dist/core/review/index.js +29 -0
  40. package/dist/core/types/schedules.d.ts +13 -31
  41. package/dist/core/validation-issues.d.ts +13 -0
  42. package/dist/core/validation-issues.js +9 -0
  43. package/dist/diagnostics/validate.d.ts +9 -30
  44. package/dist/diagnostics/validate.js +83 -21
  45. package/dist/diagnostics/version-check.d.ts +2 -2
  46. package/dist/http.js +2 -2
  47. package/dist/indexeddb.js +1 -1
  48. package/dist/mcp/session.js +2 -10
  49. package/dist/mcp/tools.js +2 -10
  50. package/dist/mcp/tuple-list-equality.d.ts +1 -0
  51. package/dist/mcp/tuple-list-equality.js +12 -0
  52. package/dist/server/openapi-error-responses.js +0 -14
  53. package/dist/server/operation-catalog/index.d.ts +1 -1
  54. package/dist/server/operation-catalog/raise-fault.d.ts +2 -1
  55. package/dist/server/operation-catalog/types.d.ts +3 -2
  56. package/dist/server/operation-fault.d.ts +1 -4
  57. package/dist/server/operation-fault.js +1 -1
  58. package/dist/server/operation-registry.d.ts +2 -31
  59. package/dist/server/operations/bulk-filter-helpers.d.ts +17 -17
  60. package/dist/server/operations/bulk-filter-helpers.js +16 -37
  61. package/dist/server/operations/bulk-mutate-workflow-tags.d.ts +27 -27
  62. package/dist/server/operations/bulk-signal-workflows.d.ts +26 -26
  63. package/dist/server/operations/create-schedule.js +7 -19
  64. package/dist/server/operations/event-stream-contracts.d.ts +20 -0
  65. package/dist/server/operations/event-stream-contracts.js +67 -0
  66. package/dist/server/operations/fleet-events-sse.js +21 -50
  67. package/dist/server/operations/fleet-events-subscription.js +8 -12
  68. package/dist/server/operations/list-reviews.js +2 -19
  69. package/dist/server/operations/list-workflows.d.ts +43 -43
  70. package/dist/server/operations/list-workflows.js +9 -30
  71. package/dist/server/operations/purge-workflows.d.ts +11 -11
  72. package/dist/server/operations/recover-all.js +1 -10
  73. package/dist/server/operations/schedule-rest-body.d.ts +11 -0
  74. package/dist/server/operations/schedule-rest-body.js +28 -0
  75. package/dist/server/operations/single-workflow-control-operation.d.ts +2 -1
  76. package/dist/server/operations/update-schedule.js +7 -19
  77. package/dist/server/operations/workflow-events-sse.js +29 -67
  78. package/dist/server/operations/workflow-events-subscription.js +9 -27
  79. package/dist/server/runtime/task-polling.js +3 -5
  80. package/dist/server/task-state.d.ts +2 -8
  81. package/dist/server/task-state.js +10 -7
  82. package/dist/storage/bounded-ndjson-response.d.ts +5 -0
  83. package/dist/storage/bounded-ndjson-response.js +33 -0
  84. package/dist/storage/bun-sql.js +52 -49
  85. package/dist/storage/conditional-batch-body.d.ts +7 -0
  86. package/dist/storage/conditional-batch-body.js +14 -0
  87. package/dist/storage/http.js +5 -37
  88. package/dist/storage/lmdb.js +1 -1
  89. package/dist/storage/memory.js +1 -1
  90. package/dist/storage/neon.d.ts +3 -16
  91. package/dist/storage/neon.js +2 -2
  92. package/dist/storage/node-sqlite-loader.d.ts +2 -2
  93. package/dist/storage/node-sqlite.js +32 -17
  94. package/dist/storage/postgres.js +1 -1
  95. package/dist/storage/resolve.js +1 -1
  96. package/dist/storage/scoped-storage.js +1 -1
  97. package/dist/storage/turso.js +1 -1
  98. package/dist/version.d.ts +1 -1
  99. package/dist/version.js +1 -1
  100. package/dist/web-extension.js +1 -1
  101. package/dist/worker/execute-with-interceptors.d.ts +1 -1
  102. package/dist/worker/execute-with-interceptors.js +2 -2
  103. package/dist/worker/index.js +5 -5
  104. package/dist/worker/long-poll.js +3 -4
  105. package/dist/worker/protocol-messages.d.ts +11 -19
  106. package/dist/worker/protocol-schemas.d.ts +8 -8
  107. package/dist/worker/protocol-schemas.js +4 -4
  108. package/dist/worker/protocol-task-result.d.ts +1 -0
  109. package/dist/worker/protocol-task-result.js +1 -3
  110. package/dist/worker/protocol.js +1 -1
  111. package/dist/worker/registry/types.d.ts +2 -7
  112. package/dist/worker/registry.d.ts +3 -9
  113. package/dist/worker/registry.js +2 -5
  114. package/package.json +1 -1
@@ -1,85 +1,85 @@
1
1
  import { z } from 'zod';
2
- import type { FailureCategory, PaginatedResult, SearchAttributeValue, WorkflowStatus, WorkflowSummary } from '../../core/types.ts';
2
+ import type { PaginatedResult, WorkflowSummary } from '../../core/types.ts';
3
3
  import type { UnknownRestBinding } from '../rest-bindings.ts';
4
4
  declare const listWorkflowsInput: z.ZodObject<{
5
- status: z.ZodOptional<z.ZodUnion<readonly [z.ZodCustom<WorkflowStatus, WorkflowStatus>, z.ZodArray<z.ZodCustom<WorkflowStatus, WorkflowStatus>>]>>;
5
+ status: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>>;
6
6
  type: z.ZodOptional<z.ZodString>;
7
- scheduleId: z.ZodOptional<z.ZodString>;
8
- parentWorkflowId: z.ZodOptional<z.ZodString>;
9
- parentWorkflowExecutionToken: z.ZodOptional<z.ZodString>;
10
- tags: z.ZodOptional<z.ZodArray<z.ZodString>>;
11
- attributes: z.ZodOptional<z.ZodArray<z.ZodObject<{
12
- key: z.ZodString;
13
- value: z.ZodOptional<z.ZodCustom<SearchAttributeValue, SearchAttributeValue>>;
14
- gt: z.ZodOptional<z.ZodCustom<SearchAttributeValue, SearchAttributeValue>>;
15
- lt: z.ZodOptional<z.ZodCustom<SearchAttributeValue, SearchAttributeValue>>;
16
- gte: z.ZodOptional<z.ZodCustom<SearchAttributeValue, SearchAttributeValue>>;
17
- lte: z.ZodOptional<z.ZodCustom<SearchAttributeValue, SearchAttributeValue>>;
18
- }, z.core.$strip>>>;
19
- limit: z.ZodOptional<z.ZodNumber>;
20
- offset: z.ZodOptional<z.ZodNumber>;
21
7
  idPrefix: z.ZodOptional<z.ZodString>;
22
8
  createdAt: z.ZodOptional<z.ZodObject<{
23
9
  gte: z.ZodOptional<z.ZodNumber>;
24
- gt: z.ZodOptional<z.ZodNumber>;
25
10
  lte: z.ZodOptional<z.ZodNumber>;
11
+ gt: z.ZodOptional<z.ZodNumber>;
26
12
  lt: z.ZodOptional<z.ZodNumber>;
27
- }, z.core.$strip>>;
13
+ }, z.core.$strict>>;
28
14
  updatedAt: z.ZodOptional<z.ZodObject<{
29
15
  gte: z.ZodOptional<z.ZodNumber>;
30
- gt: z.ZodOptional<z.ZodNumber>;
31
16
  lte: z.ZodOptional<z.ZodNumber>;
17
+ gt: z.ZodOptional<z.ZodNumber>;
32
18
  lt: z.ZodOptional<z.ZodNumber>;
33
- }, z.core.$strip>>;
19
+ }, z.core.$strict>>;
34
20
  executionDeadline: z.ZodOptional<z.ZodObject<{
35
21
  gte: z.ZodOptional<z.ZodNumber>;
36
- gt: z.ZodOptional<z.ZodNumber>;
37
22
  lte: z.ZodOptional<z.ZodNumber>;
23
+ gt: z.ZodOptional<z.ZodNumber>;
38
24
  lt: z.ZodOptional<z.ZodNumber>;
39
- }, z.core.$strip>>;
40
- failureCategory: z.ZodOptional<z.ZodUnion<readonly [z.ZodCustom<FailureCategory, FailureCategory>, z.ZodArray<z.ZodCustom<FailureCategory, FailureCategory>>]>>;
25
+ }, z.core.$strict>>;
26
+ failureCategory: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>>;
41
27
  include: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>>;
42
- }, z.core.$strip>;
28
+ scheduleId: z.ZodOptional<z.ZodString>;
29
+ parentWorkflowId: z.ZodOptional<z.ZodString>;
30
+ parentWorkflowExecutionToken: z.ZodOptional<z.ZodString>;
31
+ tags: z.ZodOptional<z.ZodArray<z.ZodString>>;
32
+ attributes: z.ZodOptional<z.ZodArray<z.ZodObject<{
33
+ key: z.ZodUnion<readonly [z.ZodString, z.ZodAny]>;
34
+ value: z.ZodOptional<z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>>;
35
+ gt: z.ZodOptional<z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>>;
36
+ lt: z.ZodOptional<z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>>;
37
+ gte: z.ZodOptional<z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>>;
38
+ lte: z.ZodOptional<z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>>;
39
+ }, z.core.$strip>>>;
40
+ limit: z.ZodOptional<z.ZodNumber>;
41
+ offset: z.ZodOptional<z.ZodNumber>;
42
+ }, z.core.$strict>;
43
43
  export type ListWorkflowsInput = z.infer<typeof listWorkflowsInput>;
44
44
  export type ListWorkflowsOutput = PaginatedResult<WorkflowSummary>;
45
45
  export declare const listWorkflowsOperation: import("../operation-catalog.ts").OperationDefinition<{
46
- status?: WorkflowStatus | WorkflowStatus[] | undefined;
46
+ status?: string | string[] | undefined;
47
47
  type?: string | undefined;
48
- scheduleId?: string | undefined;
49
- parentWorkflowId?: string | undefined;
50
- parentWorkflowExecutionToken?: string | undefined;
51
- tags?: string[] | undefined;
52
- attributes?: {
53
- key: string;
54
- value?: SearchAttributeValue | undefined;
55
- gt?: SearchAttributeValue | undefined;
56
- lt?: SearchAttributeValue | undefined;
57
- gte?: SearchAttributeValue | undefined;
58
- lte?: SearchAttributeValue | undefined;
59
- }[] | undefined;
60
- limit?: number | undefined;
61
- offset?: number | undefined;
62
48
  idPrefix?: string | undefined;
63
49
  createdAt?: {
64
50
  gte?: number | undefined;
65
- gt?: number | undefined;
66
51
  lte?: number | undefined;
52
+ gt?: number | undefined;
67
53
  lt?: number | undefined;
68
54
  } | undefined;
69
55
  updatedAt?: {
70
56
  gte?: number | undefined;
71
- gt?: number | undefined;
72
57
  lte?: number | undefined;
58
+ gt?: number | undefined;
73
59
  lt?: number | undefined;
74
60
  } | undefined;
75
61
  executionDeadline?: {
76
62
  gte?: number | undefined;
77
- gt?: number | undefined;
78
63
  lte?: number | undefined;
64
+ gt?: number | undefined;
79
65
  lt?: number | undefined;
80
66
  } | undefined;
81
- failureCategory?: FailureCategory | FailureCategory[] | undefined;
67
+ failureCategory?: string | string[] | undefined;
82
68
  include?: string | string[] | undefined;
69
+ scheduleId?: string | undefined;
70
+ parentWorkflowId?: string | undefined;
71
+ parentWorkflowExecutionToken?: string | undefined;
72
+ tags?: string[] | undefined;
73
+ attributes?: {
74
+ key: any;
75
+ value?: unknown;
76
+ gt?: unknown;
77
+ lt?: unknown;
78
+ gte?: unknown;
79
+ lte?: unknown;
80
+ }[] | undefined;
81
+ limit?: number | undefined;
82
+ offset?: number | undefined;
83
83
  }, ListWorkflowsOutput, unknown>;
84
84
  export declare const listWorkflowsRestBinding: UnknownRestBinding;
85
85
  export {};
@@ -2,45 +2,24 @@ import { z } from "zod";
2
2
  import { WorkflowListScanCapExceededError } from "../../core/engine/workflow-indexes.js";
3
3
  import {
4
4
  ListFilterValidationError,
5
+ listFilterObjectSchema,
5
6
  normalizeListFilter
6
7
  } from "../../core/list-filter-validation.js";
7
8
  import { coerceStartWorkflowTags } from "../../core/start-workflow-validation.js";
8
9
  import { defineOperation } from "../operation-registry.js";
9
10
  import { extractListFilterFromQuery } from "./list-filter-query-extractor.js";
10
11
  import { shapeRestFault } from "./operation-helpers.js";
11
- const workflowStatusSchema = z.custom((value) => typeof value === "string"), failureCategorySchema = z.custom((value) => typeof value === "string"), searchAttributeValueSchema = z.custom((value) => {
12
- if (typeof value === "string" || typeof value === "number" || typeof value === "boolean")
13
- return !0;
14
- return Array.isArray(value) && value.every((entry) => typeof entry === "string");
15
- }), attributeFilterSchema = z.object({
16
- key: z.string().min(1),
17
- value: searchAttributeValueSchema.optional(),
18
- gt: searchAttributeValueSchema.optional(),
19
- lt: searchAttributeValueSchema.optional(),
20
- gte: searchAttributeValueSchema.optional(),
21
- lte: searchAttributeValueSchema.optional()
22
- }), timeRangeSchema = z.object({
23
- gte: z.number().optional(),
24
- gt: z.number().optional(),
25
- lte: z.number().optional(),
26
- lt: z.number().optional()
27
- }), listIncludeSchema = z.union([z.string(), z.array(z.string()).min(1)]).refine((value) => Array.isArray(value) ? value.every((entry) => entry === "failureCategory") : value === "failureCategory", { message: 'include must be "failureCategory"' }), listWorkflowsInput = z.object({
28
- status: z.union([workflowStatusSchema, z.array(workflowStatusSchema)]).optional(),
12
+ const listIncludeSchema = z.union([z.string(), z.array(z.string()).min(1)]).refine((value) => Array.isArray(value) ? value.every((entry) => entry === "failureCategory") : value === "failureCategory", { message: 'include must be "failureCategory"' }), listWorkflowsInput = z.object({
13
+ ...listFilterObjectSchema.shape,
14
+ status: z.union([z.string(), z.array(z.string())]).optional(),
29
15
  type: z.string().optional(),
30
- scheduleId: z.string().min(1).optional(),
31
- parentWorkflowId: z.string().min(1).optional(),
32
- parentWorkflowExecutionToken: z.string().min(1).optional(),
33
- tags: z.array(z.string()).optional(),
34
- attributes: z.array(attributeFilterSchema).optional(),
35
- limit: z.number().int().min(1).max(1000).optional(),
36
- offset: z.number().int().min(0).optional(),
37
16
  idPrefix: z.string().optional(),
38
- createdAt: timeRangeSchema.optional(),
39
- updatedAt: timeRangeSchema.optional(),
40
- executionDeadline: timeRangeSchema.optional(),
41
- failureCategory: z.union([failureCategorySchema, z.array(failureCategorySchema)]).optional(),
17
+ createdAt: z.object(listFilterObjectSchema.shape.createdAt.unwrap().shape).strict().optional(),
18
+ updatedAt: z.object(listFilterObjectSchema.shape.updatedAt.unwrap().shape).strict().optional(),
19
+ executionDeadline: z.object(listFilterObjectSchema.shape.executionDeadline.unwrap().shape).strict().optional(),
20
+ failureCategory: z.union([z.string(), z.array(z.string())]).optional(),
42
21
  include: listIncludeSchema.optional()
43
- }), listWorkflowsOutput = z.unknown();
22
+ }).strict(), listWorkflowsOutput = z.unknown();
44
23
  export const listWorkflowsOperation = defineOperation({
45
24
  name: "weft.workflows.list",
46
25
  mcpExposable: !1,
@@ -11,34 +11,34 @@ export declare const purgeWorkflowsOperation: import("../operation-catalog.ts").
11
11
  parentWorkflowExecutionToken?: string | undefined;
12
12
  tags?: string[] | undefined;
13
13
  attributes?: {
14
- key: string;
15
- value?: import("../../index.ts").SearchAttributeValue | undefined;
16
- gt?: import("../../core/types.ts").AttributeFilterScalarValue | undefined;
17
- lt?: import("../../core/types.ts").AttributeFilterScalarValue | undefined;
18
- gte?: import("../../core/types.ts").AttributeFilterScalarValue | undefined;
19
- lte?: import("../../core/types.ts").AttributeFilterScalarValue | undefined;
14
+ key: any;
15
+ value?: unknown;
16
+ gt?: unknown;
17
+ lt?: unknown;
18
+ gte?: unknown;
19
+ lte?: unknown;
20
20
  }[] | undefined;
21
- limit?: number | undefined;
22
21
  offset?: number | undefined;
23
22
  idPrefix?: string | undefined;
24
- failureCategory?: import("../../index.ts").FailureCategory | import("../../index.ts").FailureCategory[] | undefined;
25
23
  createdAt?: {
26
24
  gte?: number | undefined;
27
- gt?: number | undefined;
28
25
  lte?: number | undefined;
26
+ gt?: number | undefined;
29
27
  lt?: number | undefined;
30
28
  } | undefined;
31
29
  updatedAt?: {
32
30
  gte?: number | undefined;
33
- gt?: number | undefined;
34
31
  lte?: number | undefined;
32
+ gt?: number | undefined;
35
33
  lt?: number | undefined;
36
34
  } | undefined;
37
35
  executionDeadline?: {
38
36
  gte?: number | undefined;
39
- gt?: number | undefined;
40
37
  lte?: number | undefined;
38
+ gt?: number | undefined;
41
39
  lt?: number | undefined;
42
40
  } | undefined;
41
+ failureCategory?: import("../../index.ts").FailureCategory | import("../../index.ts").FailureCategory[] | undefined;
42
+ limit?: number | undefined;
43
43
  }, PurgeResult, unknown>;
44
44
  export declare const purgeWorkflowsRestBinding: UnknownRestBinding;
@@ -1,6 +1,5 @@
1
1
  import { z } from "zod";
2
2
  import { WorkflowTypeNotRegisteredForRecoveryError } from "../../core/engine.js";
3
- import { shapeRestFaultBody } from "../operation-fault.js";
4
3
  import { defineOperation } from "../operation-registry.js";
5
4
  import { shapeRestFault } from "./operation-helpers.js";
6
5
  const recoverAllInput = z.object({}), recoverAllOutput = z.object({
@@ -54,13 +53,5 @@ export const recoverAllOperation = defineOperation({
54
53
  function shapeRecoverAllFault(fault) {
55
54
  if (fault.code !== "Conflict" || fault.data.missingTypes === void 0)
56
55
  return shapeRestFault(fault);
57
- return new Response(JSON.stringify({
58
- ...shapeRestFaultBody(fault, "workflow_type_not_registered_for_recovery"),
59
- missingTypes: fault.data.missingTypes,
60
- missingWorkflowCount: fault.data.missingWorkflowCount,
61
- samplesTruncated: fault.data.samplesTruncated
62
- }), {
63
- status: 409,
64
- headers: { "Content-Type": "application/json" }
65
- });
56
+ return shapeRestFault(fault, { message: "workflow_type_not_registered_for_recovery" });
66
57
  }
@@ -0,0 +1,11 @@
1
+ import type { RestInputContext } from '../rest-binding.ts';
2
+ export type SharedScheduleRestFields = {
3
+ readonly cronExpression: unknown;
4
+ readonly every: unknown;
5
+ readonly description: unknown;
6
+ readonly overlap: unknown;
7
+ readonly backfill: unknown;
8
+ readonly jitter: unknown;
9
+ };
10
+ export declare function parseScheduleRestBodyRequestRecord(request: Request, context?: RestInputContext): Promise<Record<string, unknown>>;
11
+ export declare function extractSharedScheduleRestFields(record: Record<string, unknown>): SharedScheduleRestFields;
@@ -0,0 +1,28 @@
1
+ import { readRestJsonBody } from "../rest-body.js";
2
+ import { invalidParamsFault, isOperationFault } from "./operation-helpers.js";
3
+ function isJsonObjectLikeRecord(value) {
4
+ return typeof value === "object" && value !== null;
5
+ }
6
+ export async function parseScheduleRestBodyRequestRecord(request, context) {
7
+ let body;
8
+ try {
9
+ body = await readRestJsonBody(request, context);
10
+ } catch (error) {
11
+ if (isOperationFault(error))
12
+ throw error;
13
+ throw invalidParamsFault("Invalid JSON body");
14
+ }
15
+ if (!isJsonObjectLikeRecord(body))
16
+ throw invalidParamsFault("Request body must be a JSON object");
17
+ return body;
18
+ }
19
+ export function extractSharedScheduleRestFields(record) {
20
+ return {
21
+ cronExpression: Object.hasOwn(record, "cronExpression") ? record.cronExpression : void 0,
22
+ every: Object.hasOwn(record, "every") ? record.every : void 0,
23
+ description: record.description,
24
+ overlap: record.overlap,
25
+ backfill: record.backfill,
26
+ jitter: record.jitter
27
+ };
28
+ }
@@ -1,7 +1,8 @@
1
1
  import type { z } from 'zod';
2
2
  import type { Engine } from '../../core/engine.ts';
3
+ import type { FaultCode } from '../../core/fault-code.ts';
3
4
  import type { OperationDefinition } from '../operation-catalog.ts';
4
- import type { FaultCode, OperationFault } from '../operation-fault.ts';
5
+ import type { OperationFault } from '../operation-fault.ts';
5
6
  type SingleWorkflowControlInput = {
6
7
  readonly workflowId: string;
7
8
  };
@@ -1,9 +1,12 @@
1
1
  import { z } from "zod";
2
2
  import { normalizeScheduleUpdateOptions } from "../../core/engine/validation/schedule.js";
3
3
  import { defineOperation } from "../operation-registry.js";
4
- import { readRestJsonBody } from "../rest-body.js";
5
- import { invalidParamsFault, isOperationFault, shapeRestFault } from "./operation-helpers.js";
4
+ import { invalidParamsFault, shapeRestFault } from "./operation-helpers.js";
6
5
  import { mapScheduleErrorToFault, validateScheduleInputCadence } from "./schedule-faults.js";
6
+ import {
7
+ extractSharedScheduleRestFields,
8
+ parseScheduleRestBodyRequestRecord
9
+ } from "./schedule-rest-body.js";
7
10
  const updateScheduleInput = z.object({
8
11
  scheduleId: z.string().min(1),
9
12
  cronExpression: z.unknown().optional().describe("Cron expression. Supply exactly one of cronExpression or every; runtime validation requires a non-empty string."),
@@ -100,25 +103,10 @@ export const updateScheduleOperation = defineOperation({
100
103
  jitter: { kind: "body-field", bodyField: "jitter" }
101
104
  },
102
105
  extractInput: async (request, pathParams, context) => {
103
- let body;
104
- try {
105
- body = await readRestJsonBody(request, context);
106
- } catch (error) {
107
- if (isOperationFault(error))
108
- throw error;
109
- throw invalidParamsFault("Invalid JSON body");
110
- }
111
- if (typeof body !== "object" || body === null)
112
- throw invalidParamsFault("Request body must be a JSON object");
113
- const record = body;
106
+ const record = await parseScheduleRestBodyRequestRecord(request, context);
114
107
  return {
115
108
  scheduleId: pathParams.id ?? "",
116
- cronExpression: Object.hasOwn(record, "cronExpression") ? record.cronExpression : void 0,
117
- every: Object.hasOwn(record, "every") ? record.every : void 0,
118
- description: record.description,
119
- overlap: record.overlap,
120
- backfill: record.backfill,
121
- jitter: record.jitter
109
+ ...extractSharedScheduleRestFields(record)
122
110
  };
123
111
  },
124
112
  success: { kind: "empty", status: 204 },
@@ -4,6 +4,13 @@ import { isAuthenticated } from "../principal.js";
4
4
  import {
5
5
  decodeCursor
6
6
  } from "../workflow-event-feed.js";
7
+ import {
8
+ createReplayAwareClosableIterable,
9
+ isClosableAsyncIterable,
10
+ isReplayAwareClosableIterable,
11
+ workflowEventEnvelopeSchema,
12
+ workflowEventParameterizedAccess
13
+ } from "./event-stream-contracts.js";
7
14
  import { invalidParamsFault, shapeRestFault } from "./operation-helpers.js";
8
15
  import {
9
16
  createEventEnvelopeSSEStream,
@@ -19,15 +26,7 @@ const INITIAL_CURSOR = "-1", MAX_WORKFLOW_SSE_REPLAY_EVENTS = 1000, MAXIMUM_WORK
19
26
  selector: z.enum(["events", "tokens"]).default("events"),
20
27
  fromCursor: cursorSchema.optional(),
21
28
  lastEventId: cursorSchema.optional()
22
- }), workflowEventsSseOutputSchema = z.custom(isAsyncIterable, "Expected async iterable workflow event stream"), workflowEventEnvelopeSchema = z.object({
23
- kind: z.string(),
24
- workflowId: z.string(),
25
- selector: z.enum(["events", "tokens"]),
26
- sequence: z.number(),
27
- cursor: z.string(),
28
- emittedAtMs: z.number(),
29
- payload: z.unknown()
30
- });
29
+ }), workflowEventsSseOutputSchema = z.custom(isAsyncIterable, "Expected async iterable workflow event stream");
31
30
  export const workflowEventsSseOperation = defineOperation({
32
31
  name: "weft.workflows.events.sse",
33
32
  mcpExposable: !1,
@@ -39,20 +38,7 @@ export const workflowEventsSseOperation = defineOperation({
39
38
  inputSchema: workflowEventsSseInputSchema,
40
39
  outputSchema: workflowEventsSseOutputSchema,
41
40
  eventSchema: workflowEventEnvelopeSchema,
42
- parameterizedAccess: {
43
- discriminator: "selector",
44
- defaultValue: "events",
45
- variants: [
46
- {
47
- value: "events",
48
- access: { kind: "scoped", scopes: { kind: "anyOf", scopes: ["events:read"] } }
49
- },
50
- {
51
- value: "tokens",
52
- access: { kind: "scoped", scopes: { kind: "anyOf", scopes: ["streams:read"] } }
53
- }
54
- ]
55
- },
41
+ parameterizedAccess: workflowEventParameterizedAccess,
56
42
  producibleFaults: ["InvalidParams", "UnsupportedTransport"],
57
43
  access: { kind: "authenticated" },
58
44
  authorize: async ({ input, principal }) => {
@@ -97,12 +83,6 @@ function isAsyncIterable(value) {
97
83
  return !1;
98
84
  return typeof value[Symbol.asyncIterator] === "function";
99
85
  }
100
- function isClosableWorkflowEventsIterable(value) {
101
- return "close" in value && typeof value.close === "function";
102
- }
103
- function isReplayAwareWorkflowEventsIterable(value) {
104
- return isClosableWorkflowEventsIterable(value) && "replayComplete" in value && value.replayComplete instanceof Promise;
105
- }
106
86
  function createWorkflowEventsIterable(input, context) {
107
87
  const feed = context.workflowEventFeed;
108
88
  if (feed === void 0)
@@ -110,51 +90,33 @@ function createWorkflowEventsIterable(input, context) {
110
90
  const lease = context.acquireWorkflowStreamConnection?.(input.workflowId);
111
91
  if (lease === null)
112
92
  throw maximumWorkflowStreamsFault();
113
- const controller = new AbortController, replayComplete = Promise.withResolvers();
114
- let closed = !1;
115
- const close = async () => {
116
- if (closed)
117
- return;
118
- closed = !0;
119
- controller.abort();
120
- lease?.release();
121
- }, fromCursor = input.lastEventId ?? input.fromCursor ?? INITIAL_CURSOR, subscribeOptions = {
122
- workflowId: input.workflowId,
123
- selector: input.selector,
124
- fromCursor,
125
- signal: controller.signal,
126
- replayLimit: MAX_WORKFLOW_SSE_REPLAY_EVENTS,
127
- onReplayComplete: () => replayComplete.resolve(),
128
- createReplayLimitError: (count, limit) => invalidParamsFault(`Workflow event replay window is ${count} events; maximum is ${limit}. Supply a more recent fromCursor.`)
129
- };
130
- let source;
131
- try {
132
- source = feed.subscribe(subscribeOptions);
133
- } catch (error) {
134
- close();
135
- throw error;
136
- }
137
- return {
138
- async* [Symbol.asyncIterator]() {
139
- try {
140
- for await (const envelope of source)
141
- yield envelope;
142
- } finally {
143
- await close();
144
- }
145
- },
146
- replayComplete: replayComplete.promise,
147
- close
148
- };
93
+ const fromCursor = input.lastEventId ?? input.fromCursor ?? INITIAL_CURSOR, controller = new AbortController;
94
+ return createReplayAwareClosableIterable((onReplayComplete) => {
95
+ const subscribeOptions = {
96
+ workflowId: input.workflowId,
97
+ selector: input.selector,
98
+ fromCursor,
99
+ signal: controller.signal,
100
+ replayLimit: MAX_WORKFLOW_SSE_REPLAY_EVENTS,
101
+ onReplayComplete,
102
+ createReplayLimitError: (count, limit) => invalidParamsFault(`Workflow event replay window is ${count} events; maximum is ${limit}. Supply a more recent fromCursor.`)
103
+ };
104
+ return feed.subscribe(subscribeOptions);
105
+ }, {
106
+ close: () => {
107
+ controller.abort();
108
+ lease?.release();
109
+ }
110
+ });
149
111
  }
150
112
  function shapeWorkflowEventsSseSuccess(output, request) {
151
- const close = isClosableWorkflowEventsIterable(output) ? () => output.close() : async () => {
113
+ const close = isClosableAsyncIterable(output) ? () => output.close() : async () => {
152
114
  return;
153
115
  };
154
116
  return new Response(createEventEnvelopeSSEStream({
155
117
  iterable: output,
156
118
  close,
157
- ...isReplayAwareWorkflowEventsIterable(output) ? { ready: output.replayComplete } : {},
119
+ ...isReplayAwareClosableIterable(output) ? { ready: output.replayComplete } : {},
158
120
  signal: request.signal
159
121
  }), {
160
122
  status: 200,
@@ -4,6 +4,11 @@ import { isAuthenticated } from "../principal.js";
4
4
  import {
5
5
  decodeCursor
6
6
  } from "../workflow-event-feed.js";
7
+ import {
8
+ createReplayAwareClosableIterable,
9
+ workflowEventEnvelopeSchema,
10
+ workflowEventParameterizedAccess
11
+ } from "./event-stream-contracts.js";
7
12
  import { invalidParamsFault } from "./operation-helpers.js";
8
13
  const INITIAL_SUBSCRIPTION_CURSOR = "-1", MAX_WORKFLOW_SUBSCRIPTION_REPLAY_EVENTS = 1000, workflowEventsSubscriptionInput = z.object({
9
14
  workflowId: z.string().min(1),
@@ -24,30 +29,9 @@ export const workflowEventsSubscriptionOperation = defineOperation({
24
29
  inputSchema: workflowEventsSubscriptionInput,
25
30
  outputSchema: workflowEventsSubscriptionEnvelope,
26
31
  producibleFaults: ["InvalidParams"],
27
- eventSchema: z.object({
28
- kind: z.string(),
29
- workflowId: z.string(),
30
- selector: z.enum(["events", "tokens"]),
31
- sequence: z.number(),
32
- cursor: z.string(),
33
- emittedAtMs: z.number(),
34
- payload: z.unknown()
35
- }),
32
+ eventSchema: workflowEventEnvelopeSchema,
36
33
  access: { kind: "authenticated" },
37
- parameterizedAccess: {
38
- discriminator: "selector",
39
- defaultValue: "events",
40
- variants: [
41
- {
42
- value: "events",
43
- access: { kind: "scoped", scopes: { kind: "anyOf", scopes: ["events:read"] } }
44
- },
45
- {
46
- value: "tokens",
47
- access: { kind: "scoped", scopes: { kind: "anyOf", scopes: ["streams:read"] } }
48
- }
49
- ]
50
- },
34
+ parameterizedAccess: workflowEventParameterizedAccess,
51
35
  authorize: async ({ input, principal }) => {
52
36
  const requiredScope = workflowSubscriptionScope(input.selector);
53
37
  if (!isAuthenticated(principal))
@@ -71,13 +55,11 @@ export const workflowEventsSubscriptionOperation = defineOperation({
71
55
  signal: controller.signal,
72
56
  replayLimit: MAX_WORKFLOW_SUBSCRIPTION_REPLAY_EVENTS,
73
57
  createReplayLimitError: (count, limit) => invalidParamsFault(`Workflow event replay window is ${count} events; maximum is ${limit}. Supply a more recent fromCursor.`)
74
- }, iterable = feed.subscribe(subscribeOptions);
58
+ }, iterable = createReplayAwareClosableIterable((onReplayComplete) => feed.subscribe({ ...subscribeOptions, onReplayComplete }), { close: () => controller.abort() });
75
59
  return {
76
60
  envelope: { subscriptionId: `sub_${crypto.randomUUID()}`, cursor: startingCursor },
77
61
  iterable,
78
- close: async () => {
79
- controller.abort();
80
- }
62
+ close: () => iterable.close()
81
63
  };
82
64
  }
83
65
  });
@@ -33,8 +33,8 @@ function validateTaskResultBody(body) {
33
33
  if (status !== "completed" && status !== "failed")
34
34
  return Response.json({ error: 'status must be "completed" or "failed"' }, { status: 400 });
35
35
  const rawAttemptToken = body.attemptToken;
36
- if (rawAttemptToken !== void 0 && (typeof rawAttemptToken !== "string" || rawAttemptToken === ""))
37
- return Response.json({ error: "attemptToken must be a non-empty string when present" }, { status: 400 });
36
+ if (typeof rawAttemptToken !== "string" || rawAttemptToken === "")
37
+ return Response.json({ error: "attemptToken must be a non-empty string" }, { status: 400 });
38
38
  return {
39
39
  operationId,
40
40
  status,
@@ -190,7 +190,5 @@ export async function handleTaskResultRequest(context, options, request, url, pr
190
190
  function isLongPollCompletionAuthorized(inflightRecord, validated) {
191
191
  if (validated.workerId === void 0 || inflightRecord.workerId !== validated.workerId)
192
192
  return !1;
193
- if (inflightRecord.attemptToken !== void 0 && validated.attemptToken !== inflightRecord.attemptToken)
194
- return !1;
195
- return !0;
193
+ return validated.attemptToken === inflightRecord.attemptToken;
196
194
  }
@@ -73,14 +73,8 @@ export interface InflightRecord extends TaskLifecycleFields {
73
73
  workflowId?: string | undefined;
74
74
  /** Durable token for the workflow run that dispatched this activity, when known. */
75
75
  workflowExecutionToken?: string | undefined;
76
- /**
77
- * Unique, unguessable token identifying this dispatch attempt. Rotated on every
78
- * (re-)dispatch because each dispatch writes a fresh InflightRecord. The
79
- * long-poll completion handler rejects a result whose echoed token does not
80
- * match this value. Optional for back-compatible decoding of records written
81
- * before this field existed; a missing token disables the check for that record.
82
- */
83
- attemptToken?: string | undefined;
76
+ /** Unique, unguessable token identifying this dispatch attempt. */
77
+ attemptToken: string;
84
78
  }
85
79
  /** Persisted record for a task in the resolved state. */
86
80
  export interface ResolvedRecord {
@@ -1,12 +1,16 @@
1
1
  import { decode, encode } from "../core/codec.js";
2
2
  import { KEYS } from "../storage/interface.js";
3
3
  import { buildResolvedRecord } from "./task-resolved-record.js";
4
- export async function getTaskState(storage, operationId) {
4
+ async function readTaskStateSnapshot(storage, operationId) {
5
5
  const [inflight, queued, resolved] = await Promise.all([
6
6
  storage.get(KEYS.operationInflight(operationId)),
7
7
  storage.get(KEYS.operationQueued(operationId)),
8
8
  storage.get(KEYS.operationResolved(operationId))
9
9
  ]);
10
+ return { inflight, queued, resolved };
11
+ }
12
+ export async function getTaskState(storage, operationId) {
13
+ const { inflight, queued, resolved } = await readTaskStateSnapshot(storage, operationId);
10
14
  if (inflight !== null)
11
15
  return "inflight";
12
16
  if (queued !== null)
@@ -16,11 +20,7 @@ export async function getTaskState(storage, operationId) {
16
20
  return null;
17
21
  }
18
22
  export async function getExclusiveTaskState(storage, operationId) {
19
- const [inflight, queued, resolved] = await Promise.all([
20
- storage.get(KEYS.operationInflight(operationId)),
21
- storage.get(KEYS.operationQueued(operationId)),
22
- storage.get(KEYS.operationResolved(operationId))
23
- ]), states = [];
23
+ const { inflight, queued, resolved } = await readTaskStateSnapshot(storage, operationId), states = [];
24
24
  if (inflight !== null)
25
25
  states.push("inflight");
26
26
  if (queued !== null)
@@ -37,11 +37,14 @@ export function isQueuedRecord(value) {
37
37
  const record = value;
38
38
  return typeof record.operationId === "string" && typeof record.activityName === "string" && typeof record.queue === "string" && typeof record.attempt === "number" && typeof record.visibilityTimeout === "number" && typeof record.queuedAt === "number";
39
39
  }
40
+ function isNonEmptyString(value) {
41
+ return typeof value === "string" && value.length > 0;
42
+ }
40
43
  export function isInflightRecord(value) {
41
44
  if (value === null || typeof value !== "object")
42
45
  return !1;
43
46
  const record = value;
44
- return typeof record.operationId === "string" && typeof record.activityName === "string" && typeof record.queue === "string" && typeof record.attempt === "number" && typeof record.visibilityTimeout === "number" && typeof record.workerId === "string" && typeof record.deadline === "number";
47
+ return typeof record.operationId === "string" && typeof record.activityName === "string" && typeof record.queue === "string" && typeof record.attempt === "number" && typeof record.visibilityTimeout === "number" && typeof record.workerId === "string" && typeof record.deadline === "number" && isNonEmptyString(record.attemptToken);
45
48
  }
46
49
  export function isResolvedRecord(value) {
47
50
  if (value === null || typeof value !== "object")
@@ -0,0 +1,5 @@
1
+ export type BoundedNdjsonResponseOptions = {
2
+ readonly maximumBytes: number;
3
+ readonly sizeLimitError: () => Error;
4
+ };
5
+ export declare function readBoundedNdjsonResponse(response: Response, options: BoundedNdjsonResponseOptions): AsyncIterable<string>;