@kubun/plugin-p2p 0.10.1 → 0.11.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.
Files changed (145) hide show
  1. package/lib/context/delegation.js +130 -1
  2. package/lib/context/group.js +1319 -1
  3. package/lib/context/hub.js +150 -1
  4. package/lib/context/join.js +80 -1
  5. package/lib/context/sync.js +19 -1
  6. package/lib/context/types.d.ts +3 -3
  7. package/lib/context/types.js +28 -1
  8. package/lib/groups/admin-roster.d.ts +130 -0
  9. package/lib/groups/admin-roster.js +184 -0
  10. package/lib/groups/broadcast-codec.js +6 -1
  11. package/lib/groups/broadcast.d.ts +105 -91
  12. package/lib/groups/broadcast.js +627 -1
  13. package/lib/groups/circle-projection.d.ts +122 -0
  14. package/lib/groups/circle-projection.js +288 -0
  15. package/lib/groups/circle-reducers.d.ts +178 -0
  16. package/lib/groups/circle-reducers.js +198 -0
  17. package/lib/groups/events.d.ts +5 -1
  18. package/lib/groups/events.js +10 -1
  19. package/lib/groups/group-anchor.d.ts +63 -0
  20. package/lib/groups/group-anchor.js +107 -0
  21. package/lib/groups/group-crypto.d.ts +19 -0
  22. package/lib/groups/group-crypto.js +85 -0
  23. package/lib/groups/group-handle-registry.d.ts +61 -2
  24. package/lib/groups/group-handle-registry.js +243 -1
  25. package/lib/groups/group-handlers.d.ts +29 -0
  26. package/lib/groups/group-handlers.js +92 -0
  27. package/lib/groups/group-health-monitor.d.ts +11 -0
  28. package/lib/groups/group-health-monitor.js +139 -1
  29. package/lib/groups/group-mls.d.ts +35 -0
  30. package/lib/groups/group-mls.js +164 -0
  31. package/lib/groups/group-peer-manager.d.ts +108 -0
  32. package/lib/groups/group-peer-manager.js +344 -0
  33. package/lib/groups/group-protocols.d.ts +297 -0
  34. package/lib/groups/group-protocols.js +174 -0
  35. package/lib/groups/invite-payload.d.ts +1 -1
  36. package/lib/groups/invite-payload.js +26 -1
  37. package/lib/groups/join-utils.d.ts +1 -0
  38. package/lib/groups/join-utils.js +104 -1
  39. package/lib/groups/ledger-affected-events.d.ts +15 -0
  40. package/lib/groups/ledger-affected-events.js +96 -0
  41. package/lib/groups/ledger-entry.d.ts +37 -0
  42. package/lib/groups/ledger-entry.js +56 -0
  43. package/lib/groups/ledger-fold.d.ts +56 -0
  44. package/lib/groups/ledger-fold.js +53 -0
  45. package/lib/groups/manager.d.ts +20 -71
  46. package/lib/groups/manager.js +281 -1
  47. package/lib/groups/mls-codec.d.ts +1 -1
  48. package/lib/groups/mls-codec.js +18 -1
  49. package/lib/groups/mls-encryptor.d.ts +2 -2
  50. package/lib/groups/mls-encryptor.js +30 -1
  51. package/lib/groups/mls-group-handle.d.ts +1 -1
  52. package/lib/groups/mls-group-handle.js +10 -1
  53. package/lib/groups/mls-json.js +17 -1
  54. package/lib/groups/mls-receive-errors.d.ts +29 -0
  55. package/lib/groups/mls-receive-errors.js +42 -0
  56. package/lib/groups/mls-state.d.ts +1 -1
  57. package/lib/groups/mls-state.js +56 -1
  58. package/lib/groups/store-received-grant.d.ts +4 -1
  59. package/lib/groups/store-received-grant.js +116 -1
  60. package/lib/groups/store-received-revocation.d.ts +3 -3
  61. package/lib/groups/store-received-revocation.js +82 -1
  62. package/lib/hub/ack-backoff.d.ts +22 -0
  63. package/lib/hub/ack-backoff.js +88 -0
  64. package/lib/hub/did-observing-transport.d.ts +13 -2
  65. package/lib/hub/did-observing-transport.js +126 -1
  66. package/lib/hub/errors.d.ts +0 -6
  67. package/lib/hub/errors.js +33 -1
  68. package/lib/hub/http-client.d.ts +28 -3
  69. package/lib/hub/http-client.js +32 -1
  70. package/lib/hub/hub-like.d.ts +24 -0
  71. package/lib/hub/hub-like.js +535 -0
  72. package/lib/hub/manager.js +198 -1
  73. package/lib/hub/wiring.d.ts +17 -48
  74. package/lib/hub/wiring.js +132 -1
  75. package/lib/index.d.ts +17 -16
  76. package/lib/index.js +392 -1
  77. package/lib/protocol.d.ts +1 -1
  78. package/lib/protocol.js +251 -1
  79. package/lib/schema.d.ts +2 -1
  80. package/lib/schema.js +439 -2
  81. package/lib/sync/authorize.d.ts +1 -1
  82. package/lib/sync/authorize.js +73 -1
  83. package/lib/sync/broadcast-queue.js +183 -1
  84. package/lib/sync/broadcast-sender.js +179 -1
  85. package/lib/sync/catalog-scope.js +68 -1
  86. package/lib/sync/errors.d.ts +3 -2
  87. package/lib/sync/errors.js +19 -1
  88. package/lib/sync/forwarder.js +277 -1
  89. package/lib/sync/handlers.js +160 -1
  90. package/lib/sync/hub-tunnel-sync-listener.d.ts +8 -4
  91. package/lib/sync/hub-tunnel-sync-listener.js +172 -1
  92. package/lib/sync/hub-tunnel-sync-provider.d.ts +14 -14
  93. package/lib/sync/hub-tunnel-sync-provider.js +64 -1
  94. package/lib/sync/merkle-apply.d.ts +5 -0
  95. package/lib/sync/merkle-apply.js +194 -1
  96. package/lib/sync/merkle-channel.d.ts +2 -2
  97. package/lib/sync/merkle-channel.js +182 -1
  98. package/lib/sync/merkle-tree.js +120 -1
  99. package/lib/sync/peer-registry.d.ts +1 -1
  100. package/lib/sync/peer-registry.js +107 -1
  101. package/lib/sync/receive-access-gate.js +24 -1
  102. package/lib/sync/scope-resolver.js +41 -1
  103. package/lib/sync/sync-client.d.ts +3 -3
  104. package/lib/sync/sync-client.js +192 -1
  105. package/lib/sync/sync-manager.d.ts +2 -2
  106. package/lib/sync/sync-manager.js +272 -1
  107. package/lib/types.d.ts +11 -0
  108. package/lib/types.js +2 -1
  109. package/lib/util/mutex.js +10 -1
  110. package/lib/utils.js +21 -1
  111. package/package.json +47 -40
  112. package/lib/groups/broadcast-service.d.ts +0 -263
  113. package/lib/groups/broadcast-service.js +0 -1
  114. package/lib/groups/rejoin-codec.d.ts +0 -14
  115. package/lib/groups/rejoin-codec.js +0 -1
  116. package/lib/groups/wire-frame.d.ts +0 -34
  117. package/lib/groups/wire-frame.js +0 -1
  118. package/lib/hub/circle-catchup-requester.d.ts +0 -58
  119. package/lib/hub/circle-catchup-requester.js +0 -1
  120. package/lib/hub/circle-catchup-responder.d.ts +0 -50
  121. package/lib/hub/circle-catchup-responder.js +0 -1
  122. package/lib/hub/connection-pool.d.ts +0 -43
  123. package/lib/hub/connection-pool.js +0 -1
  124. package/lib/hub/epoch-stale-detector.d.ts +0 -18
  125. package/lib/hub/epoch-stale-detector.js +0 -1
  126. package/lib/hub/forward-remote-broadcast.d.ts +0 -15
  127. package/lib/hub/forward-remote-broadcast.js +0 -1
  128. package/lib/hub/group-channel.d.ts +0 -89
  129. package/lib/hub/group-channel.js +0 -1
  130. package/lib/hub/hub-connection.d.ts +0 -96
  131. package/lib/hub/hub-connection.js +0 -1
  132. package/lib/hub/receive-handler.d.ts +0 -51
  133. package/lib/hub/receive-handler.js +0 -1
  134. package/lib/hub/rejoin-manager.d.ts +0 -78
  135. package/lib/hub/rejoin-manager.js +0 -1
  136. package/lib/hub/rejoin-responder.d.ts +0 -32
  137. package/lib/hub/rejoin-responder.js +0 -1
  138. package/lib/hub/relay-manager.d.ts +0 -142
  139. package/lib/hub/relay-manager.js +0 -1
  140. package/lib/hub/send-handler.d.ts +0 -40
  141. package/lib/hub/send-handler.js +0 -1
  142. package/lib/hub/tunnel-inbox.d.ts +0 -20
  143. package/lib/hub/tunnel-inbox.js +0 -1
  144. package/lib/hub/wait-for-gate.d.ts +0 -14
  145. package/lib/hub/wait-for-gate.js +0 -1
