@cosmicdrift/kumiko-bundled-features 0.374.0 → 0.377.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 (205) hide show
  1. package/dist/account-security/constants.d.ts +1 -0
  2. package/dist/account-security/constants.js +1 -0
  3. package/dist/account-security/index.d.ts +1 -1
  4. package/dist/account-security/index.js +1 -1
  5. package/dist/admin-shell/web/client-plugin.d.ts +1 -1
  6. package/dist/audit/web/client-plugin.d.ts +1 -1
  7. package/dist/auth-email-password/mail-notifications.js +1 -1
  8. package/dist/auth-email-password/web/auth-client.js +1 -1
  9. package/dist/auth-email-password/web/auth-form-primitives.js +1 -1
  10. package/dist/auth-email-password/web/default-topbar-actions.js +1 -1
  11. package/dist/auth-email-password/web/index.js +1 -1
  12. package/dist/auth-email-password/web/session.js +1 -1
  13. package/dist/auth-email-password/web/tenant-menu-items.js +1 -1
  14. package/dist/auth-email-password/web/tenant-switcher.js +1 -1
  15. package/dist/auth-email-password/web/user-menu.js +1 -1
  16. package/dist/auth-foundation/env.d.ts +9 -0
  17. package/dist/auth-foundation/env.js +21 -0
  18. package/dist/auth-foundation/index.d.ts +1 -0
  19. package/dist/auth-foundation/index.js +1 -0
  20. package/dist/auth-mfa/schema/user-mfa.d.ts +6 -6
  21. package/dist/billing-foundation/consumer-protection/confirmation-mail.js +1 -1
  22. package/dist/billing-foundation/consumer-protection/termination-pages.js +1 -1
  23. package/dist/billing-foundation/contact-card.d.ts +2 -0
  24. package/dist/billing-foundation/contact-card.js +16 -0
  25. package/dist/billing-foundation/entities.d.ts +8 -8
  26. package/dist/billing-foundation/handlers/billing-plans.query.js +1 -1
  27. package/dist/billing-foundation/index.d.ts +1 -1
  28. package/dist/billing-foundation/plan-catalog.js +1 -0
  29. package/dist/billing-foundation/projection.d.ts +8 -8
  30. package/dist/billing-foundation/types.d.ts +11 -0
  31. package/dist/billing-foundation/validate-options.js +13 -0
  32. package/dist/billing-foundation/web/billing-contact-plan.d.ts +5 -0
  33. package/dist/billing-foundation/web/billing-contact-plan.js +17 -0
  34. package/dist/billing-foundation/web/billing-plans-panel.js +5 -13
  35. package/dist/billing-foundation/web/cancel-contract-dialog.js +2 -2
  36. package/dist/billing-foundation/web/checkout-consent-dialog.d.ts +1 -1
  37. package/dist/billing-foundation/web/checkout-consent-dialog.js +1 -0
  38. package/dist/billing-foundation/web/client-plugin.d.ts +1 -1
  39. package/dist/billing-foundation/web/localized-params.d.ts +1 -0
  40. package/dist/billing-foundation/web/localized-params.js +11 -0
  41. package/dist/cap-counter/entity.d.ts +1 -1
  42. package/dist/cap-overview/web/cap-cards-panel.js +3 -3
  43. package/dist/cap-overview/web/client-plugin.d.ts +1 -1
  44. package/dist/channel-email/email-channel.d.ts +2 -1
  45. package/dist/compliance-profiles/web/client-plugin.d.ts +1 -1
  46. package/dist/config/feature.js +2 -2
  47. package/dist/config/table.d.ts +6 -6
  48. package/dist/custom-fields/entity.d.ts +4 -4
  49. package/dist/custom-fields/web/client-plugin.d.ts +1 -1
  50. package/dist/data-retention/schema/tenant-retention-override.d.ts +4 -4
  51. package/dist/delivery/tables.d.ts +6 -6
  52. package/dist/delivery/unsubscribe-page.js +1 -1
  53. package/dist/delivery/web/client-plugin.d.ts +1 -1
  54. package/dist/delivery/web/delivery-status-cell.js +1 -1
  55. package/dist/document-ingest-foundation/entity.d.ts +4 -4
  56. package/dist/feature-toggles/web/client-plugin.d.ts +1 -1
  57. package/dist/file-provider-s3/env-named-feature.d.ts +17 -0
  58. package/dist/file-provider-s3/env-named-feature.js +23 -0
  59. package/dist/file-provider-s3/env-schema.d.ts +7 -0
  60. package/dist/file-provider-s3/env-schema.js +15 -10
  61. package/dist/file-provider-s3/index.d.ts +1 -0
  62. package/dist/file-provider-s3/index.js +1 -0
  63. package/dist/folders/entity.d.ts +8 -8
  64. package/dist/folders/web/client-plugin.d.ts +1 -1
  65. package/dist/form-draft/entity.d.ts +2 -2
  66. package/dist/host-routing/constants.d.ts +2 -0
  67. package/dist/host-routing/constants.js +3 -0
  68. package/dist/host-routing/dispatch.d.ts +1 -1
  69. package/dist/host-routing/dispatch.js +35 -25
  70. package/dist/host-routing/feature.js +100 -27
  71. package/dist/host-routing/host.d.ts +9 -0
  72. package/dist/host-routing/host.js +24 -2
  73. package/dist/host-routing/index.d.ts +2 -1
  74. package/dist/host-routing/index.js +1 -0
  75. package/dist/host-routing/options.d.ts +14 -0
  76. package/dist/host-routing/redirects.d.ts +6 -0
  77. package/dist/host-routing/redirects.js +27 -0
  78. package/dist/host-routing/tenant-lookup.d.ts +7 -0
  79. package/dist/host-routing/tenant-lookup.js +39 -0
  80. package/dist/host-routing/validate-options.js +34 -1
  81. package/dist/inbound-mail-foundation/entities.d.ts +25 -25
  82. package/dist/inbound-mail-foundation/feature.js +7 -0
  83. package/dist/inbound-mail-foundation/i18n.d.ts +3 -0
  84. package/dist/inbound-mail-foundation/i18n.js +35 -0
  85. package/dist/inbound-mail-foundation/index.d.ts +1 -0
  86. package/dist/inbound-mail-foundation/index.js +1 -0
  87. package/dist/inbound-mail-foundation/projection.d.ts +20 -20
  88. package/dist/inbound-mail-foundation/screens.d.ts +7 -0
  89. package/dist/inbound-mail-foundation/screens.js +86 -0
  90. package/dist/inbound-mail-foundation/types.d.ts +2 -2
  91. package/dist/jobs/job-run-table.d.ts +6 -6
  92. package/dist/jobs/web/client-plugin.d.ts +1 -1
  93. package/dist/ledger/entity.d.ts +13 -13
  94. package/dist/legal-pages/feature.js +11 -1
  95. package/dist/legal-pages/layout.d.ts +2 -2
  96. package/dist/legal-pages/layout.js +4 -4
  97. package/dist/legal-pages/web/client-plugin.d.ts +1 -1
  98. package/dist/mail-foundation/feature.d.ts +20 -21
  99. package/dist/mail-foundation/feature.js +1 -1
  100. package/dist/managed-pages/table.d.ts +12 -12
  101. package/dist/managed-pages/web/client-plugin.d.ts +1 -1
  102. package/dist/notes-history/entity.d.ts +12 -12
  103. package/dist/notes-history/web/client-plugin.d.ts +1 -1
  104. package/dist/page-render/branding.js +1 -1
  105. package/dist/page-render/layout.d.ts +1 -1
  106. package/dist/page-render/layout.js +2 -2
  107. package/dist/page-render/markdown.js +1 -1
  108. package/dist/personal-access-tokens/i18n.js +1 -1
  109. package/dist/personal-access-tokens/schema/api-token.d.ts +6 -6
  110. package/dist/personal-access-tokens/screens.js +1 -1
  111. package/dist/secrets/feature.d.ts +1 -1
  112. package/dist/secrets/feature.js +3 -3
  113. package/dist/secrets/table.d.ts +1 -1
  114. package/dist/seo/sitemap.js +1 -1
  115. package/dist/sessions/schema/user-session.d.ts +4 -4
  116. package/dist/shared/mail-template-body.js +1 -1
  117. package/dist/shared/purge-files-reaching-subject.js +98 -3
  118. package/dist/tags/entity.d.ts +9 -9
  119. package/dist/tags/web/client-plugin.d.ts +1 -1
  120. package/dist/template-resolver/document-blocks.d.ts +5 -0
  121. package/dist/template-resolver/document-blocks.js +21 -0
  122. package/dist/template-resolver/document-ops.d.ts +167 -0
  123. package/dist/template-resolver/document-ops.js +390 -0
  124. package/dist/template-resolver/document-sanitizer.d.ts +3 -0
  125. package/dist/template-resolver/document-sanitizer.js +6 -0
  126. package/dist/template-resolver/feature.d.ts +2 -0
  127. package/dist/template-resolver/feature.js +4 -0
  128. package/dist/template-resolver/handlers/bundle-import.js +1 -0
  129. package/dist/template-resolver/handlers/document-base.d.ts +23 -0
  130. package/dist/template-resolver/handlers/document-base.js +55 -0
  131. package/dist/template-resolver/handlers/document-blocks.query.d.ts +22 -0
  132. package/dist/template-resolver/handlers/document-blocks.query.js +46 -0
  133. package/dist/template-resolver/handlers/export-bundle.query.d.ts +1 -0
  134. package/dist/template-resolver/handlers/export-bundle.query.js +9 -2
  135. package/dist/template-resolver/handlers/import-bundle-system.write.d.ts +1 -0
  136. package/dist/template-resolver/handlers/import-bundle.write.d.ts +1 -0
  137. package/dist/template-resolver/handlers/patch-draft.write.d.ts +66 -0
  138. package/dist/template-resolver/handlers/patch-draft.write.js +105 -0
  139. package/dist/template-resolver/handlers/preview.query.d.ts +1 -0
  140. package/dist/template-resolver/handlers/preview.query.js +2 -1
  141. package/dist/template-resolver/handlers/send-test.write.d.ts +1 -0
  142. package/dist/template-resolver/handlers/send-test.write.js +2 -1
  143. package/dist/template-resolver/handlers/template-write.d.ts +2 -0
  144. package/dist/template-resolver/handlers/template-write.js +14 -2
  145. package/dist/template-resolver/handlers/validate.query.d.ts +10 -4
  146. package/dist/template-resolver/handlers/validate.query.js +12 -7
  147. package/dist/template-resolver/{web/placeholder-text.js → placeholder-text.js} +1 -1
  148. package/dist/template-resolver/qualified-names.d.ts +2 -0
  149. package/dist/template-resolver/qualified-names.js +2 -0
  150. package/dist/template-resolver/seeding.d.ts +1 -0
  151. package/dist/template-resolver/seeding.js +2 -1
  152. package/dist/template-resolver/table.d.ts +10 -10
  153. package/dist/template-resolver/template-bundle.d.ts +1 -0
  154. package/dist/template-resolver/template-bundle.js +6 -1
  155. package/dist/template-resolver/template-preview.d.ts +4 -1
  156. package/dist/template-resolver/template-preview.js +16 -3
  157. package/dist/template-resolver/template-validation.d.ts +2 -1
  158. package/dist/template-resolver/template-validation.js +1 -1
  159. package/dist/template-resolver/user-content-table.d.ts +10 -10
  160. package/dist/template-resolver/web/client-plugin.d.ts +1 -1
  161. package/dist/template-resolver/web/client-plugin.js +3 -3
  162. package/dist/template-resolver/web/document-tiptap-editor.js +1 -1
  163. package/dist/template-resolver/web/document-toolbar-blocks.d.ts +2 -5
  164. package/dist/template-resolver/web/document-toolbar-blocks.js +1 -21
  165. package/dist/template-resolver/web/i18n.js +14 -0
  166. package/dist/template-resolver/web/placeholder-line-editor.js +2 -2
  167. package/dist/tenant/invitation-table.d.ts +6 -6
  168. package/dist/tenant/membership-table.d.ts +4 -4
  169. package/dist/tenant/schema/tenant.d.ts +6 -6
  170. package/dist/tenant/web/client-plugin.d.ts +1 -1
  171. package/dist/tenant/web/member-status-cell.js +1 -1
  172. package/dist/tier-engine/entity.d.ts +1 -1
  173. package/dist/tier-engine/tier-resolver.d.ts +1 -1
  174. package/dist/tier-engine/web/client-plugin.d.ts +1 -1
  175. package/dist/user/schema/user.d.ts +12 -12
  176. package/dist/user-data-rights/env.d.ts +5 -0
  177. package/dist/user-data-rights/env.js +11 -0
  178. package/dist/user-data-rights/feature.js +4 -2
  179. package/dist/user-data-rights/index.d.ts +1 -0
  180. package/dist/user-data-rights/index.js +1 -0
  181. package/dist/user-data-rights/lib/mail-transport-resolver.js +1 -1
  182. package/dist/user-data-rights/lib/storage-provider-resolver.js +1 -1
  183. package/dist/user-data-rights/mail-notifications.js +1 -1
  184. package/dist/user-data-rights/schema/download-attempt.d.ts +14 -14
  185. package/dist/user-data-rights/schema/download-token.d.ts +8 -8
  186. package/dist/user-data-rights/schema/export-job.d.ts +6 -6
  187. package/dist/user-data-rights/web/client-plugin.d.ts +1 -1
  188. package/dist/user-data-rights/web/privacy-center-screen.js +2 -2
  189. package/dist/waitlist/entity.d.ts +14 -14
  190. package/dist/waitlist/legacy-migrate.d.ts +2 -0
  191. package/dist/waitlist/legacy-migrate.js +38 -3
  192. package/package.json +16 -11
  193. package/src/account-security/changes.json +6 -0
  194. package/src/auth-foundation/changes.json +6 -0
  195. package/src/billing-foundation/changes.json +6 -0
  196. package/src/channel-email/changes.json +6 -0
  197. package/src/crypto-shredding/changes.json +6 -0
  198. package/src/file-provider-s3/changes.json +6 -0
  199. package/src/host-routing/changes.json +6 -0
  200. package/src/inbound-mail-foundation/changes.json +7 -0
  201. package/src/legal-pages/changes.json +6 -0
  202. package/src/template-resolver/changes.json +18 -0
  203. package/src/user-data-rights/changes.json +6 -0
  204. package/src/waitlist/changes.json +6 -0
  205. /package/dist/template-resolver/{web/placeholder-text.d.ts → placeholder-text.d.ts} +0 -0
