@powerhousedao/reactor 6.2.2-dev.8 → 6.2.2-dev.80

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.
@@ -1,2984 +0,0 @@
1
- import { n as ReactorEventTypes, t as EventBusAggregateError } from "./types-DMKLa0Ok.js";
2
- import { DowngradeNotSupportedError, applyDeleteDocumentAction, applyDeleteDocumentAction as applyDeleteDocumentAction$1, applyUpgradeDocumentAction, applyUpgradeDocumentAction as applyUpgradeDocumentAction$1, createPresignedHeader, defaultBaseState, deriveOperationId, isUndoRedo } from "@powerhousedao/shared/document-model";
3
- import { v4 } from "uuid";
4
- import { Migrator, sql } from "kysely";
5
- //#region \0rolldown/runtime.js
6
- var __defProp = Object.defineProperty;
7
- var __exportAll = (all, no_symbols) => {
8
- let target = {};
9
- for (var name in all) __defProp(target, name, {
10
- get: all[name],
11
- enumerable: true
12
- });
13
- if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
14
- return target;
15
- };
16
- //#endregion
17
- //#region src/shared/utils.ts
18
- function matchesScope(view = {}, scope) {
19
- if (view.scopes) return view.scopes.includes(scope);
20
- return true;
21
- }
22
- function yieldToMain() {
23
- const s = globalThis.scheduler;
24
- if (s?.yield) return s.yield();
25
- return new Promise((resolve) => setTimeout(resolve, 0));
26
- }
27
- const defaultAbortError = () => /* @__PURE__ */ new Error("Operation aborted");
28
- function throwIfAborted(signal, makeError = defaultAbortError) {
29
- if (signal?.aborted) throw makeError();
30
- }
31
- /**
32
- * Validates PagingOptions and returns a normalized offset and limit.
33
- * Throws if the cursor is not empty and not a non-negative integer, or if
34
- * limit is less than 1. When `paging` is undefined, returns offset 0 and
35
- * the caller-supplied `defaultLimit`.
36
- */
37
- function parsePagingOptions(paging, defaultLimit) {
38
- if (paging === void 0) return {
39
- offset: 0,
40
- limit: defaultLimit
41
- };
42
- if (!Number.isInteger(paging.limit) || paging.limit < 1) throw new Error(`Invalid paging limit: ${String(paging.limit)} (must be an integer >= 1)`);
43
- if (paging.cursor === "") return {
44
- offset: 0,
45
- limit: paging.limit
46
- };
47
- const parsed = Number(paging.cursor);
48
- if (!Number.isInteger(parsed) || parsed < 0) throw new Error(`Invalid paging cursor: ${JSON.stringify(paging.cursor)} (must be empty or a non-negative integer)`);
49
- return {
50
- offset: parsed,
51
- limit: paging.limit
52
- };
53
- }
54
- //#endregion
55
- //#region src/shared/errors.ts
56
- /**
57
- * Error thrown when attempting to access a deleted document.
58
- */
59
- var DocumentDeletedError = class DocumentDeletedError extends Error {
60
- documentId;
61
- deletedAtUtcIso;
62
- constructor(documentId, deletedAtUtcIso = null) {
63
- const message = deletedAtUtcIso ? `Document ${documentId} was deleted at ${deletedAtUtcIso}` : `Document ${documentId} has been deleted`;
64
- super(message);
65
- this.name = "DocumentDeletedError";
66
- this.documentId = documentId;
67
- this.deletedAtUtcIso = deletedAtUtcIso;
68
- Error.captureStackTrace(this, DocumentDeletedError);
69
- }
70
- static isError(error) {
71
- return Error.isError(error) && error.name === "DocumentDeletedError";
72
- }
73
- };
74
- /**
75
- * Error thrown when an operation has an invalid signature.
76
- */
77
- var InvalidSignatureError = class InvalidSignatureError extends Error {
78
- documentId;
79
- reason;
80
- constructor(documentId, reason) {
81
- super(`Invalid signature in document ${documentId}: ${reason}`);
82
- this.name = "InvalidSignatureError";
83
- this.documentId = documentId;
84
- this.reason = reason;
85
- Error.captureStackTrace(this, InvalidSignatureError);
86
- }
87
- };
88
- /**
89
- * Error thrown when a document is not found (no operations exist for the document ID).
90
- */
91
- var DocumentNotFoundError = class DocumentNotFoundError extends Error {
92
- documentId;
93
- constructor(documentId) {
94
- super(`Document ${documentId} not found`);
95
- this.name = "DocumentNotFoundError";
96
- this.documentId = documentId;
97
- Error.captureStackTrace(this, DocumentNotFoundError);
98
- }
99
- static isError(error) {
100
- return Error.isError(error) && error.name === "DocumentNotFoundError";
101
- }
102
- };
103
- //#endregion
104
- //#region src/registry/errors.ts
105
- /**
106
- * Error thrown when a document model module is not found in the registry.
107
- */
108
- var ModuleNotFoundError = class extends Error {
109
- documentType;
110
- requestedVersion;
111
- constructor(documentType, version) {
112
- const versionSuffix = version !== void 0 ? ` version ${version}` : "";
113
- super(`Document model module not found for type: ${documentType}${versionSuffix}`);
114
- this.name = "ModuleNotFoundError";
115
- this.documentType = documentType;
116
- this.requestedVersion = version;
117
- }
118
- static isError(error) {
119
- return Error.isError(error) && error.name === "ModuleNotFoundError";
120
- }
121
- };
122
- /**
123
- * Error thrown when attempting to register a module that already exists.
124
- */
125
- var DuplicateModuleError = class extends Error {
126
- constructor(documentType, version) {
127
- const versionSuffix = version !== void 0 ? ` (version ${version})` : "";
128
- super(`Document model module already registered for type: ${documentType}${versionSuffix}`);
129
- this.name = "DuplicateModuleError";
130
- }
131
- static isError(error) {
132
- return Error.isError(error) && error.name === "DuplicateModuleError";
133
- }
134
- };
135
- /**
136
- * Error thrown when a module is invalid or malformed.
137
- */
138
- var InvalidModuleError = class extends Error {
139
- constructor(message) {
140
- super(`Invalid document model module: ${message}`);
141
- this.name = "InvalidModuleError";
142
- }
143
- };
144
- /**
145
- * Error thrown when attempting to register an upgrade manifest that already exists.
146
- */
147
- var DuplicateManifestError = class extends Error {
148
- constructor(documentType) {
149
- super(`Upgrade manifest already registered for type: ${documentType}`);
150
- this.name = "DuplicateManifestError";
151
- }
152
- static isError(error) {
153
- return Error.isError(error) && error.name === "DuplicateManifestError";
154
- }
155
- };
156
- /**
157
- * Error thrown when an upgrade manifest is not found.
158
- */
159
- var ManifestNotFoundError = class extends Error {
160
- constructor(documentType) {
161
- super(`Upgrade manifest not found for type: ${documentType}`);
162
- this.name = "ManifestNotFoundError";
163
- }
164
- };
165
- /**
166
- * Error thrown when a required upgrade transition is missing from the manifest.
167
- */
168
- var MissingUpgradeTransitionError = class extends Error {
169
- constructor(documentType, fromVersion, toVersion) {
170
- super(`Missing upgrade transition for ${documentType}: v${fromVersion} to v${toVersion}`);
171
- this.name = "MissingUpgradeTransitionError";
172
- }
173
- };
174
- /**
175
- * Error thrown when getUpgradeReducer is called with a non-single-step version increment.
176
- */
177
- var InvalidUpgradeStepError = class extends Error {
178
- constructor(documentType, fromVersion, toVersion) {
179
- super(`Invalid upgrade step for ${documentType}: must be single version increment, got v${fromVersion} to v${toVersion}`);
180
- this.name = "InvalidUpgradeStepError";
181
- }
182
- };
183
- //#endregion
184
- //#region src/cache/collection-membership-cache.ts
185
- var CollectionMembershipCache = class CollectionMembershipCache {
186
- cache = /* @__PURE__ */ new Map();
187
- constructor(operationIndex) {
188
- this.operationIndex = operationIndex;
189
- }
190
- withScopedIndex(operationIndex) {
191
- const scoped = new CollectionMembershipCache(operationIndex);
192
- scoped.cache = this.cache;
193
- return scoped;
194
- }
195
- async getCollectionsForDocuments(documentIds) {
196
- const result = {};
197
- const missing = [];
198
- for (const docId of documentIds) {
199
- const cached = this.cache.get(docId);
200
- if (cached !== void 0) result[docId] = cached;
201
- else missing.push(docId);
202
- }
203
- if (missing.length > 0) {
204
- const fromDb = await this.operationIndex.getCollectionsForDocuments(missing);
205
- for (const docId of missing) {
206
- const collections = fromDb[docId] ?? [];
207
- result[docId] = collections;
208
- this.cache.set(docId, collections);
209
- }
210
- }
211
- return result;
212
- }
213
- invalidate(documentId) {
214
- this.cache.delete(documentId);
215
- }
216
- };
217
- //#endregion
218
- //#region src/executor/util.ts
219
- /**
220
- * Creates a PHDocument from a CREATE_DOCUMENT action input.
221
- * Reconstructs the document header and initializes the base state.
222
- *
223
- * @param action - The CREATE_DOCUMENT action containing the document parameters
224
- * @returns A newly constructed PHDocument with initialized header and base state
225
- */
226
- function createDocumentFromAction(action) {
227
- const input = action.input;
228
- const header = createPresignedHeader();
229
- header.id = input.documentId;
230
- header.documentType = input.model;
231
- if (input.signing) {
232
- header.createdAtUtcIso = input.signing.createdAtUtcIso;
233
- header.lastModifiedAtUtcIso = input.signing.createdAtUtcIso;
234
- header.sig = {
235
- publicKey: input.signing.publicKey,
236
- nonce: input.signing.nonce
237
- };
238
- }
239
- if (input.slug !== void 0) header.slug = input.slug;
240
- if (!header.slug) header.slug = input.documentId;
241
- if (input.name !== void 0) header.name = input.name;
242
- if (input.branch !== void 0) header.branch = input.branch;
243
- if (input.meta !== void 0) header.meta = input.meta;
244
- if (input.protocolVersions !== void 0) header.protocolVersions = input.protocolVersions;
245
- const baseState = defaultBaseState();
246
- return {
247
- header,
248
- operations: {},
249
- state: baseState,
250
- initialState: baseState,
251
- clipboard: []
252
- };
253
- }
254
- /**
255
- * Calculate the next operation index for a specific scope.
256
- * Each scope maintains its own independent index sequence.
257
- *
258
- * Per-scope indexing means:
259
- * - Each scope (document, global, local, etc.) has independent indexes
260
- * - Indexes start at 0 for each scope
261
- * - Different scopes can have operations with the same index value
262
- *
263
- * This function uses header.revision which is populated by the cache/storage layer
264
- * and contains the next available index for each scope. This design avoids requiring
265
- * the full operation history to be loaded, which is crucial for snapshot-based caching.
266
- *
267
- * @param document - The document whose header.revision to inspect
268
- * @param scope - The scope to calculate the next index for
269
- * @returns The next available index in the specified scope
270
- */
271
- const getNextIndexForScope = (document, scope) => {
272
- return document.header.revision[scope] || 0;
273
- };
274
- /**
275
- * Creates an empty consistency token with no coordinates.
276
- * Used when a job is registered or fails without writing operations.
277
- *
278
- * @returns A consistency token with an empty coordinates array
279
- */
280
- function createEmptyConsistencyToken() {
281
- return {
282
- version: 1,
283
- createdAtUtcIso: (/* @__PURE__ */ new Date()).toISOString(),
284
- coordinates: []
285
- };
286
- }
287
- /**
288
- * Creates a consistency token from operations written during job execution.
289
- * Maps each operation to a consistency coordinate tracking (documentId, scope, branch, operationIndex).
290
- * If no operations are provided, returns an empty token.
291
- *
292
- * @param operationsWithContext - Array of operations with their execution context
293
- * @returns A consistency token representing all operations written
294
- */
295
- function createConsistencyToken(operationsWithContext) {
296
- if (operationsWithContext.length === 0) return createEmptyConsistencyToken();
297
- const coordinates = [];
298
- for (let i = 0; i < operationsWithContext.length; i++) {
299
- const opWithContext = operationsWithContext[i];
300
- coordinates.push({
301
- documentId: opWithContext.context.documentId,
302
- scope: opWithContext.context.scope,
303
- branch: opWithContext.context.branch,
304
- operationIndex: opWithContext.operation.index
305
- });
306
- }
307
- return {
308
- version: 1,
309
- createdAtUtcIso: (/* @__PURE__ */ new Date()).toISOString(),
310
- coordinates
311
- };
312
- }
313
- function createOperation(action, index, skip, context) {
314
- return {
315
- id: deriveOperationId(context.documentId, context.scope, context.branch, action.id),
316
- index,
317
- timestampUtcMs: action.timestampUtcMs || (/* @__PURE__ */ new Date()).toISOString(),
318
- hash: "",
319
- skip,
320
- action
321
- };
322
- }
323
- function updateDocumentRevision(document, scope, operationIndex) {
324
- document.header.revision = {
325
- ...document.header.revision,
326
- [scope]: operationIndex + 1
327
- };
328
- }
329
- function buildSuccessResult(job, operation, documentId, documentType, resultingState, startTime) {
330
- return {
331
- job,
332
- success: true,
333
- operations: [operation],
334
- operationsWithContext: [{
335
- operation,
336
- context: {
337
- documentId,
338
- scope: job.scope,
339
- branch: job.branch,
340
- documentType,
341
- resultingState,
342
- ordinal: 0
343
- }
344
- }],
345
- duration: Date.now() - startTime
346
- };
347
- }
348
- function buildErrorResult(job, error, startTime) {
349
- return {
350
- job,
351
- success: false,
352
- error,
353
- duration: Date.now() - startTime
354
- };
355
- }
356
- //#endregion
357
- //#region src/cache/lru/lru-tracker.ts
358
- var LRUNode = class {
359
- key;
360
- prev;
361
- next;
362
- constructor(key) {
363
- this.key = key;
364
- this.prev = void 0;
365
- this.next = void 0;
366
- }
367
- };
368
- var LRUTracker = class {
369
- map;
370
- head;
371
- tail;
372
- constructor() {
373
- this.map = /* @__PURE__ */ new Map();
374
- this.head = void 0;
375
- this.tail = void 0;
376
- }
377
- get size() {
378
- return this.map.size;
379
- }
380
- touch(key) {
381
- const node = this.map.get(key);
382
- if (node) this.moveToFront(node);
383
- else this.addToFront(key);
384
- }
385
- evict() {
386
- if (!this.tail) return;
387
- const key = this.tail.key;
388
- this.remove(key);
389
- return key;
390
- }
391
- remove(key) {
392
- const node = this.map.get(key);
393
- if (!node) return;
394
- this.removeNode(node);
395
- this.map.delete(key);
396
- }
397
- clear() {
398
- this.map.clear();
399
- this.head = void 0;
400
- this.tail = void 0;
401
- }
402
- addToFront(key) {
403
- const node = new LRUNode(key);
404
- this.map.set(key, node);
405
- if (!this.head) {
406
- this.head = node;
407
- this.tail = node;
408
- } else {
409
- node.next = this.head;
410
- this.head.prev = node;
411
- this.head = node;
412
- }
413
- }
414
- moveToFront(node) {
415
- if (node === this.head) return;
416
- this.removeNode(node);
417
- node.prev = void 0;
418
- node.next = this.head;
419
- if (this.head) this.head.prev = node;
420
- this.head = node;
421
- if (!this.tail) this.tail = node;
422
- }
423
- removeNode(node) {
424
- if (node.prev) node.prev.next = node.next;
425
- else this.head = node.next;
426
- if (node.next) node.next.prev = node.prev;
427
- else this.tail = node.prev;
428
- }
429
- };
430
- //#endregion
431
- //#region src/cache/document-meta-cache.ts
432
- /**
433
- * In-memory document metadata cache with LRU eviction.
434
- *
435
- * Caches PHDocumentState per (documentId, branch) key. On cache miss,
436
- * rebuilds from document scope operations. Provides an explicit cross-scope
437
- * contract for accessing document scope metadata.
438
- *
439
- * **Thread Safety:**
440
- * Not thread-safe. Designed for single-threaded job executor environment.
441
- */
442
- var DocumentMetaCache = class DocumentMetaCache {
443
- cache;
444
- lruTracker;
445
- operationStore;
446
- config;
447
- constructor(operationStore, config) {
448
- this.operationStore = operationStore;
449
- this.config = { maxDocuments: config.maxDocuments };
450
- this.cache = /* @__PURE__ */ new Map();
451
- this.lruTracker = new LRUTracker();
452
- }
453
- withScopedStore(operationStore) {
454
- const scoped = new DocumentMetaCache(operationStore, this.config);
455
- scoped.cache = this.cache;
456
- scoped.lruTracker = this.lruTracker;
457
- return scoped;
458
- }
459
- async startup() {
460
- return Promise.resolve();
461
- }
462
- async shutdown() {
463
- return Promise.resolve();
464
- }
465
- async getDocumentMeta(documentId, branch, signal) {
466
- if (signal?.aborted) throw new Error("Operation aborted");
467
- const key = this.makeKey(documentId, branch);
468
- const cached = this.cache.get(key);
469
- if (cached) {
470
- this.lruTracker.touch(key);
471
- return cached;
472
- }
473
- const meta = await this.rebuildLatest(documentId, branch, signal);
474
- this.putDocumentMeta(documentId, branch, meta);
475
- return meta;
476
- }
477
- async rebuildAtRevision(documentId, branch, targetRevision, signal) {
478
- if (signal?.aborted) throw new Error("Operation aborted");
479
- return this.rebuildFromOperations(documentId, branch, targetRevision, signal);
480
- }
481
- putDocumentMeta(documentId, branch, meta) {
482
- const key = this.makeKey(documentId, branch);
483
- if (!this.cache.has(key) && this.cache.size >= this.config.maxDocuments) {
484
- const evictKey = this.lruTracker.evict();
485
- if (evictKey) this.cache.delete(evictKey);
486
- }
487
- this.cache.set(key, structuredClone(meta));
488
- this.lruTracker.touch(key);
489
- }
490
- invalidate(documentId, branch) {
491
- let evicted = 0;
492
- if (branch === void 0) {
493
- for (const key of this.cache.keys()) if (key.startsWith(`${documentId}:`)) {
494
- this.cache.delete(key);
495
- this.lruTracker.remove(key);
496
- evicted++;
497
- }
498
- } else {
499
- const key = this.makeKey(documentId, branch);
500
- if (this.cache.has(key)) {
501
- this.cache.delete(key);
502
- this.lruTracker.remove(key);
503
- evicted = 1;
504
- }
505
- }
506
- return evicted;
507
- }
508
- clear() {
509
- this.cache.clear();
510
- this.lruTracker.clear();
511
- }
512
- makeKey(documentId, branch) {
513
- return `${documentId}:${branch}`;
514
- }
515
- async rebuildLatest(documentId, branch, signal) {
516
- return this.rebuildFromOperations(documentId, branch, void 0, signal);
517
- }
518
- async rebuildFromOperations(documentId, branch, targetRevision, signal) {
519
- const docScopeOps = await this.operationStore.getSince(documentId, "document", branch, -1, void 0, void 0, signal);
520
- if (docScopeOps.results.length === 0) throw new DocumentNotFoundError(documentId);
521
- const createOp = docScopeOps.results[0];
522
- if (createOp.action.type !== "CREATE_DOCUMENT") throw new Error(`Invalid document: first operation must be CREATE_DOCUMENT, found ${createOp.action.type}`);
523
- const createAction = createOp.action;
524
- const documentType = createAction.input.model;
525
- let document = createDocumentFromAction(createAction);
526
- let documentScopeRevision = 0;
527
- for (const op of docScopeOps.results) {
528
- if (targetRevision !== void 0 && op.index > targetRevision) break;
529
- documentScopeRevision = op.index;
530
- if (op.action.type === "UPGRADE_DOCUMENT") {
531
- const upgradeAction = op.action;
532
- document = applyUpgradeDocumentAction$1(document, upgradeAction);
533
- } else if (op.action.type === "DELETE_DOCUMENT") document = applyDeleteDocumentAction$1(document, op.action);
534
- }
535
- return {
536
- state: document.state.document,
537
- documentType,
538
- documentScopeRevision: documentScopeRevision + 1
539
- };
540
- }
541
- };
542
- var KyselyOperationIndexTxn = class {
543
- collections = [];
544
- collectionMemberships = [];
545
- collectionRemovals = [];
546
- operations = [];
547
- createCollection(collectionId) {
548
- this.collections.push(collectionId);
549
- }
550
- addToCollection(collectionId, documentId) {
551
- const lastOpIndex = this.operations.length - 1;
552
- if (lastOpIndex < 0) throw new Error("addToCollection must be called after write() - no operations in transaction");
553
- this.collectionMemberships.push({
554
- collectionId,
555
- documentId,
556
- operationIndex: lastOpIndex
557
- });
558
- }
559
- removeFromCollection(collectionId, documentId) {
560
- const lastOpIndex = this.operations.length - 1;
561
- if (lastOpIndex < 0) throw new Error("removeFromCollection must be called after write() - no operations in transaction");
562
- this.collectionRemovals.push({
563
- collectionId,
564
- documentId,
565
- operationIndex: lastOpIndex
566
- });
567
- }
568
- write(operations) {
569
- this.operations.push(...operations);
570
- }
571
- getCollections() {
572
- return this.collections;
573
- }
574
- getCollectionMembershipRecords() {
575
- return this.collectionMemberships;
576
- }
577
- getCollectionRemovals() {
578
- return this.collectionRemovals;
579
- }
580
- getOperations() {
581
- return this.operations;
582
- }
583
- };
584
- var KyselyOperationIndex = class KyselyOperationIndex {
585
- trx;
586
- constructor(db) {
587
- this.db = db;
588
- }
589
- get queryExecutor() {
590
- return this.trx ?? this.db;
591
- }
592
- withTransaction(trx) {
593
- const instance = new KyselyOperationIndex(this.db);
594
- instance.trx = trx;
595
- return instance;
596
- }
597
- start() {
598
- return new KyselyOperationIndexTxn();
599
- }
600
- async commit(txn, signal) {
601
- if (signal?.aborted) throw new Error("Operation aborted");
602
- const kyselyTxn = txn;
603
- if (this.trx) return this.executeCommit(this.trx, kyselyTxn);
604
- let resultOrdinals = [];
605
- await this.db.transaction().execute(async (trx) => {
606
- resultOrdinals = await this.executeCommit(trx, kyselyTxn);
607
- });
608
- return resultOrdinals;
609
- }
610
- async executeCommit(trx, kyselyTxn) {
611
- const collections = kyselyTxn.getCollections();
612
- const memberships = kyselyTxn.getCollectionMembershipRecords();
613
- const removals = kyselyTxn.getCollectionRemovals();
614
- const operations = kyselyTxn.getOperations();
615
- if (collections.length > 0) {
616
- const collectionRows = collections.map((collectionId) => ({
617
- documentId: collectionId,
618
- collectionId,
619
- joinedOrdinal: BigInt(0),
620
- leftOrdinal: null
621
- }));
622
- await trx.insertInto("document_collections").values(collectionRows).onConflict((oc) => oc.doNothing()).execute();
623
- }
624
- let operationOrdinals = [];
625
- if (operations.length > 0) {
626
- const operationRows = operations.map((op) => ({
627
- opId: op.id || "",
628
- documentId: op.documentId,
629
- documentType: op.documentType,
630
- scope: op.scope,
631
- branch: op.branch,
632
- timestampUtcMs: op.timestampUtcMs,
633
- index: op.index,
634
- skip: op.skip,
635
- hash: op.hash,
636
- action: op.action,
637
- sourceRemote: op.sourceRemote
638
- }));
639
- operationOrdinals = (await trx.insertInto("operation_index_operations").values(operationRows).returning("ordinal").execute()).map((row) => row.ordinal);
640
- }
641
- if (memberships.length > 0) for (const m of memberships) {
642
- const ordinal = operationOrdinals[m.operationIndex];
643
- await trx.insertInto("document_collections").values({
644
- documentId: m.documentId,
645
- collectionId: m.collectionId,
646
- joinedOrdinal: BigInt(ordinal),
647
- leftOrdinal: null
648
- }).onConflict((oc) => oc.columns(["documentId", "collectionId"]).doUpdateSet({
649
- joinedOrdinal: BigInt(ordinal),
650
- leftOrdinal: null
651
- })).execute();
652
- }
653
- if (removals.length > 0) for (const r of removals) {
654
- const ordinal = operationOrdinals[r.operationIndex];
655
- await trx.updateTable("document_collections").set({ leftOrdinal: BigInt(ordinal) }).where("collectionId", "=", r.collectionId).where("documentId", "=", r.documentId).where("leftOrdinal", "is", null).execute();
656
- }
657
- return operationOrdinals;
658
- }
659
- async find(collectionId, cursor, view, paging, signal) {
660
- if (signal?.aborted) throw new Error("Operation aborted");
661
- const outerCursor = cursor ?? -1;
662
- const limit = paging?.limit ?? 500;
663
- const pagingCursorOrdinal = paging?.cursor !== void 0 ? Number.parseInt(paging.cursor, 10) : -1;
664
- const buildBranch = (kind) => {
665
- let qb = this.queryExecutor.selectFrom("operation_index_operations as oi").innerJoin("document_collections as dc", "oi.documentId", "dc.documentId").selectAll("oi").select(["dc.documentId", "dc.collectionId"]).where("dc.collectionId", "=", collectionId).where(sql`(dc."leftOrdinal" IS NULL OR oi.ordinal < dc."leftOrdinal")`);
666
- if (kind === "joiner") qb = qb.where("dc.joinedOrdinal", ">", BigInt(outerCursor)).where("oi.ordinal", "<=", outerCursor);
667
- else qb = qb.where("oi.ordinal", ">", outerCursor);
668
- qb = qb.where("oi.ordinal", ">", pagingCursorOrdinal);
669
- if (view?.branch) qb = qb.where("oi.branch", "=", view.branch);
670
- if (view?.scopes && view.scopes.length > 0) qb = qb.where("oi.scope", "in", view.scopes);
671
- if (view?.excludeSourceRemote) qb = qb.where("oi.sourceRemote", "!=", view.excludeSourceRemote);
672
- return qb;
673
- };
674
- const rows = await buildBranch("joiner").unionAll(buildBranch("newOps")).orderBy("ordinal", "asc").limit(limit + 1).execute();
675
- let hasMore = false;
676
- let items = rows;
677
- if (rows.length > limit) {
678
- hasMore = true;
679
- items = rows.slice(0, limit);
680
- }
681
- const nextCursor = hasMore && items.length > 0 ? items[items.length - 1].ordinal.toString() : void 0;
682
- const cursorValue = paging?.cursor || "0";
683
- return {
684
- results: items.map((row) => this.rowToOperationIndexEntry(row)),
685
- options: {
686
- cursor: cursorValue,
687
- limit
688
- },
689
- nextCursor,
690
- next: hasMore ? () => this.find(collectionId, cursor, view, {
691
- cursor: nextCursor,
692
- limit
693
- }, signal) : void 0
694
- };
695
- }
696
- async get(documentId, view, paging, signal) {
697
- if (signal?.aborted) throw new Error("Operation aborted");
698
- const limit = paging?.limit ?? 500;
699
- let query = this.queryExecutor.selectFrom("operation_index_operations").selectAll().where("documentId", "=", documentId).orderBy("ordinal", "asc");
700
- if (view?.branch) query = query.where("branch", "=", view.branch);
701
- if (view?.scopes && view.scopes.length > 0) query = query.where("scope", "in", view.scopes);
702
- if (paging?.cursor) {
703
- const cursorOrdinal = Number.parseInt(paging.cursor, 10);
704
- query = query.where("ordinal", ">", cursorOrdinal);
705
- }
706
- query = query.limit(limit + 1);
707
- const rows = await query.execute();
708
- let hasMore = false;
709
- let items = rows;
710
- if (rows.length > limit) {
711
- hasMore = true;
712
- items = rows.slice(0, limit);
713
- }
714
- const nextCursor = hasMore && items.length > 0 ? items[items.length - 1].ordinal.toString() : void 0;
715
- const cursorValue = paging?.cursor || "0";
716
- return {
717
- results: items.map((row) => this.rowToOperationIndexEntry(row)),
718
- options: {
719
- cursor: cursorValue,
720
- limit
721
- },
722
- nextCursor,
723
- next: hasMore ? () => this.get(documentId, view, {
724
- cursor: nextCursor,
725
- limit
726
- }, signal) : void 0
727
- };
728
- }
729
- async getSinceOrdinal(ordinal, paging, signal) {
730
- if (signal?.aborted) throw new Error("Operation aborted");
731
- const limit = paging?.limit ?? 500;
732
- let query = this.queryExecutor.selectFrom("operation_index_operations").selectAll().where("ordinal", ">", ordinal).orderBy("ordinal", "asc");
733
- if (paging?.cursor) {
734
- const cursorOrdinal = Number.parseInt(paging.cursor, 10);
735
- query = query.where("ordinal", ">", cursorOrdinal);
736
- }
737
- query = query.limit(limit + 1);
738
- const rows = await query.execute();
739
- let hasMore = false;
740
- let items = rows;
741
- if (rows.length > limit) {
742
- hasMore = true;
743
- items = rows.slice(0, limit);
744
- }
745
- const nextCursor = hasMore && items.length > 0 ? items[items.length - 1].ordinal.toString() : void 0;
746
- const cursorValue = paging?.cursor || "0";
747
- return {
748
- results: items.map((row) => this.rowToOperationWithContext(row)),
749
- options: {
750
- cursor: cursorValue,
751
- limit
752
- },
753
- nextCursor,
754
- next: hasMore ? () => this.getSinceOrdinal(ordinal, {
755
- cursor: nextCursor,
756
- limit
757
- }, signal) : void 0
758
- };
759
- }
760
- rowToOperationWithContext(row) {
761
- return {
762
- operation: {
763
- index: row.index,
764
- timestampUtcMs: row.timestampUtcMs,
765
- hash: row.hash,
766
- skip: row.skip,
767
- action: row.action,
768
- id: row.opId
769
- },
770
- context: {
771
- documentId: row.documentId,
772
- documentType: row.documentType,
773
- scope: row.scope,
774
- branch: row.branch,
775
- ordinal: row.ordinal
776
- }
777
- };
778
- }
779
- rowToOperationIndexEntry(row) {
780
- return {
781
- ordinal: row.ordinal,
782
- documentId: row.documentId,
783
- documentType: row.documentType,
784
- branch: row.branch,
785
- scope: row.scope,
786
- index: row.index,
787
- timestampUtcMs: row.timestampUtcMs,
788
- hash: row.hash,
789
- skip: row.skip,
790
- action: row.action,
791
- id: row.opId,
792
- sourceRemote: row.sourceRemote
793
- };
794
- }
795
- async getLatestTimestampForCollection(collectionId, signal) {
796
- if (signal?.aborted) throw new Error("Operation aborted");
797
- return (await this.queryExecutor.selectFrom("operation_index_operations as oi").innerJoin("document_collections as dc", "oi.documentId", "dc.documentId").select("oi.timestampUtcMs").where("dc.collectionId", "=", collectionId).where(sql`(dc."leftOrdinal" IS NULL OR oi.ordinal < dc."leftOrdinal")`).orderBy("oi.ordinal", "desc").limit(1).executeTakeFirst())?.timestampUtcMs ?? null;
798
- }
799
- async getCollectionsForDocuments(documentIds) {
800
- if (documentIds.length === 0) return {};
801
- const rows = await this.queryExecutor.selectFrom("document_collections").select(["documentId", "collectionId"]).where("documentId", "in", documentIds).where("leftOrdinal", "is", null).execute();
802
- const result = {};
803
- for (const row of rows) {
804
- if (!(row.documentId in result)) result[row.documentId] = [];
805
- result[row.documentId].push(row.collectionId);
806
- }
807
- return result;
808
- }
809
- };
810
- //#endregion
811
- //#region src/cache/buffer/ring-buffer.ts
812
- /**
813
- * RingBuffer is a generic circular buffer implementation that stores a fixed number
814
- * of items. When the buffer is full, new items overwrite the oldest items.
815
- *
816
- * This implementation maintains O(1) time complexity for push operations and provides
817
- * items in chronological order (oldest to newest) via getAll().
818
- *
819
- * @template T - The type of items stored in the buffer
820
- */
821
- var RingBuffer = class {
822
- buffer;
823
- head = 0;
824
- size = 0;
825
- capacity;
826
- constructor(capacity) {
827
- if (capacity <= 0) throw new Error("Ring buffer capacity must be greater than 0");
828
- this.capacity = capacity;
829
- this.buffer = new Array(capacity);
830
- }
831
- /**
832
- * Adds an item to the buffer. If the buffer is full, overwrites the oldest item.
833
- *
834
- * @param item - The item to add
835
- */
836
- push(item) {
837
- const index = (this.head + this.size) % this.capacity;
838
- if (this.size < this.capacity) {
839
- this.buffer[index] = item;
840
- this.size++;
841
- } else {
842
- this.buffer[this.head] = item;
843
- this.head = (this.head + 1) % this.capacity;
844
- }
845
- }
846
- /**
847
- * Returns all items in the buffer in chronological order (oldest to newest).
848
- *
849
- * @returns Array of items in insertion order
850
- */
851
- getAll() {
852
- if (this.size === 0) return [];
853
- const result = [];
854
- for (let i = 0; i < this.size; i++) {
855
- const index = (this.head + i) % this.capacity;
856
- result.push(this.buffer[index]);
857
- }
858
- return result;
859
- }
860
- /**
861
- * Clears all items from the buffer.
862
- */
863
- clear() {
864
- this.buffer = new Array(this.capacity);
865
- this.head = 0;
866
- this.size = 0;
867
- }
868
- /**
869
- * Gets the current number of items in the buffer.
870
- */
871
- get length() {
872
- return this.size;
873
- }
874
- };
875
- //#endregion
876
- //#region src/cache/kysely-write-cache.ts
877
- function extractModuleVersion(doc) {
878
- const v = doc.state.document.version;
879
- return v === 0 ? void 0 : v;
880
- }
881
- /**
882
- * In-memory write cache with keyframe persistence for PHDocuments.
883
- *
884
- * Caches document snapshots in ring buffers with LRU eviction. On cache miss,
885
- * rebuilds documents from nearest keyframe or full operation history.
886
- *
887
- * **Performance Characteristics:**
888
- * - Cache hit: O(1) lookup in ring buffer
889
- * - Cold miss: O(n) where n is total operation count, or O(k) where k is operations since keyframe
890
- * - Warm miss: O(m) where m is operations since cached revision
891
- * - Eviction: O(1) for LRU tracking and removal
892
- *
893
- * **Thread Safety:**
894
- * Not thread-safe. Designed for single-threaded job executor environment.
895
- * External synchronization required for concurrent access across multiple executors.
896
- *
897
- * **Example:**
898
- * ```typescript
899
- * const cache = new KyselyWriteCache(
900
- * keyframeStore,
901
- * operationStore,
902
- * registry,
903
- * { maxDocuments: 1000, ringBufferSize: 10, keyframeInterval: 10 }
904
- * );
905
- *
906
- * await cache.startup();
907
- *
908
- * // Retrieve or rebuild document
909
- * const doc = await cache.getState(docId, docType, scope, branch, revision);
910
- *
911
- * // Cache result after job execution
912
- * cache.putState(docId, docType, scope, branch, newRevision, updatedDoc);
913
- *
914
- * await cache.shutdown();
915
- * ```
916
- */
917
- var KyselyWriteCache = class KyselyWriteCache {
918
- streams;
919
- lruTracker;
920
- keyframeStore;
921
- operationStore;
922
- registry;
923
- config;
924
- constructor(keyframeStore, operationStore, registry, config) {
925
- this.keyframeStore = keyframeStore;
926
- this.operationStore = operationStore;
927
- this.registry = registry;
928
- this.config = {
929
- maxDocuments: config.maxDocuments,
930
- ringBufferSize: config.ringBufferSize,
931
- keyframeInterval: config.keyframeInterval
932
- };
933
- this.streams = /* @__PURE__ */ new Map();
934
- this.lruTracker = new LRUTracker();
935
- }
936
- withScopedStores(operationStore, keyframeStore) {
937
- const scoped = new KyselyWriteCache(keyframeStore, operationStore, this.registry, this.config);
938
- scoped.streams = this.streams;
939
- scoped.lruTracker = this.lruTracker;
940
- return scoped;
941
- }
942
- /**
943
- * Initializes the write cache.
944
- * Currently a no-op as keyframe store lifecycle is managed externally.
945
- */
946
- async startup() {
947
- return Promise.resolve();
948
- }
949
- /**
950
- * Shuts down the write cache.
951
- * Currently a no-op as keyframe store lifecycle is managed externally.
952
- */
953
- async shutdown() {
954
- return Promise.resolve();
955
- }
956
- /**
957
- * Retrieves document state at a specific revision from cache or rebuilds it.
958
- *
959
- * Cache hit path: Returns cached snapshot if available (O(1))
960
- * Warm miss path: Rebuilds from cached base revision + incremental ops
961
- * Cold miss path: Rebuilds from keyframe or from scratch using all operations
962
- *
963
- * @param documentId - The document identifier
964
- * @param scope - The operation scope
965
- * @param branch - The operation branch
966
- * @param targetRevision - The target revision, or undefined for newest
967
- * @param signal - Optional abort signal to cancel the operation
968
- * @returns The document at the target revision
969
- * @throws {Error} "Operation aborted" if signal is aborted
970
- * @throws {ModuleNotFoundError} If document type not registered in registry
971
- * @throws {Error} "Failed to rebuild document" if operation store fails
972
- * @throws {Error} If reducer throws during operation application
973
- * @throws {Error} If document serialization fails
974
- */
975
- async getState(documentId, scope, branch, targetRevision, signal) {
976
- if (signal?.aborted) throw new Error("Operation aborted");
977
- const streamKey = this.makeStreamKey(documentId, scope, branch);
978
- const stream = this.streams.get(streamKey);
979
- if (stream) {
980
- const snapshots = stream.ringBuffer.getAll();
981
- if (targetRevision === void 0) {
982
- if (snapshots.length > 0) {
983
- const newest = snapshots[snapshots.length - 1];
984
- this.lruTracker.touch(streamKey);
985
- return newest.document;
986
- }
987
- } else {
988
- const exactMatch = snapshots.find((s) => s.revision === targetRevision);
989
- if (exactMatch) {
990
- this.lruTracker.touch(streamKey);
991
- return exactMatch.document;
992
- }
993
- const newestOlder = this.findNearestOlderSnapshot(snapshots, targetRevision);
994
- if (newestOlder) {
995
- const document = await this.warmMissRebuild(newestOlder.document, newestOlder.revision, documentId, scope, branch, targetRevision, signal);
996
- this.putState(documentId, scope, branch, targetRevision, document);
997
- this.lruTracker.touch(streamKey);
998
- return document;
999
- }
1000
- }
1001
- }
1002
- const document = await this.coldMissRebuild(documentId, scope, branch, targetRevision, signal);
1003
- let revision = targetRevision;
1004
- if (revision === void 0) revision = document.header.revision[scope] || 0;
1005
- this.putState(documentId, scope, branch, revision, document);
1006
- return document;
1007
- }
1008
- /**
1009
- * Stores a document snapshot in the cache at a specific revision.
1010
- *
1011
- * The cached document is a shallow copy of the input with its operation history
1012
- * truncated to the last operation per scope and its clipboard cleared. This keeps
1013
- * memory use and copy costs constant regardless of operation count. Consumers of
1014
- * getState() must not rely on the full operation history being present; the only
1015
- * guaranteed invariant is that operations[scope].at(-1) reflects the latest
1016
- * operation index for each scope.
1017
- *
1018
- * Updates LRU tracker and may evict least recently used stream if at capacity.
1019
- * Asynchronously persists keyframes at configured intervals (fire-and-forget).
1020
- *
1021
- * @param documentId - The document identifier
1022
- * @param scope - The operation scope
1023
- * @param branch - The operation branch
1024
- * @param revision - The revision number
1025
- * @param document - The document to cache
1026
- * @throws {Error} If document serialization fails
1027
- */
1028
- putState(documentId, scope, branch, revision, document) {
1029
- const streamKey = this.makeStreamKey(documentId, scope, branch);
1030
- const stream = this.getOrCreateStream(streamKey);
1031
- const snapshot = {
1032
- revision,
1033
- document: {
1034
- ...document,
1035
- operations: Object.fromEntries(Object.entries(document.operations).map(([k, ops]) => [k, ops.length ? [ops.at(-1)] : []])),
1036
- clipboard: []
1037
- }
1038
- };
1039
- stream.ringBuffer.push(snapshot);
1040
- if (this.isKeyframeRevision(revision)) this.keyframeStore.putKeyframe(documentId, scope, branch, revision, {
1041
- ...document,
1042
- operations: {},
1043
- clipboard: []
1044
- }).catch((err) => {
1045
- console.error(`Failed to persist keyframe ${documentId}@${revision}:`, err);
1046
- });
1047
- }
1048
- /**
1049
- * Invalidates cached document streams.
1050
- *
1051
- * Supports three invalidation scopes:
1052
- * - Document-level: invalidate(documentId) - removes all streams for document
1053
- * - Scope-level: invalidate(documentId, scope) - removes all branches for scope
1054
- * - Stream-level: invalidate(documentId, scope, branch) - removes specific stream
1055
- *
1056
- * @param documentId - The document identifier
1057
- * @param scope - Optional scope to narrow invalidation
1058
- * @param branch - Optional branch to narrow invalidation (requires scope)
1059
- * @returns The number of streams evicted
1060
- */
1061
- invalidate(documentId, scope, branch) {
1062
- let evicted = 0;
1063
- if (scope === void 0 && branch === void 0) {
1064
- for (const [key] of this.streams.entries()) if (key.startsWith(`${documentId}:`)) {
1065
- this.streams.delete(key);
1066
- this.lruTracker.remove(key);
1067
- evicted++;
1068
- }
1069
- } else if (scope !== void 0 && branch === void 0) {
1070
- for (const [key] of this.streams.entries()) if (key.startsWith(`${documentId}:${scope}:`)) {
1071
- this.streams.delete(key);
1072
- this.lruTracker.remove(key);
1073
- evicted++;
1074
- }
1075
- } else if (scope !== void 0 && branch !== void 0) {
1076
- const key = this.makeStreamKey(documentId, scope, branch);
1077
- if (this.streams.has(key)) {
1078
- this.streams.delete(key);
1079
- this.lruTracker.remove(key);
1080
- evicted = 1;
1081
- }
1082
- }
1083
- return evicted;
1084
- }
1085
- /**
1086
- * Clears the entire cache, removing all cached document streams.
1087
- * Resets LRU tracking state. This operation always succeeds.
1088
- */
1089
- clear() {
1090
- this.streams.clear();
1091
- this.lruTracker.clear();
1092
- }
1093
- /**
1094
- * Retrieves a specific stream for a document. Exposed on the implementation
1095
- * for testing, but not on the interface.
1096
- *
1097
- * @internal
1098
- */
1099
- getStream(documentId, scope, branch) {
1100
- const key = this.makeStreamKey(documentId, scope, branch);
1101
- return this.streams.get(key);
1102
- }
1103
- async findNearestKeyframe(documentId, scope, branch, targetRevision, signal) {
1104
- if (targetRevision === Number.MAX_SAFE_INTEGER || targetRevision <= 0) return;
1105
- return this.keyframeStore.findNearestKeyframe(documentId, scope, branch, targetRevision, signal);
1106
- }
1107
- async coldMissRebuild(documentId, scope, branch, targetRevision, signal) {
1108
- const effectiveTargetRevision = targetRevision || Number.MAX_SAFE_INTEGER;
1109
- const keyframe = await this.findNearestKeyframe(documentId, scope, branch, effectiveTargetRevision, signal);
1110
- let document;
1111
- let startRevision;
1112
- let documentType;
1113
- const validatedUpgrades = [];
1114
- if (keyframe) {
1115
- document = keyframe.document;
1116
- startRevision = keyframe.revision;
1117
- documentType = keyframe.document.header.documentType;
1118
- const docScopeOpsAfterKeyframe = await this.operationStore.getSince(documentId, "document", branch, keyframe.revision, void 0, void 0, signal);
1119
- for (const operation of docScopeOpsAfterKeyframe.results) if (operation.action.type === "UPGRADE_DOCUMENT") {
1120
- const upgradeAction = operation.action;
1121
- const fromVersion = upgradeAction.input.fromVersion;
1122
- const toVersion = upgradeAction.input.toVersion;
1123
- if (fromVersion > 0 && fromVersion < toVersion) {
1124
- let upgradePath;
1125
- try {
1126
- upgradePath = this.registry.computeUpgradePath(documentType, fromVersion, toVersion);
1127
- } catch (err) {
1128
- if (upgradeAction.input.initialState !== void 0) upgradePath = void 0;
1129
- else throw new Error(`Failed to rebuild document ${documentId}: no upgrade manifest for ${documentType} v${fromVersion}→v${toVersion} and no initialState snapshot. ${err instanceof Error ? err.message : String(err)}`, { cause: err });
1130
- }
1131
- validatedUpgrades.push({
1132
- fromVersion,
1133
- toVersion,
1134
- revision: upgradeAction.input.revision,
1135
- timestampUtcMs: operation.timestampUtcMs
1136
- });
1137
- document = applyUpgradeDocumentAction(document, upgradeAction, upgradePath);
1138
- }
1139
- } else if (operation.action.type === "DELETE_DOCUMENT") applyDeleteDocumentAction(document, operation.action);
1140
- } else {
1141
- startRevision = -1;
1142
- const createOpResult = await this.operationStore.getSince(documentId, "document", branch, -1, void 0, {
1143
- cursor: "0",
1144
- limit: 1
1145
- }, signal);
1146
- if (createOpResult.results.length === 0) throw new Error(`Failed to rebuild document ${documentId}: no CREATE_DOCUMENT operation found in document scope`);
1147
- const createOp = createOpResult.results[0];
1148
- if (createOp.action.type !== "CREATE_DOCUMENT") throw new Error(`Failed to rebuild document ${documentId}: first operation in document scope must be CREATE_DOCUMENT, found ${createOp.action.type}`);
1149
- const documentCreateAction = createOp.action;
1150
- documentType = documentCreateAction.input.model;
1151
- if (!documentType) throw new Error(`Failed to rebuild document ${documentId}: CREATE_DOCUMENT action missing model in input`);
1152
- document = createDocumentFromAction(documentCreateAction);
1153
- let docModule = this.registry.getModule(documentType, extractModuleVersion(document));
1154
- const docScopeOps = await this.operationStore.getSince(documentId, "document", branch, 0, void 0, void 0, signal);
1155
- for (const operation of docScopeOps.results) {
1156
- if (operation.index === 0) continue;
1157
- if (operation.action.type === "UPGRADE_DOCUMENT") {
1158
- const upgradeAction = operation.action;
1159
- const fromVersion = upgradeAction.input.fromVersion;
1160
- const toVersion = upgradeAction.input.toVersion;
1161
- let upgradePath;
1162
- if (fromVersion > 0 && fromVersion < toVersion) {
1163
- try {
1164
- upgradePath = this.registry.computeUpgradePath(documentType, fromVersion, toVersion);
1165
- } catch (err) {
1166
- if (upgradeAction.input.initialState !== void 0) upgradePath = void 0;
1167
- else throw new Error(`Failed to rebuild document ${documentId}: no upgrade manifest for ${documentType} v${fromVersion}→v${toVersion} and no initialState snapshot. ${err instanceof Error ? err.message : String(err)}`, { cause: err });
1168
- }
1169
- validatedUpgrades.push({
1170
- fromVersion,
1171
- toVersion,
1172
- revision: upgradeAction.input.revision,
1173
- timestampUtcMs: operation.timestampUtcMs
1174
- });
1175
- }
1176
- document = applyUpgradeDocumentAction(document, upgradeAction, upgradePath);
1177
- docModule = this.registry.getModule(documentType, extractModuleVersion(document));
1178
- } else if (operation.action.type === "DELETE_DOCUMENT") applyDeleteDocumentAction(document, operation.action);
1179
- else {
1180
- const protocolVersion = document.header.protocolVersions?.["base-reducer"] ?? 1;
1181
- document = docModule.reducer(document, operation.action, void 0, {
1182
- skip: operation.skip,
1183
- protocolVersion
1184
- });
1185
- }
1186
- }
1187
- }
1188
- const moduleCache = /* @__PURE__ */ new Map();
1189
- const getModuleCached = (version) => {
1190
- const key = version ?? 0;
1191
- let mod = moduleCache.get(key);
1192
- if (!mod) {
1193
- mod = this.registry.getModule(documentType, version);
1194
- moduleCache.set(key, mod);
1195
- }
1196
- return mod;
1197
- };
1198
- let cursor = void 0;
1199
- const pageSize = 100;
1200
- let hasMorePages;
1201
- do {
1202
- if (signal?.aborted) throw new Error("Operation aborted");
1203
- const paging = {
1204
- cursor: cursor || "0",
1205
- limit: pageSize
1206
- };
1207
- try {
1208
- const result = await this.operationStore.getSince(documentId, scope, branch, startRevision, void 0, paging, signal);
1209
- for (const operation of result.results) {
1210
- if (targetRevision !== void 0 && operation.index > targetRevision) break;
1211
- const moduleVersion = this.resolveModuleVersionForOp(operation.index, operation.timestampUtcMs, scope, validatedUpgrades, extractModuleVersion(document));
1212
- const protocolVersion = document.header.protocolVersions?.["base-reducer"] ?? 1;
1213
- document = getModuleCached(moduleVersion).reducer(document, operation.action, void 0, {
1214
- skip: operation.skip,
1215
- protocolVersion
1216
- });
1217
- }
1218
- const reachedTarget = targetRevision !== void 0 && result.results.some((op) => op.index >= targetRevision);
1219
- hasMorePages = Boolean(result.nextCursor) && !reachedTarget;
1220
- if (hasMorePages) cursor = result.nextCursor;
1221
- } catch (err) {
1222
- throw new Error(`Failed to rebuild document ${documentId}: ${err instanceof Error ? err.message : String(err)}`, { cause: err });
1223
- }
1224
- } while (hasMorePages);
1225
- const revisions = await this.operationStore.getRevisions(documentId, branch, signal);
1226
- document.header.revision = revisions.revision;
1227
- document.header.lastModifiedAtUtcIso = revisions.latestTimestamp;
1228
- return document;
1229
- }
1230
- /**
1231
- * Resolves which module version to use for a given operation in phase 2.
1232
- *
1233
- * Uses the validated-upgrade boundary rules from D7:
1234
- * - If `input.revision` is present: op.index < revision[scope] → before the upgrade boundary
1235
- * - Otherwise: timestamp fallback
1236
- * - Falls back to final module version when neither is decidable
1237
- */
1238
- resolveModuleVersionForOp(opIndex, opTimestamp, scope, validatedUpgrades, finalVersion) {
1239
- if (validatedUpgrades.length === 0) return finalVersion;
1240
- let currentVersion = validatedUpgrades[0]?.fromVersion;
1241
- for (const upgrade of validatedUpgrades) {
1242
- let beforeUpgrade;
1243
- if (upgrade.revision !== void 0) beforeUpgrade = opIndex < (upgrade.revision[scope] ?? 0);
1244
- else beforeUpgrade = opTimestamp < upgrade.timestampUtcMs;
1245
- if (beforeUpgrade) return currentVersion;
1246
- currentVersion = upgrade.toVersion;
1247
- }
1248
- return currentVersion;
1249
- }
1250
- async warmMissRebuild(baseDocument, baseRevision, documentId, scope, branch, targetRevision, signal) {
1251
- const documentType = baseDocument.header.documentType;
1252
- const docScopeNextIndex = baseDocument.header.revision["document"] ?? 0;
1253
- if ((await this.operationStore.getSince(documentId, "document", branch, docScopeNextIndex - 1, void 0, void 0, signal)).results.some((op) => op.action.type === "UPGRADE_DOCUMENT")) return this.coldMissRebuild(documentId, scope, branch, targetRevision, signal);
1254
- const module = this.registry.getModule(documentType, extractModuleVersion(baseDocument));
1255
- let document = baseDocument;
1256
- try {
1257
- const pagedResults = await this.operationStore.getSince(documentId, scope, branch, baseRevision, void 0, void 0, signal);
1258
- for (const operation of pagedResults.results) {
1259
- if (signal?.aborted) throw new Error("Operation aborted");
1260
- if (targetRevision !== void 0 && operation.index > targetRevision) break;
1261
- const protocolVersion = document.header.protocolVersions?.["base-reducer"] ?? 1;
1262
- document = module.reducer(document, operation.action, void 0, {
1263
- skip: operation.skip,
1264
- protocolVersion
1265
- });
1266
- if (targetRevision !== void 0 && operation.index === targetRevision) break;
1267
- }
1268
- } catch (err) {
1269
- throw new Error(`Failed to rebuild document ${documentId}: ${err instanceof Error ? err.message : String(err)}`, { cause: err });
1270
- }
1271
- const revisions = await this.operationStore.getRevisions(documentId, branch, signal);
1272
- document.header.revision = revisions.revision;
1273
- document.header.lastModifiedAtUtcIso = revisions.latestTimestamp;
1274
- return document;
1275
- }
1276
- findNearestOlderSnapshot(snapshots, targetRevision) {
1277
- let nearest = void 0;
1278
- for (const snapshot of snapshots) if (snapshot.revision < targetRevision) {
1279
- if (!nearest || snapshot.revision > nearest.revision) nearest = snapshot;
1280
- }
1281
- return nearest;
1282
- }
1283
- makeStreamKey(documentId, scope, branch) {
1284
- return `${documentId}:${scope}:${branch}`;
1285
- }
1286
- getOrCreateStream(key) {
1287
- let stream = this.streams.get(key);
1288
- if (!stream) {
1289
- if (this.streams.size >= this.config.maxDocuments) {
1290
- const evictKey = this.lruTracker.evict();
1291
- if (evictKey) this.streams.delete(evictKey);
1292
- }
1293
- stream = {
1294
- key,
1295
- ringBuffer: new RingBuffer(this.config.ringBufferSize)
1296
- };
1297
- this.streams.set(key, stream);
1298
- }
1299
- this.lruTracker.touch(key);
1300
- return stream;
1301
- }
1302
- isKeyframeRevision(revision) {
1303
- return revision > 0 && revision % this.config.keyframeInterval === 0;
1304
- }
1305
- };
1306
- //#endregion
1307
- //#region src/events/event-bus.ts
1308
- var EventBus = class {
1309
- eventTypeToSubscribers = /* @__PURE__ */ new Map();
1310
- subscribe(type, subscriber) {
1311
- let list = this.eventTypeToSubscribers.get(type);
1312
- if (!list) {
1313
- list = [];
1314
- this.eventTypeToSubscribers.set(type, list);
1315
- }
1316
- list.push(subscriber);
1317
- let done = false;
1318
- return () => {
1319
- if (done) return;
1320
- done = true;
1321
- const arr = this.eventTypeToSubscribers.get(type);
1322
- if (!arr) return;
1323
- const idx = arr.indexOf(subscriber);
1324
- if (idx !== -1) arr.splice(idx, 1);
1325
- if (arr.length === 0) this.eventTypeToSubscribers.delete(type);
1326
- };
1327
- }
1328
- async emit(type, data) {
1329
- const list = this.eventTypeToSubscribers.get(type);
1330
- if (!list || list.length === 0) return;
1331
- const snapshot = list.slice();
1332
- const errors = [];
1333
- for (const fn of snapshot) try {
1334
- await Promise.resolve(fn(type, data));
1335
- } catch (err) {
1336
- errors.push(err);
1337
- }
1338
- if (errors.length > 0) throw new EventBusAggregateError(errors);
1339
- }
1340
- };
1341
- //#endregion
1342
- //#region src/executor/execution-scope.ts
1343
- var DefaultExecutionScope = class {
1344
- constructor(operationStore, operationIndex, writeCache, documentMetaCache, collectionMembershipCache) {
1345
- this.operationStore = operationStore;
1346
- this.operationIndex = operationIndex;
1347
- this.writeCache = writeCache;
1348
- this.documentMetaCache = documentMetaCache;
1349
- this.collectionMembershipCache = collectionMembershipCache;
1350
- }
1351
- async run(fn, signal) {
1352
- signal?.throwIfAborted();
1353
- return fn({
1354
- operationStore: this.operationStore,
1355
- operationIndex: this.operationIndex,
1356
- writeCache: this.writeCache,
1357
- documentMetaCache: this.documentMetaCache,
1358
- collectionMembershipCache: this.collectionMembershipCache
1359
- });
1360
- }
1361
- };
1362
- var KyselyExecutionScope = class {
1363
- constructor(db, operationStore, operationIndex, keyframeStore, writeCache, documentMetaCache, collectionMembershipCache) {
1364
- this.db = db;
1365
- this.operationStore = operationStore;
1366
- this.operationIndex = operationIndex;
1367
- this.keyframeStore = keyframeStore;
1368
- this.writeCache = writeCache;
1369
- this.documentMetaCache = documentMetaCache;
1370
- this.collectionMembershipCache = collectionMembershipCache;
1371
- }
1372
- async run(fn, signal) {
1373
- signal?.throwIfAborted();
1374
- return this.db.transaction().execute(async (trx) => {
1375
- const scopedOperationStore = this.operationStore.withTransaction(trx);
1376
- const scopedOperationIndex = this.operationIndex.withTransaction(trx);
1377
- const scopedKeyframeStore = this.keyframeStore.withTransaction(trx);
1378
- return fn({
1379
- operationStore: scopedOperationStore,
1380
- operationIndex: scopedOperationIndex,
1381
- writeCache: this.writeCache.withScopedStores(scopedOperationStore, scopedKeyframeStore),
1382
- documentMetaCache: this.documentMetaCache.withScopedStore(scopedOperationStore),
1383
- collectionMembershipCache: this.collectionMembershipCache.withScopedIndex(scopedOperationIndex)
1384
- });
1385
- });
1386
- }
1387
- };
1388
- //#endregion
1389
- //#region src/utils/reshuffle.ts
1390
- const STRICT_ORDER_ACTION_TYPES = new Set([
1391
- "CREATE_DOCUMENT",
1392
- "DELETE_DOCUMENT",
1393
- "UPGRADE_DOCUMENT",
1394
- "ADD_RELATIONSHIP",
1395
- "REMOVE_RELATIONSHIP",
1396
- "UPDATE_RELATIONSHIP",
1397
- "ADD_FOLDER",
1398
- "UPDATE_FOLDER",
1399
- "REMOVE_FOLDER"
1400
- ]);
1401
- /**
1402
- * Reshuffles operations by timestamp, then applies deterministic tie-breaking.
1403
- * Used for merging concurrent operations from different branches.
1404
- *
1405
- * For strict document-structure actions (e.g., CREATE_DOCUMENT/UPGRADE_DOCUMENT),
1406
- * logical index (index - skip) is prioritized to preserve causal replay order.
1407
- *
1408
- * For other actions, action ID is prioritized to ensure a canonical cross-reactor order
1409
- * for concurrent operations that may have diverged local indices due to prior reshuffles.
1410
- * Logical index and operation ID are then used as deterministic tie-breakers.
1411
- *
1412
- * Example:
1413
- * [0:0, 1:0, 2:0, A3:0, A4:0, A5:0] + [0:0, 1:0, 2:0, B3:0, B4:2, B5:0]
1414
- * GC => [0:0, 1:0, 2:0, A3:0, A4:0, A5:0] + [0:0, 1:0, B4:2, B5:0]
1415
- * Split => [0:0, 1:0] + [2:0, A3:0, A4:0, A5:0] + [B4:2, B5:0]
1416
- * Reshuffle(6:4) => [6:4, 7:0, 8:0, 9:0, 10:0, 11:0]
1417
- * merge => [0:0, 1:0, 6:4, 7:0, 8:0, 9:0, 10:0, 11:0]
1418
- */
1419
- function reshuffleByTimestamp(startIndex, opsA, opsB) {
1420
- return [...opsA, ...opsB].sort((a, b) => {
1421
- const timestampDiff = new Date(a.timestampUtcMs).getTime() - new Date(b.timestampUtcMs).getTime();
1422
- if (timestampDiff !== 0) return timestampDiff;
1423
- const shouldPrioritizeLogicalIndex = STRICT_ORDER_ACTION_TYPES.has(a.action?.type ?? "") || STRICT_ORDER_ACTION_TYPES.has(b.action?.type ?? "");
1424
- const logicalIndexDiff = a.index - a.skip - (b.index - b.skip);
1425
- if (shouldPrioritizeLogicalIndex) {
1426
- if (logicalIndexDiff !== 0) return logicalIndexDiff;
1427
- }
1428
- const actionIdDiff = (a.action?.id ?? "").localeCompare(b.action?.id ?? "");
1429
- if (actionIdDiff !== 0) return actionIdDiff;
1430
- if (!shouldPrioritizeLogicalIndex && logicalIndexDiff !== 0) return logicalIndexDiff;
1431
- return a.id.localeCompare(b.id);
1432
- }).map((op, i) => ({
1433
- ...op,
1434
- index: startIndex.index + i,
1435
- skip: i === 0 ? startIndex.skip : 0
1436
- }));
1437
- }
1438
- //#endregion
1439
- //#region src/cache/operation-index-types.ts
1440
- const DRIVE_COLLECTION_PREFIX = "drive.";
1441
- /**
1442
- * Identifies the collection a remote synchronizes. Collections are drive-level
1443
- * abstractions (document-drive and reactor-drive), so a collection id is the
1444
- * drive document id plus the branch it scopes to rather than an opaque string.
1445
- *
1446
- * The canonical string form (`drive.${branch}.${driveId}`) is produced only by
1447
- * `key` and parsed only by `fromKey`; that string is the wire and storage
1448
- * representation and is byte-for-byte identical to the legacy
1449
- * `driveCollectionId(branch, driveId)` output, so existing `document_collections`
1450
- * rows and persisted remotes remain valid without migration.
1451
- */
1452
- var DriveCollectionId = class DriveCollectionId {
1453
- constructor(driveId, branch) {
1454
- this.driveId = driveId;
1455
- this.branch = branch;
1456
- }
1457
- static forDrive(driveId, branch = "main") {
1458
- return new DriveCollectionId(driveId, branch);
1459
- }
1460
- /**
1461
- * The single deserializer for the wire/storage form. `branch` may contain
1462
- * dots, while `driveId` is a dot-free document id, so the drive id is the
1463
- * final dot-delimited segment.
1464
- */
1465
- static fromKey(key) {
1466
- if (!key.startsWith(DRIVE_COLLECTION_PREFIX)) throw new Error(`Unsupported collection id: ${key}`);
1467
- const rest = key.slice(6);
1468
- const lastDot = rest.lastIndexOf(".");
1469
- if (lastDot === -1 || lastDot === rest.length - 1) throw new Error(`Malformed drive collection id: ${key}`);
1470
- return new DriveCollectionId(rest.slice(lastDot + 1), rest.slice(0, lastDot));
1471
- }
1472
- get key() {
1473
- return `${DRIVE_COLLECTION_PREFIX}${this.branch}.${this.driveId}`;
1474
- }
1475
- toString() {
1476
- return this.key;
1477
- }
1478
- equals(other) {
1479
- return this.driveId === other.driveId && this.branch === other.branch;
1480
- }
1481
- };
1482
- //#endregion
1483
- //#region src/executor/document-action-handler.ts
1484
- var DocumentActionHandler = class {
1485
- constructor(registry, logger, driveContainerTypes) {
1486
- this.registry = registry;
1487
- this.logger = logger;
1488
- this.driveContainerTypes = driveContainerTypes;
1489
- }
1490
- async execute(job, action, startTime, indexTxn, stores, skip = 0, sourceRemote = "", signal) {
1491
- switch (action.type) {
1492
- case "CREATE_DOCUMENT": return this.executeCreate(job, action, startTime, indexTxn, stores, skip, sourceRemote, signal);
1493
- case "DELETE_DOCUMENT": return this.executeDelete(job, action, startTime, indexTxn, stores, sourceRemote, signal);
1494
- case "UPGRADE_DOCUMENT": return this.executeUpgrade(job, action, startTime, indexTxn, stores, skip, sourceRemote, signal);
1495
- case "ADD_RELATIONSHIP": return this.executeAddRelationship(job, action, startTime, indexTxn, stores, sourceRemote, signal);
1496
- case "REMOVE_RELATIONSHIP": return this.executeRemoveRelationship(job, action, startTime, indexTxn, stores, sourceRemote, signal);
1497
- case "UPDATE_RELATIONSHIP": return this.executeUpdateRelationship(job, action, startTime, indexTxn, stores, sourceRemote, signal);
1498
- default: return buildErrorResult(job, /* @__PURE__ */ new Error(`Unknown document action type: ${action.type}`), startTime);
1499
- }
1500
- }
1501
- async executeCreate(job, action, startTime, indexTxn, stores, skip = 0, sourceRemote = "", signal) {
1502
- if (job.scope !== "document") return {
1503
- job,
1504
- success: false,
1505
- error: /* @__PURE__ */ new Error(`CREATE_DOCUMENT must be in "document" scope, got "${job.scope}"`),
1506
- duration: Date.now() - startTime
1507
- };
1508
- const document = createDocumentFromAction(action);
1509
- let operation = createOperation(action, 0, skip, {
1510
- documentId: document.header.id,
1511
- scope: job.scope,
1512
- branch: job.branch
1513
- });
1514
- const resultingStateObj = {
1515
- header: document.header,
1516
- ...document.state
1517
- };
1518
- const resultingState = JSON.stringify(resultingStateObj);
1519
- const writeResult = await this.writeOperationToStore(document.header.id, document.header.documentType, job.scope, job.branch, operation, job, startTime, stores, signal);
1520
- if (!Array.isArray(writeResult)) return writeResult;
1521
- operation = writeResult[0];
1522
- updateDocumentRevision(document, job.scope, operation.index);
1523
- document.operations = {
1524
- ...document.operations,
1525
- [job.scope]: [...document.operations[job.scope] ?? [], operation]
1526
- };
1527
- stores.writeCache.putState(document.header.id, job.scope, job.branch, operation.index, document);
1528
- indexTxn.write([{
1529
- ...operation,
1530
- documentId: document.header.id,
1531
- documentType: document.header.documentType,
1532
- branch: job.branch,
1533
- scope: job.scope,
1534
- sourceRemote
1535
- }]);
1536
- if (this.driveContainerTypes.has(document.header.documentType)) {
1537
- const collectionId = DriveCollectionId.forDrive(document.header.id, job.branch).key;
1538
- indexTxn.createCollection(collectionId);
1539
- indexTxn.addToCollection(collectionId, document.header.id);
1540
- }
1541
- stores.documentMetaCache.putDocumentMeta(document.header.id, job.branch, {
1542
- state: document.state.document,
1543
- documentType: document.header.documentType,
1544
- documentScopeRevision: 1
1545
- });
1546
- return buildSuccessResult(job, operation, document.header.id, document.header.documentType, resultingState, startTime);
1547
- }
1548
- async executeDelete(job, action, startTime, indexTxn, stores, sourceRemote = "", signal) {
1549
- const input = action.input;
1550
- if (!input.documentId) return buildErrorResult(job, /* @__PURE__ */ new Error("DELETE_DOCUMENT action requires a documentId in input"), startTime);
1551
- const documentId = input.documentId;
1552
- let document;
1553
- try {
1554
- document = await stores.writeCache.getState(documentId, job.scope, job.branch, void 0, signal);
1555
- } catch (error) {
1556
- return buildErrorResult(job, /* @__PURE__ */ new Error(`Failed to fetch document before deletion: ${error instanceof Error ? error.message : String(error)}`), startTime);
1557
- }
1558
- const documentState = document.state.document;
1559
- if (documentState.isDeleted) return buildErrorResult(job, new DocumentDeletedError(documentId, documentState.deletedAtUtcIso), startTime);
1560
- let operation = createOperation(action, getNextIndexForScope(document, job.scope), 0, {
1561
- documentId,
1562
- scope: job.scope,
1563
- branch: job.branch
1564
- });
1565
- applyDeleteDocumentAction$1(document, action);
1566
- const resultingStateObj = {
1567
- header: document.header,
1568
- document: document.state.document
1569
- };
1570
- const resultingState = JSON.stringify(resultingStateObj);
1571
- const writeResult = await this.writeOperationToStore(documentId, document.header.documentType, job.scope, job.branch, operation, job, startTime, stores, signal);
1572
- if (!Array.isArray(writeResult)) return writeResult;
1573
- operation = writeResult[0];
1574
- updateDocumentRevision(document, job.scope, operation.index);
1575
- document.operations = {
1576
- ...document.operations,
1577
- [job.scope]: [...document.operations[job.scope] ?? [], operation]
1578
- };
1579
- stores.writeCache.putState(documentId, job.scope, job.branch, operation.index, document);
1580
- indexTxn.write([{
1581
- ...operation,
1582
- documentId,
1583
- documentType: document.header.documentType,
1584
- branch: job.branch,
1585
- scope: job.scope,
1586
- sourceRemote
1587
- }]);
1588
- stores.documentMetaCache.putDocumentMeta(documentId, job.branch, {
1589
- state: document.state.document,
1590
- documentType: document.header.documentType,
1591
- documentScopeRevision: operation.index + 1
1592
- });
1593
- return buildSuccessResult(job, operation, documentId, document.header.documentType, resultingState, startTime);
1594
- }
1595
- async executeUpgrade(job, action, startTime, indexTxn, stores, skip = 0, sourceRemote = "", signal) {
1596
- const input = action.input;
1597
- if (!input.documentId) return buildErrorResult(job, /* @__PURE__ */ new Error("UPGRADE_DOCUMENT action requires a documentId in input"), startTime);
1598
- const documentId = input.documentId;
1599
- const fromVersion = input.fromVersion;
1600
- const toVersion = input.toVersion;
1601
- let document;
1602
- try {
1603
- document = await stores.writeCache.getState(documentId, job.scope, job.branch, void 0, signal);
1604
- } catch (error) {
1605
- return buildErrorResult(job, /* @__PURE__ */ new Error(`Failed to fetch document for upgrade: ${error instanceof Error ? error.message : String(error)}`), startTime);
1606
- }
1607
- const documentState = document.state.document;
1608
- if (documentState.isDeleted) return buildErrorResult(job, new DocumentDeletedError(documentId, documentState.deletedAtUtcIso), startTime);
1609
- const nextIndex = getNextIndexForScope(document, job.scope);
1610
- let upgradePath;
1611
- if (fromVersion > 0 && fromVersion < toVersion) try {
1612
- upgradePath = this.registry.computeUpgradePath(document.header.documentType, fromVersion, toVersion);
1613
- } catch (error) {
1614
- return buildErrorResult(job, error instanceof Error ? error : new Error(String(error)), startTime);
1615
- }
1616
- if (fromVersion === toVersion && fromVersion > 0) return {
1617
- job,
1618
- success: true,
1619
- operations: [],
1620
- operationsWithContext: [],
1621
- duration: Date.now() - startTime
1622
- };
1623
- try {
1624
- document = applyUpgradeDocumentAction$1(document, action, upgradePath);
1625
- } catch (error) {
1626
- return buildErrorResult(job, error instanceof Error ? error : new Error(String(error)), startTime);
1627
- }
1628
- let operation = createOperation(action, nextIndex, skip, {
1629
- documentId,
1630
- scope: job.scope,
1631
- branch: job.branch
1632
- });
1633
- const resultingStateObj = {
1634
- header: document.header,
1635
- ...document.state
1636
- };
1637
- const resultingState = JSON.stringify(resultingStateObj);
1638
- const writeResult = await this.writeOperationToStore(documentId, document.header.documentType, job.scope, job.branch, operation, job, startTime, stores, signal);
1639
- if (!Array.isArray(writeResult)) return writeResult;
1640
- operation = writeResult[0];
1641
- updateDocumentRevision(document, job.scope, operation.index);
1642
- document.operations = {
1643
- ...document.operations,
1644
- [job.scope]: [...document.operations[job.scope] ?? [], operation]
1645
- };
1646
- stores.writeCache.putState(documentId, job.scope, job.branch, operation.index, document);
1647
- indexTxn.write([{
1648
- ...operation,
1649
- documentId,
1650
- documentType: document.header.documentType,
1651
- branch: job.branch,
1652
- scope: job.scope,
1653
- sourceRemote
1654
- }]);
1655
- stores.documentMetaCache.putDocumentMeta(documentId, job.branch, {
1656
- state: document.state.document,
1657
- documentType: document.header.documentType,
1658
- documentScopeRevision: operation.index + 1
1659
- });
1660
- return buildSuccessResult(job, operation, documentId, document.header.documentType, resultingState, startTime);
1661
- }
1662
- executeAddRelationship(job, action, startTime, indexTxn, stores, sourceRemote = "", signal) {
1663
- return this.withRelationshipAction("ADD_RELATIONSHIP", job, action, startTime, indexTxn, stores, sourceRemote, signal, (input) => input.sourceId === input.targetId ? /* @__PURE__ */ new Error("ADD_RELATIONSHIP: sourceId and targetId cannot be the same (self-relationships not allowed)") : null, ({ indexTxn: txn, stores: s, sourceDoc, input, job: j }) => {
1664
- if (this.driveContainerTypes.has(sourceDoc.header.documentType)) {
1665
- const collectionId = DriveCollectionId.forDrive(input.sourceId, j.branch).key;
1666
- txn.addToCollection(collectionId, input.targetId);
1667
- s.collectionMembershipCache.invalidate(input.targetId);
1668
- }
1669
- });
1670
- }
1671
- executeRemoveRelationship(job, action, startTime, indexTxn, stores, sourceRemote = "", signal) {
1672
- return this.withRelationshipAction("REMOVE_RELATIONSHIP", job, action, startTime, indexTxn, stores, sourceRemote, signal, null, ({ indexTxn: txn, stores: s, sourceDoc, input, job: j }) => {
1673
- if (this.driveContainerTypes.has(sourceDoc.header.documentType)) {
1674
- const collectionId = DriveCollectionId.forDrive(input.sourceId, j.branch).key;
1675
- txn.removeFromCollection(collectionId, input.targetId);
1676
- s.collectionMembershipCache.invalidate(input.targetId);
1677
- }
1678
- });
1679
- }
1680
- executeUpdateRelationship(job, action, startTime, indexTxn, stores, sourceRemote = "", signal) {
1681
- return this.withRelationshipAction("UPDATE_RELATIONSHIP", job, action, startTime, indexTxn, stores, sourceRemote, signal, null, null);
1682
- }
1683
- async withRelationshipAction(actionTypeName, job, action, startTime, indexTxn, stores, sourceRemote, signal, preValidate, postWrite) {
1684
- if (job.scope !== "document") return buildErrorResult(job, /* @__PURE__ */ new Error(`${actionTypeName} must be in "document" scope, got "${job.scope}"`), startTime);
1685
- const input = action.input;
1686
- if (!input.sourceId || !input.targetId || !input.relationshipType) return buildErrorResult(job, /* @__PURE__ */ new Error(`${actionTypeName} action requires sourceId, targetId, and relationshipType in input`), startTime);
1687
- if (preValidate !== null) {
1688
- const validationError = preValidate(input);
1689
- if (validationError !== null) return buildErrorResult(job, validationError, startTime);
1690
- }
1691
- let sourceDoc;
1692
- try {
1693
- sourceDoc = await stores.writeCache.getState(input.sourceId, "document", job.branch, void 0, signal);
1694
- } catch (error) {
1695
- return buildErrorResult(job, /* @__PURE__ */ new Error(`${actionTypeName}: source document ${input.sourceId} not found: ${error instanceof Error ? error.message : String(error)}`), startTime);
1696
- }
1697
- let operation = createOperation(action, getNextIndexForScope(sourceDoc, job.scope), 0, {
1698
- documentId: input.sourceId,
1699
- scope: job.scope,
1700
- branch: job.branch
1701
- });
1702
- const writeResult = await this.writeOperationToStore(input.sourceId, sourceDoc.header.documentType, job.scope, job.branch, operation, job, startTime, stores, signal);
1703
- if (!Array.isArray(writeResult)) return writeResult;
1704
- operation = writeResult[0];
1705
- sourceDoc.header.lastModifiedAtUtcIso = operation.timestampUtcMs || (/* @__PURE__ */ new Date()).toISOString();
1706
- updateDocumentRevision(sourceDoc, job.scope, operation.index);
1707
- sourceDoc.operations = {
1708
- ...sourceDoc.operations,
1709
- [job.scope]: [...sourceDoc.operations[job.scope] ?? [], operation]
1710
- };
1711
- const scopeState = sourceDoc.state[job.scope];
1712
- const resultingStateObj = {
1713
- header: structuredClone(sourceDoc.header),
1714
- [job.scope]: scopeState === void 0 ? {} : structuredClone(scopeState)
1715
- };
1716
- const resultingState = JSON.stringify(resultingStateObj);
1717
- stores.writeCache.putState(input.sourceId, job.scope, job.branch, operation.index, sourceDoc);
1718
- indexTxn.write([{
1719
- ...operation,
1720
- documentId: input.sourceId,
1721
- documentType: sourceDoc.header.documentType,
1722
- branch: job.branch,
1723
- scope: job.scope,
1724
- sourceRemote
1725
- }]);
1726
- if (postWrite !== null) postWrite({
1727
- indexTxn,
1728
- stores,
1729
- sourceDoc,
1730
- input,
1731
- job
1732
- });
1733
- stores.documentMetaCache.putDocumentMeta(input.sourceId, job.branch, {
1734
- state: sourceDoc.state.document,
1735
- documentType: sourceDoc.header.documentType,
1736
- documentScopeRevision: operation.index + 1
1737
- });
1738
- return buildSuccessResult(job, operation, input.sourceId, sourceDoc.header.documentType, resultingState, startTime);
1739
- }
1740
- async writeOperationToStore(documentId, documentType, scope, branch, operation, job, startTime, stores, signal) {
1741
- let storedOperations;
1742
- try {
1743
- storedOperations = await stores.operationStore.apply(documentId, documentType, scope, branch, operation.index, (txn) => {
1744
- txn.addOperations(operation);
1745
- }, signal);
1746
- } catch (error) {
1747
- this.logger.error("Error writing @Operation to IOperationStore: @Error", operation, error);
1748
- stores.writeCache.invalidate(documentId, scope, branch);
1749
- return {
1750
- job,
1751
- success: false,
1752
- error: /* @__PURE__ */ new Error(`Failed to write operation to IOperationStore: ${error instanceof Error ? error.message : String(error)}`),
1753
- duration: Date.now() - startTime
1754
- };
1755
- }
1756
- return storedOperations;
1757
- }
1758
- };
1759
- //#endregion
1760
- //#region src/executor/signature-verifier.ts
1761
- var SignatureVerifier = class {
1762
- constructor(verifier) {
1763
- this.verifier = verifier;
1764
- }
1765
- async verifyActions(documentId, branch, actions) {
1766
- if (!this.verifier) return;
1767
- for (const action of actions) {
1768
- const signer = action.context?.signer;
1769
- if (!signer) continue;
1770
- if (signer.signatures.length === 0) throw new InvalidSignatureError(documentId, `Action ${action.id} has signer but no signatures`);
1771
- const publicKey = signer.app.key;
1772
- let isValid;
1773
- try {
1774
- const tempOperation = {
1775
- id: deriveOperationId(documentId, action.scope, branch, action.id),
1776
- index: 0,
1777
- timestampUtcMs: action.timestampUtcMs || (/* @__PURE__ */ new Date()).toISOString(),
1778
- hash: "",
1779
- skip: 0,
1780
- action
1781
- };
1782
- isValid = await this.verifier(tempOperation, publicKey);
1783
- } catch (error) {
1784
- const errorMessage = error instanceof Error ? error.message : String(error);
1785
- throw new InvalidSignatureError(documentId, `Action ${action.id} verification failed: ${errorMessage}`);
1786
- }
1787
- if (!isValid) throw new InvalidSignatureError(documentId, `Action ${action.id} signature verification returned false`);
1788
- }
1789
- }
1790
- async verifyOperations(documentId, operations) {
1791
- if (!this.verifier) return;
1792
- for (let i = 0; i < operations.length; i++) {
1793
- const operation = operations[i];
1794
- const signer = operation.action.context?.signer;
1795
- if (!signer) continue;
1796
- if (signer.signatures.length === 0) throw new InvalidSignatureError(documentId, `Operation ${operation.id} at index ${operation.index} has signer but no signatures`);
1797
- const publicKey = signer.app.key;
1798
- let isValid;
1799
- try {
1800
- isValid = await this.verifier(operation, publicKey);
1801
- } catch (error) {
1802
- const errorMessage = error instanceof Error ? error.message : String(error);
1803
- throw new InvalidSignatureError(documentId, `Operation ${operation.id} at index ${operation.index} verification failed: ${errorMessage}`);
1804
- }
1805
- if (!isValid) throw new InvalidSignatureError(documentId, `Operation ${operation.id} at index ${operation.index} signature verification returned false`);
1806
- }
1807
- }
1808
- };
1809
- //#endregion
1810
- //#region src/executor/simple-job-executor.ts
1811
- const MAX_SKIP_THRESHOLD = 1e3;
1812
- const ISO_TIMESTAMP_REGEX = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?Z$/;
1813
- function isValidISOTimestamp(value) {
1814
- if (!ISO_TIMESTAMP_REGEX.test(value)) return false;
1815
- return !isNaN(new Date(value).getTime());
1816
- }
1817
- const documentScopeActions = [
1818
- "CREATE_DOCUMENT",
1819
- "DELETE_DOCUMENT",
1820
- "UPGRADE_DOCUMENT",
1821
- "ADD_RELATIONSHIP",
1822
- "REMOVE_RELATIONSHIP",
1823
- "UPDATE_RELATIONSHIP"
1824
- ];
1825
- /**
1826
- * Simple job executor that processes a job by applying actions through document model reducers.
1827
- */
1828
- var SimpleJobExecutor = class {
1829
- config;
1830
- signatureVerifierModule;
1831
- documentActionHandler;
1832
- executionScope;
1833
- constructor(logger, registry, operationStore, eventBus, writeCache, operationIndex, documentMetaCache, collectionMembershipCache, driveContainerTypes, config, signatureVerifier, executionScope) {
1834
- this.logger = logger;
1835
- this.registry = registry;
1836
- this.operationStore = operationStore;
1837
- this.eventBus = eventBus;
1838
- this.writeCache = writeCache;
1839
- this.operationIndex = operationIndex;
1840
- this.documentMetaCache = documentMetaCache;
1841
- this.collectionMembershipCache = collectionMembershipCache;
1842
- this.driveContainerTypes = driveContainerTypes;
1843
- this.config = {
1844
- maxSkipThreshold: config.maxSkipThreshold ?? MAX_SKIP_THRESHOLD,
1845
- maxConcurrency: config.maxConcurrency ?? 1,
1846
- jobTimeoutMs: config.jobTimeoutMs ?? 3e4,
1847
- retryBaseDelayMs: config.retryBaseDelayMs ?? 100,
1848
- retryMaxDelayMs: config.retryMaxDelayMs ?? 5e3,
1849
- yieldDeadlineMs: config.yieldDeadlineMs ?? 50
1850
- };
1851
- this.signatureVerifierModule = new SignatureVerifier(signatureVerifier);
1852
- this.documentActionHandler = new DocumentActionHandler(registry, logger, driveContainerTypes);
1853
- this.executionScope = executionScope ?? new DefaultExecutionScope(operationStore, operationIndex, writeCache, documentMetaCache, collectionMembershipCache);
1854
- }
1855
- /**
1856
- * Execute a single job by applying all its actions through the appropriate reducers.
1857
- * Actions are processed sequentially in order.
1858
- */
1859
- async executeJob(job, signal) {
1860
- const startTime = Date.now();
1861
- const touchedCacheEntries = [];
1862
- let pendingEvent;
1863
- let result;
1864
- try {
1865
- result = await this.executionScope.run(async (stores) => {
1866
- const indexTxn = stores.operationIndex.start();
1867
- if (job.kind === "load") {
1868
- const loadResult = await this.executeLoadJob(job, startTime, indexTxn, stores, signal);
1869
- if (loadResult.success && loadResult.operationsWithContext) {
1870
- for (const owc of loadResult.operationsWithContext) touchedCacheEntries.push({
1871
- documentId: owc.context.documentId,
1872
- scope: owc.context.scope,
1873
- branch: owc.context.branch
1874
- });
1875
- const ordinals = await stores.operationIndex.commit(indexTxn, signal);
1876
- for (let i = 0; i < loadResult.operationsWithContext.length; i++) loadResult.operationsWithContext[i].context.ordinal = ordinals[i];
1877
- const collectionMemberships = loadResult.operationsWithContext.length > 0 ? await this.getCollectionMembershipsForOperations(loadResult.operationsWithContext, stores) : {};
1878
- pendingEvent = {
1879
- jobId: job.id,
1880
- operations: loadResult.operationsWithContext,
1881
- jobMeta: job.meta,
1882
- collectionMemberships
1883
- };
1884
- }
1885
- return loadResult;
1886
- }
1887
- const actionResult = await this.processActions(job, job.actions, startTime, indexTxn, stores, void 0, void 0, "", signal);
1888
- if (!actionResult.success) return {
1889
- job,
1890
- success: false,
1891
- error: actionResult.error,
1892
- duration: Date.now() - startTime
1893
- };
1894
- if (actionResult.operationsWithContext.length > 0) for (const owc of actionResult.operationsWithContext) touchedCacheEntries.push({
1895
- documentId: owc.context.documentId,
1896
- scope: owc.context.scope,
1897
- branch: owc.context.branch
1898
- });
1899
- const ordinals = await stores.operationIndex.commit(indexTxn, signal);
1900
- if (actionResult.operationsWithContext.length > 0) {
1901
- for (let i = 0; i < actionResult.operationsWithContext.length; i++) actionResult.operationsWithContext[i].context.ordinal = ordinals[i];
1902
- const collectionMemberships = await this.getCollectionMembershipsForOperations(actionResult.operationsWithContext, stores);
1903
- pendingEvent = {
1904
- jobId: job.id,
1905
- operations: actionResult.operationsWithContext,
1906
- jobMeta: job.meta,
1907
- collectionMemberships
1908
- };
1909
- }
1910
- return {
1911
- job,
1912
- success: true,
1913
- operations: actionResult.generatedOperations,
1914
- operationsWithContext: actionResult.operationsWithContext,
1915
- duration: Date.now() - startTime
1916
- };
1917
- }, signal);
1918
- } catch (error) {
1919
- for (const entry of touchedCacheEntries) {
1920
- this.writeCache.invalidate(entry.documentId, entry.scope, entry.branch);
1921
- this.documentMetaCache.invalidate(entry.documentId, entry.branch);
1922
- }
1923
- throw error;
1924
- }
1925
- if (pendingEvent) this.eventBus.emit(ReactorEventTypes.JOB_WRITE_READY, pendingEvent).catch((error) => {
1926
- this.logger.error("Failed to emit JOB_WRITE_READY event: @Event : @Error", pendingEvent, error);
1927
- });
1928
- return result;
1929
- }
1930
- async getCollectionMembershipsForOperations(operations, stores) {
1931
- const documentIds = [...new Set(operations.map((op) => op.context.documentId))];
1932
- return stores.collectionMembershipCache.getCollectionsForDocuments(documentIds);
1933
- }
1934
- async processActions(job, actions, startTime, indexTxn, stores, skipValues, sourceOperations, sourceRemote = "", signal) {
1935
- const generatedOperations = [];
1936
- const operationsWithContext = [];
1937
- try {
1938
- await this.signatureVerifierModule.verifyActions(job.documentId, job.branch, actions);
1939
- } catch (error) {
1940
- return {
1941
- success: false,
1942
- generatedOperations,
1943
- operationsWithContext,
1944
- error: error instanceof Error ? error : new Error(String(error))
1945
- };
1946
- }
1947
- for (const action of actions) if (action.timestampUtcMs && !isValidISOTimestamp(action.timestampUtcMs)) return {
1948
- success: false,
1949
- generatedOperations,
1950
- operationsWithContext,
1951
- error: /* @__PURE__ */ new Error(`Invalid timestamp "${action.timestampUtcMs}" on action ${action.type} (id: ${action.id})`)
1952
- };
1953
- let lastYield = performance.now();
1954
- for (let actionIndex = 0; actionIndex < actions.length; actionIndex++) {
1955
- const action = actions[actionIndex];
1956
- const skip = skipValues?.[actionIndex] ?? 0;
1957
- const sourceOperation = sourceOperations?.[actionIndex];
1958
- const result = documentScopeActions.includes(action.type) ? await this.documentActionHandler.execute(job, action, startTime, indexTxn, stores, skip, sourceRemote, signal) : await this.executeRegularAction(job, action, startTime, indexTxn, stores, skip, sourceOperation, sourceRemote, signal);
1959
- const error = this.accumulateResultOrReturnError(result, generatedOperations, operationsWithContext);
1960
- if (error !== null) return {
1961
- success: false,
1962
- generatedOperations,
1963
- operationsWithContext,
1964
- error: error.error
1965
- };
1966
- if (performance.now() - lastYield > this.config.yieldDeadlineMs) {
1967
- await yieldToMain();
1968
- lastYield = performance.now();
1969
- if (signal?.aborted) return {
1970
- success: false,
1971
- generatedOperations,
1972
- operationsWithContext,
1973
- error: /* @__PURE__ */ new Error("Aborted")
1974
- };
1975
- }
1976
- }
1977
- return {
1978
- success: true,
1979
- generatedOperations,
1980
- operationsWithContext
1981
- };
1982
- }
1983
- async executeRegularAction(job, action, startTime, indexTxn, stores, skip = 0, sourceOperation, sourceRemote = "", signal) {
1984
- let docMeta;
1985
- try {
1986
- docMeta = await stores.documentMetaCache.getDocumentMeta(job.documentId, job.branch, signal);
1987
- } catch (error) {
1988
- return buildErrorResult(job, error instanceof Error ? error : new Error(String(error)), startTime);
1989
- }
1990
- if (docMeta.state.isDeleted) return buildErrorResult(job, new DocumentDeletedError(job.documentId, docMeta.state.deletedAtUtcIso), startTime);
1991
- if (isUndoRedo(action) || action.type === "PRUNE" || action.type === "NOOP" && skip > 0) stores.writeCache.invalidate(job.documentId, job.scope, job.branch);
1992
- let document;
1993
- try {
1994
- document = await stores.writeCache.getState(job.documentId, job.scope, job.branch, void 0, signal);
1995
- } catch (error) {
1996
- return buildErrorResult(job, error instanceof Error ? error : new Error(String(error)), startTime);
1997
- }
1998
- let module;
1999
- try {
2000
- const moduleVersion = docMeta.state.version === 0 ? void 0 : docMeta.state.version;
2001
- module = this.registry.getModule(document.header.documentType, moduleVersion);
2002
- } catch (error) {
2003
- return buildErrorResult(job, error instanceof Error ? error : new Error(String(error)), startTime);
2004
- }
2005
- let updatedDocument;
2006
- try {
2007
- const protocolVersion = document.header.protocolVersions?.["base-reducer"] ?? 1;
2008
- const reducerOptions = sourceOperation ? {
2009
- skip,
2010
- branch: job.branch,
2011
- replayOptions: { operation: sourceOperation },
2012
- protocolVersion
2013
- } : {
2014
- skip,
2015
- branch: job.branch,
2016
- protocolVersion
2017
- };
2018
- updatedDocument = module.reducer(document, action, void 0, reducerOptions);
2019
- } catch (error) {
2020
- const contextMessage = `Failed to apply action to document:\n Action type: ${action.type}\n Document ID: ${job.documentId}\n Document type: ${document.header.documentType}\n Scope: ${job.scope}\n Original error: ${error instanceof Error ? error.message : String(error)}`;
2021
- const enhancedError = new Error(contextMessage);
2022
- if (error instanceof Error && error.stack) enhancedError.stack = `${contextMessage}\n\nOriginal stack trace:\n${error.stack}`;
2023
- return buildErrorResult(job, enhancedError, startTime);
2024
- }
2025
- const scope = job.scope;
2026
- const operations = updatedDocument.operations[scope];
2027
- if (operations.length === 0) return buildErrorResult(job, /* @__PURE__ */ new Error("No operation generated from action"), startTime);
2028
- const newOperation = operations[operations.length - 1];
2029
- if (!isUndoRedo(action)) newOperation.skip = skip;
2030
- const resultingState = JSON.stringify({
2031
- ...updatedDocument.state,
2032
- header: updatedDocument.header
2033
- });
2034
- let storedOperations;
2035
- try {
2036
- storedOperations = await stores.operationStore.apply(job.documentId, document.header.documentType, scope, job.branch, newOperation.index, (txn) => {
2037
- txn.addOperations(newOperation);
2038
- }, signal);
2039
- } catch (error) {
2040
- this.logger.error("Error writing @Operation to IOperationStore: @Error", newOperation, error);
2041
- stores.writeCache.invalidate(job.documentId, scope, job.branch);
2042
- return {
2043
- job,
2044
- success: false,
2045
- error: /* @__PURE__ */ new Error(`Failed to write operation to IOperationStore: ${error instanceof Error ? error.message : String(error)}`),
2046
- duration: Date.now() - startTime
2047
- };
2048
- }
2049
- const storedOperation = storedOperations[0];
2050
- updatedDocument.header.revision = {
2051
- ...updatedDocument.header.revision,
2052
- [scope]: storedOperation.index + 1
2053
- };
2054
- stores.writeCache.putState(job.documentId, scope, job.branch, storedOperation.index, updatedDocument);
2055
- indexTxn.write([{
2056
- ...storedOperation,
2057
- documentId: job.documentId,
2058
- documentType: document.header.documentType,
2059
- branch: job.branch,
2060
- scope,
2061
- sourceRemote
2062
- }]);
2063
- return {
2064
- job,
2065
- success: true,
2066
- operations: [storedOperation],
2067
- operationsWithContext: [{
2068
- operation: storedOperation,
2069
- context: {
2070
- documentId: job.documentId,
2071
- scope,
2072
- branch: job.branch,
2073
- documentType: document.header.documentType,
2074
- resultingState,
2075
- ordinal: 0
2076
- }
2077
- }],
2078
- duration: Date.now() - startTime
2079
- };
2080
- }
2081
- async executeLoadJob(job, startTime, indexTxn, stores, signal) {
2082
- if (job.operations.length === 0) return buildErrorResult(job, /* @__PURE__ */ new Error("Load job must include at least one operation"), startTime);
2083
- let docMeta;
2084
- try {
2085
- docMeta = await stores.documentMetaCache.getDocumentMeta(job.documentId, job.branch, signal);
2086
- } catch {}
2087
- if (docMeta?.state.isDeleted) return buildErrorResult(job, new DocumentDeletedError(job.documentId, docMeta.state.deletedAtUtcIso), startTime);
2088
- const scope = job.scope;
2089
- let latestRevision;
2090
- try {
2091
- latestRevision = (await stores.operationStore.getRevisions(job.documentId, job.branch, signal)).revision[scope] ?? 0;
2092
- } catch {
2093
- latestRevision = 0;
2094
- }
2095
- for (const operation of job.operations) if (operation.timestampUtcMs && !isValidISOTimestamp(operation.timestampUtcMs)) return {
2096
- job,
2097
- success: false,
2098
- error: /* @__PURE__ */ new Error(`Invalid timestamp "${operation.timestampUtcMs}" on operation (index: ${operation.index})`),
2099
- duration: Date.now() - startTime
2100
- };
2101
- let minIncomingIndex = Number.POSITIVE_INFINITY;
2102
- let minIncomingTimestamp = job.operations[0]?.timestampUtcMs || "";
2103
- for (const operation of job.operations) {
2104
- minIncomingIndex = Math.min(minIncomingIndex, operation.index);
2105
- const ts = operation.timestampUtcMs || "";
2106
- if (ts < minIncomingTimestamp) minIncomingTimestamp = ts;
2107
- }
2108
- let conflictingOps;
2109
- try {
2110
- conflictingOps = (await stores.operationStore.getConflicting(job.documentId, scope, job.branch, minIncomingTimestamp, void 0, signal)).results;
2111
- } catch {
2112
- conflictingOps = [];
2113
- }
2114
- let allOpsFromMinConflictingIndex = conflictingOps;
2115
- if (conflictingOps.length > 0) {
2116
- const minConflictingIndex = Math.min(...conflictingOps.map((op) => op.index));
2117
- try {
2118
- allOpsFromMinConflictingIndex = (await stores.operationStore.getSince(job.documentId, scope, job.branch, minConflictingIndex - 1, void 0, void 0, signal)).results;
2119
- } catch {
2120
- allOpsFromMinConflictingIndex = conflictingOps;
2121
- }
2122
- }
2123
- const incomingActionIds = new Set(job.operations.map((op) => op.action.id));
2124
- const nonSupersededOps = conflictingOps.filter((op) => {
2125
- if (op.index < minIncomingIndex && !incomingActionIds.has(op.action.id)) return false;
2126
- for (const laterOp of allOpsFromMinConflictingIndex) if (laterOp.index > op.index && laterOp.skip > 0) {
2127
- if (laterOp.index - laterOp.skip <= op.index) return false;
2128
- }
2129
- return true;
2130
- });
2131
- const existingOpsToReshuffle = nonSupersededOps;
2132
- if (existingOpsToReshuffle.length > this.config.maxSkipThreshold) return {
2133
- job,
2134
- success: false,
2135
- error: /* @__PURE__ */ new Error(`Excessive reshuffle detected: existing op count of ${existingOpsToReshuffle.length} exceeds threshold of ${this.config.maxSkipThreshold}. This indicates a significant divergence between local and incoming operations.`),
2136
- duration: Date.now() - startTime
2137
- };
2138
- let skipCount = existingOpsToReshuffle.length;
2139
- if (existingOpsToReshuffle.length > 0) {
2140
- let minLogicalIndex = Number.POSITIVE_INFINITY;
2141
- for (const op of existingOpsToReshuffle) {
2142
- const logical = op.index - op.skip;
2143
- if (logical < minLogicalIndex) minLogicalIndex = logical;
2144
- }
2145
- const logicalSkip = latestRevision - minLogicalIndex;
2146
- if (logicalSkip > skipCount) skipCount = logicalSkip;
2147
- }
2148
- const existingActionIds = new Set(nonSupersededOps.map((op) => op.action.id));
2149
- const seenIncomingActionIds = /* @__PURE__ */ new Set();
2150
- const incomingOpsToApply = job.operations.filter((op) => {
2151
- if (existingActionIds.has(op.action.id)) return false;
2152
- if (seenIncomingActionIds.has(op.action.id)) return false;
2153
- seenIncomingActionIds.add(op.action.id);
2154
- return true;
2155
- });
2156
- if (incomingOpsToApply.length === 0) return {
2157
- job,
2158
- success: true,
2159
- operations: [],
2160
- operationsWithContext: [],
2161
- duration: Date.now() - startTime
2162
- };
2163
- const reshuffledOperations = existingOpsToReshuffle.length === 0 && skipCount === 0 ? incomingOpsToApply.slice().sort((a, b) => a.index - b.index).map((operation, i) => ({
2164
- ...operation,
2165
- index: latestRevision + i
2166
- })) : reshuffleByTimestamp({
2167
- index: latestRevision,
2168
- skip: skipCount
2169
- }, existingOpsToReshuffle, incomingOpsToApply.map((operation) => ({
2170
- ...operation,
2171
- id: operation.id
2172
- })));
2173
- for (const operation of reshuffledOperations) if (operation.action.type === "NOOP") operation.skip = 1;
2174
- const actions = reshuffledOperations.map((operation) => operation.action);
2175
- const skipValues = reshuffledOperations.map((operation) => operation.skip);
2176
- const effectiveSourceRemote = skipCount > 0 ? "" : job.meta.sourceRemote || "";
2177
- const result = await this.processActions(job, actions, startTime, indexTxn, stores, skipValues, reshuffledOperations, effectiveSourceRemote, signal);
2178
- if (!result.success) return {
2179
- job,
2180
- success: false,
2181
- error: result.error,
2182
- duration: Date.now() - startTime
2183
- };
2184
- stores.writeCache.invalidate(job.documentId, scope, job.branch);
2185
- if (scope === "document") stores.documentMetaCache.invalidate(job.documentId, job.branch);
2186
- return {
2187
- job,
2188
- success: true,
2189
- operations: result.generatedOperations,
2190
- operationsWithContext: result.operationsWithContext,
2191
- duration: Date.now() - startTime
2192
- };
2193
- }
2194
- accumulateResultOrReturnError(result, generatedOperations, operationsWithContext) {
2195
- if (!result.success) return result;
2196
- if (result.operations && result.operations.length > 0) generatedOperations.push(...result.operations);
2197
- if (result.operationsWithContext) operationsWithContext.push(...result.operationsWithContext);
2198
- return null;
2199
- }
2200
- };
2201
- //#endregion
2202
- //#region src/registry/implementation.ts
2203
- /**
2204
- * In-memory implementation of the IDocumentModelRegistry interface.
2205
- * Manages document model modules with version-aware storage and upgrade manifest support.
2206
- */
2207
- var DocumentModelRegistry = class {
2208
- modules = [];
2209
- manifests = [];
2210
- registerModules(...modules) {
2211
- return modules.map((module) => {
2212
- try {
2213
- const documentType = module.documentModel.global.id;
2214
- const version = module.version ?? 1;
2215
- for (let i = 0; i < this.modules.length; i++) {
2216
- const existing = this.modules[i];
2217
- const existingType = existing.documentModel.global.id;
2218
- const existingVersion = existing.version ?? 1;
2219
- if (existingType === documentType && existingVersion === version) throw new DuplicateModuleError(documentType, version);
2220
- }
2221
- this.modules.push(module);
2222
- return {
2223
- status: "success",
2224
- item: module
2225
- };
2226
- } catch (error) {
2227
- return {
2228
- status: "error",
2229
- item: module,
2230
- error: error instanceof Error ? error : new Error(String(error))
2231
- };
2232
- }
2233
- });
2234
- }
2235
- unregisterModules(...documentTypes) {
2236
- let allFound = true;
2237
- for (const documentType of documentTypes) {
2238
- if (!this.modules.some((m) => m.documentModel.global.id === documentType)) allFound = false;
2239
- this.modules = this.modules.filter((m) => m.documentModel.global.id !== documentType);
2240
- }
2241
- return allFound;
2242
- }
2243
- getModule(documentType, version) {
2244
- let latestModule;
2245
- let latestVersion = -1;
2246
- for (let i = 0; i < this.modules.length; i++) {
2247
- const module = this.modules[i];
2248
- const moduleType = module.documentModel.global.id;
2249
- const moduleVersion = module.version ?? 1;
2250
- if (moduleType === documentType) {
2251
- if (version !== void 0 && moduleVersion === version) return module;
2252
- if (moduleVersion > latestVersion) {
2253
- latestModule = module;
2254
- latestVersion = moduleVersion;
2255
- }
2256
- }
2257
- }
2258
- if (version === void 0 && latestModule !== void 0) return latestModule;
2259
- throw new ModuleNotFoundError(documentType, version);
2260
- }
2261
- getAllModules() {
2262
- return [...this.modules];
2263
- }
2264
- clear() {
2265
- this.modules = [];
2266
- this.manifests = [];
2267
- }
2268
- getSupportedVersions(documentType) {
2269
- const versions = [];
2270
- for (const module of this.modules) if (module.documentModel.global.id === documentType) versions.push(module.version ?? 1);
2271
- if (versions.length === 0) throw new ModuleNotFoundError(documentType);
2272
- return versions.sort((a, b) => a - b);
2273
- }
2274
- getLatestVersion(documentType) {
2275
- let latest = -1;
2276
- let found = false;
2277
- for (const module of this.modules) if (module.documentModel.global.id === documentType) {
2278
- found = true;
2279
- const version = module.version ?? 1;
2280
- if (version > latest) latest = version;
2281
- }
2282
- if (!found) throw new ModuleNotFoundError(documentType);
2283
- return latest;
2284
- }
2285
- registerUpgradeManifests(...manifestsToRegister) {
2286
- return manifestsToRegister.map((manifestToRegister) => {
2287
- try {
2288
- if (!manifestToRegister.documentType) throw new Error("Upgrade manifest is missing a documentType");
2289
- for (const registeredManifest of this.manifests) if (registeredManifest.documentType === manifestToRegister.documentType) throw new DuplicateManifestError(manifestToRegister.documentType);
2290
- this.manifests.push(manifestToRegister);
2291
- return {
2292
- status: "success",
2293
- item: manifestToRegister
2294
- };
2295
- } catch (error) {
2296
- return {
2297
- status: "error",
2298
- item: manifestToRegister,
2299
- error: error instanceof Error ? error : new Error(String(error))
2300
- };
2301
- }
2302
- });
2303
- }
2304
- unregisterUpgradeManifests(...documentTypes) {
2305
- let allFound = true;
2306
- for (const documentType of documentTypes) {
2307
- if (!this.manifests.some((m) => m.documentType === documentType)) allFound = false;
2308
- this.manifests = this.manifests.filter((m) => m.documentType !== documentType);
2309
- }
2310
- return allFound;
2311
- }
2312
- getUpgradeManifest(documentType) {
2313
- for (let i = 0; i < this.manifests.length; i++) if (this.manifests[i].documentType === documentType) return this.manifests[i];
2314
- throw new ManifestNotFoundError(documentType);
2315
- }
2316
- computeUpgradePath(documentType, fromVersion, toVersion) {
2317
- if (fromVersion === toVersion) return [];
2318
- if (toVersion < fromVersion) throw new DowngradeNotSupportedError(documentType, fromVersion, toVersion);
2319
- const manifest = this.getUpgradeManifest(documentType);
2320
- const path = [];
2321
- for (let v = fromVersion + 1; v <= toVersion; v++) {
2322
- const key = `v${v}`;
2323
- if (!(key in manifest.upgrades)) throw new MissingUpgradeTransitionError(documentType, v - 1, v);
2324
- const transition = manifest.upgrades[key];
2325
- path.push(transition);
2326
- }
2327
- return path;
2328
- }
2329
- getUpgradeReducer(documentType, fromVersion, toVersion) {
2330
- if (toVersion !== fromVersion + 1) throw new InvalidUpgradeStepError(documentType, fromVersion, toVersion);
2331
- const manifest = this.getUpgradeManifest(documentType);
2332
- const key = `v${toVersion}`;
2333
- if (!(key in manifest.upgrades)) throw new MissingUpgradeTransitionError(documentType, fromVersion, toVersion);
2334
- return manifest.upgrades[key].upgradeReducer;
2335
- }
2336
- };
2337
- //#endregion
2338
- //#region src/storage/kysely/keyframe-store.ts
2339
- var KyselyKeyframeStore = class KyselyKeyframeStore {
2340
- trx;
2341
- constructor(db) {
2342
- this.db = db;
2343
- }
2344
- get queryExecutor() {
2345
- return this.trx ?? this.db;
2346
- }
2347
- withTransaction(trx) {
2348
- const instance = new KyselyKeyframeStore(this.db);
2349
- instance.trx = trx;
2350
- return instance;
2351
- }
2352
- async putKeyframe(documentId, scope, branch, revision, document, signal) {
2353
- if (signal?.aborted) throw new Error("Operation aborted");
2354
- await this.queryExecutor.insertInto("Keyframe").values({
2355
- documentId,
2356
- documentType: document.header.documentType,
2357
- scope,
2358
- branch,
2359
- revision,
2360
- document
2361
- }).onConflict((oc) => oc.columns([
2362
- "documentId",
2363
- "scope",
2364
- "branch",
2365
- "revision"
2366
- ]).doUpdateSet({ document })).execute();
2367
- }
2368
- async findNearestKeyframe(documentId, scope, branch, targetRevision, signal) {
2369
- if (signal?.aborted) throw new Error("Operation aborted");
2370
- const row = await this.queryExecutor.selectFrom("Keyframe").selectAll().where("documentId", "=", documentId).where("scope", "=", scope).where("branch", "=", branch).where("revision", "<=", targetRevision).orderBy("revision", "desc").limit(1).executeTakeFirst();
2371
- if (!row) return;
2372
- return {
2373
- revision: row.revision,
2374
- document: row.document
2375
- };
2376
- }
2377
- async listKeyframes(documentId, scope, branch, signal) {
2378
- if (signal?.aborted) throw new Error("Operation aborted");
2379
- let query = this.queryExecutor.selectFrom("Keyframe").selectAll().where("documentId", "=", documentId).orderBy("revision", "asc");
2380
- if (scope !== void 0) query = query.where("scope", "=", scope);
2381
- if (branch !== void 0) query = query.where("branch", "=", branch);
2382
- return (await query.execute()).map((row) => ({
2383
- scope: row.scope,
2384
- branch: row.branch,
2385
- revision: row.revision,
2386
- document: row.document
2387
- }));
2388
- }
2389
- async deleteKeyframes(documentId, scope, branch, signal) {
2390
- if (signal?.aborted) throw new Error("Operation aborted");
2391
- let query = this.queryExecutor.deleteFrom("Keyframe").where("documentId", "=", documentId);
2392
- if (scope !== void 0 && branch !== void 0) query = query.where("scope", "=", scope).where("branch", "=", branch);
2393
- else if (scope !== void 0) query = query.where("scope", "=", scope);
2394
- const result = await query.executeTakeFirst();
2395
- return Number(result.numDeletedRows || 0n);
2396
- }
2397
- };
2398
- //#endregion
2399
- //#region src/storage/kysely/pagination.ts
2400
- const DEFAULT_LIMIT = 100;
2401
- function paginateRows(rows, paging, cursorOf, toItem, refetch) {
2402
- let hasMore = false;
2403
- let items = rows;
2404
- if (paging?.limit && rows.length > paging.limit) {
2405
- hasMore = true;
2406
- items = rows.slice(0, paging.limit);
2407
- }
2408
- const nextCursor = hasMore && items.length > 0 ? cursorOf(items[items.length - 1]).toString() : void 0;
2409
- const cursor = paging?.cursor || "0";
2410
- const limit = paging?.limit || DEFAULT_LIMIT;
2411
- return {
2412
- results: items.map(toItem),
2413
- options: {
2414
- cursor,
2415
- limit
2416
- },
2417
- nextCursor,
2418
- next: hasMore ? () => refetch(nextCursor, limit) : void 0
2419
- };
2420
- }
2421
- //#endregion
2422
- //#region src/storage/interfaces.ts
2423
- /**
2424
- * Thrown when an operation with the same identity already exists in the store.
2425
- */
2426
- var DuplicateOperationError = class extends Error {
2427
- constructor(description) {
2428
- super(`Duplicate operation: ${description}`);
2429
- this.name = "DuplicateOperationError";
2430
- }
2431
- };
2432
- /**
2433
- * Thrown when a concurrent write conflict is detected during an atomic apply.
2434
- */
2435
- var OptimisticLockError = class extends Error {
2436
- constructor(message) {
2437
- super(message);
2438
- this.name = "OptimisticLockError";
2439
- }
2440
- };
2441
- /**
2442
- * Thrown when the caller-provided revision does not match the current
2443
- * stored revision, indicating a stale read.
2444
- */
2445
- var RevisionMismatchError = class extends Error {
2446
- constructor(expected, actual) {
2447
- super(`Revision mismatch: expected ${expected}, got ${actual}`);
2448
- this.name = "RevisionMismatchError";
2449
- }
2450
- };
2451
- //#endregion
2452
- //#region src/storage/txn.ts
2453
- var AtomicTransaction = class {
2454
- operations = [];
2455
- constructor(documentId, documentType, scope, branch, baseRevision) {
2456
- this.documentId = documentId;
2457
- this.documentType = documentType;
2458
- this.scope = scope;
2459
- this.branch = branch;
2460
- this.baseRevision = baseRevision;
2461
- }
2462
- addOperations(...operations) {
2463
- for (const op of operations) this.operations.push({
2464
- jobId: v4(),
2465
- opId: op.id,
2466
- prevOpId: "",
2467
- documentId: this.documentId,
2468
- documentType: this.documentType,
2469
- scope: this.scope,
2470
- branch: this.branch,
2471
- timestampUtcMs: new Date(op.timestampUtcMs),
2472
- index: op.index,
2473
- action: JSON.stringify(op.action),
2474
- skip: op.skip,
2475
- error: op.error || null,
2476
- hash: op.hash
2477
- });
2478
- }
2479
- getOperations() {
2480
- return this.operations;
2481
- }
2482
- };
2483
- //#endregion
2484
- //#region src/storage/kysely/store.ts
2485
- var _UniqueConstraintContext = class extends Error {
2486
- constructor(documentId, scope, branch, revision, stagedOps) {
2487
- super("unique constraint");
2488
- this.documentId = documentId;
2489
- this.scope = scope;
2490
- this.branch = branch;
2491
- this.revision = revision;
2492
- this.stagedOps = stagedOps;
2493
- this.name = "UniqueConstraintContext";
2494
- }
2495
- };
2496
- var KyselyOperationStore = class KyselyOperationStore {
2497
- trx;
2498
- constructor(db) {
2499
- this.db = db;
2500
- }
2501
- get queryExecutor() {
2502
- return this.trx ?? this.db;
2503
- }
2504
- withTransaction(trx) {
2505
- const instance = new KyselyOperationStore(this.db);
2506
- instance.trx = trx;
2507
- return instance;
2508
- }
2509
- async apply(documentId, documentType, scope, branch, revision, fn, signal) {
2510
- if (this.trx) {
2511
- let executeResult = null;
2512
- let uniqueCtx = null;
2513
- try {
2514
- executeResult = await this.executeApply(this.trx, documentId, documentType, scope, branch, revision, fn, signal);
2515
- } catch (error) {
2516
- if (error instanceof _UniqueConstraintContext) uniqueCtx = error;
2517
- else throw error;
2518
- }
2519
- if (uniqueCtx !== null) return this.resolveUniqueConstraint(uniqueCtx);
2520
- return executeResult;
2521
- } else {
2522
- let transactionResult = null;
2523
- let uniqueCtx = null;
2524
- try {
2525
- transactionResult = await this.db.transaction().execute(async (trx) => {
2526
- return this.executeApply(trx, documentId, documentType, scope, branch, revision, fn, signal);
2527
- });
2528
- } catch (error) {
2529
- if (error instanceof _UniqueConstraintContext) uniqueCtx = error;
2530
- else throw error;
2531
- }
2532
- if (uniqueCtx !== null) return this.resolveUniqueConstraint(uniqueCtx);
2533
- return transactionResult;
2534
- }
2535
- }
2536
- async resolveUniqueConstraint(ctx) {
2537
- let replayOps = null;
2538
- try {
2539
- replayOps = await this.findIdempotentReplay(this.db, ctx.documentId, ctx.scope, ctx.branch, ctx.revision, ctx.stagedOps);
2540
- } catch {}
2541
- if (replayOps !== null) return replayOps;
2542
- const op = ctx.stagedOps[0];
2543
- throw new DuplicateOperationError(`${op.opId} at index ${op.index} with skip ${op.skip}`);
2544
- }
2545
- async executeApply(trx, documentId, documentType, scope, branch, revision, fn, signal) {
2546
- throwIfAborted(signal);
2547
- const atomicTxn = new AtomicTransaction(documentId, documentType, scope, branch, revision);
2548
- await fn(atomicTxn);
2549
- const operations = atomicTxn.getOperations();
2550
- if (operations.length === 0) return [];
2551
- const latestOp = await trx.selectFrom("Operation").selectAll().where("documentId", "=", documentId).where("scope", "=", scope).where("branch", "=", branch).orderBy("index", "desc").limit(1).executeTakeFirst();
2552
- const currentRevision = latestOp ? latestOp.index : -1;
2553
- if (currentRevision !== revision - 1) {
2554
- let replayOps = null;
2555
- try {
2556
- replayOps = await this.findIdempotentReplay(trx, documentId, scope, branch, revision, operations);
2557
- } catch {}
2558
- if (replayOps !== null) return replayOps;
2559
- throw new RevisionMismatchError(currentRevision + 1, revision);
2560
- }
2561
- let prevOpId = latestOp?.opId || "";
2562
- for (const op of operations) {
2563
- op.prevOpId = prevOpId;
2564
- prevOpId = op.opId;
2565
- }
2566
- try {
2567
- await trx.insertInto("Operation").values(operations).execute();
2568
- } catch (error) {
2569
- if (error instanceof Error && error.message.includes("unique constraint")) throw new _UniqueConstraintContext(documentId, scope, branch, revision, operations);
2570
- throw error;
2571
- }
2572
- return operations.map((op) => ({
2573
- index: op.index,
2574
- timestampUtcMs: op.timestampUtcMs.toISOString(),
2575
- hash: op.hash,
2576
- skip: op.skip,
2577
- error: op.error || void 0,
2578
- id: op.opId,
2579
- action: JSON.parse(op.action)
2580
- }));
2581
- }
2582
- async findIdempotentReplay(executor, documentId, scope, branch, revision, stagedOps) {
2583
- const minIndex = revision;
2584
- const maxIndex = revision + stagedOps.length - 1;
2585
- const storedRows = await executor.selectFrom("Operation").selectAll().where("documentId", "=", documentId).where("scope", "=", scope).where("branch", "=", branch).where("index", ">=", minIndex).where("index", "<=", maxIndex).orderBy("index", "asc").execute();
2586
- if (storedRows.length !== stagedOps.length) return null;
2587
- for (let i = 0; i < stagedOps.length; i++) {
2588
- const staged = stagedOps[i];
2589
- const stored = storedRows[i];
2590
- if (stored.opId !== staged.opId || stored.index !== staged.index || stored.skip !== staged.skip) return null;
2591
- }
2592
- return storedRows.map((row) => this.rowToOperation(row));
2593
- }
2594
- async getSince(documentId, scope, branch, revision, filter, paging, signal) {
2595
- throwIfAborted(signal);
2596
- let query = this.queryExecutor.selectFrom("Operation").selectAll().where("documentId", "=", documentId).where("scope", "=", scope).where("branch", "=", branch).where("index", ">", revision).orderBy("index", "asc");
2597
- if (filter) {
2598
- if (filter.actionTypes && filter.actionTypes.length > 0) {
2599
- const actionTypesArray = filter.actionTypes.map((t) => `'${t.replace(/'/g, "''")}'`).join(",");
2600
- query = query.where(sql`action->>'type' = ANY(ARRAY[${sql.raw(actionTypesArray)}]::text[])`);
2601
- }
2602
- if (filter.timestampFrom) query = query.where("timestampUtcMs", ">=", new Date(filter.timestampFrom));
2603
- if (filter.timestampTo) query = query.where("timestampUtcMs", "<=", new Date(filter.timestampTo));
2604
- if (filter.sinceRevision !== void 0) query = query.where("index", ">=", filter.sinceRevision);
2605
- }
2606
- if (paging) {
2607
- const cursorValue = Number.parseInt(paging.cursor, 10);
2608
- if (cursorValue > 0) query = query.where("index", ">", cursorValue);
2609
- if (paging.limit) query = query.limit(paging.limit + 1);
2610
- }
2611
- return paginateRows(await query.execute(), paging, (row) => row.index, (row) => this.rowToOperation(row), (cursor, limit) => this.getSince(documentId, scope, branch, revision, filter, {
2612
- cursor,
2613
- limit
2614
- }, signal));
2615
- }
2616
- async getSinceId(id, paging, signal) {
2617
- throwIfAborted(signal);
2618
- let query = this.queryExecutor.selectFrom("Operation").selectAll().where("id", ">", id).orderBy("id", "asc");
2619
- if (paging) {
2620
- const cursorValue = Number.parseInt(paging.cursor, 10);
2621
- if (cursorValue > 0) query = query.where("id", ">", cursorValue);
2622
- if (paging.limit) query = query.limit(paging.limit + 1);
2623
- }
2624
- return paginateRows(await query.execute(), paging, (row) => row.id, (row) => this.rowToOperationWithContext(row), (cursor, limit) => this.getSinceId(id, {
2625
- cursor,
2626
- limit
2627
- }, signal));
2628
- }
2629
- async getConflicting(documentId, scope, branch, minTimestamp, paging, signal) {
2630
- throwIfAborted(signal);
2631
- let query = this.queryExecutor.selectFrom("Operation").selectAll().where("documentId", "=", documentId).where("scope", "=", scope).where("branch", "=", branch).where("timestampUtcMs", ">=", new Date(minTimestamp)).orderBy("index", "asc");
2632
- if (paging) {
2633
- const cursorValue = Number.parseInt(paging.cursor, 10);
2634
- if (cursorValue > 0) query = query.where("index", ">", cursorValue);
2635
- if (paging.limit) query = query.limit(paging.limit + 1);
2636
- }
2637
- return paginateRows(await query.execute(), paging, (row) => row.index, (row) => this.rowToOperation(row), (cursor, limit) => this.getConflicting(documentId, scope, branch, minTimestamp, {
2638
- cursor,
2639
- limit
2640
- }, signal));
2641
- }
2642
- async getRevisions(documentId, branch, signal) {
2643
- throwIfAborted(signal);
2644
- const scopeRevisions = await this.queryExecutor.selectFrom("Operation as o1").select([
2645
- "o1.scope",
2646
- "o1.index",
2647
- "o1.timestampUtcMs"
2648
- ]).where("o1.documentId", "=", documentId).where("o1.branch", "=", branch).where((eb) => eb("o1.index", "=", eb.selectFrom("Operation as o2").select((eb2) => eb2.fn.max("o2.index").as("maxIndex")).where("o2.documentId", "=", eb.ref("o1.documentId")).where("o2.branch", "=", eb.ref("o1.branch")).where("o2.scope", "=", eb.ref("o1.scope")))).execute();
2649
- const revision = {};
2650
- let latestTimestamp = (/* @__PURE__ */ new Date(0)).toISOString();
2651
- for (const row of scopeRevisions) {
2652
- revision[row.scope] = row.index + 1;
2653
- const timestamp = row.timestampUtcMs.toISOString();
2654
- if (timestamp > latestTimestamp) latestTimestamp = timestamp;
2655
- }
2656
- return {
2657
- revision,
2658
- latestTimestamp
2659
- };
2660
- }
2661
- rowToOperation(row) {
2662
- return {
2663
- index: row.index,
2664
- timestampUtcMs: row.timestampUtcMs.toISOString(),
2665
- hash: row.hash,
2666
- skip: row.skip,
2667
- error: row.error || void 0,
2668
- id: row.opId,
2669
- action: row.action
2670
- };
2671
- }
2672
- rowToOperationWithContext(row) {
2673
- return {
2674
- operation: this.rowToOperation(row),
2675
- context: {
2676
- documentId: row.documentId,
2677
- documentType: row.documentType,
2678
- scope: row.scope,
2679
- branch: row.branch,
2680
- ordinal: row.id
2681
- }
2682
- };
2683
- }
2684
- };
2685
- //#endregion
2686
- //#region src/storage/pool-instrumentation.ts
2687
- /**
2688
- * Wraps an existing pg.Pool with acquire-wait timing and an event
2689
- * subscription surface. The pool is mutated in place: pool.connect()
2690
- * is replaced with a timing wrapper so all callers (Kysely included)
2691
- * pick up the instrumentation transparently.
2692
- */
2693
- function instrumentPgPool(pool, name) {
2694
- const listeners = /* @__PURE__ */ new Set();
2695
- const originalConnect = pool.connect.bind(pool);
2696
- const wrappedConnect = async () => {
2697
- const start = performance.now();
2698
- const client = await originalConnect();
2699
- const durationMs = performance.now() - start;
2700
- for (const listener of listeners) try {
2701
- listener(durationMs);
2702
- } catch {}
2703
- return client;
2704
- };
2705
- pool.connect = wrappedConnect;
2706
- return {
2707
- name,
2708
- getStats() {
2709
- return {
2710
- size: pool.totalCount,
2711
- idle: pool.idleCount,
2712
- waiting: pool.waitingCount
2713
- };
2714
- },
2715
- onAcquire(listener) {
2716
- listeners.add(listener);
2717
- return () => {
2718
- listeners.delete(listener);
2719
- };
2720
- }
2721
- };
2722
- }
2723
- function createForwardingPoolInstrumentation(name) {
2724
- const listeners = /* @__PURE__ */ new Set();
2725
- let stats = {
2726
- size: 0,
2727
- idle: 0,
2728
- waiting: 0
2729
- };
2730
- return {
2731
- name,
2732
- getStats() {
2733
- return stats;
2734
- },
2735
- onAcquire(listener) {
2736
- listeners.add(listener);
2737
- return () => {
2738
- listeners.delete(listener);
2739
- };
2740
- },
2741
- pushSamples(durations) {
2742
- for (const durationMs of durations) for (const listener of listeners) try {
2743
- listener(durationMs);
2744
- } catch {}
2745
- },
2746
- updateStats(next) {
2747
- stats = next;
2748
- }
2749
- };
2750
- }
2751
- //#endregion
2752
- //#region src/storage/migrations/001_create_operation_table.ts
2753
- var _001_create_operation_table_exports = /* @__PURE__ */ __exportAll({ up: () => up$13 });
2754
- async function up$13(db) {
2755
- await db.schema.createTable("Operation").addColumn("id", "serial", (col) => col.primaryKey()).addColumn("jobId", "text", (col) => col.notNull()).addColumn("opId", "text", (col) => col.notNull()).addColumn("prevOpId", "text", (col) => col.notNull()).addColumn("writeTimestampUtcMs", "timestamptz", (col) => col.notNull().defaultTo(sql`NOW()`)).addColumn("documentId", "text", (col) => col.notNull()).addColumn("documentType", "text", (col) => col.notNull()).addColumn("scope", "text", (col) => col.notNull()).addColumn("branch", "text", (col) => col.notNull()).addColumn("timestampUtcMs", "timestamptz", (col) => col.notNull()).addColumn("index", "integer", (col) => col.notNull()).addColumn("action", "jsonb", (col) => col.notNull()).addColumn("skip", "integer", (col) => col.notNull()).addColumn("error", "text").addColumn("hash", "text", (col) => col.notNull()).addUniqueConstraint("unique_revision", [
2756
- "documentId",
2757
- "scope",
2758
- "branch",
2759
- "index"
2760
- ]).addUniqueConstraint("unique_operation_instance", [
2761
- "opId",
2762
- "index",
2763
- "skip"
2764
- ]).execute();
2765
- await db.schema.createIndex("streamOperations").on("Operation").columns([
2766
- "documentId",
2767
- "scope",
2768
- "branch",
2769
- "id"
2770
- ]).execute();
2771
- await db.schema.createIndex("branchlessStreamOperations").on("Operation").columns([
2772
- "documentId",
2773
- "scope",
2774
- "id"
2775
- ]).execute();
2776
- }
2777
- //#endregion
2778
- //#region src/storage/migrations/002_create_keyframe_table.ts
2779
- var _002_create_keyframe_table_exports = /* @__PURE__ */ __exportAll({ up: () => up$12 });
2780
- async function up$12(db) {
2781
- await db.schema.createTable("Keyframe").addColumn("id", "serial", (col) => col.primaryKey()).addColumn("documentId", "text", (col) => col.notNull()).addColumn("documentType", "text", (col) => col.notNull()).addColumn("scope", "text", (col) => col.notNull()).addColumn("branch", "text", (col) => col.notNull()).addColumn("revision", "integer", (col) => col.notNull()).addColumn("document", "jsonb", (col) => col.notNull()).addColumn("createdAt", "timestamptz", (col) => col.notNull().defaultTo(sql`NOW()`)).addUniqueConstraint("unique_keyframe", [
2782
- "documentId",
2783
- "scope",
2784
- "branch",
2785
- "revision"
2786
- ]).execute();
2787
- await db.schema.createIndex("keyframe_lookup").on("Keyframe").columns([
2788
- "documentId",
2789
- "scope",
2790
- "branch",
2791
- "revision"
2792
- ]).execute();
2793
- }
2794
- //#endregion
2795
- //#region src/storage/migrations/003_create_document_table.ts
2796
- var _003_create_document_table_exports = /* @__PURE__ */ __exportAll({ up: () => up$11 });
2797
- async function up$11(db) {
2798
- await db.schema.createTable("Document").addColumn("id", "text", (col) => col.primaryKey()).addColumn("createdAt", "timestamptz", (col) => col.notNull().defaultTo(sql`NOW()`)).addColumn("updatedAt", "timestamptz", (col) => col.notNull().defaultTo(sql`NOW()`)).execute();
2799
- }
2800
- //#endregion
2801
- //#region src/storage/migrations/004_create_document_relationship_table.ts
2802
- var _004_create_document_relationship_table_exports = /* @__PURE__ */ __exportAll({ up: () => up$10 });
2803
- async function up$10(db) {
2804
- await db.schema.createTable("DocumentRelationship").addColumn("id", "text", (col) => col.primaryKey()).addColumn("sourceId", "text", (col) => col.notNull().references("Document.id").onDelete("cascade")).addColumn("targetId", "text", (col) => col.notNull().references("Document.id").onDelete("cascade")).addColumn("relationshipType", "text", (col) => col.notNull()).addColumn("metadata", "jsonb").addColumn("createdAt", "timestamptz", (col) => col.notNull().defaultTo(sql`NOW()`)).addColumn("updatedAt", "timestamptz", (col) => col.notNull().defaultTo(sql`NOW()`)).addUniqueConstraint("unique_source_target_type", [
2805
- "sourceId",
2806
- "targetId",
2807
- "relationshipType"
2808
- ]).execute();
2809
- await db.schema.createIndex("idx_relationship_source").on("DocumentRelationship").column("sourceId").execute();
2810
- await db.schema.createIndex("idx_relationship_target").on("DocumentRelationship").column("targetId").execute();
2811
- await db.schema.createIndex("idx_relationship_type").on("DocumentRelationship").column("relationshipType").execute();
2812
- }
2813
- //#endregion
2814
- //#region src/storage/migrations/005_create_indexer_state_table.ts
2815
- var _005_create_indexer_state_table_exports = /* @__PURE__ */ __exportAll({ up: () => up$9 });
2816
- async function up$9(db) {
2817
- await db.schema.createTable("IndexerState").addColumn("id", "integer", (col) => col.primaryKey().generatedAlwaysAsIdentity()).addColumn("lastOperationId", "integer", (col) => col.notNull()).addColumn("lastOperationTimestamp", "timestamptz", (col) => col.notNull().defaultTo(sql`NOW()`)).execute();
2818
- }
2819
- //#endregion
2820
- //#region src/storage/migrations/006_create_document_snapshot_table.ts
2821
- var _006_create_document_snapshot_table_exports = /* @__PURE__ */ __exportAll({ up: () => up$8 });
2822
- async function up$8(db) {
2823
- await db.schema.createTable("DocumentSnapshot").addColumn("id", "text", (col) => col.primaryKey()).addColumn("documentId", "text", (col) => col.notNull()).addColumn("slug", "text").addColumn("name", "text").addColumn("scope", "text", (col) => col.notNull()).addColumn("branch", "text", (col) => col.notNull()).addColumn("content", "jsonb", (col) => col.notNull()).addColumn("documentType", "text", (col) => col.notNull()).addColumn("lastOperationIndex", "integer", (col) => col.notNull()).addColumn("lastOperationHash", "text", (col) => col.notNull()).addColumn("lastUpdatedAt", "timestamptz", (col) => col.notNull().defaultTo(sql`NOW()`)).addColumn("snapshotVersion", "integer", (col) => col.notNull().defaultTo(1)).addColumn("identifiers", "jsonb").addColumn("metadata", "jsonb").addColumn("isDeleted", "boolean", (col) => col.notNull().defaultTo(false)).addColumn("deletedAt", "timestamptz").addUniqueConstraint("unique_doc_scope_branch", [
2824
- "documentId",
2825
- "scope",
2826
- "branch"
2827
- ]).execute();
2828
- await db.schema.createIndex("idx_slug_scope_branch").on("DocumentSnapshot").columns([
2829
- "slug",
2830
- "scope",
2831
- "branch"
2832
- ]).execute();
2833
- await db.schema.createIndex("idx_doctype_scope_branch").on("DocumentSnapshot").columns([
2834
- "documentType",
2835
- "scope",
2836
- "branch"
2837
- ]).execute();
2838
- await db.schema.createIndex("idx_last_updated").on("DocumentSnapshot").column("lastUpdatedAt").execute();
2839
- await db.schema.createIndex("idx_is_deleted").on("DocumentSnapshot").column("isDeleted").execute();
2840
- }
2841
- //#endregion
2842
- //#region src/storage/migrations/007_create_slug_mapping_table.ts
2843
- var _007_create_slug_mapping_table_exports = /* @__PURE__ */ __exportAll({ up: () => up$7 });
2844
- async function up$7(db) {
2845
- await db.schema.createTable("SlugMapping").addColumn("slug", "text", (col) => col.primaryKey()).addColumn("documentId", "text", (col) => col.notNull()).addColumn("scope", "text", (col) => col.notNull()).addColumn("branch", "text", (col) => col.notNull()).addColumn("createdAt", "timestamptz", (col) => col.notNull().defaultTo(sql`NOW()`)).addColumn("updatedAt", "timestamptz", (col) => col.notNull().defaultTo(sql`NOW()`)).addUniqueConstraint("unique_docid_scope_branch", [
2846
- "documentId",
2847
- "scope",
2848
- "branch"
2849
- ]).execute();
2850
- await db.schema.createIndex("idx_slug_documentid").on("SlugMapping").column("documentId").execute();
2851
- }
2852
- //#endregion
2853
- //#region src/storage/migrations/008_create_view_state_table.ts
2854
- var _008_create_view_state_table_exports = /* @__PURE__ */ __exportAll({ up: () => up$6 });
2855
- async function up$6(db) {
2856
- await db.schema.createTable("ViewState").addColumn("readModelId", "text", (col) => col.primaryKey()).addColumn("lastOrdinal", "integer", (col) => col.notNull().defaultTo(0)).addColumn("lastOperationTimestamp", "timestamptz", (col) => col.notNull().defaultTo(sql`NOW()`)).execute();
2857
- }
2858
- //#endregion
2859
- //#region src/storage/migrations/009_create_operation_index_tables.ts
2860
- var _009_create_operation_index_tables_exports = /* @__PURE__ */ __exportAll({ up: () => up$5 });
2861
- async function up$5(db) {
2862
- await db.schema.createTable("document_collections").addColumn("documentId", "text", (col) => col.notNull()).addColumn("collectionId", "text", (col) => col.notNull()).addColumn("joinedOrdinal", "bigint", (col) => col.notNull().defaultTo(0)).addColumn("leftOrdinal", "bigint").addPrimaryKeyConstraint("document_collections_pkey", ["documentId", "collectionId"]).execute();
2863
- await db.schema.createIndex("idx_document_collections_collectionId").on("document_collections").column("collectionId").execute();
2864
- await db.schema.createIndex("idx_doc_collections_collection_range").on("document_collections").columns(["collectionId", "joinedOrdinal"]).execute();
2865
- await db.schema.createTable("operation_index_operations").addColumn("ordinal", "serial", (col) => col.primaryKey()).addColumn("opId", "text", (col) => col.notNull()).addColumn("documentId", "text", (col) => col.notNull()).addColumn("documentType", "text", (col) => col.notNull()).addColumn("scope", "text", (col) => col.notNull()).addColumn("branch", "text", (col) => col.notNull()).addColumn("timestampUtcMs", "text", (col) => col.notNull()).addColumn("writeTimestampUtcMs", "timestamptz", (col) => col.notNull().defaultTo(sql`NOW()`)).addColumn("index", "integer", (col) => col.notNull()).addColumn("skip", "integer", (col) => col.notNull()).addColumn("hash", "text", (col) => col.notNull()).addColumn("action", "jsonb", (col) => col.notNull()).execute();
2866
- await db.schema.createIndex("idx_operation_index_operations_document").on("operation_index_operations").columns([
2867
- "documentId",
2868
- "branch",
2869
- "scope"
2870
- ]).execute();
2871
- await db.schema.createIndex("idx_operation_index_operations_ordinal").on("operation_index_operations").column("ordinal").execute();
2872
- }
2873
- //#endregion
2874
- //#region src/storage/migrations/010_create_sync_tables.ts
2875
- var _010_create_sync_tables_exports = /* @__PURE__ */ __exportAll({ up: () => up$4 });
2876
- async function up$4(db) {
2877
- await db.schema.createTable("sync_remotes").addColumn("name", "text", (col) => col.primaryKey()).addColumn("collection_id", "text", (col) => col.notNull()).addColumn("channel_type", "text", (col) => col.notNull()).addColumn("channel_id", "text", (col) => col.notNull().defaultTo("")).addColumn("remote_name", "text", (col) => col.notNull().defaultTo("")).addColumn("channel_parameters", "jsonb", (col) => col.notNull().defaultTo(sql`'{}'::jsonb`)).addColumn("filter_document_ids", "jsonb").addColumn("filter_scopes", "jsonb").addColumn("filter_branch", "text", (col) => col.notNull().defaultTo("main")).addColumn("push_state", "text", (col) => col.notNull().defaultTo("idle")).addColumn("push_last_success_utc_ms", "text").addColumn("push_last_failure_utc_ms", "text").addColumn("push_failure_count", "integer", (col) => col.notNull().defaultTo(0)).addColumn("pull_state", "text", (col) => col.notNull().defaultTo("idle")).addColumn("pull_last_success_utc_ms", "text").addColumn("pull_last_failure_utc_ms", "text").addColumn("pull_failure_count", "integer", (col) => col.notNull().defaultTo(0)).addColumn("created_at", "timestamptz", (col) => col.notNull().defaultTo(sql`NOW()`)).addColumn("updated_at", "timestamptz", (col) => col.notNull().defaultTo(sql`NOW()`)).execute();
2878
- await db.schema.createIndex("idx_sync_remotes_collection").on("sync_remotes").column("collection_id").execute();
2879
- await db.schema.createTable("sync_cursors").addColumn("remote_name", "text", (col) => col.primaryKey().references("sync_remotes.name").onDelete("cascade")).addColumn("cursor_ordinal", "bigint", (col) => col.notNull().defaultTo(0)).addColumn("last_synced_at_utc_ms", "text").addColumn("updated_at", "timestamptz", (col) => col.notNull().defaultTo(sql`NOW()`)).execute();
2880
- await db.schema.createIndex("idx_sync_cursors_ordinal").on("sync_cursors").column("cursor_ordinal").execute();
2881
- }
2882
- //#endregion
2883
- //#region src/storage/migrations/011_add_cursor_type_column.ts
2884
- var _011_add_cursor_type_column_exports = /* @__PURE__ */ __exportAll({ up: () => up$3 });
2885
- async function up$3(db) {
2886
- await db.deleteFrom("sync_cursors").where("remote_name", "like", "outbox::%").execute();
2887
- await db.deleteFrom("sync_remotes").where("name", "like", "outbox::%").execute();
2888
- await db.schema.dropTable("sync_cursors").execute();
2889
- await db.schema.createTable("sync_cursors").addColumn("remote_name", "text", (col) => col.notNull()).addColumn("cursor_type", "text", (col) => col.notNull().defaultTo("inbox")).addColumn("cursor_ordinal", "bigint", (col) => col.notNull().defaultTo(0)).addColumn("last_synced_at_utc_ms", "text").addColumn("updated_at", "timestamptz", (col) => col.notNull().defaultTo(sql`NOW()`)).addPrimaryKeyConstraint("sync_cursors_pk", ["remote_name", "cursor_type"]).execute();
2890
- await db.schema.createIndex("idx_sync_cursors_ordinal").on("sync_cursors").column("cursor_ordinal").execute();
2891
- }
2892
- //#endregion
2893
- //#region src/storage/migrations/012_add_source_remote_column.ts
2894
- var _012_add_source_remote_column_exports = /* @__PURE__ */ __exportAll({ up: () => up$2 });
2895
- async function up$2(db) {
2896
- await db.schema.alterTable("operation_index_operations").addColumn("sourceRemote", "text", (col) => col.notNull().defaultTo("")).execute();
2897
- }
2898
- //#endregion
2899
- //#region src/storage/migrations/013_create_sync_dead_letters_table.ts
2900
- var _013_create_sync_dead_letters_table_exports = /* @__PURE__ */ __exportAll({ up: () => up$1 });
2901
- async function up$1(db) {
2902
- await db.schema.createTable("sync_dead_letters").addColumn("ordinal", "serial", (col) => col.primaryKey()).addColumn("id", "text", (col) => col.unique().notNull()).addColumn("job_id", "text", (col) => col.notNull()).addColumn("job_dependencies", "jsonb", (col) => col.notNull().defaultTo(sql`'[]'::jsonb`)).addColumn("remote_name", "text", (col) => col.notNull().references("sync_remotes.name").onDelete("cascade")).addColumn("document_id", "text", (col) => col.notNull()).addColumn("scopes", "jsonb", (col) => col.notNull().defaultTo(sql`'[]'::jsonb`)).addColumn("branch", "text", (col) => col.notNull()).addColumn("operations", "jsonb", (col) => col.notNull().defaultTo(sql`'[]'::jsonb`)).addColumn("error_source", "text", (col) => col.notNull()).addColumn("error_message", "text", (col) => col.notNull()).addColumn("created_at", "timestamptz", (col) => col.notNull().defaultTo(sql`NOW()`)).execute();
2903
- await db.schema.createIndex("idx_sync_dead_letters_remote").on("sync_dead_letters").column("remote_name").execute();
2904
- }
2905
- //#endregion
2906
- //#region src/storage/migrations/014_create_processor_cursor_table.ts
2907
- var _014_create_processor_cursor_table_exports = /* @__PURE__ */ __exportAll({ up: () => up });
2908
- async function up(db) {
2909
- await db.schema.createTable("ProcessorCursor").addColumn("processorId", "text", (col) => col.primaryKey()).addColumn("factoryId", "text", (col) => col.notNull()).addColumn("driveId", "text", (col) => col.notNull()).addColumn("processorIndex", "integer", (col) => col.notNull()).addColumn("lastOrdinal", "integer", (col) => col.notNull().defaultTo(sql`0`)).addColumn("status", "text", (col) => col.notNull().defaultTo(sql`'active'`)).addColumn("lastError", "text").addColumn("lastErrorTimestamp", "timestamptz").addColumn("createdAt", "timestamptz", (col) => col.notNull().defaultTo(sql`NOW()`)).addColumn("updatedAt", "timestamptz", (col) => col.notNull().defaultTo(sql`NOW()`)).execute();
2910
- }
2911
- //#endregion
2912
- //#region src/storage/migrations/migrator.ts
2913
- const REACTOR_SCHEMA = "reactor";
2914
- const migrations = {
2915
- "001_create_operation_table": _001_create_operation_table_exports,
2916
- "002_create_keyframe_table": _002_create_keyframe_table_exports,
2917
- "003_create_document_table": _003_create_document_table_exports,
2918
- "004_create_document_relationship_table": _004_create_document_relationship_table_exports,
2919
- "005_create_indexer_state_table": _005_create_indexer_state_table_exports,
2920
- "006_create_document_snapshot_table": _006_create_document_snapshot_table_exports,
2921
- "007_create_slug_mapping_table": _007_create_slug_mapping_table_exports,
2922
- "008_create_view_state_table": _008_create_view_state_table_exports,
2923
- "009_create_operation_index_tables": _009_create_operation_index_tables_exports,
2924
- "010_create_sync_tables": _010_create_sync_tables_exports,
2925
- "011_add_cursor_type_column": _011_add_cursor_type_column_exports,
2926
- "012_add_source_remote_column": _012_add_source_remote_column_exports,
2927
- "013_create_sync_dead_letters_table": _013_create_sync_dead_letters_table_exports,
2928
- "014_create_processor_cursor_table": _014_create_processor_cursor_table_exports
2929
- };
2930
- var ProgrammaticMigrationProvider = class {
2931
- getMigrations() {
2932
- return Promise.resolve(migrations);
2933
- }
2934
- };
2935
- async function runMigrations(db, schema = REACTOR_SCHEMA) {
2936
- try {
2937
- await sql`CREATE SCHEMA IF NOT EXISTS ${sql.id(schema)}`.execute(db);
2938
- } catch (error) {
2939
- return {
2940
- success: false,
2941
- migrationsExecuted: [],
2942
- error: error instanceof Error ? error : /* @__PURE__ */ new Error("Failed to create schema")
2943
- };
2944
- }
2945
- const migrator = new Migrator({
2946
- db: db.withSchema(schema),
2947
- provider: new ProgrammaticMigrationProvider(),
2948
- migrationTableSchema: schema
2949
- });
2950
- let error;
2951
- let results;
2952
- try {
2953
- const result = await migrator.migrateToLatest();
2954
- error = result.error;
2955
- results = result.results;
2956
- } catch (e) {
2957
- error = e;
2958
- results = [];
2959
- }
2960
- const migrationsExecuted = results?.map((result) => result.migrationName) ?? [];
2961
- if (error) return {
2962
- success: false,
2963
- migrationsExecuted,
2964
- error: error instanceof Error ? error : /* @__PURE__ */ new Error("Unknown migration error")
2965
- };
2966
- return {
2967
- success: true,
2968
- migrationsExecuted
2969
- };
2970
- }
2971
- async function getMigrationStatus(db, schema = REACTOR_SCHEMA) {
2972
- return await new Migrator({
2973
- db: db.withSchema(schema),
2974
- provider: new ProgrammaticMigrationProvider(),
2975
- migrationTableSchema: schema
2976
- }).getMigrations();
2977
- }
2978
- //#endregion
2979
- //#region src/core/drive-container-types.ts
2980
- const DEFAULT_DRIVE_CONTAINER_TYPES = new Set(["powerhouse/document-drive", "powerhouse/reactor-drive"]);
2981
- //#endregion
2982
- export { parsePagingOptions as A, DuplicateManifestError as C, DocumentDeletedError as D, ModuleNotFoundError as E, __exportAll as M, DocumentNotFoundError as O, CollectionMembershipCache as S, InvalidModuleError as T, KyselyWriteCache as _, createForwardingPoolInstrumentation as a, createConsistencyToken as b, DuplicateOperationError as c, KyselyKeyframeStore as d, DocumentModelRegistry as f, EventBus as g, KyselyExecutionScope as h, runMigrations as i, throwIfAborted as j, matchesScope as k, OptimisticLockError as l, DriveCollectionId as m, REACTOR_SCHEMA as n, instrumentPgPool as o, SimpleJobExecutor as p, getMigrationStatus as r, KyselyOperationStore as s, DEFAULT_DRIVE_CONTAINER_TYPES as t, RevisionMismatchError as u, KyselyOperationIndex as v, DuplicateModuleError as w, createEmptyConsistencyToken as x, DocumentMetaCache as y };
2983
-
2984
- //# sourceMappingURL=drive-container-types-DpJp2AmE.js.map