@wrelik/email 0.1.3 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,23 +1,27 @@
1
1
 
2
- > @wrelik/email@0.1.3 build /home/runner/work/wrelik-kit/wrelik-kit/packages/email
3
- > tsup src/index.ts src/unsupported.ts --format cjs,esm --dts --clean
2
+ > @wrelik/email@2.0.0 build /home/runner/work/wrelik-kit/wrelik-kit/packages/email
3
+ > tsup src/server/index.ts src/client/index.ts src/shared/index.ts --format cjs,esm --dts --clean
4
4
 
5
- CLI Building entry: src/index.ts, src/unsupported.ts
5
+ CLI Building entry: src/client/index.ts, src/server/index.ts, src/shared/index.ts
6
6
  CLI Using tsconfig: tsconfig.json
7
7
  CLI tsup v8.5.1
8
8
  CLI Target: es2022
9
9
  CLI Cleaning output folder
10
10
  CJS Build start
11
11
  ESM Build start
12
- CJS dist/index.js 2.27 KB
13
- CJS dist/unsupported.js 192.00 B
14
- CJS ⚡️ Build success in 74ms
15
- ESM dist/index.mjs 1.15 KB
16
- ESM dist/unsupported.mjs 177.00 B
17
- ESM ⚡️ Build success in 75ms
12
+ ESM dist/client/index.mjs 274.00 B
13
+ ESM dist/shared/index.mjs 0 B
14
+ ESM dist/server/index.mjs 1.87 KB
15
+ ESM ⚡️ Build success in 77ms
16
+ CJS dist/client/index.js 1.28 KB
17
+ CJS dist/server/index.js 3.03 KB
18
+ CJS dist/shared/index.js 767.00 B
19
+ CJS ⚡️ Build success in 79ms
18
20
  DTS Build start
19
- DTS ⚡️ Build success in 2068ms
20
- DTS dist/unsupported.d.ts 13.00 B
21
- DTS dist/index.d.ts 900.00 B
22
- DTS dist/unsupported.d.mts 13.00 B
23
- DTS dist/index.d.mts 900.00 B
21
+ DTS ⚡️ Build success in 2585ms
22
+ DTS dist/client/index.d.ts 77.00 B
23
+ DTS dist/server/index.d.ts 1.99 KB
24
+ DTS dist/shared/index.d.ts 364.00 B
25
+ DTS dist/client/index.d.mts 77.00 B
26
+ DTS dist/server/index.d.mts 1.99 KB
27
+ DTS dist/shared/index.d.mts 364.00 B
package/CHANGELOG.md CHANGED
@@ -1,5 +1,22 @@
1
1
  # @wrelik/email
2
2
 
3
+ ## 2.0.0
4
+
5
+ ### Major Changes
6
+
7
+ - 818ed58: Refactor runtime packages to strict subpath exports (`/server`, `/client`, `/shared`) with side-effect free entrypoints and hard CI/runtime boundary enforcement.
8
+
9
+ ### Minor Changes
10
+
11
+ - Add platform wrapper packages for Clerk (Next/Expo), Next Sentry client instrumentation, and Upstash server SDK access so apps can avoid direct vendor imports while staying inside the `@wrelik/*` boundary.
12
+
13
+ Add temporary deprecated server-side compatibility singleton exports to analytics, email, jobs, and storage to support DRX migration from root-import convenience APIs to runtime subpaths in one cutover.
14
+
15
+ ### Patch Changes
16
+
17
+ - Updated dependencies [818ed58]
18
+ - @wrelik/errors@2.0.0
19
+
3
20
  ## 0.1.3
4
21
 
5
22
  ### Patch Changes
