@fluojs/notifications 1.0.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,62 @@
1
+ import type { NormalizedNotificationsModuleOptions, NotificationChannel, NotificationDispatchBatchResult, NotificationDispatchManyOptions, NotificationDispatchOptions, NotificationDispatchRequest, NotificationDispatchResult, Notifications } from './types.js';
2
+ /**
3
+ * Injectable orchestration service for shared notification dispatch.
4
+ *
5
+ * @remarks
6
+ * The foundation package keeps channel-specific payload semantics opaque. It only
7
+ * resolves channels by name, applies optional queue delegation, and emits optional
8
+ * lifecycle events through the configured publisher seam.
9
+ */
10
+ export declare class NotificationsService implements Notifications {
11
+ private readonly options;
12
+ private readonly channelsByName;
13
+ private fallbackDeliveryIdSequence;
14
+ constructor(options: NormalizedNotificationsModuleOptions, channels: readonly NotificationChannel[]);
15
+ /**
16
+ * Dispatches one notification through a registered channel or the configured queue seam.
17
+ *
18
+ * @typeParam TRequest Shared notification request envelope subtype.
19
+ * @param notification Request envelope identifying the channel and opaque payload.
20
+ * @param options Optional abort, queue, and lifecycle-publication controls.
21
+ * @returns A normalized dispatch result describing direct vs queued delivery.
22
+ * @throws {NotificationChannelNotFoundError} When no registered channel matches `notification.channel`.
23
+ * @throws {NotificationQueueNotConfiguredError} When queue delivery is requested without a queue adapter.
24
+ *
25
+ * @example
26
+ * ```ts
27
+ * await notifications.dispatch({
28
+ * channel: 'email',
29
+ * subject: 'Welcome',
30
+ * payload: { template: 'welcome', userId: 'u_123' },
31
+ * recipients: ['hello@example.com'],
32
+ * });
33
+ * ```
34
+ */
35
+ dispatch<TRequest extends NotificationDispatchRequest>(notification: TRequest, options?: NotificationDispatchOptions): Promise<NotificationDispatchResult>;
36
+ /**
37
+ * Dispatches multiple notifications in input order with optional bulk queue delegation.
38
+ *
39
+ * @typeParam TRequest Shared notification request envelope subtype.
40
+ * @param notifications Ordered notification envelopes to send or enqueue.
41
+ * @param options Optional queue preference and tolerant error-handling controls.
42
+ * @returns A batch summary containing successes and captured failures.
43
+ * @throws {NotificationQueueNotConfiguredError} When queue-backed bulk delivery is requested without a queue adapter.
44
+ */
45
+ dispatchMany<TRequest extends NotificationDispatchRequest>(notifications: readonly TRequest[], options?: NotificationDispatchManyOptions): Promise<NotificationDispatchBatchResult<TRequest>>;
46
+ /**
47
+ * Creates a health/readiness snapshot for the active notifications wiring.
48
+ *
49
+ * @returns A structured snapshot describing registered channels and optional integration seams.
50
+ */
51
+ createPlatformStatusSnapshot(): import("./status.js").NotificationsPlatformStatusSnapshot;
52
+ private createQueueJob;
53
+ private requireChannel;
54
+ private normalizeDeliveryId;
55
+ private requireQueueAdapter;
56
+ private shouldPublishLifecycleEvents;
57
+ private shouldQueueSingleDispatch;
58
+ private shouldQueue;
59
+ private publishLifecycleEvent;
60
+ private publishLifecycleEventSafely;
61
+ }
62
+ //# sourceMappingURL=service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EACV,oCAAoC,EACpC,mBAAmB,EACnB,+BAA+B,EAC/B,+BAA+B,EAC/B,2BAA2B,EAC3B,2BAA2B,EAC3B,0BAA0B,EAE1B,aAAa,EAEd,MAAM,YAAY,CAAC;AAEpB;;;;;;;GAOG;AACH,qBACa,oBAAqB,YAAW,aAAa;IAKtD,OAAO,CAAC,QAAQ,CAAC,OAAO;IAJ1B,OAAO,CAAC,QAAQ,CAAC,cAAc,CAA0C;IACzE,OAAO,CAAC,0BAA0B,CAAK;gBAGpB,OAAO,EAAE,oCAAoC,EAC9D,QAAQ,EAAE,SAAS,mBAAmB,EAAE;IAO1C;;;;;;;;;;;;;;;;;;;OAmBG;IACG,QAAQ,CAAC,QAAQ,SAAS,2BAA2B,EACzD,YAAY,EAAE,QAAQ,EACtB,OAAO,GAAE,2BAAgC,GACxC,OAAO,CAAC,0BAA0B,CAAC;IAkDtC;;;;;;;;OAQG;IACG,YAAY,CAAC,QAAQ,SAAS,2BAA2B,EAC7D,aAAa,EAAE,SAAS,QAAQ,EAAE,EAClC,OAAO,GAAE,+BAAoC,GAC5C,OAAO,CAAC,+BAA+B,CAAC,QAAQ,CAAC,CAAC;IAuFrD;;;;OAIG;IACH,4BAA4B;IAS5B,OAAO,CAAC,cAAc;IAQtB,OAAO,CAAC,cAAc;IAUtB,OAAO,CAAC,mBAAmB;IAc3B,OAAO,CAAC,mBAAmB;IAQ3B,OAAO,CAAC,4BAA4B;IAQpC,OAAO,CAAC,yBAAyB;IAIjC,OAAO,CAAC,WAAW;YAYL,qBAAqB;YA4BrB,2BAA2B;CAa1C"}
@@ -0,0 +1,260 @@
1
+ let _initClass;
2
+ function _applyDecs(e, t, n, r, o, i) { var a, c, u, s, f, l, p, d = Symbol.metadata || Symbol.for("Symbol.metadata"), m = Object.defineProperty, h = Object.create, y = [h(null), h(null)], v = t.length; function g(t, n, r) { return function (o, i) { n && (i = o, o = e); for (var a = 0; a < t.length; a++) i = t[a].apply(o, r ? [i] : []); return r ? i : o; }; } function b(e, t, n, r) { if ("function" != typeof e && (r || void 0 !== e)) throw new TypeError(t + " must " + (n || "be") + " a function" + (r ? "" : " or undefined")); return e; } function applyDec(e, t, n, r, o, i, u, s, f, l, p) { function d(e) { if (!p(e)) throw new TypeError("Attempted to access private element on non-instance"); } var h = [].concat(t[0]), v = t[3], w = !u, D = 1 === o, S = 3 === o, j = 4 === o, E = 2 === o; function I(t, n, r) { return function (o, i) { return n && (i = o, o = e), r && r(o), P[t].call(o, i); }; } if (!w) { var P = {}, k = [], F = S ? "get" : j || D ? "set" : "value"; if (f ? (l || D ? P = { get: _setFunctionName(function () { return v(this); }, r, "get"), set: function (e) { t[4](this, e); } } : P[F] = v, l || _setFunctionName(P[F], r, E ? "" : F)) : l || (P = Object.getOwnPropertyDescriptor(e, r)), !l && !f) { if ((c = y[+s][r]) && 7 !== (c ^ o)) throw Error("Decorating two elements with the same name (" + P[F].name + ") is not supported yet"); y[+s][r] = o < 3 ? 1 : o; } } for (var N = e, O = h.length - 1; O >= 0; O -= n ? 2 : 1) { var T = b(h[O], "A decorator", "be", !0), z = n ? h[O - 1] : void 0, A = {}, H = { kind: ["field", "accessor", "method", "getter", "setter", "class"][o], name: r, metadata: a, addInitializer: function (e, t) { if (e.v) throw new TypeError("attempted to call addInitializer after decoration was finished"); b(t, "An initializer", "be", !0), i.push(t); }.bind(null, A) }; if (w) c = T.call(z, N, H), A.v = 1, b(c, "class decorators", "return") && (N = c);else if (H.static = s, H.private = f, c = H.access = { has: f ? p.bind() : function (e) { return r in e; } }, j || (c.get = f ? E ? function (e) { return d(e), P.value; } : I("get", 0, d) : function (e) { return e[r]; }), E || S || (c.set = f ? I("set", 0, d) : function (e, t) { e[r] = t; }), N = T.call(z, D ? { get: P.get, set: P.set } : P[F], H), A.v = 1, D) { if ("object" == typeof N && N) (c = b(N.get, "accessor.get")) && (P.get = c), (c = b(N.set, "accessor.set")) && (P.set = c), (c = b(N.init, "accessor.init")) && k.unshift(c);else if (void 0 !== N) throw new TypeError("accessor decorators must return an object with get, set, or init properties or undefined"); } else b(N, (l ? "field" : "method") + " decorators", "return") && (l ? k.unshift(N) : P[F] = N); } return o < 2 && u.push(g(k, s, 1), g(i, s, 0)), l || w || (f ? D ? u.splice(-1, 0, I("get", s), I("set", s)) : u.push(E ? P[F] : b.call.bind(P[F])) : m(e, r, P)), N; } function w(e) { return m(e, d, { configurable: !0, enumerable: !0, value: a }); } return void 0 !== i && (a = i[d]), a = h(null == a ? null : a), f = [], l = function (e) { e && f.push(g(e)); }, p = function (t, r) { for (var i = 0; i < n.length; i++) { var a = n[i], c = a[1], l = 7 & c; if ((8 & c) == t && !l == r) { var p = a[2], d = !!a[3], m = 16 & c; applyDec(t ? e : e.prototype, a, m, d ? "#" + p : _toPropertyKey(p), l, l < 2 ? [] : t ? s = s || [] : u = u || [], f, !!t, d, r, t && d ? function (t) { return _checkInRHS(t) === e; } : o); } } }, p(8, 0), p(0, 0), p(8, 1), p(0, 1), l(u), l(s), c = f, v || w(e), { e: c, get c() { var n = []; return v && [w(e = applyDec(e, [t], r, e.name, 5, n)), g(n, 1)]; } }; }
3
+ function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : i + ""; }
4
+ function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
5
+ function _setFunctionName(e, t, n) { "symbol" == typeof t && (t = (t = t.description) ? "[" + t + "]" : ""); try { Object.defineProperty(e, "name", { configurable: !0, value: n ? n + " " + t : t }); } catch (e) {} return e; }
6
+ function _checkInRHS(e) { if (Object(e) !== e) throw TypeError("right-hand side of 'in' should be an object, got " + (null !== e ? typeof e : "null")); return e; }
7
+ import { Inject } from '@fluojs/core';
8
+ import { NotificationChannelNotFoundError, NotificationQueueNotConfiguredError } from './errors.js';
9
+ import { createNotificationsPlatformStatusSnapshot } from './status.js';
10
+ import { NOTIFICATION_CHANNELS, NOTIFICATIONS_OPTIONS } from './tokens.js';
11
+ let _NotificationsService;
12
+ /**
13
+ * Injectable orchestration service for shared notification dispatch.
14
+ *
15
+ * @remarks
16
+ * The foundation package keeps channel-specific payload semantics opaque. It only
17
+ * resolves channels by name, applies optional queue delegation, and emits optional
18
+ * lifecycle events through the configured publisher seam.
19
+ */
20
+ class NotificationsService {
21
+ static {
22
+ [_NotificationsService, _initClass] = _applyDecs(this, [Inject(NOTIFICATIONS_OPTIONS, NOTIFICATION_CHANNELS)], []).c;
23
+ }
24
+ channelsByName = new Map();
25
+ fallbackDeliveryIdSequence = 0;
26
+ constructor(options, channels) {
27
+ this.options = options;
28
+ for (const channel of channels) {
29
+ this.channelsByName.set(channel.channel, channel);
30
+ }
31
+ }
32
+
33
+ /**
34
+ * Dispatches one notification through a registered channel or the configured queue seam.
35
+ *
36
+ * @typeParam TRequest Shared notification request envelope subtype.
37
+ * @param notification Request envelope identifying the channel and opaque payload.
38
+ * @param options Optional abort, queue, and lifecycle-publication controls.
39
+ * @returns A normalized dispatch result describing direct vs queued delivery.
40
+ * @throws {NotificationChannelNotFoundError} When no registered channel matches `notification.channel`.
41
+ * @throws {NotificationQueueNotConfiguredError} When queue delivery is requested without a queue adapter.
42
+ *
43
+ * @example
44
+ * ```ts
45
+ * await notifications.dispatch({
46
+ * channel: 'email',
47
+ * subject: 'Welcome',
48
+ * payload: { template: 'welcome', userId: 'u_123' },
49
+ * recipients: ['hello@example.com'],
50
+ * });
51
+ * ```
52
+ */
53
+ async dispatch(notification, options = {}) {
54
+ await this.publishLifecycleEventSafely('notification.dispatch.requested', notification, options);
55
+ if (this.shouldQueueSingleDispatch(options)) {
56
+ this.requireChannel(notification.channel);
57
+ const job = this.createQueueJob(notification);
58
+ try {
59
+ const deliveryId = await this.requireQueueAdapter().enqueue(job);
60
+ const result = {
61
+ channel: notification.channel,
62
+ deliveryId: this.normalizeDeliveryId(deliveryId, notification),
63
+ queued: true,
64
+ status: 'queued'
65
+ };
66
+ await this.publishLifecycleEventSafely('notification.dispatch.queued', notification, options, result.deliveryId);
67
+ return result;
68
+ } catch (error) {
69
+ await this.publishLifecycleEventSafely('notification.dispatch.failed', notification, options, undefined, error);
70
+ throw error;
71
+ }
72
+ }
73
+ const channel = this.requireChannel(notification.channel);
74
+ try {
75
+ const delivery = await channel.send(notification, {
76
+ signal: options.signal
77
+ });
78
+ const result = {
79
+ channel: notification.channel,
80
+ deliveryId: this.normalizeDeliveryId(delivery.externalId, notification),
81
+ metadata: delivery.metadata,
82
+ queued: delivery.status === 'queued',
83
+ status: delivery.status ?? 'delivered'
84
+ };
85
+ await this.publishLifecycleEventSafely(result.queued ? 'notification.dispatch.queued' : 'notification.dispatch.delivered', notification, options, result.deliveryId);
86
+ return result;
87
+ } catch (error) {
88
+ await this.publishLifecycleEventSafely('notification.dispatch.failed', notification, options, undefined, error);
89
+ throw error;
90
+ }
91
+ }
92
+
93
+ /**
94
+ * Dispatches multiple notifications in input order with optional bulk queue delegation.
95
+ *
96
+ * @typeParam TRequest Shared notification request envelope subtype.
97
+ * @param notifications Ordered notification envelopes to send or enqueue.
98
+ * @param options Optional queue preference and tolerant error-handling controls.
99
+ * @returns A batch summary containing successes and captured failures.
100
+ * @throws {NotificationQueueNotConfiguredError} When queue-backed bulk delivery is requested without a queue adapter.
101
+ */
102
+ async dispatchMany(notifications, options = {}) {
103
+ if (notifications.length === 0) {
104
+ return {
105
+ failed: 0,
106
+ failures: [],
107
+ queued: 0,
108
+ results: [],
109
+ succeeded: 0
110
+ };
111
+ }
112
+ if (this.shouldQueue(notifications.length, options)) {
113
+ const queue = this.requireQueueAdapter();
114
+ for (const notification of notifications) {
115
+ this.requireChannel(notification.channel);
116
+ }
117
+ const jobs = notifications.map(notification => this.createQueueJob(notification));
118
+ for (const notification of notifications) {
119
+ await this.publishLifecycleEventSafely('notification.dispatch.requested', notification, options);
120
+ }
121
+ let ids;
122
+ try {
123
+ ids = queue.enqueueMany ? await queue.enqueueMany(jobs) : await Promise.all(jobs.map(job => queue.enqueue(job)));
124
+ } catch (error) {
125
+ await Promise.all(notifications.map(notification => this.publishLifecycleEventSafely('notification.dispatch.failed', notification, options, undefined, error)));
126
+ throw error;
127
+ }
128
+ const results = notifications.map((notification, index) => ({
129
+ channel: notification.channel,
130
+ deliveryId: this.normalizeDeliveryId(ids[index], notification),
131
+ queued: true,
132
+ status: 'queued'
133
+ }));
134
+ for (let index = 0; index < notifications.length; index += 1) {
135
+ const notification = notifications[index];
136
+ await this.publishLifecycleEventSafely('notification.dispatch.queued', notification, options, results[index]?.deliveryId);
137
+ }
138
+ return {
139
+ failed: 0,
140
+ failures: [],
141
+ queued: results.length,
142
+ results,
143
+ succeeded: results.length
144
+ };
145
+ }
146
+ const results = [];
147
+ const failures = [];
148
+ for (const notification of notifications) {
149
+ try {
150
+ results.push(await this.dispatch(notification, options));
151
+ } catch (error) {
152
+ const failure = {
153
+ error: error instanceof Error ? error : new Error('Notification dispatch failed.'),
154
+ notification
155
+ };
156
+ if (!(options.continueOnError ?? false)) {
157
+ throw failure.error;
158
+ }
159
+ failures.push(failure);
160
+ }
161
+ }
162
+ return {
163
+ failed: failures.length,
164
+ failures,
165
+ queued: results.filter(result => result.queued).length,
166
+ results,
167
+ succeeded: results.length
168
+ };
169
+ }
170
+
171
+ /**
172
+ * Creates a health/readiness snapshot for the active notifications wiring.
173
+ *
174
+ * @returns A structured snapshot describing registered channels and optional integration seams.
175
+ */
176
+ createPlatformStatusSnapshot() {
177
+ return createNotificationsPlatformStatusSnapshot({
178
+ bulkQueueThreshold: this.options.queue?.bulkThreshold ?? 0,
179
+ channelsRegistered: this.channelsByName.size,
180
+ eventPublisherConfigured: this.options.events !== undefined,
181
+ queueConfigured: this.options.queue !== undefined
182
+ });
183
+ }
184
+ createQueueJob(notification) {
185
+ return {
186
+ channel: notification.channel,
187
+ notification,
188
+ queuedAt: new Date().toISOString()
189
+ };
190
+ }
191
+ requireChannel(channelName) {
192
+ const channel = this.channelsByName.get(channelName);
193
+ if (!channel) {
194
+ throw new NotificationChannelNotFoundError(channelName);
195
+ }
196
+ return channel;
197
+ }
198
+ normalizeDeliveryId(value, fallback) {
199
+ if (value && value.length > 0) {
200
+ return value;
201
+ }
202
+ if (fallback.id) {
203
+ return fallback.id;
204
+ }
205
+ this.fallbackDeliveryIdSequence = (this.fallbackDeliveryIdSequence + 1) % Number.MAX_SAFE_INTEGER;
206
+ return `${fallback.channel}:${Date.now().toString(36)}:${this.fallbackDeliveryIdSequence.toString(36)}:${Math.random().toString(36).slice(2, 10)}`;
207
+ }
208
+ requireQueueAdapter() {
209
+ if (!this.options.queue) {
210
+ throw new NotificationQueueNotConfiguredError();
211
+ }
212
+ return this.options.queue.adapter;
213
+ }
214
+ shouldPublishLifecycleEvents(options) {
215
+ if (typeof options.publishLifecycleEvents === 'boolean') {
216
+ return options.publishLifecycleEvents;
217
+ }
218
+ return this.options.events?.publishLifecycleEvents ?? false;
219
+ }
220
+ shouldQueueSingleDispatch(options) {
221
+ return options.queue === true;
222
+ }
223
+ shouldQueue(notificationCount, options) {
224
+ if (options.queue === true) {
225
+ return true;
226
+ }
227
+ if (options.queue === false || !this.options.queue) {
228
+ return false;
229
+ }
230
+ return notificationCount >= this.options.queue.bulkThreshold;
231
+ }
232
+ async publishLifecycleEvent(name, notification, options, deliveryId, error) {
233
+ if (!this.options.events || !this.shouldPublishLifecycleEvents(options)) {
234
+ return;
235
+ }
236
+ const event = {
237
+ channel: notification.channel,
238
+ deliveryId,
239
+ error: error instanceof Error ? {
240
+ message: error.message,
241
+ name: error.name
242
+ } : undefined,
243
+ name,
244
+ notification,
245
+ occurredAt: new Date().toISOString()
246
+ };
247
+ await this.options.events.publisher.publish(event);
248
+ }
249
+ async publishLifecycleEventSafely(name, notification, options, deliveryId, error) {
250
+ try {
251
+ await this.publishLifecycleEvent(name, notification, options, deliveryId, error);
252
+ } catch {
253
+ return;
254
+ }
255
+ }
256
+ static {
257
+ _initClass();
258
+ }
259
+ }
260
+ export { _NotificationsService as NotificationsService };
@@ -0,0 +1,25 @@
1
+ import type { PlatformHealthReport, PlatformReadinessReport, PlatformSnapshot } from '@fluojs/runtime';
2
+ /** Resolved notification runtime mode used for diagnostics. */
3
+ export type NotificationsOperationMode = 'direct-only' | 'direct-with-events' | 'queue-backed' | 'queue-backed-with-events' | 'unconfigured';
4
+ /** Input required to describe the package health/readiness contract. */
5
+ export interface NotificationsStatusAdapterInput {
6
+ bulkQueueThreshold: number;
7
+ channelsRegistered: number;
8
+ eventPublisherConfigured: boolean;
9
+ queueConfigured: boolean;
10
+ }
11
+ /** Structured snapshot returned by {@link createNotificationsPlatformStatusSnapshot}. */
12
+ export interface NotificationsPlatformStatusSnapshot {
13
+ readiness: PlatformReadinessReport;
14
+ health: PlatformHealthReport;
15
+ ownership: PlatformSnapshot['ownership'];
16
+ details: Record<string, unknown>;
17
+ }
18
+ /**
19
+ * Creates a health/readiness snapshot for the notifications orchestration layer.
20
+ *
21
+ * @param input Registered-channel and optional-integration counts derived from the active module wiring.
22
+ * @returns A structured snapshot suitable for status endpoints and operational diagnostics.
23
+ */
24
+ export declare function createNotificationsPlatformStatusSnapshot(input: NotificationsStatusAdapterInput): NotificationsPlatformStatusSnapshot;
25
+ //# sourceMappingURL=status.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../src/status.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAE,uBAAuB,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAEvG,+DAA+D;AAC/D,MAAM,MAAM,0BAA0B,GAClC,aAAa,GACb,oBAAoB,GACpB,cAAc,GACd,0BAA0B,GAC1B,cAAc,CAAC;AAEnB,wEAAwE;AACxE,MAAM,WAAW,+BAA+B;IAC9C,kBAAkB,EAAE,MAAM,CAAC;IAC3B,kBAAkB,EAAE,MAAM,CAAC;IAC3B,wBAAwB,EAAE,OAAO,CAAC;IAClC,eAAe,EAAE,OAAO,CAAC;CAC1B;AAED,yFAAyF;AACzF,MAAM,WAAW,mCAAmC;IAClD,SAAS,EAAE,uBAAuB,CAAC;IACnC,MAAM,EAAE,oBAAoB,CAAC;IAC7B,SAAS,EAAE,gBAAgB,CAAC,WAAW,CAAC,CAAC;IACzC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAClC;AAyDD;;;;;GAKG;AACH,wBAAgB,yCAAyC,CACvD,KAAK,EAAE,+BAA+B,GACrC,mCAAmC,CAoBrC"}
package/dist/status.js ADDED
@@ -0,0 +1,76 @@
1
+ /** Resolved notification runtime mode used for diagnostics. */
2
+
3
+ /** Input required to describe the package health/readiness contract. */
4
+
5
+ /** Structured snapshot returned by {@link createNotificationsPlatformStatusSnapshot}. */
6
+
7
+ function resolveOperationMode(input) {
8
+ if (input.channelsRegistered === 0 && !input.queueConfigured && !input.eventPublisherConfigured) {
9
+ return 'unconfigured';
10
+ }
11
+ if (input.queueConfigured && input.eventPublisherConfigured) {
12
+ return 'queue-backed-with-events';
13
+ }
14
+ if (input.queueConfigured) {
15
+ return 'queue-backed';
16
+ }
17
+ if (input.eventPublisherConfigured) {
18
+ return 'direct-with-events';
19
+ }
20
+ return 'direct-only';
21
+ }
22
+ function createReadiness(input) {
23
+ if (input.channelsRegistered > 0) {
24
+ return {
25
+ critical: true,
26
+ status: 'ready'
27
+ };
28
+ }
29
+ return {
30
+ critical: true,
31
+ reason: 'No notification channels are registered.',
32
+ status: 'not-ready'
33
+ };
34
+ }
35
+ function createHealth(input) {
36
+ if (input.channelsRegistered > 0) {
37
+ return {
38
+ status: 'healthy'
39
+ };
40
+ }
41
+ if (input.queueConfigured || input.eventPublisherConfigured) {
42
+ return {
43
+ reason: 'Notifications infrastructure is configured, but no delivery channels are registered yet.',
44
+ status: 'degraded'
45
+ };
46
+ }
47
+ return {
48
+ reason: 'Notifications module has no registered channels or optional integrations.',
49
+ status: 'unhealthy'
50
+ };
51
+ }
52
+
53
+ /**
54
+ * Creates a health/readiness snapshot for the notifications orchestration layer.
55
+ *
56
+ * @param input Registered-channel and optional-integration counts derived from the active module wiring.
57
+ * @returns A structured snapshot suitable for status endpoints and operational diagnostics.
58
+ */
59
+ export function createNotificationsPlatformStatusSnapshot(input) {
60
+ return {
61
+ details: {
62
+ bulkQueueThreshold: input.bulkQueueThreshold,
63
+ channelsRegistered: input.channelsRegistered,
64
+ dependencies: [...(input.queueConfigured ? ['notifications.queue-adapter'] : []), ...(input.eventPublisherConfigured ? ['notifications.event-publisher'] : [])],
65
+ eventPublisherConfigured: input.eventPublisherConfigured,
66
+ operationMode: resolveOperationMode(input),
67
+ queueConfigured: input.queueConfigured
68
+ },
69
+ health: createHealth(input),
70
+ ownership: {
71
+ externallyManaged: input.queueConfigured || input.eventPublisherConfigured,
72
+ ownsResources: false
73
+ },
74
+ readiness: createReadiness(input)
75
+ };
76
+ }
@@ -0,0 +1,9 @@
1
+ import type { Token } from '@fluojs/core';
2
+ import type { Notifications, NormalizedNotificationsModuleOptions, NotificationChannel } from './types.js';
3
+ /** Compatibility injection token for the facade returned by {@link NotificationsModule.forRoot}. */
4
+ export declare const NOTIFICATIONS: Token<Notifications>;
5
+ /** Injection token for the normalized channel registry exposed to sibling notification packages. */
6
+ export declare const NOTIFICATION_CHANNELS: Token<readonly NotificationChannel[]>;
7
+ /** Injection token for normalized notifications module options consumed by {@link NotificationsService}. */
8
+ export declare const NOTIFICATIONS_OPTIONS: Token<NormalizedNotificationsModuleOptions>;
9
+ //# sourceMappingURL=tokens.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tokens.d.ts","sourceRoot":"","sources":["../src/tokens.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,cAAc,CAAC;AAE1C,OAAO,KAAK,EAAE,aAAa,EAAE,oCAAoC,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAE3G,oGAAoG;AACpG,eAAO,MAAM,aAAa,EAAE,KAAK,CAAC,aAAa,CAAoC,CAAC;AACpF,oGAAoG;AACpG,eAAO,MAAM,qBAAqB,EAAE,KAAK,CAAC,SAAS,mBAAmB,EAAE,CAA6C,CAAC;AACtH,4GAA4G;AAC5G,eAAO,MAAM,qBAAqB,EAAE,KAAK,CAAC,oCAAoC,CAA4C,CAAC"}
package/dist/tokens.js ADDED
@@ -0,0 +1,6 @@
1
+ /** Compatibility injection token for the facade returned by {@link NotificationsModule.forRoot}. */
2
+ export const NOTIFICATIONS = Symbol.for('fluo.notifications');
3
+ /** Injection token for the normalized channel registry exposed to sibling notification packages. */
4
+ export const NOTIFICATION_CHANNELS = Symbol.for('fluo.notifications.channels');
5
+ /** Injection token for normalized notifications module options consumed by {@link NotificationsService}. */
6
+ export const NOTIFICATIONS_OPTIONS = Symbol.for('fluo.notifications.options');
@@ -0,0 +1,188 @@
1
+ import type { AsyncModuleOptions } from '@fluojs/core';
2
+ /** Opaque payload shape carried through the shared notification contract. */
3
+ export type NotificationPayload = Record<string, unknown>;
4
+ /**
5
+ * Shared request envelope that leaf notification packages must understand.
6
+ *
7
+ * @typeParam TPayload Channel-specific payload shape supplied by the caller.
8
+ */
9
+ export interface NotificationDispatchRequest<TPayload extends NotificationPayload = NotificationPayload> {
10
+ channel: string;
11
+ id?: string;
12
+ locale?: string;
13
+ metadata?: Record<string, unknown>;
14
+ payload: TPayload;
15
+ recipients?: readonly string[];
16
+ subject?: string;
17
+ template?: string;
18
+ }
19
+ /** Delivery statuses emitted by direct and queue-backed notification workflows. */
20
+ export type NotificationDispatchStatus = 'delivered' | 'queued';
21
+ /**
22
+ * Channel-level delivery response returned by one concrete notification implementation.
23
+ *
24
+ * @typeParam TReceipt Provider-specific receipt or transport response shape.
25
+ */
26
+ export interface NotificationChannelDelivery<TReceipt = unknown> {
27
+ externalId?: string;
28
+ metadata?: Record<string, unknown>;
29
+ receipt?: TReceipt;
30
+ status?: NotificationDispatchStatus;
31
+ }
32
+ /** Context object passed to one registered notification channel. */
33
+ export interface NotificationChannelContext {
34
+ signal?: AbortSignal;
35
+ }
36
+ /**
37
+ * Stable contract that downstream channel packages (email, Slack, Discord, etc.) implement.
38
+ *
39
+ * @typeParam TRequest Shared request envelope subtype accepted by the channel implementation.
40
+ * @typeParam TReceipt Provider-specific delivery receipt returned by the channel.
41
+ */
42
+ export interface NotificationChannel<TRequest extends NotificationDispatchRequest = NotificationDispatchRequest, TReceipt = unknown> {
43
+ channel: string;
44
+ /**
45
+ * Sends one notification through the concrete channel implementation.
46
+ *
47
+ * @param notification Shared request envelope received from the notifications foundation package.
48
+ * @param context Optional dispatch-time context, including abort propagation.
49
+ * @returns Provider-specific delivery details normalized into the shared contract.
50
+ */
51
+ send(notification: TRequest, context: NotificationChannelContext): Promise<NotificationChannelDelivery<TReceipt>>;
52
+ }
53
+ /** Job payload forwarded to an optional queue adapter for deferred delivery. */
54
+ export interface NotificationsQueueJob<TRequest extends NotificationDispatchRequest = NotificationDispatchRequest> {
55
+ channel: string;
56
+ notification: TRequest;
57
+ queuedAt: string;
58
+ }
59
+ /**
60
+ * Queue seam used when applications prefer background delivery for bulk notifications.
61
+ *
62
+ * @remarks
63
+ * The foundation package intentionally depends on this abstract contract instead of
64
+ * a concrete `@fluojs/queue` type so that queue-backed delivery remains optional.
65
+ */
66
+ export interface NotificationsQueueAdapter {
67
+ /**
68
+ * Enqueues one notification delivery job.
69
+ *
70
+ * @param job Serialized notification envelope ready for background processing.
71
+ * @returns A queue-assigned identifier that can be surfaced to callers.
72
+ */
73
+ enqueue(job: NotificationsQueueJob): Promise<string>;
74
+ /**
75
+ * Enqueues multiple notification delivery jobs in one operation when supported.
76
+ *
77
+ * @param jobs Ordered notification envelopes to enqueue.
78
+ * @returns Ordered queue identifiers aligned with the input order.
79
+ */
80
+ enqueueMany?(jobs: readonly NotificationsQueueJob[]): Promise<readonly string[]>;
81
+ }
82
+ /** Lifecycle event names emitted through the optional event publication seam. */
83
+ export type NotificationLifecycleEventName = 'notification.dispatch.requested' | 'notification.dispatch.queued' | 'notification.dispatch.delivered' | 'notification.dispatch.failed';
84
+ /** Published event payload emitted around notification lifecycle transitions. */
85
+ export interface NotificationLifecycleEvent<TRequest extends NotificationDispatchRequest = NotificationDispatchRequest> {
86
+ channel: string;
87
+ deliveryId?: string;
88
+ error?: {
89
+ message: string;
90
+ name: string;
91
+ };
92
+ name: NotificationLifecycleEventName;
93
+ notification: TRequest;
94
+ occurredAt: string;
95
+ }
96
+ /** Optional event publication seam for notification lifecycle visibility. */
97
+ export interface NotificationsEventPublisher {
98
+ /**
99
+ * Publishes one notification lifecycle event.
100
+ *
101
+ * @param event Lifecycle event describing a requested, queued, delivered, or failed dispatch step.
102
+ * @returns A promise that resolves once the caller-visible publication completes.
103
+ */
104
+ publish(event: NotificationLifecycleEvent): Promise<void>;
105
+ }
106
+ /** Queue configuration for optional bulk-delivery offloading. */
107
+ export interface NotificationsQueueOptions {
108
+ adapter: NotificationsQueueAdapter;
109
+ bulkThreshold?: number;
110
+ }
111
+ /** Optional lifecycle publication configuration. */
112
+ export interface NotificationsEventsOptions {
113
+ publisher: NotificationsEventPublisher;
114
+ publishLifecycleEvents?: boolean;
115
+ }
116
+ /** Module options accepted by {@link NotificationsModule.forRoot} and `forRootAsync`. */
117
+ export interface NotificationsModuleOptions {
118
+ channels?: readonly NotificationChannel[];
119
+ events?: NotificationsEventsOptions;
120
+ queue?: NotificationsQueueOptions;
121
+ }
122
+ /** Normalized module options resolved once during module registration. */
123
+ export interface NormalizedNotificationsModuleOptions {
124
+ channels: readonly NotificationChannel[];
125
+ events?: {
126
+ publishLifecycleEvents: boolean;
127
+ publisher: NotificationsEventPublisher;
128
+ };
129
+ queue?: {
130
+ adapter: NotificationsQueueAdapter;
131
+ bulkThreshold: number;
132
+ };
133
+ }
134
+ /** Runtime dispatch controls applied to one notification call. */
135
+ export interface NotificationDispatchOptions {
136
+ publishLifecycleEvents?: boolean;
137
+ queue?: boolean;
138
+ signal?: AbortSignal;
139
+ }
140
+ /** Additional controls for one bulk dispatch invocation. */
141
+ export interface NotificationDispatchManyOptions extends NotificationDispatchOptions {
142
+ continueOnError?: boolean;
143
+ }
144
+ /** Caller-visible normalized result for one dispatched notification. */
145
+ export interface NotificationDispatchResult {
146
+ channel: string;
147
+ deliveryId: string;
148
+ metadata?: Record<string, unknown>;
149
+ queued: boolean;
150
+ status: NotificationDispatchStatus;
151
+ }
152
+ /** Failure record returned by tolerant bulk dispatch operations. */
153
+ export interface NotificationDispatchFailure<TRequest extends NotificationDispatchRequest = NotificationDispatchRequest> {
154
+ error: Error;
155
+ notification: TRequest;
156
+ }
157
+ /** Summary returned by {@link NotificationsService.dispatchMany}. */
158
+ export interface NotificationDispatchBatchResult<TRequest extends NotificationDispatchRequest = NotificationDispatchRequest> {
159
+ failed: number;
160
+ failures: readonly NotificationDispatchFailure<TRequest>[];
161
+ queued: number;
162
+ results: readonly NotificationDispatchResult[];
163
+ succeeded: number;
164
+ }
165
+ /** Facade exposed to application code and the compatibility token. */
166
+ export interface Notifications {
167
+ /**
168
+ * Dispatches one notification to a registered channel or the optional queue seam.
169
+ *
170
+ * @typeParam TRequest Shared notification request envelope subtype.
171
+ * @param notification Request envelope identifying the channel and opaque payload.
172
+ * @param options Optional abort, queue, and lifecycle-publication controls.
173
+ * @returns A normalized dispatch result describing whether the delivery was queued or completed directly.
174
+ */
175
+ dispatch<TRequest extends NotificationDispatchRequest>(notification: TRequest, options?: NotificationDispatchOptions): Promise<NotificationDispatchResult>;
176
+ /**
177
+ * Dispatches multiple notifications in input order with optional tolerant error handling.
178
+ *
179
+ * @typeParam TRequest Shared notification request envelope subtype.
180
+ * @param notifications Ordered notification envelopes to send or enqueue.
181
+ * @param options Optional queue preference and bulk error-handling controls.
182
+ * @returns A batch summary containing normalized results and any captured failures.
183
+ */
184
+ dispatchMany<TRequest extends NotificationDispatchRequest>(notifications: readonly TRequest[], options?: NotificationDispatchManyOptions): Promise<NotificationDispatchBatchResult<TRequest>>;
185
+ }
186
+ /** Async registration options for notifications modules that derive config through DI. */
187
+ export type NotificationsAsyncModuleOptions = AsyncModuleOptions<NotificationsModuleOptions>;
188
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
package/dist/types.js ADDED
@@ -0,0 +1 @@
1
+ export {};