ras-stack 3.0.0 → 3.0.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.
package/README.md CHANGED
@@ -32,13 +32,13 @@ An application can use one surface without adopting the others. The npm package
32
32
 
33
33
  | Layer | Supported technology | What `ras-stack` centralizes |
34
34
  | ------------------- | ------------------------------------------- | ----------------------------------------------------------------------------------------------------- |
35
- | Runtime and tooling | Node, ESM TypeScript, pnpm, Just, Oxlint | Compiler/linter bases, setup actions, and version synchronization |
36
- | Web application | TanStack Start, React, TanStack Query | Request binding, mutation-origin checks, canonical hosts, health handlers, and Query defaults |
35
+ | Runtime and tooling | Node, ESM TypeScript, pnpm, Just, Oxlint | Setup actions and version synchronization |
36
+ | Web application | TanStack Start, React | Request binding, mutation-origin checks, canonical hosts, health handlers |
37
37
  | Authentication | Better Auth | Secure option builders, origins, secrets, redirects, failure classification, and React action state |
38
38
  | Data | Drizzle, `better-sqlite3`, Postgres.js | Connection lifecycle, safety defaults, migrations, target selection, and conformance checks |
39
39
  | Realtime | Centrifuge, Centrifugo, Caddy | Publishing, tokens, browser/React lifecycle, presence, proxy configuration, binaries, and supervision |
40
- | Email and uploads | Nodemailer, `tus-js-client` | SMTP configuration/delivery, auth callbacks, and promise-based resumable uploads |
41
- | Observability | PostHog JS, React, Node, and OpenTelemetry | Analytics, replay, errors, logs, metrics, tracing, correlation, proxying, shutdown, and coverage |
40
+ | Email and uploads | Nodemailer 9/10, `tus-js-client` | SMTP configuration/delivery, auth callbacks, and promise-based resumable uploads |
41
+ | Observability | PostHog JS, React, Node, and OpenTelemetry | Analytics, replay, errors, logs, metrics, tracing, correlation, proxying, and shutdown |
42
42
  | Delivery | GitHub Actions, Changesets, Dokploy, Docker | Checks, releases, preview lifecycle/status, production assets, and runtime binaries |
43
43
 
44
44
  Applications still configure every upstream library directly. This table describes what is tested together, not a replacement API.
@@ -1,4 +1,3 @@
1
- import nodemailer from 'nodemailer';
2
1
  export type EmailMessage = {
3
2
  to: string;
4
3
  subject: string;
@@ -46,7 +45,7 @@ export declare function createAuthEmailHandler<User extends {
46
45
  }>(delivery: EmailDelivery, message: AuthEmailMessageFactory<User>): (input: AuthEmailInput<User>, request?: Request) => Promise<void>;
47
46
  export declare function smtpConfigFromEnvironment(environment?: NodeJS.ProcessEnv, keys?: SmtpEnvironmentOptions): SmtpConfig | undefined;
48
47
  export declare function createSmtpDelivery(config: SmtpConfig): EmailDelivery;
49
- export declare function createSmtpTransport(config: SmtpConfig): nodemailer.Transporter<import("nodemailer/lib/smtp-transport/index.js").SentMessageInfo, import("nodemailer/lib/smtp-transport/index.js").Options>;
48
+ export declare function createSmtpTransport(config: SmtpConfig): import("nodemailer").Mail<import("nodemailer").SMTPSentMessageInfo, import("nodemailer").SMTPTransportOptions>;
50
49
  export type StandardAuthEmailOptions = {
51
50
  productName: string;
52
51
  };
@@ -1,4 +1,4 @@
1
- import nodemailer from 'nodemailer';
1
+ import { createTransport } from 'nodemailer';
2
2
  export function createAuthEmailHandler(delivery, message) {
3
3
  return async (input, request) => delivery.send(await message(input, request));
4
4
  }
@@ -37,7 +37,7 @@ export function createSmtpDelivery(config) {
37
37
  };
38
38
  }
