@toa.io/operations 1.0.0-alpha.284 → 1.0.0-alpha.287

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 (50) hide show
  1. package/CHANGELOG.md +1 -224
  2. package/package.json +4 -4
  3. package/readme.md +43 -0
  4. package/src/deployment/.deployment/.describe/compositions.js +7 -5
  5. package/src/deployment/.deployment/.describe/events.js +7 -7
  6. package/src/deployment/.deployment/.describe/fold.js +10 -3
  7. package/src/deployment/.deployment/.describe/fold.test.js +30 -14
  8. package/src/deployment/.deployment/.describe/mounts.js +4 -7
  9. package/src/deployment/.deployment/.describe/resources.js +7 -5
  10. package/src/deployment/.deployment/.describe/resources.test.js +13 -6
  11. package/src/deployment/.deployment/.describe/services.js +10 -9
  12. package/src/deployment/.deployment/.describe/services.test.js +22 -5
  13. package/src/deployment/.deployment/.describe/variables.js +2 -3
  14. package/src/deployment/.deployment/describe.js +11 -7
  15. package/src/deployment/.deployment/merge.js +11 -6
  16. package/src/deployment/.deployment/merge.test.js +41 -10
  17. package/src/deployment/composition.js +1 -1
  18. package/src/deployment/deployment.js +35 -16
  19. package/src/deployment/factory.js +39 -19
  20. package/src/deployment/images/composition.Dockerfile +1 -1
  21. package/src/deployment/images/composition.js +10 -9
  22. package/src/deployment/images/factory.js +17 -6
  23. package/src/deployment/images/format.js +9 -5
  24. package/src/deployment/images/image.fixtures.js +2 -2
  25. package/src/deployment/images/image.js +38 -21
  26. package/src/deployment/images/image.test.js +7 -2
  27. package/src/deployment/images/mono.Dockerfile +1 -1
  28. package/src/deployment/images/mono.js +10 -9
  29. package/src/deployment/images/publish.js +98 -0
  30. package/src/deployment/images/runtime-base.test.js +12 -6
  31. package/src/deployment/images/service.Dockerfile +1 -1
  32. package/src/deployment/images/service.js +4 -4
  33. package/src/deployment/operator.js +8 -8
  34. package/src/deployment/registry.js +89 -61
  35. package/src/deployment/registry.test.js +117 -43
  36. package/src/deployment/service.js +3 -1
  37. package/src/deployment/workspace.js +1 -1
  38. package/src/process.js +1 -1
  39. package/src/process.test.js +8 -4
  40. package/types/_deployment/composition.d.ts +0 -2
  41. package/types/_deployment/dependency.d.ts +10 -6
  42. package/types/_deployment/deployment.d.ts +0 -4
  43. package/types/_deployment/factory.d.ts +0 -2
  44. package/types/_deployment/images/factory.d.ts +4 -6
  45. package/types/_deployment/images/image.d.ts +2 -4
  46. package/types/_deployment/images/registry.d.ts +7 -10
  47. package/types/_deployment/operator.d.ts +5 -7
  48. package/types/_deployment/registry.d.ts +6 -8
  49. package/types/_deployment/service.d.ts +0 -2
  50. package/types/dependency.ts +3 -0
