@toa.io/norm 1.0.0-alpha.3 → 1.0.0-alpha.300

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 (98) hide show
  1. package/CHANGELOG.md +124 -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 +5 -8
  7. package/src/.component/.expand/index.js +7 -17
  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 +55 -0
  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 +10 -6
  15. package/src/.component/.normalize/receivers.js +4 -5
  16. package/src/.component/collapse.js +60 -9
  17. package/src/.component/defaults.js +16 -25
  18. package/src/.component/dependencies.js +14 -10
  19. package/src/.component/dereference.js +20 -18
  20. package/src/.component/expand.js +6 -9
  21. package/src/.component/extensions.js +9 -10
  22. package/src/.component/index.js +10 -21
  23. package/src/.component/merge.js +25 -13
  24. package/src/.component/migrations.js +57 -0
  25. package/src/.component/normalize.js +10 -6
  26. package/src/.component/schema.yaml +139 -59
  27. package/src/.component/validate.js +37 -17
  28. package/src/.context/.dependencies/connectors.js +11 -10
  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 +179 -11
  42. package/src/.context/validate.js +8 -14
  43. package/src/component.js +37 -18
  44. package/src/context.js +33 -14
  45. package/src/definition.js +56 -0
  46. package/src/entity.js +15 -0
  47. package/src/index.js +6 -9
  48. package/src/manifest.js +154 -0
  49. package/src/shortcuts.js +9 -14
  50. package/test/component/collapse.fixtures.js +30 -39
  51. package/test/component/collapse.test.js +90 -10
  52. package/test/component/dependencies.test.js +16 -14
  53. package/test/component/dereference.fixtures.js +15 -24
  54. package/test/component/dereference.test.js +18 -8
  55. package/test/component/dummies/extension/index.js +1 -5
  56. package/test/component/dummies/migrations/conflicting/migrations/0001-one.yaml +2 -0
  57. package/test/component/dummies/migrations/conflicting/migrations/0001-one.yml +2 -0
  58. package/test/component/dummies/migrations/malformed/migrations/0001-one.yaml +1 -0
  59. package/test/component/dummies/migrations/ordered/migrations/0001-first.yml +4 -0
  60. package/test/component/dummies/migrations/ordered/migrations/0002-second.yaml +4 -0
  61. package/test/component/dummies/migrations/ordered/migrations/0010-third.json +1 -0
  62. package/test/component/dummies/migrations/stateless/migrations/0001-one.yaml +2 -0
  63. package/test/component/expand.fixtures.js +13 -12
  64. package/test/component/expand.test.js +17 -13
  65. package/test/component/migrations.test.js +62 -0
  66. package/test/component/normalize.fixtures.js +3 -7
  67. package/test/component/normalize.test.js +86 -28
  68. package/test/component/validate.fixtures.js +15 -13
  69. package/test/component/validate.test.js +181 -107
  70. package/test/context/complete.fixtures.js +2 -8
  71. package/test/context/complete.test.js +14 -8
  72. package/test/context/converge.test.js +57 -0
  73. package/test/context/dereference.fixtures.js +3 -9
  74. package/test/context/dereference.test.js +18 -7
  75. package/test/context/evict.fixtures.js +39 -0
  76. package/test/context/evict.test.js +72 -0
  77. package/test/context/expand.fixtures.js +1 -5
  78. package/test/context/expand.test.js +36 -8
  79. package/test/context/normalize.fixtures.js +2 -7
  80. package/test/context/normalize.test.js +33 -9
  81. package/test/context/validate.fixtures.js +1 -6
  82. package/test/context/validate.test.js +143 -34
  83. package/test/definition.test.js +35 -0
  84. package/test/dummies/bare/index.js +1 -0
  85. package/test/dummies/defined/definition.js +1 -0
  86. package/test/dummies/defined/index.js +1 -0
  87. package/test/dummies/defined/package.json +1 -0
  88. package/test/manifest.test.js +126 -0
  89. package/test/shortcuts.fixtures.js +4 -9
  90. package/test/shortcuts.test.js +17 -15
  91. package/types/component.d.ts +24 -5
  92. package/types/context/declaration.d.ts +33 -3
  93. package/types/context.d.ts +19 -13
  94. package/types/entity.d.ts +7 -0
  95. package/types/index.d.ts +12 -3
  96. package/src/.component/.expand/properties.js +0 -14
  97. package/src/.context/.dependencies/load.js +0 -36
  98. 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,10 +38,22 @@ 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
