@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/react/store.js
CHANGED
|
@@ -1,10 +1,16 @@
|
|
|
1
|
-
import { Predicate } from 'effect';
|
|
2
|
-
import { decodeLoomsEvent, } from '@swirls/looms/core';
|
|
1
|
+
import { Data, Effect, Fiber, Option, Predicate, Schema } from 'effect';
|
|
2
|
+
import { decodeLoomsEvent, stringifyJson, } from '@swirls/looms/core';
|
|
3
3
|
import { EventIndex, ProjectionCache } from './derived.js';
|
|
4
4
|
import { materializeEvents } from './materialize.js';
|
|
5
5
|
import { createNotifyScheduler } from './notify.js';
|
|
6
|
-
import {
|
|
6
|
+
import { consumeSseStreamEffect, eventsFromSseData } from './sse.js';
|
|
7
7
|
import { emptyTables } from './tables.js';
|
|
8
|
+
class LoomsStoreError extends Data.TaggedError('LoomsStoreError') {
|
|
9
|
+
constructor(message, cause) {
|
|
10
|
+
super({ cause, message });
|
|
11
|
+
this.name = 'LoomsStoreError';
|
|
12
|
+
}
|
|
13
|
+
}
|
|
8
14
|
function eventSourceCtor() {
|
|
9
15
|
if (typeof EventSource === 'undefined') {
|
|
10
16
|
return undefined;
|
|
@@ -36,13 +42,12 @@ export function createLoomsStore(options) {
|
|
|
36
42
|
let liveStarted = false;
|
|
37
43
|
let liveAbort;
|
|
38
44
|
let eventSource;
|
|
39
|
-
let
|
|
45
|
+
let reconnectFiber;
|
|
40
46
|
let syncing = null;
|
|
41
47
|
const publishedEvents = () => {
|
|
42
48
|
if (!eventsSnapshotDirty) {
|
|
43
49
|
return eventsSnapshot;
|
|
44
50
|
}
|
|
45
|
-
// SAFETY: events array only contains TEvent values pushed from applyBatch.
|
|
46
51
|
eventsSnapshot = Object.freeze(events.slice());
|
|
47
52
|
eventsSnapshotDirty = false;
|
|
48
53
|
return eventsSnapshot;
|
|
@@ -68,7 +73,6 @@ export function createLoomsStore(options) {
|
|
|
68
73
|
continue;
|
|
69
74
|
}
|
|
70
75
|
seenIds.add(event.id);
|
|
71
|
-
// SAFETY: wire events match the app-registered TEvent catalog at the type boundary.
|
|
72
76
|
fresh.push(event);
|
|
73
77
|
fromSeq = Math.max(fromSeq, event.seq + 1);
|
|
74
78
|
}
|
|
@@ -88,85 +92,67 @@ export function createLoomsStore(options) {
|
|
|
88
92
|
applyBatch(eventsFromSseData(data, options.storeId));
|
|
89
93
|
};
|
|
90
94
|
const liveUrl = () => {
|
|
91
|
-
|
|
92
|
-
return `${endpoint}/api/events?runId=${encodeURIComponent(options.storeId)}&live=true&cursor=${cursor}`;
|
|
95
|
+
return `${endpoint}/runs/${encodeURIComponent(options.storeId)}/events?live=true&fromSeq=${fromSeq}`;
|
|
93
96
|
};
|
|
94
|
-
const pull =
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
const text = await res.text();
|
|
104
|
-
if (text.includes('out of range') || text.includes('Range not satisfiable')) {
|
|
105
|
-
return;
|
|
106
|
-
}
|
|
107
|
-
throw new Error(`event pull failed: ${res.status}`);
|
|
108
|
-
}
|
|
109
|
-
const body = await res.json();
|
|
110
|
-
if (!Predicate.isReadonlyObject(body)) {
|
|
111
|
-
return;
|
|
97
|
+
const pull = Effect.gen(function* () {
|
|
98
|
+
const url = `${endpoint}/runs/${encodeURIComponent(options.storeId)}/events?fromSeq=${fromSeq}`;
|
|
99
|
+
const res = yield* Effect.tryPromise({
|
|
100
|
+
try: (signal) => fetchFn(url, { signal }),
|
|
101
|
+
catch: (cause) => new LoomsStoreError('Event pull failed', cause),
|
|
102
|
+
});
|
|
103
|
+
if (!res.ok) {
|
|
104
|
+
if (res.status === 404 || res.status === 416) {
|
|
105
|
+
return undefined;
|
|
112
106
|
}
|
|
113
|
-
const
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
raw, options.storeId)));
|
|
117
|
-
const head = 'head' in body && Predicate.isNumber(body.head) ? body.head : undefined;
|
|
118
|
-
if (head !== undefined) {
|
|
119
|
-
fromSeq = Math.max(fromSeq, head + 1);
|
|
107
|
+
const text = yield* Effect.promise(() => res.text());
|
|
108
|
+
if (text.includes('out of range') || text.includes('Range not satisfiable')) {
|
|
109
|
+
return undefined;
|
|
120
110
|
}
|
|
111
|
+
return yield* new LoomsStoreError(`Event pull failed: ${res.status}`);
|
|
121
112
|
}
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
// When offline or host is restarting, suppress connection refused/fetch errors during sync
|
|
127
|
-
if (err instanceof Error &&
|
|
128
|
-
(err.message.includes('fetch failed') ||
|
|
129
|
-
err.message.includes('ConnectionRefused') ||
|
|
130
|
-
err.message.includes('Unable to connect'))) {
|
|
131
|
-
return;
|
|
132
|
-
}
|
|
133
|
-
throw err;
|
|
113
|
+
const rawBody = yield* Effect.promise(() => res.json());
|
|
114
|
+
const decodedBody = Schema.decodeUnknownOption(Schema.Json)(rawBody);
|
|
115
|
+
if (Option.isNone(decodedBody) || !Predicate.isReadonlyObject(decodedBody.value)) {
|
|
116
|
+
return undefined;
|
|
134
117
|
}
|
|
135
|
-
|
|
136
|
-
|
|
118
|
+
const body = decodedBody.value;
|
|
119
|
+
const pulledEvents = 'events' in body && Array.isArray(body.events) ? body.events : [];
|
|
120
|
+
applyBatch(pulledEvents.map((raw) => decodeLoomsEvent(raw, options.storeId)));
|
|
121
|
+
return undefined;
|
|
122
|
+
}).pipe(Effect.catchIf((error) => disposed ||
|
|
123
|
+
error.message.includes('fetch failed') ||
|
|
124
|
+
error.message.includes('ConnectionRefused') ||
|
|
125
|
+
error.message.includes('Unable to connect'), () => Effect.void));
|
|
126
|
+
const sync = () => {
|
|
137
127
|
if (disposed) {
|
|
138
|
-
return;
|
|
128
|
+
return Promise.resolve();
|
|
139
129
|
}
|
|
140
130
|
if (syncing) {
|
|
141
131
|
return syncing;
|
|
142
132
|
}
|
|
143
|
-
syncing = (
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
}
|
|
147
|
-
finally {
|
|
148
|
-
syncing = null;
|
|
149
|
-
}
|
|
150
|
-
})();
|
|
133
|
+
syncing = Effect.runPromise(pull).finally(() => {
|
|
134
|
+
syncing = null;
|
|
135
|
+
});
|
|
151
136
|
return syncing;
|
|
152
137
|
};
|
|
153
138
|
const scheduleReconnect = (Ctor) => {
|
|
154
|
-
if (disposed || !liveStarted ||
|
|
139
|
+
if (disposed || !liveStarted || reconnectFiber !== undefined) {
|
|
155
140
|
return;
|
|
156
141
|
}
|
|
157
|
-
|
|
158
|
-
|
|
142
|
+
reconnectFiber = Effect.runFork(Effect.gen(function* () {
|
|
143
|
+
yield* Effect.sleep(reconnectDelayMs);
|
|
144
|
+
reconnectFiber = undefined;
|
|
159
145
|
if (disposed || !liveStarted) {
|
|
160
146
|
return;
|
|
161
147
|
}
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
}
|
|
169
|
-
}
|
|
148
|
+
yield* Effect.tryPromise({
|
|
149
|
+
try: sync,
|
|
150
|
+
catch: (cause) => new LoomsStoreError('Event resync failed', cause),
|
|
151
|
+
}).pipe(Effect.ignore);
|
|
152
|
+
if (!disposed && liveStarted) {
|
|
153
|
+
startEventSource(Ctor);
|
|
154
|
+
}
|
|
155
|
+
}));
|
|
170
156
|
};
|
|
171
157
|
const startEventSource = (Ctor) => {
|
|
172
158
|
if (disposed || !liveStarted) {
|
|
@@ -199,45 +185,46 @@ export function createLoomsStore(options) {
|
|
|
199
185
|
}
|
|
200
186
|
});
|
|
201
187
|
};
|
|
202
|
-
const startFetchStream =
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
const controller = new AbortController();
|
|
208
|
-
liveAbort = controller;
|
|
209
|
-
try {
|
|
210
|
-
const headers = new Headers({ accept: 'text/event-stream' });
|
|
211
|
-
const cursor = Math.max(0, fromSeq - 1);
|
|
212
|
-
if (cursor > 0) {
|
|
213
|
-
headers.set('last-event-id', String(cursor));
|
|
214
|
-
}
|
|
215
|
-
const res = await fetchFn(liveUrl(), { headers, signal: controller.signal });
|
|
216
|
-
if (!res.ok) {
|
|
217
|
-
throw new Error(`event stream failed: ${res.status}`);
|
|
218
|
-
}
|
|
219
|
-
if (!res.body) {
|
|
220
|
-
throw new Error('event stream missing body');
|
|
221
|
-
}
|
|
222
|
-
live = true;
|
|
223
|
-
setStatus('live');
|
|
224
|
-
await consumeSseStream(res.body, (frame) => {
|
|
225
|
-
applyEncoded(frame.data);
|
|
226
|
-
}, controller.signal);
|
|
227
|
-
}
|
|
228
|
-
catch {
|
|
229
|
-
live = false;
|
|
188
|
+
const startFetchStream = () => {
|
|
189
|
+
const controller = new AbortController();
|
|
190
|
+
liveAbort = controller;
|
|
191
|
+
const run = Effect.gen(function* () {
|
|
192
|
+
while (true) {
|
|
230
193
|
if (disposed) {
|
|
231
194
|
return;
|
|
232
195
|
}
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
196
|
+
yield* Effect.gen(function* () {
|
|
197
|
+
const headers = new Headers({ accept: 'text/event-stream' });
|
|
198
|
+
const cursor = Math.max(0, fromSeq - 1);
|
|
199
|
+
if (cursor > 0) {
|
|
200
|
+
headers.set('last-event-id', String(cursor));
|
|
201
|
+
}
|
|
202
|
+
const res = yield* Effect.tryPromise({
|
|
203
|
+
try: (signal) => fetchFn(liveUrl(), { headers, signal }),
|
|
204
|
+
catch: (cause) => new LoomsStoreError('Event stream failed', cause),
|
|
205
|
+
});
|
|
206
|
+
const body = res.body;
|
|
207
|
+
if (!res.ok || !body) {
|
|
208
|
+
return yield* new LoomsStoreError(res.ok ? 'Event stream missing body' : `Event stream failed: ${res.status}`);
|
|
209
|
+
}
|
|
210
|
+
live = true;
|
|
211
|
+
setStatus('live');
|
|
212
|
+
return yield* consumeSseStreamEffect(body, (frame) => {
|
|
213
|
+
applyEncoded(frame.data);
|
|
214
|
+
});
|
|
215
|
+
}).pipe(Effect.catch(() => Effect.sync(() => {
|
|
216
|
+
live = false;
|
|
217
|
+
if (!disposed) {
|
|
218
|
+
setStatus('reconnecting');
|
|
219
|
+
}
|
|
220
|
+
})));
|
|
221
|
+
live = false;
|
|
222
|
+
if (!disposed) {
|
|
223
|
+
yield* Effect.sleep(reconnectDelayMs);
|
|
224
|
+
}
|
|
238
225
|
}
|
|
239
|
-
|
|
240
|
-
}
|
|
226
|
+
});
|
|
227
|
+
void Effect.runPromise(run, { signal: controller.signal }).catch(() => undefined);
|
|
241
228
|
};
|
|
242
229
|
const startLive = () => {
|
|
243
230
|
if (!options.storeId || (liveStarted && !disposed)) {
|
|
@@ -250,15 +237,15 @@ export function createLoomsStore(options) {
|
|
|
250
237
|
startEventSource(ctor);
|
|
251
238
|
}
|
|
252
239
|
else {
|
|
253
|
-
|
|
240
|
+
startFetchStream();
|
|
254
241
|
}
|
|
255
242
|
};
|
|
256
243
|
const stopLive = () => {
|
|
257
244
|
liveStarted = false;
|
|
258
245
|
live = false;
|
|
259
|
-
if (
|
|
260
|
-
|
|
261
|
-
|
|
246
|
+
if (reconnectFiber !== undefined) {
|
|
247
|
+
Effect.runFork(Fiber.interrupt(reconnectFiber));
|
|
248
|
+
reconnectFiber = undefined;
|
|
262
249
|
}
|
|
263
250
|
eventSource?.close();
|
|
264
251
|
eventSource = undefined;
|
|
@@ -307,26 +294,29 @@ export function createLoomsStore(options) {
|
|
|
307
294
|
project: (definition) => projectionCache.project(definition, publishedEvents()),
|
|
308
295
|
fold: (definition) => projectionCache.fold(definition, publishedEvents()),
|
|
309
296
|
flush: notify.flush,
|
|
310
|
-
sync:
|
|
311
|
-
const res = await sync();
|
|
297
|
+
sync: () => sync().then(() => {
|
|
312
298
|
notify.flush();
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
299
|
+
}),
|
|
300
|
+
commit: (event) => Effect.runPromise(Effect.gen(function* () {
|
|
301
|
+
const res = yield* Effect.tryPromise({
|
|
302
|
+
try: (signal) => fetchFn(`${endpoint}/runs/${encodeURIComponent(options.storeId)}/events`, {
|
|
303
|
+
method: 'POST',
|
|
304
|
+
headers: { 'content-type': 'application/json' },
|
|
305
|
+
body: stringifyJson(Array.isArray(event) ? { events: event } : event),
|
|
306
|
+
signal,
|
|
307
|
+
}),
|
|
308
|
+
catch: (cause) => new LoomsStoreError('Run signal failed', cause),
|
|
320
309
|
});
|
|
321
310
|
if (!res.ok) {
|
|
322
|
-
const text =
|
|
323
|
-
|
|
311
|
+
const text = yield* Effect.promise(() => res.text());
|
|
312
|
+
return yield* new LoomsStoreError(`Run signal failed: ${res.status} ${text}`);
|
|
324
313
|
}
|
|
325
314
|
if (!live) {
|
|
326
|
-
|
|
315
|
+
yield* Effect.promise(sync);
|
|
327
316
|
}
|
|
328
317
|
notify.flush();
|
|
329
|
-
|
|
318
|
+
return undefined;
|
|
319
|
+
})),
|
|
330
320
|
dispose: () => {
|
|
331
321
|
disposed = true;
|
|
332
322
|
notify.cancel();
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { type ComposedRegistry } from '@swirls/looms/core';
|
|
2
|
+
import type { RunIngress } from './ingress';
|
|
3
|
+
import { type RuntimeObserver } from './observer';
|
|
4
|
+
import type { RuntimeRepository } from './repository';
|
|
5
|
+
import type { RunExecutionContexts } from './run-execution-context';
|
|
6
|
+
import type { EffectWorker, LoomsRuntime } from './types';
|
|
7
|
+
export interface CancellationOptions {
|
|
8
|
+
readonly registry: ComposedRegistry;
|
|
9
|
+
readonly repository: RuntimeRepository;
|
|
10
|
+
readonly executionContexts: RunExecutionContexts;
|
|
11
|
+
readonly worker?: EffectWorker;
|
|
12
|
+
readonly observer?: RuntimeObserver;
|
|
13
|
+
readonly wake: LoomsRuntime['wake'];
|
|
14
|
+
readonly withRunIngress: RunIngress;
|
|
15
|
+
}
|
|
16
|
+
export declare function createCancellation(options: CancellationOptions): LoomsRuntime['cancel'];
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { Clock, Effect } from 'effect';
|
|
2
|
+
import { createEvent, EventStoreTag, foldRun, } from '@swirls/looms/core';
|
|
3
|
+
import { RuntimeExecutionError } from './errors.js';
|
|
4
|
+
import { waitSatisfiedEvents, workerTask } from './helpers.js';
|
|
5
|
+
import { notifyObserver } from './observer.js';
|
|
6
|
+
import { isWorkerEffect } from './worker-effect-dispatch.js';
|
|
7
|
+
function descendantThreadIds(state, targetThreadId) {
|
|
8
|
+
return Object.values(state.threads)
|
|
9
|
+
.filter((record) => {
|
|
10
|
+
let parentThreadId = record.parentThreadId;
|
|
11
|
+
while (parentThreadId) {
|
|
12
|
+
if (parentThreadId === targetThreadId) {
|
|
13
|
+
return true;
|
|
14
|
+
}
|
|
15
|
+
parentThreadId = state.threads[parentThreadId]?.parentThreadId ?? null;
|
|
16
|
+
}
|
|
17
|
+
return false;
|
|
18
|
+
})
|
|
19
|
+
.map((record) => record.threadId);
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Cancel a thread and its descendants. Worker attempts in flight are asked to stop and must
|
|
23
|
+
* acknowledge; attempts waiting to retry were never handed out and are cancelled outright.
|
|
24
|
+
*/
|
|
25
|
+
function planCancellation(input) {
|
|
26
|
+
const { runId, state, registry, now } = input;
|
|
27
|
+
const targetThreadId = input.threadId ?? state.rootThreadId ?? '';
|
|
28
|
+
const idempotencyKey = `cancel:${targetThreadId}`;
|
|
29
|
+
if (state.processedIdempotencyKeys.includes(idempotencyKey)) {
|
|
30
|
+
return { events: [], threadIds: new Set(), workerCancellations: [] };
|
|
31
|
+
}
|
|
32
|
+
const descendants = descendantThreadIds(state, targetThreadId);
|
|
33
|
+
const threadIds = new Set([...descendants, targetThreadId]);
|
|
34
|
+
const workerCancellations = [];
|
|
35
|
+
const effectEvents = [];
|
|
36
|
+
for (const item of state.outstandingEffects) {
|
|
37
|
+
const execution = state.effectExecutions[item.effectId];
|
|
38
|
+
if (!execution ||
|
|
39
|
+
!threadIds.has(item.threadId) ||
|
|
40
|
+
!isWorkerEffect({ runId, item, execution, registry, worker: input.worker })) {
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
if (execution.status === 'retry_wait') {
|
|
44
|
+
effectEvents.push(createEvent(runId, {
|
|
45
|
+
type: 'runtime.effect.cancelled',
|
|
46
|
+
payload: { effectId: item.effectId, attempt: execution.attempt },
|
|
47
|
+
threadId: item.threadId,
|
|
48
|
+
effectId: item.effectId,
|
|
49
|
+
origin: { type: 'system' },
|
|
50
|
+
idempotencyKey,
|
|
51
|
+
}));
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
const timeoutMs = registry.effects.get(item.effect.type)?.retry?.cancellationTimeoutMs;
|
|
55
|
+
workerCancellations.push({ item, execution });
|
|
56
|
+
effectEvents.push(createEvent(runId, {
|
|
57
|
+
type: 'runtime.effect.cancel.requested',
|
|
58
|
+
payload: {
|
|
59
|
+
effectId: item.effectId,
|
|
60
|
+
attempt: execution.attempt,
|
|
61
|
+
deadlineAt: timeoutMs ? now + timeoutMs : null,
|
|
62
|
+
},
|
|
63
|
+
threadId: item.threadId,
|
|
64
|
+
origin: { type: 'system' },
|
|
65
|
+
idempotencyKey,
|
|
66
|
+
}));
|
|
67
|
+
}
|
|
68
|
+
const threadEvents = [
|
|
69
|
+
...descendants.map((_, index) => descendants[descendants.length - 1 - index]),
|
|
70
|
+
targetThreadId,
|
|
71
|
+
]
|
|
72
|
+
.filter((threadId) => threadId.length > 0)
|
|
73
|
+
.map((threadId) => createEvent(runId, {
|
|
74
|
+
type: 'runtime.thread.cancelled',
|
|
75
|
+
payload: { threadId, reason: 'cancelled' },
|
|
76
|
+
threadId,
|
|
77
|
+
origin: { type: 'external' },
|
|
78
|
+
idempotencyKey,
|
|
79
|
+
}));
|
|
80
|
+
const batch = [...effectEvents, ...threadEvents];
|
|
81
|
+
const folded = foldRun(batch, registry, { runId, initial: state });
|
|
82
|
+
const satisfied = waitSatisfiedEvents(folded, batch).map((event) => createEvent(runId, event));
|
|
83
|
+
return { events: [...batch, ...satisfied], threadIds, workerCancellations };
|
|
84
|
+
}
|
|
85
|
+
export function createCancellation(options) {
|
|
86
|
+
const { registry, repository, executionContexts, worker } = options;
|
|
87
|
+
return (runId, threadId) => Effect.gen(function* () {
|
|
88
|
+
const committed = yield* options.withRunIngress(runId, Effect.gen(function* () {
|
|
89
|
+
const store = yield* EventStoreTag;
|
|
90
|
+
const now = yield* Clock.currentTimeMillis;
|
|
91
|
+
return yield* repository.commit(store, runId, (current) => {
|
|
92
|
+
const plan = planCancellation({
|
|
93
|
+
runId,
|
|
94
|
+
state: current.state,
|
|
95
|
+
threadId,
|
|
96
|
+
registry,
|
|
97
|
+
worker,
|
|
98
|
+
now,
|
|
99
|
+
});
|
|
100
|
+
return Effect.succeed({
|
|
101
|
+
events: plan.events,
|
|
102
|
+
value: { plan, now },
|
|
103
|
+
idempotencyKey: `signal:cancel:${threadId ?? current.state.rootThreadId ?? ''}`,
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
}));
|
|
107
|
+
const { plan, now } = committed.value;
|
|
108
|
+
if (committed.append) {
|
|
109
|
+
executionContexts.abort(runId, plan.threadIds, 'Effect cancelled');
|
|
110
|
+
for (const { item, execution } of plan.workerCancellations) {
|
|
111
|
+
notifyObserver(options.observer, {
|
|
112
|
+
type: 'effect.cancellation',
|
|
113
|
+
runId,
|
|
114
|
+
at: now,
|
|
115
|
+
effectId: item.effectId,
|
|
116
|
+
attempt: execution.attempt,
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
const cancelledState = yield* options.wake(runId);
|
|
121
|
+
if (worker?.cancel && committed.append) {
|
|
122
|
+
yield* Effect.forEach(plan.workerCancellations, ({ item, execution }) => Effect.tryPromise({
|
|
123
|
+
try: () => Promise.resolve(worker.cancel?.(workerTask(runId, item, execution.attempt))),
|
|
124
|
+
catch: (cause) => new RuntimeExecutionError(cause instanceof Error ? cause.message : String(cause)),
|
|
125
|
+
}).pipe(Effect.ignore), { concurrency: 'unbounded', discard: true });
|
|
126
|
+
}
|
|
127
|
+
return cancelledState;
|
|
128
|
+
});
|
|
129
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { type ComposedRegistry, type EventEnvelope, type RunState } from '@swirls/looms/core';
|
|
2
|
+
export interface TimerLateNotice {
|
|
3
|
+
readonly timerId: string;
|
|
4
|
+
readonly latenessMs: number;
|
|
5
|
+
}
|
|
6
|
+
export interface EffectTimeoutNotice {
|
|
7
|
+
readonly effectId: string;
|
|
8
|
+
readonly attempt: number;
|
|
9
|
+
readonly timeout: 'schedule-to-start' | 'heartbeat' | 'start-to-close';
|
|
10
|
+
}
|
|
11
|
+
export interface DeadlineTransitionPlan {
|
|
12
|
+
readonly events: ReadonlyArray<EventEnvelope>;
|
|
13
|
+
readonly timerNotices: ReadonlyArray<TimerLateNotice>;
|
|
14
|
+
readonly timeoutNotices: ReadonlyArray<EffectTimeoutNotice>;
|
|
15
|
+
}
|
|
16
|
+
export declare function planDeadlineTransitions(input: {
|
|
17
|
+
readonly runId: string;
|
|
18
|
+
readonly state: RunState;
|
|
19
|
+
readonly registry: ComposedRegistry;
|
|
20
|
+
readonly now: number;
|
|
21
|
+
}): DeadlineTransitionPlan;
|
|
22
|
+
export declare function planTerminalCancellationTransitions(input: {
|
|
23
|
+
readonly runId: string;
|
|
24
|
+
readonly state: RunState;
|
|
25
|
+
readonly now: number;
|
|
26
|
+
}): ReadonlyArray<EventEnvelope>;
|
|
27
|
+
/**
|
|
28
|
+
* The earliest deadline after `after` that needs a wake. A terminal run only waits on
|
|
29
|
+
* unacknowledged worker cancellations; its leftover timers can never fire anything.
|
|
30
|
+
*/
|
|
31
|
+
export declare function nextRuntimeDeadline(state: RunState, after?: number): number | undefined;
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { createEvent, foldRun, isRunTerminal, isWaitOnTimer, } from '@swirls/looms/core';
|
|
2
|
+
import { waitSatisfiedEvents } from './helpers.js';
|
|
3
|
+
import { planAttemptFailure } from './retry-policy.js';
|
|
4
|
+
const EMPTY_PLAN = {
|
|
5
|
+
events: [],
|
|
6
|
+
timerNotices: [],
|
|
7
|
+
timeoutNotices: [],
|
|
8
|
+
};
|
|
9
|
+
export function planDeadlineTransitions(input) {
|
|
10
|
+
const timerPlan = planDueTimers(input);
|
|
11
|
+
if (timerPlan.events.length > 0) {
|
|
12
|
+
return timerPlan;
|
|
13
|
+
}
|
|
14
|
+
return planExpiredExecutions(input);
|
|
15
|
+
}
|
|
16
|
+
function planDueTimers(input) {
|
|
17
|
+
const due = Object.values(input.state.waits).filter((record) => isWaitOnTimer(record.on) && record.on.timerAt <= input.now + 5);
|
|
18
|
+
if (due.length === 0) {
|
|
19
|
+
return EMPTY_PLAN;
|
|
20
|
+
}
|
|
21
|
+
const fired = due.flatMap((record) => isWaitOnTimer(record.on)
|
|
22
|
+
? [
|
|
23
|
+
createEvent(input.runId, {
|
|
24
|
+
type: 'runtime.timer.fired',
|
|
25
|
+
payload: { timerId: record.waitId, waitId: record.waitId },
|
|
26
|
+
threadId: record.threadId,
|
|
27
|
+
origin: { type: 'system' },
|
|
28
|
+
}),
|
|
29
|
+
]
|
|
30
|
+
: []);
|
|
31
|
+
const stateAfterFired = foldRun(fired, input.registry, {
|
|
32
|
+
runId: input.runId,
|
|
33
|
+
initial: input.state,
|
|
34
|
+
});
|
|
35
|
+
const satisfied = waitSatisfiedEvents(stateAfterFired, fired).map((event) => createEvent(input.runId, event));
|
|
36
|
+
return {
|
|
37
|
+
events: [...fired, ...satisfied],
|
|
38
|
+
timerNotices: due.flatMap((record) => isWaitOnTimer(record.on)
|
|
39
|
+
? [{ timerId: record.waitId, latenessMs: Math.max(0, input.now - record.on.timerAt) }]
|
|
40
|
+
: []),
|
|
41
|
+
timeoutNotices: [],
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
function planExpiredExecutions(input) {
|
|
45
|
+
const expired = Object.values(input.state.effectExecutions).filter((execution) => (execution.status === 'dispatched' ||
|
|
46
|
+
execution.status === 'started' ||
|
|
47
|
+
execution.status === 'heartbeat' ||
|
|
48
|
+
execution.status === 'cancel_requested') &&
|
|
49
|
+
execution.deadlineAt !== null &&
|
|
50
|
+
execution.deadlineAt <= input.now);
|
|
51
|
+
if (expired.length === 0) {
|
|
52
|
+
return EMPTY_PLAN;
|
|
53
|
+
}
|
|
54
|
+
const events = [];
|
|
55
|
+
const timeoutNotices = [];
|
|
56
|
+
for (const execution of expired) {
|
|
57
|
+
const item = input.state.outstandingEffects.find((candidate) => candidate.effectId === execution.effectId);
|
|
58
|
+
if (!item) {
|
|
59
|
+
if (execution.status === 'cancel_requested') {
|
|
60
|
+
events.push(cancellationAmbiguous(input.runId, execution.effectId, execution.attempt));
|
|
61
|
+
}
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
const retry = input.registry.effects.get(item.effect.type)?.retry;
|
|
65
|
+
const timeout = timeoutKind(execution, retry, input.now);
|
|
66
|
+
timeoutNotices.push({
|
|
67
|
+
effectId: item.effectId,
|
|
68
|
+
attempt: execution.attempt,
|
|
69
|
+
timeout,
|
|
70
|
+
});
|
|
71
|
+
events.push(createEvent(input.runId, {
|
|
72
|
+
type: 'runtime.effect.timed_out',
|
|
73
|
+
payload: { effectId: item.effectId, attempt: execution.attempt, timeout },
|
|
74
|
+
threadId: item.threadId,
|
|
75
|
+
causationId: item.causingEventId,
|
|
76
|
+
origin: { type: 'system' },
|
|
77
|
+
}));
|
|
78
|
+
events.push(planAttemptFailure({
|
|
79
|
+
runId: input.runId,
|
|
80
|
+
item,
|
|
81
|
+
attempt: execution.attempt,
|
|
82
|
+
error: `${timeout} timeout`,
|
|
83
|
+
now: input.now,
|
|
84
|
+
retry,
|
|
85
|
+
origin: { type: 'system' },
|
|
86
|
+
}).event);
|
|
87
|
+
}
|
|
88
|
+
return { events, timerNotices: [], timeoutNotices };
|
|
89
|
+
}
|
|
90
|
+
function timeoutKind(execution, retry, now) {
|
|
91
|
+
if (execution.status === 'dispatched') {
|
|
92
|
+
return 'schedule-to-start';
|
|
93
|
+
}
|
|
94
|
+
const startToCloseAt = execution.startedAt !== null && retry?.startToCloseTimeoutMs
|
|
95
|
+
? execution.startedAt + retry.startToCloseTimeoutMs
|
|
96
|
+
: null;
|
|
97
|
+
return execution.lastHeartbeatAt !== null && (startToCloseAt === null || startToCloseAt > now)
|
|
98
|
+
? 'heartbeat'
|
|
99
|
+
: 'start-to-close';
|
|
100
|
+
}
|
|
101
|
+
export function planTerminalCancellationTransitions(input) {
|
|
102
|
+
if (!isRunTerminal(input.state)) {
|
|
103
|
+
return [];
|
|
104
|
+
}
|
|
105
|
+
return Object.values(input.state.effectExecutions)
|
|
106
|
+
.filter((execution) => execution.status === 'cancel_requested' &&
|
|
107
|
+
execution.deadlineAt !== null &&
|
|
108
|
+
execution.deadlineAt <= input.now)
|
|
109
|
+
.map((execution) => cancellationAmbiguous(input.runId, execution.effectId, execution.attempt));
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* The earliest deadline after `after` that needs a wake. A terminal run only waits on
|
|
113
|
+
* unacknowledged worker cancellations; its leftover timers can never fire anything.
|
|
114
|
+
*/
|
|
115
|
+
export function nextRuntimeDeadline(state, after = Number.NEGATIVE_INFINITY) {
|
|
116
|
+
const terminal = isRunTerminal(state);
|
|
117
|
+
const timers = terminal
|
|
118
|
+
? []
|
|
119
|
+
: Object.values(state.waits).flatMap((record) => isWaitOnTimer(record.on) && record.on.timerAt > after ? [record.on.timerAt] : []);
|
|
120
|
+
const executions = Object.values(state.effectExecutions).flatMap((execution) => (!terminal || execution.status === 'cancel_requested') &&
|
|
121
|
+
execution.deadlineAt !== null &&
|
|
122
|
+
execution.deadlineAt > after
|
|
123
|
+
? [execution.deadlineAt]
|
|
124
|
+
: []);
|
|
125
|
+
const deadlines = [...timers, ...executions];
|
|
126
|
+
return deadlines.length > 0 ? Math.min(...deadlines) : undefined;
|
|
127
|
+
}
|
|
128
|
+
function cancellationAmbiguous(runId, effectId, attempt) {
|
|
129
|
+
return createEvent(runId, {
|
|
130
|
+
type: 'runtime.effect.ambiguous',
|
|
131
|
+
payload: {
|
|
132
|
+
effectId,
|
|
133
|
+
attempt,
|
|
134
|
+
error: 'worker did not acknowledge cancellation',
|
|
135
|
+
},
|
|
136
|
+
threadId: null,
|
|
137
|
+
origin: { type: 'system' },
|
|
138
|
+
});
|
|
139
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { Context, ManagedRuntime } from 'effect';
|
|
2
|
+
import { Effect } from 'effect';
|
|
2
3
|
import { type ComposedRegistry, type EffectContext, type EventInput, type RegisteredDefinition, type RuntimeEffect } from '@swirls/looms/core';
|
|
3
|
-
export declare function dispatchEffect(registry: ComposedRegistry, services:
|
|
4
|
+
export declare function dispatchEffect(registry: ComposedRegistry, services: ManagedRuntime.ManagedRuntime<Context.Service.Any, never>, definitions: ReadonlyMap<string, RegisteredDefinition>, effect: RuntimeEffect, ctx: EffectContext): Effect.Effect<ReadonlyArray<EventInput>, Error>;
|