@fluojs/notifications
Advanced tools
@@ -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"} |
+90
-28
@@ -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(',')}}`; | ||
| } |
+3
-3
@@ -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": { |
+9
-6
@@ -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 @@ ## 관련 패키지 |
+9
-6
@@ -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 |
80279
7.59%1018
6.6%227
1.34%Updated
Updated