@12-apps/notifications 4.7.1 → 4.9.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 (96) hide show
  1. package/ADOPTING.md +145 -0
  2. package/README.md +65 -0
  3. package/dist/catalog-BOVZ2b-b.d.ts +132 -0
  4. package/dist/{chunk-XE7HZVMH.js → chunk-BGI4BQ66.js} +1 -1
  5. package/dist/chunk-BGI4BQ66.js.map +1 -0
  6. package/dist/chunk-EKUSNUBT.js +222 -0
  7. package/dist/chunk-EKUSNUBT.js.map +1 -0
  8. package/dist/chunk-FTFLU6KH.js +114 -0
  9. package/dist/chunk-FTFLU6KH.js.map +1 -0
  10. package/dist/{chunk-HHMRCMQU.js → chunk-GGAZOLNN.js} +2 -2
  11. package/dist/{chunk-CUZW62JS.js → chunk-KU4FVOFA.js} +2 -2
  12. package/dist/{chunk-WHBMPHQE.js → chunk-KZG7QGFA.js} +28 -6
  13. package/dist/chunk-KZG7QGFA.js.map +1 -0
  14. package/dist/{chunk-MMLV4EZT.js → chunk-M5XTROYM.js} +2 -2
  15. package/dist/chunk-ZFIYBNZ7.js +502 -0
  16. package/dist/chunk-ZFIYBNZ7.js.map +1 -0
  17. package/dist/{create-api-notifications-B3u6Kx3x.d.ts → create-api-notifications-B-VMezVB.d.ts} +43 -4
  18. package/dist/{create-web-notifications-BHCzaU2y.d.ts → create-web-notifications-B-xQ6R0B.d.ts} +2 -2
  19. package/dist/email/index.d.ts +26 -0
  20. package/dist/email/index.js +20 -0
  21. package/dist/email/index.js.map +1 -0
  22. package/dist/email/locales.d.ts +43 -0
  23. package/dist/email/locales.js +29 -0
  24. package/dist/email/locales.js.map +1 -0
  25. package/dist/email/previews/hono.d.ts +46 -0
  26. package/dist/email/previews/hono.js +33 -0
  27. package/dist/email/previews/hono.js.map +1 -0
  28. package/dist/email/previews/index.d.ts +67 -0
  29. package/dist/email/previews/index.js +12 -0
  30. package/dist/email/previews/index.js.map +1 -0
  31. package/dist/email/previews/react/index.d.ts +43 -0
  32. package/dist/email/previews/react/index.js +58 -0
  33. package/dist/email/previews/react/index.js.map +1 -0
  34. package/dist/{generators-B9xt3sRh.d.ts → generators-BsuXe8Mp.d.ts} +1 -1
  35. package/dist/hono/index.d.ts +6 -5
  36. package/dist/hono/index.js +4 -3
  37. package/dist/hono/index.js.map +1 -1
  38. package/dist/index.d.ts +3 -3
  39. package/dist/index.js +5 -5
  40. package/dist/{jobs-BDZ7aGHV.d.ts → jobs-D64B_bg7.d.ts} +1 -1
  41. package/dist/manifest/index.d.ts +39 -1
  42. package/dist/manifest/index.js +9 -0
  43. package/dist/manifest/index.js.map +1 -1
  44. package/dist/manifest/server.d.ts +32 -7
  45. package/dist/manifest/server.js +15 -4
  46. package/dist/manifest/server.js.map +1 -1
  47. package/dist/manifest/web.d.ts +18 -4
  48. package/dist/manifest/web.js +9 -1
  49. package/dist/manifest/web.js.map +1 -1
  50. package/dist/{preferences-screen-IOW6Y2H2.js → preferences-screen-V3ZBWP7A.js} +2 -2
  51. package/dist/preview-screen-DYJRAnAY.d.ts +83 -0
  52. package/dist/react/index.d.ts +4 -4
  53. package/dist/react/index.js +10 -10
  54. package/dist/server/index.d.ts +8 -7
  55. package/dist/server/index.js +5 -4
  56. package/dist/template-Dlt5S54z.d.ts +196 -0
  57. package/dist/{types-CXLAG3UU.d.ts → types-BFmAY0P5.d.ts} +39 -2
  58. package/dist/web-push/index.d.ts +2 -2
  59. package/dist/{web-push-Cs14Wp9u.d.ts → web-push-D-ysQd5h.d.ts} +1 -1
  60. package/dist/{wire-6dzyfDE7.d.ts → wire-_coV3tJk.d.ts} +1 -1
  61. package/package.json +23 -3
  62. package/src/email/chrome.en-US.ts +8 -0
  63. package/src/email/chrome.pt-BR.ts +21 -0
  64. package/src/email/index.ts +43 -0
  65. package/src/email/locales.ts +29 -0
  66. package/src/email/previews/catalog.ts +208 -0
  67. package/src/email/previews/hono.ts +69 -0
  68. package/src/email/previews/index.ts +26 -0
  69. package/src/email/previews/react/copy.en-US.ts +24 -0
  70. package/src/email/previews/react/copy.pt-BR.ts +24 -0
  71. package/src/email/previews/react/copy.ts +42 -0
  72. package/src/email/previews/react/index.tsx +14 -0
  73. package/src/email/previews/react/loadable.tsx +83 -0
  74. package/src/email/previews/react/message-list.tsx +133 -0
  75. package/src/email/previews/react/message-view.tsx +167 -0
  76. package/src/email/previews/react/preview-screen.tsx +342 -0
  77. package/src/email/previews/react/transport.ts +60 -0
  78. package/src/email/previews/routes.ts +111 -0
  79. package/src/email/template.ts +365 -0
  80. package/src/email/text.ts +59 -0
  81. package/src/email/theme.ts +77 -0
  82. package/src/index.ts +1 -0
  83. package/src/manifest/index.ts +37 -0
  84. package/src/manifest/server.ts +23 -0
  85. package/src/manifest/web.ts +13 -0
  86. package/src/server/db.ts +12 -1
  87. package/src/server/dispatch.ts +4 -0
  88. package/src/server/router.ts +17 -1
  89. package/src/server/transports/email.ts +62 -0
  90. package/src/types.ts +39 -1
  91. package/dist/chunk-WHBMPHQE.js.map +0 -1
  92. package/dist/chunk-XE7HZVMH.js.map +0 -1
  93. /package/dist/{chunk-HHMRCMQU.js.map → chunk-GGAZOLNN.js.map} +0 -0
  94. /package/dist/{chunk-CUZW62JS.js.map → chunk-KU4FVOFA.js.map} +0 -0
  95. /package/dist/{chunk-MMLV4EZT.js.map → chunk-M5XTROYM.js.map} +0 -0
  96. /package/dist/{preferences-screen-IOW6Y2H2.js.map → preferences-screen-V3ZBWP7A.js.map} +0 -0
