@kubun/store-graph 0.17.1 → 0.18.0

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/lib/api.d.ts CHANGED
@@ -1,8 +1,13 @@
1
+ import type { CustodyGuard, StoreAPIContext, StoreProvider } from '@kubun/db';
1
2
  import type { Adapter } from '@kubun/db-adapter';
2
3
  import { DocumentID, DocumentModelID } from '@kubun/id';
3
4
  import type { AggregateDocumentsParams, AggregateValues, DocumentFieldsMeta, DocumentModel, DocumentModelSchema, DocumentNode, GroupedAggregateDocumentsParams, GroupedAggregateValue } from '@kubun/protocol';
4
- import type { Kysely } from 'kysely';
5
- import type { AccessLevel, Catalog, ClusterDefinitionRow, ClusterModel, CreateDocumentParams, CreateGraphParams, DocumentModelRow, GraphModel, GraphModelWithRecord, GraphTables, InsertCatalog, InsertClusterModel, InsertMutationLogEntry, ListDocumentsParams, MutationLogEntry, QueryDocumentsParams, QueryDocumentsResult, SaveDocumentParams, SearchDocumentResult, SearchDocumentsParams } from './tables.js';
5
+ import type { Kysely, RawBuilder } from 'kysely';
6
+ import { type GraphAccessDefaultRecordMethods } from './access-default-records.js';
7
+ import type { GraphCatalogRecordMethods } from './catalog-records.js';
8
+ import type { CatalogUse, DocumentAvailability, DocumentHold, DocumentScope, PlaceDocumentHoldParams, RouteRulesInput, ScopeCatalog, CatalogRef as ScopeCatalogRef, SweptDocument, UnavailableDocument } from './catalog-scope.js';
9
+ import type { CatalogStateMethods } from './catalog-state.js';
10
+ import type { AccessLevel, ClusterDefinitionRow, ClusterModel, CreateDocumentParams, CreateGraphParams, DocumentLeafPageParams, DocumentLeafRow, DocumentLeafRule, DocumentModelRow, GraphModel, GraphModelWithRecord, GraphTables, InsertClusterModel, InsertMutationLogEntry, ListDocumentsParams, MutationLogEntry, QueryDocumentsParams, QueryDocumentsResult, SaveDocumentParams, SearchDocumentResult, SearchDocumentsParams } from './tables.js';
6
11
  export declare function documentModelFromTable(row: DocumentModelRow): DocumentModel;
