@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.
- package/dist/ArtifactStoreMeshReportPublisher.d.ts +18 -0
- package/dist/ArtifactStoreMeshReportPublisher.js +25 -0
- package/dist/AsyncApiCompositor.d.ts +21 -0
- package/dist/AsyncApiCompositor.js +285 -0
- package/dist/Extensions.d.ts +19 -0
- package/dist/Extensions.js +56 -0
- package/dist/FileSystemMeshArtifactStore.d.ts +25 -0
- package/dist/FileSystemMeshArtifactStore.js +62 -0
- package/dist/HttpMeshServiceSource.d.ts +37 -0
- package/dist/HttpMeshServiceSource.js +84 -0
- package/dist/IMeshArtifactStore.d.ts +25 -0
- package/dist/IMeshArtifactStore.js +7 -0
- package/dist/IMeshServiceSource.d.ts +44 -0
- package/dist/IMeshServiceSource.js +7 -0
- package/dist/MeshAggregateMessageHandler.d.ts +20 -0
- package/dist/MeshAggregateMessageHandler.js +26 -0
- package/dist/MeshAggregator.d.ts +67 -0
- package/dist/MeshAggregator.js +766 -0
- package/dist/MeshAnnotationPublisher.d.ts +36 -0
- package/dist/MeshAnnotationPublisher.js +83 -0
- package/dist/MeshAnnotationsMessageHandler.d.ts +26 -0
- package/dist/MeshAnnotationsMessageHandler.js +50 -0
- package/dist/MeshReportMessageHandler.d.ts +15 -0
- package/dist/MeshReportMessageHandler.js +20 -0
- package/dist/MeshSnapshotBuilder.d.ts +24 -0
- package/dist/MeshSnapshotBuilder.js +55 -0
- package/dist/UnknownMeshServiceSource.d.ts +17 -0
- package/dist/UnknownMeshServiceSource.js +27 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.js +38 -0
- package/package.json +19 -4
- package/README.md +0 -3
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { MeshAnnotationThread } from '@benzenejs/mesh-contracts';
|
|
2
|
+
import { IMeshArtifactStore } from './IMeshArtifactStore';
|
|
3
|
+
/**
|
|
4
|
+
* Appends discussion notes to the `annotations.json` artifact - the write half of the mesh discussion
|
|
5
|
+
* feature. The read half is the artifact itself: it lives in the same `IMeshArtifactStore` as
|
|
6
|
+
* `manifest.json`, so any static host serves recorded discussion with zero backend, and only posting a new
|
|
7
|
+
* note needs this publisher's handler (`MeshAnnotationsMessageHandler`) running somewhere.
|
|
8
|
+
*
|
|
9
|
+
* Read-modify-write of the log is serialized per process with an async mutex (C#'s `SemaphoreSlim(1, 1)`
|
|
10
|
+
* -> a promise-chain lock; the single-threaded event loop still interleaves `await`s, so the lock is
|
|
11
|
+
* genuinely needed to stop two concurrent `addAsync` calls racing the read-modify-write). Concurrent
|
|
12
|
+
* writers in separate hosts are out of scope. An unparseable existing log starts fresh rather than failing
|
|
13
|
+
* - but never silently: the previous content is preserved to a timestamped sibling artifact first.
|
|
14
|
+
* `Guid.NewGuid().ToString("n")` -> `randomUUID()` with dashes stripped; `DateTimeOffset` -> epoch ms.
|
|
15
|
+
*/
|
|
16
|
+
export declare class MeshAnnotationPublisher {
|
|
17
|
+
private readonly store;
|
|
18
|
+
private readonly clock;
|
|
19
|
+
private readonly ids;
|
|
20
|
+
private writeLock;
|
|
21
|
+
/**
|
|
22
|
+
* @param store The artifact store the log lives in - the same one the aggregator publishes to.
|
|
23
|
+
* @param clock Supplies the current time (epoch ms); defaults to `Date.now`. Overridable for deterministic tests.
|
|
24
|
+
* @param ids Supplies new annotation ids; defaults to a dashless UUID. Overridable for deterministic tests.
|
|
25
|
+
*/
|
|
26
|
+
constructor(store: IMeshArtifactStore, clock?: () => number, ids?: () => string);
|
|
27
|
+
/**
|
|
28
|
+
* Appends one note to the log and republishes `annotations.json`, returning the annotated entity's full
|
|
29
|
+
* thread (oldest first) so a caller can render the discussion it just posted into. Inputs are assumed
|
|
30
|
+
* already validated (the handler's job).
|
|
31
|
+
*/
|
|
32
|
+
addAsync(entity: string, author: string, text: string): Promise<MeshAnnotationThread>;
|
|
33
|
+
/** Serializes read-modify-write against concurrent `addAsync` calls (the `SemaphoreSlim` port). */
|
|
34
|
+
private withLock;
|
|
35
|
+
private readLogAsync;
|
|
36
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.MeshAnnotationPublisher = void 0;
|
|
4
|
+
/** Port of Benzene.Mesh.Aggregator.MeshAnnotationPublisher. */
|
|
5
|
+
const node_crypto_1 = require("node:crypto");
|
|
6
|
+
const mesh_contracts_1 = require("@benzenejs/mesh-contracts");
|
|
7
|
+
const ArtifactPath = 'annotations.json';
|
|
8
|
+
/**
|
|
9
|
+
* Appends discussion notes to the `annotations.json` artifact - the write half of the mesh discussion
|
|
10
|
+
* feature. The read half is the artifact itself: it lives in the same `IMeshArtifactStore` as
|
|
11
|
+
* `manifest.json`, so any static host serves recorded discussion with zero backend, and only posting a new
|
|
12
|
+
* note needs this publisher's handler (`MeshAnnotationsMessageHandler`) running somewhere.
|
|
13
|
+
*
|
|
14
|
+
* Read-modify-write of the log is serialized per process with an async mutex (C#'s `SemaphoreSlim(1, 1)`
|
|
15
|
+
* -> a promise-chain lock; the single-threaded event loop still interleaves `await`s, so the lock is
|
|
16
|
+
* genuinely needed to stop two concurrent `addAsync` calls racing the read-modify-write). Concurrent
|
|
17
|
+
* writers in separate hosts are out of scope. An unparseable existing log starts fresh rather than failing
|
|
18
|
+
* - but never silently: the previous content is preserved to a timestamped sibling artifact first.
|
|
19
|
+
* `Guid.NewGuid().ToString("n")` -> `randomUUID()` with dashes stripped; `DateTimeOffset` -> epoch ms.
|
|
20
|
+
*/
|
|
21
|
+
class MeshAnnotationPublisher {
|
|
22
|
+
store;
|
|
23
|
+
clock;
|
|
24
|
+
ids;
|
|
25
|
+
writeLock = Promise.resolve();
|
|
26
|
+
/**
|
|
27
|
+
* @param store The artifact store the log lives in - the same one the aggregator publishes to.
|
|
28
|
+
* @param clock Supplies the current time (epoch ms); defaults to `Date.now`. Overridable for deterministic tests.
|
|
29
|
+
* @param ids Supplies new annotation ids; defaults to a dashless UUID. Overridable for deterministic tests.
|
|
30
|
+
*/
|
|
31
|
+
constructor(store, clock, ids) {
|
|
32
|
+
this.store = store;
|
|
33
|
+
this.clock = clock ?? (() => Date.now());
|
|
34
|
+
this.ids = ids ?? (() => (0, node_crypto_1.randomUUID)().replace(/-/g, ''));
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Appends one note to the log and republishes `annotations.json`, returning the annotated entity's full
|
|
38
|
+
* thread (oldest first) so a caller can render the discussion it just posted into. Inputs are assumed
|
|
39
|
+
* already validated (the handler's job).
|
|
40
|
+
*/
|
|
41
|
+
async addAsync(entity, author, text) {
|
|
42
|
+
return this.withLock(async () => {
|
|
43
|
+
const existing = await this.readLogAsync();
|
|
44
|
+
const annotation = new mesh_contracts_1.MeshAnnotation(this.ids(), entity, author, text, this.clock());
|
|
45
|
+
const annotations = [...existing, annotation];
|
|
46
|
+
await this.store.publishAsync(ArtifactPath, JSON.stringify(new mesh_contracts_1.MeshAnnotationLog(this.clock(), annotations), null, 2));
|
|
47
|
+
return new mesh_contracts_1.MeshAnnotationThread(entity, annotations.filter((a) => a.entity === entity).sort((a, b) => a.createdAtUtc - b.createdAtUtc));
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
/** Serializes read-modify-write against concurrent `addAsync` calls (the `SemaphoreSlim` port). */
|
|
51
|
+
withLock(action) {
|
|
52
|
+
const run = this.writeLock.then(action, action);
|
|
53
|
+
// Keep the chain alive but swallow errors so one failed write doesn't poison the lock for the next.
|
|
54
|
+
this.writeLock = run.then(() => undefined, () => undefined);
|
|
55
|
+
return run;
|
|
56
|
+
}
|
|
57
|
+
async readLogAsync() {
|
|
58
|
+
let json;
|
|
59
|
+
try {
|
|
60
|
+
json = await this.store.tryReadAsync(ArtifactPath);
|
|
61
|
+
if (json === undefined) {
|
|
62
|
+
return [];
|
|
63
|
+
}
|
|
64
|
+
const log = JSON.parse(json);
|
|
65
|
+
return log?.annotations ?? [];
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
// A corrupt log must not brick discussion forever - but user-authored notes are the one artifact
|
|
69
|
+
// that can't be regenerated from the fleet, so park the unreadable content aside (best-effort)
|
|
70
|
+
// instead of overwriting it.
|
|
71
|
+
if (json !== undefined) {
|
|
72
|
+
try {
|
|
73
|
+
await this.store.publishAsync(`annotations.unreadable-${Math.floor(this.clock() / 1000)}.json`, json);
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
// Preserving the corrupt log is best-effort only.
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return [];
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
exports.MeshAnnotationPublisher = MeshAnnotationPublisher;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/** Port of Benzene.Mesh.Aggregator.MeshAnnotationsMessageHandler. */
|
|
2
|
+
import { IBenzeneResultOf } from '@benzenejs/abstractions';
|
|
3
|
+
import { IMessageHandler } from '@benzenejs/abstractions-message-handlers';
|
|
4
|
+
import { MeshAnnotationRequest, MeshAnnotationThread } from '@benzenejs/mesh-contracts';
|
|
5
|
+
import { MeshAnnotationPublisher } from './MeshAnnotationPublisher';
|
|
6
|
+
/**
|
|
7
|
+
* The discussion write endpoint - attaches one note to one entity of the estate and returns that entity's
|
|
8
|
+
* full thread. Reachable on whatever transport the host already runs, the same dogfooded shape as
|
|
9
|
+
* `MeshReportMessageHandler`.
|
|
10
|
+
*
|
|
11
|
+
* Identity is deliberately out of scope: `MeshAnnotationRequest.author` is a self-declared display name, and
|
|
12
|
+
* authenticating who may post is the fronting gateway's job. The handler enforces only shape: required
|
|
13
|
+
* fields and size bounds, so one post can't bloat the artifact every reader downloads. `const` fields ->
|
|
14
|
+
* `static readonly`.
|
|
15
|
+
*/
|
|
16
|
+
export declare class MeshAnnotationsMessageHandler implements IMessageHandler<MeshAnnotationRequest, MeshAnnotationThread> {
|
|
17
|
+
private readonly publisher;
|
|
18
|
+
/** Maximum accepted length of `MeshAnnotationRequest.entity`. */
|
|
19
|
+
static readonly MaxEntityLength = 200;
|
|
20
|
+
/** Maximum accepted length of `MeshAnnotationRequest.author`. */
|
|
21
|
+
static readonly MaxAuthorLength = 80;
|
|
22
|
+
/** Maximum accepted length of `MeshAnnotationRequest.text`. */
|
|
23
|
+
static readonly MaxTextLength = 4000;
|
|
24
|
+
constructor(publisher: MeshAnnotationPublisher);
|
|
25
|
+
handleAsync(request: MeshAnnotationRequest): Promise<IBenzeneResultOf<MeshAnnotationThread>>;
|
|
26
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.MeshAnnotationsMessageHandler = void 0;
|
|
4
|
+
const results_1 = require("@benzenejs/results");
|
|
5
|
+
/**
|
|
6
|
+
* The discussion write endpoint - attaches one note to one entity of the estate and returns that entity's
|
|
7
|
+
* full thread. Reachable on whatever transport the host already runs, the same dogfooded shape as
|
|
8
|
+
* `MeshReportMessageHandler`.
|
|
9
|
+
*
|
|
10
|
+
* Identity is deliberately out of scope: `MeshAnnotationRequest.author` is a self-declared display name, and
|
|
11
|
+
* authenticating who may post is the fronting gateway's job. The handler enforces only shape: required
|
|
12
|
+
* fields and size bounds, so one post can't bloat the artifact every reader downloads. `const` fields ->
|
|
13
|
+
* `static readonly`.
|
|
14
|
+
*/
|
|
15
|
+
class MeshAnnotationsMessageHandler {
|
|
16
|
+
publisher;
|
|
17
|
+
/** Maximum accepted length of `MeshAnnotationRequest.entity`. */
|
|
18
|
+
static MaxEntityLength = 200;
|
|
19
|
+
/** Maximum accepted length of `MeshAnnotationRequest.author`. */
|
|
20
|
+
static MaxAuthorLength = 80;
|
|
21
|
+
/** Maximum accepted length of `MeshAnnotationRequest.text`. */
|
|
22
|
+
static MaxTextLength = 4000;
|
|
23
|
+
constructor(publisher) {
|
|
24
|
+
this.publisher = publisher;
|
|
25
|
+
}
|
|
26
|
+
async handleAsync(request) {
|
|
27
|
+
const entity = request?.entity?.trim();
|
|
28
|
+
const author = request?.author?.trim();
|
|
29
|
+
const text = request?.text?.trim();
|
|
30
|
+
if (entity === undefined || entity.length === 0) {
|
|
31
|
+
return results_1.BenzeneResult.badRequest('entity is required');
|
|
32
|
+
}
|
|
33
|
+
if (author === undefined || author.length === 0) {
|
|
34
|
+
return results_1.BenzeneResult.badRequest('author is required');
|
|
35
|
+
}
|
|
36
|
+
if (text === undefined || text.length === 0) {
|
|
37
|
+
return results_1.BenzeneResult.badRequest('text is required');
|
|
38
|
+
}
|
|
39
|
+
if (entity.length > MeshAnnotationsMessageHandler.MaxEntityLength ||
|
|
40
|
+
author.length > MeshAnnotationsMessageHandler.MaxAuthorLength ||
|
|
41
|
+
text.length > MeshAnnotationsMessageHandler.MaxTextLength) {
|
|
42
|
+
return results_1.BenzeneResult.badRequest(`bounds exceeded: entity <= ${MeshAnnotationsMessageHandler.MaxEntityLength}, ` +
|
|
43
|
+
`author <= ${MeshAnnotationsMessageHandler.MaxAuthorLength}, ` +
|
|
44
|
+
`text <= ${MeshAnnotationsMessageHandler.MaxTextLength} characters`);
|
|
45
|
+
}
|
|
46
|
+
const thread = await this.publisher.addAsync(entity, author, text);
|
|
47
|
+
return results_1.BenzeneResult.created(thread);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
exports.MeshAnnotationsMessageHandler = MeshAnnotationsMessageHandler;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/** Port of Benzene.Mesh.Aggregator.MeshReportMessageHandler. */
|
|
2
|
+
import { IMessageHandlerNoResponse } from '@benzenejs/abstractions-message-handlers';
|
|
3
|
+
import { IMeshReportPublisher, MeshServiceReport } from '@benzenejs/mesh-contracts';
|
|
4
|
+
/**
|
|
5
|
+
* The push/self-report ingestion endpoint - accepts a `MeshServiceReport` and hands it to whichever
|
|
6
|
+
* `IMeshReportPublisher` is registered (`ArtifactStoreMeshReportPublisher` by default, via
|
|
7
|
+
* `addMeshAggregator`). Reachable on whatever transport the host already runs, the same dogfooded shape as
|
|
8
|
+
* `MeshAggregateMessageHandler`. `IMessageHandler<MeshServiceReport>` (no response) ->
|
|
9
|
+
* `IMessageHandlerNoResponse<MeshServiceReport>`.
|
|
10
|
+
*/
|
|
11
|
+
export declare class MeshReportMessageHandler implements IMessageHandlerNoResponse<MeshServiceReport> {
|
|
12
|
+
private readonly publisher;
|
|
13
|
+
constructor(publisher: IMeshReportPublisher);
|
|
14
|
+
handleAsync(request: MeshServiceReport): Promise<void>;
|
|
15
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.MeshReportMessageHandler = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* The push/self-report ingestion endpoint - accepts a `MeshServiceReport` and hands it to whichever
|
|
6
|
+
* `IMeshReportPublisher` is registered (`ArtifactStoreMeshReportPublisher` by default, via
|
|
7
|
+
* `addMeshAggregator`). Reachable on whatever transport the host already runs, the same dogfooded shape as
|
|
8
|
+
* `MeshAggregateMessageHandler`. `IMessageHandler<MeshServiceReport>` (no response) ->
|
|
9
|
+
* `IMessageHandlerNoResponse<MeshServiceReport>`.
|
|
10
|
+
*/
|
|
11
|
+
class MeshReportMessageHandler {
|
|
12
|
+
publisher;
|
|
13
|
+
constructor(publisher) {
|
|
14
|
+
this.publisher = publisher;
|
|
15
|
+
}
|
|
16
|
+
handleAsync(request) {
|
|
17
|
+
return this.publisher.publishAsync(request);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
exports.MeshReportMessageHandler = MeshReportMessageHandler;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/** Port of Benzene.Mesh.Aggregator.MeshSnapshotBuilder. */
|
|
2
|
+
import { HealthCheckResponse } from '@benzenejs/health-checks-core';
|
|
3
|
+
import { MeshServiceSnapshot } from '@benzenejs/mesh-contracts';
|
|
4
|
+
import { IMeshArtifactStore } from './IMeshArtifactStore';
|
|
5
|
+
/**
|
|
6
|
+
* Builds a `MeshServiceSnapshot` from a spec/health fetch (or self-reported push), reading the spec's hash
|
|
7
|
+
* and comparing it against the previous run's via `IMeshArtifactStore`. The hash is the **served**
|
|
8
|
+
* `contractHash` of the service's Contract Document (contract-document.md §6.1) — the aggregator computes no
|
|
9
|
+
* hash of its own, so its drift flag means exactly what a generated client's drift check means. A spec
|
|
10
|
+
* without one (not a Contract Document, or none fetched) has no hash, and so no drift verdict. Shared by `MeshAggregator` (pulled
|
|
11
|
+
* snapshots) and any `IMeshReportPublisher` that turns a pushed `MeshServiceReport` into the same snapshot
|
|
12
|
+
* shape, so both compute `MeshServiceSnapshot.contractDrift` identically instead of two copies of the logic.
|
|
13
|
+
*
|
|
14
|
+
* The internal C# `static class` becomes a namespace of free functions (the port's "static class -> free
|
|
15
|
+
* functions" rule). Reading back the previous snapshot deserializes only the `specHash` field: `JSON.parse`
|
|
16
|
+
* yields a plain object with that camelCase field, so no class reconstruction is needed.
|
|
17
|
+
*/
|
|
18
|
+
export declare const MeshSnapshotBuilder: {
|
|
19
|
+
/**
|
|
20
|
+
* Builds a snapshot, reading the previous run's spec hash from `store` for drift comparison.
|
|
21
|
+
* @param error The exception type name from a failed fetch, or `undefined`.
|
|
22
|
+
*/
|
|
23
|
+
buildAsync(store: IMeshArtifactStore, name: string, fetchedAtUtc: number, specJson: string | undefined, health: HealthCheckResponse | undefined, error: string | undefined): Promise<MeshServiceSnapshot>;
|
|
24
|
+
};
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.MeshSnapshotBuilder = void 0;
|
|
4
|
+
const mesh_contracts_1 = require("@benzenejs/mesh-contracts");
|
|
5
|
+
/**
|
|
6
|
+
* Builds a `MeshServiceSnapshot` from a spec/health fetch (or self-reported push), reading the spec's hash
|
|
7
|
+
* and comparing it against the previous run's via `IMeshArtifactStore`. The hash is the **served**
|
|
8
|
+
* `contractHash` of the service's Contract Document (contract-document.md §6.1) — the aggregator computes no
|
|
9
|
+
* hash of its own, so its drift flag means exactly what a generated client's drift check means. A spec
|
|
10
|
+
* without one (not a Contract Document, or none fetched) has no hash, and so no drift verdict. Shared by `MeshAggregator` (pulled
|
|
11
|
+
* snapshots) and any `IMeshReportPublisher` that turns a pushed `MeshServiceReport` into the same snapshot
|
|
12
|
+
* shape, so both compute `MeshServiceSnapshot.contractDrift` identically instead of two copies of the logic.
|
|
13
|
+
*
|
|
14
|
+
* The internal C# `static class` becomes a namespace of free functions (the port's "static class -> free
|
|
15
|
+
* functions" rule). Reading back the previous snapshot deserializes only the `specHash` field: `JSON.parse`
|
|
16
|
+
* yields a plain object with that camelCase field, so no class reconstruction is needed.
|
|
17
|
+
*/
|
|
18
|
+
exports.MeshSnapshotBuilder = {
|
|
19
|
+
/**
|
|
20
|
+
* Builds a snapshot, reading the previous run's spec hash from `store` for drift comparison.
|
|
21
|
+
* @param error The exception type name from a failed fetch, or `undefined`.
|
|
22
|
+
*/
|
|
23
|
+
async buildAsync(store, name, fetchedAtUtc, specJson, health, error) {
|
|
24
|
+
const specHash = servedContractHash(specJson);
|
|
25
|
+
const previousSpecHash = await tryGetPreviousSpecHashAsync(store, name);
|
|
26
|
+
const contractDrift = specHash !== undefined && previousSpecHash !== undefined && previousSpecHash !== specHash;
|
|
27
|
+
return new mesh_contracts_1.MeshServiceSnapshot(name, fetchedAtUtc, specJson, specHash, previousSpecHash, contractDrift, health, error);
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
/** The served `contractHash` of a fetched spec, or `undefined` when there is none. */
|
|
31
|
+
function servedContractHash(specJson) {
|
|
32
|
+
if (specJson === undefined) {
|
|
33
|
+
return undefined;
|
|
34
|
+
}
|
|
35
|
+
try {
|
|
36
|
+
const parsed = JSON.parse(specJson);
|
|
37
|
+
return typeof parsed?.contractHash === 'string' ? parsed.contractHash : undefined;
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
return undefined;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
async function tryGetPreviousSpecHashAsync(store, serviceName) {
|
|
44
|
+
const previousJson = await store.tryReadAsync(`services/${serviceName}.json`);
|
|
45
|
+
if (previousJson === undefined) {
|
|
46
|
+
return undefined;
|
|
47
|
+
}
|
|
48
|
+
try {
|
|
49
|
+
const previous = JSON.parse(previousJson);
|
|
50
|
+
return previous?.specHash ?? undefined;
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
return undefined;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/** Port of Benzene.Mesh.Aggregator.UnknownMeshServiceSource. */
|
|
2
|
+
import { MeshServiceRegistryEntry } from '@benzenejs/mesh-contracts';
|
|
3
|
+
import { IMeshServiceSource } from './IMeshServiceSource';
|
|
4
|
+
/**
|
|
5
|
+
* Stand-in `IMeshServiceSource` returned when a `MeshServiceRegistryEntry.source` has no matching
|
|
6
|
+
* registered source - throws from both fetch methods so a misconfigured single entry surfaces as that
|
|
7
|
+
* service's own `unreachable`/error result (via the same try/catch every other fetch failure goes through
|
|
8
|
+
* in `MeshAggregator`), rather than crashing the whole aggregation run.
|
|
9
|
+
*/
|
|
10
|
+
export declare class UnknownMeshServiceSource implements IMeshServiceSource {
|
|
11
|
+
private readonly requestedSource;
|
|
12
|
+
readonly key: string;
|
|
13
|
+
constructor(requestedSource: string);
|
|
14
|
+
fetchSpecAsync(_entry: MeshServiceRegistryEntry, _cancellationToken?: AbortSignal): Promise<string>;
|
|
15
|
+
fetchHealthAsync(_entry: MeshServiceRegistryEntry, _cancellationToken?: AbortSignal): Promise<string>;
|
|
16
|
+
private notRegistered;
|
|
17
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.UnknownMeshServiceSource = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Stand-in `IMeshServiceSource` returned when a `MeshServiceRegistryEntry.source` has no matching
|
|
6
|
+
* registered source - throws from both fetch methods so a misconfigured single entry surfaces as that
|
|
7
|
+
* service's own `unreachable`/error result (via the same try/catch every other fetch failure goes through
|
|
8
|
+
* in `MeshAggregator`), rather than crashing the whole aggregation run.
|
|
9
|
+
*/
|
|
10
|
+
class UnknownMeshServiceSource {
|
|
11
|
+
requestedSource;
|
|
12
|
+
key;
|
|
13
|
+
constructor(requestedSource) {
|
|
14
|
+
this.requestedSource = requestedSource;
|
|
15
|
+
this.key = requestedSource;
|
|
16
|
+
}
|
|
17
|
+
fetchSpecAsync(_entry, _cancellationToken) {
|
|
18
|
+
throw this.notRegistered();
|
|
19
|
+
}
|
|
20
|
+
fetchHealthAsync(_entry, _cancellationToken) {
|
|
21
|
+
throw this.notRegistered();
|
|
22
|
+
}
|
|
23
|
+
notRegistered() {
|
|
24
|
+
return new Error(`No IMeshServiceSource registered for source "${this.requestedSource}".`);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
exports.UnknownMeshServiceSource = UnknownMeshServiceSource;
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Port of Benzene.Mesh.Aggregator - the polling mesh aggregator: `MeshAggregator` fetches every registered
|
|
3
|
+
* service's spec/health (via an `IMeshServiceSource`), computes contract-drift and a cross-service topic
|
|
4
|
+
* catalog/topology/composite-AsyncAPI, and publishes the artifacts to an `IMeshArtifactStore`
|
|
5
|
+
* (`FileSystemMeshArtifactStore` ships). Also the push path (`ArtifactStoreMeshReportPublisher` +
|
|
6
|
+
* `MeshReportMessageHandler`), the discussion write path (`MeshAnnotationPublisher` +
|
|
7
|
+
* `MeshAnnotationsMessageHandler`), and `addMeshAggregator` wiring.
|
|
8
|
+
*/
|
|
9
|
+
export * from './IMeshArtifactStore';
|
|
10
|
+
export * from './IMeshServiceSource';
|
|
11
|
+
export * from './UnknownMeshServiceSource';
|
|
12
|
+
export * from './FileSystemMeshArtifactStore';
|
|
13
|
+
export * from './HttpMeshServiceSource';
|
|
14
|
+
export * from './MeshSnapshotBuilder';
|
|
15
|
+
export * from './ArtifactStoreMeshReportPublisher';
|
|
16
|
+
export * from './MeshAnnotationPublisher';
|
|
17
|
+
export * from './AsyncApiCompositor';
|
|
18
|
+
export * from './MeshAggregator';
|
|
19
|
+
export * from './MeshAggregateMessageHandler';
|
|
20
|
+
export * from './MeshReportMessageHandler';
|
|
21
|
+
export * from './MeshAnnotationsMessageHandler';
|
|
22
|
+
export * from './Extensions';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
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 __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
/**
|
|
18
|
+
* Port of Benzene.Mesh.Aggregator - the polling mesh aggregator: `MeshAggregator` fetches every registered
|
|
19
|
+
* service's spec/health (via an `IMeshServiceSource`), computes contract-drift and a cross-service topic
|
|
20
|
+
* catalog/topology/composite-AsyncAPI, and publishes the artifacts to an `IMeshArtifactStore`
|
|
21
|
+
* (`FileSystemMeshArtifactStore` ships). Also the push path (`ArtifactStoreMeshReportPublisher` +
|
|
22
|
+
* `MeshReportMessageHandler`), the discussion write path (`MeshAnnotationPublisher` +
|
|
23
|
+
* `MeshAnnotationsMessageHandler`), and `addMeshAggregator` wiring.
|
|
24
|
+
*/
|
|
25
|
+
__exportStar(require("./IMeshArtifactStore"), exports);
|
|
26
|
+
__exportStar(require("./IMeshServiceSource"), exports);
|
|
27
|
+
__exportStar(require("./UnknownMeshServiceSource"), exports);
|
|
28
|
+
__exportStar(require("./FileSystemMeshArtifactStore"), exports);
|
|
29
|
+
__exportStar(require("./HttpMeshServiceSource"), exports);
|
|
30
|
+
__exportStar(require("./MeshSnapshotBuilder"), exports);
|
|
31
|
+
__exportStar(require("./ArtifactStoreMeshReportPublisher"), exports);
|
|
32
|
+
__exportStar(require("./MeshAnnotationPublisher"), exports);
|
|
33
|
+
__exportStar(require("./AsyncApiCompositor"), exports);
|
|
34
|
+
__exportStar(require("./MeshAggregator"), exports);
|
|
35
|
+
__exportStar(require("./MeshAggregateMessageHandler"), exports);
|
|
36
|
+
__exportStar(require("./MeshReportMessageHandler"), exports);
|
|
37
|
+
__exportStar(require("./MeshAnnotationsMessageHandler"), exports);
|
|
38
|
+
__exportStar(require("./Extensions"), exports);
|
package/package.json
CHANGED
|
@@ -1,6 +1,21 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@benzenejs/mesh-aggregator",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.1.0-beta.2",
|
|
4
|
+
"description": "TypeScript port of Benzene.Mesh.Aggregator",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "commonjs",
|
|
7
|
+
"main": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"dependencies": {
|
|
10
|
+
"@benzenejs/abstractions": "0.1.0-beta.2",
|
|
11
|
+
"@benzenejs/abstractions-message-handlers": "0.1.0-beta.2",
|
|
12
|
+
"@benzenejs/core-message-handlers": "0.1.0-beta.2",
|
|
13
|
+
"@benzenejs/health-checks-core": "0.1.0-beta.2",
|
|
14
|
+
"@benzenejs/http": "0.1.0-beta.2",
|
|
15
|
+
"@benzenejs/mesh-contracts": "0.1.0-beta.2",
|
|
16
|
+
"@benzenejs/results": "0.1.0-beta.2"
|
|
17
|
+
},
|
|
18
|
+
"files": [
|
|
19
|
+
"dist"
|
|
20
|
+
]
|
|
21
|
+
}
|
package/README.md
DELETED