@toa.io/norm 1.0.0-alpha.297 → 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 CHANGED
@@ -3,6 +3,23 @@
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
+
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)
16
+
17
+ **Note:** Version bump only for package @toa.io/norm
18
+
19
+
20
+
21
+
22
+
6
23
  # [1.0.0-alpha.297](https://github.com/toa-io/toa/compare/v1.0.0-alpha.296...v1.0.0-alpha.297) (2026-09-08)
7
24
 
8
25
  **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.297",
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.293",
25
- "@toa.io/definitions": "1.0.0-alpha.297",
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.289",
28
- "@toa.io/schemas": "1.0.0-alpha.293",
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": "52959bba83817238725f50553d45afde1f231af1"
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']
@@ -89,7 +89,7 @@ properties:
89
89
  type: object
90
90
  propertyNames:
91
91
  not:
92
- enum: [id, VERSION, CREATED, UPDATED, DELETED]
92
+ enum: [id, VERSION, CREATED, UPDATED, DELETED, REGION]
93
93
  migrations:
94
94
  type: array
95
95
  items:
@@ -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'
@@ -1,4 +1,5 @@
1
1
  export { complete } from './complete.js'
2
+ export { converge } from './converge.js'
2
3
  export { dependencies } from './dependencies.js'
3
4
  export { dereference } from './dereference.js'
4
5
  export { expand } from './expand.js'
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) => /Property compoments is not expected to be here/.test(error.message)
125
+ (error) => /must NOT have additional properties/.test(error.message)
126
126
  )
127
127
  })
128
128
  })