@12-apps/notifications 4.11.0 → 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 (39) hide show
  1. package/dist/{chunk-6W7INOYQ.js → chunk-CPQKKLPS.js} +1 -1
  2. package/dist/{chunk-6W7INOYQ.js.map → chunk-CPQKKLPS.js.map} +1 -1
  3. package/dist/{chunk-ZIR3ILFH.js → chunk-FBBPS2LT.js} +2 -2
  4. package/dist/{chunk-SWOWHIFE.js → chunk-GK6GSC2J.js} +37 -2
  5. package/dist/chunk-GK6GSC2J.js.map +1 -0
  6. package/dist/{chunk-2TJ4D2KE.js → chunk-PNY6S6WH.js} +2 -2
  7. package/dist/{chunk-WVRODNXQ.js → chunk-WZBX7YCE.js} +32 -15
  8. package/dist/{chunk-WVRODNXQ.js.map → chunk-WZBX7YCE.js.map} +1 -1
  9. package/dist/{create-api-notifications-CcPYrM3p.d.ts → create-api-notifications-BTudlaSC.d.ts} +15 -6
  10. package/dist/{create-web-notifications-CnaXx6km.d.ts → create-web-notifications-2xxbKnrW.d.ts} +2 -2
  11. package/dist/{generators-qAD4fNPq.d.ts → generators-CQYdJfB5.d.ts} +1 -1
  12. package/dist/hono/index.d.ts +5 -5
  13. package/dist/hono/index.js +3 -3
  14. package/dist/index.d.ts +3 -3
  15. package/dist/index.js +9 -3
  16. package/dist/{jobs--fex87-q.d.ts → jobs-CaovU4GM.d.ts} +1 -1
  17. package/dist/manifest/server.d.ts +6 -6
  18. package/dist/manifest/server.js +4 -4
  19. package/dist/manifest/web.d.ts +3 -3
  20. package/dist/manifest/web.js +1 -1
  21. package/dist/{preferences-screen-SXUIGECY.js → preferences-screen-S3ZHX5LB.js} +2 -2
  22. package/dist/react/index.d.ts +4 -4
  23. package/dist/react/index.js +1 -1
  24. package/dist/server/index.d.ts +7 -7
  25. package/dist/server/index.js +4 -4
  26. package/dist/{types-BlqZkCWZ.d.ts → types-H_aFzLA0.d.ts} +33 -0
  27. package/dist/web-push/index.d.ts +2 -2
  28. package/dist/{web-push-Dnyaha2z.d.ts → web-push-C6U-5JCV.d.ts} +1 -1
  29. package/dist/{wire-BG1kuoXX.d.ts → wire-Bn6aA2nL.d.ts} +65 -2
  30. package/package.json +2 -2
  31. package/src/index.ts +4 -0
  32. package/src/preferences-core.ts +107 -0
  33. package/src/server/preferences.ts +29 -9
  34. package/src/server/router.ts +37 -7
  35. package/src/types.ts +33 -0
  36. package/dist/chunk-SWOWHIFE.js.map +0 -1
  37. /package/dist/{chunk-ZIR3ILFH.js.map → chunk-FBBPS2LT.js.map} +0 -0
  38. /package/dist/{chunk-2TJ4D2KE.js.map → chunk-PNY6S6WH.js.map} +0 -0
  39. /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
  /**
@@ -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-CnaXx6km.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,6 +1,6 @@
1
1
  import {
2
2
  createWebNotifications
3
- } from "../chunk-2TJ4D2KE.js";
3
+ } from "../chunk-PNY6S6WH.js";
4
4
  import "../chunk-2IAHFIXS.js";
5
5
  import {
6
6
  createEmailPreviewScreen
@@ -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-CnaXx6km.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-CnaXx6km.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-2TJ4D2KE.js";
6
+ } from "../chunk-PNY6S6WH.js";
7
7
  import {
8
8
  LiveSection,
9
9
  relativeTime
@@ -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.11.0",
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.22.0"
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';
@@ -70,11 +70,118 @@ export function mergeStoredRow(stored: unknown, base: ChannelRow): ChannelRow {
70
70
  return row;
71
71
  }
72
72
 
73
+ /**
74
+ * What ONE notification type says about its own channels, independent of the
75
+ * user's category preferences: {@link NotificationGenerator.channels} (the hard
76
+ * availability cap) and {@link NotificationGenerator.channelDefaults} (a
77
+ * starting point the user can still move).
78
+ *
79
+ * Structurally what a generator already is, rather than the generator itself,
80
+ * so the policy here stays free of the registry and the router.
81
+ */
82
+ export interface TypeChannelRules {
83
+ channels?: readonly NotificationChannel[];
84
+ channelDefaults?: Partial<ChannelRow>;
85
+ }
86
+
87
+ /**
88
+ * The channels a type may EVER use, coerced onto the closed channel set.
89
+ *
90
+ * `undefined` — a generator that never declared a list — means every channel,
91
+ * which is what keeps every generator written before the field working. An
92
+ * EMPTY list means no transport channel at all and is legal: the inbox record
93
+ * is written by the router regardless, and the inbox is not a channel a user
94
+ * opts out of.
95
+ *
96
+ * Filtering through {@link NOTIFICATION_CHANNELS} rather than returning the
97
+ * declaration is deliberate: it drops a value that is not a channel (a typo, a
98
+ * channel removed from the set since) instead of carrying it into an
99
+ * intersection where it would silently match nothing anyway, and it fixes the
100
+ * order so two declarations of the same set compare equal.
101
+ */
102
+ export function availableChannelsOf(
103
+ declared: readonly NotificationChannel[] | undefined,
104
+ ): NotificationChannel[] {
105
+ if (!declared) return [...NOTIFICATION_CHANNELS];
106
+ const offered = new Set<string>(declared);
107
+ return NOTIFICATION_CHANNELS.filter((channel) => offered.has(channel));
108
+ }
109
+
110
+ /**
111
+ * Drop the channels a type does not offer. Applied by the router AFTER every
112
+ * other gate, so no later stage can hand back a channel the type never offered
113
+ * — including the plan gate's own error fallback, which degrades to the free
114
+ * channels and would otherwise restore an e-mail this type had just refused.
115
+ */
116
+ export function capToAvailable(
117
+ channels: readonly NotificationChannel[],
118
+ declared: readonly NotificationChannel[] | undefined,
119
+ ): NotificationChannel[] {
120
+ if (!declared) return [...channels];
121
+ const offered = new Set(availableChannelsOf(declared));
122
+ return channels.filter((channel) => offered.has(channel));
123
+ }
124
+
125
+ /**
126
+ * The channels one (user, category, TYPE) actually enables — the whole policy
127
+ * in one pure function, so the router's gate can be argued about without a
128
+ * database.
129
+ *
130
+ * The order is the meaning:
131
+ * 1. the category's defaults, with the TYPE's defaults over them — a type
132
+ * moves the starting point;
133
+ * 2. the user's stored row over that — an explicit choice beats any default,
134
+ * which is what makes step 1 a default rather than a rule;
135
+ * 3. the type's AVAILABILITY over everything — a channel this type does not
136
+ * offer is gone even when the user's stored row explicitly asked for it,
137
+ * because it was never on offer for this message.
138
+ *
139
+ * Step 3 overriding a stored `true` is the one place a user's saved choice is
140
+ * discarded, and it is the point of the field: the answer they gave was about
141
+ * the CATEGORY, and a category can hold messages this channel was never right
142
+ * for. Step 2 beating step 1 is what keeps the other field a default.
143
+ */
144
+ export function resolveTypeChannels(input: {
145
+ /** The stored `channels` JSON for this (user, category), if any. */
146
+ stored?: unknown;
147
+ /** The category's effective default row (host defaults already merged). */
148
+ categoryDefaults: ChannelRow;
149
+ /** The type's own declarations. */
150
+ rules?: TypeChannelRules;
151
+ }): NotificationChannel[] {
152
+ const { stored, categoryDefaults, rules } = input;
153
+ const base: ChannelRow = { ...categoryDefaults, ...rules?.channelDefaults };
154
+ const row = stored === undefined || stored === null ? base : mergeStoredRow(stored, base);
155
+ return capToAvailable(enabledChannelsOf(row), rules?.channels);
156
+ }
157
+
73
158
  /** The channels enabled by one effective row — the router's gate. */
