@toa.io/core 1.0.0-alpha.42 → 1.0.0-alpha.44

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@toa.io/core",
3
- "version": "1.0.0-alpha.42",
3
+ "version": "1.0.0-alpha.44",
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,13 @@
21
21
  },
22
22
  "dependencies": {
23
23
  "@rsql/parser": "1.2.4",
24
- "@toa.io/console": "1.0.0-alpha.42",
25
- "@toa.io/generic": "1.0.0-alpha.42",
26
- "@toa.io/yaml": "1.0.0-alpha.42",
24
+ "@toa.io/console": "1.0.0-alpha.44",
25
+ "@toa.io/generic": "1.0.0-alpha.44",
26
+ "@toa.io/yaml": "1.0.0-alpha.44",
27
27
  "error-value": "0.3.0"
28
28
  },
29
29
  "devDependencies": {
30
30
  "clone-deep": "4.0.1"
31
31
  },
32
- "gitHead": "6df7ba51d3f8f419b95253538e08dc7e9bc14072"
32
+ "gitHead": "9a0401a82a7e63102a1dc5be8686ecc232fdabee"
33
33
  }
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/component.js CHANGED
@@ -1,40 +1,28 @@
1
1
  'use strict'
2
2
 
3
- const { console } = require('@toa.io/console')
4
3
  const { Connector } = require('./connector')
5
- const { NotImplementedException } = require('./exceptions')
4
+ const assert = require('node:assert')
6
5
 
7
- /**
8
- * @implements {toa.core.Component}
9
- */
10
6
  class Component extends Connector {
11
7
  locator
12
8
 
13
- #operations
9
+ /** @protected */
10
+ operations
14
11
 
15
12
  constructor (locator, operations) {
16
13
  super()
17
14
 
18
15
  this.locator = locator
19
- this.#operations = operations
16
+ this.operations = operations
20
17
 
21
18
  Object.values(operations).forEach((operation) => this.depends(operation))
22
19
  }
23
20
 
24
- async open () {
25
- console.info(`Runtime '${this.locator.id}' connected`)
26
- }
27
-
28
- async dispose () {
29
- console.info(`Runtime '${this.locator.id}' disconnected`)
30
- }
31
-
32
21
  async invoke (endpoint, request) {
33
- if (!(endpoint in this.#operations)) {
34
- throw new NotImplementedException(`Endpoint '${endpoint}' not found in '${this.locator.id}'`)
35
- }
22
+ assert.ok(endpoint in this.operations,
23
+ `Endpoint '${endpoint}' is not provided by '${this.locator.id}'`)
36
24
 
37
- return this.#operations[endpoint].invoke(request)
25
+ return this.operations[endpoint].invoke(request)
38
26
  }
39
27
  }
40
28
 
@@ -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)
17
+ }
18
+
19
+ static Exception = SystemException
20
+ }
21
+
22
+ 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
  }
@@ -2,9 +2,20 @@
2
2
 
3
3
  const schemas = require('./schemas')
4
4
  const { RequestContractException } = require('../exceptions')
5
- 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
+ }
6
18
 
