@mostajs/notifications 0.1.0 → 0.3.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/dist/index.d.ts CHANGED
@@ -1,3 +1,10 @@
1
+ import { type ThrottleStore } from './throttle.js';
2
+ import type { Mail, MailAttachment } from '@mostajs/mailer';
3
+ export type { ThrottleStore } from './throttle.js';
4
+ export { createInMemoryThrottleStore } from './throttle.js';
5
+ export { createMultiChannelSender, smsMailAdapter } from './multi-channel.js';
6
+ export type { ChannelSender } from './multi-channel.js';
7
+ export type { Mail, MailAttachment };
1
8
  export type Locale = string;
2
9
  export interface NotificationContext {
3
10
  /** Locale du destinataire — détermine quel template variant utiliser. */
@@ -5,24 +12,28 @@ export interface NotificationContext {
5
12
  /** Champs libres consommés par le template (reservation, slot, session, etc.). */
6
13
  [key: string]: unknown;
7
14
  }
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
- }
15
+ /** Sortie d'un template — un `Mail` au sens de `@mostajs/mailer`. La forme
16
+ * complète est disponible (attachments, headers, replyTo, …) ; notifications
17
+ * se contente d'y ajouter `kind` / `locale` dans `metadata`. */
18
+ export type RenderedMail = Mail;
21
19
  /** Template = fonction qui produit un Mail à partir du contexte.
22
20
  * Retour null → skip (template applicable mais l'app a décidé de pas envoyer). */
23
21
  export interface NotificationTemplate {
24
22
  render(context: NotificationContext): Promise<RenderedMail | null> | RenderedMail | null;
25
23
  }
