@nxgt/janus-mail 0.1.0 → 0.2.1

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 (43) hide show
  1. package/README.md +63 -17
  2. package/dist/expiring-mails.d.ts +7 -0
  3. package/dist/expiring-mails.d.ts.map +1 -0
  4. package/dist/expiry.d.ts +29 -0
  5. package/dist/expiry.d.ts.map +1 -0
  6. package/dist/generated/mail.d.ts +3 -0
  7. package/dist/generated/mail.d.ts.map +1 -1
  8. package/dist/index.d.ts +1 -1
  9. package/dist/index.d.ts.map +1 -1
  10. package/dist/index.js +152 -84
  11. package/dist/index.js.map +10 -6
  12. package/dist/janus-mail.d.ts +6 -0
  13. package/dist/janus-mail.d.ts.map +1 -1
  14. package/dist/notice-mails.d.ts +12 -0
  15. package/dist/notice-mails.d.ts.map +1 -0
  16. package/dist/options.d.ts +2 -0
  17. package/dist/options.d.ts.map +1 -1
  18. package/dist/send.d.ts +27 -0
  19. package/dist/send.d.ts.map +1 -0
  20. package/dist/templates.d.ts.map +1 -1
  21. package/dist/types.d.ts +36 -7
  22. package/dist/types.d.ts.map +1 -1
  23. package/docs/README.md +2 -1
  24. package/docs/guide/building.md +23 -1
  25. package/docs/guide/locales.md +10 -4
  26. package/docs/guide/sending.md +65 -11
  27. package/docs/guide/templates.md +16 -14
  28. package/docs/roadmap.md +9 -2
  29. package/docs/troubleshooting.md +98 -4
  30. package/mails/en/reset-password.html +2 -2
  31. package/mails/en/reset-password.txt +3 -2
  32. package/mails/en/sign-in-code.html +1 -1
  33. package/mails/en/sign-in-code.txt +2 -0
  34. package/mails/en/verify-email.html +2 -2
  35. package/mails/en/verify-email.txt +3 -2
  36. package/mails/fr/reset-password.html +2 -2
  37. package/mails/fr/reset-password.txt +3 -2
  38. package/mails/fr/sign-in-code.html +1 -1
  39. package/mails/fr/sign-in-code.txt +2 -0
  40. package/mails/fr/verify-email.html +2 -2
  41. package/mails/fr/verify-email.txt +3 -2
  42. package/mails/mail-manifest.json +4 -1
  43. package/package.json +6 -6
