@cosmicdrift/kumiko-bundled-features 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 (307) hide show
  1. package/dist/admin-shell/feature.d.ts +3 -0
  2. package/dist/admin-shell/feature.js +6 -1
  3. package/dist/agent-tools/agent-manifest.js +2 -1
  4. package/dist/agent-tools/filter-ops.d.ts +1 -1
  5. package/dist/agent-tools/filter-ops.js +1 -3
  6. package/dist/agent-tools/tool-dispatch.js +2 -1
  7. package/dist/auth-email-password/email-templates.d.ts +4 -26
  8. package/dist/auth-email-password/email-templates.js +9 -101
  9. package/dist/auth-email-password/feature.js +24 -6
  10. package/dist/auth-email-password/handlers/invite-create.write.js +4 -4
  11. package/dist/auth-email-password/handlers/request-account-unlock.write.js +2 -4
  12. package/dist/auth-email-password/handlers/request-email-verification.write.js +2 -4
  13. package/dist/auth-email-password/handlers/request-password-reset.write.js +2 -4
  14. package/dist/auth-email-password/handlers/signup-confirm.write.js +3 -4
  15. package/dist/auth-email-password/handlers/token-request-handler.d.ts +1 -2
  16. package/dist/auth-email-password/handlers/token-request-handler.js +0 -1
  17. package/dist/auth-email-password/index.d.ts +2 -2
  18. package/dist/auth-email-password/index.js +1 -1
  19. package/dist/auth-email-password/magic-link-mail.d.ts +3 -2
  20. package/dist/auth-email-password/magic-link-mail.js +19 -14
  21. package/dist/auth-email-password/mail-notifications.d.ts +32 -0
  22. package/dist/auth-email-password/mail-notifications.js +116 -0
  23. package/dist/auth-email-password/signup-activation.d.ts +1 -1
  24. package/dist/auth-email-password/signup-activation.js +2 -3
  25. package/dist/auth-foundation/types.js +2 -8
  26. package/dist/auth-mfa/feature.js +4 -4
  27. package/dist/auth-mfa/hmac-token-codec.js +4 -4
  28. package/dist/auth-mfa/mfa-challenge-token.js +3 -3
  29. package/dist/auth-mfa/mfa-preauth-setup-token.js +3 -3
  30. package/dist/auth-mfa/mfa-setup-token.js +8 -8
  31. package/dist/billing-foundation/constants.d.ts +3 -3
  32. package/dist/billing-foundation/constants.js +5 -4
  33. package/dist/billing-foundation/consumer-protection/confirmation-mail.d.ts +32 -14
  34. package/dist/billing-foundation/consumer-protection/confirmation-mail.js +27 -32
  35. package/dist/billing-foundation/consumer-protection/issue-confirmation.js +3 -3
  36. package/dist/billing-foundation/consumer-protection/mail-notifications.d.ts +8 -0
  37. package/dist/billing-foundation/consumer-protection/mail-notifications.js +95 -0
  38. package/dist/billing-foundation/consumer-protection/register-consent.js +2 -0
  39. package/dist/billing-foundation/consumer-protection/termination-mail.d.ts +20 -10
  40. package/dist/billing-foundation/consumer-protection/termination-mail.js +10 -21
  41. package/dist/billing-foundation/consumer-protection/termination-notify.js +2 -2
  42. package/dist/billing-foundation/consumer-protection/termination-pages.js +1 -3
  43. package/dist/billing-foundation/consumer-protection/termination-request.js +2 -2
  44. package/dist/billing-foundation/handlers/process-event.write.js +2 -2
  45. package/dist/billing-foundation/handlers/process-payment-event.write.js +1 -1
  46. package/dist/billing-foundation/subscription-tier-sync.js +3 -4
  47. package/dist/cap-overview/handlers/tenant-caps-list.query.js +2 -1
  48. package/dist/cap-overview/web/cap-usage-cell.js +5 -4
  49. package/dist/channel-email/email-channel.d.ts +2 -2
  50. package/dist/channel-email/email-channel.js +89 -26
  51. package/dist/channel-email/feature.js +3 -3
  52. package/dist/channel-email/index.d.ts +1 -1
  53. package/dist/channel-email/index.js +1 -1
  54. package/dist/channel-email/pii-guard.js +10 -0
  55. package/dist/channel-email/smtp-transport.js +13 -0
  56. package/dist/channel-email/types.d.ts +8 -0
  57. package/dist/config/handlers/set.write.js +1 -2
  58. package/dist/custom-fields/lib/value-schema.js +10 -11
  59. package/dist/custom-fields/wire-user-data-rights.js +7 -6
  60. package/dist/data-retention/index.d.ts +1 -0
  61. package/dist/data-retention/index.js +1 -0
  62. package/dist/data-retention/run-retention-cleanup.js +6 -6
  63. package/dist/delivery/attachment-ref.d.ts +39 -0
  64. package/dist/delivery/attachment-ref.js +64 -0
  65. package/dist/delivery/channel-context.d.ts +8 -4
  66. package/dist/delivery/channel-context.js +76 -2
  67. package/dist/delivery/constants.d.ts +1 -0
  68. package/dist/delivery/constants.js +1 -0
  69. package/dist/delivery/delivery-service.d.ts +4 -1
  70. package/dist/delivery/delivery-service.js +38 -32
  71. package/dist/delivery/feature.js +1 -1
  72. package/dist/delivery/index.d.ts +5 -2
  73. package/dist/delivery/index.js +4 -1
  74. package/dist/delivery/jobs.js +25 -5
  75. package/dist/delivery/notify-factory.js +4 -1
  76. package/dist/delivery/testing.d.ts +4 -1
  77. package/dist/delivery/types.d.ts +20 -14
  78. package/dist/delivery/unsubscribe.js +4 -1
  79. package/dist/derivatives-sharp/render.js +1 -18
  80. package/dist/document-ingest-foundation/pages.js +4 -4
  81. package/dist/feature-toggles/toggle-sync-signal.js +4 -4
  82. package/dist/file-foundation/index.d.ts +1 -0
  83. package/dist/file-foundation/index.js +1 -0
  84. package/dist/file-foundation/read-file-ref-content.d.ts +27 -0
  85. package/dist/file-foundation/read-file-ref-content.js +49 -0
  86. package/dist/folders/web/folder-manager.js +3 -2
  87. package/dist/form-draft/db/queries/cleanup.js +4 -4
  88. package/dist/form-draft/db/queries/draft-count.js +2 -2
  89. package/dist/form-draft/db/queries/owned-file-refs.js +2 -2
  90. package/dist/form-draft/release-file-refs.js +3 -2
  91. package/dist/inbound-mail-foundation/handlers/connect-account.write.js +1 -1
  92. package/dist/inbound-mail-foundation/handlers/ingest-message.write.js +6 -6
  93. package/dist/inbound-mail-foundation/handlers/scope-visibility.d.ts +1 -1
  94. package/dist/inbound-mail-foundation/oauth-state.js +7 -7
  95. package/dist/inbound-mail-foundation/types.js +4 -4
  96. package/dist/inbound-mail-foundation/watch-supervisor.js +22 -13
  97. package/dist/inbound-provider-imap/imap-client.js +4 -3
  98. package/dist/ledger/recurring.js +1 -3
  99. package/dist/managed-pages/branding.d.ts +2 -0
  100. package/dist/managed-pages/branding.js +24 -2
  101. package/dist/managed-pages/feature.js +1 -0
  102. package/dist/managed-pages/i18n.js +1 -0
  103. package/dist/managed-pages/screens/branding-screen.js +8 -1
  104. package/dist/page-render/branding.d.ts +1 -0
  105. package/dist/page-render/branding.js +1 -0
  106. package/dist/page-render/markdown.js +1 -1
  107. package/dist/personal-access-tokens/feature.js +1 -1
  108. package/dist/personal-access-tokens/handlers/list.query.js +2 -1
  109. package/dist/personal-access-tokens/revoke-for-user.js +2 -1
  110. package/dist/renderer-foundation/api.d.ts +13 -13
  111. package/dist/renderer-foundation/api.js +1 -1
  112. package/dist/renderer-foundation/constants.js +2 -4
  113. package/dist/renderer-foundation/feature.js +7 -7
  114. package/dist/renderer-foundation/index.d.ts +2 -1
  115. package/dist/renderer-foundation/index.js +1 -0
  116. package/dist/renderer-foundation/types.d.ts +92 -32
  117. package/dist/renderer-pdf/feature.js +7 -1
  118. package/dist/sessions/db/queries/cleanup.js +2 -2
  119. package/dist/sessions/feature.js +1 -1
  120. package/dist/sessions/handlers/list.query.js +2 -1
  121. package/dist/shared/attachment-file-name.d.ts +3 -0
  122. package/dist/shared/attachment-file-name.js +35 -0
  123. package/dist/shared/index.d.ts +3 -0
  124. package/dist/shared/index.js +3 -0
  125. package/dist/shared/is-tenant-db.js +1 -1
  126. package/dist/shared/mail-template-body.d.ts +4 -0
  127. package/dist/shared/mail-template-body.js +17 -0
  128. package/dist/shared/sharp-input-limits.d.ts +6 -0
  129. package/dist/shared/sharp-input-limits.js +22 -0
  130. package/dist/shared/signup-handover.js +2 -2
  131. package/dist/tags/handlers/assign-tag.write.js +3 -1
  132. package/dist/tags/web/tag-chip.js +2 -0
  133. package/dist/template-resolver/api.d.ts +30 -2
  134. package/dist/template-resolver/api.js +40 -5
  135. package/dist/template-resolver/compile-document-body.d.ts +8 -0
  136. package/dist/template-resolver/compile-document-body.js +163 -0
  137. package/dist/template-resolver/constants.d.ts +13 -4
  138. package/dist/template-resolver/constants.js +30 -10
  139. package/dist/template-resolver/document-sanitizer.d.ts +32 -0
  140. package/dist/template-resolver/document-sanitizer.js +201 -0
  141. package/dist/template-resolver/document-schema.d.ts +21 -0
  142. package/dist/template-resolver/document-schema.js +296 -0
  143. package/dist/template-resolver/feature.d.ts +18 -0
  144. package/dist/template-resolver/feature.js +50 -6
  145. package/dist/template-resolver/handlers/bundle-import.d.ts +20 -0
  146. package/dist/template-resolver/handlers/bundle-import.js +147 -0
  147. package/dist/template-resolver/handlers/collection-set.write.js +25 -2
  148. package/dist/template-resolver/handlers/copy-locale.write.d.ts +19 -0
  149. package/dist/template-resolver/handlers/copy-locale.write.js +116 -0
  150. package/dist/template-resolver/handlers/diff-to-system.query.d.ts +22 -0
  151. package/dist/template-resolver/handlers/diff-to-system.query.js +64 -0
  152. package/dist/template-resolver/handlers/export-bundle.query.d.ts +35 -0
  153. package/dist/template-resolver/handlers/export-bundle.query.js +68 -0
  154. package/dist/template-resolver/handlers/find-by-id.query.d.ts +1 -0
  155. package/dist/template-resolver/handlers/find-by-id.query.js +3 -3
  156. package/dist/template-resolver/handlers/import-bundle-system.write.d.ts +39 -0
  157. package/dist/template-resolver/handlers/import-bundle-system.write.js +36 -0
  158. package/dist/template-resolver/handlers/import-bundle.write.d.ts +40 -0
  159. package/dist/template-resolver/handlers/import-bundle.write.js +19 -0
  160. package/dist/template-resolver/handlers/layout-source.query.d.ts +26 -0
  161. package/dist/template-resolver/handlers/layout-source.query.js +64 -0
  162. package/dist/template-resolver/handlers/list.query.d.ts +4 -1
  163. package/dist/template-resolver/handlers/list.query.js +5 -7
  164. package/dist/template-resolver/handlers/overview.query.d.ts +59 -0
  165. package/dist/template-resolver/handlers/overview.query.js +165 -0
  166. package/dist/template-resolver/handlers/preview.query.d.ts +12 -0
  167. package/dist/template-resolver/handlers/preview.query.js +24 -0
  168. package/dist/template-resolver/handlers/reset-to-system.write.d.ts +8 -0
  169. package/dist/template-resolver/handlers/reset-to-system.write.js +50 -0
  170. package/dist/template-resolver/handlers/resource-link-pdf.write.d.ts +8 -0
  171. package/dist/template-resolver/handlers/resource-link-pdf.write.js +32 -0
  172. package/dist/template-resolver/handlers/resource-remove.write.d.ts +9 -0
  173. package/dist/template-resolver/handlers/resource-remove.write.js +44 -0
  174. package/dist/template-resolver/handlers/resource-shared.d.ts +9 -0
  175. package/dist/template-resolver/handlers/resource-shared.js +14 -0
  176. package/dist/template-resolver/handlers/resource-upload.write.d.ts +13 -0
  177. package/dist/template-resolver/handlers/resource-upload.write.js +98 -0
  178. package/dist/template-resolver/handlers/send-test.write.d.ts +11 -0
  179. package/dist/template-resolver/handlers/send-test.write.js +81 -0
  180. package/dist/template-resolver/handlers/set.write.d.ts +4 -1
  181. package/dist/template-resolver/handlers/set.write.js +25 -3
  182. package/dist/template-resolver/handlers/shared.d.ts +9 -5
  183. package/dist/template-resolver/handlers/shared.js +6 -2
  184. package/dist/template-resolver/handlers/template-gates.d.ts +36 -0
  185. package/dist/template-resolver/handlers/template-gates.js +131 -0
  186. package/dist/template-resolver/handlers/template-write.d.ts +45 -0
  187. package/dist/template-resolver/handlers/template-write.js +109 -0
  188. package/dist/template-resolver/handlers/tenant-overrides.query.d.ts +17 -0
  189. package/dist/template-resolver/handlers/tenant-overrides.query.js +48 -0
  190. package/dist/template-resolver/handlers/toggle-status.write.d.ts +12 -0
  191. package/dist/template-resolver/handlers/toggle-status.write.js +27 -11
  192. package/dist/template-resolver/handlers/upsert-system.write.d.ts +5 -8
  193. package/dist/template-resolver/handlers/upsert-system.write.js +14 -59
  194. package/dist/template-resolver/handlers/upsert-tenant.write.d.ts +5 -8
  195. package/dist/template-resolver/handlers/upsert-tenant.write.js +10 -61
  196. package/dist/template-resolver/handlers/usage.query.d.ts +17 -0
  197. package/dist/template-resolver/handlers/usage.query.js +15 -0
  198. package/dist/template-resolver/handlers/validate.query.d.ts +74 -0
  199. package/dist/template-resolver/handlers/validate.query.js +80 -0
  200. package/dist/template-resolver/index.d.ts +18 -1
  201. package/dist/template-resolver/index.js +16 -1
  202. package/dist/template-resolver/layout-regions.d.ts +23 -0
  203. package/dist/template-resolver/layout-regions.js +125 -0
  204. package/dist/template-resolver/linked-resources.d.ts +17 -0
  205. package/dist/template-resolver/linked-resources.js +75 -0
  206. package/dist/template-resolver/preview-display-html.d.ts +7 -0
  207. package/dist/template-resolver/preview-display-html.js +27 -0
  208. package/dist/template-resolver/print-document.d.ts +1 -0
  209. package/dist/template-resolver/print-document.js +55 -0
  210. package/dist/template-resolver/qualified-names.d.ts +30 -0
  211. package/dist/template-resolver/qualified-names.js +30 -0
  212. package/dist/template-resolver/record-mapping.d.ts +2 -0
  213. package/dist/template-resolver/record-mapping.js +21 -0
  214. package/dist/template-resolver/resource-image.d.ts +15 -0
  215. package/dist/template-resolver/resource-image.js +34 -0
  216. package/dist/template-resolver/seeding.d.ts +14 -1
  217. package/dist/template-resolver/seeding.js +105 -15
  218. package/dist/template-resolver/store-template-resource.d.ts +64 -0
  219. package/dist/template-resolver/store-template-resource.js +264 -0
  220. package/dist/template-resolver/table.d.ts +47 -8
  221. package/dist/template-resolver/table.js +11 -0
  222. package/dist/template-resolver/template-bundle.d.ts +36 -0
  223. package/dist/template-resolver/template-bundle.js +60 -0
  224. package/dist/template-resolver/template-diff.d.ts +7 -0
  225. package/dist/template-resolver/template-diff.js +68 -0
  226. package/dist/template-resolver/template-meta.d.ts +60 -0
  227. package/dist/template-resolver/template-meta.js +101 -0
  228. package/dist/template-resolver/template-preview.d.ts +86 -0
  229. package/dist/template-resolver/template-preview.js +374 -0
  230. package/dist/template-resolver/template-resources.d.ts +14 -0
  231. package/dist/template-resolver/template-resources.js +42 -0
  232. package/dist/template-resolver/template-usage.d.ts +12 -0
  233. package/dist/template-resolver/template-usage.js +30 -0
  234. package/dist/template-resolver/template-validation.d.ts +22 -0
  235. package/dist/template-resolver/template-validation.js +172 -0
  236. package/dist/template-resolver/testing.js +5 -5
  237. package/dist/template-resolver/user-content-table.d.ts +14 -8
  238. package/dist/template-resolver/web/client-plugin.d.ts +1 -0
  239. package/dist/template-resolver/web/client-plugin.js +9 -6
  240. package/dist/template-resolver/web/document-editor-extensions.d.ts +15 -0
  241. package/dist/template-resolver/web/document-editor-extensions.js +107 -0
  242. package/dist/template-resolver/web/document-editor.d.ts +3 -0
  243. package/dist/template-resolver/web/document-editor.js +11 -0
  244. package/dist/template-resolver/web/document-tiptap-editor.d.ts +3 -0
  245. package/dist/template-resolver/web/document-tiptap-editor.js +376 -0
  246. package/dist/template-resolver/web/document-toolbar-blocks.d.ts +33 -0
  247. package/dist/template-resolver/web/document-toolbar-blocks.js +15 -0
  248. package/dist/template-resolver/web/editor-handle.d.ts +3 -0
  249. package/dist/template-resolver/web/editor-handle.js +24 -0
  250. package/dist/template-resolver/web/i18n.js +76 -0
  251. package/dist/template-resolver/web/index.d.ts +4 -0
  252. package/dist/template-resolver/web/index.js +4 -0
  253. package/dist/template-resolver/web/placeholder-line-editor-lazy.d.ts +4 -0
  254. package/dist/template-resolver/web/placeholder-line-editor-lazy.js +16 -0
  255. package/dist/template-resolver/web/placeholder-line-editor.d.ts +15 -0
  256. package/dist/template-resolver/web/placeholder-line-editor.js +94 -0
  257. package/dist/template-resolver/web/placeholder-text.d.ts +4 -0
  258. package/dist/template-resolver/web/placeholder-text.js +34 -0
  259. package/dist/template-resolver/web/use-stable-by-key.d.ts +1 -0
  260. package/dist/template-resolver/web/use-stable-by-key.js +13 -0
  261. package/dist/tenant/handlers/team-list.query.js +3 -2
  262. package/dist/tenant/seeding.js +31 -6
  263. package/dist/tenant-lifecycle/handlers/cancel-destruction.write.d.ts +1 -1
  264. package/dist/tenant-lifecycle/handlers/cancel-destruction.write.js +2 -1
  265. package/dist/user/db/queries/stream-tenant-backfill.js +4 -4
  266. package/dist/user/handlers/detail.query.js +2 -1
  267. package/dist/user-data-rights/db/queries/forget-cleanup.d.ts +2 -2
  268. package/dist/user-data-rights/email-templates.d.ts +1 -29
  269. package/dist/user-data-rights/email-templates.js +5 -106
  270. package/dist/user-data-rights/feature.js +53 -35
  271. package/dist/user-data-rights/handlers/cancel-deletion.write.d.ts +1 -1
  272. package/dist/user-data-rights/handlers/cancel-deletion.write.js +2 -1
  273. package/dist/user-data-rights/handlers/request-deletion.write.js +5 -7
  274. package/dist/user-data-rights/lib/default-mailers.d.ts +15 -7
  275. package/dist/user-data-rights/lib/default-mailers.js +100 -48
  276. package/dist/user-data-rights/lib/update-user-lifecycle.js +1 -1
  277. package/dist/user-data-rights/mail-notifications.d.ts +13 -0
  278. package/dist/user-data-rights/mail-notifications.js +118 -0
  279. package/dist/user-data-rights/web/privacy-center-screen.js +2 -1
  280. package/dist/user-profile/web/profile-screen.js +2 -1
  281. package/dist/waitlist/feature.js +10 -0
  282. package/dist/waitlist/handlers/submit.write.js +3 -4
  283. package/dist/waitlist/mail.d.ts +19 -6
  284. package/dist/waitlist/mail.js +50 -14
  285. package/package.json +28 -17
  286. package/src/admin-shell/changes.json +6 -0
  287. package/src/auth-email-password/changes.json +14 -0
  288. package/src/billing-foundation/changes.json +7 -0
  289. package/src/channel-email/changes.json +19 -0
  290. package/src/data-retention/changes.json +6 -0
  291. package/src/delivery/changes.json +48 -0
  292. package/src/managed-pages/changes.json +6 -0
  293. package/src/renderer-foundation/changes.json +39 -0
  294. package/src/template-resolver/changes.json +118 -0
  295. package/src/user-data-rights/changes.json +7 -0
  296. package/src/waitlist/changes.json +7 -0
  297. package/dist/renderer-simple/feature.d.ts +0 -10
  298. package/dist/renderer-simple/feature.js +0 -42
  299. package/dist/renderer-simple/index.d.ts +0 -3
  300. package/dist/renderer-simple/index.js +0 -2
  301. package/dist/renderer-simple/mail-blocks.d.ts +0 -21
  302. package/dist/renderer-simple/mail-blocks.js +0 -107
  303. package/dist/renderer-simple/resolve-variables.d.ts +0 -7
  304. package/dist/renderer-simple/resolve-variables.js +0 -47
  305. package/dist/renderer-simple/simple-renderer.d.ts +0 -24
  306. package/dist/renderer-simple/simple-renderer.js +0 -200
  307. package/src/renderer-simple/changes.json +0 -27
