@toa.io/core 0.1.0-alpha.12

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 (68) hide show
  1. package/LICENSE +22 -0
  2. package/package.json +28 -0
  3. package/src/assignment.js +22 -0
  4. package/src/call.js +29 -0
  5. package/src/cascade.js +32 -0
  6. package/src/composition.js +26 -0
  7. package/src/connector.js +122 -0
  8. package/src/context.js +43 -0
  9. package/src/contract/conditions.js +21 -0
  10. package/src/contract/index.js +7 -0
  11. package/src/contract/reply.js +22 -0
  12. package/src/contract/request.js +49 -0
  13. package/src/contract/schemas/error.yaml +7 -0
  14. package/src/contract/schemas/index.js +7 -0
  15. package/src/contract/schemas/query.yaml +31 -0
  16. package/src/discovery.js +33 -0
  17. package/src/emission.js +23 -0
  18. package/src/entities/changeset.js +46 -0
  19. package/src/entities/entity.js +48 -0
  20. package/src/entities/factory.js +33 -0
  21. package/src/entities/index.js +5 -0
  22. package/src/entities/set.js +15 -0
  23. package/src/event.js +33 -0
  24. package/src/exceptions.js +88 -0
  25. package/src/exposition.js +24 -0
  26. package/src/index.js +43 -0
  27. package/src/locator.js +49 -0
  28. package/src/observation.js +19 -0
  29. package/src/operation.js +61 -0
  30. package/src/query/criteria.js +41 -0
  31. package/src/query/options.js +40 -0
  32. package/src/query.js +36 -0
  33. package/src/receiver.js +36 -0
  34. package/src/remote.js +17 -0
  35. package/src/runtime.js +38 -0
  36. package/src/state.js +95 -0
  37. package/src/transition.js +50 -0
  38. package/src/transmission.js +33 -0
  39. package/test/call.fixtures.js +25 -0
  40. package/test/call.test.js +52 -0
  41. package/test/cascade.fixtures.js +11 -0
  42. package/test/cascade.test.js +42 -0
  43. package/test/connector.fixtures.js +40 -0
  44. package/test/connector.test.js +199 -0
  45. package/test/contract/conditions.test.js +26 -0
  46. package/test/contract/contract.fixtures.js +27 -0
  47. package/test/contract/request.test.js +99 -0
  48. package/test/emission.fixtures.js +16 -0
  49. package/test/emission.test.js +35 -0
  50. package/test/entities/entity.fixtures.js +26 -0
  51. package/test/entities/entity.test.js +64 -0
  52. package/test/entities/factory.fixtures.js +18 -0
  53. package/test/entities/factory.test.js +48 -0
  54. package/test/entities/set.fixtures.js +11 -0
  55. package/test/entities/set.test.js +12 -0
  56. package/test/event.fixtures.js +28 -0
  57. package/test/event.test.js +106 -0
  58. package/test/locator.test.js +34 -0
  59. package/test/query.fixtures.js +100 -0
  60. package/test/query.test.js +86 -0
  61. package/test/receiver.fixtures.js +22 -0
  62. package/test/receiver.test.js +66 -0
  63. package/test/runtime.fixtures.js +19 -0
  64. package/test/runtime.test.js +40 -0
  65. package/test/state.fixtures.js +46 -0
  66. package/test/state.test.js +54 -0
  67. package/test/transmission.fixtures.js +15 -0
  68. package/test/transmission.test.js +46 -0
