@toa.io/norm 1.0.0-alpha.298 → 1.0.0-alpha.299
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +9 -0
- package/package.json +6 -6
- package/src/.component/collapse.js +1 -1
- package/src/.component/schema.yaml +1 -1
- package/src/.context/.dependencies/extensions.js +4 -0
- package/src/.context/converge.js +31 -0
- package/src/.context/index.js +1 -0
- package/src/context.js +3 -0
- package/src/shortcuts.js +1 -0
- package/test/context/converge.test.js +57 -0
- package/test/context/validate.test.js +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,15 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
5
|
|
|
6
|
+
# [1.0.0-alpha.299](https://github.com/toa-io/toa/compare/v1.0.0-alpha.298...v1.0.0-alpha.299) (2026-09-10)
|
|
7
|
+
|
|
8
|
+
### Features
|
|
9
|
+
|
|
10
|
+
* **convergence:** deployments of one context converge ([8a70343](https://github.com/toa-io/toa/commit/8a70343cf6953aab58d4a25bac215508ff8d9a37))
|
|
11
|
+
* **convergence:** what an extension ships converges too ([b3be33d](https://github.com/toa-io/toa/commit/b3be33d2a1db45cfd844288467441aea2f4a722d))
|
|
12
|
+
* **core:** a record carries the region that wrote it ([13ed187](https://github.com/toa-io/toa/commit/13ed187604f06dec66feec2e26ccf9dae30bb048))
|
|
13
|
+
|
|
14
|
+
|
|
6
15
|
# [1.0.0-alpha.298](https://github.com/toa-io/toa/compare/v1.0.0-alpha.297...v1.0.0-alpha.298) (2026-09-08)
|
|
7
16
|
|
|
8
17
|
**Note:** Version bump only for package @toa.io/norm
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@toa.io/norm",
|
|
3
|
-
"version": "1.0.0-alpha.
|
|
3
|
+
"version": "1.0.0-alpha.299",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Toa declarations normalization and validation",
|
|
6
6
|
"author": "temich <tema.gurtovoy@gmail.com>",
|
|
@@ -21,12 +21,12 @@
|
|
|
21
21
|
"test": "echo \"Error: run tests from root\" && exit 1"
|
|
22
22
|
},
|
|
23
23
|
"dependencies": {
|
|
24
|
-
"@toa.io/core": "1.0.0-alpha.
|
|
25
|
-
"@toa.io/definitions": "1.0.0-alpha.
|
|
24
|
+
"@toa.io/core": "1.0.0-alpha.299",
|
|
25
|
+
"@toa.io/definitions": "1.0.0-alpha.299",
|
|
26
26
|
"@toa.io/generic": "1.0.0-alpha.292",
|
|
27
|
-
"@toa.io/prototype": "1.0.0-alpha.
|
|
28
|
-
"@toa.io/schemas": "1.0.0-alpha.
|
|
27
|
+
"@toa.io/prototype": "1.0.0-alpha.299",
|
|
28
|
+
"@toa.io/schemas": "1.0.0-alpha.299",
|
|
29
29
|
"fast-glob": "3.3.3"
|
|
30
30
|
},
|
|
31
|
-
"gitHead": "
|
|
31
|
+
"gitHead": "2ba95b16f2cf06f82d6ff8d436761e843e5e4b43"
|
|
32
32
|
}
|
|
@@ -96,4 +96,4 @@ function inherit(manifest, prototype) {
|
|
|
96
96
|
}
|
|
97
97
|
|
|
98
98
|
/** What the runtime writes into every record. `id` is not among them: a component may own it. */
|
|
99
|
-
const SYSTEM = ['VERSION', 'CREATED', 'UPDATED', 'DELETED']
|
|
99
|
+
const SYSTEM = ['VERSION', 'CREATED', 'UPDATED', 'DELETED', 'REGION']
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Locator } from '@toa.io/core'
|
|
2
|
+
import { converge } from '../converge.js'
|
|
2
3
|
import { component as load } from '../../component.js'
|
|
3
4
|
import { definition } from '../../definition.js'
|
|
4
5
|
|
|
@@ -20,6 +21,9 @@ export const extensions = async (context) => {
|
|
|
20
21
|
|
|
21
22
|
components.push(...extracted)
|
|
22
23
|
|
|
24
|
+
// after the extraction, because what an extension ships converges like anything else
|
|
25
|
+
converge(context, components)
|
|
26
|
+
|
|
23
27
|
for (const component of components) {
|
|
24
28
|
if (component.extensions === undefined) continue
|
|
25
29
|
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A context that declares convergence converges every component that stores anything, and no
|
|
3
|
+
* manifest says so. An extension is loaded, deployed and hooked because a component's
|
|
4
|
+
* `extensions` names it, so this is where they come to name it.
|
|
5
|
+
*
|
|
6
|
+
* **Including the components an extension ships**, which is why this runs where they are
|
|
7
|
+
* known rather than over what the context declares: an application deployed as two regions
|
|
8
|
+
* whose identity does not converge is one where a user registered in the first does not
|
|
9
|
+
* exist in the second.
|
|
10
|
+
*
|
|
11
|
+
* There is nothing per-component to normalize and nothing to opt out of: a region is
|
|
12
|
+
* infrastructure, and what a component stores is not its business. What is genuinely owned by
|
|
13
|
+
* one region — which delayed calls this one makes — is said where that is decided, in
|
|
14
|
+
* `cadence.regions`, and not by holding the records back.
|
|
15
|
+
*
|
|
16
|
+
* @param {toa.norm.Context} context
|
|
17
|
+
* @param {toa.norm.Component[]} components every component the context deploys
|
|
18
|
+
* @returns {void}
|
|
19
|
+
*/
|
|
20
|
+
export const converge = (context, components) => {
|
|
21
|
+
if (context.annotations?.[REFERENCE] === undefined) return
|
|
22
|
+
|
|
23
|
+
for (const component of components) {
|
|
24
|
+
if (component.entity === undefined) continue
|
|
25
|
+
|
|
26
|
+
component.extensions ??= {}
|
|
27
|
+
component.extensions[REFERENCE] ??= null
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const REFERENCE = '@toa.io/extensions.convergence'
|
package/src/.context/index.js
CHANGED
package/src/context.js
CHANGED
|
@@ -30,6 +30,9 @@ export const context = async (root, environment = variables.get('TOA_ENV')) => {
|
|
|
30
30
|
const paths = await glob(resolve(root, COMPONENTS), GLOB)
|
|
31
31
|
|
|
32
32
|
context.components = await Promise.all(paths.map(component))
|
|
33
|
+
|
|
34
|
+
// what a context declares of every component that stores anything, its own and the ones its
|
|
35
|
+
// extensions bring, is given to them where those are known: inside `dependencies`
|
|
33
36
|
context.dependencies = await dependencies(context)
|
|
34
37
|
|
|
35
38
|
dereference(context)
|
package/src/shortcuts.js
CHANGED
|
@@ -48,5 +48,6 @@ export const SHORTCUTS = {
|
|
|
48
48
|
storages: '@toa.io/extensions.storages',
|
|
49
49
|
telemetry: '@toa.io/extensions.telemetry',
|
|
50
50
|
cadence: '@toa.io/extensions.cadence',
|
|
51
|
+
convergence: '@toa.io/extensions.convergence',
|
|
51
52
|
introspection: '@toa.io/extensions.introspection'
|
|
52
53
|
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { it, beforeEach } from 'node:test'
|
|
2
|
+
import assert from 'node:assert/strict'
|
|
3
|
+
|
|
4
|
+
import { converge } from '../../src/.context/index.js'
|
|
5
|
+
|
|
6
|
+
const REFERENCE = '@toa.io/extensions.convergence'
|
|
7
|
+
|
|
8
|
+
/** @type {object} */
|
|
9
|
+
let context
|
|
10
|
+
|
|
11
|
+
/** @type {object[]} */
|
|
12
|
+
let components
|
|
13
|
+
|
|
14
|
+
const component = (id, properties = {}) => ({ id, entity: {}, ...properties })
|
|
15
|
+
|
|
16
|
+
beforeEach(() => {
|
|
17
|
+
context = { annotations: { [REFERENCE]: { priority: 0 } } }
|
|
18
|
+
|
|
19
|
+
components = [
|
|
20
|
+
component('store.orders'),
|
|
21
|
+
// what an extension ships reaches a deployment the same way and converges the same way:
|
|
22
|
+
// an application whose identity does not converge has no users in its second region
|
|
23
|
+
component('identity.basic'),
|
|
24
|
+
component('exposition.octets', { entity: undefined })
|
|
25
|
+
]
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
it('should give the extension to every component that stores anything', () => {
|
|
29
|
+
converge(context, components)
|
|
30
|
+
|
|
31
|
+
assert.deepEqual(components[0].extensions, { [REFERENCE]: null })
|
|
32
|
+
assert.deepEqual(components[1].extensions, { [REFERENCE]: null })
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
it('should leave a component that stores nothing alone', () => {
|
|
36
|
+
converge(context, components)
|
|
37
|
+
|
|
38
|
+
assert.equal(components[2].extensions, undefined)
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
it('should do nothing where the context is not a region', () => {
|
|
42
|
+
delete context.annotations
|
|
43
|
+
|
|
44
|
+
converge(context, components)
|
|
45
|
+
|
|
46
|
+
for (const one of components) assert.equal(one.extensions, undefined)
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
it('should leave what a manifest declared as it was', () => {
|
|
50
|
+
const declaration = { some: 'declaration' }
|
|
51
|
+
|
|
52
|
+
components[0].extensions = { [REFERENCE]: declaration }
|
|
53
|
+
|
|
54
|
+
converge(context, components)
|
|
55
|
+
|
|
56
|
+
assert.equal(components[0].extensions[REFERENCE], declaration)
|
|
57
|
+
})
|
|
@@ -122,7 +122,7 @@ describe('compositions', () => {
|
|
|
122
122
|
|
|
123
123
|
assert.throws(
|
|
124
124
|
() => validate(context),
|
|
125
|
-
(error) => /
|
|
125
|
+
(error) => /must NOT have additional properties/.test(error.message)
|
|
126
126
|
)
|
|
127
127
|
})
|
|
128
128
|
})
|