@@ -0,0 +1,109 @@
1
+ import { fetchOne } from "@cosmicdrift/kumiko-framework/bun-db";
2
+ import { SYSTEM_TENANT_ID, } from "@cosmicdrift/kumiko-framework/engine";
3
+ import { AccessDeniedError, ValidationError, writeFailure, } from "@cosmicdrift/kumiko-framework/errors";
4
+ import { requireForTenant } from "../../shared/index.js";
5
+ import { templateResourcesTable } from "../table.js";
6
+ import { executor } from "./shared.js";
7
+ import { evaluateTemplateWrite, } from "./template-gates.js";
8
+ // The tenant-scope-under-system-tenant ban lives here so no write path can
9
+ // create the inconsistent state the resolver does not model.
10
+ export function refuseTenantScopeUnderSystemTenant(scope, tenantId) {
11
+ if (scope !== "tenant" || tenantId !== SYSTEM_TENANT_ID)
12
+ return null;
13
+ return writeFailure(new AccessDeniedError({
14
+ i18nKey: "templateResolver.errors.useUpsertSystemForSystemTenant",
15
+ details: { reason: "system_tenant_override_not_allowed_use_upsert_system" },
16
+ }));
17
+ }
18
+ // Evaluation plus the publish rule: a row that goes active must not carry
19
+ // publish issues, a draft may.
20
+ export function gateTemplateWrite(input, status) {
21
+ const evaluated = evaluateTemplateWrite(input);
22
+ if (!evaluated.ok) {
23
+ return {
24
+ ok: false,
25
+ failure: writeFailure(new ValidationError({ fields: [...evaluated.issues] })),
26
+ };
27
+ }
28
+ if (status === "active" && evaluated.publishIssues.length > 0) {
29
+ return {
30
+ ok: false,
31
+ failure: writeFailure(new ValidationError({ fields: [...evaluated.publishIssues] })),
32
+ };
33
+ }
34
+ return { ok: true, evaluated };
35
+ }
36
+ // Shared by every template write: validate through the one pipeline, then
37
+ // create or update the (tenant, slug, kind, locale) row via the event-store
38
+ // executor. linkedResources is never part of an update; resources are attached
39
+ // separately.
40
+ export async function writeTemplateRow(args) {
41
+ const { ctx, user, tenantId, scope, status, payload } = args;
42
+ const refused = refuseTenantScopeUnderSystemTenant(scope, tenantId);
43
+ if (refused !== null)
44
+ return refused;
45
+ const { db, streamTenantId } = requireForTenant(ctx, tenantId);
46
+ const gated = gateTemplateWrite({
47
+ kind: payload.kind,
48
+ scope,
49
+ contentFormat: payload.contentFormat,
50
+ content: payload.content,
51
+ rawMeta: payload.meta,
52
+ variables: args.variables,
53
+ }, status);
54
+ if (!gated.ok)
55
+ return gated.failure;
56
+ const { evaluated } = gated;
57
+ const existing = await fetchOne(db, templateResourcesTable, {
58
+ tenantId,
59
+ slug: payload.slug,
60
+ kind: payload.kind,
61
+ locale: payload.locale,
62
+ });
63
+ const fields = {
64
+ slug: payload.slug,
65
+ kind: payload.kind,
66
+ locale: payload.locale,
67
+ content: evaluated.content,
68
+ contentFormat: payload.contentFormat,
69
+ variableSchema: JSON.stringify(payload.variableSchema),
70
+ meta: evaluated.metaJson,
71
+ title: payload.title ?? null,
72
+ folder: payload.folder ?? null,
73
+ scope,
74
+ parentTemplateId: payload.parentTemplateId ?? null,
75
+ status,
76
+ };
77
+ if (existing) {
78
+ const result = await executor.update({ id: existing.id, version: existing.version, changes: fields }, user, db, { streamTenantId });
79
+ if (!result.isSuccess)
80
+ return result;
81
+ return {
82
+ isSuccess: true,
83
+ data: {
84
+ id: String(existing.id),
85
+ slug: payload.slug,
86
+ isNew: false,
87
+ validation: evaluated.validation,
88
+ },
89
+ };
90
+ }
91
+ const result = await executor.create({ ...fields, linkedResources: "{}", tenantId }, user, db, {
92
+ streamTenantId,
93
+ });
94
+ if (!result.isSuccess)
95
+ return result;
96
+ // @cast-boundary db-row — executor.create returnt Record-row aus
97
+ // INSERT RETURNING; shape { id } ist garantiert weil PK in der
98
+ // Returning-Klausel ist.
99
+ const createdRow = result.data;
100
+ return {
101
+ isSuccess: true,
102
+ data: {
103
+ id: String(createdRow.id),
104
+ slug: payload.slug,
105
+ isNew: true,
106
+ validation: evaluated.validation,
107
+ },
108
+ };
109
+ }
@@ -0,0 +1,17 @@
1
+ import * as z from "zod";
2
+ export declare const tenantOverridesQuery: import("@cosmicdrift/kumiko-types/define-handler").QueryHandlerDefinition<"tenant-overrides", z.ZodObject<{
3
+ slug: z.ZodString;
4
+ kind: z.ZodEnum<{
5
+ "ai-prompt": "ai-prompt";
6
+ "document-pdf": "document-pdf";
7
+ "image-snapshot": "image-snapshot";
8
+ mail: "mail";
9
+ "mail-layout": "mail-layout";
10
+ "pdf-layout": "pdf-layout";
11
+ }>;
12
+ locale: z.ZodOptional<z.ZodString>;
13
+ }, z.core.$strip>, {
14
+ activeOverrides: number;
15
+ draftOverrides: number;
16
+ totalTenants: number | null;
17
+ }, import("@cosmicdrift/kumiko-types/event-type-map").KumikoEventTypeMap>;
@@ -0,0 +1,48 @@
1
+ import { selectMany } from "@cosmicdrift/kumiko-framework/bun-db";
2
+ import { defineQueryHandler, SYSTEM_TENANT_ID } from "@cosmicdrift/kumiko-framework/engine";
3
+ import * as z from "zod";
4
+ import { TenantQueries } from "../../tenant/index.js";
5
+ import { templateResourcesTable } from "../table.js";
6
+ import { kindSchema, localeSchema, templateSlugSchema } from "./shared.js";
7
+ const TENANT_OVERRIDES_REASON = "SystemAdmin template overview counts how many tenants override one system template; it reads tenant-scope template rows of every tenant and returns only counts, never tenant ids or content";
8
+ const tenantIdsSchema = z.array(z.string());
9
+ export const tenantOverridesQuery = defineQueryHandler({
10
+ name: "tenant-overrides",
11
+ schema: z.object({
12
+ slug: templateSlugSchema,
13
+ kind: kindSchema,
14
+ locale: localeSchema.optional(),
15
+ }),
16
+ access: { roles: ["SystemAdmin"] },
17
+ description: "Counts how many enabled tenants override one template with an active or a draft version, next to the number of enabled tenants; use it before changing a system template to see how many tenants keep their own copy. Returns counts only, no tenant ids.",
18
+ escapeHatch: { grants: ["unsafeRaw"], reason: TENANT_OVERRIDES_REASON },
19
+ handler: async (query, ctx) => {
20
+ const { slug, kind, locale } = query.payload;
21
+ const rows = await selectMany(ctx.db.unsafeRaw(), templateResourcesTable, {
22
+ slug,
23
+ kind,
24
+ scope: "tenant",
25
+ ...(locale === undefined ? {} : { locale }),
26
+ });
27
+ const hasTenantFeature = ctx.registry.getQueryHandler(TenantQueries.activeTenantIds) !== undefined;
28
+ const enabledTenantIds = hasTenantFeature
29
+ ? new Set(tenantIdsSchema.parse(await ctx.query(TenantQueries.activeTenantIds, {})))
30
+ : null;
31
+ const statusByTenant = new Map();
32
+ for (const row of rows) {
33
+ if (row.tenantId === SYSTEM_TENANT_ID || row.status === "archived")
34
+ continue;
35
+ if (enabledTenantIds !== null && !enabledTenantIds.has(row.tenantId))
36
+ continue;
37
+ if (statusByTenant.get(row.tenantId) === "active")
38
+ continue;
39
+ statusByTenant.set(row.tenantId, row.status === "active" ? "active" : "draft");
40
+ }
41
+ const statuses = [...statusByTenant.values()];
42
+ return {
43
+ activeOverrides: statuses.filter((status) => status === "active").length,
44
+ draftOverrides: statuses.filter((status) => status === "draft").length,
45
+ totalTenants: enabledTenantIds === null ? null : enabledTenantIds.size,
46
+ };
47
+ },
48
+ });
@@ -12,4 +12,16 @@ export declare const publishWrite: import("@cosmicdrift/kumiko-types/define-hand
12
12
  id: string;
13
13
  status: TemplateStatus;
14
14
  }, import("@cosmicdrift/kumiko-types/event-type-map").KumikoEventTypeMap>;
