@kubun/store-p2p 0.9.0 → 0.10.1

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,6 +1,7 @@
1
1
  import type { Adapter } from '@kubun/db-adapter';
2
+ import { type Logger } from '@kubun/logger';
2
3
  import type { Kysely } from 'kysely';
3
- import type { Circle, CircleMember, Group, GroupMember, GroupMLSState, Hub, InsertCircle, InsertCircleMember, InsertGroup, InsertGroupHub, InsertGroupMember, InsertGroupMLSState, InsertHub, InsertSyncPeer, P2PStoreTables, PaginatedResult, PaginationParams, SyncPeer } from './tables.js';
4
+ import type { Circle, CircleMember, DelegationToken, Group, GroupMember, GroupMLSState, HeldRevocation, Hub, InsertCircle, InsertCircleMember, InsertDelegationToken, InsertGroup, InsertGroupHub, InsertGroupMember, InsertGroupMLSState, InsertHub, InsertRevokedCapability, InsertSyncPeer, P2PStoreTables, PaginatedResult, PaginationParams, RevokedCapability, SyncPeer } from './tables.js';
4
5
  export type P2PStoreAPI = {
5
6
  createGroup(group: InsertGroup): Promise<void>;
6
7
  getGroup(id: string): Promise<Group | undefined>;
@@ -9,7 +10,30 @@ export type P2PStoreAPI = {
9
10
  listGroups(): Promise<Array<Group>>;
10
11
  listGroupsOffsetPaged(params: PaginationParams): Promise<PaginatedResult<Group>>;
11
12
  addGroupMember(member: InsertGroupMember): Promise<void>;
12
- removeGroupMember(groupID: string, memberDID: string): Promise<void>;
13
+ /**
14
+ * Soft-delete a member by tombstoning the row with `hlc`. Delegates to
15
+ * `markMemberRemoved`: earliest-wins LWW, ignore-if-absent. The row stays in
16
+ * the table (read queries exclude it) so the apply-time check can compare a
17
+ * mutation's HLC against the removal HLC.
18
+ */
19
+ removeGroupMember(groupID: string, memberDID: string, hlc: string): Promise<void>;
20
+ /**
21
+ * Tombstone an existing membership row at `hlc` using earliest-wins LWW: the
22
+ * removal sticks only when no removal is recorded yet, or when `hlc` precedes
23
+ * the stored removal HLC. A removal for a `(groupID, memberDID)` with no local
24
+ * row is a no-op — absence means there is no member to tombstone, and no row
25
+ * is created.
26
+ */
27
+ markMemberRemoved(groupID: string, memberDID: string, hlc: string): Promise<void>;
28
+ /**
29
+ * Read a membership row's removal state, including tombstoned rows. This is
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>;
13
37
  getGroupMember(groupID: string, memberDID: string): Promise<GroupMember | undefined>;
14
38
  isGroupMember(groupID: string, memberDID: string): Promise<boolean>;
15
39
  isMemberOfAnyGroup(viewerDID: string, groupIDs: Array<string>): Promise<boolean>;
@@ -26,7 +50,15 @@ export type P2PStoreAPI = {
26
50
  */
27
51
  getGroupsForMembers(memberDIDs: Array<string>): Promise<Map<string, Array<Group>>>;
28
52
  createCircle(circle: InsertCircle): Promise<void>;
29
- getCircle(id: string): Promise<Circle | undefined>;
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>;
30
62
  /**
31
63
  * Batch fetch circles by ID. Returns a Map keyed by ID; missing IDs are
32
64
  * absent from the map. Single SQL query with `WHERE id IN (...)`. Empty
@@ -37,13 +69,158 @@ export type P2PStoreAPI = {
37
69
  getCircles(ids: Array<string>): Promise<Map<string, Circle>>;
38
70
  updateCircle(id: string, update: Partial<Pick<InsertCircle, 'name' | 'description' | 'catalog_ids' | 'hlc'>>): Promise<void>;
39
71
  deleteCircle(id: string): Promise<void>;
40
- listCirclesByGroup(groupID: string): Promise<Array<Circle>>;
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>>;
41
88
  addCircleMember(member: InsertCircleMember): Promise<void>;
42
- removeCircleMember(circleID: string, memberDID: string): 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>;
43
95
  getCircleMember(circleID: string, memberDID: string): Promise<CircleMember | undefined>;
44
- listCircleMembers(circleID: string): Promise<Array<CircleMember>>;
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>>;
45
104
  getCirclesForMember(memberDID: string): Promise<Array<Circle>>;
46
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>;
47
224
  saveMLSState(state: InsertGroupMLSState): Promise<void>;
48
225
  getMLSState(groupID: string, deviceID: string): Promise<GroupMLSState | undefined>;
49
226
  deleteMLSState(groupID: string, deviceID: string): Promise<void>;
@@ -80,4 +257,4 @@ export type P2PStoreAPI = {
80
257
  listGroupsByHubID(hubID: string): Promise<Array<Group>>;
81
258
  deleteGroupHubsByHubID(hubID: string): Promise<void>;
82
259
  };
83
- export declare function createP2PStore(db: Kysely<P2PStoreTables>, adapter: Adapter): P2PStoreAPI;
260
+ export declare function createP2PStore(db: Kysely<P2PStoreTables>, adapter: Adapter, logger?: Logger): P2PStoreAPI;
package/lib/api.js CHANGED
@@ -1 +1 @@
1
- export function createP2PStore(e,u){return{async createGroup(u){await e.insertInto("kubun_p2p_groups").values({id:u.id,name:u.name,description:u.description,created_by:u.created_by,hlc:u.hlc}).execute()},getGroup:async u=>await e.selectFrom("kubun_p2p_groups").selectAll().where("id","=",u).executeTakeFirst(),async updateGroup(r,t){let s={};null!=t.name&&(s.name=t.name),null!=t.description&&(s.description=t.description),null!=t.hlc&&(s.hlc=t.hlc),s.updated_at=u.encodeTimestamp(new Date),await e.updateTable("kubun_p2p_groups").set(s).where("id","=",r).execute()},async deleteGroup(u){await e.deleteFrom("kubun_p2p_groups").where("id","=",u).execute()},listGroups:async()=>await e.selectFrom("kubun_p2p_groups").selectAll().execute(),async listGroupsOffsetPaged(u){let{limit:r,offset:t}=u,s=e.selectFrom("kubun_p2p_groups").selectAll().orderBy("name");if(null!=t&&(s=s.offset(t)),null!=r){s=s.limit(r+1);let e=await s.execute(),u=e.length>r;return{entries:u?e.slice(0,r):e,hasMore:u}}return{entries:await s.execute(),hasMore:!1}},async addGroupMember(r){await e.insertInto("kubun_p2p_group_members").values({group_id:r.group_id,member_did:r.member_did,role:r.role}).onConflict(e=>e.columns(["group_id","member_did"]).doUpdateSet(e=>({role:e.ref("excluded.role"),updated_at:u.encodeTimestamp(new Date)}))).execute()},async removeGroupMember(u,r){await e.deleteFrom("kubun_p2p_group_members").where("group_id","=",u).where("member_did","=",r).execute()},getGroupMember:async(u,r)=>await e.selectFrom("kubun_p2p_group_members").selectAll().where("group_id","=",u).where("member_did","=",r).executeTakeFirst(),isGroupMember:async(u,r)=>null!=await e.selectFrom("kubun_p2p_group_members").select("member_did").where("group_id","=",u).where("member_did","=",r).executeTakeFirst(),isMemberOfAnyGroup:async(u,r)=>0!==r.length&&null!=await e.selectFrom("kubun_p2p_group_members").select("group_id").where("member_did","=",u).where("group_id","in",r).limit(1).executeTakeFirst(),listGroupMembers:async u=>await e.selectFrom("kubun_p2p_group_members").selectAll().where("group_id","=",u).execute(),getGroupsForMember:async u=>await e.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","=",u).execute(),async getGroupsForMembers(u){let r=new Map;if(0===u.length)return r;for(let t of(await e.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",u).execute())){let{member_did:e,...u}=t,s=r.get(e);null!=s?s.push(u):r.set(e,[u])}return r},async createCircle(r){await e.insertInto("kubun_p2p_circles").values({id:r.id,group_id:r.group_id,name:r.name,description:r.description,catalog_ids:u.encodeJSON(r.catalog_ids),hlc:r.hlc}).execute()},getCircle:async u=>await e.selectFrom("kubun_p2p_circles").selectAll().where("id","=",u).executeTakeFirst(),async getCircles(u){let r=new Map;if(0===u.length)return r;for(let t of(await e.selectFrom("kubun_p2p_circles").selectAll().where("id","in",u).execute()))r.set(t.id,t);return r},async updateCircle(r,t){let s={};null!=t.name&&(s.name=t.name),null!=t.description&&(s.description=t.description),null!=t.catalog_ids&&(s.catalog_ids=u.encodeJSON(t.catalog_ids)),null!=t.hlc&&(s.hlc=t.hlc),s.updated_at=u.encodeTimestamp(new Date),await e.updateTable("kubun_p2p_circles").set(s).where("id","=",r).execute()},async deleteCircle(u){await e.deleteFrom("kubun_p2p_circles").where("id","=",u).execute()},listCirclesByGroup:async u=>await e.selectFrom("kubun_p2p_circles").selectAll().where("group_id","=",u).execute(),async addCircleMember(u){await e.insertInto("kubun_p2p_circle_members").values({circle_id:u.circle_id,member_did:u.member_did,role:u.role,hlc:u.hlc}).onConflict(e=>e.columns(["circle_id","member_did"]).doUpdateSet(e=>({role:e.ref("excluded.role"),hlc:e.ref("excluded.hlc")}))).execute()},async removeCircleMember(u,r){await e.deleteFrom("kubun_p2p_circle_members").where("circle_id","=",u).where("member_did","=",r).execute()},getCircleMember:async(u,r)=>await e.selectFrom("kubun_p2p_circle_members").selectAll().where("circle_id","=",u).where("member_did","=",r).executeTakeFirst(),listCircleMembers:async u=>await e.selectFrom("kubun_p2p_circle_members").selectAll().where("circle_id","=",u).execute(),getCirclesForMember:async u=>await e.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","=",u).execute(),isMemberOfAnyCircle:async(u,r)=>0!==r.length&&null!=await e.selectFrom("kubun_p2p_circle_members").select("circle_id").where("member_did","=",u).where("circle_id","in",r).limit(1).executeTakeFirst(),async saveMLSState(r){await e.insertInto("kubun_p2p_group_mls_state").values({group_id:r.group_id,device_id:r.device_id,mls_state:u.encodeBinary(r.mls_state),credential:r.credential,epoch:r.epoch,root_capability:r.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:u.encodeTimestamp(new Date)}))).execute()},getMLSState:async(u,r)=>await e.selectFrom("kubun_p2p_group_mls_state").selectAll().where("group_id","=",u).where("device_id","=",r).executeTakeFirst(),async deleteMLSState(u,r){await e.deleteFrom("kubun_p2p_group_mls_state").where("group_id","=",u).where("device_id","=",r).execute()},listMLSStates:async u=>await e.selectFrom("kubun_p2p_group_mls_state").selectAll().where("group_id","=",u).execute(),async deleteGroupMLSStates(u){await e.deleteFrom("kubun_p2p_group_mls_state").where("group_id","=",u).execute()},async addSyncPeer(r){let t=u.encodeJSON(r.allowed_users),s=u.encodeJSON(r.config);await e.insertInto("kubun_p2p_sync_peers").values({...r,allowed_users:t,config:s}).onConflict(e=>e.column("peer_did").doUpdateSet({endpoint:r.endpoint,mode:r.mode,allowed_users:t,priority:r.priority,trust_level:r.trust_level,config:s,updated_at:r.updated_at})).execute()},getSyncPeer:async u=>await e.selectFrom("kubun_p2p_sync_peers").selectAll().where("peer_did","=",u).executeTakeFirst(),listSyncPeers:async()=>await e.selectFrom("kubun_p2p_sync_peers").selectAll().execute(),async updateSyncPeer(r,t){let s={};null!=t.endpoint&&(s.endpoint=t.endpoint),null!=t.mode&&(s.mode=t.mode),null!=t.allowed_users&&(s.allowed_users=u.encodeJSON(t.allowed_users)),null!=t.priority&&(s.priority=t.priority),null!=t.trust_level&&(s.trust_level=t.trust_level),null!=t.config&&(s.config=u.encodeJSON(t.config)),s.updated_at=Date.now(),await e.updateTable("kubun_p2p_sync_peers").set(s).where("peer_did","=",r).execute()},async removeSyncPeer(u){await e.deleteFrom("kubun_p2p_sync_peers").where("peer_did","=",u).execute()},recordBroadcast:async u=>((await e.insertInto("kubun_p2p_broadcast_log").values({doc_id:u.docID,version:u.version,mls_group_id:u.mlsGroupID}).onConflict(e=>e.columns(["doc_id","version","mls_group_id"]).doNothing()).executeTakeFirst()).numInsertedOrUpdatedRows??0n)>0n,hasBroadcast:async u=>null!=await e.selectFrom("kubun_p2p_broadcast_log").select("doc_id").where("doc_id","=",u.docID).where("version","=",u.version).where("mls_group_id","=",u.mlsGroupID).limit(1).executeTakeFirst(),async insertHub(u){await e.insertInto("kubun_p2p_hubs").values({id:u.id,label:u.label,url:u.url,server_did:u.server_did}).execute()},getHub:async u=>await e.selectFrom("kubun_p2p_hubs").selectAll().where("id","=",u).executeTakeFirst(),getHubByURL:async u=>await e.selectFrom("kubun_p2p_hubs").selectAll().where("url","=",u).executeTakeFirst(),async updateHub(r,t){let s={};void 0!==t.label&&(s.label=t.label),void 0!==t.url&&(s.url=t.url),void 0!==t.server_did&&(s.server_did=t.server_did),s.updated_at=u.encodeTimestamp(new Date),await e.updateTable("kubun_p2p_hubs").set(s).where("id","=",r).execute()},async deleteHub(u){await e.deleteFrom("kubun_p2p_hubs").where("id","=",u).execute()},listHubs:async()=>await e.selectFrom("kubun_p2p_hubs").selectAll().execute(),insertGroupHub:async u=>((await e.insertInto("kubun_p2p_group_hubs").values({group_id:u.group_id,hub_id:u.hub_id}).onConflict(e=>e.columns(["group_id","hub_id"]).doNothing()).executeTakeFirst()).numInsertedOrUpdatedRows??0n)>0n,deleteGroupHub:async u=>((await e.deleteFrom("kubun_p2p_group_hubs").where("group_id","=",u.groupID).where("hub_id","=",u.hubID).executeTakeFirst()).numDeletedRows??0n)>0n,listGroupIDsByHubID:async u=>(await e.selectFrom("kubun_p2p_group_hubs").select("group_id").where("hub_id","=",u).execute()).map(e=>e.group_id),listHubsByGroupID:async u=>await e.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","=",u).execute(),listGroupsByHubID:async u=>await e.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","=",u).execute(),async deleteGroupHubsByHubID(u){await e.deleteFrom("kubun_p2p_group_hubs").where("hub_id","=",u).execute()}}}
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}
package/lib/index.d.ts CHANGED
@@ -3,6 +3,7 @@ import type { P2PStoreAPI } from './api.js';
3
3
  export type { P2PStoreAPI } from './api.js';
4
4
  export { createP2PStore } from './api.js';
5
5
  export { p2pStoreDefinition } from './definition.js';
6
+ export { createP2PRevocationBackend } from './revocation-backend.js';
6
7
  export declare const P2P_STORE: "p2p";
7
8
  export declare function getP2PStore(provider: StoreProvider): Promise<P2PStoreAPI>;
8
- export type { Circle, CircleMember, CircleMemberTable, CircleTable, Group, GroupHub, GroupHubTable, GroupMember, GroupMemberTable, GroupMLSState, GroupMLSStateTable, GroupTable, Hub, HubTable, InsertCircle, InsertCircleMember, InsertGroup, InsertGroupHub, InsertGroupMember, InsertGroupMLSState, InsertHub, InsertSyncCheckpoint, InsertSyncPeer, P2PStoreTables, PaginatedResult, PaginationParams, SyncCheckpoint, SyncCheckpointTable, SyncPeer, SyncPeerTable, UpdateGroupMLSState, } from './tables.js';
9
+ export type { Circle, CircleMember, CircleMemberTable, CircleTable, DelegationToken, DelegationTokenTable, Group, GroupHub, GroupHubTable, GroupMember, GroupMemberTable, GroupMLSState, GroupMLSStateTable, GroupTable, HeldRevocation, Hub, HubTable, InsertCircle, InsertCircleMember, InsertDelegationToken, InsertGroup, InsertGroupHub, InsertGroupMember, InsertGroupMLSState, InsertHub, InsertSyncCheckpoint, InsertSyncPeer, P2PStoreTables, PaginatedResult, PaginationParams, SyncCheckpoint, SyncCheckpointTable, SyncPeer, SyncPeerTable, UpdateGroupMLSState, } from './tables.js';
package/lib/index.js CHANGED
@@ -1 +1 @@
1
- export{createP2PStore}from"./api.js";export{p2pStoreDefinition}from"./definition.js";export const P2P_STORE="p2p";export function getP2PStore(t){return t.getStore("p2p")}
1
+ export{createP2PStore}from"./api.js";export{p2pStoreDefinition}from"./definition.js";export{createP2PRevocationBackend}from"./revocation-backend.js";export const P2P_STORE="p2p";export function getP2PStore(e){return e.getStore("p2p")}
@@ -1,3 +1,3 @@
1
1
  import type { MigrationContext } from '@kubun/db';
2
- import type { Migration } from 'kysely';
2
+ import type { Migration } from 'kysely/migration';
3
3
  export declare function getP2PMigrations(ctx: MigrationContext): Record<string, Migration>;
package/lib/migrations.js CHANGED
@@ -1 +1 @@
1
- export function getP2PMigrations(t){let e=t.types,a=t.functions.now;return{"0-init":{async up(t){await t.schema.createTable("kubun_p2p_groups").ifNotExists().addColumn("id",e.text,t=>t.notNull().primaryKey()).addColumn("name",e.text,t=>t.notNull()).addColumn("description",e.text,t=>t.notNull().defaultTo("")).addColumn("created_by",e.text,t=>t.notNull()).addColumn("hlc",e.text,t=>t.notNull()).addColumn("created_at",e.timestamp,t=>t.defaultTo(a).notNull()).addColumn("updated_at",e.timestamp).execute(),await t.schema.createTable("kubun_p2p_group_members").ifNotExists().addColumn("group_id",e.text,t=>t.notNull()).addColumn("member_did",e.text,t=>t.notNull()).addColumn("role",e.text,t=>t.notNull().defaultTo("member")).addColumn("created_at",e.timestamp,t=>t.defaultTo(a).notNull()).addColumn("updated_at",e.timestamp).addPrimaryKeyConstraint("pk_p2p_group_members",["group_id","member_did"]).execute(),await t.schema.createIndex("idx_p2p_group_members_did").ifNotExists().on("kubun_p2p_group_members").column("member_did").execute(),await t.schema.createTable("kubun_p2p_circles").ifNotExists().addColumn("id",e.text,t=>t.notNull().primaryKey()).addColumn("group_id",e.text,t=>t.notNull()).addColumn("name",e.text,t=>t.notNull()).addColumn("description",e.text,t=>t.notNull().defaultTo("")).addColumn("catalog_ids",e.json,t=>t.notNull().defaultTo("[]")).addColumn("hlc",e.text,t=>t.notNull()).addColumn("created_at",e.timestamp,t=>t.defaultTo(a).notNull()).addColumn("updated_at",e.timestamp).execute(),await t.schema.createIndex("idx_p2p_circles_group").ifNotExists().on("kubun_p2p_circles").column("group_id").execute(),await t.schema.createTable("kubun_p2p_circle_members").ifNotExists().addColumn("circle_id",e.text,t=>t.notNull()).addColumn("member_did",e.text,t=>t.notNull()).addColumn("role",e.text,t=>t.notNull().defaultTo("member")).addColumn("hlc",e.text,t=>t.notNull()).addColumn("created_at",e.timestamp,t=>t.defaultTo(a).notNull()).addColumn("updated_at",e.timestamp).addPrimaryKeyConstraint("pk_p2p_circle_members",["circle_id","member_did"]).execute(),await t.schema.createIndex("idx_p2p_circle_members_did").ifNotExists().on("kubun_p2p_circle_members").column("member_did").execute(),await t.schema.createTable("kubun_p2p_group_mls_state").ifNotExists().addColumn("group_id",e.text,t=>t.notNull()).addColumn("device_id",e.text,t=>t.notNull()).addColumn("mls_state",e.binary,t=>t.notNull()).addColumn("credential",e.text,t=>t.notNull()).addColumn("epoch","integer",t=>t.notNull()).addColumn("root_capability",e.text,t=>t.notNull().defaultTo("")).addColumn("updated_at",e.timestamp,t=>t.defaultTo(a).notNull()).addPrimaryKeyConstraint("pk_p2p_group_mls_state",["group_id","device_id"]).execute(),await t.schema.createTable("kubun_p2p_sync_peers").ifNotExists().addColumn("id",e.text,t=>t.notNull().primaryKey()).addColumn("peer_did",e.text,t=>t.notNull().unique()).addColumn("endpoint",e.text,t=>t.notNull()).addColumn("mode",e.text,t=>t.notNull()).addColumn("allowed_users",e.json,t=>t.notNull()).addColumn("priority",e.text,t=>t.notNull().defaultTo("0")).addColumn("trust_level",e.text,t=>t.notNull()).addColumn("config",e.json,t=>t.notNull()).addColumn("created_at",e.timestamp,t=>t.defaultTo(a).notNull()).addColumn("updated_at",e.timestamp,t=>t.defaultTo(a).notNull()).execute(),await t.schema.createTable("kubun_p2p_sync_checkpoints").ifNotExists().addColumn("id",e.text,t=>t.notNull().primaryKey()).addColumn("peer_did",e.text,t=>t.notNull()).addColumn("scope",e.text,t=>t.notNull()).addColumn("checkpoint_hlc",e.text,t=>t.notNull()).addColumn("updated_at",e.timestamp,t=>t.defaultTo(a).notNull()).execute(),await t.schema.createTable("kubun_p2p_broadcast_log").ifNotExists().addColumn("doc_id",e.text,t=>t.notNull()).addColumn("version",e.text,t=>t.notNull()).addColumn("mls_group_id",e.text,t=>t.notNull()).addColumn("broadcast_at",e.timestamp,t=>t.defaultTo(a).notNull()).addPrimaryKeyConstraint("pk_p2p_broadcast_log",["doc_id","version","mls_group_id"]).execute(),await t.schema.createIndex("idx_p2p_broadcast_log_doc").ifNotExists().on("kubun_p2p_broadcast_log").column("doc_id").execute(),await t.schema.createTable("kubun_p2p_hubs").ifNotExists().addColumn("id",e.text,t=>t.notNull().primaryKey()).addColumn("label",e.text).addColumn("url",e.text,t=>t.notNull().unique()).addColumn("server_did",e.text).addColumn("created_at",e.timestamp,t=>t.defaultTo(a).notNull()).addColumn("updated_at",e.timestamp).execute(),await t.schema.createTable("kubun_p2p_group_hubs").ifNotExists().addColumn("group_id",e.text,t=>t.notNull()).addColumn("hub_id",e.text,t=>t.notNull()).addColumn("created_at",e.timestamp,t=>t.defaultTo(a).notNull()).addPrimaryKeyConstraint("pk_p2p_group_hubs",["group_id","hub_id"]).execute(),await t.schema.createIndex("idx_p2p_group_hubs_hub").ifNotExists().on("kubun_p2p_group_hubs").column("hub_id").execute()},async down(t){await t.schema.dropTable("kubun_p2p_group_hubs").ifExists().execute(),await t.schema.dropTable("kubun_p2p_hubs").ifExists().execute(),await t.schema.dropTable("kubun_p2p_broadcast_log").ifExists().execute(),await t.schema.dropTable("kubun_p2p_sync_checkpoints").ifExists().execute(),await t.schema.dropTable("kubun_p2p_sync_peers").ifExists().execute(),await t.schema.dropTable("kubun_p2p_group_mls_state").ifExists().execute(),await t.schema.dropTable("kubun_p2p_circle_members").ifExists().execute(),await t.schema.dropTable("kubun_p2p_circles").ifExists().execute(),await t.schema.dropTable("kubun_p2p_group_members").ifExists().execute(),await t.schema.dropTable("kubun_p2p_groups").ifExists().execute()}}}}
1
+ export function getP2PMigrations(e){let t=e.types,a=e.functions.now;return{"0-init":{async up(e){await e.schema.createTable("kubun_p2p_groups").ifNotExists().addColumn("id",t.text,e=>e.notNull().primaryKey()).addColumn("name",t.text,e=>e.notNull()).addColumn("description",t.text,e=>e.notNull().defaultTo("")).addColumn("created_by",t.text,e=>e.notNull()).addColumn("hlc",t.text,e=>e.notNull()).addColumn("created_at",t.timestamp,e=>e.defaultTo(a).notNull()).addColumn("updated_at",t.timestamp).execute(),await e.schema.createTable("kubun_p2p_group_members").ifNotExists().addColumn("group_id",t.text,e=>e.notNull()).addColumn("member_did",t.text,e=>e.notNull()).addColumn("role",t.text,e=>e.notNull().defaultTo("member")).addColumn("hlc",t.text,e=>e.notNull()).addColumn("removed_at_hlc",t.text).addColumn("created_at",t.timestamp,e=>e.defaultTo(a).notNull()).addColumn("updated_at",t.timestamp).addPrimaryKeyConstraint("pk_p2p_group_members",["group_id","member_did"]).execute(),await e.schema.createIndex("idx_p2p_group_members_did").ifNotExists().on("kubun_p2p_group_members").column("member_did").execute(),await e.schema.createTable("kubun_p2p_circles").ifNotExists().addColumn("id",t.text,e=>e.notNull().primaryKey()).addColumn("group_id",t.text,e=>e.notNull()).addColumn("name",t.text,e=>e.notNull()).addColumn("description",t.text,e=>e.notNull().defaultTo("")).addColumn("catalog_ids",t.json,e=>e.notNull().defaultTo("[]")).addColumn("hlc",t.text,e=>e.notNull()).addColumn("removed_at_hlc",t.text).addColumn("created_at",t.timestamp,e=>e.defaultTo(a).notNull()).addColumn("updated_at",t.timestamp).execute(),await e.schema.createIndex("idx_p2p_circles_group").ifNotExists().on("kubun_p2p_circles").column("group_id").execute(),await e.schema.createTable("kubun_p2p_circle_members").ifNotExists().addColumn("circle_id",t.text,e=>e.notNull()).addColumn("member_did",t.text,e=>e.notNull()).addColumn("role",t.text,e=>e.notNull().defaultTo("member")).addColumn("hlc",t.text,e=>e.notNull()).addColumn("removed_at_hlc",t.text).addColumn("created_at",t.timestamp,e=>e.defaultTo(a).notNull()).addColumn("updated_at",t.timestamp).addPrimaryKeyConstraint("pk_p2p_circle_members",["circle_id","member_did"]).execute(),await e.schema.createIndex("idx_p2p_circle_members_did").ifNotExists().on("kubun_p2p_circle_members").column("member_did").execute(),await e.schema.createTable("kubun_p2p_delegation_tokens").ifNotExists().addColumn("jti",t.text,e=>e.notNull()).addColumn("grantor",t.text,e=>e.notNull()).addColumn("audience",t.text,e=>e.notNull()).addColumn("token",t.text,e=>e.notNull()).addColumn("resource",t.text,e=>e.notNull()).addColumn("act",t.text,e=>e.notNull()).addColumn("exp","integer",e=>e.notNull()).addColumn("group_id",t.text,e=>e.notNull()).addColumn("hlc",t.text,e=>e.notNull()).addColumn("created_at",t.timestamp,e=>e.defaultTo(a).notNull()).addColumn("updated_at",t.timestamp).addPrimaryKeyConstraint("pk_p2p_delegation_tokens",["grantor","audience","resource"]).execute(),await e.schema.createIndex("idx_p2p_delegation_tokens_grantor_audience").ifNotExists().on("kubun_p2p_delegation_tokens").columns(["grantor","audience"]).execute(),await e.schema.createIndex("idx_p2p_delegation_tokens_audience_exp").ifNotExists().on("kubun_p2p_delegation_tokens").columns(["audience","exp"]).execute(),await e.schema.createIndex("idx_p2p_delegation_tokens_jti").ifNotExists().on("kubun_p2p_delegation_tokens").column("jti").execute(),await e.schema.createTable("kubun_p2p_revoked_capabilities").ifNotExists().addColumn("jti",t.text,e=>e.notNull().primaryKey()).addColumn("revoker_did",t.text,e=>e.notNull()).addColumn("revoked_iat","integer",e=>e.notNull()).addColumn("revocation_token",t.text,e=>e.notNull()).addColumn("verified_at","integer").addColumn("cap_exp","integer").addColumn("group_id",t.text,e=>e.notNull()).addColumn("hlc",t.text,e=>e.notNull()).addColumn("created_at",t.timestamp,e=>e.defaultTo(a).notNull()).addColumn("updated_at",t.timestamp).execute(),await e.schema.createIndex("idx_p2p_revoked_capabilities_verified_cap_exp").ifNotExists().on("kubun_p2p_revoked_capabilities").columns(["verified_at","cap_exp"]).execute(),await e.schema.createTable("kubun_p2p_group_mls_state").ifNotExists().addColumn("group_id",t.text,e=>e.notNull()).addColumn("device_id",t.text,e=>e.notNull()).addColumn("mls_state",t.binary,e=>e.notNull()).addColumn("credential",t.text,e=>e.notNull()).addColumn("epoch","integer",e=>e.notNull()).addColumn("root_capability",t.text,e=>e.notNull().defaultTo("")).addColumn("updated_at",t.timestamp,e=>e.defaultTo(a).notNull()).addPrimaryKeyConstraint("pk_p2p_group_mls_state",["group_id","device_id"]).execute(),await e.schema.createTable("kubun_p2p_sync_peers").ifNotExists().addColumn("id",t.text,e=>e.notNull().primaryKey()).addColumn("peer_did",t.text,e=>e.notNull().unique()).addColumn("endpoint",t.text,e=>e.notNull()).addColumn("mode",t.text,e=>e.notNull()).addColumn("allowed_users",t.json,e=>e.notNull()).addColumn("priority",t.text,e=>e.notNull().defaultTo("0")).addColumn("trust_level",t.text,e=>e.notNull()).addColumn("config",t.json,e=>e.notNull()).addColumn("created_at",t.timestamp,e=>e.defaultTo(a).notNull()).addColumn("updated_at",t.timestamp,e=>e.defaultTo(a).notNull()).execute(),await e.schema.createTable("kubun_p2p_sync_checkpoints").ifNotExists().addColumn("id",t.text,e=>e.notNull().primaryKey()).addColumn("peer_did",t.text,e=>e.notNull()).addColumn("scope",t.text,e=>e.notNull()).addColumn("checkpoint_hlc",t.text,e=>e.notNull()).addColumn("updated_at",t.timestamp,e=>e.defaultTo(a).notNull()).execute(),await e.schema.createTable("kubun_p2p_broadcast_log").ifNotExists().addColumn("doc_id",t.text,e=>e.notNull()).addColumn("version",t.text,e=>e.notNull()).addColumn("mls_group_id",t.text,e=>e.notNull()).addColumn("broadcast_at",t.timestamp,e=>e.defaultTo(a).notNull()).addPrimaryKeyConstraint("pk_p2p_broadcast_log",["doc_id","version","mls_group_id"]).execute(),await e.schema.createIndex("idx_p2p_broadcast_log_doc").ifNotExists().on("kubun_p2p_broadcast_log").column("doc_id").execute(),await e.schema.createTable("kubun_p2p_hubs").ifNotExists().addColumn("id",t.text,e=>e.notNull().primaryKey()).addColumn("label",t.text).addColumn("url",t.text,e=>e.notNull().unique()).addColumn("server_did",t.text).addColumn("created_at",t.timestamp,e=>e.defaultTo(a).notNull()).addColumn("updated_at",t.timestamp).execute(),await e.schema.createTable("kubun_p2p_group_hubs").ifNotExists().addColumn("group_id",t.text,e=>e.notNull()).addColumn("hub_id",t.text,e=>e.notNull()).addColumn("created_at",t.timestamp,e=>e.defaultTo(a).notNull()).addPrimaryKeyConstraint("pk_p2p_group_hubs",["group_id","hub_id"]).execute(),await e.schema.createIndex("idx_p2p_group_hubs_hub").ifNotExists().on("kubun_p2p_group_hubs").column("hub_id").execute()},async down(e){await e.schema.dropTable("kubun_p2p_group_hubs").ifExists().execute(),await e.schema.dropTable("kubun_p2p_hubs").ifExists().execute(),await e.schema.dropTable("kubun_p2p_broadcast_log").ifExists().execute(),await e.schema.dropTable("kubun_p2p_sync_checkpoints").ifExists().execute(),await e.schema.dropTable("kubun_p2p_sync_peers").ifExists().execute(),await e.schema.dropTable("kubun_p2p_group_mls_state").ifExists().execute(),await e.schema.dropTable("kubun_p2p_revoked_capabilities").ifExists().execute(),await e.schema.dropTable("kubun_p2p_delegation_tokens").ifExists().execute(),await e.schema.dropTable("kubun_p2p_circle_members").ifExists().execute(),await e.schema.dropTable("kubun_p2p_circles").ifExists().execute(),await e.schema.dropTable("kubun_p2p_group_members").ifExists().execute(),await e.schema.dropTable("kubun_p2p_groups").ifExists().execute()}}}}
@@ -0,0 +1,3 @@
1
+ import type { RevocationBackend } from '@enkaku/capability';
2
+ import type { P2PStoreAPI } from './api.js';
3
+ export declare function createP2PRevocationBackend(api: P2PStoreAPI): RevocationBackend;
@@ -0,0 +1 @@
1
+ export function createP2PRevocationBackend(e){return{async add(e){},isRevoked:async a=>await e.isRevoked(a)}}
package/lib/tables.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import type { CreatedAtColumn, UpdatedAtColumn } from '@kubun/db-adapter';
2
2
  import type { ColumnType, Insertable, Selectable, Updateable } from 'kysely';
3
3
  type DefaultedStringColumn = ColumnType<string, string | undefined, string>;
4
+ type NullableStringColumn = ColumnType<string | null, string | null | undefined, string | null>;
4
5
  type JSONArrayColumn<T> = ColumnType<Array<T>, Array<T> | undefined, Array<T>>;
5
6
  type JSONValueColumn<T> = ColumnType<T, T, T>;
6
7
  export type GroupTable = {
@@ -18,6 +19,8 @@ export type GroupMemberTable = {
18
19
  group_id: string;
19
20
  member_did: string;
20
21
  role: DefaultedStringColumn;
22
+ hlc: string;
23
+ removed_at_hlc: NullableStringColumn;
21
24
  created_at: CreatedAtColumn;
22
25
  updated_at: UpdatedAtColumn;
23
26
  };
@@ -30,6 +33,7 @@ export type CircleTable = {
30
33
  description: DefaultedStringColumn;
31
34
  catalog_ids: JSONArrayColumn<string>;
32
35
  hlc: string;
36
+ removed_at_hlc: NullableStringColumn;
33
37
  created_at: CreatedAtColumn;
34
38
  updated_at: UpdatedAtColumn;
35
39
  };
@@ -40,11 +44,51 @@ export type CircleMemberTable = {
40
44
  member_did: string;
41
45
  role: DefaultedStringColumn;
42
46
  hlc: string;
47
+ removed_at_hlc: NullableStringColumn;
43
48
  created_at: CreatedAtColumn;
44
49
  updated_at: UpdatedAtColumn;
45
50
  };
46
51
  export type CircleMember = Selectable<CircleMemberTable>;
47
52
  export type InsertCircleMember = Insertable<CircleMemberTable>;
53
+ export type DelegationTokenTable = {
54
+ jti: string;
55
+ grantor: string;
56
+ audience: string;
57
+ token: string;
58
+ resource: string;
59
+ act: string;
60
+ exp: number;
61
+ group_id: string;
62
+ hlc: string;
63
+ created_at: CreatedAtColumn;
64
+ updated_at: UpdatedAtColumn;
65
+ };
66
+ export type DelegationToken = Selectable<DelegationTokenTable>;
67
+ export type InsertDelegationToken = Insertable<DelegationTokenTable>;
68
+ export type RevokedCapabilityTable = {
69
+ jti: string;
70
+ revoker_did: string;
71
+ revoked_iat: number;
72
+ revocation_token: string;
73
+ verified_at: number | null;
74
+ cap_exp: number | null;
75
+ group_id: string;
76
+ hlc: string;
77
+ created_at: CreatedAtColumn;
78
+ updated_at: UpdatedAtColumn;
79
+ };
80
+ export type RevokedCapability = Selectable<RevokedCapabilityTable>;
81
+ export type InsertRevokedCapability = Insertable<RevokedCapabilityTable>;
82
+ export type HeldRevocation = {
83
+ jti: string;
84
+ grantor: string;
85
+ audience: string;
86
+ revoker_did: string;
87
+ revoked_iat: number;
88
+ verified_at: number;
89
+ cap_exp: number | null;
90
+ group_id: string;
91
+ };
48
92
  export type GroupMLSStateTable = {
49
93
  group_id: string;
50
94
  device_id: string;
@@ -120,6 +164,8 @@ export type P2PStoreTables = {
120
164
  kubun_p2p_group_members: GroupMemberTable;
121
165
  kubun_p2p_circles: CircleTable;
122
166
  kubun_p2p_circle_members: CircleMemberTable;
167
+ kubun_p2p_delegation_tokens: DelegationTokenTable;
168
+ kubun_p2p_revoked_capabilities: RevokedCapabilityTable;
123
169
  kubun_p2p_group_mls_state: GroupMLSStateTable;
124
170
  kubun_p2p_sync_peers: SyncPeerTable;
125
171
  kubun_p2p_sync_checkpoints: SyncCheckpointTable;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kubun/store-p2p",
3
- "version": "0.9.0",
3
+ "version": "0.10.1",
4
4
  "license": "see LICENSE.md",
5
5
  "keywords": [],
6
6
  "type": "module",
@@ -15,14 +15,17 @@
15
15
  ],
16
16
  "sideEffects": false,
17
17
  "dependencies": {
18
- "kysely": "^0.28.17",
19
- "@kubun/db": "^0.9.0",
20
- "@kubun/db-adapter": "^0.9.0"
18
+ "@enkaku/capability": "^0.16.0",
19
+ "kysely": "^0.29.2",
20
+ "@kubun/db-adapter": "^0.10.0",
21
+ "@kubun/db": "^0.10.0",
22
+ "@kubun/logger": "^0.10.0"
21
23
  },
22
24
  "devDependencies": {
23
- "@testcontainers/postgresql": "^11.14.0",
24
- "@kubun/db-better-sqlite": "^0.9.0",
25
- "@kubun/db-postgres": "^0.9.0"
25
+ "@testcontainers/postgresql": "^12.0.1",
26
+ "@kubun/db-postgres": "^0.10.0",
27
+ "@kubun/hlc": "^0.10.1",
28
+ "@kubun/db-better-sqlite": "^0.10.0"
26
29
  },
27
30
  "scripts": {
28
31
  "build:clean": "del lib",