24
+ /** Options de throttle attachées à un kind. (v0.2 / N2) */
25
+ export interface ThrottleOptions {
26
+ /** Fenêtre durant laquelle un envoi suivant pour la même clé est skippé. */
27
+ windowMs: number;
28
+ /** Construit la clé secondaire (combinée avec `kind`). Défaut : clé vide. */
29
+ keyFrom?: (context: NotificationContext) => string;
30
+ }
31
+ /** Options de registration d'un template. (v0.2) */
32
+ export interface RegisterOptions {
33
+ throttle?: ThrottleOptions;
34
+ }
35
+ /** Cause d'un skip retourné par `notify(...) → null`. (v0.2) */
36
+ export type SkipReason = 'disabled' | 'unknown-kind' | 'template-null' | 'no-recipient' | 'render-error' | 'send-error' | 'throttled';
26
37
  export interface NotificationEngineOptions {
27
38
  /** Instance @mostajs/mailer (ou compatible : tout objet avec `.send(mail)`). */
28
39
  mailer: {
@@ -38,16 +49,22 @@ export interface NotificationEngineOptions {
38
49
  defaultFrom?: string;
39
50
  /** Désactive certains kinds (e.g. mode dégradé). */
40
51
  disabled?: string[];
52
+ /** Store de throttle injecté. Défaut : in-memory. (v0.2 / N2) */
53
+ throttleStore?: ThrottleStore;
54
+ /** Source d'horloge (testabilité). Défaut : `() => Date.now()`. */
55
+ now?: () => number;
41
56
  /** Callback succès. */
42
57
  onSent?: (kind: string, context: NotificationContext, result: {
43
58
  messageId: string;
44
59
  }) => void | Promise<void>;
45
60
  /** Callback erreur. */
46
61
  onError?: (kind: string, context: NotificationContext, error: Error) => void | Promise<void>;
62
+ /** Callback skip — y compris throttle, unknown-kind, etc. (v0.2) */
63
+ onSkipped?: (kind: string, context: NotificationContext, reason: SkipReason) => void | Promise<void>;
47
64
  }
48
65
  export interface NotificationEngine {
49
- /** Enregistre / override un template. */
50
- register(kind: string, template: NotificationTemplate): void;
66
+ /** Enregistre / override un template. `options.throttle` paramètre la fenêtre anti-spam. */
67
+ register(kind: string, template: NotificationTemplate, options?: RegisterOptions): void;
51
68
  /** Retire un template. */
52
69
  unregister(kind: string): void;
53
70
  /** Liste les kinds enregistrés. */
@@ -65,6 +82,9 @@ export interface NotificationEngine {
65
82
  }>): Promise<Array<{
66
83
  messageId: string;
67
84
  } | null>>;
85
+ /** Render-only : renvoie le `RenderedMail` sans dispatcher ni journaliser.
86
+ * Utilisable pour prévisualiser un rapport mensuel côté UI. (v0.2 / N3) */
87
+ preview(kind: string, context: NotificationContext): Promise<RenderedMail | null>;
68
88
  }
69
89
  export declare function createNotificationEngine(opts: NotificationEngineOptions): NotificationEngine;
70
90
  /**
@@ -78,6 +98,7 @@ export declare function simpleTemplate(spec: {
78
98
  text?: (ctx: NotificationContext) => string;
79
99
  html?: (ctx: NotificationContext) => string;
80
100
  messageId?: (ctx: NotificationContext) => string;
101
+ attachments?: (ctx: NotificationContext) => MailAttachment[] | undefined;
81
102
  }): NotificationTemplate;
82
103
  /**
83
104
  * Template multi-locale : utilise `ctx.locale` pour choisir la variant.
@@ -89,7 +110,7 @@ export declare function localizedTemplate(variants: {
89
110
  text?: string | ((ctx: NotificationContext) => string);
90
111
  html?: string | ((ctx: NotificationContext) => string);
91
112
  };
92
- }, resolveTo: (ctx: NotificationContext) => string | null, resolveMessageId?: (ctx: NotificationContext) => string): NotificationTemplate;
113
+ }, resolveTo: (ctx: NotificationContext) => string | null, resolveMessageId?: (ctx: NotificationContext) => string, resolveAttachments?: (ctx: NotificationContext) => MailAttachment[] | undefined): NotificationTemplate;
93
114
  export declare const moduleInfo: {
94
115
  name: string;
95
116
  version: string;
@@ -1 +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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/dist/index.js CHANGED
@@ -6,51 +6,110 @@
6
6
  // et n'importe quel `context: Record<string, unknown>`. Les adapters spécifiques
7
7
  // (booking-notifications, media-sfu-notifications) traduisent leurs events
8
8
  // internes en `kind + context` et appellent `engine.notify(...)`.
9
- // ─── Engine ────────────────────────────────────────────────────────────
9
+ import { createInMemoryThrottleStore, } from './throttle.js';
10
+ export { createInMemoryThrottleStore } from './throttle.js';
11
+ export { createMultiChannelSender, smsMailAdapter } from './multi-channel.js'; // dispatch multi-canal (email + sms)
10
12
  export function createNotificationEngine(opts) {
11
- const templates = new Map(Object.entries(opts.templates ?? {}));
13
+ const entries = new Map(Object.entries(opts.templates ?? {}).map(([k, t]) => [k, { template: t }]));
12
14
  const disabled = new Set(opts.disabled ?? []);
13
15
  const defaultLocale = opts.defaultLocale ?? 'fr';
14
- async function doNotify(kind, context) {
16
+ const throttleStore = opts.throttleStore ?? createInMemoryThrottleStore();
17
+ const now = opts.now ?? (() => Date.now());
18
+ function buildContext(context) {
19
+ return { ...context, locale: context.locale ?? defaultLocale };
20
+ }
21
+ async function emitSkipped(kind, ctx, reason) {
22
+ await Promise.resolve(opts.onSkipped?.(kind, ctx, reason));
23
+ return null;
24
+ }
25
+ async function renderOnly(kind, context) {
26
+ const ctx = buildContext(context);
15
27
  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 };
28
+ return { rendered: null, skipReason: 'disabled', ctx };
29
+ const entry = entries.get(kind);
30
+ if (!entry)
31
+ return { rendered: null, skipReason: 'unknown-kind', ctx };
21
32
  let rendered;
22
33
  try {
23
- rendered = await Promise.resolve(tpl.render(ctx));
34
+ rendered = await Promise.resolve(entry.template.render(ctx));
24
35
  }
25
36
  catch (e) {
26
37
  await Promise.resolve(opts.onError?.(kind, ctx, e));
27
- return null;
38
+ return { rendered: null, skipReason: 'render-error', ctx };
28
39
  }
29
40
  if (!rendered)
30
- return null;
31
- if (!rendered.to) {
41
+ return { rendered: null, skipReason: 'template-null', ctx };
42
+ const to = rendered.to;
43
+ const hasTo = Array.isArray(to) ? to.length > 0 : !!to;
44
+ if (!hasTo) {
32
45
  await Promise.resolve(opts.onError?.(kind, ctx, new Error('Template returned no `to` address')));
46
+ return { rendered: null, skipReason: 'no-recipient', ctx };
47
+ }
48
+ return { rendered, ctx };
49
+ }
50
+ function throttleKey(kind, entry, ctx) {
51
+ const t = entry.options?.throttle;
52
+ if (!t)
33
53
  return null;
54
+ const secondary = t.keyFrom ? t.keyFrom(ctx) : '';
55
+ return `${kind}::${secondary}`;
56
+ }
57
+ async function doNotify(kind, context) {
58
+ const { rendered, skipReason, ctx } = await renderOnly(kind, context);
59
+ if (skipReason && (skipReason === 'disabled' || skipReason === 'unknown-kind' || skipReason === 'template-null')) {
60
+ return emitSkipped(kind, ctx, skipReason);
61
+ }
62
+ if (!rendered) {
63
+ // render-error / no-recipient → onError déjà appelé, on signale aussi skip
64
+ if (skipReason)
65
+ return emitSkipped(kind, ctx, skipReason);
66
+ return null;
67
+ }
68
+ // Throttle (après render OK, avant dispatch — la décision tient au déclenchement
69
+ // applicatif, pas au coût de rendu ; cohérent avec l'idempotence par messageId).
70
+ const entry = entries.get(kind);
71
+ const tKey = throttleKey(kind, entry, ctx);
72
+ if (tKey && entry.options?.throttle) {
73
+ const last = await Promise.resolve(throttleStore.getLastSent(tKey));
74
+ if (last !== undefined && now() - last < entry.options.throttle.windowMs) {
75
+ return emitSkipped(kind, ctx, 'throttled');
76
+ }
34
77
  }
35
78
  const mail = {
36
79
  ...rendered,
37
80
  from: rendered.from ?? opts.defaultFrom,
38
81
  metadata: { ...(rendered.metadata ?? {}), kind, locale: ctx.locale },
39
82
  };
83
+ let result;
40
84
  try {
41
- const result = await opts.mailer.send(mail);
42
- await Promise.resolve(opts.onSent?.(kind, ctx, result));
43
- return result;
85
+ result = await opts.mailer.send(mail);
44
86
  }
45
87
  catch (e) {
46
88
  await Promise.resolve(opts.onError?.(kind, ctx, e));
47
- return null;
89
+ return emitSkipped(kind, ctx, 'send-error');
48
90
  }
91
+ if (tKey) {
92
+ await Promise.resolve(throttleStore.recordSent(tKey, now()));
93
+ }
94
+ await Promise.resolve(opts.onSent?.(kind, ctx, result));
95
+ return result;
96
+ }
97
+ async function doPreview(kind, context) {
98
+ const { rendered, ctx } = await renderOnly(kind, context);
99
+ if (!rendered)
100
+ return null;
101
+ // Préserve la fusion `from`/`metadata` que verrait le mailer — utile pour
102
+ // valider visuellement la pièce jointe ou le sujet final.
103
+ return {
104
+ ...rendered,
105
+ from: rendered.from ?? opts.defaultFrom,
106
+ metadata: { ...(rendered.metadata ?? {}), kind, locale: ctx.locale },
107
+ };
49
108
  }
50
109
  return {
51
- register(kind, template) { templates.set(kind, template); },
52
- unregister(kind) { templates.delete(kind); },
53
- registeredKinds() { return Array.from(templates.keys()); },
110
+ register(kind, template, options) { entries.set(kind, { template, options }); },
111
+ unregister(kind) { entries.delete(kind); },
112
+ registeredKinds() { return Array.from(entries.keys()); },
54
113
  setEnabled(kind, enabled) {
55
114
  if (enabled)
56
115
  disabled.delete(kind);
@@ -61,6 +120,7 @@ export function createNotificationEngine(opts) {
61
120
  async notifyBatch(items) {
62
121
  return Promise.all(items.map(it => doNotify(it.kind, it.context)));
63
122
  },
123
+ preview: doPreview,
64
124
  };
65
125
  }
66
126
  // ─── Built-in template helpers ─────────────────────────────────────────
@@ -81,6 +141,7 @@ export function simpleTemplate(spec) {
81
141
  text: spec.text?.(ctx),
82
142
  html: spec.html?.(ctx),
83
143
  messageId: spec.messageId?.(ctx),
144
+ attachments: spec.attachments?.(ctx),
84
145
  };
85
146
  },
86
147
  };
@@ -89,7 +150,7 @@ export function simpleTemplate(spec) {
89
150
  * Template multi-locale : utilise `ctx.locale` pour choisir la variant.
90
151
  * Si la locale n'est pas dans les variants, fallback sur 'fr' puis 'en'.
91
152
  */
92
- export function localizedTemplate(variants, resolveTo, resolveMessageId) {
153
+ export function localizedTemplate(variants, resolveTo, resolveMessageId, resolveAttachments) {
93
154
  return {
94
155
  render(ctx) {
95
156
  const to = resolveTo(ctx);
@@ -106,13 +167,14 @@ export function localizedTemplate(variants, resolveTo, resolveMessageId) {
106
167
  text: resolve(v.text),
107
168
  html: resolve(v.html),
108
169
  messageId: resolveMessageId?.(ctx),
170
+ attachments: resolveAttachments?.(ctx),
109
171
  };
110
172
  },
111
173
  };
112
174
  }
113
175
  export const moduleInfo = {
114
176
  name: '@mostajs/notifications',
115
- version: '0.1.0',
116
- scope: 'Generic notification engine — domain-agnostic templating + mailer dispatch',
177
+ version: '0.2.0',
178
+ scope: 'Generic notification engine — domain-agnostic templating + mailer dispatch + throttle + preview',
117
179
  };
118
180
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,39 @@
1
+ /**
2
+ * @mostajs/notifications — sender MULTI-CANAL (email + sms + …). Author: Dr Hamid MADANI <drmdh@msn.com>
3
+ * AGPL-3.0-or-later.
4
+ *
5
+ * Le moteur appelle `opts.mailer.send(mail)`. En lui passant CE sender, la notification est routée selon
6
+ * `mail.channel` (posé par le template/contexte) vers le bon canal — email par défaut, 'sms' via @mostajs/sms-lite.
7
+ * notifications reste découplé : on COMPOSE des senders `{ send(mail) -> { messageId } }`, on ne réimplémente rien.
8
+ *
9
+ * Usage :
10
+ * const sender = createMultiChannelSender({
11
+ * email: mailer, // @mostajs/mailer
12
+ * sms: smsMailAdapter(smsTransport), // adapte @mostajs/sms-lite (cf. ci-dessous)
13
+ * });
14
+ * const engine = createNotificationEngine({ mailer: sender });
15
+ * // un template qui rend { to: ctx.phone, text: "...", channel: "sms" } part par SMS.
16
+ */
17
+ export interface ChannelSender {
18
+ send(mail: any): Promise<{
19
+ messageId?: string;
20
+ } | {
21
+ id?: string;
22
+ } | any>;
23
+ }
24
+ export declare function createMultiChannelSender(channels: Record<string, ChannelSender>, opts?: {
25
+ defaultChannel?: string;
26
+ channelOf?: (mail: any) => string | undefined;
27
+ }): ChannelSender;
28
+ /** Adapte un transport @mostajs/sms-lite ({ send({to,text}) }) en sender de notifications ({ send(mail) -> {messageId} }). */
29
+ export declare function smsMailAdapter(smsTransport: {
30
+ send(m: {
31
+ to: any;
32
+ text?: string;
33
+ }): Promise<{
34
+ ok: boolean;
35
+ id?: string;
36
+ error?: string;
37
+ }>;
38
+ }): ChannelSender;
39
+ //# sourceMappingURL=multi-channel.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"multi-channel.d.ts","sourceRoot":"","sources":["../src/multi-channel.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,MAAM,WAAW,aAAa;IAAG,IAAI,CAAC,IAAI,EAAE,GAAG,GAAG,OAAO,CAAC;QAAE,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG;QAAE,EAAE,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,GAAG,CAAC,CAAC;CAAE;AAE5G,wBAAgB,wBAAwB,CACtC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,EACvC,IAAI,GAAE;IAAE,cAAc,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,CAAC,IAAI,EAAE,GAAG,KAAK,MAAM,GAAG,SAAS,CAAA;CAAO,GACpF,aAAa,CAYf;AAED,8HAA8H;AAC9H,wBAAgB,cAAc,CAAC,YAAY,EAAE;IAAE,IAAI,CAAC,CAAC,EAAE;QAAE,EAAE,EAAE,GAAG,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC;QAAE,EAAE,EAAE,OAAO,CAAC;QAAC,EAAE,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;CAAE,GAAG,aAAa,CAS1J"}
@@ -0,0 +1,29 @@
1
+ export function createMultiChannelSender(channels, opts = {}) {
2
+ if (!channels || typeof channels !== "object")
3
+ throw new Error("createMultiChannelSender: channels requis");
4
+ const defaultChannel = opts.defaultChannel || "email";
5
+ return {
6
+ async send(mail) {
7
+ const key = (opts.channelOf ? opts.channelOf(mail) : mail?.channel) || defaultChannel;
8
+ const sender = channels[key];
9
+ if (!sender?.send)
10
+ throw new Error(`createMultiChannelSender: canal inconnu « ${key} »`);
11
+ const r = await sender.send(mail);
12
+ return { messageId: r?.messageId ?? r?.id ?? null };
13
+ },
14
+ };
15
+ }
16
+ /** Adapte un transport @mostajs/sms-lite ({ send({to,text}) }) en sender de notifications ({ send(mail) -> {messageId} }). */
17
+ export function smsMailAdapter(smsTransport) {
18
+ if (!smsTransport?.send)
19
+ throw new Error("smsMailAdapter: transport SMS requis");
20
+ return {
21
+ async send(mail) {
22
+ const r = await smsTransport.send({ to: mail?.to, text: mail?.text ?? mail?.subject ?? "" });
23
+ if (!r?.ok)
24
+ throw new Error(r?.error || "échec d'envoi SMS");
25
+ return { messageId: r.id ?? null };
26
+ },
27
+ };
28
+ }
29
+ //# sourceMappingURL=multi-channel.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"multi-channel.js","sourceRoot":"","sources":["../src/multi-channel.ts"],"names":[],"mappings":"AAkBA,MAAM,UAAU,wBAAwB,CACtC,QAAuC,EACvC,OAAmF,EAAE;IAErF,IAAI,CAAC,QAAQ,IAAI,OAAO,QAAQ,KAAK,QAAQ;QAAE,MAAM,IAAI,KAAK,CAAC,2CAA2C,CAAC,CAAC;IAC5G,MAAM,cAAc,GAAG,IAAI,CAAC,cAAc,IAAI,OAAO,CAAC;IACtD,OAAO;QACL,KAAK,CAAC,IAAI,CAAC,IAAS;YAClB,MAAM,GAAG,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,OAAO,CAAC,IAAI,cAAc,CAAC;YACtF,MAAM,MAAM,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC;YAC7B,IAAI,CAAC,MAAM,EAAE,IAAI;gBAAE,MAAM,IAAI,KAAK,CAAC,6CAA6C,GAAG,IAAI,CAAC,CAAC;YACzF,MAAM,CAAC,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YAClC,OAAO,EAAE,SAAS,EAAE,CAAC,EAAE,SAAS,IAAI,CAAC,EAAE,EAAE,IAAI,IAAI,EAAE,CAAC;QACtD,CAAC;KACF,CAAC;AACJ,CAAC;AAED,8HAA8H;AAC9H,MAAM,UAAU,cAAc,CAAC,YAA4G;IACzI,IAAI,CAAC,YAAY,EAAE,IAAI;QAAE,MAAM,IAAI,KAAK,CAAC,sCAAsC,CAAC,CAAC;IACjF,OAAO;QACL,KAAK,CAAC,IAAI,CAAC,IAAS;YAClB,MAAM,CAAC,GAAG,MAAM,YAAY,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,IAAI,IAAI,EAAE,OAAO,IAAI,EAAE,EAAE,CAAC,CAAC;YAC7F,IAAI,CAAC,CAAC,EAAE,EAAE;gBAAE,MAAM,IAAI,KAAK,CAAC,CAAC,EAAE,KAAK,IAAI,mBAAmB,CAAC,CAAC;YAC7D,OAAO,EAAE,SAAS,EAAE,CAAC,CAAC,EAAE,IAAI,IAAI,EAAE,CAAC;QACrC,CAAC;KACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,13 @@
1
+ /** Magasin de timestamps pour le throttle. In-memory par défaut. */
2
+ export interface ThrottleStore {
3
+ /** Timestamp (ms epoch) du dernier envoi pour cette clé, ou undefined. */
4
+ getLastSent(key: string): Promise<number | undefined> | number | undefined;
5
+ /** Enregistre un envoi. */
6
+ recordSent(key: string, at: number): Promise<void> | void;
7
+ /** Optionnel : purge des entrées plus anciennes que `olderThan` (ms epoch). */
8
+ prune?(olderThan: number): Promise<void> | void;
9
+ }
10
+ /** Map<key, lastSentAt> en mémoire — par défaut. Pas de persistance, pas de
11
+ * partage inter-process : adapté POC et single-instance. */
12
+ export declare function createInMemoryThrottleStore(): ThrottleStore;
13
+ //# sourceMappingURL=throttle.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"throttle.d.ts","sourceRoot":"","sources":["../src/throttle.ts"],"names":[],"mappings":"AAQA,oEAAoE;AACpE,MAAM,WAAW,aAAa;IAC5B,0EAA0E;IAC1E,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,GAAG,MAAM,GAAG,SAAS,CAAA;IAC1E,2BAA2B;IAC3B,UAAU,CAAC,GAAG,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAA;IACzD,+EAA+E;IAC/E,KAAK,CAAC,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAA;CAChD;AAED;6DAC6D;AAC7D,wBAAgB,2BAA2B,IAAI,aAAa,CAS3D"}
@@ -0,0 +1,22 @@
1
+ // @mostajs/notifications — Throttle store abstraction (N2 / N7-ready)
2
+ // Author: Dr Hamid MADANI <drmdh@msn.com>
3
+ //
4
+ // Le throttle empêche un kind de spammer quand son trigger se répète (alerte
5
+ // franchissant un seuil ré-évalué chaque heure). L'interface est injectable
6
+ // pour rester transparente lors du passage cross-instance (Mongo/Redis) en
7
+ // v0.4 — voir GAP-N7 de AUDIT-SPRINT5-FRAUDE-REPORTING-22052026.md.
8
+ /** Map<key, lastSentAt> en mémoire — par défaut. Pas de persistance, pas de
9
+ * partage inter-process : adapté POC et single-instance. */
10
+ export function createInMemoryThrottleStore() {
11
+ const map = new Map();
12
+ return {
13
+ getLastSent(key) { return map.get(key); },
14
+ recordSent(key, at) { map.set(key, at); },
15
+ prune(olderThan) {
16
+ for (const [k, v] of map)
17
+ if (v < olderThan)
18
+ map.delete(k);
19
+ },
20
+ };
21
+ }
22
+ //# sourceMappingURL=throttle.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"throttle.js","sourceRoot":"","sources":["../src/throttle.ts"],"names":[],"mappings":"AAAA,sEAAsE;AACtE,0CAA0C;AAC1C,EAAE;AACF,6EAA6E;AAC7E,4EAA4E;AAC5E,2EAA2E;AAC3E,oEAAoE;AAYpE;6DAC6D;AAC7D,MAAM,UAAU,2BAA2B;IACzC,MAAM,GAAG,GAAG,IAAI,GAAG,EAAkB,CAAA;IACrC,OAAO;QACL,WAAW,CAAC,GAAG,IAAI,OAAO,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA,CAAC,CAAC;QACxC,UAAU,CAAC,GAAG,EAAE,EAAE,IAAI,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE,EAAE,CAAC,CAAA,CAAC,CAAC;QACxC,KAAK,CAAC,SAAS;YACb,KAAK,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,GAAG;gBAAE,IAAI,CAAC,GAAG,SAAS;oBAAE,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,CAAA;QAC5D,CAAC;KACF,CAAA;AACH,CAAC"}
package/llms.txt ADDED
@@ -0,0 +1,102 @@
1
+ # @mostajs/notifications — fiche LLM
2
+ > Moteur de notifications générique : templating + i18n + dispatch MULTI-CANAL (email + SMS + …) + idempotence + throttle + preview, agnostique du domaine.
3
+
4
+ - Version: 0.3.0 · Licence: AGPL-3.0-or-later · Auteur: Dr Hamid MADANI <drmdh@msn.com>
5
+ - Chemin: mostajs/mosta-notifications · Statut audit: complet (dist/) · Tests: 23/23 + 3 (multi-canal)
6
+ - MULTI-CANAL (0.3.0) : `createMultiChannelSender(channels,{defaultChannel='email',channelOf?})` route par `mail.channel` ; `smsMailAdapter(smsTransport)` adapte @mostajs/sms-lite. Passé comme `mailer` au moteur → notify email OU sms. Rétro-compat (mailer seul inchangé).
7
+
8
+ ## RÔLE
9
+ Découple le dispatch des notifications (registry de templates, i18n, idempotence, callbacks, throttle, preview) du domaine métier (booking, media-sfu, transit-apc, apps custom). Tout domaine envoie une notification via `engine.notify(kind, context)` ; les adapters spécifiques (`@mostajs/booking-notifications`, etc.) sont de fines glues qui mappent leurs events internes en kinds génériques. S'appuie sur `@mostajs/mailer` pour l'envoi effectif.
10
+
11
+ **Frontière de responsabilités** : *quand* et *comment* déclencher (kind, throttle, locale, preview) = ce module. *Quoi* envoyer (champs du mail, pièces jointes, headers, replyTo) = `@mostajs/mailer`. `RenderedMail` est un **ré-export** du type `Mail` de mailer — pas de duplication.
12
+
13
+ ## INSTALLATION
14
+ npm i @mostajs/notifications @mostajs/mailer
15
+
16
+ ## EXPORTS
17
+ - Factories: `createNotificationEngine`, `simpleTemplate`, `localizedTemplate`, `createInMemoryThrottleStore`
18
+ - Constante: `moduleInfo` (`{ name, version, scope }`)
19
+ - Types: `Locale`, `NotificationContext`, `RenderedMail` (= `Mail` de `@mostajs/mailer`), `MailAttachment` (ré-export), `NotificationTemplate`, `NotificationEngineOptions`, `NotificationEngine`, `RegisterOptions`, `ThrottleOptions`, `ThrottleStore`, `SkipReason`
20
+
21
+ ## API — SIGNATURES
22
+ - `createNotificationEngine(opts: NotificationEngineOptions): NotificationEngine`
23
+ - `simpleTemplate(spec: { to; subject; text?; html?; messageId?; attachments? }): NotificationTemplate`
24
+ - `localizedTemplate(variants, resolveTo, resolveMessageId?, resolveAttachments?): NotificationTemplate`
25
+ - `createInMemoryThrottleStore(): ThrottleStore`
26
+
27
+ Interface `NotificationEngine` :
28
+ - `register(kind, template, options?: { throttle?: ThrottleOptions }): void`
29
+ - `unregister(kind): void`
30
+ - `registeredKinds(): string[]`
31
+ - `setEnabled(kind, enabled): void`
32
+ - `notify(kind, context): Promise<{messageId}|null>` — null = skipped
33
+ - `notifyBatch(items): Promise<Array<{messageId}|null>>`
34
+ - `preview(kind, context): Promise<RenderedMail|null>` — render-only, **n'envoie pas**, **ne consomme pas la fenêtre de throttle** *(v0.2 / N3)*
35
+
36
+ ## TYPES CLÉS
37
+ - `Locale = string`
38
+ - `NotificationContext { locale?: Locale; [key: string]: unknown }`
39
+ - `RenderedMail = Mail` (de `@mostajs/mailer`) — fournit `to/cc/bcc/from/replyTo/subject/text/html/attachments/headers/messageId/metadata`
40
+ - `NotificationTemplate { render(context): Promise<RenderedMail|null>|RenderedMail|null }`
41
+ - `ThrottleOptions { windowMs: number; keyFrom?: (ctx)=>string }` *(v0.2 / N2)*
42
+ - `ThrottleStore { getLastSent(key); recordSent(key, at); prune?(olderThan) }` — injectable (in-memory par défaut ; Mongo/Redis v0.4)
43
+ - `SkipReason = 'disabled'|'unknown-kind'|'template-null'|'no-recipient'|'render-error'|'send-error'|'throttled'`
44
+ - `NotificationEngineOptions { mailer: {send}; templates?; defaultLocale?; defaultFrom?; disabled?; throttleStore?; now?; onSent?; onError?; onSkipped? }`
45
+
46
+ ## PATTERN
47
+ ```ts
48
+ import { createNotificationEngine, simpleTemplate } from '@mostajs/notifications'
49
+ import { createMailer, createSmtpDriver } from '@mostajs/mailer'
50
+
51
+ const mailer = createMailer({
52
+ drivers: { smtp: createSmtpDriver({ host, port: 587, user, pass }) },
53
+ defaultDriver: 'smtp',
54
+ })
55
+
56
+ const engine = createNotificationEngine({
57
+ mailer, defaultFrom: 'noreply@example.com', defaultLocale: 'fr',
58
+ })
59
+
60
+ // Alerte avec anti-spam (throttle 6 h par ligne)
61
+ engine.register('fraud.threshold.exceeded', simpleTemplate({
62
+ to: ctx => `ops@${ctx.accountId}.tld`,
63
+ subject: ctx => `[Fraude] Ligne ${ctx.lineId} : ${ctx.rate}%`,
64
+ html: ctx => `…`,
65
+ }), { throttle: { windowMs: 6 * 3600_000, keyFrom: ctx => String(ctx.lineId) } })
66
+
67
+ // Rapport mensuel avec PDF en pièce jointe (les attachments transitent telles
68
+ // quelles vers @mostajs/mailer — pas de duplication de type côté notifications)
69
+ engine.register('report.monthly', simpleTemplate({
70
+ to: ctx => String(ctx.opsEmail),
71
+ subject: ctx => `Rapport mensuel — ${ctx.month}`,
72
+ text: () => 'Cf. PJ.',
73
+ attachments: ctx => [{ filename: `rapport-${ctx.month}.pdf`, content: ctx.pdf as Buffer }],
74
+ }))
75
+
76
+ // Preview avant déclenchement réel (ergo opérateur)
77
+ const preview = await engine.preview('report.monthly', { month: '2026-04', pdf, opsEmail })
78
+ // preview === RenderedMail (rendu fusionné) ; rien envoyé.
79
+
80
+ await engine.notify('report.monthly', { month: '2026-04', pdf, opsEmail })
81
+ ```
82
+
83
+ ## DÉPEND DE
84
+ - `@mostajs/mailer` (peer dep `^0.1.1`) — fournit les types `Mail`/`MailAttachment` ET le dispatch. `RenderedMail` est l'alias de `Mail` côté notifications.
85
+
86
+ ## CHANGEMENTS v0.2.0
87
+ - **N1** : `RenderedMail` est désormais le `Mail` de `@mostajs/mailer` (pas de duplication). Les pièces jointes sont ainsi disponibles sans qu'aucun nouveau type ne soit défini ici.
88
+ - **N2** : throttle par kind via `register(kind, tpl, { throttle: { windowMs, keyFrom? } })`. Store par défaut in-memory ; `ThrottleStore` injectable pour cross-instance (Mongo/Redis en v0.4).
89
+ - **N3** : `engine.preview(kind, context)` — render-only, sans dispatch, sans consommer le throttle, sans `onSent`. Utilisable pour UI ops.
90
+ - Ajout : callback `onSkipped(kind, ctx, reason: SkipReason)`, option `now?: () => number` (testabilité).
91
+ - Rétro-compat : aucun appel signé v0.1.0 ne casse (les 23 tests incluent une suite `test-backward-compat`).
92
+
93
+ ## PIÈGES
94
+ - L'option `mailer` n'exige qu'un objet `{ send }` — pas forcément `@mostajs/mailer`, n'importe quel adapter compatible.
95
+ - `notify` retourne `null` (sans erreur) avec `SkipReason` typée envoyée à `onSkipped`.
96
+ - Throttle in-memory : ne survit pas au redémarrage ni au multi-process. Pour MVP production → injecter un `ThrottleStore` persisté (gap N7, v0.4).
97
+ - `preview` applique `defaultLocale` et fusionne `from`/`metadata` comme `notify` — pour ne pas tromper l'utilisateur sur le rendu final.
98
+ - Pièces jointes = type `MailAttachment` de **mailer**, pas redéfini ici. Si l'app a besoin d'un champ absent de `Mail`, c'est mailer qu'il faut étendre.
99
+ - L'idempotence par `messageId` reste déléguée à `@mostajs/mailer` (MailLog).
100
+
101
+ ## RÉFÉRENCES
102
+ README.md · docs/STATE-OF-THE-ART.md · /home/hmd/dev/MostaGare-Install/SolutionCh/Tramway/AUDIT-SPRINT5-FRAUDE-REPORTING-22052026.md §6
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
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.",
3
+ "version": "0.3.0",
4
+ "description": "Generic notification engine for @mostajs/* — domain-agnostic templating + i18n + MULTI-CHANNEL dispatch (email + SMS + …) + idempotence + throttling + preview. Consume any event kind from any source. Plug-and-play with @mostajs/mailer (email) and @mostajs/sms-lite (SMS).",
5
5
  "author": "Dr Hamid MADANI <drmdh@msn.com>",
6
6
  "license": "AGPL-3.0-or-later",
7
7
  "type": "module",
@@ -17,12 +17,9 @@
17
17
  "files": [
18
18
  "dist",
19
19
  "README.md",
20
- "LICENSE"
20
+ "LICENSE",
21
+ "llms.txt"
21
22
  ],
22
- "scripts": {
23
- "build": "tsc",
24
- "prepublishOnly": "npm run build"
25
- },
26
23
  "peerDependencies": {
27
24
  "@mostajs/mailer": "^0.1.1"
28
25
  },
@@ -30,6 +27,11 @@
30
27
  "node": ">=18"
31
28
  },
32
29
  "devDependencies": {
30
+ "@types/node": "^20.11.0",
31
+ "tsx": "^4.7.0",
33
32
  "typescript": "^6.0.3"
33
+ },
34
+ "scripts": {
35
+ "build": "tsc"
34
36
  }
35
- }
37
+ }