@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 +257 -0
- package/dist/adapters.d.ts +43 -0
- package/dist/adapters.js +48 -0
- package/dist/backend.d.ts +2 -0
- package/dist/backend.js +557 -0
- package/dist/build-manifest.json +94 -0
- package/dist/client.d.ts +2 -0
- package/dist/client.js +82 -0
- package/dist/env.d.ts +17 -0
- package/dist/env.js +24 -0
- package/dist/errors.d.ts +8 -0
- package/dist/errors.js +30 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +10 -0
- package/dist/server.d.ts +3 -0
- package/dist/server.js +65 -0
- package/dist/types.d.ts +65 -0
- package/dist/types.js +1 -0
- package/package.json +63 -0
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 {};
|
package/dist/adapters.js
ADDED
|
@@ -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
|
+
};
|