@warlock.js/notifications 5.28.0 → 5.29.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +6 -0
- package/llms-full.txt +50 -50
- package/llms.txt +8 -8
- package/package.json +7 -7
- package/skills/configure-notifications/SKILL.md +5 -5
- package/skills/define-channel/SKILL.md +6 -6
- package/skills/define-notification/SKILL.md +5 -5
- package/skills/notifications-basics/SKILL.md +7 -7
- package/skills/observe-notifications/SKILL.md +4 -4
- package/skills/queue-notifications/SKILL.md +4 -4
- package/skills/send-ad-hoc/SKILL.md +8 -8
- package/skills/use-in-app/SKILL.md +5 -5
- package/skills/write-notification-migration/SKILL.md +6 -6
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,12 @@ All notable changes to `@warlock.js/notifications` are documented in this file.
|
|
|
4
4
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). `@warlock.js/*` packages are released in lockstep — every package shares the same version number, so a version below may list only the changes that affected this package.
|
|
6
6
|
|
|
7
|
+
## 5.29.0 - 2026-10-01
|
|
8
|
+
|
|
9
|
+
### Changed
|
|
10
|
+
|
|
11
|
+
- Lockstep release; package APIs are unchanged. Skills refer to other topics by name instead of file paths, so cross-references keep working after `agent-kit` copies them into an app.
|
|
12
|
+
|
|
7
13
|
## 5.28.0 - 2026-10-01
|
|
8
14
|
|
|
9
15
|
### Changed
|
package/llms-full.txt
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
---
|
|
10
10
|
name: configure-notifications
|
|
11
|
-
description: 'Wire `@warlock.js/notifications` via a declarative `src/config/notifications.ts` that exports a `NotificationConfig` default — the notifications connector registers it at boot, so app code never calls `setNotificationConfig`. `channels`: registry mapping name → factory result (`mailChannel`, `inApp.configure`, custom `defineChannel`); `queue`: optional `QueueDispatcher` (`.queue()` throws without it); `preferences`: optional `PreferenceProvider` (drops channels the recipient muted); `rateLimit`: optional `RateLimiter` (drops channels exceeding per-recipient budgets). Triggers: `config/notifications.ts`, `NotificationConfig`, `setNotificationConfig`, `getNotificationConfig`, `mailChannel`, `inApp.configure`, "configure notifications", "wire notifications at boot", "set preferences provider", "set rate limit"; typical import `import { type NotificationConfig, mailChannel, inApp } from "@warlock.js/notifications"`. Skip: building a reusable notification —
|
|
11
|
+
description: 'Wire `@warlock.js/notifications` via a declarative `src/config/notifications.ts` that exports a `NotificationConfig` default — the notifications connector registers it at boot, so app code never calls `setNotificationConfig`. `channels`: registry mapping name → factory result (`mailChannel`, `inApp.configure`, custom `defineChannel`); `queue`: optional `QueueDispatcher` (`.queue()` throws without it); `preferences`: optional `PreferenceProvider` (drops channels the recipient muted); `rateLimit`: optional `RateLimiter` (drops channels exceeding per-recipient budgets). Triggers: `config/notifications.ts`, `NotificationConfig`, `setNotificationConfig`, `getNotificationConfig`, `mailChannel`, `inApp.configure`, "configure notifications", "wire notifications at boot", "set preferences provider", "set rate limit"; typical import `import { type NotificationConfig, mailChannel, inApp } from "@warlock.js/notifications"`. Skip: building a reusable notification — the `define-notification` topic; the in-app read API — the `use-in-app` topic; adding a custom channel — the `define-channel` topic.'
|
|
12
12
|
---
|
|
13
13
|
|
|
14
14
|
# Configure notifications
|
|
@@ -140,17 +140,17 @@ afterEach(() => resetNotificationConfig());
|
|
|
140
140
|
|
|
141
141
|
## See also
|
|
142
142
|
|
|
143
|
-
-
|
|
144
|
-
-
|
|
145
|
-
-
|
|
146
|
-
-
|
|
143
|
+
- The `notifications-basics` topic — package front door.
|
|
144
|
+
- The `define-notification` topic — reusable multi-channel definitions.
|
|
145
|
+
- The `use-in-app` topic — in-app read API.
|
|
146
|
+
- The `define-channel` topic — custom channels.
|
|
147
147
|
|
|
148
148
|
|
|
149
149
|
## define-channel `@warlock.js/notifications/define-channel/SKILL.md`
|
|
150
150
|
|
|
151
151
|
---
|
|
152
152
|
name: define-channel
|
|
153
|
-
description: 'Add a custom notification channel — `defineChannel<P>({ name, route?, send })` returns a `Channel<P>` you register in the `channels` map of `setNotificationConfig`. `name` matches the key in `NotificationChannels` (extend via `declare module` for typing); `route(notifiable)` resolves the recipient''s address (defaults to `{ id }` when omitted); `send({ payload, route, notifiable, options })` does the actual transport. Use for Slack/Discord/internal-webhook/anything else — `fetch`-based with NO SDK is the simplest variant. Channels that load a heavy SDK should follow the lazy-import pattern from the develop-feature playbook. Triggers: `defineChannel`, `Channel<P>`, "custom notification channel", "add discord channel", "slack channel", "webhook channel", "new channel type", `declare module "@warlock.js/notifications"`; typical import `import { defineChannel } from "@warlock.js/notifications"`. Skip: lazy-loading an optional SDK behind the channel — `
|
|
153
|
+
description: 'Add a custom notification channel — `defineChannel<P>({ name, route?, send })` returns a `Channel<P>` you register in the `channels` map of `setNotificationConfig`. `name` matches the key in `NotificationChannels` (extend via `declare module` for typing); `route(notifiable)` resolves the recipient''s address (defaults to `{ id }` when omitted); `send({ payload, route, notifiable, options })` does the actual transport. Use for Slack/Discord/internal-webhook/anything else — `fetch`-based with NO SDK is the simplest variant. Channels that load a heavy SDK should follow the lazy-import pattern from the develop-feature playbook. Triggers: `defineChannel`, `Channel<P>`, "custom notification channel", "add discord channel", "slack channel", "webhook channel", "new channel type", `declare module "@warlock.js/notifications"`; typical import `import { defineChannel } from "@warlock.js/notifications"`. Skip: lazy-loading an optional SDK behind the channel — the `develop-warlock.js-feature` workspace skill; using built-in channels — the `configure-notifications` topic.'
|
|
154
154
|
---
|
|
155
155
|
|
|
156
156
|
# `defineChannel` — custom channels
|
|
@@ -224,7 +224,7 @@ Throw to fail the send. The dispatcher catches per channel, fires a `failed` eve
|
|
|
224
224
|
|
|
225
225
|
## SDK-backed channels — lazy-import the SDK
|
|
226
226
|
|
|
227
|
-
Channels that wrap a heavy SDK (Twilio, Slack SDK, FCM SDK) should follow the lazy-import pattern so apps that don't use them don't pay the install/load cost. The recipe + race-safe template lives in
|
|
227
|
+
Channels that wrap a heavy SDK (Twilio, Slack SDK, FCM SDK) should follow the lazy-import pattern so apps that don't use them don't pay the install/load cost. The recipe + race-safe template lives in the `develop-warlock.js-feature` workspace skill — copy from there; do not invent a variant.
|
|
228
228
|
|
|
229
229
|
Sketch:
|
|
230
230
|
|
|
@@ -251,17 +251,17 @@ export const slackChannel = (config: { token: string }) =>
|
|
|
251
251
|
|
|
252
252
|
## See also
|
|
253
253
|
|
|
254
|
-
-
|
|
255
|
-
-
|
|
256
|
-
-
|
|
257
|
-
- `
|
|
254
|
+
- The `configure-notifications` topic — register channels in the config.
|
|
255
|
+
- The `send-ad-hoc` topic — `notify.<custom-channel>` works after registration.
|
|
256
|
+
- The `define-notification` topic — multi-channel renderers with the custom channel key.
|
|
257
|
+
- The `develop-warlock.js-feature` workspace skill — lazy-import pattern for SDK-backed channels.
|
|
258
258
|
|
|
259
259
|
|
|
260
260
|
## define-notification `@warlock.js/notifications/define-notification/SKILL.md`
|
|
261
261
|
|
|
262
262
|
---
|
|
263
263
|
name: define-notification
|
|
264
|
-
description: 'Build a reusable multi-channel notification with `defineNotification<Data>({ type, via, ...renderers })`. `type` is the stable identifier preference/rate-limit gates use AND defaults the database channel''s `type`. `via` is `ChannelName[]` OR `(data, to) => ChannelName[]` for per-recipient channel selection. Renderers are `(data, to, ctx) => NotificationChannels[C]`; `ctx` carries `locale` + `meta` from `SendOptions`. The returned object exposes `.send(to|to[], data, options?)`, `.queue(to|to[], data, options?)`, and `.only(...channels)`. Per-recipient + per-channel `Promise.allSettled` isolates failures; the database channel''s `type` is auto-defaulted from `def.type`; `SendOptions.idempotencyKey` is injected into the database payload. Triggers: `defineNotification`, `via`, "reusable notification", "multi-channel notification", "send through several channels at once", "render per channel", `RenderContext`, `def.type`; "force on a notification", "queue with delay"; typical import `import { defineNotification } from "@warlock.js/notifications"`. Skip: single-channel ad-hoc sends —
|
|
264
|
+
description: 'Build a reusable multi-channel notification with `defineNotification<Data>({ type, via, ...renderers })`. `type` is the stable identifier preference/rate-limit gates use AND defaults the database channel''s `type`. `via` is `ChannelName[]` OR `(data, to) => ChannelName[]` for per-recipient channel selection. Renderers are `(data, to, ctx) => NotificationChannels[C]`; `ctx` carries `locale` + `meta` from `SendOptions`. The returned object exposes `.send(to|to[], data, options?)`, `.queue(to|to[], data, options?)`, and `.only(...channels)`. Per-recipient + per-channel `Promise.allSettled` isolates failures; the database channel''s `type` is auto-defaulted from `def.type`; `SendOptions.idempotencyKey` is injected into the database payload. Triggers: `defineNotification`, `via`, "reusable notification", "multi-channel notification", "send through several channels at once", "render per channel", `RenderContext`, `def.type`; "force on a notification", "queue with delay"; typical import `import { defineNotification } from "@warlock.js/notifications"`. Skip: single-channel ad-hoc sends — the `send-ad-hoc` topic; adding a brand-new channel — the `define-channel` topic; observing dispatch outcomes — the `observe-notifications` topic.'
|
|
265
265
|
---
|
|
266
266
|
|
|
267
267
|
# `defineNotification` — reusable multi-channel notification
|
|
@@ -388,10 +388,10 @@ Per-recipient: each recipient gets an independent `Promise.allSettled`. Per-chan
|
|
|
388
388
|
|
|
389
389
|
## See also
|
|
390
390
|
|
|
391
|
-
-
|
|
392
|
-
-
|
|
393
|
-
-
|
|
394
|
-
-
|
|
391
|
+
- The `notifications-basics` topic — package front door.
|
|
392
|
+
- The `send-ad-hoc` topic — `notify.<channel>` per-channel shorthand.
|
|
393
|
+
- The `configure-notifications` topic — wire channels + preferences + rate-limit + queue.
|
|
394
|
+
- The `observe-notifications` topic — `notifications.on("sending" | "sent" | "failed" | "skipped", …)`.
|
|
395
395
|
|
|
396
396
|
|
|
397
397
|
## notifications-basics `@warlock.js/notifications/notifications-basics/SKILL.md`
|
|
@@ -487,20 +487,20 @@ await inApp.markAsRead(user, "ntf_123");
|
|
|
487
487
|
|
|
488
488
|
## See also
|
|
489
489
|
|
|
490
|
-
-
|
|
491
|
-
-
|
|
492
|
-
-
|
|
493
|
-
-
|
|
494
|
-
-
|
|
495
|
-
-
|
|
496
|
-
-
|
|
490
|
+
- The `configure-notifications` topic — wire `config/notifications.ts`.
|
|
491
|
+
- The `define-notification` topic — reusable multi-channel definitions.
|
|
492
|
+
- The `send-ad-hoc` topic — `notify.<channel>` shorthand.
|
|
493
|
+
- The `use-in-app` topic — `inApp` read API.
|
|
494
|
+
- The `define-channel` topic — custom channels.
|
|
495
|
+
- The `write-notification-migration` topic — columnMap-driven columns.
|
|
496
|
+
- The `observe-notifications` topic — events + metrics.
|
|
497
497
|
|
|
498
498
|
|
|
499
499
|
## observe-notifications `@warlock.js/notifications/observe-notifications/SKILL.md`
|
|
500
500
|
|
|
501
501
|
---
|
|
502
502
|
name: observe-notifications
|
|
503
|
-
description: 'Wire metrics, logging, tracing, and audits via `notifications.on(event, handler)` — four events fire per (channel, recipient): `sending` (gates passed, about to hit the transport), `sent` (succeeded), `failed` (channel threw — carries the Error), `skipped` (a pre-send gate dropped it — carries `reason: "preference" | "rate-limit"`). Every event carries the recipient as `notifiable?` (undefined only for raw-target ad-hoc sends) and a `dispatchId` — `sending` and its terminal `sent`/`failed` share it, so observers can pair them (spans, latency, hung-send detection). `sent`/`failed` also carry `durationMs`. Handler exceptions are logged-and-swallowed so one bad listener can not break the dispatcher. `on(...)` returns an unsubscribe function; `off(event, handler)` exists for symmetry. Triggers: `notifications.on`, `notifications.off`, `"sending"` event, `"sent"` event, `"failed"` event, `"skipped"` event, `dispatchId`, `durationMs`, `NotificationEvents`, "notification metrics", "notification latency", "trace notifications", "notification audit log", "track notification drops"; typical import `import { notifications } from "@warlock.js/notifications"`. Skip: building notifications —
|
|
503
|
+
description: 'Wire metrics, logging, tracing, and audits via `notifications.on(event, handler)` — four events fire per (channel, recipient): `sending` (gates passed, about to hit the transport), `sent` (succeeded), `failed` (channel threw — carries the Error), `skipped` (a pre-send gate dropped it — carries `reason: "preference" | "rate-limit"`). Every event carries the recipient as `notifiable?` (undefined only for raw-target ad-hoc sends) and a `dispatchId` — `sending` and its terminal `sent`/`failed` share it, so observers can pair them (spans, latency, hung-send detection). `sent`/`failed` also carry `durationMs`. Handler exceptions are logged-and-swallowed so one bad listener can not break the dispatcher. `on(...)` returns an unsubscribe function; `off(event, handler)` exists for symmetry. Triggers: `notifications.on`, `notifications.off`, `"sending"` event, `"sent"` event, `"failed"` event, `"skipped"` event, `dispatchId`, `durationMs`, `NotificationEvents`, "notification metrics", "notification latency", "trace notifications", "notification audit log", "track notification drops"; typical import `import { notifications } from "@warlock.js/notifications"`. Skip: building notifications — the `define-notification` topic; configuring the preference/rate-limit gates that produce `skipped` — the `configure-notifications` topic.'
|
|
504
504
|
---
|
|
505
505
|
|
|
506
506
|
# Observe notifications
|
|
@@ -561,16 +561,16 @@ A handler that throws is logged and swallowed — the other handlers for the sam
|
|
|
561
561
|
|
|
562
562
|
## See also
|
|
563
563
|
|
|
564
|
-
-
|
|
565
|
-
-
|
|
566
|
-
-
|
|
564
|
+
- The `notifications-basics` topic — front door + mental model.
|
|
565
|
+
- The `configure-notifications` topic — `preferences` / `rateLimit` slots that emit `skipped`.
|
|
566
|
+
- The `send-ad-hoc` topic — when `notifiable` is undefined in events.
|
|
567
567
|
|
|
568
568
|
|
|
569
569
|
## queue-notifications `@warlock.js/notifications/queue-notifications/SKILL.md`
|
|
570
570
|
|
|
571
571
|
---
|
|
572
572
|
name: queue-notifications
|
|
573
|
-
description: "Send notifications asynchronously through the herald backend or the BullMQ driver. Configure a QueueDispatcher in config/notifications.ts, then use notif.queue(to, data, options?) to enqueue rendered jobs. Herald uses heraldQueue() and startNotificationsWorker(); bullmqQueue({ attempts, backoff }) from @warlock.js/notifications uses BullMQ (lazy-loading the optional @warlock.js/queue peer), supports retries, and honours SendOptions.delay. Without a queue configured, .queue() rejects NoQueueDispatcherError. Triggers: heraldQueue, bullmqQueue, startNotificationsWorker, QueueDispatcher, .queue(, async notifications, queue notifications, notification worker, background notifications, notifications.dispatch. Skip: synchronous sends -
|
|
573
|
+
description: "Send notifications asynchronously through the herald backend or the BullMQ driver. Configure a QueueDispatcher in config/notifications.ts, then use notif.queue(to, data, options?) to enqueue rendered jobs. Herald uses heraldQueue() and startNotificationsWorker(); bullmqQueue({ attempts, backoff }) from @warlock.js/notifications uses BullMQ (lazy-loading the optional @warlock.js/queue peer), supports retries, and honours SendOptions.delay. Without a queue configured, .queue() rejects NoQueueDispatcherError. Triggers: heraldQueue, bullmqQueue, startNotificationsWorker, QueueDispatcher, .queue(, async notifications, queue notifications, notification worker, background notifications, notifications.dispatch. Skip: synchronous sends - the `define-notification` topic; herald itself - @warlock.js/herald/*."
|
|
574
574
|
---
|
|
575
575
|
|
|
576
576
|
# Queue notifications (async)
|
|
@@ -675,16 +675,16 @@ await orderShipped.queue(user, { order }, { idempotencyKey: `ship:${order.id}` }
|
|
|
675
675
|
|
|
676
676
|
## See also
|
|
677
677
|
|
|
678
|
-
-
|
|
679
|
-
-
|
|
680
|
-
-
|
|
678
|
+
- The `define-notification` topic - `.queue()` on a defined notification.
|
|
679
|
+
- The `configure-notifications` topic - the `queue` config slot.
|
|
680
|
+
- The `herald-basics` topic of the `warlock-js-herald` skill - connecting the broker.
|
|
681
681
|
|
|
682
682
|
|
|
683
683
|
## send-ad-hoc `@warlock.js/notifications/send-ad-hoc/SKILL.md`
|
|
684
684
|
|
|
685
685
|
---
|
|
686
686
|
name: send-ad-hoc
|
|
687
|
-
description: 'Ad-hoc per-channel send via the `notify.<channel>(to, payload, options?)` Proxy facade. Works for any channel registered in `NotificationChannels` (built-in or custom). `to` accepts a `Notifiable` model OR a raw route string (`"x@y.com"`); `payload` is typed per channel via the registry; `options` is the standard `SendOptions`. `notify.channel(name).send(...)` is the runtime escape when the channel name is not a compile-time literal. Type-based preference gating applies when a type is known — explicit via `options.type` OR auto-detected from a database payload''s `type` field. For multi-channel sends use `defineNotification` instead — there is no inline `notify(to, { via, ... })` form on purpose. Triggers: `notify.<channel>`, `notify.channel(name)`, `notify.mail`, `notify.database`, "send a one-off notification", "ad-hoc notification", "send an email without defining a notification", "raw email target", "dynamic channel name"; typical import `import { notify } from "@warlock.js/notifications"`. Skip: reusable multi-channel notifications —
|
|
687
|
+
description: 'Ad-hoc per-channel send via the `notify.<channel>(to, payload, options?)` Proxy facade. Works for any channel registered in `NotificationChannels` (built-in or custom). `to` accepts a `Notifiable` model OR a raw route string (`"x@y.com"`); `payload` is typed per channel via the registry; `options` is the standard `SendOptions`. `notify.channel(name).send(...)` is the runtime escape when the channel name is not a compile-time literal. Type-based preference gating applies when a type is known — explicit via `options.type` OR auto-detected from a database payload''s `type` field. For multi-channel sends use `defineNotification` instead — there is no inline `notify(to, { via, ... })` form on purpose. Triggers: `notify.<channel>`, `notify.channel(name)`, `notify.mail`, `notify.database`, "send a one-off notification", "ad-hoc notification", "send an email without defining a notification", "raw email target", "dynamic channel name"; typical import `import { notify } from "@warlock.js/notifications"`. Skip: reusable multi-channel notifications — the `define-notification` topic; configuring channels — the `configure-notifications` topic.'
|
|
688
688
|
---
|
|
689
689
|
|
|
690
690
|
# `notify.<channel>` — ad-hoc, per-channel sends
|
|
@@ -698,7 +698,7 @@ await notify.mail(user, { subject: "Welcome", html: "<p>Hi!</p>" });
|
|
|
698
698
|
await notify.database(user, { type: "welcome", title: "Welcome!" });
|
|
699
699
|
```
|
|
700
700
|
|
|
701
|
-
For **multi-channel** sends, use
|
|
701
|
+
For **multi-channel** sends, use `defineNotification` (the `define-notification` topic) — that's the reusable pattern and the only mental model for "send through several channels at once" (no inline multi-channel overload).
|
|
702
702
|
|
|
703
703
|
## Shape
|
|
704
704
|
|
|
@@ -730,7 +730,7 @@ const channelName = await chooseChannelForUser(user);
|
|
|
730
730
|
await notify.channel(channelName).send(user, payload);
|
|
731
731
|
```
|
|
732
732
|
|
|
733
|
-
This is the only path with `unknown`-typed payloads — prefer the literal `notify.<channel>` when you can. `to` is still what decides the recipient: even for the `database` channel, a `recipientId`-shaped key on `payload` (or the model's physical recipient column name) is stripped before the row is built, so an untyped payload assembled from request JSON can't redirect the write to another recipient — see
|
|
733
|
+
This is the only path with `unknown`-typed payloads — prefer the literal `notify.<channel>` when you can. `to` is still what decides the recipient: even for the `database` channel, a `recipientId`-shaped key on `payload` (or the model's physical recipient column name) is stripped before the row is built, so an untyped payload assembled from request JSON can't redirect the write to another recipient — see the `createFor` write-side section of the `use-in-app` topic.
|
|
734
734
|
|
|
735
735
|
## Opt into preference/rate-limit gating
|
|
736
736
|
|
|
@@ -749,7 +749,7 @@ await notify.database(user, { type: "welcome", title: "Welcome!" });
|
|
|
749
749
|
|
|
750
750
|
## Custom channels
|
|
751
751
|
|
|
752
|
-
After registering a custom channel (see
|
|
752
|
+
After registering a custom channel (see the `define-channel` topic), it's first-class:
|
|
753
753
|
|
|
754
754
|
```ts
|
|
755
755
|
await notify.discord(user, { content: "Build finished 🎉" });
|
|
@@ -773,17 +773,17 @@ await notify.slack(staff, { text: "Deploy starting" });
|
|
|
773
773
|
|
|
774
774
|
## See also
|
|
775
775
|
|
|
776
|
-
-
|
|
777
|
-
-
|
|
778
|
-
-
|
|
779
|
-
-
|
|
776
|
+
- The `notifications-basics` topic — front door + mental model.
|
|
777
|
+
- The `define-notification` topic — reusable multi-channel.
|
|
778
|
+
- The `define-channel` topic — register a custom channel.
|
|
779
|
+
- The `observe-notifications` topic — `sending` / `sent` / `failed` / `skipped` events.
|
|
780
780
|
|
|
781
781
|
|
|
782
782
|
## use-in-app `@warlock.js/notifications/use-in-app/SKILL.md`
|
|
783
783
|
|
|
784
784
|
---
|
|
785
785
|
name: use-in-app
|
|
786
|
-
description: 'Use the `inApp` facade for the in-app/database channel''s READ side: `inApp.configure({ model: Notification })` or `{ repository: myRepo }` binds the store AND returns the channel; `inApp.list / listUnread / countUnread / markAsRead / markAsUnread` are RECIPIENT-SCOPED by construction (no IDOR — `markAsRead(user, id)` cannot flip another user''s row even with a foreign id). `countUnread` uses `RepositoryManager.countCached` for the badge fast-path. Triggers: `inApp.configure`, `inApp.list`, `inApp.listUnread`, `inApp.countUnread`, `inApp.markAsRead`, `inApp.markAsUnread`, `BaseNotificationsRepository`, `DatabaseNotification`, `NotificationsFilter`, "in-app notifications", "mark notification as read", "unread count", "notification badge", "IDOR-safe markAsRead"; typical import `import { inApp } from "@warlock.js/notifications"`. Skip: writing notifications (use `defineNotification` or `notify.database`) —
|
|
786
|
+
description: 'Use the `inApp` facade for the in-app/database channel''s READ side: `inApp.configure({ model: Notification })` or `{ repository: myRepo }` binds the store AND returns the channel; `inApp.list / listUnread / countUnread / markAsRead / markAsUnread` are RECIPIENT-SCOPED by construction (no IDOR — `markAsRead(user, id)` cannot flip another user''s row even with a foreign id). `countUnread` uses `RepositoryManager.countCached` for the badge fast-path. Triggers: `inApp.configure`, `inApp.list`, `inApp.listUnread`, `inApp.countUnread`, `inApp.markAsRead`, `inApp.markAsUnread`, `BaseNotificationsRepository`, `DatabaseNotification`, `NotificationsFilter`, "in-app notifications", "mark notification as read", "unread count", "notification badge", "IDOR-safe markAsRead"; typical import `import { inApp } from "@warlock.js/notifications"`. Skip: writing notifications (use `defineNotification` or `notify.database`) — the `define-notification` topic; the migration columns — the `write-notification-migration` topic; wiring it into config — the `configure-notifications` topic.'
|
|
787
787
|
---
|
|
788
788
|
|
|
789
789
|
# `inApp` — in-app notification read API
|
|
@@ -949,17 +949,17 @@ database: inApp.configure({ repository: notificationsRepository }),
|
|
|
949
949
|
|
|
950
950
|
## See also
|
|
951
951
|
|
|
952
|
-
-
|
|
953
|
-
-
|
|
954
|
-
-
|
|
955
|
-
-
|
|
952
|
+
- The `notifications-basics` topic — package front door.
|
|
953
|
+
- The `configure-notifications` topic — wire `inApp.configure` into the config.
|
|
954
|
+
- The `write-notification-migration` topic — `notificationColumns(Notification)`, named from `columnMap`.
|
|
955
|
+
- The `define-notification` topic — create rows via the database channel.
|
|
956
956
|
|
|
957
957
|
|
|
958
958
|
## write-notification-migration `@warlock.js/notifications/write-notification-migration/SKILL.md`
|
|
959
959
|
|
|
960
960
|
---
|
|
961
961
|
name: write-notification-migration
|
|
962
|
-
description: 'Create the notifications table with the `notificationColumns(Notification)` factory + cascade `Migration.create`. Column NAMES come from the model''s `columnMap` (recipient / tenant / readAt / isRead); the fixed columns are `type` / `title` / `body` / `payload` / `idempotency_key`. Set `{ idType: "bigint" }` for integer-key apps; UUID remains the default. Read-state follows columnMap presence: `readAt` → nullable timestamp, `isRead` → indexed boolean, both → both. Defaults to `user_id` + `read_at` when no columnMap. Spread + extend for extras (FK references, composite indexes). Triggers: `notificationColumns`, `Migration.create` for notifications, "create the notifications table", "notification migration", "add organization_id to notifications", `is_read` column, `read_at` column, `idempotency_key` column; typical import `import { Migration } from "@warlock.js/cascade"; import { notificationColumns } from "@warlock.js/notifications"`. Skip: generic cascade migration writing —
|
|
962
|
+
description: 'Create the notifications table with the `notificationColumns(Notification)` factory + cascade `Migration.create`. Column NAMES come from the model''s `columnMap` (recipient / tenant / readAt / isRead); the fixed columns are `type` / `title` / `body` / `payload` / `idempotency_key`. Set `{ idType: "bigint" }` for integer-key apps; UUID remains the default. Read-state follows columnMap presence: `readAt` → nullable timestamp, `isRead` → indexed boolean, both → both. Defaults to `user_id` + `read_at` when no columnMap. Spread + extend for extras (FK references, composite indexes). Triggers: `notificationColumns`, `Migration.create` for notifications, "create the notifications table", "notification migration", "add organization_id to notifications", `is_read` column, `read_at` column, `idempotency_key` column; typical import `import { Migration } from "@warlock.js/cascade"; import { notificationColumns } from "@warlock.js/notifications"`. Skip: generic cascade migration writing — the `write-migration` topic of the `warlock-js-cascade` skill; the model itself — the `use-in-app` topic.'
|
|
963
963
|
---
|
|
964
964
|
|
|
965
965
|
# Write the notifications migration
|
|
@@ -1017,7 +1017,7 @@ export default Migration.create(Notification, {
|
|
|
1017
1017
|
});
|
|
1018
1018
|
```
|
|
1019
1019
|
|
|
1020
|
-
Mirror any extra column in the model `schema`. To make it filterable, add a method to a custom repository (see `use-in-app
|
|
1020
|
+
Mirror any extra column in the model `schema`. To make it filterable, add a method to a custom repository (see the `use-in-app` topic).
|
|
1021
1021
|
|
|
1022
1022
|
## Column naming follows `columnMap`
|
|
1023
1023
|
|
|
@@ -1030,12 +1030,12 @@ class Notification extends DatabaseNotification {
|
|
|
1030
1030
|
// → notificationColumns(Notification) emits `userId` + `readAt`
|
|
1031
1031
|
```
|
|
1032
1032
|
|
|
1033
|
-
Without a model passed, defaults to `user_id` + `read_at` (read_at-only). See
|
|
1033
|
+
Without a model passed, defaults to `user_id` + `read_at` (read_at-only). See the `use-in-app` topic for the full `columnMap` shape.
|
|
1034
1034
|
|
|
1035
1035
|
## See also
|
|
1036
1036
|
|
|
1037
|
-
-
|
|
1038
|
-
-
|
|
1039
|
-
-
|
|
1037
|
+
- The `use-in-app` topic — the model + repository the migration backs.
|
|
1038
|
+
- The `write-migration` topic of the `warlock-js-cascade` skill — generic Cascade migration form, `Migration.alter`, etc.
|
|
1039
|
+
- The `manage-data-sources` topic of the `warlock-js-cascade` skill — multi-data-source setup.
|
|
1040
1040
|
|
|
1041
1041
|
|
package/llms.txt
CHANGED
|
@@ -6,12 +6,12 @@
|
|
|
6
6
|
|
|
7
7
|
## Skills
|
|
8
8
|
|
|
9
|
-
- [configure-notifications](@warlock.js/notifications/configure-notifications/SKILL.md): Wire `@warlock.js/notifications` via a declarative `src/config/notifications.ts` that exports a `NotificationConfig` default — the notifications connector registers it at boot, so app code never calls `setNotificationConfig`. `channels`: registry mapping name → factory result (`mailChannel`, `inApp.configure`, custom `defineChannel`); `queue`: optional `QueueDispatcher` (`.queue()` throws without it); `preferences`: optional `PreferenceProvider` (drops channels the recipient muted); `rateLimit`: optional `RateLimiter` (drops channels exceeding per-recipient budgets). Triggers: `config/notifications.ts`, `NotificationConfig`, `setNotificationConfig`, `getNotificationConfig`, `mailChannel`, `inApp.configure`, "configure notifications", "wire notifications at boot", "set preferences provider", "set rate limit"; typical import `import { type NotificationConfig, mailChannel, inApp } from "@warlock.js/notifications"`. Skip: building a reusable notification —
|
|
10
|
-
- [define-channel](@warlock.js/notifications/define-channel/SKILL.md): Add a custom notification channel — `defineChannel<P>({ name, route?, send })` returns a `Channel<P>` you register in the `channels` map of `setNotificationConfig`. `name` matches the key in `NotificationChannels` (extend via `declare module` for typing); `route(notifiable)` resolves the recipient's address (defaults to `{ id }` when omitted); `send({ payload, route, notifiable, options })` does the actual transport. Use for Slack/Discord/internal-webhook/anything else — `fetch`-based with NO SDK is the simplest variant. Channels that load a heavy SDK should follow the lazy-import pattern from the develop-feature playbook. Triggers: `defineChannel`, `Channel<P>`, "custom notification channel", "add discord channel", "slack channel", "webhook channel", "new channel type", `declare module "@warlock.js/notifications"`; typical import `import { defineChannel } from "@warlock.js/notifications"`. Skip: lazy-loading an optional SDK behind the channel — `
|
|
11
|
-
- [define-notification](@warlock.js/notifications/define-notification/SKILL.md): Build a reusable multi-channel notification with `defineNotification<Data>({ type, via, ...renderers })`. `type` is the stable identifier preference/rate-limit gates use AND defaults the database channel's `type`. `via` is `ChannelName[]` OR `(data, to) => ChannelName[]` for per-recipient channel selection. Renderers are `(data, to, ctx) => NotificationChannels[C]`; `ctx` carries `locale` + `meta` from `SendOptions`. The returned object exposes `.send(to|to[], data, options?)`, `.queue(to|to[], data, options?)`, and `.only(...channels)`. Per-recipient + per-channel `Promise.allSettled` isolates failures; the database channel's `type` is auto-defaulted from `def.type`; `SendOptions.idempotencyKey` is injected into the database payload. Triggers: `defineNotification`, `via`, "reusable notification", "multi-channel notification", "send through several channels at once", "render per channel", `RenderContext`, `def.type`; "force on a notification", "queue with delay"; typical import `import { defineNotification } from "@warlock.js/notifications"`. Skip: single-channel ad-hoc sends —
|
|
9
|
+
- [configure-notifications](@warlock.js/notifications/configure-notifications/SKILL.md): Wire `@warlock.js/notifications` via a declarative `src/config/notifications.ts` that exports a `NotificationConfig` default — the notifications connector registers it at boot, so app code never calls `setNotificationConfig`. `channels`: registry mapping name → factory result (`mailChannel`, `inApp.configure`, custom `defineChannel`); `queue`: optional `QueueDispatcher` (`.queue()` throws without it); `preferences`: optional `PreferenceProvider` (drops channels the recipient muted); `rateLimit`: optional `RateLimiter` (drops channels exceeding per-recipient budgets). Triggers: `config/notifications.ts`, `NotificationConfig`, `setNotificationConfig`, `getNotificationConfig`, `mailChannel`, `inApp.configure`, "configure notifications", "wire notifications at boot", "set preferences provider", "set rate limit"; typical import `import { type NotificationConfig, mailChannel, inApp } from "@warlock.js/notifications"`. Skip: building a reusable notification — the `define-notification` topic; the in-app read API — the `use-in-app` topic; adding a custom channel — the `define-channel` topic.
|
|
10
|
+
- [define-channel](@warlock.js/notifications/define-channel/SKILL.md): Add a custom notification channel — `defineChannel<P>({ name, route?, send })` returns a `Channel<P>` you register in the `channels` map of `setNotificationConfig`. `name` matches the key in `NotificationChannels` (extend via `declare module` for typing); `route(notifiable)` resolves the recipient's address (defaults to `{ id }` when omitted); `send({ payload, route, notifiable, options })` does the actual transport. Use for Slack/Discord/internal-webhook/anything else — `fetch`-based with NO SDK is the simplest variant. Channels that load a heavy SDK should follow the lazy-import pattern from the develop-feature playbook. Triggers: `defineChannel`, `Channel<P>`, "custom notification channel", "add discord channel", "slack channel", "webhook channel", "new channel type", `declare module "@warlock.js/notifications"`; typical import `import { defineChannel } from "@warlock.js/notifications"`. Skip: lazy-loading an optional SDK behind the channel — the `develop-warlock.js-feature` workspace skill; using built-in channels — the `configure-notifications` topic.
|
|
11
|
+
- [define-notification](@warlock.js/notifications/define-notification/SKILL.md): Build a reusable multi-channel notification with `defineNotification<Data>({ type, via, ...renderers })`. `type` is the stable identifier preference/rate-limit gates use AND defaults the database channel's `type`. `via` is `ChannelName[]` OR `(data, to) => ChannelName[]` for per-recipient channel selection. Renderers are `(data, to, ctx) => NotificationChannels[C]`; `ctx` carries `locale` + `meta` from `SendOptions`. The returned object exposes `.send(to|to[], data, options?)`, `.queue(to|to[], data, options?)`, and `.only(...channels)`. Per-recipient + per-channel `Promise.allSettled` isolates failures; the database channel's `type` is auto-defaulted from `def.type`; `SendOptions.idempotencyKey` is injected into the database payload. Triggers: `defineNotification`, `via`, "reusable notification", "multi-channel notification", "send through several channels at once", "render per channel", `RenderContext`, `def.type`; "force on a notification", "queue with delay"; typical import `import { defineNotification } from "@warlock.js/notifications"`. Skip: single-channel ad-hoc sends — the `send-ad-hoc` topic; adding a brand-new channel — the `define-channel` topic; observing dispatch outcomes — the `observe-notifications` topic.
|
|
12
12
|
- [notifications-basics](@warlock.js/notifications/notifications-basics/SKILL.md): Start here for @warlock.js/notifications channels, notification definitions, delivery, and application setup.
|
|
13
|
-
- [observe-notifications](@warlock.js/notifications/observe-notifications/SKILL.md): Wire metrics, logging, tracing, and audits via `notifications.on(event, handler)` — four events fire per (channel, recipient): `sending` (gates passed, about to hit the transport), `sent` (succeeded), `failed` (channel threw — carries the Error), `skipped` (a pre-send gate dropped it — carries `reason: "preference" | "rate-limit"`). Every event carries the recipient as `notifiable?` (undefined only for raw-target ad-hoc sends) and a `dispatchId` — `sending` and its terminal `sent`/`failed` share it, so observers can pair them (spans, latency, hung-send detection). `sent`/`failed` also carry `durationMs`. Handler exceptions are logged-and-swallowed so one bad listener can not break the dispatcher. `on(...)` returns an unsubscribe function; `off(event, handler)` exists for symmetry. Triggers: `notifications.on`, `notifications.off`, `"sending"` event, `"sent"` event, `"failed"` event, `"skipped"` event, `dispatchId`, `durationMs`, `NotificationEvents`, "notification metrics", "notification latency", "trace notifications", "notification audit log", "track notification drops"; typical import `import { notifications } from "@warlock.js/notifications"`. Skip: building notifications —
|
|
14
|
-
- [queue-notifications](@warlock.js/notifications/queue-notifications/SKILL.md): Send notifications asynchronously through the herald backend or the BullMQ driver. Configure a QueueDispatcher in config/notifications.ts, then use notif.queue(to, data, options?) to enqueue rendered jobs. Herald uses heraldQueue() and startNotificationsWorker(); bullmqQueue({ attempts, backoff }) from @warlock.js/notifications uses BullMQ (lazy-loading the optional @warlock.js/queue peer), supports retries, and honours SendOptions.delay. Without a queue configured, .queue() rejects NoQueueDispatcherError. Triggers: heraldQueue, bullmqQueue, startNotificationsWorker, QueueDispatcher, .queue(, async notifications, queue notifications, notification worker, background notifications, notifications.dispatch. Skip: synchronous sends -
|
|
15
|
-
- [send-ad-hoc](@warlock.js/notifications/send-ad-hoc/SKILL.md): Ad-hoc per-channel send via the `notify.<channel>(to, payload, options?)` Proxy facade. Works for any channel registered in `NotificationChannels` (built-in or custom). `to` accepts a `Notifiable` model OR a raw route string (`"x@y.com"`); `payload` is typed per channel via the registry; `options` is the standard `SendOptions`. `notify.channel(name).send(...)` is the runtime escape when the channel name is not a compile-time literal. Type-based preference gating applies when a type is known — explicit via `options.type` OR auto-detected from a database payload's `type` field. For multi-channel sends use `defineNotification` instead — there is no inline `notify(to, { via, ... })` form on purpose. Triggers: `notify.<channel>`, `notify.channel(name)`, `notify.mail`, `notify.database`, "send a one-off notification", "ad-hoc notification", "send an email without defining a notification", "raw email target", "dynamic channel name"; typical import `import { notify } from "@warlock.js/notifications"`. Skip: reusable multi-channel notifications —
|
|
16
|
-
- [use-in-app](@warlock.js/notifications/use-in-app/SKILL.md): Use the `inApp` facade for the in-app/database channel's READ side: `inApp.configure({ model: Notification })` or `{ repository: myRepo }` binds the store AND returns the channel; `inApp.list / listUnread / countUnread / markAsRead / markAsUnread` are RECIPIENT-SCOPED by construction (no IDOR — `markAsRead(user, id)` cannot flip another user's row even with a foreign id). `countUnread` uses `RepositoryManager.countCached` for the badge fast-path. Triggers: `inApp.configure`, `inApp.list`, `inApp.listUnread`, `inApp.countUnread`, `inApp.markAsRead`, `inApp.markAsUnread`, `BaseNotificationsRepository`, `DatabaseNotification`, `NotificationsFilter`, "in-app notifications", "mark notification as read", "unread count", "notification badge", "IDOR-safe markAsRead"; typical import `import { inApp } from "@warlock.js/notifications"`. Skip: writing notifications (use `defineNotification` or `notify.database`) —
|
|
17
|
-
- [write-notification-migration](@warlock.js/notifications/write-notification-migration/SKILL.md): Create the notifications table with the `notificationColumns(Notification)` factory + cascade `Migration.create`. Column NAMES come from the model's `columnMap` (recipient / tenant / readAt / isRead); the fixed columns are `type` / `title` / `body` / `payload` / `idempotency_key`. Set `{ idType: "bigint" }` for integer-key apps; UUID remains the default. Read-state follows columnMap presence: `readAt` → nullable timestamp, `isRead` → indexed boolean, both → both. Defaults to `user_id` + `read_at` when no columnMap. Spread + extend for extras (FK references, composite indexes). Triggers: `notificationColumns`, `Migration.create` for notifications, "create the notifications table", "notification migration", "add organization_id to notifications", `is_read` column, `read_at` column, `idempotency_key` column; typical import `import { Migration } from "@warlock.js/cascade"; import { notificationColumns } from "@warlock.js/notifications"`. Skip: generic cascade migration writing —
|
|
13
|
+
- [observe-notifications](@warlock.js/notifications/observe-notifications/SKILL.md): Wire metrics, logging, tracing, and audits via `notifications.on(event, handler)` — four events fire per (channel, recipient): `sending` (gates passed, about to hit the transport), `sent` (succeeded), `failed` (channel threw — carries the Error), `skipped` (a pre-send gate dropped it — carries `reason: "preference" | "rate-limit"`). Every event carries the recipient as `notifiable?` (undefined only for raw-target ad-hoc sends) and a `dispatchId` — `sending` and its terminal `sent`/`failed` share it, so observers can pair them (spans, latency, hung-send detection). `sent`/`failed` also carry `durationMs`. Handler exceptions are logged-and-swallowed so one bad listener can not break the dispatcher. `on(...)` returns an unsubscribe function; `off(event, handler)` exists for symmetry. Triggers: `notifications.on`, `notifications.off`, `"sending"` event, `"sent"` event, `"failed"` event, `"skipped"` event, `dispatchId`, `durationMs`, `NotificationEvents`, "notification metrics", "notification latency", "trace notifications", "notification audit log", "track notification drops"; typical import `import { notifications } from "@warlock.js/notifications"`. Skip: building notifications — the `define-notification` topic; configuring the preference/rate-limit gates that produce `skipped` — the `configure-notifications` topic.
|
|
14
|
+
- [queue-notifications](@warlock.js/notifications/queue-notifications/SKILL.md): Send notifications asynchronously through the herald backend or the BullMQ driver. Configure a QueueDispatcher in config/notifications.ts, then use notif.queue(to, data, options?) to enqueue rendered jobs. Herald uses heraldQueue() and startNotificationsWorker(); bullmqQueue({ attempts, backoff }) from @warlock.js/notifications uses BullMQ (lazy-loading the optional @warlock.js/queue peer), supports retries, and honours SendOptions.delay. Without a queue configured, .queue() rejects NoQueueDispatcherError. Triggers: heraldQueue, bullmqQueue, startNotificationsWorker, QueueDispatcher, .queue(, async notifications, queue notifications, notification worker, background notifications, notifications.dispatch. Skip: synchronous sends - the `define-notification` topic; herald itself - @warlock.js/herald/*.
|
|
15
|
+
- [send-ad-hoc](@warlock.js/notifications/send-ad-hoc/SKILL.md): Ad-hoc per-channel send via the `notify.<channel>(to, payload, options?)` Proxy facade. Works for any channel registered in `NotificationChannels` (built-in or custom). `to` accepts a `Notifiable` model OR a raw route string (`"x@y.com"`); `payload` is typed per channel via the registry; `options` is the standard `SendOptions`. `notify.channel(name).send(...)` is the runtime escape when the channel name is not a compile-time literal. Type-based preference gating applies when a type is known — explicit via `options.type` OR auto-detected from a database payload's `type` field. For multi-channel sends use `defineNotification` instead — there is no inline `notify(to, { via, ... })` form on purpose. Triggers: `notify.<channel>`, `notify.channel(name)`, `notify.mail`, `notify.database`, "send a one-off notification", "ad-hoc notification", "send an email without defining a notification", "raw email target", "dynamic channel name"; typical import `import { notify } from "@warlock.js/notifications"`. Skip: reusable multi-channel notifications — the `define-notification` topic; configuring channels — the `configure-notifications` topic.
|
|
16
|
+
- [use-in-app](@warlock.js/notifications/use-in-app/SKILL.md): Use the `inApp` facade for the in-app/database channel's READ side: `inApp.configure({ model: Notification })` or `{ repository: myRepo }` binds the store AND returns the channel; `inApp.list / listUnread / countUnread / markAsRead / markAsUnread` are RECIPIENT-SCOPED by construction (no IDOR — `markAsRead(user, id)` cannot flip another user's row even with a foreign id). `countUnread` uses `RepositoryManager.countCached` for the badge fast-path. Triggers: `inApp.configure`, `inApp.list`, `inApp.listUnread`, `inApp.countUnread`, `inApp.markAsRead`, `inApp.markAsUnread`, `BaseNotificationsRepository`, `DatabaseNotification`, `NotificationsFilter`, "in-app notifications", "mark notification as read", "unread count", "notification badge", "IDOR-safe markAsRead"; typical import `import { inApp } from "@warlock.js/notifications"`. Skip: writing notifications (use `defineNotification` or `notify.database`) — the `define-notification` topic; the migration columns — the `write-notification-migration` topic; wiring it into config — the `configure-notifications` topic.
|
|
17
|
+
- [write-notification-migration](@warlock.js/notifications/write-notification-migration/SKILL.md): Create the notifications table with the `notificationColumns(Notification)` factory + cascade `Migration.create`. Column NAMES come from the model's `columnMap` (recipient / tenant / readAt / isRead); the fixed columns are `type` / `title` / `body` / `payload` / `idempotency_key`. Set `{ idType: "bigint" }` for integer-key apps; UUID remains the default. Read-state follows columnMap presence: `readAt` → nullable timestamp, `isRead` → indexed boolean, both → both. Defaults to `user_id` + `read_at` when no columnMap. Spread + extend for extras (FK references, composite indexes). Triggers: `notificationColumns`, `Migration.create` for notifications, "create the notifications table", "notification migration", "add organization_id to notifications", `is_read` column, `read_at` column, `idempotency_key` column; typical import `import { Migration } from "@warlock.js/cascade"; import { notificationColumns } from "@warlock.js/notifications"`. Skip: generic cascade migration writing — the `write-migration` topic of the `warlock-js-cascade` skill; the model itself — the `use-in-app` topic.
|
package/package.json
CHANGED
|
@@ -5,16 +5,16 @@
|
|
|
5
5
|
"environment": "server"
|
|
6
6
|
},
|
|
7
7
|
"dependencies": {
|
|
8
|
-
"@warlock.js/cache": "5.
|
|
9
|
-
"@warlock.js/cascade": "5.
|
|
10
|
-
"@warlock.js/core": "5.
|
|
11
|
-
"@warlock.js/logger": "5.
|
|
8
|
+
"@warlock.js/cache": "5.29.0",
|
|
9
|
+
"@warlock.js/cascade": "5.29.0",
|
|
10
|
+
"@warlock.js/core": "5.29.0",
|
|
11
|
+
"@warlock.js/logger": "5.29.0"
|
|
12
12
|
},
|
|
13
13
|
"optionalDependencies": {
|
|
14
|
-
"@warlock.js/herald": "5.
|
|
14
|
+
"@warlock.js/herald": "5.29.0"
|
|
15
15
|
},
|
|
16
16
|
"peerDependencies": {
|
|
17
|
-
"@warlock.js/queue": "5.
|
|
17
|
+
"@warlock.js/queue": "5.29.0"
|
|
18
18
|
},
|
|
19
19
|
"peerDependenciesMeta": {
|
|
20
20
|
"@warlock.js/queue": {
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
],
|
|
36
36
|
"author": "hassanzohdy",
|
|
37
37
|
"license": "MIT",
|
|
38
|
-
"version": "5.
|
|
38
|
+
"version": "5.29.0",
|
|
39
39
|
"main": "./cjs/index.cjs",
|
|
40
40
|
"module": "./esm/index.mjs",
|
|
41
41
|
"types": "./esm/index.d.mts",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: configure-notifications
|
|
3
|
-
description: 'Wire `@warlock.js/notifications` via a declarative `src/config/notifications.ts` that exports a `NotificationConfig` default — the notifications connector registers it at boot, so app code never calls `setNotificationConfig`. `channels`: registry mapping name → factory result (`mailChannel`, `inApp.configure`, custom `defineChannel`); `queue`: optional `QueueDispatcher` (`.queue()` throws without it); `preferences`: optional `PreferenceProvider` (drops channels the recipient muted); `rateLimit`: optional `RateLimiter` (drops channels exceeding per-recipient budgets). Triggers: `config/notifications.ts`, `NotificationConfig`, `setNotificationConfig`, `getNotificationConfig`, `mailChannel`, `inApp.configure`, "configure notifications", "wire notifications at boot", "set preferences provider", "set rate limit"; typical import `import { type NotificationConfig, mailChannel, inApp } from "@warlock.js/notifications"`. Skip: building a reusable notification —
|
|
3
|
+
description: 'Wire `@warlock.js/notifications` via a declarative `src/config/notifications.ts` that exports a `NotificationConfig` default — the notifications connector registers it at boot, so app code never calls `setNotificationConfig`. `channels`: registry mapping name → factory result (`mailChannel`, `inApp.configure`, custom `defineChannel`); `queue`: optional `QueueDispatcher` (`.queue()` throws without it); `preferences`: optional `PreferenceProvider` (drops channels the recipient muted); `rateLimit`: optional `RateLimiter` (drops channels exceeding per-recipient budgets). Triggers: `config/notifications.ts`, `NotificationConfig`, `setNotificationConfig`, `getNotificationConfig`, `mailChannel`, `inApp.configure`, "configure notifications", "wire notifications at boot", "set preferences provider", "set rate limit"; typical import `import { type NotificationConfig, mailChannel, inApp } from "@warlock.js/notifications"`. Skip: building a reusable notification — the `define-notification` topic; the in-app read API — the `use-in-app` topic; adding a custom channel — the `define-channel` topic.'
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Configure notifications
|
|
@@ -132,7 +132,7 @@ afterEach(() => resetNotificationConfig());
|
|
|
132
132
|
|
|
133
133
|
## See also
|
|
134
134
|
|
|
135
|
-
-
|
|
136
|
-
-
|
|
137
|
-
-
|
|
138
|
-
-
|
|
135
|
+
- The `notifications-basics` topic — package front door.
|
|
136
|
+
- The `define-notification` topic — reusable multi-channel definitions.
|
|
137
|
+
- The `use-in-app` topic — in-app read API.
|
|
138
|
+
- The `define-channel` topic — custom channels.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: define-channel
|
|
3
|
-
description: 'Add a custom notification channel — `defineChannel<P>({ name, route?, send })` returns a `Channel<P>` you register in the `channels` map of `setNotificationConfig`. `name` matches the key in `NotificationChannels` (extend via `declare module` for typing); `route(notifiable)` resolves the recipient''s address (defaults to `{ id }` when omitted); `send({ payload, route, notifiable, options })` does the actual transport. Use for Slack/Discord/internal-webhook/anything else — `fetch`-based with NO SDK is the simplest variant. Channels that load a heavy SDK should follow the lazy-import pattern from the develop-feature playbook. Triggers: `defineChannel`, `Channel<P>`, "custom notification channel", "add discord channel", "slack channel", "webhook channel", "new channel type", `declare module "@warlock.js/notifications"`; typical import `import { defineChannel } from "@warlock.js/notifications"`. Skip: lazy-loading an optional SDK behind the channel — `
|
|
3
|
+
description: 'Add a custom notification channel — `defineChannel<P>({ name, route?, send })` returns a `Channel<P>` you register in the `channels` map of `setNotificationConfig`. `name` matches the key in `NotificationChannels` (extend via `declare module` for typing); `route(notifiable)` resolves the recipient''s address (defaults to `{ id }` when omitted); `send({ payload, route, notifiable, options })` does the actual transport. Use for Slack/Discord/internal-webhook/anything else — `fetch`-based with NO SDK is the simplest variant. Channels that load a heavy SDK should follow the lazy-import pattern from the develop-feature playbook. Triggers: `defineChannel`, `Channel<P>`, "custom notification channel", "add discord channel", "slack channel", "webhook channel", "new channel type", `declare module "@warlock.js/notifications"`; typical import `import { defineChannel } from "@warlock.js/notifications"`. Skip: lazy-loading an optional SDK behind the channel — the `develop-warlock.js-feature` workspace skill; using built-in channels — the `configure-notifications` topic.'
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# `defineChannel` — custom channels
|
|
@@ -74,7 +74,7 @@ Throw to fail the send. The dispatcher catches per channel, fires a `failed` eve
|
|
|
74
74
|
|
|
75
75
|
## SDK-backed channels — lazy-import the SDK
|
|
76
76
|
|
|
77
|
-
Channels that wrap a heavy SDK (Twilio, Slack SDK, FCM SDK) should follow the lazy-import pattern so apps that don't use them don't pay the install/load cost. The recipe + race-safe template lives in
|
|
77
|
+
Channels that wrap a heavy SDK (Twilio, Slack SDK, FCM SDK) should follow the lazy-import pattern so apps that don't use them don't pay the install/load cost. The recipe + race-safe template lives in the `develop-warlock.js-feature` workspace skill — copy from there; do not invent a variant.
|
|
78
78
|
|
|
79
79
|
Sketch:
|
|
80
80
|
|
|
@@ -101,7 +101,7 @@ export const slackChannel = (config: { token: string }) =>
|
|
|
101
101
|
|
|
102
102
|
## See also
|
|
103
103
|
|
|
104
|
-
-
|
|
105
|
-
-
|
|
106
|
-
-
|
|
107
|
-
- `
|
|
104
|
+
- The `configure-notifications` topic — register channels in the config.
|
|
105
|
+
- The `send-ad-hoc` topic — `notify.<custom-channel>` works after registration.
|
|
106
|
+
- The `define-notification` topic — multi-channel renderers with the custom channel key.
|
|
107
|
+
- The `develop-warlock.js-feature` workspace skill — lazy-import pattern for SDK-backed channels.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: define-notification
|
|
3
|
-
description: 'Build a reusable multi-channel notification with `defineNotification<Data>({ type, via, ...renderers })`. `type` is the stable identifier preference/rate-limit gates use AND defaults the database channel''s `type`. `via` is `ChannelName[]` OR `(data, to) => ChannelName[]` for per-recipient channel selection. Renderers are `(data, to, ctx) => NotificationChannels[C]`; `ctx` carries `locale` + `meta` from `SendOptions`. The returned object exposes `.send(to|to[], data, options?)`, `.queue(to|to[], data, options?)`, and `.only(...channels)`. Per-recipient + per-channel `Promise.allSettled` isolates failures; the database channel''s `type` is auto-defaulted from `def.type`; `SendOptions.idempotencyKey` is injected into the database payload. Triggers: `defineNotification`, `via`, "reusable notification", "multi-channel notification", "send through several channels at once", "render per channel", `RenderContext`, `def.type`; "force on a notification", "queue with delay"; typical import `import { defineNotification } from "@warlock.js/notifications"`. Skip: single-channel ad-hoc sends —
|
|
3
|
+
description: 'Build a reusable multi-channel notification with `defineNotification<Data>({ type, via, ...renderers })`. `type` is the stable identifier preference/rate-limit gates use AND defaults the database channel''s `type`. `via` is `ChannelName[]` OR `(data, to) => ChannelName[]` for per-recipient channel selection. Renderers are `(data, to, ctx) => NotificationChannels[C]`; `ctx` carries `locale` + `meta` from `SendOptions`. The returned object exposes `.send(to|to[], data, options?)`, `.queue(to|to[], data, options?)`, and `.only(...channels)`. Per-recipient + per-channel `Promise.allSettled` isolates failures; the database channel''s `type` is auto-defaulted from `def.type`; `SendOptions.idempotencyKey` is injected into the database payload. Triggers: `defineNotification`, `via`, "reusable notification", "multi-channel notification", "send through several channels at once", "render per channel", `RenderContext`, `def.type`; "force on a notification", "queue with delay"; typical import `import { defineNotification } from "@warlock.js/notifications"`. Skip: single-channel ad-hoc sends — the `send-ad-hoc` topic; adding a brand-new channel — the `define-channel` topic; observing dispatch outcomes — the `observe-notifications` topic.'
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# `defineNotification` — reusable multi-channel notification
|
|
@@ -127,7 +127,7 @@ Per-recipient: each recipient gets an independent `Promise.allSettled`. Per-chan
|
|
|
127
127
|
|
|
128
128
|
## See also
|
|
129
129
|
|
|
130
|
-
-
|
|
131
|
-
-
|
|
132
|
-
-
|
|
133
|
-
-
|
|
130
|
+
- The `notifications-basics` topic — package front door.
|
|
131
|
+
- The `send-ad-hoc` topic — `notify.<channel>` per-channel shorthand.
|
|
132
|
+
- The `configure-notifications` topic — wire channels + preferences + rate-limit + queue.
|
|
133
|
+
- The `observe-notifications` topic — `notifications.on("sending" | "sent" | "failed" | "skipped", …)`.
|
|
@@ -89,10 +89,10 @@ await inApp.markAsRead(user, "ntf_123");
|
|
|
89
89
|
|
|
90
90
|
## See also
|
|
91
91
|
|
|
92
|
-
-
|
|
93
|
-
-
|
|
94
|
-
-
|
|
95
|
-
-
|
|
96
|
-
-
|
|
97
|
-
-
|
|
98
|
-
-
|
|
92
|
+
- The `configure-notifications` topic — wire `config/notifications.ts`.
|
|
93
|
+
- The `define-notification` topic — reusable multi-channel definitions.
|
|
94
|
+
- The `send-ad-hoc` topic — `notify.<channel>` shorthand.
|
|
95
|
+
- The `use-in-app` topic — `inApp` read API.
|
|
96
|
+
- The `define-channel` topic — custom channels.
|
|
97
|
+
- The `write-notification-migration` topic — columnMap-driven columns.
|
|
98
|
+
- The `observe-notifications` topic — events + metrics.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: observe-notifications
|
|
3
|
-
description: 'Wire metrics, logging, tracing, and audits via `notifications.on(event, handler)` — four events fire per (channel, recipient): `sending` (gates passed, about to hit the transport), `sent` (succeeded), `failed` (channel threw — carries the Error), `skipped` (a pre-send gate dropped it — carries `reason: "preference" | "rate-limit"`). Every event carries the recipient as `notifiable?` (undefined only for raw-target ad-hoc sends) and a `dispatchId` — `sending` and its terminal `sent`/`failed` share it, so observers can pair them (spans, latency, hung-send detection). `sent`/`failed` also carry `durationMs`. Handler exceptions are logged-and-swallowed so one bad listener can not break the dispatcher. `on(...)` returns an unsubscribe function; `off(event, handler)` exists for symmetry. Triggers: `notifications.on`, `notifications.off`, `"sending"` event, `"sent"` event, `"failed"` event, `"skipped"` event, `dispatchId`, `durationMs`, `NotificationEvents`, "notification metrics", "notification latency", "trace notifications", "notification audit log", "track notification drops"; typical import `import { notifications } from "@warlock.js/notifications"`. Skip: building notifications —
|
|
3
|
+
description: 'Wire metrics, logging, tracing, and audits via `notifications.on(event, handler)` — four events fire per (channel, recipient): `sending` (gates passed, about to hit the transport), `sent` (succeeded), `failed` (channel threw — carries the Error), `skipped` (a pre-send gate dropped it — carries `reason: "preference" | "rate-limit"`). Every event carries the recipient as `notifiable?` (undefined only for raw-target ad-hoc sends) and a `dispatchId` — `sending` and its terminal `sent`/`failed` share it, so observers can pair them (spans, latency, hung-send detection). `sent`/`failed` also carry `durationMs`. Handler exceptions are logged-and-swallowed so one bad listener can not break the dispatcher. `on(...)` returns an unsubscribe function; `off(event, handler)` exists for symmetry. Triggers: `notifications.on`, `notifications.off`, `"sending"` event, `"sent"` event, `"failed"` event, `"skipped"` event, `dispatchId`, `durationMs`, `NotificationEvents`, "notification metrics", "notification latency", "trace notifications", "notification audit log", "track notification drops"; typical import `import { notifications } from "@warlock.js/notifications"`. Skip: building notifications — the `define-notification` topic; configuring the preference/rate-limit gates that produce `skipped` — the `configure-notifications` topic.'
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Observe notifications
|
|
@@ -61,6 +61,6 @@ A handler that throws is logged and swallowed — the other handlers for the sam
|
|
|
61
61
|
|
|
62
62
|
## See also
|
|
63
63
|
|
|
64
|
-
-
|
|
65
|
-
-
|
|
66
|
-
-
|
|
64
|
+
- The `notifications-basics` topic — front door + mental model.
|
|
65
|
+
- The `configure-notifications` topic — `preferences` / `rateLimit` slots that emit `skipped`.
|
|
66
|
+
- The `send-ad-hoc` topic — when `notifiable` is undefined in events.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: queue-notifications
|
|
3
|
-
description: "Send notifications asynchronously through the herald backend or the BullMQ driver. Configure a QueueDispatcher in config/notifications.ts, then use notif.queue(to, data, options?) to enqueue rendered jobs. Herald uses heraldQueue() and startNotificationsWorker(); bullmqQueue({ attempts, backoff }) from @warlock.js/notifications uses BullMQ (lazy-loading the optional @warlock.js/queue peer), supports retries, and honours SendOptions.delay. Without a queue configured, .queue() rejects NoQueueDispatcherError. Triggers: heraldQueue, bullmqQueue, startNotificationsWorker, QueueDispatcher, .queue(, async notifications, queue notifications, notification worker, background notifications, notifications.dispatch. Skip: synchronous sends -
|
|
3
|
+
description: "Send notifications asynchronously through the herald backend or the BullMQ driver. Configure a QueueDispatcher in config/notifications.ts, then use notif.queue(to, data, options?) to enqueue rendered jobs. Herald uses heraldQueue() and startNotificationsWorker(); bullmqQueue({ attempts, backoff }) from @warlock.js/notifications uses BullMQ (lazy-loading the optional @warlock.js/queue peer), supports retries, and honours SendOptions.delay. Without a queue configured, .queue() rejects NoQueueDispatcherError. Triggers: heraldQueue, bullmqQueue, startNotificationsWorker, QueueDispatcher, .queue(, async notifications, queue notifications, notification worker, background notifications, notifications.dispatch. Skip: synchronous sends - the `define-notification` topic; herald itself - @warlock.js/herald/*."
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Queue notifications (async)
|
|
@@ -105,6 +105,6 @@ await orderShipped.queue(user, { order }, { idempotencyKey: `ship:${order.id}` }
|
|
|
105
105
|
|
|
106
106
|
## See also
|
|
107
107
|
|
|
108
|
-
-
|
|
109
|
-
-
|
|
110
|
-
-
|
|
108
|
+
- The `define-notification` topic - `.queue()` on a defined notification.
|
|
109
|
+
- The `configure-notifications` topic - the `queue` config slot.
|
|
110
|
+
- The `herald-basics` topic of the `warlock-js-herald` skill - connecting the broker.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: send-ad-hoc
|
|
3
|
-
description: 'Ad-hoc per-channel send via the `notify.<channel>(to, payload, options?)` Proxy facade. Works for any channel registered in `NotificationChannels` (built-in or custom). `to` accepts a `Notifiable` model OR a raw route string (`"x@y.com"`); `payload` is typed per channel via the registry; `options` is the standard `SendOptions`. `notify.channel(name).send(...)` is the runtime escape when the channel name is not a compile-time literal. Type-based preference gating applies when a type is known — explicit via `options.type` OR auto-detected from a database payload''s `type` field. For multi-channel sends use `defineNotification` instead — there is no inline `notify(to, { via, ... })` form on purpose. Triggers: `notify.<channel>`, `notify.channel(name)`, `notify.mail`, `notify.database`, "send a one-off notification", "ad-hoc notification", "send an email without defining a notification", "raw email target", "dynamic channel name"; typical import `import { notify } from "@warlock.js/notifications"`. Skip: reusable multi-channel notifications —
|
|
3
|
+
description: 'Ad-hoc per-channel send via the `notify.<channel>(to, payload, options?)` Proxy facade. Works for any channel registered in `NotificationChannels` (built-in or custom). `to` accepts a `Notifiable` model OR a raw route string (`"x@y.com"`); `payload` is typed per channel via the registry; `options` is the standard `SendOptions`. `notify.channel(name).send(...)` is the runtime escape when the channel name is not a compile-time literal. Type-based preference gating applies when a type is known — explicit via `options.type` OR auto-detected from a database payload''s `type` field. For multi-channel sends use `defineNotification` instead — there is no inline `notify(to, { via, ... })` form on purpose. Triggers: `notify.<channel>`, `notify.channel(name)`, `notify.mail`, `notify.database`, "send a one-off notification", "ad-hoc notification", "send an email without defining a notification", "raw email target", "dynamic channel name"; typical import `import { notify } from "@warlock.js/notifications"`. Skip: reusable multi-channel notifications — the `define-notification` topic; configuring channels — the `configure-notifications` topic.'
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# `notify.<channel>` — ad-hoc, per-channel sends
|
|
@@ -14,7 +14,7 @@ await notify.mail(user, { subject: "Welcome", html: "<p>Hi!</p>" });
|
|
|
14
14
|
await notify.database(user, { type: "welcome", title: "Welcome!" });
|
|
15
15
|
```
|
|
16
16
|
|
|
17
|
-
For **multi-channel** sends, use
|
|
17
|
+
For **multi-channel** sends, use `defineNotification` (the `define-notification` topic) — that's the reusable pattern and the only mental model for "send through several channels at once" (no inline multi-channel overload).
|
|
18
18
|
|
|
19
19
|
## Shape
|
|
20
20
|
|
|
@@ -46,7 +46,7 @@ const channelName = await chooseChannelForUser(user);
|
|
|
46
46
|
await notify.channel(channelName).send(user, payload);
|
|
47
47
|
```
|
|
48
48
|
|
|
49
|
-
This is the only path with `unknown`-typed payloads — prefer the literal `notify.<channel>` when you can. `to` is still what decides the recipient: even for the `database` channel, a `recipientId`-shaped key on `payload` (or the model's physical recipient column name) is stripped before the row is built, so an untyped payload assembled from request JSON can't redirect the write to another recipient — see
|
|
49
|
+
This is the only path with `unknown`-typed payloads — prefer the literal `notify.<channel>` when you can. `to` is still what decides the recipient: even for the `database` channel, a `recipientId`-shaped key on `payload` (or the model's physical recipient column name) is stripped before the row is built, so an untyped payload assembled from request JSON can't redirect the write to another recipient — see the `createFor` write-side section of the `use-in-app` topic.
|
|
50
50
|
|
|
51
51
|
## Opt into preference/rate-limit gating
|
|
52
52
|
|
|
@@ -65,7 +65,7 @@ await notify.database(user, { type: "welcome", title: "Welcome!" });
|
|
|
65
65
|
|
|
66
66
|
## Custom channels
|
|
67
67
|
|
|
68
|
-
After registering a custom channel (see
|
|
68
|
+
After registering a custom channel (see the `define-channel` topic), it's first-class:
|
|
69
69
|
|
|
70
70
|
```ts
|
|
71
71
|
await notify.discord(user, { content: "Build finished 🎉" });
|
|
@@ -89,7 +89,7 @@ await notify.slack(staff, { text: "Deploy starting" });
|
|
|
89
89
|
|
|
90
90
|
## See also
|
|
91
91
|
|
|
92
|
-
-
|
|
93
|
-
-
|
|
94
|
-
-
|
|
95
|
-
-
|
|
92
|
+
- The `notifications-basics` topic — front door + mental model.
|
|
93
|
+
- The `define-notification` topic — reusable multi-channel.
|
|
94
|
+
- The `define-channel` topic — register a custom channel.
|
|
95
|
+
- The `observe-notifications` topic — `sending` / `sent` / `failed` / `skipped` events.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: use-in-app
|
|
3
|
-
description: 'Use the `inApp` facade for the in-app/database channel''s READ side: `inApp.configure({ model: Notification })` or `{ repository: myRepo }` binds the store AND returns the channel; `inApp.list / listUnread / countUnread / markAsRead / markAsUnread` are RECIPIENT-SCOPED by construction (no IDOR — `markAsRead(user, id)` cannot flip another user''s row even with a foreign id). `countUnread` uses `RepositoryManager.countCached` for the badge fast-path. Triggers: `inApp.configure`, `inApp.list`, `inApp.listUnread`, `inApp.countUnread`, `inApp.markAsRead`, `inApp.markAsUnread`, `BaseNotificationsRepository`, `DatabaseNotification`, `NotificationsFilter`, "in-app notifications", "mark notification as read", "unread count", "notification badge", "IDOR-safe markAsRead"; typical import `import { inApp } from "@warlock.js/notifications"`. Skip: writing notifications (use `defineNotification` or `notify.database`) —
|
|
3
|
+
description: 'Use the `inApp` facade for the in-app/database channel''s READ side: `inApp.configure({ model: Notification })` or `{ repository: myRepo }` binds the store AND returns the channel; `inApp.list / listUnread / countUnread / markAsRead / markAsUnread` are RECIPIENT-SCOPED by construction (no IDOR — `markAsRead(user, id)` cannot flip another user''s row even with a foreign id). `countUnread` uses `RepositoryManager.countCached` for the badge fast-path. Triggers: `inApp.configure`, `inApp.list`, `inApp.listUnread`, `inApp.countUnread`, `inApp.markAsRead`, `inApp.markAsUnread`, `BaseNotificationsRepository`, `DatabaseNotification`, `NotificationsFilter`, "in-app notifications", "mark notification as read", "unread count", "notification badge", "IDOR-safe markAsRead"; typical import `import { inApp } from "@warlock.js/notifications"`. Skip: writing notifications (use `defineNotification` or `notify.database`) — the `define-notification` topic; the migration columns — the `write-notification-migration` topic; wiring it into config — the `configure-notifications` topic.'
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# `inApp` — in-app notification read API
|
|
@@ -166,7 +166,7 @@ database: inApp.configure({ repository: notificationsRepository }),
|
|
|
166
166
|
|
|
167
167
|
## See also
|
|
168
168
|
|
|
169
|
-
-
|
|
170
|
-
-
|
|
171
|
-
-
|
|
172
|
-
-
|
|
169
|
+
- The `notifications-basics` topic — package front door.
|
|
170
|
+
- The `configure-notifications` topic — wire `inApp.configure` into the config.
|
|
171
|
+
- The `write-notification-migration` topic — `notificationColumns(Notification)`, named from `columnMap`.
|
|
172
|
+
- The `define-notification` topic — create rows via the database channel.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: write-notification-migration
|
|
3
|
-
description: 'Create the notifications table with the `notificationColumns(Notification)` factory + cascade `Migration.create`. Column NAMES come from the model''s `columnMap` (recipient / tenant / readAt / isRead); the fixed columns are `type` / `title` / `body` / `payload` / `idempotency_key`. Set `{ idType: "bigint" }` for integer-key apps; UUID remains the default. Read-state follows columnMap presence: `readAt` → nullable timestamp, `isRead` → indexed boolean, both → both. Defaults to `user_id` + `read_at` when no columnMap. Spread + extend for extras (FK references, composite indexes). Triggers: `notificationColumns`, `Migration.create` for notifications, "create the notifications table", "notification migration", "add organization_id to notifications", `is_read` column, `read_at` column, `idempotency_key` column; typical import `import { Migration } from "@warlock.js/cascade"; import { notificationColumns } from "@warlock.js/notifications"`. Skip: generic cascade migration writing —
|
|
3
|
+
description: 'Create the notifications table with the `notificationColumns(Notification)` factory + cascade `Migration.create`. Column NAMES come from the model''s `columnMap` (recipient / tenant / readAt / isRead); the fixed columns are `type` / `title` / `body` / `payload` / `idempotency_key`. Set `{ idType: "bigint" }` for integer-key apps; UUID remains the default. Read-state follows columnMap presence: `readAt` → nullable timestamp, `isRead` → indexed boolean, both → both. Defaults to `user_id` + `read_at` when no columnMap. Spread + extend for extras (FK references, composite indexes). Triggers: `notificationColumns`, `Migration.create` for notifications, "create the notifications table", "notification migration", "add organization_id to notifications", `is_read` column, `read_at` column, `idempotency_key` column; typical import `import { Migration } from "@warlock.js/cascade"; import { notificationColumns } from "@warlock.js/notifications"`. Skip: generic cascade migration writing — the `write-migration` topic of the `warlock-js-cascade` skill; the model itself — the `use-in-app` topic.'
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Write the notifications migration
|
|
@@ -58,7 +58,7 @@ export default Migration.create(Notification, {
|
|
|
58
58
|
});
|
|
59
59
|
```
|
|
60
60
|
|
|
61
|
-
Mirror any extra column in the model `schema`. To make it filterable, add a method to a custom repository (see `use-in-app
|
|
61
|
+
Mirror any extra column in the model `schema`. To make it filterable, add a method to a custom repository (see the `use-in-app` topic).
|
|
62
62
|
|
|
63
63
|
## Column naming follows `columnMap`
|
|
64
64
|
|
|
@@ -71,10 +71,10 @@ class Notification extends DatabaseNotification {
|
|
|
71
71
|
// → notificationColumns(Notification) emits `userId` + `readAt`
|
|
72
72
|
```
|
|
73
73
|
|
|
74
|
-
Without a model passed, defaults to `user_id` + `read_at` (read_at-only). See
|
|
74
|
+
Without a model passed, defaults to `user_id` + `read_at` (read_at-only). See the `use-in-app` topic for the full `columnMap` shape.
|
|
75
75
|
|
|
76
76
|
## See also
|
|
77
77
|
|
|
78
|
-
-
|
|
79
|
-
-
|
|
80
|
-
-
|
|
78
|
+
- The `use-in-app` topic — the model + repository the migration backs.
|
|
79
|
+
- The `write-migration` topic of the `warlock-js-cascade` skill — generic Cascade migration form, `Migration.alter`, etc.
|
|
80
|
+
- The `manage-data-sources` topic of the `warlock-js-cascade` skill — multi-data-source setup.
|