@12-apps/notifications 4.10.3 → 4.12.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.
Files changed (51) hide show
  1. package/ADOPTING.md +31 -2
  2. package/dist/{chunk-BW723CX2.js → chunk-2IAHFIXS.js} +51 -38
  3. package/dist/chunk-2IAHFIXS.js.map +1 -0
  4. package/dist/{chunk-6W7INOYQ.js → chunk-CPQKKLPS.js} +1 -1
  5. package/dist/{chunk-6W7INOYQ.js.map → chunk-CPQKKLPS.js.map} +1 -1
  6. package/dist/{chunk-ZIR3ILFH.js → chunk-FBBPS2LT.js} +2 -2
  7. package/dist/{chunk-SWOWHIFE.js → chunk-GK6GSC2J.js} +37 -2
  8. package/dist/chunk-GK6GSC2J.js.map +1 -0
  9. package/dist/{chunk-H55A4LHG.js → chunk-PNY6S6WH.js} +39 -28
  10. package/dist/chunk-PNY6S6WH.js.map +1 -0
  11. package/dist/{chunk-WVRODNXQ.js → chunk-WZBX7YCE.js} +32 -15
  12. package/dist/{chunk-WVRODNXQ.js.map → chunk-WZBX7YCE.js.map} +1 -1
  13. package/dist/{create-api-notifications-CcPYrM3p.d.ts → create-api-notifications-BTudlaSC.d.ts} +15 -6
  14. package/dist/{create-web-notifications-DV3Y8k7e.d.ts → create-web-notifications-2xxbKnrW.d.ts} +61 -14
  15. package/dist/{generators-qAD4fNPq.d.ts → generators-CQYdJfB5.d.ts} +1 -1
  16. package/dist/hono/index.d.ts +5 -5
  17. package/dist/hono/index.js +3 -3
  18. package/dist/index.d.ts +3 -3
  19. package/dist/index.js +9 -3
  20. package/dist/{jobs--fex87-q.d.ts → jobs-CaovU4GM.d.ts} +1 -1
  21. package/dist/manifest/server.d.ts +6 -6
  22. package/dist/manifest/server.js +4 -4
  23. package/dist/manifest/web.d.ts +3 -3
  24. package/dist/manifest/web.js +2 -2
  25. package/dist/{panel-V2ULFC4Y.js → panel-MKI4PTNZ.js} +2 -2
  26. package/dist/{preferences-screen-SXUIGECY.js → preferences-screen-S3ZHX5LB.js} +2 -2
  27. package/dist/react/index.d.ts +4 -4
  28. package/dist/react/index.js +2 -2
  29. package/dist/server/index.d.ts +7 -7
  30. package/dist/server/index.js +4 -4
  31. package/dist/{types-BlqZkCWZ.d.ts → types-H_aFzLA0.d.ts} +33 -0
  32. package/dist/web-push/index.d.ts +2 -2
  33. package/dist/{web-push-Dnyaha2z.d.ts → web-push-C6U-5JCV.d.ts} +1 -1
  34. package/dist/{wire-BG1kuoXX.d.ts → wire-Bn6aA2nL.d.ts} +65 -2
  35. package/package.json +2 -2
  36. package/src/index.ts +4 -0
  37. package/src/preferences-core.ts +107 -0
  38. package/src/react/bell-badge.ts +147 -0
  39. package/src/react/bell-button.tsx +19 -31
  40. package/src/react/create-web-notifications.tsx +55 -6
  41. package/src/react/hooks.ts +61 -13
  42. package/src/react/index.ts +13 -0
  43. package/src/server/preferences.ts +29 -9
  44. package/src/server/router.ts +37 -7
  45. package/src/types.ts +33 -0
  46. package/dist/chunk-BW723CX2.js.map +0 -1
  47. package/dist/chunk-H55A4LHG.js.map +0 -1
  48. package/dist/chunk-SWOWHIFE.js.map +0 -1
  49. /package/dist/{chunk-ZIR3ILFH.js.map → chunk-FBBPS2LT.js.map} +0 -0
  50. /package/dist/{panel-V2ULFC4Y.js.map → panel-MKI4PTNZ.js.map} +0 -0
  51. /package/dist/{preferences-screen-SXUIGECY.js.map → preferences-screen-S3ZHX5LB.js.map} +0 -0
@@ -1,7 +1,7 @@
1
- import { N as NotificationGeneratorRegistry } from './generators-qAD4fNPq.js';
2
- import { b as NotificationRow, C as ChannelMatrix, a as ChannelRow, L as ListNotificationsResult, h as NotificationsCopySource, c as NotificationWireMessages } from './wire-BG1kuoXX.js';
3
- import { c as NotificationChannel, D as DeliveryStatus, g as NotificationLogger, j as NotificationTransport, d as NotificationContent, b as NotificationCategory, e as NotificationEvent, N as NotificationGenerator } from './types-BlqZkCWZ.js';
4
- import { e as WebPushSubscriptionSource, D as DriverDeclarationBase, a as WebPushDriverDeclaration, c as WebPushSender } from './web-push-Dnyaha2z.js';
1
+ import { N as NotificationGeneratorRegistry } from './generators-CQYdJfB5.js';
2
+ import { b as NotificationRow, C as ChannelMatrix, a as ChannelRow, T as TypeChannelRules, L as ListNotificationsResult, k as NotificationsCopySource, c as NotificationWireMessages } from './wire-Bn6aA2nL.js';
3
+ import { c as NotificationChannel, D as DeliveryStatus, g as NotificationLogger, j as NotificationTransport, d as NotificationContent, b as NotificationCategory, e as NotificationEvent, N as NotificationGenerator } from './types-H_aFzLA0.js';
4
+ import { e as WebPushSubscriptionSource, D as DriverDeclarationBase, a as WebPushDriverDeclaration, c as WebPushSender } from './web-push-C6U-5JCV.js';
5
5
  import { d as EmailChromeCopy, f as EmailTheme } from './template-Dlt5S54z.js';
6
6
 
7
7
  /**
@@ -600,8 +600,17 @@ interface NotificationPreferenceStore {
600
600
  get(userId: string): Promise<ChannelMatrix>;
601
601
  /** Persist explicit choices for any subset of categories/toggles. */
602
602
  save(userId: string, input: Partial<Record<NotificationCategory, Partial<ChannelRow>>>): Promise<void>;
603
- /** The channels enabled for one (user, category) — the router's gate. */
604
- enabledChannels(userId: string, category: NotificationCategory): Promise<NotificationChannel[]>;
603
+ /**
604
+ * The channels enabled for one (user, category) — the router's gate.
605
+ *
606
+ * `rules` carries the emitting TYPE's own declarations (availability and
607
+ * per-type defaults). It is OPTIONAL, and that is what keeps a host store
608
+ * written before it working: a two-parameter implementation is assignable to
609
+ * this signature unchanged, and omitting the argument asks the same question
610
+ * the store has always answered. The router does not rely on a store
611
+ * honouring it — it caps the result by availability itself.
612
+ */
613
+ enabledChannels(userId: string, category: NotificationCategory, rules?: TypeChannelRules): Promise<NotificationChannel[]>;
605
614
  }
606
615
 
607
616
  /**
@@ -1,6 +1,6 @@
1
1
  import { ReactNode, JSX, ComponentType } from 'react';
2
- import { L as ListNotificationsResult, a as ChannelRow, I as InboxNotification, N as NotificationMessages } from './wire-BG1kuoXX.js';
3
- import { c as NotificationChannel } from './types-BlqZkCWZ.js';
2
+ import { L as ListNotificationsResult, a as ChannelRow, I as InboxNotification, N as NotificationMessages } from './wire-Bn6aA2nL.js';
3
+ import { c as NotificationChannel } from './types-H_aFzLA0.js';
4
4
  import { b as LiveActivity } from './live-DYxEFO49.js';
5
5
 
6
6
  /**
@@ -196,17 +196,20 @@ type NotificationsSubscribe = (onHint: () => void) => () => void;
196
196
  */
197
197
  type NotificationsSignalHook = (onHint: () => void) => void;
198
198
  declare function useInboxState(store: InboxStore): InboxState;
199
- /**
200
- * The bell badge number: pushed while a subscription is live, polled otherwise.
201
- *
202
- * `enabled` gates the poll AND the subscription. A signed-out header still
203
- * mounts the bell, and there is nothing for it to hear.
204
- */
205
- declare function useUnreadCount(store: InboxStore, options?: {
199
+ /** What both badge hooks below take, and what the bell passes them. */
200
+ interface BadgeSyncOptions {
206
201
  enabled?: boolean;
207
202
  subscribe?: NotificationsSubscribe;
208
203
  useSignal?: NotificationsSignalHook;
209
- }): number;
204
+ }
205
+ /**
206
+ * The bell badge number, for a host with its own trigger chrome.
207
+ *
208
+ * A host that also publishes live activities wants `useBellBadge` from the
209
+ * factory instead — this one counts inbox rows and knows nothing about what is
210
+ * happening right now.
211
+ */
212
+ declare function useUnreadCount(store: InboxStore, options?: BadgeSyncOptions): number;
210
213
  /** The panel's list — only fetches while the panel is open. */
211
214
  declare function useInboxList(store: InboxStore, open: boolean): InboxState;
212
215
 
@@ -285,10 +288,29 @@ interface LiveActivitiesConfig {
285
288
  renderIcon?: (activity: LiveActivity) => ReactNode;
286
289
  }
287
290
 
291
+ /** The bell's whole state — see the file docblock for what each half means. */
292
+ interface BellBadge {
293
+ /** What the badge shows. `0` renders no badge at all. */
294
+ count: number;
295
+ /**
296
+ * Whether any of it has arrived or moved since the reader last looked.
297
+ *
298
+ * The trigger paints this as its accent colour; a host with its own chrome
299
+ * decides how to say it, but it should be a difference somebody notices.
300
+ */
301
+ hasNew: boolean;
302
+ }
303
+
288
304
  /**
289
305
  * Bare bell trigger with the live unread badge — for hosts that do not already
290
- * have a styled icon-button slot. A host with its own trigger chrome uses
291
- * `useUnreadCount` + `Panel` directly.
306
+ * have a styled icon-button slot.
307
+ *
308
+ * A host with its own trigger chrome uses `useBellBadge` + `Panel` directly,
309
+ * and NOT `useUnreadCount`, which is what this sentence used to say. That
310
+ * advice was taken, verbatim and by name, by a storefront whose header needed
311
+ * its own trigger — and it gave that storefront a bell showing nothing at all
312
+ * while a live pedido sat in the panel it opens, because `useUnreadCount`
313
+ * counts inbox rows and knows nothing about what is happening right now.
292
314
  */
293
315
 
