mercury-composable 4.12.1
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/LICENSE +202 -0
- package/README.md +195 -0
- package/dist/src/actuator.d.ts +36 -0
- package/dist/src/actuator.js +295 -0
- package/dist/src/bus.d.ts +110 -0
- package/dist/src/bus.js +386 -0
- package/dist/src/cli.d.ts +2 -0
- package/dist/src/cli.js +72 -0
- package/dist/src/client.d.ts +48 -0
- package/dist/src/client.js +433 -0
- package/dist/src/config.d.ts +27 -0
- package/dist/src/config.js +161 -0
- package/dist/src/default-log-context.yaml +19 -0
- package/dist/src/envelope.d.ts +44 -0
- package/dist/src/envelope.js +227 -0
- package/dist/src/event-stream.d.ts +120 -0
- package/dist/src/event-stream.js +359 -0
- package/dist/src/exceptions.d.ts +18 -0
- package/dist/src/exceptions.js +25 -0
- package/dist/src/index.d.ts +24 -0
- package/dist/src/index.js +21 -0
- package/dist/src/log-context.d.ts +24 -0
- package/dist/src/log-context.js +172 -0
- package/dist/src/log.d.ts +14 -0
- package/dist/src/log.js +113 -0
- package/dist/src/registry.d.ts +64 -0
- package/dist/src/registry.js +80 -0
- package/dist/src/server.d.ts +43 -0
- package/dist/src/server.js +343 -0
- package/dist/src/trace.d.ts +39 -0
- package/dist/src/trace.js +73 -0
- package/dist/src/version.d.ts +2 -0
- package/dist/src/version.js +2 -0
- package/package.json +55 -0
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Actuator endpoints for operations and Kubernetes deployment.
|
|
3
|
+
*
|
|
4
|
+
* The same operational surface as the engines (the Java ActuatorServices and
|
|
5
|
+
* its Rust port), so a polyglot installation monitors every app one way:
|
|
6
|
+
*
|
|
7
|
+
* - GET /info - application identity (name, version, description), runtime,
|
|
8
|
+
* origin id, start/current time and uptime.
|
|
9
|
+
* - GET /info/routes - the local routing table split by visibility
|
|
10
|
+
* (routing.public / routing.private, route -> instance count).
|
|
11
|
+
* - GET /env - selected environment variables (show.env.variables) and
|
|
12
|
+
* selected configuration parameters (show.application.properties) -
|
|
13
|
+
* opt-in lists, so secrets are never dumped wholesale (engine parity).
|
|
14
|
+
* - GET /health - runs the health check functions listed in
|
|
15
|
+
* mandatory.health.dependencies / optional.health.dependencies. All
|
|
16
|
+
* mandatory up -> UP (HTTP 200); any mandatory down -> DOWN (HTTP 400,
|
|
17
|
+
* engine parity). The outcome feeds the liveness state.
|
|
18
|
+
* - GET /livenessprobe - "OK" (text) while the last health outcome is good,
|
|
19
|
+
* else HTTP 400 "Unhealthy. Please check '/health' endpoint."
|
|
20
|
+
*
|
|
21
|
+
* A health check function is a normal registered function (usually private)
|
|
22
|
+
* speaking the engines' interface contract - called through the same event
|
|
23
|
+
* bus that serves PostOffice, first with header type=info (an advisory
|
|
24
|
+
* identity map merged into its dependency entry), then with type=health (a
|
|
25
|
+
* status text or map; a non-200 reply marks the dependency down):
|
|
26
|
+
*
|
|
27
|
+
* preload('demo.health', { isPrivate: true }, async (headers, _body) => {
|
|
28
|
+
* if (headers.type === 'info') {
|
|
29
|
+
* return { service: 'demo.service', href: 'http://127.0.0.1' };
|
|
30
|
+
* }
|
|
31
|
+
* return 'demo.service is running fine';
|
|
32
|
+
* });
|
|
33
|
+
*
|
|
34
|
+
* Engine deltas (deliberate, wrapper-scale): no /info/lib (a wrapper app has
|
|
35
|
+
* no runtime dependency manifest - deferred on the Rust port too), no XML
|
|
36
|
+
* responses, and no 5-second info cache (dependencies are in-process
|
|
37
|
+
* functions, so the type=info lookup costs nothing).
|
|
38
|
+
*/
|
|
39
|
+
import { randomUUID } from 'node:crypto';
|
|
40
|
+
import { DeliveryTimeout } from './bus.js';
|
|
41
|
+
import { appConfig } from './config.js';
|
|
42
|
+
import { asText, isoUtc } from './envelope.js';
|
|
43
|
+
import { getLogger } from './log.js';
|
|
44
|
+
import { VERSION } from './version.js';
|
|
45
|
+
const log = getLogger('mercury.actuator');
|
|
46
|
+
const INFO_TIMEOUT_MS = 3000; // engine value for the advisory type=info lookup
|
|
47
|
+
const HEALTH_TIMEOUT_MS = 10000; // engine value for the type=health probe
|
|
48
|
+
const UNHEALTHY = "Unhealthy. Please check '/health' endpoint.";
|
|
49
|
+
// The engines' minimal landing page (platform-core public/index.html style);
|
|
50
|
+
// the wrappers embed it - no static file service by design.
|
|
51
|
+
const INDEX_HTML = `<!DOCTYPE html>
|
|
52
|
+
<html>
|
|
53
|
+
<body>
|
|
54
|
+
|
|
55
|
+
<h2>Welcome</h2>
|
|
56
|
+
|
|
57
|
+
<p><a href="/info">INFO endpoint</a></p>
|
|
58
|
+
<p><a href="/info/routes">Service list</a></p>
|
|
59
|
+
<p><a href="/env">Environment endpoint</a></p>
|
|
60
|
+
<p><a href="/health">Health endpoint</a></p>
|
|
61
|
+
<p><a href="/livenessprobe">Liveness probe</a></p>
|
|
62
|
+
|
|
63
|
+
</body>
|
|
64
|
+
</html>`;
|
|
65
|
+
let origin;
|
|
66
|
+
/**
|
|
67
|
+
* Unique instance id, minted once per process (the Java reference engine's
|
|
68
|
+
* format: UTC yyyyMMdd date prefix + 32-hex uuid).
|
|
69
|
+
*/
|
|
70
|
+
export function appOrigin() {
|
|
71
|
+
if (!origin) {
|
|
72
|
+
const date = isoUtc(new Date()).slice(0, 10).replaceAll('-', '');
|
|
73
|
+
origin = date + randomUUID().replaceAll('-', '');
|
|
74
|
+
}
|
|
75
|
+
return origin;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Human-readable duration matching the engines' rendering (including their
|
|
79
|
+
* strict boundary behavior, kept verbatim for parity).
|
|
80
|
+
*/
|
|
81
|
+
export function elapsedTime(milliseconds) {
|
|
82
|
+
const ONE_SECOND = 1000;
|
|
83
|
+
const ONE_MINUTE = 60 * ONE_SECOND;
|
|
84
|
+
const ONE_HOUR = 60 * ONE_MINUTE;
|
|
85
|
+
const ONE_DAY = 24 * ONE_HOUR;
|
|
86
|
+
let remaining = Math.trunc(milliseconds);
|
|
87
|
+
const parts = [];
|
|
88
|
+
if (remaining > ONE_DAY) {
|
|
89
|
+
const days = Math.trunc(remaining / ONE_DAY);
|
|
90
|
+
parts.push(`${days} day${days === 1 ? '' : 's'}`);
|
|
91
|
+
remaining -= days * ONE_DAY;
|
|
92
|
+
}
|
|
93
|
+
if (remaining > ONE_HOUR) {
|
|
94
|
+
const hours = Math.trunc(remaining / ONE_HOUR);
|
|
95
|
+
parts.push(`${hours} hour${hours === 1 ? '' : 's'}`);
|
|
96
|
+
remaining -= hours * ONE_HOUR;
|
|
97
|
+
}
|
|
98
|
+
if (remaining > ONE_MINUTE) {
|
|
99
|
+
const minutes = Math.trunc(remaining / ONE_MINUTE);
|
|
100
|
+
parts.push(`${minutes} minute${minutes === 1 ? '' : 's'}`);
|
|
101
|
+
remaining -= minutes * ONE_MINUTE;
|
|
102
|
+
}
|
|
103
|
+
if (remaining >= ONE_SECOND) {
|
|
104
|
+
const seconds = Math.trunc(remaining / ONE_SECOND);
|
|
105
|
+
parts.push(`${seconds} second${seconds === 1 ? '' : 's'}`);
|
|
106
|
+
}
|
|
107
|
+
return parts.length ? parts.join(' ') : `${remaining} ms`;
|
|
108
|
+
}
|
|
109
|
+
/** A comma/space-separated string (engine syntax) or a YAML list. */
|
|
110
|
+
function asList(value) {
|
|
111
|
+
const items = Array.isArray(value)
|
|
112
|
+
? value.map((item) => asText(item).trim())
|
|
113
|
+
: asText(value ?? '').split(/[,\s]+/);
|
|
114
|
+
return items.filter((item) => item.length > 0);
|
|
115
|
+
}
|
|
116
|
+
function isMessageShape(body) {
|
|
117
|
+
// the engines accept only text or map dependency messages
|
|
118
|
+
return typeof body === 'string' ||
|
|
119
|
+
(body !== null && typeof body === 'object' && !Array.isArray(body));
|
|
120
|
+
}
|
|
121
|
+
function sendText(res, status, text) {
|
|
122
|
+
res.writeHead(status, { 'content-type': 'text/plain; charset=utf-8' });
|
|
123
|
+
res.end(text);
|
|
124
|
+
}
|
|
125
|
+
function sendJson(res, status, body) {
|
|
126
|
+
// the engines' default serializer presentation: pretty-printed JSON
|
|
127
|
+
const bytes = Buffer.from(JSON.stringify(body, null, 2));
|
|
128
|
+
res.writeHead(status, { 'content-type': 'application/json; charset=utf-8', 'content-length': bytes.length });
|
|
129
|
+
res.end(bytes);
|
|
130
|
+
}
|
|
131
|
+
/** The engines' host-level error shape (SimpleHttpUtility signature). */
|
|
132
|
+
export function sendError(res, status, message) {
|
|
133
|
+
sendJson(res, status, { status, message, type: 'error' });
|
|
134
|
+
}
|
|
135
|
+
function sendHtml(res, page) {
|
|
136
|
+
const bytes = Buffer.from(page);
|
|
137
|
+
res.writeHead(200, { 'content-type': 'text/html; charset=utf-8', 'content-length': bytes.length });
|
|
138
|
+
res.end(bytes);
|
|
139
|
+
}
|
|
140
|
+
/** HTTP handlers for the actuator endpoints (wired by EventApiServer). */
|
|
141
|
+
export class Actuator {
|
|
142
|
+
registry;
|
|
143
|
+
start = new Date();
|
|
144
|
+
healthy = true; // liveness follows the most recent /health outcome
|
|
145
|
+
appName;
|
|
146
|
+
appVersion;
|
|
147
|
+
description;
|
|
148
|
+
required;
|
|
149
|
+
optional;
|
|
150
|
+
constructor(registry) {
|
|
151
|
+
const config = appConfig();
|
|
152
|
+
this.registry = registry;
|
|
153
|
+
this.appName = config.getProperty('application.name', 'application') || 'application';
|
|
154
|
+
this.appVersion = config.getProperty('info.app.version', VERSION) || VERSION;
|
|
155
|
+
this.description = config.getProperty('info.app.description', this.appName) || this.appName;
|
|
156
|
+
this.required = asList(config.get('mandatory.health.dependencies'));
|
|
157
|
+
this.optional = asList(config.get('optional.health.dependencies'));
|
|
158
|
+
if (this.required.length) {
|
|
159
|
+
log.info(`Mandatory service dependencies - ${JSON.stringify(this.required)}`);
|
|
160
|
+
}
|
|
161
|
+
if (this.optional.length) {
|
|
162
|
+
log.info(`Optional services dependencies - ${JSON.stringify(this.optional)}`);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
/** Route a GET to its actuator endpoint; false when the path is not ours. */
|
|
166
|
+
async handle(pathname, res) {
|
|
167
|
+
switch (pathname) {
|
|
168
|
+
case '/':
|
|
169
|
+
sendHtml(res, INDEX_HTML);
|
|
170
|
+
return true;
|
|
171
|
+
case '/info':
|
|
172
|
+
sendJson(res, 200, this.info());
|
|
173
|
+
return true;
|
|
174
|
+
case '/info/routes':
|
|
175
|
+
sendJson(res, 200, this.routes());
|
|
176
|
+
return true;
|
|
177
|
+
case '/env':
|
|
178
|
+
sendJson(res, 200, this.env());
|
|
179
|
+
return true;
|
|
180
|
+
case '/health': {
|
|
181
|
+
const [status, body] = await this.health();
|
|
182
|
+
sendJson(res, status, body);
|
|
183
|
+
return true;
|
|
184
|
+
}
|
|
185
|
+
case '/livenessprobe':
|
|
186
|
+
if (this.healthy) {
|
|
187
|
+
sendText(res, 200, 'OK');
|
|
188
|
+
}
|
|
189
|
+
else {
|
|
190
|
+
sendText(res, 400, UNHEALTHY);
|
|
191
|
+
}
|
|
192
|
+
return true;
|
|
193
|
+
default:
|
|
194
|
+
return false;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
appBlock() {
|
|
198
|
+
return { name: this.appName, version: this.appVersion, description: this.description };
|
|
199
|
+
}
|
|
200
|
+
info() {
|
|
201
|
+
const now = new Date();
|
|
202
|
+
return {
|
|
203
|
+
app: this.appBlock(),
|
|
204
|
+
runtime: { language: 'node.js', node: process.version, mercury_composable: VERSION },
|
|
205
|
+
origin: appOrigin(),
|
|
206
|
+
time: { start: isoUtc(this.start), current: isoUtc(now) },
|
|
207
|
+
up_time: elapsedTime(now.getTime() - this.start.getTime())
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
routes() {
|
|
211
|
+
const publicRoutes = {};
|
|
212
|
+
const privateRoutes = {};
|
|
213
|
+
for (const service of this.registry.routes()) { // already route-sorted
|
|
214
|
+
(service.isPrivate ? privateRoutes : publicRoutes)[service.route] = service.instances;
|
|
215
|
+
}
|
|
216
|
+
return { app: this.appBlock(), routing: { public: publicRoutes, private: privateRoutes } };
|
|
217
|
+
}
|
|
218
|
+
env() {
|
|
219
|
+
const config = appConfig();
|
|
220
|
+
const environment = {};
|
|
221
|
+
for (const name of asList(config.get('show.env.variables'))) {
|
|
222
|
+
environment[name] = process.env[name] ?? '';
|
|
223
|
+
}
|
|
224
|
+
const properties = {};
|
|
225
|
+
for (const name of asList(config.get('show.application.properties'))) {
|
|
226
|
+
properties[name] = config.getProperty(name) ?? '';
|
|
227
|
+
}
|
|
228
|
+
return { app: this.appBlock(), env: { environment, properties } };
|
|
229
|
+
}
|
|
230
|
+
async health() {
|
|
231
|
+
const dependency = [];
|
|
232
|
+
// optional services never affect the overall status (engine semantics)
|
|
233
|
+
await this.checkServices(this.optional, false, dependency);
|
|
234
|
+
const up = await this.checkServices(this.required, true, dependency);
|
|
235
|
+
this.healthy = up;
|
|
236
|
+
const result = {};
|
|
237
|
+
if (!dependency.length) {
|
|
238
|
+
result.message = 'Did you forget to define mandatory.health.dependencies ' +
|
|
239
|
+
'or optional.health.dependencies';
|
|
240
|
+
}
|
|
241
|
+
result.dependency = dependency;
|
|
242
|
+
result.status = up ? 'UP' : 'DOWN';
|
|
243
|
+
result.origin = appOrigin();
|
|
244
|
+
result.name = this.appName;
|
|
245
|
+
return [up ? 200 : 400, result];
|
|
246
|
+
}
|
|
247
|
+
async checkServices(services, required, dependency) {
|
|
248
|
+
let allUp = true;
|
|
249
|
+
for (const route of services) {
|
|
250
|
+
const entry = { route, required };
|
|
251
|
+
dependency.push(entry);
|
|
252
|
+
if (!await this.checkService(route, entry)) {
|
|
253
|
+
allUp = false;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
return allUp;
|
|
257
|
+
}
|
|
258
|
+
/** Probe one health-check function; false when it is missing or down. */
|
|
259
|
+
async checkService(route, entry) {
|
|
260
|
+
const service = this.registry.get(route);
|
|
261
|
+
if (!service) {
|
|
262
|
+
entry.status_code = 404;
|
|
263
|
+
entry.message = `Please check - Route ${route} not found`;
|
|
264
|
+
return false;
|
|
265
|
+
}
|
|
266
|
+
const bus = this.registry.bus;
|
|
267
|
+
// info is advisory - merge whatever the service reports about itself;
|
|
268
|
+
// the health probe below decides the status
|
|
269
|
+
try {
|
|
270
|
+
const info = await bus.deliver(service, { type: 'info' }, null, INFO_TIMEOUT_MS);
|
|
271
|
+
if (info.body !== null && typeof info.body === 'object' && !Array.isArray(info.body)) {
|
|
272
|
+
Object.assign(entry, info.body);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
catch (e) {
|
|
276
|
+
if (!(e instanceof DeliveryTimeout))
|
|
277
|
+
throw e;
|
|
278
|
+
}
|
|
279
|
+
try {
|
|
280
|
+
const reply = await bus.deliver(service, { type: 'health' }, null, HEALTH_TIMEOUT_MS);
|
|
281
|
+
entry.status_code = reply.getStatus();
|
|
282
|
+
if (isMessageShape(reply.body)) {
|
|
283
|
+
entry.message = reply.body;
|
|
284
|
+
}
|
|
285
|
+
return !reply.hasError();
|
|
286
|
+
}
|
|
287
|
+
catch (e) {
|
|
288
|
+
if (!(e instanceof DeliveryTimeout))
|
|
289
|
+
throw e;
|
|
290
|
+
entry.status_code = 408;
|
|
291
|
+
entry.message = `Please check - ${e.message}`;
|
|
292
|
+
return false;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The primitive in-process event bus - the single dispatch pipeline.
|
|
3
|
+
*
|
|
4
|
+
* Every invocation reaches a function the same way: through a per-route FIFO
|
|
5
|
+
* mailbox consumed by `instances` worker loops (the engines' semantics - the
|
|
6
|
+
* parameter is faithful). The HTTP host and the local side of PostOffice are
|
|
7
|
+
* thin ingress adapters over this bus; neither has its own invocation path.
|
|
8
|
+
*
|
|
9
|
+
* Deliberately primitive, riding the runtime's native event loop:
|
|
10
|
+
* - Two operations only: deliver (RPC - ttl-bounded, with a dead-work skip
|
|
11
|
+
* for queued calls whose caller already timed out) and publish
|
|
12
|
+
* (drop-n-forget - returns the 202-shape acknowledgement).
|
|
13
|
+
* - No spill tier and no queue cap: back-pressure belongs to the tier that
|
|
14
|
+
* owns recovery - the engines' flows and graphs. A leaf host fails fast by
|
|
15
|
+
* deadline (the 408 envelope) instead of hoarding work.
|
|
16
|
+
* - In-memory only; no orchestration, no flows, no persistence, no broadcast.
|
|
17
|
+
*
|
|
18
|
+
* Why a hand-built Mailbox instead of Node's EventEmitter: this contract is
|
|
19
|
+
* an ANYCAST WORK QUEUE - each delivery goes to exactly one of N workers and
|
|
20
|
+
* waits its FIFO turn while all are busy. EventEmitter is a broadcast
|
|
21
|
+
* notifier - emit() invokes every listener synchronously and buffers
|
|
22
|
+
* nothing - so a bounded-concurrency bus would still need this queue in
|
|
23
|
+
* front of it (the emitter demoted to a wake-up bell), and once()-based
|
|
24
|
+
* bridging re-registers a listener per iteration, can drop emissions
|
|
25
|
+
* between iterations, and trips MaxListenersExceededWarning right at the
|
|
26
|
+
* default instances=10. Bare promise waiters also hold no event-loop
|
|
27
|
+
* handles, which is what makes the lifecycle contract exactly true (an idle
|
|
28
|
+
* bus lets the process exit; only an in-flight RPC's deadline timer holds
|
|
29
|
+
* it). The Mailbox is node's missing asyncio.Queue, keeping the python and
|
|
30
|
+
* node twins structurally identical.
|
|
31
|
+
*
|
|
32
|
+
* The bus is internal: application code uses preload() and PostOffice, never
|
|
33
|
+
* this module - the same way engine developers never touch the engine bus.
|
|
34
|
+
*/
|
|
35
|
+
import { EventEnvelope } from './envelope.js';
|
|
36
|
+
import type { ServiceDef } from './registry.js';
|
|
37
|
+
/** An RPC delivery missed its deadline; adapters shape the 408 for their protocol. */
|
|
38
|
+
export declare class DeliveryTimeout extends Error {
|
|
39
|
+
readonly ttlMs: number;
|
|
40
|
+
constructor(ttlMs: number);
|
|
41
|
+
}
|
|
42
|
+
/** The 202 drop-n-forget acknowledgement (EventApiService shape). */
|
|
43
|
+
export declare function asyncAck(): EventEnvelope;
|
|
44
|
+
interface TraceFields {
|
|
45
|
+
traceId?: string;
|
|
46
|
+
tracePath?: string;
|
|
47
|
+
cid?: string;
|
|
48
|
+
envelope?: EventEnvelope;
|
|
49
|
+
}
|
|
50
|
+
/** Unbounded FIFO handing items to awaiting consumers (node's missing asyncio.Queue). */
|
|
51
|
+
export declare class Mailbox<T> {
|
|
52
|
+
private readonly items;
|
|
53
|
+
private readonly waiters;
|
|
54
|
+
push(item: T): void;
|
|
55
|
+
next(): Promise<T>;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Race one PENDING promise against a timeout; null on expiry. The pending
|
|
59
|
+
* promise survives a lost race (a bare promise cannot be cancelled), so a
|
|
60
|
+
* caller that keeps waiting must keep reusing THE SAME promise until it
|
|
61
|
+
* resolves - a fresh queue.next() per cycle would leave an abandoned waiter
|
|
62
|
+
* in the mailbox that steals and drops the next item. A caller that
|
|
63
|
+
* terminates on expiry may race a fresh promise each time.
|
|
64
|
+
*/
|
|
65
|
+
export declare function raceMs<T>(pending: Promise<T>, timeoutMs: number): Promise<T | null>;
|
|
66
|
+
/** Route an envelope to a local target - wired by the registry (backref). */
|
|
67
|
+
export type EnvelopeRouter = (event: EventEnvelope) => boolean;
|
|
68
|
+
/** Per-registry bus: one FIFO mailbox and N workers per registered route. */
|
|
69
|
+
export declare class EventBus {
|
|
70
|
+
private readonly mailboxes;
|
|
71
|
+
private readonly sinks;
|
|
72
|
+
private router;
|
|
73
|
+
private sinkSequence;
|
|
74
|
+
bindRouter(router: EnvelopeRouter): void;
|
|
75
|
+
/** Open a per-request reply sink under a generated local route name. */
|
|
76
|
+
openSink(): [string, Mailbox<EventEnvelope>];
|
|
77
|
+
closeSink(route: string): void;
|
|
78
|
+
/**
|
|
79
|
+
* Deliver an envelope to a reply sink; false when the sink is gone (a
|
|
80
|
+
* completed, timed-out or disconnected request) - late segments are no-op
|
|
81
|
+
* drops, the engines' semantics.
|
|
82
|
+
*/
|
|
83
|
+
offerSink(route: string, event: EventEnvelope): boolean;
|
|
84
|
+
private mailbox;
|
|
85
|
+
/** RPC: enqueue and await the reply envelope within the ttl. */
|
|
86
|
+
deliver(service: ServiceDef, headers: Record<string, string>, body: unknown, ttlMs: number, trace?: TraceFields): Promise<EventEnvelope>;
|
|
87
|
+
/** Drop-n-forget: enqueue and return the 202-shape acknowledgement. */
|
|
88
|
+
publish(service: ServiceDef, headers: Record<string, string>, body: unknown, trace?: TraceFields): EventEnvelope;
|
|
89
|
+
/**
|
|
90
|
+
* Route one envelope to a local function (the reply_to mechanism):
|
|
91
|
+
* drop-n-forget delivery carrying the raw envelope, so an interceptor
|
|
92
|
+
* handler receives reply_to and the correlation id the engines' way.
|
|
93
|
+
*/
|
|
94
|
+
publishEnvelope(service: ServiceDef, event: EventEnvelope): void;
|
|
95
|
+
/** Stop all workers (orderly shutdown; worker promises hold no OS handle). */
|
|
96
|
+
close(): void;
|
|
97
|
+
private runWorker;
|
|
98
|
+
/** Run the handler under its trace context and shape the outcome as a reply. */
|
|
99
|
+
private static execute;
|
|
100
|
+
/**
|
|
101
|
+
* Run an interceptor handler: it receives the raw envelope, replies
|
|
102
|
+
* manually through reply_to (the engines' EventInterceptor contract), and
|
|
103
|
+
* its return value is discarded. An uncaught exception becomes an error
|
|
104
|
+
* envelope to the delivery's reply_to - so a caller waiting on a reply
|
|
105
|
+
* sink sees it - and a streaming host renders it in-band.
|
|
106
|
+
*/
|
|
107
|
+
private executeInterceptor;
|
|
108
|
+
private replyInterceptorError;
|
|
109
|
+
}
|
|
110
|
+
export {};
|