@fluojs/notifications 1.0.0-beta.2 → 1.0.0-beta.4

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.ko.md CHANGED
@@ -89,7 +89,7 @@ export class WelcomeService {
89
89
  }
90
90
  ```
91
91
 
92
- `NotificationsModule.forRoot(...)`는 `NotificationsService`, `NOTIFICATIONS`, `NOTIFICATION_CHANNELS`를 global provider로 export합니다. 애플리케이션 서비스는 fluo의 class-level `@Inject(...)` decorator로 의존성을 선언해야 standard-decorator DI container가 legacy parameter decorator 없이 서비스를 resolve할 수 있습니다.
92
+ `NotificationsModule.forRoot(...)`와 `NotificationsModule.forRootAsync(...)`는 `NotificationsService`, `NOTIFICATIONS`, `NOTIFICATION_CHANNELS`를 global provider로 export합니다. 애플리케이션 서비스는 fluo의 class-level `@Inject(...)` decorator로 의존성을 선언해야 standard-decorator DI container가 parameter decorator 없이 서비스를 resolve할 수 있습니다.
93
93
 
94
94
  ## 일반적인 패턴
95
95
 
@@ -118,8 +118,12 @@ Behavioral contract 메모:
118
118
 
119
119
  - 알림 개수가 `bulkThreshold` 이상이면 대량 큐 위임이 시작됩니다.
120
120
  - `dispatch()`는 queue adapter가 구성되어 있어도 기본적으로 직접 전달을 유지합니다. 단건 알림을 큐로 보내려면 `dispatch(..., { queue: true })`를 사용합니다.
121
+ - queue adapter가 있어도 직접 전달을 강제하려면 `dispatch(..., { queue: false })`를 사용합니다.
121
122
  - 큐 기반 전달은 단건 dispatch에서는 opt-in이고, `dispatchMany(...)`에서는 threshold 기반으로 동작합니다.
123
+ - Queue job에는 `notification.id`가 있으면 그 값을, 없으면 notification envelope에서 파생한 deterministic `id` idempotency key가 포함됩니다. Queue adapter는 deduplication을 지원하는 backing queue에 이 값을 전달해야 합니다.
124
+ - `dispatchMany(..., { continueOnError: true })`는 direct delivery에서 첫 실패를 던지는 대신 실패들을 수집합니다.
122
125
  - queue enqueue가 실패하면 서비스는 enqueue 에러를 다시 던지기 전에 결정적인 `notification.dispatch.failed` 라이프사이클 이벤트를 발행합니다.
126
+ - `enqueueMany(...)`가 없으면 대량 queue delivery는 각 job을 개별적으로 enqueue하는 방식으로 fallback합니다.
123
127
  - foundation 패키지는 특정 큐 구현을 가정하거나 import하지 않습니다.
124
128
 
125
129
  ### 이벤트 발행자를 통한 라이프사이클 발행
@@ -147,6 +151,8 @@ NotificationsModule.forRoot({
147
151
  - `notification.dispatch.delivered`
148
152
  - `notification.dispatch.failed`
149
153
 
154
+ `events.publisher`가 구성되어 있으면 `publishLifecycleEvents: false`를 설정하지 않는 한 lifecycle event publication은 기본으로 켜집니다. 채널 delivery가 `externalId`를 생략하면 시간이나 난수에 의존하지 않고 notification envelope에서 파생한 deterministic fallback delivery id가 부여되어 dispatch result가 호출자에게 안정적으로 유지됩니다. 채널 해석 실패는 `NotificationChannelNotFoundError`를 던지기 전에 `requested` 이후 `failed` 이벤트를 발행하며, 이는 영구적인 구성 오류로 취급해야 합니다. Queue enqueue와 provider delivery 실패도 `failed` 이벤트를 발행하지만, retry 여부는 underlying adapter/provider error를 기준으로 분류해야 합니다. 성공 경로 lifecycle event의 publication failure는 이미 전달된 알림을 애플리케이션 실패로 바꾸지 않도록 best-effort로 유지됩니다. `notification.dispatch.failed` publication failure는 원래 dispatch error와 publisher error를 모두 포함하는 `AggregateError`로 호출자에게 드러나므로 failed-event 보장이 조용히 약해지지 않습니다.
155
+
150
156
  ### 의도적인 제한 사항
151
157
 
152
158
  foundation 패키지는 의도적으로 다음을 **포함하지 않습니다**:
@@ -164,24 +170,45 @@ foundation 패키지는 의도적으로 다음을 **포함하지 않습니다**:
164
170
 
165
171
  - `NotificationsModule.forRoot(options)` / `NotificationsModule.forRootAsync(options)`
166
172
  - `NotificationsService`
173
+ - `NotificationsService.createPlatformStatusSnapshot()`
174
+ - `Notifications`
167
175
  - `NOTIFICATIONS`
168
176
  - `NOTIFICATION_CHANNELS`
169
177
 
170
178
  ### 계약(Contracts)
171
179
 
172
180
  - `NotificationDispatchRequest`
181
+ - `NotificationDispatchOptions`
182
+ - `NotificationDispatchManyOptions`
183
+ - `NotificationDispatchResult`
184
+ - `NotificationDispatchBatchResult`
185
+ - `NotificationDispatchFailure`
186
+ - `NotificationDispatchStatus`
173
187
  - `NotificationChannel`
188
+ - `NotificationChannelContext`
189
+ - `NotificationChannelDelivery`
190
+ - `NotificationPayload`
174
191
  - `NotificationsQueueAdapter`
192
+ - `NotificationsQueueJob`
193
+ - `NotificationsQueueOptions`
194
+ - `NotificationsModuleOptions`
195
+ - `NotificationsAsyncModuleOptions`
196
+ - `NotificationsEventsOptions`
175
197
  - `NotificationsEventPublisher`
176
198
  - `NotificationLifecycleEvent`
199
+ - `NotificationLifecycleEventName`
177
200
 
178
201
  ### 상태 및 에러
179
202
 
180
203
  - `createNotificationsPlatformStatusSnapshot(...)`
204
+ - `NotificationsPlatformStatusSnapshot`
205
+ - `NotificationsStatusAdapterInput`
181
206
  - `NotificationsConfigurationError`
182
207
  - `NotificationChannelNotFoundError`
183
208
  - `NotificationQueueNotConfiguredError`
184
209
 
210
+ 상태 snapshot은 platform diagnostics를 위해 `operationMode`, dependency diagnostics, ownership, readiness, health 필드를 포함합니다.
211
+
185
212
  ## 관련 패키지
186
213
 
187
214
  - `@fluojs/queue`: 대량 알림 전달을 백그라운드에서 처리하려는 경우 권장됩니다.
package/README.md CHANGED
@@ -89,7 +89,7 @@ export class WelcomeService {
89
89
  }
90
90
  ```
