@toa.io/core 0.1.0-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.
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
@@ -0,0 +1,33 @@
1
+ 'use strict'
2
+
3
+ const { Connector } = require('./connector')
4
+ const { TransmissionException } = require('./exceptions')
5
+
6
+ class Transmission extends Connector {
7
+ #bindings
8
+
9
+ constructor (bindings) {
10
+ super()
11
+
12
+ this.#bindings = bindings
13
+ this.depends(bindings)
14
+ }
15
+
16
+ async request (request) {
17
+ let reply = false
18
+ let i = 0
19
+
20
+ while (reply === false && i < this.#bindings.length) {
21
+ reply = await this.#bindings[i].request(request)
22
+ i++
23
+ }
24
+
25
+ if (reply === false) {
26
+ throw new TransmissionException(`All (${this.#bindings.length}) bindings rejected.`)
27
+ }
28
+
29
+ return reply
30
+ }
31
+ }
32
+
33
+ exports.Transmission = Transmission
@@ -0,0 +1,25 @@
1
+ 'use strict'
2
+
3
+ const { generate } = require('randomstring')
4
+
5
+ const transmission = {
6
+ request: jest.fn((request) => ({ [request.invalid ? 'exception' : generate()]: generate() }))
7
+ }
8
+
9
+ const contract = {
10
+ fit: jest.fn(() => null)
11
+ }
12
+
13
+ const request = () => ({
14
+ ok: {
15
+ input: { [generate()]: generate() },
16
+ query: { [generate()]: generate() }
17
+ },
18
+ bad: {
19
+ invalid: true
20
+ }
21
+ })
22
+
23
+ exports.transmission = transmission
24
+ exports.contract = contract
25
+ exports.request = request
@@ -0,0 +1,52 @@
1
+ 'use strict'
2
+
3
+ const { Call } = require('../src/call')
4
+ const fixtures = require('./call.fixtures')
5
+
6
+ jest.mock('../src/connector')
7
+
8
+ const { Connector } = require('../src/connector')
9
+
10
+ let call
11
+
12
+ beforeEach(() => {
13
+ jest.clearAllMocks()
14
+
15
+ call = new Call(fixtures.transmission, fixtures.contract)
16
+ })
17
+
18
+ it('should depend on transmission', () => {
19
+ expect(call).toBeInstanceOf(Connector)
20
+ expect(call).toBe(Connector.mock.instances[0])
21
+ expect(Connector.mock.instances[0].depends).toHaveBeenCalledWith(fixtures.transmission)
22
+ })
23
+
24
+ it('should call transmission', async () => {
25
+ const request = fixtures.request().ok
26
+
27
+ await call.invoke(request)
28
+
29
+ expect(fixtures.transmission.request).toHaveBeenCalledWith(request)
30
+ })
31
+
32
+ it('should fit request', async () => {
33
+ const request = fixtures.request().ok
34
+
35
+ await call.invoke(request)
36
+
37
+ expect(fixtures.contract.fit).toHaveBeenLastCalledWith(request)
38
+ })
39
+
40
+ it('should return reply', async () => {
41
+ const request = fixtures.request().ok
42
+
43
+ const reply = await call.invoke(request)
44
+
45
+ expect(reply).toStrictEqual(fixtures.transmission.request.mock.results[0].value)
46
+ })
47
+
48
+ it('should throw received exceptions', async () => {
49
+ const request = fixtures.request().bad
50
+
51
+ await expect(call.invoke(request)).rejects.toBeDefined()
52
+ })
@@ -0,0 +1,11 @@
1
+ 'use strict'
2
+
3
+ const bridges = ['a', 'b', 'c'].map((index) => ({
4
+ run: jest.fn((request) => {
5
+ if (request?.error === index) return { error: index }
6
+
7
+ return { output: { [index]: true } }
8
+ })
9
+ }))
10
+
11
+ exports.bridges = bridges
@@ -0,0 +1,42 @@
1
+ 'use strict'
2
+
3
+ jest.mock('../src/connector')
4
+
5
+ const clone = require('clone-deep')
6
+
7
+ const { Connector } = require('../src/connector')
8
+ const { Cascade } = require('../src/cascade')
9
+ const fixtures = require('./cascade.fixtures')
10
+
11
+ let cascade
12
+
13
+ beforeEach(() => {
14
+ jest.clearAllMocks()
15
+
16
+ cascade = new Cascade(clone(fixtures.bridges))
17
+ })
18
+
19
+ it('should depend on bridges', () => {
20
+ expect(cascade).toBeInstanceOf(Connector)
21
+ expect(Connector.mock.instances[0].depends).toHaveBeenCalledWith(fixtures.bridges)
22
+ })
23
+
24
+ it('should call bridges.run', async () => {
25
+ const args = [1, 2]
26
+ await cascade.run(...args)
27
+
28
+ for (const bridge of fixtures.bridges) expect(bridge.run).toHaveBeenCalledWith(...args)
29
+ })
30
+
31
+ it('should merge output', async () => {
32
+ const reply = await cascade.run()
33
+
34
+ expect(reply).toStrictEqual({ output: { a: true, b: true, c: true } })
35
+ })
36
+
37
+ it('should interrupt on error', async () => {
38
+ const reply = await cascade.run({ error: 'b' })
39
+
40
+ expect(reply).toStrictEqual({ error: 'b' })
41
+ expect(fixtures.bridges[2].run).not.toHaveBeenCalled()
42
+ })
@@ -0,0 +1,40 @@
1
+ 'use strict'
2
+
3
+ const { random, timeout } = require('@toa.io/gears')
4
+ const { Connector } = require('../src/connector')
5
+
6
+ class TestConnector extends Connector {
7
+ #label
8
+ #seq
9
+
10
+ constructor (label, seq) {
11
+ super()
12
+
13
+ this.#seq = seq
14
+ this.#label = label
15
+ }
16
+
17
+ async connection () {
18
+ await timeout(random(10))
19
+ this.#seq.push(`+${this.#label}`)
20
+ }
21
+
22
+ async disconnection () {
23
+ await timeout(random(10))
24
+ this.#seq.push(`-${this.#label}`)
25
+ }
26
+
27
+ disconnected () {
28
+ this.#seq.push(`*${this.#label}`)
29
+ }
30
+ }
31
+
32
+ class FailingConnector extends Connector {
33
+ async connection () {
34
+ await timeout(random(10))
35
+ throw new Error('FailingConnector')
36
+ }
37
+ }
38
+
39
+ exports.TestConnector = TestConnector
40
+ exports.FailingConnector = FailingConnector
@@ -0,0 +1,199 @@
1
+ 'use strict'
2
+
3
+ const fixtures = require('./connector.fixtures')
4
+
5
+ let sequence
6
+
7
+ beforeEach(() => {
8
+ sequence = []
9
+ })
10
+
11
+ describe('callbacks', () => {
12
+ let a
13
+
14
+ beforeEach(() => {
15
+ a = new fixtures.TestConnector('a', sequence)
16
+ })
17
+
18
+ it('should call connection', async () => {
19
+ await a.connect()
20
+ expect(sequence).toEqual(['+a'])
21
+ })
22
+
23
+ it('should call disconnection', async () => {
24
+ await a.connect()
25
+ await a.disconnect()
26
+
27
+ expect(sequence.indexOf('+a')).toBeLessThan(sequence.indexOf('-a'))
28
+ })
29
+
30
+ it('should reconnect', async () => {
31
+ await a.connect()
32
+ await a.disconnect()
33
+ await a.connect()
34
+ await a.disconnect()
35
+ await a.connect()
36
+
37
+ expect(sequence).toEqual(['+a', '-a', '*a', '+a', '-a', '*a', '+a'])
38
+ })
39
+ })
40
+
41
+ describe('dependencies', () => {
42
+ let a
43
+ let b
44
+ let c
45
+ let d
46
+
47
+ beforeEach(() => {
48
+ a = new fixtures.TestConnector('a', sequence)
49
+ b = new fixtures.TestConnector('b', sequence)
50
+ c = new fixtures.TestConnector('c', sequence)
51
+ d = new fixtures.TestConnector('d', sequence)
52
+ })
53
+
54
+ it('should wait dependencies on connection', async () => {
55
+ a.depends(b).depends(c)
56
+ a.depends(d)
57
+
58
+ await a.connect()
59
+
60
+ expect(sequence.indexOf('+c')).toBeLessThan(sequence.indexOf('+b'))
61
+ expect(sequence.indexOf('+b')).toBeLessThan(sequence.indexOf('+a'))
62
+ expect(sequence.indexOf('+d')).toBeLessThan(sequence.indexOf('+a'))
63
+ })
64
+
65
+ it('should wait array of connectors', async () => {
66
+ a.depends([b, d])
67
+ b.depends(c)
68
+ d.depends(c)
69
+
70
+ await a.connect()
71
+
72
+ expect(sequence.indexOf('+c')).toBeLessThan(sequence.indexOf('+b'))
73
+ expect(sequence.indexOf('+c')).toBeLessThan(sequence.indexOf('+d'))
74
+ expect(sequence.indexOf('+b')).toBeLessThan(sequence.indexOf('+a'))
75
+ expect(sequence.indexOf('+d')).toBeLessThan(sequence.indexOf('+a'))
76
+ })
77
+
78
+ it('should wait array(1) of connectors', async () => {
79
+ a.depends([b])
80
+ b.depends(c)
81
+
82
+ await a.connect()
83
+
84
+ expect(sequence.indexOf('+c')).toBeLessThan(sequence.indexOf('+b'))
85
+ expect(sequence.indexOf('+b')).toBeLessThan(sequence.indexOf('+a'))
86
+ })
87
+
88
+ it('should not throw on empty array', async () => {
89
+ a.depends([])
90
+
91
+ await a.connect()
92
+
93
+ expect(sequence).toStrictEqual(['+a'])
94
+ })
95
+
96
+ it('should await 2-way dependencies', async () => {
97
+ a.depends([b, c, d])
98
+ d.depends([b, c])
99
+
100
+ await a.connect()
101
+
102
+ expect(sequence.indexOf('+b')).toBeLessThan(sequence.indexOf('+a'))
103
+ expect(sequence.indexOf('+c')).toBeLessThan(sequence.indexOf('+a'))
104
+ expect(sequence.indexOf('+d')).toBeLessThan(sequence.indexOf('+a'))
105
+
106
+ expect(sequence.indexOf('+b')).toBeLessThan(sequence.indexOf('+d'))
107
+ expect(sequence.indexOf('+c')).toBeLessThan(sequence.indexOf('+d'))
108
+ })
109
+
110
+ it('should disconnect before dependencies', async () => {
111
+ a.depends(b).depends(c)
112
+ b.depends(d)
113
+
114
+ await a.disconnect()
115
+
116
+ expect(sequence.indexOf('-a')).toBeLessThan(sequence.indexOf('-b'))
117
+ expect(sequence.indexOf('-b')).toBeLessThan(sequence.indexOf('-c'))
118
+ expect(sequence.indexOf('-b')).toBeLessThan(sequence.indexOf('-d'))
119
+ })
120
+
121
+ it('should call disconnected', async () => {
122
+ a.depends(b).depends(c)
123
+ b.depends(d)
124
+
125
+ await a.disconnect()
126
+
127
+ expect(sequence.indexOf('*c')).toBeLessThan(sequence.indexOf('*b'))
128
+ expect(sequence.indexOf('*b')).toBeLessThan(sequence.indexOf('*a'))
129
+ expect(sequence.indexOf('*d')).toBeLessThan(sequence.indexOf('*b'))
130
+ expect(sequence.indexOf('*a')).toBe(sequence.length - 1)
131
+ })
132
+
133
+ it('should disconnect if no parents left', async () => {
134
+ a.depends(c)
135
+ b.depends(c)
136
+
137
+ await a.connect()
138
+ await b.connect()
139
+
140
+ expect(sequence).toEqual(['+c', '+a', '+b'])
141
+
142
+ await a.disconnect()
143
+
144
+ expect(sequence).toEqual(['+c', '+a', '+b', '-a', '*a'])
145
+
146
+ await b.disconnect()
147
+
148
+ expect(sequence).toEqual(['+c', '+a', '+b', '-a', '*a', '-b', '-c', '*c', '*b'])
149
+ })
150
+
151
+ it('should not throw if depends not on Connector', async () => {
152
+ a.depends({})
153
+
154
+ await expect((async () => {
155
+ await a.connect()
156
+ await a.disconnect()
157
+ })()).resolves.not.toThrow()
158
+
159
+ b.depends([undefined, c, {}])
160
+
161
+ await expect((async () => {
162
+ await b.connect()
163
+ await b.disconnect()
164
+ })()).resolves.not.toThrow()
165
+
166
+ expect(sequence).toStrictEqual([
167
+ '+a', '-a', '*a',
168
+ '+c', '+b', '-b', '-c', '*c', '*b'
169
+ ])
170
+ })
171
+
172
+ describe('errors', () => {
173
+ let f
174
+
175
+ beforeEach(() => {
176
+ f = new fixtures.FailingConnector()
177
+ })
178
+
179
+ it('should disconnect on fail', async () => {
180
+ f.depends(b).depends(c)
181
+
182
+ await expect(f.connect()).rejects.toThrow('FailingConnector')
183
+ expect(sequence).toEqual(['+c', '+b', '-b', '-c', '*c', '*b'])
184
+ })
185
+
186
+ it('should interrupt connection chain', async () => {
187
+ a.depends(f).depends(c)
188
+ f.depends(d)
189
+
190
+ await expect(a.connect()).rejects.toThrow('FailingConnector')
191
+
192
+ expect(sequence.indexOf('+c')).toBeLessThan(sequence.indexOf('-c'))
193
+ expect(sequence.indexOf('+d')).toBeLessThan(sequence.indexOf('-d'))
194
+
195
+ expect(sequence.indexOf('+a')).toBe(-1)
196
+ expect(sequence.indexOf('-a')).toBe(-1)
197
+ })
198
+ })
199
+ })
@@ -0,0 +1,26 @@
1
+ 'use strict'
2
+
3
+ const { generate } = require('randomstring')
4
+
5
+ const { Conditions } = require('../../src/contract/conditions')
6
+ const fixtures = require('./contract.fixtures')
7
+
8
+ let conditions
9
+
10
+ beforeEach(() => {
11
+ conditions = new Conditions(fixtures.schema)
12
+ })
13
+
14
+ it('should fit value', () => {
15
+ const value = { foo: generate() }
16
+
17
+ conditions.fit(value)
18
+
19
+ expect(fixtures.schema.fit).toHaveBeenCalledWith(value)
20
+ })
21
+
22
+ it('should throw on invalid value', () => {
23
+ const value = { invalid: true }
24
+
25
+ expect(() => conditions.fit(value)).toThrow()
26
+ })
@@ -0,0 +1,27 @@
1
+ 'use strict'
2
+
3
+ const { generate } = require('randomstring')
4
+ const { yaml } = require('@toa.io/gears')
5
+ const { resolve } = require('path')
6
+
7
+ const schema = {
8
+ fit: jest.fn((input) => (input.invalid ? { message: generate() } : null))
9
+ }
10
+
11
+ const query = {
12
+ parse: jest.fn(() => ({ [generate()]: generate() }))
13
+ }
14
+
15
+ const declaration = {}
16
+
17
+ const schemas = {
18
+ request: {
19
+ properties: { query: yaml.sync(resolve(__dirname, '../../src/contract/schemas/query.yaml')) },
20
+ additionalProperties: false
21
+ }
22
+ }
23
+
24
+ exports.schema = schema
25
+ exports.query = query
26
+ exports.declaration = declaration
27
+ exports.schemas = schemas
@@ -0,0 +1,99 @@
1
+ 'use strict'
2
+
3
+ const clone = require('clone-deep')
4
+ const { generate } = require('randomstring')
5
+
6
+ jest.mock('../../src/contract/conditions')
7
+
8
+ const { Request } = require('../../src/contract/request')
9
+ const { Conditions } = require('../../src/contract/conditions')
10
+ const fixtures = require('./contract.fixtures')
11
+
12
+ let contract
13
+
14
+ beforeEach(() => {
15
+ jest.clearAllMocks()
16
+
17
+ contract = new Request(fixtures.schema)
18
+ })
19
+
20
+ it('should extend Conditions', () => {
21
+ expect(contract).toBeInstanceOf(Conditions)
22
+ expect(Conditions).toHaveBeenCalledWith(fixtures.schema)
23
+ })
24
+
25
+ it('should fit request', () => {
26
+ const request = { [generate()]: generate() }
27
+
28
+ contract.fit(request)
29
+
30
+ expect(Conditions.mock.instances[0].fit).toHaveBeenCalledWith(request)
31
+ })
32
+
33
+ describe('schema', () => {
34
+ let schema
35
+
36
+ beforeEach(() => {
37
+ schema = clone(fixtures.schemas.request)
38
+ })
39
+
40
+ it('should provide schema', () => {
41
+ expect(Request.schema({})).toBeDefined()
42
+ })
43
+
44
+ it('should add required input', () => {
45
+ const input = { type: 'number' }
46
+
47
+ expect(Request.schema({ input }).properties.input).toStrictEqual(input)
48
+ })
49
+
50
+ it('should contain query if declaration.query is not defined', () => {
51
+ expect(Request.schema({}).properties.query).toBeDefined()
52
+ })
53
+
54
+ it('should not contain query if declaration.query is false', () => {
55
+ delete schema.properties.query
56
+ expect(Request.schema({ query: false })).toStrictEqual(schema)
57
+ })
58
+
59
+ it('should require query if declaration.query is true', () => {
60
+ schema.required = ['query']
61
+ expect(Request.schema({ query: true }).required).toStrictEqual(expect.arrayContaining(['query']))
62
+ })
63
+
64
+ it('should forbid projection for non observations', () => {
65
+ expect(Request.schema({ type: 'transition' }).properties.query.properties.projection)
66
+ .toBe(undefined)
67
+
68
+ expect(Request.schema({ type: 'assignment' }).properties.query.properties.projection)
69
+ .toBe(undefined)
70
+
71
+ expect(Request.schema({ type: 'observation' }).properties.query.properties.projection)
72
+ .toBeDefined()
73
+ })
74
+
75
+ it('should forbid version for observations', () => {
76
+ expect(Request.schema({ type: 'transition' }).properties.query.properties.version)
77
+ .toBeDefined()
78
+
79
+ expect(Request.schema({ type: 'observation' }).properties.query.properties.version)
80
+ .toBe(undefined)
81
+ })
82
+
83
+ it('should allow omit, limit only for set observations', () => {
84
+ const transition = Request.schema({ type: 'transition' }).properties.query.properties
85
+
86
+ expect(transition.omit).toBeUndefined()
87
+ expect(transition.limit).toBeUndefined()
88
+
89
+ const entity = Request.schema({ type: 'observation', subject: 'entity' }).properties.query.properties
90
+
91
+ expect(entity.omit).toBeUndefined()
92
+ expect(entity.limit).toBeUndefined()
93
+
94
+ const set = Request.schema({ type: 'observation', subject: 'set' }).properties.query.properties
95
+
96
+ expect(set.omit).toBeDefined()
97
+ expect(set.limit).toBeDefined()
98
+ })
99
+ })
@@ -0,0 +1,16 @@
1
+ 'use strict'
2
+
3
+ const { generate } = require('randomstring')
4
+
5
+ const events = [0, 1, 2].map((index) => ({
6
+ emit: jest.fn(async (state) => ({ ...state, event: index }))
7
+ }))
8
+
9
+ const event = {
10
+ origin: { [generate()]: generate() },
11
+ state: { [generate()]: generate() },
12
+ changeset: { [generate()]: generate() }
13
+ }
14
+
15
+ exports.events = events
16
+ exports.event = event
@@ -0,0 +1,35 @@
1
+ 'use strict'
2
+
3
+ jest.mock('../src/connector')
4
+
5
+ const clone = require('clone-deep')
6
+
7
+ const { Connector } = require('../src/connector')
8
+ const { Emission } = require('../src/emission')
9
+ const fixtures = require('./emission.fixtures')
10
+
11
+ let emission, event
12
+
13
+ beforeEach(async () => {
14
+ jest.clearAllMocks()
15
+
16
+ emission = new Emission(fixtures.events)
17
+ event = clone(fixtures.event)
18
+
19
+ await emission.connection()
20
+ })
21
+
22
+ it('should depend on events', () => {
23
+ expect(emission).toBeInstanceOf(Connector)
24
+ expect(Connector.mock.instances[0].depends).toHaveBeenCalledWith(fixtures.events)
25
+ })
26
+
27
+ it('should emit events', async () => {
28
+ expect.assertions(fixtures.events.length)
29
+
30
+ await emission.emit(event)
31
+
32
+ for (const evt of fixtures.events) {
33
+ expect(evt.emit).toHaveBeenCalledWith(event)
34
+ }
35
+ })
@@ -0,0 +1,26 @@
1
+ 'use strict'
2
+
3
+ const { generate } = require('randomstring')
4
+ const clone = require('clone-deep')
5
+
6
+ const schema = {
7
+ fit: jest.fn((object) =>
8
+ (object.fail ? { [generate()]: generate() } : undefined)),
9
+
10
+ defaults: jest.fn(() => ({ [generate()]: generate() }))
11
+ }
12
+
13
+ const state = () => clone({
14
+ id: generate(),
15
+ foo: generate(),
16
+ _created: generate(),
17
+ _updated: generate(),
18
+ _deleted: generate(),
19
+ _version: generate()
20
+ })
21
+
22
+ const failed = () => clone({ ...state(), fail: true })
23
+
24
+ exports.schema = schema
25
+ exports.state = state
26
+ exports.failed = failed