@kubun/store-p2p 0.10.1 → 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 +26 -258
- package/lib/api.js +22 -1
- package/lib/definition.js +7 -1
- package/lib/index.d.ts +2 -3
- package/lib/index.js +6 -1
- package/lib/migrations.d.ts +21 -0
- package/lib/migrations.js +185 -1
- package/lib/ops/app-lane.d.ts +36 -0
- package/lib/ops/app-lane.js +38 -0
- package/lib/ops/circles.d.ts +153 -0
- package/lib/ops/circles.js +269 -0
- package/lib/ops/control.d.ts +118 -0
- package/lib/ops/control.js +110 -0
- package/lib/ops/delegations.d.ts +25 -0
- package/lib/ops/delegations.js +27 -0
- package/lib/ops/discovery.d.ts +92 -0
- package/lib/ops/discovery.js +90 -0
- package/lib/ops/groups.d.ts +132 -0
- package/lib/ops/groups.js +259 -0
- package/lib/ops/hubs.d.ts +56 -0
- package/lib/ops/hubs.js +222 -0
- package/lib/ops/mls.d.ts +21 -0
- package/lib/ops/mls.js +50 -0
- package/lib/ops/sync.d.ts +22 -0
- package/lib/ops/sync.js +50 -0
- package/lib/tables.d.ts +114 -45
- package/lib/tables.js +2 -1
- package/package.json +19 -18
- package/lib/revocation-backend.d.ts +0 -3
- package/lib/revocation-backend.js +0 -1
package/lib/api.d.ts
CHANGED
|
@@ -1,260 +1,28 @@
|
|
|
1
1
|
import type { Adapter } from '@kubun/db-adapter';
|
|
2
|
-
import { type Logger } from '@kubun/logger';
|
|
3
2
|
import type { Kysely } from 'kysely';
|
|
4
|
-
import
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
* the only query that sees soft-deleted members. Returns `undefined` when no
|
|
31
|
-
* row exists; `{ removed_at_hlc: null }` for an active member; and
|
|
32
|
-
* `{ removed_at_hlc: <hlc> }` for a removed one.
|
|
33
|
-
*/
|
|
34
|
-
getMemberRemoval(groupID: string, memberDID: string): Promise<{
|
|
35
|
-
removed_at_hlc: string | null;
|
|
36
|
-
} | undefined>;
|
|
37
|
-
getGroupMember(groupID: string, memberDID: string): Promise<GroupMember | undefined>;
|
|
38
|
-
isGroupMember(groupID: string, memberDID: string): Promise<boolean>;
|
|
39
|
-
isMemberOfAnyGroup(viewerDID: string, groupIDs: Array<string>): Promise<boolean>;
|
|
40
|
-
listGroupMembers(groupID: string): Promise<Array<GroupMember>>;
|
|
41
|
-
getGroupsForMember(memberDID: string): Promise<Array<Group>>;
|
|
42
|
-
/**
|
|
43
|
-
* Batch fetch groups for multiple members. Returns a Map keyed by
|
|
44
|
-
* `memberDID`; values are arrays of groups for that member. Members with
|
|
45
|
-
* no groups are absent from the map. Single SQL query joining
|
|
46
|
-
* `group_members` to `groups`. Empty input short-circuits with an empty
|
|
47
|
-
* Map (no SQL).
|
|
48
|
-
*
|
|
49
|
-
* Caller is responsible for deduplicating DIDs.
|
|
50
|
-
*/
|
|
51
|
-
getGroupsForMembers(memberDIDs: Array<string>): Promise<Map<string, Array<Group>>>;
|
|
52
|
-
createCircle(circle: InsertCircle): Promise<void>;
|
|
53
|
-
/**
|
|
54
|
-
* Active-only by default (`removed_at_hlc IS NULL`). Pass
|
|
55
|
-
* `{ includeRemoved: true }` to also see tombstoned circles — used by the
|
|
56
|
-
* apply path so a `circle:create`/`circle:update` can HLC-compare against a
|
|
57
|
-
* tombstoned row and resurrect it, and by the catch-up responder.
|
|
58
|
-
*/
|
|
59
|
-
getCircle(id: string, options?: {
|
|
60
|
-
includeRemoved?: boolean;
|
|
61
|
-
}): Promise<Circle | undefined>;
|
|
62
|
-
/**
|
|
63
|
-
* Batch fetch circles by ID. Returns a Map keyed by ID; missing IDs are
|
|
64
|
-
* absent from the map. Single SQL query with `WHERE id IN (...)`. Empty
|
|
65
|
-
* input short-circuits with an empty Map (no SQL).
|
|
66
|
-
*
|
|
67
|
-
* Caller is responsible for deduplicating IDs.
|
|
68
|
-
*/
|
|
69
|
-
getCircles(ids: Array<string>): Promise<Map<string, Circle>>;
|
|
70
|
-
updateCircle(id: string, update: Partial<Pick<InsertCircle, 'name' | 'description' | 'catalog_ids' | 'hlc'>>): Promise<void>;
|
|
71
|
-
deleteCircle(id: string): Promise<void>;
|
|
72
|
-
/**
|
|
73
|
-
* Tombstone an existing circle row at `hlc` using earliest-wins LWW: the
|
|
74
|
-
* removal sticks only when no removal is recorded yet, or when `hlc` precedes
|
|
75
|
-
* the stored removal HLC. A removal for a `circleID` with no local row is a
|
|
76
|
-
* no-op — absence means there is no circle to tombstone, and no row is
|
|
77
|
-
* created.
|
|
78
|
-
*/
|
|
79
|
-
markCircleRemoved(circleID: string, hlc: string): Promise<void>;
|
|
80
|
-
/**
|
|
81
|
-
* Active-only by default (`removed_at_hlc IS NULL`). Pass
|
|
82
|
-
* `{ includeRemoved: true }` to include tombstoned circles for the catch-up
|
|
83
|
-
* responder.
|
|
84
|
-
*/
|
|
85
|
-
listCirclesByGroup(groupID: string, options?: {
|
|
86
|
-
includeRemoved?: boolean;
|
|
87
|
-
}): Promise<Array<Circle>>;
|
|
88
|
-
addCircleMember(member: InsertCircleMember): Promise<void>;
|
|
89
|
-
/**
|
|
90
|
-
* Soft-delete a circle member by tombstoning the row with `hlc`: earliest-wins
|
|
91
|
-
* LWW, ignore-if-absent. The row stays in the table so the removal HLC stays
|
|
92
|
-
* comparable.
|
|
93
|
-
*/
|
|
94
|
-
removeCircleMember(circleID: string, memberDID: string, hlc: string): Promise<void>;
|
|
95
|
-
getCircleMember(circleID: string, memberDID: string): Promise<CircleMember | undefined>;
|
|
96
|
-
/**
|
|
97
|
-
* Active-only by default (`removed_at_hlc IS NULL`). Pass
|
|
98
|
-
* `{ includeRemoved: true }` to include tombstoned members for the catch-up
|
|
99
|
-
* responder.
|
|
100
|
-
*/
|
|
101
|
-
listCircleMembers(circleID: string, options?: {
|
|
102
|
-
includeRemoved?: boolean;
|
|
103
|
-
}): Promise<Array<CircleMember>>;
|
|
104
|
-
getCirclesForMember(memberDID: string): Promise<Array<Circle>>;
|
|
105
|
-
isMemberOfAnyCircle(memberDID: string, circleIDs: Array<string>): Promise<boolean>;
|
|
106
|
-
/**
|
|
107
|
-
* Store a delegation token. A re-minted grant covering the same
|
|
108
|
-
* `(grantor, audience, resource)` replaces the existing row when its `hlc`
|
|
109
|
-
* is causally newer; an older or equal `hlc` is ignored. Held rows
|
|
110
|
-
* (`audience` is self) and issued rows (`grantor` is self) are distinct keys
|
|
111
|
-
* and never collide.
|
|
112
|
-
*
|
|
113
|
-
* Returns `true` when the stored row is new or materially different from
|
|
114
|
-
* the prior row (insert, or LWW upsert that changed any of `jti`, `token`,
|
|
115
|
-
* `act`, `exp`, `group_id`). Returns `false` when the call was a no-op:
|
|
116
|
-
* either the incoming `hlc` lost LWW against an existing row, or the LWW
|
|
117
|
-
* upsert won but every content field matched the prior row (identical
|
|
118
|
-
* re-broadcast). Used by emission gates to suppress duplicate
|
|
119
|
-
* `delegationTokenAdded` events on idempotent re-stores.
|
|
120
|
-
*/
|
|
121
|
-
addDelegationToken(token: InsertDelegationToken): Promise<boolean>;
|
|
122
|
-
getDelegationTokens(params: {
|
|
123
|
-
grantor: string;
|
|
124
|
-
audience: string;
|
|
125
|
-
}): Promise<Array<DelegationToken>>;
|
|
126
|
-
/**
|
|
127
|
-
* Return the tokens held by `audience` that are still valid at `atTime`
|
|
128
|
-
* (`exp > atTime`). `atTime` is a Unix epoch in seconds, matching the
|
|
129
|
-
* `exp` column. Expired tokens and tokens addressed to other audiences are
|
|
130
|
-
* excluded.
|
|
131
|
-
*/
|
|
132
|
-
getHeldTokens(params: {
|
|
133
|
-
audience: string;
|
|
134
|
-
atTime: number;
|
|
135
|
-
}): Promise<Array<DelegationToken>>;
|
|
136
|
-
listIssuedTokens(grantor: string): Promise<Array<DelegationToken>>;
|
|
137
|
-
/**
|
|
138
|
-
* Look up a delegation token by its `jti`. `jti` is not part of the schema's
|
|
139
|
-
* composite PK `(grantor, audience, resource)`, so the column is non-unique
|
|
140
|
-
* at the SQL level; in practice every `grantWriteCapability` call mints a
|
|
141
|
-
* fresh random `jti`, so collisions across distinct `(grantor, audience,
|
|
142
|
-
* resource)` tuples do not occur. Returns the first matching row, or `null`
|
|
143
|
-
* when no row exists for the given `jti`.
|
|
144
|
-
*/
|
|
145
|
-
getDelegationTokenByJti(jti: string): Promise<DelegationToken | null>;
|
|
146
|
-
/**
|
|
147
|
-
* Store a revocation record keyed by the revoked capability's `jti`. LWW on
|
|
148
|
-
* `hlc` — a re-broadcast with an older or equal `hlc` is ignored. Returns
|
|
149
|
-
* `true` when the row is new, or when LWW won and any content field
|
|
150
|
-
* (`revoker_did`, `revoked_iat`, `revocation_token`, `verified_at`,
|
|
151
|
-
* `cap_exp`, `group_id`) materially changed. Returns `false` on a no-op
|
|
152
|
-
* (lost LWW, or won LWW with byte-identical content). Used by emission
|
|
153
|
-
* gates to suppress duplicate downstream events on idempotent re-stores.
|
|
154
|
-
*
|
|
155
|
-
* `verified_at` is `null` while the revoker-vs-cap-issuer cross-check is
|
|
156
|
-
* pending; verification flips it via `markRevocationVerified`.
|
|
157
|
-
*/
|
|
158
|
-
addRevocation(input: InsertRevokedCapability): Promise<boolean>;
|
|
159
|
-
getRevocation(jti: string): Promise<RevokedCapability | null>;
|
|
160
|
-
/**
|
|
161
|
-
* Hot-path check used at mutation-apply time. Returns `true` only when a
|
|
162
|
-
* row exists for `jti` AND it has been verified against the cap's issuer
|
|
163
|
-
* (`verified_at IS NOT NULL`). Pending (unverified) revocations do not
|
|
164
|
-
* gate writes; the gate fails open for light clients that do not
|
|
165
|
-
* replicate the revocations table.
|
|
166
|
-
*/
|
|
167
|
-
isRevoked(jti: string): Promise<boolean>;
|
|
168
|
-
/**
|
|
169
|
-
* Return verified revocations that target capabilities held by `audience`.
|
|
170
|
-
* INNER JOIN against `kubun_p2p_delegation_tokens` on `jti` — verified
|
|
171
|
-
* revocations whose cap row is missing locally are excluded, and
|
|
172
|
-
* `audience`/`grantor` are sourced from the cap row. Pending revocations
|
|
173
|
-
* (`verified_at IS NULL`) are not surfaced. No `atTime` filter: verified
|
|
174
|
-
* revocations remain visible to the audience until lifecycle GC removes
|
|
175
|
-
* them, even after the underlying cap has expired.
|
|
176
|
-
*/
|
|
177
|
-
getHeldRevocations(params: {
|
|
178
|
-
audience: string;
|
|
179
|
-
}): Promise<Array<HeldRevocation>>;
|
|
180
|
-
/**
|
|
181
|
-
* Flip a pending revocation to verified. Idempotent: returns `true` only
|
|
182
|
-
* when a previously-pending row was updated; returns `false` when the row
|
|
183
|
-
* is missing or already verified.
|
|
184
|
-
*/
|
|
185
|
-
markRevocationVerified(jti: string, params: {
|
|
186
|
-
cap_exp: number;
|
|
187
|
-
verified_at?: number;
|
|
188
|
-
}): Promise<boolean>;
|
|
189
|
-
getPendingRevocationByJti(jti: string): Promise<RevokedCapability | null>;
|
|
190
|
-
/**
|
|
191
|
-
* Delete a pending revocation row (`verified_at IS NULL`) for the given
|
|
192
|
-
* `jti`. Verified rows are preserved — a verified revocation is binding and
|
|
193
|
-
* must not be removed by this method (use `purgeExpiredRevocations` for
|
|
194
|
-
* lifecycle cleanup). Returns `true` when a pending row was deleted, `false`
|
|
195
|
-
* otherwise (no row, or row already verified).
|
|
196
|
-
*
|
|
197
|
-
* Used by the cap-arrival cross-check: when a held cap arrives whose issuer
|
|
198
|
-
* does not match a pre-existing pending revocation's `revoker_did`, the
|
|
199
|
-
* pending row is treated as a forged broadcast and dropped.
|
|
200
|
-
*/
|
|
201
|
-
deletePendingRevocation(jti: string): Promise<boolean>;
|
|
202
|
-
/**
|
|
203
|
-
* Delete verified revocation rows whose underlying capability expired more
|
|
204
|
-
* than `graceSeconds` ago (`cap_exp + graceSeconds < now`). Rows with a
|
|
205
|
-
* NULL `cap_exp` and pending (unverified) rows are preserved — they have
|
|
206
|
-
* no expiry to compare against. Returns the number of rows deleted.
|
|
207
|
-
*/
|
|
208
|
-
purgeExpiredRevocations(params: {
|
|
209
|
-
graceSeconds: number;
|
|
210
|
-
}): Promise<number>;
|
|
211
|
-
/**
|
|
212
|
-
* Delete pending revocation rows (`verified_at IS NULL`) whose `created_at`
|
|
213
|
-
* is older than `maxAgeSeconds`. Verified rows are preserved regardless of
|
|
214
|
-
* their `created_at` age. Returns the number of rows deleted.
|
|
215
|
-
*
|
|
216
|
-
* A negative `maxAgeSeconds` pushes the threshold into the future and will
|
|
217
|
-
* delete every pending row regardless of age — intended only for test
|
|
218
|
-
* scaffolding (avoids needing raw-SQL `UPDATE` to backdate `created_at`).
|
|
219
|
-
* Do not pass negative values from production code paths.
|
|
220
|
-
*/
|
|
221
|
-
purgeDeadPendingRevocations(params: {
|
|
222
|
-
maxAgeSeconds: number;
|
|
223
|
-
}): Promise<number>;
|
|
224
|
-
saveMLSState(state: InsertGroupMLSState): Promise<void>;
|
|
225
|
-
getMLSState(groupID: string, deviceID: string): Promise<GroupMLSState | undefined>;
|
|
226
|
-
deleteMLSState(groupID: string, deviceID: string): Promise<void>;
|
|
227
|
-
listMLSStates(groupID: string): Promise<Array<GroupMLSState>>;
|
|
228
|
-
deleteGroupMLSStates(groupID: string): Promise<void>;
|
|
229
|
-
addSyncPeer(peer: InsertSyncPeer): Promise<void>;
|
|
230
|
-
getSyncPeer(peerDID: string): Promise<SyncPeer | undefined>;
|
|
231
|
-
listSyncPeers(): Promise<Array<SyncPeer>>;
|
|
232
|
-
updateSyncPeer(peerDID: string, update: Partial<InsertSyncPeer>): Promise<void>;
|
|
233
|
-
removeSyncPeer(peerDID: string): Promise<void>;
|
|
234
|
-
recordBroadcast(entry: {
|
|
235
|
-
docID: string;
|
|
236
|
-
version: string;
|
|
237
|
-
mlsGroupID: string;
|
|
238
|
-
}): Promise<boolean>;
|
|
239
|
-
hasBroadcast(entry: {
|
|
240
|
-
docID: string;
|
|
241
|
-
version: string;
|
|
242
|
-
mlsGroupID: string;
|
|
243
|
-
}): Promise<boolean>;
|
|
244
|
-
insertHub(hub: InsertHub): Promise<void>;
|
|
245
|
-
getHub(id: string): Promise<Hub | undefined>;
|
|
246
|
-
getHubByURL(url: string): Promise<Hub | undefined>;
|
|
247
|
-
updateHub(id: string, update: Partial<Pick<InsertHub, 'label' | 'url' | 'server_did'>>): Promise<void>;
|
|
248
|
-
deleteHub(id: string): Promise<void>;
|
|
249
|
-
listHubs(): Promise<Array<Hub>>;
|
|
250
|
-
insertGroupHub(binding: InsertGroupHub): Promise<boolean>;
|
|
251
|
-
deleteGroupHub(params: {
|
|
252
|
-
groupID: string;
|
|
253
|
-
hubID: string;
|
|
254
|
-
}): Promise<boolean>;
|
|
255
|
-
listGroupIDsByHubID(hubID: string): Promise<Array<string>>;
|
|
256
|
-
listHubsByGroupID(groupID: string): Promise<Array<Hub>>;
|
|
257
|
-
listGroupsByHubID(hubID: string): Promise<Array<Group>>;
|
|
258
|
-
deleteGroupHubsByHubID(hubID: string): Promise<void>;
|
|
259
|
-
};
|
|
260
|
-
export declare function createP2PStore(db: Kysely<P2PStoreTables>, adapter: Adapter, logger?: Logger): P2PStoreAPI;
|
|
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;
|
|
28
|
+
export declare function createP2PStore(db: Kysely<P2PStoreTables>, adapter: Adapter): P2PStoreAPI;
|
package/lib/api.js
CHANGED
|
@@ -1 +1,22 @@
|
|
|
1
|
-
import{getKubunLogger as e}from"@kubun/logger";export function createP2PStore(r,t,u=e("store-p2p")){let i={async createGroup(e){await r.insertInto("kubun_p2p_groups").values({id:e.id,name:e.name,description:e.description,created_by:e.created_by,hlc:e.hlc}).execute()},getGroup:async e=>await r.selectFrom("kubun_p2p_groups").selectAll().where("id","=",e).executeTakeFirst(),async updateGroup(e,u){let i={};null!=u.name&&(i.name=u.name),null!=u.description&&(i.description=u.description),null!=u.hlc&&(i.hlc=u.hlc),i.updated_at=t.encodeTimestamp(new Date),await r.updateTable("kubun_p2p_groups").set(i).where("id","=",e).execute()},async deleteGroup(e){await r.deleteFrom("kubun_p2p_groups").where("id","=",e).execute()},listGroups:async()=>await r.selectFrom("kubun_p2p_groups").selectAll().execute(),async listGroupsOffsetPaged(e){let{limit:t,offset:u}=e,i=r.selectFrom("kubun_p2p_groups").selectAll().orderBy("name");if(null!=u&&(i=i.offset(u)),null!=t){i=i.limit(t+1);let e=await i.execute(),r=e.length>t;return{entries:r?e.slice(0,t):e,hasMore:r}}return{entries:await i.execute(),hasMore:!1}},async addGroupMember(e){await r.insertInto("kubun_p2p_group_members").values({group_id:e.group_id,member_did:e.member_did,role:e.role,hlc:e.hlc}).onConflict(e=>e.columns(["group_id","member_did"]).doUpdateSet(e=>({role:e.ref("excluded.role"),hlc:e.ref("excluded.hlc"),removed_at_hlc:null,updated_at:t.encodeTimestamp(new Date)}))).execute()},async markMemberRemoved(e,u,i){await r.updateTable("kubun_p2p_group_members").set({removed_at_hlc:i,updated_at:t.encodeTimestamp(new Date)}).where("group_id","=",e).where("member_did","=",u).where(e=>e.or([e("removed_at_hlc","is",null),e("removed_at_hlc",">",i)])).execute()},async getMemberRemoval(e,t){let u=await r.selectFrom("kubun_p2p_group_members").select("removed_at_hlc").where("group_id","=",e).where("member_did","=",t).executeTakeFirst();return null==u?void 0:{removed_at_hlc:u.removed_at_hlc}},async removeGroupMember(e,r,t){await i.markMemberRemoved(e,r,t)},getGroupMember:async(e,t)=>await r.selectFrom("kubun_p2p_group_members").selectAll().where("group_id","=",e).where("member_did","=",t).where("removed_at_hlc","is",null).executeTakeFirst(),isGroupMember:async(e,t)=>null!=await r.selectFrom("kubun_p2p_group_members").select("member_did").where("group_id","=",e).where("member_did","=",t).where("removed_at_hlc","is",null).executeTakeFirst(),isMemberOfAnyGroup:async(e,t)=>0!==t.length&&null!=await r.selectFrom("kubun_p2p_group_members").select("group_id").where("member_did","=",e).where("group_id","in",t).where("removed_at_hlc","is",null).limit(1).executeTakeFirst(),listGroupMembers:async e=>await r.selectFrom("kubun_p2p_group_members").selectAll().where("group_id","=",e).where("removed_at_hlc","is",null).execute(),getGroupsForMember:async e=>await r.selectFrom("kubun_p2p_group_members").innerJoin("kubun_p2p_groups","kubun_p2p_groups.id","kubun_p2p_group_members.group_id").selectAll("kubun_p2p_groups").where("kubun_p2p_group_members.member_did","=",e).where("kubun_p2p_group_members.removed_at_hlc","is",null).execute(),async getGroupsForMembers(e){let t=new Map;if(0===e.length)return t;for(let u of(await r.selectFrom("kubun_p2p_group_members").innerJoin("kubun_p2p_groups","kubun_p2p_groups.id","kubun_p2p_group_members.group_id").select(["kubun_p2p_group_members.member_did as member_did","kubun_p2p_groups.id as id","kubun_p2p_groups.name as name","kubun_p2p_groups.description as description","kubun_p2p_groups.created_by as created_by","kubun_p2p_groups.hlc as hlc","kubun_p2p_groups.created_at as created_at","kubun_p2p_groups.updated_at as updated_at"]).where("kubun_p2p_group_members.member_did","in",e).where("kubun_p2p_group_members.removed_at_hlc","is",null).execute())){let{member_did:e,...r}=u,i=t.get(e);null!=i?i.push(r):t.set(e,[r])}return t},async createCircle(e){await r.insertInto("kubun_p2p_circles").values({id:e.id,group_id:e.group_id,name:e.name,description:e.description,catalog_ids:t.encodeJSON(e.catalog_ids),hlc:e.hlc}).execute()},async getCircle(e,t){let u=r.selectFrom("kubun_p2p_circles").selectAll().where("id","=",e);return t?.includeRemoved||(u=u.where("removed_at_hlc","is",null)),await u.executeTakeFirst()},async getCircles(e){let t=new Map;if(0===e.length)return t;for(let u of(await r.selectFrom("kubun_p2p_circles").selectAll().where("id","in",e).execute()))t.set(u.id,u);return t},async updateCircle(e,u){let i={};null!=u.name&&(i.name=u.name),null!=u.description&&(i.description=u.description),null!=u.catalog_ids&&(i.catalog_ids=t.encodeJSON(u.catalog_ids)),null!=u.hlc&&(i.hlc=u.hlc,i.removed_at_hlc=null),i.updated_at=t.encodeTimestamp(new Date),await r.updateTable("kubun_p2p_circles").set(i).where("id","=",e).execute()},async deleteCircle(e){await r.deleteFrom("kubun_p2p_circles").where("id","=",e).execute()},async markCircleRemoved(e,u){await r.updateTable("kubun_p2p_circles").set({removed_at_hlc:u,updated_at:t.encodeTimestamp(new Date)}).where("id","=",e).where(e=>e.or([e("removed_at_hlc","is",null),e("removed_at_hlc",">",u)])).execute()},async listCirclesByGroup(e,t){let u=r.selectFrom("kubun_p2p_circles").selectAll().where("group_id","=",e);return t?.includeRemoved||(u=u.where("removed_at_hlc","is",null)),await u.execute()},async addCircleMember(e){await r.insertInto("kubun_p2p_circle_members").values({circle_id:e.circle_id,member_did:e.member_did,role:e.role,hlc:e.hlc}).onConflict(e=>e.columns(["circle_id","member_did"]).doUpdateSet(e=>({role:e.ref("excluded.role"),hlc:e.ref("excluded.hlc"),removed_at_hlc:null,updated_at:t.encodeTimestamp(new Date)}))).execute()},async removeCircleMember(e,u,i){await r.updateTable("kubun_p2p_circle_members").set({removed_at_hlc:i,updated_at:t.encodeTimestamp(new Date)}).where("circle_id","=",e).where("member_did","=",u).where(e=>e.or([e("removed_at_hlc","is",null),e("removed_at_hlc",">",i)])).execute()},getCircleMember:async(e,t)=>await r.selectFrom("kubun_p2p_circle_members").selectAll().where("circle_id","=",e).where("member_did","=",t).executeTakeFirst(),async listCircleMembers(e,t){let u=r.selectFrom("kubun_p2p_circle_members").selectAll().where("circle_id","=",e);return t?.includeRemoved||(u=u.where("removed_at_hlc","is",null)),await u.execute()},getCirclesForMember:async e=>await r.selectFrom("kubun_p2p_circle_members").innerJoin("kubun_p2p_circles","kubun_p2p_circles.id","kubun_p2p_circle_members.circle_id").selectAll("kubun_p2p_circles").where("kubun_p2p_circle_members.member_did","=",e).where("kubun_p2p_circle_members.removed_at_hlc","is",null).where("kubun_p2p_circles.removed_at_hlc","is",null).execute(),isMemberOfAnyCircle:async(e,t)=>0!==t.length&&null!=await r.selectFrom("kubun_p2p_circle_members").innerJoin("kubun_p2p_circles","kubun_p2p_circles.id","kubun_p2p_circle_members.circle_id").select("kubun_p2p_circle_members.circle_id").where("kubun_p2p_circle_members.member_did","=",e).where("kubun_p2p_circle_members.circle_id","in",t).where("kubun_p2p_circle_members.removed_at_hlc","is",null).where("kubun_p2p_circles.removed_at_hlc","is",null).limit(1).executeTakeFirst(),async addDelegationToken(e){let a,_=await r.selectFrom("kubun_p2p_delegation_tokens").select(["jti","token","act","exp","group_id","hlc"]).where("grantor","=",e.grantor).where("audience","=",e.audience).where("resource","=",e.resource).executeTakeFirst();if(await r.insertInto("kubun_p2p_delegation_tokens").values({jti:e.jti,grantor:e.grantor,audience:e.audience,token:e.token,resource:e.resource,act:e.act,exp:e.exp,group_id:e.group_id,hlc:e.hlc}).onConflict(e=>e.columns(["grantor","audience","resource"]).doUpdateSet(e=>({jti:e.ref("excluded.jti"),token:e.ref("excluded.token"),act:e.ref("excluded.act"),exp:e.ref("excluded.exp"),group_id:e.ref("excluded.group_id"),hlc:e.ref("excluded.hlc"),updated_at:t.encodeTimestamp(new Date)})).where(e=>e("excluded.hlc",">",e.ref("kubun_p2p_delegation_tokens.hlc")))).execute(),a=null==_||!(e.hlc<=_.hlc)&&(_.jti!==e.jti||_.token!==e.token||_.act!==e.act||_.exp!==e.exp||_.group_id!==e.group_id),.01>Math.random())try{await i.purgeExpiredRevocations({graceSeconds:2592e3}),await i.purgeDeadPendingRevocations({maxAgeSeconds:604800})}catch(e){u.warn("revocation GC sweep failed during addDelegationToken: {error}",{error:e})}return a},getDelegationTokens:async e=>await r.selectFrom("kubun_p2p_delegation_tokens").selectAll().where("grantor","=",e.grantor).where("audience","=",e.audience).execute(),getHeldTokens:async e=>await r.selectFrom("kubun_p2p_delegation_tokens").leftJoin("kubun_p2p_revoked_capabilities","kubun_p2p_revoked_capabilities.jti","kubun_p2p_delegation_tokens.jti").selectAll("kubun_p2p_delegation_tokens").where("audience","=",e.audience).where("exp",">",e.atTime).where(e=>e.or([e("kubun_p2p_revoked_capabilities.jti","is",null),e("kubun_p2p_revoked_capabilities.verified_at","is",null)])).execute(),listIssuedTokens:async e=>await r.selectFrom("kubun_p2p_delegation_tokens").selectAll().where("grantor","=",e).execute(),getDelegationTokenByJti:async e=>await r.selectFrom("kubun_p2p_delegation_tokens").selectAll().where("jti","=",e).executeTakeFirst()??null,async addRevocation(e){let u=await r.selectFrom("kubun_p2p_revoked_capabilities").select(["revoker_did","revoked_iat","revocation_token","verified_at","cap_exp","group_id","hlc"]).where("jti","=",e.jti).executeTakeFirst();if(await r.insertInto("kubun_p2p_revoked_capabilities").values({jti:e.jti,revoker_did:e.revoker_did,revoked_iat:e.revoked_iat,revocation_token:e.revocation_token,verified_at:e.verified_at??null,cap_exp:e.cap_exp??null,group_id:e.group_id,hlc:e.hlc}).onConflict(e=>e.column("jti").doUpdateSet(e=>({revoker_did:e.ref("excluded.revoker_did"),revoked_iat:e.ref("excluded.revoked_iat"),revocation_token:e.ref("excluded.revocation_token"),verified_at:e.ref("excluded.verified_at"),cap_exp:e.ref("excluded.cap_exp"),group_id:e.ref("excluded.group_id"),hlc:e.ref("excluded.hlc"),updated_at:t.encodeTimestamp(new Date)})).where(e=>e("excluded.hlc",">",e.ref("kubun_p2p_revoked_capabilities.hlc")))).execute(),null==u)return!0;if(e.hlc<=u.hlc)return!1;let i=e.verified_at??null,a=e.cap_exp??null;return u.revoker_did!==e.revoker_did||u.revoked_iat!==e.revoked_iat||u.revocation_token!==e.revocation_token||u.verified_at!==i||u.cap_exp!==a||u.group_id!==e.group_id},getRevocation:async e=>await r.selectFrom("kubun_p2p_revoked_capabilities").selectAll().where("jti","=",e).executeTakeFirst()??null,isRevoked:async e=>null!=await r.selectFrom("kubun_p2p_revoked_capabilities").select("jti").where("jti","=",e).where("verified_at","is not",null).limit(1).executeTakeFirst(),getHeldRevocations:async e=>(await r.selectFrom("kubun_p2p_revoked_capabilities").innerJoin("kubun_p2p_delegation_tokens","kubun_p2p_delegation_tokens.jti","kubun_p2p_revoked_capabilities.jti").select(["kubun_p2p_revoked_capabilities.jti as jti","kubun_p2p_delegation_tokens.grantor as grantor","kubun_p2p_delegation_tokens.audience as audience","kubun_p2p_revoked_capabilities.revoker_did as revoker_did","kubun_p2p_revoked_capabilities.revoked_iat as revoked_iat","kubun_p2p_revoked_capabilities.verified_at as verified_at","kubun_p2p_revoked_capabilities.cap_exp as cap_exp","kubun_p2p_revoked_capabilities.group_id as group_id"]).where("kubun_p2p_revoked_capabilities.verified_at","is not",null).where("kubun_p2p_delegation_tokens.audience","=",e.audience).execute()).map(e=>({jti:e.jti,grantor:e.grantor,audience:e.audience,revoker_did:e.revoker_did,revoked_iat:e.revoked_iat,verified_at:e.verified_at,cap_exp:e.cap_exp,group_id:e.group_id})),async markRevocationVerified(e,u){let i=u.verified_at??Math.floor(Date.now()/1e3);return((await r.updateTable("kubun_p2p_revoked_capabilities").set({verified_at:i,cap_exp:u.cap_exp,updated_at:t.encodeTimestamp(new Date)}).where("jti","=",e).where("verified_at","is",null).executeTakeFirst()).numUpdatedRows??0n)>0n},getPendingRevocationByJti:async e=>await r.selectFrom("kubun_p2p_revoked_capabilities").selectAll().where("jti","=",e).where("verified_at","is",null).executeTakeFirst()??null,deletePendingRevocation:async e=>((await r.deleteFrom("kubun_p2p_revoked_capabilities").where("jti","=",e).where("verified_at","is",null).executeTakeFirst()).numDeletedRows??0n)>0n,async purgeExpiredRevocations(e){let t=Math.floor(Date.now()/1e3)-e.graceSeconds;return Number((await r.deleteFrom("kubun_p2p_revoked_capabilities").where("verified_at","is not",null).where("cap_exp","is not",null).where("cap_exp","<",t).executeTakeFirst()).numDeletedRows??0n)},async purgeDeadPendingRevocations(e){let u=t.encodeTimestamp(new Date(Date.now()-1e3*e.maxAgeSeconds));return Number((await r.deleteFrom("kubun_p2p_revoked_capabilities").where("verified_at","is",null).where("created_at","<",u).executeTakeFirst()).numDeletedRows??0n)},async saveMLSState(e){await r.insertInto("kubun_p2p_group_mls_state").values({group_id:e.group_id,device_id:e.device_id,mls_state:t.encodeBinary(e.mls_state),credential:e.credential,epoch:e.epoch,root_capability:e.root_capability}).onConflict(e=>e.columns(["group_id","device_id"]).doUpdateSet(e=>({mls_state:e.ref("excluded.mls_state"),credential:e.ref("excluded.credential"),epoch:e.ref("excluded.epoch"),root_capability:e.ref("excluded.root_capability"),updated_at:t.encodeTimestamp(new Date)}))).execute()},getMLSState:async(e,t)=>await r.selectFrom("kubun_p2p_group_mls_state").selectAll().where("group_id","=",e).where("device_id","=",t).executeTakeFirst(),async deleteMLSState(e,t){await r.deleteFrom("kubun_p2p_group_mls_state").where("group_id","=",e).where("device_id","=",t).execute()},listMLSStates:async e=>await r.selectFrom("kubun_p2p_group_mls_state").selectAll().where("group_id","=",e).execute(),async deleteGroupMLSStates(e){await r.deleteFrom("kubun_p2p_group_mls_state").where("group_id","=",e).execute()},async addSyncPeer(e){let u=t.encodeJSON(e.allowed_users),i=t.encodeJSON(e.config);await r.insertInto("kubun_p2p_sync_peers").values({...e,allowed_users:u,config:i}).onConflict(r=>r.column("peer_did").doUpdateSet({endpoint:e.endpoint,mode:e.mode,allowed_users:u,priority:e.priority,trust_level:e.trust_level,config:i,updated_at:e.updated_at})).execute()},getSyncPeer:async e=>await r.selectFrom("kubun_p2p_sync_peers").selectAll().where("peer_did","=",e).executeTakeFirst(),listSyncPeers:async()=>await r.selectFrom("kubun_p2p_sync_peers").selectAll().execute(),async updateSyncPeer(e,u){let i={};null!=u.endpoint&&(i.endpoint=u.endpoint),null!=u.mode&&(i.mode=u.mode),null!=u.allowed_users&&(i.allowed_users=t.encodeJSON(u.allowed_users)),null!=u.priority&&(i.priority=u.priority),null!=u.trust_level&&(i.trust_level=u.trust_level),null!=u.config&&(i.config=t.encodeJSON(u.config)),i.updated_at=Date.now(),await r.updateTable("kubun_p2p_sync_peers").set(i).where("peer_did","=",e).execute()},async removeSyncPeer(e){await r.deleteFrom("kubun_p2p_sync_peers").where("peer_did","=",e).execute()},recordBroadcast:async e=>((await r.insertInto("kubun_p2p_broadcast_log").values({doc_id:e.docID,version:e.version,mls_group_id:e.mlsGroupID}).onConflict(e=>e.columns(["doc_id","version","mls_group_id"]).doNothing()).executeTakeFirst()).numInsertedOrUpdatedRows??0n)>0n,hasBroadcast:async e=>null!=await r.selectFrom("kubun_p2p_broadcast_log").select("doc_id").where("doc_id","=",e.docID).where("version","=",e.version).where("mls_group_id","=",e.mlsGroupID).limit(1).executeTakeFirst(),async insertHub(e){await r.insertInto("kubun_p2p_hubs").values({id:e.id,label:e.label,url:e.url,server_did:e.server_did}).execute()},getHub:async e=>await r.selectFrom("kubun_p2p_hubs").selectAll().where("id","=",e).executeTakeFirst(),getHubByURL:async e=>await r.selectFrom("kubun_p2p_hubs").selectAll().where("url","=",e).executeTakeFirst(),async updateHub(e,u){let i={};void 0!==u.label&&(i.label=u.label),void 0!==u.url&&(i.url=u.url),void 0!==u.server_did&&(i.server_did=u.server_did),i.updated_at=t.encodeTimestamp(new Date),await r.updateTable("kubun_p2p_hubs").set(i).where("id","=",e).execute()},async deleteHub(e){await r.deleteFrom("kubun_p2p_hubs").where("id","=",e).execute()},listHubs:async()=>await r.selectFrom("kubun_p2p_hubs").selectAll().execute(),insertGroupHub:async e=>((await r.insertInto("kubun_p2p_group_hubs").values({group_id:e.group_id,hub_id:e.hub_id}).onConflict(e=>e.columns(["group_id","hub_id"]).doNothing()).executeTakeFirst()).numInsertedOrUpdatedRows??0n)>0n,deleteGroupHub:async e=>((await r.deleteFrom("kubun_p2p_group_hubs").where("group_id","=",e.groupID).where("hub_id","=",e.hubID).executeTakeFirst()).numDeletedRows??0n)>0n,listGroupIDsByHubID:async e=>(await r.selectFrom("kubun_p2p_group_hubs").select("group_id").where("hub_id","=",e).execute()).map(e=>e.group_id),listHubsByGroupID:async e=>await r.selectFrom("kubun_p2p_group_hubs").innerJoin("kubun_p2p_hubs","kubun_p2p_hubs.id","kubun_p2p_group_hubs.hub_id").selectAll("kubun_p2p_hubs").where("kubun_p2p_group_hubs.group_id","=",e).execute(),listGroupsByHubID:async e=>await r.selectFrom("kubun_p2p_group_hubs").innerJoin("kubun_p2p_groups","kubun_p2p_groups.id","kubun_p2p_group_hubs.group_id").select(["kubun_p2p_groups.id as id","kubun_p2p_groups.name as name","kubun_p2p_groups.description as description","kubun_p2p_groups.created_by as created_by","kubun_p2p_groups.hlc as hlc","kubun_p2p_groups.created_at as created_at","kubun_p2p_groups.updated_at as updated_at"]).where("kubun_p2p_group_hubs.hub_id","=",e).execute(),async deleteGroupHubsByHubID(e){await r.deleteFrom("kubun_p2p_group_hubs").where("hub_id","=",e).execute()}};return i}
|
|
1
|
+
import { createAppLaneOps } from './ops/app-lane.js';
|
|
2
|
+
import { createCircleOps } from './ops/circles.js';
|
|
3
|
+
import { createControlOps } from './ops/control.js';
|
|
4
|
+
import { createDelegationOps } from './ops/delegations.js';
|
|
5
|
+
import { createDiscoveryOps } from './ops/discovery.js';
|
|
6
|
+
import { createGroupOps } from './ops/groups.js';
|
|
7
|
+
import { createHubOps } from './ops/hubs.js';
|
|
8
|
+
import { createMLSOps } from './ops/mls.js';
|
|
9
|
+
import { createSyncOps } from './ops/sync.js';
|
|
10
|
+
export function createP2PStore(db, adapter) {
|
|
11
|
+
return {
|
|
12
|
+
...createGroupOps(db, adapter),
|
|
13
|
+
...createCircleOps(db, adapter),
|
|
14
|
+
...createDelegationOps(db),
|
|
15
|
+
...createMLSOps(db, adapter),
|
|
16
|
+
...createSyncOps(db, adapter),
|
|
17
|
+
...createControlOps(db, adapter),
|
|
18
|
+
...createAppLaneOps(db, adapter),
|
|
19
|
+
...createHubOps(db, adapter),
|
|
20
|
+
...createDiscoveryOps(db, adapter)
|
|
21
|
+
};
|
|
22
|
+
}
|
package/lib/definition.js
CHANGED
|
@@ -1 +1,7 @@
|
|
|
1
|
-
import{createP2PStore
|
|
1
|
+
import { createP2PStore } from './api.js';
|
|
2
|
+
import { getP2PMigrations } from './migrations.js';
|
|
3
|
+
export const p2pStoreDefinition = {
|
|
4
|
+
name: 'p2p',
|
|
5
|
+
migrations: getP2PMigrations,
|
|
6
|
+
createAPI: createP2PStore
|
|
7
|
+
};
|
package/lib/index.d.ts
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import type { StoreProvider } from '@kubun/db';
|
|
2
2
|
import type { P2PStoreAPI } from './api.js';
|
|
3
|
-
export type { P2PStoreAPI } from './api.js';
|
|
3
|
+
export type { ControlRequestKind, ControlRequestOutcome, P2PStoreAPI, PutPendingJoinRequestParams, SetLocalPeerProfileParams, UpsertPeerAnnouncementParams, } from './api.js';
|
|
4
4
|
export { createP2PStore } from './api.js';
|
|
5
5
|
export { p2pStoreDefinition } from './definition.js';
|
|
6
|
-
export { createP2PRevocationBackend } from './revocation-backend.js';
|
|
7
6
|
export declare const P2P_STORE: "p2p";
|
|
8
7
|
export declare function getP2PStore(provider: StoreProvider): Promise<P2PStoreAPI>;
|
|
9
|
-
export type { Circle, CircleMember, CircleMemberTable, CircleTable,
|
|
8
|
+
export type { AppCursor, AppCursorTable, Circle, CircleMember, CircleMemberTable, CircleTable, ControlRequest, ControlRequestTable, Group, GroupAnchor, GroupAnchorTable, GroupDelegation, GroupDelegationTable, GroupHub, GroupHubTable, GroupMember, GroupMemberTable, GroupMLSState, GroupMLSStateTable, GroupStatus, GroupTable, Hub, HubTable, InsertCircle, InsertCircleMember, InsertGroup, InsertGroupDelegation, InsertGroupHub, InsertGroupMember, InsertGroupMLSState, InsertHub, InsertSyncCheckpoint, InsertSyncPeer, LocalPeerProfile, LocalPeerProfileTable, P2PStoreTables, PaginatedResult, PaginationParams, PeerAnnouncement, PeerAnnouncementTable, PeerCapabilityRecord, PendingJoinRequestRow, PendingJoinRequestTable, SyncCheckpoint, SyncCheckpointTable, SyncPeer, SyncPeerTable, UpdateGroupMLSState, } from './tables.js';
|
package/lib/index.js
CHANGED
|
@@ -1 +1,6 @@
|
|
|
1
|
-
export{createP2PStore}from
|
|
1
|
+
export { createP2PStore } from './api.js';
|
|
2
|
+
export { p2pStoreDefinition } from './definition.js';
|
|
3
|
+
export const P2P_STORE = 'p2p';
|
|
4
|
+
export function getP2PStore(provider) {
|
|
5
|
+
return provider.getStore(P2P_STORE);
|
|
6
|
+
}
|
package/lib/migrations.d.ts
CHANGED
|
@@ -1,3 +1,24 @@
|
|
|
1
1
|
import type { MigrationContext } from '@kubun/db';
|
|
2
2
|
import type { Migration } from 'kysely/migration';
|
|
3
|
+
/**
|
|
4
|
+
* Partial unique index keeping one commit-hub designation per group. Named here
|
|
5
|
+
* because the store recognises a lost first-bind race by this name appearing in
|
|
6
|
+
* the driver's constraint-violation error.
|
|
7
|
+
*/
|
|
8
|
+
export declare const COMMIT_HUB_UNIQUE_INDEX = "uq_p2p_group_hubs_commit";
|
|
9
|
+
/**
|
|
10
|
+
* A shipped migration is immutable: once a database has run it, editing it
|
|
11
|
+
* changes nothing on that database and silently diverges the schema from the
|
|
12
|
+
* code. Every schema change after a release goes in a NEW migration.
|
|
13
|
+
*
|
|
14
|
+
* That rule is suspended for this release, deliberately. The eight migrations
|
|
15
|
+
* this store had accumulated are folded into `0-init`, which is now the whole
|
|
16
|
+
* schema in one readable place. It is a BREAKING change — a database created
|
|
17
|
+
* before it will not pick the new shape up and must be recreated — and it costs
|
|
18
|
+
* nothing here only because the release already requires recreating local
|
|
19
|
+
* databases for unrelated reasons.
|
|
20
|
+
*
|
|
21
|
+
* Reinstate the rule the moment this ships: the next schema change is a new
|
|
22
|
+
* migration, and `0-init` is immutable again.
|
|
23
|
+
*/
|
|
3
24
|
export declare function getP2PMigrations(ctx: MigrationContext): Record<string, Migration>;
|