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.
Files changed (103) hide show
  1. package/README.md +214 -0
  2. package/dist/artifacts.d.ts +20 -0
  3. package/dist/artifacts.d.ts.map +1 -0
  4. package/dist/artifacts.js +312 -0
  5. package/dist/artifacts.js.map +1 -0
  6. package/dist/books.d.ts +7 -0
  7. package/dist/books.d.ts.map +1 -0
  8. package/dist/books.js +27 -0
  9. package/dist/books.js.map +1 -0
  10. package/dist/cas.d.ts +26 -0
  11. package/dist/cas.d.ts.map +1 -0
  12. package/dist/cas.js +139 -0
  13. package/dist/cas.js.map +1 -0
  14. package/dist/communications.d.ts +15 -0
  15. package/dist/communications.d.ts.map +1 -0
  16. package/dist/communications.js +139 -0
  17. package/dist/communications.js.map +1 -0
  18. package/dist/context.d.ts +42 -0
  19. package/dist/context.d.ts.map +1 -0
  20. package/dist/context.js +197 -0
  21. package/dist/context.js.map +1 -0
  22. package/dist/domain.d.ts +32 -0
  23. package/dist/domain.d.ts.map +1 -0
  24. package/dist/domain.js +399 -0
  25. package/dist/domain.js.map +1 -0
  26. package/dist/engine-types.d.ts +548 -0
  27. package/dist/engine-types.d.ts.map +1 -0
  28. package/dist/engine-types.js +10 -0
  29. package/dist/engine-types.js.map +1 -0
  30. package/dist/engine.d.ts +213 -0
  31. package/dist/engine.d.ts.map +1 -0
  32. package/dist/engine.js +130 -0
  33. package/dist/engine.js.map +1 -0
  34. package/dist/files.d.ts +28 -0
  35. package/dist/files.d.ts.map +1 -0
  36. package/dist/files.js +476 -0
  37. package/dist/files.js.map +1 -0
  38. package/dist/history-types.d.ts +74 -0
  39. package/dist/history-types.d.ts.map +1 -0
  40. package/dist/history-types.js +2 -0
  41. package/dist/history-types.js.map +1 -0
  42. package/dist/history.d.ts +20 -0
  43. package/dist/history.d.ts.map +1 -0
  44. package/dist/history.js +921 -0
  45. package/dist/history.js.map +1 -0
  46. package/dist/ids.d.ts +4 -0
  47. package/dist/ids.d.ts.map +1 -0
  48. package/dist/ids.js +14 -0
  49. package/dist/ids.js.map +1 -0
  50. package/dist/index.d.ts +16 -0
  51. package/dist/index.d.ts.map +1 -0
  52. package/dist/index.js +12 -0
  53. package/dist/index.js.map +1 -0
  54. package/dist/job-queue.d.ts +51 -0
  55. package/dist/job-queue.d.ts.map +1 -0
  56. package/dist/job-queue.js +443 -0
  57. package/dist/job-queue.js.map +1 -0
  58. package/dist/media.d.ts +7 -0
  59. package/dist/media.d.ts.map +1 -0
  60. package/dist/media.js +60 -0
  61. package/dist/media.js.map +1 -0
  62. package/dist/metadata.d.ts +21 -0
  63. package/dist/metadata.d.ts.map +1 -0
  64. package/dist/metadata.js +222 -0
  65. package/dist/metadata.js.map +1 -0
  66. package/dist/notebook/types.d.ts +51 -0
  67. package/dist/notebook/types.d.ts.map +1 -0
  68. package/dist/notebook/types.js +2 -0
  69. package/dist/notebook/types.js.map +1 -0
  70. package/dist/resolver.d.ts +15 -0
  71. package/dist/resolver.d.ts.map +1 -0
  72. package/dist/resolver.js +81 -0
  73. package/dist/resolver.js.map +1 -0
  74. package/dist/runtime-services.d.ts +65 -0
  75. package/dist/runtime-services.d.ts.map +1 -0
  76. package/dist/runtime-services.js +584 -0
  77. package/dist/runtime-services.js.map +1 -0
  78. package/dist/schema.d.ts +7 -0
  79. package/dist/schema.d.ts.map +1 -0
  80. package/dist/schema.js +509 -0
  81. package/dist/schema.js.map +1 -0
  82. package/dist/similarity.d.ts +4 -0
  83. package/dist/similarity.d.ts.map +1 -0
  84. package/dist/similarity.js +1551 -0
  85. package/dist/similarity.js.map +1 -0
  86. package/dist/status.d.ts +9 -0
  87. package/dist/status.d.ts.map +1 -0
  88. package/dist/status.js +231 -0
  89. package/dist/status.js.map +1 -0
  90. package/dist/storage.d.ts +7 -0
  91. package/dist/storage.d.ts.map +1 -0
  92. package/dist/storage.js +129 -0
  93. package/dist/storage.js.map +1 -0
  94. package/dist/store.d.ts +55 -0
  95. package/dist/store.d.ts.map +1 -0
  96. package/dist/store.js +378 -0
  97. package/dist/store.js.map +1 -0
  98. package/dist/timeline.d.ts +9 -0
  99. package/dist/timeline.d.ts.map +1 -0
  100. package/dist/timeline.js +236 -0
  101. package/dist/timeline.js.map +1 -0
  102. package/docs/engine-layout.md +489 -0
  103. package/package.json +55 -0
@@ -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