@@ -1 +1,272 @@
1
- import{EventEmitter as e}from"@enkaku/event";import{isSigningIdentity as t}from"@enkaku/token";import{getGraphStore as s}from"@kubun/store-graph";import{applySyncMutations as r}from"./merkle-apply.js";import{buildMerkleTree as i}from"./merkle-tree.js";import{PeerRegistry as n}from"./peer-registry.js";import{SyncClient as o}from"./sync-client.js";export class SyncManager{#e;#t;#s;#r;#i;#n=new e;#o=new Map;#a=new Map;#l;#c;#g;#p;constructor(e){this.#e=e.deployClusters,this.#t=e.graph,this.#s=e.runtime,this.#l=e.identity,this.#r=e.logger,this.#i=new n({runtime:e.runtime}),this.#c=e.serverResolver,this.#g=e.storeUnreadable??"persist",this.#p=e.defaultAccessLevel}get peerRegistry(){return this.#i}setIdentity(e){this.#l=e}setServerResolver(e){this.#c=e}async addPeer(e){await this.#i.addPeer(e),this.#r.info("Peer added",{peerDID:e.config.peerDID})}async removePeer(e){await this.#i.removePeer(e),this.#r.info("Peer removed",{peerDID:e.peerDID})}async updatePeerConfig(e){await this.#i.updatePeer(e),this.#r.info("Peer updated",{peerDID:e.peerDID})}async listPeers(e){return this.#i.listPeers(e)}async getPeer(e){return this.#i.getPeer(e)}async merkleSyncWithPeer(e){let n,{peerDID:a,scopes:l,delegationTokens:c=[],knownModelIDs:g,direction:p}=e;this.#r.info("Starting Merkle sync with peer",{peerDID:a,scopes:l});let m=await this.#i.getPeer({peerDID:a,stores:e.stores});if(!m)throw Error(`Peer ${a} not found`);let d=`merkle-sync-${a}-${Date.now()}`,h={peerID:a,startTime:Date.now(),documentsAttempted:0,documentsCompleted:0};this.#o.set(d,h),this.#m({type:"started",peerID:a,timestamp:Date.now()});try{let u=this.#l;if(!t(u))throw Error("Signing identity required for Merkle sync");let y=new o({runtime:this.#s,identity:u,logger:this.#r,serverResolver:this.#c});n=await y.connect(m.endpoint,a);let v=p??"pull";this.#r.info("Negotiating sync scopes",{scopes:l,direction:v});let{acceptedScopes:f,excludedDocumentIDs:D,missingClusters:w,direction:k}=await y.negotiate(n,l,c,g,v);if(0===f.length)return this.#r.info("No scopes accepted by peer"),this.#a.set(a,Date.now()),this.#m({type:"completed",peerID:a,timestamp:Date.now()}),this.#o.delete(d),{sessionID:d,divergentBuckets:0,messagesReceived:0,messagesSent:0,missingClusters:w};null!=w&&Object.keys(w).length>0&&(null!=this.#e?(this.#r.info("Deploying missing clusters from peer",{clusterCount:Object.keys(w).length}),await this.#e(w)):this.#r.warn("Missing clusters received but no deployClusters callback configured",{clusterCount:Object.keys(w).length})),this.#r.info("Building local Merkle tree",{acceptedScopes:f});let S=await s(e.stores),P=await S.getDocumentIDsForScope(f,D),R=await S.getMutationLogForDocuments(P),I=i(R),b=k??v;this.#r.info("Requesting Merkle sync from peer",{localTreeBuckets:Object.keys(I.buckets).length,direction:b});let C=await y.merkleSync(n,{scopes:f,excludedDocumentIDs:D,localTree:I,direction:b,localEntries:R,delegationTokens:c});return C.mutationJWTs.length>0&&(this.#r.info("Applying sync mutations",{mutations:C.mutationJWTs.length}),h.documentsCompleted=(await r({db:e.stores,graph:this.#t,mutationJWTs:C.mutationJWTs,selfDID:this.#l.id,storeUnreadable:this.#g,defaultAccessLevel:this.#p})).applied),this.#a.set(a,Date.now()),this.#m({type:"completed",peerID:a,timestamp:Date.now()}),this.#r.info("Merkle sync completed",{divergentBuckets:C.divergentBuckets,mutationsReceived:C.mutationJWTs.length}),{sessionID:d,divergentBuckets:C.divergentBuckets,messagesReceived:C.mutationJWTs.length,messagesSent:C.mutationsSent,missingClusters:w}}catch(e){throw this.#r.error("Merkle sync failed",{peerDID:a,error:e}),this.#m({type:"error",peerID:a,timestamp:Date.now(),error:e instanceof Error?e.message:String(e)}),e}finally{if(null!=n)try{n.abort("SyncComplete"),await n.dispose()}catch{}this.#o.delete(d)}}getStatus(e){let{peerDID:t}=e,s=Array.from(this.#o.values()).filter(e=>!t||e.peerID===t).map(e=>({peerID:e.peerID,startTime:e.startTime,documentsAttempted:e.documentsAttempted,documentsCompleted:e.documentsCompleted})),r={};for(let[e,s]of this.#a.entries())t&&e!==t||(r[e]=s);return{activeSessions:s,lastSyncByPeer:r}}onSyncEvent(e){return this.#n.on("sync",e)}#m(e){this.#n.emit("sync",e).catch(e=>{this.#r.error("Error in sync event listener",{error:e})})}async dispose(){this.#o.clear(),this.#a.clear()}}
1
+ import { isSigningIdentity } from '@kokuin/token';
2
+ import { getGraphStore } from '@kubun/store-graph';
3
+ import { EventEmitter } from '@sozai/event';
4
+ import { applySyncMutations } from './merkle-apply.js';
5
+ import { buildMerkleTree } from './merkle-tree.js';
6
+ import { PeerRegistry } from './peer-registry.js';
7
+ import { SyncClient } from './sync-client.js';
8
+ export class SyncManager {
9
+ #deployClusters;
10
+ #graph;
11
+ #runtime;
12
+ #logger;
13
+ #peerRegistry;
14
+ #events = new EventEmitter();
15
+ #activeSessions = new Map();
16
+ #lastSyncByPeer = new Map();
17
+ #identity;
18
+ #serverResolver;
19
+ #storeUnreadable;
20
+ #defaultAccessLevel;
21
+ constructor(params){
22
+ this.#deployClusters = params.deployClusters;
23
+ this.#graph = params.graph;
24
+ this.#runtime = params.runtime;
25
+ this.#identity = params.identity;
26
+ this.#logger = params.logger;
27
+ this.#peerRegistry = new PeerRegistry({
28
+ runtime: params.runtime
29
+ });
30
+ this.#serverResolver = params.serverResolver;
31
+ this.#storeUnreadable = params.storeUnreadable ?? 'persist';
32
+ this.#defaultAccessLevel = params.defaultAccessLevel;
33
+ }
34
+ get peerRegistry() {
35
+ return this.#peerRegistry;
36
+ }
37
+ /**
38
+ * Configure the signing identity used for sync operations.
39
+ */ setIdentity(identity) {
40
+ this.#identity = identity;
41
+ }
42
+ /**
43
+ * Configure the server resolver for direct transport connections.
44
+ */ setServerResolver(resolver) {
45
+ this.#serverResolver = resolver;
46
+ }
47
+ // ---- Peer management (delegates to PeerRegistry) ----
48
+ async addPeer(params) {
49
+ await this.#peerRegistry.addPeer(params);
50
+ this.#logger.info('Peer added', {
51
+ peerDID: params.config.peerDID
52
+ });
53
+ }
54
+ async removePeer(params) {
55
+ await this.#peerRegistry.removePeer(params);
56
+ this.#logger.info('Peer removed', {
57
+ peerDID: params.peerDID
58
+ });
59
+ }
60
+ async updatePeerConfig(params) {
61
+ await this.#peerRegistry.updatePeer(params);
62
+ this.#logger.info('Peer updated', {
63
+ peerDID: params.peerDID
64
+ });
65
+ }
66
+ async listPeers(params) {
67
+ return this.#peerRegistry.listPeers(params);
68
+ }
69
+ async getPeer(params) {
70
+ return this.#peerRegistry.getPeer(params);
71
+ }
72
+ // ---- Sync orchestration ----
73
+ async merkleSyncWithPeer(params) {
74
+ const { peerDID, scopes, delegationTokens = [], knownModelIDs, direction: requestedDirection } = params;
75
+ this.#logger.info('Starting Merkle sync with peer', {
76
+ peerDID,
77
+ scopes
78
+ });
79
+ const peer = await this.#peerRegistry.getPeer({
80
+ peerDID,
81
+ stores: params.stores
82
+ });
83
+ if (!peer) {
84
+ throw new Error(`Peer ${peerDID} not found`);
85
+ }
86
+ const sessionID = `merkle-sync-${peerDID}-${Date.now()}`;
87
+ const sessionData = {
88
+ peerID: peerDID,
89
+ startTime: Date.now(),
90
+ documentsAttempted: 0,
91
+ documentsCompleted: 0
92
+ };
93
+ this.#activeSessions.set(sessionID, sessionData);
94
+ this.#emitEvent({
95
+ type: 'started',
96
+ peerID: peerDID,
97
+ timestamp: Date.now()
98
+ });
99
+ let client;
100
+ try {
101
+ const identity = this.#identity;
102
+ if (!isSigningIdentity(identity)) {
103
+ throw new Error('Signing identity required for Merkle sync');
104
+ }
105
+ const syncClient = new SyncClient({
106
+ runtime: this.#runtime,
107
+ identity,
108
+ logger: this.#logger,
109
+ serverResolver: this.#serverResolver
110
+ });
111
+ client = await syncClient.connect(peer.endpoint, peerDID);
112
+ // Step 1: Negotiate scopes
113
+ const direction = requestedDirection ?? 'pull';
114
+ this.#logger.info('Negotiating sync scopes', {
115
+ scopes,
116
+ direction
117
+ });
118
+ const { acceptedScopes, excludedDocumentIDs, missingClusters, direction: agreedDirection } = await syncClient.negotiate(client, scopes, delegationTokens, knownModelIDs, direction);
119
+ if (acceptedScopes.length === 0) {
120
+ this.#logger.info('No scopes accepted by peer');
121
+ this.#lastSyncByPeer.set(peerDID, Date.now());
122
+ this.#emitEvent({
123
+ type: 'completed',
124
+ peerID: peerDID,
125
+ timestamp: Date.now()
126
+ });
127
+ this.#activeSessions.delete(sessionID);
128
+ return {
129
+ sessionID,
130
+ divergentBuckets: 0,
131
+ messagesReceived: 0,
132
+ messagesSent: 0,
133
+ missingClusters
134
+ };
135
+ }
136
+ // Step 1b: Deploy missing clusters before building merkle tree
137
+ if (missingClusters != null && Object.keys(missingClusters).length > 0) {
138
+ if (this.#deployClusters != null) {
139
+ this.#logger.info('Deploying missing clusters from peer', {
140
+ clusterCount: Object.keys(missingClusters).length
141
+ });
142
+ await this.#deployClusters(missingClusters);
143
+ } else {
144
+ this.#logger.warn('Missing clusters received but no deployClusters callback configured', {
145
+ clusterCount: Object.keys(missingClusters).length
146
+ });
147
+ }
148
+ }
149
+ // Step 2: Build local Merkle tree for accepted scopes
150
+ this.#logger.info('Building local Merkle tree', {
151
+ acceptedScopes
152
+ });
153
+ const graphStore = await getGraphStore(params.stores);
154
+ const documentIDs = await graphStore.getDocumentIDsForScope(acceptedScopes, excludedDocumentIDs);
155
+ const localEntries = await graphStore.getMutationLogForDocuments(documentIDs);
156
+ const localTree = buildMerkleTree(localEntries);
157
+ // Step 3: Sync with peer via bidirectional channel
158
+ const syncDirection = agreedDirection ?? direction;
159
+ this.#logger.info('Requesting Merkle sync from peer', {
160
+ localTreeBuckets: Object.keys(localTree.buckets).length,
161
+ direction: syncDirection
162
+ });
163
+ const syncResult = await syncClient.merkleSync(client, {
164
+ scopes: acceptedScopes,
165
+ excludedDocumentIDs,
166
+ localTree,
167
+ direction: syncDirection,
168
+ localEntries,
169
+ // Forward the same delegation tokens used during negotiate so the
170
+ // merkle-sync access gate authorizes the accepted non-owned scopes. A
171
+ // revocation landing between negotiate and sync fails the sync; the
172
+ // retry renegotiates with the updated revocation state.
173
+ delegationTokens
174
+ });
175
+ // Step 4: Apply received mutation JWTs locally
176
+ if (syncResult.mutationJWTs.length > 0) {
177
+ this.#logger.info('Applying sync mutations', {
178
+ mutations: syncResult.mutationJWTs.length
179
+ });
180
+ const applyResult = await applySyncMutations({
181
+ db: params.stores,
182
+ graph: this.#graph,
183
+ mutationJWTs: syncResult.mutationJWTs,
184
+ selfDID: this.#identity.id,
185
+ storeUnreadable: this.#storeUnreadable,
186
+ defaultAccessLevel: this.#defaultAccessLevel
187
+ });
188
+ sessionData.documentsCompleted = applyResult.applied;
189
+ if (applyResult.deferred > 0) {
190
+ this.#logger.info('Deferred sync mutations for retry next round', {
191
+ deferred: applyResult.deferred
192
+ });
193
+ }
194
+ }
195
+ this.#lastSyncByPeer.set(peerDID, Date.now());
196
+ this.#emitEvent({
197
+ type: 'completed',
198
+ peerID: peerDID,
199
+ timestamp: Date.now()
200
+ });
201
+ this.#logger.info('Merkle sync completed', {
202
+ divergentBuckets: syncResult.divergentBuckets,
203
+ mutationsReceived: syncResult.mutationJWTs.length
204
+ });
205
+ return {
206
+ sessionID,
207
+ divergentBuckets: syncResult.divergentBuckets,
208
+ messagesReceived: syncResult.mutationJWTs.length,
209
+ messagesSent: syncResult.mutationsSent,
210
+ missingClusters
211
+ };
212
+ } catch (error) {
213
+ this.#logger.error('Merkle sync failed', {
214
+ peerDID,
215
+ error
216
+ });
217
+ this.#emitEvent({
218
+ type: 'error',
219
+ peerID: peerDID,
220
+ timestamp: Date.now(),
221
+ error: error instanceof Error ? error.message : String(error)
222
+ });
223
+ throw error;
224
+ } finally{
225
+ if (client != null) {
226
+ try {
227
+ client.abort('SyncComplete');
228
+ await client.dispose();
229
+ } catch {
230
+ // Transport may already be closed
231
+ }
232
+ }
233
+ this.#activeSessions.delete(sessionID);
234
+ }
235
+ }
236
+ // ---- Status ----
237
+ getStatus(params) {
238
+ const { peerDID } = params;
239
+ const sessions = Array.from(this.#activeSessions.values()).filter((s)=>!peerDID || s.peerID === peerDID).map((data)=>({
240
+ peerID: data.peerID,
241
+ startTime: data.startTime,
242
+ documentsAttempted: data.documentsAttempted,
243
+ documentsCompleted: data.documentsCompleted
244
+ }));
245
+ const lastSyncByPeer = {};
246
+ for (const [peer, time] of this.#lastSyncByPeer.entries()){
247
+ if (!peerDID || peer === peerDID) {
248
+ lastSyncByPeer[peer] = time;
249
+ }
250
+ }
251
+ return {
252
+ activeSessions: sessions,
253
+ lastSyncByPeer
254
+ };
255
+ }
256
+ // ---- Events ----
257
+ onSyncEvent(callback) {
258
+ return this.#events.on('sync', callback);
259
+ }
260
+ #emitEvent(event) {
261
+ this.#events.emit('sync', event).catch((error)=>{
262
+ this.#logger.error('Error in sync event listener', {
263
+ error
264
+ });
265
+ });
266
+ }
267
+ // ---- Lifecycle ----
268
+ async dispose() {
269
+ this.#activeSessions.clear();
270
+ this.#lastSyncByPeer.clear();
271
+ }
272
+ }
package/lib/types.d.ts CHANGED
@@ -101,6 +101,17 @@ export type GroupRequestContext = {
101
101
  list: () => Promise<Array<GroupData>>;
102
102
  get: (id: string) => Promise<GroupData | null>;
103
103
  addMember: (groupID: string, memberDID: string, role: string) => Promise<GroupMemberData>;
104
+ /**
105
+ * Promote a member to admin or demote them back to member by emitting a
106
+ * self-signed `group.role.admin` ledger entry. Authority is rooted at the
107
+ * group's genesis anchor and folded on every peer, so a non-admin caller is
108
+ * rejected and the change converges identically across the group.
109
+ */
110
+ setMemberRole: (params: {
111
+ groupID: string;
112
+ memberDID: string;
113
+ role: 'admin' | 'member';
114
+ }) => Promise<GroupMemberData>;
104
115
  listMembers: (groupID: string) => Promise<Array<GroupMemberData>>;
105
116
  createCircle: (params: {
106
117
  groupID: string;
package/lib/types.js CHANGED
@@ -1 +1,2 @@
1
- export{};
1
+ // ---- Plugin API ----
2
+ export { };
package/lib/util/mutex.js CHANGED
@@ -1 +1,10 @@
1
- export function createMutex(){let e=Promise.resolve();return{run(t){let r=e.then(()=>t());return e=r.catch(()=>{}),r}}}
1
+ export function createMutex() {
2
+ let tail = Promise.resolve();
3
+ return {
4
+ run (fn) {
5
+ const next = tail.then(()=>fn());
6
+ tail = next.catch(()=>{});
7
+ return next;
8
+ }
9
+ };
10
+ }
package/lib/utils.js CHANGED
@@ -1 +1,21 @@
1
- export function unref(e){e.unref?.()}export function sleep(e){return new Promise(n=>{unref(setTimeout(n,e))})}export function bytesEqual(e,n){if(e.length!==n.length)return!1;for(let t=0;t<e.length;t++)if(e[t]!==n[t])return!1;return!0}
1
+ /** Node returns a Timeout object with `unref()`; browsers return a number. */ export function unref(timer) {
2
+ ;
3
+ timer.unref?.();
4
+ }
5
+ /** Resolve after `ms`, without keeping the process alive. */ export function sleep(ms) {
6
+ return new Promise((resolve)=>{
7
+ const timer = setTimeout(resolve, ms);
8
+ unref(timer);
9
+ });
10
+ }
11
+ export function bytesEqual(a, b) {
12
+ if (a.length !== b.length) {
13
+ return false;
14
+ }
15
+ for(let i = 0; i < a.length; i++){
16
+ if (a[i] !== b[i]) {
17
+ return false;
18
+ }
19
+ }
20
+ return true;
21
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kubun/plugin-p2p",
3
- "version": "0.10.1",
3
+ "version": "0.11.0",
4
4
  "license": "see LICENSE.md",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
@@ -14,54 +14,61 @@
14
14
  ],
15
15
  "sideEffects": false,
16
16
  "dependencies": {
17
- "@enkaku/async": "^0.16.0",
18
- "@enkaku/capability": "^0.16.0",
19
- "@enkaku/client": "^0.16.0",
20
- "@enkaku/codec": "^0.16.0",
21
- "@enkaku/event": "^0.16.0",
22
- "@enkaku/generator": "^0.16.0",
23
- "@enkaku/group": "^0.16.2",
24
- "@enkaku/http-client-transport": "^0.16.0",
25
- "@enkaku/http-server-transport": "^0.16.0",
26
- "@enkaku/hub-protocol": "^0.16.0",
27
- "@enkaku/hub-tunnel": "^0.16.0",
28
- "@enkaku/protocol": "^0.16.0",
29
- "@enkaku/runtime": "^0.16.0",
30
- "@enkaku/schema": "^0.16.0",
31
- "@enkaku/server": "^0.16.0",
32
- "@enkaku/stream": "^0.16.0",
33
- "@enkaku/token": "^0.16.0",
34
- "@enkaku/transport": "^0.16.0",
17
+ "@sozai/async": "^0.1.0",
18
+ "@kumiai/broadcast": "^0.1.0",
19
+ "@kokuin/capability": "^0.1.0",
20
+ "@enkaku/client": "^0.18.1",
21
+ "@sozai/codec": "^0.1.0",
22
+ "@sozai/event": "^0.1.0",
23
+ "@sozai/generator": "^0.1.0",
24
+ "@kumiai/mls": "^0.1.0",
25
+ "@kumiai/rpc": "^0.1.0",
26
+ "@enkaku/http-fetch": "^0.18.1",
27
+ "@enkaku/http-serve": "^0.18.1",
28
+ "@kumiai/hub-protocol": "^0.1.0",
29
+ "@kumiai/hub-tunnel": "^0.1.0",
30
+ "@enkaku/protocol": "^0.18.1",
31
+ "@sozai/runtime": "^0.1.0",
32
+ "@sozai/schema": "^0.1.0",
33
+ "@enkaku/server": "^0.18.1",
34
+ "@sozai/stream": "^0.1.0",
35
+ "@kokuin/token": "^0.1.1",
36
+ "@enkaku/transport": "^0.18.1",
35
37
  "@noble/hashes": "^2.2.0",
36
38
  "graphql": "^16.14.2",
37
39
  "kysely": "^0.29.2",
38
- "@kubun/client": "^0.10.1",
39
- "@kubun/db-adapter": "^0.10.0",
40
- "@kubun/db": "^0.10.0",
41
- "@kubun/engine": "^0.10.3",
42
- "@kubun/store-graph": "^0.10.2",
43
- "@kubun/store-p2p": "^0.10.1",
44
- "@kubun/hlc": "^0.10.1",
45
- "@kubun/graphql": "^0.10.1",
46
- "@kubun/id": "^0.10.0",
47
- "@kubun/mutation": "^0.10.1",
48
- "@kubun/logger": "^0.10.0",
49
- "@kubun/protocol": "^0.10.0"
40
+ "ts-mls": "2.0.0-rc.13",
41
+ "@kubun/db": "^0.11.0",
42
+ "@kubun/db-adapter": "^0.11.0",
43
+ "@kubun/graphql": "^0.11.0",
44
+ "@kubun/hlc": "^0.11.0",
45
+ "@kubun/engine": "^0.11.0",
46
+ "@kubun/http-util": "^0.11.0",
47
+ "@kubun/logger": "^0.11.0",
48
+ "@kubun/client": "^0.11.0",
49
+ "@kubun/id": "^0.11.0",
50
+ "@kubun/mutation": "^0.11.0",
51
+ "@kubun/store-graph": "^0.11.0",
52
+ "@kubun/protocol": "^0.11.0",
53
+ "@kubun/store-delegation": "^0.11.0",
54
+ "@kubun/store-p2p": "^0.11.0"
50
55
  },
51
56
  "devDependencies": {
52
- "@enkaku/hub-server": "^0.16.0",
57
+ "@kumiai/hub-server": "^0.1.0",
58
+ "@testcontainers/postgresql": "^12.0.4",
53
59
  "get-port": "^7.2.0",
54
- "@kubun/db-better-sqlite": "^0.10.0",
55
- "@kubun/hub": "^0.10.0",
56
- "@kubun/plugin-connector": "^0.10.0",
57
- "@kubun/db-node-sqlite": "^0.10.0",
58
- "@kubun/plugin-http": "^0.10.0",
59
- "@kubun/plugin-rpc": "^0.10.0",
60
- "@kubun/test-utils": "^0.10.0"
60
+ "@kubun/db-better-sqlite": "^0.11.0",
61
+ "@kubun/db-node-sqlite": "^0.11.0",
62
+ "@kubun/plugin-connector": "^0.11.0",
63
+ "@kubun/db-postgres": "^0.11.0",
64
+ "@kubun/plugin-rpc": "^0.11.0",
65
+ "@kubun/plugin-http": "^0.11.0",
66
+ "@kubun/hub": "^0.11.0",
67
+ "@kubun/test-utils": "^0.11.0"
61
68
  },
62
69
  "scripts": {
63
70
  "build:clean": "del lib",
64
- "build:js": "swc src -d ./lib --config-file ../../swc.json --strip-leading-paths",
71
+ "build:js": "swc src -d ./lib --config-file ../../node_modules/@kigu/dev/swc.json --strip-leading-paths",
65
72
  "build:types": "tsc --emitDeclarationOnly --skipLibCheck",
66
73
  "build": "pnpm run build:clean && pnpm run build:js && pnpm run build:types",
67
74
  "test:types": "tsc --noEmit -p tsconfig.test.json",