@powerhousedao/reactor 6.2.2-dev.42 → 6.2.2-dev.44

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/entry.js CHANGED
@@ -1,6 +1,6 @@
1
- import { o as instrumentPgPool } from "./drive-container-types-BJCKXJwH.js";
1
+ import { o as instrumentPgPool } from "./drive-container-types-h3M1AK3K.js";
2
2
  import { n as errorToInfo, t as createForwardingLogger } from "./forwarding-logger-BBkMSxuJ.js";
3
- import { n as defaultLoadFactory, t as buildWorkerExecutor } from "./build-worker-executor-CAEZD_yJ.js";
3
+ import { n as defaultLoadFactory, t as buildWorkerExecutor } from "./build-worker-executor-CHkkh55C.js";
4
4
  import { ConsoleLogger } from "document-model";
5
5
  import { isMainThread, parentPort } from "node:worker_threads";
6
6
  //#region src/executor/worker/run-worker.ts
package/dist/index.d.ts CHANGED
@@ -57,7 +57,14 @@ declare function updateRelationshipAction(sourceId: string, targetId: string, re
57
57
  declare function removeRelationshipAction(sourceId: string, targetId: string, relationshipType?: string): Action;
58
58
  //#endregion
59
59
  //#region src/queue/types.d.ts
60
- type JobKind = "mutation" | "load";
60
+ /**
61
+ * What a job carries: a mutation applies actions, a load imports synced
62
+ * operations, and a reevaluation re-judges a document's stored operations
63
+ * because a read-set stream elsewhere (a group document) changed. A
64
+ * reevaluation job carries no actions or operations; its work is derived
65
+ * from what is already stored.
66
+ */
67
+ type JobKind = "mutation" | "load" | "reevaluation";
61
68
  /**
62
69
  * State of a job in the queue
63
70
  */
@@ -1189,6 +1196,15 @@ interface IOperationIndexTxn {
1189
1196
  createCollection(collectionId: string): void;
1190
1197
  addToCollection(collectionId: string, documentId: string): void;
1191
1198
  removeFromCollection(collectionId: string, documentId: string): void;
1199
+ /**
1200
+ * Records the group documents an auth operation's input names, tied to the
1201
+ * last written operation like addToCollection. At commit each reference is
1202
+ * remembered permanently and the group joins every collection the
1203
+ * referencing document belongs to, keeping the earliest join and reopening
1204
+ * a closed membership, so sync serves the group's history to every remote
1205
+ * that can observe the referencing grant.
1206
+ */
1207
+ recordGroupReferences(documentId: string, groupIds: string[]): void;
1192
1208
  write(operations: OperationIndexEntry[]): void;
1193
1209
  }
1194
1210
  /**
@@ -1210,6 +1226,13 @@ interface IOperationIndex {
1210
1226
  * Returns a map of documentId to array of collection IDs.
1211
1227
  */
1212
1228
  getCollectionsForDocuments(documentIds: string[]): Promise<Record<string, string[]>>;
1229
+ /**
1230
+ * The documents whose auth history has ever referenced the group, from the
1231
+ * group-reference relation. This is the set a group-stream change owes a
1232
+ * re-evaluation pass to; it is complete because a group's auth scope cannot
1233
+ * reference other groups.
1234
+ */
1235
+ getGroupReferencers(groupId: string, signal?: AbortSignal): Promise<string[]>;
1213
1236
  }
1214
1237
  /**
1215
1238
  * Identifies the collection a remote synchronizes. Collections are drive-level
@@ -1667,6 +1690,17 @@ type CreateDocumentOptions = {
1667
1690
  /** Optional "id" or "slug" of parent document */parentIdentifier?: string; /** Optional version of the document model to use (defaults to latest) */
1668
1691
  documentModelVersion?: number;
1669
1692
  };
1693
+ /**
1694
+ * Options for upgrading a document.
1695
+ */
1696
+ type UpgradeDocumentOptions = {
1697
+ /**
1698
+ * How many times to retry with a fresh read when the executor rejects the
1699
+ * upgrade because the document changed after it was read. Defaults to
1700
+ * {@link DEFAULT_UPGRADE_CONFLICT_RETRIES}.
1701
+ */
1702
+ maxConflictRetries?: number;
1703
+ };
1670
1704
  /**
1671
1705
  * Drive-aware operations grouped under `client.drives`.
1672
1706
  *
@@ -1843,6 +1877,38 @@ interface IReactorClient {
1843
1877
  * @param signal - Optional abort signal to cancel the request
1844
1878
  */
