@toa.io/norm 1.0.0-alpha.30 → 1.0.0-alpha.300
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 +124 -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 +5 -8
- 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 +38 -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 +10 -6
- package/src/.component/.normalize/receivers.js +4 -5
- package/src/.component/collapse.js +60 -9
- package/src/.component/defaults.js +16 -10
- package/src/.component/dependencies.js +14 -10
- package/src/.component/dereference.js +20 -18
- 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 -13
- package/src/.component/migrations.js +57 -0
- package/src/.component/normalize.js +10 -6
- package/src/.component/schema.yaml +137 -73
- package/src/.component/validate.js +37 -17
- package/src/.context/.dependencies/connectors.js +5 -9
- package/src/.context/.dependencies/describe.js +4 -5
- package/src/.context/.dependencies/extensions.js +93 -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 +43 -6
- package/src/.context/dereference.js +11 -6
- package/src/.context/evict.js +38 -0
- package/src/.context/expand.js +11 -6
- package/src/.context/index.js +8 -15
- package/src/.context/normalize.js +9 -7
- package/src/.context/schema.yaml +178 -11
- package/src/.context/validate.js +8 -14
- package/src/component.js +36 -17
- package/src/context.js +33 -14
- package/src/definition.js +56 -0
- package/src/entity.js +15 -0
- package/src/index.js +6 -9
- package/src/manifest.js +154 -0
- package/src/shortcuts.js +9 -14
- 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 +15 -24
- package/test/component/dereference.test.js +18 -8
- 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 +86 -28
- package/test/component/validate.fixtures.js +15 -13
- package/test/component/validate.test.js +180 -109
- 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 +72 -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 +143 -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 +126 -0
- package/test/shortcuts.fixtures.js +4 -9
- package/test/shortcuts.test.js +17 -15
- package/types/component.d.ts +23 -4
- package/types/context/declaration.d.ts +33 -3
- package/types/context.d.ts +19 -13
- package/types/entity.d.ts +7 -0
- package/types/index.d.ts +12 -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,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,17 +1,15 @@
|
|
|
1
|
-
|
|
1
|
+
import { merge } from '@toa.io/generic'
|
|
2
2
|
|
|
3
|
-
const
|
|
4
|
-
|
|
5
|
-
const dereference = (manifest) => {
|
|
3
|
+
export const dereference = (manifest) => {
|
|
6
4
|
// schemas
|
|
7
|
-
const
|
|
8
|
-
|
|
9
|
-
if (manifest.entity !== undefined)
|
|
10
|
-
schema(manifest.entity.schema, resolver)
|
|
5
|
+
const properties = manifest.entity?.properties
|
|
6
|
+
const resolver = createResolver(properties)
|
|
11
7
|
|
|
12
|
-
if (
|
|
13
|
-
|
|
8
|
+
if (properties !== undefined)
|
|
9
|
+
for (const [name, property] of Object.entries(properties))
|
|
10
|
+
properties[name] = schema(property, resolver)
|
|
14
11
|
|
|
12
|
+
if ('operations' in manifest) operations(manifest, resolver)
|
|
15
13
|
}
|
|
16
14
|
|
|
17
15
|
const createResolver = (properties) => (property) => {
|
|
@@ -22,10 +20,14 @@ const createResolver = (properties) => (property) => {
|
|
|
22
20
|
return properties[property]
|
|
23
21
|
}
|
|
24
22
|
|
|
25
|
-
function operations
|
|
23
|
+
function operations(manifest, resolver) {
|
|
26
24
|
for (const operation of Object.values(manifest.operations)) {
|
|
27
25
|
if (operation.input !== undefined) operation.input = schema(operation.input, resolver)
|
|
28
|
-
|
|
26
|
+
|
|
27
|
+
if (operation.output !== undefined)
|
|
28
|
+
if (Array.isArray(operation.output) && operation.output.length === 1)
|
|
29
|
+
operation.output = [schema(operation.output[0], resolver)]
|
|
30
|
+
else operation.output = schema(operation.output, resolver)
|
|
29
31
|
}
|
|
30
32
|
|
|
31
33
|
// forwarding
|
|
@@ -36,18 +38,19 @@ function operations (manifest, resolver) {
|
|
|
36
38
|
for (const operation of Object.values(manifest.operations)) {
|
|
37
39
|
delete operation.forwarded
|
|
38
40
|
}
|
|
39
|
-
|
|
40
41
|
}
|
|
41
42
|
|
|
42
43
|
const schema = (object, resolve) => {
|
|
43
44
|
if (object === undefined || object === null || typeof object !== 'object') return
|
|
44
|
-
if (object.type === 'string' && object.default?.[0] === '.')
|
|
45
|
+
if (object.type === 'string' && object.default?.[0] === '.')
|
|
46
|
+
return resolve(object.default.substring(1))
|
|
45
47
|
|
|
46
48
|
if (object.type === 'array') {
|
|
47
49
|
object.items = schema(object.items, resolve)
|
|
48
50
|
} else if (object.properties !== undefined) {
|
|
49
51
|
for (const [name, value] of Object.entries(object.properties)) {
|
|
50
|
-
if (value?.type === 'string' && value.default === '.')
|
|
52
|
+
if (value?.type === 'string' && value.default === '.')
|
|
53
|
+
object.properties[name] = resolve(name)
|
|
51
54
|
else object.properties[name] = schema(value, resolve)
|
|
52
55
|
}
|
|
53
56
|
}
|
|
@@ -58,7 +61,8 @@ const schema = (object, resolve) => {
|
|
|
58
61
|
const forward = (operation, operations) => {
|
|
59
62
|
const target = operations[operation.forward]
|
|
60
63
|
|
|
61
|
-
if (target === undefined)
|
|
64
|
+
if (target === undefined)
|
|
65
|
+
throw new Error(`Referenced operation '${operation.forward}' is not defined`)
|
|
62
66
|
|
|
63
67
|
if (target.forward !== undefined) {
|
|
64
68
|
if (target.forwarded !== true) forward(target, operations)
|
|
@@ -72,5 +76,3 @@ const forward = (operation, operations) => {
|
|
|
72
76
|
|
|
73
77
|
merge(operation, real)
|
|
74
78
|
}
|
|
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
|
-
|
|
2
|
-
|
|
3
|
-
const { merge } = require('@toa.io/generic')
|
|
1
|
+
import { merge } from '@toa.io/generic'
|
|
2
|
+
import { definition } from '../definition.js'
|
|
4
3
|
|
|
5
4
|
const bridge = 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,7 +19,7 @@ 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
|
merge(item, definition)
|
|
@@ -29,8 +29,7 @@ const define = async (root, manifest, property) => {
|
|
|
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,31 @@ 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 merge(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
|
+
}
|
|
72
|
+
|
|
73
|
+
export { bridge as merge }
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { basename, extname, resolve } from 'node:path'
|
|
2
|
+
import { readFile } from 'node:fs/promises'
|
|
3
|
+
|
|
4
|
+
import glob from 'fast-glob'
|
|
5
|
+
import { yaml } from '@toa.io/generic'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* The migrations a component declares, in the order they are applied. The file name without its
|
|
9
|
+
* extension is the migration's id, and sorting those ids is what orders them — so a name is
|
|
10
|
+
* written to sort: `0001-indexes`, then `0002-default-role`.
|
|
11
|
+
*
|
|
12
|
+
* What a file contains is the storage's business; this reads it and hands it over.
|
|
13
|
+
*/
|
|
14
|
+
export const migrations = async (root, manifest) => {
|
|
15
|
+
const paths = await glob(resolve(root, DIRECTORY, '*' + EXTENSIONS), GLOB)
|
|
16
|
+
|
|
17
|
+
if (paths.length === 0) return
|
|
18
|
+
|
|
19
|
+
if (manifest.entity === undefined)
|
|
20
|
+
throw new Error(`Component at '${root}' declares migrations but stores nothing`)
|
|
21
|
+
|
|
22
|
+
const ids = new Map()
|
|
23
|
+
|
|
24
|
+
// sorted, so the two files a conflict names are the same two on every machine
|
|
25
|
+
for (const path of paths.sort()) {
|
|
26
|
+
const id = basename(path, extname(path))
|
|
27
|
+
const found = ids.get(id)
|
|
28
|
+
|
|
29
|
+
if (found !== undefined)
|
|
30
|
+
throw new Error(
|
|
31
|
+
`Component at '${root}' has more than one ${DIRECTORY}/${id}: ` +
|
|
32
|
+
`${basename(found)} and ${basename(path)}`
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
ids.set(id, path)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
manifest.entity.migrations = await Promise.all(
|
|
39
|
+
Array.from(ids, async ([id, path]) => ({ id, steps: await read(path) }))
|
|
40
|
+
)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* A migration is a list of steps. A file that is not one is refused here rather than at the
|
|
45
|
+
* storage, where the component it belongs to is no longer known.
|
|
46
|
+
*/
|
|
47
|
+
async function read(path) {
|
|
48
|
+
const steps = yaml.load(await readFile(path, 'utf8'))
|
|
49
|
+
|
|
50
|
+
if (!Array.isArray(steps)) throw new Error(`Migration '${path}' is not a list of steps`)
|
|
51
|
+
|
|
52
|
+
return steps
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const DIRECTORY = 'migrations'
|
|
56
|
+
const EXTENSIONS = '.{yaml,yml,json}'
|
|
57
|
+
const GLOB = { onlyFiles: true, absolute: true }
|
|
@@ -1,11 +1,15 @@
|
|
|
1
|
-
|
|
1
|
+
import { entity, events, operations, receivers } from './.normalize/index.js'
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
/**
|
|
4
|
+
* What a manifest does not say but the runtime needs said. The schema states what a value may
|
|
5
|
+
* be; what it is where nothing was written is decided here, so that validating a manifest is
|
|
6
|
+
* reading it rather than completing it.
|
|
7
|
+
*/
|
|
8
|
+
export const normalize = async (component) => {
|
|
9
|
+
component.namespace ??= 'default'
|
|
4
10
|
|
|
5
|
-
|
|
11
|
+
entity(component)
|
|
6
12
|
operations(component)
|
|
7
|
-
events(component)
|
|
13
|
+
await events(component)
|
|
8
14
|
receivers(component)
|
|
9
15
|
}
|
|
10
|
-
|
|
11
|
-
exports.normalize = normalize
|