@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
|
@@ -1,19 +1,26 @@
|
|
|
1
1
|
import { AppendInput, AppendRecord, S2, S2Endpoints, S2Error } from '@s2-dev/streamstore';
|
|
2
|
-
import { Effect, Layer, Option, Schema } from 'effect';
|
|
3
|
-
import { createKeyedSerializer, SnapshotStoreError, SnapshotStoreTag, } from '@swirls/looms/core';
|
|
2
|
+
import { Effect, Layer, Option, Schema, Stream } from 'effect';
|
|
3
|
+
import { createKeyedSerializer, assertSnapshotByteLimit, RunStateSchema, RunSummarySchema, SnapshotStoreError, SnapshotPayloadTooLargeError, SnapshotStoreTag, } from '@swirls/looms/core';
|
|
4
4
|
import { S2ConfigSchema } from './config.js';
|
|
5
|
+
import { paginateS2Stream } from './pagination.js';
|
|
5
6
|
const DEFAULT_PREFIX = 'snapshots';
|
|
6
7
|
const DEFAULT_MAX_CHUNK_BYTES = 900 * 1024;
|
|
7
8
|
const SnapshotFrameSchema = Schema.Struct({
|
|
8
9
|
v: Schema.Literal(1),
|
|
9
10
|
runId: Schema.String,
|
|
10
|
-
cursor: Schema.
|
|
11
|
+
cursor: Schema.Finite,
|
|
11
12
|
stateHash: Schema.String,
|
|
12
|
-
takenAt: Schema.
|
|
13
|
-
chunk: Schema.
|
|
14
|
-
chunks: Schema.
|
|
13
|
+
takenAt: Schema.Finite,
|
|
14
|
+
chunk: Schema.Finite,
|
|
15
|
+
chunks: Schema.Finite,
|
|
15
16
|
data: Schema.String,
|
|
16
17
|
});
|
|
18
|
+
const SnapshotFrameJson = Schema.fromJsonString(SnapshotFrameSchema);
|
|
19
|
+
const decodeFrame = Schema.decodeSync(SnapshotFrameJson);
|
|
20
|
+
const encodeFrame = Schema.encodeSync(SnapshotFrameJson);
|
|
21
|
+
const HeaderJson = Schema.fromJsonString(RunSummarySchema);
|
|
22
|
+
const decodeHeader = Schema.decodeUnknownOption(HeaderJson);
|
|
23
|
+
const encodeHeader = Schema.encodeSync(HeaderJson);
|
|
17
24
|
export function frameSnapshot(snapshot, maxChunkBytes = DEFAULT_MAX_CHUNK_BYTES) {
|
|
18
25
|
const json = JSON.stringify(snapshot.state);
|
|
19
26
|
const chunks = Math.max(1, Math.ceil(json.length / maxChunkBytes));
|
|
@@ -37,6 +44,8 @@ export function assembleSnapshot(frames) {
|
|
|
37
44
|
throw new SnapshotStoreError('Cannot assemble snapshot from empty frames');
|
|
38
45
|
}
|
|
39
46
|
const first = frames[0];
|
|
47
|
+
// Oxlint's immutable alternative requires ES2023; sorting a fresh copy is safe on ES2022.
|
|
48
|
+
// oxlint-disable-next-line unicorn/no-array-sort
|
|
40
49
|
const ordered = [...frames].sort((a, b) => a.chunk - b.chunk);
|
|
41
50
|
for (const frame of ordered) {
|
|
42
51
|
if (frame.cursor !== first.cursor ||
|
|
@@ -54,8 +63,7 @@ export function assembleSnapshot(frames) {
|
|
|
54
63
|
}
|
|
55
64
|
}
|
|
56
65
|
const json = ordered.map((frame) => frame.data).join('');
|
|
57
|
-
|
|
58
|
-
const state = JSON.parse(json);
|
|
66
|
+
const state = Schema.decodeSync(Schema.fromJsonString(RunStateSchema))(json);
|
|
59
67
|
return {
|
|
60
68
|
runId: first.runId,
|
|
61
69
|
cursor: first.cursor,
|
|
@@ -84,74 +92,56 @@ function createClient(config) {
|
|
|
84
92
|
}
|
|
85
93
|
function parseFrame(body) {
|
|
86
94
|
try {
|
|
87
|
-
return
|
|
95
|
+
return decodeFrame(body);
|
|
88
96
|
}
|
|
89
97
|
catch {
|
|
90
98
|
return undefined;
|
|
91
99
|
}
|
|
92
100
|
}
|
|
101
|
+
function toSnapshotError(cause, message) {
|
|
102
|
+
return cause instanceof SnapshotStoreError
|
|
103
|
+
? cause
|
|
104
|
+
: new SnapshotStoreError(`${message}: ${cause instanceof Error ? cause.message : String(cause)}`, cause);
|
|
105
|
+
}
|
|
93
106
|
/**
|
|
94
107
|
* Trailing command records (the trim written by `prune`) are skipped by
|
|
95
108
|
* `ignoreCommandRecords`, so a `tailOffset: 1` read can come back empty.
|
|
96
109
|
* Probe a few records from the tail instead.
|
|
97
110
|
*/
|
|
98
111
|
const TAIL_PROBE = 4;
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
112
|
+
function readFromTail(stream, tailOffset) {
|
|
113
|
+
return Effect.tryPromise({
|
|
114
|
+
try: () => stream.read({
|
|
102
115
|
start: { from: { tailOffset } },
|
|
103
116
|
ignoreCommandRecords: true,
|
|
104
|
-
})
|
|
105
|
-
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
if (cause instanceof S2Error && (cause.status === 404 || cause.status === 416)) {
|
|
109
|
-
return null;
|
|
110
|
-
}
|
|
111
|
-
throw cause;
|
|
112
|
-
}
|
|
117
|
+
}),
|
|
118
|
+
catch: (cause) => toSnapshotError(cause, 'Failed to read snapshot stream tail'),
|
|
119
|
+
}).pipe(Effect.map((page) => [...page.records]), Effect.catchIf((error) => error.cause instanceof S2Error &&
|
|
120
|
+
(error.cause.status === 404 || error.cause.status === 416), () => Effect.succeed([])));
|
|
113
121
|
}
|
|
114
122
|
/** Latest complete data frame near the tail, with its record, or undefined. */
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
123
|
+
function readLatestFrame(stream) {
|
|
124
|
+
return readFromTail(stream, TAIL_PROBE).pipe(Effect.map((records) => {
|
|
125
|
+
for (let i = records.length - 1; i >= 0; i--) {
|
|
126
|
+
const record = records[i];
|
|
127
|
+
const frame = parseFrame(record.body);
|
|
128
|
+
if (frame) {
|
|
129
|
+
return { record, frame };
|
|
130
|
+
}
|
|
122
131
|
}
|
|
123
|
-
|
|
124
|
-
|
|
132
|
+
return undefined;
|
|
133
|
+
}));
|
|
125
134
|
}
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
while (true) {
|
|
130
|
-
const page = await stream
|
|
131
|
-
.read({
|
|
135
|
+
function readAllSnapshotRecords(stream) {
|
|
136
|
+
return paginateS2Stream((cursor, count) => Effect.tryPromise({
|
|
137
|
+
try: () => stream.read({
|
|
132
138
|
start: { from: { seqNum: cursor }, clamp: true },
|
|
133
|
-
stop: { limits: { count
|
|
139
|
+
stop: { limits: { count } },
|
|
134
140
|
ignoreCommandRecords: true,
|
|
135
|
-
})
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
return null;
|
|
140
|
-
}
|
|
141
|
-
throw cause;
|
|
142
|
-
});
|
|
143
|
-
if (!page || page.records.length === 0) {
|
|
144
|
-
break;
|
|
145
|
-
}
|
|
146
|
-
records.push(...page.records);
|
|
147
|
-
const last = page.records[page.records.length - 1];
|
|
148
|
-
const next = last.seqNum + 1;
|
|
149
|
-
if (page.tail?.seqNum !== undefined && next >= page.tail.seqNum) {
|
|
150
|
-
break;
|
|
151
|
-
}
|
|
152
|
-
cursor = next;
|
|
153
|
-
}
|
|
154
|
-
return records;
|
|
141
|
+
}),
|
|
142
|
+
catch: (cause) => toSnapshotError(cause, 'Failed to scan snapshot stream'),
|
|
143
|
+
}).pipe(Effect.catchIf((error) => error.cause instanceof S2Error &&
|
|
144
|
+
(error.cause.status === 404 || error.cause.status === 416), () => Effect.succeed(null)))).pipe(Stream.runCollect);
|
|
155
145
|
}
|
|
156
146
|
function groupCompleteSnapshots(records) {
|
|
157
147
|
const byCursor = new Map();
|
|
@@ -173,144 +163,181 @@ function groupCompleteSnapshots(records) {
|
|
|
173
163
|
continue;
|
|
174
164
|
}
|
|
175
165
|
}
|
|
166
|
+
// `assembled` is function-local, so in-place sorting cannot mutate caller-owned data.
|
|
167
|
+
// oxlint-disable-next-line unicorn/no-array-sort
|
|
176
168
|
return assembled.sort((a, b) => a.cursor - b.cursor);
|
|
177
169
|
}
|
|
178
170
|
export function s2SnapshotStore(config) {
|
|
179
|
-
const parsed = Schema.
|
|
171
|
+
const parsed = Schema.decodeSync(S2ConfigSchema)(config);
|
|
180
172
|
const client = createClient(parsed);
|
|
181
173
|
const basin = client.basin(parsed.basin);
|
|
182
174
|
const prefix = parsed.snapshotPrefix ?? DEFAULT_PREFIX;
|
|
183
175
|
const appends = createKeyedSerializer();
|
|
184
176
|
const ensured = new Set();
|
|
177
|
+
const headers = new Map();
|
|
185
178
|
let basinEnsured = false;
|
|
179
|
+
let headersLoaded = false;
|
|
186
180
|
const streamName = (runId) => `${prefix}/${runId}`;
|
|
187
|
-
const ensureBasin =
|
|
181
|
+
const ensureBasin = Effect.suspend(() => {
|
|
188
182
|
if (basinEnsured) {
|
|
189
|
-
return;
|
|
183
|
+
return Effect.void;
|
|
190
184
|
}
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
};
|
|
201
|
-
const ensureStream = async (runId) => {
|
|
202
|
-
await ensureBasin();
|
|
185
|
+
return Effect.tryPromise({
|
|
186
|
+
try: () => client.basins.create({ basin: parsed.basin }),
|
|
187
|
+
catch: (cause) => toSnapshotError(cause, `Failed to ensure basin ${parsed.basin}`),
|
|
188
|
+
}).pipe(Effect.catchIf((error) => error.cause instanceof S2Error && error.cause.status === 409, () => Effect.void), Effect.tap(() => Effect.sync(() => {
|
|
189
|
+
basinEnsured = true;
|
|
190
|
+
})), Effect.asVoid);
|
|
191
|
+
});
|
|
192
|
+
const ensureStream = (runId) => Effect.gen(function* () {
|
|
193
|
+
yield* ensureBasin;
|
|
203
194
|
const name = streamName(runId);
|
|
204
195
|
if (!ensured.has(name)) {
|
|
205
|
-
|
|
206
|
-
|
|
196
|
+
yield* Effect.tryPromise({
|
|
197
|
+
try: () => basin.streams.create({
|
|
207
198
|
stream: name,
|
|
208
199
|
config: { retentionPolicy: { infinite: {} } },
|
|
209
|
-
})
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
}
|
|
216
|
-
}
|
|
200
|
+
}),
|
|
201
|
+
catch: (cause) => toSnapshotError(cause, `Failed to ensure stream ${name}`),
|
|
202
|
+
}).pipe(Effect.catchIf((error) => error.cause instanceof S2Error &&
|
|
203
|
+
(error.cause.status === 409 ||
|
|
204
|
+
error.cause.status === 404 ||
|
|
205
|
+
error.cause.status === 405), () => Effect.void));
|
|
217
206
|
ensured.add(name);
|
|
218
207
|
}
|
|
219
208
|
return basin.stream(name);
|
|
220
|
-
};
|
|
221
|
-
const toError = (cause, message) => cause instanceof SnapshotStoreError
|
|
222
|
-
? cause
|
|
223
|
-
: new SnapshotStoreError(`${message}: ${cause instanceof Error ? cause.message : String(cause)}`, cause);
|
|
209
|
+
});
|
|
224
210
|
const service = {
|
|
225
|
-
save: (snapshot) => Effect.
|
|
226
|
-
try
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
211
|
+
save: (snapshot) => Effect.gen(function* () {
|
|
212
|
+
yield* Effect.try({
|
|
213
|
+
try: () => assertSnapshotByteLimit(snapshot),
|
|
214
|
+
catch: (cause) => cause instanceof SnapshotPayloadTooLargeError
|
|
215
|
+
? cause
|
|
216
|
+
: new SnapshotStoreError('snapshot size validation failed', cause),
|
|
217
|
+
});
|
|
218
|
+
const stream = yield* ensureStream(snapshot.runId);
|
|
219
|
+
const frames = frameSnapshot(snapshot);
|
|
220
|
+
const records = frames.map((frame) => AppendRecord.string({
|
|
221
|
+
body: encodeFrame(frame),
|
|
222
|
+
headers: [
|
|
223
|
+
['looms-cursor', String(frame.cursor)],
|
|
224
|
+
['looms-chunk', `${frame.chunk}/${frame.chunks}`],
|
|
225
|
+
],
|
|
226
|
+
}));
|
|
227
|
+
yield* Effect.tryPromise({
|
|
228
|
+
try: () => appends.run(snapshot.runId, () => {
|
|
237
229
|
if (records.length === 1) {
|
|
238
|
-
|
|
239
|
-
return;
|
|
230
|
+
return stream.append(AppendInput.create(records)).then(() => undefined);
|
|
240
231
|
}
|
|
232
|
+
let pending = Promise.resolve();
|
|
241
233
|
for (const record of records) {
|
|
242
|
-
|
|
234
|
+
pending = pending.then(() => stream.append(AppendInput.create([record])).then(() => undefined));
|
|
243
235
|
}
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
236
|
+
return pending;
|
|
237
|
+
}),
|
|
238
|
+
catch: (cause) => toSnapshotError(cause, `Failed to save snapshot for ${snapshot.runId}`),
|
|
239
|
+
});
|
|
247
240
|
}),
|
|
248
|
-
loadLatest: (runId) => Effect.
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
return Option.some(assembleSnapshot(frames));
|
|
264
|
-
}
|
|
241
|
+
loadLatest: (runId) => Effect.gen(function* () {
|
|
242
|
+
const stream = yield* ensureStream(runId);
|
|
243
|
+
const tailFrame = yield* readLatestFrame(stream);
|
|
244
|
+
if (!tailFrame) {
|
|
245
|
+
return Option.none();
|
|
246
|
+
}
|
|
247
|
+
if (tailFrame.frame.chunk === tailFrame.frame.chunks - 1) {
|
|
248
|
+
const lastFrame = tailFrame.frame;
|
|
249
|
+
const records = (yield* readFromTail(stream, lastFrame.chunks + TAIL_PROBE)) ?? [];
|
|
250
|
+
const frames = records
|
|
251
|
+
.map((record) => parseFrame(record.body))
|
|
252
|
+
.filter((frame) => frame !== undefined)
|
|
253
|
+
.filter((frame) => frame.cursor === lastFrame.cursor);
|
|
254
|
+
if (frames.length === lastFrame.chunks) {
|
|
255
|
+
return Option.some(assembleSnapshot(frames));
|
|
265
256
|
}
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
catch: (cause) => toError(cause, `Failed to load snapshot for ${runId}`),
|
|
257
|
+
}
|
|
258
|
+
// Incomplete or interleaved tail: fall back to scanning the retained stream.
|
|
259
|
+
const records = yield* readAllSnapshotRecords(stream);
|
|
260
|
+
const complete = groupCompleteSnapshots(records);
|
|
261
|
+
const newest = complete[complete.length - 1];
|
|
262
|
+
return newest ? Option.some(newest) : Option.none();
|
|
273
263
|
}),
|
|
274
|
-
listCursors: (runId) => Effect.
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
return groupCompleteSnapshots(records).map((snapshot) => snapshot.cursor);
|
|
279
|
-
},
|
|
280
|
-
catch: (cause) => toError(cause, `Failed to list snapshot cursors for ${runId}`),
|
|
264
|
+
listCursors: (runId) => Effect.gen(function* () {
|
|
265
|
+
const stream = yield* ensureStream(runId);
|
|
266
|
+
const records = yield* readAllSnapshotRecords(stream);
|
|
267
|
+
return groupCompleteSnapshots(records).map((snapshot) => snapshot.cursor);
|
|
281
268
|
}),
|
|
282
|
-
prune: (runId, keepLatest) => Effect.
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
if (!latest || latest.frame.chunk !== latest.frame.chunks - 1) {
|
|
289
|
-
return;
|
|
290
|
-
}
|
|
291
|
-
const start = latest.record.seqNum - latest.frame.chunk;
|
|
292
|
-
if (start <= 0) {
|
|
293
|
-
return;
|
|
294
|
-
}
|
|
295
|
-
await appends.run(runId, () => stream.append(AppendInput.create([AppendRecord.trim(start)])));
|
|
269
|
+
prune: (runId, keepLatest) => Effect.gen(function* () {
|
|
270
|
+
const stream = yield* ensureStream(runId);
|
|
271
|
+
if (keepLatest <= 1) {
|
|
272
|
+
// Fast path: the latest complete frame tells us where the latest snapshot starts.
|
|
273
|
+
const latest = yield* readLatestFrame(stream);
|
|
274
|
+
if (!latest || latest.frame.chunk !== latest.frame.chunks - 1) {
|
|
296
275
|
return;
|
|
297
276
|
}
|
|
298
|
-
const
|
|
299
|
-
|
|
300
|
-
if (complete.length <= keepLatest) {
|
|
277
|
+
const start = latest.record.seqNum - latest.frame.chunk;
|
|
278
|
+
if (start <= 0) {
|
|
301
279
|
return;
|
|
302
280
|
}
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
281
|
+
yield* Effect.tryPromise({
|
|
282
|
+
try: () => appends.run(runId, () => stream.append(AppendInput.create([AppendRecord.trim(start)]))),
|
|
283
|
+
catch: (cause) => toSnapshotError(cause, `Failed to prune snapshots for ${runId}`),
|
|
284
|
+
});
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
const records = yield* readAllSnapshotRecords(stream);
|
|
288
|
+
const complete = groupCompleteSnapshots(records);
|
|
289
|
+
if (complete.length <= keepLatest) {
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
const keep = complete[complete.length - keepLatest];
|
|
293
|
+
if (!keep) {
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
const firstKept = records.find((record) => parseFrame(record.body)?.cursor === keep.cursor);
|
|
297
|
+
if (!firstKept) {
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
yield* Effect.tryPromise({
|
|
301
|
+
try: () => appends.run(runId, () => stream.append(AppendInput.create([AppendRecord.trim(firstKept.seqNum)]))),
|
|
302
|
+
catch: (cause) => toSnapshotError(cause, `Failed to prune snapshots for ${runId}`),
|
|
303
|
+
});
|
|
304
|
+
}),
|
|
305
|
+
saveHeader: (header) => Effect.gen(function* () {
|
|
306
|
+
const stream = yield* ensureStream('__headers');
|
|
307
|
+
yield* Effect.tryPromise({
|
|
308
|
+
try: () => appends.run('__headers', () => stream.append(AppendInput.create([AppendRecord.string({ body: encodeHeader(header) })]))),
|
|
309
|
+
catch: (cause) => toSnapshotError(cause, `Failed to save run header for ${header.runId}`),
|
|
310
|
+
});
|
|
311
|
+
headers.set(header.runId, header);
|
|
312
|
+
}),
|
|
313
|
+
listHeaders: Effect.gen(function* () {
|
|
314
|
+
if (headersLoaded) {
|
|
315
|
+
return [...headers.values()];
|
|
316
|
+
}
|
|
317
|
+
const stream = yield* ensureStream('__headers');
|
|
318
|
+
const records = yield* paginateS2Stream((cursor, count) => Effect.tryPromise({
|
|
319
|
+
try: () => stream.read({
|
|
320
|
+
start: { from: { seqNum: cursor }, clamp: true },
|
|
321
|
+
stop: { limits: { count } },
|
|
322
|
+
ignoreCommandRecords: true,
|
|
323
|
+
}),
|
|
324
|
+
catch: (cause) => toSnapshotError(cause, 'Failed to read run headers'),
|
|
325
|
+
}).pipe(Effect.catchIf((error) => error.cause instanceof S2Error &&
|
|
326
|
+
(error.cause.status === 404 || error.cause.status === 416), () => Effect.succeed(null)))).pipe(Stream.runCollect);
|
|
327
|
+
const loaded = new Map();
|
|
328
|
+
for (const record of records) {
|
|
329
|
+
const decoded = decodeHeader(record.body);
|
|
330
|
+
if (Option.isSome(decoded)) {
|
|
331
|
+
loaded.set(decoded.value.runId, decoded.value);
|
|
306
332
|
}
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
333
|
+
}
|
|
334
|
+
for (const [runId, header] of loaded) {
|
|
335
|
+
if (!headers.has(runId)) {
|
|
336
|
+
headers.set(runId, header);
|
|
310
337
|
}
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
338
|
+
}
|
|
339
|
+
headersLoaded = true;
|
|
340
|
+
return [...headers.values()];
|
|
314
341
|
}),
|
|
315
342
|
};
|
|
316
343
|
return service;
|
package/dist/s2/store.d.ts
CHANGED
|
@@ -1,26 +1,8 @@
|
|
|
1
|
-
import { Effect, Layer
|
|
1
|
+
import { Effect, Layer } from 'effect';
|
|
2
2
|
import { EventStoreError, EventStoreTag, type EventStore } from '@swirls/looms/core';
|
|
3
3
|
import { type S2Config } from './config';
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
readonly tail?: {
|
|
7
|
-
readonly seqNum: number;
|
|
8
|
-
} | null;
|
|
9
|
-
}
|
|
10
|
-
export interface ReadAllPagesOptions {
|
|
11
|
-
readonly fromSeq?: number;
|
|
12
|
-
readonly limit?: number;
|
|
13
|
-
readonly maxPageSize?: number;
|
|
14
|
-
}
|
|
15
|
-
/**
|
|
16
|
-
* Paginate an S2 stream lazily using Effect Streams.
|
|
17
|
-
*
|
|
18
|
-
* Pulls pages on demand until the stream tail is reached, an empty page is returned,
|
|
19
|
-
* or the caller's optional limit is satisfied.
|
|
20
|
-
*/
|
|
21
|
-
export declare function paginateS2Stream<T extends {
|
|
22
|
-
readonly seqNum: number;
|
|
23
|
-
}>(fetchPage: (cursor: number, count: number) => Effect.Effect<S2ReadBatchLike<T> | null, EventStoreError>, options?: ReadAllPagesOptions): Stream.Stream<T, EventStoreError>;
|
|
4
|
+
import { paginateS2Stream, type ReadAllPagesOptions, type S2ReadBatchLike } from './pagination';
|
|
5
|
+
export { paginateS2Stream, type ReadAllPagesOptions, type S2ReadBatchLike };
|
|
24
6
|
export declare function readAllPages<T extends {
|
|
25
7
|
readonly seqNum: number;
|
|
26
8
|
}>(fetchPage: (cursor: number, count: number) => Promise<S2ReadBatchLike<T> | null>, options?: ReadAllPagesOptions): Promise<T[]>;
|