콘텐츠로 이동

@gj-kit/nest-notifications

알림 파이프라인에서 위험한 결정은 프로덕션이 아니라 컴파일 단계에서 막습니다.

  • npm 0.1.2
  • unit 테스트 230개 이상
  • 직접 만든 저장소용 계약 케이스 30개
  • 런타임 의존성 0
  • 공개 entry point 4개, ESM + CJS

직접 짠 알림 outbox는 claim이 원자적이지 않아 두 워커가 같은 행을 집고, inbox 메시지가 중복으로 쓰이며, relay 도중 삭제된 계정으로 push가 나갑니다. 조용시간은 대개 Date에 고정 offset을 더하는 식이라 DST 전환마다 한 시간씩 어긋나고, 24시간으로 나누어떨어지지 않는 batch 창은 집계 버킷을 매일 옮겨 놓습니다.

  • 필수 옵션은 컴파일 에러입니다 — presenter 없는 createNotificationDispatcher, timeZone 없는 createQuietHoursPolicy는 컴파일되지 않습니다. 기본 카피도 기본 지역도 두지 않기 때문입니다.
  • 직접 만든 저장소에 돌리는 적합성 케이스 30개 — notificationStoreContractCases()가 원자적 claim, 배치 유일성, purge와 relay의 교차까지 29개 저장소 의무를 실행 가능한 케이스 30개로 돌려줍니다.
  • Nest가 스며들 수 없는 core — 가드가 src/core·src/expo·src/testing 소스와 dist/core.·dist/expo.·dist/testing.* 모듈 그래프 전체에서 @nestjs·rxjs·reflect-metadata 문자열을 한 건도 찾지 못해야 통과합니다. 반대로 dist/index.js에는 @nestjs가 있어야 통과하는 대조군이 함께 있어, 이 가드가 빈 집합을 검사하고 있는 게 아니라는 것까지 확인합니다.
  • wakeup 힌트의 반환 타입은 void입니다 — NotificationPipelineWakeup.request()의 반환 타입은 void입니다. await할 것도, 확인할 결과도, 잡을 에러도 없으니 여기에 정확성을 기댈 수 없습니다. 힌트만 배선한 호스트에서 시계를 12시간 앞으로 돌려도 배치 배달은 그대로 남아 있고, dispatchDue()를 부른 뒤에야 나간다는 것을 테스트가 고정합니다.
  • DST 해석은 계약입니다 — 봄 전진 갭에 삼켜진 조용시간 해제 시각은 갭 직후 첫 순간으로, 가을 후퇴로 두 번 존재하는 시각은 이른 쪽으로 확정됩니다. batchWindowMs는 24시간이 나누어떨어지는 값이어야 하고, 아니면 조립 시점에 ERR_NOTIFICATION_POLICY_INVALID로 부팅이 멈춥니다.

완료 상태: 앱의 store와 push gateway에 연결된 내구성 있는 relay·dispatch runner를 만듭니다.

Terminal window
pnpm add @gj-kit/nest-notifications

2. 앱이 소유할 경계를 정합니다

섹션 제목: “2. 앱이 소유할 경계를 정합니다”

Nest module을 등록하기 전에 제품 정책은 앱 소유 store, presenter, scheduling policy에 둡니다.

먼저 아래 코드를 복사한 뒤, 위에서 언급한 앱 소유 값만 교체하세요.

import { NestNotificationsModule, type NestNotificationsOptions } from '@gj-kit/nest-notifications';
declare const options: NestNotificationsOptions; // App stores, presenter, policy, and push gateway.
export const notifications = NestNotificationsModule.forRoot(options);

두 대목 모두 실제로 강제됩니다 — 시간대 없이는 정책을 만들 수 없고, 라이브러리가 자기 인메모리 저장소에 돌리는 케이스 30개가 그대로 직접 만든 저장소의 인수 조건이 됩니다.

import { createQuietHoursPolicy } from '@gj-kit/nest-notifications/core';
import { notificationStoreContractCases } from '@gj-kit/nest-notifications/testing';
import type { NotificationStoreSuite } from '@gj-kit/nest-notifications/testing';
declare function myPostgresStores(): Promise<NotificationStoreSuite>; // the app owns this
// The library holds no regional default, so the zone cannot be left unsaid.
export const policy = createQuietHoursPolicy({
timeZone: 'Asia/Seoul',
quietHours: { startHour: 22, endHour: 8 },
batchWindowMs: 600_000, // must divide 24h, or assembly throws ERR_NOTIFICATION_POLICY_INVALID
});
// Drop timeZone and tsc stops the build:
// error TS2345: Argument of type '{ quietHours: { startHour: number; endHour: number; }; }'
// is not assignable to parameter of type 'QuietHoursPolicyOptions'.
// The 30 cases the library runs on its own in-memory stores, now run on yours.
for (const testCase of notificationStoreContractCases({ concurrency: 8 })) {
it(testCase.name, () => testCase.run(myPostgresStores));
}

제품 이벤트를 delivery 정책을 제품 도메인에 섞지 않고 내구성 있고 dedupe된 알림 작업으로 전환해야 할 때 사용합니다.

제품 문구, 수신자 정책, 사용자 선호 결정을 범용 relay로 옮기지 마세요.

엔진: node >=20

Peer 지원 범위 필요한 위치
@nestjs/common `^10
reflect-metadata `^0.1.13
rxjs ^7 패키지 통합
Entry Exported symbols
Root entry 77
./core 78
./expo 11
./testing 14

credential, endpoint 소유권, 사용자 노출 제품 문구는 앱에 둡니다. 원본 provider 실패 대신 typed error와 delivery outcome을 사용하세요.

전체 API reference에는 선언 파일에서 생성한 모든 공개 함수, 클래스, 타입, 상수, 오류 계약이 있습니다. 각 항목은 import path, release signature, package version을 표시합니다.

관계 패키지 함께 쓰는 이유
단독 사용 — 다른 GJ Kit 패키지 없이 도입할 수 있습니다.
선택 조합 @gj-kit/nest-operations-jobs 보호된 운영 경계에서 앱 소유 relay 또는 dispatch 작업을 예약할 때 함께 사용합니다.

npm · GitHub