@12-apps/notifications 4.8.0 → 4.10.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 (114) hide show
  1. package/ADOPTING.md +173 -1
  2. package/README.md +138 -0
  3. package/dist/catalog-BOVZ2b-b.d.ts +132 -0
  4. package/dist/{chunk-M5XTROYM.js → chunk-5Y7QRORV.js} +8 -4
  5. package/dist/chunk-5Y7QRORV.js.map +1 -0
  6. package/dist/{chunk-BGI4BQ66.js → chunk-6W7INOYQ.js} +1 -1
  7. package/dist/chunk-6W7INOYQ.js.map +1 -0
  8. package/dist/chunk-EKUSNUBT.js +222 -0
  9. package/dist/chunk-EKUSNUBT.js.map +1 -0
  10. package/dist/chunk-FTFLU6KH.js +114 -0
  11. package/dist/chunk-FTFLU6KH.js.map +1 -0
  12. package/dist/chunk-JCVRQ42B.js +226 -0
  13. package/dist/chunk-JCVRQ42B.js.map +1 -0
  14. package/dist/chunk-RTURLH5U.js +33 -0
  15. package/dist/chunk-RTURLH5U.js.map +1 -0
  16. package/dist/{chunk-GGAZOLNN.js → chunk-SWOWHIFE.js} +2 -2
  17. package/dist/{chunk-7234ROOP.js → chunk-WVRODNXQ.js} +28 -6
  18. package/dist/chunk-WVRODNXQ.js.map +1 -0
  19. package/dist/chunk-ZFIYBNZ7.js +502 -0
  20. package/dist/chunk-ZFIYBNZ7.js.map +1 -0
  21. package/dist/{chunk-KU4FVOFA.js → chunk-ZIR3ILFH.js} +2 -2
  22. package/dist/{create-api-notifications-WaexrZnn.d.ts → create-api-notifications-CcPYrM3p.d.ts} +35 -4
  23. package/dist/{create-web-notifications-DrgqtU4Q.d.ts → create-web-notifications-_NVYmlvy.d.ts} +89 -4
  24. package/dist/email/index.d.ts +26 -0
  25. package/dist/email/index.js +20 -0
  26. package/dist/email/index.js.map +1 -0
  27. package/dist/email/locales.d.ts +43 -0
  28. package/dist/email/locales.js +29 -0
  29. package/dist/email/locales.js.map +1 -0
  30. package/dist/email/previews/hono.d.ts +46 -0
  31. package/dist/email/previews/hono.js +33 -0
  32. package/dist/email/previews/hono.js.map +1 -0
  33. package/dist/email/previews/index.d.ts +67 -0
  34. package/dist/email/previews/index.js +12 -0
  35. package/dist/email/previews/index.js.map +1 -0
  36. package/dist/email/previews/react/index.d.ts +43 -0
  37. package/dist/email/previews/react/index.js +58 -0
  38. package/dist/email/previews/react/index.js.map +1 -0
  39. package/dist/{generators-CH5m8Ucj.d.ts → generators-qAD4fNPq.d.ts} +1 -1
  40. package/dist/hono/index.d.ts +6 -5
  41. package/dist/hono/index.js +5 -3
  42. package/dist/hono/index.js.map +1 -1
  43. package/dist/index.d.ts +4 -3
  44. package/dist/index.js +15 -5
  45. package/dist/{jobs-CcsSA0YR.d.ts → jobs--fex87-q.d.ts} +1 -1
  46. package/dist/live-DYxEFO49.d.ts +158 -0
  47. package/dist/manifest/index.d.ts +39 -1
  48. package/dist/manifest/index.js +9 -0
  49. package/dist/manifest/index.js.map +1 -1
  50. package/dist/manifest/server.d.ts +32 -7
  51. package/dist/manifest/server.js +16 -4
  52. package/dist/manifest/server.js.map +1 -1
  53. package/dist/manifest/web.d.ts +19 -4
  54. package/dist/manifest/web.js +9 -1
  55. package/dist/manifest/web.js.map +1 -1
  56. package/dist/{panel-UFXNO4AF.js → panel-T36JEMO3.js} +37 -13
  57. package/dist/panel-T36JEMO3.js.map +1 -0
  58. package/dist/{preferences-screen-V3ZBWP7A.js → preferences-screen-SXUIGECY.js} +2 -2
  59. package/dist/preview-screen-DYJRAnAY.d.ts +83 -0
  60. package/dist/react/index.d.ts +55 -6
  61. package/dist/react/index.js +16 -10
  62. package/dist/server/index.d.ts +8 -7
  63. package/dist/server/index.js +6 -4
  64. package/dist/template-Dlt5S54z.d.ts +196 -0
  65. package/dist/{types-CYJN1xCM.d.ts → types-BlqZkCWZ.d.ts} +11 -2
  66. package/dist/web-push/index.d.ts +2 -2
  67. package/dist/{web-push-iER5StBm.d.ts → web-push-Dnyaha2z.d.ts} +21 -1
  68. package/dist/{wire-CmRrB3_s.d.ts → wire-BG1kuoXX.d.ts} +1 -1
  69. package/package.json +22 -2
  70. package/src/email/chrome.en-US.ts +8 -0
  71. package/src/email/chrome.pt-BR.ts +21 -0
  72. package/src/email/index.ts +43 -0
  73. package/src/email/locales.ts +29 -0
  74. package/src/email/previews/catalog.ts +208 -0
  75. package/src/email/previews/hono.ts +69 -0
  76. package/src/email/previews/index.ts +26 -0
  77. package/src/email/previews/react/copy.en-US.ts +24 -0
  78. package/src/email/previews/react/copy.pt-BR.ts +24 -0
  79. package/src/email/previews/react/copy.ts +42 -0
  80. package/src/email/previews/react/index.tsx +14 -0
  81. package/src/email/previews/react/loadable.tsx +83 -0
  82. package/src/email/previews/react/message-list.tsx +133 -0
  83. package/src/email/previews/react/message-view.tsx +167 -0
  84. package/src/email/previews/react/preview-screen.tsx +342 -0
  85. package/src/email/previews/react/transport.ts +60 -0
  86. package/src/email/previews/routes.ts +111 -0
  87. package/src/email/template.ts +365 -0
  88. package/src/email/text.ts +59 -0
  89. package/src/email/theme.ts +77 -0
  90. package/src/index.ts +10 -0
  91. package/src/live.ts +179 -0
  92. package/src/manifest/index.ts +37 -0
  93. package/src/manifest/server.ts +23 -0
  94. package/src/manifest/web.ts +13 -0
  95. package/src/react/create-web-notifications.tsx +15 -1
  96. package/src/react/index.ts +29 -0
  97. package/src/react/live-card.tsx +279 -0
  98. package/src/react/live-config.ts +79 -0
  99. package/src/react/live-section.tsx +141 -0
  100. package/src/react/panel-lazy.tsx +3 -0
  101. package/src/react/panel.tsx +57 -10
  102. package/src/react/relative-time.ts +14 -2
  103. package/src/server/transports/email.ts +62 -0
  104. package/src/server/transports/web-push.ts +22 -0
  105. package/src/types.ts +10 -1
  106. package/dist/chunk-7234ROOP.js.map +0 -1
  107. package/dist/chunk-BGI4BQ66.js.map +0 -1
  108. package/dist/chunk-M5XTROYM.js.map +0 -1
  109. package/dist/chunk-O5BVUXPO.js +0 -22
  110. package/dist/chunk-O5BVUXPO.js.map +0 -1
  111. package/dist/panel-UFXNO4AF.js.map +0 -1
  112. /package/dist/{chunk-GGAZOLNN.js.map → chunk-SWOWHIFE.js.map} +0 -0
  113. /package/dist/{chunk-KU4FVOFA.js.map → chunk-ZIR3ILFH.js.map} +0 -0
  114. /package/dist/{preferences-screen-V3ZBWP7A.js.map → preferences-screen-SXUIGECY.js.map} +0 -0
@@ -1,6 +1,7 @@
1
- import { JSX, ComponentType } from 'react';
2
- import { L as ListNotificationsResult, a as ChannelRow, I as InboxNotification, N as NotificationMessages } from './wire-CmRrB3_s.js';
3
- import { b as NotificationChannel } from './types-CYJN1xCM.js';
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';
4
+ import { b as LiveActivity } from './live-DYxEFO49.js';
4
5
 