@@ -1,6 +1,6 @@
1
1
  import { JSX, ComponentType } from 'react';
2
- import { L as ListNotificationsResult, a as ChannelRow, I as InboxNotification, N as NotificationMessages } from './wire-6dzyfDE7.js';
3
- import { b as NotificationChannel } from './types-CXLAG3UU.js';
2
+ import { L as ListNotificationsResult, a as ChannelRow, I as InboxNotification, N as NotificationMessages } from './wire-_coV3tJk.js';
3
+ import { c as NotificationChannel } from './types-BFmAY0P5.js';
4
4
 
5
5
  /**
6
6
  * How the notification screens reach their data (12-15) — the report-builder
@@ -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 { e as NotificationGenerator } from './types-CXLAG3UU.js';
1
+ import { N as NotificationGenerator } from './types-BFmAY0P5.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-B3u6Kx3x.js';
3
- import '../generators-B9xt3sRh.js';
4
- import '../types-CXLAG3UU.js';
5
- import '../wire-6dzyfDE7.js';
6
- import '../web-push-Cs14Wp9u.js';
2
+ import { A as ApiNotifications, N as NotificationsServerConfig, a as NotificationsActor } from '../create-api-notifications-B-VMezVB.js';
3
+ import '../generators-BsuXe8Mp.js';
4
+ import '../types-BFmAY0P5.js';
5
+ import '../wire-_coV3tJk.js';
6
+ import '../web-push-D-ysQd5h.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,12 @@
1
1
  import {
2
2
  createApiNotifications
3
- } from "../chunk-WHBMPHQE.js";
4
- import "../chunk-HHMRCMQU.js";
5
- import "../chunk-XE7HZVMH.js";
3
+ } from "../chunk-KZG7QGFA.js";
4
+ import "../chunk-GGAZOLNN.js";
6
5
  import {
7
6
  messagesOf
8
7
  } from "../chunk-M2TVBVH2.js";
8
+ import "../chunk-BGI4BQ66.js";
9
+ import "../chunk-EKUSNUBT.js";
9
10
  import {
10
11
  __name
11
12
  } 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,6 @@
1
- export { D as DeliveryStatus, N as NOTIFICATION_CHANNELS, a as NotificationCategory, b as NotificationChannel, c as NotificationContent, d as NotificationEvent, e as NotificationGenerator, f as NotificationLogger, g as NotificationRecipient, h as NotificationTaxonomy, i as NotificationTransport, T as TransportRecipient, t as taxonomyOf } from './types-CXLAG3UU.js';
2
- export { N as NotificationGeneratorRegistry, c as createGeneratorRegistry } from './generators-B9xt3sRh.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-6dzyfDE7.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-BFmAY0P5.js';
2
+ export { N as NotificationGeneratorRegistry, c as createGeneratorRegistry } from './generators-BsuXe8Mp.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-_coV3tJk.js';
4
4
 
5
5
  /** Thrown by `notify` when no generator is registered for the event type. */
