@dudousxd/nestjs-notifications-sse 0.2.0 → 0.3.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/dist/backplane.d.ts +27 -0
- package/dist/backplane.d.ts.map +1 -0
- package/dist/backplane.js +3 -0
- package/dist/backplane.js.map +1 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -1
- package/dist/index.js.map +1 -1
- package/dist/redis.backplane.d.ts +43 -0
- package/dist/redis.backplane.d.ts.map +1 -0
- package/dist/redis.backplane.js +49 -0
- package/dist/redis.backplane.js.map +1 -0
- package/dist/sse.hub.d.ts +23 -16
- package/dist/sse.hub.d.ts.map +1 -1
- package/dist/sse.hub.js +49 -20
- package/dist/sse.hub.js.map +1 -1
- package/dist/sse.module.d.ts +7 -0
- package/dist/sse.module.d.ts.map +1 -1
- package/dist/sse.module.js +1 -0
- package/dist/sse.module.js.map +1 -1
- package/dist/tokens.d.ts +2 -0
- package/dist/tokens.d.ts.map +1 -1
- package/dist/tokens.js +3 -1
- package/dist/tokens.js.map +1 -1
- package/package.json +6 -6
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/** A serializable SSE message routed through an {@link SseBackplane}. */
|
|
2
|
+
export interface SseBackplaneMessage {
|
|
3
|
+
data: unknown;
|
|
4
|
+
/** SSE event `type`. */
|
|
5
|
+
event?: string;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Cross-process fan-out for {@link import('./sse.hub').SseHub}.
|
|
9
|
+
*
|
|
10
|
+
* The default hub is **in-process** — a Subject per stream key — so a publish only reaches
|
|
11
|
+
* subscribers connected to the SAME node. In a multi-pod deployment, the code that publishes a
|
|
12
|
+
* notification and the node holding the user's SSE connection are often different processes (e.g. a
|
|
13
|
+
* worker writes, an API pod streams). Supply a backplane (e.g. {@link RedisSseBackplane}) and a
|
|
14
|
+
* publish on any node is broadcast to the SSE connections on every node.
|
|
15
|
+
*/
|
|
16
|
+
export interface SseBackplane {
|
|
17
|
+
/** Broadcast `message` for `key` to all nodes (including this one). */
|
|
18
|
+
publish(key: string, message: SseBackplaneMessage): void | Promise<void>;
|
|
19
|
+
/**
|
|
20
|
+
* Register the handler the hub uses to deliver inbound (cross-node) messages to its local
|
|
21
|
+
* subscribers. Called once when the hub initializes.
|
|
22
|
+
*/
|
|
23
|
+
subscribe(handler: (key: string, message: SseBackplaneMessage) => void): void | Promise<void>;
|
|
24
|
+
/** Tear down any connections/subscriptions. Called on module destroy. */
|
|
25
|
+
close?(): void | Promise<void>;
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=backplane.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"backplane.d.ts","sourceRoot":"","sources":["../src/backplane.ts"],"names":[],"mappings":"AAAA,yEAAyE;AACzE,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,OAAO,CAAC;IACd,wBAAwB;IACxB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;;;GAQG;AACH,MAAM,WAAW,YAAY;IAC3B,uEAAuE;IACvE,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,mBAAmB,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACzE;;;OAGG;IACH,SAAS,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,mBAAmB,KAAK,IAAI,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9F,yEAAyE;IACzE,KAAK,CAAC,IAAI,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAChC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"backplane.js","sourceRoot":"","sources":["../src/backplane.ts"],"names":[],"mappings":""}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
export { Sse, SseChannel, type SseChannelOptions, type SseNotification, } from './sse.channel';
|
|
2
2
|
export { SseHub } from './sse.hub';
|
|
3
3
|
export { SseChannelModule, type SseChannelModuleOptions } from './sse.module';
|
|
4
|
-
export { SSE_OPTIONS } from './tokens';
|
|
4
|
+
export { SSE_OPTIONS, SSE_BACKPLANE } from './tokens';
|
|
5
5
|
export { sseKey } from './sse-key';
|
|
6
|
+
export type { SseBackplane, SseBackplaneMessage } from './backplane';
|
|
7
|
+
export { RedisSseBackplane, type RedisSseBackplaneOptions, type RedisPubSubClient, } from './redis.backplane';
|
|
6
8
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,GAAG,EACH,UAAU,EACV,KAAK,iBAAiB,EACtB,KAAK,eAAe,GACrB,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AACnC,OAAO,EAAE,gBAAgB,EAAE,KAAK,uBAAuB,EAAE,MAAM,cAAc,CAAC;AAC9E,OAAO,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,GAAG,EACH,UAAU,EACV,KAAK,iBAAiB,EACtB,KAAK,eAAe,GACrB,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AACnC,OAAO,EAAE,gBAAgB,EAAE,KAAK,uBAAuB,EAAE,MAAM,cAAc,CAAC;AAC9E,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACtD,OAAO,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AACnC,YAAY,EAAE,YAAY,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AACrE,OAAO,EACL,iBAAiB,EACjB,KAAK,wBAAwB,EAC7B,KAAK,iBAAiB,GACvB,MAAM,mBAAmB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.sseKey = exports.SSE_OPTIONS = exports.SseChannelModule = exports.SseHub = exports.SseChannel = exports.Sse = void 0;
|
|
3
|
+
exports.RedisSseBackplane = exports.sseKey = exports.SSE_BACKPLANE = exports.SSE_OPTIONS = exports.SseChannelModule = exports.SseHub = exports.SseChannel = exports.Sse = void 0;
|
|
4
4
|
var sse_channel_1 = require("./sse.channel");
|
|
5
5
|
Object.defineProperty(exports, "Sse", { enumerable: true, get: function () { return sse_channel_1.Sse; } });
|
|
6
6
|
Object.defineProperty(exports, "SseChannel", { enumerable: true, get: function () { return sse_channel_1.SseChannel; } });
|
|
@@ -10,6 +10,9 @@ var sse_module_1 = require("./sse.module");
|
|
|
10
10
|
Object.defineProperty(exports, "SseChannelModule", { enumerable: true, get: function () { return sse_module_1.SseChannelModule; } });
|
|
11
11
|
var tokens_1 = require("./tokens");
|
|
12
12
|
Object.defineProperty(exports, "SSE_OPTIONS", { enumerable: true, get: function () { return tokens_1.SSE_OPTIONS; } });
|
|
13
|
+
Object.defineProperty(exports, "SSE_BACKPLANE", { enumerable: true, get: function () { return tokens_1.SSE_BACKPLANE; } });
|
|
13
14
|
var sse_key_1 = require("./sse-key");
|
|
14
15
|
Object.defineProperty(exports, "sseKey", { enumerable: true, get: function () { return sse_key_1.sseKey; } });
|
|
16
|
+
var redis_backplane_1 = require("./redis.backplane");
|
|
17
|
+
Object.defineProperty(exports, "RedisSseBackplane", { enumerable: true, get: function () { return redis_backplane_1.RedisSseBackplane; } });
|
|
15
18
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAAA,6CAKuB;AAJrB,kGAAA,GAAG,OAAA;AACH,yGAAA,UAAU,OAAA;AAIZ,qCAAmC;AAA1B,iGAAA,MAAM,OAAA;AACf,2CAA8E;AAArE,8GAAA,gBAAgB,OAAA;AACzB,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAAA,6CAKuB;AAJrB,kGAAA,GAAG,OAAA;AACH,yGAAA,UAAU,OAAA;AAIZ,qCAAmC;AAA1B,iGAAA,MAAM,OAAA;AACf,2CAA8E;AAArE,8GAAA,gBAAgB,OAAA;AACzB,mCAAsD;AAA7C,qGAAA,WAAW,OAAA;AAAE,uGAAA,aAAa,OAAA;AACnC,qCAAmC;AAA1B,iGAAA,MAAM,OAAA;AAEf,qDAI2B;AAHzB,oHAAA,iBAAiB,OAAA"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { SseBackplane, SseBackplaneMessage } from './backplane';
|
|
2
|
+
/**
|
|
3
|
+
* The slice of an `ioredis`-style client this backplane uses. Pass real `ioredis` instances (or any
|
|
4
|
+
* compatible client) — the package doesn't depend on `ioredis` itself, so you control the version
|
|
5
|
+
* and connection. Use SEPARATE clients for publisher and subscriber: a subscriber connection enters
|
|
6
|
+
* subscribe mode and can't issue regular commands.
|
|
7
|
+
*/
|
|
8
|
+
export interface RedisPubSubClient {
|
|
9
|
+
publish(channel: string, message: string): unknown;
|
|
10
|
+
subscribe(channel: string, callback?: (err: Error | null, count: number) => void): unknown;
|
|
11
|
+
on(event: 'message', listener: (channel: string, message: string) => void): unknown;
|
|
12
|
+
quit?(): unknown;
|
|
13
|
+
}
|
|
14
|
+
export interface RedisSseBackplaneOptions {
|
|
15
|
+
/** Client used to publish (regular command mode). */
|
|
16
|
+
publisher: RedisPubSubClient;
|
|
17
|
+
/** Client used to subscribe (enters subscribe mode). Must be a different connection. */
|
|
18
|
+
subscriber: RedisPubSubClient;
|
|
19
|
+
/** Pub/sub channel. Default `nestjs-notifications:sse`. */
|
|
20
|
+
channel?: string;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* A Redis pub/sub {@link SseBackplane}. Every publish fans out over one Redis channel to all nodes,
|
|
24
|
+
* so SSE connections on any pod receive notifications written on any other pod. Mirrors the common
|
|
25
|
+
* "writer pod / API pod" split.
|
|
26
|
+
*
|
|
27
|
+
* ```ts
|
|
28
|
+
* import Redis from 'ioredis';
|
|
29
|
+
* SseChannelModule.forRoot({
|
|
30
|
+
* backplane: new RedisSseBackplane({ publisher: new Redis(url), subscriber: new Redis(url) }),
|
|
31
|
+
* });
|
|
32
|
+
* ```
|
|
33
|
+
*/
|
|
34
|
+
export declare class RedisSseBackplane implements SseBackplane {
|
|
35
|
+
private readonly publisher;
|
|
36
|
+
private readonly subscriber;
|
|
37
|
+
private readonly channel;
|
|
38
|
+
constructor(options: RedisSseBackplaneOptions);
|
|
39
|
+
publish(key: string, message: SseBackplaneMessage): void;
|
|
40
|
+
subscribe(handler: (key: string, message: SseBackplaneMessage) => void): void;
|
|
41
|
+
close(): void;
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=redis.backplane.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"redis.backplane.d.ts","sourceRoot":"","sources":["../src/redis.backplane.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAErE;;;;;GAKG;AACH,MAAM,WAAW,iBAAiB;IAChC,OAAO,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC;IACnD,SAAS,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,CAAC,GAAG,EAAE,KAAK,GAAG,IAAI,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,GAAG,OAAO,CAAC;IAC3F,EAAE,CAAC,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,KAAK,IAAI,GAAG,OAAO,CAAC;IACpF,IAAI,CAAC,IAAI,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,wBAAwB;IACvC,qDAAqD;IACrD,SAAS,EAAE,iBAAiB,CAAC;IAC7B,wFAAwF;IACxF,UAAU,EAAE,iBAAiB,CAAC;IAC9B,2DAA2D;IAC3D,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAID;;;;;;;;;;;GAWG;AACH,qBAAa,iBAAkB,YAAW,YAAY;IACpD,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAoB;IAC9C,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAoB;IAC/C,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;gBAErB,OAAO,EAAE,wBAAwB;IAM7C,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,mBAAmB,GAAG,IAAI;IAIxD,SAAS,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,mBAAmB,KAAK,IAAI,GAAG,IAAI;IAa7E,KAAK,IAAI,IAAI;CAId"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.RedisSseBackplane = void 0;
|
|
4
|
+
const DEFAULT_CHANNEL = 'nestjs-notifications:sse';
|
|
5
|
+
/**
|
|
6
|
+
* A Redis pub/sub {@link SseBackplane}. Every publish fans out over one Redis channel to all nodes,
|
|
7
|
+
* so SSE connections on any pod receive notifications written on any other pod. Mirrors the common
|
|
8
|
+
* "writer pod / API pod" split.
|
|
9
|
+
*
|
|
10
|
+
* ```ts
|
|
11
|
+
* import Redis from 'ioredis';
|
|
12
|
+
* SseChannelModule.forRoot({
|
|
13
|
+
* backplane: new RedisSseBackplane({ publisher: new Redis(url), subscriber: new Redis(url) }),
|
|
14
|
+
* });
|
|
15
|
+
* ```
|
|
16
|
+
*/
|
|
17
|
+
class RedisSseBackplane {
|
|
18
|
+
publisher;
|
|
19
|
+
subscriber;
|
|
20
|
+
channel;
|
|
21
|
+
constructor(options) {
|
|
22
|
+
this.publisher = options.publisher;
|
|
23
|
+
this.subscriber = options.subscriber;
|
|
24
|
+
this.channel = options.channel ?? DEFAULT_CHANNEL;
|
|
25
|
+
}
|
|
26
|
+
publish(key, message) {
|
|
27
|
+
this.publisher.publish(this.channel, JSON.stringify({ key, message }));
|
|
28
|
+
}
|
|
29
|
+
subscribe(handler) {
|
|
30
|
+
this.subscriber.subscribe(this.channel);
|
|
31
|
+
this.subscriber.on('message', (channel, raw) => {
|
|
32
|
+
if (channel !== this.channel)
|
|
33
|
+
return;
|
|
34
|
+
try {
|
|
35
|
+
const parsed = JSON.parse(raw);
|
|
36
|
+
handler(parsed.key, parsed.message);
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
// Ignore malformed payloads — never let a bad message tear down the subscription.
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
close() {
|
|
44
|
+
this.publisher.quit?.();
|
|
45
|
+
this.subscriber.quit?.();
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
exports.RedisSseBackplane = RedisSseBackplane;
|
|
49
|
+
//# sourceMappingURL=redis.backplane.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"redis.backplane.js","sourceRoot":"","sources":["../src/redis.backplane.ts"],"names":[],"mappings":";;;AAwBA,MAAM,eAAe,GAAG,0BAA0B,CAAC;AAEnD;;;;;;;;;;;GAWG;AACH,MAAa,iBAAiB;IACX,SAAS,CAAoB;IAC7B,UAAU,CAAoB;IAC9B,OAAO,CAAS;IAEjC,YAAY,OAAiC;QAC3C,IAAI,CAAC,SAAS,GAAG,OAAO,CAAC,SAAS,CAAC;QACnC,IAAI,CAAC,UAAU,GAAG,OAAO,CAAC,UAAU,CAAC;QACrC,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC,OAAO,IAAI,eAAe,CAAC;IACpD,CAAC;IAED,OAAO,CAAC,GAAW,EAAE,OAA4B;QAC/C,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,GAAG,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC;IACzE,CAAC;IAED,SAAS,CAAC,OAA4D;QACpE,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACxC,IAAI,CAAC,UAAU,CAAC,EAAE,CAAC,SAAS,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE,EAAE;YAC7C,IAAI,OAAO,KAAK,IAAI,CAAC,OAAO;gBAAE,OAAO;YACrC,IAAI,CAAC;gBACH,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAkD,CAAC;gBAChF,OAAO,CAAC,MAAM,CAAC,GAAG,EAAE,MAAM,CAAC,OAAO,CAAC,CAAC;YACtC,CAAC;YAAC,MAAM,CAAC;gBACP,kFAAkF;YACpF,CAAC;QACH,CAAC,CAAC,CAAC;IACL,CAAC;IAED,KAAK;QACH,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,EAAE,CAAC;QACxB,IAAI,CAAC,UAAU,CAAC,IAAI,EAAE,EAAE,CAAC;IAC3B,CAAC;CACF;AAhCD,8CAgCC"}
|
package/dist/sse.hub.d.ts
CHANGED
|
@@ -1,33 +1,40 @@
|
|
|
1
|
-
import { type MessageEvent } from '@nestjs/common';
|
|
1
|
+
import { type MessageEvent, type OnModuleDestroy, type OnModuleInit } from '@nestjs/common';
|
|
2
2
|
import { type Observable } from 'rxjs';
|
|
3
|
+
import type { SseBackplane } from './backplane';
|
|
3
4
|
/**
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* {@link SseHub.
|
|
7
|
-
* {@link SseHub.stream}.
|
|
5
|
+
* Fan-out for native NestJS Server-Sent Events. Keeps one {@link Subject} per stream key; the
|
|
6
|
+
* channel pushes events into it via {@link SseHub.publish} and a controller's `@Sse()` endpoint
|
|
7
|
+
* reads from it via {@link SseHub.stream}.
|
|
8
8
|
*
|
|
9
|
-
* Each key may have multiple concurrent subscribers (e.g. a user with several
|
|
10
|
-
*
|
|
11
|
-
*
|
|
9
|
+
* Each key may have multiple concurrent subscribers (e.g. a user with several open tabs). The
|
|
10
|
+
* Subject is created lazily on first subscribe and torn down once the last subscriber unsubscribes.
|
|
11
|
+
*
|
|
12
|
+
* By default delivery is **in-process**. Provide an {@link SseBackplane} (e.g. Redis pub/sub) to
|
|
13
|
+
* fan out across pods: a publish on any node then reaches the SSE connections on every node.
|
|
12
14
|
*/
|
|
13
|
-
export declare class SseHub {
|
|
15
|
+
export declare class SseHub implements OnModuleInit, OnModuleDestroy {
|
|
16
|
+
private readonly backplane?;
|
|
14
17
|
private readonly subjects;
|
|
15
18
|
private readonly refcounts;
|
|
19
|
+
constructor(backplane?: SseBackplane | undefined);
|
|
20
|
+
onModuleInit(): Promise<void>;
|
|
21
|
+
onModuleDestroy(): Promise<void>;
|
|
16
22
|
/**
|
|
17
|
-
* Return the live stream for `key` as an Observable. Subscribing creates the
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
* this directly from a controller's `@Sse()` method.
|
|
23
|
+
* Return the live stream for `key` as an Observable. Subscribing creates the underlying Subject
|
|
24
|
+
* (if absent) and bumps its refcount; unsubscribing decrements it and removes the Subject once no
|
|
25
|
+
* subscribers remain. Return this directly from a controller's `@Sse()` method.
|
|
21
26
|
*/
|
|
22
27
|
stream(key: string): Observable<MessageEvent>;
|
|
23
28
|
/**
|
|
24
|
-
* Push a
|
|
25
|
-
*
|
|
26
|
-
* sets the SSE event `type`.
|
|
29
|
+
* Push a message to every subscriber of `key`. With a backplane it's broadcast to all nodes
|
|
30
|
+
* (delivered back through the backplane subscription, including to this node); without one it's
|
|
31
|
+
* delivered to local subscribers directly. `options.event` sets the SSE event `type`.
|
|
27
32
|
*/
|
|
28
33
|
publish(key: string, data: unknown, options?: {
|
|
29
34
|
event?: string;
|
|
30
35
|
}): void;
|
|
36
|
+
/** Deliver a message to this node's local subscribers of `key` (no-op when none). */
|
|
37
|
+
private deliverLocal;
|
|
31
38
|
private subjectFor;
|
|
32
39
|
}
|
|
33
40
|
//# sourceMappingURL=sse.hub.d.ts.map
|
package/dist/sse.hub.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sse.hub.d.ts","sourceRoot":"","sources":["../src/sse.hub.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"sse.hub.d.ts","sourceRoot":"","sources":["../src/sse.hub.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,YAAY,EACjB,KAAK,eAAe,EACpB,KAAK,YAAY,EAElB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,KAAK,UAAU,EAAqB,MAAM,MAAM,CAAC;AAC1D,OAAO,KAAK,EAAE,YAAY,EAAuB,MAAM,aAAa,CAAC;AAGrE;;;;;;;;;;GAUG;AACH,qBACa,MAAO,YAAW,YAAY,EAAE,eAAe;IAOxD,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAC;IAN7B,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAA4C;IACrE,OAAO,CAAC,QAAQ,CAAC,SAAS,CAA6B;gBAKpC,SAAS,CAAC,EAAE,YAAY,YAAA;IAGrC,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC;IAO7B,eAAe,IAAI,OAAO,CAAC,IAAI,CAAC;IAItC;;;;OAIG;IACH,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,UAAU,CAAC,YAAY,CAAC;IAkB7C;;;;OAIG;IACH,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI;IASvE,qFAAqF;IACrF,OAAO,CAAC,YAAY;IAOpB,OAAO,CAAC,UAAU;CAQnB"}
|
package/dist/sse.hub.js
CHANGED
|
@@ -5,28 +5,48 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
5
5
|
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
6
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
7
|
};
|
|
8
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
|
+
};
|
|
11
|
+
var __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
12
|
+
return function (target, key) { decorator(target, key, paramIndex); }
|
|
13
|
+
};
|
|
8
14
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
15
|
exports.SseHub = void 0;
|
|
10
16
|
const common_1 = require("@nestjs/common");
|
|
11
17
|
const rxjs_1 = require("rxjs");
|
|
18
|
+
const tokens_1 = require("./tokens");
|
|
12
19
|
/**
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
* {@link SseHub.
|
|
16
|
-
*
|
|
20
|
+
* Fan-out for native NestJS Server-Sent Events. Keeps one {@link Subject} per stream key; the
|
|
21
|
+
* channel pushes events into it via {@link SseHub.publish} and a controller's `@Sse()` endpoint
|
|
22
|
+
* reads from it via {@link SseHub.stream}.
|
|
23
|
+
*
|
|
24
|
+
* Each key may have multiple concurrent subscribers (e.g. a user with several open tabs). The
|
|
25
|
+
* Subject is created lazily on first subscribe and torn down once the last subscriber unsubscribes.
|
|
17
26
|
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
* once the last subscriber unsubscribes.
|
|
27
|
+
* By default delivery is **in-process**. Provide an {@link SseBackplane} (e.g. Redis pub/sub) to
|
|
28
|
+
* fan out across pods: a publish on any node then reaches the SSE connections on every node.
|
|
21
29
|
*/
|
|
22
30
|
let SseHub = class SseHub {
|
|
31
|
+
backplane;
|
|
23
32
|
subjects = new Map();
|
|
24
33
|
refcounts = new Map();
|
|
34
|
+
constructor(backplane) {
|
|
35
|
+
this.backplane = backplane;
|
|
36
|
+
}
|
|
37
|
+
async onModuleInit() {
|
|
38
|
+
// Inbound messages from other nodes are delivered to this node's local subscribers.
|
|
39
|
+
if (this.backplane) {
|
|
40
|
+
await this.backplane.subscribe((key, message) => this.deliverLocal(key, message));
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
async onModuleDestroy() {
|
|
44
|
+
await this.backplane?.close?.();
|
|
45
|
+
}
|
|
25
46
|
/**
|
|
26
|
-
* Return the live stream for `key` as an Observable. Subscribing creates the
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
* this directly from a controller's `@Sse()` method.
|
|
47
|
+
* Return the live stream for `key` as an Observable. Subscribing creates the underlying Subject
|
|
48
|
+
* (if absent) and bumps its refcount; unsubscribing decrements it and removes the Subject once no
|
|
49
|
+
* subscribers remain. Return this directly from a controller's `@Sse()` method.
|
|
30
50
|
*/
|
|
31
51
|
stream(key) {
|
|
32
52
|
const subject = this.subjectFor(key);
|
|
@@ -44,19 +64,25 @@ let SseHub = class SseHub {
|
|
|
44
64
|
}));
|
|
45
65
|
}
|
|
46
66
|
/**
|
|
47
|
-
* Push a
|
|
48
|
-
*
|
|
49
|
-
* sets the SSE event `type`.
|
|
67
|
+
* Push a message to every subscriber of `key`. With a backplane it's broadcast to all nodes
|
|
68
|
+
* (delivered back through the backplane subscription, including to this node); without one it's
|
|
69
|
+
* delivered to local subscribers directly. `options.event` sets the SSE event `type`.
|
|
50
70
|
*/
|
|
51
71
|
publish(key, data, options) {
|
|
72
|
+
const message = { data, event: options?.event };
|
|
73
|
+
if (this.backplane) {
|
|
74
|
+
void this.backplane.publish(key, message);
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
this.deliverLocal(key, message);
|
|
78
|
+
}
|
|
79
|
+
/** Deliver a message to this node's local subscribers of `key` (no-op when none). */
|
|
80
|
+
deliverLocal(key, message) {
|
|
52
81
|
const subject = this.subjects.get(key);
|
|
53
82
|
if (!subject)
|
|
54
83
|
return;
|
|
55
|
-
const
|
|
56
|
-
|
|
57
|
-
? { data: data_, type: options.event }
|
|
58
|
-
: { data: data_ };
|
|
59
|
-
subject.next(message);
|
|
84
|
+
const data = message.data;
|
|
85
|
+
subject.next(message.event ? { data, type: message.event } : { data });
|
|
60
86
|
}
|
|
61
87
|
subjectFor(key) {
|
|
62
88
|
let subject = this.subjects.get(key);
|
|
@@ -69,6 +95,9 @@ let SseHub = class SseHub {
|
|
|
69
95
|
};
|
|
70
96
|
exports.SseHub = SseHub;
|
|
71
97
|
exports.SseHub = SseHub = __decorate([
|
|
72
|
-
(0, common_1.Injectable)()
|
|
98
|
+
(0, common_1.Injectable)(),
|
|
99
|
+
__param(0, (0, common_1.Optional)()),
|
|
100
|
+
__param(0, (0, common_1.Inject)(tokens_1.SSE_BACKPLANE)),
|
|
101
|
+
__metadata("design:paramtypes", [Object])
|
|
73
102
|
], SseHub);
|
|
74
103
|
//# sourceMappingURL=sse.hub.js.map
|
package/dist/sse.hub.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sse.hub.js","sourceRoot":"","sources":["../src/sse.hub.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"sse.hub.js","sourceRoot":"","sources":["../src/sse.hub.ts"],"names":[],"mappings":"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"}
|
package/dist/sse.module.d.ts
CHANGED
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
import { type DynamicModule } from '@nestjs/common';
|
|
2
|
+
import type { SseBackplane } from './backplane';
|
|
2
3
|
export interface SseChannelModuleOptions {
|
|
3
4
|
/** SSE event name (`type`) emitted to clients. Defaults to `'notification'`. */
|
|
4
5
|
event?: string;
|
|
6
|
+
/**
|
|
7
|
+
* Cross-pod fan-out backplane (e.g. {@link import('./redis.backplane').RedisSseBackplane}). Omit
|
|
8
|
+
* for in-process delivery (single node). Required when publishers and SSE connections live on
|
|
9
|
+
* different processes.
|
|
10
|
+
*/
|
|
11
|
+
backplane?: SseBackplane;
|
|
5
12
|
/** Register globally so the channel is discoverable app-wide. Default true. */
|
|
6
13
|
global?: boolean;
|
|
7
14
|
}
|
package/dist/sse.module.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sse.module.d.ts","sourceRoot":"","sources":["../src/sse.module.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,aAAa,EAAyB,MAAM,gBAAgB,CAAC;
|
|
1
|
+
{"version":3,"file":"sse.module.d.ts","sourceRoot":"","sources":["../src/sse.module.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,aAAa,EAAyB,MAAM,gBAAgB,CAAC;AAC3E,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAKhD,MAAM,WAAW,uBAAuB;IACtC,gFAAgF;IAChF,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;;;OAIG;IACH,SAAS,CAAC,EAAE,YAAY,CAAC;IACzB,+EAA+E;IAC/E,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED;;;;;;;;;;;GAWG;AACH,qBACa,gBAAgB;IAC3B,MAAM,CAAC,OAAO,CAAC,OAAO,GAAE,uBAA4B,GAAG,aAAa;CAmBrE"}
|
package/dist/sse.module.js
CHANGED
|
@@ -31,6 +31,7 @@ let SseChannelModule = SseChannelModule_1 = class SseChannelModule {
|
|
|
31
31
|
};
|
|
32
32
|
const providers = [
|
|
33
33
|
{ provide: tokens_1.SSE_OPTIONS, useValue: channelOptions },
|
|
34
|
+
{ provide: tokens_1.SSE_BACKPLANE, useValue: options.backplane ?? null },
|
|
34
35
|
sse_hub_1.SseHub,
|
|
35
36
|
sse_channel_1.SseChannel,
|
|
36
37
|
];
|
package/dist/sse.module.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sse.module.js","sourceRoot":"","sources":["../src/sse.module.ts"],"names":[],"mappings":";;;;;;;;;;AAAA,2CAA2E;
|
|
1
|
+
{"version":3,"file":"sse.module.js","sourceRoot":"","sources":["../src/sse.module.ts"],"names":[],"mappings":";;;;;;;;;;AAAA,2CAA2E;AAE3E,+CAAmE;AACnE,uCAAmC;AACnC,qCAAsD;AAetD;;;;;;;;;;;GAWG;AAEI,IAAM,gBAAgB,wBAAtB,MAAM,gBAAgB;IAC3B,MAAM,CAAC,OAAO,CAAC,UAAmC,EAAE;QAClD,MAAM,cAAc,GAAsB;YACxC,KAAK,EAAE,OAAO,CAAC,KAAK,IAAI,cAAc;SACvC,CAAC;QAEF,MAAM,SAAS,GAAe;YAC5B,EAAE,OAAO,EAAE,oBAAW,EAAE,QAAQ,EAAE,cAAc,EAAE;YAClD,EAAE,OAAO,EAAE,sBAAa,EAAE,QAAQ,EAAE,OAAO,CAAC,SAAS,IAAI,IAAI,EAAE;YAC/D,gBAAM;YACN,wBAAU;SACX,CAAC;QAEF,OAAO;YACL,MAAM,EAAE,kBAAgB;YACxB,MAAM,EAAE,OAAO,CAAC,MAAM,IAAI,IAAI;YAC9B,SAAS;YACT,OAAO,EAAE,CAAC,gBAAM,EAAE,wBAAU,CAAC;SAC9B,CAAC;IACJ,CAAC;CACF,CAAA;AApBY,4CAAgB;2BAAhB,gBAAgB;IAD5B,IAAA,eAAM,EAAC,EAAE,CAAC;GACE,gBAAgB,CAoB5B"}
|
package/dist/tokens.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
1
|
/** DI token for the resolved {@link SseChannelOptions} (event name, etc.). */
|
|
2
2
|
export declare const SSE_OPTIONS: unique symbol;
|
|
3
|
+
/** DI token for the optional {@link import('./backplane').SseBackplane} (cross-pod fan-out). */
|
|
4
|
+
export declare const SSE_BACKPLANE: unique symbol;
|
|
3
5
|
//# sourceMappingURL=tokens.d.ts.map
|
package/dist/tokens.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tokens.d.ts","sourceRoot":"","sources":["../src/tokens.ts"],"names":[],"mappings":"AAAA,8EAA8E;AAC9E,eAAO,MAAM,WAAW,eAAwB,CAAC"}
|
|
1
|
+
{"version":3,"file":"tokens.d.ts","sourceRoot":"","sources":["../src/tokens.ts"],"names":[],"mappings":"AAAA,8EAA8E;AAC9E,eAAO,MAAM,WAAW,eAAwB,CAAC;AAEjD,gGAAgG;AAChG,eAAO,MAAM,aAAa,eAA0B,CAAC"}
|
package/dist/tokens.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.SSE_OPTIONS = void 0;
|
|
3
|
+
exports.SSE_BACKPLANE = exports.SSE_OPTIONS = void 0;
|
|
4
4
|
/** DI token for the resolved {@link SseChannelOptions} (event name, etc.). */
|
|
5
5
|
exports.SSE_OPTIONS = Symbol('SSE_OPTIONS');
|
|
6
|
+
/** DI token for the optional {@link import('./backplane').SseBackplane} (cross-pod fan-out). */
|
|
7
|
+
exports.SSE_BACKPLANE = Symbol('SSE_BACKPLANE');
|
|
6
8
|
//# sourceMappingURL=tokens.js.map
|
package/dist/tokens.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tokens.js","sourceRoot":"","sources":["../src/tokens.ts"],"names":[],"mappings":";;;AAAA,8EAA8E;AACjE,QAAA,WAAW,GAAG,MAAM,CAAC,aAAa,CAAC,CAAC"}
|
|
1
|
+
{"version":3,"file":"tokens.js","sourceRoot":"","sources":["../src/tokens.ts"],"names":[],"mappings":";;;AAAA,8EAA8E;AACjE,QAAA,WAAW,GAAG,MAAM,CAAC,aAAa,CAAC,CAAC;AAEjD,gGAAgG;AACnF,QAAA,aAAa,GAAG,MAAM,CAAC,eAAe,CAAC,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dudousxd/nestjs-notifications-sse",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Server-Sent Events channel for nestjs-notifications — push real-time notifications over native NestJS SSE",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Davide Carvalho",
|
|
@@ -21,11 +21,11 @@
|
|
|
21
21
|
"rxjs": "^7.0.0"
|
|
22
22
|
},
|
|
23
23
|
"devDependencies": {
|
|
24
|
-
"@nestjs/common": "
|
|
25
|
-
"reflect-metadata": "
|
|
26
|
-
"rxjs": "
|
|
27
|
-
"typescript": "
|
|
28
|
-
"@dudousxd/nestjs-notifications-core": "^0.
|
|
24
|
+
"@nestjs/common": "11.1.26",
|
|
25
|
+
"reflect-metadata": "0.2.2",
|
|
26
|
+
"rxjs": "7.8.2",
|
|
27
|
+
"typescript": "5.9.3",
|
|
28
|
+
"@dudousxd/nestjs-notifications-core": "^0.5.0"
|
|
29
29
|
},
|
|
30
30
|
"scripts": {
|
|
31
31
|
"build": "tsc -p tsconfig.json",
|