5
6
  /**
6
7
  * How the notification screens reach their data (12-15) — the report-builder
@@ -221,6 +222,81 @@ interface BellButtonProps {
221
222
  enabled?: boolean;
222
223
  }
223
224
 
225
+ /**
226
+ * The host's half of live activities: where they come from, and what they say.
227
+ *
228
+ * Both are the host's because neither can be this package's. It has no idea
229
+ * what is happening — only the application does — and it has no words, for the
230
+ * same reason `NotificationMessages` is required rather than defaulted: a
231
+ * package that ships one product's sentences ships them to every other product
232
+ * too, and the adopter reached by saying nothing is the one who never notices.
233
+ */
234
+
235
+ /**
236
+ * Where the surface gets the activities that are live RIGHT NOW.
237
+ *
238
+ * A HOOK rather than a fetcher, and rather than a factory-time `subscribe`,
239
+ * because the answer almost always lives in React context: the tenant, the
240
+ * session, the host's query client. `NotificationsSignalHook` exists for
241
+ * exactly this reason one seam over, and a host in that shape had no way to
242
+ * pass anything at all.
243
+ *
244
+ * `active` is whether the surface currently needs the answer. It is a HINT
245
+ * about need, never about correctness: a host that ignores it and always
246
+ * answers is behaving correctly and merely paying for it.
247
+ *
248
+ * What it is NOT is the only thing standing between a shut panel and a query.
249
+ * The panel is fetched lazily and renders nothing until somebody first opens
250
+ * the bell, and the drawer unmounts its content on close — so a host that
251
+ * simply reads `active` and ignores it still issues nothing while the panel is
252
+ * away. `active` is `false` for the frames of the closing transition, which is
253
+ * where it earns its keep: a query told to stand down there does not fire one
254
+ * last time on the way out.
255
+ *
256
+ * Return whatever is live, newest activity first or in whatever order the host
257
+ * means; the surface renders them in the order given. An empty array is the
258
+ * normal answer and renders nothing — no heading, no empty state, no gap.
259
+ */
260
+ type LiveActivitiesHook = (options: {
261
+ active: boolean;
262
+ }) => readonly LiveActivity[];
263
+ /** The three sentences the live section says. */
264
+ interface LiveActivityMessages {
265
+ /** The heading over the pinned entries, e.g. "Em andamento". */
266
+ sectionTitle: string;
267
+ /** The link's accessible name, e.g. `(title) => `Abrir ${title}``. */
268
+ openActivity: (title: string) => string;
269
+ /**
270
+ * The "last moved" line, given an already-relative time.
271
+ *
272
+ * Takes the phrase rather than the instant so the relative wording stays in
273
+ * ONE place — `relativeTime` and the inbox rows' `há 5 min` — and a host
274
+ * cannot end up with two vocabularies for the same duration in one panel.
275
+ */
276
+ updated: (relative: string) => string;
277
+ }
278
+ /** Live activities, as a host turns them on. */
279
+ interface LiveActivitiesConfig {
280
+ useActivities: LiveActivitiesHook;
281
+ messages: LiveActivityMessages;
282
+ /**
283
+ * The mark on the left of a card — the fastest read, before any words.
284
+ *
285
+ * A node rather than a field on {@link LiveActivity} so the contract stays
286
+ * framework-free: the root entry is shared with the server half, and a
287
+ * `ReactNode` in it would put React on that import path for a backend that
288
+ * only ever writes rows. The host switches on `kind`, which is what `kind` is
289
+ * for. No renderer, no mark, and the card is text — never a placeholder box.
290
+ *
291
+ * **Return something PRESENTATIONAL.** The mark is drawn inside the card's
292
+ * own `<button>` and inside an `aria-hidden` wrapper, so a focusable node
293
+ * here is a button inside a button — invalid HTML, and the exact defect the
294
+ * card's structure exists to prevent — as well as a control hidden from the
295
+ * accessibility tree. An icon or an `<svg>`; not a control.
296
+ */
297
+ renderIcon?: (activity: LiveActivity) => ReactNode;
298
+ }
299
+
224
300
  /**
225
301
  * The notification-centre slide-over: newest-first list with unread styling,
226
302
  * per-item open (marks read + deep-links), soft delete, mark-all, empty /
@@ -322,6 +398,15 @@ interface NotificationsWebConfig {
322
398
  useSignal?: NotificationsSignalHook;
323
399
  /** The browser push enable step's host seams (SW path, platform hint). */
324
400
  webPush?: WebPushSetupConfig;
401
+ /**
402
+ * LIVE ACTIVITIES — the ongoing-state entries pinned above the inbox list.
403
+ *
404
+ * Opt-in, and absent means absent: a host that passes nothing gets the panel
405
+ * it had, with no section, no heading and no reserved space. See
406
+ * `./live-config` for the two things a host has to supply (where they come
407
+ * from, and what the section says) and `../live` for what one IS.
408
+ */
409
+ liveActivities?: LiveActivitiesConfig;
325
410
  }
