@toa.io/norm 1.0.0-alpha.31 → 1.0.0-alpha.310

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 (99) hide show
  1. package/CHANGELOG.md +208 -0
  2. package/package.json +9 -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 +6 -8
  7. package/src/.component/.expand/index.js +7 -19
  8. package/src/.component/.expand/operations.js +2 -9
  9. package/src/.component/.expand/receivers.js +4 -7
  10. package/src/.component/.expand/version.js +67 -28
  11. package/src/.component/.normalize/entity.js +7 -0
  12. package/src/.component/.normalize/events.js +16 -4
  13. package/src/.component/.normalize/index.js +4 -9
  14. package/src/.component/.normalize/operations.js +13 -6
  15. package/src/.component/.normalize/receivers.js +4 -5
  16. package/src/.component/collapse.js +60 -9
  17. package/src/.component/defaults.js +5 -11
  18. package/src/.component/dependencies.js +14 -10
  19. package/src/.component/dereference.js +5 -49
  20. package/src/.component/expand.js +3 -9
  21. package/src/.component/extensions.js +9 -10
  22. package/src/.component/index.js +10 -21
  23. package/src/.component/merge.js +25 -15
  24. package/src/.component/migrations.js +57 -0
  25. package/src/.component/normalize.js +10 -6
  26. package/src/.component/schema.yaml +147 -73
  27. package/src/.component/validate.js +37 -17
  28. package/src/.context/.dependencies/connectors.js +5 -9
  29. package/src/.context/.dependencies/describe.js +4 -5
  30. package/src/.context/.dependencies/extensions.js +93 -20
  31. package/src/.context/.dependencies/index.js +3 -9
  32. package/src/.context/.dependencies/resolve.js +44 -7
  33. package/src/.context/complete.js +1 -5
  34. package/src/.context/converge.js +31 -0
  35. package/src/.context/dependencies.js +43 -6
  36. package/src/.context/dereference.js +11 -6
  37. package/src/.context/evict.js +38 -0
  38. package/src/.context/expand.js +11 -6
  39. package/src/.context/index.js +8 -15
  40. package/src/.context/normalize.js +9 -7
  41. package/src/.context/schema.yaml +181 -11
  42. package/src/.context/validate.js +8 -14
  43. package/src/component.js +33 -14
  44. package/src/context.js +44 -15
  45. package/src/definition.js +56 -0
  46. package/src/entity.js +15 -0
  47. package/src/index.js +7 -9
  48. package/src/manifest.js +154 -0
  49. package/src/map.js +24 -0
  50. package/src/shortcuts.js +9 -14
  51. package/test/component/collapse.fixtures.js +30 -39
  52. package/test/component/collapse.test.js +90 -10
  53. package/test/component/dependencies.test.js +16 -14
  54. package/test/component/dereference.fixtures.js +10 -98
  55. package/test/component/dereference.test.js +10 -14
  56. package/test/component/dummies/extension/index.js +1 -5
  57. package/test/component/dummies/migrations/conflicting/migrations/0001-one.yaml +2 -0
  58. package/test/component/dummies/migrations/conflicting/migrations/0001-one.yml +2 -0
  59. package/test/component/dummies/migrations/malformed/migrations/0001-one.yaml +1 -0
  60. package/test/component/dummies/migrations/ordered/migrations/0001-first.yml +4 -0
  61. package/test/component/dummies/migrations/ordered/migrations/0002-second.yaml +4 -0
  62. package/test/component/dummies/migrations/ordered/migrations/0010-third.json +1 -0
  63. package/test/component/dummies/migrations/stateless/migrations/0001-one.yaml +2 -0
  64. package/test/component/expand.fixtures.js +13 -13
  65. package/test/component/expand.test.js +17 -13
  66. package/test/component/migrations.test.js +62 -0
  67. package/test/component/normalize.fixtures.js +3 -7
  68. package/test/component/normalize.test.js +94 -28
  69. package/test/component/validate.fixtures.js +15 -13
  70. package/test/component/validate.test.js +180 -109
  71. package/test/context/complete.fixtures.js +2 -8
  72. package/test/context/complete.test.js +14 -8
  73. package/test/context/converge.test.js +57 -0
  74. package/test/context/dereference.fixtures.js +3 -9
  75. package/test/context/dereference.test.js +18 -7
  76. package/test/context/evict.fixtures.js +39 -0
  77. package/test/context/evict.test.js +72 -0
  78. package/test/context/expand.fixtures.js +1 -5
  79. package/test/context/expand.test.js +36 -8
  80. package/test/context/normalize.fixtures.js +2 -7
  81. package/test/context/normalize.test.js +33 -9
  82. package/test/context/validate.fixtures.js +1 -6
  83. package/test/context/validate.test.js +158 -35
  84. package/test/definition.test.js +35 -0
  85. package/test/dummies/bare/index.js +1 -0
  86. package/test/dummies/defined/definition.js +1 -0
  87. package/test/dummies/defined/index.js +1 -0
  88. package/test/dummies/defined/package.json +1 -0
  89. package/test/manifest.test.js +126 -0
  90. package/test/shortcuts.fixtures.js +4 -9
  91. package/test/shortcuts.test.js +17 -15
  92. package/types/component.d.ts +27 -4
  93. package/types/context/declaration.d.ts +34 -3
  94. package/types/context.d.ts +27 -13
  95. package/types/entity.d.ts +7 -0
  96. package/types/index.d.ts +18 -3
  97. package/src/.component/.expand/properties.js +0 -14
  98. package/src/.context/.dependencies/load.js +0 -36
  99. package/test/context/dependencies.load.test.js +0 -24
