@toa.io/extensions.configuration 1.0.0-alpha.29 → 1.0.0-alpha.292

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 (129) hide show
  1. package/CHANGELOG.md +42 -0
  2. package/components/configuration.values/manifest.toa.yaml +151 -0
  3. package/components/configuration.values/migrations/0002-indexes.yaml +7 -0
  4. package/components/configuration.values/operations/create.js +30 -0
  5. package/components/configuration.values/operations/fetch.js +13 -0
  6. package/components/configuration.values/operations/get.js +19 -0
  7. package/components/configuration.values/operations/lib/map.js +22 -0
  8. package/components/configuration.values/operations/lib/resolve.js +24 -0
  9. package/components/configuration.values/operations/list.js +11 -0
  10. package/components/configuration.values/source/create.ts +54 -0
  11. package/components/configuration.values/source/fetch.ts +26 -0
  12. package/components/configuration.values/source/get.ts +32 -0
  13. package/components/configuration.values/source/lib/map.test.ts +42 -0
  14. package/components/configuration.values/source/lib/map.ts +36 -0
  15. package/components/configuration.values/source/lib/resolve.ts +57 -0
  16. package/components/configuration.values/source/list.ts +21 -0
  17. package/components/configuration.values/tsconfig.json +10 -0
  18. package/components/configuration.values/tsconfig.tsbuildinfo +1 -0
  19. package/components/configuration.values/types/index.d.ts +3 -0
  20. package/components/configuration.values/types/toa.d.ts +68 -0
  21. package/cucumber.mjs +8 -0
  22. package/features/site/_app/immutable/asset.js +1 -0
  23. package/features/site/favicon.ico +0 -0
  24. package/features/site/index.html +10 -0
  25. package/features/steps/UI.ts +98 -0
  26. package/features/steps/tsconfig.json +9 -0
  27. package/features/ui.feature +93 -0
  28. package/notes/consistency.md +10 -9
  29. package/package.json +12 -10
  30. package/readme.md +165 -51
  31. package/source/Aspect.test.ts +114 -10
  32. package/source/Aspect.ts +76 -8
  33. package/source/Client.test.ts +125 -0
  34. package/source/Client.ts +251 -0
  35. package/source/Composition.ts +57 -0
  36. package/source/Factory.ts +29 -7
  37. package/source/Secret.test.ts +32 -0
  38. package/source/Secret.ts +32 -0
  39. package/source/UI.ts +213 -0
  40. package/source/configuration.test.ts +105 -43
  41. package/source/configuration.ts +36 -21
  42. package/source/epoch.test.ts +35 -0
  43. package/source/index.ts +5 -3
  44. package/transpiled/Aspect.d.ts +19 -4
  45. package/transpiled/Aspect.js +62 -9
  46. package/transpiled/Aspect.js.map +1 -1
  47. package/transpiled/Client.d.ts +61 -0
  48. package/transpiled/Client.js +175 -0
  49. package/transpiled/Client.js.map +1 -0
  50. package/transpiled/Composition.d.ts +14 -0
  51. package/transpiled/Composition.js +38 -0
  52. package/transpiled/Composition.js.map +1 -0
  53. package/transpiled/Factory.d.ts +9 -2
  54. package/transpiled/Factory.js +20 -9
  55. package/transpiled/Factory.js.map +1 -1
  56. package/transpiled/Secret.d.ts +15 -0
  57. package/transpiled/Secret.js +26 -0
  58. package/transpiled/Secret.js.map +1 -0
  59. package/transpiled/UI.d.ts +25 -0
  60. package/transpiled/UI.js +173 -0
  61. package/transpiled/UI.js.map +1 -0
  62. package/transpiled/configuration.d.ts +7 -2
  63. package/transpiled/configuration.js +32 -43
  64. package/transpiled/configuration.js.map +1 -1
  65. package/transpiled/index.d.ts +3 -3
  66. package/transpiled/index.js +4 -9
  67. package/transpiled/index.js.map +1 -1
  68. package/tsconfig.json +5 -1
  69. package/tsconfig.tsbuildinfo +1 -0
  70. package/ui/dist/_app/env.js +1 -0
  71. package/ui/dist/_app/immutable/assets/0.CNS8Cqhc.css +2 -0
  72. package/ui/dist/_app/immutable/assets/inter-cyrillic-ext-wght-normal.BOeWTOD4.woff2 +0 -0
  73. package/ui/dist/_app/immutable/assets/inter-cyrillic-wght-normal.DqGufNeO.woff2 +0 -0
  74. package/ui/dist/_app/immutable/assets/inter-greek-ext-wght-normal.DlzME5K_.woff2 +0 -0
  75. package/ui/dist/_app/immutable/assets/inter-greek-wght-normal.CkhJZR-_.woff2 +0 -0
  76. package/ui/dist/_app/immutable/assets/inter-latin-ext-wght-normal.DO1Apj_S.woff2 +0 -0
  77. package/ui/dist/_app/immutable/assets/inter-latin-wght-normal.Dx4kXJAl.woff2 +0 -0
  78. package/ui/dist/_app/immutable/assets/inter-vietnamese-wght-normal.CBcvBZtf.woff2 +0 -0
  79. package/ui/dist/_app/immutable/assets/ui.BghF56l_.css +1 -0
  80. package/ui/dist/_app/immutable/chunks/-d-dxcbq.js +8 -0
  81. package/ui/dist/_app/immutable/chunks/64H_hMtE.js +50 -0
  82. package/ui/dist/_app/immutable/chunks/BeorVtKF.js +6 -0
  83. package/ui/dist/_app/immutable/chunks/Bjy-W4x2.js +81 -0
  84. package/ui/dist/_app/immutable/chunks/C1Pl1PSE.js +1 -0
  85. package/ui/dist/_app/immutable/chunks/CcD_ISxM.js +3 -0
  86. package/ui/dist/_app/immutable/chunks/DzmF9ykx.js +1 -0
  87. package/ui/dist/_app/immutable/chunks/xihTtKlq.js +1 -0
  88. package/ui/dist/_app/immutable/entry/app.D5YrjA4l.js +2 -0
  89. package/ui/dist/_app/immutable/entry/start.CKkVSyXd.js +1 -0
  90. package/ui/dist/_app/immutable/nodes/0.Cm3Z3YNE.js +1 -0
  91. package/ui/dist/_app/immutable/nodes/1.e2aDXaPj.js +1 -0
  92. package/ui/dist/_app/immutable/nodes/2.Blh7Ex_K.js +1 -0
  93. package/ui/dist/_app/immutable/nodes/3.61NbGuZc.js +1 -0
  94. package/ui/dist/_app/immutable/nodes/4.B6OdRyVa.js +1 -0
  95. package/ui/dist/_app/version.json +1 -0
  96. package/ui/dist/apple-touch-icon.png +0 -0
  97. package/ui/dist/favicon-96x96.png +0 -0
  98. package/ui/dist/favicon.ico +0 -0
  99. package/ui/dist/index.html +57 -0
  100. package/ui/dist/robots.txt +3 -0
  101. package/schemas/annotation.cos.yaml +0 -1
  102. package/schemas/manifest.cos.yaml +0 -2
  103. package/source/deployment.test.ts +0 -21
  104. package/source/deployment.ts +0 -86
  105. package/source/manifest.test.ts +0 -15
  106. package/source/manifest.ts +0 -15
  107. package/source/schemas.ts +0 -9
  108. package/transpiled/Aspect.test.d.ts +0 -1
  109. package/transpiled/Aspect.test.js +0 -15
  110. package/transpiled/Aspect.test.js.map +0 -1
  111. package/transpiled/configuration.test.d.ts +0 -1
  112. package/transpiled/configuration.test.js +0 -64
  113. package/transpiled/configuration.test.js.map +0 -1
  114. package/transpiled/deployment.d.ts +0 -8
  115. package/transpiled/deployment.js +0 -88
  116. package/transpiled/deployment.js.map +0 -1
  117. package/transpiled/deployment.test.d.ts +0 -1
  118. package/transpiled/deployment.test.js +0 -18
  119. package/transpiled/deployment.test.js.map +0 -1
  120. package/transpiled/manifest.d.ts +0 -6
  121. package/transpiled/manifest.js +0 -35
  122. package/transpiled/manifest.js.map +0 -1
  123. package/transpiled/manifest.test.d.ts +0 -1
  124. package/transpiled/manifest.test.js +0 -14
  125. package/transpiled/manifest.test.js.map +0 -1
  126. package/transpiled/schemas.d.ts +0 -3
  127. package/transpiled/schemas.js +0 -33
  128. package/transpiled/schemas.js.map +0 -1
  129. package/transpiled/tsconfig.tsbuildinfo +0 -1
