@cosmicdrift/kumiko-samples 0.389.0 → 0.390.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (301) hide show
  1. package/package.json +1 -1
  2. package/packages/bundled-features/package.json +27 -15
  3. package/packages/bundled-features/src/audit/feature.ts +1 -1
  4. package/packages/bundled-features/src/auth-email-password/auth-user-row.ts +1 -1
  5. package/packages/bundled-features/src/auth-email-password/bootstrap.ts +13 -1
  6. package/packages/bundled-features/src/auth-email-password/changes.json +6 -0
  7. package/packages/bundled-features/src/auth-email-password/feature.ts +2 -2
  8. package/packages/bundled-features/src/auth-email-password/handlers/change-password.write.ts +7 -7
  9. package/packages/bundled-features/src/auth-email-password/handlers/confirm-account-unlock.write.ts +3 -3
  10. package/packages/bundled-features/src/auth-email-password/handlers/confirm-token-flow.ts +5 -5
  11. package/packages/bundled-features/src/auth-email-password/handlers/invite-accept-with-login.write.ts +9 -7
  12. package/packages/bundled-features/src/auth-email-password/handlers/invite-accept.write.ts +11 -7
  13. package/packages/bundled-features/src/auth-email-password/handlers/invite-create.write.ts +4 -4
  14. package/packages/bundled-features/src/auth-email-password/handlers/invite-info.query.ts +2 -2
  15. package/packages/bundled-features/src/auth-email-password/handlers/invite-signup-complete.write.ts +9 -7
  16. package/packages/bundled-features/src/auth-email-password/handlers/login.write.ts +23 -16
  17. package/packages/bundled-features/src/auth-email-password/handlers/reset-password.write.ts +3 -2
  18. package/packages/bundled-features/src/auth-email-password/handlers/signup-confirm.write.ts +12 -12
  19. package/packages/bundled-features/src/auth-email-password/handlers/signup-request.write.ts +12 -8
  20. package/packages/bundled-features/src/auth-email-password/handlers/switch-tenant-mfa-gate.write.ts +4 -2
  21. package/packages/bundled-features/src/auth-email-password/handlers/token-request-handler.ts +2 -2
  22. package/packages/bundled-features/src/auth-email-password/handlers/verify-email.write.ts +3 -2
  23. package/packages/bundled-features/src/auth-email-password/seeding.ts +14 -9
  24. package/packages/bundled-features/src/auth-foundation/feature.ts +13 -4
  25. package/packages/bundled-features/src/auth-mfa/handlers/disable.write.ts +5 -5
  26. package/packages/bundled-features/src/auth-mfa/handlers/enable-confirm-preauth.write.ts +24 -14
  27. package/packages/bundled-features/src/auth-mfa/handlers/enable-confirm.write.ts +9 -3
  28. package/packages/bundled-features/src/auth-mfa/handlers/enable-start-preauth.write.ts +6 -1
  29. package/packages/bundled-features/src/auth-mfa/handlers/enable-start.write.ts +3 -1
  30. package/packages/bundled-features/src/auth-mfa/handlers/reencrypt.job.ts +14 -9
  31. package/packages/bundled-features/src/auth-mfa/handlers/regenerate-recovery.write.ts +5 -4
  32. package/packages/bundled-features/src/auth-mfa/handlers/verify.write.ts +25 -14
  33. package/packages/bundled-features/src/auth-mfa/mfa-code-verifier.ts +10 -4
  34. package/packages/bundled-features/src/auth-mfa/mfa-status-checker.ts +9 -3
  35. package/packages/bundled-features/src/auth-mfa/verify-factor.ts +1 -1
  36. package/packages/bundled-features/src/billing-foundation/billing-info-query.ts +2 -2
  37. package/packages/bundled-features/src/billing-foundation/changes.json +6 -0
  38. package/packages/bundled-features/src/billing-foundation/consumer-protection/consent-gate.ts +1 -1
  39. package/packages/bundled-features/src/billing-foundation/consumer-protection/issue-confirmation.ts +5 -5
  40. package/packages/bundled-features/src/billing-foundation/consumer-protection/register-consent.ts +10 -7
  41. package/packages/bundled-features/src/billing-foundation/consumer-protection/register-termination.ts +15 -12
  42. package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-open-cancels.ts +22 -0
  43. package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-provider-lock.ts +4 -0
  44. package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-receipt-limit.ts +4 -4
  45. package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-record.ts +17 -58
  46. package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-request.ts +10 -8
  47. package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-revoke.ts +18 -15
  48. package/packages/bundled-features/src/billing-foundation/events.ts +1 -1
  49. package/packages/bundled-features/src/billing-foundation/feature.ts +8 -3
  50. package/packages/bundled-features/src/billing-foundation/get-subscription-for-tenant.ts +1 -1
  51. package/packages/bundled-features/src/billing-foundation/handlers/process-event.write.ts +5 -5
  52. package/packages/bundled-features/src/billing-foundation/handlers/process-payment-event.write.ts +3 -3
  53. package/packages/bundled-features/src/billing-foundation/plan-catalog.ts +2 -2
  54. package/packages/bundled-features/src/billing-foundation/projection.ts +1 -1
  55. package/packages/bundled-features/src/billing-foundation/types.ts +1 -1
  56. package/packages/bundled-features/src/cap-counter/aggregate-id.ts +1 -1
  57. package/packages/bundled-features/src/cap-counter/book-cap-usage.ts +5 -4
  58. package/packages/bundled-features/src/cap-counter/cap-reservation.ts +1 -1
  59. package/packages/bundled-features/src/cap-counter/constants.ts +1 -1
  60. package/packages/bundled-features/src/cap-counter/feature.ts +2 -2
  61. package/packages/bundled-features/src/cap-counter/handlers/increment-rolling.write.ts +1 -1
  62. package/packages/bundled-features/src/cap-counter/reservation-chain.ts +1 -1
  63. package/packages/bundled-features/src/cap-counter/with-cap-enforcement.ts +4 -4
  64. package/packages/bundled-features/src/cap-overview/handlers/caps-usage.query.ts +1 -1
  65. package/packages/bundled-features/src/cap-overview/handlers/tenant-caps-list.query.ts +1 -1
  66. package/packages/bundled-features/src/compliance-profiles/seeding.ts +12 -7
  67. package/packages/bundled-features/src/config/feature.ts +6 -6
  68. package/packages/bundled-features/src/config/handlers/cascade.query.ts +1 -1
  69. package/packages/bundled-features/src/config/handlers/readiness.query.ts +1 -1
  70. package/packages/bundled-features/src/config/handlers/reencrypt.job.ts +23 -13
  71. package/packages/bundled-features/src/config/handlers/reset.write.ts +3 -3
  72. package/packages/bundled-features/src/config/handlers/selected-extensions.query.ts +1 -1
  73. package/packages/bundled-features/src/config/handlers/set.write.ts +4 -4
  74. package/packages/bundled-features/src/config/handlers/values.query.ts +1 -1
  75. package/packages/bundled-features/src/crypto-shredding/handlers/forget-subject.write.ts +8 -8
  76. package/packages/bundled-features/src/crypto-shredding/resolve-file-bytes-deleter.ts +3 -3
  77. package/packages/bundled-features/src/custom-fields/changes.json +6 -0
  78. package/packages/bundled-features/src/custom-fields/handlers/clear-custom-field.write.ts +1 -1
  79. package/packages/bundled-features/src/custom-fields/handlers/delete-system-field.write.ts +1 -1
  80. package/packages/bundled-features/src/custom-fields/handlers/delete-tenant-field.write.ts +1 -1
  81. package/packages/bundled-features/src/custom-fields/handlers/set-custom-field.write.ts +1 -1
  82. package/packages/bundled-features/src/custom-fields/index.ts +4 -0
  83. package/packages/bundled-features/src/data-retention/feature.ts +1 -1
  84. package/packages/bundled-features/src/data-retention/run-retention-cleanup.ts +12 -2
  85. package/packages/bundled-features/src/delivery/attempt-log.ts +1 -1
  86. package/packages/bundled-features/src/delivery/delivery-service.ts +6 -5
  87. package/packages/bundled-features/src/delivery/feature.ts +2 -2
  88. package/packages/bundled-features/src/delivery/jobs.ts +11 -11
  89. package/packages/bundled-features/src/delivery/testing.ts +1 -1
  90. package/packages/bundled-features/src/document-ingest-foundation/events.ts +1 -1
  91. package/packages/bundled-features/src/feature-toggles/handlers/registered.query.ts +1 -1
  92. package/packages/bundled-features/src/feature-toggles/handlers/set.write.ts +1 -1
  93. package/packages/bundled-features/src/feature-toggles/toggle-sync-signal.ts +1 -1
  94. package/packages/bundled-features/src/file-derivatives/feature.ts +2 -2
  95. package/packages/bundled-features/src/file-derivatives/handlers/public-variant-by-file-ref.query.ts +5 -1
  96. package/packages/bundled-features/src/file-derivatives/handlers/public-variant.query.ts +7 -5
  97. package/packages/bundled-features/src/file-foundation/feature.ts +1 -1
  98. package/packages/bundled-features/src/file-foundation/read-file-ref-content.ts +7 -5
  99. package/packages/bundled-features/src/file-provider-s3/feature.ts +4 -1
  100. package/packages/bundled-features/src/files-tenant-data/handlers/sweep-orphaned-derivatives.job.ts +8 -8
  101. package/packages/bundled-features/src/form-draft/feature.ts +1 -1
  102. package/packages/bundled-features/src/form-draft/handlers/cleanup.job.ts +22 -12
  103. package/packages/bundled-features/src/form-draft/handlers/discard.write.ts +3 -3
  104. package/packages/bundled-features/src/form-draft/handlers/get.query.ts +1 -1
  105. package/packages/bundled-features/src/foundation-shared/config-helpers.ts +1 -1
  106. package/packages/bundled-features/src/inbound-mail-foundation/events.ts +1 -1
  107. package/packages/bundled-features/src/inbound-mail-foundation/feature.ts +1 -1
  108. package/packages/bundled-features/src/inbound-mail-foundation/handlers/account-state.ts +2 -2
  109. package/packages/bundled-features/src/inbound-mail-foundation/handlers/connect-account.write.ts +1 -1
  110. package/packages/bundled-features/src/inbound-mail-foundation/handlers/disconnect-account.write.ts +1 -1
  111. package/packages/bundled-features/src/inbound-mail-foundation/handlers/ingest-message.write.ts +10 -10
  112. package/packages/bundled-features/src/inbound-mail-foundation/handlers/update-account.write.ts +1 -1
  113. package/packages/bundled-features/src/inbound-mail-foundation/projection.ts +1 -1
  114. package/packages/bundled-features/src/inbound-mail-foundation/watch-supervisor.ts +1 -1
  115. package/packages/bundled-features/src/inbound-provider-imap/changes.json +6 -0
  116. package/packages/bundled-features/src/inbound-provider-imap/feature.ts +4 -1
  117. package/packages/bundled-features/src/jobs/changes.json +6 -0
  118. package/packages/bundled-features/src/jobs/events.ts +1 -1
  119. package/packages/bundled-features/src/jobs/handlers/consumer.write.ts +7 -4
  120. package/packages/bundled-features/src/jobs/handlers/projection-rebuild.job.ts +1 -1
  121. package/packages/bundled-features/src/jobs/handlers/reindex-entity.job.ts +11 -5
  122. package/packages/bundled-features/src/jobs/handlers/retention-cleanup.job.ts +1 -1
  123. package/packages/bundled-features/src/jobs/handlers/retry.write.ts +1 -1
  124. package/packages/bundled-features/src/jobs/handlers/stale-run-sweep.job.ts +1 -1
  125. package/packages/bundled-features/src/jobs/handlers/trigger.write.ts +2 -2
  126. package/packages/bundled-features/src/jobs/job-run-logger.ts +139 -90
  127. package/packages/bundled-features/src/legal-pages/README.md +1 -1
  128. package/packages/bundled-features/src/legal-pages/lib/boot-check.ts +12 -8
  129. package/packages/bundled-features/src/mail-foundation/feature.ts +2 -2
  130. package/packages/bundled-features/src/mail-transport-smtp/changes.json +6 -0
  131. package/packages/bundled-features/src/mail-transport-smtp/feature.ts +4 -1
  132. package/packages/bundled-features/src/managed-pages/seeding.ts +13 -8
  133. package/packages/bundled-features/src/notes-history/handlers/add-note.write.ts +1 -1
  134. package/packages/bundled-features/src/personal-access-tokens/feature.ts +1 -1
  135. package/packages/bundled-features/src/personal-access-tokens/handlers/create.write.ts +2 -2
  136. package/packages/bundled-features/src/personal-access-tokens/handlers/revoke.write.ts +1 -1
  137. package/packages/bundled-features/src/rate-limiting/handlers/status.query.ts +2 -2
  138. package/packages/bundled-features/src/readiness/handlers/status.query.ts +4 -1
  139. package/packages/bundled-features/src/renderer-foundation/api.ts +3 -3
  140. package/packages/bundled-features/src/secrets/announce-secret-change.ts +4 -4
  141. package/packages/bundled-features/src/secrets/feature.ts +9 -9
  142. package/packages/bundled-features/src/secrets/handlers/delete.write.ts +4 -3
  143. package/packages/bundled-features/src/secrets/handlers/rotate.job.ts +13 -8
  144. package/packages/bundled-features/src/secrets/handlers/set.write.ts +4 -3
  145. package/packages/bundled-features/src/secrets/secrets-context.ts +16 -6
  146. package/packages/bundled-features/src/secrets/write-gate.ts +1 -1
  147. package/packages/bundled-features/src/seo/feature.ts +1 -1
  148. package/packages/bundled-features/src/sessions/changes.json +12 -0
  149. package/packages/bundled-features/src/sessions/feature.ts +1 -1
  150. package/packages/bundled-features/src/sessions/handlers/cleanup.job.ts +2 -2
  151. package/packages/bundled-features/src/sessions/handlers/detail.query.ts +5 -2
  152. package/packages/bundled-features/src/sessions/handlers/list.query.ts +5 -2
  153. package/packages/bundled-features/src/sessions/handlers/mine.query.ts +5 -2
  154. package/packages/bundled-features/src/sessions/handlers/revoke-all-for-user.write.ts +3 -3
  155. package/packages/bundled-features/src/sessions/handlers/revoke-all-others.write.ts +1 -1
  156. package/packages/bundled-features/src/sessions/handlers/revoke.write.ts +1 -1
  157. package/packages/bundled-features/src/sessions/screens.ts +8 -4
  158. package/packages/bundled-features/src/sessions/session-revoked-event.ts +1 -1
  159. package/packages/bundled-features/src/sessions/web/session-device-cell.tsx +10 -12
  160. package/packages/bundled-features/src/sessions/web/user-agent.ts +12 -0
  161. package/packages/bundled-features/src/shared/index.ts +15 -0
  162. package/packages/bundled-features/src/shared/purge-files-reaching-subject.ts +12 -2
  163. package/packages/bundled-features/src/shared/require-for-tenant.ts +7 -5
  164. package/packages/bundled-features/src/shared/stream-tenant-backfill.ts +201 -0
  165. package/packages/bundled-features/src/shared/stream-tenant-derived-state.ts +107 -0
  166. package/packages/bundled-features/src/step-dispatcher/feature.ts +1 -1
  167. package/packages/bundled-features/src/subscription-dodo/plugin-methods.ts +1 -1
  168. package/packages/bundled-features/src/subscription-dodo/runtime.ts +7 -7
  169. package/packages/bundled-features/src/subscription-stripe/plugin-methods.ts +5 -3
  170. package/packages/bundled-features/src/subscription-stripe/runtime.ts +7 -7
  171. package/packages/bundled-features/src/template-resolver/api.ts +3 -3
  172. package/packages/bundled-features/src/template-resolver/changes.json +18 -0
  173. package/packages/bundled-features/src/template-resolver/handlers/bundle-import.ts +5 -4
  174. package/packages/bundled-features/src/template-resolver/handlers/copy-locale.write.ts +2 -2
  175. package/packages/bundled-features/src/template-resolver/handlers/export-bundle.query.ts +1 -1
  176. package/packages/bundled-features/src/template-resolver/handlers/overview.query.ts +2 -2
  177. package/packages/bundled-features/src/template-resolver/handlers/send-test-shared.ts +1 -1
  178. package/packages/bundled-features/src/template-resolver/handlers/send-test.write.ts +2 -2
  179. package/packages/bundled-features/src/template-resolver/handlers/tenant-override-selection.ts +6 -2
  180. package/packages/bundled-features/src/template-resolver/handlers/tenant-overrides.query.ts +4 -2
  181. package/packages/bundled-features/src/template-resolver/handlers/usage.query.ts +1 -1
  182. package/packages/bundled-features/src/template-resolver/seeding.ts +12 -2
  183. package/packages/bundled-features/src/template-resolver/store-template-resource.ts +18 -13
  184. package/packages/bundled-features/src/template-resolver/template-resources.ts +4 -4
  185. package/packages/bundled-features/src/template-resolver/testing.ts +2 -2
  186. package/packages/bundled-features/src/template-resolver/web/document-insert-fields.tsx +222 -0
  187. package/packages/bundled-features/src/template-resolver/web/document-insert-model.ts +164 -0
  188. package/packages/bundled-features/src/template-resolver/web/document-insert-panel.tsx +115 -0
  189. package/packages/bundled-features/src/template-resolver/web/document-tiptap-bubble.tsx +177 -0
  190. package/packages/bundled-features/src/template-resolver/web/document-tiptap-content.tsx +38 -0
  191. package/packages/bundled-features/src/template-resolver/web/document-tiptap-context.ts +119 -0
  192. package/packages/bundled-features/src/template-resolver/web/document-tiptap-editor.tsx +51 -1098
  193. package/packages/bundled-features/src/template-resolver/web/document-tiptap-helpers.ts +161 -0
  194. package/packages/bundled-features/src/template-resolver/web/document-tiptap-layout.tsx +83 -0
  195. package/packages/bundled-features/src/template-resolver/web/document-tiptap-toolbar-groups.tsx +153 -0
  196. package/packages/bundled-features/src/template-resolver/web/document-tiptap-toolbar.tsx +182 -0
  197. package/packages/bundled-features/src/template-resolver/web/editor-handle.ts +18 -0
  198. package/packages/bundled-features/src/template-resolver/web/placeholder-line-editor.tsx +19 -6
  199. package/packages/bundled-features/src/template-resolver/web/use-document-tiptap-model.ts +138 -0
  200. package/packages/bundled-features/src/tenant/changes.json +19 -0
  201. package/packages/bundled-features/src/tenant/handlers/cancel-invitation.write.ts +1 -1
  202. package/packages/bundled-features/src/tenant/handlers/memberships.query.ts +1 -1
  203. package/packages/bundled-features/src/tenant/handlers/remove-member.write.ts +2 -2
  204. package/packages/bundled-features/src/tenant/handlers/update-member-roles.write.ts +1 -1
  205. package/packages/bundled-features/src/tenant/index.ts +5 -0
  206. package/packages/bundled-features/src/tenant/invited-membership.ts +6 -1
  207. package/packages/bundled-features/src/tenant/schema/tenant.ts +4 -1
  208. package/packages/bundled-features/src/tenant/seeding.ts +65 -62
  209. package/packages/bundled-features/src/tenant/stream-tenant-backfill.ts +115 -0
  210. package/packages/bundled-features/src/tenant/testing.ts +77 -0
  211. package/packages/bundled-features/src/tenant-handover/events.ts +1 -1
  212. package/packages/bundled-features/src/tenant-handover/handlers/claim.write.ts +1 -1
  213. package/packages/bundled-features/src/tenant-lifecycle/feature.ts +5 -5
  214. package/packages/bundled-features/src/tenant-lifecycle/handlers/cancel-destruction.write.ts +22 -10
  215. package/packages/bundled-features/src/tenant-lifecycle/handlers/request-destruction.write.ts +22 -11
  216. package/packages/bundled-features/src/tenant-lifecycle/run-tenant-destroy.ts +23 -12
  217. package/packages/bundled-features/src/tenant-lifecycle/stages.ts +7 -3
  218. package/packages/bundled-features/src/tenant-lifecycle/testing.ts +9 -8
  219. package/packages/bundled-features/src/tier-engine/entity.ts +1 -1
  220. package/packages/bundled-features/src/tier-engine/feature.ts +8 -3
  221. package/packages/bundled-features/src/tier-engine/handlers/active-tier.query.ts +3 -3
  222. package/packages/bundled-features/src/tier-engine/handlers/get-tenant-tier.query.ts +7 -1
  223. package/packages/bundled-features/src/tier-engine/handlers/set-tenant-tier.write.ts +3 -3
  224. package/packages/bundled-features/src/user/db/queries/stream-tenant-backfill.ts +71 -97
  225. package/packages/bundled-features/src/user/handlers/find-for-auth.query.ts +2 -2
  226. package/packages/bundled-features/src/user/handlers/update-roles.ts +2 -2
  227. package/packages/bundled-features/src/user/index.ts +2 -0
  228. package/packages/bundled-features/src/user/invited-global-roles.ts +6 -1
  229. package/packages/bundled-features/src/user/principal-status.ts +1 -1
  230. package/packages/bundled-features/src/user/seeding.ts +24 -6
  231. package/packages/bundled-features/src/user/testing.ts +22 -0
  232. package/packages/bundled-features/src/user-data-rights/COMPLIANCE.md +1 -1
  233. package/packages/bundled-features/src/user-data-rights/README.md +1 -1
  234. package/packages/bundled-features/src/user-data-rights/audit-download.ts +12 -2
  235. package/packages/bundled-features/src/user-data-rights/changes.json +24 -0
  236. package/packages/bundled-features/src/user-data-rights/feature.ts +29 -24
  237. package/packages/bundled-features/src/user-data-rights/handlers/confirm-deletion-by-token.write.ts +1 -1
  238. package/packages/bundled-features/src/user-data-rights/handlers/deletion-grace-period.ts +1 -1
  239. package/packages/bundled-features/src/user-data-rights/handlers/download-by-job.query.ts +2 -2
  240. package/packages/bundled-features/src/user-data-rights/handlers/download-by-token.query.ts +2 -2
  241. package/packages/bundled-features/src/user-data-rights/handlers/request-deletion.write.ts +1 -1
  242. package/packages/bundled-features/src/user-data-rights/handlers/restrict-account.write.ts +4 -4
  243. package/packages/bundled-features/src/user-data-rights/handlers/run-forget-cleanup.write.ts +12 -6
  244. package/packages/bundled-features/src/user-data-rights/index.ts +2 -0
  245. package/packages/bundled-features/src/user-data-rights/lib/mail-transport-resolver.ts +1 -1
  246. package/packages/bundled-features/src/user-data-rights/lib/storage-provider-resolver.ts +1 -1
  247. package/packages/bundled-features/src/user-data-rights/lib/update-user-lifecycle.ts +6 -1
  248. package/packages/bundled-features/src/user-data-rights/run-export-jobs.ts +8 -2
  249. package/packages/bundled-features/src/user-data-rights/run-forget-cleanup.ts +14 -3
  250. package/packages/bundled-features/src/user-data-rights/run-user-export.ts +11 -1
  251. package/packages/bundled-features/src/user-data-rights/web/constants.ts +10 -0
  252. package/packages/bundled-features/src/user-data-rights/web/index.ts +6 -1
  253. package/packages/bundled-features/src/user-profile/handlers/change-email.write.ts +8 -6
  254. package/packages/bundled-features/src/waitlist/handlers/export-purge.write.ts +1 -1
  255. package/packages/bundled-features/src/waitlist/handlers/invite.write.ts +2 -2
  256. package/packages/bundled-features/src/waitlist/handlers/reject.write.ts +1 -1
  257. package/packages/bundled-features/src/waitlist/handlers/submit.write.ts +9 -4
  258. package/packages/bundled-features/src/waitlist/legacy-migrate.ts +6 -1
  259. package/packages/bundled-features/src/waitlist/link-on-signup.ts +7 -1
  260. package/packages/bundled-features/src/workflow-runner/changes.json +6 -0
  261. package/packages/bundled-features/src/workflow-runner/db/queries/due-runs.ts +1 -1
  262. package/packages/bundled-features/src/workflow-runner/event-subscriber.ts +4 -3
  263. package/packages/bundled-features/src/workflow-runner/event-trigger.ts +2 -1
  264. package/packages/bundled-features/src/workflow-runner/feature.ts +2 -2
  265. package/packages/bundled-features/src/workflow-runner/handlers/resume-run.write.ts +8 -8
  266. package/packages/bundled-features/src/workflow-runner/runner.ts +6 -4
  267. package/samples/apps/cap-billing-demo/package.json +1 -1
  268. package/samples/apps/cap-billing-demo/src/feature.ts +19 -5
  269. package/samples/apps/config-demo/src/app/server.ts +1 -1
  270. package/samples/apps/styleguide/src/app/server.ts +2 -2
  271. package/samples/apps/ui-walkthrough/package.json +1 -1
  272. package/samples/apps/use-all-bundled/feature-manifest.json +1 -1
  273. package/samples/apps/use-all-bundled/package.json +1 -1
  274. package/samples/apps/use-all-bundled/src/app/seed.ts +12 -2
  275. package/samples/apps/user-data-rights-demo/README.md +1 -1
  276. package/samples/apps/user-data-rights-demo/package.json +1 -1
  277. package/samples/apps/user-data-rights-demo/src/feature.ts +3 -2
  278. package/samples/recipes/api-worker-split/README.md +2 -1
  279. package/samples/recipes/api-worker-split/src/feature.ts +2 -1
  280. package/samples/recipes/cross-feature-events/README.md +5 -5
  281. package/samples/recipes/cross-feature-events/package.json +1 -1
  282. package/samples/recipes/cross-feature-events/src/feature.ts +4 -4
  283. package/samples/recipes/event-sourcing/README.md +5 -5
  284. package/samples/recipes/event-sourcing/package.json +1 -1
  285. package/samples/recipes/event-sourcing/src/feature.ts +15 -15
  286. package/samples/recipes/files-post-processing/README.md +1 -1
  287. package/samples/recipes/files-post-processing/package.json +1 -1
  288. package/samples/recipes/files-post-processing/src/feature.ts +6 -4
  289. package/samples/recipes/lane-routing/package.json +1 -1
  290. package/samples/recipes/legal-pages/README.md +2 -2
  291. package/samples/recipes/lifecycle-hooks/README.md +2 -2
  292. package/samples/recipes/lifecycle-hooks/src/feature.ts +1 -1
  293. package/samples/recipes/managed-config/README.md +3 -3
  294. package/samples/recipes/search/README.md +1 -1
  295. package/samples/recipes/search/src/feature.ts +3 -3
  296. package/samples/recipes/secrets-demo/README.md +1 -1
  297. package/samples/recipes/secrets-demo/src/feature.ts +5 -2
  298. package/samples/recipes/state-machine/src/events.ts +1 -1
  299. package/samples/recipes/state-machine/src/reducer.ts +1 -1
  300. package/samples/recipes/workflow-engine/src/resume-loop.ts +4 -4
  301. package/samples/recipes/workflow-engine/src/workflow-runner.ts +2 -2
