@visulima/notification 0.0.1 → 1.0.0-alpha.1

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 (134) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/LICENSE.md +434 -0
  3. package/README.md +296 -6
  4. package/dist/channels/email/index.d.ts +46 -0
  5. package/dist/channels/email/index.js +1 -0
  6. package/dist/channels/inapp/index.d.ts +99 -0
  7. package/dist/channels/inapp/index.js +1 -0
  8. package/dist/events/index.d.ts +37 -0
  9. package/dist/events/index.js +1 -0
  10. package/dist/index.d.ts +174 -0
  11. package/dist/index.js +1 -0
  12. package/dist/middleware/index.d.ts +382 -0
  13. package/dist/middleware/index.js +1 -0
  14. package/dist/packem_shared/MemoryEventStore-BGD_xRxu.js +1 -0
  15. package/dist/packem_shared/MemoryInAppStore-48qlrDCx.js +1 -0
  16. package/dist/packem_shared/MemoryQueue-CXgWoZoY.js +1 -0
  17. package/dist/packem_shared/Notification-D1YeYxrQ.js +1 -0
  18. package/dist/packem_shared/NotificationError-DVfMJQ1u.js +1 -0
  19. package/dist/packem_shared/NotificationMessageBuilder-Br_aZusY.js +1 -0
  20. package/dist/packem_shared/RequiredOptionError-CuL42XRq.js +1 -0
  21. package/dist/packem_shared/UnstorageInAppStore-Bq2LoDMO.js +1 -0
  22. package/dist/packem_shared/apnsProvider-C9WSv5sc.js +1 -0
  23. package/dist/packem_shared/circuitBreakerMiddleware-BlZN7SVT.js +1 -0
  24. package/dist/packem_shared/composeMiddleware-D6dR8DUS.js +1 -0
  25. package/dist/packem_shared/createQueueWorker-DjQBxjXc.js +1 -0
  26. package/dist/packem_shared/credentials-CG0zkn-W.js +1 -0
  27. package/dist/packem_shared/dedupeMiddleware-CQmPJd_h.js +1 -0
  28. package/dist/packem_shared/defineProvider-C9q-7iwt.js +1 -0
  29. package/dist/packem_shared/discordProvider-ewPfe-J7.js +1 -0
  30. package/dist/packem_shared/emailChannel-CNDlX96Q.js +1 -0
  31. package/dist/packem_shared/expoProvider-CXq101FD.js +1 -0
  32. package/dist/packem_shared/failoverProvider-D1dAmW8U.js +1 -0
  33. package/dist/packem_shared/fcmProvider-WdJGZ55l.js +1 -0
  34. package/dist/packem_shared/getHeader-C_gkdnMQ.js +1 -0
  35. package/dist/packem_shared/hmacBase64-BhhoX0uo.js +1 -0
  36. package/dist/packem_shared/http-BMYcUNBU.js +1 -0
  37. package/dist/packem_shared/id-C_FRCsUM.js +1 -0
  38. package/dist/packem_shared/inAppProvider-ElbahgSj.js +1 -0
  39. package/dist/packem_shared/loggingMiddleware-Cs6EDLcL.js +1 -0
  40. package/dist/packem_shared/messageBirdProvider-CHUFB0hd.js +1 -0
  41. package/dist/packem_shared/mockProvider-BLwTZlkw.js +1 -0
  42. package/dist/packem_shared/msTeamsProvider-DBhrmuSd.js +1 -0
  43. package/dist/packem_shared/notification.d-BBLyVuqi.d.ts +87 -0
  44. package/dist/packem_shared/otelProvider-b6LVo0BS.js +1 -0
  45. package/dist/packem_shared/plivoProvider-CIOl6DWw.js +1 -0
  46. package/dist/packem_shared/provider.d-BQAGriz6.d.ts +33 -0
  47. package/dist/packem_shared/rateLimitMiddleware-DWmEYq-m.js +1 -0
  48. package/dist/packem_shared/retryMiddleware-uUJDcUoP.js +1 -0
  49. package/dist/packem_shared/roundRobinProvider-BetXJUUm.js +1 -0
  50. package/dist/packem_shared/slackProvider-N1wNtAZ3.js +1 -0
  51. package/dist/packem_shared/slackWebhook-GEx_GdcV.js +1 -0
  52. package/dist/packem_shared/sms-CU-OZvNU.js +1 -0
  53. package/dist/packem_shared/snsProvider-KCciJ-nj.js +4 -0
  54. package/dist/packem_shared/snsWebhook-ChZnBBT3.js +1 -0
  55. package/dist/packem_shared/standardWebhook-B-LxP8U7.js +1 -0
  56. package/dist/packem_shared/suppressionMiddleware-CY-Qo1A_.js +1 -0
  57. package/dist/packem_shared/telegramProvider-jqR5BhJL.js +1 -0
  58. package/dist/packem_shared/telemetryMiddleware-B1N4k171.js +1 -0
  59. package/dist/packem_shared/telnyxProvider-C8TMPir1.js +1 -0
  60. package/dist/packem_shared/tracer.d-Co1s_NwG.d.ts +494 -0
  61. package/dist/packem_shared/twilioProvider-BxI0e6LX.js +1 -0
  62. package/dist/packem_shared/twilioWebhook-F0mTmGXD.js +1 -0
  63. package/dist/packem_shared/types.d-CximoA8M.d.ts +29 -0
  64. package/dist/packem_shared/types.d-DyM9JjL7.d.ts +33 -0
  65. package/dist/packem_shared/types.d-GUzX1jTk.d.ts +235 -0
  66. package/dist/packem_shared/types.d-td-F2Oh0.d.ts +10 -0
  67. package/dist/packem_shared/unstorage.DqlWKU2I.d-CogP3Etw.d.ts +120 -0
  68. package/dist/packem_shared/vonageProvider-BF76ca89.js +1 -0
  69. package/dist/packem_shared/webPushProvider-B962RwsA.js +1 -0
  70. package/dist/packem_shared/webcrypto-bY7wJCB5.js +1 -0
  71. package/dist/packem_shared/webhookProvider-BALSb2pc.js +1 -0
  72. package/dist/preferences/index.d.ts +49 -0
  73. package/dist/preferences/index.js +1 -0
  74. package/dist/providers/chat/discord/index.d.ts +14 -0
  75. package/dist/providers/chat/discord/index.js +1 -0
  76. package/dist/providers/chat/msteams/index.d.ts +14 -0
  77. package/dist/providers/chat/msteams/index.js +1 -0
  78. package/dist/providers/chat/slack/index.d.ts +19 -0
  79. package/dist/providers/chat/slack/index.js +1 -0
  80. package/dist/providers/chat/telegram/index.d.ts +18 -0
  81. package/dist/providers/chat/telegram/index.js +1 -0
  82. package/dist/providers/failover/index.d.ts +16 -0
  83. package/dist/providers/failover/index.js +1 -0
  84. package/dist/providers/mock/index.d.ts +29 -0
  85. package/dist/providers/mock/index.js +1 -0
  86. package/dist/providers/opentelemetry/index.d.ts +21 -0
  87. package/dist/providers/opentelemetry/index.js +1 -0
  88. package/dist/providers/push/apns/index.d.ts +24 -0
  89. package/dist/providers/push/apns/index.js +1 -0
  90. package/dist/providers/push/expo/index.d.ts +14 -0
  91. package/dist/providers/push/expo/index.js +1 -0
  92. package/dist/providers/push/fcm/index.d.ts +26 -0
  93. package/dist/providers/push/fcm/index.js +1 -0
  94. package/dist/providers/push/web-push/index.d.ts +36 -0
  95. package/dist/providers/push/web-push/index.js +1 -0
  96. package/dist/providers/roundrobin/index.d.ts +17 -0
  97. package/dist/providers/roundrobin/index.js +1 -0
  98. package/dist/providers/sms/messagebird/index.d.ts +16 -0
  99. package/dist/providers/sms/messagebird/index.js +1 -0
  100. package/dist/providers/sms/plivo/index.d.ts +18 -0
  101. package/dist/providers/sms/plivo/index.js +1 -0
  102. package/dist/providers/sms/sns/index.d.ts +21 -0
  103. package/dist/providers/sms/sns/index.js +1 -0
  104. package/dist/providers/sms/telnyx/index.d.ts +18 -0
  105. package/dist/providers/sms/telnyx/index.js +1 -0
  106. package/dist/providers/sms/twilio/index.d.ts +20 -0
  107. package/dist/providers/sms/twilio/index.js +1 -0
  108. package/dist/providers/sms/vonage/index.d.ts +18 -0
  109. package/dist/providers/sms/vonage/index.js +1 -0
  110. package/dist/providers/webhook/index.d.ts +18 -0
  111. package/dist/providers/webhook/index.js +1 -0
  112. package/dist/queue/bullmq-queue.d.ts +58 -0
  113. package/dist/queue/bullmq-queue.js +1 -0
  114. package/dist/queue/index.d.ts +50 -0
  115. package/dist/queue/index.js +1 -0
  116. package/dist/queue/pg-boss-queue.d.ts +66 -0
  117. package/dist/queue/pg-boss-queue.js +1 -0
  118. package/dist/queue/sqs-queue.d.ts +91 -0
  119. package/dist/queue/sqs-queue.js +1 -0
  120. package/dist/queue/unstorage-queue.d.ts +30 -0
  121. package/dist/queue/unstorage-queue.js +1 -0
  122. package/dist/routing/index.d.ts +35 -0
  123. package/dist/routing/index.js +1 -0
  124. package/dist/template-engines/handlebars.d.ts +16 -0
  125. package/dist/template-engines/handlebars.js +1 -0
  126. package/dist/template-engines/liquid.d.ts +16 -0
  127. package/dist/template-engines/liquid.js +1 -0
  128. package/dist/template-engines/string.d.ts +20 -0
  129. package/dist/template-engines/string.js +1 -0
  130. package/dist/utils/result.d.ts +54 -0
  131. package/dist/utils/result.js +1 -0
  132. package/dist/webhooks/index.d.ts +110 -0
  133. package/dist/webhooks/index.js +1 -0
  134. package/package.json +255 -43
