@kubun/hub 0.8.0 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/create-relay.d.ts +44 -0
- package/lib/create-relay.js +1 -0
- package/lib/hub-store.js +1 -1
- package/lib/index.d.ts +22 -2
- package/lib/index.js +1 -1
- package/lib/migrations.d.ts +1 -1
- package/lib/observers.d.ts +35 -0
- package/lib/observers.js +1 -0
- package/package.json +19 -17
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { HubStore } from '@enkaku/hub-protocol';
|
|
2
|
+
import type { Identity } from '@enkaku/token';
|
|
3
|
+
import type { Adapter } from '@kubun/db-adapter';
|
|
4
|
+
import { type AdapterInput } from '@kubun/db-provider';
|
|
5
|
+
import { Kysely } from 'kysely';
|
|
6
|
+
import { type HubDatabase } from './hub-store.js';
|
|
7
|
+
import { type RelayObservers } from './observers.js';
|
|
8
|
+
export type HubAuthConfig = {
|
|
9
|
+
mode: 'open';
|
|
10
|
+
} | {
|
|
11
|
+
mode: 'allowlist';
|
|
12
|
+
allowedDIDs?: Array<string>;
|
|
13
|
+
isAllowed?: (did: string) => boolean | Promise<boolean>;
|
|
14
|
+
};
|
|
15
|
+
export type RelayRetentionConfig = {
|
|
16
|
+
olderThanSeconds: number;
|
|
17
|
+
intervalSeconds?: number;
|
|
18
|
+
};
|
|
19
|
+
export type CreateRelayOptions = {
|
|
20
|
+
db: AdapterInput;
|
|
21
|
+
identity?: Identity;
|
|
22
|
+
auth?: HubAuthConfig;
|
|
23
|
+
allowedOrigin?: string | Array<string>;
|
|
24
|
+
retention?: RelayRetentionConfig;
|
|
25
|
+
observers?: RelayObservers;
|
|
26
|
+
};
|
|
27
|
+
export type Relay = {
|
|
28
|
+
fetch: (request: Request) => Promise<Response>;
|
|
29
|
+
hubStore: HubStore;
|
|
30
|
+
pendingDeliveries: () => Promise<number>;
|
|
31
|
+
dispose: () => Promise<void>;
|
|
32
|
+
};
|
|
33
|
+
export type RelayInternalOptions = {
|
|
34
|
+
kysely: Kysely<HubDatabase>;
|
|
35
|
+
adapter: Adapter;
|
|
36
|
+
identity?: Identity;
|
|
37
|
+
auth?: HubAuthConfig;
|
|
38
|
+
allowedOrigin?: string | Array<string>;
|
|
39
|
+
retention?: RelayRetentionConfig;
|
|
40
|
+
observers?: RelayObservers;
|
|
41
|
+
ownsKysely: boolean;
|
|
42
|
+
};
|
|
43
|
+
export declare function createRelayInternal(options: RelayInternalOptions): Promise<Relay>;
|
|
44
|
+
export declare function createRelay(options: CreateRelayOptions): Promise<Relay>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{ServerTransport as e}from"@enkaku/http-server-transport";import{createHandlers as t,HubClientRegistry as r}from"@enkaku/hub-server";import{Server as n}from"@enkaku/server";import{resolveAdapter as o}from"@kubun/db-provider";import{Kysely as i}from"kysely";import{Migrator as a}from"kysely/migration";import{createHubStore as l}from"./hub-store.js";import{createHubMigrations as s}from"./migrations.js";import{decorateHubStore as u}from"./observers.js";export async function createRelayInternal(o){var i;let c,d,{kysely:m,adapter:h,observers:y,ownsKysely:p}=o;if(null!=o.auth&&null==o.identity)throw Error("createRelay: 'identity' is required when 'auth' is set");let f=s({types:h.types,functions:h.functions}),w=new a({db:m,provider:{getMigrations:async()=>f},migrationTableName:"kubun_hub_migration",migrationLockTableName:"kubun_hub_migration_lock"}),{error:g}=await w.migrateToLatest();if(null!=g)throw g;let b=new r,v=l({kysely:m,adapter:h}),k=u(v,y);if(y?.onPurge!=null){let e=y.onPurge;c=v.events.on("purge",t=>{try{e({sequenceCount:t.sequenceIDs.length})}catch{}})}let _=t({registry:b,store:k}),R=new e({allowedOrigin:null==(i=o.allowedOrigin)?["*"]:"string"==typeof i?i.split(",").map(e=>e.trim()):i}),T=function(e){if(null==e)return{};switch(e.mode){case"open":return{"*":{allow:!0}};case"allowlist":{let{allowedDIDs:t,isAllowed:r}=e;if(null!=r)return{"*":{allow:async e=>{let n=e.payload.iss;return!!t?.includes(n)||await r(n)}}};return{"*":{allow:t??[]}}}}}(o.auth),j=new n(o.identity?{identity:o.identity,accessRules:T,handlers:_,transports:[R]}:{handlers:_,transports:[R]});if(y?.onAuthRejected!=null){let e=y.onAuthRejected;j.events.on("handlerError",t=>{if("auth"===t.category)try{e({reason:t.error.message})}catch{}})}if(null!=o.retention){let{olderThanSeconds:e}=o.retention;d=setInterval(()=>{k.purge({olderThan:e}).catch(e=>{if(y?.onError!=null)try{y.onError({stage:"purge",error:e})}catch{}})},Math.max(1,Math.floor(1e3*(o.retention.intervalSeconds??3600))))}return{fetch:e=>R.fetch(e),hubStore:k,pendingDeliveries:async()=>Number((await m.selectFrom("kubun_hub_deliveries").select(({fn:e})=>e.countAll().as("count")).executeTakeFirstOrThrow()).count),dispose:async()=>{null!=d&&(clearInterval(d),d=void 0),null!=c&&(c(),c=void 0),await j.dispose(),p&&await m.destroy()}}}export async function createRelay(e){let t=o(e.db),r=new i({dialect:t.dialect});return await createRelayInternal({kysely:r,adapter:t,identity:e.identity,auth:e.auth,allowedOrigin:e.allowedOrigin,retention:e.retention,observers:e.observers,ownsKysely:!0})}
|
package/lib/hub-store.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{EventEmitter as e}from"@enkaku/event";export function createHubStore(u){let t,
|
|
1
|
+
import{EventEmitter as e}from"@enkaku/event";export function createHubStore(u){let n,t,{adapter:a}=u;async function i(){return null==n&&(n=await u.kysely),n}async function s(){let e=await i();if(null==t){let u=await e.selectFrom("kubun_hub_messages").select(e.fn.max("sequence_id").as("max_id")).executeTakeFirst();t=u?.max_id!=null?Number.parseInt(u.max_id,10):0}return String(++t).padStart(12,"0")}let r=new e;async function _(e){if(0===e.sequenceIDs.length)return;let u=await i();await u.deleteFrom("kubun_hub_deliveries").where("recipient_did","=",e.recipientDID).where("sequence_id","in",e.sequenceIDs).execute();let n=new Set((await u.selectFrom("kubun_hub_deliveries").select("sequence_id").where("sequence_id","in",e.sequenceIDs).execute()).map(e=>e.sequence_id)),t=e.sequenceIDs.filter(e=>!n.has(e));t.length>0&&await u.deleteFrom("kubun_hub_messages").where("sequence_id","in",t).execute()}async function c(e){let u=await i(),n=Date.now()-1e3*e.olderThan,t=(await u.selectFrom("kubun_hub_messages").select("sequence_id").where("stored_at","<=",n).execute()).map(e=>e.sequence_id);return 0===t.length?[]:(await u.deleteFrom("kubun_hub_deliveries").where("sequence_id","in",t).execute(),await u.deleteFrom("kubun_hub_messages").where("sequence_id","in",t).execute(),await r.emit("purge",{sequenceIDs:t}),t)}return{events:r,store:async function(e){let u=await s(),n=Date.now(),t=await i();return await t.insertInto("kubun_hub_messages").values({sequence_id:u,sender_did:e.senderDID,group_id:e.groupID??null,payload:a.encodeBinary(e.payload),stored_at:n}).execute(),e.recipients.length>0&&await t.insertInto("kubun_hub_deliveries").values(e.recipients.map(e=>({sequence_id:u,recipient_did:e}))).execute(),u},fetch:async function(e){null!=e.ack&&e.ack.length>0&&await _({recipientDID:e.recipientDID,sequenceIDs:e.ack});let u=(await i()).selectFrom("kubun_hub_deliveries").innerJoin("kubun_hub_messages","kubun_hub_messages.sequence_id","kubun_hub_deliveries.sequence_id").select(["kubun_hub_messages.sequence_id","kubun_hub_messages.sender_did","kubun_hub_messages.group_id","kubun_hub_messages.payload"]).where("kubun_hub_deliveries.recipient_did","=",e.recipientDID).orderBy("kubun_hub_messages.sequence_id","asc");null!=e.after&&(u=u.where("kubun_hub_messages.sequence_id",">",e.after));let n=e.limit;null!=n&&(u=u.limit(n+1));let t=await u.execute(),a=!1,s=t;null!=n&&t.length>n&&(a=!0,s=t.slice(0,n));let r=s.map(e=>({sequenceID:e.sequence_id,senderDID:e.sender_did,groupID:e.group_id??void 0,payload:e.payload})),c=r.length>0?r[r.length-1].sequenceID:null;return{messages:r,cursor:c,hasMore:a}},ack:_,purge:c,storeKeyPackage:async function(e,u){let n=await i();await n.insertInto("kubun_hub_key_packages").values({owner_did:e,key_package:u}).execute()},fetchKeyPackages:async function(e,u){let n=await i();return(await n.deleteFrom("kubun_hub_key_packages").where("id","in",n.selectFrom("kubun_hub_key_packages").select("id").where("owner_did","=",e).orderBy("id","asc").limit(u??1)).returning("key_package").execute()).map(e=>e.key_package)},setGroupMembers:async function(e,u){let n=await i();await n.deleteFrom("kubun_hub_group_members").where("group_id","=",e).execute(),u.length>0&&await n.insertInto("kubun_hub_group_members").values(u.map(u=>({group_id:e,member_did:u}))).execute()},getGroupMembers:async function(e){let u=await i();return(await u.selectFrom("kubun_hub_group_members").select("member_did").where("group_id","=",e).execute()).map(e=>e.member_did)}}}
|
package/lib/index.d.ts
CHANGED
|
@@ -1,8 +1,13 @@
|
|
|
1
|
+
import type { Identity } from '@enkaku/token';
|
|
1
2
|
import type { Adapter } from '@kubun/db-adapter';
|
|
2
|
-
import {
|
|
3
|
-
import { type
|
|
3
|
+
import type { Kysely } from 'kysely';
|
|
4
|
+
import { type HubAuthConfig, type RelayRetentionConfig } from './create-relay.js';
|
|
5
|
+
import type { HubDatabase } from './hub-store.js';
|
|
6
|
+
import type { RelayObservers } from './observers.js';
|
|
7
|
+
export { type CreateRelayOptions, createRelay, createRelayInternal, type HubAuthConfig, type Relay, type RelayRetentionConfig, } from './create-relay.js';
|
|
4
8
|
export { type CreateHubStoreParams, createHubStore, type HubDatabase } from './hub-store.js';
|
|
5
9
|
export { createHubMigrations, type MigrationContext } from './migrations.js';
|
|
10
|
+
export { decorateHubStore, type RelayObserverAckInfo, type RelayObserverAuthRejectedInfo, type RelayObserverErrorInfo, type RelayObserverErrorStage, type RelayObserverFetchInfo, type RelayObserverPurgeInfo, type RelayObserverStoreInfo, type RelayObservers, } from './observers.js';
|
|
6
11
|
export type HubOptions = {
|
|
7
12
|
/** Kysely instance for the hub database. */
|
|
8
13
|
kysely: Kysely<HubDatabase>;
|
|
@@ -12,6 +17,21 @@ export type HubOptions = {
|
|
|
12
17
|
port?: number;
|
|
13
18
|
/** Allowed CORS origin(s). Default: ['*']. */
|
|
14
19
|
allowedOrigin?: string | Array<string>;
|
|
20
|
+
/**
|
|
21
|
+
* Hub server identity. Required when `auth` is provided — the server
|
|
22
|
+
* signs response tokens and clients pin its DID for TOFU verification.
|
|
23
|
+
*/
|
|
24
|
+
identity?: Identity;
|
|
25
|
+
/**
|
|
26
|
+
* Authentication configuration. When omitted, the hub runs without
|
|
27
|
+
* access control (back-compat). When provided, `identity` must also be
|
|
28
|
+
* provided.
|
|
29
|
+
*/
|
|
30
|
+
auth?: HubAuthConfig;
|
|
31
|
+
/** Optional retention scheduler. */
|
|
32
|
+
retention?: RelayRetentionConfig;
|
|
33
|
+
/** Optional observers for counters / diagnostics. */
|
|
34
|
+
observers?: RelayObservers;
|
|
15
35
|
};
|
|
16
36
|
export type Hub = {
|
|
17
37
|
port: number;
|
package/lib/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{
|
|
1
|
+
import{serve as e}from"@hono/node-server";import t from"get-port";import{Hono as r}from"hono";import{createRelayInternal as o}from"./create-relay.js";export{createRelay,createRelayInternal}from"./create-relay.js";export{createHubStore}from"./hub-store.js";export{createHubMigrations}from"./migrations.js";export{decorateHubStore}from"./observers.js";export async function createHub(a){let s=await o({kysely:a.kysely,adapter:a.adapter,identity:a.identity,auth:a.auth,allowedOrigin:a.allowedOrigin,retention:a.retention,observers:a.observers,ownsKysely:!1}),i=new r;i.get("/health",e=>e.json({status:"ok"})),i.post("*",async e=>s.fetch(e.req.raw)),i.options("*",async e=>s.fetch(e.req.raw));let n=a.port??await t(),c=`http://localhost:${n}`,l=await new Promise(t=>{let r=e({fetch:i.fetch,port:n},()=>t(r))});return{port:n,url:c,dispose:async()=>{await s.dispose(),l.close()}}}
|
package/lib/migrations.d.ts
CHANGED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { HubStore } from '@enkaku/hub-protocol';
|
|
2
|
+
export type RelayObserverStoreInfo = {
|
|
3
|
+
senderDID: string;
|
|
4
|
+
groupID?: string;
|
|
5
|
+
recipientCount: number;
|
|
6
|
+
sequenceID: string;
|
|
7
|
+
};
|
|
8
|
+
export type RelayObserverFetchInfo = {
|
|
9
|
+
recipientDID: string;
|
|
10
|
+
messageCount: number;
|
|
11
|
+
};
|
|
12
|
+
export type RelayObserverAckInfo = {
|
|
13
|
+
recipientDID: string;
|
|
14
|
+
sequenceCount: number;
|
|
15
|
+
};
|
|
16
|
+
export type RelayObserverPurgeInfo = {
|
|
17
|
+
sequenceCount: number;
|
|
18
|
+
};
|
|
19
|
+
export type RelayObserverAuthRejectedInfo = {
|
|
20
|
+
reason: string;
|
|
21
|
+
};
|
|
22
|
+
export type RelayObserverErrorStage = 'store' | 'fetch' | 'ack' | 'purge';
|
|
23
|
+
export type RelayObserverErrorInfo = {
|
|
24
|
+
stage: RelayObserverErrorStage;
|
|
25
|
+
error: unknown;
|
|
26
|
+
};
|
|
27
|
+
export type RelayObservers = {
|
|
28
|
+
onStore?: (info: RelayObserverStoreInfo) => void;
|
|
29
|
+
onFetch?: (info: RelayObserverFetchInfo) => void;
|
|
30
|
+
onAck?: (info: RelayObserverAckInfo) => void;
|
|
31
|
+
onPurge?: (info: RelayObserverPurgeInfo) => void;
|
|
32
|
+
onAuthRejected?: (info: RelayObserverAuthRejectedInfo) => void;
|
|
33
|
+
onError?: (info: RelayObserverErrorInfo) => void;
|
|
34
|
+
};
|
|
35
|
+
export declare function decorateHubStore(store: HubStore, observers?: RelayObservers): HubStore;
|
package/lib/observers.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
function e(e,t){if(null!=e)try{e(t)}catch{}}export function decorateHubStore(t,r){if(null==r)return t;let{onStore:n,onFetch:c,onAck:s,onError:a}=r;return{events:t.events,storeKeyPackage:t.storeKeyPackage.bind(t),fetchKeyPackages:t.fetchKeyPackages.bind(t),setGroupMembers:t.setGroupMembers.bind(t),getGroupMembers:t.getGroupMembers.bind(t),purge:t.purge.bind(t),async store(r){try{let c=await t.store(r);return e(n,{senderDID:r.senderDID,groupID:r.groupID,recipientCount:r.recipients.length,sequenceID:c}),c}catch(t){throw e(a,{stage:"store",error:t}),t}},async fetch(r){try{let n=await t.fetch(r);return e(c,{recipientDID:r.recipientDID,messageCount:n.messages.length}),n}catch(t){throw e(a,{stage:"fetch",error:t}),t}},async ack(r){try{await t.ack(r),e(s,{recipientDID:r.recipientDID,sequenceCount:r.sequenceIDs.length})}catch(t){throw e(a,{stage:"ack",error:t}),t}}}}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kubun/hub",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.0",
|
|
4
4
|
"license": "see LICENSE.md",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
@@ -14,25 +14,27 @@
|
|
|
14
14
|
],
|
|
15
15
|
"sideEffects": false,
|
|
16
16
|
"dependencies": {
|
|
17
|
-
"@enkaku/event": "^0.
|
|
18
|
-
"@enkaku/http-server-transport": "^0.
|
|
19
|
-
"@enkaku/hub-protocol": "^0.
|
|
20
|
-
"@enkaku/hub-server": "^0.
|
|
21
|
-
"@enkaku/server": "^0.
|
|
22
|
-
"@
|
|
17
|
+
"@enkaku/event": "^0.16.0",
|
|
18
|
+
"@enkaku/http-server-transport": "^0.16.0",
|
|
19
|
+
"@enkaku/hub-protocol": "^0.16.0",
|
|
20
|
+
"@enkaku/hub-server": "^0.16.0",
|
|
21
|
+
"@enkaku/server": "^0.16.0",
|
|
22
|
+
"@enkaku/token": "^0.16.0",
|
|
23
|
+
"@hono/node-server": "^2.0.4",
|
|
23
24
|
"get-port": "^7.2.0",
|
|
24
|
-
"hono": "^4.12.
|
|
25
|
-
"kysely": "^0.
|
|
26
|
-
"@kubun/db-adapter": "^0.
|
|
25
|
+
"hono": "^4.12.23",
|
|
26
|
+
"kysely": "^0.29.2",
|
|
27
|
+
"@kubun/db-adapter": "^0.10.0",
|
|
28
|
+
"@kubun/db-provider": "^0.10.0"
|
|
27
29
|
},
|
|
28
30
|
"devDependencies": {
|
|
29
|
-
"@enkaku/async": "^0.
|
|
30
|
-
"@enkaku/client": "^0.
|
|
31
|
-
"@enkaku/http-client-transport": "^0.
|
|
32
|
-
"@enkaku/protocol": "^0.
|
|
33
|
-
"@enkaku/transport": "0.
|
|
34
|
-
"@kubun/db-node-sqlite": "^0.
|
|
35
|
-
"@kubun/db-better-sqlite": "^0.
|
|
31
|
+
"@enkaku/async": "^0.16.0",
|
|
32
|
+
"@enkaku/client": "^0.16.0",
|
|
33
|
+
"@enkaku/http-client-transport": "^0.16.0",
|
|
34
|
+
"@enkaku/protocol": "^0.16.0",
|
|
35
|
+
"@enkaku/transport": "^0.16.0",
|
|
36
|
+
"@kubun/db-node-sqlite": "^0.10.0",
|
|
37
|
+
"@kubun/db-better-sqlite": "^0.10.0"
|
|
36
38
|
},
|
|
37
39
|
"scripts": {
|
|
38
40
|
"build:clean": "del lib",
|