@toa.io/extensions.realtime 1.0.0-alpha.28 → 1.0.0-alpha.283

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.
Files changed (85) hide show
  1. package/CHANGELOG.md +337 -0
  2. package/components/realtime.streams/manifest.toa.yaml +47 -0
  3. package/components/{streams → realtime.streams}/operations/create.d.ts +7 -4
  4. package/components/realtime.streams/operations/create.js +65 -0
  5. package/components/realtime.streams/operations/create.js.map +1 -0
  6. package/components/realtime.streams/operations/lib/Stash.d.ts +21 -0
  7. package/components/realtime.streams/operations/lib/Stash.js +86 -0
  8. package/components/realtime.streams/operations/lib/Stash.js.map +1 -0
  9. package/components/realtime.streams/operations/lib/Stream.d.ts +13 -0
  10. package/components/realtime.streams/operations/lib/Stream.js +43 -0
  11. package/components/realtime.streams/operations/lib/Stream.js.map +1 -0
  12. package/components/realtime.streams/operations/lib/index.d.ts +2 -0
  13. package/components/realtime.streams/operations/lib/index.js +3 -0
  14. package/components/realtime.streams/operations/lib/index.js.map +1 -0
  15. package/components/{streams → realtime.streams}/operations/push.d.ts +1 -1
  16. package/components/realtime.streams/operations/push.js +10 -0
  17. package/components/realtime.streams/operations/push.js.map +1 -0
  18. package/components/realtime.streams/source/create.ts +97 -0
  19. package/components/realtime.streams/source/lib/Stash.ts +133 -0
  20. package/components/realtime.streams/source/lib/Stream.ts +56 -0
  21. package/components/realtime.streams/source/lib/index.ts +2 -0
  22. package/components/realtime.streams/source/push.ts +12 -0
  23. package/components/{streams → realtime.streams}/tsconfig.json +1 -0
  24. package/components/realtime.streams/tsconfig.tsbuildinfo +1 -0
  25. package/components/realtime.streams/types/index.d.ts +20 -0
  26. package/components/realtime.streams/types/toa.d.ts +29 -0
  27. package/{components/context.toa.yaml → context.toa.yaml} +0 -1
  28. package/cucumber.mjs +5 -0
  29. package/features/static.feature +120 -0
  30. package/features/steps/Components.ts +14 -14
  31. package/features/steps/Realtime.ts +14 -18
  32. package/features/steps/Streams.ts +51 -13
  33. package/features/steps/components/messages/manifest.toa.yaml +37 -6
  34. package/features/steps/config.ts +12 -0
  35. package/package.json +8 -7
  36. package/readme.md +33 -7
  37. package/source/Composition.ts +24 -13
  38. package/source/Factory.ts +11 -13
  39. package/source/Realtime.ts +41 -13
  40. package/source/Receiver.ts +86 -0
  41. package/source/Routes.ts +14 -37
  42. package/source/extension.ts +82 -0
  43. package/source/index.ts +2 -1
  44. package/transpiled/Composition.d.ts +10 -4
  45. package/transpiled/Composition.js +21 -20
  46. package/transpiled/Composition.js.map +1 -1
  47. package/transpiled/Factory.d.ts +4 -4
  48. package/transpiled/Factory.js +13 -18
  49. package/transpiled/Factory.js.map +1 -1
  50. package/transpiled/Realtime.d.ts +3 -2
  51. package/transpiled/Realtime.js +36 -11
  52. package/transpiled/Realtime.js.map +1 -1
  53. package/transpiled/Receiver.d.ts +25 -0
  54. package/transpiled/Receiver.js +55 -0
  55. package/transpiled/Receiver.js.map +1 -0
  56. package/transpiled/Routes.d.ts +5 -10
  57. package/transpiled/Routes.js +14 -31
  58. package/transpiled/Routes.js.map +1 -1
  59. package/transpiled/extension.d.ts +19 -0
  60. package/transpiled/extension.js +48 -0
  61. package/transpiled/extension.js.map +1 -0
  62. package/transpiled/index.d.ts +2 -1
  63. package/transpiled/index.js +2 -5
  64. package/transpiled/index.js.map +1 -1
  65. package/tsconfig.json +2 -1
  66. package/tsconfig.tsbuildinfo +1 -0
  67. package/components/streams/manifest.toa.yaml +0 -21
  68. package/components/streams/operations/create.js +0 -29
  69. package/components/streams/operations/create.js.map +0 -1
  70. package/components/streams/operations/lib/stream.d.ts +0 -10
  71. package/components/streams/operations/lib/stream.js +0 -31
  72. package/components/streams/operations/lib/stream.js.map +0 -1
  73. package/components/streams/operations/push.js +0 -8
  74. package/components/streams/operations/push.js.map +0 -1
  75. package/components/streams/operations/tsconfig.tsbuildinfo +0 -1
  76. package/components/streams/operations/types.d.ts +0 -11
  77. package/components/streams/operations/types.js +0 -3
  78. package/components/streams/operations/types.js.map +0 -1
  79. package/components/streams/source/create.ts +0 -38
  80. package/components/streams/source/lib/stream.ts +0 -37
  81. package/components/streams/source/push.ts +0 -5
  82. package/components/streams/source/types.ts +0 -13
  83. package/cucumber.js +0 -8
  84. package/stage/streams.test.ts +0 -128
  85. package/transpiled/tsconfig.tsbuildinfo +0 -1
