@lostgradient/weft 0.6.0 → 0.8.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.
- package/README.md +7 -5
- package/dist/cli/generated/operation-client.generated.d.ts +1 -0
- package/dist/cli-main.js +63 -63
- package/dist/client/event-stream-options.d.ts +58 -0
- package/dist/client/event-stream-options.js +0 -0
- package/dist/client/event-stream.d.ts +27 -44
- package/dist/client/event-stream.js +21 -56
- package/dist/client/event-tail-lifecycle.d.ts +21 -0
- package/dist/client/event-tail-lifecycle.js +71 -0
- package/dist/client/event-tail.d.ts +3 -1
- package/dist/client/http-client.d.ts +4 -5
- package/dist/client/http-client.js +4 -1
- package/dist/client/http-handle.d.ts +4 -5
- package/dist/client/http-request.d.ts +7 -0
- package/dist/client/index.d.ts +2 -1
- package/dist/client/interface.d.ts +23 -7
- package/dist/client/local-handles.d.ts +2 -0
- package/dist/client/local-handles.js +10 -1
- package/dist/client/local.d.ts +3 -3
- package/dist/client/open-event-subscription.d.ts +3 -2
- package/dist/client/open-event-subscription.js +28 -1
- package/dist/client/sse-event-stream.d.ts +27 -0
- package/dist/client/sse-event-stream.js +276 -0
- package/dist/client/start-body.d.ts +2 -2
- package/dist/client/start-body.js +1 -0
- package/dist/core/context/activity-retry-state.d.ts +6 -5
- package/dist/core/context/activity-retry-state.js +31 -21
- package/dist/core/context/durable-activity.d.ts +117 -0
- package/dist/core/context/durable-activity.js +79 -0
- package/dist/core/context/operation-request.d.ts +15 -0
- package/dist/core/context/parallel-operations.js +1 -0
- package/dist/core/context/run-operation-cached-request.d.ts +8 -0
- package/dist/core/context/run-operation-cached-request.js +59 -0
- package/dist/core/context/run-operation.d.ts +23 -5
- package/dist/core/context/run-operation.js +62 -47
- package/dist/core/engine/activity-heartbeat-tracking.d.ts +8 -17
- package/dist/core/engine/activity-heartbeat-tracking.js +7 -1
- package/dist/core/engine/activity-reconciliation.d.ts +1 -0
- package/dist/core/engine/activity-reconciliation.js +7 -2
- package/dist/core/engine/anonymous-signal-sequence.js +6 -4
- package/dist/core/engine/async-activity-completion.d.ts +5 -7
- package/dist/core/engine/async-activity-completion.js +13 -9
- package/dist/core/engine/bulk-operations-purge.js +2 -1
- package/dist/core/engine/bulk-operations.js +8 -7
- package/dist/core/engine/callback-checkpoint-persistence.d.ts +3 -0
- package/dist/core/engine/callback-checkpoint-persistence.js +25 -0
- package/dist/core/engine/callback-creators-bundles.js +4 -1
- package/dist/core/engine/checkpoint-io.d.ts +4 -1
- package/dist/core/engine/checkpoint-io.js +19 -10
- package/dist/core/engine/completed-review-storage.d.ts +2 -1
- package/dist/core/engine/completed-review-storage.js +4 -3
- package/dist/core/engine/construction.js +3 -1
- package/dist/core/engine/engine-internal-types.d.ts +2 -0
- package/dist/core/engine/errors.d.ts +6 -6
- package/dist/core/engine/errors.js +1 -1
- package/dist/core/engine/handles.js +1 -1
- package/dist/core/engine/index.d.ts +37 -5
- package/dist/core/engine/index.js +7 -1
- package/dist/core/engine/internals.d.ts +2 -1
- package/dist/core/engine/lease-manager.js +2 -2
- package/dist/core/engine/lifecycle/recovered-services.js +8 -1
- package/dist/core/engine/lifecycle/start-or-signal-create.d.ts +2 -2
- package/dist/core/engine/lifecycle/start-or-signal-create.js +17 -15
- package/dist/core/engine/lifecycle/start-or-signal-resolution.d.ts +1 -1
- package/dist/core/engine/lifecycle/start-or-signal-resolution.js +23 -5
- package/dist/core/engine/lifecycle/start-or-signal.d.ts +6 -4
- package/dist/core/engine/lifecycle/start-or-signal.js +21 -4
- package/dist/core/engine/memo-durable-activity.d.ts +11 -0
- package/dist/core/engine/memo-durable-activity.js +282 -0
- package/dist/core/engine/operations-activity.d.ts +5 -1
- package/dist/core/engine/operations-activity.js +19 -7
- package/dist/core/engine/operations-data.d.ts +4 -1
- package/dist/core/engine/operations-data.js +3 -3
- package/dist/core/engine/reviews.js +7 -4
- package/dist/core/engine/schedule-run.js +12 -3
- package/dist/core/engine/schedule-timer.js +2 -0
- package/dist/core/engine/storage-io.js +2 -2
- package/dist/core/engine/termination/complete.js +3 -3
- package/dist/core/json.js +1 -1
- package/dist/core/scheduler/scheduler-class.js +2 -1
- package/dist/core/start-workflow-validation.d.ts +3 -4
- package/dist/core/start-workflow-validation.js +1 -1
- package/dist/core/timeouts.d.ts +13 -1
- package/dist/core/timeouts.js +4 -1
- package/dist/core/types/constants.d.ts +16 -0
- package/dist/core/types/options.d.ts +60 -12
- package/dist/core/types/services-resolution.d.ts +49 -3
- package/dist/core/weft-error.d.ts +1 -1
- package/dist/core/weft-error.js +3 -0
- package/dist/index.d.ts +5 -4
- package/dist/index.js +7 -0
- package/dist/json-schema.js +1 -1
- package/dist/mcp/cli.js +26 -26
- package/dist/server/asyncapi-channels.js +82 -16
- package/dist/server/handler/auth-context-principal.d.ts +19 -0
- package/dist/server/handler/auth-context-principal.js +25 -0
- package/dist/server/handler/index.d.ts +2 -1
- package/dist/server/handler/index.js +35 -18
- package/dist/server/handler/route-dispatch.d.ts +12 -22
- package/dist/server/handler/route-dispatch.js +30 -36
- package/dist/server/handler/sse-route-dispatch.d.ts +25 -0
- package/dist/server/handler/sse-route-dispatch.js +45 -0
- package/dist/server/handler.js +28 -28
- package/dist/server/index.d.ts +8 -7
- package/dist/server/index.js +17 -17
- package/dist/server/operations/fleet-events-sse.d.ts +11 -0
- package/dist/server/operations/fleet-events-sse.js +164 -0
- package/dist/server/operations/sse-stream.d.ts +43 -0
- package/dist/server/operations/sse-stream.js +152 -7
- package/dist/server/operations/start-or-signal-workflow.d.ts +2 -0
- package/dist/server/operations/start-or-signal-workflow.js +25 -14
- package/dist/server/operations/start-workflow-options.d.ts +9 -6
- package/dist/server/operations/start-workflow-options.js +13 -0
- package/dist/server/operations/workflow-events-sse.d.ts +17 -0
- package/dist/server/operations/workflow-events-sse.js +200 -0
- package/dist/server/rest-bindings.js +12 -0
- package/dist/server/runtime/authentication-bridge.js +4 -0
- package/dist/server/runtime/cors.d.ts +1 -1
- package/dist/server/runtime/cors.js +6 -1
- package/dist/server/runtime/websocket-stream.d.ts +4 -0
- package/dist/server/runtime/websocket-stream.js +20 -3
- package/dist/server/runtime/websocket-worker.js +7 -2
- package/dist/server/serve-internals.d.ts +28 -0
- package/dist/server/serve-internals.js +4 -2
- package/dist/server/workflow-event-feed.d.ts +1 -0
- package/dist/server/workflow-event-feed.js +17 -6
- package/dist/service-worker/index.js +28 -28
- package/dist/service-worker/setup.d.ts +18 -1
- package/dist/service-worker/setup.js +7 -4
- package/dist/storage/typed-storage.d.ts +1 -1
- package/dist/storage/typed-storage.js +1 -1
- package/dist/testing/index.js +27 -27
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { FleetEventEnvelope } from '../fleet-event-feed.ts';
|
|
2
|
+
import type { UnknownRestBinding } from '../rest-bindings.ts';
|
|
3
|
+
type FleetEventsSseOutput = AsyncIterable<FleetEventEnvelope>;
|
|
4
|
+
export declare const fleetEventsSseOperation: import("../operation-catalog.ts").OperationDefinition<{
|
|
5
|
+
workflowId?: string | undefined;
|
|
6
|
+
kind?: "human-review:requested" | "human-review:completed" | "alert:fired" | "alert:resolved" | "activity:started" | "activity:completed" | "activity:async-pending" | "activity:failed" | "task:dead-lettered" | "attributes:changed" | "schedule:fired" | "schedule:missed-fire" | "signal:received" | "signal:delivered" | "checkpoint:size-warning" | "development:warning" | "cleanup:warning" | "storage:size-reported" | "worker:connected" | "worker:disconnected" | "constraint:violated" | "update:received" | "update:completed" | "workflow:started" | "workflow:completed" | "workflow:failed" | "workflow:cancelled" | "workflow:timed-out" | "workflow:resumed" | "workflow:suspended" | "workflow:teardown" | undefined;
|
|
7
|
+
fromCursor?: string | undefined;
|
|
8
|
+
lastEventId?: string | undefined;
|
|
9
|
+
}, FleetEventsSseOutput, FleetEventEnvelope>;
|
|
10
|
+
export declare const fleetEventsSseRestBinding: UnknownRestBinding;
|
|
11
|
+
export {};
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { defineOperation } from "../operation-registry.js";
|
|
3
|
+
import { EVENTS_READ_EVENT_TYPES } from "../runtime/client-visible-events.js";
|
|
4
|
+
import {
|
|
5
|
+
decodeCursor
|
|
6
|
+
} from "../workflow-event-feed.js";
|
|
7
|
+
import { invalidParamsFault } from "./operation-helpers.js";
|
|
8
|
+
import {
|
|
9
|
+
createEventEnvelopeSSEStream,
|
|
10
|
+
readServerSentEventsCursor,
|
|
11
|
+
requireServerSentEventsAccept,
|
|
12
|
+
shapeServerSentEventsFault,
|
|
13
|
+
SSE_RESPONSE_HEADERS
|
|
14
|
+
} from "./sse-stream.js";
|
|
15
|
+
const INITIAL_CURSOR = "-1", MAX_FLEET_SSE_REPLAY_EVENTS = 1000, cursorSchema = z.string().refine((cursor) => decodeCursor(cursor) !== null, {
|
|
16
|
+
message: "Invalid cursor"
|
|
17
|
+
}), fleetEventKindSchema = z.enum(EVENTS_READ_EVENT_TYPES), fleetEventsSseInputSchema = z.object({
|
|
18
|
+
workflowId: z.string().min(1).optional(),
|
|
19
|
+
kind: fleetEventKindSchema.optional(),
|
|
20
|
+
fromCursor: cursorSchema.optional(),
|
|
21
|
+
lastEventId: cursorSchema.optional()
|
|
22
|
+
}), fleetEventsSseOutputSchema = z.custom(isAsyncIterable, "Expected async iterable fleet event stream"), fleetEventEnvelopeSchema = z.object({
|
|
23
|
+
kind: z.string(),
|
|
24
|
+
workflowId: z.string().optional(),
|
|
25
|
+
sequence: z.number(),
|
|
26
|
+
cursor: z.string(),
|
|
27
|
+
emittedAtMs: z.number(),
|
|
28
|
+
payload: z.unknown()
|
|
29
|
+
});
|
|
30
|
+
export const fleetEventsSseOperation = defineOperation({
|
|
31
|
+
name: "weft.events.sse",
|
|
32
|
+
mcpExposable: !1,
|
|
33
|
+
kind: "stream",
|
|
34
|
+
summary: "Stream fleet events as Server-Sent Events",
|
|
35
|
+
description: "Streams the server-level event feed across workflows. Optional filters narrow delivery by workflow id or event kind after the single fleet cursor has been applied. The stream emits `ping` keepalives while idle.",
|
|
36
|
+
destructive: !1,
|
|
37
|
+
tags: ["Events"],
|
|
38
|
+
inputSchema: fleetEventsSseInputSchema,
|
|
39
|
+
outputSchema: fleetEventsSseOutputSchema,
|
|
40
|
+
eventSchema: fleetEventEnvelopeSchema,
|
|
41
|
+
access: {
|
|
42
|
+
kind: "scoped",
|
|
43
|
+
scopes: { kind: "anyOf", scopes: ["events:read"] }
|
|
44
|
+
},
|
|
45
|
+
producibleFaults: ["InvalidParams", "UnsupportedTransport"],
|
|
46
|
+
discoverable: !0,
|
|
47
|
+
transports: { http: !0, jsonRpcHttp: !1, jsonRpcWebSocket: !1, jsonRpcStdio: !1 },
|
|
48
|
+
unknownKeyPolicy: { http: "strip", jsonRpc: "reject" },
|
|
49
|
+
invoke: async ({ input, engine }) => {
|
|
50
|
+
return createFleetEventsIterable(input, fleetEventStreamOperationContext(engine));
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
function fleetEventStreamOperationContext(engine) {
|
|
54
|
+
if (typeof engine !== "object" || engine === null)
|
|
55
|
+
return {};
|
|
56
|
+
return engine;
|
|
57
|
+
}
|
|
58
|
+
function unsupportedEventStreamContextFault(message) {
|
|
59
|
+
return {
|
|
60
|
+
code: "UnsupportedTransport",
|
|
61
|
+
message,
|
|
62
|
+
data: { transport: "http-rest", supported: ["http-rest"] }
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
function isAsyncIterable(value) {
|
|
66
|
+
if (typeof value !== "object" || value === null)
|
|
67
|
+
return !1;
|
|
68
|
+
return typeof value[Symbol.asyncIterator] === "function";
|
|
69
|
+
}
|
|
70
|
+
function isClosableFleetEventsIterable(value) {
|
|
71
|
+
return "close" in value && typeof value.close === "function";
|
|
72
|
+
}
|
|
73
|
+
function isReplayAwareFleetEventsIterable(value) {
|
|
74
|
+
return isClosableFleetEventsIterable(value) && "replayComplete" in value && value.replayComplete instanceof Promise;
|
|
75
|
+
}
|
|
76
|
+
function matchesFleetEventFilter(envelope, input) {
|
|
77
|
+
if (input.workflowId !== void 0 && envelope.workflowId !== input.workflowId)
|
|
78
|
+
return !1;
|
|
79
|
+
if (input.kind !== void 0 && envelope.kind !== input.kind)
|
|
80
|
+
return !1;
|
|
81
|
+
return !0;
|
|
82
|
+
}
|
|
83
|
+
function createFleetEventsIterable(input, context) {
|
|
84
|
+
const feed = context.fleetEventFeed;
|
|
85
|
+
if (feed === void 0)
|
|
86
|
+
throw unsupportedEventStreamContextFault("fleet event SSE requires a fleet event feed");
|
|
87
|
+
const controller = new AbortController, replayComplete = Promise.withResolvers();
|
|
88
|
+
let closed = !1;
|
|
89
|
+
const close = async () => {
|
|
90
|
+
if (closed)
|
|
91
|
+
return;
|
|
92
|
+
closed = !0;
|
|
93
|
+
controller.abort();
|
|
94
|
+
}, subscribeOptions = {
|
|
95
|
+
fromCursor: input.lastEventId ?? input.fromCursor ?? INITIAL_CURSOR,
|
|
96
|
+
signal: controller.signal,
|
|
97
|
+
replayLimit: MAX_FLEET_SSE_REPLAY_EVENTS,
|
|
98
|
+
filterEnvelope: (envelope) => matchesFleetEventFilter(envelope, input),
|
|
99
|
+
onReplayComplete: () => replayComplete.resolve(),
|
|
100
|
+
createReplayLimitError: (count, limit) => invalidParamsFault(`Fleet event replay window is ${count} matching events; maximum is ${limit}. Supply a more recent fromCursor.`)
|
|
101
|
+
};
|
|
102
|
+
let source;
|
|
103
|
+
try {
|
|
104
|
+
source = feed.subscribe(subscribeOptions);
|
|
105
|
+
} catch (error) {
|
|
106
|
+
close();
|
|
107
|
+
throw error;
|
|
108
|
+
}
|
|
109
|
+
return {
|
|
110
|
+
async* [Symbol.asyncIterator]() {
|
|
111
|
+
try {
|
|
112
|
+
for await (const envelope of source)
|
|
113
|
+
yield envelope;
|
|
114
|
+
} finally {
|
|
115
|
+
await close();
|
|
116
|
+
}
|
|
117
|
+
},
|
|
118
|
+
replayComplete: replayComplete.promise,
|
|
119
|
+
close
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
function shapeFleetEventsSseSuccess(output, request) {
|
|
123
|
+
const close = isClosableFleetEventsIterable(output) ? () => output.close() : async () => {
|
|
124
|
+
return;
|
|
125
|
+
};
|
|
126
|
+
return new Response(createEventEnvelopeSSEStream({
|
|
127
|
+
iterable: output,
|
|
128
|
+
close,
|
|
129
|
+
...isReplayAwareFleetEventsIterable(output) ? { ready: output.replayComplete } : {},
|
|
130
|
+
signal: request.signal
|
|
131
|
+
}), {
|
|
132
|
+
status: 200,
|
|
133
|
+
headers: SSE_RESPONSE_HEADERS
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
function shapeFleetEventsSseFault(fault) {
|
|
137
|
+
return shapeServerSentEventsFault(fault);
|
|
138
|
+
}
|
|
139
|
+
export const fleetEventsSseRestBinding = {
|
|
140
|
+
method: "GET",
|
|
141
|
+
path: "/v1/events/sse",
|
|
142
|
+
pathParamNames: [],
|
|
143
|
+
operationName: "weft.events.sse",
|
|
144
|
+
transportKind: "sse",
|
|
145
|
+
inputSources: {
|
|
146
|
+
workflowId: { kind: "query", queryParam: "workflowId" },
|
|
147
|
+
kind: { kind: "query", queryParam: "kind" },
|
|
148
|
+
fromCursor: { kind: "query", queryParam: "fromCursor" },
|
|
149
|
+
lastEventId: { kind: "header", headerName: "Last-Event-ID" }
|
|
150
|
+
},
|
|
151
|
+
extractInput: async (request) => {
|
|
152
|
+
requireServerSentEventsAccept(request);
|
|
153
|
+
const url = new URL(request.url), workflowId = url.searchParams.get("workflowId") ?? void 0, kind = url.searchParams.get("kind") ?? void 0, lastEventId = readServerSentEventsCursor(request.headers.get("Last-Event-ID")), fromCursor = lastEventId === void 0 ? readServerSentEventsCursor(url.searchParams.get("fromCursor")) : void 0;
|
|
154
|
+
return {
|
|
155
|
+
...workflowId === void 0 ? {} : { workflowId },
|
|
156
|
+
...kind === void 0 ? {} : { kind },
|
|
157
|
+
...fromCursor === void 0 ? {} : { fromCursor },
|
|
158
|
+
...lastEventId === void 0 ? {} : { lastEventId }
|
|
159
|
+
};
|
|
160
|
+
},
|
|
161
|
+
success: { kind: "streaming", mediaType: "text/event-stream" },
|
|
162
|
+
shapeSuccess: shapeFleetEventsSseSuccess,
|
|
163
|
+
shapeFault: shapeFleetEventsSseFault
|
|
164
|
+
};
|
|
@@ -1,4 +1,46 @@
|
|
|
1
1
|
import type { StoredStreamChunk } from '../../core/context.ts';
|
|
2
|
+
import type { OperationFault } from '../operation-fault.ts';
|
|
3
|
+
import { type Cursor } from '../workflow-event-feed.ts';
|
|
4
|
+
export type ServerSentEventTimerScheduler = (callback: () => void, intervalMs: number) => () => void;
|
|
5
|
+
export type ServerSentEventHeartbeatOptions = {
|
|
6
|
+
readonly intervalMs: number;
|
|
7
|
+
readonly schedule?: ServerSentEventTimerScheduler;
|
|
8
|
+
readonly now?: () => number;
|
|
9
|
+
};
|
|
10
|
+
type ServerSentEventEnvelope = {
|
|
11
|
+
readonly kind: string;
|
|
12
|
+
readonly cursor: string;
|
|
13
|
+
readonly emittedAtMs: number;
|
|
14
|
+
};
|
|
15
|
+
type EventEnvelopeSSEStreamOptions<TEnvelope extends ServerSentEventEnvelope> = {
|
|
16
|
+
readonly iterable: AsyncIterable<TEnvelope>;
|
|
17
|
+
readonly close: () => Promise<void>;
|
|
18
|
+
readonly ready?: Promise<void>;
|
|
19
|
+
readonly heartbeat?: ServerSentEventHeartbeatOptions;
|
|
20
|
+
readonly signal?: AbortSignal;
|
|
21
|
+
};
|
|
22
|
+
export declare const SSE_ACCEPT_REQUIRED_MESSAGE = "Accept header must include text/event-stream";
|
|
23
|
+
export declare function acceptsServerSentEvents(acceptHeader: string | null): boolean;
|
|
24
|
+
export declare function requireServerSentEventsAccept(request: Request): void;
|
|
25
|
+
/**
|
|
26
|
+
* Validate an SSE reconnect cursor taken from a `Last-Event-ID` header or
|
|
27
|
+
* `fromCursor` query parameter. Returns `undefined` for an absent value and
|
|
28
|
+
* throws an `InvalidParams` fault for a malformed one. Shared by every SSE
|
|
29
|
+
* binding so cursor validation stays defined in exactly one place.
|
|
30
|
+
*/
|
|
31
|
+
export declare function readServerSentEventsCursor(value: string | null): Cursor | undefined;
|
|
32
|
+
export declare function shapeServerSentEventsFault(fault: OperationFault): Response;
|
|
33
|
+
/** Format a single Server-Sent Events message. */
|
|
34
|
+
export declare function formatServerSentEvent(event: {
|
|
35
|
+
readonly id?: string;
|
|
36
|
+
readonly event?: string;
|
|
37
|
+
readonly data: string;
|
|
38
|
+
}): string;
|
|
39
|
+
/**
|
|
40
|
+
* Convert a replay-plus-live event envelope iterable into an SSE byte stream.
|
|
41
|
+
* Event envelopes advance the client cursor via `id`; heartbeat pings do not.
|
|
42
|
+
*/
|
|
43
|
+
export declare function createEventEnvelopeSSEStream<TEnvelope extends ServerSentEventEnvelope>(options: EventEnvelopeSSEStreamOptions<TEnvelope>): ReadableStream<Uint8Array>;
|
|
2
44
|
/**
|
|
3
45
|
* Wrap a list of stored stream chunks as a Server-Sent Events stream. Each
|
|
4
46
|
* chunk gets emitted as a `token` event keyed by `chunk.sequence`; chunks
|
|
@@ -11,3 +53,4 @@ import type { StoredStreamChunk } from '../../core/context.ts';
|
|
|
11
53
|
export declare function createStoredChunkSSEStream(chunks: ReadonlyArray<StoredStreamChunk>, mapChunkToText: (chunk: StoredStreamChunk) => string | null): ReadableStream<Uint8Array>;
|
|
12
54
|
/** Standard headers for an SSE response. */
|
|
13
55
|
export declare const SSE_RESPONSE_HEADERS: Readonly<Record<string, string>>;
|
|
56
|
+
export {};
|
|
@@ -1,5 +1,39 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
import { decodeCursor } from "../workflow-event-feed.js";
|
|
2
|
+
import {
|
|
3
|
+
invalidParamsFault,
|
|
4
|
+
isOperationFault,
|
|
5
|
+
jsonErrorResponse,
|
|
6
|
+
shapeRestFault
|
|
7
|
+
} from "./operation-helpers.js";
|
|
8
|
+
const textEncoder = new TextEncoder, DEFAULT_SSE_HEARTBEAT_INTERVAL_MS = 15000;
|
|
9
|
+
export const SSE_ACCEPT_REQUIRED_MESSAGE = "Accept header must include text/event-stream";
|
|
10
|
+
export function acceptsServerSentEvents(acceptHeader) {
|
|
11
|
+
if (acceptHeader === null)
|
|
12
|
+
return !1;
|
|
13
|
+
for (const entry of acceptHeader.split(",")) {
|
|
14
|
+
const mediaType = entry.split(";", 1)[0]?.trim().toLowerCase();
|
|
15
|
+
if (mediaType === "text/event-stream" || mediaType === "text/*" || mediaType === "*/*")
|
|
16
|
+
return !0;
|
|
17
|
+
}
|
|
18
|
+
return !1;
|
|
19
|
+
}
|
|
20
|
+
export function requireServerSentEventsAccept(request) {
|
|
21
|
+
if (!acceptsServerSentEvents(request.headers.get("Accept")))
|
|
22
|
+
throw invalidParamsFault(SSE_ACCEPT_REQUIRED_MESSAGE);
|
|
23
|
+
}
|
|
24
|
+
export function readServerSentEventsCursor(value) {
|
|
25
|
+
if (value === null)
|
|
26
|
+
return;
|
|
27
|
+
if (decodeCursor(value) === null)
|
|
28
|
+
throw invalidParamsFault("Invalid cursor");
|
|
29
|
+
return value;
|
|
30
|
+
}
|
|
31
|
+
export function shapeServerSentEventsFault(fault) {
|
|
32
|
+
if (fault.code === "InvalidParams" && fault.message === SSE_ACCEPT_REQUIRED_MESSAGE)
|
|
33
|
+
return jsonErrorResponse(fault.message, 406);
|
|
34
|
+
return shapeRestFault(fault);
|
|
35
|
+
}
|
|
36
|
+
export function formatServerSentEvent(event) {
|
|
3
37
|
const lines = [];
|
|
4
38
|
if (event.id !== void 0)
|
|
5
39
|
lines.push(`id: ${event.id}`);
|
|
@@ -14,6 +48,116 @@ function formatSSE(event) {
|
|
|
14
48
|
`) + `
|
|
15
49
|
`;
|
|
16
50
|
}
|
|
51
|
+
function scheduleInterval(callback, intervalMs) {
|
|
52
|
+
const timer = setInterval(callback, intervalMs);
|
|
53
|
+
return () => {
|
|
54
|
+
clearInterval(timer);
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
function encodeFrame(frame) {
|
|
58
|
+
return textEncoder.encode(formatServerSentEvent(frame));
|
|
59
|
+
}
|
|
60
|
+
function sanitizedErrorFrame(error) {
|
|
61
|
+
if (isOperationFault(error) && error.code !== "EngineFailure")
|
|
62
|
+
return {
|
|
63
|
+
event: "error",
|
|
64
|
+
data: JSON.stringify({
|
|
65
|
+
code: error.code,
|
|
66
|
+
message: error.message,
|
|
67
|
+
data: error.data
|
|
68
|
+
})
|
|
69
|
+
};
|
|
70
|
+
return {
|
|
71
|
+
event: "error",
|
|
72
|
+
data: JSON.stringify({
|
|
73
|
+
code: "EngineFailure",
|
|
74
|
+
message: "Internal server error"
|
|
75
|
+
})
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
function heartbeatOptions(options) {
|
|
79
|
+
return {
|
|
80
|
+
intervalMs: options?.intervalMs ?? DEFAULT_SSE_HEARTBEAT_INTERVAL_MS,
|
|
81
|
+
schedule: options?.schedule ?? scheduleInterval,
|
|
82
|
+
now: options?.now ?? Date.now
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
export function createEventEnvelopeSSEStream(options) {
|
|
86
|
+
let cleanup = null;
|
|
87
|
+
return new ReadableStream({
|
|
88
|
+
start(controller) {
|
|
89
|
+
let closed = !1, clearHeartbeat = null;
|
|
90
|
+
const closeResources = async () => {
|
|
91
|
+
if (closed)
|
|
92
|
+
return;
|
|
93
|
+
closed = !0;
|
|
94
|
+
if (clearHeartbeat !== null) {
|
|
95
|
+
clearHeartbeat();
|
|
96
|
+
clearHeartbeat = null;
|
|
97
|
+
}
|
|
98
|
+
options.signal?.removeEventListener("abort", onAbort);
|
|
99
|
+
try {
|
|
100
|
+
await options.close();
|
|
101
|
+
} catch {}
|
|
102
|
+
};
|
|
103
|
+
cleanup = closeResources;
|
|
104
|
+
const enqueue = (frame) => {
|
|
105
|
+
if (closed)
|
|
106
|
+
return;
|
|
107
|
+
try {
|
|
108
|
+
controller.enqueue(encodeFrame(frame));
|
|
109
|
+
} catch {
|
|
110
|
+
closeResources();
|
|
111
|
+
}
|
|
112
|
+
}, onAbort = () => {
|
|
113
|
+
closeResources();
|
|
114
|
+
};
|
|
115
|
+
if (options.signal?.aborted) {
|
|
116
|
+
closeResources();
|
|
117
|
+
controller.close();
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
options.signal?.addEventListener("abort", onAbort, { once: !0 });
|
|
121
|
+
const heartbeat = heartbeatOptions(options.heartbeat), enqueuePing = (data) => {
|
|
122
|
+
enqueue({
|
|
123
|
+
event: "ping",
|
|
124
|
+
data: JSON.stringify(data)
|
|
125
|
+
});
|
|
126
|
+
};
|
|
127
|
+
if (options.ready !== void 0)
|
|
128
|
+
options.ready.then(() => {
|
|
129
|
+
enqueuePing({ emittedAtMs: heartbeat.now(), replayComplete: !0 });
|
|
130
|
+
return;
|
|
131
|
+
}, () => {
|
|
132
|
+
return;
|
|
133
|
+
});
|
|
134
|
+
if (heartbeat.intervalMs > 0)
|
|
135
|
+
clearHeartbeat = heartbeat.schedule(() => {
|
|
136
|
+
enqueuePing({ emittedAtMs: heartbeat.now() });
|
|
137
|
+
}, heartbeat.intervalMs);
|
|
138
|
+
(async () => {
|
|
139
|
+
try {
|
|
140
|
+
for await (const envelope of options.iterable)
|
|
141
|
+
enqueue({
|
|
142
|
+
id: envelope.cursor,
|
|
143
|
+
event: envelope.kind,
|
|
144
|
+
data: JSON.stringify(envelope)
|
|
145
|
+
});
|
|
146
|
+
} catch (error) {
|
|
147
|
+
enqueue(sanitizedErrorFrame(error));
|
|
148
|
+
} finally {
|
|
149
|
+
await closeResources();
|
|
150
|
+
try {
|
|
151
|
+
controller.close();
|
|
152
|
+
} catch {}
|
|
153
|
+
}
|
|
154
|
+
})();
|
|
155
|
+
},
|
|
156
|
+
cancel() {
|
|
157
|
+
return cleanup?.();
|
|
158
|
+
}
|
|
159
|
+
});
|
|
160
|
+
}
|
|
17
161
|
export function createStoredChunkSSEStream(chunks, mapChunkToText) {
|
|
18
162
|
return new ReadableStream({
|
|
19
163
|
start(controller) {
|
|
@@ -21,13 +165,13 @@ export function createStoredChunkSSEStream(chunks, mapChunkToText) {
|
|
|
21
165
|
const text = mapChunkToText(chunk);
|
|
22
166
|
if (text === null)
|
|
23
167
|
continue;
|
|
24
|
-
controller.enqueue(textEncoder.encode(
|
|
168
|
+
controller.enqueue(textEncoder.encode(formatServerSentEvent({
|
|
25
169
|
id: String(chunk.sequence),
|
|
26
170
|
event: "token",
|
|
27
171
|
data: text
|
|
28
172
|
})));
|
|
29
173
|
}
|
|
30
|
-
controller.enqueue(textEncoder.encode(
|
|
174
|
+
controller.enqueue(textEncoder.encode(formatServerSentEvent({
|
|
31
175
|
event: "done",
|
|
32
176
|
data: ""
|
|
33
177
|
})));
|
|
@@ -36,7 +180,8 @@ export function createStoredChunkSSEStream(chunks, mapChunkToText) {
|
|
|
36
180
|
});
|
|
37
181
|
}
|
|
38
182
|
export const SSE_RESPONSE_HEADERS = {
|
|
39
|
-
"Content-Type": "text/event-stream",
|
|
40
|
-
"Cache-Control": "no-cache",
|
|
41
|
-
Connection: "keep-alive"
|
|
183
|
+
"Content-Type": "text/event-stream; charset=utf-8",
|
|
184
|
+
"Cache-Control": "no-cache, no-transform",
|
|
185
|
+
Connection: "keep-alive",
|
|
186
|
+
"X-Accel-Buffering": "no"
|
|
42
187
|
};
|
|
@@ -13,6 +13,7 @@ declare const startOrSignalWorkflowInput: z.ZodObject<{
|
|
|
13
13
|
tags: z.ZodOptional<z.ZodUnknown>;
|
|
14
14
|
idempotencyKey: z.ZodOptional<z.ZodUnknown>;
|
|
15
15
|
searchAttributes: z.ZodOptional<z.ZodUnknown>;
|
|
16
|
+
onTerminalConflict: z.ZodOptional<z.ZodUnknown>;
|
|
16
17
|
}, z.core.$strip>;
|
|
17
18
|
declare const startOrSignalWorkflowOutput: z.ZodObject<{
|
|
18
19
|
id: z.ZodString;
|
|
@@ -36,6 +37,7 @@ export declare const startOrSignalWorkflowOperation: import("../operation-catalo
|
|
|
36
37
|
tags?: unknown;
|
|
37
38
|
idempotencyKey?: unknown;
|
|
38
39
|
searchAttributes?: unknown;
|
|
40
|
+
onTerminalConflict?: unknown;
|
|
39
41
|
}, {
|
|
40
42
|
id: string;
|
|
41
43
|
outcome: "started" | "signalled";
|
|
@@ -2,14 +2,15 @@ import { z } from "zod";
|
|
|
2
2
|
import {
|
|
3
3
|
IdempotencyKeyPurgedError,
|
|
4
4
|
StartOrSignalConflictError,
|
|
5
|
-
WorkflowNotRegisteredError
|
|
5
|
+
WorkflowNotRegisteredError,
|
|
6
|
+
WorkflowTeardownPendingError
|
|
6
7
|
} from "../../core/engine/errors.js";
|
|
7
8
|
import { runtimeWorkflowEngine } from "../../core/runtime-workflow-engine.js";
|
|
8
9
|
import { isSignalIdWithinByteLimit } from "../../core/signal-id.js";
|
|
9
10
|
import { StartWorkflowValidationError } from "../../core/start-workflow-validation.js";
|
|
10
11
|
import { defineOperation } from "../operation-registry.js";
|
|
11
12
|
import { invalidParamsFault, shapeRestFault } from "./operation-helpers.js";
|
|
12
|
-
import {
|
|
13
|
+
import { buildStartOrSignalWorkflowOptions } from "./start-workflow-options.js";
|
|
13
14
|
import {
|
|
14
15
|
extractSharedStartWorkflowRestFields,
|
|
15
16
|
parseStartWorkflowRequestRecord
|
|
@@ -26,7 +27,8 @@ const startOrSignalWorkflowInput = z.object({
|
|
|
26
27
|
startAfter: z.unknown().optional(),
|
|
27
28
|
tags: z.unknown().optional(),
|
|
28
29
|
idempotencyKey: z.unknown().optional(),
|
|
29
|
-
searchAttributes: z.unknown().optional()
|
|
30
|
+
searchAttributes: z.unknown().optional(),
|
|
31
|
+
onTerminalConflict: z.unknown().optional()
|
|
30
32
|
}), startOrSignalWorkflowOutput = z.object({
|
|
31
33
|
id: z.string(),
|
|
32
34
|
outcome: z.enum(["started", "signalled"])
|
|
@@ -36,12 +38,12 @@ function validateStartOrSignalWorkflowInput(input, lookupSearchAttributeSchema)
|
|
|
36
38
|
throw invalidParamsFault("Missing required field: type");
|
|
37
39
|
let options;
|
|
38
40
|
try {
|
|
39
|
-
options =
|
|
41
|
+
options = buildStartOrSignalWorkflowOptions(input, lookupSearchAttributeSchema(input.type));
|
|
40
42
|
} catch (error) {
|
|
41
43
|
const message = error instanceof Error ? error.message : String(error);
|
|
42
44
|
throw invalidParamsFault(message);
|
|
43
45
|
}
|
|
44
|
-
assertConvergenceTokenProvided(input.signalId, options
|
|
46
|
+
assertConvergenceTokenProvided(input.signalId, options);
|
|
45
47
|
const signal = {
|
|
46
48
|
name: input.signalName,
|
|
47
49
|
payload: input.signalPayload,
|
|
@@ -49,22 +51,29 @@ function validateStartOrSignalWorkflowInput(input, lookupSearchAttributeSchema)
|
|
|
49
51
|
};
|
|
50
52
|
return { type: input.type, signal, options };
|
|
51
53
|
}
|
|
52
|
-
function assertConvergenceTokenProvided(signalId,
|
|
53
|
-
if (
|
|
54
|
+
function assertConvergenceTokenProvided(signalId, options) {
|
|
55
|
+
if (options.onTerminalConflict === "start-new" && signalId === void 0)
|
|
56
|
+
throw invalidParamsFault("startOrSignal options.onTerminalConflict: 'start-new' requires signalId so concurrent restart-capable callers converge on one deterministic initial signal.");
|
|
57
|
+
if (signalId === void 0 && options.idempotencyKey === void 0)
|
|
54
58
|
throw invalidParamsFault("startOrSignal requires either signalId or idempotencyKey to identify the signal to deliver. (Concurrent callers converge on one workflow and one signal only with a shared idempotencyKey, or a shared id plus signalId; a bare signalId starts a fresh run per caller.)");
|
|
55
|
-
if (signalId !== void 0 && idempotencyKey !== void 0)
|
|
59
|
+
if (signalId !== void 0 && options.idempotencyKey !== void 0)
|
|
56
60
|
throw invalidParamsFault("startOrSignal does not accept both signalId and idempotencyKey: the signal id derives from the idempotency key for convergence. Provide exactly one.");
|
|
57
61
|
}
|
|
58
62
|
function resolveStartOrSignalWorkflowFault(error) {
|
|
59
63
|
const message = error instanceof Error ? error.message : String(error);
|
|
60
64
|
if (error instanceof WorkflowNotRegisteredError)
|
|
61
65
|
throw invalidParamsFault(message, error.code);
|
|
62
|
-
if (error instanceof StartOrSignalConflictError || error instanceof IdempotencyKeyPurgedError)
|
|
66
|
+
if (error instanceof StartOrSignalConflictError || error instanceof IdempotencyKeyPurgedError || error instanceof WorkflowTeardownPendingError) {
|
|
67
|
+
const data = {
|
|
68
|
+
reason: message,
|
|
69
|
+
weftCode: error.code
|
|
70
|
+
};
|
|
63
71
|
throw {
|
|
64
72
|
code: "Conflict",
|
|
65
73
|
message,
|
|
66
|
-
data
|
|
74
|
+
data
|
|
67
75
|
};
|
|
76
|
+
}
|
|
68
77
|
if (error instanceof StartWorkflowValidationError)
|
|
69
78
|
throw invalidParamsFault(message);
|
|
70
79
|
throw {
|
|
@@ -77,8 +86,8 @@ export const startOrSignalWorkflowOperation = defineOperation({
|
|
|
77
86
|
name: "weft.workflows.startorsignal",
|
|
78
87
|
mcpExposable: !1,
|
|
79
88
|
summary: "Atomically start a workflow or signal it if it already exists",
|
|
80
|
-
description: 'Start a workflow or deliver a signal to it if it already exists (signal-with-start). An absent target is created and delivered the signal in one batch; a non-terminal target (running, pending, or suspended) is signalled; a terminal target faults with Conflict
|
|
81
|
-
destructive: !
|
|
89
|
+
description: 'Start a workflow or deliver a signal to it if it already exists (signal-with-start). An absent target is created and delivered the signal in one batch; a non-terminal target (running, pending, or suspended) is signalled; a terminal target faults with Conflict unless `onTerminalConflict: "start-new"` is supplied with `id` and `signalId`, in which case the prior terminal run is replaced through the shared terminal-replacement path and the initial signal is delivered to the fresh run. A spent `idempotencyKey` whose run was purged or swept by retention also faults with Conflict. Requires `type`, `signalName`, and exactly one of `signalId` or `idempotencyKey` (not both). Accepts `input`, `signalPayload`, and the non-idempotency start options from weft.workflows.start (`id`, `executionTimeout`, `startAt`/`startAfter`, `tags`, `searchAttributes`) plus `onTerminalConflict`; `idempotencyKey` is governed by the "exactly one of" rule above and cannot be combined with restart. Returns the workflow `id`. Concurrent callers converge on one workflow and one signal only with a shared `idempotencyKey`, or a shared `id` plus `signalId`; a bare `signalId` (no `id`/`idempotencyKey`) starts a fresh run per caller and does not converge.',
|
|
90
|
+
destructive: !0,
|
|
82
91
|
tags: ["Workflows", "Signals"],
|
|
83
92
|
inputSchema: startOrSignalWorkflowInput,
|
|
84
93
|
outputSchema: startOrSignalWorkflowOutput,
|
|
@@ -114,7 +123,8 @@ export const startOrSignalWorkflowOperation = defineOperation({
|
|
|
114
123
|
startAfter: { kind: "body-field", bodyField: "startAfter" },
|
|
115
124
|
tags: { kind: "body-field", bodyField: "tags" },
|
|
116
125
|
idempotencyKey: { kind: "body-field", bodyField: "idempotencyKey" },
|
|
117
|
-
searchAttributes: { kind: "body-field", bodyField: "searchAttributes" }
|
|
126
|
+
searchAttributes: { kind: "body-field", bodyField: "searchAttributes" },
|
|
127
|
+
onTerminalConflict: { kind: "body-field", bodyField: "onTerminalConflict" }
|
|
118
128
|
},
|
|
119
129
|
extractInput: async (request, _pathParams, context) => {
|
|
120
130
|
const record = await parseStartWorkflowRequestRecord(request, context);
|
|
@@ -122,7 +132,8 @@ export const startOrSignalWorkflowOperation = defineOperation({
|
|
|
122
132
|
...extractSharedStartWorkflowRestFields(record),
|
|
123
133
|
signalName: record.signalName,
|
|
124
134
|
signalPayload: record.signalPayload,
|
|
125
|
-
signalId: record.signalId
|
|
135
|
+
signalId: record.signalId,
|
|
136
|
+
onTerminalConflict: record.onTerminalConflict
|
|
126
137
|
};
|
|
127
138
|
},
|
|
128
139
|
success: { kind: "json", status: 201 },
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { SearchAttributeSchema, SearchAttributeValue, StartOptions } from '../../core/types.ts';
|
|
1
|
+
import type { SearchAttributeSchema, SearchAttributeValue, StartOptions, StartOrSignalOptions } from '../../core/types.ts';
|
|
2
2
|
/**
|
|
3
3
|
* The raw, transport-supplied start-option fields shared by `weft.workflows.start`
|
|
4
4
|
* and `weft.workflows.startorsignal`. Each is `unknown` because validation happens
|
|
@@ -14,6 +14,9 @@ export type SharedStartWorkflowOptionInput = {
|
|
|
14
14
|
idempotencyKey?: unknown;
|
|
15
15
|
searchAttributes?: unknown;
|
|
16
16
|
};
|
|
17
|
+
export type StartOrSignalWorkflowOptionInput = SharedStartWorkflowOptionInput & {
|
|
18
|
+
onTerminalConflict?: unknown;
|
|
19
|
+
};
|
|
17
20
|
/**
|
|
18
21
|
* Coerce the shared start-option fields into a validated {@link StartOptions}.
|
|
19
22
|
* Both start operations call this so they cannot drift in how they validate `id`,
|
|
@@ -21,13 +24,13 @@ export type SharedStartWorkflowOptionInput = {
|
|
|
21
24
|
* `searchAttributes` (a new field added here covers both surfaces at once). Throws
|
|
22
25
|
* {@link StartWorkflowValidationError} on any malformed field.
|
|
23
26
|
*
|
|
24
|
-
* `onTerminalConflict` is intentionally NOT part of this shared path:
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
* REST/JSON-RPC. See follow-up issue #489.
|
|
27
|
+
* `onTerminalConflict` is intentionally NOT part of this shared path:
|
|
28
|
+
* `weft.workflows.start` remains non-restart-capable over transport. The
|
|
29
|
+
* start-or-signal operation adds its narrower restart policy through
|
|
30
|
+
* {@link buildStartOrSignalWorkflowOptions}.
|
|
29
31
|
*/
|
|
30
32
|
export declare function buildSharedStartWorkflowOptions(input: SharedStartWorkflowOptionInput, searchAttributeSchema: SearchAttributeSchema | undefined): StartOptions;
|
|
33
|
+
export declare function buildStartOrSignalWorkflowOptions(input: StartOrSignalWorkflowOptionInput, searchAttributeSchema: SearchAttributeSchema | undefined): StartOrSignalOptions;
|
|
31
34
|
/**
|
|
32
35
|
* Coerce a transport-supplied `searchAttributes` object into validated
|
|
33
36
|
* {@link SearchAttributeValue}s. Shared by `weft.workflows.start` and
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { validateAttributeType } from "../../core/search-attributes.js";
|
|
2
2
|
import {
|
|
3
3
|
assertExclusiveStartWorkflowOptions,
|
|
4
|
+
assertValidOnTerminalConflict,
|
|
4
5
|
coerceStartWorkflowDuration,
|
|
5
6
|
coerceStartWorkflowId,
|
|
6
7
|
coerceStartWorkflowIdempotencyKey,
|
|
@@ -27,6 +28,18 @@ export function buildSharedStartWorkflowOptions(input, searchAttributeSchema) {
|
|
|
27
28
|
assertExclusiveStartWorkflowOptions(options.startAt, options.startAfter);
|
|
28
29
|
return options;
|
|
29
30
|
}
|
|
31
|
+
export function buildStartOrSignalWorkflowOptions(input, searchAttributeSchema) {
|
|
32
|
+
const options = buildSharedStartWorkflowOptions(input, searchAttributeSchema);
|
|
33
|
+
if (input.onTerminalConflict !== void 0)
|
|
34
|
+
options.onTerminalConflict = coerceStartOrSignalOnTerminalConflict(input.onTerminalConflict, 'Field "onTerminalConflict"');
|
|
35
|
+
assertValidOnTerminalConflict(options);
|
|
36
|
+
return options;
|
|
37
|
+
}
|
|
38
|
+
function coerceStartOrSignalOnTerminalConflict(value, fieldName) {
|
|
39
|
+
if (value === "error" || value === "start-new")
|
|
40
|
+
return value;
|
|
41
|
+
throw new StartWorkflowValidationError(`${fieldName} must be "error" or "start-new"`);
|
|
42
|
+
}
|
|
30
43
|
export function coerceStartWorkflowSearchAttributes(value, fieldName, schema) {
|
|
31
44
|
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
32
45
|
throw new StartWorkflowValidationError(`${fieldName} must be an object`);
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { UnknownRestBinding } from '../rest-bindings.ts';
|
|
2
|
+
import type { WorkflowStreamConnectionLease } from '../runtime/websocket-stream.ts';
|
|
3
|
+
import { type EventEnvelope, type WorkflowEventFeed } from '../workflow-event-feed.ts';
|
|
4
|
+
export type WorkflowStreamConnectionAcquirer = (workflowId: string) => WorkflowStreamConnectionLease | null;
|
|
5
|
+
export type EventStreamOperationContext = {
|
|
6
|
+
readonly workflowEventFeed?: WorkflowEventFeed;
|
|
7
|
+
readonly acquireWorkflowStreamConnection?: WorkflowStreamConnectionAcquirer;
|
|
8
|
+
};
|
|
9
|
+
type WorkflowEventsSseOutput = AsyncIterable<EventEnvelope>;
|
|
10
|
+
export declare const workflowEventsSseOperation: import("../operation-catalog.ts").OperationDefinition<{
|
|
11
|
+
workflowId: string;
|
|
12
|
+
selector: "events" | "tokens";
|
|
13
|
+
fromCursor?: string | undefined;
|
|
14
|
+
lastEventId?: string | undefined;
|
|
15
|
+
}, WorkflowEventsSseOutput, EventEnvelope>;
|
|
16
|
+
export declare const workflowEventsSseRestBinding: UnknownRestBinding;
|
|
17
|
+
export {};
|