@toa.io/boot 1.0.0-alpha.313 → 1.0.0-alpha.315

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,30 @@
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.315](https://github.com/toa-io/toa/compare/v1.0.0-alpha.314...v1.0.0-alpha.315) (2026-09-23)
7
+
8
+ * feat(map)!: leave out of a contract what the runtime gives every component ([b81837a](https://github.com/toa-io/toa/commit/b81837a3bc4771d7ee1c10fe613c9a1d7501bf1a))
9
+
10
+ ### BREAKING CHANGES
11
+
12
+ * the deployed component map is `/etc/toa/.map.json.gz`.
13
+
14
+
15
+ # [1.0.0-alpha.314](https://github.com/toa-io/toa/compare/v1.0.0-alpha.313...v1.0.0-alpha.314) (2026-09-19)
16
+
17
+ * feat(core)!: let an operation state how long its calls are remembered ([af177c2](https://github.com/toa-io/toa/commit/af177c2ab3d00d475840b5ef9e612f04e82b72bd))
18
+
19
+ ### Features
20
+
21
+ * **core:** a destination renders the component's events ([2f93b28](https://github.com/toa-io/toa/commit/2f93b28bed61a0afe99cb8cfa646ed7815467497))
22
+ * **core:** a reference may name a declaration its package claims ([8040f60](https://github.com/toa-io/toa/commit/8040f60976549ab90497675bb54e664261f5b6bb))
23
+
24
+ ### BREAKING CHANGES
25
+
26
+ * `inbox.retention` under 600 is refused. See
27
+ migrations/313.md.
28
+
29
+
6
30
  # [1.0.0-alpha.313](https://github.com/toa-io/toa/compare/v1.0.0-alpha.312...v1.0.0-alpha.313) (2026-09-18)
7
31
 
8
32
  **Note:** Version bump only for package @toa.io/boot
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@toa.io/boot",
3
- "version": "1.0.0-alpha.313",
3
+ "version": "1.0.0-alpha.315",
4
4
  "type": "module",
5
5
  "description": "Toa Boot",
6
6
  "author": "temich <tema.gurtovoy@gmail.com>",
@@ -21,15 +21,15 @@
21
21
  "test": "echo \"Error: run tests from root\" && exit 1"
22
22
  },
23
23
  "dependencies": {
24
- "@toa.io/atomicity": "1.0.0-alpha.312",
25
- "@toa.io/core": "1.0.0-alpha.312",
26
- "@toa.io/definitions": "1.0.0-alpha.313",
24
+ "@toa.io/atomicity": "1.0.0-alpha.315",
25
+ "@toa.io/core": "1.0.0-alpha.315",
26
+ "@toa.io/definitions": "1.0.0-alpha.315",
27
27
  "@toa.io/generic": "1.0.0-alpha.311",
28
- "@toa.io/norm": "1.0.0-alpha.313",
28
+ "@toa.io/norm": "1.0.0-alpha.315",
29
29
  "@toa.io/schemas": "1.0.0-alpha.311",
30
30
  "clone-deep": "4.0.1",
31
- "dotenv": "17.4.2",
31
+ "dotenv": "18.0.0",
32
32
  "openspan": "1.0.0-alpha.305"
33
33
  },
34
- "gitHead": "7da3b2627ceeefd1fa19d5c8851be88d928523b6"
34
+ "gitHead": "d88ed286bf44bf95b5f3d9db6e7d4621261dbb99"
35
35
  }
package/src/component.js CHANGED
@@ -34,6 +34,10 @@ const create = async (manifest, locator) => {
34
34
  )
35
35
  const context = await boot.context(manifest)
36
36
  const emission = await boot.emission(events, locator, context)
37
+
38
+ // a destination that renders the component's events renders them with its context
39
+ await boot.rendering(manifest, destinations, context)
40
+
37
41
  const outbox = boot.outbox(manifest, storage, emission, destinations)
38
42
 
39
43
  let state
@@ -20,12 +20,12 @@ const loading = {}
20
20
  * @returns {Promise<import('@toa.io/core/types').extensions.Factory>}
21
21
  */
22
22
  export const resolve = async (reference, base = process.cwd()) => {
23
- const path = find(reference, base)
23
+ const path = find(pack(reference), base)
24
24
 
25
25
  loading[path] ??= create(path)
26
26
  instances[path] = await loading[path]
27
27
 
28
- return instances[path]
28
+ return keyed(instances[path], reference)
29
29
  }
30
30
 
31
31
  /**
@@ -38,13 +38,37 @@ export const resolve = async (reference, base = process.cwd()) => {
38
38
  * @returns {import('@toa.io/core/types').extensions.Factory}
39
39
  */
40
40
  export const instance = (reference, base = process.cwd()) => {
41
- const factory = instances[find(reference, base)]
41
+ const factory = instances[find(pack(reference), base)]
42
42
 
43
43
  if (factory === undefined) throw new Error(`Extension '${reference}' is not loaded`)
44
44
 
45
- return factory
45
+ return keyed(factory, reference)
46
46
  }
47
47
 
48
+ /**
49
+ * `package#key` is a declaration a package claims beside its main one, and what the package's
50
+ * factory offers for that key answers for it — so a key is never taken for the package itself.
51
+ *
52
+ * @param {import('@toa.io/core/types').extensions.Factory} factory
53
+ * @param {string} reference
54
+ * @returns {import('@toa.io/core/types').extensions.Factory}
55
+ */
56
+ const keyed = (factory, reference) => {
57
+ const key = reference.split('#')[1]
58
+
59
+ if (key === undefined) return factory
60
+
61
+ const claimed = factory.keys?.[key]
62
+
63
+ if (claimed === undefined)
64
+ throw new Error(`'${reference}' names a key its package does not claim`)
65
+
66
+ return claimed
67
+ }
68
+
69
+ /** The package a reference is of. */
70
+ const pack = (reference) => reference.split('#')[0]
71
+
48
72
  const create = async (path) => {
49
73
  const { Factory } = await import(pathToFileURL(require.resolve(path)).href)
50
74
 
package/src/inbox.js CHANGED
@@ -7,4 +7,6 @@
7
7
  * @returns {boolean}
8
8
  */
9
9
  export const inbox = (manifest) =>
10
- Object.values(manifest.operations ?? {}).some((definition) => definition.once === true)
10
+ Object.values(manifest.operations ?? {}).some(
11
+ (definition) => definition.once === true || typeof definition.once === 'number'
12
+ )
package/src/index.js CHANGED
@@ -22,6 +22,7 @@ export { manifest } from './manifest.js'
22
22
  export { operation } from './operation.js'
23
23
  export { receivers, receive } from './receivers.js'
24
24
  export { remote } from './remote.js'
25
+ export { rendering } from './rendering.js'
25
26
  export { storage } from './storage.js'
26
27
  export { guards } from './guards.js'
27
28
  export { host } from './host.js'
package/src/map.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import assert from 'node:assert'
2
+ import { gunzipSync } from 'node:zlib'
2
3
  import { readFile } from 'node:fs/promises'
3
4
  import * as core from '@toa.io/core'
4
5
 
@@ -78,6 +79,9 @@ export const contract = async (id) => {
78
79
  }
79
80
 
80
81
  /**
82
+ * A map is read gzipped where it is deployed and plain where it is not, so what it is is told by
83
+ * its first two bytes rather than by its name.
84
+ *
81
85
  * @param {string} path
82
86
  * @returns {Promise<Record<string, import('@toa.io/core').Contract>>}
83
87
  */
@@ -85,13 +89,19 @@ async function read(path) {
85
89
  let contents
86
90
 
87
91
  try {
88
- contents = await readFile(path, 'utf8')
92
+ contents = await readFile(path)
89
93
  } catch (cause) {
90
94
  throw new Error(`Cannot read the component map '${path}'`, { cause })
91
95
  }
92
96
 
93
97
  try {
94
- return JSON.parse(contents)
98
+ if (contents[0] === 0x1f && contents[1] === 0x8b) contents = gunzipSync(contents)
99
+ } catch (cause) {
100
+ throw new Error(`The component map '${path}' is not readable gzip`, { cause })
101
+ }
102
+
103
+ try {
104
+ return JSON.parse(contents.toString('utf8'))
95
105
  } catch (cause) {
96
106
  throw new Error(`The component map '${path}' is not JSON`, { cause })
97
107
  }
package/src/remote.js CHANGED
@@ -1,4 +1,4 @@
1
- import { Remote, exceptions } from '@toa.io/core'
1
+ import { Remote, contract as coreContract, exceptions } from '@toa.io/core'
2
2
  import { remap } from '@toa.io/generic'
3
3
 
4
4
  import * as boot from './index.js'
@@ -11,13 +11,17 @@ import * as boot from './index.js'
11
11
  * tenant announced — and absent it, what the map this process was given states.
12
12
  */
13
13
  export const remote = async (locator, source, options = {}) => {
14
- const contract = options.contract ?? (await boot.map.contract(locator.id))
14
+ const stated = options.contract ?? (await boot.map.contract(locator.id))
15
15
 
16
- if (contract === undefined)
16
+ if (stated === undefined)
17
17
  throw new exceptions.UnstatedException(
18
18
  `Cannot call '${locator.id}': the component map states nothing of it. Run \`toa map\`.`
19
19
  )
20
20
 
21
+ // a contract states once, or not at all, what the runtime gives every component; the calls
22
+ // are made from the whole of it
23
+ const contract = coreContract.restore(stated)
24
+
21
25
  // a call binds its consumers, which are loaded rather than required
22
26
  const calls =
23
27
  contract.operations === undefined
@@ -0,0 +1,50 @@
1
+ import { Event, Rendering } from '@toa.io/core'
2
+
3
+ import * as boot from './index.js'
4
+
5
+ /**
6
+ * Gives every destination that names the events it renders a `Rendering` of them, made with the
7
+ * component's own bridges. Every event of the manifest may be named, including those nothing
8
+ * consumes and are therefore not published: a destination writes somewhere of its own.
9
+ *
10
+ * @param {toa.norm.Component} manifest
11
+ * @param {import('@toa.io/core/types').outbox.Destination[]} destinations
12
+ * @param {import('@toa.io/core').Context} context
13
+ * @returns {Promise<void>}
14
+ */
15
+ export const rendering = async (manifest, destinations, context) => {
16
+ for (const destination of destinations) {
17
+ if (destination.renders === undefined) continue
18
+
19
+ const events = new Map()
20
+
21
+ for (const label of destination.renders) {
22
+ const definition = manifest.events?.[label]
23
+
24
+ if (definition === undefined)
25
+ throw new Error(
26
+ `Component '${manifest.locator.id}' has no event '${label}' to render for ` +
27
+ `'${destination.name}'`
28
+ )
29
+
30
+ const bridge = await boot.bridge.event(
31
+ definition.bridge,
32
+ definition.path,
33
+ label,
34
+ context
35
+ )
36
+ const event = new Event(
37
+ { ...definition, label: `${manifest.locator.id}.${label}` },
38
+ null,
39
+ bridge
40
+ )
41
+
42
+ events.set(label, event)
43
+ }
44
+
45
+ const rendering = new Rendering(events)
46
+
47
+ destination.rendering = rendering
48
+ destination.depends(rendering)
49
+ }
50
+ }