@dudousxd/nestjs-notifications-sse 0.2.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/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +15 -0
- package/dist/index.js.map +1 -0
- package/dist/sse-key.d.ts +14 -0
- package/dist/sse-key.d.ts.map +1 -0
- package/dist/sse-key.js +19 -0
- package/dist/sse-key.js.map +1 -0
- package/dist/sse.channel.d.ts +47 -0
- package/dist/sse.channel.d.ts.map +1 -0
- package/dist/sse.channel.js +78 -0
- package/dist/sse.channel.js.map +1 -0
- package/dist/sse.hub.d.ts +33 -0
- package/dist/sse.hub.d.ts.map +1 -0
- package/dist/sse.hub.js +74 -0
- package/dist/sse.hub.js.map +1 -0
- package/dist/sse.module.d.ts +23 -0
- package/dist/sse.module.d.ts.map +1 -0
- package/dist/sse.module.js +49 -0
- package/dist/sse.module.js.map +1 -0
- package/dist/tokens.d.ts +3 -0
- package/dist/tokens.d.ts.map +1 -0
- package/dist/tokens.js +6 -0
- package/dist/tokens.js.map +1 -0
- package/package.json +34 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { Sse, SseChannel, type SseChannelOptions, type SseNotification, } from './sse.channel';
|
|
2
|
+
export { SseHub } from './sse.hub';
|
|
3
|
+
export { SseChannelModule, type SseChannelModuleOptions } from './sse.module';
|
|
4
|
+
export { SSE_OPTIONS } from './tokens';
|
|
5
|
+
export { sseKey } from './sse-key';
|
|
6
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +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;AACvC,OAAO,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.sseKey = exports.SSE_OPTIONS = exports.SseChannelModule = exports.SseHub = exports.SseChannel = exports.Sse = void 0;
|
|
4
|
+
var sse_channel_1 = require("./sse.channel");
|
|
5
|
+
Object.defineProperty(exports, "Sse", { enumerable: true, get: function () { return sse_channel_1.Sse; } });
|
|
6
|
+
Object.defineProperty(exports, "SseChannel", { enumerable: true, get: function () { return sse_channel_1.SseChannel; } });
|
|
7
|
+
var sse_hub_1 = require("./sse.hub");
|
|
8
|
+
Object.defineProperty(exports, "SseHub", { enumerable: true, get: function () { return sse_hub_1.SseHub; } });
|
|
9
|
+
var sse_module_1 = require("./sse.module");
|
|
10
|
+
Object.defineProperty(exports, "SseChannelModule", { enumerable: true, get: function () { return sse_module_1.SseChannelModule; } });
|
|
11
|
+
var tokens_1 = require("./tokens");
|
|
12
|
+
Object.defineProperty(exports, "SSE_OPTIONS", { enumerable: true, get: function () { return tokens_1.SSE_OPTIONS; } });
|
|
13
|
+
var sse_key_1 = require("./sse-key");
|
|
14
|
+
Object.defineProperty(exports, "sseKey", { enumerable: true, get: function () { return sse_key_1.sseKey; } });
|
|
15
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +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,mCAAuC;AAA9B,qGAAA,WAAW,OAAA;AACpB,qCAAmC;AAA1B,iGAAA,MAAM,OAAA"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Build the stream key the {@link SseChannel} publishes under, so a controller's
|
|
3
|
+
* `@Sse()` endpoint can subscribe to the exact same key.
|
|
4
|
+
*
|
|
5
|
+
* Keys are tenant-aware: when `tenant` is set, the route value is prefixed with
|
|
6
|
+
* `${tenant}:` so a user's stream is isolated per tenant.
|
|
7
|
+
*
|
|
8
|
+
* ```ts
|
|
9
|
+
* const key = sseKey(req.tenantId, String(req.user.id));
|
|
10
|
+
* return hub.stream(key);
|
|
11
|
+
* ```
|
|
12
|
+
*/
|
|
13
|
+
export declare function sseKey(tenant: string | undefined, routeValue: string): string;
|
|
14
|
+
//# sourceMappingURL=sse-key.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sse-key.d.ts","sourceRoot":"","sources":["../src/sse-key.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,wBAAgB,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,CAE7E"}
|
package/dist/sse-key.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.sseKey = sseKey;
|
|
4
|
+
/**
|
|
5
|
+
* Build the stream key the {@link SseChannel} publishes under, so a controller's
|
|
6
|
+
* `@Sse()` endpoint can subscribe to the exact same key.
|
|
7
|
+
*
|
|
8
|
+
* Keys are tenant-aware: when `tenant` is set, the route value is prefixed with
|
|
9
|
+
* `${tenant}:` so a user's stream is isolated per tenant.
|
|
10
|
+
*
|
|
11
|
+
* ```ts
|
|
12
|
+
* const key = sseKey(req.tenantId, String(req.user.id));
|
|
13
|
+
* return hub.stream(key);
|
|
14
|
+
* ```
|
|
15
|
+
*/
|
|
16
|
+
function sseKey(tenant, routeValue) {
|
|
17
|
+
return tenant ? `${tenant}:${routeValue}` : routeValue;
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=sse-key.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sse-key.js","sourceRoot":"","sources":["../src/sse-key.ts"],"names":[],"mappings":";;AAYA,wBAEC;AAdD;;;;;;;;;;;GAWG;AACH,SAAgB,MAAM,CAAC,MAA0B,EAAE,UAAkB;IACnE,OAAO,MAAM,CAAC,CAAC,CAAC,GAAG,MAAM,IAAI,UAAU,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC;AACzD,CAAC"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { type ChannelDriver, type DeliveryContext, type Notifiable, type Notification } from '@dudousxd/nestjs-notifications-core';
|
|
2
|
+
import { SseHub } from './sse.hub';
|
|
3
|
+
/** Channel handle: use as `@Sse()` on a payload method, or as a token in `via()`. */
|
|
4
|
+
export declare const Sse: import("@dudousxd/nestjs-notifications-core").ChannelHandle;
|
|
5
|
+
/** Resolved runtime options for the SSE channel. */
|
|
6
|
+
export interface SseChannelOptions {
|
|
7
|
+
/** SSE event name (`type`) emitted to clients. Defaults to `'notification'`. */
|
|
8
|
+
event?: string;
|
|
9
|
+
}
|
|
10
|
+
/** Implement this on a notification to define its SSE payload. */
|
|
11
|
+
export interface SseNotification extends Notification {
|
|
12
|
+
toSse?(notifiable: Notifiable): Record<string, unknown>;
|
|
13
|
+
toArray?(notifiable: Notifiable): Record<string, unknown>;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Pushes a notification to the notifiable's Server-Sent Events stream via
|
|
17
|
+
* {@link SseHub}, using NestJS's native `@Sse()` support. Reads the payload from
|
|
18
|
+
* `toSse()`, then `toArray()`, then a structural copy of the notification.
|
|
19
|
+
*
|
|
20
|
+
* The endpoint is mounted by the consumer, not this package. Subscribe to the
|
|
21
|
+
* same key the channel publishes to with {@link sseKey}:
|
|
22
|
+
*
|
|
23
|
+
* ```ts
|
|
24
|
+
* import { Sse, type MessageEvent } from '@nestjs/common';
|
|
25
|
+
* import { SseHub, sseKey } from '@dudousxd/nestjs-notifications-sse';
|
|
26
|
+
*
|
|
27
|
+
* @Controller('notifications')
|
|
28
|
+
* class NotificationsController {
|
|
29
|
+
* constructor(private readonly hub: SseHub) {}
|
|
30
|
+
*
|
|
31
|
+
* @Sse('stream')
|
|
32
|
+
* stream(@Req() req): Observable<MessageEvent> {
|
|
33
|
+
* // build the SAME key the notifiable routes to (tenant-aware)
|
|
34
|
+
* return this.hub.stream(sseKey(req.tenantId, String(req.user.id)));
|
|
35
|
+
* }
|
|
36
|
+
* }
|
|
37
|
+
* ```
|
|
38
|
+
*/
|
|
39
|
+
export declare class SseChannel implements ChannelDriver {
|
|
40
|
+
private readonly hub;
|
|
41
|
+
private readonly options;
|
|
42
|
+
readonly channel = "sse";
|
|
43
|
+
constructor(hub: SseHub, options: SseChannelOptions);
|
|
44
|
+
send(notifiable: Notifiable, notification: Notification, context?: DeliveryContext): Promise<void>;
|
|
45
|
+
private payloadFor;
|
|
46
|
+
}
|
|
47
|
+
//# sourceMappingURL=sse.channel.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sse.channel.d.ts","sourceRoot":"","sources":["../src/sse.channel.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,aAAa,EAClB,KAAK,eAAe,EACpB,KAAK,UAAU,EACf,KAAK,YAAY,EAIlB,MAAM,qCAAqC,CAAC;AAG7C,OAAO,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AAGnC,qFAAqF;AACrF,eAAO,MAAM,GAAG,6DAAuB,CAAC;AAExC,oDAAoD;AACpD,MAAM,WAAW,iBAAiB;IAChC,gFAAgF;IAChF,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,kEAAkE;AAClE,MAAM,WAAW,eAAgB,SAAQ,YAAY;IACnD,KAAK,CAAC,CAAC,UAAU,EAAE,UAAU,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACxD,OAAO,CAAC,CAAC,UAAU,EAAE,UAAU,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC3D;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,qBACa,UAAW,YAAW,aAAa;IAI5C,OAAO,CAAC,QAAQ,CAAC,GAAG;IAEpB,OAAO,CAAC,QAAQ,CAAC,OAAO;IAL1B,QAAQ,CAAC,OAAO,SAAS;gBAGN,GAAG,EAAE,MAAM,EAEX,OAAO,EAAE,iBAAiB;IAGvC,IAAI,CACR,UAAU,EAAE,UAAU,EACtB,YAAY,EAAE,YAAY,EAC1B,OAAO,CAAC,EAAE,eAAe,GACxB,OAAO,CAAC,IAAI,CAAC;IAShB,OAAO,CAAC,UAAU;CAUnB"}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
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
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
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
|
+
};
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.SseChannel = exports.Sse = void 0;
|
|
16
|
+
const nestjs_notifications_core_1 = require("@dudousxd/nestjs-notifications-core");
|
|
17
|
+
const common_1 = require("@nestjs/common");
|
|
18
|
+
const sse_key_1 = require("./sse-key");
|
|
19
|
+
const sse_hub_1 = require("./sse.hub");
|
|
20
|
+
const tokens_1 = require("./tokens");
|
|
21
|
+
/** Channel handle: use as `@Sse()` on a payload method, or as a token in `via()`. */
|
|
22
|
+
exports.Sse = (0, nestjs_notifications_core_1.createChannel)('sse');
|
|
23
|
+
/**
|
|
24
|
+
* Pushes a notification to the notifiable's Server-Sent Events stream via
|
|
25
|
+
* {@link SseHub}, using NestJS's native `@Sse()` support. Reads the payload from
|
|
26
|
+
* `toSse()`, then `toArray()`, then a structural copy of the notification.
|
|
27
|
+
*
|
|
28
|
+
* The endpoint is mounted by the consumer, not this package. Subscribe to the
|
|
29
|
+
* same key the channel publishes to with {@link sseKey}:
|
|
30
|
+
*
|
|
31
|
+
* ```ts
|
|
32
|
+
* import { Sse, type MessageEvent } from '@nestjs/common';
|
|
33
|
+
* import { SseHub, sseKey } from '@dudousxd/nestjs-notifications-sse';
|
|
34
|
+
*
|
|
35
|
+
* @Controller('notifications')
|
|
36
|
+
* class NotificationsController {
|
|
37
|
+
* constructor(private readonly hub: SseHub) {}
|
|
38
|
+
*
|
|
39
|
+
* @Sse('stream')
|
|
40
|
+
* stream(@Req() req): Observable<MessageEvent> {
|
|
41
|
+
* // build the SAME key the notifiable routes to (tenant-aware)
|
|
42
|
+
* return this.hub.stream(sseKey(req.tenantId, String(req.user.id)));
|
|
43
|
+
* }
|
|
44
|
+
* }
|
|
45
|
+
* ```
|
|
46
|
+
*/
|
|
47
|
+
let SseChannel = class SseChannel {
|
|
48
|
+
hub;
|
|
49
|
+
options;
|
|
50
|
+
channel = 'sse';
|
|
51
|
+
constructor(hub, options) {
|
|
52
|
+
this.hub = hub;
|
|
53
|
+
this.options = options;
|
|
54
|
+
}
|
|
55
|
+
async send(notifiable, notification, context) {
|
|
56
|
+
const routeValue = String((0, nestjs_notifications_core_1.routeFor)(notifiable, 'sse', notification));
|
|
57
|
+
const key = (0, sse_key_1.sseKey)(context?.tenant, routeValue);
|
|
58
|
+
const event = this.options.event ?? 'notification';
|
|
59
|
+
const payload = this.payloadFor(notifiable, notification);
|
|
60
|
+
this.hub.publish(key, payload, { event });
|
|
61
|
+
}
|
|
62
|
+
payloadFor(notifiable, notification) {
|
|
63
|
+
const handler = (0, nestjs_notifications_core_1.getHandler)(notification, 'sse', 'toSse');
|
|
64
|
+
if (handler)
|
|
65
|
+
return handler(notifiable);
|
|
66
|
+
if (typeof notification.toArray === 'function')
|
|
67
|
+
return notification.toArray(notifiable);
|
|
68
|
+
const { ...rest } = notification;
|
|
69
|
+
return rest;
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
exports.SseChannel = SseChannel;
|
|
73
|
+
exports.SseChannel = SseChannel = __decorate([
|
|
74
|
+
(0, common_1.Injectable)(),
|
|
75
|
+
__param(1, (0, common_1.Inject)(tokens_1.SSE_OPTIONS)),
|
|
76
|
+
__metadata("design:paramtypes", [sse_hub_1.SseHub, Object])
|
|
77
|
+
], SseChannel);
|
|
78
|
+
//# sourceMappingURL=sse.channel.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sse.channel.js","sourceRoot":"","sources":["../src/sse.channel.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;AAAA,mFAQ6C;AAC7C,2CAAoD;AACpD,uCAAmC;AACnC,uCAAmC;AACnC,qCAAuC;AAEvC,qFAAqF;AACxE,QAAA,GAAG,GAAG,IAAA,yCAAa,EAAC,KAAK,CAAC,CAAC;AAcxC;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEI,IAAM,UAAU,GAAhB,MAAM,UAAU;IAIF;IAEA;IALV,OAAO,GAAG,KAAK,CAAC;IAEzB,YACmB,GAAW,EAEX,OAA0B;QAF1B,QAAG,GAAH,GAAG,CAAQ;QAEX,YAAO,GAAP,OAAO,CAAmB;IAC1C,CAAC;IAEJ,KAAK,CAAC,IAAI,CACR,UAAsB,EACtB,YAA0B,EAC1B,OAAyB;QAEzB,MAAM,UAAU,GAAG,MAAM,CAAC,IAAA,oCAAQ,EAAC,UAAU,EAAE,KAAK,EAAE,YAAY,CAAC,CAAC,CAAC;QACrE,MAAM,GAAG,GAAG,IAAA,gBAAM,EAAC,OAAO,EAAE,MAAM,EAAE,UAAU,CAAC,CAAC;QAChD,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,KAAK,IAAI,cAAc,CAAC;QACnD,MAAM,OAAO,GAAG,IAAI,CAAC,UAAU,CAAC,UAAU,EAAE,YAA+B,CAAC,CAAC;QAE7E,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,GAAG,EAAE,OAAO,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC;IAC5C,CAAC;IAEO,UAAU,CAChB,UAAsB,EACtB,YAA6B;QAE7B,MAAM,OAAO,GAAG,IAAA,sCAAU,EAAC,YAAY,EAAE,KAAK,EAAE,OAAO,CAAC,CAAC;QACzD,IAAI,OAAO;YAAE,OAAO,OAAO,CAAC,UAAU,CAA4B,CAAC;QACnE,IAAI,OAAO,YAAY,CAAC,OAAO,KAAK,UAAU;YAAE,OAAO,YAAY,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;QACxF,MAAM,EAAE,GAAG,IAAI,EAAE,GAAG,YAAkD,CAAC;QACvE,OAAO,IAAI,CAAC;IACd,CAAC;CACF,CAAA;AAhCY,gCAAU;qBAAV,UAAU;IADtB,IAAA,mBAAU,GAAE;IAMR,WAAA,IAAA,eAAM,EAAC,oBAAW,CAAC,CAAA;qCADE,gBAAM;GAJnB,UAAU,CAgCtB"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { type MessageEvent } from '@nestjs/common';
|
|
2
|
+
import { type Observable } from 'rxjs';
|
|
3
|
+
/**
|
|
4
|
+
* In-memory fan-out for native NestJS Server-Sent Events. Keeps one
|
|
5
|
+
* {@link Subject} per stream key; the channel pushes events into it via
|
|
6
|
+
* {@link SseHub.publish} and a controller's `@Sse()` endpoint reads from it via
|
|
7
|
+
* {@link SseHub.stream}.
|
|
8
|
+
*
|
|
9
|
+
* Each key may have multiple concurrent subscribers (e.g. a user with several
|
|
10
|
+
* open tabs). The Subject is created lazily on first subscribe and torn down
|
|
11
|
+
* once the last subscriber unsubscribes.
|
|
12
|
+
*/
|
|
13
|
+
export declare class SseHub {
|
|
14
|
+
private readonly subjects;
|
|
15
|
+
private readonly refcounts;
|
|
16
|
+
/**
|
|
17
|
+
* Return the live stream for `key` as an Observable. Subscribing creates the
|
|
18
|
+
* underlying Subject (if absent) and bumps its refcount; unsubscribing
|
|
19
|
+
* decrements it and removes the Subject once no subscribers remain. Return
|
|
20
|
+
* this directly from a controller's `@Sse()` method.
|
|
21
|
+
*/
|
|
22
|
+
stream(key: string): Observable<MessageEvent>;
|
|
23
|
+
/**
|
|
24
|
+
* Push a `MessageEvent` to every subscriber of `key`. No-ops when nobody is
|
|
25
|
+
* listening (no Subject exists), since SSE is fire-and-forget. `options.event`
|
|
26
|
+
* sets the SSE event `type`.
|
|
27
|
+
*/
|
|
28
|
+
publish(key: string, data: unknown, options?: {
|
|
29
|
+
event?: string;
|
|
30
|
+
}): void;
|
|
31
|
+
private subjectFor;
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=sse.hub.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sse.hub.d.ts","sourceRoot":"","sources":["../src/sse.hub.ts"],"names":[],"mappings":"AAAA,OAAO,EAAc,KAAK,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC/D,OAAO,EAAE,KAAK,UAAU,EAAqB,MAAM,MAAM,CAAC;AAE1D;;;;;;;;;GASG;AACH,qBACa,MAAM;IACjB,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAA4C;IACrE,OAAO,CAAC,QAAQ,CAAC,SAAS,CAA6B;IAEvD;;;;;OAKG;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;IAWvE,OAAO,CAAC,UAAU;CAQnB"}
|
package/dist/sse.hub.js
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
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
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.SseHub = void 0;
|
|
10
|
+
const common_1 = require("@nestjs/common");
|
|
11
|
+
const rxjs_1 = require("rxjs");
|
|
12
|
+
/**
|
|
13
|
+
* In-memory fan-out for native NestJS Server-Sent Events. Keeps one
|
|
14
|
+
* {@link Subject} per stream key; the channel pushes events into it via
|
|
15
|
+
* {@link SseHub.publish} and a controller's `@Sse()` endpoint reads from it via
|
|
16
|
+
* {@link SseHub.stream}.
|
|
17
|
+
*
|
|
18
|
+
* Each key may have multiple concurrent subscribers (e.g. a user with several
|
|
19
|
+
* open tabs). The Subject is created lazily on first subscribe and torn down
|
|
20
|
+
* once the last subscriber unsubscribes.
|
|
21
|
+
*/
|
|
22
|
+
let SseHub = class SseHub {
|
|
23
|
+
subjects = new Map();
|
|
24
|
+
refcounts = new Map();
|
|
25
|
+
/**
|
|
26
|
+
* Return the live stream for `key` as an Observable. Subscribing creates the
|
|
27
|
+
* underlying Subject (if absent) and bumps its refcount; unsubscribing
|
|
28
|
+
* decrements it and removes the Subject once no subscribers remain. Return
|
|
29
|
+
* this directly from a controller's `@Sse()` method.
|
|
30
|
+
*/
|
|
31
|
+
stream(key) {
|
|
32
|
+
const subject = this.subjectFor(key);
|
|
33
|
+
this.refcounts.set(key, (this.refcounts.get(key) ?? 0) + 1);
|
|
34
|
+
return subject.asObservable().pipe((0, rxjs_1.finalize)(() => {
|
|
35
|
+
const next = (this.refcounts.get(key) ?? 1) - 1;
|
|
36
|
+
if (next <= 0) {
|
|
37
|
+
this.refcounts.delete(key);
|
|
38
|
+
this.subjects.delete(key);
|
|
39
|
+
subject.complete();
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
this.refcounts.set(key, next);
|
|
43
|
+
}
|
|
44
|
+
}));
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Push a `MessageEvent` to every subscriber of `key`. No-ops when nobody is
|
|
48
|
+
* listening (no Subject exists), since SSE is fire-and-forget. `options.event`
|
|
49
|
+
* sets the SSE event `type`.
|
|
50
|
+
*/
|
|
51
|
+
publish(key, data, options) {
|
|
52
|
+
const subject = this.subjects.get(key);
|
|
53
|
+
if (!subject)
|
|
54
|
+
return;
|
|
55
|
+
const data_ = data;
|
|
56
|
+
const message = options?.event
|
|
57
|
+
? { data: data_, type: options.event }
|
|
58
|
+
: { data: data_ };
|
|
59
|
+
subject.next(message);
|
|
60
|
+
}
|
|
61
|
+
subjectFor(key) {
|
|
62
|
+
let subject = this.subjects.get(key);
|
|
63
|
+
if (!subject) {
|
|
64
|
+
subject = new rxjs_1.Subject();
|
|
65
|
+
this.subjects.set(key, subject);
|
|
66
|
+
}
|
|
67
|
+
return subject;
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
exports.SseHub = SseHub;
|
|
71
|
+
exports.SseHub = SseHub = __decorate([
|
|
72
|
+
(0, common_1.Injectable)()
|
|
73
|
+
], SseHub);
|
|
74
|
+
//# sourceMappingURL=sse.hub.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sse.hub.js","sourceRoot":"","sources":["../src/sse.hub.ts"],"names":[],"mappings":";;;;;;;;;AAAA,2CAA+D;AAC/D,+BAA0D;AAE1D;;;;;;;;;GASG;AAEI,IAAM,MAAM,GAAZ,MAAM,MAAM;IACA,QAAQ,GAAG,IAAI,GAAG,EAAiC,CAAC;IACpD,SAAS,GAAG,IAAI,GAAG,EAAkB,CAAC;IAEvD;;;;;OAKG;IACH,MAAM,CAAC,GAAW;QAChB,MAAM,OAAO,GAAG,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC;QACrC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;QAE5D,OAAO,OAAO,CAAC,YAAY,EAAE,CAAC,IAAI,CAChC,IAAA,eAAQ,EAAC,GAAG,EAAE;YACZ,MAAM,IAAI,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC;YAChD,IAAI,IAAI,IAAI,CAAC,EAAE,CAAC;gBACd,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;gBAC3B,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;gBAC1B,OAAO,CAAC,QAAQ,EAAE,CAAC;YACrB,CAAC;iBAAM,CAAC;gBACN,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;YAChC,CAAC;QACH,CAAC,CAAC,CACH,CAAC;IACJ,CAAC;IAED;;;;OAIG;IACH,OAAO,CAAC,GAAW,EAAE,IAAa,EAAE,OAA4B;QAC9D,MAAM,OAAO,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACvC,IAAI,CAAC,OAAO;YAAE,OAAO;QAErB,MAAM,KAAK,GAAG,IAA4B,CAAC;QAC3C,MAAM,OAAO,GAAiB,OAAO,EAAE,KAAK;YAC1C,CAAC,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,OAAO,CAAC,KAAK,EAAE;YACtC,CAAC,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC;QACpB,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IACxB,CAAC;IAEO,UAAU,CAAC,GAAW;QAC5B,IAAI,OAAO,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACrC,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,OAAO,GAAG,IAAI,cAAO,EAAgB,CAAC;YACtC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;QAClC,CAAC;QACD,OAAO,OAAO,CAAC;IACjB,CAAC;CACF,CAAA;AApDY,wBAAM;iBAAN,MAAM;IADlB,IAAA,mBAAU,GAAE;GACA,MAAM,CAoDlB"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { type DynamicModule } from '@nestjs/common';
|
|
2
|
+
export interface SseChannelModuleOptions {
|
|
3
|
+
/** SSE event name (`type`) emitted to clients. Defaults to `'notification'`. */
|
|
4
|
+
event?: string;
|
|
5
|
+
/** Register globally so the channel is discoverable app-wide. Default true. */
|
|
6
|
+
global?: boolean;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Registers the SSE channel and its {@link SseHub}.
|
|
10
|
+
*
|
|
11
|
+
* The streaming endpoint itself is mounted by the consumer in their own
|
|
12
|
+
* controller via NestJS's native `@Sse()` decorator — this module only provides
|
|
13
|
+
* the hub (to read from) and the channel (to push into). See {@link SseChannel}
|
|
14
|
+
* for the controller example.
|
|
15
|
+
*
|
|
16
|
+
* ```ts
|
|
17
|
+
* SseChannelModule.forRoot({ event: 'notification' });
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
export declare class SseChannelModule {
|
|
21
|
+
static forRoot(options?: SseChannelModuleOptions): DynamicModule;
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=sse.module.d.ts.map
|
|
@@ -0,0 +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;AAK3E,MAAM,WAAW,uBAAuB;IACtC,gFAAgF;IAChF,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,+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;CAkBrE"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
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
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var SseChannelModule_1;
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
exports.SseChannelModule = void 0;
|
|
11
|
+
const common_1 = require("@nestjs/common");
|
|
12
|
+
const sse_channel_1 = require("./sse.channel");
|
|
13
|
+
const sse_hub_1 = require("./sse.hub");
|
|
14
|
+
const tokens_1 = require("./tokens");
|
|
15
|
+
/**
|
|
16
|
+
* Registers the SSE channel and its {@link SseHub}.
|
|
17
|
+
*
|
|
18
|
+
* The streaming endpoint itself is mounted by the consumer in their own
|
|
19
|
+
* controller via NestJS's native `@Sse()` decorator — this module only provides
|
|
20
|
+
* the hub (to read from) and the channel (to push into). See {@link SseChannel}
|
|
21
|
+
* for the controller example.
|
|
22
|
+
*
|
|
23
|
+
* ```ts
|
|
24
|
+
* SseChannelModule.forRoot({ event: 'notification' });
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
let SseChannelModule = SseChannelModule_1 = class SseChannelModule {
|
|
28
|
+
static forRoot(options = {}) {
|
|
29
|
+
const channelOptions = {
|
|
30
|
+
event: options.event ?? 'notification',
|
|
31
|
+
};
|
|
32
|
+
const providers = [
|
|
33
|
+
{ provide: tokens_1.SSE_OPTIONS, useValue: channelOptions },
|
|
34
|
+
sse_hub_1.SseHub,
|
|
35
|
+
sse_channel_1.SseChannel,
|
|
36
|
+
];
|
|
37
|
+
return {
|
|
38
|
+
module: SseChannelModule_1,
|
|
39
|
+
global: options.global ?? true,
|
|
40
|
+
providers,
|
|
41
|
+
exports: [sse_hub_1.SseHub, sse_channel_1.SseChannel],
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
exports.SseChannelModule = SseChannelModule;
|
|
46
|
+
exports.SseChannelModule = SseChannelModule = SseChannelModule_1 = __decorate([
|
|
47
|
+
(0, common_1.Module)({})
|
|
48
|
+
], SseChannelModule);
|
|
49
|
+
//# sourceMappingURL=sse.module.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sse.module.js","sourceRoot":"","sources":["../src/sse.module.ts"],"names":[],"mappings":";;;;;;;;;;AAAA,2CAA2E;AAC3E,+CAAmE;AACnE,uCAAmC;AACnC,qCAAuC;AASvC;;;;;;;;;;;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,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;AAnBY,4CAAgB;2BAAhB,gBAAgB;IAD5B,IAAA,eAAM,EAAC,EAAE,CAAC;GACE,gBAAgB,CAmB5B"}
|
package/dist/tokens.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tokens.d.ts","sourceRoot":"","sources":["../src/tokens.ts"],"names":[],"mappings":"AAAA,8EAA8E;AAC9E,eAAO,MAAM,WAAW,eAAwB,CAAC"}
|
package/dist/tokens.js
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SSE_OPTIONS = void 0;
|
|
4
|
+
/** DI token for the resolved {@link SseChannelOptions} (event name, etc.). */
|
|
5
|
+
exports.SSE_OPTIONS = Symbol('SSE_OPTIONS');
|
|
6
|
+
//# sourceMappingURL=tokens.js.map
|
|
@@ -0,0 +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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@dudousxd/nestjs-notifications-sse",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Server-Sent Events channel for nestjs-notifications — push real-time notifications over native NestJS SSE",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "Davide Carvalho",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "https://github.com/DavideCarvalho/nestjs-notifications.git",
|
|
10
|
+
"directory": "packages/sse"
|
|
11
|
+
},
|
|
12
|
+
"type": "commonjs",
|
|
13
|
+
"main": "dist/index.js",
|
|
14
|
+
"types": "dist/index.d.ts",
|
|
15
|
+
"files": [
|
|
16
|
+
"dist"
|
|
17
|
+
],
|
|
18
|
+
"peerDependencies": {
|
|
19
|
+
"@dudousxd/nestjs-notifications-core": ">=0.1.0 <1.0.0",
|
|
20
|
+
"@nestjs/common": "^10.0.0 || ^11.0.0",
|
|
21
|
+
"rxjs": "^7.0.0"
|
|
22
|
+
},
|
|
23
|
+
"devDependencies": {
|
|
24
|
+
"@nestjs/common": "^11.0.0",
|
|
25
|
+
"reflect-metadata": "^0.2.2",
|
|
26
|
+
"rxjs": "^7.0.0",
|
|
27
|
+
"typescript": "^5.7.2",
|
|
28
|
+
"@dudousxd/nestjs-notifications-core": "^0.4.0"
|
|
29
|
+
},
|
|
30
|
+
"scripts": {
|
|
31
|
+
"build": "tsc -p tsconfig.json",
|
|
32
|
+
"typecheck": "tsc -p tsconfig.json --noEmit"
|
|
33
|
+
}
|
|
34
|
+
}
|