@toa.io/extensions.realtime 1.0.0-alpha.309 → 1.0.0-alpha.310
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/realtime.streams/operations/create.js +27 -15
- package/components/realtime.streams/operations/push.js +11 -3
- package/components/realtime.streams/source/create.ts +31 -18
- package/components/realtime.streams/source/push.ts +11 -2
- package/components/realtime.streams/types/index.d.ts +1 -1
- package/package.json +5 -5
- package/readme.md +4 -0
|
@@ -1,4 +1,11 @@
|
|
|
1
1
|
import { Stream, Stash } from './lib/index.js';
|
|
2
|
+
/**
|
|
3
|
+
* A stream is opened per call, and a key has as many of them as it has consumers: one stream read
|
|
4
|
+
* by several would hand each of its values to one of them only, and a consumer leaving would
|
|
5
|
+
* destroy it under the others. What arrives under a key is pushed to every stream of it; what
|
|
6
|
+
* belongs to a connection — the welcome, the token, the events missed since a token — to that
|
|
7
|
+
* connection's stream alone.
|
|
8
|
+
*/
|
|
2
9
|
export class Effect {
|
|
3
10
|
streams = new Map();
|
|
4
11
|
stash;
|
|
@@ -10,28 +17,25 @@ export class Effect {
|
|
|
10
17
|
this.stash = context.state.stash;
|
|
11
18
|
}
|
|
12
19
|
unmount() {
|
|
13
|
-
|
|
20
|
+
const streams = [...this.streams.values()].flatMap((set) => [...set]);
|
|
21
|
+
this.logs.info('Destroying streams', { count: streams.length });
|
|
14
22
|
// closed, not destroyed: destroying a stream that is still piped to a response
|
|
15
23
|
// makes end-of-stream report a premature close, which reaches no one and takes
|
|
16
24
|
// the process down.
|
|
17
|
-
for (const stream of
|
|
25
|
+
for (const stream of streams)
|
|
18
26
|
stream.close();
|
|
19
27
|
}
|
|
20
28
|
async execute(input) {
|
|
21
29
|
const key = input.key;
|
|
22
|
-
|
|
23
|
-
const stream = this.createStream(key);
|
|
24
|
-
this.streams.set(key, stream);
|
|
25
|
-
this.logs.debug('Stream created', { key });
|
|
26
|
-
}
|
|
30
|
+
const stream = this.createStream(key);
|
|
27
31
|
// welcome
|
|
28
|
-
setTimeout(() =>
|
|
32
|
+
setTimeout(() => stream.heartbeat(), 1000);
|
|
29
33
|
if (input.token === undefined)
|
|
30
34
|
void this.stash.connect(key).then((token) => {
|
|
31
35
|
if (token instanceof Error)
|
|
32
36
|
this.logs.error('Failed to connect to stash', { key, error: token });
|
|
33
37
|
else
|
|
34
|
-
|
|
38
|
+
stream.push({ event: 'token', data: token });
|
|
35
39
|
});
|
|
36
40
|
else
|
|
37
41
|
void this.stash.pop(key, input.token).then((result) => {
|
|
@@ -43,23 +47,31 @@ export class Effect {
|
|
|
43
47
|
this.logs.error('Failed to pop from stash', { key, error: result });
|
|
44
48
|
return;
|
|
45
49
|
}
|
|
46
|
-
|
|
47
|
-
if (stream === undefined)
|
|
50
|
+
if (stream.destroyed)
|
|
48
51
|
return;
|
|
49
52
|
const [token, events] = result;
|
|
50
53
|
for (const event of events)
|
|
51
54
|
stream.push({ event: event.event, data: event.data });
|
|
52
55
|
stream.push({ event: 'token', data: token });
|
|
53
56
|
});
|
|
54
|
-
return
|
|
57
|
+
return stream;
|
|
55
58
|
}
|
|
56
59
|
createStream(key) {
|
|
57
60
|
const stream = new Stream();
|
|
61
|
+
let set = this.streams.get(key);
|
|
62
|
+
if (set === undefined) {
|
|
63
|
+
set = new Set();
|
|
64
|
+
this.streams.set(key, set);
|
|
65
|
+
}
|
|
66
|
+
set.add(stream);
|
|
67
|
+
this.logs.debug('Stream created', { key, count: set.size });
|
|
58
68
|
stream.events.once('destroy', () => {
|
|
59
|
-
|
|
60
|
-
|
|
69
|
+
set.delete(stream);
|
|
70
|
+
if (set.size === 0)
|
|
71
|
+
this.streams.delete(key);
|
|
72
|
+
this.logs.debug('Stream destroyed', { key, count: set.size });
|
|
61
73
|
});
|
|
62
74
|
return stream;
|
|
63
75
|
}
|
|
64
76
|
}
|
|
65
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
77
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY3JlYXRlLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vc291cmNlL2NyZWF0ZS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFHQSxPQUFPLEVBQUUsTUFBTSxFQUFFLEtBQUssRUFBRSxNQUFNLGdCQUFnQixDQUFBO0FBRTlDOzs7Ozs7R0FNRztBQUNILE1BQU0sT0FBTyxNQUFNO0lBQ0EsT0FBTyxHQUFHLElBQUksR0FBRyxFQUF1QixDQUFBO0lBQ2pELEtBQUssQ0FBUTtJQUNiLElBQUksQ0FBSztJQUVWLEtBQUssQ0FBQyxPQUFnQjtRQUMzQixPQUFPLENBQUMsS0FBSyxDQUFDLE9BQU8sR0FBRyxJQUFJLENBQUMsT0FBTyxDQUFBO1FBQ3BDLE9BQU8sQ0FBQyxLQUFLLENBQUMsS0FBSyxHQUFHLElBQUksS0FBSyxDQUFDLE9BQU8sQ0FBQyxLQUFLLEVBQUUsT0FBTyxDQUFDLGFBQWEsRUFBRSxPQUFPLENBQUMsSUFBSSxDQUFDLENBQUE7UUFFbkYsSUFBSSxDQUFDLElBQUksR0FBRyxPQUFPLENBQUMsSUFBSSxDQUFBO1FBQ3hCLElBQUksQ0FBQyxLQUFLLEdBQUcsT0FBTyxDQUFDLEtBQUssQ0FBQyxLQUFLLENBQUE7SUFDbEMsQ0FBQztJQUVNLE9BQU87UUFDWixNQUFNLE9BQU8sR0FBRyxDQUFDLEdBQUcsSUFBSSxDQUFDLE9BQU8sQ0FBQyxNQUFNLEVBQUUsQ0FBQyxDQUFDLE9BQU8sQ0FBQyxDQUFDLEdBQUcsRUFBRSxFQUFFLENBQUMsQ0FBQyxHQUFHLEdBQUcsQ0FBQyxDQUFDLENBQUE7UUFFckUsSUFBSSxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsb0JBQW9CLEVBQUUsRUFBRSxLQUFLLEVBQUUsT0FBTyxDQUFDLE1BQU0sRUFBRSxDQUFDLENBQUE7UUFFL0QsK0VBQStFO1FBQy9FLCtFQUErRTtRQUMvRSxvQkFBb0I7UUFDcEIsS0FBSyxNQUFNLE1BQU0sSUFBSSxPQUFPO1lBQUUsTUFBTSxDQUFDLEtBQUssRUFBRSxD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
|
|
@@ -1,10 +1,18 @@
|
|
|
1
1
|
export async function effect({ key, event, data }, context) {
|
|
2
|
-
context
|
|
2
|
+
broadcast(context, key, { event, data });
|
|
3
3
|
void context.state.stash.push(key, event, data).then((token) => {
|
|
4
4
|
if (token instanceof Error)
|
|
5
5
|
context.logs.error('Failed to push to stash', { key, error: token });
|
|
6
6
|
else
|
|
7
|
-
context
|
|
7
|
+
broadcast(context, key, { event: 'token', data: token });
|
|
8
8
|
});
|
|
9
9
|
}
|
|
10
|
-
|
|
10
|
+
/** To every stream the key has: each is a consumer of its own. */
|
|
11
|
+
function broadcast(context, key, message) {
|
|
12
|
+
const streams = context.state.streams.get(key);
|
|
13
|
+
if (streams === undefined)
|
|
14
|
+
return;
|
|
15
|
+
for (const stream of streams)
|
|
16
|
+
stream.push(message);
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicHVzaC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3NvdXJjZS9wdXNoLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUVBLE1BQU0sQ0FBQyxLQUFLLFVBQVUsTUFBTSxDQUMxQixFQUFFLEdBQUcsRUFBRSxLQUFLLEVBQUUsSUFBSSxFQUFhLEVBQy9CLE9BQWdCO0lBRWhCLFNBQVMsQ0FBQyxPQUFPLEVBQUUsR0FBRyxFQUFFLEVBQUUsS0FBSyxFQUFFLElBQUksRUFBRSxDQUFDLENBQUE7SUFFeEMsS0FBSyxPQUFPLENBQUMsS0FBSyxDQUFDLEtBQUssQ0FBQyxJQUFJLENBQUMsR0FBRyxFQUFFLEtBQUssRUFBRSxJQUFJLENBQUMsQ0FBQyxJQUFJLENBQUMsQ0FBQyxLQUFLLEVBQUUsRUFBRTtRQUM3RCxJQUFJLEtBQUssWUFBWSxLQUFLO1lBQ3hCLE9BQU8sQ0FBQyxJQUFJLENBQUMsS0FBSyxDQUFDLHlCQUF5QixFQUFFLEVBQUUsR0FBRyxFQUFFLEtBQUssRUFBRSxLQUFLLEVBQUUsQ0FBQyxDQUFBOztZQUNqRSxTQUFTLENBQUMsT0FBTyxFQUFFLEdBQUcsRUFBRSxFQUFFLEtBQUssRUFBRSxPQUFPLEVBQUUsSUFBSSxFQUFFLEtBQUssRUFBRSxDQUFDLENBQUE7SUFDL0QsQ0FBQyxDQUFDLENBQUE7QUFDSixDQUFDO0FBRUQsa0VBQWtFO0FBQ2xFLFNBQVMsU0FBUyxDQUFDLE9BQWdCLEVBQUUsR0FBVyxFQUFFLE9BQWU7SUFDL0QsTUFBTSxPQUFPLEdBQUcsT0FBTyxDQUFDLEtBQUssQ0FBQyxPQUFPLENBQUMsR0FBRyxDQUFDLEdBQUcsQ0FBQyxDQUFBO0lBRTlDLElBQUksT0FBTyxLQUFLLFNBQVM7UUFBRSxPQUFNO0lBRWpDLEtBQUssTUFBTSxNQUFNLElBQUksT0FBTztRQUFFLE1BQU0sQ0FBQyxJQUFJLENBQUMsT0FBTyxDQUFDLENBQUE7QUFDcEQsQ0FBQyJ9
|
|
@@ -3,8 +3,15 @@ import { type Operation } from '@toa.io/bridges.node'
|
|
|
3
3
|
import type { Context } from '../types/index.d.ts'
|
|
4
4
|
import { Stream, Stash } from './lib/index.ts'
|
|
5
5
|
|
|
6
|
+
/**
|
|
7
|
+
* A stream is opened per call, and a key has as many of them as it has consumers: one stream read
|
|
8
|
+
* by several would hand each of its values to one of them only, and a consumer leaving would
|
|
9
|
+
* destroy it under the others. What arrives under a key is pushed to every stream of it; what
|
|
10
|
+
* belongs to a connection — the welcome, the token, the events missed since a token — to that
|
|
11
|
+
* connection's stream alone.
|
|
12
|
+
*/
|
|
6
13
|
export class Effect implements Operation {
|
|
7
|
-
private readonly streams = new Map<string, Stream
|
|
14
|
+
private readonly streams = new Map<string, Set<Stream>>()
|
|
8
15
|
private stash!: Stash
|
|
9
16
|
private logs: any
|
|
10
17
|
|
|
@@ -17,32 +24,28 @@ export class Effect implements Operation {
|
|
|
17
24
|
}
|
|
18
25
|
|
|
19
26
|
public unmount(): void {
|
|
20
|
-
|
|
27
|
+
const streams = [...this.streams.values()].flatMap((set) => [...set])
|
|
28
|
+
|
|
29
|
+
this.logs.info('Destroying streams', { count: streams.length })
|
|
21
30
|
|
|
22
31
|
// closed, not destroyed: destroying a stream that is still piped to a response
|
|
23
32
|
// makes end-of-stream report a premature close, which reaches no one and takes
|
|
24
33
|
// the process down.
|
|
25
|
-
for (const stream of
|
|
34
|
+
for (const stream of streams) stream.close()
|
|
26
35
|
}
|
|
27
36
|
|
|
28
37
|
public async execute(input: Input): Promise<Readable> {
|
|
29
38
|
const key = input.key
|
|
30
|
-
|
|
31
|
-
if (!this.streams.has(key)) {
|
|
32
|
-
const stream = this.createStream(key)
|
|
33
|
-
|
|
34
|
-
this.streams.set(key, stream)
|
|
35
|
-
this.logs.debug('Stream created', { key })
|
|
36
|
-
}
|
|
39
|
+
const stream = this.createStream(key)
|
|
37
40
|
|
|
38
41
|
// welcome
|
|
39
|
-
setTimeout(() =>
|
|
42
|
+
setTimeout(() => stream.heartbeat(), 1000)
|
|
40
43
|
|
|
41
44
|
if (input.token === undefined)
|
|
42
45
|
void this.stash.connect(key).then((token) => {
|
|
43
46
|
if (token instanceof Error)
|
|
44
47
|
this.logs.error('Failed to connect to stash', { key, error: token })
|
|
45
|
-
else
|
|
48
|
+
else stream.push({ event: 'token', data: token })
|
|
46
49
|
})
|
|
47
50
|
else
|
|
48
51
|
void this.stash.pop(key, input.token).then((result) => {
|
|
@@ -56,9 +59,7 @@ export class Effect implements Operation {
|
|
|
56
59
|
return
|
|
57
60
|
}
|
|
58
61
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
if (stream === undefined) return
|
|
62
|
+
if (stream.destroyed) return
|
|
62
63
|
|
|
63
64
|
const [token, events] = result
|
|
64
65
|
|
|
@@ -68,15 +69,27 @@ export class Effect implements Operation {
|
|
|
68
69
|
stream.push({ event: 'token', data: token })
|
|
69
70
|
})
|
|
70
71
|
|
|
71
|
-
return
|
|
72
|
+
return stream
|
|
72
73
|
}
|
|
73
74
|
|
|
74
75
|
private createStream(key: string): Stream {
|
|
75
76
|
const stream = new Stream()
|
|
77
|
+
let set = this.streams.get(key)
|
|
78
|
+
|
|
79
|
+
if (set === undefined) {
|
|
80
|
+
set = new Set()
|
|
81
|
+
this.streams.set(key, set)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
set.add(stream)
|
|
85
|
+
this.logs.debug('Stream created', { key, count: set.size })
|
|
76
86
|
|
|
77
87
|
stream.events.once('destroy', () => {
|
|
78
|
-
|
|
79
|
-
|
|
88
|
+
set.delete(stream)
|
|
89
|
+
|
|
90
|
+
if (set.size === 0) this.streams.delete(key)
|
|
91
|
+
|
|
92
|
+
this.logs.debug('Stream destroyed', { key, count: set.size })
|
|
80
93
|
})
|
|
81
94
|
|
|
82
95
|
return stream
|
|
@@ -4,11 +4,20 @@ export async function effect(
|
|
|
4
4
|
{ key, event, data }: PushInput,
|
|
5
5
|
context: Context
|
|
6
6
|
): Promise<void> {
|
|
7
|
-
context
|
|
7
|
+
broadcast(context, key, { event, data })
|
|
8
8
|
|
|
9
9
|
void context.state.stash.push(key, event, data).then((token) => {
|
|
10
10
|
if (token instanceof Error)
|
|
11
11
|
context.logs.error('Failed to push to stash', { key, error: token })
|
|
12
|
-
else context
|
|
12
|
+
else broadcast(context, key, { event: 'token', data: token })
|
|
13
13
|
})
|
|
14
14
|
}
|
|
15
|
+
|
|
16
|
+
/** To every stream the key has: each is a consumer of its own. */
|
|
17
|
+
function broadcast(context: Context, key: string, message: object): void {
|
|
18
|
+
const streams = context.state.streams.get(key)
|
|
19
|
+
|
|
20
|
+
if (streams === undefined) return
|
|
21
|
+
|
|
22
|
+
for (const stream of streams) stream.push(message)
|
|
23
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@toa.io/extensions.realtime",
|
|
3
|
-
"version": "1.0.0-alpha.
|
|
3
|
+
"version": "1.0.0-alpha.310",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Toa Realtime",
|
|
6
6
|
"author": "temich <tema.gurtovoy@gmail.com>",
|
|
@@ -26,14 +26,14 @@
|
|
|
26
26
|
"!**/.env*"
|
|
27
27
|
],
|
|
28
28
|
"dependencies": {
|
|
29
|
-
"@toa.io/core": "1.0.0-alpha.
|
|
30
|
-
"@toa.io/definitions": "1.0.0-alpha.
|
|
31
|
-
"@toa.io/generic": "1.0.0-alpha.
|
|
29
|
+
"@toa.io/core": "1.0.0-alpha.310",
|
|
30
|
+
"@toa.io/definitions": "1.0.0-alpha.310",
|
|
31
|
+
"@toa.io/generic": "1.0.0-alpha.310",
|
|
32
32
|
"openspan": "1.0.0-alpha.305"
|
|
33
33
|
},
|
|
34
34
|
"scripts": {
|
|
35
35
|
"transpile": "npx tsc && npx tsc -p ./components/realtime.streams",
|
|
36
36
|
"features": "TSX_TSCONFIG_PATH=features/steps/tsconfig.json NODE_OPTIONS=--import=tsx cucumber-js"
|
|
37
37
|
},
|
|
38
|
-
"gitHead": "
|
|
38
|
+
"gitHead": "d952c57f77279729e26d52d50c6df47138ad0a4f"
|
|
39
39
|
}
|
package/readme.md
CHANGED
|
@@ -109,6 +109,10 @@ the [`auth:id: key`](/extensions/exposition/documentation/access.md#id) authoriz
|
|
|
109
109
|
Opening a stream is not a read, and `GET` is the method a client opens one with, so the route
|
|
110
110
|
declares [`io:readonly: false`](/extensions/exposition/documentation/io.md#readonly).
|
|
111
111
|
|
|
112
|
+
A key may be consumed by several clients at once — the same user on two devices, or in two
|
|
113
|
+
tabs. Each of them receives every event routed to the key, and one of them disconnecting leaves
|
|
114
|
+
the others connected.
|
|
115
|
+
|
|
112
116
|
Refer to the [Exposition extension](/extensions/exposition) for more details:
|
|
113
117
|
|
|
114
118
|
- [Multipart responses](/extensions/exposition/documentation/protocol.md#multipart-types)
|