1845
1879
  createEmpty<TDocument extends PHDocument>(documentModelType: string, options?: CreateDocumentOptions, signal?: AbortSignal): Promise<TDocument>;
1880
+ /**
1881
+ * Retrieves the document model module matching the version a document is
1882
+ * stamped with. Use this instead of {@link getDocumentModelModule}
1883
+ * whenever a specific document is in hand: the latest-wins lookup feeds
1884
+ * not-yet-upgraded documents the wrong reducer, diverging from replay.
1885
+ *
1886
+ * @param document - The document whose stamped version selects the module
1887
+ * @returns The document model module registered for that version
1888
+ * @throws UnsupportedDocumentModelVersionError if no module is registered for the stamped version
1889
+ */
1890
+ getDocumentModelModuleForDocument(document: PHDocument): Promise<DocumentModelModule<any>>;
1891
+ /**
1892
+ * Upgrades a document to a newer document model version by dispatching an
1893
+ * UPGRADE_DOCUMENT action. When toVersion is omitted, upgrades to the
1894
+ * latest registered module version for the document's type. Returns the
1895
+ * document unchanged when it is already at the target version.
1896
+ *
1897
+ * The action carries a snapshot of the document's version and per-scope
1898
+ * revisions, which the executor validates before persisting. When an edit
1899
+ * lands between the read and the upgrade executing, the upgrade is
1900
+ * rejected and retried with a fresh read up to
1901
+ * {@link UpgradeDocumentOptions.maxConflictRetries} times before the
1902
+ * conflict is surfaced.
1903
+ *
1904
+ * @param documentIdentifier - Target document id or slug
1905
+ * @param toVersion - Optional target document model version; defaults to latest
1906
+ * @param options - Optional upgrade options (maxConflictRetries)
1907
+ * @param signal - Optional abort signal to cancel the request
1908
+ * @returns The upgraded document
1909
+ * @throws DowngradeNotSupportedError if toVersion is less than the document's current version
1910
+ */
1911
+ upgradeDocument<TDocument extends PHDocument = PHDocument>(documentIdentifier: string, toVersion?: number, options?: UpgradeDocumentOptions, signal?: AbortSignal): Promise<TDocument>;
1846
1912
  /**
1847
1913
  * Creates an empty document in a drive as a single batched operation.
1848
1914
  * This is more efficient than createEmpty + addFile as it batches all
@@ -2032,6 +2098,12 @@ declare class ReactorClient implements IReactorClient {
2032
2098
  * @returns The document model module
2033
2099
  */
2034
2100
  getDocumentModelModule(documentType: string): Promise<DocumentModelModule<any>>;
2101
+ /**
2102
+ * Retrieves the document model module matching the version the document is
2103
+ * stamped with, so not-yet-upgraded documents get the reducer their
2104
+ * history was written with rather than the latest.
2105
+ */
2106
+ getDocumentModelModuleForDocument(document: PHDocument): Promise<DocumentModelModule<any>>;
2035
2107
  /**
2036
2108
  * Retrieves a specific PHDocument
2037
2109
  */
