@toa.io/norm 0.10.0 → 0.20.0-alpha.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@toa.io/norm",
3
- "version": "0.10.0",
3
+ "version": "0.20.0-alpha.0",
4
4
  "description": "Toa declarations normalization and validation",
5
5
  "author": "temich <tema.gurtovoy@gmail.com>",
6
6
  "homepage": "https://github.com/toa-io/toa#readme",
@@ -12,21 +12,19 @@
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
16
  "publishConfig": {
16
17
  "access": "public"
17
18
  },
18
19
  "scripts": {
19
20
  "test": "echo \"Error: run tests from root\" && exit 1"
20
21
  },
21
- "devDependencies": {
22
- "@toa.io/mock": "0.7.6"
23
- },
24
22
  "dependencies": {
25
- "@toa.io/core": "0.8.1",
26
- "@toa.io/generic": "0.9.0",
27
- "@toa.io/schema": "0.7.3",
28
- "@toa.io/schemas": "0.8.1",
29
- "@toa.io/yaml": "0.7.3"
23
+ "@toa.io/core": "0.20.0-alpha.0",
24
+ "@toa.io/generic": "0.20.0-alpha.0",
25
+ "@toa.io/schema": "0.20.0-alpha.0",
26
+ "@toa.io/schemas": "0.20.0-alpha.0",
27
+ "@toa.io/yaml": "0.20.0-alpha.0"
30
28
  },
31
- "gitHead": "a5019af0ad7b08182d0f245af3382f96a7e22d71"
29
+ "gitHead": "d047190899218b5249901a01a2a4caec5b34cf09"
32
30
  }