74
159
  export function enabledChannelsOf(row: ChannelRow): NotificationChannel[] {
75
160
  return NOTIFICATION_CHANNELS.filter((channel) => row[channel]);
76
161
  }
77
162
 
163
+ /**
164
+ * The channels a stored row states an EXPLICIT choice for, dropping everything
165
+ * else — the inverse of {@link mergeStoredRow}, which fills the gaps in.
166
+ *
167
+ * The package's storage model is "only explicit choices are stored; a missing
168
+ * key falls back to the default", and this is what lets a writer keep that
169
+ * promise. Merging a save onto the user's EFFECTIVE row instead turns every
170
+ * defaulted channel into an explicit one the moment they touch any switch, and
171
+ * the row can never say "no opinion" about a channel again.
172
+ */
173
+ export function explicitChoicesOf(stored: unknown): Partial<ChannelRow> {
174
+ const choices: Partial<ChannelRow> = {};
175
+ if (stored && typeof stored === 'object') {
176
+ const record = stored as Record<string, unknown>;
177
+ for (const channel of NOTIFICATION_CHANNELS) {
178
+ const value = record[channel];
179
+ if (typeof value === 'boolean') choices[channel] = value;
180
+ }
181
+ }
182
+ return choices;
183
+ }
184
+
78
185
  /**
79
186
  * What a PUT writes for one category: the caller's toggles merged over the
80
187
  * user's CURRENT effective row. A single-toggle save (how the settings UI