@kubun/store-graph 0.17.0 → 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,53 @@
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
- import type { AggregateDocumentsParams, AggregateValues, DocumentModel, DocumentNode, GroupedAggregateDocumentsParams, GroupedAggregateValue } from '@kubun/protocol';
4
- import type { Kysely } from 'kysely';
5
- import type { AccessLevel, Catalog, ClusterDefinitionRow, ClusterModel, CreateDocumentParams, CreateGraphParams, GraphModel, GraphModelWithRecord, GraphTables, InsertCatalog, InsertClusterModel, InsertMutationLogEntry, ListDocumentsParams, MutationLogEntry, QueryDocumentsParams, QueryDocumentsResult, SaveDocumentParams, SearchDocumentResult, SearchDocumentsParams } from './tables.js';
4
+ import type { AggregateDocumentsParams, AggregateValues, DocumentFieldsMeta, DocumentModel, DocumentModelSchema, DocumentNode, GroupedAggregateDocumentsParams, GroupedAggregateValue } from '@kubun/protocol';
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';
11
+ export declare function documentModelFromTable(row: DocumentModelRow): DocumentModel;
12
+ export declare function documentModelToTable(adapter: Adapter, id: string, model: DocumentModel): {
13
+ id: string;
14
+ version: "1.0";
15
+ name: string;
16
+ behavior: "unique" | "default" | "interface";
17
+ unique_fields: string[] | null;
18
+ interfaces: Array<string>;
19
+ schema: DocumentModelSchema;
20
+ fields_meta: DocumentFieldsMeta;
21
+ provider_merge_policy: {
22
+ group: Array<string>;
23
+ } | null;
24
+ date_field: string | null;
25
+ resolved_date_field: string | null;
26
+ };
27
+ /**
28
+ * Reads a document's `field_values` cell on the given transaction and
29
+ * delegates the merge-policy liveness comparison to `@kubun/mutation`'s pure
30
+ * `winnerStateDigestOf`. Returns the winning live `$setFetch` digest, the
31
+ * constant deleted-state digest when the delete side wins, or `null` when the
32
+ * document (or its fetch-order registers) is absent.
33
+ *
34
+ * Because graph and connector tables share one DB connection (the KubunDB
35
+ * coordinator), a caller with its own transaction over a wider `DB` type
36
+ * passes it here cast to `Kysely<GraphTables>` (the same pattern
37
+ * `addDocumentModel` above already relies on) so the read joins that
38
+ * transaction rather than opening a second connection.
39
+ */
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>>;
6
51
  /**
7
52
  * Persisted access rule shape as stored in `user_model_access_defaults`.
8
53
  */
@@ -28,7 +73,108 @@ export type StoredAccessDefaultRow = StoredAccessRule & {
28
73
  */
29
74
  hlc: string | null;
30
75
  };
