@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.
Files changed (91) hide show
  1. package/CHANGELOG.md +45 -0
  2. package/package.json +10 -7
  3. package/src/.component/.expand/bridge.js +2 -6
  4. package/src/.component/.expand/entity.js +2 -9
  5. package/src/.component/.expand/events.js +2 -6
  6. package/src/.component/.expand/extensions.js +5 -8
  7. package/src/.component/.expand/index.js +8 -19
  8. package/src/.component/.expand/operations.js +2 -9
  9. package/src/.component/.expand/properties.js +2 -6
  10. package/src/.component/.expand/receivers.js +4 -7
  11. package/src/.component/.expand/version.js +38 -28
  12. package/src/.component/.normalize/entity.js +7 -0
  13. package/src/.component/.normalize/events.js +16 -4
  14. package/src/.component/.normalize/index.js +4 -9
  15. package/src/.component/.normalize/operations.js +10 -6
  16. package/src/.component/.normalize/receivers.js +4 -5
  17. package/src/.component/collapse.js +60 -9
  18. package/src/.component/defaults.js +16 -10
  19. package/src/.component/dependencies.js +14 -10
  20. package/src/.component/dereference.js +20 -18
  21. package/src/.component/expand.js +3 -7
  22. package/src/.component/extensions.js +13 -9
  23. package/src/.component/index.js +10 -21
  24. package/src/.component/merge.js +25 -13
  25. package/src/.component/migrations.js +57 -0
  26. package/src/.component/normalize.js +10 -6
  27. package/src/.component/schema.yaml +108 -71
  28. package/src/.component/validate.js +37 -17
  29. package/src/.context/.dependencies/connectors.js +5 -9
  30. package/src/.context/.dependencies/describe.js +4 -5
  31. package/src/.context/.dependencies/extensions.js +75 -19
  32. package/src/.context/.dependencies/index.js +3 -9
  33. package/src/.context/.dependencies/resolve.js +23 -7
  34. package/src/.context/complete.js +1 -5
  35. package/src/.context/dependencies.js +2 -6
  36. package/src/.context/dereference.js +11 -6
  37. package/src/.context/expand.js +8 -6
  38. package/src/.context/index.js +6 -15
  39. package/src/.context/normalize.js +9 -7
  40. package/src/.context/schema.yaml +132 -11
  41. package/src/.context/validate.js +8 -14
  42. package/src/component.js +36 -17
  43. package/src/context.js +26 -14
  44. package/src/definition.js +56 -0
  45. package/src/entity.js +15 -0
  46. package/src/index.js +5 -9
  47. package/src/shortcuts.js +8 -13
  48. package/test/component/collapse.fixtures.js +30 -39
  49. package/test/component/collapse.test.js +90 -10
  50. package/test/component/dependencies.test.js +16 -14
  51. package/test/component/dereference.fixtures.js +15 -24
  52. package/test/component/dereference.test.js +18 -8
  53. package/test/component/dummies/extension/index.js +1 -5
  54. package/test/component/dummies/migrations/conflicting/migrations/0001-one.yaml +2 -0
  55. package/test/component/dummies/migrations/conflicting/migrations/0001-one.yml +2 -0
  56. package/test/component/dummies/migrations/malformed/migrations/0001-one.yaml +1 -0
  57. package/test/component/dummies/migrations/ordered/migrations/0001-first.yml +4 -0
  58. package/test/component/dummies/migrations/ordered/migrations/0002-second.yaml +4 -0
  59. package/test/component/dummies/migrations/ordered/migrations/0010-third.json +1 -0
  60. package/test/component/dummies/migrations/stateless/migrations/0001-one.yaml +2 -0
  61. package/test/component/expand.fixtures.js +13 -13
  62. package/test/component/expand.test.js +25 -10
  63. package/test/component/migrations.test.js +62 -0
  64. package/test/component/normalize.fixtures.js +3 -7
  65. package/test/component/normalize.test.js +86 -28
  66. package/test/component/validate.fixtures.js +15 -13
  67. package/test/component/validate.test.js +162 -109
  68. package/test/context/complete.fixtures.js +2 -8
  69. package/test/context/complete.test.js +14 -8
  70. package/test/context/dereference.fixtures.js +3 -9
  71. package/test/context/dereference.test.js +18 -7
  72. package/test/context/expand.fixtures.js +1 -5
  73. package/test/context/expand.test.js +36 -8
  74. package/test/context/normalize.fixtures.js +2 -7
  75. package/test/context/normalize.test.js +33 -9
  76. package/test/context/validate.fixtures.js +1 -6
  77. package/test/context/validate.test.js +81 -37
  78. package/test/definition.test.js +35 -0
  79. package/test/dummies/bare/index.js +1 -0
  80. package/test/dummies/defined/definition.js +1 -0
  81. package/test/dummies/defined/index.js +1 -0
  82. package/test/dummies/defined/package.json +1 -0
  83. package/test/shortcuts.fixtures.js +4 -9
  84. package/test/shortcuts.test.js +17 -15
  85. package/types/component.d.ts +17 -3
  86. package/types/context/declaration.d.ts +26 -3
  87. package/types/context.d.ts +15 -13
  88. package/types/entity.d.ts +7 -0
  89. package/types/index.d.ts +12 -3
  90. package/src/.context/.dependencies/load.js +0 -36
  91. package/test/context/dependencies.load.test.js +0 -24
