@futdevpro/nts-dynamo 1.15.362 → 1.15.364

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.
Files changed (49) hide show
  1. package/build/_modules/notifications/_models/notification-mongo-collection.interface.d.ts +21 -0
  2. package/build/_modules/notifications/_models/notification-mongo-collection.interface.d.ts.map +1 -0
  3. package/build/_modules/notifications/_models/notification-mongo-collection.interface.js +3 -0
  4. package/build/_modules/notifications/_models/notification-mongo-collection.interface.js.map +1 -0
  5. package/build/_modules/notifications/_models/notification-publisher.interface.d.ts +32 -0
  6. package/build/_modules/notifications/_models/notification-publisher.interface.d.ts.map +1 -0
  7. package/build/_modules/notifications/_models/notification-publisher.interface.js +3 -0
  8. package/build/_modules/notifications/_models/notification-publisher.interface.js.map +1 -0
  9. package/build/_modules/notifications/_models/notification-store.interface.d.ts +35 -0
  10. package/build/_modules/notifications/_models/notification-store.interface.d.ts.map +1 -0
  11. package/build/_modules/notifications/_models/notification-store.interface.js +3 -0
  12. package/build/_modules/notifications/_models/notification-store.interface.js.map +1 -0
  13. package/build/_modules/notifications/_models/notification.interface.d.ts +59 -0
  14. package/build/_modules/notifications/_models/notification.interface.d.ts.map +1 -0
  15. package/build/_modules/notifications/_models/notification.interface.js +3 -0
  16. package/build/_modules/notifications/_models/notification.interface.js.map +1 -0
  17. package/build/_modules/notifications/get-notifications-routing-module.util.d.ts +18 -0
  18. package/build/_modules/notifications/get-notifications-routing-module.util.d.ts.map +1 -0
  19. package/build/_modules/notifications/get-notifications-routing-module.util.js +26 -0
  20. package/build/_modules/notifications/get-notifications-routing-module.util.js.map +1 -0
  21. package/build/_modules/notifications/index.d.ts +15 -0
  22. package/build/_modules/notifications/index.d.ts.map +1 -0
  23. package/build/_modules/notifications/index.js +18 -0
  24. package/build/_modules/notifications/index.js.map +1 -0
  25. package/build/_modules/notifications/notification-in-memory.store.d.ts +30 -0
  26. package/build/_modules/notifications/notification-in-memory.store.d.ts.map +1 -0
  27. package/build/_modules/notifications/notification-in-memory.store.js +68 -0
  28. package/build/_modules/notifications/notification-in-memory.store.js.map +1 -0
  29. package/build/_modules/notifications/notification-mongo.store.d.ts +51 -0
  30. package/build/_modules/notifications/notification-mongo.store.d.ts.map +1 -0
  31. package/build/_modules/notifications/notification-mongo.store.js +136 -0
  32. package/build/_modules/notifications/notification-mongo.store.js.map +1 -0
  33. package/build/_modules/notifications/notification.service.d.ts +67 -0
  34. package/build/_modules/notifications/notification.service.d.ts.map +1 -0
  35. package/build/_modules/notifications/notification.service.js +271 -0
  36. package/build/_modules/notifications/notification.service.js.map +1 -0
  37. package/build/_modules/notifications/notifications.controller.d.ts +31 -0
  38. package/build/_modules/notifications/notifications.controller.d.ts.map +1 -0
  39. package/build/_modules/notifications/notifications.controller.js +118 -0
  40. package/build/_modules/notifications/notifications.controller.js.map +1 -0
  41. package/build/_modules/server/errors/errors-sink.util.d.ts +2 -0
  42. package/build/_modules/server/errors/errors-sink.util.d.ts.map +1 -1
  43. package/build/_modules/server/errors/errors-sink.util.js +8 -0
  44. package/build/_modules/server/errors/errors-sink.util.js.map +1 -1
  45. package/build/_modules/server/server-status/server-status.control-service.d.ts +8 -0
  46. package/build/_modules/server/server-status/server-status.control-service.d.ts.map +1 -1
  47. package/build/_modules/server/server-status/server-status.control-service.js +12 -4
  48. package/build/_modules/server/server-status/server-status.control-service.js.map +1 -1
  49. package/package.json +10 -1
