@swirls/looms 0.2.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +31 -0
- package/dist/actor/host.js +4 -6
- package/dist/actor/index.d.ts +1 -1
- package/dist/actor/index.js +1 -1
- package/dist/actor/routing.d.ts +5 -0
- package/dist/actor/routing.js +14 -0
- package/dist/agent/agent-session.js +1 -0
- package/dist/agent/debugger/agent-chat.js +45 -38
- package/dist/agent/debugger/streaming-text.d.ts +6 -0
- package/dist/agent/debugger/streaming-text.js +33 -0
- package/dist/agent/debugger/summarize.js +2 -0
- package/dist/agent/effects.d.ts +30 -0
- package/dist/agent/events.d.ts +2 -0
- package/dist/agent/events.js +2 -0
- package/dist/agent/llm.js +1 -8
- package/dist/agent/module.d.ts +230 -160
- package/dist/agent/module.js +11 -7
- package/dist/agent/projections.d.ts +5 -0
- package/dist/agent/projections.js +43 -2
- package/dist/agent/scope.d.ts +2 -0
- package/dist/agent/signals.d.ts +5 -0
- package/dist/agent/signals.js +7 -0
- package/dist/agent/threads.js +35 -21
- package/dist/agent/transcript.d.ts +7 -0
- package/dist/agent/transcript.js +19 -0
- package/dist/agent/types.d.ts +10 -0
- package/dist/agent/types.js +4 -0
- package/dist/approval/debugger/approvals-view.js +5 -3
- package/dist/approval/effects.d.ts +12 -0
- package/dist/approval/module.d.ts +24 -0
- package/dist/client/client.d.ts +292 -0
- package/dist/client/client.js +17 -1
- package/dist/cloudflare/router.d.ts +1 -1
- package/dist/cloudflare/router.js +6 -8
- package/dist/core/attempts.d.ts +8 -0
- package/dist/core/attempts.js +27 -0
- package/dist/core/effects.d.ts +7 -0
- package/dist/core/effects.js +1 -0
- package/dist/core/fold.js +12 -6
- package/dist/core/fork.d.ts +35 -0
- package/dist/core/fork.js +129 -0
- package/dist/core/index.d.ts +3 -0
- package/dist/core/index.js +2 -0
- package/dist/core/module-scope.d.ts +1 -0
- package/dist/core/module.d.ts +2 -2
- package/dist/core/projection.d.ts +5 -4
- package/dist/core/projection.js +1 -0
- package/dist/core/protocol.d.ts +10 -1
- package/dist/core/protocol.js +11 -0
- package/dist/{react → core}/register.d.ts +9 -8
- package/dist/core/replay.d.ts +8 -0
- package/dist/core/snapshots.js +3 -17
- package/dist/core/state.d.ts +14 -0
- package/dist/core/state.js +25 -2
- package/dist/core/store.d.ts +1 -0
- package/dist/debugger/app/assets/index-CgDzWJg7.css +2 -0
- package/dist/debugger/app/assets/index-D--9esm9.js +22 -0
- package/dist/debugger/app/assets/routes-Dakrdusf.js +47 -0
- package/dist/debugger/app/index.html +2 -2
- package/dist/debugger/components/composer.d.ts +5 -2
- package/dist/debugger/components/composer.js +5 -4
- package/dist/debugger/components/run-tree.js +2 -5
- package/dist/debugger/events/catalog.js +1 -3
- package/dist/debugger/events/protocol-summarize.js +7 -0
- package/dist/debugger/lib/event-counts.d.ts +2 -2
- package/dist/debugger/lib/event-counts.js +5 -9
- package/dist/debugger/shell/debugger-shell.js +1 -1
- package/dist/debugger/shell/run-controls.d.ts +2 -1
- package/dist/debugger/shell/run-controls.js +40 -13
- package/dist/debugger/shell/run-search.js +1 -3
- package/dist/evaluator/effects.d.ts +45 -33
- package/dist/evaluator/module.d.ts +90 -66
- package/dist/projectors/index-model.d.ts +13 -7
- package/dist/projectors/index-model.js +21 -26
- package/dist/projectors/memory.js +19 -11
- package/dist/projectors/postgres.js +20 -19
- package/dist/projectors/schema.js +20 -18
- package/dist/projectors/sqlite.js +32 -27
- package/dist/react/derived.d.ts +26 -8
- package/dist/react/derived.js +23 -26
- package/dist/react/index.d.ts +3 -3
- package/dist/react/index.js +2 -2
- package/dist/react/materialize.js +16 -11
- package/dist/react/react.d.ts +12 -9
- package/dist/react/react.js +9 -6
- package/dist/react/store.d.ts +4 -5
- package/dist/react/tables.d.ts +3 -2
- package/dist/runtime/cancellation.js +14 -27
- package/dist/runtime/errors.d.ts +39 -0
- package/dist/runtime/errors.js +32 -0
- package/dist/runtime/helpers.d.ts +2 -0
- package/dist/runtime/helpers.js +23 -1
- package/dist/runtime/index.d.ts +1 -1
- package/dist/runtime/index.js +1 -1
- package/dist/runtime/looms.d.ts +4 -0
- package/dist/runtime/looms.js +1 -0
- package/dist/runtime/run-start.js +1 -3
- package/dist/runtime/runtime.d.ts +1 -1
- package/dist/runtime/runtime.js +45 -18
- package/dist/runtime/server/http.d.ts +2 -0
- package/dist/runtime/server/http.js +17 -2
- package/dist/runtime/server/routes/runs.js +35 -2
- package/dist/runtime/types.d.ts +8 -0
- package/dist/runtime/wake-engine.js +5 -2
- package/dist/s2/snapshot-store.js +2 -6
- package/dist/workflow/effects.d.ts +24 -0
- package/dist/workflow/effects.js +1 -0
- package/dist/workflow/module.d.ts +48 -0
- package/package.json +2 -2
- package/dist/debugger/app/assets/index-B3Ah8WAE.css +0 -2
- package/dist/debugger/app/assets/index-Qb371t_O.js +0 -22
- package/dist/debugger/app/assets/routes-CjtEHgPQ.js +0 -47
- /package/dist/{react → core}/register.js +0 -0
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type EventEnvelope } from '@swirls/looms/core';
|
|
2
2
|
import type { Projector } from './projector';
|
|
3
|
+
/** A run (`actorId === runId`) or one of its threads. Thread ids are unique within a run. */
|
|
3
4
|
export interface ActorIndexRow {
|
|
5
|
+
runId: string;
|
|
4
6
|
actorId: string;
|
|
5
7
|
kind: string | null;
|
|
6
8
|
status: string | null;
|
|
@@ -9,14 +11,15 @@ export interface ActorIndexRow {
|
|
|
9
11
|
updatedAt: number;
|
|
10
12
|
}
|
|
11
13
|
export interface ReviewIndexRow {
|
|
14
|
+
runId: string;
|
|
12
15
|
reviewId: string;
|
|
13
|
-
actorId: string;
|
|
14
16
|
status: string;
|
|
15
17
|
title: string;
|
|
16
18
|
updatedAt: number;
|
|
17
19
|
}
|
|
18
20
|
export type IndexOp = {
|
|
19
21
|
type: 'upsertActor';
|
|
22
|
+
runId: string;
|
|
20
23
|
actorId: string;
|
|
21
24
|
kind: string | null;
|
|
22
25
|
status: string | null;
|
|
@@ -25,32 +28,35 @@ export type IndexOp = {
|
|
|
25
28
|
updatedAt: number;
|
|
26
29
|
} | {
|
|
27
30
|
type: 'setActorStatus';
|
|
31
|
+
runId: string;
|
|
28
32
|
actorId: string;
|
|
29
33
|
status: string;
|
|
30
34
|
updatedAt: number;
|
|
31
35
|
} | {
|
|
32
36
|
type: 'upsertReview';
|
|
37
|
+
runId: string;
|
|
33
38
|
reviewId: string;
|
|
34
|
-
actorId: string;
|
|
35
39
|
status: string;
|
|
36
40
|
title: string;
|
|
37
41
|
updatedAt: number;
|
|
38
42
|
} | {
|
|
39
43
|
type: 'setReviewStatus';
|
|
44
|
+
runId: string;
|
|
40
45
|
reviewId: string;
|
|
41
46
|
status: string;
|
|
42
47
|
updatedAt: number;
|
|
43
48
|
};
|
|
44
49
|
export interface IndexBackend {
|
|
45
50
|
applyOps: (ops: ReadonlyArray<IndexOp>) => Promise<void>;
|
|
46
|
-
getActor: (actorId: string) => Promise<ActorIndexRow | null>;
|
|
47
|
-
listReviews: (
|
|
51
|
+
getActor: (runId: string, actorId: string) => Promise<ActorIndexRow | null>;
|
|
52
|
+
listReviews: (runId?: string) => Promise<ReviewIndexRow[]>;
|
|
48
53
|
init?: () => Promise<void>;
|
|
49
54
|
dispose?: () => Promise<void>;
|
|
50
55
|
}
|
|
51
56
|
export interface IndexProjector extends Projector {
|
|
52
|
-
|
|
53
|
-
|
|
57
|
+
/** The run's row, or one of its threads when `actorId` is given. */
|
|
58
|
+
getActor: (runId: string, actorId?: string) => Promise<ActorIndexRow | null>;
|
|
59
|
+
listReviews: (runId?: string) => Promise<ReviewIndexRow[]>;
|
|
54
60
|
}
|
|
55
61
|
export declare function indexOpsFor(event: EventEnvelope): IndexOp[];
|
|
56
62
|
export declare function createIndexProjector(name: string, backend: IndexBackend, version?: string): IndexProjector;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Predicate } from 'effect';
|
|
2
|
+
import { completedRunStatus } from '@swirls/looms/core';
|
|
2
3
|
function payloadObject(event) {
|
|
3
4
|
if (!Predicate.isObject(event.payload)) {
|
|
4
5
|
return {};
|
|
@@ -11,12 +12,15 @@ function readString(obj, key) {
|
|
|
11
12
|
}
|
|
12
13
|
export function indexOpsFor(event) {
|
|
13
14
|
const payload = payloadObject(event);
|
|
15
|
+
const runId = event.runId;
|
|
16
|
+
const actorId = readString(payload, 'threadId') ?? event.threadId ?? runId;
|
|
14
17
|
switch (event.type) {
|
|
15
18
|
case 'runtime.run.started':
|
|
16
19
|
return [
|
|
17
20
|
{
|
|
18
21
|
type: 'upsertActor',
|
|
19
|
-
|
|
22
|
+
runId,
|
|
23
|
+
actorId: runId,
|
|
20
24
|
kind: readString(payload, 'kind') ?? null,
|
|
21
25
|
status: 'running',
|
|
22
26
|
definitionName: readString(payload, 'definitionName') ?? null,
|
|
@@ -28,7 +32,8 @@ export function indexOpsFor(event) {
|
|
|
28
32
|
return [
|
|
29
33
|
{
|
|
30
34
|
type: 'upsertActor',
|
|
31
|
-
|
|
35
|
+
runId,
|
|
36
|
+
actorId,
|
|
32
37
|
kind: readString(payload, 'kind') ?? null,
|
|
33
38
|
status: 'running',
|
|
34
39
|
definitionName: readString(payload, 'definitionName') ?? null,
|
|
@@ -37,39 +42,28 @@ export function indexOpsFor(event) {
|
|
|
37
42
|
},
|
|
38
43
|
];
|
|
39
44
|
case 'runtime.run.completed':
|
|
40
|
-
case 'runtime.thread.completed':
|
|
41
45
|
return [
|
|
42
46
|
{
|
|
43
47
|
type: 'setActorStatus',
|
|
44
|
-
|
|
45
|
-
|
|
48
|
+
runId,
|
|
49
|
+
actorId: runId,
|
|
50
|
+
status: completedRunStatus(event.payload),
|
|
46
51
|
updatedAt: event.ts,
|
|
47
52
|
},
|
|
48
53
|
];
|
|
54
|
+
case 'runtime.thread.completed':
|
|
55
|
+
return [{ type: 'setActorStatus', runId, actorId, status: 'completed', updatedAt: event.ts }];
|
|
49
56
|
case 'runtime.thread.failed':
|
|
50
|
-
return [
|
|
51
|
-
|
|
52
|
-
type: 'setActorStatus',
|
|
53
|
-
actorId: readString(payload, 'threadId') ?? event.threadId ?? event.runId,
|
|
54
|
-
status: 'failed',
|
|
55
|
-
updatedAt: event.ts,
|
|
56
|
-
},
|
|
57
|
-
];
|
|
57
|
+
return [{ type: 'setActorStatus', runId, actorId, status: 'failed', updatedAt: event.ts }];
|
|
58
|
+
case 'runtime.run.cancelled':
|
|
58
59
|
case 'runtime.thread.cancelled':
|
|
59
|
-
return [
|
|
60
|
-
{
|
|
61
|
-
type: 'setActorStatus',
|
|
62
|
-
actorId: readString(payload, 'threadId') ?? event.threadId ?? event.runId,
|
|
63
|
-
status: 'cancelled',
|
|
64
|
-
updatedAt: event.ts,
|
|
65
|
-
},
|
|
66
|
-
];
|
|
60
|
+
return [{ type: 'setActorStatus', runId, actorId, status: 'cancelled', updatedAt: event.ts }];
|
|
67
61
|
case 'approval.requested':
|
|
68
62
|
return [
|
|
69
63
|
{
|
|
70
64
|
type: 'upsertReview',
|
|
65
|
+
runId,
|
|
71
66
|
reviewId: readString(payload, 'approvalId') ?? 'unknown',
|
|
72
|
-
actorId: event.runId,
|
|
73
67
|
status: 'pending',
|
|
74
68
|
title: readString(payload, 'title') ?? 'Approval',
|
|
75
69
|
updatedAt: event.ts,
|
|
@@ -79,8 +73,8 @@ export function indexOpsFor(event) {
|
|
|
79
73
|
return [
|
|
80
74
|
{
|
|
81
75
|
type: 'upsertReview',
|
|
76
|
+
runId,
|
|
82
77
|
reviewId: readString(payload, 'approvalId') ?? 'unknown',
|
|
83
|
-
actorId: event.runId,
|
|
84
78
|
status: payload.outcome === 'reject' ? 'rejected' : 'approved',
|
|
85
79
|
title: 'Approval',
|
|
86
80
|
updatedAt: event.ts,
|
|
@@ -90,6 +84,7 @@ export function indexOpsFor(event) {
|
|
|
90
84
|
return [
|
|
91
85
|
{
|
|
92
86
|
type: 'setReviewStatus',
|
|
87
|
+
runId,
|
|
93
88
|
reviewId: readString(payload, 'approvalId') ?? 'unknown',
|
|
94
89
|
status: 'timed_out',
|
|
95
90
|
updatedAt: event.ts,
|
|
@@ -99,7 +94,7 @@ export function indexOpsFor(event) {
|
|
|
99
94
|
return [];
|
|
100
95
|
}
|
|
101
96
|
}
|
|
102
|
-
export function createIndexProjector(name, backend, version = '
|
|
97
|
+
export function createIndexProjector(name, backend, version = '2') {
|
|
103
98
|
return {
|
|
104
99
|
name,
|
|
105
100
|
version,
|
|
@@ -109,7 +104,7 @@ export function createIndexProjector(name, backend, version = '1') {
|
|
|
109
104
|
const ops = events.flatMap(indexOpsFor);
|
|
110
105
|
return ops.length === 0 ? Promise.resolve() : backend.applyOps(ops);
|
|
111
106
|
},
|
|
112
|
-
getActor: (actorId) => backend.getActor(actorId),
|
|
113
|
-
listReviews: (
|
|
107
|
+
getActor: (runId, actorId) => backend.getActor(runId, actorId ?? runId),
|
|
108
|
+
listReviews: (runId) => backend.listReviews(runId),
|
|
114
109
|
};
|
|
115
110
|
}
|
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
import { createIndexProjector, } from './index-model.js';
|
|
2
2
|
import { runProjectorSync } from './projector.js';
|
|
3
|
+
function rowKey(runId, id) {
|
|
4
|
+
return `${runId}\0${id}`;
|
|
5
|
+
}
|
|
3
6
|
function applyOp(actors, reviews, op) {
|
|
4
7
|
switch (op.type) {
|
|
5
8
|
case 'upsertActor':
|
|
6
|
-
actors.set(op.actorId, {
|
|
9
|
+
actors.set(rowKey(op.runId, op.actorId), {
|
|
10
|
+
runId: op.runId,
|
|
7
11
|
actorId: op.actorId,
|
|
8
12
|
kind: op.kind,
|
|
9
13
|
status: op.status,
|
|
@@ -13,8 +17,10 @@ function applyOp(actors, reviews, op) {
|
|
|
13
17
|
});
|
|
14
18
|
return;
|
|
15
19
|
case 'setActorStatus': {
|
|
16
|
-
const
|
|
17
|
-
actors.
|
|
20
|
+
const key = rowKey(op.runId, op.actorId);
|
|
21
|
+
const existing = actors.get(key);
|
|
22
|
+
actors.set(key, {
|
|
23
|
+
runId: op.runId,
|
|
18
24
|
actorId: op.actorId,
|
|
19
25
|
kind: existing?.kind ?? null,
|
|
20
26
|
status: op.status,
|
|
@@ -25,10 +31,11 @@ function applyOp(actors, reviews, op) {
|
|
|
25
31
|
return;
|
|
26
32
|
}
|
|
27
33
|
case 'upsertReview': {
|
|
28
|
-
const
|
|
29
|
-
reviews.
|
|
34
|
+
const key = rowKey(op.runId, op.reviewId);
|
|
35
|
+
const existing = reviews.get(key);
|
|
36
|
+
reviews.set(key, {
|
|
37
|
+
runId: op.runId,
|
|
30
38
|
reviewId: op.reviewId,
|
|
31
|
-
actorId: op.actorId,
|
|
32
39
|
status: op.status,
|
|
33
40
|
title: existing?.title ?? op.title,
|
|
34
41
|
updatedAt: op.updatedAt,
|
|
@@ -36,9 +43,10 @@ function applyOp(actors, reviews, op) {
|
|
|
36
43
|
return;
|
|
37
44
|
}
|
|
38
45
|
case 'setReviewStatus': {
|
|
39
|
-
const
|
|
46
|
+
const key = rowKey(op.runId, op.reviewId);
|
|
47
|
+
const existing = reviews.get(key);
|
|
40
48
|
if (existing) {
|
|
41
|
-
reviews.set(
|
|
49
|
+
reviews.set(key, { ...existing, status: op.status, updatedAt: op.updatedAt });
|
|
42
50
|
}
|
|
43
51
|
return;
|
|
44
52
|
}
|
|
@@ -57,10 +65,10 @@ export function memory() {
|
|
|
57
65
|
applyOp(actors, reviews, op);
|
|
58
66
|
}
|
|
59
67
|
}),
|
|
60
|
-
getActor: (actorId) => runProjectorSync('memory', () => actors.get(actorId) ?? null),
|
|
61
|
-
listReviews: (
|
|
68
|
+
getActor: (runId, actorId) => runProjectorSync('memory', () => actors.get(rowKey(runId, actorId)) ?? null),
|
|
69
|
+
listReviews: (runId) => runProjectorSync('memory', () => {
|
|
62
70
|
const rows = [...reviews.values()];
|
|
63
|
-
return
|
|
71
|
+
return runId ? rows.filter((row) => row.runId === runId) : rows;
|
|
64
72
|
}),
|
|
65
73
|
dispose: () => runProjectorSync('memory', () => {
|
|
66
74
|
actors.clear();
|
|
@@ -12,9 +12,9 @@ function applyOp(sql, op) {
|
|
|
12
12
|
switch (op.type) {
|
|
13
13
|
case 'upsertActor':
|
|
14
14
|
return sql `
|
|
15
|
-
INSERT INTO
|
|
16
|
-
VALUES (${op.actorId}, ${op.kind}, ${op.status}, ${op.definitionName}, ${op.parentActorId}, ${date})
|
|
17
|
-
ON CONFLICT (actor_id) DO UPDATE SET
|
|
15
|
+
INSERT INTO looms_run_actors (run_id, actor_id, kind, status, definition_name, parent_actor_id, updated_at)
|
|
16
|
+
VALUES (${op.runId}, ${op.actorId}, ${op.kind}, ${op.status}, ${op.definitionName}, ${op.parentActorId}, ${date})
|
|
17
|
+
ON CONFLICT (run_id, actor_id) DO UPDATE SET
|
|
18
18
|
kind = EXCLUDED.kind,
|
|
19
19
|
status = EXCLUDED.status,
|
|
20
20
|
definition_name = EXCLUDED.definition_name,
|
|
@@ -23,24 +23,24 @@ function applyOp(sql, op) {
|
|
|
23
23
|
`;
|
|
24
24
|
case 'setActorStatus':
|
|
25
25
|
return sql `
|
|
26
|
-
INSERT INTO
|
|
27
|
-
VALUES (${op.actorId}, ${op.status}, ${date})
|
|
28
|
-
ON CONFLICT (actor_id) DO UPDATE SET
|
|
26
|
+
INSERT INTO looms_run_actors (run_id, actor_id, status, updated_at)
|
|
27
|
+
VALUES (${op.runId}, ${op.actorId}, ${op.status}, ${date})
|
|
28
|
+
ON CONFLICT (run_id, actor_id) DO UPDATE SET
|
|
29
29
|
status = EXCLUDED.status,
|
|
30
30
|
updated_at = EXCLUDED.updated_at
|
|
31
31
|
`;
|
|
32
32
|
case 'upsertReview':
|
|
33
33
|
return sql `
|
|
34
|
-
INSERT INTO
|
|
35
|
-
VALUES (${op.
|
|
36
|
-
ON CONFLICT (review_id) DO UPDATE SET
|
|
34
|
+
INSERT INTO looms_run_reviews (run_id, review_id, status, title, updated_at)
|
|
35
|
+
VALUES (${op.runId}, ${op.reviewId}, ${op.status}, ${op.title}, ${date})
|
|
36
|
+
ON CONFLICT (run_id, review_id) DO UPDATE SET
|
|
37
37
|
status = EXCLUDED.status,
|
|
38
38
|
updated_at = EXCLUDED.updated_at
|
|
39
39
|
`;
|
|
40
40
|
case 'setReviewStatus':
|
|
41
41
|
return sql `
|
|
42
|
-
UPDATE
|
|
43
|
-
WHERE review_id = ${op.reviewId}
|
|
42
|
+
UPDATE looms_run_reviews SET status = ${op.status}, updated_at = ${date}
|
|
43
|
+
WHERE run_id = ${op.runId} AND review_id = ${op.reviewId}
|
|
44
44
|
`;
|
|
45
45
|
default: {
|
|
46
46
|
const exhaustiveCheck = op;
|
|
@@ -56,13 +56,14 @@ export function postgres(options) {
|
|
|
56
56
|
applyOps: (ops) => runProjectorPromise('postgres', () => sql.begin((tx) => Effect.runPromise(Effect.forEach(ops, (op) => Effect.promise(() => Promise.resolve(applyOp(tx, op))), {
|
|
57
57
|
discard: true,
|
|
58
58
|
})))).then(() => undefined),
|
|
59
|
-
getActor: (actorId) => runProjectorPromise('postgres', () => sql `
|
|
60
|
-
SELECT actor_id, kind, status, definition_name, parent_actor_id, updated_at
|
|
61
|
-
FROM
|
|
59
|
+
getActor: (runId, actorId) => runProjectorPromise('postgres', () => sql `
|
|
60
|
+
SELECT run_id, actor_id, kind, status, definition_name, parent_actor_id, updated_at
|
|
61
|
+
FROM looms_run_actors WHERE run_id = ${runId} AND actor_id = ${actorId}
|
|
62
62
|
`).then((rows) => {
|
|
63
63
|
const row = rows[0];
|
|
64
64
|
return row
|
|
65
65
|
? {
|
|
66
|
+
runId: row.run_id,
|
|
66
67
|
actorId: row.actor_id,
|
|
67
68
|
kind: row.kind,
|
|
68
69
|
status: row.status,
|
|
@@ -72,16 +73,16 @@ export function postgres(options) {
|
|
|
72
73
|
}
|
|
73
74
|
: null;
|
|
74
75
|
}),
|
|
75
|
-
listReviews: (
|
|
76
|
+
listReviews: (runId) => runProjectorPromise('postgres', () => runId
|
|
76
77
|
? sql `
|
|
77
|
-
SELECT
|
|
78
|
-
FROM
|
|
78
|
+
SELECT run_id, review_id, status, title, updated_at
|
|
79
|
+
FROM looms_run_reviews WHERE run_id = ${runId}
|
|
79
80
|
`
|
|
80
81
|
: sql `
|
|
81
|
-
SELECT
|
|
82
|
+
SELECT run_id, review_id, status, title, updated_at FROM looms_run_reviews
|
|
82
83
|
`).then((rows) => rows.map((row) => ({
|
|
84
|
+
runId: row.run_id,
|
|
83
85
|
reviewId: row.review_id,
|
|
84
|
-
actorId: row.actor_id,
|
|
85
86
|
status: row.status,
|
|
86
87
|
title: row.title,
|
|
87
88
|
updatedAt: row.updated_at.getTime(),
|
|
@@ -2,42 +2,44 @@
|
|
|
2
2
|
* SQL schema for the optional Looms secondary index.
|
|
3
3
|
*/
|
|
4
4
|
export const POSTGRES_SCHEMA_SQL = `
|
|
5
|
-
CREATE TABLE IF NOT EXISTS
|
|
6
|
-
|
|
5
|
+
CREATE TABLE IF NOT EXISTS looms_run_actors (
|
|
6
|
+
run_id TEXT NOT NULL,
|
|
7
|
+
actor_id TEXT NOT NULL,
|
|
7
8
|
kind TEXT,
|
|
8
9
|
status TEXT,
|
|
9
10
|
definition_name TEXT,
|
|
10
11
|
parent_actor_id TEXT,
|
|
11
|
-
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
12
|
+
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
13
|
+
PRIMARY KEY (run_id, actor_id)
|
|
12
14
|
);
|
|
13
15
|
|
|
14
|
-
CREATE TABLE IF NOT EXISTS
|
|
15
|
-
|
|
16
|
-
|
|
16
|
+
CREATE TABLE IF NOT EXISTS looms_run_reviews (
|
|
17
|
+
run_id TEXT NOT NULL,
|
|
18
|
+
review_id TEXT NOT NULL,
|
|
17
19
|
status TEXT NOT NULL,
|
|
18
20
|
title TEXT NOT NULL,
|
|
19
|
-
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
21
|
+
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
22
|
+
PRIMARY KEY (run_id, review_id)
|
|
20
23
|
);
|
|
21
|
-
|
|
22
|
-
CREATE INDEX IF NOT EXISTS looms_reviews_actor_id_idx ON looms_reviews (actor_id);
|
|
23
24
|
`.trim();
|
|
24
25
|
export const SQLITE_SCHEMA_SQL = `
|
|
25
|
-
CREATE TABLE IF NOT EXISTS
|
|
26
|
-
|
|
26
|
+
CREATE TABLE IF NOT EXISTS looms_run_actors (
|
|
27
|
+
run_id TEXT NOT NULL,
|
|
28
|
+
actor_id TEXT NOT NULL,
|
|
27
29
|
kind TEXT,
|
|
28
30
|
status TEXT,
|
|
29
31
|
definition_name TEXT,
|
|
30
32
|
parent_actor_id TEXT,
|
|
31
|
-
updated_at INTEGER NOT NULL
|
|
33
|
+
updated_at INTEGER NOT NULL,
|
|
34
|
+
PRIMARY KEY (run_id, actor_id)
|
|
32
35
|
);
|
|
33
36
|
|
|
34
|
-
CREATE TABLE IF NOT EXISTS
|
|
35
|
-
|
|
36
|
-
|
|
37
|
+
CREATE TABLE IF NOT EXISTS looms_run_reviews (
|
|
38
|
+
run_id TEXT NOT NULL,
|
|
39
|
+
review_id TEXT NOT NULL,
|
|
37
40
|
status TEXT NOT NULL,
|
|
38
41
|
title TEXT NOT NULL,
|
|
39
|
-
updated_at INTEGER NOT NULL
|
|
42
|
+
updated_at INTEGER NOT NULL,
|
|
43
|
+
PRIMARY KEY (run_id, review_id)
|
|
40
44
|
);
|
|
41
|
-
|
|
42
|
-
CREATE INDEX IF NOT EXISTS looms_reviews_actor_id_idx ON looms_reviews (actor_id);
|
|
43
45
|
`.trim();
|
|
@@ -9,35 +9,39 @@ function hasDb(options) {
|
|
|
9
9
|
function applyOp(db, op) {
|
|
10
10
|
switch (op.type) {
|
|
11
11
|
case 'upsertActor':
|
|
12
|
-
db.run(`INSERT INTO
|
|
13
|
-
VALUES (?, ?, ?, ?, ?, ?)
|
|
14
|
-
ON CONFLICT (actor_id) DO UPDATE SET
|
|
12
|
+
db.run(`INSERT INTO looms_run_actors (run_id, actor_id, kind, status, definition_name, parent_actor_id, updated_at)
|
|
13
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
14
|
+
ON CONFLICT (run_id, actor_id) DO UPDATE SET
|
|
15
15
|
kind = excluded.kind,
|
|
16
16
|
status = excluded.status,
|
|
17
17
|
definition_name = excluded.definition_name,
|
|
18
18
|
parent_actor_id = excluded.parent_actor_id,
|
|
19
|
-
updated_at = excluded.updated_at`, [
|
|
19
|
+
updated_at = excluded.updated_at`, [
|
|
20
|
+
op.runId,
|
|
21
|
+
op.actorId,
|
|
22
|
+
op.kind,
|
|
23
|
+
op.status,
|
|
24
|
+
op.definitionName,
|
|
25
|
+
op.parentActorId,
|
|
26
|
+
op.updatedAt,
|
|
27
|
+
]);
|
|
20
28
|
return;
|
|
21
29
|
case 'setActorStatus':
|
|
22
|
-
db.run(`INSERT INTO
|
|
23
|
-
VALUES (?, ?, ?)
|
|
24
|
-
ON CONFLICT (actor_id) DO UPDATE SET
|
|
30
|
+
db.run(`INSERT INTO looms_run_actors (run_id, actor_id, status, updated_at)
|
|
31
|
+
VALUES (?, ?, ?, ?)
|
|
32
|
+
ON CONFLICT (run_id, actor_id) DO UPDATE SET
|
|
25
33
|
status = excluded.status,
|
|
26
|
-
updated_at = excluded.updated_at`, [op.actorId, op.status, op.updatedAt]);
|
|
34
|
+
updated_at = excluded.updated_at`, [op.runId, op.actorId, op.status, op.updatedAt]);
|
|
27
35
|
return;
|
|
28
36
|
case 'upsertReview':
|
|
29
|
-
db.run(`INSERT INTO
|
|
37
|
+
db.run(`INSERT INTO looms_run_reviews (run_id, review_id, status, title, updated_at)
|
|
30
38
|
VALUES (?, ?, ?, ?, ?)
|
|
31
|
-
ON CONFLICT (review_id) DO UPDATE SET
|
|
39
|
+
ON CONFLICT (run_id, review_id) DO UPDATE SET
|
|
32
40
|
status = excluded.status,
|
|
33
|
-
updated_at = excluded.updated_at`, [op.
|
|
41
|
+
updated_at = excluded.updated_at`, [op.runId, op.reviewId, op.status, op.title, op.updatedAt]);
|
|
34
42
|
return;
|
|
35
43
|
case 'setReviewStatus':
|
|
36
|
-
db.run(`UPDATE
|
|
37
|
-
op.status,
|
|
38
|
-
op.updatedAt,
|
|
39
|
-
op.reviewId,
|
|
40
|
-
]);
|
|
44
|
+
db.run(`UPDATE looms_run_reviews SET status = ?, updated_at = ? WHERE run_id = ? AND review_id = ?`, [op.status, op.updatedAt, op.runId, op.reviewId]);
|
|
41
45
|
return;
|
|
42
46
|
default: {
|
|
43
47
|
const exhaustiveCheck = op;
|
|
@@ -60,13 +64,14 @@ export function sqlite(options = {}) {
|
|
|
60
64
|
});
|
|
61
65
|
apply(ops);
|
|
62
66
|
}),
|
|
63
|
-
getActor: (actorId) => runProjectorSync('sqlite', () => {
|
|
67
|
+
getActor: (runId, actorId) => runProjectorSync('sqlite', () => {
|
|
64
68
|
const row = db
|
|
65
|
-
.query(`SELECT actor_id, kind, status, definition_name, parent_actor_id, updated_at
|
|
66
|
-
FROM
|
|
67
|
-
.get(actorId);
|
|
69
|
+
.query(`SELECT run_id, actor_id, kind, status, definition_name, parent_actor_id, updated_at
|
|
70
|
+
FROM looms_run_actors WHERE run_id = ? AND actor_id = ?`)
|
|
71
|
+
.get(runId, actorId);
|
|
68
72
|
return row
|
|
69
73
|
? {
|
|
74
|
+
runId: row.run_id,
|
|
70
75
|
actorId: row.actor_id,
|
|
71
76
|
kind: row.kind,
|
|
72
77
|
status: row.status,
|
|
@@ -76,18 +81,18 @@ export function sqlite(options = {}) {
|
|
|
76
81
|
}
|
|
77
82
|
: null;
|
|
78
83
|
}),
|
|
79
|
-
listReviews: (
|
|
80
|
-
const rows =
|
|
84
|
+
listReviews: (runId) => runProjectorSync('sqlite', () => {
|
|
85
|
+
const rows = runId
|
|
81
86
|
? db
|
|
82
|
-
.query(`SELECT
|
|
83
|
-
FROM
|
|
84
|
-
.all(
|
|
87
|
+
.query(`SELECT run_id, review_id, status, title, updated_at
|
|
88
|
+
FROM looms_run_reviews WHERE run_id = ?`)
|
|
89
|
+
.all(runId)
|
|
85
90
|
: db
|
|
86
|
-
.query(`SELECT
|
|
91
|
+
.query(`SELECT run_id, review_id, status, title, updated_at FROM looms_run_reviews`)
|
|
87
92
|
.all();
|
|
88
93
|
return rows.map((row) => ({
|
|
94
|
+
runId: row.run_id,
|
|
89
95
|
reviewId: row.review_id,
|
|
90
|
-
actorId: row.actor_id,
|
|
91
96
|
status: row.status,
|
|
92
97
|
title: row.title,
|
|
93
98
|
updatedAt: row.updated_at,
|
package/dist/react/derived.d.ts
CHANGED
|
@@ -1,19 +1,37 @@
|
|
|
1
|
-
import { type EventEnvelope, type ProjectionDefinition } from '@swirls/looms/core';
|
|
2
|
-
import type { AnyEventEnvelope, RegisteredEvent } from './register';
|
|
3
|
-
export type { AnyEventEnvelope } from './register';
|
|
1
|
+
import { type AnyEventEnvelope, type EventEnvelope, type InferSchemaOutput, type ProjectionDefinition, type RegisteredEvent, type SchemaInput } from '@swirls/looms/core';
|
|
4
2
|
export { EventIndex, eventThreadKey } from '@swirls/looms/core';
|
|
5
|
-
export interface
|
|
3
|
+
export interface LiveFoldDefinition<S, TEvent extends AnyEventEnvelope = RegisteredEvent> {
|
|
4
|
+
readonly kind: 'liveFold';
|
|
6
5
|
readonly name?: string;
|
|
6
|
+
readonly shape?: unknown;
|
|
7
7
|
readonly initialState: S;
|
|
8
8
|
reduce(state: S, event: TEvent): S;
|
|
9
|
-
readonly includeEphemeral?: boolean;
|
|
10
9
|
}
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
/**
|
|
11
|
+
* Client-side fold that sees ephemeral events, which projections skip. Ephemeral events from an
|
|
12
|
+
* effect attempt that the runtime later retried never reach the reducer. The reducer still owns
|
|
13
|
+
* resets on domain events, such as the durable message that replaces streamed text. Use a
|
|
14
|
+
* projection for anything else.
|
|
15
|
+
*/
|
|
16
|
+
export declare function defineLiveFold<S, TEvent extends AnyEventEnvelope = RegisteredEvent>(def: {
|
|
17
|
+
readonly name?: string;
|
|
18
|
+
readonly shape?: undefined;
|
|
19
|
+
readonly initialState: S;
|
|
20
|
+
reduce(state: S, event: TEvent): S;
|
|
21
|
+
}): LiveFoldDefinition<S, TEvent>;
|
|
22
|
+
export declare function defineLiveFold<TShape extends SchemaInput, TEvent extends AnyEventEnvelope = RegisteredEvent>(def: {
|
|
23
|
+
readonly name?: string;
|
|
24
|
+
readonly shape: TShape;
|
|
25
|
+
readonly initialState: InferSchemaOutput<TShape>;
|
|
26
|
+
reduce(state: NoInfer<InferSchemaOutput<TShape>>, event: TEvent): InferSchemaOutput<TShape>;
|
|
27
|
+
}): LiveFoldDefinition<InferSchemaOutput<TShape>, TEvent>;
|
|
28
|
+
/** Fold events through a live fold, as `useLiveFold` does, including ephemeral events. */
|
|
29
|
+
export declare function foldLive<S, TEvent extends AnyEventEnvelope = RegisteredEvent>(definition: LiveFoldDefinition<S, TEvent>, events: readonly TEvent[]): S;
|
|
13
30
|
export declare class ProjectionCache {
|
|
14
31
|
private projectionCache;
|
|
15
32
|
private foldCache;
|
|
16
33
|
project<S>(definition: ProjectionDefinition<S>, events: readonly EventEnvelope[]): S;
|
|
17
|
-
|
|
34
|
+
/** A retry can supersede events already folded, so a batch containing one refolds from scratch. */
|
|
35
|
+
fold<S, TEvent extends AnyEventEnvelope = RegisteredEvent>(definition: LiveFoldDefinition<S, TEvent>, events: readonly TEvent[]): S;
|
|
18
36
|
clear(): void;
|
|
19
37
|
}
|
package/dist/react/derived.js
CHANGED
|
@@ -1,37 +1,33 @@
|
|
|
1
|
-
import { project } from '@swirls/looms/core';
|
|
1
|
+
import { dropSupersededAttempts, project, retriedEffectId, } from '@swirls/looms/core';
|
|
2
2
|
export { EventIndex, eventThreadKey } from '@swirls/looms/core';
|
|
3
|
-
export function
|
|
4
|
-
return def;
|
|
3
|
+
export function defineLiveFold(def) {
|
|
4
|
+
return { ...def, kind: 'liveFold' };
|
|
5
5
|
}
|
|
6
|
-
|
|
7
|
-
let state = from
|
|
8
|
-
const
|
|
9
|
-
for (const event of events) {
|
|
10
|
-
if (event.ephemeral && !includeEphemeral) {
|
|
11
|
-
continue;
|
|
12
|
-
}
|
|
6
|
+
function foldFrom(definition, events, from) {
|
|
7
|
+
let state = from;
|
|
8
|
+
for (const event of dropSupersededAttempts(events)) {
|
|
13
9
|
state = definition.reduce(state, event);
|
|
14
10
|
}
|
|
15
11
|
return state;
|
|
16
12
|
}
|
|
17
|
-
|
|
18
|
-
|
|
13
|
+
/** Fold events through a live fold, as `useLiveFold` does, including ephemeral events. */
|
|
14
|
+
export function foldLive(definition, events) {
|
|
15
|
+
return foldFrom(definition, events, definition.initialState);
|
|
16
|
+
}
|
|
17
|
+
function hasNoRetries(batch) {
|
|
18
|
+
return !batch.some((event) => retriedEffectId(event) !== undefined);
|
|
19
|
+
}
|
|
20
|
+
/** Folds only the events appended since the last call, unless `canAppend` rejects them. */
|
|
21
|
+
function advanceCache(cache, definition, events, compute, canAppend = () => true) {
|
|
19
22
|
const cached = cache.get(definition);
|
|
20
|
-
if (
|
|
21
|
-
const state = compute(definition, events);
|
|
22
|
-
cache.set(definition, { state, upTo: total });
|
|
23
|
-
return state;
|
|
24
|
-
}
|
|
25
|
-
if (cached.upTo === total) {
|
|
23
|
+
if (cached?.upTo === events.length) {
|
|
26
24
|
return cached.state;
|
|
27
25
|
}
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
}
|
|
33
|
-
const state = compute(definition, events);
|
|
34
|
-
cache.set(definition, { state, upTo: total });
|
|
26
|
+
const batch = cached && cached.upTo < events.length ? events.slice(cached.upTo) : undefined;
|
|
27
|
+
const state = cached && batch && canAppend(batch)
|
|
28
|
+
? compute(definition, batch, cached.state)
|
|
29
|
+
: compute(definition, events, definition.initialState);
|
|
30
|
+
cache.set(definition, { state, upTo: events.length });
|
|
35
31
|
return state;
|
|
36
32
|
}
|
|
37
33
|
export class ProjectionCache {
|
|
@@ -40,8 +36,9 @@ export class ProjectionCache {
|
|
|
40
36
|
project(definition, events) {
|
|
41
37
|
return advanceCache(this.projectionCache, definition, events, project);
|
|
42
38
|
}
|
|
39
|
+
/** A retry can supersede events already folded, so a batch containing one refolds from scratch. */
|
|
43
40
|
fold(definition, events) {
|
|
44
|
-
return advanceCache(this.foldCache, definition, events,
|
|
41
|
+
return advanceCache(this.foldCache, definition, events, foldFrom, hasNoRetries);
|
|
45
42
|
}
|
|
46
43
|
clear() {
|
|
47
44
|
this.projectionCache = new WeakMap();
|
package/dist/react/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export { createLoomsStore, type LoomsClientStore, type LoomsClientStoreOptions, type LoomsConnectionStatus, type LoomsStoreQuery, type CoalesceOption, type StoreListener, } from './store';
|
|
2
|
-
export {
|
|
2
|
+
export { defineLiveFold, foldLive, EventIndex, eventThreadKey, type LiveFoldDefinition, } from './derived';
|
|
3
3
|
export { materializeEvents } from './materialize';
|
|
4
4
|
export { emptyTables, type MaterializedTables, type RunRow, type ThreadRow } from './tables';
|
|
5
|
-
export type { LoomsRegister, RegisteredEvent, AnyEventEnvelope } from '
|
|
6
|
-
export { LoomsProvider, useRunStore, useProjection, useRunSelector, useRunSummary, useRunEvents, useThreadTree, useEventCounts, useEventBySeq, useLatestEvent,
|
|
5
|
+
export type { LoomsRegister, RegisteredEvent, AnyEventEnvelope } from '@swirls/looms/core';
|
|
6
|
+
export { LoomsProvider, useRunStore, useProjection, useRunSelector, useRunSummary, useRunEvents, useThreadTree, useEventCounts, useEventBySeq, useLatestEvent, useLiveFold, type LoomsProviderProps, type UseRunStoreOptions, type RunSummary, } from './react';
|
package/dist/react/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { createLoomsStore, } from './store.js';
|
|
2
|
-
export {
|
|
2
|
+
export { defineLiveFold, foldLive, EventIndex, eventThreadKey, } from './derived.js';
|
|
3
3
|
export { materializeEvents } from './materialize.js';
|
|
4
4
|
export { emptyTables } from './tables.js';
|
|
5
|
-
export { LoomsProvider, useRunStore, useProjection, useRunSelector, useRunSummary, useRunEvents, useThreadTree, useEventCounts, useEventBySeq, useLatestEvent,
|
|
5
|
+
export { LoomsProvider, useRunStore, useProjection, useRunSelector, useRunSummary, useRunEvents, useThreadTree, useEventCounts, useEventBySeq, useLatestEvent, useLiveFold, } from './react.js';
|