@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.
Files changed (67) hide show
  1. package/package.json +5 -5
  2. package/src/.component/.expand/bridge.js +1 -1
  3. package/src/.component/.expand/entity.js +1 -1
  4. package/src/.component/.expand/events.js +1 -1
  5. package/src/.component/.expand/extensions.js +1 -1
  6. package/src/.component/.expand/operations.js +1 -1
  7. package/src/.component/.expand/properties.js +1 -1
  8. package/src/.component/.expand/receivers.js +3 -2
  9. package/src/.component/.expand/version.js +11 -13
  10. package/src/.component/.normalize/entity.js +7 -0
  11. package/src/.component/.normalize/events.js +4 -1
  12. package/src/.component/.normalize/index.js +1 -0
  13. package/src/.component/.normalize/operations.js +9 -1
  14. package/src/.component/.normalize/receivers.js +4 -1
  15. package/src/.component/collapse.js +14 -6
  16. package/src/.component/defaults.js +3 -4
  17. package/src/.component/dependencies.js +7 -4
  18. package/src/.component/dereference.js +14 -13
  19. package/src/.component/expand.js +11 -2
  20. package/src/.component/extensions.js +2 -1
  21. package/src/.component/index.js +1 -0
  22. package/src/.component/merge.js +3 -5
  23. package/src/.component/migrations.js +57 -0
  24. package/src/.component/normalize.js +9 -1
  25. package/src/.component/schema.yaml +29 -39
  26. package/src/.component/validate.js +23 -3
  27. package/src/.context/.dependencies/connectors.js +3 -3
  28. package/src/.context/.dependencies/describe.js +4 -1
  29. package/src/.context/.dependencies/extensions.js +18 -13
  30. package/src/.context/.dependencies/load.js +6 -4
  31. package/src/.context/.dependencies/resolve.js +1 -1
  32. package/src/.context/dereference.js +3 -1
  33. package/src/.context/normalize.js +5 -0
  34. package/src/.context/schema.yaml +6 -4
  35. package/src/component.js +14 -5
  36. package/src/context.js +9 -5
  37. package/src/entity.js +15 -0
  38. package/src/index.js +1 -0
  39. package/src/shortcuts.js +0 -1
  40. package/test/component/collapse.fixtures.js +28 -34
  41. package/test/component/collapse.test.js +21 -0
  42. package/test/component/dependencies.test.js +6 -6
  43. package/test/component/dereference.fixtures.js +13 -17
  44. package/test/component/dereference.test.js +12 -3
  45. package/test/component/dummies/migrations/conflicting/migrations/0001-one.yaml +2 -0
  46. package/test/component/dummies/migrations/conflicting/migrations/0001-one.yml +2 -0
  47. package/test/component/dummies/migrations/malformed/migrations/0001-one.yaml +1 -0
  48. package/test/component/dummies/migrations/ordered/migrations/0001-first.yml +4 -0
  49. package/test/component/dummies/migrations/ordered/migrations/0002-second.yaml +4 -0
  50. package/test/component/dummies/migrations/ordered/migrations/0010-third.json +1 -0
  51. package/test/component/dummies/migrations/stateless/migrations/0001-one.yaml +2 -0
  52. package/test/component/migrations.test.js +62 -0
  53. package/test/component/normalize.test.js +51 -13
  54. package/test/component/validate.fixtures.js +14 -8
  55. package/test/component/validate.test.js +113 -59
  56. package/test/context/complete.test.js +5 -1
  57. package/test/context/dereference.test.js +3 -2
  58. package/test/context/expand.test.js +4 -2
  59. package/test/context/normalize.test.js +14 -1
  60. package/test/context/validate.test.js +40 -20
  61. package/test/shortcuts.test.js +2 -1
  62. package/types/component.d.ts +14 -3
  63. package/types/context/declaration.d.ts +1 -1
  64. package/types/context.d.ts +10 -9
  65. package/types/entity.d.ts +7 -0
  66. package/types/index.d.ts +1 -0
  67. 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.284",
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.284",
25
- "@toa.io/generic": "1.0.0-alpha.283",
26
- "@toa.io/schemas": "1.0.0-alpha.283",
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": "006755556879e91e8d68fa86753aa3b475bdf4bf"
30
+ "gitHead": "81f3d0688f17668b902be336d1749cc715edcdb6"
31
31
  }
@@ -1,5 +1,5 @@
1
1
  import { resolve } from '../../shortcuts.js'
2
2
 
