@powerhousedao/reactor 6.2.3-dev.2 → 6.2.3-dev.20
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/{build-worker-executor-DnU3ZU4H.js → build-worker-executor-DUjyF4NM.js} +2 -2
- package/dist/{build-worker-executor-DnU3ZU4H.js.map → build-worker-executor-DUjyF4NM.js.map} +1 -1
- package/dist/{document-indexer-C5Gsa1B3.js → document-indexer-C1oY6CaR.js} +169 -21
- package/dist/document-indexer-C1oY6CaR.js.map +1 -0
- package/dist/{drive-container-types-yZrksiJR.js → drive-container-types-CS5IxDiA.js} +174 -67
- package/dist/drive-container-types-CS5IxDiA.js.map +1 -0
- package/dist/entry.js +2 -2
- package/dist/index.d.ts +419 -78
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +355 -43
- package/dist/index.js.map +1 -1
- package/dist/projection-entry.js +8 -8
- package/dist/projection-entry.js.map +1 -1
- package/dist/{projection-shard-manager-CPQc5XHL.js → projection-shard-manager-CUo4bF4O.js} +3 -2
- package/dist/projection-shard-manager-CUo4bF4O.js.map +1 -0
- package/dist/types-DMKLa0Ok.js.map +1 -1
- package/dist/{worker-HPysBfhx.js → worker-g03lNM42.js} +2 -2
- package/dist/{worker-HPysBfhx.js.map → worker-g03lNM42.js.map} +1 -1
- package/package.json +4 -4
- package/dist/document-indexer-C5Gsa1B3.js.map +0 -1
- package/dist/drive-container-types-yZrksiJR.js.map +0 -1
- package/dist/projection-shard-manager-CPQc5XHL.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { A as
|
|
1
|
+
import { $ as parsePagingOptions, A as targetDocumentId, B as DuplicateOperationError, C as InvalidModuleError, D as createEmptyConsistencyToken, E as createConsistencyToken, F as buildDecisionModel, G as AuthorizationDeniedError, H as RevisionMismatchError, I as APPEND_CONDITION_FAILED_PREFIX, J as ExcessiveReshuffleError, K as DocumentDeletedError, L as AppendConditionFailedError, M as selectDecisionModel, N as documentDecisionModel, O as submittedActionIds, P as authDecisionModel, Q as matchesScope, R as DocumentAlreadyExistsError, S as DuplicateModuleError, T as GATED_DOCUMENT_ACTIONS, U as AuthEnforcementDisabledError, V as OptimisticLockError, W as AuthTimestampNotMonotonicError, X as RelationshipNotFoundError, Y as InvalidOperationTimestampError, Z as UpgradePreconditionFailedError, _ as DocumentMetaCache, a as createForwardingPoolInstrumentation, b as JobExecutorEventTypes, c as KyselyKeyframeStore, d as DriveCollectionId, et as throwIfAborted, f as KyselyExecutionScope, g as KyselyOperationIndex, h as KyselyWriteCache, i as runMigrations, j as decideAtHead, k as summarizeSubmittedActions, l as DocumentModelRegistry, m as EventBus, n as REACTOR_SCHEMA, o as instrumentPgPool, p as resolveFeatureFlags, q as DocumentNotFoundError, r as getMigrationStatus, s as KyselyOperationStore, t as DEFAULT_DRIVE_CONTAINER_TYPES, u as SimpleJobExecutor, v as CollectionMembershipCache, w as ModuleNotFoundError, x as DuplicateManifestError, y as DEFAULT_DEFERRED_JOB_TTL_MS, z as DocumentExistence } from "./drive-container-types-CS5IxDiA.js";
|
|
2
2
|
import { n as ReactorEventTypes, t as EventBusAggregateError } from "./types-DMKLa0Ok.js";
|
|
3
3
|
import { i as WorkerInitFailedError, r as WorkerExitedError, t as WorkerAbortTimeoutError } from "./errors-D3S6Eysd.js";
|
|
4
|
-
import { a as
|
|
4
|
+
import { a as KyselyDocumentView, c as ReadModelCoordinator, d as DEFAULT_READ_MODEL_YIELD_DEADLINE_MS, f as defaultReadModelIndexingConfig, i as DeletedDocumentRead, l as BaseReadModel, n as ConsistencyTracker, o as DOCUMENT_INDEXER_READ_MODEL, p as unchunkedReadModelIndexingConfig, r as makeConsistencyKey, s as DOCUMENT_VIEW_READ_MODEL, t as KyselyDocumentIndexer, u as DEFAULT_COMMIT_CHUNK_SIZE } from "./document-indexer-C1oY6CaR.js";
|
|
5
5
|
import { n as errorToInfo, r as sanitizeArg, t as createForwardingLogger } from "./forwarding-logger-BBkMSxuJ.js";
|
|
6
|
-
import { t as workerEntryPath } from "./worker-
|
|
6
|
+
import { t as workerEntryPath } from "./worker-g03lNM42.js";
|
|
7
7
|
import { DowngradeNotSupportedError, MAX_AUTH_GRANTS, UnsupportedDocumentModelVersionError, actions, actions as documentActions, createPresignedHeader, decide, deserializeSignature, garbageCollect, generateId, groupDocumentType, groupMembershipActionTypes, hashDocumentStateForScope, normalizeDocumentModelVersion, sortOperations, toTransportAction } from "@powerhousedao/shared/document-model";
|
|
8
8
|
import { addFile, addFolder, copyNode, deleteNode, driveCreateDocument, generateNodesCopy, getDescendants, handleTargetNameCollisions, isFileNode, isFolderNode, moveNode, updateNode } from "@powerhousedao/shared/document-drive";
|
|
9
9
|
import { v4 } from "uuid";
|
|
@@ -1248,6 +1248,79 @@ var ReactorClient = class {
|
|
|
1248
1248
|
return this.find({ ids: sourceIds }, view, paging, signal);
|
|
1249
1249
|
}
|
|
1250
1250
|
/**
|
|
1251
|
+
* Retrieves the outgoing relationship edges of a source document, carrying the
|
|
1252
|
+
* metadata and timestamps the far-end documents do not.
|
|
1253
|
+
*/
|
|
1254
|
+
async getOutgoingRelationshipEdges(sourceIdentifier, relationshipType, view, paging, signal) {
|
|
1255
|
+
this.logger.verbose("getOutgoingRelationshipEdges(@sourceIdentifier, @relationshipType, @view, @paging)", sourceIdentifier, relationshipType, view, paging);
|
|
1256
|
+
const sourceId = await this.documentView.resolveIdOrSlug(sourceIdentifier, view, void 0, signal);
|
|
1257
|
+
const edges = await this.reactor.getOutgoingRelationshipEdges(sourceId, relationshipType, paging, void 0, signal);
|
|
1258
|
+
return this.gateEdges(edges, "targetId", view, signal);
|
|
1259
|
+
}
|
|
1260
|
+
/**
|
|
1261
|
+
* Retrieves the incoming relationship edges of a target document, carrying the
|
|
1262
|
+
* metadata and timestamps the far-end documents do not.
|
|
1263
|
+
*/
|
|
1264
|
+
async getIncomingRelationshipEdges(targetIdentifier, relationshipType, view, paging, signal) {
|
|
1265
|
+
this.logger.verbose("getIncomingRelationshipEdges(@targetIdentifier, @relationshipType, @view, @paging)", targetIdentifier, relationshipType, view, paging);
|
|
1266
|
+
const targetId = await this.documentView.resolveIdOrSlug(targetIdentifier, view, void 0, signal);
|
|
1267
|
+
const edges = await this.reactor.getIncomingRelationshipEdges(targetId, relationshipType, paging, void 0, signal);
|
|
1268
|
+
return this.gateEdges(edges, "sourceId", view, signal);
|
|
1269
|
+
}
|
|
1270
|
+
/**
|
|
1271
|
+
* Drops the edges whose far-end document the subject may read no domain scope
|
|
1272
|
+
* of. An edge is withheld whole rather than stripped of its metadata: the
|
|
1273
|
+
* document-shaped relationship reads already answer with the far end stripped
|
|
1274
|
+
* to the scopes the gate allows, so the far end's existence is disclosed
|
|
1275
|
+
* either way, but an edge's metadata is content about the pair that the far
|
|
1276
|
+
* end's own reads would refuse. An edge to a far end stripped to nothing
|
|
1277
|
+
* therefore carries content past a refusal, and there is no useful shell to
|
|
1278
|
+
* hand back in its place.
|
|
1279
|
+
*
|
|
1280
|
+
* `nextCursor` and `options` are left as the underlying stream reported them,
|
|
1281
|
+
* because a caller must feed them back to resume from the right position. A
|
|
1282
|
+
* gated page can therefore be shorter than the limit it asked for.
|
|
1283
|
+
*/
|
|
1284
|
+
async gateEdges(page, farEnd, view, signal) {
|
|
1285
|
+
const ids = [...new Set(page.results.map((edge) => edge[farEnd]))];
|
|
1286
|
+
if (ids.length === 0) return page;
|
|
1287
|
+
const farEndView = {
|
|
1288
|
+
subject: view?.subject,
|
|
1289
|
+
branch: view?.branch
|
|
1290
|
+
};
|
|
1291
|
+
const documents = await this.reactor.find({ ids }, farEndView, {
|
|
1292
|
+
cursor: "0",
|
|
1293
|
+
limit: ids.length
|
|
1294
|
+
}, void 0, signal);
|
|
1295
|
+
const readable = new Map(await Promise.all(documents.results.map(async (doc) => {
|
|
1296
|
+
const allows = await this.readableScopes(doc, view, signal);
|
|
1297
|
+
const domainScopes = Object.keys(doc.state).filter((scope) => !ALWAYS_READABLE_SCOPES.has(scope));
|
|
1298
|
+
return [doc.header.id, domainScopes.length === 0 || domainScopes.some(allows)];
|
|
1299
|
+
})));
|
|
1300
|
+
const results = page.results.filter((edge) => readable.get(edge[farEnd]) !== false);
|
|
1301
|
+
if (results.length === page.results.length) return page;
|
|
1302
|
+
const nextPage = page.next;
|
|
1303
|
+
return {
|
|
1304
|
+
...page,
|
|
1305
|
+
results,
|
|
1306
|
+
next: nextPage ? async () => this.gateEdges(await nextPage(), farEnd, view, signal) : void 0
|
|
1307
|
+
};
|
|
1308
|
+
}
|
|
1309
|
+
/**
|
|
1310
|
+
* One relationship edge, or undefined when it does not exist. A point lookup:
|
|
1311
|
+
* the pair is filtered in SQL rather than scanned out of the source's edge
|
|
1312
|
+
* list, which on a drive with thousands of children is the difference between
|
|
1313
|
+
* one query and dozens.
|
|
1314
|
+
*/
|
|
1315
|
+
async readRelationshipEdge(sourceIdentifier, targetIdentifier, relationshipType, view, signal) {
|
|
1316
|
+
const sourceId = await this.documentView.resolveIdOrSlug(sourceIdentifier, view, void 0, signal);
|
|
1317
|
+
const targetId = await this.documentView.resolveIdOrSlug(targetIdentifier, view, void 0, signal);
|
|
1318
|
+
return (await this.documentIndexer.getDirectedRelationships(sourceId, targetId, [relationshipType], {
|
|
1319
|
+
cursor: "0",
|
|
1320
|
+
limit: 1
|
|
1321
|
+
}, void 0, signal)).results[0];
|
|
1322
|
+
}
|
|
1323
|
+
/**
|
|
1251
1324
|
* Filters documents by criteria and returns a list of them
|
|
1252
1325
|
*/
|
|
1253
1326
|
async find(search, view, paging, signal) {
|
|
@@ -1515,9 +1588,21 @@ var ReactorClient = class {
|
|
|
1515
1588
|
/**
|
|
1516
1589
|
* Adds multiple documents as children to another and waits for completion
|
|
1517
1590
|
*/
|
|
1518
|
-
async addRelationship(sourceIdentifier, targetIdentifier, relationshipType, branch = "main", signal) {
|
|
1519
|
-
this.logger.verbose("addRelationship(@sourceIdentifier, @targetIdentifier, @relationshipType, @branch)", sourceIdentifier, targetIdentifier, relationshipType, branch);
|
|
1520
|
-
const jobInfo = await this.reactor.addRelationship(sourceIdentifier, targetIdentifier, relationshipType, branch, this.signer, signal);
|
|
1591
|
+
async addRelationship(sourceIdentifier, targetIdentifier, relationshipType, metadata, branch = "main", signal) {
|
|
1592
|
+
this.logger.verbose("addRelationship(@sourceIdentifier, @targetIdentifier, @relationshipType, @metadata, @branch)", sourceIdentifier, targetIdentifier, relationshipType, metadata, branch);
|
|
1593
|
+
const jobInfo = await this.reactor.addRelationship(sourceIdentifier, targetIdentifier, relationshipType, metadata, branch, this.signer, signal);
|
|
1594
|
+
const completedJob = await this.waitForJob(jobInfo, signal);
|
|
1595
|
+
if (completedJob.status === JobStatus.FAILED) throw new Error(completedJob.error?.message);
|
|
1596
|
+
const result = await this.reactor.getByIdOrSlug(sourceIdentifier, { branch }, completedJob.consistencyToken, signal);
|
|
1597
|
+
return this.gateDocument(result, { branch }, signal);
|
|
1598
|
+
}
|
|
1599
|
+
/**
|
|
1600
|
+
* Replaces the metadata of an existing relationship and waits for completion.
|
|
1601
|
+
*/
|
|
1602
|
+
async updateRelationship(sourceIdentifier, targetIdentifier, relationshipType, metadata, branch = "main", signal) {
|
|
1603
|
+
this.logger.verbose("updateRelationship(@sourceIdentifier, @targetIdentifier, @relationshipType, @metadata, @branch)", sourceIdentifier, targetIdentifier, relationshipType, metadata, branch);
|
|
1604
|
+
if (!await this.readRelationshipEdge(sourceIdentifier, targetIdentifier, relationshipType, { branch }, signal)) throw new RelationshipNotFoundError(sourceIdentifier, targetIdentifier, relationshipType);
|
|
1605
|
+
const jobInfo = await this.reactor.updateRelationship(sourceIdentifier, targetIdentifier, relationshipType, metadata, branch, this.signer, signal);
|
|
1521
1606
|
const completedJob = await this.waitForJob(jobInfo, signal);
|
|
1522
1607
|
if (completedJob.status === JobStatus.FAILED) throw new Error(completedJob.error?.message);
|
|
1523
1608
|
const result = await this.reactor.getByIdOrSlug(sourceIdentifier, { branch }, completedJob.consistencyToken, signal);
|
|
@@ -1539,10 +1624,11 @@ var ReactorClient = class {
|
|
|
1539
1624
|
*/
|
|
1540
1625
|
async moveRelationship(sourceParentIdentifier, targetParentIdentifier, targetIdentifier, relationshipType, branch = "main", signal) {
|
|
1541
1626
|
this.logger.verbose("moveRelationship(@sourceParentIdentifier, @targetParentIdentifier, @targetIdentifier, @relationshipType, @branch)", sourceParentIdentifier, targetParentIdentifier, targetIdentifier, relationshipType, branch);
|
|
1627
|
+
const metadata = (await this.readRelationshipEdge(sourceParentIdentifier, targetIdentifier, relationshipType, { branch }, signal))?.metadata;
|
|
1542
1628
|
const removeJobInfo = await this.reactor.removeRelationship(sourceParentIdentifier, targetIdentifier, relationshipType, branch, this.signer, signal);
|
|
1543
1629
|
const removeCompletedJob = await this.waitForJob(removeJobInfo, signal);
|
|
1544
1630
|
if (removeCompletedJob.status === JobStatus.FAILED) throw new Error(removeCompletedJob.error?.message);
|
|
1545
|
-
const addJobInfo = await this.reactor.addRelationship(targetParentIdentifier, targetIdentifier, relationshipType, branch, this.signer, signal);
|
|
1631
|
+
const addJobInfo = await this.reactor.addRelationship(targetParentIdentifier, targetIdentifier, relationshipType, metadata, branch, this.signer, signal);
|
|
1546
1632
|
const addCompletedJob = await this.waitForJob(addJobInfo, signal);
|
|
1547
1633
|
if (addCompletedJob.status === JobStatus.FAILED) throw new Error(addCompletedJob.error?.message);
|
|
1548
1634
|
const sourceResult = await this.reactor.getByIdOrSlug(sourceParentIdentifier, { branch }, removeCompletedJob.consistencyToken, signal);
|
|
@@ -2212,6 +2298,7 @@ var WorkerPoolJobExecutorManager = class {
|
|
|
2212
2298
|
jobId: job.id,
|
|
2213
2299
|
operations: payload.operations,
|
|
2214
2300
|
jobMeta: payload.jobMeta,
|
|
2301
|
+
submittedActionIds: submittedActionIds(job),
|
|
2215
2302
|
collectionMemberships
|
|
2216
2303
|
};
|
|
2217
2304
|
try {
|
|
@@ -2495,7 +2582,8 @@ var InMemoryJobTracker = class {
|
|
|
2495
2582
|
this.jobs.set(jobId, {
|
|
2496
2583
|
...job,
|
|
2497
2584
|
status: JobStatus.WRITE_READY,
|
|
2498
|
-
consistencyToken
|
|
2585
|
+
consistencyToken,
|
|
2586
|
+
result: summarizeSubmittedActions(event.operations, event.submittedActionIds)
|
|
2499
2587
|
});
|
|
2500
2588
|
}
|
|
2501
2589
|
}
|
|
@@ -2663,7 +2751,8 @@ var ProcessorManager = class extends BaseReadModel {
|
|
|
2663
2751
|
constructor(db, operationIndex, writeCache, consistencyTracker, logger, driveContainerTypes, options = {}) {
|
|
2664
2752
|
super(db, operationIndex, writeCache, consistencyTracker, {
|
|
2665
2753
|
readModelId: "processor-manager",
|
|
2666
|
-
rebuildStateOnInit: true
|
|
2754
|
+
rebuildStateOnInit: true,
|
|
2755
|
+
indexing: unchunkedReadModelIndexingConfig
|
|
2667
2756
|
});
|
|
2668
2757
|
this.logger = logger;
|
|
2669
2758
|
this.driveContainerTypes = driveContainerTypes;
|
|
@@ -4229,9 +4318,35 @@ const DRIVE_AUTH_ERROR_MESSAGES = {
|
|
|
4229
4318
|
forbidden: "Forbidden: insufficient permissions",
|
|
4230
4319
|
authenticationRequired: "Forbidden: authentication required"
|
|
4231
4320
|
};
|
|
4321
|
+
/**
|
|
4322
|
+
* A non-GraphQL HTTP failure against a drive endpoint.
|
|
4323
|
+
*
|
|
4324
|
+
* Drive discovery (`GET <base>/d/:drive`) is REST, not GraphQL, so its
|
|
4325
|
+
* failures cannot be a `GraphQLRequestError` without the name lying about
|
|
4326
|
+
* what was called. It carries the status for the same reason that one does:
|
|
4327
|
+
* `isDriveAuthError` is what decides whether a failure prompts a login, and a
|
|
4328
|
+
* bare `Error` tells it nothing.
|
|
4329
|
+
*/
|
|
4330
|
+
var DriveRequestError = class extends Error {
|
|
4331
|
+
statusCode;
|
|
4332
|
+
constructor(message, statusCode) {
|
|
4333
|
+
super(message);
|
|
4334
|
+
this.name = "DriveRequestError";
|
|
4335
|
+
this.statusCode = statusCode;
|
|
4336
|
+
}
|
|
4337
|
+
};
|
|
4232
4338
|
/** True when the remote rejected the caller as unauthenticated/unauthorized:
|
|
4233
|
-
* an HTTP 401/403, or a Forbidden/Unauthorized GraphQL error.
|
|
4339
|
+
* an HTTP 401/403, or a Forbidden/Unauthorized GraphQL error.
|
|
4340
|
+
*
|
|
4341
|
+
* 403 and 401 only — NOT 404. The drive info endpoint answers a drive the
|
|
4342
|
+
* caller may not read with the same 404 it gives a drive that does not exist,
|
|
4343
|
+
* so that an unauthorized caller cannot enumerate drives by probing slugs.
|
|
4344
|
+
* That is deliberate, and it costs exactly this: a protected drive is
|
|
4345
|
+
* indistinguishable from a typo, and prompting for a login on every 404 would
|
|
4346
|
+
* fire on every mistyped URL. See the `WWW-Authenticate` note on the endpoint
|
|
4347
|
+
* for the signal that would let a client tell the two apart. */
|
|
4234
4348
|
function isDriveAuthError(error) {
|
|
4349
|
+
if (error instanceof DriveRequestError) return error.statusCode === 401 || error.statusCode === 403;
|
|
4235
4350
|
if (!(error instanceof GraphQLRequestError)) return false;
|
|
4236
4351
|
if (error.category === "http") return error.statusCode === 401 || error.statusCode === 403;
|
|
4237
4352
|
if (error.category === "graphql") return Object.values(DRIVE_AUTH_ERROR_MESSAGES).some((m) => error.message.includes(m));
|
|
@@ -5028,6 +5143,10 @@ var GqlRequestChannel = class {
|
|
|
5028
5143
|
if (this.isShutdown) return;
|
|
5029
5144
|
this.pollTimer.triggerNow();
|
|
5030
5145
|
}
|
|
5146
|
+
/** This channel polls a remote itself; it has no holder to hear from. */
|
|
5147
|
+
notePoll() {}
|
|
5148
|
+
/** No holder, so nothing this channel reports may strand one. */
|
|
5149
|
+
lastHolderPollUtcMs() {}
|
|
5031
5150
|
/**
|
|
5032
5151
|
* Initializes the channel by registering it on the remote server and starting polling.
|
|
5033
5152
|
*/
|
|
@@ -5611,6 +5730,7 @@ var GqlResponseChannel = class {
|
|
|
5611
5730
|
lastPersistedOutboxOrdinal = 0;
|
|
5612
5731
|
evictedOutboxFloor = Number.POSITIVE_INFINITY;
|
|
5613
5732
|
appliedOutboxOrdinal = 0;
|
|
5733
|
+
lastPollUtcMs = Date.now();
|
|
5614
5734
|
connectionState = "connecting";
|
|
5615
5735
|
connectionStateCallbacks = /* @__PURE__ */ new Set();
|
|
5616
5736
|
constructor(logger, channelId, remoteName, cursorStorage) {
|
|
@@ -5653,7 +5773,7 @@ var GqlResponseChannel = class {
|
|
|
5653
5773
|
return {
|
|
5654
5774
|
state: this.connectionState,
|
|
5655
5775
|
failureCount: 0,
|
|
5656
|
-
lastSuccessUtcMs:
|
|
5776
|
+
lastSuccessUtcMs: this.lastPollUtcMs,
|
|
5657
5777
|
lastFailureUtcMs: 0,
|
|
5658
5778
|
pushBlocked: false,
|
|
5659
5779
|
pushFailureCount: 0,
|
|
@@ -5669,6 +5789,13 @@ var GqlResponseChannel = class {
|
|
|
5669
5789
|
}
|
|
5670
5790
|
/** Response channels are push-driven; resolvers populate mailboxes directly. */
|
|
5671
5791
|
triggerPull() {}
|
|
5792
|
+
notePoll() {
|
|
5793
|
+
this.lastPollUtcMs = Date.now();
|
|
5794
|
+
}
|
|
5795
|
+
/** This channel is served: its holder's polls are the liveness it reports. */
|
|
5796
|
+
lastHolderPollUtcMs() {
|
|
5797
|
+
return this.lastPollUtcMs;
|
|
5798
|
+
}
|
|
5672
5799
|
async init() {
|
|
5673
5800
|
const cursors = await this.cursorStorage.list(this.remoteName);
|
|
5674
5801
|
const inboxOrdinal = cursors.find((c) => c.cursorType === "inbox")?.cursorOrdinal ?? 0;
|
|
@@ -6348,9 +6475,19 @@ var OutboxMode = /* @__PURE__ */ function(OutboxMode) {
|
|
|
6348
6475
|
const defaultSyncManagerConfig = {
|
|
6349
6476
|
maxDeadLettersPerRemote: 100,
|
|
6350
6477
|
maxInboxBatchSize: 32,
|
|
6351
|
-
maxHeldOperationsPerRemote: 1e4
|
|
6478
|
+
maxHeldOperationsPerRemote: 1e4,
|
|
6479
|
+
staleRemotePollWindowMs: 5 * 6e4
|
|
6352
6480
|
};
|
|
6353
6481
|
const PLAN_KEY_TO_JOB_UUID_CAP = 1e4;
|
|
6482
|
+
/**
|
|
6483
|
+
* Whether a channel failure says the caller could not authenticate or could not
|
|
6484
|
+
* reach the remote, rather than that the remote itself is misconfigured. The
|
|
6485
|
+
* remote record stays on disk for these so a retry after sign-in can re-add it.
|
|
6486
|
+
*/
|
|
6487
|
+
function isCredentialOrNetworkError(error) {
|
|
6488
|
+
if (isDriveAuthError(error)) return true;
|
|
6489
|
+
return error instanceof GraphQLRequestError && error.category === "network";
|
|
6490
|
+
}
|
|
6354
6491
|
/** Where a sync operation's run of ordinals begins. */
|
|
6355
6492
|
function firstOrdinalOf(syncOp) {
|
|
6356
6493
|
return syncOp.operations.length > 0 ? syncOp.operations[0].context.ordinal : 0;
|
|
@@ -6379,6 +6516,11 @@ var SyncManager = class {
|
|
|
6379
6516
|
backfillAbortControllers = /* @__PURE__ */ new Map();
|
|
6380
6517
|
planKeyToJobUuid = /* @__PURE__ */ new Map();
|
|
6381
6518
|
evictedOutboxFloors = /* @__PURE__ */ new Map();
|
|
6519
|
+
prunePending = /* @__PURE__ */ new Set();
|
|
6520
|
+
pruneChain = Promise.resolve();
|
|
6521
|
+
derivingOutboxes = 0;
|
|
6522
|
+
pruneDrainDeferred = false;
|
|
6523
|
+
removing = /* @__PURE__ */ new Set();
|
|
6382
6524
|
lastEnqueuedJobIdByKey = /* @__PURE__ */ new Map();
|
|
6383
6525
|
inboxChunkChain = Promise.resolve();
|
|
6384
6526
|
constructor(logger, remoteStorage, cursorStorage, deadLetterStorage, channelFactory, operationIndex, reactor, eventBus, driveContainerTypes, config = {}) {
|
|
@@ -6430,7 +6572,7 @@ var SyncManager = class {
|
|
|
6430
6572
|
await channel.init();
|
|
6431
6573
|
} catch (error) {
|
|
6432
6574
|
this.logger.error("Error initializing channel for remote (@name, @error)", record.name, error instanceof Error ? error.message : String(error));
|
|
6433
|
-
this.
|
|
6575
|
+
await this.dropRemoteAfterFailedInit(remote, false);
|
|
6434
6576
|
continue;
|
|
6435
6577
|
}
|
|
6436
6578
|
const outboxAckOrdinal = remote.channel.outbox.ackOrdinal;
|
|
@@ -6442,6 +6584,7 @@ var SyncManager = class {
|
|
|
6442
6584
|
this.logger.error("Backfill failed for remote @RemoteName: @Error", remote.meta.name, error instanceof Error ? error : new Error(String(error)));
|
|
6443
6585
|
}).finally(() => {
|
|
6444
6586
|
this.backfillAbortControllers.delete(record.name);
|
|
6587
|
+
this.drainPrunes();
|
|
6445
6588
|
});
|
|
6446
6589
|
}
|
|
6447
6590
|
}
|
|
@@ -6455,6 +6598,8 @@ var SyncManager = class {
|
|
|
6455
6598
|
this.backfillAbortControllers.clear();
|
|
6456
6599
|
this.planKeyToJobUuid.clear();
|
|
6457
6600
|
this.lastEnqueuedJobIdByKey.clear();
|
|
6601
|
+
this.prunePending.clear();
|
|
6602
|
+
this.pruneDrainDeferred = false;
|
|
6458
6603
|
this.batchAggregator.clear();
|
|
6459
6604
|
if (this.eventUnsubscribe) {
|
|
6460
6605
|
this.eventUnsubscribe();
|
|
@@ -6548,8 +6693,7 @@ var SyncManager = class {
|
|
|
6548
6693
|
try {
|
|
6549
6694
|
await channel.init();
|
|
6550
6695
|
} catch (error) {
|
|
6551
|
-
this.
|
|
6552
|
-
await this.remoteStorage.remove(name);
|
|
6696
|
+
await this.dropRemoteAfterFailedInit(remote, !isCredentialOrNetworkError(error));
|
|
6553
6697
|
throw error;
|
|
6554
6698
|
}
|
|
6555
6699
|
const backfillController = new AbortController();
|
|
@@ -6559,6 +6703,7 @@ var SyncManager = class {
|
|
|
6559
6703
|
this.logger.error("Backfill failed for remote @RemoteName: @Error", remote.meta.name, error instanceof Error ? error : new Error(String(error)));
|
|
6560
6704
|
}).finally(() => {
|
|
6561
6705
|
this.backfillAbortControllers.delete(name);
|
|
6706
|
+
this.drainPrunes();
|
|
6562
6707
|
});
|
|
6563
6708
|
return remote;
|
|
6564
6709
|
}
|
|
@@ -6570,21 +6715,62 @@ var SyncManager = class {
|
|
|
6570
6715
|
async remove(name) {
|
|
6571
6716
|
const remote = this.remotes.get(name);
|
|
6572
6717
|
if (!remote) throw new Error(`Remote with name '${name}' does not exist`);
|
|
6718
|
+
this.removing.add(name);
|
|
6719
|
+
try {
|
|
6720
|
+
await this.teardownRemoteResources(remote);
|
|
6721
|
+
await this.remoteStorage.remove(name);
|
|
6722
|
+
await this.cursorStorage.remove(name);
|
|
6723
|
+
} finally {
|
|
6724
|
+
this.remotes.delete(name);
|
|
6725
|
+
this.removing.delete(name);
|
|
6726
|
+
}
|
|
6727
|
+
}
|
|
6728
|
+
/**
|
|
6729
|
+
* Drops a remote whose channel.init() rejected, optionally removing its
|
|
6730
|
+
* stored record. Ordered like remove(): the registry slot is released last,
|
|
6731
|
+
* so a concurrent add of the same name cannot slip in and have its record
|
|
6732
|
+
* deleted by the removal below. A failure to tear down must not replace the
|
|
6733
|
+
* init error the caller has to classify.
|
|
6734
|
+
*/
|
|
6735
|
+
async dropRemoteAfterFailedInit(remote, removeStorageRecord) {
|
|
6736
|
+
const name = remote.meta.name;
|
|
6737
|
+
this.removing.add(name);
|
|
6738
|
+
try {
|
|
6739
|
+
await this.teardownRemoteResources(remote);
|
|
6740
|
+
if (removeStorageRecord) await this.remoteStorage.remove(name);
|
|
6741
|
+
} catch (error) {
|
|
6742
|
+
this.logger.error("Error tearing down remote after failed init (@name, @error)", name, error instanceof Error ? error.message : String(error));
|
|
6743
|
+
} finally {
|
|
6744
|
+
this.remotes.delete(name);
|
|
6745
|
+
this.removing.delete(name);
|
|
6746
|
+
}
|
|
6747
|
+
}
|
|
6748
|
+
/**
|
|
6749
|
+
* Releases everything wiring a remote up holds: the in-flight backfill, the
|
|
6750
|
+
* channel, the status tracker entry and the connection-state subscription.
|
|
6751
|
+
* The registry slot is deliberately NOT released here -- the caller holds it
|
|
6752
|
+
* until any storage removal has finished, so a concurrent add of the same
|
|
6753
|
+
* name is refused for the whole teardown.
|
|
6754
|
+
*/
|
|
6755
|
+
async teardownRemoteResources(remote) {
|
|
6756
|
+
const name = remote.meta.name;
|
|
6573
6757
|
const backfillController = this.backfillAbortControllers.get(name);
|
|
6574
6758
|
if (backfillController) {
|
|
6575
6759
|
backfillController.abort();
|
|
6576
6760
|
this.backfillAbortControllers.delete(name);
|
|
6577
6761
|
}
|
|
6578
|
-
|
|
6579
|
-
|
|
6580
|
-
|
|
6581
|
-
|
|
6582
|
-
|
|
6583
|
-
|
|
6584
|
-
|
|
6585
|
-
|
|
6762
|
+
try {
|
|
6763
|
+
await remote.channel.shutdown();
|
|
6764
|
+
} finally {
|
|
6765
|
+
this.syncStatusTracker.untrackRemote(name);
|
|
6766
|
+
const unsub = this.connectionStateUnsubscribes.get(name);
|
|
6767
|
+
if (unsub) {
|
|
6768
|
+
unsub();
|
|
6769
|
+
this.connectionStateUnsubscribes.delete(name);
|
|
6770
|
+
}
|
|
6771
|
+
this.evictedOutboxFloors.delete(name);
|
|
6772
|
+
this.prunePending.delete(name);
|
|
6586
6773
|
}
|
|
6587
|
-
this.remotes.delete(name);
|
|
6588
6774
|
}
|
|
6589
6775
|
list() {
|
|
6590
6776
|
return Array.from(this.remotes.values());
|
|
@@ -6679,7 +6865,7 @@ var SyncManager = class {
|
|
|
6679
6865
|
this.logger.debug("Loaded @count persisted dead letters for remote @name", records.length, remote.meta.name);
|
|
6680
6866
|
}
|
|
6681
6867
|
getRemotesForCollection(collectionId) {
|
|
6682
|
-
return Array.from(this.remotes.values()).filter((remote) => remote.meta.collectionId.key === collectionId);
|
|
6868
|
+
return Array.from(this.remotes.values()).filter((remote) => remote.meta.collectionId.key === collectionId && !this.removing.has(remote.meta.name));
|
|
6683
6869
|
}
|
|
6684
6870
|
async processCompleteBatch(batch) {
|
|
6685
6871
|
if (this.isShutdown) return;
|
|
@@ -6690,7 +6876,11 @@ var SyncManager = class {
|
|
|
6690
6876
|
for (const remote of remotes) if (!affectedRemotes.includes(remote)) affectedRemotes.push(remote);
|
|
6691
6877
|
}
|
|
6692
6878
|
for (const remote of affectedRemotes) trimMailboxFromBatch(remote.channel.inbox, batch);
|
|
6693
|
-
for (const remote of affectedRemotes)
|
|
6879
|
+
for (const remote of affectedRemotes) {
|
|
6880
|
+
if (!this.remotes.has(remote.meta.name) || this.removing.has(remote.meta.name)) continue;
|
|
6881
|
+
await this.updateOutbox(remote, remote.channel.outbox.latestOrdinal, OutboxMode.BatchTriggered);
|
|
6882
|
+
}
|
|
6883
|
+
await this.drainPrunes();
|
|
6694
6884
|
}
|
|
6695
6885
|
handleInboxAdded(remote, syncOps) {
|
|
6696
6886
|
if (this.isShutdown) return;
|
|
@@ -6891,15 +7081,95 @@ var SyncManager = class {
|
|
|
6891
7081
|
const floor = firstOrdinalOf(evicted[0]);
|
|
6892
7082
|
const known = this.evictedOutboxFloors.get(remote.meta.name);
|
|
6893
7083
|
this.evictedOutboxFloors.set(remote.meta.name, known === void 0 ? floor : Math.min(known, floor));
|
|
6894
|
-
this.
|
|
7084
|
+
const staleMs = this.stalePollAgeMs(remote);
|
|
7085
|
+
if (staleMs !== void 0) {
|
|
7086
|
+
let held = kept;
|
|
7087
|
+
for (const syncOp of evicted) held += syncOp.operations.length;
|
|
7088
|
+
if (!this.prunePending.has(remote.meta.name)) {
|
|
7089
|
+
this.prunePending.add(remote.meta.name);
|
|
7090
|
+
this.logger.warn("Outbox for @RemoteName (@Collection) is past its bound of @Cap operations holding @Held, and it has not been polled for @StaleMs ms; marking the channel for removal once this derivation ends", remote.meta.name, remote.meta.collectionId.key, cap, held, staleMs);
|
|
7091
|
+
}
|
|
7092
|
+
} else this.logger.warn("Outbox for @RemoteName is past its bound of @Cap operations; evicting @Count entries from ordinal @Floor, to be derived again once it drains", remote.meta.name, cap, evicted.length, floor);
|
|
6895
7093
|
remote.channel.outbox.remove(...evicted);
|
|
6896
7094
|
}
|
|
7095
|
+
/**
|
|
7096
|
+
* How long a served remote's holder has been silent, if past the window.
|
|
7097
|
+
*
|
|
7098
|
+
* The channel is asked, rather than its config inspected: SyncManager serves
|
|
7099
|
+
* and subscribes with the same interface, both kinds report lastSuccessUtcMs,
|
|
7100
|
+
* and the caller-supplied channelConfig.type is a free-form string the
|
|
7101
|
+
* factories do not read -- so neither could tell a dead served channel from a
|
|
7102
|
+
* client whose switchboard is merely unreachable. Only a channel that claims
|
|
7103
|
+
* a holder by reporting when it last heard from one can be removed for that
|
|
7104
|
+
* holder's silence; a channel that reports nothing (or 0) is never pruned.
|
|
7105
|
+
*/
|
|
7106
|
+
stalePollAgeMs(remote) {
|
|
7107
|
+
const last = remote.channel.lastHolderPollUtcMs();
|
|
7108
|
+
if (last === void 0 || last <= 0) return;
|
|
7109
|
+
const age = Date.now() - last;
|
|
7110
|
+
return age >= this.config.staleRemotePollWindowMs ? age : void 0;
|
|
7111
|
+
}
|
|
7112
|
+
/**
|
|
7113
|
+
* Removes the remotes marked stale during eviction.
|
|
7114
|
+
*
|
|
7115
|
+
* Removing a remote that a derivation is still iterating would pull its
|
|
7116
|
+
* mailboxes out from under it, so a drain that arrives during one is deferred
|
|
7117
|
+
* rather than run; the last derivation to finish re-arms it.
|
|
7118
|
+
*/
|
|
7119
|
+
drainPrunes() {
|
|
7120
|
+
if (this.derivingOutboxes > 0) {
|
|
7121
|
+
this.pruneDrainDeferred = true;
|
|
7122
|
+
return Promise.resolve();
|
|
7123
|
+
}
|
|
7124
|
+
const next = this.pruneChain.then(async () => {
|
|
7125
|
+
if (this.isShutdown) return;
|
|
7126
|
+
for (const name of [...this.prunePending]) {
|
|
7127
|
+
if (this.derivingOutboxes > 0) {
|
|
7128
|
+
this.pruneDrainDeferred = true;
|
|
7129
|
+
return;
|
|
7130
|
+
}
|
|
7131
|
+
this.prunePending.delete(name);
|
|
7132
|
+
const remote = this.remotes.get(name);
|
|
7133
|
+
if (!remote) continue;
|
|
7134
|
+
if (this.stalePollAgeMs(remote) === void 0) {
|
|
7135
|
+
this.logger.info("Stale removal of @name revoked: its holder polled while the outbox was being derived", name);
|
|
7136
|
+
continue;
|
|
7137
|
+
}
|
|
7138
|
+
try {
|
|
7139
|
+
await this.remove(name);
|
|
7140
|
+
} catch (error) {
|
|
7141
|
+
this.logger.error("Failed to remove stale remote (@name, @error)", name, error instanceof Error ? error.message : String(error));
|
|
7142
|
+
}
|
|
7143
|
+
}
|
|
7144
|
+
});
|
|
7145
|
+
this.pruneChain = next.catch(() => {});
|
|
7146
|
+
return next;
|
|
7147
|
+
}
|
|
6897
7148
|
outboxOperationCount(remote) {
|
|
6898
7149
|
let count = 0;
|
|
6899
7150
|
for (const syncOp of remote.channel.outbox.items) count += syncOp.operations.length;
|
|
6900
7151
|
return count;
|
|
6901
7152
|
}
|
|
7153
|
+
/**
|
|
7154
|
+
* Derives this remote's outbox, holding off prunes for the duration.
|
|
7155
|
+
*
|
|
7156
|
+
* A backfill elsewhere can finish at any await in here and drain the prunes
|
|
7157
|
+
* it marked; the count is what keeps that drain from removing the remote this
|
|
7158
|
+
* call is still adding to.
|
|
7159
|
+
*/
|
|
6902
7160
|
async updateOutbox(remote, ackOrdinal, mode = OutboxMode.Backfill, signal) {
|
|
7161
|
+
this.derivingOutboxes++;
|
|
7162
|
+
try {
|
|
7163
|
+
await this.deriveOutbox(remote, ackOrdinal, mode, signal);
|
|
7164
|
+
} finally {
|
|
7165
|
+
this.derivingOutboxes--;
|
|
7166
|
+
if (this.derivingOutboxes === 0 && this.pruneDrainDeferred) {
|
|
7167
|
+
this.pruneDrainDeferred = false;
|
|
7168
|
+
this.drainPrunes();
|
|
7169
|
+
}
|
|
7170
|
+
}
|
|
7171
|
+
}
|
|
7172
|
+
async deriveOutbox(remote, ackOrdinal, mode, signal) {
|
|
6903
7173
|
const composedSignal = signal ? AbortSignal.any([signal, this.abortController.signal]) : this.abortController.signal;
|
|
6904
7174
|
const startOrdinal = this.refillOrdinal(remote, ackOrdinal);
|
|
6905
7175
|
let maxOrdinal = startOrdinal;
|
|
@@ -7001,6 +7271,10 @@ var SyncBuilder = class {
|
|
|
7001
7271
|
this.config.maxHeldOperationsPerRemote = limit;
|
|
7002
7272
|
return this;
|
|
7003
7273
|
}
|
|
7274
|
+
withStaleRemotePollWindowMs(windowMs) {
|
|
7275
|
+
this.config.staleRemotePollWindowMs = windowMs;
|
|
7276
|
+
return this;
|
|
7277
|
+
}
|
|
7004
7278
|
build(reactor, logger, operationIndex, eventBus, db, driveContainerTypes) {
|
|
7005
7279
|
return this.buildModule(reactor, logger, operationIndex, eventBus, db, driveContainerTypes).syncManager;
|
|
7006
7280
|
}
|
|
@@ -7166,6 +7440,16 @@ var Reactor = class {
|
|
|
7166
7440
|
throwIfAborted(signal, () => new AbortError());
|
|
7167
7441
|
return relationships.results.map((rel) => rel.sourceId);
|
|
7168
7442
|
}
|
|
7443
|
+
async getOutgoingRelationshipEdges(sourceId, relationshipType, paging, consistencyToken, signal) {
|
|
7444
|
+
const relationships = await this.documentIndexer.getOutgoing(sourceId, relationshipType ? [relationshipType] : void 0, paging, consistencyToken, signal);
|
|
7445
|
+
throwIfAborted(signal, () => new AbortError());
|
|
7446
|
+
return relationships;
|
|
7447
|
+
}
|
|
7448
|
+
async getIncomingRelationshipEdges(targetId, relationshipType, paging, consistencyToken, signal) {
|
|
7449
|
+
const relationships = await this.documentIndexer.getIncoming(targetId, relationshipType ? [relationshipType] : void 0, paging, consistencyToken, signal);
|
|
7450
|
+
throwIfAborted(signal, () => new AbortError());
|
|
7451
|
+
return relationships;
|
|
7452
|
+
}
|
|
7169
7453
|
async getOperations(documentId, view, filter, paging, consistencyToken, signal) {
|
|
7170
7454
|
this.logger.verbose("getOperations(@documentId, @view, @filter, @paging)", documentId, view, filter, paging);
|
|
7171
7455
|
const branch = view?.branch || "main";
|
|
@@ -7528,10 +7812,17 @@ var Reactor = class {
|
|
|
7528
7812
|
}
|
|
7529
7813
|
return { jobs: Object.fromEntries(jobInfos) };
|
|
7530
7814
|
}
|
|
7531
|
-
async addRelationship(sourceId, targetId, relationshipType, branch = "main", signer, signal) {
|
|
7532
|
-
this.logger.verbose("addRelationship(@sourceId, @targetId, @relationshipType, @branch)", sourceId, targetId, relationshipType, branch);
|
|
7815
|
+
async addRelationship(sourceId, targetId, relationshipType, metadata, branch = "main", signer, signal) {
|
|
7816
|
+
this.logger.verbose("addRelationship(@sourceId, @targetId, @relationshipType, @metadata, @branch)", sourceId, targetId, relationshipType, metadata, branch);
|
|
7533
7817
|
throwIfAborted(signal, () => new AbortError());
|
|
7534
|
-
let actions = [addRelationshipAction(sourceId, targetId, relationshipType)];
|
|
7818
|
+
let actions = [addRelationshipAction(sourceId, targetId, relationshipType, metadata)];
|
|
7819
|
+
if (signer) actions = await signActions(actions, signer, signal);
|
|
7820
|
+
return await this.execute(sourceId, branch, actions, signal);
|
|
7821
|
+
}
|
|
7822
|
+
async updateRelationship(sourceId, targetId, relationshipType, metadata, branch = "main", signer, signal) {
|
|
7823
|
+
this.logger.verbose("updateRelationship(@sourceId, @targetId, @relationshipType, @metadata, @branch)", sourceId, targetId, relationshipType, metadata, branch);
|
|
7824
|
+
throwIfAborted(signal, () => new AbortError());
|
|
7825
|
+
let actions = [updateRelationshipAction(sourceId, targetId, relationshipType, metadata)];
|
|
7535
7826
|
if (signer) actions = await signActions(actions, signer, signal);
|
|
7536
7827
|
return await this.execute(sourceId, branch, actions, signal);
|
|
7537
7828
|
}
|
|
@@ -7903,19 +8194,31 @@ var ReactorBuilder = class {
|
|
|
7903
8194
|
} else executorManager = new SimpleJobExecutorManager(() => new SimpleJobExecutor(this.logger, documentModelRegistry, operationStore, eventBus, writeCache, operationIndex, documentMetaCache, collectionMembershipCache, this.driveContainerTypes, this.executorConfig, this.signatureVerifier, executionScope), eventBus, queue, jobTracker, this.logger, resolver, this.executorConfig.jobTimeoutMs, this.executorConfig.deferredJobTtlMs);
|
|
7904
8195
|
await executorManager.start(executorStartCount);
|
|
7905
8196
|
const callerReadModels = Array.from(new Set([...this.readModels]));
|
|
8197
|
+
const degradedComponents = [];
|
|
8198
|
+
const startDegraded = (component, error) => {
|
|
8199
|
+
degradedComponents.push({
|
|
8200
|
+
component,
|
|
8201
|
+
error: error instanceof Error ? error : new Error(String(error))
|
|
8202
|
+
});
|
|
8203
|
+
this.logger?.error("Reactor component started degraded: @component", component, error);
|
|
8204
|
+
};
|
|
8205
|
+
const readModelIndexing = {
|
|
8206
|
+
commitChunkSize: 50,
|
|
8207
|
+
yieldDeadlineMs: this.executorConfig.yieldDeadlineMs ?? 50
|
|
8208
|
+
};
|
|
7906
8209
|
const documentViewConsistencyTracker = new ConsistencyTracker();
|
|
7907
|
-
const documentView = new KyselyDocumentView(database, operationStore, operationIndex, writeCache, documentViewConsistencyTracker, featureFlags.documentDecisions);
|
|
8210
|
+
const documentView = new KyselyDocumentView(database, operationStore, operationIndex, writeCache, documentViewConsistencyTracker, featureFlags.documentDecisions ? DeletedDocumentRead.StateAtDeletion : DeletedDocumentRead.NotFound, readModelIndexing);
|
|
7908
8211
|
try {
|
|
7909
8212
|
await documentView.init();
|
|
7910
8213
|
} catch (error) {
|
|
7911
|
-
|
|
8214
|
+
startDegraded("document view", error);
|
|
7912
8215
|
}
|
|
7913
8216
|
const documentIndexerConsistencyTracker = new ConsistencyTracker();
|
|
7914
|
-
const documentIndexer = new KyselyDocumentIndexer(database, operationIndex, writeCache, documentIndexerConsistencyTracker);
|
|
8217
|
+
const documentIndexer = new KyselyDocumentIndexer(database, operationIndex, writeCache, documentIndexerConsistencyTracker, readModelIndexing);
|
|
7915
8218
|
try {
|
|
7916
8219
|
await documentIndexer.init();
|
|
7917
8220
|
} catch (error) {
|
|
7918
|
-
|
|
8221
|
+
startDegraded("document indexer", error);
|
|
7919
8222
|
}
|
|
7920
8223
|
const subscriptionManager = new ReactorSubscriptionManager(new DefaultSubscriptionErrorHandler());
|
|
7921
8224
|
const subscriptionNotificationReadModel = new SubscriptionNotificationReadModel(subscriptionManager, documentView);
|
|
@@ -7924,9 +8227,9 @@ var ReactorBuilder = class {
|
|
|
7924
8227
|
try {
|
|
7925
8228
|
await processorManager.init();
|
|
7926
8229
|
} catch (error) {
|
|
7927
|
-
|
|
8230
|
+
startDegraded("processor manager", error);
|
|
7928
8231
|
}
|
|
7929
|
-
for (const factory of this.readModelFactories) {
|
|
8232
|
+
for (const [index, factory] of this.readModelFactories.entries()) try {
|
|
7930
8233
|
const readModel = await factory({
|
|
7931
8234
|
documentModelRegistry,
|
|
7932
8235
|
operationIndex,
|
|
@@ -7934,6 +8237,8 @@ var ReactorBuilder = class {
|
|
|
7934
8237
|
processorManagerConsistencyTracker
|
|
7935
8238
|
});
|
|
7936
8239
|
callerReadModels.push(readModel);
|
|
8240
|
+
} catch (error) {
|
|
8241
|
+
startDegraded(`read model ${index}${factory.name ? ` (${factory.name})` : ""}`, error);
|
|
7937
8242
|
}
|
|
7938
8243
|
const readModelInstances = [
|
|
7939
8244
|
...callerReadModels,
|
|
@@ -7952,9 +8257,9 @@ var ReactorBuilder = class {
|
|
|
7952
8257
|
processorManager,
|
|
7953
8258
|
documentView,
|
|
7954
8259
|
documentIndexer,
|
|
7955
|
-
createProjectionShardManager: (config) => this.createProjectionShardManager(config, eventBus, hostTrackers, false),
|
|
8260
|
+
createProjectionShardManager: (config) => this.createProjectionShardManager(config, eventBus, hostTrackers, readModelIndexing, false),
|
|
7956
8261
|
registerShutdownHook: (hook) => this.shutdownHooks.push(hook)
|
|
7957
|
-
}) : this.projectionShardConfig ? await this.createProjectionShardManager(this.projectionShardConfig, eventBus, hostTrackers, true) : new ReadModelCoordinator(eventBus, readModelInstances, [subscriptionNotificationReadModel, processorManager]);
|
|
8262
|
+
}) : this.projectionShardConfig ? await this.createProjectionShardManager(this.projectionShardConfig, eventBus, hostTrackers, readModelIndexing, true) : new ReadModelCoordinator(eventBus, readModelInstances, [subscriptionNotificationReadModel, processorManager]);
|
|
7958
8263
|
const reactor = new Reactor(this.logger, documentModelRegistry, queue, jobTracker, readModelCoordinator, this.features, documentView, documentIndexer, operationStore, eventBus, executorManager);
|
|
7959
8264
|
let syncModule = void 0;
|
|
7960
8265
|
if (this.channelScheme) {
|
|
@@ -7993,8 +8298,10 @@ var ReactorBuilder = class {
|
|
|
7993
8298
|
syncModule,
|
|
7994
8299
|
reactor,
|
|
7995
8300
|
groupReevaluationTrigger,
|
|
7996
|
-
pools: this.instrumentedPools
|
|
8301
|
+
pools: this.instrumentedPools,
|
|
8302
|
+
degradedComponents
|
|
7997
8303
|
};
|
|
8304
|
+
if (degradedComponents.length > 0) this.logger.warn("Reactor started with @count degraded component(s): @components", degradedComponents.length, degradedComponents.map(({ component }) => component).join(", "));
|
|
7998
8305
|
if (this.signalHandlersEnabled) this.attachSignalHandlers(module);
|
|
7999
8306
|
return module;
|
|
8000
8307
|
}
|
|
@@ -8023,11 +8330,15 @@ var ReactorBuilder = class {
|
|
|
8023
8330
|
* models never index an operation, so the manager advances these from the
|
|
8024
8331
|
* shards' relayed indexing reports; without them every read carrying a
|
|
8025
8332
|
* consistency token waits forever.
|
|
8333
|
+
* @param indexing The chunking bounds the host's own read models index
|
|
8334
|
+
* under. The shards' read models are built inside the worker, so without
|
|
8335
|
+
* this they would fall back to the library default and a host that tuned
|
|
8336
|
+
* the cadence would silently get it on the in-process path only.
|
|
8026
8337
|
* @param registerShutdownHook Whether the builder owns `manager.shutdown()`
|
|
8027
8338
|
* at signal time. False for the coordinator-factory path, whose factory
|
|
8028
8339
|
* registers its own hook so host chains drain before the worker stops.
|
|
8029
8340
|
*/
|
|
8030
|
-
async createProjectionShardManager(config, eventBus, consistencyTrackers, registerShutdownHook) {
|
|
8341
|
+
async createProjectionShardManager(config, eventBus, consistencyTrackers, indexing, registerShutdownHook) {
|
|
8031
8342
|
const parentDb = this.resolveReactorDbConfig();
|
|
8032
8343
|
const baseDb = config.db ?? parentDb;
|
|
8033
8344
|
const caller = this.projectionShardConfig !== void 0 ? "withProjectionShards" : "withReadModelCoordinatorFactory";
|
|
@@ -8053,13 +8364,14 @@ var ReactorBuilder = class {
|
|
|
8053
8364
|
poolInstrumentations.push(forwarder);
|
|
8054
8365
|
this.instrumentedPools.push(forwarder);
|
|
8055
8366
|
}
|
|
8056
|
-
const { ProjectionShardManager } = await import("./projection-shard-manager-
|
|
8367
|
+
const { ProjectionShardManager } = await import("./projection-shard-manager-CUo4bF4O.js");
|
|
8057
8368
|
const manager = new ProjectionShardManager({
|
|
8058
8369
|
shardCount: config.shardCount,
|
|
8059
8370
|
db,
|
|
8060
8371
|
models,
|
|
8061
8372
|
preReadyKinds: config.preReadyKinds,
|
|
8062
8373
|
postReadyKinds: config.postReadyKinds,
|
|
8374
|
+
indexing,
|
|
8063
8375
|
factory,
|
|
8064
8376
|
logger: this.logger,
|
|
8065
8377
|
hostBus: eventBus,
|
|
@@ -8098,7 +8410,7 @@ var ReactorBuilder = class {
|
|
|
8098
8410
|
const [{ WorkerHandle }, { createThreadTransport }, { workerEntryPath }] = await Promise.all([
|
|
8099
8411
|
import("./worker-handle-CrERzl8s.js"),
|
|
8100
8412
|
import("./transport-ByGviWdZ.js"),
|
|
8101
|
-
import("./worker-
|
|
8413
|
+
import("./worker-g03lNM42.js").then((n) => n.n)
|
|
8102
8414
|
]);
|
|
8103
8415
|
const poolConfig = {
|
|
8104
8416
|
enabled: true,
|
|
@@ -8792,6 +9104,6 @@ var DocumentIntegrityService = class {
|
|
|
8792
9104
|
}
|
|
8793
9105
|
};
|
|
8794
9106
|
//#endregion
|
|
8795
|
-
export { ALWAYS_READABLE_SCOPES, APPEND_CONDITION_FAILED_PREFIX, AppendConditionFailedError, AuthEnforcementDisabledError, BareReadGate, BaseReadModel, ChannelError, ChannelErrorSource, ChannelScheme, ConsistencyTracker, DEFAULT_DRIVE_CONTAINER_TYPES, DOCUMENT_INDEXER_READ_MODEL, DOCUMENT_VIEW_READ_MODEL, DRIVE_AUTH_ERROR_MESSAGES, DefaultSubscriptionErrorHandler, DocumentAlreadyExistsError, DocumentChangeType, DocumentExistence, DocumentIntegrityService, DocumentModelRegistry, DocumentModelResolver, DriveClient, DriveCollectionId, DuplicateManifestError, DuplicateModuleError, DuplicateOperationError, EventBus, EventBusAggregateError, GqlRequestChannel, GqlRequestChannelFactory, GqlResponseChannel, GqlResponseChannelFactory, HybridProjectionCoordinator, SimpleJobExecutor as InMemoryJobExecutor, SimpleJobExecutor, InMemoryJobTracker, InMemoryQueue, IntervalPollTimer, InvalidModuleError, JobAwaiter, JobExecutorEventTypes, JobStatus, KyselyDocumentIndexer, KyselyDocumentView, KyselyKeyframeStore, KyselyOperationStore, KyselySyncCursorStorage, KyselySyncRemoteStorage, KyselyWriteCache, Mailbox, ModelReadGate, ModuleNotFoundError, NullDocumentModelResolver, OptimisticLockError, PollBehavior, PollingChannelError, ProcessorManager, PropagationMode, QueueEventTypes, REACTOR_SCHEMA, RECOVERABLE_GRAPHQL_ERROR_CODES, Reactor, ReactorBuilder, ReactorClient, ReactorClientBuilder, ReactorEventTypes, ReactorSubscriptionManager, ReadModelCoordinator, RelationalDbProcessor, RelationshipChangeType, RetryAccounting, RevisionMismatchError, SeededStateReader, SimpleJobExecutorManager, SyncBuilder, SyncEventTypes, SyncOperation, SyncOperationAggregateError, SyncOperationStatus, SyncScopeGate, SyncStatus, SyncStatusTracker, addRelationshipAction, authDecisionModel, batchOperationsByDocument, buildDecisionModel, classifyJobFailure, consolidateSyncOperations, createDocumentAction, createForwardingLogger, createHybridProjectionCoordinatorFactory, createMutableShutdownStatus, createReactorHostModuleBase, createRelationalDb, decideAtHead, deleteDocumentAction, documentActions, documentDecisionModel, driveIdFromUrl, envelopesToSyncOperations, errorToInfo, firstOutOfOrderPair, getMigrationStatus, instrumentPgPool, isDriveAuthError, isRecoverableGraphQLError, makeConsistencyKey, parseDriveUrl, parsePagingOptions, quarantinesDocument, readDecisionModel, removeRelationshipAction, runMigrations, sanitizeArg, selectDecisionModel, supportsLiveReadModelRegistration, syncOperationErrorType, trimMailboxFromAckOrdinal, updateRelationshipAction, upgradeDocumentAction, workerEntryPath };
|
|
9107
|
+
export { ALWAYS_READABLE_SCOPES, APPEND_CONDITION_FAILED_PREFIX, AppendConditionFailedError, AuthEnforcementDisabledError, BareReadGate, BaseReadModel, ChannelError, ChannelErrorSource, ChannelScheme, ConsistencyTracker, DEFAULT_COMMIT_CHUNK_SIZE, DEFAULT_DRIVE_CONTAINER_TYPES, DEFAULT_READ_MODEL_YIELD_DEADLINE_MS, DOCUMENT_INDEXER_READ_MODEL, DOCUMENT_VIEW_READ_MODEL, DRIVE_AUTH_ERROR_MESSAGES, DefaultSubscriptionErrorHandler, DeletedDocumentRead, DocumentAlreadyExistsError, DocumentChangeType, DocumentExistence, DocumentIntegrityService, DocumentModelRegistry, DocumentModelResolver, DriveClient, DriveCollectionId, DriveRequestError, DuplicateManifestError, DuplicateModuleError, DuplicateOperationError, EventBus, EventBusAggregateError, GqlRequestChannel, GqlRequestChannelFactory, GqlResponseChannel, GqlResponseChannelFactory, HybridProjectionCoordinator, SimpleJobExecutor as InMemoryJobExecutor, SimpleJobExecutor, InMemoryJobTracker, InMemoryQueue, IntervalPollTimer, InvalidModuleError, JobAwaiter, JobExecutorEventTypes, JobStatus, KyselyDocumentIndexer, KyselyDocumentView, KyselyKeyframeStore, KyselyOperationStore, KyselySyncCursorStorage, KyselySyncRemoteStorage, KyselyWriteCache, Mailbox, ModelReadGate, ModuleNotFoundError, NullDocumentModelResolver, OptimisticLockError, PollBehavior, PollingChannelError, ProcessorManager, PropagationMode, QueueEventTypes, REACTOR_SCHEMA, RECOVERABLE_GRAPHQL_ERROR_CODES, Reactor, ReactorBuilder, ReactorClient, ReactorClientBuilder, ReactorEventTypes, ReactorSubscriptionManager, ReadModelCoordinator, RelationalDbProcessor, RelationshipChangeType, RelationshipNotFoundError, RetryAccounting, RevisionMismatchError, SeededStateReader, SimpleJobExecutorManager, SyncBuilder, SyncEventTypes, SyncOperation, SyncOperationAggregateError, SyncOperationStatus, SyncScopeGate, SyncStatus, SyncStatusTracker, addRelationshipAction, authDecisionModel, batchOperationsByDocument, buildDecisionModel, classifyJobFailure, consolidateSyncOperations, createDocumentAction, createForwardingLogger, createHybridProjectionCoordinatorFactory, createMutableShutdownStatus, createReactorHostModuleBase, createRelationalDb, decideAtHead, defaultReadModelIndexingConfig, deleteDocumentAction, documentActions, documentDecisionModel, driveIdFromUrl, envelopesToSyncOperations, errorToInfo, firstOutOfOrderPair, getMigrationStatus, instrumentPgPool, isDriveAuthError, isRecoverableGraphQLError, makeConsistencyKey, parseDriveUrl, parsePagingOptions, quarantinesDocument, readDecisionModel, removeRelationshipAction, runMigrations, sanitizeArg, selectDecisionModel, supportsLiveReadModelRegistration, syncOperationErrorType, trimMailboxFromAckOrdinal, unchunkedReadModelIndexingConfig, updateRelationshipAction, upgradeDocumentAction, workerEntryPath };
|
|
8796
9108
|
|
|
8797
9109
|
//# sourceMappingURL=index.js.map
|