@powerhousedao/reactor 6.2.3-dev.50 → 6.2.3-dev.52

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.
Files changed (30) hide show
  1. package/dist/{document-indexer-DwAMDVRI.js → document-indexer-3fkVWCaN.js} +26 -14
  2. package/dist/document-indexer-3fkVWCaN.js.map +1 -0
  3. package/dist/{drive-container-types-Ci1pdv4X.js → drive-container-types-BAfFzBOf.js} +133 -7
  4. package/dist/drive-container-types-BAfFzBOf.js.map +1 -0
  5. package/dist/entry.js +2 -2
  6. package/dist/{forwarding-logger-ByfBFgMS.js → forwarding-logger-B9LLXItm.js} +111 -79
  7. package/dist/forwarding-logger-B9LLXItm.js.map +1 -0
  8. package/dist/{index-CNhT69De.d.ts → index-dDm0fUBT.d.ts} +518 -68
  9. package/dist/index-dDm0fUBT.d.ts.map +1 -0
  10. package/dist/index.d.ts +1040 -138
  11. package/dist/index.d.ts.map +1 -1
  12. package/dist/index.js +3110 -776
  13. package/dist/index.js.map +1 -1
  14. package/dist/projection-entry.js +2 -2
  15. package/dist/{projection-shard-manager-CQ5FwQmv.js → projection-shard-manager-Drl9w8Eo.js} +3 -13
  16. package/dist/projection-shard-manager-Drl9w8Eo.js.map +1 -0
  17. package/dist/rpc.d.ts +32 -6
  18. package/dist/rpc.d.ts.map +1 -1
  19. package/dist/rpc.js +8 -4
  20. package/dist/rpc.js.map +1 -1
  21. package/dist/{worker-handle-CN2-oRv-.js → worker-handle-Nso8i5eU.js} +2 -2
  22. package/dist/{worker-handle-CN2-oRv-.js.map → worker-handle-Nso8i5eU.js.map} +1 -1
  23. package/dist/worker-pool-router-BROOEQSE.js +35 -0
  24. package/dist/worker-pool-router-BROOEQSE.js.map +1 -0
  25. package/package.json +3 -3
  26. package/dist/document-indexer-DwAMDVRI.js.map +0 -1
  27. package/dist/drive-container-types-Ci1pdv4X.js.map +0 -1
  28. package/dist/forwarding-logger-ByfBFgMS.js.map +0 -1
  29. package/dist/index-CNhT69De.d.ts.map +0 -1
  30. package/dist/projection-shard-manager-CQ5FwQmv.js.map +0 -1
