@swirls/looms 0.0.1
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/LICENSE +190 -0
- package/README.md +9 -0
- package/dist/actor/cell.d.ts +27 -0
- package/dist/actor/cell.js +41 -0
- package/dist/actor/host.d.ts +27 -0
- package/dist/actor/host.js +82 -0
- package/dist/actor/index.d.ts +3 -0
- package/dist/actor/index.js +3 -0
- package/dist/actor/routing.d.ts +15 -0
- package/dist/actor/routing.js +80 -0
- package/dist/agent/definitions-store.d.ts +11 -0
- package/dist/agent/definitions-store.js +10 -0
- package/dist/agent/definitions.d.ts +152 -0
- package/dist/agent/definitions.js +74 -0
- package/dist/agent/effects.d.ts +425 -0
- package/dist/agent/effects.js +318 -0
- package/dist/agent/events.d.ts +170 -0
- package/dist/agent/events.js +61 -0
- package/dist/agent/index.d.ts +13 -0
- package/dist/agent/index.js +13 -0
- package/dist/agent/llm.d.ts +35 -0
- package/dist/agent/llm.js +48 -0
- package/dist/agent/module.d.ts +1037 -0
- package/dist/agent/module.js +27 -0
- package/dist/agent/projections.d.ts +17 -0
- package/dist/agent/projections.js +81 -0
- package/dist/agent/scope.d.ts +171 -0
- package/dist/agent/scope.js +7 -0
- package/dist/agent/signals.d.ts +17 -0
- package/dist/agent/signals.js +19 -0
- package/dist/agent/stop-when.d.ts +12 -0
- package/dist/agent/stop-when.js +6 -0
- package/dist/agent/threads.d.ts +34 -0
- package/dist/agent/threads.js +407 -0
- package/dist/agent/tool-schema.d.ts +21 -0
- package/dist/agent/tool-schema.js +67 -0
- package/dist/agent/types.d.ts +87 -0
- package/dist/agent/types.js +32 -0
- package/dist/ai-vercel/index.d.ts +2 -0
- package/dist/ai-vercel/index.js +2 -0
- package/dist/ai-vercel/messages.d.ts +9 -0
- package/dist/ai-vercel/messages.js +68 -0
- package/dist/ai-vercel/vercel-llm.d.ts +8 -0
- package/dist/ai-vercel/vercel-llm.js +98 -0
- package/dist/approval/effects.d.ts +133 -0
- package/dist/approval/effects.js +13 -0
- package/dist/approval/events.d.ts +52 -0
- package/dist/approval/events.js +28 -0
- package/dist/approval/index.d.ts +6 -0
- package/dist/approval/index.js +6 -0
- package/dist/approval/module.d.ts +342 -0
- package/dist/approval/module.js +10 -0
- package/dist/approval/projections.d.ts +42 -0
- package/dist/approval/projections.js +57 -0
- package/dist/approval/scope.d.ts +45 -0
- package/dist/approval/scope.js +7 -0
- package/dist/approval/signals.d.ts +19 -0
- package/dist/approval/signals.js +34 -0
- package/dist/cli/cli.d.ts +2 -0
- package/dist/cli/cli.js +6 -0
- package/dist/cli/commands.d.ts +13 -0
- package/dist/cli/commands.js +133 -0
- package/dist/cli/index.d.ts +1 -0
- package/dist/cli/index.js +1 -0
- package/dist/client/client.d.ts +69 -0
- package/dist/client/client.js +179 -0
- package/dist/client/index.d.ts +2 -0
- package/dist/client/index.js +2 -0
- package/dist/client/sse.d.ts +10 -0
- package/dist/client/sse.js +103 -0
- package/dist/cloudflare/alarm-scheduler.d.ts +9 -0
- package/dist/cloudflare/alarm-scheduler.js +13 -0
- package/dist/cloudflare/durable-object.d.ts +24 -0
- package/dist/cloudflare/durable-object.js +43 -0
- package/dist/cloudflare/event-store.d.ts +10 -0
- package/dist/cloudflare/event-store.js +18 -0
- package/dist/cloudflare/index.d.ts +4 -0
- package/dist/cloudflare/index.js +4 -0
- package/dist/cloudflare/router.d.ts +12 -0
- package/dist/cloudflare/router.js +26 -0
- package/dist/core/bun-sqlite.d.ts +13 -0
- package/dist/core/bun-sqlite.js +19 -0
- package/dist/core/catalog.d.ts +49 -0
- package/dist/core/catalog.js +39 -0
- package/dist/core/coalesce.d.ts +21 -0
- package/dist/core/coalesce.js +178 -0
- package/dist/core/compose.d.ts +59 -0
- package/dist/core/compose.js +97 -0
- package/dist/core/cursor.d.ts +16 -0
- package/dist/core/cursor.js +46 -0
- package/dist/core/effects.d.ts +110 -0
- package/dist/core/effects.js +99 -0
- package/dist/core/envelope.d.ts +79 -0
- package/dist/core/envelope.js +68 -0
- package/dist/core/event-codec.d.ts +8 -0
- package/dist/core/event-codec.js +82 -0
- package/dist/core/event-index.d.ts +28 -0
- package/dist/core/event-index.js +86 -0
- package/dist/core/fold.d.ts +19 -0
- package/dist/core/fold.js +362 -0
- package/dist/core/ids.d.ts +11 -0
- package/dist/core/ids.js +47 -0
- package/dist/core/index.d.ts +26 -0
- package/dist/core/index.js +25 -0
- package/dist/core/match.d.ts +8 -0
- package/dist/core/match.js +58 -0
- package/dist/core/module-scope.d.ts +99 -0
- package/dist/core/module-scope.js +72 -0
- package/dist/core/module.d.ts +74 -0
- package/dist/core/module.js +3 -0
- package/dist/core/projection.d.ts +83 -0
- package/dist/core/projection.js +241 -0
- package/dist/core/protocol.d.ts +99 -0
- package/dist/core/protocol.js +101 -0
- package/dist/core/replay.d.ts +13 -0
- package/dist/core/replay.js +34 -0
- package/dist/core/schema.d.ts +35 -0
- package/dist/core/schema.js +130 -0
- package/dist/core/serialize.d.ts +14 -0
- package/dist/core/serialize.js +42 -0
- package/dist/core/snapshot-store.d.ts +39 -0
- package/dist/core/snapshot-store.js +53 -0
- package/dist/core/snapshots.d.ts +26 -0
- package/dist/core/snapshots.js +100 -0
- package/dist/core/sqlite-store.d.ts +31 -0
- package/dist/core/sqlite-store.js +328 -0
- package/dist/core/state.d.ts +43 -0
- package/dist/core/state.js +31 -0
- package/dist/core/store.d.ts +146 -0
- package/dist/core/store.js +229 -0
- package/dist/core/thread.d.ts +41 -0
- package/dist/core/thread.js +13 -0
- package/dist/core/types.d.ts +27 -0
- package/dist/core/types.js +42 -0
- package/dist/core/validate-event.d.ts +19 -0
- package/dist/core/validate-event.js +42 -0
- package/dist/debugger/components/debugger-split.d.ts +23 -0
- package/dist/debugger/components/debugger-split.js +25 -0
- package/dist/debugger/components/event-inspector.d.ts +5 -0
- package/dist/debugger/components/event-inspector.js +38 -0
- package/dist/debugger/components/event-stream.d.ts +13 -0
- package/dist/debugger/components/event-stream.js +119 -0
- package/dist/debugger/components/json-view.d.ts +5 -0
- package/dist/debugger/components/json-view.js +5 -0
- package/dist/debugger/components/run-tree.d.ts +14 -0
- package/dist/debugger/components/run-tree.js +38 -0
- package/dist/debugger/components/status-dot.d.ts +3 -0
- package/dist/debugger/components/status-dot.js +5 -0
- package/dist/debugger/contracts.d.ts +22 -0
- package/dist/debugger/contracts.js +1 -0
- package/dist/debugger/events/catalog.d.ts +6 -0
- package/dist/debugger/events/catalog.js +27 -0
- package/dist/debugger/events/domain-summarize.d.ts +3 -0
- package/dist/debugger/events/domain-summarize.js +107 -0
- package/dist/debugger/events/family.d.ts +8 -0
- package/dist/debugger/events/family.js +44 -0
- package/dist/debugger/events/protocol-summarize.d.ts +3 -0
- package/dist/debugger/events/protocol-summarize.js +70 -0
- package/dist/debugger/index.d.ts +17 -0
- package/dist/debugger/index.js +16 -0
- package/dist/debugger/lib/cn.d.ts +5 -0
- package/dist/debugger/lib/cn.js +17 -0
- package/dist/debugger/lib/event-counts.d.ts +5 -0
- package/dist/debugger/lib/event-counts.js +23 -0
- package/dist/debugger/lib/event-meta.d.ts +8 -0
- package/dist/debugger/lib/event-meta.js +20 -0
- package/dist/debugger/lib/project-run.d.ts +9 -0
- package/dist/debugger/lib/project-run.js +14 -0
- package/dist/debugger/lib/status.d.ts +3 -0
- package/dist/debugger/lib/status.js +31 -0
- package/dist/debugger/lib/visible-events.d.ts +8 -0
- package/dist/debugger/lib/visible-events.js +21 -0
- package/dist/debugger/styles.css +71 -0
- package/dist/debugger/ui/checkbox.d.ts +4 -0
- package/dist/debugger/ui/checkbox.js +9 -0
- package/dist/debugger/ui/input.d.ts +3 -0
- package/dist/debugger/ui/input.js +7 -0
- package/dist/debugger/ui/scroll-area.d.ts +9 -0
- package/dist/debugger/ui/scroll-area.js +10 -0
- package/dist/debugger/ui/tabs.d.ts +7 -0
- package/dist/debugger/ui/tabs.js +17 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/projectors/index-model.d.ts +56 -0
- package/dist/projectors/index-model.js +117 -0
- package/dist/projectors/index.d.ts +4 -0
- package/dist/projectors/index.js +3 -0
- package/dist/projectors/memory.d.ts +5 -0
- package/dist/projectors/memory.js +77 -0
- package/dist/projectors/postgres.d.ts +9 -0
- package/dist/projectors/postgres.js +108 -0
- package/dist/projectors/projector.d.ts +11 -0
- package/dist/projectors/projector.js +1 -0
- package/dist/projectors/schema.d.ts +5 -0
- package/dist/projectors/schema.js +43 -0
- package/dist/projectors/sqlite.d.ts +9 -0
- package/dist/projectors/sqlite.js +103 -0
- package/dist/projectors/with-projectors.d.ts +17 -0
- package/dist/projectors/with-projectors.js +55 -0
- package/dist/react/derived.d.ts +19 -0
- package/dist/react/derived.js +50 -0
- package/dist/react/index.d.ts +6 -0
- package/dist/react/index.js +5 -0
- package/dist/react/materialize.d.ts +3 -0
- package/dist/react/materialize.js +86 -0
- package/dist/react/notify.d.ts +18 -0
- package/dist/react/notify.js +80 -0
- package/dist/react/react.d.ts +61 -0
- package/dist/react/react.js +187 -0
- package/dist/react/register.d.ts +31 -0
- package/dist/react/register.js +1 -0
- package/dist/react/sse.d.ts +1 -0
- package/dist/react/sse.js +1 -0
- package/dist/react/store.d.ts +56 -0
- package/dist/react/store.js +340 -0
- package/dist/react/tables.d.ts +21 -0
- package/dist/react/tables.js +6 -0
- package/dist/runtime/dispatch-effect.d.ts +3 -0
- package/dist/runtime/dispatch-effect.js +113 -0
- package/dist/runtime/events-api.d.ts +4 -0
- package/dist/runtime/events-api.js +84 -0
- package/dist/runtime/index.d.ts +5 -0
- package/dist/runtime/index.js +5 -0
- package/dist/runtime/looms.d.ts +74 -0
- package/dist/runtime/looms.js +109 -0
- package/dist/runtime/runtime-helpers.d.ts +51 -0
- package/dist/runtime/runtime-helpers.js +230 -0
- package/dist/runtime/runtime.d.ts +91 -0
- package/dist/runtime/runtime.js +531 -0
- package/dist/runtime/server.d.ts +17 -0
- package/dist/runtime/server.js +226 -0
- package/dist/runtime/sse.d.ts +15 -0
- package/dist/runtime/sse.js +96 -0
- package/dist/runtime/wake-scheduler.d.ts +8 -0
- package/dist/runtime/wake-scheduler.js +38 -0
- package/dist/s2/config.d.ts +27 -0
- package/dist/s2/config.js +54 -0
- package/dist/s2/index.d.ts +6 -0
- package/dist/s2/index.js +6 -0
- package/dist/s2/lite.d.ts +26 -0
- package/dist/s2/lite.js +150 -0
- package/dist/s2/projector.d.ts +8 -0
- package/dist/s2/projector.js +28 -0
- package/dist/s2/serialize.d.ts +1 -0
- package/dist/s2/serialize.js +1 -0
- package/dist/s2/snapshot-store.d.ts +17 -0
- package/dist/s2/snapshot-store.js +320 -0
- package/dist/s2/store.d.ts +35 -0
- package/dist/s2/store.js +337 -0
- package/dist/testing/index.d.ts +11 -0
- package/dist/testing/index.js +63 -0
- package/dist/workflow/definitions-store.d.ts +11 -0
- package/dist/workflow/definitions-store.js +8 -0
- package/dist/workflow/definitions.d.ts +82 -0
- package/dist/workflow/definitions.js +32 -0
- package/dist/workflow/effects.d.ts +305 -0
- package/dist/workflow/effects.js +208 -0
- package/dist/workflow/events.d.ts +60 -0
- package/dist/workflow/events.js +39 -0
- package/dist/workflow/index.d.ts +8 -0
- package/dist/workflow/index.js +8 -0
- package/dist/workflow/module.d.ts +926 -0
- package/dist/workflow/module.js +19 -0
- package/dist/workflow/projections.d.ts +30 -0
- package/dist/workflow/projections.js +43 -0
- package/dist/workflow/scope.d.ts +61 -0
- package/dist/workflow/scope.js +7 -0
- package/dist/workflow/threads.d.ts +171 -0
- package/dist/workflow/threads.js +287 -0
- package/package.json +183 -0
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { Config, Console, Effect, FileSystem, Layer, Option, Path, Queue, Stdio, Stream, Terminal, } from 'effect';
|
|
2
|
+
import { Argument, CliError, Command, Flag } from 'effect/unstable/cli';
|
|
3
|
+
import { ChildProcessSpawner } from 'effect/unstable/process';
|
|
4
|
+
import { steer as steerEvent } from '@swirls/looms/agent';
|
|
5
|
+
import { decision } from '@swirls/looms/approval';
|
|
6
|
+
import { createLoomsClient } from '@swirls/looms/client';
|
|
7
|
+
import { createLooms } from '@swirls/looms/runtime';
|
|
8
|
+
const VERSION = '0.0.1';
|
|
9
|
+
const urlFlag = Flag.string('url').pipe(Flag.withDescription('Looms host base URL (env LOOMS_URL)'), Flag.withFallbackConfig(Config.string('LOOMS_URL')), Flag.withDefault('http://127.0.0.1:8787'), Flag.map((url) => url.replace(/\/$/, '')));
|
|
10
|
+
function parseJsonArg(raw) {
|
|
11
|
+
if (Option.isNone(raw) || raw.value === '') {
|
|
12
|
+
return null;
|
|
13
|
+
}
|
|
14
|
+
try {
|
|
15
|
+
// SAFETY: CLI JSON args decode to JsonValue; parse failures fall back to the raw string
|
|
16
|
+
return JSON.parse(raw.value);
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
return raw.value;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
function clientFor(url) {
|
|
23
|
+
return createLoomsClient({ baseUrl: url });
|
|
24
|
+
}
|
|
25
|
+
function tryClient(run) {
|
|
26
|
+
return Effect.tryPromise({
|
|
27
|
+
try: run,
|
|
28
|
+
catch: (error) => new CliError.UserError({
|
|
29
|
+
cause: error,
|
|
30
|
+
userMessage: error instanceof Error ? error.message : String(error),
|
|
31
|
+
}),
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
const serve = Command.make('serve', {
|
|
35
|
+
port: Flag.integer('port').pipe(Flag.withDescription('Port to listen on (env PORT)'), Flag.withFallbackConfig(Config.int('PORT')), Flag.withDefault(8787)),
|
|
36
|
+
}, Effect.fn('serve')(function* ({ port }) {
|
|
37
|
+
const looms = createLooms();
|
|
38
|
+
const server = looms.serve({ port });
|
|
39
|
+
yield* Console.log(`Looms runtime listening on http://127.0.0.1:${server?.port ?? port}`);
|
|
40
|
+
return yield* Effect.never;
|
|
41
|
+
})).pipe(Command.withDescription('Start the Looms runtime host'), Command.withShortDescription('Start the runtime'));
|
|
42
|
+
const loomsBase = Command.make('looms').pipe(Command.withDescription('Looms thread runtime'), Command.withSharedFlags({ url: urlFlag }));
|
|
43
|
+
const start = Command.make('start', {
|
|
44
|
+
kind: Argument.string('kind').pipe(Argument.withDescription('Thread kind (e.g. agent, workflow)')),
|
|
45
|
+
name: Argument.string('name').pipe(Argument.withDescription('Definition name')),
|
|
46
|
+
json: Argument.string('json').pipe(Argument.withDescription('Optional JSON input'), Argument.optional),
|
|
47
|
+
}, Effect.fn('start')(function* ({ kind, name, json }) {
|
|
48
|
+
const { url } = yield* loomsBase;
|
|
49
|
+
const result = yield* tryClient(() => clientFor(url).startRun({ kind, definitionName: name, input: parseJsonArg(json) }));
|
|
50
|
+
yield* Console.log(JSON.stringify(result, null, 2));
|
|
51
|
+
})).pipe(Command.withDescription('Start a run from a registered definition: looms start agent echo \'{"text":"hi"}\''), Command.withShortDescription('Start a run'));
|
|
52
|
+
const events = Command.make('events', {
|
|
53
|
+
runId: Argument.string('runId').pipe(Argument.withDescription('Run id')),
|
|
54
|
+
}, Effect.fn('events')(function* ({ runId }) {
|
|
55
|
+
const { url } = yield* loomsBase;
|
|
56
|
+
const result = yield* tryClient(() => clientFor(url).getEvents(runId));
|
|
57
|
+
yield* Console.log(JSON.stringify(result, null, 2));
|
|
58
|
+
})).pipe(Command.withDescription('List events for a run'), Command.withShortDescription('List run events'));
|
|
59
|
+
const state = Command.make('state', {
|
|
60
|
+
runId: Argument.string('runId').pipe(Argument.withDescription('Run id')),
|
|
61
|
+
}, Effect.fn('state')(function* ({ runId }) {
|
|
62
|
+
const { url } = yield* loomsBase;
|
|
63
|
+
const result = yield* tryClient(() => clientFor(url).getRun(runId));
|
|
64
|
+
yield* Console.log(JSON.stringify(result, null, 2));
|
|
65
|
+
})).pipe(Command.withDescription('Show run state'), Command.withShortDescription('Show run state'));
|
|
66
|
+
const approve = Command.make('approve', {
|
|
67
|
+
runId: Argument.string('runId').pipe(Argument.withDescription('Run id')),
|
|
68
|
+
approvalId: Argument.string('approvalId').pipe(Argument.withDescription('Pending approval id')),
|
|
69
|
+
accept: Flag.boolean('approve').pipe(Flag.withAlias('a'), Flag.withDescription('Approve the request'), Flag.withDefault(false)),
|
|
70
|
+
reject: Flag.boolean('reject').pipe(Flag.withAlias('r'), Flag.withDescription('Reject the request'), Flag.withDefault(false)),
|
|
71
|
+
}, Effect.fn('approve')(function* ({ runId, approvalId, accept, reject }) {
|
|
72
|
+
if (accept === reject) {
|
|
73
|
+
return yield* new CliError.UserError({
|
|
74
|
+
cause: new Error('Specify exactly one of --approve or --reject'),
|
|
75
|
+
userMessage: 'Specify exactly one of --approve or --reject',
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
const outcome = accept ? 'approve' : 'reject';
|
|
79
|
+
const { url } = yield* loomsBase;
|
|
80
|
+
const result = yield* tryClient(() => clientFor(url).signal(runId, [decision(approvalId, outcome)]));
|
|
81
|
+
yield* Console.log(JSON.stringify(result, null, 2));
|
|
82
|
+
})).pipe(Command.withDescription('Approve or reject a pending approval'), Command.withShortDescription('Decide an approval'));
|
|
83
|
+
const replay = Command.make('replay', {
|
|
84
|
+
runId: Argument.string('runId').pipe(Argument.withDescription('Run id')),
|
|
85
|
+
seq: Argument.integer('seq').pipe(Argument.withDescription('Event sequence to replay through')),
|
|
86
|
+
}, Effect.fn('replay')(function* ({ runId, seq }) {
|
|
87
|
+
const { url } = yield* loomsBase;
|
|
88
|
+
const result = yield* tryClient(() => clientFor(url).replayTo(runId, seq));
|
|
89
|
+
yield* Console.log(JSON.stringify(result, null, 2));
|
|
90
|
+
})).pipe(Command.withDescription('Replay a run to a sequence'), Command.withShortDescription('Replay to seq'));
|
|
91
|
+
const signal = Command.make('signal', {
|
|
92
|
+
runId: Argument.string('runId').pipe(Argument.withDescription('Run id')),
|
|
93
|
+
type: Argument.string('type').pipe(Argument.withDescription('Event type')),
|
|
94
|
+
json: Argument.string('json').pipe(Argument.withDescription('Optional JSON payload'), Argument.optional),
|
|
95
|
+
}, Effect.fn('signal')(function* ({ runId, type, json }) {
|
|
96
|
+
const { url } = yield* loomsBase;
|
|
97
|
+
const result = yield* tryClient(() => clientFor(url).signal(runId, [{ type, payload: parseJsonArg(json) }]));
|
|
98
|
+
yield* Console.log(JSON.stringify(result, null, 2));
|
|
99
|
+
})).pipe(Command.withDescription('Append an external event and wake the run'), Command.withShortDescription('Signal a run'));
|
|
100
|
+
const steer = Command.make('steer', {
|
|
101
|
+
runId: Argument.string('runId').pipe(Argument.withDescription('Run id')),
|
|
102
|
+
message: Argument.string('message').pipe(Argument.withDescription('Steering message'), Argument.variadic({ min: 1 })),
|
|
103
|
+
interrupt: Flag.boolean('interrupt').pipe(Flag.withDescription('Interrupt the current turn (use --no-interrupt to disable)'), Flag.withDefault(true)),
|
|
104
|
+
}, Effect.fn('steer')(function* ({ runId, message, interrupt }) {
|
|
105
|
+
const { url } = yield* loomsBase;
|
|
106
|
+
const result = yield* tryClient(() => clientFor(url).signal(runId, [steerEvent(message.join(' '), { interrupt })]));
|
|
107
|
+
yield* Console.log(JSON.stringify(result, null, 2));
|
|
108
|
+
})).pipe(Command.withDescription('Send a steering message to a running agent'), Command.withShortDescription('Steer an agent'));
|
|
109
|
+
const tail = Command.make('tail', {
|
|
110
|
+
runId: Argument.string('runId').pipe(Argument.withDescription('Run id')),
|
|
111
|
+
}, Effect.fn('tail')(function* ({ runId }) {
|
|
112
|
+
const { url } = yield* loomsBase;
|
|
113
|
+
const client = clientFor(url);
|
|
114
|
+
yield* Console.log(`Tailing ${runId} (ctrl-c to stop)`);
|
|
115
|
+
yield* Effect.scoped(Effect.gen(function* () {
|
|
116
|
+
const queue = yield* Queue.unbounded();
|
|
117
|
+
yield* Effect.acquireRelease(Effect.sync(() => client.subscribeEvents(runId, (event) => {
|
|
118
|
+
Queue.offerUnsafe(queue, JSON.stringify(event));
|
|
119
|
+
})), (stop) => Effect.sync(() => stop()));
|
|
120
|
+
yield* Stream.fromQueue(queue).pipe(Stream.runForEach((line) => Console.log(line)));
|
|
121
|
+
}));
|
|
122
|
+
})).pipe(Command.withDescription('Stream events for a run'), Command.withShortDescription('Tail run events'));
|
|
123
|
+
export const looms = loomsBase.pipe(Command.withSubcommands([serve, start, events, state, approve, replay, signal, steer, tail]));
|
|
124
|
+
export const cliTestLayer = Layer.mergeAll(FileSystem.layerNoop({}), Path.layer, Stdio.layerTest({}), Layer.succeed(Terminal.Terminal, Terminal.make({
|
|
125
|
+
columns: Effect.succeed(80),
|
|
126
|
+
rows: Effect.succeed(24),
|
|
127
|
+
readInput: Effect.die('unused'),
|
|
128
|
+
readLine: Effect.die('unused'),
|
|
129
|
+
display: () => Effect.void,
|
|
130
|
+
})), Layer.succeed(ChildProcessSpawner.ChildProcessSpawner, ChildProcessSpawner.make(() => Effect.die('unused'))));
|
|
131
|
+
/** Run the CLI with an explicit argv (for tests and programmatic use). */
|
|
132
|
+
export const runCli = (argv) => Command.runWith(looms, { version: VERSION })(argv);
|
|
133
|
+
export { VERSION };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { runCli, looms, VERSION, cliTestLayer } from './commands';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { runCli, looms, VERSION, cliTestLayer } from './commands.js';
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { type DefinitionInput, type DefinitionRef, type EventEnvelope, type EventInput, type JsonValue, type RunState } from '@swirls/looms/core';
|
|
2
|
+
export interface LoomsClientOptions {
|
|
3
|
+
baseUrl?: string;
|
|
4
|
+
fetch?: typeof fetch;
|
|
5
|
+
}
|
|
6
|
+
export interface StartResult {
|
|
7
|
+
runId: string;
|
|
8
|
+
threadId: string;
|
|
9
|
+
state: RunState;
|
|
10
|
+
}
|
|
11
|
+
export interface StreamHandle extends AsyncIterable<EventEnvelope> {
|
|
12
|
+
readonly runId: string;
|
|
13
|
+
}
|
|
14
|
+
export declare function createLoomsClient(options?: LoomsClientOptions): {
|
|
15
|
+
/** Start a run by `{ kind, definitionName }` when you only have names. */
|
|
16
|
+
startRun: (args: {
|
|
17
|
+
kind: string;
|
|
18
|
+
definitionName: string;
|
|
19
|
+
input?: JsonValue;
|
|
20
|
+
runId?: string;
|
|
21
|
+
}) => Promise<StartResult>;
|
|
22
|
+
/** Start a run from a definition object (agent, workflow, or your own kind). */
|
|
23
|
+
start: <TDef extends DefinitionRef>(definition: TDef, input?: DefinitionInput<TDef>) => Promise<StartResult>;
|
|
24
|
+
getRun: (runId: string) => Promise<{
|
|
25
|
+
runId: string;
|
|
26
|
+
state: RunState;
|
|
27
|
+
}>;
|
|
28
|
+
getState: (runId: string) => Promise<{
|
|
29
|
+
runId: string;
|
|
30
|
+
state: RunState;
|
|
31
|
+
}>;
|
|
32
|
+
getEvents: (runId: string, opts?: {
|
|
33
|
+
fromSeq?: number;
|
|
34
|
+
limit?: number;
|
|
35
|
+
}) => Promise<{
|
|
36
|
+
runId: string;
|
|
37
|
+
events: EventEnvelope[];
|
|
38
|
+
}>;
|
|
39
|
+
signal: (runId: string, events: ReadonlyArray<EventInput>) => Promise<{
|
|
40
|
+
runId: string;
|
|
41
|
+
state: RunState;
|
|
42
|
+
}>;
|
|
43
|
+
wake: (runId: string) => Promise<{
|
|
44
|
+
runId: string;
|
|
45
|
+
state: RunState;
|
|
46
|
+
}>;
|
|
47
|
+
replayTo: (runId: string, seq: number) => Promise<{
|
|
48
|
+
runId: string;
|
|
49
|
+
step: unknown;
|
|
50
|
+
}>;
|
|
51
|
+
project: (runId: string, name: string) => Promise<{
|
|
52
|
+
runId: string;
|
|
53
|
+
name: string;
|
|
54
|
+
value: unknown;
|
|
55
|
+
}>;
|
|
56
|
+
subscribeEvents: (runId: string, onEvent: (event: EventEnvelope) => void, reconnectDelayMs?: number) => () => void;
|
|
57
|
+
/**
|
|
58
|
+
* Subscribe to the run log, then start execution so deltas arrive live.
|
|
59
|
+
* Yields events in store order, including live module events such as `agent.turn.text_delta`.
|
|
60
|
+
*/
|
|
61
|
+
streamRun: (args: {
|
|
62
|
+
kind: string;
|
|
63
|
+
definitionName: string;
|
|
64
|
+
input?: JsonValue;
|
|
65
|
+
runId?: string;
|
|
66
|
+
}) => StreamHandle;
|
|
67
|
+
stream: <TDef extends DefinitionRef>(definition: TDef, input?: DefinitionInput<TDef>) => StreamHandle;
|
|
68
|
+
};
|
|
69
|
+
export type LoomsClient = ReturnType<typeof createLoomsClient>;
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import { Predicate } from 'effect';
|
|
2
|
+
import { createRunId, } from '@swirls/looms/core';
|
|
3
|
+
import { consumeSseStream, delay, eventsFromSseData } from './sse.js';
|
|
4
|
+
export function createLoomsClient(options = {}) {
|
|
5
|
+
const baseUrl = (options.baseUrl ?? '').replace(/\/$/, '');
|
|
6
|
+
const fetchImpl = options.fetch ?? fetch;
|
|
7
|
+
async function request(path, init) {
|
|
8
|
+
const headers = new Headers(init?.headers);
|
|
9
|
+
if (!headers.has('content-type')) {
|
|
10
|
+
headers.set('content-type', 'application/json');
|
|
11
|
+
}
|
|
12
|
+
const res = await fetchImpl(`${baseUrl}${path}`, { ...init, headers });
|
|
13
|
+
if (!res.ok) {
|
|
14
|
+
const body = await res.text();
|
|
15
|
+
throw new Error(body || `HTTP ${res.status}`);
|
|
16
|
+
}
|
|
17
|
+
// SAFETY: host JSON responses match the generic requested by each route helper.
|
|
18
|
+
return (await res.json());
|
|
19
|
+
}
|
|
20
|
+
const subscribeEvents = (runId, onEvent, reconnectDelayMs = 1_000) => {
|
|
21
|
+
let fromSeq = 1;
|
|
22
|
+
const controller = new AbortController();
|
|
23
|
+
const run = async () => {
|
|
24
|
+
while (!controller.signal.aborted) {
|
|
25
|
+
try {
|
|
26
|
+
const cursor = Math.max(0, fromSeq - 1);
|
|
27
|
+
const headers = new Headers({ accept: 'text/event-stream' });
|
|
28
|
+
if (cursor > 0) {
|
|
29
|
+
headers.set('last-event-id', String(cursor));
|
|
30
|
+
}
|
|
31
|
+
const res = await fetchImpl(`${baseUrl}/api/events?runId=${encodeURIComponent(runId)}&live=true&cursor=${cursor}`, { headers, signal: controller.signal });
|
|
32
|
+
if (!res.ok || !res.body) {
|
|
33
|
+
throw new Error(res.ok ? 'event stream missing body' : `HTTP ${res.status}`);
|
|
34
|
+
}
|
|
35
|
+
await consumeSseStream(res.body, (frame) => {
|
|
36
|
+
for (const event of eventsFromSseData(frame.data, runId)) {
|
|
37
|
+
onEvent(event);
|
|
38
|
+
fromSeq = Math.max(fromSeq, event.seq + 1);
|
|
39
|
+
}
|
|
40
|
+
}, controller.signal);
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
if (controller.signal.aborted) {
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
if (controller.signal.aborted) {
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
await delay(reconnectDelayMs, controller.signal);
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
void run();
|
|
54
|
+
return () => {
|
|
55
|
+
controller.abort();
|
|
56
|
+
};
|
|
57
|
+
};
|
|
58
|
+
const startRun = (args) => request('/runs', { method: 'POST', body: JSON.stringify(args) });
|
|
59
|
+
const streamRun = (args) => {
|
|
60
|
+
const runId = args.runId ?? createRunId();
|
|
61
|
+
return {
|
|
62
|
+
runId,
|
|
63
|
+
async *[Symbol.asyncIterator]() {
|
|
64
|
+
const controller = new AbortController();
|
|
65
|
+
const res = await fetchImpl(`${baseUrl}/runs`, {
|
|
66
|
+
method: 'POST',
|
|
67
|
+
headers: {
|
|
68
|
+
'content-type': 'application/json',
|
|
69
|
+
accept: 'text/event-stream',
|
|
70
|
+
},
|
|
71
|
+
body: JSON.stringify({ ...args, runId }),
|
|
72
|
+
signal: controller.signal,
|
|
73
|
+
});
|
|
74
|
+
if (!res.ok || !res.body) {
|
|
75
|
+
throw new Error(res.ok ? 'SSE stream missing body' : `HTTP ${res.status}`);
|
|
76
|
+
}
|
|
77
|
+
const pending = [];
|
|
78
|
+
let notify;
|
|
79
|
+
let finished = false;
|
|
80
|
+
let streamError;
|
|
81
|
+
const consume = consumeSseStream(res.body, (frame) => {
|
|
82
|
+
if (frame.event === 'error') {
|
|
83
|
+
try {
|
|
84
|
+
const parsed = JSON.parse(frame.data);
|
|
85
|
+
const message = Predicate.isReadonlyObject(parsed) && Predicate.isString(parsed.error)
|
|
86
|
+
? parsed.error
|
|
87
|
+
: frame.data;
|
|
88
|
+
streamError = new Error(message);
|
|
89
|
+
}
|
|
90
|
+
catch {
|
|
91
|
+
streamError = new Error(frame.data);
|
|
92
|
+
}
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
if (frame.event === 'done') {
|
|
96
|
+
finished = true;
|
|
97
|
+
notify?.();
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
for (const event of eventsFromSseData(frame.data, runId)) {
|
|
101
|
+
pending.push(event);
|
|
102
|
+
}
|
|
103
|
+
notify?.();
|
|
104
|
+
}, controller.signal)
|
|
105
|
+
.catch((cause) => {
|
|
106
|
+
streamError = cause instanceof Error ? cause : new Error(String(cause));
|
|
107
|
+
})
|
|
108
|
+
.finally(() => {
|
|
109
|
+
finished = true;
|
|
110
|
+
notify?.();
|
|
111
|
+
});
|
|
112
|
+
try {
|
|
113
|
+
while (true) {
|
|
114
|
+
if (pending.length === 0) {
|
|
115
|
+
if (finished) {
|
|
116
|
+
break;
|
|
117
|
+
}
|
|
118
|
+
await new Promise((resolve) => {
|
|
119
|
+
notify = resolve;
|
|
120
|
+
});
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
const event = pending.shift();
|
|
124
|
+
if (event) {
|
|
125
|
+
yield event;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
await consume;
|
|
129
|
+
if (streamError) {
|
|
130
|
+
throw streamError;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
finally {
|
|
134
|
+
controller.abort();
|
|
135
|
+
}
|
|
136
|
+
},
|
|
137
|
+
};
|
|
138
|
+
};
|
|
139
|
+
return {
|
|
140
|
+
/** Start a run by `{ kind, definitionName }` when you only have names. */
|
|
141
|
+
startRun,
|
|
142
|
+
/** Start a run from a definition object (agent, workflow, or your own kind). */
|
|
143
|
+
start: (definition, input) => request('/runs', {
|
|
144
|
+
method: 'POST',
|
|
145
|
+
body: JSON.stringify({ kind: definition.kind, definitionName: definition.name, input }),
|
|
146
|
+
}),
|
|
147
|
+
getRun: (runId) => request(`/runs/${runId}`),
|
|
148
|
+
getState: (runId) => request(`/runs/${runId}`),
|
|
149
|
+
getEvents: (runId, opts) => {
|
|
150
|
+
const params = new URLSearchParams();
|
|
151
|
+
if (opts?.fromSeq !== undefined) {
|
|
152
|
+
params.set('fromSeq', String(opts.fromSeq));
|
|
153
|
+
}
|
|
154
|
+
if (opts?.limit !== undefined) {
|
|
155
|
+
params.set('limit', String(opts.limit));
|
|
156
|
+
}
|
|
157
|
+
const q = params.toString();
|
|
158
|
+
return request(`/runs/${runId}/events${q ? `?${q}` : ''}`);
|
|
159
|
+
},
|
|
160
|
+
signal: (runId, events) => request(`/runs/${runId}/events`, {
|
|
161
|
+
method: 'POST',
|
|
162
|
+
body: JSON.stringify({ events }),
|
|
163
|
+
}),
|
|
164
|
+
wake: (runId) => request(`/runs/${runId}/wake`, { method: 'POST' }),
|
|
165
|
+
replayTo: (runId, seq) => request(`/runs/${runId}/replay?seq=${seq}`),
|
|
166
|
+
project: (runId, name) => request(`/runs/${runId}/projections/${name}`),
|
|
167
|
+
subscribeEvents,
|
|
168
|
+
/**
|
|
169
|
+
* Subscribe to the run log, then start execution so deltas arrive live.
|
|
170
|
+
* Yields events in store order, including live module events such as `agent.turn.text_delta`.
|
|
171
|
+
*/
|
|
172
|
+
streamRun,
|
|
173
|
+
stream: (definition, input) => streamRun({
|
|
174
|
+
kind: definition.kind,
|
|
175
|
+
definitionName: definition.name,
|
|
176
|
+
input,
|
|
177
|
+
}),
|
|
178
|
+
};
|
|
179
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { type EventEnvelope } from '@swirls/looms/core';
|
|
2
|
+
export interface SseFrame {
|
|
3
|
+
id?: string;
|
|
4
|
+
event?: string;
|
|
5
|
+
data: string;
|
|
6
|
+
}
|
|
7
|
+
export declare function parseSseFrame(raw: string): SseFrame | undefined;
|
|
8
|
+
export declare function consumeSseStream(body: ReadableStream<Uint8Array>, onFrame: (frame: SseFrame) => void, signal?: AbortSignal): Promise<void>;
|
|
9
|
+
export declare function eventsFromSseData(data: string, runId: string): EventEnvelope[];
|
|
10
|
+
export declare function delay(ms: number, signal?: AbortSignal): Promise<void>;
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { Predicate } from 'effect';
|
|
2
|
+
import { decodeLoomsEvent } from '@swirls/looms/core';
|
|
3
|
+
export function parseSseFrame(raw) {
|
|
4
|
+
let id;
|
|
5
|
+
let event;
|
|
6
|
+
const dataLines = [];
|
|
7
|
+
for (const line of raw.replace(/\r\n/g, '\n').replace(/\r/g, '\n').split('\n')) {
|
|
8
|
+
if (line.startsWith(':')) {
|
|
9
|
+
continue;
|
|
10
|
+
}
|
|
11
|
+
if (line.startsWith('id:')) {
|
|
12
|
+
id = line.slice(3).trim();
|
|
13
|
+
continue;
|
|
14
|
+
}
|
|
15
|
+
if (line.startsWith('event:')) {
|
|
16
|
+
event = line.slice(6).trim();
|
|
17
|
+
continue;
|
|
18
|
+
}
|
|
19
|
+
if (line.startsWith('data:')) {
|
|
20
|
+
dataLines.push(line.slice(5).trimStart());
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
if (dataLines.length === 0) {
|
|
24
|
+
return undefined;
|
|
25
|
+
}
|
|
26
|
+
return { id, event, data: dataLines.join('\n') };
|
|
27
|
+
}
|
|
28
|
+
export async function consumeSseStream(body, onFrame, signal) {
|
|
29
|
+
const reader = body.getReader();
|
|
30
|
+
const decoder = new TextDecoder();
|
|
31
|
+
let buffer = '';
|
|
32
|
+
const abort = () => {
|
|
33
|
+
void reader.cancel();
|
|
34
|
+
};
|
|
35
|
+
signal?.addEventListener('abort', abort);
|
|
36
|
+
try {
|
|
37
|
+
while (true) {
|
|
38
|
+
if (signal?.aborted) {
|
|
39
|
+
break;
|
|
40
|
+
}
|
|
41
|
+
const { done, value } = await reader.read();
|
|
42
|
+
if (done) {
|
|
43
|
+
break;
|
|
44
|
+
}
|
|
45
|
+
buffer += decoder.decode(value, { stream: true });
|
|
46
|
+
const chunks = buffer.split('\n\n');
|
|
47
|
+
buffer = chunks.pop() ?? '';
|
|
48
|
+
for (const chunk of chunks) {
|
|
49
|
+
const frame = parseSseFrame(chunk);
|
|
50
|
+
if (frame) {
|
|
51
|
+
onFrame(frame);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
finally {
|
|
57
|
+
signal?.removeEventListener('abort', abort);
|
|
58
|
+
try {
|
|
59
|
+
reader.releaseLock();
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
// reader already cancelled
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
export function eventsFromSseData(data, runId) {
|
|
67
|
+
let parsed;
|
|
68
|
+
try {
|
|
69
|
+
parsed = JSON.parse(data);
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
return [];
|
|
73
|
+
}
|
|
74
|
+
if (!Predicate.isReadonlyObject(parsed)) {
|
|
75
|
+
return [];
|
|
76
|
+
}
|
|
77
|
+
const batch = 'batch' in parsed && Array.isArray(parsed.batch) ? parsed.batch : [];
|
|
78
|
+
const events = [];
|
|
79
|
+
for (const item of batch) {
|
|
80
|
+
try {
|
|
81
|
+
events.push(decodeLoomsEvent(
|
|
82
|
+
// SAFETY: host SSE batch items are encoded Looms event envelopes.
|
|
83
|
+
item, runId));
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
// skip a malformed item and keep the rest of the batch
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return events;
|
|
90
|
+
}
|
|
91
|
+
export function delay(ms, signal) {
|
|
92
|
+
return new Promise((resolve) => {
|
|
93
|
+
if (signal?.aborted) {
|
|
94
|
+
resolve();
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
const timer = setTimeout(resolve, ms);
|
|
98
|
+
signal?.addEventListener('abort', () => {
|
|
99
|
+
clearTimeout(timer);
|
|
100
|
+
resolve();
|
|
101
|
+
}, { once: true });
|
|
102
|
+
});
|
|
103
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { WakeScheduler } from '@swirls/looms/runtime';
|
|
2
|
+
export interface AlarmStorage {
|
|
3
|
+
setAlarm(scheduledTime: number | Date): Promise<void>;
|
|
4
|
+
deleteAlarm(): Promise<void>;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Adapter that maps actor wake timer scheduling to Cloudflare Durable Object alarms.
|
|
8
|
+
*/
|
|
9
|
+
export declare function alarmScheduler(storage: AlarmStorage): WakeScheduler;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Adapter that maps actor wake timer scheduling to Cloudflare Durable Object alarms.
|
|
3
|
+
*/
|
|
4
|
+
export function alarmScheduler(storage) {
|
|
5
|
+
return {
|
|
6
|
+
schedule: async (_runId, at) => {
|
|
7
|
+
await storage.setAlarm(at);
|
|
8
|
+
},
|
|
9
|
+
cancel: async (_runId) => {
|
|
10
|
+
await storage.deleteAlarm();
|
|
11
|
+
},
|
|
12
|
+
};
|
|
13
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { DurableObject } from 'cloudflare:workers';
|
|
2
|
+
import type { AnyRuntimeModule } from '@swirls/looms/core';
|
|
3
|
+
import type { Projector } from '@swirls/looms/projectors';
|
|
4
|
+
export interface LoomsDurableObjectConfig {
|
|
5
|
+
readonly modules: readonly AnyRuntimeModule[];
|
|
6
|
+
readonly projectors?: readonly Projector[];
|
|
7
|
+
readonly snapshotEvery?: number;
|
|
8
|
+
readonly maxWakeIterations?: number;
|
|
9
|
+
readonly trimAfterSnapshot?: {
|
|
10
|
+
keepSnapshots: number;
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Base Durable Object class for hosting a Looms actor cell.
|
|
15
|
+
* Subclasses implement `configure(env)` to supply configured modules.
|
|
16
|
+
* Works identically on Cloudflare Workers and celld.
|
|
17
|
+
*/
|
|
18
|
+
export declare abstract class LoomsDurableObject<Env = unknown> extends DurableObject<Env> {
|
|
19
|
+
private cellPromise?;
|
|
20
|
+
abstract configure(env: Env): LoomsDurableObjectConfig | Promise<LoomsDurableObjectConfig>;
|
|
21
|
+
private getCell;
|
|
22
|
+
fetch(req: Request): Promise<Response>;
|
|
23
|
+
alarm(): Promise<void>;
|
|
24
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { DurableObject } from 'cloudflare:workers';
|
|
2
|
+
import { createActorCell } from '@swirls/looms/actor';
|
|
3
|
+
import { alarmScheduler } from './alarm-scheduler.js';
|
|
4
|
+
import { durableObjectEventStore } from './event-store.js';
|
|
5
|
+
/**
|
|
6
|
+
* Base Durable Object class for hosting a Looms actor cell.
|
|
7
|
+
* Subclasses implement `configure(env)` to supply configured modules.
|
|
8
|
+
* Works identically on Cloudflare Workers and celld.
|
|
9
|
+
*/
|
|
10
|
+
export class LoomsDurableObject extends DurableObject {
|
|
11
|
+
cellPromise;
|
|
12
|
+
async getCell() {
|
|
13
|
+
if (!this.cellPromise) {
|
|
14
|
+
this.cellPromise = (async () => {
|
|
15
|
+
const runId = this.ctx.id.name;
|
|
16
|
+
if (!runId) {
|
|
17
|
+
throw new Error('LoomsDurableObject requires a named Durable Object id (use idFromName / getByName)');
|
|
18
|
+
}
|
|
19
|
+
const config = await this.configure(this.env);
|
|
20
|
+
const store = durableObjectEventStore(this.ctx, { projectors: config.projectors });
|
|
21
|
+
const scheduler = alarmScheduler(this.ctx.storage);
|
|
22
|
+
return createActorCell({
|
|
23
|
+
runId,
|
|
24
|
+
store,
|
|
25
|
+
scheduler,
|
|
26
|
+
modules: config.modules,
|
|
27
|
+
snapshotEvery: config.snapshotEvery,
|
|
28
|
+
maxWakeIterations: config.maxWakeIterations,
|
|
29
|
+
trimAfterSnapshot: config.trimAfterSnapshot,
|
|
30
|
+
});
|
|
31
|
+
})();
|
|
32
|
+
}
|
|
33
|
+
return this.cellPromise;
|
|
34
|
+
}
|
|
35
|
+
async fetch(req) {
|
|
36
|
+
const cell = await this.getCell();
|
|
37
|
+
return cell.fetch(req);
|
|
38
|
+
}
|
|
39
|
+
async alarm() {
|
|
40
|
+
const cell = await this.getCell();
|
|
41
|
+
await cell.wake();
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { EventStore } from '@swirls/looms/core';
|
|
2
|
+
import { type Projector } from '@swirls/looms/projectors';
|
|
3
|
+
export interface DurableObjectStoreOptions {
|
|
4
|
+
readonly projectors?: readonly Projector[];
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Creates an EventStore backed by a Durable Object's embedded SQLite database (`ctx.storage.sql`).
|
|
8
|
+
* Optionally attaches asynchronous projectors (such as S2).
|
|
9
|
+
*/
|
|
10
|
+
export declare function durableObjectEventStore(ctx: DurableObjectState, options?: DurableObjectStoreOptions): EventStore;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { sqliteEventStore, sqlStorageExec } from '@swirls/looms/core/sqlite-store';
|
|
2
|
+
import { withProjectors } from '@swirls/looms/projectors';
|
|
3
|
+
/**
|
|
4
|
+
* Creates an EventStore backed by a Durable Object's embedded SQLite database (`ctx.storage.sql`).
|
|
5
|
+
* Optionally attaches asynchronous projectors (such as S2).
|
|
6
|
+
*/
|
|
7
|
+
export function durableObjectEventStore(ctx, options = {}) {
|
|
8
|
+
const store = sqliteEventStore({
|
|
9
|
+
exec: sqlStorageExec(ctx.storage.sql, {
|
|
10
|
+
// DO SQLite rejects BEGIN/COMMIT/ROLLBACK; use the storage transaction API.
|
|
11
|
+
transaction: (fn) => ctx.storage.transactionSync(fn),
|
|
12
|
+
}),
|
|
13
|
+
});
|
|
14
|
+
if (options.projectors && options.projectors.length > 0) {
|
|
15
|
+
return withProjectors(store, options.projectors);
|
|
16
|
+
}
|
|
17
|
+
return store;
|
|
18
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { alarmScheduler, type AlarmStorage } from './alarm-scheduler';
|
|
2
|
+
export { durableObjectEventStore, type DurableObjectStoreOptions } from './event-store';
|
|
3
|
+
export { LoomsDurableObject, type LoomsDurableObjectConfig } from './durable-object';
|
|
4
|
+
export { routeToDurableObject, type DurableObjectNamespaceLike, type DurableObjectStubLike, } from './router';
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export interface DurableObjectStubLike {
|
|
2
|
+
fetch(request: Request): Promise<Response>;
|
|
3
|
+
}
|
|
4
|
+
export interface DurableObjectNamespaceLike<T extends DurableObjectStubLike = DurableObjectStubLike> {
|
|
5
|
+
getByName(name: string): T;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Routes incoming HTTP requests to the appropriate Durable Object actor cell.
|
|
9
|
+
* Handles `/health` locally, returns 501 for global `GET /runs`, and forwards
|
|
10
|
+
* all other run and event-stream requests to `namespace.getByName(runId)`.
|
|
11
|
+
*/
|
|
12
|
+
export declare function routeToDurableObject(namespace: DurableObjectNamespaceLike, req: Request): Promise<Response | null>;
|