@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,113 @@
|
|
|
1
|
+
import { Effect, Schedule } from 'effect';
|
|
2
|
+
import { asJson, isPrimitiveEffect, isWaitOnTimer, } from '@swirls/looms/core';
|
|
3
|
+
import { threadStartedEvents } from './runtime-helpers.js';
|
|
4
|
+
export function dispatchEffect(registry, services, definitions, effect, ctx) {
|
|
5
|
+
const currentThreadId = ctx.threadId;
|
|
6
|
+
const baseDispatch = (eff, effCtx) => {
|
|
7
|
+
if (isPrimitiveEffect(eff)) {
|
|
8
|
+
switch (eff.type) {
|
|
9
|
+
case 'runtime.spawn': {
|
|
10
|
+
const childThreadId = eff.childThreadId ?? '';
|
|
11
|
+
return threadStartedEvents(definitions, {
|
|
12
|
+
kind: eff.kind,
|
|
13
|
+
definitionName: eff.definitionName,
|
|
14
|
+
input: eff.input,
|
|
15
|
+
threadId: childThreadId,
|
|
16
|
+
parentThreadId: currentThreadId,
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
case 'runtime.wait': {
|
|
20
|
+
const events = [
|
|
21
|
+
{
|
|
22
|
+
type: 'runtime.wait.registered',
|
|
23
|
+
payload: asJson({
|
|
24
|
+
waitId: eff.waitId,
|
|
25
|
+
threadId: currentThreadId,
|
|
26
|
+
on: eff.on,
|
|
27
|
+
tag: eff.tag ?? null,
|
|
28
|
+
}),
|
|
29
|
+
threadId: currentThreadId,
|
|
30
|
+
},
|
|
31
|
+
];
|
|
32
|
+
if (isWaitOnTimer(eff.on)) {
|
|
33
|
+
events.push({
|
|
34
|
+
type: 'runtime.timer.set',
|
|
35
|
+
payload: { timerId: eff.waitId, waitId: eff.waitId, wakeAt: eff.on.timerAt },
|
|
36
|
+
threadId: currentThreadId,
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
return Effect.succeed(events);
|
|
40
|
+
}
|
|
41
|
+
case 'runtime.emit':
|
|
42
|
+
return Effect.succeed([eff.event]);
|
|
43
|
+
case 'runtime.complete':
|
|
44
|
+
return Effect.succeed([
|
|
45
|
+
{
|
|
46
|
+
type: 'runtime.thread.completed',
|
|
47
|
+
payload: { threadId: currentThreadId, output: eff.output },
|
|
48
|
+
threadId: currentThreadId,
|
|
49
|
+
},
|
|
50
|
+
]);
|
|
51
|
+
case 'runtime.fail':
|
|
52
|
+
return Effect.succeed([
|
|
53
|
+
{
|
|
54
|
+
type: 'runtime.thread.failed',
|
|
55
|
+
payload: { threadId: currentThreadId, error: eff.error },
|
|
56
|
+
threadId: currentThreadId,
|
|
57
|
+
},
|
|
58
|
+
]);
|
|
59
|
+
case 'runtime.cancel': {
|
|
60
|
+
const targetThreadId = eff.threadId ?? '';
|
|
61
|
+
return Effect.succeed([
|
|
62
|
+
{
|
|
63
|
+
type: 'runtime.thread.cancelled',
|
|
64
|
+
payload: { threadId: targetThreadId, reason: 'cancelled' },
|
|
65
|
+
threadId: targetThreadId,
|
|
66
|
+
},
|
|
67
|
+
]);
|
|
68
|
+
}
|
|
69
|
+
default: {
|
|
70
|
+
const exhaustiveCheck = eff;
|
|
71
|
+
return exhaustiveCheck;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
const handler = registry.effects.get(eff.type);
|
|
76
|
+
if (!handler) {
|
|
77
|
+
return Effect.succeed([
|
|
78
|
+
{
|
|
79
|
+
type: 'runtime.effect.failed',
|
|
80
|
+
payload: { effectId: effCtx.effectId, error: `No handler for ${eff.type}` },
|
|
81
|
+
threadId: currentThreadId,
|
|
82
|
+
},
|
|
83
|
+
]);
|
|
84
|
+
}
|
|
85
|
+
const input = 'input' in eff ? eff.input : {};
|
|
86
|
+
let execution = handler
|
|
87
|
+
.execute(input, effCtx)
|
|
88
|
+
.pipe(Effect.provide(services));
|
|
89
|
+
if (handler.retry && handler.retry.maxAttempts > 1) {
|
|
90
|
+
const retryPolicy = handler.retry;
|
|
91
|
+
const backoff = retryPolicy.backoffMs ?? 100;
|
|
92
|
+
execution = execution.pipe(Effect.retry({
|
|
93
|
+
times: retryPolicy.maxAttempts - 1,
|
|
94
|
+
schedule: Schedule.spaced(backoff),
|
|
95
|
+
}));
|
|
96
|
+
}
|
|
97
|
+
return execution.pipe(Effect.catch((err) => Effect.succeed([
|
|
98
|
+
{
|
|
99
|
+
type: 'runtime.effect.failed',
|
|
100
|
+
payload: {
|
|
101
|
+
effectId: effCtx.effectId,
|
|
102
|
+
error: err instanceof Error ? err.message : String(err),
|
|
103
|
+
},
|
|
104
|
+
threadId: currentThreadId,
|
|
105
|
+
},
|
|
106
|
+
])));
|
|
107
|
+
};
|
|
108
|
+
if (registry.middleware && registry.middleware.length > 0) {
|
|
109
|
+
const pipeline = registry.middleware.reduceRight((next, mw) => (e, c) => mw(e, c, next), baseDispatch);
|
|
110
|
+
return pipeline(effect, ctx);
|
|
111
|
+
}
|
|
112
|
+
return baseDispatch(effect, ctx);
|
|
113
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { encodeLoomsEvent, type EncodedLoomsEvent, type EventStore } from '@swirls/looms/core';
|
|
2
|
+
import type { LoomsRuntime } from './runtime';
|
|
3
|
+
export { encodeLoomsEvent, type EncodedLoomsEvent };
|
|
4
|
+
export declare function handleEventsApi(req: Request, runtime: LoomsRuntime, store: EventStore): Promise<Response>;
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { Effect, Predicate } from 'effect';
|
|
2
|
+
import { decodeAppendableEvent, encodeLoomsEvent, EventStoreConflictError, EventStoreTag, } from '@swirls/looms/core';
|
|
3
|
+
import { createEventStreamResponse } from './sse.js';
|
|
4
|
+
export { encodeLoomsEvent };
|
|
5
|
+
function readCursor(req, url) {
|
|
6
|
+
const lastEventId = req.headers.get('last-event-id');
|
|
7
|
+
const raw = lastEventId !== null && lastEventId !== ''
|
|
8
|
+
? lastEventId
|
|
9
|
+
: (url.searchParams.get('cursor') ?? '0');
|
|
10
|
+
const parsed = Number(raw);
|
|
11
|
+
return Number.isFinite(parsed) && parsed >= 0 ? parsed : 0;
|
|
12
|
+
}
|
|
13
|
+
function readRunId(url) {
|
|
14
|
+
return url.searchParams.get('runId');
|
|
15
|
+
}
|
|
16
|
+
export async function handleEventsApi(req, runtime, store) {
|
|
17
|
+
const url = new URL(req.url);
|
|
18
|
+
if (req.method === 'HEAD') {
|
|
19
|
+
return new Response(null, { status: 200 });
|
|
20
|
+
}
|
|
21
|
+
if (req.method === 'GET') {
|
|
22
|
+
const runId = readRunId(url);
|
|
23
|
+
if (!runId) {
|
|
24
|
+
return Response.json({ error: 'runId required' }, { status: 400 });
|
|
25
|
+
}
|
|
26
|
+
const cursor = readCursor(req, url);
|
|
27
|
+
const acceptHeader = req.headers.get('accept') ?? '';
|
|
28
|
+
const isLive = url.searchParams.get('live') === 'true' ||
|
|
29
|
+
url.searchParams.get('live') === '1' ||
|
|
30
|
+
acceptHeader.includes('text/event-stream');
|
|
31
|
+
if (!isLive) {
|
|
32
|
+
const events = await Effect.runPromise(store.read(runId, { fromSeq: cursor + 1 }));
|
|
33
|
+
const bounds = store.bounds
|
|
34
|
+
? await Effect.runPromise(store.bounds(runId))
|
|
35
|
+
: { head: events[0]?.seq ?? 1, tail: await Effect.runPromise(store.tail(runId)) };
|
|
36
|
+
return Response.json({
|
|
37
|
+
batch: events.map(encodeLoomsEvent),
|
|
38
|
+
head: bounds.head,
|
|
39
|
+
tail: bounds.tail,
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
return createEventStreamResponse({
|
|
43
|
+
signal: req.signal,
|
|
44
|
+
request: req,
|
|
45
|
+
store,
|
|
46
|
+
runId,
|
|
47
|
+
fromSeq: cursor + 1,
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
if (req.method === 'POST') {
|
|
51
|
+
const body = await req.json();
|
|
52
|
+
const runId = readRunId(url) ??
|
|
53
|
+
(Predicate.isReadonlyObject(body) && Predicate.isString(body.runId) ? body.runId : null);
|
|
54
|
+
if (!runId) {
|
|
55
|
+
return Response.json({ error: 'runId required' }, { status: 400 });
|
|
56
|
+
}
|
|
57
|
+
const batch = Predicate.isReadonlyObject(body) && Array.isArray(body.batch) ? body.batch : [];
|
|
58
|
+
const expectedTail = Predicate.isReadonlyObject(body) && Predicate.isNumber(body.expectedTail)
|
|
59
|
+
? body.expectedTail
|
|
60
|
+
: 0;
|
|
61
|
+
const tail = await Effect.runPromise(store.tail(runId));
|
|
62
|
+
if (expectedTail !== tail) {
|
|
63
|
+
return Response.json({ _tag: 'ServerAheadError', expected: expectedTail, actual: tail }, { status: 409 });
|
|
64
|
+
}
|
|
65
|
+
const appendable = batch
|
|
66
|
+
.map((item) => {
|
|
67
|
+
// SAFETY: push batch items are host-encoded envelopes.
|
|
68
|
+
return decodeAppendableEvent(item, runId);
|
|
69
|
+
})
|
|
70
|
+
.filter((item) => item !== undefined);
|
|
71
|
+
try {
|
|
72
|
+
await Effect.runPromise(store.append(runId, appendable, { expectedTail }));
|
|
73
|
+
}
|
|
74
|
+
catch (err) {
|
|
75
|
+
if (err instanceof EventStoreConflictError) {
|
|
76
|
+
return Response.json({ _tag: 'ServerAheadError', expected: err.expectedTail, actual: err.actualTail }, { status: 409 });
|
|
77
|
+
}
|
|
78
|
+
throw err;
|
|
79
|
+
}
|
|
80
|
+
await Effect.runPromise(Effect.provideService(runtime.wake(runId), EventStoreTag, store));
|
|
81
|
+
return Response.json({ ok: true });
|
|
82
|
+
}
|
|
83
|
+
return new Response('Method Not Allowed', { status: 405 });
|
|
84
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { createRuntime, createTimeoutScheduler, DuplicateEffectDispatchError, MaxWakeIterationsError, type CreateRuntimeOptions, type LoomsRuntime, type RegisteredDefinition, type StartRunArgs, type WakeError, type WakeScheduler, } from './runtime';
|
|
2
|
+
export { createLooms, type Looms, type CreateLoomsOptions, type StartResult } from './looms';
|
|
3
|
+
export { serveHttp, createFetchHandler, isLoomsApiPath, type FetchHandlerOptions, type RunningServer, type LoomsFetchResult, } from './server';
|
|
4
|
+
export { handleEventsApi, encodeLoomsEvent, type EncodedLoomsEvent } from './events-api';
|
|
5
|
+
export { createEventStreamResponse, type EventStreamOptions } from './sse';
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { createRuntime, createTimeoutScheduler, DuplicateEffectDispatchError, MaxWakeIterationsError, } from './runtime.js';
|
|
2
|
+
export { createLooms } from './looms.js';
|
|
3
|
+
export { serveHttp, createFetchHandler, isLoomsApiPath, } from './server.js';
|
|
4
|
+
export { handleEventsApi, encodeLoomsEvent } from './events-api.js';
|
|
5
|
+
export { createEventStreamResponse } from './sse.js';
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { type AnyRuntimeModule, type DefinitionInput, type DefinitionRef, type EventEnvelope, type EventInput, type EventStore, type JsonValue, type SnapshotStore, type ProjectionDefinition, type ReplayStep, type RunState } from '@swirls/looms/core';
|
|
2
|
+
import { type LoomsRuntime, type WakeScheduler } from './runtime';
|
|
3
|
+
import { type RunningServer } from './server';
|
|
4
|
+
export interface CreateLoomsOptions {
|
|
5
|
+
/** Runtime modules to enable. Pass `agent()`, `workflow()`, `approval()`, and/or your own. */
|
|
6
|
+
readonly modules?: readonly AnyRuntimeModule[];
|
|
7
|
+
readonly store?: EventStore | Promise<EventStore> | (() => Promise<EventStore>);
|
|
8
|
+
readonly serve?: boolean | {
|
|
9
|
+
port?: number;
|
|
10
|
+
hostname?: string;
|
|
11
|
+
};
|
|
12
|
+
readonly maxWakeIterations?: number;
|
|
13
|
+
/** Durable events between mid-wake snapshots. Default 200; `0` disables. Parking always snapshots. */
|
|
14
|
+
readonly snapshotEvery?: number;
|
|
15
|
+
/** Defaults to the EventStore's attached store (S2) or an in-memory SnapshotStore. */
|
|
16
|
+
readonly snapshotStore?: SnapshotStore;
|
|
17
|
+
readonly trimAfterSnapshot?: {
|
|
18
|
+
keepSnapshots: number;
|
|
19
|
+
};
|
|
20
|
+
/** In-process cursor cache size. Default `0` (stateless between calls). */
|
|
21
|
+
readonly runCacheSize?: number;
|
|
22
|
+
/** Scan existing runs for pending timers on boot. Default true. */
|
|
23
|
+
readonly rescanTimers?: boolean;
|
|
24
|
+
/** Pluggable wake scheduler for timer waits. Defaults to an in-process setTimeout scheduler. */
|
|
25
|
+
readonly scheduler?: WakeScheduler;
|
|
26
|
+
}
|
|
27
|
+
export interface StartResult {
|
|
28
|
+
readonly runId: string;
|
|
29
|
+
readonly threadId: string;
|
|
30
|
+
readonly state: RunState;
|
|
31
|
+
}
|
|
32
|
+
export interface Looms {
|
|
33
|
+
readonly runtime: Promise<LoomsRuntime>;
|
|
34
|
+
readonly store: Promise<EventStore>;
|
|
35
|
+
ready(): Promise<{
|
|
36
|
+
store: EventStore;
|
|
37
|
+
runtime: LoomsRuntime;
|
|
38
|
+
}>;
|
|
39
|
+
/** Start a run from a definition object; the input type follows the definition's schema. */
|
|
40
|
+
start<TDef extends DefinitionRef>(definition: TDef, input?: DefinitionInput<TDef>, options?: {
|
|
41
|
+
runId?: string;
|
|
42
|
+
threadId?: string;
|
|
43
|
+
idempotencyKey?: string;
|
|
44
|
+
}): Promise<StartResult>;
|
|
45
|
+
/** Start a run by `{ kind, definitionName }` when you only have names (HTTP bodies, CLIs). */
|
|
46
|
+
startRun(args: {
|
|
47
|
+
kind: string;
|
|
48
|
+
definitionName: string;
|
|
49
|
+
input?: JsonValue;
|
|
50
|
+
runId?: string;
|
|
51
|
+
threadId?: string;
|
|
52
|
+
idempotencyKey?: string;
|
|
53
|
+
}): Promise<StartResult>;
|
|
54
|
+
getRun(runId: string): Promise<RunState>;
|
|
55
|
+
getEvents(runId: string, options?: {
|
|
56
|
+
fromSeq?: number;
|
|
57
|
+
limit?: number;
|
|
58
|
+
}): Promise<EventEnvelope[]>;
|
|
59
|
+
signal(runId: string, events: ReadonlyArray<EventInput>, options?: {
|
|
60
|
+
idempotencyKey?: string;
|
|
61
|
+
}): Promise<RunState>;
|
|
62
|
+
wake(runId: string): Promise<RunState>;
|
|
63
|
+
rescanTimers(): Promise<number>;
|
|
64
|
+
project<S>(runId: string, definition: ProjectionDefinition<S>): Promise<S>;
|
|
65
|
+
replayTo(runId: string, seq: number): Promise<ReplayStep | null>;
|
|
66
|
+
cancel(runId: string, threadId?: string): Promise<RunState>;
|
|
67
|
+
fetch(req: Request): Promise<Response | null>;
|
|
68
|
+
serve(options?: {
|
|
69
|
+
port?: number;
|
|
70
|
+
hostname?: string;
|
|
71
|
+
}): RunningServer;
|
|
72
|
+
stop(): Promise<void>;
|
|
73
|
+
}
|
|
74
|
+
export declare function createLooms(options?: CreateLoomsOptions): Looms;
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { Effect, Predicate } from 'effect';
|
|
2
|
+
import { EventStoreTag, makeMemoryEventStore, snapshotStoreOf, SnapshotStoreTag, } from '@swirls/looms/core';
|
|
3
|
+
import { createRuntime } from './runtime.js';
|
|
4
|
+
import { createFetchHandler, isLoomsApiPath, serveHttp } from './server.js';
|
|
5
|
+
export function createLooms(options = {}) {
|
|
6
|
+
const modules = options.modules ?? [];
|
|
7
|
+
let initPromise;
|
|
8
|
+
let runningServer;
|
|
9
|
+
const getInit = () => {
|
|
10
|
+
if (!initPromise) {
|
|
11
|
+
initPromise = (async () => {
|
|
12
|
+
let store;
|
|
13
|
+
if (!options.store) {
|
|
14
|
+
store = await Effect.runPromise(makeMemoryEventStore);
|
|
15
|
+
}
|
|
16
|
+
else if (Predicate.isFunction(options.store)) {
|
|
17
|
+
store = await options.store();
|
|
18
|
+
}
|
|
19
|
+
else {
|
|
20
|
+
store = await options.store;
|
|
21
|
+
}
|
|
22
|
+
const runtime = createRuntime({
|
|
23
|
+
modules,
|
|
24
|
+
store,
|
|
25
|
+
maxWakeIterations: options.maxWakeIterations,
|
|
26
|
+
snapshotEvery: options.snapshotEvery,
|
|
27
|
+
snapshotStore: options.snapshotStore ?? snapshotStoreOf(store),
|
|
28
|
+
trimAfterSnapshot: options.trimAfterSnapshot,
|
|
29
|
+
runCacheSize: options.runCacheSize,
|
|
30
|
+
scheduler: options.scheduler,
|
|
31
|
+
});
|
|
32
|
+
if (options.rescanTimers !== false) {
|
|
33
|
+
await Effect.runPromise(Effect.provideService(runtime.rescanTimers(), EventStoreTag, store)).catch((err) => {
|
|
34
|
+
console.error('[rescan timers error]', err);
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
const fetchHandler = createFetchHandler({ runtime, store });
|
|
38
|
+
return { runtime, store, snapshotStore: snapshotStoreOf(store), fetchHandler };
|
|
39
|
+
})();
|
|
40
|
+
}
|
|
41
|
+
return initPromise;
|
|
42
|
+
};
|
|
43
|
+
const runEffect = async (fn) => {
|
|
44
|
+
const init = await getInit();
|
|
45
|
+
let provided = Effect.provideService(fn(init), EventStoreTag, init.store);
|
|
46
|
+
if (init.snapshotStore) {
|
|
47
|
+
provided = Effect.provideService(provided, SnapshotStoreTag, init.snapshotStore);
|
|
48
|
+
}
|
|
49
|
+
return Effect.runPromise(provided);
|
|
50
|
+
};
|
|
51
|
+
const looms = {
|
|
52
|
+
get runtime() {
|
|
53
|
+
return getInit().then((i) => i.runtime);
|
|
54
|
+
},
|
|
55
|
+
get store() {
|
|
56
|
+
return getInit().then((i) => i.store);
|
|
57
|
+
},
|
|
58
|
+
ready: async () => {
|
|
59
|
+
const init = await getInit();
|
|
60
|
+
return { store: init.store, runtime: init.runtime };
|
|
61
|
+
},
|
|
62
|
+
startRun: (args) => runEffect((i) => i.runtime.startRun(args)),
|
|
63
|
+
start: (definition, input, startOpts) => runEffect((i) => i.runtime.startRun({
|
|
64
|
+
kind: definition.kind,
|
|
65
|
+
definitionName: definition.name,
|
|
66
|
+
// SAFETY: input is validated against the definition's schema inside startRun.
|
|
67
|
+
input: input,
|
|
68
|
+
runId: startOpts?.runId,
|
|
69
|
+
threadId: startOpts?.threadId,
|
|
70
|
+
idempotencyKey: startOpts?.idempotencyKey,
|
|
71
|
+
})),
|
|
72
|
+
getRun: (runId) => runEffect((i) => i.runtime.getRun(runId)),
|
|
73
|
+
getEvents: (runId, opts) => runEffect((i) => i.runtime.getEvents(runId, opts)),
|
|
74
|
+
signal: (runId, events, signalOpts) => runEffect((i) => i.runtime.signal(runId, events, signalOpts)),
|
|
75
|
+
wake: (runId) => runEffect((i) => i.runtime.wake(runId)),
|
|
76
|
+
rescanTimers: () => runEffect((i) => i.runtime.rescanTimers()),
|
|
77
|
+
project: (runId, definition) => runEffect((i) => i.runtime.project(runId, definition)),
|
|
78
|
+
replayTo: (runId, seq) => runEffect((i) => i.runtime.replayTo(runId, seq)),
|
|
79
|
+
cancel: (runId, threadId) => runEffect((i) => i.runtime.cancel(runId, threadId)),
|
|
80
|
+
fetch: async (req) => {
|
|
81
|
+
if (!isLoomsApiPath(new URL(req.url).pathname)) {
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
const { fetchHandler } = await getInit();
|
|
85
|
+
return fetchHandler(req);
|
|
86
|
+
},
|
|
87
|
+
serve: (serveOpts) => {
|
|
88
|
+
if (runningServer) {
|
|
89
|
+
return runningServer;
|
|
90
|
+
}
|
|
91
|
+
runningServer = serveHttp((req) => looms.fetch(req), serveOpts);
|
|
92
|
+
return runningServer;
|
|
93
|
+
},
|
|
94
|
+
stop: async () => {
|
|
95
|
+
if (runningServer) {
|
|
96
|
+
runningServer.stop();
|
|
97
|
+
runningServer = undefined;
|
|
98
|
+
}
|
|
99
|
+
if (initPromise) {
|
|
100
|
+
const init = await initPromise;
|
|
101
|
+
init.runtime.dispose();
|
|
102
|
+
}
|
|
103
|
+
},
|
|
104
|
+
};
|
|
105
|
+
if (options.serve) {
|
|
106
|
+
looms.serve(options.serve === true ? {} : options.serve);
|
|
107
|
+
}
|
|
108
|
+
return looms;
|
|
109
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { Effect, Layer } from 'effect';
|
|
2
|
+
import { type AnyRuntimeModule, type AppendableEvent, type EventCatalog, type EventEnvelope, type EventInput, type EventOrigin, type EventStore, type InvalidEventError, type JsonValue, type RegisteredDefinition, type RunCursor, type RunState, type SnapshotStore } from '@swirls/looms/core';
|
|
3
|
+
export declare function resolveSnapshotStore(explicit: SnapshotStore | undefined, store: EventStore | undefined): SnapshotStore;
|
|
4
|
+
export declare class RunCursorCache {
|
|
5
|
+
private readonly max;
|
|
6
|
+
private readonly map;
|
|
7
|
+
constructor(max: number);
|
|
8
|
+
get(runId: string): RunCursor | undefined;
|
|
9
|
+
set(runId: string, cursor: RunCursor): void;
|
|
10
|
+
delete(runId: string): void;
|
|
11
|
+
}
|
|
12
|
+
export declare function stripSeq(events: ReadonlyArray<EventEnvelope>): AppendableEvent[];
|
|
13
|
+
export declare function moduleServices(modules: readonly AnyRuntimeModule[]): Layer.Layer<any>;
|
|
14
|
+
export declare function groupOutstanding(items: readonly {
|
|
15
|
+
threadId: string;
|
|
16
|
+
causingSeq: number;
|
|
17
|
+
}[]): string[];
|
|
18
|
+
/**
|
|
19
|
+
* Every `runtime.thread.started` in the log goes through this helper so
|
|
20
|
+
* definition input is validated and Standard Schema defaults are applied.
|
|
21
|
+
* A future third producer of thread.started must call this; do not emit the
|
|
22
|
+
* event with raw input.
|
|
23
|
+
*/
|
|
24
|
+
export declare function threadStartedEvents(definitions: ReadonlyMap<string, RegisteredDefinition>, args: {
|
|
25
|
+
kind: string;
|
|
26
|
+
definitionName: string;
|
|
27
|
+
input: JsonValue;
|
|
28
|
+
threadId: string;
|
|
29
|
+
parentThreadId: string | null;
|
|
30
|
+
}): Effect.Effect<ReadonlyArray<EventInput>, Error>;
|
|
31
|
+
export declare function synthesizedThreadFailed(before: RunState, after: RunState, written: readonly EventEnvelope[]): EventInput[];
|
|
32
|
+
export declare function waitSatisfiedEvents(state: RunState, events: readonly EventEnvelope[]): EventInput[];
|
|
33
|
+
export declare function createEffectFailedEvent(runId: string, item: {
|
|
34
|
+
effectId: string;
|
|
35
|
+
causingEventId: string;
|
|
36
|
+
threadId: string;
|
|
37
|
+
}, error: string): EventEnvelope;
|
|
38
|
+
export declare function materializeEffectOutcome(catalogs: readonly EventCatalog[], runId: string, item: {
|
|
39
|
+
effectId: string;
|
|
40
|
+
causingEventId: string;
|
|
41
|
+
threadId: string;
|
|
42
|
+
}, input: EventInput): Effect.Effect<EventEnvelope>;
|
|
43
|
+
export declare function createLiveEvent(runId: string, item: {
|
|
44
|
+
effectId: string;
|
|
45
|
+
causingEventId: string;
|
|
46
|
+
threadId: string;
|
|
47
|
+
}, validated: EventInput): AppendableEvent | undefined;
|
|
48
|
+
export declare function validateAndCreateEvents(catalogs: readonly EventCatalog[], runId: string, events: readonly EventInput[], defaults: {
|
|
49
|
+
origin: EventOrigin;
|
|
50
|
+
threadId?: string | null;
|
|
51
|
+
}): Effect.Effect<EventEnvelope[], InvalidEventError>;
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
import { Effect, Layer, Predicate } from 'effect';
|
|
2
|
+
import { asJson, createEvent, makeMemorySnapshotStore, matchingWaits, snapshotStoreOf, validateEventInput, validateEventsEffect, validateInput, withSnapshotStore, } from '@swirls/looms/core';
|
|
3
|
+
export function resolveSnapshotStore(explicit, store) {
|
|
4
|
+
if (explicit) {
|
|
5
|
+
if (store) {
|
|
6
|
+
withSnapshotStore(store, explicit);
|
|
7
|
+
}
|
|
8
|
+
return explicit;
|
|
9
|
+
}
|
|
10
|
+
const attached = store ? snapshotStoreOf(store) : undefined;
|
|
11
|
+
if (attached) {
|
|
12
|
+
return attached;
|
|
13
|
+
}
|
|
14
|
+
const memory = Effect.runSync(makeMemorySnapshotStore);
|
|
15
|
+
if (store) {
|
|
16
|
+
withSnapshotStore(store, memory);
|
|
17
|
+
}
|
|
18
|
+
return memory;
|
|
19
|
+
}
|
|
20
|
+
export class RunCursorCache {
|
|
21
|
+
max;
|
|
22
|
+
map = new Map();
|
|
23
|
+
constructor(max) {
|
|
24
|
+
this.max = max;
|
|
25
|
+
}
|
|
26
|
+
get(runId) {
|
|
27
|
+
const value = this.map.get(runId);
|
|
28
|
+
if (value) {
|
|
29
|
+
this.map.delete(runId);
|
|
30
|
+
this.map.set(runId, value);
|
|
31
|
+
}
|
|
32
|
+
return value;
|
|
33
|
+
}
|
|
34
|
+
set(runId, cursor) {
|
|
35
|
+
if (this.max <= 0) {
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
this.map.delete(runId);
|
|
39
|
+
this.map.set(runId, cursor);
|
|
40
|
+
while (this.map.size > this.max) {
|
|
41
|
+
const oldest = this.map.keys().next().value;
|
|
42
|
+
if (oldest === undefined) {
|
|
43
|
+
break;
|
|
44
|
+
}
|
|
45
|
+
this.map.delete(oldest);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
delete(runId) {
|
|
49
|
+
this.map.delete(runId);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
export function stripSeq(events) {
|
|
53
|
+
return events.map(({ seq: _seq, ...rest }) => rest);
|
|
54
|
+
}
|
|
55
|
+
export function moduleServices(modules) {
|
|
56
|
+
// SAFETY: Empty layer serves as seed; module service layers are merged in.
|
|
57
|
+
let merged = Layer.empty;
|
|
58
|
+
for (const module of modules) {
|
|
59
|
+
if (module.services) {
|
|
60
|
+
merged = Layer.merge(merged, module.services());
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return merged;
|
|
64
|
+
}
|
|
65
|
+
export function groupOutstanding(items) {
|
|
66
|
+
const keys = [];
|
|
67
|
+
const seen = new Set();
|
|
68
|
+
for (const item of items) {
|
|
69
|
+
const key = `${item.threadId}:${item.causingSeq}`;
|
|
70
|
+
if (seen.has(key)) {
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
seen.add(key);
|
|
74
|
+
keys.push(key);
|
|
75
|
+
}
|
|
76
|
+
return keys;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Every `runtime.thread.started` in the log goes through this helper so
|
|
80
|
+
* definition input is validated and Standard Schema defaults are applied.
|
|
81
|
+
* A future third producer of thread.started must call this; do not emit the
|
|
82
|
+
* event with raw input.
|
|
83
|
+
*/
|
|
84
|
+
export function threadStartedEvents(definitions, args) {
|
|
85
|
+
return Effect.gen(function* () {
|
|
86
|
+
const def = definitions.get(`${args.kind}:${args.definitionName}`);
|
|
87
|
+
if (!def) {
|
|
88
|
+
return yield* Effect.fail(new Error(`Unknown definition ${args.kind}:${args.definitionName}`));
|
|
89
|
+
}
|
|
90
|
+
const raw = args.input ?? null;
|
|
91
|
+
let startedInput = raw;
|
|
92
|
+
let validationError;
|
|
93
|
+
if (def?.input) {
|
|
94
|
+
const validated = yield* Effect.tryPromise({
|
|
95
|
+
try: () => {
|
|
96
|
+
// SAFETY: RegisteredDefinition.input is a Standard Schema when present.
|
|
97
|
+
return validateInput(def.input, raw);
|
|
98
|
+
},
|
|
99
|
+
catch: (err) => (err instanceof Error ? err : new Error(String(err))),
|
|
100
|
+
}).pipe(Effect.map((value) => ({ ok: true, value })), Effect.catch((err) => Effect.succeed({ ok: false, error: err.message })));
|
|
101
|
+
if (validated.ok) {
|
|
102
|
+
startedInput = validated.value;
|
|
103
|
+
}
|
|
104
|
+
else {
|
|
105
|
+
validationError = validated.error;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
const started = {
|
|
109
|
+
type: 'runtime.thread.started',
|
|
110
|
+
payload: {
|
|
111
|
+
threadId: args.threadId,
|
|
112
|
+
kind: args.kind,
|
|
113
|
+
definitionName: args.definitionName,
|
|
114
|
+
input: startedInput,
|
|
115
|
+
parentThreadId: args.parentThreadId,
|
|
116
|
+
},
|
|
117
|
+
threadId: args.threadId,
|
|
118
|
+
parentThreadId: args.parentThreadId,
|
|
119
|
+
};
|
|
120
|
+
if (!validationError) {
|
|
121
|
+
return [started];
|
|
122
|
+
}
|
|
123
|
+
return [
|
|
124
|
+
started,
|
|
125
|
+
{
|
|
126
|
+
type: 'runtime.thread.failed',
|
|
127
|
+
payload: { threadId: args.threadId, error: validationError },
|
|
128
|
+
threadId: args.threadId,
|
|
129
|
+
},
|
|
130
|
+
];
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
export function synthesizedThreadFailed(before, after, written) {
|
|
134
|
+
const out = [];
|
|
135
|
+
for (const [threadId, thread] of Object.entries(after.threads)) {
|
|
136
|
+
if (thread.status !== 'failed') {
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
const prev = before.threads[threadId];
|
|
140
|
+
if (prev?.status === 'failed') {
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
const already = written.some((event) => {
|
|
144
|
+
if (event.type !== 'runtime.thread.failed') {
|
|
145
|
+
return false;
|
|
146
|
+
}
|
|
147
|
+
if (event.threadId === threadId) {
|
|
148
|
+
return true;
|
|
149
|
+
}
|
|
150
|
+
return Predicate.isObject(event.payload) && event.payload.threadId === threadId;
|
|
151
|
+
});
|
|
152
|
+
if (already) {
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
out.push({
|
|
156
|
+
type: 'runtime.thread.failed',
|
|
157
|
+
payload: { threadId, error: thread.error ?? 'failed' },
|
|
158
|
+
threadId,
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
return out;
|
|
162
|
+
}
|
|
163
|
+
export function waitSatisfiedEvents(state, events) {
|
|
164
|
+
const produced = [];
|
|
165
|
+
const remaining = { ...state.waits };
|
|
166
|
+
for (const event of events) {
|
|
167
|
+
if (event.ephemeral || event.type === 'runtime.wait.satisfied') {
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
const matches = matchingWaits(event, Object.values(remaining));
|
|
171
|
+
for (const record of matches) {
|
|
172
|
+
produced.push({
|
|
173
|
+
type: 'runtime.wait.satisfied',
|
|
174
|
+
payload: asJson({
|
|
175
|
+
waitId: record.waitId,
|
|
176
|
+
tag: record.tag ?? null,
|
|
177
|
+
event: { id: event.id, type: event.type, payload: event.payload },
|
|
178
|
+
}),
|
|
179
|
+
threadId: record.threadId,
|
|
180
|
+
causationId: event.id,
|
|
181
|
+
});
|
|
182
|
+
delete remaining[record.waitId];
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
return produced;
|
|
186
|
+
}
|
|
187
|
+
export function createEffectFailedEvent(runId, item, error) {
|
|
188
|
+
return createEvent(runId, {
|
|
189
|
+
type: 'runtime.effect.failed',
|
|
190
|
+
payload: {
|
|
191
|
+
effectId: item.effectId,
|
|
192
|
+
error,
|
|
193
|
+
},
|
|
194
|
+
threadId: item.threadId,
|
|
195
|
+
effectId: item.effectId,
|
|
196
|
+
causationId: item.causingEventId,
|
|
197
|
+
origin: { type: 'system' },
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
export function materializeEffectOutcome(catalogs, runId, item, input) {
|
|
201
|
+
return validateEventInput(catalogs, input).pipe(Effect.map((validated) => createEvent(runId, {
|
|
202
|
+
...validated,
|
|
203
|
+
effectId: validated.effectId ?? item.effectId,
|
|
204
|
+
causationId: validated.causationId ?? item.causingEventId,
|
|
205
|
+
threadId: validated.threadId ?? item.threadId,
|
|
206
|
+
origin: validated.origin ?? { type: 'thread', threadId: item.threadId },
|
|
207
|
+
id: validated.id,
|
|
208
|
+
ts: validated.ts,
|
|
209
|
+
})), Effect.catchTag('InvalidEventError', (err) => Effect.succeed(createEffectFailedEvent(runId, item, err.message))));
|
|
210
|
+
}
|
|
211
|
+
export function createLiveEvent(runId, item, validated) {
|
|
212
|
+
const targetThreadId = validated.threadId ?? item.threadId;
|
|
213
|
+
const ephemeral = createEvent(runId, {
|
|
214
|
+
...validated,
|
|
215
|
+
effectId: item.effectId,
|
|
216
|
+
causationId: item.causingEventId,
|
|
217
|
+
threadId: targetThreadId,
|
|
218
|
+
ephemeral: validated.ephemeral ?? true,
|
|
219
|
+
origin: validated.origin ?? { type: 'thread', threadId: item.threadId },
|
|
220
|
+
});
|
|
221
|
+
const [liveEvent] = stripSeq([ephemeral]);
|
|
222
|
+
return liveEvent;
|
|
223
|
+
}
|
|
224
|
+
export function validateAndCreateEvents(catalogs, runId, events, defaults) {
|
|
225
|
+
return validateEventsEffect(catalogs, events).pipe(Effect.map((validated) => validated.map((input) => createEvent(runId, {
|
|
226
|
+
...input,
|
|
227
|
+
threadId: input.threadId !== undefined ? input.threadId : (defaults.threadId ?? null),
|
|
228
|
+
origin: input.origin ?? defaults.origin,
|
|
229
|
+
}))));
|
|
230
|
+
}
|