15
+ export declare const archiveSystemWrite: import("@cosmicdrift/kumiko-types/define-handler").WriteHandlerDefinition<string, z.ZodObject<{
16
+ id: z.ZodString;
17
+ }, z.core.$strip>, {
18
+ id: string;
19
+ status: TemplateStatus;
20
+ }, import("@cosmicdrift/kumiko-types/event-type-map").KumikoEventTypeMap>;
21
+ export declare const publishSystemWrite: import("@cosmicdrift/kumiko-types/define-handler").WriteHandlerDefinition<string, z.ZodObject<{
22
+ id: z.ZodString;
23
+ }, z.core.$strip>, {
24
+ id: string;
25
+ status: TemplateStatus;
26
+ }, import("@cosmicdrift/kumiko-types/event-type-map").KumikoEventTypeMap>;
15
27
  export {};
@@ -1,30 +1,46 @@
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 { defineWriteHandler, SYSTEM_TENANT_ID, } from "@cosmicdrift/kumiko-framework/engine";
3
+ import { NotFoundError, ValidationError, writeFailure, } from "@cosmicdrift/kumiko-framework/errors";
4
4
  import * as z from "zod";
5
+ import { requireForTenant } from "../../shared/index.js";
5
6
  import { templateResourcesTable } from "../table.js";
