@kubun/plugin-p2p 0.8.0 → 0.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/context/join.js +1 -1
- package/lib/context/space.js +1 -1
- package/lib/context/sync.js +1 -1
- package/lib/context/types.d.ts +2 -3
- package/lib/index.d.ts +3 -4
- package/lib/index.js +1 -1
- package/lib/protocol.d.ts +44 -10
- package/lib/protocol.js +1 -1
- package/lib/spaces/broadcast-service.d.ts +5 -6
- package/lib/spaces/broadcast-service.js +1 -1
- package/lib/spaces/broadcast.d.ts +6 -7
- package/lib/spaces/broadcast.js +1 -1
- package/lib/spaces/join-utils.d.ts +2 -5
- package/lib/spaces/join-utils.js +1 -1
- package/lib/spaces/manager.d.ts +22 -14
- package/lib/spaces/manager.js +1 -1
- package/lib/sync/catalog-scope.js +1 -1
- package/lib/sync/handlers.d.ts +10 -0
- package/lib/sync/handlers.js +1 -1
- package/lib/sync/merkle-apply.js +1 -1
- package/lib/sync/merkle-channel.d.ts +18 -0
- package/lib/sync/merkle-channel.js +1 -0
- package/lib/sync/merkle-tree.d.ts +3 -1
- package/lib/sync/merkle-tree.js +1 -1
- package/lib/sync/peer-registry.d.ts +27 -8
- package/lib/sync/peer-registry.js +1 -1
- package/lib/sync/sync-client.d.ts +15 -13
- package/lib/sync/sync-client.js +1 -1
- package/lib/sync/sync-manager.d.ts +36 -11
- package/lib/sync/sync-manager.js +1 -1
- package/lib/types.d.ts +3 -1
- package/package.json +14 -12
- package/lib/migrations.d.ts +0 -8
- package/lib/migrations.js +0 -1
package/lib/context/join.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{createKeyPackageBundle as e}from"@enkaku/group";import{decodeInvitePayload as
|
|
1
|
+
import{createKeyPackageBundle as e}from"@enkaku/group";import{decodeInvitePayload as t,encodeJoinRequest as i}from"../spaces/invite-payload.js";import{createSpaceFromJoin as a}from"../spaces/join-utils.js";import{checkPeerAccess as r}from"./types.js";export function createJoinContext(n,s){return{p2pPrepareJoinRequest:async()=>{r(n.viewerDID,s.identity.id,s.autoAcceptPeers);let t=s.identity,a=await e(t);return s.pendingJoinRequests.set(n.viewerDID,{publicPackage:a.publicPackage,privatePackage:a.privatePackage,did:t.id}),{joinRequest:i({did:t.id,publicPackage:a.publicPackage})}},p2pCompleteJoin:async e=>{r(n.viewerDID,s.identity.id,s.autoAcceptPeers);let i=s.pendingJoinRequests.get(n.viewerDID);if(null==i)throw Error("No pending join request found — call prepareJoinRequest first");let o=s.identity,p=t(e),c={publicPackage:i.publicPackage,privatePackage:i.privatePackage,ownerDID:o.id};await s.spaceManager.joinSpace({stores:s.stores,identity:o,spaceID:p.spaceID,invite:p.invite,keyPackageBundle:c,welcomeMessage:p.welcomeMessage,ratchetTree:p.ratchetTree});let u=await a({stores:s.stores,hlc:s.hlc,emitter:s.emitter,identity:o,invite:p});return s.pendingJoinRequests.delete(n.viewerDID),u}}}
|
package/lib/context/space.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{HLC as e}from"@kubun/hlc";import{
|
|
1
|
+
import{HLC as e}from"@kubun/hlc";import{getGraphStore as a}from"@kubun/store-graph";import{getSpaceStore as t}from"@kubun/store-space";import{restoreGroupHandle as r}from"../spaces/group-handle.js";import{decodeFullJoinRequest as i,decodeInvitePayload as c,decodeJoinRequest as s,encodeInvitePayload as n}from"../spaces/invite-payload.js";import{createSpaceFromJoin as d}from"../spaces/join-utils.js";import{checkPeerAccess as l,normalizeCredential as o,toISO as p}from"./types.js";export function createSpaceContext(m,w){let D=null,g=null,u=()=>D??=t(w.stores),I=()=>g??=a(w.stores);return{spaceCreateSpace:async e=>{let a=await w.spaceManager.createSpace({stores:w.stores,identity:w.identity,name:e.name,description:e.description,createdBy:m.viewerDID}),t=await u(),r=await t.getSpace(a.spaceID);if(null==r)throw Error("Failed to create space");let i={id:r.id,name:r.name,description:r.description,createdBy:r.created_by,createdAt:p(r.created_at)};return await w.emitter.emit("spaceJoined",i),i},spaceGetSpaces:async()=>{let e=await u();return(await e.listSpaces()).map(e=>({id:e.id,name:e.name,description:e.description,createdBy:e.created_by,createdAt:p(e.created_at)}))},spaceGetSpace:async e=>{let a=await u(),t=await a.getSpace(e);return null==t?null:{id:t.id,name:t.name,description:t.description,createdBy:t.created_by,createdAt:p(t.created_at)}},spaceAddSpaceMember:async(e,a,t)=>{let r=await u();await r.addSpaceMember({space_id:e,member_did:a,role:t});let i=await r.getSpaceMember(e,a);if(null==i)throw Error("Failed to add space member");let c={spaceID:i.space_id,memberDID:i.member_did,role:i.role,createdAt:p(i.created_at)};return await w.emitter.emit("spaceMemberJoined",{...c,spaceID:c.spaceID}),c},spaceGetSpaceMembers:async e=>{let a=await u();return(await a.listSpaceMembers(e)).map(e=>({spaceID:e.space_id,memberDID:e.member_did,role:e.role,createdAt:p(e.created_at)}))},spaceCreateCircle:async e=>{let a=await w.spaceManager.createCircle({stores:w.stores,spaceID:e.spaceID,name:e.name,description:e.description}),t=await u(),r=await t.getCircle(a.circleID);if(null==r)throw Error("Failed to create circle");let i={id:r.id,spaceID:r.space_id,name:r.name,description:r.description,createdAt:p(r.created_at)};return await w.emitter.emit("circleCreated",{...i,spaceID:i.spaceID}),i},spaceGetCircles:async e=>{let a=await u();return(await a.listCirclesBySpace(e)).map(e=>({id:e.id,spaceID:e.space_id,name:e.name,description:e.description,createdAt:p(e.created_at)}))},spaceAddCircleMember:async(a,t,r)=>{let i=await u(),c=e.serialize(w.hlc.now());await i.addCircleMember({circle_id:a,member_did:t,role:r,hlc:c});let s=(await i.listCircleMembers(a)).find(e=>e.member_did===t);if(null==s)throw Error("Failed to add circle member");let n={circleID:s.circle_id,memberDID:s.member_did,role:s.role,createdAt:p(s.created_at)};return await w.emitter.emit("circleMemberAdded",{...n,circleID:n.circleID}),n},spaceCreateCatalog:async a=>{let t=await I(),r=w.runtime.getRandomID(),i=e.serialize(w.hlc.now());await t.createCatalog({id:r,owner_did:m.viewerDID,name:a.name,description:a.description??"",filter_criteria:JSON.parse(a.filterCriteria),hlc:i});let c=await t.getCatalog(r);if(null==c)throw Error("Failed to create catalog");return{id:c.id,ownerDID:c.owner_did,name:c.name,description:c.description,filterCriteria:JSON.stringify(c.filter_criteria),createdAt:p(c.created_at)}},spaceGetCatalogs:async e=>{let a=await I();if(null!=e)return(await a.listCatalogs(e)).map(e=>({id:e.id,ownerDID:e.owner_did,name:e.name,description:e.description,filterCriteria:JSON.stringify(e.filter_criteria),createdAt:p(e.created_at)}));let t=await u(),r=new Set((await t.listSpaces()).map(e=>e.created_by));return(await Promise.all(Array.from(r).map(e=>a.listCatalogs(e)))).flat().map(e=>({id:e.id,ownerDID:e.owner_did,name:e.name,description:e.description,filterCriteria:JSON.stringify(e.filter_criteria),createdAt:p(e.created_at)}))},spaceInviteToSpace:async(e,a)=>{l(m.viewerDID,w.identity.id,w.autoAcceptPeers);let t=await u(),i=w.identity,c=await t.getMLSState(e,i.id);if(null==c)throw Error(`No MLS state for space ${e}`);let d=await r({mlsState:c.mls_state,credential:o(c.credential),epoch:c.epoch,rootCapability:c.root_capability}),p=s(a),D=await w.spaceManager.inviteToSpace({stores:w.stores,spaceID:e,identity:i,groupHandle:d,recipientDID:p.did,recipientKeyPackage:p.publicPackage,permission:"member"}),g=await t.getSpace(e),I=n({spaceID:e,spaceName:g?.name??"",hubURLs:g?.hub_urls??[],invite:D.invite,welcomeMessage:D.welcomeMessage,ratchetTree:D.updatedGroupHandle.state.ratchetTree});return await w.emitter.emit("spaceMemberJoined",{spaceID:e,memberDID:p.did,role:"member",createdAt:new Date().toISOString()}),{invitePayload:I,spaceID:e,spaceName:g?.name??""}},spaceJoinSpace:async(e,a)=>{let t=w.identity,r=c(e),s=i(a),n={publicPackage:s.publicPackage,privatePackage:s.privatePackage,ownerDID:s.did};return await w.spaceManager.joinSpace({stores:w.stores,identity:t,spaceID:r.spaceID,invite:r.invite,keyPackageBundle:n,welcomeMessage:r.welcomeMessage,ratchetTree:r.ratchetTree}),await d({stores:w.stores,hlc:w.hlc,emitter:w.emitter,identity:t,invite:r})},spaceLeaveSpace:async e=>{let a=await u(),t=await a.getSpace(e),r=w.identity;return await w.spaceManager.leaveSpace({stores:w.stores,spaceID:e,identity:r}),null!=t&&await w.emitter.emit("spaceLeft",{id:t.id,name:t.name,description:t.description,createdBy:t.created_by,createdAt:p(t.created_at),spaceID:e}),{spaceID:e}},spaceRemoveSpaceMember:async(e,a)=>{let t=await u(),i=w.identity,c=await t.getMLSState(e,i.id);if(null==c)throw Error(`No MLS state for space ${e}`);let s=await r({mlsState:c.mls_state,credential:o(c.credential),epoch:c.epoch,rootCapability:c.root_capability});return await w.spaceManager.removeSpaceMember({stores:w.stores,spaceID:e,groupHandle:s,memberDID:a}),await w.emitter.emit("spaceMemberLeft",{spaceID:e,memberDID:a,role:"",createdAt:new Date().toISOString()}),{spaceID:e}},spaceUpdateSpace:async e=>{await w.spaceManager.updateSpace({stores:w.stores,spaceID:e.spaceID,update:{name:e.name,description:e.description}});let a=await u(),t=await a.getSpace(e.spaceID);if(null==t)throw Error(`Space ${e.spaceID} not found`);let r={id:t.id,name:t.name,description:t.description,createdBy:t.created_by,createdAt:p(t.created_at)};return await w.emitter.emit("spaceDataChanged",{...r,spaceID:e.spaceID}),r},spaceUpdateCircle:async e=>{await w.spaceManager.updateCircle({stores:w.stores,circleID:e.circleID,update:{name:e.name,description:e.description,catalogIDs:e.catalogIDs}});let a=await u(),t=await a.getCircle(e.circleID);if(null==t)throw Error(`Circle ${e.circleID} not found`);let r={id:t.id,spaceID:t.space_id,name:t.name,description:t.description,createdAt:p(t.created_at)};return null!=e.catalogIDs?await w.emitter.emit("circleCatalogsChanged",{...r,circleID:e.circleID}):await w.emitter.emit("circleDataChanged",{...r,circleID:e.circleID}),r},spaceUpdateCatalog:async a=>{let t=await I(),r={hlc:e.serialize(w.hlc.now())};null!=a.name&&(r.name=a.name),null!=a.filterCriteria&&(r.filter_criteria=JSON.parse(a.filterCriteria)),await t.updateCatalog(a.catalogID,r);let i=await t.getCatalog(a.catalogID);if(null==i)throw Error(`Catalog ${a.catalogID} not found`);return{id:i.id,ownerDID:i.owner_did,name:i.name,description:i.description,filterCriteria:JSON.stringify(i.filter_criteria),createdAt:p(i.created_at)}},spaceDeleteCatalog:async e=>{let a=await I();return await a.deleteCatalog(e),{deletedID:e}},spaceDeleteCircle:async e=>{let a=await u(),t=await a.getCircle(e);return await w.spaceManager.deleteCircle({stores:w.stores,circleID:e}),null!=t&&await w.emitter.emit("circleDeleted",{id:t.id,spaceID:t.space_id,name:t.name,description:t.description,createdAt:p(t.created_at)}),{deletedID:e}},spaceRemoveCircleMember:async(e,a)=>(await w.spaceManager.removeCircleMember({stores:w.stores,circleID:e,memberDID:a}),await w.emitter.emit("circleMemberRemoved",{circleID:e,memberDID:a,role:"",createdAt:new Date().toISOString()}),{circleID:e,memberDID:a})}}
|
package/lib/context/sync.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export function createSyncContext(e,
|
|
1
|
+
export function createSyncContext(e,s){return{syncAddPeer:e=>s.syncManager.addPeer({config:e,stores:s.stores}),syncRemovePeer:e=>s.syncManager.removePeer({peerDID:e,stores:s.stores}),syncListPeers:()=>s.syncManager.listPeers({stores:s.stores}),syncGetStatus:e=>s.syncManager.getStatus({peerDID:e,stores:s.stores})}}
|
package/lib/context/types.d.ts
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import type { KeyPackageBundle } from '@enkaku/group';
|
|
2
2
|
import type { Runtime } from '@enkaku/runtime';
|
|
3
3
|
import type { OwnIdentity } from '@enkaku/token';
|
|
4
|
-
import type {
|
|
4
|
+
import type { StoreProvider } from '@kubun/db';
|
|
5
5
|
import type { Adapter } from '@kubun/db-adapter';
|
|
6
6
|
import type { HLC } from '@kubun/hlc';
|
|
7
|
-
import type { Kysely } from 'kysely';
|
|
8
7
|
import type { SpaceEventEmitter } from '../spaces/events.js';
|
|
9
8
|
import type { SpaceManager } from '../spaces/manager.js';
|
|
10
9
|
import type { SyncManager } from '../sync/sync-manager.js';
|
|
@@ -17,11 +16,11 @@ export type ContextDeps = {
|
|
|
17
16
|
identity: OwnIdentity;
|
|
18
17
|
spaceManager: SpaceManager;
|
|
19
18
|
syncManager: SyncManager;
|
|
19
|
+
stores: StoreProvider;
|
|
20
20
|
adapter: Adapter;
|
|
21
21
|
hlc: HLC;
|
|
22
22
|
emitter: SpaceEventEmitter;
|
|
23
23
|
pendingJoinRequests: Map<string, PendingJoinRequest>;
|
|
24
|
-
getDB: () => Promise<Kysely<Database>>;
|
|
25
24
|
runtime: Runtime;
|
|
26
25
|
autoAcceptPeers?: Array<string>;
|
|
27
26
|
};
|
package/lib/index.d.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import type { KubunPlugin, PluginFactoryParams } from '@kubun/engine';
|
|
2
2
|
export type { ContextDeps, PendingJoinRequest } from './context/types.js';
|
|
3
|
-
export { createP2PMigrations, type MigrationContext } from './migrations.js';
|
|
4
3
|
export type { SyncClientMessage, SyncProtocol, SyncServerMessage } from './protocol.js';
|
|
5
4
|
export { createP2PSchemaExtension } from './schema.js';
|
|
6
5
|
export { type ProcessBroadcastParams, processBroadcast, type SpaceBroadcastMessage, } from './spaces/broadcast.js';
|
|
@@ -9,13 +8,13 @@ export { type BroadcastEvent, BroadcastService, type BroadcastServiceEvents, typ
|
|
|
9
8
|
export { createFilteredGenerator, createSpaceEventEmitter, type SpaceEventEmitter, type SpaceEventMap, } from './spaces/events.js';
|
|
10
9
|
export { restoreGroupHandle } from './spaces/group-handle.js';
|
|
11
10
|
export { decodeFullJoinRequest, decodeInvitePayload, decodeJoinRequest, encodeFullJoinRequest, encodeInvitePayload, encodeJoinRequest, type FullJoinRequestPayload, type InvitePayload, type JoinRequestPayload, } from './spaces/invite-payload.js';
|
|
12
|
-
export { type AddCircleMemberParams, type AddCircleMemberResult, type CreateCircleParams, type CreateCircleResult, type CreateSpaceParams, type CreateSpaceResult, type DeleteCircleParams, type DeleteCircleResult, type InviteToSpaceParams, type InviteToSpaceResult, type JoinSpaceParams, type JoinSpaceResult, type LeaveSpaceParams, type LeaveSpaceResult, type
|
|
11
|
+
export { type AddCircleMemberParams, type AddCircleMemberResult, type CreateCircleParams, type CreateCircleResult, type CreateSpaceParams, type CreateSpaceResult, type DeleteCircleParams, type DeleteCircleResult, type InviteToSpaceParams, type InviteToSpaceResult, type JoinSpaceParams, type JoinSpaceResult, type LeaveSpaceParams, type LeaveSpaceResult, type RemoveCircleMemberParams, type RemoveCircleMemberResult, type RemoveMemberParams, type RemoveMemberResult, type RemoveSpaceMemberParams, type RemoveSpaceMemberResult, SpaceManager, type SpaceManagerParams, type UpdateCircleParams, type UpdateCircleResult, type UpdateSpaceParams, type UpdateSpaceResult, } from './spaces/manager.js';
|
|
13
12
|
export { replacer as mlsJSONReplacer, reviver as mlsJSONReviver } from './spaces/mls-json.js';
|
|
14
13
|
export { deserializeGroupState, type GroupState, type SerializedGroupState, serializeGroupState, } from './spaces/mls-state.js';
|
|
15
14
|
export { type CatalogSyncScope, resolveCatalogSyncScopes } from './sync/catalog-scope.js';
|
|
16
|
-
export { checkSyncDelegation } from './sync/handlers.js';
|
|
15
|
+
export { checkSyncDelegation, negotiateDirection } from './sync/handlers.js';
|
|
17
16
|
export { type PeerConfig, type PeerConfigWithID, PeerRegistry } from './sync/peer-registry.js';
|
|
18
|
-
export {
|
|
17
|
+
export { SyncClient, type SyncClientParams, type SyncScope, type SyncTransportProvider, } from './sync/sync-client.js';
|
|
19
18
|
export { type SyncEvent, SyncManager, type SyncManagerParams, type SyncSessionInfo, type SyncStatus, } from './sync/sync-manager.js';
|
|
20
19
|
export type { CatalogData, CircleData, CircleMemberData, DeleteCatalogData, DeleteCircleData, InviteToSpaceData, JoinSpaceData, LeaveSpaceData, P2PJoinRequestContext, RemoveCircleMemberData, RemoveSpaceMemberData, SpaceData, SpaceMemberData, SpaceRequestContext, SyncPluginAPI, SyncRequestContext, SyncSessionResult, UpdateCircleInput, UpdateSpaceInput, } from './types.js';
|
|
21
20
|
export type P2PPluginOptions = {
|
package/lib/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{ServerTransport as e}from"@enkaku/http-server-transport";import{Server as
|
|
1
|
+
import{ServerTransport as e}from"@enkaku/http-server-transport";import{Server as r}from"@enkaku/server";import{DirectTransports as t}from"@enkaku/transport";import{HLC as s}from"@kubun/hlc";import{spaceStoreDefinition as o}from"@kubun/store-space";import{createJoinContext as n}from"./context/join.js";import{createSpaceContext as a}from"./context/space.js";import{createSyncContext as c}from"./context/sync.js";import{createP2PSchemaExtension as i}from"./schema.js";import{createSpaceEventEmitter as p}from"./spaces/events.js";import{SpaceManager as m}from"./spaces/manager.js";import{createSyncHandlers as d}from"./sync/handlers.js";import{SyncManager as l}from"./sync/sync-manager.js";export{createP2PSchemaExtension}from"./schema.js";export{processBroadcast}from"./spaces/broadcast.js";export{deserializeBroadcast,serializeBroadcast}from"./spaces/broadcast-codec.js";export{BroadcastService}from"./spaces/broadcast-service.js";export{createFilteredGenerator,createSpaceEventEmitter}from"./spaces/events.js";export{restoreGroupHandle}from"./spaces/group-handle.js";export{decodeFullJoinRequest,decodeInvitePayload,decodeJoinRequest,encodeFullJoinRequest,encodeInvitePayload,encodeJoinRequest}from"./spaces/invite-payload.js";export{SpaceManager}from"./spaces/manager.js";export{replacer as mlsJSONReplacer,reviver as mlsJSONReviver}from"./spaces/mls-json.js";export{deserializeGroupState,serializeGroupState}from"./spaces/mls-state.js";export{resolveCatalogSyncScopes}from"./sync/catalog-scope.js";export{checkSyncDelegation,negotiateDirection}from"./sync/handlers.js";export{PeerRegistry}from"./sync/peer-registry.js";export{SyncClient}from"./sync/sync-client.js";export{SyncManager}from"./sync/sync-manager.js";export function createP2PPlugin(g){return y=>{let u,f;y.db.register(o);let j=y.db.adapter,h=y.identity,v=new s({nodeID:h.id}),x=new m({identity:h,getRandomID:y.runtime.getRandomID}),P=p(),S=new l({deployClusters:async e=>{let r=Object.values(e);await y.engine.deployGraph({clusters:r})},runtime:y.runtime,identity:y.identity,logger:y.getLogger("sync")}),b=d({db:y.db,logger:y.getLogger("sync-handlers"),peerRegistry:S.peerRegistry}),R=new Map,w=[],k={addPeer:e=>S.addPeer({config:e,stores:y.db}),removePeer:e=>S.removePeer({peerDID:e,stores:y.db}),listPeers:()=>S.listPeers({stores:y.db}),syncWithPeer:e=>S.merkleSyncWithPeer({...e,stores:y.db}),getStatus:e=>S.getStatus({peerDID:e,stores:y.db}),onSyncEvent:e=>S.onSyncEvent(e),createSyncTransport:function(e){let s=new t({signal:e}),o=new r({accessControl:!1,getRandomID:y.runtime.getRandomID,handlers:b,logger:y.getLogger("sync-server"),transports:[s.server],signal:e});return w.push(o),s.client}};if(g?.http){let t="string"==typeof g.http?g.http.replace(/^\//,""):"sync";k.syncReady=(async()=>{let s=await y.engine.getAPI("http");await s.listening,u=new e({allowedOrigin:["*"]}),f=new r({accessControl:!1,getRandomID:y.runtime.getRandomID,handlers:b,logger:y.getLogger("sync-http-server"),transports:[u]}),w.push(f),s.registerProtocol(t,u.fetch.bind(u))})()}return{name:"p2p",schemaExtension:e=>i(P),api:k,createContextFactory:()=>(e,r)=>{let t={identity:h,spaceManager:x,syncManager:S,stores:r,adapter:j,hlc:v,emitter:P,pendingJoinRequests:R,runtime:y.runtime,autoAcceptPeers:g?.autoAcceptPeers};return{...a(e,t),...c(e,t),...n(e,t)}},dispose:async()=>{for(let e of w)await e.dispose();await u?.dispose(),await S.dispose()}}}}
|
package/lib/protocol.d.ts
CHANGED
|
@@ -44,6 +44,10 @@ export declare const syncProtocol: {
|
|
|
44
44
|
};
|
|
45
45
|
readonly description: "Model IDs the requester already has — used to identify missing clusters";
|
|
46
46
|
};
|
|
47
|
+
readonly direction: {
|
|
48
|
+
readonly type: "string";
|
|
49
|
+
readonly enum: readonly ["push", "pull", "both"];
|
|
50
|
+
};
|
|
47
51
|
};
|
|
48
52
|
readonly required: readonly ["delegationTokens"];
|
|
49
53
|
readonly additionalProperties: false;
|
|
@@ -51,6 +55,10 @@ export declare const syncProtocol: {
|
|
|
51
55
|
readonly result: {
|
|
52
56
|
readonly type: "object";
|
|
53
57
|
readonly properties: {
|
|
58
|
+
readonly direction: {
|
|
59
|
+
readonly type: "string";
|
|
60
|
+
readonly enum: readonly ["push", "pull", "both"];
|
|
61
|
+
};
|
|
54
62
|
readonly acceptedScopes: {
|
|
55
63
|
readonly type: "array";
|
|
56
64
|
readonly items: {
|
|
@@ -84,7 +92,7 @@ export declare const syncProtocol: {
|
|
|
84
92
|
};
|
|
85
93
|
};
|
|
86
94
|
readonly 'sync/merkle-sync': {
|
|
87
|
-
readonly type: "
|
|
95
|
+
readonly type: "channel";
|
|
88
96
|
readonly param: {
|
|
89
97
|
readonly type: "object";
|
|
90
98
|
readonly properties: {
|
|
@@ -117,11 +125,15 @@ export declare const syncProtocol: {
|
|
|
117
125
|
readonly type: "string";
|
|
118
126
|
};
|
|
119
127
|
};
|
|
128
|
+
readonly direction: {
|
|
129
|
+
readonly type: "string";
|
|
130
|
+
readonly enum: readonly ["push", "pull", "both"];
|
|
131
|
+
};
|
|
120
132
|
};
|
|
121
|
-
readonly required: readonly ["scopes", "excludedDocumentIDs", "tree"];
|
|
133
|
+
readonly required: readonly ["scopes", "excludedDocumentIDs", "tree", "direction"];
|
|
122
134
|
readonly additionalProperties: false;
|
|
123
135
|
};
|
|
124
|
-
readonly
|
|
136
|
+
readonly send: {
|
|
125
137
|
readonly type: "object";
|
|
126
138
|
readonly properties: {
|
|
127
139
|
readonly type: {
|
|
@@ -135,13 +147,30 @@ export declare const syncProtocol: {
|
|
|
135
147
|
};
|
|
136
148
|
readonly description: "Signed mutation JWTs for divergent time buckets";
|
|
137
149
|
};
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
150
|
+
};
|
|
151
|
+
readonly required: readonly ["type"];
|
|
152
|
+
readonly additionalProperties: false;
|
|
153
|
+
};
|
|
154
|
+
readonly receive: {
|
|
155
|
+
readonly type: "object";
|
|
156
|
+
readonly properties: {
|
|
157
|
+
readonly type: {
|
|
158
|
+
readonly type: "string";
|
|
159
|
+
readonly enum: readonly ["tree", "mutations", "complete"];
|
|
141
160
|
};
|
|
142
|
-
readonly
|
|
143
|
-
readonly type: "
|
|
144
|
-
readonly description: "
|
|
161
|
+
readonly tree: {
|
|
162
|
+
readonly type: "object";
|
|
163
|
+
readonly description: "Responder merkle tree bucket hashes";
|
|
164
|
+
readonly additionalProperties: {
|
|
165
|
+
readonly type: "string";
|
|
166
|
+
};
|
|
167
|
+
};
|
|
168
|
+
readonly mutationJWTs: {
|
|
169
|
+
readonly type: "array";
|
|
170
|
+
readonly items: {
|
|
171
|
+
readonly type: "string";
|
|
172
|
+
};
|
|
173
|
+
readonly description: "Signed mutation JWTs for divergent time buckets";
|
|
145
174
|
};
|
|
146
175
|
};
|
|
147
176
|
readonly required: readonly ["type"];
|
|
@@ -159,8 +188,11 @@ export declare const syncProtocol: {
|
|
|
159
188
|
readonly mutationsSent: {
|
|
160
189
|
readonly type: "number";
|
|
161
190
|
};
|
|
191
|
+
readonly mutationsReceived: {
|
|
192
|
+
readonly type: "number";
|
|
193
|
+
};
|
|
162
194
|
};
|
|
163
|
-
readonly required: readonly ["success", "divergentBuckets", "mutationsSent"];
|
|
195
|
+
readonly required: readonly ["success", "divergentBuckets", "mutationsSent", "mutationsReceived"];
|
|
164
196
|
readonly additionalProperties: false;
|
|
165
197
|
};
|
|
166
198
|
};
|
|
@@ -169,7 +201,9 @@ export type SyncProtocol = typeof syncProtocol;
|
|
|
169
201
|
export type SyncNegotiateParams = FromSchema<SyncProtocol['sync/negotiate']['param']>;
|
|
170
202
|
export type SyncNegotiateResult = FromSchema<SyncProtocol['sync/negotiate']['result']>;
|
|
171
203
|
export type SyncMerkleSyncParams = FromSchema<SyncProtocol['sync/merkle-sync']['param']>;
|
|
204
|
+
export type SyncMerkleSyncSend = FromSchema<SyncProtocol['sync/merkle-sync']['send']>;
|
|
172
205
|
export type SyncMerkleSyncReceive = FromSchema<SyncProtocol['sync/merkle-sync']['receive']>;
|
|
173
206
|
export type SyncMerkleSyncResult = FromSchema<SyncProtocol['sync/merkle-sync']['result']>;
|
|
207
|
+
export type SyncDirection = 'push' | 'pull' | 'both';
|
|
174
208
|
export type SyncClientMessage = AnyClientMessageOf<SyncProtocol>;
|
|
175
209
|
export type SyncServerMessage = AnyServerMessageOf<SyncProtocol>;
|
package/lib/protocol.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const syncProtocol={"sync/negotiate":{type:"request",param:{type:"object",properties:{scopes:{type:"array",items:{type:"object",properties:{modelID:{type:"string"},ownerDID:{type:"string"}},required:["modelID","ownerDID"],additionalProperties:!1},description:"Requested sync scopes as (model, owner) pairs"},delegationTokens:{type:"array",items:{type:"string"},description:"JWT delegation tokens for authorization"},catalogIDs:{type:"array",items:{type:"string"},description:"Catalog IDs to resolve sync scope from criteria"},knownModelIDs:{type:"array",items:{type:"string"},description:"Model IDs the requester already has — used to identify missing clusters"}},required:["delegationTokens"],additionalProperties:!1},result:{type:"object",properties:{acceptedScopes:{type:"array",items:{type:"object",properties:{modelID:{type:"string"},ownerDID:{type:"string"}},required:["modelID","ownerDID"],additionalProperties:!1}},excludedDocumentIDs:{type:"array",items:{type:"string"}},missingClusters:{type:"object",description:"Cluster definitions for models the requester does not have yet",additionalProperties:!0}},required:["acceptedScopes","excludedDocumentIDs"],additionalProperties:!1}},"sync/merkle-sync":{type:"
|
|
1
|
+
export const syncProtocol={"sync/negotiate":{type:"request",param:{type:"object",properties:{scopes:{type:"array",items:{type:"object",properties:{modelID:{type:"string"},ownerDID:{type:"string"}},required:["modelID","ownerDID"],additionalProperties:!1},description:"Requested sync scopes as (model, owner) pairs"},delegationTokens:{type:"array",items:{type:"string"},description:"JWT delegation tokens for authorization"},catalogIDs:{type:"array",items:{type:"string"},description:"Catalog IDs to resolve sync scope from criteria"},knownModelIDs:{type:"array",items:{type:"string"},description:"Model IDs the requester already has — used to identify missing clusters"},direction:{type:"string",enum:["push","pull","both"]}},required:["delegationTokens"],additionalProperties:!1},result:{type:"object",properties:{direction:{type:"string",enum:["push","pull","both"]},acceptedScopes:{type:"array",items:{type:"object",properties:{modelID:{type:"string"},ownerDID:{type:"string"}},required:["modelID","ownerDID"],additionalProperties:!1}},excludedDocumentIDs:{type:"array",items:{type:"string"}},missingClusters:{type:"object",description:"Cluster definitions for models the requester does not have yet",additionalProperties:!0}},required:["acceptedScopes","excludedDocumentIDs"],additionalProperties:!1}},"sync/merkle-sync":{type:"channel",param:{type:"object",properties:{scopes:{type:"array",items:{type:"object",properties:{modelID:{type:"string"},ownerDID:{type:"string"}},required:["modelID","ownerDID"],additionalProperties:!1}},excludedDocumentIDs:{type:"array",items:{type:"string"}},tree:{type:"object",description:"Merkle tree bucket hashes from requester",additionalProperties:{type:"string"}},direction:{type:"string",enum:["push","pull","both"]}},required:["scopes","excludedDocumentIDs","tree","direction"],additionalProperties:!1},send:{type:"object",properties:{type:{type:"string",enum:["mutations","complete"]},mutationJWTs:{type:"array",items:{type:"string"},description:"Signed mutation JWTs for divergent time buckets"}},required:["type"],additionalProperties:!1},receive:{type:"object",properties:{type:{type:"string",enum:["tree","mutations","complete"]},tree:{type:"object",description:"Responder merkle tree bucket hashes",additionalProperties:{type:"string"}},mutationJWTs:{type:"array",items:{type:"string"},description:"Signed mutation JWTs for divergent time buckets"}},required:["type"],additionalProperties:!1},result:{type:"object",properties:{success:{type:"boolean"},divergentBuckets:{type:"number"},mutationsSent:{type:"number"},mutationsReceived:{type:"number"}},required:["success","divergentBuckets","mutationsSent","mutationsReceived"],additionalProperties:!1}}};
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { EventEmitter } from '@enkaku/event';
|
|
2
|
-
import type {
|
|
3
|
-
import type {
|
|
4
|
-
import type { Kysely } from 'kysely';
|
|
2
|
+
import type { GraphStoreAPI } from '@kubun/store-graph';
|
|
3
|
+
import type { SpaceStoreAPI } from '@kubun/store-space';
|
|
5
4
|
import { type SpaceBroadcastMessage } from './broadcast.js';
|
|
6
5
|
import { type SerializedGroupState } from './mls-state.js';
|
|
7
6
|
export type BroadcastEvent = {
|
|
@@ -39,9 +38,9 @@ export type ReceiveBroadcastResult = {
|
|
|
39
38
|
* for sending encrypted bytes via hub and delivering received bytes
|
|
40
39
|
* from hub. This keeps the service testable without hub infrastructure.
|
|
41
40
|
*/
|
|
42
|
-
export type BroadcastServiceParams
|
|
43
|
-
|
|
44
|
-
|
|
41
|
+
export type BroadcastServiceParams = {
|
|
42
|
+
spaceStore: SpaceStoreAPI;
|
|
43
|
+
graphStore: GraphStoreAPI;
|
|
45
44
|
};
|
|
46
45
|
export declare class BroadcastService extends EventEmitter<BroadcastServiceEvents> {
|
|
47
46
|
#private;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{EventEmitter as e}from"@enkaku/event";import{processBroadcast as
|
|
1
|
+
import{EventEmitter as e}from"@enkaku/event";import{processBroadcast as r}from"./broadcast.js";import{deserializeBroadcast as a,serializeBroadcast as t}from"./broadcast-codec.js";import{restoreGroupHandle as s}from"./group-handle.js";import{replacer as o,reviver as c}from"./mls-json.js";import{serializeGroupState as i}from"./mls-state.js";export class BroadcastService extends e{#e;constructor(e){super(),this.#e={spaceStore:e.spaceStore,graphStore:e.graphStore}}async prepareSend(e){let{message:r,groupState:a}=e,c=t(r),p=await s(a),{message:d}=await p.encrypt(c),m=i(p);return{encrypted:new TextEncoder().encode(JSON.stringify(d,o)),updatedGroupState:m}}async processReceived(e){let{spaceID:t,encrypted:o,groupState:p}=e,d=await s(p),m=JSON.parse(new TextDecoder().decode(o),c),n=a(await d.decrypt(m)),w=await r(this.#e,n),S=i(d);return await this.emit("broadcast",{spaceID:t,message:n,applied:w}),{message:n,applied:w,updatedGroupState:S}}}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import type { Database } from '@kubun/db';
|
|
2
|
-
import type { AbstractAdapter, AdapterTypes } from '@kubun/db-adapter';
|
|
3
1
|
import type { CatalogRecord, CircleMemberRecord, CircleRecord } from '@kubun/protocol';
|
|
4
|
-
import type {
|
|
2
|
+
import type { GraphStoreAPI } from '@kubun/store-graph';
|
|
3
|
+
import type { SpaceStoreAPI } from '@kubun/store-space';
|
|
5
4
|
export type SpaceBroadcastMessage = {
|
|
6
5
|
type: 'circle:create';
|
|
7
6
|
circle: CircleRecord;
|
|
@@ -52,9 +51,9 @@ export type SpaceBroadcastMessage = {
|
|
|
52
51
|
hlc: string;
|
|
53
52
|
};
|
|
54
53
|
};
|
|
55
|
-
export type ProcessBroadcastParams
|
|
56
|
-
|
|
57
|
-
|
|
54
|
+
export type ProcessBroadcastParams = {
|
|
55
|
+
spaceStore: SpaceStoreAPI;
|
|
56
|
+
graphStore: GraphStoreAPI;
|
|
58
57
|
};
|
|
59
58
|
/**
|
|
60
59
|
* Process a received broadcast message, applying it to the local database
|
|
@@ -62,4 +61,4 @@ export type ProcessBroadcastParams<T extends AdapterTypes = AdapterTypes> = {
|
|
|
62
61
|
*
|
|
63
62
|
* Returns true if the message was applied, false if it was skipped (stale HLC or missing target).
|
|
64
63
|
*/
|
|
65
|
-
export declare function processBroadcast
|
|
64
|
+
export declare function processBroadcast(params: ProcessBroadcastParams, message: SpaceBroadcastMessage): Promise<boolean>;
|
package/lib/spaces/broadcast.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{HLC as e}from"@kubun/hlc";function
|
|
1
|
+
import{HLC as e}from"@kubun/hlc";function a(a,c){return e.compare(e.parse(a),e.parse(c))>0}export async function processBroadcast(e,c){let{spaceStore:t,graphStore:r}=e;switch(c.type){case"circle:create":{let e=await t.getCircle(c.circle.id);if(null!=e){if(!a(c.circle.hlc,e.hlc))return!1;return await t.updateCircle(c.circle.id,{name:c.circle.name,description:c.circle.description,catalog_ids:c.circle.catalogIDs,hlc:c.circle.hlc}),!0}return await t.createCircle({id:c.circle.id,space_id:c.circle.spaceID,name:c.circle.name,description:c.circle.description,catalog_ids:c.circle.catalogIDs,hlc:c.circle.hlc}),!0}case"circle:update":{let e=await t.getCircle(c.circleID);if(null==e||!a(c.update.hlc,e.hlc))return!1;return await t.updateCircle(c.circleID,{name:c.update.name,description:c.update.description,catalog_ids:c.update.catalogIDs,hlc:c.update.hlc}),!0}case"circle:delete":{let e=await t.getCircle(c.circleID);if(null==e||!a(c.hlc,e.hlc))return!1;return await t.deleteCircle(c.circleID),!0}case"member:add":return await t.addCircleMember({circle_id:c.member.circleID,member_did:c.member.memberDID,role:c.member.role,hlc:c.member.hlc}),!0;case"member:remove":return await t.removeCircleMember(c.circleID,c.memberDID),!0;case"catalog:create":{let e=await r.getCatalog(c.catalog.id);if(null!=e){if(!a(c.catalog.hlc,e.hlc))return!1;return await r.updateCatalog(c.catalog.id,{name:c.catalog.name,description:c.catalog.description,filter_criteria:c.catalog.filterCriteria,hlc:c.catalog.hlc}),!0}return await r.createCatalog({id:c.catalog.id,owner_did:c.catalog.ownerDID,name:c.catalog.name,description:c.catalog.description,filter_criteria:c.catalog.filterCriteria,hlc:c.catalog.hlc}),!0}case"catalog:update":{let e=await r.getCatalog(c.catalogID);if(null==e||!a(c.update.hlc,e.hlc))return!1;return await r.updateCatalog(c.catalogID,{name:c.update.name,description:c.update.description,filter_criteria:c.update.filterCriteria,hlc:c.update.hlc}),!0}case"catalog:delete":{let e=await r.getCatalog(c.catalogID);if(null==e||!a(c.hlc,e.hlc))return!1;return await r.deleteCatalog(c.catalogID),!0}case"space:update":{let e=await t.getSpace(c.spaceID);if(null==e||!a(c.update.hlc,e.hlc))return!1;return await t.updateSpace(c.spaceID,{name:c.update.name,description:c.update.description,hub_urls:c.update.hubURLs,hlc:c.update.hlc}),!0}}}
|
|
@@ -1,13 +1,10 @@
|
|
|
1
1
|
import type { OwnIdentity } from '@enkaku/token';
|
|
2
|
-
import type {
|
|
3
|
-
import type { Adapter } from '@kubun/db-adapter';
|
|
2
|
+
import type { StoreProvider } from '@kubun/db';
|
|
4
3
|
import { HLC } from '@kubun/hlc';
|
|
5
|
-
import type { Kysely } from 'kysely';
|
|
6
4
|
import type { SpaceData } from '../types.js';
|
|
7
5
|
import type { SpaceEventEmitter } from './events.js';
|
|
8
6
|
export type CreateSpaceFromJoinParams = {
|
|
9
|
-
|
|
10
|
-
adapter: Adapter;
|
|
7
|
+
stores: StoreProvider;
|
|
11
8
|
hlc: HLC;
|
|
12
9
|
emitter: SpaceEventEmitter;
|
|
13
10
|
identity: OwnIdentity;
|
package/lib/spaces/join-utils.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{HLC as e}from"@kubun/hlc";import{toISO as t}from"../context/types.js";export async function createSpaceFromJoin(
|
|
1
|
+
import{HLC as e}from"@kubun/hlc";import{getSpaceStore as a}from"@kubun/store-space";import{toISO as t}from"../context/types.js";export async function createSpaceFromJoin(c){let{stores:i,hlc:r,emitter:n,identity:p,invite:o}=c,s=await a(i);null==await s.getSpace(o.spaceID)&&await s.createSpace({id:o.spaceID,name:o.spaceName,description:"",created_by:p.id,hub_urls:o.hubURLs,hlc:e.serialize(r.now())});let l=await s.getSpace(o.spaceID),d=null!=l?{id:l.id,name:l.name,description:l.description,createdBy:l.created_by,createdAt:t(l.created_at)}:null;return null!=d&&await n.emit("spaceJoined",d),{space:d}}
|
package/lib/spaces/manager.d.ts
CHANGED
|
@@ -1,27 +1,25 @@
|
|
|
1
1
|
import { type GroupHandle, type GroupPermission, type Invite, type KeyPackageBundle } from '@enkaku/group';
|
|
2
2
|
import type { Identity, OwnIdentity } from '@enkaku/token';
|
|
3
|
-
import type {
|
|
4
|
-
import type { AbstractAdapter, AdapterTypes } from '@kubun/db-adapter';
|
|
5
|
-
import type { Kysely, Transaction } from 'kysely';
|
|
3
|
+
import type { StoreProvider } from '@kubun/db';
|
|
6
4
|
import type { SpaceBroadcastMessage } from './broadcast.js';
|
|
7
|
-
export type
|
|
8
|
-
export type SpaceManagerParams<T extends AdapterTypes = AdapterTypes> = {
|
|
9
|
-
kysely: Kysely<Database> | Promise<Kysely<Database>>;
|
|
10
|
-
adapter: AbstractAdapter<T>;
|
|
5
|
+
export type SpaceManagerParams = {
|
|
11
6
|
identity: Identity;
|
|
12
7
|
getRandomID: () => string;
|
|
13
8
|
};
|
|
14
9
|
export type CreateSpaceParams = {
|
|
10
|
+
stores: StoreProvider;
|
|
15
11
|
identity: OwnIdentity;
|
|
16
12
|
name: string;
|
|
17
13
|
description?: string;
|
|
18
14
|
hubURLs?: Array<string>;
|
|
15
|
+
createdBy?: string;
|
|
19
16
|
};
|
|
20
17
|
export type CreateSpaceResult = {
|
|
21
18
|
spaceID: string;
|
|
22
19
|
groupHandle: GroupHandle;
|
|
23
20
|
};
|
|
24
21
|
export type UpdateSpaceParams = {
|
|
22
|
+
stores: StoreProvider;
|
|
25
23
|
spaceID: string;
|
|
26
24
|
update: {
|
|
27
25
|
name?: string;
|
|
@@ -33,6 +31,7 @@ export type UpdateSpaceResult = {
|
|
|
33
31
|
broadcast: SpaceBroadcastMessage;
|
|
34
32
|
};
|
|
35
33
|
export type InviteToSpaceParams = {
|
|
34
|
+
stores: StoreProvider;
|
|
36
35
|
spaceID: string;
|
|
37
36
|
identity: OwnIdentity;
|
|
38
37
|
groupHandle: GroupHandle;
|
|
@@ -47,6 +46,7 @@ export type InviteToSpaceResult = {
|
|
|
47
46
|
updatedGroupHandle: GroupHandle;
|
|
48
47
|
};
|
|
49
48
|
export type JoinSpaceParams = {
|
|
49
|
+
stores: StoreProvider;
|
|
50
50
|
identity: OwnIdentity;
|
|
51
51
|
spaceID: string;
|
|
52
52
|
invite: Invite;
|
|
@@ -58,6 +58,7 @@ export type JoinSpaceResult = {
|
|
|
58
58
|
groupHandle: GroupHandle;
|
|
59
59
|
};
|
|
60
60
|
export type RemoveMemberParams = {
|
|
61
|
+
stores: StoreProvider;
|
|
61
62
|
spaceID: string;
|
|
62
63
|
groupHandle: GroupHandle;
|
|
63
64
|
leafIndex: number;
|
|
@@ -68,6 +69,7 @@ export type RemoveMemberResult = {
|
|
|
68
69
|
updatedGroupHandle: GroupHandle;
|
|
69
70
|
};
|
|
70
71
|
export type RemoveSpaceMemberParams = {
|
|
72
|
+
stores: StoreProvider;
|
|
71
73
|
spaceID: string;
|
|
72
74
|
groupHandle: GroupHandle;
|
|
73
75
|
memberDID: string;
|
|
@@ -77,11 +79,13 @@ export type RemoveSpaceMemberResult = {
|
|
|
77
79
|
updatedGroupHandle: GroupHandle;
|
|
78
80
|
};
|
|
79
81
|
export type LeaveSpaceParams = {
|
|
82
|
+
stores: StoreProvider;
|
|
80
83
|
spaceID: string;
|
|
81
84
|
identity: OwnIdentity;
|
|
82
85
|
};
|
|
83
86
|
export type LeaveSpaceResult = undefined;
|
|
84
87
|
export type CreateCircleParams = {
|
|
88
|
+
stores: StoreProvider;
|
|
85
89
|
spaceID: string;
|
|
86
90
|
name: string;
|
|
87
91
|
description?: string;
|
|
@@ -92,6 +96,7 @@ export type CreateCircleResult = {
|
|
|
92
96
|
broadcast: SpaceBroadcastMessage;
|
|
93
97
|
};
|
|
94
98
|
export type UpdateCircleParams = {
|
|
99
|
+
stores: StoreProvider;
|
|
95
100
|
circleID: string;
|
|
96
101
|
update: {
|
|
97
102
|
name?: string;
|
|
@@ -104,12 +109,14 @@ export type UpdateCircleResult = {
|
|
|
104
109
|
broadcasts: Array<SpaceBroadcastMessage>;
|
|
105
110
|
};
|
|
106
111
|
export type DeleteCircleParams = {
|
|
112
|
+
stores: StoreProvider;
|
|
107
113
|
circleID: string;
|
|
108
114
|
};
|
|
109
115
|
export type DeleteCircleResult = {
|
|
110
116
|
broadcast: SpaceBroadcastMessage;
|
|
111
117
|
};
|
|
112
118
|
export type AddCircleMemberParams = {
|
|
119
|
+
stores: StoreProvider;
|
|
113
120
|
spaceID: string;
|
|
114
121
|
circleID: string;
|
|
115
122
|
memberDID: string;
|
|
@@ -119,6 +126,7 @@ export type AddCircleMemberResult = {
|
|
|
119
126
|
broadcast: SpaceBroadcastMessage;
|
|
120
127
|
};
|
|
121
128
|
export type RemoveCircleMemberParams = {
|
|
129
|
+
stores: StoreProvider;
|
|
122
130
|
circleID: string;
|
|
123
131
|
memberDID: string;
|
|
124
132
|
};
|
|
@@ -128,14 +136,14 @@ export type RemoveCircleMemberResult = {
|
|
|
128
136
|
export declare class SpaceManager {
|
|
129
137
|
#private;
|
|
130
138
|
constructor(params: SpaceManagerParams);
|
|
131
|
-
createSpace(params: CreateSpaceParams
|
|
139
|
+
createSpace(params: CreateSpaceParams): Promise<CreateSpaceResult>;
|
|
132
140
|
updateSpace(params: UpdateSpaceParams): Promise<UpdateSpaceResult>;
|
|
133
|
-
inviteToSpace(params: InviteToSpaceParams
|
|
134
|
-
joinSpace(params: JoinSpaceParams
|
|
135
|
-
removeMember(params: RemoveMemberParams
|
|
136
|
-
removeSpaceMember(params: RemoveSpaceMemberParams
|
|
137
|
-
leaveSpace(params: LeaveSpaceParams
|
|
138
|
-
loadSpace(spaceID: string): Promise<{
|
|
141
|
+
inviteToSpace(params: InviteToSpaceParams): Promise<InviteToSpaceResult>;
|
|
142
|
+
joinSpace(params: JoinSpaceParams): Promise<JoinSpaceResult>;
|
|
143
|
+
removeMember(params: RemoveMemberParams): Promise<RemoveMemberResult>;
|
|
144
|
+
removeSpaceMember(params: RemoveSpaceMemberParams): Promise<RemoveSpaceMemberResult>;
|
|
145
|
+
leaveSpace(params: LeaveSpaceParams): Promise<LeaveSpaceResult>;
|
|
146
|
+
loadSpace(stores: StoreProvider, spaceID: string): Promise<{
|
|
139
147
|
epoch: number;
|
|
140
148
|
credential: string;
|
|
141
149
|
} | undefined>;
|
package/lib/spaces/manager.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{commitInvite as e,createGroup as
|
|
1
|
+
import{commitInvite as e,createGroup as a,createInvite as t,processWelcome as i,removeMember as r}from"@enkaku/group";import{HLC as c}from"@kubun/hlc";import{getGraphStore as s}from"@kubun/store-graph";import{getSpaceStore as d}from"@kubun/store-space";import{serializeGroupState as l}from"./mls-state.js";export class SpaceManager{#e;#a;#t;constructor(e){this.#e=e.identity.id,this.#a=e.getRandomID,this.#t=new c({nodeID:e.identity.id})}async #i(e,a,t){let i=l(t),r=await d(e);await r.saveMLSState({space_id:a,device_id:this.#e,mls_state:i.mlsState,credential:i.credential,epoch:i.epoch,root_capability:i.rootCapability})}async createSpace(e){let t=this.#a(),{group:i}=await a(e.identity,t),r=await d(e.stores);return await r.createSpace({id:t,name:e.name,description:e.description??"",created_by:e.createdBy??e.identity.id,hub_urls:e.hubURLs??[],hlc:c.serialize(this.#t.now())}),await this.#i(e.stores,t,i),await r.addSpaceMember({space_id:t,member_did:e.identity.id,role:"admin"}),{spaceID:t,groupHandle:i}}async updateSpace(e){let a=c.serialize(this.#t.now()),t=await d(e.stores);return await t.updateSpace(e.spaceID,{name:e.update.name,description:e.update.description,hub_urls:e.update.hubURLs,hlc:a}),{broadcast:{type:"space:update",spaceID:e.spaceID,update:{...e.update,hlc:a}}}}async inviteToSpace(a){let{invite:i}=await t({group:a.groupHandle,identity:a.identity,recipientDID:a.recipientDID,permission:a.permission}),{commitMessage:r,welcomeMessage:c,newGroup:s}=await e(a.groupHandle,a.recipientKeyPackage);await this.#i(a.stores,a.spaceID,s);let l=await d(a.stores);return await l.addSpaceMember({space_id:a.spaceID,member_did:a.recipientDID,role:"admin"===a.permission?"admin":"member"}),{invite:i,welcomeMessage:c,commitMessage:r,updatedGroupHandle:s}}async joinSpace(e){let{group:a}=await i({identity:e.identity,invite:e.invite,welcome:e.welcomeMessage,keyPackageBundle:e.keyPackageBundle,ratchetTree:e.ratchetTree});await this.#i(e.stores,e.spaceID,a);let t=await d(e.stores);return await t.addSpaceMember({space_id:e.spaceID,member_did:e.identity.id,role:"member"}),{groupHandle:a}}async removeMember(e){let{commitMessage:a,newGroup:t}=await r(e.groupHandle,e.leafIndex);await this.#i(e.stores,e.spaceID,t);let i=await d(e.stores);return await i.removeSpaceMember(e.spaceID,e.memberDID),{commitMessage:a,updatedGroupHandle:t}}async removeSpaceMember(e){let a=e.groupHandle.findMemberLeafIndex(e.memberDID);if(null==a)throw Error(`Member ${e.memberDID} not found in MLS group`);let{commitMessage:t,newGroup:i}=await r(e.groupHandle,a);await this.#i(e.stores,e.spaceID,i);let c=await d(e.stores);return await c.removeSpaceMember(e.spaceID,e.memberDID),{commitMessage:t,updatedGroupHandle:i}}async leaveSpace(e){let a=await d(e.stores);await a.deleteMLSState(e.spaceID,this.#e),await a.removeSpaceMember(e.spaceID,e.identity.id)}async loadSpace(e,a){let t=await d(e),i=await t.getMLSState(a,this.#e);if(null!=i)return{epoch:i.epoch,credential:i.credential}}async createCircle(e){let a=this.#a(),t=c.serialize(this.#t.now()),i=e.catalogIDs??[],r=await d(e.stores);return await r.createCircle({id:a,space_id:e.spaceID,name:e.name,description:e.description??"",catalog_ids:i,hlc:t}),{circleID:a,broadcast:{type:"circle:create",circle:{id:a,spaceID:e.spaceID,name:e.name,description:e.description??"",catalogIDs:i,hlc:t}}}}async updateCircle(e){let a=c.serialize(this.#t.now()),t=[],i=await d(e.stores);if(null!=e.update.catalogIDs){let a=await i.getCircle(e.circleID),r=new Set(null!=a?a.catalog_ids:[]),c=e.update.catalogIDs.filter(e=>!r.has(e)),d=await s(e.stores);for(let e of(await Promise.all(c.map(e=>d.getCatalog(e)))))null!=e&&t.push({type:"catalog:create",catalog:{id:e.id,ownerDID:e.owner_did,name:e.name,description:e.description,filterCriteria:e.filter_criteria,hlc:e.hlc}})}await i.updateCircle(e.circleID,{name:e.update.name,description:e.update.description,catalog_ids:e.update.catalogIDs,hlc:a});let r={type:"circle:update",circleID:e.circleID,update:{...e.update,hlc:a}};return{broadcast:r,broadcasts:[r,...t]}}async deleteCircle(e){let a=c.serialize(this.#t.now()),t=await d(e.stores);return await t.deleteCircle(e.circleID),{broadcast:{type:"circle:delete",circleID:e.circleID,hlc:a}}}async addCircleMember(e){let a=await d(e.stores);if(!await a.isSpaceMember(e.spaceID,e.memberDID))throw Error(`${e.memberDID} is not a member of space ${e.spaceID}`);let t=c.serialize(this.#t.now());return await a.addCircleMember({circle_id:e.circleID,member_did:e.memberDID,role:e.role,hlc:t}),{broadcast:{type:"member:add",member:{circleID:e.circleID,memberDID:e.memberDID,role:e.role,hlc:t}}}}async removeCircleMember(e){let a=c.serialize(this.#t.now()),t=await d(e.stores);return await t.removeCircleMember(e.circleID,e.memberDID),{broadcast:{type:"member:remove",circleID:e.circleID,memberDID:e.memberDID,hlc:a}}}}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export async function resolveCatalogSyncScopes(
|
|
1
|
+
import{getGraphStore as e}from"@kubun/store-graph";import{getSpaceStore as r}from"@kubun/store-space";export async function resolveCatalogSyncScopes(l,t,o){let[a,i]=await Promise.all([e(l),r(l)]),s=new Set,f=new Set,n=!1;for(let e of t){let r=await a.resolveCatalogScope(e);if(null!=r.models)for(let e of r.models)s.add(e);if(null!=r.owners)for(let e of(n=!0,r.owners))f.add(e);let l=await a.getCatalog(e);if(null!=l){let e=l.filter_criteria;if(null!=e.circles&&e.circles.length>0)for(let r of(n=!0,e.circles))for(let e of(await i.listCircleMembers(r)))f.add(e.member_did)}}let d=Array.from(s),m=n?Array.from(f):void 0,c=new Set(o??[]),u=new Set,w=new Set;return await Promise.all(d.map(async e=>{let r=await a.getClusterForModel(e);null!=r&&(u.add(r),c.has(e)||w.add(r))})),{modelIDs:d,owners:m,requiredClusterIDs:Array.from(u),missingClusterIDs:Array.from(w)}}
|
package/lib/sync/handlers.d.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import type { KubunDB } from '@kubun/db';
|
|
2
2
|
import type { Logger } from '@kubun/logger';
|
|
3
|
+
import type { SyncDirection } from '../protocol.js';
|
|
4
|
+
import type { PeerRegistry } from './peer-registry.js';
|
|
3
5
|
/**
|
|
4
6
|
* Check if delegation tokens grant the viewer read access to a user's documents.
|
|
5
7
|
*
|
|
@@ -12,7 +14,15 @@ export declare function checkSyncDelegation(viewerDID: string, ownerDID: string,
|
|
|
12
14
|
export type CreateSyncHandlersParams = {
|
|
13
15
|
db: KubunDB;
|
|
14
16
|
logger: Logger;
|
|
17
|
+
peerRegistry: PeerRegistry;
|
|
15
18
|
};
|
|
19
|
+
/**
|
|
20
|
+
* Resolve the effective sync direction given what the initiator requested
|
|
21
|
+
* and what the responder's per-peer policy allows.
|
|
22
|
+
* If allowed is 'both' (or unset), the request passes through.
|
|
23
|
+
* Otherwise the responder's policy wins.
|
|
24
|
+
*/
|
|
25
|
+
export declare function negotiateDirection(requested: SyncDirection, allowed: SyncDirection): SyncDirection;
|
|
16
26
|
/**
|
|
17
27
|
* Create sync protocol handlers served by plugin-p2p's own Enkaku server.
|
|
18
28
|
*/
|
package/lib/sync/handlers.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{checkCapability as e}from"@enkaku/capability";import{
|
|
1
|
+
import{checkCapability as e}from"@enkaku/capability";import{getGraphStore as t}from"@kubun/store-graph";import{resolveCatalogSyncScopes as n}from"./catalog-scope.js";import{createMerkleSyncChannelHandler as o}from"./merkle-channel.js";export async function checkSyncDelegation(t,n,o){if(!o||0===o.length)return!1;let r=["*","urn:kubun:user:*",`urn:kubun:user:${n}`],l="document/read";for(let a of r)try{return await e({act:l,res:a},{iss:t,sub:n,cap:o}),!0}catch{}for(let a of o)for(let o of r)try{return await e({act:l,res:o},{iss:t,sub:n,cap:a}),!0}catch{}return!1}export function negotiateDirection(e,t){return"both"===t?e:"both"===e?t:e===t?e:t}export function createSyncHandlers(e){let{db:r,logger:l,peerRegistry:a}=e;return{"sync/negotiate":async e=>{let o,{scopes:s,delegationTokens:i,catalogIDs:c,knownModelIDs:u,direction:g}=e.param,f=e.message.payload,d=f.sub||f.iss;l.debug("sync/negotiate requested",{scopes:s,catalogIDs:c,viewerDID:d});let p=[],D=await t(r);if(null!=c&&c.length>0){let e=await n(r,c,u);if(e.modelIDs.length>0)if(null!=e.owners)for(let t of e.modelIDs)for(let n of e.owners){let e=d===n,o=!e&&await checkSyncDelegation(d,n,i);(e||o)&&p.push({modelID:t,ownerDID:n})}else for(let t of e.modelIDs)for(let e of(await D.getDistinctOwnersForModel(t))){let n=d===e,o=!n&&await checkSyncDelegation(d,e,i);(n||o)&&p.push({modelID:t,ownerDID:e})}e.missingClusterIDs.length>0&&(o=await D.getClusters(e.missingClusterIDs))}if(null!=s)for(let e of s){let t=d===e.ownerDID,n=!t&&await checkSyncDelegation(d,e.ownerDID,i);t||n?p.push(e):l.debug("sync/negotiate: scope rejected",{scope:e,viewerDID:d})}let h=new Set,m=p.filter(e=>{let t=`${e.modelID}:${e.ownerDID}`;return!h.has(t)&&(h.add(t),!0)});l.info("sync/negotiate completed",{requested:(s?.length??0)+(c?.length??0),accepted:m.length,missingClusters:null!=o?Object.keys(o).length:0});let w=g??"pull",y=await a.getPeer({peerDID:d,stores:r}),b=y?.allowedDirection??"both",k=negotiateDirection(w,b);return k!==w&&l.info("sync/negotiate: direction downgraded",{requested:w,allowed:b,agreed:k}),{direction:k,acceptedScopes:m,excludedDocumentIDs:[],...null!=o&&{missingClusters:o}}},"sync/merkle-sync":o({db:r,logger:l})}}
|
package/lib/sync/merkle-apply.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{asType as t,createValidator as a}from"@enkaku/schema";import{verifyToken as i}from"@enkaku/token";import{computeMutationHash as o}from"@kubun/engine";import{DocumentID as n}from"@kubun/id";import{applyMutation as u}from"@kubun/mutation";import{documentMutation as e}from"@kubun/protocol";let
|
|
1
|
+
import{asType as t,createValidator as a}from"@enkaku/schema";import{verifyToken as i}from"@enkaku/token";import{computeMutationHash as o}from"@kubun/engine";import{DocumentID as n}from"@kubun/id";import{applyMutation as u}from"@kubun/mutation";import{documentMutation as e}from"@kubun/protocol";import{getGraphStore as r}from"@kubun/store-graph";let m=a(e);export async function applySyncMutations(a){let{db:e,mutationJWTs:s}=a,d=await r(e),l=0,c=0,h=0,p=0,_={store:d,validators:{}};for(let a of s){let e,r=o(a);if(await d.hasMutationHash(r)){p++;continue}try{let o=await i(a);e=t(m,o.payload)}catch{c++;continue}let s=e.sub,f=n.fromString(s).model.toString(),w=n.fromString(s);if(null==await d.getDocument(w)&&"change"===e.typ){await d.insertMutationLogEntry({mutation_hash:r,model_id:f,document_id:s,author_did:e.iss,hlc:e.hlc,mutation_jwt:a,status:"pending"}),h++;continue}try{if(await u(_,e),await d.insertMutationLogEntry({mutation_hash:r,model_id:f,document_id:s,author_did:e.iss,hlc:e.hlc,mutation_jwt:a,status:"applied"}),l++,"set"===e.typ)for(let a of(await d.getPendingMutations(s)))try{let o=await i(a.mutation_jwt),n=t(m,o.payload);await u(_,n),await d.updateMutationStatus(a.mutation_hash,"applied"),l++}catch{await d.updateMutationStatus(a.mutation_hash,"rejected"),c++}}catch{await d.insertMutationLogEntry({mutation_hash:r,model_id:f,document_id:s,author_did:e.iss,hlc:e.hlc,mutation_jwt:a,status:"rejected"}),c++}}return{applied:l,rejected:c,pending:h,skipped:p}}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { KubunDB } from '@kubun/db';
|
|
2
|
+
import type { Logger } from '@kubun/logger';
|
|
3
|
+
import type { SyncMerkleSyncParams, SyncMerkleSyncReceive, SyncMerkleSyncResult, SyncMerkleSyncSend } from '../protocol.js';
|
|
4
|
+
/**
|
|
5
|
+
* Minimal handler context shape for sync/merkle-sync channel.
|
|
6
|
+
* Matches the Enkaku ChannelHandlerContext at runtime.
|
|
7
|
+
*/
|
|
8
|
+
type MerkleSyncChannelContext = {
|
|
9
|
+
param: SyncMerkleSyncParams;
|
|
10
|
+
readable: ReadableStream<SyncMerkleSyncSend>;
|
|
11
|
+
writable: WritableStream<SyncMerkleSyncReceive>;
|
|
12
|
+
};
|
|
13
|
+
export type CreateMerkleSyncChannelHandlerParams = {
|
|
14
|
+
db: KubunDB;
|
|
15
|
+
logger: Logger;
|
|
16
|
+
};
|
|
17
|
+
export declare function createMerkleSyncChannelHandler(params: CreateMerkleSyncChannelHandlerParams): (ctx: MerkleSyncChannelContext) => Promise<SyncMerkleSyncResult>;
|
|
18
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{getGraphStore as e}from"@kubun/store-graph";import{applySyncMutations as t}from"./merkle-apply.js";import{buildMerkleTree as r,findDivergentBuckets as a,getTimeBuckets as n,SYNC_BATCH_SIZE as l}from"./merkle-tree.js";export function createMerkleSyncChannelHandler(i){let{db:o,logger:s}=i;return async i=>{let{scopes:c,excludedDocumentIDs:m,tree:u,direction:p}=i.param,y=i.writable.getWriter();s.info("sync/merkle-sync channel started",{scopes:c,excludedDocumentIDs:m,direction:p});let w=0,f=0,k=0;try{let h=await e(o),d=await h.getDocumentIDsForScope(c,m),g=await h.getMutationLogForDocuments(d),b=r(g);await y.write({type:"tree",tree:b.buckets});let v={root:u.root??"",buckets:u},W=a(b,v);w=W.length;let J=async()=>{if("pull"===p||"both"===p){let e=new Set(W),t=g.filter(t=>{let{minute:r}=n(t.hlc);return e.has(r)});for(let e=0;e<t.length;e+=l){let r=t.slice(e,e+l);await y.write({type:"mutations",mutationJWTs:r.map(e=>e.mutation_jwt)}),f+=r.length}await y.write({type:"complete"})}else await y.write({type:"complete"})},S=async()=>{let e=[],t=i.readable.getReader();try{for(;;){let{done:r,value:a}=await t.read();if(r)break;if("mutations"===a.type&&null!=a.mutationJWTs)e.push(...a.mutationJWTs);else if("complete"===a.type)break}}finally{t.releaseLock()}return e},[,T]=await Promise.all([J(),S()]);if(T.length>0){let e=await t({db:o,mutationJWTs:T});k=e.applied+e.pending,s.info("sync/merkle-sync: applied received mutations",{result:e})}}catch(e){return s.error("sync/merkle-sync channel error",{error:e}),{success:!1,divergentBuckets:0,mutationsSent:0,mutationsReceived:0}}finally{try{await y.close()}catch{}}return s.info("sync/merkle-sync channel completed",{divergentBuckets:w,mutationsSent:f,mutationsReceived:k}),{success:!0,divergentBuckets:w,mutationsSent:f,mutationsReceived:k}}}
|
|
@@ -1,8 +1,10 @@
|
|
|
1
|
-
import type { MutationLogEntry } from '@kubun/
|
|
1
|
+
import type { MutationLogEntry } from '@kubun/store-graph';
|
|
2
2
|
export type MerkleTree = {
|
|
3
3
|
root: string;
|
|
4
4
|
buckets: Record<string, string>;
|
|
5
5
|
};
|
|
6
|
+
/** Maximum number of mutation JWTs to send in a single channel message. */
|
|
7
|
+
export declare const SYNC_BATCH_SIZE = 1000;
|
|
6
8
|
export declare function getTimeBuckets(hlc: string): {
|
|
7
9
|
year: string;
|
|
8
10
|
month: string;
|
package/lib/sync/merkle-tree.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{blake3 as t}from"@noble/hashes/blake3.js";let e=[4,7,10,16],o=new TextEncoder;export function getTimeBuckets(t){return{year:t.slice(0,4),month:t.slice(0,7),day:t.slice(0,10),minute:t.slice(0,16)}}function r(e){return Array.from(t(o.encode(e))).map(t=>t.toString(16).padStart(2,"0")).join("")}export function buildMerkleTree(t){if(0===t.length)return{root:"",buckets:{}};let e=new Map;for(let o of t){let{minute:t}=getTimeBuckets(o.hlc),r=e.get(t);null==r&&(r=[],e.set(t,r)),r.push(o)}let o={};for(let[t,n]of e){let e=[...n].sort((t,e)=>t.mutation_hash<e.mutation_hash?-1:+(t.mutation_hash>e.mutation_hash)).map(t=>t.mutation_hash).join("\n");o[t]=r(e)}for(let t of[10,7,4]){let e=10===t?16:7===t?10:7,n=new Map;for(let r of Object.keys(o)){if(r.length!==e)continue;let o=r.slice(0,t),s=n.get(o);null==s&&(s=[],n.set(o,s)),s.push(r)}for(let[t,e]of n){let n=e.sort().map(t=>`${t}\0${o[t]}`).join("\n");o[t]=r(n)}}let n=r(Object.keys(o).filter(t=>4===t.length).sort().map(t=>`${t}\0${o[t]}`).join("\n"));return o.root=n,{root:n,buckets:o}}export function findDivergentBuckets(t,o){if(t.root===o.root)return[];if(""===t.root)return Object.keys(o.buckets).filter(t=>16===t.length).sort();if(""===o.root)return Object.keys(t.buckets).filter(t=>16===t.length).sort();let r=[],n=new Set([...Object.keys(t.buckets),...Object.keys(o.buckets)]);return!function s(l,i){let u=e[i];for(let c of[...n].filter(t=>t.length===u&&(""===l||t.startsWith(l))).sort())t.buckets[c]!==o.buckets[c]&&(i===e.length-1?r.push(c):s(c,i+1))}("",0),r.sort()}
|
|
1
|
+
import{blake3 as t}from"@noble/hashes/blake3.js";export const SYNC_BATCH_SIZE=1e3;let e=[4,7,10,16],o=new TextEncoder;export function getTimeBuckets(t){return{year:t.slice(0,4),month:t.slice(0,7),day:t.slice(0,10),minute:t.slice(0,16)}}function r(e){return Array.from(t(o.encode(e))).map(t=>t.toString(16).padStart(2,"0")).join("")}export function buildMerkleTree(t){if(0===t.length)return{root:"",buckets:{}};let e=new Map;for(let o of t){let{minute:t}=getTimeBuckets(o.hlc),r=e.get(t);null==r&&(r=[],e.set(t,r)),r.push(o)}let o={};for(let[t,n]of e){let e=[...n].sort((t,e)=>t.mutation_hash<e.mutation_hash?-1:+(t.mutation_hash>e.mutation_hash)).map(t=>t.mutation_hash).join("\n");o[t]=r(e)}for(let t of[10,7,4]){let e=10===t?16:7===t?10:7,n=new Map;for(let r of Object.keys(o)){if(r.length!==e)continue;let o=r.slice(0,t),s=n.get(o);null==s&&(s=[],n.set(o,s)),s.push(r)}for(let[t,e]of n){let n=e.sort().map(t=>`${t}\0${o[t]}`).join("\n");o[t]=r(n)}}let n=r(Object.keys(o).filter(t=>4===t.length).sort().map(t=>`${t}\0${o[t]}`).join("\n"));return o.root=n,{root:n,buckets:o}}export function findDivergentBuckets(t,o){if(t.root===o.root)return[];if(""===t.root)return Object.keys(o.buckets).filter(t=>16===t.length).sort();if(""===o.root)return Object.keys(t.buckets).filter(t=>16===t.length).sort();let r=[],n=new Set([...Object.keys(t.buckets),...Object.keys(o.buckets)]);return!function s(l,i){let u=e[i];for(let c of[...n].filter(t=>t.length===u&&(""===l||t.startsWith(l))).sort())t.buckets[c]!==o.buckets[c]&&(i===e.length-1?r.push(c):s(c,i+1))}("",0),r.sort()}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { Runtime } from '@enkaku/runtime';
|
|
2
|
-
import type {
|
|
2
|
+
import type { StoreProvider } from '@kubun/db';
|
|
3
|
+
import type { SyncDirection } from '../protocol.js';
|
|
3
4
|
export type PeerConfig = {
|
|
4
5
|
peerDID: string;
|
|
5
6
|
endpoint: string;
|
|
@@ -9,6 +10,7 @@ export type PeerConfig = {
|
|
|
9
10
|
};
|
|
10
11
|
priority: string;
|
|
11
12
|
trustLevel: 'trusted' | 'restricted';
|
|
13
|
+
allowedDirection?: SyncDirection;
|
|
12
14
|
};
|
|
13
15
|
export type PeerConfigWithID = PeerConfig & {
|
|
14
16
|
id: string;
|
|
@@ -16,16 +18,33 @@ export type PeerConfigWithID = PeerConfig & {
|
|
|
16
18
|
updatedAt: number;
|
|
17
19
|
};
|
|
18
20
|
export type PeerRegistryParams = {
|
|
19
|
-
db: KubunDB;
|
|
20
21
|
runtime: Runtime;
|
|
21
22
|
};
|
|
22
23
|
export declare class PeerRegistry {
|
|
23
24
|
#private;
|
|
24
25
|
constructor(params: PeerRegistryParams);
|
|
25
|
-
addPeer(
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
26
|
+
addPeer(params: {
|
|
27
|
+
config: PeerConfig;
|
|
28
|
+
stores: StoreProvider;
|
|
29
|
+
}): Promise<void>;
|
|
30
|
+
getPeer(params: {
|
|
31
|
+
peerDID: string;
|
|
32
|
+
stores: StoreProvider;
|
|
33
|
+
}): Promise<PeerConfigWithID | undefined>;
|
|
34
|
+
listPeers(params: {
|
|
35
|
+
stores: StoreProvider;
|
|
36
|
+
}): Promise<Array<PeerConfigWithID>>;
|
|
37
|
+
updatePeer(params: {
|
|
38
|
+
peerDID: string;
|
|
39
|
+
updates: Partial<PeerConfig>;
|
|
40
|
+
stores: StoreProvider;
|
|
41
|
+
}): Promise<void>;
|
|
42
|
+
removePeer(params: {
|
|
43
|
+
peerDID: string;
|
|
44
|
+
stores: StoreProvider;
|
|
45
|
+
}): Promise<void>;
|
|
46
|
+
isPeerAllowed(params: {
|
|
47
|
+
peerDID: string;
|
|
48
|
+
stores: StoreProvider;
|
|
49
|
+
}): Promise<boolean>;
|
|
31
50
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
function
|
|
1
|
+
import{getSpaceStore as e}from"@kubun/store-space";function t(e){var t,r;let i="string"==typeof(t=e.config)?JSON.parse(t):t??{};return{id:e.id,peerDID:e.peer_did,endpoint:e.endpoint,mode:e.mode,allowedUsers:"string"==typeof(r=e.allowed_users)?JSON.parse(r):r,priority:e.priority,trustLevel:e.trust_level,allowedDirection:i.allowedDirection,createdAt:e.created_at,updatedAt:e.updated_at}}export class PeerRegistry{#e;constructor(e){this.#e=e.runtime}async addPeer(t){let r=await e(t.stores);await r.addSyncPeer({id:this.#e.getRandomID(),peer_did:t.config.peerDID,endpoint:t.config.endpoint,mode:t.config.mode,allowed_users:t.config.allowedUsers,priority:t.config.priority,trust_level:t.config.trustLevel,config:{allowedDirection:t.config.allowedDirection},created_at:Date.now(),updated_at:Date.now()})}async getPeer(r){let i=await e(r.stores),o=await i.getSyncPeer(r.peerDID);if(o)return t(o)}async listPeers(r){let i=await e(r.stores);return(await i.listSyncPeers()).map(e=>t(e))}async updatePeer(t){let r=await this.getPeer({peerDID:t.peerDID,stores:t.stores});if(!r)throw Error(`Peer ${t.peerDID} not found`);let i={...r,...t.updates},o=await e(t.stores);await o.updateSyncPeer(t.peerDID,{endpoint:i.endpoint,mode:i.mode,allowed_users:i.allowedUsers,priority:i.priority,trust_level:i.trustLevel,config:{allowedDirection:i.allowedDirection}})}async removePeer(t){let r=await e(t.stores);await r.removeSyncPeer(t.peerDID)}async isPeerAllowed(e){return void 0!==await this.getPeer({peerDID:e.peerDID,stores:e.stores})}}
|
|
@@ -3,7 +3,7 @@ import type { ClientTransportOf } from '@enkaku/protocol';
|
|
|
3
3
|
import type { Runtime } from '@enkaku/runtime';
|
|
4
4
|
import type { SigningIdentity } from '@enkaku/token';
|
|
5
5
|
import type { Logger } from '@kubun/logger';
|
|
6
|
-
import type { SyncProtocol } from '../protocol.js';
|
|
6
|
+
import type { SyncDirection, SyncProtocol } from '../protocol.js';
|
|
7
7
|
/**
|
|
8
8
|
* Provider for sync-specific transport to a peer's sync server.
|
|
9
9
|
* The p2p plugin exposes this via `createSyncTransport()`.
|
|
@@ -21,14 +21,6 @@ export type SyncScope = {
|
|
|
21
21
|
modelID: string;
|
|
22
22
|
ownerDID: string;
|
|
23
23
|
};
|
|
24
|
-
export type MerkleSyncMessage = {
|
|
25
|
-
type: 'mutations';
|
|
26
|
-
mutationJWTs: Array<string>;
|
|
27
|
-
} | {
|
|
28
|
-
type: 'complete';
|
|
29
|
-
divergentBuckets?: number;
|
|
30
|
-
totalMutations?: number;
|
|
31
|
-
};
|
|
32
24
|
/**
|
|
33
25
|
* Client for connecting to peer servers for sync operations.
|
|
34
26
|
* Supports both direct (in-process) and HTTP transports.
|
|
@@ -48,21 +40,31 @@ export declare class SyncClient {
|
|
|
48
40
|
/**
|
|
49
41
|
* Negotiate sync scopes with peer.
|
|
50
42
|
*/
|
|
51
|
-
negotiate(client: Client<SyncProtocol>, scopes: Array<SyncScope>, delegationTokens?: Array<string>, knownModelIDs?: Array<string
|
|
43
|
+
negotiate(client: Client<SyncProtocol>, scopes: Array<SyncScope>, delegationTokens?: Array<string>, knownModelIDs?: Array<string>, direction?: SyncDirection): Promise<{
|
|
52
44
|
acceptedScopes: Array<SyncScope>;
|
|
53
45
|
excludedDocumentIDs: Array<string>;
|
|
54
46
|
missingClusters?: Record<string, unknown>;
|
|
47
|
+
direction?: SyncDirection;
|
|
55
48
|
}>;
|
|
56
49
|
/**
|
|
57
|
-
* Perform Merkle tree-based sync with peer.
|
|
58
|
-
* Sends local tree, receives
|
|
50
|
+
* Perform Merkle tree-based sync with peer using bidirectional channel.
|
|
51
|
+
* Sends local tree and divergent mutations, receives remote mutations.
|
|
59
52
|
*/
|
|
60
53
|
merkleSync(client: Client<SyncProtocol>, params: {
|
|
61
54
|
scopes: Array<SyncScope>;
|
|
62
55
|
excludedDocumentIDs: Array<string>;
|
|
63
|
-
|
|
56
|
+
localTree: {
|
|
57
|
+
root: string;
|
|
58
|
+
buckets: Record<string, string>;
|
|
59
|
+
};
|
|
60
|
+
direction: SyncDirection;
|
|
61
|
+
localEntries: Array<{
|
|
62
|
+
hlc: string;
|
|
63
|
+
mutation_jwt: string;
|
|
64
|
+
}>;
|
|
64
65
|
}): Promise<{
|
|
65
66
|
mutationJWTs: Array<string>;
|
|
66
67
|
divergentBuckets: number;
|
|
68
|
+
mutationsSent: number;
|
|
67
69
|
}>;
|
|
68
70
|
}
|
package/lib/sync/sync-client.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{Client as e}from"@enkaku/client";import{ClientTransport as t}from"@enkaku/http-client-transport";import{
|
|
1
|
+
import{Client as e}from"@enkaku/client";import{ClientTransport as t}from"@enkaku/http-client-transport";import{findDivergentBuckets as r,getTimeBuckets as n,SYNC_BATCH_SIZE as i}from"./merkle-tree.js";export class SyncClient{#e;#t;#r;#n;constructor(e){this.#e=e.runtime,this.#t=e.identity,this.#r=e.logger,this.#n=e.serverResolver}async connect(e,t){if(e.startsWith("direct://"))return this.#i(e);if(e.startsWith("http://")||e.startsWith("https://"))return this.#s(e,t);throw Error(`Unsupported endpoint scheme: ${e}`)}#s(r,n){let i=new t({url:r,fetch:this.#e.fetch});return new e({runtime:this.#e,identity:this.#t,logger:this.#r.getChild("sync-client"),serverID:n,transport:i})}#i(t){let r=t.replace("direct://","");if(!this.#n)throw Error("Server resolver not configured for direct transport");let n=this.#n(r);if(!n)throw Error(`Server not found: ${r}`);let i=n.createSyncTransport();return new e({runtime:this.#e,identity:this.#t,logger:this.#r.getChild("sync-client"),serverID:r,transport:i})}async negotiate(e,t,r=[],n,i){this.#r.debug("Negotiate: sending {scopeCount} scopes",{scopeCount:t.length});let s=await e.request("sync/negotiate",{param:{scopes:t,delegationTokens:r,knownModelIDs:n,direction:i}});return this.#r.debug("Negotiate: accepted {acceptedCount} scopes, excluded {excludedCount} docs",{acceptedCount:s.acceptedScopes.length,excludedCount:s.excludedDocumentIDs.length}),s}async merkleSync(e,t){let{scopes:s,excludedDocumentIDs:o,localTree:c,direction:l,localEntries:a}=t;this.#r.debug("MerkleSync: creating channel with {buckets} local buckets",{buckets:Object.keys(c.buckets).length});let u=e.createChannel("sync/merkle-sync",{param:{scopes:s,excludedDocumentIDs:o,tree:c.buckets,direction:l}}),h=u.readable.getReader(),d=await h.read();if(d.done)throw Error("MerkleSync: channel closed before receiving remote tree");let g=d.value;if("tree"!==g.type)throw Error(`MerkleSync: expected 'tree' message, got '${g.type}'`);let p=r(c,{root:g.tree?.root??"",buckets:g.tree??{}}),m=p.length;this.#r.debug("MerkleSync: found {count} divergent buckets",{count:m});let y=0,f=async()=>{if("push"===l||"both"===l){let e=new Set(p),t=a.filter(t=>{let{minute:r}=n(t.hlc);return e.has(r)});for(let e=0;e<t.length;e+=i){let r=t.slice(e,e+i);await u.send({type:"mutations",mutationJWTs:r.map(e=>e.mutation_jwt)}),y+=r.length}}await u.send({type:"complete"})},k=async()=>{let e=[];for(;;){let{done:t,value:r}=await h.read();if(t)break;if("mutations"===r.type&&null!=r.mutationJWTs)e.push(...r.mutationJWTs);else if("complete"===r.type)break}return h.releaseLock(),e},[,v]=await Promise.all([f(),k()]),w=await u;if(null!=w&&!w.success)throw Error("MerkleSync: responder reported failure");return this.#r.debug("MerkleSync: completed with {received} received, {sent} sent",{received:v.length,sent:y}),{mutationJWTs:v,divergentBuckets:m,mutationsSent:y}}}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import type { Runtime } from '@enkaku/runtime';
|
|
2
2
|
import { type Identity, type SigningIdentity } from '@enkaku/token';
|
|
3
|
-
import type {
|
|
3
|
+
import type { StoreProvider } from '@kubun/db';
|
|
4
4
|
import type { Logger } from '@kubun/logger';
|
|
5
|
-
import
|
|
5
|
+
import type { SyncDirection } from '../protocol.js';
|
|
6
|
+
import { type PeerConfig, type PeerConfigWithID, PeerRegistry } from './peer-registry.js';
|
|
6
7
|
import { type SyncScope, type SyncTransportProvider } from './sync-client.js';
|
|
7
8
|
export type SyncSessionInfo = {
|
|
8
9
|
peerID: string;
|
|
@@ -23,19 +24,24 @@ export type SyncEvent = {
|
|
|
23
24
|
reason?: string;
|
|
24
25
|
};
|
|
25
26
|
export type SyncManagerParams = {
|
|
26
|
-
db: KubunDB;
|
|
27
27
|
deployClusters?: (clusters: Record<string, unknown>) => Promise<void>;
|
|
28
28
|
runtime: Runtime;
|
|
29
29
|
identity: Identity;
|
|
30
30
|
logger: Logger;
|
|
31
31
|
serverResolver?: (serverID: string) => SyncTransportProvider | undefined;
|
|
32
32
|
};
|
|
33
|
-
export type
|
|
33
|
+
export type MerkleSyncParams = {
|
|
34
|
+
peerDID: string;
|
|
35
|
+
scopes: Array<SyncScope>;
|
|
36
|
+
stores: StoreProvider;
|
|
37
|
+
delegationTokens?: Array<string>;
|
|
34
38
|
knownModelIDs?: Array<string>;
|
|
39
|
+
direction?: SyncDirection;
|
|
35
40
|
};
|
|
36
41
|
export declare class SyncManager {
|
|
37
42
|
#private;
|
|
38
43
|
constructor(params: SyncManagerParams);
|
|
44
|
+
get peerRegistry(): PeerRegistry;
|
|
39
45
|
/**
|
|
40
46
|
* Configure the signing identity used for sync operations.
|
|
41
47
|
*/
|
|
@@ -44,18 +50,37 @@ export declare class SyncManager {
|
|
|
44
50
|
* Configure the server resolver for direct transport connections.
|
|
45
51
|
*/
|
|
46
52
|
setServerResolver(resolver: (serverID: string) => SyncTransportProvider | undefined): void;
|
|
47
|
-
addPeer(
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
+
addPeer(params: {
|
|
54
|
+
config: PeerConfig;
|
|
55
|
+
stores: StoreProvider;
|
|
56
|
+
}): Promise<void>;
|
|
57
|
+
removePeer(params: {
|
|
58
|
+
peerDID: string;
|
|
59
|
+
stores: StoreProvider;
|
|
60
|
+
}): Promise<void>;
|
|
61
|
+
updatePeerConfig(params: {
|
|
62
|
+
peerDID: string;
|
|
63
|
+
updates: Partial<PeerConfig>;
|
|
64
|
+
stores: StoreProvider;
|
|
65
|
+
}): Promise<void>;
|
|
66
|
+
listPeers(params: {
|
|
67
|
+
stores: StoreProvider;
|
|
68
|
+
}): Promise<Array<PeerConfigWithID>>;
|
|
69
|
+
getPeer(params: {
|
|
70
|
+
peerDID: string;
|
|
71
|
+
stores: StoreProvider;
|
|
72
|
+
}): Promise<PeerConfigWithID | undefined>;
|
|
73
|
+
merkleSyncWithPeer(params: MerkleSyncParams): Promise<{
|
|
53
74
|
sessionID: string;
|
|
54
75
|
divergentBuckets: number;
|
|
55
76
|
messagesReceived: number;
|
|
77
|
+
messagesSent: number;
|
|
56
78
|
missingClusters?: Record<string, unknown>;
|
|
57
79
|
}>;
|
|
58
|
-
getStatus(
|
|
80
|
+
getStatus(params: {
|
|
81
|
+
peerDID?: string;
|
|
82
|
+
stores: StoreProvider;
|
|
83
|
+
}): SyncStatus;
|
|
59
84
|
onSyncEvent(callback: (event: SyncEvent) => void): () => void;
|
|
60
85
|
dispose(): Promise<void>;
|
|
61
86
|
}
|
package/lib/sync/sync-manager.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{EventEmitter as e}from"@enkaku/event";import{isSigningIdentity as t}from"@enkaku/token";import{applySyncMutations as s}from"./merkle-apply.js";import{buildMerkleTree as
|
|
1
|
+
import{EventEmitter as e}from"@enkaku/event";import{isSigningIdentity as t}from"@enkaku/token";import{getGraphStore as r}from"@kubun/store-graph";import{applySyncMutations as s}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;#r;#s;#i=new e;#n=new Map;#o=new Map;#l;#a;constructor(e){this.#e=e.deployClusters,this.#t=e.runtime,this.#l=e.identity,this.#r=e.logger,this.#s=new n({runtime:e.runtime}),this.#a=e.serverResolver}get peerRegistry(){return this.#s}setIdentity(e){this.#l=e}setServerResolver(e){this.#a=e}async addPeer(e){await this.#s.addPeer(e),this.#r.info("Peer added",{peerDID:e.config.peerDID})}async removePeer(e){await this.#s.removePeer(e),this.#r.info("Peer removed",{peerDID:e.peerDID})}async updatePeerConfig(e){await this.#s.updatePeer(e),this.#r.info("Peer updated",{peerDID:e.peerDID})}async listPeers(e){return this.#s.listPeers(e)}async getPeer(e){return this.#s.getPeer(e)}async merkleSyncWithPeer(e){let n,{peerDID:l,scopes:a,delegationTokens:c=[],knownModelIDs:g,direction:m}=e;this.#r.info("Starting Merkle sync with peer",{peerDID:l,scopes:a});let p=await this.#s.getPeer({peerDID:l,stores:e.stores});if(!p)throw Error(`Peer ${l} not found`);let y=`merkle-sync-${l}-${Date.now()}`,u={peerID:l,startTime:Date.now(),documentsAttempted:0,documentsCompleted:0};this.#n.set(y,u),this.#c({type:"started",peerID:l,timestamp:Date.now()});try{let d=this.#l;if(!t(d))throw Error("Signing identity required for Merkle sync");let h=new o({runtime:this.#t,identity:d,logger:this.#r,serverResolver:this.#a});n=await h.connect(p.endpoint,l);let v=m??"pull";this.#r.info("Negotiating sync scopes",{scopes:a,direction:v});let{acceptedScopes:f,excludedDocumentIDs:D,missingClusters:w,direction:k}=await h.negotiate(n,a,c,g,v);if(0===f.length)return this.#r.info("No scopes accepted by peer"),this.#o.set(l,Date.now()),this.#c({type:"completed",peerID:l,timestamp:Date.now()}),this.#n.delete(y),{sessionID:y,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 r(e.stores),P=await S.getDocumentIDsForScope(f,D),R=await S.getMutationLogForDocuments(P),I=i(R),C=k??v;this.#r.info("Requesting Merkle sync from peer",{localTreeBuckets:Object.keys(I.buckets).length,direction:C});let B=await h.merkleSync(n,{scopes:f,excludedDocumentIDs:D,localTree:I,direction:C,localEntries:R});return B.mutationJWTs.length>0&&(this.#r.info("Applying sync mutations",{mutations:B.mutationJWTs.length}),u.documentsCompleted=(await s({db:e.stores,mutationJWTs:B.mutationJWTs})).applied),this.#o.set(l,Date.now()),this.#c({type:"completed",peerID:l,timestamp:Date.now()}),this.#r.info("Merkle sync completed",{divergentBuckets:B.divergentBuckets,mutationsReceived:B.mutationJWTs.length}),{sessionID:y,divergentBuckets:B.divergentBuckets,messagesReceived:B.mutationJWTs.length,messagesSent:B.mutationsSent,missingClusters:w}}catch(e){throw this.#r.error("Merkle sync failed",{peerDID:l,error:e}),this.#c({type:"error",peerID:l,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.#n.delete(y)}}getStatus(e){let{peerDID:t}=e,r=Array.from(this.#n.values()).filter(e=>!t||e.peerID===t).map(e=>({peerID:e.peerID,startTime:e.startTime,documentsAttempted:e.documentsAttempted,documentsCompleted:e.documentsCompleted})),s={};for(let[e,r]of this.#o.entries())t&&e!==t||(s[e]=r);return{activeSessions:r,lastSyncByPeer:s}}onSyncEvent(e){return this.#i.on("sync",e)}#c(e){this.#i.emit("sync",e).catch(e=>{this.#r.error("Error in sync event listener",{error:e})})}async dispose(){this.#n.clear(),this.#o.clear()}}
|
package/lib/types.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ClientTransportOf } from '@enkaku/protocol';
|
|
2
|
-
import type { SyncProtocol } from './protocol.js';
|
|
2
|
+
import type { SyncDirection, SyncProtocol } from './protocol.js';
|
|
3
3
|
import type { PeerConfig, PeerConfigWithID } from './sync/peer-registry.js';
|
|
4
4
|
import type { SyncScope } from './sync/sync-client.js';
|
|
5
5
|
import type { SyncEvent, SyncStatus } from './sync/sync-manager.js';
|
|
@@ -127,6 +127,7 @@ export type SyncSessionResult = {
|
|
|
127
127
|
sessionID: string;
|
|
128
128
|
divergentBuckets: number;
|
|
129
129
|
messagesReceived: number;
|
|
130
|
+
messagesSent: number;
|
|
130
131
|
missingClusters?: Record<string, unknown>;
|
|
131
132
|
};
|
|
132
133
|
export type SyncPluginAPI = {
|
|
@@ -140,6 +141,7 @@ export type SyncPluginAPI = {
|
|
|
140
141
|
scopes: Array<SyncScope>;
|
|
141
142
|
delegationTokens?: Array<string>;
|
|
142
143
|
knownModelIDs?: Array<string>;
|
|
144
|
+
direction?: SyncDirection;
|
|
143
145
|
}): Promise<SyncSessionResult>;
|
|
144
146
|
getStatus(peerDID?: string): SyncStatus;
|
|
145
147
|
onSyncEvent(callback: (event: SyncEvent) => void): () => void;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kubun/plugin-p2p",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.1",
|
|
4
4
|
"license": "see LICENSE.md",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
@@ -34,23 +34,25 @@
|
|
|
34
34
|
"graphql": "^16.13.2",
|
|
35
35
|
"kysely": "^0.28.16",
|
|
36
36
|
"@kubun/client": "^0.8.0",
|
|
37
|
-
"@kubun/
|
|
37
|
+
"@kubun/db": "^0.8.1",
|
|
38
|
+
"@kubun/engine": "^0.8.1",
|
|
39
|
+
"@kubun/store-space": "^0.8.0",
|
|
40
|
+
"@kubun/db-adapter": "^0.8.1",
|
|
41
|
+
"@kubun/store-graph": "^0.8.0",
|
|
42
|
+
"@kubun/graphql": "^0.8.1",
|
|
38
43
|
"@kubun/hlc": "^0.8.0",
|
|
39
|
-
"@kubun/db-adapter": "^0.8.0",
|
|
40
|
-
"@kubun/db": "^0.8.0",
|
|
41
|
-
"@kubun/engine": "^0.8.0",
|
|
42
|
-
"@kubun/logger": "^0.8.0",
|
|
43
44
|
"@kubun/id": "^0.8.0",
|
|
44
|
-
"@kubun/
|
|
45
|
+
"@kubun/logger": "^0.8.0",
|
|
46
|
+
"@kubun/mutation": "^0.8.1",
|
|
45
47
|
"@kubun/protocol": "^0.8.0"
|
|
46
48
|
},
|
|
47
49
|
"devDependencies": {
|
|
48
50
|
"get-port": "^7.2.0",
|
|
49
|
-
"@kubun/db-better-sqlite": "^0.8.
|
|
50
|
-
"@kubun/plugin-connector": "^0.8.
|
|
51
|
-
"@kubun/plugin-http": "^0.8.
|
|
52
|
-
"@kubun/
|
|
53
|
-
"@kubun/
|
|
51
|
+
"@kubun/db-better-sqlite": "^0.8.1",
|
|
52
|
+
"@kubun/plugin-connector": "^0.8.1",
|
|
53
|
+
"@kubun/plugin-http": "^0.8.1",
|
|
54
|
+
"@kubun/test-utils": "^0.8.0",
|
|
55
|
+
"@kubun/plugin-rpc": "^0.8.1"
|
|
54
56
|
},
|
|
55
57
|
"scripts": {
|
|
56
58
|
"build:clean": "del lib",
|
package/lib/migrations.d.ts
DELETED
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import type { Adapter } from '@kubun/db-adapter';
|
|
2
|
-
import type { PluginMigrationContext } from '@kubun/engine';
|
|
3
|
-
import type { Migration } from 'kysely';
|
|
4
|
-
export type MigrationContext = {
|
|
5
|
-
types: Adapter['types'];
|
|
6
|
-
functions: Adapter['functions'];
|
|
7
|
-
};
|
|
8
|
-
export declare function createP2PMigrations(ctx: MigrationContext | PluginMigrationContext): Record<string, Migration>;
|
package/lib/migrations.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export function createP2PMigrations(e){let t=e.types,a=e.functions;return{"001_create_p2p_tables":{async up(e){await e.schema.createTable("kubun_spaces").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("hub_urls",t.json,e=>e.defaultTo("[]").notNull()).addColumn("hlc",t.text,e=>e.notNull()).addColumn("created_at",t.timestamp,e=>e.defaultTo(a.now).notNull()).addColumn("updated_at",t.timestamp).execute(),await e.schema.createTable("kubun_space_members").ifNotExists().addColumn("space_id",t.text,e=>e.notNull()).addColumn("member_did",t.text,e=>e.notNull()).addColumn("role",t.text,e=>e.notNull().defaultTo("member")).addColumn("created_at",t.timestamp,e=>e.defaultTo(a.now).notNull()).addColumn("updated_at",t.timestamp).addPrimaryKeyConstraint("pk_space_members",["space_id","member_did"]).execute(),await e.schema.createIndex("idx_space_members_did").ifNotExists().on("kubun_space_members").column("member_did").execute(),await e.schema.createTable("kubun_circles").ifNotExists().addColumn("id",t.text,e=>e.notNull().primaryKey()).addColumn("space_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.now).notNull()).addColumn("updated_at",t.timestamp).execute(),await e.schema.createIndex("idx_circles_space").ifNotExists().on("kubun_circles").column("space_id").execute(),await e.schema.createTable("kubun_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.now).notNull()).addColumn("updated_at",t.timestamp).addPrimaryKeyConstraint("pk_circle_members",["circle_id","member_did"]).execute(),await e.schema.createIndex("idx_circle_members_did").ifNotExists().on("kubun_circle_members").column("member_did").execute(),await e.schema.createTable("kubun_catalogs").ifNotExists().addColumn("id",t.text,e=>e.notNull().primaryKey()).addColumn("owner_did",t.text,e=>e.notNull()).addColumn("name",t.text,e=>e.notNull()).addColumn("description",t.text,e=>e.notNull().defaultTo("")).addColumn("filter_criteria",t.json,e=>e.notNull()).addColumn("hlc",t.text,e=>e.notNull()).addColumn("created_at",t.timestamp,e=>e.defaultTo(a.now).notNull()).addColumn("updated_at",t.timestamp).execute(),await e.schema.createIndex("idx_catalogs_owner").ifNotExists().on("kubun_catalogs").column("owner_did").execute(),await e.schema.createTable("kubun_space_mls_state").ifNotExists().addColumn("space_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.now).notNull()).addPrimaryKeyConstraint("pk_space_mls_state",["space_id","device_id"]).execute(),await e.schema.createTable("kubun_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.now).notNull()).addColumn("updated_at",t.timestamp,e=>e.defaultTo(a.now).notNull()).execute(),await e.schema.createTable("kubun_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.now).notNull()).execute()},async down(e){await e.schema.dropTable("kubun_sync_checkpoints").ifExists().execute(),await e.schema.dropTable("kubun_sync_peers").ifExists().execute(),await e.schema.dropTable("kubun_space_mls_state").ifExists().execute(),await e.schema.dropTable("kubun_catalogs").ifExists().execute(),await e.schema.dropTable("kubun_circle_members").ifExists().execute(),await e.schema.dropTable("kubun_circles").ifExists().execute(),await e.schema.dropTable("kubun_space_members").ifExists().execute(),await e.schema.dropTable("kubun_spaces").ifExists().execute()}}}}
|