@@ -0,0 +1,3 @@
1
+ declare const serverOnlyClientStub: never;
2
+
3
+ export { serverOnlyClientStub };
@@ -0,0 +1,3 @@
1
+ declare const serverOnlyClientStub: never;
2
+
3
+ export { serverOnlyClientStub };
@@ -0,0 +1,34 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/client/index.ts
21
+ var client_exports = {};
22
+ __export(client_exports, {
23
+ serverOnlyClientStub: () => serverOnlyClientStub
24
+ });
25
+ module.exports = __toCommonJS(client_exports);
26
+ var serverOnlyClientStub = (() => {
27
+ throw new Error(
28
+ "@wrelik/email/client is a server-only adapter stub. Use @wrelik/email/server on the backend and call your backend API from client or Expo apps."
29
+ );
30
+ })();
31
+ // Annotate the CommonJS export names for ESM import in node:
32
+ 0 && (module.exports = {
33
+ serverOnlyClientStub
34
+ });
@@ -0,0 +1,9 @@
1
+ // src/client/index.ts
2
+ var serverOnlyClientStub = (() => {
3
+ throw new Error(
4
+ "@wrelik/email/client is a server-only adapter stub. Use @wrelik/email/server on the backend and call your backend API from client or Expo apps."
5
+ );
6
+ })();
7
+ export {
8
+ serverOnlyClientStub
9
+ };
@@ -0,0 +1,54 @@
1
+ import { SendEmailOptions, TemplateRenderer } from '../shared/index.mjs';
2
+
3
+ interface EmailTransport {
4
+ emails: {
5
+ send: (input: {
6
+ from: string;
7
+ to: string | string[];
8
+ subject: string;
9
+ html?: string;
10
+ text?: string;
11
+ tags?: Array<{
12
+ name: string;
13
+ value: string;
14
+ }>;
15
+ react: null;
16
+ }) => Promise<{
17
+ data: unknown;
18
+ error: {
19
+ message: string;
20
+ } | null;
21
+ }>;
22
+ };
23
+ }
24
+ declare function createEmailServer(config: {
25
+ apiKey: string;
26
+ defaultFromAddress: string;
27
+ }, options?: {
28
+ transport?: EmailTransport;
29
+ }): {
30
+ sendEmail: ({ to, subject, html, text, from, tags }: SendEmailOptions) => Promise<unknown>;
31
+ defineTemplate<T>(id: string, renderer: TemplateRenderer<T>): void;
32
+ sendTemplate<T>(id: string, to: string | string[], data: T, options?: Partial<SendEmailOptions>): Promise<unknown>;
33
+ };
34
+ /**
35
+ * @deprecated Temporary DRX compatibility singleton. Prefer createEmailServer(...) and explicit dependency wiring.
36
+ */
37
+ declare function initEmail(config: {
38
+ apiKey: string;
39
+ defaultFromAddress: string;
40
+ }, options?: Parameters<typeof createEmailServer>[1]): void;
41
+ /**
42
+ * @deprecated Temporary DRX compatibility singleton. Prefer createEmailServer(...).defineTemplate(...)
43
+ */
44
+ declare function defineTemplate<T>(id: string, renderer: TemplateRenderer<T>): void;
45
+ /**
46
+ * @deprecated Temporary DRX compatibility singleton. Prefer createEmailServer(...).sendEmail(...)
47
+ */
48
+ declare function sendEmail(input: SendEmailOptions): Promise<unknown>;
49
+ /**
50
+ * @deprecated Temporary DRX compatibility singleton. Prefer createEmailServer(...).sendTemplate(...)
51
+ */
52
+ declare function sendTemplate<T>(id: string, to: string | string[], data: T, options?: Partial<SendEmailOptions>): Promise<unknown>;
53
+
54
+ export { type EmailTransport, SendEmailOptions, TemplateRenderer, createEmailServer, defineTemplate, initEmail, sendEmail, sendTemplate };
@@ -0,0 +1,54 @@
1
+ import { SendEmailOptions, TemplateRenderer } from '../shared/index.js';
2
+
3
+ interface EmailTransport {
4
+ emails: {
5
+ send: (input: {
6
+ from: string;
7
+ to: string | string[];
8
+ subject: string;
9
+ html?: string;
10
+ text?: string;
11
+ tags?: Array<{
12
+ name: string;
13
+ value: string;
14
+ }>;
15
+ react: null;
16
+ }) => Promise<{
17
+ data: unknown;
18
+ error: {
19
+ message: string;
20
+ } | null;
21
+ }>;
22
+ };
23
+ }
24
+ declare function createEmailServer(config: {
25
+ apiKey: string;
26
+ defaultFromAddress: string;
27
+ }, options?: {
28
+ transport?: EmailTransport;
29
+ }): {
30
+ sendEmail: ({ to, subject, html, text, from, tags }: SendEmailOptions) => Promise<unknown>;
31
+ defineTemplate<T>(id: string, renderer: TemplateRenderer<T>): void;
32
+ sendTemplate<T>(id: string, to: string | string[], data: T, options?: Partial<SendEmailOptions>): Promise<unknown>;
33
+ };
34
+ /**
35
+ * @deprecated Temporary DRX compatibility singleton. Prefer createEmailServer(...) and explicit dependency wiring.
36
+ */
37
+ declare function initEmail(config: {
38
+ apiKey: string;
39
+ defaultFromAddress: string;
40
+ }, options?: Parameters<typeof createEmailServer>[1]): void;
41
+ /**
42
+ * @deprecated Temporary DRX compatibility singleton. Prefer createEmailServer(...).defineTemplate(...)
43
+ */
44
+ declare function defineTemplate<T>(id: string, renderer: TemplateRenderer<T>): void;
45
+ /**
46
+ * @deprecated Temporary DRX compatibility singleton. Prefer createEmailServer(...).sendEmail(...)
47
+ */
48
+ declare function sendEmail(input: SendEmailOptions): Promise<unknown>;
49
+ /**
50
+ * @deprecated Temporary DRX compatibility singleton. Prefer createEmailServer(...).sendTemplate(...)
51
+ */
52
+ declare function sendTemplate<T>(id: string, to: string | string[], data: T, options?: Partial<SendEmailOptions>): Promise<unknown>;
53
+
54
+ export { type EmailTransport, SendEmailOptions, TemplateRenderer, createEmailServer, defineTemplate, initEmail, sendEmail, sendTemplate };
@@ -0,0 +1,99 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/server/index.ts
21
+ var server_exports = {};
22
+ __export(server_exports, {
23
+ createEmailServer: () => createEmailServer,
24
+ defineTemplate: () => defineTemplate,
25
+ initEmail: () => initEmail,
26
+ sendEmail: () => sendEmail,
27
+ sendTemplate: () => sendTemplate
28
+ });
29
+ module.exports = __toCommonJS(server_exports);
30
+ var import_resend = require("resend");
31
+ var import_shared = require("@wrelik/errors/shared");
32
+ function createEmailServer(config, options) {
33
+ const client = options?.transport ?? new import_resend.Resend(config.apiKey);
34
+ const templates = /* @__PURE__ */ new Map();
35
+ const sendEmail2 = async ({ to, subject, html, text, from, tags }) => {
36
+ const result = await client.emails.send({
37
+ from: from ?? config.defaultFromAddress,
38
+ to,
39
+ subject,
40
+ html,
41
+ text,
42
+ tags,
43
+ react: null
44
+ });
45
+ if (result.error) {
46
+ throw new Error(`Failed to send email: ${result.error.message}`);
47
+ }
48
+ return result.data;
49
+ };
50
+ return {
51
+ sendEmail: sendEmail2,
52
+ defineTemplate(id, renderer) {
53
+ templates.set(id, renderer);
54
+ },
55
+ async sendTemplate(id, to, data, options2) {
56
+ const renderer = templates.get(id);
57
+ if (!renderer) {
58
+ throw new import_shared.ValidationError(`Template ${id} not found`);
59
+ }
60
+ const { subject, html, text } = renderer(data);
61
+ return sendEmail2({
62
+ to,
63
+ subject,
64
+ html,
65
+ text,
66
+ ...options2
67
+ });
68
+ }
69
+ };
70
+ }
71
+ var emailSingleton = null;
72
+ function requireEmailSingleton() {
73
+ if (!emailSingleton) {
74
+ throw new Error(
75
+ "@wrelik/email/server compatibility API is not initialized. Call initEmail({ apiKey, defaultFromAddress }) before sendEmail/sendTemplate."
76
+ );
77
+ }
78
+ return emailSingleton;
79
+ }
80
+ function initEmail(config, options) {
81
+ emailSingleton = createEmailServer(config, options);
82
+ }
83
+ function defineTemplate(id, renderer) {
84
+ requireEmailSingleton().defineTemplate(id, renderer);
85
+ }
86
+ function sendEmail(input) {
87
+ return requireEmailSingleton().sendEmail(input);
88
+ }
89
+ function sendTemplate(id, to, data, options) {
90
+ return requireEmailSingleton().sendTemplate(id, to, data, options);
91
+ }
92
+ // Annotate the CommonJS export names for ESM import in node:
93
+ 0 && (module.exports = {
94
+ createEmailServer,
95
+ defineTemplate,
96
+ initEmail,
97
+ sendEmail,
98
+ sendTemplate
99
+ });
@@ -0,0 +1,70 @@
1
+ // src/server/index.ts
2
+ import { Resend } from "resend";
3
+ import { ValidationError } from "@wrelik/errors/shared";
4
+ function createEmailServer(config, options) {
5
+ const client = options?.transport ?? new Resend(config.apiKey);
6
+ const templates = /* @__PURE__ */ new Map();
7
+ const sendEmail2 = async ({ to, subject, html, text, from, tags }) => {
8
+ const result = await client.emails.send({
9
+ from: from ?? config.defaultFromAddress,
10
+ to,
11
+ subject,
12
+ html,
13
+ text,
14
+ tags,
15
+ react: null
16
+ });
17
+ if (result.error) {
18
+ throw new Error(`Failed to send email: ${result.error.message}`);
19
+ }
20
+ return result.data;
21
+ };
22
+ return {
23
+ sendEmail: sendEmail2,
24
+ defineTemplate(id, renderer) {
25
+ templates.set(id, renderer);
26
+ },
27
+ async sendTemplate(id, to, data, options2) {
28
+ const renderer = templates.get(id);
29
+ if (!renderer) {
30
+ throw new ValidationError(`Template ${id} not found`);
31
+ }
32
+ const { subject, html, text } = renderer(data);
33
+ return sendEmail2({
34
+ to,
35
+ subject,
36
+ html,
37
+ text,
38
+ ...options2
39
+ });
40
+ }
41
+ };
42
+ }
43
+ var emailSingleton = null;
44
+ function requireEmailSingleton() {
45
+ if (!emailSingleton) {
46
+ throw new Error(
47
+ "@wrelik/email/server compatibility API is not initialized. Call initEmail({ apiKey, defaultFromAddress }) before sendEmail/sendTemplate."
48
+ );
49
+ }
50
+ return emailSingleton;
51
+ }
52
+ function initEmail(config, options) {
53
+ emailSingleton = createEmailServer(config, options);
54
+ }
55
+ function defineTemplate(id, renderer) {
56
+ requireEmailSingleton().defineTemplate(id, renderer);
57
+ }
58
+ function sendEmail(input) {
59
+ return requireEmailSingleton().sendEmail(input);
60
+ }
61
+ function sendTemplate(id, to, data, options) {
62
+ return requireEmailSingleton().sendTemplate(id, to, data, options);
63
+ }
64
+ export {
65
+ createEmailServer,
66
+ defineTemplate,
67
+ initEmail,
68
+ sendEmail,
69
+ sendTemplate
70
+ };
@@ -0,0 +1,18 @@
1
+ interface SendEmailOptions {
2
+ to: string | string[];
3
+ subject: string;
4
+ html?: string;
5
+ text?: string;
6
+ from?: string;
7
+ tags?: Array<{
8
+ name: string;
9
+ value: string;
10
+ }>;
11
+ }
12
+ type TemplateRenderer<T> = (data: T) => {
13
+ subject: string;
14
+ html: string;
15
+ text?: string;
16
+ };
17
+
18
+ export type { SendEmailOptions, TemplateRenderer };
@@ -0,0 +1,18 @@
1
+ interface SendEmailOptions {
2
+ to: string | string[];
3
+ subject: string;
4
+ html?: string;
5
+ text?: string;
6
+ from?: string;
7
+ tags?: Array<{
8
+ name: string;
9
+ value: string;
10
+ }>;
11
+ }
12
+ type TemplateRenderer<T> = (data: T) => {
13
+ subject: string;
14
+ html: string;
15
+ text?: string;
16
+ };
17
+
18
+ export type { SendEmailOptions, TemplateRenderer };
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __copyProps = (to, from, except, desc) => {
7
+ if (from && typeof from === "object" || typeof from === "function") {
8
+ for (let key of __getOwnPropNames(from))
9
+ if (!__hasOwnProp.call(to, key) && key !== except)
10
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
11
+ }
12
+ return to;
13
+ };
14
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
15
+
16
+ // src/shared/index.ts
17
+ var shared_exports = {};
18
+ module.exports = __toCommonJS(shared_exports);
File without changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wrelik/email",
3
- "version": "0.1.3",
3
+ "version": "2.0.0",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -9,12 +9,28 @@
9
9
  "url": "https://github.com/lwhite702/wrelik-kit.git",
