@toa.io/norm 1.0.0-alpha.284 → 1.0.0-alpha.286
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 +5 -5
- package/src/.component/.expand/bridge.js +1 -1
- package/src/.component/.expand/entity.js +1 -1
- package/src/.component/.expand/events.js +1 -1
- package/src/.component/.expand/extensions.js +1 -1
- package/src/.component/.expand/operations.js +1 -1
- package/src/.component/.expand/properties.js +1 -1
- package/src/.component/.expand/receivers.js +3 -2
- package/src/.component/.expand/version.js +11 -13
- package/src/.component/.normalize/entity.js +7 -0
- package/src/.component/.normalize/events.js +4 -1
- package/src/.component/.normalize/index.js +1 -0
- package/src/.component/.normalize/operations.js +9 -1
- package/src/.component/.normalize/receivers.js +4 -1
- package/src/.component/collapse.js +14 -6
- package/src/.component/defaults.js +3 -4
- package/src/.component/dependencies.js +7 -4
- package/src/.component/dereference.js +14 -13
- package/src/.component/expand.js +11 -2
- package/src/.component/extensions.js +2 -1
- package/src/.component/index.js +1 -0
- package/src/.component/merge.js +3 -5
- package/src/.component/migrations.js +57 -0
- package/src/.component/normalize.js +9 -1
- package/src/.component/schema.yaml +29 -39
- package/src/.component/validate.js +23 -3
- package/src/.context/.dependencies/connectors.js +3 -3
- package/src/.context/.dependencies/describe.js +4 -1
- package/src/.context/.dependencies/extensions.js +18 -13
- package/src/.context/.dependencies/load.js +6 -4
- package/src/.context/.dependencies/resolve.js +1 -1
- package/src/.context/dereference.js +3 -1
- package/src/.context/normalize.js +5 -0
- package/src/.context/schema.yaml +6 -4
- package/src/component.js +14 -5
- package/src/context.js +9 -5
- package/src/entity.js +15 -0
- package/src/index.js +1 -0
- package/src/shortcuts.js +0 -1
- package/test/component/collapse.fixtures.js +28 -34
- package/test/component/collapse.test.js +21 -0
- package/test/component/dependencies.test.js +6 -6
- package/test/component/dereference.fixtures.js +13 -17
- package/test/component/dereference.test.js +12 -3
- 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/migrations.test.js +62 -0
- package/test/component/normalize.test.js +51 -13
- package/test/component/validate.fixtures.js +14 -8
- package/test/component/validate.test.js +113 -59
- package/test/context/complete.test.js +5 -1
- package/test/context/dereference.test.js +3 -2
- package/test/context/expand.test.js +4 -2
- package/test/context/normalize.test.js +14 -1
- package/test/context/validate.test.js +40 -20
- package/test/shortcuts.test.js +2 -1
- package/types/component.d.ts +14 -3
- package/types/context/declaration.d.ts +1 -1
- package/types/context.d.ts +10 -9
- package/types/entity.d.ts +7 -0
- package/types/index.d.ts +1 -0
- package/CHANGELOG.md +0 -203
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.286",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Toa declarations normalization and validation",
|
|
6
6
|
"author": "temich <tema.gurtovoy@gmail.com>",
|
|
@@ -21,11 +21,11 @@
|
|
|
21
21
|
"test": "echo \"Error: run tests from root\" && exit 1"
|
|
22
22
|
},
|
|
23
23
|
"dependencies": {
|
|
24
|
-
"@toa.io/core": "1.0.0-alpha.
|
|
25
|
-
"@toa.io/generic": "1.0.0-alpha.
|
|
26
|
-
"@toa.io/schemas": "1.0.0-alpha.
|
|
24
|
+
"@toa.io/core": "1.0.0-alpha.286",
|
|
25
|
+
"@toa.io/generic": "1.0.0-alpha.286",
|
|
26
|
+
"@toa.io/schemas": "1.0.0-alpha.286",
|
|
27
27
|
"fast-glob": "3.3.3",
|
|
28
28
|
"js-yaml": "5.4.1"
|
|
29
29
|
},
|
|
30
|
-
"gitHead": "
|
|
30
|
+
"gitHead": "81f3d0688f17668b902be336d1749cc715edcdb6"
|
|
31
31
|
}
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { resolve } from '../../shortcuts.js'
|
|
2
2
|
|
|
3
|
-
export function receivers
|
|
3
|
+
export function receivers(manifest) {
|
|
4
4
|
if (manifest.receivers === undefined) return
|
|
5
5
|
|
|
6
6
|
for (const [locator, receiver] of Object.entries(manifest.receivers)) {
|
|
7
|
-
if (typeof receiver === 'string')
|
|
7
|
+
if (typeof receiver === 'string')
|
|
8
|
+
manifest.receivers[locator] = { operation: receiver }
|
|
8
9
|
|
|
9
10
|
if (receiver.binding !== undefined) receiver.binding = resolve(receiver.binding)
|
|
10
11
|
if (receiver.bridge !== undefined) receiver.bridge = resolve(receiver.bridge)
|
|
@@ -2,7 +2,7 @@ import { join } from 'node:path'
|
|
|
2
2
|
import { createHash } from 'node:crypto'
|
|
3
3
|
import fs from 'node:fs/promises'
|
|
4
4
|
|
|
5
|
-
export async function version
|
|
5
|
+
export async function version(manifest) {
|
|
6
6
|
manifest.version ??= await hash(manifest.path)
|
|
7
7
|
}
|
|
8
8
|
|
|
@@ -14,14 +14,13 @@ export async function version (manifest) {
|
|
|
14
14
|
* build writes (see `operations/src/deployment/images/image.js`), so hashing it would only
|
|
15
15
|
* retag an identical image after a local install.
|
|
16
16
|
*/
|
|
17
|
-
async function hash
|
|
17
|
+
async function hash(path) {
|
|
18
18
|
const files = (await list(path)).sort()
|
|
19
19
|
const digests = await Promise.all(files.map((file) => digest(join(path, file))))
|
|
20
20
|
const total = createHash('sha256')
|
|
21
21
|
|
|
22
22
|
// the path is part of it: moving a file changes the build even if no content did
|
|
23
|
-
for (let i = 0; i < files.length; i++)
|
|
24
|
-
total.update(files[i]).update(digests[i])
|
|
23
|
+
for (let i = 0; i < files.length; i++) total.update(files[i]).update(digests[i])
|
|
25
24
|
|
|
26
25
|
return total.digest('hex').slice(0, 8)
|
|
27
26
|
}
|
|
@@ -32,26 +31,25 @@ async function hash (path) {
|
|
|
32
31
|
* @param {string[]} [acc]
|
|
33
32
|
* @returns {Promise<string[]>} paths relative to `root`
|
|
34
33
|
*/
|
|
35
|
-
async function list
|
|
34
|
+
async function list(root, path = '', acc = []) {
|
|
36
35
|
const entries = await fs.readdir(join(root, path), { withFileTypes: true })
|
|
37
36
|
|
|
38
37
|
for (const entry of entries) {
|
|
39
|
-
if (EXCLUDED.has(entry.name))
|
|
40
|
-
continue
|
|
38
|
+
if (EXCLUDED.has(entry.name)) continue
|
|
41
39
|
|
|
42
40
|
const relative = path === '' ? entry.name : `${path}/${entry.name}`
|
|
43
41
|
|
|
44
|
-
if (entry.isDirectory())
|
|
45
|
-
|
|
46
|
-
else if (entry.isFile())
|
|
47
|
-
acc.push(relative)
|
|
42
|
+
if (entry.isDirectory()) await list(root, relative, acc)
|
|
43
|
+
else if (entry.isFile()) acc.push(relative)
|
|
48
44
|
}
|
|
49
45
|
|
|
50
46
|
return acc
|
|
51
47
|
}
|
|
52
48
|
|
|
53
|
-
async function digest
|
|
54
|
-
return createHash('sha256')
|
|
49
|
+
async function digest(path) {
|
|
50
|
+
return createHash('sha256')
|
|
51
|
+
.update(await fs.readFile(path))
|
|
52
|
+
.digest()
|
|
55
53
|
}
|
|
56
54
|
|
|
57
55
|
const EXCLUDED = new Set(['node_modules', '.git'])
|
|
@@ -5,11 +5,14 @@ export const events = async (component) => {
|
|
|
5
5
|
|
|
6
6
|
for (const event of Object.values(component.events)) {
|
|
7
7
|
if (event.binding === undefined) event.binding = binding
|
|
8
|
+
|
|
9
|
+
event.conditioned ??= false
|
|
10
|
+
event.subjective ??= false
|
|
8
11
|
}
|
|
9
12
|
}
|
|
10
13
|
|
|
11
14
|
/** The first binding that carries events, which only its module can say. */
|
|
12
|
-
async function asynchronous
|
|
15
|
+
async function asynchronous(bindings) {
|
|
13
16
|
for (const binding of bindings) {
|
|
14
17
|
const { properties } = await import(binding)
|
|
15
18
|
|
|
@@ -2,8 +2,16 @@ export const operations = (component) => {
|
|
|
2
2
|
if (component.operations === undefined) return
|
|
3
3
|
|
|
4
4
|
for (const [endpoint, operation] of Object.entries(component.operations)) {
|
|
5
|
-
|
|
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'
|
|
6
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 ??= {}
|
|
7
15
|
|
|
8
16
|
if (operation.bindings === undefined) operation.bindings = component.bindings
|
|
9
17
|
if (operation.bindings === null) operation.bindings = []
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export function receivers
|
|
1
|
+
export function receivers(component) {
|
|
2
2
|
if (component.receivers === undefined) return
|
|
3
3
|
|
|
4
4
|
const receivers = component.receivers
|
|
@@ -7,6 +7,9 @@ export function receivers (component) {
|
|
|
7
7
|
const segments = key.split('.')
|
|
8
8
|
const source = value.source ?? 'default'
|
|
9
9
|
|
|
10
|
+
value.conditioned ??= false
|
|
11
|
+
value.adaptive ??= false
|
|
12
|
+
|
|
10
13
|
if (source !== 'default') continue
|
|
11
14
|
if (segments.length === 3) continue // already with a namespace
|
|
12
15
|
|
|
@@ -12,7 +12,8 @@ export const collapse = (manifest, prototype) => {
|
|
|
12
12
|
if (manifest.operations === undefined) manifest.operations = {}
|
|
13
13
|
|
|
14
14
|
for (let [endpoint, operation] of operations) {
|
|
15
|
-
if (manifest.operations[endpoint] === undefined)
|
|
15
|
+
if (manifest.operations[endpoint] === undefined)
|
|
16
|
+
manifest.operations[endpoint] = {}
|
|
16
17
|
else {
|
|
17
18
|
const { virtual, ...real } = operation
|
|
18
19
|
|
|
@@ -24,7 +25,8 @@ export const collapse = (manifest, prototype) => {
|
|
|
24
25
|
merge(manifest.operations[endpoint], declaration)
|
|
25
26
|
|
|
26
27
|
if (bridge !== undefined) {
|
|
27
|
-
if (manifest.prototype.operations === undefined)
|
|
28
|
+
if (manifest.prototype.operations === undefined)
|
|
29
|
+
manifest.prototype.operations = {}
|
|
28
30
|
|
|
29
31
|
manifest.prototype.operations[endpoint] = { bridge }
|
|
30
32
|
}
|
|
@@ -34,16 +36,22 @@ export const collapse = (manifest, prototype) => {
|
|
|
34
36
|
|
|
35
37
|
const { entity, events, extensions } = prototype
|
|
36
38
|
|
|
37
|
-
if (
|
|
39
|
+
if (
|
|
40
|
+
manifest.entity?.properties?.id !== undefined &&
|
|
41
|
+
entity?.properties?.id !== undefined
|
|
42
|
+
) {
|
|
38
43
|
manifest.entity.custom = true
|
|
39
44
|
|
|
40
|
-
delete prototype.entity.
|
|
45
|
+
delete prototype.entity.properties.id
|
|
41
46
|
}
|
|
42
47
|
|
|
43
48
|
if (prototype.events !== undefined && manifest.events !== undefined)
|
|
44
49
|
for (const event of Object.keys(prototype.events))
|
|
45
|
-
if (event in manifest.events)
|
|
46
|
-
|
|
50
|
+
if (event in manifest.events) delete prototype.events[event]
|
|
51
|
+
|
|
52
|
+
// a migration is applied to a collection, and a prototype has none: what it declares belongs
|
|
53
|
+
// to the component that declared it, so it is not collapsed into this one
|
|
54
|
+
if (entity?.migrations !== undefined) delete entity.migrations
|
|
47
55
|
|
|
48
56
|
merge(manifest, { entity, events, extensions })
|
|
49
57
|
}
|
|
@@ -13,18 +13,17 @@ export const defaults = (manifest, proto) => {
|
|
|
13
13
|
if (manifest.entity.storage === null)
|
|
14
14
|
manifest.entity.storage = '@toa.io/storages.null'
|
|
15
15
|
} else {
|
|
16
|
-
if (manifest.prototype === undefined)
|
|
17
|
-
manifest.prototype = null
|
|
16
|
+
if (manifest.prototype === undefined) manifest.prototype = null
|
|
18
17
|
}
|
|
19
18
|
|
|
20
19
|
if (manifest.prototype === undefined) manifest.prototype = '@toa.io/prototype'
|
|
21
20
|
}
|
|
22
21
|
|
|
23
|
-
function protoName
|
|
22
|
+
function protoName(manifest) {
|
|
24
23
|
return 'proto' + hash(manifest.path)
|
|
25
24
|
}
|
|
26
25
|
|
|
27
|
-
function nameAfterDir
|
|
26
|
+
function nameAfterDir(manifest) {
|
|
28
27
|
const parts = manifest.path.split('/')
|
|
29
28
|
const dirname = parts[parts.length - 1]
|
|
30
29
|
const [name, namespace] = dirname.split('.').reverse()
|
|
@@ -9,20 +9,23 @@ const require = createRequire(import.meta.url)
|
|
|
9
9
|
* @param {toa.norm.Component} component
|
|
10
10
|
*/
|
|
11
11
|
export const dependencies = (component) => {
|
|
12
|
-
if ('entity' in component)
|
|
12
|
+
if ('entity' in component)
|
|
13
|
+
component.entity.storage = resolve(component.path, component.entity.storage)
|
|
13
14
|
}
|
|
14
15
|
|
|
15
|
-
function resolve
|
|
16
|
+
function resolve(root, reference) {
|
|
16
17
|
const paths = [root, import.meta.dirname]
|
|
17
18
|
const options = { paths }
|
|
18
19
|
|
|
19
20
|
let path
|
|
20
21
|
|
|
21
|
-
try {
|
|
22
|
+
try {
|
|
23
|
+
// as package
|
|
22
24
|
const packageJsonRef = join(reference, 'package.json')
|
|
23
25
|
|
|
24
26
|
path = require.resolve(packageJsonRef, options)
|
|
25
|
-
} catch {
|
|
27
|
+
} catch {
|
|
28
|
+
// as directory
|
|
26
29
|
path = require.resolve(reference, options)
|
|
27
30
|
}
|
|
28
31
|
|
|
@@ -2,14 +2,14 @@ import { merge } from '@toa.io/generic'
|
|
|
2
2
|
|
|
3
3
|
export const dereference = (manifest) => {
|
|
4
4
|
// schemas
|
|
5
|
-
const
|
|
5
|
+
const properties = manifest.entity?.properties
|
|
6
|
+
const resolver = createResolver(properties)
|
|
6
7
|
|
|
7
|
-
if (
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
if ('operations' in manifest)
|
|
11
|
-
operations(manifest, resolver)
|
|
8
|
+
if (properties !== undefined)
|
|
9
|
+
for (const [name, property] of Object.entries(properties))
|
|
10
|
+
properties[name] = schema(property, resolver)
|
|
12
11
|
|
|
12
|
+
if ('operations' in manifest) operations(manifest, resolver)
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
const createResolver = (properties) => (property) => {
|
|
@@ -20,15 +20,14 @@ const createResolver = (properties) => (property) => {
|
|
|
20
20
|
return properties[property]
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
-
function operations
|
|
23
|
+
function operations(manifest, resolver) {
|
|
24
24
|
for (const operation of Object.values(manifest.operations)) {
|
|
25
25
|
if (operation.input !== undefined) operation.input = schema(operation.input, resolver)
|
|
26
26
|
|
|
27
27
|
if (operation.output !== undefined)
|
|
28
28
|
if (Array.isArray(operation.output) && operation.output.length === 1)
|
|
29
29
|
operation.output = [schema(operation.output[0], resolver)]
|
|
30
|
-
else
|
|
31
|
-
operation.output = schema(operation.output, resolver)
|
|
30
|
+
else operation.output = schema(operation.output, resolver)
|
|
32
31
|
}
|
|
33
32
|
|
|
34
33
|
// forwarding
|
|
@@ -39,18 +38,19 @@ function operations (manifest, resolver) {
|
|
|
39
38
|
for (const operation of Object.values(manifest.operations)) {
|
|
40
39
|
delete operation.forwarded
|
|
41
40
|
}
|
|
42
|
-
|
|
43
41
|
}
|
|
44
42
|
|
|
45
43
|
const schema = (object, resolve) => {
|
|
46
44
|
if (object === undefined || object === null || typeof object !== 'object') return
|
|
47
|
-
if (object.type === 'string' && object.default?.[0] === '.')
|
|
45
|
+
if (object.type === 'string' && object.default?.[0] === '.')
|
|
46
|
+
return resolve(object.default.substring(1))
|
|
48
47
|
|
|
49
48
|
if (object.type === 'array') {
|
|
50
49
|
object.items = schema(object.items, resolve)
|
|
51
50
|
} else if (object.properties !== undefined) {
|
|
52
51
|
for (const [name, value] of Object.entries(object.properties)) {
|
|
53
|
-
if (value?.type === 'string' && value.default === '.')
|
|
52
|
+
if (value?.type === 'string' && value.default === '.')
|
|
53
|
+
object.properties[name] = resolve(name)
|
|
54
54
|
else object.properties[name] = schema(value, resolve)
|
|
55
55
|
}
|
|
56
56
|
}
|
|
@@ -61,7 +61,8 @@ const schema = (object, resolve) => {
|
|
|
61
61
|
const forward = (operation, operations) => {
|
|
62
62
|
const target = operations[operation.forward]
|
|
63
63
|
|
|
64
|
-
if (target === undefined)
|
|
64
|
+
if (target === undefined)
|
|
65
|
+
throw new Error(`Referenced operation '${operation.forward}' is not defined`)
|
|
65
66
|
|
|
66
67
|
if (target.forward !== undefined) {
|
|
67
68
|
if (target.forwarded !== true) forward(target, operations)
|
package/src/.component/expand.js
CHANGED
|
@@ -1,6 +1,15 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
entity,
|
|
3
|
+
bridge,
|
|
4
|
+
operations,
|
|
5
|
+
events,
|
|
6
|
+
receivers,
|
|
7
|
+
extensions,
|
|
8
|
+
properties,
|
|
9
|
+
version
|
|
10
|
+
} from './.expand/index.js'
|
|
2
11
|
|
|
3
|
-
export async function expand
|
|
12
|
+
export async function expand(manifest) {
|
|
4
13
|
entity(manifest)
|
|
5
14
|
bridge(manifest)
|
|
6
15
|
operations(manifest)
|
|
@@ -21,7 +21,8 @@ export const extensions = async (manifest) => {
|
|
|
21
21
|
if (extension.manifest !== undefined) {
|
|
22
22
|
declaration = extension.manifest(declaration, manifest)
|
|
23
23
|
|
|
24
|
-
if (declaration === undefined)
|
|
24
|
+
if (declaration === undefined)
|
|
25
|
+
throw new Error(`Extension '${reference}' hasn't returned manifest`)
|
|
25
26
|
}
|
|
26
27
|
|
|
27
28
|
extensions[key] = declaration
|
package/src/.component/index.js
CHANGED
|
@@ -4,6 +4,7 @@ export { dependencies } from './dependencies.js'
|
|
|
4
4
|
export { dereference } from './dereference.js'
|
|
5
5
|
export { expand } from './expand.js'
|
|
6
6
|
export { merge } from './merge.js'
|
|
7
|
+
export { migrations } from './migrations.js'
|
|
7
8
|
export { normalize } from './normalize.js'
|
|
8
9
|
export { validate } from './validate.js'
|
|
9
10
|
export { extensions } from './extensions.js'
|
package/src/.component/merge.js
CHANGED
|
@@ -28,8 +28,7 @@ const define = async (root, manifest, property) => {
|
|
|
28
28
|
// default bridge
|
|
29
29
|
const definition = await scan(manifest.bridge, root, property)
|
|
30
30
|
|
|
31
|
-
if (definition === undefined)
|
|
32
|
-
return
|
|
31
|
+
if (definition === undefined) return
|
|
33
32
|
|
|
34
33
|
const items = Object.entries(definition)
|
|
35
34
|
|
|
@@ -57,7 +56,7 @@ const define = async (root, manifest, property) => {
|
|
|
57
56
|
const cache = {}
|
|
58
57
|
|
|
59
58
|
// the promise is what is remembered, so a bridge is loaded once
|
|
60
|
-
function req
|
|
59
|
+
function req(mod) {
|
|
61
60
|
cache[mod] ??= import(mod)
|
|
62
61
|
|
|
63
62
|
return cache[mod]
|
|
@@ -66,8 +65,7 @@ function req (mod) {
|
|
|
66
65
|
const scan = async (bridge, root, property) => {
|
|
67
66
|
const { define } = await req(bridge)
|
|
68
67
|
|
|
69
|
-
if (property in define)
|
|
70
|
-
return define[property](root)
|
|
68
|
+
if (property in define) return define[property](root)
|
|
71
69
|
else return undefined
|
|
72
70
|
}
|
|
73
71
|
|
|
@@ -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,6 +1,14 @@
|
|
|
1
|
-
import { events, operations, receivers } from './.normalize/index.js'
|
|
1
|
+
import { entity, events, operations, receivers } from './.normalize/index.js'
|
|
2
2
|
|
|
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
|
+
*/
|
|
3
8
|
export const normalize = async (component) => {
|
|
9
|
+
component.namespace ??= 'default'
|
|
10
|
+
|
|
11
|
+
entity(component)
|
|
4
12
|
operations(component)
|
|
5
13
|
await events(component)
|
|
6
14
|
receivers(component)
|
|
@@ -51,7 +51,6 @@ properties:
|
|
|
51
51
|
namespace:
|
|
52
52
|
type: string
|
|
53
53
|
pattern: ^[a-zA-Z]([a-zA-Z0-9]{1,31})?$
|
|
54
|
-
default: "default"
|
|
55
54
|
not:
|
|
56
55
|
oneOf:
|
|
57
56
|
- const: "system"
|
|
@@ -72,43 +71,42 @@ properties:
|
|
|
72
71
|
properties:
|
|
73
72
|
storage:
|
|
74
73
|
type: string
|
|
75
|
-
|
|
76
|
-
schema:
|
|
77
|
-
$ref: https://json-schema.org/draft/2019-09/schema
|
|
74
|
+
properties:
|
|
78
75
|
type: object
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
76
|
+
# a system property is named like any other, so nothing is enumerated beside them
|
|
77
|
+
propertyNames:
|
|
78
|
+
$ref: "#/definitions/name"
|
|
79
|
+
additionalProperties:
|
|
80
|
+
$ref: https://json-schema.org/draft/2019-09/schema
|
|
81
|
+
required:
|
|
82
|
+
type: array
|
|
83
|
+
uniqueItems: true
|
|
84
|
+
items:
|
|
85
|
+
$ref: "#/definitions/name"
|
|
86
|
+
blank:
|
|
87
|
+
# what a record holds before anything is written to it; a system property is the
|
|
88
|
+
# runtime's to write, so none may be named here
|
|
90
89
|
type: object
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
90
|
+
propertyNames:
|
|
91
|
+
not:
|
|
92
|
+
enum: [id, VERSION, CREATED, UPDATED, DELETED]
|
|
93
|
+
migrations:
|
|
94
|
+
type: array
|
|
95
|
+
items:
|
|
96
|
+
type: object
|
|
97
|
+
properties:
|
|
98
|
+
id:
|
|
95
99
|
type: string
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
patternProperties:
|
|
102
|
-
".*":
|
|
103
|
-
type: string
|
|
104
|
-
enum: [asc, desc, hash, text]
|
|
100
|
+
# what a step means belongs to the storage, which is the only thing that reads it
|
|
101
|
+
steps:
|
|
102
|
+
type: array
|
|
103
|
+
required: [id, steps]
|
|
104
|
+
additionalProperties: false
|
|
105
105
|
associated:
|
|
106
106
|
type: boolean
|
|
107
|
-
default: false
|
|
108
107
|
custom:
|
|
109
108
|
type: boolean
|
|
110
|
-
|
|
111
|
-
required: [schema]
|
|
109
|
+
required: [properties]
|
|
112
110
|
additionalProperties: false
|
|
113
111
|
|
|
114
112
|
bindings:
|
|
@@ -155,7 +153,6 @@ properties:
|
|
|
155
153
|
$ref: https://json-schema.org/draft/2019-09/schema
|
|
156
154
|
output:
|
|
157
155
|
$ref: https://json-schema.org/draft/2019-09/schema
|
|
158
|
-
default: {}
|
|
159
156
|
errors:
|
|
160
157
|
# what a reply may carry as a code: the error schema allows either
|
|
161
158
|
type: array
|
|
@@ -228,10 +225,8 @@ properties:
|
|
|
228
225
|
properties:
|
|
229
226
|
scope:
|
|
230
227
|
const: none
|
|
231
|
-
default: none
|
|
232
228
|
query:
|
|
233
229
|
const: false
|
|
234
|
-
default: false
|
|
235
230
|
- if: # effect
|
|
236
231
|
properties:
|
|
237
232
|
type:
|
|
@@ -239,7 +234,6 @@ properties:
|
|
|
239
234
|
then:
|
|
240
235
|
properties:
|
|
241
236
|
scope:
|
|
242
|
-
default: none
|
|
243
237
|
enum: [object, objects, stream, none]
|
|
244
238
|
additionalProperties: false
|
|
245
239
|
additionalProperties: false
|
|
@@ -260,10 +254,8 @@ properties:
|
|
|
260
254
|
$ref: "#/definitions/binding"
|
|
261
255
|
conditioned:
|
|
262
256
|
type: boolean
|
|
263
|
-
default: false
|
|
264
257
|
subjective:
|
|
265
258
|
type: boolean
|
|
266
|
-
default: false
|
|
267
259
|
required: [bridge, path]
|
|
268
260
|
additionalProperties: false
|
|
269
261
|
|
|
@@ -287,10 +279,8 @@ properties:
|
|
|
287
279
|
type: string
|
|
288
280
|
conditioned:
|
|
289
281
|
type: boolean
|
|
290
|
-
default: false
|
|
291
282
|
adaptive:
|
|
292
283
|
type: boolean
|
|
293
|
-
default: false
|
|
294
284
|
arguments:
|
|
295
285
|
type: array
|
|
296
286
|
required: [operation]
|