videobook-engine 2.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/README.md +214 -0
- package/dist/artifacts.d.ts +20 -0
- package/dist/artifacts.d.ts.map +1 -0
- package/dist/artifacts.js +312 -0
- package/dist/artifacts.js.map +1 -0
- package/dist/books.d.ts +7 -0
- package/dist/books.d.ts.map +1 -0
- package/dist/books.js +27 -0
- package/dist/books.js.map +1 -0
- package/dist/cas.d.ts +26 -0
- package/dist/cas.d.ts.map +1 -0
- package/dist/cas.js +139 -0
- package/dist/cas.js.map +1 -0
- package/dist/communications.d.ts +15 -0
- package/dist/communications.d.ts.map +1 -0
- package/dist/communications.js +139 -0
- package/dist/communications.js.map +1 -0
- package/dist/context.d.ts +42 -0
- package/dist/context.d.ts.map +1 -0
- package/dist/context.js +197 -0
- package/dist/context.js.map +1 -0
- package/dist/domain.d.ts +32 -0
- package/dist/domain.d.ts.map +1 -0
- package/dist/domain.js +399 -0
- package/dist/domain.js.map +1 -0
- package/dist/engine-types.d.ts +548 -0
- package/dist/engine-types.d.ts.map +1 -0
- package/dist/engine-types.js +10 -0
- package/dist/engine-types.js.map +1 -0
- package/dist/engine.d.ts +213 -0
- package/dist/engine.d.ts.map +1 -0
- package/dist/engine.js +130 -0
- package/dist/engine.js.map +1 -0
- package/dist/files.d.ts +28 -0
- package/dist/files.d.ts.map +1 -0
- package/dist/files.js +476 -0
- package/dist/files.js.map +1 -0
- package/dist/history-types.d.ts +74 -0
- package/dist/history-types.d.ts.map +1 -0
- package/dist/history-types.js +2 -0
- package/dist/history-types.js.map +1 -0
- package/dist/history.d.ts +20 -0
- package/dist/history.d.ts.map +1 -0
- package/dist/history.js +921 -0
- package/dist/history.js.map +1 -0
- package/dist/ids.d.ts +4 -0
- package/dist/ids.d.ts.map +1 -0
- package/dist/ids.js +14 -0
- package/dist/ids.js.map +1 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +12 -0
- package/dist/index.js.map +1 -0
- package/dist/job-queue.d.ts +51 -0
- package/dist/job-queue.d.ts.map +1 -0
- package/dist/job-queue.js +443 -0
- package/dist/job-queue.js.map +1 -0
- package/dist/media.d.ts +7 -0
- package/dist/media.d.ts.map +1 -0
- package/dist/media.js +60 -0
- package/dist/media.js.map +1 -0
- package/dist/metadata.d.ts +21 -0
- package/dist/metadata.d.ts.map +1 -0
- package/dist/metadata.js +222 -0
- package/dist/metadata.js.map +1 -0
- package/dist/notebook/types.d.ts +51 -0
- package/dist/notebook/types.d.ts.map +1 -0
- package/dist/notebook/types.js +2 -0
- package/dist/notebook/types.js.map +1 -0
- package/dist/resolver.d.ts +15 -0
- package/dist/resolver.d.ts.map +1 -0
- package/dist/resolver.js +81 -0
- package/dist/resolver.js.map +1 -0
- package/dist/runtime-services.d.ts +65 -0
- package/dist/runtime-services.d.ts.map +1 -0
- package/dist/runtime-services.js +584 -0
- package/dist/runtime-services.js.map +1 -0
- package/dist/schema.d.ts +7 -0
- package/dist/schema.d.ts.map +1 -0
- package/dist/schema.js +509 -0
- package/dist/schema.js.map +1 -0
- package/dist/similarity.d.ts +4 -0
- package/dist/similarity.d.ts.map +1 -0
- package/dist/similarity.js +1551 -0
- package/dist/similarity.js.map +1 -0
- package/dist/status.d.ts +9 -0
- package/dist/status.d.ts.map +1 -0
- package/dist/status.js +231 -0
- package/dist/status.js.map +1 -0
- package/dist/storage.d.ts +7 -0
- package/dist/storage.d.ts.map +1 -0
- package/dist/storage.js +129 -0
- package/dist/storage.js.map +1 -0
- package/dist/store.d.ts +55 -0
- package/dist/store.d.ts.map +1 -0
- package/dist/store.js +378 -0
- package/dist/store.js.map +1 -0
- package/dist/timeline.d.ts +9 -0
- package/dist/timeline.d.ts.map +1 -0
- package/dist/timeline.js +236 -0
- package/dist/timeline.js.map +1 -0
- package/docs/engine-layout.md +489 -0
- package/package.json +55 -0
package/dist/history.js
ADDED
|
@@ -0,0 +1,921 @@
|
|
|
1
|
+
import { rm } from "node:fs/promises";
|
|
2
|
+
import { ok } from "./engine-types.js";
|
|
3
|
+
import { resultOf, syncResultOf, } from "./context.js";
|
|
4
|
+
import { artifactSlug } from "./artifacts.js";
|
|
5
|
+
import { materializeArtifact } from "./files.js";
|
|
6
|
+
import { assertUuidV7, isUuidV7, newUuidV7 } from "./ids.js";
|
|
7
|
+
import { canonicalJson, EngineFault, parseJson, } from "./store.js";
|
|
8
|
+
export function createHistoryApi(context) {
|
|
9
|
+
return {
|
|
10
|
+
revisions: (limit = 20) => revisionHistory(context, limit),
|
|
11
|
+
artifact: (artifact, limit = 20) => artifactHistory(context, artifact, limit),
|
|
12
|
+
resolveRevision: (revision) => resolveRevision(context, revision),
|
|
13
|
+
recordOperation: (operation, artifact, details) => recordOperation(context, operation, artifact, details),
|
|
14
|
+
restoreArtifact: (artifactId, revision, slug) => restoreArtifact(context, artifactId, revision, slug),
|
|
15
|
+
restore: (revision) => restoreBook(context, revision),
|
|
16
|
+
logAction: (action, payload) => logAction(context, action, payload),
|
|
17
|
+
actionLog: (options) => actionLog(context, options),
|
|
18
|
+
actions: (options) => actions(context, options),
|
|
19
|
+
action: (actionId) => action(context, actionId),
|
|
20
|
+
recordAction: (input) => recordAction(context, input),
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
function revisionForHash(context, hash) {
|
|
24
|
+
const commit = context.store.db
|
|
25
|
+
.doltLog()
|
|
26
|
+
.find((item) => item.commit_hash === hash);
|
|
27
|
+
if (!commit) {
|
|
28
|
+
throw new EngineFault({
|
|
29
|
+
code: "NOT_FOUND",
|
|
30
|
+
message: `Revision not found: ${hash}`,
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
return {
|
|
34
|
+
hash,
|
|
35
|
+
message: commit.message,
|
|
36
|
+
date: commit.date,
|
|
37
|
+
author: commit.committer,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
function revisionHistory(context, limit) {
|
|
41
|
+
return allRevisions(context).slice(0, Math.max(0, limit));
|
|
42
|
+
}
|
|
43
|
+
function artifactHistory(context, artifactReference, limit) {
|
|
44
|
+
const revisions = allRevisions(context);
|
|
45
|
+
let artifactId;
|
|
46
|
+
if (isUuidV7(artifactReference)) {
|
|
47
|
+
artifactId = artifactReference;
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
try {
|
|
51
|
+
artifactId = context.artifactRow(artifactReference).artifact_id;
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
const historical = revisions.find((revision) => revision.artifactSlug === artifactReference ||
|
|
55
|
+
revision.details?.slug === artifactReference ||
|
|
56
|
+
revision.details?.oldSlug === artifactReference);
|
|
57
|
+
if (!historical?.artifactId)
|
|
58
|
+
return [];
|
|
59
|
+
artifactId = historical.artifactId;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return revisions
|
|
63
|
+
.filter((revision) => revision.artifactId === artifactId)
|
|
64
|
+
.slice(0, Math.max(0, limit));
|
|
65
|
+
}
|
|
66
|
+
function allRevisions(context) {
|
|
67
|
+
const commits = context.store.db.doltLog();
|
|
68
|
+
const revisions = [];
|
|
69
|
+
for (let index = 0; index < commits.length - 1; index += 1) {
|
|
70
|
+
const commit = commits[index];
|
|
71
|
+
const parent = commits[index + 1];
|
|
72
|
+
const operations = context.store
|
|
73
|
+
.diff(parent.commit_hash, commit.commit_hash, "operations")
|
|
74
|
+
.filter((row) => row.diff_type === "added")
|
|
75
|
+
.map(operationFromDiff)
|
|
76
|
+
.filter((operation) => operation !== null);
|
|
77
|
+
for (const operation of operations) {
|
|
78
|
+
const details = parseJson(operation.details_json, {});
|
|
79
|
+
const writeSet = parseJson(operation.write_set_json, []);
|
|
80
|
+
const artifactSlugAtRevision = operation.artifact_id
|
|
81
|
+
? artifactSlugAt(context, operation.artifact_id, commit.commit_hash)
|
|
82
|
+
: undefined;
|
|
83
|
+
const fileChanges = revisionFileChanges(context.store.diff(parent.commit_hash, commit.commit_hash, "artifact_files"), operation.artifact_id);
|
|
84
|
+
revisions.push({
|
|
85
|
+
hash: commit.commit_hash,
|
|
86
|
+
message: commit.message,
|
|
87
|
+
date: new Date(operation.created_at).toISOString(),
|
|
88
|
+
author: operation.author,
|
|
89
|
+
operationId: operation.operation_id,
|
|
90
|
+
operation: operation.operation,
|
|
91
|
+
...(operation.artifact_id
|
|
92
|
+
? { artifactId: operation.artifact_id }
|
|
93
|
+
: {}),
|
|
94
|
+
...(artifactSlugAtRevision
|
|
95
|
+
? { artifactSlug: artifactSlugAtRevision }
|
|
96
|
+
: {}),
|
|
97
|
+
details: {
|
|
98
|
+
...details,
|
|
99
|
+
...(writeSet.length > 0 ? { writeSet } : {}),
|
|
100
|
+
...(operation.base_revision
|
|
101
|
+
? { baseRevision: operation.base_revision }
|
|
102
|
+
: {}),
|
|
103
|
+
},
|
|
104
|
+
files: fileChanges.map((change) => change.file),
|
|
105
|
+
fileChanges,
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return revisions;
|
|
110
|
+
}
|
|
111
|
+
function resolveRevision(context, reference) {
|
|
112
|
+
const historical = allRevisions(context).find((revision) => revision.hash === reference || revision.hash.startsWith(reference));
|
|
113
|
+
if (historical)
|
|
114
|
+
return historical;
|
|
115
|
+
const commit = context.store.db.doltLog().find((item) => item.commit_hash === reference || item.commit_hash.startsWith(reference));
|
|
116
|
+
return commit
|
|
117
|
+
? {
|
|
118
|
+
hash: commit.commit_hash,
|
|
119
|
+
message: commit.message,
|
|
120
|
+
date: commit.date,
|
|
121
|
+
author: commit.committer,
|
|
122
|
+
}
|
|
123
|
+
: null;
|
|
124
|
+
}
|
|
125
|
+
function requiredRevision(context, reference) {
|
|
126
|
+
const revision = resolveRevision(context, reference);
|
|
127
|
+
if (!revision) {
|
|
128
|
+
throw new EngineFault({
|
|
129
|
+
code: "NOT_FOUND",
|
|
130
|
+
message: `Revision not found: ${reference}`,
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
return revision;
|
|
134
|
+
}
|
|
135
|
+
async function recordOperation(context, operation, artifactReference, details) {
|
|
136
|
+
return resultOf(async () => {
|
|
137
|
+
const artifact = artifactReference
|
|
138
|
+
? context.artifactRow(artifactReference)
|
|
139
|
+
: null;
|
|
140
|
+
const mutation = await context.store.semantic({
|
|
141
|
+
operation,
|
|
142
|
+
...(artifact ? { artifactId: artifact.artifact_id } : {}),
|
|
143
|
+
details: {
|
|
144
|
+
...(details ?? {}),
|
|
145
|
+
...(artifact ? { artifactSlug: artifact.slug } : {}),
|
|
146
|
+
},
|
|
147
|
+
writeSet: artifact
|
|
148
|
+
? [`artifact:${artifact.artifact_id}`]
|
|
149
|
+
: ["book"],
|
|
150
|
+
}, [], () => undefined);
|
|
151
|
+
return ok(revisionForHash(context, mutation.revision), mutation.revision);
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
async function restoreArtifact(context, artifactId, revisionReference, replacementSlug) {
|
|
155
|
+
return resultOf(async () => {
|
|
156
|
+
assertUuidV7(artifactId, "Artifact ID");
|
|
157
|
+
const revision = requiredRevision(context, revisionReference);
|
|
158
|
+
const target = context.store.db
|
|
159
|
+
.prepare(`SELECT artifact_id, slug, kind, created_at
|
|
160
|
+
FROM dolt_at_artifacts(?)
|
|
161
|
+
WHERE artifact_id=?`)
|
|
162
|
+
.get(revision.hash, artifactId);
|
|
163
|
+
if (!target) {
|
|
164
|
+
throw new EngineFault({
|
|
165
|
+
code: "NOT_FOUND",
|
|
166
|
+
message: `Artifact ${artifactId} did not exist at ${revision.hash}`,
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
const desiredSlug = replacementSlug
|
|
170
|
+
? artifactSlug(target.kind, replacementSlug)
|
|
171
|
+
: target.slug;
|
|
172
|
+
const owner = context.store.db
|
|
173
|
+
.prepare(`SELECT artifact_id FROM artifacts
|
|
174
|
+
WHERE slug=? AND artifact_id<>?`)
|
|
175
|
+
.get(desiredSlug, artifactId);
|
|
176
|
+
if (owner) {
|
|
177
|
+
throw new EngineFault({
|
|
178
|
+
code: "SLUG_CONFLICT",
|
|
179
|
+
message: `Slug ${desiredSlug} is owned by active artifact ${owner.artifact_id}`,
|
|
180
|
+
ownerId: owner.artifact_id,
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
const files = filesAt(context, revision.hash, artifactId);
|
|
184
|
+
const metadata = context.store.db
|
|
185
|
+
.prepare(`SELECT key, value_json
|
|
186
|
+
FROM dolt_at_artifact_metadata(?) WHERE artifact_id=?`)
|
|
187
|
+
.all(revision.hash, artifactId);
|
|
188
|
+
const waveform = context.store.db
|
|
189
|
+
.prepare(`SELECT peaks_json
|
|
190
|
+
FROM dolt_at_audio_waveforms(?) WHERE artifact_id=?`)
|
|
191
|
+
.get(revision.hash, artifactId);
|
|
192
|
+
const mutation = await context.store.semantic({
|
|
193
|
+
operation: "restore_artifact",
|
|
194
|
+
artifactId,
|
|
195
|
+
details: { fromRevision: revision.hash, slug: desiredSlug },
|
|
196
|
+
writeSet: [
|
|
197
|
+
`artifact:${artifactId}`,
|
|
198
|
+
`artifact-slug:${desiredSlug}`,
|
|
199
|
+
],
|
|
200
|
+
}, [
|
|
201
|
+
"artifacts",
|
|
202
|
+
"artifact_files",
|
|
203
|
+
"artifact_metadata",
|
|
204
|
+
"audio_waveforms",
|
|
205
|
+
], (_operationId, now) => {
|
|
206
|
+
context.store.db
|
|
207
|
+
.prepare(`INSERT INTO artifacts(
|
|
208
|
+
artifact_id, slug, kind, created_at
|
|
209
|
+
) VALUES (?, ?, ?, ?)
|
|
210
|
+
ON CONFLICT(artifact_id) DO UPDATE SET
|
|
211
|
+
slug=excluded.slug,
|
|
212
|
+
kind=excluded.kind`)
|
|
213
|
+
.run(target.artifact_id, desiredSlug, target.kind, target.created_at);
|
|
214
|
+
context.store.db
|
|
215
|
+
.prepare("DELETE FROM artifact_files WHERE artifact_id=?")
|
|
216
|
+
.run(artifactId);
|
|
217
|
+
context.store.db
|
|
218
|
+
.prepare("DELETE FROM artifact_metadata WHERE artifact_id=?")
|
|
219
|
+
.run(artifactId);
|
|
220
|
+
context.store.db
|
|
221
|
+
.prepare("DELETE FROM audio_waveforms WHERE artifact_id=?")
|
|
222
|
+
.run(artifactId);
|
|
223
|
+
insertFiles(context, files);
|
|
224
|
+
const insertMetadata = context.store.db.prepare(`INSERT INTO artifact_metadata(
|
|
225
|
+
artifact_id, key, value_json
|
|
226
|
+
) VALUES (?, ?, ?)`);
|
|
227
|
+
for (const row of metadata) {
|
|
228
|
+
insertMetadata.run(artifactId, row.key, row.value_json);
|
|
229
|
+
}
|
|
230
|
+
if (waveform) {
|
|
231
|
+
context.store.db
|
|
232
|
+
.prepare(`INSERT INTO audio_waveforms(artifact_id, peaks_json)
|
|
233
|
+
VALUES (?, ?)`)
|
|
234
|
+
.run(artifactId, waveform.peaks_json);
|
|
235
|
+
}
|
|
236
|
+
resetArtifactRuntime(context, artifactId, now);
|
|
237
|
+
});
|
|
238
|
+
await rm(context.artifactPath(artifactId), { recursive: true, force: true });
|
|
239
|
+
await materializeArtifact(context, artifactId);
|
|
240
|
+
return ok(revisionForHash(context, mutation.revision), mutation.revision);
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
async function restoreBook(context, revisionReference) {
|
|
244
|
+
return resultOf(async () => {
|
|
245
|
+
const revision = requiredRevision(context, revisionReference);
|
|
246
|
+
const targetBook = context.store.db
|
|
247
|
+
.prepare("SELECT book_id, slug, created_at FROM dolt_at_book(?)")
|
|
248
|
+
.get(revision.hash);
|
|
249
|
+
if (!targetBook) {
|
|
250
|
+
throw new EngineFault({
|
|
251
|
+
code: "NOT_FOUND",
|
|
252
|
+
message: `Book did not exist at ${revision.hash}`,
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
const currentBook = context.bookRow();
|
|
256
|
+
if (targetBook.book_id !== currentBook.book_id) {
|
|
257
|
+
throw new EngineFault({
|
|
258
|
+
code: "SCHEMA_INCOMPATIBLE",
|
|
259
|
+
message: "A restore cannot replace the catalog's stable book ID",
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
const targetArtifacts = context.store.db
|
|
263
|
+
.prepare(`SELECT artifact_id, slug, kind, created_at
|
|
264
|
+
FROM dolt_at_artifacts(?)
|
|
265
|
+
ORDER BY artifact_id`)
|
|
266
|
+
.all(revision.hash);
|
|
267
|
+
const targetIds = targetArtifacts.map((row) => row.artifact_id);
|
|
268
|
+
const targetFiles = rowsForArtifactIds(context, "artifact_files", "artifact_id, path, object_hash, mtime_ms, created_at", revision.hash, targetIds);
|
|
269
|
+
const bookMetadata = context.store.db
|
|
270
|
+
.prepare("SELECT key, value_json FROM dolt_at_book_metadata(?)")
|
|
271
|
+
.all(revision.hash);
|
|
272
|
+
const artifactMetadata = rowsForArtifactIds(context, "artifact_metadata", "artifact_id, key, value_json", revision.hash, targetIds);
|
|
273
|
+
const waveforms = rowsForArtifactIds(context, "audio_waveforms", "artifact_id, peaks_json", revision.hash, targetIds);
|
|
274
|
+
const entities = context.store.db
|
|
275
|
+
.prepare(`SELECT entity_id, type, name, description, prompt,
|
|
276
|
+
data_json, created_at
|
|
277
|
+
FROM dolt_at_entities(?)`)
|
|
278
|
+
.all(revision.hash);
|
|
279
|
+
const notebooks = context.store.db
|
|
280
|
+
.prepare(`SELECT notebook_id, name, properties_json, created_at
|
|
281
|
+
FROM dolt_at_notebooks(?)`)
|
|
282
|
+
.all(revision.hash);
|
|
283
|
+
const notebookIds = notebooks.map((row) => row.notebook_id);
|
|
284
|
+
const notebookCells = rowsForNotebookIds(context, "cells", `notebook_id, cell_id, type, title, position_x, position_y,
|
|
285
|
+
entity_id, prompt, model, inputs_json, output_artifact_id`, revision.hash, notebookIds);
|
|
286
|
+
const notebookEdges = rowsForNotebookIds(context, "edges", "notebook_id, edge_id, source_cell_id, target_cell_id, target_input", revision.hash, notebookIds);
|
|
287
|
+
const notebookRuns = rowsForNotebookIds(context, "runs", `run_id, notebook_id, status, started_at, completed_at,
|
|
288
|
+
cell_order_json, outputs_json, error`, revision.hash, notebookIds);
|
|
289
|
+
const timeline = context.store.db
|
|
290
|
+
.prepare("SELECT book_id, render FROM dolt_at_timeline(?)")
|
|
291
|
+
.get(revision.hash);
|
|
292
|
+
const timelineSlots = context.store.db
|
|
293
|
+
.prepare(`SELECT slot_id, artifact_id, ordinal, volume,
|
|
294
|
+
audio_fade_in, audio_fade_out
|
|
295
|
+
FROM dolt_at_timeline_slots(?)`)
|
|
296
|
+
.all(revision.hash);
|
|
297
|
+
const timelineAudio = context.store.db
|
|
298
|
+
.prepare(`SELECT audio_id, artifact_id, ordinal, start_frame, duration_frames,
|
|
299
|
+
volume, fade_in, fade_out
|
|
300
|
+
FROM dolt_at_timeline_audio(?)`)
|
|
301
|
+
.all(revision.hash);
|
|
302
|
+
const prompts = context.store.db
|
|
303
|
+
.prepare(`SELECT prompt_id, surface, prompt, context_json, created_at
|
|
304
|
+
FROM dolt_at_prompt_entries(?)`)
|
|
305
|
+
.all(revision.hash);
|
|
306
|
+
const messages = context.store.db
|
|
307
|
+
.prepare(`SELECT message_id, role, body_json, created_at
|
|
308
|
+
FROM dolt_at_messages(?)`)
|
|
309
|
+
.all(revision.hash);
|
|
310
|
+
const currentArtifactIds = context.store.db
|
|
311
|
+
.prepare("SELECT artifact_id FROM artifacts")
|
|
312
|
+
.all()
|
|
313
|
+
.map((row) => row.artifact_id);
|
|
314
|
+
const mutation = await context.store.semantic({
|
|
315
|
+
operation: "restore",
|
|
316
|
+
details: { fromRevision: revision.hash },
|
|
317
|
+
writeSet: ["book"],
|
|
318
|
+
}, [
|
|
319
|
+
"book",
|
|
320
|
+
"artifacts",
|
|
321
|
+
"artifact_files",
|
|
322
|
+
"book_metadata",
|
|
323
|
+
"artifact_metadata",
|
|
324
|
+
"audio_waveforms",
|
|
325
|
+
"entities",
|
|
326
|
+
"notebooks",
|
|
327
|
+
"cells",
|
|
328
|
+
"edges",
|
|
329
|
+
"runs",
|
|
330
|
+
"timeline",
|
|
331
|
+
"timeline_slots",
|
|
332
|
+
"timeline_audio",
|
|
333
|
+
"prompt_entries",
|
|
334
|
+
"messages",
|
|
335
|
+
"job_runs",
|
|
336
|
+
], (_operationId, now) => {
|
|
337
|
+
context.store.db.prepare("DELETE FROM timeline_slots").run();
|
|
338
|
+
context.store.db.prepare("DELETE FROM timeline_audio").run();
|
|
339
|
+
context.store.db.prepare("DELETE FROM edges").run();
|
|
340
|
+
context.store.db.prepare("DELETE FROM runs").run();
|
|
341
|
+
context.store.db.prepare("DELETE FROM cells").run();
|
|
342
|
+
context.store.db.prepare("DELETE FROM notebooks").run();
|
|
343
|
+
context.store.db.prepare("DELETE FROM artifact_metadata").run();
|
|
344
|
+
context.store.db.prepare("DELETE FROM audio_waveforms").run();
|
|
345
|
+
context.store.db.prepare("DELETE FROM artifact_files").run();
|
|
346
|
+
context.store.db.prepare("DELETE FROM entities").run();
|
|
347
|
+
context.store.db.prepare("DELETE FROM artifacts").run();
|
|
348
|
+
context.store.db
|
|
349
|
+
.prepare("UPDATE book SET slug=?, created_at=? WHERE book_id=?")
|
|
350
|
+
.run(targetBook.slug, targetBook.created_at, targetBook.book_id);
|
|
351
|
+
const insertArtifact = context.store.db.prepare(`INSERT INTO artifacts(artifact_id, slug, kind, created_at)
|
|
352
|
+
VALUES (?, ?, ?, ?)`);
|
|
353
|
+
for (const row of targetArtifacts) {
|
|
354
|
+
insertArtifact.run(row.artifact_id, row.slug, row.kind, row.created_at);
|
|
355
|
+
}
|
|
356
|
+
insertFiles(context, targetFiles);
|
|
357
|
+
const insertArtifactMetadata = context.store.db.prepare(`INSERT INTO artifact_metadata(artifact_id, key, value_json)
|
|
358
|
+
VALUES (?, ?, ?)`);
|
|
359
|
+
for (const row of artifactMetadata) {
|
|
360
|
+
insertArtifactMetadata.run(row.artifact_id, row.key, row.value_json);
|
|
361
|
+
}
|
|
362
|
+
const insertWaveform = context.store.db.prepare(`INSERT INTO audio_waveforms(artifact_id, peaks_json)
|
|
363
|
+
VALUES (?, ?)`);
|
|
364
|
+
for (const row of waveforms) {
|
|
365
|
+
if (!row.artifact_id)
|
|
366
|
+
continue;
|
|
367
|
+
insertWaveform.run(row.artifact_id, row.peaks_json);
|
|
368
|
+
}
|
|
369
|
+
context.store.db.prepare("DELETE FROM book_metadata").run();
|
|
370
|
+
const insertBookMetadata = context.store.db.prepare("INSERT INTO book_metadata(key, value_json) VALUES (?, ?)");
|
|
371
|
+
for (const row of bookMetadata) {
|
|
372
|
+
insertBookMetadata.run(row.key, row.value_json);
|
|
373
|
+
}
|
|
374
|
+
const insertEntity = context.store.db.prepare(`INSERT INTO entities(
|
|
375
|
+
entity_id, type, name, description, prompt, data_json, created_at
|
|
376
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?)`);
|
|
377
|
+
for (const row of entities) {
|
|
378
|
+
insertEntity.run(row.entity_id, row.type, row.name, row.description, row.prompt, row.data_json, row.created_at);
|
|
379
|
+
}
|
|
380
|
+
const insertNotebook = context.store.db.prepare(`INSERT INTO notebooks(
|
|
381
|
+
notebook_id, name, properties_json, created_at
|
|
382
|
+
) VALUES (?, ?, ?, ?)`);
|
|
383
|
+
for (const row of notebooks) {
|
|
384
|
+
insertNotebook.run(row.notebook_id, row.name, row.properties_json, row.created_at);
|
|
385
|
+
}
|
|
386
|
+
insertNotebookChildren(context, notebookCells, notebookEdges, notebookRuns);
|
|
387
|
+
context.store.db
|
|
388
|
+
.prepare("UPDATE timeline SET render=? WHERE book_id=?")
|
|
389
|
+
.run(timeline?.render ?? "landscape", targetBook.book_id);
|
|
390
|
+
const insertSlot = context.store.db.prepare(`INSERT INTO timeline_slots(
|
|
391
|
+
slot_id, artifact_id, ordinal, volume, audio_fade_in, audio_fade_out
|
|
392
|
+
) VALUES (?, ?, ?, ?, ?, ?)`);
|
|
393
|
+
for (const row of timelineSlots) {
|
|
394
|
+
insertSlot.run(row.slot_id, row.artifact_id, row.ordinal, row.volume, row.audio_fade_in, row.audio_fade_out);
|
|
395
|
+
}
|
|
396
|
+
const insertAudio = context.store.db.prepare(`INSERT INTO timeline_audio(
|
|
397
|
+
audio_id, artifact_id, ordinal, start_frame, duration_frames,
|
|
398
|
+
volume, fade_in, fade_out
|
|
399
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`);
|
|
400
|
+
for (const row of timelineAudio) {
|
|
401
|
+
insertAudio.run(row.audio_id, row.artifact_id, row.ordinal, row.start_frame, row.duration_frames, row.volume, row.fade_in, row.fade_out);
|
|
402
|
+
}
|
|
403
|
+
context.store.db.prepare("DELETE FROM prompt_entries").run();
|
|
404
|
+
const insertPrompt = context.store.db.prepare(`INSERT INTO prompt_entries(
|
|
405
|
+
prompt_id, surface, prompt, context_json, created_at
|
|
406
|
+
) VALUES (?, ?, ?, ?, ?)`);
|
|
407
|
+
for (const row of prompts) {
|
|
408
|
+
insertPrompt.run(row.prompt_id, row.surface, row.prompt, row.context_json, row.created_at);
|
|
409
|
+
}
|
|
410
|
+
context.store.db.prepare("DELETE FROM messages").run();
|
|
411
|
+
const insertMessage = context.store.db.prepare(`INSERT INTO messages(message_id, role, body_json, created_at)
|
|
412
|
+
VALUES (?, ?, ?, ?)`);
|
|
413
|
+
for (const row of messages) {
|
|
414
|
+
insertMessage.run(row.message_id, row.role, row.body_json, row.created_at);
|
|
415
|
+
}
|
|
416
|
+
abortActiveJobs(context, now, "Book restored");
|
|
417
|
+
context.store.db
|
|
418
|
+
.prepare(`UPDATE runtime_resource_leases
|
|
419
|
+
SET revoked_at=?, fence=fence+1 WHERE revoked_at IS NULL`)
|
|
420
|
+
.run(now);
|
|
421
|
+
context.store.db
|
|
422
|
+
.prepare("UPDATE runtime_workspace_entries SET invalidated_at=?")
|
|
423
|
+
.run(now);
|
|
424
|
+
context.store.db.prepare("DELETE FROM runtime_artifact_views").run();
|
|
425
|
+
context.store.db.prepare("DELETE FROM runtime_pending_tasks").run();
|
|
426
|
+
context.store.db.prepare("DELETE FROM runtime_generation_errors").run();
|
|
427
|
+
context.store.db.prepare("DELETE FROM runtime_similarity_embeddings").run();
|
|
428
|
+
context.store.db
|
|
429
|
+
.prepare("DELETE FROM runtime_text_similarity_documents")
|
|
430
|
+
.run();
|
|
431
|
+
for (const row of targetArtifacts) {
|
|
432
|
+
resetArtifactRuntime(context, row.artifact_id, now);
|
|
433
|
+
}
|
|
434
|
+
});
|
|
435
|
+
for (const artifactId of new Set([...currentArtifactIds, ...targetIds])) {
|
|
436
|
+
await rm(context.artifactPath(artifactId), { recursive: true, force: true });
|
|
437
|
+
}
|
|
438
|
+
for (const artifact of targetArtifacts) {
|
|
439
|
+
await materializeArtifact(context, artifact.artifact_id);
|
|
440
|
+
}
|
|
441
|
+
return ok(revisionForHash(context, mutation.revision), mutation.revision);
|
|
442
|
+
});
|
|
443
|
+
}
|
|
444
|
+
async function logAction(context, actionName, payload) {
|
|
445
|
+
return resultOf(async () => {
|
|
446
|
+
const result = await recordOperation(context, `action:${actionName}`, undefined, {
|
|
447
|
+
payload,
|
|
448
|
+
});
|
|
449
|
+
if (!result.ok)
|
|
450
|
+
throw new EngineFault(result.error);
|
|
451
|
+
return {
|
|
452
|
+
hash: result.value.hash,
|
|
453
|
+
action: actionName,
|
|
454
|
+
payload,
|
|
455
|
+
date: result.value.date,
|
|
456
|
+
};
|
|
457
|
+
});
|
|
458
|
+
}
|
|
459
|
+
function actionLog(context, options = {}) {
|
|
460
|
+
return revisionHistory(context, Math.max(options.limit ?? 100, 100))
|
|
461
|
+
.filter((revision) => revision.operation?.startsWith("action:") &&
|
|
462
|
+
(!options.action || revision.operation === `action:${options.action}`))
|
|
463
|
+
.slice(0, options.limit ?? 100)
|
|
464
|
+
.map((revision) => ({
|
|
465
|
+
hash: revision.hash,
|
|
466
|
+
action: revision.operation.slice("action:".length),
|
|
467
|
+
payload: revision.details?.payload ??
|
|
468
|
+
{},
|
|
469
|
+
date: revision.date,
|
|
470
|
+
}));
|
|
471
|
+
}
|
|
472
|
+
function actions(context, options = {}) {
|
|
473
|
+
return syncResultOf(() => {
|
|
474
|
+
const limit = Math.max(1, options.limit ?? 200);
|
|
475
|
+
const params = [];
|
|
476
|
+
let cursorClause = "";
|
|
477
|
+
if (options.cursor) {
|
|
478
|
+
const cursor = context.store.db
|
|
479
|
+
.prepare("SELECT created_at, action_id FROM actions WHERE action_id=?")
|
|
480
|
+
.get(options.cursor);
|
|
481
|
+
if (cursor) {
|
|
482
|
+
cursorClause = "WHERE (created_at < ? OR (created_at = ? AND action_id < ?))";
|
|
483
|
+
params.push(cursor.created_at, cursor.created_at, cursor.action_id);
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
params.push(limit + 1);
|
|
487
|
+
const rows = context.store.db
|
|
488
|
+
.prepare(`${ACTION_SELECT}
|
|
489
|
+
${cursorClause}
|
|
490
|
+
ORDER BY created_at DESC, action_id DESC
|
|
491
|
+
LIMIT ?`)
|
|
492
|
+
.all(...params);
|
|
493
|
+
const page = rows.slice(0, limit);
|
|
494
|
+
return {
|
|
495
|
+
headRevision: context.store.head,
|
|
496
|
+
actions: page
|
|
497
|
+
.map((row) => actionFromRow(context, row))
|
|
498
|
+
.sort((left, right) => left.date.localeCompare(right.date)),
|
|
499
|
+
...(rows.length > limit ? { nextCursor: page.at(-1)?.action_id } : {}),
|
|
500
|
+
};
|
|
501
|
+
});
|
|
502
|
+
}
|
|
503
|
+
function action(context, actionId) {
|
|
504
|
+
return syncResultOf(() => requiredAction(context, actionId));
|
|
505
|
+
}
|
|
506
|
+
async function recordAction(context, input) {
|
|
507
|
+
return resultOf(async () => {
|
|
508
|
+
assertWriteSet(context, input.baseRevision, input.writeSet ?? []);
|
|
509
|
+
const actionId = input.actionId ?? newUuidV7();
|
|
510
|
+
assertUuidV7(actionId, "Action ID");
|
|
511
|
+
const phase = input.phase ?? "completed";
|
|
512
|
+
const scope = input.scope ?? "book";
|
|
513
|
+
assertActionPhase(phase);
|
|
514
|
+
assertActionScope(scope);
|
|
515
|
+
for (const parentId of input.parentActionIds ?? []) {
|
|
516
|
+
assertUuidV7(parentId, "Parent action ID");
|
|
517
|
+
requiredAction(context, parentId);
|
|
518
|
+
}
|
|
519
|
+
if (input.targetActionId) {
|
|
520
|
+
assertUuidV7(input.targetActionId, "Target action ID");
|
|
521
|
+
}
|
|
522
|
+
const actor = input.actor ?? "videobook";
|
|
523
|
+
const inputArtifacts = resolveArtifactReferences(context, input.inputArtifactIds ?? []);
|
|
524
|
+
const outputArtifacts = resolveArtifactReferences(context, input.outputArtifactIds ?? []);
|
|
525
|
+
const targetArtifactId = input.targetArtifactId
|
|
526
|
+
? context.artifactRow(input.targetArtifactId).artifact_id
|
|
527
|
+
: null;
|
|
528
|
+
const mutation = await context.store.semantic({
|
|
529
|
+
operation: `action:${input.operation}`,
|
|
530
|
+
...(targetArtifactId ? { artifactId: targetArtifactId } : {}),
|
|
531
|
+
details: {
|
|
532
|
+
...(input.details ?? {}),
|
|
533
|
+
actionId,
|
|
534
|
+
phase,
|
|
535
|
+
scope,
|
|
536
|
+
lane: input.lane ?? actor,
|
|
537
|
+
},
|
|
538
|
+
...(input.baseRevision ? { baseRevision: input.baseRevision } : {}),
|
|
539
|
+
writeSet: input.writeSet ?? [],
|
|
540
|
+
}, [
|
|
541
|
+
"actions",
|
|
542
|
+
"action_events",
|
|
543
|
+
"action_parents",
|
|
544
|
+
"action_artifacts",
|
|
545
|
+
"action_write_set",
|
|
546
|
+
], (operationId, now) => {
|
|
547
|
+
context.store.db
|
|
548
|
+
.prepare(`INSERT INTO actions(
|
|
549
|
+
action_id, operation, scope, actor, lane, phase,
|
|
550
|
+
base_revision, target_artifact_id, target_action_id,
|
|
551
|
+
layout_json, details_json, created_at
|
|
552
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
553
|
+
ON CONFLICT(action_id) DO UPDATE SET
|
|
554
|
+
operation=excluded.operation,
|
|
555
|
+
scope=excluded.scope,
|
|
556
|
+
actor=excluded.actor,
|
|
557
|
+
lane=excluded.lane,
|
|
558
|
+
phase=excluded.phase,
|
|
559
|
+
base_revision=excluded.base_revision,
|
|
560
|
+
target_artifact_id=excluded.target_artifact_id,
|
|
561
|
+
target_action_id=excluded.target_action_id,
|
|
562
|
+
layout_json=excluded.layout_json,
|
|
563
|
+
details_json=excluded.details_json`)
|
|
564
|
+
.run(actionId, input.operation, scope, actor, input.lane ?? actor, phase, input.baseRevision ?? null, targetArtifactId, input.targetActionId ?? null, input.layout ? canonicalJson(input.layout) : null, canonicalJson(input.details ?? {}), now);
|
|
565
|
+
context.store.db
|
|
566
|
+
.prepare(`INSERT INTO action_events(
|
|
567
|
+
event_id, action_id, operation_id, phase,
|
|
568
|
+
details_json, created_at
|
|
569
|
+
) VALUES (?, ?, ?, ?, ?, ?)`)
|
|
570
|
+
.run(newUuidV7(), actionId, operationId, phase, canonicalJson(input.details ?? {}), now);
|
|
571
|
+
replaceActionLinks(context, actionId, input.parentActionIds ?? [], inputArtifacts, outputArtifacts, input.writeSet ?? []);
|
|
572
|
+
});
|
|
573
|
+
return ok({
|
|
574
|
+
action: requiredAction(context, actionId),
|
|
575
|
+
revision: revisionForHash(context, mutation.revision),
|
|
576
|
+
}, mutation.revision);
|
|
577
|
+
});
|
|
578
|
+
}
|
|
579
|
+
function requiredAction(context, actionId) {
|
|
580
|
+
const row = context.store.db
|
|
581
|
+
.prepare(`${ACTION_SELECT} WHERE action_id=?`)
|
|
582
|
+
.get(actionId);
|
|
583
|
+
if (!row) {
|
|
584
|
+
throw new EngineFault({
|
|
585
|
+
code: "NOT_FOUND",
|
|
586
|
+
message: `History action not found: ${actionId}`,
|
|
587
|
+
});
|
|
588
|
+
}
|
|
589
|
+
return actionFromRow(context, row);
|
|
590
|
+
}
|
|
591
|
+
function actionFromRow(context, row) {
|
|
592
|
+
const parents = context.store.db
|
|
593
|
+
.prepare("SELECT parent_action_id FROM action_parents WHERE action_id=?")
|
|
594
|
+
.all(row.action_id).map((item) => item.parent_action_id);
|
|
595
|
+
const links = context.store.db
|
|
596
|
+
.prepare(`SELECT artifact_id, direction
|
|
597
|
+
FROM action_artifacts WHERE action_id=?`)
|
|
598
|
+
.all(row.action_id);
|
|
599
|
+
const events = context.store.db
|
|
600
|
+
.prepare(`SELECT event_id, action_id, operation_id, phase,
|
|
601
|
+
details_json, created_at
|
|
602
|
+
FROM action_events WHERE action_id=?
|
|
603
|
+
ORDER BY created_at, event_id`)
|
|
604
|
+
.all(row.action_id);
|
|
605
|
+
const revisionsByOperation = new Map(allRevisions(context).map((revision) => [revision.operationId, revision]));
|
|
606
|
+
return {
|
|
607
|
+
id: row.action_id,
|
|
608
|
+
operation: row.operation,
|
|
609
|
+
title: titleForOperation(row.operation),
|
|
610
|
+
scope: row.scope,
|
|
611
|
+
actor: row.actor,
|
|
612
|
+
lane: row.lane,
|
|
613
|
+
date: new Date(row.created_at).toISOString(),
|
|
614
|
+
phase: row.phase,
|
|
615
|
+
...(row.base_revision ? { baseRevision: row.base_revision } : {}),
|
|
616
|
+
parentActionIds: parents,
|
|
617
|
+
inputArtifacts: links
|
|
618
|
+
.filter((link) => link.direction === "input")
|
|
619
|
+
.map((link) => artifactRef(context, link.artifact_id)),
|
|
620
|
+
outputArtifacts: links
|
|
621
|
+
.filter((link) => link.direction === "output")
|
|
622
|
+
.map((link) => artifactRef(context, link.artifact_id)),
|
|
623
|
+
...(row.target_artifact_id
|
|
624
|
+
? { targetArtifactId: row.target_artifact_id }
|
|
625
|
+
: {}),
|
|
626
|
+
...(row.target_action_id ? { targetActionId: row.target_action_id } : {}),
|
|
627
|
+
...(row.layout_json
|
|
628
|
+
? {
|
|
629
|
+
layout: parseJson(row.layout_json, { stage: 0, column: 0 }),
|
|
630
|
+
}
|
|
631
|
+
: {}),
|
|
632
|
+
details: parseJson(row.details_json, {}),
|
|
633
|
+
events: events.map((event) => {
|
|
634
|
+
const revision = revisionsByOperation.get(event.operation_id);
|
|
635
|
+
return {
|
|
636
|
+
id: event.event_id,
|
|
637
|
+
revision: revision?.hash ?? context.store.head,
|
|
638
|
+
phase: event.phase,
|
|
639
|
+
date: new Date(event.created_at).toISOString(),
|
|
640
|
+
details: parseJson(event.details_json, {}),
|
|
641
|
+
files: revision?.files ?? [],
|
|
642
|
+
fileChanges: revision?.fileChanges ?? [],
|
|
643
|
+
};
|
|
644
|
+
}),
|
|
645
|
+
};
|
|
646
|
+
}
|
|
647
|
+
function replaceActionLinks(context, actionId, parents, inputs, outputs, writeSet) {
|
|
648
|
+
context.store.db
|
|
649
|
+
.prepare("DELETE FROM action_parents WHERE action_id=?")
|
|
650
|
+
.run(actionId);
|
|
651
|
+
context.store.db
|
|
652
|
+
.prepare("DELETE FROM action_artifacts WHERE action_id=?")
|
|
653
|
+
.run(actionId);
|
|
654
|
+
context.store.db
|
|
655
|
+
.prepare("DELETE FROM action_write_set WHERE action_id=?")
|
|
656
|
+
.run(actionId);
|
|
657
|
+
const insertParent = context.store.db.prepare("INSERT INTO action_parents(action_id, parent_action_id) VALUES (?, ?)");
|
|
658
|
+
for (const parent of new Set(parents))
|
|
659
|
+
insertParent.run(actionId, parent);
|
|
660
|
+
const insertArtifact = context.store.db.prepare(`INSERT INTO action_artifacts(action_id, artifact_id, direction)
|
|
661
|
+
VALUES (?, ?, ?)`);
|
|
662
|
+
for (const artifactId of new Set(inputs)) {
|
|
663
|
+
insertArtifact.run(actionId, artifactId, "input");
|
|
664
|
+
}
|
|
665
|
+
for (const artifactId of new Set(outputs)) {
|
|
666
|
+
insertArtifact.run(actionId, artifactId, "output");
|
|
667
|
+
}
|
|
668
|
+
const insertResource = context.store.db.prepare("INSERT INTO action_write_set(action_id, resource) VALUES (?, ?)");
|
|
669
|
+
for (const resource of new Set(writeSet))
|
|
670
|
+
insertResource.run(actionId, resource);
|
|
671
|
+
}
|
|
672
|
+
function assertWriteSet(context, baseRevision, writeSet) {
|
|
673
|
+
if (!baseRevision || baseRevision === context.store.head)
|
|
674
|
+
return;
|
|
675
|
+
const revisions = allRevisions(context);
|
|
676
|
+
let baseIndex = revisions.findIndex((revision) => revision.hash === baseRevision || revision.hash.startsWith(baseRevision));
|
|
677
|
+
if (baseIndex < 0) {
|
|
678
|
+
const commit = context.store.db.doltLog().find((item) => item.commit_hash === baseRevision || item.commit_hash.startsWith(baseRevision));
|
|
679
|
+
if (!commit) {
|
|
680
|
+
throw new EngineFault({
|
|
681
|
+
code: "STALE_REVISION",
|
|
682
|
+
message: `Base revision not found: ${baseRevision}`,
|
|
683
|
+
});
|
|
684
|
+
}
|
|
685
|
+
// Initialization has no operation record, so it is older than every
|
|
686
|
+
// operation-bearing revision we can inspect for a write-set conflict.
|
|
687
|
+
baseIndex = revisions.length;
|
|
688
|
+
}
|
|
689
|
+
const requested = new Set(writeSet);
|
|
690
|
+
const conflicts = new Set();
|
|
691
|
+
for (const revision of revisions.slice(0, baseIndex)) {
|
|
692
|
+
const changed = revision.details?.writeSet;
|
|
693
|
+
if (!Array.isArray(changed))
|
|
694
|
+
continue;
|
|
695
|
+
for (const resource of changed) {
|
|
696
|
+
if (typeof resource === "string" && requested.has(resource)) {
|
|
697
|
+
conflicts.add(resource);
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
if (conflicts.size > 0) {
|
|
702
|
+
throw new EngineFault({
|
|
703
|
+
code: "ACTION_CONFLICT",
|
|
704
|
+
message: `Action conflicts with newer changes: ${[...conflicts].join(", ")}`,
|
|
705
|
+
details: { resources: [...conflicts] },
|
|
706
|
+
});
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
function resetArtifactRuntime(context, artifactId, now) {
|
|
710
|
+
context.store.db
|
|
711
|
+
.prepare(`INSERT INTO runtime_artifact_views(
|
|
712
|
+
artifact_id, status, meta_json, updated_at
|
|
713
|
+
) VALUES (?, 'ready', '{}', ?)
|
|
714
|
+
ON CONFLICT(artifact_id) DO UPDATE SET
|
|
715
|
+
status='ready', meta_json='{}', owner_id=NULL,
|
|
716
|
+
owner_kind=NULL, pid=NULL, deadline_at=NULL,
|
|
717
|
+
updated_at=excluded.updated_at, fence=fence+1`)
|
|
718
|
+
.run(artifactId, now);
|
|
719
|
+
context.store.db
|
|
720
|
+
.prepare(`INSERT INTO runtime_workspace_entries(
|
|
721
|
+
artifact_id, path, invalidated_at, last_accessed_at
|
|
722
|
+
) VALUES (?, ?, ?, ?)
|
|
723
|
+
ON CONFLICT(artifact_id) DO UPDATE SET
|
|
724
|
+
invalidated_at=excluded.invalidated_at,
|
|
725
|
+
hydrated_at=NULL,
|
|
726
|
+
last_accessed_at=excluded.last_accessed_at`)
|
|
727
|
+
.run(artifactId, context.artifactPath(artifactId), now, now);
|
|
728
|
+
context.store.db
|
|
729
|
+
.prepare("DELETE FROM runtime_pending_tasks WHERE artifact_id=?")
|
|
730
|
+
.run(artifactId);
|
|
731
|
+
context.store.db
|
|
732
|
+
.prepare("DELETE FROM runtime_generation_errors WHERE artifact_id=?")
|
|
733
|
+
.run(artifactId);
|
|
734
|
+
context.store.db
|
|
735
|
+
.prepare(`UPDATE runtime_resource_leases
|
|
736
|
+
SET revoked_at=?, fence=fence+1
|
|
737
|
+
WHERE artifact_id=? AND revoked_at IS NULL`)
|
|
738
|
+
.run(now, artifactId);
|
|
739
|
+
}
|
|
740
|
+
function abortActiveJobs(context, now, message) {
|
|
741
|
+
const jobs = context.store.db
|
|
742
|
+
.prepare(`SELECT id, artifact_id, type, payload_json, result_json, started_at
|
|
743
|
+
FROM runtime_jobs
|
|
744
|
+
WHERE state IN ('queued','running','completing')`)
|
|
745
|
+
.all();
|
|
746
|
+
const update = context.store.db.prepare(`UPDATE runtime_jobs
|
|
747
|
+
SET state='aborted', error_json=?, finished_at=?,
|
|
748
|
+
lease_expires_at=NULL, pid=NULL, fence=fence+1
|
|
749
|
+
WHERE id=?`);
|
|
750
|
+
const insertRun = context.store.db.prepare(`INSERT INTO job_runs(
|
|
751
|
+
run_id, artifact_id, job_type, state,
|
|
752
|
+
payload_json, result_json, error_json, started_at, finished_at
|
|
753
|
+
) VALUES (?, ?, ?, 'aborted', ?, ?, ?, ?, ?)`);
|
|
754
|
+
for (const job of jobs) {
|
|
755
|
+
const errorJson = canonicalJson({ message });
|
|
756
|
+
update.run(errorJson, now, job.id);
|
|
757
|
+
insertRun.run(newUuidV7(), job.artifact_id, job.type, job.payload_json, job.result_json, errorJson, job.started_at, now);
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
function filesAt(context, revision, artifactId) {
|
|
761
|
+
return context.store.db
|
|
762
|
+
.prepare(`SELECT artifact_id, path, object_hash, mtime_ms, created_at
|
|
763
|
+
FROM dolt_at_artifact_files(?) WHERE artifact_id=? ORDER BY path`)
|
|
764
|
+
.all(revision, artifactId);
|
|
765
|
+
}
|
|
766
|
+
function rowsForArtifactIds(context, table, columns, revision, artifactIds) {
|
|
767
|
+
if (artifactIds.length === 0)
|
|
768
|
+
return [];
|
|
769
|
+
const placeholders = artifactIds.map(() => "?").join(", ");
|
|
770
|
+
return context.store.db
|
|
771
|
+
.prepare(`SELECT ${columns} FROM dolt_at_${table}(?)
|
|
772
|
+
WHERE artifact_id IN (${placeholders})`)
|
|
773
|
+
.all(revision, ...artifactIds);
|
|
774
|
+
}
|
|
775
|
+
function rowsForNotebookIds(context, table, columns, revision, notebookIds) {
|
|
776
|
+
if (notebookIds.length === 0)
|
|
777
|
+
return [];
|
|
778
|
+
const placeholders = notebookIds.map(() => "?").join(", ");
|
|
779
|
+
return context.store.db
|
|
780
|
+
.prepare(`SELECT ${columns} FROM dolt_at_${table}(?)
|
|
781
|
+
WHERE notebook_id IN (${placeholders})`)
|
|
782
|
+
.all(revision, ...notebookIds);
|
|
783
|
+
}
|
|
784
|
+
function insertFiles(context, files) {
|
|
785
|
+
const insert = context.store.db.prepare(`INSERT INTO artifact_files(
|
|
786
|
+
artifact_id, path, object_hash, mtime_ms, created_at
|
|
787
|
+
) VALUES (?, ?, ?, ?, ?)`);
|
|
788
|
+
for (const row of files) {
|
|
789
|
+
insert.run(row.artifact_id, row.path, row.object_hash, row.mtime_ms, row.created_at);
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
function insertNotebookChildren(context, cells, edges, runs) {
|
|
793
|
+
const insertCell = context.store.db.prepare(`INSERT INTO cells(
|
|
794
|
+
notebook_id, cell_id, type, title, position_x, position_y,
|
|
795
|
+
entity_id, prompt, model, inputs_json, output_artifact_id
|
|
796
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`);
|
|
797
|
+
for (const row of cells) {
|
|
798
|
+
insertCell.run(row.notebook_id, row.cell_id, row.type, row.title, row.position_x, row.position_y, row.entity_id, row.prompt, row.model, row.inputs_json, row.output_artifact_id);
|
|
799
|
+
}
|
|
800
|
+
const insertEdge = context.store.db.prepare(`INSERT INTO edges(
|
|
801
|
+
notebook_id, edge_id, source_cell_id, target_cell_id, target_input
|
|
802
|
+
) VALUES (?, ?, ?, ?, ?)`);
|
|
803
|
+
for (const row of edges) {
|
|
804
|
+
insertEdge.run(row.notebook_id, row.edge_id, row.source_cell_id, row.target_cell_id, row.target_input);
|
|
805
|
+
}
|
|
806
|
+
const insertRun = context.store.db.prepare(`INSERT INTO runs(
|
|
807
|
+
run_id, notebook_id, status, started_at, completed_at,
|
|
808
|
+
cell_order_json, outputs_json, error
|
|
809
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`);
|
|
810
|
+
for (const row of runs) {
|
|
811
|
+
insertRun.run(row.run_id, row.notebook_id, row.status, row.started_at, row.completed_at, row.cell_order_json, row.outputs_json, row.error);
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
function operationFromDiff(row) {
|
|
815
|
+
const prefix = row.diff_type === "removed" ? "from_" : "to_";
|
|
816
|
+
const operationId = row[`${prefix}operation_id`];
|
|
817
|
+
const operation = row[`${prefix}operation`];
|
|
818
|
+
const detailsJson = row[`${prefix}details_json`];
|
|
819
|
+
const writeSetJson = row[`${prefix}write_set_json`];
|
|
820
|
+
const createdAt = row[`${prefix}created_at`];
|
|
821
|
+
const author = row[`${prefix}author`];
|
|
822
|
+
if (typeof operationId !== "string" ||
|
|
823
|
+
typeof operation !== "string" ||
|
|
824
|
+
typeof detailsJson !== "string" ||
|
|
825
|
+
typeof writeSetJson !== "string" ||
|
|
826
|
+
typeof createdAt !== "number" ||
|
|
827
|
+
typeof author !== "string") {
|
|
828
|
+
return null;
|
|
829
|
+
}
|
|
830
|
+
const artifactId = row[`${prefix}artifact_id`];
|
|
831
|
+
const baseRevision = row[`${prefix}base_revision`];
|
|
832
|
+
return {
|
|
833
|
+
operation_id: operationId,
|
|
834
|
+
operation,
|
|
835
|
+
artifact_id: typeof artifactId === "string" ? artifactId : null,
|
|
836
|
+
details_json: detailsJson,
|
|
837
|
+
write_set_json: writeSetJson,
|
|
838
|
+
base_revision: typeof baseRevision === "string" ? baseRevision : null,
|
|
839
|
+
created_at: createdAt,
|
|
840
|
+
author,
|
|
841
|
+
};
|
|
842
|
+
}
|
|
843
|
+
function revisionFileChanges(rows, artifactId) {
|
|
844
|
+
const changes = [];
|
|
845
|
+
for (const row of rows) {
|
|
846
|
+
const fromArtifact = row.from_artifact_id;
|
|
847
|
+
const toArtifact = row.to_artifact_id;
|
|
848
|
+
if (artifactId &&
|
|
849
|
+
fromArtifact !== artifactId &&
|
|
850
|
+
toArtifact !== artifactId) {
|
|
851
|
+
continue;
|
|
852
|
+
}
|
|
853
|
+
const fromPath = typeof row.from_path === "string" ? row.from_path : undefined;
|
|
854
|
+
const toPath = typeof row.to_path === "string" ? row.to_path : undefined;
|
|
855
|
+
const file = toPath ?? fromPath;
|
|
856
|
+
if (!file)
|
|
857
|
+
continue;
|
|
858
|
+
changes.push({
|
|
859
|
+
status: row.diff_type,
|
|
860
|
+
file,
|
|
861
|
+
...(fromPath && toPath && fromPath !== toPath
|
|
862
|
+
? { oldFile: fromPath }
|
|
863
|
+
: {}),
|
|
864
|
+
});
|
|
865
|
+
}
|
|
866
|
+
return changes;
|
|
867
|
+
}
|
|
868
|
+
function artifactSlugAt(context, artifactId, revision) {
|
|
869
|
+
const row = context.store.db
|
|
870
|
+
.prepare(`SELECT slug FROM dolt_at_artifacts(?) WHERE artifact_id=?`)
|
|
871
|
+
.get(revision, artifactId);
|
|
872
|
+
return row?.slug;
|
|
873
|
+
}
|
|
874
|
+
function resolveArtifactReferences(context, references) {
|
|
875
|
+
return [
|
|
876
|
+
...new Set(references.map((reference) => context.artifactRow(reference).artifact_id)),
|
|
877
|
+
];
|
|
878
|
+
}
|
|
879
|
+
function artifactRef(context, artifactId) {
|
|
880
|
+
try {
|
|
881
|
+
const artifact = context.artifactRowById(artifactId);
|
|
882
|
+
return {
|
|
883
|
+
id: artifact.artifact_id,
|
|
884
|
+
slug: artifact.slug,
|
|
885
|
+
kind: historyArtifactKind(artifact.kind),
|
|
886
|
+
};
|
|
887
|
+
}
|
|
888
|
+
catch {
|
|
889
|
+
return { id: artifactId, slug: artifactId, kind: "unknown" };
|
|
890
|
+
}
|
|
891
|
+
}
|
|
892
|
+
function historyArtifactKind(kind) {
|
|
893
|
+
return kind;
|
|
894
|
+
}
|
|
895
|
+
function assertActionPhase(value) {
|
|
896
|
+
if (![
|
|
897
|
+
"requested",
|
|
898
|
+
"started",
|
|
899
|
+
"completed",
|
|
900
|
+
"failed",
|
|
901
|
+
"cancelled",
|
|
902
|
+
"conflicted",
|
|
903
|
+
].includes(value)) {
|
|
904
|
+
throw new Error(`Invalid action phase: ${value}`);
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
function assertActionScope(value) {
|
|
908
|
+
if (!["book", "artifact", "layout", "external", "system"].includes(value)) {
|
|
909
|
+
throw new Error(`Invalid action scope: ${value}`);
|
|
910
|
+
}
|
|
911
|
+
}
|
|
912
|
+
function titleForOperation(operation) {
|
|
913
|
+
return operation.replaceAll("_", " ").replaceAll("-", " ");
|
|
914
|
+
}
|
|
915
|
+
const ACTION_SELECT = `
|
|
916
|
+
SELECT action_id, operation, scope, actor, lane, phase,
|
|
917
|
+
base_revision, target_artifact_id, target_action_id,
|
|
918
|
+
layout_json, details_json, created_at
|
|
919
|
+
FROM actions
|
|
920
|
+
`;
|
|
921
|
+
//# sourceMappingURL=history.js.map
|