@toa.io/core 1.0.0-alpha.0 → 1.0.0-alpha.107

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 (48) hide show
  1. package/package.json +7 -8
  2. package/src/assignment.js +11 -2
  3. package/src/call.js +4 -0
  4. package/src/cascade.js +2 -3
  5. package/src/component.js +15 -18
  6. package/src/composition.js +1 -1
  7. package/src/connector.js +3 -8
  8. package/src/contract/contract.js +23 -0
  9. package/src/contract/reply.js +26 -9
  10. package/src/contract/request.js +33 -14
  11. package/src/contract/schemas/index.js +2 -3
  12. package/src/contract/schemas/query.yaml +2 -4
  13. package/src/discovery.js +2 -5
  14. package/src/effect.js +19 -0
  15. package/src/entities/changeset.js +8 -15
  16. package/src/entities/entity.js +31 -12
  17. package/src/entities/factory.js +6 -0
  18. package/src/exceptions.js +21 -19
  19. package/src/exposition.js +3 -2
  20. package/src/index.js +2 -0
  21. package/src/locator.js +7 -2
  22. package/src/observation.js +1 -13
  23. package/src/operation.js +29 -7
  24. package/src/query/criteria.js +3 -3
  25. package/src/query/options.js +3 -0
  26. package/src/receiver.js +13 -10
  27. package/src/remote.js +5 -7
  28. package/src/state.js +63 -45
  29. package/src/transition.js +9 -7
  30. package/src/transmission.js +12 -3
  31. package/test/component.test.js +2 -1
  32. package/test/contract/conditions.test.js +5 -5
  33. package/test/contract/request.test.js +30 -28
  34. package/test/entities/entity.fixtures.js +5 -2
  35. package/test/entities/entity.test.js +7 -46
  36. package/types/bindings.d.ts +3 -1
  37. package/types/bridges.ts +30 -0
  38. package/types/component.d.ts +4 -1
  39. package/types/context.d.ts +12 -18
  40. package/types/extensions.d.ts +4 -3
  41. package/types/index.ts +15 -0
  42. package/types/locator.d.ts +2 -1
  43. package/types/operations.d.ts +6 -0
  44. package/types/remote.d.ts +18 -0
  45. package/types/request.d.ts +1 -0
  46. package/src/contract/conditions.js +0 -21
  47. package/types/bridges.d.ts +0 -41
  48. package/types/index.d.ts +0 -14
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@toa.io/core",
3
- "version": "1.0.0-alpha.0",
3
+ "version": "1.0.0-alpha.107",
4
4
  "description": "Toa Core",
5
5
  "author": "temich <tema.gurtovoy@gmail.com>",
6
6
  "homepage": "https://github.com/toa-io/toa#readme",
@@ -12,7 +12,7 @@
12
12
  "url": "https://github.com/toa-io/toa/issues"
13
13
  },
14
14
  "main": "src/index.js",
15
- "types": "types/index.d.ts",
15
+ "types": "types/index.ts",
16
16
  "publishConfig": {
17
17
  "access": "public"
18
18
  },
@@ -21,11 +21,10 @@
21
21
  },
22
22
  "dependencies": {
23
23
  "@rsql/parser": "1.2.4",
24
- "@toa.io/console": "1.0.0-alpha.0",
25
- "@toa.io/generic": "1.0.0-alpha.0",
26
- "@toa.io/yaml": "1.0.0-alpha.0",
27
- "clone-deep": "4.0.1",
28
- "error-value": "0.3.0"
24
+ "@toa.io/generic": "1.0.0-alpha.93",
25
+ "@toa.io/yaml": "1.0.0-alpha.93",
26
+ "error-value": "0.3.0",
27
+ "openspan": "1.0.0-alpha.93"
29
28
  },
30
- "gitHead": "06c64546f6292cc07c52f74b31415101037f7616"
29
+ "gitHead": "0c69afa09e2515ed76f88eaa9c9f5e5c97d8c5db"
31
30
  }
package/src/assignment.js CHANGED
@@ -9,13 +9,22 @@ class Assignment extends Operation {
9
9
  }
10
10
 
11
11
  async commit (store) {
12
- const { scope, state, reply } = store
12
+ const {
13
+ scope,
14
+ state,
15
+ reply
16
+ } = store
13
17
 
14
18
  if (reply.error !== undefined) return
15
19
 
16
20
  scope.set(state)
17
21
 
18
- await this.scope.apply(scope)
22
+ const output = await this.scope.apply(scope)
23
+
24
+ // assignment returns new state by default
25
+ if (store.reply.output === undefined) {
26
+ store.reply.output = output
27
+ }
19
28
  }