package/README.md CHANGED
@@ -1,12 +1,302 @@
1
- # @visulima/notification
1
+ <!-- START_PACKAGE_OG_IMAGE_PLACEHOLDER -->
2
2
 
3
- > 🚧 **Placeholder** — this name is reserved. The full multi-channel notification library is coming soon.
3
+ <a href="https://www.anolilab.com/open-source" align="center">
4
4
 
5
- A reusable, ESM-only, edge-ready multi-channel notification library with SMS, push, chat, in-app and webhook providers.
5
+ <img src="__assets__/package-og.svg" alt="notification" />
6
6
 
7
- - Homepage: https://visulima.com/packages/notification
8
- - Repository: https://github.com/visulima/visulima/tree/main/packages/notification/notification
7
+ </a>
8
+
9
+ <h3 align="center">A reusable, ESM-only, edge-ready multi-channel notification library with SMS, push, chat, in-app and webhook providers</h3>
10
+
11
+ <!-- END_PACKAGE_OG_IMAGE_PLACEHOLDER -->
12
+
13
+ <br />
14
+
15
+ <div align="center">
16
+
17
+ [![typescript-image][typescript-badge]][typescript-url]
18
+ [![mit licence][license-badge]][license]
19
+ [![npm downloads][npm-downloads-badge]][npm-downloads]
20
+ [![Chat][chat-badge]][chat]
21
+ [![PRs Welcome][prs-welcome-badge]][prs-welcome]
22
+
23
+ </div>
24
+
25
+ ---
26
+
27
+ <div align="center">
28
+ <p>
29
+ <sup>
30
+ Daniel Bannert's open source work is supported by the community on <a href="https://github.com/sponsors/prisis">GitHub Sponsors</a>
31
+ </sup>
32
+ </p>
33
+ </div>
34
+
35
+ ---
36
+
37
+ `@visulima/notification` is the multi-channel sibling of [`@visulima/email`](https://visulima.com/packages/email): one
38
+ typed facade drives many channel providers (SMS, push, chat, in-app, webhook) and the email channel delegates straight to
39
+ `@visulima/email`. It is **infra-free** (no Redis/Mongo/dashboard required), **ESM-only**, **tree-shakeable** (import only
40
+ the providers you use), and **edge-ready** — every native provider is built on `fetch` with zero Node built-ins, so it
41
+ runs unmodified on Cloudflare Workers, Vercel Edge, Deno and Bun.
42
+
43
+ ## Install
44
+
45
+ ```sh
46
+ npm install @visulima/notification
47
+ ```
48
+
49
+ ```sh
50
+ yarn add @visulima/notification
51
+ ```
52
+
53
+ ```sh
54
+ pnpm add @visulima/notification
55
+ ```
56
+
57
+ ## Usage
58
+
59
+ ### Multi-channel send
60
+
61
+ ```typescript
62
+ import { createNotification } from "@visulima/notification";
63
+ import { twilioProvider } from "@visulima/notification/providers/twilio";
64
+ import { slackProvider } from "@visulima/notification/providers/slack";
65
+ import { fcmProvider } from "@visulima/notification/providers/fcm";
66
+
67
+ const notify = createNotification({
68
+ sms: twilioProvider({ accountSid: "AC…", authToken: "…", from: "+15555550100" }),
69
+ chat: slackProvider({ token: "xoxb-…", defaultChannel: "C123" }),
70
+ push: fcmProvider({ projectId: "my-app", getAccessToken: async () => getGoogleToken() }),
71
+ });
72
+
73
+ // Each present channel is delivered in parallel; you get one receipt per channel.
74
+ const receipts = await notify.send({
75
+ sms: { to: "+15555550100", text: "Your code is 123" },
76
+ chat: { text: "🚀 Deploy finished" },
77
+ push: { to: ["device-token"], title: "Deploy", body: "Finished" },
78
+ });
79
+
80
+ for (const receipt of receipts) {
81
+ if (receipt.successful) {
82
+ console.log(`${receipt.channel}: ${receipt.messageId}`);
83
+ } else {
84
+ console.error(`${receipt.channel} failed:`, receipt.errorMessages);
85
+ }
86
+ }
87
+ ```
88
+
89
+ ### Single-channel send
90
+
91
+ ```typescript
92
+ const receipt = await notify.sendToChannel("sms", { to: "+15555550100", text: "Hi" });
93
+ ```
94
+
95
+ ### Batch send with bounded concurrency
96
+
97
+ ```typescript
98
+ for await (const receipts of notify.sendMany(messages, { concurrency: 10 })) {
99
+ // receipts for one message
100
+ }
101
+ ```
102
+
103
+ ## Channels & providers
104
+
105
+ Providers are imported from `@visulima/notification/providers/<name>` so unused integrations are tree-shaken away.
106
+
107
+ | Channel | Providers |
108
+ | ------------ | --------------------------------------------------------------- |
109
+ | **SMS** | `twilio`, `vonage`, `plivo`, `messagebird`, `telnyx`, `sns` |
110
+ | **Push** | `fcm`, `expo`, `web-push`, `apns` |
111
+ | **Chat** | `slack`, `discord`, `msteams`, `telegram` |
112
+ | **In-app** | `inAppProvider` (memory or unstorage store) |
113
+ | **Webhook** | `webhook` |
114
+ | **Email** | `emailChannel(...)` → wraps a `@visulima/email` `Mail` instance |
115
+ | **Wrappers** | `failover`, `roundrobin`, `opentelemetry`, `mock` |
116
+
117
+ > **Cloudflare / edge note:** nearly every provider is `fetch` + Web Crypto only and runs on **Cloudflare Workers**,
118
+ > Vercel Edge, Deno and Bun — including **AWS SNS** (Web Crypto SigV4) and **web-push** (Web Crypto VAPID + RFC 8291).
119
+ > FCM accepts a `getAccessToken` callback so you bring your own OAuth token without a Node-only SDK. The only **Node-only**
120
+ > pieces are **APNs** (`node:http2`) and the **bullmq / pg-boss / sqs** queue adapters — import those from a Node runtime.
121
+ > See the [runtime matrix](https://visulima.com/docs/packages/notification/installation#runtime-support).
122
+
123
+ ## Writing a provider
124
+
125
+ Every provider implements the same contract; author one with `defineProvider`:
126
+
127
+ ```typescript
128
+ import { defineProvider } from "@visulima/notification";
129
+
130
+ export const myProvider = defineProvider<MyConfig, SmsPayload>((config) => ({
131
+ id: "my-provider",
132
+ channel: "sms",
133
+ features: { batchSending: false },
134
+ initialize: () => {},
135
+ isAvailable: () => Boolean(config?.apiKey),
136
+ send: async (payload) => {
137
+ // … return { success: true, data: { messageId, channel: "sms", provider: "my-provider", sent: true, timestamp: new Date() } }
138
+ },
139
+ }));
140
+ ```
141
+
142
+ ## Failover & round-robin
143
+
144
+ Wrap several same-channel providers to gain resilience or load balancing:
145
+
146
+ ```typescript
147
+ import { failoverProvider } from "@visulima/notification/providers/failover";
148
+ import { roundRobinProvider } from "@visulima/notification/providers/roundrobin";
149
+
150
+ const sms = failoverProvider([twilioProvider({ … }), vonageProvider({ … })]); // try Twilio, fall back to Vonage
151
+ const balanced = roundRobinProvider([plivoProvider({ … }), telnyxProvider({ … })]);
152
+
153
+ const notify = createNotification({ sms });
154
+ ```
155
+
156
+ ## Routing (channel fallback & broadcast)
157
+
158
+ `route(...)` adds channel-sequence delivery and a gate, on top of per-channel provider failover:
159
+
160
+ ```typescript
161
+ import { route } from "@visulima/notification/routing";
162
+
163
+ // best-of: try sms, then push, then email — stop at the first success
164
+ await route(notify, message, { order: ["sms", "push", "email"], mode: "best-of" });
165
+
166
+ // all: broadcast to every present channel in parallel
167
+ await route(notify, message, { mode: "all" });
168
+ ```
169
+
170
+ ## Preferences
171
+
172
+ Honour per-subscriber, per-channel opt-outs (critical sends bypass them):
173
+
174
+ ```typescript
175
+ import { MemoryPreferenceStore, preferencesGate } from "@visulima/notification/preferences";
176
+
177
+ const prefs = new MemoryPreferenceStore();
178
+ prefs.set("user-1", { channels: { sms: false } });
179
+
180
+ await route(notify, message, { gate: preferencesGate(prefs) });
181
+ await route(notify, message, { gate: preferencesGate(prefs, { critical: true }) }); // bypasses opt-outs
182
+ ```
183
+
184
+ ## Middleware
185
+
186
+ Cross-cutting concerns compose around every send (first registered = outermost):
187
+
188
+ ```typescript
189
+ import { retryMiddleware, rateLimitMiddleware, circuitBreakerMiddleware, dedupeMiddleware, loggingMiddleware } from "@visulima/notification/middleware";
190
+
191
+ notify
192
+ .use(loggingMiddleware())
193
+ .use(retryMiddleware({ retries: 3 }))
194
+ .use(rateLimitMiddleware({ rate: 100, interval: 1000 }))
195
+ .use(circuitBreakerMiddleware({ threshold: 5 }))
196
+ .use(dedupeMiddleware({ ttl: 60_000 }));
197
+ ```
198
+
199
+ ## Queue & worker
200
+
201
+ Decouple enqueue from delivery with a durable queue and a retrying worker:
202
+
203
+ ```typescript
204
+ import { MemoryQueue, createQueueWorker } from "@visulima/notification/queue";
205
+
206
+ const queue = new MemoryQueue();
207
+ queue.enqueue({ sms: { to: "+15555550100", text: "Hi" } });
208
+
209
+ const worker = createQueueWorker(queue, notify, { maxAttempts: 5 });
210
+ worker.start(); // or `await worker.drain()` to process all due jobs once
211
+ ```
212
+
213
+ Back the queue with [unstorage](https://unstorage.unjs.io) for Redis/filesystem/KV persistence:
214
+
215
+ ```typescript
216
+ import { UnstorageQueue } from "@visulima/notification/queue/unstorage";
217
+ import { createStorage } from "unstorage";
218
+
219
+ const queue = new UnstorageQueue(createStorage());
220
+ ```
221
+
222
+ ## In-app inbox
223
+
224
+ ```typescript
225
+ import { inAppProvider } from "@visulima/notification/channels/inapp";
226
+
227
+ const inapp = inAppProvider();
228
+ const notify = createNotification({ inapp });
229
+
230
+ await notify.sendToChannel("inapp", { to: "user-1", title: "Welcome", body: "Thanks for joining" });
231
+
232
+ const store = inapp.getInstance();
233
+ await store.unreadCount("user-1"); // 1
234
+ await store.list("user-1");
235
+ await store.markAllRead("user-1");
236
+ ```
237
+
238
+ ## Email channel
239
+
240
+ The email channel delegates to a configured [`@visulima/email`](https://visulima.com/packages/email) `Mail` instance, so
241
+ you reuse its 29 providers, templates and deliverability tooling:
242
+
243
+ ```typescript
244
+ import { createMail } from "@visulima/email";
245
+ import { resendProvider } from "@visulima/email/providers/resend";
246
+ import { createNotification } from "@visulima/notification";
247
+ import { emailChannel } from "@visulima/notification/channels/email";
248
+
249
+ const mail = createMail(resendProvider({ apiKey: "re_…" }));
250
+ const notify = createNotification({ email: emailChannel(mail) });
251
+
252
+ await notify.sendToChannel("email", { from: "noreply@app.com", to: "user@x.com", subject: "Hi", html: "<p>Hello</p>" });
253
+ ```
254
+
255
+ ## Events
256
+
257
+ Subscribe to lifecycle events and build a timeline:
258
+
259
+ ```typescript
260
+ import { NotificationEventBus, MemoryEventStore } from "@visulima/notification/events";
261
+
262
+ const bus = new NotificationEventBus();
263
+ bus.on("sent", (event) => console.log(event.messageId));
264
+ bus.on("*", (event) => store.append(event));
265
+ ```
266
+
267
+ ## Supported Node.js Versions
268
+
269
+ Libraries in this ecosystem make the best effort to track [Node.js' release schedule](https://github.com/nodejs/release#release-schedule).
270
+ Here's [a post on why we think this is important](https://medium.com/the-node-js-collection/maintainers-should-consider-following-node-js-release-schedule-ab08ed4de71a).
271
+
272
+ ## Contributing
273
+
274
+ If you would like to help take a look at the [list of issues](https://github.com/visulima/visulima/issues) and check our [Contributing](.github/CONTRIBUTING.md) guidelines.
275
+
276
+ > **Note:** please note that this project is released with a Contributor Code of Conduct. By participating in this project you agree to abide by its terms.
277
+
278
+ ## Credits
279
+
280
+ - [Daniel Bannert](https://github.com/prisis)
281
+ - [All Contributors](https://github.com/visulima/visulima/graphs/contributors)
282
+
283
+ ## Made with ❤️ at Anolilab
284
+
285
+ This is an open source project and will always remain free to use. If you think it's cool, please star it 🌟. [Anolilab](https://www.anolilab.com/open-source) is a Development and AI Studio. Contact us at [hello@anolilab.com](mailto:hello@anolilab.com) if you need any help with these technologies or just want to say hi!
9
286
 
10
287
  ## License
11
288
 
12
- [MIT](https://github.com/visulima/visulima/blob/main/LICENSE.md) © [Daniel Bannert](https://github.com/prisis)
289
+ The visulima notification is open-sourced software licensed under the [MIT][license]
290
+
291
+ <!-- badges -->
292
+
293
+ [license-badge]: https://img.shields.io/npm/l/@visulima/notification?style=for-the-badge
294
+ [license]: https://github.com/visulima/visulima/blob/main/LICENSE
295
+ [npm-downloads-badge]: https://img.shields.io/npm/dm/@visulima/notification?style=for-the-badge
296
+ [npm-downloads]: https://www.npmjs.com/package/@visulima/notification
297
+ [prs-welcome-badge]: https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=for-the-badge
298
+ [prs-welcome]: https://github.com/visulima/visulima/blob/main/.github/CONTRIBUTING.md
299
+ [chat-badge]: https://img.shields.io/discord/932323359193186354.svg?style=for-the-badge
300
+ [chat]: https://discord.gg/TtFJY8xkFK
301
+ [typescript-badge]: https://img.shields.io/badge/Typescript-294E80.svg?style=for-the-badge&logo=typescript
302
+ [typescript-url]: https://www.typescriptlang.org/
@@ -0,0 +1,46 @@
1
+ import { P as Provider } from "../../packem_shared/provider.d-BQAGriz6.js";
2
+ import { E as EmailChannelPayload } from "../../packem_shared/types.d-GUzX1jTk.js";
3
+ /**
4
+ * The result shape returned by `@visulima/email`'s `Mail.send`.
5
+ */
6
+ interface EmailSendResult {
7
+ data?: {
8
+ messageId: string;
9
+ provider?: string;
10
+ response?: unknown;
11
+ sent?: boolean;
12
+ timestamp?: Date;
13
+ };
14
+ error?: unknown;
15
+ success: boolean;
16
+ }
17
+ /**
18
+ * Structural type for a `@visulima/email` `Mail` instance. Kept structural so the email
19
+ * peer stays optional — pass a configured `createMail(...)` result.
20
+ */
21
+ interface EmailLike {
22
+ send: (message: unknown) => Promise<EmailSendResult>;
23
+ }
24
+ interface EmailChannelConfig {
25
+ /** Provider id reported on results (default `"email"`). */
26
+ id?: string;
27
+ }
28
+ /**
29
+ * Adapts a configured `@visulima/email` `Mail` instance into a notification channel
30
+ * provider, so email can participate in multi-channel sends and routing.
31
+ * @param mail A `Mail` instance from `createMail(...)`.
32
+ * @param config Optional config.
33
+ * @returns An email-channel provider.
34
+ * @example
35
+ * ```ts
36
+ * import { createMail } from "@visulima/email";
37
+ * import { resendProvider } from "@visulima/email/providers/resend";
38
+ * import { createNotification } from "@visulima/notification";
39
+ * import { emailChannel } from "@visulima/notification/channels/email";
40
+ *
41
+ * const mail = createMail(resendProvider({ apiKey }));
42
+ * const notify = createNotification({ email: emailChannel(mail) });
43
+ * ```
44
+ */
45
+ declare const emailChannel: (mail: EmailLike, config?: EmailChannelConfig) => Provider<unknown, EmailChannelPayload>;
46
+ export { type EmailChannelConfig, type EmailLike, emailChannel };
@@ -0,0 +1 @@
1
+ import{emailChannel as l}from"../../packem_shared/emailChannel-CNDlX96Q.js";export{l as emailChannel};
@@ -0,0 +1,99 @@
1
+ import { P as Provider } from "../../packem_shared/provider.d-BQAGriz6.js";
2
+ import { I as InAppPayload } from "../../packem_shared/types.d-GUzX1jTk.js";
3
+ import { S as Storage } from "../../packem_shared/unstorage.DqlWKU2I.d-CogP3Etw.js";
4
+ /**
5
+ * A persisted in-app notification.
6
+ */
7
+ interface StoredNotification {
8
+ actions?: {
9
+ label: string;
10
+ url?: string;
11
+ }[];
12
+ body: string;
13
+ createdAt: number;
14
+ data?: Record<string, unknown>;
15
+ id: string;
16
+ read: boolean;
17
+ subscriberId: string;
18
+ title?: string;
19
+ }
20
+ interface ListOptions {
21
+ /** Max items to return. */
22
+ limit?: number;
23
+ /** Only return unread items. */
24
+ unreadOnly?: boolean;
25
+ }
26
+ /**
27
+ * Backing store for the in-app channel. The default {@link MemoryInAppStore} is
28
+ * in-process; back it with a persistent store for production feeds.
29
+ */
30
+ interface InAppStore {
31
+ add: (notification: Omit<StoredNotification, "createdAt" | "id" | "read"> & {
32
+ id?: string;
33
+ }) => Promise<StoredNotification>;
34
+ list: (subscriberId: string, options?: ListOptions) => Promise<StoredNotification[]>;
35
+ markAllRead: (subscriberId: string) => Promise<void>;
36
+ markRead: (id: string) => Promise<void>;
37
+ remove: (id: string) => Promise<void>;
38
+ unreadCount: (subscriberId: string) => Promise<number>;
39
+ }
40
+ /**
41
+ * In-memory {@link InAppStore}.
42
+ */
43
+ declare class MemoryInAppStore implements InAppStore {
44
+ #private;
45
+ add(notification: Omit<StoredNotification, "createdAt" | "id" | "read"> & {
46
+ id?: string;
47
+ }): Promise<StoredNotification>;
48
+ list(subscriberId: string, options?: ListOptions): Promise<StoredNotification[]>;
49
+ markRead(id: string): Promise<void>;
50
+ markAllRead(subscriberId: string): Promise<void>;
51
+ unreadCount(subscriberId: string): Promise<number>;
52
+ remove(id: string): Promise<void>;
53
+ }
54
+ interface InAppProviderConfig {
55
+ /** Provider id reported on results (default `"inapp"`). */
56
+ id?: string;
57
+ /** Backing store (default a new {@link MemoryInAppStore}). */
58
+ store?: InAppStore;
59
+ }
60
+ /**
61
+ * In-app channel provider. Persists notifications to an {@link InAppStore} for a feed/inbox
62
+ * UI to query. Expose the store via `getInstance()` to read/markRead.
63
+ * @param config Provider `id` and the backing {@link InAppStore} (defaults to in-memory).
64
+ * @returns A provider that writes notifications into the configured store.
65
+ */
66
+ declare const inAppProvider: (config?: InAppProviderConfig) => Provider<InAppProviderConfig, InAppPayload, InAppStore>;
67
+ /**
68
+ * An {@link InAppStore} backed by an [unstorage](https://unstorage.unjs.io) driver, giving durable,
69
+ * multi-backend persistence (Redis, filesystem, Cloudflare KV, ...). `unstorage` is an optional peer
70
+ * dependency — pass a configured `Storage` instance.
71
+ *
72
+ * Each notification is stored under `prefix:item:id`, and a per-subscriber index list is kept under
73
+ * `prefix:index:subscriberId` so `list`/`unreadCount`/`markAllRead` avoid scanning every key.
74
+ *
75
+ * The per-subscriber index is eventually-consistent: the item and index keys are written
76
+ * non-atomically, so a concurrent reader may briefly observe one without the other.
77
+ *
78
+ * Edge-safe: works on Cloudflare KV (and other edge runtimes) via the matching unstorage driver.
79
+ */
80
+ declare class UnstorageInAppStore implements InAppStore {
81
+ #private;
82
+ constructor(storage: Storage, prefix?: string);
83
+ add(notification: Omit<StoredNotification, "createdAt" | "id" | "read"> & {
84
+ id?: string;
85
+ }): Promise<StoredNotification>;
86
+ list(subscriberId: string, options?: ListOptions): Promise<StoredNotification[]>;
87
+ markRead(id: string): Promise<void>;
88
+ markAllRead(subscriberId: string): Promise<void>;
89
+ unreadCount(subscriberId: string): Promise<number>;
90
+ remove(id: string): Promise<void>;
91
+ }
92
+ /**
93
+ * Convenience factory for {@link UnstorageInAppStore}.
94
+ * @param storage A configured unstorage `Storage` instance (optional peer).
95
+ * @param prefix Key prefix under which notifications are stored.
96
+ * @returns A new {@link UnstorageInAppStore}.
97
+ */
98
+ declare const createUnstorageInAppStore: (storage: Storage, prefix?: string) => UnstorageInAppStore;
99
+ export { type InAppProviderConfig, type InAppStore, type ListOptions, MemoryInAppStore, type StoredNotification, UnstorageInAppStore, createUnstorageInAppStore, inAppProvider };
@@ -0,0 +1 @@
1
+ import{inAppProvider as e}from"../../packem_shared/inAppProvider-ElbahgSj.js";import{MemoryInAppStore as t}from"../../packem_shared/MemoryInAppStore-48qlrDCx.js";import{UnstorageInAppStore as m,createUnstorageInAppStore as A}from"../../packem_shared/UnstorageInAppStore-Bq2LoDMO.js";export{t as MemoryInAppStore,m as UnstorageInAppStore,A as createUnstorageInAppStore,e as inAppProvider};
@@ -0,0 +1,37 @@
1
+ import { N as NotificationEvent, e as NotificationEventType } from "../packem_shared/types.d-GUzX1jTk.js";
2
+ /**
3
+ * An append-only timeline store for notification events.
4
+ */
5
+ interface EventStore {
6
+ append: (event: NotificationEvent) => void | Promise<void>;
7
+ timeline: (messageId: string) => NotificationEvent[] | Promise<NotificationEvent[]>;
8
+ }
9
+ /**
10
+ * In-memory {@link EventStore} keyed by message id.
11
+ */
12
+ declare class MemoryEventStore implements EventStore {
13
+ #private;
14
+ append(event: NotificationEvent): void;
15
+ timeline(messageId: string): NotificationEvent[];
16
+ }
17
+ type NotificationEventListener = (event: NotificationEvent) => void;
18
+ /**
19
+ * A tiny synchronous event bus for notification lifecycle events. Listeners can subscribe
20
+ * to a specific {@link NotificationEventType} or to all events with `"*"`.
21
+ */
22
+ declare class NotificationEventBus {
23
+ #private;
24
+ /**
25
+ * Subscribes to an event type (or `"*"` for all).
26
+ * @param type A specific {@link NotificationEventType} to listen for, or `"*"` for every event.
27
+ * @param listener The callback invoked with each matching event.
28
+ * @returns An unsubscribe function.
29
+ */
30
+ on(type: "*" | NotificationEventType, listener: NotificationEventListener): () => void;
31
+ /**
32
+ * Emits an event to matching listeners.
33
+ * @param event The event to emit.
34
+ */
35
+ emit(event: NotificationEvent): void;
36
+ }
37
+ export { type EventStore, MemoryEventStore, NotificationEventBus, NotificationEventListener };
@@ -0,0 +1 @@
1
+ import{default as f}from"../packem_shared/MemoryEventStore-BGD_xRxu.js";class n{#t=new Map;on(t,e){const o=this.#t.get(t)??new Set;return o.add(e),this.#t.set(t,o),()=>o.delete(e)}emit(t){for(const e of this.#t.get(t.type)??[])e(t);for(const e of this.#t.get("*")??[])e(t)}}export{f as MemoryEventStore,n as NotificationEventBus};
@@ -0,0 +1,174 @@
1
+ export { type M as Middleware, type S as SendContext, type a as SendFunction, c as composeMiddleware } from "./packem_shared/types.d-CximoA8M.js";
2
+ import { C as ChannelPayloadMap, a as NotificationMessage } from "./packem_shared/notification.d-BBLyVuqi.js";
3
+ export { N as Notification, type b as NotificationProviders, type S as SendManyOptions, c as createNotification } from "./packem_shared/notification.d-BBLyVuqi.js";
4
+ export { type P as Provider, type a as ProviderFactory, d as defineProvider } from "./packem_shared/provider.d-BQAGriz6.js";
5
+ export type { d as BaseConfig, B as BaseNotificationPayload, C as ChannelType, b as ChatPayload, f as EmailAddressLike, E as EmailChannelPayload, g as FailureReceipt, F as FeatureFlags, I as InAppPayload, M as MaybePromise, N as NotificationEvent, e as NotificationEventType, h as NotificationPayload, a as NotificationResult, P as PushPayload, c as Receipt, i as RecipientResult, R as Result, S as SmsPayload, j as SuccessReceipt, W as WebhookPayload } from "./packem_shared/types.d-GUzX1jTk.js";
6
+ interface ErrorProperties {
7
+ cause?: unknown;
8
+ hint?: ErrorHint;
9
+ location?: ErrorLocation;
10
+ message?: string;
11
+ name: string;
12
+ stack?: string;
13
+ title?: string;
14
+ }
15
+ interface ErrorLocation {
16
+ column?: number;
17
+ file?: string;
18
+ line?: number;
19
+ }
20
+ /**
21
+ * A message that explains to the user how they can fix the error.
22
+ * @example
23
+ * ```ts
24
+ * const error = new VisulimaError({
25
+ * hint: "Try running `npm install` to install missing dependencies.",
26
+ * location: {
27
+ * file: "src/index.ts",
28
+ * line: 1,
29
+ * column: 1,
30
+ * },
31
+ * message: "Cannot find module 'react'",
32
+ * name: "ModuleNotFoundError",
33
+ * });
34
+ * ```
35
+ *
36
+ * For more complex hints, you can pass an array of strings or a single string in markdown format.
37
+ */
38
+ type ErrorHint = string[] | string;
39
+ declare class VisulimaError extends Error {
40
+ loc: ErrorLocation | undefined;
41
+ title: string | undefined;
42
+ /**
43
+ * A message that explains to the user how they can fix the error.
44
+ */
45
+ hint: ErrorHint | undefined;
46
+ type: string;
47
+ constructor({
48
+ cause,
49
+ hint,
50
+ location,
51
+ message,
52
+ name,
53
+ stack,
54
+ title
55
+ }: ErrorProperties);
56
+ setLocation(location: ErrorLocation): void;
57
+ setName(name: string): void;
58
+ setMessage(message: string): void;
59
+ setHint(hint: ErrorHint): void;
60
+ }
61
+ /**
62
+ * Will return an array of all causes in the error in the order they occurred.
63
+ */
64
+ /**
65
+ * Base error class for notification package operations.
66
+ * @param component The component name where the error occurred.
67
+ * @param message The error message describing what went wrong.
68
+ * @param options Optional error options including cause, code, and hint.
69
+ */
70
+ declare class NotificationError extends VisulimaError {
71
+ readonly component: string;
72
+ readonly code?: string;
73
+ constructor(component: string, message: string, options?: {
74
+ cause?: Error | unknown;
75
+ code?: string;
76
+ hint?: string | string[];
77
+ });
78
+ }
79
+ /**
80
+ * Error for missing required options.
81
+ * @param component The component name where the error occurred.
82
+ * @param name The name(s) of the missing required option(s).
83
+ */
84
+ declare class RequiredOptionError extends NotificationError {
85
+ constructor(component: string, name: string | string[]);
86
+ }
87
+ /**
88
+ * Fluent builder for assembling a multi-channel {@link NotificationMessage}.
89
+ *
90
+ * Mirrors the ergonomics of `@visulima/email`'s `MailMessage`: chainable, per-channel
91
+ * setters that accumulate a plain message object. It is a thin convenience over the
92
+ * existing `NotificationMessage` shape and does not change the facade — `build()`
93
+ * returns the exact object `Notification.send()` already accepts.
94
+ * @example
95
+ * ```ts
96
+ * const message = createNotificationMessage()
97
+ * .sms({ text: "your code is 123", to: "+15555550100" })
98
+ * .push({ body: "deploy finished", title: "CI", to: "device-token" })
99
+ * .idempotencyKey("deploy-42")
100
+ * .build();
101
+ *
102
+ * await createNotification({ sms, push }).send(message);
103
+ * ```
104
+ */
105
+ declare class NotificationMessageBuilder {
106
+ #private;
107
+ /**
108
+ * Sets the SMS payload for the message.
109
+ * @param payload The SMS channel payload.
110
+ * @returns This instance for method chaining.
111
+ */
112
+ sms(payload: ChannelPayloadMap["sms"]): this;
113
+ /**
114
+ * Sets the push payload for the message.
115
+ * @param payload The push channel payload.
116
+ * @returns This instance for method chaining.
117
+ */
118
+ push(payload: ChannelPayloadMap["push"]): this;
119
+ /**
120
+ * Sets the chat payload for the message.
121
+ * @param payload The chat channel payload.
122
+ * @returns This instance for method chaining.
123
+ */
124
+ chat(payload: ChannelPayloadMap["chat"]): this;
125
+ /**
126
+ * Sets the in-app inbox payload for the message.
127
+ * @param payload The in-app channel payload.
128
+ * @returns This instance for method chaining.
129
+ */
130
+ inApp(payload: ChannelPayloadMap["inapp"]): this;
131
+ /**
132
+ * Sets the outbound webhook payload for the message.
133
+ * @param payload The webhook channel payload.
134
+ * @returns This instance for method chaining.
135
+ */
136
+ webhook(payload: ChannelPayloadMap["webhook"]): this;
137
+ /**
138
+ * Sets the email payload for the message.
139
+ * @param payload The email channel payload.
140
+ * @returns This instance for method chaining.
141
+ */
142
+ email(payload: ChannelPayloadMap["email"]): this;
143
+ /**
144
+ * Sets metadata applied to every present channel payload at build time.
145
+ *
146
+ * Per-channel `metadata` already set on a payload takes precedence over these
147
+ * builder-level values (the builder values are spread first).
148
+ * @param metadata Arbitrary metadata to attach.
149
+ * @returns This instance for method chaining.
150
+ */
151
+ metadata(metadata: Record<string, unknown>): this;
152
+ /**
153
+ * Sets an idempotency key applied to every present channel payload at build time.
154
+ *
155
+ * A per-channel `idempotencyKey` already set on a payload takes precedence.
156
+ * @param key The idempotency key used to dedupe retried sends.
157
+ * @returns This instance for method chaining.
158
+ */
159
+ idempotencyKey(key: string): this;
160
+ /**
161
+ * Builds the plain {@link NotificationMessage}.
162
+ *
163
+ * Applies builder-level `metadata` / `idempotencyKey` to each present channel
164
+ * payload without overriding values already set on the individual payloads.
165
+ * @returns The assembled multi-channel message.
166
+ */
167
+ build(): NotificationMessage;
168
+ }
169
+ /**
170
+ * Creates a new {@link NotificationMessageBuilder}.
171
+ * @returns A fresh fluent message builder.
172
+ */
173
+ declare const createNotificationMessage: () => NotificationMessageBuilder;
174
+ export { type ChannelPayloadMap, NotificationError, type NotificationMessage, NotificationMessageBuilder, RequiredOptionError, createNotificationMessage };
package/dist/index.js ADDED
@@ -0,0 +1 @@
1
+ import{default as r}from"./packem_shared/NotificationError-DVfMJQ1u.js";import{default as t}from"./packem_shared/RequiredOptionError-CuL42XRq.js";import{composeMiddleware as f}from"./packem_shared/composeMiddleware-D6dR8DUS.js";import{Notification as d,createNotification as p}from"./packem_shared/Notification-D1YeYxrQ.js";import{NotificationMessageBuilder as n,createNotificationMessage as s}from"./packem_shared/NotificationMessageBuilder-Br_aZusY.js";import{defineProvider as N}from"./packem_shared/defineProvider-C9q-7iwt.js";export{d as Notification,r as NotificationError,n as NotificationMessageBuilder,t as RequiredOptionError,f as composeMiddleware,p as createNotification,s as createNotificationMessage,N as defineProvider};