package/dist/index.js.map CHANGED
@@ -1,14 +1,18 @@
1
1
  {
2
2
  "version": 3,
3
- "sources": ["../src/janus-mail.ts", "../src/generated/locales.ts", "../src/mails.ts", "../src/templates.ts", "../src/options.ts"],
3
+ "sources": ["../src/expiry.ts", "../src/send.ts", "../src/expiring-mails.ts", "../src/notice-mails.ts", "../src/generated/locales.ts", "../src/mails.ts", "../src/templates.ts", "../src/options.ts", "../src/janus-mail.ts"],
4
4
  "sourcesContent": [
5
- "/**\n * `janusMail()`: the e-mails of Janus's flows, rendered by the templates and\n * handed to the mailer. It sends what a flow answered, to the address the\n * flow answered, and nothing else: no retry, no queue, no error of its own —\n * the mailer's `MailFailure` and `MailRefused` reach the caller untouched.\n */\nimport type { Address, Rendered, SentMail, WantedLocales } from '@nxgt/mail';\nimport { pickLocale } from '@nxgt/mail';\nimport { type ResolvedOptions, resolveOptions } from './options';\nimport type {\n\tJanusMail,\n\tJanusMailLocale,\n\tJanusMailOptions,\n\tJanusMailTemplateName,\n\tJanusMailVariables,\n} from './types';\n\n/** A string field of an argument, or a `TypeError` naming the method and the field. */\nfunction field(method: string, value: unknown, key: string): string {\n\tconst read =\n\t\ttypeof value === 'object' && value !== null\n\t\t\t? (value as Record<string, unknown>)[key]\n\t\t\t: undefined;\n\tif (typeof read !== 'string') {\n\t\tthrow new TypeError(`janusMail.${method}: ${key} must be a string`);\n\t}\n\treturn read;\n}\n\n/**\n * What a `links` function answered, or a `TypeError` naming the method and\n * the call. Its scheme is the renderer's to check: `http(s)` and `mailto:`\n * pass, anything else is its `MailRefused`.\n */\nfunction link(method: string, value: unknown, call: string): string {\n\tif (typeof value !== 'string') {\n\t\tthrow new TypeError(\n\t\t\t`janusMail.${method}: links.${call} must answer a string`,\n\t\t);\n\t}\n\treturn value;\n}\n\n/** The locale the recipient gets: the first they want that is sent in, else the fallback. */\nfunction localeFor(options: ResolvedOptions, to: unknown): string {\n\tconst wanted =\n\t\ttypeof to === 'object' && to !== null\n\t\t\t? (to as { locale?: WantedLocales }).locale\n\t\t\t: undefined;\n\treturn pickLocale(wanted, options.locales, options.fallbackLocale);\n}\n\n/**\n * Renders one e-mail and hands it to the mailer. Only `subject`, `html` and\n * `text` are read from what the template answered: a template cannot add a\n * recipient or a header.\n */\nasync function send<K extends JanusMailTemplateName>(\n\toptions: ResolvedOptions,\n\tname: K,\n\taddress: string,\n\tvariables: JanusMailVariables[K] & { readonly locale: string },\n): Promise<SentMail> {\n\tconst template = options.templates[name] as (\n\t\tvariables: JanusMailVariables[K] & { readonly locale: string },\n\t) => Rendered | PromiseLike<Rendered>;\n\tconst { subject, html, text }: Rendered = await template(variables);\n\tconst replyTo: { replyTo?: Address } =\n\t\toptions.replyTo === undefined ? {} : { replyTo: options.replyTo };\n\treturn options.mailer.send({\n\t\tto: address,\n\t\tfrom: options.from,\n\t\t...replyTo,\n\t\tsubject,\n\t\thtml,\n\t\ttext,\n\t});\n}\n\n/** The five methods, over the resolved options. */\nfunction methods(\n\toptions: ResolvedOptions,\n): Omit<JanusMail<string>, 'templates' | 'locales'> {\n\tconst { brand, links } = options;\n\treturn {\n\t\tasync verifyEmail(issued, to) {\n\t\t\tconst token = field('verifyEmail', issued, 'token');\n\t\t\treturn send(\n\t\t\t\toptions,\n\t\t\t\t'verifyEmail',\n\t\t\t\tfield('verifyEmail', issued, 'email'),\n\t\t\t\t{\n\t\t\t\t\tbrand,\n\t\t\t\t\tname: field('verifyEmail', to, 'name'),\n\t\t\t\t\tlink: link(\n\t\t\t\t\t\t'verifyEmail',\n\t\t\t\t\t\tlinks.verifyEmail(token),\n\t\t\t\t\t\t'verifyEmail(token)',\n\t\t\t\t\t),\n\t\t\t\t\tlocale: localeFor(options, to),\n\t\t\t\t},\n\t\t\t);\n\t\t},\n\t\tasync resetPassword(issued, to) {\n\t\t\tconst token = field('resetPassword', issued, 'token');\n\t\t\treturn send(\n\t\t\t\toptions,\n\t\t\t\t'resetPassword',\n\t\t\t\tfield('resetPassword', issued, 'email'),\n\t\t\t\t{\n\t\t\t\t\tbrand,\n\t\t\t\t\tname: field('resetPassword', to, 'name'),\n\t\t\t\t\tlink: link(\n\t\t\t\t\t\t'resetPassword',\n\t\t\t\t\t\tlinks.resetPassword(token),\n\t\t\t\t\t\t'resetPassword(token)',\n\t\t\t\t\t),\n\t\t\t\t\tlocale: localeFor(options, to),\n\t\t\t\t},\n\t\t\t);\n\t\t},\n\t\tasync signInCode(issued, to) {\n\t\t\t// `code` and `email` only: the challenge is the visitor's secret.\n\t\t\treturn send(options, 'signInCode', field('signInCode', issued, 'email'), {\n\t\t\t\tbrand,\n\t\t\t\tcode: field('signInCode', issued, 'code'),\n\t\t\t\tlocale: localeFor(options, to),\n\t\t\t});\n\t\t},\n\t\tasync passwordChanged(to) {\n\t\t\treturn send(\n\t\t\t\toptions,\n\t\t\t\t'passwordChanged',\n\t\t\t\tfield('passwordChanged', to, 'email'),\n\t\t\t\t{\n\t\t\t\t\tbrand,\n\t\t\t\t\tname: field('passwordChanged', to, 'name'),\n\t\t\t\t\tlink: link(\n\t\t\t\t\t\t'passwordChanged',\n\t\t\t\t\t\tlinks.secureAccount(),\n\t\t\t\t\t\t'secureAccount()',\n\t\t\t\t\t),\n\t\t\t\t\tlocale: localeFor(options, to),\n\t\t\t\t},\n\t\t\t);\n\t\t},\n\t\tasync emailChanged(to) {\n\t\t\t// To the former address: the one a hijacker just took the account from.\n\t\t\treturn send(\n\t\t\t\toptions,\n\t\t\t\t'emailChanged',\n\t\t\t\tfield('emailChanged', to, 'formerEmail'),\n\t\t\t\t{\n\t\t\t\t\tbrand,\n\t\t\t\t\tname: field('emailChanged', to, 'name'),\n\t\t\t\t\tlink: link('emailChanged', links.secureAccount(), 'secureAccount()'),\n\t\t\t\t\tnewEmail: field('emailChanged', to, 'newEmail'),\n\t\t\t\t\tlocale: localeFor(options, to),\n\t\t\t\t},\n\t\t\t);\n\t\t},\n\t};\n}\n\n/**\n * The e-mails of Janus's flows, over your mailer:\n *\n * ```ts\n * const mail = janusMail({\n * \tmailer,\n * \tfrom: 'noreply@acme.example',\n * \tbrand: 'Acme',\n * \tlinks: {\n * \t\tverifyEmail: (token) => `https://acme.example/verify?token=${token}`,\n * \t\tresetPassword: (token) => `https://acme.example/reset?token=${token}`,\n * \t\tsecureAccount: () => 'https://acme.example/account/security',\n * \t},\n * });\n *\n * await mail.verifyEmail(await auth.verifyEmail.send(user), { name: user.name, locale: user.locale });\n * ```\n *\n * Reads nothing when called: the default e-mails are read on the first one\n * sent. A wrong option is a bare `TypeError`, thrown here.\n */\nexport function janusMail<const L extends string = JanusMailLocale>(\n\toptions: JanusMailOptions<L>,\n): JanusMail<L> {\n\tconst resolved = resolveOptions(options);\n\tconst mail: JanusMail<string> = Object.freeze({\n\t\t...methods(resolved),\n\t\ttemplates: resolved.templates,\n\t\tlocales: resolved.locales,\n\t});\n\t// The one cast at the boundary: the mirror above works on `string` locales.\n\treturn mail as unknown as JanusMail<L>;\n}\n",
5
+ "/**\n * `expiresIn`: how long the link or the code of an e-mail stays valid, as\n * the text the e-mail shows — \"1 hour\", \"1 heure\". Derived from the\n * `expiresAt` the flow answered and the locale the recipient gets, unless the\n * send passes its own.\n */\nimport type { Clock } from '@nxgt/janus';\n\nconst MINUTE_MS = 60_000;\nconst MINUTES_IN_HOUR = 60;\nconst MINUTES_IN_DAY = 24 * MINUTES_IN_HOUR;\n\n/**\n * A duration in the locale, in the largest unit it holds at least once:\n * rounded to the minute first — the flow set `expiresAt` a moment before the\n * send, so an hour is 59 minutes and 59 seconds by then — and then down to a\n * whole unit: 90 minutes is \"1 hour\", 36 hours \"1 day\". At 30 seconds or\n * more, the e-mail never promises more than half a minute beyond what is\n * left; under that, it says \"1 minute\".\n *\n * ```ts\n * formatExpiry(3_600_000, 'fr'); // '1 heure'\n * ```\n */\nexport function formatExpiry(ms: number, locale: string): string {\n\tconst minutes = Math.max(1, Math.round(ms / MINUTE_MS));\n\tconst [value, unit] =\n\t\tminutes >= MINUTES_IN_DAY\n\t\t\t? [Math.floor(minutes / MINUTES_IN_DAY), 'day']\n\t\t\t: minutes >= MINUTES_IN_HOUR\n\t\t\t\t? [Math.floor(minutes / MINUTES_IN_HOUR), 'hour']\n\t\t\t\t: [minutes, 'minute'];\n\treturn new Intl.NumberFormat(locale, {\n\t\tstyle: 'unit',\n\t\tunit,\n\t\tunitDisplay: 'long',\n\t}).format(value);\n}\n\n/** The per-send `expiresIn`, when one was passed, or `undefined`; a `TypeError` when it is not a string. */\nfunction passedExpiresIn(method: string, options: unknown): string | undefined {\n\tconst read =\n\t\ttypeof options === 'object' && options !== null\n\t\t\t? (options as { expiresIn?: unknown }).expiresIn\n\t\t\t: undefined;\n\tif (read !== undefined && typeof read !== 'string') {\n\t\tthrow new TypeError(`janusMail.${method}: expiresIn must be a string`);\n\t}\n\treturn read;\n}\n\n/**\n * The `expiresIn` of one send: the one passed, as is, or the time left\n * until the flow's `expiresAt` by `clock` — the one given to `janus()`, when\n * it was given to `janusMail()` too — in `locale`. A `TypeError` naming the method\n * when `expiresAt` is not a valid `Date` — a flow's answer that went through\n * JSON holds a string there — or is already past.\n */\nexport function expiresInFor(\n\tmethod: string,\n\tissued: unknown,\n\tlocale: string,\n\toptions: unknown,\n\tclock: Clock,\n): string {\n\tconst passed = passedExpiresIn(method, options);\n\tif (passed !== undefined) return passed;\n\tconst expiresAt =\n\t\ttypeof issued === 'object' && issued !== null\n\t\t\t? (issued as { expiresAt?: unknown }).expiresAt\n\t\t\t: undefined;\n\tif (!(expiresAt instanceof Date) || Number.isNaN(expiresAt.getTime())) {\n\t\tthrow new TypeError(`janusMail.${method}: expiresAt must be a Date`);\n\t}\n\tconst now = clock.now();\n\tif (!(now instanceof Date) || Number.isNaN(now.getTime())) {\n\t\tthrow new TypeError(`janusMail.${method}: clock.now() must answer a Date`);\n\t}\n\tconst left = expiresAt.getTime() - now.getTime();\n\tif (left <= 0) {\n\t\tthrow new TypeError(\n\t\t\t`janusMail.${method}: expiresAt is past — the link or code would not work`,\n\t\t);\n\t}\n\treturn formatExpiry(left, locale);\n}\n",
6
+ "/**\n * What every method shares: reading an argument's fields, a link, the\n * recipient's locale, and the send itself. A wrong argument is a `TypeError`\n * naming the method and the field — never its value.\n */\nimport type { Address, Rendered, SentMail, WantedLocales } from '@nxgt/mail';\nimport { pickLocale } from '@nxgt/mail';\nimport type { ResolvedOptions } from './options';\nimport type { JanusMailTemplateName, JanusMailVariables } from './types';\n\n/** A string field of an argument, or a `TypeError` naming the method and the field. */\nexport function field(method: string, value: unknown, key: string): string {\n\tconst read =\n\t\ttypeof value === 'object' && value !== null\n\t\t\t? (value as Record<string, unknown>)[key]\n\t\t\t: undefined;\n\tif (typeof read !== 'string') {\n\t\tthrow new TypeError(`janusMail.${method}: ${key} must be a string`);\n\t}\n\treturn read;\n}\n\n/**\n * What a `links` function answered, or a `TypeError` naming the method and\n * the call. Its scheme is the renderer's to check: `http(s)` and `mailto:`\n * pass, anything else is its `MailRefused`.\n */\nexport function link(method: string, value: unknown, call: string): string {\n\tif (typeof value !== 'string') {\n\t\tthrow new TypeError(\n\t\t\t`janusMail.${method}: links.${call} must answer a string`,\n\t\t);\n\t}\n\treturn value;\n}\n\n/** The locale the recipient gets: the first they want that is sent in, else the fallback. */\nexport function localeFor(options: ResolvedOptions, to: unknown): string {\n\tconst wanted =\n\t\ttypeof to === 'object' && to !== null\n\t\t\t? (to as { locale?: WantedLocales }).locale\n\t\t\t: undefined;\n\treturn pickLocale(wanted, options.locales, options.fallbackLocale);\n}\n\n/**\n * Renders one e-mail and hands it to the mailer. Only `subject`, `html` and\n * `text` are read from what the template answered: a template cannot add a\n * recipient or a header.\n */\nexport async function send<K extends JanusMailTemplateName>(\n\toptions: ResolvedOptions,\n\tname: K,\n\taddress: string,\n\tvariables: JanusMailVariables[K] & { readonly locale: string },\n): Promise<SentMail> {\n\tconst template = options.templates[name] as (\n\t\tvariables: JanusMailVariables[K] & { readonly locale: string },\n\t) => Rendered | PromiseLike<Rendered>;\n\tconst { subject, html, text }: Rendered = await template(variables);\n\tconst replyTo: { replyTo?: Address } =\n\t\toptions.replyTo === undefined ? {} : { replyTo: options.replyTo };\n\treturn options.mailer.send({\n\t\tto: address,\n\t\tfrom: options.from,\n\t\t...replyTo,\n\t\tsubject,\n\t\thtml,\n\t\ttext,\n\t});\n}\n",
7
+ "/**\n * The three e-mails that carry what a flow issued — a one-time link or a\n * sign-in code — and say how long it lasts: `verifyEmail`, `resetPassword`\n * and `signInCode`.\n */\nimport { expiresInFor } from './expiry';\nimport type { ResolvedOptions } from './options';\nimport { field, link, localeFor, send } from './send';\nimport type { JanusMail } from './types';\n\ntype ExpiringMails = Pick<\n\tJanusMail<string>,\n\t'verifyEmail' | 'resetPassword' | 'signInCode'\n>;\n\n/** The two e-mails of a one-time token, which link to your page with it. */\ntype LinkMailName = 'verifyEmail' | 'resetPassword';\n\n/** One e-mail of a one-time token: to `issued.email`, its link built from `issued.token`. */\nfunction linkMail(options: ResolvedOptions, name: LinkMailName) {\n\treturn async (issued: unknown, to: unknown, sendOptions?: unknown) => {\n\t\tconst token = field(name, issued, 'token');\n\t\tconst locale = localeFor(options, to);\n\t\treturn send(options, name, field(name, issued, 'email'), {\n\t\t\tbrand: options.brand,\n\t\t\tname: field(name, to, 'name'),\n\t\t\tlink: link(name, options.links[name](token), `${name}(token)`),\n\t\t\texpiresIn: expiresInFor(name, issued, locale, sendOptions, options.clock),\n\t\t\tlocale,\n\t\t});\n\t};\n}\n\n/** `verifyEmail`, `resetPassword` and `signInCode`, over the resolved options. */\nexport function expiringMails(options: ResolvedOptions): ExpiringMails {\n\treturn {\n\t\tverifyEmail: linkMail(options, 'verifyEmail'),\n\t\tresetPassword: linkMail(options, 'resetPassword'),\n\t\tasync signInCode(issued, to, sendOptions) {\n\t\t\t// `code`, `email` and `expiresAt` only: the challenge is the visitor's secret.\n\t\t\tconst locale = localeFor(options, to);\n\t\t\treturn send(options, 'signInCode', field('signInCode', issued, 'email'), {\n\t\t\t\tbrand: options.brand,\n\t\t\t\tcode: field('signInCode', issued, 'code'),\n\t\t\t\texpiresIn: expiresInFor(\n\t\t\t\t\t'signInCode',\n\t\t\t\t\tissued,\n\t\t\t\t\tlocale,\n\t\t\t\t\tsendOptions,\n\t\t\t\t\toptions.clock,\n\t\t\t\t),\n\t\t\t\tlocale,\n\t\t\t});\n\t\t},\n\t};\n}\n",
8
+ "/**\n * The two notices of a change the user made — or someone else did:\n * `passwordChanged` and `emailChanged`. Each links to where the account is\n * secured.\n */\nimport type { ResolvedOptions } from './options';\nimport { field, link, localeFor, send } from './send';\nimport type { JanusMail } from './types';\n\ntype NoticeMails = Pick<JanusMail<string>, 'passwordChanged' | 'emailChanged'>;\n\n/** `passwordChanged` and `emailChanged`, over the resolved options. */\nexport function noticeMails(options: ResolvedOptions): NoticeMails {\n\tconst { brand, links } = options;\n\treturn {\n\t\tasync passwordChanged(to) {\n\t\t\treturn send(\n\t\t\t\toptions,\n\t\t\t\t'passwordChanged',\n\t\t\t\tfield('passwordChanged', to, 'email'),\n\t\t\t\t{\n\t\t\t\t\tbrand,\n\t\t\t\t\tname: field('passwordChanged', to, 'name'),\n\t\t\t\t\tlink: link(\n\t\t\t\t\t\t'passwordChanged',\n\t\t\t\t\t\tlinks.secureAccount(),\n\t\t\t\t\t\t'secureAccount()',\n\t\t\t\t\t),\n\t\t\t\t\tlocale: localeFor(options, to),\n\t\t\t\t},\n\t\t\t);\n\t\t},\n\t\tasync emailChanged(to) {\n\t\t\t// To the former address: the one a hijacker just took the account from.\n\t\t\treturn send(\n\t\t\t\toptions,\n\t\t\t\t'emailChanged',\n\t\t\t\tfield('emailChanged', to, 'formerEmail'),\n\t\t\t\t{\n\t\t\t\t\tbrand,\n\t\t\t\t\tname: field('emailChanged', to, 'name'),\n\t\t\t\t\tlink: link('emailChanged', links.secureAccount(), 'secureAccount()'),\n\t\t\t\t\tnewEmail: field('emailChanged', to, 'newEmail'),\n\t\t\t\t\tlocale: localeFor(options, to),\n\t\t\t\t},\n\t\t\t);\n\t\t},\n\t};\n}\n",
6
9
  "// Generated by scripts/build-mail.ts from mails/mail-manifest.json, after\n// each build of the default e-mails. Never edited; committed.\n\n/** The locales the default e-mails are built in, the fallback locale first. */\nexport const LOCALES = ['en', 'fr'] as const;\n\n/** A locale the default e-mails are built in. */\nexport type JanusMailLocale = (typeof LOCALES)[number];\n",
7
10
  "/**\n * Where the default e-mails are, and the one renderer that reads them.\n *\n * The package ships the built e-mails in `mails/`, beside `dist/` — not in\n * it, because the root `build.ts` removes from `dist/` every file it did not\n * write itself.\n */\nimport { fileURLToPath } from 'node:url';\nimport { createMailRenderer, type MailRenderer } from '@nxgt/mail/renderer';\nimport type { MailEmails } from './generated/mail';\n\n/**\n * The folder of the built e-mails, resolved from this module rather than from\n * the working directory, which is the consumer's.\n *\n * **This file must stay directly under `src/`.** `../mails/` is right from\n * `src/mails.ts` in the specs, and from `dist/index.js` in the tarball,\n * because the build bundles this module into the single entry\n * `dist/index.js`, one level under the package as `src/` is. Moved into a\n * subfolder, or bundled into `dist/chunks/` by a second entry point, it would\n * resolve a folder that does not exist — which the artifact spec, rendering\n * from `../dist`, would catch.\n */\nexport const MAILS_DIR = fileURLToPath(new URL('../mails/', import.meta.url));\n\nlet renderer: MailRenderer<MailEmails> | undefined;\n\n/**\n * The renderer of the default e-mails, created on first use and shared by\n * every `janusMail()` and `janusTemplates()` after it.\n *\n * Lazy, so importing the package reads nothing: an application that\n * overrides every template never opens `mails/`. The first default template\n * rendered reads the whole build once — a missing `mails/` throws there, with\n * `createMailRenderer`'s own message.\n */\nexport function defaultRenderer(): MailRenderer<MailEmails> {\n\trenderer ??= createMailRenderer<MailEmails>({ dir: MAILS_DIR });\n\treturn renderer;\n}\n",
8
- "/**\n * The default templates: the prebuilt e-mails, filled by `@nxgt/mail`'s\n * renderer. Each picks from its variables exactly what its e-mail takes — the\n * renderer refuses a variable too many — so the `locale` beside them goes to\n * the renderer's options, and nowhere else.\n */\nimport type { Rendered } from '@nxgt/mail';\nimport { defaultRenderer } from './mails';\nimport type { JanusMailTemplates } from './types';\n\n/**\n * The five default templates, in the locales they are built in. The\n * renderer behind them is created on the first render, not here.\n *\n * ```ts\n * const defaults = janusTemplates();\n * const { subject } = await defaults.verifyEmail({ brand: 'Acme', name: 'Ada', link, locale: 'fr' });\n * ```\n *\n * A URL that is not `http:`, `https:` or `mailto:` throws `MailRefused`; a\n * missing `mails/` folder throws when the first one renders.\n */\nexport function janusTemplates(): JanusMailTemplates {\n\treturn Object.freeze({\n\t\tverifyEmail: ({ brand, name, link, locale }): Rendered =>\n\t\t\tdefaultRenderer().render(\n\t\t\t\t'verify-email',\n\t\t\t\t{ brand, name, link },\n\t\t\t\t{ locale },\n\t\t\t),\n\t\tresetPassword: ({ brand, name, link, locale }): Rendered =>\n\t\t\tdefaultRenderer().render(\n\t\t\t\t'reset-password',\n\t\t\t\t{ brand, name, link },\n\t\t\t\t{ locale },\n\t\t\t),\n\t\tsignInCode: ({ brand, code, locale }): Rendered =>\n\t\t\tdefaultRenderer().render('sign-in-code', { brand, code }, { locale }),\n\t\tpasswordChanged: ({ brand, name, link, locale }): Rendered =>\n\t\t\tdefaultRenderer().render(\n\t\t\t\t'password-changed',\n\t\t\t\t{ brand, name, link },\n\t\t\t\t{ locale },\n\t\t\t),\n\t\temailChanged: ({ brand, name, link, newEmail, locale }): Rendered =>\n\t\t\tdefaultRenderer().render(\n\t\t\t\t'email-changed',\n\t\t\t\t{ brand, name, link, newEmail },\n\t\t\t\t{ locale },\n\t\t\t),\n\t});\n}\n",
9
- "/**\n * `janusMail()`'s options, checked once when it is called. A mistake is a\n * wiring mistake, so a bare `TypeError` naming the option — never its value —\n * thrown before anything is sent.\n */\nimport type { Address, Mailer } from '@nxgt/mail';\nimport { LOCALES } from './generated/locales';\nimport { janusTemplates } from './templates';\nimport type {\n\tJanusMailLinks,\n\tJanusMailTemplateName,\n\tJanusMailTemplates,\n} from './types';\n\n/** The templates in the order the documentation lists them. */\nexport const TEMPLATE_NAMES: readonly JanusMailTemplateName[] = [\n\t'verifyEmail',\n\t'resetPassword',\n\t'signInCode',\n\t'passwordChanged',\n\t'emailChanged',\n];\n\nconst LINK_NAMES = ['verifyEmail', 'resetPassword', 'secureAccount'] as const;\n\n/** The options once checked, with `string` for the locales: the degenericised mirror. */\nexport interface ResolvedOptions {\n\treadonly mailer: Mailer;\n\treadonly from: Address;\n\treadonly replyTo: Address | undefined;\n\treadonly brand: string;\n\treadonly links: JanusMailLinks;\n\treadonly locales: readonly string[];\n\treadonly fallbackLocale: string;\n\treadonly templates: JanusMailTemplates<string>;\n}\n\nfunction isObject(value: unknown): value is Record<string, unknown> {\n\treturn typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction isAddress(value: unknown): value is Address {\n\tif (typeof value === 'string') return value.trim() !== '';\n\treturn (\n\t\tisObject(value) &&\n\t\ttypeof value.name === 'string' &&\n\t\ttypeof value.address === 'string' &&\n\t\tvalue.address.trim() !== ''\n\t);\n}\n\nfunction refuse(message: string): never {\n\tthrow new TypeError(`janusMail: ${message}`);\n}\n\n/** `mailer`, `from`, `replyTo`, `brand` and `links`. */\nfunction checkSending(options: Record<string, unknown>): void {\n\tconst { mailer, from, replyTo, brand, links } = options;\n\tif (!isObject(mailer) || typeof mailer.send !== 'function') {\n\t\trefuse('mailer must be a Mailer — an object with a send function');\n\t}\n\tif (!isAddress(from)) {\n\t\trefuse(\n\t\t\t\"from must be an address, as 'noreply@example.com' or { name, address }\",\n\t\t);\n\t}\n\tif (replyTo !== undefined && !isAddress(replyTo)) {\n\t\trefuse(\n\t\t\t\"replyTo must be an address, as 'support@example.com' or { name, address }\",\n\t\t);\n\t}\n\tif (typeof brand !== 'string' || brand.trim() === '') {\n\t\trefuse(\"brand must be the name the e-mails show, as 'Acme'\");\n\t}\n\tif (!isObject(links)) {\n\t\trefuse(\n\t\t\t'links must be an object, as { verifyEmail, resetPassword, secureAccount }',\n\t\t);\n\t}\n\tfor (const name of LINK_NAMES) {\n\t\tif (typeof links[name] !== 'function') {\n\t\t\trefuse(`links.${name} must be a function`);\n\t\t}\n\t}\n}\n\n/** `locales` and `fallbackLocale`, with their defaults. */\nfunction resolveLocales(options: Record<string, unknown>): {\n\tlocales: readonly string[];\n\tfallbackLocale: string;\n} {\n\tconst wanted = options.locales ?? LOCALES;\n\tif (\n\t\t!Array.isArray(wanted) ||\n\t\twanted.length === 0 ||\n\t\t!wanted.every((locale) => typeof locale === 'string' && locale !== '')\n\t) {\n\t\trefuse(\"locales must list at least one locale, as ['en', 'fr']\");\n\t}\n\tconst locales: readonly string[] = Object.freeze([...wanted]);\n\tif (new Set(locales).size !== locales.length) {\n\t\trefuse('locales holds the same locale twice');\n\t}\n\tconst fallbackLocale =\n\t\toptions.fallbackLocale ??\n\t\t(locales.includes('en') ? 'en' : (locales[0] as string));\n\tif (typeof fallbackLocale !== 'string' || !locales.includes(fallbackLocale)) {\n\t\trefuse('fallbackLocale must be one of locales');\n\t}\n\treturn { locales, fallbackLocale };\n}\n\n/** `templates` over the defaults — the defaults only while every locale is built. */\nfunction resolveTemplates(\n\toptions: Record<string, unknown>,\n\tlocales: readonly string[],\n): JanusMailTemplates<string> {\n\tconst given = options.templates ?? {};\n\tif (!isObject(given)) {\n\t\trefuse(\n\t\t\t'templates must be an object of functions, as { verifyEmail: (variables) => rendered }',\n\t\t);\n\t}\n\tfor (const [name, template] of Object.entries(given)) {\n\t\tif (!(TEMPLATE_NAMES as readonly string[]).includes(name)) {\n\t\t\trefuse(\n\t\t\t\t`templates has no template ${name} — name one of ${TEMPLATE_NAMES.join(', ')}`,\n\t\t\t);\n\t\t}\n\t\tif (typeof template !== 'function') {\n\t\t\trefuse(`templates.${name} must be a function`);\n\t\t}\n\t}\n\t// Own enumerable keys, the ones the spread below copies. A template on a\n\t// prototype — a class instance's method — or an own property that is not\n\t// enumerable would be dropped without a word, the default sent in its\n\t// place: `in` sees it and `passed` does not, so it is refused instead.\n\tconst passed = Object.keys(given);\n\tfor (const name of TEMPLATE_NAMES) {\n\t\tif (name in given && !passed.includes(name)) {\n\t\t\trefuse(\n\t\t\t\t`templates.${name} is not an own enumerable property — pass a plain object, as { ${name}: (variables) => rendered }`,\n\t\t\t);\n\t\t}\n\t}\n\tconst built = locales.every((locale) =>\n\t\t(LOCALES as readonly string[]).includes(locale),\n\t);\n\tconst missing = TEMPLATE_NAMES.filter((name) => !passed.includes(name));\n\tif (!built && missing.length > 0) {\n\t\trefuse(\n\t\t\t`the default templates are built in ${LOCALES.join(' and ')} only — with another ` +\n\t\t\t\t`locale in locales, pass every template in templates; ${missing.join(', ')} missing`,\n\t\t);\n\t}\n\t// The one cast: a default template takes a built locale only, and the check\n\t// above proves every locale is one whenever a default is kept.\n\tconst defaults = janusTemplates() as unknown as JanusMailTemplates<string>;\n\treturn Object.freeze({ ...defaults, ...given }) as JanusMailTemplates<string>;\n}\n\n/** A frozen copy of a checked address: a later change to the caller's object is not seen. */\nfunction frozenAddress(address: Address): Address {\n\treturn typeof address === 'string'\n\t\t? address\n\t\t: Object.freeze({ name: address.name, address: address.address });\n}\n\n/**\n * A frozen copy of the checked links, each bound to the caller's object — a\n * class instance's methods keep their `this` — so replacing a link after\n * `janusMail()` cannot undo the check.\n */\nfunction frozenLinks(links: JanusMailLinks): JanusMailLinks {\n\treturn Object.freeze({\n\t\tverifyEmail: links.verifyEmail.bind(links),\n\t\tresetPassword: links.resetPassword.bind(links),\n\t\tsecureAccount: links.secureAccount.bind(links),\n\t});\n}\n\n/**\n * Checks `janusMail()`'s options and answers them resolved, or throws a\n * `TypeError`. What was checked is copied and frozen, so the guarantee made\n * here holds at every send.\n */\nexport function resolveOptions(options: unknown): ResolvedOptions {\n\tif (!isObject(options)) {\n\t\trefuse('options must be an object, as { mailer, from, brand, links }');\n\t}\n\tcheckSending(options);\n\tconst { locales, fallbackLocale } = resolveLocales(options);\n\treturn Object.freeze({\n\t\tmailer: options.mailer as Mailer,\n\t\tfrom: frozenAddress(options.from as Address),\n\t\treplyTo:\n\t\t\toptions.replyTo === undefined\n\t\t\t\t? undefined\n\t\t\t\t: frozenAddress(options.replyTo as Address),\n\t\tbrand: options.brand as string,\n\t\tlinks: frozenLinks(options.links as unknown as JanusMailLinks),\n\t\tlocales,\n\t\tfallbackLocale,\n\t\ttemplates: resolveTemplates(options, locales),\n\t});\n}\n"
11
+ "/**\n * The default templates: the prebuilt e-mails, filled by `@nxgt/mail`'s\n * renderer. Each picks from its variables exactly what its e-mail takes — the\n * renderer refuses a variable too many — so the `locale` beside them goes to\n * the renderer's options, and nowhere else.\n */\nimport type { Rendered } from '@nxgt/mail';\nimport { defaultRenderer } from './mails';\nimport type { JanusMailTemplates } from './types';\n\n/**\n * The five default templates, in the locales they are built in. The\n * renderer behind them is created on the first render, not here.\n *\n * ```ts\n * const defaults = janusTemplates();\n * const { subject } = await defaults.verifyEmail({ brand: 'Acme', name: 'Ada', link, locale: 'fr' });\n * ```\n *\n * A URL that is not `http:`, `https:` or `mailto:` throws `MailRefused`; a\n * missing `mails/` folder throws when the first one renders.\n */\nexport function janusTemplates(): JanusMailTemplates {\n\treturn Object.freeze({\n\t\tverifyEmail: ({ brand, name, link, expiresIn, locale }): Rendered =>\n\t\t\tdefaultRenderer().render(\n\t\t\t\t'verify-email',\n\t\t\t\t{ brand, name, link, expiresIn },\n\t\t\t\t{ locale },\n\t\t\t),\n\t\tresetPassword: ({ brand, name, link, expiresIn, locale }): Rendered =>\n\t\t\tdefaultRenderer().render(\n\t\t\t\t'reset-password',\n\t\t\t\t{ brand, name, link, expiresIn },\n\t\t\t\t{ locale },\n\t\t\t),\n\t\tsignInCode: ({ brand, code, expiresIn, locale }): Rendered =>\n\t\t\tdefaultRenderer().render(\n\t\t\t\t'sign-in-code',\n\t\t\t\t{ brand, code, expiresIn },\n\t\t\t\t{ locale },\n\t\t\t),\n\t\tpasswordChanged: ({ brand, name, link, locale }): Rendered =>\n\t\t\tdefaultRenderer().render(\n\t\t\t\t'password-changed',\n\t\t\t\t{ brand, name, link },\n\t\t\t\t{ locale },\n\t\t\t),\n\t\temailChanged: ({ brand, name, link, newEmail, locale }): Rendered =>\n\t\t\tdefaultRenderer().render(\n\t\t\t\t'email-changed',\n\t\t\t\t{ brand, name, link, newEmail },\n\t\t\t\t{ locale },\n\t\t\t),\n\t});\n}\n",
12
+ "/**\n * `janusMail()`'s options, checked once when it is called. A mistake is a\n * wiring mistake, so a bare `TypeError` naming the option — never its value —\n * thrown before anything is sent.\n */\nimport type { Clock } from '@nxgt/janus';\nimport type { Address, Mailer } from '@nxgt/mail';\nimport { LOCALES } from './generated/locales';\nimport { janusTemplates } from './templates';\nimport type {\n\tJanusMailLinks,\n\tJanusMailTemplateName,\n\tJanusMailTemplates,\n} from './types';\n\n/** The templates in the order the documentation lists them. */\nexport const TEMPLATE_NAMES: readonly JanusMailTemplateName[] = [\n\t'verifyEmail',\n\t'resetPassword',\n\t'signInCode',\n\t'passwordChanged',\n\t'emailChanged',\n];\n\nconst LINK_NAMES = ['verifyEmail', 'resetPassword', 'secureAccount'] as const;\n\n/** The options once checked, with `string` for the locales: the degenericised mirror. */\nexport interface ResolvedOptions {\n\treadonly mailer: Mailer;\n\treadonly from: Address;\n\treadonly replyTo: Address | undefined;\n\treadonly brand: string;\n\treadonly links: JanusMailLinks;\n\treadonly locales: readonly string[];\n\treadonly fallbackLocale: string;\n\treadonly templates: JanusMailTemplates<string>;\n\treadonly clock: Clock;\n}\n\n/** The system clock, written here: `@nxgt/janus` is a peer for its types only. */\nconst SYSTEM_CLOCK: Clock = Object.freeze({ now: () => new Date() });\n\nfunction isObject(value: unknown): value is Record<string, unknown> {\n\treturn typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction isAddress(value: unknown): value is Address {\n\tif (typeof value === 'string') return value.trim() !== '';\n\treturn (\n\t\tisObject(value) &&\n\t\ttypeof value.name === 'string' &&\n\t\ttypeof value.address === 'string' &&\n\t\tvalue.address.trim() !== ''\n\t);\n}\n\nfunction refuse(message: string): never {\n\tthrow new TypeError(`janusMail: ${message}`);\n}\n\n/** `mailer`, `from`, `replyTo`, `brand` and `links`. */\nfunction checkSending(options: Record<string, unknown>): void {\n\tconst { mailer, from, replyTo, brand, links } = options;\n\tif (!isObject(mailer) || typeof mailer.send !== 'function') {\n\t\trefuse('mailer must be a Mailer — an object with a send function');\n\t}\n\tif (!isAddress(from)) {\n\t\trefuse(\n\t\t\t\"from must be an address, as 'noreply@example.com' or { name, address }\",\n\t\t);\n\t}\n\tif (replyTo !== undefined && !isAddress(replyTo)) {\n\t\trefuse(\n\t\t\t\"replyTo must be an address, as 'support@example.com' or { name, address }\",\n\t\t);\n\t}\n\tif (typeof brand !== 'string' || brand.trim() === '') {\n\t\trefuse(\"brand must be the name the e-mails show, as 'Acme'\");\n\t}\n\tif (!isObject(links)) {\n\t\trefuse(\n\t\t\t'links must be an object, as { verifyEmail, resetPassword, secureAccount }',\n\t\t);\n\t}\n\tfor (const name of LINK_NAMES) {\n\t\tif (typeof links[name] !== 'function') {\n\t\t\trefuse(`links.${name} must be a function`);\n\t\t}\n\t}\n}\n\n/** `clock`, bound to the caller's object, or the system clock. */\nfunction resolveClock(clock: unknown): Clock {\n\tif (clock === undefined) return SYSTEM_CLOCK;\n\tif (!isObject(clock) || typeof clock.now !== 'function') {\n\t\trefuse('clock must be a Clock — an object with a now function');\n\t}\n\treturn Object.freeze({ now: (clock.now as () => Date).bind(clock) });\n}\n\n/** Whether `Intl` takes the locale — the expiry is formatted in it — as `fr-CA`, not `fr_CA`. */\nfunction isLanguageTag(locale: string): boolean {\n\ttry {\n\t\tIntl.getCanonicalLocales(locale);\n\t\treturn true;\n\t} catch {\n\t\treturn false;\n\t}\n}\n\n/** `locales` and `fallbackLocale`, with their defaults. */\nfunction resolveLocales(options: Record<string, unknown>): {\n\tlocales: readonly string[];\n\tfallbackLocale: string;\n} {\n\tconst wanted = options.locales ?? LOCALES;\n\tif (\n\t\t!Array.isArray(wanted) ||\n\t\twanted.length === 0 ||\n\t\t!wanted.every((locale) => typeof locale === 'string' && locale !== '')\n\t) {\n\t\trefuse(\"locales must list at least one locale, as ['en', 'fr']\");\n\t}\n\tconst locales: readonly string[] = Object.freeze([...wanted]);\n\tif (!locales.every(isLanguageTag)) {\n\t\trefuse(\"locales must be BCP 47 language tags, as 'fr-CA'\");\n\t}\n\tif (new Set(locales).size !== locales.length) {\n\t\trefuse('locales holds the same locale twice');\n\t}\n\tconst fallbackLocale =\n\t\toptions.fallbackLocale ??\n\t\t(locales.includes('en') ? 'en' : (locales[0] as string));\n\tif (typeof fallbackLocale !== 'string' || !locales.includes(fallbackLocale)) {\n\t\trefuse('fallbackLocale must be one of locales');\n\t}\n\treturn { locales, fallbackLocale };\n}\n\n/** `templates` over the defaults — the defaults only while every locale is built. */\nfunction resolveTemplates(\n\toptions: Record<string, unknown>,\n\tlocales: readonly string[],\n): JanusMailTemplates<string> {\n\tconst given = options.templates ?? {};\n\tif (!isObject(given)) {\n\t\trefuse(\n\t\t\t'templates must be an object of functions, as { verifyEmail: (variables) => rendered }',\n\t\t);\n\t}\n\tfor (const [name, template] of Object.entries(given)) {\n\t\tif (!(TEMPLATE_NAMES as readonly string[]).includes(name)) {\n\t\t\trefuse(\n\t\t\t\t`templates has no template ${name} — name one of ${TEMPLATE_NAMES.join(', ')}`,\n\t\t\t);\n\t\t}\n\t\tif (typeof template !== 'function') {\n\t\t\trefuse(`templates.${name} must be a function`);\n\t\t}\n\t}\n\t// Own enumerable keys, the ones the spread below copies. A template on a\n\t// prototype — a class instance's method — or an own property that is not\n\t// enumerable would be dropped without a word, the default sent in its\n\t// place: `in` sees it and `passed` does not, so it is refused instead.\n\tconst passed = Object.keys(given);\n\tfor (const name of TEMPLATE_NAMES) {\n\t\tif (name in given && !passed.includes(name)) {\n\t\t\trefuse(\n\t\t\t\t`templates.${name} is not an own enumerable property — pass a plain object, as { ${name}: (variables) => rendered }`,\n\t\t\t);\n\t\t}\n\t}\n\tconst built = locales.every((locale) =>\n\t\t(LOCALES as readonly string[]).includes(locale),\n\t);\n\tconst missing = TEMPLATE_NAMES.filter((name) => !passed.includes(name));\n\tif (!built && missing.length > 0) {\n\t\trefuse(\n\t\t\t`the default templates are built in ${LOCALES.join(' and ')} only — with another ` +\n\t\t\t\t`locale in locales, pass every template in templates; ${missing.join(', ')} missing`,\n\t\t);\n\t}\n\t// The one cast: a default template takes a built locale only, and the check\n\t// above proves every locale is one whenever a default is kept.\n\tconst defaults = janusTemplates() as unknown as JanusMailTemplates<string>;\n\treturn Object.freeze({ ...defaults, ...given }) as JanusMailTemplates<string>;\n}\n\n/** A frozen copy of a checked address: a later change to the caller's object is not seen. */\nfunction frozenAddress(address: Address): Address {\n\treturn typeof address === 'string'\n\t\t? address\n\t\t: Object.freeze({ name: address.name, address: address.address });\n}\n\n/**\n * A frozen copy of the checked links, each bound to the caller's object — a\n * class instance's methods keep their `this` — so replacing a link after\n * `janusMail()` cannot undo the check.\n */\nfunction frozenLinks(links: JanusMailLinks): JanusMailLinks {\n\treturn Object.freeze({\n\t\tverifyEmail: links.verifyEmail.bind(links),\n\t\tresetPassword: links.resetPassword.bind(links),\n\t\tsecureAccount: links.secureAccount.bind(links),\n\t});\n}\n\n/**\n * Checks `janusMail()`'s options and answers them resolved, or throws a\n * `TypeError`. What was checked is copied and frozen, so the guarantee made\n * here holds at every send.\n */\nexport function resolveOptions(options: unknown): ResolvedOptions {\n\tif (!isObject(options)) {\n\t\trefuse('options must be an object, as { mailer, from, brand, links }');\n\t}\n\tcheckSending(options);\n\tconst { locales, fallbackLocale } = resolveLocales(options);\n\treturn Object.freeze({\n\t\tmailer: options.mailer as Mailer,\n\t\tfrom: frozenAddress(options.from as Address),\n\t\treplyTo:\n\t\t\toptions.replyTo === undefined\n\t\t\t\t? undefined\n\t\t\t\t: frozenAddress(options.replyTo as Address),\n\t\tbrand: options.brand as string,\n\t\tlinks: frozenLinks(options.links as unknown as JanusMailLinks),\n\t\tlocales,\n\t\tfallbackLocale,\n\t\ttemplates: resolveTemplates(options, locales),\n\t\tclock: resolveClock(options.clock),\n\t});\n}\n",
13
+ "/**\n * `janusMail()`: the e-mails of Janus's flows, rendered by the templates and\n * handed to the mailer. It sends what a flow answered, to the address the\n * flow answered, and nothing else: no retry, no queue, no error of its own —\n * the mailer's `MailFailure` and `MailRefused` reach the caller untouched.\n */\n\nimport { expiringMails } from './expiring-mails';\nimport { noticeMails } from './notice-mails';\nimport { resolveOptions } from './options';\nimport type { JanusMail, JanusMailLocale, JanusMailOptions } from './types';\n\n/**\n * The e-mails of Janus's flows, over your mailer:\n *\n * ```ts\n * const mail = janusMail({\n * \tmailer,\n * \tfrom: 'noreply@acme.example',\n * \tbrand: 'Acme',\n * \tlinks: {\n * \t\tverifyEmail: (token) => `https://acme.example/verify?token=${token}`,\n * \t\tresetPassword: (token) => `https://acme.example/reset?token=${token}`,\n * \t\tsecureAccount: () => 'https://acme.example/account/security',\n * \t},\n * });\n *\n * await mail.verifyEmail(await auth.verifyEmail.send(user), { name: user.name, locale: user.locale });\n * ```\n *\n * Reads nothing when called: the default e-mails are read on the first one\n * sent. A wrong option is a bare `TypeError`, thrown here.\n */\nexport function janusMail<const L extends string = JanusMailLocale>(\n\toptions: JanusMailOptions<L>,\n): JanusMail<L> {\n\tconst resolved = resolveOptions(options);\n\tconst mail: JanusMail<string> = Object.freeze({\n\t\t...expiringMails(resolved),\n\t\t...noticeMails(resolved),\n\t\ttemplates: resolved.templates,\n\t\tlocales: resolved.locales,\n\t});\n\t// The one cast at the boundary: the mirror above works on `string` locales.\n\treturn mail as unknown as JanusMail<L>;\n}\n"
10
14
  ],
11
- "mappings": "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",
12
- "debugId": "B2A314C222070DD464756E2164756E21",
15
+ "mappings": "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",
16
+ "debugId": "B823BF1AC1618A7C64756E2164756E21",
13
17
  "names": []
14
18
  }
