@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 +1335 -364
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +10 -10
package/dist/src/cli.js
CHANGED
|
@@ -330605,14 +330605,14 @@ class BaseReadModel {
|
|
|
330605
330605
|
operationIndex;
|
|
330606
330606
|
writeCache;
|
|
330607
330607
|
consistencyTracker;
|
|
330608
|
-
|
|
330608
|
+
config;
|
|
330609
330609
|
lastOrdinal = 0;
|
|
330610
|
-
constructor(db, operationIndex, writeCache, consistencyTracker,
|
|
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.
|
|
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
|
|
330624
|
-
await this.indexOperations(
|
|
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
|
|
330631
|
-
await this.indexOperations(
|
|
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
|
-
|
|
331815
|
-
|
|
331816
|
-
|
|
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
|
-
|
|
331837
|
-
|
|
331838
|
-
|
|
331839
|
-
|
|
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
|
-
|
|
331909
|
-
|
|
331910
|
-
|
|
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
|
-
|
|
331934
|
-
|
|
331935
|
-
|
|
331936
|
-
|
|
331937
|
-
|
|
331938
|
-
|
|
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
|
-
|
|
331943
|
-
|
|
332285
|
+
...opWithContext,
|
|
332286
|
+
operation: deserializedOperation
|
|
331944
332287
|
};
|
|
331945
332288
|
}
|
|
331946
|
-
|
|
331947
|
-
|
|
331948
|
-
|
|
331949
|
-
|
|
331950
|
-
|
|
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
|
|
331956
|
-
|
|
331957
|
-
|
|
331958
|
-
|
|
331959
|
-
|
|
331960
|
-
|
|
331961
|
-
|
|
331962
|
-
|
|
331963
|
-
|
|
331964
|
-
|
|
331965
|
-
|
|
331966
|
-
|
|
331967
|
-
|
|
331968
|
-
|
|
331969
|
-
|
|
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
|
-
|
|
331973
|
-
|
|
331974
|
-
this.
|
|
331975
|
-
|
|
331976
|
-
|
|
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
|
-
|
|
331980
|
-
|
|
331981
|
-
|
|
331982
|
-
|
|
331983
|
-
this.
|
|
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
|
-
|
|
331987
|
-
|
|
331988
|
-
this.relationshipSubscriptions.set(id, { id, callback, search });
|
|
332413
|
+
onConnectionStateChange(callback) {
|
|
332414
|
+
this.connectionStateCallbacks.add(callback);
|
|
331989
332415
|
return () => {
|
|
331990
|
-
this.
|
|
332416
|
+
this.connectionStateCallbacks.delete(callback);
|
|
331991
332417
|
};
|
|
331992
332418
|
}
|
|
331993
|
-
|
|
331994
|
-
|
|
331995
|
-
|
|
331996
|
-
|
|
331997
|
-
|
|
331998
|
-
|
|
331999
|
-
|
|
332000
|
-
|
|
332001
|
-
|
|
332002
|
-
|
|
332003
|
-
|
|
332004
|
-
|
|
332005
|
-
|
|
332006
|
-
|
|
332007
|
-
|
|
332008
|
-
|
|
332009
|
-
|
|
332010
|
-
|
|
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
|
-
|
|
332017
|
-
|
|
332018
|
-
|
|
332019
|
-
|
|
332020
|
-
|
|
332021
|
-
|
|
332022
|
-
|
|
332023
|
-
|
|
332024
|
-
|
|
332025
|
-
|
|
332026
|
-
|
|
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
|
-
|
|
332033
|
-
const
|
|
332034
|
-
|
|
332035
|
-
|
|
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
|
-
|
|
332038
|
-
|
|
332039
|
-
|
|
332040
|
-
|
|
332041
|
-
|
|
332042
|
-
|
|
332043
|
-
|
|
332044
|
-
|
|
332045
|
-
|
|
332046
|
-
|
|
332047
|
-
|
|
332048
|
-
|
|
332049
|
-
|
|
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
|
-
|
|
332056
|
-
|
|
332057
|
-
|
|
332058
|
-
|
|
332059
|
-
|
|
332060
|
-
|
|
332061
|
-
|
|
332062
|
-
|
|
332063
|
-
|
|
332064
|
-
|
|
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
|
-
|
|
332071
|
-
this.
|
|
332072
|
-
this.deletedSubscriptions.clear();
|
|
332073
|
-
this.updatedSubscriptions.clear();
|
|
332074
|
-
this.relationshipSubscriptions.clear();
|
|
332788
|
+
get poller() {
|
|
332789
|
+
return this.pollTimer;
|
|
332075
332790
|
}
|
|
332076
|
-
|
|
332077
|
-
|
|
332078
|
-
|
|
332079
|
-
|
|
332080
|
-
|
|
332081
|
-
|
|
332082
|
-
|
|
332083
|
-
|
|
332084
|
-
|
|
332085
|
-
|
|
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
|
-
|
|
332088
|
-
|
|
332089
|
-
|
|
332090
|
-
|
|
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
|
-
|
|
332096
|
-
|
|
332097
|
-
|
|
332098
|
-
return
|
|
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
|
-
|
|
332109
|
-
|
|
332110
|
-
|
|
332111
|
-
|
|
332112
|
-
|
|
332113
|
-
|
|
332114
|
-
|
|
332115
|
-
|
|
332116
|
-
|
|
332117
|
-
|
|
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,
|
|
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
|
-
|
|
337041
|
-
|
|
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
|
|
337044
|
-
|
|
337045
|
-
|
|
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,
|
|
337070
|
-
for (const
|
|
337071
|
-
await this.safeDisconnect(
|
|
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) => !
|
|
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
|
-
|
|
337087
|
-
|
|
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
|
-
|
|
337090
|
-
|
|
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
|
-
|
|
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,
|
|
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
|
-
...
|
|
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
|
|
337149
|
-
await this.safeDisconnect(
|
|
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
|
-
|
|
338061
|
+
this.logger.error("Error disconnecting processor: @Error", error48);
|
|
337161
338062
|
}
|
|
337162
338063
|
}
|
|
337163
338064
|
async routeOperationsToProcessors(operations2) {
|
|
337164
|
-
const
|
|
337165
|
-
|
|
337166
|
-
|
|
337167
|
-
|
|
337168
|
-
|
|
337169
|
-
|
|
337170
|
-
|
|
337171
|
-
|
|
337172
|
-
|
|
337173
|
-
|
|
337174
|
-
|
|
337175
|
-
|
|
337176
|
-
|
|
337177
|
-
|
|
337178
|
-
|
|
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
|
-
|
|
337184
|
-
|
|
337185
|
-
|
|
337186
|
-
|
|
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
|
-
|
|
337226
|
-
|
|
337227
|
-
|
|
337228
|
-
|
|
337229
|
-
|
|
337230
|
-
|
|
337231
|
-
|
|
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";
|