@@ -1,6 +1,57 @@
1
- import { A as GATED_DOCUMENT_ACTIONS, B as createOperation, Et as yieldToMain, F as buildErrorResult, G as relationshipTarget, H as isGenesisOperation, I as buildSuccessResult, J as targetDocumentId, K as submittedActionIds, M as TouchedStreams, N as applyDeleteDocumentAction$1, O as DOCUMENT_SCOPE_ACTIONS, P as applyUpgradeDocumentAction$1, R as createDocumentFromAction, St as ReactorEventTypes, U as jobWriteIds, V as getNextIndexForScope, W as refusalError, Y as updateDocumentRevision, Z as AppendConditionFailedError, _ as acquirePurgeLocks, _t as ReservedActionError, at as AuthorizationDeniedError, bt as UpgradePreconditionFailedError, ct as DocumentNotDeletedError, dt as ExcessiveReshuffleError, ft as GroupInUseError, ht as PurgeTooLargeError, it as AuthTimestampNotMonotonicError, j as PurgeFence, k as FencedWriteCache, lt as DocumentNotFoundError, mt as InvalidSignatureError, pt as InvalidOperationTimestampError, st as DocumentDeletedError, ut as DocumentPurgedError, v as findPurged, vt as UnsupportedProtocolVersionError } from "./forwarding-logger-ByfBFgMS.js";
1
+ import { A as GATED_DOCUMENT_ACTIONS, At as yieldToMain, B as createOperation, Ct as ReactorEventTypes, F as buildErrorResult, G as relationshipTarget, H as isGenesisOperation, I as buildSuccessResult, J as targetDocumentId, K as submittedActionIds, M as TouchedStreams, N as applyDeleteDocumentAction$1, O as DOCUMENT_SCOPE_ACTIONS, P as applyUpgradeDocumentAction$1, R as createDocumentFromAction, Tt as delay, U as jobWriteIds, V as getNextIndexForScope, W as refusalError, Y as updateDocumentRevision, Z as AppendConditionFailedError, _ as acquirePurgeLocks, at as AuthorizationDeniedError, ct as DocumentNotDeletedError, dt as ExcessiveReshuffleError, ft as GroupInUseError, gt as PurgeTooLargeError, it as AuthTimestampNotMonotonicError, j as PurgeFence, k as FencedWriteCache, kt as withDeadline, lt as DocumentNotFoundError, mt as InvalidSignatureError, pt as InvalidOperationTimestampError, st as DocumentDeletedError, ut as DocumentPurgedError, v as findPurged, vt as ReservedActionError, wt as TIMED_OUT, xt as UpgradePreconditionFailedError, yt as UnsupportedProtocolVersionError } from "./forwarding-logger-B9LLXItm.js";
2
2
  import { AUTH_ACTION_TYPES, AUTH_DENIED_BY_GRANT_REASON, AUTH_NO_GRANT_REASON, AUTH_VERSION_UNSUPPORTED_REASON, DOCUMENT_DELETED_REASON, PEER_CAPABILITIES, actionSignerIdentity, actionSigningTarget, applyAuthAction, applyDeleteDocumentAction, base58Decode, baseReducerVersion, buildOperationSignatureMessage, buildOperationSignatureParams, canonicalJson, decide, deriveDocumentId, deriveOperationId, evaluate, garbageCollect, groupDocumentType, groupMembershipActionTypes, hashActionV2, hashBrowser, hashDocumentStateForScope, isDenied, isDerivedDocumentId, isPurgeMarker, isUndoRedo, isV2ActionHash, localSupports, mentionedGroupIds, normalizeDocumentModelVersion, purgeDocumentAction, purgeMarkerOperation, referencedGroupIds, signaturePolicyOf, sortOperations, v2TupleProblem, withProtocolVersions } from "@powerhousedao/shared/document-model";
3
3
  import { sql } from "kysely";
4
+ //#region src/shared/types.ts
5
+ /**
6
+ * Enum that determines deletion propagation.
7
+ */
8
+ let PropagationMode = /* @__PURE__ */ function(PropagationMode) {
9
+ PropagationMode["None"] = "none";
10
+ PropagationMode["Cascade"] = "cascade";
11
+ return PropagationMode;
12
+ }({});
13
+ /**
14
+ * Enum that describes the type of relationship change.
15
+ */
16
+ let RelationshipChangeType = /* @__PURE__ */ function(RelationshipChangeType) {
17
+ RelationshipChangeType["Added"] = "added";
18
+ RelationshipChangeType["Removed"] = "removed";
19
+ return RelationshipChangeType;
20
+ }({});
21
+ /**
22
+ * Marks a load whose operations a sync cursor keeps re-pullable, so its
23
+ * write-ready needs no flush. A symbol, not exported from the package: it
24
+ * cannot be set by an outside caller or survive any serialization boundary.
25
+ */
26
+ const CURSOR_PROTECTED = Symbol("cursorProtected");
27
+ /** Load meta for the sync manager's inbox, the only owner of the cursor. */
28
+ function cursorProtectedLoadMeta(sourceRemote) {
29
+ return {
30
+ sourceRemote,
31
+ [CURSOR_PROTECTED]: true
32
+ };
33
+ }
34
+ function isCursorProtectedLoad(job) {
35
+ const meta = job.meta;
36
+ return job.kind === "load" && meta[CURSOR_PROTECTED] === true;
37
+ }
38
+ /**
39
+ * Job execution statuses
40
+ */
41
+ let JobStatus = /* @__PURE__ */ function(JobStatus) {
42
+ /** Job is queued but not yet started */
43
+ JobStatus["PENDING"] = "PENDING";
44
+ /** Job is currently being executed */
45
+ JobStatus["RUNNING"] = "RUNNING";
46
+ /** Operations have been written to the operation store (JOB_WRITE_READY event) */
47
+ JobStatus["WRITE_READY"] = "WRITE_READY";
48
+ /** Read models have finished indexing operations (JOB_READ_READY event) */
49
+ JobStatus["READ_READY"] = "READ_READY";
50
+ /** Job failed (may be retried) */
51
+ JobStatus["FAILED"] = "FAILED";
52
+ return JobStatus;
53
+ }({});
54
+ //#endregion
4
55
  //#region src/cache/operation-index-types.ts
