@zintrust/redis-rpc 2.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,257 @@
1
+ # @zintrust/redis-rpc
2
+
3
+ `@zintrust/redis-rpc` provides an HTTP RPC boundary for Redis-backed BullMQ operations. It is designed for runtimes that cannot safely create direct Redis or BullMQ connections, such as Cloudflare Workers, queue dashboards, remote worker controllers, and thin application processes.
4
+
5
+ Instead of asking every package to emulate Redis commands or BullMQ Lua scripts, the caller sends a typed intent to a backend-owned RPC server. The RPC server owns the real Redis connection, BullMQ queues, queue events, and worker instances.
6
+
7
+ ## When to use it
8
+
9
+ Use Redis RPC when:
10
+
11
+ - `USE_REDIS_PROXY=true` and `REDIS_RPC_URL` are both configured.
12
+ - Queue producers run in an environment where direct TCP Redis is unavailable.
13
+ - Queue monitor or worker dashboard code needs queue state without constructing local BullMQ clients.
14
+ - You want a single backend process to own Redis credentials and BullMQ execution details.
15
+
16
+ Use direct BullMQ/Redis when your process can safely connect to Redis and does not need a proxy boundary.
17
+
18
+ ## Package layout
19
+
20
+ - `server.ts` starts the HTTP RPC server and exposes `/health` and `/rpc`.
21
+ - `backend.ts` implements the default services for queues, workers, queue monitor reads, and raw Redis calls.
22
+ - `client.ts` is the low-level HTTP client.
23
+ - `adapters.ts` contains convenience wrappers used by `@zintrust/queue-redis`, `@zintrust/queue-monitor`, and worker tooling.
24
+ - `env.ts` reads runtime configuration from `.env` and `process.env`.
25
+ - `types.ts` contains the public request, client, backend, and server types.
26
+ - `test-bullmq.mjs` is an end-to-end smoke test against a real Redis instance.
27
+
28
+ ## Installation
29
+
30
+ ```bash
31
+ npm install @zintrust/redis-rpc
32
+ ```
33
+
34
+ For monorepo development this package is built like the other ZinTrust packages:
35
+
36
+ ```bash
37
+ npm --prefix packages/redis-rpc run build
38
+ ```
39
+
40
+ ## Configuration
41
+
42
+ The server and client read these variables:
43
+
44
+ | Variable | Default | Purpose |
45
+ | -------------------------- | ------------------------------------ | --------------------------------------------------------------- |
46
+ | `REDIS_RPC_URL` | empty | Full client base URL, for example `https://queues.example.com`. |
47
+ | `REDIS_RPC_HOST` | `127.0.0.1` | Server listen host and client host fallback. |
48
+ | `REDIS_RPC_PORT` | `8794` | Server listen port and client port fallback. |
49
+ | `REDIS_RPC_SECRET` | `REDIS_PROXY_SECRET` or `APP_KEY` | Shared secret sent as `x-redis-rpc-secret`. |
50
+ | `REDIS_RPC_REDIS_HOST` | `REDIS_HOST` or `127.0.0.1` | Redis host used by the RPC backend. |
51
+ | `REDIS_RPC_REDIS_PORT` | `REDIS_PORT` or `6379` | Redis port used by the RPC backend. |
52
+ | `REDIS_RPC_REDIS_PASSWORD` | `REDIS_PASSWORD` | Redis password used by the RPC backend. |
53
+ | `REDIS_RPC_REDIS_DB` | `REDIS_QUEUE_DB`, `REDIS_DB`, or `0` | Redis database used for queue operations. |
54
+ | `REDIS_RPC_BULLMQ_PREFIX` | `BULLMQ_PREFIX` or `bull` | BullMQ key prefix used by the backend. |
55
+ | `REDIS_RPC_TIMEOUT_MS` | `30000` | Client-side timeout used by integrations. |
56
+ | `REDIS_RPC_RETRY_MAX` | `2` | Client-side retry count used by integrations. |
57
+ | `REDIS_RPC_RETRY_DELAY_MS` | `500` | Client-side retry delay used by integrations. |
58
+
59
+ Set both `USE_REDIS_PROXY=true` and `REDIS_RPC_URL` to make supported ZinTrust packages select Redis RPC automatically. `USE_REDIS_PROXY=true` by itself does not enable Redis RPC; it only says the process is allowed to use a Redis proxy transport.
60
+
61
+ ## Running the server
62
+
63
+ From the repository:
64
+
65
+ ```bash
66
+ tsx packages/redis-rpc/server.ts
67
+ ```
68
+
69
+ From a ZinTrust project with `@zintrust/redis-rpc` installed:
70
+
71
+ ```bash
72
+ zin redis-rpc
73
+ # or
74
+ zin s redis-rpc
75
+ ```
76
+
77
+ CLI overrides:
78
+
79
+ ```bash
80
+ zin redis-rpc --host 0.0.0.0 --port 8794 --redis-host 127.0.0.1 --redis-db 1
81
+ ```
82
+
83
+ Health check:
84
+
85
+ ```bash
86
+ curl http://127.0.0.1:8794/health
87
+ ```
88
+
89
+ Example RPC request:
90
+
91
+ ```bash
92
+ curl -X POST http://127.0.0.1:8794/rpc \
93
+ -H 'content-type: application/json' \
94
+ -H "x-redis-rpc-secret: $REDIS_RPC_SECRET" \
95
+ -d '{
96
+ "requestId": "example-1",
97
+ "service": "queue",
98
+ "method": "add",
99
+ "payload": {
100
+ "target": "emails",
101
+ "args": ["send", {"to":"dev@example.com"}, {"attempts":3}]
102
+ }
103
+ }'
104
+ ```
105
+
106
+ ## Client usage
107
+
108
+ ```ts
109
+ import { createRedisRpcClient } from '@zintrust/redis-rpc';
110
+
111
+ const client = createRedisRpcClient({
112
+ baseUrl: process.env.REDIS_RPC_URL,
113
+ secret: process.env.REDIS_RPC_SECRET,
114
+ });
115
+
116
+ const job = await client.queue('add', {
117
+ target: 'emails',
118
+ args: ['send', { to: 'dev@example.com' }, { attempts: 3 }],
119
+ });
120
+
121
+ const counts = await client.queue('getJobCounts', { target: 'emails' });
122
+ ```
123
+
124
+ ## Adapter usage
125
+
126
+ ```ts
127
+ import {
128
+ createBullMqRpcQueue,
129
+ createQueueMonitorRpcDriver,
130
+ createWorkerRpcRuntime,
131
+ } from '@zintrust/redis-rpc/adapters';
132
+
133
+ const queue = createBullMqRpcQueue('emails');
134
+ await queue.add('send', { to: 'dev@example.com' }, { attempts: 3 });
135
+
136
+ const monitor = createQueueMonitorRpcDriver();
137
+ const snapshot = await monitor.getSnapshot(['emails']);
138
+
139
+ const workers = createWorkerRpcRuntime();
140
+ await workers.startWorker('emails', 'emails-worker', { processor: 'echo' });
141
+ ```
142
+
143
+ ## Built-in services
144
+
145
+ ### `queue`
146
+
147
+ Supported methods:
148
+
149
+ - `add` / `enqueue`
150
+ - `dequeue`
151
+ - `ack`
152
+ - `get` / `getJob`
153
+ - `getJobs`
154
+ - `getJobCounts` / `counts`
155
+ - `count` / `length`
156
+ - `pause`
157
+ - `resume`
158
+ - `drain`
159
+ - `clean`
160
+ - `removeJob`
161
+ - `retryJob`
162
+ - `promoteJob`
163
+ - `obliterate`
164
+ - `closeQueue`
165
+
166
+ Queue requests identify the queue with `payload.target`, `payload.queueName`, or `payload.queue`.
167
+
168
+ ### `worker`
169
+
170
+ Supported methods:
171
+
172
+ - `startWorker`
173
+ - `stopWorker`
174
+ - `list`
175
+
176
+ The default backend processors are intentionally small (`echo`, `sum`, and `fail`) so smoke tests and simple remote workers can run without loading application code. Register custom backend services or extend the backend process when production worker processors need application-specific behavior.
177
+
178
+ ### `queue-monitor`
179
+
180
+ Supported methods:
181
+
182
+ - `getSnapshot`
183
+ - `getEvents`
184
+ - `getRecentJobsForQueue`
185
+
186
+ `@zintrust/queue-monitor` uses this service automatically when both `USE_REDIS_PROXY=true` and `REDIS_RPC_URL` are configured.
187
+
188
+ ### `redis`
189
+
190
+ Supported methods:
191
+
192
+ - `ping`
193
+ - `call`
194
+ - `pipeline`
195
+ - `multi`
196
+
197
+ Use raw Redis calls sparingly. Prefer queue and monitor methods because they preserve the BullMQ abstraction and keep Redis command details out of callers.
198
+
199
+ `call` accepts either positional args (`{ "args": ["SET", "key", "value", "EX", 60] }`) or a `command` plus args. Object-style option arguments are expanded for Redis clients, so cache calls such as `set(key, value, { EX: 60 })` work through RPC. `pipeline` and `multi` accept a `commands` array:
200
+
201
+ ```json
202
+ {
203
+ "service": "redis",
204
+ "method": "multi",
205
+ "payload": {
206
+ "commands": [
207
+ { "command": "SET", "args": ["key1", "value1"] },
208
+ { "command": "INCRBY", "args": ["counter", 1] }
209
+ ],
210
+ "transaction": true
211
+ }
212
+ }
213
+ ```
214
+
215
+ ## Custom services
216
+
217
+ ```ts
218
+ import { createRedisRpcBackend, listenRedisRpcServer } from '@zintrust/redis-rpc/server';
219
+
220
+ const backend = createRedisRpcBackend();
221
+
222
+ backend.registerService('reports', async ({ method, payload }) => {
223
+ if (method !== 'refresh') throw new Error(`Unsupported reports method: ${method}`);
224
+ return { accepted: true, reportId: payload.reportId };
225
+ });
226
+
227
+ await listenRedisRpcServer({ backend, port: 8794 });
228
+ ```
229
+
230
+ ## ZinTrust integrations
231
+
232
+ - `@zintrust/queue-redis` routes `enqueue`, `dequeue`, `ack`, `length`, and `drain` through Redis RPC only when `USE_REDIS_PROXY=true` and `REDIS_RPC_URL` is configured.
233
+ - `@zintrust/queue-monitor` reads snapshots, job counts, recent jobs, and retry operations through Redis RPC in the same explicit mode.
234
+ - Core Redis transport uses Redis RPC for raw Redis commands when both flags are set, so cache and lock code can use Redis RPC without the older Redis HTTP proxy. When `USE_REDIS_PROXY=true` but `REDIS_RPC_URL` is empty, core Redis transport falls back to the legacy Redis HTTP proxy.
235
+ - `@zintrust/cache-redis` supports Redis RPC for the documented cache surface: `get`, `set`, `delete`, `clear`, `has`, `increment`, `decrement`, `getRedisClient().pipeline()`, and `getRedisClient().multi()`.
236
+ - Core env scaffolding includes the `REDIS_RPC_*` variables so generated projects can opt in without hand-editing config types.
237
+ - The existing queue HTTP gateway remains available for signed `/api/_sys/queue/rpc` traffic; when the queue driver is in Redis RPC mode, that gateway delegates to Redis RPC-backed queue operations.
238
+
239
+ ## Verification
240
+
241
+ Run the focused checks:
242
+
243
+ ```bash
244
+ npm --prefix packages/redis-rpc run type-check
245
+ npm --prefix packages/redis-rpc run build
246
+ npm --prefix packages/queue-redis run build
247
+ npm --prefix packages/queue-monitor run build
248
+ npm --prefix packages/workers run build
249
+ ```
250
+
251
+ Run the BullMQ smoke test when a Redis instance is available:
252
+
253
+ ```bash
254
+ npm --prefix packages/redis-rpc run test
255
+ ```
256
+
257
+ The smoke test starts the RPC server, verifies Redis `PING`, adds normal and delayed jobs, starts backend-owned workers, checks completed and failed job states, reads queue monitor snapshots, removes jobs, drains, cleans, and obliterates the temporary queue.
@@ -0,0 +1,43 @@
1
+ import type { RedisRpcClient, RedisRpcClientOptions } from './types';
2
+ type QueueRpcMethods = Readonly<{
3
+ add: (name: string, data?: unknown, opts?: Record<string, unknown>) => Promise<unknown>;
4
+ enqueue: (name: string, data?: unknown, opts?: Record<string, unknown>) => Promise<unknown>;
5
+ dequeue: (visibilityTimeoutMs?: number) => Promise<unknown>;
6
+ ack: (jobId: string) => Promise<unknown>;
7
+ get: (data?: Record<string, unknown>) => Promise<unknown>;
8
+ getJob: (jobId: string) => Promise<unknown>;
9
+ getJobs: (states?: string[], start?: number, end?: number, asc?: boolean) => Promise<unknown>;
10
+ getJobCounts: (...types: string[]) => Promise<unknown>;
11
+ count: () => Promise<unknown>;
12
+ pause: () => Promise<unknown>;
13
+ resume: () => Promise<unknown>;
14
+ drain: (delayed?: boolean) => Promise<unknown>;
15
+ clean: (grace?: number, limit?: number, type?: string) => Promise<unknown>;
16
+ removeJob: (jobId: string) => Promise<unknown>;
17
+ retryJob: (jobId: string, state?: string) => Promise<unknown>;
18
+ promoteJob: (jobId: string) => Promise<unknown>;
19
+ obliterate: (options?: Record<string, unknown>) => Promise<unknown>;
20
+ closeQueue: () => Promise<unknown>;
21
+ close: () => Promise<unknown>;
22
+ }>;
23
+ type WorkerRpcMethods = Readonly<{
24
+ startWorker: (queueName: string, workerName: string, options?: Record<string, unknown>) => Promise<unknown>;
25
+ stopWorker: (workerName: string) => Promise<unknown>;
26
+ list: () => Promise<unknown>;
27
+ }>;
28
+ type QueueMonitorRpcMethods = Readonly<{
29
+ getSnapshot: (queueNames?: string[]) => Promise<unknown>;
30
+ getEvents: (queueName: string) => Promise<unknown>;
31
+ getRecentJobsForQueue: (queueName: string, limit?: number) => Promise<unknown>;
32
+ }>;
33
+ type AdapterOptions = RedisRpcClientOptions & {
34
+ client?: RedisRpcClient;
35
+ };
36
+ type ServiceAdapterOptions = AdapterOptions & {
37
+ target?: string;
38
+ };
39
+ export declare const createBullMqRpcQueue: (queueName: string, options?: AdapterOptions) => QueueRpcMethods;
40
+ export declare const createWorkerRpcRuntime: (options?: AdapterOptions) => WorkerRpcMethods;
41
+ export declare const createQueueMonitorRpcDriver: (options?: AdapterOptions) => QueueMonitorRpcMethods;
42
+ export declare const createRedisRpcService: <TService extends object>(service: string, options?: ServiceAdapterOptions) => TService;
43
+ export {};
@@ -0,0 +1,48 @@
1
+ import { createRedisRpcClient } from './client.js';
2
+ const resolveClient = (options = {}) => {
3
+ return options.client || createRedisRpcClient(options);
4
+ };
5
+ export const createBullMqRpcQueue = (queueName, options = {}) => {
6
+ const client = resolveClient(options);
7
+ return Object.freeze({
8
+ add: (...args) => client.queue('add', { target: queueName, args }),
9
+ enqueue: (...args) => client.queue('add', { target: queueName, args }),
10
+ dequeue: (visibilityTimeoutMs) => client.queue('dequeue', { target: queueName, visibilityTimeoutMs }),
11
+ ack: (...args) => client.queue('ack', { target: queueName, args }),
12
+ get: (data = {}) => client.queue('getJob', { target: queueName, ...data }),
13
+ getJob: (...args) => client.queue('getJob', { target: queueName, args }),
14
+ getJobs: (...args) => client.queue('getJobs', { target: queueName, args }),
15
+ getJobCounts: (...args) => client.queue('getJobCounts', { target: queueName, args }),
16
+ count: (...args) => client.queue('count', { target: queueName, args }),
17
+ pause: (...args) => client.queue('pause', { target: queueName, args }),
18
+ resume: (...args) => client.queue('resume', { target: queueName, args }),
19
+ drain: (...args) => client.queue('drain', { target: queueName, args }),
20
+ clean: (...args) => client.queue('clean', { target: queueName, args }),
21
+ removeJob: (...args) => client.queue('removeJob', { target: queueName, args }),
22
+ retryJob: (...args) => client.queue('retryJob', { target: queueName, args }),
23
+ promoteJob: (...args) => client.queue('promoteJob', { target: queueName, args }),
24
+ obliterate: (...args) => client.queue('obliterate', { target: queueName, args }),
25
+ closeQueue: (...args) => client.queue('closeQueue', { target: queueName, args }),
26
+ close: (...args) => client.queue('closeQueue', { target: queueName, args }),
27
+ });
28
+ };
29
+ export const createWorkerRpcRuntime = (options = {}) => {
30
+ const client = resolveClient(options);
31
+ return Object.freeze({
32
+ startWorker: (...args) => client.worker('startWorker', { args }),
33
+ stopWorker: (...args) => client.worker('stopWorker', { args }),
34
+ list: (...args) => client.worker('list', { args }),
35
+ });
36
+ };
37
+ export const createQueueMonitorRpcDriver = (options = {}) => {
38
+ const client = resolveClient(options);
39
+ return Object.freeze({
40
+ getSnapshot: (...args) => client.monitor('getSnapshot', { args }),
41
+ getEvents: (...args) => client.monitor('getEvents', { args }),
42
+ getRecentJobsForQueue: (...args) => client.monitor('getRecentJobsForQueue', { args }),
43
+ });
44
+ };
45
+ export const createRedisRpcService = (service, options = {}) => {
46
+ const client = resolveClient(options);
47
+ return client.service(service, options.target);
48
+ };
@@ -0,0 +1,2 @@
1
+ import type { CreateRedisRpcBackendOptions, RedisRpcBackend } from './types';
2
+ export declare const createRedisRpcBackend: (options?: CreateRedisRpcBackendOptions) => RedisRpcBackend;