@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,187 @@
|
|
|
1
|
+
import { createContext, createElement, useCallback, useContext, useEffect, useMemo, useRef, useSyncExternalStore, } from 'react';
|
|
2
|
+
import { threadTree, toThreadTree } from '@swirls/looms/core';
|
|
3
|
+
import { createFold, foldEvents } from './derived.js';
|
|
4
|
+
import { createLoomsStore, } from './store.js';
|
|
5
|
+
const LoomsContext = createContext({ endpoint: '' });
|
|
6
|
+
const GRACE_MS = 5_000;
|
|
7
|
+
const registry = new Map();
|
|
8
|
+
function registryKey(endpoint, runId) {
|
|
9
|
+
return `${endpoint}\0${runId}`;
|
|
10
|
+
}
|
|
11
|
+
function disposeEntry(key) {
|
|
12
|
+
const entry = registry.get(key);
|
|
13
|
+
if (!entry || entry.refs > 0) {
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
entry.store.dispose();
|
|
17
|
+
registry.delete(key);
|
|
18
|
+
}
|
|
19
|
+
function peekStore(runId, endpoint, reconnectDelayMs, coalesce) {
|
|
20
|
+
const key = registryKey(endpoint, runId);
|
|
21
|
+
const existing = registry.get(key);
|
|
22
|
+
if (existing) {
|
|
23
|
+
// SAFETY: registry stores share the app-registered event catalog.
|
|
24
|
+
return existing.store;
|
|
25
|
+
}
|
|
26
|
+
const store = createLoomsStore({
|
|
27
|
+
storeId: runId,
|
|
28
|
+
endpoint,
|
|
29
|
+
reconnectDelayMs,
|
|
30
|
+
coalesce,
|
|
31
|
+
});
|
|
32
|
+
registry.set(key, {
|
|
33
|
+
// SAFETY: store is created with TEvent which defaults to RegisteredEvent.
|
|
34
|
+
store: store,
|
|
35
|
+
refs: 0,
|
|
36
|
+
timer: setTimeout(() => disposeEntry(key), GRACE_MS),
|
|
37
|
+
});
|
|
38
|
+
return store;
|
|
39
|
+
}
|
|
40
|
+
function retainStore(runId, endpoint, reconnectDelayMs, coalesce) {
|
|
41
|
+
const key = registryKey(endpoint, runId);
|
|
42
|
+
peekStore(runId, endpoint, reconnectDelayMs, coalesce);
|
|
43
|
+
const entry = registry.get(key);
|
|
44
|
+
if (!entry) {
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
entry.refs += 1;
|
|
48
|
+
if (entry.timer !== undefined) {
|
|
49
|
+
clearTimeout(entry.timer);
|
|
50
|
+
entry.timer = undefined;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
function releaseStore(runId, endpoint) {
|
|
54
|
+
const key = registryKey(endpoint, runId);
|
|
55
|
+
const entry = registry.get(key);
|
|
56
|
+
if (!entry) {
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
entry.refs = Math.max(0, entry.refs - 1);
|
|
60
|
+
if (entry.refs === 0 && entry.timer === undefined) {
|
|
61
|
+
entry.timer = setTimeout(() => disposeEntry(key), GRACE_MS);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
/** Root provider that supplies the Looms host endpoint to run stores. */
|
|
65
|
+
export function LoomsProvider({ children, endpoint = '', coalesce }) {
|
|
66
|
+
const value = useMemo(() => ({ endpoint, coalesce }), [endpoint, coalesce]);
|
|
67
|
+
return createElement(LoomsContext.Provider, { value, children });
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Acquires a non-subscribing handle to the run store.
|
|
71
|
+
* Retains the store in memory while mounted; does NOT trigger component re-renders.
|
|
72
|
+
* Use data hooks (`useRunEvents`, `useProjection`, `useRunSummary`, etc.) to subscribe.
|
|
73
|
+
*/
|
|
74
|
+
export function useRunStore(runId, options) {
|
|
75
|
+
const context = useContext(LoomsContext);
|
|
76
|
+
const endpoint = options?.endpoint ?? context.endpoint;
|
|
77
|
+
const reconnectDelayMs = options?.reconnectDelayMs;
|
|
78
|
+
const coalesce = options?.coalesce ?? context.coalesce;
|
|
79
|
+
const store = peekStore(runId, endpoint, reconnectDelayMs, coalesce);
|
|
80
|
+
useEffect(() => {
|
|
81
|
+
retainStore(runId, endpoint, reconnectDelayMs, coalesce);
|
|
82
|
+
return () => releaseStore(runId, endpoint);
|
|
83
|
+
}, [runId, endpoint, reconnectDelayMs, coalesce]);
|
|
84
|
+
return store;
|
|
85
|
+
}
|
|
86
|
+
function useStoreSnapshot(store, getSnapshot) {
|
|
87
|
+
const subscribe = useCallback((onChange) => store.subscribe(onChange), [store]);
|
|
88
|
+
return useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
|
|
89
|
+
}
|
|
90
|
+
function identitySelector(value) {
|
|
91
|
+
return value;
|
|
92
|
+
}
|
|
93
|
+
export function useProjection(store, definition, selector, isEqual = Object.is) {
|
|
94
|
+
const lastSelected = useRef(undefined);
|
|
95
|
+
const lastState = useRef(undefined);
|
|
96
|
+
// SAFETY: When selector is undefined, Selected equals S so identitySelector satisfies the signature.
|
|
97
|
+
const select = selector ?? identitySelector;
|
|
98
|
+
const getSnapshot = useCallback(() => {
|
|
99
|
+
const state = store.project(definition);
|
|
100
|
+
if (lastState.current === state && lastSelected.current !== undefined) {
|
|
101
|
+
return lastSelected.current;
|
|
102
|
+
}
|
|
103
|
+
const nextSelected = select(state);
|
|
104
|
+
lastState.current = state;
|
|
105
|
+
if (lastSelected.current !== undefined && isEqual(lastSelected.current, nextSelected)) {
|
|
106
|
+
return lastSelected.current;
|
|
107
|
+
}
|
|
108
|
+
lastSelected.current = nextSelected;
|
|
109
|
+
return nextSelected;
|
|
110
|
+
}, [store, definition, select, isEqual]);
|
|
111
|
+
return useStoreSnapshot(store, getSnapshot);
|
|
112
|
+
}
|
|
113
|
+
/** Subscribe to arbitrary store derivations with an equality check. */
|
|
114
|
+
export function useRunSelector(store, selector, isEqual = Object.is) {
|
|
115
|
+
const lastSelected = useRef(undefined);
|
|
116
|
+
const lastVersion = useRef(-1);
|
|
117
|
+
const getSnapshot = useCallback(() => {
|
|
118
|
+
const version = store.version();
|
|
119
|
+
if (lastVersion.current === version && lastSelected.current !== undefined) {
|
|
120
|
+
return lastSelected.current;
|
|
121
|
+
}
|
|
122
|
+
lastVersion.current = version;
|
|
123
|
+
const nextSelected = selector(store);
|
|
124
|
+
if (lastSelected.current !== undefined && isEqual(lastSelected.current, nextSelected)) {
|
|
125
|
+
return lastSelected.current;
|
|
126
|
+
}
|
|
127
|
+
lastSelected.current = nextSelected;
|
|
128
|
+
return nextSelected;
|
|
129
|
+
}, [store, selector, isEqual]);
|
|
130
|
+
return useStoreSnapshot(store, getSnapshot);
|
|
131
|
+
}
|
|
132
|
+
function shallowEqualSummary(a, b) {
|
|
133
|
+
return (a.status === b.status &&
|
|
134
|
+
a.kind === b.kind &&
|
|
135
|
+
a.definitionName === b.definitionName &&
|
|
136
|
+
a.rootThreadId === b.rootThreadId &&
|
|
137
|
+
a.startedAt === b.startedAt &&
|
|
138
|
+
a.connection === b.connection);
|
|
139
|
+
}
|
|
140
|
+
/** Lightweight subscription to top-level run metadata and connection status. */
|
|
141
|
+
export function useRunSummary(store) {
|
|
142
|
+
const selector = useCallback((s) => {
|
|
143
|
+
const run = s.getState().runs.get(s.storeId);
|
|
144
|
+
return {
|
|
145
|
+
status: run?.status ?? 'running',
|
|
146
|
+
kind: run?.kind ?? undefined,
|
|
147
|
+
definitionName: run?.definitionName ?? undefined,
|
|
148
|
+
rootThreadId: run?.rootThreadId ?? undefined,
|
|
149
|
+
startedAt: s.query.startedAt(),
|
|
150
|
+
connection: s.status(),
|
|
151
|
+
};
|
|
152
|
+
}, []);
|
|
153
|
+
return useRunSelector(store, selector, shallowEqualSummary);
|
|
154
|
+
}
|
|
155
|
+
/** Subscribe to the event log, optionally scoped to a single thread. */
|
|
156
|
+
export function useRunEvents(store, options) {
|
|
157
|
+
const threadId = options?.threadId;
|
|
158
|
+
const getSnapshot = useCallback(() => store.events({ threadId }), [store, threadId]);
|
|
159
|
+
return useStoreSnapshot(store, getSnapshot);
|
|
160
|
+
}
|
|
161
|
+
/** Subscribe to the thread hierarchy tree. */
|
|
162
|
+
export function useThreadTree(store) {
|
|
163
|
+
const treeState = useProjection(store, threadTree);
|
|
164
|
+
return useMemo(() => toThreadTree(treeState), [treeState]);
|
|
165
|
+
}
|
|
166
|
+
/** Subscribe to event count per thread. */
|
|
167
|
+
export function useEventCounts(store) {
|
|
168
|
+
const getSnapshot = useCallback(() => store.query.counts(), [store]);
|
|
169
|
+
return useStoreSnapshot(store, getSnapshot);
|
|
170
|
+
}
|
|
171
|
+
/** Subscribe to a single event by its sequence number. */
|
|
172
|
+
export function useEventBySeq(store, seq) {
|
|
173
|
+
const selector = useCallback((s) => (seq !== undefined ? s.query.bySeq(seq) : undefined), [seq]);
|
|
174
|
+
return useRunSelector(store, selector);
|
|
175
|
+
}
|
|
176
|
+
/** Subscribe to the latest event of a given type. */
|
|
177
|
+
export function useLatestEvent(store, type) {
|
|
178
|
+
const selector = useCallback((s) => s.query.latest(type), [type]);
|
|
179
|
+
return useRunSelector(store, selector);
|
|
180
|
+
}
|
|
181
|
+
/** Subscribe to a custom incremental fold. */
|
|
182
|
+
export function useEventFold(store, fold) {
|
|
183
|
+
const getSnapshot = useCallback(() => store.fold(fold), [store, fold]);
|
|
184
|
+
return useStoreSnapshot(store, getSnapshot);
|
|
185
|
+
}
|
|
186
|
+
export { createLoomsStore };
|
|
187
|
+
export { createFold, foldEvents };
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { EventEnvelope, EventsOf } from '@swirls/looms/core';
|
|
2
|
+
/** Looser event bound so catalog-inferred payloads (including Schema.Type) are accepted. */
|
|
3
|
+
export type AnyEventEnvelope = EventEnvelope<string, any>;
|
|
4
|
+
/**
|
|
5
|
+
* Application-level type registry for Looms React/client stores.
|
|
6
|
+
*
|
|
7
|
+
* Augment this interface in your app to bind the event catalog used by
|
|
8
|
+
* `useRunEvents`, `createLoomsStore`, and related hooks:
|
|
9
|
+
*
|
|
10
|
+
* ```ts
|
|
11
|
+
* declare module '@swirls/looms/react' {
|
|
12
|
+
* interface LoomsRegister {
|
|
13
|
+
* modules: typeof demoModules
|
|
14
|
+
* }
|
|
15
|
+
* }
|
|
16
|
+
* ```
|
|
17
|
+
*
|
|
18
|
+
* You may register either:
|
|
19
|
+
* - `events`: an explicit event union
|
|
20
|
+
* - `modules`: a module array / creator function (via `EventsOf`)
|
|
21
|
+
* - `catalog`: an event catalog or catalog array (via `EventsOf`)
|
|
22
|
+
*/
|
|
23
|
+
export interface LoomsRegister {
|
|
24
|
+
}
|
|
25
|
+
export type RegisteredEvent = LoomsRegister extends {
|
|
26
|
+
events: infer E;
|
|
27
|
+
} ? E & AnyEventEnvelope : LoomsRegister extends {
|
|
28
|
+
modules: infer M;
|
|
29
|
+
} ? EventsOf<M> & AnyEventEnvelope : LoomsRegister extends {
|
|
30
|
+
catalog: infer C;
|
|
31
|
+
} ? EventsOf<C> & AnyEventEnvelope : EventEnvelope;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { consumeSseStream, delay, eventsFromSseData, parseSseFrame, type SseFrame, } from '@swirls/looms/client';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { consumeSseStream, delay, eventsFromSseData, parseSseFrame, } from '@swirls/looms/client';
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { type EventInput, type ProjectionDefinition } from '@swirls/looms/core';
|
|
2
|
+
import { type EventFoldDefinition } from './derived';
|
|
3
|
+
import { type CoalesceOption } from './notify';
|
|
4
|
+
import type { AnyEventEnvelope, RegisteredEvent } from './register';
|
|
5
|
+
import { type MaterializedTables } from './tables';
|
|
6
|
+
export type { CoalesceOption } from './notify';
|
|
7
|
+
/** Fired after the store version bumps. Re-read via `events()` / `getState()` / `query`. */
|
|
8
|
+
export type StoreListener = () => void;
|
|
9
|
+
export type LoomsConnectionStatus = 'connecting' | 'live' | 'reconnecting';
|
|
10
|
+
export interface LoomsClientStoreOptions {
|
|
11
|
+
/** Run id for this client store. */
|
|
12
|
+
storeId: string;
|
|
13
|
+
/**
|
|
14
|
+
* Base URL of the Looms host (e.g. http://127.0.0.1:8787).
|
|
15
|
+
* Opens `${endpoint}/api/events?runId=&live=true` as an SSE stream.
|
|
16
|
+
*/
|
|
17
|
+
endpoint: string;
|
|
18
|
+
/** Backoff between SSE reconnects. */
|
|
19
|
+
reconnectDelayMs?: number;
|
|
20
|
+
fetch?: typeof fetch;
|
|
21
|
+
coalesce?: CoalesceOption;
|
|
22
|
+
}
|
|
23
|
+
export interface LoomsStoreQuery<TEvent extends AnyEventEnvelope = RegisteredEvent> {
|
|
24
|
+
events: (options?: {
|
|
25
|
+
threadId?: string;
|
|
26
|
+
}) => readonly TEvent[];
|
|
27
|
+
runs: () => MaterializedTables['runs'];
|
|
28
|
+
threads: () => MaterializedTables['threads'];
|
|
29
|
+
counts: () => ReadonlyMap<string, number>;
|
|
30
|
+
bySeq: (seq: number) => TEvent | undefined;
|
|
31
|
+
startedAt: () => number | undefined;
|
|
32
|
+
latest: (type: string) => TEvent | undefined;
|
|
33
|
+
fold: <S>(def: EventFoldDefinition<S, TEvent>) => S;
|
|
34
|
+
}
|
|
35
|
+
export interface LoomsClientStore<TEvent extends AnyEventEnvelope = RegisteredEvent> {
|
|
36
|
+
storeId: string;
|
|
37
|
+
version: () => number;
|
|
38
|
+
status: () => LoomsConnectionStatus;
|
|
39
|
+
getState: () => MaterializedTables;
|
|
40
|
+
events: (options?: {
|
|
41
|
+
threadId?: string;
|
|
42
|
+
}) => readonly TEvent[];
|
|
43
|
+
query: LoomsStoreQuery<TEvent>;
|
|
44
|
+
subscribe: (listener: StoreListener) => () => void;
|
|
45
|
+
project<S>(definition: ProjectionDefinition<S>): S;
|
|
46
|
+
fold<S>(definition: EventFoldDefinition<S, TEvent>): S;
|
|
47
|
+
flush: () => void;
|
|
48
|
+
commit: (event: EventInput) => Promise<void>;
|
|
49
|
+
sync: () => Promise<void>;
|
|
50
|
+
dispose: () => void;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* In-memory client store that follows a run's event log over SSE and
|
|
54
|
+
* materializes generic `runs` / `threads` tables.
|
|
55
|
+
*/
|
|
56
|
+
export declare function createLoomsStore<TEvent extends AnyEventEnvelope = RegisteredEvent>(options: LoomsClientStoreOptions): LoomsClientStore<TEvent>;
|
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
import { Predicate } from 'effect';
|
|
2
|
+
import { decodeLoomsEvent, } from '@swirls/looms/core';
|
|
3
|
+
import { EventIndex, ProjectionCache } from './derived.js';
|
|
4
|
+
import { materializeEvents } from './materialize.js';
|
|
5
|
+
import { createNotifyScheduler } from './notify.js';
|
|
6
|
+
import { consumeSseStream, delay, eventsFromSseData } from './sse.js';
|
|
7
|
+
import { emptyTables } from './tables.js';
|
|
8
|
+
function eventSourceCtor() {
|
|
9
|
+
if (typeof EventSource === 'undefined') {
|
|
10
|
+
return undefined;
|
|
11
|
+
}
|
|
12
|
+
return EventSource;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* In-memory client store that follows a run's event log over SSE and
|
|
16
|
+
* materializes generic `runs` / `threads` tables.
|
|
17
|
+
*/
|
|
18
|
+
export function createLoomsStore(options) {
|
|
19
|
+
const endpoint = options.endpoint.replace(/\/$/, '');
|
|
20
|
+
const fetchFn = options.fetch ?? fetch;
|
|
21
|
+
const reconnectDelayMs = options.reconnectDelayMs ?? 1_000;
|
|
22
|
+
const coalesce = options.coalesce ?? 'adaptive';
|
|
23
|
+
const listeners = new Set();
|
|
24
|
+
const events = [];
|
|
25
|
+
const seenIds = new Set();
|
|
26
|
+
const projectionCache = new ProjectionCache();
|
|
27
|
+
const eventIndex = new EventIndex();
|
|
28
|
+
let eventsSnapshot = Object.freeze([]);
|
|
29
|
+
let eventsSnapshotDirty = false;
|
|
30
|
+
let version = 0;
|
|
31
|
+
let status = 'connecting';
|
|
32
|
+
let tables = emptyTables();
|
|
33
|
+
let fromSeq = 1;
|
|
34
|
+
let disposed = false;
|
|
35
|
+
let live = false;
|
|
36
|
+
let liveStarted = false;
|
|
37
|
+
let liveAbort;
|
|
38
|
+
let eventSource;
|
|
39
|
+
let reconnectTimer;
|
|
40
|
+
let syncing = null;
|
|
41
|
+
const publishedEvents = () => {
|
|
42
|
+
if (!eventsSnapshotDirty) {
|
|
43
|
+
return eventsSnapshot;
|
|
44
|
+
}
|
|
45
|
+
// SAFETY: events array only contains TEvent values pushed from applyBatch.
|
|
46
|
+
eventsSnapshot = Object.freeze(events.slice());
|
|
47
|
+
eventsSnapshotDirty = false;
|
|
48
|
+
return eventsSnapshot;
|
|
49
|
+
};
|
|
50
|
+
const notify = createNotifyScheduler(coalesce, () => {
|
|
51
|
+
publishedEvents();
|
|
52
|
+
for (const listener of listeners) {
|
|
53
|
+
listener();
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
const setStatus = (next) => {
|
|
57
|
+
if (status === next) {
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
status = next;
|
|
61
|
+
version += 1;
|
|
62
|
+
notify.schedule();
|
|
63
|
+
};
|
|
64
|
+
const applyBatch = (batch) => {
|
|
65
|
+
const fresh = [];
|
|
66
|
+
for (const event of batch) {
|
|
67
|
+
if (event.seq < fromSeq || seenIds.has(event.id)) {
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
seenIds.add(event.id);
|
|
71
|
+
// SAFETY: wire events match the app-registered TEvent catalog at the type boundary.
|
|
72
|
+
fresh.push(event);
|
|
73
|
+
fromSeq = Math.max(fromSeq, event.seq + 1);
|
|
74
|
+
}
|
|
75
|
+
if (fresh.length === 0) {
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
tables = materializeEvents(fresh, tables);
|
|
79
|
+
for (const event of fresh) {
|
|
80
|
+
events.push(event);
|
|
81
|
+
}
|
|
82
|
+
eventsSnapshotDirty = true;
|
|
83
|
+
eventIndex.append(fresh);
|
|
84
|
+
version += 1;
|
|
85
|
+
notify.schedule();
|
|
86
|
+
};
|
|
87
|
+
const applyEncoded = (data) => {
|
|
88
|
+
applyBatch(eventsFromSseData(data, options.storeId));
|
|
89
|
+
};
|
|
90
|
+
const liveUrl = () => {
|
|
91
|
+
const cursor = Math.max(0, fromSeq - 1);
|
|
92
|
+
return `${endpoint}/api/events?runId=${encodeURIComponent(options.storeId)}&live=true&cursor=${cursor}`;
|
|
93
|
+
};
|
|
94
|
+
const pull = async () => {
|
|
95
|
+
try {
|
|
96
|
+
const cursor = Math.max(0, fromSeq - 1);
|
|
97
|
+
const url = `${endpoint}/api/events?runId=${encodeURIComponent(options.storeId)}&cursor=${cursor}`;
|
|
98
|
+
const res = await fetchFn(url);
|
|
99
|
+
if (!res.ok) {
|
|
100
|
+
if (res.status === 404 || res.status === 416) {
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
const text = await res.text();
|
|
104
|
+
if (text.includes('out of range') || text.includes('Range not satisfiable')) {
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
throw new Error(`event pull failed: ${res.status}`);
|
|
108
|
+
}
|
|
109
|
+
const body = await res.json();
|
|
110
|
+
if (!Predicate.isReadonlyObject(body)) {
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
const batch = 'batch' in body && Array.isArray(body.batch) ? body.batch : [];
|
|
114
|
+
applyBatch(batch.map((raw) => decodeLoomsEvent(
|
|
115
|
+
// SAFETY: host pull batch items are encoded Looms event envelopes.
|
|
116
|
+
raw, options.storeId)));
|
|
117
|
+
const head = 'head' in body && Predicate.isNumber(body.head) ? body.head : undefined;
|
|
118
|
+
if (head !== undefined) {
|
|
119
|
+
fromSeq = Math.max(fromSeq, head + 1);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
catch (err) {
|
|
123
|
+
if (disposed) {
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
// When offline or host is restarting, suppress connection refused/fetch errors during sync
|
|
127
|
+
if (err instanceof Error &&
|
|
128
|
+
(err.message.includes('fetch failed') ||
|
|
129
|
+
err.message.includes('ConnectionRefused') ||
|
|
130
|
+
err.message.includes('Unable to connect'))) {
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
throw err;
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
const sync = async () => {
|
|
137
|
+
if (disposed) {
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
if (syncing) {
|
|
141
|
+
return syncing;
|
|
142
|
+
}
|
|
143
|
+
syncing = (async () => {
|
|
144
|
+
try {
|
|
145
|
+
await pull();
|
|
146
|
+
}
|
|
147
|
+
finally {
|
|
148
|
+
syncing = null;
|
|
149
|
+
}
|
|
150
|
+
})();
|
|
151
|
+
return syncing;
|
|
152
|
+
};
|
|
153
|
+
const scheduleReconnect = (Ctor) => {
|
|
154
|
+
if (disposed || !liveStarted || reconnectTimer !== undefined) {
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
reconnectTimer = setTimeout(() => {
|
|
158
|
+
reconnectTimer = undefined;
|
|
159
|
+
if (disposed || !liveStarted) {
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
void sync()
|
|
163
|
+
.catch(() => { })
|
|
164
|
+
.finally(() => {
|
|
165
|
+
if (!disposed && liveStarted) {
|
|
166
|
+
startEventSource(Ctor);
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
}, reconnectDelayMs);
|
|
170
|
+
};
|
|
171
|
+
const startEventSource = (Ctor) => {
|
|
172
|
+
if (disposed || !liveStarted) {
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
const es = new Ctor(liveUrl());
|
|
176
|
+
eventSource = es;
|
|
177
|
+
es.addEventListener('open', () => {
|
|
178
|
+
live = true;
|
|
179
|
+
setStatus('live');
|
|
180
|
+
});
|
|
181
|
+
es.addEventListener('message', (msg) => {
|
|
182
|
+
applyEncoded(msg.data);
|
|
183
|
+
});
|
|
184
|
+
es.addEventListener('error', () => {
|
|
185
|
+
live = es.readyState === Ctor.OPEN;
|
|
186
|
+
if (es.readyState === Ctor.CLOSED && !disposed && liveStarted) {
|
|
187
|
+
setStatus('reconnecting');
|
|
188
|
+
// EventSource entered CLOSED state (e.g. server error or socket closed abruptly).
|
|
189
|
+
// Standard EventSource will NOT automatically reconnect on CLOSED, so we must
|
|
190
|
+
// clean up the dead instance and re-establish the connection.
|
|
191
|
+
es.close();
|
|
192
|
+
if (eventSource === es) {
|
|
193
|
+
eventSource = undefined;
|
|
194
|
+
}
|
|
195
|
+
scheduleReconnect(Ctor);
|
|
196
|
+
}
|
|
197
|
+
else if (!live) {
|
|
198
|
+
setStatus('reconnecting');
|
|
199
|
+
}
|
|
200
|
+
});
|
|
201
|
+
};
|
|
202
|
+
const startFetchStream = async () => {
|
|
203
|
+
while (true) {
|
|
204
|
+
if (disposed) {
|
|
205
|
+
break;
|
|
206
|
+
}
|
|
207
|
+
const controller = new AbortController();
|
|
208
|
+
liveAbort = controller;
|
|
209
|
+
try {
|
|
210
|
+
const headers = new Headers({ accept: 'text/event-stream' });
|
|
211
|
+
const cursor = Math.max(0, fromSeq - 1);
|
|
212
|
+
if (cursor > 0) {
|
|
213
|
+
headers.set('last-event-id', String(cursor));
|
|
214
|
+
}
|
|
215
|
+
const res = await fetchFn(liveUrl(), { headers, signal: controller.signal });
|
|
216
|
+
if (!res.ok) {
|
|
217
|
+
throw new Error(`event stream failed: ${res.status}`);
|
|
218
|
+
}
|
|
219
|
+
if (!res.body) {
|
|
220
|
+
throw new Error('event stream missing body');
|
|
221
|
+
}
|
|
222
|
+
live = true;
|
|
223
|
+
setStatus('live');
|
|
224
|
+
await consumeSseStream(res.body, (frame) => {
|
|
225
|
+
applyEncoded(frame.data);
|
|
226
|
+
}, controller.signal);
|
|
227
|
+
}
|
|
228
|
+
catch {
|
|
229
|
+
live = false;
|
|
230
|
+
if (disposed) {
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
setStatus('reconnecting');
|
|
234
|
+
}
|
|
235
|
+
live = false;
|
|
236
|
+
if (disposed) {
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
await delay(reconnectDelayMs, controller.signal);
|
|
240
|
+
}
|
|
241
|
+
};
|
|
242
|
+
const startLive = () => {
|
|
243
|
+
if (!options.storeId || (liveStarted && !disposed)) {
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
liveStarted = true;
|
|
247
|
+
disposed = false;
|
|
248
|
+
const ctor = options.fetch === undefined ? eventSourceCtor() : undefined;
|
|
249
|
+
if (ctor) {
|
|
250
|
+
startEventSource(ctor);
|
|
251
|
+
}
|
|
252
|
+
else {
|
|
253
|
+
void startFetchStream();
|
|
254
|
+
}
|
|
255
|
+
};
|
|
256
|
+
const stopLive = () => {
|
|
257
|
+
liveStarted = false;
|
|
258
|
+
live = false;
|
|
259
|
+
if (reconnectTimer !== undefined) {
|
|
260
|
+
clearTimeout(reconnectTimer);
|
|
261
|
+
reconnectTimer = undefined;
|
|
262
|
+
}
|
|
263
|
+
eventSource?.close();
|
|
264
|
+
eventSource = undefined;
|
|
265
|
+
liveAbort?.abort();
|
|
266
|
+
liveAbort = undefined;
|
|
267
|
+
};
|
|
268
|
+
if (options.storeId) {
|
|
269
|
+
startLive();
|
|
270
|
+
}
|
|
271
|
+
const readEvents = (filterOptions) => {
|
|
272
|
+
if (filterOptions?.threadId) {
|
|
273
|
+
return eventIndex.getByThread(filterOptions.threadId);
|
|
274
|
+
}
|
|
275
|
+
return publishedEvents();
|
|
276
|
+
};
|
|
277
|
+
const store = {
|
|
278
|
+
storeId: options.storeId,
|
|
279
|
+
version: () => version,
|
|
280
|
+
status: () => status,
|
|
281
|
+
/**
|
|
282
|
+
* Live mutable materialized tables. Re-read after each version bump;
|
|
283
|
+
* do not hold Map/array references across notifies.
|
|
284
|
+
*/
|
|
285
|
+
getState: () => tables,
|
|
286
|
+
events: readEvents,
|
|
287
|
+
query: {
|
|
288
|
+
events: readEvents,
|
|
289
|
+
runs: () => tables.runs,
|
|
290
|
+
threads: () => tables.threads,
|
|
291
|
+
counts: () => eventIndex.getCounts(),
|
|
292
|
+
bySeq: (seq) => eventIndex.getBySeq(seq),
|
|
293
|
+
startedAt: () => eventIndex.getStartedAt(),
|
|
294
|
+
latest: (type) => eventIndex.getLatest(type),
|
|
295
|
+
fold: (def) => projectionCache.fold(def, publishedEvents()),
|
|
296
|
+
},
|
|
297
|
+
subscribe: (listener) => {
|
|
298
|
+
listeners.add(listener);
|
|
299
|
+
listener();
|
|
300
|
+
if (disposed) {
|
|
301
|
+
startLive();
|
|
302
|
+
}
|
|
303
|
+
return () => {
|
|
304
|
+
listeners.delete(listener);
|
|
305
|
+
};
|
|
306
|
+
},
|
|
307
|
+
project: (definition) => projectionCache.project(definition, publishedEvents()),
|
|
308
|
+
fold: (definition) => projectionCache.fold(definition, publishedEvents()),
|
|
309
|
+
flush: notify.flush,
|
|
310
|
+
sync: async () => {
|
|
311
|
+
const res = await sync();
|
|
312
|
+
notify.flush();
|
|
313
|
+
return res;
|
|
314
|
+
},
|
|
315
|
+
commit: async (event) => {
|
|
316
|
+
const res = await fetchFn(`${endpoint}/runs/${encodeURIComponent(options.storeId)}/events`, {
|
|
317
|
+
method: 'POST',
|
|
318
|
+
headers: { 'content-type': 'application/json' },
|
|
319
|
+
body: JSON.stringify(event),
|
|
320
|
+
});
|
|
321
|
+
if (!res.ok) {
|
|
322
|
+
const text = await res.text();
|
|
323
|
+
throw new Error(`run signal failed: ${res.status} ${text}`);
|
|
324
|
+
}
|
|
325
|
+
if (!live) {
|
|
326
|
+
await sync();
|
|
327
|
+
}
|
|
328
|
+
notify.flush();
|
|
329
|
+
},
|
|
330
|
+
dispose: () => {
|
|
331
|
+
disposed = true;
|
|
332
|
+
notify.cancel();
|
|
333
|
+
stopLive();
|
|
334
|
+
listeners.clear();
|
|
335
|
+
projectionCache.clear();
|
|
336
|
+
eventIndex.clear();
|
|
337
|
+
},
|
|
338
|
+
};
|
|
339
|
+
return store;
|
|
340
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export interface RunRow {
|
|
2
|
+
runId: string;
|
|
3
|
+
status: string;
|
|
4
|
+
rootThreadId: string | null;
|
|
5
|
+
kind: string | null;
|
|
6
|
+
definitionName: string | null;
|
|
7
|
+
}
|
|
8
|
+
export interface ThreadRow {
|
|
9
|
+
threadId: string;
|
|
10
|
+
runId: string;
|
|
11
|
+
kind: string;
|
|
12
|
+
definitionName: string;
|
|
13
|
+
parentThreadId: string | null;
|
|
14
|
+
status: string;
|
|
15
|
+
}
|
|
16
|
+
/** Materialized run/thread rows. Event log lives only in `store.events()` / EventIndex. */
|
|
17
|
+
export interface MaterializedTables {
|
|
18
|
+
runs: Map<string, RunRow>;
|
|
19
|
+
threads: Map<string, ThreadRow>;
|
|
20
|
+
}
|
|
21
|
+
export declare function emptyTables(): MaterializedTables;
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { Effect, type Layer } from 'effect';
|
|
2
|
+
import { type ComposedRegistry, type EffectContext, type EventInput, type RegisteredDefinition, type RuntimeEffect } from '@swirls/looms/core';
|
|
3
|
+
export declare function dispatchEffect(registry: ComposedRegistry, services: Layer.Layer<any>, definitions: ReadonlyMap<string, RegisteredDefinition>, effect: RuntimeEffect, ctx: EffectContext): Effect.Effect<ReadonlyArray<EventInput>, Error>;
|