@toa.io/norm 1.0.0-alpha.29 → 1.0.0-alpha.292
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 +45 -0
- package/package.json +10 -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 +8 -19
- package/src/.component/.expand/operations.js +2 -9
- package/src/.component/.expand/properties.js +2 -6
- 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 -7
- package/src/.component/extensions.js +13 -9
- 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 +108 -71
- 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 +75 -19
- package/src/.context/.dependencies/index.js +3 -9
- package/src/.context/.dependencies/resolve.js +23 -7
- package/src/.context/complete.js +1 -5
- package/src/.context/dependencies.js +2 -6
- package/src/.context/dereference.js +11 -6
- package/src/.context/expand.js +8 -6
- package/src/.context/index.js +6 -15
- package/src/.context/normalize.js +9 -7
- package/src/.context/schema.yaml +132 -11
- package/src/.context/validate.js +8 -14
- package/src/component.js +36 -17
- package/src/context.js +26 -14
- package/src/definition.js +56 -0
- package/src/entity.js +15 -0
- package/src/index.js +5 -9
- package/src/shortcuts.js +8 -13
- 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 +25 -10
- 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 +162 -109
- package/test/context/complete.fixtures.js +2 -8
- package/test/context/complete.test.js +14 -8
- package/test/context/dereference.fixtures.js +3 -9
- package/test/context/dereference.test.js +18 -7
- 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 +81 -37
- 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/shortcuts.fixtures.js +4 -9
- package/test/shortcuts.test.js +17 -15
- package/types/component.d.ts +17 -3
- package/types/context/declaration.d.ts +26 -3
- package/types/context.d.ts +15 -13
- package/types/entity.d.ts +7 -0
- package/types/index.d.ts +12 -3
- package/src/.context/.dependencies/load.js +0 -36
- package/test/context/dependencies.load.test.js +0 -24
|
@@ -1,25 +1,43 @@
|
|
|
1
|
-
|
|
1
|
+
import path from 'node:path'
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
import { readFileSync } from 'node:fs'
|
|
4
|
+
import { yaml } from '@toa.io/generic'
|
|
5
|
+
import * as schemas from '@toa.io/schemas'
|
|
6
|
+
import { definition } from '../definition.js'
|
|
4
7
|
|
|
5
|
-
const
|
|
6
|
-
|
|
8
|
+
const object = yaml.load(
|
|
9
|
+
readFileSync(path.resolve(import.meta.dirname, 'schema.yaml'), 'utf8')
|
|
10
|
+
)
|
|
11
|
+
const schema = schemas.schema(object)
|
|
7
12
|
|
|
8
|
-
const
|
|
9
|
-
const schema = new Schema(object)
|
|
10
|
-
|
|
11
|
-
const validate = (manifest) => {
|
|
13
|
+
export const validate = async (manifest) => {
|
|
12
14
|
const error = schema.fit(manifest)
|
|
13
15
|
|
|
14
|
-
if (error) throw
|
|
16
|
+
if (error) throw error
|
|
15
17
|
|
|
16
|
-
if (manifest.
|
|
18
|
+
if (manifest.entity !== undefined) entity(manifest)
|
|
19
|
+
if (manifest.events !== undefined) await events(manifest)
|
|
17
20
|
if (manifest.receivers !== undefined) receivers(manifest)
|
|
18
21
|
}
|
|
19
22
|
|
|
20
|
-
|
|
23
|
+
/** What only the entity's own declaration can answer: whether a name it uses is one it declares. */
|
|
24
|
+
const entity = (manifest) => {
|
|
25
|
+
const { properties, required, blank } = manifest.entity
|
|
26
|
+
|
|
27
|
+
for (const name of required ?? [])
|
|
28
|
+
if (properties[name] === undefined)
|
|
29
|
+
throw new Error(`Entity requires property '${name}', which is not defined`)
|
|
30
|
+
|
|
31
|
+
for (const name of Object.keys(blank ?? {}))
|
|
32
|
+
if (properties[name] === undefined)
|
|
33
|
+
throw new Error(`Entity blank names property '${name}', which is not defined`)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const events = async (manifest) => {
|
|
21
37
|
for (const [label, event] of Object.entries(manifest.events)) {
|
|
22
|
-
|
|
38
|
+
const { properties } = (await definition(event.binding)).module
|
|
39
|
+
|
|
40
|
+
if (properties.async !== true) {
|
|
23
41
|
throw new Error(`Event '${label}' binding '${event.binding}' is not async`)
|
|
24
42
|
}
|
|
25
43
|
}
|
|
@@ -28,15 +46,17 @@ const events = (manifest) => {
|
|
|
28
46
|
const receivers = (manifest) => {
|
|
29
47
|
for (const [locator, receiver] of Object.entries(manifest.receivers)) {
|
|
30
48
|
if (manifest.operations?.[receiver.operation] === undefined) {
|
|
31
|
-
throw new Error(
|
|
49
|
+
throw new Error(
|
|
50
|
+
`Receiver '${locator}' refers to undefined operation '${receiver.operation}'`
|
|
51
|
+
)
|
|
32
52
|
}
|
|
33
53
|
|
|
34
54
|
if (!TYPES.has(manifest.operations[receiver.operation].type)) {
|
|
35
|
-
throw new Error(
|
|
55
|
+
throw new Error(
|
|
56
|
+
`Receiver '${locator}' must refer to an operation of the allowed types: ${Array.from(TYPES).join(', ')}`
|
|
57
|
+
)
|
|
36
58
|
}
|
|
37
59
|
}
|
|
38
60
|
}
|
|
39
61
|
|
|
40
|
-
const TYPES = new Set(['transition', 'effect'])
|
|
41
|
-
|
|
42
|
-
exports.validate = validate
|
|
62
|
+
const TYPES = new Set(['transition', 'assignment', 'effect', 'unmanaged'])
|
|
@@ -1,14 +1,12 @@
|
|
|
1
|
-
|
|
1
|
+
import { resolve } from 'node:path'
|
|
2
2
|
|
|
3
|
-
const
|
|
4
|
-
|
|
5
|
-
const connectors = (context, extracted) => {
|
|
3
|
+
export const connectors = (context, extracted) => {
|
|
6
4
|
const connectors = {}
|
|
7
5
|
|
|
8
|
-
const components =
|
|
6
|
+
const components =
|
|
7
|
+
(context.components === undefined
|
|
9
8
|
? extracted
|
|
10
|
-
: context.components.concat(extracted)
|
|
11
|
-
) ?? []
|
|
9
|
+
: context.components.concat(extracted)) ?? []
|
|
12
10
|
|
|
13
11
|
for (const component of components) {
|
|
14
12
|
if (component.entity !== undefined) {
|
|
@@ -47,5 +45,3 @@ const connectors = (context, extracted) => {
|
|
|
47
45
|
|
|
48
46
|
return connectors
|
|
49
47
|
}
|
|
50
|
-
|
|
51
|
-
exports.connectors = connectors
|
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
'use strict'
|
|
2
|
-
|
|
3
1
|
/**
|
|
4
2
|
* @param {toa.norm.Component} component
|
|
5
3
|
* @param {Object} manifest
|
|
6
4
|
* @returns {toa.norm.context.dependencies.Instance}
|
|
7
5
|
*/
|
|
8
|
-
const describe = (component, manifest = undefined) => ({
|
|
9
|
-
|
|
10
|
-
|
|
6
|
+
export const describe = (component, manifest = undefined) => ({
|
|
7
|
+
locator: component.locator,
|
|
8
|
+
manifest
|
|
9
|
+
})
|
|
@@ -1,9 +1,22 @@
|
|
|
1
|
-
|
|
1
|
+
import { Locator } from '@toa.io/core'
|
|
2
|
+
import { component as load } from '../../component.js'
|
|
3
|
+
import { definition } from '../../definition.js'
|
|
2
4
|
|
|
3
|
-
const extensions = async (context) => {
|
|
5
|
+
export const extensions = async (context) => {
|
|
4
6
|
const extensions = {}
|
|
5
7
|
const components = context.components?.slice() ?? []
|
|
6
|
-
|
|
8
|
+
|
|
9
|
+
// an extension a composition runs may be referenced by no component of this context,
|
|
10
|
+
// and its own components must be extracted all the same
|
|
11
|
+
const declared = await extractDeclaredServices(context, extensions)
|
|
12
|
+
|
|
13
|
+
const extracted = declared.concat(
|
|
14
|
+
await extractExtensionComponents(
|
|
15
|
+
components.concat(declared),
|
|
16
|
+
extensions,
|
|
17
|
+
context.annotations
|
|
18
|
+
)
|
|
19
|
+
)
|
|
7
20
|
|
|
8
21
|
components.push(...extracted)
|
|
9
22
|
|
|
@@ -20,9 +33,28 @@ const extensions = async (context) => {
|
|
|
20
33
|
return { extensions, components: extracted }
|
|
21
34
|
}
|
|
22
35
|
|
|
23
|
-
async function
|
|
24
|
-
const
|
|
36
|
+
async function extractDeclaredServices(context, extensions) {
|
|
37
|
+
const extracted = []
|
|
25
38
|
|
|
39
|
+
for (const composition of context.compositions ?? [])
|
|
40
|
+
for (const reference of composition.services ?? []) {
|
|
41
|
+
if (reference in extensions) continue
|
|
42
|
+
|
|
43
|
+
try {
|
|
44
|
+
extracted.push(...(await extract(reference, extensions, context.annotations)))
|
|
45
|
+
} catch (e) {
|
|
46
|
+
throw new Error(
|
|
47
|
+
`Composition '${composition.name}' lists '${reference}', ` +
|
|
48
|
+
`which cannot be resolved: ${e.message}`,
|
|
49
|
+
{ cause: e }
|
|
50
|
+
)
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return extracted
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async function extractExtensionComponents(components, extensions, annotations) {
|
|
26
58
|
const extracted = []
|
|
27
59
|
|
|
28
60
|
for (const component of components) {
|
|
@@ -31,26 +63,50 @@ async function extractExtensionComponents (components, extensions) {
|
|
|
31
63
|
for (const reference of Object.keys(component.extensions)) {
|
|
32
64
|
if (reference in extensions) continue
|
|
33
65
|
|
|
34
|
-
|
|
66
|
+
extracted.push(...(await extract(reference, extensions, annotations)))
|
|
67
|
+
}
|
|
68
|
+
}
|
|
35
69
|
|
|
36
|
-
|
|
70
|
+
if (extracted.length === 0) return extracted
|
|
37
71
|
|
|
38
|
-
|
|
72
|
+
const deeper = await extractExtensionComponents(extracted, extensions, annotations)
|
|
39
73
|
|
|
40
|
-
|
|
41
|
-
|
|
74
|
+
return extracted.concat(deeper)
|
|
75
|
+
}
|
|
42
76
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
77
|
+
/**
|
|
78
|
+
* The components an extension contributes, if it contributes any.
|
|
79
|
+
*
|
|
80
|
+
* @param {string} reference
|
|
81
|
+
* @param {object} extensions
|
|
82
|
+
* @param {object} [annotations]
|
|
83
|
+
* @returns {Promise<Array<toa.norm.Component>>}
|
|
84
|
+
*/
|
|
85
|
+
async function extract(reference, extensions, annotations) {
|
|
86
|
+
extensions[reference] = []
|
|
47
87
|
|
|
48
|
-
|
|
49
|
-
return extracted
|
|
88
|
+
const { name, module: mod } = await definition(reference)
|
|
50
89
|
|
|
51
|
-
|
|
90
|
+
if (mod.components === undefined) return []
|
|
52
91
|
|
|
53
|
-
|
|
92
|
+
// the annotation decides whether an extension contributes components at all
|
|
93
|
+
const annotation = annotations?.[name]
|
|
94
|
+
const { manifests, paths } = mod.components(annotation)
|
|
95
|
+
|
|
96
|
+
// a definition carries the manifests, read where the extension is not installed;
|
|
97
|
+
// an entry names the directories, where it is
|
|
98
|
+
if (manifests !== undefined) return manifests.map(revive)
|
|
99
|
+
|
|
100
|
+
const extracted = []
|
|
101
|
+
|
|
102
|
+
for (const path of paths) extracted.push(await load(path))
|
|
103
|
+
|
|
104
|
+
return extracted
|
|
54
105
|
}
|
|
55
106
|
|
|
56
|
-
|
|
107
|
+
/** A manifest as a digest carries it, which is one norm read, without the locator. */
|
|
108
|
+
function revive(manifest) {
|
|
109
|
+
manifest.locator = new Locator(manifest.name, manifest.namespace)
|
|
110
|
+
|
|
111
|
+
return manifest
|
|
112
|
+
}
|
|
@@ -1,9 +1,3 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
const { extensions } = require('./extensions')
|
|
5
|
-
const { resolve } = require('./resolve')
|
|
6
|
-
|
|
7
|
-
exports.connectors = connectors
|
|
8
|
-
exports.extensions = extensions
|
|
9
|
-
exports.resolve = resolve
|
|
1
|
+
export { connectors } from './connectors.js'
|
|
2
|
+
export { extensions } from './extensions.js'
|
|
3
|
+
export { resolve } from './resolve.js'
|
|
@@ -1,13 +1,10 @@
|
|
|
1
|
-
|
|
1
|
+
import { definition } from '../../definition.js'
|
|
2
2
|
|
|
3
|
-
const
|
|
4
|
-
|
|
5
|
-
const resolve = (references, annotations) => {
|
|
3
|
+
export const resolve = async (references, annotations = {}) => {
|
|
6
4
|
const dependencies = {}
|
|
7
5
|
|
|
8
6
|
for (const [dependency, components] of Object.entries(references)) {
|
|
9
|
-
const {
|
|
10
|
-
const id = metadata.name
|
|
7
|
+
const { name: id, module } = await definition(dependency)
|
|
11
8
|
|
|
12
9
|
const instances = components.map((component) => ({
|
|
13
10
|
locator: component.locator,
|
|
@@ -24,7 +21,26 @@ const resolve = (references, annotations) => {
|
|
|
24
21
|
}
|
|
25
22
|
}
|
|
26
23
|
|
|
24
|
+
for (const dependency of Object.keys(annotations)) {
|
|
25
|
+
if (dependency in dependencies) continue
|
|
26
|
+
|
|
27
|
+
// an annotation may be keyed by a dependency id rather than by a module reference
|
|
28
|
+
const module = await optional(dependency)
|
|
29
|
+
|
|
30
|
+
if (module?.standalone === true) dependencies[dependency] = []
|
|
31
|
+
}
|
|
32
|
+
|
|
27
33
|
return dependencies
|
|
28
34
|
}
|
|
29
35
|
|
|
30
|
-
|
|
36
|
+
/**
|
|
37
|
+
* @param {string} reference
|
|
38
|
+
* @returns {object | null}
|
|
39
|
+
*/
|
|
40
|
+
async function optional(reference) {
|
|
41
|
+
try {
|
|
42
|
+
return (await definition(reference)).module
|
|
43
|
+
} catch {
|
|
44
|
+
return null
|
|
45
|
+
}
|
|
46
|
+
}
|
package/src/.context/complete.js
CHANGED
|
@@ -1,11 +1,9 @@
|
|
|
1
|
-
'use strict'
|
|
2
|
-
|
|
3
1
|
/**
|
|
4
2
|
* Completes missing compositions with unused components
|
|
5
3
|
* @param {toa.norm.Context} context
|
|
6
4
|
* @returns {void}
|
|
7
5
|
*/
|
|
8
|
-
const complete = (context) => {
|
|
6
|
+
export const complete = (context) => {
|
|
9
7
|
/** @type {Set<string>} */
|
|
10
8
|
const composed = new Set()
|
|
11
9
|
|
|
@@ -29,5 +27,3 @@ const complete = (context) => {
|
|
|
29
27
|
context.compositions.push({ name, components: [component] })
|
|
30
28
|
}
|
|
31
29
|
}
|
|
32
|
-
|
|
33
|
-
exports.complete = complete
|
|
@@ -1,13 +1,9 @@
|
|
|
1
|
-
|
|
1
|
+
import { connectors, extensions, resolve } from './.dependencies/index.js'
|
|
2
2
|
|
|
3
|
-
const
|
|
4
|
-
|
|
5
|
-
const dependencies = async (context) => {
|
|
3
|
+
export const dependencies = async (context) => {
|
|
6
4
|
const { extensions: e, components } = await extensions(context)
|
|
7
5
|
const c = connectors(context, components)
|
|
8
6
|
const references = { ...c, ...e }
|
|
9
7
|
|
|
10
8
|
return resolve(references, context.annotations)
|
|
11
9
|
}
|
|
12
|
-
|
|
13
|
-
exports.dependencies = dependencies
|
|
@@ -1,16 +1,23 @@
|
|
|
1
|
-
'use strict'
|
|
2
|
-
|
|
3
1
|
/**
|
|
4
2
|
* Resolves component IDs within compositions with Component objects
|
|
5
3
|
* @param {toa.norm.Context} context
|
|
6
4
|
* @returns {void}
|
|
7
5
|
*/
|
|
8
|
-
const dereference = (context) => {
|
|
6
|
+
export const dereference = (context) => {
|
|
9
7
|
const components = map(context.components)
|
|
10
8
|
|
|
11
9
|
if (context.compositions !== undefined) {
|
|
12
10
|
for (const composition of context.compositions) {
|
|
13
|
-
composition.components = composition.components.map((id) =>
|
|
11
|
+
composition.components = composition.components.map((id) => {
|
|
12
|
+
const component = components[id]
|
|
13
|
+
|
|
14
|
+
if (component === undefined)
|
|
15
|
+
throw new Error(
|
|
16
|
+
`Composition '${composition.name}' lists an unknown component '${id}'.`
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
return component
|
|
20
|
+
})
|
|
14
21
|
}
|
|
15
22
|
}
|
|
16
23
|
}
|
|
@@ -27,5 +34,3 @@ const map = (components) => {
|
|
|
27
34
|
|
|
28
35
|
return map
|
|
29
36
|
}
|
|
30
|
-
|
|
31
|
-
exports.dereference = dereference
|
package/src/.context/expand.js
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
const shortcuts = require('../shortcuts')
|
|
1
|
+
import * as shortcuts from '../shortcuts.js'
|
|
4
2
|
|
|
5
3
|
/**
|
|
6
4
|
* @param {toa.norm.context.Declaration | object} context
|
|
7
5
|
*/
|
|
8
|
-
const expand = (context) => {
|
|
6
|
+
export const expand = (context) => {
|
|
9
7
|
shortcuts.recognize(shortcuts.SHORTCUTS, context, 'annotations')
|
|
10
8
|
shortcuts.recognize(shortcuts.SHORTCUTS, context.annotations)
|
|
11
|
-
}
|
|
12
9
|
|
|
13
|
-
|
|
10
|
+
// a composition names a service the way a manifest names an extension, and everything
|
|
11
|
+
// downstream matches it against a dependency, which is keyed by package reference
|
|
12
|
+
for (const composition of context.compositions ?? [])
|
|
13
|
+
if (composition.services !== undefined)
|
|
14
|
+
composition.services = composition.services.map(shortcuts.resolve)
|
|
15
|
+
}
|
package/src/.context/index.js
CHANGED
|
@@ -1,15 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
const { normalize } = require('./normalize')
|
|
8
|
-
const { validate } = require('./validate')
|
|
9
|
-
|
|
10
|
-
exports.complete = complete
|
|
11
|
-
exports.dependencies = dependencies
|
|
12
|
-
exports.dereference = dereference
|
|
13
|
-
exports.expand = expand
|
|
14
|
-
exports.normalize = normalize
|
|
15
|
-
exports.validate = validate
|
|
1
|
+
export { complete } from './complete.js'
|
|
2
|
+
export { dependencies } from './dependencies.js'
|
|
3
|
+
export { dereference } from './dereference.js'
|
|
4
|
+
export { expand } from './expand.js'
|
|
5
|
+
export { normalize } from './normalize.js'
|
|
6
|
+
export { validate } from './validate.js'
|
|
@@ -1,19 +1,21 @@
|
|
|
1
|
-
|
|
1
|
+
import * as definitions from '@toa.io/definitions'
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* @param {toa.norm.context.Declaration | Object} context
|
|
5
5
|
*/
|
|
6
|
-
const normalize = (context) => {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
if (context.runtime === undefined) context.runtime = { version: runtime.version }
|
|
6
|
+
export const normalize = (context) => {
|
|
7
|
+
if (context.runtime === undefined) context.runtime = { version: definitions.version }
|
|
10
8
|
if (typeof context.runtime === 'string') context.runtime = { version: context.runtime }
|
|
11
9
|
|
|
12
10
|
if (context.runtime.version === undefined || context.runtime.version === '.') {
|
|
13
|
-
context.runtime.version =
|
|
11
|
+
context.runtime.version = definitions.version
|
|
14
12
|
}
|
|
15
13
|
|
|
16
14
|
if (typeof context.registry === 'string') context.registry = { base: context.registry }
|
|
15
|
+
|
|
16
|
+
// what an image is built for, where the Context does not say; `~` says the runner's own
|
|
17
|
+
if (context.registry !== undefined && context.registry.platforms === undefined)
|
|
18
|
+
context.registry.platforms = PLATFORMS
|
|
17
19
|
}
|
|
18
20
|
|
|
19
|
-
|
|
21
|
+
const PLATFORMS = ['linux/amd64', 'linux/arm/v7', 'linux/arm64']
|
package/src/.context/schema.yaml
CHANGED
|
@@ -6,12 +6,10 @@ properties:
|
|
|
6
6
|
version:
|
|
7
7
|
type: string
|
|
8
8
|
name:
|
|
9
|
-
$ref: 'definitions#/definitions/label'
|
|
10
|
-
description:
|
|
11
9
|
type: string
|
|
12
|
-
|
|
10
|
+
pattern: ^([a-zA-Z]+([_a-zA-Z0-9]*[a-zA-Z0-9]+)?)(-([a-zA-Z]+([_a-zA-Z0-9]*[a-zA-Z0-9]+)?))*$
|
|
11
|
+
description:
|
|
13
12
|
type: string
|
|
14
|
-
default: components/*
|
|
15
13
|
build:
|
|
16
14
|
type: object
|
|
17
15
|
properties:
|
|
@@ -42,28 +40,151 @@ properties:
|
|
|
42
40
|
minItems: 1
|
|
43
41
|
items:
|
|
44
42
|
type: string
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
43
|
+
services:
|
|
44
|
+
description: >
|
|
45
|
+
Where an extension service's image comes from. `build`, which absence means,
|
|
46
|
+
builds it into `registry.base` the way a composition is built. `published` uses
|
|
47
|
+
the image the extension ships, which is neither built nor pushed.
|
|
48
|
+
enum: [build, published]
|
|
49
|
+
# required: [base]
|
|
50
50
|
compositions:
|
|
51
51
|
type: array
|
|
52
52
|
minItems: 1
|
|
53
53
|
items:
|
|
54
54
|
type: object
|
|
55
|
+
additionalProperties: false
|
|
55
56
|
properties:
|
|
56
57
|
name:
|
|
57
|
-
|
|
58
|
+
type: string
|
|
59
|
+
pattern: ^[a-zA-Z]([a-zA-Z0-9]{1,31})?$
|
|
58
60
|
image:
|
|
59
61
|
type: string
|
|
60
62
|
components:
|
|
61
63
|
type: array
|
|
62
64
|
minItems: 1
|
|
63
65
|
items:
|
|
64
|
-
|
|
66
|
+
type: string
|
|
67
|
+
pattern: ^([a-zA-Z]+([_a-zA-Z0-9]*[a-zA-Z0-9]+)?)(\.([a-zA-Z]+([_a-zA-Z0-9]*[a-zA-Z0-9]+)?))$
|
|
68
|
+
services:
|
|
69
|
+
description: >
|
|
70
|
+
Extension services this composition runs in its own pod, named by shortcut or
|
|
71
|
+
package reference, rather than each as its own deployment. A service listed here
|
|
72
|
+
keeps its own Service and Ingress; what changes is the pod they select.
|
|
73
|
+
type: array
|
|
74
|
+
minItems: 1
|
|
75
|
+
items:
|
|
76
|
+
type: string
|
|
77
|
+
minLength: 1
|
|
78
|
+
resources:
|
|
79
|
+
$ref: '#/definitions/resources'
|
|
65
80
|
required: [name, components]
|
|
81
|
+
atomicity:
|
|
82
|
+
description: >
|
|
83
|
+
Where the replicas of a component decide together: who owns what, what they have spent
|
|
84
|
+
between them, and which of them holds a name while it works. Absent or unreachable,
|
|
85
|
+
nothing is owned and whoever asked stands down; metering and locking are refused.
|
|
86
|
+
type: object
|
|
87
|
+
additionalProperties: false
|
|
88
|
+
properties:
|
|
89
|
+
redis:
|
|
90
|
+
description: >
|
|
91
|
+
One address, or an odd number of them — independent servers, not the nodes of a
|
|
92
|
+
cluster. A lock is taken on a quorum of them; the registry and the meter use the
|
|
93
|
+
first, because each of them counts on a single key.
|
|
94
|
+
oneOf:
|
|
95
|
+
- type: string
|
|
96
|
+
- type: array
|
|
97
|
+
minItems: 1
|
|
98
|
+
items:
|
|
99
|
+
type: string
|
|
100
|
+
interval:
|
|
101
|
+
description: >
|
|
102
|
+
How often a replica registers, in milliseconds. It has to comfortably exceed a round
|
|
103
|
+
trip to Redis and the jitter around it, and nothing is owned until two consecutive
|
|
104
|
+
intervals have agreed — so this is also how long a replica owns nothing after it
|
|
105
|
+
joins, restarts or stalls.
|
|
106
|
+
type: integer
|
|
107
|
+
minimum: 100
|
|
108
|
+
required: [redis]
|
|
109
|
+
outbox:
|
|
110
|
+
type: object
|
|
111
|
+
additionalProperties: false
|
|
112
|
+
properties:
|
|
113
|
+
interval:
|
|
114
|
+
description: >
|
|
115
|
+
The pump's cycle, in milliseconds. One cycle reads what is unpublished, publishes it
|
|
116
|
+
and marks it. In steady state it reads nothing, so this is budgeted for how fast a rare
|
|
117
|
+
failure heals rather than for how fast events flow.
|
|
118
|
+
type: integer
|
|
119
|
+
minimum: 100
|
|
120
|
+
batch:
|
|
121
|
+
description: >
|
|
122
|
+
How many rows one read brings back. A page that comes back full is read on from,
|
|
123
|
+
so this bounds a read rather than a cycle.
|
|
124
|
+
type: integer
|
|
125
|
+
minimum: 1
|
|
126
|
+
retention:
|
|
127
|
+
description: >
|
|
128
|
+
Seconds a published row is kept as a change log before it expires. A row that never
|
|
129
|
+
made it out has no expiry and is never reaped.
|
|
130
|
+
type: integer
|
|
131
|
+
minimum: 0
|
|
132
|
+
events:
|
|
133
|
+
description: >
|
|
134
|
+
Events of this context that are consumed outside it. An event is otherwise published only
|
|
135
|
+
where something in the context receives it.
|
|
136
|
+
type: array
|
|
137
|
+
items:
|
|
138
|
+
type: string
|
|
139
|
+
pattern: ^[a-zA-Z][a-zA-Z0-9]*\.[a-zA-Z][a-zA-Z0-9]*\.[a-zA-Z][a-zA-Z0-9]*$
|
|
140
|
+
mono:
|
|
141
|
+
type: object
|
|
142
|
+
additionalProperties: false
|
|
143
|
+
properties:
|
|
144
|
+
replicas:
|
|
145
|
+
type: integer
|
|
146
|
+
minimum: 1
|
|
147
|
+
resources:
|
|
148
|
+
$ref: '#/definitions/resources'
|
|
149
|
+
ingress:
|
|
150
|
+
type: object
|
|
151
|
+
additionalProperties: false
|
|
152
|
+
properties:
|
|
153
|
+
hosts:
|
|
154
|
+
type: array
|
|
155
|
+
minItems: 1
|
|
156
|
+
items:
|
|
157
|
+
type: string
|
|
158
|
+
class:
|
|
159
|
+
type: string
|
|
160
|
+
annotations:
|
|
161
|
+
type: object
|
|
162
|
+
default:
|
|
163
|
+
type: boolean
|
|
66
164
|
annotations:
|
|
67
165
|
type: object
|
|
166
|
+
resources:
|
|
167
|
+
$ref: '#/definitions/resources'
|
|
68
168
|
required: [runtime, name, registry]
|
|
69
169
|
additionalProperties: false
|
|
170
|
+
|
|
171
|
+
definitions:
|
|
172
|
+
# What a deployment may take. `null` deploys it without any, which is a decision
|
|
173
|
+
# rather than an omission — every deployment has to make one.
|
|
174
|
+
resources:
|
|
175
|
+
type: object
|
|
176
|
+
nullable: true
|
|
177
|
+
additionalProperties: false
|
|
178
|
+
properties:
|
|
179
|
+
cpu:
|
|
180
|
+
type: array
|
|
181
|
+
items:
|
|
182
|
+
type: string
|
|
183
|
+
minItems: 2
|
|
184
|
+
maxItems: 2
|
|
185
|
+
memory:
|
|
186
|
+
type: array
|
|
187
|
+
items:
|
|
188
|
+
type: string
|
|
189
|
+
minItems: 2
|
|
190
|
+
maxItems: 2
|
package/src/.context/validate.js
CHANGED
|
@@ -1,19 +1,13 @@
|
|
|
1
|
-
|
|
1
|
+
import { resolve } from 'node:path'
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
import { readFileSync } from 'node:fs'
|
|
4
|
+
import { yaml } from '@toa.io/generic'
|
|
5
|
+
import * as schemas from '@toa.io/schemas'
|
|
4
6
|
|
|
5
|
-
const
|
|
6
|
-
const
|
|
7
|
+
const path = resolve(import.meta.dirname, 'schema.yaml')
|
|
8
|
+
const object = yaml.load(readFileSync(path, 'utf8'))
|
|
9
|
+
const schema = schemas.schema(object)
|
|
7
10
|
|
|
8
|
-
const
|
|
9
|
-
const object = load.sync(path)
|
|
10
|
-
const schema = new Schema(object)
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* @param {toa.norm.context.Declaration} context
|
|
14
|
-
*/
|
|
15
|
-
const validate = (context) => {
|
|
11
|
+
export const validate = (context) => {
|
|
16
12
|
schema.validate(context)
|
|
17
13
|
}
|
|
18
|
-
|
|
19
|
-
exports.validate = validate
|