@cosmicdrift/kumiko-samples 0.362.0 → 0.363.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 (252) hide show
  1. package/package.json +1 -1
  2. package/packages/bundled-features/package.json +17 -6
  3. package/packages/bundled-features/src/admin-shell/changes.json +6 -0
  4. package/packages/bundled-features/src/admin-shell/feature.ts +8 -1
  5. package/packages/bundled-features/src/agent-tools/agent-manifest.ts +3 -4
  6. package/packages/bundled-features/src/agent-tools/filter-ops.ts +1 -3
  7. package/packages/bundled-features/src/agent-tools/tool-catalog.ts +1 -3
  8. package/packages/bundled-features/src/agent-tools/tool-dispatch.ts +2 -1
  9. package/packages/bundled-features/src/auth-email-password/changes.json +14 -0
  10. package/packages/bundled-features/src/auth-email-password/email-templates.ts +12 -138
  11. package/packages/bundled-features/src/auth-email-password/feature.ts +30 -9
  12. package/packages/bundled-features/src/auth-email-password/handlers/invite-create.write.ts +4 -5
  13. package/packages/bundled-features/src/auth-email-password/handlers/request-account-unlock.write.ts +2 -5
  14. package/packages/bundled-features/src/auth-email-password/handlers/request-email-verification.write.ts +2 -5
  15. package/packages/bundled-features/src/auth-email-password/handlers/request-password-reset.write.ts +2 -5
  16. package/packages/bundled-features/src/auth-email-password/handlers/signup-confirm.write.ts +3 -4
  17. package/packages/bundled-features/src/auth-email-password/handlers/token-request-handler.ts +3 -12
  18. package/packages/bundled-features/src/auth-email-password/index.ts +2 -17
  19. package/packages/bundled-features/src/auth-email-password/magic-link-mail.ts +30 -22
  20. package/packages/bundled-features/src/auth-email-password/mail-notifications.ts +158 -0
  21. package/packages/bundled-features/src/auth-email-password/signup-activation.ts +2 -3
  22. package/packages/bundled-features/src/auth-foundation/types.ts +8 -18
  23. package/packages/bundled-features/src/auth-mfa/feature.ts +8 -10
  24. package/packages/bundled-features/src/auth-mfa/hmac-token-codec.ts +4 -4
  25. package/packages/bundled-features/src/auth-mfa/mfa-challenge-token.ts +3 -3
  26. package/packages/bundled-features/src/auth-mfa/mfa-preauth-setup-token.ts +3 -3
  27. package/packages/bundled-features/src/auth-mfa/mfa-setup-token.ts +8 -8
  28. package/packages/bundled-features/src/billing-foundation/changes.json +7 -0
  29. package/packages/bundled-features/src/billing-foundation/constants.ts +5 -4
  30. package/packages/bundled-features/src/billing-foundation/consumer-protection/confirmation-mail.ts +38 -44
  31. package/packages/bundled-features/src/billing-foundation/consumer-protection/issue-confirmation.ts +3 -3
  32. package/packages/bundled-features/src/billing-foundation/consumer-protection/mail-notifications.ts +115 -0
  33. package/packages/bundled-features/src/billing-foundation/consumer-protection/register-consent.ts +2 -0
  34. package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-mail.ts +30 -30
  35. package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-notify.ts +2 -2
  36. package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-pages.ts +1 -4
  37. package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-request.ts +2 -2
  38. package/packages/bundled-features/src/billing-foundation/get-subscription-for-tenant.ts +2 -2
  39. package/packages/bundled-features/src/billing-foundation/handlers/create-portal-session.write.ts +4 -9
  40. package/packages/bundled-features/src/billing-foundation/handlers/list-subscriptions.query.ts +1 -1
  41. package/packages/bundled-features/src/billing-foundation/handlers/process-event.write.ts +2 -2
  42. package/packages/bundled-features/src/billing-foundation/handlers/process-payment-event.write.ts +1 -1
  43. package/packages/bundled-features/src/billing-foundation/subscription-tier-sync.ts +3 -6
  44. package/packages/bundled-features/src/cap-overview/handlers/tenant-caps-list.query.ts +2 -1
  45. package/packages/bundled-features/src/cap-overview/web/cap-usage-cell.tsx +6 -4
  46. package/packages/bundled-features/src/channel-email/changes.json +19 -0
  47. package/packages/bundled-features/src/channel-email/email-channel.ts +122 -29
  48. package/packages/bundled-features/src/channel-email/feature.ts +6 -3
  49. package/packages/bundled-features/src/channel-email/index.ts +7 -1
  50. package/packages/bundled-features/src/channel-email/pii-guard.ts +13 -0
  51. package/packages/bundled-features/src/channel-email/smtp-transport.ts +15 -1
  52. package/packages/bundled-features/src/channel-email/types.ts +23 -0
  53. package/packages/bundled-features/src/channel-in-app/handlers/mark-read.write.ts +2 -2
  54. package/packages/bundled-features/src/config/handlers/set.write.ts +1 -5
  55. package/packages/bundled-features/src/custom-fields/db/queries/user-data-rights.ts +2 -2
  56. package/packages/bundled-features/src/custom-fields/lib/value-schema.ts +10 -11
  57. package/packages/bundled-features/src/custom-fields/wire-user-data-rights.ts +7 -6
  58. package/packages/bundled-features/src/data-retention/changes.json +6 -0
  59. package/packages/bundled-features/src/data-retention/index.ts +1 -0
  60. package/packages/bundled-features/src/data-retention/run-retention-cleanup.ts +8 -6
  61. package/packages/bundled-features/src/delivery/attachment-ref.ts +84 -0
  62. package/packages/bundled-features/src/delivery/changes.json +48 -0
  63. package/packages/bundled-features/src/delivery/channel-context.ts +129 -4
  64. package/packages/bundled-features/src/delivery/constants.ts +2 -0
  65. package/packages/bundled-features/src/delivery/delivery-service.ts +104 -26
  66. package/packages/bundled-features/src/delivery/feature.ts +1 -1
  67. package/packages/bundled-features/src/delivery/index.ts +17 -2
  68. package/packages/bundled-features/src/delivery/jobs.ts +48 -5
  69. package/packages/bundled-features/src/delivery/notify-factory.ts +14 -9
  70. package/packages/bundled-features/src/delivery/testing.ts +9 -1
  71. package/packages/bundled-features/src/delivery/types.ts +28 -18
  72. package/packages/bundled-features/src/delivery/unsubscribe.ts +5 -2
  73. package/packages/bundled-features/src/derivatives-sharp/render.ts +1 -22
  74. package/packages/bundled-features/src/document-ingest-foundation/pages.ts +6 -3
  75. package/packages/bundled-features/src/feature-toggles/toggle-sync-signal.ts +4 -4
  76. package/packages/bundled-features/src/file-foundation/index.ts +7 -0
  77. package/packages/bundled-features/src/file-foundation/read-file-ref-content.ts +86 -0
  78. package/packages/bundled-features/src/folders/web/folder-manager.tsx +5 -2
  79. package/packages/bundled-features/src/form-draft/db/queries/cleanup.ts +11 -11
  80. package/packages/bundled-features/src/form-draft/db/queries/draft-count.ts +2 -2
  81. package/packages/bundled-features/src/form-draft/db/queries/owned-file-refs.ts +2 -2
  82. package/packages/bundled-features/src/form-draft/release-file-refs.ts +3 -2
  83. package/packages/bundled-features/src/inbound-mail-foundation/handlers/connect-account.write.ts +1 -1
  84. package/packages/bundled-features/src/inbound-mail-foundation/handlers/ingest-message.write.ts +6 -6
  85. package/packages/bundled-features/src/inbound-mail-foundation/handlers/list-accounts.query.ts +1 -1
  86. package/packages/bundled-features/src/inbound-mail-foundation/handlers/list-messages.query.ts +1 -1
  87. package/packages/bundled-features/src/inbound-mail-foundation/handlers/scope-visibility.ts +2 -2
  88. package/packages/bundled-features/src/inbound-mail-foundation/oauth-state.ts +7 -7
  89. package/packages/bundled-features/src/inbound-mail-foundation/retention-sweep.ts +2 -6
  90. package/packages/bundled-features/src/inbound-mail-foundation/tenant-destroy-hook.ts +5 -13
  91. package/packages/bundled-features/src/inbound-mail-foundation/types.ts +4 -6
  92. package/packages/bundled-features/src/inbound-mail-foundation/watch-supervisor.ts +40 -25
  93. package/packages/bundled-features/src/inbound-provider-imap/imap-client.ts +4 -3
  94. package/packages/bundled-features/src/jobs/handlers/catalog.query.ts +1 -1
  95. package/packages/bundled-features/src/ledger/recurring.ts +1 -3
  96. package/packages/bundled-features/src/managed-pages/branding.ts +33 -9
  97. package/packages/bundled-features/src/managed-pages/changes.json +6 -0
  98. package/packages/bundled-features/src/managed-pages/feature.ts +1 -0
  99. package/packages/bundled-features/src/managed-pages/i18n.ts +1 -0
  100. package/packages/bundled-features/src/managed-pages/screens/branding-screen.ts +8 -0
  101. package/packages/bundled-features/src/page-render/branding.ts +3 -0
  102. package/packages/bundled-features/src/page-render/markdown.ts +1 -1
  103. package/packages/bundled-features/src/personal-access-tokens/feature.ts +2 -4
  104. package/packages/bundled-features/src/personal-access-tokens/handlers/list.query.ts +2 -1
  105. package/packages/bundled-features/src/personal-access-tokens/index.ts +1 -1
  106. package/packages/bundled-features/src/personal-access-tokens/revoke-for-user.ts +2 -1
  107. package/packages/bundled-features/src/renderer-foundation/README.md +11 -10
  108. package/packages/bundled-features/src/renderer-foundation/api.ts +14 -14
  109. package/packages/bundled-features/src/renderer-foundation/changes.json +39 -0
  110. package/packages/bundled-features/src/renderer-foundation/constants.ts +6 -8
  111. package/packages/bundled-features/src/renderer-foundation/feature.ts +7 -7
  112. package/packages/bundled-features/src/renderer-foundation/index.ts +13 -2
  113. package/packages/bundled-features/src/renderer-foundation/types.ts +132 -35
  114. package/packages/bundled-features/src/renderer-pdf/feature.ts +7 -1
  115. package/packages/bundled-features/src/sessions/db/queries/cleanup.ts +2 -2
  116. package/packages/bundled-features/src/sessions/feature.ts +2 -4
  117. package/packages/bundled-features/src/sessions/handlers/list.query.ts +2 -1
  118. package/packages/bundled-features/src/shared/attachment-file-name.ts +39 -0
  119. package/packages/bundled-features/src/shared/index.ts +6 -0
  120. package/packages/bundled-features/src/shared/is-tenant-db.ts +1 -1
  121. package/packages/bundled-features/src/shared/mail-template-body.ts +25 -0
  122. package/packages/bundled-features/src/shared/sharp-input-limits.ts +26 -0
  123. package/packages/bundled-features/src/shared/signup-handover.ts +4 -4
  124. package/packages/bundled-features/src/tags/handlers/assign-tag.write.ts +5 -1
  125. package/packages/bundled-features/src/tags/web/tag-chip.tsx +2 -1
  126. package/packages/bundled-features/src/template-resolver/README.md +63 -7
  127. package/packages/bundled-features/src/template-resolver/api.ts +83 -8
  128. package/packages/bundled-features/src/template-resolver/changes.json +118 -0
  129. package/packages/bundled-features/src/template-resolver/compile-document-body.ts +214 -0
  130. package/packages/bundled-features/src/template-resolver/constants.ts +39 -11
  131. package/packages/bundled-features/src/template-resolver/document-sanitizer.ts +335 -0
  132. package/packages/bundled-features/src/template-resolver/document-schema.ts +323 -0
  133. package/packages/bundled-features/src/template-resolver/feature.ts +56 -6
  134. package/packages/bundled-features/src/template-resolver/handlers/bundle-import.ts +188 -0
  135. package/packages/bundled-features/src/template-resolver/handlers/collection-set.write.ts +30 -2
  136. package/packages/bundled-features/src/template-resolver/handlers/copy-locale.write.ts +142 -0
  137. package/packages/bundled-features/src/template-resolver/handlers/diff-to-system.query.ts +72 -0
  138. package/packages/bundled-features/src/template-resolver/handlers/export-bundle.query.ts +83 -0
  139. package/packages/bundled-features/src/template-resolver/handlers/find-by-id.query.ts +3 -3
  140. package/packages/bundled-features/src/template-resolver/handlers/import-bundle-system.write.ts +42 -0
  141. package/packages/bundled-features/src/template-resolver/handlers/import-bundle.write.ts +22 -0
  142. package/packages/bundled-features/src/template-resolver/handlers/layout-source.query.ts +108 -0
  143. package/packages/bundled-features/src/template-resolver/handlers/list.query.ts +5 -7
  144. package/packages/bundled-features/src/template-resolver/handlers/overview.query.ts +250 -0
  145. package/packages/bundled-features/src/template-resolver/handlers/preview.query.ts +25 -0
  146. package/packages/bundled-features/src/template-resolver/handlers/reset-to-system.write.ts +58 -0
  147. package/packages/bundled-features/src/template-resolver/handlers/resource-link-pdf.write.ts +34 -0
  148. package/packages/bundled-features/src/template-resolver/handlers/resource-remove.write.ts +50 -0
  149. package/packages/bundled-features/src/template-resolver/handlers/resource-shared.ts +24 -0
  150. package/packages/bundled-features/src/template-resolver/handlers/resource-upload.write.ts +135 -0
  151. package/packages/bundled-features/src/template-resolver/handlers/send-test.write.ts +106 -0
  152. package/packages/bundled-features/src/template-resolver/handlers/set.write.ts +32 -3
  153. package/packages/bundled-features/src/template-resolver/handlers/shared.ts +7 -2
  154. package/packages/bundled-features/src/template-resolver/handlers/template-gates.ts +184 -0
  155. package/packages/bundled-features/src/template-resolver/handlers/template-write.ts +183 -0
  156. package/packages/bundled-features/src/template-resolver/handlers/tenant-overrides.query.ts +55 -0
  157. package/packages/bundled-features/src/template-resolver/handlers/toggle-status.write.ts +57 -10
  158. package/packages/bundled-features/src/template-resolver/handlers/upsert-system.write.ts +19 -71
  159. package/packages/bundled-features/src/template-resolver/handlers/upsert-tenant.write.ts +15 -76
  160. package/packages/bundled-features/src/template-resolver/handlers/usage.query.ts +17 -0
  161. package/packages/bundled-features/src/template-resolver/handlers/validate.query.ts +116 -0
  162. package/packages/bundled-features/src/template-resolver/index.ts +100 -0
  163. package/packages/bundled-features/src/template-resolver/layout-regions.ts +206 -0
  164. package/packages/bundled-features/src/template-resolver/linked-resources.ts +102 -0
  165. package/packages/bundled-features/src/template-resolver/preview-display-html.ts +36 -0
  166. package/packages/bundled-features/src/template-resolver/print-document.ts +58 -0
  167. package/packages/bundled-features/src/template-resolver/qualified-names.ts +30 -0
  168. package/packages/bundled-features/src/template-resolver/record-mapping.ts +26 -0
  169. package/packages/bundled-features/src/template-resolver/resource-image.ts +47 -0
  170. package/packages/bundled-features/src/template-resolver/seeding.ts +147 -17
  171. package/packages/bundled-features/src/template-resolver/store-template-resource.ts +416 -0
  172. package/packages/bundled-features/src/template-resolver/table.ts +14 -0
  173. package/packages/bundled-features/src/template-resolver/template-bundle.ts +79 -0
  174. package/packages/bundled-features/src/template-resolver/template-diff.ts +78 -0
  175. package/packages/bundled-features/src/template-resolver/template-meta.ts +129 -0
  176. package/packages/bundled-features/src/template-resolver/template-preview.ts +573 -0
  177. package/packages/bundled-features/src/template-resolver/template-resources.ts +74 -0
  178. package/packages/bundled-features/src/template-resolver/template-usage.ts +48 -0
  179. package/packages/bundled-features/src/template-resolver/template-validation.ts +229 -0
  180. package/packages/bundled-features/src/template-resolver/testing.ts +5 -5
  181. package/packages/bundled-features/src/template-resolver/web/client-plugin.tsx +9 -6
  182. package/packages/bundled-features/src/template-resolver/web/document-editor-extensions.ts +162 -0
  183. package/packages/bundled-features/src/template-resolver/web/document-editor.tsx +28 -0
  184. package/packages/bundled-features/src/template-resolver/web/document-tiptap-editor.tsx +850 -0
  185. package/packages/bundled-features/src/template-resolver/web/document-toolbar-blocks.ts +61 -0
  186. package/packages/bundled-features/src/template-resolver/web/editor-handle.ts +26 -0
  187. package/packages/bundled-features/src/template-resolver/web/i18n.ts +100 -0
  188. package/packages/bundled-features/src/template-resolver/web/index.ts +19 -0
  189. package/packages/bundled-features/src/template-resolver/web/placeholder-line-editor-lazy.tsx +36 -0
  190. package/packages/bundled-features/src/template-resolver/web/placeholder-line-editor.tsx +169 -0
  191. package/packages/bundled-features/src/template-resolver/web/placeholder-text.ts +37 -0
  192. package/packages/bundled-features/src/template-resolver/web/use-stable-by-key.ts +14 -0
  193. package/packages/bundled-features/src/tenant/handlers/team-list.query.ts +3 -2
  194. package/packages/bundled-features/src/tenant/seeding.ts +41 -7
  195. package/packages/bundled-features/src/tenant-lifecycle/boot-checks.ts +1 -1
  196. package/packages/bundled-features/src/tenant-lifecycle/handlers/cancel-destruction.write.ts +2 -1
  197. package/packages/bundled-features/src/user/db/queries/stream-tenant-backfill.ts +4 -4
  198. package/packages/bundled-features/src/user/handlers/detail.query.ts +3 -5
  199. package/packages/bundled-features/src/user/roles-input-schema.ts +2 -2
  200. package/packages/bundled-features/src/user-data-rights/changes.json +7 -0
  201. package/packages/bundled-features/src/user-data-rights/db/queries/forget-cleanup.ts +2 -2
  202. package/packages/bundled-features/src/user-data-rights/email-templates.ts +5 -149
  203. package/packages/bundled-features/src/user-data-rights/feature.ts +72 -35
  204. package/packages/bundled-features/src/user-data-rights/handlers/cancel-deletion.write.ts +2 -1
  205. package/packages/bundled-features/src/user-data-rights/handlers/request-deletion.write.ts +5 -11
  206. package/packages/bundled-features/src/user-data-rights/lib/default-mailers.ts +134 -53
  207. package/packages/bundled-features/src/user-data-rights/lib/update-user-lifecycle.ts +2 -2
  208. package/packages/bundled-features/src/user-data-rights/mail-notifications.ts +157 -0
  209. package/packages/bundled-features/src/user-data-rights/run-forget-cleanup.ts +1 -1
  210. package/packages/bundled-features/src/user-data-rights/web/privacy-center-screen.tsx +2 -1
  211. package/packages/bundled-features/src/user-profile/web/profile-screen.tsx +2 -1
  212. package/packages/bundled-features/src/waitlist/changes.json +7 -0
  213. package/packages/bundled-features/src/waitlist/feature.ts +17 -0
  214. package/packages/bundled-features/src/waitlist/handlers/submit.write.ts +4 -5
  215. package/packages/bundled-features/src/waitlist/mail.ts +69 -18
  216. package/packages/bundled-features/src/workflow-runner/event-trigger.ts +1 -2
  217. package/samples/apps/ui-walkthrough/package.json +2 -0
  218. package/samples/apps/ui-walkthrough/src/app/auth-constants.ts +9 -0
  219. package/samples/apps/ui-walkthrough/src/app/client.tsx +6 -0
  220. package/samples/apps/ui-walkthrough/src/app/seed-templates.ts +154 -0
  221. package/samples/apps/ui-walkthrough/src/app/server.ts +5 -1
  222. package/samples/apps/ui-walkthrough/src/app/shell.tsx +12 -1
  223. package/samples/apps/ui-walkthrough/src/app/template-seed-data.ts +354 -0
  224. package/samples/apps/ui-walkthrough/src/features/billing/feature.ts +68 -0
  225. package/samples/apps/ui-walkthrough/src/features/billing/i18n.ts +18 -0
  226. package/samples/apps/ui-walkthrough/src/features/billing/index.ts +2 -0
  227. package/samples/apps/ui-walkthrough/src/features/billing/web/index.ts +7 -0
  228. package/samples/apps/ui-walkthrough/src/run-config.ts +37 -1
  229. package/samples/apps/use-all-bundled/e2e-billing-gallery/gallery-features.ts +0 -2
  230. package/samples/apps/use-all-bundled/feature-manifest.json +115 -20
  231. package/samples/apps/use-all-bundled/kumiko/migrations/0045_template-resource-meta.rebuild.json +4 -0
  232. package/samples/apps/use-all-bundled/kumiko/migrations/0046_pdf-rendered.rebuild.json +4 -0
  233. package/samples/apps/use-all-bundled/package.json +2 -0
  234. package/samples/apps/use-all-bundled/playwright.config.ts +38 -3
  235. package/samples/apps/use-all-bundled/schema/generate.ts +13 -3
  236. package/samples/apps/use-all-bundled/schema/schema.generated.ts +3 -0
  237. package/samples/apps/use-all-bundled/scripts/check-coverage.ts +3 -0
  238. package/samples/apps/use-all-bundled/src/app/auth-constants.ts +3 -0
  239. package/samples/apps/use-all-bundled/src/app/client.tsx +4 -0
  240. package/samples/apps/use-all-bundled/src/app/seed-documents.ts +128 -0
  241. package/samples/apps/use-all-bundled/src/app/seed.ts +4 -0
  242. package/samples/apps/use-all-bundled/src/run-config.ts +11 -11
  243. package/samples/recipes/delivery-notifications/README.md +26 -25
  244. package/samples/recipes/delivery-notifications/package.json +2 -1
  245. package/samples/recipes/delivery-notifications/src/feature.ts +64 -58
  246. package/samples/recipes/seed-migration/README.md +1 -1
  247. package/packages/bundled-features/src/renderer-simple/changes.json +0 -27
  248. package/packages/bundled-features/src/renderer-simple/feature.ts +0 -66
  249. package/packages/bundled-features/src/renderer-simple/index.ts +0 -8
  250. package/packages/bundled-features/src/renderer-simple/mail-blocks.ts +0 -138
  251. package/packages/bundled-features/src/renderer-simple/resolve-variables.ts +0 -57
  252. package/packages/bundled-features/src/renderer-simple/simple-renderer.ts +0 -304