@@ -1,3 +1,9 @@
1
+ /**
2
+ * `janusMail()`: the e-mails of Janus's flows, rendered by the templates and
3
+ * handed to the mailer. It sends what a flow answered, to the address the
4
+ * flow answered, and nothing else: no retry, no queue, no error of its own —
5
+ * the mailer's `MailFailure` and `MailRefused` reach the caller untouched.
6
+ */
1
7
  import type { JanusMail, JanusMailLocale, JanusMailOptions } from './types';
2
8
  /**
3
9
  * The e-mails of Janus's flows, over your mailer:
@@ -1 +1 @@
1
- {"version":3,"file":"janus-mail.d.ts","sourceRoot":"","sources":["../src/janus-mail.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EACX,SAAS,EACT,eAAe,EACf,gBAAgB,EAGhB,MAAM,SAAS,CAAC;AAqJjB;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,SAAS,CAAC,KAAK,CAAC,CAAC,SAAS,MAAM,GAAG,eAAe,EACjE,OAAO,EAAE,gBAAgB,CAAC,CAAC,CAAC,GAC1B,SAAS,CAAC,CAAC,CAAC,CASd"}
1
+ {"version":3,"file":"janus-mail.d.ts","sourceRoot":"","sources":["../src/janus-mail.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAKH,OAAO,KAAK,EAAE,SAAS,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAE5E;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,SAAS,CAAC,KAAK,CAAC,CAAC,SAAS,MAAM,GAAG,eAAe,EACjE,OAAO,EAAE,gBAAgB,CAAC,CAAC,CAAC,GAC1B,SAAS,CAAC,CAAC,CAAC,CAUd"}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * The two notices of a change the user made — or someone else did:
3
+ * `passwordChanged` and `emailChanged`. Each links to where the account is
4
+ * secured.
5
+ */
6
+ import type { ResolvedOptions } from './options';
7
+ import type { JanusMail } from './types';
8
+ type NoticeMails = Pick<JanusMail<string>, 'passwordChanged' | 'emailChanged'>;
9
+ /** `passwordChanged` and `emailChanged`, over the resolved options. */
10
+ export declare function noticeMails(options: ResolvedOptions): NoticeMails;
11
+ export {};
12
+ //# sourceMappingURL=notice-mails.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"notice-mails.d.ts","sourceRoot":"","sources":["../src/notice-mails.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AAEjD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAEzC,KAAK,WAAW,GAAG,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,EAAE,iBAAiB,GAAG,cAAc,CAAC,CAAC;AAE/E,uEAAuE;AACvE,wBAAgB,WAAW,CAAC,OAAO,EAAE,eAAe,GAAG,WAAW,CAoCjE"}
package/dist/options.d.ts CHANGED
@@ -3,6 +3,7 @@
3
3
  * wiring mistake, so a bare `TypeError` naming the option — never its value —