31
- 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 & {
32
178
  getDocument(id: DocumentID | string): Promise<DocumentNode | null>;
33
179
  /**
34
180
  * Batch fetch documents, returning a Map keyed by the document ID string. A
@@ -50,18 +196,200 @@ export type GraphStoreAPI = {
50
196
  * order from `addDocumentModel` (stable for a given graph creation).
51
197
  */
52
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>>;
53
206
  createDocument(params: CreateDocumentParams): Promise<DocumentNode>;
54
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>;
55
377
  queryDocuments(params: QueryDocumentsParams): Promise<QueryDocumentsResult>;
56
378
  aggregateDocuments(params: AggregateDocumentsParams): Promise<AggregateValues>;
57
379
  groupedAggregateDocuments(params: GroupedAggregateDocumentsParams): Promise<Array<GroupedAggregateValue>>;
58
380
  listDocuments(params: ListDocumentsParams): Promise<Array<DocumentNode>>;
59
381
  listDocumentModelIDs(): Promise<Array<string>>;
60
- getDistinctOwnersForModel(modelID: string): Promise<Array<string>>;
61
382
  queryDocumentsByOwner(modelID: string, ownerDID: string): Promise<Array<DocumentNode>>;
62
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'>;
63
388
  hasMutationHash(hash: string): Promise<boolean>;
64
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>;
65
393
  getMutationLogForDocuments(documentIDs: Array<string>): Promise<Array<MutationLogEntry>>;
66
394
  getPendingMutations(documentID: string): Promise<Array<MutationLogEntry>>;
67
395
  /**
@@ -74,6 +402,8 @@ export type GraphStoreAPI = {
74
402
  getMaxMutationHLC(): Promise<string | null>;
75
403
  getFieldHLCs(documentID: string): Promise<Record<string, string> | null>;
76
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>>>;
77
407
  updateFieldHLCs(docID: DocumentID, fieldHLCs: Record<string, string>, fieldValues: Record<string, unknown>): Promise<void>;
78
408
  listGraphs(): Promise<Array<GraphModel>>;
79
409
  createGraph(params: CreateGraphParams): Promise<string>;
@@ -85,49 +415,9 @@ export type GraphStoreAPI = {
85
415
  getClusters(ids: Array<string>): Promise<Record<string, unknown>>;
86
416
  createSearchIndex(modelID: string, fields: Array<string>): Promise<void>;
87
417
  dropSearchIndex(modelID: string): Promise<void>;
88
- updateSearchEntry(modelID: string, documentID: string, data: Record<string, unknown>, fields: Array<string>): Promise<void>;
418
+ updateSearchEntry(params: UpdateSearchEntryParams): Promise<void>;
89
419
  removeSearchEntry(modelID: string, documentID: string): Promise<void>;
90
420
  searchDocuments(params: SearchDocumentsParams): Promise<Array<SearchDocumentResult>>;
91
- /** Create an own catalog: stored active (scopes sync) with null provenance. */
92
- createCatalog(catalog: InsertCatalog): Promise<void>;
93
- /**
94
- * Insert a discovered catalog as known (not active) with first-discovery
95
- * provenance. Idempotent on the id: an already-stored catalog is left
96
- * untouched, so its local `active` flag and first-discovery source columns
97
- * are never overwritten by a later re-discovery.
98
- */
99
- upsertDiscoveredCatalog(params: {
100
- catalog: InsertCatalog;
101
- sourceGroupID: string | null;
102
- sourceCircleID: string | null;
103
- }): Promise<void>;
104
- /** Toggle a catalog's local sync activation. */
105
- setCatalogActive(id: string, active: boolean): Promise<void>;
106
- /** Flip several catalogs together — one statement, not one per id. */
107
- setCatalogsActive(ids: Array<string>, active: boolean): Promise<void>;
108
- /**
109
- * All locally-stored catalog rows regardless of owner, optionally filtered by
110
- * activation state. Backs the viewer's known-catalogs surface.
111
- */
112
- listStoredCatalogs(filter?: {
113
- active?: boolean;
114
- }): Promise<Array<Catalog>>;
115
- getCatalog(id: string): Promise<Catalog | undefined>;
116
- /**
117
- * Batch fetch catalogs by ID. Returns a Map keyed by ID; missing IDs are
118
- * absent from the map. Single SQL query with `WHERE id IN (...)`. Empty
119
- * input short-circuits with an empty Map (no SQL).
120
- *
121
- * Caller is responsible for deduplicating IDs (e.g. via `Array.from(new Set(...))`).
122
- */
123
- getCatalogs(ids: Array<string>): Promise<Map<string, Catalog>>;
124
- updateCatalog(id: string, update: Partial<Pick<InsertCatalog, 'name' | 'description' | 'filter_criteria' | 'hlc' | 'signed_token'>>): Promise<void>;
125
- deleteCatalog(id: string): Promise<void>;
126
- listCatalogs(ownerDID: string): Promise<Array<Catalog>>;
127
- resolveCatalogScope(catalogID: string): Promise<{
128
- models: Array<string> | undefined;
129
- owners: Array<string> | undefined;
130
- }>;
131
421
  registerClusterModels(clusterID: string, entries: Array<InsertClusterModel>): Promise<void>;
132
422
  /** A tombstoned (removed) rule reads as absent. */
133
423
  getUserModelAccessDefault(ownerDID: string, modelID: string, permissionType: 'read' | 'write'): Promise<StoredAccessRule | null>;
@@ -162,20 +452,89 @@ export type GraphStoreAPI = {
162
452
  * (not deleted) so a stale set arriving after the removal is rejected by HLC.
163
453
  * A type with no existing row is skipped (nothing to tombstone).
164
454
  */
165
- 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>;
166
458
  /**
167
459
  * Document IDs across every scope, one query per distinct model. Repeating a
168
460
  * (model, owner) pair yields its documents once — the result is a set, and the
169
461
  * caller builds a Merkle tree from it, where a duplicate would be a defect.
170
462
  */
171
- getDocumentIDsForScope(scopes: Array<{
172
- modelID: string;
173
- ownerDID: string;
174
- }>, 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>>;
175
465
  getDocumentMetadataForSync(modelID: string, documentIDs: Array<string>): Promise<Array<{
176
466
  id: string;
177
467
  model: string;
178
468
  owner: string;
179
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>>>;
180
485
  };
181
- 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 {};