@wizeworks/lens-cloud 0.3.0-alpha.1 → 0.3.0-alpha.3

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 (423) hide show
  1. package/README.md +58 -5
  2. package/dist/alerts/evaluate.d.ts.map +1 -1
  3. package/dist/alerts/evaluate.js +20 -6
  4. package/dist/alerts/evaluate.js.map +1 -1
  5. package/dist/alerts/notify.d.ts +13 -2
  6. package/dist/alerts/notify.d.ts.map +1 -1
  7. package/dist/alerts/notify.js +22 -16
  8. package/dist/alerts/notify.js.map +1 -1
  9. package/dist/app.d.ts +16 -2
  10. package/dist/app.d.ts.map +1 -1
  11. package/dist/auth/auth.d.ts +51 -0
  12. package/dist/auth/auth.d.ts.map +1 -0
  13. package/dist/auth/auth.js +63 -0
  14. package/dist/auth/auth.js.map +1 -0
  15. package/dist/auth/bridge.d.ts +7 -0
  16. package/dist/auth/bridge.d.ts.map +1 -0
  17. package/dist/auth/bridge.js +39 -0
  18. package/dist/auth/bridge.js.map +1 -0
  19. package/dist/auth/crypto.d.ts +0 -5
  20. package/dist/auth/crypto.d.ts.map +1 -1
  21. package/dist/auth/crypto.js +3 -20
  22. package/dist/auth/crypto.js.map +1 -1
  23. package/dist/auth/delete-user.d.ts +16 -0
  24. package/dist/auth/delete-user.d.ts.map +1 -0
  25. package/dist/auth/delete-user.js +21 -0
  26. package/dist/auth/delete-user.js.map +1 -0
  27. package/dist/auth/deps.d.ts +27 -0
  28. package/dist/auth/deps.d.ts.map +1 -0
  29. package/dist/auth/deps.js +3 -0
  30. package/dist/auth/deps.js.map +1 -0
  31. package/dist/auth/email-token.d.ts +8 -0
  32. package/dist/auth/email-token.d.ts.map +1 -0
  33. package/dist/auth/email-token.js +15 -0
  34. package/dist/auth/email-token.js.map +1 -0
  35. package/dist/auth/hooks.d.ts +4 -0
  36. package/dist/auth/hooks.d.ts.map +1 -0
  37. package/dist/auth/hooks.js +143 -0
  38. package/dist/auth/hooks.js.map +1 -0
  39. package/dist/auth/join-scope.d.ts +7 -0
  40. package/dist/auth/join-scope.d.ts.map +1 -0
  41. package/dist/auth/join-scope.js +17 -0
  42. package/dist/auth/join-scope.js.map +1 -0
  43. package/dist/auth/options.d.ts +17 -0
  44. package/dist/auth/options.d.ts.map +1 -0
  45. package/dist/auth/options.js +98 -0
  46. package/dist/auth/options.js.map +1 -0
  47. package/dist/auth/password.d.ts +10 -0
  48. package/dist/auth/password.d.ts.map +1 -0
  49. package/dist/auth/password.js +22 -0
  50. package/dist/auth/password.js.map +1 -0
  51. package/dist/auth/plugins.d.ts +4 -0
  52. package/dist/auth/plugins.d.ts.map +1 -0
  53. package/dist/auth/plugins.js +45 -0
  54. package/dist/auth/plugins.js.map +1 -0
  55. package/dist/auth/principal.d.ts +8 -3
  56. package/dist/auth/principal.d.ts.map +1 -1
  57. package/dist/auth/principal.js +13 -8
  58. package/dist/auth/principal.js.map +1 -1
  59. package/dist/auth/roles.d.ts +43 -3
  60. package/dist/auth/roles.d.ts.map +1 -1
  61. package/dist/auth/roles.js +15 -6
  62. package/dist/auth/roles.js.map +1 -1
  63. package/dist/auth/schema-names.d.ts +130 -0
  64. package/dist/auth/schema-names.d.ts.map +1 -0
  65. package/dist/auth/schema-names.js +53 -0
  66. package/dist/auth/schema-names.js.map +1 -0
  67. package/dist/auth/secret.d.ts +12 -0
  68. package/dist/auth/secret.d.ts.map +1 -0
  69. package/dist/auth/secret.js +32 -0
  70. package/dist/auth/secret.js.map +1 -0
  71. package/dist/auth/security-notices.d.ts +34 -0
  72. package/dist/auth/security-notices.d.ts.map +1 -0
  73. package/dist/auth/security-notices.js +81 -0
  74. package/dist/auth/security-notices.js.map +1 -0
  75. package/dist/auth/signup.d.ts +23 -0
  76. package/dist/auth/signup.d.ts.map +1 -0
  77. package/dist/auth/signup.js +61 -0
  78. package/dist/auth/signup.js.map +1 -0
  79. package/dist/auth/sso.d.ts +21 -0
  80. package/dist/auth/sso.d.ts.map +1 -0
  81. package/dist/auth/sso.js +35 -0
  82. package/dist/auth/sso.js.map +1 -0
  83. package/dist/auth/wire.d.ts +16 -0
  84. package/dist/auth/wire.d.ts.map +1 -0
  85. package/dist/auth/wire.js +47 -0
  86. package/dist/auth/wire.js.map +1 -0
  87. package/dist/changes.d.ts +15 -0
  88. package/dist/changes.d.ts.map +1 -0
  89. package/dist/changes.js +45 -0
  90. package/dist/changes.js.map +1 -0
  91. package/dist/cli-args.d.ts +1 -1
  92. package/dist/cli-args.d.ts.map +1 -1
  93. package/dist/cli-args.js +66 -14
  94. package/dist/cli-args.js.map +1 -1
  95. package/dist/cli.js +22 -2
  96. package/dist/cli.js.map +1 -1
  97. package/dist/config.d.ts +30 -4
  98. package/dist/config.d.ts.map +1 -1
  99. package/dist/config.js +37 -0
  100. package/dist/config.js.map +1 -1
  101. package/dist/copy-cli.d.ts +3 -0
  102. package/dist/copy-cli.d.ts.map +1 -0
  103. package/dist/copy-cli.js +24 -0
  104. package/dist/copy-cli.js.map +1 -0
  105. package/dist/db/db.d.ts +7 -2
  106. package/dist/db/db.d.ts.map +1 -1
  107. package/dist/db/db.js +28 -10
  108. package/dist/db/db.js.map +1 -1
  109. package/dist/db/migrate.d.ts +2 -2
  110. package/dist/db/migrate.d.ts.map +1 -1
  111. package/dist/db/migrate.js +15 -5
  112. package/dist/db/migrate.js.map +1 -1
  113. package/dist/db/schema-auth.d.ts +2 -0
  114. package/dist/db/schema-auth.d.ts.map +1 -0
  115. package/dist/db/schema-auth.js +93 -0
  116. package/dist/db/schema-auth.js.map +1 -0
  117. package/dist/db/schema-mail.d.ts +2 -0
  118. package/dist/db/schema-mail.d.ts.map +1 -0
  119. package/dist/db/schema-mail.js +8 -0
  120. package/dist/db/schema-mail.js.map +1 -0
  121. package/dist/db/schema-notify.d.ts +2 -0
  122. package/dist/db/schema-notify.d.ts.map +1 -0
  123. package/dist/db/schema-notify.js +26 -0
  124. package/dist/db/schema-notify.js.map +1 -0
  125. package/dist/db/schema-search.d.ts +2 -0
  126. package/dist/db/schema-search.d.ts.map +1 -0
  127. package/dist/db/schema-search.js +16 -0
  128. package/dist/db/schema-search.js.map +1 -0
  129. package/dist/db/schema-sso.d.ts +2 -0
  130. package/dist/db/schema-sso.d.ts.map +1 -0
  131. package/dist/db/schema-sso.js +17 -0
  132. package/dist/db/schema-sso.js.map +1 -0
  133. package/dist/db/schema.d.ts.map +1 -1
  134. package/dist/db/schema.js +10 -0
  135. package/dist/db/schema.js.map +1 -1
  136. package/dist/db/trigram.d.ts +4 -0
  137. package/dist/db/trigram.d.ts.map +1 -0
  138. package/dist/db/trigram.js +34 -0
  139. package/dist/db/trigram.js.map +1 -0
  140. package/dist/http/http.d.ts +2 -0
  141. package/dist/http/http.d.ts.map +1 -1
  142. package/dist/http/http.js +7 -3
  143. package/dist/http/http.js.map +1 -1
  144. package/dist/jobs.d.ts +1 -1
  145. package/dist/jobs.d.ts.map +1 -1
  146. package/dist/jobs.js +23 -11
  147. package/dist/jobs.js.map +1 -1
  148. package/dist/mail/blocks.d.ts +14 -0
  149. package/dist/mail/blocks.d.ts.map +1 -0
  150. package/dist/mail/blocks.js +35 -0
  151. package/dist/mail/blocks.js.map +1 -0
  152. package/dist/mail/content.d.ts +68 -0
  153. package/dist/mail/content.d.ts.map +1 -0
  154. package/dist/mail/content.js +6 -0
  155. package/dist/mail/content.js.map +1 -0
  156. package/dist/mail/emails-digest.d.ts +10 -0
  157. package/dist/mail/emails-digest.d.ts.map +1 -0
  158. package/dist/mail/emails-digest.js +55 -0
  159. package/dist/mail/emails-digest.js.map +1 -0
  160. package/dist/mail/emails-health.d.ts +6 -0
  161. package/dist/mail/emails-health.d.ts.map +1 -0
  162. package/dist/mail/emails-health.js +36 -0
  163. package/dist/mail/emails-health.js.map +1 -0
  164. package/dist/mail/emails-security.d.ts +14 -0
  165. package/dist/mail/emails-security.d.ts.map +1 -0
  166. package/dist/mail/emails-security.js +27 -0
  167. package/dist/mail/emails-security.js.map +1 -0
  168. package/dist/mail/emails-team.d.ts +11 -0
  169. package/dist/mail/emails-team.d.ts.map +1 -0
  170. package/dist/mail/emails-team.js +45 -0
  171. package/dist/mail/emails-team.js.map +1 -0
  172. package/dist/mail/emails.d.ts +9 -0
  173. package/dist/mail/emails.d.ts.map +1 -0
  174. package/dist/mail/emails.js +49 -0
  175. package/dist/mail/emails.js.map +1 -0
  176. package/dist/mail/layout.d.ts +6 -0
  177. package/dist/mail/layout.d.ts.map +1 -0
  178. package/dist/mail/layout.js +10 -0
  179. package/dist/mail/layout.js.map +1 -0
  180. package/dist/mail/mailer.d.ts +25 -0
  181. package/dist/mail/mailer.d.ts.map +1 -0
  182. package/dist/mail/mailer.js +50 -0
  183. package/dist/mail/mailer.js.map +1 -0
  184. package/dist/mail/preview.d.ts +6 -0
  185. package/dist/mail/preview.d.ts.map +1 -0
  186. package/dist/mail/preview.js +26 -0
  187. package/dist/mail/preview.js.map +1 -0
  188. package/dist/mail/resend.d.ts +13 -0
  189. package/dist/mail/resend.d.ts.map +1 -0
  190. package/dist/mail/resend.js +34 -0
  191. package/dist/mail/resend.js.map +1 -0
  192. package/dist/mail/samples.d.ts +3 -0
  193. package/dist/mail/samples.d.ts.map +1 -0
  194. package/dist/mail/samples.js +63 -0
  195. package/dist/mail/samples.js.map +1 -0
  196. package/dist/mail/stop-route.d.ts +4 -0
  197. package/dist/mail/stop-route.d.ts.map +1 -0
  198. package/dist/mail/stop-route.js +41 -0
  199. package/dist/mail/stop-route.js.map +1 -0
  200. package/dist/mail/svix.d.ts +8 -0
  201. package/dist/mail/svix.d.ts.map +1 -0
  202. package/dist/mail/svix.js +17 -0
  203. package/dist/mail/svix.js.map +1 -0
  204. package/dist/mail/templates.d.ts +12 -0
  205. package/dist/mail/templates.d.ts.map +1 -0
  206. package/dist/mail/templates.js +18 -0
  207. package/dist/mail/templates.js.map +1 -0
  208. package/dist/mail/text.d.ts +4 -0
  209. package/dist/mail/text.d.ts.map +1 -0
  210. package/dist/mail/text.js +17 -0
  211. package/dist/mail/text.js.map +1 -0
  212. package/dist/mail/theme.d.ts +32 -0
  213. package/dist/mail/theme.d.ts.map +1 -0
  214. package/dist/mail/theme.js +27 -0
  215. package/dist/mail/theme.js.map +1 -0
  216. package/dist/mail/unsubscribe.d.ts +14 -0
  217. package/dist/mail/unsubscribe.d.ts.map +1 -0
  218. package/dist/mail/unsubscribe.js +39 -0
  219. package/dist/mail/unsubscribe.js.map +1 -0
  220. package/dist/mail/webhook.d.ts +5 -0
  221. package/dist/mail/webhook.d.ts.map +1 -0
  222. package/dist/mail/webhook.js +41 -0
  223. package/dist/mail/webhook.js.map +1 -0
  224. package/dist/notify/digest-data.d.ts +32 -0
  225. package/dist/notify/digest-data.d.ts.map +1 -0
  226. package/dist/notify/digest-data.js +31 -0
  227. package/dist/notify/digest-data.js.map +1 -0
  228. package/dist/notify/digest.d.ts +17 -0
  229. package/dist/notify/digest.d.ts.map +1 -0
  230. package/dist/notify/digest.js +76 -0
  231. package/dist/notify/digest.js.map +1 -0
  232. package/dist/notify/health.d.ts +6 -0
  233. package/dist/notify/health.d.ts.map +1 -0
  234. package/dist/notify/health.js +42 -0
  235. package/dist/notify/health.js.map +1 -0
  236. package/dist/notify/team-describe.d.ts +22 -0
  237. package/dist/notify/team-describe.d.ts.map +1 -0
  238. package/dist/notify/team-describe.js +67 -0
  239. package/dist/notify/team-describe.js.map +1 -0
  240. package/dist/notify/team.d.ts +6 -0
  241. package/dist/notify/team.d.ts.map +1 -0
  242. package/dist/notify/team.js +56 -0
  243. package/dist/notify/team.js.map +1 -0
  244. package/dist/repo/alerts.d.ts +6 -2
  245. package/dist/repo/alerts.d.ts.map +1 -1
  246. package/dist/repo/alerts.js.map +1 -1
  247. package/dist/repo/auth-prune.d.ts +4 -0
  248. package/dist/repo/auth-prune.d.ts.map +1 -0
  249. package/dist/repo/auth-prune.js +8 -0
  250. package/dist/repo/auth-prune.js.map +1 -0
  251. package/dist/repo/mail-prefs.d.ts +35 -0
  252. package/dist/repo/mail-prefs.d.ts.map +1 -0
  253. package/dist/repo/mail-prefs.js +34 -0
  254. package/dist/repo/mail-prefs.js.map +1 -0
  255. package/dist/repo/orgs.d.ts +15 -25
  256. package/dist/repo/orgs.d.ts.map +1 -1
  257. package/dist/repo/orgs.js +18 -60
  258. package/dist/repo/orgs.js.map +1 -1
  259. package/dist/repo/services.d.ts +6 -0
  260. package/dist/repo/services.d.ts.map +1 -0
  261. package/dist/repo/services.js +16 -0
  262. package/dist/repo/services.js.map +1 -0
  263. package/dist/repo/suppressions.d.ts +8 -0
  264. package/dist/repo/suppressions.d.ts.map +1 -0
  265. package/dist/repo/suppressions.js +16 -0
  266. package/dist/repo/suppressions.js.map +1 -0
  267. package/dist/repo/trace-join.d.ts +9 -0
  268. package/dist/repo/trace-join.d.ts.map +1 -0
  269. package/dist/repo/trace-join.js +35 -0
  270. package/dist/repo/trace-join.js.map +1 -0
  271. package/dist/repo/trace-read.d.ts +6 -13
  272. package/dist/repo/trace-read.d.ts.map +1 -1
  273. package/dist/repo/trace-read.js +8 -3
  274. package/dist/repo/trace-read.js.map +1 -1
  275. package/dist/repo/trace-write.d.ts +5 -1
  276. package/dist/repo/trace-write.d.ts.map +1 -1
  277. package/dist/repo/trace-write.js +11 -2
  278. package/dist/repo/trace-write.js.map +1 -1
  279. package/dist/repo/users.d.ts +5 -16
  280. package/dist/repo/users.d.ts.map +1 -1
  281. package/dist/repo/users.js +2 -60
  282. package/dist/repo/users.js.map +1 -1
  283. package/dist/routes/alerts.d.ts.map +1 -1
  284. package/dist/routes/alerts.js +23 -6
  285. package/dist/routes/alerts.js.map +1 -1
  286. package/dist/routes/changes.d.ts +4 -0
  287. package/dist/routes/changes.d.ts.map +1 -0
  288. package/dist/routes/changes.js +19 -0
  289. package/dist/routes/changes.js.map +1 -0
  290. package/dist/routes/data.d.ts.map +1 -1
  291. package/dist/routes/data.js +24 -9
  292. package/dist/routes/data.js.map +1 -1
  293. package/dist/routes/index.d.ts.map +1 -1
  294. package/dist/routes/index.js +8 -2
  295. package/dist/routes/index.js.map +1 -1
  296. package/dist/routes/ingest.d.ts.map +1 -1
  297. package/dist/routes/ingest.js +7 -2
  298. package/dist/routes/ingest.js.map +1 -1
  299. package/dist/routes/mcp.d.ts.map +1 -1
  300. package/dist/routes/mcp.js +42 -25
  301. package/dist/routes/mcp.js.map +1 -1
  302. package/dist/routes/me.d.ts +4 -0
  303. package/dist/routes/me.d.ts.map +1 -0
  304. package/dist/routes/me.js +37 -0
  305. package/dist/routes/me.js.map +1 -0
  306. package/dist/routes/metrics.d.ts +2 -2
  307. package/dist/routes/metrics.d.ts.map +1 -1
  308. package/dist/routes/metrics.js +2 -3
  309. package/dist/routes/metrics.js.map +1 -1
  310. package/dist/routes/models.d.ts +2 -0
  311. package/dist/routes/models.d.ts.map +1 -1
  312. package/dist/routes/models.js +10 -5
  313. package/dist/routes/models.js.map +1 -1
  314. package/dist/routes/notify.d.ts +4 -0
  315. package/dist/routes/notify.d.ts.map +1 -0
  316. package/dist/routes/notify.js +55 -0
  317. package/dist/routes/notify.js.map +1 -0
  318. package/dist/routes/orgs.d.ts.map +1 -1
  319. package/dist/routes/orgs.js +34 -53
  320. package/dist/routes/orgs.js.map +1 -1
  321. package/dist/routes/projects.d.ts.map +1 -1
  322. package/dist/routes/projects.js +5 -3
  323. package/dist/routes/projects.js.map +1 -1
  324. package/dist/routes/search.d.ts +4 -0
  325. package/dist/routes/search.d.ts.map +1 -0
  326. package/dist/routes/search.js +33 -0
  327. package/dist/routes/search.js.map +1 -0
  328. package/dist/routes/shares.d.ts.map +1 -1
  329. package/dist/routes/shares.js +8 -5
  330. package/dist/routes/shares.js.map +1 -1
  331. package/dist/server.d.ts +2 -0
  332. package/dist/server.d.ts.map +1 -1
  333. package/dist/server.js +59 -16
  334. package/dist/server.js.map +1 -1
  335. package/dist/settle.d.ts +7 -6
  336. package/dist/settle.d.ts.map +1 -1
  337. package/dist/settle.js +30 -13
  338. package/dist/settle.js.map +1 -1
  339. package/dist/telemetry/cached.d.ts +11 -0
  340. package/dist/telemetry/cached.d.ts.map +1 -0
  341. package/dist/telemetry/cached.js +30 -0
  342. package/dist/telemetry/cached.js.map +1 -0
  343. package/dist/telemetry/ch/admin.d.ts +10 -0
  344. package/dist/telemetry/ch/admin.d.ts.map +1 -0
  345. package/dist/telemetry/ch/admin.js +65 -0
  346. package/dist/telemetry/ch/admin.js.map +1 -0
  347. package/dist/telemetry/ch/http.d.ts +19 -0
  348. package/dist/telemetry/ch/http.d.ts.map +1 -0
  349. package/dist/telemetry/ch/http.js +91 -0
  350. package/dist/telemetry/ch/http.js.map +1 -0
  351. package/dist/telemetry/ch/metrics.d.ts +10 -0
  352. package/dist/telemetry/ch/metrics.d.ts.map +1 -0
  353. package/dist/telemetry/ch/metrics.js +61 -0
  354. package/dist/telemetry/ch/metrics.js.map +1 -0
  355. package/dist/telemetry/ch/queries.d.ts +19 -0
  356. package/dist/telemetry/ch/queries.d.ts.map +1 -0
  357. package/dist/telemetry/ch/queries.js +30 -0
  358. package/dist/telemetry/ch/queries.js.map +1 -0
  359. package/dist/telemetry/ch/queue.d.ts +34 -0
  360. package/dist/telemetry/ch/queue.d.ts.map +1 -0
  361. package/dist/telemetry/ch/queue.js +79 -0
  362. package/dist/telemetry/ch/queue.js.map +1 -0
  363. package/dist/telemetry/ch/read.d.ts +12 -0
  364. package/dist/telemetry/ch/read.d.ts.map +1 -0
  365. package/dist/telemetry/ch/read.js +72 -0
  366. package/dist/telemetry/ch/read.js.map +1 -0
  367. package/dist/telemetry/ch/schema-facts.d.ts +6 -0
  368. package/dist/telemetry/ch/schema-facts.d.ts.map +1 -0
  369. package/dist/telemetry/ch/schema-facts.js +49 -0
  370. package/dist/telemetry/ch/schema-facts.js.map +1 -0
  371. package/dist/telemetry/ch/schema-raw.d.ts +8 -0
  372. package/dist/telemetry/ch/schema-raw.d.ts.map +1 -0
  373. package/dist/telemetry/ch/schema-raw.js +35 -0
  374. package/dist/telemetry/ch/schema-raw.js.map +1 -0
  375. package/dist/telemetry/ch/schema-rollups.d.ts +2 -0
  376. package/dist/telemetry/ch/schema-rollups.d.ts.map +1 -0
  377. package/dist/telemetry/ch/schema-rollups.js +61 -0
  378. package/dist/telemetry/ch/schema-rollups.js.map +1 -0
  379. package/dist/telemetry/ch/schema.d.ts +12 -0
  380. package/dist/telemetry/ch/schema.d.ts.map +1 -0
  381. package/dist/telemetry/ch/schema.js +25 -0
  382. package/dist/telemetry/ch/schema.js.map +1 -0
  383. package/dist/telemetry/ch/settle-rows.d.ts +13 -0
  384. package/dist/telemetry/ch/settle-rows.d.ts.map +1 -0
  385. package/dist/telemetry/ch/settle-rows.js +67 -0
  386. package/dist/telemetry/ch/settle-rows.js.map +1 -0
  387. package/dist/telemetry/ch/settle.d.ts +7 -0
  388. package/dist/telemetry/ch/settle.d.ts.map +1 -0
  389. package/dist/telemetry/ch/settle.js +96 -0
  390. package/dist/telemetry/ch/settle.js.map +1 -0
  391. package/dist/telemetry/ch/store.d.ts +16 -0
  392. package/dist/telemetry/ch/store.d.ts.map +1 -0
  393. package/dist/telemetry/ch/store.js +65 -0
  394. package/dist/telemetry/ch/store.js.map +1 -0
  395. package/dist/telemetry/ch/util.d.ts +20 -0
  396. package/dist/telemetry/ch/util.d.ts.map +1 -0
  397. package/dist/telemetry/ch/util.js +45 -0
  398. package/dist/telemetry/ch/util.js.map +1 -0
  399. package/dist/telemetry/ch/write.d.ts +11 -0
  400. package/dist/telemetry/ch/write.d.ts.map +1 -0
  401. package/dist/telemetry/ch/write.js +59 -0
  402. package/dist/telemetry/ch/write.js.map +1 -0
  403. package/dist/telemetry/copy.d.ts +9 -0
  404. package/dist/telemetry/copy.d.ts.map +1 -0
  405. package/dist/telemetry/copy.js +60 -0
  406. package/dist/telemetry/copy.js.map +1 -0
  407. package/dist/telemetry/index.d.ts +11 -0
  408. package/dist/telemetry/index.d.ts.map +1 -0
  409. package/dist/telemetry/index.js +21 -0
  410. package/dist/telemetry/index.js.map +1 -0
  411. package/dist/telemetry/pg-store.d.ts +5 -0
  412. package/dist/telemetry/pg-store.d.ts.map +1 -0
  413. package/dist/telemetry/pg-store.js +78 -0
  414. package/dist/telemetry/pg-store.js.map +1 -0
  415. package/dist/telemetry/store.d.ts +74 -0
  416. package/dist/telemetry/store.d.ts.map +1 -0
  417. package/dist/telemetry/store.js +2 -0
  418. package/dist/telemetry/store.js.map +1 -0
  419. package/package.json +20 -6
  420. package/dist/routes/auth.d.ts +0 -4
  421. package/dist/routes/auth.d.ts.map +0 -1
  422. package/dist/routes/auth.js +0 -106
  423. package/dist/routes/auth.js.map +0 -1