91
91
 
92
- `NotificationsModule.forRoot(...)` exports `NotificationsService`, `NOTIFICATIONS`, and `NOTIFICATION_CHANNELS` as global providers. Application services should declare dependencies with fluo's class-level `@Inject(...)` decorator so the standard-decorator DI container can resolve the service without legacy parameter decorators.
92
+ `NotificationsModule.forRoot(...)` and `NotificationsModule.forRootAsync(...)` export `NotificationsService`, `NOTIFICATIONS`, and `NOTIFICATION_CHANNELS` as global providers. Application services should declare dependencies with fluo's class-level `@Inject(...)` decorator so the standard-decorator DI container can resolve the service without parameter decorators.
93
93
 
94
94
  ## Common Patterns
95
95
 
@@ -118,8 +118,12 @@ Behavioral contract notes:
118
118
 
119
119
  - Bulk queue delegation starts when the notification count reaches `bulkThreshold`.
120
120
  - `dispatch()` stays direct by default even when a queue adapter is configured. Use `dispatch(..., { queue: true })` to opt one single notification into queue-backed delivery.
121
+ - Use `dispatch(..., { queue: false })` to force direct delivery even when a queue adapter exists.
121
122
  - Queue-backed delivery is opt-in for single dispatch and threshold-driven for `dispatchMany(...)`.
