@toa.io/extensions.exposition 1.0.0-alpha.313 → 1.0.0-alpha.314
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/components/exposition.octets/package.json +2 -2
- package/package.json +6 -4
- package/readme.md +1 -0
- package/transpiled/Factory.d.ts +5 -0
- package/transpiled/Factory.js +3 -0
- package/transpiled/Factory.js.map +1 -1
- package/transpiled/directives/index.js +2 -0
- package/transpiled/directives/index.js.map +1 -1
- package/transpiled/directives/realtime/Hub.d.ts +45 -0
- package/transpiled/directives/realtime/Hub.js +221 -0
- package/transpiled/directives/realtime/Hub.js.map +1 -0
- package/transpiled/directives/realtime/Realtime.d.ts +18 -0
- package/transpiled/directives/realtime/Realtime.js +37 -0
- package/transpiled/directives/realtime/Realtime.js.map +1 -0
- package/transpiled/directives/realtime/Stream.d.ts +38 -0
- package/transpiled/directives/realtime/Stream.js +89 -0
- package/transpiled/directives/realtime/Stream.js.map +1 -0
- package/transpiled/directives/realtime/index.d.ts +2 -0
- package/transpiled/directives/realtime/index.js +3 -0
- package/transpiled/directives/realtime/index.js.map +1 -0
- package/transpiled/directives/realtime/measurements.d.ts +1 -0
- package/transpiled/directives/realtime/measurements.js +11 -0
- package/transpiled/directives/realtime/measurements.js.map +1 -0
- package/transpiled/realtime/Destination.d.ts +24 -0
- package/transpiled/realtime/Destination.js +83 -0
- package/transpiled/realtime/Destination.js.map +1 -0
- package/transpiled/realtime/Factory.d.ts +11 -0
- package/transpiled/realtime/Factory.js +15 -0
- package/transpiled/realtime/Factory.js.map +1 -0
- package/transpiled/realtime/measurements.d.ts +1 -0
- package/transpiled/realtime/measurements.js +11 -0
- package/transpiled/realtime/measurements.js.map +1 -0
- package/transpiled/realtime/redis.d.ts +36 -0
- package/transpiled/realtime/redis.js +83 -0
- package/transpiled/realtime/redis.js.map +1 -0
- package/transpiled/root.js +15 -0
- package/transpiled/root.js.map +1 -1
- package/ui/dist/_app/immutable/chunks/3z-MngL5.js +1 -0
- package/ui/dist/_app/immutable/chunks/{s9vNFPIB.js → CubNYXGc.js} +1 -1
- package/ui/dist/_app/immutable/chunks/DnwsDdVR.js +6 -0
- package/ui/dist/_app/immutable/chunks/{CKEZ5KRV.js → gT7UvzUs.js} +1 -1
- package/ui/dist/_app/immutable/entry/{app.BItHi3t7.js → app.BuIzcMse.js} +2 -2
- package/ui/dist/_app/immutable/entry/start.Djj5Mm5B.js +1 -0
- package/ui/dist/_app/immutable/nodes/{0.DZjK4ZnB.js → 0.CwHL4JQh.js} +1 -1
- package/ui/dist/_app/immutable/nodes/{1.VSVDanEF.js → 1.DgOr0IJQ.js} +1 -1
- package/ui/dist/_app/immutable/nodes/{2.DjphjWoH.js → 2.1ODUSz5b.js} +1 -1
- package/ui/dist/_app/immutable/nodes/{3.BQwfZxZn.js → 3._pT85mpt.js} +1 -1
- package/ui/dist/_app/immutable/nodes/{4.B85Y-SnH.js → 4.DiXgB9MQ.js} +1 -1
- package/ui/dist/_app/immutable/nodes/{5.BlZ8Xi2u.js → 5.D7U8VR0l.js} +1 -1
- package/ui/dist/_app/immutable/nodes/{6.C7PmgKSw.js → 6.CiZdC04N.js} +1 -1
- package/ui/dist/_app/version.json +1 -1
- package/ui/dist/index.html +10 -10
- package/ui/dist/_app/immutable/chunks/5vFkvWBI.js +0 -6
- package/ui/dist/_app/immutable/chunks/C0GgtrvX.js +0 -1
- package/ui/dist/_app/immutable/entry/start.BuSLHyNk.js +0 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@toa.io/extensions.exposition",
|
|
3
|
-
"version": "1.0.0-alpha.
|
|
3
|
+
"version": "1.0.0-alpha.314",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Toa Exposition",
|
|
6
6
|
"author": "temich <tema.gurtovoy@gmail.com>",
|
|
@@ -28,11 +28,13 @@
|
|
|
28
28
|
"!**/.env*"
|
|
29
29
|
],
|
|
30
30
|
"dependencies": {
|
|
31
|
-
"@toa.io/core": "1.0.0-alpha.
|
|
32
|
-
"@toa.io/definitions": "1.0.0-alpha.
|
|
31
|
+
"@toa.io/core": "1.0.0-alpha.314",
|
|
32
|
+
"@toa.io/definitions": "1.0.0-alpha.314",
|
|
33
33
|
"@toa.io/generic": "1.0.0-alpha.311",
|
|
34
|
+
"@toa.io/pointer": "1.0.0-alpha.311",
|
|
34
35
|
"@toa.io/schemas": "1.0.0-alpha.311",
|
|
35
36
|
"content-type": "3.1.0",
|
|
37
|
+
"ioredis": "6.0.0",
|
|
36
38
|
"matchacho": "0.6.1",
|
|
37
39
|
"minimatch": "10.2.6",
|
|
38
40
|
"msgpackr": "2.1.0",
|
|
@@ -72,5 +74,5 @@
|
|
|
72
74
|
"lru-cache": "11.5.2",
|
|
73
75
|
"paseto": "4.0.1"
|
|
74
76
|
},
|
|
75
|
-
"gitHead": "
|
|
77
|
+
"gitHead": "4b7c0f4468e6fa89f95a69f43fed85c648fb033d"
|
|
76
78
|
}
|
package/readme.md
CHANGED
|
@@ -219,6 +219,7 @@ See also [telemetry ready probe](../telemetry/readme.md#ready-probe) and
|
|
|
219
219
|
- [OAuth authorization server](documentation/oauth.md)
|
|
220
220
|
- [Consent](documentation/consent.md)
|
|
221
221
|
- [BLOBs](documentation/octets.md)
|
|
222
|
+
- [Realtime streams](documentation/realtime.md)
|
|
222
223
|
- [Components and resources](documentation/components.md)
|
|
223
224
|
- [Caching](documentation/cache.md)
|
|
224
225
|
- [Features](features)
|
package/transpiled/Factory.d.ts
CHANGED
|
@@ -1,7 +1,12 @@
|
|
|
1
|
+
import { Realtime } from './realtime/Factory.ts';
|
|
1
2
|
import type { syntax } from './RTD/index.ts';
|
|
2
3
|
import type { Connector, Contract, Locator } from '@toa.io/core';
|
|
3
4
|
import type { extensions } from '@toa.io/core/types';
|
|
4
5
|
export declare class Factory implements extensions.Factory {
|
|
6
|
+
/** `realtime`, declared beside `exposition`: the routes of a component's events to streams */
|
|
7
|
+
readonly keys: {
|
|
8
|
+
realtime: Realtime;
|
|
9
|
+
};
|
|
5
10
|
private readonly host;
|
|
6
11
|
private announcing;
|
|
7
12
|
private announcements;
|
package/transpiled/Factory.js
CHANGED
|
@@ -2,8 +2,11 @@ import { createHash } from 'node:crypto';
|
|
|
2
2
|
import { contract } from '@toa.io/core';
|
|
3
3
|
import { Announcements } from './Announcements.js';
|
|
4
4
|
import { Tenant } from './Tenant.js';
|
|
5
|
+
import { Realtime } from './realtime/Factory.js';
|
|
5
6
|
import { CHANNEL } from '@toa.io/definitions/extensions.exposition';
|
|
6
7
|
export class Factory {
|
|
8
|
+
/** `realtime`, declared beside `exposition`: the routes of a component's events to streams */
|
|
9
|
+
keys = { realtime: new Realtime() };
|
|
7
10
|
host;
|
|
8
11
|
announcing;
|
|
9
12
|
announcements;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Factory.js","sourceRoot":"","sources":["../source/Factory.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AACxC,OAAO,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAA;AACvC,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAA;AAClD,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AACpC,OAAO,EAAE,OAAO,EAAE,MAAM,2CAA2C,CAAA;AAMnE,MAAM,OAAO,OAAO;
|
|
1
|
+
{"version":3,"file":"Factory.js","sourceRoot":"","sources":["../source/Factory.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AACxC,OAAO,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAA;AACvC,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAA;AAClD,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AACpC,OAAO,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAA;AAChD,OAAO,EAAE,OAAO,EAAE,MAAM,2CAA2C,CAAA;AAMnE,MAAM,OAAO,OAAO;IAClB,8FAA8F;IAC9E,IAAI,GAAG,EAAE,QAAQ,EAAE,IAAI,QAAQ,EAAE,EAAE,CAAA;IAElC,IAAI,CAAM;IACnB,UAAU,CAAoC;IAC9C,aAAa,CAA2B;IAEhD,YAAmB,IAAU;QAC3B,IAAI,CAAC,IAAI,GAAG,IAAI,CAAA;IAClB,CAAC;IAEM,KAAK,CAAC,MAAM,CACjB,OAAgB,EAChB,IAAiB,EACjB,QAAkB;QAElB,MAAM,aAAa,GAAG,MAAM,IAAI,CAAC,MAAM,EAAE,CAAA;QACzC,MAAM,MAAM,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;QAE9E,4EAA4E;QAC5E,MAAM,MAAM,GAA8B;YACxC,oFAAoF;YACpF,SAAS,EAAE,OAAO,CAAC,SAAU;YAC7B,SAAS,EAAE,OAAO,CAAC,IAAI;YACvB,QAAQ,EAAE,OAAO,CAAC,SAAS,KAAK,UAAU;YAC1C,IAAI;YACJ,OAAO,EAAE,QAAQ,CAAC,OAAO;YACzB,QAAQ,EAAE,QAAQ,CAAC,SAAS,CAAC,QAAQ,CAAC;YACtC,MAAM;SACP,CAAA;QAED,OAAO,IAAI,MAAM,CAAC,aAAa,EAAE,MAAM,CAAC,CAAA;IAC1C,CAAC;IAED;;;;OAIG;IACK,KAAK,CAAC,MAAM;QAClB,IAAI,IAAI,CAAC,aAAa,EAAE,QAAQ,KAAK,IAAI,EAAE,CAAC;YAC1C,IAAI,CAAC,UAAU,GAAG,SAAS,CAAA;YAC3B,IAAI,CAAC,aAAa,GAAG,SAAS,CAAA;QAChC,CAAC;QAED,IAAI,CAAC,UAAU,KAAK,IAAI,CAAC,QAAQ,EAAE,CAAA;QAEnC,OAAO,MAAM,IAAI,CAAC,UAAU,CAAA;IAC9B,CAAC;IAEO,KAAK,CAAC,QAAQ;QACpB,MAAM,aAAa,GAAG,IAAI,aAAa,CAAC,MAAM,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAA;QAE3E,IAAI,CAAC,aAAa,GAAG,aAAa,CAAA;QAElC,OAAO,aAAa,CAAA;IACtB,CAAC;IAEM,KAAK,CAAC,OAAO;QAClB,MAAM,EAAE,OAAO,EAAE,GAAG,MAAM,MAAM,CAAC,cAAc,CAAC,CAAA;QAEhD,OAAO,MAAM,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IACjC,CAAC;CACF"}
|
|
@@ -9,6 +9,7 @@ import { map } from './map/index.js';
|
|
|
9
9
|
import { mcp } from './mcp/index.js';
|
|
10
10
|
import { req } from './require/index.js';
|
|
11
11
|
import { flow } from './flow/index.js';
|
|
12
|
+
import { realtime } from './realtime/index.js';
|
|
12
13
|
import { help } from './help/index.js';
|
|
13
14
|
import { discovery } from './oauth/index.js';
|
|
14
15
|
import { Site } from '../Discovery/index.js';
|
|
@@ -22,6 +23,7 @@ export const families = [
|
|
|
22
23
|
req,
|
|
23
24
|
flow,
|
|
24
25
|
octets,
|
|
26
|
+
realtime,
|
|
25
27
|
dev
|
|
26
28
|
];
|
|
27
29
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../source/directives/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAA;AAC/C,OAAO,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAA;AACxC,OAAO,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAA;AACtC,OAAO,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAA;AAC1C,OAAO,EAAE,GAAG,EAAE,MAAM,gBAAgB,CAAA;AACpC,OAAO,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAA;AAC1C,OAAO,EAAE,EAAE,EAAE,MAAM,eAAe,CAAA;AAClC,OAAO,EAAE,GAAG,EAAE,MAAM,gBAAgB,CAAA;AACpC,OAAO,EAAE,GAAG,EAAE,MAAM,gBAAgB,CAAA;AACpC,OAAO,EAAE,GAAG,EAAE,MAAM,oBAAoB,CAAA;AACxC,OAAO,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAA;AACtC,OAAO,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAA;AACtC,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAA;AAC5C,OAAO,EAAE,IAAI,EAAE,MAAM,uBAAuB,CAAA;AAI5C,MAAM,CAAC,MAAM,QAAQ,GAAsB;IACzC,aAAa;IACb,EAAE;IACF,KAAK;IACL,GAAG;IACH,GAAG;IACH,IAAI;IACJ,GAAG;IACH,IAAI;IACJ,MAAM;IACN,GAAG;CACJ,CAAA;AACD;;;;GAIG;AACH,MAAM,CAAC,MAAM,YAAY,GAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../source/directives/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAA;AAC/C,OAAO,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAA;AACxC,OAAO,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAA;AACtC,OAAO,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAA;AAC1C,OAAO,EAAE,GAAG,EAAE,MAAM,gBAAgB,CAAA;AACpC,OAAO,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAA;AAC1C,OAAO,EAAE,EAAE,EAAE,MAAM,eAAe,CAAA;AAClC,OAAO,EAAE,GAAG,EAAE,MAAM,gBAAgB,CAAA;AACpC,OAAO,EAAE,GAAG,EAAE,MAAM,gBAAgB,CAAA;AACpC,OAAO,EAAE,GAAG,EAAE,MAAM,oBAAoB,CAAA;AACxC,OAAO,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAA;AACtC,OAAO,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAA;AAC9C,OAAO,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAA;AACtC,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAA;AAC5C,OAAO,EAAE,IAAI,EAAE,MAAM,uBAAuB,CAAA;AAI5C,MAAM,CAAC,MAAM,QAAQ,GAAsB;IACzC,aAAa;IACb,EAAE;IACF,KAAK;IACL,GAAG;IACH,GAAG;IACH,IAAI;IACJ,GAAG;IACH,IAAI;IACJ,MAAM;IACN,QAAQ;IACR,GAAG;CACJ,CAAA;AACD;;;;GAIG;AACH,MAAM,CAAC,MAAM,YAAY,GAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,CAAA"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { Stream } from './Stream.ts';
|
|
2
|
+
/**
|
|
3
|
+
* The streams this gateway serves, by key.
|
|
4
|
+
*
|
|
5
|
+
* A key that has a reader here is subscribed to on its channel, and its stream in Redis is kept
|
|
6
|
+
* alive: a component writes a routed event only to a stream that exists, so a stream that expired
|
|
7
|
+
* under a reader would stop receiving. After its last reader leaves, the stream lives `expire`
|
|
8
|
+
* more seconds — the window that reader reconnects in with its token.
|
|
9
|
+
*
|
|
10
|
+
* A channel keeps nothing for a subscriber that was away. Whenever the subscriber is back, what
|
|
11
|
+
* each key was written meanwhile is read from its stream, from the last event this gateway
|
|
12
|
+
* delivered of it; each reader skips what it was given already.
|
|
13
|
+
*/
|
|
14
|
+
export declare class Hub {
|
|
15
|
+
private readonly keys;
|
|
16
|
+
private shards;
|
|
17
|
+
private subscribers;
|
|
18
|
+
private connecting;
|
|
19
|
+
private prefix;
|
|
20
|
+
/** Connects on the first stream it serves, not before: a gateway may serve none. */
|
|
21
|
+
connect(): Promise<void>;
|
|
22
|
+
close(): void;
|
|
23
|
+
private start;
|
|
24
|
+
open(key: string, token?: string): Promise<Stream>;
|
|
25
|
+
private enter;
|
|
26
|
+
private leave;
|
|
27
|
+
/** Creates the stream where it is gone, and answers where it ends now. */
|
|
28
|
+
private marker;
|
|
29
|
+
private read;
|
|
30
|
+
private receive;
|
|
31
|
+
private broadcast;
|
|
32
|
+
/**
|
|
33
|
+
* What every key was written while the subscriber was away. Its channels are subscribed to
|
|
34
|
+
* again first, where the client has not done it on its own, so nothing written after the read
|
|
35
|
+
* is missed.
|
|
36
|
+
*/
|
|
37
|
+
private recover;
|
|
38
|
+
/** The connection a key's stream is kept on. */
|
|
39
|
+
private redis;
|
|
40
|
+
private subscriber;
|
|
41
|
+
private expire;
|
|
42
|
+
/** Well within the expiry, and no more often than the heartbeat where that is. */
|
|
43
|
+
private renewal;
|
|
44
|
+
private renew;
|
|
45
|
+
}
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
import { console } from 'openspan';
|
|
2
|
+
import { Stream, HEARTBEAT, decode, encode } from './Stream.js';
|
|
3
|
+
import { MAXLEN, Shards, expire, prefix, shard } from '../../realtime/redis.js';
|
|
4
|
+
import * as measure from './measurements.js';
|
|
5
|
+
/**
|
|
6
|
+
* The streams this gateway serves, by key.
|
|
7
|
+
*
|
|
8
|
+
* A key that has a reader here is subscribed to on its channel, and its stream in Redis is kept
|
|
9
|
+
* alive: a component writes a routed event only to a stream that exists, so a stream that expired
|
|
10
|
+
* under a reader would stop receiving. After its last reader leaves, the stream lives `expire`
|
|
11
|
+
* more seconds — the window that reader reconnects in with its token.
|
|
12
|
+
*
|
|
13
|
+
* A channel keeps nothing for a subscriber that was away. Whenever the subscriber is back, what
|
|
14
|
+
* each key was written meanwhile is read from its stream, from the last event this gateway
|
|
15
|
+
* delivered of it; each reader skips what it was given already.
|
|
16
|
+
*/
|
|
17
|
+
export class Hub {
|
|
18
|
+
keys = new Map();
|
|
19
|
+
shards = null;
|
|
20
|
+
// a subscribing connection can do nothing else, so each Redis has one of its own
|
|
21
|
+
subscribers = null;
|
|
22
|
+
connecting = null;
|
|
23
|
+
prefix = '';
|
|
24
|
+
/** Connects on the first stream it serves, not before: a gateway may serve none. */
|
|
25
|
+
async connect() {
|
|
26
|
+
this.connecting ??= this.start();
|
|
27
|
+
await this.connecting;
|
|
28
|
+
}
|
|
29
|
+
close() {
|
|
30
|
+
for (const { streams, timer } of this.keys.values()) {
|
|
31
|
+
clearInterval(timer);
|
|
32
|
+
for (const stream of streams)
|
|
33
|
+
stream.close();
|
|
34
|
+
}
|
|
35
|
+
this.keys.clear();
|
|
36
|
+
this.subscribers?.disconnect();
|
|
37
|
+
this.shards?.disconnect();
|
|
38
|
+
this.subscribers = null;
|
|
39
|
+
this.shards = null;
|
|
40
|
+
this.connecting = null;
|
|
41
|
+
}
|
|
42
|
+
async start() {
|
|
43
|
+
this.prefix = prefix();
|
|
44
|
+
const shards = new Shards();
|
|
45
|
+
const subscribers = new Shards();
|
|
46
|
+
for (const connection of [...shards.connections, ...subscribers.connections])
|
|
47
|
+
connection.on('error', (error) => console.debug('Realtime streams unreachable', {
|
|
48
|
+
error: error.code ?? error.message
|
|
49
|
+
}));
|
|
50
|
+
subscribers.connections.forEach((subscriber, i) => {
|
|
51
|
+
// a key's channel is subscribed to by the key, the prefix applying to it; what arrives is
|
|
52
|
+
// named in full
|
|
53
|
+
subscriber.on('smessage', (channel, message) => this.receive(channel.slice(this.prefix.length), message));
|
|
54
|
+
// `ready` follows every reconnect too, and what was written meanwhile never arrives
|
|
55
|
+
let connected = false;
|
|
56
|
+
subscriber.on('ready', () => {
|
|
57
|
+
if (connected)
|
|
58
|
+
void this.recover(i);
|
|
59
|
+
connected = true;
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
this.shards = shards;
|
|
63
|
+
this.subscribers = subscribers;
|
|
64
|
+
await Promise.all([shards.connect(), subscribers.connect()]);
|
|
65
|
+
}
|
|
66
|
+
async open(key, token) {
|
|
67
|
+
await this.connect();
|
|
68
|
+
const stream = new Stream();
|
|
69
|
+
const entry = await this.enter(key, stream);
|
|
70
|
+
stream.once('close', () => this.leave(key, stream));
|
|
71
|
+
// welcome: a reader knows it is connected before anything is written
|
|
72
|
+
setTimeout(() => stream.heartbeat(), 1000).unref();
|
|
73
|
+
// written after the subscription, so nothing written after it misses this reader, and on
|
|
74
|
+
// every connection: a stream that expired while its reader was away is created again, or
|
|
75
|
+
// nothing would be written to it
|
|
76
|
+
const marker = await this.marker(key);
|
|
77
|
+
entry.last ??= marker;
|
|
78
|
+
const cursor = token === undefined ? marker : decode(token);
|
|
79
|
+
const entries = token === undefined ? [] : await this.read(key, cursor);
|
|
80
|
+
stream.cursor = cursor;
|
|
81
|
+
stream.ready(entries);
|
|
82
|
+
// nothing was written since: the reader goes on from here
|
|
83
|
+
if (entries.length === 0)
|
|
84
|
+
stream.token(encode(marker));
|
|
85
|
+
return stream;
|
|
86
|
+
}
|
|
87
|
+
async enter(key, stream) {
|
|
88
|
+
let entry = this.keys.get(key);
|
|
89
|
+
if (entry === undefined) {
|
|
90
|
+
entry = {
|
|
91
|
+
streams: new Set(),
|
|
92
|
+
last: null,
|
|
93
|
+
timer: setInterval(() => this.renew(key), this.renewal()),
|
|
94
|
+
subscribed: this.subscriber(key)
|
|
95
|
+
.ssubscribe(key)
|
|
96
|
+
.then(() => undefined)
|
|
97
|
+
};
|
|
98
|
+
entry.timer.unref();
|
|
99
|
+
this.keys.set(key, entry);
|
|
100
|
+
}
|
|
101
|
+
entry.streams.add(stream);
|
|
102
|
+
// the first reader subscribes, and every reader waits for it: nothing is read or written
|
|
103
|
+
// for this one before the channel carries the key
|
|
104
|
+
await entry.subscribed;
|
|
105
|
+
return entry;
|
|
106
|
+
}
|
|
107
|
+
leave(key, stream) {
|
|
108
|
+
const entry = this.keys.get(key);
|
|
109
|
+
if (entry === undefined)
|
|
110
|
+
return;
|
|
111
|
+
entry.streams.delete(stream);
|
|
112
|
+
if (entry.streams.size > 0)
|
|
113
|
+
return;
|
|
114
|
+
clearInterval(entry.timer);
|
|
115
|
+
this.keys.delete(key);
|
|
116
|
+
this.renew(key);
|
|
117
|
+
this.subscriber(key)
|
|
118
|
+
.sunsubscribe(key)
|
|
119
|
+
.catch((error) => console.debug('Realtime key not unsubscribed', { key, error }));
|
|
120
|
+
}
|
|
121
|
+
/** Creates the stream where it is gone, and answers where it ends now. */
|
|
122
|
+
async marker(key) {
|
|
123
|
+
const [[error, id]] = (await this.redis(key)
|
|
124
|
+
.multi()
|
|
125
|
+
.xadd(key, 'MAXLEN', '~', MAXLEN, '*', 'type', 'connect')
|
|
126
|
+
.expire(key, this.expire())
|
|
127
|
+
.exec());
|
|
128
|
+
if (error !== null)
|
|
129
|
+
throw error;
|
|
130
|
+
return id;
|
|
131
|
+
}
|
|
132
|
+
async read(key, after) {
|
|
133
|
+
const results = await this.redis(key).xread('STREAMS', key, after);
|
|
134
|
+
const entries = [];
|
|
135
|
+
for (const [id, fields] of records(results)) {
|
|
136
|
+
const [, event, , data] = fields;
|
|
137
|
+
// the marker a reader wrote is a position, not an event
|
|
138
|
+
if (event === 'connect')
|
|
139
|
+
continue;
|
|
140
|
+
entries.push({ id, event, data: data === undefined ? undefined : JSON.parse(data) });
|
|
141
|
+
}
|
|
142
|
+
return entries;
|
|
143
|
+
}
|
|
144
|
+
receive(key, message) {
|
|
145
|
+
const entry = this.keys.get(key);
|
|
146
|
+
if (entry === undefined)
|
|
147
|
+
return;
|
|
148
|
+
const [id, event, data] = JSON.parse(message);
|
|
149
|
+
this.broadcast(entry, { id, event, data: JSON.parse(data) });
|
|
150
|
+
}
|
|
151
|
+
broadcast(entry, event) {
|
|
152
|
+
entry.last = event.id;
|
|
153
|
+
for (const stream of entry.streams)
|
|
154
|
+
stream.deliver(event);
|
|
155
|
+
measure.deliver(event.event);
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* What every key was written while the subscriber was away. Its channels are subscribed to
|
|
159
|
+
* again first, where the client has not done it on its own, so nothing written after the read
|
|
160
|
+
* is missed.
|
|
161
|
+
*/
|
|
162
|
+
async recover(i) {
|
|
163
|
+
const n = this.shards?.connections.length ?? 1;
|
|
164
|
+
const keys = [...this.keys.keys()].filter((key) => shard(key, n) === i);
|
|
165
|
+
if (keys.length > 0)
|
|
166
|
+
await this.subscribers?.connections[i].ssubscribe(...keys).catch((error) => {
|
|
167
|
+
console.warn('Realtime keys not subscribed again', { error });
|
|
168
|
+
});
|
|
169
|
+
for (const key of keys) {
|
|
170
|
+
const entry = this.keys.get(key);
|
|
171
|
+
// its last reader left meanwhile
|
|
172
|
+
if (entry === undefined)
|
|
173
|
+
continue;
|
|
174
|
+
try {
|
|
175
|
+
const after = entry.last ?? '0';
|
|
176
|
+
for (const event of await this.read(key, after))
|
|
177
|
+
this.broadcast(entry, event);
|
|
178
|
+
}
|
|
179
|
+
catch (error) {
|
|
180
|
+
console.warn('Realtime key not recovered', { key, error });
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
/** The connection a key's stream is kept on. */
|
|
185
|
+
redis(key) {
|
|
186
|
+
return this.shards.of(key);
|
|
187
|
+
}
|
|
188
|
+
subscriber(key) {
|
|
189
|
+
return this.subscribers.of(key);
|
|
190
|
+
}
|
|
191
|
+
expire() {
|
|
192
|
+
return expire();
|
|
193
|
+
}
|
|
194
|
+
/** Well within the expiry, and no more often than the heartbeat where that is. */
|
|
195
|
+
renewal() {
|
|
196
|
+
return Math.min(HEARTBEAT, (this.expire() * 1000) / 3);
|
|
197
|
+
}
|
|
198
|
+
renew(key) {
|
|
199
|
+
this.shards
|
|
200
|
+
?.of(key)
|
|
201
|
+
.expire(key, this.expire())
|
|
202
|
+
.catch((error) => {
|
|
203
|
+
// the next renewal tries again, well within the expiry
|
|
204
|
+
console.debug('Realtime key not renewed', { key, error });
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* The entries `XREAD` answers for one stream: an array of `[stream, entries]` pairs over RESP2,
|
|
210
|
+
* an object keyed by stream over RESP3, and nothing where nothing was written since.
|
|
211
|
+
*/
|
|
212
|
+
function records(results) {
|
|
213
|
+
if (Array.isArray(results))
|
|
214
|
+
return results.length === 0
|
|
215
|
+
? []
|
|
216
|
+
: results[0][1];
|
|
217
|
+
if (typeof results === 'object' && results !== null)
|
|
218
|
+
return Object.values(results)[0] ?? [];
|
|
219
|
+
return [];
|
|
220
|
+
}
|
|
221
|
+
//# sourceMappingURL=Hub.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Hub.js","sourceRoot":"","sources":["../../../source/directives/realtime/Hub.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { Context } from '../../HTTP/index.ts';
|
|
2
|
+
import type { DirectiveFamily, Parameter } from '../../RTD/index.ts';
|
|
3
|
+
import type { Output } from '../../io.ts';
|
|
4
|
+
/**
|
|
5
|
+
* `realtime:stream` answers with the stream of the key a route variable names. Who may read it is
|
|
6
|
+
* what the route's own directives say, as for anything else it serves.
|
|
7
|
+
*/
|
|
8
|
+
export declare class Realtime implements DirectiveFamily<Directive> {
|
|
9
|
+
readonly name = "realtime";
|
|
10
|
+
readonly mandatory = false;
|
|
11
|
+
private readonly hub;
|
|
12
|
+
create(name: string, value: unknown, _: unknown, route: string): Directive;
|
|
13
|
+
precall(directives: Directive[], context: Context, parameters: Parameter[]): Promise<Output>;
|
|
14
|
+
dispose(): void;
|
|
15
|
+
}
|
|
16
|
+
export interface Directive {
|
|
17
|
+
variable: string;
|
|
18
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { NotFound } from '../../HTTP/index.js';
|
|
2
|
+
import { configured } from '../../realtime/redis.js';
|
|
3
|
+
import { Hub } from './Hub.js';
|
|
4
|
+
/**
|
|
5
|
+
* `realtime:stream` answers with the stream of the key a route variable names. Who may read it is
|
|
6
|
+
* what the route's own directives say, as for anything else it serves.
|
|
7
|
+
*/
|
|
8
|
+
export class Realtime {
|
|
9
|
+
name = 'realtime';
|
|
10
|
+
mandatory = false;
|
|
11
|
+
hub = new Hub();
|
|
12
|
+
// the arguments every family is given a directive with
|
|
13
|
+
// eslint-disable-next-line max-params
|
|
14
|
+
create(name, value, _, route) {
|
|
15
|
+
if (name !== 'stream')
|
|
16
|
+
throw new Error(`Directive 'realtime:${name}' is not implemented`);
|
|
17
|
+
if (typeof value !== 'string' || !route.includes(`:${value}`))
|
|
18
|
+
throw new Error(`'realtime:stream' names the route variable that is the key, and '${String(value)}' ` +
|
|
19
|
+
`is not one of '${route}'`);
|
|
20
|
+
return { variable: value };
|
|
21
|
+
}
|
|
22
|
+
async precall(directives, context, parameters) {
|
|
23
|
+
const [directive] = directives;
|
|
24
|
+
const key = parameters.find(({ name }) => name === directive.variable)?.value;
|
|
25
|
+
if (key === undefined)
|
|
26
|
+
throw new Error(`Route variable '${directive.variable}' is not found`);
|
|
27
|
+
// nothing in the context declares realtime, so nothing is kept to be read
|
|
28
|
+
if (!configured())
|
|
29
|
+
throw new NotFound('Realtime streams are not configured');
|
|
30
|
+
const token = context.url.searchParams.get('token') ?? undefined;
|
|
31
|
+
return { body: await this.hub.open(key, token) };
|
|
32
|
+
}
|
|
33
|
+
dispose() {
|
|
34
|
+
this.hub.close();
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=Realtime.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Realtime.js","sourceRoot":"","sources":["../../../source/directives/realtime/Realtime.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAA;AAC9C,OAAO,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAA;AACpD,OAAO,EAAE,GAAG,EAAE,MAAM,UAAU,CAAA;AAK9B;;;GAGG;AACH,MAAM,OAAO,QAAQ;IACH,IAAI,GAAG,UAAU,CAAA;IACjB,SAAS,GAAG,KAAK,CAAA;IAEhB,GAAG,GAAG,IAAI,GAAG,EAAE,CAAA;IAEhC,uDAAuD;IACvD,sCAAsC;IAC/B,MAAM,CAAC,IAAY,EAAE,KAAc,EAAE,CAAU,EAAE,KAAa;QACnE,IAAI,IAAI,KAAK,QAAQ;YACnB,MAAM,IAAI,KAAK,CAAC,uBAAuB,IAAI,sBAAsB,CAAC,CAAA;QAEpE,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAI,KAAK,EAAE,CAAC;YAC3D,MAAM,IAAI,KAAK,CACb,oEAAoE,MAAM,CAAC,KAAK,CAAC,IAAI;gBACnF,kBAAkB,KAAK,GAAG,CAC7B,CAAA;QAEH,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAA;IAC5B,CAAC;IAEM,KAAK,CAAC,OAAO,CAClB,UAAuB,EACvB,OAAgB,EAChB,UAAuB;QAEvB,MAAM,CAAC,SAAS,CAAC,GAAG,UAAU,CAAA;QAC9B,MAAM,GAAG,GAAG,UAAU,CAAC,IAAI,CAAC,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC,IAAI,KAAK,SAAS,CAAC,QAAQ,CAAC,EAAE,KAAK,CAAA;QAE7E,IAAI,GAAG,KAAK,SAAS;YACnB,MAAM,IAAI,KAAK,CAAC,mBAAmB,SAAS,CAAC,QAAQ,gBAAgB,CAAC,CAAA;QAExE,0EAA0E;QAC1E,IAAI,CAAC,UAAU,EAAE;YAAE,MAAM,IAAI,QAAQ,CAAC,qCAAqC,CAAC,CAAA;QAE5E,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,SAAS,CAAA;QAEhE,OAAO,EAAE,IAAI,EAAE,MAAM,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,EAAE,KAAK,CAAC,EAAE,CAAA;IAClD,CAAC;IAEM,OAAO;QACZ,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,CAAA;IAClB,CAAC;CACF"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { Readable } from 'node:stream';
|
|
2
|
+
/**
|
|
3
|
+
* One reader of a key: what it is sent, in order, and a heartbeat while nothing is.
|
|
4
|
+
*
|
|
5
|
+
* `cursor` is the id of the last event it was given. What arrives while it is being replayed to
|
|
6
|
+
* is held until the replay is done, and whatever it was given already is not given again: a replay
|
|
7
|
+
* and the channel may both carry the same event.
|
|
8
|
+
*/
|
|
9
|
+
export declare class Stream extends Readable {
|
|
10
|
+
cursor: string | null;
|
|
11
|
+
private held;
|
|
12
|
+
private interval;
|
|
13
|
+
private ended;
|
|
14
|
+
constructor();
|
|
15
|
+
_read(): void;
|
|
16
|
+
_destroy(error: Error | null, callback: (error?: Error | null) => void): void;
|
|
17
|
+
/** An event of the key, or one held back while the replay is in flight. */
|
|
18
|
+
deliver(entry: Entry): void;
|
|
19
|
+
/** What the replay found is sent, then what was held back meanwhile, and from now on as it comes. */
|
|
20
|
+
ready(entries: Entry[]): void;
|
|
21
|
+
token(token: string): void;
|
|
22
|
+
heartbeat(): void;
|
|
23
|
+
/** Ends it: whoever reads it gets the end, not a broken pipe. */
|
|
24
|
+
close(): void;
|
|
25
|
+
private write;
|
|
26
|
+
private send;
|
|
27
|
+
private stop;
|
|
28
|
+
}
|
|
29
|
+
export interface Entry {
|
|
30
|
+
id: string;
|
|
31
|
+
event: string;
|
|
32
|
+
data: unknown;
|
|
33
|
+
}
|
|
34
|
+
export declare function encode(id: string): string;
|
|
35
|
+
export declare function decode(token: string): string;
|
|
36
|
+
/** Stream ids are `<ms>-<seq>`, ordered as two numbers. */
|
|
37
|
+
export declare function compare(a: string, b: string): number;
|
|
38
|
+
export declare const HEARTBEAT = 16000;
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { Readable } from 'node:stream';
|
|
2
|
+
/**
|
|
3
|
+
* One reader of a key: what it is sent, in order, and a heartbeat while nothing is.
|
|
4
|
+
*
|
|
5
|
+
* `cursor` is the id of the last event it was given. What arrives while it is being replayed to
|
|
6
|
+
* is held until the replay is done, and whatever it was given already is not given again: a replay
|
|
7
|
+
* and the channel may both carry the same event.
|
|
8
|
+
*/
|
|
9
|
+
export class Stream extends Readable {
|
|
10
|
+
cursor = null;
|
|
11
|
+
held = [];
|
|
12
|
+
interval = null;
|
|
13
|
+
ended = false;
|
|
14
|
+
constructor() {
|
|
15
|
+
super({ objectMode: true });
|
|
16
|
+
}
|
|
17
|
+
// a stream is read from when the reply is written, and the heartbeat starts with the reading
|
|
18
|
+
_read() {
|
|
19
|
+
this.interval ??= setInterval(() => this.heartbeat(), HEARTBEAT);
|
|
20
|
+
}
|
|
21
|
+
_destroy(error, callback) {
|
|
22
|
+
this.stop();
|
|
23
|
+
callback(error);
|
|
24
|
+
}
|
|
25
|
+
/** An event of the key, or one held back while the replay is in flight. */
|
|
26
|
+
deliver(entry) {
|
|
27
|
+
if (this.held !== null) {
|
|
28
|
+
this.held.push(entry);
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
this.write(entry);
|
|
32
|
+
}
|
|
33
|
+
/** What the replay found is sent, then what was held back meanwhile, and from now on as it comes. */
|
|
34
|
+
ready(entries) {
|
|
35
|
+
const held = this.held ?? [];
|
|
36
|
+
this.held = null;
|
|
37
|
+
for (const entry of entries)
|
|
38
|
+
this.write(entry);
|
|
39
|
+
for (const entry of held)
|
|
40
|
+
this.write(entry);
|
|
41
|
+
}
|
|
42
|
+
token(token) {
|
|
43
|
+
this.send({ event: 'token', data: token });
|
|
44
|
+
}
|
|
45
|
+
heartbeat() {
|
|
46
|
+
this.send('heartbeat ' + Date.now());
|
|
47
|
+
}
|
|
48
|
+
/** Ends it: whoever reads it gets the end, not a broken pipe. */
|
|
49
|
+
close() {
|
|
50
|
+
this.stop();
|
|
51
|
+
this.ended = true;
|
|
52
|
+
this.push(null);
|
|
53
|
+
}
|
|
54
|
+
write(entry) {
|
|
55
|
+
if (this.cursor !== null && compare(entry.id, this.cursor) <= 0)
|
|
56
|
+
return;
|
|
57
|
+
this.cursor = entry.id;
|
|
58
|
+
this.send({ event: entry.event, data: entry.data });
|
|
59
|
+
this.token(encode(entry.id));
|
|
60
|
+
}
|
|
61
|
+
send(message) {
|
|
62
|
+
if (this.ended || this.destroyed)
|
|
63
|
+
return;
|
|
64
|
+
this.push(message);
|
|
65
|
+
}
|
|
66
|
+
stop() {
|
|
67
|
+
if (this.interval !== null)
|
|
68
|
+
clearInterval(this.interval);
|
|
69
|
+
this.interval = null;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
export function encode(id) {
|
|
73
|
+
return Buffer.from(id).toString('base64url');
|
|
74
|
+
}
|
|
75
|
+
export function decode(token) {
|
|
76
|
+
return Buffer.from(token, 'base64url').toString();
|
|
77
|
+
}
|
|
78
|
+
/** Stream ids are `<ms>-<seq>`, ordered as two numbers. */
|
|
79
|
+
export function compare(a, b) {
|
|
80
|
+
const [ams, aseq] = a.split('-').map(BigInt);
|
|
81
|
+
const [bms, bseq] = b.split('-').map(BigInt);
|
|
82
|
+
if (ams !== bms)
|
|
83
|
+
return ams < bms ? -1 : 1;
|
|
84
|
+
if (aseq !== bseq)
|
|
85
|
+
return aseq < bseq ? -1 : 1;
|
|
86
|
+
return 0;
|
|
87
|
+
}
|
|
88
|
+
export const HEARTBEAT = 16_000;
|
|
89
|
+
//# sourceMappingURL=Stream.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Stream.js","sourceRoot":"","sources":["../../../source/directives/realtime/Stream.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAA;AAEtC;;;;;;GAMG;AACH,MAAM,OAAO,MAAO,SAAQ,QAAQ;IAC3B,MAAM,GAAkB,IAAI,CAAA;IAE3B,IAAI,GAAmB,EAAE,CAAA;IACzB,QAAQ,GAA0B,IAAI,CAAA;IACtC,KAAK,GAAG,KAAK,CAAA;IAErB;QACE,KAAK,CAAC,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC,CAAA;IAC7B,CAAC;IAED,6FAA6F;IAC7E,KAAK;QACnB,IAAI,CAAC,QAAQ,KAAK,WAAW,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,SAAS,EAAE,EAAE,SAAS,CAAC,CAAA;IAClE,CAAC;IAEe,QAAQ,CACtB,KAAmB,EACnB,QAAwC;QAExC,IAAI,CAAC,IAAI,EAAE,CAAA;QACX,QAAQ,CAAC,KAAK,CAAC,CAAA;IACjB,CAAC;IAED,2EAA2E;IACpE,OAAO,CAAC,KAAY;QACzB,IAAI,IAAI,CAAC,IAAI,KAAK,IAAI,EAAE,CAAC;YACvB,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;YAErB,OAAM;QACR,CAAC;QAED,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAA;IACnB,CAAC;IAED,qGAAqG;IAC9F,KAAK,CAAC,OAAgB;QAC3B,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,IAAI,EAAE,CAAA;QAE5B,IAAI,CAAC,IAAI,GAAG,IAAI,CAAA;QAEhB,KAAK,MAAM,KAAK,IAAI,OAAO;YAAE,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAA;QAC9C,KAAK,MAAM,KAAK,IAAI,IAAI;YAAE,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAA;IAC7C,CAAC;IAEM,KAAK,CAAC,KAAa;QACxB,IAAI,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAA;IAC5C,CAAC;IAEM,SAAS;QACd,IAAI,CAAC,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC,CAAA;IACtC,CAAC;IAED,iEAAiE;IAC1D,KAAK;QACV,IAAI,CAAC,IAAI,EAAE,CAAA;QACX,IAAI,CAAC,KAAK,GAAG,IAAI,CAAA;QACjB,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IACjB,CAAC;IAEO,KAAK,CAAC,KAAY;QACxB,IAAI,IAAI,CAAC,MAAM,KAAK,IAAI,IAAI,OAAO,CAAC,KAAK,CAAC,EAAE,EAAE,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC;YAAE,OAAM;QAEvE,IAAI,CAAC,MAAM,GAAG,KAAK,CAAC,EAAE,CAAA;QACtB,IAAI,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,CAAC,CAAA;QACnD,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAA;IAC9B,CAAC;IAEO,IAAI,CAAC,OAAgB;QAC3B,IAAI,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,SAAS;YAAE,OAAM;QAExC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;IACpB,CAAC;IAEO,IAAI;QACV,IAAI,IAAI,CAAC,QAAQ,KAAK,IAAI;YAAE,aAAa,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAA;QAExD,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAA;IACtB,CAAC;CACF;AAQD,MAAM,UAAU,MAAM,CAAC,EAAU;IAC/B,OAAO,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAA;AAC9C,CAAC;AAED,MAAM,UAAU,MAAM,CAAC,KAAa;IAClC,OAAO,MAAM,CAAC,IAAI,CAAC,KAAK,EAAE,WAAW,CAAC,CAAC,QAAQ,EAAE,CAAA;AACnD,CAAC;AAED,2DAA2D;AAC3D,MAAM,UAAU,OAAO,CAAC,CAAS,EAAE,CAAS;IAC1C,MAAM,CAAC,GAAG,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,CAAA;IAC5C,MAAM,CAAC,GAAG,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,CAAA;IAE5C,IAAI,GAAG,KAAK,GAAG;QAAE,OAAO,GAAG,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;IAC1C,IAAI,IAAI,KAAK,IAAI;QAAE,OAAO,IAAI,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;IAE9C,OAAO,CAAC,CAAA;AACV,CAAC;AAED,MAAM,CAAC,MAAM,SAAS,GAAG,MAAM,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../source/directives/realtime/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAA;AAExC,MAAM,CAAC,MAAM,QAAQ,GAAG,IAAI,QAAQ,EAAE,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function deliver(event: string): void;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { registry } from 'openspan';
|
|
2
|
+
/**
|
|
3
|
+
* Counted where the gateway pushes an event to the readers of a key it serves. Against
|
|
4
|
+
* `toa.realtime.routed`, which the components that write events count, it says what arrived.
|
|
5
|
+
*/
|
|
6
|
+
const meters = registry();
|
|
7
|
+
const delivered = meters.counter('toa.realtime.delivered', { event: null });
|
|
8
|
+
export function deliver(event) {
|
|
9
|
+
delivered.add(1, { event });
|
|
10
|
+
}
|
|
11
|
+
//# sourceMappingURL=measurements.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"measurements.js","sourceRoot":"","sources":["../../../source/directives/realtime/measurements.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAA;AAEnC;;;GAGG;AACH,MAAM,MAAM,GAAG,QAAQ,EAAE,CAAA;AAEzB,MAAM,SAAS,GAAG,MAAM,CAAC,OAAO,CAAC,wBAAwB,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAA;AAE3E,MAAM,UAAU,OAAO,CAAC,KAAa;IACnC,SAAS,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,KAAK,EAAE,CAAC,CAAA;AAC7B,CAAC"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { Connector, type Locator } from '@toa.io/core';
|
|
2
|
+
import type { Route } from '@toa.io/definitions/extensions.exposition/realtime';
|
|
3
|
+
import type { outbox } from '@toa.io/core/types';
|
|
4
|
+
/**
|
|
5
|
+
* Where a component's routed events go: the stream of each of their keys, and its channel. Each
|
|
6
|
+
* event of a row is rendered by the component's own condition and payload, fitted to what its
|
|
7
|
+
* route exposes, and written in one call per Redis its keys are kept on — to the streams of them
|
|
8
|
+
* that exist, which are the ones that are consumed.
|
|
9
|
+
*
|
|
10
|
+
* At least once: a row written again by the pump is written to the streams again.
|
|
11
|
+
*/
|
|
12
|
+
export declare class Destination extends Connector implements outbox.Destination {
|
|
13
|
+
readonly name = "realtime";
|
|
14
|
+
readonly renders: string[];
|
|
15
|
+
rendering?: outbox.Rendering;
|
|
16
|
+
private readonly locator;
|
|
17
|
+
private readonly routes;
|
|
18
|
+
private shards;
|
|
19
|
+
constructor(locator: Locator, routes: Route[]);
|
|
20
|
+
emit(row: outbox.Row): Promise<void>;
|
|
21
|
+
protected open(): Promise<void>;
|
|
22
|
+
protected close(): Promise<void>;
|
|
23
|
+
private route;
|
|
24
|
+
}
|