@toa.io/norm 1.0.0-alpha.33 → 1.0.0-alpha.331
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/CHANGELOG.md +389 -0
- package/package.json +9 -7
- package/src/.component/.expand/bridge.js +2 -6
- package/src/.component/.expand/entity.js +2 -9
- package/src/.component/.expand/events.js +2 -6
- package/src/.component/.expand/extensions.js +8 -9
- package/src/.component/.expand/index.js +7 -19
- package/src/.component/.expand/operations.js +2 -9
- package/src/.component/.expand/receivers.js +4 -7
- package/src/.component/.expand/version.js +72 -28
- package/src/.component/.normalize/entity.js +7 -0
- package/src/.component/.normalize/events.js +16 -4
- package/src/.component/.normalize/index.js +4 -9
- package/src/.component/.normalize/operations.js +13 -6
- package/src/.component/.normalize/receivers.js +4 -5
- package/src/.component/collapse.js +60 -9
- package/src/.component/defaults.js +5 -11
- package/src/.component/dependencies.js +14 -10
- package/src/.component/dereference.js +5 -49
- package/src/.component/expand.js +3 -9
- package/src/.component/extensions.js +9 -10
- package/src/.component/index.js +10 -21
- package/src/.component/merge.js +25 -15
- package/src/.component/migrations.js +57 -0
- package/src/.component/normalize.js +10 -6
- package/src/.component/schema.yaml +165 -70
- package/src/.component/validate.js +67 -17
- package/src/.context/.dependencies/connectors.js +5 -9
- package/src/.context/.dependencies/describe.js +4 -5
- package/src/.context/.dependencies/extensions.js +98 -20
- package/src/.context/.dependencies/index.js +3 -9
- package/src/.context/.dependencies/resolve.js +44 -7
- package/src/.context/complete.js +1 -5
- package/src/.context/converge.js +31 -0
- package/src/.context/dependencies.js +44 -6
- package/src/.context/dereference.js +11 -6
- package/src/.context/evict.js +40 -0
- package/src/.context/expand.js +37 -5
- package/src/.context/index.js +8 -15
- package/src/.context/normalize.js +9 -7
- package/src/.context/schema.yaml +183 -11
- package/src/.context/validate.js +11 -14
- package/src/component.js +33 -14
- package/src/context.js +47 -15
- package/src/definition.js +63 -0
- package/src/entity.js +15 -0
- package/src/index.js +7 -9
- package/src/manifest.js +159 -0
- package/src/map.js +26 -0
- package/src/shortcuts.js +12 -15
- package/test/component/collapse.fixtures.js +30 -39
- package/test/component/collapse.test.js +90 -10
- package/test/component/dependencies.test.js +16 -14
- package/test/component/dereference.fixtures.js +10 -98
- package/test/component/dereference.test.js +10 -14
- package/test/component/dummies/extension/index.js +1 -5
- package/test/component/dummies/migrations/conflicting/migrations/0001-one.yaml +2 -0
- package/test/component/dummies/migrations/conflicting/migrations/0001-one.yml +2 -0
- package/test/component/dummies/migrations/malformed/migrations/0001-one.yaml +1 -0
- package/test/component/dummies/migrations/ordered/migrations/0001-first.yml +4 -0
- package/test/component/dummies/migrations/ordered/migrations/0002-second.yaml +4 -0
- package/test/component/dummies/migrations/ordered/migrations/0010-third.json +1 -0
- package/test/component/dummies/migrations/stateless/migrations/0001-one.yaml +2 -0
- package/test/component/expand.fixtures.js +13 -13
- package/test/component/expand.test.js +17 -13
- package/test/component/migrations.test.js +62 -0
- package/test/component/normalize.fixtures.js +3 -7
- package/test/component/normalize.test.js +94 -28
- package/test/component/validate.fixtures.js +17 -15
- package/test/component/validate.test.js +311 -108
- package/test/context/complete.fixtures.js +2 -8
- package/test/context/complete.test.js +14 -8
- package/test/context/converge.test.js +57 -0
- package/test/context/dereference.fixtures.js +3 -9
- package/test/context/dereference.test.js +18 -7
- package/test/context/evict.fixtures.js +39 -0
- package/test/context/evict.test.js +73 -0
- package/test/context/expand.fixtures.js +1 -5
- package/test/context/expand.test.js +36 -8
- package/test/context/normalize.fixtures.js +2 -7
- package/test/context/normalize.test.js +33 -9
- package/test/context/validate.fixtures.js +1 -6
- package/test/context/validate.test.js +173 -35
- package/test/definition.test.js +35 -0
- package/test/dummies/bare/index.js +1 -0
- package/test/dummies/defined/definition.js +1 -0
- package/test/dummies/defined/index.js +1 -0
- package/test/dummies/defined/package.json +1 -0
- package/test/manifest.test.js +137 -0
- package/test/shortcuts.fixtures.js +4 -9
- package/test/shortcuts.test.js +17 -15
- package/test/system.test.js +31 -0
- package/types/component.d.ts +32 -5
- package/types/context/declaration.d.ts +34 -3
- package/types/context.d.ts +27 -13
- package/types/entity.d.ts +7 -0
- package/types/index.d.ts +18 -3
- package/src/.component/.expand/properties.js +0 -14
- package/src/.context/.dependencies/load.js +0 -36
- package/test/context/dependencies.load.test.js +0 -24
|
@@ -1,45 +1,89 @@
|
|
|
1
|
-
|
|
1
|
+
import { join } from 'node:path'
|
|
2
|
+
import { createHash } from 'node:crypto'
|
|
3
|
+
import fs from 'node:fs/promises'
|
|
4
|
+
import { glob } from 'tinyglobby'
|
|
2
5
|
|
|
3
|
-
|
|
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')
|
|
6
|
+
import { definition } from '../../definition.js'
|
|
8
7
|
|
|
9
|
-
async function version
|
|
10
|
-
manifest.version ??= await hash(manifest
|
|
8
|
+
export async function version(manifest) {
|
|
9
|
+
manifest.version ??= await hash(manifest)
|
|
11
10
|
}
|
|
12
11
|
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
/**
|
|
13
|
+
* Identifies a build of a component: it becomes the tag of its image, so it must be the
|
|
14
|
+
* same for the same sources and different for different ones — on any machine, and whichever
|
|
15
|
+
* build produced them.
|
|
16
|
+
*
|
|
17
|
+
* What a build leaves out cannot change it: the bridge states what no build ships, a component
|
|
18
|
+
* adds to that with `ignore` and puts one back with `!`, and `files` names what its version is
|
|
19
|
+
* made of where a build copies only some of it. Its manifest is always one of them, because
|
|
20
|
+
* what it declares is what a caller is held to.
|
|
21
|
+
*/
|
|
22
|
+
async function hash(manifest) {
|
|
23
|
+
const files = (await list(manifest)).sort()
|
|
24
|
+
const digests = await Promise.all(
|
|
25
|
+
files.map((file) => digest(join(manifest.path, file)))
|
|
26
|
+
)
|
|
27
|
+
const total = createHash('sha256')
|
|
15
28
|
|
|
16
|
-
|
|
29
|
+
// the path is part of it: moving a file changes the build even if no content did
|
|
30
|
+
for (let i = 0; i < files.length; i++) total.update(files[i]).update(digests[i])
|
|
31
|
+
|
|
32
|
+
return total.digest('hex').slice(0, 8)
|
|
17
33
|
}
|
|
18
34
|
|
|
19
35
|
/**
|
|
20
|
-
* @param {
|
|
21
|
-
* @
|
|
36
|
+
* @param {toa.norm.Component} manifest
|
|
37
|
+
* @returns {Promise<string[]>} paths relative to the component
|
|
22
38
|
*/
|
|
23
|
-
async function
|
|
24
|
-
const
|
|
39
|
+
async function list(manifest) {
|
|
40
|
+
const files = await glob(patterns(manifest.files), {
|
|
41
|
+
cwd: manifest.path,
|
|
42
|
+
dot: true,
|
|
43
|
+
onlyFiles: true,
|
|
44
|
+
expandDirectories: false,
|
|
45
|
+
// sources a component keeps outside its own directory are its own: what a link points to is
|
|
46
|
+
// hashed as the file it stands for, so changing it changes the version
|
|
47
|
+
followSymbolicLinks: true,
|
|
48
|
+
ignore: await ignore(manifest)
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
// what a component declares is what a caller is held to, so its manifest is always one of them
|
|
52
|
+
if (!files.includes(MANIFEST) && (await exists(join(manifest.path, MANIFEST))))
|
|
53
|
+
files.push(MANIFEST)
|
|
25
54
|
|
|
26
|
-
|
|
27
|
-
|
|
55
|
+
return files
|
|
56
|
+
}
|
|
28
57
|
|
|
29
|
-
|
|
58
|
+
/** A path names what is under it, and a pattern is taken as it is. */
|
|
59
|
+
function patterns(files = ['**']) {
|
|
60
|
+
return files.flatMap((file) => (file.includes('*') ? [file] : [file, file + '/**']))
|
|
61
|
+
}
|
|
30
62
|
|
|
31
|
-
|
|
32
|
-
}
|
|
63
|
+
async function ignore(manifest) {
|
|
64
|
+
const { module } = await definition(manifest.bridge)
|
|
65
|
+
const stated = manifest.ignore ?? []
|
|
66
|
+
const back = new Set(
|
|
67
|
+
stated.filter((pattern) => pattern[0] === '!').map((pattern) => pattern.slice(1))
|
|
68
|
+
)
|
|
33
69
|
|
|
34
|
-
|
|
35
|
-
|
|
70
|
+
return [
|
|
71
|
+
...(module.ignore ?? []).filter((pattern) => !back.has(pattern)),
|
|
72
|
+
...stated.filter((pattern) => pattern[0] !== '!')
|
|
73
|
+
]
|
|
74
|
+
}
|
|
36
75
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
76
|
+
async function exists(path) {
|
|
77
|
+
return await fs
|
|
78
|
+
.access(path)
|
|
79
|
+
.then(() => true)
|
|
80
|
+
.catch(() => false)
|
|
81
|
+
}
|
|
40
82
|
|
|
41
|
-
|
|
42
|
-
|
|
83
|
+
async function digest(path) {
|
|
84
|
+
return createHash('sha256')
|
|
85
|
+
.update(await fs.readFile(path))
|
|
86
|
+
.digest()
|
|
43
87
|
}
|
|
44
88
|
|
|
45
|
-
|
|
89
|
+
const MANIFEST = 'manifest.toa.yaml'
|
|
@@ -1,13 +1,25 @@
|
|
|
1
|
-
|
|
1
|
+
import { definition } from '../../definition.js'
|
|
2
2
|
|
|
3
|
-
const events = (component) => {
|
|
3
|
+
export const events = async (component) => {
|
|
4
4
|
if (component.events === undefined) return
|
|
5
5
|
|
|
6
|
-
const binding = component.bindings
|
|
6
|
+
const binding = await asynchronous(component.bindings)
|
|
7
7
|
|
|
8
8
|
for (const event of Object.values(component.events)) {
|
|
9
9
|
if (event.binding === undefined) event.binding = binding
|
|
10
|
+
|
|
11
|
+
event.conditioned ??= false
|
|
12
|
+
event.subjective ??= false
|
|
10
13
|
}
|
|
11
14
|
}
|
|
12
15
|
|
|
13
|
-
|
|
16
|
+
/** The first binding that carries events, which only its module can say. */
|
|
17
|
+
async function asynchronous(bindings) {
|
|
18
|
+
for (const binding of bindings) {
|
|
19
|
+
const { properties } = (await definition(binding)).module
|
|
20
|
+
|
|
21
|
+
if (properties.async === true) return binding
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return undefined
|
|
25
|
+
}
|
|
@@ -1,9 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
const { receivers } = require('./receivers')
|
|
6
|
-
|
|
7
|
-
exports.events = events
|
|
8
|
-
exports.operations = operations
|
|
9
|
-
exports.receivers = receivers
|
|
1
|
+
export { entity } from './entity.js'
|
|
2
|
+
export { events } from './events.js'
|
|
3
|
+
export { operations } from './operations.js'
|
|
4
|
+
export { receivers } from './receivers.js'
|
|
@@ -1,16 +1,23 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
const operations = (component) => {
|
|
1
|
+
export const operations = (component) => {
|
|
4
2
|
if (component.operations === undefined) return
|
|
5
3
|
|
|
6
4
|
for (const [endpoint, operation] of Object.entries(component.operations)) {
|
|
7
|
-
|
|
5
|
+
// what an operation of this type acquires, where it did not say
|
|
6
|
+
if (operation.type === 'computation' || operation.type === 'unmanaged') {
|
|
7
|
+
operation.scope = 'none'
|
|
8
8
|
operation.query = false
|
|
9
|
+
} else if (operation.type === 'effect') operation.scope ??= 'none'
|
|
10
|
+
|
|
11
|
+
if (operation.scope === 'none') operation.query = false
|
|
12
|
+
|
|
13
|
+
// an operation that states no output states an empty schema, which every reply fits
|
|
14
|
+
operation.output ??= {}
|
|
15
|
+
|
|
16
|
+
// a bridge says an algorithm takes no input with `null`, which the manifest states as none
|
|
17
|
+
if (operation.input === null) delete operation.input
|
|
9
18
|
|
|
10
19
|
if (operation.bindings === undefined) operation.bindings = component.bindings
|
|
11
20
|
if (operation.bindings === null) operation.bindings = []
|
|
12
21
|
if (operation.virtual === true) delete component.operations[endpoint]
|
|
13
22
|
}
|
|
14
23
|
}
|
|
15
|
-
|
|
16
|
-
exports.operations = operations
|
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
function receivers (component) {
|
|
1
|
+
export function receivers(component) {
|
|
4
2
|
if (component.receivers === undefined) return
|
|
5
3
|
|
|
6
4
|
const receivers = component.receivers
|
|
@@ -9,6 +7,9 @@ function receivers (component) {
|
|
|
9
7
|
const segments = key.split('.')
|
|
10
8
|
const source = value.source ?? 'default'
|
|
11
9
|
|
|
10
|
+
value.conditioned ??= false
|
|
11
|
+
value.adaptive ??= false
|
|
12
|
+
|
|
12
13
|
if (source !== 'default') continue
|
|
13
14
|
if (segments.length === 3) continue // already with a namespace
|
|
14
15
|
|
|
@@ -18,5 +19,3 @@ function receivers (component) {
|
|
|
18
19
|
receivers[newKey] = value
|
|
19
20
|
}
|
|
20
21
|
}
|
|
21
|
-
|
|
22
|
-
exports.receivers = receivers
|
|
@@ -1,8 +1,6 @@
|
|
|
1
|
-
|
|
1
|
+
import { merge } from '@toa.io/generic'
|
|
2
2
|
|
|
3
|
-
const
|
|
4
|
-
|
|
5
|
-
const collapse = (manifest, prototype) => {
|
|
3
|
+
export const collapse = (manifest, prototype) => {
|
|
6
4
|
delete manifest.prototype
|
|
7
5
|
|
|
8
6
|
if (prototype.operations) {
|
|
@@ -14,7 +12,8 @@ const collapse = (manifest, prototype) => {
|
|
|
14
12
|
if (manifest.operations === undefined) manifest.operations = {}
|
|
15
13
|
|
|
16
14
|
for (let [endpoint, operation] of operations) {
|
|
17
|
-
if (manifest.operations[endpoint] === undefined)
|
|
15
|
+
if (manifest.operations[endpoint] === undefined)
|
|
16
|
+
manifest.operations[endpoint] = {}
|
|
18
17
|
else {
|
|
19
18
|
const { virtual, ...real } = operation
|
|
20
19
|
|
|
@@ -26,7 +25,8 @@ const collapse = (manifest, prototype) => {
|
|
|
26
25
|
merge(manifest.operations[endpoint], declaration)
|
|
27
26
|
|
|
28
27
|
if (bridge !== undefined) {
|
|
29
|
-
if (manifest.prototype.operations === undefined)
|
|
28
|
+
if (manifest.prototype.operations === undefined)
|
|
29
|
+
manifest.prototype.operations = {}
|
|
30
30
|
|
|
31
31
|
manifest.prototype.operations[endpoint] = { bridge }
|
|
32
32
|
}
|
|
@@ -36,13 +36,64 @@ const collapse = (manifest, prototype) => {
|
|
|
36
36
|
|
|
37
37
|
const { entity, events, extensions } = prototype
|
|
38
38
|
|
|
39
|
-
if (
|
|
39
|
+
if (
|
|
40
|
+
manifest.entity?.properties?.id !== undefined &&
|
|
41
|
+
entity?.properties?.id !== undefined
|
|
42
|
+
) {
|
|
40
43
|
manifest.entity.custom = true
|
|
41
44
|
|
|
42
|
-
delete prototype.entity.
|
|
45
|
+
delete prototype.entity.properties.id
|
|
43
46
|
}
|
|
44
47
|
|
|
48
|
+
/*
|
|
49
|
+
* `id` is the only one a component may state for itself. The rest are written by the runtime
|
|
50
|
+
* and read by it, so one redeclared here would be merged over what the prototype says and
|
|
51
|
+
* change what the record holds, or what a criterion against it compares — without the
|
|
52
|
+
* component that wrote it having anything to do with either.
|
|
53
|
+
*/
|
|
54
|
+
for (const name of SYSTEM)
|
|
55
|
+
if (
|
|
56
|
+
manifest.entity?.properties?.[name] !== undefined &&
|
|
57
|
+
entity?.properties?.[name] !== undefined
|
|
58
|
+
)
|
|
59
|
+
throw new Error(`System property '${name}' cannot be overridden`)
|
|
60
|
+
|
|
61
|
+
if (prototype.events !== undefined && manifest.events !== undefined)
|
|
62
|
+
for (const event of Object.keys(prototype.events))
|
|
63
|
+
if (event in manifest.events) delete prototype.events[event]
|
|
64
|
+
|
|
65
|
+
if (entity?.migrations !== undefined) inherit(manifest, prototype)
|
|
66
|
+
|
|
45
67
|
merge(manifest, { entity, events, extensions })
|
|
46
68
|
}
|
|
47
69
|
|
|
48
|
-
|
|
70
|
+
/**
|
|
71
|
+
* What a prototype declares is in every record of its descendants, so its migrations are applied
|
|
72
|
+
* to each descendant's collection — ahead of the descendant's own, which are written against
|
|
73
|
+
* what they leave. An inherited one is recorded under the prototype's name, so that it collides
|
|
74
|
+
* with nothing the component names itself; one inherited from further up already carries its
|
|
75
|
+
* name and is passed on as it is.
|
|
76
|
+
*/
|
|
77
|
+
function inherit(manifest, prototype) {
|
|
78
|
+
const inherited = prototype.entity.migrations.map((migration) =>
|
|
79
|
+
migration.prototype === undefined
|
|
80
|
+
? {
|
|
81
|
+
...migration,
|
|
82
|
+
id: `${prototype.name}:${migration.id}`,
|
|
83
|
+
prototype: prototype.name
|
|
84
|
+
}
|
|
85
|
+
: migration
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
const own = manifest.entity?.migrations ?? []
|
|
89
|
+
|
|
90
|
+
if (manifest.entity === undefined) manifest.entity = {}
|
|
91
|
+
|
|
92
|
+
manifest.entity.migrations = [...inherited, ...own]
|
|
93
|
+
|
|
94
|
+
// merged below by `merge`, which would append them a second time
|
|
95
|
+
delete prototype.entity.migrations
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** What the runtime writes into every record. `id` is not among them: a component may own it. */
|
|
99
|
+
const SYSTEM = ['VERSION', 'CREATED', 'UPDATED', 'DELETED', 'REGION']
|
|
@@ -1,10 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
const { hash } = require('@toa.io/generic')
|
|
4
|
-
const assert = require('node:assert')
|
|
1
|
+
import { hash } from '@toa.io/generic'
|
|
5
2
|
|
|
6
3
|
// these defaults are required before validation
|
|
7
|
-
const defaults = (manifest, proto) => {
|
|
4
|
+
export const defaults = (manifest, proto) => {
|
|
8
5
|
if (manifest.name === undefined)
|
|
9
6
|
if (proto) manifest.name = protoName(manifest)
|
|
10
7
|
else nameAfterDir(manifest)
|
|
@@ -16,18 +13,17 @@ const defaults = (manifest, proto) => {
|
|
|
16
13
|
if (manifest.entity.storage === null)
|
|
17
14
|
manifest.entity.storage = '@toa.io/storages.null'
|
|
18
15
|
} else {
|
|
19
|
-
if (manifest.prototype === undefined)
|
|
20
|
-
manifest.prototype = null
|
|
16
|
+
if (manifest.prototype === undefined) manifest.prototype = null
|
|
21
17
|
}
|
|
22
18
|
|
|
23
19
|
if (manifest.prototype === undefined) manifest.prototype = '@toa.io/prototype'
|
|
24
20
|
}
|
|
25
21
|
|
|
26
|
-
function protoName
|
|
22
|
+
function protoName(manifest) {
|
|
27
23
|
return 'proto' + hash(manifest.path)
|
|
28
24
|
}
|
|
29
25
|
|
|
30
|
-
function nameAfterDir
|
|
26
|
+
function nameAfterDir(manifest) {
|
|
31
27
|
const parts = manifest.path.split('/')
|
|
32
28
|
const dirname = parts[parts.length - 1]
|
|
33
29
|
const [name, namespace] = dirname.split('.').reverse()
|
|
@@ -35,5 +31,3 @@ function nameAfterDir (manifest) {
|
|
|
35
31
|
manifest.name = name
|
|
36
32
|
manifest.namespace = namespace
|
|
37
33
|
}
|
|
38
|
-
|
|
39
|
-
exports.defaults = defaults
|
|
@@ -1,29 +1,33 @@
|
|
|
1
|
-
|
|
1
|
+
import { createRequire } from 'node:module'
|
|
2
|
+
import { join, dirname } from 'node:path'
|
|
2
3
|
|
|
3
|
-
|
|
4
|
+
// import.meta.resolve takes no paths, and a storage is resolved against the
|
|
5
|
+
// component that names it
|
|
6
|
+
const require = createRequire(import.meta.url)
|
|
4
7
|
|
|
5
8
|
/**
|
|
6
9
|
* @param {toa.norm.Component} component
|
|
7
10
|
*/
|
|
8
|
-
const dependencies = (component) => {
|
|
9
|
-
if ('entity' in component)
|
|
11
|
+
export const dependencies = (component) => {
|
|
12
|
+
if ('entity' in component)
|
|
13
|
+
component.entity.storage = resolve(component.path, component.entity.storage)
|
|
10
14
|
}
|
|
11
15
|
|
|
12
|
-
function resolve
|
|
13
|
-
const paths = [root,
|
|
16
|
+
function resolve(root, reference) {
|
|
17
|
+
const paths = [root, import.meta.dirname]
|
|
14
18
|
const options = { paths }
|
|
15
19
|
|
|
16
20
|
let path
|
|
17
21
|
|
|
18
|
-
try {
|
|
22
|
+
try {
|
|
23
|
+
// as package
|
|
19
24
|
const packageJsonRef = join(reference, 'package.json')
|
|
20
25
|
|
|
21
26
|
path = require.resolve(packageJsonRef, options)
|
|
22
|
-
} catch {
|
|
27
|
+
} catch {
|
|
28
|
+
// as directory
|
|
23
29
|
path = require.resolve(reference, options)
|
|
24
30
|
}
|
|
25
31
|
|
|
26
32
|
return dirname(path)
|
|
27
33
|
}
|
|
28
|
-
|
|
29
|
-
exports.dependencies = dependencies
|
|
@@ -1,34 +1,8 @@
|
|
|
1
|
-
|
|
1
|
+
import { merge } from '@toa.io/generic'
|
|
2
2
|
|
|
3
|
-
const
|
|
3
|
+
export const dereference = (manifest) => {
|
|
4
|
+
if (!('operations' in manifest)) return
|
|
4
5
|
|
|
5
|
-
const dereference = (manifest) => {
|
|
6
|
-
// schemas
|
|
7
|
-
const resolver = createResolver(manifest.entity?.schema?.properties)
|
|
8
|
-
|
|
9
|
-
if (manifest.entity !== undefined)
|
|
10
|
-
schema(manifest.entity.schema, resolver)
|
|
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) {
|
|
26
|
-
for (const operation of Object.values(manifest.operations)) {
|
|
27
|
-
if (operation.input !== undefined) operation.input = schema(operation.input, resolver)
|
|
28
|
-
if (operation.output !== undefined) operation.output = schema(operation.output, resolver)
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
// forwarding
|
|
32
6
|
for (const operation of Object.values(manifest.operations)) {
|
|
33
7
|
if (operation.forward !== undefined) forward(operation, manifest.operations)
|
|
34
8
|
}
|
|
@@ -36,29 +10,13 @@ function operations (manifest, resolver) {
|
|
|
36
10
|
for (const operation of Object.values(manifest.operations)) {
|
|
37
11
|
delete operation.forwarded
|
|
38
12
|
}
|
|
39
|
-
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
const schema = (object, resolve) => {
|
|
43
|
-
if (object === undefined || object === null || typeof object !== 'object') return
|
|
44
|
-
if (object.type === 'string' && object.default?.[0] === '.') return resolve(object.default.substring(1))
|
|
45
|
-
|
|
46
|
-
if (object.type === 'array') {
|
|
47
|
-
object.items = schema(object.items, resolve)
|
|
48
|
-
} else if (object.properties !== undefined) {
|
|
49
|
-
for (const [name, value] of Object.entries(object.properties)) {
|
|
50
|
-
if (value?.type === 'string' && value.default === '.') object.properties[name] = resolve(name)
|
|
51
|
-
else object.properties[name] = schema(value, resolve)
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
return object
|
|
56
13
|
}
|
|
57
14
|
|
|
58
15
|
const forward = (operation, operations) => {
|
|
59
16
|
const target = operations[operation.forward]
|
|
60
17
|
|
|
61
|
-
if (target === undefined)
|
|
18
|
+
if (target === undefined)
|
|
19
|
+
throw new Error(`Referenced operation '${operation.forward}' is not defined`)
|
|
62
20
|
|
|
63
21
|
if (target.forward !== undefined) {
|
|
64
22
|
if (target.forwarded !== true) forward(target, operations)
|
|
@@ -72,5 +30,3 @@ const forward = (operation, operations) => {
|
|
|
72
30
|
|
|
73
31
|
merge(operation, real)
|
|
74
32
|
}
|
|
75
|
-
|
|
76
|
-
exports.dereference = dereference
|
package/src/.component/expand.js
CHANGED
|
@@ -1,26 +1,20 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
const {
|
|
1
|
+
import {
|
|
4
2
|
entity,
|
|
5
3
|
bridge,
|
|
6
4
|
operations,
|
|
7
5
|
events,
|
|
8
6
|
receivers,
|
|
9
7
|
extensions,
|
|
10
|
-
properties,
|
|
11
8
|
version
|
|
12
|
-
}
|
|
9
|
+
} from './.expand/index.js'
|
|
13
10
|
|
|
14
|
-
async function expand
|
|
11
|
+
export async function expand(manifest) {
|
|
15
12
|
entity(manifest)
|
|
16
13
|
bridge(manifest)
|
|
17
14
|
operations(manifest)
|
|
18
15
|
events(manifest)
|
|
19
16
|
receivers(manifest)
|
|
20
|
-
properties(manifest)
|
|
21
17
|
extensions(manifest)
|
|
22
18
|
|
|
23
19
|
await version(manifest)
|
|
24
20
|
}
|
|
25
|
-
|
|
26
|
-
exports.expand = expand
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
|
|
1
|
+
import { find } from '@toa.io/generic'
|
|
2
|
+
import { PREDEFINED } from '@toa.io/definitions'
|
|
3
|
+
import { resolve } from '../shortcuts.js'
|
|
4
|
+
import { definition } from '../definition.js'
|
|
2
5
|
|
|
3
|
-
const
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
const extensions = (manifest) => {
|
|
7
|
-
if (manifest.extensions === undefined) return
|
|
6
|
+
export const extensions = async (manifest) => {
|
|
7
|
+
manifest.extensions = Object.assign({}, PREDEFINED, manifest.extensions)
|
|
8
8
|
|
|
9
9
|
const extensions = manifest.extensions
|
|
10
10
|
|
|
@@ -14,12 +14,13 @@ const extensions = (manifest) => {
|
|
|
14
14
|
// relative path
|
|
15
15
|
if (key[0] === '.') key = find(key, manifest.path)
|
|
16
16
|
|
|
17
|
-
const extension =
|
|
17
|
+
const { module: extension } = await definition(key)
|
|
18
18
|
|
|
19
19
|
if (extension.manifest !== undefined) {
|
|
20
20
|
declaration = extension.manifest(declaration, manifest)
|
|
21
21
|
|
|
22
|
-
if (declaration === undefined)
|
|
22
|
+
if (declaration === undefined)
|
|
23
|
+
throw new Error(`Extension '${reference}' hasn't returned manifest`)
|
|
23
24
|
}
|
|
24
25
|
|
|
25
26
|
extensions[key] = declaration
|
|
@@ -28,5 +29,3 @@ const extensions = (manifest) => {
|
|
|
28
29
|
if (reference !== key) delete extensions[reference]
|
|
29
30
|
}
|
|
30
31
|
}
|
|
31
|
-
|
|
32
|
-
exports.extensions = extensions
|
package/src/.component/index.js
CHANGED
|
@@ -1,21 +1,10 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
const { extensions } = require('./extensions')
|
|
12
|
-
|
|
13
|
-
exports.collapse = collapse
|
|
14
|
-
exports.defaults = defaults
|
|
15
|
-
exports.dependencies = dependencies
|
|
16
|
-
exports.dereference = dereference
|
|
17
|
-
exports.expand = expand
|
|
18
|
-
exports.merge = merge
|
|
19
|
-
exports.normalize = normalize
|
|
20
|
-
exports.validate = validate
|
|
21
|
-
exports.extensions = extensions
|
|
1
|
+
export { collapse } from './collapse.js'
|
|
2
|
+
export { defaults } from './defaults.js'
|
|
3
|
+
export { dependencies } from './dependencies.js'
|
|
4
|
+
export { dereference } from './dereference.js'
|
|
5
|
+
export { expand } from './expand.js'
|
|
6
|
+
export { merge } from './merge.js'
|
|
7
|
+
export { migrations } from './migrations.js'
|
|
8
|
+
export { normalize } from './normalize.js'
|
|
9
|
+
export { validate } from './validate.js'
|
|
10
|
+
export { extensions } from './extensions.js'
|
package/src/.component/merge.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
|
|
1
|
+
import { merge as combine } from '@toa.io/generic'
|
|
2
|
+
import { definition } from '../definition.js'
|
|
2
3
|
|
|
3
|
-
const
|
|
4
|
-
|
|
5
|
-
const bridge = async (root, manifest) => {
|
|
4
|
+
export const merge = async (root, manifest) => {
|
|
6
5
|
await Promise.all([
|
|
7
6
|
define(root, manifest, 'operations'),
|
|
8
7
|
define(root, manifest, 'events'),
|
|
9
|
-
define(root, manifest, 'receivers')
|
|
8
|
+
define(root, manifest, 'receivers'),
|
|
9
|
+
define(root, manifest, 'guards')
|
|
10
10
|
])
|
|
11
11
|
}
|
|
12
12
|
|
|
@@ -19,18 +19,17 @@ const define = async (root, manifest, property) => {
|
|
|
19
19
|
if (item.bridge === undefined || item.bridge === manifest.bridge) continue // default bridge later
|
|
20
20
|
|
|
21
21
|
const bridge = item.bridge || manifest.bridge
|
|
22
|
-
const { define } =
|
|
22
|
+
const { define } = await bridged(bridge)
|
|
23
23
|
const definition = await define[singular](root, endpoint)
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
combine(item, definition)
|
|
26
26
|
}
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
// default bridge
|
|
30
30
|
const definition = await scan(manifest.bridge, root, property)
|
|
31
31
|
|
|
32
|
-
if (definition === undefined)
|
|
33
|
-
return
|
|
32
|
+
if (definition === undefined) return
|
|
34
33
|
|
|
35
34
|
const items = Object.entries(definition)
|
|
36
35
|
|
|
@@ -44,18 +43,29 @@ const define = async (root, manifest, property) => {
|
|
|
44
43
|
|
|
45
44
|
const declared = manifest[property][endpoint]
|
|
46
45
|
|
|
47
|
-
|
|
48
|
-
|
|
46
|
+
try {
|
|
47
|
+
if (declared === undefined) manifest[property][endpoint] = item
|
|
48
|
+
else combine(declared, item)
|
|
49
|
+
} catch (error) {
|
|
50
|
+
console.error(`Error merging ${singular} '${endpoint}' in ${root}`)
|
|
51
|
+
throw error
|
|
52
|
+
}
|
|
49
53
|
}
|
|
50
54
|
}
|
|
51
55
|
}
|
|
52
56
|
|
|
53
57
|
const scan = async (bridge, root, property) => {
|
|
54
|
-
const { define } =
|
|
58
|
+
const { define } = await bridged(bridge)
|
|
55
59
|
|
|
56
|
-
if (property in define)
|
|
57
|
-
return define[property](root)
|
|
60
|
+
if (property in define) return define[property](root)
|
|
58
61
|
else return undefined
|
|
59
62
|
}
|
|
60
63
|
|
|
61
|
-
|
|
64
|
+
async function bridged(reference) {
|
|
65
|
+
const { module } = await definition(reference)
|
|
66
|
+
|
|
67
|
+
if (module.define === undefined)
|
|
68
|
+
throw new Error(`Bridge '${reference}' defines nothing: it exports no 'define'`)
|
|
69
|
+
|
|
70
|
+
return module
|
|
71
|
+
}
|