10
10
  "directory": "packages/email"
11
11
  },
12
- "main": "./dist/index.js",
13
- "types": "./dist/index.d.ts",
14
- "react-native": "./dist/unsupported.js",
12
+ "sideEffects": false,
13
+ "exports": {
14
+ "./server": {
15
+ "types": "./dist/server/index.d.ts",
16
+ "import": "./dist/server/index.mjs",
17
+ "require": "./dist/server/index.js"
18
+ },
19
+ "./client": {
20
+ "types": "./dist/client/index.d.ts",
21
+ "import": "./dist/client/index.mjs",
22
+ "require": "./dist/client/index.js"
23
+ },
24
+ "./shared": {
25
+ "types": "./dist/shared/index.d.ts",
26
+ "import": "./dist/shared/index.mjs",
27
+ "require": "./dist/shared/index.js"
28
+ },
29
+ "./package.json": "./package.json"
30
+ },
15
31
  "dependencies": {
16
32
  "resend": "^3.2.0",
17
- "@wrelik/errors": "0.2.1"
33
+ "@wrelik/errors": "2.0.0"
18
34
  },
19
35
  "devDependencies": {
20
36
  "tsup": "^8.0.1",
@@ -22,9 +38,17 @@
22
38
  "@wrelik/eslint-config": "0.1.1",
23
39
  "@wrelik/tsconfig": "0.1.1"
24
40
  },
