@powerhousedao/reactor 6.2.2-dev.42 → 6.2.2-dev.43

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
@@ -1,10 +1,10 @@
1
- import { A as OptimisticLockError, B as ExcessiveReshuffleError, C as DocumentMetaCache, D as APPEND_CONDITION_FAILED_PREFIX, E as CollectionMembershipCache, F as ModuleNotFoundError, H as matchesScope, I as AuthTimestampNotMonotonicError, L as AuthorizationDeniedError, M as DuplicateManifestError, N as DuplicateModuleError, O as AppendConditionFailedError, P as InvalidModuleError, R as DocumentDeletedError, S as KyselyOperationIndex, T as createEmptyConsistencyToken, U as parsePagingOptions, V as InvalidOperationTimestampError, W as throwIfAborted, _ as KyselyExecutionScope, a as createForwardingPoolInstrumentation, b as EventBus, c as KyselyKeyframeStore, d as DriveCollectionId, f as decideAtHead, g as authDecisionModel, h as buildDecisionModel, i as runMigrations, j as RevisionMismatchError, k as DuplicateOperationError, l as DocumentModelRegistry, m as documentDecisionModel, n as REACTOR_SCHEMA, o as instrumentPgPool, p as selectDecisionModel, r as getMigrationStatus, s as KyselyOperationStore, t as DEFAULT_DRIVE_CONTAINER_TYPES, u as SimpleJobExecutor, v as FLAG_PREREQUISITES, w as createConsistencyToken, x as KyselyWriteCache, y as validateFeatureFlags, z as DocumentNotFoundError } from "./drive-container-types-BJCKXJwH.js";
1
+ import { A as OptimisticLockError, B as ExcessiveReshuffleError, C as DocumentMetaCache, D as APPEND_CONDITION_FAILED_PREFIX, E as CollectionMembershipCache, F as ModuleNotFoundError, G as throwIfAborted, H as UpgradePreconditionFailedError, I as AuthTimestampNotMonotonicError, L as AuthorizationDeniedError, M as DuplicateManifestError, N as DuplicateModuleError, O as AppendConditionFailedError, P as InvalidModuleError, R as DocumentDeletedError, S as KyselyOperationIndex, T as createEmptyConsistencyToken, U as matchesScope, V as InvalidOperationTimestampError, W as parsePagingOptions, _ as KyselyExecutionScope, a as createForwardingPoolInstrumentation, b as EventBus, c as KyselyKeyframeStore, d as DriveCollectionId, f as decideAtHead, g as authDecisionModel, h as buildDecisionModel, i as runMigrations, j as RevisionMismatchError, k as DuplicateOperationError, l as DocumentModelRegistry, m as documentDecisionModel, n as REACTOR_SCHEMA, o as instrumentPgPool, p as selectDecisionModel, r as getMigrationStatus, s as KyselyOperationStore, t as DEFAULT_DRIVE_CONTAINER_TYPES, u as SimpleJobExecutor, v as FLAG_PREREQUISITES, w as createConsistencyToken, x as KyselyWriteCache, y as validateFeatureFlags, z as DocumentNotFoundError } from "./drive-container-types-CU1ZUfD1.js";
2
2
  import { n as ReactorEventTypes, t as EventBusAggregateError } from "./types-DMKLa0Ok.js";
3
3
  import { i as WorkerInitFailedError, r as WorkerExitedError, t as WorkerAbortTimeoutError } from "./errors-D3S6Eysd.js";
4
- import { a as ReadModelCoordinator, i as KyselyDocumentView, n as ConsistencyTracker, o as BaseReadModel, r as makeConsistencyKey, t as KyselyDocumentIndexer } from "./document-indexer-C0GB0b8Q.js";
4
+ import { a as ReadModelCoordinator, i as KyselyDocumentView, n as ConsistencyTracker, o as BaseReadModel, r as makeConsistencyKey, t as KyselyDocumentIndexer } from "./document-indexer-BfdCgjG-.js";
5
5
  import { n as errorToInfo, r as sanitizeArg, t as createForwardingLogger } from "./forwarding-logger-BBkMSxuJ.js";