@@ -20,6 +20,7 @@ export declare const importBundleWrite: import("@cosmicdrift/kumiko-types/define
20
20
  locales: z.ZodRecord<z.ZodString, z.ZodObject<{
21
21
  body: z.ZodString;
22
22
  meta: z.ZodOptional<z.ZodUnknown>;
23
+ title: z.ZodOptional<z.ZodString>;
23
24
  }, z.core.$strict>>;
24
25
  resources: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodObject<{
25
26
  fileName: z.ZodString;
@@ -0,0 +1,66 @@
1
+ import * as z from "zod";
2
+ export declare const patchDraftWrite: import("@cosmicdrift/kumiko-types/define-handler").WriteHandlerDefinition<"patch-draft", z.ZodObject<{
3
+ slug: z.ZodString;
4
+ kind: z.ZodEnum<{
5
+ "document-pdf": "document-pdf";
6
+ mail: "mail";
7
+ }>;
8
+ locale: z.ZodString;
9
+ base: z.ZodObject<{
10
+ scope: z.ZodEnum<{
11
+ system: "system";
12
+ tenant: "tenant";
13
+ }>;
14
+ version: z.ZodNumber;
15
+ }, z.core.$strip>;
16
+ ops: z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
17
+ op: z.ZodLiteral<"insertBlock">;
18
+ after: z.ZodNullable<z.ZodString>;
19
+ block: z.ZodDiscriminatedUnion<[z.ZodObject<{
20
+ type: z.ZodLiteral<"paragraph">;
21
+ text: z.ZodString;
22
+ align: z.ZodOptional<z.ZodEnum<{
23
+ center: "center";
24
+ justify: "justify";
25
+ left: "left";
26
+ right: "right";
27
+ }>>;
28
+ }, z.core.$strip>, z.ZodObject<{
29
+ type: z.ZodLiteral<"heading">;
30
+ level: z.ZodNumber;
31
+ text: z.ZodString;
32
+ }, z.core.$strip>, z.ZodObject<{
33
+ type: z.ZodLiteral<"image">;
34
+ resource: z.ZodString;
35
+ alt: z.ZodOptional<z.ZodString>;
36
+ }, z.core.$strip>, z.ZodObject<{
37
+ type: z.ZodLiteral<"button">;
38
+ text: z.ZodString;
39
+ href: z.ZodString;
40
+ }, z.core.$strip>, z.ZodObject<{
41
+ type: z.ZodLiteral<"divider">;
42
+ }, z.core.$strip>, z.ZodObject<{
43
+ type: z.ZodLiteral<"pageBreak">;
44
+ }, z.core.$strip>], "type">;
45
+ }, z.core.$strip>, z.ZodObject<{
46
+ op: z.ZodLiteral<"replaceText">;
47
+ blockId: z.ZodString;
48
+ find: z.ZodString;
49
+ replace: z.ZodString;
50
+ }, z.core.$strip>, z.ZodObject<{
51
+ op: z.ZodLiteral<"removeBlock">;
52
+ blockId: z.ZodString;
53
+ }, z.core.$strip>, z.ZodObject<{
54
+ op: z.ZodLiteral<"setResource">;
55
+ blockId: z.ZodString;
56
+ resource: z.ZodString;
57
+ alt: z.ZodOptional<z.ZodNullable<z.ZodString>>;
58
+ }, z.core.$strip>, z.ZodObject<{
59
+ op: z.ZodLiteral<"setMeta">;
60
+ meta: z.ZodRecord<z.ZodString, z.ZodUnknown>;
61
+ }, z.core.$strip>, z.ZodObject<{
62
+ op: z.ZodLiteral<"setVariable">;
63
+ name: z.ZodString;
64
+ definition: z.ZodNullable<z.ZodUnknown>;
65
+ }, z.core.$strip>], "op">>;
66
+ }, z.core.$strip>, import("./template-write.ts").WrittenTemplateRow, import("@cosmicdrift/kumiko-types/event-type-map").KumikoEventTypeMap, undefined>;
@@ -0,0 +1,105 @@
1
+ import { defineWriteHandler } from "@cosmicdrift/kumiko-framework/engine";
2
+ import { NotFoundError, ValidationError, VersionConflictError, writeFailure, } from "@cosmicdrift/kumiko-framework/errors";
3
+ import * as z from "zod";
4
+ import { DOCUMENT_HTML_KINDS } from "../constants.js";
5
+ import { applyDocumentOps, documentOpsSchema } from "../document-ops.js";
6
+ import { parseDocumentJson, serializeDocumentJson } from "../document-sanitizer.js";
7
+ import { resolveTemplateVariables } from "../template-validation.js";
8
+ import { loadDocumentBase } from "./document-base.js";
9
+ import { localeSchema, templateSlugSchema } from "./shared.js";
10
+ import { writeTemplateRow } from "./template-write.js";
11
+ import { validateTemplateDraft } from "./validate.query.js";
12
+ function validationFailure(issue) {
13
+ return writeFailure(new ValidationError({ fields: [issue] }));
14
+ }
15
+ export const patchDraftWrite = defineWriteHandler({
16
+ name: "patch-draft",
17
+ schema: z.object({
18
+ slug: templateSlugSchema,
19
+ kind: z.enum(DOCUMENT_HTML_KINDS),
20
+ locale: localeSchema,
21
+ base: z.object({
22
+ scope: z.enum(["tenant", "system"]),
23
+ version: z.number().int().nonnegative(),
24
+ }),
25
+ ops: documentOpsSchema,
26
+ }),
27
+ access: { roles: ["TenantAdmin", "SystemAdmin"] },
28
+ description: "Changes a mail or PDF document template with structured operations instead of a full rewrite and saves the result as the caller's tenant draft (never published, an active version keeps being served until publish): insertBlock after a block id (null = at the start; paragraph, heading, image, button, divider, pageBreak), replaceText inside one block (the text must occur exactly once), removeBlock, setResource on an image block, setMeta (flat merge, null removes a key) and setVariable (adds, replaces or with null removes a variable definition; refused when a notification declares the variables). Block ids and base come from document-blocks and refer to the state that was read; if the template changed since, the call fails with version_conflict and nothing is written. A patch that removes a required placeholder is refused. Returns the written row.",
29
+ handler: async (event, ctx) => {
30
+ const { slug, kind, locale, base, ops } = event.payload;
31
+ const loaded = await loadDocumentBase(ctx, event.user, { slug, kind, locale });
32
+ if (loaded === null)
33
+ return writeFailure(new NotFoundError("template-resource", slug));
34
+ if (loaded.base.scope !== base.scope || loaded.base.version !== base.version) {
35
+ return writeFailure(new VersionConflictError({
36
+ entityId: loaded.rowId,
37
+ expectedVersion: base.version,
38
+ currentVersion: loaded.base.version,
39
+ }));
40
+ }
41
+ const variableOpIndex = ops.findIndex((op) => op.op === "setVariable");
42
+ if (variableOpIndex !== -1 &&
43
+ ctx.registry.getAllNotifications().get(slug)?.variables !== undefined) {
44
+ return validationFailure({
45
+ path: `ops.${variableOpIndex}`,
46
+ code: "variables_declared_by_feature",
47
+ i18nKey: "templateResolver.errors.patch.variablesDeclaredByFeature",
48
+ params: { slug },
49
+ });
50
+ }
51
+ const applied = applyDocumentOps({
52
+ doc: parseDocumentJson(loaded.content),
53
+ meta: loaded.meta,
54
+ variableSchema: loaded.variableSchema,
55
+ kind,
56
+ ops,
57
+ });
58
+ if (!applied.ok)
59
+ return writeFailure(new ValidationError({ fields: applied.issues }));
60
+ const content = serializeDocumentJson(applied.doc);
61
+ const draftOf = (draftContent, meta, variableSchema) => validateTemplateDraft(ctx, event.user, {
62
+ slug,
63
+ kind,
64
+ contentFormat: "html",
65
+ content: draftContent,
66
+ meta,
67
+ variableSchema,
68
+ locale,
69
+ scope: "tenant",
70
+ });
71
+ const before = await draftOf(loaded.content, loaded.meta, loaded.variableSchema);
72
+ const after = await draftOf(content, applied.meta, applied.variableSchema);
73
+ const alreadyMissing = new Set(before.response.missingRequired);
74
+ const removed = after.response.missingRequired.filter((name) => !alreadyMissing.has(name));
75
+ if (removed.length > 0) {
76
+ return validationFailure({
77
+ path: "ops",
78
+ code: "required_placeholder_removed",
79
+ i18nKey: "templateResolver.errors.patch.requiredPlaceholderRemoved",
80
+ params: { names: removed },
81
+ });
82
+ }
83
+ return writeTemplateRow({
84
+ ctx,
85
+ user: event.user,
86
+ tenantId: event.user.tenantId,
87
+ scope: "tenant",
88
+ status: "draft",
89
+ variables: resolveTemplateVariables(ctx.registry, slug, applied.variableSchema),
90
+ payload: {
91
+ slug,
92
+ kind,
93
+ locale,
94
+ content,
95
+ contentFormat: "html",
96
+ variableSchema: applied.variableSchema,
97
+ meta: applied.meta,
98
+ title: loaded.title,
99
+ folder: loaded.folder,
100
+ parentTemplateId: loaded.parentTemplateId,
101
+ },
102
+ expectedVersion: loaded.base.scope === "tenant" ? loaded.base.version : null,
103
+ });
104
+ },
105
+ });
@@ -9,4 +9,5 @@ export declare const previewQuery: import("@cosmicdrift/kumiko-types/define-hand
9
9
  content: z.ZodOptional<z.ZodString>;
10
10
  meta: z.ZodOptional<z.ZodUnknown>;
11
11
  variableSchema: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
12
+ variables: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
12
13
  }, z.core.$strip>, import("../template-preview.ts").TemplatePreview, import("@cosmicdrift/kumiko-types/event-type-map").KumikoEventTypeMap>;
