@toa.io/boot 1.0.0-alpha.288 → 1.0.0-alpha.289

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,17 @@
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.289](https://github.com/toa-io/toa/compare/v1.0.0-alpha.288...v1.0.0-alpha.289) (2026-09-07)
7
+
8
+ * A deploy moves only what changed, and a component sees none of the runtime's environment (#1073) ([f38e3db](https://github.com/toa-io/toa/commit/f38e3db533f24db866c57bfa1ef294eff1eaf499)), closes [#1073](https://github.com/toa-io/toa/issues/1073) [#1064](https://github.com/toa-io/toa/issues/1064) [#1066](https://github.com/toa-io/toa/issues/1066) [#1067](https://github.com/toa-io/toa/issues/1067) [#1068](https://github.com/toa-io/toa/issues/1068) [#1069](https://github.com/toa-io/toa/issues/1069) [#1071](https://github.com/toa-io/toa/issues/1071) [#1070](https://github.com/toa-io/toa/issues/1070) [#1072](https://github.com/toa-io/toa/issues/1072)
9
+
10
+ ### BREAKING CHANGES
11
+
12
+ * a component that read `process.env.TOA_*` reads `context` instead;
13
+ `echo(input)` no longer substitutes from the environment; a bash operation sees no
14
+ `TOA_*`; images no longer set `USER node` — see migrations/289.md.
15
+
16
+
6
17
  # [1.0.0-alpha.288](https://github.com/toa-io/toa/compare/v1.0.0-alpha.287...v1.0.0-alpha.288) (2026-09-06)
7
18
 
8
19
  **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.288",
3
+ "version": "1.0.0-alpha.289",
4
4
  "type": "module",
5
5
  "description": "Toa Boot",
6
6
  "author": "temich <tema.gurtovoy@gmail.com>",
@@ -21,14 +21,14 @@
21
21
  "test": "echo \"Error: run tests from root\" && exit 1"
22
22
  },
23
23
  "dependencies": {
24
- "@toa.io/atomicity": "1.0.0-alpha.288",
25
- "@toa.io/core": "1.0.0-alpha.288",
26
- "@toa.io/generic": "1.0.0-alpha.286",
27
- "@toa.io/norm": "1.0.0-alpha.288",
28
- "@toa.io/schemas": "1.0.0-alpha.287",
24
+ "@toa.io/atomicity": "1.0.0-alpha.289",
25
+ "@toa.io/core": "1.0.0-alpha.289",
26
+ "@toa.io/generic": "1.0.0-alpha.289",
27
+ "@toa.io/norm": "1.0.0-alpha.289",
28
+ "@toa.io/schemas": "1.0.0-alpha.289",
29
29
  "clone-deep": "4.0.1",
30
30
  "dotenv": "17.4.2",
31
31
  "openspan": "1.0.0-alpha.286"
32
32
  },
33
- "gitHead": "d3d696f0a4e287c3c297ac81028d600ad7574112"
33
+ "gitHead": "4fcb482beac5896305d8084cdbc1d5fcf372d108"
34
34
  }
package/src/component.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import { Component, Locator, State, entities } from '@toa.io/core'
2
2
  import { entity as declaration } from '@toa.io/norm'
3
3
  import { schema as compileSchema } from '@toa.io/schemas'
4
+ import { environment } from '@toa.io/generic'
4
5
 
5
6
  import * as boot from './index.js'
6
7
  import { span } from './span.js'
@@ -12,6 +13,10 @@ export const component = async (manifest) => {
12
13
  }
13
14
 