4
4
  * thrown before anything is sent.
5
5
  */
6
+ import type { Clock } from '@nxgt/janus';
6
7
  import type { Address, Mailer } from '@nxgt/mail';
7
8
  import type { JanusMailLinks, JanusMailTemplateName, JanusMailTemplates } from './types';
8
9
  /** The templates in the order the documentation lists them. */
@@ -17,6 +18,7 @@ export interface ResolvedOptions {
17
18
  readonly locales: readonly string[];
18
19
  readonly fallbackLocale: string;
19
20
  readonly templates: JanusMailTemplates<string>;
21
+ readonly clock: Clock;
20
22
  }
21
23
  /**
22
24
  * Checks `janusMail()`'s options and answers them resolved, or throws a
@@ -1 +1 @@
1
- {"version":3,"file":"options.d.ts","sourceRoot":"","sources":["../src/options.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AAGlD,OAAO,KAAK,EACX,cAAc,EACd,qBAAqB,EACrB,kBAAkB,EAClB,MAAM,SAAS,CAAC;AAEjB,+DAA+D;AAC/D,eAAO,MAAM,cAAc,EAAE,SAAS,qBAAqB,EAM1D,CAAC;AAIF,yFAAyF;AACzF,MAAM,WAAW,eAAe;IAC/B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IACvB,QAAQ,CAAC,OAAO,EAAE,OAAO,GAAG,SAAS,CAAC;IACtC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,KAAK,EAAE,cAAc,CAAC;IAC/B,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;IACpC,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,SAAS,EAAE,kBAAkB,CAAC,MAAM,CAAC,CAAC;CAC/C;AAkJD;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,OAAO,GAAG,eAAe,CAmBhE"}
1
+ {"version":3,"file":"options.d.ts","sourceRoot":"","sources":["../src/options.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AAGlD,OAAO,KAAK,EACX,cAAc,EACd,qBAAqB,EACrB,kBAAkB,EAClB,MAAM,SAAS,CAAC;AAEjB,+DAA+D;AAC/D,eAAO,MAAM,cAAc,EAAE,SAAS,qBAAqB,EAM1D,CAAC;AAIF,yFAAyF;AACzF,MAAM,WAAW,eAAe;IAC/B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IACvB,QAAQ,CAAC,OAAO,EAAE,OAAO,GAAG,SAAS,CAAC;IACtC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,KAAK,EAAE,cAAc,CAAC;IAC/B,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;IACpC,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,SAAS,EAAE,kBAAkB,CAAC,MAAM,CAAC,CAAC;IAC/C,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC;CACtB;AA2KD;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,OAAO,GAAG,eAAe,CAoBhE"}
package/dist/send.d.ts ADDED
@@ -0,0 +1,27 @@
1
+ /**
2
+ * What every method shares: reading an argument's fields, a link, the
3
+ * recipient's locale, and the send itself. A wrong argument is a `TypeError`
4
+ * naming the method and the field — never its value.
5
+ */
6
+ import type { SentMail } from '@nxgt/mail';
7
+ import type { ResolvedOptions } from './options';
8
+ import type { JanusMailTemplateName, JanusMailVariables } from './types';
9
+ /** A string field of an argument, or a `TypeError` naming the method and the field. */
10
+ export declare function field(method: string, value: unknown, key: string): string;
11
+ /**
12
+ * What a `links` function answered, or a `TypeError` naming the method and
13
+ * the call. Its scheme is the renderer's to check: `http(s)` and `mailto:`
14
+ * pass, anything else is its `MailRefused`.
15
+ */
16
+ export declare function link(method: string, value: unknown, call: string): string;
17
+ /** The locale the recipient gets: the first they want that is sent in, else the fallback. */
18
+ export declare function localeFor(options: ResolvedOptions, to: unknown): string;
19
+ /**
20
+ * Renders one e-mail and hands it to the mailer. Only `subject`, `html` and
21
+ * `text` are read from what the template answered: a template cannot add a
22
+ * recipient or a header.
23
+ */
24
+ export declare function send<K extends JanusMailTemplateName>(options: ResolvedOptions, name: K, address: string, variables: JanusMailVariables[K] & {
25
+ readonly locale: string;
26
+ }): Promise<SentMail>;
27
+ //# sourceMappingURL=send.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"send.d.ts","sourceRoot":"","sources":["../src/send.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,KAAK,EAAqB,QAAQ,EAAiB,MAAM,YAAY,CAAC;AAE7E,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AACjD,OAAO,KAAK,EAAE,qBAAqB,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAC;AAEzE,uFAAuF;AACvF,wBAAgB,KAAK,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CASzE;AAED;;;;GAIG;AACH,wBAAgB,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAOzE;AAED,6FAA6F;AAC7F,wBAAgB,SAAS,CAAC,OAAO,EAAE,eAAe,EAAE,EAAE,EAAE,OAAO,GAAG,MAAM,CAMvE;AAED;;;;GAIG;AACH,wBAAsB,IAAI,CAAC,CAAC,SAAS,qBAAqB,EACzD,OAAO,EAAE,eAAe,EACxB,IAAI,EAAE,CAAC,EACP,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,kBAAkB,CAAC,CAAC,CAAC,GAAG;IAAE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAC5D,OAAO,CAAC,QAAQ,CAAC,CAenB"}
@@ -1 +1 @@
1
- {"version":3,"file":"templates.d.ts","sourceRoot":"","sources":["../src/templates.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAC;AAElD;;;;;;;;;;;GAWG;AACH,wBAAgB,cAAc,IAAI,kBAAkB,CA6BnD"}
1
+ {"version":3,"file":"templates.d.ts","sourceRoot":"","sources":["../src/templates.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAC;AAElD;;;;;;;;;;;GAWG;AACH,wBAAgB,cAAc,IAAI,kBAAkB,CAiCnD"}
package/dist/types.d.ts CHANGED
@@ -3,14 +3,16 @@
3
3
  * implementation works on a degenericised mirror (`string` locales) and casts
4
4
  * once, in `janusMail()`.
5
5
  */
6
- import type { IssuedCode, IssuedToken } from '@nxgt/janus';
6
+ import type { Clock, IssuedCode, IssuedToken } from '@nxgt/janus';
7
7
  import type { Address, Mailer, Rendered, SentMail, WantedLocales } from '@nxgt/mail';
8
8
  import type { JanusMailLocale } from './generated/locales';
9
9
  export type { JanusMailLocale } from './generated/locales';
10
10
  /**
11
11
  * What each e-mail's template is given, by template name. Every value is
12
12
  * text: `brand` is the name `janusMail({ brand })` was given, `name` the
13
- * recipient's, `link` an absolute URL from `links`.
13
+ * recipient's, `link` an absolute URL from `links`, and `expiresIn` how long
14
+ * the link or the code stays valid, in the recipient's locale — "1 hour",
15
+ * "1 heure" — or the send's own `expiresIn`.
14
16
  *
15
17
  * Held equal to the build's variables by `test/types/variables.ts`: an e-mail
16
18
  * of the build that gains or loses a variable fails the typecheck there.
@@ -20,16 +22,19 @@ export interface JanusMailVariables {
20
22
  readonly brand: string;
21
23
  readonly name: string;
22
24
  readonly link: string;
25
+ readonly expiresIn: string;
23
26
  };
24
27
  readonly resetPassword: {
25
28
  readonly brand: string;
26
29
  readonly name: string;
27
30
  readonly link: string;
31
+ readonly expiresIn: string;
28
32
  };
29
33
  /** No `challenge`, ever: it is the visitor's secret, and never goes in an e-mail. */
30
34
  readonly signInCode: {
31
35
  readonly brand: string;
32
36
  readonly code: string;
37
+ readonly expiresIn: string;
33
38
  };
34
39
  readonly passwordChanged: {
35
40
  readonly brand: string;
@@ -102,6 +107,25 @@ interface JanusMailBaseOptions<L extends string> {
102
107
  readonly locales?: readonly L[];
103
108
  /** The locale when the recipient wants none of `locales`. Default `en`, else the first of `locales`. */
104
109
  readonly fallbackLocale?: NoInfer<L>;
110
+ /**
111
+ * What the time left until a flow's `expiresAt` is measured against: pass
112
+ * the clock given to `janus({ clock })` — a `fixedClock` in tests. Default
113
+ * the system clock.
114
+ */
115
+ readonly clock?: Clock;
116
+ }
117
+ /**
118
+ * What one send of `verifyEmail`, `resetPassword` or `signInCode` may pass
119
+ * besides the flow's answer and the recipient.
120
+ */
121
+ export interface JanusMailSendOptions {
122
+ /**
123
+ * How long the link or the code stays valid, as the e-mail shows it —
124
+ * plain text, already in the recipient's language: `'24 heures'`. Default:
125
+ * the time left until the flow's `expiresAt`, in the largest whole unit,
126
+ * in the recipient's locale.
127
+ */
128
+ readonly expiresIn?: string;
105
129
  }
106
130
  /**
107
131
  * `janusMail()`'s options. `templates` replaces any of the defaults — and
@@ -119,15 +143,20 @@ export type JanusMailOptions<L extends string = JanusMailLocale> = JanusMailBase
119
143
  * rejects with the mailer's `MailFailure` or `MailRefused`, untouched.
120
144
  */
121
145
  export interface JanusMail<L extends string = JanusMailLocale> {
122
- /** What `auth.verifyEmail.send(user)` answered, to `issued.email`. */
123
- verifyEmail(issued: IssuedToken, to: Recipient): Promise<SentMail>;
146
+ /**
147
+ * What `auth.verifyEmail.send(user)` answered, to `issued.email`, saying
148
+ * how long the link stays valid — from `issued.expiresAt`, unless
149
+ * `options.expiresIn` says it.
150
+ */
151
+ verifyEmail(issued: IssuedToken, to: Recipient, options?: JanusMailSendOptions): Promise<SentMail>;
124
152
  /** What `auth.resetPassword.request(email)` answered, once checked for `null`, to `issued.email`. */
125
- resetPassword(issued: IssuedToken, to: Recipient): Promise<SentMail>;
153
+ resetPassword(issued: IssuedToken, to: Recipient, options?: JanusMailSendOptions): Promise<SentMail>;
126
154
  /**
127
155
  * The code `auth.signInCode.request(email)` answered, to `issued.email`.
128
- * Reads `code` and `email` only: the challenge never reaches the e-mail.
156
+ * Reads `code`, `email` and `expiresAt` only: the challenge never reaches
157
+ * the e-mail.
129
158
  */
130
- signInCode(issued: Pick<IssuedCode<unknown>, 'code' | 'email'>, to?: Pick<Recipient, 'locale'>): Promise<SentMail>;
159
+ signInCode(issued: Pick<IssuedCode<unknown>, 'code' | 'email' | 'expiresAt'>, to?: Pick<Recipient, 'locale'>, options?: JanusMailSendOptions): Promise<SentMail>;
131
160
  /** Tells `to.email` their password was changed, with a link to secure the account. */
132
161
  passwordChanged(to: Recipient & {
133
162
  readonly email: string;
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
package/docs/README.md CHANGED
@@ -5,7 +5,7 @@ the detail.
5
5
 
6
6
  | Page | Read it when |
7
7
  | --- | --- |
8
- | [Sending](guide/sending.md) | You are wiring `janusMail()` into your routes: the options, each method, where each e-mail goes, what a failure looks like, and a test |
8
+ | [Sending](guide/sending.md) | You are wiring `janusMail()` into your routes: the options — `clock` among them — each method, the expiry an e-mail states and how to set it, where each e-mail goes, what a failure looks like, and a test |
9
9
  | [Templates](guide/templates.md) | You want an e-mail of your own: what a template takes and answers, a partial override, `janusTemplates()`, and what the defaults say |
10
10
  | [Locales](guide/locales.md) | You send in more than one language: how the locale is picked, `fallbackLocale`, fewer locales, and adding one |
11
11
  | [Building](guide/building.md) | You work on this package: the Maizzle project in `mail/`, `mails/`, the generated files, and why none of it reaches a consumer's build |
@@ -30,5 +30,6 @@ They mean the same here. These pages add:
30
30
  | **issued** | What a flow answered: an `IssuedToken` from `verifyEmail.send` or `resetPassword.request`, an `IssuedCode` from `signInCode.request` | "result", "payload" |
31
31
  | **recipient** | Who an e-mail is for besides the address: a `Recipient`, `{ name, locale? }` | "user" — a user is a record of `@nxgt/janus`; "to" is the address |
32
32
  | **notice** | An e-mail that tells, and asks for nothing: `passwordChanged`, `emailChanged`. It links to `links.secureAccount()` | "alert", "notification" |
33
+ | **expiry** | How long a link or a code lasts, as the e-mail says it: `expiresIn`, derived from the flow's `expiresAt` in the recipient's locale — "1 hour", "1 heure" — or passed per send | "TTL", "lifetime" — the `tokens` TTL is `@nxgt/janus`'s setting, which sets `expiresAt` |
33
34
  | **brand** | The name the e-mails show, `janusMail({ brand })`: text, filled at send time | "logo", "company" |
34
35
  | **the build** | The e-mails as `scripts/build-mail.ts` writes them to `mails/`: HTML and text per locale, and the manifest | "dist" — `mails/` is not `dist/` |
@@ -56,7 +56,7 @@ The package peers `@nxgt/mail` at `>=0.1.0 <1`, so the build it ships must
56
56
  be readable by `@nxgt/mail` 0.1.0. The manifest says which format it is in —
57
57
  `formatVersion`, its first key, `MANIFEST_FORMAT` of the `@nxgt/mail-i18n`
58
58
  that built it — and within 0.x a renderer reads every format up to its own.
59
- `@nxgt/mail` 0.1.0 through 0.5.0 read format 1, and a manifest without the
59
+ `@nxgt/mail` 0.1.0 through 0.6.0 read format 1, and a manifest without the
60
60
  field is format 1.
61
61
 
62
62
  `formatProblem` in `scripts/build-mail.ts` (spec'd beside it) fails the build
@@ -95,6 +95,28 @@ the build fails until it holds every message the presets use in `en` (see
95
95
  `@nxgt/mail-presets`'
96
96
  [Another locale](https://github.com/softistx/nxgt-mail/blob/develop/packages/mail-presets/docs/guide/presets.md#another-locale)).
97
97
 
98
+ An override in a catalogue uses the presets' own key, in kebab-case since
99
+ `@nxgt/mail-presets` 0.3.0, and nested, never dotted — `presets.link-expires`
100
+ is written:
101
+
102
+ ```json
103
+ { "presets": { "link-expires": "…" } }
104
+ ```
105
+
106
+ `@nxgt/mail-i18n` accepts a camelCase key too, so an override under a key from
107
+ before presets 0.3.0, such as `presets.linkExpires`, is a key of its own that
108
+ no preset reads. In `en`, the fallback locale, it is ignored without an
109
+ error, and the preset's own text goes out instead — in `fr` too, when `fr`
110
+ carries the same old key. In `fr` alone, the build refuses it:
111
+
112
+ ```text
113
+ Error: i18n: fr: presets.linkExpires is not a key of en, the fallback locale
114
+ ```
115
+
116
+ The presets'
117
+ [Upgrading](https://github.com/softistx/nxgt-mail/blob/develop/packages/mail-presets/docs/troubleshooting.md#upgrading)
118
+ entry lists every old and new key.
119
+
98
120
  ## Why `mails/` is not in `dist/`
99
121
 
100
122
  The root `build.ts` removes from `dist/` every file it did not write itself,
@@ -48,6 +48,10 @@ override can branch on it.
48
48
  | `locales` | `['en', 'fr']`, the built ones | The locales an e-mail can be sent in. Its type is inferred as written: `['en']` makes `L` `'en'` |
49
49
  | `fallbackLocale` | `'en'` when in `locales`, else the first of them | Sent when the recipient wants none of `locales`. Must be one of them: another is a compile error, and a `TypeError` |
50
50
 
51
+ Each locale must be a BCP 47 tag — `fr-CA`, never `fr_CA` — and `janusMail()`
52
+ refuses anything else with a `TypeError`:
53
+ [`janusMail: locales must be BCP 47 language tags, as 'fr-CA'`](../troubleshooting.md#janusmail-locales-must-be-bcp-47-language-tags-as-fr-ca).
54
+
51
55
  **Fewer locales.** `locales: ['en']` sends English to everyone, a French
52
56
  speaker included; the defaults still fit, and `templates` stays partial.
53
57
 
@@ -74,9 +78,9 @@ import { janusMail, type JanusMailTemplates } from '@nxgt/janus-mail';
74
78
  type Locale = 'en' | 'fr' | 'de';
75
79
 
76
80
  const templates: JanusMailTemplates<Locale> = {
77
- verifyEmail: ({ name, link, locale }) => render('verify-email', locale, { name, link }),
78
- resetPassword: ({ name, link, locale }) => render('reset-password', locale, { name, link }),
79
- signInCode: ({ code, locale }) => render('sign-in-code', locale, { code }),
81
+ verifyEmail: ({ name, link, expiresIn, locale }) => render('verify-email', locale, { name, link, expiresIn }),
82
+ resetPassword: ({ name, link, expiresIn, locale }) => render('reset-password', locale, { name, link, expiresIn }),
83
+ signInCode: ({ code, expiresIn, locale }) => render('sign-in-code', locale, { code, expiresIn }),
80
84
  passwordChanged: ({ name, link, locale }) => render('password-changed', locale, { name, link }),
81
85
  emailChanged: ({ name, link, newEmail, locale }) => render('email-changed', locale, { name, link, newEmail }),
82
86
  };
@@ -85,7 +89,9 @@ const mail = janusMail({ mailer, from, brand: 'Acme', links, locales: ['en', 'fr
85
89
  mail.locales; // readonly ('en' | 'fr' | 'de')[]
86
90
  ```
87
91
 
88
- `render` here is yours — a renderer over a Maizzle build of your own, with
92
+ `render` here is yours; `expiresIn` is the expiry, already formatted in
93
+ `locale` — "1 Stunde" for `de`, where the runtime's `Intl` has German. A
94
+ renderer over a Maizzle build of your own, with
89
95
  `@nxgt/mail-presets` and a `locales/de.json`, is the closest to the defaults.
90
96
  With only some templates, the call does not compile — the error lands on
91
97
  `templates`: