@toa.io/extensions.configuration 0.20.0-dev.34 → 0.20.0-dev.36

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 (51) hide show
  1. package/package.json +16 -9
  2. package/readme.md +19 -27
  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 +52 -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/tsconfig.json +9 -0
  17. package/docs/discussion.md +0 -109
  18. package/source/.deployment/index.js +0 -7
  19. package/source/.deployment/secrets.js +0 -35
  20. package/source/.deployment/variables.js +0 -23
  21. package/source/.manifest/.normalize/verbose.js +0 -51
  22. package/source/.manifest/index.js +0 -7
  23. package/source/.manifest/normalize.js +0 -16
  24. package/source/.manifest/schema.yaml +0 -9
  25. package/source/.manifest/validate.js +0 -13
  26. package/source/.provider/env.js +0 -19
  27. package/source/.provider/form.js +0 -20
  28. package/source/.provider/index.js +0 -7
  29. package/source/annotation.fixtures.js +0 -39
  30. package/source/annotation.js +0 -36
  31. package/source/annotation.test.js +0 -43
  32. package/source/aspect.fixtures.js +0 -32
  33. package/source/aspect.js +0 -36
  34. package/source/aspect.test.js +0 -76
  35. package/source/configuration.js +0 -19
  36. package/source/deployment.fixtures.js +0 -38
  37. package/source/deployment.js +0 -20
  38. package/source/deployment.test.js +0 -70
  39. package/source/factory.js +0 -39
  40. package/source/factory.test.js +0 -22
  41. package/source/index.js +0 -13
  42. package/source/manifest.js +0 -13
  43. package/source/manifest.test.js +0 -123
  44. package/source/provider.js +0 -119
  45. package/source/provider.test.js +0 -130
  46. package/source/secrets.js +0 -28
  47. package/source/secrets.test.js +0 -47
  48. package/types/aspect.d.ts +0 -11
  49. package/types/factory.d.ts +0 -12
  50. package/types/provider.d.ts +0 -22
  51. /package/{docs → notes}/consistency.md +0 -0
