@warlock.js/notifications 4.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (111) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/LICENSE +21 -0
  3. package/README.md +57 -0
  4. package/cjs/index.cjs +1034 -0
  5. package/cjs/index.cjs.map +1 -0
  6. package/esm/channels/database-channel.d.mts +10 -0
  7. package/esm/channels/database-channel.d.mts.map +1 -0
  8. package/esm/channels/database-channel.mjs +19 -0
  9. package/esm/channels/database-channel.mjs.map +1 -0
  10. package/esm/channels/index.d.mts +2 -0
  11. package/esm/channels/index.mjs +4 -0
  12. package/esm/channels/mail-channel.d.mts +11 -0
  13. package/esm/channels/mail-channel.d.mts.map +1 -0
  14. package/esm/channels/mail-channel.mjs +43 -0
  15. package/esm/channels/mail-channel.mjs.map +1 -0
  16. package/esm/config.d.mts +46 -0
  17. package/esm/config.d.mts.map +1 -0
  18. package/esm/config.mjs +32 -0
  19. package/esm/config.mjs.map +1 -0
  20. package/esm/contracts/channel.contract.d.mts +38 -0
  21. package/esm/contracts/channel.contract.d.mts.map +1 -0
  22. package/esm/contracts/index.d.mts +5 -0
  23. package/esm/contracts/notification.contract.d.mts +18 -0
  24. package/esm/contracts/notification.contract.d.mts.map +1 -0
  25. package/esm/contracts/preference-provider.contract.d.mts +20 -0
  26. package/esm/contracts/preference-provider.contract.d.mts.map +1 -0
  27. package/esm/contracts/queue-dispatcher.contract.d.mts +23 -0
  28. package/esm/contracts/queue-dispatcher.contract.d.mts.map +1 -0
  29. package/esm/contracts/rate-limiter.contract.d.mts +23 -0
  30. package/esm/contracts/rate-limiter.contract.d.mts.map +1 -0
  31. package/esm/dispatch/define-channel.d.mts +37 -0
  32. package/esm/dispatch/define-channel.d.mts.map +1 -0
  33. package/esm/dispatch/define-channel.mjs +39 -0
  34. package/esm/dispatch/define-channel.mjs.map +1 -0
  35. package/esm/dispatch/define-notification.d.mts +22 -0
  36. package/esm/dispatch/define-notification.d.mts.map +1 -0
  37. package/esm/dispatch/define-notification.mjs +125 -0
  38. package/esm/dispatch/define-notification.mjs.map +1 -0
  39. package/esm/dispatch/dispatch-id.mjs +15 -0
  40. package/esm/dispatch/dispatch-id.mjs.map +1 -0
  41. package/esm/dispatch/dispatcher.mjs +120 -0
  42. package/esm/dispatch/dispatcher.mjs.map +1 -0
  43. package/esm/dispatch/index.d.mts +4 -0
  44. package/esm/dispatch/index.mjs +6 -0
  45. package/esm/dispatch/notifications-event-bus.d.mts +18 -0
  46. package/esm/dispatch/notifications-event-bus.d.mts.map +1 -0
  47. package/esm/dispatch/notifications-event-bus.mjs +58 -0
  48. package/esm/dispatch/notifications-event-bus.mjs.map +1 -0
  49. package/esm/dispatch/notify.d.mts +20 -0
  50. package/esm/dispatch/notify.d.mts.map +1 -0
  51. package/esm/dispatch/notify.mjs +80 -0
  52. package/esm/dispatch/notify.mjs.map +1 -0
  53. package/esm/errors.d.mts +32 -0
  54. package/esm/errors.d.mts.map +1 -0
  55. package/esm/errors.mjs +56 -0
  56. package/esm/errors.mjs.map +1 -0
  57. package/esm/in-app/base-notifications-repository.d.mts +82 -0
  58. package/esm/in-app/base-notifications-repository.d.mts.map +1 -0
  59. package/esm/in-app/base-notifications-repository.mjs +187 -0
  60. package/esm/in-app/base-notifications-repository.mjs.map +1 -0
  61. package/esm/in-app/column-map.d.mts +65 -0
  62. package/esm/in-app/column-map.d.mts.map +1 -0
  63. package/esm/in-app/column-map.mjs +28 -0
  64. package/esm/in-app/column-map.mjs.map +1 -0
  65. package/esm/in-app/database-notification.d.mts +30 -0
  66. package/esm/in-app/database-notification.d.mts.map +1 -0
  67. package/esm/in-app/database-notification.mjs +70 -0
  68. package/esm/in-app/database-notification.mjs.map +1 -0
  69. package/esm/in-app/in-app.d.mts +56 -0
  70. package/esm/in-app/in-app.d.mts.map +1 -0
  71. package/esm/in-app/in-app.mjs +67 -0
  72. package/esm/in-app/in-app.mjs.map +1 -0
  73. package/esm/in-app/index.d.mts +4 -0
  74. package/esm/in-app/index.mjs +6 -0
  75. package/esm/index.d.mts +25 -0
  76. package/esm/index.mjs +23 -0
  77. package/esm/migration/index.d.mts +1 -0
  78. package/esm/migration/index.mjs +3 -0
  79. package/esm/migration/notification-columns.d.mts +13 -0
  80. package/esm/migration/notification-columns.d.mts.map +1 -0
  81. package/esm/migration/notification-columns.mjs +53 -0
  82. package/esm/migration/notification-columns.mjs.map +1 -0
  83. package/esm/queue/herald-queue.d.mts +10 -0
  84. package/esm/queue/herald-queue.d.mts.map +1 -0
  85. package/esm/queue/herald-queue.mjs +14 -0
  86. package/esm/queue/herald-queue.mjs.map +1 -0
  87. package/esm/queue/index.d.mts +3 -0
  88. package/esm/queue/index.mjs +5 -0
  89. package/esm/queue/load-herald.d.mts +6 -0
  90. package/esm/queue/load-herald.d.mts.map +1 -0
  91. package/esm/queue/load-herald.mjs +35 -0
  92. package/esm/queue/load-herald.mjs.map +1 -0
  93. package/esm/queue/notifications-worker.d.mts +9 -0
  94. package/esm/queue/notifications-worker.d.mts.map +1 -0
  95. package/esm/queue/notifications-worker.mjs +46 -0
  96. package/esm/queue/notifications-worker.mjs.map +1 -0
  97. package/esm/types.d.mts +130 -0
  98. package/esm/types.d.mts.map +1 -0
  99. package/llms-full.txt +994 -0
  100. package/llms.txt +17 -0
  101. package/package.json +40 -0
  102. package/skills/README.md +23 -0
  103. package/skills/configure-notifications/SKILL.md +138 -0
  104. package/skills/define-channel/SKILL.md +107 -0
  105. package/skills/define-notification/SKILL.md +133 -0
  106. package/skills/notifications-basics/SKILL.md +94 -0
  107. package/skills/observe-notifications/SKILL.md +66 -0
  108. package/skills/queue-notifications/SKILL.md +81 -0
  109. package/skills/send-ad-hoc/SKILL.md +95 -0
  110. package/skills/use-in-app/SKILL.md +168 -0
  111. package/skills/write-notification-migration/SKILL.md +70 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"in-app.d.mts","names":[],"sources":["../../../../../../../@warlock.js/notifications/src/in-app/in-app.ts"],"mappings":";;;;;;;;;;;;KA4CY,wBAAA,GAA2B,+BAA+B,CAAC,mBAAA;;;;;;KAO3D,gBAAA;EACN,KAAA,EAAO,sBAAA;EAAwB,UAAA;AAAA;EAC/B,UAAA,EAAY,2BAA2B;EAAE,KAAA;AAAA;AAAA,cAKzC,KAAA;EAAA,QACI,IAAA;EA0D+B;;;;EApDhC,SAAA,CAAU,OAAA,EAAS,gBAAA,GAAmB,OAAA,CAAQ,eAAA;EAAA,YAQzC,UAAA;EARL;EAmBA,IAAA,CAAK,SAAA,EAAW,UAAA,GAAa,EAAA,EAAI,OAAA,GAAU,wBAAA;EAnBjC;EAwBV,UAAA,CAAW,SAAA,EAAW,UAAA,GAAa,EAAA,EAAI,OAAA,GAAU,wBAAA;EAxBH;;;;EAgC9C,WAAA,CAAY,SAAA,EAAW,UAAA,GAAa,EAAA;EAb/B;EAkBL,IAAA,CAAK,SAAA,EAAW,UAAA,GAAa,EAAA,EAAI,EAAA,EAAI,EAAA,GAAE,OAAA,CAAA,oBAAA;EAlBN;EAuBjC,UAAA,CAAW,SAAA,EAAW,UAAA,GAAa,EAAA,EAAI,EAAA,GAAK,EAAA,GAAE,OAAA;EAlBxB;EAuBtB,YAAA,CAAa,SAAA,EAAW,UAAA,GAAa,EAAA,EAAI,EAAA,GAAK,EAAA,GAAE,OAAA;EAvBrC;EA4BX,OAAA,CAAQ,SAAA,EAAW,UAAA,GAAa,EAAA,EAAI,EAAA,GAAK,EAAA,GAAE,OAAA;AAAA;;cAMvC,KAAA,EAAK,KAAc"}