3
- export function bridge (manifest) {
3
+ export function bridge(manifest) {
4
4
  manifest.bridge = resolve(manifest.bridge)
5
5
  }
@@ -1,6 +1,6 @@
1
1
  import { resolve } from '../../shortcuts.js'
2
2
 
3
- export function entity (manifest) {
3
+ export function entity(manifest) {
4
4
  if (!('entity' in manifest)) return
5
5
 
6
6
  manifest.entity.storage = resolve(manifest.entity.storage)
@@ -1,6 +1,6 @@
1
1
  import { resolve } from '../../shortcuts.js'
2
2
 
3
- export function events (manifest) {
3
+ export function events(manifest) {
4
4
  if (manifest.events === undefined) return
5
5
 
6
6
  for (const event of Object.values(manifest.events)) {
@@ -1,6 +1,6 @@
1
1
  import { recognize } from '../../shortcuts.js'
2
2
 
3
- export function extensions (manifest) {
3
+ export function extensions(manifest) {
4
4
  recognize(SHORTCUTS, manifest, 'extensions')
5
5
  recognize(SHORTCUTS, manifest.extensions)
6
6
  }
@@ -1,6 +1,6 @@
1
1
  import { resolve } from '../../shortcuts.js'
2
2
 
3
- export function operations (manifest) {
3
+ export function operations(manifest) {
4
4
  if (manifest.operations === undefined) return
5
5
 
6
6
  for (const operation of Object.values(manifest.operations)) {
@@ -1,6 +1,6 @@
1
1
  import { recognize } from '../../shortcuts.js'
2
2
 
3
- export function properties (manifest) {
3
+ export function properties(manifest) {
4
4
  recognize(SHORTCUTS, manifest, 'properties')
5
5
  recognize(SHORTCUTS, manifest.properties)
6
6
  }
@@ -1,10 +1,11 @@
1
1
  import { resolve } from '../../shortcuts.js'
2
2
 
3
- export function receivers (manifest) {
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') manifest.receivers[locator] = { operation: receiver }
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 (manifest) {
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 (path) {
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 (root, path = '', acc = []) {
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
- await list(root, relative, acc)
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 (path) {
54
- return createHash('sha256').update(await fs.readFile(path)).digest()
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'])
@@ -0,0 +1,7 @@
1
+ export function entity(component) {
2
+ if (component.entity === undefined) return
3
+
4
+ component.entity.storage ??= '@toa.io/storages.mongodb'
5
+ component.entity.associated ??= false
6
+ component.entity.custom ??= false
7
+ }
@@ -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 (bindings) {
15
+ async function asynchronous(bindings) {
13
16
  for (const binding of bindings) {
14
17
  const { properties } = await import(binding)
15
18
 
@@ -1,3 +1,4 @@
1
+ export { entity } from './entity.js'
1
2
  export { events } from './events.js'
2
3
  export { operations } from './operations.js'
3
4
  export { receivers } from './receivers.js'
@@ -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
- if (operation.scope === 'none')
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 (component) {
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) manifest.operations[endpoint] = {}
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) manifest.prototype.operations = {}
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 (manifest.entity?.schema?.properties.id !== undefined && entity?.schema?.properties.id !== undefined) {
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.schema.properties.id
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
- delete prototype.events[event]
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 (manifest) {
22
+ function protoName(manifest) {
24
23
  return 'proto' + hash(manifest.path)
25
24
  }
26
25
 
27
- function nameAfterDir (manifest) {
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) component.entity.storage = resolve(component.path, component.entity.storage)
12
+ if ('entity' in component)
13
+ component.entity.storage = resolve(component.path, component.entity.storage)
13
14
  }
14
15
 
15
- function resolve (root, reference) {
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 { // as package
22
+ try {
23
+ // as package
22
24
  const packageJsonRef = join(reference, 'package.json')
23
25
 
24
26
  path = require.resolve(packageJsonRef, options)
25
- } catch { // as directory
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 resolver = createResolver(manifest.entity?.schema?.properties)
5
+ const properties = manifest.entity?.properties
6
+ const resolver = createResolver(properties)
6
7
 
7
- if (manifest.entity !== undefined)
8
- schema(manifest.entity.schema, resolver)
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 (manifest, resolver) {
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] === '.') return resolve(object.default.substring(1))
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 === '.') object.properties[name] = resolve(name)
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) throw new Error(`Referenced operation '${operation.forward}' is not defined`)
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)
@@ -1,6 +1,15 @@
1
- import { entity, bridge, operations, events, receivers, extensions, properties, version } from './.expand/index.js'
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 (manifest) {
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) throw new Error(`Extension '${reference}' hasn't returned manifest`)
24
+ if (declaration === undefined)
25
+ throw new Error(`Extension '${reference}' hasn't returned manifest`)
25
26
  }
26
27
 
27
28
  extensions[key] = declaration
@@ -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'
@@ -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 (mod) {
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
- default: "@toa.io/storages.mongodb"
76
- schema:
77
- $ref: https://json-schema.org/draft/2019-09/schema
74
+ properties:
78
75
  type: object
79
- properties:
80
- type:
81
- default: object
82
- const: object
83
- properties:
84
- type: object
85
- propertyNames:
86
- oneOf:
87
- - $ref: "#/definitions/name"
88
- - enum: [_version, _created, _updated, _deleted]
89
- unique:
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
- patternProperties:
92
- ".*":
93
- type: array
94
- items:
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
- index:
97
- type: object
98
- patternProperties:
99
- ".*":
100
- type: object
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
- default: false
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]