6
- import { t as workerEntryPath } from "./worker-jEJW6_j7.js";
7
- import { actions, actions as documentActions, createPresignedHeader, decide, garbageCollect, generateId, hashDocumentStateForScope, replayDocument, sortOperations } from "@powerhousedao/shared/document-model";
6
+ import { t as workerEntryPath } from "./worker-CeW6WsVF.js";
7
+ import { DowngradeNotSupportedError, UnsupportedDocumentModelVersionError, actions, actions as documentActions, createPresignedHeader, decide, garbageCollect, generateId, hashDocumentStateForScope, normalizeDocumentModelVersion, sortOperations } from "@powerhousedao/shared/document-model";
8
8
  import { addFile, addFolder, copyNode, deleteNode, driveCreateDocument, generateNodesCopy, getDescendants, handleTargetNameCollisions, isFileNode, isFolderNode, moveNode, updateNode } from "@powerhousedao/shared/document-drive";
9
9
  import { v4 } from "uuid";
10
10
  import { ConsoleLogger, childLogger } from "document-model";
@@ -365,7 +365,7 @@ var DriveClient = class {
365
365
  documentId: document.header.id,
366
366
  model: document.header.documentType,
367
367
  fromVersion: 0,
368
- toVersion: 1,
368
+ toVersion: normalizeDocumentModelVersion(document.state.document?.version),
369
369
  initialState: document.state
370
370
  }),
371
371
  addRelationshipAction(driveIdentifier, documentId, "child")