package/CHANGELOG.md ADDED
@@ -0,0 +1,42 @@
1
+ # Change Log
2
+
3
+ All notable changes to this project will be documented in this file.
4
+ See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
+
6
+ # [1.0.0-alpha.292](https://github.com/toa-io/toa/compare/v1.0.0-alpha.291...v1.0.0-alpha.292) (2026-09-07)
7
+
8
+ ### Features
9
+
10
+ * **definitions:** what a package declares is read from one package ([7052341](https://github.com/toa-io/toa/commit/70523411b5d9e2b204c999aa02365173f3bb518d))
11
+
12
+
13
+ # [1.0.0-alpha.291](https://github.com/toa-io/toa/compare/v1.0.0-alpha.290...v1.0.0-alpha.291) (2026-09-07)
14
+
15
+ **Note:** Version bump only for package @toa.io/extensions.configuration
16
+
17
+
18
+
19
+
20
+
21
+ # [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)
22
+
23
+ * 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)
24
+
25
+ ### BREAKING CHANGES
26
+
27
+ * a component that read `process.env.TOA_*` reads `context` instead;
28
+ `echo(input)` no longer substitutes from the environment; a bash operation sees no
29
+ `TOA_*`; images no longer set `USER node` — see migrations/289.md.
30
+
31
+
32
+ # [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)
33
+
34
+ **Note:** Version bump only for package @toa.io/extensions.configuration
35
+
36
+
37
+
38
+
39
+
40
+ # [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)
41
+
42
+ **Note:** Version bump only for package @toa.io/extensions.configuration
@@ -0,0 +1,151 @@
1
+ namespace: configuration
2
+ name: values
3
+
4
+ # One document per configuration created. Documents are immutable: a change is a new
5
+ # document, and the latest one for a component and an epoch is the last created.
6
+ # What every component's epoch, schema and defaults are, the service is told on
7
+ # deployment (`TOA_CONFIGURATION_VALUES`). A created object is published as the
8
+ # prototype's `created` event.
9
+
10
+ entity:
11
+ properties:
12
+ component:
13
+ type: string
14
+ epoch:
15
+ type: string
16
+ configuration:
17
+ type: object
18
+ originator:
19
+ type: string
20
+ required:
21
+ - component
22
+ - epoch
23
+ - configuration
24
+ - originator
25
+
26
+ operations:
27
+ # the latest configuration of a component: created, else deployed defaults, else none
28
+ get:
29
+ query: false
30
+ input:
31
+ type: object
32
+ properties:
33
+ component:
34
+ type: string
35
+ epoch:
36
+ type: string
37
+ required:
38
+ - component
39
+ # the value and the schema it is checked against, so a caller can show and check it
40
+ output:
41
+ type: object
42
+ nullable: true
43
+ properties:
44
+ configuration:
45
+ type: object
46
+ schema:
47
+ type: object
48
+ epoch:
49
+ type: string
50
+ required:
51
+ - configuration
52
+ - epoch
53
+ # the same for many at once, as the aspects of one process ask
54
+ fetch:
55
+ query: false
56
+ input:
57
+ type: array
58
+ items:
59
+ type: object
60
+ properties:
61
+ component:
62
+ type: string
63
+ epoch:
64
+ type: string
65
+ required:
66
+ - component
67
+ - epoch
68
+ output:
69
+ type: array
70
+ items:
71
+ type: object
72
+ properties:
73
+ component:
74
+ type: string
75
+ epoch:
76
+ type: string
77
+ configuration:
78
+ type: object
79
+ nullable: true
80
+ created:
81
+ type: integer
82
+ required:
83
+ - component
84
+ - epoch
85
+ - configuration
86
+ - created
87
+ # every component's configuration for its deployed epoch, by component name
88
+ list:
89
+ query: false
90
+ output:
91
+ type: array
92
+ items:
93
+ type: object
94
+ properties:
95
+ component:
96
+ type: string
97
+ epoch:
98
+ type: string
99
+ schema:
100
+ type: object
101
+ configuration:
102
+ type: object
103
+ required:
104
+ - component
105
+ - epoch
106
+ - schema
107
+ - configuration
108
+ create:
109
+ query: false
110
+ input:
111
+ type: object
112
+ properties:
113
+ component:
114
+ type: string
115
+ configuration:
116
+ type: object
117
+ originator:
118
+ type: object
119
+ properties:
120
+ id:
121
+ type: string
122
+ required:
123
+ - id
124
+ required:
125
+ - component
126
+ - configuration
127
+ - originator
128
+ errors:
129
+ - UNKNOWN_COMPONENT
130
+ - INVALID_CONFIGURATION
131
+
132
+ exposition:
133
+ isolated: true
134
+ /:
135
+ GET:
136
+ io:output: true
137
+ auth:role: system:configuration:get
138
+ endpoint: list
139
+ /:component:
140
+ map:segments:
141
+ component: component
142
+ GET:
143
+ io:output: true
144
+ auth:role: system:configuration:get
145
+ endpoint: get
146
+ POST:
147
+ io:output: [id, epoch]
148
+ auth:rule:
149
+ role: system:configuration:create
150
+ delegate: originator
151
+ endpoint: create
@@ -0,0 +1,7 @@
1
+ - index:
2
+ name: index_lookup
3
+ keys:
4
+ component: asc
5
+ epoch: asc
6
+ CREATED: desc
7
+ sparse: true
@@ -0,0 +1,30 @@
1
+ import * as schemas from '@toa.io/schemas';
2
+ import { entry } from './lib/map.js';
3
+ export async function transition(input, object) {
4
+ const known = entry(input.component);
5
+ if (known === undefined)
6
+ return new UnknownComponentError(input.component);
7
+ const configuration = structuredClone(input.configuration);
8
+ const schema = schemas.schema(known.schema);
9
+ try {
10
+ schema.validate(configuration);
11
+ }
12
+ catch (error) {
13
+ return new InvalidConfigurationError(error.message);
14
+ }
15
+ object.component = input.component;
16
+ object.epoch = known.epoch;
17
+ object.configuration = configuration;
18
+ object.originator = input.originator.id;
19
+ return object;
20
+ }
21
+ class UnknownComponentError extends Error {
22
+ code = 'UNKNOWN_COMPONENT';
23
+ constructor(component) {
24
+ super(`Component '${component}' is not configured`);
25
+ }
26
+ }
27
+ class InvalidConfigurationError extends Error {
28
+ code = 'INVALID_CONFIGURATION';
29
+ }
30
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY3JlYXRlLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vc291cmNlL2NyZWF0ZS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEtBQUssT0FBTyxNQUFNLGlCQUFpQixDQUFBO0FBQzFDLE9BQU8sRUFBRSxLQUFLLEVBQUUsTUFBTSxjQUFjLENBQUE7QUFHcEMsTUFBTSxDQUFDLEtBQUssVUFBVSxVQUFVLENBQUMsS0FBWSxFQUFFLE1BQWM7SUFDM0QsTUFBTSxLQUFLLEdBQUcsS0FBSyxDQUFDLEtBQUssQ0FBQyxTQUFTLENBQUMsQ0FBQTtJQUVwQyxJQUFJLEtBQUssS0FBSyxTQUFTO1FBQUUsT0FBTyxJQUFJLHFCQUFxQixDQUFDLEtBQUssQ0FBQyxTQUFTLENBQUMsQ0FBQTtJQUUxRSxNQUFNLGFBQWEsR0FBRyxlQUFlLENBQUMsS0FBSyxDQUFDLGFBQWEsQ0FBQyxDQUFBO0lBRTFELE1BQU0sTUFBTSxHQUFnQixPQUFPLENBQUMsTUFBTSxDQUFDLEtBQUssQ0FBQyxNQUFNLENBQUMsQ0FBQTtJQUV4RCxJQUFJLENBQUM7UUFDSCxNQUFNLENBQUMsUUFBUSxDQUFDLGFBQWEsQ0FBQyxDQUFBO0lBQ2hDLENBQUM7SUFBQyxPQUFPLEtBQUssRUFBRSxDQUFDO1FBQ2YsT0FBTyxJQUFJLHlCQUF5QixDQUFFLEtBQWUsQ0FBQyxPQUFPLENBQUMsQ0FBQTtJQUNoRSxDQUFDO0lBRUQsTUFBTSxDQUFDLFNBQVMsR0FBRyxLQUFLLENBQUMsU0FBUyxDQUFBO0lBQ2xDLE1BQU0sQ0FBQyxLQUFLLEdBQUcsS0FBSyxDQUFDLEtBQUssQ0FBQTtJQUMxQixNQUFNLENBQUMsYUFBYSxHQUFHLGFBQWEsQ0FBQTtJQUNwQyxNQUFNLENBQUMsVUFBVSxHQUFHLEtBQUssQ0FBQyxVQUFVLENBQUMsRUFBRSxDQUFBO0lBRXZDLE9BQU8sTUFBTSxDQUFBO0FBQ2YsQ0FBQztBQUVELE1BQU0scUJBQXNCLFNBQVEsS0FBSztJQUN2QixJQUFJLEdBQUcsbUJBQW1CLENBQUE7SUFFMUMsWUFBbUIsU0FBaUI7UUFDbEMsS0FBSyxDQUFDLGNBQWMsU0FBUyxxQkFBcUIsQ0FBQyxDQUFBO0lBQ3JELENBQUM7Q0FDRjtBQUVELE1BQU0seUJBQTBCLFNBQVEsS0FBSztJQUMzQixJQUFJLEdBQUcsdUJBQXVCLENBQUE7Q0FDL0MifQ==
@@ -0,0 +1,13 @@
1
+ import { resolve } from './lib/resolve.js';
2
+ export async function computation(input, context) {
3
+ return await Promise.all(input.map(async ({ component, epoch }) => {
4
+ const value = await resolve(context, component, epoch);
5
+ return {
6
+ component,
7
+ epoch,
8
+ configuration: value === null ? null : value.configuration,
9
+ created: value === null ? 0 : value.created
10
+ };
11
+ }));
12
+ }
13
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZmV0Y2guanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9zb3VyY2UvZmV0Y2gudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxFQUFFLE9BQU8sRUFBZ0IsTUFBTSxrQkFBa0IsQ0FBQTtBQUV4RCxNQUFNLENBQUMsS0FBSyxVQUFVLFdBQVcsQ0FBQyxLQUFhLEVBQUUsT0FBZ0I7SUFDL0QsT0FBTyxNQUFNLE9BQU8sQ0FBQyxHQUFHLENBQ3RCLEtBQUssQ0FBQyxHQUFHLENBQUMsS0FBSyxFQUFFLEVBQUUsU0FBUyxFQUFFLEtBQUssRUFBRSxFQUFFLEVBQUU7UUFDdkMsTUFBTSxLQUFLLEdBQUcsTUFBTSxPQUFPLENBQUMsT0FBTyxFQUFFLFNBQVMsRUFBRSxLQUFLLENBQUMsQ0FBQTtRQUV0RCxPQUFPO1lBQ0wsU0FBUztZQUNULEtBQUs7WUFDTCxhQUFhLEVBQUUsS0FBSyxLQUFLLElBQUksQ0FBQyxDQUFDLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQyxLQUFLLENBQUMsYUFBYTtZQUMxRCxPQUFPLEVBQUUsS0FBSyxLQUFLLElBQUksQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxLQUFLLENBQUMsT0FBTztTQUM1QyxDQUFBO0lBQ0gsQ0FBQyxDQUFDLENBQ0gsQ0FBQTtBQUNILENBQUMifQ==
@@ -0,0 +1,19 @@
1
+ import { entry } from './lib/map.js';
2
+ import { resolve } from './lib/resolve.js';
3
+ /**
4
+ * The latest configuration of a component, with the schema it is checked against. The
5
+ * schema is the deployment's, so an epoch the deployment does not know has none — the
6
+ * value is still what was stored for it.
7
+ */
8
+ export async function computation(input, context) {
9
+ const value = await resolve(context, input.component, input.epoch);
10
+ if (value === null)
11
+ return null;
12
+ const known = entry(input.component);
13
+ return {
14
+ configuration: value.configuration,
15
+ schema: known?.schema,
16
+ epoch: input.epoch ?? known.epoch
17
+ };
18
+ }
19
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZ2V0LmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vc291cmNlL2dldC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQUUsS0FBSyxFQUFFLE1BQU0sY0FBYyxDQUFBO0FBQ3BDLE9BQU8sRUFBRSxPQUFPLEVBQWdCLE1BQU0sa0JBQWtCLENBQUE7QUFFeEQ7Ozs7R0FJRztBQUNILE1BQU0sQ0FBQyxLQUFLLFVBQVUsV0FBVyxDQUFDLEtBQVksRUFBRSxPQUFnQjtJQUM5RCxNQUFNLEtBQUssR0FBRyxNQUFNLE9BQU8sQ0FBQyxPQUFPLEVBQUUsS0FBSyxDQUFDLFNBQVMsRUFBRSxLQUFLLENBQUMsS0FBSyxDQUFDLENBQUE7SUFFbEUsSUFBSSxLQUFLLEtBQUssSUFBSTtRQUFFLE9BQU8sSUFBSSxDQUFBO0lBRS9CLE1BQU0sS0FBSyxHQUFHLEtBQUssQ0FBQyxLQUFLLENBQUMsU0FBUyxDQUFDLENBQUE7SUFFcEMsT0FBTztRQUNMLGFBQWEsRUFBRSxLQUFLLENBQUMsYUFBYTtRQUNsQyxNQUFNLEVBQUUsS0FBSyxFQUFFLE1BQU07UUFDckIsS0FBSyxFQUFFLEtBQUssQ0FBQyxLQUFLLElBQUksS0FBTSxDQUFDLEtBQUs7S0FDbkMsQ0FBQTtBQUNILENBQUMifQ==
@@ -0,0 +1,22 @@
1
+ import { environment } from '@toa.io/generic';
2
+ /** What the deployment told the service about every configured component. */
3
+ export function entry(component) {
4
+ return read()[component];
5
+ }
6
+ /** The configured components, by name. */
7
+ export function components() {
8
+ return Object.keys(read()).sort();
9
+ }
10
+ function read() {
11
+ const source = environment.get(VARIABLE) ?? '{}';
12
+ // read again once the variable changes, as it does between scenarios of one process
13
+ if (source !== parsed) {
14
+ values = JSON.parse(source);
15
+ parsed = source;
16
+ }
17
+ return values;
18
+ }
19
+ let parsed = null;
20
+ let values = {};
21
+ export const VARIABLE = 'TOA_CONFIGURATION_VALUES';
22
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibWFwLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc291cmNlL2xpYi9tYXAudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxFQUFFLFdBQVcsRUFBRSxNQUFNLGlCQUFpQixDQUFBO0FBRTdDLDZFQUE2RTtBQUM3RSxNQUFNLFVBQVUsS0FBSyxDQUFDLFNBQWlCO0lBQ3JDLE9BQU8sSUFBSSxFQUFFLENBQUMsU0FBUyxDQUFDLENBQUE7QUFDMUIsQ0FBQztBQUVELDBDQUEwQztBQUMxQyxNQUFNLFVBQVUsVUFBVTtJQUN4QixPQUFPLE1BQU0sQ0FBQyxJQUFJLENBQUMsSUFBSSxFQUFFLENBQUMsQ0FBQyxJQUFJLEVBQUUsQ0FBQTtBQUNuQyxDQUFDO0FBRUQsU0FBUyxJQUFJO0lBQ1gsTUFBTSxNQUFNLEdBQUcsV0FBVyxDQUFDLEdBQUcsQ0FBQyxRQUFRLENBQUMsSUFBSSxJQUFJLENBQUE7SUFFaEQsb0ZBQW9GO0lBQ3BGLElBQUksTUFBTSxLQUFLLE1BQU0sRUFBRSxDQUFDO1FBQ3RCLE1BQU0sR0FBRyxJQUFJLENBQUMsS0FBSyxDQUFDLE1BQU0sQ0FBVyxDQUFBO1FBQ3JDLE1BQU0sR0FBRyxNQUFNLENBQUE7SUFDakIsQ0FBQztJQUVELE9BQU8sTUFBTSxDQUFBO0FBQ2YsQ0FBQztBQUVELElBQUksTUFBTSxHQUFrQixJQUFJLENBQUE7QUFDaEMsSUFBSSxNQUFNLEdBQVcsRUFBRSxDQUFBO0FBRXZCLE1BQU0sQ0FBQyxNQUFNLFFBQVEsR0FBRywwQkFBMEIsQ0FBQSJ9
@@ -0,0 +1,24 @@
1
+ import { entry } from './map.js';
2
+ /**
3
+ * The latest configuration created for the component and the epoch; the deployed
4
+ * defaults when none was; `null` when the epoch is not the one deployed.
5
+ */
6
+ export async function resolve(context, component, epoch) {
7
+ const known = entry(component);
8
+ epoch ??= known?.epoch;
9
+ if (epoch === undefined)
10
+ return null;
11
+ // one query per pair, so that a component's latest is never behind another's newer ones
12
+ const query = {
13
+ criteria: `component=="${component}";epoch=="${epoch}"`,
14
+ sort: ['CREATED:desc'],
15
+ limit: 1
16
+ };
17
+ const objects = await context.local.enumerate({ query });
18
+ if (objects.length > 0)
19
+ return { configuration: objects[0].configuration, created: objects[0].CREATED };
20
+ if (known !== undefined && known.epoch === epoch)
21
+ return { configuration: known.defaults ?? {}, created: 0 };
22
+ return null;
23
+ }
24
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicmVzb2x2ZS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NvdXJjZS9saWIvcmVzb2x2ZS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQUUsS0FBSyxFQUFFLE1BQU0sVUFBVSxDQUFBO0FBRWhDOzs7R0FHRztBQUNILE1BQU0sQ0FBQyxLQUFLLFVBQVUsT0FBTyxDQUMzQixPQUFnQixFQUNoQixTQUFpQixFQUNqQixLQUFjO0lBRWQsTUFBTSxLQUFLLEdBQUcsS0FBSyxDQUFDLFNBQVMsQ0FBQyxDQUFBO0lBRTlCLEtBQUssS0FBSyxLQUFLLEVBQUUsS0FBSyxDQUFBO0lBRXRCLElBQUksS0FBSyxLQUFLLFNBQVM7UUFBRSxPQUFPLElBQUksQ0FBQTtJQUVwQyx3RkFBd0Y7SUFDeEYsTUFBTSxLQUFLLEdBQVU7UUFDbkIsUUFBUSxFQUFFLGVBQWUsU0FBUyxhQUFhLEtBQUssR0FBRztRQUN2RCxJQUFJLEVBQUUsQ0FBQyxjQUFjLENBQUM7UUFDdEIsS0FBSyxFQUFFLENBQUM7S0FDVCxDQUFBO0lBRUQsTUFBTSxPQUFPLEdBQUcsTUFBTSxPQUFPLENBQUMsS0FBSyxDQUFDLFNBQVMsQ0FBQyxFQUFFLEtBQUssRUFBRSxDQUFDLENBQUE7SUFFeEQsSUFBSSxPQUFPLENBQUMsTUFBTSxHQUFHLENBQUM7UUFDcEIsT0FBTyxFQUFFLGFBQWEsRUFBRSxPQUFPLENBQUMsQ0FBQyxDQUFDLENBQUMsYUFBYSxFQUFFLE9BQU8sRUFBRSxPQUFPLENBQUMsQ0FBQyxDQUFDLENBQUMsT0FBTyxFQUFFLENBQUE7SUFFakYsSUFBSSxLQUFLLEtBQUssU0FBUyxJQUFJLEtBQUssQ0FBQyxLQUFLLEtBQUssS0FBSztRQUM5QyxPQUFPLEVBQUUsYUFBYSxFQUFFLEtBQUssQ0FBQyxRQUFRLElBQUksRUFBRSxFQUFFLE9BQU8sRUFBRSxDQUFDLEVBQUUsQ0FBQTtJQUU1RCxPQUFPLElBQUksQ0FBQTtBQUNiLENBQUMifQ==
@@ -0,0 +1,11 @@
1
+ import { components, entry } from './lib/map.js';
2
+ import { resolve } from './lib/resolve.js';
3
+ /** The configuration of every component for its deployed epoch, by component name. */
4
+ export async function computation(_, context) {
5
+ return await Promise.all(components().map(async (component) => {
6
+ const { epoch, schema } = entry(component);
7
+ const value = await resolve(context, component, epoch);
8
+ return { component, epoch, schema, configuration: value.configuration };
9
+ }));
10
+ }
11
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibGlzdC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3NvdXJjZS9saXN0LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sRUFBRSxVQUFVLEVBQUUsS0FBSyxFQUFFLE1BQU0sY0FBYyxDQUFBO0FBQ2hELE9BQU8sRUFBRSxPQUFPLEVBQWdCLE1BQU0sa0JBQWtCLENBQUE7QUFFeEQsc0ZBQXNGO0FBQ3RGLE1BQU0sQ0FBQyxLQUFLLFVBQVUsV0FBVyxDQUFDLENBQU8sRUFBRSxPQUFnQjtJQUN6RCxPQUFPLE1BQU0sT0FBTyxDQUFDLEdBQUcsQ0FDdEIsVUFBVSxFQUFFLENBQUMsR0FBRyxDQUFDLEtBQUssRUFBRSxTQUFTLEVBQUUsRUFBRTtRQUNuQyxNQUFNLEVBQUUsS0FBSyxFQUFFLE1BQU0sRUFBRSxHQUFHLEtBQUssQ0FBQyxTQUFTLENBQUUsQ0FBQTtRQUMzQyxNQUFNLEtBQUssR0FBRyxNQUFNLE9BQU8sQ0FBQyxPQUFPLEVBQUUsU0FBUyxFQUFFLEtBQUssQ0FBQyxDQUFBO1FBRXRELE9BQU8sRUFBRSxTQUFTLEVBQUUsS0FBSyxFQUFFLE1BQU0sRUFBRSxhQUFhLEVBQUUsS0FBTSxDQUFDLGFBQWEsRUFBRSxDQUFBO0lBQzFFLENBQUMsQ0FBQyxDQUNILENBQUE7QUFDSCxDQUFDIn0=
@@ -0,0 +1,54 @@
1
+ import * as schemas from '@toa.io/schemas'
2
+ import { entry } from './lib/map.js'
3
+ import type { Schema } from '@toa.io/schemas'
4
+
5
+ export async function transition(input: Input, object: Entity): Promise<Entity | Error> {
6
+ const known = entry(input.component)
7
+
8
+ if (known === undefined) return new UnknownComponentError(input.component)
9
+
10
+ const configuration = structuredClone(input.configuration)
11
+
12
+ const schema: Schema<any> = schemas.schema(known.schema)
13
+
14
+ try {
15
+ schema.validate(configuration)
16
+ } catch (error) {
17
+ return new InvalidConfigurationError((error as Error).message)
18
+ }
19
+
20
+ object.component = input.component
21
+ object.epoch = known.epoch
22
+ object.configuration = configuration
23
+ object.originator = input.originator.id
24
+
25
+ return object
26
+ }
27
+
28
+ class UnknownComponentError extends Error {
29
+ public readonly code = 'UNKNOWN_COMPONENT'
30
+
31
+ public constructor(component: string) {
32
+ super(`Component '${component}' is not configured`)
33
+ }
34
+ }
35
+
36
+ class InvalidConfigurationError extends Error {
37
+ public readonly code = 'INVALID_CONFIGURATION'
38
+ }
39
+
40
+ interface Input {
41
+ component: string
42
+ configuration: object
43
+ originator: {
44
+ id: string
45
+ }
46
+ }
47
+
48
+ interface Entity {
49
+ id: string
50
+ component: string
51
+ epoch: string
52
+ configuration: object
53
+ originator: string
54
+ }
@@ -0,0 +1,26 @@
1
+ import { resolve, type Context } from './lib/resolve.js'
2
+
3
+ export async function computation(input: Pair[], context: Context): Promise<Fetched[]> {
4
+ return await Promise.all(
5
+ input.map(async ({ component, epoch }) => {
6
+ const value = await resolve(context, component, epoch)
7
+
8
+ return {
9
+ component,
10
+ epoch,
11
+ configuration: value === null ? null : value.configuration,
12
+ created: value === null ? 0 : value.created
13
+ }
14
+ })
15
+ )
16
+ }
17
+
18
+ interface Pair {
19
+ component: string
20
+ epoch: string
21
+ }
22
+
23
+ interface Fetched extends Pair {
24
+ configuration: object | null
25
+ created: number
26
+ }
@@ -0,0 +1,32 @@
1
+ import { entry } from './lib/map.js'
2
+ import { resolve, type Context } from './lib/resolve.js'
3
+
4
+ /**
5
+ * The latest configuration of a component, with the schema it is checked against. The
6
+ * schema is the deployment's, so an epoch the deployment does not know has none — the
7
+ * value is still what was stored for it.
8
+ */
9
+ export async function computation(input: Input, context: Context): Promise<Item | null> {
10
+ const value = await resolve(context, input.component, input.epoch)
11
+
12
+ if (value === null) return null
13
+
14
+ const known = entry(input.component)
15
+
16
+ return {
17
+ configuration: value.configuration,
18
+ schema: known?.schema,
19
+ epoch: input.epoch ?? known!.epoch
20
+ }
21
+ }
22
+
23
+ interface Input {
24
+ component: string
25
+ epoch?: string
26
+ }
27
+
28
+ interface Item {
29
+ configuration: object
30
+ schema?: object
31
+ epoch: string
32
+ }
@@ -0,0 +1,42 @@
1
+ import { it, afterEach } from 'node:test'
2
+ import assert from 'node:assert/strict'
3
+
4
+ import { VARIABLE, components, entry } from './map.js'
5
+
6
+ afterEach(() => {
7
+ delete process.env[VARIABLE]
8
+ })
9
+
10
+ it('should be empty without the variable', () => {
11
+ assert.strictEqual(entry('a.b'), undefined)
12
+ })
13
+
14
+ it('should read the variable', () => {
15
+ const values = {
16
+ 'a.b': { epoch: 'e', schema: { type: 'object' }, defaults: { foo: 1 } }
17
+ }
18
+
19
+ process.env[VARIABLE] = JSON.stringify(values)
20
+
21
+ assert.deepStrictEqual(entry('a.b'), values['a.b'])
22
+ assert.strictEqual(entry('a.c'), undefined)
23
+ })
24
+
25
+ it('should follow the variable', () => {
26
+ process.env[VARIABLE] = JSON.stringify({ 'a.b': { epoch: 'e1', schema: {} } })
27
+
28
+ assert.deepStrictEqual(entry('a.b')?.epoch, 'e1')
29
+
30
+ process.env[VARIABLE] = JSON.stringify({ 'a.b': { epoch: 'e2', schema: {} } })
31
+
32
+ assert.deepStrictEqual(entry('a.b')?.epoch, 'e2')
33
+ })
34
+
35
+ it('should list the components by name', () => {
36
+ process.env[VARIABLE] = JSON.stringify({
37
+ 'b.two': { epoch: 'e', schema: {} },
38
+ 'a.one': { epoch: 'e', schema: {} }
39
+ })
40
+
41
+ assert.deepStrictEqual(components(), ['a.one', 'b.two'])
42
+ })
@@ -0,0 +1,36 @@
1
+ import { environment } from '@toa.io/generic'
2
+
3
+ /** What the deployment told the service about every configured component. */
4
+ export function entry(component: string): Entry | undefined {
5
+ return read()[component]
6
+ }
7
+
8
+ /** The configured components, by name. */
9
+ export function components(): string[] {
10
+ return Object.keys(read()).sort()
11
+ }
12
+
13
+ function read(): Values {
14
+ const source = environment.get(VARIABLE) ?? '{}'
15
+
16
+ // read again once the variable changes, as it does between scenarios of one process
17
+ if (source !== parsed) {
18
+ values = JSON.parse(source) as Values
19
+ parsed = source
20
+ }
21
+
22
+ return values
23
+ }
24
+
25
+ let parsed: string | null = null
26
+ let values: Values = {}
27
+
28
+ export const VARIABLE = 'TOA_CONFIGURATION_VALUES'
29
+
30
+ export interface Entry {
31
+ epoch: string
32
+ schema: object
33
+ defaults?: object
34
+ }
35
+
36
+ export type Values = Record<string, Entry>
@@ -0,0 +1,57 @@
1
+ import { entry } from './map.js'
2
+
3
+ /**
4
+ * The latest configuration created for the component and the epoch; the deployed
5
+ * defaults when none was; `null` when the epoch is not the one deployed.
6
+ */
7
+ export async function resolve(
8
+ context: Context,
9
+ component: string,
10
+ epoch?: string
11
+ ): Promise<Value | null> {
12
+ const known = entry(component)
13
+
14
+ epoch ??= known?.epoch
15
+
16
+ if (epoch === undefined) return null
17
+
18
+ // one query per pair, so that a component's latest is never behind another's newer ones
19
+ const query: Query = {
20
+ criteria: `component=="${component}";epoch=="${epoch}"`,
21
+ sort: ['CREATED:desc'],
22
+ limit: 1
23
+ }
24
+
25
+ const objects = await context.local.enumerate({ query })
26
+
27
+ if (objects.length > 0)
28
+ return { configuration: objects[0].configuration, created: objects[0].CREATED }
29
+
30
+ if (known !== undefined && known.epoch === epoch)
31
+ return { configuration: known.defaults ?? {}, created: 0 }
32
+
33
+ return null
34
+ }
35
+
36
+ /** A configuration and when it was created; `0` for the deployed defaults. */
37
+ export interface Value {
38
+ configuration: object
39
+ created: number
40
+ }
41
+
42
+ export interface Context {
43
+ local: {
44
+ enumerate: (request: { query: Query }) => Promise<Stored[]>
45
+ }
46
+ }
47
+
48
+ interface Query {
49
+ criteria: string
50
+ sort: string[]
51
+ limit: number
52
+ }
53
+
54
+ interface Stored {
55
+ configuration: object
56
+ CREATED: number
57
+ }
@@ -0,0 +1,21 @@
1
+ import { components, entry } from './lib/map.js'
2
+ import { resolve, type Context } from './lib/resolve.js'
3
+
4
+ /** The configuration of every component for its deployed epoch, by component name. */
5
+ export async function computation(_: null, context: Context): Promise<Item[]> {
6
+ return await Promise.all(
7
+ components().map(async (component) => {
8
+ const { epoch, schema } = entry(component)!
9
+ const value = await resolve(context, component, epoch)
10
+
11
+ return { component, epoch, schema, configuration: value!.configuration }
12
+ })
13
+ )
14
+ }
15
+
16
+ interface Item {
17
+ component: string
18
+ epoch: string
19
+ schema: object
20
+ configuration: object
21
+ }
@@ -0,0 +1,10 @@
1
+ {
2
+ "extends": "../../../../tsconfig.component.json",
3
+ "compilerOptions": {
4
+ "outDir": "./operations",
5
+ "rootDir": "./source"
6
+ },
7
+ "include": [
8
+ "source"
9
+ ]
10
+ }