@kubun/plugin-p2p 0.8.3 → 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/context/delegation.d.ts +4 -0
- package/lib/context/delegation.js +1 -0
- package/lib/context/group.js +1 -1
- package/lib/context/hub.d.ts +4 -0
- package/lib/context/hub.js +1 -0
- package/lib/context/join.js +1 -1
- package/lib/context/types.d.ts +28 -2
- package/lib/groups/broadcast-service.d.ts +130 -17
- package/lib/groups/broadcast-service.js +1 -1
- package/lib/groups/broadcast.d.ts +162 -5
- package/lib/groups/broadcast.js +1 -1
- package/lib/groups/events.d.ts +26 -5
- package/lib/groups/events.js +1 -1
- package/lib/groups/group-handle-registry.d.ts +90 -0
- package/lib/groups/group-handle-registry.js +1 -0
- package/lib/groups/invite-payload.d.ts +21 -1
- package/lib/groups/join-utils.d.ts +68 -2
- package/lib/groups/join-utils.js +1 -1
- package/lib/groups/manager.d.ts +26 -18
- package/lib/groups/manager.js +1 -1
- package/lib/groups/mls-codec.d.ts +7 -0
- package/lib/groups/mls-codec.js +1 -0
- package/lib/groups/mls-encryptor.d.ts +25 -0
- package/lib/groups/mls-encryptor.js +1 -0
- package/lib/groups/store-received-grant.d.ts +47 -0
- package/lib/groups/store-received-grant.js +1 -0
- package/lib/groups/store-received-revocation.d.ts +46 -0
- package/lib/groups/store-received-revocation.js +1 -0
- package/lib/groups/wire-frame.d.ts +32 -0
- package/lib/groups/wire-frame.js +1 -0
- package/lib/hub/connection-pool.d.ts +24 -0
- package/lib/hub/connection-pool.js +1 -1
- package/lib/hub/did-observing-transport.d.ts +64 -0
- package/lib/hub/did-observing-transport.js +1 -0
- package/lib/hub/errors.d.ts +28 -0
- package/lib/hub/errors.js +1 -0
- package/lib/hub/forward-remote-broadcast.d.ts +15 -0
- package/lib/hub/forward-remote-broadcast.js +1 -0
- package/lib/hub/group-channel.d.ts +37 -21
- package/lib/hub/group-channel.js +1 -1
- package/lib/hub/http-client.d.ts +17 -0
- package/lib/hub/http-client.js +1 -0
- package/lib/hub/hub-connection.d.ts +96 -0
- package/lib/hub/hub-connection.js +1 -0
- package/lib/hub/manager.d.ts +117 -0
- package/lib/hub/manager.js +1 -0
- package/lib/hub/receive-handler.d.ts +3 -6
- package/lib/hub/receive-handler.js +1 -1
- package/lib/hub/relay-manager.d.ts +92 -2
- package/lib/hub/relay-manager.js +1 -1
- package/lib/hub/send-handler.d.ts +28 -16
- package/lib/hub/send-handler.js +1 -1
- package/lib/hub/tunnel-inbox.d.ts +20 -0
- package/lib/hub/tunnel-inbox.js +1 -0
- package/lib/hub/wait-for-gate.d.ts +14 -0
- package/lib/hub/wait-for-gate.js +1 -0
- package/lib/hub/wiring.d.ts +85 -0
- package/lib/hub/wiring.js +1 -0
- package/lib/index.d.ts +102 -7
- package/lib/index.js +1 -1
- package/lib/schema.d.ts +2 -2
- package/lib/schema.js +113 -21
- package/lib/sync/broadcast-queue.d.ts +59 -0
- package/lib/sync/broadcast-queue.js +1 -0
- package/lib/sync/broadcast-sender.d.ts +52 -0
- package/lib/sync/broadcast-sender.js +1 -0
- package/lib/sync/forwarder.d.ts +97 -0
- package/lib/sync/forwarder.js +1 -0
- package/lib/sync/handlers.d.ts +20 -1
- package/lib/sync/handlers.js +1 -1
- package/lib/sync/hub-tunnel-sync-listener.d.ts +24 -0
- package/lib/sync/hub-tunnel-sync-listener.js +1 -0
- package/lib/sync/hub-tunnel-sync-provider.d.ts +36 -0
- package/lib/sync/hub-tunnel-sync-provider.js +1 -0
- package/lib/sync/merkle-apply.d.ts +41 -0
- package/lib/sync/merkle-apply.js +1 -1
- package/lib/sync/merkle-channel.d.ts +15 -0
- package/lib/sync/merkle-channel.js +1 -1
- package/lib/sync/receive-access-gate.d.ts +36 -0
- package/lib/sync/receive-access-gate.js +1 -0
- package/lib/sync/scope-resolver.d.ts +32 -0
- package/lib/sync/scope-resolver.js +1 -0
- package/lib/sync/sync-manager.d.ts +10 -0
- package/lib/sync/sync-manager.js +1 -1
- package/lib/types.d.ts +151 -1
- package/lib/util/mutex.d.ts +4 -0
- package/lib/util/mutex.js +1 -0
- package/package.json +40 -37
package/lib/hub/relay-manager.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{
|
|
1
|
+
import{EventEmitter as e}from"@enkaku/event";import{HubConnectionPool as t}from"./connection-pool.js";import{GroupChannel as s}from"./group-channel.js";import{HubConnection as r}from"./hub-connection.js";export class HubRelayManager{#e;#t;#s;#r;#o;#a;#i;#n;#l;#c;#u;#h;#p;#g=new Map;#d=new Map;#b=new Map;#f=new Map;relayEvents;constructor(o){this.#e=o.p2pStore,this.#t=o.broadcastService,this.#s=o.deviceID,this.#o=o.logger,this.#a=o.ackFlushMs??500,this.#i=o.ackFlushMax??10,this.#n=o.backoffBaseMs??1e3,this.#l=o.backoffMaxMs??6e4,this.#c=o.backoffJitter??.25,this.#u=o.broadcastTimeoutMs;let a=o.p2pStore;this.#r=new t({createHubClient:o.createHubClient,logger:o.logger,onServerDIDObserved:o.onServerDIDObserved,resolvePinnedDID:async e=>{let t=await a.getHubByURL(e);return t?.server_did??null}}),this.#h=o.createGroupChannel??(e=>new s(e)),this.#p=o.createHubConnection??(e=>new r(e)),this.relayEvents=new e}async #M(e){let t=this.#g.get(e);if(null!=t)return t;let s=this.#p({hubURL:e,deviceID:this.#s,pool:this.#r,logger:this.#o,ackFlushMs:this.#a,ackFlushMax:this.#i,backoffBaseMs:this.#n,backoffMaxMs:this.#l,backoffJitter:this.#c});try{await s.open()}catch(e){try{await s.dispose()}catch{}throw e}let r=s.events.on("lifecycle",async t=>{let s=Object.freeze([...this.#b.entries()].filter(([,t])=>t===e).map(([e])=>e));await this.relayEvents.emit("relay",{...t,hubURL:e,groupIDs:s})});return this.#f.set(e,r),this.#g.set(e,s),s}async start(e){(await Promise.allSettled(e.map(e=>this.addGroup(e)))).forEach((t,s)=>{"rejected"===t.status&&this.#o?.error("addGroup failed during start",{groupID:e[s],error:t.reason})})}async addGroup(e){if(this.#d.has(e))return void this.#o?.debug("group already added",{groupID:e});if(null==await this.#e.getGroup(e))throw Error(`Group ${e} not found`);let t=await this.#e.listHubsByGroupID(e);0===t.length?this.#o?.warn("group has no hub bindings, skipping",{groupID:e}):(t.length>1&&this.#o?.warn("multi-hub not yet supported, using first",{groupID:e,hubURLs:t.map(e=>e.url)}),await this.#m(e,t[0].url))}async #m(e,t){let s=await this.#M(t),r=this.#h({groupID:e,deviceID:this.#s,hubConnection:s,broadcastService:this.#t,p2pStore:this.#e,logger:this.#o});await r.open(),this.#d.set(e,r),this.#b.set(e,t)}async addBinding(e,t){let s=this.#b.get(e);null!=s?s===t?this.#o?.debug("binding already active",{groupID:e,hubURL:t}):this.#o?.warn("multi-hub not yet supported, ignoring extra binding",{groupID:e,activeHubURL:s,newHubURL:t}):await this.#m(e,t)}async removeBinding(e,t){this.#b.get(e)!==t?this.#o?.debug("binding not active, ignoring removal",{groupID:e,hubURL:t}):await this.removeGroup(e)}async removeGroup(e){let t=this.#d.get(e);null!=t&&(await t.close(),this.#d.delete(e),this.#b.delete(e))}async broadcast(e,t,s){let r=this.#d.get(e);if(null==r)throw Error(`Group ${e} is not active in HubRelayManager`);let o=s?.timeoutMs??this.#u;await r.broadcast(t,{timeoutMs:o})}async sendCommit(e,t,s){let r=this.#d.get(e);if(null==r)throw Error(`Group ${e} is not active in HubRelayManager`);let o=s?.timeoutMs??this.#u;await r.sendCommit(t,{timeoutMs:o})}async stop(){for(let e of this.#f.values())e();this.#f.clear();let e=[...this.#d.values()];this.#d.clear(),this.#b.clear(),(await Promise.allSettled(e.map(e=>e.close()))).forEach(e=>{"rejected"===e.status&&this.#o?.warn("GroupChannel close failed",{error:e.reason})});let t=[...this.#g.values()];this.#g.clear(),(await Promise.allSettled(t.map(e=>e.close()))).forEach(e=>{"rejected"===e.status&&this.#o?.warn("HubConnection close failed",{error:e.reason})}),await this.#r.disposeAll()}}
|
|
@@ -1,28 +1,40 @@
|
|
|
1
|
-
import type { Client } from '@enkaku/client';
|
|
2
|
-
import type { HubProtocol } from '@enkaku/hub-protocol';
|
|
3
1
|
import type { Logger } from '@kubun/logger';
|
|
4
|
-
import type { P2PStoreAPI } from '@kubun/store-p2p';
|
|
5
2
|
import type { GroupBroadcastMessage } from '../groups/broadcast.js';
|
|
6
3
|
import type { BroadcastService } from '../groups/broadcast-service.js';
|
|
7
4
|
export type HandleSendBroadcastParams = {
|
|
8
5
|
groupID: string;
|
|
9
|
-
deviceID: string;
|
|
10
6
|
message: GroupBroadcastMessage;
|
|
11
|
-
p2pStore: P2PStoreAPI;
|
|
12
7
|
broadcastService: BroadcastService;
|
|
13
|
-
|
|
8
|
+
/**
|
|
9
|
+
* Wire-send callback. Receives the groupID and the base64-encoded encrypted
|
|
10
|
+
* MLS application message, and is expected to fan it out via the underlying
|
|
11
|
+
* hub transport (e.g. `HubConnection.broadcast`). Decoupling from the hub
|
|
12
|
+
* client lets callers serialize the send through a per-group mutex shared
|
|
13
|
+
* with the receive path.
|
|
14
|
+
*/
|
|
15
|
+
send: (groupID: string, payloadB64: string) => Promise<void>;
|
|
14
16
|
logger?: Logger;
|
|
15
17
|
};
|
|
16
18
|
/**
|
|
17
|
-
* Encrypt + send a broadcast for a group
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
* - `prepareSend` failure → throw, no save (epoch unchanged on disk).
|
|
22
|
-
* - `hub/group/send` failure → throw, do NOT save advanced MLS state.
|
|
23
|
-
* Prevents an MLS epoch wedge where local state outpaces recipients.
|
|
24
|
-
* - `saveMLSState` failure → throw. State has advanced in memory; on next call
|
|
25
|
-
* the load-from-disk path will re-derive a fresh handle from the prior epoch
|
|
26
|
-
* and re-encrypt. (Wedge protection only applies pre-save by design.)
|
|
19
|
+
* Encrypt + send a broadcast for a group. MLS state persistence is handled
|
|
20
|
+
* by the registry inside `BroadcastService.prepareSend`. Wedge protection
|
|
21
|
+
* (failed send → no advance of persisted state) is preserved because send
|
|
22
|
+
* runs INSIDE the registry's per-group lock and a throw skips persist.
|
|
27
23
|
*/
|
|
28
24
|
export declare function handleSendBroadcast(params: HandleSendBroadcastParams): Promise<void>;
|
|
25
|
+
export type HandleSendCommitParams = {
|
|
26
|
+
groupID: string;
|
|
27
|
+
/** Framed MLSMessage(Commit) bytes from the committer's `GroupManager` call. */
|
|
28
|
+
commitBytes: Uint8Array;
|
|
29
|
+
broadcastService: BroadcastService;
|
|
30
|
+
/** Same wire-send callback as {@link HandleSendBroadcastParams.send}. */
|
|
31
|
+
send: (groupID: string, payloadB64: string) => Promise<void>;
|
|
32
|
+
logger?: Logger;
|
|
33
|
+
};
|
|
34
|
+
/**
|
|
35
|
+
* Fan out a pre-produced MLS handshake Commit to a group's existing members.
|
|
36
|
+
* Unlike {@link handleSendBroadcast} this does not encrypt — the Commit is
|
|
37
|
+
* already a self-protected `PrivateMessage`; `prepareSendCommit` just tags it
|
|
38
|
+
* `mls` on the wire.
|
|
39
|
+
*/
|
|
40
|
+
export declare function handleSendCommit(params: HandleSendCommitParams): Promise<void>;
|
package/lib/hub/send-handler.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{toB64 as
|
|
1
|
+
import{toB64 as r}from"@enkaku/codec";export async function handleSendBroadcast(e){let{groupID:o,message:t,broadcastService:a,send:n,logger:d}=e;try{await a.prepareSend({groupID:o,message:t,send:e=>n(o,r(e))})}catch(r){throw d?.error("hub/group/send failed",{groupID:o,error:r}),r}}export async function handleSendCommit(e){let{groupID:o,commitBytes:t,broadcastService:a,send:n,logger:d}=e;try{await a.prepareSendCommit({groupID:o,commitBytes:t,send:e=>n(o,r(e))})}catch(r){throw d?.error("hub/group/send (mls commit) failed",{groupID:o,error:r}),r}}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { StoredMessage } from '@enkaku/hub-protocol';
|
|
2
|
+
/**
|
|
3
|
+
* FIFO queue used by `HubConnection.tunnelMessageStream` to deliver synthetic /
|
|
4
|
+
* forwarded `StoredMessage` values to tunnel transports. Callers `push` messages;
|
|
5
|
+
* each consumer drains via an `AsyncIterator` returned from
|
|
6
|
+
* {@link TunnelInbox.iterator}. Disposal is idempotent: once `close` runs,
|
|
7
|
+
* subsequent pushes are dropped and pending iteration resolves to `done`.
|
|
8
|
+
*
|
|
9
|
+
* Multiple sequential iterators are supported (one consumer at a time, but
|
|
10
|
+
* iterators may be created and returned across the inbox's lifetime). Each
|
|
11
|
+
* iterator tracks its own pending waiter so `iterator.return()` drains only
|
|
12
|
+
* that iterator's waiter — leaving queued messages and other consumers'
|
|
13
|
+
* waiters intact for future iterators.
|
|
14
|
+
*/
|
|
15
|
+
export declare class TunnelInbox {
|
|
16
|
+
#private;
|
|
17
|
+
push(message: StoredMessage): void;
|
|
18
|
+
close(): void;
|
|
19
|
+
iterator(onReturn?: () => void): AsyncIterator<StoredMessage>;
|
|
20
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export class TunnelInbox{#e=[];#s=[];#t=!1;push(e){if(this.#t)return;let s=this.#s.shift();null!=s?s.resolve({value:e,done:!1}):this.#e.push(e)}close(){if(!this.#t)for(this.#t=!0;this.#s.length>0;){let e=this.#s.shift();e?.resolve({value:void 0,done:!0})}}iterator(e){let s=!1,t=new Set,i=()=>{if(!s){for(let e of(s=!0,t)){let s=this.#s.indexOf(e);s>=0&&this.#s.splice(s,1),e.resolve({value:void 0,done:!0})}t.clear(),e?.()}};return{next:()=>{let e;return s?Promise.resolve({value:void 0,done:!0}):this.#e.length>0?Promise.resolve({value:this.#e.shift(),done:!1}):this.#t?(i(),Promise.resolve({value:void 0,done:!0})):new Promise(s=>{e={resolve:s},t.add(e),this.#s.push(e)}).then(s=>(null!=e&&t.delete(e),!0===s.done&&i(),s))},return:()=>(i(),Promise.resolve({value:void 0,done:!0}))}}}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wait for a reconnect gate (or similar boolean-style gate) to clear, with an
|
|
3
|
+
* optional timeout. If `gate` is null the call returns immediately without
|
|
4
|
+
* allocating any timer or signal — this is the hot path while the connection
|
|
5
|
+
* is healthy. If `timeoutMs` is undefined we simply await the gate without
|
|
6
|
+
* arming a timer (callers with no timeout can also omit `onTimeout` to skip
|
|
7
|
+
* the closure allocation). Both set: race the gate against
|
|
8
|
+
* `AbortSignal.timeout`; the first to settle wins.
|
|
9
|
+
*
|
|
10
|
+
* `onTimeout` is invoked lazily — only when the timeout actually fires — so
|
|
11
|
+
* callers can capture mutable state (e.g. current reconnect attempt) at the
|
|
12
|
+
* moment of failure rather than at call time.
|
|
13
|
+
*/
|
|
14
|
+
export declare function waitForGate(gate: Promise<void> | null, timeoutMs: number | undefined, onTimeout?: () => Error): Promise<void>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export async function waitForGate(e,t,r){if(null==e)return;if(null==t)return void await e;if(null==r)throw Error("waitForGate: onTimeout required when timeoutMs is set");let i=AbortSignal.timeout(t),n=null,o=new Promise((e,t)=>{i.aborted?t(r()):(n=()=>{t(r())},i.addEventListener("abort",n,{once:!0}))});try{await Promise.race([e,o])}finally{null!=n&&i.removeEventListener("abort",n)}}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import type { OwnIdentity } from '@enkaku/token';
|
|
2
|
+
import type { KubunDB } from '@kubun/db';
|
|
3
|
+
import type { DefaultAccessLevel, GraphInternals } from '@kubun/engine';
|
|
4
|
+
import type { Logger } from '@kubun/logger';
|
|
5
|
+
import type { GroupBroadcastMessage } from '../groups/broadcast.js';
|
|
6
|
+
import { type BroadcastEvent } from '../groups/broadcast-service.js';
|
|
7
|
+
import type { P2PEventEmitter } from '../groups/events.js';
|
|
8
|
+
import type { GroupHandleRegistry } from '../groups/group-handle-registry.js';
|
|
9
|
+
import type { ForwardingConfig } from '../sync/forwarder.js';
|
|
10
|
+
import type { StoreUnreadableMode } from '../types.js';
|
|
11
|
+
import type { OnServerDIDObserved } from './connection-pool.js';
|
|
12
|
+
import type { GroupChannel, GroupChannelParams } from './group-channel.js';
|
|
13
|
+
import type { HubConnection, HubConnectionParams } from './hub-connection.js';
|
|
14
|
+
import { type CreateHubClient } from './relay-manager.js';
|
|
15
|
+
export type HubWiring = {
|
|
16
|
+
ready: Promise<void>;
|
|
17
|
+
scheduleBroadcast: (groupID: string, message: GroupBroadcastMessage) => void;
|
|
18
|
+
/**
|
|
19
|
+
* Awaitable broadcast — resolves only after the message is encrypted + sent.
|
|
20
|
+
* Use when ordering matters relative to a subsequent local op (e.g. a
|
|
21
|
+
* leave-request that must be encrypted with the still-live MLS handle before
|
|
22
|
+
* `leaveGroup` tears it down). Rejects on send failure.
|
|
23
|
+
*/
|
|
24
|
+
broadcastNow: (groupID: string, message: GroupBroadcastMessage) => Promise<void>;
|
|
25
|
+
/**
|
|
26
|
+
* Fire-and-forget fan-out of a pre-produced MLS handshake Commit to a
|
|
27
|
+
* group's existing members. Wired to `HubRelayManager.sendCommit`.
|
|
28
|
+
*/
|
|
29
|
+
scheduleSendCommit: (groupID: string, commitBytes: Uint8Array) => void;
|
|
30
|
+
dispose: () => Promise<void>;
|
|
31
|
+
};
|
|
32
|
+
export type SetupHubRelayParams = {
|
|
33
|
+
identity: OwnIdentity;
|
|
34
|
+
db: KubunDB;
|
|
35
|
+
/**
|
|
36
|
+
* Engine graph internals — threaded through to `BroadcastService` so the
|
|
37
|
+
* `mutation:apply` broadcast variant can route peer-authored mutations
|
|
38
|
+
* through the engine with `origin: 'peer'`.
|
|
39
|
+
*/
|
|
40
|
+
graph: GraphInternals;
|
|
41
|
+
emitter: P2PEventEmitter;
|
|
42
|
+
createHubClient: CreateHubClient;
|
|
43
|
+
onRemoteBroadcast: (event: BroadcastEvent) => void;
|
|
44
|
+
/**
|
|
45
|
+
* Single canonical access point for MLS `GroupHandle` instances. Threaded
|
|
46
|
+
* into `BroadcastService` so encrypt/decrypt go through the per-group
|
|
47
|
+
* mutex once Phase 3 lands. Currently stored but unused.
|
|
48
|
+
*/
|
|
49
|
+
registry: GroupHandleRegistry;
|
|
50
|
+
logger: Logger;
|
|
51
|
+
/**
|
|
52
|
+
* Optional testing seam forwarded to `HubRelayManager`. Production code
|
|
53
|
+
* leaves this unset so the manager uses its default `GroupChannel`
|
|
54
|
+
* constructor.
|
|
55
|
+
*/
|
|
56
|
+
createGroupChannel?: (params: GroupChannelParams) => GroupChannel;
|
|
57
|
+
/**
|
|
58
|
+
* Optional testing seam forwarded to `HubRelayManager`. Production code
|
|
59
|
+
* leaves this unset so the manager uses its default `HubConnection`
|
|
60
|
+
* constructor.
|
|
61
|
+
*/
|
|
62
|
+
createHubConnection?: (params: HubConnectionParams) => HubConnection;
|
|
63
|
+
/**
|
|
64
|
+
* Receive-side storage mode. Forwarded to `BroadcastService` →
|
|
65
|
+
* `processBroadcast`. Defaults to `'persist'` when omitted.
|
|
66
|
+
*/
|
|
67
|
+
storeUnreadable?: StoreUnreadableMode;
|
|
68
|
+
/** Server default access level — required when `storeUnreadable === 'drop'`. */
|
|
69
|
+
defaultAccessLevel?: DefaultAccessLevel;
|
|
70
|
+
/**
|
|
71
|
+
* Forwarding config. Threaded through to `BroadcastService` →
|
|
72
|
+
* `processBroadcast` along with the hub's own `scheduleBroadcast` hook.
|
|
73
|
+
* Default `false` disables the receive-time forwarding pipeline.
|
|
74
|
+
*/
|
|
75
|
+
forwarding?: ForwardingConfig;
|
|
76
|
+
/**
|
|
77
|
+
* Forwarded to `HubRelayManager` → `HubConnectionPool`. Fired with the
|
|
78
|
+
* observed `serverDID` after the first signed response from a freshly-spawned
|
|
79
|
+
* client. Lets callers route capture into `captureServerDID` for TOFU
|
|
80
|
+
* pinning; omitting it skips capture entirely.
|
|
81
|
+
*/
|
|
82
|
+
onServerDIDObserved?: OnServerDIDObserved;
|
|
83
|
+
};
|
|
84
|
+
export declare function setupHubRelay(params: SetupHubRelayParams): HubWiring;
|
|
85
|
+
export declare const DISABLED_HUB: HubWiring;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{HLC as e}from"@kubun/hlc";import{BroadcastService as r}from"../groups/broadcast-service.js";import{HubRelayManager as o}from"./relay-manager.js";export function setupHubRelay(a){let t,{identity:d,db:i,graph:n,emitter:s,createHubClient:c,onRemoteBroadcast:u,registry:p,logger:h,createGroupChannel:l,createHubConnection:m,storeUnreadable:g,defaultAccessLevel:f,forwarding:b,onServerDIDObserved:y}=a,w=[],D=(e,r)=>{t.then(o=>o.broadcast(e,r).catch(r=>{h.error("broadcast failed",{groupID:e,error:r})}),()=>{})},v=async(e,r)=>{let o=await t;await o.broadcast(e,r)};return(t=(async()=>{let[a,t]=await Promise.all([i.getStore("p2p"),i.getStore("graph")]),v=new r({p2pStore:a,graphStore:t,graph:n,selfDID:d.id,registry:p,hlc:new e({nodeID:d.id}),storeUnreadable:g,defaultAccessLevel:f,forwarding:b,scheduleBroadcast:D,emitter:s,logger:h}),B=new o({p2pStore:a,broadcastService:v,deviceID:d.id,createHubClient:c,logger:h,createGroupChannel:l,createHubConnection:m,onServerDIDObserved:y});w.push(v.on("broadcast",u)),w.push(s.on("groupJoined",e=>B.addGroup(e.id).catch(r=>{h.error("addGroup failed",{groupID:e.id,error:r})}))),w.push(s.on("groupLeft",e=>B.removeGroup(e.groupID).catch(r=>{h.error("removeGroup failed",{groupID:e.groupID,error:r})}))),w.push(s.on("hubBound",({groupID:e,hubURL:r})=>B.addBinding(e,r).catch(o=>{h.error("addBinding failed",{groupID:e,hubURL:r,error:o})}))),w.push(s.on("hubUnbound",({groupID:e,hubURL:r})=>B.removeBinding(e,r).catch(o=>{h.error("removeBinding failed",{groupID:e,hubURL:r,error:o})})));let I=await a.listGroups();return await B.start(I.map(e=>e.id)),B})()).catch(e=>{h.error("hub relay setup failed",{error:e})}),{ready:t.then(()=>void 0),scheduleBroadcast:D,broadcastNow:v,scheduleSendCommit:(e,r)=>{t.then(o=>o.sendCommit(e,r).catch(r=>{h.error("sendCommit failed",{groupID:e,error:r})}),()=>{})},dispose:async()=>{for(let e of w)try{e()}catch{}w.length=0;try{let e=await t;await e.stop()}catch{}}}}export const DISABLED_HUB={ready:Promise.resolve(),scheduleBroadcast:()=>{},broadcastNow:async()=>{},scheduleSendCommit:()=>{},dispose:async()=>{}};
|
package/lib/index.d.ts
CHANGED
|
@@ -1,26 +1,121 @@
|
|
|
1
|
-
import type { KubunPlugin, PluginFactoryParams } from '@kubun/engine';
|
|
1
|
+
import type { DefaultAccessLevel, KubunPlugin, PluginFactoryParams } from '@kubun/engine';
|
|
2
|
+
import type { CreateHubClient } from './hub/relay-manager.js';
|
|
3
|
+
import { type BroadcastBatchConfig } from './sync/broadcast-queue.js';
|
|
4
|
+
import { type PushSyncConfig } from './sync/broadcast-sender.js';
|
|
5
|
+
import type { ForwardingConfig } from './sync/forwarder.js';
|
|
6
|
+
import type { StoreUnreadableMode } from './types.js';
|
|
2
7
|
export type { ContextDeps, PendingJoinRequest } from './context/types.js';
|
|
3
|
-
export { type GroupBroadcastMessage, type ProcessBroadcastParams, processBroadcast, } from './groups/broadcast.js';
|
|
8
|
+
export { type AffectedRow, type GroupBroadcastMessage, type MutationApplyEntry, type ProcessBroadcastParams, type ProcessBroadcastResult, processBroadcast, } from './groups/broadcast.js';
|
|
4
9
|
export { deserializeBroadcast, serializeBroadcast } from './groups/broadcast-codec.js';
|
|
5
|
-
export { ApplyError, type BroadcastEvent, BroadcastService, type BroadcastServiceEvents, type BroadcastServiceParams, DecryptError, type ReceiveBroadcastParams, type ReceiveBroadcastResult, type SendBroadcastParams,
|
|
6
|
-
export { createFilteredGenerator,
|
|
7
|
-
export {
|
|
10
|
+
export { ApplyError, type BroadcastEvent, BroadcastService, type BroadcastServiceEvents, type BroadcastServiceParams, DecryptError, type ReceiveBroadcastParams, type ReceiveBroadcastResult, type SendBroadcastParams, } from './groups/broadcast-service.js';
|
|
11
|
+
export { createFilteredGenerator, createP2PEventEmitter, type P2PEventEmitter, type P2PEventMap, } from './groups/events.js';
|
|
12
|
+
export { GroupHandleRegistry, type GroupHandleRegistryParams, type SeedParams, } from './groups/group-handle-registry.js';
|
|
13
|
+
export { decodeFullJoinRequest, decodeInvitePayload, decodeJoinRequest, encodeFullJoinRequest, encodeInvitePayload, encodeJoinRequest, type FullJoinRequestPayload, type InvitePayload, type JoinRequestPayload, type SuggestedHub, } from './groups/invite-payload.js';
|
|
8
14
|
export { type AddCircleMemberParams, type AddCircleMemberResult, type CreateCircleParams, type CreateCircleResult, type CreateGroupParams, type CreateGroupResult, type DeleteCircleParams, type DeleteCircleResult, GroupManager, type GroupManagerParams, type InviteToGroupParams, type InviteToGroupResult, type JoinGroupParams, type JoinGroupResult, type LeaveGroupParams, type LeaveGroupResult, type RemoveCircleMemberParams, type RemoveCircleMemberResult, type RemoveGroupMemberParams, type RemoveGroupMemberResult, type RemoveMemberParams, type RemoveMemberResult, type UpdateCircleParams, type UpdateCircleResult, type UpdateGroupParams, type UpdateGroupResult, } from './groups/manager.js';
|
|
15
|
+
export { MLSEncryptor, type MLSEncryptorParams } from './groups/mls-encryptor.js';
|
|
9
16
|
export { type MLSGroupHandle, restoreMLSGroupHandle } from './groups/mls-group-handle.js';
|
|
10
17
|
export { replacer as mlsJSONReplacer, reviver as mlsJSONReviver } from './groups/mls-json.js';
|
|
11
18
|
export { deserializeMLSGroupState, type MLSGroupState, type SerializedMLSGroupState, serializeMLSGroupState, } from './groups/mls-state.js';
|
|
12
19
|
export type { SyncClientMessage, SyncProtocol, SyncServerMessage } from './protocol.js';
|
|
13
20
|
export { createP2PSchemaExtension } from './schema.js';
|
|
21
|
+
export { type BroadcastBatchConfig, type BroadcastQueue, type BroadcastQueueParams, createBroadcastQueue, DEFAULT_BROADCAST_BATCH_CONFIG, } from './sync/broadcast-queue.js';
|
|
22
|
+
export { type BroadcastSenderParams, DEFAULT_PUSH_SYNC_CONFIG, type PushSyncConfig, wireBroadcastSender, } from './sync/broadcast-sender.js';
|
|
14
23
|
export { type CatalogSyncScope, resolveCatalogSyncScopes } from './sync/catalog-scope.js';
|
|
24
|
+
export { type EvaluateAndForwardParams, type EvaluateAndForwardResult, evaluateAndForward, type ForwardContext, type ForwardFilter, type ForwardingConfig, } from './sync/forwarder.js';
|
|
15
25
|
export { checkSyncDelegation, negotiateDirection } from './sync/handlers.js';
|
|
26
|
+
export { HubTunnelSyncListener, type HubTunnelSyncListenerParams, } from './sync/hub-tunnel-sync-listener.js';
|
|
27
|
+
export { HubTunnelSyncProvider, type HubTunnelSyncProviderParams, } from './sync/hub-tunnel-sync-provider.js';
|
|
16
28
|
export { type PeerConfig, type PeerConfigWithID, PeerRegistry } from './sync/peer-registry.js';
|
|
17
29
|
export { SyncClient, type SyncClientParams, type SyncScope, type SyncTransportProvider, } from './sync/sync-client.js';
|
|
18
30
|
export { type SyncEvent, SyncManager, type SyncManagerParams, type SyncSessionInfo, type SyncStatus, } from './sync/sync-manager.js';
|
|
31
|
+
export type { OnServerDIDObserved } from './hub/connection-pool.js';
|
|
32
|
+
export { DIDObservingTransport, type DIDObservingTransportParams, type ServerDIDObserver, } from './hub/did-observing-transport.js';
|
|
33
|
+
export { ReconnectingError } from './hub/errors.js';
|
|
19
34
|
export type { GroupChannel, GroupChannelParams } from './hub/group-channel.js';
|
|
20
|
-
export { type
|
|
21
|
-
export type {
|
|
35
|
+
export { type CreateHTTPHubClientParams, createHTTPHubClient } from './hub/http-client.js';
|
|
36
|
+
export type { ConnectionLifecycleEvent } from './hub/hub-connection.js';
|
|
37
|
+
export { type BindHubToGroupParams, bindHubToGroup, type CaptureServerDIDParams, type CreateHubParams, captureServerDID, createHub, type DeleteHubParams, type DeleteHubResult, deleteHub, type GetHubParams, getHub, type Hub, HubServerDIDConflictError, type ListGroupsByHubParams, type ListHubsByGroupParams, type ListHubsParams, listGroupsByHub, listHubs, listHubsByGroup, type UnbindHubFromGroupParams, type UpdateHubParams, type UpsertHubParams, unbindHubFromGroup, updateHub, upsertHub, } from './hub/manager.js';
|
|
38
|
+
export { type CreateGroupChannel, type CreateHubClient, type CreateHubClientOptions, type CreateHubConnection, HubRelayManager, type HubRelayManagerParams, type RelayEvent, } from './hub/relay-manager.js';
|
|
39
|
+
export { DISABLED_HUB, type HubWiring, type SetupHubRelayParams, setupHubRelay, } from './hub/wiring.js';
|
|
40
|
+
export type { CatalogData, CircleData, CircleMemberData, DeleteCatalogData, DeleteCircleData, GroupData, GroupMemberData, GroupRequestContext, InviteToGroupData, JoinGroupData, LeaveGroupData, P2PContext, P2PJoinRequestContext, RemoveCircleMemberData, RemoveGroupMemberData, StoreUnreadableMode, SyncPluginAPI, SyncRequestContext, SyncSessionResult, UpdateCircleInput, UpdateGroupInput, } from './types.js';
|
|
41
|
+
export type ReceiveConfig = {
|
|
42
|
+
/**
|
|
43
|
+
* Receiver storage mode for incoming peer mutations.
|
|
44
|
+
* - `'persist'` (default) — store every doc that passes catalog/group
|
|
45
|
+
* checks. Access enforced at query time; unreadable docs remain in DB
|
|
46
|
+
* but filtered.
|
|
47
|
+
* - `'drop'` — apply read-access check at receive time using the local
|
|
48
|
+
* peer's own DID as viewer. Mutations the local peer cannot read are
|
|
49
|
+
* not persisted.
|
|
50
|
+
*/
|
|
51
|
+
storeUnreadable: StoreUnreadableMode;
|
|
52
|
+
};
|
|
53
|
+
/**
|
|
54
|
+
* Default receive config. Under `'persist'` no gate is built and no extra
|
|
55
|
+
* DB lookups occur during receive.
|
|
56
|
+
*/
|
|
57
|
+
export declare const DEFAULT_RECEIVE_CONFIG: ReceiveConfig;
|
|
22
58
|
export type P2PPluginOptions = {
|
|
23
59
|
autoAcceptPeers?: Array<string>;
|
|
24
60
|
http?: boolean | string;
|
|
61
|
+
/**
|
|
62
|
+
* Enable hub-mediated group broadcasts.
|
|
63
|
+
* - `true` / `{}` — build a default HTTP hub client factory from
|
|
64
|
+
* `params.identity` + `params.runtime.fetch`.
|
|
65
|
+
* - `{ createHubClient }` — caller supplies a custom factory.
|
|
66
|
+
* - absent / `false` — hub relay disabled; all broadcast call-sites are
|
|
67
|
+
* no-ops.
|
|
68
|
+
*/
|
|
69
|
+
hub?: boolean | {
|
|
70
|
+
createHubClient?: CreateHubClient;
|
|
71
|
+
};
|
|
72
|
+
/**
|
|
73
|
+
* Batch broadcast queue config. Sender-side mutation:apply broadcasts are
|
|
74
|
+
* accumulated per target MLS group and flushed when window/count/byte
|
|
75
|
+
* thresholds trip. Defaults from {@link DEFAULT_BROADCAST_BATCH_CONFIG}.
|
|
76
|
+
* Set `enabled: false` to bypass the queue and emit one broadcast per
|
|
77
|
+
* mutation per scope.
|
|
78
|
+
*/
|
|
79
|
+
batch?: Partial<BroadcastBatchConfig>;
|
|
80
|
+
/**
|
|
81
|
+
* Push-sync config. Gates the local `engine:mutation:authored` subscriber
|
|
82
|
+
* that drives `mutation:apply` broadcasts.
|
|
83
|
+
* - When `enabled: false` (default), no `mutation:apply` broadcasts are
|
|
84
|
+
* emitted from this peer when local mutations land — even if `hub` is
|
|
85
|
+
* configured. Merkle-pull continues to work, and the receive path
|
|
86
|
+
* (incoming `mutation:apply` broadcasts) is unaffected.
|
|
87
|
+
* - When `enabled: true`, locally-authored mutations fan out per-scope
|
|
88
|
+
* through the hub.
|
|
89
|
+
*
|
|
90
|
+
* Defaults from {@link DEFAULT_PUSH_SYNC_CONFIG}.
|
|
91
|
+
*/
|
|
92
|
+
pushSync?: Partial<PushSyncConfig>;
|
|
93
|
+
/**
|
|
94
|
+
* Receive-side storage config. Controls whether peer mutations the local
|
|
95
|
+
* peer cannot read are persisted to the local DB or dropped at receive
|
|
96
|
+
* time. Defaults to {@link DEFAULT_RECEIVE_CONFIG} (persist).
|
|
97
|
+
*/
|
|
98
|
+
receive?: Partial<ReceiveConfig>;
|
|
99
|
+
/**
|
|
100
|
+
* Server default access level used by the receive-time access gate when
|
|
101
|
+
* `receive.storeUnreadable === 'drop'` and no document or per-user-model
|
|
102
|
+
* default rule is declared. Defaults to `{ read: 'only_owner', write:
|
|
103
|
+
* 'only_owner' }` (no fan-out for undeclared models). Ignored under
|
|
104
|
+
* `'persist'`.
|
|
105
|
+
*/
|
|
106
|
+
defaultAccessLevel?: DefaultAccessLevel;
|
|
107
|
+
/**
|
|
108
|
+
* Forwarding config. Controls per-peer N+1 re-broadcast.
|
|
109
|
+
* - `false` (default) — incoming `mutation:apply` broadcasts are NOT
|
|
110
|
+
* re-broadcast.
|
|
111
|
+
* - `true` — re-broadcast each applied entry to every other MLS group the
|
|
112
|
+
* local peer belongs to whose access + catalog gates pass.
|
|
113
|
+
* - {@link ForwardFilter} — same as `true`, plus per-candidate filter
|
|
114
|
+
* callback that further narrows. Filter receives a {@link ForwardContext}.
|
|
115
|
+
*
|
|
116
|
+
* Push-sync config (`pushSync.enabled`) is orthogonal — forwarding fires
|
|
117
|
+
* regardless of whether locally-authored mutations are broadcast.
|
|
118
|
+
*/
|
|
119
|
+
forwarding?: ForwardingConfig;
|
|
25
120
|
};
|
|
26
121
|
export declare function createP2PPlugin(options?: P2PPluginOptions): (params: PluginFactoryParams) => KubunPlugin;
|
package/lib/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{ServerTransport as e}from"@enkaku/http-server-transport";import{Server as r}from"@enkaku/server";import{
|
|
1
|
+
import{ServerTransport as e}from"@enkaku/http-server-transport";import{Server as r}from"@enkaku/server";import{isFullIdentity as t}from"@enkaku/token";import{DirectTransports as o}from"@enkaku/transport";import{HLC as s}from"@kubun/hlc";import{p2pStoreDefinition as n}from"@kubun/store-p2p";import{createDelegationContext as a}from"./context/delegation.js";import{createGroupContext as i}from"./context/group.js";import{createHubContext as c}from"./context/hub.js";import{createJoinContext as p}from"./context/join.js";import{createSyncContext as d}from"./context/sync.js";import{createP2PEventEmitter as u}from"./groups/events.js";import{GroupHandleRegistry as l}from"./groups/group-handle-registry.js";import{GroupManager as g}from"./groups/manager.js";import{forwardRemoteBroadcast as m}from"./hub/forward-remote-broadcast.js";import{createHTTPHubClient as b}from"./hub/http-client.js";import{captureServerDID as y}from"./hub/manager.js";import{DISABLED_HUB as f,setupHubRelay as h}from"./hub/wiring.js";import{createP2PSchemaExtension as j}from"./schema.js";import{createBroadcastQueue as v,DEFAULT_BROADCAST_BATCH_CONFIG as x}from"./sync/broadcast-queue.js";import{DEFAULT_PUSH_SYNC_CONFIG as S,wireBroadcastSender as w}from"./sync/broadcast-sender.js";import{createSyncHandlers as D}from"./sync/handlers.js";import{SyncManager as P}from"./sync/sync-manager.js";export{processBroadcast}from"./groups/broadcast.js";export{deserializeBroadcast,serializeBroadcast}from"./groups/broadcast-codec.js";export{ApplyError,BroadcastService,DecryptError}from"./groups/broadcast-service.js";export{createFilteredGenerator,createP2PEventEmitter}from"./groups/events.js";export{GroupHandleRegistry}from"./groups/group-handle-registry.js";export{decodeFullJoinRequest,decodeInvitePayload,decodeJoinRequest,encodeFullJoinRequest,encodeInvitePayload,encodeJoinRequest}from"./groups/invite-payload.js";export{GroupManager}from"./groups/manager.js";export{MLSEncryptor}from"./groups/mls-encryptor.js";export{restoreMLSGroupHandle}from"./groups/mls-group-handle.js";export{replacer as mlsJSONReplacer,reviver as mlsJSONReviver}from"./groups/mls-json.js";export{deserializeMLSGroupState,serializeMLSGroupState}from"./groups/mls-state.js";export{createP2PSchemaExtension}from"./schema.js";export{createBroadcastQueue,DEFAULT_BROADCAST_BATCH_CONFIG}from"./sync/broadcast-queue.js";export{DEFAULT_PUSH_SYNC_CONFIG,wireBroadcastSender}from"./sync/broadcast-sender.js";export{resolveCatalogSyncScopes}from"./sync/catalog-scope.js";export{evaluateAndForward}from"./sync/forwarder.js";export{checkSyncDelegation,negotiateDirection}from"./sync/handlers.js";export{HubTunnelSyncListener}from"./sync/hub-tunnel-sync-listener.js";export{HubTunnelSyncProvider}from"./sync/hub-tunnel-sync-provider.js";export{PeerRegistry}from"./sync/peer-registry.js";export{SyncClient}from"./sync/sync-client.js";export{SyncManager}from"./sync/sync-manager.js";export{DIDObservingTransport}from"./hub/did-observing-transport.js";export{ReconnectingError}from"./hub/errors.js";export{createHTTPHubClient}from"./hub/http-client.js";export{bindHubToGroup,captureServerDID,createHub,deleteHub,getHub,HubServerDIDConflictError,listGroupsByHub,listHubs,listHubsByGroup,unbindHubFromGroup,updateHub,upsertHub}from"./hub/manager.js";export{HubRelayManager}from"./hub/relay-manager.js";export{DISABLED_HUB,setupHubRelay}from"./hub/wiring.js";export const DEFAULT_RECEIVE_CONFIG={storeUnreadable:"persist"};export function createP2PPlugin(I){return R=>{var E;let H,L,B,C;R.db.register(n);let G=R.db.adapter;if(!(t(E=R.identity)&&"privateKey"in E))throw Error("createP2PPlugin requires an OwnIdentity (full identity with privateKey) — received a narrower Identity");let A=R.identity,F=new s({nodeID:A.id}),k={...DEFAULT_RECEIVE_CONFIG,...I?.receive},T=I?.defaultAccessLevel??{read:"only_owner",write:"only_owner"},U=new l({stores:R.db,deviceID:A.id,logger:R.getLogger("group-handle-registry")}),_=new g({identity:A,getRandomID:R.runtime.getRandomID,registry:U}),O=u(),q=new P({deployClusters:async e=>{let r=Object.values(e);await R.engine.deployGraph({clusters:r})},graph:R.graph,runtime:R.runtime,identity:R.identity,logger:R.getLogger("sync"),storeUnreadable:k.storeUnreadable,defaultAccessLevel:T}),N=D({db:R.db,graph:R.graph,logger:R.getLogger("sync-handlers"),peerRegistry:q.peerRegistry,selfDID:A.id,storeUnreadable:k.storeUnreadable,defaultAccessLevel:T}),M=new Map,J=[],z=R.getLogger("hub-relay"),K=f,V={...S,...I?.pushSync};if(I?.hub){let e="object"==typeof I.hub&&null!=I.hub.createHubClient?I.hub.createHubClient:b({identity:A,fetch:R.runtime.fetch});K=h({identity:A,db:R.db,graph:R.graph,emitter:O,createHubClient:e,registry:U,onRemoteBroadcast:e=>{m(O,e,z).catch(e=>{z.error("forwardRemoteBroadcast failed",{error:e})})},logger:z,storeUnreadable:k.storeUnreadable,defaultAccessLevel:T,forwarding:I.forwarding,onServerDIDObserved:({hubURL:e,serverDID:r})=>y({stores:R.db,hubURL:e,serverDID:r}).then(()=>void 0)}),V.enabled&&(L=v({config:{...x,...I.batch},scheduleBroadcast:K.scheduleBroadcast,logger:R.getLogger("broadcast-queue")}),H=w({db:R.db,eventBus:R.eventBus,selfDID:A.id,queue:L,logger:R.getLogger("broadcast-sender")}))}let W={hubReady:K.ready,addPeer:e=>q.addPeer({config:e,stores:R.db}),removePeer:e=>q.removePeer({peerDID:e,stores:R.db}),listPeers:()=>q.listPeers({stores:R.db}),syncWithPeer:e=>q.merkleSyncWithPeer({...e,stores:R.db}),getStatus:e=>q.getStatus({peerDID:e,stores:R.db}),onSyncEvent:e=>q.onSyncEvent(e),createSyncTransport:function(e){let t=new o({signal:e}),s=new r({getRandomID:R.runtime.getRandomID,handlers:N,logger:R.getLogger("sync-server"),transports:[t.server],signal:e});return J.push(s),t.client}};if(I?.http){let t="string"==typeof I.http?I.http.replace(/^\//,""):"sync";W.syncReady=(async()=>{let o=await R.engine.getAPI("http");await o.listening,B=new e({allowedOrigin:["*"]}),C=new r({getRandomID:R.runtime.getRandomID,handlers:N,logger:R.getLogger("sync-http-server"),transports:[B]}),J.push(C),o.registerProtocol(t,B.fetch.bind(B))})()}return{name:"p2p",schemaExtension:e=>j(O),api:W,createContextFactory:()=>(e,r)=>{let t={identity:A,groupManager:_,registry:U,syncManager:q,stores:r,adapter:G,hlc:F,emitter:O,pendingJoinRequests:M,runtime:R.runtime,autoAcceptPeers:I?.autoAcceptPeers,scheduleBroadcast:K.scheduleBroadcast,broadcastNow:K.broadcastNow,scheduleSendCommit:K.scheduleSendCommit,logger:R.getLogger("context")};return{group:i(e,t),sync:d(e,t),join:p(e,t),hub:c(e,t),delegation:a(e,t)}},dispose:async()=>{if(null!=H)try{H()}catch{}if(null!=L)try{L.dispose()}catch{}let e=(async()=>{for(let e of J)await e.dispose();null!=B&&await B.dispose()})(),r=R.getLogger("dispose"),t=await Promise.allSettled([e,q.dispose(),K.dispose()]),o=["sync-http","sync-manager","hub"];t.forEach((e,t)=>{"rejected"===e.status&&r.error("dispose branch failed",{branch:o[t],error:e.reason})})}}}}
|
package/lib/schema.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import type { SchemaExtension } from '@kubun/engine';
|
|
2
|
-
import { type
|
|
3
|
-
export declare function createP2PSchemaExtension(emitter:
|
|
2
|
+
import { type P2PEventEmitter } from './groups/events.js';
|
|
3
|
+
export declare function createP2PSchemaExtension(emitter: P2PEventEmitter): SchemaExtension;
|
package/lib/schema.js
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
|
-
import{createFilteredGenerator as e}from"./groups/events.js";function r(e){if(null==e.p2p)throw Error("p2p plugin is not wired into this context");return e.p2p}let
|
|
1
|
+
import{createFilteredGenerator as e}from"./groups/events.js";function r(e){if(null==e.p2p)throw Error("p2p plugin is not wired into this context");return e.p2p}async function*t(){}let o=`
|
|
2
2
|
type PeerGroup {
|
|
3
3
|
id: ID!
|
|
4
4
|
name: String!
|
|
5
5
|
description: String
|
|
6
|
-
createdBy:
|
|
7
|
-
createdAt:
|
|
6
|
+
createdBy: DID!
|
|
7
|
+
createdAt: DateTimeISO!
|
|
8
8
|
}
|
|
9
9
|
|
|
10
10
|
type PeerGroupMember {
|
|
11
11
|
groupID: ID!
|
|
12
|
-
memberDID:
|
|
12
|
+
memberDID: DID!
|
|
13
13
|
role: String!
|
|
14
|
-
createdAt:
|
|
14
|
+
createdAt: DateTimeISO!
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
type PeerCircle {
|
|
@@ -19,28 +19,43 @@ type PeerCircle {
|
|
|
19
19
|
groupID: ID!
|
|
20
20
|
name: String!
|
|
21
21
|
description: String
|
|
22
|
-
createdAt:
|
|
22
|
+
createdAt: DateTimeISO!
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
type PeerCircleMember {
|
|
26
26
|
circleID: ID!
|
|
27
|
-
memberDID:
|
|
27
|
+
memberDID: DID!
|
|
28
28
|
role: String!
|
|
29
|
-
createdAt:
|
|
29
|
+
createdAt: DateTimeISO!
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
type PeerCatalog {
|
|
33
33
|
id: ID!
|
|
34
|
-
ownerDID:
|
|
34
|
+
ownerDID: DID!
|
|
35
35
|
name: String!
|
|
36
36
|
description: String
|
|
37
37
|
filterCriteria: String!
|
|
38
|
-
createdAt:
|
|
38
|
+
createdAt: DateTimeISO!
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
type PeerHub {
|
|
42
|
+
id: ID!
|
|
43
|
+
label: String
|
|
44
|
+
url: URL!
|
|
45
|
+
serverDID: DID
|
|
46
|
+
createdAt: DateTimeISO!
|
|
47
|
+
updatedAt: DateTimeISO
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
input SuggestedHubInput {
|
|
51
|
+
url: URL!
|
|
52
|
+
serverDID: DID
|
|
39
53
|
}
|
|
40
54
|
|
|
41
55
|
input CreateGroupInput {
|
|
42
56
|
name: String!
|
|
43
57
|
description: String
|
|
58
|
+
hubs: [SuggestedHubInput!]
|
|
44
59
|
}
|
|
45
60
|
|
|
46
61
|
type CreateGroupPayload {
|
|
@@ -110,13 +125,13 @@ type DeleteCirclePayload {
|
|
|
110
125
|
|
|
111
126
|
type RemoveCircleMemberPayload {
|
|
112
127
|
circleID: ID!
|
|
113
|
-
memberDID:
|
|
128
|
+
memberDID: DID!
|
|
114
129
|
}
|
|
115
130
|
|
|
116
131
|
type SyncPeer {
|
|
117
132
|
id: String!
|
|
118
|
-
peerDID:
|
|
119
|
-
endpoint:
|
|
133
|
+
peerDID: DID!
|
|
134
|
+
endpoint: URL!
|
|
120
135
|
mode: String!
|
|
121
136
|
priority: String!
|
|
122
137
|
trustLevel: String!
|
|
@@ -140,35 +155,110 @@ type RemoveSyncPeerPayload {
|
|
|
140
155
|
success: Boolean!
|
|
141
156
|
}
|
|
142
157
|
|
|
158
|
+
input CreateHubInput {
|
|
159
|
+
url: URL!
|
|
160
|
+
label: String
|
|
161
|
+
serverDID: DID
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
input UpdateHubInput {
|
|
165
|
+
id: ID!
|
|
166
|
+
url: URL
|
|
167
|
+
label: String
|
|
168
|
+
serverDID: DID
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
type DeleteHubPayload {
|
|
172
|
+
success: Boolean!
|
|
173
|
+
boundGroupIDs: [ID!]!
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
type DelegationToken {
|
|
177
|
+
jwt: String!
|
|
178
|
+
jti: String!
|
|
179
|
+
grantor: ID!
|
|
180
|
+
audience: ID!
|
|
181
|
+
resource: String!
|
|
182
|
+
exp: Int!
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
input DelegationTokenCovers {
|
|
186
|
+
modelID: ID
|
|
187
|
+
docID: ID
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
"""
|
|
191
|
+
A verified revocation of a delegation token addressed to the viewer.
|
|
192
|
+
Only surfaces while the underlying held capability still exists locally;
|
|
193
|
+
revocations whose cap was deleted or never arrived are excluded by the
|
|
194
|
+
audience-derivation join.
|
|
195
|
+
"""
|
|
196
|
+
type RevokedDelegationToken {
|
|
197
|
+
jti: ID!
|
|
198
|
+
grantor: ID!
|
|
199
|
+
audience: ID!
|
|
200
|
+
revokerDID: DID!
|
|
201
|
+
"Issuer's revocation-record timestamp (iat), Unix epoch seconds."
|
|
202
|
+
revokedAt: Int!
|
|
203
|
+
"""
|
|
204
|
+
Wall-clock time the revocation became binding, Unix epoch seconds. For
|
|
205
|
+
self-issued revocations this equals revokedAt (verified at mint time);
|
|
206
|
+
for received revocations it is the receiver's clock at the moment the
|
|
207
|
+
cap-vs-revoker cross-check succeeded.
|
|
208
|
+
"""
|
|
209
|
+
verifiedAt: Int!
|
|
210
|
+
capExp: Int
|
|
211
|
+
groupID: ID!
|
|
212
|
+
}
|
|
213
|
+
|
|
143
214
|
extend type Query {
|
|
144
215
|
groups: [PeerGroup!]!
|
|
145
216
|
group(id: ID!): PeerGroup
|
|
146
217
|
groupMembers(groupID: ID!): [PeerGroupMember!]!
|
|
147
218
|
circles(groupID: ID!): [PeerCircle!]!
|
|
219
|
+
hubs: [PeerHub!]!
|
|
220
|
+
hub(id: ID!): PeerHub
|
|
148
221
|
syncPeers: [SyncPeer!]!
|
|
149
222
|
syncStatus: SyncStatus!
|
|
223
|
+
ownDelegationTokens(grantor: ID, covers: DelegationTokenCovers): [DelegationToken!]!
|
|
224
|
+
ownRevokedDelegationTokens(grantor: ID): [RevokedDelegationToken!]!
|
|
150
225
|
}
|
|
151
226
|
|
|
152
227
|
extend type Mutation {
|
|
153
228
|
createGroup(input: CreateGroupInput!): CreateGroupPayload!
|
|
154
|
-
addGroupMember(groupID: ID!, memberDID:
|
|
229
|
+
addGroupMember(groupID: ID!, memberDID: DID!, role: String!): PeerGroupMember!
|
|
155
230
|
createCircle(groupID: ID!, name: String!, description: String): PeerCircle!
|
|
156
|
-
addCircleMember(circleID: ID!, memberDID:
|
|
231
|
+
addCircleMember(circleID: ID!, memberDID: DID!, role: String!): PeerCircleMember!
|
|
157
232
|
createCatalog(name: String!, description: String, filterCriteria: String!): PeerCatalog!
|
|
158
|
-
inviteToGroup(groupID: ID!, joinRequest: String!): InviteToGroupPayload!
|
|
233
|
+
inviteToGroup(groupID: ID!, joinRequest: String!, grants: [String!], revocations: [String!]): InviteToGroupPayload!
|
|
159
234
|
joinGroup(invitePayload: String!, joinRequestPayload: String!): JoinGroupPayload!
|
|
160
235
|
prepareJoinRequest: PrepareJoinRequestPayload!
|
|
161
236
|
completeJoin(invitePayload: String!): CompleteJoinPayload!
|
|
162
237
|
leaveGroup(groupID: ID!): LeaveGroupPayload!
|
|
163
|
-
removeGroupMember(groupID: ID!, memberDID:
|
|
238
|
+
removeGroupMember(groupID: ID!, memberDID: DID!): RemoveGroupMemberPayload!
|
|
164
239
|
updateCatalog(input: UpdateCatalogInput!): PeerCatalog!
|
|
165
240
|
updateGroup(input: UpdateGroupInput!): UpdateGroupPayload!
|
|
166
241
|
updateCircle(input: UpdateCircleInput!): UpdateCirclePayload!
|
|
167
242
|
deleteCatalog(catalogID: ID!): DeleteCatalogPayload!
|
|
168
243
|
deleteCircle(circleID: ID!): DeleteCirclePayload!
|
|
169
|
-
removeCircleMember(circleID: ID!, memberDID:
|
|
170
|
-
|
|
171
|
-
|
|
244
|
+
removeCircleMember(circleID: ID!, memberDID: DID!): RemoveCircleMemberPayload!
|
|
245
|
+
grantWriteCapability(to: DID!, res: String!, groupID: ID!, expiresIn: Int): String!
|
|
246
|
+
revokeCapability(jti: ID!): Boolean!
|
|
247
|
+
addSyncPeer(peerDID: DID!, endpoint: URL!, mode: String, priority: String, trustLevel: String): Boolean!
|
|
248
|
+
removeSyncPeer(peerDID: DID!): RemoveSyncPeerPayload!
|
|
249
|
+
createHub(input: CreateHubInput!): PeerHub!
|
|
250
|
+
updateHub(input: UpdateHubInput!): PeerHub!
|
|
251
|
+
deleteHub(id: ID!, force: Boolean): DeleteHubPayload!
|
|
252
|
+
bindHubToGroup(hubID: ID!, groupID: ID!): Boolean!
|
|
253
|
+
unbindHubFromGroup(hubID: ID!, groupID: ID!): Boolean!
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
extend type PeerGroup {
|
|
257
|
+
hubs: [PeerHub!]!
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
extend type PeerHub {
|
|
261
|
+
groups: [PeerGroup!]!
|
|
172
262
|
}
|
|
173
263
|
|
|
174
264
|
extend type Subscription {
|
|
@@ -183,5 +273,7 @@ extend type Subscription {
|
|
|
183
273
|
circleMemberAdded(circleID: ID!): PeerCircleMember!
|
|
184
274
|
circleMemberRemoved(circleID: ID!): PeerCircleMember!
|
|
185
275
|
circleCatalogsChanged(circleID: ID!): PeerCircle!
|
|
276
|
+
ownDelegationTokenAdded: DelegationToken!
|
|
277
|
+
ownDelegationTokenRevoked: RevokedDelegationToken!
|
|
186
278
|
}
|
|
187
|
-
`;export function createP2PSchemaExtension(i){return{sdl:
|
|
279
|
+
`;export function createP2PSchemaExtension(i){return{sdl:o,resolvers:{queryFields:{groups:async(e,t,o)=>await r(o).group.list(),group:async(e,t,o)=>await r(o).group.get(t.id),groupMembers:async(e,t,o)=>await r(o).group.listMembers(t.groupID),circles:async(e,t,o)=>await r(o).group.listCircles(t.groupID),hubs:async(e,t,o)=>await r(o).hub.list(),hub:async(e,t,o)=>await r(o).hub.get(t.id),syncPeers:async(e,t,o)=>await r(o).sync.listPeers(),syncStatus:(e,t,o)=>{let i=r(o).sync.getStatus();return{...i,lastSyncByPeer:JSON.stringify(i.lastSyncByPeer)}},ownDelegationTokens:async(e,r,t)=>{let o=t.getViewer();if(null==o||null==t.p2p)return[];let i=r.grantor??null,a=r.covers??null;return await t.p2p.delegation.listOwnTokens({viewerDID:o,grantor:i,covers:a})},ownRevokedDelegationTokens:async(e,r,t)=>{let o=t.getViewer();if(null==o||null==t.p2p)return[];let i=r.grantor??null;return await t.p2p.delegation.listOwnRevokedTokens({viewerDID:o,grantor:i})}},mutationFields:{createGroup:async(e,t,o)=>{let i=t.input;return{group:await r(o).group.create(i)}},addGroupMember:async(e,t,o)=>await r(o).group.addMember(t.groupID,t.memberDID,t.role),createCircle:async(e,t,o)=>await r(o).group.createCircle({groupID:t.groupID,name:t.name,description:t.description}),addCircleMember:async(e,t,o)=>await r(o).group.addCircleMember(t.circleID,t.memberDID,t.role),createCatalog:async(e,t,o)=>await r(o).group.createCatalog({name:t.name,description:t.description,filterCriteria:t.filterCriteria}),inviteToGroup:async(e,t,o)=>await r(o).group.invite(t.groupID,t.joinRequest,t.grants,t.revocations),joinGroup:async(e,t,o)=>await r(o).group.join(t.invitePayload,t.joinRequestPayload),prepareJoinRequest:async(e,t,o)=>await r(o).join.prepareRequest(),completeJoin:async(e,t,o)=>await r(o).join.complete(t.invitePayload),leaveGroup:async(e,t,o)=>await r(o).group.leave(t.groupID),removeGroupMember:async(e,t,o)=>await r(o).group.removeMember(t.groupID,t.memberDID),updateCatalog:async(e,t,o)=>{let i=t.input;return await r(o).group.updateCatalog(i)},updateGroup:async(e,t,o)=>{let i=t.input;return{group:await r(o).group.update(i)}},updateCircle:async(e,t,o)=>{let i=t.input;return{circle:await r(o).group.updateCircle(i)}},deleteCatalog:async(e,t,o)=>await r(o).group.deleteCatalog(t.catalogID),deleteCircle:async(e,t,o)=>await r(o).group.deleteCircle(t.circleID),removeCircleMember:async(e,t,o)=>await r(o).group.removeCircleMember(t.circleID,t.memberDID),grantWriteCapability:async(e,t,o)=>await r(o).group.grantWriteCapability({to:t.to,res:t.res,groupID:t.groupID,expiresIn:t.expiresIn}),revokeCapability:async(e,t,o)=>await r(o).group.revokeCapability({jti:t.jti}),addSyncPeer:async(e,t,o)=>(await r(o).sync.addPeer({peerDID:t.peerDID,endpoint:t.endpoint,mode:t.mode??"on-demand",allowedUsers:{all:!0},priority:t.priority??"0",trustLevel:t.trustLevel??"trusted"}),!0),removeSyncPeer:async(e,t,o)=>(await r(o).sync.removePeer(t.peerDID),{success:!0}),createHub:async(e,t,o)=>{let i=t.input;return await r(o).hub.create(i)},updateHub:async(e,t,o)=>{let i=t.input;return await r(o).hub.update(i)},deleteHub:async(e,t,o)=>await r(o).hub.delete(t.id,t.force??!1),bindHubToGroup:async(e,t,o)=>await r(o).hub.bind({hubID:t.hubID,groupID:t.groupID}),unbindHubFromGroup:async(e,t,o)=>await r(o).hub.unbind({hubID:t.hubID,groupID:t.groupID})},typeFields:{PeerGroup:{hubs:async(e,t,o)=>await r(o).hub.listByGroup(e.id)},PeerHub:{groups:async(e,t,o)=>await r(o).hub.listGroups(e.id)}},subscriptionFields:{groupJoined:{resolve:e=>e,subscribe:()=>e(i,"groupJoined")},groupLeft:{resolve:e=>e,subscribe:()=>e(i,"groupLeft")},groupDataChanged:{resolve:e=>e,subscribe:(r,t)=>e(i,"groupDataChanged",e=>e.groupID===t.groupID)},groupMemberJoined:{resolve:e=>e,subscribe:(r,t)=>e(i,"groupMemberJoined",e=>e.groupID===t.groupID)},groupMemberLeft:{resolve:e=>e,subscribe:(r,t)=>e(i,"groupMemberLeft",e=>e.groupID===t.groupID)},circleCreated:{resolve:e=>e,subscribe:(r,t)=>e(i,"circleCreated",e=>e.groupID===t.groupID)},circleDeleted:{resolve:e=>e,subscribe:(r,t)=>e(i,"circleDeleted",e=>e.groupID===t.groupID)},circleDataChanged:{resolve:e=>e,subscribe:(r,t)=>e(i,"circleDataChanged",e=>e.circleID===t.circleID)},circleMemberAdded:{resolve:e=>e,subscribe:(r,t)=>e(i,"circleMemberAdded",e=>e.circleID===t.circleID)},circleMemberRemoved:{resolve:e=>e,subscribe:(r,t)=>e(i,"circleMemberRemoved",e=>e.circleID===t.circleID)},circleCatalogsChanged:{resolve:e=>e,subscribe:(r,t)=>e(i,"circleCatalogsChanged",e=>e.circleID===t.circleID)},ownDelegationTokenAdded:{resolve:e=>e,subscribe:(r,o,a)=>{let n=a.getViewer();return null==n?t():e(i,"delegationTokenAdded",e=>e.audience===n)}},ownDelegationTokenRevoked:{resolve:e=>e,subscribe:(r,o,a)=>{let n=a.getViewer();return null==n?t():e(i,"delegationTokenRevoked",e=>e.audience===n)}}}}}}
|