@swirls/looms 0.4.0 → 0.5.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/CHANGELOG.md +61 -0
- package/dist/actor/cell.d.ts +21 -2
- package/dist/actor/cell.js +15 -3
- package/dist/actor/host.d.ts +10 -2
- package/dist/actor/host.js +27 -4
- package/dist/actor/index.d.ts +2 -1
- package/dist/actor/index.js +1 -1
- package/dist/actor/routing.d.ts +3 -0
- package/dist/actor/routing.js +13 -2
- package/dist/agent/definitions-store.d.ts +1 -1
- package/dist/agent/definitions-store.js +1 -1
- package/dist/agent/definitions.d.ts +48 -0
- package/dist/agent/definitions.js +44 -0
- package/dist/agent/effects.d.ts +184 -0
- package/dist/agent/effects.js +172 -40
- package/dist/agent/events.d.ts +89 -0
- package/dist/agent/events.js +25 -0
- package/dist/agent/module.d.ts +412 -0
- package/dist/agent/module.js +5 -1
- package/dist/agent/scope.d.ts +88 -0
- package/dist/agent/threads.js +39 -3
- package/dist/agent/types.d.ts +4 -0
- package/dist/approval/effects.d.ts +40 -0
- package/dist/approval/module.d.ts +80 -0
- package/dist/client/client.d.ts +468 -119
- package/dist/client/client.js +214 -21
- package/dist/client/index.d.ts +1 -1
- package/dist/client/sse.d.ts +1 -1
- package/dist/client/sse.js +2 -2
- package/dist/cloudflare/durable-object.d.ts +66 -2
- package/dist/cloudflare/durable-object.js +96 -1
- package/dist/cloudflare/event-store.d.ts +7 -3
- package/dist/cloudflare/event-store.js +16 -3
- package/dist/cloudflare/index.d.ts +7 -1
- package/dist/cloudflare/index.js +7 -1
- package/dist/cloudflare/memory-queue.d.ts +18 -0
- package/dist/cloudflare/memory-queue.js +53 -0
- package/dist/cloudflare/queue-consumer.d.ts +52 -0
- package/dist/cloudflare/queue-consumer.js +102 -0
- package/dist/cloudflare/queue-message.d.ts +31 -0
- package/dist/cloudflare/queue-message.js +33 -0
- package/dist/cloudflare/queue-pull.d.ts +62 -0
- package/dist/cloudflare/queue-pull.js +130 -0
- package/dist/cloudflare/queue-worker.d.ts +39 -0
- package/dist/cloudflare/queue-worker.js +62 -0
- package/dist/cloudflare/queues.d.ts +10 -0
- package/dist/cloudflare/queues.js +10 -0
- package/dist/cloudflare/r2-blob-store.d.ts +33 -0
- package/dist/cloudflare/r2-blob-store.js +60 -0
- package/dist/cloudflare/router.d.ts +4 -1
- package/dist/cloudflare/router.js +8 -3
- package/dist/cloudflare/websocket.d.ts +27 -0
- package/dist/cloudflare/websocket.js +215 -0
- package/dist/core/blob-offload.d.ts +21 -0
- package/dist/core/blob-offload.js +143 -0
- package/dist/core/blob-store.d.ts +45 -0
- package/dist/core/blob-store.js +164 -0
- package/dist/core/blobs.d.ts +37 -0
- package/dist/core/blobs.js +160 -0
- package/dist/core/definition-resolver.d.ts +40 -0
- package/dist/core/definition-resolver.js +129 -0
- package/dist/core/definition-store.d.ts +6 -0
- package/dist/core/definition-store.js +37 -1
- package/dist/core/effects.d.ts +86 -3
- package/dist/core/effects.js +99 -3
- package/dist/core/envelope.d.ts +9 -0
- package/dist/core/envelope.js +4 -0
- package/dist/core/event-codec.js +3 -0
- package/dist/core/fold.js +83 -1
- package/dist/core/ids.d.ts +2 -0
- package/dist/core/ids.js +4 -0
- package/dist/core/index.d.ts +8 -0
- package/dist/core/index.js +8 -0
- package/dist/core/launcher.d.ts +97 -0
- package/dist/core/launcher.js +67 -0
- package/dist/core/module-scope.d.ts +7 -3
- package/dist/core/module-scope.js +3 -0
- package/dist/core/module.d.ts +10 -0
- package/dist/core/projection.js +4 -0
- package/dist/core/protocol.d.ts +36 -1
- package/dist/core/protocol.js +35 -0
- package/dist/core/published.d.ts +2 -0
- package/dist/core/published.js +4 -1
- package/dist/core/replay.d.ts +96 -0
- package/dist/core/run-context.d.ts +33 -0
- package/dist/core/run-context.js +23 -0
- package/dist/core/sqlite-store.js +57 -5
- package/dist/core/state.d.ts +60 -0
- package/dist/core/state.js +19 -0
- package/dist/core/store.d.ts +6 -0
- package/dist/core/store.js +7 -0
- package/dist/core/subscription.d.ts +97 -0
- package/dist/core/subscription.js +224 -0
- package/dist/core/thread.d.ts +7 -0
- package/dist/core/thread.js +1 -0
- package/dist/core/trace.d.ts +21 -0
- package/dist/core/trace.js +45 -0
- package/dist/core/worker-callback.d.ts +25 -1
- package/dist/debugger/app/assets/{index-CgDzWJg7.css → index-B7GdT1UA.css} +1 -1
- package/dist/debugger/app/assets/index-C31SnCuN.js +22 -0
- package/dist/debugger/app/assets/routes-DPA3O5AF.js +47 -0
- package/dist/debugger/app/index.html +2 -2
- package/dist/debugger/components/event-inspector.d.ts +3 -1
- package/dist/debugger/components/event-inspector.js +25 -4
- package/dist/debugger/components/event-stream.d.ts +4 -1
- package/dist/debugger/components/event-stream.js +2 -2
- package/dist/debugger/components/run-tree.d.ts +5 -2
- package/dist/debugger/components/run-tree.js +10 -4
- package/dist/debugger/context.d.ts +8 -1
- package/dist/debugger/context.js +15 -3
- package/dist/debugger/events/protocol-summarize.js +18 -3
- package/dist/debugger/index.d.ts +1 -1
- package/dist/debugger/index.js +1 -1
- package/dist/debugger/shell/debugger-shell.js +1 -1
- package/dist/debugger/shell/events-pane.js +4 -3
- package/dist/debugger/styles.css +1 -0
- package/dist/evaluator/definitions-store.d.ts +3 -4
- package/dist/evaluator/definitions-store.js +3 -3
- package/dist/evaluator/definitions.d.ts +4 -1
- package/dist/evaluator/definitions.js +5 -2
- package/dist/evaluator/effects.d.ts +41 -0
- package/dist/evaluator/effects.js +6 -2
- package/dist/evaluator/module.d.ts +84 -0
- package/dist/evaluator/module.js +7 -3
- package/dist/evaluator/threads.d.ts +2 -0
- package/dist/evaluator/threads.js +4 -0
- package/dist/projectors/event-log.d.ts +87 -0
- package/dist/projectors/event-log.js +396 -0
- package/dist/projectors/index-model.d.ts +12 -0
- package/dist/projectors/index-model.js +12 -3
- package/dist/projectors/memory.js +7 -0
- package/dist/projectors/postgres.d.ts +1 -0
- package/dist/projectors/postgres.js +32 -15
- package/dist/projectors/schema.d.ts +5 -0
- package/dist/projectors/schema.js +26 -1
- package/dist/projectors/sqlite.js +47 -19
- package/dist/projectors/with-projectors.d.ts +7 -1
- package/dist/projectors/with-projectors.js +13 -3
- package/dist/react/materialize.js +5 -1
- package/dist/react/react.d.ts +10 -2
- package/dist/react/react.js +43 -19
- package/dist/react/store.d.ts +11 -2
- package/dist/react/store.js +104 -12
- package/dist/react/tables.d.ts +4 -1
- package/dist/runtime/cancellation.d.ts +2 -1
- package/dist/runtime/cancellation.js +35 -2
- package/dist/runtime/child-run.d.ts +10 -0
- package/dist/runtime/child-run.js +58 -0
- package/dist/runtime/deadline-transition-planner.js +11 -2
- package/dist/runtime/dispatch-effect.d.ts +2 -2
- package/dist/runtime/dispatch-effect.js +43 -19
- package/dist/runtime/errors.d.ts +14 -1
- package/dist/runtime/errors.js +10 -0
- package/dist/runtime/filesystem-blob-store.d.ts +6 -0
- package/dist/runtime/filesystem-blob-store.js +59 -0
- package/dist/runtime/helpers.d.ts +15 -6
- package/dist/runtime/helpers.js +46 -6
- package/dist/runtime/index.d.ts +4 -2
- package/dist/runtime/index.js +3 -1
- package/dist/runtime/local-effect-executor.d.ts +11 -2
- package/dist/runtime/local-effect-executor.js +80 -4
- package/dist/runtime/looms.d.ts +38 -4
- package/dist/runtime/looms.js +17 -3
- package/dist/runtime/repository.d.ts +5 -4
- package/dist/runtime/repository.js +5 -2
- package/dist/runtime/retry-policy.d.ts +5 -0
- package/dist/runtime/retry-policy.js +10 -2
- package/dist/runtime/run-start.d.ts +1 -1
- package/dist/runtime/run-start.js +10 -0
- package/dist/runtime/runtime.d.ts +2 -2
- package/dist/runtime/runtime.js +191 -68
- package/dist/runtime/server/auth.d.ts +3 -0
- package/dist/runtime/server/fetch-handler.d.ts +2 -2
- package/dist/runtime/server/fetch-handler.js +35 -4
- package/dist/runtime/server/grant.d.ts +49 -0
- package/dist/runtime/server/grant.js +177 -0
- package/dist/runtime/server/routes/effects.js +47 -5
- package/dist/runtime/server/routes/runs.js +6 -1
- package/dist/runtime/server/run-route.d.ts +18 -0
- package/dist/runtime/server/run-route.js +26 -0
- package/dist/runtime/server.d.ts +3 -1
- package/dist/runtime/server.js +3 -1
- package/dist/runtime/sse.js +26 -4
- package/dist/runtime/subscription.d.ts +8 -0
- package/dist/runtime/subscription.js +23 -0
- package/dist/runtime/types.d.ts +87 -8
- package/dist/runtime/wake-engine.d.ts +4 -2
- package/dist/runtime/wake-engine.js +57 -6
- package/dist/runtime/worker-callbacks.d.ts +12 -2
- package/dist/runtime/worker-callbacks.js +102 -72
- package/dist/runtime/worker-effect-dispatch.d.ts +14 -5
- package/dist/runtime/worker-effect-dispatch.js +84 -18
- package/dist/schedule/debugger/index.d.ts +4 -0
- package/dist/schedule/debugger/index.js +11 -0
- package/dist/schedule/debugger/schedule-view.d.ts +3 -0
- package/dist/schedule/debugger/schedule-view.js +68 -0
- package/dist/schedule/debugger/summarize.d.ts +2 -0
- package/dist/schedule/debugger/summarize.js +43 -0
- package/dist/schedule/definitions-store.d.ts +10 -0
- package/dist/schedule/definitions-store.js +8 -0
- package/dist/schedule/definitions.d.ts +57 -0
- package/dist/schedule/definitions.js +30 -0
- package/dist/schedule/effects.d.ts +756 -0
- package/dist/schedule/effects.js +244 -0
- package/dist/schedule/ensure.d.ts +55 -0
- package/dist/schedule/ensure.js +109 -0
- package/dist/schedule/events.d.ts +116 -0
- package/dist/schedule/events.js +49 -0
- package/dist/schedule/index.d.ts +11 -0
- package/dist/schedule/index.js +11 -0
- package/dist/schedule/module.d.ts +1474 -0
- package/dist/schedule/module.js +22 -0
- package/dist/schedule/projections.d.ts +55 -0
- package/dist/schedule/projections.js +88 -0
- package/dist/schedule/scope.d.ts +121 -0
- package/dist/schedule/scope.js +7 -0
- package/dist/schedule/signals.d.ts +25 -0
- package/dist/schedule/signals.js +33 -0
- package/dist/schedule/specs.d.ts +59 -0
- package/dist/schedule/specs.js +231 -0
- package/dist/schedule/threads.d.ts +25 -0
- package/dist/schedule/threads.js +259 -0
- package/dist/testing/corpus-module.d.ts +34 -0
- package/dist/testing/corpus-module.js +87 -0
- package/dist/testing/index.d.ts +2 -0
- package/dist/testing/index.js +5 -6
- package/dist/testing/replay-corpus.d.ts +22 -0
- package/dist/testing/replay-corpus.js +50 -0
- package/dist/workflow/definitions-store.d.ts +1 -1
- package/dist/workflow/definitions-store.js +1 -1
- package/dist/workflow/definitions.d.ts +14 -0
- package/dist/workflow/effects.d.ts +122 -0
- package/dist/workflow/effects.js +16 -6
- package/dist/workflow/events.d.ts +42 -0
- package/dist/workflow/events.js +4 -0
- package/dist/workflow/module.d.ts +313 -0
- package/dist/workflow/module.js +7 -3
- package/dist/workflow/scope.d.ts +42 -0
- package/dist/workflow/threads.d.ts +72 -0
- package/dist/workflow/threads.js +106 -38
- package/package.json +14 -1
- package/dist/debugger/app/assets/index-D--9esm9.js +0 -22
- package/dist/debugger/app/assets/routes-Dakrdusf.js +0 -47
|
@@ -0,0 +1,396 @@
|
|
|
1
|
+
import { DateTime, Effect, Option, Predicate, Schema } from 'effect';
|
|
2
|
+
import createPostgresClient, {} from 'postgres';
|
|
3
|
+
import { completedRunStatus, isJsonObject, readRunLaunchSource, } from '@swirls/looms/core';
|
|
4
|
+
import { runProjectorPromise } from './projector.js';
|
|
5
|
+
const decodeAttributes = Schema.decodeUnknownOption(Schema.Record(Schema.String, Schema.String));
|
|
6
|
+
export const POSTGRES_EVENT_LOG_SCHEMA_SQL = `
|
|
7
|
+
CREATE TABLE IF NOT EXISTS looms_run_events (
|
|
8
|
+
run_id TEXT NOT NULL,
|
|
9
|
+
seq BIGINT NOT NULL,
|
|
10
|
+
event_id TEXT NOT NULL,
|
|
11
|
+
ts TIMESTAMPTZ NOT NULL,
|
|
12
|
+
type TEXT NOT NULL,
|
|
13
|
+
payload JSONB NOT NULL,
|
|
14
|
+
thread_id TEXT,
|
|
15
|
+
parent_thread_id TEXT,
|
|
16
|
+
causation_id TEXT,
|
|
17
|
+
correlation_id TEXT,
|
|
18
|
+
effect_id TEXT,
|
|
19
|
+
origin JSONB NOT NULL,
|
|
20
|
+
idempotency_key TEXT,
|
|
21
|
+
PRIMARY KEY (run_id, seq)
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
CREATE TABLE IF NOT EXISTS looms_run_index (
|
|
25
|
+
run_id TEXT PRIMARY KEY,
|
|
26
|
+
status TEXT NOT NULL,
|
|
27
|
+
kind TEXT,
|
|
28
|
+
definition_name TEXT,
|
|
29
|
+
definition_version TEXT,
|
|
30
|
+
root_thread_id TEXT,
|
|
31
|
+
parent_run_id TEXT,
|
|
32
|
+
launched_by_effect_id TEXT,
|
|
33
|
+
correlation_id TEXT,
|
|
34
|
+
attributes JSONB NOT NULL DEFAULT '{}'::jsonb,
|
|
35
|
+
tenant TEXT,
|
|
36
|
+
project TEXT,
|
|
37
|
+
org TEXT,
|
|
38
|
+
started_at TIMESTAMPTZ,
|
|
39
|
+
updated_at TIMESTAMPTZ NOT NULL,
|
|
40
|
+
completed_at TIMESTAMPTZ
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
CREATE INDEX IF NOT EXISTS looms_run_index_tenant ON looms_run_index (tenant);
|
|
44
|
+
CREATE INDEX IF NOT EXISTS looms_run_index_project ON looms_run_index (project);
|
|
45
|
+
CREATE INDEX IF NOT EXISTS looms_run_index_org ON looms_run_index (org);
|
|
46
|
+
CREATE INDEX IF NOT EXISTS looms_run_index_parent ON looms_run_index (parent_run_id);
|
|
47
|
+
CREATE INDEX IF NOT EXISTS looms_run_index_status ON looms_run_index (status);
|
|
48
|
+
`.trim();
|
|
49
|
+
const terminal = new Set(['completed', 'failed', 'cancelled']);
|
|
50
|
+
export function eventLogOps(event) {
|
|
51
|
+
if (event.ephemeral) {
|
|
52
|
+
return [];
|
|
53
|
+
}
|
|
54
|
+
const entry = {
|
|
55
|
+
runId: event.runId,
|
|
56
|
+
seq: event.seq,
|
|
57
|
+
eventId: event.id,
|
|
58
|
+
ts: event.ts,
|
|
59
|
+
type: event.type,
|
|
60
|
+
payload: event.payload,
|
|
61
|
+
threadId: event.threadId,
|
|
62
|
+
parentThreadId: event.parentThreadId ?? null,
|
|
63
|
+
causationId: event.causationId ?? null,
|
|
64
|
+
correlationId: event.correlationId ?? null,
|
|
65
|
+
effectId: event.effectId ?? null,
|
|
66
|
+
origin: event.origin,
|
|
67
|
+
idempotencyKey: event.idempotencyKey ?? null,
|
|
68
|
+
};
|
|
69
|
+
const payload = isJsonObject(event.payload) ? event.payload : {};
|
|
70
|
+
switch (event.type) {
|
|
71
|
+
case 'runtime.run.started':
|
|
72
|
+
return [{ type: 'insert', entry }, startOp(event, payload)];
|
|
73
|
+
case 'runtime.run.completed':
|
|
74
|
+
return [
|
|
75
|
+
{ type: 'insert', entry },
|
|
76
|
+
{
|
|
77
|
+
type: 'finish',
|
|
78
|
+
runId: event.runId,
|
|
79
|
+
status: completedRunStatus(event.payload),
|
|
80
|
+
at: event.ts,
|
|
81
|
+
},
|
|
82
|
+
];
|
|
83
|
+
case 'runtime.run.cancelled':
|
|
84
|
+
return [
|
|
85
|
+
{ type: 'insert', entry },
|
|
86
|
+
{ type: 'finish', runId: event.runId, status: 'cancelled', at: event.ts },
|
|
87
|
+
];
|
|
88
|
+
default:
|
|
89
|
+
return [
|
|
90
|
+
{ type: 'insert', entry },
|
|
91
|
+
{ type: 'touch', runId: event.runId, at: event.ts },
|
|
92
|
+
];
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
export function memoryEventLog() {
|
|
96
|
+
const events = new Map();
|
|
97
|
+
const runs = new Map();
|
|
98
|
+
const projector = createEventLogProjector('memory-event-log', {
|
|
99
|
+
init: () => Promise.resolve(),
|
|
100
|
+
apply: (batch) => {
|
|
101
|
+
for (const event of batch) {
|
|
102
|
+
for (const op of eventLogOps(event)) {
|
|
103
|
+
applyMemoryOp(events, runs, op);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return Promise.resolve();
|
|
107
|
+
},
|
|
108
|
+
});
|
|
109
|
+
return {
|
|
110
|
+
...projector,
|
|
111
|
+
events: (runId) => [...events.values()]
|
|
112
|
+
.filter((entry) => entry.runId === runId)
|
|
113
|
+
.toSorted((left, right) => left.seq - right.seq),
|
|
114
|
+
run: (runId) => {
|
|
115
|
+
const row = runs.get(runId);
|
|
116
|
+
return row ? { ...row, attributes: { ...row.attributes } } : null;
|
|
117
|
+
},
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
export function postgresEventLog(options) {
|
|
121
|
+
const created = 'url' in options;
|
|
122
|
+
const connected = created ? createPostgresClient(options.url) : options.sql;
|
|
123
|
+
return createEventLogProjector('postgres-event-log', postgresStore(connected, created));
|
|
124
|
+
}
|
|
125
|
+
function createEventLogProjector(name, store) {
|
|
126
|
+
return {
|
|
127
|
+
name,
|
|
128
|
+
version: '1',
|
|
129
|
+
init: store.init,
|
|
130
|
+
project: (events) => store.apply(events),
|
|
131
|
+
dispose: store.dispose,
|
|
132
|
+
rebuild: (source, options) => Effect.runPromise(rebuildEventLog(store, source, options)),
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
function rebuildEventLog(store, source, options) {
|
|
136
|
+
const batchSize = options?.batchSize ?? 100;
|
|
137
|
+
return Effect.gen(function* () {
|
|
138
|
+
yield* Effect.promise(() => store.init());
|
|
139
|
+
const runIds = options?.runId ? [options.runId] : yield* source.listRuns;
|
|
140
|
+
let written = 0;
|
|
141
|
+
for (const runId of runIds) {
|
|
142
|
+
let fromSeq = 1;
|
|
143
|
+
while (fromSeq > 0) {
|
|
144
|
+
const events = yield* source.read(runId, { fromSeq, limit: batchSize });
|
|
145
|
+
const last = events.at(-1);
|
|
146
|
+
if (!last) {
|
|
147
|
+
break;
|
|
148
|
+
}
|
|
149
|
+
yield* Effect.promise(() => store.apply(events));
|
|
150
|
+
written += events.filter((event) => !event.ephemeral).length;
|
|
151
|
+
fromSeq = events.length < batchSize ? 0 : last.seq + 1;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
return written;
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
function applyMemoryOp(events, runs, op) {
|
|
158
|
+
switch (op.type) {
|
|
159
|
+
case 'insert':
|
|
160
|
+
events.set(`${op.entry.runId}:${op.entry.seq}`, op.entry);
|
|
161
|
+
return;
|
|
162
|
+
case 'start':
|
|
163
|
+
runs.set(op.runId, mergeStart(runs.get(op.runId), op));
|
|
164
|
+
return;
|
|
165
|
+
case 'finish':
|
|
166
|
+
runs.set(op.runId, mergeFinish(runs.get(op.runId), op));
|
|
167
|
+
return;
|
|
168
|
+
case 'touch': {
|
|
169
|
+
const existing = runs.get(op.runId);
|
|
170
|
+
if (!existing || existing.updatedAt >= op.at) {
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
runs.set(op.runId, { ...existing, updatedAt: op.at });
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
default: {
|
|
177
|
+
const unexpected = op;
|
|
178
|
+
return unexpected;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
function mergeStart(existing, op) {
|
|
183
|
+
const status = existing && terminal.has(existing.status) ? existing.status : 'running';
|
|
184
|
+
return {
|
|
185
|
+
runId: op.runId,
|
|
186
|
+
status,
|
|
187
|
+
kind: existing?.kind ?? op.kind,
|
|
188
|
+
definitionName: existing?.definitionName ?? op.definitionName,
|
|
189
|
+
definitionVersion: existing?.definitionVersion ?? op.definitionVersion,
|
|
190
|
+
rootThreadId: existing?.rootThreadId ?? op.rootThreadId,
|
|
191
|
+
parentRunId: existing?.parentRunId ?? op.parentRunId,
|
|
192
|
+
launchedByEffectId: existing?.launchedByEffectId ?? op.launchedByEffectId,
|
|
193
|
+
correlationId: existing?.correlationId ?? op.correlationId,
|
|
194
|
+
attributes: existing && Object.keys(existing.attributes).length > 0
|
|
195
|
+
? existing.attributes
|
|
196
|
+
: { ...op.attributes },
|
|
197
|
+
tenant: existing?.tenant ?? op.tenant,
|
|
198
|
+
project: existing?.project ?? op.project,
|
|
199
|
+
org: existing?.org ?? op.org,
|
|
200
|
+
startedAt: existing?.startedAt ?? op.at,
|
|
201
|
+
updatedAt: Math.max(existing?.updatedAt ?? 0, op.at),
|
|
202
|
+
completedAt: existing?.completedAt ?? null,
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
function mergeFinish(existing, op) {
|
|
206
|
+
return {
|
|
207
|
+
runId: op.runId,
|
|
208
|
+
status: op.status,
|
|
209
|
+
kind: existing?.kind ?? null,
|
|
210
|
+
definitionName: existing?.definitionName ?? null,
|
|
211
|
+
definitionVersion: existing?.definitionVersion ?? null,
|
|
212
|
+
rootThreadId: existing?.rootThreadId ?? null,
|
|
213
|
+
parentRunId: existing?.parentRunId ?? null,
|
|
214
|
+
launchedByEffectId: existing?.launchedByEffectId ?? null,
|
|
215
|
+
correlationId: existing?.correlationId ?? null,
|
|
216
|
+
attributes: existing?.attributes ?? {},
|
|
217
|
+
tenant: existing?.tenant ?? null,
|
|
218
|
+
project: existing?.project ?? null,
|
|
219
|
+
org: existing?.org ?? null,
|
|
220
|
+
startedAt: existing?.startedAt ?? null,
|
|
221
|
+
updatedAt: Math.max(existing?.updatedAt ?? 0, op.at),
|
|
222
|
+
completedAt: existing?.completedAt ?? op.at,
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
function utcDate(ms) {
|
|
226
|
+
return DateTime.toDateUtc(DateTime.makeUnsafe(ms));
|
|
227
|
+
}
|
|
228
|
+
function startOp(event, payload) {
|
|
229
|
+
const launchedBy = readRunLaunchSource(payload.launchedBy === undefined ? undefined : payload.launchedBy);
|
|
230
|
+
const attributes = readAttributes(payload.attributes);
|
|
231
|
+
return {
|
|
232
|
+
type: 'start',
|
|
233
|
+
runId: event.runId,
|
|
234
|
+
kind: readString(payload, 'kind'),
|
|
235
|
+
definitionName: readString(payload, 'definitionName'),
|
|
236
|
+
definitionVersion: readString(payload, 'definitionVersion'),
|
|
237
|
+
rootThreadId: readString(payload, 'rootThreadId'),
|
|
238
|
+
parentRunId: launchedBy?.runId ?? null,
|
|
239
|
+
launchedByEffectId: launchedBy?.effectId ?? null,
|
|
240
|
+
correlationId: event.correlationId ?? null,
|
|
241
|
+
attributes,
|
|
242
|
+
tenant: attributes.tenant ?? null,
|
|
243
|
+
project: attributes.project ?? null,
|
|
244
|
+
org: attributes.org ?? null,
|
|
245
|
+
at: event.ts,
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
function readString(payload, key) {
|
|
249
|
+
const value = payload[key];
|
|
250
|
+
return Predicate.isString(value) ? value : null;
|
|
251
|
+
}
|
|
252
|
+
function readAttributes(value) {
|
|
253
|
+
if (value === undefined) {
|
|
254
|
+
return {};
|
|
255
|
+
}
|
|
256
|
+
return Option.getOrElse(decodeAttributes(value), () => ({}));
|
|
257
|
+
}
|
|
258
|
+
function postgresStore(sql, ownsClient) {
|
|
259
|
+
return {
|
|
260
|
+
init: () => runProjectorPromise('postgres-event-log', () => sql.unsafe(POSTGRES_EVENT_LOG_SCHEMA_SQL)).then(() => undefined),
|
|
261
|
+
apply: (events) => {
|
|
262
|
+
const ops = events.flatMap(eventLogOps);
|
|
263
|
+
if (ops.length === 0) {
|
|
264
|
+
return Promise.resolve();
|
|
265
|
+
}
|
|
266
|
+
return runProjectorPromise('postgres-event-log', () => sql.begin((tx) => applySqlOps(tx, ops))).then(() => undefined);
|
|
267
|
+
},
|
|
268
|
+
dispose: () => ownsClient
|
|
269
|
+
? runProjectorPromise('postgres-event-log', () => sql.end({ timeout: 5 })).then(() => undefined)
|
|
270
|
+
: Promise.resolve(),
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
/** Keeps each statement under Postgres's 65,535 bind parameters (13 per event row). */
|
|
274
|
+
const INSERT_CHUNK_ROWS = 1_000;
|
|
275
|
+
function runIndexPatches(ops) {
|
|
276
|
+
const patches = new Map();
|
|
277
|
+
for (const op of ops) {
|
|
278
|
+
if (op.type === 'insert') {
|
|
279
|
+
continue;
|
|
280
|
+
}
|
|
281
|
+
const patch = patches.get(op.runId) ?? {
|
|
282
|
+
runId: op.runId,
|
|
283
|
+
start: null,
|
|
284
|
+
finish: null,
|
|
285
|
+
completedAt: null,
|
|
286
|
+
updatedAt: op.at,
|
|
287
|
+
};
|
|
288
|
+
patch.updatedAt = Math.max(patch.updatedAt, op.at);
|
|
289
|
+
if (op.type === 'start') {
|
|
290
|
+
patch.start ??= op;
|
|
291
|
+
}
|
|
292
|
+
if (op.type === 'finish') {
|
|
293
|
+
patch.finish = op;
|
|
294
|
+
patch.completedAt ??= op.at;
|
|
295
|
+
}
|
|
296
|
+
patches.set(op.runId, patch);
|
|
297
|
+
}
|
|
298
|
+
return [...patches.values()];
|
|
299
|
+
}
|
|
300
|
+
function applySqlOps(sql, ops) {
|
|
301
|
+
const entries = ops.flatMap((op) => (op.type === 'insert' ? [op.entry] : []));
|
|
302
|
+
const patches = runIndexPatches(ops);
|
|
303
|
+
const upserts = patches.filter((patch) => patch.start !== null || patch.finish !== null);
|
|
304
|
+
const touches = patches.filter((patch) => patch.start === null && patch.finish === null);
|
|
305
|
+
const statements = [];
|
|
306
|
+
for (let index = 0; index < entries.length; index += INSERT_CHUNK_ROWS) {
|
|
307
|
+
statements.push(insertEvents(sql, entries.slice(index, index + INSERT_CHUNK_ROWS)));
|
|
308
|
+
}
|
|
309
|
+
if (upserts.length > 0) {
|
|
310
|
+
statements.push(upsertRuns(sql, upserts));
|
|
311
|
+
}
|
|
312
|
+
if (touches.length > 0) {
|
|
313
|
+
statements.push(touchRuns(sql, touches));
|
|
314
|
+
}
|
|
315
|
+
return Promise.all(statements).then(() => undefined);
|
|
316
|
+
}
|
|
317
|
+
function insertEvents(sql, entries) {
|
|
318
|
+
const rows = entries.map((entry) => ({
|
|
319
|
+
run_id: entry.runId,
|
|
320
|
+
seq: entry.seq,
|
|
321
|
+
event_id: entry.eventId,
|
|
322
|
+
ts: utcDate(entry.ts),
|
|
323
|
+
type: entry.type,
|
|
324
|
+
payload: sql.json(entry.payload),
|
|
325
|
+
thread_id: entry.threadId,
|
|
326
|
+
parent_thread_id: entry.parentThreadId,
|
|
327
|
+
causation_id: entry.causationId,
|
|
328
|
+
correlation_id: entry.correlationId,
|
|
329
|
+
effect_id: entry.effectId,
|
|
330
|
+
origin: sql.json(entry.origin),
|
|
331
|
+
idempotency_key: entry.idempotencyKey,
|
|
332
|
+
}));
|
|
333
|
+
return sql `
|
|
334
|
+
INSERT INTO looms_run_events ${sql(rows)}
|
|
335
|
+
ON CONFLICT (run_id, seq) DO NOTHING
|
|
336
|
+
`.then(() => undefined);
|
|
337
|
+
}
|
|
338
|
+
// A finish in the batch sets the status; otherwise a finished run keeps its status and a
|
|
339
|
+
// started run becomes running. Start fields fill only columns that are still empty.
|
|
340
|
+
function upsertRuns(sql, patches) {
|
|
341
|
+
const rows = patches.map(({ runId, start, finish, completedAt, updatedAt }) => ({
|
|
342
|
+
run_id: runId,
|
|
343
|
+
status: finish?.status ?? 'running',
|
|
344
|
+
kind: start?.kind ?? null,
|
|
345
|
+
definition_name: start?.definitionName ?? null,
|
|
346
|
+
definition_version: start?.definitionVersion ?? null,
|
|
347
|
+
root_thread_id: start?.rootThreadId ?? null,
|
|
348
|
+
parent_run_id: start?.parentRunId ?? null,
|
|
349
|
+
launched_by_effect_id: start?.launchedByEffectId ?? null,
|
|
350
|
+
correlation_id: start?.correlationId ?? null,
|
|
351
|
+
attributes: sql.json(start?.attributes ?? {}),
|
|
352
|
+
tenant: start?.tenant ?? null,
|
|
353
|
+
project: start?.project ?? null,
|
|
354
|
+
org: start?.org ?? null,
|
|
355
|
+
started_at: start ? utcDate(start.at) : null,
|
|
356
|
+
updated_at: utcDate(updatedAt),
|
|
357
|
+
completed_at: completedAt === null ? null : utcDate(completedAt),
|
|
358
|
+
}));
|
|
359
|
+
return sql `
|
|
360
|
+
INSERT INTO looms_run_index ${sql(rows)}
|
|
361
|
+
ON CONFLICT (run_id) DO UPDATE SET
|
|
362
|
+
status = CASE
|
|
363
|
+
WHEN EXCLUDED.completed_at IS NOT NULL THEN EXCLUDED.status
|
|
364
|
+
WHEN looms_run_index.status IN ('completed', 'failed', 'cancelled') THEN looms_run_index.status
|
|
365
|
+
ELSE EXCLUDED.status
|
|
366
|
+
END,
|
|
367
|
+
kind = COALESCE(looms_run_index.kind, EXCLUDED.kind),
|
|
368
|
+
definition_name = COALESCE(looms_run_index.definition_name, EXCLUDED.definition_name),
|
|
369
|
+
definition_version = COALESCE(looms_run_index.definition_version, EXCLUDED.definition_version),
|
|
370
|
+
root_thread_id = COALESCE(looms_run_index.root_thread_id, EXCLUDED.root_thread_id),
|
|
371
|
+
parent_run_id = COALESCE(looms_run_index.parent_run_id, EXCLUDED.parent_run_id),
|
|
372
|
+
launched_by_effect_id = COALESCE(looms_run_index.launched_by_effect_id, EXCLUDED.launched_by_effect_id),
|
|
373
|
+
correlation_id = COALESCE(looms_run_index.correlation_id, EXCLUDED.correlation_id),
|
|
374
|
+
attributes = CASE
|
|
375
|
+
WHEN looms_run_index.attributes = '{}'::jsonb THEN EXCLUDED.attributes
|
|
376
|
+
ELSE looms_run_index.attributes
|
|
377
|
+
END,
|
|
378
|
+
tenant = COALESCE(looms_run_index.tenant, EXCLUDED.tenant),
|
|
379
|
+
project = COALESCE(looms_run_index.project, EXCLUDED.project),
|
|
380
|
+
org = COALESCE(looms_run_index.org, EXCLUDED.org),
|
|
381
|
+
started_at = COALESCE(looms_run_index.started_at, EXCLUDED.started_at),
|
|
382
|
+
updated_at = GREATEST(looms_run_index.updated_at, EXCLUDED.updated_at),
|
|
383
|
+
completed_at = COALESCE(looms_run_index.completed_at, EXCLUDED.completed_at)
|
|
384
|
+
`.then(() => undefined);
|
|
385
|
+
}
|
|
386
|
+
function touchRuns(sql, patches) {
|
|
387
|
+
const runIds = patches.map((patch) => patch.runId);
|
|
388
|
+
const touchedAt = patches.map((patch) => DateTime.formatIso(DateTime.makeUnsafe(patch.updatedAt)));
|
|
389
|
+
return sql `
|
|
390
|
+
UPDATE looms_run_index AS run
|
|
391
|
+
SET updated_at = GREATEST(run.updated_at, touched.at)
|
|
392
|
+
FROM unnest(${sql.array(runIds)}::text[], ${sql.array(touchedAt)}::timestamptz[])
|
|
393
|
+
AS touched (run_id, at)
|
|
394
|
+
WHERE run.run_id = touched.run_id
|
|
395
|
+
`.then(() => undefined);
|
|
396
|
+
}
|
|
@@ -8,6 +8,12 @@ export interface ActorIndexRow {
|
|
|
8
8
|
status: string | null;
|
|
9
9
|
definitionName: string | null;
|
|
10
10
|
parentActorId: string | null;
|
|
11
|
+
/** On a run row: the run whose effect launched this one. */
|
|
12
|
+
launchedByRunId: string | null;
|
|
13
|
+
/** On a run row: the effect, in `launchedByRunId`, that launched this one. */
|
|
14
|
+
launchedByEffectId: string | null;
|
|
15
|
+
/** On a run row: the root run of its launch chain. */
|
|
16
|
+
correlationId: string | null;
|
|
11
17
|
updatedAt: number;
|
|
12
18
|
}
|
|
13
19
|
export interface ReviewIndexRow {
|
|
@@ -25,6 +31,9 @@ export type IndexOp = {
|
|
|
25
31
|
status: string | null;
|
|
26
32
|
definitionName: string | null;
|
|
27
33
|
parentActorId: string | null;
|
|
34
|
+
launchedByRunId: string | null;
|
|
35
|
+
launchedByEffectId: string | null;
|
|
36
|
+
correlationId: string | null;
|
|
28
37
|
updatedAt: number;
|
|
29
38
|
} | {
|
|
30
39
|
type: 'setActorStatus';
|
|
@@ -49,6 +58,7 @@ export type IndexOp = {
|
|
|
49
58
|
export interface IndexBackend {
|
|
50
59
|
applyOps: (ops: ReadonlyArray<IndexOp>) => Promise<void>;
|
|
51
60
|
getActor: (runId: string, actorId: string) => Promise<ActorIndexRow | null>;
|
|
61
|
+
listLaunched: (runId: string) => Promise<ActorIndexRow[]>;
|
|
52
62
|
listReviews: (runId?: string) => Promise<ReviewIndexRow[]>;
|
|
53
63
|
init?: () => Promise<void>;
|
|
54
64
|
dispose?: () => Promise<void>;
|
|
@@ -56,6 +66,8 @@ export interface IndexBackend {
|
|
|
56
66
|
export interface IndexProjector extends Projector {
|
|
57
67
|
/** The run's row, or one of its threads when `actorId` is given. */
|
|
58
68
|
getActor: (runId: string, actorId?: string) => Promise<ActorIndexRow | null>;
|
|
69
|
+
/** Rows of the runs that effects in `runId` launched. */
|
|
70
|
+
listLaunched: (runId: string) => Promise<ActorIndexRow[]>;
|
|
59
71
|
listReviews: (runId?: string) => Promise<ReviewIndexRow[]>;
|
|
60
72
|
}
|
|
61
73
|
export declare function indexOpsFor(event: EventEnvelope): IndexOp[];
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Predicate } from 'effect';
|
|
2
|
-
import { completedRunStatus } from '@swirls/looms/core';
|
|
2
|
+
import { completedRunStatus, readRunLaunchSource, } from '@swirls/looms/core';
|
|
3
3
|
function payloadObject(event) {
|
|
4
4
|
if (!Predicate.isObject(event.payload)) {
|
|
5
5
|
return {};
|
|
@@ -15,7 +15,8 @@ export function indexOpsFor(event) {
|
|
|
15
15
|
const runId = event.runId;
|
|
16
16
|
const actorId = readString(payload, 'threadId') ?? event.threadId ?? runId;
|
|
17
17
|
switch (event.type) {
|
|
18
|
-
case 'runtime.run.started':
|
|
18
|
+
case 'runtime.run.started': {
|
|
19
|
+
const launchedBy = readRunLaunchSource(payload.launchedBy);
|
|
19
20
|
return [
|
|
20
21
|
{
|
|
21
22
|
type: 'upsertActor',
|
|
@@ -25,9 +26,13 @@ export function indexOpsFor(event) {
|
|
|
25
26
|
status: 'running',
|
|
26
27
|
definitionName: readString(payload, 'definitionName') ?? null,
|
|
27
28
|
parentActorId: null,
|
|
29
|
+
launchedByRunId: launchedBy?.runId ?? null,
|
|
30
|
+
launchedByEffectId: launchedBy?.effectId ?? null,
|
|
31
|
+
correlationId: event.correlationId ?? null,
|
|
28
32
|
updatedAt: event.ts,
|
|
29
33
|
},
|
|
30
34
|
];
|
|
35
|
+
}
|
|
31
36
|
case 'runtime.thread.started':
|
|
32
37
|
return [
|
|
33
38
|
{
|
|
@@ -38,6 +43,9 @@ export function indexOpsFor(event) {
|
|
|
38
43
|
status: 'running',
|
|
39
44
|
definitionName: readString(payload, 'definitionName') ?? null,
|
|
40
45
|
parentActorId: readString(payload, 'parentThreadId') ?? null,
|
|
46
|
+
launchedByRunId: null,
|
|
47
|
+
launchedByEffectId: null,
|
|
48
|
+
correlationId: null,
|
|
41
49
|
updatedAt: event.ts,
|
|
42
50
|
},
|
|
43
51
|
];
|
|
@@ -94,7 +102,7 @@ export function indexOpsFor(event) {
|
|
|
94
102
|
return [];
|
|
95
103
|
}
|
|
96
104
|
}
|
|
97
|
-
export function createIndexProjector(name, backend, version = '
|
|
105
|
+
export function createIndexProjector(name, backend, version = '3') {
|
|
98
106
|
return {
|
|
99
107
|
name,
|
|
100
108
|
version,
|
|
@@ -105,6 +113,7 @@ export function createIndexProjector(name, backend, version = '2') {
|
|
|
105
113
|
return ops.length === 0 ? Promise.resolve() : backend.applyOps(ops);
|
|
106
114
|
},
|
|
107
115
|
getActor: (runId, actorId) => backend.getActor(runId, actorId ?? runId),
|
|
116
|
+
listLaunched: (runId) => backend.listLaunched(runId),
|
|
108
117
|
listReviews: (runId) => backend.listReviews(runId),
|
|
109
118
|
};
|
|
110
119
|
}
|
|
@@ -13,6 +13,9 @@ function applyOp(actors, reviews, op) {
|
|
|
13
13
|
status: op.status,
|
|
14
14
|
definitionName: op.definitionName,
|
|
15
15
|
parentActorId: op.parentActorId,
|
|
16
|
+
launchedByRunId: op.launchedByRunId,
|
|
17
|
+
launchedByEffectId: op.launchedByEffectId,
|
|
18
|
+
correlationId: op.correlationId,
|
|
16
19
|
updatedAt: op.updatedAt,
|
|
17
20
|
});
|
|
18
21
|
return;
|
|
@@ -26,6 +29,9 @@ function applyOp(actors, reviews, op) {
|
|
|
26
29
|
status: op.status,
|
|
27
30
|
definitionName: existing?.definitionName ?? null,
|
|
28
31
|
parentActorId: existing?.parentActorId ?? null,
|
|
32
|
+
launchedByRunId: existing?.launchedByRunId ?? null,
|
|
33
|
+
launchedByEffectId: existing?.launchedByEffectId ?? null,
|
|
34
|
+
correlationId: existing?.correlationId ?? null,
|
|
29
35
|
updatedAt: op.updatedAt,
|
|
30
36
|
});
|
|
31
37
|
return;
|
|
@@ -66,6 +72,7 @@ export function memory() {
|
|
|
66
72
|
}
|
|
67
73
|
}),
|
|
68
74
|
getActor: (runId, actorId) => runProjectorSync('memory', () => actors.get(rowKey(runId, actorId)) ?? null),
|
|
75
|
+
listLaunched: (runId) => runProjectorSync('memory', () => [...actors.values()].filter((row) => row.launchedByRunId === runId)),
|
|
69
76
|
listReviews: (runId) => runProjectorSync('memory', () => {
|
|
70
77
|
const rows = [...reviews.values()];
|
|
71
78
|
return runId ? rows.filter((row) => row.runId === runId) : rows;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { type Sql } from 'postgres';
|
|
2
2
|
import { type IndexProjector } from './index-model';
|
|
3
3
|
export { POSTGRES_SCHEMA_SQL } from './schema';
|
|
4
|
+
export { memoryEventLog, postgresEventLog, POSTGRES_EVENT_LOG_SCHEMA_SQL, type EventLogEntry, type EventLogProjector, type MemoryEventLog, type RunLogRow, } from './event-log';
|
|
4
5
|
export type PostgresProjectorOptions = {
|
|
5
6
|
url: string;
|
|
6
7
|
} | {
|
|
@@ -4,21 +4,45 @@ import { createIndexProjector, } from './index-model.js';
|
|
|
4
4
|
import { runProjectorPromise } from './projector.js';
|
|
5
5
|
import { POSTGRES_SCHEMA_SQL } from './schema.js';
|
|
6
6
|
export { POSTGRES_SCHEMA_SQL } from './schema.js';
|
|
7
|
+
export { memoryEventLog, postgresEventLog, POSTGRES_EVENT_LOG_SCHEMA_SQL, } from './event-log.js';
|
|
7
8
|
function ownsClient(options) {
|
|
8
9
|
return 'url' in options;
|
|
9
10
|
}
|
|
11
|
+
function actorFromRow(row) {
|
|
12
|
+
return {
|
|
13
|
+
runId: row.run_id,
|
|
14
|
+
actorId: row.actor_id,
|
|
15
|
+
kind: row.kind,
|
|
16
|
+
status: row.status,
|
|
17
|
+
definitionName: row.definition_name,
|
|
18
|
+
parentActorId: row.parent_actor_id,
|
|
19
|
+
launchedByRunId: row.launched_by_run_id,
|
|
20
|
+
launchedByEffectId: row.launched_by_effect_id,
|
|
21
|
+
correlationId: row.correlation_id,
|
|
22
|
+
updatedAt: row.updated_at.getTime(),
|
|
23
|
+
};
|
|
24
|
+
}
|
|
10
25
|
function applyOp(sql, op) {
|
|
11
26
|
const date = DateTime.toDateUtc(DateTime.makeUnsafe(op.updatedAt));
|
|
12
27
|
switch (op.type) {
|
|
13
28
|
case 'upsertActor':
|
|
14
29
|
return sql `
|
|
15
|
-
INSERT INTO looms_run_actors (
|
|
16
|
-
|
|
30
|
+
INSERT INTO looms_run_actors (
|
|
31
|
+
run_id, actor_id, kind, status, definition_name, parent_actor_id,
|
|
32
|
+
launched_by_run_id, launched_by_effect_id, correlation_id, updated_at
|
|
33
|
+
)
|
|
34
|
+
VALUES (
|
|
35
|
+
${op.runId}, ${op.actorId}, ${op.kind}, ${op.status}, ${op.definitionName}, ${op.parentActorId},
|
|
36
|
+
${op.launchedByRunId}, ${op.launchedByEffectId}, ${op.correlationId}, ${date}
|
|
37
|
+
)
|
|
17
38
|
ON CONFLICT (run_id, actor_id) DO UPDATE SET
|
|
18
39
|
kind = EXCLUDED.kind,
|
|
19
40
|
status = EXCLUDED.status,
|
|
20
41
|
definition_name = EXCLUDED.definition_name,
|
|
21
42
|
parent_actor_id = EXCLUDED.parent_actor_id,
|
|
43
|
+
launched_by_run_id = EXCLUDED.launched_by_run_id,
|
|
44
|
+
launched_by_effect_id = EXCLUDED.launched_by_effect_id,
|
|
45
|
+
correlation_id = EXCLUDED.correlation_id,
|
|
22
46
|
updated_at = EXCLUDED.updated_at
|
|
23
47
|
`;
|
|
24
48
|
case 'setActorStatus':
|
|
@@ -57,22 +81,15 @@ export function postgres(options) {
|
|
|
57
81
|
discard: true,
|
|
58
82
|
})))).then(() => undefined),
|
|
59
83
|
getActor: (runId, actorId) => runProjectorPromise('postgres', () => sql `
|
|
60
|
-
SELECT run_id
|
|
61
|
-
FROM looms_run_actors WHERE run_id = ${runId} AND actor_id = ${actorId}
|
|
84
|
+
SELECT * FROM looms_run_actors WHERE run_id = ${runId} AND actor_id = ${actorId}
|
|
62
85
|
`).then((rows) => {
|
|
63
86
|
const row = rows[0];
|
|
64
|
-
return row
|
|
65
|
-
? {
|
|
66
|
-
runId: row.run_id,
|
|
67
|
-
actorId: row.actor_id,
|
|
68
|
-
kind: row.kind,
|
|
69
|
-
status: row.status,
|
|
70
|
-
definitionName: row.definition_name,
|
|
71
|
-
parentActorId: row.parent_actor_id,
|
|
72
|
-
updatedAt: row.updated_at.getTime(),
|
|
73
|
-
}
|
|
74
|
-
: null;
|
|
87
|
+
return row ? actorFromRow(row) : null;
|
|
75
88
|
}),
|
|
89
|
+
listLaunched: (runId) => runProjectorPromise('postgres', () => sql `
|
|
90
|
+
SELECT * FROM looms_run_actors
|
|
91
|
+
WHERE launched_by_run_id = ${runId} AND actor_id = run_id ORDER BY updated_at
|
|
92
|
+
`).then((rows) => rows.map(actorFromRow)),
|
|
76
93
|
listReviews: (runId) => runProjectorPromise('postgres', () => runId
|
|
77
94
|
? sql `
|
|
78
95
|
SELECT run_id, review_id, status, title, updated_at
|
|
@@ -2,4 +2,9 @@
|
|
|
2
2
|
* SQL schema for the optional Looms secondary index.
|
|
3
3
|
*/
|
|
4
4
|
export declare const POSTGRES_SCHEMA_SQL: string;
|
|
5
|
+
export declare const SQLITE_TABLES_SQL: string;
|
|
6
|
+
/** Columns added after the first release. SQLite cannot add a column only if it is missing. */
|
|
7
|
+
export declare const SQLITE_ADDED_ACTOR_COLUMNS: readonly ['launched_by_run_id', 'launched_by_effect_id', 'correlation_id'];
|
|
8
|
+
export declare const SQLITE_INDEXES_SQL: string;
|
|
9
|
+
/** The full schema for a new database. An existing one needs the added columns first. */
|
|
5
10
|
export declare const SQLITE_SCHEMA_SQL: string;
|
|
@@ -9,10 +9,20 @@ CREATE TABLE IF NOT EXISTS looms_run_actors (
|
|
|
9
9
|
status TEXT,
|
|
10
10
|
definition_name TEXT,
|
|
11
11
|
parent_actor_id TEXT,
|
|
12
|
+
launched_by_run_id TEXT,
|
|
13
|
+
launched_by_effect_id TEXT,
|
|
14
|
+
correlation_id TEXT,
|
|
12
15
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
13
16
|
PRIMARY KEY (run_id, actor_id)
|
|
14
17
|
);
|
|
15
18
|
|
|
19
|
+
ALTER TABLE looms_run_actors ADD COLUMN IF NOT EXISTS launched_by_run_id TEXT;
|
|
20
|
+
ALTER TABLE looms_run_actors ADD COLUMN IF NOT EXISTS launched_by_effect_id TEXT;
|
|
21
|
+
ALTER TABLE looms_run_actors ADD COLUMN IF NOT EXISTS correlation_id TEXT;
|
|
22
|
+
|
|
23
|
+
CREATE INDEX IF NOT EXISTS looms_run_actors_launched_by ON looms_run_actors (launched_by_run_id);
|
|
24
|
+
CREATE INDEX IF NOT EXISTS looms_run_actors_correlation ON looms_run_actors (correlation_id);
|
|
25
|
+
|
|
16
26
|
CREATE TABLE IF NOT EXISTS looms_run_reviews (
|
|
17
27
|
run_id TEXT NOT NULL,
|
|
18
28
|
review_id TEXT NOT NULL,
|
|
@@ -22,7 +32,7 @@ CREATE TABLE IF NOT EXISTS looms_run_reviews (
|
|
|
22
32
|
PRIMARY KEY (run_id, review_id)
|
|
23
33
|
);
|
|
24
34
|
`.trim();
|
|
25
|
-
export const
|
|
35
|
+
export const SQLITE_TABLES_SQL = `
|
|
26
36
|
CREATE TABLE IF NOT EXISTS looms_run_actors (
|
|
27
37
|
run_id TEXT NOT NULL,
|
|
28
38
|
actor_id TEXT NOT NULL,
|
|
@@ -30,6 +40,9 @@ CREATE TABLE IF NOT EXISTS looms_run_actors (
|
|
|
30
40
|
status TEXT,
|
|
31
41
|
definition_name TEXT,
|
|
32
42
|
parent_actor_id TEXT,
|
|
43
|
+
launched_by_run_id TEXT,
|
|
44
|
+
launched_by_effect_id TEXT,
|
|
45
|
+
correlation_id TEXT,
|
|
33
46
|
updated_at INTEGER NOT NULL,
|
|
34
47
|
PRIMARY KEY (run_id, actor_id)
|
|
35
48
|
);
|
|
@@ -43,3 +56,15 @@ CREATE TABLE IF NOT EXISTS looms_run_reviews (
|
|
|
43
56
|
PRIMARY KEY (run_id, review_id)
|
|
44
57
|
);
|
|
45
58
|
`.trim();
|
|
59
|
+
/** Columns added after the first release. SQLite cannot add a column only if it is missing. */
|
|
60
|
+
export const SQLITE_ADDED_ACTOR_COLUMNS = [
|
|
61
|
+
'launched_by_run_id',
|
|
62
|
+
'launched_by_effect_id',
|
|
63
|
+
'correlation_id',
|
|
64
|
+
];
|
|
65
|
+
export const SQLITE_INDEXES_SQL = `
|
|
66
|
+
CREATE INDEX IF NOT EXISTS looms_run_actors_launched_by ON looms_run_actors (launched_by_run_id);
|
|
67
|
+
CREATE INDEX IF NOT EXISTS looms_run_actors_correlation ON looms_run_actors (correlation_id);
|
|
68
|
+
`.trim();
|
|
69
|
+
/** The full schema for a new database. An existing one needs the added columns first. */
|
|
70
|
+
export const SQLITE_SCHEMA_SQL = `${SQLITE_TABLES_SQL}\n\n${SQLITE_INDEXES_SQL}`;
|