@12-apps/notifications 4.11.0 → 4.13.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/dist/{chunk-2TJ4D2KE.js → chunk-4DTUD74E.js} +2 -2
  2. package/dist/{chunk-WVRODNXQ.js → chunk-53SH5ABN.js} +111 -46
  3. package/dist/chunk-53SH5ABN.js.map +1 -0
  4. package/dist/{chunk-6W7INOYQ.js → chunk-CSMFJJXY.js} +1 -1
  5. package/dist/chunk-CSMFJJXY.js.map +1 -0
  6. package/dist/{chunk-ZIR3ILFH.js → chunk-QPO6NSRR.js} +2 -2
  7. package/dist/{chunk-SWOWHIFE.js → chunk-XFODKRRB.js} +37 -2
  8. package/dist/chunk-XFODKRRB.js.map +1 -0
  9. package/dist/{create-api-notifications-CcPYrM3p.d.ts → create-api-notifications-cIEbFqoZ.d.ts} +132 -32
  10. package/dist/{create-web-notifications-CnaXx6km.d.ts → create-web-notifications-BODiQRK-.d.ts} +2 -2
  11. package/dist/{generators-qAD4fNPq.d.ts → generators-GUF-Kml-.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-wKoOz91Q.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-YRPNUZS5.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-DPiePHJD.d.ts} +52 -1
  27. package/dist/web-push/index.d.ts +2 -2
  28. package/dist/{web-push-Dnyaha2z.d.ts → web-push-DUn_d_gj.d.ts} +12 -2
  29. package/dist/{wire-BG1kuoXX.d.ts → wire-CJka1AvM.d.ts} +65 -2
  30. package/package.json +2 -2
  31. package/prisma/migrations/20260914210000_scope_push_subscriptions_per_store/migration.sql +31 -0
  32. package/prisma/notifications.prisma +12 -0
  33. package/src/index.ts +4 -0
  34. package/src/preferences-core.ts +107 -0
  35. package/src/server/context.ts +15 -0
  36. package/src/server/db.ts +50 -8
  37. package/src/server/dispatch.ts +9 -2
  38. package/src/server/inbox.ts +87 -18
  39. package/src/server/index.ts +2 -0
  40. package/src/server/preferences.ts +29 -9
  41. package/src/server/push-subscriptions.ts +61 -7
  42. package/src/server/router.ts +42 -8
  43. package/src/server/routes.ts +14 -5
  44. package/src/server/transports/web-push.ts +15 -2
  45. package/src/types.ts +52 -1
  46. package/dist/chunk-6W7INOYQ.js.map +0 -1
  47. package/dist/chunk-SWOWHIFE.js.map +0 -1
  48. package/dist/chunk-WVRODNXQ.js.map +0 -1
  49. /package/dist/{chunk-2TJ4D2KE.js.map → chunk-4DTUD74E.js.map} +0 -0
  50. /package/dist/{chunk-ZIR3ILFH.js.map → chunk-QPO6NSRR.js.map} +0 -0
  51. /package/dist/{preferences-screen-SXUIGECY.js.map → preferences-screen-YRPNUZS5.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-GUF-Kml-.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-CJka1AvM.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-DPiePHJD.js';
4
+ import { e as WebPushSubscriptionSource, D as DriverDeclarationBase, a as WebPushDriverDeclaration, c as WebPushSender } from './web-push-DUn_d_gj.js';
5
5
  import { d as EmailChromeCopy, f as EmailTheme } from './template-Dlt5S54z.js';
6
6
 
7
7
  /**
@@ -51,25 +51,45 @@ interface NotificationPageAfter {
51
51
  createdAt: Date;
52
52
  id: string;
53
53
  }
54
- /** The inbox read filter. `deletedAt: null` is on every read, always. */
55
- interface NotificationWhere {
54
+ /**
55
+ * One BRANCH of an inbox filter — a member of `AND`/`OR`, never a whole read.
56
+ *
57
+ * Split out when the store scope arrived, and the split is what keeps `deletedAt: null`
58
+ * REQUIRED on the read itself (see {@link NotificationWhere}). Widening `OR`
59
+ * from the old fixed keyset tuple to a general array needed its members to be
60
+ * valid filters, and the tempting move — relaxing `deletedAt` on the one type —
61
+ * would have removed the only thing that makes a Prisma-backed host exclude
62
+ * soft-deleted rows. Both in-package doubles hard-code that filter regardless,
63
+ * so Prisma is precisely the implementation the requirement was protecting.
64
+ *
65
+ * `createdAt` and `id: { lt }` are here because the keyset boundary is built
66
+ * from them (`../inbox.ts`'s `pageWhere`); without them the branch type cannot
67
+ * express the very thing the widening exists to preserve.
68
+ */
69
+ interface NotificationWhereBranch {
56
70
  userId?: string;
57
71
  id?: string | {
58
72
  in: string[];
73
+ } | {
74
+ lt: string;
59
75
  };
60
- deletedAt: null;
76
+ createdAt?: Date | {
77
+ lt: Date;
78
+ };
79
+ deletedAt?: null;
61
80
  readAt?: null;
62
- /** The keyset half of `(createdAt, id) < (anchor.createdAt, anchor.id)`. */
63
- OR?: [{
64
- createdAt: {
65
- lt: Date;
66
- };
67
- }, {
68
- createdAt: Date;
69
- id: {
70
- lt: string;
71
- };
72
- }];
81
+ /** `null` matches the platform-wide rows that travel with every store scope. */
82
+ clientId?: string | null;
83
+ OR?: NotificationWhereBranch[];
84
+ AND?: NotificationWhereBranch[];
85
+ }
86
+ /**
87
+ * The inbox read filter. `deletedAt: null` is on every read, always — and it
88
+ * stays REQUIRED here, which is the whole reason {@link NotificationWhereBranch}
89
+ * is a separate type.
90
+ */
91
+ interface NotificationWhere extends NotificationWhereBranch {
92
+ deletedAt: null;
73
93
  }
74
94
  interface NotificationDelegate {
75
95
  create(args: {
@@ -227,13 +247,28 @@ interface PushSubscriptionRow {
227
247
  endpoint: string;
228
248
  p256dh: string;
229
249
  auth: string;
250
+ /** The origin this browser registered on; `null` = the platform origin. */
251
+ clientId: string | null;
230
252
  userAgent: string | null;
231
253
  }
254
+ /**
255
+ * Which of a user's subscriptions one send may reach.
256
+ *
257
+ * The disjunction is load-bearing and `clientId: { in: [x, null] }` is NOT a
258
+ * shortcut for it: SQL `IN` never matches NULL, so the platform-wide rows that
259
+ * must travel with every store scope would silently vanish.
260
+ */
261
+ interface PushSubscriptionWhere {
262
+ userId: string;
263
+ OR?: [{
264
+ clientId: null;
265
+ }, {
266
+ clientId: string;
267
+ }];
268
+ }
232
269
  interface PushSubscriptionDelegate {
233
270
  count(args: {
234
- where: {
235
- userId: string;
236
- };
271
+ where: PushSubscriptionWhere;
237
272
  }): Promise<number>;
238
273
  /**
239
274
  * The row holding one endpoint, whoever owns it. Read BEFORE an upsert so a
@@ -247,9 +282,7 @@ interface PushSubscriptionDelegate {
247
282
  };
248
283
  }): Promise<PushSubscriptionRow | null>;
249
284
  findMany(args: {
250
- where: {
251
- userId: string;
252
- };
285
+ where: PushSubscriptionWhere;
253
286
  }): Promise<PushSubscriptionRow[]>;
254
287
  upsert(args: {
255
288
  where: {
@@ -260,12 +293,14 @@ interface PushSubscriptionDelegate {
260
293
  endpoint: string;
261
294
  p256dh: string;
262
295
  auth: string;
296
+ clientId: string | null;
263
297
  userAgent: string | null;
264
298
  };
265
299
  update: {
266
300
  userId: string;
267
301
  p256dh: string;
268
302
  auth: string;
303
+ clientId: string | null;
269
304
  userAgent: string | null;
270
305
  };
271
306
  }): Promise<PushSubscriptionRow>;
@@ -338,6 +373,11 @@ interface PushSubscriptionInput {
338
373
  };
339
374
  /** Optional browser/device hint for a device list. */
340
375
  userAgent?: string;
376
+ /**
377
+ * Which ORIGIN this browser subscribed on — the HOST's resolved
378
+ * value, never anything the caller sent. Absent/null = the platform origin.
379
+ */
380
+ clientId?: string | null;
341
381
  }
342
382
  interface PushSubscriptionStore extends WebPushSubscriptionSource {
343
383
  /**
@@ -355,8 +395,19 @@ interface PushSubscriptionStore extends WebPushSubscriptionSource {
355
395
  save(userId: string, input: PushSubscriptionInput): Promise<void>;
356
396
  /** Remove one browser's subscription (owner-scoped; unknown = no-op). */
357
397
  remove(userId: string, endpoint: string): Promise<void>;
358
- /** How many devices the user has registered (settings UI hint). */
359
- count(userId: string): Promise<number>;
398
+ /**
399
+ * How many devices the user has registered.
400
+ *
401
+ * TWO callers with different questions. The settings screen asks unscoped —
402
+ * "you have 2 devices" is a fact about the PERSON — and passes nothing. The
403
+ * dispatch path passes the notification's tenant, because this is what
404
+ * `supports()` gates on: left unscoped there, the router would enqueue a
405
+ * WEB_PUSH delivery for a notification no reachable subscription exists for,
406
+ * `send` would find an empty list and throw, and the sweep would burn every
407
+ * attempt before writing DEAD — a FAILED row for something that was never
408
+ * undeliverable, only out of scope.
409
+ */
410
+ count(userId: string, notificationClientId?: string | null): Promise<number>;
360
411
  /**
361
412
  * Whether THIS endpoint is currently registered to THIS user.
362
413
  *
@@ -600,8 +651,17 @@ interface NotificationPreferenceStore {
600
651
  get(userId: string): Promise<ChannelMatrix>;
601
652
  /** Persist explicit choices for any subset of categories/toggles. */
602
653
  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[]>;
654
+ /**
655
+ * The channels enabled for one (user, category) — the router's gate.
656
+ *
657
+ * `rules` carries the emitting TYPE's own declarations (availability and
658
+ * per-type defaults). It is OPTIONAL, and that is what keeps a host store
659
+ * written before it working: a two-parameter implementation is assignable to
660
+ * this signature unchanged, and omitting the argument asks the same question
661
+ * the store has always answered. The router does not rely on a store
662
+ * honouring it — it caps the result by availability itself.
663
+ */
664
+ enabledChannels(userId: string, category: NotificationCategory, rules?: TypeChannelRules): Promise<NotificationChannel[]>;
605
665
  }
606
666
 
607
667
  /**
@@ -783,6 +843,21 @@ type NotifyByPermission = <TPayload>(clientId: string, permissions: readonly str
783
843
  */
784
844
  interface NotificationsActor {
785
845
  userId: string;
846
+ /**
847
+ * The store whose ORIGIN this request arrived on, resolved by the
848
+ * HOST from the hostname — never read from the body or the query.
849
+ *
850
+ * The paragraph above still holds: this is not a tenant the surface
851
+ * authorizes against, it is a NARROWING of a read that is already the
852
+ * caller's own, and it can never widen one. A host with a single origin never
853
+ * sets it and every endpoint answers exactly what it answered before.
854
+ *
855
+ * It exists because a multi-tenant host installs one storefront per store as
856
+ * its own PWA, and a PWA's identity is its ORIGIN — so an inbox answering a
857
+ * store's own app with a neighbour's notifications is one store reporting on
858
+ * another.
859
+ */
860
+ scopeClientId?: string;
786
861
  }
787
862
  /** One request, already authenticated and routed by the host. */
788
863
  interface NotificationsRequest {
@@ -835,6 +910,18 @@ declare function foldApiError(error: unknown): NotificationsResponse;
835
910
  * Soft-deleted rows (`deletedAt` set) are excluded from every read and can
836
911
  * never be resurrected by mark-read.
837
912
  */
913
+ /**
914
+ * The store whose ORIGIN the caller is reading from, or absent for the platform
915
+ * origin.
916
+ *
917
+ * A host that installs one storefront per store as its own PWA reads this from
918
+ * the request's hostname; a host with one origin never sets it and every read
919
+ * below is exactly what it was. Set, it narrows to that store's rows PLUS the
920
+ * platform-wide ones (`clientId IS NULL`) — a password reset or a security
921
+ * notice is about the person and not about a store, so hiding it inside the
922
+ * only app a customer opens would be a worse failure than the leak this fixes.
923
+ */
924
+ type NotificationScope = string | undefined;
838
925
  interface ListNotificationsInput {
839
926
  /** `unread` narrows to unread rows; default lists all non-deleted. */
840
927
  filter?: 'all' | 'unread';
@@ -850,10 +937,23 @@ interface ListNotificationsInput {
850
937
  limit?: number;
851
938
  }
852
939
  interface NotificationInboxStore {
853
- list(userId: string, input?: ListNotificationsInput): Promise<ListNotificationsResult>;
854
- unreadCount(userId: string): Promise<number>;
940
+ list(userId: string, input?: ListNotificationsInput, scope?: NotificationScope): Promise<ListNotificationsResult>;
941
+ /** Scoped with `list`, or the badge and the list it sits over disagree. */
942
+ unreadCount(userId: string, scope?: NotificationScope): Promise<number>;
855
943
  markRead(userId: string, ids: readonly string[]): Promise<number>;
856
- markAllRead(userId: string): Promise<number>;
944
+ /**
945
+ * Scoped too, and this one is a WRITE.
946
+ *
947
+ * Unscoped, "mark all as read" pressed inside store A's app clears store B's
948
+ * unread rows everywhere — a cross-store write from the app that exists to be
949
+ * isolated, and strictly worse than the read leak. `markRead(ids)` and
950
+ * `softDelete(ids)` need nothing: their ids come from the already-scoped list.
951
+ *
952
+ * The platform-wide rows ARE cleared from any origin, and that follows from
953
+ * the scope rule rather than contradicting it — those rows are one person's,
954
+ * not one store's.
955
+ */
956
+ markAllRead(userId: string, scope?: NotificationScope): Promise<number>;
857
957
  softDelete(userId: string, ids: readonly string[]): Promise<number>;
858
958
  }
859
959
 
@@ -968,4 +1068,4 @@ interface ApiNotifications {
968
1068
  }
969
1069
  declare function createApiNotifications(config: NotificationsServerConfig): ApiNotifications;
970
1070
 
971
- export { createApiNotifications as $, type ApiNotifications as A, type NotificationsDbProvider as B, type CommittedNotification as C, type NotificationsRequest as D, EMAIL_DRIVERS as E, type NotificationsResponse as F, type NotifyByPermission as G, type NotifyOptions as H, type NotifyResult as I, type PermissionNotificationSkip as J, type PushSubscriptionDelegate as K, type ListNotificationsInput as L, type PushSubscriptionInput as M, type NotificationsServerConfig as N, type PushSubscriptionRow as O, type PermissionNotificationResult as P, type PushSubscriptionStore as Q, type SmsDriver as R, SMS_DRIVERS as S, type SmsDriverDeclaration as T, type SmsMessage as U, type TransportDeclaration as V, type TransportRegistry as W, WHATSAPP_DRIVERS as X, type WhatsAppDriver as Y, type WhatsAppDriverDeclaration as Z, type WhatsAppMessage as _, type NotificationsActor as a, createTransportRegistry as a0, emailTransport as a1, foldApiError as a2, formatEmail as a3, formatSms as a4, formatWhatsApp as a5, ok as a6, smsTransport as a7, whatsAppTransport as a8, type NotificationsRoute as b, type EmailDriver as c, type EmailDriverDeclaration as d, type EmailMessage as e, type ExtraDrivers as f, type NotificationAudienceDirectory as g, type NotificationChannelPolicy as h, type NotificationCommittedListener as i, type NotificationContactDirectory as j, type NotificationCreateData as k, type NotificationDelegate as l, type NotificationDeliveryDelegate as m, type NotificationDeliveryRow as n, type NotificationDeliveryWhere as o, type NotificationDispatchScheduler as p, type NotificationInboxStore as q, type NotificationPageAfter as r, type NotificationPreferenceDelegate as s, type NotificationPreferenceRow as t, type NotificationPreferenceStore as u, type NotificationRouter as v, type NotificationWhere as w, NotificationsApiError as x, type NotificationsDb as y, type NotificationsDbClient as z };
1071
+ export { type WhatsAppDriverDeclaration as $, type ApiNotifications as A, type NotificationsDbClient as B, type CommittedNotification as C, type NotificationsDbProvider as D, EMAIL_DRIVERS as E, type NotificationsRequest as F, type NotificationsResponse as G, type NotifyByPermission as H, type NotifyOptions as I, type NotifyResult as J, type PermissionNotificationSkip as K, type ListNotificationsInput as L, type PushSubscriptionDelegate as M, type NotificationsServerConfig as N, type PushSubscriptionInput as O, type PermissionNotificationResult as P, type PushSubscriptionRow as Q, type PushSubscriptionStore as R, type PushSubscriptionWhere as S, SMS_DRIVERS as T, type SmsDriver as U, type SmsDriverDeclaration as V, type SmsMessage as W, type TransportDeclaration as X, type TransportRegistry as Y, WHATSAPP_DRIVERS as Z, type WhatsAppDriver as _, type NotificationsActor as a, type WhatsAppMessage as a0, createApiNotifications as a1, createTransportRegistry as a2, emailTransport as a3, foldApiError as a4, formatEmail as a5, formatSms as a6, formatWhatsApp as a7, ok as a8, smsTransport as a9, whatsAppTransport as aa, type NotificationsRoute as b, type EmailDriver as c, type EmailDriverDeclaration as d, type EmailMessage as e, type ExtraDrivers as f, type NotificationAudienceDirectory as g, type NotificationChannelPolicy as h, type NotificationCommittedListener as i, type NotificationContactDirectory as j, type NotificationCreateData as k, type NotificationDelegate as l, type NotificationDeliveryDelegate as m, type NotificationDeliveryRow as n, type NotificationDeliveryWhere as o, type NotificationDispatchScheduler as p, type NotificationInboxStore as q, type NotificationPageAfter as r, type NotificationPreferenceDelegate as s, type NotificationPreferenceRow as t, type NotificationPreferenceStore as u, type NotificationRouter as v, type NotificationWhere as w, type NotificationWhereBranch as x, NotificationsApiError as y, type NotificationsDb as z };
@@ -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-CJka1AvM.js';
3
+ import { c as NotificationChannel } from './types-DPiePHJD.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-DPiePHJD.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-cIEbFqoZ.js';
3
+ import '../generators-GUF-Kml-.js';
4
+ import '../types-DPiePHJD.js';
5
+ import '../wire-CJka1AvM.js';
6
+ import '../web-push-DUn_d_gj.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-53SH5ABN.js";
4
+ import "../chunk-XFODKRRB.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-CSMFJJXY.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-DPiePHJD.js';
2
+ export { N as NotificationGeneratorRegistry, c as createGeneratorRegistry } from './generators-GUF-Kml-.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-CJka1AvM.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-XFODKRRB.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-CSMFJJXY.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-cIEbFqoZ.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-cIEbFqoZ.js';
5
+ import { N as NotificationsJobDeps } from '../jobs-wKoOz91Q.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-GUF-Kml-.js';
8
+ import '../types-DPiePHJD.js';
9
+ import '../wire-CJka1AvM.js';
10
+ import '../web-push-DUn_d_gj.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-QPO6NSRR.js";
7
7
  import {
8
8
  createApiNotifications
9
- } from "../chunk-WVRODNXQ.js";
10
- import "../chunk-SWOWHIFE.js";
9
+ } from "../chunk-53SH5ABN.js";
10
+ import "../chunk-XFODKRRB.js";
11
11
  import "../chunk-M2TVBVH2.js";
12
12
  import "../chunk-RTURLH5U.js";
13
- import "../chunk-6W7INOYQ.js";
13
+ import "../chunk-CSMFJJXY.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-BODiQRK-.js';
3
3
  import 'react';
4
- import '../wire-BG1kuoXX.js';
5
- import '../types-BlqZkCWZ.js';
4
+ import '../wire-CJka1AvM.js';
5
+ import '../types-DPiePHJD.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-4DTUD74E.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-CSMFJJXY.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-YRPNUZS5.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-BODiQRK-.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-BODiQRK-.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-CJka1AvM.js';
7
+ import '../types-DPiePHJD.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-4DTUD74E.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-cIEbFqoZ.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, x as NotificationWhereBranch, a as NotificationsActor, y as NotificationsApiError, z as NotificationsDb, B as NotificationsDbClient, D as NotificationsDbProvider, F as NotificationsRequest, G as NotificationsResponse, b as NotificationsRoute, N as NotificationsServerConfig, H as NotifyByPermission, I as NotifyOptions, J as NotifyResult, P as PermissionNotificationResult, K as PermissionNotificationSkip, M as PushSubscriptionDelegate, O as PushSubscriptionInput, Q as PushSubscriptionRow, R as PushSubscriptionStore, S as PushSubscriptionWhere, T as SMS_DRIVERS, U as SmsDriver, V as SmsDriverDeclaration, W as SmsMessage, X as TransportDeclaration, Y as TransportRegistry, Z as WHATSAPP_DRIVERS, _ as WhatsAppDriver, $ as WhatsAppDriverDeclaration, a0 as WhatsAppMessage, a1 as createApiNotifications, a2 as createTransportRegistry, a3 as emailTransport, a4 as foldApiError, a5 as formatEmail, a6 as formatSms, a7 as formatWhatsApp, a8 as ok, a9 as smsTransport, aa as whatsAppTransport } from '../create-api-notifications-cIEbFqoZ.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-DUn_d_gj.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-wKoOz91Q.js';
6
+ import '../generators-GUF-Kml-.js';
7
+ import '../types-DPiePHJD.js';
8
+ import '../wire-CJka1AvM.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-QPO6NSRR.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-53SH5ABN.js";
29
+ import "../chunk-XFODKRRB.js";
30
30
  import "../chunk-M2TVBVH2.js";
31
31
  import "../chunk-RTURLH5U.js";
32
- import "../chunk-6W7INOYQ.js";
32
+ import "../chunk-CSMFJJXY.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
  *
@@ -148,8 +181,26 @@ interface TransportRecipient {
148
181
  locale?: string | null;
149
182
  /** Phone as the host stores it (transports normalize per provider rules). */
150
183
  phone: string | null;
151
- /** How many active browser push subscriptions the user holds. */
184
+ /**
185
+ * How many push subscriptions this NOTIFICATION can actually reach — not how
186
+ * many the user holds. Scoped by {@link TransportRecipient.clientId}.
187
+ */
152
188
  pushSubscriptionCount: number;
189
+ /**
190
+ * The store this notification belongs to, or `null` for a platform-wide one
191
+ *.
192
+ *
193
+ * REQUIRED, and deliberately so where optional is the tempting answer. An
194
+ * optional field fails OPEN: a construction site that forgets it yields
195
+ * `undefined`, which the push scope reads as "platform notification -> every
196
+ * subscription" — precisely the cross-store fan-out this exists to stop.
197
+ * Three construction sites is the argument FOR requiring it, not against; the
198
+ * compiler then names them.
199
+ *
200
+ * Not a breaking change for adopters: every external use CONSUMES a recipient
201
+ * through `supports`/`send` below, and the only constructors are in-package.
202
+ */
203
+ clientId: string | null;
153
204
  }
154
205
  /**
155
206
  * One pluggable channel adapter: a FORMATTER (agnostic content → channel
@@ -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-DUn_d_gj.js';
2
+ import '../types-DPiePHJD.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-DPiePHJD.js';
2
2
 
3
3
  /**
4
4
  * The DRIVER port — the reason a host adds a vendor with a config entry and no
@@ -133,7 +133,17 @@ interface WebPushDriverDeclaration extends DriverDeclarationBase {
133
133
  }
134
134
  /** The subscriptions the transport reads and prunes (db-backed by the mount). */
135
135
  interface WebPushSubscriptionSource {
136
- list(userId: string): Promise<{
136
+ /**
137
+ * The subscriptions one notification may reach.
138
+ *
139
+ * `notificationClientId` is the notification's tenant — `null` for a
140
+ * platform-wide one, which every subscription receives. An ADOPTER with a
141
+ * hand-written source must honour it: a function declared with fewer
142
+ * parameters still type-checks, so an un-updated implementation silently
143
+ * ignores the scope and keeps fanning out to every origin, with nothing
144
+ * failing to compile to say so.
145
+ */
146
+ list(userId: string, notificationClientId?: string | null): Promise<{
137
147
  id: string;
138
148
  endpoint: string;
139
149
  p256dh: string;