@@ -0,0 +1,67 @@
1
+ import { databaseChannel } from "../channels/database-channel.mjs";
2
+ import { BaseNotificationsRepository } from "./base-notifications-repository.mjs";
3
+
4
+ //#region ../@warlock.js/notifications/src/in-app/in-app.ts
5
+ const idOf = (recipient) => typeof recipient === "object" ? recipient.id : recipient;
6
+ var InApp = class {
7
+ repo;
8
+ /**
9
+ * Bind the in-app store AND return the `database` channel. Called once
10
+ * from `config/notifications.ts`. Subsequent calls REPLACE the binding.
11
+ */
12
+ configure(options) {
13
+ this.repo = "repository" in options && options.repository ? options.repository : new BaseNotificationsRepository(options.model);
14
+ return databaseChannel(this.repo);
15
+ }
16
+ get repository() {
17
+ if (!this.repo) throw new Error("In-app notifications not configured — add `database: inApp.configure({ model: Notification })` to config/notifications.ts");
18
+ return this.repo;
19
+ }
20
+ /** General list — scoped to recipient; pass `options` for paging + filters. */
21
+ list(recipient, options) {
22
+ return this.repository.list({
23
+ ...options,
24
+ recipientId: idOf(recipient)
25
+ });
26
+ }
27
+ /** Unread-only list — common case for badges + dashboards. */
28
+ listUnread(recipient, options) {
29
+ return this.repository.list({
30
+ ...options,
31
+ recipientId: idOf(recipient),
32
+ unread: true
33
+ });
34
+ }
35
+ /**
36
+ * Cached unread count — backs the badge. Auto-invalidated by the repo's
37
+ * model create/update events (`RepositoryManager.registerEvents`).
38
+ */
39
+ countUnread(recipient) {
40
+ return this.repository.countCached({
41
+ recipientId: idOf(recipient),
42
+ unread: true
43
+ });
44
+ }
45
+ /** Find one notification for a recipient — for a detail view. */
46
+ find(recipient, id) {
47
+ return this.repository.findFor(idOf(recipient), id);
48
+ }
49
+ /** Mark read — `id` omitted = all unread for this recipient. */
50
+ markAsRead(recipient, id) {
51
+ return this.repository.markRead(idOf(recipient), id);
52
+ }
53
+ /** Mark unread — same recipient-scoping. */
54
+ markAsUnread(recipient, id) {
55
+ return this.repository.markUnread(idOf(recipient), id);
56
+ }
57
+ /** Delete/dismiss — `id` omitted = clear all for this recipient. */
58
+ dismiss(recipient, id) {
59
+ return this.repository.deleteFor(idOf(recipient), id);
60
+ }
61
+ };
62
+ /** Single in-app facade — bound at boot via `inApp.configure(...)`. */
63
+ const inApp = new InApp();
64
+
65
+ //#endregion
66
+ export { inApp };
67
+ //# sourceMappingURL=in-app.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"in-app.mjs","names":[],"sources":["../../../../../../../@warlock.js/notifications/src/in-app/in-app.ts"],"sourcesContent":["/**\n * `inApp` — app-facing facade for the in-app/database channel.\n *\n * `inApp.configure({ model | repository })` binds the in-app store AND\n * returns the `database` channel — so the read side and the dispatched\n * write side share ONE repository instance.\n * • `{ model: Notification }` — 90% case; default repo built internally.\n * • `{ repository: myRepo }` — 10% case; custom column mapping / extras.\n *\n * No zero-arg form: the package ships no concrete model/table (thin eject),\n * so the dev MUST tell us which model to use.\n *\n * Every read + mutation is RECIPIENT-SCOPED by construction — `markAsRead(user,\n * id)` cannot flip a row belonging to a different recipient even if handed a\n * foreign id (the recipient id is forced into the filter → 0 rows match).\n *\n * @example\n * // src/config/notifications.ts:\n * inApp.configure({ model: Notification }),\n *\n * // anywhere:\n * const unread = await inApp.listUnread(user);\n * const badge = await inApp.countUnread(user);\n * const one = await inApp.find(user, \"ntf_123\");\n * await inApp.markAsRead(user, \"ntf_123\");\n * await inApp.markAsRead(user); // mark ALL unread\n * await inApp.dismiss(user, \"ntf_123\"); // delete one\n * await inApp.dismiss(user); // clear all for this recipient\n */\nimport type { TypedRepositoryOptionsWithPages } from \"@warlock.js/core\";\nimport { databaseChannel } from \"../channels/database-channel\";\nimport type { Channel } from \"../contracts\";\nimport type { DatabasePayload, Id, Notifiable } from \"../types\";\nimport {\n BaseNotificationsRepository,\n type NotificationModelClass,\n type NotificationsFilter,\n} from \"./base-notifications-repository\";\n\n/**\n * List options for the read methods — the repository's paginated options\n * (`page` / `limit` / `orderBy` / filter keys). `recipientId` is always forced\n * from the recipient argument, so passing it here has no effect.\n */\nexport type NotificationsListOptions = TypedRepositoryOptionsWithPages<NotificationsFilter>;\n\n/**\n * Configure options — discriminated so `model` and `repository` are mutually\n * exclusive at the type level. Future knobs (cache / realtime / naming) slot\n * in here without breaking the signature.\n */\nexport type ConfigureOptions =\n | { model: NotificationModelClass; repository?: never }\n | { repository: BaseNotificationsRepository; model?: never };\n\nconst idOf = (recipient: Notifiable | Id): Id =>\n typeof recipient === \"object\" ? recipient.id : recipient;\n\nclass InApp {\n private repo?: BaseNotificationsRepository;\n\n /**\n * Bind the in-app store AND return the `database` channel. Called once\n * from `config/notifications.ts`. Subsequent calls REPLACE the binding.\n */\n public configure(options: ConfigureOptions): Channel<DatabasePayload> {\n this.repo =\n \"repository\" in options && options.repository\n ? options.repository\n : new BaseNotificationsRepository(options.model);\n return databaseChannel(this.repo);\n }\n\n private get repository(): BaseNotificationsRepository {\n if (!this.repo) {\n throw new Error(\n \"In-app notifications not configured — add \" +\n \"`database: inApp.configure({ model: Notification })` to config/notifications.ts\",\n );\n }\n return this.repo;\n }\n\n /** General list — scoped to recipient; pass `options` for paging + filters. */\n public list(recipient: Notifiable | Id, options?: NotificationsListOptions) {\n return this.repository.list({ ...options, recipientId: idOf(recipient) });\n }\n\n /** Unread-only list — common case for badges + dashboards. */\n public listUnread(recipient: Notifiable | Id, options?: NotificationsListOptions) {\n return this.repository.list({ ...options, recipientId: idOf(recipient), unread: true });\n }\n\n /**\n * Cached unread count — backs the badge. Auto-invalidated by the repo's\n * model create/update events (`RepositoryManager.registerEvents`).\n */\n public countUnread(recipient: Notifiable | Id) {\n return this.repository.countCached({ recipientId: idOf(recipient), unread: true });\n }\n\n /** Find one notification for a recipient — for a detail view. */\n public find(recipient: Notifiable | Id, id: Id) {\n return this.repository.findFor(idOf(recipient), id);\n }\n\n /** Mark read — `id` omitted = all unread for this recipient. */\n public markAsRead(recipient: Notifiable | Id, id?: Id) {\n return this.repository.markRead(idOf(recipient), id);\n }\n\n /** Mark unread — same recipient-scoping. */\n public markAsUnread(recipient: Notifiable | Id, id?: Id) {\n return this.repository.markUnread(idOf(recipient), id);\n }\n\n /** Delete/dismiss — `id` omitted = clear all for this recipient. */\n public dismiss(recipient: Notifiable | Id, id?: Id) {\n return this.repository.deleteFor(idOf(recipient), id);\n }\n}\n\n/** Single in-app facade — bound at boot via `inApp.configure(...)`. */\nexport const inApp = new InApp();\n"],"mappings":";;;;AAuDA,MAAM,QAAQ,cACZ,OAAO,cAAc,WAAW,UAAU,KAAK;AAEjD,IAAM,QAAN,MAAY;CACV,AAAQ;;;;;CAMR,AAAO,UAAU,SAAqD;EACpE,KAAK,OACH,gBAAgB,WAAW,QAAQ,aAC/B,QAAQ,aACR,IAAI,4BAA4B,QAAQ,KAAK;EACnD,OAAO,gBAAgB,KAAK,IAAI;CAClC;CAEA,IAAY,aAA0C;EACpD,IAAI,CAAC,KAAK,MACR,MAAM,IAAI,MACR,2HAEF;EAEF,OAAO,KAAK;CACd;;CAGA,AAAO,KAAK,WAA4B,SAAoC;EAC1E,OAAO,KAAK,WAAW,KAAK;GAAE,GAAG;GAAS,aAAa,KAAK,SAAS;EAAE,CAAC;CAC1E;;CAGA,AAAO,WAAW,WAA4B,SAAoC;EAChF,OAAO,KAAK,WAAW,KAAK;GAAE,GAAG;GAAS,aAAa,KAAK,SAAS;GAAG,QAAQ;EAAK,CAAC;CACxF;;;;;CAMA,AAAO,YAAY,WAA4B;EAC7C,OAAO,KAAK,WAAW,YAAY;GAAE,aAAa,KAAK,SAAS;GAAG,QAAQ;EAAK,CAAC;CACnF;;CAGA,AAAO,KAAK,WAA4B,IAAQ;EAC9C,OAAO,KAAK,WAAW,QAAQ,KAAK,SAAS,GAAG,EAAE;CACpD;;CAGA,AAAO,WAAW,WAA4B,IAAS;EACrD,OAAO,KAAK,WAAW,SAAS,KAAK,SAAS,GAAG,EAAE;CACrD;;CAGA,AAAO,aAAa,WAA4B,IAAS;EACvD,OAAO,KAAK,WAAW,WAAW,KAAK,SAAS,GAAG,EAAE;CACvD;;CAGA,AAAO,QAAQ,WAA4B,IAAS;EAClD,OAAO,KAAK,WAAW,UAAU,KAAK,SAAS,GAAG,EAAE;CACtD;AACF;;AAGA,MAAa,QAAQ,IAAI,MAAM"}
@@ -0,0 +1,4 @@
1
+ import { NotificationColumnMap, NotificationColumnMapHost, ResolvedNotificationColumnMap, resolveColumnMap } from "./column-map.mjs";
2
+ import { DatabaseNotification } from "./database-notification.mjs";
3
+ import { BaseNotificationsRepository, NotificationModelClass, NotificationsFilter } from "./base-notifications-repository.mjs";
4
+ import { ConfigureOptions, NotificationsListOptions, inApp } from "./in-app.mjs";
@@ -0,0 +1,6 @@
1
+ import { resolveColumnMap } from "./column-map.mjs";
2
+ import { BaseNotificationsRepository } from "./base-notifications-repository.mjs";
3
+ import { DatabaseNotification } from "./database-notification.mjs";
4
+ import { inApp } from "./in-app.mjs";
5
+
6
+ export { };
@@ -0,0 +1,25 @@
1
+ import { ChannelName, DatabasePayload, Id, MailPayload, Notifiable, NotificationChannels, NotificationEvents, NotificationInput, RenderContext, SendOptions } from "./types.mjs";
2
+ import { Channel } from "./contracts/channel.contract.mjs";
3
+ import { NotificationContract } from "./contracts/notification.contract.mjs";
4
+ import { PreferenceProvider } from "./contracts/preference-provider.contract.mjs";
5
+ import { QueueDispatcher } from "./contracts/queue-dispatcher.contract.mjs";
6
+ import { RateLimiter } from "./contracts/rate-limiter.contract.mjs";
7
+ import { ChannelNotFoundError, MissingRendererError, NoQueueDispatcherError, NotificationsNotConfiguredError, UnresolvableRouteError } from "./errors.mjs";
8
+ import { ChannelMap, NotificationConfig, getNotificationConfig, resetNotificationConfig, setNotificationConfig } from "./config.mjs";
9
+ import { defineChannel } from "./dispatch/define-channel.mjs";
10
+ import { DefinedNotification, NotificationDef, defineNotification } from "./dispatch/define-notification.mjs";
11
+ import { Notify, notify } from "./dispatch/notify.mjs";
12
+ import { notifications } from "./dispatch/notifications-event-bus.mjs";
13
+ import { NotificationColumnMap, NotificationColumnMapHost, ResolvedNotificationColumnMap, resolveColumnMap } from "./in-app/column-map.mjs";
14
+ import { DatabaseNotification } from "./in-app/database-notification.mjs";
15
+ import { BaseNotificationsRepository, NotificationModelClass, NotificationsFilter } from "./in-app/base-notifications-repository.mjs";
16
+ import { databaseChannel } from "./channels/database-channel.mjs";
17
+ import { MailChannelConfig, mailChannel } from "./channels/mail-channel.mjs";
18
+ import { ConfigureOptions, NotificationsListOptions, inApp } from "./in-app/in-app.mjs";
19
+ import { notificationColumns } from "./migration/notification-columns.mjs";
20
+ import { HeraldQueueOptions, heraldQueue } from "./queue/herald-queue.mjs";
21
+ import { WorkerOptions, startNotificationsWorker } from "./queue/notifications-worker.mjs";
22
+ import { DEFAULT_QUEUE_CHANNEL } from "./queue/load-herald.mjs";
23
+ export { BaseNotificationsRepository, Channel, ChannelMap, ChannelName, ChannelNotFoundError, ConfigureOptions, DEFAULT_QUEUE_CHANNEL, DatabaseNotification, DatabasePayload, DefinedNotification, HeraldQueueOptions, Id, MailChannelConfig, MailPayload, MissingRendererError, NoQueueDispatcherError, Notifiable, NotificationChannels, NotificationColumnMap, NotificationColumnMapHost, NotificationConfig, NotificationContract, NotificationDef, NotificationEvents, NotificationInput, NotificationModelClass, NotificationsFilter, NotificationsListOptions, NotificationsNotConfiguredError, Notify, PreferenceProvider, QueueDispatcher, RateLimiter, RenderContext, ResolvedNotificationColumnMap, SendOptions, UnresolvableRouteError, WorkerOptions, databaseChannel, defineChannel, defineNotification, getNotificationConfig, heraldQueue, inApp, mailChannel, notificationColumns, notifications, notify, resetNotificationConfig, resolveColumnMap, setNotificationConfig, startNotificationsWorker };
24
+ import "./dispatch/define-channel.mjs";
25
+ import "./types.mjs";
package/esm/index.mjs ADDED
@@ -0,0 +1,23 @@
1
+ import { ChannelNotFoundError, MissingRendererError, NoQueueDispatcherError, NotificationsNotConfiguredError, UnresolvableRouteError } from "./errors.mjs";
2
+ import { getNotificationConfig, resetNotificationConfig, setNotificationConfig } from "./config.mjs";
3
+ import { defineChannel } from "./dispatch/define-channel.mjs";
4
+ import { notifications } from "./dispatch/notifications-event-bus.mjs";
5
+ import { defineNotification } from "./dispatch/define-notification.mjs";
6
+ import { notify } from "./dispatch/notify.mjs";
7
+ import "./dispatch/index.mjs";
8
+ import { databaseChannel } from "./channels/database-channel.mjs";
9
+ import { mailChannel } from "./channels/mail-channel.mjs";
10
+ import "./channels/index.mjs";
11
+ import { resolveColumnMap } from "./in-app/column-map.mjs";
12
+ import { BaseNotificationsRepository } from "./in-app/base-notifications-repository.mjs";
13
+ import { DatabaseNotification } from "./in-app/database-notification.mjs";
14
+ import { inApp } from "./in-app/in-app.mjs";
15
+ import "./in-app/index.mjs";
16
+ import { notificationColumns } from "./migration/notification-columns.mjs";
17
+ import "./migration/index.mjs";
18
+ import { DEFAULT_QUEUE_CHANNEL } from "./queue/load-herald.mjs";
19
+ import { heraldQueue } from "./queue/herald-queue.mjs";
20
+ import { startNotificationsWorker } from "./queue/notifications-worker.mjs";
21
+ import "./queue/index.mjs";
22
+
23
+ export { BaseNotificationsRepository, ChannelNotFoundError, DEFAULT_QUEUE_CHANNEL, DatabaseNotification, MissingRendererError, NoQueueDispatcherError, NotificationsNotConfiguredError, UnresolvableRouteError, databaseChannel, defineChannel, defineNotification, getNotificationConfig, heraldQueue, inApp, mailChannel, notificationColumns, notifications, notify, resetNotificationConfig, resolveColumnMap, setNotificationConfig, startNotificationsWorker };
@@ -0,0 +1 @@
1
+ import { notificationColumns } from "./notification-columns.mjs";
@@ -0,0 +1,3 @@
1
+ import { notificationColumns } from "./notification-columns.mjs";
2
+
3
+ export { };
@@ -0,0 +1,13 @@
1
+ import { NotificationColumnMapHost } from "../in-app/column-map.mjs";
2
+ import { ColumnMap } from "@warlock.js/cascade";
3
+
4
+ //#region ../@warlock.js/notifications/src/migration/notification-columns.d.ts
5
+ /**
6
+ * Returns the column map for a notification table, named from the model's
7
+ * `columnMap`. Without a model, falls back to the resolved defaults
8
+ * (`user_id` recipient + `read_at` read-state).
9
+ */
10
+ declare function notificationColumns(model?: NotificationColumnMapHost): ColumnMap;
11
+ //#endregion
12
+ export { notificationColumns };
13
+ //# sourceMappingURL=notification-columns.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"notification-columns.d.mts","names":[],"sources":["../../../../../../../@warlock.js/notifications/src/migration/notification-columns.ts"],"mappings":";;;;;;;;;iBAiCgB,mBAAA,CAAoB,KAAA,GAAQ,yBAAA,GAA4B,SAAS"}
@@ -0,0 +1,53 @@
1
+ import { resolveColumnMap } from "../in-app/column-map.mjs";
2
+ import { boolCol, json, string, text, timestamp, uuid } from "@warlock.js/cascade";
3
+
4
+ //#region ../@warlock.js/notifications/src/migration/notification-columns.ts
5
+ /**
6
+ * Column factory for the notifications table, driven by the model's
7
+ * `columnMap`. The recipient / read-state / tenant columns take the NAMES the
8
+ * model declares, so the table, the repository, and the model accessors all
9
+ * agree by construction. The stable lexical columns (`type`, `title`, `body`,
10
+ * `payload`, `idempotency_key`) keep fixed names.
11
+ *
12
+ * Read-state follows the map's presence rules (see `NotificationColumnMap`):
13
+ * `readAt` → a nullable timestamp, `isRead` → an indexed boolean, both → both.
14
+ *
15
+ * Spread + extend for app-specific extras (FK references, composite indexes).
16
+ *
17
+ * @example
18
+ * import { Migration } from "@warlock.js/cascade";
19
+ * import { notificationColumns } from "@warlock.js/notifications";
20
+ * import { Notification } from "../notification.model";
21
+ *
22
+ * export default Migration.create(Notification, notificationColumns(Notification));
23
+ *
24
+ * @example // with extras (multi-tenant FK)
25
+ * export default Migration.create(Notification, {
26
+ * ...notificationColumns(Notification),
27
+ * organization_id: uuid().references(Organization.table).notNullable(),
28
+ * });
29
+ */
30
+ /**
31
+ * Returns the column map for a notification table, named from the model's
32
+ * `columnMap`. Without a model, falls back to the resolved defaults
33
+ * (`user_id` recipient + `read_at` read-state).
34
+ */
35
+ function notificationColumns(model) {
36
+ const { recipient, tenant, readAt, isRead } = resolveColumnMap(model?.columnMap);
37
+ const columns = {
38
+ [recipient]: uuid().index().notNullable(),
39
+ type: string().index().notNullable(),
40
+ title: string().notNullable(),
41
+ body: text().nullable(),
42
+ payload: json().nullable()
43
+ };
44
+ if (readAt) columns[readAt] = timestamp().nullable();
45
+ if (isRead) columns[isRead] = boolCol().default(false).index();
46
+ if (tenant) columns[tenant] = uuid().index().notNullable();
47
+ columns.idempotency_key = string().nullable().unique();
48
+ return columns;
49
+ }
50
+
51
+ //#endregion
52
+ export { notificationColumns };
53
+ //# sourceMappingURL=notification-columns.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"notification-columns.mjs","names":[],"sources":["../../../../../../../@warlock.js/notifications/src/migration/notification-columns.ts"],"sourcesContent":["/**\n * Column factory for the notifications table, driven by the model's\n * `columnMap`. The recipient / read-state / tenant columns take the NAMES the\n * model declares, so the table, the repository, and the model accessors all\n * agree by construction. The stable lexical columns (`type`, `title`, `body`,\n * `payload`, `idempotency_key`) keep fixed names.\n *\n * Read-state follows the map's presence rules (see `NotificationColumnMap`):\n * `readAt` → a nullable timestamp, `isRead` → an indexed boolean, both → both.\n *\n * Spread + extend for app-specific extras (FK references, composite indexes).\n *\n * @example\n * import { Migration } from \"@warlock.js/cascade\";\n * import { notificationColumns } from \"@warlock.js/notifications\";\n * import { Notification } from \"../notification.model\";\n *\n * export default Migration.create(Notification, notificationColumns(Notification));\n *\n * @example // with extras (multi-tenant FK)\n * export default Migration.create(Notification, {\n * ...notificationColumns(Notification),\n * organization_id: uuid().references(Organization.table).notNullable(),\n * });\n */\nimport { boolCol, type ColumnMap, json, string, text, timestamp, uuid } from \"@warlock.js/cascade\";\nimport { type NotificationColumnMapHost, resolveColumnMap } from \"../in-app/column-map\";\n\n/**\n * Returns the column map for a notification table, named from the model's\n * `columnMap`. Without a model, falls back to the resolved defaults\n * (`user_id` recipient + `read_at` read-state).\n */\nexport function notificationColumns(model?: NotificationColumnMapHost): ColumnMap {\n const { recipient, tenant, readAt, isRead } = resolveColumnMap(model?.columnMap);\n\n const columns: ColumnMap = {\n [recipient]: uuid().index().notNullable(),\n type: string().index().notNullable(),\n title: string().notNullable(),\n body: text().nullable(),\n payload: json().nullable(),\n };\n\n if (readAt) {\n columns[readAt] = timestamp().nullable();\n }\n\n if (isRead) {\n columns[isRead] = boolCol().default(false).index();\n }\n\n if (tenant) {\n columns[tenant] = uuid().index().notNullable();\n }\n\n // Unique so a retried send can't insert a duplicate. Nullable: most rows\n // have no key (NULLs are distinct in a unique index). `createFor` does\n // find-or-create; this constraint is the race backstop.\n columns.idempotency_key = string().nullable().unique();\n\n return columns;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAiCA,SAAgB,oBAAoB,OAA8C;CAChF,MAAM,EAAE,WAAW,QAAQ,QAAQ,WAAW,iBAAiB,OAAO,SAAS;CAE/E,MAAM,UAAqB;GACxB,YAAY,KAAK,CAAC,CAAC,MAAM,CAAC,CAAC,YAAY;EACxC,MAAM,OAAO,CAAC,CAAC,MAAM,CAAC,CAAC,YAAY;EACnC,OAAO,OAAO,CAAC,CAAC,YAAY;EAC5B,MAAM,KAAK,CAAC,CAAC,SAAS;EACtB,SAAS,KAAK,CAAC,CAAC,SAAS;CAC3B;CAEA,IAAI,QACF,QAAQ,UAAU,UAAU,CAAC,CAAC,SAAS;CAGzC,IAAI,QACF,QAAQ,UAAU,QAAQ,CAAC,CAAC,QAAQ,KAAK,CAAC,CAAC,MAAM;CAGnD,IAAI,QACF,QAAQ,UAAU,KAAK,CAAC,CAAC,MAAM,CAAC,CAAC,YAAY;CAM/C,QAAQ,kBAAkB,OAAO,CAAC,CAAC,SAAS,CAAC,CAAC,OAAO;CAErD,OAAO;AACT"}
@@ -0,0 +1,10 @@
1
+ import { QueueDispatcher } from "../contracts/queue-dispatcher.contract.mjs";
2
+ //#region ../@warlock.js/notifications/src/queue/herald-queue.d.ts
3
+ type HeraldQueueOptions = {
4
+ /** Herald channel to publish jobs to. Default `"notifications.dispatch"`. */channel?: string; /** Herald broker name (multi-broker setups). Default broker if omitted. */
5
+ broker?: string;
6
+ };
7
+ declare function heraldQueue(options?: HeraldQueueOptions): QueueDispatcher;
8
+ //#endregion
9
+ export { HeraldQueueOptions, heraldQueue };
10
+ //# sourceMappingURL=herald-queue.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"herald-queue.d.mts","names":[],"sources":["../../../../../../../@warlock.js/notifications/src/queue/herald-queue.ts"],"mappings":";;KAyBY,kBAAA;+EAEV,OAAA;EAEA,MAAM;AAAA;AAAA,iBAGQ,WAAA,CAAY,OAAA,GAAS,kBAAA,GAA0B,eAAe"}
@@ -0,0 +1,14 @@
1
+ import { DEFAULT_QUEUE_CHANNEL, loadHerald } from "./load-herald.mjs";
2
+
3
+ //#region ../@warlock.js/notifications/src/queue/herald-queue.ts
4
+ function heraldQueue(options = {}) {
5
+ const channelName = options.channel ?? "notifications.dispatch";
6
+ return { async dispatch(job) {
7
+ const { herald } = await loadHerald();
8
+ await herald(options.broker).channel(channelName).publish(job);
9
+ } };
10
+ }
11
+
12
+ //#endregion
13
+ export { heraldQueue };
14
+ //# sourceMappingURL=herald-queue.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"herald-queue.mjs","names":[],"sources":["../../../../../../../@warlock.js/notifications/src/queue/herald-queue.ts"],"sourcesContent":["/**\n * Herald-backed `QueueDispatcher` — the production backend for `.queue()`.\n *\n * `defineNotification` renders payloads + resolves routes BEFORE handing a job\n * to the dispatcher, so the job is fully serializable (`{ channel, route,\n * payload, options }`) — no model re-hydration or closure serialization. This\n * dispatcher just publishes the job onto a herald channel; the worker\n * (`startNotificationsWorker`) consumes it and runs `channel.send`.\n *\n * Connection is the app's job — the `@warlock.js/herald` connector connects the\n * broker from `config/herald.ts` at boot; this dispatcher only calls `herald()`.\n *\n * @example src/config/notifications.ts — declarative; the connector registers it\n * import { type NotificationConfig, heraldQueue, inApp, mailChannel } from \"@warlock.js/notifications\";\n *\n * const config: NotificationConfig = {\n * channels: { mail: mailChannel(), database: inApp.configure({ model: Notification }) },\n * queue: heraldQueue(), // → `.queue()` now works\n * };\n *\n * export default config;\n */\nimport type { QueueDispatcher } from \"../contracts\";\nimport { DEFAULT_QUEUE_CHANNEL, loadHerald } from \"./load-herald\";\n\nexport type HeraldQueueOptions = {\n /** Herald channel to publish jobs to. Default `\"notifications.dispatch\"`. */\n channel?: string;\n /** Herald broker name (multi-broker setups). Default broker if omitted. */\n broker?: string;\n};\n\nexport function heraldQueue(options: HeraldQueueOptions = {}): QueueDispatcher {\n const channelName = options.channel ?? DEFAULT_QUEUE_CHANNEL;\n\n return {\n async dispatch(job) {\n const { herald } = await loadHerald();\n await herald(options.broker).channel(channelName).publish(job);\n },\n };\n}\n"],"mappings":";;;AAgCA,SAAgB,YAAY,UAA8B,CAAC,GAAoB;CAC7E,MAAM,cAAc,QAAQ;CAE5B,OAAO,EACL,MAAM,SAAS,KAAK;EAClB,MAAM,EAAE,WAAW,MAAM,WAAW;EACpC,MAAM,OAAO,QAAQ,MAAM,CAAC,CAAC,QAAQ,WAAW,CAAC,CAAC,QAAQ,GAAG;CAC/D,EACF;AACF"}
@@ -0,0 +1,3 @@
1
+ import { HeraldQueueOptions, heraldQueue } from "./herald-queue.mjs";
2
+ import { WorkerOptions, startNotificationsWorker } from "./notifications-worker.mjs";
3
+ import { DEFAULT_QUEUE_CHANNEL } from "./load-herald.mjs";
@@ -0,0 +1,5 @@
1
+ import { DEFAULT_QUEUE_CHANNEL } from "./load-herald.mjs";
2
+ import { heraldQueue } from "./herald-queue.mjs";
3
+ import { startNotificationsWorker } from "./notifications-worker.mjs";
4
+
5
+ export { };
@@ -0,0 +1,6 @@
1
+ //#region ../@warlock.js/notifications/src/queue/load-herald.d.ts
2
+ /** The herald channel notification jobs are published to / consumed from. */
3
+ declare const DEFAULT_QUEUE_CHANNEL = "notifications.dispatch";
4
+ //#endregion
5
+ export { DEFAULT_QUEUE_CHANNEL };
6
+ //# sourceMappingURL=load-herald.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"load-herald.d.mts","names":[],"sources":["../../../../../../../@warlock.js/notifications/src/queue/load-herald.ts"],"mappings":";AAmCA;AAAA,cAAa,qBAAA"}
@@ -0,0 +1,35 @@
1
+ //#region ../@warlock.js/notifications/src/queue/load-herald.ts
2
+ /**
3
+ * Lazily load `@warlock.js/herald` — the optional peer backing `.queue()`.
4
+ *
5
+ * Memoized: the dynamic import runs at most once. A missing package surfaces a
6
+ * curated install message at use time rather than a boot-time resolution error
7
+ * (the lazy-optional-peer pattern used across the framework's drivers).
8
+ */
9
+ let heraldModule;
10
+ const INSTALL_INSTRUCTIONS = `
11
+ The notifications queue requires the @warlock.js/herald package.
12
+ Install it with:
13
+
14
+ npm install @warlock.js/herald
15
+
16
+ Or with your preferred package manager:
17
+
18
+ pnpm add @warlock.js/herald
19
+ yarn add @warlock.js/herald
20
+ `.trim();
21
+ async function loadHerald() {
22
+ if (heraldModule) return heraldModule;
23
+ try {
24
+ heraldModule = await import("@warlock.js/herald");
25
+ return heraldModule;
26
+ } catch {
27
+ throw new Error(INSTALL_INSTRUCTIONS);
28
+ }
29
+ }
30
+ /** The herald channel notification jobs are published to / consumed from. */
31
+ const DEFAULT_QUEUE_CHANNEL = "notifications.dispatch";
32
+
33
+ //#endregion
34
+ export { DEFAULT_QUEUE_CHANNEL, loadHerald };
35
+ //# sourceMappingURL=load-herald.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"load-herald.mjs","names":[],"sources":["../../../../../../../@warlock.js/notifications/src/queue/load-herald.ts"],"sourcesContent":["/**\n * Lazily load `@warlock.js/herald` — the optional peer backing `.queue()`.\n *\n * Memoized: the dynamic import runs at most once. A missing package surfaces a\n * curated install message at use time rather than a boot-time resolution error\n * (the lazy-optional-peer pattern used across the framework's drivers).\n */\nlet heraldModule: typeof import(\"@warlock.js/herald\") | undefined;\n\nconst INSTALL_INSTRUCTIONS = `\nThe notifications queue requires the @warlock.js/herald package.\nInstall it with:\n\n npm install @warlock.js/herald\n\nOr with your preferred package manager:\n\n pnpm add @warlock.js/herald\n yarn add @warlock.js/herald\n`.trim();\n\nexport async function loadHerald(): Promise<typeof import(\"@warlock.js/herald\")> {\n if (heraldModule) {\n return heraldModule;\n }\n\n try {\n heraldModule = await import(\"@warlock.js/herald\");\n return heraldModule;\n } catch {\n throw new Error(INSTALL_INSTRUCTIONS);\n }\n}\n\n/** The herald channel notification jobs are published to / consumed from. */\nexport const DEFAULT_QUEUE_CHANNEL = \"notifications.dispatch\";\n"],"mappings":";;;;;;;;AAOA,IAAI;AAEJ,MAAM,uBAAuB;;;;;;;;;;EAU3B,KAAK;AAEP,eAAsB,aAA2D;CAC/E,IAAI,cACF,OAAO;CAGT,IAAI;EACF,eAAe,MAAM,OAAO;EAC5B,OAAO;CACT,QAAQ;EACN,MAAM,IAAI,MAAM,oBAAoB;CACtC;AACF;;AAGA,MAAa,wBAAwB"}
@@ -0,0 +1,9 @@
1
+ //#region ../@warlock.js/notifications/src/queue/notifications-worker.d.ts
2
+ type WorkerOptions = {
3
+ /** Herald channel to consume from. Must match the dispatcher. Default `"notifications.dispatch"`. */channel?: string; /** Herald broker name. Default broker if omitted. */
4
+ broker?: string;
5
+ };
6
+ declare function startNotificationsWorker(options?: WorkerOptions): Promise<void>;
7
+ //#endregion
8
+ export { WorkerOptions, startNotificationsWorker };
9
+ //# sourceMappingURL=notifications-worker.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"notifications-worker.d.mts","names":[],"sources":["../../../../../../../@warlock.js/notifications/src/queue/notifications-worker.ts"],"mappings":";KAyBY,aAAA;EAAA,qGAEV,OAAA;EAEA,MAAM;AAAA;AAAA,iBAGc,wBAAA,CAAyB,OAAA,GAAS,aAAA,GAAqB,OAAO"}
@@ -0,0 +1,46 @@
1
+ import { getNotificationConfig } from "../config.mjs";
2
+ import { DEFAULT_QUEUE_CHANNEL, loadHerald } from "./load-herald.mjs";
3
+ import { log } from "@warlock.js/logger";
4
+
5
+ //#region ../@warlock.js/notifications/src/queue/notifications-worker.ts
6
+ /**
7
+ * Notification queue worker — consumes the jobs `heraldQueue` publishes and
8
+ * runs the actual `channel.send`. Call this once in a worker process (or the
9
+ * web process) after the notifications config + herald broker are up.
10
+ *
11
+ * The job carries an ALREADY-RENDERED payload + resolved route, so the worker
12
+ * only looks the channel up by name and dispatches — no notifiable needed.
13
+ *
14
+ * @example
15
+ * // in a worker entrypoint, after the notifications config + broker are up
16
+ * import { startNotificationsWorker } from "@warlock.js/notifications";
17
+ * await startNotificationsWorker();
18
+ */
19
+ async function startNotificationsWorker(options = {}) {
20
+ const channelName = options.channel ?? "notifications.dispatch";
21
+ const { herald } = await loadHerald();
22
+ await herald(options.broker).channel(channelName).subscribe(async (message, ctx) => {
23
+ const job = message.payload;
24
+ try {
25
+ const channel = getNotificationConfig().channels[job.channel];
26
+ if (!channel) {
27
+ log.error("notifications", "queue.worker", `Unknown channel "${job.channel}" — dropping job`);
28
+ await ctx.ack();
29
+ return;
30
+ }
31
+ await channel.send({
32
+ payload: job.payload,
33
+ route: job.route,
34
+ options: job.options
35
+ });
36
+ await ctx.ack();
37
+ } catch (error) {
38
+ log.error("notifications", "queue.worker", error);
39
+ await ctx.ack();
40
+ }
41
+ });
42
+ }
43
+
44
+ //#endregion
45
+ export { startNotificationsWorker };
46
+ //# sourceMappingURL=notifications-worker.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"notifications-worker.mjs","names":[],"sources":["../../../../../../../@warlock.js/notifications/src/queue/notifications-worker.ts"],"sourcesContent":["/**\n * Notification queue worker — consumes the jobs `heraldQueue` publishes and\n * runs the actual `channel.send`. Call this once in a worker process (or the\n * web process) after the notifications config + herald broker are up.\n *\n * The job carries an ALREADY-RENDERED payload + resolved route, so the worker\n * only looks the channel up by name and dispatches — no notifiable needed.\n *\n * @example\n * // in a worker entrypoint, after the notifications config + broker are up\n * import { startNotificationsWorker } from \"@warlock.js/notifications\";\n * await startNotificationsWorker();\n */\nimport { log } from \"@warlock.js/logger\";\nimport { getNotificationConfig } from \"../config\";\nimport type { ChannelName, SendOptions } from \"../types\";\nimport { DEFAULT_QUEUE_CHANNEL, loadHerald } from \"./load-herald\";\n\ntype NotificationJob = {\n channel: string;\n route: unknown;\n payload: unknown;\n options: SendOptions;\n};\n\nexport type WorkerOptions = {\n /** Herald channel to consume from. Must match the dispatcher. Default `\"notifications.dispatch\"`. */\n channel?: string;\n /** Herald broker name. Default broker if omitted. */\n broker?: string;\n};\n\nexport async function startNotificationsWorker(options: WorkerOptions = {}): Promise<void> {\n const channelName = options.channel ?? DEFAULT_QUEUE_CHANNEL;\n const { herald } = await loadHerald();\n\n await herald(options.broker)\n .channel(channelName)\n .subscribe(async (message: { payload: unknown }, ctx: { ack(): Promise<void> }) => {\n const job = message.payload as NotificationJob;\n\n try {\n const channel = getNotificationConfig().channels[job.channel as ChannelName];\n\n if (!channel) {\n // Unknown channel — log and ack to avoid a poison-message loop.\n log.error(\"notifications\", \"queue.worker\", `Unknown channel \"${job.channel}\" — dropping job`);\n await ctx.ack();\n return;\n }\n\n await channel.send({\n payload: job.payload as never,\n route: job.route as never,\n options: job.options,\n });\n\n await ctx.ack();\n } catch (error) {\n // Phase 2: log + ack (no retry/DLQ yet — that lands with the\n // delay-aware worker). A dead-letter strategy is a follow-up.\n log.error(\"notifications\", \"queue.worker\", error as Error);\n await ctx.ack();\n }\n });\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAgCA,eAAsB,yBAAyB,UAAyB,CAAC,GAAkB;CACzF,MAAM,cAAc,QAAQ;CAC5B,MAAM,EAAE,WAAW,MAAM,WAAW;CAEpC,MAAM,OAAO,QAAQ,MAAM,CAAC,CACzB,QAAQ,WAAW,CAAC,CACpB,UAAU,OAAO,SAA+B,QAAkC;EACjF,MAAM,MAAM,QAAQ;EAEpB,IAAI;GACF,MAAM,UAAU,sBAAsB,CAAC,CAAC,SAAS,IAAI;GAErD,IAAI,CAAC,SAAS;IAEZ,IAAI,MAAM,iBAAiB,gBAAgB,oBAAoB,IAAI,QAAQ,iBAAiB;IAC5F,MAAM,IAAI,IAAI;IACd;GACF;GAEA,MAAM,QAAQ,KAAK;IACjB,SAAS,IAAI;IACb,OAAO,IAAI;IACX,SAAS,IAAI;GACf,CAAC;GAED,MAAM,IAAI,IAAI;EAChB,SAAS,OAAO;GAGd,IAAI,MAAM,iBAAiB,gBAAgB,KAAc;GACzD,MAAM,IAAI,IAAI;EAChB;CACF,CAAC;AACL"}
@@ -0,0 +1,130 @@
1
+ import { Model } from "@warlock.js/cascade";
2
+
3
+ //#region ../@warlock.js/notifications/src/types.d.ts
4
+ /**
5
+ * A recipient you send TO (a User, etc.) — NOT the stored record.
6
+ * It's a cascade `Model` instance, so `.id` (number | string) and `.get(path)`
7
+ * are native. Routing is a CHANNEL concern (convention column + per-channel
8
+ * `route` override in config), not a model concern — there is intentionally
9
+ * NO `routeNotificationFor` on the model.
10
+ */
11
+ type Notifiable = Model;
12
+ /** An identifier — string (uuid / objectId) or number (auto-increment). */
13
+ type Id = string | number;
14
+ /** What the `mail` channel renders to and `core.sendMail` consumes. */
15
+ type MailPayload = {
16
+ subject: string;
17
+ html?: string;
18
+ text?: string;
19
+ cc?: string | string[];
20
+ };
21
+ /** Normalized in-app record shape written through the database channel. */
22
+ type NotificationInput = {
23
+ recipientId: Id;
24
+ type: string;
25
+ title: string;
26
+ body?: string;
27
+ payload?: Record<string, unknown>; /** Unique-per-recipient dedupe key — see `SendOptions.idempotencyKey`. */
28
+ idempotencyKey?: string;
29
+ };
30
+ /**
31
+ * Database payload as seen by RENDERERS — `recipientId` is set by the channel
32
+ * from the resolved route, not by the render closure.
33
+ */
34
+ type DatabasePayload = Omit<NotificationInput, "recipientId">;
35
+ /**
36
+ * Channel REGISTRY — the single declaration-merge target mapping channel NAME
37
+ * → payload type. Drives `notify.<channel>` proxy typing and
38
+ * `defineNotification` renderers. Custom channels extend via:
39
+ *
40
+ * declare module "@warlock.js/notifications" {
41
+ * interface NotificationChannels { discord: { content: string } }
42
+ * }
43
+ *
44
+ * Phase 1 ships ONLY `mail` and `database`. Bridges-backed channels
45
+ * (`whatsapp`, `telegram`, `push`, `slack`) ship in Phase 2 as the same
46
+ * declaration-merge pattern.
47
+ */
48
+ interface NotificationChannels {
49
+ mail: MailPayload;
50
+ database: DatabasePayload;
51
+ }
52
+ type ChannelName = keyof NotificationChannels;
53
+ /**
54
+ * Per-send options — the optional 3rd arg of `notify.<channel>` / `send` /
55
+ * `queue`. Lean by design: `queue` and `only` are METHODS on a defined
56
+ * notification (one path each), not flags here.
57
+ *
58
+ * - `force` bypasses `PreferenceProvider` (security/critical sends). It does
59
+ * NOT bypass `RateLimiter` — that's a safety valve, not a UX preference.
60
+ * - `idempotencyKey` dedupes on insert (database channel unique constraint)
61
+ * and on enqueue (queue dispatcher). Use for any send that may be retried.
62
+ * - `type` opts ad-hoc `notify.<channel>` sends into preference/rate-limit
63
+ * gating. `defineNotification` reads its own `def.type` and ignores this.
64
+ */
65
+ type SendOptions = {
66
+ /**
67
+ * Delay the dispatch, e.g. `"10m"` / `"3d"` / `600` (seconds). RESERVED —
68
+ * NOT honored yet: `.send()` always dispatches immediately, and the current
69
+ * `.queue()` worker runs each job the moment it's consumed. Delayed delivery
70
+ * lands with the delay-aware worker (Phase 2); until then this field is a
71
+ * no-op on both paths.
72
+ */
73
+ delay?: number | string; /** Locale for rendering — passed to renderers via `RenderContext`. */
74
+ locale?: string; /** Passthrough to channels + every observability event (via `options`). */
75
+ meta?: Record<string, unknown>; /** Dedupe key for retry-safe sends. */
76
+ idempotencyKey?: string; /** Bypass `PreferenceProvider`. Does NOT bypass `RateLimiter`. */
77
+ force?: boolean; /** Notification type for ad-hoc gating (unused by `defineNotification`). */
78
+ type?: string;
79
+ };
80
+ /** What a renderer receives as its 3rd arg — i18n + meta passthrough only. */
81
+ type RenderContext = Pick<SendOptions, "locale" | "meta">;
82
+ /**
83
+ * Event surface for metrics, logging, audits. Fires per (channel, recipient)
84
+ * — fan-out emits N events.
85
+ *
86
+ * `dispatchId` is a unique id for ONE (channel, recipient) dispatch: `sending`
87
+ * and its terminal `sent` / `failed` share it, so observers can pair them (for
88
+ * spans, latency, or detecting a `sending` with no terminal — a hung send). A
89
+ * `skipped` carries its own id (nothing precedes it).
90
+ *
91
+ * `durationMs` on `sent` / `failed` is the transport time for a synchronous
92
+ * send; on a queued send it is the enqueue time (real delivery timing is
93
+ * worker-side).
94
+ */
95
+ type NotificationEvents = {
96
+ sending: {
97
+ dispatchId: string;
98
+ channel: string;
99
+ notifiable?: Notifiable;
100
+ payload: unknown;
101
+ options: SendOptions;
102
+ };
103
+ sent: {
104
+ dispatchId: string;
105
+ channel: string;
106
+ notifiable?: Notifiable;
107
+ payload: unknown;
108
+ options: SendOptions;
109
+ durationMs: number;
110
+ };
111
+ failed: {
112
+ dispatchId: string;
113
+ channel: string;
114
+ notifiable?: Notifiable;
115
+ payload: unknown;
116
+ error: Error;
117
+ options: SendOptions;
118
+ durationMs: number;
119
+ };
120
+ skipped: {
121
+ dispatchId: string;
122
+ channel: string;
123
+ notifiable?: Notifiable;
124
+ reason: "preference" | "rate-limit";
125
+ options: SendOptions;
126
+ };
127
+ };
128
+ //#endregion
129
+ export { ChannelName, DatabasePayload, Id, MailPayload, Notifiable, NotificationChannels, NotificationEvents, NotificationInput, RenderContext, SendOptions };
130
+ //# sourceMappingURL=types.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.mts","names":[],"sources":["../../../../../../@warlock.js/notifications/src/types.ts"],"mappings":";;;AAqBA;;;;AAAc;AAKd;;AALA,KAHY,UAAA,GAAa,KAAK;;KAGlB,EAAA;;KAKA,WAAA;EACV,OAAA;EACA,IAAA;EACA,IAAA;EACA,EAAA;AAAA;;KAIU,iBAAA;EACV,WAAA,EAAa,EAAA;EACb,IAAA;EACA,KAAA;EACA,IAAA;EACA,OAAA,GAAU,MAAM,mBAAhB;EAEA,cAAA;AAAA;;AAAc;AAOhB;;KAAY,eAAA,GAAkB,IAAI,CAAC,iBAAA;;AAAiB;AAiBpD;;;;;;;;;AAE2B;AAG3B;UALiB,oBAAA;EACf,IAAA,EAAM,WAAA;EACN,QAAA,EAAU,eAAe;AAAA;AAAA,KAGf,WAAA,SAAoB,oBAAoB;;;;;;;;;;;;AAkC9C;KAlBM,WAAA;EAsBa;;;AAAmB;AAiB5C;;;EA/BE,KAAA,oBAqCW;EAnCX,MAAA,WA0CW;EAxCX,IAAA,GAAO,MAAM,mBAgDJ;EA9CT,cAAA,WAqDe;EAnDf,KAAA,YAqDsB;EAnDtB,IAAA;AAAA;;KAIU,aAAA,GAAgB,IAAI,CAAC,WAAA;;;;;;;;;;;;;;KAiBrB,kBAAA;EACV,OAAA;IACE,UAAA;IACA,OAAA;IACA,UAAA,GAAa,UAAA;IACb,OAAA;IACA,OAAA,EAAS,WAAA;EAAA;EAEX,IAAA;IACE,UAAA;IACA,OAAA;IACA,UAAA,GAAa,UAAA;IACb,OAAA;IACA,OAAA,EAAS,WAAA;IACT,UAAA;EAAA;EAEF,MAAA;IACE,UAAA;IACA,OAAA;IACA,UAAA,GAAa,UAAA;IACb,OAAA;IACA,KAAA,EAAO,KAAA;IACP,OAAA,EAAS,WAAA;IACT,UAAA;EAAA;EAEF,OAAA;IACE,UAAA;IACA,OAAA;IACA,UAAA,GAAa,UAAA;IACb,MAAA;IACA,OAAA,EAAS,WAAA;EAAA;AAAA"}