package/CHANGELOG.md CHANGED
@@ -3,229 +3,6 @@
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.284](https://github.com/toa-io/toa/compare/v1.0.0-alpha.283...v1.0.0-alpha.284) (2026-09-05)
6
+ # [1.0.0-alpha.287](https://github.com/toa-io/toa/compare/v1.0.0-alpha.286...v1.0.0-alpha.287) (2026-09-06)
7
7
 
8
8
  **Note:** Version bump only for package @toa.io/operations
9
-
10
-
11
-
12
-
13
-
14
- # [1.0.0-alpha.283](https://github.com/toa-io/toa/compare/v1.0.0-alpha.282...v1.0.0-alpha.283) (2026-09-04)
15
-
16
- ### Bug Fixes
17
-
18
- * **lint:** lint the operations package ([aab0db5](https://github.com/toa-io/toa/commit/aab0db5ebcec05f5f49db716b86ea71f4377cfe3))
19
-
20
- ### Features
21
-
22
- * **operations:** run extension services inside a composition ([154aced](https://github.com/toa-io/toa/commit/154aced047228a250ac3bab353e09432fcc5a34d))
23
-
24
-
25
- # [1.0.0-alpha.282](https://github.com/toa-io/toa/compare/v1.0.0-alpha.281...v1.0.0-alpha.282) (2026-09-03)
26
-
27
- **Note:** Version bump only for package @toa.io/operations
28
-
29
-
30
-
31
-
32
-
33
- # [1.0.0-alpha.280](https://github.com/toa-io/toa/compare/v1.0.0-alpha.279...v1.0.0-alpha.280) (2026-09-03)
34
-
35
- ### Bug Fixes
36
-
37
- * **operations:** call execa, not the module it lives in ([9fe87d5](https://github.com/toa-io/toa/commit/9fe87d54cc02f7ec18e325c85882ff174acbe8ab))
38
-
39
-
40
- # [1.0.0-alpha.278](https://github.com/toa-io/toa/compare/v1.0.0-alpha.277...v1.0.0-alpha.278) (2026-09-03)
41
-
42
- **Note:** Version bump only for package @toa.io/operations
43
-
44
-
45
-
46
-
47
-
48
- # [1.0.0-alpha.277](https://github.com/toa-io/toa/compare/v1.0.0-alpha.276...v1.0.0-alpha.277) (2026-09-03)
49
-
50
- ### Bug Fixes
51
-
52
- * read a manifest the way js-yaml read one before ([fcea1b2](https://github.com/toa-io/toa/commit/fcea1b21996d6efa6c3198acbfc9f70f1ce0c35c))
53
- * settle what the module loader made asynchronous ([d4ab9df](https://github.com/toa-io/toa/commit/d4ab9dfb09e382da311dba08f52214e7645f4a6c))
54
- * what the loader resolves, and what it will not ([1b8a02f](https://github.com/toa-io/toa/commit/1b8a02ff0a5e0f18ee62e3f71bc81116f86f9af1))
55
-
56
-
57
- # [1.0.0-alpha.276](https://github.com/toa-io/toa/compare/v1.0.0-alpha.275...v1.0.0-alpha.276) (2026-09-03)
58
-
59
-
60
- ### Bug Fixes
61
-
62
- * **operations:** let a deployment state that it has no probe ([477b483](https://github.com/toa-io/toa/commit/477b483560cf2464d486a78e432f8e9b47f011dd))
63
-
64
-
65
-
66
-
67
-
68
- # [1.0.0-alpha.274](https://github.com/toa-io/toa/compare/v1.0.0-alpha.273...v1.0.0-alpha.274) (2026-09-02)
69
-
70
- **Note:** Version bump only for package @toa.io/operations
71
-
72
-
73
-
74
-
75
-
76
- # [1.0.0-alpha.273](https://github.com/toa-io/toa/compare/v1.0.0-alpha.272...v1.0.0-alpha.273) (2026-09-02)
77
-
78
- **Note:** Version bump only for package @toa.io/operations
79
-
80
-
81
-
82
-
83
-
84
- # [1.0.0-alpha.272](https://github.com/toa-io/toa/compare/v1.0.0-alpha.271...v1.0.0-alpha.272) (2026-09-01)
85
-
86
-
87
- * feat(atomicity)!: take a quorum of independent servers ([862562f](https://github.com/toa-io/toa/commit/862562f24d77ec00127dbbd88d43802b208c643b))
88
- * refactor(atomicity)!: take one Redis, not a cluster ([b0a4770](https://github.com/toa-io/toa/commit/b0a4770dff5dfe8fee4b9ae637a4dd297454fcd4))
89
- * refactor(core)!: pump the outbox in one cycle ([bf590fe](https://github.com/toa-io/toa/commit/bf590fe8ed59c701b5fd1a91ba4874685b79242f))
90
- * refactor(atomicity)!: rename the connector and free it of the outbox ([21f1a41](https://github.com/toa-io/toa/commit/21f1a41aceafcfd35c7620014062fe46b54afa95))
91
- * feat(core)!: commit events with the state that produced them ([1eb68cc](https://github.com/toa-io/toa/commit/1eb68cc435dbfa03faa16009fceb866693d22e1a)), closes [#20](https://github.com/toa-io/toa/issues/20)
92
-
93
-
94
- ### Features
95
-
96
- * **atomicity:** make the interval a setting, and stop repeating n-and-i ([5c3a6d1](https://github.com/toa-io/toa/commit/5c3a6d1533751a21b7eb7d9c737f1a270bf5a5ae))
97
-
98
-
99
- ### BREAKING CHANGES
100
-
101
- * `atomicity.redis` accepts a list again, of independent servers rather
102
- than cluster nodes, and refuses an even number of them.
103
-
104
- Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
105
- * `atomicity.redis` is a string. A list is refused at export.
106
-
107
- Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
108
- * `Storage.outbox.pending` takes a fourth argument, the id to
109
- continue from.
110
-
111
- Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
112
- * `@toa.io/partitions.redis` is now `@toa.io/atomicity`, and its
113
- `lanes(total)` is `slots(total)`. Redis is declared as `atomicity` in the context
114
- rather than `outbox.redis`, and read from `TOA_ATOMICITY_REDIS`.
115
- `TOA_OUTBOX_PARTITION_INTERVAL` is `TOA_ATOMICITY_INTERVAL`.
116
-
117
- Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
118
- * `event.changeset` is removed; use `origin` and `state`. `State`
119
- takes an `Outbox` in place of an `Emission`. `difference` is dropped from
120
- `@toa.io/generic` along with its last caller.
121
-
122
- Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
123
-
124
-
125
-
126
-
127
-
128
- # [1.0.0-alpha.270](https://github.com/toa-io/toa/compare/v1.0.0-alpha.269...v1.0.0-alpha.270) (2026-08-31)
129
-
130
-
131
- ### Bug Fixes
132
-
133
- * **operations:** let a composition set its own base image ([61ba331](https://github.com/toa-io/toa/commit/61ba3319dc91915a3197d348111af411bbd5bff1))
134
-
135
-
136
-
137
-
138
-
139
- # [1.0.0-alpha.266](https://github.com/toa-io/toa/compare/v1.0.0-alpha.265...v1.0.0-alpha.266) (2026-08-29)
140
-
141
- **Note:** Version bump only for package @toa.io/operations
142
-
143
-
144
-
145
-
146
-
147
- # [1.0.0-alpha.265](https://github.com/toa-io/toa/compare/v1.0.0-alpha.264...v1.0.0-alpha.265) (2026-08-29)
148
-
149
- **Note:** Version bump only for package @toa.io/operations
150
-
151
-
152
-
153
-
154
-
155
- # [1.0.0-alpha.264](https://github.com/toa-io/toa/compare/v1.0.0-alpha.263...v1.0.0-alpha.264) (2026-08-29)
156
-
157
- **Note:** Version bump only for package @toa.io/operations
158
-
159
-
160
-
161
-
162
-
163
- # [1.0.0-alpha.263](https://github.com/toa-io/toa/compare/v1.0.0-alpha.262...v1.0.0-alpha.263) (2026-08-29)
164
-
165
- **Note:** Version bump only for package @toa.io/operations
166
-
167
-
168
-
169
-
170
-
171
- # [1.0.0-alpha.262](https://github.com/toa-io/toa/compare/v1.0.0-alpha.261...v1.0.0-alpha.262) (2026-08-28)
172
-
173
-
174
- ### Bug Fixes
175
-
176
- * **cli:** boot toa mono from components and env, not context ([1518f72](https://github.com/toa-io/toa/commit/1518f729d7611ae88e0538f286231d283a7098ef))
177
-
178
-
179
- * feat(operations)!: require service ports to be unique ([b22cf87](https://github.com/toa-io/toa/commit/b22cf8770d24c555b5f355a5f117a9043a57799b))
180
-
181
-
182
- ### Features
183
-
184
- * **operations:** let a service claim a path prefix on the host ([02677d1](https://github.com/toa-io/toa/commit/02677d17fcd4222b07c3be458d93bcc496a016a8))
185
-
186
-
187
- ### BREAKING CHANGES
188
-
189
- * an application whose own extension runs a service on a port
190
- already taken by another — 8000 by the exposition gateway, 8001 by the telemetry
191
- readiness probe — must move it.
192
-
193
- Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
194
-
195
-
196
-
197
-
198
-
199
- # [1.0.0-alpha.261](https://github.com/toa-io/toa/compare/v1.0.0-alpha.260...v1.0.0-alpha.261) (2026-08-26)
200
-
201
-
202
- ### Bug Fixes
203
-
204
- * **operations:** include extension pointer variables in --mono ([f8be23d](https://github.com/toa-io/toa/commit/f8be23d2aaf7bbe1b0580101c8bf4d32c3cab99f))
205
-
206
-
207
-
208
-
209
-
210
- # [1.0.0-alpha.259](https://github.com/toa-io/toa/compare/v1.0.0-alpha.258...v1.0.0-alpha.259) (2026-08-24)
211
-
212
- **Note:** Version bump only for package @toa.io/operations
213
-
214
-
215
-
216
-
217
-
218
- # [1.0.0-alpha.257](https://github.com/toa-io/toa/compare/v1.0.0-alpha.256...v1.0.0-alpha.257) (2026-08-24)
219
-
220
- **Note:** Version bump only for package @toa.io/operations
221
-
222
-
223
-
224
-
225
-
226
- # [1.0.0-alpha.256](https://github.com/toa-io/toa/compare/v1.0.0-alpha.255...v1.0.0-alpha.256) (2026-08-23)
227
-
228
-
229
- ### Features
230
-
231
- * deploy a single mono image with toa deploy --mono ([a7f14f9](https://github.com/toa-io/toa/commit/a7f14f9877a280e9c74d16c195028b35d74fb15a))
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@toa.io/operations",
3
- "version": "1.0.0-alpha.284",
3
+ "version": "1.0.0-alpha.287",
4
4
  "type": "module",
5
5
  "description": "Toa Deployment",
6
6
  "homepage": "https://toa.io",
@@ -28,11 +28,11 @@
28
28
  "test": "echo \"Error: run tests from root\" && exit 1"
29
29
  },
30
30
  "dependencies": {
31
- "@toa.io/generic": "1.0.0-alpha.283",
32
- "@toa.io/norm": "1.0.0-alpha.284",
31
+ "@toa.io/generic": "1.0.0-alpha.286",
32
+ "@toa.io/norm": "1.0.0-alpha.287",
33
33
  "execa": "10.0.1",
34
34
  "fs-extra": "11.4.0",
35
35
  "js-yaml": "5.4.1"
36
36
  },
37
- "gitHead": "006755556879e91e8d68fa86753aa3b475bdf4bf"
37
+ "gitHead": "5c7944a70c16065ab6372072385cc1f02f5db77c"
38
38
  }
package/readme.md CHANGED
@@ -38,6 +38,49 @@ registry:
38
38
  run: npm i -g @toa.io/runtime --omit=dev
39
39
  ```
40
40
 
41
+ #### Extension Service Images
42
+
43
+ `registry.services` says where an extension service's image comes from.
44
+
45
+ `build`, the default, builds one per service into `registry.base`. Its tag carries the
46
+ runtime version, so every Toa release rebuilds them all.
47
+
48
+ `published` deploys the image the extension ships and builds nothing:
49
+
50
+ ```yaml
51
+ # context.toa.yaml
52
+
53
+ registry:
54
+ services: published
55
+ ```
56
+
57
+ The images are tagged with the runtime version:
58
+
59
+ ```
60
+ ghcr.io/toa-io/extension-exposition-gateway:1.0.0-alpha.285
61
+ ghcr.io/toa-io/extension-realtime-streams:1.0.0-alpha.285
62
+ ghcr.io/toa-io/extension-introspection-explorer:1.0.0-alpha.285
63
+ ghcr.io/toa-io/extension-configuration-values:1.0.0-alpha.285
64
+ ```
65
+
66
+ The cluster pulls them from `ghcr.io` rather than from `registry.base`. They are public and
67
+ `registry.credentials` does not apply; a cluster that cannot reach `ghcr.io` needs `build`.
68
+
69
+ The field is environment-scoped like the rest:
70
+
71
+ ```yaml
72
+ # context.toa.yaml
73
+
74
+ registry:
75
+ base@local: localhost:5000
76
+ base@production: registry.digitalocean.com/acme
77
+ services: published
78
+ services@local: build
79
+ ```
80
+
81
+ An extension that states no image is built whatever this says, as is a service a
82
+ composition runs in its own pod.
83
+
41
84
  #### Build Options
42
85
 
43
86
  ```yaml
@@ -2,15 +2,17 @@ import { addVariables } from './variables.js'
2
2
  import { addMounts } from './mounts.js'
3
3
  import { fold } from './fold.js'
4
4
 
5
- export function compositions (compositions, dependency) {
5
+ export function compositions(compositions, dependency) {
6
6
  for (const composition of compositions) {
7
- const claimed = (dependency.services ?? [])
8
- .filter((service) => service.workload?.includes(composition.name) === true)
7
+ const claimed = (dependency.services ?? []).filter(
8
+ (service) => service.workload?.includes(composition.name) === true
9
+ )
9
10
 
10
11
  // a service brings components of its own — the identity components inside the gateway —
11
12
  // and their variables are keyed by their own labels, not by the composition's
12
- const keys = composition.components
13
- .concat(...claimed.map((service) => service.components ?? []))
13
+ const keys = composition.components.concat(
14
+ ...claimed.map((service) => service.components ?? [])
15
+ )
14
16
 
15
17
  addVariables(composition, dependency.variables, keys)
16
18
  addMounts(composition, dependency.mounts, keys)
@@ -9,15 +9,16 @@
9
9
  * @param {toa.norm.Context} context
10
10
  * @param {toa.deployment.Dependency} dependency
11
11
  */
12
- export function events (context, dependency) {
12
+ export function events(context, dependency) {
13
13
  const components = deployed(context)
14
14
  const consumed = collect(components, context.events, dependency.events)
15
15
 
16
16
  for (const { locator, events } of components) {
17
17
  if (events === undefined) continue
18
18
 
19
- const labels = Object.keys(events)
20
- .filter((label) => consumed.has(locator.id + '.' + label))
19
+ const labels = Object.keys(events).filter((label) =>
20
+ consumed.has(locator.id + '.' + label)
21
+ )
21
22
 
22
23
  dependency.variables[locator.label] ??= []
23
24
 
@@ -33,12 +34,11 @@ export function events (context, dependency) {
33
34
  * holds only the former; the latter reach a deployment as instances of what they depend on,
34
35
  * which is also how they are given the rest of their variables.
35
36
  */
36
- function deployed (context) {
37
+ function deployed(context) {
37
38
  const components = new Map()
38
39
 
39
40
  for (const instances of Object.values(context.dependencies ?? {}))
40
- for (const { component } of instances)
41
- components.set(component.locator.id, component)
41
+ for (const { component } of instances) components.set(component.locator.id, component)
42
42
 
43
43
  return [...components.values()]
44
44
  }
@@ -47,7 +47,7 @@ function deployed (context) {
47
47
  * Everything that consumes an event of this context: the receivers of its own components, what
48
48
  * a dependency declares it consumes, and what the context says is consumed outside it.
49
49
  */
50
- function collect (components, declared, contributed) {
50
+ function collect(components, declared, contributed) {
51
51
  const consumed = new Set(declared ?? [])
52
52
 
53
53
  for (const label of contributed ?? []) consumed.add(label)
@@ -10,7 +10,7 @@
10
10
  * @param {toa.deployment.dependency.Service[]} services
11
11
  * @param {toa.deployment.Dependency} dependency
12
12
  */
13
- export function fold (workload, services, dependency) {
13
+ export function fold(workload, services, dependency) {
14
14
  workload.variables ??= []
15
15
 
16
16
  for (const service of services) {
@@ -21,13 +21,20 @@ export function fold (workload, services, dependency) {
21
21
 
22
22
  // every declared port is bound by the single process, none is primary
23
23
  if (service.port !== undefined)
24
- (workload.backends ??= []).push({ port: service.port, path: service.ingress?.path ?? '/' })
24
+ (workload.backends ??= []).push({
25
+ port: service.port,
26
+ path: service.ingress?.path ?? '/'
27
+ })
25
28
 
26
29
  if (service.probe !== undefined && service.probe !== false)
27
30
  workload.probe = service.probe
28
31
  }
29
32
 
30
- if (workload.probe === undefined && dependency.probe !== undefined && dependency.probe !== false)
33
+ if (
34
+ workload.probe === undefined &&
35
+ dependency.probe !== undefined &&
36
+ dependency.probe !== false
37
+ )
31
38
  workload.probe = dependency.probe
32
39
 
33
40
  // the more specific prefix must come first, whatever the controller's tie-break
@@ -10,8 +10,10 @@ it('should collect the ports of every service', () => {
10
10
 
11
11
  fold(workload, [service('one', { port: 8000 }), service('two', { port: 8002 })], {})
12
12
 
13
- assert.deepStrictEqual(workload.backends,
14
- [{ port: 8000, path: '/' }, { port: 8002, path: '/' }])
13
+ assert.deepStrictEqual(workload.backends, [
14
+ { port: 8000, path: '/' },
15
+ { port: 8002, path: '/' }
16
+ ])
15
17
  })
16
18
 
17
19
  it('should take the path a service declares', () => {
@@ -25,12 +27,19 @@ it('should take the path a service declares', () => {
25
27
  it('should put the more specific prefix first', () => {
26
28
  const workload = {}
27
29
 
28
- fold(workload, [
29
- service('one', { port: 8000, ingress: { path: '/' } }),
30
- service('two', { port: 8002, ingress: { path: '/explorer' } })
31
- ], {})
32
-
33
- assert.deepStrictEqual(workload.backends.map((backend) => backend.path), ['/explorer', '/'])
30
+ fold(
31
+ workload,
32
+ [
33
+ service('one', { port: 8000, ingress: { path: '/' } }),
34
+ service('two', { port: 8002, ingress: { path: '/explorer' } })
35
+ ],
36
+ {}
37
+ )
38
+
39
+ assert.deepStrictEqual(
40
+ workload.backends.map((backend) => backend.path),
41
+ ['/explorer', '/']
42
+ )
34
43
  })
35
44
 
36
45
  it('should leave a service without a port out of the backends', () => {
@@ -45,12 +54,19 @@ describe('variables', () => {
45
54
  it('should take those of every service', () => {
46
55
  const workload = {}
47
56
 
48
- fold(workload, [
49
- service('one', { variables: [{ name: 'A', value: '1' }] }),
50
- service('two', { variables: [{ name: 'B', value: '2' }] })
51
- ], {})
52
-
53
- assert.deepStrictEqual(workload.variables, [{ name: 'A', value: '1' }, { name: 'B', value: '2' }])
57
+ fold(
58
+ workload,
59
+ [
60
+ service('one', { variables: [{ name: 'A', value: '1' }] }),
61
+ service('two', { variables: [{ name: 'B', value: '2' }] })
62
+ ],
63
+ {}
64
+ )
65
+
66
+ assert.deepStrictEqual(workload.variables, [
67
+ { name: 'A', value: '1' },
68
+ { name: 'B', value: '2' }
69
+ ])
54
70
  })
55
71
 
56
72
  it('should keep the first of a name', () => {
@@ -1,16 +1,13 @@
1
- export function addMounts (composition, mounts, keys = composition.components) {
2
- if (mounts === undefined)
3
- return
1
+ export function addMounts(composition, mounts, keys = composition.components) {
2
+ if (mounts === undefined) return
4
3
 
5
4
  const used = new Set()
6
5
 
7
6
  for (const [key, mount] of Object.entries(mounts)) {
8
- if (key !== 'global' && !keys?.includes(key))
9
- continue
7
+ if (key !== 'global' && !keys?.includes(key)) continue
10
8
 
11
9
  for (const { name, path, claim } of mount) {
12
- if (used.has(name))
13
- continue
10
+ if (used.has(name)) continue
14
11
 
15
12
  composition.mounts ??= []
16
13
  composition.mounts.push({ name, path, claim })
@@ -8,20 +8,22 @@
8
8
  * Deploying without any is a decision rather than an omission, and it is spelled
9
9
  * `resources: null` — at either place.
10
10
  */
11
- export function resources (context, values) {
11
+ export function resources(context, values) {
12
12
  for (const unit of units(values)) {
13
13
  // `null` is an answer and `undefined` is not one, so the fallback reads only the latter
14
14
  if (unit.deployment.resources === undefined)
15
15
  unit.deployment.resources = context.resources
16
16
 
17
17
  if (unit.deployment.resources === undefined)
18
- throw new Error(`${unit.subject} declares no resources. ` +
19
- 'Declare them on it or as the context\'s \'resources\', ' +
20
- 'or \'resources: null\' to deploy it without any.')
18
+ throw new Error(
19
+ `${unit.subject} declares no resources. ` +
20
+ "Declare them on it or as the context's 'resources', " +
21
+ "or 'resources: null' to deploy it without any."
22
+ )
21
23
  }
22
24
  }
23
25
 
24
- function * units (values) {
26
+ function* units(values) {
25
27
  if (values.mono !== undefined)
26
28
  yield { deployment: values.mono, subject: 'The mono deployment' }
27
29
 
@@ -32,22 +32,29 @@ describe('refusal', () => {
32
32
  it('should refuse a composition that declares none', () => {
33
33
  const values = { compositions: [{ name: 'edge' }] }
34
34
 
35
- assert.throws(() => resources({}, values),
36
- (error) => /^Composition 'edge' declares no resources\./.test(error.message))
35
+ assert.throws(
36
+ () => resources({}, values),
37
+ (error) => /^Composition 'edge' declares no resources\./.test(error.message)
38
+ )
37
39
  })
38
40
 
39
41
  it('should refuse a service that declares none', () => {
40
42
  const values = { services: [{ name: 'exposition-gateway' }] }
41
43
 
42
- assert.throws(() => resources({}, values),
43
- (error) => /^Service 'exposition-gateway' declares no resources\./.test(error.message))
44
+ assert.throws(
45
+ () => resources({}, values),
46
+ (error) =>
47
+ /^Service 'exposition-gateway' declares no resources\./.test(error.message)
48
+ )
44
49
  })
45
50
 
46
51
  it('should refuse the mono deployment when it declares none', () => {
47
52
  const values = { mono: {} }
48
53
 
49
- assert.throws(() => resources({}, values),
50
- (error) => /^The mono deployment declares no resources\./.test(error.message))
54
+ assert.throws(
55
+ () => resources({}, values),
56
+ (error) => /^The mono deployment declares no resources\./.test(error.message)
57
+ )
51
58
  })
52
59
 
53
60
  it('should not ask a service a composition runs', () => {
@@ -1,18 +1,16 @@
1
1
  import { addVariables } from './variables.js'
2
2
 
3
- export function services (services, variables, probe, ingress) {
3
+ export function services(services, variables, probe, ingress) {
4
4
  for (const service of services) {
5
5
  // one a composition runs has no deployment of its own to carry them; the composition
6
6
  // it runs in states them, and repeating them here is dead weight in the values
7
7
  if (service.workload === undefined) addVariables(service, variables)
8
8
 
9
- if (service.probe === false)
10
- delete service.probe
9
+ if (service.probe === false) delete service.probe
11
10
  else if (service.probe === undefined && probe !== undefined && probe !== false)
12
11
  service.probe = probe
13
12
 
14
- if (service.ingress !== undefined)
15
- expose(service, ingress)
13
+ if (service.ingress !== undefined) expose(service, ingress)
16
14
  }
17
15
  }
18
16
 
@@ -21,15 +19,18 @@ export function services (services, variables, probe, ingress) {
21
19
  * the context's business, so everything else comes from its `ingress` section.
22
20
  * What the service did declare wins.
23
21
  */
24
- function expose (service, ingress = {}) {
22
+ function expose(service, ingress = {}) {
25
23
  const declared = Object.fromEntries(
26
- Object.entries(service.ingress).filter(([, value]) => value !== undefined))
24
+ Object.entries(service.ingress).filter(([, value]) => value !== undefined)
25
+ )
27
26
 
28
27
  service.ingress = Object.assign({}, ingress, declared)
29
28
 
30
29
  if (service.ingress.hosts === undefined)
31
- throw new Error(`Service '${service.name}' declares an ingress, but no hosts are defined. ` +
32
- "Declare them in the context's 'ingress' section.")
30
+ throw new Error(
31
+ `Service '${service.name}' declares an ingress, but no hosts are defined. ` +
32
+ "Declare them in the context's 'ingress' section."
33
+ )
33
34
 
34
35
  if (service.port === undefined)
35
36
  throw new Error(`Service '${service.name}' declares an ingress, but no port.`)
@@ -3,7 +3,12 @@ import assert from 'node:assert/strict'
3
3
 
4
4
  import { services } from './services.js'
5
5
 
6
- const service = (extra = {}) => ({ group: 'group', name: 'group-one', version: '0', ...extra })
6
+ const service = (extra = {}) => ({
7
+ group: 'group',
8
+ name: 'group-one',
9
+ version: '0',
10
+ ...extra
11
+ })
7
12
 
8
13
  it('should leave a service without an ingress alone', () => {
9
14
  const list = [service()]
@@ -20,7 +25,11 @@ it('should leave a service without an ingress alone', () => {
20
25
  it('should supply hosts, class and annotations from the context', () => {
21
26
  const list = [service({ port: 8002, ingress: { path: '/.introspection' } })]
22
27
 
23
- services(list, {}, undefined, { hosts: ['api.dev'], class: 'alb', annotations: { a: 'b' } })
28
+ services(list, {}, undefined, {
29
+ hosts: ['api.dev'],
30
+ class: 'alb',
31
+ annotations: { a: 'b' }
32
+ })
24
33
 
25
34
  assert.deepStrictEqual(list[0].ingress, {
26
35
  path: '/.introspection',
@@ -50,12 +59,20 @@ it('should ignore properties the service left undefined', () => {
50
59
  it('should reject an ingress with nowhere to land', () => {
51
60
  const list = [service({ port: 8002, ingress: { path: '/.introspection' } })]
52
61
 
53
- assert.throws(() => services(list, {}, undefined, undefined), (error) => /Service 'group-one' declares an ingress, but no hosts are defined/.test(error.message))
62
+ assert.throws(
63
+ () => services(list, {}, undefined, undefined),
64
+ (error) =>
65
+ /Service 'group-one' declares an ingress, but no hosts are defined/.test(
66
+ error.message
67
+ )
68
+ )
54
69
  })
55
70
 
56
71
  it('should reject an ingress without a port', () => {
57
72
  const list = [service({ ingress: { path: '/.introspection' } })]
58
73
 
59
- assert.throws(() => services(list, {}, undefined, { hosts: ['api.dev'] }), (error) => /Service 'group-one' declares an ingress, but no port/.test(error.message))
74
+ assert.throws(
75
+ () => services(list, {}, undefined, { hosts: ['api.dev'] }),
76
+ (error) => /Service 'group-one' declares an ingress, but no port/.test(error.message)
77
+ )
60
78
  })
61
-
@@ -1,11 +1,10 @@
1
- export function addVariables (composition, variables, keys = composition.components) {
1
+ export function addVariables(composition, variables, keys = composition.components) {
2
2
  composition.variables ??= []
3
3
 
4
4
  const used = new Set(composition.variables.map((variable) => variable.name))
5
5
 
6
6
  for (const [key, set] of Object.entries(variables)) {
7
- if (key !== 'global' && !keys?.includes(key))
8
- continue
7
+ if (key !== 'global' && !keys?.includes(key)) continue
9
8
 
10
9
  for (const variable of set) {
11
10
  if (used.has(variable.name)) continue