20
29
  }
21
30
 
package/src/call.js CHANGED
@@ -37,6 +37,10 @@ class Call extends Connector {
37
37
  return reply.output
38
38
  }
39
39
  }
40
+
41
+ explain () {
42
+ return this.#contract.discovery
43
+ }
40
44
  }
41
45
 
42
46
  exports.Call = Call
package/src/cascade.js CHANGED
@@ -1,16 +1,15 @@
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
9
  constructor (bridges) {
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
  this.depends(bridges)
package/src/component.js CHANGED
@@ -1,40 +1,37 @@
1
1
  'use strict'
2
2
 
3
- const { console } = require('@toa.io/console')
3
+ const assert = require('node:assert')
4
+ const { console } = 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
- #operations
10
+ /** @protected */
11
+ operations
14
12
 
15
13
  constructor (locator, operations) {
16
14
  super()
17
15
 
18
16
  this.locator = locator
19
- this.#operations = operations
17
+ this.operations = operations
20
18
 
21
19
  Object.values(operations).forEach((operation) => this.depends(operation))
22
20
  }
23
21
 
24
- async open () {
25
- console.info(`Runtime '${this.locator.id}' connected`)
26
- }
22
+ async invoke (endpoint, request) {
23
+ assert.ok(endpoint in this.operations,
24
+ `Endpoint '${endpoint}' is not provided by '${this.locator.id}'`)
27
25
 
28
- async dispose () {
29
- console.info(`Runtime '${this.locator.id}' disconnected`)
30
- }
26
+ const reply = await this.operations[endpoint].invoke(request)
31
27
 
32
- async invoke (endpoint, request) {
33
- if (!(endpoint in this.#operations)) {
34
- throw new NotImplementedException(`Endpoint '${endpoint}' not found in '${this.locator.id}'`)
35
- }
28
+ if (reply?.exception !== undefined)
29
+ console.error('Failed to execute operation', {
30
+ endpoint: `${this.locator.id}.${endpoint}`,
31
+ exception: reply.exception
32
+ })
36
33
 
37
- return this.#operations[endpoint].invoke(request)
34
+ return reply
38
35
  }
39
36
  }
40
37
 
@@ -1,6 +1,6 @@
1
1
  'use strict'
2
2
 
3
- const { console } = require('@toa.io/console')
3
+ const { console } = require('openspan')
4
4
  const { Connector } = require('./connector')
5
5
 
6
6
  class Composition extends Connector {
package/src/connector.js CHANGED
@@ -1,6 +1,6 @@
1
1
  'use strict'
2
2
 
3
- const { console } = require('@toa.io/console')
3
+ const { console } = require('openspan')
4
4
  const { newid } = require('@toa.io/generic')
5
5
 
6
6
  /**
@@ -86,8 +86,6 @@ class Connector {
86
86
 
87
87
  this.#disconnecting = undefined
88
88
 
89
- console.debug(`Connecting '${this.id}' with ${this.#dependencies.length} dependencies...`)
90
-
91
89
  this.#connecting = (async () => {
92
90
  await Promise.all(this.#dependencies.map((connector) => connector.connect()))
93
91
  await this.open()
@@ -100,8 +98,6 @@ class Connector {
100
98
  await this.disconnect(true)
101
99
  throw e
102
100
  }
103
-
104
- console.debug(`Connector '${this.id}' connected`)
105
101
  }
106
102
 
107
103
  /**
@@ -132,7 +128,8 @@ class Connector {
132
128
  const interval = setInterval(() => {
133
129
  const delay = +new Date() - start
134
130
 
135
- if (delay > DELAY) console.warn(`Connector ${this.id} still disconnecting (${delay})`)
131
+ if (delay > DELAY)
132
+ console.warn(`Connector ${this.id} still disconnecting (${delay})`)
136
133
  }, DELAY)
137
134
 
138
135
  if (interrupt !== true) await this.close()
@@ -145,8 +142,6 @@ class Connector {
145
142
  })()
146
143
 
147
144
  await this.#disconnecting
148
-
149
- console.debug(`Connector '${this.id}' disconnected`)
150
145
  }
151
146
 
152
147
  async reconnect () {
@@ -0,0 +1,23 @@
1
+ 'use strict'
2
+
3
+ const { Readable } = require('node:stream')
4
+ const { SystemException } = require('../exceptions')
5
+
6
+ class Contract {
7
+ schema
8
+
9
+ constructor (schema) {
10
+ this.schema = schema
11
+ }
12
+
13
+ fit (value) {
14
+ const error = this.schema.fit(value)
15
+
16
+ if (error !== null)
17
+ throw new this.constructor.Exception(error, value)
18
+ }
19
+
20
+ static Exception = SystemException
21
+ }
22
+
23
+ exports.Contract = Contract
@@ -1,22 +1,39 @@
1
1
  'use strict'
2
2
 
3
3
  const schemas = require('./schemas')
4
- const { Conditions } = require('./conditions')
4
+ const { Contract } = require('./contract')
5
5
  const { ResponseContractException } = require('../exceptions')
6
6
 
7
- class Reply extends Conditions {
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) schema.properties.output = output
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
- if (error !== undefined) schema.properties.error = error
19
- else schema.properties.error = schemas.error
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
  }
@@ -1,36 +1,54 @@
1
1
  'use strict'
2
2
 
3
- const clone = require('clone-deep')
4
-
5
3
  const schemas = require('./schemas')
6
4
  const { RequestContractException } = require('../exceptions')
7
- const { Conditions } = require('./conditions')
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
+ }
8
18
 
9
- class Request extends Conditions {
10
19
  static Exception = RequestContractException
11
20
 
12
21
  /**
13
22
  * @returns {toa.schema.JSON}
14
23
  */
15
- static schema (definition) {
16
- const schema = { type: 'object', properties: { authentic: { type: 'boolean' } }, additionalProperties: true }
24
+ static schema (definition, entity) {
25
+ const schema = {
26
+ type: 'object',
27
+ properties: { authentic: { type: 'boolean' }, task: { type: 'boolean' } },
28
+ additionalProperties: true
29
+ }
30
+
17
31
  const required = []
18
32
 
19
33
  if (definition.input !== undefined) {
20
34
  schema.properties.input = definition.input
21
35
  required.push('input')
22
- } else {
36
+ } else
23
37
  schema.properties.input = { type: 'null' }
24
- }
25
38
 
26
- if (definition.query === true) required.push('query')
39
+ if (entity === undefined)
40
+ definition.query = false
27
41
 
28
- if (definition.query === false) {
29
- schema.not = { required: ['query'] }
30
- }
42
+ if (definition.query === true)
43
+ required.push('query')
44
+
45
+ if (definition.query === false)
46
+ schema.properties.query = { type: 'null' }
31
47
 
32
48
  if (definition.query !== false) {
33
- const query = clone(schemas.query)
49
+ const query = structuredClone(schemas.query)
50
+
51
+ query.properties.id = entity.schema.properties.id
34
52
 
35
53
  if (definition.type === 'observation') {
36
54
  delete query.properties.version
@@ -49,7 +67,8 @@ class Request extends Conditions {
49
67
  schema.properties.query = query
50
68
  }
51
69
 
52
- if (required.length > 0) schema.required = required
70
+ if (required.length > 0)
71
+ schema.required = required
53
72
 
54
73
  return schema
55
74
  }
@@ -2,7 +2,6 @@
2
2
 
3
3
  const { resolve } = require('path')
4
4
  const { load } = require('@toa.io/yaml')
5
- const { freeze } = require('@toa.io/generic')
6
5
 
7
- exports.query = freeze(load.sync(resolve(__dirname, './query.yaml')))
8
- exports.error = freeze(load.sync(resolve(__dirname, './error.yaml')))
6
+ exports.query = load.sync(resolve(__dirname, './query.yaml'))
7
+ exports.error = load.sync(resolve(__dirname, './error.yaml'))
@@ -1,7 +1,6 @@
1
1
  type: object
2
2
  properties:
3
- id:
4
- $ref: https://schemas.toa.io/0.0.0/definitions#/definitions/id
3
+ id: { }
5
4
  version:
6
5
  type: integer
7
6
  minimum: 0
@@ -19,14 +18,13 @@ properties:
19
18
  minItems: 1
20
19
  items:
21
20
  type: string
22
- pattern: ^[a-zA-Z]+([-a-zA-Z0-9]*[a-zA-Z0-9]+)?(:(asc|desc))?$
21
+ pattern: ^\w{1,32}(?::(?:asc|desc))?$
23
22
  projection:
24
23
  type: array
25
24
  uniqueItems: true
26
25
  minItems: 1
27
26
  items:
28
27
  type: string
29
- pattern: ^([a-zA-Z]+([-a-zA-Z0-9]*[a-zA-Z0-9]+)?)$
30
28
  not:
31
29
  const: id
32
30
  additionalProperties: false
package/src/discovery.js CHANGED
@@ -1,6 +1,6 @@
1
1
  'use strict'
2
2
 
3
- const { console } = require('@toa.io/console')
3
+ const { console } = require('openspan')
4
4
  const { Connector } = require('./connector')
5
5
 
6
6
  class Discovery extends Connector {
@@ -25,14 +25,11 @@ class Discovery extends Connector {
25
25
  this.depends(this.#lookups[id])
26
26
  }
27
27
 
28
- console.debug(`Sending lookup request to '${id}'`)
29
-
30
- const warning = () => console.warn(`Waiting for lookup response from '${id}'...`)
28
+ const warning = () => console.warn(`Waiting for lookup response`, { component: id })
31
29
  const timeout = setTimeout(warning, TIMEOUT)
32
30
 
33
31
  const output = await this.#lookups[id].invoke()
34
32
 
35
- console.debug(`Lookup response from '${id}' received`)
36
33
  clearTimeout(timeout)
37
34
 
38
35
  return output
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,6 +1,5 @@
1
1
  'use strict'
2
2
 
3
- const { merge, overwrite, newid } = require('@toa.io/generic')
4
3
  const { EntityContractException } = require('../exceptions')
5
4
 
6
5
  class Changeset {
@@ -14,7 +13,7 @@ class Changeset {
14
13
  this.query = query
15
14
 
16
15
  this.#schema = schema
17
- this.#state = schema.system()
16
+ this.#state = {}
18
17
  }
19
18
 
20
19
  get () {
@@ -22,25 +21,19 @@ class Changeset {
22
21
  }
23
22
 
24
23
  set (value) {
25
- const error = this.#schema.adapt(value)
24
+ const error = this.#schema.match(value)
26
25
 
27
- if (error !== null) throw new EntityContractException(error)
26
+ if (error !== null)
27
+ throw new EntityContractException(error, value)
28
+
29
+ delete value._version
30
+ value._updated = Date.now()
28
31
 
29
32
  this.#state = value
30
33
  }
31
34
 
32
35
  export () {
33
- const changeset = this.#state
34
- const result = { changeset }
35
- const insert = merge({ id: newid() }, changeset)
36
- const error = this.#schema.fit(insert)
37
-
38
- if (error === null) {
39
- delete insert.id
40
- result.insert = overwrite(insert, changeset)
41
- }
42
-
43
- return result
36
+ return this.#state
44
37
  }
45
38
  }
46
39
 
@@ -1,11 +1,10 @@
1
1
  'use strict'
2
2
 
3
- const clone = require('clone-deep')
4
3
  const { difference, newid } = require('@toa.io/generic')
5
-
6
4
  const { EntityContractException } = require('../exceptions')
7
5
 
8
6
  class Entity {
7
+ deleted = false
9
8
  #schema
10
9
  #origin = null
11
10
  #state
@@ -14,11 +13,11 @@ class Entity {
14
13
  this.#schema = schema
15
14
 
16
15
  if (typeof argument === 'object') {
17
- const object = clone(argument)
16
+ const object = structuredClone(argument)
18
17
  this.set(object)
19
18
  this.#origin = argument
20
19
  } else {
21
- const id = typeof argument === 'string' ? argument : newid()
20
+ const id = argument === undefined ? newid() : argument
22
21
  this.#init(id)
23
22
  }
24
23
  }
@@ -27,30 +26,50 @@ class Entity {
27
26
  return this.#state
28
27
  }
29
28
 
30
- set (value) {
31
- const error = this.#schema.fit(value)
29
+ set (value, optional = false) {
30
+ const error = optional ? this.#schema.fitOptional(value) : this.#schema.fit(value)
32
31
 
33
- if (error !== null) throw new EntityContractException(error)
32
+ if (error !== null)
33
+ throw new EntityContractException(error, value)
34
34
 
35
35
  this.#set(value)
36
36
  }
37
37
 
38
- event () {
38
+ event (input = undefined) {
39
39
  return {
40
40
  origin: this.#origin,
41
41
  state: this.#state,
42
- changeset: this.#origin === null ? this.#state : difference(this.#origin, this.#state)
42
+ changeset: this.#origin === null ? this.#state : difference(this.#origin, this.#state),
43
+ trailers: this.#state._trailers,
44
+ input
43
45
  }
44
46
  }
45
47
 
46
48
  #init (id) {
47
- const value = { ...this.#schema.defaults({ id }), _version: 0 }
49
+ const value = { id, _version: 0 }
48
50
 
49
- this.#set(value)
51
+ this.set(value, true)
50
52
  }
51
53
 
52
54
  #set (value) {
53
- Object.defineProperty(value, 'id', { writable: false, configurable: false })
55
+ if (!('_trailers' in value))
56
+ Object.defineProperty(value, '_trailers', {
57
+ writable: false,
58
+ configurable: false,
59
+ enumerable: false,
60
+ value: {}
61
+ })
62
+
63
+ if (!('_created' in value))
64
+ value._created = Date.now()
65
+
66
+ if (value._deleted !== null)
67
+ this.deleted = true
68
+
69
+ if (this.#state !== undefined) {
70
+ value._updated = Date.now()
71
+ value._version++
72
+ }
54
73
 
55
74
  this.#state = value
56
75
  }
@@ -1,5 +1,7 @@
1
1
  'use strict'
2
2
 
3
+ const { newid } = require('@toa.io/generic')
4
+
3
5
  const { Entity } = require('./entity')
4
6
  const { EntitySet } = require('./set')
5
7
  const { Changeset } = require('./changeset')
@@ -11,6 +13,10 @@ class Factory {
11
13
  this.#schema = schema
12
14
  }
13
15
 
16
+ fit (values) {
17
+ this.#schema.validate({ id: newid(), ...values }, 'Entity')
18
+ }
19
+
14
20
  init (id) {
15
21
  return new Entity(this.#schema, id)
16
22
  }
package/src/exceptions.js CHANGED
@@ -4,7 +4,6 @@ const { swap } = require('@toa.io/generic')
4
4
 
5
5
  const codes = {
6
6
  System: 0,
7
- NotImplemented: 10,
8
7
 
9
8
  Contract: 200,
10
9
  RequestSyntax: 201,
@@ -19,6 +18,7 @@ const codes = {
19
18
  StatePrecondition: 303,
20
19
  StateConcurrency: 304,
21
20
  StateInitialization: 305,
21
+ Duplicate: 306,
22
22
 
23
23
  Communication: 400,
24
24
  Transmission: 401
@@ -31,9 +31,12 @@ class Exception {
31
31
  code
32
32
  message
33
33
 
34
- constructor (code, message) {
34
+ constructor (code, message, cause) {
35
35
  this.code = code
36
36
  this.message = message
37
+
38
+ if (cause !== undefined)
39
+ this.cause = cause
37
40
  }
38
41
  }
39
42
 
@@ -48,31 +51,26 @@ class SystemException extends Exception {
48
51
  }
49
52
 
50
53
  class ContractException extends Exception {
51
- keyword
52
- property
53
- schema
54
- path
55
-
56
- constructor (code, error) {
57
- super(code || codes.Contract, error.message)
58
-
59
- this.keyword = error.keyword
60
- this.property = error.property
61
- this.schema = error.schema
62
- this.path = error.path
54
+ constructor (code, error, cause) {
55
+ super(code || codes.Contract, typeof error === 'string' ? error : error?.message, cause)
56
+
57
+ if (typeof error === 'object' && error !== null)
58
+ for (const k of ['keyword', 'property', 'schema', 'path', 'params'])
59
+ if (k in error)
60
+ this[k] = error[k]
63
61
  }
64
62
  }
65
63
 
66
64
  class RequestContractException extends ContractException {
67
- constructor (error) { super(codes.RequestContract, error) }
65
+ constructor (error, cause) { super(codes.RequestContract, error, cause) }
68
66
  }
69
67
 
70
68
  class ResponseContractException extends ContractException {
71
- constructor (error) { super(codes.ResponseContract, error) }
69
+ constructor (error, cause) { super(codes.ResponseContract, error, cause) }
72
70
  }
73
71
 
74
72
  class EntityContractException extends ContractException {
75
- constructor (error) { super(codes.EntityContract, error) }
73
+ constructor (error, cause) { super(codes.EntityContract, error, cause) }
76
74
  }
77
75
 
78
76
  // #region exports
@@ -87,8 +85,12 @@ for (const [name, code] of Object.entries(codes)) {
87
85
 
88
86
  if (exports[classname] === undefined) {
89
87
  exports[classname] = class extends Exception {
90
- constructor (message) {
91
- super(code, message || classname)
88
+ constructor (message, cause) {
89
+ message = message
90
+ ? `${classname}: ${message}`
91
+ : classname
92
+
93
+ super(code, message ?? classname, cause)
92
94
  }
93
95
  }
94
96
  }