@futdevpro/nts-dynamo 1.15.362 → 1.15.363

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 (41) 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/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":";;;AAKA;;;GAGG;AACH,MAAa,gCAAgC;IAC1B,OAAO,GAAmC,EAAE,CAAC;IAE9D,KAAK,CAAC,MAAM,CAAC,YAA0C;QACrD,MAAM,QAAQ,GAA6C,YAAY,CAAC,SAAS;YAC/E,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,MAAoC,EAAW,EAAE,CAAC,CACrE,MAAM,CAAC,SAAS,KAAK,YAAY,CAAC,SAAS,IAAI,MAAM,CAAC,SAAS,KAAK,YAAY,CAAC,SAAS,CAC3F,CAAC;YACF,CAAC,CAAC,SAAS,CAAC;QAEd,IAAI,QAAQ,EAAE,CAAC;YACb,OAAO,gCAAgC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QACzD,CAAC;QAED,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,gCAAgC,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC,CAAC;QAEvE,OAAO,gCAAgC,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;IAC7D,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,KAEV;QACC,OAAO,IAAI,CAAC,OAAO;aAChB,MAAM,CAAC,CAAC,MAAoC,EAAW,EAAE,CAAC,MAAM,CAAC,SAAS,KAAK,KAAK,CAAC,SAAS;eAC1F,CAAC,CAAC,KAAK,CAAC,UAAU,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC;eACnC,CAAC,CAAC,KAAK,CAAC,MAAM,IAAI,gCAAgC,CAAC,OAAO,CAAC,MAAM,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC;aACtF,IAAI,CAAC,gCAAgC,CAAC,WAAW,CAAC;aAClD,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,KAAK,CAAC;aACrB,GAAG,CAAC,gCAAgC,CAAC,IAAI,CAAC,CAAC;IAChD,CAAC;IAED,KAAK,CAAC,WAAW,CAAC,SAAiB;QACjC,OAAO,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,MAAoC,EAAW,EAAE,CAAC,CAC5E,MAAM,CAAC,SAAS,KAAK,SAAS,IAAI,CAAC,MAAM,CAAC,IAAI,CAC/C,CAAC,CAAC,MAAM,CAAC;IACZ,CAAC;IAED,KAAK,CAAC,WAAW,CAAC,SAAiB,EAAE,GAAa;QAChD,OAAO,GAAG,CAAC,MAAM,CAAC,CAAC,EAAU,EAAW,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,MAAoC,EAAW,EAAE,CAAC,CAC9G,MAAM,CAAC,SAAS,KAAK,SAAS,IAAI,MAAM,CAAC,GAAG,KAAK,EAAE,CACpD,CAAC,CAAC,CAAC;IACN,CAAC;IAED,KAAK,CAAC,QAAQ,CAAC,KAAmE;QAChF,IAAI,OAAO,GAAW,CAAC,CAAC;QAExB,KAAK,MAAM,MAAM,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;YAClC,MAAM,QAAQ,GAAY,KAAK,CAAC,GAAG;gBACjC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC;gBAChC,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,IAAI,MAAM,CAAC,SAAS,CAAC,OAAO,EAAE,IAAI,KAAK,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC;YAEtE,IAAI,MAAM,CAAC,SAAS,KAAK,KAAK,CAAC,SAAS,IAAI,CAAC,MAAM,CAAC,IAAI,IAAI,QAAQ,EAAE,CAAC;gBACrE,MAAM,CAAC,IAAI,GAAG,IAAI,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;gBACjC,OAAO,EAAE,CAAC;YACZ,CAAC;QACH,CAAC;QAED,OAAO,OAAO,CAAC;IACjB,CAAC;IAED,yFAAyF;IACzF,MAAM,CAAC,WAAW,CAAC,CAA+B,EAAE,CAA+B;QACjF,OAAO,CAAC,CAAC,SAAS,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC,SAAS,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACvG,CAAC;IAED,2DAA2D;IACnD,MAAM,CAAC,OAAO,CAAC,MAAoC,EAAE,MAA0C;QACrG,MAAM,OAAO,GAAW,MAAM,CAAC,SAAS,CAAC,OAAO,EAAE,CAAC;QAEnD,OAAO,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC,OAAO,EAAE,IAAI,CAAC,OAAO,KAAK,MAAM,CAAC,OAAO,CAAC,OAAO,EAAE,IAAI,MAAM,CAAC,GAAG,GAAG,MAAM,CAAC,EAAE,CAAC,CAAC;IAChH,CAAC;IAEO,MAAM,CAAC,IAAI,CAAC,MAAoC;QACtD,OAAO;YACL,GAAG,MAAM;YACT,SAAS,EAAE,IAAI,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC;YACrC,IAAI,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI;YAChD,WAAW,EAAE,MAAM,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,GAAG,MAAM,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,SAAS;YACvE,OAAO,EAAE,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS;SACjF,CAAC;IACJ,CAAC;CACF;AAjFD,4EAiFC"}
@@ -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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"}
@@ -0,0 +1,67 @@
1
+ import { DyNTS_Notification_Interface, DyNTS_NotificationCreate_Interface, DyNTS_NotificationPage_Interface, DyNTS_NotificationReadResult_Interface } from './_models/notification.interface';
2
+ import { DyNTS_NotificationPublisher_Type } from './_models/notification-publisher.interface';
3
+ import { DyNTS_NotificationStore_Interface } from './_models/notification-store.interface';
4
+ /** BFR-OLDLIGHTSITE-001 — the notification service settings. */
5
+ export interface DyNTS_Notification_Settings_Interface {
6
+ store: DyNTS_NotificationStore_Interface;
7
+ /** Pushes every change to the account's devices (e.g. a socket room). Optional, best effort. */
8
+ publisher?: DyNTS_NotificationPublisher_Type;
9
+ /** When given, `create` accepts ONLY these event types (a typo in a producer is a 400, not a silent new type). */
10
+ eventTypes?: string[];
11
+ /** A test seam (default: `() => new Date()`). */
12
+ now?: () => Date;
13
+ }
14
+ /**
15
+ * BFR-OLDLIGHTSITE-001 — a persisted, per-account notification service: create · list · unread count ·
16
+ * mark read · mark all read, with an optional push publisher.
17
+ *
18
+ * @description
19
+ * - **The read state never jumps back.** A read only ever turns unread → read, the FIRST read's time is kept, and
20
+ * nothing clears it; `markAllRead` takes an `upTo` watermark, so a notification that arrived AFTER the user looked
21
+ * at the list is not marked read by a late "mark all" from another device.
22
+ * - **Idempotent.** A repeated `markRead` / `markAllRead` changes nothing (`updated: 0`); a producer that retries
23
+ * `create` with the same `dedupeKey` gets the same notification back.
24
+ * - **A stable retry contract for clients.** 400 `DyNTS-NTF-IN0` = the request is wrong, do not retry; 401
25
+ * `DyNTS-NTF-AU0` = no account; 503 `DyNTS-NTF-ST0` = the store failed, retry with backoff (every operation is
26
+ * idempotent, so a retry is always safe).
27
+ */
28
+ export declare class DyNTS_Notification_Service {
29
+ private readonly store;
30
+ private readonly publisher?;
31
+ private readonly eventTypes?;
32
+ private readonly now;
33
+ /** Keeps the store and the optional publisher; `eventTypes` restricts `create`. */
34
+ constructor(settings: DyNTS_Notification_Settings_Interface);
35
+ /** Stores a notification for one account (idempotent with `dedupeKey`) and publishes it. */
36
+ create(input: DyNTS_NotificationCreate_Interface): Promise<DyNTS_Notification_Interface>;
37
+ /** One page, newest first; `before` is the `nextCursor` of the previous page. */
38
+ listForAccount(accountId: string, options?: {
39
+ limit?: number;
40
+ before?: string | null;
41
+ unreadOnly?: boolean;
42
+ }): Promise<DyNTS_NotificationPage_Interface>;
43
+ unreadCount(accountId: string): Promise<number>;
44
+ /** Marks the given notifications read (the account's own only; an already read one is left as it is). */
45
+ markRead(accountId: string, ids: string[]): Promise<DyNTS_NotificationReadResult_Interface>;
46
+ /**
47
+ * Marks every unread notification created at or before `upTo` read. Pass the time the user SAW the list (default:
48
+ * now), so a notification that arrived later stays unread.
49
+ */
50
+ markAllRead(accountId: string, upTo?: Date | string | null): Promise<DyNTS_NotificationReadResult_Interface>;
51
+ /** The opaque paging cursor of a record. */
52
+ static cursorOf(record: DyNTS_Notification_Interface): string;
53
+ private static parseCursor;
54
+ private static limitOf;
55
+ private validateCreate;
56
+ private validateAccount;
57
+ private validateIds;
58
+ private static isQueryParams;
59
+ private static isSmallJsonObject;
60
+ private invalid;
61
+ private static invalidStatic;
62
+ /** A store call; a failure becomes a retryable 503 (every operation is idempotent). */
63
+ private call;
64
+ /** Best effort: a failing publisher (or its unread count) is logged, never thrown. */
65
+ private publish;
66
+ }
67
+ //# sourceMappingURL=notification.service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"notification.service.d.ts","sourceRoot":"","sources":["../../../src/_modules/notifications/notification.service.ts"],"names":[],"mappings":"AAMA,OAAO,EACL,4BAA4B,EAAE,kCAAkC,EAChE,gCAAgC,EAAE,sCAAsC,EACzE,MAAM,kCAAkC,CAAC;AAC1C,OAAO,EACyB,gCAAgC,EAC/D,MAAM,4CAA4C,CAAC;AACpD,OAAO,EAC+B,iCAAiC,EACtE,MAAM,wCAAwC,CAAC;AAIhD,gEAAgE;AAChE,MAAM,WAAW,qCAAqC;IACpD,KAAK,EAAE,iCAAiC,CAAC;IACzC,gGAAgG;IAChG,SAAS,CAAC,EAAE,gCAAgC,CAAC;IAC7C,kHAAkH;IAClH,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IACtB,iDAAiD;IACjD,GAAG,CAAC,EAAE,MAAM,IAAI,CAAC;CAClB;AAED;;;;;;;;;;;;;GAaG;AACH,qBAAa,0BAA0B;IACrC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAoC;IAC1D,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAmC;IAC9D,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAc;IAC1C,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAa;IAEjC,mFAAmF;gBACvE,QAAQ,EAAE,qCAAqC;IAU3D,4FAA4F;IACtF,MAAM,CAAC,KAAK,EAAE,kCAAkC,GAAG,OAAO,CAAC,4BAA4B,CAAC;IAiB9F,iFAAiF;IAC3E,cAAc,CAClB,SAAS,EAAE,MAAM,EAAE,OAAO,GAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,UAAU,CAAC,EAAE,OAAO,CAAA;KAAO,GAChG,OAAO,CAAC,gCAAgC,CAAC;IAsBtC,WAAW,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAMrD,yGAAyG;IACnG,QAAQ,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,sCAAsC,CAAC;IA2BjG;;;OAGG;IACG,WAAW,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,IAAI,GAAG,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,sCAAsC,CAAC;IA0BlH,4CAA4C;IAC5C,MAAM,CAAC,QAAQ,CAAC,MAAM,EAAE,4BAA4B,GAAG,MAAM;IAI7D,OAAO,CAAC,MAAM,CAAC,WAAW;IAW1B,OAAO,CAAC,MAAM,CAAC,OAAO;IAMtB,OAAO,CAAC,cAAc;IA0DtB,OAAO,CAAC,eAAe;IAavB,OAAO,CAAC,WAAW;IASnB,OAAO,CAAC,MAAM,CAAC,aAAa;IAY5B,OAAO,CAAC,MAAM,CAAC,iBAAiB;IAWhC,OAAO,CAAC,OAAO;IAIf,OAAO,CAAC,MAAM,CAAC,aAAa;IAU5B,uFAAuF;YACzE,IAAI;IAoBlB,sFAAsF;YACxE,OAAO;CAetB"}
@@ -0,0 +1,271 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DyNTS_Notification_Service = void 0;
4
+ const crypto_1 = require("crypto");
5
+ const fsm_dynamo_1 = require("@futdevpro/fsm-dynamo");
6
+ const global_settings_const_1 = require("../../_collections/global-settings.const");
7
+ const secret_redaction_util_1 = require("../../_collections/secret-redaction.util");
8
+ const KINDS = ['info', 'success', 'warning', 'error'];
9
+ /**
10
+ * BFR-OLDLIGHTSITE-001 — a persisted, per-account notification service: create · list · unread count ·
11
+ * mark read · mark all read, with an optional push publisher.
12
+ *
13
+ * @description
14
+ * - **The read state never jumps back.** A read only ever turns unread → read, the FIRST read's time is kept, and
15
+ * nothing clears it; `markAllRead` takes an `upTo` watermark, so a notification that arrived AFTER the user looked
16
+ * at the list is not marked read by a late "mark all" from another device.
17
+ * - **Idempotent.** A repeated `markRead` / `markAllRead` changes nothing (`updated: 0`); a producer that retries
18
+ * `create` with the same `dedupeKey` gets the same notification back.
19
+ * - **A stable retry contract for clients.** 400 `DyNTS-NTF-IN0` = the request is wrong, do not retry; 401
20
+ * `DyNTS-NTF-AU0` = no account; 503 `DyNTS-NTF-ST0` = the store failed, retry with backoff (every operation is
21
+ * idempotent, so a retry is always safe).
22
+ */
23
+ class DyNTS_Notification_Service {
24
+ store;
25
+ publisher;
26
+ eventTypes;
27
+ now;
28
+ /** Keeps the store and the optional publisher; `eventTypes` restricts `create`. */
29
+ constructor(settings) {
30
+ if (!settings?.store) {
31
+ throw new Error('DYNTS_NOTIFICATION_SERVICE_STORE_MISSING');
32
+ }
33
+ this.store = settings.store;
34
+ this.publisher = settings.publisher;
35
+ this.eventTypes = settings.eventTypes ? new Set(settings.eventTypes) : undefined;
36
+ this.now = settings.now ?? (() => new Date());
37
+ }
38
+ /** Stores a notification for one account (idempotent with `dedupeKey`) and publishes it. */
39
+ async create(input) {
40
+ const notification = this.validateCreate(input);
41
+ const stored = await this.call('create', () => this.store.insert(notification));
42
+ // a deduplicated retry returns the existing record — it was published the first time
43
+ if (stored._id === notification._id) {
44
+ await this.publish(async () => ({
45
+ kind: 'created', accountId: stored.accountId, notification: stored,
46
+ unreadCount: await this.store.countUnread(stored.accountId),
47
+ }));
48
+ }
49
+ return stored;
50
+ }
51
+ /** One page, newest first; `before` is the `nextCursor` of the previous page. */
52
+ async listForAccount(accountId, options = {}) {
53
+ const account = this.validateAccount(accountId);
54
+ const limit = DyNTS_Notification_Service.limitOf(options.limit);
55
+ const before = options.before
56
+ ? DyNTS_Notification_Service.parseCursor(options.before)
57
+ : undefined;
58
+ // one extra item tells whether an older page exists
59
+ const items = await this.call('list', () => this.store.list({
60
+ accountId: account, limit: limit + 1, before: before, unreadOnly: !!options.unreadOnly,
61
+ }));
62
+ const page = items.slice(0, limit);
63
+ const last = page[page.length - 1];
64
+ return {
65
+ items: page,
66
+ nextCursor: items.length > limit && last ? DyNTS_Notification_Service.cursorOf(last) : null,
67
+ unreadCount: await this.unreadCount(account),
68
+ };
69
+ }
70
+ async unreadCount(accountId) {
71
+ const account = this.validateAccount(accountId);
72
+ return this.call('count', () => this.store.countUnread(account));
73
+ }
74
+ /** Marks the given notifications read (the account's own only; an already read one is left as it is). */
75
+ async markRead(accountId, ids) {
76
+ const account = this.validateAccount(accountId);
77
+ const wanted = this.validateIds(ids);
78
+ const at = this.now();
79
+ const existing = await this.call('read', () => this.store.existingIds(account, wanted));
80
+ const updated = existing.length
81
+ ? await this.call('read', () => this.store.markSeen({ accountId: account, at: at, ids: existing }))
82
+ : 0;
83
+ const unreadCount = await this.unreadCount(account);
84
+ if (updated) {
85
+ await this.publish(() => ({
86
+ kind: 'read', accountId: account, ids: existing, seen: at, unreadCount: unreadCount,
87
+ }));
88
+ }
89
+ return {
90
+ updated: updated,
91
+ notFound: wanted.filter((id) => !existing.includes(id)),
92
+ unreadCount: unreadCount,
93
+ };
94
+ }
95
+ /**
96
+ * Marks every unread notification created at or before `upTo` read. Pass the time the user SAW the list (default:
97
+ * now), so a notification that arrived later stays unread.
98
+ */
99
+ async markAllRead(accountId, upTo) {
100
+ const account = this.validateAccount(accountId);
101
+ const at = this.now();
102
+ const requested = upTo === undefined || upTo === null ? at : new Date(upTo);
103
+ if (Number.isNaN(requested.getTime())) {
104
+ throw this.invalid(['upTo must be a date']);
105
+ }
106
+ // never later than this request: a fast client clock must not reach notifications created meanwhile
107
+ const watermark = requested.getTime() > at.getTime() ? at : requested;
108
+ const updated = await this.call('read-all', () => (this.store.markSeen({ accountId: account, at: at, upTo: watermark })));
109
+ const unreadCount = await this.unreadCount(account);
110
+ if (updated) {
111
+ await this.publish(() => ({
112
+ kind: 'read-all', accountId: account, upTo: watermark, seen: at, unreadCount: unreadCount,
113
+ }));
114
+ }
115
+ return { updated: updated, notFound: [], unreadCount: unreadCount };
116
+ }
117
+ /** The opaque paging cursor of a record. */
118
+ static cursorOf(record) {
119
+ return Buffer.from(`${record.__created.toISOString()}|${record._id}`).toString('base64url');
120
+ }
121
+ static parseCursor(cursor) {
122
+ const [iso, id] = Buffer.from(String(cursor), 'base64url').toString('utf8').split('|');
123
+ const created = new Date(iso);
124
+ if (!id || Number.isNaN(created.getTime())) {
125
+ throw DyNTS_Notification_Service.invalidStatic(['before is not a valid cursor']);
126
+ }
127
+ return { created: created, id: id };
128
+ }
129
+ static limitOf(limit) {
130
+ const value = Math.floor(Number(limit ?? 20));
131
+ return Number.isFinite(value) ? Math.min(100, Math.max(1, value)) : 20;
132
+ }
133
+ validateCreate(input) {
134
+ const problems = [];
135
+ const text = (value, max) => typeof value === 'string' && value.trim().length > 0
136
+ && value.length <= max;
137
+ const eventType = String(input?.eventType ?? '');
138
+ if (!text(input?.accountId, 200)) {
139
+ problems.push('accountId is required (at most 200 characters)');
140
+ }
141
+ if (!/^[a-z0-9][a-z0-9._-]{0,99}$/.test(eventType)) {
142
+ problems.push('eventType must be a lowercase identifier (a-z, 0-9, . _ -), at most 100 characters');
143
+ }
144
+ else if (this.eventTypes && !this.eventTypes.has(eventType)) {
145
+ problems.push(`eventType ${eventType} is not one of the configured event types`);
146
+ }
147
+ if (!text(input?.title, 200)) {
148
+ problems.push('title is required (at most 200 characters)');
149
+ }
150
+ if (input?.message !== undefined && (typeof input.message !== 'string' || input.message.length > 4000)) {
151
+ problems.push('message must be a string of at most 4000 characters');
152
+ }
153
+ if (input?.type !== undefined && !KINDS.includes(input.type)) {
154
+ problems.push(`type must be one of ${KINDS.join(', ')}`);
155
+ }
156
+ if (input?.routerLink !== undefined
157
+ && (typeof input.routerLink !== 'string' || !/^\/(?!\/)/.test(input.routerLink)
158
+ || input.routerLink.length > 2000)) {
159
+ // an app route only: an absolute, protocol-relative or `javascript:` link would turn a notification into a lure
160
+ problems.push('routerLink must be an app-internal path starting with a single /');
161
+ }
162
+ if (input?.queryParams !== undefined && !DyNTS_Notification_Service.isQueryParams(input.queryParams)) {
163
+ problems.push('queryParams must be an object of at most 20 string or number values');
164
+ }
165
+ if (input?.payload !== undefined && !DyNTS_Notification_Service.isSmallJsonObject(input.payload)) {
166
+ problems.push('payload must be a JSON object of at most 16 KB');
167
+ }
168
+ if (input?.dedupeKey !== undefined && !text(input.dedupeKey, 200)) {
169
+ problems.push('dedupeKey must be a non-empty string of at most 200 characters');
170
+ }
171
+ if (problems.length) {
172
+ throw this.invalid(problems);
173
+ }
174
+ return {
175
+ _id: (0, crypto_1.randomUUID)(),
176
+ accountId: input.accountId.trim(),
177
+ eventType: eventType,
178
+ title: input.title.trim(),
179
+ message: input.message,
180
+ type: input.type ?? 'info',
181
+ routerLink: input.routerLink,
182
+ queryParams: input.queryParams ? { ...input.queryParams } : undefined,
183
+ payload: input.payload ? JSON.parse(JSON.stringify(input.payload)) : undefined,
184
+ dedupeKey: input.dedupeKey,
185
+ seen: null,
186
+ __created: this.now(),
187
+ };
188
+ }
189
+ validateAccount(accountId) {
190
+ if (typeof accountId !== 'string' || !accountId.trim() || accountId.length > 200) {
191
+ throw new fsm_dynamo_1.DyFM_Error({
192
+ status: 401,
193
+ errorCode: `${global_settings_const_1.DyNTS_global_settings.systemShortCodeName}|DyNTS-NTF-AU0`,
194
+ message: 'Notifications: no account on the request.',
195
+ userMessage: 'Please sign in to see your notifications.',
196
+ });
197
+ }
198
+ return accountId.trim();
199
+ }
200
+ validateIds(ids) {
201
+ if (!Array.isArray(ids) || !ids.length || ids.length > 500
202
+ || ids.some((id) => typeof id !== 'string' || !id || id.length > 200)) {
203
+ throw this.invalid(['ids must be 1–500 non-empty strings']);
204
+ }
205
+ return [...new Set(ids)];
206
+ }
207
+ static isQueryParams(value) {
208
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
209
+ return false;
210
+ }
211
+ const entries = Object.entries(value);
212
+ return entries.length <= 20 && entries.every(([, item]) => (typeof item === 'string' || (typeof item === 'number' && Number.isFinite(item))));
213
+ }
214
+ static isSmallJsonObject(value) {
215
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
216
+ return false;
217
+ }
218
+ try {
219
+ return Buffer.byteLength(JSON.stringify(value), 'utf8') <= 16 * 1024;
220
+ }
221
+ catch {
222
+ return false;
223
+ }
224
+ }
225
+ invalid(problems) {
226
+ return DyNTS_Notification_Service.invalidStatic(problems);
227
+ }
228
+ static invalidStatic(problems) {
229
+ return new fsm_dynamo_1.DyFM_Error({
230
+ status: 400,
231
+ errorCode: `${global_settings_const_1.DyNTS_global_settings.systemShortCodeName}|DyNTS-NTF-IN0`,
232
+ message: `Notification request rejected: ${problems.join('; ')}`,
233
+ userMessage: 'The notification request is invalid.',
234
+ additionalContent: { problems: problems },
235
+ });
236
+ }
237
+ /** A store call; a failure becomes a retryable 503 (every operation is idempotent). */
238
+ async call(operation, run) {
239
+ try {
240
+ return await run();
241
+ }
242
+ catch (error) {
243
+ if (error instanceof fsm_dynamo_1.DyFM_Error) {
244
+ throw error;
245
+ }
246
+ throw new fsm_dynamo_1.DyFM_Error({
247
+ status: 503,
248
+ errorCode: `${global_settings_const_1.DyNTS_global_settings.systemShortCodeName}|DyNTS-NTF-ST0`,
249
+ message: `Notification store ${operation} failed: `
250
+ + secret_redaction_util_1.DyNTS_SecretRedaction_Util.redactText(fsm_dynamo_1.DyFM_Error.getErrorMessage(error)),
251
+ userMessage: 'Notifications are not available at the moment. Please try again.',
252
+ error: secret_redaction_util_1.DyNTS_SecretRedaction_Util.redactErrorForLog(error),
253
+ additionalContent: { operation: operation, retryable: true },
254
+ });
255
+ }
256
+ }
257
+ /** Best effort: a failing publisher (or its unread count) is logged, never thrown. */
258
+ async publish(build) {
259
+ if (!this.publisher) {
260
+ return;
261
+ }
262
+ try {
263
+ await this.publisher(await build());
264
+ }
265
+ catch (error) {
266
+ fsm_dynamo_1.DyFM_Log.warn(`[DyNTS notifications] the push failed (the stored state is intact): ${fsm_dynamo_1.DyFM_Error.getErrorMessage(error)}`);
267
+ }
268
+ }
269
+ }
270
+ exports.DyNTS_Notification_Service = DyNTS_Notification_Service;
271
+ //# sourceMappingURL=notification.service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"notification.service.js","sourceRoot":"","sources":["../../../src/_modules/notifications/notification.service.ts"],"names":[],"mappings":";;;AAAA,mCAAoC;AAEpC,sDAA6D;AAE7D,oFAAiF;AACjF,oFAAsF;AAYtF,MAAM,KAAK,GAAkC,CAAE,MAAM,EAAE,SAAS,EAAE,SAAS,EAAE,OAAO,CAAE,CAAC;AAavF;;;;;;;;;;;;;GAaG;AACH,MAAa,0BAA0B;IACpB,KAAK,CAAoC;IACzC,SAAS,CAAoC;IAC7C,UAAU,CAAe;IACzB,GAAG,CAAa;IAEjC,mFAAmF;IACnF,YAAY,QAA+C;QACzD,IAAI,CAAC,QAAQ,EAAE,KAAK,EAAE,CAAC;YACrB,MAAM,IAAI,KAAK,CAAC,0CAA0C,CAAC,CAAC;QAC9D,CAAC;QACD,IAAI,CAAC,KAAK,GAAG,QAAQ,CAAC,KAAK,CAAC;QAC5B,IAAI,CAAC,SAAS,GAAG,QAAQ,CAAC,SAAS,CAAC;QACpC,IAAI,CAAC,UAAU,GAAG,QAAQ,CAAC,UAAU,CAAC,CAAC,CAAC,IAAI,GAAG,CAAS,QAAQ,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;QACzF,IAAI,CAAC,GAAG,GAAG,QAAQ,CAAC,GAAG,IAAI,CAAC,GAAS,EAAE,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC;IACtD,CAAC;IAED,4FAA4F;IAC5F,KAAK,CAAC,MAAM,CAAC,KAAyC;QACpD,MAAM,YAAY,GAAiC,IAAI,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC;QAC9E,MAAM,MAAM,GAAiC,MAAM,IAAI,CAAC,IAAI,CAC1D,QAAQ,EAAE,GAA0C,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,YAAY,CAAC,CACvF,CAAC;QAEF,qFAAqF;QACrF,IAAI,MAAM,CAAC,GAAG,KAAK,YAAY,CAAC,GAAG,EAAE,CAAC;YACpC,MAAM,IAAI,CAAC,OAAO,CAAC,KAAK,IAA2C,EAAE,CAAC,CAAC;gBACrE,IAAI,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,CAAC,SAAS,EAAE,YAAY,EAAE,MAAM;gBAClE,WAAW,EAAE,MAAM,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,MAAM,CAAC,SAAS,CAAC;aAC5D,CAAC,CAAC,CAAC;QACN,CAAC;QAED,OAAO,MAAM,CAAC;IAChB,CAAC;IAED,iFAAiF;IACjF,KAAK,CAAC,cAAc,CAClB,SAAiB,EAAE,UAA4E,EAAE;QAEjG,MAAM,OAAO,GAAW,IAAI,CAAC,eAAe,CAAC,SAAS,CAAC,CAAC;QACxD,MAAM,KAAK,GAAW,0BAA0B,CAAC,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;QACxE,MAAM,MAAM,GAAmD,OAAO,CAAC,MAAM;YAC3E,CAAC,CAAC,0BAA0B,CAAC,WAAW,CAAC,OAAO,CAAC,MAAM,CAAC;YACxD,CAAC,CAAC,SAAS,CAAC;QACd,oDAAoD;QACpD,MAAM,KAAK,GAAmC,MAAM,IAAI,CAAC,IAAI,CAC3D,MAAM,EAAE,GAA4C,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC;YACrE,SAAS,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,GAAG,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,CAAC,CAAC,OAAO,CAAC,UAAU;SACvF,CAAC,CACH,CAAC;QACF,MAAM,IAAI,GAAmC,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC;QACnE,MAAM,IAAI,GAA6C,IAAI,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;QAE7E,OAAO;YACL,KAAK,EAAE,IAAI;YACX,UAAU,EAAE,KAAK,CAAC,MAAM,GAAG,KAAK,IAAI,IAAI,CAAC,CAAC,CAAC,0BAA0B,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI;YAC3F,WAAW,EAAE,MAAM,IAAI,CAAC,WAAW,CAAC,OAAO,CAAC;SAC7C,CAAC;IACJ,CAAC;IAED,KAAK,CAAC,WAAW,CAAC,SAAiB;QACjC,MAAM,OAAO,GAAW,IAAI,CAAC,eAAe,CAAC,SAAS,CAAC,CAAC;QAExD,OAAO,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,GAAoB,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC,CAAC;IACpF,CAAC;IAED,yGAAyG;IACzG,KAAK,CAAC,QAAQ,CAAC,SAAiB,EAAE,GAAa;QAC7C,MAAM,OAAO,GAAW,IAAI,CAAC,eAAe,CAAC,SAAS,CAAC,CAAC;QACxD,MAAM,MAAM,GAAa,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;QAC/C,MAAM,EAAE,GAAS,IAAI,CAAC,GAAG,EAAE,CAAC;QAC5B,MAAM,QAAQ,GAAa,MAAM,IAAI,CAAC,IAAI,CACxC,MAAM,EAAE,GAAsB,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,OAAO,EAAE,MAAM,CAAC,CACzE,CAAC;QACF,MAAM,OAAO,GAAW,QAAQ,CAAC,MAAM;YACrC,CAAC,CAAC,MAAM,IAAI,CAAC,IAAI,CACf,MAAM,EAAE,GAAoB,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,EAAE,SAAS,EAAE,OAAO,EAAE,EAAE,EAAE,EAAE,EAAE,GAAG,EAAE,QAAQ,EAAE,CAAC,CAClG;YACD,CAAC,CAAC,CAAC,CAAC;QACN,MAAM,WAAW,GAAW,MAAM,IAAI,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC;QAE5D,IAAI,OAAO,EAAE,CAAC;YACZ,MAAM,IAAI,CAAC,OAAO,CAAC,GAAiC,EAAE,CAAC,CAAC;gBACtD,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,EAAE,IAAI,EAAE,EAAE,EAAE,WAAW,EAAE,WAAW;aACpF,CAAC,CAAC,CAAC;QACN,CAAC;QAED,OAAO;YACL,OAAO,EAAE,OAAO;YAChB,QAAQ,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,EAAU,EAAW,EAAE,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;YACxE,WAAW,EAAE,WAAW;SACzB,CAAC;IACJ,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,WAAW,CAAC,SAAiB,EAAE,IAA2B;QAC9D,MAAM,OAAO,GAAW,IAAI,CAAC,eAAe,CAAC,SAAS,CAAC,CAAC;QACxD,MAAM,EAAE,GAAS,IAAI,CAAC,GAAG,EAAE,CAAC;QAC5B,MAAM,SAAS,GAAS,IAAI,KAAK,SAAS,IAAI,IAAI,KAAK,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,IAAI,CAAC,CAAC;QAElF,IAAI,MAAM,CAAC,KAAK,CAAC,SAAS,CAAC,OAAO,EAAE,CAAC,EAAE,CAAC;YACtC,MAAM,IAAI,CAAC,OAAO,CAAC,CAAE,qBAAqB,CAAE,CAAC,CAAC;QAChD,CAAC;QAED,oGAAoG;QACpG,MAAM,SAAS,GAAS,SAAS,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC;QAE5E,MAAM,OAAO,GAAW,MAAM,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,GAAoB,EAAE,CAAC,CACzE,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,EAAE,SAAS,EAAE,OAAO,EAAE,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC,CACrE,CAAC,CAAC;QACH,MAAM,WAAW,GAAW,MAAM,IAAI,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC;QAE5D,IAAI,OAAO,EAAE,CAAC;YACZ,MAAM,IAAI,CAAC,OAAO,CAAC,GAAiC,EAAE,CAAC,CAAC;gBACtD,IAAI,EAAE,UAAU,EAAE,SAAS,EAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,EAAE,EAAE,WAAW,EAAE,WAAW;aAC1F,CAAC,CAAC,CAAC;QACN,CAAC;QAED,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,WAAW,EAAE,WAAW,EAAE,CAAC;IACtE,CAAC;IAED,4CAA4C;IAC5C,MAAM,CAAC,QAAQ,CAAC,MAAoC;QAClD,OAAO,MAAM,CAAC,IAAI,CAAC,GAAG,MAAM,CAAC,SAAS,CAAC,WAAW,EAAE,IAAI,MAAM,CAAC,GAAG,EAAE,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC;IAC9F,CAAC;IAEO,MAAM,CAAC,WAAW,CAAC,MAAc;QACvC,MAAM,CAAE,GAAG,EAAE,EAAE,CAAE,GAAG,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,WAAW,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QACzF,MAAM,OAAO,GAAS,IAAI,IAAI,CAAC,GAAG,CAAC,CAAC;QAEpC,IAAI,CAAC,EAAE,IAAI,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC,EAAE,CAAC;YAC3C,MAAM,0BAA0B,CAAC,aAAa,CAAC,CAAE,8BAA8B,CAAE,CAAC,CAAC;QACrF,CAAC;QAED,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC;IACtC,CAAC;IAEO,MAAM,CAAC,OAAO,CAAC,KAAyB;QAC9C,MAAM,KAAK,GAAW,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,CAAC;QAEtD,OAAO,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IACzE,CAAC;IAEO,cAAc,CAAC,KAAyC;QAC9D,MAAM,QAAQ,GAAa,EAAE,CAAC;QAC9B,MAAM,IAAI,GAAG,CAAC,KAAc,EAAE,GAAW,EAAW,EAAE,CAAC,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC;eACtG,KAAK,CAAC,MAAM,IAAI,GAAG,CAAC;QACzB,MAAM,SAAS,GAAW,MAAM,CAAC,KAAK,EAAE,SAAS,IAAI,EAAE,CAAC,CAAC;QAEzD,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE,SAAS,EAAE,GAAG,CAAC,EAAE,CAAC;YACjC,QAAQ,CAAC,IAAI,CAAC,gDAAgD,CAAC,CAAC;QAClE,CAAC;QACD,IAAI,CAAC,6BAA6B,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,CAAC;YACnD,QAAQ,CAAC,IAAI,CAAC,oFAAoF,CAAC,CAAC;QACtG,CAAC;aAAM,IAAI,IAAI,CAAC,UAAU,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,SAAS,CAAC,EAAE,CAAC;YAC9D,QAAQ,CAAC,IAAI,CAAC,aAAa,SAAS,2CAA2C,CAAC,CAAC;QACnF,CAAC;QACD,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE,KAAK,EAAE,GAAG,CAAC,EAAE,CAAC;YAC7B,QAAQ,CAAC,IAAI,CAAC,4CAA4C,CAAC,CAAC;QAC9D,CAAC;QACD,IAAI,KAAK,EAAE,OAAO,KAAK,SAAS,IAAI,CAAC,OAAO,KAAK,CAAC,OAAO,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,EAAE,CAAC;YACvG,QAAQ,CAAC,IAAI,CAAC,qDAAqD,CAAC,CAAC;QACvE,CAAC;QACD,IAAI,KAAK,EAAE,IAAI,KAAK,SAAS,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;YAC7D,QAAQ,CAAC,IAAI,CAAC,uBAAuB,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAC3D,CAAC;QACD,IAAI,KAAK,EAAE,UAAU,KAAK,SAAS;eAC9B,CAAC,OAAO,KAAK,CAAC,UAAU,KAAK,QAAQ,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC;mBAC1E,KAAK,CAAC,UAAU,CAAC,MAAM,GAAG,IAAI,CAAC,EAAE,CAAC;YACvC,gHAAgH;YAChH,QAAQ,CAAC,IAAI,CAAC,kEAAkE,CAAC,CAAC;QACpF,CAAC;QACD,IAAI,KAAK,EAAE,WAAW,KAAK,SAAS,IAAI,CAAC,0BAA0B,CAAC,aAAa,CAAC,KAAK,CAAC,WAAW,CAAC,EAAE,CAAC;YACrG,QAAQ,CAAC,IAAI,CAAC,qEAAqE,CAAC,CAAC;QACvF,CAAC;QACD,IAAI,KAAK,EAAE,OAAO,KAAK,SAAS,IAAI,CAAC,0BAA0B,CAAC,iBAAiB,CAAC,KAAK,CAAC,OAAO,CAAC,EAAE,CAAC;YACjG,QAAQ,CAAC,IAAI,CAAC,gDAAgD,CAAC,CAAC;QAClE,CAAC;QACD,IAAI,KAAK,EAAE,SAAS,KAAK,SAAS,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,SAAS,EAAE,GAAG,CAAC,EAAE,CAAC;YAClE,QAAQ,CAAC,IAAI,CAAC,gEAAgE,CAAC,CAAC;QAClF,CAAC;QACD,IAAI,QAAQ,CAAC,MAAM,EAAE,CAAC;YACpB,MAAM,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;QAC/B,CAAC;QAED,OAAO;YACL,GAAG,EAAE,IAAA,mBAAU,GAAE;YACjB,SAAS,EAAE,KAAK,CAAC,SAAS,CAAC,IAAI,EAAE;YACjC,SAAS,EAAE,SAAS;YACpB,KAAK,EAAE,KAAK,CAAC,KAAK,CAAC,IAAI,EAAE;YACzB,OAAO,EAAE,KAAK,CAAC,OAAO;YACtB,IAAI,EAAE,KAAK,CAAC,IAAI,IAAI,MAAM;YAC1B,UAAU,EAAE,KAAK,CAAC,UAAU;YAC5B,WAAW,EAAE,KAAK,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,GAAG,KAAK,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,SAAS;YACrE,OAAO,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS;YAC9E,SAAS,EAAE,KAAK,CAAC,SAAS;YAC1B,IAAI,EAAE,IAAI;YACV,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE;SACtB,CAAC;IACJ,CAAC;IAEO,eAAe,CAAC,SAAiB;QACvC,IAAI,OAAO,SAAS,KAAK,QAAQ,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,SAAS,CAAC,MAAM,GAAG,GAAG,EAAE,CAAC;YACjF,MAAM,IAAI,uBAAU,CAAC;gBACnB,MAAM,EAAE,GAAG;gBACX,SAAS,EAAE,GAAG,6CAAqB,CAAC,mBAAmB,gBAAgB;gBACvE,OAAO,EAAE,2CAA2C;gBACpD,WAAW,EAAE,2CAA2C;aACzD,CAAC,CAAC;QACL,CAAC;QAED,OAAO,SAAS,CAAC,IAAI,EAAE,CAAC;IAC1B,CAAC;IAEO,WAAW,CAAC,GAAa;QAC/B,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,IAAI,GAAG,CAAC,MAAM,GAAG,GAAG;eACrD,GAAG,CAAC,IAAI,CAAC,CAAC,EAAW,EAAW,EAAE,CAAC,OAAO,EAAE,KAAK,QAAQ,IAAI,CAAC,EAAE,IAAI,EAAE,CAAC,MAAM,GAAG,GAAG,CAAC,EAAE,CAAC;YAC1F,MAAM,IAAI,CAAC,OAAO,CAAC,CAAE,qCAAqC,CAAE,CAAC,CAAC;QAChE,CAAC;QAED,OAAO,CAAE,GAAG,IAAI,GAAG,CAAS,GAAG,CAAC,CAAE,CAAC;IACrC,CAAC;IAEO,MAAM,CAAC,aAAa,CAAC,KAAc;QACzC,IAAI,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;YAChE,OAAO,KAAK,CAAC;QACf,CAAC;QAED,MAAM,OAAO,GAAwB,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;QAE3D,OAAO,OAAO,CAAC,MAAM,IAAI,EAAE,IAAI,OAAO,CAAC,KAAK,CAAC,CAAC,CAAE,AAAD,EAAG,IAAI,CAAqB,EAAW,EAAE,CAAC,CACvF,OAAO,IAAI,KAAK,QAAQ,IAAI,CAAC,OAAO,IAAI,KAAK,QAAQ,IAAI,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAChF,CAAC,CAAC;IACL,CAAC;IAEO,MAAM,CAAC,iBAAiB,CAAC,KAAc;QAC7C,IAAI,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;YAChE,OAAO,KAAK,CAAC;QACf,CAAC;QACD,IAAI,CAAC;YACH,OAAO,MAAM,CAAC,UAAU,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC,IAAI,EAAE,GAAG,IAAI,CAAC;QACvE,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,KAAK,CAAC;QACf,CAAC;IACH,CAAC;IAEO,OAAO,CAAC,QAAkB;QAChC,OAAO,0BAA0B,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC;IAC5D,CAAC;IAEO,MAAM,CAAC,aAAa,CAAC,QAAkB;QAC7C,OAAO,IAAI,uBAAU,CAAC;YACpB,MAAM,EAAE,GAAG;YACX,SAAS,EAAE,GAAG,6CAAqB,CAAC,mBAAmB,gBAAgB;YACvE,OAAO,EAAE,kCAAkC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE;YAChE,WAAW,EAAE,sCAAsC;YACnD,iBAAiB,EAAE,EAAE,QAAQ,EAAE,QAAQ,EAAE;SAC1C,CAAC,CAAC;IACL,CAAC;IAED,uFAAuF;IAC/E,KAAK,CAAC,IAAI,CAAI,SAAiB,EAAE,GAAqB;QAC5D,IAAI,CAAC;YACH,OAAO,MAAM,GAAG,EAAE,CAAC;QACrB,CAAC;QAAC,OAAO,KAAc,EAAE,CAAC;YACxB,IAAI,KAAK,YAAY,uBAAU,EAAE,CAAC;gBAChC,MAAM,KAAK,CAAC;YACd,CAAC;YAED,MAAM,IAAI,uBAAU,CAAC;gBACnB,MAAM,EAAE,GAAG;gBACX,SAAS,EAAE,GAAG,6CAAqB,CAAC,mBAAmB,gBAAgB;gBACvE,OAAO,EAAE,sBAAsB,SAAS,WAAW;sBAC/C,kDAA0B,CAAC,UAAU,CAAC,uBAAU,CAAC,eAAe,CAAC,KAAK,CAAC,CAAC;gBAC5E,WAAW,EAAE,kEAAkE;gBAC/E,KAAK,EAAE,kDAA0B,CAAC,iBAAiB,CAAC,KAAK,CAAC;gBAC1D,iBAAiB,EAAE,EAAE,SAAS,EAAE,SAAS,EAAE,SAAS,EAAE,IAAI,EAAE;aAC7D,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAED,sFAAsF;IAC9E,KAAK,CAAC,OAAO,CACnB,KAAiF;QAEjF,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,CAAC;YACpB,OAAO;QACT,CAAC;QAED,IAAI,CAAC;YACH,MAAM,IAAI,CAAC,SAAS,CAAC,MAAM,KAAK,EAAE,CAAC,CAAC;QACtC,CAAC;QAAC,OAAO,KAAc,EAAE,CAAC;YACxB,qBAAQ,CAAC,IAAI,CACX,uEAAuE,uBAAU,CAAC,eAAe,CAAC,KAAK,CAAC,EAAE,CAC3G,CAAC;QACJ,CAAC;IACH,CAAC;CACF;AA5SD,gEA4SC"}
@@ -0,0 +1,31 @@
1
+ import { Request, Response } from 'express';
2
+ import { DyNTS_Controller } from '../../_services/route/controller.service';
3
+ import { DyNTS_Notification_Service } from './notification.service';
4
+ /** BFR-OLDLIGHTSITE-001 — the notification routes' configuration. */
5
+ export interface DyNTS_NotificationsController_Config {
6
+ service: DyNTS_Notification_Service;
7
+ /** REQUIRED: every route is per account, so it must be authenticated. */
8
+ authPreProcess: (req: Request, res: Response) => Promise<void>;
9
+ /** The account of the request (default: the auth service's issuer — `fdp-issuer-is-account-id`). */
10
+ resolveAccountId?: (req: Request) => string | undefined;
11
+ }
12
+ /**
13
+ * BFR-OLDLIGHTSITE-001 — the account's own notifications over HTTP (creating one is a server-side call of the
14
+ * service, never a route):
15
+ *
16
+ * - `GET /list?limit=20&before=<cursor>&unreadOnly=true` → `{ items, nextCursor, unreadCount }`
17
+ * - `GET /unread-count` → `{ unreadCount }`
18
+ * - `POST /read` `{ ids: string[] }` → `{ updated, notFound, unreadCount }`
19
+ * - `POST /read-all` `{ upTo?: ISO date }` → `{ updated, notFound: [], unreadCount }`
20
+ */
21
+ export declare class DyNTS_Notifications_Controller extends DyNTS_Controller {
22
+ private static config;
23
+ static configure(config: DyNTS_NotificationsController_Config): void;
24
+ static getInstance(): DyNTS_Notifications_Controller;
25
+ /** The current configuration — read on EVERY request, so a reconfiguration never leaves a stale service or auth. */
26
+ static requireConfig(): DyNTS_NotificationsController_Config;
27
+ setupEndpoints(): void;
28
+ /** The request's account — a missing one is a 401 (never an "empty account" read). */
29
+ static accountOf(req: Request): string;
30
+ }
31
+ //# sourceMappingURL=notifications.controller.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"notifications.controller.d.ts","sourceRoot":"","sources":["../../../src/_modules/notifications/notifications.controller.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAO5C,OAAO,EAAE,gBAAgB,EAAE,MAAM,0CAA0C,CAAC;AAC5E,OAAO,EAAE,0BAA0B,EAAE,MAAM,wBAAwB,CAAC;AAEpE,qEAAqE;AACrE,MAAM,WAAW,oCAAoC;IACnD,OAAO,EAAE,0BAA0B,CAAC;IACpC,yEAAyE;IACzE,cAAc,EAAE,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/D,oGAAoG;IACpG,gBAAgB,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,MAAM,GAAG,SAAS,CAAC;CACzD;AAED;;;;;;;;GAQG;AACH,qBAAa,8BAA+B,SAAQ,gBAAgB;IAClE,OAAO,CAAC,MAAM,CAAC,MAAM,CAAqD;IAE1E,MAAM,CAAC,SAAS,CAAC,MAAM,EAAE,oCAAoC,GAAG,IAAI;IAOpE,MAAM,CAAC,WAAW,IAAI,8BAA8B;IAIpD,oHAAoH;IACpH,MAAM,CAAC,aAAa,IAAI,oCAAoC;IAY5D,cAAc,IAAI,IAAI;IA8DtB,sFAAsF;IACtF,MAAM,CAAC,SAAS,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM;CAuBvC"}
@@ -0,0 +1,118 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DyNTS_Notifications_Controller = void 0;
4
+ const fsm_dynamo_1 = require("@futdevpro/fsm-dynamo");
5
+ const global_settings_const_1 = require("../../_collections/global-settings.const");
6
+ const endpoint_params_control_model_1 = require("../../_models/control-models/endpoint-params.control-model");
7
+ const global_service_1 = require("../../_services/core/global.service");
8
+ const controller_service_1 = require("../../_services/route/controller.service");
9
+ /**
10
+ * BFR-OLDLIGHTSITE-001 — the account's own notifications over HTTP (creating one is a server-side call of the
11
+ * service, never a route):
12
+ *
13
+ * - `GET /list?limit=20&before=<cursor>&unreadOnly=true` → `{ items, nextCursor, unreadCount }`
14
+ * - `GET /unread-count` → `{ unreadCount }`
15
+ * - `POST /read` `{ ids: string[] }` → `{ updated, notFound, unreadCount }`
16
+ * - `POST /read-all` `{ upTo?: ISO date }` → `{ updated, notFound: [], unreadCount }`
17
+ */
18
+ class DyNTS_Notifications_Controller extends controller_service_1.DyNTS_Controller {
19
+ static config = null;
20
+ static configure(config) {
21
+ if (!config?.service || typeof config.authPreProcess !== 'function') {
22
+ throw new Error('DYNTS_NOTIFICATIONS_CONTROLLER_CONFIG_INVALID: a service and an authPreProcess are required');
23
+ }
24
+ DyNTS_Notifications_Controller.config = config;
25
+ }
26
+ static getInstance() {
27
+ return DyNTS_Notifications_Controller.getSingletonInstance();
28
+ }
29
+ /** The current configuration — read on EVERY request, so a reconfiguration never leaves a stale service or auth. */
30
+ static requireConfig() {
31
+ const config = DyNTS_Notifications_Controller.config;
32
+ if (!config) {
33
+ throw new Error('DYNTS_NOTIFICATIONS_CONTROLLER_NOT_CONFIGURED: call DyNTS_getNotificationsRoutingModule(config)');
34
+ }
35
+ return config;
36
+ }
37
+ setupEndpoints() {
38
+ DyNTS_Notifications_Controller.requireConfig();
39
+ const auth = [
40
+ (req, res) => (DyNTS_Notifications_Controller.requireConfig().authPreProcess(req, res)),
41
+ ];
42
+ const service = () => DyNTS_Notifications_Controller.requireConfig().service;
43
+ this.endpoints = [
44
+ new endpoint_params_control_model_1.DyNTS_Endpoint_Params({
45
+ name: 'listNotifications',
46
+ type: fsm_dynamo_1.DyFM_HttpCallType.get,
47
+ endpoint: '/list',
48
+ preProcesses: auth,
49
+ tasks: [
50
+ async (req, res) => {
51
+ res.send(await service().listForAccount(DyNTS_Notifications_Controller.accountOf(req), {
52
+ limit: req.query.limit === undefined ? undefined : Number(req.query.limit),
53
+ before: typeof req.query.before === 'string' ? req.query.before : undefined,
54
+ unreadOnly: req.query.unreadOnly === 'true',
55
+ }));
56
+ },
57
+ ],
58
+ }),
59
+ new endpoint_params_control_model_1.DyNTS_Endpoint_Params({
60
+ name: 'unreadNotificationCount',
61
+ type: fsm_dynamo_1.DyFM_HttpCallType.get,
62
+ endpoint: '/unread-count',
63
+ preProcesses: auth,
64
+ tasks: [
65
+ async (req, res) => {
66
+ res.send({ unreadCount: await service().unreadCount(DyNTS_Notifications_Controller.accountOf(req)) });
67
+ },
68
+ ],
69
+ }),
70
+ new endpoint_params_control_model_1.DyNTS_Endpoint_Params({
71
+ name: 'markNotificationsRead',
72
+ type: fsm_dynamo_1.DyFM_HttpCallType.post,
73
+ endpoint: '/read',
74
+ preProcesses: auth,
75
+ tasks: [
76
+ async (req, res) => {
77
+ res.send(await service().markRead(DyNTS_Notifications_Controller.accountOf(req), req.body?.ids));
78
+ },
79
+ ],
80
+ }),
81
+ new endpoint_params_control_model_1.DyNTS_Endpoint_Params({
82
+ name: 'markAllNotificationsRead',
83
+ type: fsm_dynamo_1.DyFM_HttpCallType.post,
84
+ endpoint: '/read-all',
85
+ preProcesses: auth,
86
+ tasks: [
87
+ async (req, res) => {
88
+ res.send(await service().markAllRead(DyNTS_Notifications_Controller.accountOf(req), req.body?.upTo));
89
+ },
90
+ ],
91
+ }),
92
+ ];
93
+ }
94
+ /** The request's account — a missing one is a 401 (never an "empty account" read). */
95
+ static accountOf(req) {
96
+ const config = DyNTS_Notifications_Controller.config;
97
+ let accountId;
98
+ try {
99
+ accountId = config?.resolveAccountId
100
+ ? config.resolveAccountId(req)
101
+ : global_service_1.DyNTS_GlobalService.getAuthService()?.getIssuerFromRequest(req);
102
+ }
103
+ catch {
104
+ accountId = undefined;
105
+ }
106
+ if (!accountId || accountId === 'unknown') {
107
+ throw new fsm_dynamo_1.DyFM_Error({
108
+ status: 401,
109
+ errorCode: `${global_settings_const_1.DyNTS_global_settings.systemShortCodeName}|DyNTS-NTF-AU0`,
110
+ message: 'Notifications: the request has no account.',
111
+ userMessage: 'Please sign in to see your notifications.',
112
+ });
113
+ }
114
+ return accountId;
115
+ }
116
+ }
117
+ exports.DyNTS_Notifications_Controller = DyNTS_Notifications_Controller;
118
+ //# sourceMappingURL=notifications.controller.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"notifications.controller.js","sourceRoot":"","sources":["../../../src/_modules/notifications/notifications.controller.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@futdevpro/nts-dynamo",
3
- "version": "01.15.362",
3
+ "version": "01.15.363",
4
4
  "description": "Dynamic NodeTS (NodeJS-Typescript), MongoDB Backend System Framework by Future Development Program Ltd.",
5
5
  "DyBu_settings": {
6
6
  "packageType": "server-package",
@@ -332,6 +332,12 @@
332
332
  "module": "./build/_modules/git-sync/index.js",
333
333
  "types": "./build/_modules/git-sync/index.d.ts",
334
334
  "typings": "./build/_modules/git-sync/index.d.ts"
335
+ },
336
+ "./notifications": {
337
+ "default": "./build/_modules/notifications/index.js",
338
+ "module": "./build/_modules/notifications/index.js",
339
+ "types": "./build/_modules/notifications/index.d.ts",
340
+ "typings": "./build/_modules/notifications/index.d.ts"
335
341
  }
336
342
  },
337
343
  "typesVersions": {
@@ -461,6 +467,9 @@
461
467
  ],
462
468
  "git-sync": [
463
469
  "build/_modules/git-sync/index.d.ts"
470
+ ],
471
+ "notifications": [
472
+ "build/_modules/notifications/index.d.ts"
464
473
  ]
465
474
  }
466
475
  },