@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/tsconfig.json ADDED
@@ -0,0 +1,9 @@
1
+ {
2
+ "extends": "../../tsconfig.json",
3
+ "compilerOptions": {
4
+ "outDir": "./transpiled"
5
+ },
6
+ "include": [
7
+ "source"
8
+ ]
9
+ }
@@ -1,109 +0,0 @@
1
- # Discussion
2
-
3
- ## Change Requests
4
-
5
- - [x] feat(configuration): add configuration extension
6
- - manifest (schema) validation
7
- - context extension
8
- - [x] feat(formation): add well-known extension 'configuration'
9
- - component
10
- - context
11
- - [x] feat(node): add well-known context extension 'configuration'
12
- - [x] feat(configuration): add concise declarations
13
- - [x] feat(configuration): add runtime configuration resolution
14
- - [x] feat(cli): add `toa configure <key> <value> --reset`
15
- - validate type
16
- - [x] feat(operations): add configuration deployment
17
- - annotations (values) validation
18
- - [ ] feat(configuration): add secrets resolution
19
- - [ ] feat(operations): add secrets deployment
20
- - [ ] feat(cli): add `toa conceal`
21
- - validate type
22
- - [ ] feat(cli): add `toa configure`
23
- - prompt required values
24
- - use JSON Schema title
25
-
26
- ## Statements
27
-
28
- ### Common
29
-
30
- - Secrets are being deployed separately by `toa conceal` command
31
-
32
- ### 1: Environment variables
33
-
34
- - Configuration values and secrets are mapped as environment variables to composition deployments
35
- - Extensions may expose *deployment mutators*, which are able to modify deployment declaration
36
- - Configuration context extension reads environment variables to resolve configuration and secrets
37
-
38
- ### 2: Dedicated Components
39
-
40
- - Hot updates
41
- - [Configuration consistency](consistency.md)
42
-
43
- ## Questions
44
-
45
- ### Where are values comes from?
46
-
47
- Environment variables.
48
-
49
- ### Is there a configuration service or configuration component?
50
-
51
- No. It will be implemented later as a part of [consistent configuration](consistency.md).
52
-
53
- ### How are configuration values being stored?
54
-
55
- As a kubernetes secrets mapped as environment variables.
56
-
57
- ### Where are secrets being stored and how do they resolve to value?
58
-
59
- As a kubernetes secrets mapped as environment variables.
60
-
61
- ### Is configuration a single environment variable or a set (one per component)?
62
-
63
- #### Context Configuration
64
-
65
- In later versions, context extension will resolve configuration values by component locator. Given
66
- that it is yet
67
- unknown when this will happen, a certain context might have appeared which configuration is big
68
- enough to not fit the
69
- environment variable limitations.
70
-
71
- That is, Context Configuration must be mapped as a set of environment variables (one per component).
72
- Values are
73
- serialized Configuration Objects.
74
-
75
- > This will also allow to configure local environment per component.
76
-
77
- #### Secrets
78
-
79
- Secrets are mapped per secret as they are not bound to components.
80
-
81
- ### Is configuration a single kubernetes secret or a set (one per component)?
82
-
83
- #### Configuration
84
-
85
- Single secret with a set of values per component.
86
-
87
- #### Secrets
88
-
89
- Once kubernetes secret per configuration secret.
90
-
91
- ### Is there an option to configure local environment?
92
-
93
- <dl>
94
- <dt><code>toa configure &lt;component&gt;</code></dt>
95
- <dd>Create local environment configuration values</dd>
96
- </dl>
97
-
98
- ### Whose responsibility is to call annotations?
99
-
100
- - norm
101
- - deployment
102
-
103
- `toa export context` should throw errors if context has invalid annotations, and it's not a part of
104
- the deployment.
105
-
106
- ## References
107
-
108
- - [#125](https://github.com/toa-io/toa/issues/125)
109
- - [#132](https://github.com/toa-io/toa/issues/132)
@@ -1,48 +0,0 @@
1
- 'use strict'
2
-
3
- const { remap } = require('@toa.io/generic')
4
-
5
- const verbose = (node) => {
6
- const isObject = node.properties !== undefined
7
- const isValue = node.type !== undefined && node.type !== 'object'
8
- const isProperties = node[SYM] === 1
9
-
10
- if (!isObject && !isValue && !isProperties) return convert(node)
11
-
12
- return node
13
- }
14
-
15
- const convert = (node) => {
16
- const properties = remap(node, property)
17
-
18
- properties[SYM] = 1
19
-
20
- return { type: 'object', properties, additionalProperties: false }
21
- }
22
-
23
- function property (node) {
24
- if (node === null) throw new Error('Configuration: cannot resolve type of null, use JSONSchema declaration.')
25
-
26
- const type = Array.isArray(node) ? 'array' : typeof node
27
-
28
- if (type === 'object') return node
29
- if (type === 'array') return array(node)
30
-
31
- return { type, default: node }
32
- }
33
-
34
- const array = (array) => {
35
- if (array.length === 0) throw new Error('Configuration: cannot resolve concise array items type because it\'s empty')
36
-
37
- const type = typeof array[0]
38
-
39
- return {
40
- type: 'array',
41
- items: { type },
42
- default: array
43
- }
44
- }
45
-
46
- const SYM = Symbol()
47
-
48
- exports.verbose = verbose
@@ -1,7 +0,0 @@
1
- 'use strict'
2
-
3
- const { normalize } = require('./normalize')
4
- const { validate } = require('./validate')
5
-
6
- exports.normalize = normalize
7
- exports.validate = validate
@@ -1,16 +0,0 @@
1
- 'use strict'
2
-
3
- const { traverse } = require('@toa.io/generic')
4
- const { verbose } = require('./.normalize/verbose')
5
-
6
- const normalize = (manifest) => {
7
- if (manifest.properties === undefined) manifest = expand(manifest)
8
-
9
- return manifest
10
- }
11
-
12
- const expand = (concise) => {
13
- return traverse(concise, verbose)
14
- }
15
-
16
- exports.normalize = normalize
@@ -1,8 +0,0 @@
1
- $schema: https://json-schema.org/draft/2019-09/schema
2
- $id: https://schemas.toa.io/0.0.0/extensions/configuration/manifest
3
-
4
- $ref: 'https://schemas.toa.io/0.0.0/definitions#/definitions/schema'
5
- properties:
6
- type:
7
- const: object
8
- default: object
@@ -1,13 +0,0 @@
1
- 'use strict'
2
-
3
- const path = require('path')
4
-
5
- const { Schema } = require('@toa.io/schema')
6
- const { load } = require('@toa.io/yaml')
7
-
8
- const schema = load.sync(path.resolve(__dirname, 'schema.yaml'))
9
- const validator = new Schema(schema)
10
-
11
- const validate = (declaration) => validator.validate(declaration)
12
-
13
- exports.validate = validate
@@ -1,20 +0,0 @@
1
- 'use strict'
2
-
3
- const { traverse } = require('@toa.io/generic')
4
-
5
- /**
6
- * @param {toa.schema.JSON | Object} schema
7
- * @return {Object}
8
- */
9
- const form = (schema) => {
10
- const defaults = (node) => {
11
- if (node.properties !== undefined) return { ...node.properties }
12
- if (node.default !== undefined) return node.default
13
-
14
- return null
15
- }
16
-
17
- return traverse(schema, defaults)
18
- }
19
-
20
- exports.form = form
package/src/annotation.js DELETED
@@ -1,36 +0,0 @@
1
- 'use strict'
2
-
3
- const { Schema } = require('@toa.io/schema')
4
-
5
- /**
6
- * @param {Object} annotation
7
- * @param {toa.norm.context.dependencies.Instance[]} instances
8
- */
9
- const annotation = (annotation, instances) => {
10
- const keys = Object.keys(annotation)
11
-
12
- check(keys, instances)
13
-
14
- for (const instance of instances) {
15
- const object = annotation[instance.locator.id] ?? {}
16
- const schema = new Schema(instance.manifest)
17
-
18
- schema.validate(object)
19
- }
20
-
21
- return annotation
22
- }
23
-
24
- /**
25
- * @param {string[]} keys
26
- * @param {toa.norm.context.dependencies.Instance[]} instances
27
- */
28
- const check = (keys, instances) => {
29
- const ids = instances.map((instance) => instance.locator.id)
30
-
31
- for (const key of keys) {
32
- if (!ids.includes(key)) throw new Error(`Configuration Schema '${key}' is not defined`)
33
- }
34
- }
35
-
36
- exports.annotation = annotation
package/src/aspect.js DELETED
@@ -1,36 +0,0 @@
1
- 'use strict'
2
-
3
- const { Connector } = require('@toa.io/core')
4
-
5
- /**
6
- * @implements {toa.extensions.configuration.Aspect}
7
- */
8
- class Aspect extends Connector {
9
- /** @readonly */
10
- name = 'configuration'
11
-
12
- /** @type {toa.core.Reflection} */
13
- #refection
14
-
15
- /**
16
- * @param {toa.core.Reflection} reflection
17
- */
18
- constructor (reflection) {
19
- super()
20
-
21
- this.#refection = reflection
22
-
23
- this.depends(reflection)
24
- }
25
-
26
- invoke (path) {
27
- /** @type {any} */
28
- let cursor = this.#refection.value
29
-
30
- if (path !== undefined) for (const segment of path) cursor = cursor[segment]
31
-
32
- return cursor
33
- }
34
- }
35
-
36
- exports.Aspect = Aspect
@@ -1,19 +0,0 @@
1
- 'use strict'
2
-
3
- const { Reflection } = require('@toa.io/core')
4
-
5
- /**
6
- * @implements {toa.core.Reflection}
7
- */
8
- class Configuration extends Reflection {
9
- /**
10
- * @param {toa.extensions.configuration.Provider} provider
11
- */
12
- constructor (provider) {
13
- super(provider.source)
14
-
15
- this.depends(provider)
16
- }
17
- }
18
-
19
- exports.Configuration = Configuration
package/src/deployment.js DELETED
@@ -1,23 +0,0 @@
1
- 'use strict'
2
-
3
- const { encode } = require('@toa.io/generic')
4
-
5
- /**
6
- * @type {toa.deployment.dependency.Constructor}
7
- */
8
- const deployment = (components, annotations) => {
9
- const variables = {}
10
-
11
- for (const [id, annotation] of Object.entries(annotations)) {
12
- const component = components.find((component) => component.locator.id === id)
13
-
14
- variables[component.locator.label] = [{
15
- name: 'TOA_CONFIGURATION_' + component.locator.uppercase,
16
- value: encode(annotation)
17
- }]
18
- }
19
-
20
- return { variables }
21
- }
22
-
23
- exports.deployment = deployment
package/src/factory.js DELETED
@@ -1,39 +0,0 @@
1
- 'use strict'
2
-
3
- const { Schema } = require('@toa.io/schema')
4
- const { Aspect } = require('./aspect')
5
- const { Configuration } = require('./configuration')
6
- const { Provider } = require('./provider')
7
-
8
- /**
9
- * @implements {toa.extensions.configuration.Factory}
10
- */
11
- class Factory {
12
- /**
13
- * @param {toa.core.Locator} locator
14
- * @param {toa.schema.JSON | Object} declaration
15
- * @return {toa.extensions.configuration.Aspect}
16
- */
17
- aspect (locator, declaration) {
18
- const schema = new Schema(declaration)
19
- const provider = new Provider(locator, schema)
20
- const configuration = new Configuration(provider)
21
-
22
- return new Aspect(configuration)
23
- }
24
-
25
- provider (component) {
26
- const locator = component.locator
27
- const declaration = component.extensions?.[ID]
28
-
29
- if (declaration === undefined) throw new Error(`Configuration extension not found in '${locator.id}'`)
30
-
31
- const schema = new Schema(declaration)
32
-
33
- return new Provider(locator, schema)
34
- }
35
- }
36
-
37
- const ID = require('../package.json').name
38
-
39
- exports.Factory = Factory
package/src/index.js DELETED
@@ -1,13 +0,0 @@
1
- 'use strict'
2
-
3
- const { manifest } = require('./manifest')
4
- const { annotation } = require('./annotation')
5
- const { deployment } = require('./deployment')
6
-
7
- const { Factory } = require('./factory')
8
-
9
- exports.manifest = manifest
10
- exports.annotation = annotation
11
- exports.deployment = deployment
12
-
13
- exports.Factory = Factory
package/src/manifest.js DELETED
@@ -1,13 +0,0 @@
1
- 'use strict'
2
-
3
- const { normalize, validate } = require('./.manifest')
4
-
5
- const manifest = (manifest) => {
6
- const declaration = normalize(manifest)
7
-
8
- validate(declaration)
9
-
10
- return declaration
11
- }
12
-
13
- exports.manifest = manifest
package/src/provider.js DELETED
@@ -1,114 +0,0 @@
1
- 'use strict'
2
-
3
- const clone = require('clone-deep')
4
- const { decode, encode, empty, overwrite } = require('@toa.io/generic')
5
-
6
- const { Connector } = require('@toa.io/core')
7
- const { form } = require('./.provider/form')
8
-
9
- /**
10
- * @implements {toa.extensions.configuration.Provider}
11
- */
12
- class Provider extends Connector {
13
- /** @type {toa.schema.Schema} */
14
- #schema
15
-
16
- /** @type {Object} */
17
- #form
18
- /** @type {Object} */
19
- #value
20
-
21
- source
22
- object
23
- key
24
-
25
- /**
26
- * @param {toa.core.Locator} locator
27
- * @param {toa.schema.Schema} schema
28
- */
29
- constructor (locator, schema) {
30
- super()
31
-
32
- this.source = this.#source.bind(this)
33
-
34
- this.key = PREFIX + locator.uppercase
35
- this.#schema = schema
36
-
37
- // form is required to enable nested defaults
38
- this.#form = form(schema.schema)
39
- }
40
-
41
- async open () {
42
- await this.#retrieve()
43
- }
44
-
45
- async #source () {
46
- return this.#value
47
- }
48
-
49
- set (key, value) {
50
- const object = this.object === undefined ? {} : clone(this.object)
51
- const properties = key.split('.')
52
- const property = properties.pop()
53
-
54
- let cursor = object
55
-
56
- for (const name of properties) {
57
- if (cursor[name] === undefined) cursor[name] = {}
58
-
59
- cursor = cursor[name]
60
- }
61
-
62
- if (value === undefined) delete cursor[property]
63
- else cursor[property] = value
64
-
65
- this.#set(object)
66
- }
67
-
68
- unset (key) {
69
- this.set(key, undefined)
70
- }
71
-
72
- reset () {
73
- this.object = undefined
74
- }
75
-
76
- export () {
77
- return this.object === undefined ? undefined : encode(this.object)
78
- }
79
-
80
- async #retrieve () {
81
- const string = process.env[this.key]
82
- const object = string === undefined ? {} : decode(string)
83
-
84
- this.#set(object)
85
- }
86
-
87
- #set (object) {
88
- this.#validate(object)
89
- this.#merge(object)
90
-
91
- this.object = empty(object) ? undefined : object
92
- }
93
-
94
- #validate (object) {
95
- const error = this.#schema.match(object)
96
-
97
- if (error !== null) throw new TypeError(error.message)
98
- }
99
-
100
- #merge (object) {
101
- object = clone(object)
102
-
103
- const form = clone(this.#form)
104
- const value = overwrite(form, object)
105
-
106
- this.#schema.validate(value)
107
-
108
- this.#value = value
109
- }
110
- }
111
-
112
- const PREFIX = 'TOA_CONFIGURATION_'
113
-
114
- exports.Provider = Provider
@@ -1,38 +0,0 @@
1
- 'use strict'
2
-
3
- const { generate } = require('randomstring')
4
- const { Locator } = require('@toa.io/core')
5
- const { random } = require('@toa.io/generic')
6
-
7
- const instance = () => {
8
- const name = generate()
9
- const namespace = generate()
10
- const locator = new Locator(name, namespace)
11
-
12
- const manifest = {
13
- properties: {
14
- foo: {
15
- type: 'number',
16
- default: 1
17
- }
18
- }
19
- }
20
-
21
- return { locator, manifest }
22
- }
23
-
24
- /** @type {toa.norm.context.dependencies.Instance[]} */
25
- const instances = []
26
-
27
- for (let i = 0; i < random(5) + 5; i++) instances.push(instance())
28
-
29
- /** @type {Object} */
30
- const annotation = {}
31
-
32
- for (let i = 0; i < random(instances.length - 1) + 1; i++) {
33
- const instance = instances[i]
34
- annotation[instance.locator.id] = { foo: random() }
35
- }
36
-
37
- exports.instances = instances
38
- exports.annotation = annotation
@@ -1,43 +0,0 @@
1
- 'use strict'
2
-
3
- const clone = require('clone-deep')
4
- const { generate } = require('randomstring')
5
- const { sample } = require('@toa.io/generic')
6
-
7
- const fixtures = require('./annotations.fixtures')
8
- const { annotation } = require('../')
9
-
10
- let input
11
- /** @type {toa.norm.context.dependencies.Instance[]} */ let instances
12
-
13
- const call = () => annotation(input, instances)
14
-
15
- beforeEach(() => {
16
- input = clone(fixtures.annotation)
17
- instances = clone(fixtures.instances)
18
- })
19
-
20
- it('sample must be valid', () => {
21
- expect(call).not.toThrow()
22
- })
23
-
24
- it('should must be a function', () => {
25
- expect(annotation).toBeDefined()
26
- expect(annotation).toBeInstanceOf(Function)
27
- })
28
-
29
- it('should throw on non-existent component', () => {
30
- input[generate()] = {}
31
-
32
- expect(call).toThrow(/Configuration Schema/)
33
- })
34
-
35
- it('should throw if object doesn\'t match schema', () => {
36
- const keys = Object.keys(input)
37
- const key = sample(keys)
38
- const object = input[key]
39
-
40
- object.foo = generate()
41
-
42
- expect(call).toThrow(/foo must be number/)
43
- })
@@ -1,33 +0,0 @@
1
- 'use strict'
2
-
3
- const { generate } = require('randomstring')
4
-
5
- const schema = {
6
- properties: {
7
- foo: {
8
- type: 'string',
9
- default: generate()
10
- },
11
- bar: {
12
- properties: {
13
- baz: {
14
- type: 'number',
15
- default: 1
16
- }
17
- }
18
- },
19
- quu: {
20
- type: 'number'
21
- }
22
- }
23
- }
24
-
25
- const concise = {
26
- foo: schema.properties.foo.default,
27
- bar: {
28
- baz: schema.properties.bar.properties.baz.default
29
- }
30
- }
31
-
32
- exports.schema = schema
33
- exports.concise = concise