@toa.io/norm 0.2.1-dev.1

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 (71) hide show
  1. package/features/component.operations.feature +54 -0
  2. package/features/steps/.test/manifest.test.js +110 -0
  3. package/features/steps/.types/context.d.ts +9 -0
  4. package/features/steps/hooks.js +18 -0
  5. package/features/steps/manifest.js +66 -0
  6. package/features/steps/parameters.js +9 -0
  7. package/package.json +31 -0
  8. package/src/.component/.expand/bridge.js +9 -0
  9. package/src/.component/.expand/entity.js +14 -0
  10. package/src/.component/.expand/events.js +14 -0
  11. package/src/.component/.expand/extensions.js +10 -0
  12. package/src/.component/.expand/index.js +15 -0
  13. package/src/.component/.expand/operations.js +17 -0
  14. package/src/.component/.expand/receivers.js +16 -0
  15. package/src/.component/.normalize/events.js +13 -0
  16. package/src/.component/.normalize/extensions.js +35 -0
  17. package/src/.component/.normalize/index.js +9 -0
  18. package/src/.component/.normalize/operations.js +12 -0
  19. package/src/.component/collapse.js +44 -0
  20. package/src/.component/defaults.js +25 -0
  21. package/src/.component/dereference.js +72 -0
  22. package/src/.component/expand.js +14 -0
  23. package/src/.component/index.js +17 -0
  24. package/src/.component/merge.js +55 -0
  25. package/src/.component/normalize.js +13 -0
  26. package/src/.component/schema.yaml +222 -0
  27. package/src/.component/validate.js +40 -0
  28. package/src/.context/.dependencies/connectors.js +44 -0
  29. package/src/.context/.dependencies/describe.js +10 -0
  30. package/src/.context/.dependencies/extensions.js +24 -0
  31. package/src/.context/.dependencies/index.js +9 -0
  32. package/src/.context/.dependencies/resolve.js +45 -0
  33. package/src/.context/complete.js +33 -0
  34. package/src/.context/dependencies.js +16 -0
  35. package/src/.context/dereference.js +31 -0
  36. package/src/.context/expand.js +14 -0
  37. package/src/.context/index.js +15 -0
  38. package/src/.context/normalize.js +19 -0
  39. package/src/.context/schema.yaml +60 -0
  40. package/src/.context/validate.js +19 -0
  41. package/src/component.js +59 -0
  42. package/src/context.js +48 -0
  43. package/src/index.js +9 -0
  44. package/src/shortcuts.js +52 -0
  45. package/test/component/collapse.fixtures.js +134 -0
  46. package/test/component/collapse.test.js +81 -0
  47. package/test/component/dereference.fixtures.js +165 -0
  48. package/test/component/dereference.test.js +31 -0
  49. package/test/component/dummies/extension/index.js +9 -0
  50. package/test/component/dummies/extension/package.json +5 -0
  51. package/test/component/expand.fixtures.js +137 -0
  52. package/test/component/expand.test.js +17 -0
  53. package/test/component/normalize.fixtures.js +22 -0
  54. package/test/component/normalize.test.js +45 -0
  55. package/test/component/validate.fixtures.js +58 -0
  56. package/test/component/validate.test.js +250 -0
  57. package/test/context/complete.fixtures.js +59 -0
  58. package/test/context/complete.test.js +31 -0
  59. package/test/context/dereference.fixtures.js +60 -0
  60. package/test/context/dereference.test.js +24 -0
  61. package/test/context/expand.fixtures.js +5 -0
  62. package/test/context/expand.test.js +41 -0
  63. package/test/context/normalize.fixtures.js +32 -0
  64. package/test/context/normalize.test.js +34 -0
  65. package/test/context/validate.fixtures.js +38 -0
  66. package/test/context/validate.test.js +91 -0
  67. package/test/shortcuts.fixtures.js +17 -0
  68. package/test/shortcuts.test.js +92 -0
  69. package/types/component.d.ts +78 -0
  70. package/types/context.d.ts +75 -0
  71. package/types/index.d.ts +5 -0
