@swirls/looms 0.0.2 → 0.2.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 +24 -2
- package/dist/actor/cell.d.ts +9 -4
- package/dist/actor/cell.js +7 -11
- package/dist/actor/host.d.ts +7 -3
- package/dist/actor/host.js +57 -33
- package/dist/actor/index.d.ts +1 -1
- package/dist/actor/index.js +1 -1
- package/dist/actor/routing.d.ts +12 -1
- package/dist/actor/routing.js +57 -51
- package/dist/agent/agent-session.d.ts +5 -0
- package/dist/agent/agent-session.js +270 -0
- package/dist/agent/debugger/agent-chat.d.ts +2 -0
- package/dist/agent/debugger/agent-chat.js +176 -0
- package/dist/agent/debugger/index.d.ts +5 -0
- package/dist/agent/debugger/index.js +19 -0
- package/dist/agent/debugger/summarize.d.ts +2 -0
- package/dist/agent/debugger/summarize.js +51 -0
- package/dist/agent/debugger/token-usage.d.ts +3 -0
- package/dist/agent/debugger/token-usage.js +11 -0
- package/dist/agent/definitions-store.d.ts +2 -3
- package/dist/agent/definitions-store.js +2 -4
- package/dist/agent/definitions.d.ts +47 -29
- package/dist/agent/definitions.js +39 -12
- package/dist/agent/effects.d.ts +359 -2
- package/dist/agent/effects.js +60 -34
- package/dist/agent/events.d.ts +175 -19
- package/dist/agent/events.js +45 -10
- package/dist/agent/index.d.ts +1 -0
- package/dist/agent/index.js +1 -0
- package/dist/agent/llm.d.ts +11 -2
- package/dist/agent/llm.js +13 -3
- package/dist/agent/module.d.ts +812 -78
- package/dist/agent/module.js +16 -4
- package/dist/agent/scope.d.ts +174 -18
- package/dist/agent/signals.d.ts +26 -1
- package/dist/agent/signals.js +79 -0
- package/dist/agent/threads.d.ts +3 -33
- package/dist/agent/threads.js +20 -25
- package/dist/agent/tool-schema.d.ts +1 -17
- package/dist/agent/tool-schema.js +3 -43
- package/dist/agent/types.d.ts +49 -4
- package/dist/agent/types.js +5 -5
- package/dist/ai-vercel/index.d.ts +1 -0
- package/dist/ai-vercel/index.js +1 -0
- package/dist/ai-vercel/messages.js +22 -7
- package/dist/ai-vercel/vercel-evaluator.d.ts +36 -0
- package/dist/ai-vercel/vercel-evaluator.js +73 -0
- package/dist/ai-vercel/vercel-llm.js +64 -44
- package/dist/approval/debugger/approvals-view.d.ts +3 -0
- package/dist/approval/debugger/approvals-view.js +16 -0
- package/dist/approval/debugger/index.d.ts +4 -0
- package/dist/approval/debugger/index.js +11 -0
- package/dist/approval/debugger/summarize.d.ts +2 -0
- package/dist/approval/debugger/summarize.js +17 -0
- package/dist/approval/effects.d.ts +89 -0
- package/dist/approval/module.d.ts +179 -29
- package/dist/approval/projections.d.ts +0 -14
- package/dist/approval/projections.js +20 -3
- package/dist/approval/signals.d.ts +2 -0
- package/dist/approval/signals.js +18 -2
- package/dist/cli/cli.js +2 -0
- package/dist/cli/commands.js +12 -16
- package/dist/client/client.d.ts +1213 -18
- package/dist/client/client.js +120 -120
- package/dist/client/index.d.ts +1 -1
- package/dist/client/index.js +1 -1
- package/dist/client/sse.d.ts +13 -0
- package/dist/client/sse.js +38 -44
- package/dist/cloudflare/alarm-scheduler.d.ts +12 -2
- package/dist/cloudflare/alarm-scheduler.js +34 -7
- package/dist/cloudflare/durable-object.d.ts +10 -2
- package/dist/cloudflare/durable-object.js +59 -28
- package/dist/cloudflare/event-store.d.ts +7 -2
- package/dist/cloudflare/event-store.js +8 -7
- package/dist/cloudflare/index.d.ts +2 -2
- package/dist/cloudflare/index.js +2 -2
- package/dist/cloudflare/router.d.ts +1 -1
- package/dist/cloudflare/router.js +15 -16
- package/dist/core/catalog.d.ts +3 -3
- package/dist/core/catalog.js +2 -5
- package/dist/core/coalesce.js +5 -3
- package/dist/core/compose.d.ts +2 -1
- package/dist/core/compose.js +19 -2
- package/dist/core/debugger-host.d.ts +16 -0
- package/dist/core/debugger-host.js +1 -0
- package/dist/core/definition-store.d.ts +11 -0
- package/dist/core/definition-store.js +6 -0
- package/dist/core/effects.d.ts +127 -15
- package/dist/core/effects.js +89 -17
- package/dist/core/envelope.d.ts +27 -7
- package/dist/core/envelope.js +20 -6
- package/dist/core/event-codec.d.ts +4 -2
- package/dist/core/event-codec.js +10 -5
- package/dist/core/event-index.js +1 -3
- package/dist/core/fold.d.ts +3 -0
- package/dist/core/fold.js +171 -24
- package/dist/core/ids.d.ts +5 -0
- package/dist/core/ids.js +35 -2
- package/dist/core/index.d.ts +7 -1
- package/dist/core/index.js +6 -1
- package/dist/core/kind.d.ts +22 -0
- package/dist/core/kind.js +17 -0
- package/dist/core/limits.d.ts +34 -0
- package/dist/core/limits.js +46 -0
- package/dist/core/match.d.ts +1 -0
- package/dist/core/match.js +10 -1
- package/dist/core/module-scope.d.ts +22 -20
- package/dist/core/module-scope.js +38 -28
- package/dist/core/module.d.ts +18 -5
- package/dist/core/module.js +4 -0
- package/dist/core/projection.d.ts +13 -13
- package/dist/core/projection.js +9 -5
- package/dist/core/protocol.d.ts +68 -11
- package/dist/core/protocol.js +82 -11
- package/dist/core/published.d.ts +43 -0
- package/dist/core/published.js +44 -0
- package/dist/core/replay.d.ts +312 -3
- package/dist/core/replay.js +11 -1
- package/dist/core/schema.d.ts +11 -20
- package/dist/core/schema.js +63 -80
- package/dist/core/serialize.js +2 -4
- package/dist/core/snapshot-store.d.ts +6 -1
- package/dist/core/snapshot-store.js +15 -2
- package/dist/core/snapshots.d.ts +5 -1
- package/dist/core/snapshots.js +10 -15
- package/dist/core/sqlite-store.js +95 -11
- package/dist/core/state.d.ts +142 -4
- package/dist/core/state.js +78 -0
- package/dist/core/store.d.ts +38 -3
- package/dist/core/store.js +77 -22
- package/dist/core/thread.d.ts +10 -9
- package/dist/core/types.d.ts +5 -8
- package/dist/core/types.js +28 -24
- package/dist/core/validate-event.js +14 -4
- package/dist/core/worker-callback.d.ts +17 -0
- package/dist/core/worker-callback.js +1 -0
- package/dist/debugger/app/assets/index-B3Ah8WAE.css +2 -0
- package/dist/debugger/app/assets/index-Qb371t_O.js +22 -0
- package/dist/debugger/app/assets/rolldown-runtime-CbXtAM7H.js +1 -0
- package/dist/debugger/app/assets/routes-CjtEHgPQ.js +47 -0
- package/dist/debugger/app/index.html +14 -0
- package/dist/debugger/components/composer.d.ts +17 -0
- package/dist/debugger/components/composer.js +25 -0
- package/dist/debugger/components/debugger-split.d.ts +2 -1
- package/dist/debugger/components/debugger-split.js +2 -2
- package/dist/debugger/components/event-inspector.d.ts +2 -1
- package/dist/debugger/components/event-inspector.js +25 -16
- package/dist/debugger/components/event-stream.d.ts +2 -1
- package/dist/debugger/components/event-stream.js +14 -74
- package/dist/debugger/components/follow-list.d.ts +19 -0
- package/dist/debugger/components/follow-list.js +96 -0
- package/dist/debugger/components/json-tree.d.ts +11 -0
- package/dist/debugger/components/json-tree.js +120 -0
- package/dist/debugger/components/json-view.js +2 -2
- package/dist/debugger/components/run-tree.js +53 -20
- package/dist/debugger/components/status-dot.d.ts +4 -0
- package/dist/debugger/components/status-dot.js +6 -2
- package/dist/debugger/context.d.ts +14 -0
- package/dist/debugger/context.js +17 -0
- package/dist/debugger/contracts.d.ts +7 -4
- package/dist/debugger/events/builtin-families.d.ts +3 -0
- package/dist/debugger/events/builtin-families.js +9 -0
- package/dist/debugger/events/catalog.d.ts +3 -4
- package/dist/debugger/events/catalog.js +27 -17
- package/dist/debugger/events/protocol-summarize.d.ts +1 -2
- package/dist/debugger/events/protocol-summarize.js +42 -24
- package/dist/debugger/index.d.ts +20 -6
- package/dist/debugger/index.js +19 -5
- package/dist/debugger/lib/cn.d.ts +1 -0
- package/dist/debugger/lib/cn.js +3 -0
- package/dist/debugger/lib/event-meta.d.ts +2 -3
- package/dist/debugger/lib/event-meta.js +6 -12
- package/dist/debugger/lib/json.d.ts +25 -0
- package/dist/debugger/lib/json.js +87 -0
- package/dist/debugger/lib/payload.d.ts +7 -0
- package/dist/debugger/lib/payload.js +11 -0
- package/dist/debugger/lib/time.d.ts +3 -0
- package/dist/debugger/lib/time.js +24 -0
- package/dist/debugger/plugin.d.ts +36 -0
- package/dist/debugger/plugin.js +14 -0
- package/dist/debugger/server/index.d.ts +14 -0
- package/dist/debugger/server/index.js +114 -0
- package/dist/debugger/shell/debugger-shell.d.ts +15 -0
- package/dist/debugger/shell/debugger-shell.js +43 -0
- package/dist/debugger/shell/definition-key.d.ts +3 -0
- package/dist/debugger/shell/definition-key.js +4 -0
- package/dist/debugger/shell/events-pane.d.ts +12 -0
- package/dist/debugger/shell/events-pane.js +25 -0
- package/dist/debugger/shell/projections-panel.d.ts +4 -0
- package/dist/debugger/shell/projections-panel.js +59 -0
- package/dist/debugger/shell/run-controls.d.ts +4 -0
- package/dist/debugger/shell/run-controls.js +72 -0
- package/dist/debugger/shell/run-search.d.ts +6 -0
- package/dist/debugger/shell/run-search.js +102 -0
- package/dist/debugger/shell/run-type-menu.d.ts +6 -0
- package/dist/debugger/shell/run-type-menu.js +11 -0
- package/dist/debugger/shell/schema-form.d.ts +31 -0
- package/dist/debugger/shell/schema-form.js +117 -0
- package/dist/debugger/shell/start-form.d.ts +3 -0
- package/dist/debugger/shell/start-form.js +75 -0
- package/dist/debugger/shell/use-start-run.d.ts +14 -0
- package/dist/debugger/shell/use-start-run.js +32 -0
- package/dist/debugger/shell/workspace.d.ts +6 -0
- package/dist/debugger/shell/workspace.js +15 -0
- package/dist/debugger/styles.css +111 -27
- package/dist/debugger/ui/button.d.ts +24 -0
- package/dist/debugger/ui/button.js +24 -0
- package/dist/debugger/ui/command.d.ts +9 -0
- package/dist/debugger/ui/command.js +24 -0
- package/dist/debugger/ui/dropdown-menu.d.ts +13 -0
- package/dist/debugger/ui/dropdown-menu.js +37 -0
- package/dist/debugger/ui/panel.d.ts +14 -0
- package/dist/debugger/ui/panel.js +16 -0
- package/dist/debugger/ui/popover.d.ts +7 -0
- package/dist/debugger/ui/popover.js +17 -0
- package/dist/debugger/ui/resizable.d.ts +7 -0
- package/dist/debugger/ui/resizable.js +14 -0
- package/dist/debugger/ui/scroll-area.d.ts +2 -1
- package/dist/debugger/ui/scroll-area.js +5 -3
- package/dist/debugger/ui/select.d.ts +10 -0
- package/dist/debugger/ui/select.js +21 -0
- package/dist/debugger/ui/tabs.js +2 -2
- package/dist/debugger/ui/textarea.d.ts +3 -0
- package/dist/debugger/ui/textarea.js +7 -0
- package/dist/evaluator/definitions-store.d.ts +11 -0
- package/dist/evaluator/definitions-store.js +8 -0
- package/dist/evaluator/definitions.d.ts +18 -0
- package/dist/evaluator/definitions.js +4 -0
- package/dist/evaluator/effects.d.ts +226 -0
- package/dist/evaluator/effects.js +132 -0
- package/dist/evaluator/evaluator.d.ts +24 -0
- package/dist/evaluator/evaluator.js +67 -0
- package/dist/evaluator/events.d.ts +59 -0
- package/dist/evaluator/events.js +21 -0
- package/dist/evaluator/index.d.ts +11 -0
- package/dist/evaluator/index.js +11 -0
- package/dist/evaluator/module.d.ts +606 -0
- package/dist/evaluator/module.js +25 -0
- package/dist/evaluator/projections.d.ts +50 -0
- package/dist/evaluator/projections.js +20 -0
- package/dist/evaluator/scope.d.ts +59 -0
- package/dist/evaluator/scope.js +7 -0
- package/dist/evaluator/signals.d.ts +3 -0
- package/dist/evaluator/signals.js +17 -0
- package/dist/evaluator/threads.d.ts +62 -0
- package/dist/evaluator/threads.js +117 -0
- package/dist/evaluator/types.d.ts +125 -0
- package/dist/evaluator/types.js +51 -0
- package/dist/projectors/delivery.d.ts +83 -0
- package/dist/projectors/delivery.js +182 -0
- package/dist/projectors/index-model.d.ts +1 -1
- package/dist/projectors/index-model.js +4 -6
- package/dist/projectors/index.d.ts +2 -0
- package/dist/projectors/index.js +2 -0
- package/dist/projectors/memory.d.ts +1 -1
- package/dist/projectors/memory.js +10 -8
- package/dist/projectors/postgres.js +44 -58
- package/dist/projectors/projector.d.ts +17 -0
- package/dist/projectors/projector.js +26 -1
- package/dist/projectors/sqlite-delivery.d.ts +4 -0
- package/dist/projectors/sqlite-delivery.js +147 -0
- package/dist/projectors/sqlite.js +23 -23
- package/dist/projectors/with-projectors.d.ts +7 -2
- package/dist/projectors/with-projectors.js +27 -28
- package/dist/react/notify.js +11 -8
- package/dist/react/react.d.ts +2 -3
- package/dist/react/react.js +14 -30
- package/dist/react/sse.d.ts +1 -1
- package/dist/react/sse.js +1 -1
- package/dist/react/store.d.ts +3 -3
- package/dist/react/store.js +109 -119
- package/dist/runtime/cancellation.d.ts +16 -0
- package/dist/runtime/cancellation.js +129 -0
- package/dist/runtime/deadline-transition-planner.d.ts +31 -0
- package/dist/runtime/deadline-transition-planner.js +139 -0
- package/dist/runtime/dispatch-effect.d.ts +3 -2
- package/dist/runtime/dispatch-effect.js +7 -13
- package/dist/runtime/errors.d.ts +51 -0
- package/dist/runtime/errors.js +39 -0
- package/dist/runtime/{runtime-helpers.d.ts → helpers.d.ts} +24 -4
- package/dist/runtime/{runtime-helpers.js → helpers.js} +56 -19
- package/dist/runtime/index.d.ts +3 -3
- package/dist/runtime/index.js +3 -3
- package/dist/runtime/ingress.d.ts +15 -0
- package/dist/runtime/ingress.js +42 -0
- package/dist/runtime/local-effect-executor.d.ts +21 -0
- package/dist/runtime/local-effect-executor.js +137 -0
- package/dist/runtime/looms.d.ts +30 -4
- package/dist/runtime/looms.js +57 -37
- package/dist/runtime/observer.d.ts +45 -0
- package/dist/runtime/observer.js +11 -0
- package/dist/runtime/repository.d.ts +55 -0
- package/dist/runtime/repository.js +293 -0
- package/dist/runtime/retry-policy.d.ts +16 -0
- package/dist/runtime/retry-policy.js +29 -0
- package/dist/runtime/run-execution-context.d.ts +30 -0
- package/dist/runtime/run-execution-context.js +56 -0
- package/dist/runtime/run-start.d.ts +4 -0
- package/dist/runtime/run-start.js +44 -0
- package/dist/runtime/runtime.d.ts +6 -87
- package/dist/runtime/runtime.js +248 -470
- package/dist/runtime/server/auth.d.ts +41 -0
- package/dist/runtime/server/auth.js +45 -0
- package/dist/runtime/server/debugger-host.d.ts +11 -0
- package/dist/runtime/server/debugger-host.js +32 -0
- package/dist/runtime/server/fetch-handler.d.ts +18 -0
- package/dist/runtime/server/fetch-handler.js +62 -0
- package/dist/runtime/server/http.d.ts +38 -0
- package/dist/runtime/server/http.js +66 -0
- package/dist/runtime/server/route-table.d.ts +55 -0
- package/dist/runtime/server/route-table.js +74 -0
- package/dist/runtime/server/routes/definitions.d.ts +3 -0
- package/dist/runtime/server/routes/definitions.js +20 -0
- package/dist/runtime/server/routes/effects.d.ts +3 -0
- package/dist/runtime/server/routes/effects.js +59 -0
- package/dist/runtime/server/routes/operations.d.ts +3 -0
- package/dist/runtime/server/routes/operations.js +14 -0
- package/dist/runtime/server/routes/runs.d.ts +3 -0
- package/dist/runtime/server/routes/runs.js +199 -0
- package/dist/runtime/server.d.ts +4 -8
- package/dist/runtime/server.js +6 -211
- package/dist/runtime/sse.d.ts +2 -1
- package/dist/runtime/sse.js +24 -17
- package/dist/runtime/types.d.ts +117 -0
- package/dist/runtime/types.js +1 -0
- package/dist/runtime/wake-engine.d.ts +20 -0
- package/dist/runtime/wake-engine.js +311 -0
- package/dist/runtime/wake-scheduler.d.ts +3 -4
- package/dist/runtime/wake-scheduler.js +25 -31
- package/dist/runtime/worker-callbacks.d.ts +16 -0
- package/dist/runtime/worker-callbacks.js +167 -0
- package/dist/runtime/worker-effect-dispatch.d.ts +36 -0
- package/dist/runtime/worker-effect-dispatch.js +126 -0
- package/dist/s2/config.d.ts +1 -1
- package/dist/s2/config.js +1 -1
- package/dist/s2/lite.js +70 -82
- package/dist/s2/pagination.d.ts +15 -0
- package/dist/s2/pagination.js +36 -0
- package/dist/s2/projector.js +6 -5
- package/dist/s2/snapshot-store.js +188 -161
- package/dist/s2/store.d.ts +3 -21
- package/dist/s2/store.js +71 -165
- package/dist/testing/index.d.ts +15 -0
- package/dist/testing/index.js +53 -16
- package/dist/workflow/debugger/index.d.ts +4 -0
- package/dist/workflow/debugger/index.js +11 -0
- package/dist/workflow/debugger/nodes-view.d.ts +3 -0
- package/dist/workflow/debugger/nodes-view.js +13 -0
- package/dist/workflow/debugger/summarize.d.ts +2 -0
- package/dist/workflow/debugger/summarize.js +30 -0
- package/dist/workflow/definitions-store.d.ts +2 -3
- package/dist/workflow/definitions-store.js +2 -2
- package/dist/workflow/definitions.d.ts +78 -22
- package/dist/workflow/definitions.js +80 -14
- package/dist/workflow/effects.d.ts +453 -107
- package/dist/workflow/effects.js +214 -115
- package/dist/workflow/events.d.ts +149 -5
- package/dist/workflow/events.js +27 -5
- package/dist/workflow/graph.d.ts +56 -0
- package/dist/workflow/graph.js +207 -0
- package/dist/workflow/index.d.ts +1 -0
- package/dist/workflow/index.js +1 -0
- package/dist/workflow/module.d.ts +1047 -266
- package/dist/workflow/projections.d.ts +14 -12
- package/dist/workflow/projections.js +30 -8
- package/dist/workflow/scope.d.ts +146 -4
- package/dist/workflow/threads.d.ts +383 -155
- package/dist/workflow/threads.js +308 -110
- package/package.json +26 -2
- package/dist/debugger/events/domain-summarize.d.ts +0 -3
- package/dist/debugger/events/domain-summarize.js +0 -107
- package/dist/debugger/events/family.d.ts +0 -8
- package/dist/debugger/events/family.js +0 -44
- package/dist/runtime/events-api.d.ts +0 -4
- package/dist/runtime/events-api.js +0 -84
package/dist/client/client.js
CHANGED
|
@@ -1,151 +1,147 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { createRunId, } from '@swirls/looms/core';
|
|
3
|
-
import {
|
|
1
|
+
import { Data, Effect, Option, Schema, Stream } from 'effect';
|
|
2
|
+
import { createRunId, DefinitionCatalogSchema, EventEnvelopeSchema, ReplayStepSchema, RunStateSchema, RunSummariesSchema, stringifyJson, } from '@swirls/looms/core';
|
|
3
|
+
import { consumeSseStreamEffect, eventsFromSseData, sseFrames } from './sse.js';
|
|
4
|
+
class LoomsClientError extends Data.TaggedError('LoomsClientError') {
|
|
5
|
+
constructor(message, cause) {
|
|
6
|
+
super({ cause, message });
|
|
7
|
+
this.name = 'LoomsClientError';
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
const decodeStreamError = Schema.decodeUnknownOption(Schema.fromJsonString(Schema.Struct({ error: Schema.String })));
|
|
11
|
+
const StartResultSchema = Schema.Struct({
|
|
12
|
+
runId: Schema.String,
|
|
13
|
+
threadId: Schema.String,
|
|
14
|
+
state: RunStateSchema,
|
|
15
|
+
});
|
|
16
|
+
const RunResultSchema = Schema.Struct({ runId: Schema.String, state: RunStateSchema });
|
|
17
|
+
const EventsResultSchema = Schema.Struct({
|
|
18
|
+
runId: Schema.String,
|
|
19
|
+
events: Schema.Array(EventEnvelopeSchema),
|
|
20
|
+
});
|
|
21
|
+
const ReplayResultSchema = Schema.Struct({
|
|
22
|
+
runId: Schema.String,
|
|
23
|
+
step: Schema.NullOr(ReplayStepSchema),
|
|
24
|
+
});
|
|
25
|
+
const ProjectionResultSchema = Schema.Struct({
|
|
26
|
+
runId: Schema.String,
|
|
27
|
+
name: Schema.String,
|
|
28
|
+
value: Schema.Unknown,
|
|
29
|
+
});
|
|
4
30
|
export function createLoomsClient(options = {}) {
|
|
5
31
|
const baseUrl = (options.baseUrl ?? '').replace(/\/$/, '');
|
|
6
32
|
const fetchImpl = options.fetch ?? fetch;
|
|
7
|
-
|
|
33
|
+
function request(path, schema, init) {
|
|
8
34
|
const headers = new Headers(init?.headers);
|
|
9
35
|
if (!headers.has('content-type')) {
|
|
10
36
|
headers.set('content-type', 'application/json');
|
|
11
37
|
}
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
38
|
+
return Effect.runPromise(Effect.gen(function* () {
|
|
39
|
+
const res = yield* Effect.tryPromise({
|
|
40
|
+
try: (signal) => fetchImpl(`${baseUrl}${path}`, { ...init, headers, signal }),
|
|
41
|
+
catch: (cause) => new LoomsClientError('Request failed', cause),
|
|
42
|
+
});
|
|
43
|
+
if (!res.ok) {
|
|
44
|
+
const body = yield* Effect.promise(() => res.text());
|
|
45
|
+
return yield* new LoomsClientError(body || `HTTP ${res.status}`);
|
|
46
|
+
}
|
|
47
|
+
const body = yield* Effect.promise(() => res.json());
|
|
48
|
+
return yield* Schema.decodeUnknownEffect(schema)(body).pipe(Effect.mapError((cause) => new LoomsClientError('Invalid response body', cause)));
|
|
49
|
+
}));
|
|
19
50
|
}
|
|
20
51
|
const subscribeEvents = (runId, onEvent, reconnectDelayMs = 1_000) => {
|
|
21
52
|
let fromSeq = 1;
|
|
22
53
|
const controller = new AbortController();
|
|
23
|
-
const run =
|
|
54
|
+
const run = Effect.gen(function* () {
|
|
24
55
|
while (!controller.signal.aborted) {
|
|
25
|
-
|
|
56
|
+
yield* Effect.gen(function* () {
|
|
26
57
|
const cursor = Math.max(0, fromSeq - 1);
|
|
27
58
|
const headers = new Headers({ accept: 'text/event-stream' });
|
|
28
59
|
if (cursor > 0) {
|
|
29
60
|
headers.set('last-event-id', String(cursor));
|
|
30
61
|
}
|
|
31
|
-
const res =
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
62
|
+
const res = yield* Effect.tryPromise({
|
|
63
|
+
try: (signal) => fetchImpl(`${baseUrl}/runs/${encodeURIComponent(runId)}/events?fromSeq=${fromSeq}`, {
|
|
64
|
+
headers,
|
|
65
|
+
signal,
|
|
66
|
+
}),
|
|
67
|
+
catch: (cause) => new LoomsClientError('Event subscription failed', cause),
|
|
68
|
+
});
|
|
69
|
+
const body = res.body;
|
|
70
|
+
if (res.ok && body) {
|
|
71
|
+
return yield* consumeSseStreamEffect(body, (frame) => {
|
|
72
|
+
for (const event of eventsFromSseData(frame.data, runId)) {
|
|
73
|
+
onEvent(event);
|
|
74
|
+
fromSeq = Math.max(fromSeq, event.seq + 1);
|
|
75
|
+
}
|
|
76
|
+
});
|
|
45
77
|
}
|
|
46
|
-
|
|
78
|
+
return yield* new LoomsClientError(res.ok ? 'event stream missing body' : `HTTP ${res.status}`);
|
|
79
|
+
}).pipe(Effect.ignore);
|
|
47
80
|
if (controller.signal.aborted) {
|
|
48
|
-
|
|
81
|
+
break;
|
|
49
82
|
}
|
|
50
|
-
|
|
83
|
+
yield* Effect.sleep(reconnectDelayMs);
|
|
51
84
|
}
|
|
52
|
-
};
|
|
53
|
-
void run();
|
|
85
|
+
});
|
|
86
|
+
void Effect.runPromise(run, { signal: controller.signal }).catch(() => undefined);
|
|
54
87
|
return () => {
|
|
55
88
|
controller.abort();
|
|
56
89
|
};
|
|
57
90
|
};
|
|
58
|
-
const startRun = (args) => request('/runs', { method: 'POST', body:
|
|
91
|
+
const startRun = (args) => request('/runs', StartResultSchema, { method: 'POST', body: stringifyJson(args) });
|
|
92
|
+
const workerCallback = (runId, input) => {
|
|
93
|
+
const body = input.kind === 'complete'
|
|
94
|
+
? { events: input.events }
|
|
95
|
+
: input.kind === 'fail'
|
|
96
|
+
? { error: input.error }
|
|
97
|
+
: {};
|
|
98
|
+
return request(`/runs/${encodeURIComponent(runId)}/effects/${encodeURIComponent(input.effectId)}/${input.attempt}/${input.kind}`, RunResultSchema, {
|
|
99
|
+
method: 'POST',
|
|
100
|
+
headers: options.workerCallbackToken
|
|
101
|
+
? { authorization: `Bearer ${options.workerCallbackToken}` }
|
|
102
|
+
: undefined,
|
|
103
|
+
body: stringifyJson(body),
|
|
104
|
+
});
|
|
105
|
+
};
|
|
59
106
|
const streamRun = (args) => {
|
|
60
107
|
const runId = args.runId ?? createRunId();
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
if (frame.event === 'error') {
|
|
83
|
-
try {
|
|
84
|
-
const parsed = JSON.parse(frame.data);
|
|
85
|
-
const message = Predicate.isReadonlyObject(parsed) && Predicate.isString(parsed.error)
|
|
86
|
-
? parsed.error
|
|
87
|
-
: frame.data;
|
|
88
|
-
streamError = new Error(message);
|
|
89
|
-
}
|
|
90
|
-
catch {
|
|
91
|
-
streamError = new Error(frame.data);
|
|
92
|
-
}
|
|
93
|
-
return;
|
|
94
|
-
}
|
|
95
|
-
if (frame.event === 'done') {
|
|
96
|
-
finished = true;
|
|
97
|
-
notify?.();
|
|
98
|
-
return;
|
|
99
|
-
}
|
|
100
|
-
for (const event of eventsFromSseData(frame.data, runId)) {
|
|
101
|
-
pending.push(event);
|
|
102
|
-
}
|
|
103
|
-
notify?.();
|
|
104
|
-
}, controller.signal)
|
|
105
|
-
.catch((cause) => {
|
|
106
|
-
streamError = cause instanceof Error ? cause : new Error(String(cause));
|
|
107
|
-
})
|
|
108
|
-
.finally(() => {
|
|
109
|
-
finished = true;
|
|
110
|
-
notify?.();
|
|
111
|
-
});
|
|
112
|
-
try {
|
|
113
|
-
while (true) {
|
|
114
|
-
if (pending.length === 0) {
|
|
115
|
-
if (finished) {
|
|
116
|
-
break;
|
|
117
|
-
}
|
|
118
|
-
await new Promise((resolve) => {
|
|
119
|
-
notify = resolve;
|
|
120
|
-
});
|
|
121
|
-
continue;
|
|
122
|
-
}
|
|
123
|
-
const event = pending.shift();
|
|
124
|
-
if (event) {
|
|
125
|
-
yield event;
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
await consume;
|
|
129
|
-
if (streamError) {
|
|
130
|
-
throw streamError;
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
finally {
|
|
134
|
-
controller.abort();
|
|
135
|
-
}
|
|
136
|
-
},
|
|
137
|
-
};
|
|
108
|
+
const stream = Stream.unwrap(Effect.tryPromise({
|
|
109
|
+
try: (signal) => fetchImpl(`${baseUrl}/runs`, {
|
|
110
|
+
method: 'POST',
|
|
111
|
+
headers: {
|
|
112
|
+
'content-type': 'application/json',
|
|
113
|
+
accept: 'text/event-stream',
|
|
114
|
+
},
|
|
115
|
+
body: stringifyJson({ ...args, runId }),
|
|
116
|
+
signal,
|
|
117
|
+
}),
|
|
118
|
+
catch: (cause) => new LoomsClientError('Failed to start event stream', cause),
|
|
119
|
+
}).pipe(Effect.flatMap((res) => res.ok && res.body
|
|
120
|
+
? Effect.succeed(sseFrames(res.body))
|
|
121
|
+
: new LoomsClientError(res.ok ? 'SSE stream missing body' : `HTTP ${res.status}`)))).pipe(Stream.takeWhile((frame) => frame.event !== 'done'), Stream.mapEffect((frame) => {
|
|
122
|
+
if (frame.event !== 'error') {
|
|
123
|
+
return Effect.succeed(eventsFromSseData(frame.data, runId));
|
|
124
|
+
}
|
|
125
|
+
const decoded = decodeStreamError(frame.data);
|
|
126
|
+
return new LoomsClientError(Option.isSome(decoded) ? decoded.value.error : frame.data);
|
|
127
|
+
}), Stream.flatMap(Stream.fromIterable));
|
|
128
|
+
return Object.assign(Stream.toAsyncIterable(stream), { runId });
|
|
138
129
|
};
|
|
139
130
|
return {
|
|
140
131
|
/** Start a run by `{ kind, definitionName }` when you only have names. */
|
|
141
132
|
startRun,
|
|
142
133
|
/** Start a run from a definition object (agent, workflow, or your own kind). */
|
|
143
|
-
start: (definition, input) => request('/runs', {
|
|
134
|
+
start: (definition, input) => request('/runs', StartResultSchema, {
|
|
144
135
|
method: 'POST',
|
|
145
|
-
body:
|
|
136
|
+
body: stringifyJson({
|
|
137
|
+
kind: definition.kind,
|
|
138
|
+
definitionName: definition.name,
|
|
139
|
+
definitionVersion: definition.version,
|
|
140
|
+
input,
|
|
141
|
+
}),
|
|
146
142
|
}),
|
|
147
|
-
getRun: (runId) => request(`/runs/${runId}
|
|
148
|
-
getState: (runId) => request(`/runs/${runId}
|
|
143
|
+
getRun: (runId) => request(`/runs/${encodeURIComponent(runId)}`, RunResultSchema),
|
|
144
|
+
getState: (runId) => request(`/runs/${encodeURIComponent(runId)}`, RunResultSchema),
|
|
149
145
|
getEvents: (runId, opts) => {
|
|
150
146
|
const params = new URLSearchParams();
|
|
151
147
|
if (opts?.fromSeq !== undefined) {
|
|
@@ -155,15 +151,18 @@ export function createLoomsClient(options = {}) {
|
|
|
155
151
|
params.set('limit', String(opts.limit));
|
|
156
152
|
}
|
|
157
153
|
const q = params.toString();
|
|
158
|
-
return request(`/runs/${runId}/events${q ? `?${q}` : ''}
|
|
154
|
+
return request(`/runs/${encodeURIComponent(runId)}/events${q ? `?${q}` : ''}`, EventsResultSchema);
|
|
159
155
|
},
|
|
160
|
-
signal: (runId, events) => request(`/runs/${runId}/events`, {
|
|
156
|
+
signal: (runId, events) => request(`/runs/${encodeURIComponent(runId)}/events`, RunResultSchema, {
|
|
161
157
|
method: 'POST',
|
|
162
|
-
body:
|
|
158
|
+
body: stringifyJson({ events }),
|
|
163
159
|
}),
|
|
164
|
-
wake: (runId) => request(`/runs/${runId}/wake`, { method: 'POST' }),
|
|
165
|
-
|
|
166
|
-
|
|
160
|
+
wake: (runId) => request(`/runs/${encodeURIComponent(runId)}/wake`, RunResultSchema, { method: 'POST' }),
|
|
161
|
+
workerCallback,
|
|
162
|
+
listRunSummaries: () => request('/runs/summaries', RunSummariesSchema),
|
|
163
|
+
listDefinitions: () => request('/definitions', DefinitionCatalogSchema),
|
|
164
|
+
replayTo: (runId, seq) => request(`/runs/${encodeURIComponent(runId)}/replay?seq=${seq}`, ReplayResultSchema),
|
|
165
|
+
project: (runId, name) => request(`/runs/${encodeURIComponent(runId)}/projections/${encodeURIComponent(name)}`, ProjectionResultSchema),
|
|
167
166
|
subscribeEvents,
|
|
168
167
|
/**
|
|
169
168
|
* Subscribe to the run log, then start execution so deltas arrive live.
|
|
@@ -173,6 +172,7 @@ export function createLoomsClient(options = {}) {
|
|
|
173
172
|
stream: (definition, input) => streamRun({
|
|
174
173
|
kind: definition.kind,
|
|
175
174
|
definitionName: definition.name,
|
|
175
|
+
definitionVersion: definition.version,
|
|
176
176
|
input,
|
|
177
177
|
}),
|
|
178
178
|
};
|
package/dist/client/index.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
export { createLoomsClient, type LoomsClient, type LoomsClientOptions, type StartResult, type StreamHandle, } from './client';
|
|
2
|
-
export { consumeSseStream, delay, eventsFromSseData, parseSseFrame, type SseFrame } from './sse';
|
|
2
|
+
export { consumeSseStream, consumeSseStreamEffect, delay, eventsFromSseData, parseSseFrame, type SseFrame, } from './sse';
|
package/dist/client/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
export { createLoomsClient, } from './client.js';
|
|
2
|
-
export { consumeSseStream, delay, eventsFromSseData, parseSseFrame } from './sse.js';
|
|
2
|
+
export { consumeSseStream, consumeSseStreamEffect, delay, eventsFromSseData, parseSseFrame, } from './sse.js';
|
package/dist/client/sse.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { Effect, Stream } from 'effect';
|
|
1
2
|
import { type EventEnvelope } from '@swirls/looms/core';
|
|
2
3
|
export interface SseFrame {
|
|
3
4
|
id?: string;
|
|
@@ -5,6 +6,18 @@ export interface SseFrame {
|
|
|
5
6
|
data: string;
|
|
6
7
|
}
|
|
7
8
|
export declare function parseSseFrame(raw: string): SseFrame | undefined;
|
|
9
|
+
declare const SseStreamError_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & {
|
|
10
|
+
readonly _tag: "SseStreamError";
|
|
11
|
+
} & Readonly<A>;
|
|
12
|
+
export declare class SseStreamError extends SseStreamError_base<{
|
|
13
|
+
readonly cause: unknown;
|
|
14
|
+
readonly message: string;
|
|
15
|
+
}> {
|
|
16
|
+
constructor(cause: unknown);
|
|
17
|
+
}
|
|
18
|
+
export declare function consumeSseStreamEffect(body: ReadableStream<Uint8Array>, onFrame: (frame: SseFrame) => void): Effect.Effect<void, SseStreamError>;
|
|
19
|
+
export declare function sseFrames(body: ReadableStream<Uint8Array>): Stream.Stream<SseFrame, SseStreamError>;
|
|
8
20
|
export declare function consumeSseStream(body: ReadableStream<Uint8Array>, onFrame: (frame: SseFrame) => void, signal?: AbortSignal): Promise<void>;
|
|
9
21
|
export declare function eventsFromSseData(data: string, runId: string): EventEnvelope[];
|
|
10
22
|
export declare function delay(ms: number, signal?: AbortSignal): Promise<void>;
|
|
23
|
+
export {};
|
package/dist/client/sse.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { Predicate } from 'effect';
|
|
1
|
+
import { Data, Effect, Option, Predicate, Queue, Schema, Stream } from 'effect';
|
|
2
2
|
import { decodeLoomsEvent } from '@swirls/looms/core';
|
|
3
|
+
const decodeSseData = Schema.decodeOption(Schema.fromJsonString(Schema.Unknown));
|
|
3
4
|
export function parseSseFrame(raw) {
|
|
4
5
|
let id;
|
|
5
6
|
let event;
|
|
@@ -25,20 +26,24 @@ export function parseSseFrame(raw) {
|
|
|
25
26
|
}
|
|
26
27
|
return { id, event, data: dataLines.join('\n') };
|
|
27
28
|
}
|
|
28
|
-
export
|
|
29
|
-
|
|
29
|
+
export class SseStreamError extends Data.TaggedError('SseStreamError') {
|
|
30
|
+
constructor(cause) {
|
|
31
|
+
super({
|
|
32
|
+
cause,
|
|
33
|
+
message: cause instanceof Error ? cause.message : String(cause),
|
|
34
|
+
});
|
|
35
|
+
this.name = 'SseStreamError';
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
export function consumeSseStreamEffect(body, onFrame) {
|
|
30
39
|
const decoder = new TextDecoder();
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
void reader.cancel();
|
|
34
|
-
};
|
|
35
|
-
signal?.addEventListener('abort', abort);
|
|
36
|
-
try {
|
|
40
|
+
return Effect.acquireUseRelease(Effect.sync(() => body.getReader()), (reader) => Effect.gen(function* () {
|
|
41
|
+
let buffer = '';
|
|
37
42
|
while (true) {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
43
|
+
const { done, value } = yield* Effect.tryPromise({
|
|
44
|
+
try: () => reader.read(),
|
|
45
|
+
catch: (cause) => new SseStreamError(cause),
|
|
46
|
+
});
|
|
42
47
|
if (done) {
|
|
43
48
|
break;
|
|
44
49
|
}
|
|
@@ -52,35 +57,31 @@ export async function consumeSseStream(body, onFrame, signal) {
|
|
|
52
57
|
}
|
|
53
58
|
}
|
|
54
59
|
}
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
60
|
+
}), (reader) => Effect.promise(() => reader.cancel()).pipe(Effect.ignore, Effect.ensuring(Effect.sync(() => reader.releaseLock()).pipe(Effect.ignore))));
|
|
61
|
+
}
|
|
62
|
+
export function sseFrames(body) {
|
|
63
|
+
return Stream.callback((queue) => consumeSseStreamEffect(body, (frame) => {
|
|
64
|
+
Queue.offerUnsafe(queue, frame);
|
|
65
|
+
}).pipe(Effect.tap(() => Effect.sync(() => {
|
|
66
|
+
Queue.endUnsafe(queue);
|
|
67
|
+
}))));
|
|
68
|
+
}
|
|
69
|
+
export function consumeSseStream(body, onFrame, signal) {
|
|
70
|
+
return Effect.runPromise(consumeSseStreamEffect(body, onFrame), { signal });
|
|
65
71
|
}
|
|
66
72
|
export function eventsFromSseData(data, runId) {
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
parsed = JSON.parse(data);
|
|
70
|
-
}
|
|
71
|
-
catch {
|
|
73
|
+
const parsed = decodeSseData(data);
|
|
74
|
+
if (Option.isNone(parsed)) {
|
|
72
75
|
return [];
|
|
73
76
|
}
|
|
74
|
-
if (!Predicate.isReadonlyObject(parsed)) {
|
|
77
|
+
if (!Predicate.isReadonlyObject(parsed.value)) {
|
|
75
78
|
return [];
|
|
76
79
|
}
|
|
77
|
-
const batch = 'batch' in parsed && Array.isArray(parsed.batch) ? parsed.batch : [];
|
|
80
|
+
const batch = 'batch' in parsed.value && Array.isArray(parsed.value.batch) ? parsed.value.batch : [];
|
|
78
81
|
const events = [];
|
|
79
82
|
for (const item of batch) {
|
|
80
83
|
try {
|
|
81
|
-
events.push(decodeLoomsEvent(
|
|
82
|
-
// SAFETY: host SSE batch items are encoded Looms event envelopes.
|
|
83
|
-
item, runId));
|
|
84
|
+
events.push(decodeLoomsEvent(item, runId));
|
|
84
85
|
}
|
|
85
86
|
catch {
|
|
86
87
|
// skip a malformed item and keep the rest of the batch
|
|
@@ -89,15 +90,8 @@ export function eventsFromSseData(data, runId) {
|
|
|
89
90
|
return events;
|
|
90
91
|
}
|
|
91
92
|
export function delay(ms, signal) {
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
}
|
|
97
|
-
const timer = setTimeout(resolve, ms);
|
|
98
|
-
signal?.addEventListener('abort', () => {
|
|
99
|
-
clearTimeout(timer);
|
|
100
|
-
resolve();
|
|
101
|
-
}, { once: true });
|
|
102
|
-
});
|
|
93
|
+
if (signal?.aborted) {
|
|
94
|
+
return Promise.resolve();
|
|
95
|
+
}
|
|
96
|
+
return Effect.runPromise(Effect.sleep(ms), { signal }).catch(() => undefined);
|
|
103
97
|
}
|
|
@@ -1,9 +1,19 @@
|
|
|
1
|
+
import { Effect } from 'effect';
|
|
1
2
|
import type { WakeScheduler } from '@swirls/looms/runtime';
|
|
2
3
|
export interface AlarmStorage {
|
|
3
4
|
setAlarm(scheduledTime: number | Date): Promise<void>;
|
|
4
5
|
deleteAlarm(): Promise<void>;
|
|
6
|
+
getAlarm?(): Promise<number | null>;
|
|
7
|
+
}
|
|
8
|
+
export interface DurableObjectAlarms {
|
|
9
|
+
/** Wake scheduler for the runtime; its deadline is remembered across rearms. */
|
|
10
|
+
readonly scheduler: WakeScheduler;
|
|
11
|
+
/** Ensures the alarm fires no later than `at` without forgetting the runtime deadline. */
|
|
12
|
+
readonly scheduleAtEarliest: (at: number) => Effect.Effect<void>;
|
|
5
13
|
}
|
|
6
14
|
/**
|
|
7
|
-
*
|
|
15
|
+
* The single owner of a Durable Object's one alarm. Every write is serialized and sets the alarm to
|
|
16
|
+
* the earliest of the runtime deadline, `additionalDeadline` (such as projector retries), and the
|
|
17
|
+
* requested time, so concurrent writers cannot overwrite each other's deadlines.
|
|
8
18
|
*/
|
|
9
|
-
export declare function
|
|
19
|
+
export declare function durableObjectAlarms(storage: AlarmStorage, additionalDeadline?: () => Effect.Effect<number | null>): DurableObjectAlarms;
|
|
@@ -1,13 +1,40 @@
|
|
|
1
|
+
import { Effect, Semaphore } from 'effect';
|
|
2
|
+
function earliest(...deadlines) {
|
|
3
|
+
const known = deadlines.filter((deadline) => deadline !== null && deadline !== undefined);
|
|
4
|
+
return known.length === 0 ? null : Math.min(...known);
|
|
5
|
+
}
|
|
1
6
|
/**
|
|
2
|
-
*
|
|
7
|
+
* The single owner of a Durable Object's one alarm. Every write is serialized and sets the alarm to
|
|
8
|
+
* the earliest of the runtime deadline, `additionalDeadline` (such as projector retries), and the
|
|
9
|
+
* requested time, so concurrent writers cannot overwrite each other's deadlines.
|
|
3
10
|
*/
|
|
4
|
-
export function
|
|
11
|
+
export function durableObjectAlarms(storage, additionalDeadline) {
|
|
12
|
+
// `undefined` until the runtime reports a deadline; until then an existing alarm is preserved.
|
|
13
|
+
let runtimeDeadline;
|
|
14
|
+
const writes = Semaphore.makeUnsafe(1);
|
|
15
|
+
const rearm = (requested) => Semaphore.withPermits(writes, 1, Effect.gen(function* () {
|
|
16
|
+
const [additional, current] = yield* Effect.all([
|
|
17
|
+
additionalDeadline?.() ?? Effect.succeed(null),
|
|
18
|
+
Effect.promise(() => storage.getAlarm?.() ?? Promise.resolve(null)),
|
|
19
|
+
], { concurrency: 'unbounded' });
|
|
20
|
+
const preserved = runtimeDeadline === undefined ? current : null;
|
|
21
|
+
const desired = earliest(runtimeDeadline, additional, requested, preserved);
|
|
22
|
+
if (desired === current) {
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
yield* Effect.promise(() => desired === null ? storage.deleteAlarm() : storage.setAlarm(desired));
|
|
26
|
+
}));
|
|
5
27
|
return {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
28
|
+
scheduler: {
|
|
29
|
+
schedule: (_runId, at) => Effect.suspend(() => {
|
|
30
|
+
runtimeDeadline = at;
|
|
31
|
+
return rearm(null);
|
|
32
|
+
}),
|
|
33
|
+
cancel: () => Effect.suspend(() => {
|
|
34
|
+
runtimeDeadline = null;
|
|
35
|
+
return rearm(null);
|
|
36
|
+
}),
|
|
11
37
|
},
|
|
38
|
+
scheduleAtEarliest: rearm,
|
|
12
39
|
};
|
|
13
40
|
}
|
|
@@ -1,14 +1,19 @@
|
|
|
1
1
|
import { DurableObject } from 'cloudflare:workers';
|
|
2
|
-
import type { AnyRuntimeModule } from '@swirls/looms/core';
|
|
2
|
+
import type { AnyRuntimeModule, EventStoreTrimCoverage } from '@swirls/looms/core';
|
|
3
3
|
import type { Projector } from '@swirls/looms/projectors';
|
|
4
|
+
import type { Authorize } from '@swirls/looms/runtime';
|
|
4
5
|
export interface LoomsDurableObjectConfig {
|
|
5
6
|
readonly modules: readonly AnyRuntimeModule[];
|
|
6
7
|
readonly projectors?: readonly Projector[];
|
|
7
8
|
readonly snapshotEvery?: number;
|
|
8
9
|
readonly maxWakeIterations?: number;
|
|
10
|
+
readonly maxPendingRunOperations?: number;
|
|
9
11
|
readonly trimAfterSnapshot?: {
|
|
10
|
-
keepSnapshots: number;
|
|
12
|
+
readonly keepSnapshots: number;
|
|
13
|
+
readonly coverage?: (runId: string) => EventStoreTrimCoverage | Promise<EventStoreTrimCoverage>;
|
|
11
14
|
};
|
|
15
|
+
/** Authorizes the actor's HTTP API; see `createFetchHandler`. */
|
|
16
|
+
readonly authorize?: Authorize;
|
|
12
17
|
}
|
|
13
18
|
/**
|
|
14
19
|
* Base Durable Object class for hosting a Looms actor cell.
|
|
@@ -17,8 +22,11 @@ export interface LoomsDurableObjectConfig {
|
|
|
17
22
|
*/
|
|
18
23
|
export declare abstract class LoomsDurableObject<Env = unknown> extends DurableObject<Env> {
|
|
19
24
|
private cellPromise?;
|
|
25
|
+
private store?;
|
|
20
26
|
abstract configure(env: Env): LoomsDurableObjectConfig | Promise<LoomsDurableObjectConfig>;
|
|
21
27
|
private getCell;
|
|
28
|
+
private createCell;
|
|
22
29
|
fetch(req: Request): Promise<Response>;
|
|
30
|
+
/** A rejected alarm is retried by the platform with backoff, so failures just propagate. */
|
|
23
31
|
alarm(): Promise<void>;
|
|
24
32
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { DurableObject } from 'cloudflare:workers';
|
|
2
|
+
import { Effect } from 'effect';
|
|
2
3
|
import { createActorCell } from '@swirls/looms/actor';
|
|
3
|
-
import {
|
|
4
|
+
import { durableObjectAlarms } from './alarm-scheduler.js';
|
|
4
5
|
import { durableObjectEventStore } from './event-store.js';
|
|
5
6
|
/**
|
|
6
7
|
* Base Durable Object class for hosting a Looms actor cell.
|
|
@@ -9,35 +10,65 @@ import { durableObjectEventStore } from './event-store.js';
|
|
|
9
10
|
*/
|
|
10
11
|
export class LoomsDurableObject extends DurableObject {
|
|
11
12
|
cellPromise;
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
if (!runId) {
|
|
17
|
-
throw new Error('LoomsDurableObject requires a named Durable Object id (use idFromName / getByName)');
|
|
18
|
-
}
|
|
19
|
-
const config = await this.configure(this.env);
|
|
20
|
-
const store = durableObjectEventStore(this.ctx, { projectors: config.projectors });
|
|
21
|
-
const scheduler = alarmScheduler(this.ctx.storage);
|
|
22
|
-
return createActorCell({
|
|
23
|
-
runId,
|
|
24
|
-
store,
|
|
25
|
-
scheduler,
|
|
26
|
-
modules: config.modules,
|
|
27
|
-
snapshotEvery: config.snapshotEvery,
|
|
28
|
-
maxWakeIterations: config.maxWakeIterations,
|
|
29
|
-
trimAfterSnapshot: config.trimAfterSnapshot,
|
|
30
|
-
});
|
|
31
|
-
})();
|
|
13
|
+
store;
|
|
14
|
+
getCell() {
|
|
15
|
+
if (this.cellPromise) {
|
|
16
|
+
return this.cellPromise;
|
|
32
17
|
}
|
|
33
|
-
|
|
18
|
+
const created = this.createCell();
|
|
19
|
+
this.cellPromise = created;
|
|
20
|
+
// A failed setup must not be cached, or every later request and alarm would fail with it.
|
|
21
|
+
created.catch(() => {
|
|
22
|
+
if (this.cellPromise === created) {
|
|
23
|
+
this.cellPromise = undefined;
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
return created;
|
|
34
27
|
}
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
28
|
+
createCell() {
|
|
29
|
+
return Promise.resolve(this.configure(this.env)).then((config) => {
|
|
30
|
+
const runId = this.ctx.id.name;
|
|
31
|
+
if (!runId) {
|
|
32
|
+
throw new Error('LoomsDurableObject requires a named Durable Object id (use idFromName / getByName)');
|
|
33
|
+
}
|
|
34
|
+
const alarms = durableObjectAlarms(this.ctx.storage, () => this.store?.projectorDelivery?.nextRetryAt(runId).pipe(Effect.orDie) ??
|
|
35
|
+
Effect.succeed(null));
|
|
36
|
+
const store = durableObjectEventStore(this.ctx, {
|
|
37
|
+
projectors: config.projectors,
|
|
38
|
+
scheduleProjectorRetry: alarms.scheduleAtEarliest,
|
|
39
|
+
});
|
|
40
|
+
this.store = store;
|
|
41
|
+
const cell = createActorCell({
|
|
42
|
+
runId,
|
|
43
|
+
store,
|
|
44
|
+
scheduler: alarms.scheduler,
|
|
45
|
+
modules: config.modules,
|
|
46
|
+
snapshotEvery: config.snapshotEvery,
|
|
47
|
+
maxWakeIterations: config.maxWakeIterations,
|
|
48
|
+
maxPendingRunOperations: config.maxPendingRunOperations,
|
|
49
|
+
trimAfterSnapshot: config.trimAfterSnapshot,
|
|
50
|
+
authorize: config.authorize,
|
|
51
|
+
});
|
|
52
|
+
const delivery = store.projectorDelivery;
|
|
53
|
+
const recoverProjectors = delivery
|
|
54
|
+
? Effect.runPromise(delivery.recover(runId).pipe(Effect.andThen(delivery.deliver(runId))))
|
|
55
|
+
: Promise.resolve();
|
|
56
|
+
return recoverProjectors.then(() => cell.recoverWake()).then(() => cell);
|
|
57
|
+
});
|
|
38
58
|
}
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
59
|
+
fetch(req) {
|
|
60
|
+
return this.getCell().then((cell) => cell.fetch(req));
|
|
61
|
+
}
|
|
62
|
+
/** A rejected alarm is retried by the platform with backoff, so failures just propagate. */
|
|
63
|
+
alarm() {
|
|
64
|
+
return this.getCell()
|
|
65
|
+
.then((cell) => {
|
|
66
|
+
const delivery = this.store?.projectorDelivery;
|
|
67
|
+
const delivered = delivery
|
|
68
|
+
? Effect.runPromise(delivery.deliver(this.ctx.id.name ?? ''))
|
|
69
|
+
: Promise.resolve();
|
|
70
|
+
return delivered.then(() => cell.wake());
|
|
71
|
+
})
|
|
72
|
+
.then(() => undefined);
|
|
42
73
|
}
|
|
43
74
|
}
|