123
+ - Queue jobs include a deterministic `id` idempotency key derived from `notification.id` when present, otherwise from the notification envelope. Queue adapters should pass this value to backing queues that support deduplication.
124
+ - `dispatchMany(..., { continueOnError: true })` collects failures instead of throwing on the first failed direct delivery.
122
125
  - When queue enqueue fails, the service emits deterministic `notification.dispatch.failed` lifecycle events before rethrowing the enqueue error to the caller.
126
+ - If `enqueueMany(...)` is unavailable, bulk queue delivery falls back to enqueueing each job individually.
123
127
  - The foundation package does not assume or import a concrete queue implementation.
124
128
 
125
129
  ### Lifecycle publication through an event publisher
@@ -147,6 +151,8 @@ Published event names:
147
151
  - `notification.dispatch.delivered`
148
152
  - `notification.dispatch.failed`
149
153
 
154
+ If `events.publisher` is configured, lifecycle event publication defaults to on unless `publishLifecycleEvents: false` is set. Channel deliveries that omit `externalId` receive a deterministic fallback delivery id derived from the notification envelope so dispatch results remain stable for callers without relying on time or random data. Channel resolution failures publish `requested` and then `failed` events before throwing `NotificationChannelNotFoundError`; treat those failures as permanent configuration errors. Queue enqueue and provider delivery failures also publish `failed` events, but callers should classify their retry behavior from the underlying adapter/provider error. Publication failures for success-path lifecycle events remain best-effort so a delivered notification is not converted into an application failure. Publication failures for `notification.dispatch.failed` are caller-visible as `AggregateError` values that include both the original dispatch error and the publisher error so failed-event guarantees are not silently weakened.
155
+
150
156
  ### Intentional limitations
151
157
 
152
158
  The foundation package intentionally does **not**:
@@ -164,24 +170,45 @@ These limitations are part of the package contract so leaf packages can evolve i
164
170
 
165
171
  - `NotificationsModule.forRoot(options)` / `NotificationsModule.forRootAsync(options)`
166
172
  - `NotificationsService`
173
+ - `NotificationsService.createPlatformStatusSnapshot()`
174
+ - `Notifications`
167
175
  - `NOTIFICATIONS`
168
176
  - `NOTIFICATION_CHANNELS`
169
177
 
170
178
  ### Contracts
171
179
 
172
180
  - `NotificationDispatchRequest`
181
+ - `NotificationDispatchOptions`
182
+ - `NotificationDispatchManyOptions`
183
+ - `NotificationDispatchResult`
184
+ - `NotificationDispatchBatchResult`
185
+ - `NotificationDispatchFailure`
186
+ - `NotificationDispatchStatus`
173
187
  - `NotificationChannel`
188
+ - `NotificationChannelContext`
189
+ - `NotificationChannelDelivery`
190
+ - `NotificationPayload`
174
191
  - `NotificationsQueueAdapter`
192
+ - `NotificationsQueueJob`
193
+ - `NotificationsQueueOptions`
194
+ - `NotificationsModuleOptions`
195
+ - `NotificationsAsyncModuleOptions`
196
+ - `NotificationsEventsOptions`
175
197
  - `NotificationsEventPublisher`
176
198
  - `NotificationLifecycleEvent`
199
+ - `NotificationLifecycleEventName`
177
200
 
178
201
  ### Status and errors
179
202
 
180
203
  - `createNotificationsPlatformStatusSnapshot(...)`
204
+ - `NotificationsPlatformStatusSnapshot`
205
+ - `NotificationsStatusAdapterInput`
181
206
  - `NotificationsConfigurationError`
182
207
  - `NotificationChannelNotFoundError`
183
208
  - `NotificationQueueNotConfiguredError`
184
209
 
210
+ Status snapshots include `operationMode`, dependency diagnostics, ownership, readiness, and health fields for platform diagnostics.
211
+
185
212
  ## Related Packages
186
213
 
187
214
  - `@fluojs/queue`: Recommended when bulk notification delivery should run in the background.
