@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
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import { Effect, Schema } from 'effect';
|
|
2
|
+
import { createRunId, EventStoreTag, JsonValueSchema, stringifyJson, } from '@swirls/looms/core';
|
|
3
|
+
import { createEventStreamResponse } from '../../sse.js';
|
|
4
|
+
import { acceptsEventStream, EventInputBodySchema, extractIdempotencyKey, provideStore, readIntegerQuery, readJson, RequestQueryError, } from '../http.js';
|
|
5
|
+
import { route } from '../route-table.js';
|
|
6
|
+
const StartRunBodySchema = Schema.Struct({
|
|
7
|
+
kind: Schema.optional(Schema.String),
|
|
8
|
+
definitionName: Schema.optional(Schema.String),
|
|
9
|
+
definitionVersion: Schema.optional(Schema.String),
|
|
10
|
+
name: Schema.optional(Schema.String),
|
|
11
|
+
input: Schema.optional(JsonValueSchema),
|
|
12
|
+
runId: Schema.optional(Schema.String),
|
|
13
|
+
idempotencyKey: Schema.optional(Schema.String),
|
|
14
|
+
});
|
|
15
|
+
const SignalBodySchema = Schema.Struct({
|
|
16
|
+
events: Schema.optional(Schema.Array(EventInputBodySchema)),
|
|
17
|
+
type: Schema.optional(Schema.String),
|
|
18
|
+
payload: Schema.optional(JsonValueSchema),
|
|
19
|
+
threadId: Schema.optional(Schema.NullOr(Schema.String)),
|
|
20
|
+
idempotencyKey: Schema.optional(Schema.String),
|
|
21
|
+
});
|
|
22
|
+
function wantsRunStream(req, url) {
|
|
23
|
+
return url.searchParams.get('stream') === 'true' || acceptsEventStream(req);
|
|
24
|
+
}
|
|
25
|
+
function wantsEventStream(req, url) {
|
|
26
|
+
const live = url.searchParams.get('live');
|
|
27
|
+
return live === 'true' || live === '1' || acceptsEventStream(req);
|
|
28
|
+
}
|
|
29
|
+
function readEventStreamFromSeq(req, url) {
|
|
30
|
+
const lastEventId = req.headers.get('last-event-id');
|
|
31
|
+
if (lastEventId !== null && lastEventId.trim() !== '') {
|
|
32
|
+
const value = Number(lastEventId);
|
|
33
|
+
if (!Number.isSafeInteger(value) || value < 0) {
|
|
34
|
+
return Effect.fail(new RequestQueryError({
|
|
35
|
+
message: 'Last-Event-ID must be a non-negative integer',
|
|
36
|
+
}));
|
|
37
|
+
}
|
|
38
|
+
return Effect.succeed(value + 1);
|
|
39
|
+
}
|
|
40
|
+
return readIntegerQuery(url, 'fromSeq', { minimum: 1 }).pipe(Effect.map((fromSeq) => fromSeq ?? 1));
|
|
41
|
+
}
|
|
42
|
+
function streamStartRun(req, runtime, store, args) {
|
|
43
|
+
return createEventStreamResponse({
|
|
44
|
+
signal: req.signal,
|
|
45
|
+
request: req,
|
|
46
|
+
store,
|
|
47
|
+
runId: args.runId,
|
|
48
|
+
fromSeq: 1,
|
|
49
|
+
onStart: (write, close) => runtime
|
|
50
|
+
.startRun({
|
|
51
|
+
kind: args.kind,
|
|
52
|
+
definitionName: args.definitionName,
|
|
53
|
+
definitionVersion: args.definitionVersion,
|
|
54
|
+
input: args.input,
|
|
55
|
+
runId: args.runId,
|
|
56
|
+
idempotencyKey: args.idempotencyKey,
|
|
57
|
+
})
|
|
58
|
+
.pipe(Effect.provideService(EventStoreTag, store), Effect.tap((result) => Effect.sync(() => write(`event: done\ndata: ${stringifyJson(result)}\n\n`))), Effect.catch((error) => Effect.sync(() => write(`event: error\ndata: ${stringifyJson({ error: error.message })}\n\n`))), Effect.ensuring(Effect.sync(close)), Effect.asVoid),
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
export function runRoutes({ runtime, store }) {
|
|
62
|
+
return [
|
|
63
|
+
route({
|
|
64
|
+
method: 'GET',
|
|
65
|
+
path: '/health',
|
|
66
|
+
name: 'health',
|
|
67
|
+
access: 'public',
|
|
68
|
+
handle: () => Effect.succeed(Response.json({ ok: true })),
|
|
69
|
+
}),
|
|
70
|
+
route({
|
|
71
|
+
method: 'GET',
|
|
72
|
+
path: '/runs',
|
|
73
|
+
name: 'runs.list',
|
|
74
|
+
access: 'read',
|
|
75
|
+
handle: () => provideStore(runtime.listRuns.pipe(Effect.map((runIds) => Response.json({ runIds }))), store),
|
|
76
|
+
}),
|
|
77
|
+
route({
|
|
78
|
+
method: 'GET',
|
|
79
|
+
path: '/runs/summaries',
|
|
80
|
+
name: 'runs.summaries',
|
|
81
|
+
access: 'read',
|
|
82
|
+
handle: () => provideStore(runtime.listRunSummaries.pipe(Effect.map((runs) => Response.json({ runs }))), store),
|
|
83
|
+
}),
|
|
84
|
+
route({
|
|
85
|
+
method: 'POST',
|
|
86
|
+
path: '/runs',
|
|
87
|
+
name: 'runs.start',
|
|
88
|
+
access: 'write',
|
|
89
|
+
handle: ({ req, url }) => Effect.gen(function* () {
|
|
90
|
+
const body = yield* readJson(req).pipe(Effect.flatMap(Schema.decodeUnknownEffect(StartRunBodySchema)));
|
|
91
|
+
const definitionName = body.definitionName ?? body.name;
|
|
92
|
+
const kind = body.kind;
|
|
93
|
+
if (!definitionName || !kind) {
|
|
94
|
+
return Response.json({ error: 'kind and definitionName required' }, { status: 400 });
|
|
95
|
+
}
|
|
96
|
+
const runId = body.runId ?? createRunId();
|
|
97
|
+
const idempotencyKey = extractIdempotencyKey(req, body.idempotencyKey);
|
|
98
|
+
const args = {
|
|
99
|
+
kind,
|
|
100
|
+
definitionName,
|
|
101
|
+
definitionVersion: body.definitionVersion,
|
|
102
|
+
input: body.input ?? null,
|
|
103
|
+
runId,
|
|
104
|
+
idempotencyKey,
|
|
105
|
+
};
|
|
106
|
+
if (wantsRunStream(req, url)) {
|
|
107
|
+
return streamStartRun(req, runtime, store, args);
|
|
108
|
+
}
|
|
109
|
+
const result = yield* provideStore(runtime.startRun(args), store);
|
|
110
|
+
return Response.json(result);
|
|
111
|
+
}),
|
|
112
|
+
}),
|
|
113
|
+
route({
|
|
114
|
+
method: 'GET',
|
|
115
|
+
path: '/runs/:runId/replay',
|
|
116
|
+
name: 'runs.replay',
|
|
117
|
+
access: 'read',
|
|
118
|
+
handle: ({ url, params }) => provideStore(runtime.replayTo(params.runId, Number(url.searchParams.get('seq') ?? '0')), store).pipe(Effect.map((step) => Response.json({ runId: params.runId, step }))),
|
|
119
|
+
}),
|
|
120
|
+
route({
|
|
121
|
+
method: 'GET',
|
|
122
|
+
path: '/runs/:runId/status',
|
|
123
|
+
name: 'runs.status',
|
|
124
|
+
access: 'read',
|
|
125
|
+
handle: ({ params }) => provideStore(runtime.inspectRun(params.runId), store).pipe(Effect.map((status) => Response.json(status))),
|
|
126
|
+
}),
|
|
127
|
+
route({
|
|
128
|
+
method: 'GET',
|
|
129
|
+
path: '/runs/:runId/projections/:projectionName',
|
|
130
|
+
name: 'runs.projection',
|
|
131
|
+
access: 'read',
|
|
132
|
+
handle: ({ params }) => {
|
|
133
|
+
const definition = runtime.registry.projections.get(params.projectionName);
|
|
134
|
+
if (!definition) {
|
|
135
|
+
return Effect.succeed(Response.json({ error: `Unknown projection: ${params.projectionName}` }, { status: 404 }));
|
|
136
|
+
}
|
|
137
|
+
return provideStore(runtime.project(params.runId, definition), store).pipe(Effect.map((value) => Response.json({ runId: params.runId, name: params.projectionName, value })));
|
|
138
|
+
},
|
|
139
|
+
}),
|
|
140
|
+
route({
|
|
141
|
+
method: 'GET',
|
|
142
|
+
path: '/runs/:runId/events',
|
|
143
|
+
name: 'runs.events',
|
|
144
|
+
access: 'read',
|
|
145
|
+
handle: ({ req, url, params }) => Effect.gen(function* () {
|
|
146
|
+
if (wantsEventStream(req, url)) {
|
|
147
|
+
return createEventStreamResponse({
|
|
148
|
+
signal: req.signal,
|
|
149
|
+
request: req,
|
|
150
|
+
store,
|
|
151
|
+
runId: params.runId,
|
|
152
|
+
fromSeq: yield* readEventStreamFromSeq(req, url),
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
const fromSeq = (yield* readIntegerQuery(url, 'fromSeq', { minimum: 1 })) ?? undefined;
|
|
156
|
+
const limit = (yield* readIntegerQuery(url, 'limit', { minimum: 1 })) ?? undefined;
|
|
157
|
+
const events = yield* provideStore(runtime.getEvents(params.runId, { fromSeq, limit }), store);
|
|
158
|
+
return Response.json({ runId: params.runId, events }, { headers: { vary: 'accept' } });
|
|
159
|
+
}),
|
|
160
|
+
}),
|
|
161
|
+
route({
|
|
162
|
+
method: 'GET',
|
|
163
|
+
path: '/runs/:runId/threads',
|
|
164
|
+
name: 'runs.threads',
|
|
165
|
+
access: 'read',
|
|
166
|
+
handle: ({ params }) => provideStore(runtime.getRun(params.runId), store).pipe(Effect.map((state) => Response.json({ runId: params.runId, threads: state.threads }))),
|
|
167
|
+
}),
|
|
168
|
+
route({
|
|
169
|
+
method: 'GET',
|
|
170
|
+
path: '/runs/:runId',
|
|
171
|
+
name: 'runs.get',
|
|
172
|
+
access: 'read',
|
|
173
|
+
handle: ({ params }) => provideStore(runtime.getRun(params.runId), store).pipe(Effect.map((state) => Response.json({ runId: params.runId, state }))),
|
|
174
|
+
}),
|
|
175
|
+
route({
|
|
176
|
+
method: 'POST',
|
|
177
|
+
path: '/runs/:runId/events',
|
|
178
|
+
name: 'runs.signal',
|
|
179
|
+
access: 'write',
|
|
180
|
+
handle: ({ req, params }) => Effect.gen(function* () {
|
|
181
|
+
const body = yield* readJson(req).pipe(Effect.flatMap(Schema.decodeUnknownEffect(SignalBodySchema)));
|
|
182
|
+
const events = body.events ??
|
|
183
|
+
(body.type
|
|
184
|
+
? [{ type: body.type, payload: body.payload ?? null, threadId: body.threadId }]
|
|
185
|
+
: []);
|
|
186
|
+
const idempotencyKey = extractIdempotencyKey(req, body.idempotencyKey);
|
|
187
|
+
const state = yield* provideStore(runtime.signal(params.runId, events, { idempotencyKey }), store);
|
|
188
|
+
return Response.json({ runId: params.runId, state });
|
|
189
|
+
}),
|
|
190
|
+
}),
|
|
191
|
+
route({
|
|
192
|
+
method: 'POST',
|
|
193
|
+
path: '/runs/:runId/wake',
|
|
194
|
+
name: 'runs.wake',
|
|
195
|
+
access: 'write',
|
|
196
|
+
handle: ({ params }) => provideStore(runtime.wake(params.runId), store).pipe(Effect.map((state) => Response.json({ runId: params.runId, state }))),
|
|
197
|
+
}),
|
|
198
|
+
];
|
|
199
|
+
}
|
package/dist/runtime/server.d.ts
CHANGED
|
@@ -1,16 +1,12 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
export
|
|
4
|
-
|
|
5
|
-
store: EventStore;
|
|
6
|
-
}
|
|
1
|
+
export { createFetchHandler, handlesPath, type FetchHandlerOptions } from './server/fetch-handler';
|
|
2
|
+
export { isLoomsApiPath } from './server/http';
|
|
3
|
+
export { mountDebugger, type DebuggerMount } from './server/debugger-host';
|
|
4
|
+
export { authAllowed, authDenied, bearerAuth, defaultAuthorize, type AuthDecision, type AuthorizedAccess, type Authorize, type BearerTokens, type RouteAccess, type RouteInfo, } from './server/auth';
|
|
7
5
|
export interface RunningServer {
|
|
8
6
|
port: number;
|
|
9
7
|
stop: () => void;
|
|
10
8
|
}
|
|
11
9
|
export type LoomsFetchResult = Response | null;
|
|
12
|
-
export declare function isLoomsApiPath(path: string): boolean;
|
|
13
|
-
export declare function createFetchHandler(options: FetchHandlerOptions): (req: Request) => Promise<Response | null>;
|
|
14
10
|
export declare function serveHttp(fetchHandler: (req: Request) => Promise<Response | null>, options?: {
|
|
15
11
|
port?: number;
|
|
16
12
|
hostname?: string;
|
package/dist/runtime/server.js
CHANGED
|
@@ -1,208 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
const StartRunBodySchema = Schema.Struct({
|
|
6
|
-
kind: Schema.optional(Schema.String),
|
|
7
|
-
definitionName: Schema.optional(Schema.String),
|
|
8
|
-
name: Schema.optional(Schema.String),
|
|
9
|
-
input: Schema.optional(JsonValueSchema),
|
|
10
|
-
runId: Schema.optional(Schema.String),
|
|
11
|
-
idempotencyKey: Schema.optional(Schema.String),
|
|
12
|
-
});
|
|
13
|
-
const SignalBodySchema = Schema.Struct({
|
|
14
|
-
events: Schema.optional(Schema.Array(Schema.Struct({
|
|
15
|
-
type: Schema.String,
|
|
16
|
-
payload: JsonValueSchema,
|
|
17
|
-
threadId: Schema.optional(Schema.NullOr(Schema.String)),
|
|
18
|
-
}))),
|
|
19
|
-
type: Schema.optional(Schema.String),
|
|
20
|
-
payload: Schema.optional(JsonValueSchema),
|
|
21
|
-
threadId: Schema.optional(Schema.NullOr(Schema.String)),
|
|
22
|
-
idempotencyKey: Schema.optional(Schema.String),
|
|
23
|
-
});
|
|
24
|
-
function extractIdempotencyKey(req, bodyKey) {
|
|
25
|
-
return (bodyKey ??
|
|
26
|
-
req.headers.get('idempotency-key') ??
|
|
27
|
-
req.headers.get('x-idempotency-key') ??
|
|
28
|
-
undefined);
|
|
29
|
-
}
|
|
30
|
-
function run(effect, store) {
|
|
31
|
-
return Effect.runPromise(Effect.provideService(effect, EventStoreTag, store));
|
|
32
|
-
}
|
|
33
|
-
function wantsRunStream(req, url) {
|
|
34
|
-
const accept = req.headers.get('accept') ?? '';
|
|
35
|
-
return url.searchParams.get('stream') === 'true' || accept.includes('text/event-stream');
|
|
36
|
-
}
|
|
37
|
-
function streamStartRun(req, runtime, store, args) {
|
|
38
|
-
return createEventStreamResponse({
|
|
39
|
-
signal: req.signal,
|
|
40
|
-
request: req,
|
|
41
|
-
store,
|
|
42
|
-
runId: args.runId,
|
|
43
|
-
fromSeq: 1,
|
|
44
|
-
onStart: async (write, close) => {
|
|
45
|
-
try {
|
|
46
|
-
const result = await run(runtime.startRun({
|
|
47
|
-
kind: args.kind,
|
|
48
|
-
definitionName: args.definitionName,
|
|
49
|
-
input: args.input,
|
|
50
|
-
runId: args.runId,
|
|
51
|
-
idempotencyKey: args.idempotencyKey,
|
|
52
|
-
}), store);
|
|
53
|
-
write(`event: done\ndata: ${JSON.stringify(result)}\n\n`);
|
|
54
|
-
}
|
|
55
|
-
catch (cause) {
|
|
56
|
-
const error = cause instanceof Error ? cause.message : String(cause);
|
|
57
|
-
write(`event: error\ndata: ${JSON.stringify({ error })}\n\n`);
|
|
58
|
-
}
|
|
59
|
-
finally {
|
|
60
|
-
close();
|
|
61
|
-
}
|
|
62
|
-
},
|
|
63
|
-
});
|
|
64
|
-
}
|
|
65
|
-
async function readJson(req) {
|
|
66
|
-
try {
|
|
67
|
-
const value = await req.json();
|
|
68
|
-
return Schema.decodeUnknownSync(JsonValueSchema)(value);
|
|
69
|
-
}
|
|
70
|
-
catch {
|
|
71
|
-
return {};
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
export function isLoomsApiPath(path) {
|
|
75
|
-
return path === '/health' || path.startsWith('/api/events') || path.startsWith('/runs');
|
|
76
|
-
}
|
|
77
|
-
function toErrorResponse(err) {
|
|
78
|
-
if (err instanceof InvalidInputError || err instanceof InvalidEventError) {
|
|
79
|
-
return Response.json({ error: err.message, issues: err.issues }, { status: 400 });
|
|
80
|
-
}
|
|
81
|
-
return Response.json({ error: err.message }, { status: 500 });
|
|
82
|
-
}
|
|
83
|
-
export function createFetchHandler(options) {
|
|
84
|
-
const { runtime, store } = options;
|
|
85
|
-
return async (req) => {
|
|
86
|
-
const url = new URL(req.url);
|
|
87
|
-
const path = url.pathname;
|
|
88
|
-
if (!isLoomsApiPath(path)) {
|
|
89
|
-
return null;
|
|
90
|
-
}
|
|
91
|
-
const accept = req.headers.get('accept') ?? '';
|
|
92
|
-
if (req.method === 'GET' && accept.includes('text/html') && !path.startsWith('/api/')) {
|
|
93
|
-
return null;
|
|
94
|
-
}
|
|
95
|
-
if (path === '/health') {
|
|
96
|
-
return Response.json({ ok: true });
|
|
97
|
-
}
|
|
98
|
-
if (path.startsWith('/api/events')) {
|
|
99
|
-
return handleEventsApi(req, runtime, store);
|
|
100
|
-
}
|
|
101
|
-
try {
|
|
102
|
-
if (req.method === 'GET' && path === '/runs') {
|
|
103
|
-
const runIds = await run(runtime.listRuns(), store);
|
|
104
|
-
return Response.json({ runIds });
|
|
105
|
-
}
|
|
106
|
-
if (req.method === 'POST' && path === '/runs') {
|
|
107
|
-
const body = Schema.decodeUnknownSync(StartRunBodySchema)(await readJson(req));
|
|
108
|
-
const definitionName = body.definitionName ?? body.name;
|
|
109
|
-
const kind = body.kind;
|
|
110
|
-
if (!definitionName || !kind) {
|
|
111
|
-
return Response.json({ error: 'kind and definitionName required' }, { status: 400 });
|
|
112
|
-
}
|
|
113
|
-
const runId = body.runId ?? createRunId();
|
|
114
|
-
const idempotencyKey = extractIdempotencyKey(req, body.idempotencyKey);
|
|
115
|
-
if (wantsRunStream(req, url)) {
|
|
116
|
-
return streamStartRun(req, runtime, store, {
|
|
117
|
-
kind,
|
|
118
|
-
definitionName,
|
|
119
|
-
input: body.input ?? null,
|
|
120
|
-
runId,
|
|
121
|
-
idempotencyKey,
|
|
122
|
-
});
|
|
123
|
-
}
|
|
124
|
-
const result = await run(runtime.startRun({
|
|
125
|
-
kind,
|
|
126
|
-
definitionName,
|
|
127
|
-
input: body.input ?? null,
|
|
128
|
-
runId,
|
|
129
|
-
idempotencyKey,
|
|
130
|
-
}), store);
|
|
131
|
-
return Response.json(result);
|
|
132
|
-
}
|
|
133
|
-
const replayMatch = path.match(/^\/runs\/([^/]+)\/replay$/);
|
|
134
|
-
if (req.method === 'GET' && replayMatch) {
|
|
135
|
-
const runId = decodeURIComponent(replayMatch[1]);
|
|
136
|
-
const seq = Number(url.searchParams.get('seq') ?? '0');
|
|
137
|
-
const step = await run(runtime.replayTo(runId, seq), store);
|
|
138
|
-
return Response.json({ runId, step });
|
|
139
|
-
}
|
|
140
|
-
const projectionMatch = path.match(/^\/runs\/([^/]+)\/projections\/([^/]+)$/);
|
|
141
|
-
if (req.method === 'GET' && projectionMatch) {
|
|
142
|
-
const runId = decodeURIComponent(projectionMatch[1]);
|
|
143
|
-
const name = decodeURIComponent(projectionMatch[2]);
|
|
144
|
-
const definition = runtime.registry.projections.get(name);
|
|
145
|
-
if (!definition) {
|
|
146
|
-
return Response.json({ error: `Unknown projection: ${name}` }, { status: 404 });
|
|
147
|
-
}
|
|
148
|
-
const value = await run(runtime.project(runId, definition), store);
|
|
149
|
-
return Response.json({ runId, name, value });
|
|
150
|
-
}
|
|
151
|
-
const eventsMatch = path.match(/^\/runs\/([^/]+)\/events$/);
|
|
152
|
-
if (req.method === 'POST' && eventsMatch) {
|
|
153
|
-
const runId = decodeURIComponent(eventsMatch[1]);
|
|
154
|
-
const body = Schema.decodeUnknownSync(SignalBodySchema)(await readJson(req));
|
|
155
|
-
const events = body.events ??
|
|
156
|
-
(body.type
|
|
157
|
-
? [
|
|
158
|
-
{
|
|
159
|
-
type: body.type,
|
|
160
|
-
payload: body.payload ?? null,
|
|
161
|
-
threadId: body.threadId,
|
|
162
|
-
},
|
|
163
|
-
]
|
|
164
|
-
: []);
|
|
165
|
-
const idempotencyKey = extractIdempotencyKey(req, body.idempotencyKey);
|
|
166
|
-
const state = await run(runtime.signal(runId, events, { idempotencyKey }), store);
|
|
167
|
-
return Response.json({ runId, state });
|
|
168
|
-
}
|
|
169
|
-
if (req.method === 'GET' && eventsMatch) {
|
|
170
|
-
const runId = decodeURIComponent(eventsMatch[1]);
|
|
171
|
-
const fromSeq = url.searchParams.get('fromSeq')
|
|
172
|
-
? Number(url.searchParams.get('fromSeq'))
|
|
173
|
-
: undefined;
|
|
174
|
-
const limit = url.searchParams.get('limit')
|
|
175
|
-
? Number(url.searchParams.get('limit'))
|
|
176
|
-
: undefined;
|
|
177
|
-
const events = await run(runtime.getEvents(runId, { fromSeq, limit }), store);
|
|
178
|
-
return Response.json({ runId, events });
|
|
179
|
-
}
|
|
180
|
-
const threadsMatch = path.match(/^\/runs\/([^/]+)\/(threads)$/);
|
|
181
|
-
if (req.method === 'GET' && threadsMatch) {
|
|
182
|
-
const runId = decodeURIComponent(threadsMatch[1]);
|
|
183
|
-
const state = await run(runtime.getRun(runId), store);
|
|
184
|
-
return Response.json({ runId, threads: state.threads });
|
|
185
|
-
}
|
|
186
|
-
const wakeMatch = path.match(/^\/runs\/([^/]+)\/wake$/);
|
|
187
|
-
if (req.method === 'POST' && wakeMatch) {
|
|
188
|
-
const runId = decodeURIComponent(wakeMatch[1]);
|
|
189
|
-
const state = await run(runtime.wake(runId), store);
|
|
190
|
-
return Response.json({ runId, state });
|
|
191
|
-
}
|
|
192
|
-
const runMatch = path.match(/^\/runs\/([^/]+)$/);
|
|
193
|
-
if (req.method === 'GET' && runMatch) {
|
|
194
|
-
const runId = decodeURIComponent(runMatch[1]);
|
|
195
|
-
const state = await run(runtime.getRun(runId), store);
|
|
196
|
-
return Response.json({ runId, state });
|
|
197
|
-
}
|
|
198
|
-
return new Response('Not Found', { status: 404 });
|
|
199
|
-
}
|
|
200
|
-
catch (rawErr) {
|
|
201
|
-
const err = rawErr instanceof Error ? rawErr : new Error(String(rawErr));
|
|
202
|
-
return toErrorResponse(err);
|
|
203
|
-
}
|
|
204
|
-
};
|
|
205
|
-
}
|
|
1
|
+
export { createFetchHandler, handlesPath } from './server/fetch-handler.js';
|
|
2
|
+
export { isLoomsApiPath } from './server/http.js';
|
|
3
|
+
export { mountDebugger } from './server/debugger-host.js';
|
|
4
|
+
export { authAllowed, authDenied, bearerAuth, defaultAuthorize, } from './server/auth.js';
|
|
206
5
|
export function serveHttp(fetchHandler, options) {
|
|
207
6
|
const port = options?.port ?? 8787;
|
|
208
7
|
const hostname = options?.hostname ?? '0.0.0.0';
|
|
@@ -210,13 +9,9 @@ export function serveHttp(fetchHandler, options) {
|
|
|
210
9
|
port,
|
|
211
10
|
hostname,
|
|
212
11
|
idleTimeout: 0,
|
|
213
|
-
|
|
12
|
+
fetch(req, srv) {
|
|
214
13
|
srv.timeout(req, 0);
|
|
215
|
-
|
|
216
|
-
if (result === null) {
|
|
217
|
-
return new Response('Not Found', { status: 404 });
|
|
218
|
-
}
|
|
219
|
-
return result;
|
|
14
|
+
return fetchHandler(req).then((result) => result ?? new Response('Not Found', { status: 404 }));
|
|
220
15
|
},
|
|
221
16
|
});
|
|
222
17
|
return {
|
package/dist/runtime/sse.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { Effect } from 'effect';
|
|
1
2
|
import { type EventStore } from '@swirls/looms/core';
|
|
2
3
|
export interface EventStreamOptions {
|
|
3
4
|
signal: AbortSignal;
|
|
@@ -5,7 +6,7 @@ export interface EventStreamOptions {
|
|
|
5
6
|
runId: string;
|
|
6
7
|
fromSeq?: number;
|
|
7
8
|
heartbeatMs?: number;
|
|
8
|
-
onStart?: (write: (text: string) => void, close: () => void) => Promise<void> | void
|
|
9
|
+
onStart?: (write: (text: string) => void, close: () => void) => void | Promise<void> | Effect.Effect<void>;
|
|
9
10
|
request?: Request;
|
|
10
11
|
}
|
|
11
12
|
/**
|
package/dist/runtime/sse.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { Effect, Fiber, Stream } from 'effect';
|
|
2
|
-
import { encodeLoomsEvent } from '@swirls/looms/core';
|
|
1
|
+
import { Effect, Fiber, Schedule, Stream } from 'effect';
|
|
2
|
+
import { encodeLoomsEvent, stringifyJson } from '@swirls/looms/core';
|
|
3
3
|
function disableBunSocketTimeout(request) {
|
|
4
4
|
if (!request) {
|
|
5
5
|
return;
|
|
@@ -24,19 +24,23 @@ export function createEventStreamResponse(options) {
|
|
|
24
24
|
const { signal, store, runId, fromSeq = 1, heartbeatMs = 3_000, onStart, request } = options;
|
|
25
25
|
disableBunSocketTimeout(request);
|
|
26
26
|
let closed = false;
|
|
27
|
-
let
|
|
28
|
-
let
|
|
27
|
+
let heartbeatFiber;
|
|
28
|
+
let startFiber;
|
|
29
|
+
let subscriptionFiber;
|
|
29
30
|
let streamController;
|
|
30
31
|
const close = () => {
|
|
31
32
|
if (closed) {
|
|
32
33
|
return;
|
|
33
34
|
}
|
|
34
35
|
closed = true;
|
|
35
|
-
if (
|
|
36
|
-
|
|
36
|
+
if (heartbeatFiber) {
|
|
37
|
+
Effect.runFork(Fiber.interrupt(heartbeatFiber));
|
|
37
38
|
}
|
|
38
|
-
if (
|
|
39
|
-
Effect.runFork(Fiber.interrupt(
|
|
39
|
+
if (startFiber) {
|
|
40
|
+
Effect.runFork(Fiber.interrupt(startFiber));
|
|
41
|
+
}
|
|
42
|
+
if (subscriptionFiber) {
|
|
43
|
+
Effect.runFork(Fiber.interrupt(subscriptionFiber));
|
|
40
44
|
}
|
|
41
45
|
try {
|
|
42
46
|
streamController?.close();
|
|
@@ -60,19 +64,21 @@ export function createEventStreamResponse(options) {
|
|
|
60
64
|
close();
|
|
61
65
|
}
|
|
62
66
|
};
|
|
63
|
-
|
|
64
|
-
write(`id: ${event.seq}\ndata: ${
|
|
67
|
+
subscriptionFiber = Effect.runFork(store.subscribe(runId, { fromSeq }).pipe(Stream.tap((event) => Effect.sync(() => {
|
|
68
|
+
write(`id: ${event.seq}\ndata: ${stringifyJson({ batch: [encodeLoomsEvent(event)] })}\n\n`);
|
|
65
69
|
})), Stream.runDrain));
|
|
66
70
|
// Send an immediate keepalive so bytes and headers flush over the wire immediately,
|
|
67
71
|
// preventing dev server proxies and Bun.serve from aborting quiet connections.
|
|
68
72
|
write(': keepalive\n\n');
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
73
|
+
heartbeatFiber = Effect.runFork(Effect.repeat(Effect.sleep(heartbeatMs).pipe(Effect.andThen(Effect.sync(() => write(': keepalive\n\n')))), Schedule.forever).pipe(Effect.asVoid));
|
|
74
|
+
const startResult = onStart?.(write, close);
|
|
75
|
+
const startEffect = Effect.isEffect(startResult)
|
|
76
|
+
? startResult
|
|
77
|
+
: startResult
|
|
78
|
+
? Effect.promise(() => startResult)
|
|
79
|
+
: undefined;
|
|
80
|
+
if (startEffect) {
|
|
81
|
+
startFiber = Effect.runFork(startEffect);
|
|
76
82
|
}
|
|
77
83
|
if (signal.aborted) {
|
|
78
84
|
close();
|
|
@@ -90,6 +96,7 @@ export function createEventStreamResponse(options) {
|
|
|
90
96
|
'content-type': 'text/event-stream',
|
|
91
97
|
'cache-control': 'no-cache, no-transform',
|
|
92
98
|
connection: 'keep-alive',
|
|
99
|
+
vary: 'accept',
|
|
93
100
|
'x-accel-buffering': 'no',
|
|
94
101
|
},
|
|
95
102
|
});
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import type { Effect } from 'effect';
|
|
2
|
+
import type { AnyRuntimeModule, ComposedRegistry, EventEnvelope, EventInput, WorkerCallbackInput, EventStore, EventStoreAppendError, EventStoreError, EventStoreFencedError, EventStoreTag, EventStoreTrimCoverage, EventStoreTruncationError, JsonValue, ProjectionDefinition, ReplayStep, RunState, RunSummary, SnapshotStore, SnapshotStoreError } from '@swirls/looms/core';
|
|
3
|
+
import type { EventStoreTrimmedError } from '@swirls/looms/core';
|
|
4
|
+
import type { DuplicateEffectDispatchError, MaxWakeIterationsError } from './errors';
|
|
5
|
+
import type { RuntimeObserver } from './observer';
|
|
6
|
+
import type { WakeScheduler } from './wake-scheduler';
|
|
7
|
+
export type WakeError = DuplicateEffectDispatchError | MaxWakeIterationsError | EventStoreTruncationError | EventStoreTrimmedError | EventStoreFencedError | EventStoreError | Error;
|
|
8
|
+
export interface CreateRuntimeOptions<TModules extends readonly AnyRuntimeModule[] = readonly AnyRuntimeModule[]> {
|
|
9
|
+
readonly modules: TModules;
|
|
10
|
+
readonly store?: EventStore;
|
|
11
|
+
/**
|
|
12
|
+
* Durable events between mid-wake snapshots. Default 200; `0` disables.
|
|
13
|
+
* A snapshot is always taken when a wake parks, so this only bounds replay
|
|
14
|
+
* after a crash inside one long wake.
|
|
15
|
+
*/
|
|
16
|
+
readonly snapshotEvery?: number;
|
|
17
|
+
readonly maxWakeIterations?: number;
|
|
18
|
+
/** Maximum active and queued mutation operations per run. Default 256. */
|
|
19
|
+
readonly maxPendingRunOperations?: number;
|
|
20
|
+
/** Defaults to the EventStore's attached store or an in-memory SnapshotStore. */
|
|
21
|
+
readonly snapshotStore?: SnapshotStore;
|
|
22
|
+
/** Trim the event log behind the oldest kept snapshot. Also sets how many snapshots to keep. */
|
|
23
|
+
readonly trimAfterSnapshot?: {
|
|
24
|
+
keepSnapshots: number;
|
|
25
|
+
/**
|
|
26
|
+
* Required proof that history remains rebuildable. Trimming is rejected
|
|
27
|
+
* unless an archive covers the cut or every configured projector checkpoint does.
|
|
28
|
+
*/
|
|
29
|
+
coverage?: (runId: string) => EventStoreTrimCoverage | Promise<EventStoreTrimCoverage>;
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* Cursors to keep in process between calls. Default `0`: every call starts
|
|
33
|
+
* from the store (snapshot + delta). A cached cursor is validated against
|
|
34
|
+
* `store.tail` before use.
|
|
35
|
+
*/
|
|
36
|
+
readonly runCacheSize?: number;
|
|
37
|
+
/**
|
|
38
|
+
* Pluggable wake scheduler for timer waits.
|
|
39
|
+
* Defaults to an in-process Effect fiber scheduler.
|
|
40
|
+
*/
|
|
41
|
+
readonly scheduler?: WakeScheduler;
|
|
42
|
+
readonly worker?: EffectWorker;
|
|
43
|
+
/** Failure-isolated, framework-neutral operational event sink. */
|
|
44
|
+
readonly observer?: RuntimeObserver;
|
|
45
|
+
}
|
|
46
|
+
export interface EffectWorkerTask {
|
|
47
|
+
readonly runId: string;
|
|
48
|
+
readonly effectId: string;
|
|
49
|
+
readonly attempt: number;
|
|
50
|
+
readonly type: string;
|
|
51
|
+
readonly input: JsonValue;
|
|
52
|
+
}
|
|
53
|
+
export interface EffectWorker {
|
|
54
|
+
/** Effect types this worker claims, even when a local handler exists. */
|
|
55
|
+
readonly handles?: ReadonlyArray<string> | ((type: string) => boolean);
|
|
56
|
+
/** Per-task claim, checked with attempt 0 before the runtime assigns an attempt. */
|
|
57
|
+
readonly canDispatch?: (task: EffectWorkerTask) => boolean;
|
|
58
|
+
dispatch(task: EffectWorkerTask): void | Promise<void>;
|
|
59
|
+
cancel?(task: EffectWorkerTask): void | Promise<void>;
|
|
60
|
+
}
|
|
61
|
+
export interface StartRunArgs {
|
|
62
|
+
kind: string;
|
|
63
|
+
definitionName: string;
|
|
64
|
+
definitionVersion?: string;
|
|
65
|
+
input?: JsonValue;
|
|
66
|
+
runId?: string;
|
|
67
|
+
threadId?: string;
|
|
68
|
+
idempotencyKey?: string;
|
|
69
|
+
}
|
|
70
|
+
export interface StartResult {
|
|
71
|
+
runId: string;
|
|
72
|
+
threadId: string;
|
|
73
|
+
state: RunState;
|
|
74
|
+
}
|
|
75
|
+
export interface RunOperationalStatus {
|
|
76
|
+
readonly runId: string;
|
|
77
|
+
readonly state: RunState;
|
|
78
|
+
readonly head: number;
|
|
79
|
+
readonly tail: number;
|
|
80
|
+
readonly effects: ReadonlyArray<{
|
|
81
|
+
readonly effectId: string;
|
|
82
|
+
readonly threadId: string;
|
|
83
|
+
readonly type: string;
|
|
84
|
+
readonly attempt: number;
|
|
85
|
+
readonly status: string;
|
|
86
|
+
readonly deadlineAt: number | null;
|
|
87
|
+
readonly lastError: string | null;
|
|
88
|
+
}>;
|
|
89
|
+
}
|
|
90
|
+
export interface LoomsRuntime<TModules extends readonly AnyRuntimeModule[] = readonly AnyRuntimeModule[]> {
|
|
91
|
+
readonly modules: TModules;
|
|
92
|
+
readonly registry: ComposedRegistry;
|
|
93
|
+
startRun(args: StartRunArgs): Effect.Effect<StartResult, Error | EventStoreAppendError | EventStoreError, EventStoreTag>;
|
|
94
|
+
signal(runId: string, events: ReadonlyArray<EventInput>, options?: {
|
|
95
|
+
idempotencyKey?: string;
|
|
96
|
+
}): Effect.Effect<RunState, Error | EventStoreAppendError | EventStoreError, EventStoreTag>;
|
|
97
|
+
wake(runId: string): Effect.Effect<RunState, WakeError, EventStoreTag>;
|
|
98
|
+
getRun(runId: string): Effect.Effect<RunState, EventStoreTruncationError | EventStoreTrimmedError | EventStoreError, EventStoreTag>;
|
|
99
|
+
getEvents(runId: string, options?: {
|
|
100
|
+
fromSeq?: number;
|
|
101
|
+
limit?: number;
|
|
102
|
+
}): Effect.Effect<EventEnvelope[], EventStoreError, EventStoreTag>;
|
|
103
|
+
project<S>(runId: string, definition: ProjectionDefinition<S>): Effect.Effect<S, EventStoreError, EventStoreTag>;
|
|
104
|
+
replayTo(runId: string, seq: number): Effect.Effect<ReplayStep | null, EventStoreError, EventStoreTag>;
|
|
105
|
+
inspectRun(runId: string): Effect.Effect<RunOperationalStatus, EventStoreError | EventStoreTrimmedError | EventStoreTruncationError, EventStoreTag>;
|
|
106
|
+
retryEffect(runId: string, effectId: string): Effect.Effect<RunState, Error | EventStoreAppendError | EventStoreError, EventStoreTag>;
|
|
107
|
+
cancel(runId: string, threadId?: string): Effect.Effect<RunState, Error | EventStoreAppendError | EventStoreError, EventStoreTag>;
|
|
108
|
+
/** Applies a worker's report about one effect attempt; stale or foreign reports are ignored. */
|
|
109
|
+
workerCallback(runId: string, input: WorkerCallbackInput): Effect.Effect<RunState, Error | EventStoreAppendError | EventStoreError, EventStoreTag>;
|
|
110
|
+
readonly listRuns: Effect.Effect<string[], EventStoreError, EventStoreTag>;
|
|
111
|
+
/** Indexed run rows. Folds a run only when its header has not been written yet. */
|
|
112
|
+
readonly listRunSummaries: Effect.Effect<readonly RunSummary[], EventStoreError | EventStoreTrimmedError | EventStoreTruncationError | SnapshotStoreError, EventStoreTag>;
|
|
113
|
+
readonly rescanTimers: Effect.Effect<number, EventStoreTruncationError | EventStoreTrimmedError | EventStoreError, EventStoreTag>;
|
|
114
|
+
/** Reschedule every durable timer and effect deadline after restart or repair. */
|
|
115
|
+
readonly recoverDeadlines: Effect.Effect<number, EventStoreTruncationError | EventStoreTrimmedError | EventStoreError, EventStoreTag>;
|
|
116
|
+
readonly dispose: Effect.Effect<void>;
|
|
117
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|