@benzenejs/mesh-aggregator 0.0.0-stage → 0.1.0-beta.2

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.
@@ -0,0 +1,25 @@
1
+ /** Port of Benzene.Mesh.Aggregator.IMeshArtifactStore. */
2
+ import { ServiceToken } from '@benzenejs/abstractions';
3
+ /**
4
+ * Publishes (and reads back) the JSON artifacts a `MeshAggregator` produces.
5
+ *
6
+ * Kept as a small port so a blob-storage adapter (S3, Azure Blob) can be added later as a drop-in
7
+ * implementation of this interface, without changing `MeshAggregator`'s own logic. Only a local-disk
8
+ * implementation (`FileSystemMeshArtifactStore`) ships in this package.
9
+ */
10
+ export interface IMeshArtifactStore {
11
+ /**
12
+ * Writes `content` to `relativePath`, creating or overwriting it.
13
+ * @param relativePath A path relative to the store's root, e.g. `"manifest.json"` or `"services/orders-api.json"`.
14
+ * @param content The content to write.
15
+ */
16
+ publishAsync(relativePath: string, content: string): Promise<void>;
17
+ /**
18
+ * Reads the content previously published at `relativePath`, if any.
19
+ * @param relativePath A path relative to the store's root.
20
+ * @returns The content, or `undefined` if nothing has been published at that path.
21
+ */
22
+ tryReadAsync(relativePath: string): Promise<string | undefined>;
23
+ }
24
+ /** Container token: the aggregator resolves the configured artifact store by this interface. */
25
+ export declare const IMeshArtifactStore: ServiceToken<IMeshArtifactStore>;
@@ -0,0 +1,7 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.IMeshArtifactStore = void 0;
4
+ /** Port of Benzene.Mesh.Aggregator.IMeshArtifactStore. */
5
+ const abstractions_1 = require("@benzenejs/abstractions");
6
+ /** Container token: the aggregator resolves the configured artifact store by this interface. */
7
+ exports.IMeshArtifactStore = (0, abstractions_1.serviceToken)('IMeshArtifactStore');
@@ -0,0 +1,44 @@
1
+ /** Port of Benzene.Mesh.Aggregator.IMeshServiceSource. */
2
+ import { ServiceToken } from '@benzenejs/abstractions';
3
+ import { MeshServiceRegistryEntry } from '@benzenejs/mesh-contracts';
4
+ /**
5
+ * Fetches a registered service's spec and health documents however fits that service's
6
+ * `MeshServiceRegistryEntry.source` - HTTP (`HttpMeshServiceSource`, the only implementation this package
7
+ * ships), an AWS Lambda Invoke, or any other transport an adapter package chooses to add. `MeshAggregator`
8
+ * is decoupled from any one transport by depending on this port rather than a `fetch`/`HttpClient` directly.
9
+ *
10
+ * `CancellationToken` -> optional `AbortSignal`. C#'s default-interface-method `TryFetchSpecAsync` (returns
11
+ * a completed `null` task) has no TS interface default; sources that don't override it are handled by the
12
+ * aggregator treating a missing method as "not supported" - so it is declared optional here.
13
+ */
14
+ export interface IMeshServiceSource {
15
+ /**
16
+ * The `MeshServiceRegistryEntry.source` value this source answers for (see `MeshServiceSource`).
17
+ * Matched case-insensitively.
18
+ */
19
+ readonly key: string;
20
+ /**
21
+ * Fetches `entry`'s spec document, verbatim, as raw JSON text. Should throw on failure (connection
22
+ * error, timeout, non-success response) rather than returning nothing - `MeshAggregator` catches and
23
+ * records the exception's type name.
24
+ */
25
+ fetchSpecAsync(entry: MeshServiceRegistryEntry, cancellationToken?: AbortSignal): Promise<string>;
26
+ /**
27
+ * Fetches `entry`'s health check response, verbatim, as raw JSON text (to be deserialized by the
28
+ * caller). Should throw only on a genuine fetch failure - an unhealthy-but-successfully-fetched response
29
+ * (e.g. HTTP 503 with a valid body) must still be returned, not treated as a failure, so it can be
30
+ * distinguished from a truly unreachable service.
31
+ */
32
+ fetchHealthAsync(entry: MeshServiceRegistryEntry, cancellationToken?: AbortSignal): Promise<string>;
33
+ /**
34
+ * Fetches `entry`'s spec in an alternate representation (e.g. `"asyncapi"` - the same `spec` endpoint's
35
+ * other `type`), or `undefined` when this source can't serve that type. Best-effort and additive: unlike
36
+ * {@link fetchSpecAsync} (whose failure marks the service unreachable), a source that doesn't support
37
+ * alternate spec types just returns `undefined` and the aggregator omits that service from whatever
38
+ * artifact is built on it. Optional (C#'s default interface method returned `null`), so existing sources
39
+ * don't have to implement it.
40
+ */
41
+ tryFetchSpecAsync?(entry: MeshServiceRegistryEntry, specType: string, cancellationToken?: AbortSignal): Promise<string | undefined>;
42
+ }
43
+ /** Container token: sources are registered and collected via `getServices(IMeshServiceSource)`. */
44
+ export declare const IMeshServiceSource: ServiceToken<IMeshServiceSource>;
@@ -0,0 +1,7 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.IMeshServiceSource = void 0;
4
+ /** Port of Benzene.Mesh.Aggregator.IMeshServiceSource. */
5
+ const abstractions_1 = require("@benzenejs/abstractions");
6
+ /** Container token: sources are registered and collected via `getServices(IMeshServiceSource)`. */
7
+ exports.IMeshServiceSource = (0, abstractions_1.serviceToken)('IMeshServiceSource');
@@ -0,0 +1,20 @@
1
+ /** Port of Benzene.Mesh.Aggregator.MeshAggregateMessageHandler. */
2
+ import { IBenzeneResultOf, VoidResult } from '@benzenejs/abstractions';
3
+ import { IMessageHandler } from '@benzenejs/abstractions-message-handlers';
4
+ import { MeshManifest, MeshServiceRegistry } from '@benzenejs/mesh-contracts';
5
+ import { MeshAggregator } from './MeshAggregator';
6
+ /**
7
+ * Exposes `MeshAggregator.runOnceAsync` as a Benzene message handler - reachable on whatever transport the
8
+ * host already runs (HTTP, a scheduled invocation, a queue message) with no bespoke hosting code.
9
+ *
10
+ * The C# `[HttpEndpoint("POST", "/mesh/aggregate")]` + `[Message("benzene:mesh:aggregate")]` attributes are declared
11
+ * in `addMeshAggregator` (as registered `IHttpEndpointDefinition`/`IMessageHandlerDefinition`s) rather than
12
+ * class decorators - the port's Extensions-registration convention, since JS has no assembly scan.
13
+ * `Void` request -> `VoidResult`.
14
+ */
15
+ export declare class MeshAggregateMessageHandler implements IMessageHandler<VoidResult, MeshManifest> {
16
+ private readonly aggregator;
17
+ private readonly registry;
18
+ constructor(aggregator: MeshAggregator, registry: MeshServiceRegistry);
19
+ handleAsync(_request: VoidResult): Promise<IBenzeneResultOf<MeshManifest>>;
20
+ }
@@ -0,0 +1,26 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MeshAggregateMessageHandler = void 0;
4
+ const results_1 = require("@benzenejs/results");
5
+ /**
6
+ * Exposes `MeshAggregator.runOnceAsync` as a Benzene message handler - reachable on whatever transport the
7
+ * host already runs (HTTP, a scheduled invocation, a queue message) with no bespoke hosting code.
8
+ *
9
+ * The C# `[HttpEndpoint("POST", "/mesh/aggregate")]` + `[Message("benzene:mesh:aggregate")]` attributes are declared
10
+ * in `addMeshAggregator` (as registered `IHttpEndpointDefinition`/`IMessageHandlerDefinition`s) rather than
11
+ * class decorators - the port's Extensions-registration convention, since JS has no assembly scan.
12
+ * `Void` request -> `VoidResult`.
13
+ */
14
+ class MeshAggregateMessageHandler {
15
+ aggregator;
16
+ registry;
17
+ constructor(aggregator, registry) {
18
+ this.aggregator = aggregator;
19
+ this.registry = registry;
20
+ }
21
+ async handleAsync(_request) {
22
+ const manifest = await this.aggregator.runOnceAsync(this.registry);
23
+ return results_1.BenzeneResult.ok(manifest);
24
+ }
25
+ }
26
+ exports.MeshAggregateMessageHandler = MeshAggregateMessageHandler;
@@ -0,0 +1,67 @@
1
+ import { IMeshUsageSource, MeshManifest, MeshServiceRegistry } from '@benzenejs/mesh-contracts';
2
+ import { IMeshArtifactStore } from './IMeshArtifactStore';
3
+ import { IMeshServiceSource } from './IMeshServiceSource';
4
+ /**
5
+ * Polls every service in a `MeshServiceRegistry` for its spec and health documents, computes contract-drift,
6
+ * and publishes the resulting catalog to an `IMeshArtifactStore`. `System.Text.Json` (nodes + document) ->
7
+ * native `JSON.parse`/`JSON.stringify`; `DateTimeOffset` clock -> `() => number` (epoch ms); `Task.WhenAll`
8
+ * -> `Promise.all`; LINQ `GroupBy`/`ToDictionary` -> `Map`s.
9
+ */
10
+ export declare class MeshAggregator {
11
+ private readonly sources;
12
+ private readonly store;
13
+ private readonly clock;
14
+ private readonly usageSources;
15
+ /**
16
+ * @param sources Every registered `IMeshServiceSource`, keyed by `key` (case-insensitive) to resolve each
17
+ * entry's `source` against. An entry whose `source` has no matching source here is recorded as that
18
+ * service's own fetch error, not a run-wide failure.
19
+ * @param store Where generated catalog artifacts are published (and, for drift comparison, read back from).
20
+ * @param clock Supplies the current time (epoch ms); defaults to `Date.now`. Overridable for deterministic tests.
21
+ * @param usageSources Every registered `IMeshUsageSource`. Optional and empty by default: with none
22
+ * registered no `usage.json` is ever published, so the artifact's absence keeps meaning "no usage feed wired".
23
+ */
24
+ constructor(sources: Iterable<IMeshServiceSource>, store: IMeshArtifactStore, clock?: () => number, usageSources?: Iterable<IMeshUsageSource>);
25
+ /**
26
+ * Polls every registered service once, publishing a `services/{name}.json` snapshot per service and a
27
+ * top-level `manifest.json` summarizing all of them. A single service's spec/health fetch failing (or
28
+ * timing out) does not prevent the rest from being processed and published. Services are polled
29
+ * concurrently, so one slow service adds to the run's total time only up to its own timeout.
30
+ */
31
+ runOnceAsync(registry: MeshServiceRegistry): Promise<MeshManifest>;
32
+ /**
33
+ * Polls every registered usage adapter (bounded by the per-service timeout each; a throwing/timing-out
34
+ * source contributes nothing rather than failing the run) and merges the reports into one `MeshUsage`.
35
+ * Returns `undefined` when no source reported.
36
+ */
37
+ private fetchUsageAsync;
38
+ private fetchOneUsageAsync;
39
+ /**
40
+ * Builds the composite AsyncAPI document from every service that returned an AsyncAPI doc, passing each
41
+ * service's reserved-topic ids (from its benzene spec) so utility channels are dropped.
42
+ */
43
+ private buildCompositeAsyncApi;
44
+ /**
45
+ * Derives the structural topology: for every domain topic a service declares it *sends* (the spec's
46
+ * `events` and `sends`), an edge to every service that *handles* it (the spec's `requests`). Where the merged `usage`
47
+ * feed can attribute a topic's observed traffic to a specific edge *unambiguously*, that edge also carries
48
+ * a usage-derived req/min and (when the outcome is classifiable) error rate.
49
+ */
50
+ private buildTopology;
51
+ /**
52
+ * Builds the cross-service topic catalog: every (topic, version) pair seen anywhere in the fleet, who
53
+ * consumes it (spec `requests`) and who produces it (spec `events`), plus an informational status.
54
+ */
55
+ private buildTopicCatalog;
56
+ /**
57
+ * Annotates the freshly-built catalog with what changed since the previous run, by reading back the
58
+ * store's own last `topics.json`. A first run, or an unreadable/unparseable previous catalog, claims no
59
+ * changes at all - never a wall of "added" noise, and never a failed run.
60
+ */
61
+ private applyCatalogDiffAsync;
62
+ private buildServiceAsync;
63
+ private fetchSpecAsync;
64
+ private fetchHealthAsync;
65
+ private fetchAsyncApiAsync;
66
+ private resolveSource;
67
+ }