@@ -12,9 +12,10 @@ export const previewQuery = defineQueryHandler({
12
12
  content: z.string().max(200_000).optional(),
13
13
  meta: z.unknown().optional(),
14
14
  variableSchema: z.record(z.string(), z.unknown()).default({}),
15
+ variables: z.record(z.string(), z.unknown()).optional(),
15
16
  }),
16
17
  access: { roles: ["TenantAdmin", "SystemAdmin"] },
17
- description: "Renders a template draft, or the stored template when no content is given, with sample values taken from the declared variable examples, without saving or sending anything: returns the mail subject, html and plain text with its inline images (a PDF template returns its body and layout regions instead), the sample values used, and checks for unresolved images, missing plain text, mail size and unused required placeholders; a draft that fails the write rules is rejected with the same problems as a save.",
18
+ description: "Renders a template draft, or the stored template when no content is given, with sample values taken from the declared variable examples (values passed in variables replace them and are checked against the variable schema), without saving or sending anything: returns the mail subject, html and plain text with its inline images (a PDF template returns its body and layout regions instead), the sample values used, and checks for unresolved images, missing plain text, mail size and unused required placeholders; a draft that fails the write rules is rejected with the same problems as a save.",
18
19
  handler: async (query, ctx) => {
19
20
  const outcome = await renderTemplatePreview(ctx, query.user, query.payload);
20
21
  if (!outcome.ok)
@@ -5,6 +5,7 @@ export declare const sendTestWrite: import("@cosmicdrift/kumiko-types/define-han
5
5
  content: z.ZodOptional<z.ZodString>;
6
6
  meta: z.ZodOptional<z.ZodUnknown>;
7
7
  variableSchema: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
8
+ variables: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
8
9
  includeAttachments: z.ZodDefault<z.ZodBoolean>;
9
10
  prefixSubject: z.ZodDefault<z.ZodBoolean>;
10
11
  }, z.core.$strip>, {
@@ -31,12 +31,13 @@ export const sendTestWrite = defineWriteHandler({
31
31
  content: z.string().max(200_000).optional(),
32
32
  meta: z.unknown().optional(),
33
33
  variableSchema: z.record(z.string(), z.unknown()).default({}),
34
+ variables: z.record(z.string(), z.unknown()).optional(),
34
35
  includeAttachments: z.boolean().default(true),
35
36
  prefixSubject: z.boolean().default(true),
36
37
  }),
37
38
  access: { roles: ["TenantAdmin", "SystemAdmin"] },
38
39
  rateLimit: { per: "user+handler", ...TEST_MAIL_RATE_LIMIT },
39
- description: "Sends the rendered preview of a mail template draft, or of the stored template when no content is given, to the calling user's own address with the subject prefixed by [TEST] unless prefixSubject is false, carrying the inline images and, unless includeAttachments is false, the files attached to the template; there is no recipient input, the sample values come from the declared variable examples, and a draft that fails the write rules is rejected without sending.",
40
+ description: "Sends the rendered preview of a mail template draft, or of the stored template when no content is given, to the calling user's own address with the subject prefixed by [TEST] unless prefixSubject is false, carrying the inline images and, unless includeAttachments is false, the files attached to the template; there is no recipient input, the sample values come from the declared variable examples unless variables replaces them (checked against the variable schema), and a draft that fails the write rules is rejected without sending.",
40
41
  handler: async (event, ctx) => {
41
42
  const { includeAttachments, prefixSubject, ...previewPayload } = event.payload;
42
43
  const outcome = await renderTemplatePreview(ctx, event.user, {
@@ -30,6 +30,8 @@ export type TemplateRowWrite = {
30
30
  readonly status: "draft" | "active";
31
31
  readonly variables: TemplateVariables;
32
32
  readonly payload: TemplateRowPayload;
33
+ /** The row version the edit was made against; null means no own row may exist yet. */
34
+ readonly expectedVersion?: number | null;
33
35
  };
34
36
  export declare function refuseTenantScopeUnderSystemTenant(scope: TemplateWriteScope, tenantId: string): WriteResult<never> | null;
35
37
  export declare function refuseSystemOnlyTenantOverride(registry: Registry, scope: TemplateWriteScope, slug: string, kind: string): WriteFailure | null;
@@ -1,6 +1,6 @@
1
1
  import { fetchOne } from "@cosmicdrift/kumiko-framework/db";
2
2
  import { SYSTEM_TENANT_ID, } from "@cosmicdrift/kumiko-framework/engine";
3
- import { AccessDeniedError, ValidationError, writeFailure, } from "@cosmicdrift/kumiko-framework/errors";
3
+ import { AccessDeniedError, ValidationError, VersionConflictError, writeFailure, } from "@cosmicdrift/kumiko-framework/errors";
4
4
  import { requireForTenant } from "../../shared/index.js";
5
5
  import { TemplateResolverErrors } from "../qualified-names.js";
6
6
  import { templateResourcesTable } from "../table.js";
@@ -67,6 +67,15 @@ function updateChanges(existing, status, fields) {
67
67
  }
68
68
  : { draft };
69
69
  }
70
+ function versionConflictFor(expectedVersion, existing, slug) {
71
+ if (expectedVersion === undefined || (existing?.version ?? null) === expectedVersion)
72
+ return null;
73
+ return writeFailure(new VersionConflictError({
74
+ entityId: existing?.id ?? slug,
75
+ expectedVersion: expectedVersion ?? 0,
76
+ currentVersion: existing?.version ?? 0,
77
+ }));
78
+ }
70
79
  // Shared by every template write: validate through the one pipeline, then
71
80
  // create or update the (tenant, slug, kind, locale) row via the event-store
72
81
  // executor. linkedResources is never part of an update; resources are attached
@@ -109,10 +118,13 @@ export async function writeTemplateRow(args) {
109
118
  parentTemplateId: payload.parentTemplateId ?? null,
110
119
  status,
111
120
  };
121
+ const conflict = versionConflictFor(args.expectedVersion, existing, payload.slug);
122
+ if (conflict !== null)
123
+ return conflict;
112
124
  if (existing) {
113
125
  const result = await executor.update({
114
126
  id: existing.id,
115
- version: existing.version,
127
+ version: args.expectedVersion ?? existing.version,
116
128
  changes: updateChanges(existing, status, fields),
117
129
  }, user, db, { streamTenantId });
118
130
  if (!result.isSuccess)
@@ -12,12 +12,18 @@ type TemplateDraftInput = {
12
12
  readonly locale?: string | undefined;
13
13
  readonly scope: TemplateWriteScope;
14
14
  };
15
+ export declare function knownResourceKeysFor(ctx: HandlerContext, user: SessionUser, where: {
16
+ readonly slug: string;
17
+ readonly kind: string;
18
+ readonly scope: TemplateWriteScope;
19
+ readonly locale: string | undefined;
20
+ }): Promise<string[]>;
15
21
  export declare function validateTemplateDraft(ctx: HandlerContext, user: SessionUser, payload: TemplateDraftInput): Promise<{
16
22
  response: {
17
23
  canSave: boolean;
18
24
  canPublish: boolean;
19
- issues: import("@cosmicdrift/kumiko-headless").FieldIssue[];
20
- publishIssues: import("@cosmicdrift/kumiko-headless").FieldIssue[];
25
+ issues: import("@cosmicdrift/kumiko-framework/errors").FieldIssue[];
26
+ publishIssues: import("@cosmicdrift/kumiko-framework/errors").FieldIssue[];
21
27
  used: string[];
22
28
  unknown: string[];
23
29
  missingRequired: string[];
@@ -58,8 +64,8 @@ export declare const validateQuery: import("@cosmicdrift/kumiko-types/define-han
58
64
  }, z.core.$strip>, {
59
65
  canSave: boolean;
60
66
  canPublish: boolean;
61
- issues: import("@cosmicdrift/kumiko-headless").FieldIssue[];
62
- publishIssues: import("@cosmicdrift/kumiko-headless").FieldIssue[];
67
+ issues: import("@cosmicdrift/kumiko-framework/errors").FieldIssue[];
68
+ publishIssues: import("@cosmicdrift/kumiko-framework/errors").FieldIssue[];
63
69
  used: string[];
64
70
  unknown: string[];
65
71
  missingRequired: string[];
@@ -18,13 +18,10 @@ async function knownResourceKeys(db, where) {
18
18
  });
19
19
  return rows.flatMap((row) => imageResourceKeysOf(row.linkedResources));
20
20
  }
21
- // The dry run of the write pipeline; preview feeds on the evaluation, validate returns the response.
22
- export async function validateTemplateDraft(ctx, user, payload) {
23
- const variables = resolveTemplateVariables(ctx.registry, payload.slug, payload.variableSchema);
24
- const { slug, kind, locale } = payload;
25
- // The dry run must not promise what the caller's write handlers would refuse.
26
- const scope = isSystemAdmin(user) ? payload.scope : "tenant";
27
- const known = [
21
+ // Image keys the editor may reference: the system rows plus, for a tenant write, the caller's own.
22
+ export async function knownResourceKeysFor(ctx, user, where) {
23
+ const { slug, kind, scope, locale } = where;
24
+ return [
28
25
  ...(await knownResourceKeys(ctx.db, {
29
26
  tenantId: SYSTEM_TENANT_ID,
30
27
  slug,
@@ -36,6 +33,14 @@ export async function validateTemplateDraft(ctx, user, payload) {
36
33
  ? await knownResourceKeys(ctx.db, { tenantId: user.tenantId, slug, kind, scope, locale })
37
34
  : []),
38
35
  ];
36
+ }
37
+ // The dry run of the write pipeline; preview feeds on the evaluation, validate returns the response.
38
+ export async function validateTemplateDraft(ctx, user, payload) {
39
+ const variables = resolveTemplateVariables(ctx.registry, payload.slug, payload.variableSchema);
40
+ const { slug, kind, locale } = payload;
41
+ // The dry run must not promise what the caller's write handlers would refuse.
42
+ const scope = isSystemAdmin(user) ? payload.scope : "tenant";
43
+ const known = await knownResourceKeysFor(ctx, user, { slug, kind, scope, locale });
39
44
  const evaluation = evaluateTemplateWrite({
40
45
  kind,
41
46
  scope,
@@ -1,4 +1,4 @@
1
- import { isDocumentReferenceName } from "../document-schema.js";
1
+ import { isDocumentReferenceName } from "./document-schema.js";
2
2
  const PLACEHOLDER_TOKEN = /\{\{([^{}]*)\}\}/g;
3
3
  // Names the schema refuses stay plain text, so a typo never becomes a chip
4
4
  // that the server would reject.
@@ -17,6 +17,7 @@ export declare const TemplateResolverHandlers: {
17
17
  readonly resourceRemove: "template-resolver:write:resource-remove";
18
18
  readonly resourceLinkPdf: "template-resolver:write:resource-link-pdf";
19
19
  readonly sendTest: "template-resolver:write:send-test";
20
+ readonly patchDraft: "template-resolver:write:patch-draft";
20
21
  };
21
22
  export declare const TemplateResolverQueries: {
22
23
  readonly findById: "template-resolver:query:find-by-id";
@@ -28,6 +29,7 @@ export declare const TemplateResolverQueries: {
28
29
  readonly diffToSystem: "template-resolver:query:diff-to-system";
29
30
  readonly validate: "template-resolver:query:validate";
30
31
  readonly preview: "template-resolver:query:preview";
32
+ readonly documentBlocks: "template-resolver:query:document-blocks";
31
33
  readonly exportBundle: "template-resolver:query:export-bundle";
32
34
  readonly bySlug: "template-resolver:query:by-slug";
33
35
  readonly byTenant: "template-resolver:query:by-tenant";
@@ -19,6 +19,7 @@ export const TemplateResolverHandlers = {
19
19
  resourceRemove: "template-resolver:write:resource-remove",
20
20
  resourceLinkPdf: "template-resolver:write:resource-link-pdf",
21
21
  sendTest: "template-resolver:write:send-test",
22
+ patchDraft: "template-resolver:write:patch-draft",
22
23
  };
23
24
  export const TemplateResolverQueries = {
24
25
  findById: "template-resolver:query:find-by-id",
@@ -30,6 +31,7 @@ export const TemplateResolverQueries = {
30
31
  diffToSystem: "template-resolver:query:diff-to-system",
31
32
  validate: "template-resolver:query:validate",
32
33
  preview: "template-resolver:query:preview",
34
+ documentBlocks: "template-resolver:query:document-blocks",
33
35
  exportBundle: "template-resolver:query:export-bundle",
34
36
  // Public pair — anonymous-reachable, kind pinned to text-block.
35
37
  bySlug: "template-resolver:query:by-slug",
@@ -15,6 +15,7 @@ export type SeedSystemTemplateOptions = {
15
15
  * system admin uploaded through resource-upload. */
16
16
  readonly linkedResources?: Record<string, string>;
17
17
  readonly meta?: unknown;
18
+ readonly title?: string;
18
19
  readonly parentTemplateId?: string | null;
19
20
  readonly by?: SessionUser;
20
21
  readonly ifExists?: SeedIfExists;
@@ -81,7 +81,7 @@ export async function seedSystemTemplate(db, opts) {
81
81
  variableSchema,
82
82
  ...(linkedResources !== undefined && { linkedResources }),
83
83
  meta,
84
- title: null,
84
+ title: opts.title ?? null,
85
85
  folder: null,
86
86
  scope: "system",
87
87
  parentTemplateId,
@@ -171,6 +171,7 @@ export async function seedSystemTemplateBundle(db, bundle, opts = {}) {
171
171
  contentFormat: bundle.contentFormat,
172
172
  variableSchema: bundle.variableSchema ?? {},
173
173
  meta: entry.meta,
174
+ ...(entry.title !== undefined ? { title: entry.title } : {}),
174
175
  ...(opts.by !== undefined ? { by: opts.by } : {}),
175
176
  ...(opts.ifExists !== undefined ? { ifExists: opts.ifExists } : {}),
176
177
  });
@@ -14,7 +14,7 @@ export declare const templateResourceEntity: import("@cosmicdrift/kumiko-types/f
14
14
  readonly sensitive?: boolean;
15
15
  readonly writeOnly?: boolean;
16
16
  readonly readAsInstruction?: boolean;
17
- readonly format?: "email" | "url" | "phone" | "password";
17
+ readonly format?: "email" | "url" | "phone" | "password" | "month";
18
18
  readonly default?: string;
19
19
  readonly access?: import("@cosmicdrift/kumiko-types/fields").FieldAccess;
20
20
  readonly multiline?: boolean | {
@@ -66,7 +66,7 @@ export declare const templateResourceEntity: import("@cosmicdrift/kumiko-types/f
66
66
  readonly sensitive?: boolean;
67
67
  readonly writeOnly?: boolean;
68
68
  readonly readAsInstruction?: boolean;
69
- readonly format?: "email" | "url" | "phone" | "password";
69
+ readonly format?: "email" | "url" | "phone" | "password" | "month";
70
70
  readonly default?: string;
71
71
  readonly access?: import("@cosmicdrift/kumiko-types/fields").FieldAccess;
72
72
  readonly multiline?: boolean | {
@@ -89,7 +89,7 @@ export declare const templateResourceEntity: import("@cosmicdrift/kumiko-types/f
89
89
  readonly sensitive?: boolean;
90
90
  readonly writeOnly?: boolean;
91
91
  readonly readAsInstruction?: boolean;
92
- readonly format?: "email" | "url" | "phone" | "password";
92
+ readonly format?: "email" | "url" | "phone" | "password" | "month";
93
93
  readonly default?: string;
94
94
  readonly access?: import("@cosmicdrift/kumiko-types/fields").FieldAccess;
95
95
  readonly multiline?: boolean | {
@@ -112,7 +112,7 @@ export declare const templateResourceEntity: import("@cosmicdrift/kumiko-types/f
112
112
  readonly sensitive?: boolean;
113
113
  readonly writeOnly?: boolean;
114
114
  readonly readAsInstruction?: boolean;
115
- readonly format?: "email" | "url" | "phone" | "password";
115
+ readonly format?: "email" | "url" | "phone" | "password" | "month";
116
116
  readonly default?: string;
117
117
  readonly access?: import("@cosmicdrift/kumiko-types/fields").FieldAccess;
118
118
  readonly multiline?: boolean | {
@@ -245,7 +245,7 @@ export declare const templateResourceEntity: import("@cosmicdrift/kumiko-types/f
245
245
  readonly sensitive?: boolean;
246
246
  readonly writeOnly?: boolean;
247
247
  readonly readAsInstruction?: boolean;
248
- readonly format?: "email" | "url" | "phone" | "password";
248
+ readonly format?: "email" | "url" | "phone" | "password" | "month";
249
249
  readonly default?: string;
250
250
  readonly access?: import("@cosmicdrift/kumiko-types/fields").FieldAccess;
251
251
  readonly multiline?: boolean | {
@@ -343,7 +343,7 @@ export declare const templateResourcesTable: import("@cosmicdrift/kumiko-framewo
343
343
  readonly sensitive?: boolean;
344
344
  readonly writeOnly?: boolean;
345
345
  readonly readAsInstruction?: boolean;
346
- readonly format?: "email" | "url" | "phone" | "password";
346
+ readonly format?: "email" | "url" | "phone" | "password" | "month";
347
347
  readonly default?: string;
348
348
  readonly access?: import("@cosmicdrift/kumiko-types/fields").FieldAccess;
349
349
  readonly multiline?: boolean | {
@@ -395,7 +395,7 @@ export declare const templateResourcesTable: import("@cosmicdrift/kumiko-framewo
395
395
  readonly sensitive?: boolean;
396
396
  readonly writeOnly?: boolean;
397
397
  readonly readAsInstruction?: boolean;
398
- readonly format?: "email" | "url" | "phone" | "password";
398
+ readonly format?: "email" | "url" | "phone" | "password" | "month";
399
399
  readonly default?: string;
400
400
  readonly access?: import("@cosmicdrift/kumiko-types/fields").FieldAccess;
401
401
  readonly multiline?: boolean | {
@@ -418,7 +418,7 @@ export declare const templateResourcesTable: import("@cosmicdrift/kumiko-framewo
418
418
  readonly sensitive?: boolean;
419
419
  readonly writeOnly?: boolean;
420
420
  readonly readAsInstruction?: boolean;
421
- readonly format?: "email" | "url" | "phone" | "password";
421
+ readonly format?: "email" | "url" | "phone" | "password" | "month";
422
422
  readonly default?: string;
423
423
  readonly access?: import("@cosmicdrift/kumiko-types/fields").FieldAccess;
424
424
  readonly multiline?: boolean | {
@@ -441,7 +441,7 @@ export declare const templateResourcesTable: import("@cosmicdrift/kumiko-framewo
441
441
  readonly sensitive?: boolean;
442
442
  readonly writeOnly?: boolean;
443
443
  readonly readAsInstruction?: boolean;
444
- readonly format?: "email" | "url" | "phone" | "password";
444
+ readonly format?: "email" | "url" | "phone" | "password" | "month";
445
445
  readonly default?: string;
446
446
  readonly access?: import("@cosmicdrift/kumiko-types/fields").FieldAccess;
447
447
  readonly multiline?: boolean | {
@@ -574,7 +574,7 @@ export declare const templateResourcesTable: import("@cosmicdrift/kumiko-framewo
574
574
  readonly sensitive?: boolean;
575
575
  readonly writeOnly?: boolean;
576
576
  readonly readAsInstruction?: boolean;
577
- readonly format?: "email" | "url" | "phone" | "password";
577
+ readonly format?: "email" | "url" | "phone" | "password" | "month";
578
578
  readonly default?: string;
579
579
  readonly access?: import("@cosmicdrift/kumiko-types/fields").FieldAccess;
580
580
  readonly multiline?: boolean | {
@@ -19,6 +19,7 @@ export declare const templateBundleSchema: z.ZodObject<{
19
19
  locales: z.ZodRecord<z.ZodString, z.ZodObject<{
20
20
  body: z.ZodString;
21
21
  meta: z.ZodOptional<z.ZodUnknown>;
22
+ title: z.ZodOptional<z.ZodString>;
22
23
  }, z.core.$strict>>;
23
24
  resources: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodObject<{
24
25
  fileName: z.ZodString;
@@ -33,7 +33,12 @@ const bundleResourceSchema = z
33
33
  .strict();
34
34
  const pdfAttachmentLinkBodySchema = templatePdfAttachmentLinkSchema.omit({ kind: true });
35
35
  const bundleLocaleEntrySchema = z
36
- .object({ body: z.string().max(200_000), meta: z.unknown().optional() })
36
+ .object({
37
+ body: z.string().max(200_000),
38
+ meta: z.unknown().optional(),
39
+ // Shown in the template overview instead of the slug; one per locale, so it is translated.
40
+ title: z.string().min(1).max(200).optional(),
41
+ })
37
42
  .strict();
38
43
  export const templateBundleSchema = z
39
44
  .object({
@@ -1,4 +1,4 @@
1
- import type { HandlerContext, SessionUser, TemplateVariables } from "@cosmicdrift/kumiko-framework/engine";
1
+ import { type HandlerContext, type SessionUser, type TemplateVariables } from "@cosmicdrift/kumiko-framework/engine";
2
2
  import { type KumikoError } from "@cosmicdrift/kumiko-framework/errors";
3
3
  import { type MailAttachmentRef } from "../renderer-foundation/index.ts";
4
4
  import { type TemplateScope } from "./constants.ts";
@@ -41,6 +41,8 @@ export type TemplatePreviewInput = {
41
41
  readonly content?: string | undefined;
42
42
  readonly meta?: unknown;
43
43
  readonly variableSchema: Readonly<Record<string, unknown>>;
44
+ /** Replaces the declared example values for the named variables. */
45
+ readonly variables?: Readonly<Record<string, unknown>> | undefined;
44
46
  };
45
47
  type ValidationResponse = Awaited<ReturnType<typeof validateTemplateDraft>>["response"];
46
48
  export type TemplatePreview = {
@@ -59,6 +61,7 @@ export type TemplatePreview = {
59
61
  readonly scope: TemplateScope;
60
62
  readonly version: number;
61
63
  } | null;
64
+ /** The values the preview was actually rendered with: examples plus any overrides. */
62
65
  readonly sampleValues: Readonly<Record<string, unknown>>;
63
66
  readonly checks: readonly PreviewCheck[];
64
67
  readonly validation: ValidationResponse;
@@ -1,8 +1,10 @@
1
1
  import { selectMany } from "@cosmicdrift/kumiko-framework/db";
2
- import { NotFoundError, UnprocessableError, ValidationError, } from "@cosmicdrift/kumiko-framework/errors";
2
+ import { templateVariablesToZod, } from "@cosmicdrift/kumiko-framework/engine";
3
+ import { NotFoundError, UnprocessableError, ValidationError, validationErrorFromZod, } from "@cosmicdrift/kumiko-framework/errors";
3
4
  import { fileRefsTable } from "@cosmicdrift/kumiko-framework/files";
4
5
  import { createI18n } from "@cosmicdrift/kumiko-framework/i18n";
5
- import { escapeHtml } from "@cosmicdrift/kumiko-headless";
6
+ import { escapeHtml } from "@cosmicdrift/kumiko-renderer/headless";
7
+ import * as z from "zod";
6
8
  import { createEmailAttachmentReader, createTemplateMailRenderer } from "../delivery/index.js";
7
9
  import { fileContextForTenant } from "../file-foundation/index.js";
8
10
  import { collectRendererPlugins, createRendererFoundationApi, RendererError, } from "../renderer-foundation/index.js";
@@ -413,7 +415,18 @@ export async function renderTemplatePreview(ctx, user, input) {
413
415
  return failed(new ValidationError({ fields: [...evaluation.issues] }));
414
416
  const content = evaluation.content ?? "";
415
417
  const meta = parseTemplateMeta(input.kind, parseStoredJson(evaluation.metaJson));
416
- const sampleValues = sampleValuesFor(validation.variables, labelTextFor(ctx, input.locale));
418
+ const exampleValues = sampleValuesFor(validation.variables, labelTextFor(ctx, input.locale));
419
+ let sampleValues = exampleValues;
420
+ if (input.variables !== undefined) {
421
+ const overrides = z.object({
422
+ variables: templateVariablesToZod(validation.variables).partial().strict(),
423
+ });
424
+ const checked = overrides.safeParse({ variables: input.variables });
425
+ if (!checked.success)
426
+ return failed(validationErrorFromZod(checked.error));
427
+ // Original values, not the parse result: parsing would strip keys inside list rows.
428
+ sampleValues = { ...exampleValues, ...input.variables };
429
+ }
417
430
  if (input.kind === "document-pdf") {
418
431
  return renderDocumentPreview(ctx, user, {
419
432
  input,
@@ -1,4 +1,4 @@
1
- import type { Registry, TemplateVariables } from "@cosmicdrift/kumiko-framework/engine";
1
+ import type { Registry, TemplateVariableDef, TemplateVariables } from "@cosmicdrift/kumiko-framework/engine";
2
2
  import type { TemplateMeta } from "./template-meta.ts";
3
3
  export declare const SYSTEM_PLACEHOLDERS: ReadonlySet<string>;
4
4
  export declare function systemPlaceholdersFor(kind: string): ReadonlySet<string>;
@@ -9,6 +9,7 @@ export type TemplateValidation = {
9
9
  /** data-resource keys with no uploaded file; a warning, never a blocker. */
10
10
  readonly unknownResources: readonly string[];
11
11
  };
12
+ export declare function toVariableDef(raw: unknown): TemplateVariableDef | null;
12
13
  export declare function resolveTemplateVariables(registry: Registry, slug: string, variableSchema: Readonly<Record<string, unknown>>): TemplateVariables;
13
14
  export declare function variablesFromSchema(variableSchema: Readonly<Record<string, unknown>>): TemplateVariables;
14
15
  export declare function validateTemplatePlaceholders(input: {
@@ -48,7 +48,7 @@ const variableDefSchema = z.union([
48
48
  // A stored variableSchema value is either a bare string (legacy "this is a text
49
49
  // variable") or a TemplateVariableDef object; anything else is not declared and
50
50
  // therefore shows up as an unknown placeholder instead of being guessed at.
51
- function toVariableDef(raw) {
51
+ export function toVariableDef(raw) {
52
52
  if (typeof raw === "string")
53
53
  return { type: "text" };
54
54
  const parsed = variableDefSchema.safeParse(raw);