@fluojs/notifications 1.0.0-beta.4 → 1.0.0-beta.5
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 +3 -3
- package/README.md +3 -3
- package/dist/service.d.ts +2 -0
- package/dist/service.d.ts.map +1 -1
- package/dist/service.js +69 -12
- package/package.json +3 -3
package/README.ko.md
CHANGED
|
@@ -121,9 +121,9 @@ Behavioral contract 메모:
|
|
|
121
121
|
- queue adapter가 있어도 직접 전달을 강제하려면 `dispatch(..., { queue: false })`를 사용합니다.
|
|
122
122
|
- 큐 기반 전달은 단건 dispatch에서는 opt-in이고, `dispatchMany(...)`에서는 threshold 기반으로 동작합니다.
|
|
123
123
|
- Queue job에는 `notification.id`가 있으면 그 값을, 없으면 notification envelope에서 파생한 deterministic `id` idempotency key가 포함됩니다. Queue adapter는 deduplication을 지원하는 backing queue에 이 값을 전달해야 합니다.
|
|
124
|
-
- `dispatchMany(..., { continueOnError: true })`는 direct delivery에서 첫 실패를 던지는 대신 실패들을 수집합니다.
|
|
125
|
-
- queue enqueue가 실패하면 서비스는 enqueue 에러를 다시 던지기 전에 결정적인 `notification.dispatch.failed` 라이프사이클 이벤트를 발행합니다.
|
|
126
|
-
- `enqueueMany(...)`가 없으면 대량 queue delivery는 각 job을
|
|
124
|
+
- `dispatchMany(..., { continueOnError: true })`는 direct delivery 또는 순차 queue fallback enqueue에서 첫 실패를 던지는 대신 실패들을 수집합니다.
|
|
125
|
+
- queue enqueue가 실패하면 서비스는 enqueue 에러를 다시 던지기 전에 결정적인 `notification.dispatch.failed` 라이프사이클 이벤트를 발행합니다. queued bulk dispatch는 queue 미구성, channel 해석, provider/adapter failure 경로를 포함해 이미 `requested`를 발행한 모든 notification에 대해 terminal `queued` 또는 `failed` 이벤트도 발행합니다.
|
|
126
|
+
- `enqueueMany(...)`가 없으면 대량 queue delivery는 input order대로 각 job을 개별 enqueue하는 방식으로 fallback합니다. `continueOnError: true`이면 성공한 enqueue는 `results`에 남고 실패한 enqueue는 `failures`로 반환됩니다. 그렇지 않으면 첫 enqueue failure를 다시 던지기 전에 아직 terminal 상태가 없는 나머지 requested fallback job에 `failed` 라이프사이클 이벤트를 발행합니다.
|
|
127
127
|
- foundation 패키지는 특정 큐 구현을 가정하거나 import하지 않습니다.
|
|
128
128
|
|
|
129
129
|
### 이벤트 발행자를 통한 라이프사이클 발행
|
package/README.md
CHANGED
|
@@ -121,9 +121,9 @@ Behavioral contract notes:
|
|
|
121
121
|
- Use `dispatch(..., { queue: false })` to force direct delivery even when a queue adapter exists.
|
|
122
122
|
- Queue-backed delivery is opt-in for single dispatch and threshold-driven for `dispatchMany(...)`.
|
|
123
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.
|
|
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.
|
|
124
|
+
- `dispatchMany(..., { continueOnError: true })` collects failures instead of throwing on the first failed direct delivery or sequential queue fallback enqueue.
|
|
125
|
+
- When queue enqueue fails, the service emits deterministic `notification.dispatch.failed` lifecycle events before rethrowing the enqueue error to the caller. Queued bulk dispatch also publishes a terminal `queued` or `failed` event for every notification that already emitted `requested`, including queue-missing, channel-resolution, and provider/adapter failure paths.
|
|
126
|
+
- If `enqueueMany(...)` is unavailable, bulk queue delivery falls back to enqueueing each job individually in input order. With `continueOnError: true`, successful enqueues remain visible in `results` while failed enqueues are returned in `failures`; without it, the first enqueue failure is rethrown after the remaining requested fallback jobs receive `failed` lifecycle events.
|
|
127
127
|
- The foundation package does not assume or import a concrete queue implementation.
|
|
128
128
|
|
|
129
129
|
### Lifecycle publication through an event publisher
|
package/dist/service.d.ts
CHANGED
|
@@ -59,5 +59,7 @@ export declare class NotificationsService implements Notifications {
|
|
|
59
59
|
private publishLifecycleEvent;
|
|
60
60
|
private publishLifecycleEventSafely;
|
|
61
61
|
private publishFailureLifecycleEvent;
|
|
62
|
+
private publishFailureLifecycleEvents;
|
|
63
|
+
private dispatchManyThroughSequentialQueueFallback;
|
|
62
64
|
}
|
|
63
65
|
//# sourceMappingURL=service.d.ts.map
|
package/dist/service.d.ts.map
CHANGED
|
@@ -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;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;
|
|
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;IAmGrD;;;;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;YAY5B,6BAA6B;YAoB7B,0CAA0C;CA2DzD"}
|
package/dist/service.js
CHANGED
|
@@ -120,28 +120,33 @@ class NotificationsService {
|
|
|
120
120
|
};
|
|
121
121
|
}
|
|
122
122
|
if (this.shouldQueue(notifications.length, options)) {
|
|
123
|
-
const queue = this.requireQueueAdapter();
|
|
124
123
|
for (const notification of notifications) {
|
|
125
124
|
await this.publishLifecycleEventSafely('notification.dispatch.requested', notification, options);
|
|
126
125
|
}
|
|
127
|
-
|
|
128
|
-
|
|
126
|
+
let queue;
|
|
127
|
+
try {
|
|
128
|
+
queue = this.requireQueueAdapter();
|
|
129
|
+
} catch (error) {
|
|
130
|
+
await this.publishFailureLifecycleEvents(notifications, options, error);
|
|
131
|
+
throw error;
|
|
132
|
+
}
|
|
133
|
+
try {
|
|
134
|
+
for (const notification of notifications) {
|
|
129
135
|
this.requireChannel(notification.channel);
|
|
130
|
-
} catch (error) {
|
|
131
|
-
await this.publishFailureLifecycleEvent(notification, options, error);
|
|
132
|
-
throw error;
|
|
133
136
|
}
|
|
137
|
+
} catch (error) {
|
|
138
|
+
await this.publishFailureLifecycleEvents(notifications, options, error);
|
|
139
|
+
throw error;
|
|
134
140
|
}
|
|
135
141
|
const jobs = notifications.map(notification => this.createQueueJob(notification));
|
|
142
|
+
if (!queue.enqueueMany) {
|
|
143
|
+
return this.dispatchManyThroughSequentialQueueFallback(notifications, jobs, options);
|
|
144
|
+
}
|
|
136
145
|
let ids;
|
|
137
146
|
try {
|
|
138
|
-
ids =
|
|
147
|
+
ids = await queue.enqueueMany(jobs);
|
|
139
148
|
} catch (error) {
|
|
140
|
-
|
|
141
|
-
const publicationFailures = failurePublicationResults.filter(result => result.status === 'rejected').map(result => result.reason);
|
|
142
|
-
if (publicationFailures.length > 0) {
|
|
143
|
-
throw createLifecyclePublicationFailureError(error, ...publicationFailures);
|
|
144
|
-
}
|
|
149
|
+
await this.publishFailureLifecycleEvents(notifications, options, error);
|
|
145
150
|
throw error;
|
|
146
151
|
}
|
|
147
152
|
const results = notifications.map((notification, index) => ({
|
|
@@ -285,6 +290,58 @@ class NotificationsService {
|
|
|
285
290
|
throw createLifecyclePublicationFailureError(error, publicationError);
|
|
286
291
|
}
|
|
287
292
|
}
|
|
293
|
+
async publishFailureLifecycleEvents(notifications, options, error) {
|
|
294
|
+
const failurePublicationResults = await Promise.allSettled(notifications.map(notification => this.publishLifecycleEvent('notification.dispatch.failed', notification, options, undefined, error)));
|
|
295
|
+
const publicationFailures = failurePublicationResults.filter(result => result.status === 'rejected').map(result => result.reason);
|
|
296
|
+
if (publicationFailures.length > 0) {
|
|
297
|
+
throw createLifecyclePublicationFailureError(error, ...publicationFailures);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
async dispatchManyThroughSequentialQueueFallback(notifications, jobs, options) {
|
|
301
|
+
const queue = this.requireQueueAdapter();
|
|
302
|
+
const results = [];
|
|
303
|
+
const failures = [];
|
|
304
|
+
for (let index = 0; index < jobs.length; index += 1) {
|
|
305
|
+
const job = jobs[index];
|
|
306
|
+
const notification = notifications[index];
|
|
307
|
+
if (!job || !notification) {
|
|
308
|
+
continue;
|
|
309
|
+
}
|
|
310
|
+
try {
|
|
311
|
+
const deliveryId = this.normalizeDeliveryId(await queue.enqueue(job), notification);
|
|
312
|
+
const result = {
|
|
313
|
+
channel: notification.channel,
|
|
314
|
+
deliveryId,
|
|
315
|
+
queued: true,
|
|
316
|
+
status: 'queued'
|
|
317
|
+
};
|
|
318
|
+
results.push(result);
|
|
319
|
+
await this.publishLifecycleEventSafely('notification.dispatch.queued', notification, options, deliveryId);
|
|
320
|
+
} catch (error) {
|
|
321
|
+
const failure = {
|
|
322
|
+
error: error instanceof Error ? error : new Error('Notification queue enqueue failed.'),
|
|
323
|
+
notification
|
|
324
|
+
};
|
|
325
|
+
if (!(options.continueOnError ?? false)) {
|
|
326
|
+
await this.publishFailureLifecycleEvents(notifications.slice(index), options, error);
|
|
327
|
+
throw error;
|
|
328
|
+
}
|
|
329
|
+
try {
|
|
330
|
+
await this.publishFailureLifecycleEvent(notification, options, error);
|
|
331
|
+
} catch (publicationError) {
|
|
332
|
+
failure.error = publicationError instanceof Error ? publicationError : createLifecyclePublicationFailureError(error, publicationError);
|
|
333
|
+
}
|
|
334
|
+
failures.push(failure);
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
return {
|
|
338
|
+
failed: failures.length,
|
|
339
|
+
failures,
|
|
340
|
+
queued: results.length,
|
|
341
|
+
results,
|
|
342
|
+
succeeded: results.length
|
|
343
|
+
};
|
|
344
|
+
}
|
|
288
345
|
static {
|
|
289
346
|
_initClass();
|
|
290
347
|
}
|
package/package.json
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
"event-bus",
|
|
10
10
|
"channels"
|
|
11
11
|
],
|
|
12
|
-
"version": "1.0.0-beta.
|
|
12
|
+
"version": "1.0.0-beta.5",
|
|
13
13
|
"private": false,
|
|
14
14
|
"license": "MIT",
|
|
15
15
|
"repository": {
|
|
@@ -36,8 +36,8 @@
|
|
|
36
36
|
"dist"
|
|
37
37
|
],
|
|
38
38
|
"dependencies": {
|
|
39
|
-
"@fluojs/core": "^1.0.0-beta.
|
|
40
|
-
"@fluojs/di": "^1.0.0-beta.
|
|
39
|
+
"@fluojs/core": "^1.0.0-beta.6",
|
|
40
|
+
"@fluojs/di": "^1.0.0-beta.8",
|
|
41
41
|
"@fluojs/runtime": "^1.0.0-beta.12"
|
|
42
42
|
},
|
|
43
43
|
"devDependencies": {
|