6
6
  declare class UnknownNotificationTypeError extends Error {
package/dist/index.js CHANGED
@@ -9,14 +9,14 @@ import {
9
9
  mergeChoices,
10
10
  mergeStoredRow,
11
11
  normalizePhoneE164
12
- } from "./chunk-HHMRCMQU.js";
13
- import {
14
- NOTIFICATION_CHANNELS,
15
- taxonomyOf
16
- } from "./chunk-XE7HZVMH.js";
12
+ } from "./chunk-GGAZOLNN.js";
17
13
  import {
18
14
  messagesOf
19
15
  } from "./chunk-M2TVBVH2.js";
16
+ import {
17
+ NOTIFICATION_CHANNELS,
18
+ taxonomyOf
19
+ } from "./chunk-BGI4BQ66.js";
20
20
  import "./chunk-7QVYU63E.js";
21
21
  export {
22
22
  DEFAULT_CHANNEL_ROW,
@@ -1,5 +1,5 @@
1
1
  import { WireJobBlueprint } from '@12-apps/wiring';
2
- import { A as ApiNotifications } from './create-api-notifications-B3u6Kx3x.js';
2
+ import { A as ApiNotifications } from './create-api-notifications-B-VMezVB.js';
3
3
 
4
4
  /**
5
5
  * The two background jobs getting a message out actually needs.
@@ -63,5 +63,43 @@ declare const notificationsManifest: {
63
63
  readonly server: readonly ["http", "jobs"];
64
64
  readonly web: readonly ["surface"];
65
65
  };
66
+ /**
67
+ * The e-mail PREVIEW console — a second manifest, from the same package.
68
+ *
69
+ * ## Why a second manifest and not two more keys on the first
70
+ *
71
+ * `notificationsManifest` has already spent both slots this surface needs:
72
+ * `http` is the account inbox at `/api/account`, `surface` is the bell and the
73
+ * preference matrix. A capability is singular by the contract's shape, so a
74
+ * package with two genuinely different surfaces declares two manifests — which
75
+ * is exactly what `@12-apps/auth` does with `@12-apps/auth-platform`, and for
76
+ * the same reason it gives: those two switches turn a sign-in method off for
77
+ * EVERYBODY, so they do not belong behind the same gate as "reset my password".
78
+ *
79
+ * The split here is the same one. The inbox ships to every signed-in user; this
80
+ * console publishes the product's whole transactional-mail inventory and the
81
+ * exact wording and link shape of its verification and reset mails, which is
82
+ * the reference someone writing a convincing phishing mail would want. Two
83
+ * manifests keep that expressible: a host mounts the inbox and DECLINES the
84
+ * console, in writing, rather than silently getting both behind one gate.
85
+ *
86
+ * ## What it deliberately does not declare
87
+ *
88
+ * No `db` — a catalogue is DERIVED from what a host already sends, so an
89
+ * adopter mounts it without touching its schema. No `notifications`, because
90
+ * this surface authors no alert. And no `email`: that capability is a DELIVERY
91
+ * port, and this manifest renders and never sends. The delivery port is the
92
+ * OTHER half of this package, and it stays where it is.
93
+ */
94
+ declare const notificationEmailPreviewsManifest: {
95
+ readonly name: "@12-apps/notifications-email-previews";
96
+ readonly contract: 1;
97
+ /** A refusal to render a preview files under its own namespace, not nowhere. */
98
+ readonly observability: {
99
+ readonly namespace: "email-previews";
100
+ };
101
+ readonly server: readonly ["http"];
102
+ readonly web: readonly ["surface"];
103
+ };
66
104
 
67
- export { notificationsManifest };
105
+ export { notificationEmailPreviewsManifest, notificationsManifest };
@@ -13,7 +13,16 @@ var notificationsManifest = {
13
13
  server: ["http", "jobs"],
14
14
  web: ["surface"]
15
15
  };
16
+ var notificationEmailPreviewsManifest = {
17
+ name: "@12-apps/notifications-email-previews",
18
+ contract: 1,
19
+ /** A refusal to render a preview files under its own namespace, not nowhere. */
20
+ observability: { namespace: "email-previews" },
21
+ server: ["http"],
22
+ web: ["surface"]
23
+ };
16
24
  export {
25
+ notificationEmailPreviewsManifest,
17
26
  notificationsManifest
18
27
  };
19
28
  //# sourceMappingURL=index.js.map