7
12
  export declare function documentModelToTable(adapter: Adapter, id: string, model: DocumentModel): {
8
13
  id: string;
@@ -16,6 +21,8 @@ export declare function documentModelToTable(adapter: Adapter, id: string, model
16
21
  provider_merge_policy: {
17
22
  group: Array<string>;
18
23
  } | null;
24
+ date_field: string | null;
25
+ resolved_date_field: string | null;
19
26
  };
20
27
  /**
21
28
  * Reads a document's `field_values` cell on the given transaction and
@@ -31,6 +38,16 @@ export declare function documentModelToTable(adapter: Adapter, id: string, model
31
38
  * transaction rather than opening a second connection.
32
39
  */
33
40
  export declare function readWinnerStateDigest(tx: Kysely<GraphTables>, documentID: string): Promise<string | null>;
41
+ export type ReadWinnerStateDigestsParams = {
42
+ tx: Kysely<GraphTables>;
43
+ documentIDs: Iterable<string>;
44
+ };
45
+ /**
46
+ * Batch form of `readWinnerStateDigest`: one chunked `id in` read per model rather than one
47
+ * read per document. A document whose digest cannot be read (bad id, model table not deployed,
48
+ * no row, no digest) is left out of the map.
49
+ */
50
+ export declare function readWinnerStateDigests(params: ReadWinnerStateDigestsParams): Promise<Map<string, string>>;
34
51
  /**
35
52
  * Persisted access rule shape as stored in `user_model_access_defaults`.
36
53
  */
@@ -56,7 +73,108 @@ export type StoredAccessDefaultRow = StoredAccessRule & {
56
73
  */
57
74
  hlc: string | null;
58
75
  };
59
- export type GraphStoreAPI = {
76
+ export type RecomputeDocumentRefsParams = {
77
+ documentID: string;
78
+ routeRules: RouteRulesInput;
79
+ nowMS?: number;
80
+ catalog?: ScopeCatalogRef;
81
+ /** Custody pins to drop rather than keep, once a completed handoff is confirmed. */
82
+ releaseCustodyPins?: CustodyPinRelease;
83
+ };
84
+ export type CustodyPinRelease = {
85
+ refs: ReadonlyArray<string>;
86
+ /**
87
+ * Proves the handoff, called under the document's ref lock in the recomputing transaction, so
88
+ * no concurrent apply can change what it proved before the pins go. False keeps them.
89
+ */
90
+ confirm: () => Promise<boolean>;
91
+ };
92
+ export type SweepDocumentParams = {
93
+ documentID: string;
94
+ nowMS: number;
95
+ /** How long a document stays marked, with no change, before it may be swept. */
96
+ graceMS: number;
97
+ /**
98
+ * Proves the document may go (a valid receipt, or positive foreign ownership), called last,
99
+ * under the document's row and ref locks, with the sweeping transaction. False keeps it.
100
+ */
101
+ confirm: (stores: StoreProvider) => Promise<boolean>;
102
+ };
103
+ export type DocumentSweepRefusal = 'absent' | 'retained' | 'held' | 'unmarked' | 'changed' | 'grace' | 'pending' | 'unconfirmed';
104
+ export type DocumentSweepResult = {
105
+ swept: true;
106
+ } | {
107
+ swept: false;
108
+ refusal: DocumentSweepRefusal;
109
+ };
110
+ export type ReconcileCatalogModelRefsParams = {
111
+ modelID: string;
112
+ routeRules: RouteRulesInput;
113
+ nowMS: number;
114
+ owners?: Array<string>;
115
+ catalog?: ScopeCatalogRef;
116
+ };
117
+ export type RecordPendingMutationAttemptParams = {
118
+ mutationHash: string;
119
+ outcome: 'transient' | 'rejected' | 'applied';
120
+ now: number;
121
+ jitter: number;
122
+ };
123
+ export type UpdateSearchEntryParams = {
124
+ modelID: string;
125
+ documentID: string;
126
+ data: Record<string, unknown>;
127
+ fields: Array<string>;
128
+ };
129
+ export type RemoveUserModelAccessDefaultsParams = {
130
+ ownerDID: string;
131
+ modelID: string;
132
+ permissionTypes: Array<string>;
133
+ hlc: string;
134
+ };
135
+ export type GetDocumentIDsForScopeParams = {
136
+ scopes: Array<{
137
+ modelID: string;
138
+ ownerDID: string;
139
+ }>;
140
+ excludedDocumentIDs?: Array<string>;
141
+ /** Read at most this many IDs in total; a caller passes its cap plus one. */
142
+ limit?: number;
143
+ groupRead?: {
144
+ groupID: string;
145
+ circleIDs: Array<string>;
146
+ };
147
+ };
148
+ export type GCMark = {
149
+ documentID: string;
150
+ digest: string;
151
+ markedAt: number;
152
+ };
153
+ /** Whether a mark has waited out the grace: the sweep's own test, shared so a listing agrees. */
154
+ export declare function gcMarkPastGrace(mark: Pick<GCMark, 'markedAt'>, graceMS: number, nowMS: number): boolean;
155
+ export type DropGCMarkParams = Pick<GCMark, 'documentID' | 'digest'>;
156
+ export type CustodyNonce = {
157
+ nonce: string;
158
+ custodian: string;
159
+ assignmentDigest: string;
160
+ expiresAt: number;
161
+ };
162
+ export type CustodyReceiptRecord = {
163
+ documentID: string;
164
+ custodian: string;
165
+ /** The custody owner's own-agents group the receipt was answered through. */
166
+ groupID: string;
167
+ custodyOwner: string;
168
+ catalog: ScopeCatalogRef;
169
+ assignmentDigest: string;
170
+ headDigest: string;
171
+ /** The digest of the accepted mutation IDs the custodian answered for. */
172
+ mutationDigest: string;
173
+ receivedAt: number;
174
+ };
175
+ /** Hex blake3 over mutation IDs, sorted and joined by newline: the document mutation digest. */
176
+ export declare function mutationIDsDigest(mutationIDs: ReadonlyArray<string>): string;
177
+ export type GraphStoreAPI = GraphAccessDefaultRecordMethods & GraphCatalogRecordMethods & CatalogStateMethods & {
60
178
  getDocument(id: DocumentID | string): Promise<DocumentNode | null>;
61
179
  /**
62
180
  * Batch fetch documents, returning a Map keyed by the document ID string. A
@@ -78,18 +196,200 @@ export type GraphStoreAPI = {
78
196
  * order from `addDocumentModel` (stable for a given graph creation).
79
197
  */
80
198
  getModelInterfaces(modelID: string): Promise<Array<string>>;
199
+ /**
200
+ * The interface IDs of every model that declares any, keyed by model, from
201
+ * one read. A model without interfaces is absent.
202
+ */
203
+ listModelInterfaces(): Promise<Map<string, Array<string>>>;
204
+ /** The deployed models among `ids`, from one read. An unknown ID is absent. */
205
+ getDocumentModelsByID(ids: ReadonlyArray<string>): Promise<Map<string, DocumentModel>>;
81
206
  createDocument(params: CreateDocumentParams): Promise<DocumentNode>;
82
207
  saveDocument(params: SaveDocumentParams): Promise<DocumentNode>;
208
+ getDocumentDates(documentID: string): Promise<{
209
+ createdAt: number | null;
210
+ updatedAt: number | null;
211
+ scopeDate: number | null;
212
+ } | null>;
213
+ setCatalogDefinition(ref: ScopeCatalogRef, catalog: ScopeCatalog): Promise<void>;
214
+ setScopeReaderDID(did: string, defaultReadLevel?: AccessLevel): Promise<void>;
215
+ setCatalogUse(ref: ScopeCatalogRef, use: CatalogUse): Promise<void>;
216
+ getCatalogUse(ref: ScopeCatalogRef): Promise<CatalogUse | null>;
217
+ /** The use of each catalog in `refs`, in order, from one read. */
218
+ getCatalogUses(refs: ReadonlyArray<ScopeCatalogRef>): Promise<Array<CatalogUse | null>>;
219
+ deleteCatalogUse(ref: ScopeCatalogRef): Promise<void>;
220
+ markDocumentLocal(documentID: string): Promise<void>;
221
+ /** Writes the hold row and its retain-only `hold:` ref in one transaction. */
222
+ placeDocumentHold(params: PlaceDocumentHoldParams): Promise<DocumentHold>;
223
+ /** Null when the hold is unknown or already released. */
224
+ releaseDocumentHold(holdID: string, nowMS: number): Promise<DocumentHold | null>;
225
+ getDocumentHold(holdID: string): Promise<DocumentHold | null>;
226
+ /** Holds on the document that are neither released nor expired at `nowMS`. */
227
+ listDocumentHolds(documentID: string, nowMS: number): Promise<Array<DocumentHold>>;
228
+ /**
229
+ * The earliest expiry among holds not released that scope aging has not yet passed, or null.
230
+ * It may already be due: aging recomputes a hold's document only once its tick passes it.
231
+ */
232
+ nextDocumentHoldExpiry(): Promise<number | null>;
233
+ getDocumentScope(documentID: string): Promise<DocumentScope>;
234
+ /**
235
+ * `LOCAL` for a document here (a tombstone reads null), `UNAVAILABLE` for one swept here that
236
+ * a retaining rule matches again, else null. Says nothing of who may read it.
237
+ */
238
+ getDocumentAvailability(documentID: string, nowMS: number): Promise<DocumentAvailability | null>;
239
+ /**
240
+ * The earliest time after `nowMS` at which a rule window starts or stops matching a swept
241
+ * document, or null. Ignores the read check: an extra wake costs one refresh.
242
+ */
243
+ nextSweptDocumentTransition(nowMS: number): Promise<number | null>;
244
+ /** What this device kept of a document it swept and has not re-acquired. */
245
+ getSweptDocument(documentID: string): Promise<SweptDocument | null>;
246
+ /** Swept documents a retaining rule matches again, ordered by ID, strictly after `after`. */
247
+ listUnavailableDocuments(params: {
248
+ nowMS: number;
249
+ limit: number;
250
+ after?: string;
251
+ }): Promise<Array<UnavailableDocument>>;
252
+ /**
253
+ * Evaluate swept documents (of one model, or all) against the current rules and record a scope
254
+ * change for each that started or stopped matching one. With `ttlMS`, drop those out of every
255
+ * scope for that long.
256
+ */
257
+ refreshSweptDocuments(params: {
258
+ nowMS: number;
259
+ ttlMS?: number;
260
+ modelID?: string;
261
+ }): Promise<void>;
262
+ /** Whether a custody pin or a `SERVE_ONLY` catalog keeps the live document's bytes here. */
263
+ documentNeedsBytes(documentID: string): Promise<boolean>;
264
+ /** Hex blake3 over the document's applied mutation hashes, sorted and joined by newline. */
265
+ documentMutationDigest(documentID: string): Promise<string>;
266
+ /** Whether every listed mutation is applied to the document. */
267
+ hasAppliedMutations(documentID: string, mutationIDs: ReadonlyArray<string>): Promise<boolean>;
268
+ /** Records a receipt nonce this device issued, dropping expired ones. */
269
+ putCustodyNonce(nonce: CustodyNonce): Promise<void>;
270
+ /** True once only: for an unexpired nonce issued to this custodian for this assignment. */
271
+ consumeCustodyNonce(params: Omit<CustodyNonce, 'expiresAt'> & {
272
+ nowMS: number;
273
+ }): Promise<boolean>;
274
+ /** One receipt per document and custodian; a later receipt replaces the earlier. */
275
+ putCustodyReceipts(receipts: ReadonlyArray<CustodyReceiptRecord>): Promise<void>;
276
+ /** Ordered by custodian. */
277
+ listCustodyReceipts(documentID: string): Promise<Array<CustodyReceiptRecord>>;
278
+ /** Documents holding this custody pin ref, ordered by ID, strictly after `after`. */
279
+ listCustodyPinnedDocuments(params: {
280
+ ref: string;
281
+ limit: number;
282
+ after?: string;
283
+ }): Promise<Array<string>>;
284
+ getGCMark(documentID: string): Promise<GCMark | null>;
285
+ /**
286
+ * Drop the document's mark only while it still carries `digest`: a mutation since remarks
287
+ * the document, and that mark stays. Dropping a mark only ever keeps a document.
288
+ */
289
+ dropGCMark(params: DropGCMarkParams): Promise<void>;
290
+ /** Marks ordered by document ID, strictly after `after`. */
291
+ listGCMarks(params: {
292
+ limit: number;
293
+ after?: string;
294
+ }): Promise<Array<GCMark>>;
295
+ /** Distinct documents holding any of the catalog's rule refs. */
296
+ countCatalogDocuments(ref: ScopeCatalogRef): Promise<number>;
297
+ listPendingScopeChanges(): Promise<Array<{
298
+ documentID: string;
299
+ version: number;
300
+ }>>;
301
+ ackDocumentScopeChange(documentID: string, version: number): Promise<void>;
302
+ recomputeDocumentRefs(params: RecomputeDocumentRefsParams): Promise<DocumentScope>;
303
+ /**
304
+ * Delete the document's graph rows, under its row and ref locks, once its refs recomputed now
305
+ * retain nothing, its mark is past grace with an unchanged digest, no pending mutation waits,
306
+ * and `confirm` agrees. Writes no tombstone; bumps the scope change so a displayed copy leaves
307
+ * the view. Call inside the transaction that also sweeps the other stores.
308
+ */
309
+ sweepDocument(params: SweepDocumentParams): Promise<DocumentSweepResult>;
310
+ /**
311
+ * Drop this custody pin ref from every document holding it, at once, under the exclusive ref
312
+ * lock; each recompute then marks or releases what nothing else keeps. Returns how many
313
+ * documents were recomputed. Deletes no document.
314
+ */
315
+ releaseCustodyPinRef(params: {
316
+ ref: string;
317
+ nowMS: number;
318
+ }): Promise<number>;
319
+ /** Whether a rule of the catalog, as this device stores its definition, matches the document. */
320
+ catalogRulesMatchDocument(params: {
321
+ catalog: ScopeCatalogRef;
322
+ documentID: string;
323
+ nowMS: number;
324
+ }): Promise<boolean>;
325
+ /**
326
+ * Lock the document's row until the transaction ends (Postgres; SQLite transactions already
327
+ * run one at a time) and say whether it exists. Taken first, as an apply and the sweep take
328
+ * it, so a writer outside the apply re-checks the document against a sweep without inverting
329
+ * their locks.
330
+ */
331
+ lockDocumentRow(documentID: string): Promise<boolean>;
332
+ /**
333
+ * Recompute these documents' refs in one transaction, under the all-documents ref lock.
334
+ * Absent documents are skipped.
335
+ */
336
+ recomputeDocuments(params: {
337
+ documentIDs: ReadonlyArray<string>;
338
+ routeRules: RouteRulesInput;
339
+ nowMS: number;
340
+ }): Promise<void>;
341
+ /**
342
+ * A catalog argument updates only that catalog's contribution, including former holders.
343
+ * Without one, the recompute of every document runs in batches, each in its own transaction
344
+ * when called outside one, and `shouldStop` is checked before each: a stopped run returns
345
+ * without recording the reconcile, so the next call redoes it in full.
346
+ */
347
+ reconcileCatalogRefs(nowMS: number, routeRules: RouteRulesInput, catalog?: ScopeCatalogRef, options?: {
348
+ shouldStop?: () => boolean;
349
+ }): Promise<void>;
350
+ /** Settle already queued scope work now, without reconciling any catalog of its own. */
351
+ settlePendingCatalogRefs(nowMS: number): Promise<void>;
352
+ /**
353
+ * Queue a recompute of every model's refs after the enclosing transaction
354
+ * commits. Route rules are read from the root provider when it runs; a
355
+ * failure keeps the existing refs and retries with the scheduler's backoff.
356
+ */
357
+ scheduleCatalogRefReconcile(): Promise<void>;
358
+ reconcileCatalogModelRefs(params: ReconcileCatalogModelRefsParams): Promise<void>;
359
+ /** Queue a scan that pins the guarded catalog's existing documents, restarting one queued. */
360
+ scheduleCustodyScan(catalog: ScopeCatalogRef): Promise<void>;
361
+ /** Scan one batch of the oldest queued custody scan. True while scans remain. */
362
+ advanceCustodyScan(params: {
363
+ nowMS: number;
364
+ batchSize: number;
365
+ }): Promise<boolean>;
366
+ /**
367
+ * Run queued custody scans, each batch in its own transaction, so a restart resumes after the
368
+ * last committed batch. Call outside a transaction. True once no scan remains.
369
+ */
370
+ runCustodyScans(options?: {
371
+ nowMS?: number;
372
+ batchSize?: number;
373
+ maxBatches?: number;
374
+ }): Promise<boolean>;
375
+ onScopeRecomputed(listener: () => Promise<void> | void): () => void;
376
+ advanceCatalogAging(nowMS: number, routeRules: RouteRulesInput): Promise<void>;
83
377
  queryDocuments(params: QueryDocumentsParams): Promise<QueryDocumentsResult>;
84
378
  aggregateDocuments(params: AggregateDocumentsParams): Promise<AggregateValues>;
85
379
  groupedAggregateDocuments(params: GroupedAggregateDocumentsParams): Promise<Array<GroupedAggregateValue>>;
86
380
  listDocuments(params: ListDocumentsParams): Promise<Array<DocumentNode>>;
87
381
  listDocumentModelIDs(): Promise<Array<string>>;
88
- getDistinctOwnersForModel(modelID: string): Promise<Array<string>>;
89
382
  queryDocumentsByOwner(modelID: string, ownerDID: string): Promise<Array<DocumentNode>>;
90
383
  insertMutationLogEntry(entry: InsertMutationLogEntry): Promise<void>;
384
+ parkPushedMutation(entry: InsertMutationLogEntry & {
385
+ arrival_group_id: string;
386
+ sender_peer_did: string;
387
+ }): Promise<'parked' | 'duplicate' | 'full'>;
91
388
  hasMutationHash(hash: string): Promise<boolean>;
92
389
  updateMutationStatus(mutationHash: string, status: string): Promise<void>;
390
+ getDuePendingMutations(now: number): Promise<Array<MutationLogEntry>>;
391
+ getNextPendingAttemptAt(): Promise<number | null>;
392
+ recordPendingMutationAttempt(params: RecordPendingMutationAttemptParams): Promise<void>;
93
393
  getMutationLogForDocuments(documentIDs: Array<string>): Promise<Array<MutationLogEntry>>;
94
394
  getPendingMutations(documentID: string): Promise<Array<MutationLogEntry>>;
95
395
  /**
@@ -102,6 +402,8 @@ export type GraphStoreAPI = {
102
402
  getMaxMutationHLC(): Promise<string | null>;
103
403
  getFieldHLCs(documentID: string): Promise<Record<string, string> | null>;
104
404
  getFieldValues(documentID: string): Promise<Record<string, unknown> | null>;
405
+ /** `getFieldValues` for many documents in one read per model; absent documents are left out. */
406
+ getFieldValuesMany(documentIDs: ReadonlyArray<string>): Promise<Map<string, Record<string, unknown>>>;
105
407
  updateFieldHLCs(docID: DocumentID, fieldHLCs: Record<string, string>, fieldValues: Record<string, unknown>): Promise<void>;
106
408
  listGraphs(): Promise<Array<GraphModel>>;
107
409
  createGraph(params: CreateGraphParams): Promise<string>;
@@ -113,49 +415,9 @@ export type GraphStoreAPI = {
113
415
  getClusters(ids: Array<string>): Promise<Record<string, unknown>>;
114
416
  createSearchIndex(modelID: string, fields: Array<string>): Promise<void>;
115
417
  dropSearchIndex(modelID: string): Promise<void>;
116
- updateSearchEntry(modelID: string, documentID: string, data: Record<string, unknown>, fields: Array<string>): Promise<void>;
418
+ updateSearchEntry(params: UpdateSearchEntryParams): Promise<void>;
117
419
  removeSearchEntry(modelID: string, documentID: string): Promise<void>;
118
420
  searchDocuments(params: SearchDocumentsParams): Promise<Array<SearchDocumentResult>>;
119
- /** Create an own catalog: stored active (scopes sync) with null provenance. */
120
- createCatalog(catalog: InsertCatalog): Promise<void>;
121
- /**
122
- * Insert a discovered catalog as known (not active) with first-discovery
123
- * provenance. Idempotent on the id: an already-stored catalog is left
124
- * untouched, so its local `active` flag and first-discovery source columns
125
- * are never overwritten by a later re-discovery.
126
- */
127
- upsertDiscoveredCatalog(params: {
128
- catalog: InsertCatalog;
129
- sourceGroupID: string | null;
130
- sourceCircleID: string | null;
131
- }): Promise<void>;
132
- /** Toggle a catalog's local sync activation. */
133
- setCatalogActive(id: string, active: boolean): Promise<void>;
134
- /** Flip several catalogs together — one statement, not one per id. */
135
- setCatalogsActive(ids: Array<string>, active: boolean): Promise<void>;
136
- /**
137
- * All locally-stored catalog rows regardless of owner, optionally filtered by
138
- * activation state. Backs the viewer's known-catalogs surface.
139
- */
140
- listStoredCatalogs(filter?: {
141
- active?: boolean;
142
- }): Promise<Array<Catalog>>;
143
- getCatalog(id: string): Promise<Catalog | undefined>;
144
- /**
145
- * Batch fetch catalogs by ID. Returns a Map keyed by ID; missing IDs are
146
- * absent from the map. Single SQL query with `WHERE id IN (...)`. Empty
147
- * input short-circuits with an empty Map (no SQL).
148
- *
149
- * Caller is responsible for deduplicating IDs (e.g. via `Array.from(new Set(...))`).
150
- */
151
- getCatalogs(ids: Array<string>): Promise<Map<string, Catalog>>;
152
- updateCatalog(id: string, update: Partial<Pick<InsertCatalog, 'name' | 'description' | 'filter_criteria' | 'hlc' | 'signed_token'>>): Promise<void>;
153
- deleteCatalog(id: string): Promise<void>;
154
- listCatalogs(ownerDID: string): Promise<Array<Catalog>>;
155
- resolveCatalogScope(catalogID: string): Promise<{
156
- models: Array<string> | undefined;
157
- owners: Array<string> | undefined;
158
- }>;
159
421
  registerClusterModels(clusterID: string, entries: Array<InsertClusterModel>): Promise<void>;
160
422
  /** A tombstoned (removed) rule reads as absent. */
161
423
  getUserModelAccessDefault(ownerDID: string, modelID: string, permissionType: 'read' | 'write'): Promise<StoredAccessRule | null>;
@@ -190,20 +452,89 @@ export type GraphStoreAPI = {
190
452
  * (not deleted) so a stale set arriving after the removal is rejected by HLC.
191
453
  * A type with no existing row is skipped (nothing to tombstone).
192
454
  */
193
- removeUserModelAccessDefaults(ownerDID: string, modelID: string, permissionTypes: Array<string>, hlc: string): Promise<void>;
455
+ removeUserModelAccessDefaults(params: RemoveUserModelAccessDefaultsParams): Promise<void>;
456
+ hasControllerAccessDefaults(ownerDID: string, modelID?: string): Promise<boolean>;
457
+ invalidateAccessDefaultProjections(ownerDID?: string): Promise<void>;
194
458
  /**
195
459
  * Document IDs across every scope, one query per distinct model. Repeating a
196
460
  * (model, owner) pair yields its documents once — the result is a set, and the
197
461
  * caller builds a Merkle tree from it, where a duplicate would be a defect.
198
462
  */
199
- getDocumentIDsForScope(scopes: Array<{
200
- modelID: string;
201
- ownerDID: string;
202
- }>, excludedDocumentIDs?: Array<string>): Promise<Array<string>>;
463
+ /** Read at most this many IDs in total; a caller passes its cap plus one. */
464
+ getDocumentIDsForScope(params: GetDocumentIDsForScopeParams): Promise<Array<string>>;
203
465
  getDocumentMetadataForSync(modelID: string, documentIDs: Array<string>): Promise<Array<{
204
466
  id: string;
205
467
  model: string;
206
468
  owner: string;
207
469
  }>>;
470
+ /**
471
+ * One keyset page of stored documents matching any rule, in bytewise
472
+ * document-ID order, each with the sorted IDs of its applied mutations.
473
+ * Pending and rejected mutations are not included. One statement per model
474
+ * reads membership and mutation IDs together, and the per-model pages are
475
+ * merged; no statement binds a list of document IDs.
476
+ */
477
+ listDocumentLeafPage(params: DocumentLeafPageParams): Promise<Array<DocumentLeafRow>>;
478
+ /** A document's applied mutation log entries, in HLC order. */
479
+ getAppliedMutations(documentID: string): Promise<Array<MutationLogEntry>>;
480
+ /**
481
+ * Each document's applied mutation log entries, in HLC order, keyed by document ID. A
482
+ * document with none is absent.
483
+ */
484
+ getAppliedMutationsForDocuments(documentIDs: Iterable<string>): Promise<Map<string, Array<MutationLogEntry>>>;
208
485
  };
209
- export declare function createGraphStore(db: Kysely<GraphTables>, adapter: Adapter): GraphStoreAPI;
486
+ /**
487
+ * Resolves once the root's scope scheduler has no timer armed, no recompute running and nothing
488
+ * queued. While recomputes keep failing it keeps retrying, so this does not resolve.
489
+ */
490
+ export declare function scopeSchedulerIdle(root: StoreProvider): Promise<void>;
491
+ /** The sticky ref a custodian pins a document with: the catalog ID part holds no `:`. */
492
+ export declare function custodyPinRef(guard: CustodyGuard): string;
493
+ export declare function isCustodyPinRef(ref: string): boolean;
494
+ export declare function catalogRuleRef(catalog: ScopeCatalogRef, ruleID: string): string;
495
+ /**
496
+ * Whether a ref is one of the catalog's rule refs. Catalog IDs may contain `:`, so a prefix
497
+ * alone would also claim the refs of a catalog whose ID extends this one.
498
+ */
499
+ export declare function isCatalogRuleRef(ref: string, catalog: ScopeCatalogRef): boolean;
500
+ /** A document holding a ref that may be one of a catalog's rule refs. */
501
+ export type CatalogRefHolder = {
502
+ document_id: string;
503
+ ref: string;
504
+ };
505
+ /**
506
+ * The refs that start with a catalog's rule-ref prefix: a bytewise range rather
507
+ * than LIKE, whose wildcards and escape character catalog IDs may contain.
508
+ * On a Postgres adapter it compares `ref collate "C"`, which the ref index is
509
+ * built on. The caller keeps only the catalog's own refs with
510
+ * `isCatalogRuleRef`. Exported so the plan of the exact statement can be
511
+ * checked.
512
+ */
513
+ export declare function catalogRefHoldersQuery(db: Kysely<GraphTables>, adapter: Adapter, catalog: ScopeCatalogRef): import("kysely").SelectQueryBuilder<GraphTables, "kubun_graph_document_refs", {
514
+ document_id: string;
515
+ ref: string;
516
+ }>;
517
+ /** One row of a document-leaf page: a document and one applied mutation of it, if any. */
518
+ type LeafPageSQLRow = {
519
+ id: string;
520
+ owner: string;
521
+ authored_here: unknown;
522
+ mutation_hash: string | null;
523
+ };
524
+ /**
525
+ * The statement one model's keyset leaf page runs: matching documents after
526
+ * `after` (and up to `through`) in bytewise ID order, at most `limit`, each with
527
+ * whether it was created here and joined to its applied mutations. Exported so
528
+ * the plan of the exact statement can be checked.
529
+ */
530
+ export declare function documentLeafPageStatement(params: {
531
+ modelID: string;
532
+ rules: ReadonlyArray<DocumentLeafRule>;
533
+ after: string | null;
534
+ through: string | null;
535
+ limit: number;
536
+ postgres: boolean;
537
+ groupReadPredicate?: RawBuilder<boolean>;
538
+ }): RawBuilder<LeafPageSQLRow>;
539
+ export declare function createGraphStore(db: Kysely<GraphTables>, adapter: Adapter, context?: StoreAPIContext): GraphStoreAPI;
540
+ export {};