ts-server-lib 0.0.17

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/db/TSRQW.d.ts ADDED
@@ -0,0 +1,271 @@
1
+ /**
2
+ * TSRQW — Redis Queue Worker (standalone / sentinel / cluster).
3
+ *
4
+ * R = Redis · Q = Queue · W = Worker
5
+ *
6
+ * Single class unifying queue storage (Redis Streams) + worker lifecycle
7
+ * (events, retry, dead-letter, cron schedule, thread pool).
8
+ *
9
+ * Backend: Redis Streams (XADD / XREADGROUP / XAUTOCLAIM / XACK).
10
+ * - All keys hash-tagged → zero CROSSSLOT on any topology.
11
+ * - No Lua scripts, no EVALSHA, no SCRIPT LOAD broadcast.
12
+ * - Consumer-group model: crashed consumers recovered automatically via XAUTOCLAIM.
13
+ * - Built-in dead-letter stream (`{ns:name}:dlq`).
14
+ *
15
+ * Performance:
16
+ * - send: 1 roundtrip (XADD MAXLEN ~)
17
+ * - receive: 1 roundtrip (XREADGROUP); XAUTOCLAIM throttled to reclaimIntervalMs
18
+ * - node-redis v5 auto-pipelines concurrent callers → linear throughput scaling
19
+ *
20
+ * Events: new · ready · completed · retry · exceeded · failed · deleted · error
21
+ */
22
+ import { EventEmitter } from 'events';
23
+ import { AsyncResource } from 'async_hooks';
24
+ import { CronJob } from 'cron';
25
+ import { Worker } from 'worker_threads';
26
+ import type { TSRedisClient } from './TSRedis';
27
+ export declare enum TSRQWEvents {
28
+ new = "new",
29
+ ready = "ready",
30
+ deleted = "deleted",
31
+ completed = "completed",
32
+ retry = "retry",
33
+ exceeded = "exceeded",
34
+ failed = "failed",
35
+ error = "error"
36
+ }
37
+ export type TSRQWStatus = 'idle' | 'completed' | 'retry' | 'failed';
38
+ export type TSRQWHandler = (message: string, meta: {
39
+ id: string;
40
+ rc: number;
41
+ fr: number;
42
+ }) => Promise<boolean | void> | boolean | void;
43
+ export type TSRQWCallback = (data?: unknown) => Promise<(data?: unknown) => Promise<unknown>>;
44
+ export interface TSRQWReceiveResult {
45
+ status: TSRQWStatus;
46
+ message?: string;
47
+ meta?: {
48
+ id: string;
49
+ rc: number;
50
+ fr: number;
51
+ };
52
+ }
53
+ export interface TSRQWAttributes {
54
+ msgs: number;
55
+ hiddenmsgs: number;
56
+ totalsent: number;
57
+ totalrecv: number;
58
+ }
59
+ /** Raw message shape returned by receiveRaw() and the adaptive consumer API. */
60
+ export interface TSRQWMessage {
61
+ id: string;
62
+ message: string;
63
+ rc: number;
64
+ fr: number;
65
+ sent: number;
66
+ }
67
+ /** Queue health state — derived from snapshot fields, no extra Redis calls. */
68
+ export type TSRQWHealth = 'empty' | 'healthy' | 'lagging' | 'stuck';
69
+ /**
70
+ * Rich Streams-native snapshot — call from dashboards / platform stats.
71
+ * Extends TSRQWAttributes with fields only Redis Streams can provide.
72
+ * NOT for the hot path — use attributes() there (2 roundtrips).
73
+ */
74
+ export interface TSRQWSnapshot extends TSRQWAttributes {
75
+ /** Messages in stream not yet delivered to this group — real queue backlog. */
76
+ lag: number;
77
+ /** Active consumers in this group (0 = no worker connected). */
78
+ consumers: number;
79
+ /** Dead-letter queue depth — poison messages accumulating. */
80
+ dlqDepth: number;
81
+ /** Exact total-sent counter, unaffected by MAXLEN trim (entries-added, Redis 7.2+). */
82
+ totalsentExact: number;
83
+ /** Unix ms of last message delivered to this group (0 = never). */
84
+ lastDeliveredMs: number;
85
+ /** Unix ms of oldest in-flight (PEL) message (0 = nothing pending). */
86
+ oldestPendingMs: number;
87
+ /** Unix ms of last XADD — 0 if stream has no entries. Idle indicator. */
88
+ lastAddedMs: number;
89
+ /** lag + hiddenmsgs — total messages in the system not yet completed. */
90
+ backpressure: number;
91
+ /** (totalrecv / totalsentExact) * 100 — % of sent messages consumed. 100 = fully drained. */
92
+ consumptionRate: number;
93
+ /** (dlqDepth / totalsentExact) * 100 — % of messages dead-lettered. */
94
+ dlqRate: number;
95
+ /**
96
+ * empty — no messages ever sent.
97
+ * healthy — no lag, nothing stuck.
98
+ * lagging — lag > 0 (messages waiting to be delivered to this group).
99
+ * stuck — oldest pending entry has been in-flight > 2 min.
100
+ */
101
+ health: TSRQWHealth;
102
+ }
103
+ export interface TSRQWOptions {
104
+ /** Logical queue name (default: 'queue'). */
105
+ name?: string;
106
+ /** Redis key namespace prefix (default: 'tsq'). */
107
+ ns?: string;
108
+ /** Max delivery attempts before dead-lettering (default: 3). */
109
+ attempts?: number;
110
+ /** Consumer group name (default: ns). One group per service. */
111
+ group?: string;
112
+ /** Consumer name (default: auto-generated per process). */
113
+ consumer?: string;
114
+ /** Approximate stream MAXLEN (default: 100_000). */
115
+ maxLen?: number;
116
+ /** How often to run XAUTOCLAIM for stale-consumer recovery (ms, default: 2_000). */
117
+ reclaimIntervalMs?: number;
118
+ }
119
+ declare const kTaskInfo: unique symbol;
120
+ interface WorkerWithTaskInfo extends Worker {
121
+ [kTaskInfo]?: TSRQWPoolTask | null;
122
+ }
123
+ declare class TSRQWPoolTask extends AsyncResource {
124
+ callback: (err: Error | null, result: unknown) => void;
125
+ constructor(callback: (err: Error | null, result: unknown) => void);
126
+ done(err: Error | null, result: unknown): void;
127
+ }
128
+ export declare class TSRQWPool extends EventEmitter {
129
+ factor: number;
130
+ callback: TSRQWCallback;
131
+ wd?: unknown | undefined;
132
+ workers: WorkerWithTaskInfo[];
133
+ freeWorkers: WorkerWithTaskInfo[];
134
+ tasks: Array<{
135
+ task: unknown;
136
+ callback: (err: Error | null, result: unknown) => void;
137
+ }>;
138
+ constructor(factor: number | undefined, callback: TSRQWCallback, wd?: unknown | undefined);
139
+ work: (data: unknown) => Promise<unknown>;
140
+ update(data: Record<string, unknown> & {
141
+ _wd_updated?: number;
142
+ }): void;
143
+ close(): void;
144
+ private _runTask;
145
+ private _addWorker;
146
+ }
147
+ export declare class TSRQW extends EventEmitter {
148
+ protected readonly redis: TSRedisClient;
149
+ protected readonly qname: string;
150
+ protected readonly rawNs: string;
151
+ protected readonly ns: string;
152
+ protected readonly group: string;
153
+ protected readonly consumer: string;
154
+ protected readonly maxLen: number;
155
+ protected readonly attempts: number;
156
+ protected readonly reclaimIntervalMs: number;
157
+ connected: boolean;
158
+ private offlineBuffer;
159
+ private ensured;
160
+ private ensurePromise;
161
+ private lastReclaimAt;
162
+ /** Resolves when the consumer group is ready and the first offline drain completes. */
163
+ readonly initialized: Promise<void>;
164
+ constructor(redis: TSRedisClient, options?: TSRQWOptions);
165
+ private _streamKey;
166
+ private _dlqKey;
167
+ private _init;
168
+ private _ensureGroup;
169
+ private _createGroup;
170
+ /** Reset the ensured flag so the next receive call recreates stream+group.
171
+ * Call this when XREADGROUP returns NOGROUP (stream/group wiped, e.g. after Redis flush). */
172
+ resetEnsured(): void;
173
+ /**
174
+ * Append a message. Returns the stream entry ID for immediate sends;
175
+ * undefined for buffered (pre-connect) or delayed sends.
176
+ */
177
+ send(message: string, delay?: number): Promise<string | undefined>;
178
+ private _xadd;
179
+ receiveWithStatus({ handle, visibility: vt, }: {
180
+ handle: TSRQWHandler;
181
+ visibility?: number;
182
+ }): Promise<TSRQWReceiveResult>;
183
+ receive(opts: {
184
+ handle: TSRQWHandler;
185
+ visibility?: number;
186
+ }): Promise<void>;
187
+ /**
188
+ * Blocking receive — XREADGROUP with BLOCK so the connection sleeps until a message
189
+ * arrives, then wakes immediately. Zero polling overhead vs CronJob polling.
190
+ *
191
+ * Preferred consumer pattern for durable/recovery consumers (e.g. integration event
192
+ * streams). Each call blocks for up to `blockMs` ms. On timeout: returns null (no
193
+ * message). On message: calls `handler`, ACKs on success, leaves in PEL on failure.
194
+ *
195
+ * Run in a `while (running) { await q.receiveBlocking(handler) }` loop per channel.
196
+ * The loop is woken by Redis as soon as a message is written — no polling overhead.
197
+ *
198
+ * @param handler - Return true to ACK, false to leave in PEL (retry after vt).
199
+ * @param vt - Visibility timeout seconds.
200
+ * @param blockMs - Max ms to wait for a message (Redis BLOCK option). Default 2 000.
201
+ * Keep short (≤5s) when using a shared cluster client — BLOCK holds
202
+ * the master socket and prevents other commands from executing on it.
203
+ */
204
+ receiveBlocking(handler: (message: string, meta: {
205
+ id: string;
206
+ rc: number;
207
+ fr: number;
208
+ }) => Promise<boolean>, vt?: number, blockMs?: number): Promise<void>;
209
+ /** Pull one raw message without calling a handler or auto-acking. */
210
+ receiveRaw(vt?: number): Promise<TSRQWMessage | null>;
211
+ /** XACK a message by ID. Returns true when the PEL entry was removed. */
212
+ ack(id: string): Promise<boolean>;
213
+ /**
214
+ * XACK multiple messages in one roundtrip.
215
+ * All IDs must belong to this stream — guaranteed by the caller holding them from receiveRaw/receiveRawBatch.
216
+ * Returns the number of entries removed from the PEL.
217
+ */
218
+ ackBatch(ids: string[]): Promise<number>;
219
+ /**
220
+ * Pull up to `count` messages in one roundtrip (XREADGROUP COUNT N).
221
+ * Reclaim path uses a single XPENDING RANGE for all stale entries — no per-message round-trips.
222
+ * Returns an empty array when the queue is idle. Caller owns ack/deadLetter for each message.
223
+ */
224
+ receiveRawBatch(vt?: number, count?: number): Promise<TSRQWMessage[]>;
225
+ private _receiveRawBatchImpl;
226
+ /** Move a message to the `:dlq` stream and XACK the source. */
227
+ deadLetter(id: string, message: string, rc: number): Promise<void>;
228
+ private _readOne;
229
+ private _reclaimOne;
230
+ private _deliveryCount;
231
+ private _ack;
232
+ private _moveToDeadLetter;
233
+ private _msFromId;
234
+ attributes(): Promise<TSRQWAttributes | null>;
235
+ /**
236
+ * Rich Streams-native snapshot — for dashboards, platform stats, admin UI.
237
+ * 4–5 parallel roundtrips (XLEN×2 + XINFO STREAM + XINFO GROUPS + optional XPENDING).
238
+ * Do NOT call on the hot message path — use attributes() there.
239
+ */
240
+ snapshot(): Promise<TSRQWSnapshot | null>;
241
+ private _drainOffline;
242
+ /** List all queue names registered in a namespace (reads from `{ns:QUEUES}` index set). */
243
+ static listQueues(redis: TSRedisClient, ns: string): Promise<string[]>;
244
+ /**
245
+ * Read-only aggregate snapshot across ALL consumer groups on a stream — no XGROUP CREATE.
246
+ * Use from dashboards instead of `new TSRQW(...).snapshot()` to avoid creating phantom groups.
247
+ *
248
+ * Aggregation rules (correct for fan-out / multi-group topologies):
249
+ * - lag : max across groups (worst consumer defines backpressure)
250
+ * - consumers : sum across groups (total workers active)
251
+ * - totalrecv : max entries-read across groups (how far the fastest consumer has reached)
252
+ * - lastDeliveredMs: max last-delivered-id ms (most-recently-delivered across groups)
253
+ * - dlqRate : dlqDepth / totalsentExact capped at entries-read by the most-advanced group
254
+ * (avoids 100% false-positive when DLQ accumulated across stream recreations)
255
+ *
256
+ * Returns null when the stream does not exist.
257
+ */
258
+ static snapshotFromGroups(redis: TSRedisClient, ns: string, name: string): Promise<TSRQWSnapshot | null>;
259
+ private static _msFromIdStatic;
260
+ static schedule({ onTick, onComplete, runOnInit, cronTime, context, start, timeZone, }: {
261
+ onTick?: () => void;
262
+ onComplete?: (() => void) | null;
263
+ runOnInit?: boolean;
264
+ cronTime?: string;
265
+ context?: unknown;
266
+ start?: boolean;
267
+ timeZone?: string;
268
+ }): CronJob;
269
+ static worker(cb: TSRQWCallback, factor?: number, wd?: unknown): TSRQWPool;
270
+ }
271
+ export {};