@toa.io/core 0.2.0-dev.3 → 0.2.1-dev.0
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 +5 -4
- package/src/assignment.js +7 -7
- package/src/cascade.js +1 -1
- package/src/{runtime.js → component.js} +7 -4
- package/src/composition.js +6 -7
- package/src/connector.js +89 -24
- package/src/context.js +23 -11
- package/src/contract/reply.js +4 -1
- package/src/contract/request.js +13 -3
- package/src/contract/schemas/index.js +4 -3
- package/src/discovery.js +11 -0
- package/src/entities/changeset.js +4 -3
- package/src/entities/entity.js +17 -6
- package/src/entities/factory.js +3 -3
- package/src/event.js +12 -5
- package/src/exceptions.js +9 -1
- package/src/exposition.js +12 -9
- package/src/index.js +4 -2
- package/src/locator.js +21 -35
- package/src/observation.js +6 -6
- package/src/operation.js +20 -13
- package/src/query/options.js +4 -7
- package/src/query.js +6 -1
- package/src/receiver.js +36 -7
- package/src/reflection.js +28 -0
- package/src/remote.js +3 -3
- package/src/state.js +33 -25
- package/src/transition.js +24 -16
- package/test/call.fixtures.js +1 -0
- package/test/{runtime.fixtures.js → component.fixtures.js} +4 -2
- package/test/{runtime.test.js → component.test.js} +7 -7
- package/test/connector.fixtures.js +1 -1
- package/test/connector.test.js +6 -29
- package/test/context.fixtures.js +18 -0
- package/test/context.test.js +29 -0
- package/test/contract/contract.fixtures.js +8 -3
- package/test/contract/request.test.js +14 -3
- package/test/emission.fixtures.js +1 -0
- package/test/entities/entity.fixtures.js +4 -4
- package/test/entities/entity.test.js +22 -10
- package/test/entities/factory.test.js +6 -6
- package/test/event.fixtures.js +1 -0
- package/test/event.test.js +16 -4
- package/test/locator.test.js +76 -19
- package/test/receiver.fixtures.js +6 -5
- package/test/receiver.test.js +36 -7
- package/test/reflection.test.js +30 -0
- package/test/state.fixtures.js +6 -5
- package/test/state.test.js +8 -16
- package/types/bindings.d.ts +39 -0
- package/types/bridges.d.ts +35 -0
- package/types/component.ts +16 -0
- package/types/connector.d.ts +22 -0
- package/types/context.d.ts +24 -0
- package/types/entity.d.ts +46 -0
- package/types/event.d.ts +11 -0
- package/types/exception.d.ts +10 -0
- package/types/extensions.d.ts +36 -0
- package/types/index.d.ts +14 -0
- package/types/locator.d.ts +16 -0
- package/types/message.d.ts +9 -0
- package/types/query.d.ts +15 -0
- package/types/receiver.d.ts +12 -0
- package/types/reflection.d.ts +16 -0
- package/types/reply.d.ts +15 -0
- package/types/request.d.ts +25 -0
- package/types/state.d.ts +35 -0
- package/types/storages.d.ts +71 -0
- package/LICENSE +0 -22
package/src/exposition.js
CHANGED
|
@@ -1,24 +1,27 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
const { Connector } = require('./connector')
|
|
4
|
+
|
|
5
|
+
class Exposition extends Connector {
|
|
4
6
|
locator
|
|
5
7
|
|
|
6
|
-
#
|
|
8
|
+
#exposition
|
|
7
9
|
|
|
8
10
|
constructor (locator, manifest) {
|
|
9
|
-
|
|
11
|
+
super()
|
|
10
12
|
|
|
11
|
-
this
|
|
13
|
+
this.locator = locator
|
|
14
|
+
this.#exposition = expose(manifest)
|
|
12
15
|
}
|
|
13
16
|
|
|
14
17
|
async invoke () {
|
|
15
|
-
return { output: this.#
|
|
18
|
+
return { output: this.#exposition }
|
|
16
19
|
}
|
|
20
|
+
}
|
|
17
21
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
}
|
|
22
|
+
const expose = (manifest) => {
|
|
23
|
+
const { namespace, name, operations, events } = manifest
|
|
24
|
+
return { namespace, name, operations, events }
|
|
22
25
|
}
|
|
23
26
|
|
|
24
27
|
exports.Exposition = Exposition
|
package/src/index.js
CHANGED
|
@@ -12,8 +12,9 @@ const { Locator } = require('./locator')
|
|
|
12
12
|
const { Observation } = require('./observation')
|
|
13
13
|
const { Query } = require('./query')
|
|
14
14
|
const { Receiver } = require('./receiver')
|
|
15
|
+
const { Reflection } = require('./reflection')
|
|
15
16
|
const { Remote } = require('./remote')
|
|
16
|
-
const {
|
|
17
|
+
const { Component } = require('./component')
|
|
17
18
|
const { State } = require('./state')
|
|
18
19
|
const { Transition } = require('./transition')
|
|
19
20
|
const { Transmission } = require('./transmission')
|
|
@@ -25,6 +26,7 @@ exports.contract = require('./contract')
|
|
|
25
26
|
exports.Assignment = Assignment
|
|
26
27
|
exports.Call = Call
|
|
27
28
|
exports.Cascade = Cascade
|
|
29
|
+
exports.Component = Component
|
|
28
30
|
exports.Composition = Composition
|
|
29
31
|
exports.Connector = Connector
|
|
30
32
|
exports.Context = Context
|
|
@@ -36,8 +38,8 @@ exports.Locator = Locator
|
|
|
36
38
|
exports.Observation = Observation
|
|
37
39
|
exports.Query = Query
|
|
38
40
|
exports.Receiver = Receiver
|
|
41
|
+
exports.Reflection = Reflection
|
|
39
42
|
exports.Remote = Remote
|
|
40
|
-
exports.Runtime = Runtime
|
|
41
43
|
exports.State = State
|
|
42
44
|
exports.Transition = Transition
|
|
43
45
|
exports.Transmission = Transmission
|
package/src/locator.js
CHANGED
|
@@ -1,51 +1,37 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
const { concat } = require('@toa.io/
|
|
3
|
+
const { concat } = require('@toa.io/generic')
|
|
4
4
|
|
|
5
|
+
// noinspection JSClosureCompilerSyntax
|
|
6
|
+
/**
|
|
7
|
+
* @implements {toa.core.Locator}
|
|
8
|
+
*/
|
|
5
9
|
class Locator {
|
|
6
|
-
domain = 'system'
|
|
7
10
|
name
|
|
11
|
+
namespace
|
|
12
|
+
|
|
8
13
|
id
|
|
9
14
|
label
|
|
15
|
+
uppercase
|
|
10
16
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
17
|
+
/**
|
|
18
|
+
* @param {string} name
|
|
19
|
+
* @param {string} [namespace]
|
|
20
|
+
*/
|
|
21
|
+
constructor (name, namespace) {
|
|
22
|
+
if (name === undefined) throw new TypeError('Locator name must be defined')
|
|
16
23
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
}
|
|
24
|
+
this.name = name
|
|
25
|
+
this.namespace = namespace
|
|
20
26
|
|
|
21
|
-
this.id =
|
|
22
|
-
this.label =
|
|
27
|
+
this.id = concat(namespace, '.') + name
|
|
28
|
+
this.label = (concat(namespace, '-') + name).toLowerCase()
|
|
29
|
+
this.uppercase = (concat(namespace, '_') + name).toUpperCase()
|
|
23
30
|
}
|
|
24
31
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
const segments = LEVELS.slice(0, level + 1)
|
|
29
|
-
|
|
30
|
-
for (const segment of segments) {
|
|
31
|
-
host += concat(segment(this), '-')
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
if (type) host += type.toLowerCase()
|
|
35
|
-
|
|
36
|
-
return host
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
static parse (label) {
|
|
40
|
-
const [domain, name, ...rest] = label.split('.')
|
|
41
|
-
|
|
42
|
-
return { domain, name, endpoint: rest.join('.') }
|
|
32
|
+
hostname (prefix) {
|
|
33
|
+
return concat(prefix?.toLowerCase(), '-') + this.label
|
|
43
34
|
}
|
|
44
35
|
}
|
|
45
36
|
|
|
46
|
-
const LEVELS = [
|
|
47
|
-
(locator) => locator.domain,
|
|
48
|
-
(locator) => locator.name
|
|
49
|
-
]
|
|
50
|
-
|
|
51
37
|
exports.Locator = Locator
|
package/src/observation.js
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
const { freeze } = require('@toa.io/
|
|
3
|
+
const { freeze } = require('@toa.io/generic')
|
|
4
4
|
|
|
5
5
|
const { Operation } = require('./operation')
|
|
6
6
|
|
|
7
7
|
class Observation extends Operation {
|
|
8
|
-
async acquire (
|
|
9
|
-
const
|
|
10
|
-
const state =
|
|
8
|
+
async acquire (store) {
|
|
9
|
+
const scope = await this.query(store.request.query)
|
|
10
|
+
const state = scope === null ? null : scope.get()
|
|
11
11
|
|
|
12
12
|
freeze(state)
|
|
13
13
|
|
|
14
|
-
scope
|
|
15
|
-
|
|
14
|
+
store.scope = scope
|
|
15
|
+
store.state = state
|
|
16
16
|
}
|
|
17
17
|
}
|
|
18
18
|
|
package/src/operation.js
CHANGED
|
@@ -4,20 +4,23 @@ const { Connector } = require('./connector')
|
|
|
4
4
|
const { SystemException } = require('./exceptions')
|
|
5
5
|
|
|
6
6
|
class Operation extends Connector {
|
|
7
|
-
|
|
7
|
+
scope
|
|
8
8
|
|
|
9
9
|
#cascade
|
|
10
10
|
#contract
|
|
11
11
|
#query
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
#scope
|
|
14
|
+
|
|
15
|
+
constructor (cascade, scope, contract, query, definition) {
|
|
14
16
|
super()
|
|
15
17
|
|
|
16
|
-
this.
|
|
18
|
+
this.scope = scope
|
|
17
19
|
|
|
18
20
|
this.#cascade = cascade
|
|
19
21
|
this.#contract = contract
|
|
20
22
|
this.#query = query
|
|
23
|
+
this.#scope = definition.scope
|
|
21
24
|
|
|
22
25
|
this.depends(cascade)
|
|
23
26
|
}
|
|
@@ -26,9 +29,9 @@ class Operation extends Connector {
|
|
|
26
29
|
try {
|
|
27
30
|
if (request.query) request.query = this.#query.parse(request.query)
|
|
28
31
|
|
|
29
|
-
const
|
|
32
|
+
const store = { request }
|
|
30
33
|
|
|
31
|
-
return await this.process(
|
|
34
|
+
return await this.process(store)
|
|
32
35
|
} catch (e) {
|
|
33
36
|
const exception = e instanceof Error ? new SystemException(e) : e
|
|
34
37
|
|
|
@@ -36,26 +39,30 @@ class Operation extends Connector {
|
|
|
36
39
|
}
|
|
37
40
|
}
|
|
38
41
|
|
|
39
|
-
async process (
|
|
40
|
-
await this.acquire(
|
|
41
|
-
await this.run(
|
|
42
|
-
await this.commit(
|
|
42
|
+
async process (store) {
|
|
43
|
+
await this.acquire(store)
|
|
44
|
+
await this.run(store)
|
|
45
|
+
await this.commit(store)
|
|
43
46
|
|
|
44
|
-
return
|
|
47
|
+
return store.reply
|
|
45
48
|
}
|
|
46
49
|
|
|
47
50
|
async acquire () {}
|
|
48
51
|
|
|
49
|
-
async run (
|
|
50
|
-
const { request, state } =
|
|
52
|
+
async run (store) {
|
|
53
|
+
const { request, state } = store
|
|
51
54
|
const reply = await this.#cascade.run(request.input, state) || {}
|
|
52
55
|
|
|
53
56
|
this.#contract.fit(reply)
|
|
54
57
|
|
|
55
|
-
|
|
58
|
+
store.reply = reply
|
|
56
59
|
}
|
|
57
60
|
|
|
58
61
|
async commit () {}
|
|
62
|
+
|
|
63
|
+
async query (query) {
|
|
64
|
+
return this.scope[this.#scope](query)
|
|
65
|
+
}
|
|
59
66
|
}
|
|
60
67
|
|
|
61
68
|
exports.Operation = Operation
|
package/src/query/options.js
CHANGED
|
@@ -4,7 +4,8 @@ const { QuerySyntaxException } = require('../exceptions')
|
|
|
4
4
|
|
|
5
5
|
const options = (options, properties, system) => {
|
|
6
6
|
if (options.sort !== undefined) options.sort = sort(options.sort, properties)
|
|
7
|
-
|
|
7
|
+
|
|
8
|
+
if (options.projection !== undefined) projection(options.projection, properties)
|
|
8
9
|
|
|
9
10
|
return options
|
|
10
11
|
}
|
|
@@ -25,16 +26,12 @@ const sort = (sort, properties) => {
|
|
|
25
26
|
return result
|
|
26
27
|
}
|
|
27
28
|
|
|
28
|
-
const projection = (projection, properties
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
for (const property of set) {
|
|
29
|
+
const projection = (projection, properties) => {
|
|
30
|
+
for (const property of projection) {
|
|
32
31
|
if (properties[property] === undefined) {
|
|
33
32
|
throw new QuerySyntaxException(`Projection property '${property}' is not defined`)
|
|
34
33
|
}
|
|
35
34
|
}
|
|
36
|
-
|
|
37
|
-
return set
|
|
38
35
|
}
|
|
39
36
|
|
|
40
37
|
exports.options = options
|
package/src/query.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
const { empty } = require('@toa.io/
|
|
3
|
+
const { empty } = require('@toa.io/generic')
|
|
4
4
|
const parse = { ...require('./query/criteria'), ...require('./query/options') }
|
|
5
5
|
|
|
6
6
|
class Query {
|
|
@@ -12,7 +12,12 @@ class Query {
|
|
|
12
12
|
this.#system = Object.keys(properties).filter((key) => properties[key].system === true)
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
+
/**
|
|
16
|
+
* @param {toa.core.request.Query} query
|
|
17
|
+
* @returns {toa.core.storages.Query}
|
|
18
|
+
*/
|
|
15
19
|
parse (query) {
|
|
20
|
+
/** @type {toa.core.storages.Query} */
|
|
16
21
|
const result = {}
|
|
17
22
|
const { id, version, criteria, ...rest } = query
|
|
18
23
|
|
package/src/receiver.js
CHANGED
|
@@ -1,21 +1,41 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
+
const { add } = require('@toa.io/generic')
|
|
3
4
|
const { Connector } = require('./connector')
|
|
4
5
|
|
|
6
|
+
/**
|
|
7
|
+
* @implements {toa.core.Receiver}
|
|
8
|
+
*/
|
|
5
9
|
class Receiver extends Connector {
|
|
10
|
+
/** @type {boolean} */
|
|
6
11
|
#conditioned
|
|
12
|
+
|
|
13
|
+
/** @type {boolean} */
|
|
7
14
|
#adaptive
|
|
8
|
-
#transition
|
|
9
15
|
|
|
16
|
+
/** @type {string} */
|
|
17
|
+
#endpoint
|
|
18
|
+
|
|
19
|
+
/** @type {toa.core.Component} */
|
|
10
20
|
#local
|
|
21
|
+
|
|
22
|
+
/** @type {toa.core.bridges.Event} */
|
|
11
23
|
#bridge
|
|
12
24
|
|
|
25
|
+
/**
|
|
26
|
+
*
|
|
27
|
+
* @param {toa.norm.component.Receiver} definition
|
|
28
|
+
* @param {toa.core.Component} local
|
|
29
|
+
* @param {toa.core.bridges.Event} bridge
|
|
30
|
+
*/
|
|
13
31
|
constructor (definition, local, bridge) {
|
|
14
32
|
super()
|
|
15
33
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
this.#
|
|
34
|
+
const { conditioned, adaptive, transition } = definition
|
|
35
|
+
|
|
36
|
+
this.#conditioned = conditioned
|
|
37
|
+
this.#adaptive = adaptive
|
|
38
|
+
this.#endpoint = transition
|
|
19
39
|
|
|
20
40
|
this.#local = local
|
|
21
41
|
this.#bridge = bridge
|
|
@@ -24,12 +44,21 @@ class Receiver extends Connector {
|
|
|
24
44
|
this.depends(bridge)
|
|
25
45
|
}
|
|
26
46
|
|
|
27
|
-
|
|
47
|
+
/** @hot */
|
|
48
|
+
async receive (message) {
|
|
49
|
+
const { payload, ...extensions } = message
|
|
50
|
+
|
|
28
51
|
if (this.#conditioned && await this.#bridge.condition(payload) === false) return
|
|
29
52
|
|
|
30
|
-
const request =
|
|
53
|
+
const request = await this.#request(payload)
|
|
54
|
+
|
|
55
|
+
if (extensions) add(request, extensions)
|
|
56
|
+
|
|
57
|
+
await this.#local.invoke(this.#endpoint, request)
|
|
58
|
+
}
|
|
31
59
|
|
|
32
|
-
|
|
60
|
+
async #request (payload) {
|
|
61
|
+
return this.#adaptive ? await this.#bridge.request(payload) : payload
|
|
33
62
|
}
|
|
34
63
|
}
|
|
35
64
|
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { Connector } = require('./connector')
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @implements {toa.core.Reflection}
|
|
7
|
+
*/
|
|
8
|
+
class Reflection extends Connector {
|
|
9
|
+
/** @type {toa.core.reflection.Source} */
|
|
10
|
+
#source
|
|
11
|
+
|
|
12
|
+
value
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @param {toa.core.reflection.Source} source
|
|
16
|
+
*/
|
|
17
|
+
constructor (source) {
|
|
18
|
+
super()
|
|
19
|
+
|
|
20
|
+
this.#source = source
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async connection () {
|
|
24
|
+
this.value = await this.#source()
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
exports.Reflection = Reflection
|
package/src/remote.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
const { console } = require('@toa.io/
|
|
3
|
+
const { console } = require('@toa.io/console')
|
|
4
4
|
|
|
5
|
-
const {
|
|
5
|
+
const { Component } = require('./component')
|
|
6
6
|
|
|
7
|
-
class Remote extends
|
|
7
|
+
class Remote extends Component {
|
|
8
8
|
async connection () {
|
|
9
9
|
console.info(`Remote '${this.locator.id}' connected`)
|
|
10
10
|
}
|
package/src/state.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
const { empty } = require('@toa.io/
|
|
3
|
+
const { empty } = require('@toa.io/generic')
|
|
4
4
|
|
|
5
5
|
const {
|
|
6
6
|
StatePreconditionException,
|
|
@@ -8,28 +8,34 @@ const {
|
|
|
8
8
|
StateInitializationException
|
|
9
9
|
} = require('./exceptions')
|
|
10
10
|
|
|
11
|
+
/**
|
|
12
|
+
* @implements {toa.core.State}
|
|
13
|
+
*/
|
|
11
14
|
class State {
|
|
15
|
+
/** @type {toa.core.Storage} */
|
|
12
16
|
#storage
|
|
17
|
+
|
|
18
|
+
/** @type {toa.core.entity.Factory} */
|
|
13
19
|
#entity
|
|
14
|
-
#
|
|
20
|
+
#emission
|
|
15
21
|
#initialized
|
|
16
22
|
|
|
17
|
-
constructor (storage, entity,
|
|
23
|
+
constructor (storage, entity, emission, initialized) {
|
|
18
24
|
this.#storage = storage
|
|
19
25
|
this.#entity = entity
|
|
20
|
-
this.#
|
|
26
|
+
this.#emission = emission
|
|
21
27
|
this.#initialized = initialized
|
|
22
28
|
}
|
|
23
29
|
|
|
24
30
|
init (id) {
|
|
25
31
|
if (this.#initialized === true && id === undefined) {
|
|
26
|
-
throw new StateInitializationException('
|
|
32
|
+
throw new StateInitializationException('Cannot initialize entity which is initialized. Use request.query.id to access.')
|
|
27
33
|
}
|
|
28
34
|
|
|
29
35
|
return this.#entity.init(id)
|
|
30
36
|
}
|
|
31
37
|
|
|
32
|
-
async
|
|
38
|
+
async object (query) {
|
|
33
39
|
const record = await this.#storage.get(query)
|
|
34
40
|
|
|
35
41
|
if (record === null) {
|
|
@@ -38,57 +44,59 @@ class State {
|
|
|
38
44
|
else throw new StateNotFoundException()
|
|
39
45
|
}
|
|
40
46
|
|
|
41
|
-
return this.#entity.
|
|
47
|
+
return this.#entity.object(record)
|
|
42
48
|
}
|
|
43
49
|
|
|
44
|
-
async
|
|
50
|
+
async objects (query) {
|
|
45
51
|
const recordset = await this.#storage.find(query)
|
|
46
52
|
|
|
47
|
-
return this.#entity.
|
|
53
|
+
return this.#entity.objects(recordset)
|
|
48
54
|
}
|
|
49
55
|
|
|
50
56
|
changeset (query) {
|
|
51
57
|
return this.#entity.changeset(query)
|
|
52
58
|
}
|
|
53
59
|
|
|
54
|
-
|
|
55
|
-
|
|
60
|
+
none () {
|
|
61
|
+
return null
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async commit (state) {
|
|
65
|
+
const event = state.event()
|
|
56
66
|
|
|
57
67
|
let ok = true
|
|
58
68
|
|
|
59
69
|
if (!empty(event.changeset)) {
|
|
60
|
-
|
|
70
|
+
const object = state.get()
|
|
71
|
+
|
|
72
|
+
ok = await this.#storage.store(object)
|
|
61
73
|
|
|
62
|
-
// TODO: do not wait because outbox will handle failures
|
|
63
74
|
// TODO: handle slow emissions (too many concurrent emissions)
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
}
|
|
75
|
+
// TODO: do not wait because outbox will handle failures
|
|
76
|
+
await this.#emission.emit(event)
|
|
67
77
|
}
|
|
68
78
|
|
|
69
79
|
return ok
|
|
70
80
|
}
|
|
71
81
|
|
|
72
|
-
async apply (
|
|
73
|
-
const { changeset, insert } =
|
|
82
|
+
async apply (state) {
|
|
83
|
+
const { changeset, insert } = state.export()
|
|
74
84
|
|
|
75
85
|
let upsert
|
|
76
86
|
|
|
77
|
-
if (this.#initialized &&
|
|
87
|
+
if (this.#initialized && state.query.id !== undefined && state.query.version === undefined) {
|
|
78
88
|
upsert = insert
|
|
79
89
|
}
|
|
80
90
|
|
|
81
|
-
const
|
|
91
|
+
const result = await this.#storage.upsert(state.query, changeset, upsert)
|
|
82
92
|
|
|
83
|
-
if (
|
|
84
|
-
if (
|
|
93
|
+
if (result === null) {
|
|
94
|
+
if (state.query.version !== undefined) throw new StatePreconditionException()
|
|
85
95
|
else throw new StateNotFoundException()
|
|
86
96
|
}
|
|
87
97
|
|
|
88
98
|
// TODO: same as above
|
|
89
|
-
|
|
90
|
-
await this.#emitter.emit({ changeset, state })
|
|
91
|
-
}
|
|
99
|
+
await this.#emission.emit({ changeset, state: result })
|
|
92
100
|
}
|
|
93
101
|
}
|
|
94
102
|
|
package/src/transition.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
const { retry } = require('@toa.io/
|
|
3
|
+
const { retry } = require('@toa.io/generic')
|
|
4
4
|
|
|
5
5
|
const { Operation } = require('./operation')
|
|
6
6
|
const { StateConcurrencyException } = require('./exceptions')
|
|
@@ -8,31 +8,31 @@ const { StateConcurrencyException } = require('./exceptions')
|
|
|
8
8
|
class Transition extends Operation {
|
|
9
9
|
#concurrency
|
|
10
10
|
|
|
11
|
-
constructor (cascade,
|
|
12
|
-
super(cascade,
|
|
11
|
+
constructor (cascade, scope, contract, query, definition) {
|
|
12
|
+
super(cascade, scope, contract, query, definition)
|
|
13
13
|
|
|
14
14
|
this.#concurrency = definition.concurrency
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
async process (
|
|
18
|
-
return retry((retry) => this.#retry(
|
|
17
|
+
async process (store) {
|
|
18
|
+
return retry((retry) => this.#retry(store, retry), RETRY)
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
-
async acquire (
|
|
22
|
-
const { request } =
|
|
21
|
+
async acquire (store) {
|
|
22
|
+
const { request } = store
|
|
23
23
|
|
|
24
|
-
scope
|
|
25
|
-
|
|
24
|
+
store.scope = request.query ? await this.query(request.query) : this.scope.init()
|
|
25
|
+
store.state = store.scope.get()
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
async commit (
|
|
29
|
-
const {
|
|
28
|
+
async commit (store) {
|
|
29
|
+
const { scope, state, reply, retry } = store
|
|
30
30
|
|
|
31
31
|
if (reply.error !== undefined) return
|
|
32
32
|
|
|
33
|
-
|
|
33
|
+
scope.set(state)
|
|
34
34
|
|
|
35
|
-
const ok = await this.
|
|
35
|
+
const ok = await this.scope.commit(scope)
|
|
36
36
|
|
|
37
37
|
if (ok !== true) {
|
|
38
38
|
if (this.#concurrency === 'retry') retry()
|
|
@@ -40,11 +40,19 @@ class Transition extends Operation {
|
|
|
40
40
|
}
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
-
async #retry (
|
|
44
|
-
|
|
43
|
+
async #retry (store, retry) {
|
|
44
|
+
store.retry = retry
|
|
45
45
|
|
|
46
|
-
return super.process(
|
|
46
|
+
return super.process(store)
|
|
47
47
|
}
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
+
/** @type {toa.generic.retry.Options} */
|
|
51
|
+
const RETRY = {
|
|
52
|
+
base: 10,
|
|
53
|
+
dispersion: 1,
|
|
54
|
+
max: 5000,
|
|
55
|
+
retries: Infinity
|
|
56
|
+
}
|
|
57
|
+
|
|
50
58
|
exports.Transition = Transition
|
package/test/call.fixtures.js
CHANGED
|
@@ -6,10 +6,12 @@ const invocation = () => jest.fn(() => randomstring.generate())
|
|
|
6
6
|
|
|
7
7
|
const invocations = {
|
|
8
8
|
foo: {
|
|
9
|
-
invoke: invocation('foo')
|
|
9
|
+
invoke: invocation('foo'),
|
|
10
|
+
link: () => null
|
|
10
11
|
},
|
|
11
12
|
bar: {
|
|
12
|
-
invoke: invocation('bar')
|
|
13
|
+
invoke: invocation('bar'),
|
|
14
|
+
link: () => null
|
|
13
15
|
}
|
|
14
16
|
}
|
|
15
17
|
|
|
@@ -1,39 +1,39 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
const {
|
|
3
|
+
const { Component } = require('../src/component')
|
|
4
4
|
const { codes } = require('../src/exceptions')
|
|
5
|
-
const fixtures = require('./
|
|
5
|
+
const fixtures = require('./component.fixtures')
|
|
6
6
|
|
|
7
7
|
describe('Invocations', () => {
|
|
8
8
|
const name = ['foo', 'bar'][Math.floor(2 * Math.random())]
|
|
9
9
|
const invocation = fixtures.invocations[name]
|
|
10
|
-
const
|
|
10
|
+
const component = new Component(fixtures.locator, fixtures.invocations)
|
|
11
11
|
|
|
12
12
|
beforeEach(() => {
|
|
13
13
|
jest.clearAllMocks()
|
|
14
14
|
})
|
|
15
15
|
|
|
16
16
|
it('should invoke', async () => {
|
|
17
|
-
await
|
|
17
|
+
await component.invoke(name)
|
|
18
18
|
|
|
19
19
|
expect(invocation.invoke).toBeCalled()
|
|
20
20
|
})
|
|
21
21
|
|
|
22
22
|
it('should throw on unknown invocation name', async () => {
|
|
23
|
-
await expect(() =>
|
|
23
|
+
await expect(() => component.invoke('baz'))
|
|
24
24
|
.rejects.toMatchObject({ code: codes.NotImplemented })
|
|
25
25
|
})
|
|
26
26
|
|
|
27
27
|
it('should invoke input and query', async () => {
|
|
28
28
|
const input = { test: Math.random() }
|
|
29
29
|
const query = { test: Math.random() }
|
|
30
|
-
await
|
|
30
|
+
await component.invoke(name, { input, query })
|
|
31
31
|
|
|
32
32
|
expect(invocation.invoke).toBeCalledWith({ input, query })
|
|
33
33
|
})
|
|
34
34
|
|
|
35
35
|
it('should return io', async () => {
|
|
36
|
-
const io = await
|
|
36
|
+
const io = await component.invoke(name)
|
|
37
37
|
|
|
38
38
|
expect(io).toBe(fixtures.invocations[name].invoke.mock.results[0].value)
|
|
39
39
|
})
|