@powerhousedao/ph-cli 6.0.0-dev.77 → 6.0.0-dev.78

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/src/cli.js CHANGED
@@ -330605,14 +330605,14 @@ class BaseReadModel {
330605
330605
  operationIndex;
330606
330606
  writeCache;
330607
330607
  consistencyTracker;
330608
- readModelId;
330608
+ config;
330609
330609
  lastOrdinal = 0;
330610
- constructor(db, operationIndex, writeCache, consistencyTracker, readModelId) {
330610
+ constructor(db, operationIndex, writeCache, consistencyTracker, config2) {
330611
330611
  this.db = db;
330612
330612
  this.operationIndex = operationIndex;
330613
330613
  this.writeCache = writeCache;
330614
330614
  this.consistencyTracker = consistencyTracker;
330615
- this.readModelId = readModelId;
330615
+ this.config = config2;
330616
330616
  }
330617
330617
  async init() {
330618
330618
  const viewState = await this.loadState();
@@ -330620,21 +330620,22 @@ class BaseReadModel {
330620
330620
  this.lastOrdinal = viewState;
330621
330621
  const missedOperations = await this.operationIndex.getSinceOrdinal(this.lastOrdinal);
330622
330622
  if (missedOperations.results.length > 0) {
330623
- const opsWithState = await this.rebuildStateForOperations(missedOperations.results);
330624
- await this.indexOperations(opsWithState);
330623
+ const ops = this.config.rebuildStateOnInit ? await this.rebuildStateForOperations(missedOperations.results) : missedOperations.results;
330624
+ await this.indexOperations(ops);
330625
330625
  }
330626
330626
  } else {
330627
330627
  await this.initializeState();
330628
330628
  const allOperations = await this.operationIndex.getSinceOrdinal(0);
330629
330629
  if (allOperations.results.length > 0) {
330630
- const opsWithState = await this.rebuildStateForOperations(allOperations.results);
330631
- await this.indexOperations(opsWithState);
330630
+ const ops = this.config.rebuildStateOnInit ? await this.rebuildStateForOperations(allOperations.results) : allOperations.results;
330631
+ await this.indexOperations(ops);
330632
330632
  }
330633
330633
  }
330634
330634
  }
330635
330635
  async indexOperations(items) {
330636
330636
  if (items.length === 0)
330637
330637
  return;
330638
+ await this.commitOperations(items);
330638
330639
  await this.db.transaction().execute(async (trx) => {
330639
330640
  await this.saveState(trx, items);
330640
330641
  });
@@ -330646,6 +330647,7 @@ class BaseReadModel {
330646
330647
  }
330647
330648
  await this.consistencyTracker.waitFor(token.coordinates, timeoutMs, signal);
330648
330649
  }
330650
+ async commitOperations(items) {}
330649
330651
  async rebuildStateForOperations(operations2) {
330650
330652
  const result = [];
330651
330653
  for (const op of operations2) {
@@ -330664,13 +330666,13 @@ class BaseReadModel {
330664
330666
  }
330665
330667
  async loadState() {
330666
330668
  const viewStateDb = this.db;
330667
- const row = await viewStateDb.selectFrom("ViewState").select("lastOrdinal").where("readModelId", "=", this.readModelId).executeTakeFirst();
330669
+ const row = await viewStateDb.selectFrom("ViewState").select("lastOrdinal").where("readModelId", "=", this.config.readModelId).executeTakeFirst();
330668
330670
  return row?.lastOrdinal;
330669
330671
  }
330670
330672
  async initializeState() {
330671
330673
  const viewStateDb = this.db;
330672
330674
  await viewStateDb.insertInto("ViewState").values({
330673
- readModelId: this.readModelId,
330675
+ readModelId: this.config.readModelId,
330674
330676
  lastOrdinal: 0
330675
330677
  }).execute();
330676
330678
  }
@@ -330680,7 +330682,7 @@ class BaseReadModel {
330680
330682
  await trx.updateTable("ViewState").set({
330681
330683
  lastOrdinal: maxOrdinal,
330682
330684
  lastOperationTimestamp: new Date
330683
- }).where("readModelId", "=", this.readModelId).execute();
330685
+ }).where("readModelId", "=", this.config.readModelId).execute();
330684
330686
  }
330685
330687
  updateConsistencyTracker(items) {
330686
330688
  const coordinates = [];
@@ -331348,6 +331350,315 @@ class ConsistencyTracker {
331348
331350
  }
331349
331351
  }
331350
331352
  }
331353
+ async function up(db) {
331354
+ await db.schema.createTable("Operation").addColumn("id", "serial", (col) => col.primaryKey()).addColumn("jobId", "text", (col) => col.notNull()).addColumn("opId", "text", (col) => col.notNull()).addColumn("prevOpId", "text", (col) => col.notNull()).addColumn("writeTimestampUtcMs", "timestamptz", (col) => col.notNull().defaultTo(sql3`NOW()`)).addColumn("documentId", "text", (col) => col.notNull()).addColumn("documentType", "text", (col) => col.notNull()).addColumn("scope", "text", (col) => col.notNull()).addColumn("branch", "text", (col) => col.notNull()).addColumn("timestampUtcMs", "timestamptz", (col) => col.notNull()).addColumn("index", "integer", (col) => col.notNull()).addColumn("action", "jsonb", (col) => col.notNull()).addColumn("skip", "integer", (col) => col.notNull()).addColumn("error", "text").addColumn("hash", "text", (col) => col.notNull()).addUniqueConstraint("unique_revision", [
331355
+ "documentId",
331356
+ "scope",
331357
+ "branch",
331358
+ "index"
331359
+ ]).addUniqueConstraint("unique_operation_instance", ["opId", "index", "skip"]).execute();
331360
+ await db.schema.createIndex("streamOperations").on("Operation").columns(["documentId", "scope", "branch", "id"]).execute();
331361
+ await db.schema.createIndex("branchlessStreamOperations").on("Operation").columns(["documentId", "scope", "id"]).execute();
331362
+ }
331363
+ async function up2(db) {
331364
+ await db.schema.createTable("Keyframe").addColumn("id", "serial", (col) => col.primaryKey()).addColumn("documentId", "text", (col) => col.notNull()).addColumn("documentType", "text", (col) => col.notNull()).addColumn("scope", "text", (col) => col.notNull()).addColumn("branch", "text", (col) => col.notNull()).addColumn("revision", "integer", (col) => col.notNull()).addColumn("document", "jsonb", (col) => col.notNull()).addColumn("createdAt", "timestamptz", (col) => col.notNull().defaultTo(sql3`NOW()`)).addUniqueConstraint("unique_keyframe", [
331365
+ "documentId",
331366
+ "scope",
331367
+ "branch",
331368
+ "revision"
331369
+ ]).execute();
331370
+ await db.schema.createIndex("keyframe_lookup").on("Keyframe").columns(["documentId", "scope", "branch", "revision"]).execute();
331371
+ }
331372
+ async function up3(db) {
331373
+ await db.schema.createTable("Document").addColumn("id", "text", (col) => col.primaryKey()).addColumn("createdAt", "timestamptz", (col) => col.notNull().defaultTo(sql3`NOW()`)).addColumn("updatedAt", "timestamptz", (col) => col.notNull().defaultTo(sql3`NOW()`)).execute();
331374
+ }
331375
+ async function up4(db) {
331376
+ await db.schema.createTable("DocumentRelationship").addColumn("id", "text", (col) => col.primaryKey()).addColumn("sourceId", "text", (col) => col.notNull().references("Document.id").onDelete("cascade")).addColumn("targetId", "text", (col) => col.notNull().references("Document.id").onDelete("cascade")).addColumn("relationshipType", "text", (col) => col.notNull()).addColumn("metadata", "jsonb").addColumn("createdAt", "timestamptz", (col) => col.notNull().defaultTo(sql3`NOW()`)).addColumn("updatedAt", "timestamptz", (col) => col.notNull().defaultTo(sql3`NOW()`)).addUniqueConstraint("unique_source_target_type", [
331377
+ "sourceId",
331378
+ "targetId",
331379
+ "relationshipType"
331380
+ ]).execute();
331381
+ await db.schema.createIndex("idx_relationship_source").on("DocumentRelationship").column("sourceId").execute();
331382
+ await db.schema.createIndex("idx_relationship_target").on("DocumentRelationship").column("targetId").execute();
331383
+ await db.schema.createIndex("idx_relationship_type").on("DocumentRelationship").column("relationshipType").execute();
331384
+ }
331385
+ async function up5(db) {
331386
+ await db.schema.createTable("IndexerState").addColumn("id", "integer", (col) => col.primaryKey().generatedAlwaysAsIdentity()).addColumn("lastOperationId", "integer", (col) => col.notNull()).addColumn("lastOperationTimestamp", "timestamptz", (col) => col.notNull().defaultTo(sql3`NOW()`)).execute();
331387
+ }
331388
+ async function up6(db) {
331389
+ await db.schema.createTable("DocumentSnapshot").addColumn("id", "text", (col) => col.primaryKey()).addColumn("documentId", "text", (col) => col.notNull()).addColumn("slug", "text").addColumn("name", "text").addColumn("scope", "text", (col) => col.notNull()).addColumn("branch", "text", (col) => col.notNull()).addColumn("content", "jsonb", (col) => col.notNull()).addColumn("documentType", "text", (col) => col.notNull()).addColumn("lastOperationIndex", "integer", (col) => col.notNull()).addColumn("lastOperationHash", "text", (col) => col.notNull()).addColumn("lastUpdatedAt", "timestamptz", (col) => col.notNull().defaultTo(sql3`NOW()`)).addColumn("snapshotVersion", "integer", (col) => col.notNull().defaultTo(1)).addColumn("identifiers", "jsonb").addColumn("metadata", "jsonb").addColumn("isDeleted", "boolean", (col) => col.notNull().defaultTo(false)).addColumn("deletedAt", "timestamptz").addUniqueConstraint("unique_doc_scope_branch", [
331390
+ "documentId",
331391
+ "scope",
331392
+ "branch"
331393
+ ]).execute();
331394
+ await db.schema.createIndex("idx_slug_scope_branch").on("DocumentSnapshot").columns(["slug", "scope", "branch"]).execute();
331395
+ await db.schema.createIndex("idx_doctype_scope_branch").on("DocumentSnapshot").columns(["documentType", "scope", "branch"]).execute();
331396
+ await db.schema.createIndex("idx_last_updated").on("DocumentSnapshot").column("lastUpdatedAt").execute();
331397
+ await db.schema.createIndex("idx_is_deleted").on("DocumentSnapshot").column("isDeleted").execute();
331398
+ }
331399
+ async function up7(db) {
331400
+ await db.schema.createTable("SlugMapping").addColumn("slug", "text", (col) => col.primaryKey()).addColumn("documentId", "text", (col) => col.notNull()).addColumn("scope", "text", (col) => col.notNull()).addColumn("branch", "text", (col) => col.notNull()).addColumn("createdAt", "timestamptz", (col) => col.notNull().defaultTo(sql3`NOW()`)).addColumn("updatedAt", "timestamptz", (col) => col.notNull().defaultTo(sql3`NOW()`)).addUniqueConstraint("unique_docid_scope_branch", [
331401
+ "documentId",
331402
+ "scope",
331403
+ "branch"
331404
+ ]).execute();
331405
+ await db.schema.createIndex("idx_slug_documentid").on("SlugMapping").column("documentId").execute();
331406
+ }
331407
+ async function up8(db) {
331408
+ await db.schema.createTable("ViewState").addColumn("readModelId", "text", (col) => col.primaryKey()).addColumn("lastOrdinal", "integer", (col) => col.notNull().defaultTo(0)).addColumn("lastOperationTimestamp", "timestamptz", (col) => col.notNull().defaultTo(sql3`NOW()`)).execute();
331409
+ }
331410
+ async function up9(db) {
331411
+ await db.schema.createTable("document_collections").addColumn("documentId", "text", (col) => col.notNull()).addColumn("collectionId", "text", (col) => col.notNull()).addColumn("joinedOrdinal", "bigint", (col) => col.notNull().defaultTo(0)).addColumn("leftOrdinal", "bigint").addPrimaryKeyConstraint("document_collections_pkey", [
331412
+ "documentId",
331413
+ "collectionId"
331414
+ ]).execute();
331415
+ await db.schema.createIndex("idx_document_collections_collectionId").on("document_collections").column("collectionId").execute();
331416
+ await db.schema.createIndex("idx_doc_collections_collection_range").on("document_collections").columns(["collectionId", "joinedOrdinal"]).execute();
331417
+ await db.schema.createTable("operation_index_operations").addColumn("ordinal", "serial", (col) => col.primaryKey()).addColumn("opId", "text", (col) => col.notNull()).addColumn("documentId", "text", (col) => col.notNull()).addColumn("documentType", "text", (col) => col.notNull()).addColumn("scope", "text", (col) => col.notNull()).addColumn("branch", "text", (col) => col.notNull()).addColumn("timestampUtcMs", "text", (col) => col.notNull()).addColumn("writeTimestampUtcMs", "timestamptz", (col) => col.notNull().defaultTo(sql3`NOW()`)).addColumn("index", "integer", (col) => col.notNull()).addColumn("skip", "integer", (col) => col.notNull()).addColumn("hash", "text", (col) => col.notNull()).addColumn("action", "jsonb", (col) => col.notNull()).execute();
331418
+ await db.schema.createIndex("idx_operation_index_operations_document").on("operation_index_operations").columns(["documentId", "branch", "scope"]).execute();
331419
+ await db.schema.createIndex("idx_operation_index_operations_ordinal").on("operation_index_operations").column("ordinal").execute();
331420
+ }
331421
+ async function up10(db) {
331422
+ await db.schema.createTable("sync_remotes").addColumn("name", "text", (col) => col.primaryKey()).addColumn("collection_id", "text", (col) => col.notNull()).addColumn("channel_type", "text", (col) => col.notNull()).addColumn("channel_id", "text", (col) => col.notNull().defaultTo("")).addColumn("remote_name", "text", (col) => col.notNull().defaultTo("")).addColumn("channel_parameters", "jsonb", (col) => col.notNull().defaultTo(sql3`'{}'::jsonb`)).addColumn("filter_document_ids", "jsonb").addColumn("filter_scopes", "jsonb").addColumn("filter_branch", "text", (col) => col.notNull().defaultTo("main")).addColumn("push_state", "text", (col) => col.notNull().defaultTo("idle")).addColumn("push_last_success_utc_ms", "text").addColumn("push_last_failure_utc_ms", "text").addColumn("push_failure_count", "integer", (col) => col.notNull().defaultTo(0)).addColumn("pull_state", "text", (col) => col.notNull().defaultTo("idle")).addColumn("pull_last_success_utc_ms", "text").addColumn("pull_last_failure_utc_ms", "text").addColumn("pull_failure_count", "integer", (col) => col.notNull().defaultTo(0)).addColumn("created_at", "timestamptz", (col) => col.notNull().defaultTo(sql3`NOW()`)).addColumn("updated_at", "timestamptz", (col) => col.notNull().defaultTo(sql3`NOW()`)).execute();
331423
+ await db.schema.createIndex("idx_sync_remotes_collection").on("sync_remotes").column("collection_id").execute();
331424
+ await db.schema.createTable("sync_cursors").addColumn("remote_name", "text", (col) => col.primaryKey().references("sync_remotes.name").onDelete("cascade")).addColumn("cursor_ordinal", "bigint", (col) => col.notNull().defaultTo(0)).addColumn("last_synced_at_utc_ms", "text").addColumn("updated_at", "timestamptz", (col) => col.notNull().defaultTo(sql3`NOW()`)).execute();
331425
+ await db.schema.createIndex("idx_sync_cursors_ordinal").on("sync_cursors").column("cursor_ordinal").execute();
331426
+ }
331427
+ async function up11(db) {
331428
+ await db.deleteFrom("sync_cursors").where("remote_name", "like", "outbox::%").execute();
331429
+ await db.deleteFrom("sync_remotes").where("name", "like", "outbox::%").execute();
331430
+ await db.schema.dropTable("sync_cursors").execute();
331431
+ await db.schema.createTable("sync_cursors").addColumn("remote_name", "text", (col) => col.notNull()).addColumn("cursor_type", "text", (col) => col.notNull().defaultTo("inbox")).addColumn("cursor_ordinal", "bigint", (col) => col.notNull().defaultTo(0)).addColumn("last_synced_at_utc_ms", "text").addColumn("updated_at", "timestamptz", (col) => col.notNull().defaultTo(sql3`NOW()`)).addPrimaryKeyConstraint("sync_cursors_pk", ["remote_name", "cursor_type"]).execute();
331432
+ await db.schema.createIndex("idx_sync_cursors_ordinal").on("sync_cursors").column("cursor_ordinal").execute();
331433
+ }
331434
+ async function up12(db) {
331435
+ await db.schema.alterTable("operation_index_operations").addColumn("sourceRemote", "text", (col) => col.notNull().defaultTo("")).execute();
331436
+ }
331437
+ async function up13(db) {
331438
+ await db.schema.createTable("sync_dead_letters").addColumn("ordinal", "serial", (col) => col.primaryKey()).addColumn("id", "text", (col) => col.unique().notNull()).addColumn("job_id", "text", (col) => col.notNull()).addColumn("job_dependencies", "jsonb", (col) => col.notNull().defaultTo(sql3`'[]'::jsonb`)).addColumn("remote_name", "text", (col) => col.notNull().references("sync_remotes.name").onDelete("cascade")).addColumn("document_id", "text", (col) => col.notNull()).addColumn("scopes", "jsonb", (col) => col.notNull().defaultTo(sql3`'[]'::jsonb`)).addColumn("branch", "text", (col) => col.notNull()).addColumn("operations", "jsonb", (col) => col.notNull().defaultTo(sql3`'[]'::jsonb`)).addColumn("error_source", "text", (col) => col.notNull()).addColumn("error_message", "text", (col) => col.notNull()).addColumn("created_at", "timestamptz", (col) => col.notNull().defaultTo(sql3`NOW()`)).execute();
331439
+ await db.schema.createIndex("idx_sync_dead_letters_remote").on("sync_dead_letters").column("remote_name").execute();
331440
+ }
331441
+ async function up14(db) {
331442
+ await db.schema.createTable("ProcessorCursor").addColumn("processorId", "text", (col) => col.primaryKey()).addColumn("factoryId", "text", (col) => col.notNull()).addColumn("driveId", "text", (col) => col.notNull()).addColumn("processorIndex", "integer", (col) => col.notNull()).addColumn("lastOrdinal", "integer", (col) => col.notNull().defaultTo(sql3`0`)).addColumn("status", "text", (col) => col.notNull().defaultTo(sql3`'active'`)).addColumn("lastError", "text").addColumn("lastErrorTimestamp", "timestamptz").addColumn("createdAt", "timestamptz", (col) => col.notNull().defaultTo(sql3`NOW()`)).addColumn("updatedAt", "timestamptz", (col) => col.notNull().defaultTo(sql3`NOW()`)).execute();
331443
+ }
331444
+
331445
+ class ProgrammaticMigrationProvider {
331446
+ getMigrations() {
331447
+ return Promise.resolve(migrations);
331448
+ }
331449
+ }
331450
+ async function runMigrations(db, schema2 = REACTOR_SCHEMA) {
331451
+ try {
331452
+ await sql3`CREATE SCHEMA IF NOT EXISTS ${sql3.id(schema2)}`.execute(db);
331453
+ } catch (error210) {
331454
+ return {
331455
+ success: false,
331456
+ migrationsExecuted: [],
331457
+ error: error210 instanceof Error ? error210 : new Error("Failed to create schema")
331458
+ };
331459
+ }
331460
+ const migrator2 = new Migrator2({
331461
+ db: db.withSchema(schema2),
331462
+ provider: new ProgrammaticMigrationProvider,
331463
+ migrationTableSchema: schema2
331464
+ });
331465
+ let error48;
331466
+ let results;
331467
+ try {
331468
+ const result = await migrator2.migrateToLatest();
331469
+ error48 = result.error;
331470
+ results = result.results;
331471
+ } catch (e) {
331472
+ error48 = e;
331473
+ results = [];
331474
+ }
331475
+ const migrationsExecuted = results?.map((result) => result.migrationName) ?? [];
331476
+ if (error48) {
331477
+ return {
331478
+ success: false,
331479
+ migrationsExecuted,
331480
+ error: error48 instanceof Error ? error48 : new Error("Unknown migration error")
331481
+ };
331482
+ }
331483
+ return {
331484
+ success: true,
331485
+ migrationsExecuted
331486
+ };
331487
+ }
331488
+ async function getMigrationStatus(db, schema2 = REACTOR_SCHEMA) {
331489
+ const migrator2 = new Migrator2({
331490
+ db: db.withSchema(schema2),
331491
+ provider: new ProgrammaticMigrationProvider,
331492
+ migrationTableSchema: schema2
331493
+ });
331494
+ return await migrator2.getMigrations();
331495
+ }
331496
+
331497
+ class ReactorSubscriptionManager {
331498
+ createdSubscriptions = new Map;
331499
+ deletedSubscriptions = new Map;
331500
+ updatedSubscriptions = new Map;
331501
+ relationshipSubscriptions = new Map;
331502
+ subscriptionCounter = 0;
331503
+ errorHandler;
331504
+ constructor(errorHandler) {
331505
+ this.errorHandler = errorHandler;
331506
+ }
331507
+ onDocumentCreated(callback, search) {
331508
+ const id = `created-${++this.subscriptionCounter}`;
331509
+ this.createdSubscriptions.set(id, { id, callback, search });
331510
+ return () => {
331511
+ this.createdSubscriptions.delete(id);
331512
+ };
331513
+ }
331514
+ onDocumentDeleted(callback, search) {
331515
+ const id = `deleted-${++this.subscriptionCounter}`;
331516
+ this.deletedSubscriptions.set(id, { id, callback, search });
331517
+ return () => {
331518
+ this.deletedSubscriptions.delete(id);
331519
+ };
331520
+ }
331521
+ onDocumentStateUpdated(callback, search, view) {
331522
+ const id = `updated-${++this.subscriptionCounter}`;
331523
+ this.updatedSubscriptions.set(id, { id, callback, search, view });
331524
+ return () => {
331525
+ this.updatedSubscriptions.delete(id);
331526
+ };
331527
+ }
331528
+ onRelationshipChanged(callback, search) {
331529
+ const id = `relationship-${++this.subscriptionCounter}`;
331530
+ this.relationshipSubscriptions.set(id, { id, callback, search });
331531
+ return () => {
331532
+ this.relationshipSubscriptions.delete(id);
331533
+ };
331534
+ }
331535
+ notifyDocumentsCreated(documentIds, documentTypes, parentIds) {
331536
+ const result = {
331537
+ results: documentIds,
331538
+ options: { cursor: "", limit: documentIds.length }
331539
+ };
331540
+ for (const subscription of this.createdSubscriptions.values()) {
331541
+ const filteredIds = this.filterDocumentIds(documentIds, subscription.search, documentTypes, parentIds);
331542
+ if (filteredIds.length > 0) {
331543
+ try {
331544
+ subscription.callback({
331545
+ ...result,
331546
+ results: filteredIds
331547
+ });
331548
+ } catch (error48) {
331549
+ this.errorHandler.handleError(error48, {
331550
+ eventType: "created",
331551
+ subscriptionId: subscription.id,
331552
+ eventData: filteredIds
331553
+ });
331554
+ }
331555
+ }
331556
+ }
331557
+ }
331558
+ notifyDocumentsDeleted(documentIds, documentTypes, parentIds) {
331559
+ for (const subscription of this.deletedSubscriptions.values()) {
331560
+ const filteredIds = this.filterDocumentIds(documentIds, subscription.search, documentTypes, parentIds);
331561
+ if (filteredIds.length > 0) {
331562
+ try {
331563
+ subscription.callback(filteredIds);
331564
+ } catch (error48) {
331565
+ this.errorHandler.handleError(error48, {
331566
+ eventType: "deleted",
331567
+ subscriptionId: subscription.id,
331568
+ eventData: filteredIds
331569
+ });
331570
+ }
331571
+ }
331572
+ }
331573
+ }
331574
+ notifyDocumentsUpdated(documents2) {
331575
+ const result = {
331576
+ results: documents2,
331577
+ options: { cursor: "", limit: documents2.length }
331578
+ };
331579
+ for (const subscription of this.updatedSubscriptions.values()) {
331580
+ const filteredDocs = this.filterDocuments(documents2, subscription.search);
331581
+ if (filteredDocs.length > 0) {
331582
+ try {
331583
+ subscription.callback({
331584
+ ...result,
331585
+ results: filteredDocs
331586
+ });
331587
+ } catch (error48) {
331588
+ this.errorHandler.handleError(error48, {
331589
+ eventType: "updated",
331590
+ subscriptionId: subscription.id,
331591
+ eventData: filteredDocs
331592
+ });
331593
+ }
331594
+ }
331595
+ }
331596
+ }
331597
+ notifyRelationshipChanged(parentId, childId, changeType, childType) {
331598
+ for (const subscription of this.relationshipSubscriptions.values()) {
331599
+ if (this.matchesRelationshipFilter(parentId, childId, childType, subscription.search)) {
331600
+ try {
331601
+ subscription.callback(parentId, childId, changeType);
331602
+ } catch (error48) {
331603
+ this.errorHandler.handleError(error48, {
331604
+ eventType: "relationshipChanged",
331605
+ subscriptionId: subscription.id,
331606
+ eventData: { parentId, childId, changeType }
331607
+ });
331608
+ }
331609
+ }
331610
+ }
331611
+ }
331612
+ clearAll() {
331613
+ this.createdSubscriptions.clear();
331614
+ this.deletedSubscriptions.clear();
331615
+ this.updatedSubscriptions.clear();
331616
+ this.relationshipSubscriptions.clear();
331617
+ }
331618
+ filterDocumentIds(documentIds, search, documentTypes, parentIds) {
331619
+ if (!search)
331620
+ return documentIds;
331621
+ return documentIds.filter((id) => {
331622
+ if (search.ids && !search.ids.includes(id))
331623
+ return false;
331624
+ if (search.type && documentTypes) {
331625
+ const docType = documentTypes.get(id);
331626
+ if (docType !== search.type)
331627
+ return false;
331628
+ }
331629
+ if (search.parentId && parentIds) {
331630
+ const parentId = parentIds.get(id);
331631
+ if (parentId !== search.parentId)
331632
+ return false;
331633
+ }
331634
+ return true;
331635
+ });
331636
+ }
331637
+ filterDocuments(documents2, search) {
331638
+ if (!search)
331639
+ return documents2;
331640
+ return documents2.filter((doc2) => {
331641
+ if (search.ids && !search.ids.includes(doc2.header.id))
331642
+ return false;
331643
+ if (search.type && doc2.header.documentType !== search.type)
331644
+ return false;
331645
+ if (search.slugs && !search.slugs.includes(doc2.header.slug))
331646
+ return false;
331647
+ return true;
331648
+ });
331649
+ }
331650
+ matchesRelationshipFilter(parentId, childId, childType, search) {
331651
+ if (!search)
331652
+ return true;
331653
+ if (search.parentId && parentId !== search.parentId)
331654
+ return false;
331655
+ if (search.ids && !search.ids.includes(childId))
331656
+ return false;
331657
+ if (search.type && childType && childType !== search.type)
331658
+ return false;
331659
+ return true;
331660
+ }
331661
+ }
331351
331662
 
331352
331663
  class Mailbox {
331353
331664
  itemsMap = new Map;
@@ -331707,6 +332018,24 @@ function trimMailboxFromBatch(mailbox, batch) {
331707
332018
  mailbox.remove(...toRemove);
331708
332019
  }
331709
332020
  }
332021
+ function trimMailboxFromAckOrdinal(mailbox, ackOrdinal) {
332022
+ const toRemove = [];
332023
+ for (const syncOp of mailbox.items) {
332024
+ let maxOrdinal = 0;
332025
+ for (const op of syncOp.operations) {
332026
+ maxOrdinal = Math.max(maxOrdinal, op.context.ordinal);
332027
+ }
332028
+ if (maxOrdinal <= ackOrdinal) {
332029
+ toRemove.push(syncOp);
332030
+ }
332031
+ }
332032
+ if (toRemove.length > 0) {
332033
+ for (const syncOp of toRemove) {
332034
+ syncOp.executed();
332035
+ }
332036
+ mailbox.remove(...toRemove);
332037
+ }
332038
+ }
331710
332039
  function filterOperations(operations2, filter) {
331711
332040
  return operations2.filter((op) => {
331712
332041
  if (filter.branch && op.context.branch !== filter.branch) {
@@ -331727,6 +332056,19 @@ function createIdleHealth() {
331727
332056
  failureCount: 0
331728
332057
  };
331729
332058
  }
332059
+ function sortEnvelopesByFirstOperationTimestamp(envelopes) {
332060
+ return envelopes.slice().sort((a2, b3) => {
332061
+ const aTimestamp = a2.operations?.[0]?.operation.timestampUtcMs;
332062
+ const bTimestamp = b3.operations?.[0]?.operation.timestampUtcMs;
332063
+ if (!aTimestamp && !bTimestamp)
332064
+ return 0;
332065
+ if (!aTimestamp)
332066
+ return 1;
332067
+ if (!bTimestamp)
332068
+ return -1;
332069
+ return new Date(aTimestamp).getTime() - new Date(bTimestamp).getTime();
332070
+ });
332071
+ }
331730
332072
  function batchOperationsByDocument(operations2) {
331731
332073
  const batches = [];
331732
332074
  let currentDocId = null;
@@ -331776,6 +332118,62 @@ function toOperationWithContext(entry) {
331776
332118
  }
331777
332119
  };
331778
332120
  }
332121
+ function consolidateSyncOperations(syncOps) {
332122
+ if (syncOps.length <= 1) {
332123
+ return syncOps;
332124
+ }
332125
+ const groups = new Map;
332126
+ const jobIdRemap = new Map;
332127
+ const insertionOrder = [];
332128
+ for (const syncOp of syncOps) {
332129
+ const key = `${syncOp.documentId}|${syncOp.scopes.slice().sort().join(",")}|${syncOp.branch}`;
332130
+ const existing = groups.get(key);
332131
+ if (existing) {
332132
+ existing.ops.push(syncOp);
332133
+ if (syncOp.jobId && syncOp.jobId !== existing.canonicalJobId) {
332134
+ jobIdRemap.set(syncOp.jobId, existing.canonicalJobId);
332135
+ }
332136
+ } else {
332137
+ groups.set(key, { ops: [syncOp], canonicalJobId: syncOp.jobId });
332138
+ insertionOrder.push(key);
332139
+ }
332140
+ }
332141
+ const result = [];
332142
+ for (const key of insertionOrder) {
332143
+ const group = groups.get(key);
332144
+ const allOperations = group.ops.flatMap((op) => op.operations).sort((a2, b3) => a2.context.ordinal - b3.context.ordinal);
332145
+ const allDeps = new Set;
332146
+ for (const op of group.ops) {
332147
+ for (const dep of op.jobDependencies) {
332148
+ allDeps.add(dep);
332149
+ }
332150
+ }
332151
+ allDeps.delete(group.canonicalJobId);
332152
+ for (const op of group.ops) {
332153
+ allDeps.delete(op.jobId);
332154
+ }
332155
+ const remappedDeps = [];
332156
+ for (const dep of allDeps) {
332157
+ const mapped = jobIdRemap.get(dep) ?? dep;
332158
+ if (!remappedDeps.includes(mapped) && mapped !== group.canonicalJobId) {
332159
+ remappedDeps.push(mapped);
332160
+ }
332161
+ }
332162
+ const first = group.ops[0];
332163
+ const merged = new SyncOperation(first.id, first.jobId, remappedDeps, first.remoteName, first.documentId, first.scopes, first.branch, allOperations);
332164
+ if (first.status > 0) {
332165
+ if (first.status >= 3) {
332166
+ merged.executed();
332167
+ } else if (first.status >= 2) {
332168
+ merged.transported();
332169
+ } else {
332170
+ merged.started();
332171
+ }
332172
+ }
332173
+ result.push(merged);
332174
+ }
332175
+ return result;
332176
+ }
331779
332177
  function mergeCollectionMemberships(events) {
331780
332178
  const mergedMemberships = {};
331781
332179
  for (const event of events) {
@@ -331811,310 +332209,681 @@ function mergeCollectionMemberships(events) {
331811
332209
  }
331812
332210
  return mergedMemberships;
331813
332211
  }
331814
- async function up(db) {
331815
- await db.schema.createTable("Operation").addColumn("id", "serial", (col) => col.primaryKey()).addColumn("jobId", "text", (col) => col.notNull()).addColumn("opId", "text", (col) => col.notNull()).addColumn("prevOpId", "text", (col) => col.notNull()).addColumn("writeTimestampUtcMs", "timestamptz", (col) => col.notNull().defaultTo(sql3`NOW()`)).addColumn("documentId", "text", (col) => col.notNull()).addColumn("documentType", "text", (col) => col.notNull()).addColumn("scope", "text", (col) => col.notNull()).addColumn("branch", "text", (col) => col.notNull()).addColumn("timestampUtcMs", "timestamptz", (col) => col.notNull()).addColumn("index", "integer", (col) => col.notNull()).addColumn("action", "jsonb", (col) => col.notNull()).addColumn("skip", "integer", (col) => col.notNull()).addColumn("error", "text").addColumn("hash", "text", (col) => col.notNull()).addUniqueConstraint("unique_revision", [
331816
- "documentId",
331817
- "scope",
331818
- "branch",
331819
- "index"
331820
- ]).addUniqueConstraint("unique_operation_instance", ["opId", "index", "skip"]).execute();
331821
- await db.schema.createIndex("streamOperations").on("Operation").columns(["documentId", "scope", "branch", "id"]).execute();
331822
- await db.schema.createIndex("branchlessStreamOperations").on("Operation").columns(["documentId", "scope", "id"]).execute();
331823
- }
331824
- async function up2(db) {
331825
- await db.schema.createTable("Keyframe").addColumn("id", "serial", (col) => col.primaryKey()).addColumn("documentId", "text", (col) => col.notNull()).addColumn("documentType", "text", (col) => col.notNull()).addColumn("scope", "text", (col) => col.notNull()).addColumn("branch", "text", (col) => col.notNull()).addColumn("revision", "integer", (col) => col.notNull()).addColumn("document", "jsonb", (col) => col.notNull()).addColumn("createdAt", "timestamptz", (col) => col.notNull().defaultTo(sql3`NOW()`)).addUniqueConstraint("unique_keyframe", [
331826
- "documentId",
331827
- "scope",
331828
- "branch",
331829
- "revision"
331830
- ]).execute();
331831
- await db.schema.createIndex("keyframe_lookup").on("Keyframe").columns(["documentId", "scope", "branch", "revision"]).execute();
331832
- }
331833
- async function up3(db) {
331834
- await db.schema.createTable("Document").addColumn("id", "text", (col) => col.primaryKey()).addColumn("createdAt", "timestamptz", (col) => col.notNull().defaultTo(sql3`NOW()`)).addColumn("updatedAt", "timestamptz", (col) => col.notNull().defaultTo(sql3`NOW()`)).execute();
332212
+ function calculateBackoffDelay(consecutiveFailures, retryBaseDelayMs, retryMaxDelayMs, random) {
332213
+ const backoff = Math.min(retryMaxDelayMs, retryBaseDelayMs * Math.pow(2, consecutiveFailures - 1));
332214
+ return backoff / 2 + random * (backoff / 2);
331835
332215
  }
331836
- async function up4(db) {
331837
- await db.schema.createTable("DocumentRelationship").addColumn("id", "text", (col) => col.primaryKey()).addColumn("sourceId", "text", (col) => col.notNull().references("Document.id").onDelete("cascade")).addColumn("targetId", "text", (col) => col.notNull().references("Document.id").onDelete("cascade")).addColumn("relationshipType", "text", (col) => col.notNull()).addColumn("metadata", "jsonb").addColumn("createdAt", "timestamptz", (col) => col.notNull().defaultTo(sql3`NOW()`)).addColumn("updatedAt", "timestamptz", (col) => col.notNull().defaultTo(sql3`NOW()`)).addUniqueConstraint("unique_source_target_type", [
331838
- "sourceId",
331839
- "targetId",
331840
- "relationshipType"
331841
- ]).execute();
331842
- await db.schema.createIndex("idx_relationship_source").on("DocumentRelationship").column("sourceId").execute();
331843
- await db.schema.createIndex("idx_relationship_target").on("DocumentRelationship").column("targetId").execute();
331844
- await db.schema.createIndex("idx_relationship_type").on("DocumentRelationship").column("relationshipType").execute();
331845
- }
331846
- async function up5(db) {
331847
- await db.schema.createTable("IndexerState").addColumn("id", "integer", (col) => col.primaryKey().generatedAlwaysAsIdentity()).addColumn("lastOperationId", "integer", (col) => col.notNull()).addColumn("lastOperationTimestamp", "timestamptz", (col) => col.notNull().defaultTo(sql3`NOW()`)).execute();
331848
- }
331849
- async function up6(db) {
331850
- await db.schema.createTable("DocumentSnapshot").addColumn("id", "text", (col) => col.primaryKey()).addColumn("documentId", "text", (col) => col.notNull()).addColumn("slug", "text").addColumn("name", "text").addColumn("scope", "text", (col) => col.notNull()).addColumn("branch", "text", (col) => col.notNull()).addColumn("content", "jsonb", (col) => col.notNull()).addColumn("documentType", "text", (col) => col.notNull()).addColumn("lastOperationIndex", "integer", (col) => col.notNull()).addColumn("lastOperationHash", "text", (col) => col.notNull()).addColumn("lastUpdatedAt", "timestamptz", (col) => col.notNull().defaultTo(sql3`NOW()`)).addColumn("snapshotVersion", "integer", (col) => col.notNull().defaultTo(1)).addColumn("identifiers", "jsonb").addColumn("metadata", "jsonb").addColumn("isDeleted", "boolean", (col) => col.notNull().defaultTo(false)).addColumn("deletedAt", "timestamptz").addUniqueConstraint("unique_doc_scope_branch", [
331851
- "documentId",
331852
- "scope",
331853
- "branch"
331854
- ]).execute();
331855
- await db.schema.createIndex("idx_slug_scope_branch").on("DocumentSnapshot").columns(["slug", "scope", "branch"]).execute();
331856
- await db.schema.createIndex("idx_doctype_scope_branch").on("DocumentSnapshot").columns(["documentType", "scope", "branch"]).execute();
331857
- await db.schema.createIndex("idx_last_updated").on("DocumentSnapshot").column("lastUpdatedAt").execute();
331858
- await db.schema.createIndex("idx_is_deleted").on("DocumentSnapshot").column("isDeleted").execute();
331859
- }
331860
- async function up7(db) {
331861
- await db.schema.createTable("SlugMapping").addColumn("slug", "text", (col) => col.primaryKey()).addColumn("documentId", "text", (col) => col.notNull()).addColumn("scope", "text", (col) => col.notNull()).addColumn("branch", "text", (col) => col.notNull()).addColumn("createdAt", "timestamptz", (col) => col.notNull().defaultTo(sql3`NOW()`)).addColumn("updatedAt", "timestamptz", (col) => col.notNull().defaultTo(sql3`NOW()`)).addUniqueConstraint("unique_docid_scope_branch", [
331862
- "documentId",
331863
- "scope",
331864
- "branch"
331865
- ]).execute();
331866
- await db.schema.createIndex("idx_slug_documentid").on("SlugMapping").column("documentId").execute();
331867
- }
331868
- async function up8(db) {
331869
- await db.schema.createTable("ViewState").addColumn("readModelId", "text", (col) => col.primaryKey()).addColumn("lastOrdinal", "integer", (col) => col.notNull().defaultTo(0)).addColumn("lastOperationTimestamp", "timestamptz", (col) => col.notNull().defaultTo(sql3`NOW()`)).execute();
331870
- }
331871
- async function up9(db) {
331872
- await db.schema.createTable("document_collections").addColumn("documentId", "text", (col) => col.notNull()).addColumn("collectionId", "text", (col) => col.notNull()).addColumn("joinedOrdinal", "bigint", (col) => col.notNull().defaultTo(0)).addColumn("leftOrdinal", "bigint").addPrimaryKeyConstraint("document_collections_pkey", [
331873
- "documentId",
331874
- "collectionId"
331875
- ]).execute();
331876
- await db.schema.createIndex("idx_document_collections_collectionId").on("document_collections").column("collectionId").execute();
331877
- await db.schema.createIndex("idx_doc_collections_collection_range").on("document_collections").columns(["collectionId", "joinedOrdinal"]).execute();
331878
- await db.schema.createTable("operation_index_operations").addColumn("ordinal", "serial", (col) => col.primaryKey()).addColumn("opId", "text", (col) => col.notNull()).addColumn("documentId", "text", (col) => col.notNull()).addColumn("documentType", "text", (col) => col.notNull()).addColumn("scope", "text", (col) => col.notNull()).addColumn("branch", "text", (col) => col.notNull()).addColumn("timestampUtcMs", "text", (col) => col.notNull()).addColumn("writeTimestampUtcMs", "timestamptz", (col) => col.notNull().defaultTo(sql3`NOW()`)).addColumn("index", "integer", (col) => col.notNull()).addColumn("skip", "integer", (col) => col.notNull()).addColumn("hash", "text", (col) => col.notNull()).addColumn("action", "jsonb", (col) => col.notNull()).execute();
331879
- await db.schema.createIndex("idx_operation_index_operations_document").on("operation_index_operations").columns(["documentId", "branch", "scope"]).execute();
331880
- await db.schema.createIndex("idx_operation_index_operations_ordinal").on("operation_index_operations").column("ordinal").execute();
331881
- }
331882
- async function up10(db) {
331883
- await db.schema.createTable("sync_remotes").addColumn("name", "text", (col) => col.primaryKey()).addColumn("collection_id", "text", (col) => col.notNull()).addColumn("channel_type", "text", (col) => col.notNull()).addColumn("channel_id", "text", (col) => col.notNull().defaultTo("")).addColumn("remote_name", "text", (col) => col.notNull().defaultTo("")).addColumn("channel_parameters", "jsonb", (col) => col.notNull().defaultTo(sql3`'{}'::jsonb`)).addColumn("filter_document_ids", "jsonb").addColumn("filter_scopes", "jsonb").addColumn("filter_branch", "text", (col) => col.notNull().defaultTo("main")).addColumn("push_state", "text", (col) => col.notNull().defaultTo("idle")).addColumn("push_last_success_utc_ms", "text").addColumn("push_last_failure_utc_ms", "text").addColumn("push_failure_count", "integer", (col) => col.notNull().defaultTo(0)).addColumn("pull_state", "text", (col) => col.notNull().defaultTo("idle")).addColumn("pull_last_success_utc_ms", "text").addColumn("pull_last_failure_utc_ms", "text").addColumn("pull_failure_count", "integer", (col) => col.notNull().defaultTo(0)).addColumn("created_at", "timestamptz", (col) => col.notNull().defaultTo(sql3`NOW()`)).addColumn("updated_at", "timestamptz", (col) => col.notNull().defaultTo(sql3`NOW()`)).execute();
331884
- await db.schema.createIndex("idx_sync_remotes_collection").on("sync_remotes").column("collection_id").execute();
331885
- await db.schema.createTable("sync_cursors").addColumn("remote_name", "text", (col) => col.primaryKey().references("sync_remotes.name").onDelete("cascade")).addColumn("cursor_ordinal", "bigint", (col) => col.notNull().defaultTo(0)).addColumn("last_synced_at_utc_ms", "text").addColumn("updated_at", "timestamptz", (col) => col.notNull().defaultTo(sql3`NOW()`)).execute();
331886
- await db.schema.createIndex("idx_sync_cursors_ordinal").on("sync_cursors").column("cursor_ordinal").execute();
331887
- }
331888
- async function up11(db) {
331889
- await db.deleteFrom("sync_cursors").where("remote_name", "like", "outbox::%").execute();
331890
- await db.deleteFrom("sync_remotes").where("name", "like", "outbox::%").execute();
331891
- await db.schema.dropTable("sync_cursors").execute();
331892
- await db.schema.createTable("sync_cursors").addColumn("remote_name", "text", (col) => col.notNull()).addColumn("cursor_type", "text", (col) => col.notNull().defaultTo("inbox")).addColumn("cursor_ordinal", "bigint", (col) => col.notNull().defaultTo(0)).addColumn("last_synced_at_utc_ms", "text").addColumn("updated_at", "timestamptz", (col) => col.notNull().defaultTo(sql3`NOW()`)).addPrimaryKeyConstraint("sync_cursors_pk", ["remote_name", "cursor_type"]).execute();
331893
- await db.schema.createIndex("idx_sync_cursors_ordinal").on("sync_cursors").column("cursor_ordinal").execute();
331894
- }
331895
- async function up12(db) {
331896
- await db.schema.alterTable("operation_index_operations").addColumn("sourceRemote", "text", (col) => col.notNull().defaultTo("")).execute();
331897
- }
331898
- async function up13(db) {
331899
- await db.schema.createTable("sync_dead_letters").addColumn("ordinal", "serial", (col) => col.primaryKey()).addColumn("id", "text", (col) => col.unique().notNull()).addColumn("job_id", "text", (col) => col.notNull()).addColumn("job_dependencies", "jsonb", (col) => col.notNull().defaultTo(sql3`'[]'::jsonb`)).addColumn("remote_name", "text", (col) => col.notNull().references("sync_remotes.name").onDelete("cascade")).addColumn("document_id", "text", (col) => col.notNull()).addColumn("scopes", "jsonb", (col) => col.notNull().defaultTo(sql3`'[]'::jsonb`)).addColumn("branch", "text", (col) => col.notNull()).addColumn("operations", "jsonb", (col) => col.notNull().defaultTo(sql3`'[]'::jsonb`)).addColumn("error_source", "text", (col) => col.notNull()).addColumn("error_message", "text", (col) => col.notNull()).addColumn("created_at", "timestamptz", (col) => col.notNull().defaultTo(sql3`NOW()`)).execute();
331900
- await db.schema.createIndex("idx_sync_dead_letters_remote").on("sync_dead_letters").column("remote_name").execute();
331901
- }
331902
-
331903
- class ProgrammaticMigrationProvider {
331904
- getMigrations() {
331905
- return Promise.resolve(migrations);
332216
+ function serializeAction(action) {
332217
+ const signer = action.context?.signer;
332218
+ if (!signer?.signatures) {
332219
+ return action;
331906
332220
  }
332221
+ return {
332222
+ ...action,
332223
+ context: {
332224
+ ...action.context,
332225
+ signer: {
332226
+ ...signer,
332227
+ signatures: signer.signatures.map((sig) => Array.isArray(sig) ? sig.join(", ") : sig)
332228
+ }
332229
+ }
332230
+ };
332231
+ }
332232
+ function serializeEnvelope(envelope) {
332233
+ return {
332234
+ type: envelope.type.toUpperCase(),
332235
+ channelMeta: envelope.channelMeta,
332236
+ operations: envelope.operations?.map((opWithContext) => ({
332237
+ operation: {
332238
+ index: opWithContext.operation.index,
332239
+ timestampUtcMs: opWithContext.operation.timestampUtcMs,
332240
+ hash: opWithContext.operation.hash,
332241
+ skip: opWithContext.operation.skip,
332242
+ error: opWithContext.operation.error,
332243
+ id: opWithContext.operation.id,
332244
+ action: serializeAction(opWithContext.operation.action)
332245
+ },
332246
+ context: {
332247
+ documentId: opWithContext.context.documentId,
332248
+ documentType: opWithContext.context.documentType,
332249
+ scope: opWithContext.context.scope,
332250
+ branch: opWithContext.context.branch,
332251
+ ordinal: opWithContext.context.ordinal
332252
+ }
332253
+ })),
332254
+ cursor: envelope.cursor,
332255
+ key: envelope.key,
332256
+ dependsOn: envelope.dependsOn
332257
+ };
331907
332258
  }
331908
- async function runMigrations(db, schema2 = REACTOR_SCHEMA) {
331909
- try {
331910
- await sql3`CREATE SCHEMA IF NOT EXISTS ${sql3.id(schema2)}`.execute(db);
331911
- } catch (error210) {
331912
- return {
331913
- success: false,
331914
- migrationsExecuted: [],
331915
- error: error210 instanceof Error ? error210 : new Error("Failed to create schema")
331916
- };
331917
- }
331918
- const migrator2 = new Migrator2({
331919
- db: db.withSchema(schema2),
331920
- provider: new ProgrammaticMigrationProvider,
331921
- migrationTableSchema: schema2
331922
- });
331923
- let error48;
331924
- let results;
331925
- try {
331926
- const result = await migrator2.migrateToLatest();
331927
- error48 = result.error;
331928
- results = result.results;
331929
- } catch (e) {
331930
- error48 = e;
331931
- results = [];
332259
+ function deserializeSignature(sig) {
332260
+ if (Array.isArray(sig)) {
332261
+ return sig;
331932
332262
  }
331933
- const migrationsExecuted = results?.map((result) => result.migrationName) ?? [];
331934
- if (error48) {
331935
- return {
331936
- success: false,
331937
- migrationsExecuted,
331938
- error: error48 instanceof Error ? error48 : new Error("Unknown migration error")
331939
- };
332263
+ return sig.split(", ");
332264
+ }
332265
+ function deserializeOperationSignatures(opWithContext) {
332266
+ const signer = opWithContext.operation.action.context?.signer;
332267
+ if (!signer?.signatures || signer.signatures.length === 0) {
332268
+ return opWithContext;
331940
332269
  }
332270
+ const deserializedSignatures = signer.signatures.map(deserializeSignature);
332271
+ const deserializedOperation = {
332272
+ ...opWithContext.operation,
332273
+ action: {
332274
+ ...opWithContext.operation.action,
332275
+ context: {
332276
+ ...opWithContext.operation.action.context,
332277
+ signer: {
332278
+ ...signer,
332279
+ signatures: deserializedSignatures
332280
+ }
332281
+ }
332282
+ }
332283
+ };
331941
332284
  return {
331942
- success: true,
331943
- migrationsExecuted
332285
+ ...opWithContext,
332286
+ operation: deserializedOperation
331944
332287
  };
331945
332288
  }
331946
- async function getMigrationStatus(db, schema2 = REACTOR_SCHEMA) {
331947
- const migrator2 = new Migrator2({
331948
- db: db.withSchema(schema2),
331949
- provider: new ProgrammaticMigrationProvider,
331950
- migrationTableSchema: schema2
332289
+ function envelopesToSyncOperations(envelope, remoteName) {
332290
+ if (!envelope.operations || envelope.operations.length === 0) {
332291
+ return [];
332292
+ }
332293
+ const deserializedOps = envelope.operations.map(deserializeOperationSignatures);
332294
+ const batches = batchOperationsByDocument(deserializedOps);
332295
+ return batches.map((batch) => {
332296
+ const syncOpId = `syncop-${envelope.channelMeta.id}-${Date.now()}-${syncOpCounter++}`;
332297
+ return new SyncOperation(syncOpId, envelope.key ?? "", (envelope.dependsOn ?? []).filter(Boolean), remoteName, batch.documentId, [batch.scope], batch.branch, batch.operations);
331951
332298
  });
331952
- return await migrator2.getMigrations();
331953
332299
  }
331954
332300
 
331955
- class ReactorSubscriptionManager {
331956
- createdSubscriptions = new Map;
331957
- deletedSubscriptions = new Map;
331958
- updatedSubscriptions = new Map;
331959
- relationshipSubscriptions = new Map;
331960
- subscriptionCounter = 0;
331961
- errorHandler;
331962
- constructor(errorHandler) {
331963
- this.errorHandler = errorHandler;
331964
- }
331965
- onDocumentCreated(callback, search) {
331966
- const id = `created-${++this.subscriptionCounter}`;
331967
- this.createdSubscriptions.set(id, { id, callback, search });
331968
- return () => {
331969
- this.createdSubscriptions.delete(id);
332301
+ class GqlRequestChannel {
332302
+ logger;
332303
+ inbox;
332304
+ outbox;
332305
+ deadLetter;
332306
+ config;
332307
+ bufferedOutbox;
332308
+ channelId;
332309
+ remoteName;
332310
+ cursorStorage;
332311
+ operationIndex;
332312
+ pollTimer;
332313
+ isShutdown;
332314
+ failureCount;
332315
+ lastSuccessUtcMs;
332316
+ lastFailureUtcMs;
332317
+ lastPersistedInboxOrdinal = 0;
332318
+ lastPersistedOutboxOrdinal = 0;
332319
+ pushFailureCount = 0;
332320
+ pushRetryTimer = null;
332321
+ pushBlocked = false;
332322
+ connectionState = "connecting";
332323
+ connectionStateCallbacks = new Set;
332324
+ constructor(logger3, channelId, remoteName, cursorStorage, config2, operationIndex, pollTimer) {
332325
+ this.logger = logger3;
332326
+ this.channelId = channelId;
332327
+ this.remoteName = remoteName;
332328
+ this.cursorStorage = cursorStorage;
332329
+ this.operationIndex = operationIndex;
332330
+ this.pollTimer = pollTimer;
332331
+ this.config = {
332332
+ url: config2.url,
332333
+ jwtHandler: config2.jwtHandler,
332334
+ fetchFn: config2.fetchFn,
332335
+ collectionId: config2.collectionId,
332336
+ filter: config2.filter,
332337
+ retryBaseDelayMs: config2.retryBaseDelayMs,
332338
+ retryMaxDelayMs: config2.retryMaxDelayMs
331970
332339
  };
332340
+ this.isShutdown = false;
332341
+ this.failureCount = 0;
332342
+ this.inbox = new Mailbox;
332343
+ this.bufferedOutbox = new BufferedMailbox(500, 25);
332344
+ this.outbox = this.bufferedOutbox;
332345
+ this.deadLetter = new Mailbox;
332346
+ this.deadLetter.onAdded(() => {
332347
+ this.pollTimer.stop();
332348
+ if (this.pushRetryTimer) {
332349
+ clearTimeout(this.pushRetryTimer);
332350
+ this.pushRetryTimer = null;
332351
+ }
332352
+ this.transitionConnectionState("error");
332353
+ });
332354
+ this.outbox.onAdded((syncOps) => {
332355
+ if (this.isShutdown)
332356
+ return;
332357
+ if (this.pushBlocked)
332358
+ return;
332359
+ if (this.deadLetter.items.length > 0)
332360
+ return;
332361
+ this.attemptPush(syncOps);
332362
+ });
332363
+ this.outbox.onRemoved((syncOps) => {
332364
+ const maxOrdinal = getLatestAppliedOrdinal(syncOps);
332365
+ if (maxOrdinal > this.lastPersistedOutboxOrdinal) {
332366
+ this.lastPersistedOutboxOrdinal = maxOrdinal;
332367
+ this.cursorStorage.upsert({
332368
+ remoteName: this.remoteName,
332369
+ cursorType: "outbox",
332370
+ cursorOrdinal: maxOrdinal,
332371
+ lastSyncedAtUtcMs: Date.now()
332372
+ }).catch((error48) => {
332373
+ this.logger.error("Failed to update outbox cursor for @ChannelId! This means that future application runs may resend duplicate operations. This is recoverable (with deduplication protection), but not-optimal: @Error", this.channelId, error48);
332374
+ });
332375
+ }
332376
+ });
332377
+ this.inbox.onRemoved((syncOps) => {
332378
+ const maxOrdinal = getLatestAppliedOrdinal(syncOps);
332379
+ if (maxOrdinal > this.lastPersistedInboxOrdinal) {
332380
+ this.lastPersistedInboxOrdinal = maxOrdinal;
332381
+ this.cursorStorage.upsert({
332382
+ remoteName: this.remoteName,
332383
+ cursorType: "inbox",
332384
+ cursorOrdinal: maxOrdinal,
332385
+ lastSyncedAtUtcMs: Date.now()
332386
+ }).catch((error48) => {
332387
+ this.logger.error("Failed to update inbox cursor for @ChannelId! This is unlikely to cause a problem, but not-optimal: @Error", this.channelId, error48);
332388
+ });
332389
+ }
332390
+ });
331971
332391
  }
331972
- onDocumentDeleted(callback, search) {
331973
- const id = `deleted-${++this.subscriptionCounter}`;
331974
- this.deletedSubscriptions.set(id, { id, callback, search });
331975
- return () => {
331976
- this.deletedSubscriptions.delete(id);
331977
- };
332392
+ shutdown() {
332393
+ this.bufferedOutbox.flush();
332394
+ this.isShutdown = true;
332395
+ this.pollTimer.stop();
332396
+ if (this.pushRetryTimer) {
332397
+ clearTimeout(this.pushRetryTimer);
332398
+ this.pushRetryTimer = null;
332399
+ }
332400
+ this.transitionConnectionState("disconnected");
332401
+ return Promise.resolve();
331978
332402
  }
331979
- onDocumentStateUpdated(callback, search, view) {
331980
- const id = `updated-${++this.subscriptionCounter}`;
331981
- this.updatedSubscriptions.set(id, { id, callback, search, view });
331982
- return () => {
331983
- this.updatedSubscriptions.delete(id);
332403
+ getConnectionState() {
332404
+ return {
332405
+ state: this.connectionState,
332406
+ failureCount: this.failureCount,
332407
+ lastSuccessUtcMs: this.lastSuccessUtcMs ?? 0,
332408
+ lastFailureUtcMs: this.lastFailureUtcMs ?? 0,
332409
+ pushBlocked: this.pushBlocked,
332410
+ pushFailureCount: this.pushFailureCount
331984
332411
  };
331985
332412
  }
331986
- onRelationshipChanged(callback, search) {
331987
- const id = `relationship-${++this.subscriptionCounter}`;
331988
- this.relationshipSubscriptions.set(id, { id, callback, search });
332413
+ onConnectionStateChange(callback) {
332414
+ this.connectionStateCallbacks.add(callback);
331989
332415
  return () => {
331990
- this.relationshipSubscriptions.delete(id);
332416
+ this.connectionStateCallbacks.delete(callback);
331991
332417
  };
331992
332418
  }
331993
- notifyDocumentsCreated(documentIds, documentTypes, parentIds) {
331994
- const result = {
331995
- results: documentIds,
331996
- options: { cursor: "", limit: documentIds.length }
331997
- };
331998
- for (const subscription of this.createdSubscriptions.values()) {
331999
- const filteredIds = this.filterDocumentIds(documentIds, subscription.search, documentTypes, parentIds);
332000
- if (filteredIds.length > 0) {
332001
- try {
332002
- subscription.callback({
332003
- ...result,
332004
- results: filteredIds
332005
- });
332006
- } catch (error48) {
332007
- this.errorHandler.handleError(error48, {
332008
- eventType: "created",
332009
- subscriptionId: subscription.id,
332010
- eventData: filteredIds
332011
- });
332012
- }
332419
+ async init() {
332420
+ await this.touchRemoteChannel();
332421
+ const cursors = await this.cursorStorage.list(this.remoteName);
332422
+ const inboxOrdinal = cursors.find((c2) => c2.cursorType === "inbox")?.cursorOrdinal ?? 0;
332423
+ const outboxOrdinal = cursors.find((c2) => c2.cursorType === "outbox")?.cursorOrdinal ?? 0;
332424
+ this.inbox.init(inboxOrdinal);
332425
+ this.outbox.init(outboxOrdinal);
332426
+ this.lastPersistedInboxOrdinal = inboxOrdinal;
332427
+ this.lastPersistedOutboxOrdinal = outboxOrdinal;
332428
+ this.pollTimer.setDelegate(() => this.poll());
332429
+ this.pollTimer.start();
332430
+ this.transitionConnectionState("connected");
332431
+ }
332432
+ transitionConnectionState(next) {
332433
+ if (this.connectionState === next)
332434
+ return;
332435
+ this.connectionState = next;
332436
+ const snapshot = this.getConnectionState();
332437
+ for (const callback of this.connectionStateCallbacks) {
332438
+ try {
332439
+ callback(snapshot);
332440
+ } catch (error48) {
332441
+ this.logger.error("Connection state change callback error: @Error", error48);
332013
332442
  }
332014
332443
  }
332015
332444
  }
332016
- notifyDocumentsDeleted(documentIds, documentTypes, parentIds) {
332017
- for (const subscription of this.deletedSubscriptions.values()) {
332018
- const filteredIds = this.filterDocumentIds(documentIds, subscription.search, documentTypes, parentIds);
332019
- if (filteredIds.length > 0) {
332020
- try {
332021
- subscription.callback(filteredIds);
332022
- } catch (error48) {
332023
- this.errorHandler.handleError(error48, {
332024
- eventType: "deleted",
332025
- subscriptionId: subscription.id,
332026
- eventData: filteredIds
332027
- });
332445
+ async poll() {
332446
+ if (this.isShutdown) {
332447
+ return;
332448
+ }
332449
+ let response;
332450
+ try {
332451
+ response = await this.pollSyncEnvelopes(this.inbox.ackOrdinal, this.inbox.latestOrdinal);
332452
+ } catch (error48) {
332453
+ if (!this.handlePollError(error48)) {
332454
+ throw error48;
332455
+ }
332456
+ return;
332457
+ }
332458
+ const { envelopes, ackOrdinal, deadLetters } = response;
332459
+ if (ackOrdinal > 0) {
332460
+ trimMailboxFromAckOrdinal(this.outbox, ackOrdinal);
332461
+ }
332462
+ const sortedEnvelopes = sortEnvelopesByFirstOperationTimestamp(envelopes);
332463
+ const allSyncOps = [];
332464
+ for (const envelope of sortedEnvelopes) {
332465
+ if (envelope.type.toLowerCase() === "operations" && envelope.operations) {
332466
+ const syncOps = envelopesToSyncOperations(envelope, this.remoteName);
332467
+ for (const syncOp of syncOps) {
332468
+ syncOp.transported();
332028
332469
  }
332470
+ allSyncOps.push(...syncOps);
332029
332471
  }
332030
332472
  }
332473
+ const consolidated = allSyncOps.length > 1 ? consolidateSyncOperations(allSyncOps) : allSyncOps;
332474
+ if (consolidated.length > 0) {
332475
+ this.inbox.add(...consolidated);
332476
+ }
332477
+ if (deadLetters.length > 0) {
332478
+ this.handleRemoteDeadLetters(deadLetters);
332479
+ }
332480
+ this.lastSuccessUtcMs = Date.now();
332481
+ this.failureCount = 0;
332482
+ this.transitionConnectionState("connected");
332031
332483
  }
332032
- notifyDocumentsUpdated(documents2) {
332033
- const result = {
332034
- results: documents2,
332035
- options: { cursor: "", limit: documents2.length }
332484
+ handleRemoteDeadLetters(deadLetters) {
332485
+ for (const dl of deadLetters) {
332486
+ this.logger.error("Remote dead letter on @ChannelId: document @DocumentId failed with: @Error", this.channelId, dl.documentId, dl.error);
332487
+ }
332488
+ const syncOps = [];
332489
+ for (const dl of deadLetters) {
332490
+ const syncOp = new SyncOperation(crypto.randomUUID(), dl.jobId, [], this.remoteName, dl.documentId, dl.scopes, dl.branch, []);
332491
+ syncOp.failed(new ChannelError("outbox", new Error(dl.error)));
332492
+ syncOps.push(syncOp);
332493
+ }
332494
+ this.deadLetter.add(...syncOps);
332495
+ }
332496
+ handlePollError(error48) {
332497
+ const err2 = error48 instanceof Error ? error48 : new Error(String(error48));
332498
+ if (err2.message.includes("Channel not found")) {
332499
+ this.transitionConnectionState("reconnecting");
332500
+ this.recoverFromChannelNotFound();
332501
+ return true;
332502
+ }
332503
+ this.failureCount++;
332504
+ this.lastFailureUtcMs = Date.now();
332505
+ this.transitionConnectionState("error");
332506
+ const channelError = new ChannelError("inbox", err2);
332507
+ this.logger.error("GqlChannel poll error (@FailureCount): @Error", this.failureCount, channelError);
332508
+ return false;
332509
+ }
332510
+ recoverFromChannelNotFound() {
332511
+ this.logger.info("GqlChannel @ChannelId not found on remote, re-registering...", this.channelId);
332512
+ this.pollTimer.stop();
332513
+ this.touchRemoteChannel().then(() => {
332514
+ this.logger.info("GqlChannel @ChannelId re-registered successfully", this.channelId);
332515
+ this.failureCount = 0;
332516
+ this.pollTimer.start();
332517
+ this.transitionConnectionState("connected");
332518
+ }).catch((recoveryError) => {
332519
+ this.logger.error("GqlChannel @ChannelId failed to re-register: @Error", this.channelId, recoveryError);
332520
+ this.failureCount++;
332521
+ this.lastFailureUtcMs = Date.now();
332522
+ this.pollTimer.start();
332523
+ this.transitionConnectionState("error");
332524
+ });
332525
+ }
332526
+ async pollSyncEnvelopes(ackOrdinal, latestOrdinal) {
332527
+ const query = `
332528
+ query PollSyncEnvelopes($channelId: String!, $outboxAck: Int!, $outboxLatest: Int!) {
332529
+ pollSyncEnvelopes(channelId: $channelId, outboxAck: $outboxAck, outboxLatest: $outboxLatest) {
332530
+ envelopes {
332531
+ type
332532
+ channelMeta {
332533
+ id
332534
+ }
332535
+ operations {
332536
+ operation {
332537
+ index
332538
+ timestampUtcMs
332539
+ hash
332540
+ skip
332541
+ error
332542
+ id
332543
+ action {
332544
+ id
332545
+ type
332546
+ timestampUtcMs
332547
+ input
332548
+ scope
332549
+ attachments {
332550
+ data
332551
+ mimeType
332552
+ hash
332553
+ extension
332554
+ fileName
332555
+ }
332556
+ context {
332557
+ signer {
332558
+ user {
332559
+ address
332560
+ networkId
332561
+ chainId
332562
+ }
332563
+ app {
332564
+ name
332565
+ key
332566
+ }
332567
+ signatures
332568
+ }
332569
+ }
332570
+ }
332571
+ }
332572
+ context {
332573
+ documentId
332574
+ documentType
332575
+ scope
332576
+ branch
332577
+ ordinal
332578
+ }
332579
+ }
332580
+ cursor {
332581
+ remoteName
332582
+ cursorOrdinal
332583
+ lastSyncedAtUtcMs
332584
+ }
332585
+ key
332586
+ dependsOn
332587
+ }
332588
+ ackOrdinal
332589
+ deadLetters {
332590
+ documentId
332591
+ error
332592
+ jobId
332593
+ branch
332594
+ scopes
332595
+ operationCount
332596
+ }
332597
+ }
332598
+ }
332599
+ `;
332600
+ const variables = {
332601
+ channelId: this.channelId,
332602
+ outboxAck: ackOrdinal,
332603
+ outboxLatest: latestOrdinal
332036
332604
  };
332037
- for (const subscription of this.updatedSubscriptions.values()) {
332038
- const filteredDocs = this.filterDocuments(documents2, subscription.search);
332039
- if (filteredDocs.length > 0) {
332040
- try {
332041
- subscription.callback({
332042
- ...result,
332043
- results: filteredDocs
332044
- });
332045
- } catch (error48) {
332046
- this.errorHandler.handleError(error48, {
332047
- eventType: "updated",
332048
- subscriptionId: subscription.id,
332049
- eventData: filteredDocs
332050
- });
332605
+ const response = await this.executeGraphQL(query, variables);
332606
+ return {
332607
+ envelopes: response.pollSyncEnvelopes.envelopes,
332608
+ ackOrdinal: response.pollSyncEnvelopes.ackOrdinal,
332609
+ deadLetters: response.pollSyncEnvelopes.deadLetters ?? []
332610
+ };
332611
+ }
332612
+ async touchRemoteChannel() {
332613
+ let sinceTimestampUtcMs = "0";
332614
+ try {
332615
+ const result = await this.operationIndex.getLatestTimestampForCollection(this.config.collectionId);
332616
+ if (result) {
332617
+ sinceTimestampUtcMs = result;
332618
+ }
332619
+ } catch {}
332620
+ const mutation = `
332621
+ mutation TouchChannel($input: TouchChannelInput!) {
332622
+ touchChannel(input: $input)
332623
+ }
332624
+ `;
332625
+ const variables = {
332626
+ input: {
332627
+ id: this.channelId,
332628
+ name: this.channelId,
332629
+ collectionId: this.config.collectionId,
332630
+ filter: {
332631
+ documentId: this.config.filter.documentId,
332632
+ scope: this.config.filter.scope,
332633
+ branch: this.config.filter.branch
332634
+ },
332635
+ sinceTimestampUtcMs
332636
+ }
332637
+ };
332638
+ await this.executeGraphQL(mutation, variables);
332639
+ }
332640
+ attemptPush(syncOps) {
332641
+ this.pushSyncOperations(syncOps).then(() => {
332642
+ this.pushBlocked = false;
332643
+ this.pushFailureCount = 0;
332644
+ if (this.connectionState === "reconnecting" || this.connectionState === "error") {
332645
+ this.transitionConnectionState("connected");
332646
+ }
332647
+ }).catch((error48) => {
332648
+ const err2 = error48 instanceof Error ? error48 : new Error(String(error48));
332649
+ if (this.isRecoverablePushError(err2)) {
332650
+ this.pushFailureCount++;
332651
+ this.pushBlocked = true;
332652
+ this.logger.error("GqlChannel push failed (attempt @FailureCount), will retry: @Error", this.pushFailureCount, err2);
332653
+ this.transitionConnectionState("reconnecting");
332654
+ this.schedulePushRetry();
332655
+ } else {
332656
+ const channelError = new ChannelError("outbox", err2);
332657
+ for (const syncOp of syncOps) {
332658
+ syncOp.failed(channelError);
332659
+ }
332660
+ this.deadLetter.add(...syncOps);
332661
+ this.outbox.remove(...syncOps);
332662
+ this.transitionConnectionState("error");
332663
+ }
332664
+ });
332665
+ }
332666
+ schedulePushRetry() {
332667
+ if (this.pushRetryTimer)
332668
+ return;
332669
+ const delay = calculateBackoffDelay(this.pushFailureCount, this.config.retryBaseDelayMs, this.config.retryMaxDelayMs, Math.random());
332670
+ this.pushRetryTimer = setTimeout(() => {
332671
+ this.pushRetryTimer = null;
332672
+ if (this.isShutdown)
332673
+ return;
332674
+ const allItems = this.outbox.items;
332675
+ if (allItems.length === 0) {
332676
+ this.pushBlocked = false;
332677
+ this.pushFailureCount = 0;
332678
+ return;
332679
+ }
332680
+ this.attemptPush([...allItems]);
332681
+ }, delay);
332682
+ }
332683
+ isRecoverablePushError(error48) {
332684
+ if (error48.message.startsWith("GraphQL errors:"))
332685
+ return false;
332686
+ if (error48.message === "GraphQL response missing data field")
332687
+ return false;
332688
+ return true;
332689
+ }
332690
+ async pushSyncOperations(syncOps) {
332691
+ for (const syncOp of syncOps) {
332692
+ syncOp.started();
332693
+ }
332694
+ const jobIdToKeys = new Map;
332695
+ const envelopes = [];
332696
+ for (let i3 = 0;i3 < syncOps.length; i3++) {
332697
+ const syncOp = syncOps[i3];
332698
+ const key = String(i3);
332699
+ if (syncOp.jobId) {
332700
+ if (!jobIdToKeys.has(syncOp.jobId)) {
332701
+ jobIdToKeys.set(syncOp.jobId, []);
332702
+ }
332703
+ jobIdToKeys.get(syncOp.jobId).push(key);
332704
+ }
332705
+ const dependsOn = [];
332706
+ for (const dep of syncOp.jobDependencies) {
332707
+ const depKeys = jobIdToKeys.get(dep);
332708
+ if (depKeys) {
332709
+ dependsOn.push(...depKeys);
332051
332710
  }
332052
332711
  }
332712
+ this.logger.debug("[PUSH]: @Operations", syncOp.operations.map((op) => `(${op.context.documentId}, ${op.context.branch}, ${op.context.scope}, ${op.operation.index})`));
332713
+ envelopes.push({
332714
+ type: "operations",
332715
+ channelMeta: { id: this.channelId },
332716
+ operations: syncOp.operations,
332717
+ key,
332718
+ dependsOn
332719
+ });
332720
+ }
332721
+ const mutation = `
332722
+ mutation PushSyncEnvelopes($envelopes: [SyncEnvelopeInput!]!) {
332723
+ pushSyncEnvelopes(envelopes: $envelopes)
332724
+ }
332725
+ `;
332726
+ const variables = {
332727
+ envelopes: envelopes.map((e) => serializeEnvelope(e))
332728
+ };
332729
+ await this.executeGraphQL(mutation, variables);
332730
+ }
332731
+ async getAuthorizationHeader() {
332732
+ if (!this.config.jwtHandler) {
332733
+ return;
332734
+ }
332735
+ try {
332736
+ const token = await this.config.jwtHandler(this.config.url);
332737
+ if (token) {
332738
+ return `Bearer ${token}`;
332739
+ }
332740
+ } catch (error48) {
332741
+ this.logger.error("JWT handler failed: @Error", error48);
332053
332742
  }
332743
+ return;
332054
332744
  }
332055
- notifyRelationshipChanged(parentId, childId, changeType, childType) {
332056
- for (const subscription of this.relationshipSubscriptions.values()) {
332057
- if (this.matchesRelationshipFilter(parentId, childId, childType, subscription.search)) {
332058
- try {
332059
- subscription.callback(parentId, childId, changeType);
332060
- } catch (error48) {
332061
- this.errorHandler.handleError(error48, {
332062
- eventType: "relationshipChanged",
332063
- subscriptionId: subscription.id,
332064
- eventData: { parentId, childId, changeType }
332065
- });
332066
- }
332067
- }
332745
+ async executeGraphQL(query, variables) {
332746
+ const headers = {
332747
+ "Content-Type": "application/json"
332748
+ };
332749
+ const authHeader = await this.getAuthorizationHeader();
332750
+ if (authHeader) {
332751
+ headers["Authorization"] = authHeader;
332752
+ }
332753
+ const operationMatch = query.match(/(?:query|mutation)\s+(\w+)/);
332754
+ const operationName = operationMatch?.[1] ?? "unknown";
332755
+ this.logger.verbose("GQL request @channelId @operation @url vars=@variables", this.channelId, operationName, this.config.url, JSON.stringify(variables));
332756
+ const fetchFn = this.config.fetchFn ?? fetch;
332757
+ let response;
332758
+ try {
332759
+ response = await fetchFn(this.config.url, {
332760
+ method: "POST",
332761
+ headers,
332762
+ body: JSON.stringify({
332763
+ query,
332764
+ variables
332765
+ })
332766
+ });
332767
+ } catch (error48) {
332768
+ throw new Error(`GraphQL request failed: ${error48 instanceof Error ? error48.message : String(error48)}`);
332769
+ }
332770
+ if (!response.ok) {
332771
+ throw new Error(`GraphQL request failed: ${response.status} ${response.statusText}`);
332772
+ }
332773
+ let result;
332774
+ try {
332775
+ result = await response.json();
332776
+ } catch (error48) {
332777
+ throw new Error(`Failed to parse GraphQL response: ${error48 instanceof Error ? error48.message : String(error48)}`);
332778
+ }
332779
+ this.logger.verbose("GQL response @channelId @operation status=@status data=@data errors=@errors", this.channelId, operationName, response.status, JSON.stringify(result.data), result.errors ? JSON.stringify(result.errors) : "none");
332780
+ if (result.errors) {
332781
+ throw new Error(`GraphQL errors: ${JSON.stringify(result.errors, null, 2)}`);
332782
+ }
332783
+ if (!result.data) {
332784
+ throw new Error("GraphQL response missing data field");
332068
332785
  }
332786
+ return result.data;
332069
332787
  }
332070
- clearAll() {
332071
- this.createdSubscriptions.clear();
332072
- this.deletedSubscriptions.clear();
332073
- this.updatedSubscriptions.clear();
332074
- this.relationshipSubscriptions.clear();
332788
+ get poller() {
332789
+ return this.pollTimer;
332075
332790
  }
332076
- filterDocumentIds(documentIds, search, documentTypes, parentIds) {
332077
- if (!search)
332078
- return documentIds;
332079
- return documentIds.filter((id) => {
332080
- if (search.ids && !search.ids.includes(id))
332081
- return false;
332082
- if (search.type && documentTypes) {
332083
- const docType = documentTypes.get(id);
332084
- if (docType !== search.type)
332085
- return false;
332791
+ }
332792
+
332793
+ class GqlResponseChannel {
332794
+ logger;
332795
+ inbox;
332796
+ outbox;
332797
+ deadLetter;
332798
+ channelId;
332799
+ remoteName;
332800
+ cursorStorage;
332801
+ isShutdown;
332802
+ lastPersistedInboxOrdinal = 0;
332803
+ lastPersistedOutboxOrdinal = 0;
332804
+ connectionState = "connecting";
332805
+ connectionStateCallbacks = new Set;
332806
+ constructor(logger3, channelId, remoteName, cursorStorage) {
332807
+ this.logger = logger3;
332808
+ this.channelId = channelId;
332809
+ this.remoteName = remoteName;
332810
+ this.cursorStorage = cursorStorage;
332811
+ this.isShutdown = false;
332812
+ this.inbox = new Mailbox;
332813
+ this.outbox = new Mailbox;
332814
+ this.deadLetter = new Mailbox;
332815
+ this.outbox.onRemoved((syncOps) => {
332816
+ const maxOrdinal = getLatestAppliedOrdinal(syncOps);
332817
+ if (maxOrdinal > this.lastPersistedOutboxOrdinal) {
332818
+ this.lastPersistedOutboxOrdinal = maxOrdinal;
332819
+ this.cursorStorage.upsert({
332820
+ remoteName: this.remoteName,
332821
+ cursorType: "outbox",
332822
+ cursorOrdinal: maxOrdinal,
332823
+ lastSyncedAtUtcMs: Date.now()
332824
+ }).catch((error48) => {
332825
+ this.logger.error("Failed to update outbox cursor for @ChannelId! This means that future application runs may resend duplicate operations. This is recoverable (with deduplication protection), but not-optimal: @Error", this.channelId, error48);
332826
+ });
332086
332827
  }
332087
- if (search.parentId && parentIds) {
332088
- const parentId = parentIds.get(id);
332089
- if (parentId !== search.parentId)
332090
- return false;
332828
+ });
332829
+ this.inbox.onRemoved((syncOps) => {
332830
+ const maxOrdinal = getLatestAppliedOrdinal(syncOps);
332831
+ if (maxOrdinal > this.lastPersistedInboxOrdinal) {
332832
+ this.lastPersistedInboxOrdinal = maxOrdinal;
332833
+ this.cursorStorage.upsert({
332834
+ remoteName: this.remoteName,
332835
+ cursorType: "inbox",
332836
+ cursorOrdinal: maxOrdinal,
332837
+ lastSyncedAtUtcMs: Date.now()
332838
+ }).catch((error48) => {
332839
+ this.logger.error("Failed to update inbox cursor for @ChannelId! This is unlikely to cause a problem, but not-optimal: @Error", this.channelId, error48);
332840
+ });
332091
332841
  }
332092
- return true;
332093
332842
  });
332094
332843
  }
332095
- filterDocuments(documents2, search) {
332096
- if (!search)
332097
- return documents2;
332098
- return documents2.filter((doc2) => {
332099
- if (search.ids && !search.ids.includes(doc2.header.id))
332100
- return false;
332101
- if (search.type && doc2.header.documentType !== search.type)
332102
- return false;
332103
- if (search.slugs && !search.slugs.includes(doc2.header.slug))
332104
- return false;
332105
- return true;
332106
- });
332844
+ shutdown() {
332845
+ this.isShutdown = true;
332846
+ this.transitionConnectionState("disconnected");
332847
+ return Promise.resolve();
332107
332848
  }
332108
- matchesRelationshipFilter(parentId, childId, childType, search) {
332109
- if (!search)
332110
- return true;
332111
- if (search.parentId && parentId !== search.parentId)
332112
- return false;
332113
- if (search.ids && !search.ids.includes(childId))
332114
- return false;
332115
- if (search.type && childType && childType !== search.type)
332116
- return false;
332117
- return true;
332849
+ getConnectionState() {
332850
+ return {
332851
+ state: this.connectionState,
332852
+ failureCount: 0,
332853
+ lastSuccessUtcMs: 0,
332854
+ lastFailureUtcMs: 0,
332855
+ pushBlocked: false,
332856
+ pushFailureCount: 0
332857
+ };
332858
+ }
332859
+ onConnectionStateChange(callback) {
332860
+ this.connectionStateCallbacks.add(callback);
332861
+ return () => {
332862
+ this.connectionStateCallbacks.delete(callback);
332863
+ };
332864
+ }
332865
+ transitionConnectionState(next) {
332866
+ if (this.connectionState === next)
332867
+ return;
332868
+ this.connectionState = next;
332869
+ const snapshot = this.getConnectionState();
332870
+ for (const callback of this.connectionStateCallbacks) {
332871
+ try {
332872
+ callback(snapshot);
332873
+ } catch (error48) {
332874
+ this.logger.error("Connection state change callback error: @Error", error48);
332875
+ }
332876
+ }
332877
+ }
332878
+ async init() {
332879
+ const cursors = await this.cursorStorage.list(this.remoteName);
332880
+ const inboxOrdinal = cursors.find((c2) => c2.cursorType === "inbox")?.cursorOrdinal ?? 0;
332881
+ const outboxOrdinal = cursors.find((c2) => c2.cursorType === "outbox")?.cursorOrdinal ?? 0;
332882
+ this.inbox.init(inboxOrdinal);
332883
+ this.outbox.init(outboxOrdinal);
332884
+ this.lastPersistedInboxOrdinal = inboxOrdinal;
332885
+ this.lastPersistedOutboxOrdinal = outboxOrdinal;
332886
+ this.transitionConnectionState("connected");
332118
332887
  }
332119
332888
  }
332120
332889
  function isTerminalStatus(status) {
@@ -332569,6 +333338,7 @@ class SyncManager {
332569
333338
  batchAggregator;
332570
333339
  syncStatusTracker;
332571
333340
  maxDeadLettersPerRemote;
333341
+ connectionStateUnsubscribes = new Map;
332572
333342
  loadJobs = new Map;
332573
333343
  constructor(logger3, remoteStorage, cursorStorage, deadLetterStorage, channelFactory, operationIndex, reactor, eventBus, maxDeadLettersPerRemote = 100) {
332574
333344
  this.logger = logger3;
@@ -332634,6 +333404,10 @@ class SyncManager {
332634
333404
  this.awaiter.shutdown();
332635
333405
  this.syncAwaiter.shutdown();
332636
333406
  this.syncStatusTracker.clear();
333407
+ for (const unsub of this.connectionStateUnsubscribes.values()) {
333408
+ unsub();
333409
+ }
333410
+ this.connectionStateUnsubscribes.clear();
332637
333411
  const promises = [];
332638
333412
  for (const remote of this.remotes.values()) {
332639
333413
  promises.push(remote.channel.shutdown());
@@ -332715,6 +333489,11 @@ class SyncManager {
332715
333489
  await this.remoteStorage.remove(name4);
332716
333490
  await this.cursorStorage.remove(name4);
332717
333491
  this.syncStatusTracker.untrackRemote(name4);
333492
+ const unsub = this.connectionStateUnsubscribes.get(name4);
333493
+ if (unsub) {
333494
+ unsub();
333495
+ this.connectionStateUnsubscribes.delete(name4);
333496
+ }
332718
333497
  this.remotes.delete(name4);
332719
333498
  }
332720
333499
  list() {
@@ -332732,6 +333511,16 @@ class SyncManager {
332732
333511
  wireChannelCallbacks(remote) {
332733
333512
  remote.channel.inbox.onAdded((syncOps) => this.handleInboxAdded(remote, syncOps));
332734
333513
  this.syncStatusTracker.trackRemote(remote.name, remote.channel);
333514
+ const unsubscribe = remote.channel.onConnectionStateChange((snapshot) => {
333515
+ this.eventBus.emit(SyncEventTypes.CONNECTION_STATE_CHANGED, {
333516
+ remoteName: remote.name,
333517
+ remoteId: remote.id,
333518
+ previous: snapshot.state,
333519
+ current: snapshot.state,
333520
+ snapshot
333521
+ }).catch(() => {});
333522
+ });
333523
+ this.connectionStateUnsubscribes.set(remote.name, unsubscribe);
332735
333524
  remote.channel.deadLetter.onAdded((syncOps) => {
332736
333525
  for (const syncOp of syncOps) {
332737
333526
  this.logger.error("Dead letter (@remote, @documentId, @jobId, @error, @dependencies)", remote.name, syncOp.documentId, syncOp.jobId, syncOp.error?.message ?? "unknown", syncOp.jobDependencies);
@@ -332975,7 +333764,17 @@ var __defProp2, __export2 = (target, all) => {
332975
333764
  configurable: true,
332976
333765
  set: (newValue) => all[name4] = () => newValue
332977
333766
  });
332978
- }, byteToHex, rnds8, randomUUID, PropagationMode, RelationshipChangeType, JobStatus, DocumentChangeType, AlterTableNode2, IdentifierNode2, CreateIndexNode2, CreateSchemaNode2, ON_COMMIT_ACTIONS2, CreateTableNode2, SchemableIdentifierNode2, DropIndexNode2, DropSchemaNode2, DropTableNode2, AliasNode2, TableNode2, SelectModifierNode2, AndNode2, OrNode2, OnNode2, JoinNode2, BinaryOperationNode2, COMPARISON_OPERATORS2, ARITHMETIC_OPERATORS2, JSON_OPERATORS2, BINARY_OPERATORS2, UNARY_FILTER_OPERATORS2, UNARY_OPERATORS2, OPERATORS2, OperatorNode2, ColumnNode2, SelectAllNode2, ReferenceNode2, OrderByItemNode2, RawNode2, CollateNode2, LOGGED_MESSAGES2, JSONReferenceNode2, JSONOperatorChainNode2, JSONPathNode2, PrimitiveValueListNode2, ValueListNode2, ValueNode2, ParensNode2, OrderByNode2, PartitionByNode2, OverNode2, FromNode2, GroupByNode2, HavingNode2, InsertQueryNode2, ListNode2, UpdateQueryNode2, UsingNode2, DeleteQueryNode2, WhereNode2, ReturningNode2, ExplainNode2, WhenNode2, MergeQueryNode2, OutputNode2, QueryNode2, SelectQueryNode2, PartitionByItemNode2, SelectionNode2, ValuesNode2, DefaultInsertValueNode2, ColumnUpdateNode2, OnDuplicateKeyNode2, NoResultError2, OnConflictNode2, TopNode2, OrActionNode2, LimitNode2, CommonTableExpressionNameNode2, CommonTableExpressionNode2, WithNode2, CHARS2, ROOT_OPERATION_NODES2, SCHEMALESS_FUNCTIONS2, WithSchemaTransformer2, MatchedNode2, NO_PLUGINS2, NoopQueryExecutor2, NOOP_QUERY_EXECUTOR2, OffsetNode2, GroupByItemNode2, SetOperationNode2, FetchNode2, AggregateFunctionNode2, FunctionNode2, UnaryOperationNode2, CaseNode2, JSONPathLegNode2, TraversedJSONPathBuilder2, TupleNode2, SIMPLE_COLUMN_DATA_TYPES2, COLUMN_DATA_TYPE_REGEX2, DataTypeNode2, CastNode2, AddColumnNode2, ColumnDefinitionNode2, DropColumnNode2, RenameColumnNode2, CheckConstraintNode2, ON_MODIFY_FOREIGN_ACTIONS2, ReferencesNode2, GeneratedNode2, DefaultValueNode2, ModifyColumnNode2, ForeignKeyConstraintNode2, AddConstraintNode2, UniqueConstraintNode2, DropConstraintNode2, AlterColumnNode2, PrimaryKeyConstraintNode2, AddIndexNode2, RenameConstraintNode2, ImmediateValueTransformer2, CreateViewNode2, DropViewNode2, CreateTypeNode2, DropTypeNode2, RefreshMaterializedViewNode2, DefaultQueryExecutor2, ignoreError2 = () => {}, TRANSACTION_ACCESS_MODES2, TRANSACTION_ISOLATION_LEVELS2, logLevels2, LOG_LEVELS4, Kysely2, Transaction2, ControlledTransaction2, sql3, LIT_WRAP_REGEX2, DefaultQueryCompiler2, SELECT_MODIFIER_SQL2, SELECT_MODIFIER_PRIORITY2, JOIN_TYPE_SQL2, CompiledQuery2, DEFAULT_MIGRATION_TABLE2 = "kysely_migration", DEFAULT_MIGRATION_LOCK_TABLE2 = "kysely_migration_lock", DEFAULT_ALLOW_UNORDERED_MIGRATIONS2 = false, MIGRATION_LOCK_ID2 = "migration_lock", NO_MIGRATIONS2, MigrationResultSetError2, LOCK_ID3, DocumentDeletedError, DocumentNotFoundError, EventBusAggregateError, ReactorEventTypes, QueueEventTypes, ModuleNotFoundError, DuplicateModuleError, STRICT_ORDER_ACTION_TYPES, DRIVE_DOCUMENT_TYPE = "powerhouse/document-drive", ProcessorManager, ChannelScheme, SyncOperationStatus, ChannelErrorSource, SyncEventTypes, MailboxAggregateError, ChannelError, SyncOperationAggregateError, exports_001_create_operation_table, exports_002_create_keyframe_table, exports_003_create_document_table, exports_004_create_document_relationship_table, exports_005_create_indexer_state_table, exports_006_create_document_snapshot_table, exports_007_create_slug_mapping_table, exports_008_create_view_state_table, exports_009_create_operation_index_tables, exports_010_create_sync_tables, exports_011_add_cursor_type_column, exports_012_add_source_remote_column, exports_013_create_sync_dead_letters_table, REACTOR_SCHEMA = "reactor", migrations, SyncStatus, cachedEncoder, LOG2_262, IS_RELATIONAL_DB_PROCESSOR2;
333767
+ }, byteToHex, rnds8, randomUUID, PropagationMode, RelationshipChangeType, JobStatus, DocumentChangeType, AlterTableNode2, IdentifierNode2, CreateIndexNode2, CreateSchemaNode2, ON_COMMIT_ACTIONS2, CreateTableNode2, SchemableIdentifierNode2, DropIndexNode2, DropSchemaNode2, DropTableNode2, AliasNode2, TableNode2, SelectModifierNode2, AndNode2, OrNode2, OnNode2, JoinNode2, BinaryOperationNode2, COMPARISON_OPERATORS2, ARITHMETIC_OPERATORS2, JSON_OPERATORS2, BINARY_OPERATORS2, UNARY_FILTER_OPERATORS2, UNARY_OPERATORS2, OPERATORS2, OperatorNode2, ColumnNode2, SelectAllNode2, ReferenceNode2, OrderByItemNode2, RawNode2, CollateNode2, LOGGED_MESSAGES2, JSONReferenceNode2, JSONOperatorChainNode2, JSONPathNode2, PrimitiveValueListNode2, ValueListNode2, ValueNode2, ParensNode2, OrderByNode2, PartitionByNode2, OverNode2, FromNode2, GroupByNode2, HavingNode2, InsertQueryNode2, ListNode2, UpdateQueryNode2, UsingNode2, DeleteQueryNode2, WhereNode2, ReturningNode2, ExplainNode2, WhenNode2, MergeQueryNode2, OutputNode2, QueryNode2, SelectQueryNode2, PartitionByItemNode2, SelectionNode2, ValuesNode2, DefaultInsertValueNode2, ColumnUpdateNode2, OnDuplicateKeyNode2, NoResultError2, OnConflictNode2, TopNode2, OrActionNode2, LimitNode2, CommonTableExpressionNameNode2, CommonTableExpressionNode2, WithNode2, CHARS2, ROOT_OPERATION_NODES2, SCHEMALESS_FUNCTIONS2, WithSchemaTransformer2, MatchedNode2, NO_PLUGINS2, NoopQueryExecutor2, NOOP_QUERY_EXECUTOR2, OffsetNode2, GroupByItemNode2, SetOperationNode2, FetchNode2, AggregateFunctionNode2, FunctionNode2, UnaryOperationNode2, CaseNode2, JSONPathLegNode2, TraversedJSONPathBuilder2, TupleNode2, SIMPLE_COLUMN_DATA_TYPES2, COLUMN_DATA_TYPE_REGEX2, DataTypeNode2, CastNode2, AddColumnNode2, ColumnDefinitionNode2, DropColumnNode2, RenameColumnNode2, CheckConstraintNode2, ON_MODIFY_FOREIGN_ACTIONS2, ReferencesNode2, GeneratedNode2, DefaultValueNode2, ModifyColumnNode2, ForeignKeyConstraintNode2, AddConstraintNode2, UniqueConstraintNode2, DropConstraintNode2, AlterColumnNode2, PrimaryKeyConstraintNode2, AddIndexNode2, RenameConstraintNode2, ImmediateValueTransformer2, CreateViewNode2, DropViewNode2, CreateTypeNode2, DropTypeNode2, RefreshMaterializedViewNode2, DefaultQueryExecutor2, ignoreError2 = () => {}, TRANSACTION_ACCESS_MODES2, TRANSACTION_ISOLATION_LEVELS2, logLevels2, LOG_LEVELS4, Kysely2, Transaction2, ControlledTransaction2, sql3, LIT_WRAP_REGEX2, DefaultQueryCompiler2, SELECT_MODIFIER_SQL2, SELECT_MODIFIER_PRIORITY2, JOIN_TYPE_SQL2, CompiledQuery2, DEFAULT_MIGRATION_TABLE2 = "kysely_migration", DEFAULT_MIGRATION_LOCK_TABLE2 = "kysely_migration_lock", DEFAULT_ALLOW_UNORDERED_MIGRATIONS2 = false, MIGRATION_LOCK_ID2 = "migration_lock", NO_MIGRATIONS2, MigrationResultSetError2, LOCK_ID3, DocumentDeletedError, DocumentNotFoundError, EventBusAggregateError, ReactorEventTypes, QueueEventTypes, ModuleNotFoundError, DuplicateModuleError, STRICT_ORDER_ACTION_TYPES, DRIVE_DOCUMENT_TYPE = "powerhouse/document-drive", ProcessorManager, exports_001_create_operation_table, exports_002_create_keyframe_table, exports_003_create_document_table, exports_004_create_document_relationship_table, exports_005_create_indexer_state_table, exports_006_create_document_snapshot_table, exports_007_create_slug_mapping_table, exports_008_create_view_state_table, exports_009_create_operation_index_tables, exports_010_create_sync_tables, exports_011_add_cursor_type_column, exports_012_add_source_remote_column, exports_013_create_sync_dead_letters_table, exports_014_create_processor_cursor_table, REACTOR_SCHEMA = "reactor", migrations, ChannelScheme, SyncOperationStatus, ChannelErrorSource, SyncEventTypes, MailboxAggregateError, ChannelError, SyncOperationAggregateError, syncOpCounter = 0, getLatestAppliedOrdinal = (syncOps) => {
333768
+ let maxOrdinal = 0;
333769
+ for (const syncOp of syncOps) {
333770
+ if (syncOp.status === 2) {
333771
+ for (const op of syncOp.operations) {
333772
+ maxOrdinal = Math.max(maxOrdinal, op.context.ordinal);
333773
+ }
333774
+ }
333775
+ }
333776
+ return maxOrdinal;
333777
+ }, SyncStatus, cachedEncoder, LOG2_262, IS_RELATIONAL_DB_PROCESSOR2;
332979
333778
  var init_src6 = __esm(() => {
332980
333779
  init_dist3();
332981
333780
  __defProp2 = Object.defineProperty;
@@ -337037,19 +337836,24 @@ var init_src6 = __esm(() => {
337037
337836
  processorsByDrive = new Map;
337038
337837
  factoryToProcessors = new Map;
337039
337838
  knownDriveIds = new Set;
337040
- constructor(db, operationIndex, writeCache, consistencyTracker) {
337041
- super(db, operationIndex, writeCache, consistencyTracker, "processor-manager");
337839
+ cursorCache = new Map;
337840
+ logger;
337841
+ constructor(db, operationIndex, writeCache, consistencyTracker, logger3) {
337842
+ super(db, operationIndex, writeCache, consistencyTracker, {
337843
+ readModelId: "processor-manager",
337844
+ rebuildStateOnInit: true
337845
+ });
337846
+ this.logger = logger3;
337042
337847
  }
337043
- async indexOperations(items) {
337044
- if (items.length === 0)
337045
- return;
337848
+ async init() {
337849
+ await super.init();
337850
+ await this.loadAllCursors();
337851
+ await this.discoverExistingDrives();
337852
+ }
337853
+ async commitOperations(items) {
337046
337854
  await this.detectAndRegisterNewDrives(items);
337047
337855
  await this.detectAndCleanupDeletedDrives(items);
337048
337856
  await this.routeOperationsToProcessors(items);
337049
- await this.db.transaction().execute(async (trx) => {
337050
- await this.saveState(trx, items);
337051
- });
337052
- this.updateConsistencyTracker(items);
337053
337857
  }
337054
337858
  async registerFactory(identifier, factory) {
337055
337859
  if (this.factoryRegistry.has(identifier)) {
@@ -337066,13 +337870,13 @@ var init_src6 = __esm(() => {
337066
337870
  const factoryProcessors = this.factoryToProcessors.get(identifier);
337067
337871
  if (!factoryProcessors)
337068
337872
  return;
337069
- for (const [driveId, records] of factoryProcessors) {
337070
- for (const record2 of records) {
337071
- await this.safeDisconnect(record2.processor);
337873
+ for (const [driveId, tracked] of factoryProcessors) {
337874
+ for (const t2 of tracked) {
337875
+ await this.safeDisconnect(t2.record.processor);
337072
337876
  }
337073
337877
  const driveProcessors = this.processorsByDrive.get(driveId);
337074
337878
  if (driveProcessors) {
337075
- const remaining = driveProcessors.filter((p4) => !records.includes(p4));
337879
+ const remaining = driveProcessors.filter((p4) => !tracked.includes(p4));
337076
337880
  if (remaining.length > 0) {
337077
337881
  this.processorsByDrive.set(driveId, remaining);
337078
337882
  } else {
@@ -337080,14 +337884,24 @@ var init_src6 = __esm(() => {
337080
337884
  }
337081
337885
  }
337082
337886
  }
337887
+ await this.deleteProcessorCursors({ factoryId: identifier });
337083
337888
  this.factoryToProcessors.delete(identifier);
337084
337889
  this.factoryRegistry.delete(identifier);
337085
337890
  }
337086
- getFactoryIdentifiers() {
337087
- return Array.from(this.factoryRegistry.keys());
337891
+ get(processorId) {
337892
+ for (const tracked of this.allTrackedProcessors()) {
337893
+ if (tracked.processorId === processorId)
337894
+ return tracked;
337895
+ }
337896
+ return;
337897
+ }
337898
+ getAll() {
337899
+ return Array.from(this.allTrackedProcessors());
337088
337900
  }
337089
- getProcessorsForDrive(driveId) {
337090
- return this.processorsByDrive.get(driveId) ?? [];
337901
+ *allTrackedProcessors() {
337902
+ for (const tracked of this.processorsByDrive.values()) {
337903
+ yield* tracked;
337904
+ }
337091
337905
  }
337092
337906
  async detectAndRegisterNewDrives(operations2) {
337093
337907
  for (const op of operations2) {
@@ -337118,6 +337932,12 @@ var init_src6 = __esm(() => {
337118
337932
  this.knownDriveIds.delete(driveId);
337119
337933
  }
337120
337934
  }
337935
+ async discoverExistingDrives() {
337936
+ const drives = await this.db.selectFrom("DocumentSnapshot").select("documentId").where("documentType", "=", DRIVE_DOCUMENT_TYPE).where("isDeleted", "=", false).execute();
337937
+ for (const drive of drives) {
337938
+ this.knownDriveIds.add(drive.documentId);
337939
+ }
337940
+ }
337121
337941
  isDeletedDocumentADrive(documentId) {
337122
337942
  return this.knownDriveIds.has(documentId);
337123
337943
  }
@@ -337126,109 +337946,203 @@ var init_src6 = __esm(() => {
337126
337946
  try {
337127
337947
  records = await factory(driveHeader);
337128
337948
  } catch (error48) {
337129
- console.error(`ProcessorManager: Factory '${identifier}' failed for drive '${driveId}':`, error48);
337949
+ this.logger.error("Factory '@FactoryId' failed for drive '@DriveId': @Error", identifier, driveId, error48);
337130
337950
  return;
337131
337951
  }
337132
337952
  if (records.length === 0)
337133
337953
  return;
337954
+ const trackedList = [];
337955
+ for (let i3 = 0;i3 < records.length; i3++) {
337956
+ const record2 = records[i3];
337957
+ const processorId = `${identifier}:${driveId}:${i3}`;
337958
+ const cached2 = this.cursorCache.get(processorId);
337959
+ let lastOrdinal;
337960
+ let status;
337961
+ let lastError;
337962
+ let lastErrorTimestamp;
337963
+ if (cached2) {
337964
+ lastOrdinal = cached2.lastOrdinal;
337965
+ status = cached2.status;
337966
+ lastError = cached2.lastError ?? undefined;
337967
+ lastErrorTimestamp = cached2.lastErrorTimestamp ?? undefined;
337968
+ } else {
337969
+ const startFrom = record2.startFrom ?? "beginning";
337970
+ lastOrdinal = startFrom === "current" ? this.lastOrdinal : 0;
337971
+ status = "active";
337972
+ lastError = undefined;
337973
+ lastErrorTimestamp = undefined;
337974
+ }
337975
+ const tracked = {
337976
+ processorId,
337977
+ factoryId: identifier,
337978
+ driveId,
337979
+ processorIndex: i3,
337980
+ record: record2,
337981
+ lastOrdinal,
337982
+ status,
337983
+ lastError,
337984
+ lastErrorTimestamp,
337985
+ retry: () => this.retryProcessor(tracked)
337986
+ };
337987
+ trackedList.push(tracked);
337988
+ await this.saveProcessorCursor(tracked);
337989
+ }
337990
+ await this.db.deleteFrom("ProcessorCursor").where("factoryId", "=", identifier).where("driveId", "=", driveId).where("processorIndex", ">=", records.length).execute();
337991
+ for (const [id, row] of this.cursorCache) {
337992
+ if (row.factoryId === identifier && row.driveId === driveId && row.processorIndex >= records.length) {
337993
+ this.cursorCache.delete(id);
337994
+ }
337995
+ }
337134
337996
  const factoryProcessors = this.factoryToProcessors.get(identifier);
337135
337997
  if (factoryProcessors) {
337136
- factoryProcessors.set(driveId, records);
337998
+ factoryProcessors.set(driveId, trackedList);
337137
337999
  }
337138
338000
  const existingDriveProcessors = this.processorsByDrive.get(driveId) ?? [];
337139
338001
  this.processorsByDrive.set(driveId, [
337140
338002
  ...existingDriveProcessors,
337141
- ...records
338003
+ ...trackedList
337142
338004
  ]);
338005
+ for (const tracked of trackedList) {
338006
+ if (tracked.status === "active" && tracked.lastOrdinal < this.lastOrdinal) {
338007
+ await this.backfillProcessor(tracked);
338008
+ }
338009
+ }
338010
+ }
338011
+ async backfillProcessor(tracked) {
338012
+ let page = await this.operationIndex.getSinceOrdinal(tracked.lastOrdinal);
338013
+ while (page.results.length > 0) {
338014
+ const matching = page.results.filter((op) => matchesFilter(op, tracked.record.filter));
338015
+ if (matching.length > 0) {
338016
+ try {
338017
+ await tracked.record.processor.onOperations(matching);
338018
+ } catch (error48) {
338019
+ tracked.status = "errored";
338020
+ tracked.lastError = error48 instanceof Error ? error48.message : String(error48);
338021
+ tracked.lastErrorTimestamp = new Date;
338022
+ await this.safeSaveProcessorCursor(tracked);
338023
+ this.logger.error("Processor '@ProcessorId' failed during backfill at ordinal @Ordinal: @Error", tracked.processorId, tracked.lastOrdinal, error48);
338024
+ return;
338025
+ }
338026
+ }
338027
+ const maxOrdinal = Math.max(...page.results.map((op) => op.context.ordinal));
338028
+ tracked.lastOrdinal = maxOrdinal;
338029
+ await this.safeSaveProcessorCursor(tracked);
338030
+ if (!page.next)
338031
+ break;
338032
+ page = await page.next();
338033
+ }
338034
+ }
338035
+ async retryProcessor(tracked) {
338036
+ if (tracked.status !== "errored")
338037
+ return;
338038
+ tracked.status = "active";
338039
+ tracked.lastError = undefined;
338040
+ tracked.lastErrorTimestamp = undefined;
338041
+ await this.saveProcessorCursor(tracked);
338042
+ await this.backfillProcessor(tracked);
337143
338043
  }
337144
338044
  async cleanupDriveProcessors(driveId) {
337145
338045
  const processors = this.processorsByDrive.get(driveId);
337146
338046
  if (!processors)
337147
338047
  return;
337148
- for (const record2 of processors) {
337149
- await this.safeDisconnect(record2.processor);
338048
+ for (const tracked of processors) {
338049
+ await this.safeDisconnect(tracked.record.processor);
337150
338050
  }
337151
338051
  this.processorsByDrive.delete(driveId);
337152
338052
  for (const factoryProcessors of this.factoryToProcessors.values()) {
337153
338053
  factoryProcessors.delete(driveId);
337154
338054
  }
338055
+ await this.deleteProcessorCursors({ driveId });
337155
338056
  }
337156
338057
  async safeDisconnect(processor) {
337157
338058
  try {
337158
338059
  await processor.onDisconnect();
337159
338060
  } catch (error48) {
337160
- console.error("ProcessorManager: Error disconnecting processor:", error48);
338061
+ this.logger.error("Error disconnecting processor: @Error", error48);
337161
338062
  }
337162
338063
  }
337163
338064
  async routeOperationsToProcessors(operations2) {
337164
- const processorOperations = new Map;
337165
- for (const [, records] of this.processorsByDrive) {
337166
- for (const { processor, filter } of records) {
337167
- const matching = operations2.filter((op) => matchesFilter(op, filter));
337168
- if (matching.length === 0)
337169
- continue;
337170
- const existing = processorOperations.get(processor) ?? [];
337171
- processorOperations.set(processor, [...existing, ...matching]);
337172
- }
337173
- }
337174
- await Promise.all(Array.from(processorOperations.entries()).map(async ([processor, ops]) => {
337175
- try {
337176
- await processor.onOperations(ops);
337177
- } catch (error48) {
337178
- console.error("ProcessorManager: Error in processor.onOperations:", error48);
338065
+ const maxOrdinal = Math.max(...operations2.map((op) => op.context.ordinal));
338066
+ const allTracked = Array.from(this.allTrackedProcessors());
338067
+ await Promise.all(allTracked.map(async (tracked) => {
338068
+ if (tracked.status !== "active")
338069
+ return;
338070
+ const unseen = operations2.filter((op) => op.context.ordinal > tracked.lastOrdinal);
338071
+ const matching = unseen.filter((op) => matchesFilter(op, tracked.record.filter));
338072
+ if (matching.length > 0) {
338073
+ try {
338074
+ await tracked.record.processor.onOperations(matching);
338075
+ } catch (error48) {
338076
+ tracked.status = "errored";
338077
+ tracked.lastError = error48 instanceof Error ? error48.message : String(error48);
338078
+ tracked.lastErrorTimestamp = new Date;
338079
+ await this.safeSaveProcessorCursor(tracked);
338080
+ this.logger.error("Processor '@ProcessorId' failed at ordinal @Ordinal: @Error", tracked.processorId, tracked.lastOrdinal, error48);
338081
+ return;
338082
+ }
337179
338083
  }
338084
+ tracked.lastOrdinal = maxOrdinal;
338085
+ await this.safeSaveProcessorCursor(tracked);
337180
338086
  }));
337181
338087
  }
337182
- };
337183
- ((ChannelScheme2) => {
337184
- ChannelScheme2["CONNECT"] = "connect";
337185
- ChannelScheme2["SWITCHBOARD"] = "switchboard";
337186
- })(ChannelScheme ||= {});
337187
- ((SyncOperationStatus2) => {
337188
- SyncOperationStatus2[SyncOperationStatus2["Unknown"] = -1] = "Unknown";
337189
- SyncOperationStatus2[SyncOperationStatus2["TransportPending"] = 0] = "TransportPending";
337190
- SyncOperationStatus2[SyncOperationStatus2["ExecutionPending"] = 1] = "ExecutionPending";
337191
- SyncOperationStatus2[SyncOperationStatus2["Applied"] = 2] = "Applied";
337192
- SyncOperationStatus2[SyncOperationStatus2["Error"] = 3] = "Error";
337193
- })(SyncOperationStatus ||= {});
337194
- ((ChannelErrorSource2) => {
337195
- ChannelErrorSource2["None"] = "none";
337196
- ChannelErrorSource2["Channel"] = "channel";
337197
- ChannelErrorSource2["Inbox"] = "inbox";
337198
- ChannelErrorSource2["Outbox"] = "outbox";
337199
- })(ChannelErrorSource ||= {});
337200
- SyncEventTypes = {
337201
- SYNC_PENDING: 20001,
337202
- SYNC_SUCCEEDED: 20002,
337203
- SYNC_FAILED: 20003,
337204
- DEAD_LETTER_ADDED: 20004
337205
- };
337206
- MailboxAggregateError = class MailboxAggregateError extends Error {
337207
- errors;
337208
- constructor(errors5) {
337209
- const messages = errors5.map((e) => e.message).join("; ");
337210
- super(`Mailbox callback failed with ${errors5.length} error(s): ${messages}`);
337211
- this.name = "MailboxAggregateError";
337212
- this.errors = errors5;
337213
- }
337214
- };
337215
- ChannelError = class ChannelError extends Error {
337216
- source;
337217
- error;
337218
- constructor(source, error48) {
337219
- super(`ChannelError[${source}]: ${error48.message}`);
337220
- this.name = "ChannelError";
337221
- this.source = source;
337222
- this.error = error48;
338088
+ async loadAllCursors() {
338089
+ const rows = await this.db.selectFrom("ProcessorCursor").selectAll().execute();
338090
+ for (const row of rows) {
338091
+ this.cursorCache.set(row.processorId, row);
338092
+ }
337223
338093
  }
337224
- };
337225
- SyncOperationAggregateError = class SyncOperationAggregateError extends Error {
337226
- errors;
337227
- constructor(errors5) {
337228
- const messages = errors5.map((e) => e.message).join("; ");
337229
- super(`SyncOperation callback failed with ${errors5.length} error(s): ${messages}`);
337230
- this.name = "SyncOperationAggregateError";
337231
- this.errors = errors5;
338094
+ async safeSaveProcessorCursor(tracked) {
338095
+ try {
338096
+ await this.saveProcessorCursor(tracked);
338097
+ } catch (error48) {
338098
+ this.logger.error("Failed to persist cursor for '@ProcessorId': @Error", tracked.processorId, error48);
338099
+ }
338100
+ }
338101
+ async saveProcessorCursor(tracked) {
338102
+ await this.db.insertInto("ProcessorCursor").values({
338103
+ processorId: tracked.processorId,
338104
+ factoryId: tracked.factoryId,
338105
+ driveId: tracked.driveId,
338106
+ processorIndex: tracked.processorIndex,
338107
+ lastOrdinal: tracked.lastOrdinal,
338108
+ status: tracked.status,
338109
+ lastError: tracked.lastError ?? null,
338110
+ lastErrorTimestamp: tracked.lastErrorTimestamp ?? null,
338111
+ updatedAt: new Date
338112
+ }).onConflict((oc) => oc.column("processorId").doUpdateSet({
338113
+ lastOrdinal: tracked.lastOrdinal,
338114
+ status: tracked.status,
338115
+ lastError: tracked.lastError ?? null,
338116
+ lastErrorTimestamp: tracked.lastErrorTimestamp ?? null,
338117
+ updatedAt: new Date
338118
+ })).execute();
338119
+ this.cursorCache.set(tracked.processorId, {
338120
+ processorId: tracked.processorId,
338121
+ factoryId: tracked.factoryId,
338122
+ driveId: tracked.driveId,
338123
+ processorIndex: tracked.processorIndex,
338124
+ lastOrdinal: tracked.lastOrdinal,
338125
+ status: tracked.status,
338126
+ lastError: tracked.lastError ?? null,
338127
+ lastErrorTimestamp: tracked.lastErrorTimestamp ?? null,
338128
+ createdAt: new Date,
338129
+ updatedAt: new Date
338130
+ });
338131
+ }
338132
+ async deleteProcessorCursors(filter) {
338133
+ if ("factoryId" in filter) {
338134
+ await this.db.deleteFrom("ProcessorCursor").where("factoryId", "=", filter.factoryId).execute();
338135
+ for (const [id, row] of this.cursorCache) {
338136
+ if (row.factoryId === filter.factoryId)
338137
+ this.cursorCache.delete(id);
338138
+ }
338139
+ } else {
338140
+ await this.db.deleteFrom("ProcessorCursor").where("driveId", "=", filter.driveId).execute();
338141
+ for (const [id, row] of this.cursorCache) {
338142
+ if (row.driveId === filter.driveId)
338143
+ this.cursorCache.delete(id);
338144
+ }
338145
+ }
337232
338146
  }
337233
338147
  };
337234
338148
  exports_001_create_operation_table = {};
@@ -337283,6 +338197,10 @@ var init_src6 = __esm(() => {
337283
338197
  __export2(exports_013_create_sync_dead_letters_table, {
337284
338198
  up: () => up13
337285
338199
  });
338200
+ exports_014_create_processor_cursor_table = {};
338201
+ __export2(exports_014_create_processor_cursor_table, {
338202
+ up: () => up14
338203
+ });
337286
338204
  migrations = {
337287
338205
  "001_create_operation_table": exports_001_create_operation_table,
337288
338206
  "002_create_keyframe_table": exports_002_create_keyframe_table,
@@ -337296,7 +338214,60 @@ var init_src6 = __esm(() => {
337296
338214
  "010_create_sync_tables": exports_010_create_sync_tables,
337297
338215
  "011_add_cursor_type_column": exports_011_add_cursor_type_column,
337298
338216
  "012_add_source_remote_column": exports_012_add_source_remote_column,
337299
- "013_create_sync_dead_letters_table": exports_013_create_sync_dead_letters_table
338217
+ "013_create_sync_dead_letters_table": exports_013_create_sync_dead_letters_table,
338218
+ "014_create_processor_cursor_table": exports_014_create_processor_cursor_table
338219
+ };
338220
+ ((ChannelScheme2) => {
338221
+ ChannelScheme2["CONNECT"] = "connect";
338222
+ ChannelScheme2["SWITCHBOARD"] = "switchboard";
338223
+ })(ChannelScheme ||= {});
338224
+ ((SyncOperationStatus2) => {
338225
+ SyncOperationStatus2[SyncOperationStatus2["Unknown"] = -1] = "Unknown";
338226
+ SyncOperationStatus2[SyncOperationStatus2["TransportPending"] = 0] = "TransportPending";
338227
+ SyncOperationStatus2[SyncOperationStatus2["ExecutionPending"] = 1] = "ExecutionPending";
338228
+ SyncOperationStatus2[SyncOperationStatus2["Applied"] = 2] = "Applied";
338229
+ SyncOperationStatus2[SyncOperationStatus2["Error"] = 3] = "Error";
338230
+ })(SyncOperationStatus ||= {});
338231
+ ((ChannelErrorSource2) => {
338232
+ ChannelErrorSource2["None"] = "none";
338233
+ ChannelErrorSource2["Channel"] = "channel";
338234
+ ChannelErrorSource2["Inbox"] = "inbox";
338235
+ ChannelErrorSource2["Outbox"] = "outbox";
338236
+ })(ChannelErrorSource ||= {});
338237
+ SyncEventTypes = {
338238
+ SYNC_PENDING: 20001,
338239
+ SYNC_SUCCEEDED: 20002,
338240
+ SYNC_FAILED: 20003,
338241
+ DEAD_LETTER_ADDED: 20004,
338242
+ CONNECTION_STATE_CHANGED: 20005
338243
+ };
338244
+ MailboxAggregateError = class MailboxAggregateError extends Error {
338245
+ errors;
338246
+ constructor(errors5) {
338247
+ const messages = errors5.map((e) => e.message).join("; ");
338248
+ super(`Mailbox callback failed with ${errors5.length} error(s): ${messages}`);
338249
+ this.name = "MailboxAggregateError";
338250
+ this.errors = errors5;
338251
+ }
338252
+ };
338253
+ ChannelError = class ChannelError extends Error {
338254
+ source;
338255
+ error;
338256
+ constructor(source, error48) {
338257
+ super(`ChannelError[${source}]: ${error48.message}`);
338258
+ this.name = "ChannelError";
338259
+ this.source = source;
338260
+ this.error = error48;
338261
+ }
338262
+ };
338263
+ SyncOperationAggregateError = class SyncOperationAggregateError extends Error {
338264
+ errors;
338265
+ constructor(errors5) {
338266
+ const messages = errors5.map((e) => e.message).join("; ");
338267
+ super(`SyncOperation callback failed with ${errors5.length} error(s): ${messages}`);
338268
+ this.name = "SyncOperationAggregateError";
338269
+ this.errors = errors5;
338270
+ }
337300
338271
  };
337301
338272
  ((SyncStatus2) => {
337302
338273
  SyncStatus2["Synced"] = "SYNCED";