294
316
  interface BellButtonProps {
@@ -434,10 +456,35 @@ interface WebNotifications {
434
456
  enabled?: boolean;
435
457
  onNavigate?: (link: string) => void;
436
458
  }>;
437
- /** The badge number, for a host with its own trigger chrome. */
459
+ /**
460
+ * The unread INBOX count.
461
+ *
462
+ * For a host with its own trigger chrome only when that host configured no
463
+ * live activities — otherwise it is a bell that ignores everything happening
464
+ * right now, and `useBellBadge` below is the door. Still the right hook for
465
+ * anything that genuinely wants "how many unread rows".
466
+ */
438
467
  useUnreadCount: (options?: {
439
468
  enabled?: boolean;
440
469
  }) => number;
470
+ /**
471
+ * The badge's NUMBER AND TONE, for a host with its own trigger chrome.
472
+ *
473
+ * What `useUnreadCount` should have been for a host that also configured live
474
+ * activities, and the reason it is a second door rather than a change to that
475
+ * one: a count alone cannot express a bell, because a live entry is present
476
+ * without being news (see `./bell-badge`). A host that renders
477
+ * `useUnreadCount` in its own chrome gets a badge that ignores everything
478
+ * happening right now — which is not a subtle wrongness, it is the pinned
479
+ * pedido on screen going uncounted.
480
+ *
481
+ * Identical to what this package's own `BellButton` draws, because it is the
482
+ * hook that bell uses. Without live activities configured it is
483
+ * `useUnreadCount` plus `hasNew: count > 0`.
484
+ */
485
+ useBellBadge: (options?: {
486
+ enabled?: boolean;
487
+ }) => BellBadge;
441
488
  /** The shared client state, for host glue. */
442
489
  store: InboxStore;
443
490
  /** The bound wire client. */
@@ -447,4 +494,4 @@ interface WebNotifications {
447
494
  }
448
495
  declare function createWebNotifications(config: NotificationsWebConfig): WebNotifications;
449
496
 
450
- export { BADGE_POLL_MS as B, type InboxListStatus as I, type LiveActivitiesConfig as L, type NotificationsApiClient as N, PAGE_SIZE as P, type WebNotifications as W, BADGE_RECONCILE_MS as a, type BellButtonProps as b, createWebNotifications as c, type InboxState as d, type InboxStore as e, type LiveActivitiesHook as f, type LiveActivityMessages as g, NotificationsHttpError as h, type NotificationsPanelProps as i, type NotificationsResult as j, type NotificationsSignalHook as k, type NotificationsSubscribe as l, type NotificationsTransport as m, type NotificationsWebConfig as n, type PreferencesPayload as o, type PreferencesScreenProps as p, type PushRegistrationPayload as q, type WebPushPlatformHint as r, type WebPushSetupConfig as s, createInboxStore as t, createNotificationsApiClient as u, httpNotificationsTransport as v, useInboxList as w, useInboxState as x, useUnreadCount as y };
497
+ export { useUnreadCount as A, BADGE_POLL_MS as B, type InboxListStatus as I, type LiveActivitiesConfig as L, type NotificationsApiClient as N, PAGE_SIZE as P, type WebNotifications as W, BADGE_RECONCILE_MS as a, type BadgeSyncOptions as b, createWebNotifications as c, type BellBadge as d, type BellButtonProps as e, type InboxState as f, type InboxStore as g, type LiveActivitiesHook as h, type LiveActivityMessages as i, NotificationsHttpError as j, type NotificationsPanelProps as k, type NotificationsResult as l, type NotificationsSignalHook as m, type NotificationsSubscribe as n, type NotificationsTransport as o, type NotificationsWebConfig as p, type PreferencesPayload as q, type PreferencesScreenProps as r, type PushRegistrationPayload as s, type WebPushPlatformHint as t, type WebPushSetupConfig as u, createInboxStore as v, createNotificationsApiClient as w, httpNotificationsTransport as x, useInboxList as y, useInboxState as z };
@@ -1,4 +1,4 @@
1
- import { N as NotificationGenerator } from './types-BlqZkCWZ.js';
1
+ import { N as NotificationGenerator } from './types-H_aFzLA0.js';
2
2
 
3
3
  /**
4
4
  * Generator registry: one {@link NotificationGenerator} per event `type`.
@@ -1,9 +1,9 @@
1
1
  import { Hono, Context } from 'hono';
2
- import { A as ApiNotifications, N as NotificationsServerConfig, a as NotificationsActor } from '../create-api-notifications-CcPYrM3p.js';
3
- import '../generators-qAD4fNPq.js';
4
- import '../types-BlqZkCWZ.js';
5
- import '../wire-BG1kuoXX.js';
6
- import '../web-push-Dnyaha2z.js';
2
+ import { A as ApiNotifications, N as NotificationsServerConfig, a as NotificationsActor } from '../create-api-notifications-BTudlaSC.js';
3
+ import '../generators-CQYdJfB5.js';
4
+ import '../types-H_aFzLA0.js';
5
+ import '../wire-Bn6aA2nL.js';
6
+ import '../web-push-C6U-5JCV.js';
7
7
  import '../template-Dlt5S54z.js';
8
8
 
9
9
  /**
@@ -1,12 +1,12 @@
1
1
  import {
2
2
  createApiNotifications
3
- } from "../chunk-WVRODNXQ.js";
4
- import "../chunk-SWOWHIFE.js";
3
+ } from "../chunk-WZBX7YCE.js";
4
+ import "../chunk-GK6GSC2J.js";
5
5
  import {
6
6
  messagesOf
7
7
  } from "../chunk-M2TVBVH2.js";
8
8
  import "../chunk-RTURLH5U.js";
9
- import "../chunk-6W7INOYQ.js";
9
+ import "../chunk-CPQKKLPS.js";
10
10
  import "../chunk-EKUSNUBT.js";
11
11
  import {
12
12
  __name
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
- export { D as DeliveryStatus, a as NOTIFICATION_CHANNELS, b as NotificationCategory, c as NotificationChannel, d as NotificationContent, e as NotificationEvent, f as NotificationGenerateContext, N as NotificationGenerator, g as NotificationLogger, h as NotificationRecipient, i as NotificationTaxonomy, j as NotificationTransport, T as TransportRecipient, t as taxonomyOf } from './types-BlqZkCWZ.js';
2
- export { N as NotificationGeneratorRegistry, c as createGeneratorRegistry } from './generators-qAD4fNPq.js';
3
- export { C as ChannelMatrix, a as ChannelRow, D as DEFAULT_CHANNEL_ROW, I as InboxNotification, L as ListNotificationsResult, N as NotificationMessages, b as NotificationRow, c as NotificationWireMessages, d as defaultChannelMatrix, e as enabledChannelsOf, i as inboxWire, m as mergeChoices, f as mergeStoredRow, g as messagesOf } from './wire-BG1kuoXX.js';
1
+ export { D as DeliveryStatus, a as NOTIFICATION_CHANNELS, b as NotificationCategory, c as NotificationChannel, d as NotificationContent, e as NotificationEvent, f as NotificationGenerateContext, N as NotificationGenerator, g as NotificationLogger, h as NotificationRecipient, i as NotificationTaxonomy, j as NotificationTransport, T as TransportRecipient, t as taxonomyOf } from './types-H_aFzLA0.js';
2
+ export { N as NotificationGeneratorRegistry, c as createGeneratorRegistry } from './generators-CQYdJfB5.js';
3
+ export { C as ChannelMatrix, a as ChannelRow, D as DEFAULT_CHANNEL_ROW, I as InboxNotification, L as ListNotificationsResult, N as NotificationMessages, b as NotificationRow, c as NotificationWireMessages, T as TypeChannelRules, d as availableChannelsOf, e as capToAvailable, f as defaultChannelMatrix, g as enabledChannelsOf, i as inboxWire, m as mergeChoices, h as mergeStoredRow, j as messagesOf, r as resolveTypeChannels } from './wire-Bn6aA2nL.js';
4
4
  export { L as LIVE_PUSH_TAG_PREFIX, a as LIVE_SUBJECT_KEY, b as LiveActivity, c as LiveActivityLane, d as LiveActivityStep, l as liveActivityLane, e as livePushTag } from './live-DYxEFO49.js';
5
5
 
6
6
  /** Thrown by `notify` when no generator is registered for the event type. */
package/dist/index.js CHANGED
@@ -2,14 +2,17 @@ import {
2
2
  DEFAULT_CHANNEL_ROW,
3
3
  UnknownNotificationRecipientError,
4
4
  UnknownNotificationTypeError,
5
+ availableChannelsOf,
6
+ capToAvailable,
5
7
  createGeneratorRegistry,
6
8
  defaultChannelMatrix,
7
9
  enabledChannelsOf,
8
10
  inboxWire,
9
11
  mergeChoices,
10
12
  mergeStoredRow,
11
- normalizePhoneE164
12
- } from "./chunk-SWOWHIFE.js";
13
+ normalizePhoneE164,
14
+ resolveTypeChannels
15
+ } from "./chunk-GK6GSC2J.js";
13
16
  import {
14
17
  messagesOf
15
18
  } from "./chunk-M2TVBVH2.js";
@@ -22,7 +25,7 @@ import {
22
25
  import {
23
26
  NOTIFICATION_CHANNELS,
24
27
  taxonomyOf
25
- } from "./chunk-6W7INOYQ.js";
28
+ } from "./chunk-CPQKKLPS.js";
26
29
  import "./chunk-7QVYU63E.js";
27
30
  export {
28
31
  DEFAULT_CHANNEL_ROW,
@@ -31,6 +34,8 @@ export {
31
34
  NOTIFICATION_CHANNELS,
32
35
  UnknownNotificationRecipientError,
33
36
  UnknownNotificationTypeError,
37
+ availableChannelsOf,
38
+ capToAvailable,
34
39
  createGeneratorRegistry,
35
40
  defaultChannelMatrix,
36
41
  enabledChannelsOf,
@@ -41,6 +46,7 @@ export {
41
46
  mergeStoredRow,
42
47
  messagesOf,
43
48
  normalizePhoneE164,
49
+ resolveTypeChannels,
44
50
  taxonomyOf
45
51
  };
46
52
  //# sourceMappingURL=index.js.map
@@ -1,5 +1,5 @@
1
1
  import { WireJobBlueprint } from '@12-apps/wiring';
2
- import { A as ApiNotifications } from './create-api-notifications-CcPYrM3p.js';
2
+ import { A as ApiNotifications } from './create-api-notifications-BTudlaSC.js';
3
3
 
4
4
  /**
5
5
  * The two background jobs getting a message out actually needs.
@@ -1,13 +1,13 @@
1
1
  import { EmailPreviewRoute } from '../email/previews/index.js';
2
2
  import * as _12_apps_wiring from '@12-apps/wiring';
3
3
  import { WireRequest } from '@12-apps/wiring';
4
- import { N as NotificationsServerConfig, A as ApiNotifications, b as NotificationsRoute } from '../create-api-notifications-CcPYrM3p.js';
5
- import { N as NotificationsJobDeps } from '../jobs--fex87-q.js';
4
+ import { N as NotificationsServerConfig, A as ApiNotifications, b as NotificationsRoute } from '../create-api-notifications-BTudlaSC.js';
5
+ import { N as NotificationsJobDeps } from '../jobs-CaovU4GM.js';
6
6
  import { E as EmailPreviewsConfig } from '../catalog-BOVZ2b-b.js';
7
- import '../generators-qAD4fNPq.js';
8
- import '../types-BlqZkCWZ.js';
9
- import '../wire-BG1kuoXX.js';
10
- import '../web-push-Dnyaha2z.js';
7
+ import '../generators-CQYdJfB5.js';
8
+ import '../types-H_aFzLA0.js';
9
+ import '../wire-Bn6aA2nL.js';
10
+ import '../web-push-C6U-5JCV.js';
11
11
  import '../template-Dlt5S54z.js';
12
12
 
13
13
  /** One `NotificationsRoute` as the wiring contract reads it. */
@@ -3,14 +3,14 @@ import {
3
3
  } from "../chunk-FTFLU6KH.js";
4
4
  import {
5
5
  NOTIFICATIONS_JOBS
6
- } from "../chunk-ZIR3ILFH.js";
6
+ } from "../chunk-FBBPS2LT.js";
7
7
  import {
8
8
  createApiNotifications
9
- } from "../chunk-WVRODNXQ.js";
10
- import "../chunk-SWOWHIFE.js";
9
+ } from "../chunk-WZBX7YCE.js";
10
+ import "../chunk-GK6GSC2J.js";
11
11
  import "../chunk-M2TVBVH2.js";
12
12
  import "../chunk-RTURLH5U.js";