@@ -0,0 +1,126 @@
1
+ import { it, describe } from 'node:test'
2
+ import assert from 'node:assert/strict'
3
+
4
+ import { resolve } from 'node:path'
5
+
6
+ import { component } from '../src/component.js'
7
+ import { plain, revive } from '../src/manifest.js'
8
+
9
+ /** the prototype itself: an entity, operations and events, and no prototype of its own */
10
+ const PROTOTYPE = resolve(import.meta.dirname, '../../prototype')
11
+
12
+ /** a component on the default prototype, with an entity, events and receivers */
13
+ const COMPONENT = resolve(
14
+ import.meta.dirname,
15
+ '../../../features/steps/.workspace/components/collection/external.consumer'
16
+ )
17
+
18
+ /** a component whose prototype is a directory of the workspace's own */
19
+ const EXTENDED = resolve(
20
+ import.meta.dirname,
21
+ '../../../features/steps/.workspace/components/collection/configuration.extended'
22
+ )
23
+
24
+ const carry = (manifest) => JSON.parse(JSON.stringify(plain(manifest)))
25
+
26
+ describe('a manifest carried through a file', () => {
27
+ for (const [name, path] of [
28
+ ['the prototype', PROTOTYPE],
29
+ ['a component', COMPONENT]
30
+ ])
31
+ it(`comes back the same: ${name}`, async () => {
32
+ const manifest = await component(path)
33
+
34
+ assert.deepStrictEqual(revive(carry(manifest), path), manifest)
35
+ })
36
+
37
+ it('does not carry a key with no value', () => {
38
+ const carried = plain({ name: 'a', namespace: 'b', path: '/x', operations: { echo: { input: undefined } } })
39
+
40
+ assert.deepStrictEqual(carried.operations.echo, {})
41
+ })
42
+
43
+ it('leaves the manifest it was given alone', async () => {
44
+ const manifest = await component(COMPONENT)
45
+ const before = JSON.stringify(manifest)
46
+
47
+ plain(manifest)
48
+
49
+ assert.strictEqual(JSON.stringify(manifest), before)
50
+ })
51
+
52
+ it('is read beside the component wherever it is', async () => {
53
+ const carried = carry(await component(COMPONENT))
54
+ const revived = revive(carried, '/composition/external-consumer')
55
+
56
+ assert.strictEqual(revived.path, '/composition/external-consumer')
57
+
58
+ // what the component declares is in the component
59
+ assert.strictEqual(
60
+ revived.receivers['something_happened'].path,
61
+ '/composition/external-consumer'
62
+ )
63
+ })
64
+
65
+ it('reads what is inherited where the prototype is', async () => {
66
+ const prototype = (await component(COMPONENT)).prototype.path
67
+ const revived = revive(carry(await component(COMPONENT)), '/composition/external-consumer')
68
+
69
+ // an event of the prototype's is in the prototype, wherever the component is
70
+ assert.strictEqual(revived.events.created.path, prototype)
71
+ })
72
+
73
+ it('carries what is inherited by the package that holds it', async () => {
74
+ const carried = carry(await component(COMPONENT))
75
+
76
+ assert.strictEqual(carried.prototype.path, '@toa.io/prototype')
77
+ assert.strictEqual(carried.events.created.path, '@toa.io/prototype')
78
+ assert.strictEqual(carried.receivers['something_happened'].path, '.')
79
+ })
80
+ })
81
+
82
+ describe('what cannot be carried', () => {
83
+ it('leaves out where the manifest is and what only a deploy reads', async () => {
84
+ const manifest = await component(PROTOTYPE)
85
+
86
+ manifest.packages = { cloudinary: '2.11.0' }
87
+
88
+ const carried = plain(manifest)
89
+
90
+ assert.ok(!('locator' in carried))
91
+ assert.ok(!('path' in carried))
92
+ assert.ok(!('packages' in carried))
93
+ })
94
+
95
+ it('refuses a prototype an image would not have', async () => {
96
+ const manifest = await component(EXTENDED)
97
+
98
+ assert.throws(() => plain(manifest), /is in no package, so an image cannot carry what is in it/)
99
+ })
100
+
101
+ it('refuses a path inside the component it does not know about', () => {
102
+ assert.throws(
103
+ () => plain({ name: 'a', namespace: 'b', path: '/x', invented: { at: '/x/y' } }),
104
+ /carries a path of the machine that read it/
105
+ )
106
+ })
107
+
108
+ it('refuses a component directory it does not know about', async () => {
109
+ const manifest = await component(PROTOTYPE)
110
+
111
+ manifest.invented = { at: COMPONENT }
112
+
113
+ assert.throws(() => plain(manifest), /carries a path of the machine that read it/)
114
+ })
115
+
116
+ it('leaves a value of the application\'s own alone', () => {
117
+ const carried = plain({
118
+ name: 'a',
119
+ namespace: 'b',
120
+ path: '/x',
121
+ extensions: { exposition: { routes: [{ path: '/accounts' }] } }
122
+ })
123
+
124
+ assert.strictEqual(carried.extensions.exposition.routes[0].path, '/accounts')
125
+ })
126
+ })
@@ -1,14 +1,9 @@
1
- 'use strict'
1
+ import { generate } from 'randomstring'
2
+ import { random } from '@toa.io/generic'
2
3
 
