@toa.io/extensions.realtime 1.0.0-alpha.21 → 1.0.0-alpha.213
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/{streams → realtime.streams}/manifest.toa.yaml +12 -1
- package/components/{streams → realtime.streams}/operations/create.d.ts +6 -2
- package/components/realtime.streams/operations/create.js +62 -0
- package/components/realtime.streams/operations/create.js.map +1 -0
- package/components/realtime.streams/operations/lib/Stash.d.ts +20 -0
- package/components/realtime.streams/operations/lib/Stash.js +71 -0
- package/components/realtime.streams/operations/lib/Stash.js.map +1 -0
- package/components/realtime.streams/operations/lib/Stream.d.ts +11 -0
- package/components/realtime.streams/operations/lib/Stream.js +34 -0
- package/components/realtime.streams/operations/lib/Stream.js.map +1 -0
- package/components/realtime.streams/operations/lib/index.d.ts +2 -0
- package/components/realtime.streams/operations/lib/index.js +8 -0
- package/components/realtime.streams/operations/lib/index.js.map +1 -0
- package/components/realtime.streams/operations/lib/types.d.ts +22 -0
- package/components/realtime.streams/operations/lib/types.js.map +1 -0
- package/components/{streams → realtime.streams}/operations/push.d.ts +1 -1
- package/components/realtime.streams/operations/push.js +14 -0
- package/components/realtime.streams/operations/push.js.map +1 -0
- package/components/realtime.streams/operations/tsconfig.tsbuildinfo +1 -0
- package/components/realtime.streams/source/create.ts +88 -0
- package/components/realtime.streams/source/lib/Stash.ts +105 -0
- package/components/realtime.streams/source/lib/Stream.ts +40 -0
- package/components/realtime.streams/source/lib/index.ts +2 -0
- package/components/realtime.streams/source/lib/types.ts +24 -0
- package/components/realtime.streams/source/push.ts +12 -0
- package/features/static.feature +89 -0
- package/features/steps/Realtime.ts +2 -2
- package/features/steps/Streams.ts +44 -9
- package/features/steps/components/messages/manifest.toa.yaml +2 -0
- package/package.json +6 -2
- package/readme.md +32 -6
- package/source/Composition.ts +18 -7
- package/source/Realtime.ts +4 -2
- package/source/Receiver.ts +49 -0
- package/source/Routes.ts +7 -27
- package/source/extension.ts +65 -0
- package/source/index.ts +1 -0
- package/transpiled/Composition.d.ts +7 -1
- package/transpiled/Composition.js +12 -5
- package/transpiled/Composition.js.map +1 -1
- package/transpiled/Realtime.js +5 -3
- package/transpiled/Realtime.js.map +1 -1
- package/transpiled/Receiver.d.ts +11 -0
- package/transpiled/Receiver.js +41 -0
- package/transpiled/Receiver.js.map +1 -0
- package/transpiled/Routes.d.ts +1 -2
- package/transpiled/Routes.js +5 -17
- package/transpiled/Routes.js.map +1 -1
- package/transpiled/extension.d.ts +8 -0
- package/transpiled/extension.js +45 -0
- package/transpiled/extension.js.map +1 -0
- package/transpiled/index.d.ts +1 -0
- package/transpiled/index.js +15 -0
- package/transpiled/index.js.map +1 -1
- package/transpiled/tsconfig.tsbuildinfo +1 -1
- package/components/streams/operations/create.js +0 -29
- package/components/streams/operations/create.js.map +0 -1
- package/components/streams/operations/lib/stream.d.ts +0 -10
- package/components/streams/operations/lib/stream.js +0 -31
- package/components/streams/operations/lib/stream.js.map +0 -1
- package/components/streams/operations/push.js +0 -8
- package/components/streams/operations/push.js.map +0 -1
- package/components/streams/operations/tsconfig.tsbuildinfo +0 -1
- package/components/streams/operations/types.d.ts +0 -11
- package/components/streams/operations/types.js.map +0 -1
- package/components/streams/source/create.ts +0 -38
- package/components/streams/source/lib/stream.ts +0 -37
- package/components/streams/source/push.ts +0 -5
- package/components/streams/source/types.ts +0 -13
- package/stage/streams.test.ts +0 -128
- /package/components/{streams/operations → realtime.streams/operations/lib}/types.js +0 -0
- /package/components/{streams → realtime.streams}/tsconfig.json +0 -0
package/features/static.feature
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
Feature: Static routes
|
|
2
2
|
|
|
3
|
+
Scenario: Debug
|
|
4
|
+
Given the `messages` component is running with routes:
|
|
5
|
+
"""yaml
|
|
6
|
+
created: [sender, recipient]
|
|
7
|
+
"""
|
|
8
|
+
And the stream `004e02a959c04cecaf111827f91caa36` is consumed
|
|
9
|
+
|
|
3
10
|
Scenario: Routing an event
|
|
4
11
|
Given the `messages` component is running with routes:
|
|
5
12
|
"""yaml
|
|
@@ -30,3 +37,85 @@ Feature: Static routes
|
|
|
30
37
|
recipient: 004e02a959c04cecaf111827f91caa36
|
|
31
38
|
text: Hello!
|
|
32
39
|
"""
|
|
40
|
+
|
|
41
|
+
Scenario: Routing an event using array
|
|
42
|
+
Given the `messages` component is running with routes:
|
|
43
|
+
"""yaml
|
|
44
|
+
created: watchers
|
|
45
|
+
"""
|
|
46
|
+
And the stream `51c15a7290ce47e0af8ec41d60dccb32` is consumed
|
|
47
|
+
And the stream `bb27366509a64178a39313aac42435ae` is consumed
|
|
48
|
+
When the `messages.create` is called with:
|
|
49
|
+
"""yaml
|
|
50
|
+
input:
|
|
51
|
+
watchers:
|
|
52
|
+
- 51c15a7290ce47e0af8ec41d60dccb32
|
|
53
|
+
- bb27366509a64178a39313aac42435ae
|
|
54
|
+
sender: 96db5a47a8244eb3b21820781b7d596e
|
|
55
|
+
recipient: 004e02a959c04cecaf111827f91caa36
|
|
56
|
+
text: Hello!
|
|
57
|
+
"""
|
|
58
|
+
Then an event is received from the stream `51c15a7290ce47e0af8ec41d60dccb32`:
|
|
59
|
+
"""yaml
|
|
60
|
+
event: default.messages.created
|
|
61
|
+
data:
|
|
62
|
+
sender: 96db5a47a8244eb3b21820781b7d596e
|
|
63
|
+
recipient: 004e02a959c04cecaf111827f91caa36
|
|
64
|
+
text: Hello!
|
|
65
|
+
"""
|
|
66
|
+
And an event is received from the stream `bb27366509a64178a39313aac42435ae`:
|
|
67
|
+
"""yaml
|
|
68
|
+
event: default.messages.created
|
|
69
|
+
data:
|
|
70
|
+
sender: 96db5a47a8244eb3b21820781b7d596e
|
|
71
|
+
recipient: 004e02a959c04cecaf111827f91caa36
|
|
72
|
+
text: Hello!
|
|
73
|
+
"""
|
|
74
|
+
|
|
75
|
+
Scenario: Continuation token
|
|
76
|
+
Given the `messages` component is running with routes:
|
|
77
|
+
"""yaml
|
|
78
|
+
created: [sender, recipient]
|
|
79
|
+
"""
|
|
80
|
+
And the stream `004e02a959c04cecaf111827f91caa36` is consumed
|
|
81
|
+
And an event is received from the stream `004e02a959c04cecaf111827f91caa36`:
|
|
82
|
+
"""yaml
|
|
83
|
+
event: token
|
|
84
|
+
"""
|
|
85
|
+
|
|
86
|
+
When the consumer `004e02a959c04cecaf111827f91caa36` is disconnected
|
|
87
|
+
And the `messages.create` is called with:
|
|
88
|
+
"""yaml
|
|
89
|
+
input:
|
|
90
|
+
sender: 96db5a47a8244eb3b21820781b7d596e
|
|
91
|
+
recipient: 004e02a959c04cecaf111827f91caa36
|
|
92
|
+
text: Hello!
|
|
93
|
+
"""
|
|
94
|
+
And the `messages.create` is called with:
|
|
95
|
+
"""yaml
|
|
96
|
+
input:
|
|
97
|
+
sender: 004e02a959c04cecaf111827f91caa36
|
|
98
|
+
recipient: 96db5a47a8244eb3b21820781b7d596e
|
|
99
|
+
text: Hi!
|
|
100
|
+
"""
|
|
101
|
+
And the consumer `004e02a959c04cecaf111827f91caa36` is reconnected
|
|
102
|
+
Then an event is received from the stream `004e02a959c04cecaf111827f91caa36`:
|
|
103
|
+
"""yaml
|
|
104
|
+
event: default.messages.created
|
|
105
|
+
data:
|
|
106
|
+
sender: 96db5a47a8244eb3b21820781b7d596e
|
|
107
|
+
recipient: 004e02a959c04cecaf111827f91caa36
|
|
108
|
+
text: Hello!
|
|
109
|
+
"""
|
|
110
|
+
And an event is received from the stream `004e02a959c04cecaf111827f91caa36`:
|
|
111
|
+
"""yaml
|
|
112
|
+
event: default.messages.created
|
|
113
|
+
data:
|
|
114
|
+
sender: 004e02a959c04cecaf111827f91caa36
|
|
115
|
+
recipient: 96db5a47a8244eb3b21820781b7d596e
|
|
116
|
+
text: Hi!
|
|
117
|
+
"""
|
|
118
|
+
And an event is received from the stream `004e02a959c04cecaf111827f91caa36`:
|
|
119
|
+
"""yaml
|
|
120
|
+
event: token
|
|
121
|
+
"""
|
|
@@ -2,7 +2,7 @@ import * as boot from '@toa.io/boot'
|
|
|
2
2
|
import { encode } from '@toa.io/generic'
|
|
3
3
|
import { type Connector } from '@toa.io/core'
|
|
4
4
|
import { after, binding } from 'cucumber-tsflow'
|
|
5
|
-
import { Factory } from '../../source
|
|
5
|
+
import { Factory } from '../../source'
|
|
6
6
|
|
|
7
7
|
@binding()
|
|
8
8
|
export class Realtime {
|
|
@@ -15,7 +15,7 @@ export class Realtime {
|
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
@after()
|
|
18
|
-
|
|
18
|
+
public async shutdown (): Promise<void> {
|
|
19
19
|
this.connected = false
|
|
20
20
|
|
|
21
21
|
await this.service.disconnect()
|
|
@@ -1,35 +1,38 @@
|
|
|
1
|
-
import
|
|
1
|
+
import * as assert from 'node:assert'
|
|
2
|
+
import { setTimeout } from 'node:timers/promises'
|
|
2
3
|
import { after, binding, given, then } from 'cucumber-tsflow'
|
|
3
4
|
import { match } from '@toa.io/generic'
|
|
4
|
-
|
|
5
|
-
import { type Component } from '@toa.io/core'
|
|
6
5
|
import { parse } from '@toa.io/yaml'
|
|
7
6
|
import * as stage from '@toa.io/userland/stage'
|
|
8
7
|
import { Realtime } from './Realtime'
|
|
8
|
+
import type { Readable } from 'node:stream'
|
|
9
|
+
import type { Component } from '@toa.io/core'
|
|
9
10
|
|
|
10
11
|
@binding([Realtime])
|
|
11
12
|
export class Streams {
|
|
12
13
|
private readonly realtime: Realtime
|
|
13
14
|
private remote: Component | null = null
|
|
14
15
|
private streams: Record<string, Readable> = {}
|
|
15
|
-
private events: Record<string,
|
|
16
|
+
private events: Record<string, Event[]> = {}
|
|
16
17
|
|
|
17
18
|
public constructor (realtime: Realtime) {
|
|
18
19
|
this.realtime = realtime
|
|
19
20
|
}
|
|
20
21
|
|
|
21
|
-
@given('the stream `{word}` is consumed')
|
|
22
|
+
@given('the stream `{word}` is consumed', { timeout: 30_000 })
|
|
22
23
|
public async consume (key: string): Promise<void> {
|
|
23
24
|
await this.realtime.serve()
|
|
24
25
|
|
|
25
26
|
this.remote ??= await stage.remote('realtime.streams')
|
|
26
27
|
this.events[key] = []
|
|
27
|
-
|
|
28
|
-
this.
|
|
28
|
+
|
|
29
|
+
await this.createStream(key)
|
|
29
30
|
}
|
|
30
31
|
|
|
31
32
|
@then('an event is received from the stream `{word}`:')
|
|
32
|
-
public received (key: string, yaml: string): void {
|
|
33
|
+
public async received (key: string, yaml: string): Promise<void> {
|
|
34
|
+
await setTimeout(100)
|
|
35
|
+
|
|
33
36
|
const expected = parse<object>(yaml)
|
|
34
37
|
|
|
35
38
|
for (const event of this.events[key])
|
|
@@ -39,12 +42,44 @@ export class Streams {
|
|
|
39
42
|
throw new Error('No matching event received')
|
|
40
43
|
}
|
|
41
44
|
|
|
45
|
+
@then('the consumer `{word}` is disconnected')
|
|
46
|
+
public disconnected (key: string): void {
|
|
47
|
+
this.streams[key]?.destroy()
|
|
48
|
+
delete this.streams[key]
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
@then('the consumer `{word}` is reconnected')
|
|
52
|
+
public async reconnected (key: string): Promise<void> {
|
|
53
|
+
const last = this.events[key].findLast((event) => event.event === 'token')
|
|
54
|
+
|
|
55
|
+
assert.ok(last, `No last event found for stream ${key}`)
|
|
56
|
+
|
|
57
|
+
await this.createStream(key, last.data as string)
|
|
58
|
+
}
|
|
59
|
+
|
|
42
60
|
@after()
|
|
43
|
-
|
|
61
|
+
public async shutdown (): Promise<void> {
|
|
44
62
|
for (const stream of Object.values(this.streams))
|
|
45
63
|
stream.destroy()
|
|
46
64
|
|
|
47
65
|
this.streams = {}
|
|
48
66
|
this.events = {}
|
|
67
|
+
|
|
68
|
+
await setTimeout(100)
|
|
49
69
|
}
|
|
70
|
+
|
|
71
|
+
private async createStream (key: string, token?: string): Promise<void> {
|
|
72
|
+
this.streams[key] = await this.remote!.invoke('create', { input: { key, token } })
|
|
73
|
+
this.streams[key].on('data', (event: Event) => {
|
|
74
|
+
console.log('[TEST] Received event', event)
|
|
75
|
+
this.events[key]?.push(event)
|
|
76
|
+
})
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
interface Event {
|
|
81
|
+
key: string
|
|
82
|
+
token: string
|
|
83
|
+
event: string
|
|
84
|
+
data?: unknown
|
|
50
85
|
}
|
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.213",
|
|
4
4
|
"description": "Toa Realtime",
|
|
5
5
|
"author": "temich <tema.gurtovoy@gmail.com>",
|
|
6
6
|
"homepage": "https://github.com/toa-io/toa#readme",
|
|
@@ -16,6 +16,10 @@
|
|
|
16
16
|
"publishConfig": {
|
|
17
17
|
"access": "public"
|
|
18
18
|
},
|
|
19
|
+
"dependencies": {
|
|
20
|
+
"@toa.io/core": "1.0.0-alpha.212",
|
|
21
|
+
"openspan": "1.0.0-alpha.173"
|
|
22
|
+
},
|
|
19
23
|
"jest": {
|
|
20
24
|
"preset": "ts-jest",
|
|
21
25
|
"testEnvironment": "node"
|
|
@@ -24,5 +28,5 @@
|
|
|
24
28
|
"transpile": "npx tsc && npx tsc -p ./components/streams",
|
|
25
29
|
"features": "npx cucumber-js"
|
|
26
30
|
},
|
|
27
|
-
"gitHead": "
|
|
31
|
+
"gitHead": "80ad08ebad7151c6777767228a0b832efc71f8c3"
|
|
28
32
|
}
|
package/readme.md
CHANGED
|
@@ -10,17 +10,20 @@
|
|
|
10
10
|
</a>
|
|
11
11
|
|
|
12
12
|
Realtime extension combines application events into streams according to defined routes.
|
|
13
|
-
Clients may consume these streams [via Exposition](
|
|
13
|
+
Clients may consume these streams [via Exposition](/extensions/exposition).
|
|
14
|
+
|
|
15
|
+
If stream is idle for 16 seconds, a `heartbeat` message is sent.
|
|
14
16
|
|
|
15
17
|
## Static routes
|
|
16
18
|
|
|
17
19
|
Static route specifies an event that should be combined into a stream using specified property of
|
|
18
|
-
event's payload as a stream key.
|
|
20
|
+
event's payload as a stream key or an array of stream keys.
|
|
19
21
|
|
|
20
22
|
Static routes may be defined in Component manifest or the Context annotation.
|
|
21
23
|
|
|
22
24
|
```yaml
|
|
23
25
|
# manifest.toa.yaml
|
|
26
|
+
|
|
24
27
|
name: users
|
|
25
28
|
|
|
26
29
|
realtime:
|
|
@@ -29,13 +32,24 @@ realtime:
|
|
|
29
32
|
|
|
30
33
|
```yaml
|
|
31
34
|
# context.toa.yaml
|
|
35
|
+
|
|
32
36
|
realtime:
|
|
33
37
|
users.updated: id
|
|
34
|
-
orders.created:
|
|
38
|
+
orders.created: customer_id
|
|
35
39
|
```
|
|
36
40
|
|
|
37
41
|
In case of conflict, the Context annotation takes precedence.
|
|
38
42
|
|
|
43
|
+
Multiple stream keys may be defined for a single event.
|
|
44
|
+
|
|
45
|
+
```yaml
|
|
46
|
+
# manifest.toa.yaml
|
|
47
|
+
name: messages
|
|
48
|
+
|
|
49
|
+
realtime:
|
|
50
|
+
updated: [sender_id, recipient_id]
|
|
51
|
+
```
|
|
52
|
+
|
|
39
53
|
### Static route examples
|
|
40
54
|
|
|
41
55
|
Given two rules: `users.updated: id` and `orders.created: customer_id`,
|
|
@@ -92,8 +106,20 @@ Realtime extension, and are
|
|
|
92
106
|
accessible via the `/realtime/streams/:key/` resource with
|
|
93
107
|
the [`auth:id: key`](/extensions/exposition/documentation/access.md#id) authorization rule.
|
|
94
108
|
|
|
95
|
-
Refer to the [Exposition extension](/extensions/exposition) for more
|
|
96
|
-
details:
|
|
109
|
+
Refer to the [Exposition extension](/extensions/exposition) for more details:
|
|
97
110
|
|
|
98
|
-
- [
|
|
111
|
+
- [Multipart responses](/extensions/exposition/documentation/protocol.md#multipart-types)
|
|
99
112
|
- [Access authorization](/extensions/exposition/documentation/access.md)
|
|
113
|
+
|
|
114
|
+
## Resources management
|
|
115
|
+
|
|
116
|
+
Resource requests and limits can be specified by `resources` annotation:
|
|
117
|
+
|
|
118
|
+
```yaml
|
|
119
|
+
# context.toa.yaml
|
|
120
|
+
|
|
121
|
+
realtime:
|
|
122
|
+
resources:
|
|
123
|
+
cpu: [100m, 500m]
|
|
124
|
+
memory: [100Mi, 200Mi]
|
|
125
|
+
```
|
package/source/Composition.ts
CHANGED
|
@@ -18,16 +18,10 @@ export class Composition extends Connector {
|
|
|
18
18
|
await composition.connect()
|
|
19
19
|
|
|
20
20
|
this.depends(composition)
|
|
21
|
-
|
|
22
|
-
console.info('Composition complete.')
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
protected override dispose (): void {
|
|
26
|
-
console.info('Composition shutdown complete.')
|
|
27
21
|
}
|
|
28
22
|
}
|
|
29
23
|
|
|
30
|
-
function find (): string[] {
|
|
24
|
+
export function find (): string[] {
|
|
31
25
|
return entries().map((entry) => resolve(ROOT, entry.name))
|
|
32
26
|
}
|
|
33
27
|
|
|
@@ -37,4 +31,21 @@ function entries (): Dirent[] {
|
|
|
37
31
|
return entries.filter((entry) => entry.isDirectory())
|
|
38
32
|
}
|
|
39
33
|
|
|
34
|
+
export function components (): Components {
|
|
35
|
+
const labels: string[] = []
|
|
36
|
+
const paths: string[] = []
|
|
37
|
+
|
|
38
|
+
for (const entry of entries()) {
|
|
39
|
+
labels.push(entry.name.replace('.', '-'))
|
|
40
|
+
paths.push(resolve(ROOT, entry.name))
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return { labels, paths }
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
interface Components {
|
|
47
|
+
labels: string[]
|
|
48
|
+
paths: string[]
|
|
49
|
+
}
|
|
50
|
+
|
|
40
51
|
const ROOT = resolve(__dirname, '../components/')
|
package/source/Realtime.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { console } from 'openspan'
|
|
1
2
|
import { type Component, Connector } from '@toa.io/core'
|
|
2
3
|
import { type Routes } from './Routes'
|
|
3
4
|
|
|
@@ -19,15 +20,16 @@ export class Realtime extends Connector {
|
|
|
19
20
|
|
|
20
21
|
await this.streams.connect()
|
|
21
22
|
|
|
22
|
-
console.
|
|
23
|
+
console.info('Realtime service started')
|
|
23
24
|
}
|
|
24
25
|
|
|
25
26
|
protected override dispose (): void {
|
|
26
|
-
console.
|
|
27
|
+
console.info('Realtime service shutdown complete')
|
|
27
28
|
}
|
|
28
29
|
|
|
29
30
|
private push (event: Event): void {
|
|
30
31
|
void this.streams?.invoke('push', { input: event })
|
|
32
|
+
.catch((error) => console.error('Realtime push failed', error))
|
|
31
33
|
}
|
|
32
34
|
}
|
|
33
35
|
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { console } from 'openspan'
|
|
2
|
+
import { Connector, type Message } from '@toa.io/core'
|
|
3
|
+
import type { Readable } from 'node:stream'
|
|
4
|
+
|
|
5
|
+
export class Receiver extends Connector {
|
|
6
|
+
private readonly event: string
|
|
7
|
+
private readonly properties: string[]
|
|
8
|
+
private readonly stream: Readable
|
|
9
|
+
|
|
10
|
+
public constructor (event: string, properties: string[], stream: Readable) {
|
|
11
|
+
super()
|
|
12
|
+
|
|
13
|
+
this.event = event
|
|
14
|
+
this.properties = properties
|
|
15
|
+
this.stream = stream
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
public receive (message: Message<Record<string, string>>): void {
|
|
19
|
+
for (const property of this.properties) {
|
|
20
|
+
const key = message.payload[property]
|
|
21
|
+
|
|
22
|
+
if (key === undefined) {
|
|
23
|
+
console.debug('Event does not contain key property',
|
|
24
|
+
{ property, event: this.event })
|
|
25
|
+
|
|
26
|
+
continue
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (Array.isArray(key))
|
|
30
|
+
// eslint-disable-next-line max-depth
|
|
31
|
+
for (const k of key as string[])
|
|
32
|
+
this.push(k, message.payload)
|
|
33
|
+
else
|
|
34
|
+
this.push(key, message.payload)
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
private push (key: string | null, data: Record<string, string>): void {
|
|
39
|
+
if (key === null || typeof key === 'undefined') {
|
|
40
|
+
console.debug('Key is null or undefined, skipping', { key, event: this.event })
|
|
41
|
+
|
|
42
|
+
return
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
console.debug('Pushing event to stream', { key, event: this.event, data })
|
|
46
|
+
|
|
47
|
+
this.stream.push({ key, event: this.event, data })
|
|
48
|
+
}
|
|
49
|
+
}
|
package/source/Routes.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { Readable } from 'node:stream'
|
|
2
|
-
import {
|
|
2
|
+
import { console } from 'openspan'
|
|
3
|
+
import { Connector } from '@toa.io/core'
|
|
3
4
|
import { decode } from '@toa.io/generic'
|
|
4
|
-
import {
|
|
5
|
+
import { Receiver } from './Receiver'
|
|
6
|
+
import type { Bootloader } from './Factory'
|
|
5
7
|
|
|
6
8
|
export class Routes extends Connector {
|
|
7
9
|
public events = new Events()
|
|
@@ -26,7 +28,7 @@ export class Routes extends Connector {
|
|
|
26
28
|
const creating = []
|
|
27
29
|
|
|
28
30
|
for (const { event, properties } of routes) {
|
|
29
|
-
const consumer = this.boot.receive(event,
|
|
31
|
+
const consumer = this.boot.receive(event, new Receiver(event, properties, this.events))
|
|
30
32
|
|
|
31
33
|
creating.push(consumer)
|
|
32
34
|
}
|
|
@@ -39,29 +41,11 @@ export class Routes extends Connector {
|
|
|
39
41
|
await Promise.all(connecting)
|
|
40
42
|
this.depends(consumers)
|
|
41
43
|
|
|
42
|
-
console.
|
|
44
|
+
console.info('Event sources connected', { count: creating.length })
|
|
43
45
|
}
|
|
44
46
|
|
|
45
47
|
public override async close (): Promise<void> {
|
|
46
|
-
console.
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
private getReceiver (event: string, properties: string[]): Receiver {
|
|
50
|
-
return {
|
|
51
|
-
receive: (message: Message<Record<string, string>>) => {
|
|
52
|
-
for (const property of properties) {
|
|
53
|
-
const key = message.payload[property]
|
|
54
|
-
|
|
55
|
-
if (key === undefined) {
|
|
56
|
-
console.error(`Event '${event}' does not contain the '${property}' property.`)
|
|
57
|
-
|
|
58
|
-
return
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
this.events.push({ key, event, data: message.payload })
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
}
|
|
48
|
+
console.info('Event sources disconnected')
|
|
65
49
|
}
|
|
66
50
|
}
|
|
67
51
|
|
|
@@ -78,7 +62,3 @@ export interface Route {
|
|
|
78
62
|
event: string
|
|
79
63
|
properties: string[]
|
|
80
64
|
}
|
|
81
|
-
|
|
82
|
-
interface Receiver {
|
|
83
|
-
receive: (message: Message<Record<string, string>>) => void
|
|
84
|
-
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { encode } from '@toa.io/generic'
|
|
2
|
+
import { components } from './Composition'
|
|
3
|
+
import type { Dependency, Instances, Resources, Service } from '@toa.io/operations'
|
|
4
|
+
|
|
5
|
+
export const standalone = true
|
|
6
|
+
export { components } from './Composition'
|
|
7
|
+
|
|
8
|
+
export function deployment (instances: Instances<Declaration>, annotation?: Declaration & Annotation): Dependency {
|
|
9
|
+
const routes = []
|
|
10
|
+
const { resources, ...annotatedRoutes } = annotation ?? {}
|
|
11
|
+
const labels = components().labels
|
|
12
|
+
|
|
13
|
+
if (annotatedRoutes !== undefined)
|
|
14
|
+
routes.push(...parse(annotatedRoutes))
|
|
15
|
+
|
|
16
|
+
for (const instance of instances) {
|
|
17
|
+
const completed: Declaration = {}
|
|
18
|
+
|
|
19
|
+
for (const [key, value] of Object.entries(instance.manifest)) {
|
|
20
|
+
const event = instance.locator.id + '.' + key
|
|
21
|
+
|
|
22
|
+
completed[event] = value
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
routes.push(...parse(completed))
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const service: Service = {
|
|
29
|
+
group: 'realtime',
|
|
30
|
+
name: 'streams',
|
|
31
|
+
|
|
32
|
+
version: require('../package.json').version,
|
|
33
|
+
components: labels,
|
|
34
|
+
resources,
|
|
35
|
+
variables: [{
|
|
36
|
+
name: 'TOA_REALTIME',
|
|
37
|
+
value: encode(routes)
|
|
38
|
+
}]
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return { services: [service] }
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function parse (declaration: Declaration): Route[] {
|
|
45
|
+
const routes: Route[] = []
|
|
46
|
+
|
|
47
|
+
for (const [event, value] of Object.entries(declaration)) {
|
|
48
|
+
const properties = Array.isArray(value) ? value : [value]
|
|
49
|
+
|
|
50
|
+
routes.push({ event, properties })
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return routes
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
type Declaration = Record<string, string | string[]>
|
|
57
|
+
|
|
58
|
+
interface Route {
|
|
59
|
+
event: string
|
|
60
|
+
properties: string[]
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
interface Annotation {
|
|
64
|
+
resources?: Resources
|
|
65
|
+
}
|
package/source/index.ts
CHANGED
|
@@ -4,5 +4,11 @@ export declare class Composition extends Connector {
|
|
|
4
4
|
private readonly boot;
|
|
5
5
|
constructor(boot: Bootloader);
|
|
6
6
|
protected open(): Promise<void>;
|
|
7
|
-
protected dispose(): void;
|
|
8
7
|
}
|
|
8
|
+
export declare function find(): string[];
|
|
9
|
+
export declare function components(): Components;
|
|
10
|
+
interface Components {
|
|
11
|
+
labels: string[];
|
|
12
|
+
paths: string[];
|
|
13
|
+
}
|
|
14
|
+
export {};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.Composition = void 0;
|
|
3
|
+
exports.components = exports.find = exports.Composition = void 0;
|
|
4
4
|
const node_fs_1 = require("node:fs");
|
|
5
5
|
const node_path_1 = require("node:path");
|
|
6
6
|
const core_1 = require("@toa.io/core");
|
|
@@ -15,19 +15,26 @@ class Composition extends core_1.Connector {
|
|
|
15
15
|
const composition = await this.boot.composition(paths);
|
|
16
16
|
await composition.connect();
|
|
17
17
|
this.depends(composition);
|
|
18
|
-
console.info('Composition complete.');
|
|
19
|
-
}
|
|
20
|
-
dispose() {
|
|
21
|
-
console.info('Composition shutdown complete.');
|
|
22
18
|
}
|
|
23
19
|
}
|
|
24
20
|
exports.Composition = Composition;
|
|
25
21
|
function find() {
|
|
26
22
|
return entries().map((entry) => (0, node_path_1.resolve)(ROOT, entry.name));
|
|
27
23
|
}
|
|
24
|
+
exports.find = find;
|
|
28
25
|
function entries() {
|
|
29
26
|
const entries = (0, node_fs_1.readdirSync)(ROOT, { withFileTypes: true });
|
|
30
27
|
return entries.filter((entry) => entry.isDirectory());
|
|
31
28
|
}
|
|
29
|
+
function components() {
|
|
30
|
+
const labels = [];
|
|
31
|
+
const paths = [];
|
|
32
|
+
for (const entry of entries()) {
|
|
33
|
+
labels.push(entry.name.replace('.', '-'));
|
|
34
|
+
paths.push((0, node_path_1.resolve)(ROOT, entry.name));
|
|
35
|
+
}
|
|
36
|
+
return { labels, paths };
|
|
37
|
+
}
|
|
38
|
+
exports.components = components;
|
|
32
39
|
const ROOT = (0, node_path_1.resolve)(__dirname, '../components/');
|
|
33
40
|
//# sourceMappingURL=Composition.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Composition.js","sourceRoot":"","sources":["../source/Composition.ts"],"names":[],"mappings":";;;AAAA,qCAAkD;AAClD,yCAAmC;AACnC,uCAAwC;AAGxC,MAAa,WAAY,SAAQ,gBAAS;IACvB,IAAI,CAAY;IAEjC,YAAoB,IAAgB;QAClC,KAAK,EAAE,CAAA;QACP,IAAI,CAAC,IAAI,GAAG,IAAI,CAAA;IAClB,CAAC;IAEkB,KAAK,CAAC,IAAI;QAC3B,MAAM,KAAK,GAAG,IAAI,EAAE,CAAA;QACpB,MAAM,WAAW,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,CAAA;QAEtD,MAAM,WAAW,CAAC,OAAO,EAAE,CAAA;QAE3B,IAAI,CAAC,OAAO,CAAC,WAAW,CAAC,CAAA;
|
|
1
|
+
{"version":3,"file":"Composition.js","sourceRoot":"","sources":["../source/Composition.ts"],"names":[],"mappings":";;;AAAA,qCAAkD;AAClD,yCAAmC;AACnC,uCAAwC;AAGxC,MAAa,WAAY,SAAQ,gBAAS;IACvB,IAAI,CAAY;IAEjC,YAAoB,IAAgB;QAClC,KAAK,EAAE,CAAA;QACP,IAAI,CAAC,IAAI,GAAG,IAAI,CAAA;IAClB,CAAC;IAEkB,KAAK,CAAC,IAAI;QAC3B,MAAM,KAAK,GAAG,IAAI,EAAE,CAAA;QACpB,MAAM,WAAW,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,CAAA;QAEtD,MAAM,WAAW,CAAC,OAAO,EAAE,CAAA;QAE3B,IAAI,CAAC,OAAO,CAAC,WAAW,CAAC,CAAA;IAC3B,CAAC;CACF;AAhBD,kCAgBC;AAED,SAAgB,IAAI;IAClB,OAAO,OAAO,EAAE,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,IAAA,mBAAO,EAAC,IAAI,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC,CAAA;AAC5D,CAAC;AAFD,oBAEC;AAED,SAAS,OAAO;IACd,MAAM,OAAO,GAAG,IAAA,qBAAW,EAAC,IAAI,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAA;IAE1D,OAAO,OAAO,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,WAAW,EAAE,CAAC,CAAA;AACvD,CAAC;AAED,SAAgB,UAAU;IACxB,MAAM,MAAM,GAAa,EAAE,CAAA;IAC3B,MAAM,KAAK,GAAa,EAAE,CAAA;IAE1B,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,EAAE,CAAC;QAC9B,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC,CAAA;QACzC,KAAK,CAAC,IAAI,CAAC,IAAA,mBAAO,EAAC,IAAI,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC,CAAA;IACvC,CAAC;IAED,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,CAAA;AAC1B,CAAC;AAVD,gCAUC;AAOD,MAAM,IAAI,GAAG,IAAA,mBAAO,EAAC,SAAS,EAAE,gBAAgB,CAAC,CAAA"}
|
package/transpiled/Realtime.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.Realtime = void 0;
|
|
4
|
+
const openspan_1 = require("openspan");
|
|
4
5
|
const core_1 = require("@toa.io/core");
|
|
5
6
|
class Realtime extends core_1.Connector {
|
|
6
7
|
discovery;
|
|
@@ -14,13 +15,14 @@ class Realtime extends core_1.Connector {
|
|
|
14
15
|
this.streams = await this.discovery;
|
|
15
16
|
this.depends(this.streams);
|
|
16
17
|
await this.streams.connect();
|
|
17
|
-
console.
|
|
18
|
+
openspan_1.console.info('Realtime service started');
|
|
18
19
|
}
|
|
19
20
|
dispose() {
|
|
20
|
-
console.
|
|
21
|
+
openspan_1.console.info('Realtime service shutdown complete');
|
|
21
22
|
}
|
|
22
23
|
push(event) {
|
|
23
|
-
void this.streams?.invoke('push', { input: event })
|
|
24
|
+
void this.streams?.invoke('push', { input: event })
|
|
25
|
+
.catch((error) => openspan_1.console.error('Realtime push failed', error));
|
|
24
26
|
}
|
|
25
27
|
}
|
|
26
28
|
exports.Realtime = Realtime;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Realtime.js","sourceRoot":"","sources":["../source/Realtime.ts"],"names":[],"mappings":";;;AAAA,uCAAwD;AAGxD,MAAa,QAAS,SAAQ,gBAAS;IACpB,SAAS,CAAoB;IACtC,OAAO,GAAqB,IAAI,CAAA;IAExC,YAAoB,MAAc,EAAE,SAA6B;QAC/D,KAAK,EAAE,CAAA;QAEP,IAAI,CAAC,SAAS,GAAG,SAAS,CAAA;QAE1B,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAA;IAChD,CAAC;IAEkB,KAAK,CAAC,IAAI;QAC3B,IAAI,CAAC,OAAO,GAAG,MAAM,IAAI,CAAC,SAAS,CAAA;QACnC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;QAE1B,MAAM,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,CAAA;QAE5B,
|
|
1
|
+
{"version":3,"file":"Realtime.js","sourceRoot":"","sources":["../source/Realtime.ts"],"names":[],"mappings":";;;AAAA,uCAAkC;AAClC,uCAAwD;AAGxD,MAAa,QAAS,SAAQ,gBAAS;IACpB,SAAS,CAAoB;IACtC,OAAO,GAAqB,IAAI,CAAA;IAExC,YAAoB,MAAc,EAAE,SAA6B;QAC/D,KAAK,EAAE,CAAA;QAEP,IAAI,CAAC,SAAS,GAAG,SAAS,CAAA;QAE1B,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAA;IAChD,CAAC;IAEkB,KAAK,CAAC,IAAI;QAC3B,IAAI,CAAC,OAAO,GAAG,MAAM,IAAI,CAAC,SAAS,CAAA;QACnC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;QAE1B,MAAM,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,CAAA;QAE5B,kBAAO,CAAC,IAAI,CAAC,0BAA0B,CAAC,CAAA;IAC1C,CAAC;IAEkB,OAAO;QACxB,kBAAO,CAAC,IAAI,CAAC,oCAAoC,CAAC,CAAA;IACpD,CAAC;IAEO,IAAI,CAAE,KAAY;QACxB,KAAK,IAAI,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC;aAChD,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,kBAAO,CAAC,KAAK,CAAC,sBAAsB,EAAE,KAAK,CAAC,CAAC,CAAA;IACnE,CAAC;CACF;AA7BD,4BA6BC"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/// <reference types="node" />
|
|
2
|
+
import { Connector, type Message } from '@toa.io/core';
|
|
3
|
+
import type { Readable } from 'node:stream';
|
|
4
|
+
export declare class Receiver extends Connector {
|
|
5
|
+
private readonly event;
|
|
6
|
+
private readonly properties;
|
|
7
|
+
private readonly stream;
|
|
8
|
+
constructor(event: string, properties: string[], stream: Readable);
|
|
9
|
+
receive(message: Message<Record<string, string>>): void;
|
|
10
|
+
private push;
|
|
11
|
+
}
|