@hotmeshio/long-tail 0.25.4 → 0.27.0
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/README.md +1 -0
- package/build/index.d.ts +5 -0
- package/build/index.js +36 -7
- package/build/lib/events/nats.d.ts +11 -0
- package/build/lib/events/nats.js +33 -2
- package/build/lib/machines/index.d.ts +6 -0
- package/build/lib/machines/index.js +22 -0
- package/build/lib/machines/line.d.ts +44 -0
- package/build/lib/machines/line.js +114 -0
- package/build/lib/machines/protocol/codec.d.ts +108 -0
- package/build/lib/machines/protocol/codec.js +80 -0
- package/build/lib/machines/protocol/constants.d.ts +120 -0
- package/build/lib/machines/protocol/constants.js +97 -0
- package/build/lib/machines/protocol/contract.d.ts +19 -0
- package/build/lib/machines/protocol/contract.js +141 -0
- package/build/lib/machines/protocol/envelope.d.ts +14 -0
- package/build/lib/machines/protocol/envelope.js +43 -0
- package/build/lib/machines/protocol/index.d.ts +5 -0
- package/build/lib/machines/protocol/index.js +21 -0
- package/build/lib/machines/protocol/topics.d.ts +33 -0
- package/build/lib/machines/protocol/topics.js +79 -0
- package/build/lib/machines/site/edge-account.d.ts +11 -0
- package/build/lib/machines/site/edge-account.js +24 -0
- package/build/lib/machines/site/mosquitto-conf.d.ts +27 -0
- package/build/lib/machines/site/mosquitto-conf.js +81 -0
- package/build/lib/machines/site/site-conf.d.ts +23 -0
- package/build/lib/machines/site/site-conf.js +62 -0
- package/build/lib/machines/transport/callback-feed.d.ts +18 -0
- package/build/lib/machines/transport/callback-feed.js +31 -0
- package/build/lib/machines/transport/delivery.d.ts +20 -0
- package/build/lib/machines/transport/delivery.js +37 -0
- package/build/lib/machines/transport/index.d.ts +13 -0
- package/build/lib/machines/transport/index.js +29 -0
- package/build/lib/machines/transport/memory-broker.d.ts +35 -0
- package/build/lib/machines/transport/memory-broker.js +145 -0
- package/build/lib/machines/transport/memory.d.ts +40 -0
- package/build/lib/machines/transport/memory.js +79 -0
- package/build/lib/machines/transport/mqtt-bus.d.ts +24 -0
- package/build/lib/machines/transport/mqtt-bus.js +43 -0
- package/build/lib/machines/transport/mqtt-link.d.ts +16 -0
- package/build/lib/machines/transport/mqtt-link.js +37 -0
- package/build/lib/machines/transport/mqtt-session.d.ts +34 -0
- package/build/lib/machines/transport/mqtt-session.js +127 -0
- package/build/lib/machines/transport/nats-bus.d.ts +36 -0
- package/build/lib/machines/transport/nats-bus.js +47 -0
- package/build/lib/machines/transport/nats-feed.d.ts +26 -0
- package/build/lib/machines/transport/nats-feed.js +49 -0
- package/build/lib/machines/transport/pubsub-feed.d.ts +17 -0
- package/build/lib/machines/transport/pubsub-feed.js +39 -0
- package/build/lib/machines/transport/serial.d.ts +21 -0
- package/build/lib/machines/transport/serial.js +54 -0
- package/build/lib/machines/transport/stream-bus.d.ts +40 -0
- package/build/lib/machines/transport/stream-bus.js +57 -0
- package/build/lib/machines/transport/streams.d.ts +24 -0
- package/build/lib/machines/transport/streams.js +49 -0
- package/build/lib/messaging/config.d.ts +25 -0
- package/build/lib/messaging/config.js +55 -0
- package/build/lib/messaging/constants.d.ts +26 -0
- package/build/lib/messaging/constants.js +26 -0
- package/build/lib/messaging/index.d.ts +24 -0
- package/build/lib/messaging/index.js +65 -0
- package/build/lib/messaging/memory.d.ts +16 -0
- package/build/lib/messaging/memory.js +147 -0
- package/build/lib/messaging/nats/permissions.d.ts +13 -0
- package/build/lib/messaging/nats/permissions.js +22 -0
- package/build/lib/messaging/nats/provider.d.ts +30 -0
- package/build/lib/messaging/nats/provider.js +77 -0
- package/build/lib/messaging/nats/render-conf.d.ts +68 -0
- package/build/lib/messaging/nats/render-conf.js +58 -0
- package/build/lib/messaging/nats/streams.d.ts +26 -0
- package/build/lib/messaging/nats/streams.js +97 -0
- package/build/lib/messaging/subjects.d.ts +8 -0
- package/build/lib/messaging/subjects.js +25 -0
- package/build/scripts/bambu-boat.d.ts +17 -0
- package/build/scripts/bambu-boat.js +134 -0
- package/build/scripts/bambu-print.d.ts +18 -0
- package/build/scripts/bambu-print.js +114 -0
- package/build/scripts/machine-line.d.ts +33 -0
- package/build/scripts/machine-line.js +301 -0
- package/build/services/machines/commands.d.ts +29 -0
- package/build/services/machines/commands.js +37 -0
- package/build/services/machines/config.d.ts +49 -0
- package/build/services/machines/config.js +94 -0
- package/build/services/machines/contracts.d.ts +210 -0
- package/build/services/machines/contracts.js +124 -0
- package/build/services/machines/forms.d.ts +55 -0
- package/build/services/machines/forms.js +37 -0
- package/build/services/machines/gateway/actions.d.ts +111 -0
- package/build/services/machines/gateway/actions.js +8 -0
- package/build/services/machines/gateway/address-book.d.ts +16 -0
- package/build/services/machines/gateway/address-book.js +24 -0
- package/build/services/machines/gateway/admission.d.ts +35 -0
- package/build/services/machines/gateway/admission.js +66 -0
- package/build/services/machines/gateway/api-escalations.d.ts +23 -0
- package/build/services/machines/gateway/api-escalations.js +101 -0
- package/build/services/machines/gateway/dispatcher.d.ts +76 -0
- package/build/services/machines/gateway/dispatcher.js +197 -0
- package/build/services/machines/gateway/escalations.d.ts +51 -0
- package/build/services/machines/gateway/escalations.js +40 -0
- package/build/services/machines/gateway/executor.d.ts +60 -0
- package/build/services/machines/gateway/executor.js +221 -0
- package/build/services/machines/gateway/gateway.d.ts +88 -0
- package/build/services/machines/gateway/gateway.js +270 -0
- package/build/services/machines/gateway/index.d.ts +10 -0
- package/build/services/machines/gateway/index.js +26 -0
- package/build/services/machines/gateway/jobs.d.ts +16 -0
- package/build/services/machines/gateway/jobs.js +24 -0
- package/build/services/machines/gateway/lifecycle.d.ts +21 -0
- package/build/services/machines/gateway/lifecycle.js +89 -0
- package/build/services/machines/gateway/log.d.ts +41 -0
- package/build/services/machines/gateway/log.js +42 -0
- package/build/services/machines/gateway/lt-http.d.ts +51 -0
- package/build/services/machines/gateway/lt-http.js +120 -0
- package/build/services/machines/gateway/pacing.d.ts +43 -0
- package/build/services/machines/gateway/pacing.js +158 -0
- package/build/services/machines/gateway/plan-escalation.d.ts +19 -0
- package/build/services/machines/gateway/plan-escalation.js +210 -0
- package/build/services/machines/gateway/plan-machine.d.ts +17 -0
- package/build/services/machines/gateway/plan-machine.js +283 -0
- package/build/services/machines/gateway/plan-shared.d.ts +18 -0
- package/build/services/machines/gateway/plan-shared.js +36 -0
- package/build/services/machines/gateway/provision.d.ts +22 -0
- package/build/services/machines/gateway/provision.js +79 -0
- package/build/services/machines/gateway/receipts.d.ts +33 -0
- package/build/services/machines/gateway/receipts.js +65 -0
- package/build/services/machines/gateway/reconcile.d.ts +35 -0
- package/build/services/machines/gateway/reconcile.js +99 -0
- package/build/services/machines/gateway/registration.d.ts +38 -0
- package/build/services/machines/gateway/registration.js +121 -0
- package/build/services/machines/gateway/reply-waiter.d.ts +24 -0
- package/build/services/machines/gateway/reply-waiter.js +65 -0
- package/build/services/machines/gateway/state.d.ts +145 -0
- package/build/services/machines/gateway/state.js +241 -0
- package/build/services/machines/gateway/trace.d.ts +16 -0
- package/build/services/machines/gateway/trace.js +43 -0
- package/build/services/machines/identities.d.ts +25 -0
- package/build/services/machines/identities.js +77 -0
- package/build/services/machines/index.d.ts +11 -0
- package/build/services/machines/index.js +27 -0
- package/build/services/machines/leadership.d.ts +35 -0
- package/build/services/machines/leadership.js +100 -0
- package/build/services/machines/roles.d.ts +13 -0
- package/build/services/machines/roles.js +62 -0
- package/build/services/machines/rows.d.ts +30 -0
- package/build/services/machines/rows.js +71 -0
- package/build/services/machines/select.d.ts +25 -0
- package/build/services/machines/select.js +76 -0
- package/build/services/machines/surface.d.ts +33 -0
- package/build/services/machines/surface.js +104 -0
- package/build/services/machines/testing/fake-long-tail.d.ts +79 -0
- package/build/services/machines/testing/fake-long-tail.js +158 -0
- package/build/services/machines/testing/index.d.ts +3 -0
- package/build/services/machines/testing/index.js +19 -0
- package/build/services/machines/testing/recording-log.d.ts +14 -0
- package/build/services/machines/testing/recording-log.js +22 -0
- package/build/services/machines/testing/wait.d.ts +3 -0
- package/build/services/machines/testing/wait.js +17 -0
- package/build/services/mcp/db-server/schemas.d.ts +2 -2
- package/build/start/adapters.js +11 -2
- package/build/start/index.js +20 -1
- package/build/system/mcp-servers/admin/schemas.d.ts +221 -221
- package/build/system/mcp-servers/db-query/schemas.d.ts +2 -2
- package/build/system/mcp-servers/human-queue-schemas.d.ts +6 -6
- package/build/tsconfig.tsbuildinfo +1 -1
- package/build/types/index.d.ts +3 -1
- package/build/types/machines.d.ts +167 -0
- package/build/types/machines.js +7 -0
- package/build/types/messaging.d.ts +72 -0
- package/build/types/messaging.js +11 -0
- package/build/types/startup.d.ts +49 -1
- package/docs/events.md +50 -4
- package/docs/machines.md +322 -0
- package/package.json +13 -1
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The line's durable log, as specs for the messaging capability. Machine
|
|
3
|
+
* events and telemetry each land in a stream; the gateway reads events through
|
|
4
|
+
* a durable consumer, so a restart or a link outage replays instead of losing.
|
|
5
|
+
*/
|
|
6
|
+
import type { ConsumerSpec, StreamSpec } from '../../../types/messaging';
|
|
7
|
+
import { type LineCodec } from '../protocol/codec';
|
|
8
|
+
export declare const LINE_STREAM: {
|
|
9
|
+
readonly EVENTS: "LINE_EVENTS";
|
|
10
|
+
readonly TELEMETRY: "LINE_TELEMETRY";
|
|
11
|
+
};
|
|
12
|
+
/** What the streams need from a codec: its root, channels and topic layout. */
|
|
13
|
+
export type StreamCodec = Pick<LineCodec, 'root' | 'channels' | 'topicFor'>;
|
|
14
|
+
/** An MQTT filter as a subject: levels become tokens, `+` becomes `*`, `#` becomes `>`. */
|
|
15
|
+
export declare function filterToSubject(filter: string): string;
|
|
16
|
+
/** The line streams, for every line under the codec's root; a root alone means the generic layout. */
|
|
17
|
+
export declare function lineStreamSpecs(codec: StreamCodec | string): StreamSpec[];
|
|
18
|
+
/** The telemetry subjects for one line, read live rather than replayed. */
|
|
19
|
+
export declare function telemetrySubjects(codec: StreamCodec | string, siteId: string, lineId: string): string[];
|
|
20
|
+
/**
|
|
21
|
+
* One line's event consumer. It starts at new messages the first time (open
|
|
22
|
+
* rows are reconciled at boot); after that its position survives restarts.
|
|
23
|
+
*/
|
|
24
|
+
export declare function lineEventConsumerSpec(codec: StreamCodec | string, siteId: string, lineId: string, name: string): ConsumerSpec;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* The line's durable log, as specs for the messaging capability. Machine
|
|
4
|
+
* events and telemetry each land in a stream; the gateway reads events through
|
|
5
|
+
* a durable consumer, so a restart or a link outage replays instead of losing.
|
|
6
|
+
*/
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
exports.LINE_STREAM = void 0;
|
|
9
|
+
exports.filterToSubject = filterToSubject;
|
|
10
|
+
exports.lineStreamSpecs = lineStreamSpecs;
|
|
11
|
+
exports.telemetrySubjects = telemetrySubjects;
|
|
12
|
+
exports.lineEventConsumerSpec = lineEventConsumerSpec;
|
|
13
|
+
const codec_1 = require("../protocol/codec");
|
|
14
|
+
exports.LINE_STREAM = {
|
|
15
|
+
EVENTS: 'LINE_EVENTS',
|
|
16
|
+
TELEMETRY: 'LINE_TELEMETRY',
|
|
17
|
+
};
|
|
18
|
+
const DAY_MS = 86_400_000;
|
|
19
|
+
const ACK_WAIT_MS = 30_000;
|
|
20
|
+
const GENERIC = (root) => ({ root, channels: codec_1.GENERIC_CHANNELS });
|
|
21
|
+
const codecOf = (codec) => (typeof codec === 'string' ? GENERIC(codec) : codec);
|
|
22
|
+
/** An MQTT filter as a subject: levels become tokens, `+` becomes `*`, `#` becomes `>`. */
|
|
23
|
+
function filterToSubject(filter) {
|
|
24
|
+
return filter.split('/').map((level) => (level === '+' ? '*' : level === '#' ? '>' : level)).join('.');
|
|
25
|
+
}
|
|
26
|
+
function subjectsFor(codec, channels, siteId, lineId) {
|
|
27
|
+
return channels.map((c) => filterToSubject((0, codec_1.topicOf)(codec, { siteId, lineId }, c)));
|
|
28
|
+
}
|
|
29
|
+
/** The line streams, for every line under the codec's root; a root alone means the generic layout. */
|
|
30
|
+
function lineStreamSpecs(codec) {
|
|
31
|
+
const c = codecOf(codec);
|
|
32
|
+
return [
|
|
33
|
+
{ name: exports.LINE_STREAM.EVENTS, subjects: subjectsFor(c, c.channels.events), maxAgeMs: 30 * DAY_MS },
|
|
34
|
+
{ name: exports.LINE_STREAM.TELEMETRY, subjects: subjectsFor(c, c.channels.telemetry), maxAgeMs: 3 * DAY_MS },
|
|
35
|
+
];
|
|
36
|
+
}
|
|
37
|
+
/** The telemetry subjects for one line, read live rather than replayed. */
|
|
38
|
+
function telemetrySubjects(codec, siteId, lineId) {
|
|
39
|
+
const c = codecOf(codec);
|
|
40
|
+
return subjectsFor(c, c.channels.telemetry, siteId, lineId);
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* One line's event consumer. It starts at new messages the first time (open
|
|
44
|
+
* rows are reconciled at boot); after that its position survives restarts.
|
|
45
|
+
*/
|
|
46
|
+
function lineEventConsumerSpec(codec, siteId, lineId, name) {
|
|
47
|
+
const c = codecOf(codec);
|
|
48
|
+
return { name, filterSubjects: subjectsFor(c, c.channels.events, siteId, lineId), ackWaitMs: ACK_WAIT_MS, startAt: 'new' };
|
|
49
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which messaging provider a deployment runs, from start() or the environment.
|
|
3
|
+
* Precedence: an explicit provider, then `messaging.nats` or `messaging.memory`,
|
|
4
|
+
* then LT_MESSAGING with LT_NATS_*, then `events.nats` (the same NATS, configured
|
|
5
|
+
* as events always have been). With none, Long Tail runs without messaging.
|
|
6
|
+
*/
|
|
7
|
+
import type { LTMessagingProvider } from '../../types/messaging';
|
|
8
|
+
import type { LTStartConfig } from '../../types/startup';
|
|
9
|
+
export declare const MESSAGING_KIND: {
|
|
10
|
+
readonly NATS: "nats";
|
|
11
|
+
readonly MEMORY: "memory";
|
|
12
|
+
};
|
|
13
|
+
/** What browsers need to reach the provider's events: a public WebSocket URL, a proxy target, a credential. */
|
|
14
|
+
export interface MessagingWs {
|
|
15
|
+
url?: string;
|
|
16
|
+
proxy?: string;
|
|
17
|
+
token?: string;
|
|
18
|
+
}
|
|
19
|
+
export interface ResolvedMessaging {
|
|
20
|
+
provider: LTMessagingProvider | null;
|
|
21
|
+
ws: MessagingWs;
|
|
22
|
+
}
|
|
23
|
+
type Env = Record<string, string | undefined>;
|
|
24
|
+
export declare function resolveMessaging(start: Pick<LTStartConfig, 'messaging' | 'events'>, env?: Env): ResolvedMessaging;
|
|
25
|
+
export {};
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Which messaging provider a deployment runs, from start() or the environment.
|
|
4
|
+
* Precedence: an explicit provider, then `messaging.nats` or `messaging.memory`,
|
|
5
|
+
* then LT_MESSAGING with LT_NATS_*, then `events.nats` (the same NATS, configured
|
|
6
|
+
* as events always have been). With none, Long Tail runs without messaging.
|
|
7
|
+
*/
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.MESSAGING_KIND = void 0;
|
|
10
|
+
exports.resolveMessaging = resolveMessaging;
|
|
11
|
+
const config_1 = require("../../modules/config");
|
|
12
|
+
const memory_1 = require("./memory");
|
|
13
|
+
const provider_1 = require("./nats/provider");
|
|
14
|
+
exports.MESSAGING_KIND = { NATS: 'nats', MEMORY: 'memory' };
|
|
15
|
+
const NONE = { provider: null, ws: {} };
|
|
16
|
+
function fromEnv(env) {
|
|
17
|
+
const kind = env.LT_MESSAGING;
|
|
18
|
+
if (!kind)
|
|
19
|
+
return null;
|
|
20
|
+
if (kind === exports.MESSAGING_KIND.MEMORY)
|
|
21
|
+
return { provider: new memory_1.MemoryMessagingProvider(), ws: {} };
|
|
22
|
+
if (kind !== exports.MESSAGING_KIND.NATS)
|
|
23
|
+
throw new Error(`LT_MESSAGING="${kind}" is not one of ${Object.values(exports.MESSAGING_KIND).join(', ')}`);
|
|
24
|
+
const url = env.LT_NATS_URL;
|
|
25
|
+
if (!url)
|
|
26
|
+
throw new Error('LT_MESSAGING=nats needs LT_NATS_URL');
|
|
27
|
+
const token = env.LT_NATS_TOKEN || undefined;
|
|
28
|
+
return {
|
|
29
|
+
provider: new provider_1.NatsMessagingProvider({ url, token, user: env.LT_NATS_USER || undefined, password: env.LT_NATS_PASSWORD || undefined, name: 'long-tail' }),
|
|
30
|
+
ws: { url: env.LT_NATS_WS_URL || undefined, proxy: env.LT_NATS_WS_PROXY || undefined, token },
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
function resolveMessaging(start, env = process.env) {
|
|
34
|
+
const m = start.messaging;
|
|
35
|
+
if (m?.provider)
|
|
36
|
+
return { provider: m.provider, ws: m.nats?.ws ?? {} };
|
|
37
|
+
if (m?.nats) {
|
|
38
|
+
const { ws, ...nats } = m.nats;
|
|
39
|
+
return { provider: new provider_1.NatsMessagingProvider({ name: 'long-tail', ...nats }), ws: { ...ws, token: ws?.token ?? nats.token } };
|
|
40
|
+
}
|
|
41
|
+
if (m?.memory)
|
|
42
|
+
return { provider: new memory_1.MemoryMessagingProvider(), ws: {} };
|
|
43
|
+
const configured = fromEnv(env);
|
|
44
|
+
if (configured)
|
|
45
|
+
return configured;
|
|
46
|
+
const legacy = start.events?.nats;
|
|
47
|
+
if (legacy) {
|
|
48
|
+
const token = legacy.token || config_1.config.NATS_TOKEN || undefined;
|
|
49
|
+
return {
|
|
50
|
+
provider: new provider_1.NatsMessagingProvider({ url: legacy.url || config_1.config.NATS_URL, token, name: 'long-tail' }),
|
|
51
|
+
ws: { url: legacy.wsUrl || config_1.config.NATS_WS_URL || undefined, proxy: legacy.wsProxy, token },
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
return NONE;
|
|
55
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/** Deliveries, log events and subjects for the messaging capability. */
|
|
2
|
+
/** What a consumer does with a stream message. */
|
|
3
|
+
export declare const DELIVERY: {
|
|
4
|
+
/** Handled; do not deliver again. */
|
|
5
|
+
readonly ACK: "ack";
|
|
6
|
+
/** Not yet actionable; deliver again after a delay. */
|
|
7
|
+
readonly NAK: "nak";
|
|
8
|
+
/** Never actionable; drop it. */
|
|
9
|
+
readonly TERM: "term";
|
|
10
|
+
};
|
|
11
|
+
/** Where a new consumer starts: only messages appended after it, or the whole retained history. */
|
|
12
|
+
export declare const START_AT: {
|
|
13
|
+
readonly NEW: "new";
|
|
14
|
+
readonly ALL: "all";
|
|
15
|
+
};
|
|
16
|
+
export declare const MESSAGING_LOG: {
|
|
17
|
+
readonly CONNECTED: "messaging.provider.connected";
|
|
18
|
+
readonly DISCONNECTED: "messaging.provider.disconnected";
|
|
19
|
+
readonly CONNECT_FAILED: "messaging.provider.connect_failed";
|
|
20
|
+
readonly STREAMS_UNAVAILABLE: "messaging.provider.streams_unavailable";
|
|
21
|
+
};
|
|
22
|
+
/** Subjects Long Tail itself publishes under. */
|
|
23
|
+
export declare const LT_SUBJECTS: {
|
|
24
|
+
readonly EVENTS: "lt.events";
|
|
25
|
+
readonly ESCALATIONS: "lt.events.system.escalation.>";
|
|
26
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/** Deliveries, log events and subjects for the messaging capability. */
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.LT_SUBJECTS = exports.MESSAGING_LOG = exports.START_AT = exports.DELIVERY = void 0;
|
|
5
|
+
/** What a consumer does with a stream message. */
|
|
6
|
+
exports.DELIVERY = {
|
|
7
|
+
/** Handled; do not deliver again. */
|
|
8
|
+
ACK: 'ack',
|
|
9
|
+
/** Not yet actionable; deliver again after a delay. */
|
|
10
|
+
NAK: 'nak',
|
|
11
|
+
/** Never actionable; drop it. */
|
|
12
|
+
TERM: 'term',
|
|
13
|
+
};
|
|
14
|
+
/** Where a new consumer starts: only messages appended after it, or the whole retained history. */
|
|
15
|
+
exports.START_AT = { NEW: 'new', ALL: 'all' };
|
|
16
|
+
exports.MESSAGING_LOG = {
|
|
17
|
+
CONNECTED: 'messaging.provider.connected',
|
|
18
|
+
DISCONNECTED: 'messaging.provider.disconnected',
|
|
19
|
+
CONNECT_FAILED: 'messaging.provider.connect_failed',
|
|
20
|
+
STREAMS_UNAVAILABLE: 'messaging.provider.streams_unavailable',
|
|
21
|
+
};
|
|
22
|
+
/** Subjects Long Tail itself publishes under. */
|
|
23
|
+
exports.LT_SUBJECTS = {
|
|
24
|
+
EVENTS: 'lt.events',
|
|
25
|
+
ESCALATIONS: 'lt.events.system.escalation.>',
|
|
26
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The messaging registry: one provider, registered at startup and shared by
|
|
3
|
+
* every part of Long Tail that needs pub/sub or durable streams (events, the
|
|
4
|
+
* machine surface). Asking for messaging that is not configured fails loudly,
|
|
5
|
+
* because a caller that gets nothing back would believe it was handled.
|
|
6
|
+
*/
|
|
7
|
+
import type { LTMessagingProvider, LTPubSub, LTStreams } from '../../types/messaging';
|
|
8
|
+
declare class LTMessagingRegistry {
|
|
9
|
+
private current;
|
|
10
|
+
/** Register the provider; replaces any registered before. Call before connect(). */
|
|
11
|
+
register(provider: LTMessagingProvider): void;
|
|
12
|
+
get hasProvider(): boolean;
|
|
13
|
+
get provider(): LTMessagingProvider;
|
|
14
|
+
get pubsub(): LTPubSub;
|
|
15
|
+
get streams(): LTStreams;
|
|
16
|
+
connect(): Promise<void>;
|
|
17
|
+
disconnect(): Promise<void>;
|
|
18
|
+
/** Forget the provider (tests). */
|
|
19
|
+
clear(): void;
|
|
20
|
+
}
|
|
21
|
+
export declare const messagingRegistry: LTMessagingRegistry;
|
|
22
|
+
export { MemoryMessagingProvider } from './memory';
|
|
23
|
+
export { subjectMatches, isSubject } from './subjects';
|
|
24
|
+
export { DELIVERY, START_AT, MESSAGING_LOG, LT_SUBJECTS } from './constants';
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* The messaging registry: one provider, registered at startup and shared by
|
|
4
|
+
* every part of Long Tail that needs pub/sub or durable streams (events, the
|
|
5
|
+
* machine surface). Asking for messaging that is not configured fails loudly,
|
|
6
|
+
* because a caller that gets nothing back would believe it was handled.
|
|
7
|
+
*/
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.LT_SUBJECTS = exports.MESSAGING_LOG = exports.START_AT = exports.DELIVERY = exports.isSubject = exports.subjectMatches = exports.MemoryMessagingProvider = exports.messagingRegistry = void 0;
|
|
10
|
+
const logger_1 = require("../logger");
|
|
11
|
+
const constants_1 = require("./constants");
|
|
12
|
+
const NOT_CONFIGURED = 'No messaging provider is configured: set LT_MESSAGING=nats with LT_NATS_URL, or pass start({ messaging })';
|
|
13
|
+
class LTMessagingRegistry {
|
|
14
|
+
constructor() {
|
|
15
|
+
this.current = null;
|
|
16
|
+
}
|
|
17
|
+
/** Register the provider; replaces any registered before. Call before connect(). */
|
|
18
|
+
register(provider) {
|
|
19
|
+
this.current = provider;
|
|
20
|
+
}
|
|
21
|
+
get hasProvider() {
|
|
22
|
+
return this.current !== null;
|
|
23
|
+
}
|
|
24
|
+
get provider() {
|
|
25
|
+
if (!this.current)
|
|
26
|
+
throw new Error(NOT_CONFIGURED);
|
|
27
|
+
return this.current;
|
|
28
|
+
}
|
|
29
|
+
get pubsub() {
|
|
30
|
+
return this.provider.pubsub;
|
|
31
|
+
}
|
|
32
|
+
get streams() {
|
|
33
|
+
const streams = this.provider.streams;
|
|
34
|
+
if (!streams)
|
|
35
|
+
throw new Error(`Messaging provider ${this.provider.name} has no durable streams; enable JetStream on its NATS server`);
|
|
36
|
+
return streams;
|
|
37
|
+
}
|
|
38
|
+
async connect() {
|
|
39
|
+
if (!this.current || this.current.connected)
|
|
40
|
+
return;
|
|
41
|
+
await this.current.connect();
|
|
42
|
+
logger_1.loggerRegistry.info(`${constants_1.MESSAGING_LOG.CONNECTED} provider=${this.current.name} streams=${this.current.streams ? 'yes' : 'no'}`);
|
|
43
|
+
}
|
|
44
|
+
async disconnect() {
|
|
45
|
+
if (!this.current?.connected)
|
|
46
|
+
return;
|
|
47
|
+
await this.current.disconnect();
|
|
48
|
+
logger_1.loggerRegistry.info(`${constants_1.MESSAGING_LOG.DISCONNECTED} provider=${this.current.name}`);
|
|
49
|
+
}
|
|
50
|
+
/** Forget the provider (tests). */
|
|
51
|
+
clear() {
|
|
52
|
+
this.current = null;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
exports.messagingRegistry = new LTMessagingRegistry();
|
|
56
|
+
var memory_1 = require("./memory");
|
|
57
|
+
Object.defineProperty(exports, "MemoryMessagingProvider", { enumerable: true, get: function () { return memory_1.MemoryMessagingProvider; } });
|
|
58
|
+
var subjects_1 = require("./subjects");
|
|
59
|
+
Object.defineProperty(exports, "subjectMatches", { enumerable: true, get: function () { return subjects_1.subjectMatches; } });
|
|
60
|
+
Object.defineProperty(exports, "isSubject", { enumerable: true, get: function () { return subjects_1.isSubject; } });
|
|
61
|
+
var constants_2 = require("./constants");
|
|
62
|
+
Object.defineProperty(exports, "DELIVERY", { enumerable: true, get: function () { return constants_2.DELIVERY; } });
|
|
63
|
+
Object.defineProperty(exports, "START_AT", { enumerable: true, get: function () { return constants_2.START_AT; } });
|
|
64
|
+
Object.defineProperty(exports, "MESSAGING_LOG", { enumerable: true, get: function () { return constants_2.MESSAGING_LOG; } });
|
|
65
|
+
Object.defineProperty(exports, "LT_SUBJECTS", { enumerable: true, get: function () { return constants_2.LT_SUBJECTS; } });
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Messaging in memory, for tests and single-process development. Pub/sub fans
|
|
3
|
+
* out to every matching subscriber. Streams keep each message until it ages
|
|
4
|
+
* out, and deliver to each consumer one at a time in order; a nak'd message
|
|
5
|
+
* comes back after its delay while later messages go on, as JetStream does.
|
|
6
|
+
*/
|
|
7
|
+
import type { LTMessagingProvider, LTPubSub, LTStreams } from '../../types/messaging';
|
|
8
|
+
export declare class MemoryMessagingProvider implements LTMessagingProvider {
|
|
9
|
+
readonly name = "memory";
|
|
10
|
+
readonly pubsub: LTPubSub;
|
|
11
|
+
readonly streams: LTStreams;
|
|
12
|
+
private live;
|
|
13
|
+
get connected(): boolean;
|
|
14
|
+
connect(): Promise<void>;
|
|
15
|
+
disconnect(): Promise<void>;
|
|
16
|
+
}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Messaging in memory, for tests and single-process development. Pub/sub fans
|
|
4
|
+
* out to every matching subscriber. Streams keep each message until it ages
|
|
5
|
+
* out, and deliver to each consumer one at a time in order; a nak'd message
|
|
6
|
+
* comes back after its delay while later messages go on, as JetStream does.
|
|
7
|
+
*/
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.MemoryMessagingProvider = void 0;
|
|
10
|
+
const constants_1 = require("./constants");
|
|
11
|
+
const subjects_1 = require("./subjects");
|
|
12
|
+
const DEFAULT_NAK_DELAY_MS = 1_000;
|
|
13
|
+
class MemoryPubSub {
|
|
14
|
+
constructor() {
|
|
15
|
+
this.subs = new Set();
|
|
16
|
+
}
|
|
17
|
+
async publish(subject, payload) {
|
|
18
|
+
for (const sub of [...this.subs])
|
|
19
|
+
if ((0, subjects_1.subjectMatches)(sub.filter, subject))
|
|
20
|
+
sub.handler({ subject, payload });
|
|
21
|
+
}
|
|
22
|
+
subscribe(filter, handler) {
|
|
23
|
+
const sub = { filter, handler };
|
|
24
|
+
this.subs.add(sub);
|
|
25
|
+
return { close: async () => { this.subs.delete(sub); } };
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
class MemoryStreams {
|
|
29
|
+
constructor() {
|
|
30
|
+
this.streams = new Map();
|
|
31
|
+
}
|
|
32
|
+
async ensureStream(spec) {
|
|
33
|
+
const existing = this.streams.get(spec.name);
|
|
34
|
+
if (existing)
|
|
35
|
+
existing.spec = spec;
|
|
36
|
+
else
|
|
37
|
+
this.streams.set(spec.name, { spec, messages: [], seq: 0, consumers: new Map(), wake: new Set() });
|
|
38
|
+
}
|
|
39
|
+
async ensureConsumer(stream, spec) {
|
|
40
|
+
const s = this.stream(stream);
|
|
41
|
+
const existing = s.consumers.get(spec.name);
|
|
42
|
+
if (existing) {
|
|
43
|
+
existing.spec = { ...existing.spec, filterSubjects: spec.filterSubjects };
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
const floor = (spec.startAt ?? constants_1.START_AT.NEW) === constants_1.START_AT.ALL ? 0 : s.seq;
|
|
47
|
+
s.consumers.set(spec.name, { spec, floor, settled: new Set(), deliveries: new Map(), retryAt: new Map() });
|
|
48
|
+
}
|
|
49
|
+
async append(subject, payload) {
|
|
50
|
+
const now = Date.now();
|
|
51
|
+
for (const s of this.streams.values()) {
|
|
52
|
+
if (!s.spec.subjects.some((f) => (0, subjects_1.subjectMatches)(f, subject)))
|
|
53
|
+
continue;
|
|
54
|
+
s.messages.push({ seq: ++s.seq, subject, payload, at: now });
|
|
55
|
+
s.messages = s.messages.filter((m) => now - m.at <= s.spec.maxAgeMs);
|
|
56
|
+
for (const wake of [...s.wake])
|
|
57
|
+
wake();
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
async consume(stream, consumer, handler, opts = {}) {
|
|
61
|
+
const s = this.stream(stream);
|
|
62
|
+
const c = s.consumers.get(consumer);
|
|
63
|
+
if (!c)
|
|
64
|
+
throw new Error(`stream ${stream} has no consumer ${consumer}; ensure it first`);
|
|
65
|
+
const nakDelayMs = opts.nakDelayMs ?? DEFAULT_NAK_DELAY_MS;
|
|
66
|
+
let open = true;
|
|
67
|
+
let running = false;
|
|
68
|
+
let timer;
|
|
69
|
+
const next = () => {
|
|
70
|
+
const now = Date.now();
|
|
71
|
+
let waitMs;
|
|
72
|
+
for (const m of s.messages) {
|
|
73
|
+
if (m.seq <= c.floor || c.settled.has(m.seq))
|
|
74
|
+
continue;
|
|
75
|
+
if (!c.spec.filterSubjects.some((f) => (0, subjects_1.subjectMatches)(f, m.subject)))
|
|
76
|
+
continue;
|
|
77
|
+
const at = c.retryAt.get(m.seq);
|
|
78
|
+
if (at === undefined || at <= now)
|
|
79
|
+
return { message: m };
|
|
80
|
+
waitMs = Math.min(waitMs ?? Infinity, at - now);
|
|
81
|
+
}
|
|
82
|
+
return { waitMs };
|
|
83
|
+
};
|
|
84
|
+
const pump = async () => {
|
|
85
|
+
if (running || !open)
|
|
86
|
+
return;
|
|
87
|
+
running = true;
|
|
88
|
+
try {
|
|
89
|
+
for (let step = next(); open && step.message; step = next()) {
|
|
90
|
+
const m = step.message;
|
|
91
|
+
const deliveries = (c.deliveries.get(m.seq) ?? 0) + 1;
|
|
92
|
+
c.deliveries.set(m.seq, deliveries);
|
|
93
|
+
const outcome = await handler({ subject: m.subject, payload: m.payload, deliveries }).catch(() => constants_1.DELIVERY.NAK);
|
|
94
|
+
if (outcome === constants_1.DELIVERY.NAK) {
|
|
95
|
+
c.retryAt.set(m.seq, Date.now() + nakDelayMs);
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
c.settled.add(m.seq);
|
|
99
|
+
c.retryAt.delete(m.seq);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
const { waitMs } = next();
|
|
103
|
+
if (open && waitMs !== undefined) {
|
|
104
|
+
clearTimeout(timer);
|
|
105
|
+
timer = setTimeout(() => void pump(), Math.max(0, waitMs));
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
finally {
|
|
109
|
+
running = false;
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
const wake = () => void pump();
|
|
113
|
+
s.wake.add(wake);
|
|
114
|
+
void pump();
|
|
115
|
+
return {
|
|
116
|
+
close: async () => {
|
|
117
|
+
open = false;
|
|
118
|
+
clearTimeout(timer);
|
|
119
|
+
s.wake.delete(wake);
|
|
120
|
+
},
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
stream(name) {
|
|
124
|
+
const s = this.streams.get(name);
|
|
125
|
+
if (!s)
|
|
126
|
+
throw new Error(`no stream ${name}; ensure it first`);
|
|
127
|
+
return s;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
class MemoryMessagingProvider {
|
|
131
|
+
constructor() {
|
|
132
|
+
this.name = 'memory';
|
|
133
|
+
this.pubsub = new MemoryPubSub();
|
|
134
|
+
this.streams = new MemoryStreams();
|
|
135
|
+
this.live = false;
|
|
136
|
+
}
|
|
137
|
+
get connected() {
|
|
138
|
+
return this.live;
|
|
139
|
+
}
|
|
140
|
+
async connect() {
|
|
141
|
+
this.live = true;
|
|
142
|
+
}
|
|
143
|
+
async disconnect() {
|
|
144
|
+
this.live = false;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
exports.MemoryMessagingProvider = MemoryMessagingProvider;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JetStream rights scoped to named streams and one durable consumer: look up,
|
|
3
|
+
* create and update those streams, create, read and acknowledge through that
|
|
4
|
+
* consumer, and nothing else. Deleting or purging a stream is not granted.
|
|
5
|
+
*/
|
|
6
|
+
export interface JsScope {
|
|
7
|
+
streams: string[];
|
|
8
|
+
durable: string;
|
|
9
|
+
}
|
|
10
|
+
export declare function jsPermissions({ streams, durable }: JsScope): {
|
|
11
|
+
publish: string[];
|
|
12
|
+
subscribe: string[];
|
|
13
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* JetStream rights scoped to named streams and one durable consumer: look up,
|
|
4
|
+
* create and update those streams, create, read and acknowledge through that
|
|
5
|
+
* consumer, and nothing else. Deleting or purging a stream is not granted.
|
|
6
|
+
*/
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
exports.jsPermissions = jsPermissions;
|
|
9
|
+
function jsPermissions({ streams, durable }) {
|
|
10
|
+
const perStream = streams.flatMap((s) => [
|
|
11
|
+
`$JS.API.STREAM.INFO.${s}`,
|
|
12
|
+
`$JS.API.STREAM.CREATE.${s}`,
|
|
13
|
+
`$JS.API.STREAM.UPDATE.${s}`,
|
|
14
|
+
`$JS.API.CONSUMER.INFO.${s}.${durable}`,
|
|
15
|
+
`$JS.API.CONSUMER.CREATE.${s}.${durable}`,
|
|
16
|
+
`$JS.API.CONSUMER.CREATE.${s}.${durable}.>`,
|
|
17
|
+
`$JS.API.CONSUMER.DURABLE.CREATE.${s}.${durable}`,
|
|
18
|
+
`$JS.API.CONSUMER.MSG.NEXT.${s}.${durable}`,
|
|
19
|
+
`$JS.ACK.${s}.${durable}.>`,
|
|
20
|
+
]);
|
|
21
|
+
return { publish: ['$JS.API.INFO', ...perStream], subscribe: ['_INBOX.>'] };
|
|
22
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Messaging on NATS. One connection carries pub/sub (core NATS subjects) and,
|
|
3
|
+
* when the server runs JetStream for this account, durable streams. Without
|
|
4
|
+
* JetStream the provider still serves pub/sub and reports no streams.
|
|
5
|
+
*/
|
|
6
|
+
import type { LTMessagingProvider, LTPubSub, LTStreams } from '../../../types/messaging';
|
|
7
|
+
export interface NatsMessagingOptions {
|
|
8
|
+
url: string;
|
|
9
|
+
/** Token auth, or user and password; neither for an open server. */
|
|
10
|
+
token?: string;
|
|
11
|
+
user?: string;
|
|
12
|
+
password?: string;
|
|
13
|
+
/** The connection's name on the server. */
|
|
14
|
+
name?: string;
|
|
15
|
+
}
|
|
16
|
+
export declare class NatsMessagingProvider implements LTMessagingProvider {
|
|
17
|
+
private readonly opts;
|
|
18
|
+
readonly name = "nats";
|
|
19
|
+
readonly pubsub: LTPubSub;
|
|
20
|
+
private nc;
|
|
21
|
+
private durable;
|
|
22
|
+
constructor(opts: NatsMessagingOptions);
|
|
23
|
+
get connected(): boolean;
|
|
24
|
+
get streams(): LTStreams | null;
|
|
25
|
+
connect(): Promise<void>;
|
|
26
|
+
disconnect(): Promise<void>;
|
|
27
|
+
/** Streams when the account has JetStream; null otherwise, and pub/sub still serves. */
|
|
28
|
+
private jetStream;
|
|
29
|
+
private connection;
|
|
30
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Messaging on NATS. One connection carries pub/sub (core NATS subjects) and,
|
|
4
|
+
* when the server runs JetStream for this account, durable streams. Without
|
|
5
|
+
* JetStream the provider still serves pub/sub and reports no streams.
|
|
6
|
+
*/
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
exports.NatsMessagingProvider = void 0;
|
|
9
|
+
const nats_1 = require("nats");
|
|
10
|
+
const logger_1 = require("../../logger");
|
|
11
|
+
const constants_1 = require("../constants");
|
|
12
|
+
const streams_1 = require("./streams");
|
|
13
|
+
class NatsPubSub {
|
|
14
|
+
constructor(nc) {
|
|
15
|
+
this.nc = nc;
|
|
16
|
+
}
|
|
17
|
+
async publish(subject, payload) {
|
|
18
|
+
this.nc().publish(subject, payload);
|
|
19
|
+
}
|
|
20
|
+
subscribe(filter, handler) {
|
|
21
|
+
const sub = this.nc().subscribe(filter, {
|
|
22
|
+
callback: (err, m) => { if (!err)
|
|
23
|
+
handler({ subject: m.subject, payload: (0, streams_1.payloadOf)(m) }); },
|
|
24
|
+
});
|
|
25
|
+
return { close: async () => { sub.unsubscribe(); } };
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
class NatsMessagingProvider {
|
|
29
|
+
constructor(opts) {
|
|
30
|
+
this.opts = opts;
|
|
31
|
+
this.name = 'nats';
|
|
32
|
+
this.pubsub = new NatsPubSub(() => this.connection());
|
|
33
|
+
this.nc = null;
|
|
34
|
+
this.durable = null;
|
|
35
|
+
}
|
|
36
|
+
get connected() {
|
|
37
|
+
return !!this.nc && !this.nc.isClosed();
|
|
38
|
+
}
|
|
39
|
+
get streams() {
|
|
40
|
+
return this.durable;
|
|
41
|
+
}
|
|
42
|
+
async connect() {
|
|
43
|
+
if (this.connected)
|
|
44
|
+
return;
|
|
45
|
+
const { url, token, user, password, name } = this.opts;
|
|
46
|
+
this.nc = await (0, nats_1.connect)({ servers: url, token, user, pass: password, name });
|
|
47
|
+
this.durable = await this.jetStream(this.nc);
|
|
48
|
+
}
|
|
49
|
+
async disconnect() {
|
|
50
|
+
if (!this.nc)
|
|
51
|
+
return;
|
|
52
|
+
await this.nc.drain();
|
|
53
|
+
this.nc = null;
|
|
54
|
+
this.durable = null;
|
|
55
|
+
}
|
|
56
|
+
/** Streams when the account has JetStream; null otherwise, and pub/sub still serves. */
|
|
57
|
+
async jetStream(nc) {
|
|
58
|
+
try {
|
|
59
|
+
const jsm = await nc.jetstreamManager();
|
|
60
|
+
await jsm.getAccountInfo();
|
|
61
|
+
return new streams_1.NatsStreams(jsm, nc.jetstream());
|
|
62
|
+
}
|
|
63
|
+
catch (err) {
|
|
64
|
+
const reason = String(err?.message ?? err?.code ?? err);
|
|
65
|
+
if (!/jetstream not enabled|503|no responders/i.test(reason)) {
|
|
66
|
+
logger_1.loggerRegistry.warn(`${constants_1.MESSAGING_LOG.STREAMS_UNAVAILABLE} provider=${this.name} reason=${reason}`);
|
|
67
|
+
}
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
connection() {
|
|
72
|
+
if (!this.nc)
|
|
73
|
+
throw new Error('NATS messaging is not connected; call connect() first');
|
|
74
|
+
return this.nc;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
exports.NatsMessagingProvider = NatsMessagingProvider;
|