41
+ "wrelik": {
42
+ "runtimes": [
43
+ "server",
44
+ "client",
45
+ "shared"
46
+ ]
47
+ },
25
48
  "scripts": {
26
- "build": "tsup src/index.ts src/unsupported.ts --format cjs,esm --dts --clean",
49
+ "build": "tsup src/server/index.ts src/client/index.ts src/shared/index.ts --format cjs,esm --dts --clean",
27
50
  "lint": "eslint src/",
51
+ "typecheck": "tsc --noEmit",
28
52
  "test": "vitest run --passWithNoTests"
29
53
  }
30
54
  }
@@ -0,0 +1,7 @@
1
+ import { describe, expect, it } from 'vitest';
2
+
3
+ describe('@wrelik/email/client', () => {
4
+ it('fails fast with a server-only adapter message', async () => {
5
+ await expect(import('./index.js')).rejects.toThrow(/server-only adapter stub/i);
6
+ });
7
+ });
@@ -0,0 +1,5 @@
1
+ export const serverOnlyClientStub = (() => {
2
+ throw new Error(
3
+ '@wrelik/email/client is a server-only adapter stub. Use @wrelik/email/server on the backend and call your backend API from client or Expo apps.',
4
+ );
5
+ })();
@@ -0,0 +1,116 @@
1
+ import { afterEach, describe, expect, it, vi } from 'vitest';
2
+ import { ValidationError } from '@wrelik/errors/shared';
3
+ import { createEmailServer } from './index';
4
+
5
+ afterEach(() => {
6
+ vi.resetModules();
7
+ vi.clearAllMocks();
8
+ });
9
+
10
+ describe('@wrelik/email/server', () => {
11
+ it('registers templates and applies default sender in sendTemplate', async () => {
12
+ const send = vi.fn(async () => ({ data: { id: 'msg_1' }, error: null }));
13
+ const email = createEmailServer(
14
+ { apiKey: 'rk_test', defaultFromAddress: 'hello@wrelik.com' },
15
+ { transport: { emails: { send } } },
16
+ );
17
+
18
+ email.defineTemplate('welcome', (data: { name: string }) => ({
19
+ subject: `Welcome ${data.name}`,
20
+ html: `<p>${data.name}</p>`,
21
+ text: data.name,
22
+ }));
23
+
24
+ const result = await email.sendTemplate('welcome', 'lee@example.com', { name: 'Lee' });
25
+
26
+ expect(result).toEqual({ id: 'msg_1' });
27
+ expect(send).toHaveBeenCalledWith({
28
+ from: 'hello@wrelik.com',
29
+ to: 'lee@example.com',
30
+ subject: 'Welcome Lee',
31
+ html: '<p>Lee</p>',
32
+ text: 'Lee',
33
+ tags: undefined,
34
+ react: null,
35
+ });
36
+ });
37
+
38
+ it('throws a validation error when template is missing', async () => {
39
+ const email = createEmailServer(
40
+ { apiKey: 'rk_test', defaultFromAddress: 'hello@wrelik.com' },
41
+ { transport: { emails: { send: vi.fn() } } },
42
+ );
43
+
44
+ await expect(email.sendTemplate('missing', 'a@b.com', {})).rejects.toThrow(ValidationError);
45
+ });
46
+
47
+ it('surfaces provider errors from sendEmail', async () => {
48
+ const email = createEmailServer(
49
+ { apiKey: 'rk_test', defaultFromAddress: 'hello@wrelik.com' },
50
+ {
51
+ transport: {
52
+ emails: {
53
+ send: vi.fn(async () => ({ data: null, error: { message: 'provider down' } })),
54
+ },
55
+ },
56
+ },
57
+ );
58
+
59
+ await expect(
60
+ email.sendEmail({
61
+ to: 'a@b.com',
62
+ subject: 'Hello',
63
+ html: '<p>hi</p>',
64
+ }),
65
+ ).rejects.toThrow(/provider down/);
66
+ });
67
+
68
+ it('compatibility singleton throws before initEmail', async () => {
69
+ const mod = await import('./index.js');
70
+ expect(() =>
71
+ mod.sendEmail({
72
+ to: 'a@b.com',
73
+ subject: 'Hello',
74
+ html: '<p>hi</p>',
75
+ }),
76
+ ).toThrow(/initEmail/i);
77
+ });
78
+
79
+ it('compatibility singleton delegates after initEmail', async () => {
80
+ const send = vi.fn(async () => ({ data: { id: 'msg_2' }, error: null }));
81
+ const mod = await import('./index.js');
82
+
83
+ mod.initEmail(
84
+ { apiKey: 'rk_test', defaultFromAddress: 'hello@wrelik.com' },
85
+ { transport: { emails: { send } } },
86
+ );
87
+ mod.defineTemplate('receipt', (data: { total: number }) => ({
88
+ subject: `Receipt ${data.total}`,
89
+ html: `<p>${data.total}</p>`,
90
+ text: String(data.total),
91
+ }));
92
+
93
+ await mod.sendTemplate('receipt', 'lee@example.com', { total: 42 });
94
+ await mod.sendEmail({ to: 'lee@example.com', subject: 'Ping', text: 'pong' });
95
+
96
+ expect(send).toHaveBeenCalledTimes(2);
97
+ expect(send).toHaveBeenNthCalledWith(1, {
98
+ from: 'hello@wrelik.com',
99
+ to: 'lee@example.com',
100
+ subject: 'Receipt 42',
101
+ html: '<p>42</p>',
102
+ text: '42',
103
+ tags: undefined,
104
+ react: null,
105
+ });
106
+ expect(send).toHaveBeenNthCalledWith(2, {
107
+ from: 'hello@wrelik.com',
108
+ to: 'lee@example.com',
109
+ subject: 'Ping',
110
+ html: undefined,
111
+ text: 'pong',
112
+ tags: undefined,
113
+ react: null,
114
+ });
115
+ });
116
+ });
@@ -0,0 +1,118 @@
1
+ /* eslint-disable no-restricted-imports */
2
+ import { Resend } from 'resend';
3
+ import { ValidationError } from '@wrelik/errors/shared';
4
+ import type { SendEmailOptions, TemplateRenderer } from '../shared';
5
+ export type { SendEmailOptions, TemplateRenderer } from '../shared';
6
+
7
+ export interface EmailTransport {
8
+ emails: {
9
+ send: (input: {
10
+ from: string;
11
+ to: string | string[];
12
+ subject: string;
13
+ html?: string;
14
+ text?: string;
15
+ tags?: Array<{ name: string; value: string }>;
16
+ react: null;
17
+ }) => Promise<{ data: unknown; error: { message: string } | null }>;
18
+ };
19
+ }
20
+
21
+ export function createEmailServer(
22
+ config: { apiKey: string; defaultFromAddress: string },
23
+ options?: { transport?: EmailTransport },
24
+ ) {
25
+ const client: EmailTransport = options?.transport ?? new Resend(config.apiKey);
26
+ const templates = new Map<string, TemplateRenderer<unknown>>();
27
+ const sendEmail = async ({ to, subject, html, text, from, tags }: SendEmailOptions) => {
28
+ const result = await client.emails.send({
29
+ from: from ?? config.defaultFromAddress,
30
+ to,
31
+ subject,
32
+ html,
33
+ text,
34
+ tags,
35
+ react: null,
36
+ });
37
+
38
+ if (result.error) {
39
+ throw new Error(`Failed to send email: ${result.error.message}`);
40
+ }
41
+
42
+ return result.data;
43
+ };
44
+
45
+ return {
46
+ sendEmail,
47
+
48
+ defineTemplate<T>(id: string, renderer: TemplateRenderer<T>) {
49
+ templates.set(id, renderer as TemplateRenderer<unknown>);
50
+ },
51
+
52
+ async sendTemplate<T>(id: string, to: string | string[], data: T, options?: Partial<SendEmailOptions>) {
53
+ const renderer = templates.get(id);
54
+ if (!renderer) {
55
+ throw new ValidationError(`Template ${id} not found`);
56
+ }
57
+
58
+ const { subject, html, text } = renderer(data);
59
+ return sendEmail({
60
+ to,
61
+ subject,
62
+ html,
63
+ text,
64
+ ...options,
65
+ });
66
+ },
67
+ };
68
+ }
69
+
70
+ type EmailServerCompat = ReturnType<typeof createEmailServer>;
71
+
72
+ let emailSingleton: EmailServerCompat | null = null;
73
+
74
+ function requireEmailSingleton(): EmailServerCompat {
75
+ if (!emailSingleton) {
76
+ throw new Error(
77
+ '@wrelik/email/server compatibility API is not initialized. Call initEmail({ apiKey, defaultFromAddress }) before sendEmail/sendTemplate.',
78
+ );
79
+ }
80
+
81
+ return emailSingleton;
82
+ }
83
+
84
+ /**
85
+ * @deprecated Temporary DRX compatibility singleton. Prefer createEmailServer(...) and explicit dependency wiring.
86
+ */
87
+ export function initEmail(
88
+ config: { apiKey: string; defaultFromAddress: string },
89
+ options?: Parameters<typeof createEmailServer>[1],
90
+ ): void {
91
+ emailSingleton = createEmailServer(config, options);
92
+ }
93
+
94
+ /**
95
+ * @deprecated Temporary DRX compatibility singleton. Prefer createEmailServer(...).defineTemplate(...)
96
+ */
97
+ export function defineTemplate<T>(id: string, renderer: TemplateRenderer<T>): void {
98
+ requireEmailSingleton().defineTemplate(id, renderer);
99
+ }
100
+
101
+ /**
102
+ * @deprecated Temporary DRX compatibility singleton. Prefer createEmailServer(...).sendEmail(...)
103
+ */
104
+ export function sendEmail(input: SendEmailOptions): Promise<unknown> {
105
+ return requireEmailSingleton().sendEmail(input);
106
+ }
107
+
108
+ /**
109
+ * @deprecated Temporary DRX compatibility singleton. Prefer createEmailServer(...).sendTemplate(...)
110
+ */
111
+ export function sendTemplate<T>(
112
+ id: string,
113
+ to: string | string[],
114
+ data: T,
115
+ options?: Partial<SendEmailOptions>,
116
+ ): Promise<unknown> {
117
+ return requireEmailSingleton().sendTemplate(id, to, data, options);
118
+ }
@@ -0,0 +1 @@
1
+ export type { SendEmailOptions, TemplateRenderer } from './types';
@@ -0,0 +1,10 @@
1
+ export interface SendEmailOptions {
2
+ to: string | string[];
3
+ subject: string;
4
+ html?: string;
5
+ text?: string;
6
+ from?: string;
7
+ tags?: Array<{ name: string; value: string }>;
8
+ }
9
+
10
+ export type TemplateRenderer<T> = (data: T) => { subject: string; html: string; text?: string };
package/dist/index.d.mts DELETED
@@ -1,24 +0,0 @@
1
- import * as resend from 'resend';
2
-
3
- declare function initEmail(apiKey: string, defaultFromAddress: string): void;
4
- interface SendEmailOptions {
5
- to: string | string[];
6
- subject: string;
7
- html?: string;
8
- text?: string;
9
- from?: string;
10
- tags?: Array<{
11
- name: string;
12
- value: string;
13
- }>;
14
- }
15
- declare function sendEmail({ to, subject, html, text, from, tags }: SendEmailOptions): Promise<resend.CreateEmailResponseSuccess | null>;
16
- type TemplateRenderer<T> = (data: T) => {
17
- subject: string;
18
- html: string;
19
- text?: string;
20
- };
21
- declare function defineTemplate<T>(id: string, renderer: TemplateRenderer<T>): void;
22
- declare function sendTemplate<T>(id: string, to: string | string[], data: T, options?: Partial<SendEmailOptions>): Promise<resend.CreateEmailResponseSuccess | null>;
23
-
24
- export { type SendEmailOptions, defineTemplate, initEmail, sendEmail, sendTemplate };
package/dist/index.d.ts DELETED
@@ -1,24 +0,0 @@
1
- import * as resend from 'resend';
2
-
3
- declare function initEmail(apiKey: string, defaultFromAddress: string): void;
4
- interface SendEmailOptions {
5
- to: string | string[];
6
- subject: string;
7
- html?: string;
8
- text?: string;
9
- from?: string;
10
- tags?: Array<{
11
- name: string;
12
- value: string;
13
- }>;
14
- }
15
- declare function sendEmail({ to, subject, html, text, from, tags }: SendEmailOptions): Promise<resend.CreateEmailResponseSuccess | null>;
16
- type TemplateRenderer<T> = (data: T) => {
17
- subject: string;
18
- html: string;
19
- text?: string;
20
- };
21
- declare function defineTemplate<T>(id: string, renderer: TemplateRenderer<T>): void;
22
- declare function sendTemplate<T>(id: string, to: string | string[], data: T, options?: Partial<SendEmailOptions>): Promise<resend.CreateEmailResponseSuccess | null>;
23
-
24
- export { type SendEmailOptions, defineTemplate, initEmail, sendEmail, sendTemplate };
package/dist/index.js DELETED
@@ -1,80 +0,0 @@
1
- "use strict";
2
- var __defProp = Object.defineProperty;
3
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
- var __getOwnPropNames = Object.getOwnPropertyNames;
5
- var __hasOwnProp = Object.prototype.hasOwnProperty;
6
- var __export = (target, all) => {
7
- for (var name in all)
8
- __defProp(target, name, { get: all[name], enumerable: true });
9
- };
10
- var __copyProps = (to, from, except, desc) => {
11
- if (from && typeof from === "object" || typeof from === "function") {
12
- for (let key of __getOwnPropNames(from))
13
- if (!__hasOwnProp.call(to, key) && key !== except)
14
- __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
- }
16
- return to;
17
- };
18
- var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
-
20
- // src/index.ts
21
- var index_exports = {};
22
- __export(index_exports, {
23
- defineTemplate: () => defineTemplate,
24
- initEmail: () => initEmail,
25
- sendEmail: () => sendEmail,
26
- sendTemplate: () => sendTemplate
27
- });
28
- module.exports = __toCommonJS(index_exports);
29
- var import_resend = require("resend");
30
- var import_errors = require("@wrelik/errors");
31
- var client;
32
- var defaultFrom;
33
- function initEmail(apiKey, defaultFromAddress) {
34
- client = new import_resend.Resend(apiKey);
35
- defaultFrom = defaultFromAddress;
36
- }
37
- function getClient() {
38
- if (!client) throw new Error("Email not initialized");
39
- return client;
40
- }
41
- async function sendEmail({ to, subject, html, text, from, tags }) {
42
- const result = await getClient().emails.send({
43
- from: from || defaultFrom,
44
- to,
45
- subject,
46
- html,
47
- text,
48
- tags,
49
- react: null
50
- });
51
- if (result.error) {
52
- throw new Error(`Failed to send email: ${result.error.message}`);
53
- }
54
- return result.data;
55
- }
56
- var templates = /* @__PURE__ */ new Map();
57
- function defineTemplate(id, renderer) {
58
- templates.set(id, renderer);
59
- }
60
- async function sendTemplate(id, to, data, options) {
61
- const renderer = templates.get(id);
62
- if (!renderer) {
63
- throw new import_errors.ValidationError(`Template ${id} not found`);
64
- }
65
- const { subject, html, text } = renderer(data);
66
- return sendEmail({
67
- to,
68
- subject,
69
- html,
70
- text,
71
- ...options
72
- });
73
- }
74
- // Annotate the CommonJS export names for ESM import in node:
75
- 0 && (module.exports = {
76
- defineTemplate,
77
- initEmail,
78
- sendEmail,
79
- sendTemplate
80
- });
package/dist/index.mjs DELETED
@@ -1,52 +0,0 @@
1
- // src/index.ts
2
- import { Resend } from "resend";
3
- import { ValidationError } from "@wrelik/errors";
4
- var client;
5
- var defaultFrom;
6
- function initEmail(apiKey, defaultFromAddress) {
7
- client = new Resend(apiKey);
8
- defaultFrom = defaultFromAddress;
9
- }
10
- function getClient() {
11
- if (!client) throw new Error("Email not initialized");
12
- return client;
13
- }
14
- async function sendEmail({ to, subject, html, text, from, tags }) {
15
- const result = await getClient().emails.send({
16
- from: from || defaultFrom,
17
- to,
18
- subject,
19
- html,
20
- text,
21
- tags,
22
- react: null
23
- });
24
- if (result.error) {
25
- throw new Error(`Failed to send email: ${result.error.message}`);
26
- }
27
- return result.data;
28
- }
29
- var templates = /* @__PURE__ */ new Map();
30
- function defineTemplate(id, renderer) {
31
- templates.set(id, renderer);
32
- }
33
- async function sendTemplate(id, to, data, options) {
34
- const renderer = templates.get(id);
35
- if (!renderer) {
36
- throw new ValidationError(`Template ${id} not found`);
37
- }
38
- const { subject, html, text } = renderer(data);
39
- return sendEmail({
40
- to,
41
- subject,
42
- html,
43
- text,
44
- ...options
45
- });
46
- }
47
- export {
48
- defineTemplate,
49
- initEmail,
50
- sendEmail,
51
- sendTemplate
52
- };
@@ -1,2 +0,0 @@
1
-
2
- export { }
@@ -1,2 +0,0 @@
1
-
2
- export { }
@@ -1,6 +0,0 @@
1
- "use strict";
2
-
3
- // src/unsupported.ts
4
- throw new Error(
5
- "This package is server-only and cannot be used in client/mobile environments. Access this functionality via a backend API instead."
6
- );
@@ -1,4 +0,0 @@
1
- // src/unsupported.ts
2
- throw new Error(
3
- "This package is server-only and cannot be used in client/mobile environments. Access this functionality via a backend API instead."
4
- );
package/src/index.ts DELETED
@@ -1,73 +0,0 @@
1
- // eslint-disable-next-line no-restricted-imports
2
- import { Resend } from 'resend';
3
- import { ValidationError } from '@wrelik/errors';
4
-
5
- let client: Resend;
6
- let defaultFrom: string;
7
-
8
- export function initEmail(apiKey: string, defaultFromAddress: string) {
9
- client = new Resend(apiKey);
10
- defaultFrom = defaultFromAddress;
11
- }
12
-
13
- function getClient() {
14
- if (!client) throw new Error('Email not initialized');
15
- return client;
16
- }
17
-
18
- export interface SendEmailOptions {
19
- to: string | string[];
20
- subject: string;
21
- html?: string;
22
- text?: string;
23
- from?: string;
24
- tags?: Array<{ name: string; value: string }>;
25
- }
26
-
27
- export async function sendEmail({ to, subject, html, text, from, tags }: SendEmailOptions) {
28
- const result = await getClient().emails.send({
29
- from: from || defaultFrom,
30
- to,
31
- subject,
32
- html,
33
- text,
34
- tags,
35
- react: null,
36
- });
37
-
38
- if (result.error) {
39
- throw new Error(`Failed to send email: ${result.error.message}`);
40
- }
41
-
42
- return result.data;
43
- }
44
-
45
- // Template Registry
46
- type TemplateRenderer<T> = (data: T) => { subject: string; html: string; text?: string };
47
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
48
- const templates = new Map<string, TemplateRenderer<any>>();
49
-
50
- export function defineTemplate<T>(id: string, renderer: TemplateRenderer<T>) {
51
- templates.set(id, renderer);
52
- }
53
-
54
- export async function sendTemplate<T>(
55
- id: string,
56
- to: string | string[],
57
- data: T,
58
- options?: Partial<SendEmailOptions>,
59
- ) {
60
- const renderer = templates.get(id);
61
- if (!renderer) {
62
- throw new ValidationError(`Template ${id} not found`);
63
- }
64
-
65
- const { subject, html, text } = renderer(data);
66
- return sendEmail({
67
- to,
68
- subject,
69
- html,
70
- text,
71
- ...options,
72
- });
73
- }
@@ -1,4 +0,0 @@
1
- throw new Error(
2
- 'This package is server-only and cannot be used in client/mobile environments. ' +
3
- 'Access this functionality via a backend API instead.',
4
- );