@toa.io/bridges.node 0.2.0-dev.0 → 0.2.1-dev.3

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 (42) hide show
  1. package/package.json +5 -3
  2. package/readme.md +103 -0
  3. package/src/algorithms/class.js +6 -0
  4. package/src/algorithms/factory.js +10 -0
  5. package/src/algorithms/function.js +10 -0
  6. package/src/algorithms/runner.js +50 -0
  7. package/src/context.js +68 -3
  8. package/src/define/.operations/define.js +16 -0
  9. package/src/define/.operations/extract.js +48 -0
  10. package/src/define/.operations/index.js +7 -0
  11. package/src/define/.operations/syntaxes/class.js +42 -0
  12. package/src/define/.operations/syntaxes/constants.js +4 -0
  13. package/src/define/.operations/syntaxes/factory.js +27 -0
  14. package/src/define/.operations/syntaxes/function.js +41 -0
  15. package/src/define/.operations/syntaxes/index.js +5 -0
  16. package/src/define/index.js +3 -6
  17. package/src/define/operations.js +11 -2
  18. package/src/event.js +8 -1
  19. package/src/factory.js +24 -4
  20. package/src/index.js +0 -1
  21. package/src/load.js +1 -1
  22. package/src/receiver.js +8 -1
  23. package/test/algorithms.runner.test.js +40 -0
  24. package/test/context.configuration.fixtures.js +21 -0
  25. package/test/context.configuration.test.js +23 -0
  26. package/test/context.origins.fixtures.js +16 -0
  27. package/test/context.origins.test.js +53 -0
  28. package/test/define.algorithms.test.js +36 -0
  29. package/test/define.operations.define.test.js +195 -0
  30. package/test/dummies/one/operations/cls.js +18 -0
  31. package/test/dummies/one/operations/fct.js +20 -0
  32. package/test/dummies/one/operations/fn.js +7 -0
  33. package/test/factory.algorithm.test.js +43 -0
  34. package/types/algorithms.d.ts +15 -0
  35. package/types/context.d.ts +21 -0
  36. package/types/define.d.ts +30 -0
  37. package/LICENSE +0 -22
  38. package/src/define/operations/definition.js +0 -40
  39. package/src/operation.js +0 -23
  40. package/test/operation.fixtures.js +0 -9
  41. package/test/operation.test.js +0 -39
  42. package/test/operations.definition.test.js +0 -40
