ocpp-ws-io 2.1.7 → 2.1.9
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 -1
- package/dist/adapters/redis.d.mts +2 -1
- package/dist/adapters/redis.d.ts +2 -1
- package/dist/adapters/redis.js +27 -6
- package/dist/adapters/redis.js.map +1 -1
- package/dist/adapters/redis.mjs +27 -6
- package/dist/adapters/redis.mjs.map +1 -1
- package/dist/browser.d.mts +1129 -6
- package/dist/browser.d.ts +1129 -6
- package/dist/browser.js +34 -20
- package/dist/browser.js.map +1 -1
- package/dist/browser.mjs +34 -20
- package/dist/browser.mjs.map +1 -1
- package/dist/index-C0mn42-8.d.mts +161 -0
- package/dist/index-s9f97CmV.d.ts +161 -0
- package/dist/index.d.mts +131 -291
- package/dist/index.d.ts +131 -291
- package/dist/index.js +658 -78
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +672 -80
- package/dist/index.mjs.map +1 -1
- package/dist/plugins.d.mts +246 -0
- package/dist/plugins.d.ts +246 -0
- package/dist/plugins.js +370 -0
- package/dist/plugins.js.map +1 -0
- package/dist/plugins.mjs +350 -0
- package/dist/plugins.mjs.map +1 -0
- package/dist/{index-BRblF1XV.d.mts → types-BZXEmDQ1.d.mts} +468 -89
- package/dist/{index-BRblF1XV.d.ts → types-BZXEmDQ1.d.ts} +468 -89
- package/package.json +13 -9
- package/assets/banner.svg +0 -31
package/README.md
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<p align="center">
|
|
2
|
-
<img src="assets/banner.svg" alt="ocpp-ws-io" width="420" />
|
|
2
|
+
<img src="https://raw.githubusercontent.com/rohittiwari-dev/ocpp-ws-io/main/assets/banner.svg" alt="ocpp-ws-io" width="420" />
|
|
3
3
|
</p>
|
|
4
4
|
|
|
5
5
|
> built with TypeScript — supports OCPP 1.6, 2.0.1, and 2.1 with optional JSON schema validation, all security profiles, clustering support, and structured logging powered by [voltlog-io](https://ocpp-ws-io.rohittiwari.me/docs/voltlog-io).
|
package/dist/adapters/redis.d.ts
CHANGED
package/dist/adapters/redis.js
CHANGED
|
@@ -287,6 +287,9 @@ var RedisAdapter = class {
|
|
|
287
287
|
_unsubReconnect;
|
|
288
288
|
// Stored presence entries for rehydration on reconnect
|
|
289
289
|
_presenceCache = /* @__PURE__ */ new Map();
|
|
290
|
+
// Connection pool
|
|
291
|
+
_driverPool;
|
|
292
|
+
_nextPoolIndex;
|
|
290
293
|
constructor(options) {
|
|
291
294
|
this._prefix = options.prefix ?? "ocpp-ws-io:";
|
|
292
295
|
this._streamMaxLen = options.streamMaxLen ?? 1e3;
|
|
@@ -297,6 +300,14 @@ var RedisAdapter = class {
|
|
|
297
300
|
options.subClient,
|
|
298
301
|
options.blockingClient
|
|
299
302
|
);
|
|
303
|
+
const poolSize = options.poolSize ?? 1;
|
|
304
|
+
this._driverPool = [this._driver];
|
|
305
|
+
this._nextPoolIndex = 0;
|
|
306
|
+
if (poolSize > 1 && options.driverFactory) {
|
|
307
|
+
for (let i = 1; i < poolSize; i++) {
|
|
308
|
+
this._driverPool.push(options.driverFactory());
|
|
309
|
+
}
|
|
310
|
+
}
|
|
300
311
|
if (this._driver.onError) {
|
|
301
312
|
this._unsubError = this._driver.onError((err) => {
|
|
302
313
|
console.error("[RedisAdapter] Redis error:", err.message);
|
|
@@ -309,6 +320,13 @@ var RedisAdapter = class {
|
|
|
309
320
|
});
|
|
310
321
|
}
|
|
311
322
|
}
|
|
323
|
+
/** Get the next driver from the pool (round-robin) */
|
|
324
|
+
_getPoolDriver() {
|
|
325
|
+
if (this._driverPool.length === 1) return this._driver;
|
|
326
|
+
const driver = this._driverPool[this._nextPoolIndex];
|
|
327
|
+
this._nextPoolIndex = (this._nextPoolIndex + 1) % this._driverPool.length;
|
|
328
|
+
return driver;
|
|
329
|
+
}
|
|
312
330
|
async publish(channel, data) {
|
|
313
331
|
const prefixedChannel = this._prefix + channel;
|
|
314
332
|
const payload = data;
|
|
@@ -319,11 +337,12 @@ var RedisAdapter = class {
|
|
|
319
337
|
}
|
|
320
338
|
const message = JSON.stringify(data);
|
|
321
339
|
if (channel.startsWith("ocpp:node:")) {
|
|
322
|
-
|
|
323
|
-
await
|
|
340
|
+
const poolDriver = this._getPoolDriver();
|
|
341
|
+
await poolDriver.xadd(prefixedChannel, { message }, this._streamMaxLen);
|
|
342
|
+
await poolDriver.expire(prefixedChannel, this._streamTtlSeconds).catch(() => {
|
|
324
343
|
});
|
|
325
344
|
} else {
|
|
326
|
-
await this.
|
|
345
|
+
await this._getPoolDriver().publish(prefixedChannel, message);
|
|
327
346
|
}
|
|
328
347
|
}
|
|
329
348
|
async publishBatch(messages) {
|
|
@@ -340,13 +359,15 @@ var RedisAdapter = class {
|
|
|
340
359
|
}
|
|
341
360
|
const promises = [];
|
|
342
361
|
if (streamMessages.length > 0) {
|
|
343
|
-
promises.push(
|
|
362
|
+
promises.push(
|
|
363
|
+
this._getPoolDriver().xaddBatch(streamMessages, this._streamMaxLen)
|
|
364
|
+
);
|
|
344
365
|
}
|
|
345
366
|
if (broadcastMessages.length > 0) {
|
|
346
367
|
promises.push(
|
|
347
368
|
Promise.all(
|
|
348
369
|
broadcastMessages.map(
|
|
349
|
-
(bm) => this.
|
|
370
|
+
(bm) => this._getPoolDriver().publish(bm.channel, bm.message)
|
|
350
371
|
)
|
|
351
372
|
).then(() => {
|
|
352
373
|
})
|
|
@@ -391,7 +412,7 @@ var RedisAdapter = class {
|
|
|
391
412
|
this._sequenceCounters.clear();
|
|
392
413
|
if (this._unsubError) this._unsubError();
|
|
393
414
|
if (this._unsubReconnect) this._unsubReconnect();
|
|
394
|
-
await this.
|
|
415
|
+
await Promise.allSettled(this._driverPool.map((d) => d.disconnect()));
|
|
395
416
|
}
|
|
396
417
|
_handleMessage(channel, message) {
|
|
397
418
|
const handlers = this._handlers.get(channel);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/adapters/redis/index.ts","../../src/adapters/redis/helpers.ts"],"sourcesContent":["import type { EventAdapterInterface } from \"../../types.js\";\nimport {\n createDriver,\n type RedisLikeClient,\n type RedisPubSubDriver,\n} from \"./helpers.js\";\n\nexport interface RedisAdapterOptions {\n /** Redis client for publishing */\n pubClient: RedisLikeClient;\n /** Redis client for subscribing (must be a separate connection) */\n subClient: RedisLikeClient;\n /** Redis client for blocking stream operations (recommended for reliability) */\n blockingClient?: RedisLikeClient;\n /** Optional key prefix for channels (default: 'ocpp-ws-io:') */\n prefix?: string;\n /** StreamMaxLen for trimming (default: 1000) */\n streamMaxLen?: number;\n /**\n * TTL in seconds for ephemeral stream keys (default: 300).\n * Prevents abandoned channel keys from leaking memory in Redis.\n */\n streamTtlSeconds?: number;\n /**\n * Presence TTL in seconds (default: 300).\n * Used for batch presence heartbeat pipeline.\n */\n presenceTtlSeconds?: number;\n}\n\n/**\n * Redis adapter for cross-process event distribution.\n *\n * Supports `ioredis` and `node-redis` (v4+).\n * Uses Redis Streams for reliable unicast (node-to-node) and Pub/Sub for broadcast.\n */\nexport class RedisAdapter implements EventAdapterInterface {\n private _driver: RedisPubSubDriver;\n private _prefix: string;\n private _streamMaxLen: number;\n private _streamTtlSeconds: number;\n private _presenceTtlSeconds: number;\n private _handlers = new Map<string, Set<(data: unknown) => void>>();\n private _streamOffsets = new Map<string, string>(); // streamKey -> lastId\n private _streams = new Set<string>(); // Active streams to poll\n private _polling = false;\n private _closed = false;\n\n // Per-stream sequence counter for message ordering\n private _sequenceCounters = new Map<string, number>();\n\n // Rehydration callbacks\n private _unsubError?: () => void;\n private _unsubReconnect?: () => void;\n\n // Stored presence entries for rehydration on reconnect\n private _presenceCache = new Map<string, { nodeId: string; ttl: number }>();\n\n constructor(options: RedisAdapterOptions) {\n this._prefix = options.prefix ?? \"ocpp-ws-io:\";\n this._streamMaxLen = options.streamMaxLen ?? 1000;\n this._streamTtlSeconds = options.streamTtlSeconds ?? 300;\n this._presenceTtlSeconds = options.presenceTtlSeconds ?? 300;\n this._driver = createDriver(\n options.pubClient,\n options.subClient,\n options.blockingClient,\n );\n\n // Redis Failure Rehydration — listen for errors and re-sync on reconnect\n if (this._driver.onError) {\n this._unsubError = this._driver.onError((err) => {\n // Log for observability — consumers can attach their own logger\n console.error(\"[RedisAdapter] Redis error:\", err.message);\n });\n }\n if (this._driver.onReconnect) {\n this._unsubReconnect = this._driver.onReconnect(() => {\n this._rehydratePresence().catch(() => {});\n });\n }\n }\n\n async publish(channel: string, data: unknown): Promise<void> {\n const prefixedChannel = this._prefix + channel;\n\n // Attach sequence ID to unicast messages for ordering\n const payload = data as Record<string, unknown> | null;\n if (\n payload &&\n typeof payload === \"object\" &&\n channel.startsWith(\"ocpp:node:\")\n ) {\n const seq = (this._sequenceCounters.get(channel) ?? 0) + 1;\n this._sequenceCounters.set(channel, seq);\n (payload as Record<string, unknown>).__seq = seq;\n }\n\n const message = JSON.stringify(data);\n\n // Unicast (Node-to-Node) -> Use Streams\n if (channel.startsWith(\"ocpp:node:\")) {\n await this._driver.xadd(prefixedChannel, { message }, this._streamMaxLen);\n // Set TTL lease on ephemeral stream key to prevent memory leaks\n await this._driver\n .expire(prefixedChannel, this._streamTtlSeconds)\n .catch(() => {});\n } else {\n // Broadcast -> Use Pub/Sub\n await this._driver.publish(prefixedChannel, message);\n }\n }\n\n async publishBatch(\n messages: { channel: string; data: unknown }[],\n ): Promise<void> {\n const streamMessages: { stream: string; args: Record<string, string> }[] =\n [];\n const broadcastMessages: { channel: string; message: string }[] = [];\n\n for (const msg of messages) {\n const prefixedChannel = this._prefix + msg.channel;\n const message = JSON.stringify(msg.data);\n\n if (msg.channel.startsWith(\"ocpp:node:\")) {\n streamMessages.push({ stream: prefixedChannel, args: { message } });\n } else {\n broadcastMessages.push({ channel: prefixedChannel, message });\n }\n }\n\n const promises: Promise<void>[] = [];\n\n if (streamMessages.length > 0) {\n promises.push(this._driver.xaddBatch(streamMessages, this._streamMaxLen));\n }\n\n if (broadcastMessages.length > 0) {\n promises.push(\n Promise.all(\n broadcastMessages.map((bm) =>\n this._driver.publish(bm.channel, bm.message),\n ),\n ).then(() => {}), // Map `Promise<void[]>` to `Promise<void>`\n );\n }\n\n await Promise.all(promises);\n }\n\n async subscribe(\n channel: string,\n handler: (data: unknown) => void,\n ): Promise<void> {\n if (!this._handlers.has(channel)) {\n this._handlers.set(channel, new Set());\n const prefixedChannel = this._prefix + channel;\n\n if (channel.startsWith(\"ocpp:node:\")) {\n // Stream subscription\n // Start from '0' (beginning) to pick up missed messages during downtime (persistence).\n // Since we trim the stream (MAXLEN), this will only replay recent pending messages.\n if (!this._streams.has(prefixedChannel)) {\n this._streams.add(prefixedChannel);\n this._streamOffsets.set(prefixedChannel, \"0\");\n this._ensurePolling();\n }\n } else {\n // Pub/Sub subscription\n await this._driver.subscribe(prefixedChannel, (message) => {\n this._handleMessage(channel, message);\n });\n }\n }\n this._handlers.get(channel)?.add(handler);\n }\n\n async unsubscribe(channel: string): Promise<void> {\n const prefixedChannel = this._prefix + channel;\n\n if (this._streams.has(prefixedChannel)) {\n this._streams.delete(prefixedChannel);\n this._streamOffsets.delete(prefixedChannel); // Cleanup offset\n } else {\n await this._driver.unsubscribe(prefixedChannel);\n }\n\n this._handlers.delete(channel);\n }\n\n async disconnect(): Promise<void> {\n this._closed = true;\n this._handlers.clear();\n this._streams.clear();\n this._presenceCache.clear();\n this._sequenceCounters.clear();\n if (this._unsubError) this._unsubError();\n if (this._unsubReconnect) this._unsubReconnect();\n await this._driver.disconnect();\n }\n\n private _handleMessage(channel: string, message: string): void {\n const handlers = this._handlers.get(channel);\n if (!handlers) return;\n\n let data: unknown;\n try {\n data = JSON.parse(message);\n } catch {\n data = message;\n }\n\n for (const handler of handlers) {\n try {\n handler(data);\n } catch {\n // Swallow handler errors\n }\n }\n }\n\n // ─── Stream Polling ───────────────────────────────────────────────\n\n private _ensurePolling() {\n if (this._polling || this._closed) return;\n this._polling = true;\n this._pollLoop().catch(() => {\n this._polling = false;\n });\n }\n\n private async _pollLoop() {\n while (!this._closed) {\n if (this._streams.size === 0) {\n await new Promise((resolve) => setTimeout(resolve, 1000));\n continue;\n }\n\n const streamsArg = Array.from(this._streams).map((key) => ({\n key,\n id: this._streamOffsets.get(key) || \"$\",\n }));\n\n try {\n // Block for 1s. This allows picking up new subscriptions reasonably fast.\n const entries = await this._driver.xread(streamsArg, undefined, 1000);\n\n if (entries) {\n for (const entry of entries) {\n const channel = entry.stream.replace(this._prefix, \"\"); // remove prefix to find handler key\n\n for (const msg of entry.messages) {\n // Update offset\n this._streamOffsets.set(entry.stream, msg.id);\n\n const messageContent = msg.data.message;\n if (messageContent) {\n this._handleMessage(channel, messageContent);\n }\n }\n }\n }\n } catch (_err) {\n // Log error? For now swallow to keep loop alive\n // Avoid tight loop on error\n await new Promise((resolve) => setTimeout(resolve, 1000));\n }\n }\n this._polling = false;\n }\n\n // ─── Presence Registry ─────────────────────────────────────────────\n\n async setPresence(\n identity: string,\n nodeId: string,\n ttl: number,\n ): Promise<void> {\n const key = `${this._prefix}presence:${identity}`;\n // Cache for rehydration on reconnect (C3)\n this._presenceCache.set(identity, { nodeId, ttl });\n await this._driver.set(key, nodeId, ttl);\n }\n\n async getPresence(identity: string): Promise<string | null> {\n const key = `${this._prefix}presence:${identity}`;\n return await this._driver.get(key);\n }\n\n async getPresenceBatch(identities: string[]): Promise<(string | null)[]> {\n if (identities.length === 0) return [];\n const keys = identities.map((id) => `${this._prefix}presence:${id}`);\n if (this._driver.mget) {\n return await this._driver.mget(keys);\n }\n // Fallback if mget not available\n return await Promise.all(keys.map((k) => this._driver.get(k)));\n }\n\n async removePresence(identity: string): Promise<void> {\n const key = `${this._prefix}presence:${identity}`;\n await this._driver.del(key);\n }\n\n // ─── Observability Pipeline ────────────────────────────────────────\n\n async metrics(): Promise<Record<string, unknown>> {\n let pendingMessages = 0;\n const streamDetails: Record<string, number> = {};\n\n // Calculate \"consumer lag\" by checking the length of all active streams\n // Since we use MAXLEN for trimming, XLEN directly equals pending unread messages\n for (const streamKey of this._streams) {\n try {\n const length = await this._driver.xlen(streamKey);\n pendingMessages += length;\n streamDetails[streamKey] = length;\n } catch {\n // Ignore failures for individual stream stats\n streamDetails[streamKey] = -1;\n }\n }\n\n return {\n pendingMessages,\n activeStreams: this._streams.size,\n streamDetails,\n };\n }\n\n // ─── C1: Batch Presence Pipeline ────────────────────────────────────\n\n /**\n * Set multiple presence entries in a single Redis pipeline.\n * Reduces N network round-trips to 1 for bulk presence updates.\n */\n async setPresenceBatch(\n entries: { identity: string; nodeId: string; ttl?: number }[],\n ): Promise<void> {\n if (entries.length === 0) return;\n\n const batchEntries = entries.map(({ identity, nodeId, ttl }) => {\n const key = `${this._prefix}presence:${identity}`;\n const ttlSeconds = ttl ?? this._presenceTtlSeconds;\n // Cache for rehydration\n this._presenceCache.set(identity, { nodeId, ttl: ttlSeconds });\n return { key, value: nodeId, ttlSeconds };\n });\n\n await this._driver.setPresenceBatch(batchEntries);\n }\n\n // ─── C3: Redis Failure Rehydration ──────────────────────────────────\n\n /**\n * Re-syncs all cached presence entries to Redis after a reconnection.\n * Called automatically when the Redis client reconnects.\n */\n private async _rehydratePresence(): Promise<void> {\n if (this._presenceCache.size === 0) return;\n\n const entries = Array.from(this._presenceCache.entries()).map(\n ([identity, { nodeId, ttl }]) => ({\n key: `${this._prefix}presence:${identity}`,\n value: nodeId,\n ttlSeconds: ttl,\n }),\n );\n\n await this._driver.setPresenceBatch(entries);\n }\n}\n","export interface RedisLikeClient {\n publish(\n channel: string,\n message: string,\n ): Promise<number | unknown | undefined>;\n subscribe(channel: string, ...args: unknown[]): Promise<unknown | undefined>;\n unsubscribe(\n channel: string,\n ...args: unknown[]\n ): Promise<unknown | undefined>;\n on?(\n event: \"message\",\n callback: (channel: string, message: string) => void,\n ): unknown;\n disconnect?(): Promise<void> | void;\n quit?(): Promise<unknown> | undefined;\n // Node Redis v4 specific\n isOpen?: boolean;\n}\n\n// ─── Stream Types ───────────────────────────────────────────────\n\nexport interface StreamMessage {\n id: string;\n data: Record<string, string>;\n}\n\nexport interface StreamEntry {\n stream: string;\n messages: StreamMessage[];\n}\n\n// ─── Extended Redis Driver ──────────────────────────────────────\n\nexport interface RedisPubSubDriver {\n publish(channel: string, message: string): Promise<void>;\n subscribe(channel: string, handler: (message: string) => void): Promise<void>;\n unsubscribe(channel: string): Promise<void>;\n disconnect(): Promise<void>;\n\n // Key-Value Store for Presence\n set(key: string, value: string, ttlSeconds?: number): Promise<void>;\n get(key: string): Promise<string | null>;\n mget(keys: string[]): Promise<(string | null)[]>;\n del(key: string): Promise<void>;\n\n /**\n * Batch set multiple presence keys with TTL in a single pipeline.\n * Falls back to sequential sets if pipelining is unavailable.\n */\n setPresenceBatch(\n entries: { key: string; value: string; ttlSeconds: number }[],\n ): Promise<void>;\n\n /**\n * Set a TTL on an existing key (for ephemeral stream channel leases).\n */\n expire(key: string, ttlSeconds: number): Promise<void>;\n\n /**\n * Subscribe to connection error events for rehydration.\n * Returns an unsubscribe function.\n */\n onError?(handler: (err: Error) => void): () => void;\n\n /**\n * Subscribe to reconnection events.\n * Returns an unsubscribe function.\n */\n onReconnect?(handler: () => void): () => void;\n\n // Streams\n xadd(\n stream: string,\n args: Record<string, string>,\n maxLen?: number,\n ): Promise<string>;\n xaddBatch(\n messages: { stream: string; args: Record<string, string> }[],\n maxLen?: number,\n ): Promise<void>;\n xread(\n streams: { key: string; id: string }[],\n count?: number,\n block?: number,\n ): Promise<StreamEntry[] | null>;\n xlen(stream: string): Promise<number>;\n}\n\nexport class IoRedisDriver implements RedisPubSubDriver {\n private _handlers = new Map<string, (msg: string) => void>();\n\n constructor(\n private pub: any,\n private sub: any,\n private blocking?: any,\n ) {\n if (this.sub.on) {\n this.sub.on(\"message\", (channel: string, message: string) => {\n const handler = this._handlers.get(channel);\n if (handler) handler(message);\n });\n }\n }\n\n async publish(channel: string, message: string): Promise<void> {\n await this.pub.publish(channel, message);\n }\n\n async subscribe(\n channel: string,\n handler: (message: string) => void,\n ): Promise<void> {\n this._handlers.set(channel, handler);\n await this.sub.subscribe(channel);\n }\n\n async unsubscribe(channel: string): Promise<void> {\n await this.sub.unsubscribe(channel);\n this._handlers.delete(channel);\n }\n\n async set(key: string, value: string, ttlSeconds?: number): Promise<void> {\n if (ttlSeconds) {\n await this.pub.set(key, value, \"EX\", ttlSeconds);\n } else {\n await this.pub.set(key, value);\n }\n }\n\n async get(key: string): Promise<string | null> {\n return (await this.pub.get(key)) || null;\n }\n\n async mget(keys: string[]): Promise<(string | null)[]> {\n if (keys.length === 0) return [];\n return await this.pub.mget(...keys);\n }\n\n async del(key: string): Promise<void> {\n await this.pub.del(key);\n }\n\n async xadd(\n stream: string,\n args: Record<string, string>,\n maxLen?: number,\n ): Promise<string> {\n const flatArgs: string[] = [];\n if (maxLen) {\n flatArgs.push(\"MAXLEN\", \"~\", maxLen.toString());\n }\n flatArgs.push(\"*\"); // ID = auto\n for (const [k, v] of Object.entries(args)) {\n flatArgs.push(k, v);\n }\n return (await this.pub.xadd(stream, ...flatArgs)) as string;\n }\n\n async xaddBatch(\n messages: { stream: string; args: Record<string, string> }[],\n maxLen?: number,\n ): Promise<void> {\n if (messages.length === 0) return;\n const pipeline = this.pub.pipeline();\n for (const msg of messages) {\n const flatArgs: string[] = [];\n if (maxLen) {\n flatArgs.push(\"MAXLEN\", \"~\", maxLen.toString());\n }\n flatArgs.push(\"*\");\n for (const [k, v] of Object.entries(msg.args)) {\n flatArgs.push(k, v);\n }\n pipeline.xadd(msg.stream, ...flatArgs);\n }\n await pipeline.exec();\n }\n\n async xread(\n streams: { key: string; id: string }[],\n count?: number,\n block?: number,\n ): Promise<StreamEntry[] | null> {\n const args: (string | number)[] = [];\n if (count) {\n args.push(\"COUNT\", count);\n }\n if (typeof block === \"number\") {\n args.push(\"BLOCK\", block);\n }\n args.push(\"STREAMS\");\n streams.forEach((s) => {\n args.push(s.key);\n });\n streams.forEach((s) => {\n args.push(s.id);\n });\n\n // Use blocking client if available and blocking is requested\n const client = block && this.blocking ? this.blocking : this.pub;\n\n // ioredis returns [[key, [[id, [k,v,k,v]]]]]\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const result = (await client.xread(...args)) as any;\n\n if (!result) return null;\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return result.map(([stream, messages]: any) => ({\n stream,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n messages: messages.map(([id, fields]: any) => {\n const data: Record<string, string> = {};\n for (let i = 0; i < fields.length; i += 2) {\n data[fields[i]] = fields[i + 1];\n }\n return { id, data };\n }),\n }));\n }\n\n async xlen(stream: string): Promise<number> {\n return (await this.pub.xlen(stream)) as number;\n }\n\n async disconnect(): Promise<void> {\n this._handlers.clear();\n const close = async (c: any) => {\n if (c.quit) await c.quit();\n else if (c.disconnect) await c.disconnect();\n };\n await Promise.all([close(this.pub), close(this.sub)]);\n }\n\n async setPresenceBatch(\n entries: { key: string; value: string; ttlSeconds: number }[],\n ): Promise<void> {\n if (entries.length === 0) return;\n const pipeline = this.pub.pipeline();\n for (const { key, value, ttlSeconds } of entries) {\n pipeline.set(key, value, \"EX\", ttlSeconds);\n }\n await pipeline.exec();\n }\n\n async expire(key: string, ttlSeconds: number): Promise<void> {\n await this.pub.expire(key, ttlSeconds);\n }\n\n onError(handler: (err: Error) => void): () => void {\n this.pub.on(\"error\", handler);\n return () => this.pub.removeListener(\"error\", handler);\n }\n\n onReconnect(handler: () => void): () => void {\n this.pub.on(\"connect\", handler);\n return () => this.pub.removeListener(\"connect\", handler);\n }\n}\n\nexport class NodeRedisDriver implements RedisPubSubDriver {\n constructor(\n private pub: any,\n private sub: any,\n private blocking?: any,\n ) {}\n\n async publish(channel: string, message: string): Promise<void> {\n await this.pub.publish(channel, message);\n }\n\n async subscribe(\n channel: string,\n handler: (message: string) => void,\n ): Promise<void> {\n await this.sub.subscribe(channel, handler);\n }\n\n async unsubscribe(channel: string): Promise<void> {\n await this.sub.unsubscribe(channel);\n }\n\n async set(key: string, value: string, ttlSeconds?: number): Promise<void> {\n if (ttlSeconds) {\n await this.pub.set(key, value, { EX: ttlSeconds });\n } else {\n await this.pub.set(key, value);\n }\n }\n\n async get(key: string): Promise<string | null> {\n return (await this.pub.get(key)) || null;\n }\n\n async mget(keys: string[]): Promise<(string | null)[]> {\n if (keys.length === 0) return [];\n return await this.pub.mGet(keys);\n }\n\n async del(key: string): Promise<void> {\n await this.pub.del(key);\n }\n\n async xadd(\n stream: string,\n args: Record<string, string>,\n maxLen?: number,\n ): Promise<string> {\n const options: any = {};\n if (maxLen) {\n options.MKSTREAM = true; // Make sure stream exists\n // node-redis specific options for MAXLEN\n // But basic xadd signature is (key, id, message, options?)\n }\n // Node Redis v4 xAdd: (key, id, message)\n // For trimming, it might be in options.\n // Let's assume standard usage for now.\n // Actually Node Redis v4: .xAdd(key, id, message, options)\n\n // Construct message object\n return await this.pub.xAdd(stream, \"*\", args, {\n TRIM: maxLen\n ? {\n strategy: \"MAXLEN\",\n strategyModifier: \"~\",\n threshold: maxLen,\n }\n : undefined,\n });\n }\n\n async xaddBatch(\n messages: { stream: string; args: Record<string, string> }[],\n maxLen?: number,\n ): Promise<void> {\n if (messages.length === 0) return;\n const multi = this.pub.multi();\n for (const msg of messages) {\n multi.xAdd(msg.stream, \"*\", msg.args, {\n TRIM: maxLen\n ? {\n strategy: \"MAXLEN\",\n strategyModifier: \"~\",\n threshold: maxLen,\n }\n : undefined,\n });\n }\n await multi.exec();\n }\n\n async xread(\n streams: { key: string; id: string }[],\n count?: number,\n block?: number,\n ): Promise<StreamEntry[] | null> {\n // Node Redis v4 .xRead(streams, options)\n const options: any = {};\n if (count) options.COUNT = count;\n if (typeof block === \"number\") options.BLOCK = block;\n\n const streamsParam = streams.map((s) => ({\n key: s.key,\n id: s.id,\n }));\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const client = block && this.blocking ? this.blocking : this.pub;\n const result = (await client.xRead(streamsParam, options)) as any;\n\n if (!result || result.length === 0) return null;\n\n // Node Redis v4 returns: { name: string, messages: { id: string, message: Record<string,string> }[] }[]\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return result.map((entry: any) => ({\n stream: entry.name,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n messages: entry.messages.map((msg: any) => ({\n id: msg.id,\n data: msg.message,\n })),\n }));\n }\n\n async xlen(stream: string): Promise<number> {\n return (await this.pub.xLen(stream)) as number;\n }\n\n async disconnect(): Promise<void> {\n await Promise.all([this.pub.disconnect(), this.sub.disconnect()]);\n }\n\n async setPresenceBatch(\n entries: { key: string; value: string; ttlSeconds: number }[],\n ): Promise<void> {\n if (entries.length === 0) return;\n const multi = this.pub.multi();\n for (const { key, value, ttlSeconds } of entries) {\n multi.set(key, value, { EX: ttlSeconds });\n }\n await multi.exec();\n }\n\n async expire(key: string, ttlSeconds: number): Promise<void> {\n await this.pub.expire(key, ttlSeconds);\n }\n\n onError(handler: (err: Error) => void): () => void {\n this.pub.on(\"error\", handler);\n return () => this.pub.removeListener(\"error\", handler);\n }\n\n onReconnect(handler: () => void): () => void {\n this.pub.on(\"connect\", handler);\n return () => this.pub.removeListener(\"connect\", handler);\n }\n}\n\nexport function createDriver(\n pub: any,\n sub: any,\n blocking?: any,\n): RedisPubSubDriver {\n // Simple heuristic: Node Redis v4 clients usually have 'isOpen' boolean\n if (sub.isOpen !== undefined && typeof sub.subscribe === \"function\") {\n return new NodeRedisDriver(pub, sub, blocking);\n }\n // Default to IoRedis / Generic\n return new IoRedisDriver(pub, sub, blocking);\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/adapters/redis/index.ts","../../src/adapters/redis/helpers.ts"],"sourcesContent":["import type { EventAdapterInterface } from \"../../types.js\";\nimport {\n createDriver,\n type RedisLikeClient,\n type RedisPubSubDriver,\n} from \"./helpers.js\";\n\nexport interface RedisAdapterOptions {\n /** Redis client for publishing */\n pubClient: RedisLikeClient;\n /** Redis client for subscribing (must be a separate connection) */\n subClient: RedisLikeClient;\n /** Redis client for blocking stream operations (recommended for reliability) */\n blockingClient?: RedisLikeClient;\n /** Optional key prefix for channels (default: 'ocpp-ws-io:') */\n prefix?: string;\n /** StreamMaxLen for trimming (default: 1000) */\n streamMaxLen?: number;\n /**\n * TTL in seconds for ephemeral stream keys (default: 300).\n * Prevents abandoned channel keys from leaking memory in Redis.\n */\n streamTtlSeconds?: number;\n /**\n * Presence TTL in seconds (default: 300).\n * Used for batch presence heartbeat pipeline.\n */\n presenceTtlSeconds?: number;\n /**\n * Number of Redis connections to pool for write operations (default: 1).\n * Higher values distribute load via round-robin, eliminating TCP head-of-line blocking.\n * Pub/Sub subscriptions always use the primary driver (pool index 0).\n * - `1` = current single-connection behavior (backward compatible)\n * - `N` = round-robin across N connections for xadd/set/publish\n *\n * When poolSize > 1, additional pub/blocking clients are created by the factory.\n * Provide `driverFactory` to control how additional drivers are created.\n */\n poolSize?: number;\n /**\n * Factory function to create additional driver instances for the pool.\n * Each call should return a fresh, independent set of Redis connections.\n * Required when `poolSize > 1`.\n */\n driverFactory?: () => RedisPubSubDriver;\n}\n\n/**\n * Redis adapter for cross-process event distribution.\n *\n * Supports `ioredis` and `node-redis` (v4+).\n * Uses Redis Streams for reliable unicast (node-to-node) and Pub/Sub for broadcast.\n */\nexport class RedisAdapter implements EventAdapterInterface {\n private _driver: RedisPubSubDriver;\n private _prefix: string;\n private _streamMaxLen: number;\n private _streamTtlSeconds: number;\n private _presenceTtlSeconds: number;\n private _handlers = new Map<string, Set<(data: unknown) => void>>();\n private _streamOffsets = new Map<string, string>(); // streamKey -> lastId\n private _streams = new Set<string>(); // Active streams to poll\n private _polling = false;\n private _closed = false;\n\n // Per-stream sequence counter for message ordering\n private _sequenceCounters = new Map<string, number>();\n\n // Rehydration callbacks\n private _unsubError?: () => void;\n private _unsubReconnect?: () => void;\n\n // Stored presence entries for rehydration on reconnect\n private _presenceCache = new Map<string, { nodeId: string; ttl: number }>();\n\n // Connection pool\n private _driverPool: RedisPubSubDriver[];\n private _nextPoolIndex: number;\n\n constructor(options: RedisAdapterOptions) {\n this._prefix = options.prefix ?? \"ocpp-ws-io:\";\n this._streamMaxLen = options.streamMaxLen ?? 1000;\n this._streamTtlSeconds = options.streamTtlSeconds ?? 300;\n this._presenceTtlSeconds = options.presenceTtlSeconds ?? 300;\n\n // Primary driver (always created)\n this._driver = createDriver(\n options.pubClient,\n options.subClient,\n options.blockingClient,\n );\n\n // Connection pool — default 1 (backward compatible)\n const poolSize = options.poolSize ?? 1;\n this._driverPool = [this._driver];\n this._nextPoolIndex = 0;\n\n if (poolSize > 1 && options.driverFactory) {\n for (let i = 1; i < poolSize; i++) {\n this._driverPool.push(options.driverFactory());\n }\n }\n\n // Redis Failure Rehydration — listen for errors and re-sync on reconnect\n if (this._driver.onError) {\n this._unsubError = this._driver.onError((err) => {\n // Log for observability — consumers can attach their own logger\n console.error(\"[RedisAdapter] Redis error:\", err.message);\n });\n }\n if (this._driver.onReconnect) {\n this._unsubReconnect = this._driver.onReconnect(() => {\n this._rehydratePresence().catch(() => {});\n });\n }\n }\n\n /** Get the next driver from the pool (round-robin) */\n private _getPoolDriver(): RedisPubSubDriver {\n if (this._driverPool.length === 1) return this._driver;\n const driver = this._driverPool[this._nextPoolIndex];\n this._nextPoolIndex = (this._nextPoolIndex + 1) % this._driverPool.length;\n return driver;\n }\n\n async publish(channel: string, data: unknown): Promise<void> {\n const prefixedChannel = this._prefix + channel;\n\n // Attach sequence ID to unicast messages for ordering\n const payload = data as Record<string, unknown> | null;\n if (\n payload &&\n typeof payload === \"object\" &&\n channel.startsWith(\"ocpp:node:\")\n ) {\n const seq = (this._sequenceCounters.get(channel) ?? 0) + 1;\n this._sequenceCounters.set(channel, seq);\n (payload as Record<string, unknown>).__seq = seq;\n }\n\n const message = JSON.stringify(data);\n\n // Unicast (Node-to-Node) -> Use Streams\n if (channel.startsWith(\"ocpp:node:\")) {\n const poolDriver = this._getPoolDriver();\n await poolDriver.xadd(prefixedChannel, { message }, this._streamMaxLen);\n // Set TTL lease on ephemeral stream key to prevent memory leaks\n await poolDriver\n .expire(prefixedChannel, this._streamTtlSeconds)\n .catch(() => {});\n } else {\n // Broadcast -> Use Pub/Sub\n await this._getPoolDriver().publish(prefixedChannel, message);\n }\n }\n\n async publishBatch(\n messages: { channel: string; data: unknown }[],\n ): Promise<void> {\n const streamMessages: { stream: string; args: Record<string, string> }[] =\n [];\n const broadcastMessages: { channel: string; message: string }[] = [];\n\n for (const msg of messages) {\n const prefixedChannel = this._prefix + msg.channel;\n const message = JSON.stringify(msg.data);\n\n if (msg.channel.startsWith(\"ocpp:node:\")) {\n streamMessages.push({ stream: prefixedChannel, args: { message } });\n } else {\n broadcastMessages.push({ channel: prefixedChannel, message });\n }\n }\n\n const promises: Promise<void>[] = [];\n\n if (streamMessages.length > 0) {\n promises.push(\n this._getPoolDriver().xaddBatch(streamMessages, this._streamMaxLen),\n );\n }\n\n if (broadcastMessages.length > 0) {\n promises.push(\n Promise.all(\n broadcastMessages.map((bm) =>\n this._getPoolDriver().publish(bm.channel, bm.message),\n ),\n ).then(() => {}), // Map `Promise<void[]>` to `Promise<void>`\n );\n }\n\n await Promise.all(promises);\n }\n\n async subscribe(\n channel: string,\n handler: (data: unknown) => void,\n ): Promise<void> {\n if (!this._handlers.has(channel)) {\n this._handlers.set(channel, new Set());\n const prefixedChannel = this._prefix + channel;\n\n if (channel.startsWith(\"ocpp:node:\")) {\n // Stream subscription\n // Start from '0' (beginning) to pick up missed messages during downtime (persistence).\n // Since we trim the stream (MAXLEN), this will only replay recent pending messages.\n if (!this._streams.has(prefixedChannel)) {\n this._streams.add(prefixedChannel);\n this._streamOffsets.set(prefixedChannel, \"0\");\n this._ensurePolling();\n }\n } else {\n // Pub/Sub subscription\n await this._driver.subscribe(prefixedChannel, (message) => {\n this._handleMessage(channel, message);\n });\n }\n }\n this._handlers.get(channel)?.add(handler);\n }\n\n async unsubscribe(channel: string): Promise<void> {\n const prefixedChannel = this._prefix + channel;\n\n if (this._streams.has(prefixedChannel)) {\n this._streams.delete(prefixedChannel);\n this._streamOffsets.delete(prefixedChannel); // Cleanup offset\n } else {\n await this._driver.unsubscribe(prefixedChannel);\n }\n\n this._handlers.delete(channel);\n }\n\n async disconnect(): Promise<void> {\n this._closed = true;\n this._handlers.clear();\n this._streams.clear();\n this._presenceCache.clear();\n this._sequenceCounters.clear();\n if (this._unsubError) this._unsubError();\n if (this._unsubReconnect) this._unsubReconnect();\n // Disconnect all pool drivers\n await Promise.allSettled(this._driverPool.map((d) => d.disconnect()));\n }\n\n private _handleMessage(channel: string, message: string): void {\n const handlers = this._handlers.get(channel);\n if (!handlers) return;\n\n let data: unknown;\n try {\n data = JSON.parse(message);\n } catch {\n data = message;\n }\n\n for (const handler of handlers) {\n try {\n handler(data);\n } catch {\n // Swallow handler errors\n }\n }\n }\n\n // ─── Stream Polling ───────────────────────────────────────────────\n\n private _ensurePolling() {\n if (this._polling || this._closed) return;\n this._polling = true;\n this._pollLoop().catch(() => {\n this._polling = false;\n });\n }\n\n private async _pollLoop() {\n while (!this._closed) {\n if (this._streams.size === 0) {\n await new Promise((resolve) => setTimeout(resolve, 1000));\n continue;\n }\n\n const streamsArg = Array.from(this._streams).map((key) => ({\n key,\n id: this._streamOffsets.get(key) || \"$\",\n }));\n\n try {\n // Block for 1s. This allows picking up new subscriptions reasonably fast.\n const entries = await this._driver.xread(streamsArg, undefined, 1000);\n\n if (entries) {\n for (const entry of entries) {\n const channel = entry.stream.replace(this._prefix, \"\"); // remove prefix to find handler key\n\n for (const msg of entry.messages) {\n // Update offset\n this._streamOffsets.set(entry.stream, msg.id);\n\n const messageContent = msg.data.message;\n if (messageContent) {\n this._handleMessage(channel, messageContent);\n }\n }\n }\n }\n } catch (_err) {\n // Log error? For now swallow to keep loop alive\n // Avoid tight loop on error\n await new Promise((resolve) => setTimeout(resolve, 1000));\n }\n }\n this._polling = false;\n }\n\n // ─── Presence Registry ─────────────────────────────────────────────\n\n async setPresence(\n identity: string,\n nodeId: string,\n ttl: number,\n ): Promise<void> {\n const key = `${this._prefix}presence:${identity}`;\n // Cache for rehydration on reconnect (C3)\n this._presenceCache.set(identity, { nodeId, ttl });\n await this._driver.set(key, nodeId, ttl);\n }\n\n async getPresence(identity: string): Promise<string | null> {\n const key = `${this._prefix}presence:${identity}`;\n return await this._driver.get(key);\n }\n\n async getPresenceBatch(identities: string[]): Promise<(string | null)[]> {\n if (identities.length === 0) return [];\n const keys = identities.map((id) => `${this._prefix}presence:${id}`);\n if (this._driver.mget) {\n return await this._driver.mget(keys);\n }\n // Fallback if mget not available\n return await Promise.all(keys.map((k) => this._driver.get(k)));\n }\n\n async removePresence(identity: string): Promise<void> {\n const key = `${this._prefix}presence:${identity}`;\n await this._driver.del(key);\n }\n\n // ─── Observability Pipeline ────────────────────────────────────────\n\n async metrics(): Promise<Record<string, unknown>> {\n let pendingMessages = 0;\n const streamDetails: Record<string, number> = {};\n\n // Calculate \"consumer lag\" by checking the length of all active streams\n // Since we use MAXLEN for trimming, XLEN directly equals pending unread messages\n for (const streamKey of this._streams) {\n try {\n const length = await this._driver.xlen(streamKey);\n pendingMessages += length;\n streamDetails[streamKey] = length;\n } catch {\n // Ignore failures for individual stream stats\n streamDetails[streamKey] = -1;\n }\n }\n\n return {\n pendingMessages,\n activeStreams: this._streams.size,\n streamDetails,\n };\n }\n\n // ─── C1: Batch Presence Pipeline ────────────────────────────────────\n\n /**\n * Set multiple presence entries in a single Redis pipeline.\n * Reduces N network round-trips to 1 for bulk presence updates.\n */\n async setPresenceBatch(\n entries: { identity: string; nodeId: string; ttl?: number }[],\n ): Promise<void> {\n if (entries.length === 0) return;\n\n const batchEntries = entries.map(({ identity, nodeId, ttl }) => {\n const key = `${this._prefix}presence:${identity}`;\n const ttlSeconds = ttl ?? this._presenceTtlSeconds;\n // Cache for rehydration\n this._presenceCache.set(identity, { nodeId, ttl: ttlSeconds });\n return { key, value: nodeId, ttlSeconds };\n });\n\n await this._driver.setPresenceBatch(batchEntries);\n }\n\n // ─── C3: Redis Failure Rehydration ──────────────────────────────────\n\n /**\n * Re-syncs all cached presence entries to Redis after a reconnection.\n * Called automatically when the Redis client reconnects.\n */\n private async _rehydratePresence(): Promise<void> {\n if (this._presenceCache.size === 0) return;\n\n const entries = Array.from(this._presenceCache.entries()).map(\n ([identity, { nodeId, ttl }]) => ({\n key: `${this._prefix}presence:${identity}`,\n value: nodeId,\n ttlSeconds: ttl,\n }),\n );\n\n await this._driver.setPresenceBatch(entries);\n }\n}\n","export interface RedisLikeClient {\n publish(\n channel: string,\n message: string,\n ): Promise<number | unknown | undefined>;\n subscribe(channel: string, ...args: unknown[]): Promise<unknown | undefined>;\n unsubscribe(\n channel: string,\n ...args: unknown[]\n ): Promise<unknown | undefined>;\n on?(\n event: \"message\",\n callback: (channel: string, message: string) => void,\n ): unknown;\n disconnect?(): Promise<void> | void;\n quit?(): Promise<unknown> | undefined;\n // Node Redis v4 specific\n isOpen?: boolean;\n}\n\n// ─── Stream Types ───────────────────────────────────────────────\n\nexport interface StreamMessage {\n id: string;\n data: Record<string, string>;\n}\n\nexport interface StreamEntry {\n stream: string;\n messages: StreamMessage[];\n}\n\n// ─── Extended Redis Driver ──────────────────────────────────────\n\nexport interface RedisPubSubDriver {\n publish(channel: string, message: string): Promise<void>;\n subscribe(channel: string, handler: (message: string) => void): Promise<void>;\n unsubscribe(channel: string): Promise<void>;\n disconnect(): Promise<void>;\n\n // Key-Value Store for Presence\n set(key: string, value: string, ttlSeconds?: number): Promise<void>;\n get(key: string): Promise<string | null>;\n mget(keys: string[]): Promise<(string | null)[]>;\n del(key: string): Promise<void>;\n\n /**\n * Batch set multiple presence keys with TTL in a single pipeline.\n * Falls back to sequential sets if pipelining is unavailable.\n */\n setPresenceBatch(\n entries: { key: string; value: string; ttlSeconds: number }[],\n ): Promise<void>;\n\n /**\n * Set a TTL on an existing key (for ephemeral stream channel leases).\n */\n expire(key: string, ttlSeconds: number): Promise<void>;\n\n /**\n * Subscribe to connection error events for rehydration.\n * Returns an unsubscribe function.\n */\n onError?(handler: (err: Error) => void): () => void;\n\n /**\n * Subscribe to reconnection events.\n * Returns an unsubscribe function.\n */\n onReconnect?(handler: () => void): () => void;\n\n // Streams\n xadd(\n stream: string,\n args: Record<string, string>,\n maxLen?: number,\n ): Promise<string>;\n xaddBatch(\n messages: { stream: string; args: Record<string, string> }[],\n maxLen?: number,\n ): Promise<void>;\n xread(\n streams: { key: string; id: string }[],\n count?: number,\n block?: number,\n ): Promise<StreamEntry[] | null>;\n xlen(stream: string): Promise<number>;\n}\n\nexport class IoRedisDriver implements RedisPubSubDriver {\n private _handlers = new Map<string, (msg: string) => void>();\n\n constructor(\n private pub: any,\n private sub: any,\n private blocking?: any,\n ) {\n if (this.sub.on) {\n this.sub.on(\"message\", (channel: string, message: string) => {\n const handler = this._handlers.get(channel);\n if (handler) handler(message);\n });\n }\n }\n\n async publish(channel: string, message: string): Promise<void> {\n await this.pub.publish(channel, message);\n }\n\n async subscribe(\n channel: string,\n handler: (message: string) => void,\n ): Promise<void> {\n this._handlers.set(channel, handler);\n await this.sub.subscribe(channel);\n }\n\n async unsubscribe(channel: string): Promise<void> {\n await this.sub.unsubscribe(channel);\n this._handlers.delete(channel);\n }\n\n async set(key: string, value: string, ttlSeconds?: number): Promise<void> {\n if (ttlSeconds) {\n await this.pub.set(key, value, \"EX\", ttlSeconds);\n } else {\n await this.pub.set(key, value);\n }\n }\n\n async get(key: string): Promise<string | null> {\n return (await this.pub.get(key)) || null;\n }\n\n async mget(keys: string[]): Promise<(string | null)[]> {\n if (keys.length === 0) return [];\n return await this.pub.mget(...keys);\n }\n\n async del(key: string): Promise<void> {\n await this.pub.del(key);\n }\n\n async xadd(\n stream: string,\n args: Record<string, string>,\n maxLen?: number,\n ): Promise<string> {\n const flatArgs: string[] = [];\n if (maxLen) {\n flatArgs.push(\"MAXLEN\", \"~\", maxLen.toString());\n }\n flatArgs.push(\"*\"); // ID = auto\n for (const [k, v] of Object.entries(args)) {\n flatArgs.push(k, v);\n }\n return (await this.pub.xadd(stream, ...flatArgs)) as string;\n }\n\n async xaddBatch(\n messages: { stream: string; args: Record<string, string> }[],\n maxLen?: number,\n ): Promise<void> {\n if (messages.length === 0) return;\n const pipeline = this.pub.pipeline();\n for (const msg of messages) {\n const flatArgs: string[] = [];\n if (maxLen) {\n flatArgs.push(\"MAXLEN\", \"~\", maxLen.toString());\n }\n flatArgs.push(\"*\");\n for (const [k, v] of Object.entries(msg.args)) {\n flatArgs.push(k, v);\n }\n pipeline.xadd(msg.stream, ...flatArgs);\n }\n await pipeline.exec();\n }\n\n async xread(\n streams: { key: string; id: string }[],\n count?: number,\n block?: number,\n ): Promise<StreamEntry[] | null> {\n const args: (string | number)[] = [];\n if (count) {\n args.push(\"COUNT\", count);\n }\n if (typeof block === \"number\") {\n args.push(\"BLOCK\", block);\n }\n args.push(\"STREAMS\");\n streams.forEach((s) => {\n args.push(s.key);\n });\n streams.forEach((s) => {\n args.push(s.id);\n });\n\n // Use blocking client if available and blocking is requested\n const client = block && this.blocking ? this.blocking : this.pub;\n\n // ioredis returns [[key, [[id, [k,v,k,v]]]]]\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const result = (await client.xread(...args)) as any;\n\n if (!result) return null;\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return result.map(([stream, messages]: any) => ({\n stream,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n messages: messages.map(([id, fields]: any) => {\n const data: Record<string, string> = {};\n for (let i = 0; i < fields.length; i += 2) {\n data[fields[i]] = fields[i + 1];\n }\n return { id, data };\n }),\n }));\n }\n\n async xlen(stream: string): Promise<number> {\n return (await this.pub.xlen(stream)) as number;\n }\n\n async disconnect(): Promise<void> {\n this._handlers.clear();\n const close = async (c: any) => {\n if (c.quit) await c.quit();\n else if (c.disconnect) await c.disconnect();\n };\n await Promise.all([close(this.pub), close(this.sub)]);\n }\n\n async setPresenceBatch(\n entries: { key: string; value: string; ttlSeconds: number }[],\n ): Promise<void> {\n if (entries.length === 0) return;\n const pipeline = this.pub.pipeline();\n for (const { key, value, ttlSeconds } of entries) {\n pipeline.set(key, value, \"EX\", ttlSeconds);\n }\n await pipeline.exec();\n }\n\n async expire(key: string, ttlSeconds: number): Promise<void> {\n await this.pub.expire(key, ttlSeconds);\n }\n\n onError(handler: (err: Error) => void): () => void {\n this.pub.on(\"error\", handler);\n return () => this.pub.removeListener(\"error\", handler);\n }\n\n onReconnect(handler: () => void): () => void {\n this.pub.on(\"connect\", handler);\n return () => this.pub.removeListener(\"connect\", handler);\n }\n}\n\nexport class NodeRedisDriver implements RedisPubSubDriver {\n constructor(\n private pub: any,\n private sub: any,\n private blocking?: any,\n ) {}\n\n async publish(channel: string, message: string): Promise<void> {\n await this.pub.publish(channel, message);\n }\n\n async subscribe(\n channel: string,\n handler: (message: string) => void,\n ): Promise<void> {\n await this.sub.subscribe(channel, handler);\n }\n\n async unsubscribe(channel: string): Promise<void> {\n await this.sub.unsubscribe(channel);\n }\n\n async set(key: string, value: string, ttlSeconds?: number): Promise<void> {\n if (ttlSeconds) {\n await this.pub.set(key, value, { EX: ttlSeconds });\n } else {\n await this.pub.set(key, value);\n }\n }\n\n async get(key: string): Promise<string | null> {\n return (await this.pub.get(key)) || null;\n }\n\n async mget(keys: string[]): Promise<(string | null)[]> {\n if (keys.length === 0) return [];\n return await this.pub.mGet(keys);\n }\n\n async del(key: string): Promise<void> {\n await this.pub.del(key);\n }\n\n async xadd(\n stream: string,\n args: Record<string, string>,\n maxLen?: number,\n ): Promise<string> {\n const options: any = {};\n if (maxLen) {\n options.MKSTREAM = true; // Make sure stream exists\n // node-redis specific options for MAXLEN\n // But basic xadd signature is (key, id, message, options?)\n }\n // Node Redis v4 xAdd: (key, id, message)\n // For trimming, it might be in options.\n // Let's assume standard usage for now.\n // Actually Node Redis v4: .xAdd(key, id, message, options)\n\n // Construct message object\n return await this.pub.xAdd(stream, \"*\", args, {\n TRIM: maxLen\n ? {\n strategy: \"MAXLEN\",\n strategyModifier: \"~\",\n threshold: maxLen,\n }\n : undefined,\n });\n }\n\n async xaddBatch(\n messages: { stream: string; args: Record<string, string> }[],\n maxLen?: number,\n ): Promise<void> {\n if (messages.length === 0) return;\n const multi = this.pub.multi();\n for (const msg of messages) {\n multi.xAdd(msg.stream, \"*\", msg.args, {\n TRIM: maxLen\n ? {\n strategy: \"MAXLEN\",\n strategyModifier: \"~\",\n threshold: maxLen,\n }\n : undefined,\n });\n }\n await multi.exec();\n }\n\n async xread(\n streams: { key: string; id: string }[],\n count?: number,\n block?: number,\n ): Promise<StreamEntry[] | null> {\n // Node Redis v4 .xRead(streams, options)\n const options: any = {};\n if (count) options.COUNT = count;\n if (typeof block === \"number\") options.BLOCK = block;\n\n const streamsParam = streams.map((s) => ({\n key: s.key,\n id: s.id,\n }));\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const client = block && this.blocking ? this.blocking : this.pub;\n const result = (await client.xRead(streamsParam, options)) as any;\n\n if (!result || result.length === 0) return null;\n\n // Node Redis v4 returns: { name: string, messages: { id: string, message: Record<string,string> }[] }[]\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return result.map((entry: any) => ({\n stream: entry.name,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n messages: entry.messages.map((msg: any) => ({\n id: msg.id,\n data: msg.message,\n })),\n }));\n }\n\n async xlen(stream: string): Promise<number> {\n return (await this.pub.xLen(stream)) as number;\n }\n\n async disconnect(): Promise<void> {\n await Promise.all([this.pub.disconnect(), this.sub.disconnect()]);\n }\n\n async setPresenceBatch(\n entries: { key: string; value: string; ttlSeconds: number }[],\n ): Promise<void> {\n if (entries.length === 0) return;\n const multi = this.pub.multi();\n for (const { key, value, ttlSeconds } of entries) {\n multi.set(key, value, { EX: ttlSeconds });\n }\n await multi.exec();\n }\n\n async expire(key: string, ttlSeconds: number): Promise<void> {\n await this.pub.expire(key, ttlSeconds);\n }\n\n onError(handler: (err: Error) => void): () => void {\n this.pub.on(\"error\", handler);\n return () => this.pub.removeListener(\"error\", handler);\n }\n\n onReconnect(handler: () => void): () => void {\n this.pub.on(\"connect\", handler);\n return () => this.pub.removeListener(\"connect\", handler);\n }\n}\n\nexport function createDriver(\n pub: any,\n sub: any,\n blocking?: any,\n): RedisPubSubDriver {\n // Simple heuristic: Node Redis v4 clients usually have 'isOpen' boolean\n if (sub.isOpen !== undefined && typeof sub.subscribe === \"function\") {\n return new NodeRedisDriver(pub, sub, blocking);\n }\n // Default to IoRedis / Generic\n return new IoRedisDriver(pub, sub, blocking);\n}\n"],"mappings":"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","names":[]}
|
package/dist/adapters/redis.mjs
CHANGED
|
@@ -261,6 +261,9 @@ var RedisAdapter = class {
|
|
|
261
261
|
_unsubReconnect;
|
|
262
262
|
// Stored presence entries for rehydration on reconnect
|
|
263
263
|
_presenceCache = /* @__PURE__ */ new Map();
|
|
264
|
+
// Connection pool
|
|
265
|
+
_driverPool;
|
|
266
|
+
_nextPoolIndex;
|
|
264
267
|
constructor(options) {
|
|
265
268
|
this._prefix = options.prefix ?? "ocpp-ws-io:";
|
|
266
269
|
this._streamMaxLen = options.streamMaxLen ?? 1e3;
|
|
@@ -271,6 +274,14 @@ var RedisAdapter = class {
|
|
|
271
274
|
options.subClient,
|
|
272
275
|
options.blockingClient
|
|
273
276
|
);
|
|
277
|
+
const poolSize = options.poolSize ?? 1;
|
|
278
|
+
this._driverPool = [this._driver];
|
|
279
|
+
this._nextPoolIndex = 0;
|
|
280
|
+
if (poolSize > 1 && options.driverFactory) {
|
|
281
|
+
for (let i = 1; i < poolSize; i++) {
|
|
282
|
+
this._driverPool.push(options.driverFactory());
|
|
283
|
+
}
|
|
284
|
+
}
|
|
274
285
|
if (this._driver.onError) {
|
|
275
286
|
this._unsubError = this._driver.onError((err) => {
|
|
276
287
|
console.error("[RedisAdapter] Redis error:", err.message);
|
|
@@ -283,6 +294,13 @@ var RedisAdapter = class {
|
|
|
283
294
|
});
|
|
284
295
|
}
|
|
285
296
|
}
|
|
297
|
+
/** Get the next driver from the pool (round-robin) */
|
|
298
|
+
_getPoolDriver() {
|
|
299
|
+
if (this._driverPool.length === 1) return this._driver;
|
|
300
|
+
const driver = this._driverPool[this._nextPoolIndex];
|
|
301
|
+
this._nextPoolIndex = (this._nextPoolIndex + 1) % this._driverPool.length;
|
|
302
|
+
return driver;
|
|
303
|
+
}
|
|
286
304
|
async publish(channel, data) {
|
|
287
305
|
const prefixedChannel = this._prefix + channel;
|
|
288
306
|
const payload = data;
|
|
@@ -293,11 +311,12 @@ var RedisAdapter = class {
|
|
|
293
311
|
}
|
|
294
312
|
const message = JSON.stringify(data);
|
|
295
313
|
if (channel.startsWith("ocpp:node:")) {
|
|
296
|
-
|
|
297
|
-
await
|
|
314
|
+
const poolDriver = this._getPoolDriver();
|
|
315
|
+
await poolDriver.xadd(prefixedChannel, { message }, this._streamMaxLen);
|
|
316
|
+
await poolDriver.expire(prefixedChannel, this._streamTtlSeconds).catch(() => {
|
|
298
317
|
});
|
|
299
318
|
} else {
|
|
300
|
-
await this.
|
|
319
|
+
await this._getPoolDriver().publish(prefixedChannel, message);
|
|
301
320
|
}
|
|
302
321
|
}
|
|
303
322
|
async publishBatch(messages) {
|
|
@@ -314,13 +333,15 @@ var RedisAdapter = class {
|
|
|
314
333
|
}
|
|
315
334
|
const promises = [];
|
|
316
335
|
if (streamMessages.length > 0) {
|
|
317
|
-
promises.push(
|
|
336
|
+
promises.push(
|
|
337
|
+
this._getPoolDriver().xaddBatch(streamMessages, this._streamMaxLen)
|
|
338
|
+
);
|
|
318
339
|
}
|
|
319
340
|
if (broadcastMessages.length > 0) {
|
|
320
341
|
promises.push(
|
|
321
342
|
Promise.all(
|
|
322
343
|
broadcastMessages.map(
|
|
323
|
-
(bm) => this.
|
|
344
|
+
(bm) => this._getPoolDriver().publish(bm.channel, bm.message)
|
|
324
345
|
)
|
|
325
346
|
).then(() => {
|
|
326
347
|
})
|
|
@@ -365,7 +386,7 @@ var RedisAdapter = class {
|
|
|
365
386
|
this._sequenceCounters.clear();
|
|
366
387
|
if (this._unsubError) this._unsubError();
|
|
367
388
|
if (this._unsubReconnect) this._unsubReconnect();
|
|
368
|
-
await this.
|
|
389
|
+
await Promise.allSettled(this._driverPool.map((d) => d.disconnect()));
|
|
369
390
|
}
|
|
370
391
|
_handleMessage(channel, message) {
|
|
371
392
|
const handlers = this._handlers.get(channel);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/adapters/redis/helpers.ts","../../src/adapters/redis/index.ts"],"sourcesContent":["export interface RedisLikeClient {\n publish(\n channel: string,\n message: string,\n ): Promise<number | unknown | undefined>;\n subscribe(channel: string, ...args: unknown[]): Promise<unknown | undefined>;\n unsubscribe(\n channel: string,\n ...args: unknown[]\n ): Promise<unknown | undefined>;\n on?(\n event: \"message\",\n callback: (channel: string, message: string) => void,\n ): unknown;\n disconnect?(): Promise<void> | void;\n quit?(): Promise<unknown> | undefined;\n // Node Redis v4 specific\n isOpen?: boolean;\n}\n\n// ─── Stream Types ───────────────────────────────────────────────\n\nexport interface StreamMessage {\n id: string;\n data: Record<string, string>;\n}\n\nexport interface StreamEntry {\n stream: string;\n messages: StreamMessage[];\n}\n\n// ─── Extended Redis Driver ──────────────────────────────────────\n\nexport interface RedisPubSubDriver {\n publish(channel: string, message: string): Promise<void>;\n subscribe(channel: string, handler: (message: string) => void): Promise<void>;\n unsubscribe(channel: string): Promise<void>;\n disconnect(): Promise<void>;\n\n // Key-Value Store for Presence\n set(key: string, value: string, ttlSeconds?: number): Promise<void>;\n get(key: string): Promise<string | null>;\n mget(keys: string[]): Promise<(string | null)[]>;\n del(key: string): Promise<void>;\n\n /**\n * Batch set multiple presence keys with TTL in a single pipeline.\n * Falls back to sequential sets if pipelining is unavailable.\n */\n setPresenceBatch(\n entries: { key: string; value: string; ttlSeconds: number }[],\n ): Promise<void>;\n\n /**\n * Set a TTL on an existing key (for ephemeral stream channel leases).\n */\n expire(key: string, ttlSeconds: number): Promise<void>;\n\n /**\n * Subscribe to connection error events for rehydration.\n * Returns an unsubscribe function.\n */\n onError?(handler: (err: Error) => void): () => void;\n\n /**\n * Subscribe to reconnection events.\n * Returns an unsubscribe function.\n */\n onReconnect?(handler: () => void): () => void;\n\n // Streams\n xadd(\n stream: string,\n args: Record<string, string>,\n maxLen?: number,\n ): Promise<string>;\n xaddBatch(\n messages: { stream: string; args: Record<string, string> }[],\n maxLen?: number,\n ): Promise<void>;\n xread(\n streams: { key: string; id: string }[],\n count?: number,\n block?: number,\n ): Promise<StreamEntry[] | null>;\n xlen(stream: string): Promise<number>;\n}\n\nexport class IoRedisDriver implements RedisPubSubDriver {\n private _handlers = new Map<string, (msg: string) => void>();\n\n constructor(\n private pub: any,\n private sub: any,\n private blocking?: any,\n ) {\n if (this.sub.on) {\n this.sub.on(\"message\", (channel: string, message: string) => {\n const handler = this._handlers.get(channel);\n if (handler) handler(message);\n });\n }\n }\n\n async publish(channel: string, message: string): Promise<void> {\n await this.pub.publish(channel, message);\n }\n\n async subscribe(\n channel: string,\n handler: (message: string) => void,\n ): Promise<void> {\n this._handlers.set(channel, handler);\n await this.sub.subscribe(channel);\n }\n\n async unsubscribe(channel: string): Promise<void> {\n await this.sub.unsubscribe(channel);\n this._handlers.delete(channel);\n }\n\n async set(key: string, value: string, ttlSeconds?: number): Promise<void> {\n if (ttlSeconds) {\n await this.pub.set(key, value, \"EX\", ttlSeconds);\n } else {\n await this.pub.set(key, value);\n }\n }\n\n async get(key: string): Promise<string | null> {\n return (await this.pub.get(key)) || null;\n }\n\n async mget(keys: string[]): Promise<(string | null)[]> {\n if (keys.length === 0) return [];\n return await this.pub.mget(...keys);\n }\n\n async del(key: string): Promise<void> {\n await this.pub.del(key);\n }\n\n async xadd(\n stream: string,\n args: Record<string, string>,\n maxLen?: number,\n ): Promise<string> {\n const flatArgs: string[] = [];\n if (maxLen) {\n flatArgs.push(\"MAXLEN\", \"~\", maxLen.toString());\n }\n flatArgs.push(\"*\"); // ID = auto\n for (const [k, v] of Object.entries(args)) {\n flatArgs.push(k, v);\n }\n return (await this.pub.xadd(stream, ...flatArgs)) as string;\n }\n\n async xaddBatch(\n messages: { stream: string; args: Record<string, string> }[],\n maxLen?: number,\n ): Promise<void> {\n if (messages.length === 0) return;\n const pipeline = this.pub.pipeline();\n for (const msg of messages) {\n const flatArgs: string[] = [];\n if (maxLen) {\n flatArgs.push(\"MAXLEN\", \"~\", maxLen.toString());\n }\n flatArgs.push(\"*\");\n for (const [k, v] of Object.entries(msg.args)) {\n flatArgs.push(k, v);\n }\n pipeline.xadd(msg.stream, ...flatArgs);\n }\n await pipeline.exec();\n }\n\n async xread(\n streams: { key: string; id: string }[],\n count?: number,\n block?: number,\n ): Promise<StreamEntry[] | null> {\n const args: (string | number)[] = [];\n if (count) {\n args.push(\"COUNT\", count);\n }\n if (typeof block === \"number\") {\n args.push(\"BLOCK\", block);\n }\n args.push(\"STREAMS\");\n streams.forEach((s) => {\n args.push(s.key);\n });\n streams.forEach((s) => {\n args.push(s.id);\n });\n\n // Use blocking client if available and blocking is requested\n const client = block && this.blocking ? this.blocking : this.pub;\n\n // ioredis returns [[key, [[id, [k,v,k,v]]]]]\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const result = (await client.xread(...args)) as any;\n\n if (!result) return null;\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return result.map(([stream, messages]: any) => ({\n stream,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n messages: messages.map(([id, fields]: any) => {\n const data: Record<string, string> = {};\n for (let i = 0; i < fields.length; i += 2) {\n data[fields[i]] = fields[i + 1];\n }\n return { id, data };\n }),\n }));\n }\n\n async xlen(stream: string): Promise<number> {\n return (await this.pub.xlen(stream)) as number;\n }\n\n async disconnect(): Promise<void> {\n this._handlers.clear();\n const close = async (c: any) => {\n if (c.quit) await c.quit();\n else if (c.disconnect) await c.disconnect();\n };\n await Promise.all([close(this.pub), close(this.sub)]);\n }\n\n async setPresenceBatch(\n entries: { key: string; value: string; ttlSeconds: number }[],\n ): Promise<void> {\n if (entries.length === 0) return;\n const pipeline = this.pub.pipeline();\n for (const { key, value, ttlSeconds } of entries) {\n pipeline.set(key, value, \"EX\", ttlSeconds);\n }\n await pipeline.exec();\n }\n\n async expire(key: string, ttlSeconds: number): Promise<void> {\n await this.pub.expire(key, ttlSeconds);\n }\n\n onError(handler: (err: Error) => void): () => void {\n this.pub.on(\"error\", handler);\n return () => this.pub.removeListener(\"error\", handler);\n }\n\n onReconnect(handler: () => void): () => void {\n this.pub.on(\"connect\", handler);\n return () => this.pub.removeListener(\"connect\", handler);\n }\n}\n\nexport class NodeRedisDriver implements RedisPubSubDriver {\n constructor(\n private pub: any,\n private sub: any,\n private blocking?: any,\n ) {}\n\n async publish(channel: string, message: string): Promise<void> {\n await this.pub.publish(channel, message);\n }\n\n async subscribe(\n channel: string,\n handler: (message: string) => void,\n ): Promise<void> {\n await this.sub.subscribe(channel, handler);\n }\n\n async unsubscribe(channel: string): Promise<void> {\n await this.sub.unsubscribe(channel);\n }\n\n async set(key: string, value: string, ttlSeconds?: number): Promise<void> {\n if (ttlSeconds) {\n await this.pub.set(key, value, { EX: ttlSeconds });\n } else {\n await this.pub.set(key, value);\n }\n }\n\n async get(key: string): Promise<string | null> {\n return (await this.pub.get(key)) || null;\n }\n\n async mget(keys: string[]): Promise<(string | null)[]> {\n if (keys.length === 0) return [];\n return await this.pub.mGet(keys);\n }\n\n async del(key: string): Promise<void> {\n await this.pub.del(key);\n }\n\n async xadd(\n stream: string,\n args: Record<string, string>,\n maxLen?: number,\n ): Promise<string> {\n const options: any = {};\n if (maxLen) {\n options.MKSTREAM = true; // Make sure stream exists\n // node-redis specific options for MAXLEN\n // But basic xadd signature is (key, id, message, options?)\n }\n // Node Redis v4 xAdd: (key, id, message)\n // For trimming, it might be in options.\n // Let's assume standard usage for now.\n // Actually Node Redis v4: .xAdd(key, id, message, options)\n\n // Construct message object\n return await this.pub.xAdd(stream, \"*\", args, {\n TRIM: maxLen\n ? {\n strategy: \"MAXLEN\",\n strategyModifier: \"~\",\n threshold: maxLen,\n }\n : undefined,\n });\n }\n\n async xaddBatch(\n messages: { stream: string; args: Record<string, string> }[],\n maxLen?: number,\n ): Promise<void> {\n if (messages.length === 0) return;\n const multi = this.pub.multi();\n for (const msg of messages) {\n multi.xAdd(msg.stream, \"*\", msg.args, {\n TRIM: maxLen\n ? {\n strategy: \"MAXLEN\",\n strategyModifier: \"~\",\n threshold: maxLen,\n }\n : undefined,\n });\n }\n await multi.exec();\n }\n\n async xread(\n streams: { key: string; id: string }[],\n count?: number,\n block?: number,\n ): Promise<StreamEntry[] | null> {\n // Node Redis v4 .xRead(streams, options)\n const options: any = {};\n if (count) options.COUNT = count;\n if (typeof block === \"number\") options.BLOCK = block;\n\n const streamsParam = streams.map((s) => ({\n key: s.key,\n id: s.id,\n }));\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const client = block && this.blocking ? this.blocking : this.pub;\n const result = (await client.xRead(streamsParam, options)) as any;\n\n if (!result || result.length === 0) return null;\n\n // Node Redis v4 returns: { name: string, messages: { id: string, message: Record<string,string> }[] }[]\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return result.map((entry: any) => ({\n stream: entry.name,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n messages: entry.messages.map((msg: any) => ({\n id: msg.id,\n data: msg.message,\n })),\n }));\n }\n\n async xlen(stream: string): Promise<number> {\n return (await this.pub.xLen(stream)) as number;\n }\n\n async disconnect(): Promise<void> {\n await Promise.all([this.pub.disconnect(), this.sub.disconnect()]);\n }\n\n async setPresenceBatch(\n entries: { key: string; value: string; ttlSeconds: number }[],\n ): Promise<void> {\n if (entries.length === 0) return;\n const multi = this.pub.multi();\n for (const { key, value, ttlSeconds } of entries) {\n multi.set(key, value, { EX: ttlSeconds });\n }\n await multi.exec();\n }\n\n async expire(key: string, ttlSeconds: number): Promise<void> {\n await this.pub.expire(key, ttlSeconds);\n }\n\n onError(handler: (err: Error) => void): () => void {\n this.pub.on(\"error\", handler);\n return () => this.pub.removeListener(\"error\", handler);\n }\n\n onReconnect(handler: () => void): () => void {\n this.pub.on(\"connect\", handler);\n return () => this.pub.removeListener(\"connect\", handler);\n }\n}\n\nexport function createDriver(\n pub: any,\n sub: any,\n blocking?: any,\n): RedisPubSubDriver {\n // Simple heuristic: Node Redis v4 clients usually have 'isOpen' boolean\n if (sub.isOpen !== undefined && typeof sub.subscribe === \"function\") {\n return new NodeRedisDriver(pub, sub, blocking);\n }\n // Default to IoRedis / Generic\n return new IoRedisDriver(pub, sub, blocking);\n}\n","import type { EventAdapterInterface } from \"../../types.js\";\nimport {\n createDriver,\n type RedisLikeClient,\n type RedisPubSubDriver,\n} from \"./helpers.js\";\n\nexport interface RedisAdapterOptions {\n /** Redis client for publishing */\n pubClient: RedisLikeClient;\n /** Redis client for subscribing (must be a separate connection) */\n subClient: RedisLikeClient;\n /** Redis client for blocking stream operations (recommended for reliability) */\n blockingClient?: RedisLikeClient;\n /** Optional key prefix for channels (default: 'ocpp-ws-io:') */\n prefix?: string;\n /** StreamMaxLen for trimming (default: 1000) */\n streamMaxLen?: number;\n /**\n * TTL in seconds for ephemeral stream keys (default: 300).\n * Prevents abandoned channel keys from leaking memory in Redis.\n */\n streamTtlSeconds?: number;\n /**\n * Presence TTL in seconds (default: 300).\n * Used for batch presence heartbeat pipeline.\n */\n presenceTtlSeconds?: number;\n}\n\n/**\n * Redis adapter for cross-process event distribution.\n *\n * Supports `ioredis` and `node-redis` (v4+).\n * Uses Redis Streams for reliable unicast (node-to-node) and Pub/Sub for broadcast.\n */\nexport class RedisAdapter implements EventAdapterInterface {\n private _driver: RedisPubSubDriver;\n private _prefix: string;\n private _streamMaxLen: number;\n private _streamTtlSeconds: number;\n private _presenceTtlSeconds: number;\n private _handlers = new Map<string, Set<(data: unknown) => void>>();\n private _streamOffsets = new Map<string, string>(); // streamKey -> lastId\n private _streams = new Set<string>(); // Active streams to poll\n private _polling = false;\n private _closed = false;\n\n // Per-stream sequence counter for message ordering\n private _sequenceCounters = new Map<string, number>();\n\n // Rehydration callbacks\n private _unsubError?: () => void;\n private _unsubReconnect?: () => void;\n\n // Stored presence entries for rehydration on reconnect\n private _presenceCache = new Map<string, { nodeId: string; ttl: number }>();\n\n constructor(options: RedisAdapterOptions) {\n this._prefix = options.prefix ?? \"ocpp-ws-io:\";\n this._streamMaxLen = options.streamMaxLen ?? 1000;\n this._streamTtlSeconds = options.streamTtlSeconds ?? 300;\n this._presenceTtlSeconds = options.presenceTtlSeconds ?? 300;\n this._driver = createDriver(\n options.pubClient,\n options.subClient,\n options.blockingClient,\n );\n\n // Redis Failure Rehydration — listen for errors and re-sync on reconnect\n if (this._driver.onError) {\n this._unsubError = this._driver.onError((err) => {\n // Log for observability — consumers can attach their own logger\n console.error(\"[RedisAdapter] Redis error:\", err.message);\n });\n }\n if (this._driver.onReconnect) {\n this._unsubReconnect = this._driver.onReconnect(() => {\n this._rehydratePresence().catch(() => {});\n });\n }\n }\n\n async publish(channel: string, data: unknown): Promise<void> {\n const prefixedChannel = this._prefix + channel;\n\n // Attach sequence ID to unicast messages for ordering\n const payload = data as Record<string, unknown> | null;\n if (\n payload &&\n typeof payload === \"object\" &&\n channel.startsWith(\"ocpp:node:\")\n ) {\n const seq = (this._sequenceCounters.get(channel) ?? 0) + 1;\n this._sequenceCounters.set(channel, seq);\n (payload as Record<string, unknown>).__seq = seq;\n }\n\n const message = JSON.stringify(data);\n\n // Unicast (Node-to-Node) -> Use Streams\n if (channel.startsWith(\"ocpp:node:\")) {\n await this._driver.xadd(prefixedChannel, { message }, this._streamMaxLen);\n // Set TTL lease on ephemeral stream key to prevent memory leaks\n await this._driver\n .expire(prefixedChannel, this._streamTtlSeconds)\n .catch(() => {});\n } else {\n // Broadcast -> Use Pub/Sub\n await this._driver.publish(prefixedChannel, message);\n }\n }\n\n async publishBatch(\n messages: { channel: string; data: unknown }[],\n ): Promise<void> {\n const streamMessages: { stream: string; args: Record<string, string> }[] =\n [];\n const broadcastMessages: { channel: string; message: string }[] = [];\n\n for (const msg of messages) {\n const prefixedChannel = this._prefix + msg.channel;\n const message = JSON.stringify(msg.data);\n\n if (msg.channel.startsWith(\"ocpp:node:\")) {\n streamMessages.push({ stream: prefixedChannel, args: { message } });\n } else {\n broadcastMessages.push({ channel: prefixedChannel, message });\n }\n }\n\n const promises: Promise<void>[] = [];\n\n if (streamMessages.length > 0) {\n promises.push(this._driver.xaddBatch(streamMessages, this._streamMaxLen));\n }\n\n if (broadcastMessages.length > 0) {\n promises.push(\n Promise.all(\n broadcastMessages.map((bm) =>\n this._driver.publish(bm.channel, bm.message),\n ),\n ).then(() => {}), // Map `Promise<void[]>` to `Promise<void>`\n );\n }\n\n await Promise.all(promises);\n }\n\n async subscribe(\n channel: string,\n handler: (data: unknown) => void,\n ): Promise<void> {\n if (!this._handlers.has(channel)) {\n this._handlers.set(channel, new Set());\n const prefixedChannel = this._prefix + channel;\n\n if (channel.startsWith(\"ocpp:node:\")) {\n // Stream subscription\n // Start from '0' (beginning) to pick up missed messages during downtime (persistence).\n // Since we trim the stream (MAXLEN), this will only replay recent pending messages.\n if (!this._streams.has(prefixedChannel)) {\n this._streams.add(prefixedChannel);\n this._streamOffsets.set(prefixedChannel, \"0\");\n this._ensurePolling();\n }\n } else {\n // Pub/Sub subscription\n await this._driver.subscribe(prefixedChannel, (message) => {\n this._handleMessage(channel, message);\n });\n }\n }\n this._handlers.get(channel)?.add(handler);\n }\n\n async unsubscribe(channel: string): Promise<void> {\n const prefixedChannel = this._prefix + channel;\n\n if (this._streams.has(prefixedChannel)) {\n this._streams.delete(prefixedChannel);\n this._streamOffsets.delete(prefixedChannel); // Cleanup offset\n } else {\n await this._driver.unsubscribe(prefixedChannel);\n }\n\n this._handlers.delete(channel);\n }\n\n async disconnect(): Promise<void> {\n this._closed = true;\n this._handlers.clear();\n this._streams.clear();\n this._presenceCache.clear();\n this._sequenceCounters.clear();\n if (this._unsubError) this._unsubError();\n if (this._unsubReconnect) this._unsubReconnect();\n await this._driver.disconnect();\n }\n\n private _handleMessage(channel: string, message: string): void {\n const handlers = this._handlers.get(channel);\n if (!handlers) return;\n\n let data: unknown;\n try {\n data = JSON.parse(message);\n } catch {\n data = message;\n }\n\n for (const handler of handlers) {\n try {\n handler(data);\n } catch {\n // Swallow handler errors\n }\n }\n }\n\n // ─── Stream Polling ───────────────────────────────────────────────\n\n private _ensurePolling() {\n if (this._polling || this._closed) return;\n this._polling = true;\n this._pollLoop().catch(() => {\n this._polling = false;\n });\n }\n\n private async _pollLoop() {\n while (!this._closed) {\n if (this._streams.size === 0) {\n await new Promise((resolve) => setTimeout(resolve, 1000));\n continue;\n }\n\n const streamsArg = Array.from(this._streams).map((key) => ({\n key,\n id: this._streamOffsets.get(key) || \"$\",\n }));\n\n try {\n // Block for 1s. This allows picking up new subscriptions reasonably fast.\n const entries = await this._driver.xread(streamsArg, undefined, 1000);\n\n if (entries) {\n for (const entry of entries) {\n const channel = entry.stream.replace(this._prefix, \"\"); // remove prefix to find handler key\n\n for (const msg of entry.messages) {\n // Update offset\n this._streamOffsets.set(entry.stream, msg.id);\n\n const messageContent = msg.data.message;\n if (messageContent) {\n this._handleMessage(channel, messageContent);\n }\n }\n }\n }\n } catch (_err) {\n // Log error? For now swallow to keep loop alive\n // Avoid tight loop on error\n await new Promise((resolve) => setTimeout(resolve, 1000));\n }\n }\n this._polling = false;\n }\n\n // ─── Presence Registry ─────────────────────────────────────────────\n\n async setPresence(\n identity: string,\n nodeId: string,\n ttl: number,\n ): Promise<void> {\n const key = `${this._prefix}presence:${identity}`;\n // Cache for rehydration on reconnect (C3)\n this._presenceCache.set(identity, { nodeId, ttl });\n await this._driver.set(key, nodeId, ttl);\n }\n\n async getPresence(identity: string): Promise<string | null> {\n const key = `${this._prefix}presence:${identity}`;\n return await this._driver.get(key);\n }\n\n async getPresenceBatch(identities: string[]): Promise<(string | null)[]> {\n if (identities.length === 0) return [];\n const keys = identities.map((id) => `${this._prefix}presence:${id}`);\n if (this._driver.mget) {\n return await this._driver.mget(keys);\n }\n // Fallback if mget not available\n return await Promise.all(keys.map((k) => this._driver.get(k)));\n }\n\n async removePresence(identity: string): Promise<void> {\n const key = `${this._prefix}presence:${identity}`;\n await this._driver.del(key);\n }\n\n // ─── Observability Pipeline ────────────────────────────────────────\n\n async metrics(): Promise<Record<string, unknown>> {\n let pendingMessages = 0;\n const streamDetails: Record<string, number> = {};\n\n // Calculate \"consumer lag\" by checking the length of all active streams\n // Since we use MAXLEN for trimming, XLEN directly equals pending unread messages\n for (const streamKey of this._streams) {\n try {\n const length = await this._driver.xlen(streamKey);\n pendingMessages += length;\n streamDetails[streamKey] = length;\n } catch {\n // Ignore failures for individual stream stats\n streamDetails[streamKey] = -1;\n }\n }\n\n return {\n pendingMessages,\n activeStreams: this._streams.size,\n streamDetails,\n };\n }\n\n // ─── C1: Batch Presence Pipeline ────────────────────────────────────\n\n /**\n * Set multiple presence entries in a single Redis pipeline.\n * Reduces N network round-trips to 1 for bulk presence updates.\n */\n async setPresenceBatch(\n entries: { identity: string; nodeId: string; ttl?: number }[],\n ): Promise<void> {\n if (entries.length === 0) return;\n\n const batchEntries = entries.map(({ identity, nodeId, ttl }) => {\n const key = `${this._prefix}presence:${identity}`;\n const ttlSeconds = ttl ?? this._presenceTtlSeconds;\n // Cache for rehydration\n this._presenceCache.set(identity, { nodeId, ttl: ttlSeconds });\n return { key, value: nodeId, ttlSeconds };\n });\n\n await this._driver.setPresenceBatch(batchEntries);\n }\n\n // ─── C3: Redis Failure Rehydration ──────────────────────────────────\n\n /**\n * Re-syncs all cached presence entries to Redis after a reconnection.\n * Called automatically when the Redis client reconnects.\n */\n private async _rehydratePresence(): Promise<void> {\n if (this._presenceCache.size === 0) return;\n\n const entries = Array.from(this._presenceCache.entries()).map(\n ([identity, { nodeId, ttl }]) => ({\n key: `${this._prefix}presence:${identity}`,\n value: nodeId,\n ttlSeconds: ttl,\n }),\n );\n\n await this._driver.setPresenceBatch(entries);\n }\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/adapters/redis/helpers.ts","../../src/adapters/redis/index.ts"],"sourcesContent":["export interface RedisLikeClient {\n publish(\n channel: string,\n message: string,\n ): Promise<number | unknown | undefined>;\n subscribe(channel: string, ...args: unknown[]): Promise<unknown | undefined>;\n unsubscribe(\n channel: string,\n ...args: unknown[]\n ): Promise<unknown | undefined>;\n on?(\n event: \"message\",\n callback: (channel: string, message: string) => void,\n ): unknown;\n disconnect?(): Promise<void> | void;\n quit?(): Promise<unknown> | undefined;\n // Node Redis v4 specific\n isOpen?: boolean;\n}\n\n// ─── Stream Types ───────────────────────────────────────────────\n\nexport interface StreamMessage {\n id: string;\n data: Record<string, string>;\n}\n\nexport interface StreamEntry {\n stream: string;\n messages: StreamMessage[];\n}\n\n// ─── Extended Redis Driver ──────────────────────────────────────\n\nexport interface RedisPubSubDriver {\n publish(channel: string, message: string): Promise<void>;\n subscribe(channel: string, handler: (message: string) => void): Promise<void>;\n unsubscribe(channel: string): Promise<void>;\n disconnect(): Promise<void>;\n\n // Key-Value Store for Presence\n set(key: string, value: string, ttlSeconds?: number): Promise<void>;\n get(key: string): Promise<string | null>;\n mget(keys: string[]): Promise<(string | null)[]>;\n del(key: string): Promise<void>;\n\n /**\n * Batch set multiple presence keys with TTL in a single pipeline.\n * Falls back to sequential sets if pipelining is unavailable.\n */\n setPresenceBatch(\n entries: { key: string; value: string; ttlSeconds: number }[],\n ): Promise<void>;\n\n /**\n * Set a TTL on an existing key (for ephemeral stream channel leases).\n */\n expire(key: string, ttlSeconds: number): Promise<void>;\n\n /**\n * Subscribe to connection error events for rehydration.\n * Returns an unsubscribe function.\n */\n onError?(handler: (err: Error) => void): () => void;\n\n /**\n * Subscribe to reconnection events.\n * Returns an unsubscribe function.\n */\n onReconnect?(handler: () => void): () => void;\n\n // Streams\n xadd(\n stream: string,\n args: Record<string, string>,\n maxLen?: number,\n ): Promise<string>;\n xaddBatch(\n messages: { stream: string; args: Record<string, string> }[],\n maxLen?: number,\n ): Promise<void>;\n xread(\n streams: { key: string; id: string }[],\n count?: number,\n block?: number,\n ): Promise<StreamEntry[] | null>;\n xlen(stream: string): Promise<number>;\n}\n\nexport class IoRedisDriver implements RedisPubSubDriver {\n private _handlers = new Map<string, (msg: string) => void>();\n\n constructor(\n private pub: any,\n private sub: any,\n private blocking?: any,\n ) {\n if (this.sub.on) {\n this.sub.on(\"message\", (channel: string, message: string) => {\n const handler = this._handlers.get(channel);\n if (handler) handler(message);\n });\n }\n }\n\n async publish(channel: string, message: string): Promise<void> {\n await this.pub.publish(channel, message);\n }\n\n async subscribe(\n channel: string,\n handler: (message: string) => void,\n ): Promise<void> {\n this._handlers.set(channel, handler);\n await this.sub.subscribe(channel);\n }\n\n async unsubscribe(channel: string): Promise<void> {\n await this.sub.unsubscribe(channel);\n this._handlers.delete(channel);\n }\n\n async set(key: string, value: string, ttlSeconds?: number): Promise<void> {\n if (ttlSeconds) {\n await this.pub.set(key, value, \"EX\", ttlSeconds);\n } else {\n await this.pub.set(key, value);\n }\n }\n\n async get(key: string): Promise<string | null> {\n return (await this.pub.get(key)) || null;\n }\n\n async mget(keys: string[]): Promise<(string | null)[]> {\n if (keys.length === 0) return [];\n return await this.pub.mget(...keys);\n }\n\n async del(key: string): Promise<void> {\n await this.pub.del(key);\n }\n\n async xadd(\n stream: string,\n args: Record<string, string>,\n maxLen?: number,\n ): Promise<string> {\n const flatArgs: string[] = [];\n if (maxLen) {\n flatArgs.push(\"MAXLEN\", \"~\", maxLen.toString());\n }\n flatArgs.push(\"*\"); // ID = auto\n for (const [k, v] of Object.entries(args)) {\n flatArgs.push(k, v);\n }\n return (await this.pub.xadd(stream, ...flatArgs)) as string;\n }\n\n async xaddBatch(\n messages: { stream: string; args: Record<string, string> }[],\n maxLen?: number,\n ): Promise<void> {\n if (messages.length === 0) return;\n const pipeline = this.pub.pipeline();\n for (const msg of messages) {\n const flatArgs: string[] = [];\n if (maxLen) {\n flatArgs.push(\"MAXLEN\", \"~\", maxLen.toString());\n }\n flatArgs.push(\"*\");\n for (const [k, v] of Object.entries(msg.args)) {\n flatArgs.push(k, v);\n }\n pipeline.xadd(msg.stream, ...flatArgs);\n }\n await pipeline.exec();\n }\n\n async xread(\n streams: { key: string; id: string }[],\n count?: number,\n block?: number,\n ): Promise<StreamEntry[] | null> {\n const args: (string | number)[] = [];\n if (count) {\n args.push(\"COUNT\", count);\n }\n if (typeof block === \"number\") {\n args.push(\"BLOCK\", block);\n }\n args.push(\"STREAMS\");\n streams.forEach((s) => {\n args.push(s.key);\n });\n streams.forEach((s) => {\n args.push(s.id);\n });\n\n // Use blocking client if available and blocking is requested\n const client = block && this.blocking ? this.blocking : this.pub;\n\n // ioredis returns [[key, [[id, [k,v,k,v]]]]]\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const result = (await client.xread(...args)) as any;\n\n if (!result) return null;\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return result.map(([stream, messages]: any) => ({\n stream,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n messages: messages.map(([id, fields]: any) => {\n const data: Record<string, string> = {};\n for (let i = 0; i < fields.length; i += 2) {\n data[fields[i]] = fields[i + 1];\n }\n return { id, data };\n }),\n }));\n }\n\n async xlen(stream: string): Promise<number> {\n return (await this.pub.xlen(stream)) as number;\n }\n\n async disconnect(): Promise<void> {\n this._handlers.clear();\n const close = async (c: any) => {\n if (c.quit) await c.quit();\n else if (c.disconnect) await c.disconnect();\n };\n await Promise.all([close(this.pub), close(this.sub)]);\n }\n\n async setPresenceBatch(\n entries: { key: string; value: string; ttlSeconds: number }[],\n ): Promise<void> {\n if (entries.length === 0) return;\n const pipeline = this.pub.pipeline();\n for (const { key, value, ttlSeconds } of entries) {\n pipeline.set(key, value, \"EX\", ttlSeconds);\n }\n await pipeline.exec();\n }\n\n async expire(key: string, ttlSeconds: number): Promise<void> {\n await this.pub.expire(key, ttlSeconds);\n }\n\n onError(handler: (err: Error) => void): () => void {\n this.pub.on(\"error\", handler);\n return () => this.pub.removeListener(\"error\", handler);\n }\n\n onReconnect(handler: () => void): () => void {\n this.pub.on(\"connect\", handler);\n return () => this.pub.removeListener(\"connect\", handler);\n }\n}\n\nexport class NodeRedisDriver implements RedisPubSubDriver {\n constructor(\n private pub: any,\n private sub: any,\n private blocking?: any,\n ) {}\n\n async publish(channel: string, message: string): Promise<void> {\n await this.pub.publish(channel, message);\n }\n\n async subscribe(\n channel: string,\n handler: (message: string) => void,\n ): Promise<void> {\n await this.sub.subscribe(channel, handler);\n }\n\n async unsubscribe(channel: string): Promise<void> {\n await this.sub.unsubscribe(channel);\n }\n\n async set(key: string, value: string, ttlSeconds?: number): Promise<void> {\n if (ttlSeconds) {\n await this.pub.set(key, value, { EX: ttlSeconds });\n } else {\n await this.pub.set(key, value);\n }\n }\n\n async get(key: string): Promise<string | null> {\n return (await this.pub.get(key)) || null;\n }\n\n async mget(keys: string[]): Promise<(string | null)[]> {\n if (keys.length === 0) return [];\n return await this.pub.mGet(keys);\n }\n\n async del(key: string): Promise<void> {\n await this.pub.del(key);\n }\n\n async xadd(\n stream: string,\n args: Record<string, string>,\n maxLen?: number,\n ): Promise<string> {\n const options: any = {};\n if (maxLen) {\n options.MKSTREAM = true; // Make sure stream exists\n // node-redis specific options for MAXLEN\n // But basic xadd signature is (key, id, message, options?)\n }\n // Node Redis v4 xAdd: (key, id, message)\n // For trimming, it might be in options.\n // Let's assume standard usage for now.\n // Actually Node Redis v4: .xAdd(key, id, message, options)\n\n // Construct message object\n return await this.pub.xAdd(stream, \"*\", args, {\n TRIM: maxLen\n ? {\n strategy: \"MAXLEN\",\n strategyModifier: \"~\",\n threshold: maxLen,\n }\n : undefined,\n });\n }\n\n async xaddBatch(\n messages: { stream: string; args: Record<string, string> }[],\n maxLen?: number,\n ): Promise<void> {\n if (messages.length === 0) return;\n const multi = this.pub.multi();\n for (const msg of messages) {\n multi.xAdd(msg.stream, \"*\", msg.args, {\n TRIM: maxLen\n ? {\n strategy: \"MAXLEN\",\n strategyModifier: \"~\",\n threshold: maxLen,\n }\n : undefined,\n });\n }\n await multi.exec();\n }\n\n async xread(\n streams: { key: string; id: string }[],\n count?: number,\n block?: number,\n ): Promise<StreamEntry[] | null> {\n // Node Redis v4 .xRead(streams, options)\n const options: any = {};\n if (count) options.COUNT = count;\n if (typeof block === \"number\") options.BLOCK = block;\n\n const streamsParam = streams.map((s) => ({\n key: s.key,\n id: s.id,\n }));\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const client = block && this.blocking ? this.blocking : this.pub;\n const result = (await client.xRead(streamsParam, options)) as any;\n\n if (!result || result.length === 0) return null;\n\n // Node Redis v4 returns: { name: string, messages: { id: string, message: Record<string,string> }[] }[]\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return result.map((entry: any) => ({\n stream: entry.name,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n messages: entry.messages.map((msg: any) => ({\n id: msg.id,\n data: msg.message,\n })),\n }));\n }\n\n async xlen(stream: string): Promise<number> {\n return (await this.pub.xLen(stream)) as number;\n }\n\n async disconnect(): Promise<void> {\n await Promise.all([this.pub.disconnect(), this.sub.disconnect()]);\n }\n\n async setPresenceBatch(\n entries: { key: string; value: string; ttlSeconds: number }[],\n ): Promise<void> {\n if (entries.length === 0) return;\n const multi = this.pub.multi();\n for (const { key, value, ttlSeconds } of entries) {\n multi.set(key, value, { EX: ttlSeconds });\n }\n await multi.exec();\n }\n\n async expire(key: string, ttlSeconds: number): Promise<void> {\n await this.pub.expire(key, ttlSeconds);\n }\n\n onError(handler: (err: Error) => void): () => void {\n this.pub.on(\"error\", handler);\n return () => this.pub.removeListener(\"error\", handler);\n }\n\n onReconnect(handler: () => void): () => void {\n this.pub.on(\"connect\", handler);\n return () => this.pub.removeListener(\"connect\", handler);\n }\n}\n\nexport function createDriver(\n pub: any,\n sub: any,\n blocking?: any,\n): RedisPubSubDriver {\n // Simple heuristic: Node Redis v4 clients usually have 'isOpen' boolean\n if (sub.isOpen !== undefined && typeof sub.subscribe === \"function\") {\n return new NodeRedisDriver(pub, sub, blocking);\n }\n // Default to IoRedis / Generic\n return new IoRedisDriver(pub, sub, blocking);\n}\n","import type { EventAdapterInterface } from \"../../types.js\";\nimport {\n createDriver,\n type RedisLikeClient,\n type RedisPubSubDriver,\n} from \"./helpers.js\";\n\nexport interface RedisAdapterOptions {\n /** Redis client for publishing */\n pubClient: RedisLikeClient;\n /** Redis client for subscribing (must be a separate connection) */\n subClient: RedisLikeClient;\n /** Redis client for blocking stream operations (recommended for reliability) */\n blockingClient?: RedisLikeClient;\n /** Optional key prefix for channels (default: 'ocpp-ws-io:') */\n prefix?: string;\n /** StreamMaxLen for trimming (default: 1000) */\n streamMaxLen?: number;\n /**\n * TTL in seconds for ephemeral stream keys (default: 300).\n * Prevents abandoned channel keys from leaking memory in Redis.\n */\n streamTtlSeconds?: number;\n /**\n * Presence TTL in seconds (default: 300).\n * Used for batch presence heartbeat pipeline.\n */\n presenceTtlSeconds?: number;\n /**\n * Number of Redis connections to pool for write operations (default: 1).\n * Higher values distribute load via round-robin, eliminating TCP head-of-line blocking.\n * Pub/Sub subscriptions always use the primary driver (pool index 0).\n * - `1` = current single-connection behavior (backward compatible)\n * - `N` = round-robin across N connections for xadd/set/publish\n *\n * When poolSize > 1, additional pub/blocking clients are created by the factory.\n * Provide `driverFactory` to control how additional drivers are created.\n */\n poolSize?: number;\n /**\n * Factory function to create additional driver instances for the pool.\n * Each call should return a fresh, independent set of Redis connections.\n * Required when `poolSize > 1`.\n */\n driverFactory?: () => RedisPubSubDriver;\n}\n\n/**\n * Redis adapter for cross-process event distribution.\n *\n * Supports `ioredis` and `node-redis` (v4+).\n * Uses Redis Streams for reliable unicast (node-to-node) and Pub/Sub for broadcast.\n */\nexport class RedisAdapter implements EventAdapterInterface {\n private _driver: RedisPubSubDriver;\n private _prefix: string;\n private _streamMaxLen: number;\n private _streamTtlSeconds: number;\n private _presenceTtlSeconds: number;\n private _handlers = new Map<string, Set<(data: unknown) => void>>();\n private _streamOffsets = new Map<string, string>(); // streamKey -> lastId\n private _streams = new Set<string>(); // Active streams to poll\n private _polling = false;\n private _closed = false;\n\n // Per-stream sequence counter for message ordering\n private _sequenceCounters = new Map<string, number>();\n\n // Rehydration callbacks\n private _unsubError?: () => void;\n private _unsubReconnect?: () => void;\n\n // Stored presence entries for rehydration on reconnect\n private _presenceCache = new Map<string, { nodeId: string; ttl: number }>();\n\n // Connection pool\n private _driverPool: RedisPubSubDriver[];\n private _nextPoolIndex: number;\n\n constructor(options: RedisAdapterOptions) {\n this._prefix = options.prefix ?? \"ocpp-ws-io:\";\n this._streamMaxLen = options.streamMaxLen ?? 1000;\n this._streamTtlSeconds = options.streamTtlSeconds ?? 300;\n this._presenceTtlSeconds = options.presenceTtlSeconds ?? 300;\n\n // Primary driver (always created)\n this._driver = createDriver(\n options.pubClient,\n options.subClient,\n options.blockingClient,\n );\n\n // Connection pool — default 1 (backward compatible)\n const poolSize = options.poolSize ?? 1;\n this._driverPool = [this._driver];\n this._nextPoolIndex = 0;\n\n if (poolSize > 1 && options.driverFactory) {\n for (let i = 1; i < poolSize; i++) {\n this._driverPool.push(options.driverFactory());\n }\n }\n\n // Redis Failure Rehydration — listen for errors and re-sync on reconnect\n if (this._driver.onError) {\n this._unsubError = this._driver.onError((err) => {\n // Log for observability — consumers can attach their own logger\n console.error(\"[RedisAdapter] Redis error:\", err.message);\n });\n }\n if (this._driver.onReconnect) {\n this._unsubReconnect = this._driver.onReconnect(() => {\n this._rehydratePresence().catch(() => {});\n });\n }\n }\n\n /** Get the next driver from the pool (round-robin) */\n private _getPoolDriver(): RedisPubSubDriver {\n if (this._driverPool.length === 1) return this._driver;\n const driver = this._driverPool[this._nextPoolIndex];\n this._nextPoolIndex = (this._nextPoolIndex + 1) % this._driverPool.length;\n return driver;\n }\n\n async publish(channel: string, data: unknown): Promise<void> {\n const prefixedChannel = this._prefix + channel;\n\n // Attach sequence ID to unicast messages for ordering\n const payload = data as Record<string, unknown> | null;\n if (\n payload &&\n typeof payload === \"object\" &&\n channel.startsWith(\"ocpp:node:\")\n ) {\n const seq = (this._sequenceCounters.get(channel) ?? 0) + 1;\n this._sequenceCounters.set(channel, seq);\n (payload as Record<string, unknown>).__seq = seq;\n }\n\n const message = JSON.stringify(data);\n\n // Unicast (Node-to-Node) -> Use Streams\n if (channel.startsWith(\"ocpp:node:\")) {\n const poolDriver = this._getPoolDriver();\n await poolDriver.xadd(prefixedChannel, { message }, this._streamMaxLen);\n // Set TTL lease on ephemeral stream key to prevent memory leaks\n await poolDriver\n .expire(prefixedChannel, this._streamTtlSeconds)\n .catch(() => {});\n } else {\n // Broadcast -> Use Pub/Sub\n await this._getPoolDriver().publish(prefixedChannel, message);\n }\n }\n\n async publishBatch(\n messages: { channel: string; data: unknown }[],\n ): Promise<void> {\n const streamMessages: { stream: string; args: Record<string, string> }[] =\n [];\n const broadcastMessages: { channel: string; message: string }[] = [];\n\n for (const msg of messages) {\n const prefixedChannel = this._prefix + msg.channel;\n const message = JSON.stringify(msg.data);\n\n if (msg.channel.startsWith(\"ocpp:node:\")) {\n streamMessages.push({ stream: prefixedChannel, args: { message } });\n } else {\n broadcastMessages.push({ channel: prefixedChannel, message });\n }\n }\n\n const promises: Promise<void>[] = [];\n\n if (streamMessages.length > 0) {\n promises.push(\n this._getPoolDriver().xaddBatch(streamMessages, this._streamMaxLen),\n );\n }\n\n if (broadcastMessages.length > 0) {\n promises.push(\n Promise.all(\n broadcastMessages.map((bm) =>\n this._getPoolDriver().publish(bm.channel, bm.message),\n ),\n ).then(() => {}), // Map `Promise<void[]>` to `Promise<void>`\n );\n }\n\n await Promise.all(promises);\n }\n\n async subscribe(\n channel: string,\n handler: (data: unknown) => void,\n ): Promise<void> {\n if (!this._handlers.has(channel)) {\n this._handlers.set(channel, new Set());\n const prefixedChannel = this._prefix + channel;\n\n if (channel.startsWith(\"ocpp:node:\")) {\n // Stream subscription\n // Start from '0' (beginning) to pick up missed messages during downtime (persistence).\n // Since we trim the stream (MAXLEN), this will only replay recent pending messages.\n if (!this._streams.has(prefixedChannel)) {\n this._streams.add(prefixedChannel);\n this._streamOffsets.set(prefixedChannel, \"0\");\n this._ensurePolling();\n }\n } else {\n // Pub/Sub subscription\n await this._driver.subscribe(prefixedChannel, (message) => {\n this._handleMessage(channel, message);\n });\n }\n }\n this._handlers.get(channel)?.add(handler);\n }\n\n async unsubscribe(channel: string): Promise<void> {\n const prefixedChannel = this._prefix + channel;\n\n if (this._streams.has(prefixedChannel)) {\n this._streams.delete(prefixedChannel);\n this._streamOffsets.delete(prefixedChannel); // Cleanup offset\n } else {\n await this._driver.unsubscribe(prefixedChannel);\n }\n\n this._handlers.delete(channel);\n }\n\n async disconnect(): Promise<void> {\n this._closed = true;\n this._handlers.clear();\n this._streams.clear();\n this._presenceCache.clear();\n this._sequenceCounters.clear();\n if (this._unsubError) this._unsubError();\n if (this._unsubReconnect) this._unsubReconnect();\n // Disconnect all pool drivers\n await Promise.allSettled(this._driverPool.map((d) => d.disconnect()));\n }\n\n private _handleMessage(channel: string, message: string): void {\n const handlers = this._handlers.get(channel);\n if (!handlers) return;\n\n let data: unknown;\n try {\n data = JSON.parse(message);\n } catch {\n data = message;\n }\n\n for (const handler of handlers) {\n try {\n handler(data);\n } catch {\n // Swallow handler errors\n }\n }\n }\n\n // ─── Stream Polling ───────────────────────────────────────────────\n\n private _ensurePolling() {\n if (this._polling || this._closed) return;\n this._polling = true;\n this._pollLoop().catch(() => {\n this._polling = false;\n });\n }\n\n private async _pollLoop() {\n while (!this._closed) {\n if (this._streams.size === 0) {\n await new Promise((resolve) => setTimeout(resolve, 1000));\n continue;\n }\n\n const streamsArg = Array.from(this._streams).map((key) => ({\n key,\n id: this._streamOffsets.get(key) || \"$\",\n }));\n\n try {\n // Block for 1s. This allows picking up new subscriptions reasonably fast.\n const entries = await this._driver.xread(streamsArg, undefined, 1000);\n\n if (entries) {\n for (const entry of entries) {\n const channel = entry.stream.replace(this._prefix, \"\"); // remove prefix to find handler key\n\n for (const msg of entry.messages) {\n // Update offset\n this._streamOffsets.set(entry.stream, msg.id);\n\n const messageContent = msg.data.message;\n if (messageContent) {\n this._handleMessage(channel, messageContent);\n }\n }\n }\n }\n } catch (_err) {\n // Log error? For now swallow to keep loop alive\n // Avoid tight loop on error\n await new Promise((resolve) => setTimeout(resolve, 1000));\n }\n }\n this._polling = false;\n }\n\n // ─── Presence Registry ─────────────────────────────────────────────\n\n async setPresence(\n identity: string,\n nodeId: string,\n ttl: number,\n ): Promise<void> {\n const key = `${this._prefix}presence:${identity}`;\n // Cache for rehydration on reconnect (C3)\n this._presenceCache.set(identity, { nodeId, ttl });\n await this._driver.set(key, nodeId, ttl);\n }\n\n async getPresence(identity: string): Promise<string | null> {\n const key = `${this._prefix}presence:${identity}`;\n return await this._driver.get(key);\n }\n\n async getPresenceBatch(identities: string[]): Promise<(string | null)[]> {\n if (identities.length === 0) return [];\n const keys = identities.map((id) => `${this._prefix}presence:${id}`);\n if (this._driver.mget) {\n return await this._driver.mget(keys);\n }\n // Fallback if mget not available\n return await Promise.all(keys.map((k) => this._driver.get(k)));\n }\n\n async removePresence(identity: string): Promise<void> {\n const key = `${this._prefix}presence:${identity}`;\n await this._driver.del(key);\n }\n\n // ─── Observability Pipeline ────────────────────────────────────────\n\n async metrics(): Promise<Record<string, unknown>> {\n let pendingMessages = 0;\n const streamDetails: Record<string, number> = {};\n\n // Calculate \"consumer lag\" by checking the length of all active streams\n // Since we use MAXLEN for trimming, XLEN directly equals pending unread messages\n for (const streamKey of this._streams) {\n try {\n const length = await this._driver.xlen(streamKey);\n pendingMessages += length;\n streamDetails[streamKey] = length;\n } catch {\n // Ignore failures for individual stream stats\n streamDetails[streamKey] = -1;\n }\n }\n\n return {\n pendingMessages,\n activeStreams: this._streams.size,\n streamDetails,\n };\n }\n\n // ─── C1: Batch Presence Pipeline ────────────────────────────────────\n\n /**\n * Set multiple presence entries in a single Redis pipeline.\n * Reduces N network round-trips to 1 for bulk presence updates.\n */\n async setPresenceBatch(\n entries: { identity: string; nodeId: string; ttl?: number }[],\n ): Promise<void> {\n if (entries.length === 0) return;\n\n const batchEntries = entries.map(({ identity, nodeId, ttl }) => {\n const key = `${this._prefix}presence:${identity}`;\n const ttlSeconds = ttl ?? this._presenceTtlSeconds;\n // Cache for rehydration\n this._presenceCache.set(identity, { nodeId, ttl: ttlSeconds });\n return { key, value: nodeId, ttlSeconds };\n });\n\n await this._driver.setPresenceBatch(batchEntries);\n }\n\n // ─── C3: Redis Failure Rehydration ──────────────────────────────────\n\n /**\n * Re-syncs all cached presence entries to Redis after a reconnection.\n * Called automatically when the Redis client reconnects.\n */\n private async _rehydratePresence(): Promise<void> {\n if (this._presenceCache.size === 0) return;\n\n const entries = Array.from(this._presenceCache.entries()).map(\n ([identity, { nodeId, ttl }]) => ({\n key: `${this._prefix}presence:${identity}`,\n value: nodeId,\n ttlSeconds: ttl,\n }),\n );\n\n await this._driver.setPresenceBatch(entries);\n }\n}\n"],"mappings":"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","names":[]}
|