@@ -0,0 +1,50 @@
1
+ import { suppressionOf } from "../repo/suppressions.js";
2
+ import { resendSender } from "./resend.js";
3
+ /** Kinds a spam complaint does not stop: the person asked for them, or they protect the account. */
4
+ const ALWAYS = new Set(["account", "security"]);
5
+ async function smtpSender(config) {
6
+ const { createTransport } = await import("nodemailer");
7
+ const defaults = { from: config.mailFrom, ...(config.mailReplyTo ? { replyTo: config.mailReplyTo } : {}) };
8
+ const transport = createTransport(config.smtpUrl, defaults);
9
+ return { via: "smtp", send: async (m) => void (await transport.sendMail({ ...defaults, ...m })), close: async () => transport.close() };
10
+ }
11
+ const logSender = async (m) => console.info(`[wizelens-cloud] mail (no mail service set, not sent) to ${m.to}: ${m.subject}\n${m.text}\n`);
12
+ /** The test sender, then Resend, then SMTP, then the log. */
13
+ async function transportOf(config) {
14
+ if (config.sendMail)
15
+ return { via: "test", send: config.sendMail };
16
+ if (config.resendApiKey)
17
+ return { via: "resend", send: resendSender({ apiKey: config.resendApiKey, from: config.mailFrom, replyTo: config.mailReplyTo, fetch: config.fetch }) };
18
+ if (config.smtpUrl)
19
+ return smtpSender(config);
20
+ return { via: "log", send: logSender };
21
+ }
22
+ /** A bounce skips every email. A complaint skips mail the person did not ask for. */
23
+ async function skipReason(db, mail) {
24
+ const reason = await suppressionOf(db, mail.to);
25
+ if (reason === "bounce")
26
+ return "it bounced before";
27
+ if (reason === "complaint" && !ALWAYS.has(mail.kind))
28
+ return "it marked our mail as spam";
29
+ return null;
30
+ }
31
+ /** The cloud's mail, with the suppression list checked before each send. */
32
+ export async function createMailer(config, db) {
33
+ const t = await transportOf(config);
34
+ const deliver = async (pending) => {
35
+ const mail = await pending;
36
+ const skip = await skipReason(db, mail);
37
+ if (skip)
38
+ return console.info(`[wizelens-cloud] mail to ${mail.to} skipped: ${skip}.`);
39
+ await t.send(mail).catch((err) => {
40
+ throw new Error(`mail to ${mail.to} failed: ${err instanceof Error ? err.message : String(err)}`);
41
+ });
42
+ };
43
+ return {
44
+ live: t.via !== "log",
45
+ via: t.via,
46
+ send: (mail) => void deliver(mail).catch((err) => console.error("[wizelens-cloud]", err instanceof Error ? err.message : err)),
47
+ close: async () => t.close?.(),
48
+ };
49
+ }
50
+ //# sourceMappingURL=mailer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mailer.js","sourceRoot":"","sources":["../../src/mail/mailer.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AACxD,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAK3C,oGAAoG;AACpG,MAAM,MAAM,GAAG,IAAI,GAAG,CAAW,CAAC,SAAS,EAAE,UAAU,CAAC,CAAC,CAAC;AA8B1D,KAAK,UAAU,UAAU,CAAC,MAAmB;IAC3C,MAAM,EAAE,eAAe,EAAE,GAAG,MAAM,MAAM,CAAC,YAAY,CAAC,CAAC;IACvD,MAAM,QAAQ,GAAG,EAAE,IAAI,EAAE,MAAM,CAAC,QAAQ,EAAE,GAAG,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,MAAM,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC;IAC3G,MAAM,SAAS,GAAG,eAAe,CAAC,MAAM,CAAC,OAAQ,EAAE,QAAQ,CAAC,CAAC;IAC7D,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,MAAM,SAAS,CAAC,QAAQ,CAAC,EAAE,GAAG,QAAQ,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,KAAK,IAAI,EAAE,CAAC,SAAS,CAAC,KAAK,EAAE,EAAE,CAAC;AAC1I,CAAC;AAED,MAAM,SAAS,GAAW,KAAK,EAAE,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,4DAA4D,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,OAAO,KAAK,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC;AAEnJ,6DAA6D;AAC7D,KAAK,UAAU,WAAW,CAAC,MAAmB;IAC5C,IAAI,MAAM,CAAC,QAAQ;QAAE,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,QAAQ,EAAE,CAAC;IACnE,IAAI,MAAM,CAAC,YAAY;QAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,EAAE,IAAI,EAAE,YAAY,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,YAAY,EAAE,IAAI,EAAE,MAAM,CAAC,QAAQ,EAAE,OAAO,EAAE,MAAM,CAAC,WAAW,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,CAAC,EAAE,CAAC;IAChL,IAAI,MAAM,CAAC,OAAO;QAAE,OAAO,UAAU,CAAC,MAAM,CAAC,CAAC;IAC9C,OAAO,EAAE,GAAG,EAAE,KAAK,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC;AACzC,CAAC;AAED,qFAAqF;AACrF,KAAK,UAAU,UAAU,CAAC,EAAa,EAAE,IAAU;IACjD,MAAM,MAAM,GAAG,MAAM,aAAa,CAAC,EAAE,EAAE,IAAI,CAAC,EAAE,CAAC,CAAC;IAChD,IAAI,MAAM,KAAK,QAAQ;QAAE,OAAO,mBAAmB,CAAC;IACpD,IAAI,MAAM,KAAK,WAAW,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC;QAAE,OAAO,4BAA4B,CAAC;IAC1F,OAAO,IAAI,CAAC;AACd,CAAC;AAED,4EAA4E;AAC5E,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,MAAmB,EAAE,EAAa;IACnE,MAAM,CAAC,GAAG,MAAM,WAAW,CAAC,MAAM,CAAC,CAAC;IACpC,MAAM,OAAO,GAAG,KAAK,EAAE,OAA6B,EAAE,EAAE;QACtD,MAAM,IAAI,GAAG,MAAM,OAAO,CAAC;QAC3B,MAAM,IAAI,GAAG,MAAM,UAAU,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC;QACxC,IAAI,IAAI;YAAE,OAAO,OAAO,CAAC,IAAI,CAAC,4BAA4B,IAAI,CAAC,EAAE,aAAa,IAAI,GAAG,CAAC,CAAC;QACvF,MAAM,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,CAAC,GAAY,EAAE,EAAE;YACxC,MAAM,IAAI,KAAK,CAAC,WAAW,IAAI,CAAC,EAAE,YAAY,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QACpG,CAAC,CAAC,CAAC;IACL,CAAC,CAAC;IACF,OAAO;QACL,IAAI,EAAE,CAAC,CAAC,GAAG,KAAK,KAAK;QACrB,GAAG,EAAE,CAAC,CAAC,GAAG;QACV,IAAI,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,CAAC,GAAY,EAAE,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,kBAAkB,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;QACvI,KAAK,EAAE,KAAK,IAAI,EAAE,CAAC,CAAC,CAAC,KAAK,EAAE,EAAE;KAC/B,CAAC;AACJ,CAAC"}
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env node
2
+ import { SAMPLES } from "./samples.js";
3
+ export { SAMPLES };
4
+ /** Write each sample as .html and .txt, plus an index page that links them. */
5
+ export declare function writePreviews(dir: string): Promise<string[]>;
6
+ //# sourceMappingURL=preview.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"preview.d.ts","sourceRoot":"","sources":["../../src/mail/preview.ts"],"names":[],"mappings":";AAIA,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAGvC,OAAO,EAAE,OAAO,EAAE,CAAC;AAEnB,+EAA+E;AAC/E,wBAAsB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAWlE"}
@@ -0,0 +1,26 @@
1
+ #!/usr/bin/env node
2
+ import { mkdir, writeFile } from "node:fs/promises";
3
+ import { join, resolve } from "node:path";
4
+ import { pathToFileURL } from "node:url";
5
+ import { SAMPLES } from "./samples.js";
6
+ import { renderMail } from "./templates.js";
7
+ export { SAMPLES };
8
+ /** Write each sample as .html and .txt, plus an index page that links them. */
9
+ export async function writePreviews(dir) {
10
+ await mkdir(dir, { recursive: true });
11
+ const names = Object.keys(SAMPLES);
12
+ for (const name of names) {
13
+ const mail = await renderMail("ann@acme.io", SAMPLES[name]);
14
+ await writeFile(join(dir, `${name}.html`), mail.html);
15
+ await writeFile(join(dir, `${name}.txt`), `Subject: ${mail.subject}\n\n${mail.text}`);
16
+ }
17
+ const links = names.map((n) => `<li><a href="${n}.html">${n}</a> · <a href="${n}.txt">text</a></li>`).join("");
18
+ await writeFile(join(dir, "index.html"), `<!doctype html><meta charset="utf-8"><title>WizeLens emails</title><ul>${links}</ul>`);
19
+ return names;
20
+ }
21
+ if (import.meta.url === pathToFileURL(process.argv[1] ?? "").href) {
22
+ const dir = resolve(process.argv[2] ?? "email-preview");
23
+ const names = await writePreviews(dir);
24
+ console.log(`Wrote ${names.length} emails to ${dir}. Open index.html.`);
25
+ }
26
+ //# sourceMappingURL=preview.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"preview.js","sourceRoot":"","sources":["../../src/mail/preview.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvC,OAAO,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAE5C,OAAO,EAAE,OAAO,EAAE,CAAC;AAEnB,+EAA+E;AAC/E,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,GAAW;IAC7C,MAAM,KAAK,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACtC,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IACnC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,IAAI,GAAG,MAAM,UAAU,CAAC,aAAa,EAAE,OAAO,CAAC,IAAI,CAAE,CAAC,CAAC;QAC7D,MAAM,SAAS,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,OAAO,CAAC,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC;QACtD,MAAM,SAAS,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,MAAM,CAAC,EAAE,YAAY,IAAI,CAAC,OAAO,OAAO,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC;IACxF,CAAC;IACD,MAAM,KAAK,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,gBAAgB,CAAC,UAAU,CAAC,mBAAmB,CAAC,qBAAqB,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAC/G,MAAM,SAAS,CAAC,IAAI,CAAC,GAAG,EAAE,YAAY,CAAC,EAAE,0EAA0E,KAAK,OAAO,CAAC,CAAC;IACjI,OAAO,KAAK,CAAC;AACf,CAAC;AAED,IAAI,OAAO,IAAI,CAAC,GAAG,KAAK,aAAa,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;IAClE,MAAM,GAAG,GAAG,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,eAAe,CAAC,CAAC;IACxD,MAAM,KAAK,GAAG,MAAM,aAAa,CAAC,GAAG,CAAC,CAAC;IACvC,OAAO,CAAC,GAAG,CAAC,SAAS,KAAK,CAAC,MAAM,cAAc,GAAG,oBAAoB,CAAC,CAAC;AAC1E,CAAC"}
@@ -0,0 +1,13 @@
1
+ import type { Mail } from "./mailer.js";
2
+ export interface ResendSettings {
3
+ apiKey: string;
4
+ from: string;
5
+ /** Where replies go. Left out when unset. */
6
+ replyTo?: string;
7
+ fetch: typeof fetch;
8
+ /** Wait between tries. Tests pass a fast one. */
9
+ sleep?: (ms: number) => Promise<void>;
10
+ }
11
+ /** Send one email through the Resend API. The same Idempotency-Key on every try, so a retry never sends it twice. */
12
+ export declare function resendSender(s: ResendSettings): (mail: Mail) => Promise<void>;
13
+ //# sourceMappingURL=resend.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resend.d.ts","sourceRoot":"","sources":["../../src/mail/resend.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,aAAa,CAAC;AAKxC,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,6CAA6C;IAC7C,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,OAAO,KAAK,CAAC;IACpB,iDAAiD;IACjD,KAAK,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACvC;AAaD,qHAAqH;AACrH,wBAAgB,YAAY,CAAC,CAAC,EAAE,cAAc,GAAG,CAAC,IAAI,EAAE,IAAI,KAAK,OAAO,CAAC,IAAI,CAAC,CAkB7E"}
@@ -0,0 +1,34 @@
1
+ import { randomUUID } from "node:crypto";
2
+ const API = "https://api.resend.com/emails";
3
+ const TRIES = 4;
4
+ const pause = (ms) => new Promise((r) => setTimeout(r, ms));
5
+ /** 429 and 5xx are worth another try, except a used-up daily or monthly quota. */
6
+ const retryable = (status, body) => (status === 429 && !/quota_exceeded/.test(body)) || status >= 500;
7
+ /** Seconds from Retry-After, else 1 s, 2 s, 4 s. At most 30 s. */
8
+ const waitMs = (res, attempt) => Math.min(30_000, Number(res?.headers.get("retry-after")) * 1000 || 1000 * 2 ** attempt);
9
+ const payload = (s, m) => JSON.stringify({ from: s.from, ...(s.replyTo ? { reply_to: s.replyTo } : {}), to: [m.to], subject: m.subject, html: m.html, text: m.text, tags: [{ name: "kind", value: m.kind }], ...(m.headers ? { headers: m.headers } : {}) });
10
+ /** Send one email through the Resend API. The same Idempotency-Key on every try, so a retry never sends it twice. */
11
+ export function resendSender(s) {
12
+ const sleep = s.sleep ?? pause;
13
+ return async (mail) => {
14
+ const headers = { Authorization: `Bearer ${s.apiKey}`, "Content-Type": "application/json", "Idempotency-Key": randomUUID() };
15
+ let problem = "";
16
+ for (let attempt = 0; attempt < TRIES; attempt++) {
17
+ const res = await s.fetch(API, { method: "POST", headers, body: payload(s, mail), signal: AbortSignal.timeout(15_000) }).catch((err) => {
18
+ problem = err instanceof Error ? err.message : String(err);
19
+ return null;
20
+ });
21
+ if (res?.ok)
22
+ return;
23
+ const body = res ? await res.text().catch(() => "") : "";
24
+ if (res)
25
+ problem = `Resend answered ${res.status}: ${body.slice(0, 300)}`;
26
+ if (res && !retryable(res.status, body))
27
+ break;
28
+ if (attempt < TRIES - 1)
29
+ await sleep(waitMs(res, attempt));
30
+ }
31
+ throw new Error(problem);
32
+ };
33
+ }
34
+ //# sourceMappingURL=resend.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resend.js","sourceRoot":"","sources":["../../src/mail/resend.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import type { EmailContent } from "./content.js";
2
+ export declare const SAMPLES: Record<string, EmailContent>;
3
+ //# sourceMappingURL=samples.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"samples.d.ts","sourceRoot":"","sources":["../../src/mail/samples.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AA8CjD,eAAO,MAAM,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CAqBhD,CAAC"}
@@ -0,0 +1,63 @@
1
+ import * as emails from "./emails.js";
2
+ import { digest } from "./emails-digest.js";
3
+ import { projectBack, projectQuiet } from "./emails-health.js";
4
+ import * as security from "./emails-security.js";
5
+ import { orgDeleted, roleChanged, teamChanges } from "./emails-team.js";
6
+ // Sample data for every email, for the preview command and the template tests.
7
+ const APP = "https://app.wizelens.com/_wizelens";
8
+ const LINK = `${APP}/#/verify-email?token=sample-token`;
9
+ const STOP = `${APP}/api/mail/unsubscribe?token=sample`;
10
+ const AT = Date.UTC(2026, 9, 18, 14, 30);
11
+ const LINKS = { account: `${APP}/#/account`, forgot: `${APP}/#/forgot-password` };
12
+ const notice = (state) => ({
13
+ rule: "Checkout errors",
14
+ project: "shop",
15
+ state,
16
+ message: state === "fired" ? "Error rate is 7.4% (above 5)" : "Error rate is 1.2% (no longer above 5)",
17
+ value: state === "fired" ? 7.4 : 1.2,
18
+ threshold: 5,
19
+ timeMs: AT,
20
+ url: `${APP}/#/alerts?project=p1`,
21
+ });
22
+ const h = (value, previous) => ({ value, previous, change: ((value - previous) / previous) * 100, series: [] });
23
+ const WEEK = {
24
+ empty: false,
25
+ requests: h(48_210, 41_900),
26
+ errorRate: h(1.8, 1.1),
27
+ avgMs: h(132, 140),
28
+ sqlQueries: h(310_442, 306_100),
29
+ p95Ms: 412,
30
+ errors: [
31
+ { type: "TimeoutError", message: "payments-api did not answer in 5000 ms", count: 214 },
32
+ { type: "TypeError", message: "Cannot read properties of null (reading 'total')", count: 37 },
33
+ ],
34
+ slowest: [
35
+ { endpoint: "POST /checkout", p95Ms: 1840, count: 3120 },
36
+ { endpoint: "GET /orders/:id", p95Ms: 620, count: 9811 },
37
+ ],
38
+ insights: [{ title: "N+1 query: SELECT * FROM order_items WHERE order_id = ?", occurrences: 9811 }],
39
+ alertsFired: 1,
40
+ };
41
+ export const SAMPLES = {
42
+ "verify-email": emails.verifyEmail(LINK),
43
+ "reset-password": emails.resetPassword(LINK),
44
+ "change-email": emails.changeEmail("new@acme.io", LINK),
45
+ "delete-account": emails.deleteAccount(LINK),
46
+ invite: emails.invite("Olive", "Acme", "member", LINK),
47
+ "alert-fired": emails.alert(notice("fired")),
48
+ "alert-resolved": emails.alert(notice("resolved")),
49
+ "security-password": security.passwordChanged(LINKS, AT),
50
+ "security-email": security.emailChanged(LINKS, "olive@new.io", AT),
51
+ "security-2fa-off": security.twoFactorChanged(LINKS, false, AT),
52
+ "security-passkey": security.passkeyChanged(LINKS, true, "MacBook Touch ID", AT),
53
+ "security-backup-codes": security.backupCodesMade(LINKS, AT),
54
+ "security-new-sign-in": security.newSignIn(LINKS, "Chrome on Windows", "203.0.113.7", AT),
55
+ "team-changes": teamChanges("Acme", [{ text: "Vic (vic@acme.io) made an ingest key for shop.", timeMs: AT }, { text: "Vic (vic@acme.io) invited ann@acme.io as member.", timeMs: AT + 60_000 }], `${APP}/#/team`, STOP),
56
+ "team-role": roleChanged("Acme", "admin", `${APP}/#/team`, STOP),
57
+ "team-org-deleted": orgDeleted("Acme", "Olive (olive@acme.io)", `${APP}/`),
58
+ "health-quiet": projectQuiet("shop", "Acme", AT, AT + 3 * 3_600_000, `${APP}/#/`, STOP),
59
+ "health-back": projectBack("shop", "Acme", 5 * 3_600_000, `${APP}/#/`, STOP),
60
+ "digest-weekly": digest("shop", "Acme", "weekly", WEEK, { project: `${APP}/#/`, stop: STOP }),
61
+ "digest-daily": digest("shop", "Acme", "daily", { ...WEEK, alertsFired: 0, errorRate: h(0.9, 1.4), errors: [] }, { project: `${APP}/#/`, stop: STOP }),
62
+ };
63
+ //# sourceMappingURL=samples.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"samples.js","sourceRoot":"","sources":["../../src/mail/samples.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ import type { Ctx } from "../app.js";
2
+ /** GET shows a button (mail scanners open links, so a GET changes nothing). POST turns the mail off; it is also the RFC 8058 one-click call. */
3
+ export declare function stopRoute(ctx: Ctx): Promise<void>;
4
+ //# sourceMappingURL=stop-route.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stop-route.d.ts","sourceRoot":"","sources":["../../src/mail/stop-route.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,WAAW,CAAC;AA0BrC,gJAAgJ;AAChJ,wBAAsB,SAAS,CAAC,GAAG,EAAE,GAAG,GAAG,OAAO,CAAC,IAAI,CAAC,CAavD"}
@@ -0,0 +1,41 @@
1
+ import { readRawBody } from "../http/http.js";
2
+ import { applyStop } from "../repo/mail-prefs.js";
3
+ import { theme } from "./theme.js";
4
+ import { readStop } from "./unsubscribe.js";
5
+ const escape = (s) => s.replace(/[&<>"']/g, (c) => `&#${c.charCodeAt(0)};`);
6
+ async function labelOf(ctx, scope) {
7
+ if (scope === "team")
8
+ return "emails about team changes";
9
+ if (scope === "health")
10
+ return "emails when a project goes quiet";
11
+ if (scope === "digests")
12
+ return "every digest";
13
+ const [p] = await ctx.app.db.query("SELECT name FROM projects WHERE id = $1", [scope.projectId]);
14
+ return `the ${scope.period} digest for ${p?.name ?? "this project"}`;
15
+ }
16
+ function page(ctx, status, title, body) {
17
+ const html = `<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><meta name="robots" content="noindex">
18
+ <title>${escape(title)}</title><style>body{margin:0;padding:48px 16px;background:${theme.page};font-family:${theme.sans};color:${theme.body}}
19
+ main{max-width:480px;margin:0 auto;background:${theme.card};border:1px solid ${theme.line};border-radius:${theme.radius};padding:32px}
20
+ h1{margin:0 0 12px;font-size:24px;letter-spacing:-0.02em;color:${theme.ink}}button{background:${theme.accent};color:#fff;border:0;border-radius:8px;padding:12px 20px;font:600 15px ${theme.sans};cursor:pointer}
21
+ a{color:${theme.accent}}</style></head><body><main><h1>${escape(title)}</h1>${body}</main></body></html>`;
22
+ ctx.res.writeHead(status, { "Content-Type": "text/html; charset=utf-8", "Cache-Control": "no-store", "Content-Security-Policy": "default-src 'none'; style-src 'unsafe-inline'; form-action 'self'" });
23
+ ctx.res.end(html);
24
+ }
25
+ /** GET shows a button (mail scanners open links, so a GET changes nothing). POST turns the mail off; it is also the RFC 8058 one-click call. */
26
+ export async function stopRoute(ctx) {
27
+ const token = ctx.url.searchParams.get("token") ?? "";
28
+ const stop = readStop(ctx.app.secrets, token);
29
+ if (!stop)
30
+ return page(ctx, 400, "This link does not work.", "<p>Change your emails on the Account page in WizeLens instead.</p>");
31
+ const label = await labelOf(ctx, stop.scope);
32
+ const account = `${ctx.app.baseUrl()}/_wizelens/#/account`;
33
+ if (ctx.req.method !== "POST") {
34
+ const form = `<form method="post" action="?token=${escape(encodeURIComponent(token))}"><button type="submit">Stop ${escape(label)}</button></form>`;
35
+ return page(ctx, 200, "Stop these emails?", `<p>You will not get ${escape(label)} any more.</p>${form}`);
36
+ }
37
+ await readRawBody(ctx.req);
38
+ await applyStop(ctx.app.db, stop.userId, stop.scope);
39
+ page(ctx, 200, "Done.", `<p>You will not get ${escape(label)} any more. Turn it back on any time on <a href="${escape(account)}">your Account page</a>.</p>`);
40
+ }
41
+ //# sourceMappingURL=stop-route.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stop-route.js","sourceRoot":"","sources":["../../src/mail/stop-route.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ export interface SvixHeaders {
2
+ id: string | undefined;
3
+ timestamp: string | undefined;
4
+ signature: string | undefined;
5
+ }
6
+ /** True when a webhook body was signed with this `whsec_` secret (Svix, used by Resend) within the last 5 minutes. */
7
+ export declare function verifySvix(secret: string, h: SvixHeaders, rawBody: string, nowMs: number): boolean;
8
+ //# sourceMappingURL=svix.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"svix.d.ts","sourceRoot":"","sources":["../../src/mail/svix.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,MAAM,GAAG,SAAS,CAAC;IACvB,SAAS,EAAE,MAAM,GAAG,SAAS,CAAC;IAC9B,SAAS,EAAE,MAAM,GAAG,SAAS,CAAC;CAC/B;AAED,sHAAsH;AACtH,wBAAgB,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,EAAE,WAAW,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAUlG"}
@@ -0,0 +1,17 @@
1
+ import { createHmac, timingSafeEqual } from "node:crypto";
2
+ const TOLERANCE_S = 5 * 60;
3
+ /** True when a webhook body was signed with this `whsec_` secret (Svix, used by Resend) within the last 5 minutes. */
4
+ export function verifySvix(secret, h, rawBody, nowMs) {
5
+ if (!h.id || !h.timestamp || !h.signature || !/^\d+$/.test(h.timestamp))
6
+ return false;
7
+ if (Math.abs(nowMs / 1000 - Number(h.timestamp)) > TOLERANCE_S)
8
+ return false;
9
+ const key = Buffer.from(secret.replace(/^whsec_/, ""), "base64");
10
+ const expected = createHmac("sha256", key).update(`${h.id}.${h.timestamp}.${rawBody}`).digest();
11
+ return h.signature.split(" ").some((entry) => {
12
+ const [version, sig = ""] = entry.split(",");
13
+ const given = Buffer.from(sig, "base64");
14
+ return version === "v1" && given.length === expected.length && timingSafeEqual(given, expected);
15
+ });
16
+ }
17
+ //# sourceMappingURL=svix.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"svix.js","sourceRoot":"","sources":["../../src/mail/svix.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAE1D,MAAM,WAAW,GAAG,CAAC,GAAG,EAAE,CAAC;AAQ3B,sHAAsH;AACtH,MAAM,UAAU,UAAU,CAAC,MAAc,EAAE,CAAc,EAAE,OAAe,EAAE,KAAa;IACvF,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC,SAAS,IAAI,CAAC,CAAC,CAAC,SAAS,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC;QAAE,OAAO,KAAK,CAAC;IACtF,IAAI,IAAI,CAAC,GAAG,CAAC,KAAK,GAAG,IAAI,GAAG,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,GAAG,WAAW;QAAE,OAAO,KAAK,CAAC;IAC7E,MAAM,GAAG,GAAG,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,SAAS,EAAE,EAAE,CAAC,EAAE,QAAQ,CAAC,CAAC;IACjE,MAAM,QAAQ,GAAG,UAAU,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,SAAS,IAAI,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC;IAChG,OAAO,CAAC,CAAC,SAAS,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE;QAC3C,MAAM,CAAC,OAAO,EAAE,GAAG,GAAG,EAAE,CAAC,GAAG,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QAC7C,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC;QACzC,OAAO,OAAO,KAAK,IAAI,IAAI,KAAK,CAAC,MAAM,KAAK,QAAQ,CAAC,MAAM,IAAI,eAAe,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC;IAClG,CAAC,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,12 @@
1
+ import type { Notice } from "../alerts/notify.js";
2
+ import type { EmailContent } from "./content.js";
3
+ import type { Mail } from "./mailer.js";
4
+ /** One email for one address: the layout as HTML, and the same content as plain text. */
5
+ export declare function renderMail(to: string, content: EmailContent): Promise<Mail>;
6
+ export declare const verifyEmailMail: (to: string, url: string) => Promise<Mail>;
7
+ export declare const resetPasswordMail: (to: string, url: string) => Promise<Mail>;
8
+ export declare const changeEmailMail: (to: string, newEmail: string, url: string) => Promise<Mail>;
9
+ export declare const deleteAccountMail: (to: string, url: string) => Promise<Mail>;
10
+ export declare const inviteMail: (to: string, inviter: string, org: string, role: string, url: string) => Promise<Mail>;
11
+ export declare const alertMail: (to: string, notice: Notice) => Promise<Mail>;
12
+ //# sourceMappingURL=templates.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"templates.d.ts","sourceRoot":"","sources":["../../src/mail/templates.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAClD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAGjD,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,aAAa,CAAC;AAGxC,yFAAyF;AACzF,wBAAsB,UAAU,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAIjF;AAED,eAAO,MAAM,eAAe,OAAQ,MAAM,OAAO,MAAM,kBAA4C,CAAC;AACpG,eAAO,MAAM,iBAAiB,OAAQ,MAAM,OAAO,MAAM,kBAA8C,CAAC;AACxG,eAAO,MAAM,eAAe,OAAQ,MAAM,YAAY,MAAM,OAAO,MAAM,kBAAsD,CAAC;AAChI,eAAO,MAAM,iBAAiB,OAAQ,MAAM,OAAO,MAAM,kBAA8C,CAAC;AACxG,eAAO,MAAM,UAAU,OAAQ,MAAM,WAAW,MAAM,OAAO,MAAM,QAAQ,MAAM,OAAO,MAAM,kBAA2D,CAAC;AAC1J,eAAO,MAAM,SAAS,OAAQ,MAAM,UAAU,MAAM,kBAAyC,CAAC"}
@@ -0,0 +1,18 @@
1
+ import { render } from "@react-email/components";
2
+ import { createElement } from "react";
3
+ import * as emails from "./emails.js";
4
+ import { EmailLayout } from "./layout.js";
5
+ import { textOf } from "./text.js";
6
+ /** One email for one address: the layout as HTML, and the same content as plain text. */
7
+ export async function renderMail(to, content) {
8
+ const html = await render(createElement(EmailLayout, { content }));
9
+ const headers = content.unsubscribe && { "List-Unsubscribe": `<${content.unsubscribe.url}>`, "List-Unsubscribe-Post": "List-Unsubscribe=One-Click" };
10
+ return { kind: content.kind, to, subject: content.subject, html, text: textOf(content), ...(headers ? { headers } : {}) };
11
+ }
12
+ export const verifyEmailMail = (to, url) => renderMail(to, emails.verifyEmail(url));
13
+ export const resetPasswordMail = (to, url) => renderMail(to, emails.resetPassword(url));
14
+ export const changeEmailMail = (to, newEmail, url) => renderMail(to, emails.changeEmail(newEmail, url));
15
+ export const deleteAccountMail = (to, url) => renderMail(to, emails.deleteAccount(url));
16
+ export const inviteMail = (to, inviter, org, role, url) => renderMail(to, emails.invite(inviter, org, role, url));
17
+ export const alertMail = (to, notice) => renderMail(to, emails.alert(notice));
18
+ //# sourceMappingURL=templates.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"templates.js","sourceRoot":"","sources":["../../src/mail/templates.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAC;AACjD,OAAO,EAAE,aAAa,EAAE,MAAM,OAAO,CAAC;AAGtC,OAAO,KAAK,MAAM,MAAM,aAAa,CAAC;AACtC,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE1C,OAAO,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AAEnC,yFAAyF;AACzF,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,EAAU,EAAE,OAAqB;IAChE,MAAM,IAAI,GAAG,MAAM,MAAM,CAAC,aAAa,CAAC,WAAW,EAAE,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC;IACnE,MAAM,OAAO,GAAG,OAAO,CAAC,WAAW,IAAI,EAAE,kBAAkB,EAAE,IAAI,OAAO,CAAC,WAAW,CAAC,GAAG,GAAG,EAAE,uBAAuB,EAAE,4BAA4B,EAAE,CAAC;IACrJ,OAAO,EAAE,IAAI,EAAE,OAAO,CAAC,IAAI,EAAE,EAAE,EAAE,OAAO,EAAE,OAAO,CAAC,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,CAAC,OAAO,CAAC,EAAE,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC;AAC5H,CAAC;AAED,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,EAAU,EAAE,GAAW,EAAE,EAAE,CAAC,UAAU,CAAC,EAAE,EAAE,MAAM,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC;AACpG,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,EAAU,EAAE,GAAW,EAAE,EAAE,CAAC,UAAU,CAAC,EAAE,EAAE,MAAM,CAAC,aAAa,CAAC,GAAG,CAAC,CAAC,CAAC;AACxG,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,EAAU,EAAE,QAAgB,EAAE,GAAW,EAAE,EAAE,CAAC,UAAU,CAAC,EAAE,EAAE,MAAM,CAAC,WAAW,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC,CAAC;AAChI,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,EAAU,EAAE,GAAW,EAAE,EAAE,CAAC,UAAU,CAAC,EAAE,EAAE,MAAM,CAAC,aAAa,CAAC,GAAG,CAAC,CAAC,CAAC;AACxG,MAAM,CAAC,MAAM,UAAU,GAAG,CAAC,EAAU,EAAE,OAAe,EAAE,GAAW,EAAE,IAAY,EAAE,GAAW,EAAE,EAAE,CAAC,UAAU,CAAC,EAAE,EAAE,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,GAAG,CAAC,CAAC,CAAC;AAC1J,MAAM,CAAC,MAAM,SAAS,GAAG,CAAC,EAAU,EAAE,MAAc,EAAE,EAAE,CAAC,UAAU,CAAC,EAAE,EAAE,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC"}
@@ -0,0 +1,4 @@
1
+ import type { EmailContent } from "./content.js";
2
+ /** The plain-text version. Each link sits on its own line, so mail apps keep it whole. */
3
+ export declare function textOf(c: EmailContent): string;
4
+ //# sourceMappingURL=text.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"text.d.ts","sourceRoot":"","sources":["../../src/mail/text.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAS,YAAY,EAAE,MAAM,cAAc,CAAC;AAUxD,0FAA0F;AAC1F,wBAAgB,MAAM,CAAC,CAAC,EAAE,YAAY,GAAG,MAAM,CAI9C"}
@@ -0,0 +1,17 @@
1
+ import { theme } from "./theme.js";
2
+ const block = (b) => {
3
+ if (b.kind === "facts")
4
+ return b.rows.map((r) => `${r.label}: ${r.value}`).join("\n");
5
+ if (b.kind === "stats")
6
+ return b.items.map((s) => `${s.label}: ${s.value}${s.change ? ` (${s.change})` : ""}`).join("\n");
7
+ if (b.kind === "list")
8
+ return [b.title, ...b.items.map((i) => `- ${i.text}${i.meta ? ` (${i.meta})` : ""}`)].join("\n");
9
+ return b.text;
10
+ };
11
+ /** The plain-text version. Each link sits on its own line, so mail apps keep it whole. */
12
+ export function textOf(c) {
13
+ const action = c.action ? [`${c.action.label}:`, c.action.url, ""] : [];
14
+ const stop = c.unsubscribe ? [`${c.unsubscribe.label}:`, c.unsubscribe.url] : [];
15
+ return [c.heading, "", ...c.blocks.flatMap((b) => [block(b), ""]), ...action, "--", c.footer, ...stop, theme.company, ""].join("\n");
16
+ }
17
+ //# sourceMappingURL=text.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"text.js","sourceRoot":"","sources":["../../src/mail/text.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAEnC,MAAM,KAAK,GAAG,CAAC,CAAQ,EAAU,EAAE;IACjC,IAAI,CAAC,CAAC,IAAI,KAAK,OAAO;QAAE,OAAO,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,CAAC,KAAK,KAAK,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACtF,IAAI,CAAC,CAAC,IAAI,KAAK,OAAO;QAAE,OAAO,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,CAAC,KAAK,KAAK,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC1H,IAAI,CAAC,CAAC,IAAI,KAAK,MAAM;QAAE,OAAO,CAAC,CAAC,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACxH,OAAO,CAAC,CAAC,IAAI,CAAC;AAChB,CAAC,CAAC;AAEF,0FAA0F;AAC1F,MAAM,UAAU,MAAM,CAAC,CAAe;IACpC,MAAM,MAAM,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,KAAK,GAAG,EAAE,CAAC,CAAC,MAAM,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IACxE,MAAM,IAAI,GAAG,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,WAAW,CAAC,KAAK,GAAG,EAAE,CAAC,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IACjF,OAAO,CAAC,CAAC,CAAC,OAAO,EAAE,EAAE,EAAE,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,GAAG,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,GAAG,IAAI,EAAE,KAAK,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACvI,CAAC"}
@@ -0,0 +1,32 @@
1
+ export declare const theme: {
2
+ readonly page: "#f6f7f9";
3
+ readonly card: "#ffffff";
4
+ readonly line: "#e5e8ee";
5
+ readonly ink: "#1f1f1f";
6
+ readonly body: "#3d4350";
7
+ readonly muted: "#586273";
8
+ readonly accent: "#2563eb";
9
+ readonly accentText: "#ffffff";
10
+ readonly factsBg: "#f6f7f9";
11
+ readonly tone: {
12
+ readonly neutral: "#2563eb";
13
+ readonly good: "#22c55e";
14
+ readonly warn: "#f4b400";
15
+ readonly bad: "#e63946";
16
+ };
17
+ readonly sans: 'Inter, ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif';
18
+ readonly mono: 'ui-monospace, SFMono-Regular, Menlo, Consolas, "Liberation Mono", monospace';
19
+ readonly radius: "12px";
20
+ readonly width: 560;
21
+ /** The mark with its center dot in the tone color. The site build makes these (site/scripts/og/build.mjs). */
22
+ readonly markUrl: {
23
+ readonly neutral: "https://wizelens.com/brand/mail-neutral.png";
24
+ readonly good: "https://wizelens.com/brand/mail-good.png";
25
+ readonly warn: "https://wizelens.com/brand/mail-warn.png";
26
+ readonly bad: "https://wizelens.com/brand/mail-bad.png";
27
+ };
28
+ readonly homeUrl: "https://wizelens.com";
29
+ readonly company: "WizeLens by WizeWorks LLC, Visalia, California";
30
+ };
31
+ export type Tone = keyof typeof theme.tone;
32
+ //# sourceMappingURL=theme.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"theme.d.ts","sourceRoot":"","sources":["../../src/mail/theme.ts"],"names":[],"mappings":"AACA,eAAO,MAAM,KAAK;aAChB,IAAI,EAAE,SAAS;aACf,IAAI,EAAE,SAAS;aACf,IAAI,EAAE,SAAS;aACf,GAAG,EAAE,SAAS;aACd,IAAI,EAAE,SAAS;aACf,KAAK,EAAE,SAAS;aAChB,MAAM,EAAE,SAAS;aACjB,UAAU,EAAE,SAAS;aACrB,OAAO,EAAE,SAAS;aAClB,IAAI;iBAAI,OAAO,EAAE,SAAS;iBAAE,IAAI,EAAE,SAAS;iBAAE,IAAI,EAAE,SAAS;iBAAE,GAAG,EAAE,SAAS;;aAC5E,IAAI,EAAE,kGAAkG;aACxG,IAAI,EAAE,6EAA6E;aACnF,MAAM,EAAE,MAAM;aACd,KAAK,EAAE,GAAG;IACV,8GAA8G;aAC9G,OAAO;iBACL,OAAO,EAAE,6CAA6C;iBACtD,IAAI,EAAE,0CAA0C;iBAChD,IAAI,EAAE,0CAA0C;iBAChD,GAAG,EAAE,yCAAyC;;aAEhD,OAAO,EAAE,sBAAsB;aAC/B,OAAO,EAAE,gDAAgD;CACjD,CAAC;AAEX,MAAM,MAAM,IAAI,GAAG,MAAM,OAAO,KAAK,CAAC,IAAI,CAAC"}
@@ -0,0 +1,27 @@
1
+ // The look of every email, from the WizeLens brand guide. Solid colors only. A new design changes this file and layout.tsx.
2
+ export const theme = {
3
+ page: "#f6f7f9",
4
+ card: "#ffffff",
5
+ line: "#e5e8ee",
6
+ ink: "#1f1f1f",
7
+ body: "#3d4350",
8
+ muted: "#586273",
9
+ accent: "#2563eb",
10
+ accentText: "#ffffff",
11
+ factsBg: "#f6f7f9",
12
+ tone: { neutral: "#2563eb", good: "#22c55e", warn: "#f4b400", bad: "#e63946" },
13
+ sans: 'Inter, ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif',
14
+ mono: 'ui-monospace, SFMono-Regular, Menlo, Consolas, "Liberation Mono", monospace',
15
+ radius: "12px",
16
+ width: 560,
17
+ /** The mark with its center dot in the tone color. The site build makes these (site/scripts/og/build.mjs). */
18
+ markUrl: {
19
+ neutral: "https://wizelens.com/brand/mail-neutral.png",
20
+ good: "https://wizelens.com/brand/mail-good.png",
21
+ warn: "https://wizelens.com/brand/mail-warn.png",
22
+ bad: "https://wizelens.com/brand/mail-bad.png",
23
+ },
24
+ homeUrl: "https://wizelens.com",
25
+ company: "WizeLens by WizeWorks LLC, Visalia, California",
26
+ };
27
+ //# sourceMappingURL=theme.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"theme.js","sourceRoot":"","sources":["../../src/mail/theme.ts"],"names":[],"mappings":"AAAA,4HAA4H;AAC5H,MAAM,CAAC,MAAM,KAAK,GAAG;IACnB,IAAI,EAAE,SAAS;IACf,IAAI,EAAE,SAAS;IACf,IAAI,EAAE,SAAS;IACf,GAAG,EAAE,SAAS;IACd,IAAI,EAAE,SAAS;IACf,KAAK,EAAE,SAAS;IAChB,MAAM,EAAE,SAAS;IACjB,UAAU,EAAE,SAAS;IACrB,OAAO,EAAE,SAAS;IAClB,IAAI,EAAE,EAAE,OAAO,EAAE,SAAS,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,SAAS,EAAE,GAAG,EAAE,SAAS,EAAE;IAC9E,IAAI,EAAE,kGAAkG;IACxG,IAAI,EAAE,6EAA6E;IACnF,MAAM,EAAE,MAAM;IACd,KAAK,EAAE,GAAG;IACV,8GAA8G;IAC9G,OAAO,EAAE;QACP,OAAO,EAAE,6CAA6C;QACtD,IAAI,EAAE,0CAA0C;QAChD,IAAI,EAAE,0CAA0C;QAChD,GAAG,EAAE,yCAAyC;KAC/C;IACD,OAAO,EAAE,sBAAsB;IAC/B,OAAO,EAAE,gDAAgD;CACjD,CAAC"}
@@ -0,0 +1,14 @@
1
+ import type { CloudApp } from "../app.js";
2
+ import { type StopScope } from "../repo/mail-prefs.js";
3
+ export declare const UNSUBSCRIBE_PATH = "api/mail/unsubscribe";
4
+ export declare function signStop(secret: string, userId: string, scope: StopScope): string;
5
+ /** Who and what a stop token names, when any of the secrets signed it. */
6
+ export declare function readStop(secrets: readonly {
7
+ value: string;
8
+ }[], token: string): {
9
+ userId: string;
10
+ scope: StopScope;
11
+ } | null;
12
+ /** The stop link for one person and one kind of mail. */
13
+ export declare const stopUrl: (app: CloudApp, userId: string, scope: StopScope) => string;
14
+ //# sourceMappingURL=unsubscribe.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"unsubscribe.d.ts","sourceRoot":"","sources":["../../src/mail/unsubscribe.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAqC,KAAK,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAE1F,eAAO,MAAM,gBAAgB,yBAAyB,CAAC;AAcvD,wBAAgB,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,GAAG,MAAM,CAGjF;AAED,0EAA0E;AAC1E,wBAAgB,QAAQ,CAAC,OAAO,EAAE,SAAS;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,EAAE,EAAE,KAAK,EAAE,MAAM,GAAG;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,SAAS,CAAA;CAAE,GAAG,IAAI,CAe1H;AAED,yDAAyD;AACzD,eAAO,MAAM,OAAO,QAAS,QAAQ,UAAU,MAAM,SAAS,SAAS,KAAG,MACgC,CAAC"}
@@ -0,0 +1,39 @@
1
+ import { createHmac, timingSafeEqual } from "node:crypto";
2
+ import { DIGEST_PERIODS } from "../repo/mail-prefs.js";
3
+ export const UNSUBSCRIBE_PATH = "api/mail/unsubscribe";
4
+ // Stop links carry who and what, signed with the auth secret. They never expire: a stop link must keep working.
5
+ const keyOf = (secret) => createHmac("sha256", secret).update("wizelens-unsubscribe").digest();
6
+ const mac = (secret, payload) => createHmac("sha256", keyOf(secret)).update(payload).digest();
7
+ const scopeText = (s) => (typeof s === "object" ? `digest:${s.projectId}:${s.period}` : s);
8
+ function scopeOf(text) {
9
+ if (text === "team" || text === "health" || text === "digests")
10
+ return text;
11
+ const m = /^digest:([^:]+):([a-z]+)$/.exec(text);
12
+ return m && DIGEST_PERIODS.includes(m[2]) ? { projectId: m[1], period: m[2] } : null;
13
+ }
14
+ export function signStop(secret, userId, scope) {
15
+ const payload = Buffer.from(JSON.stringify([userId, scopeText(scope)])).toString("base64url");
16
+ return `${payload}.${mac(secret, payload).toString("base64url")}`;
17
+ }
18
+ /** Who and what a stop token names, when any of the secrets signed it. */
19
+ export function readStop(secrets, token) {
20
+ const [payload = "", sig = ""] = token.split(".");
21
+ const given = Buffer.from(sig, "base64url");
22
+ const ok = secrets.some((s) => {
23
+ const want = mac(s.value, payload);
24
+ return given.length === want.length && timingSafeEqual(given, want);
25
+ });
26
+ if (!ok)
27
+ return null;
28
+ try {
29
+ const [userId, scope] = JSON.parse(Buffer.from(payload, "base64url").toString("utf8"));
30
+ const parsed = typeof scope === "string" ? scopeOf(scope) : null;
31
+ return typeof userId === "string" && parsed ? { userId, scope: parsed } : null;
32
+ }
33
+ catch {
34
+ return null;
35
+ }
36
+ }
37
+ /** The stop link for one person and one kind of mail. */
38
+ export const stopUrl = (app, userId, scope) => `${app.baseUrl()}/_wizelens/${UNSUBSCRIBE_PATH}?token=${signStop(app.secrets[0].value, userId, scope)}`;
39
+ //# sourceMappingURL=unsubscribe.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"unsubscribe.js","sourceRoot":"","sources":["../../src/mail/unsubscribe.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ import type { Ctx } from "../app.js";
2
+ export declare const RESEND_WEBHOOK_PATH = "api/mail/resend";
3
+ /** POST from Resend's webhook, signed with the endpoint's secret. Hard bounces and complaints go on the suppression list. */
4
+ export declare function resendWebhook(ctx: Ctx): Promise<void>;
5
+ //# sourceMappingURL=webhook.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"webhook.d.ts","sourceRoot":"","sources":["../../src/mail/webhook.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,WAAW,CAAC;AAKrC,eAAO,MAAM,mBAAmB,oBAAoB,CAAC;AAoBrD,6HAA6H;AAC7H,wBAAsB,aAAa,CAAC,GAAG,EAAE,GAAG,GAAG,OAAO,CAAC,IAAI,CAAC,CAgB3D"}