@@ -0,0 +1,201 @@
1
+ // One-time migration tooling for event streams that live on the wrong tenant.
2
+ //
3
+ // An aggregate type that became tenant-independent (systemStream) or whose
4
+ // stream must follow the tenant in its payload has older streams on whichever
5
+ // tenant happened to write them. Newer writes then version-conflict against
6
+ // the empty target stream, and a raw tenant_id UPDATE trips
7
+ // events_aggregate_version_uq as soon as an aggregate has BOTH a legacy
8
+ // stream and newer events on the target (split stream).
9
+ //
10
+ // This tool merges per aggregate: all events of the aggregate, ordered by
11
+ // global event id, are renumbered 1..n and retenanted to the target - two-phase
12
+ // version writes (negative interim values) dodge the unique index while the
13
+ // per-aggregate transaction is in flight. Snapshots of migrated aggregates
14
+ // are dropped (version numbering changed; the next snapshotting load
15
+ // recreates them); archived-stream markers move with the stream.
16
+ //
17
+ // Idempotent: candidates are only aggregates that still have events outside
18
+ // their target tenant. One failing aggregate does not abort the run: the rest
19
+ // of the estate migrates, failures are reported.
20
+ //
21
+ // After a run, rebuild the affected projections so read-table tenant_id
22
+ // reflects the stream move.
23
+
24
+ import type { DbConnection } from "@cosmicdrift/kumiko-framework/db";
25
+ import {
26
+ asRawClient,
27
+ toPostgresArrayLiteral,
28
+ transaction,
29
+ unsafeReadRetrying,
30
+ } from "@cosmicdrift/kumiko-framework/db";
31
+ import { SYSTEM_TENANT_ID } from "@cosmicdrift/kumiko-framework/engine";
32
+
33
+ export type StreamTenantTarget =
34
+ | { readonly kind: "system" }
35
+ // The tenantId carried by the aggregate's earliest event payload.
36
+ | { readonly kind: "payload-tenant" };
37
+
38
+ export type StreamBackfillCandidate = {
39
+ readonly aggregateId: string;
40
+ readonly targetTenantId: string;
41
+ readonly sourceTenantIds: readonly string[];
42
+ };
43
+
44
+ export type StreamBackfillFailure = { readonly aggregateId: string; readonly error: string };
45
+
46
+ export type StreamBackfillResult = {
47
+ readonly aggregatesMigrated: number;
48
+ readonly eventsMigrated: number;
49
+ readonly failed: readonly StreamBackfillFailure[];
50
+ };
51
+
52
+ export type StreamBackfillOptions = {
53
+ readonly aggregateType: string;
54
+ readonly target: StreamTenantTarget;
55
+ // Runs per candidate before its events move; a throw skips the aggregate
56
+ // and lands in `failed` (search cleanup must not be lost by a move).
57
+ readonly beforeMove?: (candidate: StreamBackfillCandidate) => Promise<void>;
58
+ // Restricts the candidate scan to events on these tenants, which lets Postgres use
59
+ // events_tenant_type_idx instead of grouping every event of the type. Streams on a tenant
60
+ // outside the list are not found; omit it for the full scan.
61
+ readonly scanTenantIds?: readonly string[];
62
+ // Called once when the scan found candidates, before the first move. "skip" ends the run
63
+ // with nothing moved (this process cannot do the work); a throw propagates to the caller.
64
+ readonly onFirstCandidate?: () => Promise<"proceed" | "skip">;
65
+ };
66
+
67
+ type CandidateRow = {
68
+ aggregate_id: string;
69
+ target_tenant_id: string | null;
70
+ source_tenant_ids: string[];
71
+ };
72
+
73
+ export async function backfillAggregateStreamTenants(
74
+ db: DbConnection,
75
+ options: StreamBackfillOptions,
76
+ ): Promise<StreamBackfillResult> {
77
+ const rows = await readCandidateRows(db, options);
78
+ if (rows.length > 0 && options.onFirstCandidate) {
79
+ if ((await options.onFirstCandidate()) === "skip") {
80
+ return { aggregatesMigrated: 0, eventsMigrated: 0, failed: [] };
81
+ }
82
+ }
83
+
84
+ let aggregatesMigrated = 0;
85
+ let eventsMigrated = 0;
86
+ const failed: StreamBackfillFailure[] = [];
87
+
88
+ for (const row of rows) {
89
+ try {
90
+ if (!row.target_tenant_id) {
91
+ throw new Error(`earliest event of the aggregate carries no payload.tenantId`);
92
+ }
93
+ const candidate: StreamBackfillCandidate = {
94
+ aggregateId: row.aggregate_id,
95
+ targetTenantId: row.target_tenant_id,
96
+ sourceTenantIds: row.source_tenant_ids,
97
+ };
98
+ await options.beforeMove?.(candidate);
99
+ eventsMigrated += await migrateAggregate(db, options.aggregateType, candidate);
100
+ aggregatesMigrated++;
101
+ } catch (e) {
102
+ failed.push({
103
+ aggregateId: row.aggregate_id,
104
+ error: e instanceof Error ? e.message : String(e),
105
+ });
106
+ }
107
+ }
108
+ return { aggregatesMigrated, eventsMigrated, failed };
109
+ }
110
+
111
+ async function readCandidateRows(
112
+ db: DbConnection,
113
+ options: StreamBackfillOptions,
114
+ ): Promise<readonly CandidateRow[]> {
115
+ const targetSql =
116
+ options.target.kind === "system"
117
+ ? `$2::text`
118
+ : `(SELECT e2."payload"->>'tenantId' FROM "kumiko_events" e2
119
+ WHERE e2."aggregate_type" = $1 AND e2."aggregate_id" = e."aggregate_id"
120
+ ORDER BY e2."id" ASC LIMIT 1)`;
121
+ const params: unknown[] = [options.aggregateType];
122
+ if (options.target.kind === "system") params.push(SYSTEM_TENANT_ID);
123
+ let scanSql = "";
124
+ if (options.scanTenantIds) {
125
+ // Events already on the SYSTEM target are not legacy; leaving them out keeps the scan to
126
+ // the legacy events only. A payload target differs per aggregate, so nothing is left out.
127
+ const scanned =
128
+ options.target.kind === "system"
129
+ ? options.scanTenantIds.filter((id) => id !== SYSTEM_TENANT_ID)
130
+ : options.scanTenantIds;
131
+ params.push(toPostgresArrayLiteral(scanned));
132
+ scanSql = ` AND e."tenant_id" = ANY($${params.length}::uuid[])`;
133
+ }
134
+ // An aggregate whose earliest event has no payload tenant yields a NULL
135
+ // target and is reported as failed instead of being silently skipped.
136
+ return unsafeReadRetrying<CandidateRow>(
137
+ db,
138
+ `SELECT e."aggregate_id" AS aggregate_id,
139
+ ${targetSql} AS target_tenant_id,
140
+ array_agg(DISTINCT e."tenant_id"::text) AS source_tenant_ids
141
+ FROM "kumiko_events" e
142
+ WHERE e."aggregate_type" = $1${scanSql}
143
+ GROUP BY e."aggregate_id"
144
+ HAVING bool_or(e."tenant_id"::text IS DISTINCT FROM ${targetSql})`,
145
+ params,
146
+ );
147
+ }
148
+
149
+ async function migrateAggregate(
150
+ db: DbConnection,
151
+ aggregateType: string,
152
+ candidate: StreamBackfillCandidate,
153
+ ): Promise<number> {
154
+ const { aggregateId, targetTenantId } = candidate;
155
+ return transaction(db, async (tx) => {
156
+ const raw = asRawClient(tx);
157
+ // Global id order = commit-adjacent replay order; merges a split stream
158
+ // into one consistent version sequence.
159
+ const events = await raw.unsafe<{ id: bigint | string }>(
160
+ `SELECT "id" FROM "kumiko_events"
161
+ WHERE "aggregate_type" = $1 AND "aggregate_id" = $2::uuid
162
+ ORDER BY "id" ASC
163
+ FOR UPDATE`,
164
+ [aggregateType, aggregateId],
165
+ );
166
+
167
+ // Negative interim versions: real versions are always >= 1, so nothing
168
+ // can collide with events_aggregate_version_uq mid-migration.
169
+ for (const [i, ev] of events.entries()) {
170
+ await raw.unsafe(
171
+ `UPDATE "kumiko_events" SET "tenant_id" = $1::uuid, "version" = $2 WHERE "id" = $3`,
172
+ [targetTenantId, -(i + 1), ev.id],
173
+ );
174
+ }
175
+ for (const [i, ev] of events.entries()) {
176
+ await raw.unsafe(`UPDATE "kumiko_events" SET "version" = $1 WHERE "id" = $2`, [i + 1, ev.id]);
177
+ }
178
+
179
+ // Version numbering changed, so any snapshot of this aggregate is stale.
180
+ await raw.unsafe(`DELETE FROM "kumiko_snapshots" WHERE "aggregate_id" = $1::uuid`, [
181
+ aggregateId,
182
+ ]);
183
+
184
+ // Archived markers key on (tenant_id, aggregate_id): move them with the
185
+ // stream, keeping an existing target marker if both exist.
186
+ await raw.unsafe(
187
+ `INSERT INTO "kumiko_archived_streams" ("tenant_id", "aggregate_id", "aggregate_type", "archived_at", "archived_by", "reason")
188
+ SELECT $1::uuid, "aggregate_id", "aggregate_type", "archived_at", "archived_by", "reason"
189
+ FROM "kumiko_archived_streams"
190
+ WHERE "aggregate_id" = $2::uuid AND "tenant_id" <> $1::uuid
191
+ ON CONFLICT DO NOTHING`,
192
+ [targetTenantId, aggregateId],
193
+ );
194
+ await raw.unsafe(
195
+ `DELETE FROM "kumiko_archived_streams" WHERE "aggregate_id" = $1::uuid AND "tenant_id" <> $2::uuid`,
196
+ [aggregateId, targetTenantId],
197
+ );
198
+
199
+ return events.length;
200
+ });
201
+ }
@@ -0,0 +1,107 @@
1
+ // Derived state of a stream-tenant backfill: search docs in the buckets the streams
2
+ // left, projection rows and the index on SYSTEM_TENANT_ID. The stream move itself lives
3
+ // in stream-tenant-backfill.ts; this part is repeatable and safe to run without moves.
4
+
5
+ import type { DbConnection } from "@cosmicdrift/kumiko-framework/db";
6
+ import { type Registry, SYSTEM_TENANT_ID } from "@cosmicdrift/kumiko-framework/engine";
7
+ import { rebuildProjection } from "@cosmicdrift/kumiko-framework/pipeline";
8
+ import { reindexEntity, type SearchAdapter } from "@cosmicdrift/kumiko-framework/search";
9
+ import type { StreamBackfillCandidate, StreamBackfillFailure } from "./stream-tenant-backfill.ts";
10
+
11
+ export type StreamBackfillStepFailure = StreamBackfillFailure & { readonly step: string };
12
+
13
+ // "if-moved" skips rebuild and reindex when no stream moved; "always" repairs
14
+ // derived state after a run that moved streams and then crashed.
15
+ export type DerivedStateMode = "if-moved" | "always";
16
+
17
+ export function createSearchBucketCleanup(
18
+ searchAdapter: SearchAdapter | undefined,
19
+ entityType: string,
20
+ ): {
21
+ readonly beforeMove: (candidate: StreamBackfillCandidate) => Promise<void>;
22
+ readonly cleanedAggregates: () => number;
23
+ } {
24
+ let cleaned = 0;
25
+ return {
26
+ cleanedAggregates: () => cleaned,
27
+ beforeMove: async (candidate) => {
28
+ // skip: no adapter means no search docs to clean
29
+ if (!searchAdapter) return;
30
+ const oldBuckets = candidate.sourceTenantIds.filter((id) => id !== candidate.targetTenantId);
31
+ for (const bucket of oldBuckets) {
32
+ if (searchAdapter.removeBatch) {
33
+ await searchAdapter.removeBatch(bucket, [
34
+ { entityType, entityId: candidate.aggregateId },
35
+ ]);
36
+ } else {
37
+ await searchAdapter.remove(bucket, entityType, candidate.aggregateId);
38
+ }
39
+ }
40
+ if (oldBuckets.length > 0) cleaned++;
41
+ },
42
+ };
43
+ }
44
+
45
+ export type DerivedStateResult = {
46
+ readonly projectionsRebuilt: readonly string[];
47
+ readonly reindexedRows: number;
48
+ readonly failures: readonly StreamBackfillStepFailure[];
49
+ };
50
+
51
+ export async function rebuildAndReindex(deps: {
52
+ readonly db: DbConnection;
53
+ readonly registry: Registry;
54
+ readonly searchAdapter?: SearchAdapter;
55
+ readonly projections: readonly string[];
56
+ readonly searchEntity: string;
57
+ }): Promise<DerivedStateResult> {
58
+ const { db, registry, searchAdapter, projections, searchEntity } = deps;
59
+ const failures: StreamBackfillStepFailure[] = [];
60
+ const projectionsRebuilt: string[] = [];
61
+ for (const projection of projections) {
62
+ try {
63
+ await rebuildProjection(projection, { db, registry });
64
+ projectionsRebuilt.push(projection);
65
+ } catch (e) {
66
+ failures.push({ aggregateId: projection, step: "rebuild", error: messageOf(e) });
67
+ }
68
+ }
69
+
70
+ let reindexedRows = 0;
71
+ if (searchAdapter && isEntitySearchable(registry, searchEntity)) {
72
+ try {
73
+ const reindex = await reindexEntity(
74
+ db,
75
+ registry,
76
+ searchAdapter,
77
+ searchEntity,
78
+ SYSTEM_TENANT_ID,
79
+ );
80
+ reindexedRows = reindex.indexedRows;
81
+ for (const f of reindex.failures) {
82
+ failures.push({ aggregateId: f.entityId, step: "reindex", error: f.reason });
83
+ }
84
+ } catch (e) {
85
+ failures.push({ aggregateId: searchEntity, step: "reindex", error: messageOf(e) });
86
+ }
87
+ }
88
+ return { projectionsRebuilt, reindexedRows, failures };
89
+ }
90
+
91
+ function isEntitySearchable(registry: Registry, entityName: string): boolean {
92
+ return (
93
+ registry.getSearchableFields(entityName).length > 0 ||
94
+ registry.getSearchPayloadExtensions(entityName).length > 0
95
+ );
96
+ }
97
+
98
+ export function stepFailures(
99
+ step: string,
100
+ failed: readonly StreamBackfillFailure[],
101
+ ): StreamBackfillStepFailure[] {
102
+ return failed.map((f) => ({ ...f, step }));
103
+ }
104
+
105
+ function messageOf(e: unknown): string {
106
+ return e instanceof Error ? e.message : String(e);
107
+ }
@@ -94,7 +94,7 @@ export function createStepDispatcherFeature(): FeatureDefinition {
94
94
  tenantId,
95
95
  userId,
96
96
  callerFeature: STEP_DISPATCHER_FEATURE,
97
- ...(ctx.secrets && { secrets: ctx.secrets }),
97
+ ...(ctx.services.secrets && { secrets: ctx.services.secrets }),
98
98
  }),
