@toa.io/extensions.configuration 1.0.0-alpha.31 → 1.0.0-alpha.310

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 (104) hide show
  1. package/components/configuration.values/manifest.toa.yaml +151 -0
  2. package/components/configuration.values/migrations/0002-indexes.yaml +7 -0
  3. package/components/configuration.values/operations/create.js +32 -0
  4. package/components/configuration.values/operations/fetch.js +13 -0
  5. package/components/configuration.values/operations/get.js +19 -0
  6. package/components/configuration.values/operations/lib/map.js +22 -0
  7. package/components/configuration.values/operations/lib/resolve.js +24 -0
  8. package/components/configuration.values/operations/list.js +11 -0
  9. package/components/configuration.values/source/create.ts +56 -0
  10. package/components/configuration.values/source/fetch.ts +26 -0
  11. package/components/configuration.values/source/get.ts +32 -0
  12. package/components/configuration.values/source/lib/map.ts +36 -0
  13. package/components/configuration.values/source/lib/resolve.ts +57 -0
  14. package/components/configuration.values/source/list.ts +21 -0
  15. package/components/configuration.values/tsconfig.json +10 -0
  16. package/components/configuration.values/types/index.d.ts +3 -0
  17. package/components/configuration.values/types/toa.d.ts +69 -0
  18. package/package.json +21 -10
  19. package/readme.md +178 -50
  20. package/transpiled/Aspect.d.ts +19 -4
  21. package/transpiled/Aspect.js +62 -9
  22. package/transpiled/Aspect.js.map +1 -1
  23. package/transpiled/Client.d.ts +59 -0
  24. package/transpiled/Client.js +171 -0
  25. package/transpiled/Client.js.map +1 -0
  26. package/transpiled/Composition.d.ts +14 -0
  27. package/transpiled/Composition.js +38 -0
  28. package/transpiled/Composition.js.map +1 -0
  29. package/transpiled/Factory.d.ts +9 -2
  30. package/transpiled/Factory.js +21 -9
  31. package/transpiled/Factory.js.map +1 -1
  32. package/transpiled/Secret.d.ts +15 -0
  33. package/transpiled/Secret.js +26 -0
  34. package/transpiled/Secret.js.map +1 -0
  35. package/transpiled/UI.d.ts +25 -0
  36. package/transpiled/UI.js +173 -0
  37. package/transpiled/UI.js.map +1 -0
  38. package/transpiled/configuration.d.ts +7 -2
  39. package/transpiled/configuration.js +33 -43
  40. package/transpiled/configuration.js.map +1 -1
  41. package/transpiled/index.d.ts +3 -3
  42. package/transpiled/index.js +4 -9
  43. package/transpiled/index.js.map +1 -1
  44. package/ui/dist/_app/env.js +1 -0
  45. package/ui/dist/_app/immutable/assets/0.BGFdDICE.css +2 -0
  46. package/ui/dist/_app/immutable/assets/ui.BghF56l_.css +1 -0
  47. package/ui/dist/_app/immutable/chunks/Bjy-W4x2.js +81 -0
  48. package/ui/dist/_app/immutable/chunks/BkLAzDsj.js +1 -0
  49. package/ui/dist/_app/immutable/chunks/C_aaOW6e.js +1 -0
  50. package/ui/dist/_app/immutable/chunks/CcD_ISxM.js +3 -0
  51. package/ui/dist/_app/immutable/chunks/Ccdp29n0.js +6 -0
  52. package/ui/dist/_app/immutable/chunks/Dw75U_Lh.js +50 -0
  53. package/ui/dist/_app/immutable/chunks/sXOccAdQ.js +8 -0
  54. package/ui/dist/_app/immutable/chunks/xihTtKlq.js +1 -0
  55. package/ui/dist/_app/immutable/entry/app.DgOXi1S4.js +2 -0
  56. package/ui/dist/_app/immutable/entry/start.C5YMpV87.js +1 -0
  57. package/ui/dist/_app/immutable/nodes/0.BGAo0V8q.js +1 -0
  58. package/ui/dist/_app/immutable/nodes/1.DYGSbNvw.js +1 -0
  59. package/ui/dist/_app/immutable/nodes/2.nR_WPY04.js +1 -0
  60. package/ui/dist/_app/immutable/nodes/3.p9Hd9gcE.js +1 -0
  61. package/ui/dist/_app/immutable/nodes/4.Chf5nqMR.js +1 -0
  62. package/ui/dist/_app/version.json +1 -0
  63. package/ui/dist/apple-touch-icon.png +0 -0
  64. package/ui/dist/favicon-96x96.png +0 -0
  65. package/ui/dist/favicon.ico +0 -0
  66. package/ui/dist/index.html +57 -0
  67. package/ui/dist/robots.txt +3 -0
  68. package/notes/consistency.md +0 -69
  69. package/schemas/annotation.cos.yaml +0 -1
  70. package/schemas/manifest.cos.yaml +0 -2
  71. package/source/Aspect.test.ts +0 -15
  72. package/source/Aspect.ts +0 -23
  73. package/source/Factory.ts +0 -12
  74. package/source/configuration.test.ts +0 -90
  75. package/source/configuration.ts +0 -59
  76. package/source/deployment.test.ts +0 -21
  77. package/source/deployment.ts +0 -86
  78. package/source/index.ts +0 -3
  79. package/source/manifest.test.ts +0 -15
  80. package/source/manifest.ts +0 -15
  81. package/source/schemas.ts +0 -9
  82. package/transpiled/Aspect.test.d.ts +0 -1
  83. package/transpiled/Aspect.test.js +0 -15
  84. package/transpiled/Aspect.test.js.map +0 -1
  85. package/transpiled/configuration.test.d.ts +0 -1
  86. package/transpiled/configuration.test.js +0 -64
  87. package/transpiled/configuration.test.js.map +0 -1
  88. package/transpiled/deployment.d.ts +0 -8
  89. package/transpiled/deployment.js +0 -88
  90. package/transpiled/deployment.js.map +0 -1
  91. package/transpiled/deployment.test.d.ts +0 -1
  92. package/transpiled/deployment.test.js +0 -18
  93. package/transpiled/deployment.test.js.map +0 -1
  94. package/transpiled/manifest.d.ts +0 -6
  95. package/transpiled/manifest.js +0 -35
  96. package/transpiled/manifest.js.map +0 -1
  97. package/transpiled/manifest.test.d.ts +0 -1
  98. package/transpiled/manifest.test.js +0 -14
  99. package/transpiled/manifest.test.js.map +0 -1
  100. package/transpiled/schemas.d.ts +0 -3
  101. package/transpiled/schemas.js +0 -33
  102. package/transpiled/schemas.js.map +0 -1
  103. package/transpiled/tsconfig.tsbuildinfo +0 -1
  104. package/tsconfig.json +0 -9