3
- const { generate } = require('randomstring')
4
- const { random } = require('@toa.io/generic')
5
-
6
- const SHORTCUTS = {
4
+ export const SHORTCUTS = {
7
5
  amqp: '@toa.io/bindings.amqp',
8
6
  mongodb: '@toa.io/storages.mongodb'
9
7
  }
10
8
 
11
- const object = { foo: random(), bar: { baz: generate() } }
12
-
13
- exports.object = object
14
- exports.SHORTCUTS = SHORTCUTS
9
+ export const object = { foo: random(), bar: { baz: generate() } }
@@ -1,10 +1,11 @@
1
- 'use strict'
1
+ import { describe, it, beforeEach } from 'node:test'
2
+ import assert from 'node:assert/strict'
2
3
 
3
- const clone = require('clone-deep')
4
- const { generate } = require('randomstring')
4
+ import clone from 'clone-deep'
5
+ import { generate } from 'randomstring'
5
6
 
6
- const { recognize, resolve } = require('../src/shortcuts')
7
- const fixtures = require('./shortcuts.fixtures')
7
+ import { recognize, resolve } from '../src/shortcuts.js'
8
+ import * as fixtures from './shortcuts.fixtures.js'
8
9
 
9
10
  let object
10
11
 
@@ -14,16 +15,16 @@ beforeEach(() => {
14
15
 
15
16
  describe('resolve', () => {
16
17
  it('should be defined', () => {
17
- expect(resolve).toBeDefined()
18
+ assert.notStrictEqual(resolve, undefined)
18
19
  })
19
20
 
20
21
  it('should resolve', () => {
21
- expect(Object.keys(fixtures.SHORTCUTS).length).toBeGreaterThan(0)
22
+ assert.ok(Object.keys(fixtures.SHORTCUTS).length > 0)
22
23
 
23
24
  for (const [key, value] of Object.entries(fixtures.SHORTCUTS)) {
24
25
  const resolved = resolve(key)
25
26
 
26
- expect(resolved).toStrictEqual(value)
27
+ assert.deepStrictEqual(resolved, value)
27
28
  }
28
29
  })
29
30
  })
@@ -32,7 +33,7 @@ describe('recognize', () => {
32
33
  it('should not change unknown', () => {
33
34
  recognize(fixtures.SHORTCUTS, object)
34
35
 
35
- expect(object).toStrictEqual(fixtures.object)
36
+ assert.deepStrictEqual(object, fixtures.object)
36
37
  })
37
38
 
38
39
  it('should resolve known', () => {
@@ -41,8 +42,8 @@ describe('recognize', () => {
41
42
  recognize(fixtures.SHORTCUTS, object)
42
43
 
43
44
  for (const [alias, name] of Object.entries(fixtures.SHORTCUTS)) {
44
- expect(object[alias]).toBeUndefined()
45
- expect(object[name]).toStrictEqual(known[name])
45
+ assert.strictEqual(object[alias], undefined)
46
+ assert.deepStrictEqual(object[name], known[name])
46
47
  }
47
48
  })
48
49
 
@@ -52,9 +53,10 @@ describe('recognize', () => {
52
53
 
53
54
  recognize(fixtures.SHORTCUTS, object, group)
54
55
 
55
- expect(object[group]).toStrictEqual(known)
56
+ assert.deepStrictEqual(object[group], known)
56
57
 
57
- for (const alias of Object.keys(fixtures.SHORTCUTS)) expect(object[alias]).toBeUndefined()
58
+ for (const alias of Object.keys(fixtures.SHORTCUTS))
59
+ assert.strictEqual(object[alias], undefined)
58
60
  })
59
61
 
60
62
  it('should not overwrite group', () => {
@@ -67,7 +69,7 @@ describe('recognize', () => {
67
69
 
68
70
  recognize(fixtures.SHORTCUTS, object, group)
69
71
 
70
- expect(object[group]).toStrictEqual(expect.objectContaining(existing))
72
+ assert.partialDeepStrictEqual(object[group], existing)
71
73
  })
72
74
 
73
75
  it('should not create empty group', () => {
@@ -75,7 +77,7 @@ describe('recognize', () => {
75
77
 
76
78
  recognize(object, group)
77
79
 
78
- expect(object[group]).toBeUndefined()
80
+ assert.strictEqual(object[group], undefined)
79
81
  })
80
82
 
81
83
  const append = () => {
@@ -1,4 +1,5 @@
1
- import type { Locator, operations } from '@toa.io/core'
1
+ import type { Locator } from '@toa.io/core'
2
+ import type { operations } from '@toa.io/core/types'
2
3
 
3
4
  type Map = {
4
5
  [id: string]: Component
@@ -8,10 +9,13 @@ export type Operation = {
8
9
  type: operations.type
9
10
  scope?: operations.scope
10
11
  bindings?: string[]
12
+ description?: string
11
13
  input?: any
12
14
  output?: any
13
15
  error?: any
14
16
  query?: boolean
17
+ /** served by every process under an address of its own, which a call to it names */
18
+ stateful?: boolean
15
19
  }
16
20
 
17
21
  export type Operations = Record<string, Operation>
@@ -34,14 +38,30 @@ type Receiver = {
34
38
  source?: string
35
39
  }
36
40
 
37
- type Entity = {
38
- schema: Object
41
+ type Migration = {
42
+ id: string
43
+ steps: unknown[]
44
+ /** the prototype it is inherited from; absent on the component's own */
45
+ prototype?: string
46
+ }
47
+
48
+ export type Entity = {
49
+ properties: Record<string, Object>
50
+ required?: string[]
51
+ /** what a record holds before anything is written to it */
52
+ blank?: Record<string, unknown>
39
53
  storage?: string
40
54
  associated?: boolean
55
+ custom?: boolean
56
+ migrations?: Migration[]
41
57
  }
42
58
 
43
59
  type Declaration = {
44
60
  prototype?: string
61
+ /** what this component's version is made of; everything under it by default */
62
+ files?: string[]
63
+ /** what its version is not made of, beside what the bridge states */
64
+ ignore?: string[]
45
65
  namespace: string
46
66
  name: string
47
67
  version: string
@@ -51,10 +71,13 @@ type Declaration = {
51
71
  events?: Events
52
72
  receivers?: Record<string, Receiver>
53
73
  extensions?: Record<string, object>
54
- properties?: Record<string, object>
55
74
  }
56
75
 
57
76
  export type Manifest = Declaration & {
58
77
  locator: Locator
59
78
  path: string
79
+ /** what a deploy installs beside this component for what it declares, by package name */
80
+ packages?: Record<string, string>
81
+ /** Toa deploys nothing for it: the context evicts it, or only evicted components require it */
82
+ evicted?: boolean
60
83
  }
@@ -1,8 +1,37 @@
1
- import { Composition, Registry, Runtime } from '../context'
1
+ import { Registry, Runtime } from '../context.d.ts'
2
2
 
3
+ /** A composition as declared, before its members are resolved. */
3
4
  interface Composition {
4
- name: string,
5
+ name: string
5
6
  components: string[]
7
+ services?: string[]
8
+ replicas?: number
9
+ }
10
+
11
+ /** What Toa does not deploy for a context, whatever else names it: still part of it, deployed by other means. */
12
+ export interface Evicted {
13
+ components?: string[]
14
+ services?: string[]
15
+ }
16
+
17
+ export interface Resources {
18
+ cpu?: [string, string]
19
+ memory?: [string, string]
20
+ }
21
+
22
+ export interface Mono {
23
+ replicas?: number
24
+ resources?: Resources
25
+ }
26
+
27
+ /**
28
+ * Cluster-level ingress configuration, applied to every service that declares one.
29
+ */
30
+ export interface Ingress {
31
+ hosts?: string[]
32
+ class?: string
33
+ annotations?: Record<string, string>
34
+ default?: boolean
6
35
  }
7
36
 
8
37
  export interface Declaration {
@@ -11,7 +40,9 @@ export interface Declaration {
11
40
  version?: string
12
41
  runtime?: Runtime | string
13
42
  registry?: Registry | string
14
- packages?: string
15
43
  compositions?: Composition[]
44
+ evicted?: Evicted
45
+ mono?: Mono
46
+ ingress?: Ingress
16
47
  annotations?: Record<string, object>
17
48
  }
@@ -1,41 +1,55 @@
1
- import { Manifest } from './component'
1
+ import { Manifest } from './component.d.ts'
2
2
  import { Locator } from '@toa.io/core'
3
- import type { Declaration } from './context/declaration'
3
+ import type { Declaration } from './context/declaration.d.ts'
4
4
 
5
- interface Runtime{
5
+ interface Runtime {
6
6
  version: string
7
7
  registry?: string
8
8
  proxy?: string
9
9
  }
10
10
 
11
- interface Registry{
11
+ interface Registry {
12
12
  base?: string
13
13
  platforms?: string[] | null
14
+ services?: 'build' | 'published'
14
15
  build?: {
15
16
  arguments?: string[]
16
17
  run?: string
17
- },
18
+ }
18
19
  credentials: string
19
20
  }
20
21
 
21
- interface Composition{
22
- name: string,
23
- components: _component.Component[]
22
+ interface Composition {
23
+ name: string
24
+ components: Manifest[]
25
+ services?: string[]
26
+ replicas?: number
27
+ /** what a deploy installs for the services this composition runs, by package name */
28
+ packages?: Record<string, string>
24
29
  }
25
30
 
26
- export interface Dependency<T = undefined>{
31
+ export interface Dependency<T = undefined> {
27
32
  locator: Locator
28
- manifest: T,
33
+ manifest: T
29
34
  component: Manifest
30
35
  }
31
36
 
32
- interface Context extends Declaration{
37
+ export interface Context extends Declaration {
33
38
  runtime?: Runtime
34
39
  environment?: string
35
40
  registry?: Registry
36
41
  compositions?: Composition[]
37
- components?: _component.Component[]
42
+ components?: Manifest[]
38
43
  dependencies?: Record<string, Dependency[]>
44
+ /** what a deploy installs for every service, which is what `mono` runs */
45
+ packages?: Record<string, string>
39
46
  }
40
47
 
41
- export function context (path: string, environment?: string): Promise<Context>
48
+ export function context(
49
+ path: string,
50
+ environment?: string,
51
+ options?: {
52
+ /** `false` does not apply eviction: every component is treated as deployed */
53
+ evicted?: boolean
54
+ }
55
+ ): Promise<Context>
@@ -0,0 +1,7 @@
1
+ import type { Entity } from './component.d.ts'
2
+
3
+ /** The schema a stored record must fit. */
4
+ export function schema(entity: Entity): object
5
+
6
+ /** The schema a changeset must fit: the same properties, none of them required. */
7
+ export function changeset(entity: Entity): object
package/types/index.d.ts CHANGED
@@ -1,4 +1,19 @@
1
- export * as context from './context'
2
- export * as component from './component'
1
+ import type { Contract } from '@toa.io/core'
2
+ import type { Context } from './context.d.ts'
3
3
 
4
- export { Manifest } from './component'
4
+ export * as context from './context.d.ts'
5
+ export * as component from './component.d.ts'
6
+
7
+ export { Manifest } from './component.d.ts'
8
+ export * as entity from './entity.d.ts'
9
+
10
+ /** Every component a Context has, by id, with the contract of the version it runs. */
11
+ export function map(context: Context): Record<string, Contract>
12
+
13
+ export interface Definition {
14
+ /** The package's name, or the reference where it has no package.json */
15
+ name: string
16
+ module: Record<string, any>
17
+ }
18
+
19
+ export function definition(reference: string): Promise<Definition>
@@ -1,14 +0,0 @@
1
- 'use strict'
2
-
3
- const { recognize } = require('../../shortcuts')
4
-
5
- function properties (manifest) {
6
- recognize(SHORTCUTS, manifest, 'properties')
7
- recognize(SHORTCUTS, manifest.properties)
8
- }
9
-
10
- const SHORTCUTS = {
11
- queues: '@toa.io/storages.queues'
12
- }
13
-
14
- exports.properties = properties
@@ -1,36 +0,0 @@
1
- 'use strict'
2
-
3
- const { join } = require('node:path')
4
-
5
- /**
6
- * @param {string} path
7
- * @returns {{ metadata: object, module: object }}
8
- */
9
- function load (path) {
10
- const metadata = loadMetadata(path)
11
- const module = loadModule(path)
12
-
13
- return { metadata, module }
14
- }
15
-
16
- function loadMetadata (reference) {
17
- reference = join(reference, 'package.json')
18
-
19
- try {
20
- return require(reference)
21
- } catch {
22
- return null
23
- }
24
- }
25
-
26
- /**
27
- * @param {string} reference
28
- * @returns {object}
29
- */
30
- function loadModule (reference) {
31
- const path = require.resolve(reference)
32
-
33
- return require(path)
34
- }
35
-
36
- exports.load = load
@@ -1,24 +0,0 @@
1
- 'use strict'
2
-
3
- const { join } = require('node:path')
4
- const { load } = require('../../src/.context/.dependencies/load')
5
-
6
- it('should be', async () => {
7
- expect(load).toBeInstanceOf(Function)
8
- })
9
-
10
- it('should load module', async () => {
11
- const path = join(__dirname, '../../')
12
- const { metadata, module } = load(path)
13
-
14
- expect(metadata.name).toStrictEqual('@toa.io/norm')
15
- expect(module.context).toBeInstanceOf(Function)
16
- })
17
-
18
- it('should return null metadata if no package.json', async () => {
19
- const path = join(__dirname, '../../src')
20
- const { metadata, module } = load(path)
21
-
22
- expect(metadata).toBeNull()
23
- expect(module.context).toBeInstanceOf(Function)
24
- })