@stackframe/stack-shared 2.8.27 → 2.8.30

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 (86) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/dist/config/schema.d.mts +391 -15
  3. package/dist/config/schema.d.ts +391 -15
  4. package/dist/config/schema.js +70 -2
  5. package/dist/config/schema.js.map +1 -1
  6. package/dist/esm/config/schema.js +70 -3
  7. package/dist/esm/config/schema.js.map +1 -1
  8. package/dist/esm/helpers/emails.js +31 -7
  9. package/dist/esm/helpers/emails.js.map +1 -1
  10. package/dist/esm/interface/admin-interface.js +65 -9
  11. package/dist/esm/interface/admin-interface.js.map +1 -1
  12. package/dist/esm/interface/crud/config.js +40 -0
  13. package/dist/esm/interface/crud/config.js.map +1 -0
  14. package/dist/esm/interface/crud/projects.js +4 -0
  15. package/dist/esm/interface/crud/projects.js.map +1 -1
  16. package/dist/esm/interface/server-interface.js +26 -0
  17. package/dist/esm/interface/server-interface.js.map +1 -1
  18. package/dist/esm/known-errors.js +73 -1
  19. package/dist/esm/known-errors.js.map +1 -1
  20. package/dist/esm/schema-fields.js +67 -4
  21. package/dist/esm/schema-fields.js.map +1 -1
  22. package/dist/esm/utils/errors.js.map +1 -1
  23. package/dist/esm/utils/esbuild.js +15 -11
  24. package/dist/esm/utils/esbuild.js.map +1 -1
  25. package/dist/esm/utils/featurebase.js +176 -0
  26. package/dist/esm/utils/featurebase.js.map +1 -0
  27. package/dist/esm/utils/html.js +6 -1
  28. package/dist/esm/utils/html.js.map +1 -1
  29. package/dist/esm/utils/promises.js +24 -13
  30. package/dist/esm/utils/promises.js.map +1 -1
  31. package/dist/esm/utils/telemetry.js +39 -0
  32. package/dist/esm/utils/telemetry.js.map +1 -0
  33. package/dist/helpers/emails.d.mts +1 -1
  34. package/dist/helpers/emails.d.ts +1 -1
  35. package/dist/helpers/emails.js +31 -7
  36. package/dist/helpers/emails.js.map +1 -1
  37. package/dist/index.d.mts +2 -1
  38. package/dist/index.d.ts +2 -1
  39. package/dist/interface/admin-interface.d.mts +15 -6
  40. package/dist/interface/admin-interface.d.ts +15 -6
  41. package/dist/interface/admin-interface.js +65 -9
  42. package/dist/interface/admin-interface.js.map +1 -1
  43. package/dist/interface/crud/config.d.mts +49 -0
  44. package/dist/interface/crud/config.d.ts +49 -0
  45. package/dist/interface/crud/config.js +79 -0
  46. package/dist/interface/crud/config.js.map +1 -0
  47. package/dist/interface/crud/project-api-keys.d.mts +4 -4
  48. package/dist/interface/crud/project-api-keys.d.ts +4 -4
  49. package/dist/interface/crud/projects.d.mts +35 -7
  50. package/dist/interface/crud/projects.d.ts +35 -7
  51. package/dist/interface/crud/projects.js +4 -0
  52. package/dist/interface/crud/projects.js.map +1 -1
  53. package/dist/interface/server-interface.d.mts +9 -0
  54. package/dist/interface/server-interface.d.ts +9 -0
  55. package/dist/interface/server-interface.js +26 -0
  56. package/dist/interface/server-interface.js.map +1 -1
  57. package/dist/known-errors.d.mts +15 -0
  58. package/dist/known-errors.d.ts +15 -0
  59. package/dist/known-errors.js +73 -1
  60. package/dist/known-errors.js.map +1 -1
  61. package/dist/schema-fields.d.mts +88 -2
  62. package/dist/schema-fields.d.ts +88 -2
  63. package/dist/schema-fields.js +71 -3
  64. package/dist/schema-fields.js.map +1 -1
  65. package/dist/utils/errors.d.mts +3 -1
  66. package/dist/utils/errors.d.ts +3 -1
  67. package/dist/utils/errors.js.map +1 -1
  68. package/dist/utils/esbuild.js +15 -11
  69. package/dist/utils/esbuild.js.map +1 -1
  70. package/dist/utils/featurebase.d.mts +28 -0
  71. package/dist/utils/featurebase.d.ts +28 -0
  72. package/dist/utils/featurebase.js +201 -0
  73. package/dist/utils/featurebase.js.map +1 -0
  74. package/dist/utils/html.d.mts +2 -1
  75. package/dist/utils/html.d.ts +2 -1
  76. package/dist/utils/html.js +8 -2
  77. package/dist/utils/html.js.map +1 -1
  78. package/dist/utils/promises.d.mts +5 -1
  79. package/dist/utils/promises.d.ts +5 -1
  80. package/dist/utils/promises.js +24 -12
  81. package/dist/utils/promises.js.map +1 -1
  82. package/dist/utils/telemetry.d.mts +13 -0
  83. package/dist/utils/telemetry.d.ts +13 -0
  84. package/dist/utils/telemetry.js +66 -0
  85. package/dist/utils/telemetry.js.map +1 -0
  86. package/package.json +2 -1
