@powerhousedao/reactor 6.2.0-dev.5 → 6.2.0-dev.50

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,13 +1,13 @@
1
- import { A as parsePagingOptions, C as DuplicateManifestError, D as DocumentDeletedError, E as ModuleNotFoundError, O as DocumentNotFoundError, S as CollectionMembershipCache, T as InvalidModuleError, _ as KyselyWriteCache, a as createForwardingPoolInstrumentation, b as createConsistencyToken, c as DuplicateOperationError, d as KyselyKeyframeStore, f as DocumentModelRegistry, g as EventBus, h as KyselyExecutionScope, i as runMigrations, j as throwIfAborted, k as matchesScope, l as OptimisticLockError, m as driveCollectionId, n as REACTOR_SCHEMA, o as instrumentPgPool, p as SimpleJobExecutor, r as getMigrationStatus, s as KyselyOperationStore, t as DEFAULT_DRIVE_CONTAINER_TYPES, u as RevisionMismatchError, v as KyselyOperationIndex, w as DuplicateModuleError, x as createEmptyConsistencyToken, y as DocumentMetaCache } from "./drive-container-types-BNpMlgT_.js";
2
- import { n as ReactorEventTypes, t as EventBusAggregateError } from "./types-CxSpmNGK.js";
1
+ import { A as parsePagingOptions, C as DuplicateManifestError, D as DocumentDeletedError, E as ModuleNotFoundError, O as DocumentNotFoundError, S as CollectionMembershipCache, T as InvalidModuleError, _ as KyselyWriteCache, a as createForwardingPoolInstrumentation, b as createConsistencyToken, c as DuplicateOperationError, d as KyselyKeyframeStore, f as DocumentModelRegistry, g as EventBus, h as KyselyExecutionScope, i as runMigrations, j as throwIfAborted, k as matchesScope, l as OptimisticLockError, m as DriveCollectionId, n as REACTOR_SCHEMA, o as instrumentPgPool, p as SimpleJobExecutor, r as getMigrationStatus, s as KyselyOperationStore, t as DEFAULT_DRIVE_CONTAINER_TYPES, u as RevisionMismatchError, v as KyselyOperationIndex, w as DuplicateModuleError, x as createEmptyConsistencyToken, y as DocumentMetaCache } from "./drive-container-types-BuFNr0j0.js";
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-B2iLRB0o.js";
4
+ import { a as ReadModelCoordinator, i as KyselyDocumentView, n as ConsistencyTracker, o as BaseReadModel, r as makeConsistencyKey, t as KyselyDocumentIndexer } from "./document-indexer-B6iippzG.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-SUoDhurA.js";
6
+ import { t as workerEntryPath } from "./worker-Dzrw1YMV.js";
7
7
  import { actions, actions as documentActions, createPresignedHeader, generateId, hashDocumentStateForScope, replayDocument } 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
- import { ConsoleLogger } from "document-model";
10
+ import { ConsoleLogger, childLogger } from "document-model";
11
11
  import { sql } from "kysely";
12
12
  import { RelationalDbProcessor, createRelationalDb } from "@powerhousedao/shared/processors";
13
13
  //#region src/actions/index.ts
@@ -737,6 +737,14 @@ var ReactorClient = class {
737
737
  return await this.reactor.getByIdOrSlug(identifier, view, void 0, signal);
738
738
  }
739
739
  /**
740
+ * Resolves an identifier (id or slug) to the canonical document id, using the
741
+ * same lookup as the data path. Resolves against the "main" branch. Throws if
742
+ * the identifier cannot be resolved or is ambiguous.
743
+ */
744
+ async resolveIdOrSlug(identifier, signal) {
745
+ return this.documentView.resolveIdOrSlug(identifier, void 0, void 0, signal);
746
+ }
747
+ /**
740
748
  * Retrieves operations for a document
741
749
  */
