@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,59 @@
1
+ 'use strict'
2
+
3
+ const { join } = require('node:path')
4
+
5
+ const { load: yaml } = require('@toa.io/yaml')
6
+ const { directory: { find } } = require('@toa.io/filesystem')
7
+ const { Locator } = require('@toa.io/core')
8
+
9
+ const {
10
+ expand,
11
+ merge,
12
+ validate,
13
+ collapse,
14
+ dereference,
15
+ defaults,
16
+ normalize
17
+ } = require('./.component')
18
+
19
+ /**
20
+ * @type {toa.norm.component.Constructor}
21
+ */
22
+ const component = async (path) => {
23
+ const manifest = await load(path)
24
+
25
+ normalize(manifest, process.env.TOA_ENV)
26
+ validate(manifest)
27
+
28
+ return manifest
29
+ }
30
+
31
+ const load = async (path, base) => {
32
+ if (base !== undefined) path = find(path, base, MANIFEST)
33
+
34
+ const file = join(path, MANIFEST)
35
+ const manifest = await yaml(file)
36
+
37
+ manifest.path = path
38
+
39
+ defaults(manifest)
40
+ expand(manifest)
41
+
42
+ await merge(path, manifest)
43
+
44
+ if (manifest.prototype !== null) {
45
+ manifest.locator = new Locator(manifest.name, manifest.namespace)
46
+
47
+ const prototype = await load(manifest.prototype, path)
48
+
49
+ collapse(manifest, prototype)
50
+ }
51
+
52
+ dereference(manifest)
53
+
54
+ return manifest
55
+ }
56
+
57
+ const MANIFEST = 'manifest.toa.yaml'
58
+
59
+ exports.component = component
package/src/context.js ADDED
@@ -0,0 +1,48 @@
1
+ 'use strict'
2
+
3
+ const { resolve } = require('node:path')
4
+ const { convolve } = require('@toa.io/generic')
5
+ const { directory: { glob } } = require('@toa.io/filesystem')
6
+ const { load } = require('@toa.io/yaml')
7
+
8
+ const { component } = require('./component')
9
+
10
+ const {
11
+ dependencies,
12
+ normalize,
13
+ complete,
14
+ dereference,
15
+ expand,
16
+ validate
17
+ } = require('./.context')
18
+
19
+ /**
20
+ * @type {toa.norm.context.Constructor}
21
+ */
22
+ const context = async (root, environment = undefined) => {
23
+ const path = resolve(root, CONTEXT)
24
+ const context = /** @type {toa.norm.Context} */ await load(path)
25
+ const pattern = resolve(root, context.packages)
26
+
27
+ context.environment = environment
28
+
29
+ convolve(context, environment)
30
+ expand(context)
31
+ normalize(context)
32
+
33
+ validate(context)
34
+
35
+ const paths = await glob(pattern)
36
+
37
+ context.components = await Promise.all(paths.map(component))
38
+ context.dependencies = dependencies(context)
39
+
40
+ dereference(context)
41
+ complete(context)
42
+
43
+ return context
44
+ }
45
+
46
+ const CONTEXT = 'context.toa.yaml'
47
+
48
+ exports.context = context
package/src/index.js ADDED
@@ -0,0 +1,9 @@
1
+ 'use strict'
2
+
3
+ const { context } = require('./context')
4
+ const { component } = require('./component')
5
+
6
+ exports.shortcuts = require('./shortcuts')
7
+
8
+ exports.context = context
9
+ exports.component = component
@@ -0,0 +1,52 @@
1
+ 'use strict'
2
+
3
+ const { empty, merge } = require('@toa.io/generic')
4
+
5
+ /**
6
+ * Resolves shortcuts
7
+ *
8
+ * @param {string} token
9
+ * @returns {string}
10
+ */
11
+ const resolve = (token) => {
12
+ if (token in SHORTCUTS) return SHORTCUTS[token]
13
+ else return token
14
+ }
15
+
16
+ /**
17
+ * Finds object keys with known shortcuts, resolves and groups them to a given group key if provided
18
+ *
19
+ * @param {Object} object
20
+ * @param {string} [group]
21
+ */
22
+ const recognize = (object, group) => {
23
+ if (object === undefined) return
24
+
25
+ const target = group === undefined ? object : {}
26
+
27
+ for (const [alias, name] of Object.entries(SHORTCUTS)) {
28
+ const value = object[alias]
29
+
30
+ if (value === undefined) continue
31
+
32
+ delete object[alias]
33
+
34
+ target[name] = value
35
+ }
36
+
37
+ if (group !== undefined && !empty(target)) object[group] = merge(object[group], target)
38
+ }
39
+
40
+ const SHORTCUTS = {
41
+ http: '@toa.io/bindings.http',
42
+ amqp: '@toa.io/bindings.amqp',
43
+ node: '@toa.io/bridges.node',
44
+ mongodb: '@toa.io/storages.mongodb',
45
+ sql: '@toa.io/storages.sql',
46
+ exposition: '@toa.io/extensions.exposition',
47
+ origins: '@toa.io/extensions.origins',
48
+ configuration: '@toa.io/extensions.configuration'
49
+ }
50
+
51
+ exports.recognize = recognize
52
+ exports.resolve = resolve
@@ -0,0 +1,134 @@
1
+ 'use strict'
2
+
3
+ const { generate } = require('randomstring')
4
+
5
+ const entity = {
6
+ manifest: {
7
+ entity: {
8
+ schema: {
9
+ properties: {
10
+ foo: {
11
+ type: 'string'
12
+ },
13
+ bar: {
14
+ type: 'integer'
15
+ }
16
+ },
17
+ required: ['foo']
18
+ }
19
+ }
20
+ },
21
+ prototype: {
22
+ entity: {
23
+ schema: {
24
+ properties: {
25
+ foo: {
26
+ default: 'ok'
27
+ },
28
+ baz: {
29
+ type: 'boolean'
30
+ }
31
+ },
32
+ required: ['baz']
33
+ }
34
+ }
35
+ },
36
+ result: {
37
+ entity: {
38
+ schema: {
39
+ properties: {
40
+ foo: {
41
+ type: 'string',
42
+ default: 'ok'
43
+ },
44
+ bar: {
45
+ type: 'integer'
46
+ },
47
+ baz: {
48
+ type: 'boolean'
49
+ }
50
+ },
51
+ required: ['foo', 'baz']
52
+ }
53
+ }
54
+ }
55
+ }
56
+
57
+ const operations = {
58
+ manifest: {
59
+ operations: {
60
+ add: {
61
+ bridge: 'b',
62
+ query: false
63
+ },
64
+ get: {
65
+ bridge: 'b'
66
+ },
67
+ find: {
68
+ bridge: 'b'
69
+ }
70
+ }
71
+ },
72
+ prototype: {
73
+ prototype: null,
74
+ path: generate(),
75
+ operations: {
76
+ add: {
77
+ bridge: 'a'
78
+ },
79
+ find: {
80
+ bridge: 'a'
81
+ },
82
+ observe: {
83
+ bridge: 'a',
84
+ input: 'object'
85
+ }
86
+ }
87
+ },
88
+ result: {
89
+ prototype: {
90
+ prototype: null,
91
+ path: expect.any(String),
92
+ operations: {
93
+ add: {
94
+ bridge: 'a'
95
+ },
96
+ find: {
97
+ bridge: 'a'
98
+ },
99
+ observe: {
100
+ bridge: 'a'
101
+ }
102
+ }
103
+ },
104
+ operations: {
105
+ add: {
106
+ bridge: 'b',
107
+ query: false
108
+ },
109
+ get: {
110
+ bridge: 'b'
111
+ },
112
+ find: {
113
+ bridge: 'b'
114
+ },
115
+ observe: {
116
+ input: 'object'
117
+ }
118
+ }
119
+ }
120
+ }
121
+
122
+ const remotes = {
123
+ manifest: {
124
+ remotes: ['a', 'b']
125
+ },
126
+ prototype: {
127
+ remotes: ['c', 'd']
128
+ },
129
+ result: {
130
+ remotes: ['a', 'b', 'c', 'd']
131
+ }
132
+ }
133
+
134
+ exports.samples = { entity, operations, remotes }
@@ -0,0 +1,81 @@
1
+ 'use strict'
2
+
3
+ const clone = require('clone-deep')
4
+
5
+ const fixtures = require('./collapse.fixtures')
6
+ const { collapse } = require('../../src/.component')
7
+
8
+ let samples
9
+
10
+ beforeEach(() => {
11
+ samples = clone(fixtures.samples)
12
+ })
13
+
14
+ it('should ignore locator', () => {
15
+ const source = {}
16
+ const prototype = { namespace: 'foo1', name: 'bar1' }
17
+ const manifest = clone(source)
18
+
19
+ collapse(manifest, prototype)
20
+
21
+ expect(manifest).toStrictEqual(source)
22
+ })
23
+
24
+ it('should remove prototype property', () => {
25
+ const manifest = { prototype: 'a' }
26
+
27
+ collapse(manifest, {})
28
+
29
+ expect(manifest).toStrictEqual({})
30
+ })
31
+
32
+ describe('entity', () => {
33
+ it('should ignore storage', () => {
34
+ const source = { entity: { storage: 'foo' } }
35
+ const prototype = { entity: { storage: 'bar' } }
36
+ const manifest = clone(source)
37
+
38
+ collapse(manifest, prototype)
39
+ expect(manifest).toStrictEqual(source)
40
+
41
+ delete manifest.entity.storage
42
+ collapse(manifest, prototype)
43
+ expect(manifest).toStrictEqual({ entity: {} })
44
+ })
45
+
46
+ it('should merge entity schema', () => {
47
+ const manifest = clone(samples.entity.manifest)
48
+
49
+ collapse(manifest, samples.entity.prototype)
50
+ expect(manifest).toStrictEqual(samples.entity.result)
51
+ })
52
+ })
53
+
54
+ it('should ignore bindings', () => {
55
+ const source = { bindings: ['foo'] }
56
+ const prototype = { bindings: ['bar'] }
57
+ const manifest = clone(source)
58
+
59
+ collapse(manifest, prototype)
60
+ expect(manifest).toStrictEqual(source)
61
+
62
+ delete manifest.bindings
63
+ collapse(manifest, prototype)
64
+ expect(manifest).toStrictEqual({})
65
+ })
66
+
67
+ it('should merge operations', () => {
68
+ const manifest = clone(samples.operations.manifest)
69
+ const prototype = clone(samples.operations.prototype)
70
+
71
+ collapse(manifest, prototype, '/somewhere')
72
+ expect(manifest).toStrictEqual(samples.operations.result)
73
+ })
74
+
75
+ it('should merge remotes', () => {
76
+ const manifest = clone(samples.remotes.manifest)
77
+ const prototype = clone(samples.remotes.prototype)
78
+
79
+ collapse(manifest, prototype, '/somewhere')
80
+ expect(manifest).toStrictEqual(samples.remotes.result)
81
+ })
@@ -0,0 +1,165 @@
1
+ 'use strict'
2
+
3
+ const source = {
4
+ entity: {
5
+ schema: {
6
+ properties: {
7
+ foo: {
8
+ type: 'string'
9
+ },
10
+ bar: {
11
+ type: 'string',
12
+ default: '.foo'
13
+ }
14
+ }
15
+ }
16
+ },
17
+ operations: {
18
+ transit: {
19
+ input: {
20
+ properties: {
21
+ foo: {
22
+ type: 'string',
23
+ default: '.'
24
+ },
25
+ bar: {
26
+ type: 'string',
27
+ default: '.foo'
28
+ },
29
+ baz1: {
30
+ type: 'array',
31
+ items: {
32
+ type: 'string',
33
+ default: '.foo'
34
+ }
35
+ },
36
+ baz2: {
37
+ type: 'array',
38
+ items: {
39
+ properties: {
40
+ foo: {
41
+ type: 'string',
42
+ default: '.'
43
+ },
44
+ bar: {
45
+ type: 'string',
46
+ default: '.foo'
47
+ }
48
+ }
49
+ }
50
+ }
51
+ }
52
+ },
53
+ output: {
54
+ properties: {
55
+ bar: {
56
+ type: 'string',
57
+ default: '.foo'
58
+ }
59
+ }
60
+ }
61
+ },
62
+ create: {
63
+ forward: 'transit',
64
+ query: false
65
+ }
66
+ }
67
+ }
68
+
69
+ const target = {
70
+ entity: {
71
+ schema: {
72
+ properties: {
73
+ foo: {
74
+ type: 'string'
75
+ },
76
+ bar: {
77
+ type: 'string'
78
+ }
79
+ }
80
+ }
81
+ },
82
+ operations: {
83
+ transit: {
84
+ input: {
85
+ properties: {
86
+ foo: {
87
+ type: 'string'
88
+ },
89
+ bar: {
90
+ type: 'string'
91
+ },
92
+ baz1: {
93
+ type: 'array',
94
+ items: {
95
+ type: 'string'
96
+ }
97
+ },
98
+ baz2: {
99
+ type: 'array',
100
+ items: {
101
+ properties: {
102
+ foo: {
103
+ type: 'string'
104
+ },
105
+ bar: {
106
+ type: 'string'
107
+ }
108
+ }
109
+ }
110
+ }
111
+ }
112
+ },
113
+ output: {
114
+ properties: {
115
+ bar: {
116
+ type: 'string'
117
+ }
118
+ }
119
+ }
120
+ },
121
+ create: {
122
+ forward: 'transit',
123
+ query: false,
124
+ input: {
125
+ properties: {
126
+ foo: {
127
+ type: 'string'
128
+ },
129
+ bar: {
130
+ type: 'string'
131
+ },
132
+ baz1: {
133
+ type: 'array',
134
+ items: {
135
+ type: 'string'
136
+ }
137
+ },
138
+ baz2: {
139
+ type: 'array',
140
+ items: {
141
+ properties: {
142
+ foo: {
143
+ type: 'string'
144
+ },
145
+ bar: {
146
+ type: 'string'
147
+ }
148
+ }
149
+ }
150
+ }
151
+ }
152
+ },
153
+ output: {
154
+ properties: {
155
+ bar: {
156
+ type: 'string'
157
+ }
158
+ }
159
+ }
160
+ }
161
+ }
162
+ }
163
+
164
+ exports.source = source
165
+ exports.target = target
@@ -0,0 +1,31 @@
1
+ 'use strict'
2
+
3
+ const clone = require('clone-deep')
4
+
5
+ const { dereference } = require('../../src/.component')
6
+ const fixtures = require('./dereference.fixtures')
7
+
8
+ let source
9
+
10
+ beforeEach(() => {
11
+ source = clone(fixtures.source)
12
+ })
13
+
14
+ it('should dereference', () => {
15
+ dereference(source)
16
+
17
+ expect(source).toStrictEqual(fixtures.target)
18
+ })
19
+
20
+ it('should throw on invalid schema reference', () => {
21
+ source.operations.transit.output.properties.baz = { type: 'string', default: '.' }
22
+ expect(() => dereference(source)).toThrow(/is not defined/)
23
+
24
+ source.operations.transit.output.properties.baz = { type: 'string', default: '.baz' }
25
+ expect(() => dereference(source)).toThrow(/is not defined/)
26
+ })
27
+
28
+ it('should throw on invalid forwarding', () => {
29
+ source.operations.create.forward = 'foo'
30
+ expect(() => dereference(source)).toThrow(/is not defined/)
31
+ })
@@ -0,0 +1,9 @@
1
+ 'use strict'
2
+
3
+ const manifest = (declaration) => {
4
+ if (declaration.ok !== true) return
5
+
6
+ return declaration
7
+ }
8
+
9
+ exports.manifest = manifest
@@ -0,0 +1,5 @@
1
+ {
2
+ "name": "norm-dummy-extension",
3
+ "version": "1.0.0",
4
+ "private": true
5
+ }