@toa.io/extensions.configuration 0.7.3 → 0.20.0-alpha.0

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 (77) hide show
  1. package/package.json +15 -9
  2. package/readme.md +77 -137
  3. package/schemas/annotation.cos.yaml +1 -0
  4. package/schemas/manifest.cos.yaml +2 -0
  5. package/source/Aspect.test.ts +15 -0
  6. package/source/Aspect.ts +23 -0
  7. package/source/Factory.ts +12 -0
  8. package/source/configuration.test.ts +89 -0
  9. package/source/configuration.ts +53 -0
  10. package/source/deployment.test.ts +21 -0
  11. package/source/deployment.ts +69 -0
  12. package/source/index.ts +3 -0
  13. package/source/manifest.test.ts +15 -0
  14. package/source/manifest.ts +15 -0
  15. package/source/schemas.ts +8 -0
  16. package/transpiled/Aspect.d.ts +7 -0
  17. package/transpiled/Aspect.js +21 -0
  18. package/transpiled/Aspect.js.map +1 -0
  19. package/transpiled/Aspect.test.d.ts +1 -0
  20. package/transpiled/Aspect.test.js +15 -0
  21. package/transpiled/Aspect.test.js.map +1 -0
  22. package/transpiled/Factory.d.ts +5 -0
  23. package/transpiled/Factory.js +13 -0
  24. package/transpiled/Factory.js.map +1 -0
  25. package/transpiled/configuration.d.ts +4 -0
  26. package/transpiled/configuration.js +66 -0
  27. package/transpiled/configuration.js.map +1 -0
  28. package/transpiled/configuration.test.d.ts +1 -0
  29. package/transpiled/configuration.test.js +64 -0
  30. package/transpiled/configuration.test.js.map +1 -0
  31. package/transpiled/deployment.d.ts +8 -0
  32. package/transpiled/deployment.js +75 -0
  33. package/transpiled/deployment.js.map +1 -0
  34. package/transpiled/deployment.test.d.ts +1 -0
  35. package/transpiled/deployment.test.js +18 -0
  36. package/transpiled/deployment.test.js.map +1 -0
  37. package/transpiled/index.d.ts +3 -0
  38. package/transpiled/index.js +10 -0
  39. package/transpiled/index.js.map +1 -0
  40. package/transpiled/manifest.d.ts +6 -0
  41. package/transpiled/manifest.js +35 -0
  42. package/transpiled/manifest.js.map +1 -0
  43. package/transpiled/manifest.test.d.ts +1 -0
  44. package/transpiled/manifest.test.js +14 -0
  45. package/transpiled/manifest.test.js.map +1 -0
  46. package/transpiled/schemas.d.ts +2 -0
  47. package/transpiled/schemas.js +33 -0
  48. package/transpiled/schemas.js.map +1 -0
  49. package/transpiled/tsconfig.tsbuildinfo +1 -0
  50. package/tsconfig.json +9 -0
  51. package/docs/discussion.md +0 -109
  52. package/src/.manifest/.normalize/verbose.js +0 -48
  53. package/src/.manifest/index.js +0 -7
  54. package/src/.manifest/normalize.js +0 -16
  55. package/src/.manifest/schema.yaml +0 -8
  56. package/src/.manifest/validate.js +0 -13
  57. package/src/.provider/form.js +0 -20
  58. package/src/annotation.js +0 -36
  59. package/src/aspect.js +0 -36
  60. package/src/configuration.js +0 -19
  61. package/src/deployment.js +0 -23
  62. package/src/factory.js +0 -39
  63. package/src/index.js +0 -13
  64. package/src/manifest.js +0 -13
  65. package/src/provider.js +0 -114
  66. package/test/annotations.fixtures.js +0 -38
  67. package/test/annotations.test.js +0 -43
  68. package/test/aspect.fixtures.js +0 -33
  69. package/test/aspect.test.js +0 -77
  70. package/test/deployment.fixtures.js +0 -38
  71. package/test/deployment.test.js +0 -45
  72. package/test/factory.test.js +0 -22
  73. package/test/manifest.test.js +0 -132
  74. package/types/aspect.ts +0 -11
  75. package/types/factory.d.ts +0 -12
  76. package/types/provider.d.ts +0 -22
  77. /package/{docs → notes}/consistency.md +0 -0
