Skip to content

@gj-kit/nest-notifications

A dispatcher with no presenter, or quiet hours with no time zone, is a compile error — not a DST bug in production.

  • npm 0.1.2
  • 230+ unit tests
  • 30 store contract cases for your database
  • 0 runtime dependencies
  • 4 entry points, ESM + CJS

A hand-rolled notification outbox loses deliveries to non-atomic claims, writes duplicate inbox rows when two workers grab the same batch, and pushes to accounts that were deleted mid-relay. Then quiet hours turn out to be a fixed offset added to a Date, drifting an hour every DST transition, and a batch window that does not divide 24h moves the aggregation bucket every day.

  • Required options, enforced by tsc — createNotificationDispatcher without a presenter and createQuietHoursPolicy without a timeZone do not compile — no default copy, no default region.
  • 30 contract cases for your database — notificationStoreContractCases() returns 30 runnable cases covering 29 numbered obligations: atomic claim, batch uniqueness, purge-versus-relay interleaving.
  • A core Nest cannot leak into — Guards assert the strings @nestjs, rxjs and reflect-metadata appear nowhere in src/core, src/expo or src/testing, and nowhere in the dist/core., dist/expo. and dist/testing.* module graphs — with a control case requiring dist/index.js to contain @nestjs, so the guard is proven not to be checking an empty set.
  • The latency hint returns void — NotificationPipelineWakeup.request() returns void — nothing to await, no result to inspect, no error to catch — so it cannot be mistaken for the owner of correctness. A test wires only that hint, advances the clock 12 hours, and finds the batched delivery still undelivered until dispatchDue() is called.
  • DST resolution is a contract — A spring-forward gap releases at the first instant after it, an autumn fold at the earlier one, and batchWindowMs must divide 24h evenly or assembly throws ERR_NOTIFICATION_POLICY_INVALID.

Outcome: Durable relay and dispatch runners wired to your stores and push gateway.

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

Keep product policy in app-owned stores, presenter, and scheduling policy before registering the Nest module.

Copy this first, then replace only the app-owned values named above.

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);

Both halves are load-bearing: the policy cannot be constructed without a time zone, and the same 30 cases the library runs against its in-memory stores become the acceptance criteria for yours.

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));
}

Use it when product events must become durable, deduplicated notification work without making delivery policy part of the product domain.

Do not move product copy, recipient policy, or user-preference decisions into the generic relay.

Engines: node >=20

Peer Supported range Required for
@nestjs/common `^10
reflect-metadata `^0.1.13
rxjs ^7 the package integration
Entry Exported symbols
Root entry 77
./core 78
./expo 11
./testing 14

Keep credentials, endpoint ownership, and user-visible product wording in the application. Use the typed error and delivery outcomes instead of raw provider failures.

complete API reference contains every public function, class, type, constant, and error contract generated from release declarations. Each item shows its import path, release signature, and package version.

Relationship Package Why combine them
Standalone — This package can be adopted without another GJ Kit package.
Optional companion @gj-kit/nest-operations-jobs Schedule application-owned relay or dispatch work through a protected operations boundary.

npm · GitHub