@@ -2067,6 +2139,18 @@ declare class ReactorClient implements IReactorClient {
2067
2139
  * Creates an empty document and waits for completion
2068
2140
  */
2069
2141
  createEmpty<TDocument extends PHDocument>(documentModelType: string, options?: CreateDocumentOptions, signal?: AbortSignal): Promise<TDocument>;
2142
+ /**
2143
+ * Upgrades a document to a newer document model version by dispatching an
2144
+ * UPGRADE_DOCUMENT action. When toVersion is omitted, upgrades to the
2145
+ * latest registered module version for the document's type. Returns the
2146
+ * document unchanged when it is already at the target version.
2147
+ *
2148
+ * The executor validates the action's version and revision snapshot against
2149
+ * the state the migration actually runs on. When a concurrent edit
2150
+ * invalidates the snapshot, the upgrade is rebuilt from a fresh read and
2151
+ * retried up to maxConflictRetries times before the conflict is surfaced.
2152
+ */
2153
+ upgradeDocument<TDocument extends PHDocument = PHDocument>(documentIdentifier: string, toVersion?: number, options?: UpgradeDocumentOptions, signal?: AbortSignal): Promise<TDocument>;
2070
2154
  /**
2071
2155
  * Creates an empty document in a drive as a single batched operation.
2072
2156
  * Delegates to {@link IDriveClient.addFile}.
@@ -2339,11 +2423,20 @@ interface SyncDeadLetterTable {
2339
2423
  error_type: Generated<string>;
2340
2424
  created_at: Generated<Date>;
2341
2425
  }
2426
+ /**
2427
+ * One (document, group) reference ever discovered from an auth operation's
2428
+ * input. Rows are never updated or deleted (see migration 017).
2429
+ */
2430
+ interface GroupReferenceTable {
2431
+ documentId: string;
2432
+ groupId: string;
2433
+ }
2342
2434
  interface Database$1 {
2343
2435
  Operation: OperationTable;
2344
2436
  Keyframe: KeyframeTable;
2345
2437
  document_collections: DocumentCollectionTable;
2346
2438
  operation_index_operations: OperationIndexOperationTable;
2439
+ group_references: GroupReferenceTable;
2347
2440
  sync_remotes: SyncRemoteTable;
2348
2441
  sync_cursors: SyncCursorTable;
2349
2442
  sync_dead_letters: SyncDeadLetterTable;
@@ -3037,7 +3130,40 @@ declare class KyselyWriteCache implements IWriteCache {
3037
3130
  */
3038
3131
  getStream(documentId: string, scope: string, branch: string): DocumentStream | undefined;
3039
3132
  private findNearestKeyframe;
3133
+ /**
3134
+ * Rebuilds a scope from a keyframe or from the whole operation history.
3135
+ *
3136
+ * The document scope is always rebuilt first, because it carries the type,
3137
+ * the upgrades and the deletion marker. Its version-changing upgrades are not
3138
+ * applied there though: an upgrade reducer must see the state the requested
3139
+ * scope has reached at that upgrade's boundary, so each one is held back and
3140
+ * applied when the replay below crosses the boundary that
3141
+ * resolveModuleVersionForOp derives from it. Upgrades whose boundary lies past
3142
+ * the last replayed operation are applied at the end. Creation-time 0->N seed
3143
+ * upgrades carry the initial state, so they still apply immediately.
3144
+ */
3040
3145
  private coldMissRebuild;
3146
+ /**
3147
+ * Applies and removes every held-back upgrade whose target version is at or
3148
+ * below `throughVersion`, in the order the document scope recorded them.
3149
+ */
3150
+ private applyPendingUpgrades;
3151
+ /**
3152
+ * Applies the remaining held-back upgrades after the requested scope's
3153
+ * replay has finished. A head read applies them all. A positional read
3154
+ * applies only those whose boundary for this scope lies at or before the
3155
+ * target position: applying a later one would label migrated state with a
3156
+ * pre-upgrade revision, and a keyframe stored from that poisons every
3157
+ * rebuild that resumes from it. Boundaries come from the upgrade's revision
3158
+ * snapshot; an upgrade without one records no position for this scope, and
3159
+ * the replay loop not having crossed it already places it past the target.
3160
+ */
3161
+ private applyTailPendingUpgrades;
3162
+ /**
3163
+ * Applies one held-back upgrade, then re-applies the deletes the document
3164
+ * scope recorded after it so the hold-back cannot invert their order.
3165
+ */
3166
+ private applyPendingUpgrade;
3041
3167
  /**
3042
3168
  * Copies the current document revisions onto the document. Overwrites the
3043
3169
  * requested scope revision with the target revision, if provided.
@@ -3166,6 +3292,16 @@ type ReactorFeatureFlags = {
3166
3292
  * Requires documentDecisions.
3167
3293
  */
3168
3294
  authEnforcement: boolean;
3295
+ /**
3296
+ * Match { group } principals by folding the referenced PHGroup documents as
3297
+ * derived projections. Requires authEnforcement.
3298
+ */
3299
+ authGroups: boolean;
3300
+ /**
3301
+ * Evaluate `where` clauses and { match } principals against the executing
3302
+ * scope's state, the subject, and the action input. Requires authGroups.
3303
+ */
3304
+ authConditions: boolean;
3169
3305
  };
3170
3306
  /**
3171
3307
  * Configuration options for the job executor
@@ -3509,6 +3645,30 @@ interface IQueue {
3509
3645
  unblock(): void;
3510
3646
  }
3511
3647
  //#endregion
3648
+ //#region src/core/group-reevaluation-trigger.d.ts
3649
+ /**
3650
+ * Watches committed writes for group membership changes and enqueues a
3651
+ * re-evaluation job for every document whose auth history references the
3652
+ * changed group, found through the reverse direction of the group-reference
3653
+ * relation. Each affected document is re-judged in its own job, so the work
3654
+ * runs under that document's execution slot rather than the group's.
3655
+ *
3656
+ * The job carries the earliest changed membership timestamp; the executor
3657
+ * skips the pass when everything the document holds sorts before it, which
3658
+ * keeps the common case (a membership write later than all history) free.
3659
+ */
3660
+ declare class GroupReevaluationTrigger {
3661
+ private logger;
3662
+ private eventBus;
3663
+ private queue;
3664
+ private operationIndex;
3665
+ private unsubscribe?;
3666
+ constructor(logger: ILogger, eventBus: IEventBus, queue: IQueue, operationIndex: IOperationIndex);
3667
+ startup(): void;
3668
+ shutdown(): void;
3669
+ private onWriteReady;
3670
+ }
3671
+ //#endregion
3512
3672
  //#region src/read-models/types.d.ts
3513
3673
  interface ViewStateTable {
3514
3674
  readModelId: string;
@@ -4295,6 +4455,12 @@ interface InProcessReactorModule extends ReactorModule {
4295
4455
  processorManagerConsistencyTracker: IConsistencyTracker;
4296
4456
  reactor: IReactor;
4297
4457
  syncModule: InProcessSyncModule | undefined;
4458
+ /**
4459
+ * Present when authGroups is on: enqueues re-evaluation jobs for the
4460
+ * documents a group membership change affects. Started by the builder;
4461
+ * hosts shut it down alongside the sync manager.
4462
+ */
4463
+ groupReevaluationTrigger: GroupReevaluationTrigger | undefined;
4298
4464
  /**
4299
4465
  * Instrumented pg.Pool handles registered with the builder, either by
4300
4466
  * createPostgresDatabase or by withInstrumentedPool. Empty when no pg
@@ -5314,6 +5480,13 @@ declare class SimpleJobExecutor implements IJobExecutor {
5314
5480
  * re-appended, carrying a skip that spans the indices it supersedes.
5315
5481
  */
5316
5482
  private reevaluateDocument;
5483
+ /**
5484
+ * Re-judges a document's stored operations because a read-set stream in
5485
+ * another document (a group) gained an operation. The trigger timestamp
5486
+ * bounds the work: an operation later than everything this document holds
5487
+ * cannot change any evaluation, so the pass is skipped.
5488
+ */
5489
+ private executeReevaluationJob;
5317
5490
  private executeLoadJob;
5318
5491
  private accumulateResultOrReturnError;
5319
5492
  }
@@ -5394,9 +5567,20 @@ type DecisionTarget = {
5394
5567
  documentId: string;
5395
5568
  branch: string;
5396
5569
  };
5397
- /** The executing scope's own state, for conditions that read it. */
5570
+ /**
5571
+ * What a decision's conditions may read beyond the projections: the executing
5572
+ * scope's own state and the attempted action's input. Populated only while
5573
+ * authConditions is on; otherwise both stay undefined and conditional grants
5574
+ * never apply.
5575
+ */
5398
5576
  type DecisionContext = {
5399
5577
  scopeState: unknown;
5578
+ actionInput?: unknown;
5579
+ };
5580
+ /** A statically-queried stream's operations, named after its projection. */
5581
+ type StreamHistory = {
5582
+ name: string;
5583
+ operations: Operation[];
5400
5584
  };
5401
5585
  /**
5402
5586
  * A named stream whose value in the model is that scope's state from the
@@ -5405,6 +5589,16 @@ type DecisionContext = {
5405
5589
  */
5406
5590
  type Projection<M> = {
5407
5591
  query: StreamQuery | ((model: Partial<M>) => StreamQuery[]);
5592
+ /**
5593
+ * For a derived projection, the streams it may read anywhere in an
5594
+ * evaluated range, derived from the statically-queried streams' operations
5595
+ * (including the operations under evaluation). A positional walk cannot use
5596
+ * `query`, because the folded state it depends on changes over the range;
5597
+ * this over-approximates by design, since a stream referenced at any
5598
+ * position stays readable when the earlier range is re-evaluated even if a
5599
+ * later operation removes the reference. Ignored on static projections.
5600
+ */
5601
+ queryOverHistory?: (reads: StreamHistory[]) => StreamQuery[];
5408
5602
  /**
5409
5603
  * Action types in this stream that can change an evaluation. Reads of the stream
5410
5604
  * are filtered to these, so anything left out is invisible to a decision.
@@ -5425,6 +5619,14 @@ type Evaluation = {
5425
5619
  /** Projections plus a decision function over the built model. */
5426
5620
  type DecisionModel<M> = {
5427
5621
  projections: { [K in keyof M]: Projection<M> };
5622
+ /**
5623
+ * Present when decide reads the executing scope's state through the
5624
+ * decision context. A positional walk then folds the evaluated stream with
5625
+ * this, from its base state through every effective operation, so
5626
+ * conditions read the state as it stood at each operation's position
5627
+ * rather than at the head.
5628
+ */
5629
+ foldEvaluatedScope?: (document: PHDocument, operation: Operation) => PHDocument;
5428
5630
  /**
5429
5631
  * Whether or not this model decides about operations in a given scope. That
5430
5632
  * is, a scope it reads is not necessarily one it evaluates, and vise-versa.
@@ -5480,16 +5682,31 @@ type AdmissionDecision = {
5480
5682
  documentVersion: number;
5481
5683
  deletedAtUtcIso: string | null;
5482
5684
  };
5685
+ /**
5686
+ * What decideAtHead resolves a condition context from: the action's input,
5687
+ * with the executing scope's state read at the head. Supplied only while
5688
+ * authConditions is on.
5689
+ */
5690
+ type AdmissionConditions = {
5691
+ actionInput?: unknown;
5692
+ };
5483
5693
  /**
5484
5694
  * Builds the model at the stream heads and decides one request against it. The
5485
5695
  * append condition it returns is the read-set the store enforces at write time.
5696
+ *
5697
+ * With `conditions` supplied, the executing scope's state is read at the head
5698
+ * for `doc.<scope>.*` paths. That read carries no append-condition entry of
5699
+ * its own: the written stream's expected-revision check already refuses a
5700
+ * write whose scope grew between the read and the append.
5486
5701
  */
5487
- declare function decideAtHead(model: RegisteredDecisionModel, cache: IWriteCache, target: DecisionTarget, subject: AuthSubject, request: AuthRequest, signal?: AbortSignal): Promise<AdmissionDecision>;
5702
+ declare function decideAtHead(model: RegisteredDecisionModel, cache: IWriteCache, target: DecisionTarget, subject: AuthSubject, request: AuthRequest, signal?: AbortSignal, conditions?: AdmissionConditions): Promise<AdmissionDecision>;
5488
5703
  /**
5489
5704
  * The model this reactor enforces. With `authEnforcement` off the auth scope is
5490
- * absent from every append condition and no load walks it.
5705
+ * absent from every append condition and no load walks it; with `authGroups`
5706
+ * on, the group documents the grant list names join the read-set and the
5707
+ * registry supplies the reducer that folds them.
5491
5708
  */
5492
- declare function selectDecisionModel(flags: ReactorFeatureFlags): RegisteredDecisionModel;
5709
+ declare function selectDecisionModel(flags: ReactorFeatureFlags, registry: IDocumentModelRegistry): RegisteredDecisionModel;
5493
5710
  //#endregion
5494
5711
  //#region src/decision/stream-order.d.ts
5495
5712
  /** Where a stream's stored order contradicts its timestamps. */
@@ -5649,6 +5866,16 @@ declare class KyselyDocumentView extends BaseReadModel implements IDocumentView
5649
5866
  */
5650
5867
 
5651
5868
  servesDeletionBoundary: boolean);
5869
+ /**
5870
+ * Indexes committed operations into DocumentSnapshot rows. CREATE_DOCUMENT
5871
+ * only seeds header/document/auth. UPGRADE_DOCUMENT reindexes every scope
5872
+ * present in resultingState when the operation vouches for them — a seed
5873
+ * carrying initialState or a migration stamped with the __migrated marker
5874
+ * — since the upgrade reducer may have reshaped any of them; upgrades
5875
+ * without either fall back to header/document/auth, because their sibling
5876
+ * echoes may be stale. All other action types index only header and their
5877
+ * own scope.
5878
+ */
5652
5879
  protected commitOperations(items: OperationWithContext$1[]): Promise<void>;
5653
5880
  exists(documentIds: string[], consistencyToken?: ConsistencyToken, signal?: AbortSignal): Promise<boolean[]>;
5654
5881
  get<TDocument extends PHDocument>(documentId: string, view?: ViewFilter$1, consistencyToken?: ConsistencyToken, signal?: AbortSignal): Promise<TDocument>;