39
39
  export function createSmtpTransport(config) {
40
- return nodemailer.createTransport({
40
+ return createTransport({
41
41
  host: config.host,
42
42
  port: config.port,
43
43
  secure: config.secure,
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/email/index.ts"],"names":[],"mappings":"AAAA,OAAO,UAAU,MAAM,YAAY,CAAA;AAsCnC,MAAM,UAAU,sBAAsB,CACpC,QAAuB,EACvB,OAAsC;IAEtC,OAAO,KAAK,EAAE,KAA2B,EAAE,OAAiB,EAAE,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,MAAM,OAAO,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC,CAAA;AAC/G,CAAC;AAED,MAAM,UAAU,yBAAyB,CACvC,WAAW,GAAsB,OAAO,CAAC,GAAG,EAC5C,IAAI,GAA2B,EAAE;IAEjC,MAAM,IAAI,GAAG,WAAW,CAAC,IAAI,CAAC,IAAI,IAAI,WAAW,CAAC,EAAE,IAAI,EAAE,CAAA;IAC1D,MAAM,IAAI,GAAG,WAAW,CAAC,IAAI,CAAC,IAAI,IAAI,YAAY,CAAC,EAAE,IAAI,EAAE,CAAA;IAC3D,IAAI,CAAC,IAAI,IAAI,CAAC,IAAI;QAAE,OAAO,SAAS,CAAA;IACpC,IAAI,CAAC,IAAI;QAAE,MAAM,IAAI,KAAK,CAAC,GAAG,IAAI,CAAC,IAAI,IAAI,WAAW,6BAA6B,CAAC,CAAA;IACpF,IAAI,CAAC,IAAI;QAAE,MAAM,IAAI,KAAK,CAAC,GAAG,IAAI,CAAC,IAAI,IAAI,YAAY,6BAA6B,CAAC,CAAA;IAErF,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,IAAI,WAAW,CAAA;IACxC,MAAM,IAAI,GAAG,MAAM,CAAC,WAAW,CAAC,OAAO,CAAC,IAAI,GAAG,CAAC,CAAA;IAChD,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,IAAI,GAAG,CAAC,IAAI,IAAI,GAAG,MAAM;QAAE,MAAM,IAAI,KAAK,CAAC,GAAG,OAAO,2BAA2B,CAAC,CAAA;IAEhH,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,IAAI,WAAW,CAAA;IACxC,MAAM,WAAW,GAAG,IAAI,CAAC,QAAQ,IAAI,eAAe,CAAA;IACpD,MAAM,IAAI,GAAG,WAAW,CAAC,OAAO,CAAC,EAAE,IAAI,EAAE,CAAA;IACzC,MAAM,QAAQ,GAAG,WAAW,CAAC,WAAW,CAAC,CAAA;IACzC,IAAI,OAAO,CAAC,IAAI,CAAC,KAAK,OAAO,CAAC,QAAQ,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,GAAG,OAAO,QAAQ,WAAW,8BAA8B,CAAC,CAAA;IAErH,OAAO;QACL,IAAI;QACJ,IAAI;QACJ,IAAI;QACJ,MAAM,EAAE,WAAW,CAAC,IAAI,CAAC,MAAM,IAAI,aAAa,CAAC,EAAE,IAAI,EAAE,CAAC,WAAW,EAAE,KAAK,MAAM;QAClF,GAAG,CAAC,IAAI,IAAI,QAAQ,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KAChD,CAAA;AACH,CAAC;AAED,MAAM,UAAU,kBAAkB,CAAC,MAAkB;IACnD,MAAM,SAAS,GAAG,mBAAmB,CAAC,MAAM,CAAC,CAAA;IAC7C,OAAO;QACL,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE,CAAC,SAAS,CAAC,QAAQ,CAAC,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE,GAAG,OAAO,EAAE,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC;QACpG,MAAM,EAAE,KAAK,IAAI,EAAE,CAAC,SAAS,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC;KAC7D,CAAA;AACH,CAAC;AAED,MAAM,UAAU,mBAAmB,CAAC,MAAkB;IACpD,OAAO,UAAU,CAAC,eAAe,CAAC;QAChC,IAAI,EAAE,MAAM,CAAC,IAAI;QACjB,IAAI,EAAE,MAAM,CAAC,IAAI;QACjB,MAAM,EAAE,MAAM,CAAC,MAAM;QACrB,IAAI,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE,IAAI,EAAE,MAAM,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,SAAS;KAC7E,CAAC,CAAA;AACJ,CAAC;AAID,MAAM,UAAU,kBAAkB,CAChC,QAAuB,EACvB,OAAiC;IAEjC,MAAM,MAAM,GAAG,gCAAgC,CAAA;IAC/C,MAAM,OAAO,GACX,CAAC,OAAe,EAAE,KAAa,EAAiC,EAAE,CAClE,CAAC,EAAE,IAAI,EAAE,GAAG,EAAE,EAAE,EAAE,CAAC,CAAC;QAClB,EAAE,EAAE,IAAI,CAAC,KAAK;QACd,OAAO;QACP,IAAI,EAAE,GAAG,OAAO,qBAAqB,GAAG,OAAO,MAAM,EAAE;QACvD,IAAI,EAAE,MAAM,UAAU,CAAC,OAAO,CAAC,yCAAyC,UAAU,CAAC,GAAG,CAAC,KAAK,KAAK,cAAc,MAAM,MAAM;KAC5H,CAAC,CAAA;IACJ,OAAO;QACL,iBAAiB,EAAE,sBAAsB,CAAC,QAAQ,EAAE,OAAO,CAAC,cAAc,OAAO,CAAC,WAAW,WAAW,EAAE,gBAAgB,CAAC,CAAC;QAC5H,qBAAqB,EAAE,sBAAsB,CAC3C,QAAQ,EACR,OAAO,CAAC,eAAe,OAAO,CAAC,WAAW,gBAAgB,EAAE,sBAAsB,CAAC,CACpF;KACF,CAAA;AACH,CAAC;AAED,SAAS,UAAU,CAAC,KAAa;IAC/B,OAAO,KAAK,CAAC,UAAU,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC,UAAU,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC,UAAU,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC,UAAU,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAA;AACjH,CAAC","sourcesContent":["import nodemailer from 'nodemailer'\n\nexport type EmailMessage = { to: string; subject: string; text: string; html?: string }\n\nexport type SmtpConfig = {\n from: string\n host: string\n port: number\n secure: boolean\n user?: string\n password?: string\n}\n\nexport type SmtpEnvironmentOptions = {\n host?: string\n port?: string\n secure?: string\n user?: string\n password?: string\n from?: string\n}\n\nexport type EmailDelivery = {\n send(message: EmailMessage): Promise<void>\n verify(): Promise<void>\n}\n\nexport type AuthEmailInput<User extends { email: string } = { email: string }> = {\n user: User\n url: string\n token: string\n}\n\nexport type AuthEmailMessageFactory<User extends { email: string } = { email: string }> = (\n input: AuthEmailInput<User>,\n request?: Request,\n) => EmailMessage | Promise<EmailMessage>\n\nexport function createAuthEmailHandler<User extends { email: string } = { email: string }>(\n delivery: EmailDelivery,\n message: AuthEmailMessageFactory<User>,\n) {\n return async (input: AuthEmailInput<User>, request?: Request) => delivery.send(await message(input, request))\n}\n\nexport function smtpConfigFromEnvironment(\n environment: NodeJS.ProcessEnv = process.env,\n keys: SmtpEnvironmentOptions = {},\n): SmtpConfig | undefined {\n const host = environment[keys.host ?? 'SMTP_HOST']?.trim()\n const from = environment[keys.from ?? 'EMAIL_FROM']?.trim()\n if (!host && !from) return undefined\n if (!host) throw new Error(`${keys.host ?? 'SMTP_HOST'} is required for SMTP email`)\n if (!from) throw new Error(`${keys.from ?? 'EMAIL_FROM'} is required for SMTP email`)\n\n const portKey = keys.port ?? 'SMTP_PORT'\n const port = Number(environment[portKey] ?? 587)\n if (!Number.isInteger(port) || port < 1 || port > 65_535) throw new Error(`${portKey} must be a valid TCP port`)\n\n const userKey = keys.user ?? 'SMTP_USER'\n const passwordKey = keys.password ?? 'SMTP_PASSWORD'\n const user = environment[userKey]?.trim()\n const password = environment[passwordKey]\n if (Boolean(user) !== Boolean(password)) throw new Error(`${userKey} and ${passwordKey} must be configured together`)\n\n return {\n from,\n host,\n port,\n secure: environment[keys.secure ?? 'SMTP_SECURE']?.trim().toLowerCase() === 'true',\n ...(user && password ? { user, password } : {}),\n }\n}\n\nexport function createSmtpDelivery(config: SmtpConfig): EmailDelivery {\n const transport = createSmtpTransport(config)\n return {\n send: async (message) => transport.sendMail({ from: config.from, ...message }).then(() => undefined),\n verify: async () => transport.verify().then(() => undefined),\n }\n}\n\nexport function createSmtpTransport(config: SmtpConfig) {\n return nodemailer.createTransport({\n host: config.host,\n port: config.port,\n secure: config.secure,\n auth: config.user ? { user: config.user, pass: config.password } : undefined,\n })\n}\n\nexport type StandardAuthEmailOptions = { productName: string }\n\nexport function standardAuthEmails<User extends { email: string } = { email: string }>(\n delivery: EmailDelivery,\n options: StandardAuthEmailOptions,\n) {\n const expiry = 'This link expires in one hour.'\n const message =\n (subject: string, label: string): AuthEmailMessageFactory<User> =>\n ({ user, url }) => ({\n to: user.email,\n subject,\n text: `${subject} using this link: ${url}\\n\\n${expiry}`,\n html: `<p>${escapeHtml(subject)} using the link below.</p><p><a href=\"${escapeHtml(url)}\">${label}</a></p><p>${expiry}</p>`,\n })\n return {\n sendResetPassword: createAuthEmailHandler(delivery, message(`Reset your ${options.productName} password`, 'Reset password')),\n sendVerificationEmail: createAuthEmailHandler(\n delivery,\n message(`Verify your ${options.productName} email address`, 'Verify email address'),\n ),\n }\n}\n\nfunction escapeHtml(value: string) {\n return value.replaceAll('&', '&amp;').replaceAll('<', '&lt;').replaceAll('>', '&gt;').replaceAll('\"', '&quot;')\n}\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/email/index.ts"],"names":[],"mappings":"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","sourcesContent":["import { createTransport } from 'nodemailer'\n\nexport type EmailMessage = { to: string; subject: string; text: string; html?: string }\n\nexport type SmtpConfig = {\n from: string\n host: string\n port: number\n secure: boolean\n user?: string\n password?: string\n}\n\nexport type SmtpEnvironmentOptions = {\n host?: string\n port?: string\n secure?: string\n user?: string\n password?: string\n from?: string\n}\n\nexport type EmailDelivery = {\n send(message: EmailMessage): Promise<void>\n verify(): Promise<void>\n}\n\nexport type AuthEmailInput<User extends { email: string } = { email: string }> = {\n user: User\n url: string\n token: string\n}\n\nexport type AuthEmailMessageFactory<User extends { email: string } = { email: string }> = (\n input: AuthEmailInput<User>,\n request?: Request,\n) => EmailMessage | Promise<EmailMessage>\n\nexport function createAuthEmailHandler<User extends { email: string } = { email: string }>(\n delivery: EmailDelivery,\n message: AuthEmailMessageFactory<User>,\n) {\n return async (input: AuthEmailInput<User>, request?: Request) => delivery.send(await message(input, request))\n}\n\nexport function smtpConfigFromEnvironment(\n environment: NodeJS.ProcessEnv = process.env,\n keys: SmtpEnvironmentOptions = {},\n): SmtpConfig | undefined {\n const host = environment[keys.host ?? 'SMTP_HOST']?.trim()\n const from = environment[keys.from ?? 'EMAIL_FROM']?.trim()\n if (!host && !from) return undefined\n if (!host) throw new Error(`${keys.host ?? 'SMTP_HOST'} is required for SMTP email`)\n if (!from) throw new Error(`${keys.from ?? 'EMAIL_FROM'} is required for SMTP email`)\n\n const portKey = keys.port ?? 'SMTP_PORT'\n const port = Number(environment[portKey] ?? 587)\n if (!Number.isInteger(port) || port < 1 || port > 65_535) throw new Error(`${portKey} must be a valid TCP port`)\n\n const userKey = keys.user ?? 'SMTP_USER'\n const passwordKey = keys.password ?? 'SMTP_PASSWORD'\n const user = environment[userKey]?.trim()\n const password = environment[passwordKey]\n if (Boolean(user) !== Boolean(password)) throw new Error(`${userKey} and ${passwordKey} must be configured together`)\n\n return {\n from,\n host,\n port,\n secure: environment[keys.secure ?? 'SMTP_SECURE']?.trim().toLowerCase() === 'true',\n ...(user && password ? { user, password } : {}),\n }\n}\n\nexport function createSmtpDelivery(config: SmtpConfig): EmailDelivery {\n const transport = createSmtpTransport(config)\n return {\n send: async (message) => transport.sendMail({ from: config.from, ...message }).then(() => undefined),\n verify: async () => transport.verify().then(() => undefined),\n }\n}\n\nexport function createSmtpTransport(config: SmtpConfig) {\n return createTransport({\n host: config.host,\n port: config.port,\n secure: config.secure,\n auth: config.user ? { user: config.user, pass: config.password } : undefined,\n })\n}\n\nexport type StandardAuthEmailOptions = { productName: string }\n\nexport function standardAuthEmails<User extends { email: string } = { email: string }>(\n delivery: EmailDelivery,\n options: StandardAuthEmailOptions,\n) {\n const expiry = 'This link expires in one hour.'\n const message =\n (subject: string, label: string): AuthEmailMessageFactory<User> =>\n ({ user, url }) => ({\n to: user.email,\n subject,\n text: `${subject} using this link: ${url}\\n\\n${expiry}`,\n html: `<p>${escapeHtml(subject)} using the link below.</p><p><a href=\"${escapeHtml(url)}\">${label}</a></p><p>${expiry}</p>`,\n })\n return {\n sendResetPassword: createAuthEmailHandler(delivery, message(`Reset your ${options.productName} password`, 'Reset password')),\n sendVerificationEmail: createAuthEmailHandler(\n delivery,\n message(`Verify your ${options.productName} email address`, 'Verify email address'),\n ),\n }\n}\n\nfunction escapeHtml(value: string) {\n return value.replaceAll('&', '&amp;').replaceAll('<', '&lt;').replaceAll('>', '&gt;').replaceAll('\"', '&quot;')\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ras-stack",
3
- "version": "3.0.0",
3
+ "version": "3.0.1",
4
4
  "description": "Composable full-stack primitives shared across Richard Solomou's applications.",
5
5
  "keywords": [
6
6
  "authentication",
@@ -150,7 +150,7 @@
150
150
  "better-sqlite3": "12.11.1",
151
151
  "centrifuge": "5.7.1",
152
152
  "drizzle-orm": "0.45.2",
153
- "nodemailer": "^9.0.3",
153
+ "nodemailer": "^10.0.11",
154
154
  "oxfmt": "^0.63.0",
155
155
  "oxlint": "^1.74.0",
156
156
  "oxlint-tsgolint": "^7.0.2001",
@@ -176,7 +176,7 @@
176
176
  "better-sqlite3": ">=12 <14",
177
177
  "centrifuge": ">=5 <6",
178
178
  "drizzle-orm": ">=0.45 <1",
179
- "nodemailer": ">=9 <10",
179
+ "nodemailer": ">=9 <11",
180
180
  "postgres": ">=3.4 <4",
181
181
  "posthog-js": ">=1.430.3 <2",
182
182
  "posthog-node": ">=5.52 <6",