@toa.io/core 1.0.0-alpha.243 → 1.0.0-alpha.246
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/package.json +4 -4
- package/src/call.js +6 -0
- package/src/component.js +35 -8
- package/src/connector.js +11 -2
- package/src/event.js +19 -1
- package/src/receiver.js +53 -11
- package/src/remote.js +2 -0
- package/test/event.test.js +4 -4
- package/test/receiver.fixtures.js +1 -0
- package/types/message.d.ts +1 -0
- package/types/request.d.ts +1 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@toa.io/core",
|
|
3
|
-
"version": "1.0.0-alpha.
|
|
3
|
+
"version": "1.0.0-alpha.246",
|
|
4
4
|
"description": "Toa Core",
|
|
5
5
|
"author": "temich <tema.gurtovoy@gmail.com>",
|
|
6
6
|
"homepage": "https://github.com/toa-io/toa#readme",
|
|
@@ -22,10 +22,10 @@
|
|
|
22
22
|
"dependencies": {
|
|
23
23
|
"@rsql/parser": "1.2.4",
|
|
24
24
|
"@toa.io/generic": "1.0.0-alpha.225",
|
|
25
|
-
"@toa.io/yaml": "1.0.0-alpha.
|
|
25
|
+
"@toa.io/yaml": "1.0.0-alpha.246",
|
|
26
26
|
"error-value": "0.3.0",
|
|
27
|
-
"openspan": "1.0.0-alpha.
|
|
27
|
+
"openspan": "1.0.0-alpha.246",
|
|
28
28
|
"uuid": "11.1.1"
|
|
29
29
|
},
|
|
30
|
-
"gitHead": "
|
|
30
|
+
"gitHead": "d6ee2a7246c64a44c50c28fadcfd1e27aaf137cb"
|
|
31
31
|
}
|
package/src/call.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
3
|
const { Readable } = require('node:stream')
|
|
4
|
+
const { current, encode } = require('openspan')
|
|
4
5
|
const { Connector } = require('./connector')
|
|
5
6
|
const { Err } = require('error-value')
|
|
6
7
|
|
|
@@ -25,6 +26,11 @@ class Call extends Connector {
|
|
|
25
26
|
// avoid validation on the recipient's side
|
|
26
27
|
request.authentic = true
|
|
27
28
|
|
|
29
|
+
const context = current()
|
|
30
|
+
|
|
31
|
+
if (context !== undefined)
|
|
32
|
+
request.telemetry = encode(context)
|
|
33
|
+
|
|
28
34
|
const reply = await this.#transmitter.request(request)
|
|
29
35
|
|
|
30
36
|
if (reply === null) return null
|
package/src/component.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
3
|
const assert = require('node:assert')
|
|
4
|
-
const { console } = require('openspan')
|
|
4
|
+
const { console, current, decode, run } = require('openspan')
|
|
5
5
|
const { Connector } = require('./connector')
|
|
6
6
|
|
|
7
7
|
class Component extends Connector {
|
|
@@ -10,6 +10,9 @@ class Component extends Connector {
|
|
|
10
10
|
/** @protected */
|
|
11
11
|
operations
|
|
12
12
|
|
|
13
|
+
/** @protected */
|
|
14
|
+
kind = 'server'
|
|
15
|
+
|
|
13
16
|
constructor (locator, operations) {
|
|
14
17
|
super()
|
|
15
18
|
|
|
@@ -23,15 +26,39 @@ class Component extends Connector {
|
|
|
23
26
|
assert.ok(endpoint in this.operations,
|
|
24
27
|
`Endpoint '${endpoint}' is not provided by '${this.locator.id}'`)
|
|
25
28
|
|
|
26
|
-
|
|
29
|
+
// if the request carries no telemetry, the trace starts here
|
|
30
|
+
const remote = request?.telemetry === undefined ? null : decode(request.telemetry)
|
|
31
|
+
const task = () => this.process(endpoint, request)
|
|
32
|
+
|
|
33
|
+
if (remote === null)
|
|
34
|
+
return task()
|
|
35
|
+
else
|
|
36
|
+
return run(remote, task)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** @private */
|
|
40
|
+
async process (endpoint, request) {
|
|
41
|
+
const options = { name: `${this.locator.id}.${endpoint}`, kind: this.kind }
|
|
42
|
+
|
|
43
|
+
// the server span is emitted by the component itself,
|
|
44
|
+
// while the client span belongs to the calling service and inherits it from the context
|
|
45
|
+
if (this.kind === 'server')
|
|
46
|
+
options.service = this.locator.id
|
|
47
|
+
|
|
48
|
+
return console.span(options, async () => {
|
|
49
|
+
const reply = await this.operations[endpoint].invoke(request)
|
|
50
|
+
|
|
51
|
+
if (reply?.exception !== undefined) {
|
|
52
|
+
current().status = 'error'
|
|
27
53
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
}
|
|
54
|
+
console.error('Failed to execute operation', {
|
|
55
|
+
endpoint: `${this.locator.id}.${endpoint}`,
|
|
56
|
+
exception: reply.exception
|
|
57
|
+
})
|
|
58
|
+
}
|
|
33
59
|
|
|
34
|
-
|
|
60
|
+
return reply
|
|
61
|
+
})
|
|
35
62
|
}
|
|
36
63
|
}
|
|
37
64
|
|
package/src/connector.js
CHANGED
|
@@ -85,10 +85,18 @@ class Connector {
|
|
|
85
85
|
|
|
86
86
|
this.#disconnecting = undefined
|
|
87
87
|
|
|
88
|
-
|
|
88
|
+
const work = async () => {
|
|
89
89
|
await Promise.all(this.#dependencies.map((connector) => connector.connect()))
|
|
90
90
|
await this.open()
|
|
91
|
-
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// anonymous grouping nodes are not worth a span
|
|
94
|
+
this.#connecting = TRACE_BOOT && this.constructor.name !== 'Connector'
|
|
95
|
+
? console.span({
|
|
96
|
+
name: `connect ${this.constructor.name}`,
|
|
97
|
+
attributes: { id: this.locator?.id ?? this.id }
|
|
98
|
+
}, work)
|
|
99
|
+
: work()
|
|
92
100
|
|
|
93
101
|
try {
|
|
94
102
|
await this.#connecting
|
|
@@ -177,5 +185,6 @@ class Connector {
|
|
|
177
185
|
}
|
|
178
186
|
|
|
179
187
|
const DELAY = 5000
|
|
188
|
+
const TRACE_BOOT = process.env.TOA_BOOT_TRACE === '1'
|
|
180
189
|
|
|
181
190
|
exports.Connector = Connector
|
package/src/event.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
+
const { console, current, encode } = require('openspan')
|
|
3
4
|
const { Connector } = require('./connector')
|
|
4
5
|
|
|
5
6
|
/**
|
|
@@ -12,11 +13,15 @@ class Event extends Connector {
|
|
|
12
13
|
#conditioned
|
|
13
14
|
#subjective
|
|
14
15
|
|
|
16
|
+
/** @type {string} */
|
|
17
|
+
#label
|
|
18
|
+
|
|
15
19
|
constructor (definition, emitter, bridge = undefined) {
|
|
16
20
|
super()
|
|
17
21
|
|
|
18
22
|
this.#conditioned = definition.conditioned
|
|
19
23
|
this.#subjective = definition.subjective
|
|
24
|
+
this.#label = definition.label ?? 'event'
|
|
20
25
|
this.#emitter = emitter
|
|
21
26
|
this.#bridge = bridge
|
|
22
27
|
|
|
@@ -32,7 +37,20 @@ class Event extends Connector {
|
|
|
32
37
|
/** @type {toa.core.Message} */
|
|
33
38
|
const message = { payload }
|
|
34
39
|
|
|
35
|
-
|
|
40
|
+
const options = {
|
|
41
|
+
name: `${this.#label} publish`,
|
|
42
|
+
kind: 'producer',
|
|
43
|
+
attributes: { 'messaging.destination.name': this.#label }
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
await console.span(options, async () => {
|
|
47
|
+
const context = current()
|
|
48
|
+
|
|
49
|
+
if (context !== undefined)
|
|
50
|
+
message.telemetry = encode(context)
|
|
51
|
+
|
|
52
|
+
await this.#emitter.emit(message)
|
|
53
|
+
})
|
|
36
54
|
}
|
|
37
55
|
}
|
|
38
56
|
}
|
package/src/receiver.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
const { console } = require('openspan')
|
|
3
|
+
const { console, decode, run } = require('openspan')
|
|
4
4
|
const { add } = require('@toa.io/generic')
|
|
5
5
|
const { Connector } = require('./connector')
|
|
6
6
|
|
|
@@ -17,6 +17,12 @@ class Receiver extends Connector {
|
|
|
17
17
|
/** @type {string} */
|
|
18
18
|
#endpoint
|
|
19
19
|
|
|
20
|
+
/** @type {string} */
|
|
21
|
+
#label
|
|
22
|
+
|
|
23
|
+
/** @type {string} */
|
|
24
|
+
#destination
|
|
25
|
+
|
|
20
26
|
/** @type {unknown[]} */
|
|
21
27
|
#arguments
|
|
22
28
|
|
|
@@ -32,6 +38,8 @@ class Receiver extends Connector {
|
|
|
32
38
|
this.#conditioned = conditioned
|
|
33
39
|
this.#adaptive = adaptive
|
|
34
40
|
this.#endpoint = operation
|
|
41
|
+
this.#label = definition.label ?? operation
|
|
42
|
+
this.#destination = definition.destination ?? this.#label
|
|
35
43
|
this.#arguments = definition.arguments
|
|
36
44
|
|
|
37
45
|
this.#local = local
|
|
@@ -43,7 +51,7 @@ class Receiver extends Connector {
|
|
|
43
51
|
|
|
44
52
|
/** @hot */
|
|
45
53
|
async receive (message) {
|
|
46
|
-
const { payload, ...extensions } = message
|
|
54
|
+
const { payload, telemetry, ...extensions } = message
|
|
47
55
|
|
|
48
56
|
if (this.#conditioned && await this.#bridge.condition(payload) === false) return
|
|
49
57
|
|
|
@@ -51,17 +59,51 @@ class Receiver extends Connector {
|
|
|
51
59
|
|
|
52
60
|
add(request, extensions)
|
|
53
61
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
console.error('Receiver error', {
|
|
58
|
-
component: this.#local.locator.id,
|
|
59
|
-
endpoint: this.#endpoint,
|
|
60
|
-
error
|
|
61
|
-
})
|
|
62
|
+
// continue the trace from the producer span
|
|
63
|
+
const remote = telemetry === undefined ? null : decode(telemetry)
|
|
64
|
+
const task = () => this.#process(request)
|
|
62
65
|
|
|
63
|
-
|
|
66
|
+
if (remote === null)
|
|
67
|
+
await task()
|
|
68
|
+
else
|
|
69
|
+
await run(remote, task)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
async #process (request) {
|
|
73
|
+
/*
|
|
74
|
+
* The delivery span is created on behalf of the messaging destination, so that
|
|
75
|
+
* each consumer forms its own complete producer/consumer pair, and service graphs
|
|
76
|
+
* display fan-out correctly: producer -> destination -> each consumer
|
|
77
|
+
* (Tempo pairs spans one-to-one, thus multiple consumers can not pair
|
|
78
|
+
* with a single producer span, see grafana/tempo#5408)
|
|
79
|
+
*/
|
|
80
|
+
const delivery = {
|
|
81
|
+
name: `${this.#label} deliver`,
|
|
82
|
+
kind: 'producer',
|
|
83
|
+
service: this.#destination,
|
|
84
|
+
attributes: { 'messaging.destination.name': this.#destination }
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const options = {
|
|
88
|
+
name: `${this.#label} process`,
|
|
89
|
+
kind: 'consumer',
|
|
90
|
+
service: this.#local.locator.id,
|
|
91
|
+
attributes: { 'messaging.destination.name': this.#destination }
|
|
64
92
|
}
|
|
93
|
+
|
|
94
|
+
return console.span(delivery, async () => console.span(options, async () => {
|
|
95
|
+
try {
|
|
96
|
+
await this.#local.invoke(this.#endpoint, request)
|
|
97
|
+
} catch (error) {
|
|
98
|
+
console.error('Receiver error', {
|
|
99
|
+
component: this.#local.locator.id,
|
|
100
|
+
endpoint: this.#endpoint,
|
|
101
|
+
error
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
throw error
|
|
105
|
+
}
|
|
106
|
+
}))
|
|
65
107
|
}
|
|
66
108
|
|
|
67
109
|
async #request (payload) {
|
package/src/remote.js
CHANGED
|
@@ -4,6 +4,8 @@ const assert = require('node:assert')
|
|
|
4
4
|
const { Component } = require('./component')
|
|
5
5
|
|
|
6
6
|
class Remote extends Component {
|
|
7
|
+
kind = 'client'
|
|
8
|
+
|
|
7
9
|
explain (endpoint) {
|
|
8
10
|
assert.ok(endpoint in this.operations,
|
|
9
11
|
`Endpoint '${endpoint}' is not provided by '${this.locator.id}'`)
|
package/test/event.test.js
CHANGED
|
@@ -41,7 +41,7 @@ describe('condition', () => {
|
|
|
41
41
|
await emit()
|
|
42
42
|
|
|
43
43
|
const payload = await fixtures.bridge.payload.mock.results[0].value
|
|
44
|
-
const message = { payload }
|
|
44
|
+
const message = { payload, telemetry: expect.any(String) }
|
|
45
45
|
expect(fixtures.binding.emit).toHaveBeenCalledWith(message)
|
|
46
46
|
})
|
|
47
47
|
|
|
@@ -71,7 +71,7 @@ describe('condition', () => {
|
|
|
71
71
|
expect(fixtures.bridge.condition).not.toHaveBeenCalledWith()
|
|
72
72
|
|
|
73
73
|
const payload = await fixtures.bridge.payload.mock.results[0].value
|
|
74
|
-
const message = { payload }
|
|
74
|
+
const message = { payload, telemetry: expect.any(String) }
|
|
75
75
|
|
|
76
76
|
expect(fixtures.binding.emit).toHaveBeenCalledWith(message)
|
|
77
77
|
})
|
|
@@ -84,7 +84,7 @@ describe('payload', () => {
|
|
|
84
84
|
await emit()
|
|
85
85
|
|
|
86
86
|
const payload = await fixtures.bridge.payload.mock.results[0].value
|
|
87
|
-
const message = { payload }
|
|
87
|
+
const message = { payload, telemetry: expect.any(String) }
|
|
88
88
|
|
|
89
89
|
expect(fixtures.binding.emit).toHaveBeenCalledWith(message)
|
|
90
90
|
})
|
|
@@ -110,7 +110,7 @@ describe('payload', () => {
|
|
|
110
110
|
await emit()
|
|
111
111
|
|
|
112
112
|
const payload = fixtures.event.state
|
|
113
|
-
const message = { payload }
|
|
113
|
+
const message = { payload, telemetry: expect.any(String) }
|
|
114
114
|
|
|
115
115
|
expect(fixtures.binding.emit).toHaveBeenCalledWith(message)
|
|
116
116
|
})
|
package/types/message.d.ts
CHANGED