6
7
  import { executor } from "./shared.js";
7
- function createStatusUpdateHandler(name, status, description) {
8
+ import { publishGateIssues } from "./template-gates.js";
9
+ function isRowOfTarget(row, target, callerTenantId) {
10
+ return target === "system"
11
+ ? row.tenantId === SYSTEM_TENANT_ID && row.scope === "system"
12
+ : row.tenantId === callerTenantId && row.scope === "tenant";
13
+ }
14
+ function createStatusUpdateHandler(name, target, status, description, gate) {
8
15
  return defineWriteHandler({
9
16
  name,
10
17
  schema: z.object({ id: z.string().min(1) }),
11
- access: { roles: ["TenantAdmin", "SystemAdmin"] },
18
+ access: { roles: target === "system" ? ["SystemAdmin"] : ["TenantAdmin", "SystemAdmin"] },
19
+ // A system template change reaches every tenant without an override.
20
+ ...(target === "system" ? { agent: { risk: "high" } } : {}),
12
21
  description,
13
22
  handler: async (event, ctx) => {
14
- // ctx.db is tenant-scoped: a foreign tenant's id reads as absent → NotFound.
15
- // Cross-tenant toggling needs SystemAdmin with tenantIdOverride.
16
- const existing = await fetchOne(ctx.db, templateResourcesTable, {
23
+ const scoped = target === "system" ? requireForTenant(ctx, SYSTEM_TENANT_ID) : null;
24
+ const db = scoped?.db ?? ctx.db;
25
+ // The tenant db also reads system rows; the row's own tenant must match
26
+ // the target or the executor would append to a foreign event stream.
27
+ const existing = await fetchOne(db, templateResourcesTable, {
17
28
  id: event.payload.id,
18
29
  });
19
- if (!existing) {
30
+ if (!existing || !isRowOfTarget(existing, target, event.user.tenantId)) {
20
31
  return writeFailure(new NotFoundError("template-resource", event.payload.id));
21
32
  }
22
- const result = await executor.update({ id: existing.id, version: existing.version, changes: { status } }, event.user, ctx.db);
33
+ const issues = gate?.(ctx.registry, existing) ?? [];
34
+ if (issues.length > 0)
35
+ return writeFailure(new ValidationError({ fields: [...issues] }));
36
+ const result = await executor.update({ id: existing.id, version: existing.version, changes: { status } }, event.user, db, scoped ? { streamTenantId: scoped.streamTenantId } : undefined);
23
37
  if (!result.isSuccess)
24
38
  return result;
25
39
  return { isSuccess: true, data: { id: String(existing.id), status } };
26
40
  },
27
41
  });
28
42
  }
29
- export const archiveWrite = createStatusUpdateHandler("archive", "archived", "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.");
30
- export const publishWrite = createStatusUpdateHandler("publish", "active", "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.");
43
+ export const archiveWrite = createStatusUpdateHandler("archive", "own", "archived", "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.");
44
+ export const publishWrite = createStatusUpdateHandler("publish", "own", "active", "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.", publishGateIssues);
45
+ export const archiveSystemWrite = createStatusUpdateHandler("archive-system", "system", "archived", "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.");
46
+ export const publishSystemWrite = createStatusUpdateHandler("publish-system", "system", "active", "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.", publishGateIssues);
@@ -4,8 +4,9 @@ export declare const upsertSystemWrite: import("@cosmicdrift/kumiko-types/define
4
4
  "ai-prompt": "ai-prompt";
5
5
  "document-pdf": "document-pdf";
6
6
  "image-snapshot": "image-snapshot";
7
- "mail-html": "mail-html";
8
- notification: "notification";
7
+ mail: "mail";
8
+ "mail-layout": "mail-layout";
9
+ "pdf-layout": "pdf-layout";
9
10
  }>;
10
11
  locale: import("zod").ZodString;
11
12
  content: import("zod").ZodString;
@@ -16,12 +17,8 @@ export declare const upsertSystemWrite: import("@cosmicdrift/kumiko-types/define
16
17
  plain: "plain";
17
18
  }>;
18
19
  variableSchema: import("zod").ZodDefault<import("zod").ZodRecord<import("zod").ZodString, import("zod").ZodUnknown>>;
19
- linkedResources: import("zod").ZodDefault<import("zod").ZodRecord<import("zod").ZodString, import("zod").ZodString>>;
20
+ meta: import("zod").ZodOptional<import("zod").ZodUnknown>;
20
21
  parentTemplateId: import("zod").ZodOptional<import("zod").ZodString>;
21
22
  title: import("zod").ZodOptional<import("zod").ZodNullable<import("zod").ZodString>>;
22
23
  folder: import("zod").ZodOptional<import("zod").ZodNullable<import("zod").ZodString>>;
23
- }, import("zod/v4/core").$strip>, {
24
- id: string;
25
- slug: string;
26
- isNew: boolean;
27
- }, import("@cosmicdrift/kumiko-types/event-type-map").KumikoEventTypeMap>;
24
+ }, import("zod/v4/core").$strip>, import("./template-write.js").WrittenTemplateRow, import("@cosmicdrift/kumiko-types/event-type-map").KumikoEventTypeMap>;
@@ -1,8 +1,7 @@
1
- import { fetchOne } from "@cosmicdrift/kumiko-framework/bun-db";
2
- import { defineWriteHandler, SYSTEM_TENANT_ID, } from "@cosmicdrift/kumiko-framework/engine";
3
- import { requireForTenant } from "../../shared/index.js";
4
- import { templateResourcesTable } from "../table.js";
5
- 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";
6
5
  // System-Template anlegen/updaten. Liegt unter SYSTEM_TENANT_ID,
7
6
  // scope='system'. Nur SystemAdmin (globale Rolle). TenantAdmin kann
8
7
  // keine System-Defaults überschreiben — der nutzt upsertTenant für
@@ -11,58 +10,14 @@ export const upsertSystemWrite = defineWriteHandler({
11
10
  name: "upsert-system",
12
11
  schema: upsertPayloadSchema,
13
12
  access: { roles: ["SystemAdmin"] },
14
- description: "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.",
15
- handler: async (event, ctx) => {
16
- // @cast-boundary engine-payload — SYSTEM_TENANT_ID is a UUID literal,
17
- // asserted to the branded TenantId (parseTenantId equivalent).
18
- const tenantId = SYSTEM_TENANT_ID;
19
- const { db, streamTenantId } = requireForTenant(ctx, tenantId);
20
- const existing = await fetchOne(db, templateResourcesTable, {
21
- tenantId,
22
- slug: event.payload.slug,
23
- kind: event.payload.kind,
24
- locale: event.payload.locale,
25
- });
26
- const fields = {
27
- slug: event.payload.slug,
28
- kind: event.payload.kind,
29
- locale: event.payload.locale,
30
- content: event.payload.content,
31
- contentFormat: event.payload.contentFormat,
32
- variableSchema: JSON.stringify(event.payload.variableSchema),
33
- linkedResources: JSON.stringify(event.payload.linkedResources),
34
- title: event.payload.title ?? null,
35
- folder: event.payload.folder ?? null,
36
- scope: "system",
37
- parentTemplateId: event.payload.parentTemplateId ?? null,
38
- // System-Defaults sind sofort active (kein draft-Stage für seeds).
39
- status: "active",
40
- };
41
- if (existing) {
42
- const result = await executor.update({ id: existing.id, version: existing.version, changes: fields }, event.user, db, { streamTenantId });
43
- if (!result.isSuccess)
44
- return result;
45
- return {
46
- isSuccess: true,
47
- data: { id: String(existing.id), slug: event.payload.slug, isNew: false },
48
- };
49
- }
50
- const result = await executor.create({ ...fields, tenantId }, event.user, db, {
51
- streamTenantId,
52
- });
53
- if (!result.isSuccess)
54
- return result;
55
- // @cast-boundary db-row — executor.create returnt Record-row aus
56
- // INSERT RETURNING; shape { id } ist garantiert weil PK in der
57
- // Returning-Klausel ist.
58
- const createdRow = result.data;
59
- return {
60
- isSuccess: true,
61
- data: {
62
- id: String(createdRow.id),
63
- slug: event.payload.slug,
64
- isNew: true,
65
- },
66
- };
67
- },
13
+ description: "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.",
14
+ handler: (event, ctx) => writeTemplateRow({
15
+ ctx,
16
+ user: event.user,
17
+ tenantId: SYSTEM_TENANT_ID,
18
+ scope: "system",
19
+ status: "active",
20
+ variables: resolveTemplateVariables(ctx.registry, event.payload.slug, event.payload.variableSchema),
21
+ payload: event.payload,
22
+ }),
68
23
  });
@@ -5,8 +5,9 @@ export declare const upsertTenantWrite: import("@cosmicdrift/kumiko-types/define
5
5
  "ai-prompt": "ai-prompt";
6
6
  "document-pdf": "document-pdf";
7
7
  "image-snapshot": "image-snapshot";
8
- "mail-html": "mail-html";
9
- notification: "notification";
8
+ mail: "mail";
9
+ "mail-layout": "mail-layout";
10
+ "pdf-layout": "pdf-layout";
10
11
  }>;
11
12
  locale: z.ZodString;
12
13
  content: z.ZodString;
@@ -17,7 +18,7 @@ export declare const upsertTenantWrite: import("@cosmicdrift/kumiko-types/define
17
18
  plain: "plain";
18
19
  }>;
19
20
  variableSchema: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
20
- linkedResources: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodString>>;
21
+ meta: z.ZodOptional<z.ZodUnknown>;
21
22
  parentTemplateId: z.ZodOptional<z.ZodString>;
22
23
  title: z.ZodOptional<z.ZodNullable<z.ZodString>>;
23
24
  folder: z.ZodOptional<z.ZodNullable<z.ZodString>>;
@@ -26,8 +27,4 @@ export declare const upsertTenantWrite: import("@cosmicdrift/kumiko-types/define
26
27
  active: "active";
27
28
  draft: "draft";
28
29
  }>>;
29
- }, z.core.$strip>, {
30
- id: string;
31
- slug: string;
32
- isNew: boolean;
33
- }, import("@cosmicdrift/kumiko-types/event-type-map").KumikoEventTypeMap>;
30
+ }, z.core.$strip>, import("./template-write.js").WrittenTemplateRow, import("@cosmicdrift/kumiko-types/event-type-map").KumikoEventTypeMap>;
@@ -1,10 +1,8 @@
1
- import { fetchOne } from "@cosmicdrift/kumiko-framework/bun-db";
2
- import { defineWriteHandler, SYSTEM_TENANT_ID, } from "@cosmicdrift/kumiko-framework/engine";
3
- import { AccessDeniedError, writeFailure } from "@cosmicdrift/kumiko-framework/errors";
1
+ import { defineWriteHandler } from "@cosmicdrift/kumiko-framework/engine";
4
2
  import * as z from "zod";
