@powerhousedao/reactor 6.1.0-dev.1 → 6.1.0-dev.11

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