@mostajs/notifications 0.1.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.
- package/README.md +444 -0
- package/dist/index.d.ts +98 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +118 -0
- package/dist/index.js.map +1 -0
- package/package.json +35 -0
package/README.md
ADDED
|
@@ -0,0 +1,444 @@
|
|
|
1
|
+
# @mostajs/notifications
|
|
2
|
+
|
|
3
|
+
**Auteur** : Dr Hamid MADANI <drmdh@msn.com>
|
|
4
|
+
**License** : AGPL-3.0-or-later
|
|
5
|
+
**Version** : 0.1.0
|
|
6
|
+
|
|
7
|
+
Moteur **générique** de notifications pour l'écosystème `@mostajs/*` — découple le **dispatch** (mailer + i18n + templating + idempotence) du **domaine métier** (booking, media-sfu, custom apps). Tout domaine peut envoyer des notifications en appelant `engine.notify(kind, context)` ; les adapters spécifiques (`@mostajs/booking-notifications`, `@mostajs/media-sfu-notifications`, …) sont de fins glues qui mappent leurs events internes en kinds génériques.
|
|
8
|
+
|
|
9
|
+
📊 **Étude état de l'art** (Knock, Courier, Novu, SendGrid, Postmark, Resend, Customer.io, …) → [`docs/STATE-OF-THE-ART.md`](./docs/STATE-OF-THE-ART.md)
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Table des matières
|
|
14
|
+
|
|
15
|
+
1. [Pourquoi un moteur générique](#1-pourquoi-un-moteur-générique)
|
|
16
|
+
2. [Architecture](#2-architecture)
|
|
17
|
+
3. [Quick start — how to use](#3-quick-start--how-to-use)
|
|
18
|
+
4. [API détaillée](#4-api-détaillée)
|
|
19
|
+
5. [Templates — patterns recommandés](#5-templates--patterns-recommandés)
|
|
20
|
+
6. [Implémentation — how to impl](#6-implémentation--how-to-impl)
|
|
21
|
+
7. [I18n & locales](#7-i18n--locales)
|
|
22
|
+
8. [Idempotence](#8-idempotence)
|
|
23
|
+
9. [Tests](#9-tests)
|
|
24
|
+
10. [Modules liés](#10-modules-liés)
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## 1. Pourquoi un moteur générique
|
|
29
|
+
|
|
30
|
+
Avant ce module, chaque domaine `@mostajs/*` qui voulait envoyer un mail dupliquait :
|
|
31
|
+
- Templates locale (FR / EN / AR)
|
|
32
|
+
- Dispatch via `@mostajs/mailer`
|
|
33
|
+
- Idempotence (`messageId`)
|
|
34
|
+
- Retry / fallback / callbacks
|
|
35
|
+
|
|
36
|
+
Avec ce module :
|
|
37
|
+
- `@mostajs/booking-notifications` : 100 LOC de glue (subscribe events booking → notify)
|
|
38
|
+
- `@mostajs/media-sfu-notifications` (futur) : 80 LOC de glue (subscribe events SFU → notify)
|
|
39
|
+
- `@mostajs/orphan-care-notifications` (futur) : idem
|
|
40
|
+
- Plomberie commune (templates registry, mailer, i18n, idempotence) : **un seul code à maintenir**
|
|
41
|
+
|
|
42
|
+
Cas où ne PAS utiliser ce module :
|
|
43
|
+
- App très simple avec 1 seul mail à envoyer → utilise `@mostajs/mailer` directement
|
|
44
|
+
- Domaine très spécifique avec templates DB-driven (CMS-like) → roll your own avec `@mostajs/mailer`
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
## 2. Architecture
|
|
49
|
+
|
|
50
|
+
```
|
|
51
|
+
┌──────────────────────────────────────────────────────────────┐
|
|
52
|
+
│ App / Adapter consumer │
|
|
53
|
+
│ - subscribe events @mostajs/booking, @mostajs/media-sfu, … │
|
|
54
|
+
│ - map event → { kind: 'booking.reservation.created', │
|
|
55
|
+
│ context: { reservation, slot, ... } } │
|
|
56
|
+
│ - appelle engine.notify(...) │
|
|
57
|
+
└──────────────────────────────────┬───────────────────────────┘
|
|
58
|
+
│
|
|
59
|
+
▼
|
|
60
|
+
┌──────────────────────────────────────────────────────────────┐
|
|
61
|
+
│ @mostajs/notifications — NotificationEngine │
|
|
62
|
+
│ │
|
|
63
|
+
│ ┌──────────────────┐ ┌──────────────────┐ │
|
|
64
|
+
│ │ Templates │ │ Locale picker │ │
|
|
65
|
+
│ │ registry │ ◀─│ ctx.locale → │ │
|
|
66
|
+
│ │ (kind → tpl) │ │ variant FR/EN/AR │ │
|
|
67
|
+
│ └────────┬─────────┘ └──────────────────┘ │
|
|
68
|
+
│ ▼ │
|
|
69
|
+
│ ┌──────────────────┐ ┌──────────────────┐ │
|
|
70
|
+
│ │ render(ctx) │ → │ Mail (subject, │ │
|
|
71
|
+
│ │ → RenderedMail │ │ to, html, text) │ │
|
|
72
|
+
│ └────────┬─────────┘ └────────┬─────────┘ │
|
|
73
|
+
│ ▼ ▼ │
|
|
74
|
+
│ ┌──────────────────────────────────────────┐ │
|
|
75
|
+
│ │ Idempotence (messageId) │ │
|
|
76
|
+
│ │ Callbacks (onSent / onError) │ │
|
|
77
|
+
│ └────────────────────┬─────────────────────┘ │
|
|
78
|
+
└────────────────────────┼─────────────────────────────────────┘
|
|
79
|
+
│
|
|
80
|
+
▼
|
|
81
|
+
┌──────────────────────────────────────────────────────────────┐
|
|
82
|
+
│ @mostajs/mailer — driver-based dispatch │
|
|
83
|
+
│ SMTP / Resend / Postmark / SES / Brevo / Console / Mock │
|
|
84
|
+
└──────────────────────────────────────────────────────────────┘
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
---
|
|
88
|
+
|
|
89
|
+
## 3. Quick start — how to use
|
|
90
|
+
|
|
91
|
+
### Installation
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
npm install @mostajs/notifications @mostajs/mailer
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### Bootstrap minimal
|
|
98
|
+
|
|
99
|
+
```ts
|
|
100
|
+
import { createNotificationEngine, simpleTemplate, localizedTemplate } from '@mostajs/notifications'
|
|
101
|
+
import { createMailer, createConsoleDriver } from '@mostajs/mailer'
|
|
102
|
+
|
|
103
|
+
const mailer = createMailer({ driver: createConsoleDriver() })
|
|
104
|
+
|
|
105
|
+
const engine = createNotificationEngine({
|
|
106
|
+
mailer,
|
|
107
|
+
defaultFrom: 'noreply@example.com',
|
|
108
|
+
defaultLocale: 'fr',
|
|
109
|
+
templates: {
|
|
110
|
+
'welcome': simpleTemplate({
|
|
111
|
+
to: ctx => (ctx.user as any)?.email ?? null,
|
|
112
|
+
subject: ctx => `Bienvenue ${(ctx.user as any)?.name}`,
|
|
113
|
+
html: ctx => `<h1>Bonjour ${(ctx.user as any)?.name}</h1><p>Heureux de vous accueillir !</p>`,
|
|
114
|
+
}),
|
|
115
|
+
},
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
await engine.notify('welcome', {
|
|
119
|
+
user: { name: 'Amina', email: 'amina@example.com' },
|
|
120
|
+
})
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### Template multi-locale (FR + EN + AR)
|
|
124
|
+
|
|
125
|
+
```ts
|
|
126
|
+
engine.register('appointment.reminder', localizedTemplate({
|
|
127
|
+
fr: {
|
|
128
|
+
subject: ctx => `Rappel : votre rendez-vous ${formatDate(ctx.startAt)}`,
|
|
129
|
+
html: ctx => `<p>Bonjour, n'oubliez pas votre rendez-vous demain à <strong>${formatDate(ctx.startAt)}</strong>.</p>`,
|
|
130
|
+
},
|
|
131
|
+
en: {
|
|
132
|
+
subject: ctx => `Reminder: your appointment ${formatDate(ctx.startAt)}`,
|
|
133
|
+
html: ctx => `<p>Hi, don't forget your appointment tomorrow at <strong>${formatDate(ctx.startAt)}</strong>.</p>`,
|
|
134
|
+
},
|
|
135
|
+
ar: {
|
|
136
|
+
subject: ctx => `تذكير: موعدكم ${formatDate(ctx.startAt)}`,
|
|
137
|
+
html: ctx => `<p>تذكير بموعدكم غداً في <strong>${formatDate(ctx.startAt)}</strong>.</p>`,
|
|
138
|
+
},
|
|
139
|
+
}, ctx => (ctx.user as any)?.email,
|
|
140
|
+
ctx => `appt-reminder-${(ctx as any).reservationId}-24h`, // idempotence
|
|
141
|
+
))
|
|
142
|
+
|
|
143
|
+
// L'app envoie selon la locale du user :
|
|
144
|
+
await engine.notify('appointment.reminder', {
|
|
145
|
+
user: { email: 'patient@example.com' },
|
|
146
|
+
startAt: Date.now() + 86400_000,
|
|
147
|
+
reservationId: 'res-123',
|
|
148
|
+
locale: 'ar',
|
|
149
|
+
})
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
### Dans un adapter domain (@mostajs/booking-notifications, etc.)
|
|
153
|
+
|
|
154
|
+
```ts
|
|
155
|
+
// @mostajs/booking-notifications fait :
|
|
156
|
+
manager.onEvent = async (ev) => {
|
|
157
|
+
if (ev.type !== 'reservation.created') return
|
|
158
|
+
const r = await manager.findReservation(ev.reservationId!)
|
|
159
|
+
const slot = await manager.findSlot(r.slotId)
|
|
160
|
+
await engine.notify('booking.reservation.created', {
|
|
161
|
+
reservation: r, slot, locale: r.metadata?.locale,
|
|
162
|
+
})
|
|
163
|
+
}
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
---
|
|
167
|
+
|
|
168
|
+
## 4. API détaillée
|
|
169
|
+
|
|
170
|
+
### `createNotificationEngine(opts) → NotificationEngine`
|
|
171
|
+
|
|
172
|
+
```ts
|
|
173
|
+
interface NotificationEngineOptions {
|
|
174
|
+
mailer: { send: (mail: any) => Promise<{ messageId: string }> }
|
|
175
|
+
templates?: Record<string, NotificationTemplate>
|
|
176
|
+
defaultLocale?: string
|
|
177
|
+
defaultFrom?: string
|
|
178
|
+
disabled?: string[]
|
|
179
|
+
onSent?: (kind, context, result) => void | Promise<void>
|
|
180
|
+
onError?: (kind, context, error) => void | Promise<void>
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
interface NotificationEngine {
|
|
184
|
+
register(kind: string, template: NotificationTemplate): void
|
|
185
|
+
unregister(kind: string): void
|
|
186
|
+
registeredKinds(): string[]
|
|
187
|
+
setEnabled(kind: string, enabled: boolean): void
|
|
188
|
+
|
|
189
|
+
notify(kind: string, context: NotificationContext): Promise<{ messageId: string } | null>
|
|
190
|
+
notifyBatch(items: Array<{ kind, context }>): Promise<Array<{ messageId: string } | null>>
|
|
191
|
+
}
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
### `NotificationTemplate`
|
|
195
|
+
|
|
196
|
+
```ts
|
|
197
|
+
interface NotificationTemplate {
|
|
198
|
+
render(context: NotificationContext): Promise<RenderedMail | null> | RenderedMail | null
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
interface RenderedMail {
|
|
202
|
+
to: string // requis (sinon skip)
|
|
203
|
+
cc?: string | string[]
|
|
204
|
+
bcc?: string | string[]
|
|
205
|
+
from?: string // override defaultFrom
|
|
206
|
+
subject: string
|
|
207
|
+
text?: string
|
|
208
|
+
html?: string
|
|
209
|
+
messageId?: string // idempotence (mailer skip si déjà sent)
|
|
210
|
+
metadata?: Record<string, unknown>
|
|
211
|
+
}
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
### Helpers
|
|
215
|
+
|
|
216
|
+
- `simpleTemplate({ to, subject, text?, html?, messageId? })` — factory function-based
|
|
217
|
+
- `localizedTemplate(variants, resolveTo, resolveMessageId?)` — multi-locale avec fallback fr → en
|
|
218
|
+
|
|
219
|
+
---
|
|
220
|
+
|
|
221
|
+
## 5. Templates — patterns recommandés
|
|
222
|
+
|
|
223
|
+
### Pattern 1 — Templates inline (mini app)
|
|
224
|
+
|
|
225
|
+
```ts
|
|
226
|
+
templates: {
|
|
227
|
+
'welcome': simpleTemplate({
|
|
228
|
+
to: ctx => (ctx.user as any).email,
|
|
229
|
+
subject: () => 'Bienvenue',
|
|
230
|
+
html: ctx => `<h1>Hello ${(ctx.user as any).name}</h1>`,
|
|
231
|
+
}),
|
|
232
|
+
}
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
### Pattern 2 — Templates dans des fichiers séparés
|
|
236
|
+
|
|
237
|
+
```
|
|
238
|
+
my-app/
|
|
239
|
+
├── lib/
|
|
240
|
+
│ └── notifications.ts ← createNotificationEngine + register
|
|
241
|
+
└── templates/
|
|
242
|
+
├── welcome.ts
|
|
243
|
+
├── booking-created.ts
|
|
244
|
+
└── ...
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
```ts
|
|
248
|
+
// templates/welcome.ts
|
|
249
|
+
import { localizedTemplate } from '@mostajs/notifications'
|
|
250
|
+
export const welcomeTemplate = localizedTemplate({
|
|
251
|
+
fr: { subject: 'Bienvenue', html: ctx => `<h1>Hello ${(ctx.user as any).name}</h1>` },
|
|
252
|
+
en: { subject: 'Welcome', html: ctx => `<h1>Hello ${(ctx.user as any).name}</h1>` },
|
|
253
|
+
}, ctx => (ctx.user as any).email)
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
```ts
|
|
257
|
+
// lib/notifications.ts
|
|
258
|
+
import { welcomeTemplate } from '../templates/welcome.js'
|
|
259
|
+
const engine = createNotificationEngine({ mailer, templates: { welcome: welcomeTemplate } })
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
### Pattern 3 — Templates DB-driven (CMS)
|
|
263
|
+
|
|
264
|
+
Pour permettre à des non-devs d'éditer les templates :
|
|
265
|
+
|
|
266
|
+
```ts
|
|
267
|
+
const engine = createNotificationEngine({ mailer })
|
|
268
|
+
|
|
269
|
+
async function loadTemplates() {
|
|
270
|
+
const rows = await db.select().from('notification_templates')
|
|
271
|
+
for (const row of rows) {
|
|
272
|
+
engine.register(row.kind, simpleTemplate({
|
|
273
|
+
to: ctx => evalExpr(row.toExpr, ctx),
|
|
274
|
+
subject: ctx => interpolate(row.subject, ctx),
|
|
275
|
+
html: ctx => interpolate(row.htmlBody, ctx),
|
|
276
|
+
}))
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
await loadTemplates()
|
|
281
|
+
db.on('change:notification_templates', loadTemplates) // hot-reload
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
### Pattern 4 — Moteur de template externe (Handlebars, EJS)
|
|
285
|
+
|
|
286
|
+
```ts
|
|
287
|
+
import Handlebars from 'handlebars'
|
|
288
|
+
|
|
289
|
+
const subjectTpl = Handlebars.compile('Rappel : {{appointmentName}} le {{startAt}}')
|
|
290
|
+
const htmlTpl = Handlebars.compile(htmlTemplate)
|
|
291
|
+
|
|
292
|
+
engine.register('reminder', simpleTemplate({
|
|
293
|
+
to: ctx => (ctx.user as any).email,
|
|
294
|
+
subject: ctx => subjectTpl(ctx),
|
|
295
|
+
html: ctx => htmlTpl(ctx),
|
|
296
|
+
}))
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
---
|
|
300
|
+
|
|
301
|
+
## 6. Implémentation — how to impl
|
|
302
|
+
|
|
303
|
+
### Adapter pour un domaine (pattern type)
|
|
304
|
+
|
|
305
|
+
```ts
|
|
306
|
+
// my-domain-notifications.ts
|
|
307
|
+
import type { NotificationEngine } from '@mostajs/notifications'
|
|
308
|
+
import { localizedTemplate } from '@mostajs/notifications'
|
|
309
|
+
import type { MyManager, MyEvent } from '@mostajs/my-domain'
|
|
310
|
+
|
|
311
|
+
export function createMyDomainNotifications(opts: {
|
|
312
|
+
engine: NotificationEngine
|
|
313
|
+
manager: MyManager
|
|
314
|
+
/** Prefix pour les kinds : 'my-domain' → 'my-domain.event-x'. Default = nom du domain. */
|
|
315
|
+
kindPrefix?: string
|
|
316
|
+
}) {
|
|
317
|
+
const prefix = opts.kindPrefix ?? 'my-domain'
|
|
318
|
+
|
|
319
|
+
// 1. Enregistrer les templates par défaut du domaine
|
|
320
|
+
opts.engine.register(`${prefix}.welcome`, localizedTemplate({
|
|
321
|
+
fr: { subject: 'Bienvenue dans mon domaine', html: ctx => `...` },
|
|
322
|
+
en: { subject: 'Welcome', html: ctx => `...` },
|
|
323
|
+
}, ctx => (ctx.entity as any)?.email))
|
|
324
|
+
|
|
325
|
+
// 2. Subscribe aux events du manager → notify
|
|
326
|
+
opts.manager.onEvent = async (ev: MyEvent) => {
|
|
327
|
+
switch (ev.type) {
|
|
328
|
+
case 'entity.created':
|
|
329
|
+
const e = await opts.manager.findEntity(ev.entityId!)
|
|
330
|
+
await opts.engine.notify(`${prefix}.welcome`, { entity: e, locale: e.locale })
|
|
331
|
+
break
|
|
332
|
+
// ...
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
return {
|
|
337
|
+
/** Permet à l'app d'override les templates par défaut. */
|
|
338
|
+
setTemplate(kind: string, template: NotificationTemplate) {
|
|
339
|
+
opts.engine.register(`${prefix}.${kind}`, template)
|
|
340
|
+
},
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
### Routes admin (Next.js)
|
|
346
|
+
|
|
347
|
+
```ts
|
|
348
|
+
// app/api/admin/notifications/test/route.ts
|
|
349
|
+
import { engine } from '@/lib/notifications'
|
|
350
|
+
|
|
351
|
+
export async function POST(req: Request) {
|
|
352
|
+
const { kind, context } = await req.json()
|
|
353
|
+
const result = await engine.notify(kind, context)
|
|
354
|
+
return Response.json({ sent: !!result, messageId: result?.messageId })
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// app/api/admin/notifications/kinds/route.ts
|
|
358
|
+
export async function GET() {
|
|
359
|
+
return Response.json({ kinds: engine.registeredKinds() })
|
|
360
|
+
}
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
---
|
|
364
|
+
|
|
365
|
+
## 7. I18n & locales
|
|
366
|
+
|
|
367
|
+
Le `context.locale` détermine quel template variant est choisi. Helper `localizedTemplate(variants, ...)` :
|
|
368
|
+
- Cherche `variants[locale]` (ex. `fr-DZ`)
|
|
369
|
+
- Fallback sur `variants[locale.split('-')[0]]` (ex. `fr`)
|
|
370
|
+
- Fallback final sur `variants.fr` puis `variants.en`
|
|
371
|
+
|
|
372
|
+
**Résolution de la locale** : c'est à l'app de mettre `ctx.locale` (depuis `user.locale` DB, `Accept-Language` header, ou subdomain `fr.app.com`).
|
|
373
|
+
|
|
374
|
+
```ts
|
|
375
|
+
await engine.notify('reminder', {
|
|
376
|
+
user: u,
|
|
377
|
+
locale: u.locale ?? extractFromHeaders(req) ?? 'fr',
|
|
378
|
+
})
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
---
|
|
382
|
+
|
|
383
|
+
## 8. Idempotence
|
|
384
|
+
|
|
385
|
+
Pour éviter d'envoyer **2× le même reminder 24h** si un cron retrigger :
|
|
386
|
+
- Le template doit fournir un `messageId` déterministe : `reminder-24h-${reservation.id}`
|
|
387
|
+
- `@mostajs/mailer` (Phase 2 MailLog) persistera ce messageId en DB → 2e send avec même ID = no-op silencieux
|
|
388
|
+
|
|
389
|
+
```ts
|
|
390
|
+
localizedTemplate(
|
|
391
|
+
{ fr: { subject: 'Rappel', html: ... } },
|
|
392
|
+
ctx => (ctx.user as any).email,
|
|
393
|
+
ctx => `reminder-24h-${(ctx.reservation as any).id}`, // messageId
|
|
394
|
+
)
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
---
|
|
398
|
+
|
|
399
|
+
## 9. Tests
|
|
400
|
+
|
|
401
|
+
```ts
|
|
402
|
+
// tests/engine.test.ts
|
|
403
|
+
import { createNotificationEngine, simpleTemplate } from '@mostajs/notifications'
|
|
404
|
+
|
|
405
|
+
describe('NotificationEngine', () => {
|
|
406
|
+
it('dispatch template registered', async () => {
|
|
407
|
+
const sent: any[] = []
|
|
408
|
+
const mailer = { send: async (m: any) => { sent.push(m); return { messageId: 'x-1' } } }
|
|
409
|
+
const engine = createNotificationEngine({ mailer })
|
|
410
|
+
engine.register('welcome', simpleTemplate({
|
|
411
|
+
to: _ => 'user@x.com',
|
|
412
|
+
subject: _ => 'Hello',
|
|
413
|
+
text: _ => 'Welcome!',
|
|
414
|
+
}))
|
|
415
|
+
|
|
416
|
+
const r = await engine.notify('welcome', { user: 'x' })
|
|
417
|
+
expect(r?.messageId).toBe('x-1')
|
|
418
|
+
expect(sent[0]).toMatchObject({ to: 'user@x.com', subject: 'Hello' })
|
|
419
|
+
})
|
|
420
|
+
|
|
421
|
+
it('skip if template absent', async () => {
|
|
422
|
+
const engine = createNotificationEngine({ mailer: { send: async () => ({ messageId: '' }) } })
|
|
423
|
+
expect(await engine.notify('unknown', {})).toBeNull()
|
|
424
|
+
})
|
|
425
|
+
|
|
426
|
+
it('locale fallback', async () => {
|
|
427
|
+
// ... test localizedTemplate avec variant manquante
|
|
428
|
+
})
|
|
429
|
+
})
|
|
430
|
+
```
|
|
431
|
+
|
|
432
|
+
---
|
|
433
|
+
|
|
434
|
+
## 10. Modules liés
|
|
435
|
+
|
|
436
|
+
- [`@mostajs/mailer`](../mosta-mailer) — peer dep, dispatch SMTP/Resend/SES/etc.
|
|
437
|
+
- [`@mostajs/booking-notifications`](../mosta-booking-stack/mosta-booking-notifications) — adapter booking events → notifications
|
|
438
|
+
- (futur) `@mostajs/media-sfu-notifications` — adapter SFU events
|
|
439
|
+
- [`@mostajs/url`](../mosta-url) — pour construire des liens dans les templates (`signedUrl` pour invite tokens)
|
|
440
|
+
|
|
441
|
+
---
|
|
442
|
+
|
|
443
|
+
**License** : AGPL-3.0-or-later
|
|
444
|
+
**Auteur** : Dr Hamid MADANI <drmdh@msn.com>
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
export type Locale = string;
|
|
2
|
+
export interface NotificationContext {
|
|
3
|
+
/** Locale du destinataire — détermine quel template variant utiliser. */
|
|
4
|
+
locale?: Locale;
|
|
5
|
+
/** Champs libres consommés par le template (reservation, slot, session, etc.). */
|
|
6
|
+
[key: string]: unknown;
|
|
7
|
+
}
|
|
8
|
+
export interface RenderedMail {
|
|
9
|
+
to: string;
|
|
10
|
+
cc?: string | string[];
|
|
11
|
+
bcc?: string | string[];
|
|
12
|
+
from?: string;
|
|
13
|
+
subject: string;
|
|
14
|
+
text?: string;
|
|
15
|
+
html?: string;
|
|
16
|
+
/** Idempotence : mailer skip si déjà sent avec ce messageId. */
|
|
17
|
+
messageId?: string;
|
|
18
|
+
/** Metadata libre persistée dans MailLog. */
|
|
19
|
+
metadata?: Record<string, unknown>;
|
|
20
|
+
}
|
|
21
|
+
/** Template = fonction qui produit un Mail à partir du contexte.
|
|
22
|
+
* Retour null → skip (template applicable mais l'app a décidé de pas envoyer). */
|
|
23
|
+
export interface NotificationTemplate {
|
|
24
|
+
render(context: NotificationContext): Promise<RenderedMail | null> | RenderedMail | null;
|
|
25
|
+
}
|
|
26
|
+
export interface NotificationEngineOptions {
|
|
27
|
+
/** Instance @mostajs/mailer (ou compatible : tout objet avec `.send(mail)`). */
|
|
28
|
+
mailer: {
|
|
29
|
+
send: (mail: any) => Promise<{
|
|
30
|
+
messageId: string;
|
|
31
|
+
}>;
|
|
32
|
+
};
|
|
33
|
+
/** Templates initialement enregistrés (kind → template). */
|
|
34
|
+
templates?: Record<string, NotificationTemplate>;
|
|
35
|
+
/** Locale par défaut si le contexte n'en spécifie pas. */
|
|
36
|
+
defaultLocale?: Locale;
|
|
37
|
+
/** Default `from` (peut être overridé par chaque template). */
|
|
38
|
+
defaultFrom?: string;
|
|
39
|
+
/** Désactive certains kinds (e.g. mode dégradé). */
|
|
40
|
+
disabled?: string[];
|
|
41
|
+
/** Callback succès. */
|
|
42
|
+
onSent?: (kind: string, context: NotificationContext, result: {
|
|
43
|
+
messageId: string;
|
|
44
|
+
}) => void | Promise<void>;
|
|
45
|
+
/** Callback erreur. */
|
|
46
|
+
onError?: (kind: string, context: NotificationContext, error: Error) => void | Promise<void>;
|
|
47
|
+
}
|
|
48
|
+
export interface NotificationEngine {
|
|
49
|
+
/** Enregistre / override un template. */
|
|
50
|
+
register(kind: string, template: NotificationTemplate): void;
|
|
51
|
+
/** Retire un template. */
|
|
52
|
+
unregister(kind: string): void;
|
|
53
|
+
/** Liste les kinds enregistrés. */
|
|
54
|
+
registeredKinds(): string[];
|
|
55
|
+
/** Désactive ou réactive un kind sans le supprimer. */
|
|
56
|
+
setEnabled(kind: string, enabled: boolean): void;
|
|
57
|
+
/** Notifie : render + dispatch via mailer. Retourne le résultat (null = skipped). */
|
|
58
|
+
notify(kind: string, context: NotificationContext): Promise<{
|
|
59
|
+
messageId: string;
|
|
60
|
+
} | null>;
|
|
61
|
+
/** Notifie en parallèle plusieurs (kind, context). */
|
|
62
|
+
notifyBatch(items: Array<{
|
|
63
|
+
kind: string;
|
|
64
|
+
context: NotificationContext;
|
|
65
|
+
}>): Promise<Array<{
|
|
66
|
+
messageId: string;
|
|
67
|
+
} | null>>;
|
|
68
|
+
}
|
|
69
|
+
export declare function createNotificationEngine(opts: NotificationEngineOptions): NotificationEngine;
|
|
70
|
+
/**
|
|
71
|
+
* Template simple — function-based. Pas de moteur de template (pas de mustache,
|
|
72
|
+
* pas de handlebars). L'app peut construire ses chaines manuellement OU importer
|
|
73
|
+
* son moteur préféré (ejs, handlebars, etc.) et le wraper dans cette interface.
|
|
74
|
+
*/
|
|
75
|
+
export declare function simpleTemplate(spec: {
|
|
76
|
+
to: (ctx: NotificationContext) => string | null;
|
|
77
|
+
subject: (ctx: NotificationContext) => string;
|
|
78
|
+
text?: (ctx: NotificationContext) => string;
|
|
79
|
+
html?: (ctx: NotificationContext) => string;
|
|
80
|
+
messageId?: (ctx: NotificationContext) => string;
|
|
81
|
+
}): NotificationTemplate;
|
|
82
|
+
/**
|
|
83
|
+
* Template multi-locale : utilise `ctx.locale` pour choisir la variant.
|
|
84
|
+
* Si la locale n'est pas dans les variants, fallback sur 'fr' puis 'en'.
|
|
85
|
+
*/
|
|
86
|
+
export declare function localizedTemplate(variants: {
|
|
87
|
+
[locale: string]: {
|
|
88
|
+
subject: string | ((ctx: NotificationContext) => string);
|
|
89
|
+
text?: string | ((ctx: NotificationContext) => string);
|
|
90
|
+
html?: string | ((ctx: NotificationContext) => string);
|
|
91
|
+
};
|
|
92
|
+
}, resolveTo: (ctx: NotificationContext) => string | null, resolveMessageId?: (ctx: NotificationContext) => string): NotificationTemplate;
|
|
93
|
+
export declare const moduleInfo: {
|
|
94
|
+
name: string;
|
|
95
|
+
version: string;
|
|
96
|
+
scope: string;
|
|
97
|
+
};
|
|
98
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAWA,MAAM,MAAM,MAAM,GAAG,MAAM,CAAA;AAE3B,MAAM,WAAW,mBAAmB;IAClC,yEAAyE;IACzE,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,kFAAkF;IAClF,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;CACvB;AAED,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,MAAM,CAAA;IACV,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAA;IACtB,GAAG,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAA;IACvB,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,OAAO,EAAE,MAAM,CAAA;IACf,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,gEAAgE;IAChE,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,6CAA6C;IAC7C,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CACnC;AAED;mFACmF;AACnF,MAAM,WAAW,oBAAoB;IACnC,MAAM,CAAC,OAAO,EAAE,mBAAmB,GAAG,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,GAAG,YAAY,GAAG,IAAI,CAAA;CACzF;AAED,MAAM,WAAW,yBAAyB;IACxC,gFAAgF;IAChF,MAAM,EAAE;QAAE,IAAI,EAAE,CAAC,IAAI,EAAE,GAAG,KAAK,OAAO,CAAC;YAAE,SAAS,EAAE,MAAM,CAAA;SAAE,CAAC,CAAA;KAAE,CAAA;IAE/D,4DAA4D;IAC5D,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,oBAAoB,CAAC,CAAA;IAEhD,0DAA0D;IAC1D,aAAa,CAAC,EAAE,MAAM,CAAA;IAEtB,+DAA+D;IAC/D,WAAW,CAAC,EAAE,MAAM,CAAA;IAEpB,oDAAoD;IACpD,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAA;IAEnB,uBAAuB;IACvB,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,mBAAmB,EAAE,MAAM,EAAE;QAAE,SAAS,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAC5G,uBAAuB;IACvB,OAAO,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,mBAAmB,EAAE,KAAK,EAAE,KAAK,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CAC7F;AAED,MAAM,WAAW,kBAAkB;IACjC,yCAAyC;IACzC,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,oBAAoB,GAAG,IAAI,CAAA;IAC5D,0BAA0B;IAC1B,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;IAC9B,mCAAmC;IACnC,eAAe,IAAI,MAAM,EAAE,CAAA;IAC3B,uDAAuD;IACvD,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,IAAI,CAAA;IAEhD,qFAAqF;IACrF,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,mBAAmB,GAAG,OAAO,CAAC;QAAE,SAAS,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC,CAAA;IAEzF,sDAAsD;IACtD,WAAW,CAAC,KAAK,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,mBAAmB,CAAA;KAAE,CAAC,GAAG,OAAO,CAAC,KAAK,CAAC;QAAE,SAAS,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC,CAAC,CAAA;CACxH;AAID,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,yBAAyB,GAAG,kBAAkB,CAuD5F;AAID;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE;IACnC,EAAE,EAAE,CAAC,GAAG,EAAE,mBAAmB,KAAK,MAAM,GAAG,IAAI,CAAA;IAC/C,OAAO,EAAE,CAAC,GAAG,EAAE,mBAAmB,KAAK,MAAM,CAAA;IAC7C,IAAI,CAAC,EAAE,CAAC,GAAG,EAAE,mBAAmB,KAAK,MAAM,CAAA;IAC3C,IAAI,CAAC,EAAE,CAAC,GAAG,EAAE,mBAAmB,KAAK,MAAM,CAAA;IAC3C,SAAS,CAAC,EAAE,CAAC,GAAG,EAAE,mBAAmB,KAAK,MAAM,CAAA;CACjD,GAAG,oBAAoB,CAcvB;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE;IAC1C,CAAC,MAAM,EAAE,MAAM,GAAG;QAChB,OAAO,EAAE,MAAM,GAAG,CAAC,CAAC,GAAG,EAAE,mBAAmB,KAAK,MAAM,CAAC,CAAA;QACxD,IAAI,CAAC,EAAK,MAAM,GAAG,CAAC,CAAC,GAAG,EAAE,mBAAmB,KAAK,MAAM,CAAC,CAAA;QACzD,IAAI,CAAC,EAAK,MAAM,GAAG,CAAC,CAAC,GAAG,EAAE,mBAAmB,KAAK,MAAM,CAAC,CAAA;KAC1D,CAAA;CACF,EAAE,SAAS,EAAE,CAAC,GAAG,EAAE,mBAAmB,KAAK,MAAM,GAAG,IAAI,EACtD,gBAAgB,CAAC,EAAE,CAAC,GAAG,EAAE,mBAAmB,KAAK,MAAM,GACvD,oBAAoB,CAmBtB;AAED,eAAO,MAAM,UAAU;;;;CAItB,CAAA"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
// @mostajs/notifications — Generic notification engine
|
|
2
|
+
// Author: Dr Hamid MADANI <drmdh@msn.com>
|
|
3
|
+
//
|
|
4
|
+
// Domain-agnostic : accepte n'importe quel `kind: string` (namespacé par convention
|
|
5
|
+
// `<domain>.<event>` : 'booking.reservation.created', 'media.session.started', etc.)
|
|
6
|
+
// et n'importe quel `context: Record<string, unknown>`. Les adapters spécifiques
|
|
7
|
+
// (booking-notifications, media-sfu-notifications) traduisent leurs events
|
|
8
|
+
// internes en `kind + context` et appellent `engine.notify(...)`.
|
|
9
|
+
// ─── Engine ────────────────────────────────────────────────────────────
|
|
10
|
+
export function createNotificationEngine(opts) {
|
|
11
|
+
const templates = new Map(Object.entries(opts.templates ?? {}));
|
|
12
|
+
const disabled = new Set(opts.disabled ?? []);
|
|
13
|
+
const defaultLocale = opts.defaultLocale ?? 'fr';
|
|
14
|
+
async function doNotify(kind, context) {
|
|
15
|
+
if (disabled.has(kind))
|
|
16
|
+
return null;
|
|
17
|
+
const tpl = templates.get(kind);
|
|
18
|
+
if (!tpl)
|
|
19
|
+
return null;
|
|
20
|
+
const ctx = { ...context, locale: context.locale ?? defaultLocale };
|
|
21
|
+
let rendered;
|
|
22
|
+
try {
|
|
23
|
+
rendered = await Promise.resolve(tpl.render(ctx));
|
|
24
|
+
}
|
|
25
|
+
catch (e) {
|
|
26
|
+
await Promise.resolve(opts.onError?.(kind, ctx, e));
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
if (!rendered)
|
|
30
|
+
return null;
|
|
31
|
+
if (!rendered.to) {
|
|
32
|
+
await Promise.resolve(opts.onError?.(kind, ctx, new Error('Template returned no `to` address')));
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
const mail = {
|
|
36
|
+
...rendered,
|
|
37
|
+
from: rendered.from ?? opts.defaultFrom,
|
|
38
|
+
metadata: { ...(rendered.metadata ?? {}), kind, locale: ctx.locale },
|
|
39
|
+
};
|
|
40
|
+
try {
|
|
41
|
+
const result = await opts.mailer.send(mail);
|
|
42
|
+
await Promise.resolve(opts.onSent?.(kind, ctx, result));
|
|
43
|
+
return result;
|
|
44
|
+
}
|
|
45
|
+
catch (e) {
|
|
46
|
+
await Promise.resolve(opts.onError?.(kind, ctx, e));
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return {
|
|
51
|
+
register(kind, template) { templates.set(kind, template); },
|
|
52
|
+
unregister(kind) { templates.delete(kind); },
|
|
53
|
+
registeredKinds() { return Array.from(templates.keys()); },
|
|
54
|
+
setEnabled(kind, enabled) {
|
|
55
|
+
if (enabled)
|
|
56
|
+
disabled.delete(kind);
|
|
57
|
+
else
|
|
58
|
+
disabled.add(kind);
|
|
59
|
+
},
|
|
60
|
+
notify: doNotify,
|
|
61
|
+
async notifyBatch(items) {
|
|
62
|
+
return Promise.all(items.map(it => doNotify(it.kind, it.context)));
|
|
63
|
+
},
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
// ─── Built-in template helpers ─────────────────────────────────────────
|
|
67
|
+
/**
|
|
68
|
+
* Template simple — function-based. Pas de moteur de template (pas de mustache,
|
|
69
|
+
* pas de handlebars). L'app peut construire ses chaines manuellement OU importer
|
|
70
|
+
* son moteur préféré (ejs, handlebars, etc.) et le wraper dans cette interface.
|
|
71
|
+
*/
|
|
72
|
+
export function simpleTemplate(spec) {
|
|
73
|
+
return {
|
|
74
|
+
render(ctx) {
|
|
75
|
+
const to = spec.to(ctx);
|
|
76
|
+
if (!to)
|
|
77
|
+
return null;
|
|
78
|
+
return {
|
|
79
|
+
to,
|
|
80
|
+
subject: spec.subject(ctx),
|
|
81
|
+
text: spec.text?.(ctx),
|
|
82
|
+
html: spec.html?.(ctx),
|
|
83
|
+
messageId: spec.messageId?.(ctx),
|
|
84
|
+
};
|
|
85
|
+
},
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Template multi-locale : utilise `ctx.locale` pour choisir la variant.
|
|
90
|
+
* Si la locale n'est pas dans les variants, fallback sur 'fr' puis 'en'.
|
|
91
|
+
*/
|
|
92
|
+
export function localizedTemplate(variants, resolveTo, resolveMessageId) {
|
|
93
|
+
return {
|
|
94
|
+
render(ctx) {
|
|
95
|
+
const to = resolveTo(ctx);
|
|
96
|
+
if (!to)
|
|
97
|
+
return null;
|
|
98
|
+
const loc = ctx.locale ?? 'fr';
|
|
99
|
+
const v = variants[loc] ?? variants[loc.split('-')[0]] ?? variants.fr ?? variants.en;
|
|
100
|
+
if (!v)
|
|
101
|
+
return null;
|
|
102
|
+
const resolve = (s) => typeof s === 'function' ? s(ctx) : s;
|
|
103
|
+
return {
|
|
104
|
+
to,
|
|
105
|
+
subject: resolve(v.subject),
|
|
106
|
+
text: resolve(v.text),
|
|
107
|
+
html: resolve(v.html),
|
|
108
|
+
messageId: resolveMessageId?.(ctx),
|
|
109
|
+
};
|
|
110
|
+
},
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
export const moduleInfo = {
|
|
114
|
+
name: '@mostajs/notifications',
|
|
115
|
+
version: '0.1.0',
|
|
116
|
+
scope: 'Generic notification engine — domain-agnostic templating + mailer dispatch',
|
|
117
|
+
};
|
|
118
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@mostajs/notifications",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Generic notification engine for @mostajs/* — domain-agnostic templating + i18n + mailer dispatch + idempotence. Consume any event kind from any source (booking, media-sfu, custom apps). Plug-and-play with @mostajs/mailer.",
|
|
5
|
+
"author": "Dr Hamid MADANI <drmdh@msn.com>",
|
|
6
|
+
"license": "AGPL-3.0-or-later",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"main": "dist/index.js",
|
|
9
|
+
"types": "dist/index.d.ts",
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"import": "./dist/index.js",
|
|
14
|
+
"default": "./dist/index.js"
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
"files": [
|
|
18
|
+
"dist",
|
|
19
|
+
"README.md",
|
|
20
|
+
"LICENSE"
|
|
21
|
+
],
|
|
22
|
+
"scripts": {
|
|
23
|
+
"build": "tsc",
|
|
24
|
+
"prepublishOnly": "npm run build"
|
|
25
|
+
},
|
|
26
|
+
"peerDependencies": {
|
|
27
|
+
"@mostajs/mailer": "^0.1.1"
|
|
28
|
+
},
|
|
29
|
+
"engines": {
|
|
30
|
+
"node": ">=18"
|
|
31
|
+
},
|
|
32
|
+
"devDependencies": {
|
|
33
|
+
"typescript": "^6.0.3"
|
|
34
|
+
}
|
|
35
|
+
}
|