@@ -0,0 +1,21 @@
1
+ /**
2
+ * BFR-OLDLIGHTSITE-001 — the minimal MongoDB collection surface `DyNTS_Notification_MongoStore` needs. A native
3
+ * driver `Collection` and `mongoose.connection.collection(name)` satisfy it structurally, so the store has no hard
4
+ * driver dependency.
5
+ */
6
+ export interface DyNTS_NotificationMongoCollection_Interface {
7
+ insertOne(document: object): Promise<unknown>;
8
+ findOne(filter: object): Promise<unknown>;
9
+ find(filter: object, options?: object): {
10
+ toArray(): Promise<unknown[]>;
11
+ };
12
+ countDocuments(filter: object): Promise<number>;
13
+ updateMany(filter: object, update: object): Promise<{
14
+ modifiedCount?: number;
15
+ }>;
16
+ /** Ascending / descending keys only — the shape a native `Collection` accepts without a cast. */
17
+ createIndex(spec: {
18
+ [key: string]: 1 | -1;
19
+ }, options: object): Promise<unknown>;
20
+ }
21
+ //# sourceMappingURL=notification-mongo-collection.interface.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"notification-mongo-collection.interface.d.ts","sourceRoot":"","sources":["../../../../src/_modules/notifications/_models/notification-mongo-collection.interface.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,MAAM,WAAW,2CAA2C;IAC1D,SAAS,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC9C,OAAO,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC1C,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG;QAAE,OAAO,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC,CAAA;KAAE,CAAC;IAC1E,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAChD,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,aAAa,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAChF,iGAAiG;IACjG,WAAW,CAAC,IAAI,EAAE;QAAE,CAAC,GAAG,EAAE,MAAM,GAAG,CAAC,GAAG,CAAC,CAAC,CAAA;KAAE,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CACjF"}
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=notification-mongo-collection.interface.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"notification-mongo-collection.interface.js","sourceRoot":"","sources":["../../../../src/_modules/notifications/_models/notification-mongo-collection.interface.ts"],"names":[],"mappings":""}
@@ -0,0 +1,32 @@
1
+ import { DyNTS_Notification_Interface } from './notification.interface';
2
+ /**
3
+ * BFR-OLDLIGHTSITE-001 — a change pushed to the account's other devices / tabs.
4
+ *
5
+ * @description Every event carries the account's `unreadCount` after the change, so a client can set its badge
6
+ * without a round-trip. A read event carries the ids and the `seen` time; a client applies it MONOTONICALLY (it
7
+ * only ever turns unread → read), so an event arriving late or out of order can never make a read item unread.
8
+ */
9
+ export type DyNTS_NotificationEvent_Type = {
10
+ kind: 'created';
11
+ accountId: string;
12
+ notification: DyNTS_Notification_Interface;
13
+ unreadCount: number;
14
+ } | {
15
+ kind: 'read';
16
+ accountId: string;
17
+ ids: string[];
18
+ seen: Date;
19
+ unreadCount: number;
20
+ } | {
21
+ kind: 'read-all';
22
+ accountId: string;
23
+ upTo: Date;
24
+ seen: Date;
25
+ unreadCount: number;
26
+ };
27
+ /**
28
+ * BFR-OLDLIGHTSITE-001 — delivers the events (e.g. a socket emit to the account's room). Best effort: the stored
29
+ * list is the truth, so a failing publisher is logged and never fails the operation that raised the event.
30
+ */
31
+ export type DyNTS_NotificationPublisher_Type = (event: DyNTS_NotificationEvent_Type) => void | Promise<void>;
32
+ //# sourceMappingURL=notification-publisher.interface.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"notification-publisher.interface.d.ts","sourceRoot":"","sources":["../../../../src/_modules/notifications/_models/notification-publisher.interface.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,4BAA4B,EAAE,MAAM,0BAA0B,CAAC;AAExE;;;;;;GAMG;AACH,MAAM,MAAM,4BAA4B,GACpC;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,4BAA4B,CAAC;IAAC,WAAW,EAAE,MAAM,CAAA;CAAE,GACvG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,EAAE,CAAC;IAAC,IAAI,EAAE,IAAI,CAAC;IAAC,WAAW,EAAE,MAAM,CAAA;CAAE,GACnF;IAAE,IAAI,EAAE,UAAU,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,IAAI,CAAC;IAAC,IAAI,EAAE,IAAI,CAAC;IAAC,WAAW,EAAE,MAAM,CAAA;CAAE,CAAC;AAEzF;;;GAGG;AACH,MAAM,MAAM,gCAAgC,GAAG,CAAC,KAAK,EAAE,4BAA4B,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC"}
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=notification-publisher.interface.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"notification-publisher.interface.js","sourceRoot":"","sources":["../../../../src/_modules/notifications/_models/notification-publisher.interface.ts"],"names":[],"mappings":""}
@@ -0,0 +1,35 @@
1
+ import { DyNTS_Notification_Interface } from './notification.interface';
2
+ /** A position in the newest-first order: the page continues with the notifications strictly OLDER than it. */
3
+ export interface DyNTS_NotificationCursor_Interface {
4
+ created: Date;
5
+ id: string;
6
+ }
7
+ /**
8
+ * BFR-OLDLIGHTSITE-001 — the persistence contract behind `DyNTS_Notification_Service` (in-memory and MongoDB
9
+ * implementations). The store does NO validation — the service does it before every call.
10
+ */
11
+ export interface DyNTS_NotificationStore_Interface {
12
+ /** Inserts, or — same `accountId` + `dedupeKey` — returns the EXISTING one unchanged. */
13
+ insert(notification: DyNTS_Notification_Interface): Promise<DyNTS_Notification_Interface>;
14
+ /** Newest first (`__created` desc, then `_id` desc), strictly after `before`. */
15
+ list(query: {
16
+ accountId: string;
17
+ limit: number;
18
+ before?: DyNTS_NotificationCursor_Interface;
19
+ unreadOnly?: boolean;
20
+ }): Promise<DyNTS_Notification_Interface[]>;
21
+ countUnread(accountId: string): Promise<number>;
22
+ /** The ids (of the given ones) that exist for the account. */
23
+ existingIds(accountId: string, ids: string[]): Promise<string[]>;
24
+ /**
25
+ * Sets `seen = at` on the account's UNREAD notifications among `ids` (an already read one is left untouched), or —
26
+ * `ids` omitted — on every unread one created at or before `upTo`. Returns how many changed.
27
+ */
28
+ markSeen(query: {
29
+ accountId: string;
30
+ at: Date;
31
+ ids?: string[];
32
+ upTo?: Date;
33
+ }): Promise<number>;
34
+ }
35
+ //# sourceMappingURL=notification-store.interface.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"notification-store.interface.d.ts","sourceRoot":"","sources":["../../../../src/_modules/notifications/_models/notification-store.interface.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,4BAA4B,EAAE,MAAM,0BAA0B,CAAC;AAExE,8GAA8G;AAC9G,MAAM,WAAW,kCAAkC;IACjD,OAAO,EAAE,IAAI,CAAC;IACd,EAAE,EAAE,MAAM,CAAC;CACZ;AAED;;;GAGG;AACH,MAAM,WAAW,iCAAiC;IAChD,yFAAyF;IACzF,MAAM,CAAC,YAAY,EAAE,4BAA4B,GAAG,OAAO,CAAC,4BAA4B,CAAC,CAAC;IAC1F,iFAAiF;IACjF,IAAI,CAAC,KAAK,EAAE;QACV,SAAS,EAAE,MAAM,CAAC;QAClB,KAAK,EAAE,MAAM,CAAC;QACd,MAAM,CAAC,EAAE,kCAAkC,CAAC;QAC5C,UAAU,CAAC,EAAE,OAAO,CAAC;KACtB,GAAG,OAAO,CAAC,4BAA4B,EAAE,CAAC,CAAC;IAC5C,WAAW,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAChD,8DAA8D;IAC9D,WAAW,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IACjE;;;OAGG;IACH,QAAQ,CAAC,KAAK,EAAE;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,IAAI,CAAC;QAAC,GAAG,CAAC,EAAE,MAAM,EAAE,CAAC;QAAC,IAAI,CAAC,EAAE,IAAI,CAAA;KAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;CAChG"}
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=notification-store.interface.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"notification-store.interface.js","sourceRoot":"","sources":["../../../../src/_modules/notifications/_models/notification-store.interface.ts"],"names":[],"mappings":""}
@@ -0,0 +1,59 @@
1
+ /** BFR-OLDLIGHTSITE-001 — the severity of a notification (the values of the client's `DyNX_Notification_Type`). */
2
+ export type DyNTS_NotificationKind_Type = 'info' | 'success' | 'warning' | 'error';
3
+ /**
4
+ * BFR-OLDLIGHTSITE-001 — one stored notification of one account.
5
+ *
6
+ * @description The display fields (`title`, `message`, `type`, `routerLink`, `queryParams`, `seen`, `_id`,
7
+ * `__created`) carry the SAME names as the client's `DyNX_Notification`, so a stored record can be handed to the
8
+ * `ngx-dynamo` notification components unchanged.
9
+ */
10
+ export interface DyNTS_Notification_Interface {
11
+ _id: string;
12
+ /** The recipient (`fdp-issuer-is-account-id`: always the accountId, never the userId). */
13
+ accountId: string;
14
+ /** The domain event that raised it, e.g. `sp-review.reminder` — for filtering and client routing. */
15
+ eventType: string;
16
+ title: string;
17
+ message?: string;
18
+ type: DyNTS_NotificationKind_Type;
19
+ /** An app-internal route (always starts with `/` — an external or `javascript:` link is rejected). */
20
+ routerLink?: string;
21
+ queryParams?: Record<string, string | number>;
22
+ /** Event-specific data (JSON, at most 16 KB). */
23
+ payload?: Record<string, unknown>;
24
+ /** Same account + same key → the SAME notification (a retried producer never duplicates it). */
25
+ dedupeKey?: string;
26
+ /** `null` = unread. Set ONCE, by the first read; never cleared, never moved (the read state cannot jump back). */
27
+ seen: Date | null;
28
+ __created: Date;
29
+ }
30
+ /** BFR-OLDLIGHTSITE-001 — what a producer passes to `create`. */
31
+ export interface DyNTS_NotificationCreate_Interface {
32
+ accountId: string;
33
+ eventType: string;
34
+ title: string;
35
+ message?: string;
36
+ /** Default `info`. */
37
+ type?: DyNTS_NotificationKind_Type;
38
+ routerLink?: string;
39
+ queryParams?: Record<string, string | number>;
40
+ payload?: Record<string, unknown>;
41
+ dedupeKey?: string;
42
+ }
43
+ /** BFR-OLDLIGHTSITE-001 — one page of an account's notifications, newest first. */
44
+ export interface DyNTS_NotificationPage_Interface {
45
+ items: DyNTS_Notification_Interface[];
46
+ /** Pass it as `before` for the next (older) page; `null` = no more. */
47
+ nextCursor: string | null;
48
+ unreadCount: number;
49
+ }
50
+ /** BFR-OLDLIGHTSITE-001 — the outcome of `markRead` / `markAllRead`. */
51
+ export interface DyNTS_NotificationReadResult_Interface {
52
+ /** How many notifications THIS call turned from unread to read (a repeated call → 0: idempotent). */
53
+ updated: number;
54
+ /** Requested ids that do not exist for this account (`markRead` only). */
55
+ notFound: string[];
56
+ /** The account's unread count after the call — the client sets its badge from it. */
57
+ unreadCount: number;
58
+ }
59
+ //# sourceMappingURL=notification.interface.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"notification.interface.d.ts","sourceRoot":"","sources":["../../../../src/_modules/notifications/_models/notification.interface.ts"],"names":[],"mappings":"AAAA,mHAAmH;AACnH,MAAM,MAAM,2BAA2B,GAAG,MAAM,GAAG,SAAS,GAAG,SAAS,GAAG,OAAO,CAAC;AAEnF;;;;;;GAMG;AACH,MAAM,WAAW,4BAA4B;IAC3C,GAAG,EAAE,MAAM,CAAC;IACZ,0FAA0F;IAC1F,SAAS,EAAE,MAAM,CAAC;IAClB,qGAAqG;IACrG,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,2BAA2B,CAAC;IAClC,sGAAsG;IACtG,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,CAAC;IAC9C,iDAAiD;IACjD,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAClC,gGAAgG;IAChG,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,kHAAkH;IAClH,IAAI,EAAE,IAAI,GAAG,IAAI,CAAC;IAClB,SAAS,EAAE,IAAI,CAAC;CACjB;AAED,iEAAiE;AACjE,MAAM,WAAW,kCAAkC;IACjD,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,sBAAsB;IACtB,IAAI,CAAC,EAAE,2BAA2B,CAAC;IACnC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,CAAC;IAC9C,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAClC,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,mFAAmF;AACnF,MAAM,WAAW,gCAAgC;IAC/C,KAAK,EAAE,4BAA4B,EAAE,CAAC;IACtC,uEAAuE;IACvE,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,wEAAwE;AACxE,MAAM,WAAW,sCAAsC;IACrD,qGAAqG;IACrG,OAAO,EAAE,MAAM,CAAC;IAChB,0EAA0E;IAC1E,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,qFAAqF;IACrF,WAAW,EAAE,MAAM,CAAC;CACrB"}
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=notification.interface.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"notification.interface.js","sourceRoot":"","sources":["../../../../src/_modules/notifications/_models/notification.interface.ts"],"names":[],"mappings":""}
@@ -0,0 +1,18 @@
1
+ import { DyNTS_RoutingModule } from '../../_services/route/routing-module.service';
2
+ import { DyNTS_NotificationsController_Config } from './notifications.controller';
3
+ /**
4
+ * BFR-OLDLIGHTSITE-001 — the routing module of the account notification routes (default mount: `/notifications`).
5
+ *
6
+ * @example
7
+ * ```ts
8
+ * const notifications = new DyNTS_Notification_Service({
9
+ * store: new DyNTS_Notification_MongoStore({ collection: mongoose.connection.collection('dynts_notifications') }),
10
+ * publisher: (event) => socketServer.to(`account:${event.accountId}`).emit('notification', event),
11
+ * });
12
+ * routingModules.push(DyNTS_getNotificationsRoutingModule({ service: notifications, authPreProcess: auth }));
13
+ * ```
14
+ */
15
+ export declare function DyNTS_getNotificationsRoutingModule(config: DyNTS_NotificationsController_Config & {
16
+ route?: string;
17
+ }): DyNTS_RoutingModule;
18
+ //# sourceMappingURL=get-notifications-routing-module.util.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"get-notifications-routing-module.util.d.ts","sourceRoot":"","sources":["../../../src/_modules/notifications/get-notifications-routing-module.util.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,mBAAmB,EAAE,MAAM,8CAA8C,CAAC;AACnF,OAAO,EAAkC,oCAAoC,EAAE,MAAM,4BAA4B,CAAC;AAElH;;;;;;;;;;;GAWG;AACH,wBAAgB,mCAAmC,CACjD,MAAM,EAAE,oCAAoC,GAAG;IAAE,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,GAChE,mBAAmB,CASrB"}
@@ -0,0 +1,26 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DyNTS_getNotificationsRoutingModule = DyNTS_getNotificationsRoutingModule;
4
+ const routing_module_service_1 = require("../../_services/route/routing-module.service");
5
+ const notifications_controller_1 = require("./notifications.controller");
6
+ /**
7
+ * BFR-OLDLIGHTSITE-001 — the routing module of the account notification routes (default mount: `/notifications`).
8
+ *
9
+ * @example
10
+ * ```ts
11
+ * const notifications = new DyNTS_Notification_Service({
12
+ * store: new DyNTS_Notification_MongoStore({ collection: mongoose.connection.collection('dynts_notifications') }),
13
+ * publisher: (event) => socketServer.to(`account:${event.accountId}`).emit('notification', event),
14
+ * });
15
+ * routingModules.push(DyNTS_getNotificationsRoutingModule({ service: notifications, authPreProcess: auth }));
16
+ * ```
17
+ */
18
+ function DyNTS_getNotificationsRoutingModule(config) {
19
+ notifications_controller_1.DyNTS_Notifications_Controller.configure(config);
20
+ const set = {
21
+ route: config.route ?? '/notifications',
22
+ controllers: [notifications_controller_1.DyNTS_Notifications_Controller.getInstance()],
23
+ };
24
+ return new routing_module_service_1.DyNTS_RoutingModule(set);
25
+ }
26
+ //# sourceMappingURL=get-notifications-routing-module.util.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"get-notifications-routing-module.util.js","sourceRoot":"","sources":["../../../src/_modules/notifications/get-notifications-routing-module.util.ts"],"names":[],"mappings":";;AAgBA,kFAWC;AA1BD,yFAAmF;AACnF,yEAAkH;AAElH;;;;;;;;;;;GAWG;AACH,SAAgB,mCAAmC,CACjD,MAAiE;IAEjE,yDAA8B,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;IAEjD,MAAM,GAAG,GAAiC;QACxC,KAAK,EAAE,MAAM,CAAC,KAAK,IAAI,gBAAgB;QACvC,WAAW,EAAE,CAAE,yDAA8B,CAAC,WAAW,EAAE,CAAE;KAC9D,CAAC;IAEF,OAAO,IAAI,4CAAmB,CAAC,GAAG,CAAC,CAAC;AACtC,CAAC"}
@@ -0,0 +1,15 @@
1
+ /**
2
+ * BFR-OLDLIGHTSITE-001 — `@futdevpro/nts-dynamo/notifications`: persisted per-account notifications
3
+ * (create · list · unread count · mark read · mark all read) with a monotonic read state, an optional push
4
+ * publisher, and the account routes. In-memory and MongoDB stores.
5
+ */
6
+ export * from './_models/notification.interface';
7
+ export * from './_models/notification-store.interface';
8
+ export * from './_models/notification-publisher.interface';
9
+ export * from './_models/notification-mongo-collection.interface';
10
+ export * from './notification-in-memory.store';
11
+ export * from './notification-mongo.store';
12
+ export * from './notification.service';
13
+ export * from './notifications.controller';
14
+ export * from './get-notifications-routing-module.util';
15
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/_modules/notifications/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,cAAc,kCAAkC,CAAC;AACjD,cAAc,wCAAwC,CAAC;AACvD,cAAc,4CAA4C,CAAC;AAC3D,cAAc,mDAAmD,CAAC;AAClE,cAAc,gCAAgC,CAAC;AAC/C,cAAc,4BAA4B,CAAC;AAC3C,cAAc,wBAAwB,CAAC;AACvC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,yCAAyC,CAAC"}
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const tslib_1 = require("tslib");
4
+ /**
5
+ * BFR-OLDLIGHTSITE-001 — `@futdevpro/nts-dynamo/notifications`: persisted per-account notifications
6
+ * (create · list · unread count · mark read · mark all read) with a monotonic read state, an optional push
7
+ * publisher, and the account routes. In-memory and MongoDB stores.
8
+ */
9
+ tslib_1.__exportStar(require("./_models/notification.interface"), exports);
10
+ tslib_1.__exportStar(require("./_models/notification-store.interface"), exports);
11
+ tslib_1.__exportStar(require("./_models/notification-publisher.interface"), exports);
12
+ tslib_1.__exportStar(require("./_models/notification-mongo-collection.interface"), exports);
13
+ tslib_1.__exportStar(require("./notification-in-memory.store"), exports);
14
+ tslib_1.__exportStar(require("./notification-mongo.store"), exports);
15
+ tslib_1.__exportStar(require("./notification.service"), exports);
16
+ tslib_1.__exportStar(require("./notifications.controller"), exports);
17
+ tslib_1.__exportStar(require("./get-notifications-routing-module.util"), exports);
18
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/_modules/notifications/index.ts"],"names":[],"mappings":";;;AAAA;;;;GAIG;AACH,2EAAiD;AACjD,iFAAuD;AACvD,qFAA2D;AAC3D,4FAAkE;AAClE,yEAA+C;AAC/C,qEAA2C;AAC3C,iEAAuC;AACvC,qEAA2C;AAC3C,kFAAwD"}
@@ -0,0 +1,30 @@
1
+ import { DyNTS_Notification_Interface } from './_models/notification.interface';
2
+ import { DyNTS_NotificationCursor_Interface, DyNTS_NotificationStore_Interface } from './_models/notification-store.interface';
3
+ /**
4
+ * BFR-OLDLIGHTSITE-001 — a single-process notification store (tests, local development, one-replica tools).
5
+ * Same semantics as `DyNTS_Notification_MongoStore`; every returned record is a copy.
6
+ */
7
+ export declare class DyNTS_Notification_InMemoryStore implements DyNTS_NotificationStore_Interface {
8
+ private readonly records;
9
+ insert(notification: DyNTS_Notification_Interface): Promise<DyNTS_Notification_Interface>;
10
+ list(query: {
11
+ accountId: string;
12
+ limit: number;
13
+ before?: DyNTS_NotificationCursor_Interface;
14
+ unreadOnly?: boolean;
15
+ }): Promise<DyNTS_Notification_Interface[]>;
16
+ countUnread(accountId: string): Promise<number>;
17
+ existingIds(accountId: string, ids: string[]): Promise<string[]>;
18
+ markSeen(query: {
19
+ accountId: string;
20
+ at: Date;
21
+ ids?: string[];
22
+ upTo?: Date;
23
+ }): Promise<number>;
24
+ /** Newest first: `__created` desc, then `_id` desc (a stable total order for paging). */
25
+ static newestFirst(a: DyNTS_Notification_Interface, b: DyNTS_Notification_Interface): number;
26
+ /** Strictly after the cursor in the newest-first order. */
27
+ private static isOlder;
28
+ private static copy;
29
+ }
30
+ //# sourceMappingURL=notification-in-memory.store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"notification-in-memory.store.d.ts","sourceRoot":"","sources":["../../../src/_modules/notifications/notification-in-memory.store.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,4BAA4B,EAAE,MAAM,kCAAkC,CAAC;AAChF,OAAO,EACL,kCAAkC,EAAE,iCAAiC,EACtE,MAAM,wCAAwC,CAAC;AAEhD;;;GAGG;AACH,qBAAa,gCAAiC,YAAW,iCAAiC;IACxF,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAsC;IAExD,MAAM,CAAC,YAAY,EAAE,4BAA4B,GAAG,OAAO,CAAC,4BAA4B,CAAC;IAgBzF,IAAI,CAAC,KAAK,EAAE;QAChB,SAAS,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,kCAAkC,CAAC;QAAC,UAAU,CAAC,EAAE,OAAO,CAAC;KACrG,GAAG,OAAO,CAAC,4BAA4B,EAAE,CAAC;IAUrC,WAAW,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAM/C,WAAW,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IAMhE,QAAQ,CAAC,KAAK,EAAE;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,IAAI,CAAC;QAAC,GAAG,CAAC,EAAE,MAAM,EAAE,CAAC;QAAC,IAAI,CAAC,EAAE,IAAI,CAAA;KAAE,GAAG,OAAO,CAAC,MAAM,CAAC;IAiBpG,yFAAyF;IACzF,MAAM,CAAC,WAAW,CAAC,CAAC,EAAE,4BAA4B,EAAE,CAAC,EAAE,4BAA4B,GAAG,MAAM;IAI5F,2DAA2D;IAC3D,OAAO,CAAC,MAAM,CAAC,OAAO;IAMtB,OAAO,CAAC,MAAM,CAAC,IAAI;CASpB"}
@@ -0,0 +1,68 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DyNTS_Notification_InMemoryStore = void 0;
4
+ /**
5
+ * BFR-OLDLIGHTSITE-001 — a single-process notification store (tests, local development, one-replica tools).
6
+ * Same semantics as `DyNTS_Notification_MongoStore`; every returned record is a copy.
7
+ */
8
+ class DyNTS_Notification_InMemoryStore {
9
+ records = [];
10
+ async insert(notification) {
11
+ const existing = notification.dedupeKey
12
+ ? this.records.find((record) => (record.accountId === notification.accountId && record.dedupeKey === notification.dedupeKey))
13
+ : undefined;
14
+ if (existing) {
15
+ return DyNTS_Notification_InMemoryStore.copy(existing);
16
+ }
17
+ this.records.push(DyNTS_Notification_InMemoryStore.copy(notification));
18
+ return DyNTS_Notification_InMemoryStore.copy(notification);
19
+ }
20
+ async list(query) {
21
+ return this.records
22
+ .filter((record) => record.accountId === query.accountId
23
+ && (!query.unreadOnly || !record.seen)
24
+ && (!query.before || DyNTS_Notification_InMemoryStore.isOlder(record, query.before)))
25
+ .sort(DyNTS_Notification_InMemoryStore.newestFirst)
26
+ .slice(0, query.limit)
27
+ .map(DyNTS_Notification_InMemoryStore.copy);
28
+ }
29
+ async countUnread(accountId) {
30
+ return this.records.filter((record) => (record.accountId === accountId && !record.seen)).length;
31
+ }
32
+ async existingIds(accountId, ids) {
33
+ return ids.filter((id) => this.records.some((record) => (record.accountId === accountId && record._id === id)));
34
+ }
35
+ async markSeen(query) {
36
+ let updated = 0;
37
+ for (const record of this.records) {
38
+ const selected = query.ids
39
+ ? query.ids.includes(record._id)
40
+ : !query.upTo || record.__created.getTime() <= query.upTo.getTime();
41
+ if (record.accountId === query.accountId && !record.seen && selected) {
42
+ record.seen = new Date(query.at);
43
+ updated++;
44
+ }
45
+ }
46
+ return updated;
47
+ }
48
+ /** Newest first: `__created` desc, then `_id` desc (a stable total order for paging). */
49
+ static newestFirst(a, b) {
50
+ return b.__created.getTime() - a.__created.getTime() || (a._id < b._id ? 1 : a._id > b._id ? -1 : 0);
51
+ }
52
+ /** Strictly after the cursor in the newest-first order. */
53
+ static isOlder(record, before) {
54
+ const created = record.__created.getTime();
55
+ return created < before.created.getTime() || (created === before.created.getTime() && record._id < before.id);
56
+ }
57
+ static copy(record) {
58
+ return {
59
+ ...record,
60
+ __created: new Date(record.__created),
61
+ seen: record.seen ? new Date(record.seen) : null,
62
+ queryParams: record.queryParams ? { ...record.queryParams } : undefined,
63
+ payload: record.payload ? JSON.parse(JSON.stringify(record.payload)) : undefined,
64
+ };
65
+ }
66
+ }
67
+ exports.DyNTS_Notification_InMemoryStore = DyNTS_Notification_InMemoryStore;
68
+ //# sourceMappingURL=notification-in-memory.store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"notification-in-memory.store.js","sourceRoot":"","sources":["../../../src/_modules/notifications/notification-in-memory.store.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,51 @@
1
+ import { DyNTS_Notification_Interface } from './_models/notification.interface';
2
+ import { DyNTS_NotificationMongoCollection_Interface } from './_models/notification-mongo-collection.interface';
3
+ import { DyNTS_NotificationCursor_Interface, DyNTS_NotificationStore_Interface } from './_models/notification-store.interface';
4
+ /**
5
+ * BFR-OLDLIGHTSITE-001 — the shared (multi-process, multi-replica) notification store, on MongoDB.
6
+ *
7
+ * @description
8
+ * - **Read state never jumps back:** `markSeen` updates with the filter `seen: null`, so an already read
9
+ * notification is never touched again — two devices, the API and the socket may race, the first read wins and
10
+ * the state only ever goes unread → read. There is no operation that clears `seen`.
11
+ * - **Idempotent create:** a unique partial index on `(accountId, dedupeKey)` makes a retried producer's second
12
+ * insert fail with a duplicate key; the store then returns the EXISTING record.
13
+ * - Paging is a keyset on `(__created desc, _id desc)` — stable under concurrent inserts, no skip/offset.
14
+ *
15
+ * Usage: `new DyNTS_Notification_MongoStore({ collection: mongoose.connection.collection('dynts_notifications') })`,
16
+ * then `ensureIndexes()` once at boot.
17
+ */
18
+ export declare class DyNTS_Notification_MongoStore implements DyNTS_NotificationStore_Interface {
19
+ private readonly collection;
20
+ /** Every replica must point at the SAME collection. */
21
+ constructor(set: {
22
+ collection: DyNTS_NotificationMongoCollection_Interface;
23
+ });
24
+ /**
25
+ * The list, unread-count and dedupe indexes; with `retentionDays`, a TTL index that deletes notifications older than
26
+ * that. Idempotent — call once at boot.
27
+ */
28
+ ensureIndexes(options?: {
29
+ retentionDays?: number;
30
+ }): Promise<void>;
31
+ insert(notification: DyNTS_Notification_Interface): Promise<DyNTS_Notification_Interface>;
32
+ list(query: {
33
+ accountId: string;
34
+ limit: number;
35
+ before?: DyNTS_NotificationCursor_Interface;
36
+ unreadOnly?: boolean;
37
+ }): Promise<DyNTS_Notification_Interface[]>;
38
+ countUnread(accountId: string): Promise<number>;
39
+ existingIds(accountId: string, ids: string[]): Promise<string[]>;
40
+ markSeen(query: {
41
+ accountId: string;
42
+ at: Date;
43
+ ids?: string[];
44
+ upTo?: Date;
45
+ }): Promise<number>;
46
+ /** A stored document → a clean record (dates as `Date`, no driver-specific fields). */
47
+ static fromDocument(document: unknown): DyNTS_Notification_Interface;
48
+ private static toDate;
49
+ private static kindOf;
50
+ }
51
+ //# sourceMappingURL=notification-mongo.store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"notification-mongo.store.d.ts","sourceRoot":"","sources":["../../../src/_modules/notifications/notification-mongo.store.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,4BAA4B,EAAE,MAAM,kCAAkC,CAAC;AAChF,OAAO,EAAE,2CAA2C,EAAE,MAAM,mDAAmD,CAAC;AAChH,OAAO,EACL,kCAAkC,EAAE,iCAAiC,EACtE,MAAM,wCAAwC,CAAC;AAKhD;;;;;;;;;;;;;GAaG;AACH,qBAAa,6BAA8B,YAAW,iCAAiC;IACrF,OAAO,CAAC,QAAQ,CAAC,UAAU,CAA8C;IAEzE,uDAAuD;gBAC3C,GAAG,EAAE;QAAE,UAAU,EAAE,2CAA2C,CAAA;KAAE;IAO5E;;;OAGG;IACG,aAAa,CAAC,OAAO,GAAE;QAAE,aAAa,CAAC,EAAE,MAAM,CAAA;KAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IAgBtE,MAAM,CAAC,YAAY,EAAE,4BAA4B,GAAG,OAAO,CAAC,4BAA4B,CAAC;IA+BzF,IAAI,CAAC,KAAK,EAAE;QAChB,SAAS,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,kCAAkC,CAAC;QAAC,UAAU,CAAC,EAAE,OAAO,CAAC;KACrG,GAAG,OAAO,CAAC,4BAA4B,EAAE,CAAC;IAoBrC,WAAW,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAI/C,WAAW,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IAWhE,QAAQ,CAAC,KAAK,EAAE;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,IAAI,CAAC;QAAC,GAAG,CAAC,EAAE,MAAM,EAAE,CAAC;QAAC,IAAI,CAAC,EAAE,IAAI,CAAA;KAAE,GAAG,OAAO,CAAC,MAAM,CAAC;IAepG,uFAAuF;IACvF,MAAM,CAAC,YAAY,CAAC,QAAQ,EAAE,OAAO,GAAG,4BAA4B;IAsBpE,OAAO,CAAC,MAAM,CAAC,MAAM;IAIrB,OAAO,CAAC,MAAM,CAAC,MAAM;CAGtB"}
@@ -0,0 +1,136 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DyNTS_Notification_MongoStore = void 0;
4
+ /** MongoDB's duplicate-key error code. */
5
+ const DUPLICATE_KEY = 11000;
6
+ /**
7
+ * BFR-OLDLIGHTSITE-001 — the shared (multi-process, multi-replica) notification store, on MongoDB.
8
+ *
9
+ * @description
10
+ * - **Read state never jumps back:** `markSeen` updates with the filter `seen: null`, so an already read
11
+ * notification is never touched again — two devices, the API and the socket may race, the first read wins and
12
+ * the state only ever goes unread → read. There is no operation that clears `seen`.
13
+ * - **Idempotent create:** a unique partial index on `(accountId, dedupeKey)` makes a retried producer's second
14
+ * insert fail with a duplicate key; the store then returns the EXISTING record.
15
+ * - Paging is a keyset on `(__created desc, _id desc)` — stable under concurrent inserts, no skip/offset.
16
+ *
17
+ * Usage: `new DyNTS_Notification_MongoStore({ collection: mongoose.connection.collection('dynts_notifications') })`,
18
+ * then `ensureIndexes()` once at boot.
19
+ */
20
+ class DyNTS_Notification_MongoStore {
21
+ collection;
22
+ /** Every replica must point at the SAME collection. */
23
+ constructor(set) {
24
+ if (!set?.collection) {
25
+ throw new Error('DYNTS_NOTIFICATION_MONGO_STORE_COLLECTION_MISSING');
26
+ }
27
+ this.collection = set.collection;
28
+ }
29
+ /**
30
+ * The list, unread-count and dedupe indexes; with `retentionDays`, a TTL index that deletes notifications older than
31
+ * that. Idempotent — call once at boot.
32
+ */
33
+ async ensureIndexes(options = {}) {
34
+ await this.collection.createIndex({ accountId: 1, __created: -1, _id: -1 }, { name: 'dynts_notification_list' });
35
+ await this.collection.createIndex({ accountId: 1, seen: 1 }, { name: 'dynts_notification_unread' });
36
+ await this.collection.createIndex({ accountId: 1, dedupeKey: 1 }, {
37
+ name: 'dynts_notification_dedupe', unique: true, partialFilterExpression: { dedupeKey: { $type: 'string' } },
38
+ });
39
+ if (options.retentionDays) {
40
+ await this.collection.createIndex({ __created: 1 }, {
41
+ name: 'dynts_notification_retention', expireAfterSeconds: Math.round(options.retentionDays * 86400),
42
+ });
43
+ }
44
+ }
45
+ async insert(notification) {
46
+ const document = { ...notification };
47
+ // an absent optional field is not stored (a `null` dedupeKey would collide in the unique index)
48
+ Object.keys(document).forEach((key) => {
49
+ if (document[key] === undefined) {
50
+ delete document[key];
51
+ }
52
+ });
53
+ try {
54
+ await this.collection.insertOne(document);
55
+ return notification;
56
+ }
57
+ catch (error) {
58
+ if (Reflect.get(Object(error), 'code') !== DUPLICATE_KEY || !notification.dedupeKey) {
59
+ throw error;
60
+ }
61
+ const existing = await this.collection.findOne({
62
+ accountId: notification.accountId, dedupeKey: notification.dedupeKey,
63
+ });
64
+ if (!existing) {
65
+ throw error;
66
+ }
67
+ return DyNTS_Notification_MongoStore.fromDocument(existing);
68
+ }
69
+ }
70
+ async list(query) {
71
+ const filter = { accountId: query.accountId };
72
+ if (query.unreadOnly) {
73
+ filter.seen = null;
74
+ }
75
+ if (query.before) {
76
+ filter.$or = [
77
+ { __created: { $lt: query.before.created } },
78
+ { __created: query.before.created, _id: { $lt: query.before.id } },
79
+ ];
80
+ }
81
+ const documents = await this.collection
82
+ .find(filter, { sort: { __created: -1, _id: -1 }, limit: query.limit })
83
+ .toArray();
84
+ return documents.map(DyNTS_Notification_MongoStore.fromDocument);
85
+ }
86
+ async countUnread(accountId) {
87
+ return this.collection.countDocuments({ accountId: accountId, seen: null });
88
+ }
89
+ async existingIds(accountId, ids) {
90
+ const documents = await this.collection
91
+ .find({ accountId: accountId, _id: { $in: ids } }, { projection: { _id: 1 } })
92
+ .toArray();
93
+ const found = new Set(documents.map((document) => (String(Reflect.get(Object(document), '_id')))));
94
+ return ids.filter((id) => found.has(id));
95
+ }
96
+ async markSeen(query) {
97
+ // `seen: null` in the filter is the whole guarantee: a read notification is never matched again
98
+ const filter = { accountId: query.accountId, seen: null };
99
+ if (query.ids) {
100
+ filter._id = { $in: query.ids };
101
+ }
102
+ else if (query.upTo) {
103
+ filter.__created = { $lte: query.upTo };
104
+ }
105
+ const result = await this.collection.updateMany(filter, { $set: { seen: query.at } });
106
+ return Number(result?.modifiedCount ?? 0);
107
+ }
108
+ /** A stored document → a clean record (dates as `Date`, no driver-specific fields). */
109
+ static fromDocument(document) {
110
+ const read = (key) => Reflect.get(Object(document), key);
111
+ const seen = read('seen');
112
+ const record = {
113
+ _id: String(read('_id')),
114
+ accountId: String(read('accountId')),
115
+ eventType: String(read('eventType')),
116
+ title: String(read('title')),
117
+ type: DyNTS_Notification_MongoStore.kindOf(read('type')),
118
+ seen: seen ? DyNTS_Notification_MongoStore.toDate(seen) : null,
119
+ __created: DyNTS_Notification_MongoStore.toDate(read('__created')),
120
+ };
121
+ for (const key of ['message', 'routerLink', 'queryParams', 'payload', 'dedupeKey']) {
122
+ if (read(key) !== undefined && read(key) !== null) {
123
+ Reflect.set(record, key, read(key));
124
+ }
125
+ }
126
+ return record;
127
+ }
128
+ static toDate(value) {
129
+ return value instanceof Date ? new Date(value.getTime()) : new Date(String(value));
130
+ }
131
+ static kindOf(value) {
132
+ return value === 'success' || value === 'warning' || value === 'error' ? value : 'info';
133
+ }
134
+ }
135
+ exports.DyNTS_Notification_MongoStore = DyNTS_Notification_MongoStore;
136
+ //# sourceMappingURL=notification-mongo.store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"notification-mongo.store.js","sourceRoot":"","sources":["../../../src/_modules/notifications/notification-mongo.store.ts"],"names":[],"mappings":"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"}