5
56
  const DRIVE_COLLECTION_PREFIX = "drive.";
6
57
  /**
@@ -28,6 +79,16 @@ var DriveCollectionId = class DriveCollectionId {
28
79
  * The single deserializer for the wire/storage form. `branch` may contain
29
80
  * dots, while `driveId` is a dot-free document id, so the drive id is the
30
81
  * final dot-delimited segment.
82
+ *
83
+ * That asymmetry is a real constraint, not a convention: a drive id
84
+ * containing a dot does NOT survive `key` -> `fromKey`, because the split
85
+ * takes the LAST dot and so hands the tail of the drive id back as the drive
86
+ * id and everything before it as the branch. The key format is the wire and
87
+ * storage representation of existing `document_collections` rows, so it
88
+ * cannot be changed; callers that accept a drive id or branch from outside
89
+ * (a UI field, an RPC payload) must reject dots in the drive id at their own
90
+ * boundary. The round-trip is verifiable without parsing rules:
91
+ * `DriveCollectionId.fromKey(id.key).key === id.key`.
31
92
  */
32
93
  static fromKey(key) {
33
94
  if (!key.startsWith(DRIVE_COLLECTION_PREFIX)) throw new Error(`Unsupported collection id: ${key}`);
@@ -409,6 +470,7 @@ function selectDecisionModel(flags, registry) {
409
470
  const DEFAULT_MAX_PURGE_OPERATIONS = 2e5;
410
471
  /** How long a deferred job waits for its document before it fails. */
411
472
  const DEFAULT_DEFERRED_JOB_TTL_MS = 3e4;
473
+ const DEFAULT_JOB_TIMEOUT_MS = 3e4;
412
474
  /**
413
475
  * Event types for the job executor
414
476
  */
@@ -687,6 +749,20 @@ var KyselyExecutionScope = class {
687
749
  }
688
750
  };
689
751
  //#endregion
752
+ //#region src/storage/storage-flush.ts
753
+ /** The store's session is poisoned; only a host restart recovers it. */
754
+ var StoragePoisonedError = class extends Error {
755
+ constructor(message, cause) {
756
+ super(message, { cause });
757
+ this.name = "StoragePoisonedError";
758
+ }
759
+ };
760
+ var NoopStorageFlusher = class {
761
+ flush() {
762
+ return Promise.resolve();
763
+ }
764
+ };
765
+ //#endregion
690
766
  //#region src/utils/reshuffle.ts
691
767
  const STRICT_ORDER_ACTION_TYPES = /* @__PURE__ */ new Set([
692
768
  "CREATE_DOCUMENT",
@@ -2184,6 +2260,8 @@ function sameContent(stored, submitted) {
2184
2260
  //#endregion
2185
2261
  //#region src/executor/simple-job-executor.ts
2186
2262
  const MAX_SKIP_THRESHOLD = 1e3;
2263
+ /** Retries of a failed flush before a committed job's write-ready is withheld. */
2264
+ const MAX_ANNOUNCE_FLUSH_ATTEMPTS = 5;
2187
2265
  const ISO_TIMESTAMP_REGEX = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?Z$/;
2188
2266
  function isValidISOTimestamp(value) {
2189
2267
  if (!ISO_TIMESTAMP_REGEX.test(value)) return false;
@@ -2246,11 +2324,13 @@ var SimpleJobExecutor = class {
2246
2324
  documentActionHandler;
2247
2325
  executionScope;
2248
2326
  signer;
2327
+ flusher;
2249
2328
  /**
2250
2329
  * `signer` signs the operations the reducer synthesizes; unsigned if omitted.
2251
2330
  * `trustPolicy` decides which keys may sign as which users at admission.
2331
+ * `flusher` makes a job durable before its write-ready is announced.
2252
2332
  */
2253
- constructor(logger, registry, operationStore, eventBus, writeCache, operationIndex, documentMetaCache, collectionMembershipCache, driveContainerTypes, config, executionScope, signer, trustPolicy) {
2333
+ constructor(logger, registry, operationStore, eventBus, writeCache, operationIndex, documentMetaCache, collectionMembershipCache, driveContainerTypes, config, executionScope, signer, trustPolicy, flusher = new NoopStorageFlusher()) {
2254
2334
  this.logger = logger;
2255
2335
  this.registry = registry;
2256
2336
  this.operationStore = operationStore;
@@ -2260,6 +2340,7 @@ var SimpleJobExecutor = class {
2260
2340
  this.documentMetaCache = documentMetaCache;
2261
2341
  this.collectionMembershipCache = collectionMembershipCache;
2262
2342
  this.driveContainerTypes = driveContainerTypes;
2343
+ this.flusher = flusher;
2263
2344
  this.signer = signer ?? new PassthroughSigner();
2264
2345
  this.featureFlags = resolveFeatureFlags(config.featureFlags);
2265
2346
  this.config = {
@@ -2274,7 +2355,8 @@ var SimpleJobExecutor = class {
2274
2355
  batchApplies: config.batchApplies ?? true,
2275
2356
  signatureVerification: config.signatureVerification ?? "enforce",
2276
2357
  protocolSupport: config.protocolSupport ?? localSupports(PEER_CAPABILITIES, this.featureFlags).protocols,
2277
- maxPurgeOperations: config.maxPurgeOperations ?? 2e5
2358
+ maxPurgeOperations: config.maxPurgeOperations ?? 2e5,
2359
+ durabilityWaitMs: config.durabilityWaitMs ?? 0
2278
2360
  };
2279
2361
  this.decisionModel = selectDecisionModel(this.featureFlags, registry);
2280
2362
  this.signatureAdmission = new SignatureAdmission(this.config.signatureVerification, logger, eventBus, this.featureFlags.documentDecisions ? "write-cache" : "meta", {
@@ -2305,7 +2387,7 @@ var SimpleJobExecutor = class {
2305
2387
  * but nothing here closes it, and a caller that needs to know a write is
2306
2388
  * real has the job status to ask.
2307
2389
  */
2308
- async executeJob(job, signal) {
2390
+ async executeJob(job, signal, onCommitting) {
2309
2391
  const startTime = Date.now();
2310
2392
  const touchedStreams = new TouchedStreams();
2311
2393
  const postCommitInvalidations = [];
@@ -2325,6 +2407,8 @@ var SimpleJobExecutor = class {
2325
2407
  postCommitMetaInvalidations
2326
2408
  });
2327
2409
  if (!scoped.result.success) throw new JobRollbackSignal(scoped.result);
2410
+ signal?.throwIfAborted();
2411
+ onCommitting?.();
2328
2412
  return scoped;
2329
2413
  }, signal);
2330
2414
  } catch (error) {
@@ -2336,12 +2420,54 @@ var SimpleJobExecutor = class {
2336
2420
  for (const documentId of postCommitMembershipInvalidations) this.collectionMembershipCache.invalidate(documentId);
2337
2421
  for (const documentId of postCommitMetaInvalidations) this.documentMetaCache.invalidate(documentId);
2338
2422
  const { pendingEvent } = outcome;
2339
- if (pendingEvent) this.eventBus.emit(ReactorEventTypes.JOB_WRITE_READY, pendingEvent).catch((error) => {
2423
+ if (pendingEvent && await this.awaitDurable(job, signal)) this.eventBus.emit(ReactorEventTypes.JOB_WRITE_READY, pendingEvent).catch((error) => {
2340
2424
  this.logger.error("Failed to emit JOB_WRITE_READY event: @Event : @Error", pendingEvent, error);
2341
2425
  });
2342
2426
  return outcome.result;
2343
2427
  }
2344
2428
  /**
2429
+ * Waits for a flush covering the committed job before its write-ready goes
2430
+ * out; false withholds the announcement. A failed flush is never a failed
2431
+ * job, since the commit stands: it is retried, and the announcement is
2432
+ * withheld once the store is poisoned (the host restarts), the attempts run
2433
+ * out, the wait outlives `durabilityWaitMs`, or the job's signal aborts.
2434
+ */
2435
+ async awaitDurable(job, signal) {
2436
+ if (isCursorProtectedLoad(job)) return true;
2437
+ const budgetMs = this.config.durabilityWaitMs;
2438
+ const deadline = budgetMs > 0 ? Date.now() + budgetMs : Infinity;
2439
+ for (let attempt = 1;; attempt++) {
2440
+ try {
2441
+ if (await this.withinDurabilityWait(this.flusher.flush(), deadline, signal) !== TIMED_OUT) return true;
2442
+ this.logger.error("Job @JobId committed but its flush outlived the durability wait or the job was aborted; its write-ready is withheld", job.id);
2443
+ return false;
2444
+ } catch (error) {
2445
+ this.logger.error("Flush before announcing job @JobId failed (attempt @Attempt): @Error", job.id, attempt, error);
2446
+ if (error instanceof StoragePoisonedError || attempt >= MAX_ANNOUNCE_FLUSH_ATTEMPTS) {
2447
+ this.logger.error("Job @JobId committed but is not durable; its write-ready is withheld", job.id);
2448
+ return false;
2449
+ }
2450
+ }
2451
+ if (await this.withinDurabilityWait(delay(Math.min(this.config.retryMaxDelayMs, this.config.retryBaseDelayMs * 2 ** (attempt - 1))), deadline, signal) === TIMED_OUT) return false;
2452
+ }
2453
+ }
2454
+ /** Settles with TIMED_OUT at the deadline or on abort; the wait itself is abandoned. */
2455
+ async withinDurabilityWait(pending, deadline, signal) {
2456
+ pending.catch(() => void 0);
2457
+ if (signal?.aborted) return TIMED_OUT;
2458
+ let onAbort;
2459
+ const aborted = new Promise((resolve) => {
2460
+ onAbort = () => resolve(TIMED_OUT);
2461
+ signal?.addEventListener("abort", onAbort, { once: true });
2462
+ });
2463
+ try {
2464
+ const raced = Promise.race([pending, aborted]);
2465
+ return deadline === Infinity ? await raced : await withDeadline(raced, Math.max(0, deadline - Date.now()));
2466
+ } finally {
2467
+ if (onAbort) signal?.removeEventListener("abort", onAbort);
2468
+ }
2469
+ }
2470
+ /**
2345
2471
  * The body of a job, run inside the execution scope's transaction.
2346
2472
  *
2347
2473
  * The stores and caches it works through are the copies scoped to that
@@ -3761,6 +3887,6 @@ var SimpleJobExecutor = class {
3761
3887
  //#region src/core/drive-container-types.ts
3762
3888
  const DEFAULT_DRIVE_CONTAINER_TYPES = /* @__PURE__ */ new Set(["powerhouse/document-drive", "powerhouse/reactor-drive"]);
3763
3889
  //#endregion
3764
- export { buildDecisionModel as _, KyselyExecutionScope as a, appliedDelete as c, DEFAULT_MAX_PURGE_OPERATIONS as d, JobExecutorEventTypes as f, authDecisionModel as g, documentDecisionModel as h, PassthroughSigner as i, resolveFeatureFlags as l, selectDecisionModel as m, SimpleJobExecutor as n, DEFAULT_PURGE_DELETE_BATCH as o, decideAtHead as p, verifyActionSignature as r, KyselyDocumentPurger as s, DEFAULT_DRIVE_CONTAINER_TYPES as t, DEFAULT_DEFERRED_JOB_TTL_MS as u, DriveCollectionId as v };
3890
+ export { PropagationMode as C, JobStatus as S, cursorProtectedLoadMeta as T, selectDecisionModel as _, NoopStorageFlusher as a, buildDecisionModel as b, DEFAULT_PURGE_DELETE_BATCH as c, resolveFeatureFlags as d, DEFAULT_DEFERRED_JOB_TTL_MS as f, decideAtHead as g, JobExecutorEventTypes as h, PassthroughSigner as i, KyselyDocumentPurger as l, DEFAULT_MAX_PURGE_OPERATIONS as m, SimpleJobExecutor as n, StoragePoisonedError as o, DEFAULT_JOB_TIMEOUT_MS as p, verifyActionSignature as r, KyselyExecutionScope as s, DEFAULT_DRIVE_CONTAINER_TYPES as t, appliedDelete as u, documentDecisionModel as v, RelationshipChangeType as w, DriveCollectionId as x, authDecisionModel as y };
3765
3891
 
3766
- //# sourceMappingURL=drive-container-types-Ci1pdv4X.js.map
3892
+ //# sourceMappingURL=drive-container-types-BAfFzBOf.js.map