@kubun/store-p2p 0.11.0 → 0.12.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,254 +1,28 @@
1
1
  import type { Adapter } from '@kubun/db-adapter';
2
2
  import type { Kysely } from 'kysely';
3
- import type { Circle, CircleMember, ControlLedgerEntry, Group, GroupMember, GroupMLSState, Hub, InsertCircle, InsertCircleMember, InsertControlLedgerEntry, InsertGroup, InsertGroupDelegation, InsertGroupHub, InsertGroupMember, InsertGroupMLSState, InsertHub, InsertSyncPeer, P2PStoreTables, PaginatedResult, PaginationParams, SyncPeer } from './tables.js';
4
- export type P2PStoreAPI = {
5
- createGroup(group: InsertGroup): Promise<void>;
6
- getGroup(id: string): Promise<Group | undefined>;
7
- updateGroup(id: string, update: Partial<Pick<InsertGroup, 'name' | 'description' | 'hlc'>>): Promise<void>;
8
- deleteGroup(id: string): Promise<void>;
9
- listGroups(): Promise<Array<Group>>;
10
- listGroupsOffsetPaged(params: PaginationParams): Promise<PaginatedResult<Group>>;
11
- addGroupMember(member: InsertGroupMember): Promise<void>;
12
- /**
13
- * Add (or revive) several members in a single multi-row insert. Same
14
- * re-add-clears-tombstone semantics as {@link addGroupMember}, applied to every
15
- * row. A single statement keeps the whole batch atomic and avoids issuing one
16
- * round-trip per member on a single-connection adapter. A no-op for an empty list.
17
- */
18
- addGroupMembers(members: Array<InsertGroupMember>): Promise<void>;
19
- /**
20
- * Soft-delete a member by tombstoning the row with `hlc`. Delegates to
21
- * `markMemberRemoved`: earliest-wins LWW, ignore-if-absent. The row stays in
22
- * the table (read queries exclude it) so the apply-time check can compare a
23
- * mutation's HLC against the removal HLC.
24
- */
25
- removeGroupMember(groupID: string, memberDID: string, hlc: string): Promise<void>;
26
- /**
27
- * Tombstone an existing membership row at `hlc` using earliest-wins LWW: the
28
- * removal sticks only when no removal is recorded yet, or when `hlc` precedes
29
- * the stored removal HLC. A removal for a `(groupID, memberDID)` with no local
30
- * row is a no-op — absence means there is no member to tombstone, and no row
31
- * is created.
32
- */
33
- markMemberRemoved(groupID: string, memberDID: string, hlc: string): Promise<void>;
34
- /**
35
- * Read a membership row's removal state, including tombstoned rows. This is
36
- * the only query that sees soft-deleted members. Returns `undefined` when no
37
- * row exists; `{ removed_at_hlc: null }` for an active member; and
38
- * `{ removed_at_hlc: <hlc> }` for a removed one.
39
- */
40
- getMemberRemoval(groupID: string, memberDID: string): Promise<{
41
- removed_at_hlc: string | null;
42
- } | undefined>;
43
- getGroupMember(groupID: string, memberDID: string): Promise<GroupMember | undefined>;
44
- isGroupMember(groupID: string, memberDID: string): Promise<boolean>;
45
- /**
46
- * Whether `did` was a member of `groupID` at the given HLC, ignoring role.
47
- * Mirrors {@link isGroupAdmin}'s removal-boundary semantics without the
48
- * `role = 'admin'` filter: with no `atHLC`, tombstoned rows are excluded
49
- * (`removed_at_hlc IS NULL`); with `atHLC`, a member removed at HLC `R` still
50
- * counts iff `atHLC < R`. Conjoined with ledger admin authority so an
51
- * MLS-removed member loses admin regardless of the ledger.
52
- */
53
- isGroupMemberAtHLC(groupID: string, did: string, atHLC?: string): Promise<boolean>;
54
- /**
55
- * Whether `did` holds an active `admin` role in `groupID`. With no `atHLC`,
56
- * tombstoned rows are ignored (`removed_at_hlc IS NULL`) — the apply-time
57
- * admin check. With `atHLC`, an admin removed at HLC `R` still counts iff the
58
- * supplied HLC strictly precedes `R` (`atHLC < R`): a write claiming a moment
59
- * before the removal is admitted, one at or after it is denied. This mirrors
60
- * the membership boundary in `checkMembership` (which denies at
61
- * `removed_at_hlc <= mutationHLC`), so the two checks agree on the
62
- * remove-boundary instant.
63
- */
64
- isGroupAdmin(groupID: string, did: string, atHLC?: string): Promise<boolean>;
65
- /**
66
- * Overlay the role column for an existing member. Update-only: a grant for a
67
- * DID with no membership row is a no-op, since membership is MLS-native and a
68
- * non-member can hold no role. Never touches `hlc` or `removed_at_hlc`, so it
69
- * neither churns membership LWW nor resurrects a tombstone — it is purely the
70
- * projection target for the admin-roster fold.
71
- */
72
- setGroupMemberRole(groupID: string, memberDID: string, role: string): Promise<void>;
73
- isMemberOfAnyGroup(viewerDID: string, groupIDs: Array<string>): Promise<boolean>;
74
- listGroupMembers(groupID: string): Promise<Array<GroupMember>>;
75
- getGroupsForMember(memberDID: string): Promise<Array<Group>>;
76
- /**
77
- * Batch fetch groups for multiple members. Returns a Map keyed by
78
- * `memberDID`; values are arrays of groups for that member. Members with
79
- * no groups are absent from the map. Single SQL query joining
80
- * `group_members` to `groups`. Empty input short-circuits with an empty
81
- * Map (no SQL).
82
- *
83
- * Caller is responsible for deduplicating DIDs.
84
- */
85
- getGroupsForMembers(memberDIDs: Array<string>): Promise<Map<string, Array<Group>>>;
86
- createCircle(circle: InsertCircle): Promise<void>;
87
- /**
88
- * Active-only by default (`removed_at_hlc IS NULL`). Pass
89
- * `{ includeRemoved: true }` to also see tombstoned circles — used by the
90
- * apply path so a `circle:create`/`circle:update` can HLC-compare against a
91
- * tombstoned row and resurrect it, and by the catch-up responder.
92
- */
93
- getCircle(id: string, options?: {
94
- includeRemoved?: boolean;
95
- }): Promise<Circle | undefined>;
96
- /**
97
- * Batch fetch circles by ID. Returns a Map keyed by ID; missing IDs are
98
- * absent from the map. Single SQL query with `WHERE id IN (...)`. Empty
99
- * input short-circuits with an empty Map (no SQL).
100
- *
101
- * Caller is responsible for deduplicating IDs.
102
- */
103
- getCircles(ids: Array<string>): Promise<Map<string, Circle>>;
104
- updateCircle(id: string, update: Partial<Pick<InsertCircle, 'name' | 'description' | 'catalog_ids' | 'hlc'>>): Promise<void>;
105
- deleteCircle(id: string): Promise<void>;
106
- /**
107
- * Tombstone an existing circle row at `hlc` using earliest-wins LWW: the
108
- * removal sticks only when no removal is recorded yet, or when `hlc` precedes
109
- * the stored removal HLC. A removal for a `circleID` with no local row is a
110
- * no-op — absence means there is no circle to tombstone, and no row is
111
- * created.
112
- */
113
- markCircleRemoved(circleID: string, hlc: string): Promise<void>;
114
- /**
115
- * Active-only by default (`removed_at_hlc IS NULL`). Pass
116
- * `{ includeRemoved: true }` to include tombstoned circles for the catch-up
117
- * responder.
118
- */
119
- listCirclesByGroup(groupID: string, options?: {
120
- includeRemoved?: boolean;
121
- }): Promise<Array<Circle>>;
122
- addCircleMember(member: InsertCircleMember): Promise<void>;
123
- /**
124
- * Soft-delete a circle member by tombstoning the row with `hlc`: earliest-wins
125
- * LWW, ignore-if-absent. The row stays in the table so the removal HLC stays
126
- * comparable.
127
- */
128
- removeCircleMember(circleID: string, memberDID: string, hlc: string): Promise<void>;
129
- getCircleMember(circleID: string, memberDID: string): Promise<CircleMember | undefined>;
130
- /**
131
- * Active-only by default (`removed_at_hlc IS NULL`). Pass
132
- * `{ includeRemoved: true }` to include tombstoned members for the catch-up
133
- * responder.
134
- */
135
- listCircleMembers(circleID: string, options?: {
136
- includeRemoved?: boolean;
137
- }): Promise<Array<CircleMember>>;
138
- getCirclesForMember(memberDID: string): Promise<Array<Circle>>;
139
- isMemberOfAnyCircle(memberDID: string, circleIDs: Array<string>): Promise<boolean>;
140
- /**
141
- * Fold-authoritative circle projection write: unconditionally set the row for
142
- * `id` to the supplied derived state. Distinct from the LWW writers —
143
- * `createCircle` (insert-only), `updateCircle` (update-only, clears the
144
- * tombstone) and `markCircleRemoved` (earliest-wins LWW). A reproject derives
145
- * `hlc` from the winning active entry and `removedAtHLC` from the winning
146
- * tombstone (or null when active), then overwrites every projected column so
147
- * the row equals exactly what the ledger fold decides regardless of prior
148
- * contents. A removed circle keeps a tombstone row (`removed_at_hlc` set), not
149
- * a hard delete, so `includeRemoved` reads and the read-ACL joins keep working.
150
- */
151
- reconcileCircleProjection(params: {
152
- id: string;
153
- groupID: string;
154
- name: string;
155
- description: string;
156
- catalogIDs: Array<string>;
157
- hlc: string;
158
- removedAtHLC: string | null;
159
- }): Promise<void>;
160
- /**
161
- * Fold-authoritative circle-member projection write, the member-row analogue
162
- * of {@link reconcileCircleProjection}: unconditionally upsert the
163
- * `(circleID, memberDID)` row to the derived `(role, hlc, removedAtHLC)` state.
164
- * A removed member keeps a tombstone row so the read-ACL joins still gate on
165
- * the marker.
166
- */
167
- reconcileCircleMemberProjection(params: {
168
- circleID: string;
169
- memberDID: string;
170
- role: string;
171
- hlc: string;
172
- removedAtHLC: string | null;
173
- }): Promise<void>;
174
- /**
175
- * Record the group a delegation `jti` was granted in. Idempotent on `jti`
176
- * (the primary key): a re-record for a `jti` already present is a no-op. The
177
- * delegation token itself lives in the core delegation store; this edge only
178
- * carries the group association the p2p layer needs.
179
- */
180
- addGroupDelegation(edge: InsertGroupDelegation): Promise<void>;
181
- /**
182
- * Resolve the group a delegation `jti` was granted in, or `null` when no edge
183
- * exists. Used to target revoke broadcasts and to enrich revocation listings
184
- * with their originating group.
185
- */
186
- getGroupIDByJTI(jti: string): Promise<string | null>;
187
- /**
188
- * Batch variant of {@link getGroupIDByJTI}: resolve the group each `jti` was
189
- * granted in, returning a `Map` keyed by `jti`. A `jti` with no edge is absent
190
- * from the map. Used to enrich revocation listings without an N+1 lookup.
191
- */
192
- getGroupIDsByJTIs(jtis: Array<string>): Promise<Map<string, string>>;
193
- saveMLSState(state: InsertGroupMLSState): Promise<void>;
194
- getMLSState(groupID: string, deviceID: string): Promise<GroupMLSState | undefined>;
195
- deleteMLSState(groupID: string, deviceID: string): Promise<void>;
196
- listMLSStates(groupID: string): Promise<Array<GroupMLSState>>;
197
- deleteGroupMLSStates(groupID: string): Promise<void>;
198
- addSyncPeer(peer: InsertSyncPeer): Promise<void>;
199
- getSyncPeer(peerDID: string): Promise<SyncPeer | undefined>;
200
- listSyncPeers(): Promise<Array<SyncPeer>>;
201
- updateSyncPeer(peerDID: string, update: Partial<InsertSyncPeer>): Promise<void>;
202
- removeSyncPeer(peerDID: string): Promise<void>;
203
- recordBroadcast(entry: {
204
- docID: string;
205
- version: string;
206
- mlsGroupID: string;
207
- }): Promise<boolean>;
208
- hasBroadcast(entry: {
209
- docID: string;
210
- version: string;
211
- mlsGroupID: string;
212
- }): Promise<boolean>;
213
- /**
214
- * Append a verified ledger entry. Idempotent on `entry_id` (the token
215
- * digest): a re-delivered entry is a no-op. `issuer_did` is normalized on
216
- * write. Returns `true` when a new row was inserted, `false` on a duplicate.
217
- */
218
- appendLedgerEntry(entry: InsertControlLedgerEntry): Promise<boolean>;
219
- /** All ledger entries for a group, ordered by `(hlc, entry_id)`. */
220
- getLedgerEntries(groupID: string): Promise<Array<ControlLedgerEntry>>;
221
- /** Ledger entries for a group filtered by `type`, ordered by `(hlc, entry_id)`. */
222
- getLedgerEntriesByType(groupID: string, type: string): Promise<Array<ControlLedgerEntry>>;
223
- /**
224
- * The HLC through which `projection` has been folded for `groupID`, or `null`
225
- * when no projection has run yet. A caller treats an entry whose HLC is `>`
226
- * this value as in-order (apply incrementally); `<=` or `null` means the
227
- * cached projection is no longer a correct fold-through-this-entry and must be
228
- * rebuilt from the whole ledger.
229
- */
230
- getProjectionWatermark(groupID: string, projection: string): Promise<string | null>;
231
- /**
232
- * Set the `(groupID, projection)` watermark to `hlc`. Overwrite-on-conflict:
233
- * the row is unconditionally replaced. Callers must only advance the watermark
234
- * — the incremental path passes a value strictly newer than the current one,
235
- * and the reproject path passes the true max over the folded ledger.
236
- */
237
- setProjectionWatermark(groupID: string, projection: string, hlc: string): Promise<void>;
238
- insertHub(hub: InsertHub): Promise<void>;
239
- getHub(id: string): Promise<Hub | undefined>;
240
- getHubByURL(url: string): Promise<Hub | undefined>;
241
- updateHub(id: string, update: Partial<Pick<InsertHub, 'label' | 'url' | 'server_did'>>): Promise<void>;
242
- deleteHub(id: string): Promise<void>;
243
- listHubs(): Promise<Array<Hub>>;
244
- insertGroupHub(binding: InsertGroupHub): Promise<boolean>;
245
- deleteGroupHub(params: {
246
- groupID: string;
247
- hubID: string;
248
- }): Promise<boolean>;
249
- listGroupIDsByHubID(hubID: string): Promise<Array<string>>;
250
- listHubsByGroupID(groupID: string): Promise<Array<Hub>>;
251
- listGroupsByHubID(hubID: string): Promise<Array<Group>>;
252
- deleteGroupHubsByHubID(hubID: string): Promise<void>;
253
- };
3
+ import { type AppLaneOps } from './ops/app-lane.js';
4
+ import { type CircleOps } from './ops/circles.js';
5
+ import { type ControlOps } from './ops/control.js';
6
+ import { type DelegationOps } from './ops/delegations.js';
7
+ import { type DiscoveryOps } from './ops/discovery.js';
8
+ import { type GroupOps } from './ops/groups.js';
9
+ import { type HubOps } from './ops/hubs.js';
10
+ import { type MLSOps } from './ops/mls.js';
11
+ import { type SyncOps } from './ops/sync.js';
12
+ import type { P2PStoreTables } from './tables.js';
13
+ export type { AppLaneOps } from './ops/app-lane.js';
14
+ export type { CircleOps } from './ops/circles.js';
15
+ export type { CommitJournalEntryKind, ControlOps, ControlRequestKind, ControlRequestOutcome, } from './ops/control.js';
16
+ export type { DelegationOps } from './ops/delegations.js';
17
+ export type { DiscoveryOps, PutPendingJoinRequestParams, SetLocalPeerProfileParams, UpsertPeerAnnouncementParams, } from './ops/discovery.js';
18
+ export type { GroupOps } from './ops/groups.js';
19
+ export type { HubOps } from './ops/hubs.js';
20
+ export type { MLSOps } from './ops/mls.js';
21
+ export type { SyncOps } from './ops/sync.js';
22
+ /**
23
+ * The store's whole surface, one slice per domain. Each slice module owns its
24
+ * tables and its own helpers; this intersection is the only coupling between
25
+ * them, so a caller still sees one flat API.
26
+ */
27
+ export type P2PStoreAPI = GroupOps & CircleOps & DelegationOps & MLSOps & SyncOps & ControlOps & AppLaneOps & HubOps & DiscoveryOps;
254
28
  export declare function createP2PStore(db: Kysely<P2PStoreTables>, adapter: Adapter): P2PStoreAPI;