nestjs-web-repl 2.0.1 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +37 -42
- package/dist/adapters/redis/base-redis-web-repl.adapter.d.ts +20 -0
- package/dist/adapters/redis/base-redis-web-repl.adapter.js +53 -0
- package/dist/adapters/redis/index.d.ts +5 -0
- package/dist/adapters/redis/index.js +9 -0
- package/dist/adapters/redis/ioredis-web-repl.adapter.d.ts +22 -0
- package/dist/adapters/redis/ioredis-web-repl.adapter.js +34 -0
- package/dist/adapters/redis/node-redis-web-repl.adapter.d.ts +24 -0
- package/dist/adapters/redis/node-redis-web-repl.adapter.js +42 -0
- package/dist/redis.d.ts +1 -0
- package/dist/redis.js +17 -0
- package/package.json +31 -2
- package/skill/SKILL.md +8 -7
package/README.md
CHANGED
|
@@ -217,62 +217,56 @@ are distinct from, and not to be confused with, the client-visible `system`
|
|
|
217
217
|
*SSE event type* documented under [Endpoints](#endpoints), which only ever
|
|
218
218
|
carries `{ping}`/`{done}`/`{error}`).
|
|
219
219
|
|
|
220
|
-
### Redis
|
|
220
|
+
### Redis (multi-instance)
|
|
221
|
+
|
|
222
|
+
Behind a load balancer the default in-memory adapter is per-process: a command
|
|
223
|
+
posted to one replica never reaches a session owned by another. Supply a Redis
|
|
224
|
+
adapter so every replica shares one pub/sub bus. Import it from the
|
|
225
|
+
`nestjs-web-repl/redis` subpath and hand it one connected client — the adapter
|
|
226
|
+
creates its own dedicated subscriber connection (Redis requires one for subscribe
|
|
227
|
+
mode) and closes only that connection on shutdown; your client stays yours.
|
|
228
|
+
|
|
229
|
+
**ioredis:**
|
|
221
230
|
|
|
222
231
|
```ts
|
|
223
|
-
import { Injectable, OnModuleDestroy } from '@nestjs/common';
|
|
224
232
|
import Redis from 'ioredis';
|
|
225
|
-
import
|
|
226
|
-
|
|
227
|
-
@Injectable()
|
|
228
|
-
export class RedisWebReplAdapter implements WebReplAdapter, OnModuleDestroy {
|
|
229
|
-
private readonly pub = new Redis(process.env.REDIS_URL);
|
|
230
|
-
private readonly sub = new Redis(process.env.REDIS_URL);
|
|
231
|
-
|
|
232
|
-
async publish(topic: string, message: string): Promise<void> {
|
|
233
|
-
await this.pub.publish(topic, message);
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
async subscribe(topic: string, handler: (message: string) => void): Promise<void> {
|
|
237
|
-
await this.sub.subscribe(topic);
|
|
238
|
-
this.sub.on('message', (channel, message) => {
|
|
239
|
-
if (channel === topic) handler(message);
|
|
240
|
-
});
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
async onModuleDestroy(): Promise<void> {
|
|
244
|
-
await this.pub.quit();
|
|
245
|
-
await this.sub.quit();
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
```
|
|
233
|
+
import { WebReplModule } from 'nestjs-web-repl';
|
|
234
|
+
import { IoRedisWebReplAdapter } from 'nestjs-web-repl/redis';
|
|
249
235
|
|
|
250
|
-
```ts
|
|
251
|
-
// as a ready-made instance
|
|
252
236
|
WebReplModule.register({
|
|
253
237
|
enabled: process.env.REPL_ENABLED === 'true',
|
|
254
|
-
adapter: new
|
|
255
|
-
instanceId: process.env.HOSTNAME, // shows up in `command` SSE events and
|
|
256
|
-
// internal webrepl:sys claim messages
|
|
238
|
+
adapter: new IoRedisWebReplAdapter(new Redis(process.env.REDIS_URL!)),
|
|
257
239
|
});
|
|
240
|
+
```
|
|
258
241
|
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
}
|
|
242
|
+
**node-redis:**
|
|
243
|
+
|
|
244
|
+
```ts
|
|
245
|
+
import { createClient } from 'redis';
|
|
246
|
+
import { WebReplModule } from 'nestjs-web-repl';
|
|
247
|
+
import { NodeRedisWebReplAdapter } from 'nestjs-web-repl/redis';
|
|
264
248
|
|
|
265
|
-
// or build it with a factory
|
|
266
249
|
WebReplModule.register({
|
|
267
250
|
enabled: process.env.REPL_ENABLED === 'true',
|
|
268
251
|
adapter: {
|
|
269
|
-
useFactory: (
|
|
270
|
-
|
|
271
|
-
|
|
252
|
+
useFactory: async () => {
|
|
253
|
+
const client = createClient({ url: process.env.REDIS_URL });
|
|
254
|
+
await client.connect();
|
|
255
|
+
return new NodeRedisWebReplAdapter(client);
|
|
256
|
+
},
|
|
272
257
|
},
|
|
273
258
|
});
|
|
274
259
|
```
|
|
275
260
|
|
|
261
|
+
`ioredis` and `redis` are optional peer dependencies — install whichever you use.
|
|
262
|
+
Both adapters wrap a small shared base; to target another broker, subclass
|
|
263
|
+
`BaseRedisWebReplAdapter` or implement `WebReplAdapter` directly.
|
|
264
|
+
|
|
265
|
+
The `adapter` extra also accepts a DI-configured provider — `{ useClass, imports? }`
|
|
266
|
+
or `{ useFactory, inject?, imports? }` — so a custom adapter can pull its own
|
|
267
|
+
dependencies (a shared client, a config service) from a Nest module. See the
|
|
268
|
+
[extras table](#options-webreplmoduleoptions) below.
|
|
269
|
+
|
|
276
270
|
## Options (`WebReplModuleOptions`)
|
|
277
271
|
|
|
278
272
|
| Option | Type | Default | Notes |
|
|
@@ -389,10 +383,11 @@ We tell you this because the honest thing to do is let you judge the code on
|
|
|
389
383
|
its merits rather than guess at its origins. If you are skeptical of AI-written
|
|
390
384
|
code, here is what to actually look at:
|
|
391
385
|
|
|
392
|
-
- **The tests.**
|
|
386
|
+
- **The tests.** 100 automated tests, including a two-instance end-to-end test
|
|
393
387
|
that proves cross-instance command routing and output fan-out, and an
|
|
394
388
|
execution-proof test that resolves a real provider through the live REPL
|
|
395
|
-
context. `npm test`, `npm run build`, and `npx tsc
|
|
389
|
+
context. `npm test`, `npm run build`, and `npx tsc -p tsconfig.build.json
|
|
390
|
+
--noEmit` are all green.
|
|
396
391
|
- **The commit history.** The real TDD trail is preserved — failing test,
|
|
397
392
|
implementation, fixes — including several rounds where review caught genuine
|
|
398
393
|
defects (the trickiest: `node:repl` completion detection on modern Node, and
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { WebReplAdapter } from '../../interfaces/web-repl-adapter.interface';
|
|
2
|
+
type Handler = (message: string) => void;
|
|
3
|
+
/**
|
|
4
|
+
* Library-agnostic Redis pub/sub bridge. Subclasses wire the abstract hooks to a
|
|
5
|
+
* concrete client. Topics are opaque; the service owns the `webrepl:*` names.
|
|
6
|
+
*/
|
|
7
|
+
export declare abstract class BaseRedisWebReplAdapter implements WebReplAdapter {
|
|
8
|
+
private readonly handlers;
|
|
9
|
+
private readonly subscribed;
|
|
10
|
+
private readonly logger;
|
|
11
|
+
publish(topic: string, message: string): Promise<void>;
|
|
12
|
+
subscribe(topic: string, handler: Handler): Promise<void>;
|
|
13
|
+
/** Fan an inbound message out to every handler registered for the topic. */
|
|
14
|
+
protected dispatch(topic: string, message: string): void;
|
|
15
|
+
onModuleDestroy(): Promise<void>;
|
|
16
|
+
protected abstract doPublish(topic: string, message: string): Promise<void>;
|
|
17
|
+
protected abstract ensureSubscribed(topic: string): Promise<void>;
|
|
18
|
+
protected abstract teardown(): Promise<void>;
|
|
19
|
+
}
|
|
20
|
+
export {};
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.BaseRedisWebReplAdapter = void 0;
|
|
4
|
+
const common_1 = require("@nestjs/common");
|
|
5
|
+
/**
|
|
6
|
+
* Library-agnostic Redis pub/sub bridge. Subclasses wire the abstract hooks to a
|
|
7
|
+
* concrete client. Topics are opaque; the service owns the `webrepl:*` names.
|
|
8
|
+
*/
|
|
9
|
+
class BaseRedisWebReplAdapter {
|
|
10
|
+
constructor() {
|
|
11
|
+
this.handlers = new Map();
|
|
12
|
+
this.subscribed = new Set();
|
|
13
|
+
this.logger = new common_1.Logger(this.constructor.name);
|
|
14
|
+
}
|
|
15
|
+
async publish(topic, message) {
|
|
16
|
+
await this.doPublish(topic, message);
|
|
17
|
+
}
|
|
18
|
+
async subscribe(topic, handler) {
|
|
19
|
+
let set = this.handlers.get(topic);
|
|
20
|
+
if (!set) {
|
|
21
|
+
set = new Set();
|
|
22
|
+
this.handlers.set(topic, set);
|
|
23
|
+
}
|
|
24
|
+
set.add(handler);
|
|
25
|
+
if (!this.subscribed.has(topic)) {
|
|
26
|
+
await this.ensureSubscribed(topic);
|
|
27
|
+
this.subscribed.add(topic);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
/** Fan an inbound message out to every handler registered for the topic. */
|
|
31
|
+
dispatch(topic, message) {
|
|
32
|
+
const set = this.handlers.get(topic);
|
|
33
|
+
if (!set)
|
|
34
|
+
return;
|
|
35
|
+
for (const handler of set) {
|
|
36
|
+
try {
|
|
37
|
+
handler(message);
|
|
38
|
+
}
|
|
39
|
+
catch (err) {
|
|
40
|
+
// A handler fault (e.g. malformed inbound JSON on a shared Redis
|
|
41
|
+
// channel) must not escape into the client's message emitter and crash
|
|
42
|
+
// the process, nor abort delivery to the remaining handlers.
|
|
43
|
+
this.logger.error(`web-repl adapter handler for "${topic}" threw: ${String(err)}`);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
async onModuleDestroy() {
|
|
48
|
+
await this.teardown();
|
|
49
|
+
this.handlers.clear();
|
|
50
|
+
this.subscribed.clear();
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
exports.BaseRedisWebReplAdapter = BaseRedisWebReplAdapter;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { BaseRedisWebReplAdapter } from './base-redis-web-repl.adapter';
|
|
2
|
+
export { IoRedisWebReplAdapter } from './ioredis-web-repl.adapter';
|
|
3
|
+
export type { IoRedisLike } from './ioredis-web-repl.adapter';
|
|
4
|
+
export { NodeRedisWebReplAdapter } from './node-redis-web-repl.adapter';
|
|
5
|
+
export type { NodeRedisLike } from './node-redis-web-repl.adapter';
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.NodeRedisWebReplAdapter = exports.IoRedisWebReplAdapter = exports.BaseRedisWebReplAdapter = void 0;
|
|
4
|
+
var base_redis_web_repl_adapter_1 = require("./base-redis-web-repl.adapter");
|
|
5
|
+
Object.defineProperty(exports, "BaseRedisWebReplAdapter", { enumerable: true, get: function () { return base_redis_web_repl_adapter_1.BaseRedisWebReplAdapter; } });
|
|
6
|
+
var ioredis_web_repl_adapter_1 = require("./ioredis-web-repl.adapter");
|
|
7
|
+
Object.defineProperty(exports, "IoRedisWebReplAdapter", { enumerable: true, get: function () { return ioredis_web_repl_adapter_1.IoRedisWebReplAdapter; } });
|
|
8
|
+
var node_redis_web_repl_adapter_1 = require("./node-redis-web-repl.adapter");
|
|
9
|
+
Object.defineProperty(exports, "NodeRedisWebReplAdapter", { enumerable: true, get: function () { return node_redis_web_repl_adapter_1.NodeRedisWebReplAdapter; } });
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { BaseRedisWebReplAdapter } from './base-redis-web-repl.adapter';
|
|
2
|
+
/** Minimal structural surface of an ioredis client (no `ioredis` import). */
|
|
3
|
+
export interface IoRedisLike {
|
|
4
|
+
publish(channel: string, message: string): Promise<number>;
|
|
5
|
+
subscribe(channel: string): Promise<unknown>;
|
|
6
|
+
duplicate(): IoRedisLike;
|
|
7
|
+
on(event: 'message', listener: (channel: string, message: string) => void): unknown;
|
|
8
|
+
quit(): Promise<unknown>;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Redis adapter backed by ioredis. Pass one connected client (the publisher);
|
|
12
|
+
* the adapter creates and owns a duplicated subscriber connection.
|
|
13
|
+
*/
|
|
14
|
+
export declare class IoRedisWebReplAdapter extends BaseRedisWebReplAdapter {
|
|
15
|
+
private readonly publisher;
|
|
16
|
+
private subscriber?;
|
|
17
|
+
constructor(publisher: IoRedisLike);
|
|
18
|
+
private ensureSubscriber;
|
|
19
|
+
protected doPublish(topic: string, message: string): Promise<void>;
|
|
20
|
+
protected ensureSubscribed(topic: string): Promise<void>;
|
|
21
|
+
protected teardown(): Promise<void>;
|
|
22
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.IoRedisWebReplAdapter = void 0;
|
|
4
|
+
const base_redis_web_repl_adapter_1 = require("./base-redis-web-repl.adapter");
|
|
5
|
+
/**
|
|
6
|
+
* Redis adapter backed by ioredis. Pass one connected client (the publisher);
|
|
7
|
+
* the adapter creates and owns a duplicated subscriber connection.
|
|
8
|
+
*/
|
|
9
|
+
class IoRedisWebReplAdapter extends base_redis_web_repl_adapter_1.BaseRedisWebReplAdapter {
|
|
10
|
+
constructor(publisher) {
|
|
11
|
+
super();
|
|
12
|
+
this.publisher = publisher;
|
|
13
|
+
}
|
|
14
|
+
ensureSubscriber() {
|
|
15
|
+
if (!this.subscriber) {
|
|
16
|
+
this.subscriber = this.publisher.duplicate();
|
|
17
|
+
this.subscriber.on('message', (channel, message) => this.dispatch(channel, message));
|
|
18
|
+
}
|
|
19
|
+
return this.subscriber;
|
|
20
|
+
}
|
|
21
|
+
async doPublish(topic, message) {
|
|
22
|
+
await this.publisher.publish(topic, message);
|
|
23
|
+
}
|
|
24
|
+
async ensureSubscribed(topic) {
|
|
25
|
+
await this.ensureSubscriber().subscribe(topic);
|
|
26
|
+
}
|
|
27
|
+
async teardown() {
|
|
28
|
+
if (this.subscriber) {
|
|
29
|
+
await this.subscriber.quit();
|
|
30
|
+
this.subscriber = undefined;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
exports.IoRedisWebReplAdapter = IoRedisWebReplAdapter;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { BaseRedisWebReplAdapter } from './base-redis-web-repl.adapter';
|
|
2
|
+
/** Minimal structural surface of a node-redis v4 client (no `redis` import). */
|
|
3
|
+
export interface NodeRedisLike {
|
|
4
|
+
publish(channel: string, message: string): Promise<number>;
|
|
5
|
+
subscribe(channel: string, listener: (message: string, channel: string) => void): Promise<void>;
|
|
6
|
+
duplicate(): NodeRedisLike;
|
|
7
|
+
connect(): Promise<unknown>;
|
|
8
|
+
quit(): Promise<unknown>;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Redis adapter backed by node-redis v4. Pass one connected client (the
|
|
12
|
+
* publisher); the adapter duplicates it, connects the copy (v4 duplicates start
|
|
13
|
+
* disconnected), and owns that subscriber connection.
|
|
14
|
+
*/
|
|
15
|
+
export declare class NodeRedisWebReplAdapter extends BaseRedisWebReplAdapter {
|
|
16
|
+
private readonly publisher;
|
|
17
|
+
private subscriber?;
|
|
18
|
+
private connecting?;
|
|
19
|
+
constructor(publisher: NodeRedisLike);
|
|
20
|
+
private ensureSubscriber;
|
|
21
|
+
protected doPublish(topic: string, message: string): Promise<void>;
|
|
22
|
+
protected ensureSubscribed(topic: string): Promise<void>;
|
|
23
|
+
protected teardown(): Promise<void>;
|
|
24
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.NodeRedisWebReplAdapter = void 0;
|
|
4
|
+
const base_redis_web_repl_adapter_1 = require("./base-redis-web-repl.adapter");
|
|
5
|
+
/**
|
|
6
|
+
* Redis adapter backed by node-redis v4. Pass one connected client (the
|
|
7
|
+
* publisher); the adapter duplicates it, connects the copy (v4 duplicates start
|
|
8
|
+
* disconnected), and owns that subscriber connection.
|
|
9
|
+
*/
|
|
10
|
+
class NodeRedisWebReplAdapter extends base_redis_web_repl_adapter_1.BaseRedisWebReplAdapter {
|
|
11
|
+
constructor(publisher) {
|
|
12
|
+
super();
|
|
13
|
+
this.publisher = publisher;
|
|
14
|
+
}
|
|
15
|
+
async ensureSubscriber() {
|
|
16
|
+
if (this.subscriber)
|
|
17
|
+
return this.subscriber;
|
|
18
|
+
if (!this.connecting) {
|
|
19
|
+
const sub = this.publisher.duplicate();
|
|
20
|
+
this.connecting = sub.connect().then(() => {
|
|
21
|
+
this.subscriber = sub;
|
|
22
|
+
return sub;
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
return this.connecting;
|
|
26
|
+
}
|
|
27
|
+
async doPublish(topic, message) {
|
|
28
|
+
await this.publisher.publish(topic, message);
|
|
29
|
+
}
|
|
30
|
+
async ensureSubscribed(topic) {
|
|
31
|
+
const sub = await this.ensureSubscriber();
|
|
32
|
+
await sub.subscribe(topic, (message) => this.dispatch(topic, message));
|
|
33
|
+
}
|
|
34
|
+
async teardown() {
|
|
35
|
+
if (this.subscriber) {
|
|
36
|
+
await this.subscriber.quit();
|
|
37
|
+
this.subscriber = undefined;
|
|
38
|
+
this.connecting = undefined;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
exports.NodeRedisWebReplAdapter = NodeRedisWebReplAdapter;
|
package/dist/redis.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './adapters/redis';
|
package/dist/redis.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./adapters/redis"), exports);
|
package/package.json
CHANGED
|
@@ -1,10 +1,28 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nestjs-web-repl",
|
|
3
|
-
"version": "2.0
|
|
3
|
+
"version": "2.1.0",
|
|
4
4
|
"description": "Expose a live NestJS REPL over the network (HTTP + SSE + Monaco UI).",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"types": "dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"default": "./dist/index.js"
|
|
12
|
+
},
|
|
13
|
+
"./redis": {
|
|
14
|
+
"types": "./dist/redis.d.ts",
|
|
15
|
+
"default": "./dist/redis.js"
|
|
16
|
+
},
|
|
17
|
+
"./package.json": "./package.json"
|
|
18
|
+
},
|
|
19
|
+
"typesVersions": {
|
|
20
|
+
"*": {
|
|
21
|
+
"redis": [
|
|
22
|
+
"dist/redis.d.ts"
|
|
23
|
+
]
|
|
24
|
+
}
|
|
25
|
+
},
|
|
8
26
|
"bin": {
|
|
9
27
|
"nestjs-web-repl": "dist/cli/index.js"
|
|
10
28
|
},
|
|
@@ -35,7 +53,17 @@
|
|
|
35
53
|
"peerDependencies": {
|
|
36
54
|
"@nestjs/common": "^10 || ^11",
|
|
37
55
|
"@nestjs/core": "^10 || ^11",
|
|
38
|
-
"rxjs": "^7"
|
|
56
|
+
"rxjs": "^7",
|
|
57
|
+
"ioredis": "^5",
|
|
58
|
+
"redis": "^4 || ^5"
|
|
59
|
+
},
|
|
60
|
+
"peerDependenciesMeta": {
|
|
61
|
+
"ioredis": {
|
|
62
|
+
"optional": true
|
|
63
|
+
},
|
|
64
|
+
"redis": {
|
|
65
|
+
"optional": true
|
|
66
|
+
}
|
|
39
67
|
},
|
|
40
68
|
"devDependencies": {
|
|
41
69
|
"@nestjs/common": "^11",
|
|
@@ -47,6 +75,7 @@
|
|
|
47
75
|
"@swc/core": "^1.15.43",
|
|
48
76
|
"@types/node": "^20",
|
|
49
77
|
"@types/supertest": "^6",
|
|
78
|
+
"ioredis-mock": "^8.13.1",
|
|
50
79
|
"reflect-metadata": "^0.2",
|
|
51
80
|
"rxjs": "^7",
|
|
52
81
|
"semantic-release": "^25.0.8",
|
package/skill/SKILL.md
CHANGED
|
@@ -89,13 +89,14 @@ Open `http://localhost:3000/repl/main/ui` in a browser for the interactive UI.
|
|
|
89
89
|
`@UseGuards(...)`, and pass it as `controller:` to `register`/`registerAsync`.
|
|
90
90
|
See "Securing it" in the package README.
|
|
91
91
|
- **Multiple app instances** (load-balanced replicas): the default in-memory
|
|
92
|
-
adapter is per-process
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
92
|
+
adapter is per-process. For Redis, import a ready-made adapter from the
|
|
93
|
+
`nestjs-web-repl/redis` subpath — `IoRedisWebReplAdapter` (ioredis) or
|
|
94
|
+
`NodeRedisWebReplAdapter` (node-redis) — and pass it as the `adapter` extra in
|
|
95
|
+
the same `register` call:
|
|
96
|
+
`register({ enabled, adapter: new IoRedisWebReplAdapter(client) })`. Hand it
|
|
97
|
+
one connected client; it owns its own subscriber connection. For any other
|
|
98
|
+
broker, supply a custom adapter (a ready instance, `{ useClass, imports }`, or
|
|
99
|
+
`{ useFactory, inject, imports }`). See "Adapter / multi-instance" in the README.
|
|
99
100
|
- **Options** (base path, TTLs, heartbeats) and **exported symbols:** see
|
|
100
101
|
"Options" and "Exports" in the README.
|
|
101
102
|
- **Runnable example gotcha:** run examples with `ts-node`, not `tsx` —
|