@cosmicdrift/kumiko-bundled-features 0.392.2 → 0.394.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (380) hide show
  1. package/dist/account-security/feature.d.ts +2 -0
  2. package/dist/account-security/feature.js +3 -1
  3. package/dist/audit/event-classification.js +1 -0
  4. package/dist/auth-email-password/handlers/change-password.write.d.ts +1 -1
  5. package/dist/auth-email-password/handlers/confirm-account-unlock.write.d.ts +1 -1
  6. package/dist/auth-email-password/handlers/invite-accept-with-login.write.d.ts +1 -1
  7. package/dist/auth-email-password/handlers/invite-accept.write.d.ts +1 -1
  8. package/dist/auth-email-password/handlers/invite-create.write.d.ts +2 -2
  9. package/dist/auth-email-password/handlers/invite-signup-complete.write.d.ts +1 -1
  10. package/dist/auth-email-password/handlers/login.write.d.ts +1 -1
  11. package/dist/auth-email-password/handlers/logout.write.d.ts +1 -1
  12. package/dist/auth-email-password/handlers/request-account-unlock.write.d.ts +1 -1
  13. package/dist/auth-email-password/handlers/request-email-verification.write.d.ts +1 -1
  14. package/dist/auth-email-password/handlers/request-password-reset.write.d.ts +1 -1
  15. package/dist/auth-email-password/handlers/reset-password.write.d.ts +1 -1
  16. package/dist/auth-email-password/handlers/signup-confirm.write.d.ts +1 -1
  17. package/dist/auth-email-password/handlers/signup-request.write.d.ts +1 -1
  18. package/dist/auth-email-password/handlers/switch-tenant-mfa-gate.write.d.ts +1 -1
  19. package/dist/auth-email-password/handlers/token-request-handler.d.ts +1 -1
  20. package/dist/auth-email-password/handlers/verify-email.write.d.ts +1 -1
  21. package/dist/auth-email-password/web/auth-client.d.ts +3 -7
  22. package/dist/auth-email-password/web/auth-client.js +3 -24
  23. package/dist/auth-email-password/web/auth-gate.d.ts +2 -4
  24. package/dist/auth-email-password/web/auth-redirect.d.ts +2 -2
  25. package/dist/auth-email-password/web/auth-redirect.js +3 -12
  26. package/dist/auth-email-password/web/index.d.ts +2 -1
  27. package/dist/auth-email-password/web/index.js +1 -0
  28. package/dist/auth-email-password/web/login-failure.d.ts +9 -0
  29. package/dist/auth-email-password/web/login-failure.js +32 -0
  30. package/dist/auth-email-password/web/login-url.d.ts +5 -0
  31. package/dist/auth-email-password/web/login-url.js +13 -0
  32. package/dist/auth-email-password/web/static-login.d.ts +42 -0
  33. package/dist/auth-email-password/web/static-login.js +122 -0
  34. package/dist/auth-mfa/handlers/disable.write.d.ts +1 -1
  35. package/dist/auth-mfa/handlers/enable-confirm-preauth.write.d.ts +1 -1
  36. package/dist/auth-mfa/handlers/enable-confirm.write.d.ts +1 -1
  37. package/dist/auth-mfa/handlers/enable-start-preauth.write.d.ts +1 -1
  38. package/dist/auth-mfa/handlers/enable-start.write.d.ts +1 -1
  39. package/dist/auth-mfa/handlers/regenerate-recovery.write.d.ts +1 -1
  40. package/dist/auth-mfa/handlers/verify.write.d.ts +1 -1
  41. package/dist/auth-mfa/web/mfa-client.js +4 -43
  42. package/dist/billing-foundation/checkout-core.d.ts +2 -1
  43. package/dist/billing-foundation/checkout-core.js +2 -11
  44. package/dist/billing-foundation/events.d.ts +1 -0
  45. package/dist/billing-foundation/events.js +2 -0
  46. package/dist/billing-foundation/feature.js +1 -1
  47. package/dist/billing-foundation/handlers/create-checkout-session.write.js +9 -2
  48. package/dist/billing-foundation/handlers/process-payment-event.write.d.ts +1 -0
  49. package/dist/billing-foundation/handlers/process-payment-event.write.js +2 -0
  50. package/dist/billing-foundation/index.d.ts +1 -1
  51. package/dist/billing-foundation/one-off-checkout-gate.d.ts +9 -0
  52. package/dist/billing-foundation/one-off-checkout-gate.js +30 -0
  53. package/dist/billing-foundation/one-off-customer.d.ts +6 -0
  54. package/dist/billing-foundation/one-off-customer.js +12 -0
  55. package/dist/billing-foundation/plan-catalog.js +1 -0
  56. package/dist/billing-foundation/types.d.ts +25 -0
  57. package/dist/billing-foundation/web/billing-plans-panel.js +1 -0
  58. package/dist/billing-foundation/web/plan-switch-confirm-dialog.js +7 -3
  59. package/dist/billing-foundation/web/plan-switch-recurring.d.ts +16 -0
  60. package/dist/billing-foundation/web/plan-switch-recurring.js +10 -0
  61. package/dist/billing-foundation/webhook-handler.js +1 -0
  62. package/dist/cap-overview/handlers/caps-usage.query.js +3 -3
  63. package/dist/cap-overview/types.d.ts +2 -0
  64. package/dist/channel-in-app/handlers/mark-all-read.write.d.ts +1 -1
  65. package/dist/channel-in-app/handlers/mark-read.write.d.ts +1 -1
  66. package/dist/compliance-profiles/handlers/set-profile.write.d.ts +1 -1
  67. package/dist/compliance-profiles/web/compliance-profile-catalog.js +8 -1
  68. package/dist/config/handlers/reset.write.d.ts +1 -1
  69. package/dist/config/handlers/set.write.d.ts +1 -1
  70. package/dist/crypto-shredding/handlers/forget-subject.write.d.ts +1 -1
  71. package/dist/delivery/handlers/resubscribe-address.write.d.ts +1 -1
  72. package/dist/delivery/handlers/resubscribe-user.write.d.ts +1 -1
  73. package/dist/delivery/handlers/set-preference.write.d.ts +1 -1
  74. package/dist/delivery/handlers/unsubscribe-address.write.d.ts +1 -1
  75. package/dist/delivery/handlers/unsubscribe-user.write.d.ts +1 -1
  76. package/dist/feature-toggles/handlers/set.write.d.ts +1 -1
  77. package/dist/file-foundation/read-file-ref-content.d.ts +2 -0
  78. package/dist/file-foundation/read-file-ref-content.js +9 -3
  79. package/dist/form-draft/handlers/discard.write.d.ts +1 -1
  80. package/dist/form-draft/handlers/save.write.d.ts +1 -1
  81. package/dist/inbound-mail-foundation/account-health.d.ts +17 -0
  82. package/dist/inbound-mail-foundation/account-health.js +101 -0
  83. package/dist/inbound-mail-foundation/account-poll-schedule.d.ts +19 -0
  84. package/dist/inbound-mail-foundation/account-poll-schedule.js +44 -0
  85. package/dist/inbound-mail-foundation/account-poll.d.ts +24 -0
  86. package/dist/inbound-mail-foundation/account-poll.js +157 -0
  87. package/dist/inbound-mail-foundation/account-record.d.ts +1 -0
  88. package/dist/inbound-mail-foundation/account-record.js +1 -0
  89. package/dist/inbound-mail-foundation/account-wake.d.ts +3 -0
  90. package/dist/inbound-mail-foundation/account-wake.js +17 -0
  91. package/dist/inbound-mail-foundation/connect-callback-verify.d.ts +10 -0
  92. package/dist/inbound-mail-foundation/connect-callback-verify.js +31 -0
  93. package/dist/inbound-mail-foundation/connect-route-responses.d.ts +13 -0
  94. package/dist/inbound-mail-foundation/connect-route-responses.js +16 -0
  95. package/dist/inbound-mail-foundation/connect-routes.js +52 -33
  96. package/dist/inbound-mail-foundation/constants.d.ts +50 -2
  97. package/dist/inbound-mail-foundation/constants.js +53 -3
  98. package/dist/inbound-mail-foundation/content-refetch.d.ts +14 -0
  99. package/dist/inbound-mail-foundation/content-refetch.js +93 -0
  100. package/dist/inbound-mail-foundation/describe-error.d.ts +1 -0
  101. package/dist/inbound-mail-foundation/describe-error.js +5 -0
  102. package/dist/inbound-mail-foundation/entities.d.ts +26 -0
  103. package/dist/inbound-mail-foundation/entities.js +43 -3
  104. package/dist/inbound-mail-foundation/events.d.ts +97 -0
  105. package/dist/inbound-mail-foundation/events.js +57 -2
  106. package/dist/inbound-mail-foundation/feature.d.ts +5 -2
  107. package/dist/inbound-mail-foundation/feature.js +122 -106
  108. package/dist/inbound-mail-foundation/file-storage-failure.d.ts +8 -0
  109. package/dist/inbound-mail-foundation/file-storage-failure.js +19 -0
  110. package/dist/inbound-mail-foundation/handlers/account-address.d.ts +16 -0
  111. package/dist/inbound-mail-foundation/handlers/account-address.js +51 -0
  112. package/dist/inbound-mail-foundation/handlers/acknowledge-quarantine.write.d.ts +8 -0
  113. package/dist/inbound-mail-foundation/handlers/acknowledge-quarantine.write.js +39 -0
  114. package/dist/inbound-mail-foundation/handlers/attach-message-content.write.d.ts +60 -0
  115. package/dist/inbound-mail-foundation/handlers/attach-message-content.write.js +128 -0
  116. package/dist/inbound-mail-foundation/handlers/connect-account.write.d.ts +3 -2
  117. package/dist/inbound-mail-foundation/handlers/connect-account.write.js +97 -56
  118. package/dist/inbound-mail-foundation/handlers/disconnect-account.write.d.ts +2 -1
  119. package/dist/inbound-mail-foundation/handlers/disconnect-account.write.js +55 -42
  120. package/dist/inbound-mail-foundation/handlers/ingest-message-prepare.d.ts +26 -0
  121. package/dist/inbound-mail-foundation/handlers/ingest-message-prepare.js +131 -0
  122. package/dist/inbound-mail-foundation/handlers/ingest-message-schema.d.ts +64 -0
  123. package/dist/inbound-mail-foundation/handlers/ingest-message-schema.js +39 -0
  124. package/dist/inbound-mail-foundation/handlers/ingest-message.write.d.ts +74 -21
  125. package/dist/inbound-mail-foundation/handlers/ingest-message.write.js +57 -134
  126. package/dist/inbound-mail-foundation/handlers/list-accounts.query.d.ts +2 -1
  127. package/dist/inbound-mail-foundation/handlers/list-accounts.query.js +60 -26
  128. package/dist/inbound-mail-foundation/handlers/list-messages.query.d.ts +2 -1
  129. package/dist/inbound-mail-foundation/handlers/list-messages.query.js +43 -29
  130. package/dist/inbound-mail-foundation/handlers/list-quarantine.query.d.ts +3 -0
  131. package/dist/inbound-mail-foundation/handlers/list-quarantine.query.js +75 -0
  132. package/dist/inbound-mail-foundation/handlers/mark-credential-stored.write.d.ts +7 -0
  133. package/dist/inbound-mail-foundation/handlers/mark-credential-stored.write.js +38 -0
  134. package/dist/inbound-mail-foundation/handlers/message-content.d.ts +70 -0
  135. package/dist/inbound-mail-foundation/handlers/message-content.js +137 -0
  136. package/dist/inbound-mail-foundation/handlers/message-content.query.d.ts +19 -0
  137. package/dist/inbound-mail-foundation/handlers/message-content.query.js +59 -0
  138. package/dist/inbound-mail-foundation/handlers/message-detail.query.d.ts +3 -0
  139. package/dist/inbound-mail-foundation/handlers/message-detail.query.js +23 -0
  140. package/dist/inbound-mail-foundation/handlers/reactivate-account.write.d.ts +8 -0
  141. package/dist/inbound-mail-foundation/handlers/reactivate-account.write.js +84 -0
  142. package/dist/inbound-mail-foundation/handlers/retry-quarantined-message.write.d.ts +8 -0
  143. package/dist/inbound-mail-foundation/handlers/retry-quarantined-message.write.js +34 -0
  144. package/dist/inbound-mail-foundation/handlers/scope-visibility.d.ts +10 -2
  145. package/dist/inbound-mail-foundation/handlers/scope-visibility.js +27 -8
  146. package/dist/inbound-mail-foundation/handlers/set-trusted-authserv-id.write.d.ts +13 -0
  147. package/dist/inbound-mail-foundation/handlers/set-trusted-authserv-id.write.js +54 -0
  148. package/dist/inbound-mail-foundation/handlers/thread-rollup.d.ts +24 -0
  149. package/dist/inbound-mail-foundation/handlers/thread-rollup.js +98 -0
  150. package/dist/inbound-mail-foundation/handlers/trusted-authserv-id.d.ts +3 -0
  151. package/dist/inbound-mail-foundation/handlers/trusted-authserv-id.js +3 -0
  152. package/dist/inbound-mail-foundation/handlers/update-account.write.d.ts +13 -3
  153. package/dist/inbound-mail-foundation/handlers/update-account.write.js +62 -46
  154. package/dist/inbound-mail-foundation/i18n.js +72 -2
  155. package/dist/inbound-mail-foundation/inbound-oauth-flow.d.ts +43 -0
  156. package/dist/inbound-mail-foundation/inbound-oauth-flow.js +1 -0
  157. package/dist/inbound-mail-foundation/index.d.ts +19 -6
  158. package/dist/inbound-mail-foundation/index.js +17 -6
  159. package/dist/inbound-mail-foundation/ingest-failures.d.ts +77 -0
  160. package/dist/inbound-mail-foundation/ingest-failures.js +79 -0
  161. package/dist/inbound-mail-foundation/ingest-payload.d.ts +7 -0
  162. package/dist/inbound-mail-foundation/ingest-payload.js +34 -0
  163. package/dist/inbound-mail-foundation/mail-roles.d.ts +21 -0
  164. package/dist/inbound-mail-foundation/mail-roles.js +83 -0
  165. package/dist/inbound-mail-foundation/normalize-inbound-message.d.ts +17 -0
  166. package/dist/inbound-mail-foundation/normalize-inbound-message.js +90 -0
  167. package/dist/inbound-mail-foundation/oauth-access-token.d.ts +3 -1
  168. package/dist/inbound-mail-foundation/oauth-access-token.js +13 -2
  169. package/dist/inbound-mail-foundation/outbound-attachment-access.d.ts +5 -0
  170. package/dist/inbound-mail-foundation/outbound-attachment-access.js +16 -0
  171. package/dist/inbound-mail-foundation/outbound-attachments.d.ts +18 -0
  172. package/dist/inbound-mail-foundation/outbound-attachments.js +77 -0
  173. package/dist/inbound-mail-foundation/outbound-mail.d.ts +15 -1
  174. package/dist/inbound-mail-foundation/outbound-mail.js +4 -0
  175. package/dist/inbound-mail-foundation/probe.js +1 -1
  176. package/dist/inbound-mail-foundation/projection.d.ts +28 -0
  177. package/dist/inbound-mail-foundation/projection.js +25 -3
  178. package/dist/inbound-mail-foundation/provider-errors.d.ts +28 -0
  179. package/dist/inbound-mail-foundation/provider-errors.js +66 -0
  180. package/dist/inbound-mail-foundation/quarantine-entities.d.ts +61 -0
  181. package/dist/inbound-mail-foundation/quarantine-entities.js +47 -0
  182. package/dist/inbound-mail-foundation/quarantine-header-import.d.ts +9 -0
  183. package/dist/inbound-mail-foundation/quarantine-header-import.js +53 -0
  184. package/dist/inbound-mail-foundation/quarantine-retry.d.ts +13 -0
  185. package/dist/inbound-mail-foundation/quarantine-retry.js +97 -0
  186. package/dist/inbound-mail-foundation/quarantine-screens.d.ts +4 -0
  187. package/dist/inbound-mail-foundation/quarantine-screens.js +57 -0
  188. package/dist/inbound-mail-foundation/quarantine.d.ts +13 -0
  189. package/dist/inbound-mail-foundation/quarantine.js +46 -0
  190. package/dist/inbound-mail-foundation/retention-job.d.ts +2 -0
  191. package/dist/inbound-mail-foundation/retention-job.js +62 -0
  192. package/dist/inbound-mail-foundation/retention-sweep.d.ts +4 -0
  193. package/dist/inbound-mail-foundation/retention-sweep.js +35 -8
  194. package/dist/inbound-mail-foundation/screens.d.ts +14 -2
  195. package/dist/inbound-mail-foundation/screens.js +217 -79
  196. package/dist/inbound-mail-foundation/send-from-account.d.ts +11 -1
  197. package/dist/inbound-mail-foundation/send-from-account.js +19 -2
  198. package/dist/inbound-mail-foundation/sender-auth.d.ts +63 -0
  199. package/dist/inbound-mail-foundation/sender-auth.js +198 -0
  200. package/dist/inbound-mail-foundation/supervisor-ingest.d.ts +43 -0
  201. package/dist/inbound-mail-foundation/supervisor-ingest.js +195 -0
  202. package/dist/inbound-mail-foundation/sync-account-now.d.ts +22 -0
  203. package/dist/inbound-mail-foundation/sync-account-now.js +29 -0
  204. package/dist/inbound-mail-foundation/sync-cursor-store.d.ts +9 -0
  205. package/dist/inbound-mail-foundation/sync-cursor-store.js +54 -0
  206. package/dist/inbound-mail-foundation/syncable-accounts.d.ts +16 -0
  207. package/dist/inbound-mail-foundation/syncable-accounts.js +79 -0
  208. package/dist/inbound-mail-foundation/tenant-destroy-hook.js +5 -1
  209. package/dist/inbound-mail-foundation/types.d.ts +61 -62
  210. package/dist/inbound-mail-foundation/types.js +0 -54
  211. package/dist/inbound-mail-foundation/wake-consumer.d.ts +2 -0
  212. package/dist/inbound-mail-foundation/wake-consumer.js +30 -0
  213. package/dist/inbound-mail-foundation/watch-catch-up.d.ts +19 -0
  214. package/dist/inbound-mail-foundation/watch-catch-up.js +33 -0
  215. package/dist/inbound-mail-foundation/watch-lease.d.ts +15 -0
  216. package/dist/inbound-mail-foundation/watch-lease.js +81 -0
  217. package/dist/inbound-mail-foundation/watch-supervisor-types.d.ts +83 -0
  218. package/dist/inbound-mail-foundation/watch-supervisor-types.js +1 -0
  219. package/dist/inbound-mail-foundation/watch-supervisor.d.ts +1 -53
  220. package/dist/inbound-mail-foundation/watch-supervisor.js +123 -290
  221. package/dist/inbound-mail-foundation/web/client-plugin.d.ts +14 -0
  222. package/dist/inbound-mail-foundation/web/client-plugin.js +40 -0
  223. package/dist/inbound-mail-foundation/web/constants.d.ts +5 -0
  224. package/dist/inbound-mail-foundation/web/constants.js +6 -0
  225. package/dist/inbound-mail-foundation/web/index.d.ts +1 -0
  226. package/dist/inbound-mail-foundation/web/index.js +1 -0
  227. package/dist/inbound-provider-imap/connect-mailbox.d.ts +28 -5
  228. package/dist/inbound-provider-imap/connect-mailbox.js +91 -41
  229. package/dist/inbound-provider-imap/connect-my-mailbox.d.ts +13 -0
  230. package/dist/inbound-provider-imap/connect-my-mailbox.js +25 -0
  231. package/dist/inbound-provider-imap/constants.d.ts +12 -1
  232. package/dist/inbound-provider-imap/constants.js +12 -1
  233. package/dist/inbound-provider-imap/credential-document.js +2 -2
  234. package/dist/inbound-provider-imap/download-batch.d.ts +6 -0
  235. package/dist/inbound-provider-imap/download-batch.js +24 -0
  236. package/dist/inbound-provider-imap/feature.d.ts +2 -2
  237. package/dist/inbound-provider-imap/feature.js +49 -32
  238. package/dist/inbound-provider-imap/fetch-content.d.ts +4 -0
  239. package/dist/inbound-provider-imap/fetch-content.js +68 -0
  240. package/dist/inbound-provider-imap/i18n.js +18 -2
  241. package/dist/inbound-provider-imap/imap-client.d.ts +27 -23
  242. package/dist/inbound-provider-imap/imap-client.js +30 -68
  243. package/dist/inbound-provider-imap/index.d.ts +2 -1
  244. package/dist/inbound-provider-imap/index.js +2 -1
  245. package/dist/inbound-provider-imap/mime-content.d.ts +11 -0
  246. package/dist/inbound-provider-imap/mime-content.js +66 -0
  247. package/dist/inbound-provider-imap/parse-message.d.ts +35 -0
  248. package/dist/inbound-provider-imap/parse-message.js +113 -0
  249. package/dist/inbound-provider-imap/reconnect-account.d.ts +17 -0
  250. package/dist/inbound-provider-imap/reconnect-account.js +18 -0
  251. package/dist/inbound-provider-imap/reconnect-mailbox.d.ts +14 -0
  252. package/dist/inbound-provider-imap/reconnect-mailbox.js +71 -0
  253. package/dist/inbound-provider-imap/reconnect-prefill.query.d.ts +3 -0
  254. package/dist/inbound-provider-imap/reconnect-prefill.query.js +34 -0
  255. package/dist/inbound-provider-imap/screens.d.ts +6 -1
  256. package/dist/inbound-provider-imap/screens.js +174 -41
  257. package/dist/inbound-provider-imap/send-mail.d.ts +2 -2
  258. package/dist/inbound-provider-imap/send-mail.js +8 -1
  259. package/dist/inbound-provider-imap/sender-auth-headers.d.ts +3 -0
  260. package/dist/inbound-provider-imap/sender-auth-headers.js +18 -0
  261. package/dist/inbound-provider-inmemory/feature.d.ts +15 -0
  262. package/dist/inbound-provider-inmemory/feature.js +57 -2
  263. package/dist/inbound-provider-inmemory/index.d.ts +1 -1
  264. package/dist/inbound-provider-inmemory/index.js +1 -1
  265. package/dist/jobs/handlers/consumer.write.d.ts +4 -4
  266. package/dist/jobs/handlers/retry.write.d.ts +1 -1
  267. package/dist/jobs/handlers/trigger.write.d.ts +1 -1
  268. package/dist/managed-pages/handlers/set.write.d.ts +1 -1
  269. package/dist/managed-pages/web/i18n.js +1 -0
  270. package/dist/personal-access-tokens/handlers/create.write.d.ts +1 -1
  271. package/dist/personal-access-tokens/handlers/revoke.write.d.ts +1 -1
  272. package/dist/readiness/handlers/run-probes.write.d.ts +1 -1
  273. package/dist/secrets/handlers/delete.write.d.ts +1 -1
  274. package/dist/secrets/handlers/set.write.d.ts +1 -1
  275. package/dist/sessions/handlers/revoke-all-for-user.write.d.ts +1 -1
  276. package/dist/sessions/handlers/revoke-all-others.write.d.ts +1 -1
  277. package/dist/sessions/handlers/revoke.write.d.ts +1 -1
  278. package/dist/subscription-paddle/checkout-config.query.d.ts +3 -0
  279. package/dist/subscription-paddle/checkout-config.query.js +14 -0
  280. package/dist/subscription-paddle/checkout-methods.d.ts +6 -0
  281. package/dist/subscription-paddle/checkout-methods.js +85 -0
  282. package/dist/subscription-paddle/checkout-page.d.ts +19 -0
  283. package/dist/subscription-paddle/checkout-page.js +126 -0
  284. package/dist/subscription-paddle/client.d.ts +24 -0
  285. package/dist/subscription-paddle/client.js +60 -0
  286. package/dist/subscription-paddle/constants.d.ts +16 -0
  287. package/dist/subscription-paddle/constants.js +25 -0
  288. package/dist/subscription-paddle/feature.d.ts +49 -0
  289. package/dist/subscription-paddle/feature.js +192 -0
  290. package/dist/subscription-paddle/index.d.ts +2 -0
  291. package/dist/subscription-paddle/index.js +4 -0
  292. package/dist/subscription-paddle/plan-switch-methods.d.ts +6 -0
  293. package/dist/subscription-paddle/plan-switch-methods.js +114 -0
  294. package/dist/subscription-paddle/price-methods.d.ts +15 -0
  295. package/dist/subscription-paddle/price-methods.js +105 -0
  296. package/dist/subscription-paddle/runtime.d.ts +42 -0
  297. package/dist/subscription-paddle/runtime.js +109 -0
  298. package/dist/subscription-paddle/subscription-state.d.ts +34 -0
  299. package/dist/subscription-paddle/subscription-state.js +48 -0
  300. package/dist/subscription-paddle/tenant-binding.d.ts +8 -0
  301. package/dist/subscription-paddle/tenant-binding.js +26 -0
  302. package/dist/subscription-paddle/verify-webhook.d.ts +4 -0
  303. package/dist/subscription-paddle/verify-webhook.js +55 -0
  304. package/dist/subscription-paddle/webhook-events.d.ts +9 -0
  305. package/dist/subscription-paddle/webhook-events.js +124 -0
  306. package/dist/template-resolver/handlers/collection-item.query.js +2 -2
  307. package/dist/template-resolver/handlers/collection-list.query.js +4 -2
  308. package/dist/template-resolver/handlers/collection-set.write.d.ts +1 -1
  309. package/dist/template-resolver/handlers/collection-shared.d.ts +11 -0
  310. package/dist/template-resolver/handlers/collection-shared.js +16 -0
  311. package/dist/template-resolver/handlers/copy-locale.write.d.ts +1 -1
  312. package/dist/template-resolver/handlers/discard-draft.write.d.ts +2 -2
  313. package/dist/template-resolver/handlers/import-bundle-system.write.d.ts +1 -1
  314. package/dist/template-resolver/handlers/import-bundle.write.d.ts +1 -1
  315. package/dist/template-resolver/handlers/patch-draft.write.d.ts +1 -1
  316. package/dist/template-resolver/handlers/reset-to-system.write.d.ts +1 -1
  317. package/dist/template-resolver/handlers/resource-link-pdf.write.d.ts +1 -1
  318. package/dist/template-resolver/handlers/resource-remove.write.d.ts +1 -1
  319. package/dist/template-resolver/handlers/resource-upload.write.d.ts +1 -1
  320. package/dist/template-resolver/handlers/send-test.write.d.ts +1 -1
  321. package/dist/template-resolver/handlers/set.write.d.ts +1 -1
  322. package/dist/template-resolver/handlers/toggle-status.write.d.ts +4 -4
  323. package/dist/template-resolver/handlers/upsert-system.write.d.ts +1 -1
  324. package/dist/template-resolver/handlers/upsert-tenant.write.d.ts +1 -1
  325. package/dist/template-resolver/web/client-plugin.d.ts +4 -0
  326. package/dist/template-resolver/web/client-plugin.js +134 -52
  327. package/dist/template-resolver/web/i18n.js +1 -1
  328. package/dist/tenant/handlers/add-member.write.d.ts +1 -1
  329. package/dist/tenant/handlers/cancel-invitation.write.d.ts +1 -1
  330. package/dist/tenant/handlers/create.write.d.ts +1 -1
  331. package/dist/tenant/handlers/remove-member.write.d.ts +1 -1
  332. package/dist/tenant/handlers/toggle-enabled.write.d.ts +2 -2
  333. package/dist/tenant/handlers/update-member-roles.write.d.ts +1 -1
  334. package/dist/tenant/handlers/update.write.d.ts +1 -1
  335. package/dist/tenant-handover/handlers/claim.write.d.ts +1 -1
  336. package/dist/tenant-lifecycle/handlers/cancel-destruction.write.d.ts +1 -1
  337. package/dist/tenant-lifecycle/handlers/request-destruction.write.d.ts +1 -1
  338. package/dist/tier-engine/handlers/set-tenant-tier.write.d.ts +1 -1
  339. package/dist/user/handlers/create.write.d.ts +1 -1
  340. package/dist/user/handlers/update.write.d.ts +1 -1
  341. package/dist/user-data-rights/handlers/cancel-deletion.write.d.ts +1 -1
  342. package/dist/user-data-rights/handlers/confirm-deletion-by-token.write.d.ts +1 -1
  343. package/dist/user-data-rights/handlers/lift-restriction.write.d.ts +1 -1
  344. package/dist/user-data-rights/handlers/request-deletion-by-email.write.d.ts +1 -1
  345. package/dist/user-data-rights/handlers/request-deletion.write.d.ts +1 -1
  346. package/dist/user-data-rights/handlers/request-export.write.d.ts +1 -1
  347. package/dist/user-data-rights/handlers/restrict-account.write.d.ts +1 -1
  348. package/dist/user-data-rights/handlers/run-forget-cleanup.write.d.ts +1 -1
  349. package/dist/user-data-rights-defaults/hooks/file-ref.userdata-hook.js +2 -1
  350. package/dist/user-profile/handlers/change-email.write.d.ts +1 -1
  351. package/dist/waitlist/handlers/export-purge.write.d.ts +2 -2
  352. package/dist/waitlist/handlers/invite.write.d.ts +1 -1
  353. package/dist/waitlist/handlers/reject.write.d.ts +1 -1
  354. package/dist/waitlist/handlers/submit.write.d.ts +1 -1
  355. package/package.json +33 -17
  356. package/src/account-security/changes.json +6 -0
  357. package/src/auth-email-password/changes.json +6 -0
  358. package/src/billing-foundation/changes.json +30 -0
  359. package/src/cap-overview/changes.json +6 -0
  360. package/src/files/changes.json +18 -0
  361. package/src/inbound-mail-foundation/changes.json +155 -0
  362. package/src/inbound-provider-imap/changes.json +42 -0
  363. package/src/jobs/changes.json +18 -0
  364. package/src/subscription-paddle/changes.json +15 -0
  365. package/src/template-resolver/changes.json +12 -0
  366. package/dist/subscription-dodo/client.d.ts +0 -17
  367. package/dist/subscription-dodo/client.js +0 -31
  368. package/dist/subscription-dodo/constants.d.ts +0 -12
  369. package/dist/subscription-dodo/constants.js +0 -19
  370. package/dist/subscription-dodo/feature.d.ts +0 -37
  371. package/dist/subscription-dodo/feature.js +0 -144
  372. package/dist/subscription-dodo/index.d.ts +0 -2
  373. package/dist/subscription-dodo/index.js +0 -4
  374. package/dist/subscription-dodo/plugin-methods.d.ts +0 -23
  375. package/dist/subscription-dodo/plugin-methods.js +0 -260
  376. package/dist/subscription-dodo/runtime.d.ts +0 -27
  377. package/dist/subscription-dodo/runtime.js +0 -89
  378. package/dist/subscription-dodo/verify-webhook.d.ts +0 -37
  379. package/dist/subscription-dodo/verify-webhook.js +0 -189
  380. package/src/subscription-dodo/changes.json +0 -14