@@ -0,0 +1,55 @@
1
+ 'use strict'
2
+
3
+ const { merge } = require('@toa.io/generic')
4
+
5
+ const bridge = async (root, manifest) => {
6
+ await Promise.all([
7
+ define(root, manifest, 'operations'),
8
+ define(root, manifest, 'events'),
9
+ define(root, manifest, 'receivers')
10
+ ])
11
+ }
12
+
13
+ const define = async (root, manifest, property) => {
14
+ const singular = property.replace(/(s)$/, '')
15
+
16
+ // non default bridges
17
+ if (manifest[property]) {
18
+ for (const [endpoint, item] of Object.entries(manifest[property])) {
19
+ if (item.bridge === undefined || item.bridge === manifest.bridge) continue // default bridge later
20
+
21
+ const bridge = item.bridge || manifest.bridge
22
+ const { define } = require(bridge)
23
+ const definition = await define[singular](root, endpoint)
24
+
25
+ merge(item, definition)
26
+ }
27
+ }
28
+
29
+ // default bridge
30
+ const definition = await scan(manifest.bridge, root, property)
31
+ const items = Object.entries(definition)
32
+
33
+ if (items.length) {
34
+ if (manifest[property] === undefined) manifest[property] = {}
35
+
36
+ for (const [endpoint, item] of items) {
37
+ if (property !== 'operations') item.path = root
38
+
39
+ item.bridge = manifest.bridge
40
+
41
+ const declared = manifest[property][endpoint]
42
+
43
+ if (declared === undefined) manifest[property][endpoint] = item
44
+ else merge(declared, item)
45
+ }
46
+ }
47
+ }
48
+
49
+ const scan = async (bridge, root, property) => {
50
+ const { define } = require(bridge)
51
+
52
+ return define[property](root)
53
+ }
54
+
55
+ exports.merge = bridge
@@ -0,0 +1,13 @@
1
+ 'use strict'
2
+
3
+ const { convolve } = require('@toa.io/generic')
4
+ const { events, operations, extensions } = require('./.normalize')
5
+
6
+ const normalize = (component, environment) => {
7
+ convolve(component, environment)
8
+ operations(component)
9
+ events(component)
10
+ extensions(component)
11
+ }
12
+
13
+ exports.normalize = normalize
@@ -0,0 +1,222 @@
1
+ $schema: https://json-schema.org/draft/2019-09/schema
2
+ $id: https://schemas.toa.io/0.0.0/manifest
3
+
4
+ definitions:
5
+ binding:
6
+ type: string
7
+ not:
8
+ const: '@toa.io/bindings.loop' # loop is for system use only
9
+
10
+ type: object
11
+ properties:
12
+ prototype:
13
+ type: object
14
+ nullable: true
15
+ properties:
16
+ prototype:
17
+ $ref: '#/properties/prototype'
18
+ path:
19
+ type: string
20
+ operations:
21
+ type: object
22
+ propertyNames:
23
+ $ref: 'definitions#/definitions/token'
24
+ patternProperties:
25
+ '.*':
26
+ type: object
27
+ properties:
28
+ bridge:
29
+ $ref: '#/properties/bridge'
30
+
31
+ path:
32
+ type: string
33
+
34
+ locator:
35
+ type: object
36
+ properties:
37
+ id:
38
+ $ref: 'definitions#/definitions/locator'
39
+ label:
40
+ $ref: 'definitions#/definitions/label'
41
+
42
+ namespace:
43
+ $ref: 'definitions#/definitions/token'
44
+ not:
45
+ oneOf:
46
+ - const: 'system'
47
+ - const: 'default'
48
+ name:
49
+ $ref: 'definitions#/definitions/token'
50
+
51
+ version:
52
+ $ref: 'definitions#/definitions/version'
53
+
54
+ entity:
55
+ type: object
56
+ properties:
57
+ storage:
58
+ type: string
59
+ schema:
60
+ $ref: 'definitions#/definitions/schema'
61
+ type: object
62
+ properties:
63
+ type:
64
+ default: object
65
+ const: object
66
+ additionalProperties:
67
+ default: false
68
+ const: false
69
+ properties:
70
+ type: object
71
+ propertyNames:
72
+ oneOf:
73
+ - $ref: 'definitions#/definitions/token'
74
+ - enum: [_version]
75
+ initialized:
76
+ type: boolean
77
+ default: false
78
+ required: [schema]
79
+ additionalProperties: false
80
+
81
+ bindings:
82
+ type: array
83
+ uniqueItems: true
84
+ minItems: 1
85
+ items:
86
+ $ref: '#/definitions/binding'
87
+
88
+ bridge:
89
+ type: string
90
+
91
+ operations:
92
+ type: object
93
+ propertyNames:
94
+ $ref: 'definitions#/definitions/token'
95
+ patternProperties:
96
+ '.*':
97
+ type: object
98
+ properties:
99
+ type:
100
+ enum: [transition, observation, assignment]
101
+ scope:
102
+ enum: [object, objects, changeset, none]
103
+ concurrency:
104
+ enum: [none, retry]
105
+ forward:
106
+ $ref: 'definitions#/definitions/token'
107
+ bridge:
108
+ type: string
109
+ bindings:
110
+ $ref: '#/properties/bindings'
111
+ input:
112
+ $ref: 'definitions#/definitions/schema'
113
+ not:
114
+ properties:
115
+ additionalProperties:
116
+ const: true
117
+ required: [additionalProperties]
118
+ output:
119
+ $ref: 'definitions#/definitions/schema'
120
+ query:
121
+ type: boolean
122
+ required: [type, scope, bindings]
123
+ allOf:
124
+ - if: # transition
125
+ properties:
126
+ type:
127
+ const: transition
128
+ then:
129
+ properties:
130
+ scope:
131
+ enum: [object]
132
+ if: # transition query: false
133
+ not:
134
+ properties:
135
+ query:
136
+ const: false
137
+ required: [query]
138
+ then:
139
+ required: [concurrency]
140
+ - if: # not transition
141
+ not:
142
+ properties:
143
+ type:
144
+ const: transition
145
+ then:
146
+ properties:
147
+ concurrency:
148
+ const: ~ # never pass
149
+ - if: # observation
150
+ properties:
151
+ type:
152
+ const: observation
153
+ then:
154
+ properties:
155
+ scope:
156
+ enum: [object, objects, none]
157
+ query:
158
+ not:
159
+ const: false
160
+ - if: # assignment
161
+ properties:
162
+ type:
163
+ const: assignment
164
+ then:
165
+ properties:
166
+ scope:
167
+ enum: [changeset]
168
+ additionalProperties: false
169
+ additionalProperties: false
170
+
171
+ events:
172
+ type: object
173
+ propertyNames:
174
+ $ref: 'definitions#/definitions/token'
175
+ patternProperties:
176
+ '.*':
177
+ type: object
178
+ properties:
179
+ bridge:
180
+ type: string
181
+ path:
182
+ type: string
183
+ binding:
184
+ $ref: '#/definitions/binding'
185
+ conditioned:
186
+ type: boolean
187
+ default: false
188
+ subjective:
189
+ type: boolean
190
+ default: false
191
+ required: [bridge, path]
192
+ additionalProperties: false
193
+
194
+ receivers:
195
+ type: object
196
+ propertyNames:
197
+ $ref: 'definitions#/definitions/endpoint'
198
+ patternProperties:
199
+ '.*':
200
+ type: object
201
+ properties:
202
+ transition:
203
+ $ref: 'definitions#/definitions/token'
204
+ bridge:
205
+ type: string
206
+ binding:
207
+ type: string
208
+ path:
209
+ type: string
210
+ conditioned:
211
+ type: boolean
212
+ default: false
213
+ adaptive:
214
+ type: boolean
215
+ default: false
216
+ required: [transition, bridge, path]
217
+ additionalProperties: false
218
+
219
+ extensions:
220
+ type: object
221
+
222
+ additionalProperties: false
@@ -0,0 +1,40 @@
1
+ 'use strict'
2
+
3
+ const path = require('node:path')
4
+
5
+ const { load } = require('@toa.io/yaml')
6
+ const { Schema } = require('@toa.io/schema')
7
+
8
+ const object = load.sync(path.resolve(__dirname, 'schema.yaml'))
9
+ const schema = new Schema(object)
10
+
11
+ const validate = (manifest) => {
12
+ const error = schema.fit(manifest)
13
+
14
+ if (error) throw new Error(error.message)
15
+
16
+ if (manifest.events !== undefined) events(manifest)
17
+ if (manifest.receivers !== undefined) receivers(manifest)
18
+ }
19
+
20
+ const events = (manifest) => {
21
+ for (const [label, event] of Object.entries(manifest.events)) {
22
+ if (require(event.binding).properties.async !== true) {
23
+ throw new Error(`Event '${label}' binding '${event.binding}' is not async`)
24
+ }
25
+ }
26
+ }
27
+
28
+ const receivers = (manifest) => {
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}'`)
32
+ }
33
+
34
+ if (manifest.operations[receiver.transition].type !== 'transition') {
35
+ throw new Error(`Receiver '${locator}' refers to non-transition '${receiver.transition}'`)
36
+ }
37
+ }
38
+ }
39
+
40
+ exports.validate = validate
@@ -0,0 +1,44 @@
1
+ 'use strict'
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} */
9
+ const connectors = {}
10
+
11
+ for (const component of context.components) {
12
+ if (connectors[component.entity.storage] === undefined) {
13
+ connectors[component.entity.storage] = []
14
+ }
15
+
16
+ connectors[component.entity.storage].push(component)
17
+
18
+ const bindings = new Set()
19
+
20
+ if (component.operations !== undefined) {
21
+ for (const operation of Object.values(component.operations)) {
22
+ operation.bindings.forEach((binding) => bindings.add(binding))
23
+ }
24
+ }
25
+
26
+ if (component.events !== undefined) {
27
+ for (const event of Object.values(component.events)) {
28
+ bindings.add(event.binding)
29
+ }
30
+ }
31
+
32
+ for (const binding of bindings) {
33
+ if (connectors[binding] === undefined) {
34
+ connectors[binding] = []
35
+ }
36
+
37
+ connectors[binding].push(component)
38
+ }
39
+ }
40
+
41
+ return connectors
42
+ }
43
+
44
+ exports.connectors = connectors
@@ -0,0 +1,10 @@
1
+ 'use strict'
2
+
3
+ /**
4
+ * @param {toa.norm.Component} component
5
+ * @param {Object} manifest
6
+ * @returns {toa.norm.context.dependencies.Instance}
7
+ */
8
+ const describe = (component, manifest = undefined) => ({ locator: component.locator, manifest })
9
+
10
+ exports.describe = describe
@@ -0,0 +1,24 @@
1
+ 'use strict'
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} */
9
+ const extensions = {}
10
+
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] = []
15
+
16
+ extensions[reference].push(component)
17
+ }
18
+ }
19
+ }
20
+
21
+ return extensions
22
+ }
23
+
24
+ exports.extensions = extensions
@@ -0,0 +1,9 @@
1
+ 'use strict'
2
+
3
+ const { connectors } = require('./connectors')
4
+ const { extensions } = require('./extensions')
5
+ const { resolve } = require('./resolve')
6
+
7
+ exports.connectors = connectors
8
+ exports.extensions = extensions
9
+ exports.resolve = resolve
@@ -0,0 +1,45 @@
1
+ 'use strict'
2
+
3
+ const { join } = require('node:path')
4
+
5
+ /**
6
+ * @param {toa.norm.context.dependencies.References} references
7
+ * @param {Object} annotations
8
+ * @returns {toa.norm.context.Dependencies}
9
+ */
10
+ const resolve = (references, annotations) => {
11
+ /** @type {toa.norm.context.Dependencies} */
12
+ const dependencies = {}
13
+
14
+ for (const [dependency, components] of Object.entries(references)) {
15
+ const id = name(dependency)
16
+
17
+ const instances = components.map((component) => ({
18
+ locator: component.locator,
19
+ manifest: component.extensions?.[id]
20
+ }))
21
+
22
+ dependencies[dependency] = instances
23
+
24
+ const annotation = annotations?.[id]
25
+ const module = require(dependency)
26
+
27
+ if (annotation !== undefined && module.annotation !== undefined) {
28
+ annotations[id] = module.annotation(annotation, instances)
29
+ }
30
+ }
31
+
32
+ return dependencies
33
+ }
34
+
35
+ /**
36
+ * @param {string} dependency
37
+ * @returns {string}
38
+ */
39
+ const name = (dependency) => {
40
+ const pkg = require(join(dependency, 'package.json'))
41
+
42
+ return pkg.name
43
+ }
44
+
45
+ exports.resolve = resolve
@@ -0,0 +1,33 @@
1
+ 'use strict'
2
+
3
+ /**
4
+ * Completes missing compositions with unused components
5
+ * @param {toa.norm.Context} context
6
+ * @returns {void}
7
+ */
8
+ const complete = (context) => {
9
+ /** @type {Set<string>} */
10
+ const composed = new Set()
11
+
12
+ if (context.compositions === undefined) context.compositions = []
13
+
14
+ for (const composition of context.compositions) {
15
+ for (const component of composition.components) {
16
+ composed.add(component.locator.id)
17
+ }
18
+ }
19
+
20
+ /** @type {Set<string>} */
21
+ const names = new Set(context.compositions.map((composition) => composition.name))
22
+
23
+ for (const component of context.components) {
24
+ const { id, label: name } = component.locator
25
+
26
+ if (composed.has(id)) continue
27
+ if (names.has(name)) throw new Error(`Duplicate composition name '${name}'`)
28
+
29
+ context.compositions.push({ name, components: [component] })
30
+ }
31
+ }
32
+
33
+ exports.complete = complete
@@ -0,0 +1,16 @@
1
+ 'use strict'
2
+
3
+ const { connectors, extensions, resolve } = require('./.dependencies')
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) }
12
+
13
+ return resolve(references, context.annotations)
14
+ }
15
+
16
+ exports.dependencies = dependencies
@@ -0,0 +1,31 @@
1
+ 'use strict'
2
+
3
+ /**
4
+ * Resolves component IDs within compositions with Component objects
5
+ * @param {toa.norm.Context} context
6
+ * @returns {void}
7
+ */
8
+ const dereference = (context) => {
9
+ const components = map(context.components)
10
+
11
+ if (context.compositions !== undefined) {
12
+ for (const composition of context.compositions) {
13
+ composition.components = composition.components.map((id) => components[id])
14
+ }
15
+ }
16
+ }
17
+
18
+ /**
19
+ * @param {Array<toa.norm.Component>} components
20
+ * @returns {toa.norm.component.Map}
21
+ */
22
+ const map = (components) => {
23
+ /** @type {toa.norm.component.Map} */
24
+ const map = {}
25
+
26
+ for (const component of components) map[component.locator.id] = component
27
+
28
+ return map
29
+ }
30
+
31
+ exports.dereference = dereference
@@ -0,0 +1,14 @@
1
+ 'use strict'
2
+
3
+ const { recognize } = require('../shortcuts')
4
+
5
+ /**
6
+ * @param {toa.norm.context.Declaration | object} context
7
+ */
8
+ const expand = (context) => {
9
+ recognize(context, 'annotations')
10
+ recognize(context.annotations)
11
+
12
+ }
13
+
14
+ exports.expand = expand
@@ -0,0 +1,15 @@
1
+ 'use strict'
2
+
3
+ const { complete } = require('./complete')
4
+ const { dependencies } = require('./dependencies')
5
+ const { dereference } = require('./dereference')
6
+ const { expand } = require('./expand')
7
+ const { normalize } = require('./normalize')
8
+ const { validate } = require('./validate')
9
+
10
+ exports.complete = complete
11
+ exports.dependencies = dependencies
12
+ exports.dereference = dereference
13
+ exports.expand = expand
14
+ exports.normalize = normalize
15
+ exports.validate = validate
@@ -0,0 +1,19 @@
1
+ 'use strict'
2
+
3
+ /**
4
+ * @param {toa.norm.context.Declaration | Object} context
5
+ */
6
+ const normalize = (context) => {
7
+ const runtime = require('@toa.io/runtime')
8
+
9
+ if (context.runtime === undefined) context.runtime = { version: runtime.version }
10
+ if (typeof context.runtime === 'string') context.runtime = { version: context.runtime }
11
+
12
+ if (context.runtime.version === undefined || context.runtime.version === '.') {
13
+ context.runtime.version = runtime.version
14
+ }
15
+
16
+ if (typeof context.registry === 'string') context.registry = { base: context.registry }
17
+ }
18
+
19
+ exports.normalize = normalize
@@ -0,0 +1,60 @@
1
+ $schema: https://json-schema.org/draft/2019-09/schema
2
+ $id: https://schemas.toa.io/0.0.0/context
3
+
4
+ type: object
5
+ properties:
6
+ version:
7
+ $ref: 'definitions#/definitions/version'
8
+ name:
9
+ $ref: 'definitions#/definitions/label'
10
+ description:
11
+ type: string
12
+ packages:
13
+ type: string
14
+ runtime:
15
+ properties:
16
+ version:
17
+ $ref: 'definitions#/definitions/version'
18
+ registry:
19
+ type: string
20
+ format: uri
21
+ proxy:
22
+ type: string
23
+ format: uri
24
+ required: [version]
25
+ environment:
26
+ type: string
27
+ registry:
28
+ type: object
29
+ properties:
30
+ base:
31
+ type: string
32
+ platforms:
33
+ type: array
34
+ nullable: true
35
+ minItems: 1
36
+ items:
37
+ type: string
38
+ default:
39
+ - linux/amd64
40
+ - linux/arm/v7
41
+ - linux/arm64
42
+ required: [base, platforms]
43
+ compositions:
44
+ type: array
45
+ minItems: 1
46
+ items:
47
+ type: object
48
+ properties:
49
+ name:
50
+ $ref: 'definitions#/definitions/token'
51
+ components:
52
+ type: array
53
+ minItems: 1
54
+ items:
55
+ $ref: 'definitions#/definitions/locator'
56
+ required: [name, components]
57
+ annotations:
58
+ type: object
59
+ required: [runtime, name, packages, registry]
60
+ additionalProperties: false
@@ -0,0 +1,19 @@
1
+ 'use strict'
2
+
3
+ const { resolve } = require('node:path')
4
+
5
+ const { load } = require('@toa.io/yaml')
6
+ const { Schema } = require('@toa.io/schema')
7
+
8
+ const path = resolve(__dirname, 'schema.yaml')
9
+ const object = load.sync(path)
10
+ const schema = new Schema(object)
11
+
12
+ /**
13
+ * @param {toa.norm.context.Declaration} context
14
+ */
15
+ const validate = (context) => {
16
+ schema.validate(context)
17
+ }
18
+
19
+ exports.validate = validate