14
15
  const create = async (manifest, locator) => {
16
+ // what a test set after this package loaded goes to the store now, before a module of the
17
+ // component is imported: nothing of it is left in `process.env` for that module to read
18
+ environment.absorb()
19
+
15
20
  await boot.extensions.load(manifest)
16
21
 
17
22
  // the storage is told whether there will be an outbox, so the events come first
@@ -1,11 +1,16 @@
1
1
  import { console } from 'openspan'
2
2
  import { Composition } from '@toa.io/core'
3
3
  import { version } from '@toa.io/runtime'
4
+ import { environment } from '@toa.io/generic'
4
5
 
5
6
  import * as boot from './index.js'
6
7
  import { span } from './span.js'
7
8
 
8
9
  export async function composition(paths, options) {
10
+ // what a test set after this package loaded goes to the store now, before a module of a
11
+ // component is imported: nothing of it is left in `process.env` for that module to read
12
+ environment.absorb()
13
+
9
14
  options = Object.assign({}, options)
10
15
 
11
16
  return span('boot composition', async () => {
package/src/env.js CHANGED
@@ -1,15 +1,22 @@
1
+ import { readFileSync } from 'node:fs'
1
2
  import dotenv from 'dotenv'
2
- import { findUp } from '@toa.io/generic'
3
+ import { environment, findUp } from '@toa.io/generic'
3
4
 
4
5
  /*
5
- * A local run reads what `toa env` wrote. `dotenv` leaves a variable that is already set
6
- * alone, so what the environment says still wins and reading the file costs nothing where
7
- * there is none — a deployment has no `.env` to find.
6
+ * A local run reads what `toa env` wrote. What the file says goes to the environment store
7
+ * and never into `process.env`: a `.env` is where a local run keeps its secrets, and a
8
+ * component's code must not find them. A variable already set is left alone, as `dotenv`
9
+ * left it, so what the environment says still wins — and reading the file costs nothing
10
+ * where there is none: a deployment has no `.env` to find.
8
11
  *
9
12
  * It used to be read only where `TOA_ENV` was unset, taking that one variable as proof the
10
13
  * environment had been injected. Setting it for any other reason then dropped the file
11
14
  * whole, and every variable in it with it.
12
15
  */
16
+ export function load(path) {
17
+ environment.absorbEntries(dotenv.parse(readFileSync(path, 'utf8')))
18
+ }
19
+
13
20
  const path = findUp('.env')
14
21
 
15
- if (path !== undefined) dotenv.config({ path })
22
+ if (path !== undefined) load(path)
package/src/events.js CHANGED
@@ -1,3 +1,5 @@
1
+ import { environment } from '@toa.io/generic'
2
+
1
3
  /**
2
4
  * The events of this component that something consumes. An event nobody consumes is not
3
5
  * published, so it gets no emitter and no exchange, and a component none of whose events are
@@ -12,7 +14,7 @@
12
14
  export const events = (manifest) => {
13
15
  if (manifest.events === undefined) return
14
16
 
15
- const value = process.env[VARIABLE + manifest.locator.uppercase]
17
+ const value = environment.get(VARIABLE + manifest.locator.uppercase)
16
18
 
17
19
  if (value === undefined) return manifest.events
18
20
 
package/src/index.js CHANGED
@@ -1,6 +1,5 @@
1
1
  // the environment is read at module scope elsewhere, so it is loaded first
2
- // oxlint-disable-next-line import/no-unassigned-import
3
- import './env.js'
2
+ import { load as env } from './env.js'
4
3
 
5
4
  import * as bindings from './bindings/index.js'
6
5
  import * as bridge from './bridge.js'
@@ -29,6 +28,7 @@ import { rc } from './rc.js'
29
28
 
30
29
  export {
31
30
  call,
31
+ env,
32
32
  cascade,
33
33
  component,
34
34
  composition,
package/src/span.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import { console, traces } from 'openspan'
2
+ import { environment } from '@toa.io/generic'
2
3
 
3
- const enabled = process.env.TOA_BOOT_TRACE === '1'
4
+ const enabled = environment.get('TOA_BOOT_TRACE') === '1'
4
5
 
5
6
  // Tracing is off until an exporter is configured, and the CLI opens its own span before
6
7
  // any extension gets to configure one — so asking for a boot trace turns the console
package/src/storage.js CHANGED
@@ -20,8 +20,14 @@ export const storage = async (manifest, outbox) => {
20
20
  const factory = new Factory()
21
21
  const storage = factory.storage(manifest.locator, manifest.entity, { outbox })
22
22
 
23
- // a component whose structure nothing will make must not start with the structure it lacks
24
- if (manifest.entity.migrations?.length > 0 && storage.migrates !== true)
23
+ // a component whose structure nothing will make must not start with the structure it lacks;
24
+ // what it inherits was written for descendants that store, and one that stores nothing has
25
+ // nothing to convert
26
+ const own = manifest.entity.migrations?.some(
27
+ (migration) => migration.prototype === undefined
28
+ )
29
+
30
+ if (own === true && storage.migrates !== true)
25
31
  throw new Error(
26
32
  `Component '${manifest.locator.id}' declares migrations, ` +
27
33
  `which storage '${manifest.entity.storage}' does not apply`
@@ -30,6 +30,16 @@ it('should refuse migrations a storage does not apply', async () => {
30
30
  )
31
31
  })
32
32
 
33
+ it('should accept inherited migrations a storage does not apply', async () => {
34
+ const entity = {
35
+ schema,
36
+ storage: '@toa.io/storages.null',
37
+ migrations: [{ id: 'system:0001', steps: [], prototype: 'system' }]
38
+ }
39
+
40
+ assert.notEqual(await storage(manifest(entity), false), undefined)
41
+ })
42
+
33
43
  it('should accept a storage with no migrations', async () => {
34
44
  const entity = { schema, storage: '@toa.io/storages.null' }
35
45