@visulima/notification 0.0.1 → 1.0.0-alpha.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +35 -0
- package/LICENSE.md +874 -0
- package/README.md +313 -6
- package/dist/channels/email/index.d.ts +46 -0
- package/dist/channels/email/index.js +1 -0
- package/dist/channels/inapp/index.d.ts +99 -0
- package/dist/channels/inapp/index.js +1 -0
- package/dist/digest/index.d.ts +128 -0
- package/dist/digest/index.js +1 -0
- package/dist/events/index.d.ts +37 -0
- package/dist/events/index.js +1 -0
- package/dist/i18n/index.d.ts +42 -0
- package/dist/i18n/index.js +1 -0
- package/dist/index.d.ts +174 -0
- package/dist/index.js +1 -0
- package/dist/layouts/index.d.ts +45 -0
- package/dist/layouts/index.js +1 -0
- package/dist/middleware/index.d.ts +382 -0
- package/dist/middleware/index.js +1 -0
- package/dist/packem_shared/MemoryDigestStore-Bw-sE5MI.js +1 -0
- package/dist/packem_shared/MemoryEventStore-BGD_xRxu.js +1 -0
- package/dist/packem_shared/MemoryInAppStore-48qlrDCx.js +1 -0
- package/dist/packem_shared/MemoryQueue-CXgWoZoY.js +1 -0
- package/dist/packem_shared/Notification-B22tuJPy.js +1 -0
- package/dist/packem_shared/NotificationError-DVfMJQ1u.js +1 -0
- package/dist/packem_shared/NotificationMessageBuilder-Br_aZusY.js +1 -0
- package/dist/packem_shared/RequiredOptionError-CuL42XRq.js +1 -0
- package/dist/packem_shared/UnstorageDigestStore-QAcO3Vb_.js +1 -0
- package/dist/packem_shared/UnstorageInAppStore-Bq2LoDMO.js +1 -0
- package/dist/packem_shared/apnsProvider-C9WSv5sc.js +1 -0
- package/dist/packem_shared/circuitBreakerMiddleware-BlZN7SVT.js +1 -0
- package/dist/packem_shared/composeMiddleware-D6dR8DUS.js +1 -0
- package/dist/packem_shared/createDigester-vMNPejGF.js +1 -0
- package/dist/packem_shared/createNotificationWorkflow-B70OgeED.js +1 -0
- package/dist/packem_shared/createQueueWorker-DjQBxjXc.js +1 -0
- package/dist/packem_shared/createTranslator-CY91C2eS.js +1 -0
- package/dist/packem_shared/credentials-CG0zkn-W.js +1 -0
- package/dist/packem_shared/dedupeMiddleware-CQmPJd_h.js +1 -0
- package/dist/packem_shared/defineLayout-73TNxYPq.js +1 -0
- package/dist/packem_shared/defineProvider-C9q-7iwt.js +1 -0
- package/dist/packem_shared/discordProvider-ewPfe-J7.js +1 -0
- package/dist/packem_shared/emailChannel-CNDlX96Q.js +1 -0
- package/dist/packem_shared/expoProvider-CXq101FD.js +1 -0
- package/dist/packem_shared/failoverProvider-D1dAmW8U.js +1 -0
- package/dist/packem_shared/fcmProvider-WdJGZ55l.js +1 -0
- package/dist/packem_shared/getHeader-C_gkdnMQ.js +1 -0
- package/dist/packem_shared/hmacBase64-BhhoX0uo.js +1 -0
- package/dist/packem_shared/http-BMYcUNBU.js +1 -0
- package/dist/packem_shared/id-C_FRCsUM.js +1 -0
- package/dist/packem_shared/inAppProvider-ElbahgSj.js +1 -0
- package/dist/packem_shared/loggingMiddleware-Cs6EDLcL.js +1 -0
- package/dist/packem_shared/messageBirdProvider-CHUFB0hd.js +1 -0
- package/dist/packem_shared/mockProvider-BLwTZlkw.js +1 -0
- package/dist/packem_shared/msTeamsProvider-DBhrmuSd.js +1 -0
- package/dist/packem_shared/notification.d-DFCVTods.d.ts +104 -0
- package/dist/packem_shared/otelProvider-b6LVo0BS.js +1 -0
- package/dist/packem_shared/plivoProvider-CIOl6DWw.js +1 -0
- package/dist/packem_shared/provider.d-BQAGriz6.d.ts +33 -0
- package/dist/packem_shared/rateLimitMiddleware-DWmEYq-m.js +1 -0
- package/dist/packem_shared/retryMiddleware-uUJDcUoP.js +1 -0
- package/dist/packem_shared/roundRobinProvider-BetXJUUm.js +1 -0
- package/dist/packem_shared/slackProvider-N1wNtAZ3.js +1 -0
- package/dist/packem_shared/slackWebhook-GEx_GdcV.js +1 -0
- package/dist/packem_shared/sms-CU-OZvNU.js +1 -0
- package/dist/packem_shared/snsProvider-KCciJ-nj.js +4 -0
- package/dist/packem_shared/snsWebhook-ChZnBBT3.js +1 -0
- package/dist/packem_shared/standardWebhook-B-LxP8U7.js +1 -0
- package/dist/packem_shared/suppressionMiddleware-CY-Qo1A_.js +1 -0
- package/dist/packem_shared/telegramProvider-jqR5BhJL.js +1 -0
- package/dist/packem_shared/telemetryMiddleware-B1N4k171.js +1 -0
- package/dist/packem_shared/telnyxProvider-C8TMPir1.js +1 -0
- package/dist/packem_shared/tracer.d-Co1s_NwG.d.ts +494 -0
- package/dist/packem_shared/twilioProvider-BxI0e6LX.js +1 -0
- package/dist/packem_shared/twilioWebhook-F0mTmGXD.js +1 -0
- package/dist/packem_shared/types.d-67TogK4k.d.ts +33 -0
- package/dist/packem_shared/types.d-C1GoEAlI.d-Bv3grfyD.d.ts +83 -0
- package/dist/packem_shared/types.d-CximoA8M.d.ts +29 -0
- package/dist/packem_shared/types.d-GUzX1jTk.d.ts +235 -0
- package/dist/packem_shared/types.d-td-F2Oh0.d.ts +10 -0
- package/dist/packem_shared/unstorage.DqlWKU2I.d-CogP3Etw.d.ts +120 -0
- package/dist/packem_shared/vonageProvider-BF76ca89.js +1 -0
- package/dist/packem_shared/webPushProvider-B962RwsA.js +1 -0
- package/dist/packem_shared/webcrypto-bY7wJCB5.js +1 -0
- package/dist/packem_shared/webhookProvider-BALSb2pc.js +1 -0
- package/dist/preferences/index.d.ts +49 -0
- package/dist/preferences/index.js +1 -0
- package/dist/providers/chat/discord/index.d.ts +14 -0
- package/dist/providers/chat/discord/index.js +1 -0
- package/dist/providers/chat/msteams/index.d.ts +14 -0
- package/dist/providers/chat/msteams/index.js +1 -0
- package/dist/providers/chat/slack/index.d.ts +19 -0
- package/dist/providers/chat/slack/index.js +1 -0
- package/dist/providers/chat/telegram/index.d.ts +18 -0
- package/dist/providers/chat/telegram/index.js +1 -0
- package/dist/providers/failover/index.d.ts +16 -0
- package/dist/providers/failover/index.js +1 -0
- package/dist/providers/mock/index.d.ts +29 -0
- package/dist/providers/mock/index.js +1 -0
- package/dist/providers/opentelemetry/index.d.ts +21 -0
- package/dist/providers/opentelemetry/index.js +1 -0
- package/dist/providers/push/apns/index.d.ts +24 -0
- package/dist/providers/push/apns/index.js +1 -0
- package/dist/providers/push/expo/index.d.ts +14 -0
- package/dist/providers/push/expo/index.js +1 -0
- package/dist/providers/push/fcm/index.d.ts +26 -0
- package/dist/providers/push/fcm/index.js +1 -0
- package/dist/providers/push/web-push/index.d.ts +36 -0
- package/dist/providers/push/web-push/index.js +1 -0
- package/dist/providers/roundrobin/index.d.ts +17 -0
- package/dist/providers/roundrobin/index.js +1 -0
- package/dist/providers/sms/messagebird/index.d.ts +16 -0
- package/dist/providers/sms/messagebird/index.js +1 -0
- package/dist/providers/sms/plivo/index.d.ts +18 -0
- package/dist/providers/sms/plivo/index.js +1 -0
- package/dist/providers/sms/sns/index.d.ts +21 -0
- package/dist/providers/sms/sns/index.js +1 -0
- package/dist/providers/sms/telnyx/index.d.ts +18 -0
- package/dist/providers/sms/telnyx/index.js +1 -0
- package/dist/providers/sms/twilio/index.d.ts +20 -0
- package/dist/providers/sms/twilio/index.js +1 -0
- package/dist/providers/sms/vonage/index.d.ts +18 -0
- package/dist/providers/sms/vonage/index.js +1 -0
- package/dist/providers/webhook/index.d.ts +18 -0
- package/dist/providers/webhook/index.js +1 -0
- package/dist/queue/bullmq-queue.d.ts +58 -0
- package/dist/queue/bullmq-queue.js +1 -0
- package/dist/queue/index.d.ts +50 -0
- package/dist/queue/index.js +1 -0
- package/dist/queue/pg-boss-queue.d.ts +66 -0
- package/dist/queue/pg-boss-queue.js +1 -0
- package/dist/queue/sqs-queue.d.ts +91 -0
- package/dist/queue/sqs-queue.js +1 -0
- package/dist/queue/unstorage-queue.d.ts +30 -0
- package/dist/queue/unstorage-queue.js +1 -0
- package/dist/routing/index.d.ts +35 -0
- package/dist/routing/index.js +1 -0
- package/dist/template-engines/handlebars.d.ts +16 -0
- package/dist/template-engines/handlebars.js +1 -0
- package/dist/template-engines/liquid.d.ts +16 -0
- package/dist/template-engines/liquid.js +1 -0
- package/dist/template-engines/string.d.ts +20 -0
- package/dist/template-engines/string.js +1 -0
- package/dist/utils/result.d.ts +54 -0
- package/dist/utils/result.js +1 -0
- package/dist/webhooks/index.d.ts +110 -0
- package/dist/webhooks/index.js +1 -0
- package/dist/workflow/index.d.ts +83 -0
- package/dist/workflow/index.js +1 -0
- package/package.json +283 -43
package/README.md
CHANGED
|
@@ -1,12 +1,319 @@
|
|
|
1
|
-
|
|
1
|
+
<!-- START_PACKAGE_OG_IMAGE_PLACEHOLDER -->
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
<a href="https://www.anolilab.com/open-source" align="center">
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
<img src="__assets__/package-og.svg" alt="notification" />
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
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
|
+
> **Every provider is `fetch`-based** — no per-provider SDK to install, and it runs on Node, Cloudflare Workers, Vercel
|
|
60
|
+
> Edge, Deno and Bun. Optional peers are only needed by specific subpaths (e.g. `liquidjs` for `./template/liquid`,
|
|
61
|
+
> `@visulima/workflow` for `./workflow`) — see the [install table](https://visulima.com/docs/packages/notification/installation#optional-peer-dependencies).
|
|
62
|
+
> Beyond sending, the package ships [workflows](https://visulima.com/docs/packages/notification/workflow),
|
|
63
|
+
> [digest](https://visulima.com/docs/packages/notification/digest),
|
|
64
|
+
> [layouts](https://visulima.com/docs/packages/notification/layouts) and
|
|
65
|
+
> [i18n](https://visulima.com/docs/packages/notification/i18n).
|
|
66
|
+
|
|
67
|
+
### Quick send (one channel, no setup)
|
|
68
|
+
|
|
69
|
+
```typescript
|
|
70
|
+
import { send } from "@visulima/notification";
|
|
71
|
+
import { twilioProvider } from "@visulima/notification/providers/twilio";
|
|
72
|
+
|
|
73
|
+
await send("sms", twilioProvider({ accountSid: "AC…", authToken: "…", from: "+15555550100" }), { to: "+15555550100", text: "Your code is 123" });
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### Multi-channel send
|
|
77
|
+
|
|
78
|
+
```typescript
|
|
79
|
+
import { createNotification } from "@visulima/notification";
|
|
80
|
+
import { twilioProvider } from "@visulima/notification/providers/twilio";
|
|
81
|
+
import { slackProvider } from "@visulima/notification/providers/slack";
|
|
82
|
+
import { fcmProvider } from "@visulima/notification/providers/fcm";
|
|
83
|
+
|
|
84
|
+
const notify = createNotification({
|
|
85
|
+
sms: twilioProvider({ accountSid: "AC…", authToken: "…", from: "+15555550100" }),
|
|
86
|
+
chat: slackProvider({ token: "xoxb-…", defaultChannel: "C123" }),
|
|
87
|
+
push: fcmProvider({ projectId: "my-app", getAccessToken: async () => getGoogleToken() }),
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
// Each present channel is delivered in parallel; you get one receipt per channel.
|
|
91
|
+
const receipts = await notify.send({
|
|
92
|
+
sms: { to: "+15555550100", text: "Your code is 123" },
|
|
93
|
+
chat: { text: "🚀 Deploy finished" },
|
|
94
|
+
push: { to: ["device-token"], title: "Deploy", body: "Finished" },
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
for (const receipt of receipts) {
|
|
98
|
+
if (receipt.successful) {
|
|
99
|
+
console.log(`${receipt.channel}: ${receipt.messageId}`);
|
|
100
|
+
} else {
|
|
101
|
+
console.error(`${receipt.channel} failed:`, receipt.errorMessages);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### Single-channel send
|
|
107
|
+
|
|
108
|
+
```typescript
|
|
109
|
+
const receipt = await notify.sendToChannel("sms", { to: "+15555550100", text: "Hi" });
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### Batch send with bounded concurrency
|
|
113
|
+
|
|
114
|
+
```typescript
|
|
115
|
+
for await (const receipts of notify.sendMany(messages, { concurrency: 10 })) {
|
|
116
|
+
// receipts for one message
|
|
117
|
+
}
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## Channels & providers
|
|
121
|
+
|
|
122
|
+
Providers are imported from `@visulima/notification/providers/<name>` so unused integrations are tree-shaken away.
|
|
123
|
+
|
|
124
|
+
| Channel | Providers |
|
|
125
|
+
| ------------ | --------------------------------------------------------------- |
|
|
126
|
+
| **SMS** | `twilio`, `vonage`, `plivo`, `messagebird`, `telnyx`, `sns` |
|
|
127
|
+
| **Push** | `fcm`, `expo`, `web-push`, `apns` |
|
|
128
|
+
| **Chat** | `slack`, `discord`, `msteams`, `telegram` |
|
|
129
|
+
| **In-app** | `inAppProvider` (memory or unstorage store) |
|
|
130
|
+
| **Webhook** | `webhook` |
|
|
131
|
+
| **Email** | `emailChannel(...)` → wraps a `@visulima/email` `Mail` instance |
|
|
132
|
+
| **Wrappers** | `failover`, `roundrobin`, `opentelemetry`, `mock` |
|
|
133
|
+
|
|
134
|
+
> **Cloudflare / edge note:** nearly every provider is `fetch` + Web Crypto only and runs on **Cloudflare Workers**,
|
|
135
|
+
> Vercel Edge, Deno and Bun — including **AWS SNS** (Web Crypto SigV4) and **web-push** (Web Crypto VAPID + RFC 8291).
|
|
136
|
+
> FCM accepts a `getAccessToken` callback so you bring your own OAuth token without a Node-only SDK. The only **Node-only**
|
|
137
|
+
> pieces are **APNs** (`node:http2`) and the **bullmq / pg-boss / sqs** queue adapters — import those from a Node runtime.
|
|
138
|
+
> See the [runtime matrix](https://visulima.com/docs/packages/notification/installation#runtime-support).
|
|
139
|
+
|
|
140
|
+
## Writing a provider
|
|
141
|
+
|
|
142
|
+
Every provider implements the same contract; author one with `defineProvider`:
|
|
143
|
+
|
|
144
|
+
```typescript
|
|
145
|
+
import { defineProvider } from "@visulima/notification";
|
|
146
|
+
|
|
147
|
+
export const myProvider = defineProvider<MyConfig, SmsPayload>((config) => ({
|
|
148
|
+
id: "my-provider",
|
|
149
|
+
channel: "sms",
|
|
150
|
+
features: { batchSending: false },
|
|
151
|
+
initialize: () => {},
|
|
152
|
+
isAvailable: () => Boolean(config?.apiKey),
|
|
153
|
+
send: async (payload) => {
|
|
154
|
+
// … return { success: true, data: { messageId, channel: "sms", provider: "my-provider", sent: true, timestamp: new Date() } }
|
|
155
|
+
},
|
|
156
|
+
}));
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
## Failover & round-robin
|
|
160
|
+
|
|
161
|
+
Wrap several same-channel providers to gain resilience or load balancing:
|
|
162
|
+
|
|
163
|
+
```typescript
|
|
164
|
+
import { failoverProvider } from "@visulima/notification/providers/failover";
|
|
165
|
+
import { roundRobinProvider } from "@visulima/notification/providers/roundrobin";
|
|
166
|
+
|
|
167
|
+
const sms = failoverProvider([twilioProvider({ … }), vonageProvider({ … })]); // try Twilio, fall back to Vonage
|
|
168
|
+
const balanced = roundRobinProvider([plivoProvider({ … }), telnyxProvider({ … })]);
|
|
169
|
+
|
|
170
|
+
const notify = createNotification({ sms });
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
## Routing (channel fallback & broadcast)
|
|
174
|
+
|
|
175
|
+
`route(...)` adds channel-sequence delivery and a gate, on top of per-channel provider failover:
|
|
176
|
+
|
|
177
|
+
```typescript
|
|
178
|
+
import { route } from "@visulima/notification/routing";
|
|
179
|
+
|
|
180
|
+
// best-of: try sms, then push, then email — stop at the first success
|
|
181
|
+
await route(notify, message, { order: ["sms", "push", "email"], mode: "best-of" });
|
|
182
|
+
|
|
183
|
+
// all: broadcast to every present channel in parallel
|
|
184
|
+
await route(notify, message, { mode: "all" });
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
## Preferences
|
|
188
|
+
|
|
189
|
+
Honour per-subscriber, per-channel opt-outs (critical sends bypass them):
|
|
190
|
+
|
|
191
|
+
```typescript
|
|
192
|
+
import { MemoryPreferenceStore, preferencesGate } from "@visulima/notification/preferences";
|
|
193
|
+
|
|
194
|
+
const prefs = new MemoryPreferenceStore();
|
|
195
|
+
prefs.set("user-1", { channels: { sms: false } });
|
|
196
|
+
|
|
197
|
+
await route(notify, message, { gate: preferencesGate(prefs) });
|
|
198
|
+
await route(notify, message, { gate: preferencesGate(prefs, { critical: true }) }); // bypasses opt-outs
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
## Middleware
|
|
202
|
+
|
|
203
|
+
Cross-cutting concerns compose around every send (first registered = outermost):
|
|
204
|
+
|
|
205
|
+
```typescript
|
|
206
|
+
import { retryMiddleware, rateLimitMiddleware, circuitBreakerMiddleware, dedupeMiddleware, loggingMiddleware } from "@visulima/notification/middleware";
|
|
207
|
+
|
|
208
|
+
notify
|
|
209
|
+
.use(loggingMiddleware())
|
|
210
|
+
.use(retryMiddleware({ retries: 3 }))
|
|
211
|
+
.use(rateLimitMiddleware({ rate: 100, interval: 1000 }))
|
|
212
|
+
.use(circuitBreakerMiddleware({ threshold: 5 }))
|
|
213
|
+
.use(dedupeMiddleware({ ttl: 60_000 }));
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
## Queue & worker
|
|
217
|
+
|
|
218
|
+
Decouple enqueue from delivery with a durable queue and a retrying worker:
|
|
219
|
+
|
|
220
|
+
```typescript
|
|
221
|
+
import { MemoryQueue, createQueueWorker } from "@visulima/notification/queue";
|
|
222
|
+
|
|
223
|
+
const queue = new MemoryQueue();
|
|
224
|
+
queue.enqueue({ sms: { to: "+15555550100", text: "Hi" } });
|
|
225
|
+
|
|
226
|
+
const worker = createQueueWorker(queue, notify, { maxAttempts: 5 });
|
|
227
|
+
worker.start(); // or `await worker.drain()` to process all due jobs once
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
Back the queue with [unstorage](https://unstorage.unjs.io) for Redis/filesystem/KV persistence:
|
|
231
|
+
|
|
232
|
+
```typescript
|
|
233
|
+
import { UnstorageQueue } from "@visulima/notification/queue/unstorage";
|
|
234
|
+
import { createStorage } from "unstorage";
|
|
235
|
+
|
|
236
|
+
const queue = new UnstorageQueue(createStorage());
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
## In-app inbox
|
|
240
|
+
|
|
241
|
+
```typescript
|
|
242
|
+
import { inAppProvider } from "@visulima/notification/channels/inapp";
|
|
243
|
+
|
|
244
|
+
const inapp = inAppProvider();
|
|
245
|
+
const notify = createNotification({ inapp });
|
|
246
|
+
|
|
247
|
+
await notify.sendToChannel("inapp", { to: "user-1", title: "Welcome", body: "Thanks for joining" });
|
|
248
|
+
|
|
249
|
+
const store = inapp.getInstance();
|
|
250
|
+
await store.unreadCount("user-1"); // 1
|
|
251
|
+
await store.list("user-1");
|
|
252
|
+
await store.markAllRead("user-1");
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
## Email channel
|
|
256
|
+
|
|
257
|
+
The email channel delegates to a configured [`@visulima/email`](https://visulima.com/packages/email) `Mail` instance, so
|
|
258
|
+
you reuse its 29 providers, templates and deliverability tooling:
|
|
259
|
+
|
|
260
|
+
```typescript
|
|
261
|
+
import { createMail } from "@visulima/email";
|
|
262
|
+
import { resendProvider } from "@visulima/email/providers/resend";
|
|
263
|
+
import { createNotification } from "@visulima/notification";
|
|
264
|
+
import { emailChannel } from "@visulima/notification/channels/email";
|
|
265
|
+
|
|
266
|
+
const mail = createMail(resendProvider({ apiKey: "re_…" }));
|
|
267
|
+
const notify = createNotification({ email: emailChannel(mail) });
|
|
268
|
+
|
|
269
|
+
await notify.sendToChannel("email", { from: "noreply@app.com", to: "user@x.com", subject: "Hi", html: "<p>Hello</p>" });
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
## Events
|
|
273
|
+
|
|
274
|
+
Subscribe to lifecycle events and build a timeline:
|
|
275
|
+
|
|
276
|
+
```typescript
|
|
277
|
+
import { NotificationEventBus, MemoryEventStore } from "@visulima/notification/events";
|
|
278
|
+
|
|
279
|
+
const bus = new NotificationEventBus();
|
|
280
|
+
bus.on("sent", (event) => console.log(event.messageId));
|
|
281
|
+
bus.on("*", (event) => store.append(event));
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
## Supported Node.js Versions
|
|
285
|
+
|
|
286
|
+
Libraries in this ecosystem make the best effort to track [Node.js' release schedule](https://github.com/nodejs/release#release-schedule).
|
|
287
|
+
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).
|
|
288
|
+
|
|
289
|
+
## Contributing
|
|
290
|
+
|
|
291
|
+
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.
|
|
292
|
+
|
|
293
|
+
> **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.
|
|
294
|
+
|
|
295
|
+
## Credits
|
|
296
|
+
|
|
297
|
+
- [Daniel Bannert](https://github.com/prisis)
|
|
298
|
+
- [All Contributors](https://github.com/visulima/visulima/graphs/contributors)
|
|
299
|
+
|
|
300
|
+
## Made with ❤️ at Anolilab
|
|
301
|
+
|
|
302
|
+
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
303
|
|
|
10
304
|
## License
|
|
11
305
|
|
|
12
|
-
|
|
306
|
+
The visulima notification is open-sourced software licensed under the [MIT][license]
|
|
307
|
+
|
|
308
|
+
<!-- badges -->
|
|
309
|
+
|
|
310
|
+
[license-badge]: https://img.shields.io/npm/l/@visulima/notification?style=for-the-badge
|
|
311
|
+
[license]: https://github.com/visulima/visulima/blob/main/LICENSE
|
|
312
|
+
[npm-downloads-badge]: https://img.shields.io/npm/dm/@visulima/notification?style=for-the-badge
|
|
313
|
+
[npm-downloads]: https://www.npmjs.com/package/@visulima/notification
|
|
314
|
+
[prs-welcome-badge]: https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=for-the-badge
|
|
315
|
+
[prs-welcome]: https://github.com/visulima/visulima/blob/main/.github/CONTRIBUTING.md
|
|
316
|
+
[chat-badge]: https://img.shields.io/discord/932323359193186354.svg?style=for-the-badge
|
|
317
|
+
[chat]: https://discord.gg/TtFJY8xkFK
|
|
318
|
+
[typescript-badge]: https://img.shields.io/badge/Typescript-294E80.svg?style=for-the-badge&logo=typescript
|
|
319
|
+
[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,128 @@
|
|
|
1
|
+
import { M as MaybePromise, D as Duration } from "../packem_shared/types.d-C1GoEAlI.d-Bv3grfyD.js";
|
|
2
|
+
import '@standard-schema/spec';
|
|
3
|
+
/** A single event buffered inside a digest window. */
|
|
4
|
+
interface DigestEvent<PayloadT> {
|
|
5
|
+
/** Stable id of this event within the window. */
|
|
6
|
+
id: string;
|
|
7
|
+
/** The event payload. */
|
|
8
|
+
payload: PayloadT;
|
|
9
|
+
/** Epoch ms the event was added. */
|
|
10
|
+
time: number;
|
|
11
|
+
}
|
|
12
|
+
/** An open digest window: the events collected so far for a key, and when it closes. */
|
|
13
|
+
interface DigestWindow<PayloadT> {
|
|
14
|
+
events: DigestEvent<PayloadT>[];
|
|
15
|
+
key: string;
|
|
16
|
+
/** Epoch ms at which the window is due to flush. */
|
|
17
|
+
wakeAt: number;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Durability contract for `createDigester`. Implementations own the buffered
|
|
21
|
+
* windows and the "what is due" query; the digester stays storage-agnostic.
|
|
22
|
+
*
|
|
23
|
+
* `read` and `remove` are separate (rather than a single destructive `drain`) so
|
|
24
|
+
* the digester can flush a window's events *before* removing it — a failed flush
|
|
25
|
+
* leaves the window in place to be retried (at-least-once).
|
|
26
|
+
*/
|
|
27
|
+
interface DigestStore<PayloadT> {
|
|
28
|
+
/**
|
|
29
|
+
* Append an event to the window for `key`, opening it (with `wakeAt`) if absent.
|
|
30
|
+
* The window's `wakeAt` is fixed when it opens — later events do not extend it.
|
|
31
|
+
* @returns `true` if this call opened a new window.
|
|
32
|
+
*/
|
|
33
|
+
append: (key: string, event: DigestEvent<PayloadT>, wakeAt: number) => Promise<boolean>;
|
|
34
|
+
/** Keys whose window `wakeAt` is at or before `now`, up to `limit`. */
|
|
35
|
+
due: (now: number, limit: number) => Promise<string[]>;
|
|
36
|
+
/** Return the window for `key` without removing it, or `undefined` if none. */
|
|
37
|
+
read: (key: string) => Promise<DigestWindow<PayloadT> | undefined>;
|
|
38
|
+
/** Permanently remove the window for `key`. */
|
|
39
|
+
remove: (key: string) => Promise<void>;
|
|
40
|
+
}
|
|
41
|
+
/** Options for `createDigester`. */
|
|
42
|
+
interface DigesterOptions<PayloadT> {
|
|
43
|
+
/** Group events into windows by this key (e.g. `subscriberId` or `subscriberId:postId`). */
|
|
44
|
+
key: (event: PayloadT) => string;
|
|
45
|
+
/**
|
|
46
|
+
* Called once per window when it closes, with every event collected. Delivery is
|
|
47
|
+
* **at-least-once**: the window is removed only after `onFlush` resolves, so a
|
|
48
|
+
* throwing `onFlush` is retried on the next sweep (and may run again if removal
|
|
49
|
+
* later fails). Make it idempotent.
|
|
50
|
+
*/
|
|
51
|
+
onFlush: (events: DigestEvent<PayloadT>[], key: string) => MaybePromise<void>;
|
|
52
|
+
/** The durable store; defaults to an in-memory store. */
|
|
53
|
+
store?: DigestStore<PayloadT>;
|
|
54
|
+
/** How long a window stays open from its first event (a {@link Duration} or a per-event function). */
|
|
55
|
+
window: ((event: PayloadT) => Duration) | Duration;
|
|
56
|
+
}
|
|
57
|
+
/** Aggregates many events into windowed batches, flushing each as one notification. */
|
|
58
|
+
interface Digester<PayloadT> {
|
|
59
|
+
/** Buffer an event; returns `true` if it opened a new window for its key. */
|
|
60
|
+
add: (event: PayloadT) => Promise<boolean>;
|
|
61
|
+
/** Flush every window whose wake-at has passed; returns the number flushed. */
|
|
62
|
+
sweep: (now?: number, limit?: number) => Promise<number>;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Create a {@link Digester} that batches events into time/cron windows keyed by
|
|
66
|
+
* `options.key`, flushing each closed window once via `options.onFlush` — turning
|
|
67
|
+
* a burst of N events into a single notification.
|
|
68
|
+
*
|
|
69
|
+
* Windows close on a poll: call {@link Digester.sweep} from a cron job, a
|
|
70
|
+
* Cloudflare alarm, or any timer (alongside a workflow runtime's `sweep`).
|
|
71
|
+
* @param options Key function, window duration, flush handler and optional store.
|
|
72
|
+
* @returns A {@link Digester}.
|
|
73
|
+
* @example
|
|
74
|
+
* ```ts
|
|
75
|
+
* const digester = createDigester<{ subscriberId: string; postId: string }>({
|
|
76
|
+
* key: (event) => `${event.subscriberId}:${event.postId}`,
|
|
77
|
+
* window: { amount: 10, unit: "minutes" },
|
|
78
|
+
* onFlush: (events) => runtime.trigger(summaryWorkflow, { count: events.length, events }),
|
|
79
|
+
* });
|
|
80
|
+
*
|
|
81
|
+
* await digester.add({ subscriberId: "u1", postId: "p1" }); // opens a 10-minute window
|
|
82
|
+
* // …later, on a timer:
|
|
83
|
+
* await digester.sweep();
|
|
84
|
+
* ```
|
|
85
|
+
*/
|
|
86
|
+
declare const createDigester: <PayloadT>(options: DigesterOptions<PayloadT>) => Digester<PayloadT>;
|
|
87
|
+
/**
|
|
88
|
+
* In-process {@link DigestStore}. Ideal for tests and single-instance apps; swap
|
|
89
|
+
* for the unstorage adapter for durability across restarts and instances.
|
|
90
|
+
*/
|
|
91
|
+
declare class MemoryDigestStore<PayloadT> implements DigestStore<PayloadT> {
|
|
92
|
+
#private;
|
|
93
|
+
append(key: string, event: DigestEvent<PayloadT>, wakeAt: number): Promise<boolean>;
|
|
94
|
+
read(key: string): Promise<DigestWindow<PayloadT> | undefined>;
|
|
95
|
+
remove(key: string): Promise<void>;
|
|
96
|
+
due(now: number, limit: number): Promise<string[]>;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Minimal structural view of an [unstorage](https://unstorage.unjs.io) instance,
|
|
100
|
+
* declared locally so the package needs no hard `unstorage` dependency.
|
|
101
|
+
*/
|
|
102
|
+
interface UnstorageLike {
|
|
103
|
+
getItem: <T = unknown>(key: string) => Promise<T | null>;
|
|
104
|
+
getKeys: (base?: string) => Promise<string[]>;
|
|
105
|
+
removeItem: (key: string) => Promise<void>;
|
|
106
|
+
setItem: (key: string, value: unknown) => Promise<void>;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* {@link DigestStore} backed by [unstorage](https://unstorage.unjs.io), for durable,
|
|
110
|
+
* edge-friendly digest windows over any unstorage driver.
|
|
111
|
+
*
|
|
112
|
+
* Each window is a single self-contained document keyed by `digest:w:` plus the
|
|
113
|
+
* digest key; there is no shared index, so concurrent `add`s for different keys cannot
|
|
114
|
+
* lose-update each other. `due` scans the window documents under the prefix — fine for the
|
|
115
|
+
* transient, bounded set of open windows. Individual reads/writes are still not
|
|
116
|
+
* transactional, so concurrent writers on the *same* key (or a sweep racing an
|
|
117
|
+
* `add` for that key) can drop an event or double-flush; for that level of
|
|
118
|
+
* contention prefer a store with atomic guarantees, or sweep from one instance.
|
|
119
|
+
*/
|
|
120
|
+
declare class UnstorageDigestStore<PayloadT> implements DigestStore<PayloadT> {
|
|
121
|
+
#private;
|
|
122
|
+
constructor(storage: UnstorageLike);
|
|
123
|
+
append(key: string, event: DigestEvent<PayloadT>, wakeAt: number): Promise<boolean>;
|
|
124
|
+
read(key: string): Promise<DigestWindow<PayloadT> | undefined>;
|
|
125
|
+
remove(key: string): Promise<void>;
|
|
126
|
+
due(now: number, limit: number): Promise<string[]>;
|
|
127
|
+
}
|
|
128
|
+
export { type DigestEvent, type DigestStore, type DigestWindow, type Digester, type DigesterOptions, MemoryDigestStore, UnstorageDigestStore, createDigester };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{default as r}from"../packem_shared/createDigester-vMNPejGF.js";import{default as a}from"../packem_shared/MemoryDigestStore-Bw-sE5MI.js";import{default as f}from"../packem_shared/UnstorageDigestStore-QAcO3Vb_.js";export{a as MemoryDigestStore,f as UnstorageDigestStore,r as createDigester};
|
|
@@ -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};
|