@@ -1,25 +1,43 @@
1
- 'use strict'
1
+ import path from 'node:path'
2
2
 
3
- const path = require('node:path')
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 { load } = require('@toa.io/yaml')
6
- const { Schema } = require('@toa.io/schema')
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 object = load.sync(path.resolve(__dirname, 'schema.yaml'))
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 new Error(error.message)
16
+ if (error) throw error
15
17
 
16
- if (manifest.events !== undefined) events(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
- const events = (manifest) => {
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
- if (require(event.binding).properties.async !== true) {
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(`Receiver '${locator}' refers to undefined operation '${receiver.operation}'`)
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(`Receiver '${locator}' must refer to an operation of one of the allowed types: ${Array.from(TYPES).join(', ')}`)
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
- 'use strict'
1
+ import { resolve } from 'node:path'
2
2
 
3
- const { resolve } = require('node:path')
4
-
5
- const connectors = (context, extracted) => {
3
+ export const connectors = (context, extracted) => {
6
4
  const connectors = {}
7
5
 
8
- const components = (context.components === undefined
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) => ({ locator: component.locator, manifest })
9
-
10
- exports.describe = describe
6
+ export const describe = (component, manifest = undefined) => ({
7
+ locator: component.locator,
8
+ manifest
9
+ })
@@ -1,9 +1,22 @@
1
- 'use strict'
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
- const extracted = await extractExtensionComponents(components, extensions)
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 extractExtensionComponents (components, extensions) {
24
- const { component: load } = require('../../component')
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
- extensions[reference] = []
66
+ extracted.push(...(await extract(reference, extensions, annotations)))
67
+ }
68
+ }
35
69
 
36
- const mod = require(reference)
70
+ if (extracted.length === 0) return extracted
37
71
 
38
- if (mod.components === undefined) continue
72
+ const deeper = await extractExtensionComponents(extracted, extensions, annotations)
39
73
 
40
- for (const path of mod.components().paths) {
41
- const component = await load(path)
74
+ return extracted.concat(deeper)
75
+ }
42
76
 
43
- extracted.push(component)
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
- if (extracted.length === 0)
49
- return extracted
88
+ const { name, module: mod } = await definition(reference)
50
89
 
51
- const deeper = await extractExtensionComponents(extracted, extensions)
90
+ if (mod.components === undefined) return []
52
91
 
53
- return extracted.concat(deeper)
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
- exports.extensions = extensions
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
- 'use strict'
2
-
3
- const { connectors } = require('./connectors')
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
- 'use strict'
1
+ import { definition } from '../../definition.js'
2
2
 
3
- const { load } = require('./load')
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 { metadata, module } = load(dependency)
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
- exports.resolve = resolve
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
+ }
@@ -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
- 'use strict'
1
+ import { connectors, extensions, resolve } from './.dependencies/index.js'
2
2
 
3
- const { connectors, extensions, resolve } = require('./.dependencies')
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) => components[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
@@ -1,13 +1,15 @@
1
- 'use strict'
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
- exports.expand = expand
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
+ }
@@ -1,15 +1,6 @@
1
- 'use strict'
2
-
3
- const { complete } = require('./complete')
4
- const { dependencies } = require('./dependencies')
5
- const { dereference } = require('./dereference')
6
- const { expand } = require('./expand')
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
- 'use strict'
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
- const runtime = require('@toa.io/runtime')
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 = 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
- exports.normalize = normalize
21
+ const PLATFORMS = ['linux/amd64', 'linux/arm/v7', 'linux/arm64']
@@ -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
- packages:
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
- default:
46
- - linux/amd64
47
- - linux/arm/v7
48
- - linux/arm64
49
- required: [base, platforms]
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
- $ref: 'definitions#/definitions/token'
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
- $ref: 'definitions#/definitions/locator'
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
@@ -1,19 +1,13 @@
1
- 'use strict'
1
+ import { resolve } from 'node:path'
2
2
 
3
- const { resolve } = require('node:path')
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 { load } = require('@toa.io/yaml')
6
- const { Schema } = require('@toa.io/schema')
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 path = resolve(__dirname, 'schema.yaml')
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