@@ -0,0 +1,40 @@
1
+ 'use strict'
2
+
3
+ const { generate } = require('randomstring')
4
+ const { Connector } = require('@toa.io/core')
5
+
6
+ const { Runner } = require('../src/algorithms/runner')
7
+
8
+ it('should be', () => {
9
+ expect(Runner).toBeDefined()
10
+ })
11
+
12
+ const context = /** @type {toa.node.Context} */ new Connector()
13
+
14
+ it('should return output', async () => {
15
+ const values = [{ [generate()]: generate() }, generate()]
16
+
17
+ for (const value of values) {
18
+ const run = () => value
19
+ const ctor = () => /** @type {toa.core.bridges.Algorithm} */ ({ run })
20
+ const runner = new Runner(ctor, context)
21
+
22
+ await runner.connect()
23
+
24
+ const reply = await runner.run()
25
+
26
+ expect(reply.output).toStrictEqual(value)
27
+ }
28
+ })
29
+
30
+ it('should not return undefined output', async () => {
31
+ const run = () => undefined
32
+ const ctor = () => ({ run })
33
+ const runner = new Runner(ctor, context)
34
+
35
+ await runner.connect()
36
+
37
+ const reply = await runner.run()
38
+
39
+ expect(reply).toStrictEqual(undefined)
40
+ })
@@ -0,0 +1,21 @@
1
+ 'use strict'
2
+
3
+ const { generate } = require('randomstring')
4
+
5
+ const configuration = { foo: { bar: generate() } }
6
+
7
+ const context = /** @type {toa.core.Context} */ {
8
+ apply: jest.fn(),
9
+ call: jest.fn(),
10
+ aspects: [
11
+ {
12
+ name: 'configuration',
13
+ invoke: jest.fn(() => configuration)
14
+ }
15
+ ],
16
+ link: jest.fn(),
17
+ connect: jest.fn()
18
+ }
19
+
20
+ exports.context = context
21
+ exports.configuration = configuration
@@ -0,0 +1,23 @@
1
+ 'use strict'
2
+
3
+ const fixtures = require('./context.configuration.fixtures')
4
+ const { Context } = require('../src/context')
5
+
6
+ let context
7
+
8
+ beforeEach(async () => {
9
+ jest.clearAllMocks()
10
+
11
+ context = new Context(fixtures.context)
12
+
13
+ await context.connect()
14
+ })
15
+
16
+ it('should expose aspect', async () => {
17
+ expect(context.aspects.configuration).toBeDefined()
18
+ })
19
+
20
+ it('should expose values', () => {
21
+ expect(context.configuration).toStrictEqual(fixtures.configuration)
22
+ expect(context.configuration.foo).toStrictEqual(fixtures.configuration.foo)
23
+ })
@@ -0,0 +1,16 @@
1
+ 'use strict'
2
+
3
+ const context = /** @type {toa.core.Context} */ {
4
+ apply: jest.fn(),
5
+ call: jest.fn(),
6
+ aspects: [
7
+ {
8
+ name: 'origins',
9
+ invoke: jest.fn()
10
+ }
11
+ ],
12
+ link: jest.fn(),
13
+ connect: jest.fn()
14
+ }
15
+
16
+ exports.context = context
@@ -0,0 +1,53 @@
1
+ 'use strict'
2
+
3
+ const { generate } = require('randomstring')
4
+
5
+ const fixtures = require('./context.origins.fixtures')
6
+ const { Context } = require('../src/context')
7
+
8
+ const origins = fixtures.context.aspects[0]
9
+
10
+ let context
11
+
12
+ beforeEach(async () => {
13
+ jest.clearAllMocks()
14
+
15
+ context = new Context(fixtures.context)
16
+
17
+ await context.connect()
18
+ })
19
+
20
+ it('should expose aspect', async () => {
21
+ expect(context.aspects.origins).toBeDefined()
22
+ })
23
+
24
+ it('should invoke', async () => {
25
+ const name = generate()
26
+ const arg = { [generate()]: generate() }
27
+
28
+ await context.origins[name].baz.quu.get(arg)
29
+
30
+ expect(origins.invoke).toHaveBeenCalled()
31
+ expect(origins.invoke).toHaveBeenCalledWith(name, 'baz/quu', expect.objectContaining(arg), undefined)
32
+ })
33
+
34
+ it('should define request method', async () => {
35
+ const arg = { [generate()]: generate() }
36
+
37
+ await context.origins.foo.post(arg)
38
+ await context.origins.bar.baz.put()
39
+
40
+ expect(origins.invoke).toHaveBeenNthCalledWith(1, 'foo', '', { method: 'POST', ...arg }, undefined)
41
+ expect(origins.invoke).toHaveBeenNthCalledWith(2, 'bar', 'baz', { method: 'PUT' }, undefined)
42
+ })
43
+
44
+ it('should throw if no origin name specified', async () => {
45
+ await expect(context.origins.get()).rejects.toThrow(/at least 2 arguments/)
46
+ })
47
+
48
+ it('should pass options', async () => {
49
+ const options = { [generate()]: generate() }
50
+ await context.origins.foo.post({}, options)
51
+
52
+ expect(origins.invoke.mock.calls[0][3]).toStrictEqual(options)
53
+ })
@@ -0,0 +1,36 @@
1
+ 'use strict'
2
+
3
+ const { resolve } = require('node:path')
4
+ const define = require('../src/define')
5
+
6
+ it('should be', () => {
7
+ expect(define.operations).toBeDefined()
8
+ })
9
+
10
+ const DUMMIES = resolve(__dirname, 'dummies')
11
+ const find = (component) => resolve(DUMMIES, component)
12
+
13
+ const root = find('one')
14
+
15
+ /** @type {toa.node.define.algorithms.List} */
16
+ let operations
17
+
18
+ beforeAll(async () => {
19
+ operations = await define.operations(root)
20
+ })
21
+
22
+ it('should be', () => {
23
+ expect(operations).toBeDefined()
24
+ })
25
+
26
+ it('should find function operations', () => {
27
+ expect(operations.fn).toBeDefined()
28
+ })
29
+
30
+ it('should find class operations', () => {
31
+ expect(operations.cls).toBeDefined()
32
+ })
33
+
34
+ it('should find factory operations', () => {
35
+ expect(operations.fct).toBeDefined()
36
+ })
@@ -0,0 +1,195 @@
1
+ // noinspection JSUnusedLocalSymbols,JSUnusedGlobalSymbols
2
+
3
+ 'use strict'
4
+
5
+ const { define } = require('../src/define/.operations')
6
+
7
+ it('should be', () => {
8
+ expect(define).toBeDefined()
9
+ })
10
+
11
+ /** @type {toa.node.define.operations.Definition} */
12
+ let definition
13
+
14
+ it('should throw if function does not match conventions', () => {
15
+ const append = () => null
16
+ const module = { append }
17
+
18
+ expect(() => define(module)).toThrow('does not match conventions')
19
+ })
20
+
21
+ it('should throw if class does not match conventions', () => {
22
+ class Foo {}
23
+
24
+ const module = { Foo }
25
+
26
+ expect(() => define(module)).toThrow('does not match conventions')
27
+ })
28
+
29
+ it('should throw if no function exported', () => {
30
+ const foo = 'bar'
31
+ const module = { foo }
32
+
33
+ expect(() => define(module)).toThrow('Module does not export function')
34
+ })
35
+
36
+ describe('function', () => {
37
+ it('should parse declaration', () => {
38
+ function transition (input, object) {}
39
+
40
+ const module = { transition }
41
+ const definition = define(module)
42
+
43
+ expect(definition).toMatchObject({ type: 'transition', scope: 'object' })
44
+ })
45
+
46
+ it('should parse expression', () => {
47
+ const observation = (input, objects) => null
48
+ const module = { observation }
49
+ const definition = define(module)
50
+
51
+ expect(definition).toMatchObject({ type: 'observation', scope: 'objects' })
52
+ })
53
+
54
+ it('should parse scope changeset', () => {
55
+ const assignment = (input, changeset) => null
56
+ const module = { assignment }
57
+ const definition = define(module)
58
+
59
+ expect(definition.scope).toStrictEqual('changeset')
60
+ })
61
+
62
+ it('should not define unknown scope', () => {
63
+ const assignment = (input, message) => null
64
+ const module = { assignment }
65
+ const definition = define(module)
66
+
67
+ expect(definition.scope).toStrictEqual(undefined)
68
+ })
69
+
70
+ it('should define none scope', async () => {
71
+ const observation = (input) => null
72
+ const module = { observation }
73
+ const definition = define(module)
74
+
75
+ expect(definition.scope).toStrictEqual('none')
76
+ })
77
+
78
+ it('should define none scope for _', async () => {
79
+ const observation = (input, none, context) => null
80
+ const module = { observation }
81
+ const definition = define(module)
82
+
83
+ expect(definition.scope).toStrictEqual('none')
84
+ })
85
+
86
+ it('should define null input', async () => {
87
+ const observation = () => null
88
+ const module = { observation }
89
+ const definition = define(module)
90
+
91
+ expect(definition.input).toStrictEqual(null)
92
+ })
93
+ })
94
+
95
+ describe('class', () => {
96
+ class Transition {
97
+ run (input, object) {}
98
+ }
99
+
100
+ const module = { Transition }
101
+
102
+ beforeAll(() => {
103
+ definition = define(module)
104
+ })
105
+
106
+ it('should define type', () => {
107
+ expect(definition.type).toStrictEqual('transition')
108
+ })
109
+
110
+ it('should define scope', () => {
111
+ expect(definition.scope).toStrictEqual('object')
112
+ })
113
+
114
+ it('should find run method', () => {
115
+ class Assignment {
116
+ execute (input, objects) {}
117
+
118
+ run (input, object) {}
119
+ }
120
+
121
+ const module = { Assignment }
122
+ const definition = define(module)
123
+
124
+ expect(definition).toMatchObject({ type: 'assignment', scope: 'object' })
125
+ })
126
+
127
+ it('should throw if no run method found', () => {
128
+ class Observation {}
129
+
130
+ const module = { Observation }
131
+
132
+ expect(() => define(module)).toThrow('Method \'run\' not found')
133
+ })
134
+
135
+ it('should throw if function is not a class', () => {
136
+ function Transition () {}
137
+
138
+ const module = { Transition }
139
+
140
+ expect(() => define(module)).toThrow('does not match conventions')
141
+ })
142
+
143
+ it('should define none scope', async () => {
144
+ class Observation {
145
+ run (input) {}
146
+ }
147
+
148
+ const module = { Observation }
149
+ const definition = define(module)
150
+
151
+ expect(definition.scope).toStrictEqual('none')
152
+ })
153
+
154
+ it('should define null input', async () => {
155
+ class Observation {
156
+ run () {}
157
+ }
158
+
159
+ const module = { Observation }
160
+ const definition = define(module)
161
+
162
+ expect(definition.input).toStrictEqual(null)
163
+ })
164
+ })
165
+
166
+ describe('factory', () => {
167
+ class ObjectTransitionFactory {
168
+ create () {}
169
+ }
170
+
171
+ const module = { ObjectTransitionFactory }
172
+
173
+ beforeAll(() => {
174
+ definition = define(module)
175
+ })
176
+
177
+ it('should define type', () => {
178
+ expect(definition.type).toStrictEqual('transition')
179
+ })
180
+
181
+ it('should define scope', () => {
182
+ expect(definition.scope).toStrictEqual('object')
183
+ })
184
+
185
+ it('should define none scope', async () => {
186
+ class NoneObservationFactory {
187
+ create () {}
188
+ }
189
+
190
+ const module = { NoneObservationFactory }
191
+ const definition = define(module)
192
+
193
+ expect(definition.scope).toStrictEqual('none')
194
+ })
195
+ })
@@ -0,0 +1,18 @@
1
+ 'use strict'
2
+
3
+ /**
4
+ * @implements {toa.core.bridges.Algorithm}
5
+ */
6
+ class Transition {
7
+ #context
8
+
9
+ constructor (context) {
10
+ this.#context = context
11
+ }
12
+
13
+ async run (input, object) {
14
+ return { output: { input, state: object, context: this.#context !== undefined } }
15
+ }
16
+ }
17
+
18
+ exports.Transition = Transition
@@ -0,0 +1,20 @@
1
+ 'use strict'
2
+
3
+ const { Transition } = require('./cls')
4
+
5
+ /**
6
+ * @implements {toa.node.algorithms.Factory}
7
+ */
8
+ class ObjectTransitionFactory {
9
+ #context
10
+
11
+ constructor (context) {
12
+ this.#context = context
13
+ }
14
+
15
+ create () {
16
+ return new Transition(this.#context)
17
+ }
18
+ }
19
+
20
+ exports.ObjectTransitionFactory = ObjectTransitionFactory
@@ -0,0 +1,7 @@
1
+ 'use strict'
2
+
3
+ async function transition (input, object, context) {
4
+ return { output: { input, state: object, context: context !== undefined } }
5
+ }
6
+
7
+ exports.transition = transition
@@ -0,0 +1,43 @@
1
+ 'use strict'
2
+
3
+ const { resolve } = require('node:path')
4
+ const { generate } = require('randomstring')
5
+ const { Connector } = require('@toa.io/core')
6
+
7
+ const { Factory } = require('../src/factory')
8
+
9
+ const root = resolve(__dirname, 'dummies/one')
10
+
11
+ let factory
12
+
13
+ const context = new Connector()
14
+ const input = generate()
15
+ const state = generate()
16
+
17
+ context.aspects = []
18
+
19
+ beforeAll(() => {
20
+ factory = new Factory()
21
+ })
22
+
23
+ it('should be', () => {
24
+ expect(factory.algorithm).toBeDefined()
25
+ })
26
+
27
+ for (const sample of ['fn', 'cls', 'fct']) {
28
+ it(`should create '${sample}' operation`, async () => {
29
+ const algorithm = factory.algorithm(root, sample, context)
30
+
31
+ expect(algorithm).toBeDefined()
32
+
33
+ await algorithm.connect()
34
+
35
+ const promise = algorithm.run(input, state)
36
+
37
+ await expect(promise).resolves.not.toThrow()
38
+
39
+ const response = await promise
40
+
41
+ expect(response.output).toStrictEqual({ input, state, context: true })
42
+ })
43
+ }
@@ -0,0 +1,15 @@
1
+ import { bridges } from '@toa.io/core/types'
2
+
3
+ declare namespace toa.node {
4
+
5
+ namespace algorithms {
6
+
7
+ type Constructor = () => bridges.Algorithm
8
+
9
+ interface Factory {
10
+ create: Constructor
11
+ }
12
+
13
+ }
14
+
15
+ }
@@ -0,0 +1,21 @@
1
+ import { Underlay } from '@toa.io/generic/types'
2
+ import { Connector } from "@toa.io/core/types";
3
+
4
+ declare namespace toa.node {
5
+
6
+ interface Aspectes {
7
+ [key: string]: Function
8
+ }
9
+
10
+ interface Context extends Connector {
11
+ local: Underlay
12
+ remote: Underlay
13
+ aspects: Aspectes
14
+
15
+ // known extensions
16
+ origins?: Underlay
17
+ }
18
+
19
+ }
20
+
21
+ export type Context = toa.node.Context
@@ -0,0 +1,30 @@
1
+ import { Operation } from '@toa.io/norm/types'
2
+ import { Node, Statement } from '@babel/types'
3
+ import { bridges } from '@toa.io/core/types'
4
+ import * as context from './context'
5
+
6
+ declare namespace toa.node.define {
7
+
8
+ namespace algorithms {
9
+ type Definition = Partial<Operation>
10
+
11
+ type List = Record<string, Definition>
12
+
13
+ type Syntax = 'function' | 'class' | 'factory'
14
+
15
+ type Descriptor = {
16
+ name: string
17
+ statement: Statement
18
+ syntax: Syntax
19
+ }
20
+
21
+ type Define = (descriptor: Descriptor) => Definition
22
+
23
+ type Test = (node: Node, type: string) => boolean
24
+
25
+ type Constructor = (func: Function, context: context.Context) => bridges.Algorithm
26
+ }
27
+
28
+ type Algorithms = (root: string) => Promise<algorithms.List>
29
+
30
+ }
package/LICENSE DELETED
@@ -1,22 +0,0 @@
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.
@@ -1,40 +0,0 @@
1
- 'use strict'
2
-
3
- const parser = require('@babel/parser')
4
- const { merge } = require('@toa.io/gears')
5
-
6
- const definition = (module) => {
7
- const definition = {}
8
-
9
- if (typeof module.transition === 'function') definition.type = 'transition'
10
- if (typeof module.observation === 'function') definition.type = 'observation'
11
- if (typeof module.assignment === 'function') definition.type = 'assignment'
12
-
13
- if (definition.type === undefined) {
14
- throw new Error('Operation must export either transition, observation or assignment function')
15
- }
16
-
17
- const func = module[definition.type]
18
- const meta = parse(func)
19
-
20
- return merge(definition, meta)
21
- }
22
-
23
- const parse = (func) => {
24
- const ast = parser.parse(func.toString())
25
-
26
- return node(ast.program.body[0])
27
- }
28
-
29
- function node (node) {
30
- if (node.type === 'ExpressionStatement') node = node.expression
31
- if (node.async !== true) { throw new Error('Operation must export async function') }
32
-
33
- const result = {}
34
-
35
- if (node.params.length > 1) result.subject = node.params[1]?.name
36
-
37
- return result
38
- }
39
-
40
- exports.definition = definition
package/src/operation.js DELETED
@@ -1,23 +0,0 @@
1
- 'use strict'
2
-
3
- const { Connector } = require('@toa.io/core')
4
-
5
- class Operation extends Connector {
6
- #operation
7
- #context
8
-
9
- constructor (operation, context) {
10
- super()
11
-
12
- this.#operation = operation.transition || operation.observation || operation.assignment
13
- this.#context = context
14
-
15
- this.depends(context)
16
- }
17
-
18
- async run (input, state) {
19
- return this.#operation(input, state, this.#context)
20
- }
21
- }
22
-
23
- exports.Operation = Operation
@@ -1,9 +0,0 @@
1
- 'use strict'
2
-
3
- const { generate } = require('randomstring')
4
-
5
- const operation = { observation: jest.fn(async () => generate()) }
6
- const context = { [generate()]: generate() }
7
-
8
- exports.operation = operation
9
- exports.context = context
@@ -1,39 +0,0 @@
1
- 'use strict'
2
-
3
- const { Connector } = require('@toa.io/core')
4
-
5
- const { Operation } = require('../src/operation')
6
- const fixtures = require('./operation.fixtures')
7
-
8
- let operation
9
-
10
- beforeEach(() => {
11
- operation = new Operation(fixtures.operation, fixtures.context)
12
- })
13
-
14
- it('should extend Connector', () => {
15
- expect(Operation.prototype).toBeInstanceOf(Connector)
16
- })
17
-
18
- it('should run algorithm', async () => {
19
- await operation.run({ input: 1 })
20
-
21
- expect(fixtures.operation.observation).toHaveBeenCalled()
22
- })
23
-
24
- it('should pass input, state, context', async () => {
25
- const input = { a: 1 }
26
- const state = { b: 2 }
27
-
28
- await operation.run(input, state)
29
-
30
- expect(fixtures.operation.observation).toHaveBeenCalledWith(input, state, fixtures.context)
31
- })
32
-
33
- it('should return output', async () => {
34
- const reply = await operation.run()
35
- const result = await fixtures.operation.observation.mock.results[0].value
36
-
37
- expect(reply).toBeDefined()
38
- expect(reply).toStrictEqual(result)
39
- })