@12-apps/notifications 1.0.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@12-apps/notifications",
3
- "version": "1.0.0",
3
+ "version": "2.0.0",
4
4
  "type": "module",
5
5
  "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.",
6
6
  "exports": {
@@ -22,7 +22,7 @@
22
22
  "prisma:sync:check": "node scripts/sync-notifications-schema.mjs --check"
23
23
  },
24
24
  "dependencies": {
25
- "@12-apps/ui": "^4.0.1"
25
+ "@12-apps/ui": "^4.1.0"
26
26
  },
27
27
  "peerDependencies": {
28
28
  "hono": ">=4.0.0",
package/src/index.ts CHANGED
@@ -21,7 +21,6 @@
21
21
  */
22
22
 
23
23
  export {
24
- NOTIFICATION_CATEGORIES,
25
24
  NOTIFICATION_CHANNELS,
26
25
  taxonomyOf,
27
26
  type DeliveryStatus,
@@ -76,8 +76,16 @@ export interface NotificationsServerConfig {
76
76
  drivers?: ExtraDrivers;
77
77
  /** The domain events this mount can emit. */
78
78
  generators?: readonly NotificationGenerator<never>[];
79
- /** Preference categories. Default: orders / payments / stock / system. */
80
- categories?: readonly NotificationCategory[];
79
+ /**
80
+ * Preference categories — the granularity at which a user chooses channels.
81
+ *
82
+ * REQUIRED. This defaulted to one product's four (`orders`, `payments`,
83
+ * `stock`, `system`), which is the host's vocabulary and not this library's:
84
+ * a host that omitted it rendered four rows it never chose, with its own
85
+ * categories absent, and nothing failed — `category` is a free string by
86
+ * design, so there was no layer left to notice.
87
+ */
88
+ categories: readonly NotificationCategory[];
81
89
  /** Override which channels a never-touched category defaults to. */
82
90
  channelDefaults?: Partial<ChannelRow>;
83
91
  /** The tenant plan gate, answered per emit. */
@@ -54,8 +54,16 @@ export interface WhatsAppDriverDeclaration extends DriverDeclarationBase {
54
54
  accessToken?: string;
55
55
  phoneNumberId?: string;
56
56
  templateName?: string;
57
- /** Default `pt_BR`. */
58
- templateLanguage?: string;
57
+ /**
58
+ * The WhatsApp template's language code, e.g. `pt_BR`, `en_US`.
59
+ *
60
+ * REQUIRED: a template is registered with Meta under one language, and
61
+ * sending it with the wrong code is rejected by the Graph API. This defaulted
62
+ * to `pt_BR` — one market's answer — so a host that forgot it did not get a
63
+ * sensible fallback, it got somebody else's template language and a delivery
64
+ * failure it had no reason to expect.
65
+ */
66
+ templateLanguage: string;
59
67
  /** Graph API base, so a host can pin a version. */
60
68
  graphApiBase?: string;
61
69
  appUrl?: string;
@@ -80,7 +88,7 @@ function templatePayload(
80
88
  type: 'template',
81
89
  template: {
82
90
  name: declaration.templateName,
83
- language: { code: declaration.templateLanguage ?? 'pt_BR' },
91
+ language: { code: declaration.templateLanguage },
84
92
  components: [
85
93
  {
86
94
  type: 'body',
package/src/types.ts CHANGED
@@ -18,18 +18,25 @@ export const NOTIFICATION_CHANNELS = ['EMAIL', 'SMS', 'WHATSAPP', 'WEB_PUSH'] as
18
18
  export type NotificationChannel = (typeof NOTIFICATION_CHANNELS)[number];
19
19
 
20
20
  /**
21
- * The DEFAULT preference categories — the granularity at which a user chooses
22
- * channels. Every notification `type` belongs to exactly one category via its
23
- * generator.
21
+ * The preference categories are the HOST's, and required.
24
22
  *
25
- * A host may replace the set entirely (`categories` on the server config): it
26
- * is product vocabulary, not machinery. These four are the future-pay set, and
27
- * the packaged migration deliberately puts **no CHECK** on
28
- * `notifications.category` — a closed set in the schema would be wrong for
29
- * every host but the first. Unlike `channel` and `status`, which ARE the
30
- * library's own closed sets and do carry one.
23
+ * There used to be a `NOTIFICATION_CATEGORIES = ['orders','payments','stock',
24
+ * 'system']` here — one product's set — and `taxonomyOf` fell back to it
25
+ * whenever a host passed none. The docstring argued the case itself: "it is
26
+ * product vocabulary, not machinery", and then shipped the vocabulary anyway as
27
+ * the default, which is the only part a forgetful host would ever see.
28
+ *
29
+ * The consequence was quiet rather than loud: the settings screen renders four
30
+ * rows a foreign host never chose, its own categories are absent, and every
31
+ * preference a user sets is filed against a taxonomy nothing else in that
32
+ * system uses. Nothing throws, because `category` is deliberately a free string
33
+ * — the packaged migration puts **no CHECK** on it, precisely because a closed
34
+ * set would be wrong for every host but the first. That freedom is what made
35
+ * the default undetectable.
36
+ *
37
+ * `channel` and `status` are different and keep their CHECKs: those ARE this
38
+ * library's own closed sets.
31
39
  */
32
- export const NOTIFICATION_CATEGORIES = ['orders', 'payments', 'stock', 'system'] as const;
33
40
  export type NotificationCategory = string;
34
41
 
35
42
  /**
@@ -140,13 +147,22 @@ export interface NotificationTaxonomy {
140
147
  categories: readonly NotificationCategory[];
141
148
  }
142
149
 
143
- /** The taxonomy in force, defaulted to the four future-pay categories. */
150
+ /**
151
+ * The taxonomy in force. `categories` is REQUIRED — see above.
152
+ *
153
+ * The empty check was already here and stays: an empty list and a missing one
154
+ * are the same mistake, and both now fail at assembly rather than rendering an
155
+ * empty settings screen or somebody else's four rows.
156
+ */
144
157
  export function taxonomyOf(config: {
145
- categories?: readonly NotificationCategory[];
158
+ categories: readonly NotificationCategory[];
146
159
  }): NotificationTaxonomy {
147
- const categories = config.categories ?? NOTIFICATION_CATEGORIES;
148
- if (categories.length === 0) {
149
- throw new Error('@12-apps/notifications: `categories` must not be empty.');
160
+ const categories = config.categories;
161
+ if (!categories || categories.length === 0) {
162
+ throw new Error(
163
+ '@12-apps/notifications: `categories` is required and must not be empty — ' +
164
+ 'the preference categories are the host\'s product vocabulary.',
165
+ );
150
166
  }
151
167
  return { categories: [...categories] };
152
168
  }