@@ -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,32 @@
1
+ import * as schemas from '@toa.io/schemas';
2
+ import { assertSecrets } from '@toa.io/definitions/extensions.configuration';
3
+ import { entry } from './lib/map.js';
4
+ export async function transition(input, object) {
5
+ const known = entry(input.component);
6
+ if (known === undefined)
7
+ return new UnknownComponentError(input.component);
8
+ const configuration = structuredClone(input.configuration);
9
+ const schema = schemas.schema(known.schema);
10
+ try {
11
+ schema.validate(configuration);
12
+ assertSecrets(known.schema, configuration);
13
+ }
14
+ catch (error) {
15
+ return new InvalidConfigurationError(error.message);
16
+ }
17
+ object.component = input.component;
18
+ object.epoch = known.epoch;
19
+ object.configuration = configuration;
20
+ object.originator = input.originator.id;
21
+ return object;
22
+ }
23
+ class UnknownComponentError extends Error {
24
+ code = 'UNKNOWN_COMPONENT';
25
+ constructor(component) {
26
+ super(`Component '${component}' is not configured`);
27
+ }
28
+ }
29
+ class InvalidConfigurationError extends Error {
30
+ code = 'INVALID_CONFIGURATION';
31
+ }
32
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY3JlYXRlLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vc291cmNlL2NyZWF0ZS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEtBQUssT0FBTyxNQUFNLGlCQUFpQixDQUFBO0FBQzFDLE9BQU8sRUFBRSxhQUFhLEVBQUUsTUFBTSw4Q0FBOEMsQ0FBQTtBQUM1RSxPQUFPLEVBQUUsS0FBSyxFQUFFLE1BQU0sY0FBYyxDQUFBO0FBR3BDLE1BQU0sQ0FBQyxLQUFLLFVBQVUsVUFBVSxDQUFDLEtBQVksRUFBRSxNQUFjO0lBQzNELE1BQU0sS0FBSyxHQUFHLEtBQUssQ0FBQyxLQUFLLENBQUMsU0FBUyxDQUFDLENBQUE7SUFFcEMsSUFBSSxLQUFLLEtBQUssU0FBUztRQUFFLE9BQU8sSUFBSSxxQkFBcUIsQ0FBQyxLQUFLLENBQUMsU0FBUyxDQUFDLENBQUE7SUFFMUUsTUFBTSxhQUFhLEdBQUcsZUFBZSxDQUFDLEtBQUssQ0FBQyxhQUFhLENBQUMsQ0FBQTtJQUUxRCxNQUFNLE1BQU0sR0FBZ0IsT0FBTyxDQUFDLE1BQU0sQ0FBQyxLQUFLLENBQUMsTUFBTSxDQUFDLENBQUE7SUFFeEQsSUFBSSxDQUFDO1FBQ0gsTUFBTSxDQUFDLFFBQVEsQ0FBQyxhQUFhLENBQUMsQ0FBQTtRQUM5QixhQUFhLENBQUMsS0FBSyxDQUFDLE1BQU0sRUFBRSxhQUFhLENBQUMsQ0FBQTtJQUM1QyxDQUFDO0lBQUMsT0FBTyxLQUFLLEVBQUUsQ0FBQztRQUNmLE9BQU8sSUFBSSx5QkFBeUIsQ0FBRSxLQUFlLENBQUMsT0FBTyxDQUFDLENBQUE7SUFDaEUsQ0FBQztJQUVELE1BQU0sQ0FBQyxTQUFTLEdBQUcsS0FBSyxDQUFDLFNBQVMsQ0FBQTtJQUNsQyxNQUFNLENBQUMsS0FBSyxHQUFHLEtBQUssQ0FBQyxLQUFLLENBQUE7SUFDMUIsTUFBTSxDQUFDLGFBQWEsR0FBRyxhQUFhLENBQUE7SUFDcEMsTUFBTSxDQUFDLFVBQVUsR0FBRyxLQUFLLENBQUMsVUFBVSxDQUFDLEVBQUUsQ0FBQTtJQUV2QyxPQUFPLE1BQU0sQ0FBQTtBQUNmLENBQUM7QUFFRCxNQUFNLHFCQUFzQixTQUFRLEtBQUs7SUFDdkIsSUFBSSxHQUFHLG1CQUFtQixDQUFBO0lBRTFDLFlBQW1CLFNBQWlCO1FBQ2xDLEtBQUssQ0FBQyxjQUFjLFNBQVMscUJBQXFCLENBQUMsQ0FBQTtJQUNyRCxDQUFDO0NBQ0Y7QUFFRCxNQUFNLHlCQUEwQixTQUFRLEtBQUs7SUFDM0IsSUFBSSxHQUFHLHVCQUF1QixDQUFBO0NBQy9DIn0=
@@ -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,56 @@
1
+ import * as schemas from '@toa.io/schemas'
2
+ import { assertSecrets } from '@toa.io/definitions/extensions.configuration'
3
+ import { entry } from './lib/map.ts'
4
+ import type { Schema } from '@toa.io/schemas'
5
+
6
+ export async function transition(input: Input, object: Entity): Promise<Entity | Error> {
7
+ const known = entry(input.component)
8
+
9
+ if (known === undefined) return new UnknownComponentError(input.component)
10
+
11
+ const configuration = structuredClone(input.configuration)
12
+
13
+ const schema: Schema<any> = schemas.schema(known.schema)
14
+
15
+ try {
16
+ schema.validate(configuration)
17
+ assertSecrets(known.schema, configuration)
18
+ } catch (error) {
19
+ return new InvalidConfigurationError((error as Error).message)
20
+ }
21
+
22
+ object.component = input.component
23
+ object.epoch = known.epoch
24
+ object.configuration = configuration
25
+ object.originator = input.originator.id
26
+
27
+ return object
28
+ }
29
+
30
+ class UnknownComponentError extends Error {
31
+ public readonly code = 'UNKNOWN_COMPONENT'
32
+
33
+ public constructor(component: string) {
34
+ super(`Component '${component}' is not configured`)
35
+ }
36
+ }
37
+
38
+ class InvalidConfigurationError extends Error {
39
+ public readonly code = 'INVALID_CONFIGURATION'
40
+ }
41
+
42
+ interface Input {
43
+ component: string
44
+ configuration: object
45
+ originator: {
46
+ id: string
47
+ }
48
+ }
49
+
50
+ interface Entity {
51
+ id: string
52
+ component: string
53
+ epoch: string
54
+ configuration: object
55
+ originator: string
56
+ }
@@ -0,0 +1,26 @@
1
+ import { resolve, type Context } from './lib/resolve.ts'
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.ts'
2
+ import { resolve, type Context } from './lib/resolve.ts'
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,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.ts'
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.ts'
2
+ import { resolve, type Context } from './lib/resolve.ts'
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
+ }
@@ -0,0 +1,3 @@
1
+ export * from './toa.d.ts'
2
+
3
+ // What a manifest does not state belongs here, and every run keeps it.
@@ -0,0 +1,69 @@
1
+ // Written by `toa types`. Every run rewrites it.
2
+ // What a manifest does not state belongs in a file of your own.
3
+
4
+ import type { Options, Query, RemoteError } from '@toa.io/core/types'
5
+ import type { Readable } from 'node:stream'
6
+
7
+ export interface Entity {
8
+ component: string
9
+ epoch: string
10
+ configuration: Record<string, unknown>
11
+ originator: string
12
+ id: string
13
+ VERSION: number
14
+ CREATED: number
15
+ UPDATED: number
16
+ DELETED: number | null
17
+ REGION: number
18
+ }
19
+
20
+ export type GetInput = {
21
+ component: string
22
+ epoch?: string
23
+ }
24
+
25
+ export type GetOutput = {
26
+ configuration: Record<string, unknown>
27
+ schema?: Record<string, unknown>
28
+ epoch: string
29
+ } | null
30
+
31
+ export type FetchInput = Array<{
32
+ component: string
33
+ epoch: string
34
+ }>
35
+
36
+ export type FetchOutput = Array<{
37
+ component: string
38
+ epoch: string
39
+ configuration: Record<string, unknown> | null
40
+ created: number
41
+ }>
42
+
43
+ export type ListOutput = Array<{
44
+ component: string
45
+ epoch: string
46
+ schema: Record<string, unknown>
47
+ configuration: Record<string, unknown>
48
+ }>
49
+
50
+ export type CreateInput = {
51
+ component: string
52
+ configuration: Record<string, unknown>
53
+ originator: {
54
+ id: string
55
+ }
56
+ }
57
+
58
+ export interface Component {
59
+ get: (request: { input: GetInput, task?: boolean }, options?: Options) => Promise<GetOutput>
60
+ fetch: (request: { input: FetchInput, task?: boolean }, options?: Options) => Promise<FetchOutput>
61
+ list: (request: { input?: null, task?: boolean }, options?: Options) => Promise<ListOutput>
62
+ create: (request: { input: CreateInput, task?: boolean }, options?: Options) => Promise<unknown | RemoteError<"UNKNOWN_COMPONENT" | "INVALID_CONFIGURATION">>
63
+ assign: (request: { input?: null, query?: Query<Entity>, task?: boolean }, options?: Options) => Promise<Entity>
64
+ ensure: (request: { input?: null, query?: Query<Entity>, task?: boolean }, options?: Options) => Promise<Entity>
65
+ enumerate: (request: { input?: null, query?: Query<Entity>, task?: boolean }, options?: Options) => Promise<Entity[]>
66
+ observe: (request: { input?: null, query?: Query<Entity>, task?: boolean }, options?: Options) => Promise<Entity | null>
67
+ stream: (request: { input?: null, query?: Query<Entity>, task?: boolean }, options?: Options) => Promise<Readable>
68
+ terminate: (request: { input?: null, query?: Query<Entity>, task?: boolean }, options?: Options) => Promise<Entity>
69
+ }
package/package.json CHANGED
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "name": "@toa.io/extensions.configuration",
3
- "version": "1.0.0-alpha.31",
3
+ "version": "1.0.0-alpha.310",
4
+ "type": "module",
4
5
  "description": "Toa Configuration",