@@ -42,37 +42,36 @@
42
42
  // only while the connection is open. Losing the claim (for example through
43
43
  // a Redis outage or expired TTL) tears down the local watcher instead of
44
44
  // racing a new holder.
45
- import { configuredPiiSubjectKms, decryptPiiFieldValues, } from "@cosmicdrift/kumiko-framework/crypto";
46
- import { fetchOne, insertOne, selectMany, updateMany } from "@cosmicdrift/kumiko-framework/db";
47
45
  import { SYSTEM_USER_ID } from "@cosmicdrift/kumiko-framework/engine";
48
46
  import { systemScopedRunner, tenantScopedSecrets, } from "@cosmicdrift/kumiko-framework/internal/row-level-security";
49
- import { Temporal } from "@cosmicdrift/kumiko-types/temporal";
50
- import { isPlainObject } from "@cosmicdrift/kumiko-types/type-guards";
47
+ import { mapWithConcurrency } from "../shared/index.js";
48
+ import { createAccountHealth } from "./account-health.js";
49
+ import { createAccountPoller } from "./account-poll.js";
50
+ import { pollScheduleOf, withPollDeadline } from "./account-poll-schedule.js";
51
+ import { registerMailAccountWakeListener } from "./account-wake.js";
51
52
  import { InboundMailAccountStatuses, InboundMailFoundationHandlers } from "./constants.js";