742
750
  async getOperations(documentIdentifier, view, filter, paging, signal) {
@@ -1858,6 +1866,7 @@ var InMemoryJobTracker = class {
1858
1866
  if (!job) {
1859
1867
  this.jobs.set(jobId, {
1860
1868
  id: jobId,
1869
+ documentId: "",
1861
1870
  status: JobStatus.RUNNING,
1862
1871
  createdAtUtcIso: (/* @__PURE__ */ new Date()).toISOString(),
1863
1872
  consistencyToken: createEmptyConsistencyToken(),
@@ -1878,6 +1887,7 @@ var InMemoryJobTracker = class {
1878
1887
  if (!existing) {
1879
1888
  this.jobs.set(jobId, {
1880
1889
  id: jobId,
1890
+ documentId: job?.documentId ?? "",
1881
1891
  status: JobStatus.FAILED,
1882
1892
  createdAtUtcIso: (/* @__PURE__ */ new Date()).toISOString(),
1883
1893
  completedAtUtcIso: (/* @__PURE__ */ new Date()).toISOString(),
@@ -2686,6 +2696,8 @@ var DocumentModelResolver = class {
2686
2696
  loadingModels = /* @__PURE__ */ new Map();
2687
2697
  failedModelTypes = /* @__PURE__ */ new Set();
2688
2698
  broadcastHook = null;
2699
+ modelLoadedHook = null;
2700
+ logger = childLogger(["reactor", "document-model-resolver"]);
2689
2701
  constructor(registry, loader) {
2690
2702
  this.registry = registry;
2691
2703
  this.loader = loader;
@@ -2698,6 +2710,13 @@ var DocumentModelResolver = class {
2698
2710
  setBroadcastHook(hook) {
2699
2711
  this.broadcastHook = hook;
2700
2712
  }
2713
+ /**
2714
+ * Post-success hook called with the document type after a model loads, so
2715
+ * peers (tabs) can load the same type via the event bus.
2716
+ */
2717
+ setModelLoadedHook(hook) {
2718
+ this.modelLoadedHook = hook;
2719
+ }
2701
2720
  async ensureModelLoaded(documentType) {
2702
2721
  try {
2703
2722
  this.registry.getModule(documentType);
@@ -2710,20 +2729,32 @@ var DocumentModelResolver = class {
2710
2729
  if (existing) return existing;
2711
2730
  const loadPromise = (async () => {
2712
2731
  try {
2713
- const module = await this.loader.load(documentType);
2714
- const [result] = this.registry.registerModules(module);
2715
- if (result.status === "error" && !DuplicateModuleError.isError(result.error)) throw result.error;
2716
- await this.broadcastIfPossible(documentType);
2732
+ await this.loadRegisterAndBroadcast(documentType);
2717
2733
  } catch (error) {
2718
2734
  this.failedModelTypes.add(documentType);
2719
2735
  throw error;
2720
2736
  } finally {
2721
2737
  this.loadingModels.delete(documentType);
2722
2738
  }
2739
+ await this.notifyPeersModelLoaded(documentType);
2723
2740
  })();
2724
2741
  this.loadingModels.set(documentType, loadPromise);
2725
2742
  return loadPromise;
2726
2743
  }
2744
+ async loadRegisterAndBroadcast(documentType) {
2745
+ const module = await this.loader.load(documentType);
2746
+ const [result] = this.registry.registerModules(module);
2747
+ if (result.status === "error" && !DuplicateModuleError.isError(result.error)) throw result.error;
2748
+ await this.broadcastIfPossible(documentType);
2749
+ }
2750
+ async notifyPeersModelLoaded(documentType) {
2751
+ if (!this.modelLoadedHook) return;
2752
+ try {
2753
+ await this.modelLoadedHook(documentType);
2754
+ } catch (error) {
2755
+ this.logger.warn(`MODEL_LOADED hook failed: ${documentType}`, error);
2756
+ }
2757
+ }
2727
2758
  async broadcastIfPossible(documentType) {
2728
2759
  if (!this.broadcastHook || !this.loader.resolveSpec) return;
2729
2760
  const entry = await this.loader.resolveSpec(documentType);
@@ -3295,6 +3326,20 @@ var GraphQLRequestError = class extends Error {
3295
3326
  this.statusCode = statusCode;
3296
3327
  }
3297
3328
  };
3329
+ /** Auth-rejection message fragments the switchboard emits. Shared with
3330
+ * reactor-api so server throws and this client check can't drift. */
3331
+ const DRIVE_AUTH_ERROR_MESSAGES = {
3332
+ forbidden: "Forbidden: insufficient permissions",
3333
+ authenticationRequired: "Forbidden: authentication required"
3334
+ };
3335
+ /** True when the remote rejected the caller as unauthenticated/unauthorized:
3336
+ * an HTTP 401/403, or a Forbidden/Unauthorized GraphQL error. */
3337
+ function isDriveAuthError(error) {
3338
+ if (!(error instanceof GraphQLRequestError)) return false;
3339
+ if (error.category === "http") return error.statusCode === 401 || error.statusCode === 403;
3340
+ if (error.category === "graphql") return Object.values(DRIVE_AUTH_ERROR_MESSAGES).some((m) => error.message.includes(m));
3341
+ return false;
3342
+ }
3298
3343
  var PollingChannelError = class extends Error {
3299
3344
  constructor(message) {
3300
3345
  super(message);
@@ -3946,7 +3991,10 @@ var GqlRequestChannel = class {
3946
3991
  isPushing = false;
3947
3992
  pendingDrain = false;
3948
3993
  receivingPages = false;
3994
+ isRecovering = false;
3949
3995
  connectionState = "connecting";
3996
+ /** Latest unrecoverable error was an auth rejection; cleared on connect. */
3997
+ requiresAuth = false;
3950
3998
  connectionStateCallbacks = /* @__PURE__ */ new Set();
3951
3999
  constructor(logger, channelId, remoteName, cursorStorage, config, operationIndex, pollTimer) {
3952
4000
  this.logger = logger;
@@ -4038,7 +4086,8 @@ var GqlRequestChannel = class {
4038
4086
  lastFailureUtcMs: this.lastFailureUtcMs ?? 0,
4039
4087
  pushBlocked: this.pushBlocked,
4040
4088
  pushFailureCount: this.pushFailureCount,
4041
- receivingPages: this.receivingPages
4089
+ receivingPages: this.receivingPages,
4090
+ requiresAuth: this.requiresAuth
4042
4091
  };
4043
4092
  }
4044
4093
  onConnectionStateChange(callback) {
@@ -4069,6 +4118,7 @@ var GqlRequestChannel = class {
4069
4118
  this.transitionConnectionState("connected");
4070
4119
  }
4071
4120
  transitionConnectionState(next) {
4121
+ if (next === "connected") this.requiresAuth = false;
4072
4122
  if (this.connectionState === next) return;
4073
4123
  this.connectionState = next;
4074
4124
  const snapshot = this.getConnectionState();
@@ -4143,6 +4193,7 @@ var GqlRequestChannel = class {
4143
4193
  this.logger.error("GqlChannel poll error (@FailureCount, @Classification): @Error", this.failureCount, classification, channelError);
4144
4194
  if (classification === "unrecoverable") {
4145
4195
  this.pollTimer.stop();
4196
+ this.requiresAuth = isDriveAuthError(err);
4146
4197
  this.transitionConnectionState("error");
4147
4198
  return true;
4148
4199
  }
@@ -4154,16 +4205,23 @@ var GqlRequestChannel = class {
4154
4205
  * Self-retries with backoff instead of restarting the poll timer on failure.
4155
4206
  */
4156
4207
  recoverFromChannelNotFound() {
4208
+ if (this.isRecovering) return;
4209
+ this.isRecovering = true;
4157
4210
  this.logger.info("GqlChannel @ChannelId not found on remote, re-registering...", this.channelId);
4158
4211
  this.pollTimer.stop();
4159
4212
  const attemptRecovery = (attempt) => {
4160
- if (this.isShutdown) return;
4213
+ if (this.isShutdown) {
4214
+ this.isRecovering = false;
4215
+ return;
4216
+ }
4161
4217
  this.touchRemoteChannel().then(({ ackOrdinal }) => {
4162
4218
  this.logger.info("GqlChannel @ChannelId re-registered successfully", this.channelId);
4219
+ this.isRecovering = false;
4163
4220
  this.failureCount = 0;
4164
4221
  if (ackOrdinal > 0) trimMailboxFromAckOrdinal(this.outbox, ackOrdinal);
4165
4222
  this.pollTimer.start();
4166
4223
  this.transitionConnectionState("connected");
4224
+ this.resumePushAfterRecovery();
4167
4225
  }).catch((recoveryError) => {
4168
4226
  const err = recoveryError instanceof Error ? recoveryError : new Error(String(recoveryError));
4169
4227
  const classification = this.classifyError(err);
@@ -4171,6 +4229,8 @@ var GqlRequestChannel = class {
4171
4229
  this.failureCount++;
4172
4230
  this.lastFailureUtcMs = Date.now();
4173
4231
  if (classification === "unrecoverable") {
4232
+ this.isRecovering = false;
4233
+ this.requiresAuth = isDriveAuthError(err);
4174
4234
  this.transitionConnectionState("error");
4175
4235
  return;
4176
4236
  }
@@ -4182,6 +4242,22 @@ var GqlRequestChannel = class {
4182
4242
  attemptRecovery(1);
4183
4243
  }
4184
4244
  /**
4245
+ * Resumes pushing outbox items that were blocked while the channel was being
4246
+ * recreated. Called after a successful re-registration.
4247
+ */
4248
+ resumePushAfterRecovery() {
4249
+ if (this.isShutdown) return;
4250
+ if (!this.pushBlocked) return;
4251
+ if (this.pushRetryTimer) {
4252
+ clearTimeout(this.pushRetryTimer);
4253
+ this.pushRetryTimer = null;
4254
+ }
4255
+ this.pushBlocked = false;
4256
+ this.pushFailureCount = 0;
4257
+ const items = this.outbox.items;
4258
+ if (items.length > 0) this.attemptPush([...items]);
4259
+ }
4260
+ /**
4185
4261
  * Queries the remote GraphQL endpoint for sync envelopes.
4186
4262
  */
4187
4263
  async pollSyncEnvelopes(ackOrdinal, latestOrdinal) {
@@ -4272,7 +4348,7 @@ var GqlRequestChannel = class {
4272
4348
  async touchRemoteChannel() {
4273
4349
  let sinceTimestampUtcMs = "0";
4274
4350
  try {
4275
- const result = await this.operationIndex.getLatestTimestampForCollection(this.config.collectionId);
4351
+ const result = await this.operationIndex.getLatestTimestampForCollection(this.config.collectionId.key);
4276
4352
  if (result) sinceTimestampUtcMs = result;
4277
4353
  } catch {}
4278
4354
  const mutation = `
@@ -4286,7 +4362,7 @@ var GqlRequestChannel = class {
4286
4362
  const variables = { input: {
4287
4363
  id: this.channelId,
4288
4364
  name: this.channelId,
4289
- collectionId: this.config.collectionId,
4365
+ collectionId: this.config.collectionId.key,
4290
4366
  filter: {
4291
4367
  documentId: this.config.filter.documentId,
4292
4368
  scope: this.config.filter.scope,
@@ -4317,6 +4393,12 @@ var GqlRequestChannel = class {
4317
4393
  this.pendingDrain = false;
4318
4394
  if (this.isShutdown) return;
4319
4395
  const err = error instanceof Error ? error : new Error(String(error));
4396
+ if (err.message.includes("Channel not found")) {
4397
+ this.pushBlocked = true;
4398
+ this.transitionConnectionState("reconnecting");
4399
+ this.recoverFromChannelNotFound();
4400
+ return;
4401
+ }
4320
4402
  if (this.classifyError(err) === "recoverable") {
4321
4403
  this.pushFailureCount++;
4322
4404
  this.pushBlocked = true;
@@ -4328,6 +4410,7 @@ var GqlRequestChannel = class {
4328
4410
  for (const syncOp of syncOps) syncOp.failed(channelError);
4329
4411
  this.deadLetter.add(...syncOps);
4330
4412
  this.outbox.remove(...syncOps);
4413
+ this.requiresAuth = isDriveAuthError(err);
4331
4414
  this.transitionConnectionState("error");
4332
4415
  }
4333
4416
  });
@@ -4620,7 +4703,8 @@ var GqlResponseChannel = class {
4620
4703
  lastFailureUtcMs: 0,
4621
4704
  pushBlocked: false,
4622
4705
  pushFailureCount: 0,
4623
- receivingPages: false
4706
+ receivingPages: false,
4707
+ requiresAuth: false
4624
4708
  };
4625
4709
  }
4626
4710
  onConnectionStateChange(callback) {
@@ -4819,7 +4903,7 @@ function rowToRemoteRecord(row) {
4819
4903
  return {
4820
4904
  id: row.channel_id,
4821
4905
  name: row.name,
4822
- collectionId: row.collection_id,
4906
+ collectionId: DriveCollectionId.fromKey(row.collection_id),
4823
4907
  channelConfig: {
4824
4908
  type: row.channel_type,
4825
4909
  parameters: row.channel_parameters ?? {}
@@ -4846,16 +4930,21 @@ function rowToRemoteRecord(row) {
4846
4930
  }
4847
4931
  };
4848
4932
  }
4933
+ /**
4934
+ * Maps a RemoteRecord to a row. jsonb array columns are JSON-encoded because the
4935
+ * pg driver would otherwise emit a Postgres array literal ("{a,b}"), which jsonb
4936
+ * rejects (matches sync-dead-letter-storage).
4937
+ */
4849
4938
  function remoteRecordToRow(remote) {
4850
4939
  return {
4851
4940
  name: remote.name,
4852
- collection_id: remote.collectionId,
4941
+ collection_id: remote.collectionId.key,
4853
4942
  channel_type: remote.channelConfig.type,
4854
4943
  channel_id: remote.id,
4855
4944
  remote_name: remote.name,
4856
4945
  channel_parameters: remote.channelConfig.parameters,
4857
- filter_document_ids: remote.filter.documentId.length > 0 ? remote.filter.documentId : null,
4858
- filter_scopes: remote.filter.scope.length > 0 ? remote.filter.scope : null,
4946
+ filter_document_ids: remote.filter.documentId.length > 0 ? JSON.stringify(remote.filter.documentId) : null,
4947
+ filter_scopes: remote.filter.scope.length > 0 ? JSON.stringify(remote.filter.scope) : null,
4859
4948
  filter_branch: remote.filter.branch,
4860
4949
  push_state: remote.status.push.state,
4861
4950
  push_last_success_utc_ms: remote.status.push.lastSuccessUtcMs ? new Date(remote.status.push.lastSuccessUtcMs).toISOString() : null,
@@ -5002,7 +5091,7 @@ var BatchAggregator = class {
5002
5091
  if (!this.driveContainerTypes.has(op.context.documentType)) continue;
5003
5092
  const input = action.input;
5004
5093
  if (!input?.sourceId || !input.targetId) continue;
5005
- const collectionId = driveCollectionId(op.context.branch, input.sourceId);
5094
+ const collectionId = DriveCollectionId.forDrive(input.sourceId, op.context.branch).key;
5006
5095
  if (!(input.targetId in mergedMemberships)) mergedMemberships[input.targetId] = [];
5007
5096
  if (!mergedMemberships[input.targetId].includes(collectionId)) mergedMemberships[input.targetId].push(collectionId);
5008
5097
  }
@@ -5292,11 +5381,14 @@ var SyncManager = class {
5292
5381
  for (const record of remoteRecords) {
5293
5382
  const channel = this.channelFactory.instance(record.id, record.name, record.channelConfig, this.cursorStorage, record.collectionId, record.filter, this.operationIndex, record.options);
5294
5383
  const remote = {
5295
- id: record.id,
5296
- name: record.name,
5297
- collectionId: record.collectionId,
5298
- filter: record.filter,
5299
- options: record.options,
5384
+ meta: {
5385
+ id: record.id,
5386
+ name: record.name,
5387
+ collectionId: record.collectionId,
5388
+ channelConfig: record.channelConfig,
5389
+ filter: record.filter,
5390
+ options: record.options
5391
+ },
5300
5392
  channel
5301
5393
  };
5302
5394
  this.remotes.set(record.name, remote);
@@ -5315,7 +5407,7 @@ var SyncManager = class {
5315
5407
  this.backfillAbortControllers.set(record.name, backfillController);
5316
5408
  this.updateOutbox(remote, outboxAckOrdinal, OutboxMode.Backfill, backfillController.signal).catch((error) => {
5317
5409
  if (backfillController.signal.aborted) return;
5318
- this.logger.error("Backfill failed for remote @RemoteName: @Error", remote.name, error instanceof Error ? error : new Error(String(error)));
5410
+ this.logger.error("Backfill failed for remote @RemoteName: @Error", remote.meta.name, error instanceof Error ? error : new Error(String(error)));
5319
5411
  }).finally(() => {
5320
5412
  this.backfillAbortControllers.delete(record.name);
5321
5413
  });
@@ -5359,7 +5451,7 @@ var SyncManager = class {
5359
5451
  return remote;
5360
5452
  }
5361
5453
  getById(id) {
5362
- for (const remote of this.remotes.values()) if (remote.id === id) return remote;
5454
+ for (const remote of this.remotes.values()) if (remote.meta.id === id) return remote;
5363
5455
  throw new Error(`Remote with id '${id}' does not exist`);
5364
5456
  }
5365
5457
  async add(name, collectionId, channelConfig, filter = {
@@ -5369,7 +5461,7 @@ var SyncManager = class {
5369
5461
  }, options = { sinceTimestampUtcMs: "0" }, id) {
5370
5462
  if (this.isShutdown) throw new Error("SyncManager is shutdown and cannot add remotes");
5371
5463
  if (this.remotes.has(name)) throw new Error(`Remote with name '${name}' already exists`);
5372
- this.logger.debug("Adding remote (@name, @collectionId, @channelConfig, @filter, @options, @id)", name, collectionId, channelConfig, filter, options, id);
5464
+ this.logger.debug("Adding remote (@name, @collectionId, @channelConfig, @filter, @options, @id)", name, collectionId.key, channelConfig, filter, options, id);
5373
5465
  const remoteId = id ?? crypto.randomUUID();
5374
5466
  const remoteRecord = {
5375
5467
  id: remoteId,
@@ -5386,11 +5478,14 @@ var SyncManager = class {
5386
5478
  await this.remoteStorage.upsert(remoteRecord);
5387
5479
  const channel = this.channelFactory.instance(remoteId, name, channelConfig, this.cursorStorage, collectionId, filter, this.operationIndex, options);
5388
5480
  const remote = {
5389
- id: remoteId,
5390
- name,
5391
- collectionId,
5392
- filter,
5393
- options,
5481
+ meta: {
5482
+ id: remoteId,
5483
+ name,
5484
+ collectionId,
5485
+ channelConfig,
5486
+ filter,
5487
+ options
5488
+ },
5394
5489
  channel
5395
5490
  };
5396
5491
  this.remotes.set(name, remote);
@@ -5407,7 +5502,7 @@ var SyncManager = class {
5407
5502
  this.backfillAbortControllers.set(name, backfillController);
5408
5503
  this.updateOutbox(remote, 0, OutboxMode.Backfill, backfillController.signal).catch((error) => {
5409
5504
  if (backfillController.signal.aborted) return;
5410
- this.logger.error("Backfill failed for remote @RemoteName: @Error", remote.name, error instanceof Error ? error : new Error(String(error)));
5505
+ this.logger.error("Backfill failed for remote @RemoteName: @Error", remote.meta.name, error instanceof Error ? error : new Error(String(error)));
5411
5506
  }).finally(() => {
5412
5507
  this.backfillAbortControllers.delete(name);
5413
5508
  });
@@ -5458,20 +5553,20 @@ var SyncManager = class {
5458
5553
  }
5459
5554
  wireChannelCallbacks(remote) {
5460
5555
  remote.channel.inbox.onAdded((syncOps) => this.handleInboxAdded(remote, syncOps));
5461
- this.syncStatusTracker.trackRemote(remote.name, remote.channel);
5556
+ this.syncStatusTracker.trackRemote(remote.meta.name, remote.channel);
5462
5557
  const unsubscribe = remote.channel.onConnectionStateChange((snapshot) => {
5463
5558
  this.eventBus.emit(SyncEventTypes.CONNECTION_STATE_CHANGED, {
5464
- remoteName: remote.name,
5465
- remoteId: remote.id,
5559
+ remoteName: remote.meta.name,
5560
+ remoteId: remote.meta.id,
5466
5561
  previous: snapshot.state,
5467
5562
  current: snapshot.state,
5468
5563
  snapshot
5469
5564
  }).catch(() => {});
5470
5565
  });
5471
- this.connectionStateUnsubscribes.set(remote.name, unsubscribe);
5566
+ this.connectionStateUnsubscribes.set(remote.meta.name, unsubscribe);
5472
5567
  remote.channel.deadLetter.onAdded((syncOps) => {
5473
5568
  for (const syncOp of syncOps) {
5474
- this.logger.error("Dead letter (@remote, @documentId, @jobId, @error, @dependencies)", remote.name, syncOp.documentId, syncOp.jobId, syncOp.error?.message ?? "unknown", syncOp.jobDependencies);
5569
+ this.logger.error("Dead letter (@remote, @documentId, @jobId, @error, @dependencies)", remote.meta.name, syncOp.documentId, syncOp.jobId, syncOp.error?.message ?? "unknown", syncOp.jobDependencies);
5475
5570
  this.quarantinedDocumentIds.add(syncOp.documentId);
5476
5571
  const record = {
5477
5572
  id: syncOp.id,
@@ -5507,12 +5602,12 @@ var SyncManager = class {
5507
5602
  async loadDeadLetters(remote) {
5508
5603
  let records;
5509
5604
  try {
5510
- records = (await this.deadLetterStorage.list(remote.name, {
5605
+ records = (await this.deadLetterStorage.list(remote.meta.name, {
5511
5606
  cursor: "0",
5512
5607
  limit: this.config.maxDeadLettersPerRemote
5513
5608
  })).results;
5514
5609
  } catch (error) {
5515
- this.logger.error("Failed to load dead letters for remote (@name, @error)", remote.name, error instanceof Error ? error.message : String(error));
5610
+ this.logger.error("Failed to load dead letters for remote (@name, @error)", remote.meta.name, error instanceof Error ? error.message : String(error));
5516
5611
  return;
5517
5612
  }
5518
5613
  if (records.length === 0) return;
@@ -5524,10 +5619,10 @@ var SyncManager = class {
5524
5619
  syncOps.push(syncOp);
5525
5620
  }
5526
5621
  remote.channel.deadLetter.add(...syncOps);
5527
- this.logger.debug("Loaded @count persisted dead letters for remote @name", records.length, remote.name);
5622
+ this.logger.debug("Loaded @count persisted dead letters for remote @name", records.length, remote.meta.name);
5528
5623
  }
5529
5624
  getRemotesForCollection(collectionId) {
5530
- return Array.from(this.remotes.values()).filter((remote) => remote.collectionId === collectionId);
5625
+ return Array.from(this.remotes.values()).filter((remote) => remote.meta.collectionId.key === collectionId);
5531
5626
  }
5532
5627
  async processCompleteBatch(batch) {
5533
5628
  if (this.isShutdown) return;
@@ -5573,11 +5668,11 @@ var SyncManager = class {
5573
5668
  const operations = syncOp.operations.map((op) => op.operation);
5574
5669
  let jobInfo;
5575
5670
  try {
5576
- jobInfo = await this.reactor.load(syncOp.documentId, syncOp.branch, operations, this.abortController.signal, { sourceRemote: remote.name });
5671
+ jobInfo = await this.reactor.load(syncOp.documentId, syncOp.branch, operations, this.abortController.signal, { sourceRemote: remote.meta.name });
5577
5672
  } catch (error) {
5578
5673
  if (this.isShutdown) return;
5579
5674
  const err = error instanceof Error ? error : new Error(String(error));
5580
- this.logger.error("Failed to load operations from inbox (@remote, @documentId, @error)", remote.name, syncOp.documentId, err.message);
5675
+ this.logger.error("Failed to load operations from inbox (@remote, @documentId, @error)", remote.meta.name, syncOp.documentId, err.message);
5581
5676
  const channelError = new ChannelError(ChannelErrorSource.Inbox, err);
5582
5677
  syncOp.failed(channelError);
5583
5678
  remote.channel.deadLetter.add(syncOp);
@@ -5590,7 +5685,7 @@ var SyncManager = class {
5590
5685
  } catch (error) {
5591
5686
  if (this.isShutdown) return;
5592
5687
  const err = error instanceof Error ? error : new Error(String(error));
5593
- this.logger.error("Failed to wait for job completion (@remote, @documentId, @jobId, @error)", remote.name, syncOp.documentId, jobInfo.id, err.message);
5688
+ this.logger.error("Failed to wait for job completion (@remote, @documentId, @jobId, @error)", remote.meta.name, syncOp.documentId, jobInfo.id, err.message);
5594
5689
  const channelError = new ChannelError(ChannelErrorSource.Inbox, err);
5595
5690
  syncOp.failed(channelError);
5596
5691
  remote.channel.deadLetter.add(syncOp);
@@ -5600,7 +5695,7 @@ var SyncManager = class {
5600
5695
  if (this.isShutdown) return;
5601
5696
  if (completedJobInfo.status === JobStatus.FAILED) {
5602
5697
  const errorMessage = completedJobInfo.error?.message || "Unknown error";
5603
- this.logger.error("Failed to apply operations from inbox (@remote, @documentId, @jobId, @error)", remote.name, syncOp.documentId, completedJobInfo.id, errorMessage);
5698
+ this.logger.error("Failed to apply operations from inbox (@remote, @documentId, @jobId, @error)", remote.meta.name, syncOp.documentId, completedJobInfo.id, errorMessage);
5604
5699
  const error = new ChannelError(ChannelErrorSource.Inbox, /* @__PURE__ */ new Error(`Failed to apply operations: ${errorMessage}`));
5605
5700
  syncOp.failed(error);
5606
5701
  remote.channel.deadLetter.add(syncOp);
@@ -5608,7 +5703,7 @@ var SyncManager = class {
5608
5703
  remote.channel.inbox.remove(syncOp);
5609
5704
  }
5610
5705
  async applyInboxBatch(items) {
5611
- const sourceRemote = items[0].remote.name;
5706
+ const sourceRemote = items[0].remote.meta.name;
5612
5707
  const chunkKeys = new Set(items.map(({ syncOp }) => syncOp.jobId));
5613
5708
  const jobs = items.map(({ syncOp }) => {
5614
5709
  const dependsOn = [];
@@ -5658,7 +5753,7 @@ var SyncManager = class {
5658
5753
  }
5659
5754
  for (const { remote, syncOp } of items) {
5660
5755
  if (!(syncOp.jobId in result.jobs)) {
5661
- this.logger.error("Job key missing from batch load result (@remote, @documentId, @jobId)", remote.name, syncOp.documentId, syncOp.jobId);
5756
+ this.logger.error("Job key missing from batch load result (@remote, @documentId, @jobId)", remote.meta.name, syncOp.documentId, syncOp.jobId);
5662
5757
  const error = new ChannelError(ChannelErrorSource.Inbox, /* @__PURE__ */ new Error(`Job key '${syncOp.jobId}' missing from batch load result`));
5663
5758
  syncOp.failed(error);
5664
5759
  remote.channel.deadLetter.add(syncOp);
@@ -5692,7 +5787,7 @@ var SyncManager = class {
5692
5787
  let maxOrdinal = ackOrdinal;
5693
5788
  const lastJobByDoc = /* @__PURE__ */ new Map();
5694
5789
  let prevChainJobId;
5695
- const sinceTimestamp = remote.options.sinceTimestampUtcMs;
5790
+ const sinceTimestamp = remote.meta.options.sinceTimestampUtcMs;
5696
5791
  const emitBatches = (operations) => {
5697
5792
  if (operations.length === 0) return;
5698
5793
  const batches = batchOperationsByDocument(operations);
@@ -5703,14 +5798,14 @@ var SyncManager = class {
5703
5798
  const deps = [];
5704
5799
  if (prevJobId) deps.push(prevJobId);
5705
5800
  if (mode === OutboxMode.BatchTriggered && prevChainJobId && prevChainJobId !== prevJobId) deps.push(prevChainJobId);
5706
- const syncOp = new SyncOperation(crypto.randomUUID(), jobId, deps, remote.name, batch.documentId, [batch.scope], batch.branch, batch.operations);
5801
+ const syncOp = new SyncOperation(crypto.randomUUID(), jobId, deps, remote.meta.name, batch.documentId, [batch.scope], batch.branch, batch.operations);
5707
5802
  syncOps.push(syncOp);
5708
5803
  lastJobByDoc.set(batch.documentId, jobId);
5709
5804
  if (mode === OutboxMode.BatchTriggered) prevChainJobId = jobId;
5710
5805
  }
5711
5806
  remote.channel.outbox.add(...syncOps);
5712
5807
  };
5713
- let page = await this.operationIndex.find(remote.collectionId, ackOrdinal, { excludeSourceRemote: remote.name }, void 0, composedSignal);
5808
+ let page = await this.operationIndex.find(remote.meta.collectionId.key, ackOrdinal, { excludeSourceRemote: remote.meta.name }, void 0, composedSignal);
5714
5809
  let carry = [];
5715
5810
  let hasMore;
5716
5811
  do {
@@ -5722,7 +5817,7 @@ var SyncManager = class {
5722
5817
  carry = [];
5723
5818
  }
5724
5819
  if (sinceTimestamp && sinceTimestamp !== "0") operations = operations.filter((op) => op.operation.timestampUtcMs >= sinceTimestamp);
5725
- operations = filterOperations(operations, remote.filter);
5820
+ operations = filterOperations(operations, remote.meta.filter);
5726
5821
  operations = operations.filter((op) => !this.quarantinedDocumentIds.has(op.context.documentId));
5727
5822
  hasMore = !!page.next;
5728
5823
  if (operations.length > 0) {
@@ -6029,6 +6124,7 @@ var Reactor = class {
6029
6124
  };
6030
6125
  const jobInfo = {
6031
6126
  id: jobId,
6127
+ documentId: job.documentId,
6032
6128
  status: JobStatus.PENDING,
6033
6129
  createdAtUtcIso,
6034
6130
  consistencyToken: {
@@ -6067,6 +6163,7 @@ var Reactor = class {
6067
6163
  };
6068
6164
  const jobInfo = {
6069
6165
  id: jobId,
6166
+ documentId: job.documentId,
6070
6167
  status: JobStatus.PENDING,
6071
6168
  createdAtUtcIso,
6072
6169
  consistencyToken: {
@@ -6104,6 +6201,7 @@ var Reactor = class {
6104
6201
  };
6105
6202
  const jobInfo = {
6106
6203
  id: jobId,
6204
+ documentId: job.documentId,
6107
6205
  status: JobStatus.PENDING,
6108
6206
  createdAtUtcIso,
6109
6207
  consistencyToken: {
@@ -6143,6 +6241,7 @@ var Reactor = class {
6143
6241
  };
6144
6242
  const jobInfo = {
6145
6243
  id: jobId,
6244
+ documentId: job.documentId,
6146
6245
  status: JobStatus.PENDING,
6147
6246
  createdAtUtcIso,
6148
6247
  consistencyToken: {
@@ -6177,6 +6276,7 @@ var Reactor = class {
6177
6276
  for (const jobPlan of request.jobs) {
6178
6277
  const jobInfo = {
6179
6278
  id: planKeyToJobId.get(jobPlan.key),
6279
+ documentId: jobPlan.documentId,
6180
6280
  status: JobStatus.PENDING,
6181
6281
  createdAtUtcIso,
6182
6282
  consistencyToken: {
@@ -6246,6 +6346,7 @@ var Reactor = class {
6246
6346
  for (const jobPlan of request.jobs) {
6247
6347
  const jobInfo = {
6248
6348
  id: planKeyToJobId.get(jobPlan.key),
6349
+ documentId: jobPlan.documentId,
6249
6350
  status: JobStatus.PENDING,
6250
6351
  createdAtUtcIso,
6251
6352
  consistencyToken: {
@@ -6318,6 +6419,7 @@ var Reactor = class {
6318
6419
  const now = (/* @__PURE__ */ new Date()).toISOString();
6319
6420
  return Promise.resolve({
6320
6421
  id: jobId,
6422
+ documentId: "",
6321
6423
  status: JobStatus.FAILED,
6322
6424
  createdAtUtcIso: now,
6323
6425
  completedAtUtcIso: now,
@@ -6645,6 +6747,7 @@ var ReactorBuilder = class {
6645
6747
  const keyframeStore = new KyselyKeyframeStore(database);
6646
6748
  const eventBus = this.eventBus || new EventBus();
6647
6749
  const resolver = this.documentModelLoader ? new DocumentModelResolver(documentModelRegistry, this.documentModelLoader) : new NullDocumentModelResolver(documentModelRegistry);
6750
+ if (resolver instanceof DocumentModelResolver) resolver.setModelLoadedHook((documentType) => eventBus.emit(ReactorEventTypes.MODEL_LOADED, { documentType }));
6648
6751
  const queue = this.queueInstance ?? new InMemoryQueue(eventBus, resolver);
6649
6752
  const jobTracker = new InMemoryJobTracker(eventBus);
6650
6753
  const writeCache = new KyselyWriteCache(keyframeStore, operationStore, documentModelRegistry, {
@@ -6760,7 +6863,7 @@ var ReactorBuilder = class {
6760
6863
  poolInstrumentations.push(forwarder);
6761
6864
  this.instrumentedPools.push(forwarder);
6762
6865
  }
6763
- const { ProjectionShardManager } = await import("./projection-shard-manager-_c7orNo5.js");
6866
+ const { ProjectionShardManager } = await import("./projection-shard-manager-BnQKFqQd.js");
6764
6867
  const manager = new ProjectionShardManager({
6765
6868
  shardCount: config.shardCount,
6766
6869
  db,
@@ -6793,7 +6896,7 @@ var ReactorBuilder = class {
6793
6896
  const [{ WorkerHandle }, { createThreadTransport }, { workerEntryPath }] = await Promise.all([
6794
6897
  import("./worker-handle-B1w03nRA.js"),
6795
6898
  import("./transport-ByGviWdZ.js"),
6796
- import("./worker-SUoDhurA.js").then((n) => n.n)
6899
+ import("./worker-Dzrw1YMV.js").then((n) => n.n)
6797
6900
  ]);
6798
6901
  const db = this.workerDbConfig;
6799
6902
  const signatureVerifier = this.workerSignatureVerifierSpec;
@@ -7160,6 +7263,6 @@ var DocumentIntegrityService = class {
7160
7263
  }
7161
7264
  };
7162
7265
  //#endregion
7163
- 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, createForwardingLogger, createMutableShutdownStatus, createRelationalDb, deleteDocumentAction, documentActions, driveCollectionId, driveIdFromUrl, envelopesToSyncOperations, errorToInfo, getMigrationStatus, instrumentPgPool, makeConsistencyKey, parseDriveUrl, parsePagingOptions, removeRelationshipAction, runMigrations, sanitizeArg, trimMailboxFromAckOrdinal, updateRelationshipAction, upgradeDocumentAction, workerEntryPath };
7266
+ export { BaseReadModel, ChannelError, ChannelErrorSource, ChannelScheme, ConsistencyTracker, DEFAULT_DRIVE_CONTAINER_TYPES, DRIVE_AUTH_ERROR_MESSAGES, DefaultSubscriptionErrorHandler, DocumentChangeType, DocumentIntegrityService, DocumentModelRegistry, DocumentModelResolver, DriveClient, DriveCollectionId, 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, createForwardingLogger, createMutableShutdownStatus, createRelationalDb, deleteDocumentAction, documentActions, driveIdFromUrl, envelopesToSyncOperations, errorToInfo, getMigrationStatus, instrumentPgPool, isDriveAuthError, makeConsistencyKey, parseDriveUrl, parsePagingOptions, removeRelationshipAction, runMigrations, sanitizeArg, trimMailboxFromAckOrdinal, updateRelationshipAction, upgradeDocumentAction, workerEntryPath };
7164
7267
 
7165
7268
  //# sourceMappingURL=index.js.map