5
6
  "author": "temich <tema.gurtovoy@gmail.com>",
6
7
  "homepage": "https://github.com/toa-io/toa#readme",
@@ -16,17 +17,27 @@
16
17
  "publishConfig": {
17
18
  "access": "public"
18
19
  },
20
+ "files": [
21
+ "transpiled",
22
+ "components",
23
+ "ui/dist",
24
+ "readme.md",
25
+ "!**/tsconfig.tsbuildinfo",
26
+ "!**/*.test.*",
27
+ "!**/.env*"
28
+ ],
19
29
  "scripts": {
20
- "transpile": "tsc"
30
+ "transpile": "tsc && tsc -p ./components/configuration.values",
31
+ "build:ui": "npm --prefix ui ci && npm --prefix ui run build",
32
+ "prepack": "npm run build:ui",
33
+ "features": "TSX_TSCONFIG_PATH=features/steps/tsconfig.json NODE_OPTIONS=--import=tsx cucumber-js"
21
34
  },
22
- "jest": {
23
- "preset": "ts-jest",
24
- "testEnvironment": "node"
25
- },
26
- "gitHead": "91839a488176853a0731f49abc70dc7103ed7cca",
35
+ "gitHead": "d952c57f77279729e26d52d50c6df47138ad0a4f",
27
36
  "dependencies": {
28
- "@toa.io/core": "1.0.0-alpha.31",
29
- "@toa.io/generic": "1.0.0-alpha.31",
30
- "@toa.io/schemas": "1.0.0-alpha.31"
37
+ "@toa.io/core": "1.0.0-alpha.310",
38
+ "@toa.io/definitions": "1.0.0-alpha.310",
39
+ "@toa.io/generic": "1.0.0-alpha.310",
40
+ "@toa.io/schemas": "1.0.0-alpha.310",
41
+ "openspan": "1.0.0-alpha.305"
31
42
  }
32
43
  }