5
- import { requireForTenant } from "../../shared/index.js";
6
- import { templateResourcesTable } from "../table.js";
7
- 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";
8
6
  // Tenant-Override anlegen/updaten. Liegt unter event.user.tenantId,
9
7
  // scope='tenant'. Default-Status='draft' — User publisht explizit
10
8
  // via publish-Handler. SystemAdmin kann via tenantIdOverride für
@@ -19,67 +17,18 @@ export const upsertTenantWrite = defineWriteHandler({
19
17
  access: { roles: ["TenantAdmin", "SystemAdmin"] },
20
18
  description: "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.",
21
19
  handler: async (event, ctx) => {
22
- const override = event.payload.tenantIdOverride;
23
- // upsertTenant erzeugt scope='tenant'. SYSTEM_TENANT_ID-Override würde
24
- // scope='tenant' unter SYSTEM_TENANT_ID schreiben → inkonsistenter Zustand
25
- // (Resolver-Logik trennt sauber zwischen system+tenant). SystemAdmin muss
26
- // upsertSystem für System-Defaults nutzen.
27
- if (override === SYSTEM_TENANT_ID) {
28
- return writeFailure(new AccessDeniedError({
29
- i18nKey: "templateResolver.errors.useUpsertSystemForSystemTenant",
30
- details: { reason: "system_tenant_override_not_allowed_use_upsert_system" },
31
- }));
32
- }
33
20
  // @cast-boundary engine-payload — override aus Zod-parsed string,
34
21
  // event.user.tenantId schon TenantId-branded; union als TenantId casten
35
22
  // ist legit (override ist UUID-Format-validiert in schema).
36
- const tenantId = (override ?? event.user.tenantId);
37
- const { db, streamTenantId } = requireForTenant(ctx, tenantId);
38
- const existing = await fetchOne(db, templateResourcesTable, {
23
+ const tenantId = (event.payload.tenantIdOverride ?? event.user.tenantId);
24
+ return writeTemplateRow({
25
+ ctx,
26
+ user: event.user,
39
27
  tenantId,
40
- slug: event.payload.slug,
41
- kind: event.payload.kind,
42
- locale: event.payload.locale,
43
- });
44
- const fields = {
45
- slug: event.payload.slug,
46
- kind: event.payload.kind,
47
- locale: event.payload.locale,
48
- content: event.payload.content,
49
- contentFormat: event.payload.contentFormat,
50
- variableSchema: JSON.stringify(event.payload.variableSchema),
51
- linkedResources: JSON.stringify(event.payload.linkedResources),
52
- title: event.payload.title ?? null,
53
- folder: event.payload.folder ?? null,
54
28
  scope: "tenant",
55
- parentTemplateId: event.payload.parentTemplateId ?? null,
56
29
  status: event.payload.status,
57
- };
58
- if (existing) {
59
- const result = await executor.update({ id: existing.id, version: existing.version, changes: fields }, event.user, db, { streamTenantId });
60
- if (!result.isSuccess)
61
- return result;
62
- return {
63
- isSuccess: true,
64
- data: { id: String(existing.id), slug: event.payload.slug, isNew: false },
65
- };
66
- }
67
- const result = await executor.create({ ...fields, tenantId }, event.user, db, {
68
- streamTenantId,
30
+ variables: resolveTemplateVariables(ctx.registry, event.payload.slug, event.payload.variableSchema),
31
+ payload: event.payload,
69
32
  });
70
- if (!result.isSuccess)
71
- return result;
72
- // @cast-boundary db-row — executor.create returnt Record-row aus
73
- // INSERT RETURNING; shape { id } ist garantiert weil PK in der
74
- // Returning-Klausel ist.
75
- const createdRow = result.data;
76
- return {
77
- isSuccess: true,
78
- data: {
79
- id: String(createdRow.id),
80
- slug: event.payload.slug,
81
- isNew: true,
82
- },
83
- };
84
33
  },
85
34
  });
@@ -0,0 +1,17 @@
1
+ import * as z from "zod";
2
+ export declare const usageQuery: import("@cosmicdrift/kumiko-types/define-handler").QueryHandlerDefinition<"usage", z.ZodObject<{
3
+ slug: z.ZodString;
4
+ kind: z.ZodEnum<{
5
+ "ai-prompt": "ai-prompt";
6
+ "document-pdf": "document-pdf";
7
+ "image-snapshot": "image-snapshot";
8
+ mail: "mail";
9
+ "mail-layout": "mail-layout";
10
+ notification: "notification";
11
+ "pdf-layout": "pdf-layout";
12
+ snippet: "snippet";
13
+ "text-block": "text-block";
14
+ }>;
15
+ }, z.core.$strip>, {
16
+ usedBy: import("../template-usage.js").TemplateUsageEntry[];
17
+ }, import("@cosmicdrift/kumiko-types/event-type-map").KumikoEventTypeMap>;
@@ -0,0 +1,15 @@
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
+ export const usageQuery = defineQueryHandler({
7
+ name: "usage",
8
+ schema: z.object({ slug: templateSlugSchema, kind: z.enum(TEMPLATE_KINDS) }),
9
+ access: { roles: ["TenantAdmin", "SystemAdmin"] },
10
+ description: "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.",
11
+ handler: async (query, ctx) => {
12
+ const { slug, kind } = query.payload;
13
+ return { usedBy: collectTemplateUsage(ctx.registry, slug, kind) };
14
+ },
15
+ });
@@ -0,0 +1,74 @@
1
+ import { type HandlerContext, type SessionUser } from "@cosmicdrift/kumiko-framework/engine";
2
+ import * as z from "zod";
3
+ import type { ContentFormat, UpsertKind } from "../constants.js";
4
+ import type { TemplateWriteScope } from "../layout-regions.js";
5
+ type TemplateDraftInput = {
6
+ readonly slug: string;
7
+ readonly kind: UpsertKind;
8
+ readonly contentFormat: ContentFormat;
9
+ readonly content: string;
10
+ readonly meta?: unknown;
11
+ readonly variableSchema: Readonly<Record<string, unknown>>;
12
+ readonly locale?: string | undefined;
13
+ readonly scope: TemplateWriteScope;
14
+ };
15
+ export declare function validateTemplateDraft(ctx: HandlerContext, user: SessionUser, payload: TemplateDraftInput): Promise<{
16
+ response: {
17
+ canSave: boolean;
18
+ canPublish: boolean;
19
+ issues: import("@cosmicdrift/kumiko-headless").FieldIssue[];
20
+ publishIssues: import("@cosmicdrift/kumiko-headless").FieldIssue[];
21
+ used: string[];
22
+ unknown: string[];
23
+ missingRequired: string[];
24
+ warnings: {
25
+ code: "resource_not_uploaded";
26
+ detail: string;
27
+ i18nKey: "templateResolver.warnings.resourceNotUploaded";
28
+ }[];
29
+ variables: Readonly<Record<string, import("@cosmicdrift/kumiko-types/config").TemplateVariableDef>>;
30
+ systemPlaceholders: string[];
31
+ };
32
+ evaluation: import("./template-gates.js").TemplateWriteResult;
33
+ }>;
34
+ export declare const validateQuery: import("@cosmicdrift/kumiko-types/define-handler").QueryHandlerDefinition<"validate", z.ZodObject<{
35
+ slug: z.ZodString;
36
+ kind: z.ZodEnum<{
37
+ "ai-prompt": "ai-prompt";
38
+ "document-pdf": "document-pdf";
39
+ "image-snapshot": "image-snapshot";
40
+ mail: "mail";
41
+ "mail-layout": "mail-layout";
42
+ "pdf-layout": "pdf-layout";
43
+ }>;
44
+ contentFormat: z.ZodEnum<{
45
+ html: "html";
46
+ markdown: "markdown";
47
+ mjml: "mjml";
48
+ plain: "plain";
49
+ }>;
50
+ content: z.ZodString;
51
+ meta: z.ZodOptional<z.ZodUnknown>;
52
+ variableSchema: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
53
+ locale: z.ZodOptional<z.ZodString>;
54
+ scope: z.ZodDefault<z.ZodEnum<{
55
+ system: "system";
56
+ tenant: "tenant";
57
+ }>>;
58
+ }, z.core.$strip>, {
59
+ canSave: boolean;
60
+ canPublish: boolean;
61
+ issues: import("@cosmicdrift/kumiko-headless").FieldIssue[];
62
+ publishIssues: import("@cosmicdrift/kumiko-headless").FieldIssue[];
63
+ used: string[];
64
+ unknown: string[];
65
+ missingRequired: string[];
66
+ warnings: {
67
+ code: "resource_not_uploaded";
68
+ detail: string;
69
+ i18nKey: "templateResolver.warnings.resourceNotUploaded";
70
+ }[];
71
+ variables: Readonly<Record<string, import("@cosmicdrift/kumiko-types/config").TemplateVariableDef>>;
72
+ systemPlaceholders: string[];
73
+ }, import("@cosmicdrift/kumiko-types/event-type-map").KumikoEventTypeMap>;
74
+ export {};