@@ -470,8 +470,12 @@ var DriveClient = class {
470
470
  const node = drive.state.global.nodes.find((n) => n.id === entry.srcId);
471
471
  if (!node || !isFileNode(node)) continue;
472
472
  const srcDoc = await this.client.get(entry.srcId, void 0, signal);
473
- const module = await this.client.getDocumentModelModule(srcDoc.header.documentType);
474
- const duplicated = replayDocument(srcDoc.initialState, srcDoc.operations, module.reducer, createPresignedHeader(entry.targetId, srcDoc.header.documentType));
473
+ const duplicated = {
474
+ ...srcDoc,
475
+ header: createPresignedHeader(entry.targetId, srcDoc.header.documentType),
476
+ initialState: srcDoc.state,
477
+ operations: {}
478
+ };
475
479
  const resolvedName = resolvedNamesByTargetId.get(entry.targetId);
476
480
  if (resolvedName) duplicated.header.name = resolvedName;
477
481
  await this.addFile(driveIdentifier, duplicated, entry.targetParentFolder ?? void 0, signal);
@@ -769,9 +773,37 @@ var ReactorClient = class {
769
773
  * @returns The document model module
770
774
  */
771
775
  async getDocumentModelModule(documentType) {
772
- const module = (await this.reactor.getDocumentModels()).results.find((m) => m.documentModel.global.id === documentType);
773
- if (!module) throw new Error(`Document model module not found for type: ${documentType}`);
774
- return module;
776
+ const modules = await this.reactor.getDocumentModels();
777
+ let latestModule;
778
+ let latestVersion = -1;
779
+ for (const module of modules.results) {
780
+ if (module.documentModel.global.id !== documentType) continue;
781
+ const version = module.version ?? 1;
782
+ if (version > latestVersion) {
783
+ latestVersion = version;
784
+ latestModule = module;
785
+ }
786
+ }
787
+ if (!latestModule) throw new Error(`Document model module not found for type: ${documentType}`);
788
+ return latestModule;
789
+ }
790
+ /**
791
+ * Retrieves the document model module matching the version the document is
792
+ * stamped with, so not-yet-upgraded documents get the reducer their
793
+ * history was written with rather than the latest.
794
+ */
795
+ async getDocumentModelModuleForDocument(document) {
796
+ const documentType = document.header.documentType;
797
+ const version = normalizeDocumentModelVersion(document.state.document?.version);
798
+ const modules = await this.reactor.getDocumentModels();
799
+ const availableVersions = [];
800
+ for (const module of modules.results) {
801
+ if (module.documentModel.global.id !== documentType) continue;
802
+ const moduleVersion = normalizeDocumentModelVersion(module.version);
803
+ if (moduleVersion === version) return module;
804
+ availableVersions.push(moduleVersion);
805
+ }
806
+ throw new UnsupportedDocumentModelVersionError(documentType, version, availableVersions.sort((a, b) => a - b));
775
807
  }
776
808
  /**
777
809
  * Retrieves a specific PHDocument
@@ -925,7 +957,7 @@ var ReactorClient = class {
925
957
  documentId,
926
958
  model: document.header.documentType,
927
959
  fromVersion: 0,
928
- toVersion: document.state.document.version,
960
+ toVersion: normalizeDocumentModelVersion(document.state.document?.version),
929
961
  initialState: document.state
930
962
  })], this.signer, signal);
931
963
  const jobs = [{
@@ -960,20 +992,61 @@ var ReactorClient = class {
960
992
  const matchingModules = (await this.reactor.getDocumentModels(void 0, void 0, signal)).results.filter((m) => m.documentModel.global.id === documentModelType);
961
993
  let module;
962
994
  if (options?.documentModelVersion !== void 0) {
963
- module = matchingModules.find((m) => m.version === options.documentModelVersion);
995
+ const requestedVersion = normalizeDocumentModelVersion(options.documentModelVersion);
996
+ module = matchingModules.find((m) => normalizeDocumentModelVersion(m.version) === requestedVersion);
964
997
  if (!module) throw new Error(`Document model not found for type: ${documentModelType} with version: ${options.documentModelVersion}`);
965
998
  } else {
966
999
  module = matchingModules.reduce((latest, current) => {
967
1000
  if (latest === void 0) return current;
968
- return (current.version ?? 0) > (latest.version ?? 0) ? current : latest;
1001
+ return normalizeDocumentModelVersion(current.version) > normalizeDocumentModelVersion(latest.version) ? current : latest;
969
1002
  }, void 0);
970
1003
  if (!module) throw new Error(`Document model not found for type: ${documentModelType}`);
971
1004
  }
972
1005
  const document = module.utils.createDocument();
973
- document.state.document.version = module.version ?? 1;
1006
+ document.state.document.version = normalizeDocumentModelVersion(module.version);
974
1007
  return this.create(document, options?.parentIdentifier, signal);
975
1008
  }
976
1009
  /**
1010
+ * Upgrades a document to a newer document model version by dispatching an
1011
+ * UPGRADE_DOCUMENT action. When toVersion is omitted, upgrades to the
1012
+ * latest registered module version for the document's type. Returns the
1013
+ * document unchanged when it is already at the target version.
1014
+ *
1015
+ * The executor validates the action's version and revision snapshot against
1016
+ * the state the migration actually runs on. When a concurrent edit
1017
+ * invalidates the snapshot, the upgrade is rebuilt from a fresh read and
1018
+ * retried up to maxConflictRetries times before the conflict is surfaced.
1019
+ */
1020
+ async upgradeDocument(documentIdentifier, toVersion, options, signal) {
1021
+ this.logger.verbose("upgradeDocument(@documentIdentifier, @toVersion)", documentIdentifier, toVersion);
1022
+ const maxConflictRetries = options?.maxConflictRetries ?? 3;
1023
+ let lastConflictMessage = "";
1024
+ for (let attempt = 0; attempt <= maxConflictRetries; attempt++) {
1025
+ const document = await this.reactor.getByIdOrSlug(documentIdentifier, void 0, void 0, signal);
1026
+ const documentId = document.header.id;
1027
+ const documentType = document.header.documentType;
1028
+ const branch = document.header.branch || "main";
1029
+ const fromVersion = normalizeDocumentModelVersion(document.state.document?.version);
1030
+ let targetVersion = toVersion;
1031
+ if (targetVersion === void 0) targetVersion = normalizeDocumentModelVersion((await this.getDocumentModelModule(documentType)).version);
1032
+ if (targetVersion === fromVersion) return document;
1033
+ if (targetVersion < fromVersion) throw new DowngradeNotSupportedError(documentType, fromVersion, targetVersion);
1034
+ const signedActions = await signActions([upgradeDocumentAction({
1035
+ documentId,
1036
+ model: documentType,
1037
+ fromVersion,
1038
+ toVersion: targetVersion,
1039
+ revision: { ...document.header.revision }
1040
+ })], this.signer, signal);
1041
+ const jobInfo = await this.reactor.execute(documentId, branch, signedActions, signal);
1042
+ const completedJob = await this.waitForJob(jobInfo, signal);
1043
+ if (completedJob.status !== JobStatus.FAILED) return await this.reactor.getByIdOrSlug(documentId, { branch }, completedJob.consistencyToken, signal);
1044
+ if (completedJob.error?.name !== "UpgradePreconditionFailedError") throw new Error(completedJob.error?.message);
1045
+ lastConflictMessage = completedJob.error.message;
1046
+ }
1047
+ throw new Error(`Upgrade of document ${documentIdentifier} conflicted with concurrent edits after ${maxConflictRetries + 1} attempts: ${lastConflictMessage}`);
1048
+ }
1049
+ /**
977
1050
  * Creates an empty document in a drive as a single batched operation.
978
1051
  * Delegates to {@link IDriveClient.addFile}.
979
1052
  *
@@ -1274,7 +1347,7 @@ var JobResultHandler = class {
1274
1347
  callbacks.deferJob(handle.job.documentId, handle.job);
1275
1348
  return;
1276
1349
  }
1277
- if (result.error && (DocumentDeletedError.isError(result.error) || AuthorizationDeniedError.isError(result.error) || AuthTimestampNotMonotonicError.isError(result.error) || InvalidOperationTimestampError.isError(result.error) || ExcessiveReshuffleError.isError(result.error))) {
1350
+ if (result.error && (DocumentDeletedError.isError(result.error) || AuthorizationDeniedError.isError(result.error) || AuthTimestampNotMonotonicError.isError(result.error) || InvalidOperationTimestampError.isError(result.error) || ExcessiveReshuffleError.isError(result.error) || UpgradePreconditionFailedError.isError(result.error))) {
1278
1351
  const errorInfo = toErrorInfo(result.error);
1279
1352
  this.jobTracker.markFailed(handle.job.id, errorInfo, handle.job);
1280
1353
  this.eventBus.emit(ReactorEventTypes.JOB_FAILED, {
@@ -2668,9 +2741,11 @@ var InMemoryQueue = class {
2668
2741
  this.jobIndex.delete(jobId);
2669
2742
  this.completedJobs.add(jobId);
2670
2743
  await this.remove(jobId);
2744
+ const emittedError = new Error(error?.message ?? "Job failed");
2745
+ if (error?.name) emittedError.name = error.name;
2671
2746
  this.eventBus.emit(ReactorEventTypes.JOB_FAILED, {
2672
2747
  jobId,
2673
- error: new Error(error?.message ?? "Job failed"),
2748
+ error: emittedError,
2674
2749
  job
2675
2750
  }).catch(() => {});
2676
2751
  this.checkDrained();
@@ -6385,7 +6460,7 @@ var Reactor = class {
6385
6460
  documentId: document.header.id,
6386
6461
  model: document.header.documentType,
6387
6462
  fromVersion: 0,
6388
- toVersion: document.state.document.version,
6463
+ toVersion: normalizeDocumentModelVersion(document.state.document?.version),
6389
6464
  initialState: document.state
6390
6465
  })];
6391
6466
  if (signer) actions = await signActions(actions, signer, signal);
@@ -7162,7 +7237,7 @@ var ReactorBuilder = class {
7162
7237
  const [{ WorkerHandle }, { createThreadTransport }, { workerEntryPath }] = await Promise.all([
7163
7238
  import("./worker-handle-CrERzl8s.js"),
7164
7239
  import("./transport-ByGviWdZ.js"),
7165
- import("./worker-jEJW6_j7.js").then((n) => n.n)
7240
+ import("./worker-CeW6WsVF.js").then((n) => n.n)
7166
7241
  ]);
7167
7242
  const poolConfig = {
7168
7243
  enabled: true,