@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.
- package/CHANGELOG.md +42 -0
- package/components/configuration.values/manifest.toa.yaml +151 -0
- package/components/configuration.values/migrations/0002-indexes.yaml +7 -0
- package/components/configuration.values/operations/create.js +30 -0
- package/components/configuration.values/operations/fetch.js +13 -0
- package/components/configuration.values/operations/get.js +19 -0
- package/components/configuration.values/operations/lib/map.js +22 -0
- package/components/configuration.values/operations/lib/resolve.js +24 -0
- package/components/configuration.values/operations/list.js +11 -0
- package/components/configuration.values/source/create.ts +54 -0
- package/components/configuration.values/source/fetch.ts +26 -0
- package/components/configuration.values/source/get.ts +32 -0
- package/components/configuration.values/source/lib/map.test.ts +42 -0
- package/components/configuration.values/source/lib/map.ts +36 -0
- package/components/configuration.values/source/lib/resolve.ts +57 -0
- package/components/configuration.values/source/list.ts +21 -0
- package/components/configuration.values/tsconfig.json +10 -0
- package/components/configuration.values/tsconfig.tsbuildinfo +1 -0
- package/components/configuration.values/types/index.d.ts +3 -0
- package/components/configuration.values/types/toa.d.ts +68 -0
- package/cucumber.mjs +8 -0
- package/features/site/_app/immutable/asset.js +1 -0
- package/features/site/favicon.ico +0 -0
- package/features/site/index.html +10 -0
- package/features/steps/UI.ts +98 -0
- package/features/steps/tsconfig.json +9 -0
- package/features/ui.feature +93 -0
- package/notes/consistency.md +10 -9
- package/package.json +12 -10
- package/readme.md +165 -51
- package/source/Aspect.test.ts +114 -10
- package/source/Aspect.ts +76 -8
- package/source/Client.test.ts +125 -0
- package/source/Client.ts +251 -0
- package/source/Composition.ts +57 -0
- package/source/Factory.ts +29 -7
- package/source/Secret.test.ts +32 -0
- package/source/Secret.ts +32 -0
- package/source/UI.ts +213 -0
- package/source/configuration.test.ts +105 -43
- package/source/configuration.ts +36 -21
- package/source/epoch.test.ts +35 -0
- package/source/index.ts +5 -3
- package/transpiled/Aspect.d.ts +19 -4
- package/transpiled/Aspect.js +62 -9
- package/transpiled/Aspect.js.map +1 -1
- package/transpiled/Client.d.ts +61 -0
- package/transpiled/Client.js +175 -0
- package/transpiled/Client.js.map +1 -0
- package/transpiled/Composition.d.ts +14 -0
- package/transpiled/Composition.js +38 -0
- package/transpiled/Composition.js.map +1 -0
- package/transpiled/Factory.d.ts +9 -2
- package/transpiled/Factory.js +20 -9
- package/transpiled/Factory.js.map +1 -1
- package/transpiled/Secret.d.ts +15 -0
- package/transpiled/Secret.js +26 -0
- package/transpiled/Secret.js.map +1 -0
- package/transpiled/UI.d.ts +25 -0
- package/transpiled/UI.js +173 -0
- package/transpiled/UI.js.map +1 -0
- package/transpiled/configuration.d.ts +7 -2
- package/transpiled/configuration.js +32 -43
- package/transpiled/configuration.js.map +1 -1
- package/transpiled/index.d.ts +3 -3
- package/transpiled/index.js +4 -9
- package/transpiled/index.js.map +1 -1
- package/tsconfig.json +5 -1
- package/tsconfig.tsbuildinfo +1 -0
- package/ui/dist/_app/env.js +1 -0
- package/ui/dist/_app/immutable/assets/0.CNS8Cqhc.css +2 -0
- package/ui/dist/_app/immutable/assets/inter-cyrillic-ext-wght-normal.BOeWTOD4.woff2 +0 -0
- package/ui/dist/_app/immutable/assets/inter-cyrillic-wght-normal.DqGufNeO.woff2 +0 -0
- package/ui/dist/_app/immutable/assets/inter-greek-ext-wght-normal.DlzME5K_.woff2 +0 -0
- package/ui/dist/_app/immutable/assets/inter-greek-wght-normal.CkhJZR-_.woff2 +0 -0
- package/ui/dist/_app/immutable/assets/inter-latin-ext-wght-normal.DO1Apj_S.woff2 +0 -0
- package/ui/dist/_app/immutable/assets/inter-latin-wght-normal.Dx4kXJAl.woff2 +0 -0
- package/ui/dist/_app/immutable/assets/inter-vietnamese-wght-normal.CBcvBZtf.woff2 +0 -0
- package/ui/dist/_app/immutable/assets/ui.BghF56l_.css +1 -0
- package/ui/dist/_app/immutable/chunks/-d-dxcbq.js +8 -0
- package/ui/dist/_app/immutable/chunks/64H_hMtE.js +50 -0
- package/ui/dist/_app/immutable/chunks/BeorVtKF.js +6 -0
- package/ui/dist/_app/immutable/chunks/Bjy-W4x2.js +81 -0
- package/ui/dist/_app/immutable/chunks/C1Pl1PSE.js +1 -0
- package/ui/dist/_app/immutable/chunks/CcD_ISxM.js +3 -0
- package/ui/dist/_app/immutable/chunks/DzmF9ykx.js +1 -0
- package/ui/dist/_app/immutable/chunks/xihTtKlq.js +1 -0
- package/ui/dist/_app/immutable/entry/app.D5YrjA4l.js +2 -0
- package/ui/dist/_app/immutable/entry/start.CKkVSyXd.js +1 -0
- package/ui/dist/_app/immutable/nodes/0.Cm3Z3YNE.js +1 -0
- package/ui/dist/_app/immutable/nodes/1.e2aDXaPj.js +1 -0
- package/ui/dist/_app/immutable/nodes/2.Blh7Ex_K.js +1 -0
- package/ui/dist/_app/immutable/nodes/3.61NbGuZc.js +1 -0
- package/ui/dist/_app/immutable/nodes/4.B6OdRyVa.js +1 -0
- package/ui/dist/_app/version.json +1 -0
- package/ui/dist/apple-touch-icon.png +0 -0
- package/ui/dist/favicon-96x96.png +0 -0
- package/ui/dist/favicon.ico +0 -0
- package/ui/dist/index.html +57 -0
- package/ui/dist/robots.txt +3 -0
- package/schemas/annotation.cos.yaml +0 -1
- package/schemas/manifest.cos.yaml +0 -2
- package/source/deployment.test.ts +0 -21
- package/source/deployment.ts +0 -86
- package/source/manifest.test.ts +0 -15
- package/source/manifest.ts +0 -15
- package/source/schemas.ts +0 -9
- package/transpiled/Aspect.test.d.ts +0 -1
- package/transpiled/Aspect.test.js +0 -15
- package/transpiled/Aspect.test.js.map +0 -1
- package/transpiled/configuration.test.d.ts +0 -1
- package/transpiled/configuration.test.js +0 -64
- package/transpiled/configuration.test.js.map +0 -1
- package/transpiled/deployment.d.ts +0 -8
- package/transpiled/deployment.js +0 -88
- package/transpiled/deployment.js.map +0 -1
- package/transpiled/deployment.test.d.ts +0 -1
- package/transpiled/deployment.test.js +0 -18
- package/transpiled/deployment.test.js.map +0 -1
- package/transpiled/manifest.d.ts +0 -6
- package/transpiled/manifest.js +0 -35
- package/transpiled/manifest.js.map +0 -1
- package/transpiled/manifest.test.d.ts +0 -1
- package/transpiled/manifest.test.js +0 -14
- package/transpiled/manifest.test.js.map +0 -1
- package/transpiled/schemas.d.ts +0 -3
- package/transpiled/schemas.js +0 -33
- package/transpiled/schemas.js.map +0 -1
- package/transpiled/tsconfig.tsbuildinfo +0 -1
|
@@ -1,85 +1,147 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { describe, it, beforeEach, afterEach } from 'node:test'
|
|
2
|
+
import assert from 'node:assert/strict'
|
|
3
|
+
|
|
2
4
|
import { Locator } from '@toa.io/core'
|
|
3
5
|
import { generate } from 'randomstring'
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
+
import { fit, local, overridden } from './configuration.js'
|
|
7
|
+
import { Secret } from './Secret.js'
|
|
8
|
+
import { type Manifest } from '@toa.io/definitions/extensions.configuration'
|
|
6
9
|
|
|
7
10
|
let locator: Locator
|
|
8
11
|
let manifest: Manifest
|
|
9
12
|
|
|
10
13
|
beforeEach(() => {
|
|
11
14
|
locator = new Locator(generate(), generate())
|
|
12
|
-
manifest = {
|
|
15
|
+
manifest = {
|
|
16
|
+
schema: {
|
|
17
|
+
type: 'object',
|
|
18
|
+
properties: { foo: { type: 'string' } }
|
|
19
|
+
}
|
|
20
|
+
}
|
|
13
21
|
})
|
|
14
22
|
|
|
15
23
|
afterEach(() => {
|
|
16
|
-
for (const name of used)
|
|
17
|
-
delete process.env[name]
|
|
24
|
+
for (const name of used) delete process.env[name]
|
|
18
25
|
|
|
19
26
|
used = []
|
|
20
27
|
})
|
|
21
28
|
|
|
22
|
-
|
|
23
|
-
|
|
29
|
+
describe('overridden', () => {
|
|
30
|
+
it('should be false without the variable', async () => {
|
|
31
|
+
assert.deepStrictEqual(overridden(locator), false)
|
|
32
|
+
})
|
|
24
33
|
|
|
25
|
-
|
|
34
|
+
it('should be true with the variable', async () => {
|
|
35
|
+
set({})
|
|
26
36
|
|
|
27
|
-
|
|
37
|
+
assert.deepStrictEqual(overridden(locator), true)
|
|
38
|
+
})
|
|
39
|
+
})
|
|
28
40
|
|
|
29
|
-
|
|
41
|
+
describe('local', () => {
|
|
42
|
+
it('should read value', async () => {
|
|
43
|
+
const value: object = { foo: generate() }
|
|
30
44
|
|
|
31
|
-
|
|
32
|
-
})
|
|
45
|
+
set(value)
|
|
33
46
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
})
|
|
47
|
+
assert.deepStrictEqual(local(locator, manifest), value)
|
|
48
|
+
})
|
|
37
49
|
|
|
38
|
-
it('should
|
|
39
|
-
|
|
50
|
+
it('should return empty object if no value set', async () => {
|
|
51
|
+
assert.deepStrictEqual(local(locator, manifest), {})
|
|
52
|
+
})
|
|
40
53
|
|
|
41
|
-
|
|
42
|
-
|
|
54
|
+
it('should substitute secrets', async () => {
|
|
55
|
+
set({ foo: '$BAR' })
|
|
56
|
+
set('bar', '_BAR')
|
|
43
57
|
|
|
44
|
-
|
|
58
|
+
const { foo } = local(locator, manifest)
|
|
45
59
|
|
|
46
|
-
|
|
47
|
-
|
|
60
|
+
assert.ok(foo instanceof Secret)
|
|
61
|
+
assert.deepStrictEqual((foo as Secret).unwrap(), 'bar')
|
|
62
|
+
assert.deepStrictEqual(String(foo), '<REDACTED>')
|
|
63
|
+
})
|
|
48
64
|
|
|
49
|
-
it('should
|
|
50
|
-
|
|
51
|
-
manifest.defaults = { foo: 'bar', bar: [1] }
|
|
65
|
+
it('should substitute secrets in defaults', async () => {
|
|
66
|
+
manifest.defaults = { foo: '$BAR' }
|
|
52
67
|
|
|
53
|
-
|
|
68
|
+
set('bar', '_BAR')
|
|
54
69
|
|
|
55
|
-
|
|
70
|
+
assert.deepStrictEqual((local(locator, manifest).foo as Secret).unwrap(), 'bar')
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
it('should use defaults', async () => {
|
|
74
|
+
manifest.schema = {
|
|
75
|
+
type: 'object',
|
|
76
|
+
properties: {
|
|
77
|
+
foo: { type: 'string' },
|
|
78
|
+
bar: { type: 'array', items: { type: 'number' } },
|
|
79
|
+
baz: { type: 'string' }
|
|
80
|
+
},
|
|
81
|
+
required: ['foo', 'bar']
|
|
82
|
+
}
|
|
83
|
+
manifest.defaults = { foo: 'bar', bar: [1] }
|
|
84
|
+
|
|
85
|
+
set({ bar: [2], baz: 'foo' })
|
|
86
|
+
|
|
87
|
+
assert.deepStrictEqual(local(locator, manifest), { foo: 'bar', bar: [2], baz: 'foo' })
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
it('should validate', async () => {
|
|
91
|
+
manifest.schema = {
|
|
92
|
+
type: 'object',
|
|
93
|
+
properties: {
|
|
94
|
+
foo: { type: 'string' },
|
|
95
|
+
bar: { type: 'number' }
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
set({ bar: 5 })
|
|
56
100
|
|
|
57
|
-
|
|
101
|
+
assert.deepStrictEqual(local(locator, manifest), { bar: 5 })
|
|
58
102
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
103
|
+
set({ bar: 'five' })
|
|
104
|
+
|
|
105
|
+
assert.throws(
|
|
106
|
+
() => local(locator, manifest),
|
|
107
|
+
(error: Error) => /must be number/.test(error.message)
|
|
108
|
+
)
|
|
63
109
|
})
|
|
64
110
|
})
|
|
65
111
|
|
|
66
|
-
|
|
67
|
-
|
|
112
|
+
describe('fit', () => {
|
|
113
|
+
it('should substitute secrets and apply the schema', async () => {
|
|
114
|
+
manifest.schema = {
|
|
115
|
+
type: 'object',
|
|
116
|
+
properties: {
|
|
117
|
+
foo: { type: 'string' },
|
|
118
|
+
bar: { type: 'number' }
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
set('secret', '_FOO')
|
|
68
123
|
|
|
69
|
-
|
|
124
|
+
const raw = { foo: '$FOO', bar: 1 }
|
|
125
|
+
const values = fit(raw, manifest)
|
|
70
126
|
|
|
71
|
-
|
|
127
|
+
assert.deepStrictEqual((values.foo as Secret).unwrap(), 'secret')
|
|
128
|
+
assert.deepStrictEqual(values.bar, 1)
|
|
129
|
+
assert.deepStrictEqual(raw, { foo: '$FOO', bar: 1 }) // untouched
|
|
130
|
+
})
|
|
72
131
|
|
|
73
|
-
|
|
132
|
+
it('should not apply the manifest defaults', async () => {
|
|
133
|
+
manifest.defaults = { foo: 'hello' }
|
|
134
|
+
|
|
135
|
+
assert.deepStrictEqual(fit({}, manifest), {})
|
|
136
|
+
})
|
|
74
137
|
|
|
75
|
-
|
|
76
|
-
foo:
|
|
77
|
-
bar: 5
|
|
138
|
+
it('should throw on a value not fitting the schema', async () => {
|
|
139
|
+
assert.throws(() => fit({ foo: { nested: true } }, manifest))
|
|
78
140
|
})
|
|
79
141
|
})
|
|
80
142
|
|
|
81
|
-
function set
|
|
82
|
-
const string = typeof value === 'string' ? value :
|
|
143
|
+
function set(value: object | string, key = locator.uppercase): void {
|
|
144
|
+
const string = typeof value === 'string' ? value : JSON.stringify(value)
|
|
83
145
|
const name = 'TOA_CONFIGURATION_' + key
|
|
84
146
|
|
|
85
147
|
process.env[name] = string
|
package/source/configuration.ts
CHANGED
|
@@ -1,39 +1,54 @@
|
|
|
1
1
|
import { type Locator } from '@toa.io/core'
|
|
2
|
-
import {
|
|
2
|
+
import { add, environment } from '@toa.io/generic'
|
|
3
3
|
import * as schemas from '@toa.io/schemas'
|
|
4
|
-
import { PREFIX, SECRET_RX } from '
|
|
5
|
-
import {
|
|
4
|
+
import { PREFIX, SECRET_RX } from '@toa.io/definitions/extensions.configuration'
|
|
5
|
+
import { Secret } from './Secret.js'
|
|
6
6
|
import type { Schema } from '@toa.io/schemas'
|
|
7
|
+
import type { Manifest } from '@toa.io/definitions/extensions.configuration'
|
|
7
8
|
|
|
8
|
-
|
|
9
|
-
|
|
9
|
+
/** The variable is set, so the values service is not consulted. */
|
|
10
|
+
export function overridden(locator: Locator): boolean {
|
|
11
|
+
return environment.get(PREFIX + locator.uppercase) !== undefined
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/** The variable, the manifest defaults, then the schema. */
|
|
15
|
+
export function local(locator: Locator, manifest: Manifest): Node {
|
|
16
|
+
const values = read(locator.uppercase)
|
|
17
|
+
|
|
18
|
+
if (manifest.defaults !== undefined) add(values, manifest.defaults)
|
|
19
|
+
|
|
20
|
+
return fit(values, manifest)
|
|
21
|
+
}
|
|
10
22
|
|
|
23
|
+
/** A copy of the value with the schema applied and the secrets substituted. */
|
|
24
|
+
export function fit(raw: object, manifest: Manifest): Node {
|
|
25
|
+
// a copy of the caller's, and one of this realm: what came over the wire is JSON anyway
|
|
26
|
+
const values = JSON.parse(JSON.stringify(raw)) as Node
|
|
27
|
+
|
|
28
|
+
// the schema sees the references, which are the strings it declares
|
|
29
|
+
validate(values, manifest)
|
|
11
30
|
substituteSecrets(values)
|
|
12
31
|
|
|
13
|
-
|
|
14
|
-
|
|
32
|
+
return values
|
|
33
|
+
}
|
|
15
34
|
|
|
35
|
+
function validate(values: Node, manifest: Manifest): void {
|
|
16
36
|
const schema: Schema<any> = schemas.schema(manifest.schema)
|
|
17
37
|
|
|
18
38
|
schema.validate(values)
|
|
19
|
-
|
|
20
|
-
return values
|
|
21
39
|
}
|
|
22
40
|
|
|
23
|
-
function
|
|
41
|
+
function read(suffix: string): Node {
|
|
24
42
|
const variable = PREFIX + suffix
|
|
25
|
-
const string =
|
|
43
|
+
const string = environment.get(variable)
|
|
26
44
|
|
|
27
|
-
if (string === undefined)
|
|
28
|
-
|
|
29
|
-
else
|
|
30
|
-
return decode(string)
|
|
45
|
+
if (string === undefined) return {}
|
|
46
|
+
else return JSON.parse(string)
|
|
31
47
|
}
|
|
32
48
|
|
|
33
|
-
function substituteSecrets
|
|
49
|
+
function substituteSecrets(configuration: Node): void {
|
|
34
50
|
for (const [key, value] of Object.entries(configuration)) {
|
|
35
|
-
if (typeof value === 'object' && value !== null)
|
|
36
|
-
substituteSecrets(value as Node)
|
|
51
|
+
if (typeof value === 'object' && value !== null) substituteSecrets(value as Node)
|
|
37
52
|
|
|
38
53
|
if (typeof value !== 'string') continue
|
|
39
54
|
|
|
@@ -43,13 +58,13 @@ function substituteSecrets (configuration: Node): void {
|
|
|
43
58
|
|
|
44
59
|
const name = match.groups?.variable
|
|
45
60
|
|
|
46
|
-
configuration[key] = getSecret(name!)
|
|
61
|
+
configuration[key] = new Secret(getSecret(name!))
|
|
47
62
|
}
|
|
48
63
|
}
|
|
49
64
|
|
|
50
|
-
function getSecret
|
|
65
|
+
function getSecret(name: string): string {
|
|
51
66
|
const variable = PREFIX + '_' + name
|
|
52
|
-
const value =
|
|
67
|
+
const value = environment.get(variable)
|
|
53
68
|
|
|
54
69
|
if (value === undefined) throw new Error(`${variable} is not set.`)
|
|
55
70
|
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { it } from 'node:test'
|
|
2
|
+
import assert from 'node:assert/strict'
|
|
3
|
+
|
|
4
|
+
import { epoch } from '@toa.io/definitions/extensions.configuration'
|
|
5
|
+
|
|
6
|
+
it('should be a sha256 hex', () => {
|
|
7
|
+
assert.match(epoch({ type: 'object' }), /^[a-f0-9]{64}$/)
|
|
8
|
+
})
|
|
9
|
+
|
|
10
|
+
it('should not depend on key order', () => {
|
|
11
|
+
const a = {
|
|
12
|
+
type: 'object',
|
|
13
|
+
properties: { foo: { type: 'string', default: 'x' }, bar: { type: 'number' } }
|
|
14
|
+
}
|
|
15
|
+
const b = {
|
|
16
|
+
properties: { bar: { type: 'number' }, foo: { default: 'x', type: 'string' } },
|
|
17
|
+
type: 'object'
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
assert.deepStrictEqual(epoch(a), epoch(b))
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
it('should depend on values', () => {
|
|
24
|
+
const a = { type: 'object', properties: { foo: { type: 'string' } } }
|
|
25
|
+
const b = { type: 'object', properties: { foo: { type: 'number' } } }
|
|
26
|
+
|
|
27
|
+
assert.notDeepStrictEqual(epoch(a), epoch(b))
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
it('should keep array order', () => {
|
|
31
|
+
const a = { enum: [1, 2] }
|
|
32
|
+
const b = { enum: [2, 1] }
|
|
33
|
+
|
|
34
|
+
assert.notDeepStrictEqual(epoch(a), epoch(b))
|
|
35
|
+
})
|
package/source/index.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
-
export {
|
|
2
|
-
export {
|
|
3
|
-
|
|
1
|
+
export { components } from './Composition.js'
|
|
2
|
+
export { Factory } from './Factory.js'
|
|
3
|
+
|
|
4
|
+
// what a value declared `format: secret` is on a component's context
|
|
5
|
+
export { Secret } from './Secret.js'
|
package/transpiled/Aspect.d.ts
CHANGED
|
@@ -1,7 +1,22 @@
|
|
|
1
|
-
import { Connector, type
|
|
1
|
+
import { Connector, type Locator } from '@toa.io/core';
|
|
2
|
+
import type { extensions } from '@toa.io/core/types';
|
|
3
|
+
import type { Client } from './Client.js';
|
|
4
|
+
import type { Manifest } from '@toa.io/definitions/extensions.configuration';
|
|
2
5
|
export declare class Aspect extends Connector implements extensions.Aspect {
|
|
3
6
|
readonly name = "configuration";
|
|
4
|
-
private readonly
|
|
5
|
-
|
|
6
|
-
|
|
7
|
+
private readonly locator;
|
|
8
|
+
private readonly manifest;
|
|
9
|
+
private readonly client;
|
|
10
|
+
private readonly epoch;
|
|
11
|
+
private value;
|
|
12
|
+
private created;
|
|
13
|
+
/**
|
|
14
|
+
* Without a client the value is local: the variable, the defaults and the schema.
|
|
15
|
+
* With one, the value is what the service holds, and it follows the service.
|
|
16
|
+
*/
|
|
17
|
+
constructor(locator: Locator, manifest: Manifest, client: Client | null);
|
|
18
|
+
invoke(path?: string[]): any;
|
|
19
|
+
protected open(): Promise<void>;
|
|
20
|
+
protected close(): Promise<void>;
|
|
21
|
+
private readonly listener;
|
|
7
22
|
}
|
package/transpiled/Aspect.js
CHANGED
|
@@ -1,13 +1,27 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
class Aspect extends
|
|
1
|
+
import { console } from 'openspan';
|
|
2
|
+
import { Connector } from '@toa.io/core';
|
|
3
|
+
import { fit, local } from './configuration.js';
|
|
4
|
+
import { epoch } from '@toa.io/definitions/extensions.configuration';
|
|
5
|
+
export class Aspect extends Connector {
|
|
6
6
|
name = 'configuration';
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
locator;
|
|
8
|
+
manifest;
|
|
9
|
+
client;
|
|
10
|
+
epoch;
|
|
11
|
+
value = {};
|
|
12
|
+
created = 0;
|
|
13
|
+
/**
|
|
14
|
+
* Without a client the value is local: the variable, the defaults and the schema.
|
|
15
|
+
* With one, the value is what the service holds, and it follows the service.
|
|
16
|
+
*/
|
|
17
|
+
constructor(locator, manifest, client) {
|
|
9
18
|
super();
|
|
10
|
-
this.
|
|
19
|
+
this.locator = locator;
|
|
20
|
+
this.manifest = manifest;
|
|
21
|
+
this.client = client;
|
|
22
|
+
this.epoch = epoch(manifest.schema);
|
|
23
|
+
if (client !== null)
|
|
24
|
+
this.depends(client);
|
|
11
25
|
}
|
|
12
26
|
invoke(path) {
|
|
13
27
|
let cursor = this.value;
|
|
@@ -16,6 +30,45 @@ class Aspect extends core_1.Connector {
|
|
|
16
30
|
cursor = cursor[segment];
|
|
17
31
|
return cursor;
|
|
18
32
|
}
|
|
33
|
+
async open() {
|
|
34
|
+
if (this.client === null) {
|
|
35
|
+
this.value = local(this.locator, this.manifest);
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
const { configuration, created } = await this.client.fetch(this.locator.id, this.epoch);
|
|
39
|
+
this.value = fit(configuration, this.manifest);
|
|
40
|
+
this.created = created;
|
|
41
|
+
this.client.subscribe(this.locator.id, this.epoch, this.listener);
|
|
42
|
+
console.info('Configuration resolved', {
|
|
43
|
+
component: this.locator.id,
|
|
44
|
+
epoch: this.epoch,
|
|
45
|
+
created
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
async close() {
|
|
49
|
+
this.client?.unsubscribe(this.locator.id, this.epoch, this.listener);
|
|
50
|
+
}
|
|
51
|
+
listener = ({ configuration, created }) => {
|
|
52
|
+
// deliveries may repeat or cross: only what is newer than the held value replaces it
|
|
53
|
+
if (created <= this.created)
|
|
54
|
+
return;
|
|
55
|
+
try {
|
|
56
|
+
this.value = fit(configuration, this.manifest);
|
|
57
|
+
this.created = created;
|
|
58
|
+
console.info('Configuration updated', {
|
|
59
|
+
component: this.locator.id,
|
|
60
|
+
epoch: this.epoch,
|
|
61
|
+
created
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
catch (error) {
|
|
65
|
+
// the service validated it against the schema of its epoch, so the two schemas differ
|
|
66
|
+
console.error('Configuration value does not match the schema', {
|
|
67
|
+
component: this.locator.id,
|
|
68
|
+
epoch: this.epoch,
|
|
69
|
+
error
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
};
|
|
19
73
|
}
|
|
20
|
-
exports.Aspect = Aspect;
|
|
21
74
|
//# sourceMappingURL=Aspect.js.map
|
package/transpiled/Aspect.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Aspect.js","sourceRoot":"","sources":["../source/Aspect.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"Aspect.js","sourceRoot":"","sources":["../source/Aspect.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { Connector } from '@toa.io/core';
|
|
2
|
+
import type { Host } from './Factory.js';
|
|
3
|
+
/**
|
|
4
|
+
* One per process: one remote to the values service and one subscription to its events,
|
|
5
|
+
* shared by every Aspect. What the Aspects ask for is collected and sent as one call;
|
|
6
|
+
* what the service creates afterwards is handed to whoever subscribed.
|
|
7
|
+
*/
|
|
8
|
+
export declare class Client extends Connector {
|
|
9
|
+
/** Disconnected once, a connector keeps what it depended on, so a gone client is not reused. */
|
|
10
|
+
disposed: boolean;
|
|
11
|
+
private readonly host;
|
|
12
|
+
private readonly options;
|
|
13
|
+
private readonly pending;
|
|
14
|
+
private readonly listeners;
|
|
15
|
+
private remote;
|
|
16
|
+
private timer;
|
|
17
|
+
private flushing;
|
|
18
|
+
private fresh;
|
|
19
|
+
private round;
|
|
20
|
+
constructor(host: Host, options?: Partial<Options>);
|
|
21
|
+
/** The configuration of a component for an epoch, once the service has one. */
|
|
22
|
+
fetch(component: string, epoch: string): Promise<Value>;
|
|
23
|
+
subscribe(component: string, epoch: string, listener: Listener): void;
|
|
24
|
+
unsubscribe(component: string, epoch: string, listener: Listener): void;
|
|
25
|
+
protected open(): Promise<void>;
|
|
26
|
+
protected close(): Promise<void>;
|
|
27
|
+
protected dispose(): Promise<void>;
|
|
28
|
+
private schedule;
|
|
29
|
+
private flush;
|
|
30
|
+
/** Those the service has served are told; the rest stay for the next round. */
|
|
31
|
+
private settle;
|
|
32
|
+
private report;
|
|
33
|
+
private backoff;
|
|
34
|
+
/** A created object goes to the subscribers of its component and epoch, as it is. */
|
|
35
|
+
private deliver;
|
|
36
|
+
}
|
|
37
|
+
export interface Options {
|
|
38
|
+
base: number;
|
|
39
|
+
max: number;
|
|
40
|
+
/** Every n-th round of waiting is reported. */
|
|
41
|
+
warn: number;
|
|
42
|
+
}
|
|
43
|
+
/** A configuration and when it was created; `0` for the deployed defaults. */
|
|
44
|
+
export interface Value {
|
|
45
|
+
configuration: object;
|
|
46
|
+
created: number;
|
|
47
|
+
}
|
|
48
|
+
export interface Fetched {
|
|
49
|
+
component: string;
|
|
50
|
+
epoch: string;
|
|
51
|
+
configuration: object | null;
|
|
52
|
+
created: number;
|
|
53
|
+
}
|
|
54
|
+
/** The `configuration.values.created` payload: the object as stored. */
|
|
55
|
+
export interface Created {
|
|
56
|
+
component: string;
|
|
57
|
+
epoch: string;
|
|
58
|
+
configuration: object;
|
|
59
|
+
CREATED: number;
|
|
60
|
+
}
|
|
61
|
+
export type Listener = (value: Value) => void;
|