13
- import "../chunk-6W7INOYQ.js";
13
+ import "../chunk-CPQKKLPS.js";
14
14
  import "../chunk-EKUSNUBT.js";
15
15
  import {
16
16
  __name
@@ -1,8 +1,8 @@
1
1
  import { c as createEmailPreviewScreen } from '../preview-screen-DYJRAnAY.js';
2
- import { c as createWebNotifications } from '../create-web-notifications-DV3Y8k7e.js';
2
+ import { c as createWebNotifications } from '../create-web-notifications-2xxbKnrW.js';
3
3
  import 'react';
4
- import '../wire-BG1kuoXX.js';
5
- import '../types-BlqZkCWZ.js';
4
+ import '../wire-Bn6aA2nL.js';
5
+ import '../types-H_aFzLA0.js';
6
6
  import '../live-DYxEFO49.js';
7
7
 
8
8
  /**
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  createWebNotifications
3
- } from "../chunk-H55A4LHG.js";
4
- import "../chunk-BW723CX2.js";
3
+ } from "../chunk-PNY6S6WH.js";
4
+ import "../chunk-2IAHFIXS.js";
5
5
  import {
6
6
  createEmailPreviewScreen
7
7
  } from "../chunk-KEYRE245.js";
@@ -5,7 +5,7 @@ import {
5
5
  import {
6
6
  BellIcon,
7
7
  useInboxList
8
- } from "./chunk-BW723CX2.js";
8
+ } from "./chunk-2IAHFIXS.js";
9
9
  import "./chunk-RTURLH5U.js";
10
10
  import {
11
11
  __name
@@ -295,4 +295,4 @@ __name(NotificationsPanel, "NotificationsPanel");
295
295
  export {
296
296
  NotificationsPanel
297
297
  };
298
- //# sourceMappingURL=panel-V2ULFC4Y.js.map
298
+ //# sourceMappingURL=panel-MKI4PTNZ.js.map
@@ -4,7 +4,7 @@ import {
4
4
  } from "./chunk-CQZMTFPY.js";
5
5
  import {
6
6
  NOTIFICATION_CHANNELS
7
- } from "./chunk-6W7INOYQ.js";
7
+ } from "./chunk-CPQKKLPS.js";
8
8
  import {
9
9
  __name
10
10
  } from "./chunk-7QVYU63E.js";
@@ -291,4 +291,4 @@ __name(PreferencesScreen, "PreferencesScreen");
291
291
  export {
292
292
  PreferencesScreen
293
293
  };
294
- //# sourceMappingURL=preferences-screen-SXUIGECY.js.map
294
+ //# sourceMappingURL=preferences-screen-S3ZHX5LB.js.map
@@ -1,10 +1,10 @@
1
- import { L as LiveActivitiesConfig, N as NotificationsApiClient } from '../create-web-notifications-DV3Y8k7e.js';
2
- export { B as BADGE_POLL_MS, a as BADGE_RECONCILE_MS, b as BellButtonProps, I as InboxListStatus, d as InboxState, e as InboxStore, f as LiveActivitiesHook, g as LiveActivityMessages, h as NotificationsHttpError, i as NotificationsPanelProps, j as NotificationsResult, k as NotificationsSignalHook, l as NotificationsSubscribe, m as NotificationsTransport, n as NotificationsWebConfig, P as PAGE_SIZE, o as PreferencesPayload, p as PreferencesScreenProps, q as PushRegistrationPayload, W as WebNotifications, r as WebPushPlatformHint, s as WebPushSetupConfig, t as createInboxStore, u as createNotificationsApiClient, c as createWebNotifications, v as httpNotificationsTransport, w as useInboxList, x as useInboxState, y as useUnreadCount } from '../create-web-notifications-DV3Y8k7e.js';
1
+ import { L as LiveActivitiesConfig, N as NotificationsApiClient } from '../create-web-notifications-2xxbKnrW.js';
2
+ export { B as BADGE_POLL_MS, a as BADGE_RECONCILE_MS, b as BadgeSyncOptions, d as BellBadge, e as BellButtonProps, I as InboxListStatus, f as InboxState, g as InboxStore, h as LiveActivitiesHook, i as LiveActivityMessages, j as NotificationsHttpError, k as NotificationsPanelProps, l as NotificationsResult, m as NotificationsSignalHook, n as NotificationsSubscribe, o as NotificationsTransport, p as NotificationsWebConfig, P as PAGE_SIZE, q as PreferencesPayload, r as PreferencesScreenProps, s as PushRegistrationPayload, W as WebNotifications, t as WebPushPlatformHint, u as WebPushSetupConfig, v as createInboxStore, w as createNotificationsApiClient, c as createWebNotifications, x as httpNotificationsTransport, y as useInboxList, z as useInboxState, A as useUnreadCount } from '../create-web-notifications-2xxbKnrW.js';
3
3
  import { JSX, ReactNode } from 'react';
4
4
  import { b as LiveActivity } from '../live-DYxEFO49.js';
5
5
  export { c as LiveActivityLane, d as LiveActivityStep, l as liveActivityLane } from '../live-DYxEFO49.js';
6
- import { N as NotificationMessages } from '../wire-BG1kuoXX.js';
7
- import '../types-BlqZkCWZ.js';
6
+ import { N as NotificationMessages } from '../wire-Bn6aA2nL.js';
7
+ import '../types-H_aFzLA0.js';
8
8
 
9
9
  /**
10
10
  * What the reader has already been shown, so the bell can say NEW rather than
@@ -3,7 +3,7 @@ import {
3
3
  createNotificationsApiClient,
4
4
  createWebNotifications,
5
5
  httpNotificationsTransport
6
- } from "../chunk-H55A4LHG.js";
6
+ } from "../chunk-PNY6S6WH.js";
7
7
  import {
8
8
  LiveSection,
9
9
  relativeTime
@@ -17,7 +17,7 @@ import {
17
17
  useInboxList,
18
18
  useInboxState,
19
19
  useUnreadCount
20
- } from "../chunk-BW723CX2.js";
20
+ } from "../chunk-2IAHFIXS.js";
21
21
  import {
22
22
  disableWebPush,
23
23
  enableWebPush,
@@ -1,11 +1,11 @@
1
- import { A as ApiNotifications } from '../create-api-notifications-CcPYrM3p.js';
2
- export { C as CommittedNotification, E as EMAIL_DRIVERS, c as EmailDriver, d as EmailDriverDeclaration, e as EmailMessage, f as ExtraDrivers, L as ListNotificationsInput, g as NotificationAudienceDirectory, h as NotificationChannelPolicy, i as NotificationCommittedListener, j as NotificationContactDirectory, k as NotificationCreateData, l as NotificationDelegate, m as NotificationDeliveryDelegate, n as NotificationDeliveryRow, o as NotificationDeliveryWhere, p as NotificationDispatchScheduler, q as NotificationInboxStore, r as NotificationPageAfter, s as NotificationPreferenceDelegate, t as NotificationPreferenceRow, u as NotificationPreferenceStore, v as NotificationRouter, w as NotificationWhere, a as NotificationsActor, x as NotificationsApiError, y as NotificationsDb, z as NotificationsDbClient, B as NotificationsDbProvider, D as NotificationsRequest, F as NotificationsResponse, b as NotificationsRoute, N as NotificationsServerConfig, G as NotifyByPermission, H as NotifyOptions, I as NotifyResult, P as PermissionNotificationResult, J as PermissionNotificationSkip, K as PushSubscriptionDelegate, M as PushSubscriptionInput, O as PushSubscriptionRow, Q as PushSubscriptionStore, S as SMS_DRIVERS, R as SmsDriver, T as SmsDriverDeclaration, U as SmsMessage, V as TransportDeclaration, W as TransportRegistry, X as WHATSAPP_DRIVERS, Y as WhatsAppDriver, Z as WhatsAppDriverDeclaration, _ as WhatsAppMessage, $ as createApiNotifications, a0 as createTransportRegistry, a1 as emailTransport, a2 as foldApiError, a3 as formatEmail, a4 as formatSms, a5 as formatWhatsApp, a6 as ok, a7 as smsTransport, a8 as whatsAppTransport } from '../create-api-notifications-CcPYrM3p.js';
3
- export { D as DriverDeclarationBase, F as FetchImpl, N as NotificationProviderError, W as WEB_PUSH_DRIVERS, a as WebPushDriverDeclaration, b as WebPushMessage, c as WebPushSender, d as WebPushSubscription, e as WebPushSubscriptionSource, f as absoluteLink, g as formatWebPush, w as webPushTransport } from '../web-push-Dnyaha2z.js';
1
+ import { A as ApiNotifications } from '../create-api-notifications-BTudlaSC.js';
2
+ export { C as CommittedNotification, E as EMAIL_DRIVERS, c as EmailDriver, d as EmailDriverDeclaration, e as EmailMessage, f as ExtraDrivers, L as ListNotificationsInput, g as NotificationAudienceDirectory, h as NotificationChannelPolicy, i as NotificationCommittedListener, j as NotificationContactDirectory, k as NotificationCreateData, l as NotificationDelegate, m as NotificationDeliveryDelegate, n as NotificationDeliveryRow, o as NotificationDeliveryWhere, p as NotificationDispatchScheduler, q as NotificationInboxStore, r as NotificationPageAfter, s as NotificationPreferenceDelegate, t as NotificationPreferenceRow, u as NotificationPreferenceStore, v as NotificationRouter, w as NotificationWhere, a as NotificationsActor, x as NotificationsApiError, y as NotificationsDb, z as NotificationsDbClient, B as NotificationsDbProvider, D as NotificationsRequest, F as NotificationsResponse, b as NotificationsRoute, N as NotificationsServerConfig, G as NotifyByPermission, H as NotifyOptions, I as NotifyResult, P as PermissionNotificationResult, J as PermissionNotificationSkip, K as PushSubscriptionDelegate, M as PushSubscriptionInput, O as PushSubscriptionRow, Q as PushSubscriptionStore, S as SMS_DRIVERS, R as SmsDriver, T as SmsDriverDeclaration, U as SmsMessage, V as TransportDeclaration, W as TransportRegistry, X as WHATSAPP_DRIVERS, Y as WhatsAppDriver, Z as WhatsAppDriverDeclaration, _ as WhatsAppMessage, $ as createApiNotifications, a0 as createTransportRegistry, a1 as emailTransport, a2 as foldApiError, a3 as formatEmail, a4 as formatSms, a5 as formatWhatsApp, a6 as ok, a7 as smsTransport, a8 as whatsAppTransport } from '../create-api-notifications-BTudlaSC.js';
3
+ export { D as DriverDeclarationBase, F as FetchImpl, N as NotificationProviderError, W as WEB_PUSH_DRIVERS, a as WebPushDriverDeclaration, b as WebPushMessage, c as WebPushSender, d as WebPushSubscription, e as WebPushSubscriptionSource, f as absoluteLink, g as formatWebPush, w as webPushTransport } from '../web-push-C6U-5JCV.js';
4
4
  import { NotifyPort } from '@12-apps/wiring/ports';
5
- export { a as NOTIFICATIONS_DRAIN_CRON, b as NOTIFICATIONS_DRAIN_LEASE_MS, c as NOTIFICATIONS_JOBS, d as NOTIFICATIONS_SWEEP_QUEUE, N as NotificationsJobDeps } from '../jobs--fex87-q.js';
6
- import '../generators-qAD4fNPq.js';
7
- import '../types-BlqZkCWZ.js';
8
- import '../wire-BG1kuoXX.js';
5
+ export { a as NOTIFICATIONS_DRAIN_CRON, b as NOTIFICATIONS_DRAIN_LEASE_MS, c as NOTIFICATIONS_JOBS, d as NOTIFICATIONS_SWEEP_QUEUE, N as NotificationsJobDeps } from '../jobs-CaovU4GM.js';
6
+ import '../generators-CQYdJfB5.js';
7
+ import '../types-H_aFzLA0.js';
8
+ import '../wire-Bn6aA2nL.js';
9
9
  import '../template-Dlt5S54z.js';
10
10
  import '@12-apps/wiring';
11
11
 
@@ -4,7 +4,7 @@ import {
4
4
  NOTIFICATIONS_JOBS,
5
5
  NOTIFICATIONS_SWEEP_QUEUE,
6
6
  wireNotifyPort
7
- } from "../chunk-ZIR3ILFH.js";
7
+ } from "../chunk-FBBPS2LT.js";
8
8
  import {
9
9
  EMAIL_DRIVERS,
10
10
  NotificationProviderError,
@@ -25,11 +25,11 @@ import {
25
25
  smsTransport,
26
26
  webPushTransport,
27
27
  whatsAppTransport
28
- } from "../chunk-WVRODNXQ.js";
29
- import "../chunk-SWOWHIFE.js";
28
+ } from "../chunk-WZBX7YCE.js";
29
+ import "../chunk-GK6GSC2J.js";
30
30
  import "../chunk-M2TVBVH2.js";
31
31
  import "../chunk-RTURLH5U.js";
32
- import "../chunk-6W7INOYQ.js";
32
+ import "../chunk-CPQKKLPS.js";
33
33
  import "../chunk-EKUSNUBT.js";
34
34
  import "../chunk-7QVYU63E.js";
35
35
  export {
@@ -122,6 +122,39 @@ interface NotificationGenerator<TPayload = unknown> {
122
122
  type: string;
123
123
  /** The preference category the router gates this type's fan-out on. */
124
124
  category: NotificationCategory;
125
+ /**
126
+ * The channels this type may EVER use — AVAILABILITY, not preference.
127
+ *
128
+ * Absent (the default) means every channel, which is what every generator
129
+ * written before this field keeps doing. A declared list is a hard cap the
130
+ * router applies AFTER preferences: a user whose stored row explicitly
131
+ * enables a channel this type does not offer still does not get it, because
132
+ * the channel was never on offer for this message. That is the difference
133
+ * from {@link channelDefaults} — a default is a starting point a user can
134
+ * move, availability is the set of starting points that exist.
135
+ *
136
+ * It is per TYPE because the category is too coarse to say it: the three
137
+ * comanda kitchen messages sit in `orders` next to `order.paid`, and a diner
138
+ * three metres from the food wants a push, not correspondence, while the
139
+ * buyer of a delivery order still wants the e-mail. Declaring `["WEB_PUSH"]`
140
+ * on the mesa messages says that without splitting the category or taking
141
+ * `order.paid`'s e-mail away with it.
142
+ *
143
+ * Unknown entries are ignored and order is irrelevant — the list is coerced
144
+ * onto {@link NOTIFICATION_CHANNELS}. An EMPTY list is legal and means no
145
+ * transport channel at all; the inbox record is written regardless, because
146
+ * the inbox is not a channel a user opts out of.
147
+ */
148
+ channels?: readonly NotificationChannel[];
149
+ /**
150
+ * This type's starting toggles, overriding the category's defaults for the
151
+ * channels it names and only where the user has made NO explicit choice.
152
+ *
153
+ * Stored preferences still win over it — that is what makes it a default.
154
+ * To take a channel away from a user who asked for it, declare
155
+ * {@link channels} instead.
156
+ */
157
+ channelDefaults?: Partial<Record<NotificationChannel, boolean>>;
125
158
  /**
126
159
  * Render this event's content for ONE recipient.
127
160
  *
@@ -1,5 +1,5 @@
1
- import { c as WebPushSender } from '../web-push-Dnyaha2z.js';
2
- import '../types-BlqZkCWZ.js';
1
+ import { c as WebPushSender } from '../web-push-C6U-5JCV.js';
2
+ import '../types-H_aFzLA0.js';
3
3
 
4
4
  /**
5
5
  * `@12-apps/notifications/web-push` — the VAPID sender, behind its own subpath.
@@ -1,4 +1,4 @@
1
- import { g as NotificationLogger, d as NotificationContent, j as NotificationTransport } from './types-BlqZkCWZ.js';
1
+ import { g as NotificationLogger, d as NotificationContent, j as NotificationTransport } from './types-H_aFzLA0.js';
2
2
 
3
3
  /**
4
4
  * The DRIVER port — the reason a host adds a vendor with a config entry and no
@@ -1,4 +1,4 @@
1
- import { b as NotificationCategory, c as NotificationChannel } from './types-BlqZkCWZ.js';
1
+ import { b as NotificationCategory, c as NotificationChannel } from './types-H_aFzLA0.js';
2
2
 
3
3
  /**
4
4
  * Every sentence this package can say to a USER, stated by the HOST.
@@ -163,6 +163,69 @@ declare function defaultChannelMatrix(categories: readonly NotificationCategory[
163
163
  * a data migration for every existing row on every channel that ever ships.
164
164
  */
165
165
  declare function mergeStoredRow(stored: unknown, base: ChannelRow): ChannelRow;
166
+ /**
167
+ * What ONE notification type says about its own channels, independent of the
168
+ * user's category preferences: {@link NotificationGenerator.channels} (the hard
169
+ * availability cap) and {@link NotificationGenerator.channelDefaults} (a
170
+ * starting point the user can still move).
171
+ *
172
+ * Structurally what a generator already is, rather than the generator itself,
173
+ * so the policy here stays free of the registry and the router.
174
+ */
175
+ interface TypeChannelRules {
176
+ channels?: readonly NotificationChannel[];
177
+ channelDefaults?: Partial<ChannelRow>;
178
+ }
179
+ /**
180
+ * The channels a type may EVER use, coerced onto the closed channel set.
181
+ *
182
+ * `undefined` — a generator that never declared a list — means every channel,
183
+ * which is what keeps every generator written before the field working. An
184
+ * EMPTY list means no transport channel at all and is legal: the inbox record
185
+ * is written by the router regardless, and the inbox is not a channel a user
186
+ * opts out of.
187
+ *
188
+ * Filtering through {@link NOTIFICATION_CHANNELS} rather than returning the
189
+ * declaration is deliberate: it drops a value that is not a channel (a typo, a
190
+ * channel removed from the set since) instead of carrying it into an
191
+ * intersection where it would silently match nothing anyway, and it fixes the
192
+ * order so two declarations of the same set compare equal.
193
+ */
194
+ declare function availableChannelsOf(declared: readonly NotificationChannel[] | undefined): NotificationChannel[];
195
+ /**
196
+ * Drop the channels a type does not offer. Applied by the router AFTER every
197
+ * other gate, so no later stage can hand back a channel the type never offered
198
+ * — including the plan gate's own error fallback, which degrades to the free
199
+ * channels and would otherwise restore an e-mail this type had just refused.
200
+ */
201
+ declare function capToAvailable(channels: readonly NotificationChannel[], declared: readonly NotificationChannel[] | undefined): NotificationChannel[];
202
+ /**
203
+ * The channels one (user, category, TYPE) actually enables — the whole policy
204
+ * in one pure function, so the router's gate can be argued about without a
205
+ * database.
206
+ *
207
+ * The order is the meaning:
208
+ * 1. the category's defaults, with the TYPE's defaults over them — a type
209
+ * moves the starting point;
210
+ * 2. the user's stored row over that — an explicit choice beats any default,
211
+ * which is what makes step 1 a default rather than a rule;
212
+ * 3. the type's AVAILABILITY over everything — a channel this type does not
213
+ * offer is gone even when the user's stored row explicitly asked for it,
214
+ * because it was never on offer for this message.
215
+ *
216
+ * Step 3 overriding a stored `true` is the one place a user's saved choice is
217
+ * discarded, and it is the point of the field: the answer they gave was about
218
+ * the CATEGORY, and a category can hold messages this channel was never right
219
+ * for. Step 2 beating step 1 is what keeps the other field a default.
220
+ */
221
+ declare function resolveTypeChannels(input: {
222
+ /** The stored `channels` JSON for this (user, category), if any. */
223
+ stored?: unknown;
224
+ /** The category's effective default row (host defaults already merged). */
225
+ categoryDefaults: ChannelRow;
226
+ /** The type's own declarations. */
227
+ rules?: TypeChannelRules;
228
+ }): NotificationChannel[];
166
229
  /** The channels enabled by one effective row — the router's gate. */
167
230
  declare function enabledChannelsOf(row: ChannelRow): NotificationChannel[];
168
231
  /**
@@ -220,4 +283,4 @@ interface NotificationRow {
220
283
  /** Row → wire. Dates become ISO strings; a null `data` becomes `{}`. */
221
284
  declare function inboxWire(row: NotificationRow): InboxNotification;
222
285
 
223
- export { type ChannelMatrix as C, DEFAULT_CHANNEL_ROW as D, type InboxNotification as I, type ListNotificationsResult as L, type NotificationMessages as N, type ChannelRow as a, type NotificationRow as b, type NotificationWireMessages as c, defaultChannelMatrix as d, enabledChannelsOf as e, mergeStoredRow as f, messagesOf as g, type NotificationsCopySource as h, inboxWire as i, mergeChoices as m };
286
+ export { type ChannelMatrix as C, DEFAULT_CHANNEL_ROW as D, type InboxNotification as I, type ListNotificationsResult as L, type NotificationMessages as N, type TypeChannelRules as T, type ChannelRow as a, type NotificationRow as b, type NotificationWireMessages as c, availableChannelsOf as d, capToAvailable as e, defaultChannelMatrix as f, enabledChannelsOf as g, mergeStoredRow as h, inboxWire as i, messagesOf as j, type NotificationsCopySource as k, mergeChoices as m, resolveTypeChannels as r };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@12-apps/notifications",
3
- "version": "4.10.3",
3
+ "version": "4.12.0",
4
4
  "type": "module",
5
5
  "sideEffects": false,
6
6
  "description": "Plug-and-play notification system (12-15): an always-on in-app inbox, per-user × per-category channel preferences, and email / SMS / WhatsApp / web-push transports behind vendor DRIVERS so a second provider is a config entry. Framework-free core (.), host-mounted backend surface (./server: inbox / preferences / push-subscription endpoints, the channel router with delivery records + retry sweep, the permission fan-out, duck-typed Prisma seam), Hono adapter (./hono), React surface (./react: bell + badge, inbox drawer, preferences screen), VAPID sender (./web-push) and the package-owned Prisma partial + migrations. Standardized adoption contract in ADOPTING.md.",
@@ -71,7 +71,7 @@
71
71
  "prisma:sync:check": "node scripts/sync-notifications-schema.mjs --check"
72
72
  },
73
73
  "dependencies": {
74
- "@12-apps/ui": "^6.19.1"
74
+ "@12-apps/ui": "^6.25.0"
75
75
  },
76
76
  "peerDependencies": {
77
77
  "@12-apps/wiring": ">=1.3.0",
package/src/index.ts CHANGED
@@ -48,13 +48,17 @@ export {
48
48
  } from './generators';
49
49
 
50
50
  export {
51
+ availableChannelsOf,
52
+ capToAvailable,
51
53
  DEFAULT_CHANNEL_ROW,
52
54
  defaultChannelMatrix,
53
55
  enabledChannelsOf,
54
56
  mergeChoices,
55
57
  mergeStoredRow,
58
+ resolveTypeChannels,
56
59
  type ChannelMatrix,
57
60
  type ChannelRow,
61
+ type TypeChannelRules,
58
62
  } from './preferences-core';
59
63
 
60
64
  export { normalizePhoneE164, type PhoneNormalizeOptions } from './phone';