7
- class Request extends Conditions {
8
19
  static Exception = RequestContractException
9
20
 
10
21
  /**
@@ -13,7 +24,7 @@ class Request extends Conditions {
13
24
  static schema (definition, entity) {
14
25
  const schema = {
15
26
  type: 'object',
16
- properties: { authentic: { type: 'boolean' } },
27
+ properties: { authentic: { type: 'boolean' }, task: { type: 'boolean' } },
17
28
  additionalProperties: true
18
29
  }
19
30
 
@@ -22,9 +33,8 @@ class Request extends Conditions {
22
33
  if (definition.input !== undefined) {
23
34
  schema.properties.input = definition.input
24
35
  required.push('input')
25
- } else {
36
+ } else
26
37
  schema.properties.input = { type: 'null' }
27
- }
28
38
 
29
39
  if (entity === undefined)
30
40
  definition.query = false
package/src/discovery.js CHANGED
@@ -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
28
  const warning = () => console.warn(`Waiting for lookup response from '${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
@@ -13,7 +13,7 @@ class Changeset {
13
13
  this.query = query
14
14
 
15
15
  this.#schema = schema
16
- this.#state = schema.system()
16
+ this.#state = {}
17
17
  }
18
18
 
19
19
  get () {
@@ -21,10 +21,12 @@ class Changeset {
21
21
  }
22
22
 
23
23
  set (value) {
24
- const error = this.#schema.adapt(value)
24
+ const error = this.#schema.fitOptional(value)
25
25
 
26
- if (error !== null) throw new EntityContractException(error)
26
+ if (error !== null)
27
+ throw new EntityContractException(error)
27
28
 
29
+ delete value._version
28
30
  value._updated = Date.now()
29
31
 
30
32
  this.#state = value
@@ -25,8 +25,8 @@ class Entity {
25
25
  return this.#state
26
26
  }
27
27
 
28
- set (value) {
29
- const error = this.#schema.fit(value)
28
+ set (value, optional = false) {
29
+ const error = optional ? this.#schema.fitOptional(value) : this.#schema.fit(value)
30
30
 
31
31
  if (error !== null)
32
32
  throw new EntityContractException(error)
@@ -44,12 +44,9 @@ class Entity {
44
44
  }
45
45
 
46
46
  #init (id) {
47
- const value = {
48
- ...this.#schema.defaults({ id }),
49
- _version: 0
50
- }
47
+ const value = { id, _version: 0 }
51
48
 
52
- this.#set(value)
49
+ this.set(value, true)
53
50
  }
54
51
 
55
52
  #set (value) {
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,
@@ -53,7 +52,7 @@ class ContractException extends Exception {
53
52
  super(code || codes.Contract, typeof error === 'string' ? error : error?.message)
54
53
 
55
54
  if (typeof error === 'object' && error !== null)
56
- for (const k of ['keyword', 'property', 'schema', 'path'])
55
+ for (const k of ['keyword', 'property', 'schema', 'path', 'params'])
57
56
  if (k in error)
58
57
  this[k] = error[k]
59
58
  }
package/src/exposition.js CHANGED
@@ -20,8 +20,9 @@ class Exposition extends Connector {
20
20
  }
21
21
 
22
22
  const expose = (manifest) => {
23
- const { namespace, name, operations, events, entity } = manifest
24
- return { namespace, name, operations, events, entity }
23
+ const { namespace, name, entity, operations, events } = manifest
24
+
25
+ return { namespace, name, entity, operations, events }
25
26
  }
26
27
 
27
28
  exports.Exposition = Exposition
package/src/operation.js CHANGED
@@ -70,10 +70,11 @@ class Operation extends Connector {
70
70
 
71
71
  async run (store) {
72
72
  const { request, state } = store
73
- // noinspection UnnecessaryLocalVariableJS
74
- const reply = await this.#cascade.run(request.input, state) || {}
73
+ const reply = await this.#cascade.run(request.input, state)
75
74
 
76
- // this.#contracts.reply.fit(reply)
75
+ // validate reply only on local environments
76
+ if (process.env.TOA_ENV === 'local')
77
+ this.#contracts.reply.fit(reply)
77
78
 
78
79
  store.reply = reply
79
80
  }
package/src/remote.js CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  const { console } = require('@toa.io/console')
4
4
 
5
+ const assert = require('node:assert')
5
6
  const { Component } = require('./component')
6
7
 
7
8
  class Remote extends Component {
@@ -12,6 +13,13 @@ class Remote extends Component {
12
13
  async dispose () {
13
14
  console.info(`Remote '${this.locator.id}' disconnected`)
14
15
  }
16
+
17
+ explain (endpoint) {
18
+ assert.ok(endpoint in this.operations,
19
+ `Endpoint '${endpoint}' is not provided by '${this.locator.id}'`)
20
+
21
+ return this.operations[endpoint].explain()
22
+ }
15
23
  }
16
24
 
17
25
  exports.Remote = Remote
@@ -18,13 +18,22 @@ class Transmission extends Connector {
18
18
  let i = 0
19
19
 
20
20
  while (reply === false && i < this.#bindings.length) {
21
- reply = await this.#bindings[i].request(request)
21
+ const binding = this.#bindings[i]
22
+
22
23
  i++
24
+
25
+ if (request?.task === true) {
26
+ if (binding.task === undefined)
27
+ continue
28
+
29
+ await binding.task(request)
30
+ reply = null
31
+ } else
32
+ reply = await binding.request(request)
23
33
  }
24
34
 
25
- if (reply === false) {
35
+ if (reply === false)
26
36
  throw new TransmissionException(`All (${this.#bindings.length}) bindings rejected.`)
27
- }
28
37
 
29
38
  return reply
30
39
  }
@@ -3,6 +3,7 @@
3
3
  const { Component } = require('../src/component')
4
4
  const { codes } = require('../src/exceptions')
5
5
  const fixtures = require('./component.fixtures')
6
+ const { AssertionError } = require('node:assert')
6
7
 
7
8
  describe('Invocations', () => {
8
9
  const name = ['foo', 'bar'][Math.floor(2 * Math.random())]
@@ -21,7 +22,7 @@ describe('Invocations', () => {
21
22
 
22
23
  it('should throw on unknown invocation name', async () => {
23
24
  await expect(() => component.invoke('baz'))
24
- .rejects.toMatchObject({ code: codes.NotImplemented })
25
+ .rejects.toThrow(AssertionError)
25
26
  })
26
27
 
27
28
  it('should invoke input and query', async () => {
@@ -2,19 +2,19 @@
2
2
 
3
3
  const { generate } = require('randomstring')
4
4
 
5
- const { Conditions } = require('../../src/contract/conditions')
5
+ const { Contract } = require('../../src/contract/contract')
6
6
  const fixtures = require('./contract.fixtures')
7
7
 
8
- let conditions
8
+ let contract
9
9
 
10
10
  beforeEach(() => {
11
- conditions = new Conditions(fixtures.schema)
11
+ contract = new Contract(fixtures.schema)
12
12
  })
13
13
 
14
14
  it('should fit value', () => {
15
15
  const value = { foo: generate() }
16
16
 
17
- conditions.fit(value)
17
+ contract.fit(value)
18
18
 
19
19
  expect(fixtures.schema.fit).toHaveBeenCalledWith(value)
20
20
  })
@@ -22,5 +22,5 @@ it('should fit value', () => {
22
22
  it('should throw on invalid value', () => {
23
23
  const value = { invalid: true }
24
24
 
25
- expect(() => conditions.fit(value)).toThrow()
25
+ expect(() => contract.fit(value)).toThrow()
26
26
  })
@@ -3,10 +3,10 @@
3
3
  const clone = require('clone-deep')
4
4
  const { generate } = require('randomstring')
5
5
 
6
- jest.mock('../../src/contract/conditions')
6
+ jest.mock('../../src/contract/contract')
7
7
 
8
8
  const { Request } = require('../../src/contract/request')
9
- const { Conditions } = require('../../src/contract/conditions')
9
+ const { Contract } = require('../../src/contract/contract')
10
10
  const fixtures = require('./contract.fixtures')
11
11
 
12
12
  let contract
@@ -14,14 +14,14 @@ let contract
14
14
  beforeEach(() => {
15
15
  jest.clearAllMocks()
16
16
 
17
- contract = new Request(fixtures.schema)
17
+ contract = new Request(fixtures.schema, {})
18
18
  })
19
19
 
20
20
  const dummy = { schema: { properties: {} } }
21
21
 
22
22
  it('should extend Conditions', () => {
23
- expect(contract).toBeInstanceOf(Conditions)
24
- expect(Conditions).toHaveBeenCalledWith(fixtures.schema)
23
+ expect(contract).toBeInstanceOf(Contract)
24
+ expect(Contract).toHaveBeenCalledWith(fixtures.schema)
25
25
  })
26
26
 
27
27
  it('should fit request', () => {
@@ -29,7 +29,7 @@ it('should fit request', () => {
29
29
 
30
30
  contract.fit(request)
31
31
 
32
- expect(Conditions.mock.instances[0].fit).toHaveBeenCalledWith(request)
32
+ expect(Contract.mock.instances[0].fit).toHaveBeenCalledWith(request)
33
33
  })
34
34
 
35
35
  describe('schema', () => {
@@ -59,7 +59,7 @@ describe('schema', () => {
59
59
 
60
60
  it('should not contain query if declaration.query is false', () => {
61
61
  schema.properties.query = { type: 'null' }
62
- expect(Request.schema({ query: false }, dummy)).toStrictEqual(schema)
62
+ expect(Request.schema({ query: false }, dummy)).toMatchObject(schema)
63
63
  })
64
64
 
65
65
  it('should require query if declaration.query is true', () => {
@@ -7,35 +7,7 @@ beforeEach(() => {
7
7
  jest.clearAllMocks()
8
8
  })
9
9
 
10
- describe('new', () => {
11
- it('should throw on schema error', () => {
12
- const entity = new Entity(fixtures.schema)
13
-
14
- expect(() => entity.set(fixtures.failed())).toThrow()
15
- })
16
-
17
- it('should provide state', () => {
18
- const entity = new Entity(fixtures.schema)
19
- const state = fixtures.state()
20
-
21
- entity.set(state)
22
-
23
- expect(entity.get()).toEqual(state)
24
- })
25
- })
26
-
27
10
  describe('argument', () => {
28
- it('should provide initial state if no argument passed', () => {
29
- const entity = new Entity(fixtures.schema)
30
- const defaults = fixtures.schema.defaults.mock.results[0].value
31
- const expected = {
32
- ...defaults,
33
- _version: 0
34
- }
35
-
36
- expect(entity.get()).toStrictEqual(expect.objectContaining(expected))
37
- })
38
-
39
11
  it('should set state', () => {
40
12
  const state = fixtures.state()
41
13
  const entity = new Entity(fixtures.schema, state)
@@ -1,26 +1,28 @@
1
1
  import * as _core from './index'
2
2
 
3
- declare namespace toa.core.bindings{
3
+ declare namespace toa.core.bindings {
4
4
 
5
5
  type Properties = {
6
6
  async?: boolean
7
7
  }
8
8
 
9
- interface Consumer extends _core.Connector{
9
+ interface Consumer extends _core.Connector {
10
10
  request (request: Request): Promise<_core.Reply>
11
+
12
+ task (request: Request): Promise<void>
11
13
  }
12
14
 
13
- interface Emitter extends _core.Connector{
15
+ interface Emitter extends _core.Connector {
14
16
  emit (message: _core.Message): Promise<void>
15
17
  }
16
18
 
17
- interface Broadcast<L> extends _core.Connector{
19
+ interface Broadcast<L> extends _core.Connector {
18
20
  transmit<T> (label: L, payload: T): Promise<void>
19
21
 
20
22
  receive<T> (label: L, callback: (payload: T) => void | Promise<void>): Promise<void>
21
23
  }
22
24
 
23
- interface Factory{
25
+ interface Factory {
24
26
  producer? (locator: _core.Locator, endpoints: Array<string>, producer: _core.Component): _core.Connector
25
27
 
26
28
  consumer? (locator: _core.Locator, endpoint: string): Consumer
@@ -1,9 +1,12 @@
1
1
  import { Connector } from './connector'
2
2
  import { Locator } from './locator'
3
3
  import { Request } from './request'
4
+ import { Operation } from './operations'
4
5
 
5
- export interface Component extends Connector{
6
+ export class Component extends Connector {
6
7
  locator: Locator
7
8
 
9
+ constructor (locator: Locator, operations: Record<string, Operation>)
10
+
8
11
  invoke<T = any> (endpoint: string, request: Request): Promise<T>
9
12
  }
package/types/index.ts CHANGED
@@ -5,6 +5,7 @@ export * as bridges from './bridges'
5
5
  export * as operations from './operations'
6
6
 
7
7
  export type { Component } from './component'
8
+ export type { Remote } from './remote'
8
9
  export { Connector } from './connector'
9
10
  export type { Context } from './context'
10
11
  export type { Exception } from './exception'
@@ -1,2 +1,8 @@
1
+ import { Request } from './request'
2
+
1
3
  export type type = 'transition' | 'observation' | 'assignment' | 'computation' | 'effect'
2
4
  export type scope = 'object' | 'objects' | 'changeset'
5
+
6
+ export class Operation {
7
+ invoke<T = any> (request: Request): Promise<T>
8
+ }
@@ -0,0 +1,11 @@
1
+ import { Component } from './component'
2
+
3
+ export class Remote extends Component {
4
+ explain: (endpoint: string) => Explanation
5
+ }
6
+
7
+ interface Explanation {
8
+ input: object | null
9
+ output: object | null
10
+ errors?: string[]
11
+ }
@@ -14,6 +14,7 @@ export interface Request {
14
14
  input?: any
15
15
  query?: Query
16
16
  authentic?: boolean
17
+ task?: boolean
17
18
  }
18
19
 
19
20
  export interface Reply {
@@ -1,21 +0,0 @@
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