52
- import { MAIL_ACCOUNT_PII_FIELDS, seenMessageTable, syncCursorTable } from "./entities.js";
53
- import { createOAuthAccessTokenManager, usesFoundationManagedOAuth, } from "./oauth-access-token.js";
54
- import { mailAccountsProjectionTable } from "./projection.js";
53
+ import { describeError } from "./describe-error.js";
54
+ import { currentCountBucket } from "./ingest-failures.js";
55
+ import { createOAuthAccessTokenManager, usesFoundationManagedOAuth } from "./oauth-access-token.js";
56
+ import { isInboundAuthError, isInboundCursorInvalidError, isInboundRateLimitError, } from "./provider-errors.js";
55
57
  import { resolveInboundProviderForAccount } from "./provider-factory.js";
56
- import { isInboundAuthError, isInboundCursorInvalidError, isInboundRateLimitError, } from "./types.js";
57
- // Provider errors can embed token or response data in `message`; logs carry
58
- // only the error class.
59
- function describeError(err) {
60
- return err instanceof Error ? err.name : typeof err;
61
- }
58
+ import { createMessageIngester } from "./supervisor-ingest.js";
59
+ import { createSyncAccountNow, runExclusivePerAccount, } from "./sync-account-now.js";
60
+ import { createSyncCursorStore } from "./sync-cursor-store.js";
61
+ import { listSyncableAccounts } from "./syncable-accounts.js";
62
+ import { createWatchCatchUp } from "./watch-catch-up.js";
63
+ import { createWatchLease } from "./watch-lease.js";
62
64
  const DEFAULT_POLL_INTERVAL_MS = 5 * 60 * 1000;
