🚀 Socket Launch Week Day 5:Introducing Repository Access Permissions and Custom Roles.Learn more
Sign In

@fluojs/notifications

Package Overview
Dependencies
Maintainers
1
Versions
8
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@fluojs/notifications - npm Package Compare versions

Comparing version
1.0.1
to
1.0.2
+2
-1
dist/service.d.ts

@@ -60,7 +60,8 @@ import type { NormalizedNotificationsModuleOptions, NotificationChannel, NotificationDispatchBatchResult, NotificationDispatchManyOptions, NotificationDispatchOptions, NotificationDispatchRequest, NotificationDispatchResult, Notifications } from './types.js';

private publishLifecycleEvent;
private publishLifecycleEventSafely;
private publishLifecycleEventBestEffort;
private publishFailureLifecycleEvent;
private publishFailureLifecycleEvents;
private publishRequestedLifecycleEvents;
private dispatchManyThroughSequentialQueueFallback;
}
//# sourceMappingURL=service.d.ts.map

@@ -1,1 +0,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"}
{"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;IAmEtC;;;;;;;;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;IAiGrD;;;;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,+BAA+B;YAgB/B,4BAA4B;YAmB5B,6BAA6B;YA0B7B,+BAA+B;YAmB/B,0CAA0C;CA4DzD"}

@@ -53,3 +53,3 @@ let _initClass;

async dispatch(notification, options = {}) {
await this.publishLifecycleEventSafely('notification.dispatch.requested', notification, options);
const requestedPublicationError = await this.publishLifecycleEventBestEffort('notification.dispatch.requested', notification, options);
if (this.shouldQueueSingleDispatch(options)) {

@@ -59,3 +59,3 @@ try {

} catch (error) {
await this.publishFailureLifecycleEvent(notification, options, error);
await this.publishFailureLifecycleEvent(notification, options, error, requestedPublicationError);
throw error;

@@ -72,6 +72,6 @@ }

};
await this.publishLifecycleEventSafely('notification.dispatch.queued', notification, options, result.deliveryId);
await this.publishLifecycleEventBestEffort('notification.dispatch.queued', notification, options, result.deliveryId);
return result;
} catch (error) {
await this.publishFailureLifecycleEvent(notification, options, error);
await this.publishFailureLifecycleEvent(notification, options, error, requestedPublicationError);
throw error;

@@ -84,3 +84,3 @@ }

} catch (error) {
await this.publishFailureLifecycleEvent(notification, options, error);
await this.publishFailureLifecycleEvent(notification, options, error, requestedPublicationError);
throw error;

@@ -99,6 +99,6 @@ }

};
await this.publishLifecycleEventSafely(result.queued ? 'notification.dispatch.queued' : 'notification.dispatch.delivered', notification, options, result.deliveryId);
await this.publishLifecycleEventBestEffort(result.queued ? 'notification.dispatch.queued' : 'notification.dispatch.delivered', notification, options, result.deliveryId);
return result;
} catch (error) {
await this.publishFailureLifecycleEvent(notification, options, error);
await this.publishFailureLifecycleEvent(notification, options, error, requestedPublicationError);
throw error;

@@ -128,5 +128,3 @@ }

if (this.shouldQueue(notifications.length, options)) {
for (const notification of notifications) {
await this.publishLifecycleEventSafely('notification.dispatch.requested', notification, options);
}
const requestedPublicationErrors = await this.publishRequestedLifecycleEvents(notifications, options);
let queue;

@@ -136,3 +134,3 @@ try {

} catch (error) {
await this.publishFailureLifecycleEvents(notifications, options, error);
await this.publishFailureLifecycleEvents(notifications, options, error, requestedPublicationErrors);
throw error;

@@ -145,3 +143,3 @@ }

} catch (error) {
await this.publishFailureLifecycleEvents(notifications, options, error);
await this.publishFailureLifecycleEvents(notifications, options, error, requestedPublicationErrors);
throw error;

@@ -151,9 +149,9 @@ }

