@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,103 @@
|
|
|
1
|
+
import { Database } from 'bun:sqlite';
|
|
2
|
+
import { createIndexProjector, } from './index-model.js';
|
|
3
|
+
import { SQLITE_SCHEMA_SQL } from './schema.js';
|
|
4
|
+
export { SQLITE_SCHEMA_SQL } from './schema.js';
|
|
5
|
+
function hasDb(options) {
|
|
6
|
+
return 'db' in options;
|
|
7
|
+
}
|
|
8
|
+
function applyOp(db, op) {
|
|
9
|
+
switch (op.type) {
|
|
10
|
+
case 'upsertActor':
|
|
11
|
+
db.run(`INSERT INTO looms_actors (actor_id, kind, status, definition_name, parent_actor_id, updated_at)
|
|
12
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
13
|
+
ON CONFLICT (actor_id) DO UPDATE SET
|
|
14
|
+
kind = excluded.kind,
|
|
15
|
+
status = excluded.status,
|
|
16
|
+
definition_name = excluded.definition_name,
|
|
17
|
+
parent_actor_id = excluded.parent_actor_id,
|
|
18
|
+
updated_at = excluded.updated_at`, [op.actorId, op.kind, op.status, op.definitionName, op.parentActorId, op.updatedAt]);
|
|
19
|
+
return;
|
|
20
|
+
case 'setActorStatus':
|
|
21
|
+
db.run(`INSERT INTO looms_actors (actor_id, status, updated_at)
|
|
22
|
+
VALUES (?, ?, ?)
|
|
23
|
+
ON CONFLICT (actor_id) DO UPDATE SET
|
|
24
|
+
status = excluded.status,
|
|
25
|
+
updated_at = excluded.updated_at`, [op.actorId, op.status, op.updatedAt]);
|
|
26
|
+
return;
|
|
27
|
+
case 'upsertReview':
|
|
28
|
+
db.run(`INSERT INTO looms_reviews (review_id, actor_id, status, title, updated_at)
|
|
29
|
+
VALUES (?, ?, ?, ?, ?)
|
|
30
|
+
ON CONFLICT (review_id) DO UPDATE SET
|
|
31
|
+
status = excluded.status,
|
|
32
|
+
updated_at = excluded.updated_at`, [op.reviewId, op.actorId, op.status, op.title, op.updatedAt]);
|
|
33
|
+
return;
|
|
34
|
+
case 'setReviewStatus':
|
|
35
|
+
db.run(`UPDATE looms_reviews SET status = ?, updated_at = ? WHERE review_id = ?`, [
|
|
36
|
+
op.status,
|
|
37
|
+
op.updatedAt,
|
|
38
|
+
op.reviewId,
|
|
39
|
+
]);
|
|
40
|
+
return;
|
|
41
|
+
default: {
|
|
42
|
+
const exhaustiveCheck = op;
|
|
43
|
+
return exhaustiveCheck;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
export function sqlite(options = {}) {
|
|
48
|
+
const createdDb = !hasDb(options);
|
|
49
|
+
const db = hasDb(options) ? options.db : new Database(options.path ?? ':memory:');
|
|
50
|
+
const backend = {
|
|
51
|
+
init: async () => {
|
|
52
|
+
db.exec(SQLITE_SCHEMA_SQL);
|
|
53
|
+
},
|
|
54
|
+
applyOps: async (ops) => {
|
|
55
|
+
const apply = db.transaction((batch) => {
|
|
56
|
+
for (const op of batch) {
|
|
57
|
+
applyOp(db, op);
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
apply(ops);
|
|
61
|
+
},
|
|
62
|
+
getActor: async (actorId) => {
|
|
63
|
+
const row = db
|
|
64
|
+
.query(`SELECT actor_id, kind, status, definition_name, parent_actor_id, updated_at
|
|
65
|
+
FROM looms_actors WHERE actor_id = ?`)
|
|
66
|
+
.get(actorId);
|
|
67
|
+
if (!row) {
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
return {
|
|
71
|
+
actorId: row.actor_id,
|
|
72
|
+
kind: row.kind,
|
|
73
|
+
status: row.status,
|
|
74
|
+
definitionName: row.definition_name,
|
|
75
|
+
parentActorId: row.parent_actor_id,
|
|
76
|
+
updatedAt: row.updated_at,
|
|
77
|
+
};
|
|
78
|
+
},
|
|
79
|
+
listReviews: async (actorId) => {
|
|
80
|
+
const rows = actorId
|
|
81
|
+
? db
|
|
82
|
+
.query(`SELECT review_id, actor_id, status, title, updated_at
|
|
83
|
+
FROM looms_reviews WHERE actor_id = ?`)
|
|
84
|
+
.all(actorId)
|
|
85
|
+
: db
|
|
86
|
+
.query(`SELECT review_id, actor_id, status, title, updated_at FROM looms_reviews`)
|
|
87
|
+
.all();
|
|
88
|
+
return rows.map((row) => ({
|
|
89
|
+
reviewId: row.review_id,
|
|
90
|
+
actorId: row.actor_id,
|
|
91
|
+
status: row.status,
|
|
92
|
+
title: row.title,
|
|
93
|
+
updatedAt: row.updated_at,
|
|
94
|
+
}));
|
|
95
|
+
},
|
|
96
|
+
dispose: async () => {
|
|
97
|
+
if (createdDb) {
|
|
98
|
+
db.close();
|
|
99
|
+
}
|
|
100
|
+
},
|
|
101
|
+
};
|
|
102
|
+
return createIndexProjector('sqlite', backend);
|
|
103
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { Layer } from 'effect';
|
|
2
|
+
import { EventStoreTag, type EventStore, type EventEnvelope } from '@swirls/looms/core';
|
|
3
|
+
import type { Projector, ProjectorErrorHandler } from './projector';
|
|
4
|
+
export interface WithProjectorsOptions {
|
|
5
|
+
onError?: ProjectorErrorHandler;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Wrap an EventStore so every successful append is also projected.
|
|
9
|
+
* Projection failures are isolated so the primary log stays authoritative.
|
|
10
|
+
*/
|
|
11
|
+
export declare function withProjectors(store: EventStore, projectors: ReadonlyArray<Projector>, options?: WithProjectorsOptions): EventStore;
|
|
12
|
+
/** Project events after the fact (e.g. from a runtime hook). */
|
|
13
|
+
export declare function projectEvents(projectors: ReadonlyArray<Projector>, events: readonly EventEnvelope[]): Promise<void>;
|
|
14
|
+
/**
|
|
15
|
+
* Layer that decorates a base EventStore with projection.
|
|
16
|
+
*/
|
|
17
|
+
export declare function ProjectorsStoreLive(base: EventStore, projectors: ReadonlyArray<Projector>, options?: WithProjectorsOptions): Layer.Layer<EventStoreTag>;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { Effect, Layer } from 'effect';
|
|
2
|
+
import { EventStoreTag, snapshotStoreOf, withSnapshotStore, } from '@swirls/looms/core';
|
|
3
|
+
function defaultOnError(error, projector) {
|
|
4
|
+
console.warn(`[@swirls/looms/projectors] ${projector.name} failed to project: ${error.message}`);
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Wrap an EventStore so every successful append is also projected.
|
|
8
|
+
* Projection failures are isolated so the primary log stays authoritative.
|
|
9
|
+
*/
|
|
10
|
+
export function withProjectors(store, projectors, options) {
|
|
11
|
+
const onError = options?.onError ?? defaultOnError;
|
|
12
|
+
const wrapped = {
|
|
13
|
+
append: (runId, events, appendOptions) => Effect.gen(function* () {
|
|
14
|
+
const result = yield* store.append(runId, events, appendOptions);
|
|
15
|
+
const fromSeq = result.sequences[0];
|
|
16
|
+
if (fromSeq !== undefined && projectors.length > 0) {
|
|
17
|
+
const written = yield* store.read(runId, {
|
|
18
|
+
fromSeq,
|
|
19
|
+
limit: result.sequences.length,
|
|
20
|
+
});
|
|
21
|
+
yield* Effect.promise(async () => {
|
|
22
|
+
for (const projector of projectors) {
|
|
23
|
+
try {
|
|
24
|
+
await projector.project(written);
|
|
25
|
+
}
|
|
26
|
+
catch (cause) {
|
|
27
|
+
onError(cause instanceof Error ? cause : new Error(String(cause)), projector, written);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
return result;
|
|
33
|
+
}),
|
|
34
|
+
read: (runId, readOptions) => store.read(runId, readOptions),
|
|
35
|
+
tail: (runId) => store.tail(runId),
|
|
36
|
+
bounds: store.bounds ? (runId) => store.bounds(runId) : undefined,
|
|
37
|
+
trim: store.trim ? (runId, beforeSeq) => store.trim(runId, beforeSeq) : undefined,
|
|
38
|
+
subscribe: (runId, subscribeOptions) => store.subscribe(runId, subscribeOptions),
|
|
39
|
+
listRuns: () => store.listRuns(),
|
|
40
|
+
};
|
|
41
|
+
const snapshots = snapshotStoreOf(store);
|
|
42
|
+
return snapshots ? withSnapshotStore(wrapped, snapshots) : wrapped;
|
|
43
|
+
}
|
|
44
|
+
/** Project events after the fact (e.g. from a runtime hook). */
|
|
45
|
+
export async function projectEvents(projectors, events) {
|
|
46
|
+
for (const projector of projectors) {
|
|
47
|
+
await projector.project(events);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Layer that decorates a base EventStore with projection.
|
|
52
|
+
*/
|
|
53
|
+
export function ProjectorsStoreLive(base, projectors, options) {
|
|
54
|
+
return Layer.succeed(EventStoreTag, withProjectors(base, projectors, options));
|
|
55
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { type EventEnvelope, type ProjectionDefinition } from '@swirls/looms/core';
|
|
2
|
+
import type { AnyEventEnvelope, RegisteredEvent } from './register';
|
|
3
|
+
export type { AnyEventEnvelope } from './register';
|
|
4
|
+
export { EventIndex, eventThreadKey } from '@swirls/looms/core';
|
|
5
|
+
export interface EventFoldDefinition<S, TEvent extends AnyEventEnvelope = RegisteredEvent> {
|
|
6
|
+
readonly name?: string;
|
|
7
|
+
readonly initialState: S;
|
|
8
|
+
reduce(state: S, event: TEvent): S;
|
|
9
|
+
readonly includeEphemeral?: boolean;
|
|
10
|
+
}
|
|
11
|
+
export declare function createFold<S, TEvent extends AnyEventEnvelope = RegisteredEvent>(def: EventFoldDefinition<S, TEvent>): EventFoldDefinition<S, TEvent>;
|
|
12
|
+
export declare function foldEvents<S, TEvent extends AnyEventEnvelope = RegisteredEvent>(definition: EventFoldDefinition<S, TEvent>, events: readonly TEvent[], from?: S): S;
|
|
13
|
+
export declare class ProjectionCache {
|
|
14
|
+
private projectionCache;
|
|
15
|
+
private foldCache;
|
|
16
|
+
project<S>(definition: ProjectionDefinition<S>, events: readonly EventEnvelope[]): S;
|
|
17
|
+
fold<S, TEvent extends AnyEventEnvelope = RegisteredEvent>(definition: EventFoldDefinition<S, TEvent>, events: readonly TEvent[]): S;
|
|
18
|
+
clear(): void;
|
|
19
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { project } from '@swirls/looms/core';
|
|
2
|
+
export { EventIndex, eventThreadKey } from '@swirls/looms/core';
|
|
3
|
+
export function createFold(def) {
|
|
4
|
+
return def;
|
|
5
|
+
}
|
|
6
|
+
export function foldEvents(definition, events, from) {
|
|
7
|
+
let state = from !== undefined ? from : definition.initialState;
|
|
8
|
+
const includeEphemeral = definition.includeEphemeral ?? false;
|
|
9
|
+
for (const event of events) {
|
|
10
|
+
if (event.ephemeral && !includeEphemeral) {
|
|
11
|
+
continue;
|
|
12
|
+
}
|
|
13
|
+
state = definition.reduce(state, event);
|
|
14
|
+
}
|
|
15
|
+
return state;
|
|
16
|
+
}
|
|
17
|
+
function advanceCache(cache, definition, events, compute) {
|
|
18
|
+
const total = events.length;
|
|
19
|
+
const cached = cache.get(definition);
|
|
20
|
+
if (!cached) {
|
|
21
|
+
const state = compute(definition, events);
|
|
22
|
+
cache.set(definition, { state, upTo: total });
|
|
23
|
+
return state;
|
|
24
|
+
}
|
|
25
|
+
if (cached.upTo === total) {
|
|
26
|
+
return cached.state;
|
|
27
|
+
}
|
|
28
|
+
if (cached.upTo < total) {
|
|
29
|
+
const state = compute(definition, events.slice(cached.upTo), cached.state);
|
|
30
|
+
cache.set(definition, { state, upTo: total });
|
|
31
|
+
return state;
|
|
32
|
+
}
|
|
33
|
+
const state = compute(definition, events);
|
|
34
|
+
cache.set(definition, { state, upTo: total });
|
|
35
|
+
return state;
|
|
36
|
+
}
|
|
37
|
+
export class ProjectionCache {
|
|
38
|
+
projectionCache = new WeakMap();
|
|
39
|
+
foldCache = new WeakMap();
|
|
40
|
+
project(definition, events) {
|
|
41
|
+
return advanceCache(this.projectionCache, definition, events, project);
|
|
42
|
+
}
|
|
43
|
+
fold(definition, events) {
|
|
44
|
+
return advanceCache(this.foldCache, definition, events, foldEvents);
|
|
45
|
+
}
|
|
46
|
+
clear() {
|
|
47
|
+
this.projectionCache = new WeakMap();
|
|
48
|
+
this.foldCache = new WeakMap();
|
|
49
|
+
}
|
|
50
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { createLoomsStore, type LoomsClientStore, type LoomsClientStoreOptions, type LoomsConnectionStatus, type LoomsStoreQuery, type CoalesceOption, type StoreListener, } from './store';
|
|
2
|
+
export { createFold, foldEvents, EventIndex, eventThreadKey, type EventFoldDefinition, } from './derived';
|
|
3
|
+
export { materializeEvents } from './materialize';
|
|
4
|
+
export { emptyTables, type MaterializedTables, type RunRow, type ThreadRow } from './tables';
|
|
5
|
+
export type { LoomsRegister, RegisteredEvent, AnyEventEnvelope } from './register';
|
|
6
|
+
export { LoomsProvider, useRunStore, useProjection, useRunSelector, useRunSummary, useRunEvents, useThreadTree, useEventCounts, useEventBySeq, useLatestEvent, useEventFold, type LoomsProviderProps, type UseRunStoreOptions, type RunSummary, } from './react';
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { createLoomsStore, } from './store.js';
|
|
2
|
+
export { createFold, foldEvents, EventIndex, eventThreadKey, } from './derived.js';
|
|
3
|
+
export { materializeEvents } from './materialize.js';
|
|
4
|
+
export { emptyTables } from './tables.js';
|
|
5
|
+
export { LoomsProvider, useRunStore, useProjection, useRunSelector, useRunSummary, useRunEvents, useThreadTree, useEventCounts, useEventBySeq, useLatestEvent, useEventFold, } from './react.js';
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { isJsonObject, isJsonString } from '@swirls/looms/core';
|
|
2
|
+
import { emptyTables } from './tables.js';
|
|
3
|
+
function payloadObject(event) {
|
|
4
|
+
return isJsonObject(event.payload) ? event.payload : {};
|
|
5
|
+
}
|
|
6
|
+
function readString(obj, key) {
|
|
7
|
+
const value = obj[key];
|
|
8
|
+
return isJsonString(value) ? value : undefined;
|
|
9
|
+
}
|
|
10
|
+
export function materializeEvents(events, from = emptyTables()) {
|
|
11
|
+
const tables = {
|
|
12
|
+
runs: from.runs,
|
|
13
|
+
threads: from.threads,
|
|
14
|
+
};
|
|
15
|
+
for (const event of events) {
|
|
16
|
+
const payload = payloadObject(event);
|
|
17
|
+
switch (event.type) {
|
|
18
|
+
case 'runtime.run.started': {
|
|
19
|
+
const existing = tables.runs.get(event.runId);
|
|
20
|
+
const rootThreadId = readString(payload, 'rootThreadId') ?? existing?.rootThreadId ?? null;
|
|
21
|
+
const next = {
|
|
22
|
+
runId: event.runId,
|
|
23
|
+
status: 'running',
|
|
24
|
+
rootThreadId,
|
|
25
|
+
kind: readString(payload, 'kind') ?? existing?.kind ?? null,
|
|
26
|
+
definitionName: readString(payload, 'definitionName') ?? existing?.definitionName ?? null,
|
|
27
|
+
};
|
|
28
|
+
tables.runs.set(event.runId, next);
|
|
29
|
+
break;
|
|
30
|
+
}
|
|
31
|
+
case 'runtime.run.completed': {
|
|
32
|
+
const existing = tables.runs.get(event.runId);
|
|
33
|
+
tables.runs.set(event.runId, {
|
|
34
|
+
runId: event.runId,
|
|
35
|
+
status: readString(payload, 'error') ? 'failed' : 'completed',
|
|
36
|
+
rootThreadId: existing?.rootThreadId ?? null,
|
|
37
|
+
kind: existing?.kind ?? null,
|
|
38
|
+
definitionName: existing?.definitionName ?? null,
|
|
39
|
+
});
|
|
40
|
+
break;
|
|
41
|
+
}
|
|
42
|
+
case 'runtime.thread.started': {
|
|
43
|
+
const threadId = readString(payload, 'threadId') ?? event.threadId;
|
|
44
|
+
const kind = readString(payload, 'kind');
|
|
45
|
+
const definitionName = readString(payload, 'definitionName');
|
|
46
|
+
if (!threadId || !kind || !definitionName) {
|
|
47
|
+
break;
|
|
48
|
+
}
|
|
49
|
+
const parent = payload.parentThreadId;
|
|
50
|
+
const parentThreadId = parent === null || isJsonString(parent) ? parent : null;
|
|
51
|
+
const row = {
|
|
52
|
+
threadId,
|
|
53
|
+
runId: event.runId,
|
|
54
|
+
kind,
|
|
55
|
+
definitionName,
|
|
56
|
+
parentThreadId,
|
|
57
|
+
status: 'running',
|
|
58
|
+
};
|
|
59
|
+
tables.threads.set(threadId, row);
|
|
60
|
+
break;
|
|
61
|
+
}
|
|
62
|
+
case 'runtime.thread.completed':
|
|
63
|
+
case 'runtime.thread.failed':
|
|
64
|
+
case 'runtime.thread.cancelled': {
|
|
65
|
+
const threadId = readString(payload, 'threadId') ?? event.threadId;
|
|
66
|
+
if (!threadId) {
|
|
67
|
+
break;
|
|
68
|
+
}
|
|
69
|
+
const existing = tables.threads.get(threadId);
|
|
70
|
+
if (!existing) {
|
|
71
|
+
break;
|
|
72
|
+
}
|
|
73
|
+
const status = event.type === 'runtime.thread.completed'
|
|
74
|
+
? 'completed'
|
|
75
|
+
: event.type === 'runtime.thread.failed'
|
|
76
|
+
? 'failed'
|
|
77
|
+
: 'cancelled';
|
|
78
|
+
tables.threads.set(threadId, { ...existing, status });
|
|
79
|
+
break;
|
|
80
|
+
}
|
|
81
|
+
default:
|
|
82
|
+
break;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return tables;
|
|
86
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How store→subscriber notifications are scheduled after events land.
|
|
3
|
+
*
|
|
4
|
+
* - `adaptive` (default): notify on the next macrotask when the stream is
|
|
5
|
+
* quiet so UI can tick +1; under burst, coalesce to an animation frame.
|
|
6
|
+
* - `frame`: always at most one notify per animation frame.
|
|
7
|
+
* - `immediate`: notify synchronously on every apply (max granularity).
|
|
8
|
+
* - `{ delayMs }`: debounce-style timer coalesce.
|
|
9
|
+
*/
|
|
10
|
+
export type CoalesceOption = 'adaptive' | 'frame' | 'immediate' | {
|
|
11
|
+
delayMs: number;
|
|
12
|
+
};
|
|
13
|
+
export interface NotifyScheduler {
|
|
14
|
+
schedule: () => void;
|
|
15
|
+
flush: () => void;
|
|
16
|
+
cancel: () => void;
|
|
17
|
+
}
|
|
18
|
+
export declare function createNotifyScheduler(coalesce: CoalesceOption, onNotify: () => void): NotifyScheduler;
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { Predicate } from 'effect';
|
|
2
|
+
/** Quiet→burst threshold for `adaptive` coalesce (ms since last notify). */
|
|
3
|
+
const ADAPTIVE_BURST_WINDOW_MS = 8;
|
|
4
|
+
function nowMs() {
|
|
5
|
+
const perf = globalThis.performance;
|
|
6
|
+
return perf === undefined ? Date.now() : perf.now();
|
|
7
|
+
}
|
|
8
|
+
export function createNotifyScheduler(coalesce, onNotify) {
|
|
9
|
+
let notifyScheduled = false;
|
|
10
|
+
let notifyTimer;
|
|
11
|
+
let notifyRaf;
|
|
12
|
+
let lastNotifyAt = Number.NEGATIVE_INFINITY;
|
|
13
|
+
const fire = () => {
|
|
14
|
+
notifyScheduled = false;
|
|
15
|
+
notifyTimer = undefined;
|
|
16
|
+
notifyRaf = undefined;
|
|
17
|
+
lastNotifyAt = nowMs();
|
|
18
|
+
onNotify();
|
|
19
|
+
};
|
|
20
|
+
const scheduleFrame = () => {
|
|
21
|
+
if (Predicate.isFunction(globalThis.requestAnimationFrame)) {
|
|
22
|
+
notifyRaf = requestAnimationFrame(fire);
|
|
23
|
+
}
|
|
24
|
+
else {
|
|
25
|
+
notifyTimer = setTimeout(fire, 0);
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
const cancel = () => {
|
|
29
|
+
if (!notifyScheduled) {
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
notifyScheduled = false;
|
|
33
|
+
if (notifyTimer !== undefined) {
|
|
34
|
+
clearTimeout(notifyTimer);
|
|
35
|
+
notifyTimer = undefined;
|
|
36
|
+
}
|
|
37
|
+
if (notifyRaf !== undefined && Predicate.isFunction(globalThis.cancelAnimationFrame)) {
|
|
38
|
+
cancelAnimationFrame(notifyRaf);
|
|
39
|
+
notifyRaf = undefined;
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
const schedule = () => {
|
|
43
|
+
if (coalesce === 'immediate') {
|
|
44
|
+
fire();
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
if (notifyScheduled) {
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
notifyScheduled = true;
|
|
51
|
+
if (coalesce === 'adaptive') {
|
|
52
|
+
const inBurst = nowMs() - lastNotifyAt < ADAPTIVE_BURST_WINDOW_MS;
|
|
53
|
+
if (inBurst) {
|
|
54
|
+
scheduleFrame();
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
notifyTimer = setTimeout(fire, 0);
|
|
58
|
+
}
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
if (coalesce === 'frame') {
|
|
62
|
+
scheduleFrame();
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
if (Predicate.isObject(coalesce) && 'delayMs' in coalesce) {
|
|
66
|
+
const delayMs = Predicate.isNumber(coalesce.delayMs) ? Math.max(0, coalesce.delayMs) : 0;
|
|
67
|
+
notifyTimer = setTimeout(fire, delayMs);
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
const _exhaustive = coalesce;
|
|
71
|
+
void _exhaustive;
|
|
72
|
+
};
|
|
73
|
+
const flush = () => {
|
|
74
|
+
if (notifyScheduled) {
|
|
75
|
+
cancel();
|
|
76
|
+
fire();
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
return { schedule, flush, cancel };
|
|
80
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { type ReactNode } from 'react';
|
|
2
|
+
import { type ProjectionDefinition, type ThreadTree } from '@swirls/looms/core';
|
|
3
|
+
import { createFold, foldEvents, type EventFoldDefinition } from './derived';
|
|
4
|
+
import type { AnyEventEnvelope, RegisteredEvent } from './register';
|
|
5
|
+
import { createLoomsStore, type CoalesceOption, type LoomsClientStore, type LoomsConnectionStatus } from './store';
|
|
6
|
+
export interface LoomsProviderProps {
|
|
7
|
+
children: ReactNode;
|
|
8
|
+
endpoint?: string;
|
|
9
|
+
coalesce?: CoalesceOption;
|
|
10
|
+
}
|
|
11
|
+
interface ProviderContextValue {
|
|
12
|
+
endpoint: string;
|
|
13
|
+
coalesce?: CoalesceOption;
|
|
14
|
+
}
|
|
15
|
+
/** Root provider that supplies the Looms host endpoint to run stores. */
|
|
16
|
+
export declare function LoomsProvider({ children, endpoint, coalesce }: LoomsProviderProps): import("react").FunctionComponentElement<import("react").ProviderProps<ProviderContextValue>>;
|
|
17
|
+
export interface UseRunStoreOptions {
|
|
18
|
+
endpoint?: string;
|
|
19
|
+
reconnectDelayMs?: number;
|
|
20
|
+
coalesce?: CoalesceOption;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Acquires a non-subscribing handle to the run store.
|
|
24
|
+
* Retains the store in memory while mounted; does NOT trigger component re-renders.
|
|
25
|
+
* Use data hooks (`useRunEvents`, `useProjection`, `useRunSummary`, etc.) to subscribe.
|
|
26
|
+
*/
|
|
27
|
+
export declare function useRunStore<TEvent extends AnyEventEnvelope = RegisteredEvent>(runId: string, options?: UseRunStoreOptions): LoomsClientStore<TEvent>;
|
|
28
|
+
/** Subscribe to incremental projection state, with optional slice selector. */
|
|
29
|
+
export declare function useProjection<S, TEvent extends AnyEventEnvelope = RegisteredEvent>(store: LoomsClientStore<TEvent>, definition: ProjectionDefinition<S>): S;
|
|
30
|
+
export declare function useProjection<S, Selected, TEvent extends AnyEventEnvelope = RegisteredEvent>(store: LoomsClientStore<TEvent>, definition: ProjectionDefinition<S>, selector: (state: S) => Selected, isEqual?: (a: Selected, b: Selected) => boolean): Selected;
|
|
31
|
+
/** Subscribe to arbitrary store derivations with an equality check. */
|
|
32
|
+
export declare function useRunSelector<Selected, TEvent extends AnyEventEnvelope = RegisteredEvent>(store: LoomsClientStore<TEvent>, selector: (store: LoomsClientStore<TEvent>) => Selected, isEqual?: (a: Selected, b: Selected) => boolean): Selected;
|
|
33
|
+
export interface RunSummary {
|
|
34
|
+
readonly status: string;
|
|
35
|
+
readonly kind: string | undefined;
|
|
36
|
+
readonly definitionName: string | undefined;
|
|
37
|
+
readonly rootThreadId: string | undefined;
|
|
38
|
+
readonly startedAt: number | undefined;
|
|
39
|
+
readonly connection: LoomsConnectionStatus;
|
|
40
|
+
}
|
|
41
|
+
/** Lightweight subscription to top-level run metadata and connection status. */
|
|
42
|
+
export declare function useRunSummary<TEvent extends AnyEventEnvelope = RegisteredEvent>(store: LoomsClientStore<TEvent>): RunSummary;
|
|
43
|
+
/** Subscribe to the event log, optionally scoped to a single thread. */
|
|
44
|
+
export declare function useRunEvents<TEvent extends AnyEventEnvelope = RegisteredEvent>(store: LoomsClientStore<TEvent>, options?: {
|
|
45
|
+
threadId?: string;
|
|
46
|
+
}): readonly TEvent[];
|
|
47
|
+
/** Subscribe to the thread hierarchy tree. */
|
|
48
|
+
export declare function useThreadTree<TEvent extends AnyEventEnvelope = RegisteredEvent>(store: LoomsClientStore<TEvent>): ThreadTree;
|
|
49
|
+
/** Subscribe to event count per thread. */
|
|
50
|
+
export declare function useEventCounts<TEvent extends AnyEventEnvelope = RegisteredEvent>(store: LoomsClientStore<TEvent>): ReadonlyMap<string, number>;
|
|
51
|
+
/** Subscribe to a single event by its sequence number. */
|
|
52
|
+
export declare function useEventBySeq<TEvent extends AnyEventEnvelope = RegisteredEvent>(store: LoomsClientStore<TEvent>, seq: number | undefined): TEvent | undefined;
|
|
53
|
+
/** Subscribe to the latest event of a given type. */
|
|
54
|
+
export declare function useLatestEvent<TEvent extends AnyEventEnvelope = RegisteredEvent>(store: LoomsClientStore<TEvent>, type: string): TEvent | undefined;
|
|
55
|
+
/** Subscribe to a custom incremental fold. */
|
|
56
|
+
export declare function useEventFold<S, TEvent extends AnyEventEnvelope = RegisteredEvent>(store: LoomsClientStore<TEvent>, fold: EventFoldDefinition<S, TEvent>): S;
|
|
57
|
+
export { createLoomsStore };
|
|
58
|
+
export { createFold, foldEvents };
|
|
59
|
+
export type { LoomsClientStore, LoomsClientStoreOptions, LoomsConnectionStatus, LoomsStoreQuery, CoalesceOption, StoreListener, } from './store';
|
|
60
|
+
export type { EventFoldDefinition } from './derived';
|
|
61
|
+
export type { LoomsRegister, RegisteredEvent, AnyEventEnvelope } from './register';
|