@swirls/looms 0.3.0 → 0.5.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/CHANGELOG.md +64 -0
- package/dist/actor/cell.d.ts +21 -2
- package/dist/actor/cell.js +15 -3
- package/dist/actor/host.d.ts +10 -2
- package/dist/actor/host.js +27 -4
- package/dist/actor/index.d.ts +2 -1
- package/dist/actor/index.js +1 -1
- package/dist/actor/routing.d.ts +3 -0
- package/dist/actor/routing.js +13 -2
- package/dist/agent/debugger/agent-chat.js +3 -30
- package/dist/agent/debugger/streaming-text.d.ts +6 -0
- package/dist/agent/debugger/streaming-text.js +33 -0
- package/dist/agent/definitions-store.d.ts +1 -1
- package/dist/agent/definitions-store.js +1 -1
- package/dist/agent/definitions.d.ts +48 -0
- package/dist/agent/definitions.js +44 -0
- package/dist/agent/effects.d.ts +184 -0
- package/dist/agent/effects.js +172 -40
- package/dist/agent/events.d.ts +89 -0
- package/dist/agent/events.js +25 -0
- package/dist/agent/module.d.ts +819 -407
- package/dist/agent/module.js +5 -1
- package/dist/agent/scope.d.ts +88 -0
- package/dist/agent/threads.js +39 -3
- package/dist/agent/types.d.ts +4 -0
- package/dist/approval/effects.d.ts +40 -0
- package/dist/approval/module.d.ts +80 -0
- package/dist/client/client.d.ts +468 -119
- package/dist/client/client.js +214 -21
- package/dist/client/index.d.ts +1 -1
- package/dist/client/sse.d.ts +1 -1
- package/dist/client/sse.js +2 -2
- package/dist/cloudflare/durable-object.d.ts +66 -2
- package/dist/cloudflare/durable-object.js +96 -1
- package/dist/cloudflare/event-store.d.ts +7 -3
- package/dist/cloudflare/event-store.js +16 -3
- package/dist/cloudflare/index.d.ts +7 -1
- package/dist/cloudflare/index.js +7 -1
- package/dist/cloudflare/memory-queue.d.ts +18 -0
- package/dist/cloudflare/memory-queue.js +53 -0
- package/dist/cloudflare/queue-consumer.d.ts +52 -0
- package/dist/cloudflare/queue-consumer.js +102 -0
- package/dist/cloudflare/queue-message.d.ts +31 -0
- package/dist/cloudflare/queue-message.js +33 -0
- package/dist/cloudflare/queue-pull.d.ts +62 -0
- package/dist/cloudflare/queue-pull.js +130 -0
- package/dist/cloudflare/queue-worker.d.ts +39 -0
- package/dist/cloudflare/queue-worker.js +62 -0
- package/dist/cloudflare/r2-blob-store.d.ts +33 -0
- package/dist/cloudflare/r2-blob-store.js +60 -0
- package/dist/cloudflare/router.d.ts +4 -1
- package/dist/cloudflare/router.js +8 -3
- package/dist/cloudflare/websocket.d.ts +27 -0
- package/dist/cloudflare/websocket.js +215 -0
- package/dist/core/attempts.d.ts +8 -0
- package/dist/core/attempts.js +27 -0
- package/dist/core/blob-offload.d.ts +21 -0
- package/dist/core/blob-offload.js +143 -0
- package/dist/core/blob-store.d.ts +45 -0
- package/dist/core/blob-store.js +164 -0
- package/dist/core/blobs.d.ts +37 -0
- package/dist/core/blobs.js +160 -0
- package/dist/core/definition-resolver.d.ts +40 -0
- package/dist/core/definition-resolver.js +129 -0
- package/dist/core/definition-store.d.ts +6 -0
- package/dist/core/definition-store.js +37 -1
- package/dist/core/effects.d.ts +86 -3
- package/dist/core/effects.js +99 -3
- package/dist/core/envelope.d.ts +9 -0
- package/dist/core/envelope.js +4 -0
- package/dist/core/event-codec.js +3 -0
- package/dist/core/fold.js +83 -1
- package/dist/core/ids.d.ts +2 -0
- package/dist/core/ids.js +4 -0
- package/dist/core/index.d.ts +10 -0
- package/dist/core/index.js +9 -0
- package/dist/core/launcher.d.ts +97 -0
- package/dist/core/launcher.js +67 -0
- package/dist/core/module-scope.d.ts +7 -3
- package/dist/core/module-scope.js +3 -0
- package/dist/core/module.d.ts +10 -0
- package/dist/core/projection.d.ts +5 -4
- package/dist/core/projection.js +4 -0
- package/dist/core/protocol.d.ts +36 -1
- package/dist/core/protocol.js +35 -0
- package/dist/core/published.d.ts +2 -0
- package/dist/core/published.js +4 -1
- package/dist/{react → core}/register.d.ts +9 -8
- package/dist/core/replay.d.ts +96 -0
- package/dist/core/run-context.d.ts +33 -0
- package/dist/core/run-context.js +23 -0
- package/dist/core/sqlite-store.js +57 -5
- package/dist/core/state.d.ts +60 -0
- package/dist/core/state.js +19 -0
- package/dist/core/store.d.ts +6 -0
- package/dist/core/store.js +7 -0
- package/dist/core/subscription.d.ts +97 -0
- package/dist/core/subscription.js +224 -0
- package/dist/core/thread.d.ts +7 -0
- package/dist/core/thread.js +1 -0
- package/dist/core/trace.d.ts +21 -0
- package/dist/core/trace.js +45 -0
- package/dist/core/worker-callback.d.ts +25 -1
- package/dist/debugger/app/assets/{index-CgDzWJg7.css → index-B7GdT1UA.css} +1 -1
- package/dist/debugger/app/assets/index-C31SnCuN.js +22 -0
- package/dist/debugger/app/assets/routes-DPA3O5AF.js +47 -0
- package/dist/debugger/app/index.html +2 -2
- package/dist/debugger/components/event-inspector.d.ts +3 -1
- package/dist/debugger/components/event-inspector.js +25 -4
- package/dist/debugger/components/event-stream.d.ts +4 -1
- package/dist/debugger/components/event-stream.js +2 -2
- package/dist/debugger/components/run-tree.d.ts +5 -2
- package/dist/debugger/components/run-tree.js +10 -4
- package/dist/debugger/context.d.ts +8 -1
- package/dist/debugger/context.js +15 -3
- package/dist/debugger/events/protocol-summarize.js +18 -3
- package/dist/debugger/index.d.ts +1 -1
- package/dist/debugger/index.js +1 -1
- package/dist/debugger/shell/debugger-shell.js +1 -1
- package/dist/debugger/shell/events-pane.js +4 -3
- package/dist/debugger/styles.css +1 -0
- package/dist/evaluator/definitions-store.d.ts +3 -4
- package/dist/evaluator/definitions-store.js +3 -3
- package/dist/evaluator/definitions.d.ts +4 -1
- package/dist/evaluator/definitions.js +5 -2
- package/dist/evaluator/effects.d.ts +41 -0
- package/dist/evaluator/effects.js +6 -2
- package/dist/evaluator/module.d.ts +84 -0
- package/dist/evaluator/module.js +7 -3
- package/dist/evaluator/threads.d.ts +2 -0
- package/dist/evaluator/threads.js +4 -0
- package/dist/projectors/event-log.d.ts +87 -0
- package/dist/projectors/event-log.js +396 -0
- package/dist/projectors/index-model.d.ts +12 -0
- package/dist/projectors/index-model.js +12 -3
- package/dist/projectors/memory.js +7 -0
- package/dist/projectors/postgres.d.ts +1 -0
- package/dist/projectors/postgres.js +32 -15
- package/dist/projectors/schema.d.ts +5 -0
- package/dist/projectors/schema.js +26 -1
- package/dist/projectors/sqlite.js +47 -19
- package/dist/projectors/with-projectors.d.ts +7 -1
- package/dist/projectors/with-projectors.js +13 -3
- package/dist/react/derived.d.ts +26 -8
- package/dist/react/derived.js +23 -26
- package/dist/react/index.d.ts +3 -3
- package/dist/react/index.js +2 -2
- package/dist/react/materialize.js +5 -1
- package/dist/react/react.d.ts +16 -9
- package/dist/react/react.js +47 -23
- package/dist/react/store.d.ts +14 -6
- package/dist/react/store.js +104 -12
- package/dist/react/tables.d.ts +4 -1
- package/dist/runtime/cancellation.d.ts +2 -1
- package/dist/runtime/cancellation.js +35 -2
- package/dist/runtime/child-run.d.ts +10 -0
- package/dist/runtime/child-run.js +58 -0
- package/dist/runtime/deadline-transition-planner.js +11 -2
- package/dist/runtime/dispatch-effect.d.ts +2 -2
- package/dist/runtime/dispatch-effect.js +43 -19
- package/dist/runtime/errors.d.ts +14 -1
- package/dist/runtime/errors.js +10 -0
- package/dist/runtime/filesystem-blob-store.d.ts +6 -0
- package/dist/runtime/filesystem-blob-store.js +59 -0
- package/dist/runtime/helpers.d.ts +15 -6
- package/dist/runtime/helpers.js +46 -6
- package/dist/runtime/index.d.ts +4 -2
- package/dist/runtime/index.js +3 -1
- package/dist/runtime/local-effect-executor.d.ts +11 -2
- package/dist/runtime/local-effect-executor.js +80 -4
- package/dist/runtime/looms.d.ts +38 -4
- package/dist/runtime/looms.js +17 -3
- package/dist/runtime/repository.d.ts +5 -4
- package/dist/runtime/repository.js +5 -2
- package/dist/runtime/retry-policy.d.ts +5 -0
- package/dist/runtime/retry-policy.js +10 -2
- package/dist/runtime/run-start.d.ts +1 -1
- package/dist/runtime/run-start.js +10 -0
- package/dist/runtime/runtime.d.ts +2 -2
- package/dist/runtime/runtime.js +191 -68
- package/dist/runtime/server/auth.d.ts +3 -0
- package/dist/runtime/server/fetch-handler.d.ts +2 -2
- package/dist/runtime/server/fetch-handler.js +35 -4
- package/dist/runtime/server/grant.d.ts +49 -0
- package/dist/runtime/server/grant.js +177 -0
- package/dist/runtime/server/routes/effects.js +47 -5
- package/dist/runtime/server/routes/runs.js +6 -1
- package/dist/runtime/server/run-route.d.ts +18 -0
- package/dist/runtime/server/run-route.js +26 -0
- package/dist/runtime/server.d.ts +3 -1
- package/dist/runtime/server.js +3 -1
- package/dist/runtime/sse.js +26 -4
- package/dist/runtime/subscription.d.ts +8 -0
- package/dist/runtime/subscription.js +23 -0
- package/dist/runtime/types.d.ts +87 -8
- package/dist/runtime/wake-engine.d.ts +4 -2
- package/dist/runtime/wake-engine.js +57 -6
- package/dist/runtime/worker-callbacks.d.ts +12 -2
- package/dist/runtime/worker-callbacks.js +102 -72
- package/dist/runtime/worker-effect-dispatch.d.ts +14 -5
- package/dist/runtime/worker-effect-dispatch.js +84 -18
- package/dist/schedule/debugger/index.d.ts +4 -0
- package/dist/schedule/debugger/index.js +11 -0
- package/dist/schedule/debugger/schedule-view.d.ts +3 -0
- package/dist/schedule/debugger/schedule-view.js +68 -0
- package/dist/schedule/debugger/summarize.d.ts +2 -0
- package/dist/schedule/debugger/summarize.js +43 -0
- package/dist/schedule/definitions-store.d.ts +10 -0
- package/dist/schedule/definitions-store.js +8 -0
- package/dist/schedule/definitions.d.ts +57 -0
- package/dist/schedule/definitions.js +30 -0
- package/dist/schedule/effects.d.ts +756 -0
- package/dist/schedule/effects.js +244 -0
- package/dist/schedule/ensure.d.ts +55 -0
- package/dist/schedule/ensure.js +109 -0
- package/dist/schedule/events.d.ts +116 -0
- package/dist/schedule/events.js +49 -0
- package/dist/schedule/index.d.ts +11 -0
- package/dist/schedule/index.js +11 -0
- package/dist/schedule/module.d.ts +1474 -0
- package/dist/schedule/module.js +22 -0
- package/dist/schedule/projections.d.ts +55 -0
- package/dist/schedule/projections.js +88 -0
- package/dist/schedule/scope.d.ts +121 -0
- package/dist/schedule/scope.js +7 -0
- package/dist/schedule/signals.d.ts +25 -0
- package/dist/schedule/signals.js +33 -0
- package/dist/schedule/specs.d.ts +59 -0
- package/dist/schedule/specs.js +231 -0
- package/dist/schedule/threads.d.ts +25 -0
- package/dist/schedule/threads.js +259 -0
- package/dist/testing/corpus-module.d.ts +34 -0
- package/dist/testing/corpus-module.js +87 -0
- package/dist/testing/index.d.ts +2 -0
- package/dist/testing/index.js +5 -6
- package/dist/testing/replay-corpus.d.ts +22 -0
- package/dist/testing/replay-corpus.js +50 -0
- package/dist/workflow/definitions-store.d.ts +1 -1
- package/dist/workflow/definitions-store.js +1 -1
- package/dist/workflow/definitions.d.ts +14 -0
- package/dist/workflow/effects.d.ts +122 -0
- package/dist/workflow/effects.js +16 -6
- package/dist/workflow/events.d.ts +42 -0
- package/dist/workflow/events.js +4 -0
- package/dist/workflow/module.d.ts +345 -32
- package/dist/workflow/module.js +7 -3
- package/dist/workflow/scope.d.ts +42 -0
- package/dist/workflow/threads.d.ts +72 -0
- package/dist/workflow/threads.js +106 -38
- package/package.json +11 -2
- package/dist/debugger/app/assets/index-BevQYa-U.js +0 -22
- package/dist/debugger/app/assets/routes-B17Om93i.js +0 -47
- /package/dist/{react → core}/register.js +0 -0
package/dist/client/client.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { Data, Effect, Option, Schema, Stream } from 'effect';
|
|
2
|
-
import { createRunId, DefinitionCatalogSchema, EventEnvelopeSchema, ReplayStepSchema, RunStateSchema, RunSummariesSchema, stringifyJson, } from '@swirls/looms/core';
|
|
1
|
+
import { Data, Effect, Option, Predicate, Schema, Stream } from 'effect';
|
|
2
|
+
import { createRunId, DefinitionCatalogSchema, EventEnvelopeSchema, readSubscriptionData, ReplayStepSchema, RunStateSchema, RunSummariesSchema, SecretContextSchema, stringifyJson, traceHeaders, } from '@swirls/looms/core';
|
|
3
3
|
import { consumeSseStreamEffect, eventsFromSseData, sseFrames } from './sse.js';
|
|
4
4
|
class LoomsClientError extends Data.TaggedError('LoomsClientError') {
|
|
5
5
|
constructor(message, cause) {
|
|
@@ -14,6 +14,46 @@ const StartResultSchema = Schema.Struct({
|
|
|
14
14
|
state: RunStateSchema,
|
|
15
15
|
});
|
|
16
16
|
const RunResultSchema = Schema.Struct({ runId: Schema.String, state: RunStateSchema });
|
|
17
|
+
function attemptPath(runId, attempt) {
|
|
18
|
+
return `/runs/${encodeURIComponent(runId)}/effects/${encodeURIComponent(attempt.effectId)}/${attempt.attempt}`;
|
|
19
|
+
}
|
|
20
|
+
function workerCallbackBody(input) {
|
|
21
|
+
switch (input.kind) {
|
|
22
|
+
case 'complete':
|
|
23
|
+
return { events: input.events };
|
|
24
|
+
case 'fail': {
|
|
25
|
+
const body = { error: input.error };
|
|
26
|
+
if (input.nonRetryable !== undefined) {
|
|
27
|
+
body.nonRetryable = input.nonRetryable;
|
|
28
|
+
}
|
|
29
|
+
if (input.retryAfterMs !== undefined) {
|
|
30
|
+
body.retryAfterMs = input.retryAfterMs;
|
|
31
|
+
}
|
|
32
|
+
return body;
|
|
33
|
+
}
|
|
34
|
+
case 'heartbeat':
|
|
35
|
+
return input.details === undefined ? {} : { details: input.details };
|
|
36
|
+
case 'started':
|
|
37
|
+
case 'cancelled':
|
|
38
|
+
return {};
|
|
39
|
+
default: {
|
|
40
|
+
const exhaustive = input;
|
|
41
|
+
return exhaustive;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
const WorkerCallbackResultSchema = Schema.Struct({
|
|
46
|
+
status: Schema.Union([
|
|
47
|
+
Schema.Literal('accepted'),
|
|
48
|
+
Schema.Literal('stale'),
|
|
49
|
+
Schema.Literal('cancelled'),
|
|
50
|
+
]),
|
|
51
|
+
cancelRequested: Schema.Boolean,
|
|
52
|
+
});
|
|
53
|
+
const WorkerSecretsResultSchema = Schema.Union([
|
|
54
|
+
Schema.Struct({ status: Schema.Literal('accepted'), secrets: SecretContextSchema }),
|
|
55
|
+
Schema.Struct({ status: Schema.Union([Schema.Literal('stale'), Schema.Literal('cancelled')]) }),
|
|
56
|
+
]);
|
|
17
57
|
/** `appended` counts the events the run gained while the wake ran. */
|
|
18
58
|
const WakeResultSchema = Schema.Struct({
|
|
19
59
|
runId: Schema.String,
|
|
@@ -54,19 +94,33 @@ export function createLoomsClient(options = {}) {
|
|
|
54
94
|
return yield* Schema.decodeUnknownEffect(schema)(body).pipe(Effect.mapError((cause) => new LoomsClientError('Invalid response body', cause)));
|
|
55
95
|
}));
|
|
56
96
|
}
|
|
57
|
-
const subscribeEvents = (runId, onEvent,
|
|
97
|
+
const subscribeEvents = (runId, onEvent, third = 1_000) => {
|
|
98
|
+
const settings = subscriptionSettings(third, options.grant);
|
|
58
99
|
let fromSeq = 1;
|
|
59
100
|
const controller = new AbortController();
|
|
60
101
|
const run = Effect.gen(function* () {
|
|
61
102
|
while (!controller.signal.aborted) {
|
|
62
103
|
yield* Effect.gen(function* () {
|
|
104
|
+
if (useWebSocket(settings)) {
|
|
105
|
+
return yield* consumeWebSocket({
|
|
106
|
+
baseUrl,
|
|
107
|
+
runId,
|
|
108
|
+
settings,
|
|
109
|
+
signal: controller.signal,
|
|
110
|
+
currentSeq: () => fromSeq,
|
|
111
|
+
setSeq: (next) => {
|
|
112
|
+
fromSeq = next;
|
|
113
|
+
},
|
|
114
|
+
onEvent,
|
|
115
|
+
});
|
|
116
|
+
}
|
|
63
117
|
const cursor = Math.max(0, fromSeq - 1);
|
|
64
118
|
const headers = new Headers({ accept: 'text/event-stream' });
|
|
65
119
|
if (cursor > 0) {
|
|
66
120
|
headers.set('last-event-id', String(cursor));
|
|
67
121
|
}
|
|
68
122
|
const res = yield* Effect.tryPromise({
|
|
69
|
-
try: (signal) => fetchImpl(`${baseUrl}
|
|
123
|
+
try: (signal) => fetchImpl(`${baseUrl}${eventsQuery(runId, fromSeq, settings)}`, {
|
|
70
124
|
headers,
|
|
71
125
|
signal,
|
|
72
126
|
}),
|
|
@@ -75,10 +129,20 @@ export function createLoomsClient(options = {}) {
|
|
|
75
129
|
const body = res.body;
|
|
76
130
|
if (res.ok && body) {
|
|
77
131
|
return yield* consumeSseStreamEffect(body, (frame) => {
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
132
|
+
let resync = false;
|
|
133
|
+
deliverSubscriptionData({
|
|
134
|
+
data: frame.data,
|
|
135
|
+
runId,
|
|
136
|
+
currentSeq: () => fromSeq,
|
|
137
|
+
setSeq: (next) => {
|
|
138
|
+
fromSeq = next;
|
|
139
|
+
},
|
|
140
|
+
onEvent,
|
|
141
|
+
onResync: () => {
|
|
142
|
+
resync = true;
|
|
143
|
+
},
|
|
144
|
+
});
|
|
145
|
+
return resync ? false : undefined;
|
|
82
146
|
});
|
|
83
147
|
}
|
|
84
148
|
return yield* new LoomsClientError(res.ok ? 'event stream missing body' : `HTTP ${res.status}`);
|
|
@@ -86,7 +150,7 @@ export function createLoomsClient(options = {}) {
|
|
|
86
150
|
if (controller.signal.aborted) {
|
|
87
151
|
break;
|
|
88
152
|
}
|
|
89
|
-
yield* Effect.sleep(reconnectDelayMs);
|
|
153
|
+
yield* Effect.sleep(settings.reconnectDelayMs);
|
|
90
154
|
}
|
|
91
155
|
});
|
|
92
156
|
void Effect.runPromise(run, { signal: controller.signal }).catch(() => undefined);
|
|
@@ -94,31 +158,43 @@ export function createLoomsClient(options = {}) {
|
|
|
94
158
|
controller.abort();
|
|
95
159
|
};
|
|
96
160
|
};
|
|
97
|
-
const startRun = (args) =>
|
|
161
|
+
const startRun = (args) => {
|
|
162
|
+
const { trace, ...body } = args;
|
|
163
|
+
return request('/runs', StartResultSchema, {
|
|
164
|
+
method: 'POST',
|
|
165
|
+
headers: traceHeaders(trace),
|
|
166
|
+
body: stringifyJson(body),
|
|
167
|
+
});
|
|
168
|
+
};
|
|
169
|
+
const workerHeaders = options.workerCallbackToken
|
|
170
|
+
? { authorization: `Bearer ${options.workerCallbackToken}` }
|
|
171
|
+
: undefined;
|
|
98
172
|
const workerCallback = (runId, input) => {
|
|
99
|
-
const body = input
|
|
100
|
-
|
|
101
|
-
: input.kind === 'fail'
|
|
102
|
-
? { error: input.error }
|
|
103
|
-
: {};
|
|
104
|
-
return request(`/runs/${encodeURIComponent(runId)}/effects/${encodeURIComponent(input.effectId)}/${input.attempt}/${input.kind}`, RunResultSchema, {
|
|
173
|
+
const body = workerCallbackBody(input);
|
|
174
|
+
return request(`${attemptPath(runId, input)}/${input.kind}`, WorkerCallbackResultSchema, {
|
|
105
175
|
method: 'POST',
|
|
106
|
-
headers:
|
|
107
|
-
? { authorization: `Bearer ${options.workerCallbackToken}` }
|
|
108
|
-
: undefined,
|
|
176
|
+
headers: workerHeaders,
|
|
109
177
|
body: stringifyJson(body),
|
|
110
178
|
});
|
|
111
179
|
};
|
|
180
|
+
/** Read the run's secret context. Only the attempt that is currently running gets it. */
|
|
181
|
+
const workerSecrets = (runId, attempt) => request(`${attemptPath(runId, attempt)}/secrets`, WorkerSecretsResultSchema, {
|
|
182
|
+
method: 'POST',
|
|
183
|
+
headers: workerHeaders,
|
|
184
|
+
body: '{}',
|
|
185
|
+
});
|
|
112
186
|
const streamRun = (args) => {
|
|
113
|
-
const
|
|
187
|
+
const { trace, ...body } = args;
|
|
188
|
+
const runId = body.runId ?? createRunId();
|
|
114
189
|
const stream = Stream.unwrap(Effect.tryPromise({
|
|
115
190
|
try: (signal) => fetchImpl(`${baseUrl}/runs`, {
|
|
116
191
|
method: 'POST',
|
|
117
192
|
headers: {
|
|
193
|
+
...traceHeaders(trace),
|
|
118
194
|
'content-type': 'application/json',
|
|
119
195
|
accept: 'text/event-stream',
|
|
120
196
|
},
|
|
121
|
-
body: stringifyJson({ ...
|
|
197
|
+
body: stringifyJson({ ...body, runId }),
|
|
122
198
|
signal,
|
|
123
199
|
}),
|
|
124
200
|
catch: (cause) => new LoomsClientError('Failed to start event stream', cause),
|
|
@@ -175,6 +251,7 @@ export function createLoomsClient(options = {}) {
|
|
|
175
251
|
body: stringifyJson(forkOptions?.runId === undefined ? {} : { runId: forkOptions.runId }),
|
|
176
252
|
}),
|
|
177
253
|
workerCallback,
|
|
254
|
+
workerSecrets,
|
|
178
255
|
listRunSummaries: () => request('/runs/summaries', RunSummariesSchema),
|
|
179
256
|
listDefinitions: () => request('/definitions', DefinitionCatalogSchema),
|
|
180
257
|
replayTo: (runId, seq) => request(`/runs/${encodeURIComponent(runId)}/replay?seq=${seq}`, ReplayResultSchema),
|
|
@@ -193,3 +270,119 @@ export function createLoomsClient(options = {}) {
|
|
|
193
270
|
}),
|
|
194
271
|
};
|
|
195
272
|
}
|
|
273
|
+
function subscriptionSettings(third, grant) {
|
|
274
|
+
if (Predicate.isNumber(third)) {
|
|
275
|
+
return { transport: 'sse', reconnectDelayMs: third, grant };
|
|
276
|
+
}
|
|
277
|
+
return {
|
|
278
|
+
transport: third.transport ?? 'sse',
|
|
279
|
+
reconnectDelayMs: third.reconnectDelayMs ?? 1_000,
|
|
280
|
+
threadId: third.threadId,
|
|
281
|
+
includeEphemeral: third.includeEphemeral,
|
|
282
|
+
webSocket: third.webSocket,
|
|
283
|
+
grant,
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
function useWebSocket(settings) {
|
|
287
|
+
if (settings.transport !== 'websocket' && settings.transport !== 'auto') {
|
|
288
|
+
return false;
|
|
289
|
+
}
|
|
290
|
+
return settings.webSocket !== undefined || globalWebSocket() !== undefined;
|
|
291
|
+
}
|
|
292
|
+
function eventsQuery(runId, fromSeq, settings) {
|
|
293
|
+
const params = new URLSearchParams({ fromSeq: String(fromSeq) });
|
|
294
|
+
if (settings.threadId) {
|
|
295
|
+
params.set('threadId', settings.threadId);
|
|
296
|
+
}
|
|
297
|
+
if (settings.includeEphemeral === false) {
|
|
298
|
+
params.set('includeEphemeral', 'false');
|
|
299
|
+
}
|
|
300
|
+
if (settings.grant) {
|
|
301
|
+
params.set('grant', settings.grant);
|
|
302
|
+
}
|
|
303
|
+
return `/runs/${encodeURIComponent(runId)}/events?${params.toString()}`;
|
|
304
|
+
}
|
|
305
|
+
function webSocketUrl(baseUrl, path) {
|
|
306
|
+
const url = new URL(path, baseUrl);
|
|
307
|
+
url.protocol = url.protocol === 'https:' ? 'wss:' : 'ws:';
|
|
308
|
+
return url.toString();
|
|
309
|
+
}
|
|
310
|
+
function globalWebSocket() {
|
|
311
|
+
if (!('WebSocket' in globalThis) || !Predicate.isFunction(globalThis.WebSocket)) {
|
|
312
|
+
return undefined;
|
|
313
|
+
}
|
|
314
|
+
return globalThis.WebSocket;
|
|
315
|
+
}
|
|
316
|
+
function consumeWebSocket(subscription) {
|
|
317
|
+
const Socket = subscription.settings.webSocket ?? globalWebSocket();
|
|
318
|
+
if (!Socket) {
|
|
319
|
+
return Effect.void;
|
|
320
|
+
}
|
|
321
|
+
const url = webSocketUrl(subscription.baseUrl, eventsQuery(subscription.runId, subscription.currentSeq(), subscription.settings));
|
|
322
|
+
return Effect.callback((resume) => {
|
|
323
|
+
const socket = new Socket(url);
|
|
324
|
+
const finish = () => {
|
|
325
|
+
resume(Effect.void);
|
|
326
|
+
};
|
|
327
|
+
if (subscription.signal.aborted) {
|
|
328
|
+
socket.close();
|
|
329
|
+
finish();
|
|
330
|
+
return Effect.void;
|
|
331
|
+
}
|
|
332
|
+
const onAbort = () => {
|
|
333
|
+
socket.close();
|
|
334
|
+
finish();
|
|
335
|
+
};
|
|
336
|
+
subscription.signal.addEventListener('abort', onAbort);
|
|
337
|
+
socket.addEventListener('message', (event) => {
|
|
338
|
+
if (!Predicate.isString(event.data)) {
|
|
339
|
+
return;
|
|
340
|
+
}
|
|
341
|
+
deliverSubscriptionData({
|
|
342
|
+
data: event.data,
|
|
343
|
+
runId: subscription.runId,
|
|
344
|
+
currentSeq: subscription.currentSeq,
|
|
345
|
+
setSeq: subscription.setSeq,
|
|
346
|
+
onEvent: subscription.onEvent,
|
|
347
|
+
onResync: () => {
|
|
348
|
+
socket.close();
|
|
349
|
+
},
|
|
350
|
+
});
|
|
351
|
+
});
|
|
352
|
+
socket.addEventListener('close', finish);
|
|
353
|
+
socket.addEventListener('error', finish);
|
|
354
|
+
return Effect.sync(() => {
|
|
355
|
+
subscription.signal.removeEventListener('abort', onAbort);
|
|
356
|
+
socket.close();
|
|
357
|
+
});
|
|
358
|
+
});
|
|
359
|
+
}
|
|
360
|
+
function deliverSubscriptionData(delivery) {
|
|
361
|
+
const read = readSubscriptionData(delivery.data, delivery.runId);
|
|
362
|
+
switch (read.kind) {
|
|
363
|
+
case 'events': {
|
|
364
|
+
const fromSeq = delivery.currentSeq();
|
|
365
|
+
let next = fromSeq;
|
|
366
|
+
for (const event of read.events) {
|
|
367
|
+
if (event.seq < fromSeq) {
|
|
368
|
+
continue;
|
|
369
|
+
}
|
|
370
|
+
delivery.onEvent(event);
|
|
371
|
+
next = Math.max(next, event.seq + 1);
|
|
372
|
+
}
|
|
373
|
+
delivery.setSeq(next);
|
|
374
|
+
return;
|
|
375
|
+
}
|
|
376
|
+
case 'resync':
|
|
377
|
+
delivery.setSeq(read.head);
|
|
378
|
+
delivery.onResync();
|
|
379
|
+
return;
|
|
380
|
+
case 'hello':
|
|
381
|
+
case 'ignore':
|
|
382
|
+
return;
|
|
383
|
+
default: {
|
|
384
|
+
const unexpected = read;
|
|
385
|
+
return unexpected;
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
}
|
package/dist/client/index.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { createLoomsClient, type LoomsClient, type LoomsClientOptions, type StartResult, type StreamHandle, } from './client';
|
|
1
|
+
export { createLoomsClient, type LoomsClient, type LoomsClientOptions, type StartResult, type StreamHandle, type SubscribeEventsOptions, type SubscriptionSocket, type SubscriptionSocketConstructor, } from './client';
|
|
2
2
|
export { consumeSseStream, consumeSseStreamEffect, delay, eventsFromSseData, parseSseFrame, type SseFrame, } from './sse';
|
package/dist/client/sse.d.ts
CHANGED
|
@@ -15,7 +15,7 @@ export declare class SseStreamError extends SseStreamError_base<{
|
|
|
15
15
|
}> {
|
|
16
16
|
constructor(cause: unknown);
|
|
17
17
|
}
|
|
18
|
-
export declare function consumeSseStreamEffect(body: ReadableStream<Uint8Array>, onFrame: (frame: SseFrame) => void): Effect.Effect<void, SseStreamError>;
|
|
18
|
+
export declare function consumeSseStreamEffect(body: ReadableStream<Uint8Array>, onFrame: (frame: SseFrame) => void | boolean): Effect.Effect<void, SseStreamError>;
|
|
19
19
|
export declare function sseFrames(body: ReadableStream<Uint8Array>): Stream.Stream<SseFrame, SseStreamError>;
|
|
20
20
|
export declare function consumeSseStream(body: ReadableStream<Uint8Array>, onFrame: (frame: SseFrame) => void, signal?: AbortSignal): Promise<void>;
|
|
21
21
|
export declare function eventsFromSseData(data: string, runId: string): EventEnvelope[];
|
package/dist/client/sse.js
CHANGED
|
@@ -52,8 +52,8 @@ export function consumeSseStreamEffect(body, onFrame) {
|
|
|
52
52
|
buffer = chunks.pop() ?? '';
|
|
53
53
|
for (const chunk of chunks) {
|
|
54
54
|
const frame = parseSseFrame(chunk);
|
|
55
|
-
if (frame) {
|
|
56
|
-
|
|
55
|
+
if (frame && onFrame(frame) === false) {
|
|
56
|
+
return;
|
|
57
57
|
}
|
|
58
58
|
}
|
|
59
59
|
}
|
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
import { DurableObject } from 'cloudflare:workers';
|
|
2
|
-
import type
|
|
2
|
+
import { type AnyRuntimeModule, type DefinitionResolver, type EventInput, type BlobStore, type EventStoreTrimCoverage, type LaunchRunArgs, type RunLauncher, type RunState, type RunStatusSnapshot } from '@swirls/looms/core';
|
|
3
3
|
import type { Projector } from '@swirls/looms/projectors';
|
|
4
|
-
import type
|
|
4
|
+
import { type Authorize, type StartResult, type StartRunArgs } from '@swirls/looms/runtime';
|
|
5
5
|
export interface LoomsDurableObjectConfig {
|
|
6
6
|
readonly modules: readonly AnyRuntimeModule[];
|
|
7
|
+
/** Loads definitions that were not registered on a module. See `CreateRuntimeOptions`. */
|
|
8
|
+
readonly definitionResolver?: DefinitionResolver;
|
|
7
9
|
readonly projectors?: readonly Projector[];
|
|
10
|
+
/** Content-addressed store for event and snapshot JSON over the inline byte limits. */
|
|
11
|
+
readonly blobs?: BlobStore;
|
|
8
12
|
readonly snapshotEvery?: number;
|
|
9
13
|
readonly maxWakeIterations?: number;
|
|
10
14
|
readonly maxPendingRunOperations?: number;
|
|
@@ -14,6 +18,11 @@ export interface LoomsDurableObjectConfig {
|
|
|
14
18
|
};
|
|
15
19
|
/** Authorizes the actor's HTTP API; see `createFetchHandler`. */
|
|
16
20
|
readonly authorize?: Authorize;
|
|
21
|
+
/**
|
|
22
|
+
* Starts runs from effect handlers. A schedule object passes `durableObjectLauncher` so fires
|
|
23
|
+
* land in the target's object. Without it, a fire fails instead of writing into this object.
|
|
24
|
+
*/
|
|
25
|
+
readonly launcher?: RunLauncher;
|
|
17
26
|
}
|
|
18
27
|
/**
|
|
19
28
|
* Base Durable Object class for hosting a Looms actor cell.
|
|
@@ -23,10 +32,65 @@ export interface LoomsDurableObjectConfig {
|
|
|
23
32
|
export declare abstract class LoomsDurableObject<Env = unknown> extends DurableObject<Env> {
|
|
24
33
|
private cellPromise?;
|
|
25
34
|
private store?;
|
|
35
|
+
private authorize;
|
|
26
36
|
abstract configure(env: Env): LoomsDurableObjectConfig | Promise<LoomsDurableObjectConfig>;
|
|
27
37
|
private getCell;
|
|
28
38
|
private createCell;
|
|
29
39
|
fetch(req: Request): Promise<Response>;
|
|
40
|
+
/** Client messages are ignored. Signals and other writes stay on HTTP. */
|
|
41
|
+
webSocketMessage(): void;
|
|
42
|
+
webSocketClose(): void;
|
|
43
|
+
webSocketError(): void;
|
|
44
|
+
private acceptRunSocket;
|
|
45
|
+
/** Commit this object's run and set its alarm. Returns before the run wakes. */
|
|
46
|
+
launch(args: LaunchRunArgs): Promise<void>;
|
|
47
|
+
status(): Promise<RunStatusSnapshot | null>;
|
|
48
|
+
startRun(args: StartRunArgs): Promise<StartResult>;
|
|
49
|
+
signal(events: ReadonlyArray<EventInput>, options?: {
|
|
50
|
+
idempotencyKey?: string;
|
|
51
|
+
coalesce?: boolean;
|
|
52
|
+
}): Promise<RunState>;
|
|
53
|
+
accept(events: ReadonlyArray<EventInput>, options?: {
|
|
54
|
+
idempotencyKey?: string;
|
|
55
|
+
coalesce?: boolean;
|
|
56
|
+
}): Promise<RunState>;
|
|
57
|
+
cancel(): Promise<void>;
|
|
58
|
+
getRun(): Promise<RunState>;
|
|
30
59
|
/** A rejected alarm is retried by the platform with backoff, so failures just propagate. */
|
|
31
60
|
alarm(): Promise<void>;
|
|
32
61
|
}
|
|
62
|
+
export interface DurableObjectRunStub {
|
|
63
|
+
fetch(request: Request): Promise<Response>;
|
|
64
|
+
launch(args: LaunchRunArgs): Promise<void>;
|
|
65
|
+
status(): Promise<RunStatusSnapshot | null>;
|
|
66
|
+
startRun(args: StartRunArgs): Promise<StartResult>;
|
|
67
|
+
signal(events: ReadonlyArray<EventInput>, options?: {
|
|
68
|
+
idempotencyKey?: string;
|
|
69
|
+
coalesce?: boolean;
|
|
70
|
+
}): Promise<RunState>;
|
|
71
|
+
accept(events: ReadonlyArray<EventInput>, options?: {
|
|
72
|
+
idempotencyKey?: string;
|
|
73
|
+
coalesce?: boolean;
|
|
74
|
+
}): Promise<RunState>;
|
|
75
|
+
cancel(): Promise<void>;
|
|
76
|
+
getRun(): Promise<RunState>;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Launches and reads runs in other Durable Objects. One object per `runId`.
|
|
80
|
+
* The argument is the binding, not the stub, so a class can pass its own namespace
|
|
81
|
+
* without the type checker expanding that class through this function.
|
|
82
|
+
*/
|
|
83
|
+
export declare function durableObjectLauncher(namespace: {
|
|
84
|
+
getByName(name: string): object;
|
|
85
|
+
}): RunLauncher;
|
|
86
|
+
export interface DurableObjectScheduleHost {
|
|
87
|
+
startRun(args: StartRunArgs): Promise<StartResult>;
|
|
88
|
+
signal(runId: string, events: ReadonlyArray<EventInput>, options?: {
|
|
89
|
+
idempotencyKey?: string;
|
|
90
|
+
}): Promise<RunState>;
|
|
91
|
+
getRun(runId: string): Promise<RunState>;
|
|
92
|
+
}
|
|
93
|
+
/** `ScheduleHost` for a Worker that reconciles schedules onto Durable Objects. */
|
|
94
|
+
export declare function durableObjectScheduleHost(namespace: {
|
|
95
|
+
getByName(name: string): object;
|
|
96
|
+
}): DurableObjectScheduleHost;
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import { DurableObject } from 'cloudflare:workers';
|
|
2
2
|
import { Effect } from 'effect';
|
|
3
3
|
import { createActorCell } from '@swirls/looms/actor';
|
|
4
|
+
import { createRemoteRunLauncher, } from '@swirls/looms/core';
|
|
5
|
+
import { defaultAuthorize, } from '@swirls/looms/runtime';
|
|
4
6
|
import { durableObjectAlarms } from './alarm-scheduler.js';
|
|
5
7
|
import { durableObjectEventStore } from './event-store.js';
|
|
8
|
+
import { attachSocketFanout, isRunSocketUpgrade, openRunSocket } from './websocket.js';
|
|
6
9
|
/**
|
|
7
10
|
* Base Durable Object class for hosting a Looms actor cell.
|
|
8
11
|
* Subclasses implement `configure(env)` to supply configured modules.
|
|
@@ -11,6 +14,7 @@ import { durableObjectEventStore } from './event-store.js';
|
|
|
11
14
|
export class LoomsDurableObject extends DurableObject {
|
|
12
15
|
cellPromise;
|
|
13
16
|
store;
|
|
17
|
+
authorize = defaultAuthorize;
|
|
14
18
|
getCell() {
|
|
15
19
|
if (this.cellPromise) {
|
|
16
20
|
return this.cellPromise;
|
|
@@ -36,18 +40,22 @@ export class LoomsDurableObject extends DurableObject {
|
|
|
36
40
|
const store = durableObjectEventStore(this.ctx, {
|
|
37
41
|
projectors: config.projectors,
|
|
38
42
|
scheduleProjectorRetry: alarms.scheduleAtEarliest,
|
|
43
|
+
blobs: config.blobs,
|
|
39
44
|
});
|
|
40
45
|
this.store = store;
|
|
46
|
+
this.authorize = config.authorize ?? defaultAuthorize;
|
|
41
47
|
const cell = createActorCell({
|
|
42
48
|
runId,
|
|
43
|
-
store,
|
|
49
|
+
store: attachSocketFanout(this.ctx, store, runId),
|
|
44
50
|
scheduler: alarms.scheduler,
|
|
45
51
|
modules: config.modules,
|
|
52
|
+
definitionResolver: config.definitionResolver,
|
|
46
53
|
snapshotEvery: config.snapshotEvery,
|
|
47
54
|
maxWakeIterations: config.maxWakeIterations,
|
|
48
55
|
maxPendingRunOperations: config.maxPendingRunOperations,
|
|
49
56
|
trimAfterSnapshot: config.trimAfterSnapshot,
|
|
50
57
|
authorize: config.authorize,
|
|
58
|
+
launcher: config.launcher,
|
|
51
59
|
});
|
|
52
60
|
const delivery = store.projectorDelivery;
|
|
53
61
|
const recoverProjectors = delivery
|
|
@@ -57,8 +65,58 @@ export class LoomsDurableObject extends DurableObject {
|
|
|
57
65
|
});
|
|
58
66
|
}
|
|
59
67
|
fetch(req) {
|
|
68
|
+
if (isRunSocketUpgrade(req)) {
|
|
69
|
+
return this.getCell().then((cell) => this.acceptRunSocket(req, cell.runtime));
|
|
70
|
+
}
|
|
60
71
|
return this.getCell().then((cell) => cell.fetch(req));
|
|
61
72
|
}
|
|
73
|
+
/** Client messages are ignored. Signals and other writes stay on HTTP. */
|
|
74
|
+
webSocketMessage() {
|
|
75
|
+
// A hibernated socket stays quiet. The platform delivers this only if a client writes.
|
|
76
|
+
}
|
|
77
|
+
webSocketClose() {
|
|
78
|
+
// The platform drops the socket. Nothing is retained for it.
|
|
79
|
+
}
|
|
80
|
+
webSocketError() {
|
|
81
|
+
// The platform drops the socket. Nothing is retained for it.
|
|
82
|
+
}
|
|
83
|
+
acceptRunSocket(req, runtime) {
|
|
84
|
+
const store = this.store;
|
|
85
|
+
const runId = this.ctx.id.name;
|
|
86
|
+
if (!store || !runId) {
|
|
87
|
+
return Promise.resolve(Response.json({ error: 'Run is not ready' }, { status: 500 }));
|
|
88
|
+
}
|
|
89
|
+
return Effect.runPromise(openRunSocket({
|
|
90
|
+
ctx: this.ctx,
|
|
91
|
+
runtime,
|
|
92
|
+
store,
|
|
93
|
+
request: req,
|
|
94
|
+
runId,
|
|
95
|
+
authorize: this.authorize,
|
|
96
|
+
}));
|
|
97
|
+
}
|
|
98
|
+
/** Commit this object's run and set its alarm. Returns before the run wakes. */
|
|
99
|
+
launch(args) {
|
|
100
|
+
return this.getCell().then((cell) => cell.launch(args));
|
|
101
|
+
}
|
|
102
|
+
status() {
|
|
103
|
+
return this.getCell().then((cell) => cell.status());
|
|
104
|
+
}
|
|
105
|
+
startRun(args) {
|
|
106
|
+
return this.getCell().then((cell) => cell.startRun(args));
|
|
107
|
+
}
|
|
108
|
+
signal(events, options) {
|
|
109
|
+
return this.getCell().then((cell) => cell.signal(events, options));
|
|
110
|
+
}
|
|
111
|
+
accept(events, options) {
|
|
112
|
+
return this.getCell().then((cell) => cell.accept(events, options));
|
|
113
|
+
}
|
|
114
|
+
cancel() {
|
|
115
|
+
return this.getCell().then((cell) => cell.cancel());
|
|
116
|
+
}
|
|
117
|
+
getRun() {
|
|
118
|
+
return this.getCell().then((cell) => cell.getRun());
|
|
119
|
+
}
|
|
62
120
|
/** A rejected alarm is retried by the platform with backoff, so failures just propagate. */
|
|
63
121
|
alarm() {
|
|
64
122
|
return this.getCell()
|
|
@@ -72,3 +130,40 @@ export class LoomsDurableObject extends DurableObject {
|
|
|
72
130
|
.then(() => undefined);
|
|
73
131
|
}
|
|
74
132
|
}
|
|
133
|
+
/**
|
|
134
|
+
* Launches and reads runs in other Durable Objects. One object per `runId`.
|
|
135
|
+
* The argument is the binding, not the stub, so a class can pass its own namespace
|
|
136
|
+
* without the type checker expanding that class through this function.
|
|
137
|
+
*/
|
|
138
|
+
export function durableObjectLauncher(namespace) {
|
|
139
|
+
const runs = loomsNamespace(namespace);
|
|
140
|
+
return createRemoteRunLauncher({
|
|
141
|
+
launch: (runId, args) => runs.getByName(runId).launch(args),
|
|
142
|
+
status: (runId) => runs.getByName(runId).status(),
|
|
143
|
+
accept: (runId, events, options) => runs
|
|
144
|
+
.getByName(runId)
|
|
145
|
+
.accept(events, options)
|
|
146
|
+
.then(() => undefined),
|
|
147
|
+
cancel: (runId) => runs.getByName(runId).cancel(),
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
/** `ScheduleHost` for a Worker that reconciles schedules onto Durable Objects. */
|
|
151
|
+
export function durableObjectScheduleHost(namespace) {
|
|
152
|
+
const runs = loomsNamespace(namespace);
|
|
153
|
+
return {
|
|
154
|
+
startRun: (args) => {
|
|
155
|
+
const runId = args.runId;
|
|
156
|
+
if (!runId) {
|
|
157
|
+
return Promise.reject(new Error('ScheduleHost.startRun requires runId'));
|
|
158
|
+
}
|
|
159
|
+
return runs.getByName(runId).startRun(args);
|
|
160
|
+
},
|
|
161
|
+
signal: (runId, events, options) => runs.getByName(runId).signal(events, options),
|
|
162
|
+
getRun: (runId) => runs.getByName(runId).getRun(),
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
function loomsNamespace(namespace) {
|
|
166
|
+
// SAFETY: callers pass a Looms Durable Object namespace, whose stub implements these methods.
|
|
167
|
+
// oxlint-disable-next-line typescript/no-unsafe-type-assertion
|
|
168
|
+
return namespace;
|
|
169
|
+
}
|
|
@@ -1,15 +1,19 @@
|
|
|
1
|
-
import
|
|
2
|
-
import type
|
|
1
|
+
import { Effect } from 'effect';
|
|
2
|
+
import { type BlobStore, type EventStore } from '@swirls/looms/core';
|
|
3
3
|
import { type Projector, type ProjectorDelivery } from '@swirls/looms/projectors';
|
|
4
4
|
export interface DurableObjectStoreOptions {
|
|
5
5
|
readonly projectors?: readonly Projector[];
|
|
6
6
|
readonly scheduleProjectorRetry?: (at: number) => Effect.Effect<void>;
|
|
7
|
+
/** Offloads oversized event and snapshot JSON. See `withBlobOffload`. */
|
|
8
|
+
readonly blobs?: BlobStore;
|
|
7
9
|
}
|
|
8
10
|
export interface DurableObjectEventStore extends EventStore {
|
|
9
11
|
readonly projectorDelivery?: ProjectorDelivery;
|
|
10
12
|
}
|
|
11
13
|
/**
|
|
12
14
|
* Creates an EventStore backed by a Durable Object's embedded SQLite database (`ctx.storage.sql`).
|
|
13
|
-
* Optionally attaches
|
|
15
|
+
* Optionally attaches projectors. Their delivery is scheduled with `waitUntil`, so an append
|
|
16
|
+
* returns when the log commits. A crashed delivery is retried from the durable cursor on the next
|
|
17
|
+
* alarm or request.
|
|
14
18
|
*/
|
|
15
19
|
export declare function durableObjectEventStore(ctx: DurableObjectState, options?: DurableObjectStoreOptions): DurableObjectEventStore;
|
|
@@ -1,18 +1,31 @@
|
|
|
1
|
+
import { Effect } from 'effect';
|
|
2
|
+
import { snapshotStoreOf, withBlobOffload } from '@swirls/looms/core';
|
|
1
3
|
import { sqliteEventStore, sqlStorageExec } from '@swirls/looms/core/sqlite-store';
|
|
2
4
|
import { createProjectorDelivery, sqliteProjectorCursorStore, withProjectors, } from '@swirls/looms/projectors';
|
|
3
5
|
/**
|
|
4
6
|
* Creates an EventStore backed by a Durable Object's embedded SQLite database (`ctx.storage.sql`).
|
|
5
|
-
* Optionally attaches
|
|
7
|
+
* Optionally attaches projectors. Their delivery is scheduled with `waitUntil`, so an append
|
|
8
|
+
* returns when the log commits. A crashed delivery is retried from the durable cursor on the next
|
|
9
|
+
* alarm or request.
|
|
6
10
|
*/
|
|
7
11
|
export function durableObjectEventStore(ctx, options = {}) {
|
|
8
12
|
const exec = sqlStorageExec(ctx.storage.sql, {
|
|
9
13
|
// DO SQLite rejects BEGIN/COMMIT/ROLLBACK; use the storage transaction API.
|
|
10
14
|
transaction: (fn) => ctx.storage.transactionSync(fn),
|
|
11
15
|
});
|
|
12
|
-
const
|
|
16
|
+
const sqlite = sqliteEventStore({ exec });
|
|
17
|
+
const snapshots = snapshotStoreOf(sqlite);
|
|
18
|
+
const store = options.blobs
|
|
19
|
+
? withBlobOffload(sqlite, options.blobs, snapshots ? { snapshots } : undefined)
|
|
20
|
+
: sqlite;
|
|
13
21
|
if (options.projectors && options.projectors.length > 0) {
|
|
14
22
|
const delivery = createProjectorDelivery(store, options.projectors, sqliteProjectorCursorStore(exec), { scheduleRetry: options.scheduleProjectorRetry });
|
|
15
|
-
const wrapped = withProjectors(store, options.projectors, {
|
|
23
|
+
const wrapped = withProjectors(store, options.projectors, {
|
|
24
|
+
delivery,
|
|
25
|
+
scheduleDelivery: (_runId, work) => {
|
|
26
|
+
ctx.waitUntil(Effect.runPromise(work));
|
|
27
|
+
},
|
|
28
|
+
});
|
|
16
29
|
return Object.assign(wrapped, { projectorDelivery: delivery });
|
|
17
30
|
}
|
|
18
31
|
return store;
|
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
export { durableObjectAlarms, type AlarmStorage, type DurableObjectAlarms } from './alarm-scheduler';
|
|
2
2
|
export { durableObjectEventStore, type DurableObjectEventStore, type DurableObjectStoreOptions, } from './event-store';
|
|
3
|
-
export { LoomsDurableObject, type LoomsDurableObjectConfig } from './durable-object';
|
|
3
|
+
export { durableObjectLauncher, durableObjectScheduleHost, LoomsDurableObject, type DurableObjectRunStub, type LoomsDurableObjectConfig, } from './durable-object';
|
|
4
|
+
export { r2BlobStore, type R2BucketLike, type R2ListResult, type R2ObjectBodyLike, type R2StoredObject, } from './r2-blob-store';
|
|
4
5
|
export { routeToDurableObject, type DurableObjectNamespaceLike, type DurableObjectStubLike, } from './router';
|
|
6
|
+
export { createMemoryQueue, type MemoryQueue, type MemoryQueueOptions } from './memory-queue';
|
|
7
|
+
export { processQueueMessage, queueConsumer, QueueTaskError, type QueueBatchLike, type QueueConsumerOptions, type QueueTask, type QueueTaskContext, type QueueTaskHandler, type WorkerCallbackClient, } from './queue-consumer';
|
|
8
|
+
export { readQueueEffectMessage, type QueueEffectMessage, type QueueMessageLike, } from './queue-message';
|
|
9
|
+
export { cloudflareQueuePullSource, createQueuePullConsumer, QueuePullError, type CloudflareQueuePullSourceOptions, type PulledQueueMessage, type QueueLeaseRetry, type QueuePullConsumer, type QueuePullConsumerOptions, type QueuePullResult, type QueuePullSource, } from './queue-pull';
|
|
10
|
+
export { cloudflareQueueWorker, DEFAULT_MAX_QUEUE_MESSAGE_BYTES, QueueDispatchError, type CloudflareQueueWorkerOptions, type QueueProducer, type QueueRoute, } from './queue-worker';
|
package/dist/cloudflare/index.js
CHANGED
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
export { durableObjectAlarms } from './alarm-scheduler.js';
|
|
2
2
|
export { durableObjectEventStore, } from './event-store.js';
|
|
3
|
-
export { LoomsDurableObject } from './durable-object.js';
|
|
3
|
+
export { durableObjectLauncher, durableObjectScheduleHost, LoomsDurableObject, } from './durable-object.js';
|
|
4
|
+
export { r2BlobStore, } from './r2-blob-store.js';
|
|
4
5
|
export { routeToDurableObject, } from './router.js';
|
|
6
|
+
export { createMemoryQueue } from './memory-queue.js';
|
|
7
|
+
export { processQueueMessage, queueConsumer, QueueTaskError, } from './queue-consumer.js';
|
|
8
|
+
export { readQueueEffectMessage, } from './queue-message.js';
|
|
9
|
+
export { cloudflareQueuePullSource, createQueuePullConsumer, QueuePullError, } from './queue-pull.js';
|
|
10
|
+
export { cloudflareQueueWorker, DEFAULT_MAX_QUEUE_MESSAGE_BYTES, QueueDispatchError, } from './queue-worker.js';
|