- dependent?: boolean
54
+ associated?: boolean
55
+ custom?: boolean
56
+ migrations?: Migration[]
41
57
  }
42
58
 
43
59
  type Declaration = {
@@ -51,10 +67,13 @@ type Declaration = {
51
67
  events?: Events
52
68
  receivers?: Record<string, Receiver>
53
69
  extensions?: Record<string, object>
54
- properties?: Record<string, object>
55
70
  }
56
71
 
57
72
  export type Manifest = Declaration & {
58
73
  locator: Locator
59
74
  path: string
75
+ /** what a deploy installs beside this component for what it declares, by package name */
76
+ packages?: Record<string, string>
77
+ /** Toa deploys nothing for it: the context evicts it, or only evicted components require it */
78
+ evicted?: boolean
60
79
  }
@@ -1,8 +1,36 @@
1
- import { Composition, Registry, Runtime } from '../context'
1
+ import { Registry, Runtime } from '../context.js'
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
+ }
9
+
10
+ /** What Toa does not deploy for a context, whatever else names it: still part of it, deployed by other means. */
11
+ export interface Evicted {
12
+ components?: string[]
13
+ services?: string[]
14
+ }
15
+
16
+ export interface Resources {
17
+ cpu?: [string, string]
18
+ memory?: [string, string]
19
+ }
20
+
21
+ export interface Mono {
22
+ replicas?: number
23
+ resources?: Resources
24
+ }
25
+
26
+ /**
27
+ * Cluster-level ingress configuration, applied to every service that declares one.
28
+ */
29
+ export interface Ingress {
30
+ hosts?: string[]
31
+ class?: string
32
+ annotations?: Record<string, string>
33
+ default?: boolean
6
34
  }
7
35
 
8
36
  export interface Declaration {
@@ -11,7 +39,9 @@ export interface Declaration {
11
39
  version?: string
12
40
  runtime?: Runtime | string
13
41
  registry?: Registry | string
14
- packages?: string
15
42
  compositions?: Composition[]
43
+ evicted?: Evicted
44
+ mono?: Mono
45
+ ingress?: Ingress
16
46
  annotations?: Record<string, object>
17
47
  }
@@ -1,41 +1,47 @@
1
- import { Manifest } from './component'
1
+ import { Manifest } from './component.js'
2
2
  import { Locator } from '@toa.io/core'
3
- import type { Declaration } from './context/declaration'
3
+ import type { Declaration } from './context/declaration.js'
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
+ /** what a deploy installs for the services this composition runs, by package name */
27
+ packages?: Record<string, string>
24
28
  }
25
29
 
26
- export interface Dependency<T = undefined>{
30
+ export interface Dependency<T = undefined> {
27
31
  locator: Locator
28
- manifest: T,
32
+ manifest: T
29
33
  component: Manifest
30
34
  }
31
35
 
32
- interface Context extends Declaration{
36
+ interface Context extends Declaration {
33
37
  runtime?: Runtime
34
38
  environment?: string
35
39
  registry?: Registry
36
40
  compositions?: Composition[]
37
- components?: _component.Component[]
41
+ components?: Manifest[]
38
42
  dependencies?: Record<string, Dependency[]>
43
+ /** what a deploy installs for every service, which is what `mono` runs */
44
+ packages?: Record<string, string>
39
45
  }
40
46
 
41
- export function context (path: string, environment?: string): Promise<Context>
47
+ export function context(path: string, environment?: string): Promise<Context>
@@ -0,0 +1,7 @@
1
+ import type { Entity } from './component.js'
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,13 @@
1
- export * as context from './context'
2
- export * as component from './component'
1
+ export * as context from './context.js'
2
+ export * as component from './component.js'
3
3
 
4
- export { Manifest } from './component'
4
+ export { Manifest } from './component.js'
5
+ export * as entity from './entity.js'
6
+
7
+ export interface Definition {
8
+ /** The package's name, or the reference where it has no package.json */
9
+ name: string
10
+ module: Record<string, any>
11
+ }
12
+
13
+ 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
- })