@kumiai/hub-server 0.11.0 → 0.12.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/README.md +5 -0
- package/lib/handlers.d.ts +3 -0
- package/lib/handlers.js +2 -0
- package/lib/hub.d.ts +5 -0
- package/lib/hub.js +24 -9
- package/package.json +10 -10
package/README.md
CHANGED
|
@@ -84,6 +84,11 @@ request belongs to the store, because the store is what refuses the subscribe
|
|
|
84
84
|
pin a topic's frames forever with `subscribe({ retention: 2 ** 31 })`. A subscribe above the ceiling
|
|
85
85
|
is refused with `RetentionExceededError`, never clamped.
|
|
86
86
|
|
|
87
|
+
When the store supports subscription reaping, each tick also removes inactive subscriptions after message purge settles.
|
|
88
|
+
`purge.subscriptionOlderThan` sets the inactivity window in seconds, defaulting to `7776000` (90 days).
|
|
89
|
+
The effective window is `Math.max(subscriptionOlderThan, olderThan)`, so subscriptions outlive the message retention window.
|
|
90
|
+
Both operations report failures through `onStoreError` independently. Disposal waits for both operations.
|
|
91
|
+
|
|
87
92
|
`createMemoryStore`'s `maxDepth` (default 1000) evicts the oldest **log** frames beyond that count.
|
|
88
93
|
Mailbox frames on the same topic are not counted, so a member cannot evict the log with a mailbox
|
|
89
94
|
flood.
|
package/lib/handlers.d.ts
CHANGED
|
@@ -98,6 +98,9 @@ export type AuthorizeHook = (req: AuthorizeRequest) => AuthorizeDecision | Promi
|
|
|
98
98
|
export type HubStoreErrorEvent = {
|
|
99
99
|
method: 'purge';
|
|
100
100
|
error: unknown;
|
|
101
|
+
} | {
|
|
102
|
+
method: 'purgeSubscriptions';
|
|
103
|
+
error: unknown;
|
|
101
104
|
}
|
|
102
105
|
/** The DID whose ack the store refused. */
|
|
103
106
|
| {
|
package/lib/handlers.js
CHANGED
|
@@ -21,6 +21,7 @@ function normalizeAuthorizeDecision(decision) {
|
|
|
21
21
|
fetchLastResortKeyPackage: 'The fetch returned what the pool could serve, without the last-resort top-up. A read that ' + 'keeps failing means the availability floor the last-resort slot exists to provide is absent, ' + 'and joins fail downstream at the inviter with no signal here.',
|
|
22
22
|
ack: 'The frame stays pending and the client re-acks on the next round. An ack that keeps failing ' + 'redelivers every frame forever.',
|
|
23
23
|
purge: 'Retried on the next interval. A purge that keeps failing means the store grows without bound.',
|
|
24
|
+
purgeSubscriptions: 'Retried on the next interval. Persistent failure leaves stale subscriptions consuming storage and quota.',
|
|
24
25
|
getSubscribers: 'The frame is committed and queued, but the live push to connected subscribers was skipped: ' + 'each of them stops receiving new frames until it reconnects. A getSubscribers that keeps ' + 'failing means the hub has silently degraded from push to pull for every publish.',
|
|
25
26
|
wake: 'The offline device was not pinged for this frame. It still redelivers on the next reconnect ' + '(store-and-forward), so nothing is lost — but a wake that keeps failing means the device ' + 'stays asleep until something else wakes it.'
|
|
26
27
|
};
|
|
@@ -32,6 +33,7 @@ const reportStoreError = getReporter([
|
|
|
32
33
|
* methods, a topic for fan-out, nothing for a store-wide purge. */ function subjectOf(event) {
|
|
33
34
|
switch(event.method){
|
|
34
35
|
case 'purge':
|
|
36
|
+
case 'purgeSubscriptions':
|
|
35
37
|
return '';
|
|
36
38
|
case 'getSubscribers':
|
|
37
39
|
return ` on topic ${event.topicID}`;
|
package/lib/hub.d.ts
CHANGED
|
@@ -20,6 +20,11 @@ export type HubPurgeOptions = {
|
|
|
20
20
|
* store's, since the store is the thing that refuses the subscribe.
|
|
21
21
|
*/
|
|
22
22
|
olderThan?: number;
|
|
23
|
+
/**
|
|
24
|
+
* Subscription inactivity window in seconds. Default: 7776000 (90 days).
|
|
25
|
+
* Clamped to at least olderThan so subscriptions outlive the message retention window.
|
|
26
|
+
*/
|
|
27
|
+
subscriptionOlderThan?: number;
|
|
23
28
|
};
|
|
24
29
|
/** Replay settings a host may tune. The hub always enables replay checks and rejects stale tokens. */
|
|
25
30
|
export type HubReplayOptions = Omit<ReplayOptions, 'enabled' | 'rejectStale'>;
|
package/lib/hub.js
CHANGED
|
@@ -69,16 +69,31 @@ export function createHub(params) {
|
|
|
69
69
|
if (params.purge !== false) {
|
|
70
70
|
const interval = params.purge?.interval ?? 3_600_000;
|
|
71
71
|
const olderThan = params.purge?.olderThan ?? 604_800;
|
|
72
|
+
const subscriptionOlderThan = Math.max(params.purge?.subscriptionOlderThan ?? 7_776_000, olderThan);
|
|
73
|
+
const store = params.store;
|
|
72
74
|
purgeTimer = setInterval(()=>{
|
|
73
|
-
const run =
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
75
|
+
const run = (async ()=>{
|
|
76
|
+
try {
|
|
77
|
+
await store.purge({
|
|
78
|
+
olderThan
|
|
79
|
+
});
|
|
80
|
+
} catch (error) {
|
|
81
|
+
storeErrorReporter({
|
|
82
|
+
method: 'purge',
|
|
83
|
+
error
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
try {
|
|
87
|
+
await store.purgeSubscriptions?.({
|
|
88
|
+
olderThan: subscriptionOlderThan
|
|
89
|
+
});
|
|
90
|
+
} catch (error) {
|
|
91
|
+
storeErrorReporter({
|
|
92
|
+
method: 'purgeSubscriptions',
|
|
93
|
+
error
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
})();
|
|
82
97
|
purgesInFlight.add(run);
|
|
83
98
|
void run.finally(()=>{
|
|
84
99
|
purgesInFlight.delete(run);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kumiai/hub-server",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.0",
|
|
4
4
|
"description": "Hub server for Enkaku E2EE group message routing",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"hub",
|
|
@@ -25,20 +25,20 @@
|
|
|
25
25
|
"lib/*"
|
|
26
26
|
],
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@enkaku/protocol": "^0.22.
|
|
29
|
-
"@enkaku/server": "^0.22.
|
|
30
|
-
"@kokuin/capability": "^0.5.
|
|
31
|
-
"@kokuin/token": "^0.6.
|
|
32
|
-
"@kumiai/hub-protocol": "^0.
|
|
28
|
+
"@enkaku/protocol": "^0.22.2",
|
|
29
|
+
"@enkaku/server": "^0.22.2",
|
|
30
|
+
"@kokuin/capability": "^0.5.2",
|
|
31
|
+
"@kokuin/token": "^0.6.2",
|
|
32
|
+
"@kumiai/hub-protocol": "^0.12.0",
|
|
33
33
|
"@sozai/codec": "^0.4.3",
|
|
34
34
|
"@sozai/event": "^0.1.6",
|
|
35
35
|
"@sozai/log": "^0.4.0"
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|
|
38
|
-
"@enkaku/client": "^0.22.
|
|
39
|
-
"@enkaku/transport": "^0.22.
|
|
40
|
-
"@kumiai/hub-conformance": "^0.
|
|
41
|
-
"@kumiai/hub-wake": "^0.
|
|
38
|
+
"@enkaku/client": "^0.22.2",
|
|
39
|
+
"@enkaku/transport": "^0.22.2",
|
|
40
|
+
"@kumiai/hub-conformance": "^0.12.0",
|
|
41
|
+
"@kumiai/hub-wake": "^0.12.0"
|
|
42
42
|
},
|
|
43
43
|
"publishConfig": {
|
|
44
44
|
"access": "public"
|