@@ -0,0 +1,176 @@
1
+ // src/utils/featurebase.tsx
2
+ import { getEnvVariable } from "./env.js";
3
+ import { StackAssertionError } from "./errors.js";
4
+ async function findFeaturebaseUserById(stackAuthUserId, apiKey) {
5
+ try {
6
+ const response = await fetch(`https://do.featurebase.app/v2/organization/identifyUser?id=${stackAuthUserId}`, {
7
+ method: "GET",
8
+ headers: {
9
+ "X-API-Key": apiKey
10
+ }
11
+ });
12
+ if (response.status === 404) {
13
+ return null;
14
+ }
15
+ if (!response.ok) {
16
+ throw new StackAssertionError(`Failed to find Featurebase user by ID: ${response.statusText}`);
17
+ }
18
+ const data = await response.json();
19
+ const user = data.user;
20
+ if (!user) {
21
+ throw new StackAssertionError(`Featurebase API returned success but no user data for ID: ${stackAuthUserId}`, { data });
22
+ }
23
+ return {
24
+ userId: user.externalUserId || user.userId || stackAuthUserId,
25
+ email: user.email,
26
+ name: user.name,
27
+ profilePicture: user.profilePicture
28
+ };
29
+ } catch (error) {
30
+ if (error instanceof StackAssertionError) {
31
+ throw error;
32
+ }
33
+ throw new StackAssertionError("Failed to find Featurebase user by ID", { cause: error });
34
+ }
35
+ }
36
+ async function findFeaturebaseUserByEmail(email, apiKey) {
37
+ try {
38
+ const response = await fetch(`https://do.featurebase.app/v2/organization/identifyUser?email=${encodeURIComponent(email)}`, {
39
+ method: "GET",
40
+ headers: {
41
+ "X-API-Key": apiKey
42
+ }
43
+ });
44
+ if (response.status === 404) {
45
+ return null;
46
+ }
47
+ if (!response.ok) {
48
+ throw new StackAssertionError(`Failed to find Featurebase user by email: ${response.statusText}`);
49
+ }
50
+ const data = await response.json();
51
+ const user = data.user;
52
+ if (!user) {
53
+ throw new StackAssertionError(`Featurebase API returned success but no user data for email: ${email}`, { data });
54
+ }
55
+ return {
56
+ userId: user.externalUserId || user.userId,
57
+ email: user.email,
58
+ name: user.name,
59
+ profilePicture: user.profilePicture
60
+ };
61
+ } catch (error) {
62
+ console.error("Error finding Featurebase user by email:", error);
63
+ return null;
64
+ }
65
+ }
66
+ async function createFeaturebaseUser(user, apiKey) {
67
+ try {
68
+ const response = await fetch("https://do.featurebase.app/v2/organization/identifyUser", {
69
+ method: "POST",
70
+ headers: {
71
+ "Content-Type": "application/json",
72
+ "X-API-Key": apiKey
73
+ },
74
+ body: JSON.stringify({
75
+ userId: user.userId,
76
+ email: user.email,
77
+ name: user.name,
78
+ profilePicture: user.profilePicture
79
+ })
80
+ });
81
+ if (!response.ok) {
82
+ const errorData = await response.json().catch(() => ({}));
83
+ throw new StackAssertionError(`Failed to create Featurebase user: ${errorData.error || response.statusText}`, { errorData });
84
+ }
85
+ return user;
86
+ } catch (error) {
87
+ if (error instanceof StackAssertionError) {
88
+ throw error;
89
+ }
90
+ throw new StackAssertionError("Failed to create Featurebase user", { cause: error });
91
+ }
92
+ }
93
+ async function updateFeaturebaseUser(userId, updates, apiKey) {
94
+ try {
95
+ const response = await fetch(`https://do.featurebase.app/v2/users/${userId}`, {
96
+ method: "PATCH",
97
+ headers: {
98
+ "Content-Type": "application/json",
99
+ "X-API-Key": apiKey
100
+ },
101
+ body: JSON.stringify(updates)
102
+ });
103
+ if (!response.ok) {
104
+ const errorData = await response.json().catch(() => ({}));
105
+ throw new StackAssertionError(`Failed to update Featurebase user: ${errorData.error || response.statusText}`, { errorData });
106
+ }
107
+ const data = await response.json();
108
+ return {
109
+ userId: data.userId || userId,
110
+ email: data.email,
111
+ name: data.name,
112
+ profilePicture: data.profilePicture
113
+ };
114
+ } catch (error) {
115
+ if (error instanceof StackAssertionError) {
116
+ throw error;
117
+ }
118
+ throw new StackAssertionError("Failed to update Featurebase user", { cause: error });
119
+ }
120
+ }
121
+ async function getOrCreateFeaturebaseUser(stackAuthUser, options) {
122
+ const apiKey = options?.apiKey || getEnvVariable("STACK_FEATUREBASE_API_KEY");
123
+ const fallbackEmail = `${stackAuthUser.id}@featurebase-user.stack-auth-app.com`;
124
+ const existingById = await findFeaturebaseUserById(stackAuthUser.id, apiKey);
125
+ if (existingById) {
126
+ let ensuredEmail = existingById.email;
127
+ if (!ensuredEmail) {
128
+ try {
129
+ await createFeaturebaseUser({
130
+ userId: existingById.userId,
131
+ email: fallbackEmail,
132
+ name: stackAuthUser.displayName || void 0,
133
+ profilePicture: stackAuthUser.profileImageUrl || void 0
134
+ }, apiKey);
135
+ ensuredEmail = fallbackEmail;
136
+ } catch (e) {
137
+ throw new StackAssertionError(`Failed to set fallback email for existing Featurebase user ${existingById.userId}`, { cause: e });
138
+ }
139
+ }
140
+ try {
141
+ const updates = {};
142
+ if (stackAuthUser.displayName && stackAuthUser.displayName !== existingById.name) {
143
+ updates.name = stackAuthUser.displayName;
144
+ }
145
+ if (stackAuthUser.profileImageUrl && stackAuthUser.profileImageUrl !== existingById.profilePicture) {
146
+ updates.profilePicture = stackAuthUser.profileImageUrl;
147
+ }
148
+ if (Object.keys(updates).length > 0) {
149
+ await updateFeaturebaseUser(existingById.userId, updates, apiKey);
150
+ }
151
+ } catch (error) {
152
+ console.error("Failed to update existing Featurebase user profile:", error);
153
+ }
154
+ return {
155
+ userId: existingById.userId,
156
+ email: ensuredEmail
157
+ };
158
+ }
159
+ const candidateEmail = stackAuthUser.primaryEmail ?? fallbackEmail;
160
+ const existingByEmail = await findFeaturebaseUserByEmail(candidateEmail, apiKey);
161
+ const safeEmail = existingByEmail ? fallbackEmail : candidateEmail;
162
+ const created = await createFeaturebaseUser({
163
+ userId: stackAuthUser.id,
164
+ email: safeEmail,
165
+ name: stackAuthUser.displayName || stackAuthUser.primaryEmail?.split("@")[0] || "User",
166
+ profilePicture: stackAuthUser.profileImageUrl || void 0
167
+ }, apiKey);
168
+ return {
169
+ userId: created.userId,
170
+ email: created.email
171
+ };
172
+ }
173
+ export {
174
+ getOrCreateFeaturebaseUser
175
+ };
176
+ //# sourceMappingURL=featurebase.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/utils/featurebase.tsx"],"sourcesContent":["import { getEnvVariable } from \"./env\";\nimport { StackAssertionError } from \"./errors\";\n\nexport type FeaturebaseUser = {\n userId: string,\n email: string,\n name?: string,\n profilePicture?: string,\n};\n\nexport type StackAuthUser = {\n id: string,\n primaryEmail: string | null,\n displayName?: string | null,\n profileImageUrl?: string | null,\n};\n\n/**\n * Find a Featurebase user by their Stack Auth user ID\n */\nasync function findFeaturebaseUserById(stackAuthUserId: string, apiKey: string): Promise<FeaturebaseUser | null> {\n try {\n const response = await fetch(`https://do.featurebase.app/v2/organization/identifyUser?id=${stackAuthUserId}`, {\n method: 'GET',\n headers: {\n 'X-API-Key': apiKey,\n },\n });\n\n if (response.status === 404) {\n return null;\n }\n\n if (!response.ok) {\n throw new StackAssertionError(`Failed to find Featurebase user by ID: ${response.statusText}`);\n }\n\n const data = await response.json();\n const user = data.user;\n\n if (!user) {\n throw new StackAssertionError(`Featurebase API returned success but no user data for ID: ${stackAuthUserId}`, { data });\n }\n\n return {\n userId: user.externalUserId || user.userId || stackAuthUserId,\n email: user.email,\n name: user.name,\n profilePicture: user.profilePicture,\n };\n } catch (error) {\n if (error instanceof StackAssertionError) {\n throw error;\n }\n throw new StackAssertionError(\"Failed to find Featurebase user by ID\", { cause: error });\n }\n}\n\n/**\n * Find a Featurebase user by their email address\n */\nasync function findFeaturebaseUserByEmail(email: string, apiKey: string): Promise<FeaturebaseUser | null> {\n try {\n const response = await fetch(`https://do.featurebase.app/v2/organization/identifyUser?email=${encodeURIComponent(email)}`, {\n method: 'GET',\n headers: {\n 'X-API-Key': apiKey,\n },\n });\n\n if (response.status === 404) {\n return null;\n }\n\n if (!response.ok) {\n throw new StackAssertionError(`Failed to find Featurebase user by email: ${response.statusText}`);\n }\n\n const data = await response.json();\n const user = data.user;\n\n if (!user) {\n throw new StackAssertionError(`Featurebase API returned success but no user data for email: ${email}`, { data });\n }\n\n return {\n userId: user.externalUserId || user.userId,\n email: user.email,\n name: user.name,\n profilePicture: user.profilePicture,\n };\n } catch (error) {\n console.error('Error finding Featurebase user by email:', error);\n return null;\n }\n}\n\n/**\n * Create a new Featurebase user using the identifyUser endpoint\n */\nasync function createFeaturebaseUser(user: FeaturebaseUser, apiKey: string): Promise<FeaturebaseUser> {\n try {\n const response = await fetch('https://do.featurebase.app/v2/organization/identifyUser', {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'X-API-Key': apiKey,\n },\n body: JSON.stringify({\n userId: user.userId,\n email: user.email,\n name: user.name,\n profilePicture: user.profilePicture,\n }),\n });\n\n if (!response.ok) {\n const errorData = await response.json().catch(() => ({}));\n throw new StackAssertionError(`Failed to create Featurebase user: ${errorData.error || response.statusText}`, { errorData });\n }\n\n // The identifyUser endpoint just returns { \"success\": true }, so we return the input data\n return user;\n } catch (error) {\n if (error instanceof StackAssertionError) {\n throw error;\n }\n throw new StackAssertionError(\"Failed to create Featurebase user\", { cause: error });\n\n }\n}\n\n/**\n * Update an existing Featurebase user (excluding email)\n */\nasync function updateFeaturebaseUser(userId: string, updates: Partial<Omit<FeaturebaseUser, 'userId' | 'email'>>, apiKey: string): Promise<FeaturebaseUser> {\n try {\n const response = await fetch(`https://do.featurebase.app/v2/users/${userId}`, {\n method: 'PATCH',\n headers: {\n 'Content-Type': 'application/json',\n 'X-API-Key': apiKey,\n },\n body: JSON.stringify(updates),\n });\n\n if (!response.ok) {\n const errorData = await response.json().catch(() => ({}));\n throw new StackAssertionError(`Failed to update Featurebase user: ${errorData.error || response.statusText}`, { errorData });\n }\n\n const data = await response.json();\n return {\n userId: data.userId || userId,\n email: data.email,\n name: data.name,\n profilePicture: data.profilePicture,\n };\n } catch (error) {\n if (error instanceof StackAssertionError) {\n throw error;\n }\n throw new StackAssertionError(\"Failed to update Featurebase user\", { cause: error });\n }\n}\n\n/**\n * Get or create a Featurebase user based on Stack Auth user data.\n * This function ensures that:\n * 1. We never change a user's email address on Featurebase\n * 2. We use Stack Auth user ID as the primary identifier\n * 3. We handle email conflicts by using fallback emails\n * 4. We update profile information when needed\n */\nexport async function getOrCreateFeaturebaseUser(\n stackAuthUser: StackAuthUser,\n options?: { apiKey?: string }\n): Promise<{ userId: string, email: string }> {\n const apiKey = options?.apiKey || getEnvVariable(\"STACK_FEATUREBASE_API_KEY\");\n const fallbackEmail = `${stackAuthUser.id}@featurebase-user.stack-auth-app.com`;\n\n // First, try to find existing user by Stack Auth user ID\n const existingById = await findFeaturebaseUserById(stackAuthUser.id, apiKey);\n if (existingById) {\n // Ensure the user has an email on Featurebase.\n let ensuredEmail = existingById.email;\n if (!ensuredEmail) {\n try {\n await createFeaturebaseUser({\n userId: existingById.userId,\n email: fallbackEmail,\n name: stackAuthUser.displayName || undefined,\n profilePicture: stackAuthUser.profileImageUrl || undefined,\n }, apiKey);\n ensuredEmail = fallbackEmail;\n } catch (e) {\n // If setting fallback email failed, keep ensuredEmail as-is (undefined) and let callers handle\n throw new StackAssertionError(`Failed to set fallback email for existing Featurebase user ${existingById.userId}`, { cause: e });\n }\n }\n\n // Update profile information if needed (but not email)\n try {\n const updates: Partial<Omit<FeaturebaseUser, 'userId' | 'email'>> = {};\n\n if (stackAuthUser.displayName && stackAuthUser.displayName !== existingById.name) {\n updates.name = stackAuthUser.displayName;\n }\n\n if (stackAuthUser.profileImageUrl && stackAuthUser.profileImageUrl !== existingById.profilePicture) {\n updates.profilePicture = stackAuthUser.profileImageUrl;\n }\n\n if (Object.keys(updates).length > 0) {\n await updateFeaturebaseUser(existingById.userId, updates, apiKey);\n }\n } catch (error) {\n console.error('Failed to update existing Featurebase user profile:', error);\n // Continue with existing user data even if update fails\n }\n\n return {\n userId: existingById.userId,\n email: ensuredEmail,\n };\n }\n\n // No existing user found by ID, need to create one\n const candidateEmail = stackAuthUser.primaryEmail ?? fallbackEmail;\n\n // Check if someone already has this email on Featurebase\n const existingByEmail = await findFeaturebaseUserByEmail(candidateEmail, apiKey);\n const safeEmail = existingByEmail ? fallbackEmail : candidateEmail;\n\n // Create new user\n const created = await createFeaturebaseUser({\n userId: stackAuthUser.id,\n email: safeEmail,\n name: stackAuthUser.displayName || stackAuthUser.primaryEmail?.split('@')[0] || 'User',\n profilePicture: stackAuthUser.profileImageUrl || undefined,\n }, apiKey);\n\n return {\n userId: created.userId,\n email: created.email,\n };\n}\n"],"mappings":"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","names":[]}
@@ -6,8 +6,13 @@ function escapeHtml(unsafe) {
6
6
  function html(strings, ...values) {
7
7
  return templateIdentity(strings, ...values.map((v) => escapeHtml(`${v}`)));
8
8
  }
9
+ function htmlToText(untrustedHtml) {
10
+ const doc = new DOMParser().parseFromString(untrustedHtml, "text/html");
11
+ return doc.body.textContent ?? "";
12
+ }
9
13
  export {
10
14
  escapeHtml,
11
- html
15
+ html,
16
+ htmlToText
12
17
  };
13
18
  //# sourceMappingURL=html.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/utils/html.tsx"],"sourcesContent":["import { templateIdentity } from \"./strings\";\n\nexport function escapeHtml(unsafe: string): string {\n return `${unsafe}`\n .replace(/&/g, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n .replace(/\"/g, \"&quot;\")\n .replace(/'/g, \"&#039;\");\n}\nundefined?.test(\"escapeHtml\", ({ expect }) => {\n // Test with empty string\n expect(escapeHtml(\"\")).toBe(\"\");\n\n // Test with string without special characters\n expect(escapeHtml(\"hello world\")).toBe(\"hello world\");\n\n // Test with special characters\n expect(escapeHtml(\"<div>\")).toBe(\"&lt;div&gt;\");\n expect(escapeHtml(\"a & b\")).toBe(\"a &amp; b\");\n expect(escapeHtml('a \"quoted\" string')).toBe(\"a &quot;quoted&quot; string\");\n expect(escapeHtml(\"it's a test\")).toBe(\"it&#039;s a test\");\n\n // Test with multiple special characters\n expect(escapeHtml(\"<a href=\\\"test\\\">It's a link</a>\")).toBe(\n \"&lt;a href=&quot;test&quot;&gt;It&#039;s a link&lt;/a&gt;\"\n );\n});\n\nexport function html(strings: TemplateStringsArray, ...values: any[]): string {\n return templateIdentity(strings, ...values.map(v => escapeHtml(`${v}`)));\n}\nundefined?.test(\"html\", ({ expect }) => {\n // Test with no interpolation\n expect(html`simple string`).toBe(\"simple string\");\n\n // Test with string interpolation\n expect(html`Hello, ${\"world\"}!`).toBe(\"Hello, world!\");\n\n // Test with number interpolation\n expect(html`Count: ${42}`).toBe(\"Count: 42\");\n\n // Test with HTML special characters in interpolated values\n expect(html`<div>${\"<script>\"}</div>`).toBe(\"<div>&lt;script&gt;</div>\");\n\n // Test with multiple interpolations\n expect(html`${1} + ${2} = ${\"<3\"}`).toBe(\"1 + 2 = &lt;3\");\n\n // Test with object interpolation\n const obj = { toString: () => \"<object>\" };\n expect(html`Object: ${obj}`).toBe(\"Object: &lt;object&gt;\");\n});\n"],"mappings":";AAAA,SAAS,wBAAwB;AAE1B,SAAS,WAAW,QAAwB;AACjD,SAAO,GAAG,MAAM,GACb,QAAQ,MAAM,OAAO,EACrB,QAAQ,MAAM,MAAM,EACpB,QAAQ,MAAM,MAAM,EACpB,QAAQ,MAAM,QAAQ,EACtB,QAAQ,MAAM,QAAQ;AAC3B;AAoBO,SAAS,KAAK,YAAkC,QAAuB;AAC5E,SAAO,iBAAiB,SAAS,GAAG,OAAO,IAAI,OAAK,WAAW,GAAG,CAAC,EAAE,CAAC,CAAC;AACzE;","names":[]}
1
+ {"version":3,"sources":["../../../src/utils/html.tsx"],"sourcesContent":["import { templateIdentity } from \"./strings\";\n\nexport function escapeHtml(unsafe: string): string {\n return `${unsafe}`\n .replace(/&/g, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n .replace(/\"/g, \"&quot;\")\n .replace(/'/g, \"&#039;\");\n}\nundefined?.test(\"escapeHtml\", ({ expect }) => {\n // Test with empty string\n expect(escapeHtml(\"\")).toBe(\"\");\n\n // Test with string without special characters\n expect(escapeHtml(\"hello world\")).toBe(\"hello world\");\n\n // Test with special characters\n expect(escapeHtml(\"<div>\")).toBe(\"&lt;div&gt;\");\n expect(escapeHtml(\"a & b\")).toBe(\"a &amp; b\");\n expect(escapeHtml('a \"quoted\" string')).toBe(\"a &quot;quoted&quot; string\");\n expect(escapeHtml(\"it's a test\")).toBe(\"it&#039;s a test\");\n\n // Test with multiple special characters\n expect(escapeHtml(\"<a href=\\\"test\\\">It's a link</a>\")).toBe(\n \"&lt;a href=&quot;test&quot;&gt;It&#039;s a link&lt;/a&gt;\"\n );\n});\n\nexport function html(strings: TemplateStringsArray, ...values: any[]): string {\n return templateIdentity(strings, ...values.map(v => escapeHtml(`${v}`)));\n}\nundefined?.test(\"html\", ({ expect }) => {\n // Test with no interpolation\n expect(html`simple string`).toBe(\"simple string\");\n\n // Test with string interpolation\n expect(html`Hello, ${\"world\"}!`).toBe(\"Hello, world!\");\n\n // Test with number interpolation\n expect(html`Count: ${42}`).toBe(\"Count: 42\");\n\n // Test with HTML special characters in interpolated values\n expect(html`<div>${\"<script>\"}</div>`).toBe(\"<div>&lt;script&gt;</div>\");\n\n // Test with multiple interpolations\n expect(html`${1} + ${2} = ${\"<3\"}`).toBe(\"1 + 2 = &lt;3\");\n\n // Test with object interpolation\n const obj = { toString: () => \"<object>\" };\n expect(html`Object: ${obj}`).toBe(\"Object: &lt;object&gt;\");\n});\n\nexport function htmlToText(untrustedHtml: string): string {\n\n const doc = new DOMParser().parseFromString(untrustedHtml, 'text/html');\n\n return doc.body.textContent ?? '';\n\n}\n"],"mappings":";AAAA,SAAS,wBAAwB;AAE1B,SAAS,WAAW,QAAwB;AACjD,SAAO,GAAG,MAAM,GACb,QAAQ,MAAM,OAAO,EACrB,QAAQ,MAAM,MAAM,EACpB,QAAQ,MAAM,MAAM,EACpB,QAAQ,MAAM,QAAQ,EACtB,QAAQ,MAAM,QAAQ;AAC3B;AAoBO,SAAS,KAAK,YAAkC,QAAuB;AAC5E,SAAO,iBAAiB,SAAS,GAAG,OAAO,IAAI,OAAK,WAAW,GAAG,CAAC,EAAE,CAAC,CAAC;AACzE;AAsBO,SAAS,WAAW,eAA+B;AAExD,QAAM,MAAM,IAAI,UAAU,EAAE,gBAAgB,eAAe,WAAW;AAEtE,SAAO,IAAI,KAAK,eAAe;AAEjC;","names":[]}
@@ -1,6 +1,6 @@
1
1
  // src/utils/promises.tsx
2
2
  import { KnownError } from "../index.js";
3
- import { StackAssertionError, captureError, concatStacktraces } from "./errors.js";
3
+ import { StackAssertionError, captureError, concatStacktraces, errorToNiceString } from "./errors.js";
4
4
  import { DependenciesMap } from "./maps.js";
5
5
  import { Result } from "./results.js";
6
6
  import { generateUuid } from "./uuids.js";
@@ -83,6 +83,15 @@ function ignoreUnhandledRejection(promise) {
83
83
  promise.catch(() => {
84
84
  });
85
85
  }
86
+ function concatStacktracesIfRejected(promise) {
87
+ const currentError = new Error();
88
+ promise.catch((error) => {
89
+ if (error instanceof Error) {
90
+ concatStacktraces(error, currentError);
91
+ } else {
92
+ }
93
+ });
94
+ }
86
95
  async function wait(ms) {
87
96
  if (!Number.isFinite(ms) || ms < 0) {
88
97
  throw new StackAssertionError(`wait() requires a non-negative integer number of milliseconds to wait. (found: ${ms}ms)`);
@@ -118,18 +127,19 @@ function runAsynchronously(promiseOrFunc, options = {}) {
118
127
  if (typeof promiseOrFunc === "function") {
119
128
  promiseOrFunc = promiseOrFunc();
120
129
  }
121
- const duringError = new Error();
122
- promiseOrFunc?.catch((error) => {
123
- options.onError?.(error);
124
- const newError = new StackAssertionError(
125
- "Uncaught error in asynchronous function: " + error.toString(),
126
- { cause: error }
127
- );
128
- concatStacktraces(newError, duringError);
129
- if (!options.noErrorLogging) {
130
- captureError("runAsynchronously", newError);
131
- }
132
- });
130
+ if (promiseOrFunc) {
131
+ concatStacktracesIfRejected(promiseOrFunc);
132
+ promiseOrFunc.catch((error) => {
133
+ options.onError?.(error);
134
+ const newError = new StackAssertionError(
135
+ "Uncaught error in asynchronous function: " + errorToNiceString(error),
136
+ { cause: error }
137
+ );
138
+ if (!options.noErrorLogging) {
139
+ captureError("runAsynchronously", newError);
140
+ }
141
+ });
142
+ }
133
143
  }
134
144
  var TimeoutError = class extends Error {
135
145
  constructor(ms) {
@@ -215,6 +225,7 @@ function throttled(func, delayMs) {
215
225
  };
216
226
  }
217
227
  export {
228
+ concatStacktracesIfRejected,
218
229
  createPromise,
219
230
  ignoreUnhandledRejection,
220
231
  neverResolve,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/utils/promises.tsx"],"sourcesContent":["import { KnownError } from \"..\";\nimport { StackAssertionError, captureError, concatStacktraces } from \"./errors\";\nimport { DependenciesMap } from \"./maps\";\nimport { Result } from \"./results\";\nimport { generateUuid } from \"./uuids\";\n\nexport type ReactPromise<T> = Promise<T> & (\n | { status: \"rejected\", reason: unknown }\n | { status: \"fulfilled\", value: T }\n | { status: \"pending\" }\n);\n\ntype Resolve<T> = (value: T) => void;\ntype Reject = (reason: unknown) => void;\nexport function createPromise<T>(callback: (resolve: Resolve<T>, reject: Reject) => void): ReactPromise<T> {\n let status = \"pending\" as \"fulfilled\" | \"rejected\" | \"pending\";\n let valueOrReason: T | unknown | undefined = undefined;\n let resolve: Resolve<T> | null = null;\n let reject: Reject | null = null;\n const promise = new Promise<T>((res, rej) => {\n resolve = (value) => {\n if (status !== \"pending\") return;\n status = \"fulfilled\";\n valueOrReason = value;\n res(value);\n };\n reject = (reason) => {\n if (status !== \"pending\") return;\n status = \"rejected\";\n valueOrReason = reason;\n rej(reason);\n };\n });\n\n callback(resolve!, reject!);\n return Object.assign(promise, {\n status: status,\n ...status === \"fulfilled\" ? { value: valueOrReason as T } : {},\n ...status === \"rejected\" ? { reason: valueOrReason } : {},\n } as any);\n}\nundefined?.test(\"createPromise\", async ({ expect }) => {\n // Test resolved promise\n const resolvedPromise = createPromise<number>((resolve) => {\n resolve(42);\n });\n expect(resolvedPromise.status).toBe(\"fulfilled\");\n expect((resolvedPromise as any).value).toBe(42);\n expect(await resolvedPromise).toBe(42);\n\n // Test rejected promise\n const error = new Error(\"Test error\");\n const rejectedPromise = createPromise<number>((_, reject) => {\n reject(error);\n });\n expect(rejectedPromise.status).toBe(\"rejected\");\n expect((rejectedPromise as any).reason).toBe(error);\n await expect(rejectedPromise).rejects.toBe(error);\n\n // Test pending promise\n const pendingPromise = createPromise<number>(() => {\n // Do nothing, leave it pending\n });\n expect(pendingPromise.status).toBe(\"pending\");\n expect((pendingPromise as any).value).toBeUndefined();\n expect((pendingPromise as any).reason).toBeUndefined();\n\n // Test that resolving after already resolved does nothing\n let resolveCount = 0;\n const multiResolvePromise = createPromise<number>((resolve) => {\n resolve(1);\n resolveCount++;\n resolve(2);\n resolveCount++;\n });\n expect(resolveCount).toBe(2); // Both resolve calls executed\n expect(multiResolvePromise.status).toBe(\"fulfilled\");\n expect((multiResolvePromise as any).value).toBe(1); // Only first resolve took effect\n expect(await multiResolvePromise).toBe(1);\n});\n\nlet resolvedCache: DependenciesMap<[unknown], ReactPromise<unknown>> | null = null;\n/**\n * Like Promise.resolve(...), but also adds the status and value properties for use with React's `use` hook, and caches\n * the value so that invoking `resolved` twice returns the same promise.\n */\nexport function resolved<T>(value: T): ReactPromise<T> {\n resolvedCache ??= new DependenciesMap<[unknown], ReactPromise<unknown>>();\n if (resolvedCache.has([value])) {\n return resolvedCache.get([value]) as ReactPromise<T>;\n }\n\n const res = Object.assign(Promise.resolve(value), {\n status: \"fulfilled\",\n value,\n } as const);\n resolvedCache.set([value], res);\n return res;\n}\nundefined?.test(\"resolved\", async ({ expect }) => {\n // Test with primitive value\n const promise1 = resolved(42);\n expect(promise1.status).toBe(\"fulfilled\");\n // Need to use type assertion since value is only available when status is \"fulfilled\"\n expect((promise1 as { value: number }).value).toBe(42);\n expect(await promise1).toBe(42);\n\n // Test with object value\n const obj = { test: true };\n const promise2 = resolved(obj);\n expect(promise2.status).toBe(\"fulfilled\");\n expect((promise2 as { value: typeof obj }).value).toBe(obj);\n expect(await promise2).toBe(obj);\n\n // Test caching (same reference for same value)\n const promise3 = resolved(42);\n expect(promise3).toBe(promise1); // Same reference due to caching\n\n // Test with different value (different reference)\n const promise4 = resolved(43);\n expect(promise4).not.toBe(promise1);\n});\n\nlet rejectedCache: DependenciesMap<[unknown], ReactPromise<unknown>> | null = null;\n/**\n * Like Promise.reject(...), but also adds the status and value properties for use with React's `use` hook, and caches\n * the value so that invoking `rejected` twice returns the same promise.\n */\nexport function rejected<T>(reason: unknown): ReactPromise<T> {\n rejectedCache ??= new DependenciesMap<[unknown], ReactPromise<unknown>>();\n if (rejectedCache.has([reason])) {\n return rejectedCache.get([reason]) as ReactPromise<T>;\n }\n\n const promise = Promise.reject(reason);\n ignoreUnhandledRejection(promise);\n const res = Object.assign(promise, {\n status: \"rejected\",\n reason: reason,\n } as const);\n rejectedCache.set([reason], res);\n return res;\n}\nundefined?.test(\"rejected\", ({ expect }) => {\n // Test with error object\n const error = new Error(\"Test error\");\n const promise1 = rejected<number>(error);\n expect(promise1.status).toBe(\"rejected\");\n // Need to use type assertion since reason is only available when status is \"rejected\"\n expect((promise1 as { reason: Error }).reason).toBe(error);\n\n // Test with string reason\n const promise2 = rejected<string>(\"error message\");\n expect(promise2.status).toBe(\"rejected\");\n expect((promise2 as { reason: string }).reason).toBe(\"error message\");\n\n // Test caching (same reference for same reason)\n const promise3 = rejected<number>(error);\n expect(promise3).toBe(promise1); // Same reference due to caching\n\n // Test with different reason (different reference)\n const differentError = new Error(\"Different error\");\n const promise4 = rejected<number>(differentError);\n expect(promise4).not.toBe(promise1);\n\n // Note: We're not using await expect(promise).rejects to avoid unhandled rejections\n});\n\n// We'll skip the rejection test for pending() since it's causing unhandled rejections\n// The function is already well tested through other tests like rejected() and createPromise()\n\n\nconst neverResolvePromise = pending(new Promise<never>(() => {}));\nexport function neverResolve(): ReactPromise<never> {\n return neverResolvePromise;\n}\nundefined?.test(\"neverResolve\", ({ expect }) => {\n const promise = neverResolve();\n expect(promise.status).toBe(\"pending\");\n expect((promise as any).value).toBeUndefined();\n expect((promise as any).reason).toBeUndefined();\n\n // Test that multiple calls return the same promise\n const promise2 = neverResolve();\n expect(promise2).toBe(promise);\n});\n\nexport function pending<T>(promise: Promise<T>, options: { disableErrorWrapping?: boolean } = {}): ReactPromise<T> {\n const res = promise.then(\n value => {\n res.status = \"fulfilled\";\n (res as any).value = value;\n return value;\n },\n actualReason => {\n res.status = \"rejected\";\n (res as any).reason = actualReason;\n throw actualReason;\n },\n ) as ReactPromise<T>;\n res.status = \"pending\";\n return res;\n}\nundefined?.test(\"pending\", async ({ expect }) => {\n // Test with a promise that resolves\n const resolvePromise = Promise.resolve(42);\n const pendingPromise = pending(resolvePromise);\n\n // Initially it should be pending\n expect(pendingPromise.status).toBe(\"pending\");\n\n // After resolution, it should be fulfilled\n await resolvePromise;\n // Need to wait a tick for the then handler to execute\n await new Promise(resolve => setTimeout(resolve, 0));\n expect(pendingPromise.status).toBe(\"fulfilled\");\n expect((pendingPromise as { value: number }).value).toBe(42);\n\n // For the rejection test, we'll use a separate test to avoid unhandled rejections\n});\n\n/**\n * Should be used to wrap Promises that are not immediately awaited, so they don't throw an unhandled promise rejection\n * error.\n *\n * Vercel kills serverless functions on unhandled promise rejection errors, so this is important.\n */\nexport function ignoreUnhandledRejection<T extends Promise<any>>(promise: T): void {\n promise.catch(() => {});\n}\nundefined?.test(\"ignoreUnhandledRejection\", async ({ expect }) => {\n // Test with a promise that resolves\n const resolvePromise = Promise.resolve(42);\n ignoreUnhandledRejection(resolvePromise);\n expect(await resolvePromise).toBe(42); // Should still resolve to the same value\n\n // Test with a promise that rejects\n // The promise should still reject, but the rejection is caught internally\n // so it doesn't cause an unhandled rejection error\n const error = new Error(\"Test error\");\n const rejectPromise = Promise.reject(error);\n ignoreUnhandledRejection(rejectPromise);\n await expect(rejectPromise).rejects.toBe(error);\n});\n\nexport async function wait(ms: number) {\n if (!Number.isFinite(ms) || ms < 0) {\n throw new StackAssertionError(`wait() requires a non-negative integer number of milliseconds to wait. (found: ${ms}ms)`);\n }\n if (ms >= 2**31) {\n throw new StackAssertionError(\"The maximum timeout for wait() is 2147483647ms (2**31 - 1). (found: ${ms}ms)\");\n }\n return await new Promise<void>(resolve => setTimeout(resolve, ms));\n}\nundefined?.test(\"wait\", async ({ expect }) => {\n // Test with valid input\n const start = Date.now();\n await wait(10);\n const elapsed = Date.now() - start;\n expect(elapsed).toBeGreaterThanOrEqual(5); // Allow some flexibility in timing\n\n // Test with zero\n await expect(wait(0)).resolves.toBeUndefined();\n\n // Test with negative number\n await expect(wait(-10)).rejects.toThrow(\"wait() requires a non-negative integer\");\n\n // Test with non-finite number\n await expect(wait(NaN)).rejects.toThrow(\"wait() requires a non-negative integer\");\n await expect(wait(Infinity)).rejects.toThrow(\"wait() requires a non-negative integer\");\n\n // Test with too large number\n await expect(wait(2**31)).rejects.toThrow(\"The maximum timeout for wait()\");\n});\n\nexport async function waitUntil(date: Date) {\n return await wait(date.getTime() - Date.now());\n}\nundefined?.test(\"waitUntil\", async ({ expect }) => {\n // Test with future date\n const futureDate = new Date(Date.now() + 10);\n const start = Date.now();\n await waitUntil(futureDate);\n const elapsed = Date.now() - start;\n expect(elapsed).toBeGreaterThanOrEqual(5); // Allow some flexibility in timing\n\n // Test with past date - this will throw because wait() requires non-negative time\n // We need to verify it throws the correct error\n try {\n await waitUntil(new Date(Date.now() - 1000));\n expect.fail(\"Should have thrown an error\");\n } catch (error) {\n expect(error).toBeInstanceOf(StackAssertionError);\n expect((error as Error).message).toContain(\"wait() requires a non-negative integer\");\n }\n});\n\nexport function runAsynchronouslyWithAlert(...args: Parameters<typeof runAsynchronously>) {\n return runAsynchronously(\n args[0],\n {\n ...args[1],\n onError: error => {\n if (KnownError.isKnownError(error) && typeof process !== \"undefined\" && (process.env.NODE_ENV as any)?.includes(\"production\")) {\n alert(error.message);\n } else {\n alert(`An unhandled error occurred. Please ${process.env.NODE_ENV === \"development\" ? `check the browser console for the full error.` : \"report this to the developer.\"}\\n\\n${error}`);\n }\n args[1]?.onError?.(error);\n },\n },\n ...args.slice(2) as [],\n );\n}\nundefined?.test(\"runAsynchronouslyWithAlert\", ({ expect }) => {\n // Simple test to verify the function calls runAsynchronously\n // We can't easily test the alert functionality without mocking\n const testFn = () => Promise.resolve(\"test\");\n const testOptions = { noErrorLogging: true };\n\n // Just verify it doesn't throw\n expect(() => runAsynchronouslyWithAlert(testFn, testOptions)).not.toThrow();\n\n // We can't easily test the error handling without mocking, so we'll\n // just verify the function exists and can be called\n expect(typeof runAsynchronouslyWithAlert).toBe(\"function\");\n});\n\nexport function runAsynchronously(\n promiseOrFunc: void | Promise<unknown> | (() => void | Promise<unknown>) | undefined,\n options: {\n noErrorLogging?: boolean,\n onError?: (error: Error) => void,\n } = {},\n): void {\n if (typeof promiseOrFunc === \"function\") {\n promiseOrFunc = promiseOrFunc();\n }\n const duringError = new Error();\n promiseOrFunc?.catch(error => {\n options.onError?.(error);\n const newError = new StackAssertionError(\n \"Uncaught error in asynchronous function: \" + error.toString(),\n { cause: error },\n );\n concatStacktraces(newError, duringError);\n if (!options.noErrorLogging) {\n captureError(\"runAsynchronously\", newError);\n }\n });\n}\nundefined?.test(\"runAsynchronously\", ({ expect }) => {\n // Simple test to verify the function exists and can be called\n const testFn = () => Promise.resolve(\"test\");\n\n // Just verify it doesn't throw\n expect(() => runAsynchronously(testFn)).not.toThrow();\n expect(() => runAsynchronously(Promise.resolve(\"test\"))).not.toThrow();\n expect(() => runAsynchronously(undefined)).not.toThrow();\n\n // We can't easily test the error handling without mocking, so we'll\n // just verify the function exists and can be called with options\n expect(() => runAsynchronously(testFn, { noErrorLogging: true })).not.toThrow();\n expect(() => runAsynchronously(testFn, { onError: () => {} })).not.toThrow();\n});\n\n\nclass TimeoutError extends Error {\n constructor(public readonly ms: number) {\n super(`Timeout after ${ms}ms`);\n this.name = \"TimeoutError\";\n }\n}\n\nexport async function timeout<T>(promise: Promise<T>, ms: number): Promise<Result<T, TimeoutError>> {\n return await Promise.race([\n promise.then(value => Result.ok(value)),\n wait(ms).then(() => Result.error(new TimeoutError(ms))),\n ]);\n}\nundefined?.test(\"timeout\", async ({ expect }) => {\n // Test with a promise that resolves quickly\n const fastPromise = Promise.resolve(42);\n const fastResult = await timeout(fastPromise, 100);\n expect(fastResult.status).toBe(\"ok\");\n if (fastResult.status === \"ok\") {\n expect(fastResult.data).toBe(42);\n }\n\n // Test with a promise that takes longer than the timeout\n const slowPromise = new Promise(resolve => setTimeout(() => resolve(\"too late\"), 50));\n const slowResult = await timeout(slowPromise, 10);\n expect(slowResult.status).toBe(\"error\");\n if (slowResult.status === \"error\") {\n expect(slowResult.error).toBeInstanceOf(TimeoutError);\n expect((slowResult.error as TimeoutError).ms).toBe(10);\n }\n});\n\nexport async function timeoutThrow<T>(promise: Promise<T>, ms: number): Promise<T> {\n return Result.orThrow(await timeout(promise, ms));\n}\nundefined?.test(\"timeoutThrow\", async ({ expect }) => {\n // Test with a promise that resolves quickly\n const fastPromise = Promise.resolve(42);\n const fastResult = await timeoutThrow(fastPromise, 100);\n expect(fastResult).toBe(42);\n\n // Test with a promise that takes longer than the timeout\n const slowPromise = new Promise(resolve => setTimeout(() => resolve(\"too late\"), 50));\n await expect(timeoutThrow(slowPromise, 10)).rejects.toThrow(\"Timeout after 10ms\");\n await expect(timeoutThrow(slowPromise, 10)).rejects.toBeInstanceOf(TimeoutError);\n});\n\n\nexport type RateLimitOptions = {\n /**\n * The number of requests to process in parallel. Currently only 1 is supported.\n */\n concurrency: 1,\n\n /**\n * If true, multiple requests waiting at the same time will be reduced to just one. Default is false.\n */\n batchCalls?: boolean,\n\n /**\n * Waits for throttleMs since the start of last request before starting the next request. Default is 0.\n */\n throttleMs?: number,\n\n /**\n * Waits for gapMs since the end of last request before starting the next request. Default is 0.\n */\n gapMs?: number,\n\n /**\n * Waits until there have been no new requests for debounceMs before starting a new request. Default is 0.\n */\n debounceMs?: number,\n};\n\nexport function rateLimited<T>(\n func: () => Promise<T>,\n options: RateLimitOptions,\n): () => Promise<T> {\n let waitUntil = performance.now();\n let queue: [(t: T) => void, (e: unknown) => void][] = [];\n let addedToQueueCallbacks = new Map<string, () => void>;\n\n const next = async () => {\n while (true) {\n if (waitUntil > performance.now()) {\n await wait(Math.max(1, waitUntil - performance.now() + 1));\n } else if (queue.length === 0) {\n const uuid = generateUuid();\n await new Promise<void>(resolve => {\n addedToQueueCallbacks.set(uuid, resolve);\n });\n addedToQueueCallbacks.delete(uuid);\n } else {\n break;\n }\n }\n const nextFuncs = options.batchCalls ? queue.splice(0, queue.length) : [queue.shift()!];\n\n const start = performance.now();\n const value = await Result.fromPromise(func());\n const end = performance.now();\n\n waitUntil = Math.max(\n waitUntil,\n start + (options.throttleMs ?? 0),\n end + (options.gapMs ?? 0),\n );\n\n for (const nextFunc of nextFuncs) {\n if (value.status === \"ok\") {\n nextFunc[0](value.data);\n } else {\n nextFunc[1](value.error);\n }\n }\n };\n\n runAsynchronously(async () => {\n while (true) {\n await next();\n }\n });\n\n return () => {\n return new Promise<T>((resolve, reject) => {\n waitUntil = Math.max(\n waitUntil,\n performance.now() + (options.debounceMs ?? 0),\n );\n queue.push([resolve, reject]);\n addedToQueueCallbacks.forEach(cb => cb());\n });\n };\n}\n\nexport function throttled<T, A extends any[]>(func: (...args: A) => Promise<T>, delayMs: number): (...args: A) => Promise<T> {\n let timeout: ReturnType<typeof setTimeout> | null = null;\n let nextAvailable: Promise<T> | null = null;\n return async (...args) => {\n while (nextAvailable !== null) {\n await nextAvailable;\n }\n nextAvailable = new Promise<T>(resolve => {\n timeout = setTimeout(() => {\n nextAvailable = null;\n resolve(func(...args));\n }, delayMs);\n });\n return await nextAvailable;\n };\n}\n"],"mappings":"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","names":["waitUntil","timeout"]}
1
+ {"version":3,"sources":["../../../src/utils/promises.tsx"],"sourcesContent":["import { KnownError } from \"..\";\nimport { StackAssertionError, captureError, concatStacktraces, errorToNiceString } from \"./errors\";\nimport { DependenciesMap } from \"./maps\";\nimport { Result } from \"./results\";\nimport { generateUuid } from \"./uuids\";\n\nexport type ReactPromise<T> = Promise<T> & (\n | { status: \"rejected\", reason: unknown }\n | { status: \"fulfilled\", value: T }\n | { status: \"pending\" }\n);\n\ntype Resolve<T> = (value: T) => void;\ntype Reject = (reason: unknown) => void;\nexport function createPromise<T>(callback: (resolve: Resolve<T>, reject: Reject) => void): ReactPromise<T> {\n let status = \"pending\" as \"fulfilled\" | \"rejected\" | \"pending\";\n let valueOrReason: T | unknown | undefined = undefined;\n let resolve: Resolve<T> | null = null;\n let reject: Reject | null = null;\n const promise = new Promise<T>((res, rej) => {\n resolve = (value) => {\n if (status !== \"pending\") return;\n status = \"fulfilled\";\n valueOrReason = value;\n res(value);\n };\n reject = (reason) => {\n if (status !== \"pending\") return;\n status = \"rejected\";\n valueOrReason = reason;\n rej(reason);\n };\n });\n\n callback(resolve!, reject!);\n return Object.assign(promise, {\n status: status,\n ...status === \"fulfilled\" ? { value: valueOrReason as T } : {},\n ...status === \"rejected\" ? { reason: valueOrReason } : {},\n } as any);\n}\nundefined?.test(\"createPromise\", async ({ expect }) => {\n // Test resolved promise\n const resolvedPromise = createPromise<number>((resolve) => {\n resolve(42);\n });\n expect(resolvedPromise.status).toBe(\"fulfilled\");\n expect((resolvedPromise as any).value).toBe(42);\n expect(await resolvedPromise).toBe(42);\n\n // Test rejected promise\n const error = new Error(\"Test error\");\n const rejectedPromise = createPromise<number>((_, reject) => {\n reject(error);\n });\n expect(rejectedPromise.status).toBe(\"rejected\");\n expect((rejectedPromise as any).reason).toBe(error);\n await expect(rejectedPromise).rejects.toBe(error);\n\n // Test pending promise\n const pendingPromise = createPromise<number>(() => {\n // Do nothing, leave it pending\n });\n expect(pendingPromise.status).toBe(\"pending\");\n expect((pendingPromise as any).value).toBeUndefined();\n expect((pendingPromise as any).reason).toBeUndefined();\n\n // Test that resolving after already resolved does nothing\n let resolveCount = 0;\n const multiResolvePromise = createPromise<number>((resolve) => {\n resolve(1);\n resolveCount++;\n resolve(2);\n resolveCount++;\n });\n expect(resolveCount).toBe(2); // Both resolve calls executed\n expect(multiResolvePromise.status).toBe(\"fulfilled\");\n expect((multiResolvePromise as any).value).toBe(1); // Only first resolve took effect\n expect(await multiResolvePromise).toBe(1);\n});\n\nlet resolvedCache: DependenciesMap<[unknown], ReactPromise<unknown>> | null = null;\n/**\n * Like Promise.resolve(...), but also adds the status and value properties for use with React's `use` hook, and caches\n * the value so that invoking `resolved` twice returns the same promise.\n */\nexport function resolved<T>(value: T): ReactPromise<T> {\n resolvedCache ??= new DependenciesMap<[unknown], ReactPromise<unknown>>();\n if (resolvedCache.has([value])) {\n return resolvedCache.get([value]) as ReactPromise<T>;\n }\n\n const res = Object.assign(Promise.resolve(value), {\n status: \"fulfilled\",\n value,\n } as const);\n resolvedCache.set([value], res);\n return res;\n}\nundefined?.test(\"resolved\", async ({ expect }) => {\n // Test with primitive value\n const promise1 = resolved(42);\n expect(promise1.status).toBe(\"fulfilled\");\n // Need to use type assertion since value is only available when status is \"fulfilled\"\n expect((promise1 as { value: number }).value).toBe(42);\n expect(await promise1).toBe(42);\n\n // Test with object value\n const obj = { test: true };\n const promise2 = resolved(obj);\n expect(promise2.status).toBe(\"fulfilled\");\n expect((promise2 as { value: typeof obj }).value).toBe(obj);\n expect(await promise2).toBe(obj);\n\n // Test caching (same reference for same value)\n const promise3 = resolved(42);\n expect(promise3).toBe(promise1); // Same reference due to caching\n\n // Test with different value (different reference)\n const promise4 = resolved(43);\n expect(promise4).not.toBe(promise1);\n});\n\nlet rejectedCache: DependenciesMap<[unknown], ReactPromise<unknown>> | null = null;\n/**\n * Like Promise.reject(...), but also adds the status and value properties for use with React's `use` hook, and caches\n * the value so that invoking `rejected` twice returns the same promise.\n */\nexport function rejected<T>(reason: unknown): ReactPromise<T> {\n rejectedCache ??= new DependenciesMap<[unknown], ReactPromise<unknown>>();\n if (rejectedCache.has([reason])) {\n return rejectedCache.get([reason]) as ReactPromise<T>;\n }\n\n const promise = Promise.reject(reason);\n ignoreUnhandledRejection(promise);\n const res = Object.assign(promise, {\n status: \"rejected\",\n reason: reason,\n } as const);\n rejectedCache.set([reason], res);\n return res;\n}\nundefined?.test(\"rejected\", ({ expect }) => {\n // Test with error object\n const error = new Error(\"Test error\");\n const promise1 = rejected<number>(error);\n expect(promise1.status).toBe(\"rejected\");\n // Need to use type assertion since reason is only available when status is \"rejected\"\n expect((promise1 as { reason: Error }).reason).toBe(error);\n\n // Test with string reason\n const promise2 = rejected<string>(\"error message\");\n expect(promise2.status).toBe(\"rejected\");\n expect((promise2 as { reason: string }).reason).toBe(\"error message\");\n\n // Test caching (same reference for same reason)\n const promise3 = rejected<number>(error);\n expect(promise3).toBe(promise1); // Same reference due to caching\n\n // Test with different reason (different reference)\n const differentError = new Error(\"Different error\");\n const promise4 = rejected<number>(differentError);\n expect(promise4).not.toBe(promise1);\n\n // Note: We're not using await expect(promise).rejects to avoid unhandled rejections\n});\n\n// We'll skip the rejection test for pending() since it's causing unhandled rejections\n// The function is already well tested through other tests like rejected() and createPromise()\n\n\nconst neverResolvePromise = pending(new Promise<never>(() => {}));\nexport function neverResolve(): ReactPromise<never> {\n return neverResolvePromise;\n}\nundefined?.test(\"neverResolve\", ({ expect }) => {\n const promise = neverResolve();\n expect(promise.status).toBe(\"pending\");\n expect((promise as any).value).toBeUndefined();\n expect((promise as any).reason).toBeUndefined();\n\n // Test that multiple calls return the same promise\n const promise2 = neverResolve();\n expect(promise2).toBe(promise);\n});\n\nexport function pending<T>(promise: Promise<T>, options: { disableErrorWrapping?: boolean } = {}): ReactPromise<T> {\n const res = promise.then(\n value => {\n res.status = \"fulfilled\";\n (res as any).value = value;\n return value;\n },\n actualReason => {\n res.status = \"rejected\";\n (res as any).reason = actualReason;\n throw actualReason;\n },\n ) as ReactPromise<T>;\n res.status = \"pending\";\n return res;\n}\nundefined?.test(\"pending\", async ({ expect }) => {\n // Test with a promise that resolves\n const resolvePromise = Promise.resolve(42);\n const pendingPromise = pending(resolvePromise);\n\n // Initially it should be pending\n expect(pendingPromise.status).toBe(\"pending\");\n\n // After resolution, it should be fulfilled\n await resolvePromise;\n // Need to wait a tick for the then handler to execute\n await new Promise(resolve => setTimeout(resolve, 0));\n expect(pendingPromise.status).toBe(\"fulfilled\");\n expect((pendingPromise as { value: number }).value).toBe(42);\n\n // For the rejection test, we'll use a separate test to avoid unhandled rejections\n});\n\n/**\n * Should be used to wrap Promises that are not immediately awaited, so they don't throw an unhandled promise rejection\n * error.\n *\n * Vercel kills serverless functions on unhandled promise rejection errors, so this is important.\n */\nexport function ignoreUnhandledRejection<T extends Promise<any>>(promise: T): void {\n promise.catch(() => {});\n}\nundefined?.test(\"ignoreUnhandledRejection\", async ({ expect }) => {\n // Test with a promise that resolves\n const resolvePromise = Promise.resolve(42);\n ignoreUnhandledRejection(resolvePromise);\n expect(await resolvePromise).toBe(42); // Should still resolve to the same value\n\n // Test with a promise that rejects\n // The promise should still reject, but the rejection is caught internally\n // so it doesn't cause an unhandled rejection error\n const error = new Error(\"Test error\");\n const rejectPromise = Promise.reject(error);\n ignoreUnhandledRejection(rejectPromise);\n await expect(rejectPromise).rejects.toBe(error);\n});\n\n/**\n * See concatStacktraces for more information.\n */\nexport function concatStacktracesIfRejected<T>(promise: Promise<T>): void {\n const currentError = new Error();\n promise.catch(error => {\n if (error instanceof Error) {\n concatStacktraces(error, currentError);\n } else {\n // we can only concatenate errors, so we'll just ignore the non-error\n }\n });\n}\n\nexport async function wait(ms: number) {\n if (!Number.isFinite(ms) || ms < 0) {\n throw new StackAssertionError(`wait() requires a non-negative integer number of milliseconds to wait. (found: ${ms}ms)`);\n }\n if (ms >= 2**31) {\n throw new StackAssertionError(\"The maximum timeout for wait() is 2147483647ms (2**31 - 1). (found: ${ms}ms)\");\n }\n return await new Promise<void>(resolve => setTimeout(resolve, ms));\n}\nundefined?.test(\"wait\", async ({ expect }) => {\n // Test with valid input\n const start = Date.now();\n await wait(10);\n const elapsed = Date.now() - start;\n expect(elapsed).toBeGreaterThanOrEqual(5); // Allow some flexibility in timing\n\n // Test with zero\n await expect(wait(0)).resolves.toBeUndefined();\n\n // Test with negative number\n await expect(wait(-10)).rejects.toThrow(\"wait() requires a non-negative integer\");\n\n // Test with non-finite number\n await expect(wait(NaN)).rejects.toThrow(\"wait() requires a non-negative integer\");\n await expect(wait(Infinity)).rejects.toThrow(\"wait() requires a non-negative integer\");\n\n // Test with too large number\n await expect(wait(2**31)).rejects.toThrow(\"The maximum timeout for wait()\");\n});\n\nexport async function waitUntil(date: Date) {\n return await wait(date.getTime() - Date.now());\n}\nundefined?.test(\"waitUntil\", async ({ expect }) => {\n // Test with future date\n const futureDate = new Date(Date.now() + 10);\n const start = Date.now();\n await waitUntil(futureDate);\n const elapsed = Date.now() - start;\n expect(elapsed).toBeGreaterThanOrEqual(5); // Allow some flexibility in timing\n\n // Test with past date - this will throw because wait() requires non-negative time\n // We need to verify it throws the correct error\n try {\n await waitUntil(new Date(Date.now() - 1000));\n expect.fail(\"Should have thrown an error\");\n } catch (error) {\n expect(error).toBeInstanceOf(StackAssertionError);\n expect((error as Error).message).toContain(\"wait() requires a non-negative integer\");\n }\n});\n\nexport function runAsynchronouslyWithAlert(...args: Parameters<typeof runAsynchronously>) {\n return runAsynchronously(\n args[0],\n {\n ...args[1],\n onError: error => {\n if (KnownError.isKnownError(error) && typeof process !== \"undefined\" && (process.env.NODE_ENV as any)?.includes(\"production\")) {\n alert(error.message);\n } else {\n alert(`An unhandled error occurred. Please ${process.env.NODE_ENV === \"development\" ? `check the browser console for the full error.` : \"report this to the developer.\"}\\n\\n${error}`);\n }\n args[1]?.onError?.(error);\n },\n },\n ...args.slice(2) as [],\n );\n}\nundefined?.test(\"runAsynchronouslyWithAlert\", ({ expect }) => {\n // Simple test to verify the function calls runAsynchronously\n // We can't easily test the alert functionality without mocking\n const testFn = () => Promise.resolve(\"test\");\n const testOptions = { noErrorLogging: true };\n\n // Just verify it doesn't throw\n expect(() => runAsynchronouslyWithAlert(testFn, testOptions)).not.toThrow();\n\n // We can't easily test the error handling without mocking, so we'll\n // just verify the function exists and can be called\n expect(typeof runAsynchronouslyWithAlert).toBe(\"function\");\n});\n\nexport function runAsynchronously(\n promiseOrFunc: void | Promise<unknown> | (() => void | Promise<unknown>) | undefined,\n options: {\n noErrorLogging?: boolean,\n onError?: (error: Error) => void,\n } = {},\n): void {\n if (typeof promiseOrFunc === \"function\") {\n promiseOrFunc = promiseOrFunc();\n }\n if (promiseOrFunc) {\n concatStacktracesIfRejected(promiseOrFunc);\n promiseOrFunc.catch(error => {\n options.onError?.(error);\n const newError = new StackAssertionError(\n \"Uncaught error in asynchronous function: \" + errorToNiceString(error),\n { cause: error },\n );\n if (!options.noErrorLogging) {\n captureError(\"runAsynchronously\", newError);\n }\n });\n }\n}\nundefined?.test(\"runAsynchronously\", ({ expect }) => {\n // Simple test to verify the function exists and can be called\n const testFn = () => Promise.resolve(\"test\");\n\n // Just verify it doesn't throw\n expect(() => runAsynchronously(testFn)).not.toThrow();\n expect(() => runAsynchronously(Promise.resolve(\"test\"))).not.toThrow();\n expect(() => runAsynchronously(undefined)).not.toThrow();\n\n // We can't easily test the error handling without mocking, so we'll\n // just verify the function exists and can be called with options\n expect(() => runAsynchronously(testFn, { noErrorLogging: true })).not.toThrow();\n expect(() => runAsynchronously(testFn, { onError: () => {} })).not.toThrow();\n});\n\n\nclass TimeoutError extends Error {\n constructor(public readonly ms: number) {\n super(`Timeout after ${ms}ms`);\n this.name = \"TimeoutError\";\n }\n}\n\nexport async function timeout<T>(promise: Promise<T>, ms: number): Promise<Result<T, TimeoutError>> {\n return await Promise.race([\n promise.then(value => Result.ok(value)),\n wait(ms).then(() => Result.error(new TimeoutError(ms))),\n ]);\n}\nundefined?.test(\"timeout\", async ({ expect }) => {\n // Test with a promise that resolves quickly\n const fastPromise = Promise.resolve(42);\n const fastResult = await timeout(fastPromise, 100);\n expect(fastResult.status).toBe(\"ok\");\n if (fastResult.status === \"ok\") {\n expect(fastResult.data).toBe(42);\n }\n\n // Test with a promise that takes longer than the timeout\n const slowPromise = new Promise(resolve => setTimeout(() => resolve(\"too late\"), 50));\n const slowResult = await timeout(slowPromise, 10);\n expect(slowResult.status).toBe(\"error\");\n if (slowResult.status === \"error\") {\n expect(slowResult.error).toBeInstanceOf(TimeoutError);\n expect((slowResult.error as TimeoutError).ms).toBe(10);\n }\n});\n\nexport async function timeoutThrow<T>(promise: Promise<T>, ms: number): Promise<T> {\n return Result.orThrow(await timeout(promise, ms));\n}\nundefined?.test(\"timeoutThrow\", async ({ expect }) => {\n // Test with a promise that resolves quickly\n const fastPromise = Promise.resolve(42);\n const fastResult = await timeoutThrow(fastPromise, 100);\n expect(fastResult).toBe(42);\n\n // Test with a promise that takes longer than the timeout\n const slowPromise = new Promise(resolve => setTimeout(() => resolve(\"too late\"), 50));\n await expect(timeoutThrow(slowPromise, 10)).rejects.toThrow(\"Timeout after 10ms\");\n await expect(timeoutThrow(slowPromise, 10)).rejects.toBeInstanceOf(TimeoutError);\n});\n\n\nexport type RateLimitOptions = {\n /**\n * The number of requests to process in parallel. Currently only 1 is supported.\n */\n concurrency: 1,\n\n /**\n * If true, multiple requests waiting at the same time will be reduced to just one. Default is false.\n */\n batchCalls?: boolean,\n\n /**\n * Waits for throttleMs since the start of last request before starting the next request. Default is 0.\n */\n throttleMs?: number,\n\n /**\n * Waits for gapMs since the end of last request before starting the next request. Default is 0.\n */\n gapMs?: number,\n\n /**\n * Waits until there have been no new requests for debounceMs before starting a new request. Default is 0.\n */\n debounceMs?: number,\n};\n\nexport function rateLimited<T>(\n func: () => Promise<T>,\n options: RateLimitOptions,\n): () => Promise<T> {\n let waitUntil = performance.now();\n let queue: [(t: T) => void, (e: unknown) => void][] = [];\n let addedToQueueCallbacks = new Map<string, () => void>;\n\n const next = async () => {\n while (true) {\n if (waitUntil > performance.now()) {\n await wait(Math.max(1, waitUntil - performance.now() + 1));\n } else if (queue.length === 0) {\n const uuid = generateUuid();\n await new Promise<void>(resolve => {\n addedToQueueCallbacks.set(uuid, resolve);\n });\n addedToQueueCallbacks.delete(uuid);\n } else {\n break;\n }\n }\n const nextFuncs = options.batchCalls ? queue.splice(0, queue.length) : [queue.shift()!];\n\n const start = performance.now();\n const value = await Result.fromPromise(func());\n const end = performance.now();\n\n waitUntil = Math.max(\n waitUntil,\n start + (options.throttleMs ?? 0),\n end + (options.gapMs ?? 0),\n );\n\n for (const nextFunc of nextFuncs) {\n if (value.status === \"ok\") {\n nextFunc[0](value.data);\n } else {\n nextFunc[1](value.error);\n }\n }\n };\n\n runAsynchronously(async () => {\n while (true) {\n await next();\n }\n });\n\n return () => {\n return new Promise<T>((resolve, reject) => {\n waitUntil = Math.max(\n waitUntil,\n performance.now() + (options.debounceMs ?? 0),\n );\n queue.push([resolve, reject]);\n addedToQueueCallbacks.forEach(cb => cb());\n });\n };\n}\n\nexport function throttled<T, A extends any[]>(func: (...args: A) => Promise<T>, delayMs: number): (...args: A) => Promise<T> {\n let timeout: ReturnType<typeof setTimeout> | null = null;\n let nextAvailable: Promise<T> | null = null;\n return async (...args) => {\n while (nextAvailable !== null) {\n await nextAvailable;\n }\n nextAvailable = new Promise<T>(resolve => {\n timeout = setTimeout(() => {\n nextAvailable = null;\n resolve(func(...args));\n }, delayMs);\n });\n return await nextAvailable;\n };\n}\n"],"mappings":"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","names":["waitUntil","timeout"]}
@@ -0,0 +1,39 @@
1
+ // src/utils/telemetry.tsx
2
+ import { trace } from "@opentelemetry/api";
3
+ import { getEnvVariable } from "./env.js";
4
+ import { StackAssertionError } from "./errors.js";
5
+ var tracer = trace.getTracer("stack-tracer");
6
+ function withTraceSpan(optionsOrDescription, fn) {
7
+ return async (...args) => {
8
+ return await traceSpan(optionsOrDescription, (span) => fn(...args));
9
+ };
10
+ }
11
+ async function traceSpan(optionsOrDescription, fn) {
12
+ let options = typeof optionsOrDescription === "string" ? { description: optionsOrDescription } : optionsOrDescription;
13
+ return await tracer.startActiveSpan(`STACK: ${options.description}`, async (span) => {
14
+ if (options.attributes) {
15
+ for (const [key, value] of Object.entries(options.attributes)) {
16
+ span.setAttribute(key, value);
17
+ }
18
+ }
19
+ try {
20
+ return await fn(span);
21
+ } finally {
22
+ span.end();
23
+ }
24
+ });
25
+ }
26
+ function log(message, attributes) {
27
+ const span = trace.getActiveSpan();
28
+ if (span) {
29
+ span.addEvent(message, attributes);
30
+ } else if (getEnvVariable("STACK_SEED_MODE", "false") !== "true") {
31
+ throw new StackAssertionError("No active span found");
32
+ }
33
+ }
34
+ export {
35
+ log,
36
+ traceSpan,
37
+ withTraceSpan
38
+ };
39
+ //# sourceMappingURL=telemetry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/utils/telemetry.tsx"],"sourcesContent":["import { Attributes, AttributeValue, Span, trace } from \"@opentelemetry/api\";\nimport { getEnvVariable } from \"./env\";\nimport { StackAssertionError } from \"./errors\";\n\nconst tracer = trace.getTracer('stack-tracer');\n\nexport function withTraceSpan<P extends any[], T>(optionsOrDescription: string | { description: string, attributes?: Record<string, AttributeValue> }, fn: (...args: P) => Promise<T>): (...args: P) => Promise<T> {\n return async (...args: P) => {\n return await traceSpan(optionsOrDescription, (span) => fn(...args));\n };\n}\n\nexport async function traceSpan<T>(optionsOrDescription: string | { description: string, attributes?: Record<string, AttributeValue> }, fn: (span: Span) => Promise<T>): Promise<T> {\n let options = typeof optionsOrDescription === 'string' ? { description: optionsOrDescription } : optionsOrDescription;\n return await tracer.startActiveSpan(`STACK: ${options.description}`, async (span) => {\n if (options.attributes) {\n for (const [key, value] of Object.entries(options.attributes)) {\n span.setAttribute(key, value);\n }\n }\n try {\n return await fn(span);\n } finally {\n span.end();\n }\n });\n}\n\nexport function log(message: string, attributes: Attributes) {\n const span = trace.getActiveSpan();\n if (span) {\n span.addEvent(message, attributes);\n // Telemetry is not initialized while seeding, so we don't want to throw an error\n } else if (getEnvVariable('STACK_SEED_MODE', 'false') !== 'true') {\n throw new StackAssertionError('No active span found');\n }\n}\n"],"mappings":";AAAA,SAA2C,aAAa;AACxD,SAAS,sBAAsB;AAC/B,SAAS,2BAA2B;AAEpC,IAAM,SAAS,MAAM,UAAU,cAAc;AAEtC,SAAS,cAAkC,sBAAqG,IAA4D;AACjN,SAAO,UAAU,SAAY;AAC3B,WAAO,MAAM,UAAU,sBAAsB,CAAC,SAAS,GAAG,GAAG,IAAI,CAAC;AAAA,EACpE;AACF;AAEA,eAAsB,UAAa,sBAAqG,IAA4C;AAClL,MAAI,UAAU,OAAO,yBAAyB,WAAW,EAAE,aAAa,qBAAqB,IAAI;AACjG,SAAO,MAAM,OAAO,gBAAgB,UAAU,QAAQ,WAAW,IAAI,OAAO,SAAS;AACnF,QAAI,QAAQ,YAAY;AACtB,iBAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,QAAQ,UAAU,GAAG;AAC7D,aAAK,aAAa,KAAK,KAAK;AAAA,MAC9B;AAAA,IACF;AACA,QAAI;AACF,aAAO,MAAM,GAAG,IAAI;AAAA,IACtB,UAAE;AACA,WAAK,IAAI;AAAA,IACX;AAAA,EACF,CAAC;AACH;AAEO,SAAS,IAAI,SAAiB,YAAwB;AAC3D,QAAM,OAAO,MAAM,cAAc;AACjC,MAAI,MAAM;AACR,SAAK,SAAS,SAAS,UAAU;AAAA,EAEnC,WAAW,eAAe,mBAAmB,OAAO,MAAM,QAAQ;AAChE,UAAM,IAAI,oBAAoB,sBAAsB;AAAA,EACtD;AACF;","names":[]}
@@ -1,6 +1,6 @@
1
1
  declare const previewTemplateSource: string;
2
2
  declare const emptyEmailTheme: string;
3
- declare const LightEmailTheme = "import { Html, Head, Tailwind, Body, Container } from '@react-email/components';\n\nexport function EmailTheme({ children }: { children: React.ReactNode }) {\n return (\n <Html>\n <Head />\n <Tailwind>\n <Body className=\"bg-[#fafbfb] font-sans text-base\">\n <Container className=\"bg-white p-[45px] rounded-lg\">\n {children}\n </Container>\n </Body>\n </Tailwind>\n </Html>\n );\n}";
3
+ declare const LightEmailTheme = "import { Html, Head, Tailwind, Body, Container, Link } from '@react-email/components';\nimport { ThemeProps } from \"@stackframe/emails\"\n\nexport function EmailTheme({ children, unsubscribeLink }: ThemeProps) {\n return (\n <Html>\n <Head />\n <Tailwind>\n <Body className=\"bg-[#fafbfb] font-sans text-base\">\n <Container className=\"bg-white p-[45px] rounded-lg\">\n {children}\n </Container>\n {unsubscribeLink && (\n <div className=\"p-4\">\n <Link href={unsubscribeLink}>Click here{\" \"}</Link>\n to unsubscribe from these emails\n </div>\n )}\n </Body>\n </Tailwind>\n </Html>\n );\n}\n\nEmailTheme.PreviewProps = {\n unsubscribeLink: \"https://example.com\"\n} satisfies Partial<ThemeProps>\n";
4
4
  declare const DEFAULT_EMAIL_THEME_ID = "1df07ae6-abf3-4a40-83a5-a1a2cbe336ac";
5
5
  declare const DEFAULT_EMAIL_THEMES: {
6
6
  "1df07ae6-abf3-4a40-83a5-a1a2cbe336ac": {
@@ -1,6 +1,6 @@
1
1
  declare const previewTemplateSource: string;
2
2
  declare const emptyEmailTheme: string;
3
- declare const LightEmailTheme = "import { Html, Head, Tailwind, Body, Container } from '@react-email/components';\n\nexport function EmailTheme({ children }: { children: React.ReactNode }) {\n return (\n <Html>\n <Head />\n <Tailwind>\n <Body className=\"bg-[#fafbfb] font-sans text-base\">\n <Container className=\"bg-white p-[45px] rounded-lg\">\n {children}\n </Container>\n </Body>\n </Tailwind>\n </Html>\n );\n}";
3
+ declare const LightEmailTheme = "import { Html, Head, Tailwind, Body, Container, Link } from '@react-email/components';\nimport { ThemeProps } from \"@stackframe/emails\"\n\nexport function EmailTheme({ children, unsubscribeLink }: ThemeProps) {\n return (\n <Html>\n <Head />\n <Tailwind>\n <Body className=\"bg-[#fafbfb] font-sans text-base\">\n <Container className=\"bg-white p-[45px] rounded-lg\">\n {children}\n </Container>\n {unsubscribeLink && (\n <div className=\"p-4\">\n <Link href={unsubscribeLink}>Click here{\" \"}</Link>\n to unsubscribe from these emails\n </div>\n )}\n </Body>\n </Tailwind>\n </Html>\n );\n}\n\nEmailTheme.PreviewProps = {\n unsubscribeLink: \"https://example.com\"\n} satisfies Partial<ThemeProps>\n";
4
4
  declare const DEFAULT_EMAIL_THEME_ID = "1df07ae6-abf3-4a40-83a5-a1a2cbe336ac";
5
5
  declare const DEFAULT_EMAIL_THEMES: {
6
6
  "1df07ae6-abf3-4a40-83a5-a1a2cbe336ac": {
@@ -31,7 +31,7 @@ __export(emails_exports, {
31
31
  module.exports = __toCommonJS(emails_exports);
32
32
  var import_strings = require("../utils/strings.js");
33
33
  var previewTemplateSource = import_strings.deindent`
34
- import { Heading, Section, Button, Link, Column } from "@react-email/components";
34
+ import { Heading, Section, Row, Button, Link, Column } from "@react-email/components";
35
35
  export const variablesSchema = v => v;
36
36
  export function EmailTemplate() {
37
37
  return <>
@@ -70,9 +70,10 @@ var emptyEmailTheme = import_strings.deindent`
70
70
  );
71
71
  }
72
72
  `;
73
- var LightEmailTheme = `import { Html, Head, Tailwind, Body, Container } from '@react-email/components';
73
+ var LightEmailTheme = `import { Html, Head, Tailwind, Body, Container, Link } from '@react-email/components';
74
+ import { ThemeProps } from "@stackframe/emails"
74
75
 
75
- export function EmailTheme({ children }: { children: React.ReactNode }) {
76
+ export function EmailTheme({ children, unsubscribeLink }: ThemeProps) {
76
77
  return (
77
78
  <Html>
78
79
  <Head />
@@ -81,14 +82,26 @@ export function EmailTheme({ children }: { children: React.ReactNode }) {
81
82
  <Container className="bg-white p-[45px] rounded-lg">
82
83
  {children}
83
84
  </Container>
85
+ {unsubscribeLink && (
86
+ <div className="p-4">
87
+ <Link href={unsubscribeLink}>Click here{" "}</Link>
88
+ to unsubscribe from these emails
89
+ </div>
90
+ )}
84
91
  </Body>
85
92
  </Tailwind>
86
93
  </Html>
87
94
  );
88
- }`;
89
- var DarkEmailTheme = `import { Html, Head, Tailwind, Body, Container } from '@react-email/components';
95
+ }
96
+
97
+ EmailTheme.PreviewProps = {
98
+ unsubscribeLink: "https://example.com"
99
+ } satisfies Partial<ThemeProps>
100
+ `;
101
+ var DarkEmailTheme = `import { Html, Head, Tailwind, Body, Container, Link } from '@react-email/components';
102
+ import { ThemeProps } from "@stackframe/emails"
90
103
 
91
- export function EmailTheme({ children }: { children: React.ReactNode }) {
104
+ export function EmailTheme({ children, unsubscribeLink }: ThemeProps) {
92
105
  return (
93
106
  <Html>
94
107
  <Head />
@@ -97,11 +110,22 @@ export function EmailTheme({ children }: { children: React.ReactNode }) {
97
110
  <Container className="bg-black p-[45px] rounded-lg">
98
111
  {children}
99
112
  </Container>
113
+ {unsubscribeLink && (
114
+ <div className="p-4">
115
+ <Link href={unsubscribeLink}>Click here{" "}</Link>
116
+ to unsubscribe from these emails
117
+ </div>
118
+ )}
100
119
  </Body>
101
120
  </Tailwind>
102
121
  </Html>
103
122
  );
104
- }`;
123
+ }
124
+
125
+ EmailTheme.PreviewProps = {
126
+ unsubscribeLink: "https://example.com"
127
+ } satisfies Partial<ThemeProps>
128
+ `;
105
129
  var DEFAULT_EMAIL_THEME_ID = "1df07ae6-abf3-4a40-83a5-a1a2cbe336ac";
106
130
  var DEFAULT_EMAIL_THEMES = {
107
131
  [DEFAULT_EMAIL_THEME_ID]: {