63
65
  const DEFAULT_BACKFILL_WINDOW_DAYS = 30;
64
66
  const DEFAULT_MAX_MESSAGES_PER_POLL = 200;
65
67
  const DEFAULT_WATCH_BACKOFF_INITIAL_MS = 5_000;
66
68
  const DEFAULT_WATCH_BACKOFF_MAX_MS = 5 * 60 * 1000;
67
69
  const DEFAULT_WATCH_LEASE_TTL_SECONDS = 90;
68
- /** One cursor per account (one mailbox inbox) for now; multi-folder support
69
- * can add further scopes later without a schema change. */
70
- const CURSOR_SCOPE = "default";
71
- const SEEN_LOOKUP_CHUNK_SIZE = 1000;
72
70
  export function createInboundMailSupervisor(deps) {
73
71
  const pollIntervalMs = deps.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
74
72
  const backfillWindowDays = deps.backfillWindowDays ?? DEFAULT_BACKFILL_WINDOW_DAYS;
75
73
  const maxMessagesPerPoll = deps.maxMessagesPerPoll ?? DEFAULT_MAX_MESSAGES_PER_POLL;
74
+ const { pollConcurrency, accountPollDeadlineMs } = pollScheduleOf(deps);
76
75
  const backoffInitialMs = deps.watchBackoffInitialMs ?? DEFAULT_WATCH_BACKOFF_INITIAL_MS;
77
76
  const backoffMaxMs = deps.watchBackoffMaxMs ?? DEFAULT_WATCH_BACKOFF_MAX_MS;
78
77
  const leaseTtlSeconds = deps.watchLeaseTtlSeconds ?? DEFAULT_WATCH_LEASE_TTL_SECONDS;
@@ -112,135 +111,16 @@ export function createInboundMailSupervisor(deps) {
112
111
  let running = false;
113
112
  let pollTimer = null;
114
113
  let pollInFlight = null;
114
+ let unregisterWake = null;
115
115
  const watchers = new Map();
116
- // ---------------------------------------------------------------
117
- // Account snapshot: active accounts of all tenants with the address
118
- // decrypted (providers never work with ciphertext).
119
- // ---------------------------------------------------------------
120
- async function listActiveAccounts() {
121
- const rows = await selectMany(systemDb, mailAccountsProjectionTable, {
122
- status: InboundMailAccountStatuses.active,
123
- });
124
- const piiKms = configuredPiiSubjectKms();
125
- const records = [];
126
- for (const row of rows) {
127
- let decrypted;
128
- try {
129
- decrypted = piiKms
130
- ? await decryptPiiFieldValues(row, MAIL_ACCOUNT_PII_FIELDS, piiKms, {
131
- requestId: "inbound-mail-foundation:supervisor:list-accounts",
132
- })
133
- : row;
134
- }
135
- catch (error) {
136
- // Class name only: the message may carry ciphertext or key details.
137
- const errorName = error instanceof Error ? error.name : "unknown error";
138
- log(`inbound-mail: account ${row.id} skipped — address did not decrypt (${errorName})`);
139
- continue;
140
- }
141
- const decryptedAddress = decrypted["address"];
142
- // Falling back to row.address would hand the ciphertext to a provider.
143
- // One undecryptable account must not stop polling of every other tenant's accounts.
144
- if (typeof decryptedAddress !== "string") {
145
- log(`inbound-mail: account ${row.id} skipped — address did not decrypt to text`);
146
- continue;
147
- }
148
- records.push({
149
- id: row.id,
150
- tenantId: row.tenantId,
151
- provider: row.provider,
152
- authMethod: row.authMethod,
153
- ownerUserId: row.ownerUserId ?? null,
154
- address: decryptedAddress,
155
- displayName: row.displayName ?? "",
156
- status: InboundMailAccountStatuses.active,
157
- watchState: row.watchState ?? "idle",
158
- });
159
- }
160
- return records;
161
- }
162
- // ---------------------------------------------------------------
163
- // Cursor-Persistenz (unmanaged direct-write store).
164
- // ---------------------------------------------------------------
165
- async function loadCursor(accountId) {
166
- const row = await fetchOne(systemDb, syncCursorTable, {
167
- accountId,
168
- scope: CURSOR_SCOPE,
169
- });
170
- if (!row)
171
- return null;
172
- try {
173
- const parsed = JSON.parse(row.cursor);
174
- return isPlainObject(parsed) ? parsed : null;
175
- }
176
- catch {
177
- return null; // a corrupt cursor counts as none → backfill; dedup catches duplicates
178
- }
179
- }
180
- async function saveCursor(account, cursor) {
181
- const accountId = account.id;
182
- const now = Temporal.Now.instant().toString();
183
- const serialized = JSON.stringify(cursor);
184
- const existing = await fetchOne(systemDb, syncCursorTable, {
185
- accountId,
186
- scope: CURSOR_SCOPE,
187
- });
188
- if (existing) {
189
- await updateMany(systemDb, syncCursorTable, { cursor: serialized, updatedAt: now }, { accountId, scope: CURSOR_SCOPE });
190
- // skip: the update path is complete; do not fall through to the insert branch.
191
- return;
192
- }
193
- await insertOne(systemDb, syncCursorTable, {
194
- id: crypto.randomUUID(),
195
- // Direct-write store: no executor stamps the NOT NULL tenant_id for us.
196
- tenantId: account.tenantId,
197
- accountId,
198
- scope: CURSOR_SCOPE,
199
- cursor: serialized,
200
- updatedAt: now,
201
- });
202
- }
203
- async function resetCursor(accountId) {
204
- // No deleteMany import is needed: an empty cursor parses to null, so the
205
- // next fetch runs as a backfill.
206
- await updateMany(systemDb, syncCursorTable, { cursor: "", updatedAt: Temporal.Now.instant().toString() }, { accountId, scope: CURSOR_SCOPE });
207
- }
208
- // ---------------------------------------------------------------
209
- // Ingest: every message goes through the standard write handler.
210
- // ---------------------------------------------------------------
211
- async function ingestBatch(account, msgs, cursorSnapshot) {
212
- for (const msg of msgs) {
213
- const bodyRef = deps.storeBody && msg.rawMime ? await deps.storeBody(account, msg) : "";
214
- const result = await deps.dispatchWrite({
215
- handlerQn: InboundMailFoundationHandlers.ingestMessage,
216
- tenantId: account.tenantId,
217
- payload: {
218
- accountId: account.id,
219
- ownerUserId: account.ownerUserId,
220
- providerName: account.provider,
221
- providerMessageId: msg.providerMessageId,
222
- messageIdHeader: msg.messageIdHeader,
223
- providerThreadId: msg.providerThreadId,
224
- references: msg.references,
225
- from: msg.from,
226
- to: msg.to,
227
- cc: msg.cc,
228
- subject: msg.subject,
229
- snippet: msg.snippet,
230
- receivedAtIso: msg.receivedAtIso,
231
- bodyRef,
232
- scope: msg.scope,
233
- providerCursor: cursorSnapshot,
234
- },
235
- });
236
- if (!result.isSuccess) {
237
- // A single-message failure aborts the batch without persisting the cursor.
238
- // The next tick refetches from the old cursor, and deduplication skips
239
- // messages that were already processed.
240
- throw new Error(`ingest-message failed for account ${account.id}: ${JSON.stringify(result.error ?? {})}`);
241
- }
242
- }
243
- }
116
+ const cursors = createSyncCursorStore(systemDb);
117
+ const ingester = createMessageIngester({
118
+ db: systemDb,
119
+ dispatchWrite: deps.dispatchWrite,
120
+ log,
121
+ ...(deps.storeBody && { storeBody: deps.storeBody }),
122
+ });
123
+ const health = createAccountHealth({ db: systemDb, dispatchWrite: deps.dispatchWrite, log });
244
124
  async function markAccount(account, fields, reason) {
245
125
  await deps.dispatchWrite({
246
126
  handlerQn: InboundMailFoundationHandlers.updateAccount,
@@ -265,162 +145,113 @@ export function createInboundMailSupervisor(deps) {
265
145
  }
266
146
  if (isInboundCursorInvalidError(err)) {
267
147
  log(`inbound-mail: account ${account.id} cursor invalid — full resync in backfill window`);
268
- await resetCursor(account.id);
148
+ await cursors.reset(account.id);
269
149
  return true;
270
150
  }
271
151
  // Transient or unknown error: log it and retry on the next tick.
272
152
  log(`inbound-mail: account ${account.id} sync error: ${describeError(err)}`);
273
153
  return true;
274
154
  }
275
- // ---------------------------------------------------------------
276
- // Poll (Reconciliation).
277
- // ---------------------------------------------------------------
278
- function unknownMessageIdsFilterFor(account) {
279
- return async (providerMessageIds) => {
280
- if (providerMessageIds.length === 0)
281
- return new Set();
282
- // One bind parameter per id: a full-mailbox listing would exceed Postgres' 65535 limit.
283
- const seenIds = new Set();
284
- for (let start = 0; start < providerMessageIds.length; start += SEEN_LOOKUP_CHUNK_SIZE) {
285
- const seen = await selectMany(systemDb, seenMessageTable, {
286
- tenantId: account.tenantId,
287
- accountId: account.id,
288
- providerMessageId: {
289
- in: providerMessageIds.slice(start, start + SEEN_LOOKUP_CHUNK_SIZE),
290
- },
291
- }, { fields: ["providerMessageId"] });
292
- for (const row of seen)
293
- seenIds.add(row.providerMessageId);
294
- }
295
- return new Set(providerMessageIds.filter((id) => !seenIds.has(id)));
296
- };
297
- }
298
- async function pollAccount(account, plugin) {
299
- let cursor = await loadCursor(account.id);
300
- let budget = maxMessagesPerPoll;
155
+ const { pollAccount } = createAccountPoller({
156
+ systemDb,
157
+ cursors,
158
+ ingester,
159
+ health,
160
+ providerCtx,
161
+ dispatchWrite: deps.dispatchWrite,
162
+ backfillWindowDays,
163
+ maxMessagesPerPoll,
164
+ resolveAccessToken,
165
+ handleSyncError,
166
+ log,
167
+ });
168
+ function resolvePluginOrLog(account) {
301
169
  try {
302
- // hasMore loop: paginate within this tick until the message budget is
303
- // exhausted.
304
- for (;;) {
305
- const accessToken = await resolveAccessToken(account, plugin);
306
- const result = await plugin.fetch(providerCtx, account, cursor, {
307
- backfillWindowDays,
308
- maxMessages: budget,
309
- filterUnknownMessageIds: unknownMessageIdsFilterFor(account),
310
- ...(accessToken !== undefined && { accessToken }),
311
- });
312
- await ingestBatch(account, result.messages, JSON.stringify(result.nextCursor));
313
- await saveCursor(account, result.nextCursor);
314
- cursor = result.nextCursor;
315
- budget -= result.messages.length;
316
- if (!result.hasMore || budget <= 0)
317
- break;
318
- }
170
+ return resolveInboundProviderForAccount(providerCtx, account);
319
171
  }
320
172
  catch (err) {
321
- await handleSyncError(account, err);
173
+ log(`inbound-mail: ${describeError(err)}`);
174
+ return undefined;
175
+ }
176
+ }
177
+ const inFlightSyncs = new Map();
178
+ const pollAccountWithDeadline = withPollDeadline(pollAccount, {
179
+ ms: accountPollDeadlineMs,
180
+ onDeadline: async (account) => {
181
+ log(`inbound-mail: account ${account.id} sync passed its ${accountPollDeadlineMs}ms deadline`);
182
+ await health.onPollFailure(account, "transient");
183
+ },
184
+ });
185
+ async function pollAccountOfTick(account, plugin) {
186
+ // Joining would hang this tick on the same stuck sync; wake and syncAccountNow still join.
187
+ if (inFlightSyncs.has(account.id)) {
188
+ log(`inbound-mail: account ${account.id} is still syncing, skipped this tick`);
322
189
  }
190
+ else {
191
+ await runExclusivePerAccount(inFlightSyncs, account.id, async () => {
192
+ await pollAccountWithDeadline(account, plugin, {
193
+ countBucket: currentCountBucket(deps.now, pollIntervalMs),
194
+ });
195
+ });
196
+ }
197
+ if (running)
198
+ await ensureWatcher(account, plugin);
323
199
  }
324
200
  async function pollOnce() {
325
- const accounts = await listActiveAccounts();
326
- for (const account of accounts) {
327
- let plugin;
201
+ const accounts = await listSyncableAccounts(systemDb, log, { health });
202
+ // One account failing must not abort the others, and mapWithConcurrency would reject on the first error.
203
+ await mapWithConcurrency(accounts, Math.max(1, pollConcurrency), async (account) => {
328
204
  try {
329
- plugin = resolveInboundProviderForAccount(providerCtx, account);
205
+ const plugin = resolvePluginOrLog(account);
206
+ if (plugin)
207
+ await pollAccountOfTick(account, plugin);
330
208
  }
331
209
  catch (err) {
332
- log(`inbound-mail: ${describeError(err)}`);
333
- continue;
210
+ log(`inbound-mail: account ${account.id} tick failed: ${describeError(err)}`);
334
211
  }
335
- await pollAccount(account, plugin);
336
- if (running)
337
- await ensureWatcher(account, plugin);
338
- }
339
- // Remove watchers for accounts that are no longer active.
340
- const activeIds = new Set(accounts.map((a) => a.id));
212
+ });
213
+ // Remove watchers for accounts that are no longer syncable.
214
+ const syncableIds = new Set(accounts.map((a) => a.id));
341
215
  for (const accountId of watchers.keys()) {
342
- if (!activeIds.has(accountId))
216
+ if (!syncableIds.has(accountId))
343
217
  await stopWatcher(accountId);
344
218
  }
345
219
  }
220
+ const watchCatchUp = createWatchCatchUp({
221
+ inFlight: inFlightSyncs,
222
+ pollAccount: pollAccountWithDeadline,
223
+ isRunning: () => running,
224
+ log,
225
+ });
226
+ const syncAccountNow = createSyncAccountNow({
227
+ inFlight: inFlightSyncs,
228
+ loadAccount: async (onlyAccountId) => (await listSyncableAccounts(systemDb, log, { onlyAccountId }))[0],
229
+ resolvePlugin: resolvePluginOrLog,
230
+ pollAccount: pollAccountWithDeadline,
231
+ ensureWatcher,
232
+ isRunning: () => running,
233
+ });
346
234
  // ---------------------------------------------------------------
347
235
  // Watch lifecycle with backoff restart.
348
236
  // ---------------------------------------------------------------
349
- // Heartbeat for a held watch lease. Independent of the connection's own
350
- // lifecycle — it keeps renewing across reconnect backoff, not just while
351
- // `plugin.watch()` is actually connected, so a flaky IMAP link doesn't
352
- // make this worker lose the account to a peer mid-backoff.
353
- function scheduleRenew(account, state, generation) {
354
- state.renewTimer = setTimeout(() => {
355
- void renewLeaseOnce(account, state, generation);
356
- }, renewIntervalMs);
357
- }
358
- async function tearDownAfterRenewFailures(account, state, reason) {
359
- log(`inbound-mail: watch lease ${reason} for account ${account.id} — tearing down local watcher`);
360
- state.lockToken = null;
361
- await stopWatcher(account.id);
362
- }
363
- async function renewLeaseOnce(account, state, generation) {
364
- state.renewTimer = null;
365
- // skip: supervisor stopped, generation superseded, or lease already lost — stale timer fire, nothing to renew.
366
- if (!running || state.generation !== generation || !state.lockToken || !deps.lock)
367
- return;
368
- let renewed;
369
- try {
370
- renewed = await deps.lock.renew(account.id, state.lockToken, leaseTtlSeconds);
371
- }
372
- catch (err) {
373
- log(`inbound-mail: watch lease renew for account ${account.id} failed: ${describeError(err)}`);
374
- state.renewFailures += 1;
375
- // After failures spanning a full lease TTL, tear down — otherwise a
376
- // peer can acquire the expired key while we still hold an IDLE conn.
377
- if (state.renewFailures * renewIntervalMs >= leaseTtlSeconds * 1000) {
378
- await tearDownAfterRenewFailures(account, state, "renew failed across TTL");
379
- // skip: connection torn down, no reschedule
380
- return;
381
- }
382
- rescheduleIfStillHeld(account, state, generation);
383
- // skip: renew failed but TTL not yet exhausted, retry already scheduled
384
- return;
385
- }
386
- state.renewFailures = 0;
387
- if (!renewed) {
388
- await tearDownAfterRenewFailures(account, state, "lost");
389
- // skip: lease lost and torn down, nothing to reschedule
390
- return;
391
- }
392
- rescheduleIfStillHeld(account, state, generation);
393
- }
394
- function rescheduleIfStillHeld(account, state, generation) {
395
- if (running && state.generation === generation && state.lockToken) {
396
- scheduleRenew(account, state, generation);
397
- }
398
- }
399
- // kumiko-lint-ignore complexity-budget yield-point stale-map check after acquire is intentional
400
- async function acquireWatchLease(account, state) {
401
- if (!deps.lock || state.lockToken)
402
- return "acquired";
403
- const token = await deps.lock.acquire(account.id, { ttlSeconds: leaseTtlSeconds });
404
- if (!token)
405
- return "no-token";
406
- // The acquire() await is a yield point: a concurrent stopWatcher()
407
- // (e.g. from a lost-lease renewal on a different generation) may have
408
- // already retired this exact state and removed it from the map. If so,
409
- // committing the fresh token onto it would leak the lease — nothing
410
- // would ever release it, blocking failover for the full TTL.
411
- if (watchers.get(account.id) !== state) {
412
- await deps.lock.release(account.id, token);
413
- return "stale";
414
- }
415
- state.lockToken = token;
416
- scheduleRenew(account, state, state.generation);
417
- return "acquired";
418
- }
237
+ const watchLease = createWatchLease({
238
+ lock: deps.lock,
239
+ ttlSeconds: leaseTtlSeconds,
240
+ renewIntervalMs,
241
+ watchers,
242
+ isRunning: () => running,
243
+ stopWatcher,
244
+ log,
245
+ });
419
246
  // kumiko-lint-ignore complexity-budget watch lifecycle (start/stop/restart) stays cohesive
420
247
  async function ensureWatcher(account, plugin) {
421
248
  // skip: provider without live push; the reconciliation poll covers the account.
422
249
  if (!plugin.watch)
423
250
  return;
251
+ const stale = watchers.get(account.id);
252
+ if (stale?.stop && stale.trustedAuthservId !== (account.trustedAuthservId ?? null)) {
253
+ await stopWatcher(account.id);
254
+ }
424
255
  const existing = watchers.get(account.id);
425
256
  // skip: the watcher is already running, starting, or scheduled for restart.
426
257
  if (existing?.stop || existing?.restartTimer || existing?.starting)
@@ -434,27 +265,21 @@ export function createInboundMailSupervisor(deps) {
434
265
  renewTimer: null,
435
266
  renewFailures: 0,
436
267
  starting: false,
268
+ trustedAuthservId: null,
437
269
  };
438
270
  watchers.set(account.id, state);
439
- const leaseResult = await acquireWatchLease(account, state);
440
- if (leaseResult === "no-token") {
441
- // Another worker already holds this account's watch lease — the poll
442
- // still covers it, this worker just doesn't open a second IDLE
443
- // connection. Retried on the next tick.
444
- if (!existing)
271
+ state.starting = true;
272
+ const leaseResult = await watchLease.acquire(account, state);
273
+ if (leaseResult !== "acquired") {
274
+ if (leaseResult === "no-token" && !existing)
445
275
  watchers.delete(account.id);
446
- // skip: no lease token acquired above — nothing more to set up here.
447
- return;
448
- }
449
- if (leaseResult === "stale") {
450
- // skip: state was retired by a concurrent stopWatcher while acquire()
451
- // awaited — committing here would leak the lease.
276
+ state.starting = false;
277
+ // skip: no-token means the poll still covers the account and the next tick retries; stale means stopWatcher retired this state.
452
278
  return;
453
279
  }
454
280
  const generation = state.generation;
455
281
  const scheduleRestart = (err) => {
456
- // skip: the supervisor stopped or the watcher generation changed; a restart
457
- // would create a zombie watcher.
282
+ // skip: stopped or superseded generation; a restart would create a zombie watcher.
458
283
  if (!running || state.generation !== generation)
459
284
  return;
460
285
  state.stop = null;
@@ -467,13 +292,13 @@ export function createInboundMailSupervisor(deps) {
467
292
  void ensureWatcher(account, plugin);
468
293
  }, delay);
469
294
  };
470
- state.starting = true;
295
+ state.trustedAuthservId = account.trustedAuthservId ?? null;
471
296
  try {
472
297
  const accessToken = await resolveAccessToken(account, plugin);
473
298
  const stop = await plugin.watch(providerCtx, account, {
474
- onMessages: async (msgs) => {
299
+ onMessages: async (msgs, rejected = []) => {
475
300
  try {
476
- await ingestBatch(account, msgs, "watch");
301
+ await ingester.ingestBatch(account, msgs, rejected, "watch");
477
302
  }
478
303
  catch (err) {
479
304
  // An ingest failure must not kill the watcher: the next poll
@@ -509,6 +334,7 @@ export function createInboundMailSupervisor(deps) {
509
334
  catch (err) {
510
335
  log(`inbound-mail: markAccount(watching) for account ${account.id} failed: ${describeError(err)}`);
511
336
  }
337
+ watchCatchUp.schedule(account, plugin);
512
338
  }
513
339
  catch (err) {
514
340
  const keepRunning = await handleSyncError(account, err);
@@ -581,18 +407,25 @@ export function createInboundMailSupervisor(deps) {
581
407
  if (running)
582
408
  return;
583
409
  running = true;
410
+ unregisterWake = registerMailAccountWakeListener((_tenantId, accountId) => {
411
+ syncAccountNow(accountId).catch((err) => log(`inbound-mail: wake sync for account ${accountId} failed: ${describeError(err)}`));
412
+ });
584
413
  await pollOnce();
585
414
  scheduleNextPoll();
586
415
  },
587
416
  pollOnce,
417
+ syncAccountNow,
588
418
  async stop() {
589
419
  running = false;
420
+ unregisterWake?.();
421
+ unregisterWake = null;
590
422
  if (pollTimer) {
591
423
  clearTimeout(pollTimer);
592
424
  pollTimer = null;
593
425
  }
594
426
  if (pollInFlight)
595
427
  await pollInFlight;
428
+ await Promise.allSettled([...inFlightSyncs.values(), watchCatchUp.settled()]);
596
429
  for (const accountId of [...watchers.keys()]) {
597
430
  await stopWatcher(accountId);
598
431
  }
@@ -0,0 +1,14 @@
1
+ import type { ClientFeatureDefinition } from "@cosmicdrift/kumiko-renderer/web";
2
+ export type InboundMailFoundationClientOptions = {
3
+ /** Mount path of the connect route, when the app moved it (`connectPath` of
4
+ * createInboundMailConnectRoutes). Default "/api/inbound-mail/connect". */
5
+ readonly connectPath?: string;
6
+ /** Leaves the app for the provider's sign-in page. Default: `location.assign`. */
7
+ readonly navigate?: (url: string) => void;
8
+ };
9
+ export declare function oauthReconnectUrl(connectPath: string, row: {
10
+ readonly provider: string;
11
+ readonly address: string;
12
+ readonly ownerUserId?: string;
13
+ }): string;
14
+ export declare function inboundMailFoundationClient(options?: InboundMailFoundationClientOptions): ClientFeatureDefinition;
@@ -0,0 +1,40 @@
1
+ // Client feature for inbound-mail-foundation. The mail account screens offer "Reactivate" for an OAuth
2
+ // mailbox as a clientAction: signing in again is a browser redirect into the same connect route and
3
+ // callback that created the mailbox, which a writeHandler cannot do.
4
+ import { INBOUND_MAIL_CONNECT_PATH, INBOUND_MAIL_FOUNDATION_FEATURE, INBOUND_MAIL_RECONNECT_OAUTH_ACTION, } from "./constants.js";
5
+ const INVALID_RECONNECT_ROW = "inboundMail.accounts.reconnect.invalidRow";
6
+ function textField(payload, field) {
7
+ const value = payload[field];
8
+ return typeof value === "string" && value !== "" ? value : undefined;
9
+ }
10
+ // The callback trusts only the signed state, which the connect route builds from the session and these
11
+ // parameters; scope "user" makes the signed-in caller the owner, so the route re-checks ownership.
12
+ export function oauthReconnectUrl(connectPath, row) {
13
+ const query = new URLSearchParams({
14
+ provider: row.provider,
15
+ scope: row.ownerUserId === undefined ? "shared" : "user",
16
+ mailbox: row.address,
17
+ });
18
+ return `${connectPath}?${query.toString()}`;
19
+ }
20
+ function reconnectOauthHandler(connectPath, navigate) {
21
+ return async (payload) => {
22
+ const provider = textField(payload, "provider");
23
+ const address = textField(payload, "address");
24
+ if (provider === undefined || address === undefined) {
25
+ return { ok: false, message: INVALID_RECONNECT_ROW };
26
+ }
27
+ const ownerUserId = textField(payload, "ownerUserId");
28
+ navigate(oauthReconnectUrl(connectPath, { provider, address, ...(ownerUserId && { ownerUserId }) }));
29
+ return { ok: true };
30
+ };
31
+ }
32
+ export function inboundMailFoundationClient(options = {}) {
33
+ const navigate = options.navigate ?? ((url) => globalThis.location.assign(url));
34
+ return {
35
+ name: INBOUND_MAIL_FOUNDATION_FEATURE,
36
+ clientActions: {
37
+ [INBOUND_MAIL_RECONNECT_OAUTH_ACTION]: reconnectOauthHandler(options.connectPath ?? INBOUND_MAIL_CONNECT_PATH, navigate),
38
+ },
39
+ };
40
+ }
@@ -0,0 +1,5 @@
1
+ export declare const INBOUND_MAIL_FOUNDATION_FEATURE: "inbound-mail-foundation";
2
+ /** Default mount path of the OAuth connect route; the web client builds the reconnect link from it. */
3
+ export declare const INBOUND_MAIL_CONNECT_PATH = "/api/inbound-mail/connect";
4
+ /** Client action of the OAuth "Reactivate" row action: sends the browser to the OAuth connect route. */
5
+ export declare const INBOUND_MAIL_RECONNECT_OAUTH_ACTION: string;
@@ -0,0 +1,6 @@
1
+ // Constants the browser client shares with the server feature; the server constants re-export them.
2
+ export const INBOUND_MAIL_FOUNDATION_FEATURE = "inbound-mail-foundation";
3
+ /** Default mount path of the OAuth connect route; the web client builds the reconnect link from it. */
4
+ export const INBOUND_MAIL_CONNECT_PATH = "/api/inbound-mail/connect";
5
+ /** Client action of the OAuth "Reactivate" row action: sends the browser to the OAuth connect route. */
6
+ export const INBOUND_MAIL_RECONNECT_OAUTH_ACTION = `${INBOUND_MAIL_FOUNDATION_FEATURE}:reconnect-oauth`;
@@ -0,0 +1 @@
1
+ export { type InboundMailFoundationClientOptions, inboundMailFoundationClient, oauthReconnectUrl, } from "./client-plugin.ts";
@@ -0,0 +1 @@
1
+ export { inboundMailFoundationClient, oauthReconnectUrl, } from "./client-plugin.js";