@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/runtime/looms.js
CHANGED
|
@@ -1,53 +1,72 @@
|
|
|
1
|
-
import { Effect, Predicate } from 'effect';
|
|
1
|
+
import { Data, Effect, Predicate } from 'effect';
|
|
2
2
|
import { EventStoreTag, makeMemoryEventStore, snapshotStoreOf, SnapshotStoreTag, } from '@swirls/looms/core';
|
|
3
|
-
import { createRuntime } from './runtime.js';
|
|
4
|
-
import { createFetchHandler,
|
|
3
|
+
import { createRuntime, } from './runtime.js';
|
|
4
|
+
import { createFetchHandler, handlesPath, mountDebugger, serveHttp, } from './server.js';
|
|
5
|
+
class LoomsInitializationError extends Data.TaggedError('LoomsInitializationError') {
|
|
6
|
+
constructor(cause) {
|
|
7
|
+
super({
|
|
8
|
+
cause,
|
|
9
|
+
message: cause instanceof Error ? cause.message : String(cause),
|
|
10
|
+
});
|
|
11
|
+
this.name = 'LoomsInitializationError';
|
|
12
|
+
}
|
|
13
|
+
}
|
|
5
14
|
export function createLooms(options = {}) {
|
|
6
15
|
const modules = options.modules ?? [];
|
|
16
|
+
const debuggerMount = options.debugger ? mountDebugger(options.debugger) : undefined;
|
|
7
17
|
let initPromise;
|
|
8
18
|
let runningServer;
|
|
9
19
|
const getInit = () => {
|
|
10
20
|
if (!initPromise) {
|
|
11
|
-
initPromise = (
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
21
|
+
initPromise = Effect.runPromise(Effect.gen(function* () {
|
|
22
|
+
const configuredStore = options.store;
|
|
23
|
+
const store = !configuredStore
|
|
24
|
+
? yield* makeMemoryEventStore
|
|
25
|
+
: Predicate.isFunction(configuredStore)
|
|
26
|
+
? yield* Effect.tryPromise({
|
|
27
|
+
try: configuredStore,
|
|
28
|
+
catch: (cause) => new LoomsInitializationError(cause),
|
|
29
|
+
})
|
|
30
|
+
: configuredStore instanceof Promise
|
|
31
|
+
? yield* Effect.tryPromise({
|
|
32
|
+
try: () => configuredStore,
|
|
33
|
+
catch: (cause) => new LoomsInitializationError(cause),
|
|
34
|
+
})
|
|
35
|
+
: configuredStore;
|
|
22
36
|
const runtime = createRuntime({
|
|
23
37
|
modules,
|
|
24
38
|
store,
|
|
25
39
|
maxWakeIterations: options.maxWakeIterations,
|
|
40
|
+
maxPendingRunOperations: options.maxPendingRunOperations,
|
|
26
41
|
snapshotEvery: options.snapshotEvery,
|
|
27
42
|
snapshotStore: options.snapshotStore ?? snapshotStoreOf(store),
|
|
28
43
|
trimAfterSnapshot: options.trimAfterSnapshot,
|
|
29
44
|
runCacheSize: options.runCacheSize,
|
|
30
45
|
scheduler: options.scheduler,
|
|
46
|
+
worker: options.worker,
|
|
47
|
+
observer: options.observer,
|
|
31
48
|
});
|
|
32
49
|
if (options.rescanTimers !== false) {
|
|
33
|
-
|
|
34
|
-
console.error('[rescan timers error]', err);
|
|
35
|
-
});
|
|
50
|
+
yield* runtime.rescanTimers.pipe(Effect.provideService(EventStoreTag, store), Effect.tapError(Effect.logError), Effect.ignore);
|
|
36
51
|
}
|
|
37
|
-
const fetchHandler = createFetchHandler({
|
|
52
|
+
const fetchHandler = createFetchHandler({
|
|
53
|
+
runtime,
|
|
54
|
+
store,
|
|
55
|
+
authorize: options.authorize,
|
|
56
|
+
debugger: debuggerMount,
|
|
57
|
+
});
|
|
38
58
|
return { runtime, store, snapshotStore: snapshotStoreOf(store), fetchHandler };
|
|
39
|
-
})
|
|
59
|
+
}));
|
|
40
60
|
}
|
|
41
61
|
return initPromise;
|
|
42
62
|
};
|
|
43
|
-
const runEffect =
|
|
44
|
-
const init = await getInit();
|
|
63
|
+
const runEffect = (fn) => getInit().then((init) => {
|
|
45
64
|
let provided = Effect.provideService(fn(init), EventStoreTag, init.store);
|
|
46
65
|
if (init.snapshotStore) {
|
|
47
66
|
provided = Effect.provideService(provided, SnapshotStoreTag, init.snapshotStore);
|
|
48
67
|
}
|
|
49
68
|
return Effect.runPromise(provided);
|
|
50
|
-
};
|
|
69
|
+
});
|
|
51
70
|
const looms = {
|
|
52
71
|
get runtime() {
|
|
53
72
|
return getInit().then((i) => i.runtime);
|
|
@@ -55,14 +74,12 @@ export function createLooms(options = {}) {
|
|
|
55
74
|
get store() {
|
|
56
75
|
return getInit().then((i) => i.store);
|
|
57
76
|
},
|
|
58
|
-
ready:
|
|
59
|
-
const init = await getInit();
|
|
60
|
-
return { store: init.store, runtime: init.runtime };
|
|
61
|
-
},
|
|
77
|
+
ready: () => getInit().then(({ store, runtime }) => ({ store, runtime })),
|
|
62
78
|
startRun: (args) => runEffect((i) => i.runtime.startRun(args)),
|
|
63
79
|
start: (definition, input, startOpts) => runEffect((i) => i.runtime.startRun({
|
|
64
80
|
kind: definition.kind,
|
|
65
81
|
definitionName: definition.name,
|
|
82
|
+
definitionVersion: definition.version,
|
|
66
83
|
// SAFETY: input is validated against the definition's schema inside startRun.
|
|
67
84
|
input: input,
|
|
68
85
|
runId: startOpts?.runId,
|
|
@@ -73,16 +90,20 @@ export function createLooms(options = {}) {
|
|
|
73
90
|
getEvents: (runId, opts) => runEffect((i) => i.runtime.getEvents(runId, opts)),
|
|
74
91
|
signal: (runId, events, signalOpts) => runEffect((i) => i.runtime.signal(runId, events, signalOpts)),
|
|
75
92
|
wake: (runId) => runEffect((i) => i.runtime.wake(runId)),
|
|
76
|
-
rescanTimers: () => runEffect((i) => i.runtime.rescanTimers
|
|
93
|
+
rescanTimers: () => runEffect((i) => i.runtime.rescanTimers),
|
|
94
|
+
recoverDeadlines: () => runEffect((i) => i.runtime.recoverDeadlines),
|
|
95
|
+
inspectRun: (runId) => runEffect((i) => i.runtime.inspectRun(runId)),
|
|
96
|
+
retryEffect: (runId, effectId) => runEffect((i) => i.runtime.retryEffect(runId, effectId)),
|
|
97
|
+
listRuns: () => runEffect((i) => i.runtime.listRuns),
|
|
77
98
|
project: (runId, definition) => runEffect((i) => i.runtime.project(runId, definition)),
|
|
78
99
|
replayTo: (runId, seq) => runEffect((i) => i.runtime.replayTo(runId, seq)),
|
|
79
100
|
cancel: (runId, threadId) => runEffect((i) => i.runtime.cancel(runId, threadId)),
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
101
|
+
workerCallback: (runId, input) => runEffect((i) => i.runtime.workerCallback(runId, input)),
|
|
102
|
+
fetch: (req) => {
|
|
103
|
+
if (!handlesPath(new URL(req.url).pathname, debuggerMount)) {
|
|
104
|
+
return Promise.resolve(null);
|
|
83
105
|
}
|
|
84
|
-
|
|
85
|
-
return fetchHandler(req);
|
|
106
|
+
return getInit().then(({ fetchHandler }) => fetchHandler(req));
|
|
86
107
|
},
|
|
87
108
|
serve: (serveOpts) => {
|
|
88
109
|
if (runningServer) {
|
|
@@ -91,15 +112,14 @@ export function createLooms(options = {}) {
|
|
|
91
112
|
runningServer = serveHttp((req) => looms.fetch(req), serveOpts);
|
|
92
113
|
return runningServer;
|
|
93
114
|
},
|
|
94
|
-
stop:
|
|
115
|
+
stop: () => {
|
|
95
116
|
if (runningServer) {
|
|
96
117
|
runningServer.stop();
|
|
97
118
|
runningServer = undefined;
|
|
98
119
|
}
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
}
|
|
120
|
+
return initPromise
|
|
121
|
+
? initPromise.then(({ runtime }) => Effect.runPromise(runtime.dispose))
|
|
122
|
+
: Promise.resolve();
|
|
103
123
|
},
|
|
104
124
|
};
|
|
105
125
|
if (options.serve) {
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
export type RuntimeObservation = {
|
|
2
|
+
readonly type: 'run.start';
|
|
3
|
+
readonly runId: string;
|
|
4
|
+
readonly at: number;
|
|
5
|
+
} | {
|
|
6
|
+
readonly type: 'run.wake';
|
|
7
|
+
readonly runId: string;
|
|
8
|
+
readonly at: number;
|
|
9
|
+
} | {
|
|
10
|
+
readonly type: 'run.park' | 'run.terminal';
|
|
11
|
+
readonly runId: string;
|
|
12
|
+
readonly at: number;
|
|
13
|
+
readonly status: string;
|
|
14
|
+
} | {
|
|
15
|
+
readonly type: 'event.append';
|
|
16
|
+
readonly runId: string;
|
|
17
|
+
readonly at: number;
|
|
18
|
+
readonly eventCount: number;
|
|
19
|
+
readonly latencyMs: number;
|
|
20
|
+
readonly outcome: 'ok' | 'conflict' | 'error';
|
|
21
|
+
} | {
|
|
22
|
+
readonly type: 'snapshot.load' | 'snapshot.save' | 'snapshot.trim';
|
|
23
|
+
readonly runId: string;
|
|
24
|
+
readonly at: number;
|
|
25
|
+
readonly latencyMs: number;
|
|
26
|
+
readonly outcome: 'ok' | 'miss' | 'error';
|
|
27
|
+
readonly cursor?: number;
|
|
28
|
+
} | {
|
|
29
|
+
readonly type: 'timer.late';
|
|
30
|
+
readonly runId: string;
|
|
31
|
+
readonly at: number;
|
|
32
|
+
readonly timerId: string;
|
|
33
|
+
readonly latenessMs: number;
|
|
34
|
+
} | {
|
|
35
|
+
readonly type: 'effect.attempt' | 'effect.retry' | 'effect.timeout' | 'effect.heartbeat' | 'effect.cancellation';
|
|
36
|
+
readonly runId: string;
|
|
37
|
+
readonly at: number;
|
|
38
|
+
readonly effectId: string;
|
|
39
|
+
readonly attempt: number;
|
|
40
|
+
readonly detail?: string;
|
|
41
|
+
};
|
|
42
|
+
export interface RuntimeObserver {
|
|
43
|
+
observe(event: RuntimeObservation): void | Promise<void>;
|
|
44
|
+
}
|
|
45
|
+
export declare function notifyObserver(observer: RuntimeObserver | undefined, event: RuntimeObservation): void;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { Effect } from 'effect';
|
|
2
|
+
import { EventStoreTrimmedError, type AppendableEvent, type AppendResult, type ComposedRegistry, type EventEnvelope, type EventStore, type EventStoreAppendError, type EventStoreError, type EventStoreTrimCoverage, type EventStoreTruncationError, type RunCursor, type SnapshotStore } from '@swirls/looms/core';
|
|
3
|
+
import { RuntimeExecutionError } from './errors';
|
|
4
|
+
import { type RunCursorCache } from './helpers';
|
|
5
|
+
import { type RuntimeObserver } from './observer';
|
|
6
|
+
import type { RunExecutionContexts } from './run-execution-context';
|
|
7
|
+
declare const LiveAppendError_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 & {
|
|
8
|
+
readonly _tag: "LiveAppendError";
|
|
9
|
+
} & Readonly<A>;
|
|
10
|
+
declare class LiveAppendError extends LiveAppendError_base<{
|
|
11
|
+
readonly message: string;
|
|
12
|
+
}> {
|
|
13
|
+
constructor();
|
|
14
|
+
}
|
|
15
|
+
export interface CommitPlan<A> {
|
|
16
|
+
readonly events: ReadonlyArray<EventEnvelope>;
|
|
17
|
+
readonly value: A;
|
|
18
|
+
readonly idempotencyKey?: string | undefined;
|
|
19
|
+
}
|
|
20
|
+
export interface CommitResult<A> {
|
|
21
|
+
readonly value: A;
|
|
22
|
+
/** The durable state the committed batch was planned against. */
|
|
23
|
+
readonly cursor: RunCursor;
|
|
24
|
+
/** `null` when the plan had nothing to append. */
|
|
25
|
+
readonly append: AppendResult | null;
|
|
26
|
+
}
|
|
27
|
+
export interface RuntimeRepositoryOptions {
|
|
28
|
+
readonly registry: ComposedRegistry;
|
|
29
|
+
readonly snapshotStore: SnapshotStore;
|
|
30
|
+
readonly trimAfterSnapshot?: {
|
|
31
|
+
readonly keepSnapshots: number;
|
|
32
|
+
readonly coverage?: (runId: string) => EventStoreTrimCoverage | Promise<EventStoreTrimCoverage>;
|
|
33
|
+
};
|
|
34
|
+
readonly keepSnapshots: number;
|
|
35
|
+
readonly runCache: RunCursorCache;
|
|
36
|
+
readonly executionContexts: RunExecutionContexts;
|
|
37
|
+
readonly observer?: RuntimeObserver;
|
|
38
|
+
}
|
|
39
|
+
export declare function createRuntimeRepository(options: RuntimeRepositoryOptions): {
|
|
40
|
+
appendObserved: (store: EventStore, runId: string, events: ReadonlyArray<AppendableEvent>, appendOptions?: Parameters<EventStore['append']>[2]) => Effect.Effect<AppendResult, EventStoreAppendError>;
|
|
41
|
+
commit: <A, E, R>(store: EventStore, runId: string, plan: (cursor: RunCursor) => Effect.Effect<CommitPlan<A>, E, R>) => Effect.Effect<CommitResult<A>, E | RuntimeExecutionError | EventStoreAppendError | EventStoreTruncationError | EventStoreTrimmedError | EventStoreError, R>;
|
|
42
|
+
liveAppends: {
|
|
43
|
+
push: (key: string, event: AppendableEvent) => Effect.Effect<void, LiveAppendError | EventStoreAppendError, never>;
|
|
44
|
+
drain: (key: string) => Effect.Effect<void>;
|
|
45
|
+
clear: (key?: string) => void;
|
|
46
|
+
size: () => number;
|
|
47
|
+
};
|
|
48
|
+
loadCursor: (store: EventStore, runId: string) => Effect.Effect<RunCursor, EventStoreTruncationError | EventStoreTrimmedError | EventStoreError>;
|
|
49
|
+
persistSnapshot: (store: EventStore, runId: string, cursor: RunCursor) => Effect.Effect<RunCursor>;
|
|
50
|
+
applyAppend: (store: EventStore, runId: string, cursor: RunCursor, batch: ReadonlyArray<AppendableEvent>, appendResult: AppendResult, commandRecords?: number) => Effect.Effect<RunCursor, EventStoreTruncationError | EventStoreTrimmedError | EventStoreError>;
|
|
51
|
+
deleteCachedCursor: (runId: string) => void;
|
|
52
|
+
setCachedCursor: (runId: string, cursor: RunCursor) => void;
|
|
53
|
+
};
|
|
54
|
+
export type RuntimeRepository = ReturnType<typeof createRuntimeRepository>;
|
|
55
|
+
export {};
|
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
import { Clock, Data, Effect, Option } from 'effect';
|
|
2
|
+
import { advanceCursor, assignSequences, createCoalescingAppender, emptyCursor, EventStoreTrimmedError, hashRunState, summarizeRun, trimEventStoreSafely, } from '@swirls/looms/core';
|
|
3
|
+
import { RuntimeExecutionError } from './errors.js';
|
|
4
|
+
import { stripSeq } from './helpers.js';
|
|
5
|
+
import { notifyObserver } from './observer.js';
|
|
6
|
+
class LiveAppendError extends Data.TaggedError('LiveAppendError') {
|
|
7
|
+
constructor() {
|
|
8
|
+
super({ message: 'Cannot append live events without a store' });
|
|
9
|
+
this.name = 'LiveAppendError';
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
function currentTimeMillis() {
|
|
13
|
+
return Math.floor(performance.timeOrigin + performance.now());
|
|
14
|
+
}
|
|
15
|
+
const MAX_COMMIT_CONFLICTS = 16;
|
|
16
|
+
function readRemaining(store, runId, events, expectedTail) {
|
|
17
|
+
return Effect.gen(function* () {
|
|
18
|
+
let collected = events;
|
|
19
|
+
while (collected.length > 0) {
|
|
20
|
+
const lastSeq = collected[collected.length - 1].seq;
|
|
21
|
+
const target = expectedTail ?? (yield* store.tail(runId));
|
|
22
|
+
if (lastSeq >= target) {
|
|
23
|
+
break;
|
|
24
|
+
}
|
|
25
|
+
const more = yield* store.read(runId, {
|
|
26
|
+
fromSeq: lastSeq + 1,
|
|
27
|
+
limit: expectedTail !== undefined ? Math.max(0, target - lastSeq) : undefined,
|
|
28
|
+
});
|
|
29
|
+
if (more.length === 0 || more[0].seq <= lastSeq) {
|
|
30
|
+
break;
|
|
31
|
+
}
|
|
32
|
+
collected = [...collected, ...more];
|
|
33
|
+
}
|
|
34
|
+
return collected;
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
export function createRuntimeRepository(options) {
|
|
38
|
+
const { registry, snapshotStore, trimAfterSnapshot, keepSnapshots, runCache, executionContexts } = options;
|
|
39
|
+
const headerFingerprints = new Map();
|
|
40
|
+
const observe = (event) => notifyObserver(options.observer, event);
|
|
41
|
+
const rememberHeader = (state) => {
|
|
42
|
+
const header = summarizeRun(state);
|
|
43
|
+
const fingerprint = `${header.status}\0${header.kind ?? ''}\0${header.definitionName ?? ''}`;
|
|
44
|
+
if (headerFingerprints.get(header.runId) === fingerprint) {
|
|
45
|
+
return Effect.void;
|
|
46
|
+
}
|
|
47
|
+
return snapshotStore.saveHeader(header).pipe(Effect.tap(() => Effect.sync(() => {
|
|
48
|
+
headerFingerprints.set(header.runId, fingerprint);
|
|
49
|
+
})), Effect.catch((error) => Effect.logWarning(`Run header persistence failed for "${header.runId}"`, error)));
|
|
50
|
+
};
|
|
51
|
+
const appendObserved = (store, runId, events, appendOptions) => Effect.suspend(() => {
|
|
52
|
+
const startedAt = currentTimeMillis();
|
|
53
|
+
return store.append(runId, events, appendOptions).pipe(Effect.tap(() => Effect.sync(() => observe({
|
|
54
|
+
type: 'event.append',
|
|
55
|
+
runId,
|
|
56
|
+
at: currentTimeMillis(),
|
|
57
|
+
eventCount: events.length,
|
|
58
|
+
latencyMs: currentTimeMillis() - startedAt,
|
|
59
|
+
outcome: 'ok',
|
|
60
|
+
}))), Effect.tapError((error) => Effect.sync(() => observe({
|
|
61
|
+
type: 'event.append',
|
|
62
|
+
runId,
|
|
63
|
+
at: currentTimeMillis(),
|
|
64
|
+
eventCount: events.length,
|
|
65
|
+
latencyMs: currentTimeMillis() - startedAt,
|
|
66
|
+
outcome: error._tag === 'EventStoreConflictError' ? 'conflict' : 'error',
|
|
67
|
+
}))));
|
|
68
|
+
});
|
|
69
|
+
const liveAppends = createCoalescingAppender((runId, batch) => {
|
|
70
|
+
const context = executionContexts.peek(runId);
|
|
71
|
+
if (!context?.liveStore) {
|
|
72
|
+
return Effect.fail(new LiveAppendError());
|
|
73
|
+
}
|
|
74
|
+
const store = context.liveStore;
|
|
75
|
+
return appendObserved(store, runId, batch).pipe(Effect.tap((result) => Effect.sync(() => {
|
|
76
|
+
context.liveCount += result.sequences.length;
|
|
77
|
+
})), Effect.asVoid);
|
|
78
|
+
});
|
|
79
|
+
const catchUp = (store, runId, cursor, expectedTail) => Effect.gen(function* () {
|
|
80
|
+
if (expectedTail !== undefined && cursor.seq === expectedTail) {
|
|
81
|
+
return cursor;
|
|
82
|
+
}
|
|
83
|
+
const fromSeq = cursor.seq + 1;
|
|
84
|
+
const limit = expectedTail !== undefined ? Math.max(0, expectedTail - cursor.seq) : undefined;
|
|
85
|
+
let delta = yield* store.read(runId, { fromSeq, limit });
|
|
86
|
+
if (delta.length === 0) {
|
|
87
|
+
return cursor;
|
|
88
|
+
}
|
|
89
|
+
const firstSeq = delta[0].seq;
|
|
90
|
+
if (firstSeq > fromSeq) {
|
|
91
|
+
if (store.bounds) {
|
|
92
|
+
const bounds = yield* store.bounds(runId);
|
|
93
|
+
if (bounds.head > fromSeq) {
|
|
94
|
+
return yield* new EventStoreTrimmedError(runId, bounds.head, fromSeq);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
return yield* new EventStoreTrimmedError(runId, firstSeq, fromSeq);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
delta = yield* readRemaining(store, runId, delta, expectedTail);
|
|
102
|
+
return advanceCursor(cursor, delta, registry);
|
|
103
|
+
});
|
|
104
|
+
const loadCursorCold = (store, runId) => Effect.gen(function* () {
|
|
105
|
+
let cursor = emptyCursor(runId);
|
|
106
|
+
if (snapshotStore) {
|
|
107
|
+
const loadStartedAt = currentTimeMillis();
|
|
108
|
+
const snap = yield* snapshotStore.loadLatest(runId).pipe(Effect.tapError((error) => Effect.sync(() => {
|
|
109
|
+
observe({
|
|
110
|
+
type: 'snapshot.load',
|
|
111
|
+
runId,
|
|
112
|
+
at: currentTimeMillis(),
|
|
113
|
+
latencyMs: currentTimeMillis() - loadStartedAt,
|
|
114
|
+
outcome: 'error',
|
|
115
|
+
});
|
|
116
|
+
}).pipe(Effect.andThen(Effect.logWarning(`Snapshot load failed for run "${runId}"; replaying the log`, error)))), Effect.orElseSucceed(() => Option.none()));
|
|
117
|
+
if (Option.isSome(snap)) {
|
|
118
|
+
observe({
|
|
119
|
+
type: 'snapshot.load',
|
|
120
|
+
runId,
|
|
121
|
+
at: currentTimeMillis(),
|
|
122
|
+
latencyMs: currentTimeMillis() - loadStartedAt,
|
|
123
|
+
outcome: 'ok',
|
|
124
|
+
cursor: snap.value.cursor,
|
|
125
|
+
});
|
|
126
|
+
cursor = {
|
|
127
|
+
state: snap.value.state,
|
|
128
|
+
seq: snap.value.cursor,
|
|
129
|
+
durableSinceSnapshot: 0,
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
else {
|
|
133
|
+
observe({
|
|
134
|
+
type: 'snapshot.load',
|
|
135
|
+
runId,
|
|
136
|
+
at: currentTimeMillis(),
|
|
137
|
+
latencyMs: currentTimeMillis() - loadStartedAt,
|
|
138
|
+
outcome: 'miss',
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
const fromSeq = cursor.seq + 1;
|
|
143
|
+
let delta = yield* store.read(runId, { fromSeq });
|
|
144
|
+
if (cursor.seq === 0 && delta[0] && delta[0].seq > 1) {
|
|
145
|
+
if (store.bounds) {
|
|
146
|
+
const bounds = yield* store.bounds(runId);
|
|
147
|
+
if (bounds.head > 1) {
|
|
148
|
+
return yield* new EventStoreTrimmedError(runId, bounds.head, 1);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
else {
|
|
152
|
+
return yield* new EventStoreTrimmedError(runId, delta[0].seq, 1);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
if (delta.length > 0) {
|
|
156
|
+
delta = yield* readRemaining(store, runId, delta);
|
|
157
|
+
}
|
|
158
|
+
return advanceCursor(cursor, delta, registry);
|
|
159
|
+
});
|
|
160
|
+
const loadCursor = (store, runId) => Effect.gen(function* () {
|
|
161
|
+
const cached = runCache.get(runId);
|
|
162
|
+
if (cached) {
|
|
163
|
+
const tail = yield* store.tail(runId);
|
|
164
|
+
// A tail behind the cursor means the stream was recreated; the cache is poison.
|
|
165
|
+
if (tail < cached.seq) {
|
|
166
|
+
runCache.delete(runId);
|
|
167
|
+
return yield* loadCursorCold(store, runId);
|
|
168
|
+
}
|
|
169
|
+
if (tail === cached.seq) {
|
|
170
|
+
yield* rememberHeader(cached.state);
|
|
171
|
+
return cached;
|
|
172
|
+
}
|
|
173
|
+
const next = yield* catchUp(store, runId, cached, tail).pipe(Effect.catchTag('EventStoreTrimmedError', () => {
|
|
174
|
+
runCache.delete(runId);
|
|
175
|
+
return loadCursorCold(store, runId);
|
|
176
|
+
}));
|
|
177
|
+
runCache.set(runId, next);
|
|
178
|
+
yield* rememberHeader(next.state);
|
|
179
|
+
return next;
|
|
180
|
+
}
|
|
181
|
+
const next = yield* loadCursorCold(store, runId);
|
|
182
|
+
runCache.set(runId, next);
|
|
183
|
+
yield* rememberHeader(next.state);
|
|
184
|
+
return next;
|
|
185
|
+
});
|
|
186
|
+
/**
|
|
187
|
+
* Save the current state externally, keep only `keepSnapshots`, and optionally
|
|
188
|
+
* trim the event log behind the oldest kept snapshot. Failures are swallowed:
|
|
189
|
+
* a snapshot is an optimization, the log stays authoritative.
|
|
190
|
+
*/
|
|
191
|
+
const persistSnapshot = (store, runId, cursor) => Effect.gen(function* () {
|
|
192
|
+
if (cursor.durableSinceSnapshot === 0) {
|
|
193
|
+
return cursor;
|
|
194
|
+
}
|
|
195
|
+
const takenAt = yield* Clock.currentTimeMillis;
|
|
196
|
+
const saveStartedAt = currentTimeMillis();
|
|
197
|
+
yield* snapshotStore.save({
|
|
198
|
+
runId,
|
|
199
|
+
cursor: cursor.seq,
|
|
200
|
+
stateHash: hashRunState(cursor.state),
|
|
201
|
+
takenAt,
|
|
202
|
+
state: cursor.state,
|
|
203
|
+
});
|
|
204
|
+
observe({
|
|
205
|
+
type: 'snapshot.save',
|
|
206
|
+
runId,
|
|
207
|
+
at: currentTimeMillis(),
|
|
208
|
+
latencyMs: currentTimeMillis() - saveStartedAt,
|
|
209
|
+
outcome: 'ok',
|
|
210
|
+
cursor: cursor.seq,
|
|
211
|
+
});
|
|
212
|
+
if (snapshotStore.prune) {
|
|
213
|
+
yield* snapshotStore.prune(runId, keepSnapshots);
|
|
214
|
+
}
|
|
215
|
+
const coverageForRun = trimAfterSnapshot?.coverage;
|
|
216
|
+
if (coverageForRun && store.trim && snapshotStore.listCursors) {
|
|
217
|
+
const cursors = yield* snapshotStore.listCursors(runId);
|
|
218
|
+
const oldestKept = cursors[Math.max(0, cursors.length - keepSnapshots)];
|
|
219
|
+
if (oldestKept !== undefined) {
|
|
220
|
+
const trimStartedAt = currentTimeMillis();
|
|
221
|
+
const coverage = yield* Effect.tryPromise({
|
|
222
|
+
try: () => Promise.resolve(coverageForRun(runId)),
|
|
223
|
+
catch: (cause) => new RuntimeExecutionError(cause instanceof Error ? cause.message : String(cause)),
|
|
224
|
+
});
|
|
225
|
+
yield* trimEventStoreSafely(store, runId, oldestKept + 1, coverage);
|
|
226
|
+
observe({
|
|
227
|
+
type: 'snapshot.trim',
|
|
228
|
+
runId,
|
|
229
|
+
at: currentTimeMillis(),
|
|
230
|
+
latencyMs: currentTimeMillis() - trimStartedAt,
|
|
231
|
+
outcome: 'ok',
|
|
232
|
+
cursor: oldestKept,
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
return { ...cursor, durableSinceSnapshot: 0 };
|
|
237
|
+
}).pipe(Effect.tapError((error) => Effect.logWarning(`Snapshot persistence failed for run "${runId}"`, error)), Effect.orElseSucceed(() => cursor));
|
|
238
|
+
const applyAppend = (store, runId, cursor, batch, appendResult, commandRecords = 0) => Effect.gen(function* () {
|
|
239
|
+
const echo = assignSequences(batch, appendResult);
|
|
240
|
+
const first = echo[0];
|
|
241
|
+
let next = cursor;
|
|
242
|
+
if (first && first.seq !== cursor.seq + 1 + commandRecords) {
|
|
243
|
+
const gap = first.seq - 1 - cursor.seq;
|
|
244
|
+
const context = executionContexts.get(runId);
|
|
245
|
+
if (context.liveCount === gap) {
|
|
246
|
+
next = { ...cursor, seq: first.seq - 1 };
|
|
247
|
+
}
|
|
248
|
+
else {
|
|
249
|
+
next = yield* catchUp(store, runId, cursor, first.seq - 1);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
next = advanceCursor(next, echo, registry);
|
|
253
|
+
executionContexts.get(runId).liveCount = 0;
|
|
254
|
+
if (next.seq !== appendResult.tail) {
|
|
255
|
+
next = yield* catchUp(store, runId, next, appendResult.tail);
|
|
256
|
+
}
|
|
257
|
+
runCache.set(runId, next);
|
|
258
|
+
yield* rememberHeader(next.state);
|
|
259
|
+
return next;
|
|
260
|
+
});
|
|
261
|
+
/**
|
|
262
|
+
* Plan a batch against the latest durable state and append it with optimistic concurrency,
|
|
263
|
+
* replanning from fresh state whenever another writer got there first.
|
|
264
|
+
*/
|
|
265
|
+
const commit = (store, runId, plan) => Effect.gen(function* () {
|
|
266
|
+
for (let conflicts = 0; conflicts < MAX_COMMIT_CONFLICTS; conflicts += 1) {
|
|
267
|
+
const cursor = yield* loadCursor(store, runId);
|
|
268
|
+
const planned = yield* plan(cursor);
|
|
269
|
+
if (planned.events.length === 0) {
|
|
270
|
+
return { value: planned.value, cursor, append: null };
|
|
271
|
+
}
|
|
272
|
+
const append = yield* appendObserved(store, runId, stripSeq(planned.events), {
|
|
273
|
+
expectedTail: cursor.seq,
|
|
274
|
+
idempotency: planned.idempotencyKey ? { key: planned.idempotencyKey } : undefined,
|
|
275
|
+
}).pipe(Effect.asSome, Effect.catchTag('EventStoreConflictError', () => Effect.succeedNone));
|
|
276
|
+
if (Option.isSome(append)) {
|
|
277
|
+
return { value: planned.value, cursor, append: append.value };
|
|
278
|
+
}
|
|
279
|
+
runCache.delete(runId);
|
|
280
|
+
}
|
|
281
|
+
return yield* new RuntimeExecutionError(`Could not commit to run "${runId}" after ${MAX_COMMIT_CONFLICTS} conflicts`);
|
|
282
|
+
});
|
|
283
|
+
return {
|
|
284
|
+
appendObserved,
|
|
285
|
+
commit,
|
|
286
|
+
liveAppends,
|
|
287
|
+
loadCursor,
|
|
288
|
+
persistSnapshot,
|
|
289
|
+
applyAppend,
|
|
290
|
+
deleteCachedCursor: (runId) => runCache.delete(runId),
|
|
291
|
+
setCachedCursor: (runId, cursor) => runCache.set(runId, cursor),
|
|
292
|
+
};
|
|
293
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { type EventEnvelope, type EventOrigin, type OutstandingEffect, type RetryPolicy } from '@swirls/looms/core';
|
|
2
|
+
export declare function retryBackoff(retry: RetryPolicy, attempt: number): number;
|
|
3
|
+
export interface AttemptFailurePlan {
|
|
4
|
+
readonly event: EventEnvelope;
|
|
5
|
+
readonly retrying: boolean;
|
|
6
|
+
}
|
|
7
|
+
/** Schedule the next attempt when the retry policy allows one, otherwise fail the effect. */
|
|
8
|
+
export declare function planAttemptFailure(input: {
|
|
9
|
+
readonly runId: string;
|
|
10
|
+
readonly item: OutstandingEffect;
|
|
11
|
+
readonly attempt: number;
|
|
12
|
+
readonly error: string;
|
|
13
|
+
readonly now: number;
|
|
14
|
+
readonly retry: RetryPolicy | undefined;
|
|
15
|
+
readonly origin: EventOrigin;
|
|
16
|
+
}): AttemptFailurePlan;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { createEvent, } from '@swirls/looms/core';
|
|
2
|
+
import { createEffectFailedEvent } from './helpers.js';
|
|
3
|
+
export function retryBackoff(retry, attempt) {
|
|
4
|
+
const base = Math.max(1, retry.backoffMs ?? 100);
|
|
5
|
+
const multiplier = Math.max(1, retry.backoffMultiplier ?? 2);
|
|
6
|
+
return Math.min(base * multiplier ** Math.max(0, attempt - 1), retry.maxBackoffMs ?? Number.MAX_SAFE_INTEGER);
|
|
7
|
+
}
|
|
8
|
+
/** Schedule the next attempt when the retry policy allows one, otherwise fail the effect. */
|
|
9
|
+
export function planAttemptFailure(input) {
|
|
10
|
+
const { runId, item, attempt, error, retry } = input;
|
|
11
|
+
if (!retry || attempt >= retry.maxAttempts) {
|
|
12
|
+
return { event: createEffectFailedEvent(runId, item, error), retrying: false };
|
|
13
|
+
}
|
|
14
|
+
return {
|
|
15
|
+
retrying: true,
|
|
16
|
+
event: createEvent(runId, {
|
|
17
|
+
type: 'runtime.effect.retry.scheduled',
|
|
18
|
+
payload: {
|
|
19
|
+
effectId: item.effectId,
|
|
20
|
+
attempt,
|
|
21
|
+
nextAttemptAt: input.now + retryBackoff(retry, attempt),
|
|
22
|
+
error,
|
|
23
|
+
},
|
|
24
|
+
threadId: item.threadId,
|
|
25
|
+
causationId: item.causingEventId,
|
|
26
|
+
origin: input.origin,
|
|
27
|
+
}),
|
|
28
|
+
};
|
|
29
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { Exit } from 'effect';
|
|
2
|
+
import type { EventStore, RunState } from '@swirls/looms/core';
|
|
3
|
+
import type { WakeError } from './types';
|
|
4
|
+
export type WakeCompletion = Exit.Exit<RunState, WakeError>;
|
|
5
|
+
export interface ActiveEffect {
|
|
6
|
+
readonly controller: AbortController;
|
|
7
|
+
readonly threadId: string;
|
|
8
|
+
}
|
|
9
|
+
export interface RunIngressState {
|
|
10
|
+
readonly pending: number;
|
|
11
|
+
readonly tail: Promise<void>;
|
|
12
|
+
}
|
|
13
|
+
export interface RunExecutionContext {
|
|
14
|
+
readonly activeEffects: Map<string, ActiveEffect>;
|
|
15
|
+
waking: boolean;
|
|
16
|
+
wakeAgain: boolean;
|
|
17
|
+
wakeCompletion: Promise<WakeCompletion> | null;
|
|
18
|
+
resolveWakeCompletion: ((completion: WakeCompletion) => void) | null;
|
|
19
|
+
ingress: RunIngressState | null;
|
|
20
|
+
liveStore: EventStore | undefined;
|
|
21
|
+
liveCount: number;
|
|
22
|
+
}
|
|
23
|
+
export declare class RunExecutionContexts {
|
|
24
|
+
private readonly runs;
|
|
25
|
+
get(runId: string): RunExecutionContext;
|
|
26
|
+
peek(runId: string): RunExecutionContext | undefined;
|
|
27
|
+
/** Abort in-process effects owned by the given threads. */
|
|
28
|
+
abort(runId: string, threadIds: ReadonlySet<string>, message: string): void;
|
|
29
|
+
releaseIfIdle(runId: string): void;
|
|
30
|
+
}
|