package/cucumber.mjs ADDED
@@ -0,0 +1,5 @@
1
+ export default {
2
+ paths: ['features/**/*.feature'],
3
+ import: ['./features/**/*.ts'],
4
+ failFast: true
5
+ }
@@ -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,116 @@ Feature: Static routes
30
37
  recipient: 004e02a959c04cecaf111827f91caa36
31
38
  text: Hello!
32
39
  """
40
+
41
+ Scenario: Exposing event properties
42
+ Given the `messages` component is running with routes:
43
+ """yaml
44
+ created:
45
+ key: [sender, recipient]
46
+ expose: [text, sender]
47
+ """
48
+ And the stream `004e02a959c04cecaf111827f91caa36` is consumed
49
+ And the stream `96db5a47a8244eb3b21820781b7d596e` is consumed
50
+ When the `messages.create` is called with:
51
+ """yaml
52
+ input:
53
+ sender: 96db5a47a8244eb3b21820781b7d596e
54
+ recipient: 004e02a959c04cecaf111827f91caa36
55
+ text: Hello!
56
+ """
57
+ Then an event is received from the stream `004e02a959c04cecaf111827f91caa36`:
58
+ """yaml
59
+ event: default.messages.created
60
+ data:
61
+ sender: 96db5a47a8244eb3b21820781b7d596e
62
+ text: Hello!
63
+ """
64
+ And an event is received from the stream `96db5a47a8244eb3b21820781b7d596e`:
65
+ """yaml
66
+ event: default.messages.created
67
+ data:
68
+ sender: 96db5a47a8244eb3b21820781b7d596e
69
+ text: Hello!
70
+ """
71
+
72
+ Scenario: Routing an event using array
73
+ Given the `messages` component is running with routes:
74
+ """yaml
75
+ created: watchers
76
+ """
77
+ And the stream `51c15a7290ce47e0af8ec41d60dccb32` is consumed
78
+ And the stream `bb27366509a64178a39313aac42435ae` is consumed
79
+ When the `messages.create` is called with:
80
+ """yaml
81
+ input:
82
+ watchers:
83
+ - 51c15a7290ce47e0af8ec41d60dccb32
84
+ - bb27366509a64178a39313aac42435ae
85
+ sender: 96db5a47a8244eb3b21820781b7d596e
86
+ recipient: 004e02a959c04cecaf111827f91caa36
87
+ text: Hello!
88
+ """
89
+ Then an event is received from the stream `51c15a7290ce47e0af8ec41d60dccb32`:
90
+ """yaml
91
+ event: default.messages.created
92
+ data:
93
+ sender: 96db5a47a8244eb3b21820781b7d596e
94
+ recipient: 004e02a959c04cecaf111827f91caa36
95
+ text: Hello!
96
+ """
97
+ And an event is received from the stream `bb27366509a64178a39313aac42435ae`:
98
+ """yaml
99
+ event: default.messages.created
100
+ data:
101
+ sender: 96db5a47a8244eb3b21820781b7d596e
102
+ recipient: 004e02a959c04cecaf111827f91caa36
103
+ text: Hello!
104
+ """
105
+
106
+ Scenario: Continuation token
107
+ Given the `messages` component is running with routes:
108
+ """yaml
109
+ created: [sender, recipient]
110
+ """
111
+ And the stream `004e02a959c04cecaf111827f91caa36` is consumed
112
+ And an event is received from the stream `004e02a959c04cecaf111827f91caa36`:
113
+ """yaml
114
+ event: token
115
+ """
116
+
117
+ When the consumer `004e02a959c04cecaf111827f91caa36` is disconnected
118
+ And the `messages.create` is called with:
119
+ """yaml
120
+ input:
121
+ sender: 96db5a47a8244eb3b21820781b7d596e
122
+ recipient: 004e02a959c04cecaf111827f91caa36
123
+ text: Hello!
124
+ """
125
+ And the `messages.create` is called with:
126
+ """yaml
127
+ input:
128
+ sender: 004e02a959c04cecaf111827f91caa36
129
+ recipient: 96db5a47a8244eb3b21820781b7d596e
130
+ text: Hi!
131
+ """
132
+ And the consumer `004e02a959c04cecaf111827f91caa36` is reconnected
133
+ Then an event is received from the stream `004e02a959c04cecaf111827f91caa36`:
134
+ """yaml
135
+ event: default.messages.created
136
+ data:
137
+ sender: 96db5a47a8244eb3b21820781b7d596e
138
+ recipient: 004e02a959c04cecaf111827f91caa36
139
+ text: Hello!
140
+ """
141
+ And an event is received from the stream `004e02a959c04cecaf111827f91caa36`:
142
+ """yaml
143
+ event: default.messages.created
144
+ data:
145
+ sender: 004e02a959c04cecaf111827f91caa36
146
+ recipient: 96db5a47a8244eb3b21820781b7d596e
147
+ text: Hi!
148
+ """
149
+ And an event is received from the stream `004e02a959c04cecaf111827f91caa36`:
150
+ """yaml
151
+ event: token
152
+ """
@@ -1,11 +1,15 @@
1
1
  import { resolve } from 'node:path'
2
2
  import { readdirSync } from 'node:fs'
3
- import { after, before, binding, given, when } from 'cucumber-tsflow'
4
- import { parse } from '@toa.io/yaml'
3
+ import tsflow from 'cucumber-tsflow'
4
+
5
+ import { load as parse } from 'js-yaml'
5
6
  import * as stage from '@toa.io/userland/stage'
6
7
  import { type Component, type Request } from '@toa.io/core'
7
8
  import { timeout } from '@toa.io/generic'
8
- import { Realtime } from './Realtime'
9
+ import { parse as parseRoutes, type Declaration } from '../../source/extension.js'
10
+ import { Realtime } from './Realtime.js'
11
+
12
+ const { after, before, binding, given, when } = tsflow
9
13
 
10
14
  @binding([Realtime])
11
15
  export class Components {
@@ -18,20 +22,20 @@ export class Components {
18
22
 
19
23
  @given('the `{word}` component is running with routes:')
20
24
  public async run (component: string, yaml: string): Promise<void> {
21
- const routes = parse<Manifest>(yaml)
25
+ const declaration = parse(yaml) as Declaration
22
26
  const [name, namespace = 'default'] = component.split('.').reverse()
27
+ const routes = parseRoutes(declaration)
23
28
 
24
- for (const [event, value] of Object.entries(routes)) {
25
- const label = `${namespace}.${name}.${event}`
26
- const properties = Array.isArray(value) ? value : [value]
29
+ for (const route of routes) {
30
+ const label = `${namespace}.${name}.${route.event}`
27
31
 
28
- this.realtime.declare(label, properties)
32
+ this.realtime.declare(label, route.properties, route.expose)
29
33
  }
30
34
  }
31
35
 
32
36
  @when('the `{word}` is called with:')
33
37
  public async call (endpoint: string, yaml: string): Promise<void> {
34
- const request = parse<Request>(yaml)
38
+ const request = parse(yaml) as Request
35
39
  const [operation, component, namespace = 'default'] = endpoint.split('.').reverse()
36
40
  const id = `${namespace}.${component}`
37
41
 
@@ -64,8 +68,4 @@ function componentPaths (): string[] {
64
68
  .map((entry) => resolve(ROOT, entry.name))
65
69
  }
66
70
 
67
- const ROOT = resolve(__dirname, 'components')
68
-
69
- interface Manifest {
70
- realtime: Map<string, string | string[]>
71
- }
71
+ const ROOT = resolve(import.meta.dirname, 'components')
@@ -1,43 +1,39 @@
1
1
  import * as boot from '@toa.io/boot'
2
- import { encode } from '@toa.io/generic'
3
2
  import { type Connector } from '@toa.io/core'
4
- import { after, binding } from 'cucumber-tsflow'
5
- import { Factory } from '../../source/Factory'
3
+ import tsflow from 'cucumber-tsflow'
4
+
5
+ import { Factory } from '../../source/index.js'
6
+ import type { Route } from '../../source/extension.js'
7
+
8
+ const { after, binding } = tsflow
6
9
 
7
10
  @binding()
8
11
  export class Realtime {
9
12
  private readonly routes: Route[] = []
10
- private readonly service: Connector
13
+ // the factory loads its binding, so the service is made when it is first served
14
+ private service: Connector | undefined
11
15
  private connected = false
12
16
 
13
- public constructor () {
14
- this.service = new Factory(boot).service()
15
- }
16
-
17
17
  @after()
18
- private async shutdown (): Promise<void> {
18
+ public async shutdown (): Promise<void> {
19
19
  this.connected = false
20
20
 
21
- await this.service.disconnect()
21
+ await this.service?.disconnect()
22
22
  }
23
23
 
24
- public declare (event: string, properties: string[]): void {
25
- this.routes.push({ event, properties })
24
+ public declare (event: string, properties: string[], expose?: string[]): void {
25
+ this.routes.push({ event, properties, expose })
26
26
  }
27
27
 
28
28
  public async serve (): Promise<void> {
29
29
  if (this.connected)
30
30
  return
31
31
 
32
- process.env.TOA_REALTIME = encode(this.routes)
32
+ process.env.TOA_REALTIME = JSON.stringify(this.routes)
33
33
 
34
34
  this.connected = true
35
+ this.service = await new Factory(boot.host()).service()
35
36
 
36
37
  await this.service.connect()
37
38
  }
38
39
  }
39
-
40
- interface Route {
41
- event: string
42
- properties: string[]
43
- }
@@ -1,36 +1,42 @@
1
- import { type Readable } from 'node:stream'
2
- import { after, binding, given, then } from 'cucumber-tsflow'
3
- import { match } from '@toa.io/generic'
1
+ import * as assert from 'node:assert'
2
+ import { setTimeout } from 'node:timers/promises'
3
+ import tsflow from 'cucumber-tsflow'
4
4
 
5
- import { type Component } from '@toa.io/core'
6
- import { parse } from '@toa.io/yaml'
5
+ import { match } from '@toa.io/generic'
6
+ import { load as parse } from 'js-yaml'
7
7
  import * as stage from '@toa.io/userland/stage'
8
- import { Realtime } from './Realtime'
8
+ import { Realtime } from './Realtime.js'
9
+ import type { Readable } from 'node:stream'
10
+ import type { Component } from '@toa.io/core'
11
+
12
+ const { after, binding, given, then } = tsflow
9
13
 
10
14
  @binding([Realtime])
11
15
  export class Streams {
12
16
  private readonly realtime: Realtime
13
17
  private remote: Component | null = null
14
18
  private streams: Record<string, Readable> = {}
15
- private events: Record<string, object[]> = {}
19
+ private events: Record<string, Event[]> = {}
16
20
 
17
21
  public constructor (realtime: Realtime) {
18
22
  this.realtime = realtime
19
23
  }
20
24
 
21
- @given('the stream `{word}` is consumed')
25
+ @given('the stream `{word}` is consumed', { timeout: 30_000 })
22
26
  public async consume (key: string): Promise<void> {
23
27
  await this.realtime.serve()
24
28
 
25
29
  this.remote ??= await stage.remote('realtime.streams')
26
30
  this.events[key] = []
27
- this.streams[key] = await this.remote.invoke('create', { input: { key } })
28
- this.streams[key].on('data', (data: object) => this.events[key].push(data))
31
+
32
+ await this.createStream(key)
29
33
  }
30
34
 
31
35
  @then('an event is received from the stream `{word}`:')
32
- public received (key: string, yaml: string): void {
33
- const expected = parse<object>(yaml)
36
+ public async received (key: string, yaml: string): Promise<void> {
37
+ await setTimeout(100)
38
+
39
+ const expected = parse(yaml) as object
34
40
 
35
41
  for (const event of this.events[key])
36
42
  if (match(event, expected))
@@ -39,12 +45,44 @@ export class Streams {
39
45
  throw new Error('No matching event received')
40
46
  }
41
47
 
48
+ @then('the consumer `{word}` is disconnected')
49
+ public disconnected (key: string): void {
50
+ this.streams[key]?.destroy()
51
+ delete this.streams[key]
52
+ }
53
+
54
+ @then('the consumer `{word}` is reconnected')
55
+ public async reconnected (key: string): Promise<void> {
56
+ const last = this.events[key].findLast((event) => event.event === 'token')
57
+
58
+ assert.ok(last, `No last event found for stream ${key}`)
59
+
60
+ await this.createStream(key, last.data as string)
61
+ }
62
+
42
63
  @after()
43
- private shutdown (): void {
64
+ public async shutdown (): Promise<void> {
44
65
  for (const stream of Object.values(this.streams))
45
66
  stream.destroy()
46
67
 
47
68
  this.streams = {}
48
69
  this.events = {}
70
+
71
+ await setTimeout(100)
49
72
  }
73
+
74
+ private async createStream (key: string, token?: string): Promise<void> {
75
+ this.streams[key] = await this.remote!.invoke('create', { input: { key, token } })
76
+ this.streams[key].on('data', (event: Event) => {
77
+ console.log('[TEST] Received event', event)
78
+ this.events[key]?.push(event)
79
+ })
80
+ }
81
+ }
82
+
83
+ interface Event {
84
+ key: string
85
+ token: string
86
+ event: string
87
+ data?: unknown
50
88
  }
@@ -2,15 +2,46 @@ name: messages
2
2
 
3
3
  entity:
4
4
  schema:
5
- sender*: string(32)
6
- recipient*: string(32)
7
- text*: string
5
+ properties:
6
+ watchers:
7
+ type: array
8
+ items:
9
+ type: string
10
+ maxLength: 32
11
+ sender:
12
+ type: string
13
+ maxLength: 32
14
+ recipient:
15
+ type: string
16
+ maxLength: 32
17
+ text:
18
+ type: string
19
+ type: object
20
+ required:
21
+ - sender
22
+ - recipient
23
+ - text
8
24
 
9
25
  operations:
10
26
  create:
11
27
  query: false
12
28
  forward: transit
13
29
  input:
14
- sender*: .
15
- recipient*: .
16
- text*: .
30
+ properties:
31
+ watchers:
32
+ type: string
33
+ default: .
34
+ sender:
35
+ type: string
36
+ default: .
37
+ recipient:
38
+ type: string
39
+ default: .
40
+ text:
41
+ type: string
42
+ default: .
43
+ type: object
44
+ required:
45
+ - sender
46
+ - recipient
47
+ - text
@@ -1 +1,13 @@
1
+ import { setDefaultTimeout } from '@cucumber/cucumber'
2
+
1
3
  process.env.TOA_DEV = '1'
4
+
5
+ // a reply is checked against what the operation declares, so the suite runs Toa under the
6
+ // contract it asks applications to keep
7
+ process.env.TOA_ENV ??= 'local'
8
+
9
+ // the streams component boots inside the service, and without a variable it would wait
10
+ // for the values service, which these features do not run
11
+ process.env.TOA_CONFIGURATION_REALTIME_STREAMS ??= '{}'
12
+
13
+ setDefaultTimeout(60 * 1000)
package/package.json CHANGED
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "name": "@toa.io/extensions.realtime",
3
- "version": "1.0.0-alpha.28",
3
+ "version": "1.0.0-alpha.283",
4
+ "type": "module",
4
5
  "description": "Toa Realtime",
5
6
  "author": "temich <tema.gurtovoy@gmail.com>",
6
7
  "homepage": "https://github.com/toa-io/toa#readme",
@@ -16,13 +17,13 @@
16
17
  "publishConfig": {
17
18
  "access": "public"
18
19
  },
19
- "jest": {
20
- "preset": "ts-jest",
21
- "testEnvironment": "node"
20
+ "dependencies": {
21
+ "@toa.io/core": "1.0.0-alpha.283",
22
+ "openspan": "1.0.0-alpha.277"
22
23
  },
23
24
  "scripts": {
24
- "transpile": "npx tsc && npx tsc -p ./components/streams",
25
- "features": "npx cucumber-js"
25
+ "transpile": "npx tsc && npx tsc -p ./components/realtime.streams",
26
+ "features": "TSX_TSCONFIG_PATH=features/steps/tsconfig.json NODE_OPTIONS=--import=tsx cucumber-js"
26
27
  },
27
- "gitHead": "89c3b7d1314e357c9e33df8d95f8d46954cd3bcf"
28
+ "gitHead": "2f52b023bed0ccb2ecdb2b82fe59fbe38bf282e1"
28
29
  }
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](#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: custromer_id
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`,
@@ -87,13 +101,25 @@ Dynamic routes are managed by the `realtime.routes` component, running in the Re
87
101
 
88
102
  ## Exposition
89
103
 
90
- Streams are exposed by the [`realtime.streams`](components/streams) component, running in the
104
+ Streams are exposed by the [`realtime.streams`](components/realtime.streams) component, running in the
91
105
  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
- - [Streams](/extensions/exposition/documentation/protocol.md#streams)
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
+ ```
@@ -1,33 +1,27 @@
1
1
  import { readdirSync, type Dirent } from 'node:fs'
2
2
  import { resolve } from 'node:path'
3
3
  import { Connector } from '@toa.io/core'
4
- import { type Bootloader } from './Factory'
4
+ import { type Host } from './Factory.js'
5
5
 
6
6
  export class Composition extends Connector {
7
- private readonly boot: Bootloader
7
+ private readonly host: Host
8
8
 
9
- public constructor (boot: Bootloader) {
9
+ public constructor (host: Host) {
10
10
  super()
11
- this.boot = boot
11
+ this.host = host
12
12
  }
13
13
 
14
14
  protected override async open (): Promise<void> {
15
15
  const paths = find()
16
- const composition = await this.boot.composition(paths)
16
+ const composition = await this.host.composition(paths)
17
17
 
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
 
40
- const ROOT = resolve(__dirname, '../components/')
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
+
51
+ const ROOT = resolve(import.meta.dirname, '../components/')
package/source/Factory.ts CHANGED
@@ -1,20 +1,19 @@
1
1
  import { type Component, type extensions, Locator } from '@toa.io/core'
2
- import { Realtime } from './Realtime'
3
- import { Composition } from './Composition'
4
- import { Routes } from './Routes'
2
+ import { Realtime } from './Realtime.js'
3
+ import { Composition } from './Composition.js'
4
+ import { Routes } from './Routes.js'
5
5
 
6
6
  export class Factory implements extensions.Factory {
7
- private readonly boot: Bootloader
7
+ private readonly host: Host
8
8
 
9
- public constructor (boot: Bootloader) {
10
- this.boot = boot
9
+ public constructor (host: Host) {
10
+ this.host = host
11
11
  }
12
12
 
13
13
  public service (): Realtime {
14
- const discovery = this.discovery()
15
- const routes = new Routes(this.boot)
16
- const composition = new Composition(this.boot)
17
- const realtime = new Realtime(routes, discovery)
14
+ const routes = new Routes(this.host)
15
+ const composition = new Composition(this.host)
16
+ const realtime = new Realtime(routes, async () => await this.discovery())
18
17
 
19
18
  realtime.depends(routes)
20
19
  realtime.depends(composition)
@@ -25,9 +24,8 @@ export class Factory implements extensions.Factory {
25
24
  private async discovery (): Promise<Component> {
26
25
  const locator = new Locator('streams', 'realtime')
27
26
 
28
- return await this.boot.remote(locator)
27
+ return await this.host.remote(locator, { service: 'realtime' })
29
28
  }
30
29
  }
31
30
 
32
- // eslint-disable-next-line @typescript-eslint/consistent-type-imports
33
- export type Bootloader = typeof import('@toa.io/boot')
31
+ export type Host = extensions.Host