@powerhousedao/reactor 6.0.2-staging.8 → 6.1.0-dev.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -55,13 +55,18 @@ function deleteDocumentAction(documentId) {
55
55
  };
56
56
  }
57
57
  /**
58
- * Creates an ADD_RELATIONSHIP action to establish a parent-child relationship.
58
+ * Creates an ADD_RELATIONSHIP action that records a directed edge from
59
+ * `sourceId` to `targetId` with an arbitrary `relationshipType` and optional
60
+ * `metadata`. The edge is opaque to the reactor — consumers (e.g. reactor-drive)
61
+ * define their own type strings such as `"drive/child"` and attach
62
+ * domain-specific metadata.
59
63
  */
60
- function addRelationshipAction(sourceId, targetId, relationshipType = "child") {
64
+ function addRelationshipAction(sourceId, targetId, relationshipType, metadata) {
61
65
  const input = {
62
66
  sourceId,
63
67
  targetId,
64
- relationshipType
68
+ relationshipType,
69
+ ...metadata !== void 0 ? { metadata } : {}
65
70
  };
66
71
  return {
67
72
  id: generateId(),
@@ -72,6 +77,25 @@ function addRelationshipAction(sourceId, targetId, relationshipType = "child") {
72
77
  };
73
78
  }
74
79
  /**
80
+ * Creates an UPDATE_RELATIONSHIP action to replace a relationship's metadata
81
+ * without losing its createdAt ordering.
82
+ */
83
+ function updateRelationshipAction(sourceId, targetId, relationshipType, metadata) {
84
+ const input = {
85
+ sourceId,
86
+ targetId,
87
+ relationshipType,
88
+ metadata
89
+ };
90
+ return {
91
+ id: generateId(),
92
+ type: "UPDATE_RELATIONSHIP",
93
+ scope: "document",
94
+ timestampUtcMs: (/* @__PURE__ */ new Date()).toISOString(),
95
+ input
96
+ };
97
+ }
98
+ /**
75
99
  * Creates a REMOVE_RELATIONSHIP action to remove a parent-child relationship.
76
100
  */
77
101
  function removeRelationshipAction(sourceId, targetId, relationshipType = "child") {
@@ -291,6 +315,44 @@ let JobStatus = /* @__PURE__ */ function(JobStatus) {
291
315
  return JobStatus;
292
316
  }({});
293
317
  //#endregion
318
+ //#region src/shared/utils.ts
319
+ function matchesScope(view = {}, scope) {
320
+ if (view.scopes) return view.scopes.includes(scope);
321
+ return true;
322
+ }
323
+ function yieldToMain() {
324
+ const s = globalThis.scheduler;
325
+ if (s?.yield) return s.yield();
326
+ return new Promise((resolve) => setTimeout(resolve, 0));
327
+ }
328
+ const defaultAbortError = () => /* @__PURE__ */ new Error("Operation aborted");
329
+ function throwIfAborted(signal, makeError = defaultAbortError) {
330
+ if (signal?.aborted) throw makeError();
331
+ }
332
+ /**
333
+ * Validates PagingOptions and returns a normalized offset and limit.
334
+ * Throws if the cursor is not empty and not a non-negative integer, or if
335
+ * limit is less than 1. When `paging` is undefined, returns offset 0 and
336
+ * the caller-supplied `defaultLimit`.
337
+ */
338
+ function parsePagingOptions(paging, defaultLimit) {
339
+ if (paging === void 0) return {
340
+ offset: 0,
341
+ limit: defaultLimit
342
+ };
343
+ if (!Number.isInteger(paging.limit) || paging.limit < 1) throw new Error(`Invalid paging limit: ${String(paging.limit)} (must be an integer >= 1)`);
344
+ if (paging.cursor === "") return {
345
+ offset: 0,
346
+ limit: paging.limit
347
+ };
348
+ const parsed = Number(paging.cursor);
349
+ if (!Number.isInteger(parsed) || parsed < 0) throw new Error(`Invalid paging cursor: ${JSON.stringify(paging.cursor)} (must be empty or a non-negative integer)`);
350
+ return {
351
+ offset: parsed,
352
+ limit: paging.limit
353
+ };
354
+ }
355
+ //#endregion
294
356
  //#region src/client/drive-client.ts
295
357
  /**
296
358
  * Implementation of {@link IDriveClient}.
@@ -409,6 +471,10 @@ var DriveClient = class {
409
471
  if (!node) throw new Error("Node missing from drive after rename");
410
472
  return node;
411
473
  }
474
+ async setPreferredEditorOnNode(nodeId, preferredEditor, signal) {
475
+ this.logger.verbose("drives.setPreferredEditorOnNode(@nodeId, @preferredEditor)", nodeId, preferredEditor);
476
+ return this.client.setPreferredEditor(nodeId, preferredEditor, "main", signal);
477
+ }
412
478
  async moveNode(driveIdentifier, srcNodeId, targetParentFolderId, signal) {
413
479
  this.logger.verbose("drives.moveNode(@driveIdentifier, @srcNodeId, @targetParentFolderId)", driveIdentifier, srcNodeId, targetParentFolderId);
414
480
  return this.client.execute(driveIdentifier, "main", [moveNode({
@@ -456,11 +522,22 @@ var DriveClient = class {
456
522
  if (!node) throw new Error(`Node ${nodeId} not found in drive ${driveIdentifier}`);
457
523
  return node;
458
524
  }
459
- async listNodes(driveIdentifier, parentFolder, signal) {
460
- this.logger.verbose("drives.listNodes(@driveIdentifier, @parentFolder)", driveIdentifier, parentFolder);
461
- const nodes = (await this.client.get(driveIdentifier, void 0, signal)).state.global.nodes;
462
- if (parentFolder === void 0) return [...nodes];
463
- return nodes.filter((n) => (n.parentFolder ?? null) === parentFolder);
525
+ async listNodes(driveIdentifier, parentFolder, paging, signal) {
526
+ this.logger.verbose("drives.listNodes(@driveIdentifier, @parentFolder, @paging)", driveIdentifier, parentFolder, paging);
527
+ const allNodes = (await this.client.get(driveIdentifier, void 0, signal)).state.global.nodes;
528
+ const filtered = parentFolder === void 0 ? [...allNodes] : allNodes.filter((n) => (n.parentFolder ?? null) === parentFolder);
529
+ const { offset: startIndex, limit } = parsePagingOptions(paging, filtered.length);
530
+ const effective = paging ?? {
531
+ cursor: "",
532
+ limit
533
+ };
534
+ const endIndex = startIndex + limit;
535
+ return {
536
+ results: filtered.slice(startIndex, endIndex),
537
+ options: effective,
538
+ ...endIndex < filtered.length ? { nextCursor: String(endIndex) } : {},
539
+ totalCount: filtered.length
540
+ };
464
541
  }
465
542
  async removeFileNode(driveId, fileId, signal) {
466
543
  const relationshipActions = await signActions([removeRelationshipAction(driveId, fileId, "child")], this.signer, signal);
@@ -642,7 +719,7 @@ function decodeCompositeCursor(cursor) {
642
719
  if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) throw new Error("Invalid composite cursor format");
643
720
  return parsed;
644
721
  } catch (error) {
645
- if (error instanceof SyntaxError) throw new Error("Invalid composite cursor format");
722
+ if (error instanceof SyntaxError) throw new Error("Invalid composite cursor format", { cause: error });
646
723
  throw error;
647
724
  }
648
725
  }
@@ -920,6 +997,17 @@ var ReactorClient = class {
920
997
  const signedActions = await signActions(actions, this.signer, signal);
921
998
  return this.reactor.execute(documentIdentifier, branch, signedActions, signal);
922
999
  }
1000
+ async executeBatch(request, signal) {
1001
+ this.logger.verbose("executeBatch(@count jobs)", request.jobs.length);
1002
+ const signedJobs = await Promise.all(request.jobs.map(async (job) => ({
1003
+ ...job,
1004
+ actions: await signActions(job.actions, this.signer, signal)
1005
+ })));
1006
+ const batchResult = await this.reactor.executeBatch({ jobs: signedJobs }, signal);
1007
+ const completedJobs = await Promise.all(Object.values(batchResult.jobs).map((job) => this.waitForJob(job, signal)));
1008
+ for (const job of completedJobs) if (job.status === JobStatus.FAILED) throw new Error(job.error?.message);
1009
+ return batchResult;
1010
+ }
923
1011
  /**
924
1012
  * Renames a document and waits for completion
925
1013
  */
@@ -928,6 +1016,14 @@ var ReactorClient = class {
928
1016
  return this.execute(documentIdentifier, branch, [actions.setName(name)], signal);
929
1017
  }
930
1018
  /**
1019
+ * Updates the preferred editor recorded in the document header meta.
1020
+ * Pass `null` to clear it.
1021
+ */
1022
+ async setPreferredEditor(documentIdentifier, preferredEditor, branch = "main", signal) {
1023
+ this.logger.verbose("setPreferredEditor(@documentIdentifier, @preferredEditor, @branch)", documentIdentifier, preferredEditor, branch);
1024
+ return this.execute(documentIdentifier, branch, [actions.setPreferredEditor(preferredEditor)], signal);
1025
+ }
1026
+ /**
931
1027
  * Adds multiple documents as children to another and waits for completion
932
1028
  */
933
1029
  async addRelationship(sourceIdentifier, targetIdentifier, relationshipType, branch = "main", signal) {
@@ -2150,7 +2246,6 @@ var KyselyWriteCache = class KyselyWriteCache {
2150
2246
  startRevision = keyframe.revision;
2151
2247
  documentType = keyframe.document.header.documentType;
2152
2248
  } else {
2153
- document = void 0;
2154
2249
  startRevision = -1;
2155
2250
  const createOpResult = await this.operationStore.getSince(documentId, "document", branch, -1, void 0, {
2156
2251
  cursor: "0",
@@ -2205,7 +2300,7 @@ var KyselyWriteCache = class KyselyWriteCache {
2205
2300
  hasMorePages = Boolean(result.nextCursor) && !reachedTarget;
2206
2301
  if (hasMorePages) cursor = result.nextCursor;
2207
2302
  } catch (err) {
2208
- throw new Error(`Failed to rebuild document ${documentId}: ${err instanceof Error ? err.message : String(err)}`);
2303
+ throw new Error(`Failed to rebuild document ${documentId}: ${err instanceof Error ? err.message : String(err)}`, { cause: err });
2209
2304
  }
2210
2305
  } while (hasMorePages);
2211
2306
  const revisions = await this.operationStore.getRevisions(documentId, branch, signal);
@@ -2230,7 +2325,7 @@ var KyselyWriteCache = class KyselyWriteCache {
2230
2325
  if (targetRevision !== void 0 && operation.index === targetRevision) break;
2231
2326
  }
2232
2327
  } catch (err) {
2233
- throw new Error(`Failed to rebuild document ${documentId}: ${err instanceof Error ? err.message : String(err)}`);
2328
+ throw new Error(`Failed to rebuild document ${documentId}: ${err instanceof Error ? err.message : String(err)}`, { cause: err });
2234
2329
  }
2235
2330
  const revisions = await this.operationStore.getRevisions(documentId, branch, signal);
2236
2331
  document.header.revision = revisions.revision;
@@ -2368,7 +2463,7 @@ let JobQueueState = /* @__PURE__ */ function(JobQueueState) {
2368
2463
  */
2369
2464
  const QueueEventTypes = { JOB_AVAILABLE: 1e4 };
2370
2465
  //#endregion
2371
- //#region src/registry/implementation.ts
2466
+ //#region src/registry/errors.ts
2372
2467
  /**
2373
2468
  * Error thrown when a document model module is not found in the registry.
2374
2469
  */
@@ -2456,140 +2551,6 @@ var InvalidUpgradeStepError = class extends Error {
2456
2551
  this.name = "InvalidUpgradeStepError";
2457
2552
  }
2458
2553
  };
2459
- /**
2460
- * In-memory implementation of the IDocumentModelRegistry interface.
2461
- * Manages document model modules with version-aware storage and upgrade manifest support.
2462
- */
2463
- var DocumentModelRegistry = class {
2464
- modules = [];
2465
- manifests = [];
2466
- registerModules(...modules) {
2467
- return modules.map((module) => {
2468
- try {
2469
- const documentType = module.documentModel.global.id;
2470
- const version = module.version ?? 1;
2471
- for (let i = 0; i < this.modules.length; i++) {
2472
- const existing = this.modules[i];
2473
- const existingType = existing.documentModel.global.id;
2474
- const existingVersion = existing.version ?? 1;
2475
- if (existingType === documentType && existingVersion === version) throw new DuplicateModuleError(documentType, version);
2476
- }
2477
- this.modules.push(module);
2478
- return {
2479
- status: "success",
2480
- item: module
2481
- };
2482
- } catch (error) {
2483
- return {
2484
- status: "error",
2485
- item: module,
2486
- error: error instanceof Error ? error : new Error(String(error))
2487
- };
2488
- }
2489
- });
2490
- }
2491
- unregisterModules(...documentTypes) {
2492
- let allFound = true;
2493
- for (const documentType of documentTypes) {
2494
- if (!this.modules.some((m) => m.documentModel.global.id === documentType)) allFound = false;
2495
- this.modules = this.modules.filter((m) => m.documentModel.global.id !== documentType);
2496
- }
2497
- return allFound;
2498
- }
2499
- getModule(documentType, version) {
2500
- let latestModule;
2501
- let latestVersion = -1;
2502
- for (let i = 0; i < this.modules.length; i++) {
2503
- const module = this.modules[i];
2504
- const moduleType = module.documentModel.global.id;
2505
- const moduleVersion = module.version ?? 1;
2506
- if (moduleType === documentType) {
2507
- if (version !== void 0 && moduleVersion === version) return module;
2508
- if (moduleVersion > latestVersion) {
2509
- latestModule = module;
2510
- latestVersion = moduleVersion;
2511
- }
2512
- }
2513
- }
2514
- if (version === void 0 && latestModule !== void 0) return latestModule;
2515
- throw new ModuleNotFoundError(documentType, version);
2516
- }
2517
- getAllModules() {
2518
- return [...this.modules];
2519
- }
2520
- clear() {
2521
- this.modules = [];
2522
- this.manifests = [];
2523
- }
2524
- getSupportedVersions(documentType) {
2525
- const versions = [];
2526
- for (const module of this.modules) if (module.documentModel.global.id === documentType) versions.push(module.version ?? 1);
2527
- if (versions.length === 0) throw new ModuleNotFoundError(documentType);
2528
- return versions.sort((a, b) => a - b);
2529
- }
2530
- getLatestVersion(documentType) {
2531
- let latest = -1;
2532
- let found = false;
2533
- for (const module of this.modules) if (module.documentModel.global.id === documentType) {
2534
- found = true;
2535
- const version = module.version ?? 1;
2536
- if (version > latest) latest = version;
2537
- }
2538
- if (!found) throw new ModuleNotFoundError(documentType);
2539
- return latest;
2540
- }
2541
- registerUpgradeManifests(...manifestsToRegister) {
2542
- return manifestsToRegister.map((manifestToRegister) => {
2543
- try {
2544
- if (!manifestToRegister.documentType) throw new Error("Upgrade manifest is missing a documentType");
2545
- for (const registeredManifest of this.manifests) if (registeredManifest.documentType === manifestToRegister.documentType) throw new DuplicateManifestError(manifestToRegister.documentType);
2546
- this.manifests.push(manifestToRegister);
2547
- return {
2548
- status: "success",
2549
- item: manifestToRegister
2550
- };
2551
- } catch (error) {
2552
- return {
2553
- status: "error",
2554
- item: manifestToRegister,
2555
- error: error instanceof Error ? error : new Error(String(error))
2556
- };
2557
- }
2558
- });
2559
- }
2560
- unregisterUpgradeManifests(...documentTypes) {
2561
- let allFound = true;
2562
- for (const documentType of documentTypes) {
2563
- if (!this.manifests.some((m) => m.documentType === documentType)) allFound = false;
2564
- this.manifests = this.manifests.filter((m) => m.documentType !== documentType);
2565
- }
2566
- return allFound;
2567
- }
2568
- getUpgradeManifest(documentType) {
2569
- for (let i = 0; i < this.manifests.length; i++) if (this.manifests[i].documentType === documentType) return this.manifests[i];
2570
- throw new ManifestNotFoundError(documentType);
2571
- }
2572
- computeUpgradePath(documentType, fromVersion, toVersion) {
2573
- if (fromVersion === toVersion) return [];
2574
- if (toVersion < fromVersion) throw new DowngradeNotSupportedError(documentType, fromVersion, toVersion);
2575
- const manifest = this.getUpgradeManifest(documentType);
2576
- const path = [];
2577
- for (let v = fromVersion + 1; v <= toVersion; v++) {
2578
- const key = `v${v}`;
2579
- if (!(key in manifest.upgrades)) throw new MissingUpgradeTransitionError(documentType, v - 1, v);
2580
- const transition = manifest.upgrades[key];
2581
- path.push(transition);
2582
- }
2583
- return path;
2584
- }
2585
- getUpgradeReducer(documentType, fromVersion, toVersion) {
2586
- if (toVersion !== fromVersion + 1) throw new InvalidUpgradeStepError(documentType, fromVersion, toVersion);
2587
- const manifest = this.getUpgradeManifest(documentType);
2588
- const key = `v${toVersion}`;
2589
- if (!(key in manifest.upgrades)) throw new MissingUpgradeTransitionError(documentType, fromVersion, toVersion);
2590
- return manifest.upgrades[key].upgradeReducer;
2591
- }
2592
- };
2593
2554
  //#endregion
2594
2555
  //#region src/executor/simple-job-executor-manager.ts
2595
2556
  /**
@@ -2844,24 +2805,17 @@ var SimpleJobExecutorManager = class {
2844
2805
  }
2845
2806
  };
2846
2807
  //#endregion
2847
- //#region src/shared/utils.ts
2848
- function matchesScope(view = {}, scope) {
2849
- if (view.scopes) return view.scopes.includes(scope);
2850
- return true;
2851
- }
2852
- function yieldToMain() {
2853
- const s = globalThis.scheduler;
2854
- if (s?.yield) return s.yield();
2855
- return new Promise((resolve) => setTimeout(resolve, 0));
2856
- }
2857
- //#endregion
2858
2808
  //#region src/utils/reshuffle.ts
2859
2809
  const STRICT_ORDER_ACTION_TYPES = new Set([
2860
2810
  "CREATE_DOCUMENT",
2861
2811
  "DELETE_DOCUMENT",
2862
2812
  "UPGRADE_DOCUMENT",
2863
2813
  "ADD_RELATIONSHIP",
2864
- "REMOVE_RELATIONSHIP"
2814
+ "REMOVE_RELATIONSHIP",
2815
+ "UPDATE_RELATIONSHIP",
2816
+ "ADD_FOLDER",
2817
+ "UPDATE_FOLDER",
2818
+ "REMOVE_FOLDER"
2865
2819
  ]);
2866
2820
  /**
2867
2821
  * Reshuffles operations by timestamp, then applies deterministic tie-breaking.
@@ -2908,9 +2862,10 @@ function driveCollectionId(branch, driveId) {
2908
2862
  //#endregion
2909
2863
  //#region src/executor/document-action-handler.ts
2910
2864
  var DocumentActionHandler = class {
2911
- constructor(registry, logger) {
2865
+ constructor(registry, logger, driveContainerTypes) {
2912
2866
  this.registry = registry;
2913
2867
  this.logger = logger;
2868
+ this.driveContainerTypes = driveContainerTypes;
2914
2869
  }
2915
2870
  async execute(job, action, startTime, indexTxn, stores, skip = 0, sourceRemote = "", signal) {
2916
2871
  switch (action.type) {
@@ -2919,6 +2874,7 @@ var DocumentActionHandler = class {
2919
2874
  case "UPGRADE_DOCUMENT": return this.executeUpgrade(job, action, startTime, indexTxn, stores, skip, sourceRemote, signal);
2920
2875
  case "ADD_RELATIONSHIP": return this.executeAddRelationship(job, action, startTime, indexTxn, stores, sourceRemote, signal);
2921
2876
  case "REMOVE_RELATIONSHIP": return this.executeRemoveRelationship(job, action, startTime, indexTxn, stores, sourceRemote, signal);
2877
+ case "UPDATE_RELATIONSHIP": return this.executeUpdateRelationship(job, action, startTime, indexTxn, stores, sourceRemote, signal);
2922
2878
  default: return buildErrorResult(job, /* @__PURE__ */ new Error(`Unknown document action type: ${action.type}`), startTime);
2923
2879
  }
2924
2880
  }
@@ -2943,6 +2899,10 @@ var DocumentActionHandler = class {
2943
2899
  const writeError = await this.writeOperationToStore(document.header.id, document.header.documentType, job.scope, job.branch, operation, job, startTime, stores, signal);
2944
2900
  if (writeError !== null) return writeError;
2945
2901
  updateDocumentRevision(document, job.scope, operation.index);
2902
+ document.operations = {
2903
+ ...document.operations,
2904
+ [job.scope]: [...document.operations[job.scope] ?? [], operation]
2905
+ };
2946
2906
  stores.writeCache.putState(document.header.id, job.scope, job.branch, operation.index, document);
2947
2907
  indexTxn.write([{
2948
2908
  ...operation,
@@ -2952,7 +2912,7 @@ var DocumentActionHandler = class {
2952
2912
  scope: job.scope,
2953
2913
  sourceRemote
2954
2914
  }]);
2955
- if (document.header.documentType === "powerhouse/document-drive") {
2915
+ if (this.driveContainerTypes.has(document.header.documentType)) {
2956
2916
  const collectionId = driveCollectionId(job.branch, document.header.id);
2957
2917
  indexTxn.createCollection(collectionId);
2958
2918
  indexTxn.addToCollection(collectionId, document.header.id);
@@ -2990,6 +2950,10 @@ var DocumentActionHandler = class {
2990
2950
  const writeError = await this.writeOperationToStore(documentId, document.header.documentType, job.scope, job.branch, operation, job, startTime, stores, signal);
2991
2951
  if (writeError !== null) return writeError;
2992
2952
  updateDocumentRevision(document, job.scope, operation.index);
2953
+ document.operations = {
2954
+ ...document.operations,
2955
+ [job.scope]: [...document.operations[job.scope] ?? [], operation]
2956
+ };
2993
2957
  stores.writeCache.putState(documentId, job.scope, job.branch, operation.index, document);
2994
2958
  indexTxn.write([{
2995
2959
  ...operation,
@@ -3052,6 +3016,10 @@ var DocumentActionHandler = class {
3052
3016
  const writeError = await this.writeOperationToStore(documentId, document.header.documentType, job.scope, job.branch, operation, job, startTime, stores, signal);
3053
3017
  if (writeError !== null) return writeError;
3054
3018
  updateDocumentRevision(document, job.scope, operation.index);
3019
+ document.operations = {
3020
+ ...document.operations,
3021
+ [job.scope]: [...document.operations[job.scope] ?? [], operation]
3022
+ };
3055
3023
  stores.writeCache.putState(documentId, job.scope, job.branch, operation.index, document);
3056
3024
  indexTxn.write([{
3057
3025
  ...operation,
@@ -3068,66 +3036,40 @@ var DocumentActionHandler = class {
3068
3036
  });
3069
3037
  return buildSuccessResult(job, operation, documentId, document.header.documentType, resultingState, startTime);
3070
3038
  }
3071
- async executeAddRelationship(job, action, startTime, indexTxn, stores, sourceRemote = "", signal) {
3072
- if (job.scope !== "document") return buildErrorResult(job, /* @__PURE__ */ new Error(`ADD_RELATIONSHIP must be in "document" scope, got "${job.scope}"`), startTime);
3073
- const input = action.input;
3074
- if (!input.sourceId || !input.targetId || !input.relationshipType) return buildErrorResult(job, /* @__PURE__ */ new Error("ADD_RELATIONSHIP action requires sourceId, targetId, and relationshipType in input"), startTime);
3075
- if (input.sourceId === input.targetId) return buildErrorResult(job, /* @__PURE__ */ new Error("ADD_RELATIONSHIP: sourceId and targetId cannot be the same (self-relationships not allowed)"), startTime);
3076
- let sourceDoc;
3077
- try {
3078
- sourceDoc = await stores.writeCache.getState(input.sourceId, "document", job.branch, void 0, signal);
3079
- } catch (error) {
3080
- return buildErrorResult(job, /* @__PURE__ */ new Error(`ADD_RELATIONSHIP: source document ${input.sourceId} not found: ${error instanceof Error ? error.message : String(error)}`), startTime);
3081
- }
3082
- const operation = createOperation(action, getNextIndexForScope(sourceDoc, job.scope), 0, {
3083
- documentId: input.sourceId,
3084
- scope: job.scope,
3085
- branch: job.branch
3039
+ executeAddRelationship(job, action, startTime, indexTxn, stores, sourceRemote = "", signal) {
3040
+ 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 }) => {
3041
+ if (this.driveContainerTypes.has(sourceDoc.header.documentType)) {
3042
+ const collectionId = driveCollectionId(j.branch, input.sourceId);
3043
+ txn.addToCollection(collectionId, input.targetId);
3044
+ s.collectionMembershipCache.invalidate(input.targetId);
3045
+ }
3086
3046
  });
3087
- const writeError = await this.writeOperationToStore(input.sourceId, sourceDoc.header.documentType, job.scope, job.branch, operation, job, startTime, stores, signal);
3088
- if (writeError !== null) return writeError;
3089
- sourceDoc.header.lastModifiedAtUtcIso = operation.timestampUtcMs || (/* @__PURE__ */ new Date()).toISOString();
3090
- updateDocumentRevision(sourceDoc, job.scope, operation.index);
3091
- sourceDoc.operations = {
3092
- ...sourceDoc.operations,
3093
- [job.scope]: [...sourceDoc.operations[job.scope] ?? [], operation]
3094
- };
3095
- const scopeState = sourceDoc.state[job.scope];
3096
- const resultingStateObj = {
3097
- header: structuredClone(sourceDoc.header),
3098
- [job.scope]: scopeState === void 0 ? {} : structuredClone(scopeState)
3099
- };
3100
- const resultingState = JSON.stringify(resultingStateObj);
3101
- stores.writeCache.putState(input.sourceId, job.scope, job.branch, operation.index, sourceDoc);
3102
- indexTxn.write([{
3103
- ...operation,
3104
- documentId: input.sourceId,
3105
- documentType: sourceDoc.header.documentType,
3106
- branch: job.branch,
3107
- scope: job.scope,
3108
- sourceRemote
3109
- }]);
3110
- if (sourceDoc.header.documentType === "powerhouse/document-drive") {
3111
- const collectionId = driveCollectionId(job.branch, input.sourceId);
3112
- indexTxn.addToCollection(collectionId, input.targetId);
3113
- stores.collectionMembershipCache.invalidate(input.targetId);
3114
- }
3115
- stores.documentMetaCache.putDocumentMeta(input.sourceId, job.branch, {
3116
- state: sourceDoc.state.document,
3117
- documentType: sourceDoc.header.documentType,
3118
- documentScopeRevision: operation.index + 1
3047
+ }
3048
+ executeRemoveRelationship(job, action, startTime, indexTxn, stores, sourceRemote = "", signal) {
3049
+ return this.withRelationshipAction("REMOVE_RELATIONSHIP", job, action, startTime, indexTxn, stores, sourceRemote, signal, null, ({ indexTxn: txn, stores: s, sourceDoc, input, job: j }) => {
3050
+ if (this.driveContainerTypes.has(sourceDoc.header.documentType)) {
3051
+ const collectionId = driveCollectionId(j.branch, input.sourceId);
3052
+ txn.removeFromCollection(collectionId, input.targetId);
3053
+ s.collectionMembershipCache.invalidate(input.targetId);
3054
+ }
3119
3055
  });
3120
- return buildSuccessResult(job, operation, input.sourceId, sourceDoc.header.documentType, resultingState, startTime);
3121
3056
  }
3122
- async executeRemoveRelationship(job, action, startTime, indexTxn, stores, sourceRemote = "", signal) {
3123
- if (job.scope !== "document") return buildErrorResult(job, /* @__PURE__ */ new Error(`REMOVE_RELATIONSHIP must be in "document" scope, got "${job.scope}"`), startTime);
3057
+ executeUpdateRelationship(job, action, startTime, indexTxn, stores, sourceRemote = "", signal) {
3058
+ return this.withRelationshipAction("UPDATE_RELATIONSHIP", job, action, startTime, indexTxn, stores, sourceRemote, signal, null, null);
3059
+ }
3060
+ async withRelationshipAction(actionTypeName, job, action, startTime, indexTxn, stores, sourceRemote, signal, preValidate, postWrite) {
3061
+ if (job.scope !== "document") return buildErrorResult(job, /* @__PURE__ */ new Error(`${actionTypeName} must be in "document" scope, got "${job.scope}"`), startTime);
3124
3062
  const input = action.input;
3125
- if (!input.sourceId || !input.targetId || !input.relationshipType) return buildErrorResult(job, /* @__PURE__ */ new Error("REMOVE_RELATIONSHIP action requires sourceId, targetId, and relationshipType in input"), startTime);
3063
+ if (!input.sourceId || !input.targetId || !input.relationshipType) return buildErrorResult(job, /* @__PURE__ */ new Error(`${actionTypeName} action requires sourceId, targetId, and relationshipType in input`), startTime);
3064
+ if (preValidate !== null) {
3065
+ const validationError = preValidate(input);
3066
+ if (validationError !== null) return buildErrorResult(job, validationError, startTime);
3067
+ }
3126
3068
  let sourceDoc;
3127
3069
  try {
3128
3070
  sourceDoc = await stores.writeCache.getState(input.sourceId, "document", job.branch, void 0, signal);
3129
3071
  } catch (error) {
3130
- return buildErrorResult(job, /* @__PURE__ */ new Error(`REMOVE_RELATIONSHIP: source document ${input.sourceId} not found: ${error instanceof Error ? error.message : String(error)}`), startTime);
3072
+ return buildErrorResult(job, /* @__PURE__ */ new Error(`${actionTypeName}: source document ${input.sourceId} not found: ${error instanceof Error ? error.message : String(error)}`), startTime);
3131
3073
  }
3132
3074
  const operation = createOperation(action, getNextIndexForScope(sourceDoc, job.scope), 0, {
3133
3075
  documentId: input.sourceId,
@@ -3157,11 +3099,13 @@ var DocumentActionHandler = class {
3157
3099
  scope: job.scope,
3158
3100
  sourceRemote
3159
3101
  }]);
3160
- if (sourceDoc.header.documentType === "powerhouse/document-drive") {
3161
- const collectionId = driveCollectionId(job.branch, input.sourceId);
3162
- indexTxn.removeFromCollection(collectionId, input.targetId);
3163
- stores.collectionMembershipCache.invalidate(input.targetId);
3164
- }
3102
+ if (postWrite !== null) postWrite({
3103
+ indexTxn,
3104
+ stores,
3105
+ sourceDoc,
3106
+ input,
3107
+ job
3108
+ });
3165
3109
  stores.documentMetaCache.putDocumentMeta(input.sourceId, job.branch, {
3166
3110
  state: sourceDoc.state.document,
3167
3111
  documentType: sourceDoc.header.documentType,
@@ -3200,7 +3144,7 @@ var SignatureVerifier = class {
3200
3144
  if (!signer) continue;
3201
3145
  if (signer.signatures.length === 0) throw new InvalidSignatureError(documentId, `Action ${action.id} has signer but no signatures`);
3202
3146
  const publicKey = signer.app.key;
3203
- let isValid = false;
3147
+ let isValid;
3204
3148
  try {
3205
3149
  const tempOperation = {
3206
3150
  id: deriveOperationId(documentId, action.scope, branch, action.id),
@@ -3226,7 +3170,7 @@ var SignatureVerifier = class {
3226
3170
  if (!signer) continue;
3227
3171
  if (signer.signatures.length === 0) throw new InvalidSignatureError(documentId, `Operation ${operation.id} at index ${operation.index} has signer but no signatures`);
3228
3172
  const publicKey = signer.app.key;
3229
- let isValid = false;
3173
+ let isValid;
3230
3174
  try {
3231
3175
  isValid = await this.verifier(operation, publicKey);
3232
3176
  } catch (error) {
@@ -3250,7 +3194,8 @@ const documentScopeActions = [
3250
3194
  "DELETE_DOCUMENT",
3251
3195
  "UPGRADE_DOCUMENT",
3252
3196
  "ADD_RELATIONSHIP",
3253
- "REMOVE_RELATIONSHIP"
3197
+ "REMOVE_RELATIONSHIP",
3198
+ "UPDATE_RELATIONSHIP"
3254
3199
  ];
3255
3200
  /**
3256
3201
  * Simple job executor that processes a job by applying actions through document model reducers.
@@ -3260,7 +3205,7 @@ var SimpleJobExecutor = class {
3260
3205
  signatureVerifierModule;
3261
3206
  documentActionHandler;
3262
3207
  executionScope;
3263
- constructor(logger, registry, operationStore, eventBus, writeCache, operationIndex, documentMetaCache, collectionMembershipCache, config, signatureVerifier, executionScope) {
3208
+ constructor(logger, registry, operationStore, eventBus, writeCache, operationIndex, documentMetaCache, collectionMembershipCache, driveContainerTypes, config, signatureVerifier, executionScope) {
3264
3209
  this.logger = logger;
3265
3210
  this.registry = registry;
3266
3211
  this.operationStore = operationStore;
@@ -3269,6 +3214,7 @@ var SimpleJobExecutor = class {
3269
3214
  this.operationIndex = operationIndex;
3270
3215
  this.documentMetaCache = documentMetaCache;
3271
3216
  this.collectionMembershipCache = collectionMembershipCache;
3217
+ this.driveContainerTypes = driveContainerTypes;
3272
3218
  this.config = {
3273
3219
  maxSkipThreshold: config.maxSkipThreshold ?? MAX_SKIP_THRESHOLD,
3274
3220
  maxConcurrency: config.maxConcurrency ?? 1,
@@ -3278,7 +3224,7 @@ var SimpleJobExecutor = class {
3278
3224
  yieldDeadlineMs: config.yieldDeadlineMs ?? 50
3279
3225
  };
3280
3226
  this.signatureVerifierModule = new SignatureVerifier(signatureVerifier);
3281
- this.documentActionHandler = new DocumentActionHandler(registry, logger);
3227
+ this.documentActionHandler = new DocumentActionHandler(registry, logger, driveContainerTypes);
3282
3228
  this.executionScope = executionScope ?? new DefaultExecutionScope(operationStore, operationIndex, writeCache, documentMetaCache, collectionMembershipCache);
3283
3229
  }
3284
3230
  /**
@@ -3513,7 +3459,7 @@ var SimpleJobExecutor = class {
3513
3459
  } catch {}
3514
3460
  if (docMeta?.state.isDeleted) return buildErrorResult(job, new DocumentDeletedError(job.documentId, docMeta.state.deletedAtUtcIso), startTime);
3515
3461
  const scope = job.scope;
3516
- let latestRevision = 0;
3462
+ let latestRevision;
3517
3463
  try {
3518
3464
  latestRevision = (await stores.operationStore.getRevisions(job.documentId, job.branch, signal)).revision[scope] ?? 0;
3519
3465
  } catch {
@@ -3532,7 +3478,7 @@ var SimpleJobExecutor = class {
3532
3478
  const ts = operation.timestampUtcMs || "";
3533
3479
  if (ts < minIncomingTimestamp) minIncomingTimestamp = ts;
3534
3480
  }
3535
- let conflictingOps = [];
3481
+ let conflictingOps;
3536
3482
  try {
3537
3483
  conflictingOps = (await stores.operationStore.getConflicting(job.documentId, scope, job.branch, minIncomingTimestamp, void 0, signal)).results;
3538
3484
  } catch {
@@ -3547,7 +3493,9 @@ var SimpleJobExecutor = class {
3547
3493
  allOpsFromMinConflictingIndex = conflictingOps;
3548
3494
  }
3549
3495
  }
3496
+ const incomingActionIds = new Set(job.operations.map((op) => op.action.id));
3550
3497
  const nonSupersededOps = conflictingOps.filter((op) => {
3498
+ if (op.index < minIncomingIndex && !incomingActionIds.has(op.action.id)) return false;
3551
3499
  for (const laterOp of allOpsFromMinConflictingIndex) if (laterOp.index > op.index && laterOp.skip > 0) {
3552
3500
  if (laterOp.index - laterOp.skip <= op.index) return false;
3553
3501
  }
@@ -3585,7 +3533,10 @@ var SimpleJobExecutor = class {
3585
3533
  operationsWithContext: [],
3586
3534
  duration: Date.now() - startTime
3587
3535
  };
3588
- const reshuffledOperations = reshuffleByTimestamp({
3536
+ const reshuffledOperations = existingOpsToReshuffle.length === 0 && skipCount === 0 ? incomingOpsToApply.slice().sort((a, b) => a.index - b.index).map((operation, i) => ({
3537
+ ...operation,
3538
+ index: latestRevision + i
3539
+ })) : reshuffleByTimestamp({
3589
3540
  index: latestRevision,
3590
3541
  skip: skipCount
3591
3542
  }, existingOpsToReshuffle, incomingOpsToApply.map((operation) => ({
@@ -3854,10 +3805,6 @@ var BaseReadModel = class {
3854
3805
  };
3855
3806
  //#endregion
3856
3807
  //#region src/processors/utils.ts
3857
- const DRIVE_DOCUMENT_TYPE = "powerhouse/document-drive";
3858
- function isDriveCreation(op) {
3859
- return op.operation.action.type === "CREATE_DOCUMENT" && op.context.documentType === "powerhouse/document-drive";
3860
- }
3861
3808
  function isDriveDeletion(op) {
3862
3809
  return op.operation.action.type === "DELETE_DOCUMENT";
3863
3810
  }
@@ -3868,10 +3815,10 @@ function extractDriveHeader(op) {
3868
3815
  function extractDeletedDocumentId(op) {
3869
3816
  return op.operation.action.input.documentId ?? op.context.documentId;
3870
3817
  }
3871
- function createMinimalDriveHeader(driveId) {
3818
+ function createMinimalDriveHeader(driveId, documentType) {
3872
3819
  return {
3873
3820
  id: driveId,
3874
- documentType: DRIVE_DOCUMENT_TYPE,
3821
+ documentType,
3875
3822
  sig: {
3876
3823
  publicKey: {},
3877
3824
  nonce: ""
@@ -3916,15 +3863,17 @@ var ProcessorManager = class extends BaseReadModel {
3916
3863
  factoryRegistry = /* @__PURE__ */ new Map();
3917
3864
  processorsByDrive = /* @__PURE__ */ new Map();
3918
3865
  factoryToProcessors = /* @__PURE__ */ new Map();
3919
- knownDriveIds = /* @__PURE__ */ new Set();
3866
+ knownDrives = /* @__PURE__ */ new Map();
3920
3867
  cursorCache = /* @__PURE__ */ new Map();
3921
3868
  logger;
3922
- constructor(db, operationIndex, writeCache, consistencyTracker, logger) {
3869
+ driveContainerTypes;
3870
+ constructor(db, operationIndex, writeCache, consistencyTracker, logger, driveContainerTypes) {
3923
3871
  super(db, operationIndex, writeCache, consistencyTracker, {
3924
3872
  readModelId: "processor-manager",
3925
3873
  rebuildStateOnInit: true
3926
3874
  });
3927
3875
  this.logger = logger;
3876
+ this.driveContainerTypes = driveContainerTypes;
3928
3877
  }
3929
3878
  async init() {
3930
3879
  await super.init();
@@ -3940,8 +3889,8 @@ var ProcessorManager = class extends BaseReadModel {
3940
3889
  if (this.factoryRegistry.has(identifier)) await this.unregisterFactory(identifier);
3941
3890
  this.factoryRegistry.set(identifier, factory);
3942
3891
  this.factoryToProcessors.set(identifier, /* @__PURE__ */ new Map());
3943
- for (const driveId of this.knownDriveIds) {
3944
- const driveHeader = createMinimalDriveHeader(driveId);
3892
+ for (const [driveId, documentType] of this.knownDrives) {
3893
+ const driveHeader = createMinimalDriveHeader(driveId, documentType);
3945
3894
  await this.createProcessorsForDrive(driveId, identifier, factory, driveHeader);
3946
3895
  }
3947
3896
  }
@@ -3972,34 +3921,37 @@ var ProcessorManager = class extends BaseReadModel {
3972
3921
  }
3973
3922
  async detectAndRegisterNewDrives(operations) {
3974
3923
  for (const op of operations) {
3975
- if (!isDriveCreation(op)) continue;
3924
+ if (!this.isDriveCreation(op)) continue;
3976
3925
  const driveId = op.context.documentId;
3977
- if (this.knownDriveIds.has(driveId)) continue;
3978
- this.knownDriveIds.add(driveId);
3926
+ if (this.knownDrives.has(driveId)) continue;
3927
+ this.knownDrives.set(driveId, op.context.documentType);
3979
3928
  const driveHeader = extractDriveHeader(op);
3980
3929
  if (!driveHeader) continue;
3981
3930
  for (const [identifier, factory] of this.factoryRegistry) await this.createProcessorsForDrive(driveId, identifier, factory, driveHeader);
3982
3931
  }
3983
3932
  }
3933
+ isDriveCreation(op) {
3934
+ return op.operation.action.type === "CREATE_DOCUMENT" && this.driveContainerTypes.has(op.context.documentType);
3935
+ }
3984
3936
  async detectAndCleanupDeletedDrives(operations) {
3985
3937
  for (const op of operations) {
3986
3938
  if (!isDriveDeletion(op)) continue;
3987
3939
  const driveId = extractDeletedDocumentId(op);
3988
- if (!driveId || !this.knownDriveIds.has(driveId)) continue;
3940
+ if (!driveId || !this.knownDrives.has(driveId)) continue;
3989
3941
  if (!this.isDeletedDocumentADrive(driveId)) continue;
3990
3942
  await this.cleanupDriveProcessors(driveId);
3991
- this.knownDriveIds.delete(driveId);
3943
+ this.knownDrives.delete(driveId);
3992
3944
  }
3993
3945
  }
3994
3946
  async discoverExistingDrives() {
3995
- const drives = await this.db.selectFrom("DocumentSnapshot").select("documentId").where("documentType", "=", DRIVE_DOCUMENT_TYPE).where("isDeleted", "=", false).execute();
3996
- for (const drive of drives) this.knownDriveIds.add(drive.documentId);
3947
+ const drives = await this.db.selectFrom("DocumentSnapshot").select(["documentId", "documentType"]).where("documentType", "in", [...this.driveContainerTypes]).where("isDeleted", "=", false).execute();
3948
+ for (const drive of drives) this.knownDrives.set(drive.documentId, drive.documentType);
3997
3949
  }
3998
3950
  isDeletedDocumentADrive(documentId) {
3999
- return this.knownDriveIds.has(documentId);
3951
+ return this.knownDrives.has(documentId);
4000
3952
  }
4001
3953
  async createProcessorsForDrive(driveId, identifier, factory, driveHeader) {
4002
- let records = [];
3954
+ let records;
4003
3955
  try {
4004
3956
  records = await factory(driveHeader);
4005
3957
  } catch (error) {
@@ -4668,11 +4620,11 @@ var KyselyDocumentView = class extends BaseReadModel {
4668
4620
  const { documentId, scope, branch, documentType, resultingState } = context;
4669
4621
  const { index, hash } = operation;
4670
4622
  if (!resultingState) throw new Error(`Missing resultingState in context for operation ${operation.id || "unknown"}. IDocumentView requires resultingState from upstream - it does not rebuild documents.`);
4671
- let fullState = {};
4623
+ let fullState;
4672
4624
  try {
4673
4625
  fullState = JSON.parse(resultingState);
4674
4626
  } catch (error) {
4675
- throw new Error(`Failed to parse resultingState for operation ${operation.id || "unknown"}: ${error instanceof Error ? error.message : String(error)}`);
4627
+ throw new Error(`Failed to parse resultingState for operation ${operation.id || "unknown"}: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
4676
4628
  }
4677
4629
  const operationType = operation.action.type;
4678
4630
  if (operationType === "DELETE_DOCUMENT") {
@@ -4995,6 +4947,142 @@ var NullDocumentModelResolver = class {
4995
4947
  }
4996
4948
  };
4997
4949
  //#endregion
4950
+ //#region src/registry/implementation.ts
4951
+ /**
4952
+ * In-memory implementation of the IDocumentModelRegistry interface.
4953
+ * Manages document model modules with version-aware storage and upgrade manifest support.
4954
+ */
4955
+ var DocumentModelRegistry = class {
4956
+ modules = [];
4957
+ manifests = [];
4958
+ registerModules(...modules) {
4959
+ return modules.map((module) => {
4960
+ try {
4961
+ const documentType = module.documentModel.global.id;
4962
+ const version = module.version ?? 1;
4963
+ for (let i = 0; i < this.modules.length; i++) {
4964
+ const existing = this.modules[i];
4965
+ const existingType = existing.documentModel.global.id;
4966
+ const existingVersion = existing.version ?? 1;
4967
+ if (existingType === documentType && existingVersion === version) throw new DuplicateModuleError(documentType, version);
4968
+ }
4969
+ this.modules.push(module);
4970
+ return {
4971
+ status: "success",
4972
+ item: module
4973
+ };
4974
+ } catch (error) {
4975
+ return {
4976
+ status: "error",
4977
+ item: module,
4978
+ error: error instanceof Error ? error : new Error(String(error))
4979
+ };
4980
+ }
4981
+ });
4982
+ }
4983
+ unregisterModules(...documentTypes) {
4984
+ let allFound = true;
4985
+ for (const documentType of documentTypes) {
4986
+ if (!this.modules.some((m) => m.documentModel.global.id === documentType)) allFound = false;
4987
+ this.modules = this.modules.filter((m) => m.documentModel.global.id !== documentType);
4988
+ }
4989
+ return allFound;
4990
+ }
4991
+ getModule(documentType, version) {
4992
+ let latestModule;
4993
+ let latestVersion = -1;
4994
+ for (let i = 0; i < this.modules.length; i++) {
4995
+ const module = this.modules[i];
4996
+ const moduleType = module.documentModel.global.id;
4997
+ const moduleVersion = module.version ?? 1;
4998
+ if (moduleType === documentType) {
4999
+ if (version !== void 0 && moduleVersion === version) return module;
5000
+ if (moduleVersion > latestVersion) {
5001
+ latestModule = module;
5002
+ latestVersion = moduleVersion;
5003
+ }
5004
+ }
5005
+ }
5006
+ if (version === void 0 && latestModule !== void 0) return latestModule;
5007
+ throw new ModuleNotFoundError(documentType, version);
5008
+ }
5009
+ getAllModules() {
5010
+ return [...this.modules];
5011
+ }
5012
+ clear() {
5013
+ this.modules = [];
5014
+ this.manifests = [];
5015
+ }
5016
+ getSupportedVersions(documentType) {
5017
+ const versions = [];
5018
+ for (const module of this.modules) if (module.documentModel.global.id === documentType) versions.push(module.version ?? 1);
5019
+ if (versions.length === 0) throw new ModuleNotFoundError(documentType);
5020
+ return versions.sort((a, b) => a - b);
5021
+ }
5022
+ getLatestVersion(documentType) {
5023
+ let latest = -1;
5024
+ let found = false;
5025
+ for (const module of this.modules) if (module.documentModel.global.id === documentType) {
5026
+ found = true;
5027
+ const version = module.version ?? 1;
5028
+ if (version > latest) latest = version;
5029
+ }
5030
+ if (!found) throw new ModuleNotFoundError(documentType);
5031
+ return latest;
5032
+ }
5033
+ registerUpgradeManifests(...manifestsToRegister) {
5034
+ return manifestsToRegister.map((manifestToRegister) => {
5035
+ try {
5036
+ if (!manifestToRegister.documentType) throw new Error("Upgrade manifest is missing a documentType");
5037
+ for (const registeredManifest of this.manifests) if (registeredManifest.documentType === manifestToRegister.documentType) throw new DuplicateManifestError(manifestToRegister.documentType);
5038
+ this.manifests.push(manifestToRegister);
5039
+ return {
5040
+ status: "success",
5041
+ item: manifestToRegister
5042
+ };
5043
+ } catch (error) {
5044
+ return {
5045
+ status: "error",
5046
+ item: manifestToRegister,
5047
+ error: error instanceof Error ? error : new Error(String(error))
5048
+ };
5049
+ }
5050
+ });
5051
+ }
5052
+ unregisterUpgradeManifests(...documentTypes) {
5053
+ let allFound = true;
5054
+ for (const documentType of documentTypes) {
5055
+ if (!this.manifests.some((m) => m.documentType === documentType)) allFound = false;
5056
+ this.manifests = this.manifests.filter((m) => m.documentType !== documentType);
5057
+ }
5058
+ return allFound;
5059
+ }
5060
+ getUpgradeManifest(documentType) {
5061
+ for (let i = 0; i < this.manifests.length; i++) if (this.manifests[i].documentType === documentType) return this.manifests[i];
5062
+ throw new ManifestNotFoundError(documentType);
5063
+ }
5064
+ computeUpgradePath(documentType, fromVersion, toVersion) {
5065
+ if (fromVersion === toVersion) return [];
5066
+ if (toVersion < fromVersion) throw new DowngradeNotSupportedError(documentType, fromVersion, toVersion);
5067
+ const manifest = this.getUpgradeManifest(documentType);
5068
+ const path = [];
5069
+ for (let v = fromVersion + 1; v <= toVersion; v++) {
5070
+ const key = `v${v}`;
5071
+ if (!(key in manifest.upgrades)) throw new MissingUpgradeTransitionError(documentType, v - 1, v);
5072
+ const transition = manifest.upgrades[key];
5073
+ path.push(transition);
5074
+ }
5075
+ return path;
5076
+ }
5077
+ getUpgradeReducer(documentType, fromVersion, toVersion) {
5078
+ if (toVersion !== fromVersion + 1) throw new InvalidUpgradeStepError(documentType, fromVersion, toVersion);
5079
+ const manifest = this.getUpgradeManifest(documentType);
5080
+ const key = `v${toVersion}`;
5081
+ if (!(key in manifest.upgrades)) throw new MissingUpgradeTransitionError(documentType, fromVersion, toVersion);
5082
+ return manifest.upgrades[key].upgradeReducer;
5083
+ }
5084
+ };
5085
+ //#endregion
4998
5086
  //#region src/shared/consistency-tracker.ts
4999
5087
  /**
5000
5088
  * Creates a consistency key from documentId, scope, and branch.
@@ -5126,6 +5214,7 @@ var KyselyDocumentIndexer = class extends BaseReadModel {
5126
5214
  const actionType = operation.action.type;
5127
5215
  if (actionType === "ADD_RELATIONSHIP") await this.handleAddRelationship(trx, operation);
5128
5216
  else if (actionType === "REMOVE_RELATIONSHIP") await this.handleRemoveRelationship(trx, operation);
5217
+ else if (actionType === "UPDATE_RELATIONSHIP") await this.handleUpdateRelationship(trx, operation);
5129
5218
  }
5130
5219
  });
5131
5220
  }
@@ -5340,6 +5429,13 @@ var KyselyDocumentIndexer = class extends BaseReadModel {
5340
5429
  const input = operation.action.input;
5341
5430
  await trx.deleteFrom("DocumentRelationship").where("sourceId", "=", input.sourceId).where("targetId", "=", input.targetId).where("relationshipType", "=", input.relationshipType).execute();
5342
5431
  }
5432
+ async handleUpdateRelationship(trx, operation) {
5433
+ const input = operation.action.input;
5434
+ await trx.updateTable("DocumentRelationship").set({
5435
+ metadata: input.metadata,
5436
+ updatedAt: /* @__PURE__ */ new Date()
5437
+ }).where("sourceId", "=", input.sourceId).where("targetId", "=", input.targetId).where("relationshipType", "=", input.relationshipType).execute();
5438
+ }
5343
5439
  };
5344
5440
  //#endregion
5345
5441
  //#region src/storage/kysely/keyframe-store.ts
@@ -5403,6 +5499,29 @@ var KyselyKeyframeStore = class KyselyKeyframeStore {
5403
5499
  }
5404
5500
  };
5405
5501
  //#endregion
5502
+ //#region src/storage/kysely/pagination.ts
5503
+ const DEFAULT_LIMIT = 100;
5504
+ function paginateRows(rows, paging, cursorOf, toItem, refetch) {
5505
+ let hasMore = false;
5506
+ let items = rows;
5507
+ if (paging?.limit && rows.length > paging.limit) {
5508
+ hasMore = true;
5509
+ items = rows.slice(0, paging.limit);
5510
+ }
5511
+ const nextCursor = hasMore && items.length > 0 ? cursorOf(items[items.length - 1]).toString() : void 0;
5512
+ const cursor = paging?.cursor || "0";
5513
+ const limit = paging?.limit || DEFAULT_LIMIT;
5514
+ return {
5515
+ results: items.map(toItem),
5516
+ options: {
5517
+ cursor,
5518
+ limit
5519
+ },
5520
+ nextCursor,
5521
+ next: hasMore ? () => refetch(nextCursor, limit) : void 0
5522
+ };
5523
+ }
5524
+ //#endregion
5406
5525
  //#region src/storage/interfaces.ts
5407
5526
  /**
5408
5527
  * Thrown when an operation with the same identity already exists in the store.
@@ -5486,7 +5605,7 @@ var KyselyOperationStore = class KyselyOperationStore {
5486
5605
  });
5487
5606
  }
5488
5607
  async executeApply(trx, documentId, documentType, scope, branch, revision, fn, signal) {
5489
- if (signal?.aborted) throw new Error("Operation aborted");
5608
+ throwIfAborted(signal);
5490
5609
  const latestOp = await trx.selectFrom("Operation").selectAll().where("documentId", "=", documentId).where("scope", "=", scope).where("branch", "=", branch).orderBy("index", "desc").limit(1).executeTakeFirst();
5491
5610
  const currentRevision = latestOp ? latestOp.index : -1;
5492
5611
  if (currentRevision !== revision - 1) throw new RevisionMismatchError(currentRevision + 1, revision);
@@ -5514,7 +5633,7 @@ var KyselyOperationStore = class KyselyOperationStore {
5514
5633
  }
5515
5634
  }
5516
5635
  async getSince(documentId, scope, branch, revision, filter, paging, signal) {
5517
- if (signal?.aborted) throw new Error("Operation aborted");
5636
+ throwIfAborted(signal);
5518
5637
  let query = this.queryExecutor.selectFrom("Operation").selectAll().where("documentId", "=", documentId).where("scope", "=", scope).where("branch", "=", branch).where("index", ">", revision).orderBy("index", "asc");
5519
5638
  if (filter) {
5520
5639
  if (filter.actionTypes && filter.actionTypes.length > 0) {
@@ -5530,93 +5649,39 @@ var KyselyOperationStore = class KyselyOperationStore {
5530
5649
  if (cursorValue > 0) query = query.where("index", ">", cursorValue);
5531
5650
  if (paging.limit) query = query.limit(paging.limit + 1);
5532
5651
  }
5533
- const rows = await query.execute();
5534
- let hasMore = false;
5535
- let items = rows;
5536
- if (paging?.limit && rows.length > paging.limit) {
5537
- hasMore = true;
5538
- items = rows.slice(0, paging.limit);
5539
- }
5540
- const nextCursor = hasMore && items.length > 0 ? items[items.length - 1].index.toString() : void 0;
5541
- const cursor = paging?.cursor || "0";
5542
- const limit = paging?.limit || 100;
5543
- return {
5544
- results: items.map((row) => this.rowToOperation(row)),
5545
- options: {
5546
- cursor,
5547
- limit
5548
- },
5549
- nextCursor,
5550
- next: hasMore ? () => this.getSince(documentId, scope, branch, revision, filter, {
5551
- cursor: nextCursor,
5552
- limit
5553
- }, signal) : void 0
5554
- };
5652
+ return paginateRows(await query.execute(), paging, (row) => row.index, (row) => this.rowToOperation(row), (cursor, limit) => this.getSince(documentId, scope, branch, revision, filter, {
5653
+ cursor,
5654
+ limit
5655
+ }, signal));
5555
5656
  }
5556
5657
  async getSinceId(id, paging, signal) {
5557
- if (signal?.aborted) throw new Error("Operation aborted");
5658
+ throwIfAborted(signal);
5558
5659
  let query = this.queryExecutor.selectFrom("Operation").selectAll().where("id", ">", id).orderBy("id", "asc");
5559
5660
  if (paging) {
5560
5661
  const cursorValue = Number.parseInt(paging.cursor, 10);
5561
5662
  if (cursorValue > 0) query = query.where("id", ">", cursorValue);
5562
5663
  if (paging.limit) query = query.limit(paging.limit + 1);
5563
5664
  }
5564
- const rows = await query.execute();
5565
- let hasMore = false;
5566
- let items = rows;
5567
- if (paging?.limit && rows.length > paging.limit) {
5568
- hasMore = true;
5569
- items = rows.slice(0, paging.limit);
5570
- }
5571
- const nextCursor = hasMore && items.length > 0 ? items[items.length - 1].id.toString() : void 0;
5572
- const cursor = paging?.cursor || "0";
5573
- const limit = paging?.limit || 100;
5574
- return {
5575
- results: items.map((row) => this.rowToOperationWithContext(row)),
5576
- options: {
5577
- cursor,
5578
- limit
5579
- },
5580
- nextCursor,
5581
- next: hasMore ? () => this.getSinceId(id, {
5582
- cursor: nextCursor,
5583
- limit
5584
- }, signal) : void 0
5585
- };
5665
+ return paginateRows(await query.execute(), paging, (row) => row.id, (row) => this.rowToOperationWithContext(row), (cursor, limit) => this.getSinceId(id, {
5666
+ cursor,
5667
+ limit
5668
+ }, signal));
5586
5669
  }
5587
5670
  async getConflicting(documentId, scope, branch, minTimestamp, paging, signal) {
5588
- if (signal?.aborted) throw new Error("Operation aborted");
5671
+ throwIfAborted(signal);
5589
5672
  let query = this.queryExecutor.selectFrom("Operation").selectAll().where("documentId", "=", documentId).where("scope", "=", scope).where("branch", "=", branch).where("timestampUtcMs", ">=", new Date(minTimestamp)).orderBy("index", "asc");
5590
5673
  if (paging) {
5591
5674
  const cursorValue = Number.parseInt(paging.cursor, 10);
5592
5675
  if (cursorValue > 0) query = query.where("index", ">", cursorValue);
5593
5676
  if (paging.limit) query = query.limit(paging.limit + 1);
5594
5677
  }
5595
- const rows = await query.execute();
5596
- let hasMore = false;
5597
- let items = rows;
5598
- if (paging?.limit && rows.length > paging.limit) {
5599
- hasMore = true;
5600
- items = rows.slice(0, paging.limit);
5601
- }
5602
- const nextCursor = hasMore && items.length > 0 ? items[items.length - 1].index.toString() : void 0;
5603
- const cursor = paging?.cursor || "0";
5604
- const limit = paging?.limit || 100;
5605
- return {
5606
- results: items.map((row) => this.rowToOperation(row)),
5607
- options: {
5608
- cursor,
5609
- limit
5610
- },
5611
- nextCursor,
5612
- next: hasMore ? () => this.getConflicting(documentId, scope, branch, minTimestamp, {
5613
- cursor: nextCursor,
5614
- limit
5615
- }, signal) : void 0
5616
- };
5678
+ return paginateRows(await query.execute(), paging, (row) => row.index, (row) => this.rowToOperation(row), (cursor, limit) => this.getConflicting(documentId, scope, branch, minTimestamp, {
5679
+ cursor,
5680
+ limit
5681
+ }, signal));
5617
5682
  }
5618
5683
  async getRevisions(documentId, branch, signal) {
5619
- if (signal?.aborted) throw new Error("Operation aborted");
5684
+ throwIfAborted(signal);
5620
5685
  const scopeRevisions = await this.queryExecutor.selectFrom("Operation as o1").select([
5621
5686
  "o1.scope",
5622
5687
  "o1.index",
@@ -6610,6 +6675,38 @@ function batchOperationsByDocument(operations) {
6610
6675
  flushBatch();
6611
6676
  return batches;
6612
6677
  }
6678
+ /**
6679
+ * Splits a sorted page of operations into a safe-to-emit prefix and a
6680
+ * deferred tail containing the trailing run that shares the same
6681
+ * (documentId, branch, scope, timestampUtcMs) as the last operation.
6682
+ *
6683
+ * The page is assumed to be sorted by (documentId, scope, ordinal), so a
6684
+ * same-(docId, scope, ts) run is contiguous and lives at the end of any
6685
+ * page that contains its last member. Holding that tail back lets callers
6686
+ * prepend it to the next page so a single producer-side execute() call
6687
+ * never gets split across two outbound envelopes.
6688
+ */
6689
+ function splitTrailingSameTimestampRun(operations) {
6690
+ if (operations.length === 0) return {
6691
+ emit: [],
6692
+ carry: []
6693
+ };
6694
+ const last = operations[operations.length - 1];
6695
+ const lastDocId = last.context.documentId;
6696
+ const lastBranch = last.context.branch;
6697
+ const lastScope = last.context.scope;
6698
+ const lastTs = last.operation.timestampUtcMs;
6699
+ let carryStart = operations.length;
6700
+ for (let i = operations.length - 1; i >= 0; i--) {
6701
+ const op = operations[i];
6702
+ if (op.context.documentId === lastDocId && op.context.branch === lastBranch && op.context.scope === lastScope && op.operation.timestampUtcMs === lastTs) carryStart = i;
6703
+ else break;
6704
+ }
6705
+ return {
6706
+ emit: operations.slice(0, carryStart),
6707
+ carry: operations.slice(carryStart)
6708
+ };
6709
+ }
6613
6710
  function toOperationWithContext(entry) {
6614
6711
  return {
6615
6712
  operation: {
@@ -6676,25 +6773,6 @@ function consolidateSyncOperations(syncOps) {
6676
6773
  }
6677
6774
  return result;
6678
6775
  }
6679
- function mergeCollectionMemberships(events) {
6680
- const mergedMemberships = {};
6681
- for (const event of events) {
6682
- if (event.collectionMemberships) for (const [docId, collections] of Object.entries(event.collectionMemberships)) {
6683
- if (!(docId in mergedMemberships)) mergedMemberships[docId] = [];
6684
- for (const c of collections) if (!mergedMemberships[docId].includes(c)) mergedMemberships[docId].push(c);
6685
- }
6686
- for (const op of event.operations) {
6687
- const action = op.operation.action;
6688
- if (action.type !== "ADD_RELATIONSHIP") continue;
6689
- const input = action.input;
6690
- if (!input?.sourceId || !input.targetId) continue;
6691
- const collectionId = driveCollectionId(op.context.branch, input.sourceId);
6692
- if (!(input.targetId in mergedMemberships)) mergedMemberships[input.targetId] = [];
6693
- if (!mergedMemberships[input.targetId].includes(collectionId)) mergedMemberships[input.targetId].push(collectionId);
6694
- }
6695
- }
6696
- return mergedMemberships;
6697
- }
6698
6776
  /**
6699
6777
  * Chunks sync operations into batches that respect dependency-connected
6700
6778
  * components. SyncOps linked by jobDependencies are kept in the same chunk.
@@ -8023,12 +8101,14 @@ var KyselySyncRemoteStorage = class {
8023
8101
  //#region src/sync/batch-aggregator.ts
8024
8102
  var BatchAggregator = class {
8025
8103
  logger;
8104
+ driveContainerTypes;
8026
8105
  onBatchReady;
8027
8106
  queue = [];
8028
8107
  processing = false;
8029
8108
  pendingBatches = /* @__PURE__ */ new Map();
8030
- constructor(logger, onBatchReady) {
8109
+ constructor(logger, driveContainerTypes, onBatchReady) {
8031
8110
  this.logger = logger;
8111
+ this.driveContainerTypes = driveContainerTypes;
8032
8112
  this.onBatchReady = onBatchReady;
8033
8113
  }
8034
8114
  async enqueueWriteReady(event) {
@@ -8087,7 +8167,7 @@ var BatchAggregator = class {
8087
8167
  }
8088
8168
  }
8089
8169
  prepareBatch(events) {
8090
- const collectionMemberships = mergeCollectionMemberships(events);
8170
+ const collectionMemberships = this.mergeCollectionMemberships(events);
8091
8171
  const isBatch = events.length > 1;
8092
8172
  const priorJobIds = [];
8093
8173
  const entries = [];
@@ -8103,6 +8183,26 @@ var BatchAggregator = class {
8103
8183
  entries
8104
8184
  };
8105
8185
  }
8186
+ mergeCollectionMemberships(events) {
8187
+ const mergedMemberships = {};
8188
+ for (const event of events) {
8189
+ if (event.collectionMemberships) for (const [docId, collections] of Object.entries(event.collectionMemberships)) {
8190
+ if (!(docId in mergedMemberships)) mergedMemberships[docId] = [];
8191
+ for (const c of collections) if (!mergedMemberships[docId].includes(c)) mergedMemberships[docId].push(c);
8192
+ }
8193
+ for (const op of event.operations) {
8194
+ const action = op.operation.action;
8195
+ if (action.type !== "ADD_RELATIONSHIP") continue;
8196
+ if (!this.driveContainerTypes.has(op.context.documentType)) continue;
8197
+ const input = action.input;
8198
+ if (!input?.sourceId || !input.targetId) continue;
8199
+ const collectionId = driveCollectionId(op.context.branch, input.sourceId);
8200
+ if (!(input.targetId in mergedMemberships)) mergedMemberships[input.targetId] = [];
8201
+ if (!mergedMemberships[input.targetId].includes(collectionId)) mergedMemberships[input.targetId].push(collectionId);
8202
+ }
8203
+ }
8204
+ return mergedMemberships;
8205
+ }
8106
8206
  };
8107
8207
  //#endregion
8108
8208
  //#region src/sync/sync-awaiter.ts
@@ -8354,7 +8454,7 @@ var SyncManager = class {
8354
8454
  planKeyToJobUuid = /* @__PURE__ */ new Map();
8355
8455
  lastEnqueuedJobIdByKey = /* @__PURE__ */ new Map();
8356
8456
  inboxChunkChain = Promise.resolve();
8357
- constructor(logger, remoteStorage, cursorStorage, deadLetterStorage, channelFactory, operationIndex, reactor, eventBus, config = {}) {
8457
+ constructor(logger, remoteStorage, cursorStorage, deadLetterStorage, channelFactory, operationIndex, reactor, eventBus, driveContainerTypes, config = {}) {
8358
8458
  this.logger = logger;
8359
8459
  this.remoteStorage = remoteStorage;
8360
8460
  this.cursorStorage = cursorStorage;
@@ -8371,7 +8471,7 @@ var SyncManager = class {
8371
8471
  this.awaiter = new JobAwaiter(eventBus, (jobId, signal) => reactor.getJobStatus(jobId, signal));
8372
8472
  this.syncAwaiter = new SyncAwaiter(eventBus);
8373
8473
  this.isShutdown = false;
8374
- this.batchAggregator = new BatchAggregator(logger, (batch) => this.processCompleteBatch(batch));
8474
+ this.batchAggregator = new BatchAggregator(logger, driveContainerTypes, (batch) => this.processCompleteBatch(batch));
8375
8475
  this.syncStatusTracker = new SyncStatusTracker();
8376
8476
  }
8377
8477
  async startup() {
@@ -8744,8 +8844,8 @@ var SyncManager = class {
8744
8844
  }
8745
8845
  if (this.isShutdown) return;
8746
8846
  for (const plan of jobs) {
8847
+ if (!(plan.key in result.jobs)) continue;
8747
8848
  const info = result.jobs[plan.key];
8748
- if (!info) continue;
8749
8849
  this.recordPlanKeyMapping(plan.key, info.id);
8750
8850
  const fifoKey = `${plan.documentId}:${plan.scope}:${plan.branch}`;
8751
8851
  this.lastEnqueuedJobIdByKey.set(fifoKey, info.id);
@@ -8785,41 +8885,55 @@ var SyncManager = class {
8785
8885
  const composedSignal = signal ? AbortSignal.any([signal, this.abortController.signal]) : this.abortController.signal;
8786
8886
  let maxOrdinal = ackOrdinal;
8787
8887
  const lastJobByDoc = /* @__PURE__ */ new Map();
8888
+ let prevChainJobId;
8788
8889
  const sinceTimestamp = remote.options.sinceTimestampUtcMs;
8890
+ const emitBatches = (operations) => {
8891
+ if (operations.length === 0) return;
8892
+ const batches = batchOperationsByDocument(operations);
8893
+ const syncOps = [];
8894
+ for (const batch of batches) {
8895
+ const jobId = crypto.randomUUID();
8896
+ const prevJobId = lastJobByDoc.get(batch.documentId);
8897
+ const deps = [];
8898
+ if (prevJobId) deps.push(prevJobId);
8899
+ if (mode === OutboxMode.BatchTriggered && prevChainJobId && prevChainJobId !== prevJobId) deps.push(prevChainJobId);
8900
+ const syncOp = new SyncOperation(crypto.randomUUID(), jobId, deps, remote.name, batch.documentId, [batch.scope], batch.branch, batch.operations);
8901
+ syncOps.push(syncOp);
8902
+ lastJobByDoc.set(batch.documentId, jobId);
8903
+ if (mode === OutboxMode.BatchTriggered) prevChainJobId = jobId;
8904
+ }
8905
+ remote.channel.outbox.add(...syncOps);
8906
+ };
8789
8907
  let page = await this.operationIndex.find(remote.collectionId, ackOrdinal, { excludeSourceRemote: remote.name }, void 0, composedSignal);
8908
+ let carry = [];
8790
8909
  let hasMore;
8791
8910
  do {
8792
8911
  if (composedSignal.aborted) return;
8793
8912
  for (const entry of page.results) maxOrdinal = Math.max(maxOrdinal, entry.ordinal ?? 0);
8794
8913
  let operations = page.results.map((entry) => toOperationWithContext(entry));
8914
+ if (carry.length > 0) {
8915
+ operations = [...carry, ...operations];
8916
+ carry = [];
8917
+ }
8795
8918
  if (sinceTimestamp && sinceTimestamp !== "0") operations = operations.filter((op) => op.operation.timestampUtcMs >= sinceTimestamp);
8796
8919
  operations = filterOperations(operations, remote.filter);
8797
8920
  operations = operations.filter((op) => !this.quarantinedDocumentIds.has(op.context.documentId));
8921
+ hasMore = !!page.next;
8798
8922
  if (operations.length > 0) {
8799
8923
  operations.sort((a, b) => {
8800
8924
  if (a.context.documentId !== b.context.documentId) return a.context.documentId < b.context.documentId ? -1 : 1;
8801
8925
  if (a.context.scope !== b.context.scope) return a.context.scope < b.context.scope ? -1 : 1;
8802
8926
  return a.context.ordinal - b.context.ordinal;
8803
8927
  });
8804
- const batches = batchOperationsByDocument(operations);
8805
- const syncOps = [];
8806
- let prevChainJobId;
8807
- for (const batch of batches) {
8808
- const jobId = crypto.randomUUID();
8809
- const prevJobId = lastJobByDoc.get(batch.documentId);
8810
- const deps = [];
8811
- if (prevJobId) deps.push(prevJobId);
8812
- if (mode === OutboxMode.BatchTriggered && prevChainJobId && prevChainJobId !== prevJobId) deps.push(prevChainJobId);
8813
- const syncOp = new SyncOperation(crypto.randomUUID(), jobId, deps, remote.name, batch.documentId, [batch.scope], batch.branch, batch.operations);
8814
- syncOps.push(syncOp);
8815
- lastJobByDoc.set(batch.documentId, jobId);
8816
- if (mode === OutboxMode.BatchTriggered) prevChainJobId = jobId;
8817
- }
8818
- remote.channel.outbox.add(...syncOps);
8928
+ if (hasMore) {
8929
+ const split = splitTrailingSameTimestampRun(operations);
8930
+ carry = split.carry;
8931
+ emitBatches(split.emit);
8932
+ } else emitBatches(operations);
8819
8933
  }
8820
- hasMore = !!page.next;
8821
8934
  if (hasMore) page = await page.next();
8822
8935
  } while (hasMore);
8936
+ if (carry.length > 0) emitBatches(carry);
8823
8937
  remote.channel.outbox.advanceOrdinal(maxOrdinal);
8824
8938
  }
8825
8939
  };
@@ -8855,15 +8969,15 @@ var SyncBuilder = class {
8855
8969
  this.config.maxInboxBatchSize = limit;
8856
8970
  return this;
8857
8971
  }
8858
- build(reactor, logger, operationIndex, eventBus, db) {
8859
- return this.buildModule(reactor, logger, operationIndex, eventBus, db).syncManager;
8972
+ build(reactor, logger, operationIndex, eventBus, db, driveContainerTypes) {
8973
+ return this.buildModule(reactor, logger, operationIndex, eventBus, db, driveContainerTypes).syncManager;
8860
8974
  }
8861
- buildModule(reactor, logger, operationIndex, eventBus, db) {
8975
+ buildModule(reactor, logger, operationIndex, eventBus, db, driveContainerTypes) {
8862
8976
  if (!this.channelFactory) throw new Error("Channel factory is required");
8863
8977
  const remoteStorage = this.remoteStorage ?? new KyselySyncRemoteStorage(db);
8864
8978
  const cursorStorage = this.cursorStorage ?? new KyselySyncCursorStorage(db);
8865
8979
  const deadLetterStorage = this.deadLetterStorage ?? new KyselySyncDeadLetterStorage(db);
8866
- const syncManager = new SyncManager(logger, remoteStorage, cursorStorage, deadLetterStorage, this.channelFactory, operationIndex, reactor, eventBus, this.config);
8980
+ const syncManager = new SyncManager(logger, remoteStorage, cursorStorage, deadLetterStorage, this.channelFactory, operationIndex, reactor, eventBus, driveContainerTypes, this.config);
8867
8981
  return {
8868
8982
  remoteStorage,
8869
8983
  cursorStorage,
@@ -8882,6 +8996,9 @@ async function createDefaultDatabase() {
8882
8996
  return new Kysely({ dialect: new PGliteDialect(new PGlite()) });
8883
8997
  }
8884
8998
  //#endregion
8999
+ //#region src/core/drive-container-types.ts
9000
+ const DEFAULT_DRIVE_CONTAINER_TYPES = new Set(["powerhouse/document-drive", "powerhouse/reactor-drive"]);
9001
+ //#endregion
8885
9002
  //#region src/shared/factories.ts
8886
9003
  /**
8887
9004
  * Factory method to create a ShutdownStatus that can be updated
@@ -8976,7 +9093,7 @@ var Reactor = class {
8976
9093
  }
8977
9094
  getDocumentModels(namespace, paging, signal) {
8978
9095
  this.logger.verbose("getDocumentModels(@namespace, @paging)", namespace, paging);
8979
- if (signal?.aborted) throw new AbortError();
9096
+ throwIfAborted(signal, () => new AbortError());
8980
9097
  const filteredModels = this.documentModelRegistry.getAllModules().filter((module) => !namespace || module.documentModel.global.id.startsWith(namespace));
8981
9098
  const startIndex = paging ? parseInt(paging.cursor) || 0 : 0;
8982
9099
  const limit = paging?.limit || filteredModels.length;
@@ -9012,24 +9129,24 @@ var Reactor = class {
9012
9129
  }
9013
9130
  async getOutgoingRelationships(sourceId, relationshipType, consistencyToken, signal) {
9014
9131
  const relationships = await this.documentIndexer.getOutgoing(sourceId, [relationshipType], void 0, consistencyToken, signal);
9015
- if (signal?.aborted) throw new AbortError();
9132
+ throwIfAborted(signal, () => new AbortError());
9016
9133
  return relationships.results.map((rel) => rel.targetId);
9017
9134
  }
9018
9135
  async getIncomingRelationships(targetId, relationshipType, consistencyToken, signal) {
9019
9136
  const relationships = await this.documentIndexer.getIncoming(targetId, [relationshipType], void 0, consistencyToken, signal);
9020
- if (signal?.aborted) throw new AbortError();
9137
+ throwIfAborted(signal, () => new AbortError());
9021
9138
  return relationships.results.map((rel) => rel.sourceId);
9022
9139
  }
9023
9140
  async getOperations(documentId, view, filter, paging, consistencyToken, signal) {
9024
9141
  this.logger.verbose("getOperations(@documentId, @view, @filter, @paging)", documentId, view, filter, paging);
9025
9142
  const branch = view?.branch || "main";
9026
9143
  const revisions = await this.operationStore.getRevisions(documentId, branch, signal);
9027
- if (signal?.aborted) throw new AbortError();
9144
+ throwIfAborted(signal, () => new AbortError());
9028
9145
  const allScopes = Object.keys(revisions.revision);
9029
9146
  const result = {};
9030
9147
  for (const scope of allScopes) {
9031
9148
  if (!matchesScope(view, scope)) continue;
9032
- if (signal?.aborted) throw new AbortError();
9149
+ throwIfAborted(signal, () => new AbortError());
9033
9150
  const scopeResult = await this.operationStore.getSince(documentId, scope, branch, -1, filter, paging, signal);
9034
9151
  result[scope] = {
9035
9152
  results: scopeResult.results,
@@ -9060,13 +9177,13 @@ var Reactor = class {
9060
9177
  if (search.type) results = filterByType(results, search.type);
9061
9178
  } else if (search.type) results = await this.findByType(search.type, view, paging, consistencyToken, signal);
9062
9179
  else throw new Error("No search criteria provided");
9063
- if (signal?.aborted) throw new AbortError();
9180
+ throwIfAborted(signal, () => new AbortError());
9064
9181
  return results;
9065
9182
  }
9066
9183
  async create(document, signer, signal, meta) {
9067
9184
  this.logger.verbose("create(@id, @type, @slug)", document.header.id, document.header.documentType, document.header.slug);
9068
9185
  const createdAtUtcIso = (/* @__PURE__ */ new Date()).toISOString();
9069
- if (signal?.aborted) throw new AbortError();
9186
+ throwIfAborted(signal, () => new AbortError());
9070
9187
  let actions = [createDocumentAction({
9071
9188
  model: document.header.documentType,
9072
9189
  version: 0,
@@ -9126,7 +9243,7 @@ var Reactor = class {
9126
9243
  async deleteDocument(id, signer, signal, meta) {
9127
9244
  this.logger.verbose("deleteDocument(@id)", id);
9128
9245
  const createdAtUtcIso = (/* @__PURE__ */ new Date()).toISOString();
9129
- if (signal?.aborted) throw new AbortError();
9246
+ throwIfAborted(signal, () => new AbortError());
9130
9247
  let action = deleteDocumentAction(id);
9131
9248
  if (signer) action = await signAction(action, signer, signal);
9132
9249
  const jobId = v4();
@@ -9163,7 +9280,7 @@ var Reactor = class {
9163
9280
  }
9164
9281
  async execute(docId, branch, actions, signal, meta) {
9165
9282
  this.logger.verbose("execute(@docId, @branch, @actions)", docId, branch, actions);
9166
- if (signal?.aborted) throw new AbortError();
9283
+ throwIfAborted(signal, () => new AbortError());
9167
9284
  const createdAtUtcIso = (/* @__PURE__ */ new Date()).toISOString();
9168
9285
  const scope = getSharedActionScope(actions);
9169
9286
  const jobId = v4();
@@ -9196,12 +9313,12 @@ var Reactor = class {
9196
9313
  this.jobTracker.registerJob(jobInfo);
9197
9314
  this.emitJobPending(jobInfo.id, jobMeta);
9198
9315
  await this.queue.enqueue(job);
9199
- if (signal?.aborted) throw new AbortError();
9316
+ throwIfAborted(signal, () => new AbortError());
9200
9317
  return jobInfo;
9201
9318
  }
9202
9319
  async load(docId, branch, operations, signal, meta) {
9203
9320
  this.logger.verbose("load(@docId, @branch, @count, @operations)", docId, branch, operations.length, operations);
9204
- if (signal?.aborted) throw new AbortError();
9321
+ throwIfAborted(signal, () => new AbortError());
9205
9322
  if (operations.length === 0) throw new Error("load requires at least one operation");
9206
9323
  const scope = getSharedOperationScope(operations);
9207
9324
  const createdAtUtcIso = (/* @__PURE__ */ new Date()).toISOString();
@@ -9235,12 +9352,12 @@ var Reactor = class {
9235
9352
  this.jobTracker.registerJob(jobInfo);
9236
9353
  this.emitJobPending(jobInfo.id, jobMeta);
9237
9354
  await this.queue.enqueue(job);
9238
- if (signal?.aborted) throw new AbortError();
9355
+ throwIfAborted(signal, () => new AbortError());
9239
9356
  return jobInfo;
9240
9357
  }
9241
9358
  async executeBatch(request, signal, meta) {
9242
9359
  this.logger.verbose("executeBatch(@count jobs)", request.jobs.length);
9243
- if (signal?.aborted) throw new AbortError();
9360
+ throwIfAborted(signal, () => new AbortError());
9244
9361
  validateBatchRequest(request.jobs);
9245
9362
  for (const jobPlan of request.jobs) validateActionScopes(jobPlan);
9246
9363
  const createdAtUtcIso = (/* @__PURE__ */ new Date()).toISOString();
@@ -9274,7 +9391,7 @@ var Reactor = class {
9274
9391
  const enqueuedKeys = [];
9275
9392
  try {
9276
9393
  for (const key of sortedKeys) {
9277
- if (signal?.aborted) throw new AbortError();
9394
+ throwIfAborted(signal, () => new AbortError());
9278
9395
  const jobPlan = request.jobs.find((j) => j.key === key);
9279
9396
  const jobId = planKeyToJobId.get(key);
9280
9397
  const queueHint = jobPlan.dependsOn.map((depKey) => planKeyToJobId.get(depKey));
@@ -9309,7 +9426,7 @@ var Reactor = class {
9309
9426
  }
9310
9427
  async loadBatch(request, signal, meta) {
9311
9428
  this.logger.verbose("loadBatch(@count jobs)", request.jobs.length);
9312
- if (signal?.aborted) throw new AbortError();
9429
+ throwIfAborted(signal, () => new AbortError());
9313
9430
  validateBatchLoadRequest(request.jobs);
9314
9431
  for (const jobPlan of request.jobs) validateOperationScopes(jobPlan);
9315
9432
  const createdAtUtcIso = (/* @__PURE__ */ new Date()).toISOString();
@@ -9343,7 +9460,7 @@ var Reactor = class {
9343
9460
  const enqueuedKeys = [];
9344
9461
  try {
9345
9462
  for (const key of sortedKeys) {
9346
- if (signal?.aborted) throw new AbortError();
9463
+ throwIfAborted(signal, () => new AbortError());
9347
9464
  const jobPlan = request.jobs.find((j) => j.key === key);
9348
9465
  const jobId = planKeyToJobId.get(key);
9349
9466
  const queueHint = [...jobPlan.dependsOn.map((depKey) => planKeyToJobId.get(depKey)), ...jobPlan.externalDeps];
@@ -9378,21 +9495,21 @@ var Reactor = class {
9378
9495
  }
9379
9496
  async addRelationship(sourceId, targetId, relationshipType, branch = "main", signer, signal) {
9380
9497
  this.logger.verbose("addRelationship(@sourceId, @targetId, @relationshipType, @branch)", sourceId, targetId, relationshipType, branch);
9381
- if (signal?.aborted) throw new AbortError();
9498
+ throwIfAborted(signal, () => new AbortError());
9382
9499
  let actions = [addRelationshipAction(sourceId, targetId, relationshipType)];
9383
9500
  if (signer) actions = await signActions(actions, signer, signal);
9384
9501
  return await this.execute(sourceId, branch, actions, signal);
9385
9502
  }
9386
9503
  async removeRelationship(sourceId, targetId, relationshipType, branch = "main", signer, signal) {
9387
9504
  this.logger.verbose("removeRelationship(@sourceId, @targetId, @relationshipType, @branch)", sourceId, targetId, relationshipType, branch);
9388
- if (signal?.aborted) throw new AbortError();
9505
+ throwIfAborted(signal, () => new AbortError());
9389
9506
  let actions = [removeRelationshipAction(sourceId, targetId, relationshipType)];
9390
9507
  if (signer) actions = await signActions(actions, signer, signal);
9391
9508
  return await this.execute(sourceId, branch, actions, signal);
9392
9509
  }
9393
9510
  getJobStatus(jobId, signal) {
9394
9511
  this.logger.verbose("getJobStatus(@jobId)", jobId);
9395
- if (signal?.aborted) throw new AbortError();
9512
+ throwIfAborted(signal, () => new AbortError());
9396
9513
  const jobInfo = this.jobTracker.getJobStatus(jobId);
9397
9514
  if (!jobInfo) {
9398
9515
  const now = (/* @__PURE__ */ new Date()).toISOString();
@@ -9461,6 +9578,7 @@ var ReactorBuilder = class {
9461
9578
  upgradeManifests = [];
9462
9579
  features = { legacyStorageEnabled: false };
9463
9580
  readModels = [];
9581
+ readModelFactories = [];
9464
9582
  executorManager;
9465
9583
  executorConfig = {};
9466
9584
  writeCacheConfig;
@@ -9476,6 +9594,7 @@ var ReactorBuilder = class {
9476
9594
  jwtHandler;
9477
9595
  documentModelLoader;
9478
9596
  shutdownHooks = [];
9597
+ driveContainerTypes = DEFAULT_DRIVE_CONTAINER_TYPES;
9479
9598
  withLogger(logger) {
9480
9599
  this.logger = logger;
9481
9600
  return this;
@@ -9499,6 +9618,17 @@ var ReactorBuilder = class {
9499
9618
  this.readModels.push(readModel);
9500
9619
  return this;
9501
9620
  }
9621
+ /**
9622
+ * Register a factory that builds a pre-ready read model after the reactor's
9623
+ * internal `operationIndex`, `writeCache`, and processor-manager consistency
9624
+ * tracker are constructed. Use this for read models (e.g. `BaseReadModel`
9625
+ * subclasses) that need those dependencies and therefore cannot be built
9626
+ * before calling `buildModule()`.
9627
+ */
9628
+ withReadModelFactory(factory) {
9629
+ this.readModelFactories.push(factory);
9630
+ return this;
9631
+ }
9502
9632
  withReadModelCoordinator(readModelCoordinator) {
9503
9633
  this.readModelCoordinator = readModelCoordinator;
9504
9634
  return this;
@@ -9518,6 +9648,10 @@ var ReactorBuilder = class {
9518
9648
  this.writeCacheConfig = config;
9519
9649
  return this;
9520
9650
  }
9651
+ withDriveContainerTypes(types) {
9652
+ this.driveContainerTypes = new Set(types);
9653
+ return this;
9654
+ }
9521
9655
  withMigrationStrategy(strategy) {
9522
9656
  this.migrationStrategy = strategy;
9523
9657
  return this;
@@ -9608,7 +9742,7 @@ var ReactorBuilder = class {
9608
9742
  const collectionMembershipCache = new CollectionMembershipCache(operationIndex);
9609
9743
  const executionScope = new KyselyExecutionScope(database, operationStore, operationIndex, keyframeStore, writeCache, documentMetaCache, collectionMembershipCache);
9610
9744
  let executorManager = this.executorManager;
9611
- if (!executorManager) executorManager = new SimpleJobExecutorManager(() => new SimpleJobExecutor(this.logger, documentModelRegistry, operationStore, eventBus, writeCache, operationIndex, documentMetaCache, collectionMembershipCache, this.executorConfig, this.signatureVerifier, executionScope), eventBus, queue, jobTracker, this.logger, resolver, this.executorConfig.jobTimeoutMs);
9745
+ if (!executorManager) executorManager = new SimpleJobExecutorManager(() => new SimpleJobExecutor(this.logger, documentModelRegistry, operationStore, eventBus, writeCache, operationIndex, documentMetaCache, collectionMembershipCache, this.driveContainerTypes, this.executorConfig, this.signatureVerifier, executionScope), eventBus, queue, jobTracker, this.logger, resolver, this.executorConfig.jobTimeoutMs);
9612
9746
  await executorManager.start(this.executorConfig.maxConcurrency ?? 1);
9613
9747
  const readModelInstances = Array.from(new Set([...this.readModels]));
9614
9748
  const documentViewConsistencyTracker = new ConsistencyTracker();
@@ -9630,21 +9764,29 @@ var ReactorBuilder = class {
9630
9764
  const subscriptionManager = new ReactorSubscriptionManager(new DefaultSubscriptionErrorHandler());
9631
9765
  const subscriptionNotificationReadModel = new SubscriptionNotificationReadModel(subscriptionManager, documentView);
9632
9766
  const processorManagerConsistencyTracker = new ConsistencyTracker();
9633
- const processorManager = new ProcessorManager(database, operationIndex, writeCache, processorManagerConsistencyTracker, this.logger);
9767
+ const processorManager = new ProcessorManager(database, operationIndex, writeCache, processorManagerConsistencyTracker, this.logger, this.driveContainerTypes);
9634
9768
  try {
9635
9769
  await processorManager.init();
9636
9770
  } catch (error) {
9637
9771
  console.error("Error initializing processor manager", error);
9638
9772
  }
9773
+ for (const factory of this.readModelFactories) {
9774
+ const readModel = await factory({
9775
+ operationIndex,
9776
+ writeCache,
9777
+ processorManagerConsistencyTracker
9778
+ });
9779
+ readModelInstances.push(readModel);
9780
+ }
9639
9781
  const readModelCoordinator = this.readModelCoordinator ? this.readModelCoordinator : new ReadModelCoordinator(eventBus, readModelInstances, [subscriptionNotificationReadModel, processorManager]);
9640
9782
  const reactor = new Reactor(this.logger, documentModelRegistry, queue, jobTracker, readModelCoordinator, this.features, documentView, documentIndexer, operationStore, eventBus, executorManager);
9641
9783
  let syncModule = void 0;
9642
9784
  if (this.channelScheme) {
9643
9785
  const factory = this.channelScheme === ChannelScheme.CONNECT ? new GqlRequestChannelFactory(this.logger, this.jwtHandler, queue) : new GqlResponseChannelFactory(this.logger);
9644
- syncModule = new SyncBuilder().withChannelFactory(factory).buildModule(reactor, this.logger, operationIndex, eventBus, database);
9786
+ syncModule = new SyncBuilder().withChannelFactory(factory).buildModule(reactor, this.logger, operationIndex, eventBus, database, this.driveContainerTypes);
9645
9787
  await syncModule.syncManager.startup();
9646
9788
  } else if (this.syncBuilder) {
9647
- syncModule = this.syncBuilder.buildModule(reactor, this.logger, operationIndex, eventBus, database);
9789
+ syncModule = this.syncBuilder.buildModule(reactor, this.logger, operationIndex, eventBus, database, this.driveContainerTypes);
9648
9790
  await syncModule.syncManager.startup();
9649
9791
  }
9650
9792
  const module = {
@@ -9890,13 +10032,15 @@ const JobExecutorEventTypes = {
9890
10032
  EXECUTOR_STOPPED: 20004
9891
10033
  };
9892
10034
  //#endregion
9893
- //#region src/admin/document-integrity-service.ts
9894
- const nullKeyframeStore = {
10035
+ //#region src/admin/passthrough-keyframe-store.ts
10036
+ const passthroughKeyframeStore = {
9895
10037
  putKeyframe: () => Promise.resolve(),
9896
10038
  findNearestKeyframe: () => Promise.resolve(void 0),
9897
10039
  listKeyframes: () => Promise.resolve([]),
9898
10040
  deleteKeyframes: () => Promise.resolve(0)
9899
10041
  };
10042
+ //#endregion
10043
+ //#region src/admin/document-integrity-service.ts
9900
10044
  var DocumentIntegrityService = class {
9901
10045
  keyframeStore;
9902
10046
  operationStore;
@@ -9913,14 +10057,14 @@ var DocumentIntegrityService = class {
9913
10057
  async validateDocument(documentId, branch = "main", signal) {
9914
10058
  const keyframeIssues = [];
9915
10059
  const snapshotIssues = [];
9916
- const replayCache = new KyselyWriteCache(nullKeyframeStore, this.operationStore, this.documentModelRegistry, {
10060
+ const replayCache = new KyselyWriteCache(passthroughKeyframeStore, this.operationStore, this.documentModelRegistry, {
9917
10061
  maxDocuments: 1,
9918
10062
  ringBufferSize: 1,
9919
10063
  keyframeInterval: Number.MAX_SAFE_INTEGER
9920
10064
  });
9921
10065
  const keyframes = await this.keyframeStore.listKeyframes(documentId, void 0, branch, signal);
9922
10066
  for (const keyframe of keyframes) {
9923
- if (signal?.aborted) throw new Error("Operation aborted");
10067
+ throwIfAborted(signal);
9924
10068
  replayCache.invalidate(documentId, keyframe.scope, branch);
9925
10069
  const replayedDoc = await replayCache.getState(documentId, keyframe.scope, branch, keyframe.revision, signal);
9926
10070
  const kfHash = hashDocumentStateForScope(keyframe.document, keyframe.scope);
@@ -9953,7 +10097,7 @@ var DocumentIntegrityService = class {
9953
10097
  try {
9954
10098
  replayedDoc = await replayCache.getState(documentId, scope, branch, void 0, signal);
9955
10099
  } catch {
9956
- if (signal?.aborted) throw new Error("Operation aborted");
10100
+ throwIfAborted(signal);
9957
10101
  continue;
9958
10102
  }
9959
10103
  const snapshotHash = hashDocumentStateForScope(currentDoc, scope);
@@ -9982,7 +10126,7 @@ var DocumentIntegrityService = class {
9982
10126
  async rebuildSnapshots(documentId, branch = "main", signal) {
9983
10127
  const scopes = await this.discoverScopes(documentId, branch, signal);
9984
10128
  for (const scope of scopes) {
9985
- if (signal?.aborted) throw new Error("Operation aborted");
10129
+ throwIfAborted(signal);
9986
10130
  this.writeCache.invalidate(documentId, scope, branch);
9987
10131
  }
9988
10132
  return {
@@ -9997,6 +10141,6 @@ var DocumentIntegrityService = class {
9997
10141
  }
9998
10142
  };
9999
10143
  //#endregion
10000
- export { BaseReadModel, ChannelError, ChannelErrorSource, ChannelScheme, ConsistencyTracker, DefaultSubscriptionErrorHandler, DocumentChangeType, DocumentIntegrityService, DocumentModelRegistry, DocumentModelResolver, DriveClient, DuplicateManifestError, DuplicateModuleError, DuplicateOperationError, EventBus, EventBusAggregateError, GqlRequestChannel, GqlRequestChannelFactory, GqlResponseChannel, GqlResponseChannelFactory, SimpleJobExecutor as InMemoryJobExecutor, SimpleJobExecutor, InMemoryJobTracker, InMemoryQueue, IntervalPollTimer, InvalidModuleError, JobAwaiter, JobExecutorEventTypes, JobStatus, KyselyDocumentIndexer, KyselyDocumentView, KyselyKeyframeStore, KyselyOperationStore, KyselySyncCursorStorage, KyselySyncRemoteStorage, KyselyWriteCache, Mailbox, ModuleNotFoundError, NullDocumentModelResolver, OptimisticLockError, PollBehavior, PollingChannelError, ProcessorManager, PropagationMode, QueueEventTypes, REACTOR_SCHEMA, Reactor, ReactorBuilder, ReactorClient, ReactorClientBuilder, ReactorEventTypes, ReactorSubscriptionManager, ReadModelCoordinator, RelationalDbProcessor, RelationshipChangeType, RevisionMismatchError, SimpleJobExecutorManager, SyncBuilder, SyncEventTypes, SyncOperation, SyncOperationAggregateError, SyncOperationStatus, SyncStatus, SyncStatusTracker, addRelationshipAction, batchOperationsByDocument, consolidateSyncOperations, createDocumentAction, createMutableShutdownStatus, createRelationalDb, deleteDocumentAction, documentActions, driveCollectionId, driveIdFromUrl, envelopesToSyncOperations, getMigrationStatus, makeConsistencyKey, parseDriveUrl, removeRelationshipAction, runMigrations, trimMailboxFromAckOrdinal, upgradeDocumentAction };
10144
+ export { BaseReadModel, ChannelError, ChannelErrorSource, ChannelScheme, ConsistencyTracker, DEFAULT_DRIVE_CONTAINER_TYPES, DefaultSubscriptionErrorHandler, DocumentChangeType, DocumentIntegrityService, DocumentModelRegistry, DocumentModelResolver, DriveClient, DuplicateManifestError, DuplicateModuleError, DuplicateOperationError, EventBus, EventBusAggregateError, GqlRequestChannel, GqlRequestChannelFactory, GqlResponseChannel, GqlResponseChannelFactory, SimpleJobExecutor as InMemoryJobExecutor, SimpleJobExecutor, InMemoryJobTracker, InMemoryQueue, IntervalPollTimer, InvalidModuleError, JobAwaiter, JobExecutorEventTypes, JobStatus, KyselyDocumentIndexer, KyselyDocumentView, KyselyKeyframeStore, KyselyOperationStore, KyselySyncCursorStorage, KyselySyncRemoteStorage, KyselyWriteCache, Mailbox, ModuleNotFoundError, NullDocumentModelResolver, OptimisticLockError, PollBehavior, PollingChannelError, ProcessorManager, PropagationMode, QueueEventTypes, REACTOR_SCHEMA, Reactor, ReactorBuilder, ReactorClient, ReactorClientBuilder, ReactorEventTypes, ReactorSubscriptionManager, ReadModelCoordinator, RelationalDbProcessor, RelationshipChangeType, RevisionMismatchError, SimpleJobExecutorManager, SyncBuilder, SyncEventTypes, SyncOperation, SyncOperationAggregateError, SyncOperationStatus, SyncStatus, SyncStatusTracker, addRelationshipAction, batchOperationsByDocument, consolidateSyncOperations, createDocumentAction, createMutableShutdownStatus, createRelationalDb, deleteDocumentAction, documentActions, driveCollectionId, driveIdFromUrl, envelopesToSyncOperations, getMigrationStatus, makeConsistencyKey, parseDriveUrl, parsePagingOptions, removeRelationshipAction, runMigrations, trimMailboxFromAckOrdinal, updateRelationshipAction, upgradeDocumentAction };
10001
10145
 
10002
10146
  //# sourceMappingURL=index.js.map