nestjs-web-repl 2.0.0 → 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 +47 -53
- 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
|
@@ -7,19 +7,18 @@ so `get(SomeService)`, `resolve(...)`, `select(...)`, and friends all work exact
|
|
|
7
7
|
they do in the local REPL — except reachable over HTTP, from anywhere, against a
|
|
8
8
|
running server.
|
|
9
9
|
|
|
10
|
-
> ## ⚠️ Security
|
|
10
|
+
> ## ⚠️ Security
|
|
11
11
|
>
|
|
12
|
-
>
|
|
13
|
-
>
|
|
14
|
-
>
|
|
15
|
-
>
|
|
16
|
-
> built-in safety rail, and it is a blunt boolean — it does not check who is
|
|
17
|
-
> asking.
|
|
12
|
+
> These endpoints run arbitrary code inside your app, with the full privileges of
|
|
13
|
+
> your Node process. That's the whole point — it's a debugging tool — and it's
|
|
14
|
+
> also the risk: anyone who can reach an enabled endpoint can run anything your
|
|
15
|
+
> app can.
|
|
18
16
|
>
|
|
19
|
-
>
|
|
20
|
-
>
|
|
21
|
-
> [Securing it](#securing-it)
|
|
22
|
-
>
|
|
17
|
+
> The module ships no authentication of its own; `enabled` is an on/off switch,
|
|
18
|
+
> not a lock. Control access yourself: gate `enabled` behind an environment
|
|
19
|
+
> variable and put your own guard in front of the routes ([Securing it](#securing-it)).
|
|
20
|
+
>
|
|
21
|
+
> Guarded and on a trusted network, it's a safe way to inspect a running app.
|
|
23
22
|
|
|
24
23
|
## Install
|
|
25
24
|
|
|
@@ -218,62 +217,56 @@ are distinct from, and not to be confused with, the client-visible `system`
|
|
|
218
217
|
*SSE event type* documented under [Endpoints](#endpoints), which only ever
|
|
219
218
|
carries `{ping}`/`{done}`/`{error}`).
|
|
220
219
|
|
|
221
|
-
### 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:**
|
|
222
230
|
|
|
223
231
|
```ts
|
|
224
|
-
import { Injectable, OnModuleDestroy } from '@nestjs/common';
|
|
225
232
|
import Redis from 'ioredis';
|
|
226
|
-
import
|
|
227
|
-
|
|
228
|
-
@Injectable()
|
|
229
|
-
export class RedisWebReplAdapter implements WebReplAdapter, OnModuleDestroy {
|
|
230
|
-
private readonly pub = new Redis(process.env.REDIS_URL);
|
|
231
|
-
private readonly sub = new Redis(process.env.REDIS_URL);
|
|
232
|
-
|
|
233
|
-
async publish(topic: string, message: string): Promise<void> {
|
|
234
|
-
await this.pub.publish(topic, message);
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
async subscribe(topic: string, handler: (message: string) => void): Promise<void> {
|
|
238
|
-
await this.sub.subscribe(topic);
|
|
239
|
-
this.sub.on('message', (channel, message) => {
|
|
240
|
-
if (channel === topic) handler(message);
|
|
241
|
-
});
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
async onModuleDestroy(): Promise<void> {
|
|
245
|
-
await this.pub.quit();
|
|
246
|
-
await this.sub.quit();
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
```
|
|
233
|
+
import { WebReplModule } from 'nestjs-web-repl';
|
|
234
|
+
import { IoRedisWebReplAdapter } from 'nestjs-web-repl/redis';
|
|
250
235
|
|
|
251
|
-
```ts
|
|
252
|
-
// as a ready-made instance
|
|
253
236
|
WebReplModule.register({
|
|
254
237
|
enabled: process.env.REPL_ENABLED === 'true',
|
|
255
|
-
adapter: new
|
|
256
|
-
instanceId: process.env.HOSTNAME, // shows up in `command` SSE events and
|
|
257
|
-
// internal webrepl:sys claim messages
|
|
238
|
+
adapter: new IoRedisWebReplAdapter(new Redis(process.env.REDIS_URL!)),
|
|
258
239
|
});
|
|
240
|
+
```
|
|
259
241
|
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
}
|
|
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';
|
|
265
248
|
|
|
266
|
-
// or build it with a factory
|
|
267
249
|
WebReplModule.register({
|
|
268
250
|
enabled: process.env.REPL_ENABLED === 'true',
|
|
269
251
|
adapter: {
|
|
270
|
-
useFactory: (
|
|
271
|
-
|
|
272
|
-
|
|
252
|
+
useFactory: async () => {
|
|
253
|
+
const client = createClient({ url: process.env.REDIS_URL });
|
|
254
|
+
await client.connect();
|
|
255
|
+
return new NodeRedisWebReplAdapter(client);
|
|
256
|
+
},
|
|
273
257
|
},
|
|
274
258
|
});
|
|
275
259
|
```
|
|
276
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
|
+
|
|
277
270
|
## Options (`WebReplModuleOptions`)
|
|
278
271
|
|
|
279
272
|
| Option | Type | Default | Notes |
|
|
@@ -390,10 +383,11 @@ We tell you this because the honest thing to do is let you judge the code on
|
|
|
390
383
|
its merits rather than guess at its origins. If you are skeptical of AI-written
|
|
391
384
|
code, here is what to actually look at:
|
|
392
385
|
|
|
393
|
-
- **The tests.**
|
|
386
|
+
- **The tests.** 100 automated tests, including a two-instance end-to-end test
|
|
394
387
|
that proves cross-instance command routing and output fan-out, and an
|
|
395
388
|
execution-proof test that resolves a real provider through the live REPL
|
|
396
|
-
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.
|
|
397
391
|
- **The commit history.** The real TDD trail is preserved — failing test,
|
|
398
392
|
implementation, fixes — including several rounds where review caught genuine
|
|
399
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.
|
|
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` —
|