@@ -10,7 +10,9 @@ function extensions (manifest) {
10
10
  const SHORTCUTS = {
11
11
  exposition: '@toa.io/extensions.exposition',
12
12
  origins: '@toa.io/extensions.origins',
13
- configuration: '@toa.io/extensions.configuration'
13
+ configuration: '@toa.io/extensions.configuration',
14
+ state: '@toa.io/extensions.state',
15
+ stash: '@toa.io/extensions.stash'
14
16
  }
15
17
 
16
18
  exports.extensions = extensions
@@ -6,7 +6,7 @@ function receivers (manifest) {
6
6
  if (manifest.receivers === undefined) return
7
7
 
8
8
  for (const [locator, receiver] of Object.entries(manifest.receivers)) {
9
- if (typeof receiver === 'string') manifest.receivers[locator] = { transition: receiver }
9
+ if (typeof receiver === 'string') manifest.receivers[locator] = { operation: receiver }
10
10
 
11
11
  if (receiver.binding !== undefined) receiver.binding = resolve(receiver.binding)
12
12
  if (receiver.bridge !== undefined) receiver.bridge = resolve(receiver.bridge)
@@ -7,8 +7,10 @@ function receivers (component) {
7
7
 
8
8
  for (const [key, value] of Object.entries(receivers)) {
9
9
  const segments = key.split('.')
10
+ const source = value.source ?? 'default'
10
11
 
11
- if (segments.length === 3) continue
12
+ if (source !== 'default') continue
13
+ if (segments.length === 3) continue // already with a namespace
12
14
 
13
15
  const newKey = 'default.' + key
14
16
 
@@ -36,9 +36,9 @@ const collapse = (manifest, prototype) => {
36
36
 
37
37
  delete prototype.entity?.storage // ???
38
38
 
39
- const { entity, events } = prototype
39
+ const { entity, events, extensions } = prototype
40
40
 
41
- merge(manifest, { entity, events })
41
+ merge(manifest, { entity, events, extensions })
42
42
  }
43
43
 
44
44
  exports.collapse = collapse
@@ -1,19 +1,13 @@
1
1
  'use strict'
2
2
 
3
+ const { hash } = require('@toa.io/generic')
4
+
3
5
  // these defaults are required before validation
4
6
  const defaults = (manifest) => {
5
7
  if (manifest.prototype === undefined) manifest.prototype = '@toa.io/prototype'
6
-
7
- if (manifest.bindings === undefined) {
8
- const local = process.env.TOA_DEV === '1'
9
-
10
- manifest.bindings = local
11
- ? ['@toa.io/bindings.amqp']
12
- : ['@toa.io/bindings.http', '@toa.io/bindings.amqp']
13
- }
14
-
8
+ if (manifest.name === undefined) manifest.name = protoName(manifest)
9
+ if (manifest.bindings === undefined) manifest.bindings = ['@toa.io/bindings.amqp']
15
10
  if (manifest.bridge === undefined) manifest.bridge = '@toa.io/bridges.node'
16
-
17
11
  if (manifest.entity === null || manifest.entity === undefined) manifest.entity = { storage: null }
18
12
  if (manifest.entity.storage === undefined) manifest.entity.storage = '@toa.io/storages.mongodb'
19
13
  if (manifest.entity.storage === null) manifest.entity.storage = '@toa.io/storages.null'
@@ -22,4 +16,8 @@ const defaults = (manifest) => {
22
16
  if (manifest.version === undefined) manifest.version = '0.0.0'
23
17
  }
24
18
 
19
+ function protoName (manifest) {
20
+ return 'proto' + hash(manifest.path)
21
+ }
22
+
25
23
  exports.defaults = defaults
@@ -1,10 +1,8 @@
1
1
  'use strict'
2
2
 
3
- const { convolve } = require('@toa.io/generic')
4
3
  const { events, operations, extensions, receivers } = require('./.normalize')
5
4
 
6
5
  const normalize = (component, environment) => {
7
- convolve(component, environment)
8
6
  operations(component)
9
7
  events(component)
10
8
  receivers(component)
@@ -20,7 +20,7 @@ properties:
20
20
  operations:
21
21
  type: object
22
22
  propertyNames:
23
- $ref: 'definitions#/definitions/token'
23
+ $ref: 'definitions#/definitions/name'
24
24
  patternProperties:
25
25
  '.*':
26
26
  type: object
@@ -39,14 +39,15 @@ properties:
39
39
  label:
40
40
  $ref: 'definitions#/definitions/label'
41
41
 
42
+ name:
43
+ $ref: 'definitions#/definitions/token'
44
+
42
45
  namespace:
43
46
  $ref: 'definitions#/definitions/token'
44
47
  default: 'default'
45
48
  not:
46
49
  oneOf:
47
50
  - const: 'system'
48
- name:
49
- $ref: 'definitions#/definitions/token'
50
51
 
51
52
  version:
52
53
  $ref: 'definitions#/definitions/version'
@@ -67,12 +68,12 @@ properties:
67
68
  type: object
68
69
  propertyNames:
69
70
  oneOf:
70
- - $ref: 'definitions#/definitions/token'
71
- - enum: [ _version ]
71
+ - $ref: 'definitions#/definitions/name'
72
+ - enum: [_version]
72
73
  initialized:
73
74
  type: boolean
74
75
  default: false
75
- required: [ schema ]
76
+ required: [schema]
76
77
  additionalProperties: false
77
78
 
78
79
  bindings:
@@ -88,19 +89,19 @@ properties:
88
89
  operations:
89
90
  type: object
90
91
  propertyNames:
91
- $ref: 'definitions#/definitions/token'
92
+ $ref: 'definitions#/definitions/name'
92
93
  patternProperties:
93
94
  '.*':
94
95
  type: object
95
96
  properties:
96
97
  type:
97
- enum: [ transition, observation, assignment, computation, effect ]
98
+ enum: [transition, observation, assignment, computation, effect]
98
99
  scope:
99
- enum: [ object, objects, changeset, none ]
100
+ enum: [object, objects, changeset, none]
100
101
  concurrency:
101
- enum: [ none, retry ]
102
+ enum: [none, retry]
102
103
  forward:
103
- $ref: 'definitions#/definitions/token'
104
+ $ref: 'definitions#/definitions/name'
104
105
  bridge:
105
106
  type: string
106
107
  bindings:
@@ -111,7 +112,7 @@ properties:
111
112
  $ref: 'definitions#/definitions/schema'
112
113
  query:
113
114
  type: boolean
114
- required: [ type, scope, bindings ]
115
+ required: [type, scope, bindings]
115
116
  allOf:
116
117
  - if: # transition
117
118
  properties:
@@ -120,15 +121,15 @@ properties:
120
121
  then:
121
122
  properties:
122
123
  scope:
123
- enum: [ object ]
124
+ enum: [object]
124
125
  if: # transition query: false
125
126
  not:
126
127
  properties:
127
128
  query:
128
129
  const: false
129
- required: [ query ]
130
+ required: [query]
130
131
  then:
131
- required: [ concurrency ]
132
+ required: [concurrency]
132
133
  - if: # not transition
133
134
  not:
134
135
  properties:
@@ -145,7 +146,7 @@ properties:
145
146
  then:
146
147
  properties:
147
148
  scope:
148
- enum: [ object, objects, none ]
149
+ enum: [object, objects, none]
149
150
  query:
150
151
  not:
151
152
  const: false
@@ -156,7 +157,7 @@ properties:
156
157
  then:
157
158
  properties:
158
159
  scope:
159
- enum: [ changeset ]
160
+ enum: [changeset]
160
161
  - if: # computation
161
162
  properties:
162
163
  type:
@@ -181,7 +182,7 @@ properties:
181
182
  events:
182
183
  type: object
183
184
  propertyNames:
184
- $ref: 'definitions#/definitions/token'
185
+ $ref: 'definitions#/definitions/name'
185
186
  patternProperties:
186
187
  '.*':
187
188
  type: object
@@ -198,7 +199,7 @@ properties:
198
199
  subjective:
199
200
  type: boolean
200
201
  default: false
201
- required: [ bridge, path ]
202
+ required: [bridge, path]
202
203
  additionalProperties: false
203
204
 
204
205
  receivers:
@@ -207,14 +208,16 @@ properties:
207
208
  '.*':
208
209
  type: object
209
210
  properties:
210
- transition:
211
- $ref: 'definitions#/definitions/token'
211
+ operation:
212
+ $ref: 'definitions#/definitions/name'
212
213
  bridge:
213
214
  type: string
214
215
  binding:
215
216
  type: string
216
217
  source:
217
- $ref: 'definitions#/definitions/token'
218
+ $ref: 'definitions#/definitions/name'
219
+ not:
220
+ const: context
218
221
  path:
219
222
  type: string
220
223
  conditioned:
@@ -223,7 +226,7 @@ properties:
223
226
  adaptive:
224
227
  type: boolean
225
228
  default: false
226
- required: [ transition, bridge, path ]
229
+ required: [operation]
227
230
  additionalProperties: false
228
231
 
229
232
  extensions:
@@ -27,14 +27,16 @@ const events = (manifest) => {
27
27
 
28
28
  const receivers = (manifest) => {
29
29
  for (const [locator, receiver] of Object.entries(manifest.receivers)) {
30
- if (manifest.operations?.[receiver.transition] === undefined) {
31
- throw new Error(`Receiver '${locator}' refers to undefined transition '${receiver.transition}'`)
30
+ if (manifest.operations?.[receiver.operation] === undefined) {
31
+ throw new Error(`Receiver '${locator}' refers to undefined operation '${receiver.operation}'`)
32
32
  }
33
33
 
34
- if (manifest.operations[receiver.transition].type !== 'transition') {
35
- throw new Error(`Receiver '${locator}' refers to non-transition '${receiver.transition}'`)
34
+ if (!TYPES.has(manifest.operations[receiver.operation].type)) {
35
+ throw new Error(`Receiver '${locator}' must refer to an operation of one of the allowed types: ${Array.from(TYPES).join(', ')}`)
36
36
  }
37
37
  }
38
38
  }
39
39
 
40
+ const TYPES = new Set(['transition', 'effect'])
41
+
40
42
  exports.validate = validate
@@ -1,14 +1,14 @@
1
1
  'use strict'
2
2
 
3
- /**
4
- * @param {toa.norm.Context} context
5
- * @returns {toa.norm.context.dependencies.References}
6
- */
7
- const connectors = (context) => {
8
- /** @type {toa.norm.context.dependencies.References} */
3
+ const connectors = (context, extracted) => {
9
4
  const connectors = {}
10
5
 
11
- for (const component of context.components) {
6
+ const components = (context.components === undefined
7
+ ? extracted
8
+ : context.components.concat(extracted)
9
+ ) ?? []
10
+
11
+ for (const component of components) {
12
12
  if (connectors[component.entity.storage] === undefined) {
13
13
  connectors[component.entity.storage] = []
14
14
  }
@@ -1,24 +1,56 @@
1
1
  'use strict'
2
2
 
3
- /**
4
- * @param {toa.norm.Context} context
5
- * @returns {toa.norm.context.dependencies.References}
6
- */
7
- const extensions = (context) => {
8
- /** @type {toa.norm.context.dependencies.References} */
3
+ const extensions = async (context) => {
9
4
  const extensions = {}
5
+ const components = context.components?.slice() ?? []
6
+ const extracted = await extractExtensionComponents(components, extensions)
10
7
 
11
- for (const component of context.components) {
12
- if (component.extensions !== undefined) {
13
- for (const reference of Object.keys(component.extensions)) {
14
- if (extensions[reference] === undefined) extensions[reference] = []
8
+ components.push(...extracted)
15
9
 
16
- extensions[reference].push(component)
10
+ for (const component of components) {
11
+ if (component.extensions === undefined) continue
12
+
13
+ for (const reference of Object.keys(component.extensions)) {
14
+ if (extensions[reference] === undefined) extensions[reference] = []
15
+
16
+ extensions[reference].push(component)
17
+ }
18
+ }
19
+
20
+ return { extensions, components: extracted }
21
+ }
22
+
23
+ async function extractExtensionComponents (components, extensions) {
24
+ const { component: load } = require('../../component')
25
+
26
+ const extracted = []
27
+
28
+ for (const component of components) {
29
+ if (component.extensions === undefined) continue
30
+
31
+ for (const reference of Object.keys(component.extensions)) {
32
+ if (reference in extensions) continue
33
+
34
+ extensions[reference] = []
35
+
36
+ const mod = require(reference)
37
+
38
+ if (mod.components === undefined) continue
39
+
40
+ for (const path of mod.components().paths) {
41
+ const component = await load(path)
42
+
43
+ extracted.push(component)
17
44
  }
18
45
  }
19
46
  }
20
47
 
21
- return extensions
48
+ if (extracted.length === 0)
49
+ return extracted
50
+
51
+ const deeper = await extractExtensionComponents(extracted, extensions)
52
+
53
+ return extracted.concat(deeper)
22
54
  }
23
55
 
24
56
  exports.extensions = extensions
@@ -1,15 +1,8 @@
1
1
  'use strict'
2
2
 
3
- const { join } = require('node:path')
4
3
  const { load } = require('./load')
5
4
 
6
- /**
7
- * @param {toa.norm.context.dependencies.References} references
8
- * @param {Object} annotations
9
- * @returns {toa.norm.context.Dependencies}
10
- */
11
5
  const resolve = (references, annotations) => {
12
- /** @type {toa.norm.context.Dependencies} */
13
6
  const dependencies = {}
14
7
 
15
8
  for (const [dependency, components] of Object.entries(references)) {
@@ -18,7 +11,8 @@ const resolve = (references, annotations) => {
18
11
 
19
12
  const instances = components.map((component) => ({
20
13
  locator: component.locator,
21
- manifest: component.extensions?.[id]
14
+ manifest: component.extensions?.[id],
15
+ component
22
16
  }))
23
17
 
24
18
  dependencies[dependency] = instances
@@ -2,13 +2,10 @@
2
2
 
3
3
  const { connectors, extensions, resolve } = require('./.dependencies')
4
4
 
5
- /**
6
- * @param {toa.norm.Context} context
7
- * @returns {toa.norm.context.Dependencies}
8
- */
9
- const dependencies = (context) => {
10
- /** @type {toa.norm.context.dependencies.References} */
11
- const references = { ...connectors(context), ...extensions(context) }
5
+ const dependencies = async (context) => {
6
+ const { extensions: e, components } = await extensions(context)
7
+ const c = connectors(context, components)
8
+ const references = { ...c, ...e }
12
9
 
13
10
  return resolve(references, context.annotations)
14
11
  }
@@ -11,7 +11,13 @@ properties:
11
11
  type: string
12
12
  packages:
13
13
  type: string
14
+ build:
15
+ type: object
16
+ properties:
17
+ command:
18
+ type: string
14
19
  runtime:
20
+ type: object
15
21
  properties:
16
22
  version:
17
23
  $ref: 'definitions#/definitions/version'
package/src/component.js CHANGED
@@ -35,7 +35,7 @@ const load = async (path, base) => {
35
35
  if (base !== undefined) path = find(path, base, MANIFEST)
36
36
 
37
37
  const file = join(path, MANIFEST)
38
- const manifest = /** @type {toa.norm.Component} */ await yaml(file)
38
+ const manifest = /** @type {toa.norm.Component} */ await yaml(file) ?? {}
39
39
 
40
40
  manifest.path = path
41
41
 
package/src/context.js CHANGED
@@ -16,10 +16,7 @@ const {
16
16
  validate
17
17
  } = require('./.context')
18
18
 
19
- /**
20
- * @type {toa.norm.context.Constructor}
21
- */
22
- const context = async (root, environment = undefined) => {
19
+ const context = async (root, environment = process.env.TOA_ENV) => {
23
20
  const path = resolve(root, CONTEXT)
24
21
  const context = /** @type {toa.norm.Context} */ await load(path)
25
22
  const pattern = resolve(root, context.packages)
@@ -35,7 +32,7 @@ const context = async (root, environment = undefined) => {
35
32
  const paths = await glob(pattern)
36
33
 
37
34
  context.components = await Promise.all(paths.map(component))
38
- context.dependencies = dependencies(context)
35
+ context.dependencies = await dependencies(context)
39
36
 
40
37
  dereference(context)
41
38
  complete(context)
package/src/shortcuts.js CHANGED
@@ -47,7 +47,8 @@ const SHORTCUTS = {
47
47
  queues: '@toa.io/storages.queues',
48
48
  exposition: '@toa.io/extensions.exposition',
49
49
  configuration: '@toa.io/extensions.configuration',
50
- origins: '@toa.io/extensions.origins'
50
+ origins: '@toa.io/extensions.origins',
51
+ stash: '@toa.io/extensions.stash'
51
52
  }
52
53
 
53
54
  exports.recognize = recognize
@@ -53,7 +53,7 @@ const source = {
53
53
  two: {
54
54
  binding: 'amqp',
55
55
  bridge: 'node',
56
- transition: 'transit'
56
+ operation: 'transit'
57
57
  }
58
58
  }
59
59
  }
@@ -123,12 +123,12 @@ const target = {
123
123
  },
124
124
  receivers: {
125
125
  one: {
126
- transition: 'transit'
126
+ operation: 'transit'
127
127
  },
128
128
  two: {
129
129
  bridge: '@toa.io/bridges.node',
130
130
  binding: '@toa.io/bindings.amqp',
131
- transition: 'transit'
131
+ operation: 'transit'
132
132
  }
133
133
  }
134
134
  }
@@ -3,15 +3,19 @@
3
3
  const { generate } = require('randomstring')
4
4
 
5
5
  const operations = {
6
+ namespace: 'dummies',
7
+ name: 'dummy',
6
8
  path: __dirname,
7
9
  bindings: ['foo', 'bar'],
8
10
  'bindings@local': ['foo'],
9
11
  operations: {
10
- add: {}
12
+ add: {
13
+ type: 'assignment'
14
+ }
11
15
  },
12
16
  extensions: {
13
17
  '@toa.io/extensions.exposition': {
14
- ['/' + generate()]: ['add']
18
+ ['/' + generate()]: {}
15
19
  },
16
20
  './dummies/extension': {
17
21
  ok: true
@@ -12,13 +12,6 @@ beforeEach(() => {
12
12
  manifest = clone(fixtures.operations)
13
13
  })
14
14
 
15
- describe('environment', () => {
16
- it('should convolve with environment argument', () => {
17
- normalize(manifest, 'local')
18
- expect(manifest.operations.add.bindings).toStrictEqual(['foo'])
19
- })
20
- })
21
-
22
15
  describe('operations', () => {
23
16
  it('should set default bindings', () => {
24
17
  normalize(manifest)
@@ -56,4 +49,21 @@ describe('receivers', () => {
56
49
  'default.messages.created': 'add'
57
50
  })
58
51
  })
52
+
53
+ it('should not substitute default namespace for foreign events', async () => {
54
+ const receiver = {
55
+ transition: 'add',
56
+ source: 'test'
57
+ }
58
+
59
+ manifest.receivers = {
60
+ 'messages.created': receiver
61
+ }
62
+
63
+ normalize(manifest)
64
+
65
+ expect(manifest.receivers).toStrictEqual({
66
+ 'messages.created': receiver
67
+ })
68
+ })
59
69
  })
@@ -47,7 +47,7 @@ const ok = {
47
47
  },
48
48
  receivers: {
49
49
  'foo.bar.happened': {
50
- transition: 'add',
50
+ operation: 'add',
51
51
  bridge: 'whatever',
52
52
  binding: 'amqp',
53
53
  path: '/somewhere'
@@ -37,14 +37,14 @@ describe('namespace', () => {
37
37
  manifest.namespace = 'foo_'
38
38
  expect(() => validate(manifest)).toThrow(/must match pattern/)
39
39
 
40
+ manifest.namespace = 'foo_bar'
41
+ expect(() => validate(manifest)).toThrow(/must match pattern/)
42
+
40
43
  manifest.namespace = 'foo-'
41
44
  expect(() => validate(manifest)).toThrow(/must match pattern/)
42
45
 
43
- manifest.namespace = 'foo-BAR'
44
- expect(() => validate(manifest)).not.toThrow()
45
-
46
- manifest.namespace = 'foo_bar'
47
- expect(() => validate(manifest)).not.toThrow()
46
+ manifest.namespace = 'foo-bar'
47
+ expect(() => validate(manifest)).toThrow('must match pattern')
48
48
 
49
49
  manifest.namespace = 'FooBar12'
50
50
  expect(() => validate(manifest)).not.toThrow()
@@ -229,15 +229,21 @@ describe('operations', () => {
229
229
  })
230
230
 
231
231
  describe('receivers', () => {
232
- it('should throw if transition points to undefined transition', () => {
233
- manifest.receivers['foo.bar.happened'].transition = 'not-exists'
232
+ it('should throw if transition points to undefined operation', () => {
233
+ manifest.receivers['foo.bar.happened'].operation = 'notExists'
234
234
 
235
- expect(() => validate(manifest)).toThrow(/refers to undefined transition/)
235
+ expect(() => validate(manifest)).toThrow(/refers to undefined operation/)
236
236
  })
237
237
 
238
- it('should throw if transition points to non transition', () => {
239
- manifest.receivers['foo.bar.happened'].transition = 'get'
238
+ it('should throw if transition points to observation', () => {
239
+ manifest.receivers['foo.bar.happened'].operation = 'get'
240
+
241
+ expect(() => validate(manifest)).toThrow(/one of the allowed types/)
242
+ })
240
243
 
241
- expect(() => validate(manifest)).toThrow(/refers to non-transition/)
244
+ it('should throw if source has a name `context`', async () => {
245
+ manifest.receivers['foo.bar.happened'].source = 'context'
246
+
247
+ expect(() => validate(manifest)).toThrow(/must NOT be valid/)
242
248
  })
243
249
  })
@@ -8,6 +8,9 @@ const context = {
8
8
  description: 'context fixture',
9
9
  version: '0.0.0',
10
10
  packages: 'namespaces/**/*',
11
+ build: {
12
+ command: 'echo test'
13
+ },
11
14
  registry: {
12
15
  base: 'localhost:5000',
13
16
  platforms: ['linux/amd64', 'linux/arm/v7', 'linux/arm64']
@@ -1,81 +1,61 @@
1
- import type { Locator } from '@toa.io/core/types'
1
+ import type { Locator, operations } from '@toa.io/core'
2
2
 
3
- export namespace toa.norm {
4
-
5
- namespace component {
6
- namespace operations {
7
-
8
- type Type = 'transition' | 'observation' | 'assignment' | 'computation' | 'effect'
9
- type Scope = 'object' | 'objects' | 'changeset'
10
-
11
- }
12
-
13
- interface Map {
14
- [id: string]: Component
15
- }
16
-
17
- interface Operation {
18
- type?: operations.Type
19
- scope?: operations.Scope
20
- bindings?: string[]
21
- input?: any
22
- output?: any
23
- error?: any
24
- }
25
-
26
- interface Operations {
27
- [key: string]: Operation
28
- }
29
-
30
- interface Event {
31
- binding: string
32
- }
3
+ type Map = {
4
+ [id: string]: Component
5
+ }
33
6
 
34
- interface Events {
35
- [key: string]: Event
36
- }
7
+ type Operation = {
8
+ type: operations.type
9
+ scope?: operations.scope
10
+ bindings?: string[]
11
+ input?: any
12
+ output?: any
13
+ error?: any
14
+ }
37
15
 
38
- interface Receiver {
39
- transition: string
40
- adaptive: boolean
41
- conditioned: boolean
42
- bridge: string
43
- binding: string
44
- path: string
45
- source?: string
46
- }
16
+ type Operations = {
17
+ [key: string]: Operation
18
+ }
47
19
 
48
- type Entity = {
49
- schema: Object
50
- storage?: string
51
- initialized?: boolean
52
- }
20
+ type Event = {
21
+ binding: string
22
+ }
53
23
 
54
- interface Declaration {
55
- prototype: string
56
- namespace: string
57
- name: string
58
- version: string
59
- entity: Entity
60
- bindings: string[]
61
- operations?: Operations
62
- events?: Events
63
- receivers: Record<string, Receiver>
64
- extensions?: Record<string, object>
65
- properties?: Record<string, object>
66
- }
24
+ type Events = {
25
+ [key: string]: Event
26
+ }
67
27
 
68
- type Constructor = (path: string) => Promise<Component>
69
- }
28
+ type Receiver = {
29
+ operation: string
30
+ adaptive: boolean
31
+ conditioned: boolean
32
+ bridge: string
33
+ binding: string
34
+ path: string
35
+ source?: string
36
+ }
70
37
 
71
- interface Component extends component.Declaration {
72
- locator: Locator
73
- path: string
74
- }
38
+ type Entity = {
39
+ schema: Object
40
+ storage?: string
41
+ initialized?: boolean
75
42
  }
76
43
 
77
- export type Component = toa.norm.Component
78
- export type Operation = toa.norm.component.Operation
79
- export type Declaration = toa.norm.component.Declaration
44
+ type Declaration = {
45
+ prototype?: string
46
+ namespace: string
47
+ name: string
48
+ version: string
49
+ entity?: Entity
50
+ bindings?: string[]
51
+ operations: Operations
52
+ events?: Events
53
+ receivers?: Record<string, Receiver>
54
+ extensions?: Record<string, object>
55
+ properties?: Record<string, object>
56
+ }
80
57
 
81
- export const component: toa.norm.component.Constructor
58
+ export type Manifest = Declaration & {
59
+ locator: Locator
60
+ path: string
61
+ }
@@ -0,0 +1,17 @@
1
+ import { Composition, Registry, Runtime } from '../context'
2
+
3
+ interface Composition {
4
+ name: string,
5
+ components: string[]
6
+ }
7
+
8
+ export interface Declaration {
9
+ name: string
10
+ description?: string
11
+ version?: string
12
+ runtime?: Runtime | string
13
+ registry?: Registry | string
14
+ packages?: string
15
+ compositions?: Composition[]
16
+ annotations?: Record<string, object>
17
+ }
@@ -1,75 +1,41 @@
1
- // noinspection ES6UnusedImports
2
-
3
- import { Component } from './component'
1
+ import * as _component from './component'
4
2
  import { Locator } from '@toa.io/core/types'
3
+ import type { Declaration } from './context/declaration'
5
4
 
6
- declare namespace toa.norm {
7
-
8
- namespace context {
9
-
10
- interface Runtime {
11
- version: string
12
- registry?: string
13
- proxy?: string
14
- }
15
-
16
- interface Registry {
17
- base: string
18
- platforms?: string[] | null
19
- }
20
-
21
- interface Composition {
22
- name: string,
23
- components: string[] | Component[]
24
- }
25
-
26
- namespace dependencies {
27
-
28
- type Instance = {
29
- locator: Locator
30
- manifest?: Object
31
- }
32
-
33
- type References = {
34
- [reference: string]: Component[]
35
- }
36
-
37
- }
38
-
39
- interface Dependencies {
40
- [reference: string]: dependencies.Instance[]
41
- }
42
-
43
- interface Declaration {
44
- name: string
45
- description: string
46
- version: string
47
- runtime: Runtime | string
48
- registry: Registry | string
49
- packages: string
50
- compositions?: Composition[]
51
- annotations?: Record<string, object>
52
- }
53
-
54
- type Constructor = (path: string, environment?: string) => Promise<Context>
55
- }
5
+ interface Runtime {
6
+ version: string
7
+ registry?: string
8
+ proxy?: string
9
+ }
56
10
 
57
- interface Context extends context.Declaration {
58
- locator: Locator
59
- runtime: context.Runtime
60
- environment?: string
61
- registry: context.Registry
62
- components: Component[]
63
- dependencies?: context.Dependencies
64
- }
11
+ interface Registry {
12
+ base?: string
13
+ platforms?: string[] | null
14
+ build?: {
15
+ arguments?: string[]
16
+ run?: string
17
+ },
18
+ credentials: string
19
+ }
65
20
 
21
+ interface Composition {
22
+ name: string,
23
+ components: _component.Component[]
66
24
  }
67
25
 
68
- export type Composition = toa.norm.context.Composition
69
- export type Context = toa.norm.Context
26
+ export interface Dependency<T = undefined> {
27
+ locator: Locator
28
+ manifest: T,
29
+ component: _component.Component
30
+ }
70
31
 
71
- export namespace dependencies {
72
- export type Instance = toa.norm.context.dependencies.Instance
32
+ interface Context extends Declaration {
33
+ runtime?: Runtime
34
+ environment?: string
35
+ registry?: Registry
36
+ compositions?: Composition[]
37
+ components?: _component.Component[]
38
+ dependencies?: Record<string, Dependency[]>
73
39
  }
74
40
 
75
- export const context: toa.norm.context.Constructor
41
+ export function context (path: string, environment?: string): Promise<Context>
package/types/index.d.ts CHANGED
@@ -1,5 +1,4 @@
1
- export { component } from './component'
2
- export { context } from './context'
1
+ export * as context from './context'
2
+ export * as component from './component'
3
3
 
4
- export type { Composition, dependencies } from './context'
5
- export type { Component, Operation } from './component'
4
+ export { Manifest } from './component'
@@ -1,54 +0,0 @@
1
- Feature: Operations declaration
2
-
3
- Scenario Outline: Operation <channel> entity references
4
-
5
- Operation IO schemas may reference entity's properties using syntax:
6
- - `.` - entity's property with the same name
7
- - '.foo` - entity's property with `foo` name
8
-
9
- Given I have an entity schema:
10
- """
11
- foo: string
12
- bar: 1
13
- """
14
- When I declare assignment with:
15
- """
16
- <channel>:
17
- foo: .
18
- baz: .bar
19
- """
20
- Then normalized assignment declaration must contain:
21
- """
22
- <channel>:
23
- type: object
24
- properties:
25
- foo:
26
- type: string
27
- baz:
28
- type: number
29
- default: 1
30
- """
31
-
32
- Examples:
33
- | channel |
34
- | input |
35
- | output |
36
-
37
- Scenario: Prototype property reference
38
-
39
- Operation IO schemas must be able to reference prototype's entity properties.
40
-
41
- When I declare assignment with:
42
- """
43
- output:
44
- id: .
45
- """
46
-
47
- Then normalized assignment declaration must contain:
48
- """
49
- output:
50
- type: object
51
- properties:
52
- id:
53
- $ref: https://schemas.toa.io/0.0.0/definitions#/definitions/id
54
- """
@@ -1,111 +0,0 @@
1
- 'use strict'
2
-
3
- const { AssertionError } = require('assert')
4
- const { generate } = require('randomstring')
5
- const { dump } = require('@toa.io/yaml')
6
- const gherkin = require('@toa.io/tomato')
7
-
8
- const mock = { gherkin }
9
-
10
- jest.mock('@cucumber/cucumber', () => mock.gherkin)
11
- require('../manifest')
12
-
13
- it('should be', () => undefined)
14
-
15
- /** @type {toa.norm.features.Context} */
16
- let context
17
-
18
- beforeEach(() => {
19
- const manifest = /** @type {toa.norm.component.Declaration} */ {
20
- name: 'test',
21
- namespace: 'features',
22
- version: '1.0.0',
23
- entity: null
24
- }
25
-
26
- context = { manifest }
27
- })
28
-
29
- describe('Given I have an entity schema:', () => {
30
- const step = gherkin.steps.Gi('I have an entity schema:')
31
-
32
- it('should be', () => undefined)
33
-
34
- it('should set entity schema', () => {
35
- const schema = { foo: 'string' }
36
- const yaml = dump(schema)
37
-
38
- step.call(context, yaml)
39
-
40
- expect(context.manifest.entity).toStrictEqual({ schema })
41
- })
42
- })
43
-
44
- describe('When I declare {operation} with:', () => {
45
- const step = gherkin.steps.Wh('I declare {operation} with:')
46
-
47
- it('should be', () => undefined)
48
-
49
- it('should declare operation', () => {
50
- const type = 'assignment'
51
- const scope = 'changeset'
52
- const input = {}
53
- const yaml = dump(input)
54
-
55
- step.call(context, type, yaml)
56
-
57
- expect(context.manifest.operations[type]).toStrictEqual({ type, scope })
58
- })
59
- })
60
-
61
- describe('Then normalized {operation} declaration must contain:', () => {
62
- const step = gherkin.steps.Th('normalized {operation} declaration must contain:')
63
-
64
- it('should be', () => undefined)
65
-
66
- it('should throw if does not contain', async () => {
67
- const type = 'assignment'
68
-
69
- context.manifest.operations = {
70
- [type]: {
71
- type,
72
- input: { bar: 'number' },
73
- scope: 'changeset'
74
- }
75
- }
76
-
77
- const input = {
78
- type: 'object',
79
- properties: {
80
- bar: { type: 'string' }
81
- }
82
- }
83
-
84
- const yaml = dump({ input })
85
-
86
- await expect(step.call(context, type, yaml)).rejects.toThrow(AssertionError)
87
- })
88
-
89
- it('should not throw if contain', async () => {
90
- const type = 'assignment'
91
-
92
- context.manifest.operations = {
93
- [type]: {
94
- type,
95
- input: { bar: 'number' },
96
- scope: 'changeset'
97
- }
98
- }
99
-
100
- const input = {
101
- type: 'object',
102
- properties: {
103
- bar: { type: 'number' }
104
- }
105
- }
106
-
107
- const yaml = dump({ input })
108
-
109
- await expect(step.call(context, type, yaml)).resolves.not.toThrow()
110
- })
111
- })
@@ -1,9 +0,0 @@
1
- import { Declaration } from '../../../types/component'
2
-
3
- declare namespace toa.norm.features {
4
-
5
- type Context = {
6
- manifest?: Declaration
7
- }
8
-
9
- }
@@ -1,18 +0,0 @@
1
- 'use strict'
2
-
3
- const { Before } = require('@cucumber/cucumber')
4
-
5
- Before(
6
- /**
7
- * @this {toa.norm.features.Context}
8
- */
9
- function () {
10
- this.manifest = {
11
- name: 'test',
12
- namespace: 'features',
13
- version: '1.0.0',
14
- entity: {
15
- storage: null
16
- }
17
- }
18
- })
@@ -1,66 +0,0 @@
1
- 'use strict'
2
-
3
- const assert = require('node:assert')
4
- const { join } = require('node:path')
5
- const { parse, save } = require('@toa.io/yaml')
6
- const { directory } = require('@toa.io/filesystem')
7
- const { match } = require('@toa.io/generic')
8
-
9
- const { component: load } = require('../../src')
10
-
11
- const { Given, When, Then } = require('@cucumber/cucumber')
12
-
13
- Given('I have an entity schema:',
14
- /**
15
- * @param {string} yaml
16
- * @this {toa.norm.features.Context}
17
- */
18
- function (yaml) {
19
- const schema = parse(yaml)
20
-
21
- this.manifest.entity = { schema }
22
- })
23
-
24
- When('I declare {operation} with:',
25
- /**
26
- * @param {toa.norm.component.operations.Type} type
27
- * @param {string} yaml
28
- * @this {toa.norm.features.Context}
29
- */
30
- function (type, yaml) {
31
- /** @type {toa.norm.component.Operation} */
32
- const declaration = parse(yaml)
33
-
34
- declaration.type = type
35
- declaration.scope = scope(type)
36
-
37
- this.manifest.operations = { [type]: declaration }
38
- })
39
-
40
- Then('normalized {operation} declaration must contain:',
41
- /**
42
- * @param {toa.norm.component.operations.Type} type
43
- * @param {string} yaml
44
- * @this {toa.norm.features.Context}
45
- */
46
- async function (type, yaml) {
47
- const temp = await directory.temp()
48
- const path = join(temp, 'manifest.toa.yaml')
49
-
50
- await save(this.manifest, path)
51
-
52
- const manifest = await load(temp)
53
- const operation = manifest.operations[type]
54
- const query = parse(yaml)
55
- const contains = match(operation, query)
56
-
57
- assert.equal(contains, true)
58
- })
59
-
60
- /**
61
- * @param {toa.norm.component.operations.Type} type
62
- * @returns {toa.norm.component.operations.Scope}
63
- */
64
- const scope = (type) => {
65
- return type === 'assignment' ? 'changeset' : 'object'
66
- }
@@ -1,9 +0,0 @@
1
- 'use strict'
2
-
3
- const { defineParameterType } = require('@cucumber/cucumber')
4
-
5
- defineParameterType({
6
- name: 'operation',
7
- regexp: /transition|observation|assignment/,
8
- transformer: (type) => type
9
- })