@@ -1,32 +1,63 @@
1
1
  import { fetchOne } from "@cosmicdrift/kumiko-framework/bun-db";
2
- import { defineWriteHandler } from "@cosmicdrift/kumiko-framework/engine";
3
- import { NotFoundError, writeFailure } from "@cosmicdrift/kumiko-framework/errors";
2
+ import {
3
+ defineWriteHandler,
4
+ type Registry,
5
+ SYSTEM_TENANT_ID,
6
+ } from "@cosmicdrift/kumiko-framework/engine";
7
+ import {
8
+ type FieldIssue,
9
+ NotFoundError,
10
+ ValidationError,
11
+ writeFailure,
12
+ } from "@cosmicdrift/kumiko-framework/errors";
4
13
  import * as z from "zod";
14
+ import { requireForTenant } from "../../shared/index.js";
5
15
  import type { TemplateResourceRow } from "../table.js";
6
16
  import { templateResourcesTable } from "../table.js";
7
17
  import { executor } from "./shared.js";
18
+ import { publishGateIssues } from "./template-gates.js";
8
19
 
9
20
  type TemplateStatus = "active" | "archived";
21
+ type StatusTarget = "own" | "system";
10
22
 
11
- function createStatusUpdateHandler(name: string, status: TemplateStatus, description: string) {
23
+ function isRowOfTarget(row: TemplateResourceRow, target: StatusTarget, callerTenantId: string) {
24
+ return target === "system"
25
+ ? row.tenantId === SYSTEM_TENANT_ID && row.scope === "system"
26
+ : row.tenantId === callerTenantId && row.scope === "tenant";
27
+ }
28
+
29
+ function createStatusUpdateHandler(
30
+ name: string,
31
+ target: StatusTarget,
32
+ status: TemplateStatus,
33
+ description: string,
34
+ gate?: (registry: Registry, row: TemplateResourceRow) => readonly FieldIssue[],
35
+ ) {
12
36
  return defineWriteHandler({
13
37
  name,
14
38
  schema: z.object({ id: z.string().min(1) }),
15
- access: { roles: ["TenantAdmin", "SystemAdmin"] },
39
+ access: { roles: target === "system" ? ["SystemAdmin"] : ["TenantAdmin", "SystemAdmin"] },
40
+ // A system template change reaches every tenant without an override.
41
+ ...(target === "system" ? { agent: { risk: "high" as const } } : {}),
16
42
  description,
17
43
  handler: async (event, ctx) => {
18
- // ctx.db is tenant-scoped: a foreign tenant's id reads as absent → NotFound.
19
- // Cross-tenant toggling needs SystemAdmin with tenantIdOverride.
20
- const existing = await fetchOne<TemplateResourceRow>(ctx.db, templateResourcesTable, {
44
+ const scoped = target === "system" ? requireForTenant(ctx, SYSTEM_TENANT_ID) : null;
45
+ const db = scoped?.db ?? ctx.db;
46
+ // The tenant db also reads system rows; the row's own tenant must match
47
+ // the target or the executor would append to a foreign event stream.
48
+ const existing = await fetchOne<TemplateResourceRow>(db, templateResourcesTable, {
21
49
  id: event.payload.id,
22
50
  });
23
- if (!existing) {
51
+ if (!existing || !isRowOfTarget(existing, target, event.user.tenantId)) {
24
52
  return writeFailure(new NotFoundError("template-resource", event.payload.id));
25
53
  }
54
+ const issues = gate?.(ctx.registry, existing) ?? [];
55
+ if (issues.length > 0) return writeFailure(new ValidationError({ fields: [...issues] }));
26
56
  const result = await executor.update(
27
57
  { id: existing.id, version: existing.version, changes: { status } },
28
58
  event.user,
29
- ctx.db,
59
+ db,
60
+ scoped ? { streamTenantId: scoped.streamTenantId } : undefined,
30
61
  );
31
62
  if (!result.isSuccess) return result;
32
63
  return { isSuccess: true as const, data: { id: String(existing.id), status } };
@@ -36,11 +67,27 @@ function createStatusUpdateHandler(name: string, status: TemplateStatus, descrip
36
67
 
37
68
  export const archiveWrite = createStatusUpdateHandler(
38
69
  "archive",
70
+ "own",
39
71
  "archived",
40
72
  "Sets one template resource of the caller's tenant to archived so the resolver stops serving it while the row and its content survive; use it to retire a template reversibly instead of deleting it.",
41
73
  );
42
74
  export const publishWrite = createStatusUpdateHandler(
43
75
  "publish",
76
+ "own",
77
+ "active",
78
+ "Sets one template resource of the caller's tenant to active so the resolver starts serving it; use it to release a draft written by upsert-tenant, or to bring an archived template back; a mail template with unknown placeholders or unused required variables is refused.",
79
+ publishGateIssues,
80
+ );
81
+ export const archiveSystemWrite = createStatusUpdateHandler(
82
+ "archive-system",
83
+ "system",
84
+ "archived",
85
+ "Sets one system-default template to archived so the resolver stops serving it to every tenant that has no override; SystemAdmin only, reversible with publish-system.",
86
+ );
87
+ export const publishSystemWrite = createStatusUpdateHandler(
88
+ "publish-system",
89
+ "system",
44
90
  "active",
45
- "Sets one template resource of the caller's tenant to active so the resolver starts serving it; use it to release a draft written by upsert-tenant, or to bring an archived template back.",
91
+ "Sets one system-default template to active so the resolver serves it again to every tenant that has no override; SystemAdmin only, a mail template with unknown placeholders or unused required variables is refused.",
92
+ publishGateIssues,
46
93
  );
@@ -1,13 +1,7 @@
1
- import { fetchOne } from "@cosmicdrift/kumiko-framework/bun-db";
2
- import {
3
- defineWriteHandler,
4
- SYSTEM_TENANT_ID,
5
- type TenantId,
6
- } from "@cosmicdrift/kumiko-framework/engine";
7
- import { requireForTenant } from "../../shared/index.js";
8
- import type { TemplateResourceRow } from "../table.js";
9
- import { templateResourcesTable } from "../table.js";
10
- import { executor, upsertPayloadSchema } from "./shared.js";
1
+ import { defineWriteHandler, SYSTEM_TENANT_ID } from "@cosmicdrift/kumiko-framework/engine";
2
+ import { resolveTemplateVariables } from "../template-validation.js";
3
+ import { upsertPayloadSchema } from "./shared.js";
4
+ import { writeTemplateRow } from "./template-write.js";
11
5
 
12
6
  // System-Template anlegen/updaten. Liegt unter SYSTEM_TENANT_ID,
13
7
  // scope='system'. Nur SystemAdmin (globale Rolle). TenantAdmin kann
@@ -18,65 +12,19 @@ export const upsertSystemWrite = defineWriteHandler({
18
12
  schema: upsertPayloadSchema,
19
13
  access: { roles: ["SystemAdmin"] },
20
14
  description:
21
- "Creates or overwrites a system-default template under the system tenant with its full variable schema and linked resources, active straight away with no draft stage; SystemAdmin only, since tenants shadow these defaults through upsert-tenant instead.",
22
- handler: async (event, ctx) => {
23
- // @cast-boundary engine-payload — SYSTEM_TENANT_ID is a UUID literal,
24
- // asserted to the branded TenantId (parseTenantId equivalent).
25
- const tenantId = SYSTEM_TENANT_ID as TenantId;
26
- const { db, streamTenantId } = requireForTenant(ctx, tenantId);
27
-
28
- const existing = await fetchOne<TemplateResourceRow>(db, templateResourcesTable, {
29
- tenantId,
30
- slug: event.payload.slug,
31
- kind: event.payload.kind,
32
- locale: event.payload.locale,
33
- });
34
-
35
- const fields = {
36
- slug: event.payload.slug,
37
- kind: event.payload.kind,
38
- locale: event.payload.locale,
39
- content: event.payload.content,
40
- contentFormat: event.payload.contentFormat,
41
- variableSchema: JSON.stringify(event.payload.variableSchema),
42
- linkedResources: JSON.stringify(event.payload.linkedResources),
43
- title: event.payload.title ?? null,
44
- folder: event.payload.folder ?? null,
45
- scope: "system" as const,
46
- parentTemplateId: event.payload.parentTemplateId ?? null,
47
- // System-Defaults sind sofort active (kein draft-Stage für seeds).
48
- status: "active" as const,
49
- };
50
-
51
- if (existing) {
52
- const result = await executor.update(
53
- { id: existing.id, version: existing.version, changes: fields },
54
- event.user,
55
- db,
56
- { streamTenantId },
57
- );
58
- if (!result.isSuccess) return result;
59
- return {
60
- isSuccess: true as const,
61
- data: { id: String(existing.id), slug: event.payload.slug, isNew: false },
62
- };
63
- }
64
-
65
- const result = await executor.create({ ...fields, tenantId }, event.user, db, {
66
- streamTenantId,
67
- });
68
- if (!result.isSuccess) return result;
69
- // @cast-boundary db-row — executor.create returnt Record-row aus
70
- // INSERT RETURNING; shape { id } ist garantiert weil PK in der
71
- // Returning-Klausel ist.
72
- const createdRow = result.data as { id: string | number };
73
- return {
74
- isSuccess: true as const,
75
- data: {
76
- id: String(createdRow.id),
77
- slug: event.payload.slug,
78
- isNew: true,
79
- },
80
- };
81
- },
15
+ "Creates or overwrites a system-default template under the system tenant with its full variable schema (resource files go through the resource upload), active straight away with no draft stage; SystemAdmin only, since tenants shadow these defaults through upsert-tenant instead.",
16
+ handler: (event, ctx) =>
17
+ writeTemplateRow({
18
+ ctx,
19
+ user: event.user,
20
+ tenantId: SYSTEM_TENANT_ID,
21
+ scope: "system",
22
+ status: "active",
23
+ variables: resolveTemplateVariables(
24
+ ctx.registry,
25
+ event.payload.slug,
26
+ event.payload.variableSchema,
27
+ ),
28
+ payload: event.payload,
29
+ }),
82
30
  });
@@ -1,15 +1,8 @@
1
- import { fetchOne } from "@cosmicdrift/kumiko-framework/bun-db";
2
- import {
3
- defineWriteHandler,
4
- SYSTEM_TENANT_ID,
5
- type TenantId,
6
- } from "@cosmicdrift/kumiko-framework/engine";
7
- import { AccessDeniedError, writeFailure } from "@cosmicdrift/kumiko-framework/errors";
1
+ import { defineWriteHandler, type TenantId } from "@cosmicdrift/kumiko-framework/engine";
8
2
  import * as z from "zod";
9
- import { requireForTenant } from "../../shared/index.js";
10
- import type { TemplateResourceRow } from "../table.js";
11
- import { templateResourcesTable } from "../table.js";
12
- import { executor, upsertPayloadSchema } from "./shared.js";
3
+ import { resolveTemplateVariables } from "../template-validation.js";
4
+ import { upsertPayloadSchema } from "./shared.js";
5
+ import { writeTemplateRow } from "./template-write.js";
13
6
 
14
7
  // Tenant-Override anlegen/updaten. Liegt unter event.user.tenantId,
15
8
  // scope='tenant'. Default-Status='draft' — User publisht explizit
@@ -26,76 +19,22 @@ export const upsertTenantWrite = defineWriteHandler({
26
19
  description:
27
20
  "Creates or overwrites a tenant-scoped template override with its variable schema, landing as a draft that only publish makes live; it refuses a system-tenant target, which is what upsert-system is for.",
28
21
  handler: async (event, ctx) => {
29
- const override = event.payload.tenantIdOverride;
30
- // upsertTenant erzeugt scope='tenant'. SYSTEM_TENANT_ID-Override würde
31
- // scope='tenant' unter SYSTEM_TENANT_ID schreiben → inkonsistenter Zustand
32
- // (Resolver-Logik trennt sauber zwischen system+tenant). SystemAdmin muss
33
- // upsertSystem für System-Defaults nutzen.
34
- if (override === SYSTEM_TENANT_ID) {
35
- return writeFailure(
36
- new AccessDeniedError({
37
- i18nKey: "templateResolver.errors.useUpsertSystemForSystemTenant",
38
- details: { reason: "system_tenant_override_not_allowed_use_upsert_system" },
39
- }),
40
- );
41
- }
42
22
  // @cast-boundary engine-payload — override aus Zod-parsed string,
43
23
  // event.user.tenantId schon TenantId-branded; union als TenantId casten
44
24
  // ist legit (override ist UUID-Format-validiert in schema).
45
- const tenantId = (override ?? event.user.tenantId) as TenantId;
46
- const { db, streamTenantId } = requireForTenant(ctx, tenantId);
47
-
48
- const existing = await fetchOne<TemplateResourceRow>(db, templateResourcesTable, {
25
+ const tenantId = (event.payload.tenantIdOverride ?? event.user.tenantId) as TenantId;
26
+ return writeTemplateRow({
27
+ ctx,
28
+ user: event.user,
49
29
  tenantId,
50
- slug: event.payload.slug,
51
- kind: event.payload.kind,
52
- locale: event.payload.locale,
53
- });
54
-
55
- const fields = {
56
- slug: event.payload.slug,
57
- kind: event.payload.kind,
58
- locale: event.payload.locale,
59
- content: event.payload.content,
60
- contentFormat: event.payload.contentFormat,
61
- variableSchema: JSON.stringify(event.payload.variableSchema),
62
- linkedResources: JSON.stringify(event.payload.linkedResources),
63
- title: event.payload.title ?? null,
64
- folder: event.payload.folder ?? null,
65
- scope: "tenant" as const,
66
- parentTemplateId: event.payload.parentTemplateId ?? null,
30
+ scope: "tenant",
67
31
  status: event.payload.status,
68
- };
69
-
70
- if (existing) {
71
- const result = await executor.update(
72
- { id: existing.id, version: existing.version, changes: fields },
73
- event.user,
74
- db,
75
- { streamTenantId },
76
- );
77
- if (!result.isSuccess) return result;
78
- return {
79
- isSuccess: true as const,
80
- data: { id: String(existing.id), slug: event.payload.slug, isNew: false },
81
- };
82
- }
83
-
84
- const result = await executor.create({ ...fields, tenantId }, event.user, db, {
85
- streamTenantId,
32
+ variables: resolveTemplateVariables(
33
+ ctx.registry,
34
+ event.payload.slug,
35
+ event.payload.variableSchema,
36
+ ),
37
+ payload: event.payload,
86
38
  });
87
- if (!result.isSuccess) return result;
88
- // @cast-boundary db-row — executor.create returnt Record-row aus
89
- // INSERT RETURNING; shape { id } ist garantiert weil PK in der
90
- // Returning-Klausel ist.
91
- const createdRow = result.data as { id: string | number };
92
- return {
93
- isSuccess: true as const,
94
- data: {
95
- id: String(createdRow.id),
96
- slug: event.payload.slug,
97
- isNew: true,
98
- },
99
- };
100
39
  },
101
40
  });
@@ -0,0 +1,17 @@
1
+ import { defineQueryHandler } from "@cosmicdrift/kumiko-framework/engine";
2
+ import * as z from "zod";
3
+ import { TEMPLATE_KINDS } from "../constants.js";
4
+ import { templateSlugSchema } from "../template-meta.js";
5
+ import { collectTemplateUsage } from "../template-usage.js";
6
+
7
+ export const usageQuery = defineQueryHandler({
8
+ name: "usage",
9
+ schema: z.object({ slug: templateSlugSchema, kind: z.enum(TEMPLATE_KINDS) }),
10
+ access: { roles: ["TenantAdmin", "SystemAdmin"] },
11
+ description:
12
+ "Lists the code-registered notifications that use one template: a mail template is used by the notification whose qualified name is its slug, a mail layout by every mail notification that names it in its meta or falls back to the default layout; use it to show what a change to a template affects.",
13
+ handler: async (query, ctx) => {
14
+ const { slug, kind } = query.payload;
15
+ return { usedBy: collectTemplateUsage(ctx.registry, slug, kind) };
16
+ },
17
+ });
@@ -0,0 +1,116 @@
1
+ import { selectMany } from "@cosmicdrift/kumiko-framework/bun-db";
2
+ import {
3
+ defineQueryHandler,
4
+ type HandlerContext,
5
+ type SessionUser,
6
+ SYSTEM_TENANT_ID,
7
+ } from "@cosmicdrift/kumiko-framework/engine";
8
+ import * as z from "zod";
9
+ import type { ContentFormat, UpsertKind } from "../constants.js";
10
+ import type { TemplateWriteScope } from "../layout-regions.js";
11
+ import { imageResourceKeysOf } from "../linked-resources.js";
12
+ import { type TemplateResourceRow, templateResourcesTable } from "../table.js";
13
+ import { resolveTemplateVariables, systemPlaceholdersFor } from "../template-validation.js";
14
+ import { contentFormatSchema, kindSchema, localeSchema, templateSlugSchema } from "./shared.js";
15
+ import { evaluateTemplateWrite } from "./template-gates.js";
16
+
17
+ async function knownResourceKeys(
18
+ db: Parameters<typeof selectMany>[0],
19
+ where: {
20
+ readonly tenantId: string;
21
+ readonly slug: string;
22
+ readonly kind: string;
23
+ readonly scope: "tenant" | "system";
24
+ readonly locale: string | undefined;
25
+ },
26
+ ): Promise<string[]> {
27
+ const rows = await selectMany<TemplateResourceRow>(db, templateResourcesTable, {
28
+ tenantId: where.tenantId,
29
+ slug: where.slug,
30
+ kind: where.kind,
31
+ scope: where.scope,
32
+ ...(where.locale === undefined ? {} : { locale: where.locale }),
33
+ ...(where.scope === "system" ? { status: "active" } : {}),
34
+ });
35
+ return rows.flatMap((row) => imageResourceKeysOf(row.linkedResources));
36
+ }
37
+
38
+ type TemplateDraftInput = {
39
+ readonly slug: string;
40
+ readonly kind: UpsertKind;
41
+ readonly contentFormat: ContentFormat;
42
+ readonly content: string;
43
+ readonly meta?: unknown;
44
+ readonly variableSchema: Readonly<Record<string, unknown>>;
45
+ readonly locale?: string | undefined;
46
+ readonly scope: TemplateWriteScope;
47
+ };
48
+
49
+ // The dry run of the write pipeline; preview feeds on the evaluation, validate returns the response.
50
+ export async function validateTemplateDraft(
51
+ ctx: HandlerContext,
52
+ user: SessionUser,
53
+ payload: TemplateDraftInput,
54
+ ) {
55
+ const variables = resolveTemplateVariables(ctx.registry, payload.slug, payload.variableSchema);
56
+ const { slug, kind, scope, locale } = payload;
57
+ const known = [
58
+ ...(await knownResourceKeys(ctx.db, {
59
+ tenantId: SYSTEM_TENANT_ID,
60
+ slug,
61
+ kind,
62
+ scope: "system",
63
+ locale,
64
+ })),
65
+ ...(scope === "tenant"
66
+ ? await knownResourceKeys(ctx.db, { tenantId: user.tenantId, slug, kind, scope, locale })
67
+ : []),
68
+ ];
69
+ const evaluation = evaluateTemplateWrite({
70
+ kind,
71
+ scope,
72
+ contentFormat: payload.contentFormat,
73
+ content: payload.content,
74
+ rawMeta: payload.meta,
75
+ variables,
76
+ resourceKeys: known,
77
+ });
78
+ const validation = evaluation.validation;
79
+ const response = {
80
+ canSave: evaluation.ok,
81
+ canPublish: evaluation.ok && evaluation.publishIssues.length === 0,
82
+ issues: evaluation.ok ? [] : [...evaluation.issues],
83
+ publishIssues: evaluation.ok ? [...evaluation.publishIssues] : [],
84
+ used: [...(validation?.used ?? [])],
85
+ unknown: [...(validation?.unknown ?? [])],
86
+ missingRequired: [...(validation?.missingRequired ?? [])],
87
+ warnings: (validation?.unknownResources ?? []).map((key) => ({
88
+ code: "resource_not_uploaded" as const,
89
+ detail: key,
90
+ i18nKey: "templateResolver.warnings.resourceNotUploaded" as const,
91
+ })),
92
+ variables,
93
+ systemPlaceholders: [...systemPlaceholdersFor(kind)].sort(),
94
+ };
95
+ return { response, evaluation };
96
+ }
97
+
98
+ export const validateQuery = defineQueryHandler({
99
+ name: "validate",
100
+ schema: z.object({
101
+ slug: templateSlugSchema,
102
+ kind: kindSchema,
103
+ contentFormat: contentFormatSchema,
104
+ content: z.string().max(200_000),
105
+ meta: z.unknown().optional(),
106
+ variableSchema: z.record(z.string(), z.unknown()).default({}),
107
+ /** Narrows the known resource keys to one locale's rows. */
108
+ locale: localeSchema.optional(),
109
+ scope: z.enum(["tenant", "system"]).default("tenant"),
110
+ }),
111
+ access: { roles: ["TenantAdmin", "SystemAdmin"] },
112
+ description:
113
+ "Dry-runs the write pipeline for a template draft without saving: reports whether it can be saved or published, the sanitizer and placeholder problems, which placeholders it uses, which are unknown, which required variables are missing, and the variables and system placeholders available for the kind; use it to give an editor live feedback.",
114
+ handler: async (query, ctx) =>
115
+ (await validateTemplateDraft(ctx, query.user, query.payload)).response,
116
+ });
@@ -6,12 +6,23 @@ export {
6
6
  type TemplateResolverApi,
7
7
  type TemplateResource,
8
8
  } from "./api.js";
9
+ export {
10
+ type CompileDocumentBodyOptions,
11
+ collectDocumentResourceKeys,
12
+ compileDocumentBodyToHandlebars,
13
+ toResourceDataUri,
14
+ } from "./compile-document-body.js";
9
15
  export {
10
16
  CONTENT_FORMATS,
11
17
  type ContentFormat,
18
+ DEFAULT_MAIL_LAYOUT_SLUG,
19
+ DOCUMENT_HTML_KINDS,
20
+ type DocumentHtmlKind,
12
21
  FALLBACK_LOCALE,
13
22
  NON_RENDER_KINDS,
14
23
  type NonRenderKind,
24
+ PDF_PAGE_FORMATS,
25
+ type PdfPageFormat,
15
26
  RENDER_KINDS,
16
27
  type RenderKind,
17
28
  SYSTEM_TENANT_ID,
@@ -25,18 +36,107 @@ export {
25
36
  UPSERT_KINDS,
26
37
  type UpsertKind,
27
38
  } from "./constants.js";
39
+ export {
40
+ type DocumentViolation,
41
+ type DocumentViolationCode,
42
+ documentViolationsToFieldIssues,
43
+ type SanitizeBodyResult,
44
+ type SanitizeHtmlResult,
45
+ sanitizeDocumentHtml,
46
+ sanitizeTemplateBody,
47
+ } from "./document-sanitizer.js";
48
+ export {
49
+ DOCUMENT_NODE_NAMES,
50
+ DOCUMENT_TEXT_ALIGNMENTS,
51
+ DocumentButton,
52
+ DocumentDivider,
53
+ DocumentLink,
54
+ DocumentPageBreak,
55
+ DocumentRepeatRow,
56
+ DocumentResource,
57
+ DocumentTable,
58
+ DocumentTableCell,
59
+ DocumentTableHeader,
60
+ DocumentTableRow,
61
+ DocumentTextAlign,
62
+ type DocumentTextAlignment,
63
+ DocumentVariable,
64
+ documentExtensions,
65
+ isAllowedDocumentLinkHref,
66
+ isDocumentPlaceholderHref,
67
+ isDocumentReferenceName,
68
+ isDocumentResourceKey,
69
+ } from "./document-schema.js";
28
70
  export { createTemplateResolverFeature } from "./feature.js";
71
+ export type { LayoutSourceResponse, LayoutSourceRow } from "./handlers/layout-source.query.js";
72
+ export type {
73
+ OverviewKindGroup,
74
+ OverviewOrigin,
75
+ OverviewResponse,
76
+ OverviewRow,
77
+ OverviewUsage,
78
+ } from "./handlers/overview.query.js";
79
+ export { localeSchema } from "./handlers/shared.js";
80
+ export {
81
+ type LayoutRegions,
82
+ readLayoutRegions,
83
+ type SanitizeLayoutResult,
84
+ sanitizeLayoutHtml,
85
+ } from "./layout-regions.js";
86
+ export {
87
+ fileAttachmentsOf,
88
+ imageLinksOf,
89
+ type LinkedResourceEntry,
90
+ parseLinkedResourceEntries,
91
+ pdfAttachmentLinksOf,
92
+ type TemplatePdfAttachmentLink,
93
+ templatePdfAttachmentLinkSchema,
94
+ } from "./linked-resources.js";
95
+ export { prepareDocumentForPrint } from "./print-document.js";
29
96
  export {
30
97
  TEMPLATE_RESOLVER_FEATURE,
31
98
  TemplateResolverErrors,
32
99
  TemplateResolverHandlers,
33
100
  TemplateResolverQueries,
34
101
  } from "./qualified-names.js";
102
+ export { mapRecordToVariables } from "./record-mapping.js";
103
+ export {
104
+ readTemplateResourceMaxBytes,
105
+ type StoredTemplateResource,
106
+ storeTemplateResource,
107
+ } from "./store-template-resource.js";
35
108
  export {
36
109
  type TemplateResourceRow,
37
110
  templateResourceEntity,
38
111
  templateResourcesTable,
39
112
  } from "./table.js";
113
+ export { type TemplateBundle, templateBundleSchema } from "./template-bundle.js";
114
+ export {
115
+ isMailTemplateMeta,
116
+ type LayoutTemplateMeta,
117
+ MAIL_CATEGORIES,
118
+ type MailCategory,
119
+ type MailTemplateMeta,
120
+ type PdfTemplateMeta,
121
+ parseTemplateMeta,
122
+ pdfAttachmentFileNamePatternSchema,
123
+ readPdfTemplateMeta,
124
+ type TemplateMeta,
125
+ type TemplateMetaIssue,
126
+ type TemplateMetaResult,
127
+ templateSlugSchema,
128
+ } from "./template-meta.js";
129
+ export { type DocumentDraftOutcome, evaluateDocumentDraft } from "./template-preview.js";
130
+ export { readTemplateResource, type TemplateResourceFile } from "./template-resources.js";
131
+ export { collectTemplateUsage, type TemplateUsageEntry } from "./template-usage.js";
132
+ export {
133
+ hasPlaceholderProblems,
134
+ resolveTemplateVariables,
135
+ SYSTEM_PLACEHOLDERS,
136
+ systemPlaceholdersFor,
137
+ type TemplateValidation,
138
+ validateTemplatePlaceholders,
139
+ } from "./template-validation.js";
40
140
  export {
41
141
  type UserContentEntryRow,
42
142
  userContentEntriesTable,