if (!queue.enqueueMany) {
return this.dispatchManyThroughSequentialQueueFallback(notifications, jobs, options);
return this.dispatchManyThroughSequentialQueueFallback(notifications, jobs, options, requestedPublicationErrors);
}
let ids;
try {
ids = await queue.enqueueMany(jobs);
ids = validateQueueBatchDeliveryIds(await queue.enqueueMany(jobs), jobs.length);
} catch (error) {
await this.publishFailureLifecycleEvents(notifications, options, error);
await this.publishFailureLifecycleEvents(notifications, options, error, requestedPublicationErrors);
throw error;

@@ -169,3 +167,3 @@ }

const notification = notifications[index];
await this.publishLifecycleEventSafely('notification.dispatch.queued', notification, options, results[index]?.deliveryId);
await this.publishLifecycleEventBestEffort('notification.dispatch.queued', notification, options, results[index]?.deliveryId);
}

@@ -289,24 +287,41 @@ return {

}
async publishLifecycleEventSafely(name, notification, options, deliveryId, error) {
async publishLifecycleEventBestEffort(name, notification, options, deliveryId, error) {
try {
await this.publishLifecycleEvent(name, notification, options, deliveryId, error);
} catch {
return;
} catch (publicationError) {
return publicationError;
}
return undefined;
}
async publishFailureLifecycleEvent(notification, options, error) {
async publishFailureLifecycleEvent(notification, options, error, ...precedingPublicationErrors) {
const priorPublicationErrors = precedingPublicationErrors.filter(entry => entry !== undefined);
try {
await this.publishLifecycleEvent('notification.dispatch.failed', notification, options, undefined, error);
} catch (publicationError) {
throw createLifecyclePublicationFailureError(error, publicationError);
throw createLifecyclePublicationFailureError(error, ...priorPublicationErrors, publicationError);
}
if (priorPublicationErrors.length > 0) {
throw createLifecyclePublicationFailureError(error, ...priorPublicationErrors);
}
}
async publishFailureLifecycleEvents(notifications, options, error) {
async publishFailureLifecycleEvents(notifications, options, error, precedingPublicationErrors = []) {
const priorPublicationErrors = precedingPublicationErrors.filter(entry => entry !== undefined);
const failurePublicationResults = await Promise.allSettled(notifications.map(notification => this.publishLifecycleEvent('notification.dispatch.failed', notification, options, undefined, error)));
const publicationFailures = failurePublicationResults.filter(result => result.status === 'rejected').map(result => result.reason);
if (publicationFailures.length > 0) {
throw createLifecyclePublicationFailureError(error, ...publicationFailures);
throw createLifecyclePublicationFailureError(error, ...priorPublicationErrors, ...publicationFailures);
}
if (priorPublicationErrors.length > 0) {
throw createLifecyclePublicationFailureError(error, ...priorPublicationErrors);
}
}
async dispatchManyThroughSequentialQueueFallback(notifications, jobs, options) {
async publishRequestedLifecycleEvents(notifications, options) {
const publicationErrors = [];
for (const notification of notifications) {
const publicationError = await this.publishLifecycleEventBestEffort('notification.dispatch.requested', notification, options);
publicationErrors.push(publicationError);
}
return publicationErrors;
}
async dispatchManyThroughSequentialQueueFallback(notifications, jobs, options, requestedPublicationErrors) {
const queue = this.requireQueueAdapter();

@@ -330,3 +345,3 @@ const results = [];

results.push(result);
await this.publishLifecycleEventSafely('notification.dispatch.queued', notification, options, deliveryId);
await this.publishLifecycleEventBestEffort('notification.dispatch.queued', notification, options, deliveryId);
} catch (error) {

@@ -338,7 +353,7 @@ const failure = {

if (!(options.continueOnError ?? false)) {
await this.publishFailureLifecycleEvents(notifications.slice(index), options, error);
await this.publishFailureLifecycleEvents(notifications.slice(index), options, error, requestedPublicationErrors.slice(index));
throw error;
}
try {
await this.publishFailureLifecycleEvent(notification, options, error);
await this.publishFailureLifecycleEvent(notification, options, error, requestedPublicationErrors[index]);
} catch (publicationError) {

@@ -363,2 +378,27 @@ failure.error = publicationError instanceof Error ? publicationError : createLifecyclePublicationFailureError(error, publicationError);

export { _NotificationsService as NotificationsService };
function validateQueueBatchDeliveryIds(value, expectedCount) {
if (!Array.isArray(value)) {
throw createQueueBatchResultIntegrityError(`expected ${expectedCount} queue ids but received a non-array result`);
}
if (value.length !== expectedCount) {
throw createQueueBatchResultIntegrityError(`expected ${expectedCount} queue ids but received ${value.length}`);
}
const ids = [];
for (let index = 0; index < value.length; index += 1) {
if (!Object.hasOwn(value, index)) {
throw createQueueBatchResultIntegrityError(`queue id at index ${index} must be present`);
}
const entry = value[index];
if (typeof entry !== 'string' || entry.length === 0) {
throw createQueueBatchResultIntegrityError(`queue id at index ${index} must be a non-empty string`);
}
ids.push(entry);
}
return ids;
}
function createQueueBatchResultIntegrityError(message) {
const error = new Error(`Notifications queue adapter returned an invalid enqueueMany() result: ${message}.`);
error.name = 'NotificationQueueResultIntegrityError';
return error;
}
function createLifecyclePublicationFailureError(dispatchError, ...publicationErrors) {

@@ -381,7 +421,29 @@ const primaryMessage = dispatchError instanceof Error ? dispatchError.message : 'Notification dispatch failed.';

}
if (value instanceof Date) {
return Number.isNaN(value.getTime()) ? 'Date:Invalid' : `Date:${JSON.stringify(value.toISOString())}`;
}
if (value instanceof URL) {
return `URL:${JSON.stringify(value.href)}`;
}
if (value instanceof URLSearchParams) {
return `URLSearchParams:${JSON.stringify(value.toString())}`;
}
if (value instanceof RegExp) {
return `RegExp:${JSON.stringify(value.source)}/${value.flags}`;
}
if (value instanceof Map) {
const entries = Array.from(value.entries()).map(([key, entry]) => `[${stableStringify(key)},${stableStringify(entry)}]`).sort();
return `Map:{${entries.join(',')}}`;
}
if (value instanceof Set) {
const entries = Array.from(value.values()).map(entry => stableStringify(entry)).sort();
return `Set:[${entries.join(',')}]`;
}
if (Array.isArray(value)) {
return `[${value.map(entry => stableStringify(entry)).join(',')}]`;
}
const prototype = Object.getPrototypeOf(value);
const objectTag = prototype && prototype !== Object.prototype ? `${prototype.constructor?.name ?? 'Object'}:` : '';
const entries = Object.entries(value).filter(([, entry]) => entry !== undefined).sort(([left], [right]) => left.localeCompare(right));
return `{${entries.map(([key, entry]) => `${JSON.stringify(key)}:${stableStringify(entry)}`).join(',')}}`;
return `${objectTag}{${entries.map(([key, entry]) => `${JSON.stringify(key)}:${stableStringify(entry)}`).join(',')}}`;
}

@@ -12,3 +12,3 @@ {

],
"version": "1.0.1",
"version": "1.0.2",
"private": false,

@@ -41,4 +41,4 @@ "license": "MIT",

"@fluojs/core": "^1.0.3",
"@fluojs/di": "^1.0.3",
"@fluojs/runtime": "^1.1.1"
"@fluojs/di": "^1.1.0",
"@fluojs/runtime": "^1.1.8"
},

@@ -45,0 +45,0 @@ "devDependencies": {

@@ -5,3 +5,3 @@ # @fluojs/notifications

fluo를 위한 채널 중립(notification channel-agnostic) 알림 오케스트레이션 패키지입니다. 알림 채널의 공통 계약을 고정하고, Nest-like 모듈 API를 제공하며, 선택적인 큐 기반 전달 심(seam)과 라이프사이클 이벤트 발행 심을 노출합니다.
fluo를 위한 채널 중립(notification channel-agnostic) 알림 오케스트레이션 패키지입니다. 알림 채널의 공통 계약을 고정하고, 친숙한 dynamic-module 사용감을 가진 명시적 모듈 등록 API를 제공하며, 선택적인 큐 기반 전달 심(seam)과 라이프사이클 이벤트 발행 심을 노출합니다.

@@ -38,3 +38,3 @@ ## 목차

알림 모듈 등록은 `NotificationsModule.forRoot(...)` 또는 `NotificationsModule.forRootAsync(...)`로 구성합니다.
알림 모듈 등록은 `channels`에 명시적인 `NotificationChannel` 값을 전달하는 `NotificationsModule.forRoot(...)` 또는 `NotificationsModule.forRootAsync(...)`로 구성합니다.

@@ -96,2 +96,4 @@ ```typescript

Migration boundary: channel registration은 metadata 기반이 아니라 value 기반입니다. NestJS provider discovery, `@Injectable()` metadata, `emitDecoratorMetadata`에 기대어 channel이 등록된다고 가정하지 마세요. 애플리케이션 코드에서 `NotificationChannel` object를 만들거나 `NotificationsModule.forRootAsync({ inject, useFactory, global? })`에서 반환한 뒤, `channels` option으로 전달합니다.
## 일반적인 패턴

@@ -101,3 +103,3 @@

많은 알림을 백그라운드 워커로 넘기고 싶다면 선택적인 queue seam을 사용합니다.
많은 알림을 백그라운드 워커로 넘기고 싶다면 선택적인 queue seam을 사용합니다. Queue adapter는 애플리케이션 소유 integration이며, `@fluojs/notifications`는 abstract adapter contract만 호출합니다.

@@ -131,7 +133,7 @@ ```typescript

- `enqueueMany(...)`가 없으면 대량 queue delivery는 input order대로 각 job을 개별 enqueue하는 방식으로 fallback합니다. `continueOnError: true`이면 성공한 enqueue는 `results`에 남고 실패한 enqueue는 `failures`로 반환됩니다. 그렇지 않으면 첫 enqueue failure를 다시 던지기 전에 아직 terminal 상태가 없는 나머지 requested fallback job에 `failed` 라이프사이클 이벤트를 발행합니다.
- foundation 패키지는 특정 큐 구현을 가정하거나 import하지 않습니다.
- foundation 패키지는 특정 큐 구현을 가정하거나 import하지 않고, queue client/worker를 만들거나 애플리케이션 소유 queue resource를 close/drain하지 않습니다.
### 이벤트 발행자를 통한 라이프사이클 발행
foundation 패키지를 `@fluojs/event-bus` 구현에 직접 결합하지 않고도 caller-visible 라이프사이클 이벤트를 발행할 수 있습니다.
foundation 패키지를 `@fluojs/event-bus` 구현에 직접 결합하지 않고도 caller-visible 라이프사이클 이벤트를 발행할 수 있습니다. Event publisher 역시 애플리케이션 소유이며, foundation 패키지는 concrete event bus를 create/import/close/drain하지 않습니다.

@@ -168,2 +170,3 @@ ```typescript

- `@fluojs/queue` 또는 `@fluojs/event-bus`의 concrete runtime 타입 의존성
- concrete queue 또는 event-bus resource를 create/import/close/drain하는 것. Queue adapter와 event publisher는 애플리케이션 소유 integration입니다.
- provider별 payload 의미를 공유 계약에 인코딩하는 것

@@ -217,3 +220,3 @@

상태 snapshot은 platform diagnostics를 위해 `operationMode`, dependency diagnostics, ownership, readiness, health 필드를 포함합니다.
Queue adapter가 구성되면 `details.dependencies`에 `notifications.queue-adapter`가 포함되고, lifecycle event가 event publisher를 통해 발행되면 `notifications.event-publisher`가 포함됩니다. 이러한 선택적 통합은 `ownership.externallyManaged: true`로 표시되지만, foundation 패키지가 concrete queue 또는 event-bus 리소스를 닫지 않으므로 `ownsResources: false`를 유지합니다.
Queue adapter가 구성되면 `details.dependencies`에 `notifications.queue-adapter`가 포함되고, lifecycle event가 event publisher를 통해 발행되면 `notifications.event-publisher`가 포함됩니다. 이러한 선택적 통합은 `ownership.externallyManaged: true`로 표시되지만, foundation 패키지가 concrete queue 또는 event-bus 리소스를 create/close/drain하지 않으므로 `ownsResources: false`를 유지합니다.

@@ -220,0 +223,0 @@ ## 관련 패키지

@@ -5,3 +5,3 @@ # @fluojs/notifications

Channel-agnostic notification orchestration for fluo. It freezes the shared contract for notification channels, provides a Nest-like module API, and exposes optional queue-backed delivery and lifecycle event publication seams.
Channel-agnostic notification orchestration for fluo. It freezes the shared contract for notification channels, provides explicit module registration with familiar dynamic-module ergonomics, and exposes optional queue-backed delivery and lifecycle event publication seams.

@@ -38,3 +38,3 @@ ## Table of Contents

Register notifications with `NotificationsModule.forRoot(...)` or `NotificationsModule.forRootAsync(...)`.
Register notifications with `NotificationsModule.forRoot(...)` or `NotificationsModule.forRootAsync(...)` by passing explicit `NotificationChannel` values in `channels`.

@@ -96,2 +96,4 @@ ```typescript

Migration boundary: channel registration is value-based, not metadata-based. Do not rely on NestJS provider discovery, `@Injectable()` metadata, or `emitDecoratorMetadata` to register channels. Build `NotificationChannel` objects in application code or return them from `NotificationsModule.forRootAsync({ inject, useFactory, global? })`, then pass them through the `channels` option.
## Common Patterns

@@ -101,3 +103,3 @@

Use the optional queue seam when many notifications should be deferred to background workers.
Use the optional queue seam when many notifications should be deferred to background workers. The queue adapter is an application-owned integration; `@fluojs/notifications` only calls the abstract adapter contract.

@@ -131,7 +133,7 @@ ```typescript

- 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.
- The foundation package does not assume or import a concrete queue implementation.
- The foundation package does not assume or import a concrete queue implementation, create queue clients/workers, or close/drain application-owned queue resources.
### Lifecycle publication through an event publisher
Publish caller-visible lifecycle events without coupling the foundation package to `@fluojs/event-bus` directly.
Publish caller-visible lifecycle events without coupling the foundation package to `@fluojs/event-bus` directly. The event publisher is also application-owned; the foundation package does not create, import, close, or drain a concrete event bus.

@@ -168,2 +170,3 @@ ```typescript

- depend on `@fluojs/queue` or `@fluojs/event-bus` concrete runtime types
- create, import, close, or drain concrete queue or event-bus resources; queue adapters and event publishers are application-owned integrations
- encode provider-specific payload semantics into the shared contract

@@ -217,3 +220,3 @@

Status snapshots include `operationMode`, dependency diagnostics, ownership, readiness, and health fields for platform diagnostics.
When a queue adapter is configured, `details.dependencies` includes `notifications.queue-adapter`; when lifecycle events are published through an event publisher, it includes `notifications.event-publisher`. Those optional integrations mark `ownership.externallyManaged: true` while the foundation package still reports `ownsResources: false` because it does not close concrete queue or event-bus resources.
When a queue adapter is configured, `details.dependencies` includes `notifications.queue-adapter`; when lifecycle events are published through an event publisher, it includes `notifications.event-publisher`. Those optional integrations mark `ownership.externallyManaged: true` while the foundation package still reports `ownsResources: false` because it does not create, close, or drain concrete queue or event-bus resources.

@@ -220,0 +223,0 @@ ## Related Packages