@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로 부팅이 멈춥니다.
Golden path
섹션 제목: “Golden path”완료 상태: 앱의 store와 push gateway에 연결된 내구성 있는 relay·dispatch runner를 만듭니다.
1. 설치
섹션 제목: “1. 설치”pnpm add @gj-kit/nest-notifications2. 앱이 소유할 경계를 정합니다
섹션 제목: “2. 앱이 소유할 경계를 정합니다”Nest module을 등록하기 전에 제품 정책은 앱 소유 store, presenter, scheduling policy에 둡니다.
3. 최소 연결부터 시작합니다
섹션 제목: “3. 최소 연결부터 시작합니다”먼저 아래 코드를 복사한 뒤, 위에서 언급한 앱 소유 값만 교체하세요.
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로 옮기지 마세요.
환경과 peer
섹션 제목: “환경과 peer”엔진: node >=20
| Peer | 지원 범위 | 필요한 위치 |
|---|---|---|
@nestjs/common |
`^10 | |
reflect-metadata |
`^0.1.13 | |
rxjs |
^7 |
패키지 통합 |
공개 subpath
섹션 제목: “공개 subpath”| Entry | Exported symbols |
|---|---|
Root entry |
77 |
./core |
78 |
./expo |
11 |
./testing |
14 |
안전 경계
섹션 제목: “안전 경계”credential, endpoint 소유권, 사용자 노출 제품 문구는 앱에 둡니다. 원본 provider 실패 대신 typed error와 delivery outcome을 사용하세요.
API reference
섹션 제목: “API reference”전체 API reference에는 선언 파일에서 생성한 모든 공개 함수, 클래스, 타입, 상수, 오류 계약이 있습니다. 각 항목은 import path, release signature, package version을 표시합니다.
패키지 관계
섹션 제목: “패키지 관계”| 관계 | 패키지 | 함께 쓰는 이유 |
|---|---|---|
| 단독 사용 | — | 다른 GJ Kit 패키지 없이 도입할 수 있습니다. |
| 선택 조합 | @gj-kit/nest-operations-jobs |
보호된 운영 경계에서 앱 소유 relay 또는 dispatch 작업을 예약할 때 함께 사용합니다. |