@toa.io/norm 1.0.0-alpha.0 → 1.0.0-alpha.11
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 +7 -7
- package/src/.component/.expand/entity.js +1 -1
- package/src/.component/.expand/index.js +2 -0
- package/src/.component/.expand/version.js +45 -0
- package/src/.component/collapse.js +6 -2
- package/src/.component/defaults.js +10 -6
- package/src/.component/dereference.js +19 -11
- package/src/.component/expand.js +5 -2
- package/src/.component/merge.js +7 -1
- package/src/.component/schema.yaml +35 -6
- package/src/.context/.dependencies/connectors.js +11 -4
- package/src/.context/schema.yaml +4 -2
- package/src/component.js +2 -3
- package/src/shortcuts.js +1 -0
- package/test/component/collapse.test.js +0 -13
- package/test/component/expand.fixtures.js +1 -0
- package/test/component/validate.test.js +2 -2
- package/test/context/dereference.fixtures.js +1 -2
- package/test/context/validate.test.js +0 -8
- package/types/component.d.ts +3 -5
- package/types/context.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.11",
|
|
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,11 @@
|
|
|
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/schema": "1.0.0-alpha.
|
|
26
|
-
"@toa.io/schemas": "1.0.0-alpha.
|
|
27
|
-
"@toa.io/yaml": "1.0.0-alpha.
|
|
23
|
+
"@toa.io/core": "1.0.0-alpha.11",
|
|
24
|
+
"@toa.io/generic": "1.0.0-alpha.11",
|
|
25
|
+
"@toa.io/schema": "1.0.0-alpha.11",
|
|
26
|
+
"@toa.io/schemas": "1.0.0-alpha.11",
|
|
27
|
+
"@toa.io/yaml": "1.0.0-alpha.11"
|
|
28
28
|
},
|
|
29
|
-
"gitHead": "
|
|
29
|
+
"gitHead": "e343ac81eef12957cfa5e520119b1276b8ec0ad2"
|
|
30
30
|
}
|
|
@@ -5,7 +5,7 @@ const { expand } = require('@toa.io/schemas')
|
|
|
5
5
|
const { resolve } = require('../../shortcuts')
|
|
6
6
|
|
|
7
7
|
function entity (manifest) {
|
|
8
|
-
if (
|
|
8
|
+
if (!('entity' in manifest)) return
|
|
9
9
|
if ('schema' in manifest.entity) manifest.entity.schema = expand(manifest.entity.schema)
|
|
10
10
|
|
|
11
11
|
manifest.entity.storage = resolve(manifest.entity.storage)
|
|
@@ -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
|
|
@@ -34,10 +34,14 @@ const collapse = (manifest, prototype) => {
|
|
|
34
34
|
}
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
-
delete prototype.entity?.storage // ???
|
|
38
|
-
|
|
39
37
|
const { entity, events, extensions } = prototype
|
|
40
38
|
|
|
39
|
+
if (manifest.entity?.schema?.properties.id !== undefined && entity?.schema?.properties.id !== undefined) {
|
|
40
|
+
manifest.entity.custom = true
|
|
41
|
+
|
|
42
|
+
delete prototype.entity.schema.properties.id
|
|
43
|
+
}
|
|
44
|
+
|
|
41
45
|
merge(manifest, { entity, events, extensions })
|
|
42
46
|
}
|
|
43
47
|
|
|
@@ -1,19 +1,23 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
3
|
const { hash } = require('@toa.io/generic')
|
|
4
|
+
const assert = require('node:assert')
|
|
4
5
|
|
|
5
6
|
// these defaults are required before validation
|
|
6
7
|
const defaults = (manifest) => {
|
|
7
|
-
if (manifest.prototype === undefined) manifest.prototype = '@toa.io/prototype'
|
|
8
8
|
if (manifest.name === undefined) manifest.name = protoName(manifest)
|
|
9
9
|
if (manifest.bindings === undefined) manifest.bindings = ['@toa.io/bindings.amqp']
|
|
10
10
|
if (manifest.bridge === undefined) manifest.bridge = '@toa.io/bridges.node'
|
|
11
|
-
if (manifest.entity === null || manifest.entity === undefined) manifest.entity = { storage: null }
|
|
12
|
-
if (manifest.entity.storage === undefined) manifest.entity.storage = '@toa.io/storages.mongodb'
|
|
13
|
-
if (manifest.entity.storage === null) manifest.entity.storage = '@toa.io/storages.null'
|
|
14
11
|
|
|
15
|
-
|
|
16
|
-
|
|
12
|
+
if ('entity' in manifest) {
|
|
13
|
+
if (manifest.entity.storage === null)
|
|
14
|
+
manifest.entity.storage = '@toa.io/storages.null'
|
|
15
|
+
} else {
|
|
16
|
+
if (manifest.prototype === undefined)
|
|
17
|
+
manifest.prototype = null
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
if (manifest.prototype === undefined) manifest.prototype = '@toa.io/prototype'
|
|
17
21
|
}
|
|
18
22
|
|
|
19
23
|
function protoName (manifest) {
|
|
@@ -4,13 +4,28 @@ const { merge } = require('@toa.io/generic')
|
|
|
4
4
|
|
|
5
5
|
const dereference = (manifest) => {
|
|
6
6
|
// schemas
|
|
7
|
-
const
|
|
7
|
+
const resolver = createResolver(manifest.entity?.schema?.properties)
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
if (manifest.entity !== undefined)
|
|
10
|
+
schema(manifest.entity.schema, resolver)
|
|
10
11
|
|
|
12
|
+
if ('operations' in manifest)
|
|
13
|
+
operations(manifest, resolver)
|
|
14
|
+
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const createResolver = (properties) => (property) => {
|
|
18
|
+
if (properties?.[property] === undefined) {
|
|
19
|
+
throw new Error(`Referenced property '${property}' is not defined`)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return properties[property]
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function operations (manifest, resolver) {
|
|
11
26
|
for (const operation of Object.values(manifest.operations)) {
|
|
12
|
-
if (operation.input !== undefined) operation.input = schema(operation.input,
|
|
13
|
-
if (operation.output !== undefined) operation.output = schema(operation.output,
|
|
27
|
+
if (operation.input !== undefined) operation.input = schema(operation.input, resolver)
|
|
28
|
+
if (operation.output !== undefined) operation.output = schema(operation.output, resolver)
|
|
14
29
|
}
|
|
15
30
|
|
|
16
31
|
// forwarding
|
|
@@ -21,14 +36,7 @@ const dereference = (manifest) => {
|
|
|
21
36
|
for (const operation of Object.values(manifest.operations)) {
|
|
22
37
|
delete operation.forwarded
|
|
23
38
|
}
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
const resolve = (schema) => (property) => {
|
|
27
|
-
if (schema[property] === undefined) {
|
|
28
|
-
throw new Error(`Referenced property '${property}' is not defined`)
|
|
29
|
-
}
|
|
30
39
|
|
|
31
|
-
return schema[property]
|
|
32
40
|
}
|
|
33
41
|
|
|
34
42
|
const schema = (object, resolve) => {
|
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
|
package/src/.component/merge.js
CHANGED
|
@@ -28,6 +28,10 @@ const define = async (root, manifest, property) => {
|
|
|
28
28
|
|
|
29
29
|
// default bridge
|
|
30
30
|
const definition = await scan(manifest.bridge, root, property)
|
|
31
|
+
|
|
32
|
+
if (definition === undefined)
|
|
33
|
+
return
|
|
34
|
+
|
|
31
35
|
const items = Object.entries(definition)
|
|
32
36
|
|
|
33
37
|
if (items.length) {
|
|
@@ -49,7 +53,9 @@ const define = async (root, manifest, property) => {
|
|
|
49
53
|
const scan = async (bridge, root, property) => {
|
|
50
54
|
const { define } = require(bridge)
|
|
51
55
|
|
|
52
|
-
|
|
56
|
+
if (property in define)
|
|
57
|
+
return define[property](root)
|
|
58
|
+
else return undefined
|
|
53
59
|
}
|
|
54
60
|
|
|
55
61
|
exports.merge = bridge
|
|
@@ -2,6 +2,9 @@ $schema: https://json-schema.org/draft/2019-09/schema
|
|
|
2
2
|
$id: https://schemas.toa.io/0.0.0/manifest
|
|
3
3
|
|
|
4
4
|
definitions:
|
|
5
|
+
name:
|
|
6
|
+
type: string
|
|
7
|
+
pattern: ^[a-zA-Z]+([_a-zA-Z0-9]*[a-zA-Z0-9]+)?$
|
|
5
8
|
binding:
|
|
6
9
|
type: string
|
|
7
10
|
not:
|
|
@@ -20,7 +23,7 @@ properties:
|
|
|
20
23
|
operations:
|
|
21
24
|
type: object
|
|
22
25
|
propertyNames:
|
|
23
|
-
$ref: '
|
|
26
|
+
$ref: '#/definitions/name'
|
|
24
27
|
patternProperties:
|
|
25
28
|
'.*':
|
|
26
29
|
type: object
|
|
@@ -50,13 +53,20 @@ properties:
|
|
|
50
53
|
- const: 'system'
|
|
51
54
|
|
|
52
55
|
version:
|
|
53
|
-
|
|
56
|
+
type: string
|
|
57
|
+
|
|
58
|
+
build:
|
|
59
|
+
type: object
|
|
60
|
+
properties:
|
|
61
|
+
image:
|
|
62
|
+
type: string
|
|
54
63
|
|
|
55
64
|
entity:
|
|
56
65
|
type: object
|
|
57
66
|
properties:
|
|
58
67
|
storage:
|
|
59
68
|
type: string
|
|
69
|
+
default: '@toa.io/storages.mongodb'
|
|
60
70
|
schema:
|
|
61
71
|
$ref: 'definitions#/definitions/schema'
|
|
62
72
|
type: object
|
|
@@ -68,9 +78,28 @@ properties:
|
|
|
68
78
|
type: object
|
|
69
79
|
propertyNames:
|
|
70
80
|
oneOf:
|
|
71
|
-
- $ref: '
|
|
81
|
+
- $ref: '#/definitions/name'
|
|
72
82
|
- enum: [_version]
|
|
73
|
-
|
|
83
|
+
unique:
|
|
84
|
+
type: object
|
|
85
|
+
patternProperties:
|
|
86
|
+
'.*':
|
|
87
|
+
type: array
|
|
88
|
+
items:
|
|
89
|
+
type: string
|
|
90
|
+
index:
|
|
91
|
+
type: object
|
|
92
|
+
patternProperties:
|
|
93
|
+
'.*':
|
|
94
|
+
type: object
|
|
95
|
+
patternProperties:
|
|
96
|
+
'.*':
|
|
97
|
+
type: string
|
|
98
|
+
enum: [asc, desc, hash]
|
|
99
|
+
dependent:
|
|
100
|
+
type: boolean
|
|
101
|
+
default: false
|
|
102
|
+
custom:
|
|
74
103
|
type: boolean
|
|
75
104
|
default: false
|
|
76
105
|
required: [schema]
|
|
@@ -88,7 +117,7 @@ properties:
|
|
|
88
117
|
operations:
|
|
89
118
|
type: object
|
|
90
119
|
propertyNames:
|
|
91
|
-
$ref: '
|
|
120
|
+
$ref: '#/definitions/name'
|
|
92
121
|
patternProperties:
|
|
93
122
|
'.*':
|
|
94
123
|
type: object
|
|
@@ -100,7 +129,7 @@ properties:
|
|
|
100
129
|
concurrency:
|
|
101
130
|
enum: [none, retry]
|
|
102
131
|
forward:
|
|
103
|
-
$ref: '
|
|
132
|
+
$ref: '#/definitions/name'
|
|
104
133
|
bridge:
|
|
105
134
|
type: string
|
|
106
135
|
bindings:
|
|
@@ -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
|
|
|
@@ -9,11 +11,16 @@ const connectors = (context, extracted) => {
|
|
|
9
11
|
) ?? []
|
|
10
12
|
|
|
11
13
|
for (const component of components) {
|
|
12
|
-
if (
|
|
13
|
-
|
|
14
|
-
}
|
|
14
|
+
if (component.entity !== undefined) {
|
|
15
|
+
let storage = component.entity.storage
|
|
15
16
|
|
|
16
|
-
|
|
17
|
+
if (storage[0] === '.') {
|
|
18
|
+
storage = resolve(component.path, storage)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
connectors[storage] ??= []
|
|
22
|
+
connectors[storage].push(component)
|
|
23
|
+
}
|
|
17
24
|
|
|
18
25
|
const bindings = new Set()
|
|
19
26
|
|
package/src/.context/schema.yaml
CHANGED
|
@@ -4,7 +4,7 @@ $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
|
$ref: 'definitions#/definitions/label'
|
|
10
10
|
description:
|
|
@@ -20,7 +20,7 @@ properties:
|
|
|
20
20
|
type: object
|
|
21
21
|
properties:
|
|
22
22
|
version:
|
|
23
|
-
|
|
23
|
+
type: string
|
|
24
24
|
registry:
|
|
25
25
|
type: string
|
|
26
26
|
format: uri
|
|
@@ -54,6 +54,8 @@ properties:
|
|
|
54
54
|
properties:
|
|
55
55
|
name:
|
|
56
56
|
$ref: 'definitions#/definitions/token'
|
|
57
|
+
image:
|
|
58
|
+
type: string
|
|
57
59
|
components:
|
|
58
60
|
type: array
|
|
59
61
|
minItems: 1
|
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')
|
|
@@ -39,7 +38,7 @@ const load = async (path, base) => {
|
|
|
39
38
|
manifest.path = path
|
|
40
39
|
|
|
41
40
|
defaults(manifest)
|
|
42
|
-
expand(manifest)
|
|
41
|
+
await expand(manifest)
|
|
43
42
|
|
|
44
43
|
await merge(path, manifest)
|
|
45
44
|
|
|
@@ -50,7 +49,7 @@ const load = async (path, base) => {
|
|
|
50
49
|
}
|
|
51
50
|
|
|
52
51
|
dereference(manifest)
|
|
53
|
-
dependencies(manifest)
|
|
52
|
+
// dependencies(manifest)
|
|
54
53
|
|
|
55
54
|
return manifest
|
|
56
55
|
}
|
package/src/shortcuts.js
CHANGED
|
@@ -41,6 +41,7 @@ const recognize = (shortcuts, object, group) => {
|
|
|
41
41
|
const SHORTCUTS = {
|
|
42
42
|
amqp: '@toa.io/bindings.amqp',
|
|
43
43
|
node: '@toa.io/bridges.node',
|
|
44
|
+
bash: '@toa.io/bridges.bash',
|
|
44
45
|
mongodb: '@toa.io/storages.mongodb',
|
|
45
46
|
sql: '@toa.io/storages.sql',
|
|
46
47
|
queues: '@toa.io/storages.queues',
|
|
@@ -30,19 +30,6 @@ it('should remove prototype property', () => {
|
|
|
30
30
|
})
|
|
31
31
|
|
|
32
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
33
|
it('should merge entity schema', () => {
|
|
47
34
|
const manifest = clone(samples.entity.manifest)
|
|
48
35
|
|
|
@@ -117,10 +117,10 @@ describe('entity', () => {
|
|
|
117
117
|
})
|
|
118
118
|
})
|
|
119
119
|
|
|
120
|
-
describe('
|
|
120
|
+
describe('dependent', () => {
|
|
121
121
|
it('should provide default', () => {
|
|
122
122
|
expect(() => validate(manifest)).not.toThrow()
|
|
123
|
-
expect(manifest.entity.
|
|
123
|
+
expect(manifest.entity.dependent).toBe(false)
|
|
124
124
|
})
|
|
125
125
|
})
|
|
126
126
|
})
|
|
@@ -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/)
|
package/types/component.d.ts
CHANGED
|
@@ -4,7 +4,7 @@ type Map = {
|
|
|
4
4
|
[id: string]: Component
|
|
5
5
|
}
|
|
6
6
|
|
|
7
|
-
type Operation = {
|
|
7
|
+
export type Operation = {
|
|
8
8
|
type: operations.type
|
|
9
9
|
scope?: operations.scope
|
|
10
10
|
bindings?: string[]
|
|
@@ -14,9 +14,7 @@ type Operation = {
|
|
|
14
14
|
query?: boolean
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
type Operations =
|
|
18
|
-
[key: string]: Operation
|
|
19
|
-
}
|
|
17
|
+
export type Operations = Record<string, Operation>
|
|
20
18
|
|
|
21
19
|
type Event = {
|
|
22
20
|
binding: string
|
|
@@ -39,7 +37,7 @@ type Receiver = {
|
|
|
39
37
|
type Entity = {
|
|
40
38
|
schema: Object
|
|
41
39
|
storage?: string
|
|
42
|
-
|
|
40
|
+
dependent?: boolean
|
|
43
41
|
}
|
|
44
42
|
|
|
45
43
|
type Declaration = {
|
package/types/context.d.ts
CHANGED