package/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2020-present Artem Gurtovoi
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package/package.json ADDED
@@ -0,0 +1,28 @@
1
+ {
2
+ "name": "@toa.io/core",
3
+ "version": "0.1.0-alpha.12+7af8037",
4
+ "description": "Toa Core",
5
+ "author": "temich <tema.gurtovoy@gmail.com>",
6
+ "homepage": "https://github.com/toa-io/toa#readme",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/toa-io/toa.git"
10
+ },
11
+ "bugs": {
12
+ "url": "https://github.com/toa-io/toa/issues"
13
+ },
14
+ "main": "src/index.js",
15
+ "publishConfig": {
16
+ "access": "public"
17
+ },
18
+ "scripts": {
19
+ "test": "echo \"Error: run tests from root\" && exit 1"
20
+ },
21
+ "dependencies": {
22
+ "@rsql/parser": "1.2.4",
23
+ "@toa.io/gears": "0.1.0-alpha.12+7af8037",
24
+ "@toa.io/schema": "0.1.0-alpha.12+7af8037",
25
+ "clone-deep": "4.0.1"
26
+ },
27
+ "gitHead": "7af80377d59d326298804b1c10c8dffde54abd16"
28
+ }
@@ -0,0 +1,22 @@
1
+ 'use strict'
2
+
3
+ const { Operation } = require('./operation')
4
+
5
+ class Assignment extends Operation {
6
+ async acquire (scope) {
7
+ scope.subject = this.subject.changeset(scope.request.query)
8
+ scope.state = scope.subject.get()
9
+ }
10
+
11
+ async commit (scope) {
12
+ const { subject, state, reply } = scope
13
+
14
+ if (reply.error !== undefined) return
15
+
16
+ subject.set(state)
17
+
18
+ await this.subject.apply(subject)
19
+ }
20
+ }
21
+
22
+ exports.Assignment = Assignment
package/src/call.js ADDED
@@ -0,0 +1,29 @@
1
+ 'use strict'
2
+
3
+ const { Connector } = require('./connector')
4
+
5
+ class Call extends Connector {
6
+ #transmitter
7
+ #contract
8
+
9
+ constructor (transmitter, contract) {
10
+ super()
11
+
12
+ this.#transmitter = transmitter
13
+ this.#contract = contract
14
+
15
+ this.depends(transmitter)
16
+ }
17
+
18
+ async invoke (request = {}) {
19
+ this.#contract.fit(request)
20
+
21
+ const { exception, ...reply } = await this.#transmitter.request(request)
22
+
23
+ if (exception) throw exception
24
+
25
+ return reply
26
+ }
27
+ }
28
+
29
+ exports.Call = Call
package/src/cascade.js ADDED
@@ -0,0 +1,32 @@
1
+ 'use strict'
2
+
3
+ const { merge } = require('@toa.io/gears')
4
+ const { Connector } = require('./connector')
5
+
6
+ class Cascade extends Connector {
7
+ #bridges
8
+
9
+ constructor (bridges) {
10
+ super()
11
+
12
+ this.#bridges = bridges
13
+
14
+ this.depends(bridges)
15
+ }
16
+
17
+ async run (...args) {
18
+ const reply = {}
19
+
20
+ for (const bridge of this.#bridges) {
21
+ const partial = await bridge.run(...args)
22
+
23
+ if (partial.error) return { error: partial.error }
24
+
25
+ merge(reply, partial)
26
+ }
27
+
28
+ return reply
29
+ }
30
+ }
31
+
32
+ exports.Cascade = Cascade
@@ -0,0 +1,26 @@
1
+ 'use strict'
2
+
3
+ const { console } = require('@toa.io/gears')
4
+
5
+ const { Connector } = require('./connector')
6
+
7
+ class Composition extends Connector {
8
+ constructor (expositions, producers, receivers, extensions) {
9
+ super()
10
+
11
+ this.depends(expositions)
12
+ this.depends(producers)
13
+ this.depends(receivers)
14
+ this.depends(extensions)
15
+ }
16
+
17
+ async connection () {
18
+ console.info('Composition complete')
19
+ }
20
+
21
+ async disconnected () {
22
+ console.info('Composition shutdown complete')
23
+ }
24
+ }
25
+
26
+ exports.Composition = Composition
@@ -0,0 +1,122 @@
1
+ 'use strict'
2
+
3
+ const { console } = require('@toa.io/gears')
4
+ const { newid } = require('@toa.io/gears')
5
+
6
+ class Connector {
7
+ #connectors = []
8
+ #linked = []
9
+ #connecting
10
+ #disconnecting
11
+
12
+ id
13
+ connected = false
14
+
15
+ constructor () {
16
+ this.id = this.constructor.name + '#' + newid().substring(0, 8)
17
+ }
18
+
19
+ depends (connector) {
20
+ let next
21
+
22
+ if (connector instanceof Array) {
23
+ connector = connector.filter((item) => item instanceof Connector)
24
+
25
+ if (connector.length > 0) {
26
+ next = new Connector()
27
+
28
+ for (const item of connector) {
29
+ this.#connectors.push(item)
30
+ item.depends(next)
31
+ }
32
+ }
33
+ } else {
34
+ if (connector instanceof Connector) {
35
+ next = connector
36
+ }
37
+ }
38
+
39
+ if (next !== undefined) {
40
+ this.#connectors.push(next)
41
+ next.link(this)
42
+
43
+ return next
44
+ } else return this
45
+ }
46
+
47
+ link (connector) {
48
+ this.#linked.push(connector)
49
+ }
50
+
51
+ async connect () {
52
+ if (this.#connecting) return this.#connecting
53
+
54
+ this.#disconnecting = undefined
55
+
56
+ this.#connecting = (async () => {
57
+ await Promise.all(this.#connectors.map(connector => connector.connect()))
58
+ await this.connection()
59
+ })()
60
+
61
+ try {
62
+ await this.#connecting
63
+ this.connected = true
64
+ } catch (e) {
65
+ await this.disconnect(true)
66
+ throw e
67
+ }
68
+
69
+ console.debug(`Connector '${this.id}' connected`)
70
+ }
71
+
72
+ async disconnect (interrupt) {
73
+ if (interrupt !== true) await this.#connecting
74
+
75
+ if (this.#disconnecting) return this.#disconnecting
76
+
77
+ const linked = this.#linked.reduce((acc, parent) => acc || parent.connected, false)
78
+
79
+ if (linked && interrupt !== true) return
80
+
81
+ this.connected = false
82
+ this.#connecting = undefined
83
+
84
+ this.#disconnecting = (async () => {
85
+ const start = +new Date()
86
+ const interval = setInterval(() => {
87
+ const delay = +new Date() - start
88
+
89
+ if (delay > DELAY) console.warn(`Connector ${this.id} still disconnecting (${delay})`)
90
+ }, DELAY)
91
+
92
+ if (interrupt !== true) await this.disconnection()
93
+
94
+ clearInterval(interval)
95
+
96
+ await Promise.all(this.#connectors.map(connector => connector.disconnect()))
97
+ this.disconnected()
98
+ })()
99
+
100
+ await this.#disconnecting
101
+
102
+ console.debug(`Connector '${this.id}' disconnected`)
103
+ }
104
+
105
+ debug (node = {}) {
106
+ node[this.id] = { connected: this.connected }
107
+
108
+ if (this.#connectors.length > 0) for (const connector of this.#connectors) connector.debug?.(node[this.id])
109
+
110
+ return node
111
+ }
112
+
113
+ async connection () {}
114
+
115
+ async disconnection () {}
116
+
117
+ disconnected () {}
118
+ }
119
+
120
+ const DELAY = 5000
121
+
122
+ exports.Connector = Connector
package/src/context.js ADDED
@@ -0,0 +1,43 @@
1
+ 'use strict'
2
+
3
+ const { Connector } = require('./connector')
4
+
5
+ class Context extends Connector {
6
+ #local
7
+ #discover
8
+ #remotes = {}
9
+
10
+ constructor (local, discover) {
11
+ super()
12
+
13
+ this.#local = local
14
+ this.#discover = discover
15
+
16
+ this.depends(local)
17
+ }
18
+
19
+ async apply (endpoint, request) {
20
+ return this.#local.invoke(endpoint, request)
21
+ }
22
+
23
+ async call (domain, name, endpoint, request) {
24
+ const remote = await this.#remote(domain, name)
25
+
26
+ return remote.invoke(endpoint, request)
27
+ }
28
+
29
+ async #remote (domain, name) {
30
+ if (this.#remotes[domain] === undefined) this.#remotes[domain] = {}
31
+
32
+ if (this.#remotes[domain][name] === undefined) {
33
+ const remote = await this.#discover(domain, name)
34
+
35
+ this.depends(remote)
36
+ this.#remotes[domain][name] = remote
37
+ }
38
+
39
+ return this.#remotes[domain][name]
40
+ }
41
+ }
42
+
43
+ exports.Context = Context
@@ -0,0 +1,21 @@
1
+ 'use strict'
2
+
3
+ const { SystemException } = require('../exceptions')
4
+
5
+ class Conditions {
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) throw new this.constructor.Exception(error)
16
+ }
17
+
18
+ static Exception = SystemException
19
+ }
20
+
21
+ exports.Conditions = Conditions
@@ -0,0 +1,7 @@
1
+ 'use strict'
2
+
3
+ const { Request } = require('./request')
4
+ const { Reply } = require('./reply')
5
+
6
+ exports.Request = Request
7
+ exports.Reply = Reply
@@ -0,0 +1,22 @@
1
+ 'use strict'
2
+
3
+ const schemas = require('./schemas')
4
+ const { Conditions } = require('./conditions')
5
+ const { ResponseContractException } = require('../exceptions')
6
+
7
+ class Reply extends Conditions {
8
+ static Exception = ResponseContractException
9
+
10
+ static schema (output, error) {
11
+ const schema = { properties: {}, additionalProperties: false }
12
+
13
+ if (output !== undefined) schema.properties.output = output
14
+
15
+ if (error !== undefined) schema.properties.error = error
16
+ else schema.properties.error = schemas.error
17
+
18
+ return schema
19
+ }
20
+ }
21
+
22
+ exports.Reply = Reply
@@ -0,0 +1,49 @@
1
+ 'use strict'
2
+
3
+ const clone = require('clone-deep')
4
+ const schemas = require('./schemas')
5
+ const { RequestContractException } = require('../exceptions')
6
+ const { Conditions } = require('./conditions')
7
+
8
+ class Request extends Conditions {
9
+ static Exception = RequestContractException
10
+
11
+ static schema (definition) {
12
+ const schema = { properties: {}, additionalProperties: false }
13
+ const required = []
14
+
15
+ if (definition.input) {
16
+ definition.input.additionalProperties = false
17
+ schema.properties.input = definition.input
18
+ required.push('input')
19
+ }
20
+
21
+ if (definition.query === true) required.push('query')
22
+
23
+ if (definition.query !== false) {
24
+ const query = clone(schemas.query)
25
+
26
+ if (definition.type === 'observation') {
27
+ delete query.properties.version
28
+ } else {
29
+ delete query.properties.projection
30
+ }
31
+
32
+ if (definition.type !== 'observation' || definition.subject !== 'set') {
33
+ delete query.properties.omit
34
+ delete query.properties.limit
35
+ } else {
36
+ if (query.required === undefined) query.required = ['limit']
37
+ else query.required.push('limit')
38
+ }
39
+
40
+ schema.properties.query = query
41
+ }
42
+
43
+ if (required.length > 0) schema.required = required
44
+
45
+ return schema
46
+ }
47
+ }
48
+
49
+ exports.Request = Request
@@ -0,0 +1,7 @@
1
+ properties:
2
+ code:
3
+ type: integer
4
+ minimum: 0
5
+ message:
6
+ type: string
7
+ required: [code, message]
@@ -0,0 +1,7 @@
1
+ 'use strict'
2
+
3
+ const { resolve } = require('path')
4
+ const { yaml, freeze } = require('@toa.io/gears')
5
+
6
+ exports.query = freeze(yaml.sync(resolve(__dirname, './query.yaml')))
7
+ exports.error = freeze(yaml.sync(resolve(__dirname, './error.yaml')))
@@ -0,0 +1,31 @@
1
+ properties:
2
+ id:
3
+ $ref: https://schemas.toa.io/0.0.0/definitions#/definitions/id
4
+ version:
5
+ type: integer
6
+ minimum: 0
7
+ criteria:
8
+ type: string
9
+ omit:
10
+ type: integer
11
+ minimum: 0
12
+ limit:
13
+ type: integer
14
+ minimum: 0
15
+ sort:
16
+ type: array
17
+ uniqueItems: true
18
+ minItems: 1
19
+ items:
20
+ type: string
21
+ pattern: ^[a-zA-Z]+([-a-zA-Z0-9]*[a-zA-Z0-9]+)?(:(asc|desc))?$
22
+ projection:
23
+ type: array
24
+ uniqueItems: true
25
+ minItems: 1
26
+ items:
27
+ type: string
28
+ pattern: ^([a-zA-Z]+([-a-zA-Z0-9]*[a-zA-Z0-9]+)?)$
29
+ not:
30
+ const: id
31
+ additionalProperties: false
@@ -0,0 +1,33 @@
1
+ 'use strict'
2
+
3
+ const { Connector } = require('./connector')
4
+
5
+ class Discovery extends Connector {
6
+ #lookup
7
+ #lookups
8
+
9
+ constructor (lookup) {
10
+ super()
11
+
12
+ this.#lookup = lookup
13
+ }
14
+
15
+ async connection () {
16
+ this.#lookups = {}
17
+ }
18
+
19
+ async lookup (locator) {
20
+ const id = locator.id
21
+
22
+ if (this.#lookups[id] === undefined) {
23
+ this.#lookups[id] = await this.#lookup(locator)
24
+ this.depends(this.#lookups[id])
25
+ }
26
+
27
+ const { output } = await this.#lookups[id].invoke()
28
+
29
+ return output
30
+ }
31
+ }
32
+
33
+ exports.Discovery = Discovery
@@ -0,0 +1,23 @@
1
+ 'use strict'
2
+
3
+ const { Connector } = require('./connector')
4
+
5
+ class Emission extends Connector {
6
+ #events
7
+
8
+ constructor (events) {
9
+ super()
10
+
11
+ this.#events = events
12
+
13
+ this.depends(events)
14
+ }
15
+
16
+ async emit (event) {
17
+ const emission = this.#events.map((e) => e.emit(event))
18
+
19
+ await Promise.all(emission)
20
+ }
21
+ }
22
+
23
+ exports.Emission = Emission
@@ -0,0 +1,46 @@
1
+ 'use strict'
2
+
3
+ const { merge, newid } = require('@toa.io/gears')
4
+ const { EntityContractException } = require('../exceptions')
5
+
6
+ class Changeset {
7
+ query
8
+
9
+ #schema
10
+ #state
11
+
12
+ constructor (schema, query) {
13
+ this.query = query
14
+
15
+ this.#schema = schema
16
+ this.#state = schema.system()
17
+ }
18
+
19
+ get () {
20
+ return this.#state
21
+ }
22
+
23
+ set (value) {
24
+ const error = this.#schema.match(value)
25
+
26
+ if (error !== null) throw new EntityContractException(error)
27
+
28
+ this.#state = value
29
+ }
30
+
31
+ export () {
32
+ const changeset = this.#state
33
+ const result = { changeset }
34
+ const insert = merge({ id: newid() }, changeset)
35
+ const error = this.#schema.fit(insert)
36
+
37
+ if (error === null) {
38
+ delete insert.id
39
+ result.insert = merge(insert, changeset, { override: true })
40
+ }
41
+
42
+ return result
43
+ }
44
+ }
45
+
46
+ exports.Changeset = Changeset
@@ -0,0 +1,48 @@
1
+ 'use strict'
2
+
3
+ const clone = require('clone-deep')
4
+ const { difference, newid } = require('@toa.io/gears')
5
+
6
+ const { EntityContractException } = require('../exceptions')
7
+
8
+ class Entity {
9
+ #schema
10
+ #origin = null
11
+ #state
12
+
13
+ constructor (schema, argument) {
14
+ this.#schema = schema
15
+
16
+ if (typeof argument === 'object') {
17
+ this.#state = clone(argument)
18
+ this.#origin = argument
19
+ } else {
20
+ const id = typeof argument === 'string' ? argument : newid()
21
+ this.#state = this.#initial(id)
22
+ }
23
+ }
24
+
25
+ get () {
26
+ return this.#state
27
+ }
28
+
29
+ set (value) {
30
+ const error = this.#schema.fit(value)
31
+
32
+ if (error) throw new EntityContractException(error)
33
+
34
+ this.#state = value
35
+ }
36
+
37
+ event () {
38
+ return {
39
+ origin: this.#origin,
40
+ state: this.#state,
41
+ changeset: this.#origin === null ? this.#state : difference(this.#origin, this.#state)
42
+ }
43
+ }
44
+
45
+ #initial = (id) => this.#schema.defaults({ id })
46
+ }
47
+
48
+ exports.Entity = Entity
@@ -0,0 +1,33 @@
1
+ 'use strict'
2
+
3
+ const { Entity } = require('./entity')
4
+ const { EntitySet } = require('./set')
5
+ const { Changeset } = require('./changeset')
6
+
7
+ class Factory {
8
+ #schema
9
+
10
+ constructor (schema) {
11
+ this.#schema = schema
12
+ }
13
+
14
+ init (id) {
15
+ return new Entity(this.#schema, id)
16
+ }
17
+
18
+ entity (record) {
19
+ return new Entity(this.#schema, record)
20
+ }
21
+
22
+ set (recordset) {
23
+ const set = recordset.map((record) => this.entity(record))
24
+
25
+ return new EntitySet(set)
26
+ }
27
+
28
+ changeset (query) {
29
+ return new Changeset(this.#schema, query)
30
+ }
31
+ }
32
+
33
+ exports.Factory = Factory
@@ -0,0 +1,5 @@
1
+ 'use strict'
2
+
3
+ const { Factory } = require('./factory')
4
+
5
+ exports.Factory = Factory
@@ -0,0 +1,15 @@
1
+ 'use strict'
2
+
3
+ class EntitySet {
4
+ #set
5
+
6
+ constructor (set) {
7
+ this.#set = set
8
+ }
9
+
10
+ get () {
11
+ return this.#set.map((entity) => entity.get())
12
+ }
13
+ }
14
+
15
+ exports.EntitySet = EntitySet