@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,10 +1,11 @@
1
1
  import {
2
2
  DEFAULT_CHANNEL_ROW,
3
- enabledChannelsOf,
4
- mergeChoices,
3
+ explicitChoicesOf,
5
4
  mergeStoredRow,
5
+ resolveTypeChannels,
6
6
  type ChannelMatrix,
7
7
  type ChannelRow,
8
+ type TypeChannelRules,
8
9
  } from '../preferences-core';
9
10
  import type {
10
11
  NotificationCategory,
@@ -32,10 +33,20 @@ export interface NotificationPreferenceStore {
32
33
  userId: string,
33
34
  input: Partial<Record<NotificationCategory, Partial<ChannelRow>>>,
34
35
  ): Promise<void>;
35
- /** The channels enabled for one (user, category) — the router's gate. */
36
+ /**
37
+ * The channels enabled for one (user, category) — the router's gate.
38
+ *
39
+ * `rules` carries the emitting TYPE's own declarations (availability and
40
+ * per-type defaults). It is OPTIONAL, and that is what keeps a host store
41
+ * written before it working: a two-parameter implementation is assignable to
42
+ * this signature unchanged, and omitting the argument asks the same question
43
+ * the store has always answered. The router does not rely on a store
44
+ * honouring it — it caps the result by availability itself.
45
+ */
36
46
  enabledChannels(
37
47
  userId: string,
38
48
  category: NotificationCategory,
49
+ rules?: TypeChannelRules,
39
50
  ): Promise<NotificationChannel[]>;
40
51
  }
41
52
 
