@kubun/store-p2p 0.9.0 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/api.d.ts CHANGED
@@ -1,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>;
@@ -44,6 +68,124 @@ export type P2PStoreAPI = {
44
68
  listCircleMembers(circleID: string): Promise<Array<CircleMember>>;
45
69
  getCirclesForMember(memberDID: string): Promise<Array<Circle>>;
46
70
  isMemberOfAnyCircle(memberDID: string, circleIDs: Array<string>): Promise<boolean>;
71
+ /**
72
+ * Store a delegation token. A re-minted grant covering the same
73
+ * `(grantor, audience, resource)` replaces the existing row when its `hlc`
74
+ * is causally newer; an older or equal `hlc` is ignored. Held rows
75
+ * (`audience` is self) and issued rows (`grantor` is self) are distinct keys
76
+ * and never collide.
77
+ *
78
+ * Returns `true` when the stored row is new or materially different from
79
+ * the prior row (insert, or LWW upsert that changed any of `jti`, `token`,
80
+ * `act`, `exp`, `group_id`). Returns `false` when the call was a no-op:
81
+ * either the incoming `hlc` lost LWW against an existing row, or the LWW
82
+ * upsert won but every content field matched the prior row (identical
83
+ * re-broadcast). Used by emission gates to suppress duplicate
84
+ * `delegationTokenAdded` events on idempotent re-stores.
85
+ */
86
+ addDelegationToken(token: InsertDelegationToken): Promise<boolean>;
87
+ getDelegationTokens(params: {
88
+ grantor: string;
89
+ audience: string;
90
+ }): Promise<Array<DelegationToken>>;
91
+ /**
92
+ * Return the tokens held by `audience` that are still valid at `atTime`
93
+ * (`exp > atTime`). `atTime` is a Unix epoch in seconds, matching the
94
+ * `exp` column. Expired tokens and tokens addressed to other audiences are
95
+ * excluded.
96
+ */
97
+ getHeldTokens(params: {
98
+ audience: string;
99
+ atTime: number;
100
+ }): Promise<Array<DelegationToken>>;
101
+ listIssuedTokens(grantor: string): Promise<Array<DelegationToken>>;
102
+ /**
103
+ * Look up a delegation token by its `jti`. `jti` is not part of the schema's
104
+ * composite PK `(grantor, audience, resource)`, so the column is non-unique
105
+ * at the SQL level; in practice every `grantWriteCapability` call mints a
106
+ * fresh random `jti`, so collisions across distinct `(grantor, audience,
107
+ * resource)` tuples do not occur. Returns the first matching row, or `null`
108
+ * when no row exists for the given `jti`.
109
+ */
110
+ getDelegationTokenByJti(jti: string): Promise<DelegationToken | null>;
111
+ /**
112
+ * Store a revocation record keyed by the revoked capability's `jti`. LWW on
113
+ * `hlc` — a re-broadcast with an older or equal `hlc` is ignored. Returns
114
+ * `true` when the row is new, or when LWW won and any content field
115
+ * (`revoker_did`, `revoked_iat`, `revocation_token`, `verified_at`,
116
+ * `cap_exp`, `group_id`) materially changed. Returns `false` on a no-op
117
+ * (lost LWW, or won LWW with byte-identical content). Used by emission
118
+ * gates to suppress duplicate downstream events on idempotent re-stores.
119
+ *
120
+ * `verified_at` is `null` while the revoker-vs-cap-issuer cross-check is
121
+ * pending; verification flips it via `markRevocationVerified`.
122
+ */
123
+ addRevocation(input: InsertRevokedCapability): Promise<boolean>;
124
+ getRevocation(jti: string): Promise<RevokedCapability | null>;
125
+ /**
126
+ * Hot-path check used at mutation-apply time. Returns `true` only when a
127
+ * row exists for `jti` AND it has been verified against the cap's issuer
128
+ * (`verified_at IS NOT NULL`). Pending (unverified) revocations do not
129
+ * gate writes; the gate fails open for light clients that do not
130
+ * replicate the revocations table.
131
+ */
132
+ isRevoked(jti: string): Promise<boolean>;
133
+ /**
134
+ * Return verified revocations that target capabilities held by `audience`.
135
+ * INNER JOIN against `kubun_p2p_delegation_tokens` on `jti` — verified
136
+ * revocations whose cap row is missing locally are excluded, and
137
+ * `audience`/`grantor` are sourced from the cap row. Pending revocations
138
+ * (`verified_at IS NULL`) are not surfaced. No `atTime` filter: verified
139
+ * revocations remain visible to the audience until lifecycle GC removes
140
+ * them, even after the underlying cap has expired.
141
+ */
142
+ getHeldRevocations(params: {
143
+ audience: string;
144
+ }): Promise<Array<HeldRevocation>>;
145
+ /**
146
+ * Flip a pending revocation to verified. Idempotent: returns `true` only
147
+ * when a previously-pending row was updated; returns `false` when the row
148
+ * is missing or already verified.
149
+ */
150
+ markRevocationVerified(jti: string, params: {
151
+ cap_exp: number;
152
+ verified_at?: number;
153
+ }): Promise<boolean>;
154
+ getPendingRevocationByJti(jti: string): Promise<RevokedCapability | null>;
155
+ /**
156
+ * Delete a pending revocation row (`verified_at IS NULL`) for the given
157
+ * `jti`. Verified rows are preserved — a verified revocation is binding and
158
+ * must not be removed by this method (use `purgeExpiredRevocations` for
159
+ * lifecycle cleanup). Returns `true` when a pending row was deleted, `false`
160
+ * otherwise (no row, or row already verified).
161
+ *
162
+ * Used by the cap-arrival cross-check: when a held cap arrives whose issuer
163
+ * does not match a pre-existing pending revocation's `revoker_did`, the
164
+ * pending row is treated as a forged broadcast and dropped.
165
+ */
166
+ deletePendingRevocation(jti: string): Promise<boolean>;
167
+ /**
168
+ * Delete verified revocation rows whose underlying capability expired more
169
+ * than `graceSeconds` ago (`cap_exp + graceSeconds < now`). Rows with a
170
+ * NULL `cap_exp` and pending (unverified) rows are preserved — they have
171
+ * no expiry to compare against. Returns the number of rows deleted.
172
+ */
173
+ purgeExpiredRevocations(params: {
174
+ graceSeconds: number;
175
+ }): Promise<number>;
176
+ /**
177
+ * Delete pending revocation rows (`verified_at IS NULL`) whose `created_at`
178
+ * is older than `maxAgeSeconds`. Verified rows are preserved regardless of
179
+ * their `created_at` age. Returns the number of rows deleted.
180
+ *
181
+ * A negative `maxAgeSeconds` pushes the threshold into the future and will
182
+ * delete every pending row regardless of age — intended only for test
183
+ * scaffolding (avoids needing raw-SQL `UPDATE` to backdate `created_at`).
184
+ * Do not pass negative values from production code paths.
185
+ */
186
+ purgeDeadPendingRevocations(params: {
187
+ maxAgeSeconds: number;
188
+ }): Promise<number>;
47
189
  saveMLSState(state: InsertGroupMLSState): Promise<void>;
48
190
  getMLSState(groupID: string, deviceID: string): Promise<GroupMLSState | undefined>;
49
191
  deleteMLSState(groupID: string, deviceID: string): Promise<void>;
@@ -80,4 +222,4 @@ export type P2PStoreAPI = {
80
222
  listGroupsByHubID(hubID: string): Promise<Array<Group>>;
81
223
  deleteGroupHubsByHubID(hubID: string): Promise<void>;
82
224
  };
83
- export declare function createP2PStore(db: Kysely<P2PStoreTables>, adapter: Adapter): P2PStoreAPI;
225
+ 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(t,r,u=e("store-p2p")){let i={async createGroup(e){await t.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 t.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=r.encodeTimestamp(new Date),await t.updateTable("kubun_p2p_groups").set(i).where("id","=",e).execute()},async deleteGroup(e){await t.deleteFrom("kubun_p2p_groups").where("id","=",e).execute()},listGroups:async()=>await t.selectFrom("kubun_p2p_groups").selectAll().execute(),async listGroupsOffsetPaged(e){let{limit:r,offset:u}=e,i=t.selectFrom("kubun_p2p_groups").selectAll().orderBy("name");if(null!=u&&(i=i.offset(u)),null!=r){i=i.limit(r+1);let e=await i.execute(),t=e.length>r;return{entries:t?e.slice(0,r):e,hasMore:t}}return{entries:await i.execute(),hasMore:!1}},async addGroupMember(e){await t.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:r.encodeTimestamp(new Date)}))).execute()},async markMemberRemoved(e,u,i){await t.updateTable("kubun_p2p_group_members").set({removed_at_hlc:i,updated_at:r.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,r){let u=await t.selectFrom("kubun_p2p_group_members").select("removed_at_hlc").where("group_id","=",e).where("member_did","=",r).executeTakeFirst();return null==u?void 0:{removed_at_hlc:u.removed_at_hlc}},async removeGroupMember(e,t,r){await i.markMemberRemoved(e,t,r)},getGroupMember:async(e,r)=>await t.selectFrom("kubun_p2p_group_members").selectAll().where("group_id","=",e).where("member_did","=",r).where("removed_at_hlc","is",null).executeTakeFirst(),isGroupMember:async(e,r)=>null!=await t.selectFrom("kubun_p2p_group_members").select("member_did").where("group_id","=",e).where("member_did","=",r).where("removed_at_hlc","is",null).executeTakeFirst(),isMemberOfAnyGroup:async(e,r)=>0!==r.length&&null!=await t.selectFrom("kubun_p2p_group_members").select("group_id").where("member_did","=",e).where("group_id","in",r).where("removed_at_hlc","is",null).limit(1).executeTakeFirst(),listGroupMembers:async e=>await t.selectFrom("kubun_p2p_group_members").selectAll().where("group_id","=",e).where("removed_at_hlc","is",null).execute(),getGroupsForMember:async e=>await t.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 r=new Map;if(0===e.length)return r;for(let u of(await t.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,...t}=u,i=r.get(e);null!=i?i.push(t):r.set(e,[t])}return r},async createCircle(e){await t.insertInto("kubun_p2p_circles").values({id:e.id,group_id:e.group_id,name:e.name,description:e.description,catalog_ids:r.encodeJSON(e.catalog_ids),hlc:e.hlc}).execute()},getCircle:async e=>await t.selectFrom("kubun_p2p_circles").selectAll().where("id","=",e).executeTakeFirst(),async getCircles(e){let r=new Map;if(0===e.length)return r;for(let u of(await t.selectFrom("kubun_p2p_circles").selectAll().where("id","in",e).execute()))r.set(u.id,u);return r},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=r.encodeJSON(u.catalog_ids)),null!=u.hlc&&(i.hlc=u.hlc),i.updated_at=r.encodeTimestamp(new Date),await t.updateTable("kubun_p2p_circles").set(i).where("id","=",e).execute()},async deleteCircle(e){await t.deleteFrom("kubun_p2p_circles").where("id","=",e).execute()},listCirclesByGroup:async e=>await t.selectFrom("kubun_p2p_circles").selectAll().where("group_id","=",e).execute(),async addCircleMember(e){await t.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")}))).execute()},async removeCircleMember(e,r){await t.deleteFrom("kubun_p2p_circle_members").where("circle_id","=",e).where("member_did","=",r).execute()},getCircleMember:async(e,r)=>await t.selectFrom("kubun_p2p_circle_members").selectAll().where("circle_id","=",e).where("member_did","=",r).executeTakeFirst(),listCircleMembers:async e=>await t.selectFrom("kubun_p2p_circle_members").selectAll().where("circle_id","=",e).execute(),getCirclesForMember:async e=>await t.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).execute(),isMemberOfAnyCircle:async(e,r)=>0!==r.length&&null!=await t.selectFrom("kubun_p2p_circle_members").select("circle_id").where("member_did","=",e).where("circle_id","in",r).limit(1).executeTakeFirst(),async addDelegationToken(e){let a,_=await t.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 t.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:r.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 t.selectFrom("kubun_p2p_delegation_tokens").selectAll().where("grantor","=",e.grantor).where("audience","=",e.audience).execute(),getHeldTokens:async e=>await t.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 t.selectFrom("kubun_p2p_delegation_tokens").selectAll().where("grantor","=",e).execute(),getDelegationTokenByJti:async e=>await t.selectFrom("kubun_p2p_delegation_tokens").selectAll().where("jti","=",e).executeTakeFirst()??null,async addRevocation(e){let u=await t.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 t.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:r.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 t.selectFrom("kubun_p2p_revoked_capabilities").selectAll().where("jti","=",e).executeTakeFirst()??null,isRevoked:async e=>null!=await t.selectFrom("kubun_p2p_revoked_capabilities").select("jti").where("jti","=",e).where("verified_at","is not",null).limit(1).executeTakeFirst(),getHeldRevocations:async e=>(await t.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 t.updateTable("kubun_p2p_revoked_capabilities").set({verified_at:i,cap_exp:u.cap_exp,updated_at:r.encodeTimestamp(new Date)}).where("jti","=",e).where("verified_at","is",null).executeTakeFirst()).numUpdatedRows??0n)>0n},getPendingRevocationByJti:async e=>await t.selectFrom("kubun_p2p_revoked_capabilities").selectAll().where("jti","=",e).where("verified_at","is",null).executeTakeFirst()??null,deletePendingRevocation:async e=>((await t.deleteFrom("kubun_p2p_revoked_capabilities").where("jti","=",e).where("verified_at","is",null).executeTakeFirst()).numDeletedRows??0n)>0n,async purgeExpiredRevocations(e){let r=Math.floor(Date.now()/1e3)-e.graceSeconds;return Number((await t.deleteFrom("kubun_p2p_revoked_capabilities").where("verified_at","is not",null).where("cap_exp","is not",null).where("cap_exp","<",r).executeTakeFirst()).numDeletedRows??0n)},async purgeDeadPendingRevocations(e){let u=r.encodeTimestamp(new Date(Date.now()-1e3*e.maxAgeSeconds));return Number((await t.deleteFrom("kubun_p2p_revoked_capabilities").where("verified_at","is",null).where("created_at","<",u).executeTakeFirst()).numDeletedRows??0n)},async saveMLSState(e){await t.insertInto("kubun_p2p_group_mls_state").values({group_id:e.group_id,device_id:e.device_id,mls_state:r.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:r.encodeTimestamp(new Date)}))).execute()},getMLSState:async(e,r)=>await t.selectFrom("kubun_p2p_group_mls_state").selectAll().where("group_id","=",e).where("device_id","=",r).executeTakeFirst(),async deleteMLSState(e,r){await t.deleteFrom("kubun_p2p_group_mls_state").where("group_id","=",e).where("device_id","=",r).execute()},listMLSStates:async e=>await t.selectFrom("kubun_p2p_group_mls_state").selectAll().where("group_id","=",e).execute(),async deleteGroupMLSStates(e){await t.deleteFrom("kubun_p2p_group_mls_state").where("group_id","=",e).execute()},async addSyncPeer(e){let u=r.encodeJSON(e.allowed_users),i=r.encodeJSON(e.config);await t.insertInto("kubun_p2p_sync_peers").values({...e,allowed_users:u,config:i}).onConflict(t=>t.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 t.selectFrom("kubun_p2p_sync_peers").selectAll().where("peer_did","=",e).executeTakeFirst(),listSyncPeers:async()=>await t.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=r.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=r.encodeJSON(u.config)),i.updated_at=Date.now(),await t.updateTable("kubun_p2p_sync_peers").set(i).where("peer_did","=",e).execute()},async removeSyncPeer(e){await t.deleteFrom("kubun_p2p_sync_peers").where("peer_did","=",e).execute()},recordBroadcast:async e=>((await t.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 t.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 t.insertInto("kubun_p2p_hubs").values({id:e.id,label:e.label,url:e.url,server_did:e.server_did}).execute()},getHub:async e=>await t.selectFrom("kubun_p2p_hubs").selectAll().where("id","=",e).executeTakeFirst(),getHubByURL:async e=>await t.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=r.encodeTimestamp(new Date),await t.updateTable("kubun_p2p_hubs").set(i).where("id","=",e).execute()},async deleteHub(e){await t.deleteFrom("kubun_p2p_hubs").where("id","=",e).execute()},listHubs:async()=>await t.selectFrom("kubun_p2p_hubs").selectAll().execute(),insertGroupHub:async e=>((await t.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 t.deleteFrom("kubun_p2p_group_hubs").where("group_id","=",e.groupID).where("hub_id","=",e.hubID).executeTakeFirst()).numDeletedRows??0n)>0n,listGroupIDsByHubID:async e=>(await t.selectFrom("kubun_p2p_group_hubs").select("group_id").where("hub_id","=",e).execute()).map(e=>e.group_id),listHubsByGroupID:async e=>await t.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 t.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 t.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("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("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
  };
@@ -45,6 +48,45 @@ export type CircleMemberTable = {
45
48
  };
46
49
  export type CircleMember = Selectable<CircleMemberTable>;
47
50
  export type InsertCircleMember = Insertable<CircleMemberTable>;
51
+ export type DelegationTokenTable = {
52
+ jti: string;
53
+ grantor: string;
54
+ audience: string;
55
+ token: string;
56
+ resource: string;
57
+ act: string;
58
+ exp: number;
59
+ group_id: string;
60
+ hlc: string;
61
+ created_at: CreatedAtColumn;
62
+ updated_at: UpdatedAtColumn;
63
+ };
64
+ export type DelegationToken = Selectable<DelegationTokenTable>;
65
+ export type InsertDelegationToken = Insertable<DelegationTokenTable>;
66
+ export type RevokedCapabilityTable = {
67
+ jti: string;
68
+ revoker_did: string;
69
+ revoked_iat: number;
70
+ revocation_token: string;
71
+ verified_at: number | null;
72
+ cap_exp: number | null;
73
+ group_id: string;
74
+ hlc: string;
75
+ created_at: CreatedAtColumn;
76
+ updated_at: UpdatedAtColumn;
77
+ };
78
+ export type RevokedCapability = Selectable<RevokedCapabilityTable>;
79
+ export type InsertRevokedCapability = Insertable<RevokedCapabilityTable>;
80
+ export type HeldRevocation = {
81
+ jti: string;
82
+ grantor: string;
83
+ audience: string;
84
+ revoker_did: string;
85
+ revoked_iat: number;
86
+ verified_at: number;
87
+ cap_exp: number | null;
88
+ group_id: string;
89
+ };
48
90
  export type GroupMLSStateTable = {
49
91
  group_id: string;
50
92
  device_id: string;
@@ -120,6 +162,8 @@ export type P2PStoreTables = {
120
162
  kubun_p2p_group_members: GroupMemberTable;
121
163
  kubun_p2p_circles: CircleTable;
122
164
  kubun_p2p_circle_members: CircleMemberTable;
165
+ kubun_p2p_delegation_tokens: DelegationTokenTable;
166
+ kubun_p2p_revoked_capabilities: RevokedCapabilityTable;
123
167
  kubun_p2p_group_mls_state: GroupMLSStateTable;
124
168
  kubun_p2p_sync_peers: SyncPeerTable;
125
169
  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.0",
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": "^0.10.0",
21
+ "@kubun/db-adapter": "^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-better-sqlite": "^0.10.0",
27
+ "@kubun/db-postgres": "^0.10.0",
28
+ "@kubun/hlc": "^0.10.0"
26
29
  },
27
30
  "scripts": {
28
31
  "build:clean": "del lib",