99
99
  });
100
100
  },
@@ -225,7 +225,7 @@ async function fillPriceCache(
225
225
  } else {
226
226
  const message =
227
227
  outcome.reason instanceof Error ? outcome.reason.message : String(outcome.reason);
228
- ctx.log?.warn("subscription-dodo: product lookup failed", { message });
228
+ ctx.obs.log?.warn("subscription-dodo: product lookup failed", { message });
229
229
  }
230
230
  }
231
231
  }
@@ -64,11 +64,11 @@ export function createDodoRuntimes(deps: DodoRuntimeDeps): DodoRuntimes {
64
64
 
65
65
  async function ctxApiKey(ctx: HandlerContext): Promise<string> {
66
66
  let key: string | undefined;
67
- if (ctx.secrets) {
68
- const got = await requireSecretsContext(ctx, SUBSCRIPTION_DODO_FEATURE).get(
69
- SYSTEM_TENANT_ID,
70
- deps.apiKeyHandle.name,
71
- );
67
+ if (ctx.services.secrets) {
68
+ const got = await requireSecretsContext(
69
+ { secrets: ctx.services.secrets, user: ctx.user },
70
+ SUBSCRIPTION_DODO_FEATURE,
71
+ ).get(SYSTEM_TENANT_ID, deps.apiKeyHandle.name);
72
72
  key = parseStoredSecret(got?.reveal());
73
73
  }
74
74
  if (!key && deps.fallback.apiKey && deps.fallback.apiKey.length > 0) {
@@ -102,8 +102,8 @@ export function createDodoRuntimes(deps: DodoRuntimeDeps): DodoRuntimes {
102
102
  if (live !== true) return false;
103
103
  if (deps.fallback.apiKey !== undefined && deps.fallback.apiKey.length > 0) return true;
104
104
  // Existence-only probe on every billing-plans render: must stay un-audited.
105
- return ctx.secrets
106
- ? await ctx.secrets.has(SYSTEM_TENANT_ID, deps.apiKeyHandle.name)
105
+ return ctx.services.secrets
106
+ ? await ctx.services.secrets.has(SYSTEM_TENANT_ID, deps.apiKeyHandle.name)
107
107
  : false;
108
108
  },
109
109
  },
@@ -169,7 +169,9 @@ export function createStripeCheckoutSession(
169
169
  if (!options.providerCustomerId || !isUnknownCustomerStripeError(error)) throw error;
170
170
  // Stripe account switch (test -> live) or a customer deleted in the dashboard leaves a
171
171
  // stored customer id Stripe no longer knows; checkout creates a fresh customer instead.
172
- ctx.log?.warn("subscription-stripe: stored customer unknown to Stripe, retrying without it");
172
+ ctx.obs.log?.warn(
173
+ "subscription-stripe: stored customer unknown to Stripe, retrying without it",
174
+ );
173
175
  session = await stripe.checkout.sessions.create(sessionParams(undefined));
174
176
  }
175
177
 
@@ -383,7 +385,7 @@ async function resolvePricesViaCache(
383
385
  } else {
384
386
  const message =
385
387
  outcome.reason instanceof Error ? outcome.reason.message : String(outcome.reason);
386
- ctx.log?.warn("subscription-stripe: prices.retrieve failed", { message });
388
+ ctx.obs.log?.warn("subscription-stripe: prices.retrieve failed", { message });
387
389
  }
388
390
  }
389
391
  }
@@ -402,7 +404,7 @@ export function createStripeRetrievePrices(runtime: StripeCtxRuntime, cache: Str
402
404
  const rawInterval = price.recurring?.interval;
403
405
  if (rawInterval && mapped.interval === null && !warnedUnknownIntervalPriceIds.has(priceId)) {
404
406
  warnedUnknownIntervalPriceIds.add(priceId);
405
- ctx.log?.warn("subscription-stripe: price has an unknown recurring interval", {
407
+ ctx.obs.log?.warn("subscription-stripe: price has an unknown recurring interval", {
406
408
  priceId,
407
409
  interval: rawInterval,
408
410
  });
@@ -114,11 +114,11 @@ export function createStripeRuntimes(deps: StripeRuntimeDeps): StripeRuntimes {
114
114
 
115
115
  async function ctxApiKey(ctx: HandlerContext): Promise<string> {
116
116
  let key: string | undefined;
117
- if (ctx.secrets) {
118
- const got = await requireSecretsContext(ctx, SUBSCRIPTION_STRIPE_FEATURE).get(
119
- SYSTEM_TENANT_ID,
120
- deps.apiKeyHandle.name,
121
- );
117
+ if (ctx.services.secrets) {
118
+ const got = await requireSecretsContext(
119
+ { secrets: ctx.services.secrets, user: ctx.user },
120
+ SUBSCRIPTION_STRIPE_FEATURE,
121
+ ).get(SYSTEM_TENANT_ID, deps.apiKeyHandle.name);
122
122
  key = parseStoredSecret(got?.reveal());
123
123
  }
124
124
  if (!key && deps.fallback.apiKey && deps.fallback.apiKey.length > 0) {
@@ -167,8 +167,8 @@ export function createStripeRuntimes(deps: StripeRuntimeDeps): StripeRuntimes {
167
167
  if (hasFallbackKey) return true;
168
168
  // Existence-only probe — called on every billing-plans page render,
169
169
  // must stay un-audited (see SecretsContext.has's own doc comment).
170
- return ctx.secrets
171
- ? await ctx.secrets.has(SYSTEM_TENANT_ID, deps.apiKeyHandle.name)
170
+ return ctx.services.secrets
171
+ ? await ctx.services.secrets.has(SYSTEM_TENANT_ID, deps.apiKeyHandle.name)
172
172
  : false;
173
173
  },
174
174
  },
@@ -332,12 +332,12 @@ export class TemplateNotFoundError extends Error {
332
332
  // Single source of truth for "this handler requires template-resolver".
333
333
  // Pattern symmetric with requireTextContent.
334
334
  export function requireTemplateResolver(
335
- ctx: { readonly templateResolver?: TemplateResolverApi } | object,
335
+ ctx: { readonly services: { readonly extensions: Readonly<Record<string, unknown>> } },
336
336
  callerName: string,
337
337
  ): TemplateResolverApi {
338
338
  // @cast-boundary engine-bridge — templateResolver kommt per extraContext
339
- // from App-Bootstrap; the framework container does not know this field on its own.
340
- const api = (ctx as { templateResolver?: TemplateResolverApi }).templateResolver;
339
+ // from App-Bootstrap and lands in services.extensions; the framework does not know its type.
340
+ const api = ctx.services.extensions["templateResolver"] as TemplateResolverApi | undefined;
341
341
  if (!api) {
342
342
  throw new InternalError({
343
343
  message:
@@ -1,4 +1,22 @@
1
1
  [
2
+ {
3
+ "version": "0.390.0",
4
+ "type": "fix",
5
+ "title": "Rail inserts no longer do nothing after the placeholder labels load",
6
+ "detail": "Inserting a placeholder into the subject, preheader, header chip or footer field could silently do nothing once the placeholder labels had loaded, because the replaced editor unregistered the handle of its remounted successor. The editors now report their handle from the React commit instead of tiptap's create and destroy callbacks."
7
+ },
8
+ {
9
+ "version": "0.390.0",
10
+ "type": "fix",
11
+ "title": "Placeholder inserts keep working after a line editor remounts",
12
+ "detail": "When a placeholder line editor (subject, preheader, header chip, footer note) remounted (labels arriving from the async validate query, or a new external value), TipTap could destroy the replaced editor after the new one was created. Its late null report then removed the live handle and placeholder inserts into that field did nothing. A null from a superseded editor is now ignored."
13
+ },
14
+ {
15
+ "version": "0.390.0",
16
+ "type": "improvement",
17
+ "title": "Split DocumentTiptapEditor into a model hook and render functions",
18
+ "detail": "DocumentTiptapEditor and its insert panel compose a model hook, pure helpers and render functions for toolbar, bubble menu, content and insert fields. Rendered markup and behavior are unchanged."
19
+ },
2
20
  {
3
21
  "version": "0.389.0",
4
22
  "type": "fix",
@@ -14,6 +14,7 @@ import {
14
14
  import { requireForTenant } from "../../shared/index.ts";
15
15
  import type { TemplateWriteScope } from "../layout-regions.ts";
16
16
  import type { LinkedResourceEntry } from "../linked-resources.ts";
17
+ import type { PrepareContext } from "../store-template-resource.ts";
17
18
  import {
18
19
  attachmentLimitFailure,
19
20
  attachmentNeedsMailTemplate,
@@ -48,8 +49,8 @@ export function targetExistsIssue(locale: string): FieldIssue {
48
49
  };
49
50
  }
50
51
 
51
- type BundleImport = {
52
- readonly ctx: HandlerContext;
52
+ type BundleImport<TCtx extends PrepareContext = HandlerContext> = {
53
+ readonly ctx: TCtx;
53
54
  readonly user: SessionUser;
54
55
  readonly tenantId: TenantId;
55
56
  readonly scope: TemplateWriteScope;
@@ -60,7 +61,7 @@ type BundleImport = {
60
61
  type BundleImportStatus = "draft" | "active";
61
62
 
62
63
  async function collectBundleIssues(
63
- args: BundleImport,
64
+ args: BundleImport<PrepareContext>,
64
65
  status: BundleImportStatus,
65
66
  ): Promise<readonly FieldIssue[]> {
66
67
  const { ctx, tenantId, scope, bundle, overwrite } = args;
@@ -113,7 +114,7 @@ function checkBundleAttachments(bundle: TemplateBundle): WriteResult<null> {
113
114
  // beforeTransaction half: validates the bundle and uploads every resource file to the target
114
115
  // tenant's storage. A failed or throwing upload releases the ones before it.
115
116
  export async function prepareTemplateBundleBeforeTransaction(
116
- args: BundleImport & { readonly status: BundleImportStatus },
117
+ args: BundleImport<PrepareContext> & { readonly status: BundleImportStatus },
117
118
  ): Promise<BeforeTransactionResult<PreparedBundleResources>> {
118
119
  const refused =
119
120
  refuseTenantScopeUnderSystemTenant(args.scope, args.tenantId) ??
@@ -1,7 +1,6 @@
1
1
  import { fetchOne } from "@cosmicdrift/kumiko-framework/db";
2
2
  import {
3
3
  defineWriteHandler,
4
- type HandlerContext,
5
4
  type SessionUser,
6
5
  SYSTEM_TENANT_ID,
7
6
  type TenantId,
@@ -18,6 +17,7 @@ import { isOneOf } from "@cosmicdrift/kumiko-types/type-guards";
18
17
  import * as z from "zod";
19
18
  import { requireForTenant } from "../../shared/index.ts";
20
19
  import { type LinkedResourceEntry, parseLinkedResourceEntries } from "../linked-resources.ts";
20
+ import type { PrepareContext } from "../store-template-resource.ts";
21
21
  import {
22
22
  createLinkedResourceCopies,
23
23
  prepareLinkedResourceCopiesBeforeTransaction,
@@ -43,7 +43,7 @@ import {
43
43
  } from "./template-write.ts";
44
44
 
45
45
  type CopyScope = "tenant" | "system";
46
- type WriteCtx = HandlerContext;
46
+ type WriteCtx = PrepareContext;
47
47
 
48
48
  function readDbFor(ctx: WriteCtx, scope: CopyScope) {
49
49
  return scope === "system" ? requireForTenant(ctx, SYSTEM_TENANT_ID).db : ctx.db;
@@ -124,7 +124,7 @@ export const exportBundleQuery = defineQueryHandler({
124
124
  }
125
125
  const attachment = isFileAttachmentEntry(entry);
126
126
  const file = await readTemplateResource(
127
- { db: ctx.db, files: ctx.files, user: query.user },
127
+ { db: ctx.db, services: { files: ctx.services.files, extensions: {} }, user: query.user },
128
128
  attachment ? entry.fileRefId : entry,
129
129
  );
130
130
  if (!file) throw new NotFoundError("template-resource-file", key);
@@ -412,7 +412,7 @@ export function makeOverviewQuery(policy: TemplateEditPolicy) {
412
412
  const truncated = fetchedRows.length > OVERVIEW_ROW_LIMIT;
413
413
  const rows = truncated ? fetchedRows.slice(0, OVERVIEW_ROW_LIMIT) : fetchedRows;
414
414
  if (truncated) {
415
- ctx.log?.warn("template overview: row limit reached, result is incomplete", {
415
+ ctx.obs.log?.warn("template overview: row limit reached, result is incomplete", {
416
416
  limit: OVERVIEW_ROW_LIMIT,
417
417
  });
418
418
  }
@@ -434,7 +434,7 @@ export function makeOverviewQuery(policy: TemplateEditPolicy) {
434
434
  ? withOverriddenBy(
435
435
  rowsWithUsage,
436
436
  tenantOverrideCountsResultSchema.parse(
437
- await ctx.query(TemplateResolverQueries.tenantOverrideCounts, {}),
437
+ await ctx.dispatch.query(TemplateResolverQueries.tenantOverrideCounts, {}),
438
438
  ),
439
439
  )
440
440
  : rowsWithUsage;
@@ -17,7 +17,7 @@ export function testMailChannelContext(ctx: HandlerContext, user: SessionUser):
17
17
  registry: ctx.registry,
18
18
  sseBroker: undefined,
19
19
  tenantId: user.tenantId,
20
- secrets: ctx.secrets,
20
+ secrets: ctx.services.secrets,
21
21
  config: ctx.config,
22
22
  readAttachment: createEmailAttachmentReader(
23
23
  ctx.db,
@@ -112,7 +112,7 @@ export function makeSendTestWrite(policy: TemplateEditPolicy) {
112
112
  sentStatus = result.status;
113
113
  } catch (error) {
114
114
  // Class and code only: the message can carry the address or a provider response body.
115
- ctx.log?.warn("template test mail: channel threw", {
115
+ ctx.obs.log?.warn("template test mail: channel threw", {
116
116
  tenantId: event.user.tenantId,
117
117
  errorClass: error instanceof Error ? error.name : typeof error,
118
118
  errorCode: errorCodeOf(error),
@@ -120,7 +120,7 @@ export function makeSendTestWrite(policy: TemplateEditPolicy) {
120
120
  return testMailFailed("channel_error");
121
121
  }
122
122
  if (sentStatus !== "sent") {
123
- ctx.log?.warn("template test mail: channel did not send", {
123
+ ctx.obs.log?.warn("template test mail: channel did not send", {
124
124
  tenantId: event.user.tenantId,
125
125
  status: sentStatus,
126
126
  });
@@ -10,7 +10,9 @@ const tenantIdsSchema = z.array(z.string());
10
10
 
11
11
  type TenantIdQueryContext = {
12
12
  readonly registry: { getQueryHandler(name: string): unknown };
13
- readonly query: (name: string, payload: Record<string, never>) => Promise<unknown>;
13
+ readonly dispatch: {
14
+ readonly query: (name: string, payload: Record<string, never>) => Promise<unknown>;
15
+ };
14
16
  };
15
17
 
16
18
  /** Null when the tenant feature is not installed: every tenant row then counts. */
@@ -18,7 +20,9 @@ export async function loadEnabledTenantIds(
18
20
  ctx: TenantIdQueryContext,
19
21
  ): Promise<ReadonlySet<string> | null> {
20
22
  if (ctx.registry.getQueryHandler(TenantQueries.activeTenantIds) === undefined) return null;
21
- return new Set(tenantIdsSchema.parse(await ctx.query(TenantQueries.activeTenantIds, {})));
23
+ return new Set(
24
+ tenantIdsSchema.parse(await ctx.dispatch.query(TenantQueries.activeTenantIds, {})),
25
+ );
22
26
  }
23
27
 
24
28
  export type OverrideCandidate = Pick<TemplateResourceRow, "tenantId" | "status" | "version">;
@@ -31,7 +31,9 @@ function chunksOf<T>(items: readonly T[], size: number): T[][] {
31
31
  }
32
32
 
33
33
  type DirectoryQueryContext = {
34
- readonly query: (name: string, payload: { ids: string[] }) => Promise<unknown>;
34
+ readonly dispatch: {
35
+ readonly query: (name: string, payload: { ids: string[] }) => Promise<unknown>;
36
+ };
35
37
  };
36
38
 
37
39
  // Looks up only the ids the overrides need, so labels never depend on the directory's default cap.
@@ -42,7 +44,7 @@ async function labelsFor(
42
44
  ): Promise<ReadonlyMap<string, string>> {
43
45
  const labels = new Map<string, string>();
44
46
  for (const chunk of chunksOf([...ids], MAX_LIST_LIMIT)) {
45
- const raw = await ctx.query(queryName, { ids: chunk });
47
+ const raw = await ctx.dispatch.query(queryName, { ids: chunk });
46
48
  for (const row of directorySchema.parse(raw).rows) labels.set(row.id, row.label);
47
49
  }
48
50
  return labels;
@@ -28,7 +28,7 @@ export function makeUsageQuery(policy: TemplateEditPolicy) {
28
28
  { orderBy: { col: "id", direction: "asc" }, limit: MAIL_ROW_LIMIT + 1 },
29
29
  );
30
30
  if (fetchedRows.length > MAIL_ROW_LIMIT) {
31
- ctx.log?.warn("template usage: row limit reached, result is incomplete", {
31
+ ctx.obs.log?.warn("template usage: row limit reached, result is incomplete", {
32
32
  limit: MAIL_ROW_LIMIT,
33
33
  });
34
34
  }
@@ -107,7 +107,12 @@ export async function seedSystemTemplate(
107
107
  ): Promise<{ id: string }> {
108
108
  const tenantId = SYSTEM_TENANT_ID;
109
109
  const by = opts.by ?? createSystemUser(tenantId);
110
- const tdb = createTenantDb({ db, tenantId, mode: "system" });
110
+ const tdb = createTenantDb({
111
+ db,
112
+ tenantId,
113
+ mode: "system",
114
+ reason: "system templates live in the system tenant",
115
+ });
111
116
 
112
117
  const label = `seedSystemTemplate ${opts.slug}/${opts.kind}/${opts.locale}`;
113
118
  const slug = templateSlugSchema.safeParse(opts.slug);
@@ -408,7 +413,12 @@ export async function seedTextBlock(
408
413
  opts: SeedTextBlockOptions,
409
414
  ): Promise<{ id: string }> {
410
415
  const by = opts.by ?? createSystemUser(opts.tenantId);
411
- const tdb = createTenantDb({ db, tenantId: opts.tenantId, mode: "system" });
416
+ const tdb = createTenantDb({
417
+ db,
418
+ tenantId: opts.tenantId,
419
+ mode: "system",
420
+ reason: "seeding writes text blocks into an explicitly named tenant",
421
+ });
412
422
 
413
423
  const existingRow = await fetchOne<TemplateResourceRow>(db, templateResourcesTable, {
414
424
  tenantId: opts.tenantId,