package/package.json CHANGED
@@ -1,10 +1,11 @@
1
1
  {
2
2
  "name": "@toa.io/extensions.configuration",
3
- "version": "0.7.3",
3
+ "version": "0.20.0-alpha.0",
4
4
  "description": "Toa Configuration",
5
5
  "author": "temich <tema.gurtovoy@gmail.com>",
6
6
  "homepage": "https://github.com/toa-io/toa#readme",
7
- "main": "src/index.js",
7
+ "main": "transpiled/index.js",
8
+ "types": "transpiled/index.d.ts",
8
9
  "repository": {
9
10
  "type": "git",
10
11
  "url": "git+https://github.com/toa-io/toa.git"
@@ -15,12 +16,17 @@
15
16
  "publishConfig": {
16
17
  "access": "public"
17
18
  },
18
- "dependencies": {
19
- "@toa.io/core": "0.8.1",
20
- "@toa.io/generic": "0.9.0",
21
- "@toa.io/schema": "0.7.3",
22
- "@toa.io/yaml": "0.7.3",
23
- "clone-deep": "4.0.1"
19
+ "scripts": {
20
+ "transpile": "tsc"
21
+ },
22
+ "jest": {
23
+ "preset": "ts-jest",
24
+ "testEnvironment": "node"
24
25
  },
25
- "gitHead": "a5019af0ad7b08182d0f245af3382f96a7e22d71"
26
+ "gitHead": "d047190899218b5249901a01a2a4caec5b34cf09",
27
+ "dependencies": {
28
+ "@toa.io/core": "0.20.0-alpha.0",
29
+ "@toa.io/generic": "0.20.0-alpha.0",
30
+ "@toa.io/schemas": "0.20.0-alpha.0"
31
+ }
26
32
  }
package/readme.md CHANGED
@@ -1,24 +1,28 @@
1
- # Toa Configuration Extension
1
+ # Toa Configuration
2
2
 
3
3
  ## TL;DR
4
4
 
5
5
  ### Define
6
6
 
7
7
  ```yaml
8
- # component.toa.yaml
8
+ # manifest.toa.yaml
9
9
  name: dummy
10
10
  namespace: dummies
11
11
 
12
12
  configuration:
13
- foo: bar
14
- baz: 1
13
+ schema:
14
+ foo: string
15
+ bar: number
16
+ defaults:
17
+ foo: bar
18
+ bar: 1
15
19
  ```
16
20
 
17
21
  ### Use
18
22
 
19
23
  ```javascript
20
24
  function transition (input, entity, context) {
21
- const { foo, baz } = context.configuration
25
+ const { foo, bar } = context.configuration
22
26
 
23
27
  // ...
24
28
  }
@@ -30,152 +34,111 @@ function transition (input, entity, context) {
30
34
  # context.toa.yaml
31
35
  configuration:
32
36
  dummies.dummy:
33
- foo: qux
34
- foo@staging: quux # use deployment environment discriminator
35
- baz: $BAZ_VALUE # use secrets
37
+ foo: qux # override default value
38
+ foo@staging: quux # deployment environment discriminator
39
+ bar: $BAZ_VALUE # secret
36
40
  ```
37
41
 
38
42
  ### Deploy secrets
39
43
 
40
44
  ```shell
41
- $ toa conceal
45
+ $ toa conceal configuration BAZ_VALUE=$ecr3t
42
46
  ```
43
47
 
44
48
  ---
45
49
 
46
50
  ## Problem Definition
47
51
 
48
- - Components must be reusable in different contexts and deployment environments,
49
- that is in different configurations.
50
- - Some algorithm parameters must be deployed secretly.
51
-
52
- ## Definitions
53
-
54
- ### Configuration (Distributed System Configuration)
55
-
56
- Set of static[^1] parameters for all algorithms within a given system.
57
-
58
- ### Configuration Schema
52
+ - Components should be runnable in different deployment environments.
53
+ - Some algorithm's parameters should be deployed secretly.
54
+ - Components should be reusable in different contexts.
59
55
 
60
- Schema defining component's algorithms parameters (optionally with default
61
- values).
56
+ ## Manifest
62
57
 
63
- ### Configuration Object
58
+ Component's configuration is declared using `configuration` manifest,
59
+ containing `schema` and optionnaly `defaults` properties.
64
60
 
65
- Value valid against Configuration Schema.
61
+ ### Schema
66
62
 
67
- ### Configuration Value
63
+ Configuration schema is declared with [COS](/libraries/concise).
68
64
 
69
- Merge result of Configuration Schema's defaults and Configuration Object.
70
-
71
- ### Context Configuration
72
-
73
- Map of Configuration Objects for components added to a given context.
74
-
75
- ## Responsibility Segregation
65
+ ```yaml
66
+ # manifest.toa.yaml
67
+ name: dummy
68
+ namespace: dummies
76
69
 
77
- Configuration Schema is a *form* of configuration defined by component. Specific *values* for
78
- specific contexts and deployment environments are defined by Context Configuration according to the
79
- Schema.
70
+ configuration:
71
+ schema:
72
+ foo: string
73
+ bar: number
74
+ ```
80
75
 
81
- See [Reusable Components](#).
76
+ > Introducing non-backward compatible changes to a configuration schema will result in a loss of
77
+ > compatibility with existing contexts and deployment environments.
78
+ > Therefore, configuration schema changes are subject to component versioning.
82
79
 
83
- ## Configuration Schema
80
+ If `configuration` object doesn't contain property `schema`, then it is considered to be schema.
84
81
 
85
- Configuration Schema is declared as a component extension
86
- using [JSON Schema](https://json-schema.org) `object` type.
82
+ ```yaml
83
+ # manifest.toa.yaml
84
+ name: dummy
85
+ namespace: dummies
87
86
 
88
- > ![Warning](https://img.shields.io/badge/Warning-yellow)<br/>
89
- > By introducing non-backward compatible changes to a Configuration Schema the compatibility
90
- > with existent contexts and deployment environments will be broken. That is, Configuration
91
- > Schema changes are subjects of component versioning.
87
+ configuration:
88
+ foo: string
89
+ bar: number
90
+ ```
92
91
 
93
- > ![Recommendation](https://img.shields.io/badge/Recommendation-green)<br/>
94
- > Having default values for all required parameters will allow components to be runnable
95
- > without configuration (i.e. on local environment).
92
+ ### Defaults
96
93
 
97
- ### Example
94
+ The default configuration value can be provided using the `defaults` property, which should conform
95
+ to the configuration schema.
98
96
 
99
97
  ```yaml
100
- # component.toa.yaml
98
+ # manifest.toa.yaml
101
99
  name: dummy
102
100
  namespace: dummies
103
101
 
104
- extensions:
105
- "@toa.io/extensions.configuration":
106
- properties:
107
- foo:
108
- type: string
109
- default: 'baz'
110
- bar:
111
- type: number
112
- required: [foo]
102
+ configuration:
103
+ schema:
104
+ foo: string
105
+ bar: number
106
+ defaults:
107
+ foo: hello
108
+ bar: 0
113
109
  ```
114
110
 
115
- ### Concise Declaration
111
+ #### Schema defaults hint
116
112
 
117
- As it is known that Configuration Schema is declared with a JSON Schema `object` type, any
118
- configuration declaration without defined `properties` considered as concise. Properties of concise
119
- declaration are treated as required Configuration Schema properties with the same type as its value
120
- type and no additional properties allowed.
121
-
122
- Also note that a well-known shortcut `configuration` is available.
123
-
124
- Next two declarations are equivalent.
113
+ The configuration schema itself can contain default primitive values using the COS syntax.
125
114
 
126
115
  ```yaml
127
- # component.toa.yaml
128
- configuration:
129
- foo: baz
130
- bar: 1
131
- ```
116
+ # manifest.toa.yaml
117
+ name: dummy
118
+ namespace: dummies
132
119
 
133
- ```yaml
134
- # component.toa.yaml
135
- extensions:
136
- "@toa.io/extensions.configuration":
137
- properties:
138
- foo:
139
- type: string
140
- default: baz
141
- bar:
142
- type: number
143
- default: 1
144
- additionalProperties: false
145
- required: [foo, bar]
120
+ configuration:
121
+ schema:
122
+ foo: hello
123
+ bar: 0
146
124
  ```
147
125
 
148
- ## Context Configuration
149
-
150
- Context Configuration is declared as a context annotaion. Its keys must be
151
- component identifiers and its values must be Configuration Objects for those
152
- components.
126
+ ## Annotation
153
127
 
154
- Context Configuration keys and Configuration Object keys may be defined
155
- with [deployment environment discriminators](#).
156
-
157
- ### Example
128
+ A component's configuration can be overridden using the configuration context annotation.
158
129
 
159
130
  ```yaml
160
131
  # context.toa.yaml
161
132
  configuration:
162
133
  dummies.dummy:
163
- foo: quu
134
+ foo: bye
164
135
  bar: 1
165
136
  bar@staging: 2
166
137
  ```
167
138
 
168
- ### Local environment
169
-
170
- Configuration Objects for local environment may be created
171
- by [`toa configure`](../../runtime/cli/readme.md#configure) command.
172
-
173
- ## Configuration Secrets
139
+ ## Secrets
174
140
 
175
- Context Configuration values which are uppercase strings prefixed with `$`
176
- considered as Secrets.
177
-
178
- ### Example
141
+ Configuration annotation top-level values which are uppercase strings prefixed with `$` considered as secrets.
179
142
 
180
143
  ```yaml
181
144
  # context.toa.yaml
@@ -184,18 +147,20 @@ configuration:
184
147
  api-key: $STRIPE_API_KEY
185
148
  ```
186
149
 
187
- ### Secrets Deployment
188
-
189
150
  Secrets are not being deployed with context
190
- deployment ([`toa deploy`](../../runtime/cli/readme.md#deploy)),
191
- thus must be deployed separately at least once for each deployment environment
192
- manually ([`toa conceal`](../../runtime/cli/readme.md#conceal)).
151
+ deployment ([`toa deploy`](/runtime/cli/readme.md#deploy)), thus must be deployed separately at
152
+ least once for each deployment environment
153
+ manually ([`toa conceal`](/runtime/cli/readme.md#conceal)).
193
154
 
194
- ## Operation Context
155
+ Deployed kubernetes secret's name is predefined as `configuration`.
195
156
 
196
- Configuration Value is available as a well-known operation context extension `configuration`.
157
+ ```shell
158
+ $ toa conceal configuration STRIPE_API_KEY=xxxxxxxx
159
+ ```
197
160
 
198
- ### Usage: node
161
+ ## Aspect
162
+
163
+ Component's configuration values are available as a well-known Aspect `configuration`.
199
164
 
200
165
  ```javascript
201
166
  function transition (input, entity, context) {
@@ -204,28 +169,3 @@ function transition (input, entity, context) {
204
169
  // ...
205
170
  }
206
171
  ```
207
-
208
- > ![Warning](https://img.shields.io/badge/Warning-yellow)<br/>
209
- > It is strongly **not** recommended to store a copy of value type configuration
210
- > values outside of operation scope, thus it prevents operation to benefit
211
- > from [hot updates](#).
212
- >
213
- > ```javascript
214
- > // THIS IS WEIRD, BAD AND NOT RECOMMENDED
215
- > let foo
216
- >
217
- > function transition (input, entity, context) {
218
- > if (foo === undefined) foo = context.configuration.foo
219
- >
220
- > // ...
221
- > }
222
- > ```
223
- > See [Genuine operations](#).
224
-
225
- ## Appendix
226
-
227
- - [Discussion](./docs/discussion.md)
228
- - [Configuration consistency](./docs/consistency.md)
229
-
230
- [^1]: Cannot be changed without a deployment. New values are considered to be a subject of
231
- testing. [#146](https://github.com/toa-io/toa/issues/146)
@@ -0,0 +1 @@
1
+ <object>
@@ -0,0 +1,2 @@
1
+ schema: object
2
+ defaults?: object
@@ -0,0 +1,15 @@
1
+ import { Aspect } from './Aspect'
2
+
3
+ it('should return value', async () => {
4
+ const configuration = {
5
+ foo: 'bar',
6
+ bar: {
7
+ baz: 'quux'
8
+ }
9
+ }
10
+
11
+ const aspect = new Aspect(configuration)
12
+
13
+ expect(aspect.invoke(['foo'])).toStrictEqual(configuration.foo)
14
+ expect(aspect.invoke(['bar', 'baz'])).toStrictEqual(configuration.bar.baz)
15
+ })
@@ -0,0 +1,23 @@
1
+ import { Connector, type extensions } from '@toa.io/core'
2
+
3
+ export class Aspect extends Connector implements extensions.Aspect {
4
+ public readonly name = 'configuration'
5
+
6
+ private readonly value: object
7
+
8
+ public constructor (value: object) {
9
+ super()
10
+
11
+ this.value = value
12
+ }
13
+
14
+ public invoke (path: string[]): any {
15
+ let cursor: any = this.value
16
+
17
+ if (path !== undefined)
18
+ for (const segment of path)
19
+ cursor = cursor[segment]
20
+
21
+ return cursor
22
+ }
23
+ }
@@ -0,0 +1,12 @@
1
+ import { type Locator, type extensions } from '@toa.io/core'
2
+ import { Aspect } from './Aspect'
3
+ import { type Manifest } from './manifest'
4
+ import { get } from './configuration'
5
+
6
+ export class Factory implements extensions.Factory {
7
+ public aspect (locator: Locator, manifest: Manifest): extensions.Aspect {
8
+ const value = get(locator, manifest)
9
+
10
+ return new Aspect(value)
11
+ }
12
+ }
@@ -0,0 +1,89 @@
1
+ import { encode } from '@toa.io/generic'
2
+ import { Locator } from '@toa.io/core'
3
+ import { generate } from 'randomstring'
4
+ import { get } from './configuration'
5
+ import { type Manifest } from './manifest'
6
+
7
+ let locator: Locator
8
+ let manifest: Manifest
9
+
10
+ beforeEach(() => {
11
+ locator = new Locator(generate(), generate())
12
+ manifest = { schema: { foo: 'string' } }
13
+ })
14
+
15
+ afterEach(() => {
16
+ for (const name of used)
17
+ process.env[name] = undefined
18
+
19
+ used = []
20
+ })
21
+
22
+ it('should read value', async () => {
23
+ manifest.schema = { foo: 'string' }
24
+ const value: object = { foo: generate() }
25
+
26
+ set(value)
27
+
28
+ const result = get(locator, manifest)
29
+
30
+ expect(result).toStrictEqual(value)
31
+ })
32
+
33
+ it('should return empty object if no value set', async () => {
34
+ expect(get(locator, manifest)).toStrictEqual({})
35
+ })
36
+
37
+ it('should substitute secrets', async () => {
38
+ const value: object = { foo: '$BAR' }
39
+
40
+ set(value)
41
+ set('bar', '_BAR')
42
+
43
+ const result = get(locator, manifest)
44
+
45
+ expect(result).toStrictEqual({ foo: 'bar' })
46
+ })
47
+
48
+ it('should use defaults', async () => {
49
+ manifest.schema = { foo: 'string', bar: ['number'], 'baz?': 'string' }
50
+ manifest.defaults = { foo: 'bar', bar: [1] }
51
+
52
+ const values = { bar: [2], baz: 'foo' }
53
+
54
+ set(values)
55
+
56
+ const result = get(locator, manifest)
57
+
58
+ expect(result).toStrictEqual({
59
+ foo: 'bar',
60
+ bar: [2],
61
+ baz: 'foo'
62
+ })
63
+ })
64
+
65
+ it('should validate', async () => {
66
+ manifest.schema = { foo: 'hello', bar: 'number' }
67
+
68
+ const values = { bar: 5 }
69
+
70
+ set(values)
71
+
72
+ const result = get(locator, manifest)
73
+
74
+ expect(result).toStrictEqual({
75
+ foo: 'hello',
76
+ bar: 5
77
+ })
78
+ })
79
+
80
+ function set (value: object | string, key = locator.uppercase): void {
81
+ const string = typeof value === 'string' ? value : encode(value)
82
+ const name = 'TOA_CONFIGURATION_' + key
83
+
84
+ process.env[name] = string
85
+
86
+ used.push(name)
87
+ }
88
+
89
+ let used: string[] = []
@@ -0,0 +1,53 @@
1
+ import { type Locator } from '@toa.io/core'
2
+ import { decode, add } from '@toa.io/generic'
3
+ import * as schemas from '@toa.io/schemas'
4
+ import { PREFIX, SECRET_RX } from './deployment'
5
+ import { type Manifest } from './manifest'
6
+
7
+ export function get (locator: Locator, manifest: Manifest): Configuration {
8
+ const values = getConfiguration(locator.uppercase)
9
+
10
+ substituteSecrets(values)
11
+
12
+ if (manifest.defaults !== undefined)
13
+ add(values, manifest.defaults)
14
+
15
+ const schema = schemas.schema(manifest.schema)
16
+
17
+ schema.validate(values)
18
+
19
+ return values
20
+ }
21
+
22
+ function getConfiguration (suffix: string): Configuration {
23
+ const variable = PREFIX + suffix
24
+ const string = process.env[variable]
25
+
26
+ if (string === undefined) return {}
27
+ else return decode(string)
28
+ }
29
+
30
+ function substituteSecrets (configuration: Configuration): void {
31
+ for (const [key, value] of Object.entries(configuration)) {
32
+ if (typeof value !== 'string') continue
33
+
34
+ const match = value.match(SECRET_RX)
35
+
36
+ if (match === null) continue
37
+
38
+ const name = match.groups?.variable as string
39
+
40
+ configuration[key] = getSecret(name)
41
+ }
42
+ }
43
+
44
+ function getSecret (name: string): string {
45
+ const variable = PREFIX + '_' + name
46
+ const value = process.env[variable]
47
+
48
+ if (value === undefined) throw new Error(`${variable} is not set.`)
49
+
50
+ return value
51
+ }
52
+
53
+ export type Configuration = Record<string, any>
@@ -0,0 +1,21 @@
1
+ import { type Annotation, deployment, type Instance } from './deployment'
2
+ import { type Manifest } from './manifest'
3
+
4
+ it('should validate annotation', async () => {
5
+ const wrongType = 'not ok' as unknown as Annotation
6
+
7
+ expect(() => deployment([], wrongType)).toThrow('object')
8
+ })
9
+
10
+ it('should validate values', async () => {
11
+ const manifest: Manifest = {
12
+ schema: { foo: 'string', bar: 'number' },
13
+ defaults: { foo: 'ok', bar: 0 }
14
+ }
15
+
16
+ const locator = { id: 'component' }
17
+ const instances = [{ manifest, locator }] as unknown as Instance[]
18
+ const annotation: Annotation = { [locator.id]: { bar: 'not a number' } }
19
+
20
+ expect(() => deployment(instances, annotation)).toThrow('number')
21
+ })
@@ -0,0 +1,69 @@
1
+ import { type Dependency, type Variable, type Variables } from '@toa.io/operations'
2
+ import * as schemas from '@toa.io/schemas'
3
+ import { encode, overwrite } from '@toa.io/generic'
4
+ import { type Manifest } from './manifest'
5
+ import * as validators from './schemas'
6
+ import type { context } from '@toa.io/norm'
7
+
8
+ export function deployment (instances: Instance[], annotation: Annotation = {}): Dependency {
9
+ validators.annotation.validate(annotation)
10
+
11
+ const variables: Variables = {}
12
+
13
+ for (const instance of instances) {
14
+ const values = annotation[instance.locator.id]
15
+
16
+ validate(instance, values)
17
+
18
+ if (values === undefined) continue
19
+
20
+ variables[instance.locator.label] = [{
21
+ name: PREFIX + instance.locator.uppercase,
22
+ value: encode(values)
23
+ }]
24
+
25
+ const secrets = createSecrets(values)
26
+
27
+ variables[instance.locator.label].push(...secrets)
28
+ }
29
+
30
+ return { variables }
31
+ }
32
+
33
+ function createSecrets (values: object): Variable[] {
34
+ const secrets: Variable[] = []
35
+
36
+ for (const value of Object.values(values)) {
37
+ if (typeof value !== 'string') continue
38
+
39
+ const match = value.match(SECRET_RX)
40
+
41
+ if (match === null) continue
42
+
43
+ const name = match.groups?.variable as string
44
+
45
+ secrets.push({
46
+ name: PREFIX + '_' + name,
47
+ secret: {
48
+ name: 'toa-configuration',
49
+ key: name
50
+ }
51
+ })
52
+ }
53
+
54
+ return secrets
55
+ }
56
+
57
+ function validate (instace: Instance, values: object = {}): void {
58
+ const defaults = instace.manifest.defaults ?? {}
59
+ const configuration = overwrite(defaults, values)
60
+ const schema = schemas.schema(instace.manifest.schema)
61
+
62
+ schema.validate(configuration)
63
+ }
64
+
65
+ export const SECRET_RX = /^\$(?<variable>[A-Z0-9_]{1,32})$/
66
+ export const PREFIX = 'TOA_CONFIGURATION_'
67
+
68
+ export type Annotation = Record<string, object>
69
+ export type Instance = context.Dependency<Manifest>
@@ -0,0 +1,3 @@
1
+ export { manifest } from './manifest'
2
+ export { deployment } from './deployment'
3
+ export { Factory } from './Factory'
@@ -0,0 +1,15 @@
1
+ import { type Manifest, manifest } from './manifest'
2
+
3
+ it('should validate', async () => {
4
+ const additional = { schema: {}, foo: 'bar' } as unknown as Manifest
5
+
6
+ expect(() => {
7
+ manifest(additional)
8
+ }).toThrow('not expected')
9
+
10
+ const wrongType = { schema: 'not ok' } as unknown as Manifest
11
+
12
+ expect(() => {
13
+ manifest(wrongType)
14
+ }).toThrow('object')
15
+ })
@@ -0,0 +1,15 @@
1
+ import * as schemas from './schemas'
2
+ import { type Configuration } from './configuration'
3
+
4
+ export function manifest (manifest: Manifest): Manifest {
5
+ if (manifest.schema === undefined) manifest = { schema: manifest }
6
+
7
+ schemas.manifest.validate(manifest)
8
+
9
+ return manifest
10
+ }
11
+
12
+ export interface Manifest {
13
+ schema: object
14
+ defaults?: Configuration
15
+ }
@@ -0,0 +1,8 @@
1
+ import { resolve } from 'node:path'
2
+ import * as schemas from '@toa.io/schemas'
3
+
4
+ const path = resolve(__dirname, '../schemas')
5
+ const namespace = schemas.namespace(path)
6
+
7
+ export const manifest = namespace.schema('manifest')
8
+ export const annotation = namespace.schema('annotation')