@toa.io/extensions.introspection 1.0.0-alpha.262
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 +61 -0
- package/components/introspection.edges/manifest.toa.yaml +48 -0
- package/components/introspection.edges/operations/lib/types.d.ts +28 -0
- package/components/introspection.edges/operations/lib/types.js +3 -0
- package/components/introspection.edges/operations/lib/types.js.map +1 -0
- package/components/introspection.edges/operations/merge.d.ts +9 -0
- package/components/introspection.edges/operations/merge.js +25 -0
- package/components/introspection.edges/operations/merge.js.map +1 -0
- package/components/introspection.edges/source/lib/types.ts +33 -0
- package/components/introspection.edges/source/merge.ts +26 -0
- package/components/introspection.edges/tsconfig.json +9 -0
- package/components/introspection.nodes/manifest.toa.yaml +49 -0
- package/components/introspection.nodes/operations/lib/types.d.ts +16 -0
- package/components/introspection.nodes/operations/lib/types.js +3 -0
- package/components/introspection.nodes/operations/lib/types.js.map +1 -0
- package/components/introspection.nodes/operations/merge.d.ts +9 -0
- package/components/introspection.nodes/operations/merge.js +21 -0
- package/components/introspection.nodes/operations/merge.js.map +1 -0
- package/components/introspection.nodes/source/lib/types.ts +18 -0
- package/components/introspection.nodes/source/merge.ts +21 -0
- package/components/introspection.nodes/tsconfig.json +9 -0
- package/cucumber.js +8 -0
- package/features/calls.feature +123 -0
- package/features/nodes.feature +70 -0
- package/features/samples.feature +75 -0
- package/features/shutdown.feature +37 -0
- package/features/site/_app/immutable/asset.js +1 -0
- package/features/site/index.html +6 -0
- package/features/steps/Map.ts +151 -0
- package/features/steps/UI.ts +88 -0
- package/features/steps/components/probe.discreet/manifest.toa.yaml +12 -0
- package/features/steps/components/probe.discreet/operations/ping.js +7 -0
- package/features/steps/components/probe.quiet/manifest.toa.yaml +11 -0
- package/features/steps/components/probe.quiet/operations/ping.js +7 -0
- package/features/steps/components/probe.source/manifest.toa.yaml +27 -0
- package/features/steps/components/probe.source/operations/crash.js +7 -0
- package/features/steps/components/probe.source/operations/fail.js +12 -0
- package/features/steps/components/probe.source/operations/relay.js +7 -0
- package/features/steps/components/probe.target/manifest.toa.yaml +24 -0
- package/features/steps/components/probe.target/operations/compute.js +7 -0
- package/features/steps/components/probe.target/operations/count.js +9 -0
- package/features/steps/config.ts +20 -0
- package/features/steps/tsconfig.json +9 -0
- package/features/ui.feature +85 -0
- package/package.json +31 -0
- package/readme.md +141 -0
- package/schemas/annotation.cos.yaml +16 -0
- package/schemas/declaration.cos.yaml +2 -0
- package/source/Composition.ts +61 -0
- package/source/Explorer.ts +16 -0
- package/source/Factory.ts +119 -0
- package/source/Reporter.ts +210 -0
- package/source/Tenant.ts +47 -0
- package/source/UI.ts +197 -0
- package/source/annotation.ts +83 -0
- package/source/const.ts +32 -0
- package/source/describe.ts +68 -0
- package/source/extension.ts +45 -0
- package/source/index.ts +7 -0
- package/source/keys.ts +0 -0
- package/source/manifest.ts +19 -0
- package/source/model.ts +82 -0
- package/source/sample.ts +49 -0
- package/source/schemas.ts +10 -0
- package/transpiled/Composition.d.ts +21 -0
- package/transpiled/Composition.js +48 -0
- package/transpiled/Composition.js.map +1 -0
- package/transpiled/Explorer.d.ts +9 -0
- package/transpiled/Explorer.js +19 -0
- package/transpiled/Explorer.js.map +1 -0
- package/transpiled/Factory.d.ts +22 -0
- package/transpiled/Factory.js +93 -0
- package/transpiled/Factory.js.map +1 -0
- package/transpiled/Reporter.d.ts +44 -0
- package/transpiled/Reporter.js +187 -0
- package/transpiled/Reporter.js.map +1 -0
- package/transpiled/Tenant.d.ts +18 -0
- package/transpiled/Tenant.js +40 -0
- package/transpiled/Tenant.js.map +1 -0
- package/transpiled/UI.d.ts +25 -0
- package/transpiled/UI.js +186 -0
- package/transpiled/UI.js.map +1 -0
- package/transpiled/annotation.d.ts +40 -0
- package/transpiled/annotation.js +46 -0
- package/transpiled/annotation.js.map +1 -0
- package/transpiled/const.d.ts +23 -0
- package/transpiled/const.js +27 -0
- package/transpiled/const.js.map +1 -0
- package/transpiled/contracts.d.ts +51 -0
- package/transpiled/contracts.js +53 -0
- package/transpiled/contracts.js.map +1 -0
- package/transpiled/describe.d.ts +9 -0
- package/transpiled/describe.js +64 -0
- package/transpiled/describe.js.map +1 -0
- package/transpiled/extension.d.ts +11 -0
- package/transpiled/extension.js +64 -0
- package/transpiled/extension.js.map +1 -0
- package/transpiled/index.d.ts +6 -0
- package/transpiled/index.js +25 -0
- package/transpiled/index.js.map +1 -0
- package/transpiled/keys.d.ts +9 -0
- package/transpiled/keys.js +0 -0
- package/transpiled/keys.js.map +1 -0
- package/transpiled/manifest.d.ts +9 -0
- package/transpiled/manifest.js +43 -0
- package/transpiled/manifest.js.map +1 -0
- package/transpiled/model.d.ts +71 -0
- package/transpiled/model.js +3 -0
- package/transpiled/model.js.map +1 -0
- package/transpiled/sample.d.ts +7 -0
- package/transpiled/sample.js +44 -0
- package/transpiled/sample.js.map +1 -0
- package/transpiled/schemas.d.ts +4 -0
- package/transpiled/schemas.js +13 -0
- package/transpiled/schemas.js.map +1 -0
- package/tsconfig.json +12 -0
- package/ui/dist/_app/env.js +1 -0
- package/ui/dist/_app/immutable/assets/0.fg2cP6bO.css +2 -0
- package/ui/dist/_app/immutable/assets/2.BghF56l_.css +1 -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/chunks/Bjy-W4x2.js +81 -0
- package/ui/dist/_app/immutable/chunks/CIvRtvVA.js +1 -0
- package/ui/dist/_app/immutable/chunks/D1jNriZ8.js +1 -0
- package/ui/dist/_app/immutable/chunks/DINmginf.js +1 -0
- package/ui/dist/_app/immutable/chunks/DKZCAkrF.js +1 -0
- package/ui/dist/_app/immutable/chunks/DLG2JvyF.js +6 -0
- package/ui/dist/_app/immutable/chunks/DSi-FjwI.js +2 -0
- package/ui/dist/_app/immutable/chunks/DdoZuyU1.js +1 -0
- package/ui/dist/_app/immutable/chunks/O9IjE_TH.js +1 -0
- package/ui/dist/_app/immutable/chunks/PUjOYRcs.js +3 -0
- package/ui/dist/_app/immutable/chunks/QKD4Mgck.js +5 -0
- package/ui/dist/_app/immutable/chunks/Ux-r8C9U.js +1 -0
- package/ui/dist/_app/immutable/chunks/xihTtKlq.js +1 -0
- package/ui/dist/_app/immutable/entry/app.CTY34L35.js +2 -0
- package/ui/dist/_app/immutable/entry/start.Bq2Gn1Ax.js +1 -0
- package/ui/dist/_app/immutable/nodes/0.lOXtBRjQ.js +1 -0
- package/ui/dist/_app/immutable/nodes/1.r8ESZdgM.js +1 -0
- package/ui/dist/_app/immutable/nodes/2.DuVj0loz.js +8 -0
- package/ui/dist/_app/immutable/nodes/3.DhdkQ2Q1.js +1 -0
- package/ui/dist/_app/immutable/nodes/4.DUAEZdRr.js +1 -0
- package/ui/dist/_app/immutable/nodes/5.CIVn_xgq.js +1 -0
- package/ui/dist/_app/version.json +1 -0
- package/ui/dist/index.html +48 -0
- package/ui/dist/robots.txt +3 -0
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Explorer = void 0;
|
|
4
|
+
const core_1 = require("@toa.io/core");
|
|
5
|
+
const openspan_1 = require("openspan");
|
|
6
|
+
/**
|
|
7
|
+
* The explorer process. It hosts the introspection components — the map is read
|
|
8
|
+
* through their own operations — and serves the UI.
|
|
9
|
+
*/
|
|
10
|
+
class Explorer extends core_1.Connector {
|
|
11
|
+
async open() {
|
|
12
|
+
openspan_1.console.info('Introspection explorer started');
|
|
13
|
+
}
|
|
14
|
+
dispose() {
|
|
15
|
+
openspan_1.console.info('Introspection explorer is closed');
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
exports.Explorer = Explorer;
|
|
19
|
+
//# sourceMappingURL=Explorer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Explorer.js","sourceRoot":"","sources":["../source/Explorer.ts"],"names":[],"mappings":";;;AAAA,uCAAwC;AACxC,uCAAkC;AAElC;;;GAGG;AACH,MAAa,QAAS,SAAQ,gBAAS;IAClB,KAAK,CAAC,IAAI;QAC3B,kBAAO,CAAC,IAAI,CAAC,gCAAgC,CAAC,CAAA;IAChD,CAAC;IAEkB,OAAO;QACxB,kBAAO,CAAC,IAAI,CAAC,kCAAkC,CAAC,CAAA;IAClD,CAAC;CACF;AARD,4BAQC"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { Connector } from '@toa.io/core';
|
|
2
|
+
import type { Declaration } from './annotation';
|
|
3
|
+
import type { Manifest } from '@toa.io/norm';
|
|
4
|
+
import type { Component, Locator, extensions } from '@toa.io/core';
|
|
5
|
+
export declare class Factory implements extensions.Factory {
|
|
6
|
+
private readonly boot;
|
|
7
|
+
private readonly options;
|
|
8
|
+
private readonly settings;
|
|
9
|
+
private reporter;
|
|
10
|
+
constructor(boot: Bootloader);
|
|
11
|
+
tenant(locator: Locator, decl: Declaration | null, manifest: Manifest): Connector;
|
|
12
|
+
component(component: Component): Component;
|
|
13
|
+
service(): Connector | null;
|
|
14
|
+
/**
|
|
15
|
+
* `tenant()` runs before any component is created, so settings are warm.
|
|
16
|
+
* A component booted on its own (without a composition) falls back to
|
|
17
|
+
* the environment, with sampling off.
|
|
18
|
+
*/
|
|
19
|
+
private resolve;
|
|
20
|
+
private collector;
|
|
21
|
+
}
|
|
22
|
+
export type Bootloader = typeof import('@toa.io/boot');
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Factory = void 0;
|
|
4
|
+
const core_1 = require("@toa.io/core");
|
|
5
|
+
const annotation_1 = require("./annotation");
|
|
6
|
+
const const_1 = require("./const");
|
|
7
|
+
const describe_1 = require("./describe");
|
|
8
|
+
const Reporter_1 = require("./Reporter");
|
|
9
|
+
const Tenant_1 = require("./Tenant");
|
|
10
|
+
const Composition_1 = require("./Composition");
|
|
11
|
+
const Explorer_1 = require("./Explorer");
|
|
12
|
+
const UI_1 = require("./UI");
|
|
13
|
+
const sample_1 = require("./sample");
|
|
14
|
+
class Factory {
|
|
15
|
+
boot;
|
|
16
|
+
options;
|
|
17
|
+
settings = {};
|
|
18
|
+
reporter = null;
|
|
19
|
+
constructor(boot) {
|
|
20
|
+
this.boot = boot;
|
|
21
|
+
this.options = (0, annotation_1.environment)();
|
|
22
|
+
}
|
|
23
|
+
tenant(locator, decl, manifest) {
|
|
24
|
+
const resolved = (0, annotation_1.settings)(locator.namespace, (0, annotation_1.component)(decl), this.options);
|
|
25
|
+
this.settings[locator.id] = resolved;
|
|
26
|
+
if (!resolved.enabled || locator.namespace === const_1.NAMESPACE)
|
|
27
|
+
return new core_1.Connector();
|
|
28
|
+
return new Tenant_1.Tenant(this.collector(), (0, describe_1.describe)(manifest));
|
|
29
|
+
}
|
|
30
|
+
component(component) {
|
|
31
|
+
const locator = component.locator;
|
|
32
|
+
const resolved = this.resolve(locator);
|
|
33
|
+
if (!resolved.enabled)
|
|
34
|
+
return component;
|
|
35
|
+
const reporter = this.collector();
|
|
36
|
+
const invoke = component.invoke.bind(component);
|
|
37
|
+
component.invoke = async (endpoint, request) => {
|
|
38
|
+
let outcome = 'ok';
|
|
39
|
+
let reply;
|
|
40
|
+
try {
|
|
41
|
+
reply = await invoke(endpoint, request);
|
|
42
|
+
if (reply?.exception !== undefined)
|
|
43
|
+
outcome = 'exception';
|
|
44
|
+
else if (reply?.error !== undefined)
|
|
45
|
+
outcome = 'error';
|
|
46
|
+
return reply;
|
|
47
|
+
}
|
|
48
|
+
catch (error) {
|
|
49
|
+
outcome = 'exception';
|
|
50
|
+
throw error;
|
|
51
|
+
}
|
|
52
|
+
finally {
|
|
53
|
+
// a call that failed is still a connection between two components
|
|
54
|
+
const src = request?.source ?? UNKNOWN;
|
|
55
|
+
const dst = { namespace: locator.namespace, component: locator.name, operation: endpoint };
|
|
56
|
+
const sample = resolved.samples && (0, sample_1.samplable)(request?.input)
|
|
57
|
+
? (0, sample_1.capture)(request?.input, outcome)
|
|
58
|
+
: undefined;
|
|
59
|
+
reporter.observe({ src, dst, sample });
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
component.depends(reporter);
|
|
63
|
+
return component;
|
|
64
|
+
}
|
|
65
|
+
service() {
|
|
66
|
+
if (this.options === null)
|
|
67
|
+
return null;
|
|
68
|
+
const composition = new Composition_1.Composition(this.boot);
|
|
69
|
+
const explorer = new Explorer_1.Explorer();
|
|
70
|
+
explorer.depends(composition);
|
|
71
|
+
if (this.options.ui)
|
|
72
|
+
explorer.depends(new UI_1.UI(const_1.UI_PORT));
|
|
73
|
+
return explorer;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* `tenant()` runs before any component is created, so settings are warm.
|
|
77
|
+
* A component booted on its own (without a composition) falls back to
|
|
78
|
+
* the environment, with sampling off.
|
|
79
|
+
*/
|
|
80
|
+
resolve(locator) {
|
|
81
|
+
if (locator.namespace === const_1.NAMESPACE)
|
|
82
|
+
return annotation_1.DISABLED;
|
|
83
|
+
return this.settings[locator.id] ??
|
|
84
|
+
(0, annotation_1.settings)(locator.namespace, {}, this.options === null ? null : { ...this.options, samples: false });
|
|
85
|
+
}
|
|
86
|
+
collector() {
|
|
87
|
+
this.reporter ??= new Reporter_1.Reporter(this.boot, this.options);
|
|
88
|
+
return this.reporter;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
exports.Factory = Factory;
|
|
92
|
+
const UNKNOWN = { service: 'unknown' };
|
|
93
|
+
//# sourceMappingURL=Factory.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Factory.js","sourceRoot":"","sources":["../source/Factory.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { Connector } from '@toa.io/core';
|
|
2
|
+
import type { Bootloader } from './Factory';
|
|
3
|
+
import type { Options } from './annotation';
|
|
4
|
+
import type { Edge, Node } from './model';
|
|
5
|
+
/**
|
|
6
|
+
* Buffers what a process observes and flushes it into the introspection
|
|
7
|
+
* components.
|
|
8
|
+
*
|
|
9
|
+
* Nothing here is on the critical path. Reaching the explorer is a discovery,
|
|
10
|
+
* which waits as long as it takes, so the collector never holds up a component
|
|
11
|
+
* starting, running or stopping: it buffers until the connection is there, and
|
|
12
|
+
* when it has to choose it gives up on the data rather than on the application.
|
|
13
|
+
*/
|
|
14
|
+
export declare class Reporter extends Connector {
|
|
15
|
+
private readonly boot;
|
|
16
|
+
private readonly options;
|
|
17
|
+
private readonly nodes;
|
|
18
|
+
private readonly edges;
|
|
19
|
+
/** Holds a remote only once it is connected and usable. */
|
|
20
|
+
private readonly remotes;
|
|
21
|
+
private timer;
|
|
22
|
+
private flushing;
|
|
23
|
+
private acquiring;
|
|
24
|
+
private dropped;
|
|
25
|
+
constructor(boot: Bootloader, options: Options);
|
|
26
|
+
/** The static description of a component. */
|
|
27
|
+
expose(node: Node): void;
|
|
28
|
+
/** A call between two components. */
|
|
29
|
+
observe(observed: Edge): void;
|
|
30
|
+
protected open(): Promise<void>;
|
|
31
|
+
protected close(): Promise<void>;
|
|
32
|
+
private ready;
|
|
33
|
+
private flush;
|
|
34
|
+
private discard;
|
|
35
|
+
private dispatch;
|
|
36
|
+
/**
|
|
37
|
+
* A mass transition: every affected object is acquired and committed at once,
|
|
38
|
+
* so a flush is one call per component whatever it carries.
|
|
39
|
+
*/
|
|
40
|
+
private merge;
|
|
41
|
+
/** Runs in the background: discovery waits for the explorer as long as it takes. */
|
|
42
|
+
private acquire;
|
|
43
|
+
private reach;
|
|
44
|
+
}
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
19
|
+
if (mod && mod.__esModule) return mod;
|
|
20
|
+
var result = {};
|
|
21
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
22
|
+
__setModuleDefault(result, mod);
|
|
23
|
+
return result;
|
|
24
|
+
};
|
|
25
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
26
|
+
exports.Reporter = void 0;
|
|
27
|
+
const core_1 = require("@toa.io/core");
|
|
28
|
+
const openspan_1 = require("openspan");
|
|
29
|
+
const const_1 = require("./const");
|
|
30
|
+
const keys = __importStar(require("./keys"));
|
|
31
|
+
/**
|
|
32
|
+
* Buffers what a process observes and flushes it into the introspection
|
|
33
|
+
* components.
|
|
34
|
+
*
|
|
35
|
+
* Nothing here is on the critical path. Reaching the explorer is a discovery,
|
|
36
|
+
* which waits as long as it takes, so the collector never holds up a component
|
|
37
|
+
* starting, running or stopping: it buffers until the connection is there, and
|
|
38
|
+
* when it has to choose it gives up on the data rather than on the application.
|
|
39
|
+
*/
|
|
40
|
+
class Reporter extends core_1.Connector {
|
|
41
|
+
boot;
|
|
42
|
+
options;
|
|
43
|
+
nodes = new Map();
|
|
44
|
+
edges = new Map();
|
|
45
|
+
/** Holds a remote only once it is connected and usable. */
|
|
46
|
+
remotes = {};
|
|
47
|
+
timer = null;
|
|
48
|
+
flushing = null;
|
|
49
|
+
acquiring = false;
|
|
50
|
+
dropped = 0;
|
|
51
|
+
constructor(boot, options) {
|
|
52
|
+
super();
|
|
53
|
+
this.boot = boot;
|
|
54
|
+
this.options = options;
|
|
55
|
+
}
|
|
56
|
+
/** The static description of a component. */
|
|
57
|
+
expose(node) {
|
|
58
|
+
this.nodes.set(keys.node(node.namespace, node.component), node);
|
|
59
|
+
void this.flush();
|
|
60
|
+
}
|
|
61
|
+
/** A call between two components. */
|
|
62
|
+
observe(observed) {
|
|
63
|
+
const id = keys.edge(observed.src, observed.dst);
|
|
64
|
+
const edge = this.edges.get(id);
|
|
65
|
+
if (edge === undefined) {
|
|
66
|
+
/*
|
|
67
|
+
* `source` arrives over the wire, so the number of distinct edges a process
|
|
68
|
+
* can hold has to be bounded regardless of what peers send — and of whether
|
|
69
|
+
* anyone is there to take them.
|
|
70
|
+
*/
|
|
71
|
+
if (this.edges.size >= const_1.MAX_EDGES) {
|
|
72
|
+
this.dropped++;
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
this.edges.set(id, observed);
|
|
76
|
+
}
|
|
77
|
+
else if (observed.sample !== undefined)
|
|
78
|
+
edge.sample = observed.sample;
|
|
79
|
+
if (this.edges.size >= this.options.threshold)
|
|
80
|
+
void this.flush();
|
|
81
|
+
}
|
|
82
|
+
async open() {
|
|
83
|
+
// deliberately not awaited: the explorer may not be there yet, or at all
|
|
84
|
+
this.acquire();
|
|
85
|
+
this.timer = setInterval(() => void this.flush(), this.options.interval * 1000);
|
|
86
|
+
this.timer.unref();
|
|
87
|
+
}
|
|
88
|
+
async close() {
|
|
89
|
+
if (this.timer !== null) {
|
|
90
|
+
clearInterval(this.timer);
|
|
91
|
+
this.timer = null;
|
|
92
|
+
}
|
|
93
|
+
await this.flushing;
|
|
94
|
+
if (!this.ready()) {
|
|
95
|
+
this.discard('the explorer was never reached');
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
// the remotes are still up: dependencies are disconnected after this returns
|
|
99
|
+
await this.dispatch().catch((error) => {
|
|
100
|
+
openspan_1.console.debug('Introspection final flush failed', { message: error.message });
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
ready() {
|
|
104
|
+
return const_1.NODES in this.remotes && const_1.EDGES in this.remotes;
|
|
105
|
+
}
|
|
106
|
+
async flush() {
|
|
107
|
+
// a dispatch in flight keeps observations buffered, they join the next batch
|
|
108
|
+
if (this.flushing !== null)
|
|
109
|
+
return;
|
|
110
|
+
if (this.nodes.size === 0 && this.edges.size === 0)
|
|
111
|
+
return;
|
|
112
|
+
if (!this.ready()) {
|
|
113
|
+
this.acquire();
|
|
114
|
+
if (this.edges.size >= const_1.MAX_EDGES)
|
|
115
|
+
this.discard('the explorer is not reachable');
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
this.flushing = this.dispatch()
|
|
119
|
+
.catch((error) => {
|
|
120
|
+
openspan_1.console.debug('Introspection flush failed', { message: error.message });
|
|
121
|
+
})
|
|
122
|
+
.finally(() => {
|
|
123
|
+
this.flushing = null;
|
|
124
|
+
});
|
|
125
|
+
await this.flushing;
|
|
126
|
+
}
|
|
127
|
+
discard(reason) {
|
|
128
|
+
const nodes = this.nodes.size;
|
|
129
|
+
const edges = this.edges.size + this.dropped;
|
|
130
|
+
this.nodes.clear();
|
|
131
|
+
this.edges.clear();
|
|
132
|
+
this.dropped = 0;
|
|
133
|
+
if (nodes === 0 && edges === 0)
|
|
134
|
+
return;
|
|
135
|
+
openspan_1.console.warn(`Introspection data discarded, ${reason}`, { nodes, edges });
|
|
136
|
+
}
|
|
137
|
+
async dispatch() {
|
|
138
|
+
const nodes = [...this.nodes.entries()];
|
|
139
|
+
const edges = [...this.edges.entries()];
|
|
140
|
+
this.nodes.clear();
|
|
141
|
+
this.edges.clear();
|
|
142
|
+
if (this.dropped > 0) {
|
|
143
|
+
openspan_1.console.warn('Introspection edges dropped', { dropped: this.dropped, limit: const_1.MAX_EDGES });
|
|
144
|
+
this.dropped = 0;
|
|
145
|
+
}
|
|
146
|
+
await Promise.all([
|
|
147
|
+
this.merge(const_1.NODES, 'nodes', nodes),
|
|
148
|
+
this.merge(const_1.EDGES, 'edges', edges)
|
|
149
|
+
]);
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* A mass transition: every affected object is acquired and committed at once,
|
|
153
|
+
* so a flush is one call per component whatever it carries.
|
|
154
|
+
*/
|
|
155
|
+
async merge(name, property, observed) {
|
|
156
|
+
if (observed.length === 0)
|
|
157
|
+
return;
|
|
158
|
+
const objects = {};
|
|
159
|
+
for (const [id, object] of observed)
|
|
160
|
+
objects[id] = object;
|
|
161
|
+
await this.remotes[name].invoke('merge', {
|
|
162
|
+
query: { ids: observed.map(([id]) => id) },
|
|
163
|
+
input: { [property]: objects },
|
|
164
|
+
task: true
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
/** Runs in the background: discovery waits for the explorer as long as it takes. */
|
|
168
|
+
acquire() {
|
|
169
|
+
if (this.acquiring)
|
|
170
|
+
return;
|
|
171
|
+
this.acquiring = true;
|
|
172
|
+
void this.reach().catch((error) => {
|
|
173
|
+
this.acquiring = false;
|
|
174
|
+
openspan_1.console.error('Introspection cannot reach its explorer', { message: error.message });
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
async reach() {
|
|
178
|
+
await Promise.all([const_1.NODES, const_1.EDGES].map(async (name) => {
|
|
179
|
+
const remote = await this.boot.remote(new core_1.Locator(name, const_1.NAMESPACE));
|
|
180
|
+
this.depends(remote);
|
|
181
|
+
await remote.connect();
|
|
182
|
+
this.remotes[name] = remote;
|
|
183
|
+
}));
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
exports.Reporter = Reporter;
|
|
187
|
+
//# sourceMappingURL=Reporter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Reporter.js","sourceRoot":"","sources":["../source/Reporter.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { Connector } from '@toa.io/core';
|
|
2
|
+
import type { Reporter } from './Reporter';
|
|
3
|
+
import type { Node } from './model';
|
|
4
|
+
/**
|
|
5
|
+
* Announces the static description of a component.
|
|
6
|
+
*
|
|
7
|
+
* Delivery is guaranteed, so the repeat is not about reliability — it keeps
|
|
8
|
+
* `_updated` fresh, which is how a removed component fades off the map.
|
|
9
|
+
*/
|
|
10
|
+
export declare class Tenant extends Connector {
|
|
11
|
+
private readonly reporter;
|
|
12
|
+
private readonly node;
|
|
13
|
+
private stopped;
|
|
14
|
+
constructor(reporter: Reporter, node: Node);
|
|
15
|
+
protected open(): Promise<void>;
|
|
16
|
+
protected dispose(): void;
|
|
17
|
+
private announce;
|
|
18
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Tenant = void 0;
|
|
4
|
+
const promises_1 = require("node:timers/promises");
|
|
5
|
+
const core_1 = require("@toa.io/core");
|
|
6
|
+
const const_1 = require("./const");
|
|
7
|
+
/**
|
|
8
|
+
* Announces the static description of a component.
|
|
9
|
+
*
|
|
10
|
+
* Delivery is guaranteed, so the repeat is not about reliability — it keeps
|
|
11
|
+
* `_updated` fresh, which is how a removed component fades off the map.
|
|
12
|
+
*/
|
|
13
|
+
class Tenant extends core_1.Connector {
|
|
14
|
+
reporter;
|
|
15
|
+
node;
|
|
16
|
+
stopped = false;
|
|
17
|
+
constructor(reporter, node) {
|
|
18
|
+
super();
|
|
19
|
+
this.reporter = reporter;
|
|
20
|
+
this.node = node;
|
|
21
|
+
this.depends(reporter);
|
|
22
|
+
}
|
|
23
|
+
async open() {
|
|
24
|
+
this.reporter.expose(this.node);
|
|
25
|
+
void this.announce();
|
|
26
|
+
}
|
|
27
|
+
dispose() {
|
|
28
|
+
this.stopped = true;
|
|
29
|
+
}
|
|
30
|
+
async announce() {
|
|
31
|
+
while (!this.stopped) {
|
|
32
|
+
await (0, promises_1.setTimeout)(const_1.ANNOUNCE_INTERVAL, undefined, { ref: false });
|
|
33
|
+
if (this.stopped)
|
|
34
|
+
break;
|
|
35
|
+
this.reporter.expose(this.node);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
exports.Tenant = Tenant;
|
|
40
|
+
//# sourceMappingURL=Tenant.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Tenant.js","sourceRoot":"","sources":["../source/Tenant.ts"],"names":[],"mappings":";;;AAAA,mDAAiD;AACjD,uCAAwC;AACxC,mCAA2C;AAI3C;;;;;GAKG;AACH,MAAa,MAAO,SAAQ,gBAAS;IAClB,QAAQ,CAAU;IAClB,IAAI,CAAM;IACnB,OAAO,GAAG,KAAK,CAAA;IAEvB,YAAoB,QAAkB,EAAE,IAAU;QAChD,KAAK,EAAE,CAAA;QAEP,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAA;QACxB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAA;QAEhB,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAA;IACxB,CAAC;IAEkB,KAAK,CAAC,IAAI;QAC3B,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;QAE/B,KAAK,IAAI,CAAC,QAAQ,EAAE,CAAA;IACtB,CAAC;IAEkB,OAAO;QACxB,IAAI,CAAC,OAAO,GAAG,IAAI,CAAA;IACrB,CAAC;IAEO,KAAK,CAAC,QAAQ;QACpB,OAAO,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC;YACrB,MAAM,IAAA,qBAAU,EAAC,yBAAiB,EAAE,SAAS,EAAE,EAAE,GAAG,EAAE,KAAK,EAAE,CAAC,CAAA;YAE9D,IAAI,IAAI,CAAC,OAAO;gBACd,MAAK;YAEP,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;QACjC,CAAC;IACH,CAAC;CACF;AAlCD,wBAkCC"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { Connector } from '@toa.io/core';
|
|
2
|
+
/**
|
|
3
|
+
* Serves the introspection 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. `/introspection/*` is left alone: that is the components' 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
|
+
}
|
package/transpiled/UI.js
ADDED
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
19
|
+
if (mod && mod.__esModule) return mod;
|
|
20
|
+
var result = {};
|
|
21
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
22
|
+
__setModuleDefault(result, mod);
|
|
23
|
+
return result;
|
|
24
|
+
};
|
|
25
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
26
|
+
exports.UI = void 0;
|
|
27
|
+
const fs = __importStar(require("node:fs"));
|
|
28
|
+
const http = __importStar(require("node:http"));
|
|
29
|
+
const path = __importStar(require("node:path"));
|
|
30
|
+
const core_1 = require("@toa.io/core");
|
|
31
|
+
const openspan_1 = require("openspan");
|
|
32
|
+
const const_1 = require("./const");
|
|
33
|
+
/**
|
|
34
|
+
* Serves the introspection UI: the directory `ui` builds, and nothing else.
|
|
35
|
+
*
|
|
36
|
+
* The page is a single-page application whose mount path is baked into the bundle,
|
|
37
|
+
* so this server routes relative to `UI_PATH` — which is also what the ingress
|
|
38
|
+
* forwards. `/introspection/*` is left alone: that is the components' own API.
|
|
39
|
+
*/
|
|
40
|
+
class UI extends core_1.Connector {
|
|
41
|
+
server = http.createServer();
|
|
42
|
+
port;
|
|
43
|
+
root;
|
|
44
|
+
constructor(port, root = SITE) {
|
|
45
|
+
super();
|
|
46
|
+
this.port = port;
|
|
47
|
+
this.root = root;
|
|
48
|
+
this.server.on('request', (request, response) => {
|
|
49
|
+
this.listen(request, response);
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
async open() {
|
|
53
|
+
/*
|
|
54
|
+
* A taken port is a real error: uniqueness across services is settled at export
|
|
55
|
+
* time, so nothing here has to negotiate for one.
|
|
56
|
+
*/
|
|
57
|
+
await new Promise((resolve, reject) => {
|
|
58
|
+
const failed = (error) => {
|
|
59
|
+
this.server.off('listening', listening);
|
|
60
|
+
reject(error);
|
|
61
|
+
};
|
|
62
|
+
const listening = () => {
|
|
63
|
+
this.server.off('error', failed);
|
|
64
|
+
resolve();
|
|
65
|
+
};
|
|
66
|
+
this.server.once('error', failed);
|
|
67
|
+
this.server.once('listening', listening);
|
|
68
|
+
this.server.listen(this.port);
|
|
69
|
+
});
|
|
70
|
+
if (!isFile(path.join(this.root, 'index.html')))
|
|
71
|
+
openspan_1.console.warn('Introspection UI is not built', { root: this.root });
|
|
72
|
+
openspan_1.console.info('Introspection UI started', { port: this.port, path: const_1.UI_PATH + '/' });
|
|
73
|
+
}
|
|
74
|
+
async close() {
|
|
75
|
+
const closed = new Promise((resolve) => this.server.once('close', () => {
|
|
76
|
+
resolve();
|
|
77
|
+
}));
|
|
78
|
+
this.server.close();
|
|
79
|
+
this.server.closeAllConnections();
|
|
80
|
+
await closed;
|
|
81
|
+
}
|
|
82
|
+
listen(request, response) {
|
|
83
|
+
void this.respond(request, response).catch((error) => {
|
|
84
|
+
openspan_1.console.error('Introspection UI failure', { message: error.message });
|
|
85
|
+
if (!response.writableEnded)
|
|
86
|
+
response.writeHead(500).end();
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
async respond(request, response) {
|
|
90
|
+
if (request.method !== 'GET' && request.method !== 'HEAD') {
|
|
91
|
+
response.writeHead(405, { allow: 'GET, HEAD' }).end();
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
const pathname = decode(request.url ?? '/');
|
|
95
|
+
if (pathname === null) {
|
|
96
|
+
response.writeHead(400).end();
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
// the port is the explorer's alone, so the root is the way in
|
|
100
|
+
if (pathname === '/') {
|
|
101
|
+
response.writeHead(302, { location: const_1.UI_PATH + '/' }).end();
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
const file = this.resolve(pathname);
|
|
105
|
+
if (file === null)
|
|
106
|
+
response.writeHead(404).end();
|
|
107
|
+
else
|
|
108
|
+
await this.send(file, request, response);
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* The file a request lands on, or `null` when nothing does. A path that exists is
|
|
112
|
+
* served as it is; anything else that could be a route falls back to the page,
|
|
113
|
+
* because the client router — not this server — knows what routes there are.
|
|
114
|
+
*/
|
|
115
|
+
resolve(pathname) {
|
|
116
|
+
if (!pathname.startsWith(const_1.UI_PATH))
|
|
117
|
+
return null;
|
|
118
|
+
const relative = pathname.slice(const_1.UI_PATH.length);
|
|
119
|
+
if (relative !== '' && !relative.startsWith('/'))
|
|
120
|
+
return null;
|
|
121
|
+
const file = path.join(this.root, relative);
|
|
122
|
+
if (file !== this.root && !file.startsWith(this.root + path.sep))
|
|
123
|
+
return null;
|
|
124
|
+
if (isFile(file))
|
|
125
|
+
return file;
|
|
126
|
+
/*
|
|
127
|
+
* A missing asset is missing, but a route can look like one: `identity.passkeys` is a
|
|
128
|
+
* component, not a file with an extension. What this server would have served is what
|
|
129
|
+
* it knows how to serve, so anything else is a route and falls back to the page — as
|
|
130
|
+
* does anything ending in a slash, which is no name for a file at all.
|
|
131
|
+
*/
|
|
132
|
+
const asset = !relative.endsWith('/') && path.extname(relative) in TYPES;
|
|
133
|
+
return asset ? null : path.join(this.root, 'index.html');
|
|
134
|
+
}
|
|
135
|
+
async send(file, request, response) {
|
|
136
|
+
const stats = await fs.promises.stat(file).catch(() => null);
|
|
137
|
+
if (stats === null) {
|
|
138
|
+
response.writeHead(503, { 'content-type': 'text/plain' })
|
|
139
|
+
.end('The introspection UI is not built. Run `npm run build:ui`.\n');
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
response.writeHead(200, {
|
|
143
|
+
'content-type': TYPES[path.extname(file)] ?? 'application/octet-stream',
|
|
144
|
+
'content-length': stats.size,
|
|
145
|
+
'cache-control': file.startsWith(path.join(this.root, IMMUTABLE)) ? FOREVER : 'no-cache'
|
|
146
|
+
});
|
|
147
|
+
if (request.method === 'HEAD')
|
|
148
|
+
response.end();
|
|
149
|
+
else
|
|
150
|
+
fs.createReadStream(file).pipe(response);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
exports.UI = UI;
|
|
154
|
+
function decode(url) {
|
|
155
|
+
try {
|
|
156
|
+
return decodeURIComponent(url.split('?')[0]);
|
|
157
|
+
}
|
|
158
|
+
catch {
|
|
159
|
+
return null;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
function isFile(file) {
|
|
163
|
+
return fs.existsSync(file) && fs.statSync(file).isFile();
|
|
164
|
+
}
|
|
165
|
+
/** Where `npm run build:ui` puts the page, from both `source` and `transpiled`. */
|
|
166
|
+
const SITE = path.resolve(__dirname, '..', 'ui', 'dist');
|
|
167
|
+
/** Assets under this prefix carry their build hash in the name. */
|
|
168
|
+
const IMMUTABLE = path.join('_app', 'immutable');
|
|
169
|
+
const FOREVER = 'public, max-age=31536000, immutable';
|
|
170
|
+
const TYPES = {
|
|
171
|
+
'.css': 'text/css; charset=utf-8',
|
|
172
|
+
'.html': 'text/html; charset=utf-8',
|
|
173
|
+
'.ico': 'image/x-icon',
|
|
174
|
+
'.jpg': 'image/jpeg',
|
|
175
|
+
'.js': 'text/javascript; charset=utf-8',
|
|
176
|
+
'.json': 'application/json; charset=utf-8',
|
|
177
|
+
'.map': 'application/json; charset=utf-8',
|
|
178
|
+
'.png': 'image/png',
|
|
179
|
+
'.svg': 'image/svg+xml',
|
|
180
|
+
'.txt': 'text/plain; charset=utf-8',
|
|
181
|
+
'.webmanifest': 'application/manifest+json',
|
|
182
|
+
'.webp': 'image/webp',
|
|
183
|
+
'.woff': 'font/woff',
|
|
184
|
+
'.woff2': 'font/woff2'
|
|
185
|
+
};
|
|
186
|
+
//# sourceMappingURL=UI.js.map
|