package/dist/module.d.ts CHANGED
@@ -1,6 +1,5 @@
1
- import type { AsyncModuleOptions } from '@fluojs/core';
2
1
  import { type ModuleType } from '@fluojs/runtime';
3
- import type { NotificationsModuleOptions } from './types.js';
2
+ import type { NotificationsAsyncModuleOptions, NotificationsModuleOptions } from './types.js';
4
3
  /** Runtime module entrypoint for notification orchestration. */
5
4
  export declare class NotificationsModule {
6
5
  /**
@@ -33,6 +32,6 @@ export declare class NotificationsModule {
33
32
  * });
34
33
  * ```
35
34
  */
36
- static forRootAsync(options: AsyncModuleOptions<NotificationsModuleOptions>): ModuleType;
35
+ static forRootAsync(options: NotificationsAsyncModuleOptions): ModuleType;
37
36
  }
38
37
  //# sourceMappingURL=module.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"module.d.ts","sourceRoot":"","sources":["../src/module.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAgB,MAAM,cAAc,CAAC;AAErE,OAAO,EAAgB,KAAK,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAKhE,OAAO,KAAK,EAMV,0BAA0B,EAC3B,MAAM,YAAY,CAAC;AA2GpB,gEAAgE;AAChE,qBAAa,mBAAmB;IAC9B;;;;;;;;;;;;OAYG;IACH,MAAM,CAAC,OAAO,CAAC,OAAO,GAAE,0BAA+B,GAAG,UAAU;IAIpE;;;;;;;;;;;;;;;OAeG;IACH,MAAM,CAAC,YAAY,CAAC,OAAO,EAAE,kBAAkB,CAAC,0BAA0B,CAAC,GAAG,UAAU;CAGzF"}
1
+ {"version":3,"file":"module.d.ts","sourceRoot":"","sources":["../src/module.ts"],"names":[],"mappings":"AAEA,OAAO,EAAgB,KAAK,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAKhE,OAAO,KAAK,EAMV,+BAA+B,EAC/B,0BAA0B,EAC3B,MAAM,YAAY,CAAC;AA2GpB,gEAAgE;AAChE,qBAAa,mBAAmB;IAC9B;;;;;;;;;;;;OAYG;IACH,MAAM,CAAC,OAAO,CAAC,OAAO,GAAE,0BAA+B,GAAG,UAAU;IAIpE;;;;;;;;;;;;;;;OAeG;IACH,MAAM,CAAC,YAAY,CAAC,OAAO,EAAE,+BAA+B,GAAG,UAAU;CAG1E"}
package/dist/module.js CHANGED
@@ -48,7 +48,7 @@ function buildNotificationsModule(options) {
48
48
  class NotificationsRootModuleDefinition {}
49
49
  return defineModule(NotificationsRootModuleDefinition, {
50
50
  exports: [NotificationsService, NOTIFICATIONS, NOTIFICATION_CHANNELS],
51
- global: true,
51
+ global: options.global ?? true,
52
52
  providers: buildNotificationsProviders(options)
53
53
  });
54
54
  }
@@ -64,7 +64,7 @@ function buildNotificationsModuleAsync(options) {
64
64
  };