@@ -1,123 +0,0 @@
1
- 'use strict'
2
-
3
- const { generate } = require('randomstring')
4
- const { random } = require('@toa.io/generic')
5
-
6
- const { manifest } = require('../')
7
-
8
- it('should export', () => {
9
- expect(manifest).toBeInstanceOf(Function)
10
- })
11
-
12
- describe('validation', () => {
13
- it('should throw if not an object', () => {
14
- const call = () => manifest(generate())
15
- expect(call).toThrow(/must be object/)
16
- })
17
-
18
- it('should throw if not a valid schema', () => {
19
- const object = { type: generate() }
20
- const call = () => manifest(object)
21
-
22
- expect(call).toThrow(/one of the allowed values/)
23
- })
24
-
25
- it('should throw if schema is not an object type', () => {
26
- const schema = { type: 'number' }
27
- const call = () => manifest(schema)
28
-
29
- expect(call).toThrow(/equal to constant 'object'/)
30
- })
31
- })
32
-
33
- describe('normalization', () => {
34
- it('should expand concise', () => {
35
- const concise = {
36
- foo: generate(),
37
- bar: {
38
- baz: random()
39
- }
40
- }
41
-
42
- const declaration = manifest(concise)
43
-
44
- expect(declaration).toMatchObject({
45
- type: 'object',
46
- properties: {
47
- foo: {
48
- type: 'string',
49
- default: concise.foo
50
- },
51
- bar: {
52
- type: 'object',
53
- properties: {
54
- baz: {
55
- type: 'number',
56
- default: concise.bar.baz
57
- }
58
- }
59
- }
60
- }
61
- })
62
- })
63
-
64
- it('should expand partially concise', () => {
65
- const concise = {
66
- foo: generate(),
67
- bar: {
68
- baz: random()
69
- },
70
- qux: {
71
- type: 'string',
72
- default: null
73
- }
74
- }
75
-
76
- const declaration = manifest(concise)
77
-
78
- expect(declaration).toMatchObject({
79
- type: 'object',
80
- properties: {
81
- foo: {
82
- type: 'string',
83
- default: concise.foo
84
- },
85
- bar: {
86
- type: 'object',
87
- properties: {
88
- baz: {
89
- type: 'number',
90
- default: concise.bar.baz
91
- }
92
- }
93
- },
94
- qux: {
95
- type: 'string',
96
- default: null
97
- }
98
- }
99
- })
100
- })
101
-
102
- it('should expand arrays', () => {
103
- const concise = { foo: [1, 2, 3] }
104
-
105
- const declaration = manifest(concise)
106
-
107
- expect(declaration).toMatchObject({
108
- type: 'object',
109
- properties: {
110
- foo: {
111
- type: 'array',
112
- default: [1, 2, 3]
113
- }
114
- }
115
- })
116
- })
117
-
118
- it('should throw on empty array', () => {
119
- const concise = { foo: [] }
120
-
121
- expect(() => manifest(concise)).toThrow(/array items type because it's empty/)
122
- })
123
- })
@@ -1,119 +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
-
8
- const { secrets } = require('./secrets')
9
- const { env, form } = require('./.provider')
10
-
11
- /**
12
- * @implements {toa.extensions.configuration.Provider}
13
- */
14
- class Provider extends Connector {
15
- /** @type {toa.schema.Schema} */
16
- #schema
17
-
18
- /** @type {Object} */
19
- #form
20
- /** @type {Object} */
21
- #value
22
-
23
- source
24
- object
25
- key
26
-
27
- /**
28
- * @param {toa.core.Locator} locator
29
- * @param {toa.schema.Schema} schema
30
- */
31
- constructor (locator, schema) {
32
- super()
33
-
34
- this.source = this.#source.bind(this)
35
-
36
- this.key = PREFIX + locator.uppercase
37
- this.#schema = schema
38
-
39
- // form is required to enable nested defaults
40
- this.#form = form(schema.schema)
41
- }
42
-
43
- async open () {
44
- this.#retrieve()
45
- }
46
-
47
- set (key, value) {
48
- const object = this.object === undefined ? {} : clone(this.object)
49
- const properties = key.split('.')
50
- const property = properties.pop()
51
-
52
- let cursor = object
53
-
54
- for (const name of properties) {
55
- if (cursor[name] === undefined) cursor[name] = {}
56
-
57
- cursor = cursor[name]
58
- }
59
-
60
- if (value === undefined) delete cursor[property]
61
- else cursor[property] = value
62
-
63
- this.#set(object)
64
- }
65
-
66
- unset (key) {
67
- this.set(key, undefined)
68
- }
69
-
70
- reset () {
71
- this.object = undefined
72
- }
73
-
74
- export () {
75
- return this.object === undefined ? undefined : encode(this.object)
76
- }
77
-
78
- #source () {
79
- return this.#value
80
- }
81
-
82
- #retrieve () {
83
- const string = process.env[this.key]
84
- const object = string === undefined ? {} : decode(string)
85
-
86
- this.#set(object)
87
- }
88
-
89
- #set (object) {
90
- object = this.#reveal(object)
91
- object = env(object)
92
-
93
- this.#merge(object)
94
-
95
- this.object = empty(object) ? undefined : object
96
- }
97
-
98
- #merge (object) {
99
- object = clone(object)
100
-
101
- const form = clone(this.#form)
102
- const value = overwrite(form, object)
103
-
104
- this.#schema.validate(value)
105
- this.#value = value
106
- }
107
-
108
- #reveal (object) {
109
- return secrets(object, (variable) => {
110
- if (!(variable in process.env)) throw new Error(`Configuration secret value ${variable} is not set`)
111
-
112
- return process.env[variable]
113
- })
114
- }
115
- }
116
-
117
- const PREFIX = 'TOA_CONFIGURATION_'
118
-
119
- exports.Provider = Provider
@@ -1,130 +0,0 @@
1
- 'use strict'
2
-
3
- /* eslint-disable no-template-curly-in-string */
4
-
5
- const { generate } = require('randomstring')
6
- const { encode } = require('@toa.io/generic')
7
-
8
- const { Provider } = require('./provider')
9
-
10
- it('should be', async () => {
11
- expect(Provider).toBeInstanceOf(Function)
12
- })
13
-
14
- const locator = /** @type {toa.core.Locator} */ { uppercase: generate().toUpperCase() }
15
- const schema = /** @type {toa.schema.Schema} */ { validate: () => undefined }
16
-
17
- /** @type {Provider} */
18
- let provider
19
-
20
- beforeEach(() => {
21
- cleanEnv()
22
- provider = new Provider(locator, schema)
23
- })
24
-
25
- it('should replace secret values', async () => {
26
- const configuration = { foo: '$FOO_SECRET' }
27
- const secrets = { FOO_SECRET: generate() }
28
-
29
- setEnv(configuration, secrets)
30
-
31
- await provider.open()
32
- const value = provider.source()
33
-
34
- expect(value).toStrictEqual({ foo: secrets.FOO_SECRET })
35
- })
36
-
37
- it('should throw if secret value is not set', async () => {
38
- const configuration = { foo: '$FOO_SECRET' }
39
-
40
- setEnv(configuration)
41
-
42
- await expect(provider.open()).rejects.toThrow('FOO_SECRET is not set')
43
- })
44
-
45
- it('should replace nested secrets', async () => {
46
- const configuration = { foo: { bar: '$BAR' } }
47
- const secrets = { BAR: generate() }
48
-
49
- setEnv(configuration, secrets)
50
-
51
- await provider.open()
52
- const value = provider.source()
53
-
54
- expect(value).toStrictEqual({ foo: { bar: secrets.BAR } })
55
- })
56
-
57
- it('should replace placeholders', async () => {
58
- const name = 'FOO_VALUE'
59
- const configuration = { foo: { bar: 'foo_${' + name + '}' } }
60
- const value = generate()
61
-
62
- setEnv(configuration)
63
- setVal(name, value)
64
-
65
- await provider.open()
66
- const source = provider.source()
67
-
68
- expect(source).toStrictEqual({ foo: { bar: 'foo_' + value } })
69
- })
70
-
71
- it('should replace multiple placeholders', async () => {
72
- const configuration = { foo: '${FOO} ${BAR}' }
73
-
74
- setEnv(configuration)
75
- setVal('FOO', 'hello')
76
- setVal('BAR', 'world')
77
-
78
- await provider.open()
79
- const source = provider.source()
80
-
81
- expect(source).toStrictEqual({ foo: 'hello world' })
82
- })
83
-
84
- it('should not replace if variable not set', async () => {
85
- const configuration = { foo: '${FOO}' }
86
-
87
- setEnv(configuration)
88
-
89
- await provider.open()
90
- const value = provider.source()
91
-
92
- expect(value).toStrictEqual(configuration)
93
- })
94
-
95
- const usedVariables = []
96
-
97
- /**
98
- * @param {object} configuration
99
- * @param {Record<string, string>} [secrets]
100
- */
101
- function setEnv (configuration, secrets) {
102
- const variable = PREFIX + locator.uppercase
103
- const encoded = encode(configuration)
104
-
105
- setVal(variable, encoded)
106
-
107
- if (secrets !== undefined) {
108
- for (const [key, value] of Object.entries(secrets)) {
109
- const variable = PREFIX + '_' + key
110
-
111
- process.env[variable] = value
112
- usedVariables.push(variable)
113
- }
114
- }
115
- }
116
-
117
- function setVal (variable, value) {
118
- process.env[variable] = value
119
- usedVariables.push(variable)
120
- }
121
-
122
- function cleanEnv () {
123
- for (const variable of usedVariables) {
124
- delete process.env[variable]
125
- }
126
-
127
- usedVariables.length = 0
128
- }
129
-
130
- const PREFIX = 'TOA_CONFIGURATION_'
package/source/secrets.js DELETED
@@ -1,28 +0,0 @@
1
- 'use strict'
2
-
3
- const { map } = require('@toa.io/generic')
4
-
5
- /**
6
- * @param {object} configuration
7
- * @param {(variable: string, name?: string) => void} callback
8
- * @returns {object}
9
- */
10
- function secrets (configuration, callback) {
11
- return map(configuration, (value) => {
12
- if (typeof value !== 'string') return
13
-
14
- const match = value.match(SECRET_RX)
15
-
16
- if (match === null) return
17
-
18
- const name = match.groups.variable
19
- const variable = PREFIX + name
20
-
21
- return callback(variable, name)
22
- })
23
- }
24
-
25
- const PREFIX = 'TOA_CONFIGURATION__'
26
- const SECRET_RX = /^\$(?<variable>[A-Z0-9_]{1,32})$/
27
-
28
- exports.secrets = secrets
@@ -1,47 +0,0 @@
1
- 'use strict'
2
-
3
- const { secrets } = require('./secrets')
4
-
5
- it('should be', async () => {
6
- expect(secrets).toBeInstanceOf(Function)
7
- })
8
-
9
- it('should find secrets', async () => {
10
- const configuration = {
11
- foo: {
12
- bar: '$BAR_VALUE'
13
- },
14
- baz: '$BAZ_VALUE'
15
- }
16
-
17
- const variables = new Set()
18
- const names = new Set()
19
-
20
- secrets(configuration, (variable, name) => {
21
- variables.add(variable)
22
- names.add(name)
23
- })
24
-
25
- expect(variables.has('TOA_CONFIGURATION__BAR_VALUE')).toStrictEqual(true)
26
- expect(variables.has('TOA_CONFIGURATION__BAZ_VALUE')).toStrictEqual(true)
27
-
28
- expect(names.has('BAR_VALUE')).toStrictEqual(true)
29
- expect(names.has('BAZ_VALUE')).toStrictEqual(true)
30
- })
31
-
32
- it('should replace values', async () => {
33
- const configuration = { foo: '$FOO' }
34
-
35
- const output = secrets(configuration, (variable) => 'hello')
36
-
37
- expect(output).toStrictEqual({ foo: 'hello' })
38
- })
39
-
40
- it('should allow numbers in secret names', async () => {
41
- const configuration = { foo: '$HOST_0' }
42
- const found = new Set()
43
-
44
- secrets(configuration, (variable) => found.add(variable))
45
-
46
- expect(found.has('TOA_CONFIGURATION__HOST_0')).toStrictEqual(true)
47
- })
package/types/aspect.d.ts DELETED
@@ -1,11 +0,0 @@
1
- // noinspection ES6UnusedImports
2
-
3
- import * as core from '@toa.io/core/types/extensions'
4
-
5
- declare namespace toa.extensions.configuration {
6
-
7
- interface Aspect extends core.Aspect {
8
- invoke(path?: string[]): any
9
- }
10
-
11
- }
@@ -1,12 +0,0 @@
1
- import { Component } from '@toa.io/norm/types'
2
-
3
- import * as _extensions from '@toa.io/core/types/extensions'
4
- import * as _provider from './provider'
5
-
6
- declare namespace toa.extensions.configuration {
7
-
8
- interface Factory extends _extensions.Factory {
9
- provider(component: Component): _provider.Provider
10
- }
11
-
12
- }
@@ -1,22 +0,0 @@
1
- import { Source } from '@toa.io/core/types/reflection'
2
- import { Connector } from '@toa.io/core/types'
3
-
4
- declare namespace toa.extensions.configuration {
5
-
6
- interface Provider extends Connector {
7
- source: Source
8
- object: Object
9
- key: string
10
-
11
- set(key: string, value: any): void
12
-
13
- unset(key: string): void
14
-
15
- reset(): void
16
-
17
- export(): string
18
- }
19
-
20
- }
21
-
22
- export type Provider = toa.extensions.configuration.Provider
File without changes