@@ -72,6 +83,14 @@ export function createPreferenceStore(
72
83
  * A category outside the taxonomy is IGNORED rather than stored: the DB
73
84
  * CHECK would reject it anyway, and a 500 from a stale client's extra key
74
85
  * would fail the whole save including the toggle the user did flip.
86
+ *
87
+ * What is written is the user's EXPLICIT choices only, never their
88
+ * effective row. Merging onto the effective row wrote a boolean for all
89
+ * four channels the moment anyone touched any switch, so the row could
90
+ * never again say "no opinion" about a channel — which silently disabled
91
+ * every per-type and per-host default for that user, and defeated the
92
+ * missing-key fallback that lets a new channel ship without a data
93
+ * migration.
75
94
  */
76
95
  async save(userId, input) {
77
96
  const client = await db();
@@ -80,8 +99,7 @@ export function createPreferenceStore(
80
99
  const existing = await client.notificationPreference.findUnique({
81
100
  where: { userId_category: { userId, category } },
82
101
  });
83
- const current = existing ? mergeStoredRow(existing.channels, defaultRow) : defaultRow;
84
- const channels = mergeChoices(current, choices);
102
+ const channels = { ...explicitChoicesOf(existing?.channels), ...choices };
85
103
  await client.notificationPreference.upsert({
86
104
  where: { userId_category: { userId, category } },
87
105
  create: { userId, category, channels },
@@ -90,14 +108,16 @@ export function createPreferenceStore(
90
108
  }
91
109
  },
92
110
 
93
- async enabledChannels(userId, category) {
111
+ async enabledChannels(userId, category, rules) {
94
112
  const client = await db();
95
113
  const row = await client.notificationPreference.findUnique({
96
114
  where: { userId_category: { userId, category } },
97
115
  });
98
- return enabledChannelsOf(
99
- row ? mergeStoredRow(row.channels, defaultRow) : defaultRow,
100
- );
116
+ return resolveTypeChannels({
117
+ stored: row?.channels,
118
+ categoryDefaults: defaultRow,
119
+ rules,
120
+ });
101
121
  },
102
122
  };
103
123
  }
@@ -1,7 +1,12 @@
1
1
  import { UnknownNotificationRecipientError } from '../errors';
2
2
  import type { NotificationGeneratorRegistry } from '../generators';
3
- import { DEFAULT_CHANNEL_ROW, enabledChannelsOf } from '../preferences-core';
4
- import type { NotificationChannel, NotificationEvent, TransportRecipient } from '../types';
3
+ import { capToAvailable, DEFAULT_CHANNEL_ROW, enabledChannelsOf } from '../preferences-core';
4
+ import type {
5
+ NotificationChannel,
6
+ NotificationEvent,
7
+ NotificationGenerator,
8
+ TransportRecipient,
9
+ } from '../types';
5
10
 
6
11
  import {
7
12
  dispatchOne,
@@ -174,14 +179,38 @@ function announce(deps: NotificationRouterDeps, notification: CommittedNotificat
174
179
  }
175
180
  }
176
181
 
177
- /** The channels one emit will actually enqueue: preference ∩ transport ∩ plan. */
182
+ /**
183
+ * The channels one emit will actually enqueue:
184
+ * preference ∩ transport ∩ plan ∩ the TYPE's own availability.
185
+ *
186
+ * Availability is applied LAST rather than left to the preference store,
187
+ * because {@link NotificationChannelPolicy} is a HOST function returning an
188
+ * array and nothing constrains it to a subset of what it was handed. A policy
189
+ * that returns a channel it was not given — a host reading the plan's own
190
+ * entitlement list rather than filtering the argument — would otherwise put
191
+ * back a channel this type had just declared it does not offer. The final
192
+ * filter makes availability independent of what any host code returns.
193
+ *
194
+ * {@link policyFallback}, by contrast, cannot reintroduce anything: it filters
195
+ * its own already-capped input. It is not a reason for this filter, and the
196
+ * cost of the filter is one pass over at most four strings.
197
+ *
198
+ * The inbox record is NOT gated by any of this — `commit` writes it whatever
199
+ * this returns, including the empty array — because the inbox is the record of
200
+ * what happened rather than a channel a user opts out of.
201
+ */
178
202
  async function resolveChannels(
179
203
  deps: NotificationRouterDeps,
180
204
  event: NotificationEvent<unknown>,
181
- category: string,
205
+ generator: Pick<NotificationGenerator<never>, 'category' | 'channels' | 'channelDefaults'>,
182
206
  recipient: TransportRecipient,
183
207
  ): Promise<NotificationChannel[]> {
184
- const enabled = await deps.preferences.enabledChannels(event.recipient.userId, category);
208
+ const rules = { channels: generator.channels, channelDefaults: generator.channelDefaults };
209
+ const enabled = await deps.preferences.enabledChannels(
210
+ event.recipient.userId,
211
+ generator.category,
212
+ rules,
213
+ );
185
214
  const supported = enabled.filter((channel) => {
186
215
  const transport = deps.transports.get(channel);
187
216
  return transport !== null && transport.supports(recipient);
@@ -189,7 +218,8 @@ async function resolveChannels(
189
218
  // The host's plan gate: a tenant-scoped emit keeps only the channels the
190
219
  // tenant's plan covers, so a revoked transport DEGRADES to the remaining ones
191
220
  // rather than dropping the notification silently.
192
- return applyPolicy(deps, event.recipient.clientId, supported);
221
+ const permitted = await applyPolicy(deps, event.recipient.clientId, supported);
222
+ return capToAvailable(permitted, generator.channels);
193
223
  }
194
224
 
195
225
  /** Commit the inbox record and its delivery rows together, in one transaction. */
@@ -256,7 +286,7 @@ export function createNotificationRouter(deps: NotificationRouterDeps): Notifica
256
286
  // generator owns the fallback, in one place a reader can find.
257
287
  const content = generator.generate(event.payload as never, { locale: recipient.locale });
258
288
 
259
- const channels = await resolveChannels(deps, event, generator.category, recipient);
289
+ const channels = await resolveChannels(deps, event, generator, recipient);
260
290
  const notification = await commit(deps, event, generator.category, content, channels);
261
291
 
262
292
  // AFTER the transaction, never inside it: a subscriber woken by an event
package/src/types.ts CHANGED
@@ -130,6 +130,39 @@ export interface NotificationGenerator<TPayload = unknown> {
130
130
  type: string;
131
131
  /** The preference category the router gates this type's fan-out on. */
132
132
  category: NotificationCategory;
133
+ /**
134
+ * The channels this type may EVER use — AVAILABILITY, not preference.
135
+ *
136
+ * Absent (the default) means every channel, which is what every generator
137
+ * written before this field keeps doing. A declared list is a hard cap the
138
+ * router applies AFTER preferences: a user whose stored row explicitly
139
+ * enables a channel this type does not offer still does not get it, because
140
+ * the channel was never on offer for this message. That is the difference
141
+ * from {@link channelDefaults} — a default is a starting point a user can
142
+ * move, availability is the set of starting points that exist.
143
+ *
144
+ * It is per TYPE because the category is too coarse to say it: the three
145
+ * comanda kitchen messages sit in `orders` next to `order.paid`, and a diner
146
+ * three metres from the food wants a push, not correspondence, while the
147
+ * buyer of a delivery order still wants the e-mail. Declaring `["WEB_PUSH"]`
148
+ * on the mesa messages says that without splitting the category or taking
149
+ * `order.paid`'s e-mail away with it.
150
+ *
151
+ * Unknown entries are ignored and order is irrelevant — the list is coerced
152
+ * onto {@link NOTIFICATION_CHANNELS}. An EMPTY list is legal and means no
153
+ * transport channel at all; the inbox record is written regardless, because
154
+ * the inbox is not a channel a user opts out of.
155
+ */
156
+ channels?: readonly NotificationChannel[];
157
+ /**
158
+ * This type's starting toggles, overriding the category's defaults for the
159
+ * channels it names and only where the user has made NO explicit choice.
160
+ *
161
+ * Stored preferences still win over it — that is what makes it a default.
162
+ * To take a channel away from a user who asked for it, declare
163
+ * {@link channels} instead.
164
+ */
165
+ channelDefaults?: Partial<Record<NotificationChannel, boolean>>;
133
166
  /**
134
167
  * Render this event's content for ONE recipient.
135
168
  *
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/errors.ts","../src/generators.ts","../src/preferences-core.ts","../src/phone.ts","../src/wire.ts"],"sourcesContent":["/** Thrown by `notify` when no generator is registered for the event type. */\nexport class UnknownNotificationTypeError extends Error {\n readonly type: string;\n constructor(type: string) {\n super(`No notification generator registered for type \"${type}\".`);\n this.name = 'UnknownNotificationTypeError';\n this.type = type;\n Object.setPrototypeOf(this, UnknownNotificationTypeError.prototype);\n }\n}\n\n/** Thrown by `notify` when the recipient has no contact record in the host. */\nexport class UnknownNotificationRecipientError extends Error {\n readonly userId: string;\n constructor(userId: string) {\n super(`notify(): unknown recipient user \"${userId}\".`);\n this.name = 'UnknownNotificationRecipientError';\n this.userId = userId;\n Object.setPrototypeOf(this, UnknownNotificationRecipientError.prototype);\n }\n}\n","import { UnknownNotificationTypeError } from './errors';\nimport type { NotificationGenerator } from './types';\n\n/**\n * Generator registry: one {@link NotificationGenerator} per event `type`.\n *\n * INSTANCE state, not a module-level Map. the origin's own was\n * process-wide, which is what a package of loose functions forces; a factory\n * config does not need it, and one registry per mount is what makes a test (or\n * a second mount) able to hold its own set without clearing anyone else's.\n * Domain modules still register from the OUTSIDE — that is the open/closed\n * seam the whole pipeline is built on — either through the server config's\n * `generators` array or through `registerGenerator` for a late arrival.\n */\nexport interface NotificationGeneratorRegistry {\n /** Register (last-wins, so a re-import is idempotent). */\n register<TPayload>(generator: NotificationGenerator<TPayload>): void;\n /** Resolve for `type`, throwing {@link UnknownNotificationTypeError}. */\n resolve(type: string): NotificationGenerator<never>;\n /** Whether a generator is registered for `type` (emit-site guard). */\n has(type: string): boolean;\n /** Every registered type, for diagnostics. */\n types(): string[];\n}\n\nexport function createGeneratorRegistry(\n initial: readonly NotificationGenerator<never>[] = [],\n): NotificationGeneratorRegistry {\n const generators = new Map<string, NotificationGenerator<never>>();\n const registry: NotificationGeneratorRegistry = {\n register(generator) {\n generators.set(generator.type, generator as NotificationGenerator<never>);\n },\n resolve(type) {\n const generator = generators.get(type);\n if (!generator) throw new UnknownNotificationTypeError(type);\n return generator;\n },\n has: (type) => generators.has(type),\n types: () => [...generators.keys()],\n };\n for (const generator of initial) registry.register(generator);\n return registry;\n}\n","import {\n NOTIFICATION_CHANNELS,\n type NotificationCategory,\n type NotificationChannel,\n} from './types';\n\n/**\n * The preference POLICY, with no storage in it (12-15): which channels a\n * category defaults to, how a stored JSON row is coerced onto the closed\n * channel set, and how a partial save merges. `./server`'s store is the only\n * thing that touches a database, so every rule here is unit-testable without\n * one — and the react half can render the same defaults before the first read\n * lands.\n *\n * Storage stores only EXPLICIT choices (one row per (user, category)); a\n * missing row — or a missing channel key inside a row — falls back to\n * {@link DEFAULT_CHANNEL_ROW}. Defaults: the free, low-friction channels\n * (e-mail + web push) on; the paid per-message channels (SMS + WhatsApp) off\n * until the user opts in. A host that disagrees passes `channelDefaults`.\n */\n\n/** One category's channel toggles. */\nexport type ChannelRow = Record<NotificationChannel, boolean>;\n\n/** A user's full category × channel matrix. */\nexport type ChannelMatrix = Record<NotificationCategory, ChannelRow>;\n\n/** The policy applied when a user never touched a category's toggles. */\nexport const DEFAULT_CHANNEL_ROW: ChannelRow = {\n EMAIL: true,\n SMS: false,\n WHATSAPP: false,\n WEB_PUSH: true,\n};\n\n/** The default matrix for one taxonomy (what the settings UI starts from). */\nexport function defaultChannelMatrix(\n categories: readonly NotificationCategory[],\n channelDefaults: Partial<ChannelRow> = {},\n): ChannelMatrix {\n const row = { ...DEFAULT_CHANNEL_ROW, ...channelDefaults };\n return Object.fromEntries(\n categories.map((category) => [category, { ...row }]),\n ) as ChannelMatrix;\n}\n\n/**\n * Coerce a stored JSON `channels` map onto the closed channel set, filling the\n * gaps from `base`. A stored row that predates a channel keeps that channel's\n * default rather than reading as \"off\", which is what lets a new transport ship\n * without a data migration.\n *\n * The consequence, and the rule it implies: a channel ADDED later turns itself\n * ON for a user who had explicitly switched every channel in that category off,\n * because their stored row has no key for it. That is harmless for the four\n * shipped channels — the two that cost money default off — so **a new channel\n * must be added with a `false` default** unless the user's existing consent\n * already covers it. The alternative (reading a missing key as \"off\") would need\n * a data migration for every existing row on every channel that ever ships.\n */\nexport function mergeStoredRow(stored: unknown, base: ChannelRow): ChannelRow {\n const row = { ...base };\n if (stored && typeof stored === 'object') {\n const record = stored as Record<string, unknown>;\n for (const channel of NOTIFICATION_CHANNELS) {\n const value = record[channel];\n if (typeof value === 'boolean') row[channel] = value;\n }\n }\n return row;\n}\n\n/** The channels enabled by one effective row — the router's gate. */\nexport function enabledChannelsOf(row: ChannelRow): NotificationChannel[] {\n return NOTIFICATION_CHANNELS.filter((channel) => row[channel]);\n}\n\n/**\n * What a PUT writes for one category: the caller's toggles merged over the\n * user's CURRENT effective row. A single-toggle save (how the settings UI\n * writes) must never reset the category's other channels back to their\n * defaults, which is exactly what a whole-row write would do.\n */\nexport function mergeChoices(\n current: ChannelRow,\n choices: Partial<ChannelRow>,\n): ChannelRow {\n return { ...current, ...choices };\n}\n","/**\n * Shared phone-destination rules for the SMS + WhatsApp transports.\n *\n * Providers need E.164 (`+5531999998888`); a host stores the phone as the user\n * entered it. Best-effort normalization: an explicit `+` prefix is trusted; a\n * bare 10/11-digit number is assumed to belong to `defaultCountryCode` and\n * prefixed; anything else is unusable and makes the channel unavailable for\n * that recipient.\n *\n * `defaultCountryCode` is REQUIRED, and that is the whole point of it being a\n * parameter. It used to default to `55` (Brazil, the first host's market),\n * which a published package must not do: a US adopter that never set it turned\n * `4155552671` into `+554155552671` — a plausible Brazilian mobile — and sent a\n * stranger the customer's order details. There is no country this package could\n * assume that is not wrong for every other adopter, so it assumes none and the\n * omission is a compile error rather than a wrong number. the origin passes\n * `'55'` explicitly.\n *\n * NOTE: \"verified phone\" is approximated by \"has a normalizable phone on\n * file\" — a host with a real verification flow should tighten its contact\n * directory to only return verified numbers, which is the single seam both\n * transports funnel through.\n */\n\n/** Options for {@link normalizePhoneE164}. */\nexport interface PhoneNormalizeOptions {\n /**\n * Country calling code for a bare local number, digits only (`'55'`, `'1'`).\n * Required: see the module docstring for why there is no default.\n */\n defaultCountryCode: string;\n}\n\n/** A local subscriber number: area code (2) + 8 or 9 digits. */\nconst isLocal = (digits: string): boolean => digits.length === 10 || digits.length === 11;\n\n/** An already-international number. E.164 allows 8..15 digits. */\nconst international = (digits: string): string | null =>\n digits.length >= 8 && digits.length <= 15 ? `+${digits}` : null;\n\n/** Normalize a stored phone to E.164, or null when it can't be inferred. */\nexport function normalizePhoneE164(\n raw: string | null | undefined,\n options: PhoneNormalizeOptions,\n): string | null {\n if (!raw) return null;\n const country = options.defaultCountryCode;\n const trimmed = raw.trim();\n const digits = trimmed.replace(/\\D/g, '');\n if (trimmed.startsWith('+')) return international(digits);\n if (isLocal(digits)) return `+${country}${digits}`;\n // A bare number that already carries the country code.\n if (digits.startsWith(country) && isLocal(digits.slice(country.length))) {\n return `+${digits}`;\n }\n return null;\n}\n","/**\n * The inbox WIRE shape — the one contract the two halves share.\n *\n * It lives in the root entry rather than in `./server` or `./react` because\n * both halves need it and neither owns it: the api serializes to it, the panel\n * deserializes from it, and a change here is a change to both at once. That is\n * the same reason the response envelope and the route paths are the package's\n * and not the host's.\n */\n\n/** One inbox entry as the notification centre renders it. */\nexport interface InboxNotification {\n id: string;\n type: string;\n category: string;\n title: string;\n body: string;\n link: string | null;\n data: Record<string, unknown>;\n /** ISO-8601, or null while unread. */\n readAt: string | null;\n /** ISO-8601. */\n createdAt: string;\n}\n\n/** One page of the owner's inbox. */\nexport interface ListNotificationsResult {\n items: InboxNotification[];\n /** Cursor for the next page, or null when this page is the last. */\n nextCursor: string | null;\n}\n\n/** A stored notification row, as the db seam hands it back. */\nexport interface NotificationRow {\n id: string;\n userId: string;\n clientId: string | null;\n type: string;\n category: string;\n title: string;\n body: string;\n link: string | null;\n data: unknown;\n readAt: Date | null;\n deletedAt: Date | null;\n createdAt: Date;\n}\n\n/** Row → wire. Dates become ISO strings; a null `data` becomes `{}`. */\nexport function inboxWire(row: NotificationRow): InboxNotification {\n return {\n id: row.id,\n type: row.type,\n category: row.category,\n title: row.title,\n body: row.body,\n link: row.link,\n data: (row.data ?? {}) as Record<string, unknown>,\n readAt: row.readAt ? row.readAt.toISOString() : null,\n createdAt: row.createdAt.toISOString(),\n };\n}\n"],"mappings":"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","names":[]}