326
411
  interface WebNotifications {
327
412
  /**
@@ -362,4 +447,4 @@ interface WebNotifications {
362
447
  }
363
448
  declare function createWebNotifications(config: NotificationsWebConfig): WebNotifications;
364
449
 
365
- export { BADGE_POLL_MS as B, type InboxListStatus as I, type NotificationsApiClient as N, PAGE_SIZE as P, type WebNotifications as W, BADGE_RECONCILE_MS as a, type BellButtonProps as b, createWebNotifications as c, type InboxState as d, type InboxStore as e, NotificationsHttpError as f, type NotificationsPanelProps as g, type NotificationsResult as h, type NotificationsSignalHook as i, type NotificationsSubscribe as j, type NotificationsTransport as k, type NotificationsWebConfig as l, type PreferencesPayload as m, type PreferencesScreenProps as n, type PushRegistrationPayload as o, type WebPushPlatformHint as p, type WebPushSetupConfig as q, createInboxStore as r, createNotificationsApiClient as s, httpNotificationsTransport as t, useInboxList as u, useInboxState as v, useUnreadCount as w };
450
+ export { BADGE_POLL_MS as B, type InboxListStatus as I, type LiveActivitiesConfig as L, type NotificationsApiClient as N, PAGE_SIZE as P, type WebNotifications as W, BADGE_RECONCILE_MS as a, type BellButtonProps as b, createWebNotifications as c, type InboxState as d, type InboxStore as e, type LiveActivitiesHook as f, type LiveActivityMessages as g, NotificationsHttpError as h, type NotificationsPanelProps as i, type NotificationsResult as j, type NotificationsSignalHook as k, type NotificationsSubscribe as l, type NotificationsTransport as m, type NotificationsWebConfig as n, type PreferencesPayload as o, type PreferencesScreenProps as p, type PushRegistrationPayload as q, type WebPushPlatformHint as r, type WebPushSetupConfig as s, createInboxStore as t, createNotificationsApiClient as u, httpNotificationsTransport as v, useInboxList as w, useInboxState as x, useUnreadCount as y };
@@ -0,0 +1,26 @@
1
+ import { E as EmailDocument } from '../template-Dlt5S54z.js';
2
+ export { a as EMAIL_CONTENT_WIDTH, b as EMAIL_FONT_STACK, c as EmailAction, d as EmailChromeCopy, e as EmailFact, f as EmailTheme, N as NEUTRAL_EMAIL_THEME, R as RenderedEmail, r as renderEmail, g as renderEmailHtml, s as safeHref } from '../template-Dlt5S54z.js';
3
+
4
+ /**
5
+ * The plain-text half.
6
+ *
7
+ * Not a courtesy. Every major spam filter scores a `text/html` part with no
8
+ * `text/plain` twin, and a watch, a terminal client and a screen reader in
9
+ * plain-text mode show this and nothing else.
10
+ *
11
+ * It renders from the SAME `EmailDocument` the HTML half does, which is the
12
+ * property that matters: a line added to one is in the other, so the two cannot
13
+ * drift. Its own module only because the HTML renderer is already a file's
14
+ * worth of client workarounds — the seam is the output format, not the content.
15
+ */
16
+ /**
17
+ * Render the document as the plain-text half.
18
+ *
19
+ * Not a courtesy. Every major spam filter scores a `text/html` part with no
20
+ * `text/plain` twin, and a watch, a terminal client and a screen reader in
21
+ * plain-text mode show this and nothing else. It is rendered from the SAME
22
+ * object, so it cannot say less than the HTML does.
23
+ */
24
+ declare function renderEmailText(document: EmailDocument): string;
25
+
26
+ export { EmailDocument, renderEmailText };
@@ -0,0 +1,20 @@
1
+ import {
2
+ EMAIL_CONTENT_WIDTH,
3
+ EMAIL_FONT_STACK,
4
+ NEUTRAL_EMAIL_THEME,
5
+ renderEmail,
6
+ renderEmailHtml,
7
+ renderEmailText,
8
+ safeHref
9
+ } from "../chunk-EKUSNUBT.js";
10
+ import "../chunk-7QVYU63E.js";
11
+ export {
12
+ EMAIL_CONTENT_WIDTH,
13
+ EMAIL_FONT_STACK,
14
+ NEUTRAL_EMAIL_THEME,
15
+ renderEmail,
16
+ renderEmailHtml,
17
+ renderEmailText,
18
+ safeHref
19
+ };
20
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -0,0 +1,43 @@
1
+ import { d as EmailChromeCopy } from '../template-Dlt5S54z.js';
2
+
3
+ /** US English — the twin that makes {@link PT_BR_EMAIL_CHROME} a choice. */
4
+ declare const EN_US_EMAIL_CHROME: EmailChromeCopy;
5
+
6
+ /**
7
+ * Brazilian Portuguese, as a NAMED pack.
8
+ *
9
+ * A host passes this by name — `chrome: PT_BR_EMAIL_CHROME`, or
10
+ * `localeCopy(EMAIL_CHROME)` for a host whose readers do not share one
11
+ * language. It is never a default: a pack a host CHOOSES is a decision in a
12
+ * diff, and a pack a package applies silently is another product's voice in
13
+ * somebody's inbox.
14
+ *
15
+ * The tagline names the brand and stops. It is deliberately not a description
16
+ * of what the product does — that sentence is the host's, it belongs to a
17
+ * vocabulary this package cannot know, and a package that guessed one would be
18
+ * putting words in every adopter's footer.
19
+ */
20
+ declare const PT_BR_EMAIL_CHROME: EmailChromeCopy;
21
+
22
+ /**
23
+ * The layout's own words, in every language this package ships.
24
+ *
25
+ * Its own subpath (`@12-apps/notifications/email/locales`) for the reason every
26
+ * copy-shipping package here uses one: keeping the packs off the layout entry
27
+ * is what lets that entry stay the framework-free mechanism a browser can
28
+ * import, and it is what makes "which language" a decision a host takes by
29
+ * name rather than one this package takes by default.
30
+ *
31
+ * Shaped as `Record<tag, pack>` rather than importing `@12-apps/i18n`'s
32
+ * `LocalePack`: a package must stay liftable into a repo that has never heard
33
+ * of that one, so the type is a local structural mirror. The two agree by tag,
34
+ * and `scripts/locale-coverage-gate.mjs` is what checks that they do.
35
+ *
36
+ * A host with ONE audience passes a pack by name. A host whose readers do not
37
+ * share a language passes `localeCopy(EMAIL_CHROME)` and each message is
38
+ * written in its own recipient's language — which is the whole reason
39
+ * `EmailDocument.chrome` is resolved per message rather than at a mount.
40
+ */
41
+ declare const EMAIL_CHROME: Readonly<Record<string, EmailChromeCopy>>;
42
+
43
+ export { EMAIL_CHROME, EN_US_EMAIL_CHROME, PT_BR_EMAIL_CHROME };
@@ -0,0 +1,29 @@
1
+ import {
2
+ __name
3
+ } from "../chunk-7QVYU63E.js";
4
+
5
+ // src/email/chrome.en-US.ts
6
+ var EN_US_EMAIL_CHROME = {
7
+ fallbackHint: "If the button above does not work, copy and paste this address into your browser:",
8
+ automated: "This is an automated message. Please do not reply to this email.",
9
+ tagline: /* @__PURE__ */ __name((brand) => `Sent by ${brand}.`, "tagline")
10
+ };
11
+
12
+ // src/email/chrome.pt-BR.ts
13
+ var PT_BR_EMAIL_CHROME = {
14
+ fallbackHint: "Se o bot\xE3o acima n\xE3o funcionar, copie e cole este endere\xE7o no seu navegador:",
15
+ automated: "Esta \xE9 uma mensagem autom\xE1tica. N\xE3o responda a este e-mail.",
16
+ tagline: /* @__PURE__ */ __name((brand) => `Enviado por ${brand}.`, "tagline")
17
+ };
18
+
19
+ // src/email/locales.ts
20
+ var EMAIL_CHROME = {
21
+ "pt-BR": PT_BR_EMAIL_CHROME,
22
+ "en-US": EN_US_EMAIL_CHROME
23
+ };
24
+ export {
25
+ EMAIL_CHROME,
26
+ EN_US_EMAIL_CHROME,
27
+ PT_BR_EMAIL_CHROME
28
+ };
29
+ //# sourceMappingURL=locales.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/email/chrome.en-US.ts","../../src/email/chrome.pt-BR.ts","../../src/email/locales.ts"],"sourcesContent":["import type { EmailChromeCopy } from './template';\n\n/** US English — the twin that makes {@link PT_BR_EMAIL_CHROME} a choice. */\nexport const EN_US_EMAIL_CHROME: EmailChromeCopy = {\n fallbackHint: 'If the button above does not work, copy and paste this address into your browser:',\n automated: 'This is an automated message. Please do not reply to this email.',\n tagline: (brand) => `Sent by ${brand}.`,\n};\n","import type { EmailChromeCopy } from './template';\n\n/**\n * Brazilian Portuguese, as a NAMED pack.\n *\n * A host passes this by name — `chrome: PT_BR_EMAIL_CHROME`, or\n * `localeCopy(EMAIL_CHROME)` for a host whose readers do not share one\n * language. It is never a default: a pack a host CHOOSES is a decision in a\n * diff, and a pack a package applies silently is another product's voice in\n * somebody's inbox.\n *\n * The tagline names the brand and stops. It is deliberately not a description\n * of what the product does — that sentence is the host's, it belongs to a\n * vocabulary this package cannot know, and a package that guessed one would be\n * putting words in every adopter's footer.\n */\nexport const PT_BR_EMAIL_CHROME: EmailChromeCopy = {\n fallbackHint: 'Se o botão acima não funcionar, copie e cole este endereço no seu navegador:',\n automated: 'Esta é uma mensagem automática. Não responda a este e-mail.',\n tagline: (brand) => `Enviado por ${brand}.`,\n};\n","import { EN_US_EMAIL_CHROME } from './chrome.en-US';\nimport { PT_BR_EMAIL_CHROME } from './chrome.pt-BR';\nimport type { EmailChromeCopy } from './template';\n\n/**\n * The layout's own words, in every language this package ships.\n *\n * Its own subpath (`@12-apps/notifications/email/locales`) for the reason every\n * copy-shipping package here uses one: keeping the packs off the layout entry\n * is what lets that entry stay the framework-free mechanism a browser can\n * import, and it is what makes \"which language\" a decision a host takes by\n * name rather than one this package takes by default.\n *\n * Shaped as `Record<tag, pack>` rather than importing `@12-apps/i18n`'s\n * `LocalePack`: a package must stay liftable into a repo that has never heard\n * of that one, so the type is a local structural mirror. The two agree by tag,\n * and `scripts/locale-coverage-gate.mjs` is what checks that they do.\n *\n * A host with ONE audience passes a pack by name. A host whose readers do not\n * share a language passes `localeCopy(EMAIL_CHROME)` and each message is\n * written in its own recipient's language — which is the whole reason\n * `EmailDocument.chrome` is resolved per message rather than at a mount.\n */\nexport const EMAIL_CHROME: Readonly<Record<string, EmailChromeCopy>> = {\n 'pt-BR': PT_BR_EMAIL_CHROME,\n 'en-US': EN_US_EMAIL_CHROME,\n};\n\nexport { EN_US_EMAIL_CHROME, PT_BR_EMAIL_CHROME };\n"],"mappings":";;;;;AAGO,IAAM,qBAAsC;AAAA,EACjD,cAAc;AAAA,EACd,WAAW;AAAA,EACX,SAAS,wBAAC,UAAU,WAAW,KAAK,KAA3B;AACX;;;ACSO,IAAM,qBAAsC;AAAA,EACjD,cAAc;AAAA,EACd,WAAW;AAAA,EACX,SAAS,wBAAC,UAAU,eAAe,KAAK,KAA/B;AACX;;;ACGO,IAAM,eAA0D;AAAA,EACrE,SAAS;AAAA,EACT,SAAS;AACX;","names":[]}
@@ -0,0 +1,46 @@
1
+ import { Context, Hono } from 'hono';
2
+ import { E as EmailPreviewsConfig } from '../../catalog-BOVZ2b-b.js';
3
+ import '../../template-Dlt5S54z.js';
4
+
5
+ /**
6
+ * `@12-apps/notifications/email/previews/hono` — the catalogue as a router.
7
+ *
8
+ * The framework-neutral descriptors in `./server` are the contract; this is the
9
+ * adapter for the framework we happen to use, behind its own subpath with
10
+ * `hono` as an OPTIONAL peer — a host on Express, or one that only wants the
11
+ * layout, never resolves it.
12
+ *
13
+ * A host writes:
14
+ *
15
+ * const previews = emailPreviewsRouter({ sources, locales, defaultLocale });
16
+ * app.use('/api/platform/email-previews/*', requirePlatformOperator);
17
+ * app.route('/api/platform/email-previews', previews.router);
18
+ *
19
+ * ## The `use` line above is not decoration
20
+ *
21
+ * This surface publishes a host's whole transactional-mail inventory and the
22
+ * exact wording of its verification and reset mails. The routes declare no
23
+ * session of their own because a package cannot know who a host lets look — so
24
+ * the gate is the host's, it is REQUIRED, and it is written here rather than
25
+ * left to be inferred, because the failure is silent: an ungated mount answers
26
+ * every stranger and nothing about it looks wrong.
27
+ */
28
+ interface EmailPreviewsHonoConfig extends EmailPreviewsConfig {
29
+ /**
30
+ * Optional last-resort refusal, for a host that would rather state the gate
31
+ * once here than mount middleware around the router.
32
+ *
33
+ * Returning `false` answers 403 before any handler runs. Absent means the
34
+ * host has gated the mount itself — which is the ordinary case, and why this
35
+ * is not required: a package that demanded its own guard would be a second
36
+ * authorization system beside the one the host already runs.
37
+ */
38
+ readonly allow?: (c: Context) => Promise<boolean> | boolean;
39
+ }
40
+ /** The mounted router, plus the descriptors it was built from. */
41
+ interface EmailPreviewsRouter {
42
+ router: Hono;
43
+ }
44
+ declare function emailPreviewsRouter(config: EmailPreviewsHonoConfig): EmailPreviewsRouter;
45
+
46
+ export { type EmailPreviewsHonoConfig, type EmailPreviewsRouter, emailPreviewsRouter };
@@ -0,0 +1,33 @@
1
+ import {
2
+ emailPreviewRoutes
3
+ } from "../../chunk-FTFLU6KH.js";
4
+ import {
5
+ __name
6
+ } from "../../chunk-7QVYU63E.js";
7
+
8
+ // src/email/previews/hono.ts
9
+ import { Hono } from "hono";
10
+ function emailPreviewsRouter(config) {
11
+ const router = new Hono();
12
+ for (const route of emailPreviewRoutes(config)) {
13
+ router.get(route.path, async (c) => {
14
+ if (config.allow && !await config.allow(c)) {
15
+ return c.json({ error: "Forbidden." }, 403);
16
+ }
17
+ const response = await route.handle({
18
+ params: c.req.param(),
19
+ // `Object.fromEntries` over the parsed query rather than the raw string:
20
+ // a repeated `?locale=` is a caller error, and taking the last value is
21
+ // the same answer every framework here gives.
22
+ query: c.req.query()
23
+ });
24
+ return c.json(response.body, response.status);
25
+ });
26
+ }
27
+ return { router };
28
+ }
29
+ __name(emailPreviewsRouter, "emailPreviewsRouter");
30
+ export {
31
+ emailPreviewsRouter
32
+ };
33
+ //# sourceMappingURL=hono.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/email/previews/hono.ts"],"sourcesContent":["import { Hono } from 'hono';\nimport type { Context } from 'hono';\n\nimport { emailPreviewRoutes } from './routes';\nimport type { EmailPreviewsConfig } from './catalog';\n\n/**\n * `@12-apps/notifications/email/previews/hono` — the catalogue as a router.\n *\n * The framework-neutral descriptors in `./server` are the contract; this is the\n * adapter for the framework we happen to use, behind its own subpath with\n * `hono` as an OPTIONAL peer — a host on Express, or one that only wants the\n * layout, never resolves it.\n *\n * A host writes:\n *\n * const previews = emailPreviewsRouter({ sources, locales, defaultLocale });\n * app.use('/api/platform/email-previews/*', requirePlatformOperator);\n * app.route('/api/platform/email-previews', previews.router);\n *\n * ## The `use` line above is not decoration\n *\n * This surface publishes a host's whole transactional-mail inventory and the\n * exact wording of its verification and reset mails. The routes declare no\n * session of their own because a package cannot know who a host lets look — so\n * the gate is the host's, it is REQUIRED, and it is written here rather than\n * left to be inferred, because the failure is silent: an ungated mount answers\n * every stranger and nothing about it looks wrong.\n */\n\nexport interface EmailPreviewsHonoConfig extends EmailPreviewsConfig {\n /**\n * Optional last-resort refusal, for a host that would rather state the gate\n * once here than mount middleware around the router.\n *\n * Returning `false` answers 403 before any handler runs. Absent means the\n * host has gated the mount itself — which is the ordinary case, and why this\n * is not required: a package that demanded its own guard would be a second\n * authorization system beside the one the host already runs.\n */\n readonly allow?: (c: Context) => Promise<boolean> | boolean;\n}\n\n/** The mounted router, plus the descriptors it was built from. */\nexport interface EmailPreviewsRouter {\n router: Hono;\n}\n\nexport function emailPreviewsRouter(config: EmailPreviewsHonoConfig): EmailPreviewsRouter {\n const router = new Hono();\n\n for (const route of emailPreviewRoutes(config)) {\n router.get(route.path, async (c) => {\n if (config.allow && !(await config.allow(c))) {\n return c.json({ error: 'Forbidden.' }, 403);\n }\n const response = await route.handle({\n params: c.req.param() as Record<string, string | undefined>,\n // `Object.fromEntries` over the parsed query rather than the raw string:\n // a repeated `?locale=` is a caller error, and taking the last value is\n // the same answer every framework here gives.\n query: c.req.query() as Record<string, string | undefined>,\n });\n return c.json(response.body as Record<string, unknown>, response.status as 200 | 400 | 404);\n });\n }\n\n return { router };\n}\n"],"mappings":";;;;;;;;AAAA,SAAS,YAAY;AAgDd,SAAS,oBAAoB,QAAsD;AACxF,QAAM,SAAS,IAAI,KAAK;AAExB,aAAW,SAAS,mBAAmB,MAAM,GAAG;AAC9C,WAAO,IAAI,MAAM,MAAM,OAAO,MAAM;AAClC,UAAI,OAAO,SAAS,CAAE,MAAM,OAAO,MAAM,CAAC,GAAI;AAC5C,eAAO,EAAE,KAAK,EAAE,OAAO,aAAa,GAAG,GAAG;AAAA,MAC5C;AACA,YAAM,WAAW,MAAM,MAAM,OAAO;AAAA,QAClC,QAAQ,EAAE,IAAI,MAAM;AAAA;AAAA;AAAA;AAAA,QAIpB,OAAO,EAAE,IAAI,MAAM;AAAA,MACrB,CAAC;AACD,aAAO,EAAE,KAAK,SAAS,MAAiC,SAAS,MAAyB;AAAA,IAC5F,CAAC;AAAA,EACH;AAEA,SAAO,EAAE,OAAO;AAClB;AApBgB;","names":[]}
@@ -0,0 +1,67 @@
1
+ import { E as EmailPreviewsConfig } from '../../catalog-BOVZ2b-b.js';
2
+ export { A as ApiEmailPreviews, D as DuplicateEmailPreviewIdError, a as EmailPreviewCoverage, b as EmailPreviewDetail, c as EmailPreviewIndex, d as EmailPreviewMessage, e as EmailPreviewRow, f as EmailPreviewSource, g as createEmailPreviews } from '../../catalog-BOVZ2b-b.js';
3
+ import '../../template-Dlt5S54z.js';
4
+
5
+ /**
6
+ * The two endpoints over the catalogue, as descriptors.
7
+ *
8
+ * `GET /` answers the list — one row per message, a subject each, no rendered
9
+ * bytes — and `GET /:id` answers one whole document. Two calls rather than one
10
+ * because a rendered mail is 6-10 KB and a catalogue of twenty would be twenty
11
+ * times that on a screen where the operator reads one.
12
+ *
13
+ * ## Nothing here can send anything
14
+ *
15
+ * The surface holds no driver, no transport and no address. `render` is pure by
16
+ * the contract `EmailPreviewMessage` states, so the one mistake a preview
17
+ * surface must be incapable of — putting a sample in somebody's inbox — is not
18
+ * reachable from this code at all rather than merely not done.
19
+ *
20
+ * ## The gate is the HOST's — but the POSTURE is declared here
21
+ *
22
+ * Every route carries `kind: 'authenticated'`, the wiring contract's own word
23
+ * for "behind the host's session resolution and its RBAC". A package cannot
24
+ * know WHICH gate a host uses, and this one does not try: it names no
25
+ * permission id, because the ids belong to whichever host mounts it.
26
+ *
27
+ * What it must not do is stay silent. `public` is the contract's word for
28
+ * anonymous-by-design, and the contract's DEFAULT is `authenticated` — so a
29
+ * descriptor that declared nothing would still read as authenticated to a
30
+ * host's gates, while saying nothing to the person adopting it. This surface
31
+ * publishes a host's whole transactional-mail inventory and the exact wording
32
+ * and link shape of its verification and password-reset mails, which is the
33
+ * reference somebody writing a convincing phishing mail would want. That is
34
+ * worth one word in the descriptor rather than a sentence in a document.
35
+ *
36
+ * `ADOPTING.md` states the obligation in prose as well, because a host still
37
+ * has to supply the gate this word only asks for.
38
+ *
39
+ * An unknown LOCALE is a 400 rather than a silent fall back to the default.
40
+ * This is a diagnostic surface: quietly answering the default language to
41
+ * `?locale=es-AR` looks exactly like a product with no Spanish, which is the
42
+ * question the operator was asking.
43
+ */
44
+ /** Twin of the wiring contract's request; no import, so this package stays liftable. */
45
+ interface EmailPreviewRequest {
46
+ /** The path segment after the mount, when the caller asked for one message. */
47
+ params?: Readonly<Record<string, string | undefined>>;
48
+ query?: Readonly<Record<string, string | undefined>>;
49
+ }
50
+ interface EmailPreviewResponse {
51
+ status: number;
52
+ body: unknown;
53
+ }
54
+ interface EmailPreviewRoute {
55
+ method: 'GET';
56
+ path: string;
57
+ /**
58
+ * The wiring contract's `WireRouteKind`, restated. Always `authenticated`
59
+ * here: the host supplies the gate, and this is the descriptor asking for
60
+ * one — see the docblock above for why silence was not an option.
61
+ */
62
+ kind: 'authenticated';
63
+ handle(request: EmailPreviewRequest): Promise<EmailPreviewResponse>;
64
+ }
65
+ declare function emailPreviewRoutes(config: EmailPreviewsConfig): EmailPreviewRoute[];
66
+
67
+ export { type EmailPreviewRequest, type EmailPreviewResponse, type EmailPreviewRoute, EmailPreviewsConfig, emailPreviewRoutes };
@@ -0,0 +1,12 @@
1
+ import {
2
+ DuplicateEmailPreviewIdError,
3
+ createEmailPreviews,
4
+ emailPreviewRoutes
5
+ } from "../../chunk-FTFLU6KH.js";
6
+ import "../../chunk-7QVYU63E.js";
7
+ export {
8
+ DuplicateEmailPreviewIdError,
9
+ createEmailPreviews,
10
+ emailPreviewRoutes
11
+ };
12
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -0,0 +1,43 @@
1
+ import { E as EmailPreviewScreenCopy } from '../../../preview-screen-DYJRAnAY.js';
2
+ export { a as EmailPreviewScreenConfig, c as createEmailPreviewScreen } from '../../../preview-screen-DYJRAnAY.js';
3
+ import 'react';
4
+
5
+ /** US English — the twin that makes {@link PT_BR_EMAIL_PREVIEW_COPY} a choice. */
6
+ declare const EN_US_EMAIL_PREVIEW_COPY: EmailPreviewScreenCopy;
7
+
8
+ /** Brazilian Portuguese, as a NAMED pack — never a default. */
9
+ declare const PT_BR_EMAIL_PREVIEW_COPY: EmailPreviewScreenCopy;
10
+
11
+ /**
12
+ * One rendered message, in the three ways it is worth looking at.
13
+ *
14
+ * - **HTML** — what most people will see, in a sandboxed frame at either of the
15
+ * two widths that matter. A phone is not a nice-to-have: more than half of
16
+ * transactional mail is opened on one, and the 600px card is exactly the
17
+ * thing that either survives that or does not.
18
+ * - **Text** — the plain-text twin. Worth its own tab because it is what a spam
19
+ * filter scores, what a watch shows, and what a screen reader in plain-text
20
+ * mode reads — and because it is the half nobody ever looks at, which is how
21
+ * it drifts out of step with the HTML.
22
+ * - **Source** — the markup itself, for the moment somebody is debugging why a
23
+ * client rendered it oddly.
24
+ *
25
+ * ## Why an iframe, and why sandboxed
26
+ *
27
+ * The mail is a whole document with its own `<body>` background, and rendering
28
+ * that inside the console's DOM would both break the mail (the console's CSS
29
+ * reaches it) and break the console (the mail's body styles reach the page). A
30
+ * frame is the only honest preview.
31
+ *
32
+ * `sandbox=""` — no scripts, no forms, no top-level navigation. These documents
33
+ * come from the host's own renderer and carry no script, so this is less a
34
+ * containment measure than a statement that the preview is INERT: a click on a
35
+ * CTA inside a previewed mail must never navigate the operator anywhere, least
36
+ * of all to a sample verification link.
37
+ */
38
+ /** The two widths the HTML view renders at. */
39
+ type PreviewWidth = 'desktop' | 'mobile';
40
+ /** Which of the three views is showing. */
41
+ type PreviewTab = 'html' | 'text' | 'source';
42
+
43
+ export { EN_US_EMAIL_PREVIEW_COPY, EmailPreviewScreenCopy, PT_BR_EMAIL_PREVIEW_COPY, type PreviewTab, type PreviewWidth };
@@ -0,0 +1,58 @@
1
+ import {
2
+ createEmailPreviewScreen
3
+ } from "../../../chunk-ZFIYBNZ7.js";
4
+ import {
5
+ __name
6
+ } from "../../../chunk-7QVYU63E.js";
7
+
8
+ // src/email/previews/react/copy.en-US.ts
9
+ var EN_US_EMAIL_PREVIEW_COPY = {
10
+ title: "Email previews",
11
+ description: "Every email this system sends, grouped by the package that owns it. Nothing is sent from here.",
12
+ searchLabel: "Filter",
13
+ searchPlaceholder: "subject, event or package",
14
+ noMatches: "No message matches the filter.",
15
+ pickOne: "Pick a message from the list to see its preview.",
16
+ tabHtml: "HTML",
17
+ tabText: "Text",
18
+ tabSource: "Source",
19
+ widthDesktop: "Desktop",
20
+ widthMobile: "Mobile",
21
+ subjectLabel: "Subject",
22
+ frameTitle: "Email preview",
23
+ coverageTitle: "Incomplete coverage",
24
+ missingSamples: /* @__PURE__ */ __name((keys) => `No sample data, so no preview: ${keys}.`, "missingSamples"),
25
+ orphanSamples: /* @__PURE__ */ __name((keys) => `A sample exists but nothing produces this message any more: ${keys}.`, "orphanSamples"),
26
+ loading: "Loading...",
27
+ loadError: "The previews could not be loaded.",
28
+ retry: "Try again"
29
+ };
30
+
31
+ // src/email/previews/react/copy.pt-BR.ts
32
+ var PT_BR_EMAIL_PREVIEW_COPY = {
33
+ title: "Pr\xE9via de e-mails",
34
+ description: "Todos os e-mails que este sistema envia, agrupados por pacote. Nada \xE9 enviado a partir daqui.",
35
+ searchLabel: "Filtrar",
36
+ searchPlaceholder: "assunto, evento ou pacote",
37
+ noMatches: "Nenhuma mensagem corresponde ao filtro.",
38
+ pickOne: "Escolha uma mensagem na lista para ver a pr\xE9via.",
39
+ tabHtml: "HTML",
40
+ tabText: "Texto",
41
+ tabSource: "C\xF3digo",
42
+ widthDesktop: "Computador",
43
+ widthMobile: "Celular",
44
+ subjectLabel: "Assunto",
45
+ frameTitle: "Pr\xE9via do e-mail",
46
+ coverageTitle: "Cobertura incompleta",
47
+ missingSamples: /* @__PURE__ */ __name((keys) => `Sem dados de exemplo, ent\xE3o n\xE3o h\xE1 pr\xE9via: ${keys}.`, "missingSamples"),
48
+ orphanSamples: /* @__PURE__ */ __name((keys) => `H\xE1 exemplo, mas nada mais produz esta mensagem: ${keys}.`, "orphanSamples"),
49
+ loading: "Carregando...",
50
+ loadError: "N\xE3o foi poss\xEDvel carregar as pr\xE9vias.",
51
+ retry: "Tentar novamente"
52
+ };
53
+ export {
54
+ EN_US_EMAIL_PREVIEW_COPY,
55
+ PT_BR_EMAIL_PREVIEW_COPY,
56
+ createEmailPreviewScreen
57
+ };
58
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../src/email/previews/react/copy.en-US.ts","../../../../src/email/previews/react/copy.pt-BR.ts"],"sourcesContent":["import type { EmailPreviewScreenCopy } from './copy';\n\n/** US English — the twin that makes {@link PT_BR_EMAIL_PREVIEW_COPY} a choice. */\nexport const EN_US_EMAIL_PREVIEW_COPY: EmailPreviewScreenCopy = {\n title: 'Email previews',\n description: 'Every email this system sends, grouped by the package that owns it. Nothing is sent from here.',\n searchLabel: 'Filter',\n searchPlaceholder: 'subject, event or package',\n noMatches: 'No message matches the filter.',\n pickOne: 'Pick a message from the list to see its preview.',\n tabHtml: 'HTML',\n tabText: 'Text',\n tabSource: 'Source',\n widthDesktop: 'Desktop',\n widthMobile: 'Mobile',\n subjectLabel: 'Subject',\n frameTitle: 'Email preview',\n coverageTitle: 'Incomplete coverage',\n missingSamples: (keys) => `No sample data, so no preview: ${keys}.`,\n orphanSamples: (keys) => `A sample exists but nothing produces this message any more: ${keys}.`,\n loading: 'Loading...',\n loadError: 'The previews could not be loaded.',\n retry: 'Try again',\n};\n","import type { EmailPreviewScreenCopy } from './copy';\n\n/** Brazilian Portuguese, as a NAMED pack — never a default. */\nexport const PT_BR_EMAIL_PREVIEW_COPY: EmailPreviewScreenCopy = {\n title: 'Prévia de e-mails',\n description: 'Todos os e-mails que este sistema envia, agrupados por pacote. Nada é enviado a partir daqui.',\n searchLabel: 'Filtrar',\n searchPlaceholder: 'assunto, evento ou pacote',\n noMatches: 'Nenhuma mensagem corresponde ao filtro.',\n pickOne: 'Escolha uma mensagem na lista para ver a prévia.',\n tabHtml: 'HTML',\n tabText: 'Texto',\n tabSource: 'Código',\n widthDesktop: 'Computador',\n widthMobile: 'Celular',\n subjectLabel: 'Assunto',\n frameTitle: 'Prévia do e-mail',\n coverageTitle: 'Cobertura incompleta',\n missingSamples: (keys) => `Sem dados de exemplo, então não há prévia: ${keys}.`,\n orphanSamples: (keys) => `Há exemplo, mas nada mais produz esta mensagem: ${keys}.`,\n loading: 'Carregando...',\n loadError: 'Não foi possível carregar as prévias.',\n retry: 'Tentar novamente',\n};\n"],"mappings":";;;;;;;;AAGO,IAAM,2BAAmD;AAAA,EAC9D,OAAO;AAAA,EACP,aAAa;AAAA,EACb,aAAa;AAAA,EACb,mBAAmB;AAAA,EACnB,WAAW;AAAA,EACX,SAAS;AAAA,EACT,SAAS;AAAA,EACT,SAAS;AAAA,EACT,WAAW;AAAA,EACX,cAAc;AAAA,EACd,aAAa;AAAA,EACb,cAAc;AAAA,EACd,YAAY;AAAA,EACZ,eAAe;AAAA,EACf,gBAAgB,wBAAC,SAAS,kCAAkC,IAAI,KAAhD;AAAA,EAChB,eAAe,wBAAC,SAAS,+DAA+D,IAAI,KAA7E;AAAA,EACf,SAAS;AAAA,EACT,WAAW;AAAA,EACX,OAAO;AACT;;;ACpBO,IAAM,2BAAmD;AAAA,EAC9D,OAAO;AAAA,EACP,aAAa;AAAA,EACb,aAAa;AAAA,EACb,mBAAmB;AAAA,EACnB,WAAW;AAAA,EACX,SAAS;AAAA,EACT,SAAS;AAAA,EACT,SAAS;AAAA,EACT,WAAW;AAAA,EACX,cAAc;AAAA,EACd,aAAa;AAAA,EACb,cAAc;AAAA,EACd,YAAY;AAAA,EACZ,eAAe;AAAA,EACf,gBAAgB,wBAAC,SAAS,0DAA8C,IAAI,KAA5D;AAAA,EAChB,eAAe,wBAAC,SAAS,sDAAmD,IAAI,KAAjE;AAAA,EACf,SAAS;AAAA,EACT,WAAW;AAAA,EACX,OAAO;AACT;","names":[]}
@@ -1,4 +1,4 @@
1
- import { f as NotificationGenerator } from './types-CYJN1xCM.js';
1
+ import { N as NotificationGenerator } from './types-BlqZkCWZ.js';
2
2
 
3
3
  /**
4
4
  * Generator registry: one {@link NotificationGenerator} per event `type`.
@@ -1,9 +1,10 @@
1
1
  import { Hono, Context } from 'hono';
2
- import { A as ApiNotifications, N as NotificationsServerConfig, a as NotificationsActor } from '../create-api-notifications-WaexrZnn.js';
3
- import '../generators-CH5m8Ucj.js';
4
- import '../types-CYJN1xCM.js';
5
- import '../wire-CmRrB3_s.js';
6
- import '../web-push-iER5StBm.js';
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';
7
+ import '../template-Dlt5S54z.js';
7
8
 
8
9
  /**
9
10
  * `@12-apps/notifications/hono` — the account notification endpoints as a
@@ -1,11 +1,13 @@
1
1
  import {
2
2
  createApiNotifications
3
- } from "../chunk-7234ROOP.js";
4
- import "../chunk-GGAZOLNN.js";
5
- import "../chunk-BGI4BQ66.js";
3
+ } from "../chunk-WVRODNXQ.js";
4
+ import "../chunk-SWOWHIFE.js";
6
5
  import {
7
6
  messagesOf
8
7
  } from "../chunk-M2TVBVH2.js";
8
+ import "../chunk-RTURLH5U.js";
9
+ import "../chunk-6W7INOYQ.js";
10
+ import "../chunk-EKUSNUBT.js";
9
11
  import {
10
12
  __name
11
13
  } from "../chunk-7QVYU63E.js";
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/hono/index.ts"],"sourcesContent":["import { Hono } from 'hono';\nimport type { Context } from 'hono';\n\nimport { messagesOf } from '../messages';\n\nimport {\n createApiNotifications,\n type ApiNotifications,\n type NotificationsServerConfig,\n} from '../server/create-api-notifications';\nimport type { NotificationsActor } from '../server/context';\n\n/**\n * `@12-apps/notifications/hono` — the account notification endpoints as a\n * mountable router.\n *\n * The framework-neutral descriptors in `/server` are the contract; this is the\n * adapter for the framework we happen to use, behind its own subpath with\n * `hono` as an OPTIONAL peer (the report-builder precedent — a host on Express,\n * or one that only wants the React surface, never resolves Hono).\n *\n * A host writes:\n *\n * const notifications = notificationsRouter({ …config, resolveActor });\n * app.route('/api/account', notifications.router);\n *\n * and keeps what is genuinely its own: who the caller is. Everything after\n * that — parsing, status codes, the envelope, the pt-BR copy — is the\n * package's.\n */\n\n/**\n * Resolve the caller. Returning `null` means unauthenticated, which answers 401\n * before any handler runs.\n *\n * Note the 401 is self-guarded HERE rather than assumed from middleware: these\n * paths sit under an API prefix that a host's page middleware typically does not\n * match, and an unauthenticated inbox read that fell through would answer\n * somebody else's rows or none at all — both worse than a 401.\n */\nexport type ResolveNotificationsActor = (\n c: Context,\n) => Promise<NotificationsActor | null> | NotificationsActor | null;\n\nexport interface NotificationsHonoConfig extends NotificationsServerConfig {\n resolveActor: ResolveNotificationsActor;\n}\n\nexport interface NotificationsHono extends ApiNotifications {\n router: Hono;\n}\n\n/**\n * Reads the JSON body, tolerating an absent or malformed one — and only when\n * the caller SAID it was JSON.\n *\n * The content-type check is a CSRF speed bump, not a defence (see ADOPTING rule\n * 13, which names the actual one). `text/plain`, `multipart/form-data` and\n * `application/x-www-form-urlencoded` are the three types a cross-site `fetch`\n * or a plain `<form>` can send with NO preflight, so parsing a body regardless\n * of its type is what lets such a request reach these handlers at all. Refusing\n * them means a cross-site write has to earn a preflight first, which the browser\n * will then refuse on its own. The price is nil: every client of this surface,\n * the packaged one included, sends `application/json`.\n */\nfunction saysJson(c: Context): boolean {\n const type = c.req.header('content-type');\n if (!type) return false;\n const mime = (type.split(';')[0] ?? '').trim().toLowerCase();\n return mime === 'application/json' || mime.endsWith('+json');\n}\n\nasync function readBody(c: Context): Promise<unknown> {\n if (c.req.method === 'GET') return undefined;\n if (!saysJson(c)) return undefined;\n try {\n return await c.req.json();\n } catch {\n // A malformed body is the caller's error; the handler's own validation\n // reports it far better than a parse failure would.\n return undefined;\n }\n}\n\nexport function notificationsRouter(config: NotificationsHonoConfig): NotificationsHono {\n const api = createApiNotifications(config);\n const messages = messagesOf(config);\n const router = new Hono();\n\n // Mounted IN DESCRIPTOR ORDER, which any adapter must preserve. Hono resolves\n // by registration order, so a host route shaped `/notifications/:id` under the\n // same prefix must be registered AFTER this router or it captures\n // `/notifications/unread-count`.\n for (const route of api.routes) {\n const handler = async (c: Context): Promise<Response> => {\n const actor = await config.resolveActor(c);\n if (!actor) return c.json({ error: messages.unauthenticated }, 401);\n\n const response = await route.handle({\n actor,\n params: c.req.param() as Record<string, string | undefined>,\n query: c.req.query() as Record<string, string | undefined>,\n body: await readBody(c),\n headers: { 'user-agent': c.req.header('user-agent') },\n });\n\n // A handler that chose NO body means exactly that (204).\n if (response.body === undefined) return c.body(null, response.status as 204);\n // The status travels with the body the handler chose; the adapter never\n // reinterprets either.\n return c.json(response.body as Record<string, unknown>, response.status as 200);\n };\n\n if (route.method === 'GET') router.get(route.path, handler);\n else if (route.method === 'POST') router.post(route.path, handler);\n else if (route.method === 'PUT') router.put(route.path, handler);\n else router.delete(route.path, handler);\n }\n\n return { ...api, router };\n}\n"],"mappings":";;;;;;;;;;;;;AAAA,SAAS,YAAY;AAiErB,SAAS,SAAS,GAAqB;AACrC,QAAM,OAAO,EAAE,IAAI,OAAO,cAAc;AACxC,MAAI,CAAC,KAAM,QAAO;AAClB,QAAM,QAAQ,KAAK,MAAM,GAAG,EAAE,CAAC,KAAK,IAAI,KAAK,EAAE,YAAY;AAC3D,SAAO,SAAS,sBAAsB,KAAK,SAAS,OAAO;AAC7D;AALS;AAOT,eAAe,SAAS,GAA8B;AACpD,MAAI,EAAE,IAAI,WAAW,MAAO,QAAO;AACnC,MAAI,CAAC,SAAS,CAAC,EAAG,QAAO;AACzB,MAAI;AACF,WAAO,MAAM,EAAE,IAAI,KAAK;AAAA,EAC1B,QAAQ;AAGN,WAAO;AAAA,EACT;AACF;AAVe;AAYR,SAAS,oBAAoB,QAAoD;AACtF,QAAM,MAAM,uBAAuB,MAAM;AACzC,QAAM,WAAW,WAAW,MAAM;AAClC,QAAM,SAAS,IAAI,KAAK;AAMxB,aAAW,SAAS,IAAI,QAAQ;AAC9B,UAAM,UAAU,8BAAO,MAAkC;AACvD,YAAM,QAAQ,MAAM,OAAO,aAAa,CAAC;AACzC,UAAI,CAAC,MAAO,QAAO,EAAE,KAAK,EAAE,OAAO,SAAS,gBAAgB,GAAG,GAAG;AAElE,YAAM,WAAW,MAAM,MAAM,OAAO;AAAA,QAClC;AAAA,QACA,QAAQ,EAAE,IAAI,MAAM;AAAA,QACpB,OAAO,EAAE,IAAI,MAAM;AAAA,QACnB,MAAM,MAAM,SAAS,CAAC;AAAA,QACtB,SAAS,EAAE,cAAc,EAAE,IAAI,OAAO,YAAY,EAAE;AAAA,MACtD,CAAC;AAGD,UAAI,SAAS,SAAS,OAAW,QAAO,EAAE,KAAK,MAAM,SAAS,MAAa;AAG3E,aAAO,EAAE,KAAK,SAAS,MAAiC,SAAS,MAAa;AAAA,IAChF,GAjBgB;AAmBhB,QAAI,MAAM,WAAW,MAAO,QAAO,IAAI,MAAM,MAAM,OAAO;AAAA,aACjD,MAAM,WAAW,OAAQ,QAAO,KAAK,MAAM,MAAM,OAAO;AAAA,aACxD,MAAM,WAAW,MAAO,QAAO,IAAI,MAAM,MAAM,OAAO;AAAA,QAC1D,QAAO,OAAO,MAAM,MAAM,OAAO;AAAA,EACxC;AAEA,SAAO,EAAE,GAAG,KAAK,OAAO;AAC1B;AApCgB;","names":[]}
1
+ {"version":3,"sources":["../../src/hono/index.ts"],"sourcesContent":["import { Hono } from 'hono';\nimport type { Context } from 'hono';\n\nimport { messagesOf } from '../messages';\n\nimport {\n createApiNotifications,\n type ApiNotifications,\n type NotificationsServerConfig,\n} from '../server/create-api-notifications';\nimport type { NotificationsActor } from '../server/context';\n\n/**\n * `@12-apps/notifications/hono` — the account notification endpoints as a\n * mountable router.\n *\n * The framework-neutral descriptors in `/server` are the contract; this is the\n * adapter for the framework we happen to use, behind its own subpath with\n * `hono` as an OPTIONAL peer (the report-builder precedent — a host on Express,\n * or one that only wants the React surface, never resolves Hono).\n *\n * A host writes:\n *\n * const notifications = notificationsRouter({ …config, resolveActor });\n * app.route('/api/account', notifications.router);\n *\n * and keeps what is genuinely its own: who the caller is. Everything after\n * that — parsing, status codes, the envelope, the pt-BR copy — is the\n * package's.\n */\n\n/**\n * Resolve the caller. Returning `null` means unauthenticated, which answers 401\n * before any handler runs.\n *\n * Note the 401 is self-guarded HERE rather than assumed from middleware: these\n * paths sit under an API prefix that a host's page middleware typically does not\n * match, and an unauthenticated inbox read that fell through would answer\n * somebody else's rows or none at all — both worse than a 401.\n */\nexport type ResolveNotificationsActor = (\n c: Context,\n) => Promise<NotificationsActor | null> | NotificationsActor | null;\n\nexport interface NotificationsHonoConfig extends NotificationsServerConfig {\n resolveActor: ResolveNotificationsActor;\n}\n\nexport interface NotificationsHono extends ApiNotifications {\n router: Hono;\n}\n\n/**\n * Reads the JSON body, tolerating an absent or malformed one — and only when\n * the caller SAID it was JSON.\n *\n * The content-type check is a CSRF speed bump, not a defence (see ADOPTING rule\n * 13, which names the actual one). `text/plain`, `multipart/form-data` and\n * `application/x-www-form-urlencoded` are the three types a cross-site `fetch`\n * or a plain `<form>` can send with NO preflight, so parsing a body regardless\n * of its type is what lets such a request reach these handlers at all. Refusing\n * them means a cross-site write has to earn a preflight first, which the browser\n * will then refuse on its own. The price is nil: every client of this surface,\n * the packaged one included, sends `application/json`.\n */\nfunction saysJson(c: Context): boolean {\n const type = c.req.header('content-type');\n if (!type) return false;\n const mime = (type.split(';')[0] ?? '').trim().toLowerCase();\n return mime === 'application/json' || mime.endsWith('+json');\n}\n\nasync function readBody(c: Context): Promise<unknown> {\n if (c.req.method === 'GET') return undefined;\n if (!saysJson(c)) return undefined;\n try {\n return await c.req.json();\n } catch {\n // A malformed body is the caller's error; the handler's own validation\n // reports it far better than a parse failure would.\n return undefined;\n }\n}\n\nexport function notificationsRouter(config: NotificationsHonoConfig): NotificationsHono {\n const api = createApiNotifications(config);\n const messages = messagesOf(config);\n const router = new Hono();\n\n // Mounted IN DESCRIPTOR ORDER, which any adapter must preserve. Hono resolves\n // by registration order, so a host route shaped `/notifications/:id` under the\n // same prefix must be registered AFTER this router or it captures\n // `/notifications/unread-count`.\n for (const route of api.routes) {\n const handler = async (c: Context): Promise<Response> => {\n const actor = await config.resolveActor(c);\n if (!actor) return c.json({ error: messages.unauthenticated }, 401);\n\n const response = await route.handle({\n actor,\n params: c.req.param() as Record<string, string | undefined>,\n query: c.req.query() as Record<string, string | undefined>,\n body: await readBody(c),\n headers: { 'user-agent': c.req.header('user-agent') },\n });\n\n // A handler that chose NO body means exactly that (204).\n if (response.body === undefined) return c.body(null, response.status as 204);\n // The status travels with the body the handler chose; the adapter never\n // reinterprets either.\n return c.json(response.body as Record<string, unknown>, response.status as 200);\n };\n\n if (route.method === 'GET') router.get(route.path, handler);\n else if (route.method === 'POST') router.post(route.path, handler);\n else if (route.method === 'PUT') router.put(route.path, handler);\n else router.delete(route.path, handler);\n }\n\n return { ...api, router };\n}\n"],"mappings":";;;;;;;;;;;;;;;AAAA,SAAS,YAAY;AAiErB,SAAS,SAAS,GAAqB;AACrC,QAAM,OAAO,EAAE,IAAI,OAAO,cAAc;AACxC,MAAI,CAAC,KAAM,QAAO;AAClB,QAAM,QAAQ,KAAK,MAAM,GAAG,EAAE,CAAC,KAAK,IAAI,KAAK,EAAE,YAAY;AAC3D,SAAO,SAAS,sBAAsB,KAAK,SAAS,OAAO;AAC7D;AALS;AAOT,eAAe,SAAS,GAA8B;AACpD,MAAI,EAAE,IAAI,WAAW,MAAO,QAAO;AACnC,MAAI,CAAC,SAAS,CAAC,EAAG,QAAO;AACzB,MAAI;AACF,WAAO,MAAM,EAAE,IAAI,KAAK;AAAA,EAC1B,QAAQ;AAGN,WAAO;AAAA,EACT;AACF;AAVe;AAYR,SAAS,oBAAoB,QAAoD;AACtF,QAAM,MAAM,uBAAuB,MAAM;AACzC,QAAM,WAAW,WAAW,MAAM;AAClC,QAAM,SAAS,IAAI,KAAK;AAMxB,aAAW,SAAS,IAAI,QAAQ;AAC9B,UAAM,UAAU,8BAAO,MAAkC;AACvD,YAAM,QAAQ,MAAM,OAAO,aAAa,CAAC;AACzC,UAAI,CAAC,MAAO,QAAO,EAAE,KAAK,EAAE,OAAO,SAAS,gBAAgB,GAAG,GAAG;AAElE,YAAM,WAAW,MAAM,MAAM,OAAO;AAAA,QAClC;AAAA,QACA,QAAQ,EAAE,IAAI,MAAM;AAAA,QACpB,OAAO,EAAE,IAAI,MAAM;AAAA,QACnB,MAAM,MAAM,SAAS,CAAC;AAAA,QACtB,SAAS,EAAE,cAAc,EAAE,IAAI,OAAO,YAAY,EAAE;AAAA,MACtD,CAAC;AAGD,UAAI,SAAS,SAAS,OAAW,QAAO,EAAE,KAAK,MAAM,SAAS,MAAa;AAG3E,aAAO,EAAE,KAAK,SAAS,MAAiC,SAAS,MAAa;AAAA,IAChF,GAjBgB;AAmBhB,QAAI,MAAM,WAAW,MAAO,QAAO,IAAI,MAAM,MAAM,OAAO;AAAA,aACjD,MAAM,WAAW,OAAQ,QAAO,KAAK,MAAM,MAAM,OAAO;AAAA,aACxD,MAAM,WAAW,MAAO,QAAO,IAAI,MAAM,MAAM,OAAO;AAAA,QAC1D,QAAO,OAAO,MAAM,MAAM,OAAO;AAAA,EACxC;AAEA,SAAO,EAAE,GAAG,KAAK,OAAO;AAC1B;AApCgB;","names":[]}
package/dist/index.d.ts CHANGED
@@ -1,6 +1,7 @@
1
- export { D as DeliveryStatus, N as NOTIFICATION_CHANNELS, a as NotificationCategory, b as NotificationChannel, c as NotificationContent, d as NotificationEvent, e as NotificationGenerateContext, f as NotificationGenerator, g as NotificationLogger, h as NotificationRecipient, i as NotificationTaxonomy, j as NotificationTransport, T as TransportRecipient, t as taxonomyOf } from './types-CYJN1xCM.js';
2
- export { N as NotificationGeneratorRegistry, c as createGeneratorRegistry } from './generators-CH5m8Ucj.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-CmRrB3_s.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-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';
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';
4
5
 
5
6
  /** Thrown by `notify` when no generator is registered for the event type. */
6
7
  declare class UnknownNotificationTypeError extends Error {