65
65
  return defineModule(NotificationsAsyncModuleDefinition, {
66
66
  exports: [NotificationsService, NOTIFICATIONS, NOTIFICATION_CHANNELS],
67
- global: true,
67
+ global: options.global ?? true,
68
68
  providers: createNotificationsRuntimeProviders({
69
69
  inject: options.inject,
70
70
  provide: NOTIFICATIONS_OPTIONS,
package/dist/service.d.ts CHANGED
@@ -10,7 +10,6 @@ import type { NormalizedNotificationsModuleOptions, NotificationChannel, Notific
10
10
  export declare class NotificationsService implements Notifications {
11
11
  private readonly options;
12
12
  private readonly channelsByName;
13
- private fallbackDeliveryIdSequence;
14
13
  constructor(options: NormalizedNotificationsModuleOptions, channels: readonly NotificationChannel[]);
15
14
  /**
16
15
  * Dispatches one notification through a registered channel or the configured queue seam.
@@ -50,6 +49,7 @@ export declare class NotificationsService implements Notifications {
50
49
  */
51
50
  createPlatformStatusSnapshot(): import("./status.js").NotificationsPlatformStatusSnapshot;
52
51
  private createQueueJob;
52
+ private createQueueJobId;
53
53
  private requireChannel;
54
54
  private normalizeDeliveryId;
55
55
  private requireQueueAdapter;
@@ -58,5 +58,6 @@ export declare class NotificationsService implements Notifications {
58
58
  private shouldQueue;
59
59
  private publishLifecycleEvent;
60
60
  private publishLifecycleEventSafely;
61
+ private publishFailureLifecycleEvent;
61
62
  }
62
63
  //# sourceMappingURL=service.d.ts.map
@@ -1 +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"}
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;IAItD,OAAO,CAAC,QAAQ,CAAC,OAAO;IAH1B,OAAO,CAAC,QAAQ,CAAC,cAAc,CAA0C;gBAGtD,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;IA+DtC;;;;;;;;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;IAuGrD;;;;OAIG;IACH,4BAA4B;IAS5B,OAAO,CAAC,cAAc;IAStB,OAAO,CAAC,gBAAgB;IAQxB,OAAO,CAAC,cAAc;IAUtB,OAAO,CAAC,mBAAmB;IAY3B,OAAO,CAAC,mBAAmB;IAQ3B,OAAO,CAAC,4BAA4B;IAQpC,OAAO,CAAC,yBAAyB;IAIjC,OAAO,CAAC,WAAW;YAYL,qBAAqB;YA4BrB,2BAA2B;YAc3B,4BAA4B;CAW3C"}
package/dist/service.js CHANGED
@@ -22,7 +22,6 @@ class NotificationsService {
22
22
  [_NotificationsService, _initClass] = _applyDecs(this, [Inject(NOTIFICATIONS_OPTIONS, NOTIFICATION_CHANNELS)], []).c;
23
23
  }
24
24
  channelsByName = new Map();
25
- fallbackDeliveryIdSequence = 0;
26
25
  constructor(options, channels) {
27
26
  this.options = options;
28
27
  for (const channel of channels) {
@@ -53,7 +52,12 @@ class NotificationsService {
53
52
  async dispatch(notification, options = {}) {
54
53
  await this.publishLifecycleEventSafely('notification.dispatch.requested', notification, options);
55
54
  if (this.shouldQueueSingleDispatch(options)) {
56
- this.requireChannel(notification.channel);
55
+ try {
56
+ this.requireChannel(notification.channel);
57
+ } catch (error) {
58
+ await this.publishFailureLifecycleEvent(notification, options, error);
59
+ throw error;
60
+ }
57
61
  const job = this.createQueueJob(notification);
58
62
  try {
59
63
  const deliveryId = await this.requireQueueAdapter().enqueue(job);
@@ -66,11 +70,17 @@ class NotificationsService {
66
70
  await this.publishLifecycleEventSafely('notification.dispatch.queued', notification, options, result.deliveryId);
67
71
  return result;
68
72
  } catch (error) {
69
- await this.publishLifecycleEventSafely('notification.dispatch.failed', notification, options, undefined, error);
73
+ await this.publishFailureLifecycleEvent(notification, options, error);
70
74
  throw error;
71
75
  }
72
76
  }
73
- const channel = this.requireChannel(notification.channel);
77
+ let channel;
78
+ try {
79
+ channel = this.requireChannel(notification.channel);
80
+ } catch (error) {
81
+ await this.publishFailureLifecycleEvent(notification, options, error);
82
+ throw error;
83
+ }
74
84
  try {
75
85
  const delivery = await channel.send(notification, {
76
86
  signal: options.signal
@@ -85,7 +95,7 @@ class NotificationsService {
85
95
  await this.publishLifecycleEventSafely(result.queued ? 'notification.dispatch.queued' : 'notification.dispatch.delivered', notification, options, result.deliveryId);
86
96
  return result;
87
97
  } catch (error) {
88
- await this.publishLifecycleEventSafely('notification.dispatch.failed', notification, options, undefined, error);
98
+ await this.publishFailureLifecycleEvent(notification, options, error);
89
99
  throw error;
90
100
  }
91
101
  }
@@ -112,17 +122,26 @@ class NotificationsService {
112
122
  if (this.shouldQueue(notifications.length, options)) {
113
123
  const queue = this.requireQueueAdapter();
114
124
  for (const notification of notifications) {
115
- this.requireChannel(notification.channel);
125
+ await this.publishLifecycleEventSafely('notification.dispatch.requested', notification, options);
116
126
  }
117
- const jobs = notifications.map(notification => this.createQueueJob(notification));
118
127
  for (const notification of notifications) {
119
- await this.publishLifecycleEventSafely('notification.dispatch.requested', notification, options);
128
+ try {
129
+ this.requireChannel(notification.channel);
130
+ } catch (error) {
131
+ await this.publishFailureLifecycleEvent(notification, options, error);
132
+ throw error;
133
+ }
120
134
  }
135
+ const jobs = notifications.map(notification => this.createQueueJob(notification));
121
136
  let ids;
122
137
  try {
123
138
  ids = queue.enqueueMany ? await queue.enqueueMany(jobs) : await Promise.all(jobs.map(job => queue.enqueue(job)));
124
139
  } catch (error) {
125
- await Promise.all(notifications.map(notification => this.publishLifecycleEventSafely('notification.dispatch.failed', notification, options, undefined, error)));
140
+ const failurePublicationResults = await Promise.allSettled(notifications.map(notification => this.publishLifecycleEvent('notification.dispatch.failed', notification, options, undefined, error)));
141
+ const publicationFailures = failurePublicationResults.filter(result => result.status === 'rejected').map(result => result.reason);
142
+ if (publicationFailures.length > 0) {
143
+ throw createLifecyclePublicationFailureError(error, ...publicationFailures);
144
+ }
126
145
  throw error;
127
146
  }
128
147
  const results = notifications.map((notification, index) => ({
@@ -184,10 +203,17 @@ class NotificationsService {
184
203
  createQueueJob(notification) {
185
204
  return {
186
205
  channel: notification.channel,
206
+ id: this.createQueueJobId(notification),
187
207
  notification,
188
208
  queuedAt: new Date().toISOString()
189
209
  };
190
210
  }
211
+ createQueueJobId(notification) {
212
+ if (notification.id && notification.id.length > 0) {
213
+ return notification.id;
214
+ }
215
+ return `notification:${notification.channel}:${stableNotificationHash(notification)}`;
216
+ }
191
217
  requireChannel(channelName) {
192
218
  const channel = this.channelsByName.get(channelName);
193
219
  if (!channel) {
@@ -202,8 +228,7 @@ class NotificationsService {
202
228
  if (fallback.id) {
203
229
  return fallback.id;
204
230
  }
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)}`;
231
+ return `fallback:${fallback.channel}:${stableNotificationHash(fallback)}`;
207
232
  }
208
233
  requireQueueAdapter() {
209
234
  if (!this.options.queue) {
@@ -253,8 +278,38 @@ class NotificationsService {
253
278
  return;
254
279
  }
255
280
  }
281
+ async publishFailureLifecycleEvent(notification, options, error) {
282
+ try {
283
+ await this.publishLifecycleEvent('notification.dispatch.failed', notification, options, undefined, error);
284
+ } catch (publicationError) {
285
+ throw createLifecyclePublicationFailureError(error, publicationError);
286
+ }
287
+ }
256
288
  static {
257
289
  _initClass();
258
290
  }
259
291
  }
260
- export { _NotificationsService as NotificationsService };
292
+ export { _NotificationsService as NotificationsService };
293
+ function createLifecyclePublicationFailureError(dispatchError, ...publicationErrors) {
294
+ const primaryMessage = dispatchError instanceof Error ? dispatchError.message : 'Notification dispatch failed.';
295
+ return new AggregateError([dispatchError, ...publicationErrors], `Notification dispatch failed, and failed lifecycle event publication also failed: ${primaryMessage}`);
296
+ }
297
+ function stableNotificationHash(notification) {
298
+ let hash = 0x811c9dc5;
299
+ const input = stableStringify(notification);
300
+ for (let index = 0; index < input.length; index += 1) {
301
+ hash ^= input.charCodeAt(index);
302
+ hash = Math.imul(hash, 0x01000193) >>> 0;
303
+ }
304
+ return hash.toString(36).padStart(7, '0');
305
+ }
306
+ function stableStringify(value) {
307
+ if (value === null || typeof value !== 'object') {
308
+ return JSON.stringify(value) ?? String(value);
309
+ }
310
+ if (Array.isArray(value)) {
311
+ return `[${value.map(entry => stableStringify(entry)).join(',')}]`;
312
+ }
313
+ const entries = Object.entries(value).filter(([, entry]) => entry !== undefined).sort(([left], [right]) => left.localeCompare(right));
314
+ return `{${entries.map(([key, entry]) => `${JSON.stringify(key)}:${stableStringify(entry)}`).join(',')}}`;
315
+ }
package/dist/types.d.ts CHANGED
@@ -53,6 +53,14 @@ export interface NotificationChannel<TRequest extends NotificationDispatchReques
53
53
  /** Job payload forwarded to an optional queue adapter for deferred delivery. */
54
54
  export interface NotificationsQueueJob<TRequest extends NotificationDispatchRequest = NotificationDispatchRequest> {
55
55
  channel: string;
56
+ /**
57
+ * Deterministic idempotency key derived from the notification envelope.
58
+ *
59
+ * @remarks
60
+ * Queue adapters should use this value as their deduplication key when the
61
+ * backing queue supports idempotent enqueue semantics.
62
+ */
63
+ id: string;
56
64
  notification: TRequest;
57
65
  queuedAt: string;
58
66
  }
@@ -117,6 +125,8 @@ export interface NotificationsEventsOptions {
117
125
  export interface NotificationsModuleOptions {
118
126
  channels?: readonly NotificationChannel[];
119
127
  events?: NotificationsEventsOptions;
128
+ /** Whether notification providers should be visible globally. Defaults to `true`. */
129
+ global?: boolean;
120
130
  queue?: NotificationsQueueOptions;
121
131
  }
122
132
  /** Normalized module options resolved once during module registration. */
@@ -184,5 +194,5 @@ export interface Notifications {
184
194
  dispatchMany<TRequest extends NotificationDispatchRequest>(notifications: readonly TRequest[], options?: NotificationDispatchManyOptions): Promise<NotificationDispatchBatchResult<TRequest>>;
185
195
  }
186
196
  /** Async registration options for notifications modules that derive config through DI. */
187
- export type NotificationsAsyncModuleOptions = AsyncModuleOptions<NotificationsModuleOptions>;
197
+ export type NotificationsAsyncModuleOptions = AsyncModuleOptions<Omit<NotificationsModuleOptions, 'global'>> & Pick<NotificationsModuleOptions, 'global'>;
188
198
  //# sourceMappingURL=types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -9,7 +9,7 @@
9
9
  "event-bus",
10
10
  "channels"
11
11
  ],
12
- "version": "1.0.0-beta.2",
12
+ "version": "1.0.0-beta.4",
13
13
  "private": false,
14
14
  "license": "MIT",
15
15
  "repository": {
@@ -36,9 +36,9 @@
36
36
  "dist"
37
37
  ],
38
38
  "dependencies": {
39
- "@fluojs/core": "^1.0.0-beta.1",
40
- "@fluojs/di": "^1.0.0-beta.2",
41
- "@fluojs/runtime": "^1.0.0-beta.2"
39
+ "@fluojs/core": "^1.0.0-beta.5",
40
+ "@fluojs/di": "^1.0.0-beta.7",
41
+ "@fluojs/runtime": "^1.0.0-beta.12"
42
42
  },
43
43
  "devDependencies": {
44
44
  "vitest": "^3.2.4"