@toa.io/extensions.configuration 1.0.0-alpha.29 → 1.0.0-alpha.291
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 +35 -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 +11 -10
- package/readme.md +165 -51
- package/schemas/annotation.cos.yaml +29 -1
- package/schemas/manifest.cos.yaml +9 -2
- 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/const.ts +30 -0
- package/source/context.ts +13 -0
- package/source/deployment.test.ts +103 -12
- package/source/deployment.ts +105 -31
- package/source/epoch.test.ts +35 -0
- package/source/epoch.ts +22 -0
- package/source/index.ts +10 -3
- package/source/manifest.test.ts +16 -7
- package/source/manifest.ts +3 -5
- package/source/schemas.ts +1 -1
- 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/const.d.ts +18 -0
- package/transpiled/const.js +22 -0
- package/transpiled/const.js.map +1 -0
- package/transpiled/context.d.ts +4 -0
- package/transpiled/context.js +9 -0
- package/transpiled/context.js.map +1 -0
- package/transpiled/deployment.d.ts +12 -3
- package/transpiled/deployment.js +76 -57
- package/transpiled/deployment.js.map +1 -1
- package/transpiled/epoch.d.ts +2 -0
- package/transpiled/epoch.js +19 -0
- package/transpiled/epoch.js.map +1 -0
- package/transpiled/index.d.ts +6 -3
- package/transpiled/index.js +8 -9
- package/transpiled/index.js.map +1 -1
- package/transpiled/manifest.d.ts +1 -1
- package/transpiled/manifest.js +2 -31
- package/transpiled/manifest.js.map +1 -1
- package/transpiled/schemas.js +5 -31
- package/transpiled/schemas.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/3Kbn_1Vv.js +1 -0
- package/ui/dist/_app/immutable/chunks/BV5cDXGB.js +50 -0
- package/ui/dist/_app/immutable/chunks/B_CH3nFk.js +1 -0
- package/ui/dist/_app/immutable/chunks/Bjy-W4x2.js +81 -0
- package/ui/dist/_app/immutable/chunks/CNZAv2Es.js +8 -0
- package/ui/dist/_app/immutable/chunks/CcD_ISxM.js +3 -0
- package/ui/dist/_app/immutable/chunks/vl8APhvf.js +6 -0
- package/ui/dist/_app/immutable/chunks/xihTtKlq.js +1 -0
- package/ui/dist/_app/immutable/entry/app.CK-db-gz.js +2 -0
- package/ui/dist/_app/immutable/entry/start.Bs7xr9Ne.js +1 -0
- package/ui/dist/_app/immutable/nodes/0.vPwVABRs.js +1 -0
- package/ui/dist/_app/immutable/nodes/1.DS63PME5.js +1 -0
- package/ui/dist/_app/immutable/nodes/2.KRpWYBfz.js +1 -0
- package/ui/dist/_app/immutable/nodes/3.DCxORePS.js +1 -0
- package/ui/dist/_app/immutable/nodes/4.D8lluH3u.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/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.test.d.ts +0 -1
- package/transpiled/deployment.test.js +0 -18
- package/transpiled/deployment.test.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/tsconfig.tsbuildinfo +0 -1
package/source/epoch.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto'
|
|
2
|
+
|
|
3
|
+
/** The same for the same schema, whatever the key order it was written in. */
|
|
4
|
+
export function epoch(schema: object): string {
|
|
5
|
+
return createHash('sha256').update(canonical(schema)).digest('hex')
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
function canonical(value: unknown): string {
|
|
9
|
+
if (Array.isArray(value)) return '[' + value.map(canonical).join(',') + ']'
|
|
10
|
+
|
|
11
|
+
if (value !== null && typeof value === 'object') {
|
|
12
|
+
const object = value as Record<string, unknown>
|
|
13
|
+
const keys = Object.keys(object)
|
|
14
|
+
.filter((key) => object[key] !== undefined)
|
|
15
|
+
.sort()
|
|
16
|
+
const entries = keys.map((key) => JSON.stringify(key) + ':' + canonical(object[key]))
|
|
17
|
+
|
|
18
|
+
return '{' + entries.join(',') + '}'
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return JSON.stringify(value) ?? 'null'
|
|
22
|
+
}
|
package/source/index.ts
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
-
export { manifest } from './manifest'
|
|
2
|
-
export { deployment } from './deployment'
|
|
3
|
-
export {
|
|
1
|
+
export { manifest } from './manifest.js'
|
|
2
|
+
export { deployment, describe, image } from './deployment.js'
|
|
3
|
+
export { components } from './Composition.js'
|
|
4
|
+
export { Factory } from './Factory.js'
|
|
5
|
+
|
|
6
|
+
// what a value declared `format: secret` is on a component's context
|
|
7
|
+
export { Secret } from './Secret.js'
|
|
8
|
+
|
|
9
|
+
// what this extension puts on a component's context
|
|
10
|
+
export { context } from './context.js'
|
package/source/manifest.test.ts
CHANGED
|
@@ -1,15 +1,24 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { it } from 'node:test'
|
|
2
|
+
import assert from 'node:assert/strict'
|
|
3
|
+
|
|
4
|
+
import { type Manifest, manifest } from './manifest.js'
|
|
2
5
|
|
|
3
6
|
it('should validate', async () => {
|
|
4
7
|
const additional = { schema: {}, foo: 'bar' } as unknown as Manifest
|
|
5
8
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
+
assert.throws(
|
|
10
|
+
() => {
|
|
11
|
+
manifest(additional)
|
|
12
|
+
},
|
|
13
|
+
(error: any) => /not expected/.test(error.message)
|
|
14
|
+
)
|
|
9
15
|
|
|
10
16
|
const wrongType = { schema: 'not ok' } as unknown as Manifest
|
|
11
17
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
18
|
+
assert.throws(
|
|
19
|
+
() => {
|
|
20
|
+
manifest(wrongType)
|
|
21
|
+
},
|
|
22
|
+
(error: any) => /object/.test(error.message)
|
|
23
|
+
)
|
|
15
24
|
})
|
package/source/manifest.ts
CHANGED
|
@@ -1,9 +1,7 @@
|
|
|
1
|
-
import * as schemas from './schemas'
|
|
2
|
-
import { type Node } from './configuration'
|
|
3
|
-
|
|
4
|
-
export function manifest (manifest: Manifest): Manifest {
|
|
5
|
-
if (manifest.schema === undefined) manifest = { schema: manifest }
|
|
1
|
+
import * as schemas from './schemas.js'
|
|
2
|
+
import { type Node } from './configuration.js'
|
|
6
3
|
|
|
4
|
+
export function manifest(manifest: Manifest): Manifest {
|
|
7
5
|
schemas.manifest.validate(manifest)
|
|
8
6
|
|
|
9
7
|
return manifest
|
package/source/schemas.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { resolve } from 'node:path'
|
|
|
2
2
|
import * as schemas from '@toa.io/schemas'
|
|
3
3
|
import type { Schema } from '@toa.io/schemas'
|
|
4
4
|
|
|
5
|
-
const path = resolve(
|
|
5
|
+
const path = resolve(import.meta.dirname, '../schemas')
|
|
6
6
|
const namespace = schemas.namespace(path)
|
|
7
7
|
|
|
8
8
|
export const manifest: Schema<any> = namespace.schema('manifest')
|
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 './manifest.js';
|
|
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 './epoch.js';
|
|
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;
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import { console } from 'openspan';
|
|
2
|
+
import { Connector, Locator } from '@toa.io/core';
|
|
3
|
+
import { EVENT, SOURCE } from './const.js';
|
|
4
|
+
/**
|
|
5
|
+
* One per process: one remote to the values service and one subscription to its events,
|
|
6
|
+
* shared by every Aspect. What the Aspects ask for is collected and sent as one call;
|
|
7
|
+
* what the service creates afterwards is handed to whoever subscribed.
|
|
8
|
+
*/
|
|
9
|
+
export class Client extends Connector {
|
|
10
|
+
/** Disconnected once, a connector keeps what it depended on, so a gone client is not reused. */
|
|
11
|
+
disposed = false;
|
|
12
|
+
host;
|
|
13
|
+
options;
|
|
14
|
+
pending = new Map();
|
|
15
|
+
listeners = new Map();
|
|
16
|
+
remote = null;
|
|
17
|
+
timer = null;
|
|
18
|
+
flushing = false;
|
|
19
|
+
fresh = false;
|
|
20
|
+
round = 0;
|
|
21
|
+
constructor(host, options = {}) {
|
|
22
|
+
super();
|
|
23
|
+
this.host = host;
|
|
24
|
+
this.options = { ...DEFAULTS, ...options };
|
|
25
|
+
}
|
|
26
|
+
/** The configuration of a component for an epoch, once the service has one. */
|
|
27
|
+
async fetch(component, epoch) {
|
|
28
|
+
const key = id(component, epoch);
|
|
29
|
+
let entry = this.pending.get(key);
|
|
30
|
+
if (entry === undefined) {
|
|
31
|
+
entry = { component, epoch, waiters: [] };
|
|
32
|
+
this.pending.set(key, entry);
|
|
33
|
+
}
|
|
34
|
+
const promise = new Promise((resolve) => {
|
|
35
|
+
entry.waiters.push(resolve);
|
|
36
|
+
});
|
|
37
|
+
if (this.flushing)
|
|
38
|
+
this.fresh = true;
|
|
39
|
+
else
|
|
40
|
+
this.schedule(0);
|
|
41
|
+
return await promise;
|
|
42
|
+
}
|
|
43
|
+
subscribe(component, epoch, listener) {
|
|
44
|
+
const key = id(component, epoch);
|
|
45
|
+
if (!this.listeners.has(key))
|
|
46
|
+
this.listeners.set(key, new Set());
|
|
47
|
+
this.listeners.get(key).add(listener);
|
|
48
|
+
}
|
|
49
|
+
unsubscribe(component, epoch, listener) {
|
|
50
|
+
this.listeners.get(id(component, epoch))?.delete(listener);
|
|
51
|
+
}
|
|
52
|
+
async open() {
|
|
53
|
+
this.remote = await this.host.remote(LOCATOR, SOURCE);
|
|
54
|
+
this.depends(this.remote);
|
|
55
|
+
await this.remote.connect();
|
|
56
|
+
const subscription = new Subscription(this.deliver.bind(this));
|
|
57
|
+
const consumer = await this.host.receive(EVENT, subscription);
|
|
58
|
+
this.depends(consumer);
|
|
59
|
+
await consumer.connect();
|
|
60
|
+
}
|
|
61
|
+
async close() {
|
|
62
|
+
if (this.timer !== null) {
|
|
63
|
+
clearTimeout(this.timer);
|
|
64
|
+
this.timer = null;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
async dispose() {
|
|
68
|
+
this.disposed = true;
|
|
69
|
+
}
|
|
70
|
+
schedule(delay) {
|
|
71
|
+
if (this.flushing)
|
|
72
|
+
return;
|
|
73
|
+
if (this.timer !== null) {
|
|
74
|
+
// a request that has just arrived does not wait for the round already planned
|
|
75
|
+
if (delay > 0)
|
|
76
|
+
return;
|
|
77
|
+
clearTimeout(this.timer);
|
|
78
|
+
}
|
|
79
|
+
this.timer = setTimeout(() => {
|
|
80
|
+
void this.flush();
|
|
81
|
+
}, delay);
|
|
82
|
+
}
|
|
83
|
+
async flush() {
|
|
84
|
+
this.timer = null;
|
|
85
|
+
this.flushing = true;
|
|
86
|
+
this.fresh = false;
|
|
87
|
+
const batch = [...this.pending.values()];
|
|
88
|
+
const input = batch.map(({ component, epoch }) => ({ component, epoch }));
|
|
89
|
+
try {
|
|
90
|
+
const output = await this.remote.invoke('fetch', { input });
|
|
91
|
+
if (output instanceof Error)
|
|
92
|
+
throw output;
|
|
93
|
+
this.settle(output);
|
|
94
|
+
}
|
|
95
|
+
catch (error) {
|
|
96
|
+
console.warn('Configuration fetch failed', { error });
|
|
97
|
+
}
|
|
98
|
+
finally {
|
|
99
|
+
this.flushing = false;
|
|
100
|
+
}
|
|
101
|
+
this.report(batch);
|
|
102
|
+
if (this.pending.size === 0)
|
|
103
|
+
return;
|
|
104
|
+
if (this.fresh)
|
|
105
|
+
this.schedule(0);
|
|
106
|
+
else
|
|
107
|
+
this.schedule(this.backoff());
|
|
108
|
+
}
|
|
109
|
+
/** Those the service has served are told; the rest stay for the next round. */
|
|
110
|
+
settle(output) {
|
|
111
|
+
for (const { component, epoch, configuration, created } of output) {
|
|
112
|
+
if (configuration === null)
|
|
113
|
+
continue;
|
|
114
|
+
const key = id(component, epoch);
|
|
115
|
+
const entry = this.pending.get(key);
|
|
116
|
+
if (entry === undefined)
|
|
117
|
+
continue;
|
|
118
|
+
this.pending.delete(key);
|
|
119
|
+
for (const resolve of entry.waiters)
|
|
120
|
+
resolve({ configuration, created });
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
report(batch) {
|
|
124
|
+
const waiting = batch
|
|
125
|
+
.filter(({ component, epoch }) => this.pending.has(id(component, epoch)))
|
|
126
|
+
.map(({ component }) => component);
|
|
127
|
+
if (waiting.length === 0) {
|
|
128
|
+
this.round = 0;
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
this.round++;
|
|
132
|
+
if (this.round % this.options.warn === 1)
|
|
133
|
+
console.warn('Waiting for configuration', {
|
|
134
|
+
components: waiting,
|
|
135
|
+
round: this.round
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
backoff() {
|
|
139
|
+
return Math.min(this.options.base * Math.pow(FACTOR, this.round), this.options.max);
|
|
140
|
+
}
|
|
141
|
+
/** A created object goes to the subscribers of its component and epoch, as it is. */
|
|
142
|
+
deliver(created) {
|
|
143
|
+
const listeners = this.listeners.get(id(created.component, created.epoch));
|
|
144
|
+
if (listeners === undefined)
|
|
145
|
+
return;
|
|
146
|
+
const value = {
|
|
147
|
+
configuration: created.configuration,
|
|
148
|
+
created: created.CREATED
|
|
149
|
+
};
|
|
150
|
+
for (const listener of listeners)
|
|
151
|
+
listener(value);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
/** What the event consumer hands deliveries to. */
|
|
155
|
+
class Subscription extends Connector {
|
|
156
|
+
handler;
|
|
157
|
+
constructor(handler) {
|
|
158
|
+
super();
|
|
159
|
+
this.handler = handler;
|
|
160
|
+
}
|
|
161
|
+
async receive(message) {
|
|
162
|
+
this.handler(message.payload);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
function id(component, epoch) {
|
|
166
|
+
return component + '\0' + epoch;
|
|
167
|
+
}
|
|
168
|
+
const LOCATOR = new Locator('values', 'configuration');
|
|
169
|
+
const FACTOR = 1.5;
|
|
170
|
+
const DEFAULTS = {
|
|
171
|
+
base: 1000,
|
|
172
|
+
max: 10000,
|
|
173
|
+
warn: 5
|
|
174
|
+
};
|
|
175
|
+
//# sourceMappingURL=Client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Client.js","sourceRoot":"","sources":["../source/Client.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { Connector } from '@toa.io/core';
|
|
2
|
+
import type { Host } from './Factory.js';
|
|
3
|
+
/** Hosts the values component in the service process. */
|
|
4
|
+
export declare class Composition extends Connector {
|
|
5
|
+
private readonly host;
|
|
6
|
+
constructor(host: Host);
|
|
7
|
+
protected open(): Promise<void>;
|
|
8
|
+
}
|
|
9
|
+
export declare function components(): Components;
|
|
10
|
+
interface Components {
|
|
11
|
+
labels: string[];
|
|
12
|
+
paths: string[];
|
|
13
|
+
}
|
|
14
|
+
export {};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { readdirSync } from 'node:fs';
|
|
2
|
+
import { resolve } from 'node:path';
|
|
3
|
+
import { Connector } from '@toa.io/core';
|
|
4
|
+
import { uiPort } from './const.js';
|
|
5
|
+
import { UI } from './UI.js';
|
|
6
|
+
/** Hosts the values component in the service process. */
|
|
7
|
+
export class Composition extends Connector {
|
|
8
|
+
host;
|
|
9
|
+
constructor(host) {
|
|
10
|
+
super();
|
|
11
|
+
this.host = host;
|
|
12
|
+
}
|
|
13
|
+
async open() {
|
|
14
|
+
const composition = await this.host.composition(components().paths);
|
|
15
|
+
await composition.connect();
|
|
16
|
+
this.depends(composition);
|
|
17
|
+
// connected here rather than declared as a dependency: dependencies are connected
|
|
18
|
+
// before `open` runs, so one added from inside it would never be
|
|
19
|
+
const ui = new UI(uiPort());
|
|
20
|
+
await ui.connect();
|
|
21
|
+
this.depends(ui);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
export function components() {
|
|
25
|
+
const labels = [];
|
|
26
|
+
const paths = [];
|
|
27
|
+
for (const entry of entries()) {
|
|
28
|
+
labels.push(entry.name.replace('.', '-'));
|
|
29
|
+
paths.push(resolve(ROOT, entry.name));
|
|
30
|
+
}
|
|
31
|
+
return { labels, paths };
|
|
32
|
+
}
|
|
33
|
+
function entries() {
|
|
34
|
+
const entries = readdirSync(ROOT, { withFileTypes: true });
|
|
35
|
+
return entries.filter((entry) => entry.isDirectory());
|
|
36
|
+
}
|
|
37
|
+
const ROOT = resolve(import.meta.dirname, '../components/');
|
|
38
|
+
//# sourceMappingURL=Composition.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Composition.js","sourceRoot":"","sources":["../source/Composition.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAe,MAAM,SAAS,CAAA;AAClD,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AACnC,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAA;AACxC,OAAO,EAAE,MAAM,EAAE,MAAM,YAAY,CAAA;AACnC,OAAO,EAAE,EAAE,EAAE,MAAM,SAAS,CAAA;AAG5B,yDAAyD;AACzD,MAAM,OAAO,WAAY,SAAQ,SAAS;IACvB,IAAI,CAAM;IAE3B,YAAmB,IAAU;QAC3B,KAAK,EAAE,CAAA;QACP,IAAI,CAAC,IAAI,GAAG,IAAI,CAAA;IAClB,CAAC;IAEkB,KAAK,CAAC,IAAI;QAC3B,MAAM,WAAW,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,WAAW,CAAC,UAAU,EAAE,CAAC,KAAK,CAAC,CAAA;QAEnE,MAAM,WAAW,CAAC,OAAO,EAAE,CAAA;QAE3B,IAAI,CAAC,OAAO,CAAC,WAAW,CAAC,CAAA;QAEzB,kFAAkF;QAClF,iEAAiE;QACjE,MAAM,EAAE,GAAG,IAAI,EAAE,CAAC,MAAM,EAAE,CAAC,CAAA;QAE3B,MAAM,EAAE,CAAC,OAAO,EAAE,CAAA;QAElB,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC,CAAA;IAClB,CAAC;CACF;AAED,MAAM,UAAU,UAAU;IACxB,MAAM,MAAM,GAAa,EAAE,CAAA;IAC3B,MAAM,KAAK,GAAa,EAAE,CAAA;IAE1B,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,EAAE,CAAC;QAC9B,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC,CAAA;QACzC,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC,CAAA;IACvC,CAAC;IAED,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,CAAA;AAC1B,CAAC;AAED,SAAS,OAAO;IACd,MAAM,OAAO,GAAG,WAAW,CAAC,IAAI,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAA;IAE1D,OAAO,OAAO,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,WAAW,EAAE,CAAC,CAAA;AACvD,CAAC;AAOD,MAAM,IAAI,GAAG,OAAO,CAAC,OAAO,IAAI,CAAC,OAAO,EAAE,gBAAgB,CAAC,CAAA"}
|
package/transpiled/Factory.d.ts
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
|
-
import { type
|
|
2
|
-
import {
|
|
1
|
+
import { type Connector, type Locator } from '@toa.io/core';
|
|
2
|
+
import type { extensions } from '@toa.io/core/types';
|
|
3
|
+
import type { Manifest } from './manifest.js';
|
|
3
4
|
export declare class Factory implements extensions.Factory {
|
|
5
|
+
private readonly host;
|
|
6
|
+
private client;
|
|
7
|
+
constructor(host: Host);
|
|
4
8
|
aspect(locator: Locator, manifest: Manifest): extensions.Aspect;
|
|
9
|
+
service(): Connector;
|
|
10
|
+
private shared;
|
|
5
11
|
}
|
|
12
|
+
export type Host = extensions.Host;
|
package/transpiled/Factory.js
CHANGED
|
@@ -1,13 +1,24 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
1
|
+
import { Aspect } from './Aspect.js';
|
|
2
|
+
import { Client } from './Client.js';
|
|
3
|
+
import { Composition } from './Composition.js';
|
|
4
|
+
import { overridden } from './configuration.js';
|
|
5
|
+
export class Factory {
|
|
6
|
+
host;
|
|
7
|
+
client = null;
|
|
8
|
+
constructor(host) {
|
|
9
|
+
this.host = host;
|
|
10
|
+
}
|
|
7
11
|
aspect(locator, manifest) {
|
|
8
|
-
const
|
|
9
|
-
return new
|
|
12
|
+
const client = overridden(locator) ? null : this.shared();
|
|
13
|
+
return new Aspect(locator, manifest, client);
|
|
14
|
+
}
|
|
15
|
+
service() {
|
|
16
|
+
return new Composition(this.host);
|
|
17
|
+
}
|
|
18
|
+
shared() {
|
|
19
|
+
if (this.client === null || this.client.disposed)
|
|
20
|
+
this.client = new Client(this.host);
|
|
21
|
+
return this.client;
|
|
10
22
|
}
|
|
11
23
|
}
|
|
12
|
-
exports.Factory = Factory;
|
|
13
24
|
//# sourceMappingURL=Factory.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Factory.js","sourceRoot":"","sources":["../source/Factory.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"Factory.js","sourceRoot":"","sources":["../source/Factory.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AACpC,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AACpC,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAA;AAC9C,OAAO,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAA;AAG/C,MAAM,OAAO,OAAO;IACD,IAAI,CAAM;IACnB,MAAM,GAAkB,IAAI,CAAA;IAEpC,YAAmB,IAAU;QAC3B,IAAI,CAAC,IAAI,GAAG,IAAI,CAAA;IAClB,CAAC;IAEM,MAAM,CAAC,OAAgB,EAAE,QAAkB;QAChD,MAAM,MAAM,GAAG,UAAU,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,EAAE,CAAA;QAEzD,OAAO,IAAI,MAAM,CAAC,OAAO,EAAE,QAAQ,EAAE,MAAM,CAAC,CAAA;IAC9C,CAAC;IAEM,OAAO;QACZ,OAAO,IAAI,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IACnC,CAAC;IAEO,MAAM;QACZ,IAAI,IAAI,CAAC,MAAM,KAAK,IAAI,IAAI,IAAI,CAAC,MAAM,CAAC,QAAQ;YAAE,IAAI,CAAC,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;QAErF,OAAO,IAAI,CAAC,MAAM,CAAA;IACpB,CAAC;CACF"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { inspect } from 'node:util';
|
|
2
|
+
/**
|
|
3
|
+
* A configuration value that must not leak: a string only to whoever asks for it.
|
|
4
|
+
*
|
|
5
|
+
* This is what a value declared `format: secret` is on a component's context.
|
|
6
|
+
*/
|
|
7
|
+
export declare class Secret {
|
|
8
|
+
#private;
|
|
9
|
+
constructor(value: string);
|
|
10
|
+
unwrap(): string;
|
|
11
|
+
toString(): string;
|
|
12
|
+
toJSON(): string;
|
|
13
|
+
[inspect.custom](): string;
|
|
14
|
+
}
|
|
15
|
+
export declare const REDACTED = "<REDACTED>";
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { inspect } from 'node:util';
|
|
2
|
+
/**
|
|
3
|
+
* A configuration value that must not leak: a string only to whoever asks for it.
|
|
4
|
+
*
|
|
5
|
+
* This is what a value declared `format: secret` is on a component's context.
|
|
6
|
+
*/
|
|
7
|
+
export class Secret {
|
|
8
|
+
#value;
|
|
9
|
+
constructor(value) {
|
|
10
|
+
this.#value = value;
|
|
11
|
+
}
|
|
12
|
+
unwrap() {
|
|
13
|
+
return this.#value;
|
|
14
|
+
}
|
|
15
|
+
toString() {
|
|
16
|
+
return REDACTED;
|
|
17
|
+
}
|
|
18
|
+
toJSON() {
|
|
19
|
+
return REDACTED;
|
|
20
|
+
}
|
|
21
|
+
[inspect.custom]() {
|
|
22
|
+
return REDACTED;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
export const REDACTED = '<REDACTED>';
|
|
26
|
+
//# sourceMappingURL=Secret.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Secret.js","sourceRoot":"","sources":["../source/Secret.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AAEnC;;;;GAIG;AACH,MAAM,OAAO,MAAM;IACR,MAAM,CAAQ;IAEvB,YAAmB,KAAa;QAC9B,IAAI,CAAC,MAAM,GAAG,KAAK,CAAA;IACrB,CAAC;IAEM,MAAM;QACX,OAAO,IAAI,CAAC,MAAM,CAAA;IACpB,CAAC;IAEM,QAAQ;QACb,OAAO,QAAQ,CAAA;IACjB,CAAC;IAEM,MAAM;QACX,OAAO,QAAQ,CAAA;IACjB,CAAC;IAEM,CAAC,OAAO,CAAC,MAAM,CAAC;QACrB,OAAO,QAAQ,CAAA;IACjB,CAAC;CACF;AAED,MAAM,CAAC,MAAM,QAAQ,GAAG,YAAY,CAAA"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { Connector } from '@toa.io/core';
|
|
2
|
+
/**
|
|
3
|
+
* Serves the configuration UI: the directory `ui` builds, and nothing else.
|
|
4
|
+
*
|
|
5
|
+
* The page is a single-page application whose mount path is baked into the bundle,
|
|
6
|
+
* so this server routes relative to `UI_PATH` — which is also what the ingress
|
|
7
|
+
* forwards. `/configuration/*` is left alone: that is the component's own API.
|
|
8
|
+
*/
|
|
9
|
+
export declare class UI extends Connector {
|
|
10
|
+
private readonly server;
|
|
11
|
+
private readonly port;
|
|
12
|
+
private readonly root;
|
|
13
|
+
constructor(port: number, root?: string);
|
|
14
|
+
protected open(): Promise<void>;
|
|
15
|
+
protected close(): Promise<void>;
|
|
16
|
+
private listen;
|
|
17
|
+
private respond;
|
|
18
|
+
/**
|
|
19
|
+
* The file a request lands on, or `null` when nothing does. A path that exists is
|
|
20
|
+
* served as it is; anything else that could be a route falls back to the page,
|
|
21
|
+
* because the client router — not this server — knows what routes there are.
|
|
22
|
+
*/
|
|
23
|
+
private resolve;
|
|
24
|
+
private send;
|
|
25
|
+
}
|