@toa.io/norm 1.0.0-alpha.2 → 1.0.0-alpha.200
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 +6 -7
- package/src/.component/.expand/extensions.js +2 -1
- package/src/.component/.expand/index.js +2 -0
- package/src/.component/.expand/version.js +45 -0
- package/src/.component/.normalize/operations.js +1 -1
- package/src/.component/collapse.js +5 -0
- package/src/.component/defaults.js +14 -5
- package/src/.component/dereference.js +6 -1
- package/src/.component/expand.js +5 -2
- package/src/.component/extensions.js +12 -2
- package/src/.component/merge.js +19 -5
- package/src/.component/schema.yaml +96 -40
- package/src/.component/validate.js +5 -5
- package/src/.context/.dependencies/connectors.js +8 -3
- package/src/.context/.dependencies/resolve.js +9 -0
- package/src/.context/schema.yaml +11 -7
- package/src/.context/validate.js +2 -5
- package/src/component.js +6 -7
- package/src/shortcuts.js +3 -1
- package/test/component/expand.fixtures.js +1 -0
- package/test/component/validate.test.js +14 -14
- package/test/context/validate.test.js +3 -16
- package/types/component.d.ts +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@toa.io/norm",
|
|
3
|
-
"version": "1.0.0-alpha.
|
|
3
|
+
"version": "1.0.0-alpha.200",
|
|
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",
|
|
@@ -20,11 +20,10 @@
|
|
|
20
20
|
"test": "echo \"Error: run tests from root\" && exit 1"
|
|
21
21
|
},
|
|
22
22
|
"dependencies": {
|
|
23
|
-
"@toa.io/core": "1.0.0-alpha.
|
|
24
|
-
"@toa.io/generic": "1.0.0-alpha.
|
|
25
|
-
"@toa.io/
|
|
26
|
-
"@toa.io/
|
|
27
|
-
"@toa.io/yaml": "1.0.0-alpha.2"
|
|
23
|
+
"@toa.io/core": "1.0.0-alpha.200",
|
|
24
|
+
"@toa.io/generic": "1.0.0-alpha.173",
|
|
25
|
+
"@toa.io/schemas": "1.0.0-alpha.200",
|
|
26
|
+
"@toa.io/yaml": "1.0.0-alpha.182"
|
|
28
27
|
},
|
|
29
|
-
"gitHead": "
|
|
28
|
+
"gitHead": "35b4adeedae2e9450ef44a74f42a53eff20ee203"
|
|
30
29
|
}
|
|
@@ -14,7 +14,8 @@ const SHORTCUTS = {
|
|
|
14
14
|
configuration: '@toa.io/extensions.configuration',
|
|
15
15
|
state: '@toa.io/extensions.state',
|
|
16
16
|
stash: '@toa.io/extensions.stash',
|
|
17
|
-
storages: '@toa.io/extensions.storages'
|
|
17
|
+
storages: '@toa.io/extensions.storages',
|
|
18
|
+
mail: '@toa.io/extensions.mail'
|
|
18
19
|
}
|
|
19
20
|
|
|
20
21
|
exports.extensions = extensions
|
|
@@ -7,6 +7,7 @@ const { extensions } = require('./extensions')
|
|
|
7
7
|
const { operations } = require('./operations')
|
|
8
8
|
const { properties } = require('./properties')
|
|
9
9
|
const { receivers } = require('./receivers')
|
|
10
|
+
const { version } = require('./version')
|
|
10
11
|
|
|
11
12
|
exports.bridge = bridge
|
|
12
13
|
exports.entity = entity
|
|
@@ -15,3 +16,4 @@ exports.extensions = extensions
|
|
|
15
16
|
exports.operations = operations
|
|
16
17
|
exports.properties = properties
|
|
17
18
|
exports.receivers = receivers
|
|
19
|
+
exports.version = version
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { join } = require('node:path')
|
|
4
|
+
const { createHash } = require('node:crypto')
|
|
5
|
+
const fs = require('node:fs/promises')
|
|
6
|
+
const { createReadStream } = require('node:fs')
|
|
7
|
+
const { once } = require('node:events')
|
|
8
|
+
|
|
9
|
+
async function version (manifest) {
|
|
10
|
+
manifest.version ??= await hash(manifest.path)
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
async function hash (path) {
|
|
14
|
+
const hash = await hashd(path)
|
|
15
|
+
|
|
16
|
+
return hash.digest('hex').slice(0, 8)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* @param {string} path
|
|
21
|
+
* @param {import('node:crypto').Hash} hash
|
|
22
|
+
*/
|
|
23
|
+
async function hashd (path, hash = createHash('sha256')) {
|
|
24
|
+
const stat = await fs.stat(path)
|
|
25
|
+
|
|
26
|
+
if (stat.isFile()) {
|
|
27
|
+
const stream = createReadStream(path)
|
|
28
|
+
|
|
29
|
+
stream.pipe(hash, { end: false })
|
|
30
|
+
|
|
31
|
+
return await once(stream, 'end')
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (stat.isDirectory()) {
|
|
35
|
+
const entries = await fs.opendir(path)
|
|
36
|
+
|
|
37
|
+
for await (const entry of entries) {
|
|
38
|
+
await hashd(join(path, entry.name), hash)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return hash
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
exports.version = version
|
|
@@ -4,7 +4,7 @@ const operations = (component) => {
|
|
|
4
4
|
if (component.operations === undefined) return
|
|
5
5
|
|
|
6
6
|
for (const [endpoint, operation] of Object.entries(component.operations)) {
|
|
7
|
-
if (operation.
|
|
7
|
+
if (operation.scope === 'none')
|
|
8
8
|
operation.query = false
|
|
9
9
|
|
|
10
10
|
if (operation.bindings === undefined) operation.bindings = component.bindings
|
|
@@ -42,6 +42,11 @@ const collapse = (manifest, prototype) => {
|
|
|
42
42
|
delete prototype.entity.schema.properties.id
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
+
if (prototype.events !== undefined && manifest.events !== undefined)
|
|
46
|
+
for (const event of Object.keys(prototype.events))
|
|
47
|
+
if (event in manifest.events)
|
|
48
|
+
delete prototype.events[event]
|
|
49
|
+
|
|
45
50
|
merge(manifest, { entity, events, extensions })
|
|
46
51
|
}
|
|
47
52
|
|
|
@@ -3,8 +3,11 @@
|
|
|
3
3
|
const { hash } = require('@toa.io/generic')
|
|
4
4
|
|
|
5
5
|
// these defaults are required before validation
|
|
6
|
-
const defaults = (manifest) => {
|
|
7
|
-
if (manifest.name === undefined)
|
|
6
|
+
const defaults = (manifest, proto) => {
|
|
7
|
+
if (manifest.name === undefined)
|
|
8
|
+
if (proto) manifest.name = protoName(manifest)
|
|
9
|
+
else nameAfterDir(manifest)
|
|
10
|
+
|
|
8
11
|
if (manifest.bindings === undefined) manifest.bindings = ['@toa.io/bindings.amqp']
|
|
9
12
|
if (manifest.bridge === undefined) manifest.bridge = '@toa.io/bridges.node'
|
|
10
13
|
|
|
@@ -17,13 +20,19 @@ const defaults = (manifest) => {
|
|
|
17
20
|
}
|
|
18
21
|
|
|
19
22
|
if (manifest.prototype === undefined) manifest.prototype = '@toa.io/prototype'
|
|
20
|
-
|
|
21
|
-
// TODO: bridge.version()
|
|
22
|
-
if (manifest.version === undefined) manifest.version = '0.0.0'
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
function protoName (manifest) {
|
|
26
26
|
return 'proto' + hash(manifest.path)
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
+
function nameAfterDir (manifest) {
|
|
30
|
+
const parts = manifest.path.split('/')
|
|
31
|
+
const dirname = parts[parts.length - 1]
|
|
32
|
+
const [name, namespace] = dirname.split('.').reverse()
|
|
33
|
+
|
|
34
|
+
manifest.name = name
|
|
35
|
+
manifest.namespace = namespace
|
|
36
|
+
}
|
|
37
|
+
|
|
29
38
|
exports.defaults = defaults
|
|
@@ -25,7 +25,12 @@ const createResolver = (properties) => (property) => {
|
|
|
25
25
|
function operations (manifest, resolver) {
|
|
26
26
|
for (const operation of Object.values(manifest.operations)) {
|
|
27
27
|
if (operation.input !== undefined) operation.input = schema(operation.input, resolver)
|
|
28
|
-
|
|
28
|
+
|
|
29
|
+
if (operation.output !== undefined)
|
|
30
|
+
if (Array.isArray(operation.output) && operation.output.length === 1)
|
|
31
|
+
operation.output = [schema(operation.output[0], resolver)]
|
|
32
|
+
else
|
|
33
|
+
operation.output = schema(operation.output, resolver)
|
|
29
34
|
}
|
|
30
35
|
|
|
31
36
|
// forwarding
|
package/src/.component/expand.js
CHANGED
|
@@ -7,10 +7,11 @@ const {
|
|
|
7
7
|
events,
|
|
8
8
|
receivers,
|
|
9
9
|
extensions,
|
|
10
|
-
properties
|
|
10
|
+
properties,
|
|
11
|
+
version
|
|
11
12
|
} = require('./.expand')
|
|
12
13
|
|
|
13
|
-
|
|
14
|
+
async function expand (manifest) {
|
|
14
15
|
entity(manifest)
|
|
15
16
|
bridge(manifest)
|
|
16
17
|
operations(manifest)
|
|
@@ -18,6 +19,8 @@ const expand = (manifest) => {
|
|
|
18
19
|
receivers(manifest)
|
|
19
20
|
properties(manifest)
|
|
20
21
|
extensions(manifest)
|
|
22
|
+
|
|
23
|
+
await version(manifest)
|
|
21
24
|
}
|
|
22
25
|
|
|
23
26
|
exports.expand = expand
|
|
@@ -3,8 +3,13 @@
|
|
|
3
3
|
const { directory: { find } } = require('@toa.io/filesystem')
|
|
4
4
|
const { resolve } = require('../shortcuts')
|
|
5
5
|
|
|
6
|
+
const cache = {}
|
|
7
|
+
|
|
6
8
|
const extensions = (manifest) => {
|
|
7
|
-
if (manifest.extensions === undefined)
|
|
9
|
+
if (manifest.extensions === undefined)
|
|
10
|
+
manifest.extensions = PREDEFINED
|
|
11
|
+
else
|
|
12
|
+
manifest.extensions = Object.assign({}, PREDEFINED, manifest.extensions)
|
|
8
13
|
|
|
9
14
|
const extensions = manifest.extensions
|
|
10
15
|
|
|
@@ -14,7 +19,8 @@ const extensions = (manifest) => {
|
|
|
14
19
|
// relative path
|
|
15
20
|
if (key[0] === '.') key = find(key, manifest.path)
|
|
16
21
|
|
|
17
|
-
|
|
22
|
+
cache[key] ??= require(key)
|
|
23
|
+
const extension = cache[key]
|
|
18
24
|
|
|
19
25
|
if (extension.manifest !== undefined) {
|
|
20
26
|
declaration = extension.manifest(declaration, manifest)
|
|
@@ -29,4 +35,8 @@ const extensions = (manifest) => {
|
|
|
29
35
|
}
|
|
30
36
|
}
|
|
31
37
|
|
|
38
|
+
const PREDEFINED = {
|
|
39
|
+
'@toa.io/extensions.telemetry': null
|
|
40
|
+
}
|
|
41
|
+
|
|
32
42
|
exports.extensions = extensions
|
package/src/.component/merge.js
CHANGED
|
@@ -6,7 +6,8 @@ const bridge = async (root, manifest) => {
|
|
|
6
6
|
await Promise.all([
|
|
7
7
|
define(root, manifest, 'operations'),
|
|
8
8
|
define(root, manifest, 'events'),
|
|
9
|
-
define(root, manifest, 'receivers')
|
|
9
|
+
define(root, manifest, 'receivers'),
|
|
10
|
+
define(root, manifest, 'guards')
|
|
10
11
|
])
|
|
11
12
|
}
|
|
12
13
|
|
|
@@ -19,7 +20,7 @@ const define = async (root, manifest, property) => {
|
|
|
19
20
|
if (item.bridge === undefined || item.bridge === manifest.bridge) continue // default bridge later
|
|
20
21
|
|
|
21
22
|
const bridge = item.bridge || manifest.bridge
|
|
22
|
-
const
|
|
23
|
+
const define = req(bridge).define
|
|
23
24
|
const definition = await define[singular](root, endpoint)
|
|
24
25
|
|
|
25
26
|
merge(item, definition)
|
|
@@ -44,14 +45,27 @@ const define = async (root, manifest, property) => {
|
|
|
44
45
|
|
|
45
46
|
const declared = manifest[property][endpoint]
|
|
46
47
|
|
|
47
|
-
|
|
48
|
-
|
|
48
|
+
try {
|
|
49
|
+
if (declared === undefined) manifest[property][endpoint] = item
|
|
50
|
+
else merge(declared, item)
|
|
51
|
+
} catch (error) {
|
|
52
|
+
console.error(`Error merging ${singular} '${endpoint}' in ${root}`)
|
|
53
|
+
throw error
|
|
54
|
+
}
|
|
49
55
|
}
|
|
50
56
|
}
|
|
51
57
|
}
|
|
52
58
|
|
|
59
|
+
const cache = {}
|
|
60
|
+
|
|
61
|
+
function req(mod) {
|
|
62
|
+
cache[mod] ??= require(mod)
|
|
63
|
+
|
|
64
|
+
return cache[mod]
|
|
65
|
+
}
|
|
66
|
+
|
|
53
67
|
const scan = async (bridge, root, property) => {
|
|
54
|
-
const
|
|
68
|
+
const define = req(bridge).define
|
|
55
69
|
|
|
56
70
|
if (property in define)
|
|
57
71
|
return define[property](root)
|
|
@@ -8,7 +8,7 @@ definitions:
|
|
|
8
8
|
binding:
|
|
9
9
|
type: string
|
|
10
10
|
not:
|
|
11
|
-
const:
|
|
11
|
+
const: "@toa.io/bindings.loop" # loop is for system use only
|
|
12
12
|
|
|
13
13
|
type: object
|
|
14
14
|
properties:
|
|
@@ -17,19 +17,19 @@ properties:
|
|
|
17
17
|
nullable: true
|
|
18
18
|
properties:
|
|
19
19
|
prototype:
|
|
20
|
-
$ref:
|
|
20
|
+
$ref: "#/properties/prototype"
|
|
21
21
|
path:
|
|
22
22
|
type: string
|
|
23
23
|
operations:
|
|
24
24
|
type: object
|
|
25
25
|
propertyNames:
|
|
26
|
-
$ref:
|
|
26
|
+
$ref: "#/definitions/name"
|
|
27
27
|
patternProperties:
|
|
28
|
-
|
|
28
|
+
".*":
|
|
29
29
|
type: object
|
|
30
30
|
properties:
|
|
31
31
|
bridge:
|
|
32
|
-
$ref:
|
|
32
|
+
$ref: "#/properties/bridge"
|
|
33
33
|
|
|
34
34
|
path:
|
|
35
35
|
type: string
|
|
@@ -38,37 +38,43 @@ properties:
|
|
|
38
38
|
type: object
|
|
39
39
|
properties:
|
|
40
40
|
id:
|
|
41
|
-
|
|
41
|
+
type: string
|
|
42
|
+
pattern: ^([a-zA-Z]+([_a-zA-Z0-9]*[a-zA-Z0-9]+)?)(\.([a-zA-Z]+([_a-zA-Z0-9]*[a-zA-Z0-9]+)?))$
|
|
42
43
|
label:
|
|
43
|
-
|
|
44
|
+
type: string
|
|
45
|
+
pattern: ^([a-zA-Z]+([_a-zA-Z0-9]*[a-zA-Z0-9]+)?)(-([a-zA-Z]+([_a-zA-Z0-9]*[a-zA-Z0-9]+)?))*$
|
|
44
46
|
|
|
45
47
|
name:
|
|
46
|
-
|
|
48
|
+
type: string
|
|
49
|
+
pattern: ^[a-zA-Z]([a-zA-Z0-9]{1,31})?$
|
|
47
50
|
|
|
48
51
|
namespace:
|
|
49
|
-
|
|
50
|
-
|
|
52
|
+
type: string
|
|
53
|
+
pattern: ^[a-zA-Z]([a-zA-Z0-9]{1,31})?$
|
|
54
|
+
default: "default"
|
|
51
55
|
not:
|
|
52
56
|
oneOf:
|
|
53
|
-
- const:
|
|
57
|
+
- const: "system"
|
|
54
58
|
|
|
55
59
|
version:
|
|
56
|
-
|
|
60
|
+
type: string
|
|
57
61
|
|
|
58
62
|
build:
|
|
59
63
|
type: object
|
|
60
64
|
properties:
|
|
61
65
|
image:
|
|
62
66
|
type: string
|
|
67
|
+
run:
|
|
68
|
+
type: string
|
|
63
69
|
|
|
64
70
|
entity:
|
|
65
71
|
type: object
|
|
66
72
|
properties:
|
|
67
73
|
storage:
|
|
68
74
|
type: string
|
|
69
|
-
default:
|
|
75
|
+
default: "@toa.io/storages.mongodb"
|
|
70
76
|
schema:
|
|
71
|
-
$ref:
|
|
77
|
+
$ref: https://json-schema.org/draft/2019-09/schema
|
|
72
78
|
type: object
|
|
73
79
|
properties:
|
|
74
80
|
type:
|
|
@@ -78,9 +84,25 @@ properties:
|
|
|
78
84
|
type: object
|
|
79
85
|
propertyNames:
|
|
80
86
|
oneOf:
|
|
81
|
-
- $ref:
|
|
82
|
-
- enum: [_version]
|
|
83
|
-
|
|
87
|
+
- $ref: "#/definitions/name"
|
|
88
|
+
- enum: [_version, _created, _updated, _deleted]
|
|
89
|
+
unique:
|
|
90
|
+
type: object
|
|
91
|
+
patternProperties:
|
|
92
|
+
".*":
|
|
93
|
+
type: array
|
|
94
|
+
items:
|
|
95
|
+
type: string
|
|
96
|
+
index:
|
|
97
|
+
type: object
|
|
98
|
+
patternProperties:
|
|
99
|
+
".*":
|
|
100
|
+
type: object
|
|
101
|
+
patternProperties:
|
|
102
|
+
".*":
|
|
103
|
+
type: string
|
|
104
|
+
enum: [asc, desc, hash, text]
|
|
105
|
+
associated:
|
|
84
106
|
type: boolean
|
|
85
107
|
default: false
|
|
86
108
|
custom:
|
|
@@ -93,7 +115,7 @@ properties:
|
|
|
93
115
|
type: array
|
|
94
116
|
uniqueItems: true
|
|
95
117
|
items:
|
|
96
|
-
$ref:
|
|
118
|
+
$ref: "#/definitions/binding"
|
|
97
119
|
|
|
98
120
|
bridge:
|
|
99
121
|
type: string
|
|
@@ -101,27 +123,40 @@ properties:
|
|
|
101
123
|
operations:
|
|
102
124
|
type: object
|
|
103
125
|
propertyNames:
|
|
104
|
-
$ref:
|
|
126
|
+
$ref: "#/definitions/name"
|
|
105
127
|
patternProperties:
|
|
106
|
-
|
|
128
|
+
".*":
|
|
107
129
|
type: object
|
|
108
130
|
properties:
|
|
109
131
|
type:
|
|
110
|
-
enum:
|
|
132
|
+
enum:
|
|
133
|
+
[
|
|
134
|
+
transition,
|
|
135
|
+
observation,
|
|
136
|
+
assignment,
|
|
137
|
+
computation,
|
|
138
|
+
effect,
|
|
139
|
+
unmanaged,
|
|
140
|
+
]
|
|
111
141
|
scope:
|
|
112
|
-
enum: [object, objects, changeset, none]
|
|
142
|
+
enum: [object, objects, changeset, stream, none]
|
|
113
143
|
concurrency:
|
|
114
144
|
enum: [none, retry]
|
|
115
145
|
forward:
|
|
116
|
-
$ref:
|
|
146
|
+
$ref: "#/definitions/name"
|
|
117
147
|
bridge:
|
|
118
148
|
type: string
|
|
119
149
|
bindings:
|
|
120
|
-
$ref:
|
|
150
|
+
$ref: "#/properties/bindings"
|
|
121
151
|
input:
|
|
122
|
-
$ref:
|
|
152
|
+
$ref: https://json-schema.org/draft/2019-09/schema
|
|
123
153
|
output:
|
|
124
|
-
$ref:
|
|
154
|
+
$ref: https://json-schema.org/draft/2019-09/schema
|
|
155
|
+
default: {}
|
|
156
|
+
errors:
|
|
157
|
+
type: array
|
|
158
|
+
items:
|
|
159
|
+
type: string
|
|
125
160
|
query:
|
|
126
161
|
type: boolean
|
|
127
162
|
required: [type, scope, bindings]
|
|
@@ -133,8 +168,8 @@ properties:
|
|
|
133
168
|
then:
|
|
134
169
|
properties:
|
|
135
170
|
scope:
|
|
136
|
-
enum: [object]
|
|
137
|
-
if: #
|
|
171
|
+
enum: [object, objects]
|
|
172
|
+
if: # not query: false
|
|
138
173
|
not:
|
|
139
174
|
properties:
|
|
140
175
|
query:
|
|
@@ -142,6 +177,17 @@ properties:
|
|
|
142
177
|
required: [query]
|
|
143
178
|
then:
|
|
144
179
|
required: [concurrency]
|
|
180
|
+
- if: # transition and scope=objects
|
|
181
|
+
properties:
|
|
182
|
+
type:
|
|
183
|
+
const: transition
|
|
184
|
+
scope:
|
|
185
|
+
const: objects
|
|
186
|
+
then:
|
|
187
|
+
properties:
|
|
188
|
+
query:
|
|
189
|
+
not:
|
|
190
|
+
const: false
|
|
145
191
|
- if: # not transition
|
|
146
192
|
not:
|
|
147
193
|
properties:
|
|
@@ -158,7 +204,7 @@ properties:
|
|
|
158
204
|
then:
|
|
159
205
|
properties:
|
|
160
206
|
scope:
|
|
161
|
-
enum: [object, objects, none]
|
|
207
|
+
enum: [object, objects, stream, none]
|
|
162
208
|
query:
|
|
163
209
|
not:
|
|
164
210
|
const: false
|
|
@@ -173,7 +219,7 @@ properties:
|
|
|
173
219
|
- if: # computation
|
|
174
220
|
properties:
|
|
175
221
|
type:
|
|
176
|
-
|
|
222
|
+
enum: [computation, unmanaged]
|
|
177
223
|
then:
|
|
178
224
|
properties:
|
|
179
225
|
scope:
|
|
@@ -189,20 +235,17 @@ properties:
|
|
|
189
235
|
then:
|
|
190
236
|
properties:
|
|
191
237
|
scope:
|
|
192
|
-
const: none
|
|
193
238
|
default: none
|
|
194
|
-
|
|
195
|
-
const: false
|
|
196
|
-
default: false
|
|
239
|
+
enum: [object, objects, stream, none]
|
|
197
240
|
additionalProperties: false
|
|
198
241
|
additionalProperties: false
|
|
199
242
|
|
|
200
243
|
events:
|
|
201
244
|
type: object
|
|
202
245
|
propertyNames:
|
|
203
|
-
$ref:
|
|
246
|
+
$ref: "#/definitions/name"
|
|
204
247
|
patternProperties:
|
|
205
|
-
|
|
248
|
+
".*":
|
|
206
249
|
type: object
|
|
207
250
|
properties:
|
|
208
251
|
bridge:
|
|
@@ -210,7 +253,7 @@ properties:
|
|
|
210
253
|
path:
|
|
211
254
|
type: string
|
|
212
255
|
binding:
|
|
213
|
-
$ref:
|
|
256
|
+
$ref: "#/definitions/binding"
|
|
214
257
|
conditioned:
|
|
215
258
|
type: boolean
|
|
216
259
|
default: false
|
|
@@ -223,17 +266,17 @@ properties:
|
|
|
223
266
|
receivers:
|
|
224
267
|
type: object
|
|
225
268
|
patternProperties:
|
|
226
|
-
|
|
269
|
+
".*":
|
|
227
270
|
type: object
|
|
228
271
|
properties:
|
|
229
272
|
operation:
|
|
230
|
-
$ref:
|
|
273
|
+
$ref: "#/definitions/name"
|
|
231
274
|
bridge:
|
|
232
275
|
type: string
|
|
233
276
|
binding:
|
|
234
277
|
type: string
|
|
235
278
|
source:
|
|
236
|
-
$ref:
|
|
279
|
+
$ref: "#/definitions/name"
|
|
237
280
|
not:
|
|
238
281
|
const: context
|
|
239
282
|
path:
|
|
@@ -244,9 +287,22 @@ properties:
|
|
|
244
287
|
adaptive:
|
|
245
288
|
type: boolean
|
|
246
289
|
default: false
|
|
290
|
+
arguments:
|
|
291
|
+
type: array
|
|
247
292
|
required: [operation]
|
|
248
293
|
additionalProperties: false
|
|
249
294
|
|
|
295
|
+
guards:
|
|
296
|
+
type: object
|
|
297
|
+
patternProperties:
|
|
298
|
+
".*":
|
|
299
|
+
type: object
|
|
300
|
+
properties:
|
|
301
|
+
bridge: { type: string }
|
|
302
|
+
path: { type: string }
|
|
303
|
+
required: [bridge, path]
|
|
304
|
+
additionalProperties: false
|
|
305
|
+
|
|
250
306
|
extensions:
|
|
251
307
|
type: object
|
|
252
308
|
|
|
@@ -3,15 +3,15 @@
|
|
|
3
3
|
const path = require('node:path')
|
|
4
4
|
|
|
5
5
|
const { load } = require('@toa.io/yaml')
|
|
6
|
-
const
|
|
6
|
+
const schemas = require('@toa.io/schemas')
|
|
7
7
|
|
|
8
8
|
const object = load.sync(path.resolve(__dirname, 'schema.yaml'))
|
|
9
|
-
const schema =
|
|
9
|
+
const schema = schemas.schema(object)
|
|
10
10
|
|
|
11
11
|
const validate = (manifest) => {
|
|
12
12
|
const error = schema.fit(manifest)
|
|
13
13
|
|
|
14
|
-
if (error) throw
|
|
14
|
+
if (error) throw error
|
|
15
15
|
|
|
16
16
|
if (manifest.events !== undefined) events(manifest)
|
|
17
17
|
if (manifest.receivers !== undefined) receivers(manifest)
|
|
@@ -32,11 +32,11 @@ const receivers = (manifest) => {
|
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
if (!TYPES.has(manifest.operations[receiver.operation].type)) {
|
|
35
|
-
throw new Error(`Receiver '${locator}' must refer to an operation of
|
|
35
|
+
throw new Error(`Receiver '${locator}' must refer to an operation of the allowed types: ${Array.from(TYPES).join(', ')}`)
|
|
36
36
|
}
|
|
37
37
|
}
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
-
const TYPES = new Set(['transition', 'effect'])
|
|
40
|
+
const TYPES = new Set(['transition', 'assignment', 'effect', 'unmanaged'])
|
|
41
41
|
|
|
42
42
|
exports.validate = validate
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
+
const { resolve } = require('node:path')
|
|
4
|
+
|
|
3
5
|
const connectors = (context, extracted) => {
|
|
4
6
|
const connectors = {}
|
|
5
7
|
|
|
@@ -10,11 +12,14 @@ const connectors = (context, extracted) => {
|
|
|
10
12
|
|
|
11
13
|
for (const component of components) {
|
|
12
14
|
if (component.entity !== undefined) {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
+
let storage = component.entity.storage
|
|
16
|
+
|
|
17
|
+
if (storage[0] === '.') {
|
|
18
|
+
storage = resolve(component.path, storage)
|
|
15
19
|
}
|
|
16
20
|
|
|
17
|
-
connectors[
|
|
21
|
+
connectors[storage] ??= []
|
|
22
|
+
connectors[storage].push(component)
|
|
18
23
|
}
|
|
19
24
|
|
|
20
25
|
const bindings = new Set()
|
|
@@ -24,6 +24,15 @@ const resolve = (references, annotations) => {
|
|
|
24
24
|
}
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
+
for (const dependency of Object.keys(annotations)) {
|
|
28
|
+
const { module } = load(dependency)
|
|
29
|
+
|
|
30
|
+
if (!module.standalone || (dependency in dependencies))
|
|
31
|
+
continue
|
|
32
|
+
|
|
33
|
+
dependencies[dependency] = []
|
|
34
|
+
}
|
|
35
|
+
|
|
27
36
|
return dependencies
|
|
28
37
|
}
|
|
29
38
|
|
package/src/.context/schema.yaml
CHANGED
|
@@ -4,13 +4,15 @@ $id: https://schemas.toa.io/0.0.0/context
|
|
|
4
4
|
type: object
|
|
5
5
|
properties:
|
|
6
6
|
version:
|
|
7
|
-
|
|
7
|
+
type: string
|
|
8
8
|
name:
|
|
9
|
-
|
|
9
|
+
type: string
|
|
10
|
+
pattern: ^([a-zA-Z]+([_a-zA-Z0-9]*[a-zA-Z0-9]+)?)(-([a-zA-Z]+([_a-zA-Z0-9]*[a-zA-Z0-9]+)?))*$
|
|
10
11
|
description:
|
|
11
12
|
type: string
|
|
12
13
|
packages:
|
|
13
14
|
type: string
|
|
15
|
+
default: components/*
|
|
14
16
|
build:
|
|
15
17
|
type: object
|
|
16
18
|
properties:
|
|
@@ -20,7 +22,7 @@ properties:
|
|
|
20
22
|
type: object
|
|
21
23
|
properties:
|
|
22
24
|
version:
|
|
23
|
-
|
|
25
|
+
type: string
|
|
24
26
|
registry:
|
|
25
27
|
type: string
|
|
26
28
|
format: uri
|
|
@@ -45,7 +47,7 @@ properties:
|
|
|
45
47
|
- linux/amd64
|
|
46
48
|
- linux/arm/v7
|
|
47
49
|
- linux/arm64
|
|
48
|
-
required: [base
|
|
50
|
+
# required: [base]
|
|
49
51
|
compositions:
|
|
50
52
|
type: array
|
|
51
53
|
minItems: 1
|
|
@@ -53,16 +55,18 @@ properties:
|
|
|
53
55
|
type: object
|
|
54
56
|
properties:
|
|
55
57
|
name:
|
|
56
|
-
|
|
58
|
+
type: string
|
|
59
|
+
pattern: ^[a-zA-Z]([a-zA-Z0-9]{1,31})?$
|
|
57
60
|
image:
|
|
58
61
|
type: string
|
|
59
62
|
components:
|
|
60
63
|
type: array
|
|
61
64
|
minItems: 1
|
|
62
65
|
items:
|
|
63
|
-
|
|
66
|
+
type: string
|
|
67
|
+
pattern: ^([a-zA-Z]+([_a-zA-Z0-9]*[a-zA-Z0-9]+)?)(\.([a-zA-Z]+([_a-zA-Z0-9]*[a-zA-Z0-9]+)?))$
|
|
64
68
|
required: [name, components]
|
|
65
69
|
annotations:
|
|
66
70
|
type: object
|
|
67
|
-
required: [runtime, name,
|
|
71
|
+
required: [runtime, name, registry]
|
|
68
72
|
additionalProperties: false
|
package/src/.context/validate.js
CHANGED
|
@@ -3,15 +3,12 @@
|
|
|
3
3
|
const { resolve } = require('node:path')
|
|
4
4
|
|
|
5
5
|
const { load } = require('@toa.io/yaml')
|
|
6
|
-
const
|
|
6
|
+
const schemas = require('@toa.io/schemas')
|
|
7
7
|
|
|
8
8
|
const path = resolve(__dirname, 'schema.yaml')
|
|
9
9
|
const object = load.sync(path)
|
|
10
|
-
const schema =
|
|
10
|
+
const schema = schemas.schema(object)
|
|
11
11
|
|
|
12
|
-
/**
|
|
13
|
-
* @param {toa.norm.context.Declaration} context
|
|
14
|
-
*/
|
|
15
12
|
const validate = (context) => {
|
|
16
13
|
schema.validate(context)
|
|
17
14
|
}
|
package/src/component.js
CHANGED
|
@@ -13,7 +13,6 @@ const {
|
|
|
13
13
|
collapse,
|
|
14
14
|
dereference,
|
|
15
15
|
defaults,
|
|
16
|
-
dependencies,
|
|
17
16
|
normalize,
|
|
18
17
|
extensions
|
|
19
18
|
} = require('./.component')
|
|
@@ -21,7 +20,7 @@ const {
|
|
|
21
20
|
const component = async (path) => {
|
|
22
21
|
const manifest = await load(path)
|
|
23
22
|
|
|
24
|
-
normalize(manifest)
|
|
23
|
+
normalize(manifest, path)
|
|
25
24
|
validate(manifest)
|
|
26
25
|
extensions(manifest)
|
|
27
26
|
|
|
@@ -30,21 +29,21 @@ const component = async (path) => {
|
|
|
30
29
|
return manifest
|
|
31
30
|
}
|
|
32
31
|
|
|
33
|
-
const load = async (path, base) => {
|
|
32
|
+
const load = async (path, base, proto = false) => {
|
|
34
33
|
if (base !== undefined) path = find(path, base, MANIFEST)
|
|
35
34
|
|
|
36
35
|
const file = join(path, MANIFEST)
|
|
37
|
-
const manifest =
|
|
36
|
+
const manifest = await yaml(file) ?? {}
|
|
38
37
|
|
|
39
38
|
manifest.path = path
|
|
40
39
|
|
|
41
|
-
defaults(manifest)
|
|
42
|
-
expand(manifest)
|
|
40
|
+
defaults(manifest, proto)
|
|
41
|
+
await expand(manifest)
|
|
43
42
|
|
|
44
43
|
await merge(path, manifest)
|
|
45
44
|
|
|
46
45
|
if (manifest.prototype !== null) {
|
|
47
|
-
const prototype = await load(manifest.prototype, path)
|
|
46
|
+
const prototype = await load(manifest.prototype, path, true)
|
|
48
47
|
|
|
49
48
|
collapse(manifest, prototype)
|
|
50
49
|
}
|
package/src/shortcuts.js
CHANGED
|
@@ -50,7 +50,9 @@ const SHORTCUTS = {
|
|
|
50
50
|
configuration: '@toa.io/extensions.configuration',
|
|
51
51
|
origins: '@toa.io/extensions.origins',
|
|
52
52
|
stash: '@toa.io/extensions.stash',
|
|
53
|
-
storages: '@toa.io/extensions.storages'
|
|
53
|
+
storages: '@toa.io/extensions.storages',
|
|
54
|
+
telemetry: '@toa.io/extensions.telemetry',
|
|
55
|
+
mail: '@toa.io/extensions.mail'
|
|
54
56
|
}
|
|
55
57
|
|
|
56
58
|
exports.recognize = recognize
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
// noinspection JSUnresolvedVariable
|
|
2
|
-
|
|
3
1
|
'use strict'
|
|
4
2
|
|
|
5
3
|
const clone = require('clone-deep')
|
|
@@ -20,13 +18,13 @@ it('should be ok', () => {
|
|
|
20
18
|
it('should provide error', () => {
|
|
21
19
|
manifest.foo = 'bar'
|
|
22
20
|
|
|
23
|
-
expect(() => validate(manifest)).toThrow(
|
|
21
|
+
expect(() => validate(manifest)).toThrow()
|
|
24
22
|
})
|
|
25
23
|
|
|
26
24
|
it('should not have additional properties', () => {
|
|
27
25
|
manifest.foo = 'bar'
|
|
28
26
|
|
|
29
|
-
expect(() => validate(manifest)).toThrow(
|
|
27
|
+
expect(() => validate(manifest)).toThrow()
|
|
30
28
|
})
|
|
31
29
|
|
|
32
30
|
describe('namespace', () => {
|
|
@@ -83,13 +81,13 @@ describe('entity', () => {
|
|
|
83
81
|
|
|
84
82
|
it('should not have additional properties', () => {
|
|
85
83
|
manifest.entity.foo = 'bar'
|
|
86
|
-
expect(() => validate(manifest)).toThrow(
|
|
84
|
+
expect(() => validate(manifest)).toThrow()
|
|
87
85
|
})
|
|
88
86
|
|
|
89
87
|
describe('schema', () => {
|
|
90
88
|
it('should be required', () => {
|
|
91
89
|
delete manifest.entity.schema
|
|
92
|
-
expect(() => validate(manifest)).toThrow(
|
|
90
|
+
expect(() => validate(manifest)).toThrow()
|
|
93
91
|
})
|
|
94
92
|
|
|
95
93
|
it('should be JSON schema object', () => {
|
|
@@ -99,7 +97,7 @@ describe('entity', () => {
|
|
|
99
97
|
|
|
100
98
|
it('should be JSON schema object of type object', () => {
|
|
101
99
|
manifest.entity.schema = { type: 'integer' }
|
|
102
|
-
expect(() => validate(manifest)).toThrow(/must be equal to constant
|
|
100
|
+
expect(() => validate(manifest)).toThrow(/must be equal to constant/)
|
|
103
101
|
|
|
104
102
|
manifest.entity.schema = {}
|
|
105
103
|
validate(manifest)
|
|
@@ -112,15 +110,18 @@ describe('entity', () => {
|
|
|
112
110
|
})
|
|
113
111
|
|
|
114
112
|
it('should allow default id', () => {
|
|
115
|
-
manifest.entity.schema.properties.id = {
|
|
113
|
+
manifest.entity.schema.properties.id = {
|
|
114
|
+
type: 'string',
|
|
115
|
+
pattern: '^[a-fA-F0-9]+$'
|
|
116
|
+
}
|
|
116
117
|
expect(() => validate(manifest)).not.toThrow()
|
|
117
118
|
})
|
|
118
119
|
})
|
|
119
120
|
|
|
120
|
-
describe('
|
|
121
|
+
describe('associated', () => {
|
|
121
122
|
it('should provide default', () => {
|
|
122
123
|
expect(() => validate(manifest)).not.toThrow()
|
|
123
|
-
expect(manifest.entity.
|
|
124
|
+
expect(manifest.entity.associated).toBe(false)
|
|
124
125
|
})
|
|
125
126
|
})
|
|
126
127
|
})
|
|
@@ -151,7 +152,7 @@ describe('operations', () => {
|
|
|
151
152
|
|
|
152
153
|
it('should not have additional properties', () => {
|
|
153
154
|
manifest.operations.get.foo = 'bar'
|
|
154
|
-
expect(() => validate(manifest)).toThrow(
|
|
155
|
+
expect(() => validate(manifest)).toThrow()
|
|
155
156
|
})
|
|
156
157
|
|
|
157
158
|
it('should have type (transition or observation)', () => {
|
|
@@ -173,8 +174,7 @@ describe('operations', () => {
|
|
|
173
174
|
})
|
|
174
175
|
|
|
175
176
|
it.each([
|
|
176
|
-
['computation', 'compute']
|
|
177
|
-
['effect', 'affect']
|
|
177
|
+
['computation', 'compute']
|
|
178
178
|
])('should set query: false for %s', async (_, operation) => {
|
|
179
179
|
validate(manifest)
|
|
180
180
|
|
|
@@ -247,7 +247,7 @@ describe('receivers', () => {
|
|
|
247
247
|
it('should throw if transition points to observation', () => {
|
|
248
248
|
manifest.receivers['foo.bar.happened'].operation = 'get'
|
|
249
249
|
|
|
250
|
-
expect(() => validate(manifest)).toThrow(/
|
|
250
|
+
expect(() => validate(manifest)).toThrow(/of the allowed types/)
|
|
251
251
|
})
|
|
252
252
|
|
|
253
253
|
it('should throw if source has a name `context`', async () => {
|
|
@@ -21,14 +21,6 @@ describe('runtime', () => {
|
|
|
21
21
|
expect(() => validate(context)).toThrow(/required/)
|
|
22
22
|
})
|
|
23
23
|
|
|
24
|
-
it('should require version as semver', () => {
|
|
25
|
-
delete context.runtime.version
|
|
26
|
-
expect(() => validate(context)).toThrow(/required/)
|
|
27
|
-
|
|
28
|
-
context.runtime.version = '.'
|
|
29
|
-
expect(() => validate(context)).toThrow(/pattern/)
|
|
30
|
-
})
|
|
31
|
-
|
|
32
24
|
it('should require registry to match uri format', () => {
|
|
33
25
|
context.runtime.registry = 'not-a-uri'
|
|
34
26
|
expect(() => validate(context)).toThrow(/must match format/)
|
|
@@ -53,12 +45,6 @@ describe('registry', () => {
|
|
|
53
45
|
expect(() => validate(context)).toThrow(/required property 'registry'/)
|
|
54
46
|
})
|
|
55
47
|
|
|
56
|
-
it('should require base', () => {
|
|
57
|
-
delete context.registry.base
|
|
58
|
-
|
|
59
|
-
expect(() => validate(context)).toThrow(/required property 'base'/)
|
|
60
|
-
})
|
|
61
|
-
|
|
62
48
|
it('should set default platforms', () => {
|
|
63
49
|
delete context.registry.platforms
|
|
64
50
|
|
|
@@ -80,9 +66,10 @@ it('should require name as label', () => {
|
|
|
80
66
|
expect(() => validate(context)).not.toThrow(/pattern/)
|
|
81
67
|
})
|
|
82
68
|
|
|
83
|
-
it('should
|
|
69
|
+
it('should set default packages location', () => {
|
|
84
70
|
delete context.packages
|
|
85
|
-
expect(() => validate(context)).toThrow(
|
|
71
|
+
expect(() => validate(context)).not.toThrow()
|
|
72
|
+
expect(context.packages).toBe('components/*')
|
|
86
73
|
})
|
|
87
74
|
|
|
88
75
|
it('should require registry url', () => {
|