@toa.io/core 1.0.0-alpha.26 → 1.0.0-alpha.262
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/CHANGELOG.md +33 -0
- package/package.json +7 -9
- package/src/call.js +19 -1
- package/src/cascade.js +7 -5
- package/src/component.js +41 -17
- package/src/composition.js +1 -1
- package/src/connector.js +21 -14
- package/src/context.js +6 -0
- package/src/contract/contract.js +22 -0
- package/src/contract/reply.js +26 -9
- package/src/contract/request.js +19 -5
- package/src/contract/schemas/index.js +1 -0
- package/src/contract/schemas/query.yaml +14 -1
- package/src/contract/schemas/source.yaml +23 -0
- package/src/discovery.js +13 -7
- package/src/effect.js +19 -0
- package/src/entities/changeset.js +5 -8
- package/src/entities/entity.js +58 -22
- package/src/entities/factory.js +15 -4
- package/src/entities/newid.js +11 -0
- package/src/entities/set.js +13 -0
- package/src/event.js +19 -1
- package/src/exceptions.js +26 -19
- package/src/exposition.js +3 -2
- package/src/guard.js +17 -0
- package/src/index.js +6 -0
- package/src/observation.js +1 -9
- package/src/operation.js +28 -7
- package/src/query/options.js +3 -2
- package/src/query.js +3 -1
- package/src/receiver.js +67 -11
- package/src/remote.js +7 -7
- package/src/state.js +69 -33
- package/src/transition.js +8 -19
- package/src/transmission.js +12 -3
- package/src/unmanaged.js +11 -0
- package/test/component.test.js +4 -3
- package/test/connector.fixtures.js +8 -0
- package/test/connector.test.js +20 -0
- package/test/contract/conditions.test.js +5 -5
- package/test/contract/request.test.js +46 -7
- package/test/discovery.test.js +56 -0
- package/test/entities/entity.test.js +24 -41
- package/test/entities/factory.test.js +3 -3
- package/test/event.test.js +4 -4
- package/test/receiver.fixtures.js +1 -0
- package/test/state.test.js +0 -14
- package/types/bindings.d.ts +7 -5
- package/types/bridges.ts +3 -0
- package/types/component.d.ts +4 -1
- package/types/context.d.ts +3 -0
- package/types/extensions.d.ts +7 -4
- package/types/index.ts +2 -1
- package/types/message.d.ts +1 -0
- package/types/operations.d.ts +6 -0
- package/types/query.d.ts +2 -0
- package/types/remote.d.ts +18 -0
- package/types/request.d.ts +15 -0
- package/types/storages.d.ts +11 -9
- package/src/contract/conditions.js +0 -21
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# Change Log
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
|
+
|
|
6
|
+
# [1.0.0-alpha.262](https://github.com/toa-io/toa/compare/v1.0.0-alpha.261...v1.0.0-alpha.262) (2026-08-28)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
### Features
|
|
10
|
+
|
|
11
|
+
* **core:** identify the origin of a call with `request.source` ([9da6b66](https://github.com/toa-io/toa/commit/9da6b66df852690351a1e82c7b8cc176fd89774a))
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
# [1.0.0-alpha.259](https://github.com/toa-io/toa/compare/v1.0.0-alpha.258...v1.0.0-alpha.259) (2026-08-24)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
### Features
|
|
21
|
+
|
|
22
|
+
* **runtime:** split component RC into preflight and settle ([0d648b3](https://github.com/toa-io/toa/commit/0d648b314e4aac226c254bf01a86affa04b59b34))
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
# [1.0.0-alpha.257](https://github.com/toa-io/toa/compare/v1.0.0-alpha.256...v1.0.0-alpha.257) (2026-08-24)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
### Bug Fixes
|
|
32
|
+
|
|
33
|
+
* **core:** lift tombstone on commit when transition leaves it untouched ([38c0d15](https://github.com/toa-io/toa/commit/38c0d15e946c3e2d559d4b18aecc33e0fda2471a))
|
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.262",
|
|
4
4
|
"description": "Toa Core",
|
|
5
5
|
"author": "temich <tema.gurtovoy@gmail.com>",
|
|
6
6
|
"homepage": "https://github.com/toa-io/toa#readme",
|
|
@@ -21,13 +21,11 @@
|
|
|
21
21
|
},
|
|
22
22
|
"dependencies": {
|
|
23
23
|
"@rsql/parser": "1.2.4",
|
|
24
|
-
"@toa.io/
|
|
25
|
-
"@toa.io/
|
|
26
|
-
"
|
|
27
|
-
"
|
|
24
|
+
"@toa.io/generic": "1.0.0-alpha.254",
|
|
25
|
+
"@toa.io/yaml": "1.0.0-alpha.254",
|
|
26
|
+
"error-value": "0.3.0",
|
|
27
|
+
"openspan": "1.0.0-alpha.254",
|
|
28
|
+
"uuid": "11.1.1"
|
|
28
29
|
},
|
|
29
|
-
"
|
|
30
|
-
"clone-deep": "4.0.1"
|
|
31
|
-
},
|
|
32
|
-
"gitHead": "0ddd9260762d1fe2b8be88eb7b9725e3f7838299"
|
|
30
|
+
"gitHead": "e14059cf5cc364d933f2a4b3d7114f3b5ec0764f"
|
|
33
31
|
}
|
package/src/call.js
CHANGED
|
@@ -1,28 +1,42 @@
|
|
|
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
|
|
|
7
8
|
class Call extends Connector {
|
|
8
9
|
#transmitter
|
|
9
10
|
#contract
|
|
11
|
+
#source
|
|
10
12
|
|
|
11
|
-
constructor (transmitter, contract) {
|
|
13
|
+
constructor (transmitter, contract, source) {
|
|
12
14
|
super()
|
|
13
15
|
|
|
14
16
|
this.#transmitter = transmitter
|
|
15
17
|
this.#contract = contract
|
|
18
|
+
this.#source = source
|
|
16
19
|
|
|
17
20
|
this.depends(transmitter)
|
|
18
21
|
}
|
|
19
22
|
|
|
20
23
|
async invoke (request = {}) {
|
|
24
|
+
request.input ??= null
|
|
25
|
+
|
|
26
|
+
// the caller may have attributed the call itself, as the node bridge does
|
|
27
|
+
if (this.#source !== undefined)
|
|
28
|
+
request.source ??= this.#source
|
|
29
|
+
|
|
21
30
|
this.#contract.fit(request)
|
|
22
31
|
|
|
23
32
|
// avoid validation on the recipient's side
|
|
24
33
|
request.authentic = true
|
|
25
34
|
|
|
35
|
+
const context = current()
|
|
36
|
+
|
|
37
|
+
if (context !== undefined)
|
|
38
|
+
request.telemetry = encode(context)
|
|
39
|
+
|
|
26
40
|
const reply = await this.#transmitter.request(request)
|
|
27
41
|
|
|
28
42
|
if (reply === null) return null
|
|
@@ -37,6 +51,10 @@ class Call extends Connector {
|
|
|
37
51
|
return reply.output
|
|
38
52
|
}
|
|
39
53
|
}
|
|
54
|
+
|
|
55
|
+
explain () {
|
|
56
|
+
return this.#contract.discovery
|
|
57
|
+
}
|
|
40
58
|
}
|
|
41
59
|
|
|
42
60
|
exports.Call = Call
|
package/src/cascade.js
CHANGED
|
@@ -1,19 +1,21 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
const { merge } = require('@toa.io/generic')
|
|
4
3
|
const { Connector } = require('./connector')
|
|
5
4
|
|
|
6
5
|
class Cascade extends Connector {
|
|
7
|
-
#bridges
|
|
6
|
+
// #bridges
|
|
8
7
|
#last
|
|
9
8
|
|
|
10
|
-
constructor (bridges) {
|
|
9
|
+
constructor (bridges, preflight) {
|
|
11
10
|
super()
|
|
12
11
|
|
|
13
|
-
this.#bridges = bridges
|
|
12
|
+
// this.#bridges = bridges
|
|
14
13
|
this.#last = bridges[bridges.length - 1]
|
|
15
14
|
|
|
16
|
-
|
|
15
|
+
if (preflight === undefined)
|
|
16
|
+
this.depends(bridges)
|
|
17
|
+
else
|
|
18
|
+
this.depends(bridges).depends(preflight)
|
|
17
19
|
}
|
|
18
20
|
|
|
19
21
|
async run (...args) {
|
package/src/component.js
CHANGED
|
@@ -1,40 +1,64 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
const
|
|
3
|
+
const assert = require('node:assert')
|
|
4
|
+
const { console, current, decode, run } = require('openspan')
|
|
4
5
|
const { Connector } = require('./connector')
|
|
5
|
-
const { NotImplementedException } = require('./exceptions')
|
|
6
6
|
|
|
7
|
-
/**
|
|
8
|
-
* @implements {toa.core.Component}
|
|
9
|
-
*/
|
|
10
7
|
class Component extends Connector {
|
|
11
8
|
locator
|
|
12
9
|
|
|
13
|
-
|
|
10
|
+
/** @protected */
|
|
11
|
+
operations
|
|
12
|
+
|
|
13
|
+
/** @protected */
|
|
14
|
+
kind = 'server'
|
|
14
15
|
|
|
15
16
|
constructor (locator, operations) {
|
|
16
17
|
super()
|
|
17
18
|
|
|
18
19
|
this.locator = locator
|
|
19
|
-
this
|
|
20
|
+
this.operations = operations
|
|
20
21
|
|
|
21
22
|
Object.values(operations).forEach((operation) => this.depends(operation))
|
|
22
23
|
}
|
|
23
24
|
|
|
24
|
-
async
|
|
25
|
-
|
|
26
|
-
|
|
25
|
+
async invoke (endpoint, request) {
|
|
26
|
+
assert.ok(endpoint in this.operations,
|
|
27
|
+
`Endpoint '${endpoint}' is not provided by '${this.locator.id}'`)
|
|
27
28
|
|
|
28
|
-
|
|
29
|
-
|
|
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)
|
|
30
37
|
}
|
|
31
38
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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'
|
|
53
|
+
|
|
54
|
+
console.error('Failed to execute operation', {
|
|
55
|
+
endpoint: `${this.locator.id}.${endpoint}`,
|
|
56
|
+
exception: reply.exception
|
|
57
|
+
})
|
|
58
|
+
}
|
|
36
59
|
|
|
37
|
-
|
|
60
|
+
return reply
|
|
61
|
+
})
|
|
38
62
|
}
|
|
39
63
|
}
|
|
40
64
|
|
package/src/composition.js
CHANGED
package/src/connector.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
const { console } = require('
|
|
4
|
-
const { newid } = require('@toa.io/generic')
|
|
3
|
+
const { console } = require('openspan')
|
|
5
4
|
|
|
6
5
|
/**
|
|
7
6
|
* Abstract connections hierarchy
|
|
@@ -26,7 +25,7 @@ class Connector {
|
|
|
26
25
|
connected = false
|
|
27
26
|
|
|
28
27
|
constructor () {
|
|
29
|
-
this.id = this.constructor.name + '#' +
|
|
28
|
+
this.id = this.constructor.name + '#' + Math.random().toString(36).substring(2, 8)
|
|
30
29
|
}
|
|
31
30
|
|
|
32
31
|
/**
|
|
@@ -86,12 +85,18 @@ class Connector {
|
|
|
86
85
|
|
|
87
86
|
this.#disconnecting = undefined
|
|
88
87
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
this.#connecting = (async () => {
|
|
88
|
+
const work = async () => {
|
|
92
89
|
await Promise.all(this.#dependencies.map((connector) => connector.connect()))
|
|
93
90
|
await this.open()
|
|
94
|
-
}
|
|
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()
|
|
95
100
|
|
|
96
101
|
try {
|
|
97
102
|
await this.#connecting
|
|
@@ -100,8 +105,6 @@ class Connector {
|
|
|
100
105
|
await this.disconnect(true)
|
|
101
106
|
throw e
|
|
102
107
|
}
|
|
103
|
-
|
|
104
|
-
console.debug(`Connector '${this.id}' connected`)
|
|
105
108
|
}
|
|
106
109
|
|
|
107
110
|
/**
|
|
@@ -115,7 +118,11 @@ class Connector {
|
|
|
115
118
|
* @returns {Promise<void>}
|
|
116
119
|
*/
|
|
117
120
|
async disconnect (interrupt) {
|
|
118
|
-
|
|
121
|
+
// a connector that has not finished connecting has nothing to close, and
|
|
122
|
+
// awaiting a connection that may never settle outlives any grace period
|
|
123
|
+
const pending = interrupt === true || this.connected === false
|
|
124
|
+
|
|
125
|
+
if (!pending) await this.#connecting
|
|
119
126
|
|
|
120
127
|
if (this.#disconnecting) return this.#disconnecting
|
|
121
128
|
|
|
@@ -132,10 +139,11 @@ class Connector {
|
|
|
132
139
|
const interval = setInterval(() => {
|
|
133
140
|
const delay = +new Date() - start
|
|
134
141
|
|
|
135
|
-
if (delay > DELAY)
|
|
142
|
+
if (delay > DELAY)
|
|
143
|
+
console.warn(`Connector ${this.id} still disconnecting (${delay})`)
|
|
136
144
|
}, DELAY)
|
|
137
145
|
|
|
138
|
-
if (
|
|
146
|
+
if (!pending) await this.close()
|
|
139
147
|
|
|
140
148
|
clearInterval(interval)
|
|
141
149
|
|
|
@@ -145,8 +153,6 @@ class Connector {
|
|
|
145
153
|
})()
|
|
146
154
|
|
|
147
155
|
await this.#disconnecting
|
|
148
|
-
|
|
149
|
-
console.debug(`Connector '${this.id}' disconnected`)
|
|
150
156
|
}
|
|
151
157
|
|
|
152
158
|
async reconnect () {
|
|
@@ -179,5 +185,6 @@ class Connector {
|
|
|
179
185
|
}
|
|
180
186
|
|
|
181
187
|
const DELAY = 5000
|
|
188
|
+
const TRACE_BOOT = process.env.TOA_BOOT_TRACE === '1'
|
|
182
189
|
|
|
183
190
|
exports.Connector = Connector
|
package/src/context.js
CHANGED
|
@@ -6,7 +6,10 @@ const { Connector } = require('./connector')
|
|
|
6
6
|
* @implements {toa.core.Context}
|
|
7
7
|
*/
|
|
8
8
|
class Context extends Connector {
|
|
9
|
+
env
|
|
10
|
+
name
|
|
9
11
|
aspects
|
|
12
|
+
locator
|
|
10
13
|
|
|
11
14
|
#local
|
|
12
15
|
#discover
|
|
@@ -15,7 +18,10 @@ class Context extends Connector {
|
|
|
15
18
|
constructor (local, discover, aspects = []) {
|
|
16
19
|
super()
|
|
17
20
|
|
|
21
|
+
this.env = process.env.TOA_ENV
|
|
22
|
+
this.name = process.env.TOA_CONTEXT
|
|
18
23
|
this.aspects = aspects
|
|
24
|
+
this.locator = local?.locator
|
|
19
25
|
|
|
20
26
|
this.#local = local
|
|
21
27
|
this.#discover = discover
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { SystemException } = require('../exceptions')
|
|
4
|
+
|
|
5
|
+
class Contract {
|
|
6
|
+
schema
|
|
7
|
+
|
|
8
|
+
constructor (schema) {
|
|
9
|
+
this.schema = schema
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
fit (value) {
|
|
13
|
+
const error = this.schema.fit(value)
|
|
14
|
+
|
|
15
|
+
if (error !== null)
|
|
16
|
+
throw new this.constructor.Exception(error, value)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
static Exception = SystemException
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
exports.Contract = Contract
|
package/src/contract/reply.js
CHANGED
|
@@ -1,22 +1,39 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
3
|
const schemas = require('./schemas')
|
|
4
|
-
const {
|
|
4
|
+
const { Contract } = require('./contract')
|
|
5
5
|
const { ResponseContractException } = require('../exceptions')
|
|
6
6
|
|
|
7
|
-
class Reply extends
|
|
7
|
+
class Reply extends Contract {
|
|
8
8
|
static Exception = ResponseContractException
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
* @returns {toa.schema.JSON}
|
|
12
|
-
*/
|
|
13
|
-
static schema (output, error) {
|
|
10
|
+
static schema (output, errors) {
|
|
14
11
|
const schema = { type: 'object', properties: {}, additionalProperties: false }
|
|
15
12
|
|
|
16
|
-
if (output !== undefined)
|
|
13
|
+
if (output !== undefined) {
|
|
14
|
+
if (output.type === 'object')
|
|
15
|
+
output.additionalProperties = true
|
|
16
|
+
else if (output.type === 'array' && output.items?.type === 'object')
|
|
17
|
+
output.items.additionalProperties = true
|
|
17
18
|
|
|
18
|
-
|
|
19
|
-
|
|
19
|
+
schema.properties.output = output
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (errors !== undefined)
|
|
23
|
+
schema.properties.error = {
|
|
24
|
+
type: 'object',
|
|
25
|
+
properties: {
|
|
26
|
+
code: {
|
|
27
|
+
enum: errors
|
|
28
|
+
},
|
|
29
|
+
message: {
|
|
30
|
+
type: 'string'
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
required: ['code']
|
|
34
|
+
}
|
|
35
|
+
else
|
|
36
|
+
schema.properties.error = schemas.error
|
|
20
37
|
|
|
21
38
|
return schema
|
|
22
39
|
}
|
package/src/contract/request.js
CHANGED
|
@@ -2,9 +2,20 @@
|
|
|
2
2
|
|
|
3
3
|
const schemas = require('./schemas')
|
|
4
4
|
const { RequestContractException } = require('../exceptions')
|
|
5
|
-
const {
|
|
5
|
+
const { Contract } = require('./contract')
|
|
6
|
+
|
|
7
|
+
class Request extends Contract {
|
|
8
|
+
/** @readonly */
|
|
9
|
+
discovery = {}
|
|
10
|
+
|
|
11
|
+
constructor (schema, definition) {
|
|
12
|
+
super(schema)
|
|
13
|
+
|
|
14
|
+
for (const key of ['input', 'output', 'errors'])
|
|
15
|
+
if (definition[key] !== undefined)
|
|
16
|
+
this.discovery[key] = definition[key]
|
|
17
|
+
}
|
|
6
18
|
|
|
7
|
-
class Request extends Conditions {
|
|
8
19
|
static Exception = RequestContractException
|
|
9
20
|
|
|
10
21
|
/**
|
|
@@ -13,7 +24,11 @@ class Request extends Conditions {
|
|
|
13
24
|
static schema (definition, entity) {
|
|
14
25
|
const schema = {
|
|
15
26
|
type: 'object',
|
|
16
|
-
properties: {
|
|
27
|
+
properties: {
|
|
28
|
+
authentic: { type: 'boolean' },
|
|
29
|
+
task: { type: 'boolean' },
|
|
30
|
+
source: structuredClone(schemas.source)
|
|
31
|
+
},
|
|
17
32
|
additionalProperties: true
|
|
18
33
|
}
|
|
19
34
|
|
|
@@ -22,9 +37,8 @@ class Request extends Conditions {
|
|
|
22
37
|
if (definition.input !== undefined) {
|
|
23
38
|
schema.properties.input = definition.input
|
|
24
39
|
required.push('input')
|
|
25
|
-
} else
|
|
40
|
+
} else
|
|
26
41
|
schema.properties.input = { type: 'null' }
|
|
27
|
-
}
|
|
28
42
|
|
|
29
43
|
if (entity === undefined)
|
|
30
44
|
definition.query = false
|
|
@@ -1,11 +1,22 @@
|
|
|
1
1
|
type: object
|
|
2
2
|
properties:
|
|
3
|
-
id:
|
|
3
|
+
id:
|
|
4
|
+
type: string
|
|
5
|
+
ids:
|
|
6
|
+
type: array
|
|
7
|
+
uniqueItems: true
|
|
8
|
+
minItems: 1
|
|
9
|
+
items:
|
|
10
|
+
type: string
|
|
4
11
|
version:
|
|
5
12
|
type: integer
|
|
6
13
|
minimum: 0
|
|
7
14
|
criteria:
|
|
8
15
|
type: string
|
|
16
|
+
search:
|
|
17
|
+
type: string
|
|
18
|
+
sample:
|
|
19
|
+
type: number
|
|
9
20
|
omit:
|
|
10
21
|
type: integer
|
|
11
22
|
minimum: 0
|
|
@@ -27,4 +38,6 @@ properties:
|
|
|
27
38
|
type: string
|
|
28
39
|
not:
|
|
29
40
|
const: id
|
|
41
|
+
deleted:
|
|
42
|
+
type: boolean
|
|
30
43
|
additionalProperties: false
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# Origin of a call, see toa.core.Source.
|
|
2
|
+
# Deliberately permissive on required properties: the contract throws on a mismatch,
|
|
3
|
+
# and a strict union would turn a partially stamped source into a failed business call.
|
|
4
|
+
# `additionalProperties: false` combined with `removeAdditional` is what matters here —
|
|
5
|
+
# it strips whatever a peer puts in, bounding the cardinality of the resulting map.
|
|
6
|
+
type: object
|
|
7
|
+
properties:
|
|
8
|
+
namespace:
|
|
9
|
+
type: string
|
|
10
|
+
maxLength: 64
|
|
11
|
+
component:
|
|
12
|
+
type: string
|
|
13
|
+
maxLength: 64
|
|
14
|
+
operation:
|
|
15
|
+
type: string
|
|
16
|
+
maxLength: 64
|
|
17
|
+
event:
|
|
18
|
+
type: string
|
|
19
|
+
maxLength: 64
|
|
20
|
+
service:
|
|
21
|
+
type: string
|
|
22
|
+
maxLength: 64
|
|
23
|
+
additionalProperties: false
|
package/src/discovery.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
const { console } = require('
|
|
3
|
+
const { console } = require('openspan')
|
|
4
4
|
const { Connector } = require('./connector')
|
|
5
5
|
|
|
6
6
|
class Discovery extends Connector {
|
|
@@ -25,20 +25,26 @@ class Discovery extends Connector {
|
|
|
25
25
|
this.depends(this.#lookups[id])
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
|
|
28
|
+
const since = Date.now()
|
|
29
29
|
|
|
30
|
-
|
|
31
|
-
|
|
30
|
+
// the wait is unbounded by design, as a dependency may still be starting,
|
|
31
|
+
// so repeating is the only way a component stuck on one stays visible
|
|
32
|
+
const warning = setInterval(() => {
|
|
33
|
+
const waiting = Math.round((Date.now() - since) / 1000)
|
|
34
|
+
|
|
35
|
+
console.warn('Waiting for lookup response', { component: id, waiting })
|
|
36
|
+
}, INTERVAL)
|
|
37
|
+
|
|
38
|
+
warning.unref()
|
|
32
39
|
|
|
33
40
|
const output = await this.#lookups[id].invoke()
|
|
34
41
|
|
|
35
|
-
|
|
36
|
-
clearTimeout(timeout)
|
|
42
|
+
clearInterval(warning)
|
|
37
43
|
|
|
38
44
|
return output
|
|
39
45
|
}
|
|
40
46
|
}
|
|
41
47
|
|
|
42
|
-
const
|
|
48
|
+
const INTERVAL = 5000
|
|
43
49
|
|
|
44
50
|
exports.Discovery = Discovery
|
package/src/effect.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { Observation } = require('./observation')
|
|
4
|
+
|
|
5
|
+
class Effect extends Observation {
|
|
6
|
+
|
|
7
|
+
async acquire (store) {
|
|
8
|
+
const { query, entity, input } = store.request
|
|
9
|
+
|
|
10
|
+
if (entity === undefined)
|
|
11
|
+
return super.acquire(store)
|
|
12
|
+
|
|
13
|
+
store.scope = await this.scope.ensure(query, entity, input)
|
|
14
|
+
store.state = store.scope.get()
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
exports.Effect = Effect
|
|
@@ -1,10 +1,5 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
const {
|
|
4
|
-
merge,
|
|
5
|
-
overwrite,
|
|
6
|
-
newid
|
|
7
|
-
} = require('@toa.io/generic')
|
|
8
3
|
const { EntityContractException } = require('../exceptions')
|
|
9
4
|
|
|
10
5
|
class Changeset {
|
|
@@ -18,7 +13,7 @@ class Changeset {
|
|
|
18
13
|
this.query = query
|
|
19
14
|
|
|
20
15
|
this.#schema = schema
|
|
21
|
-
this.#state =
|
|
16
|
+
this.#state = {}
|
|
22
17
|
}
|
|
23
18
|
|
|
24
19
|
get () {
|
|
@@ -26,10 +21,12 @@ class Changeset {
|
|
|
26
21
|
}
|
|
27
22
|
|
|
28
23
|
set (value) {
|
|
29
|
-
const error = this.#schema.
|
|
24
|
+
const error = this.#schema.match(value)
|
|
30
25
|
|
|
31
|
-
if (error !== null)
|
|
26
|
+
if (error !== null)
|
|
27
|
+
throw new EntityContractException(error, value)
|
|
32
28
|
|
|
29
|
+
delete value._version
|
|
33
30
|
value._updated = Date.now()
|
|
34
31
|
|
|
35
32
|
this.#state = value
|