@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.
- package/dist/account-security/constants.d.ts +1 -0
- package/dist/account-security/constants.js +1 -0
- package/dist/account-security/index.d.ts +1 -1
- package/dist/account-security/index.js +1 -1
- package/dist/admin-shell/web/client-plugin.d.ts +1 -1
- package/dist/audit/web/client-plugin.d.ts +1 -1
- package/dist/auth-email-password/mail-notifications.js +1 -1
- package/dist/auth-email-password/web/auth-client.js +1 -1
- package/dist/auth-email-password/web/auth-form-primitives.js +1 -1
- package/dist/auth-email-password/web/default-topbar-actions.js +1 -1
- package/dist/auth-email-password/web/index.js +1 -1
- package/dist/auth-email-password/web/session.js +1 -1
- package/dist/auth-email-password/web/tenant-menu-items.js +1 -1
- package/dist/auth-email-password/web/tenant-switcher.js +1 -1
- package/dist/auth-email-password/web/user-menu.js +1 -1
- package/dist/auth-foundation/env.d.ts +9 -0
- package/dist/auth-foundation/env.js +21 -0
- package/dist/auth-foundation/index.d.ts +1 -0
- package/dist/auth-foundation/index.js +1 -0
- package/dist/auth-mfa/schema/user-mfa.d.ts +6 -6
- package/dist/billing-foundation/consumer-protection/confirmation-mail.js +1 -1
- package/dist/billing-foundation/consumer-protection/termination-pages.js +1 -1
- package/dist/billing-foundation/contact-card.d.ts +2 -0
- package/dist/billing-foundation/contact-card.js +16 -0
- package/dist/billing-foundation/entities.d.ts +8 -8
- package/dist/billing-foundation/handlers/billing-plans.query.js +1 -1
- package/dist/billing-foundation/index.d.ts +1 -1
- package/dist/billing-foundation/plan-catalog.js +1 -0
- package/dist/billing-foundation/projection.d.ts +8 -8
- package/dist/billing-foundation/types.d.ts +11 -0
- package/dist/billing-foundation/validate-options.js +13 -0
- package/dist/billing-foundation/web/billing-contact-plan.d.ts +5 -0
- package/dist/billing-foundation/web/billing-contact-plan.js +17 -0
- package/dist/billing-foundation/web/billing-plans-panel.js +5 -13
- package/dist/billing-foundation/web/cancel-contract-dialog.js +2 -2
- package/dist/billing-foundation/web/checkout-consent-dialog.d.ts +1 -1
- package/dist/billing-foundation/web/checkout-consent-dialog.js +1 -0
- package/dist/billing-foundation/web/client-plugin.d.ts +1 -1
- package/dist/billing-foundation/web/localized-params.d.ts +1 -0
- package/dist/billing-foundation/web/localized-params.js +11 -0
- package/dist/cap-counter/entity.d.ts +1 -1
- package/dist/cap-overview/web/cap-cards-panel.js +3 -3
- package/dist/cap-overview/web/client-plugin.d.ts +1 -1
- package/dist/channel-email/email-channel.d.ts +2 -1
- package/dist/compliance-profiles/web/client-plugin.d.ts +1 -1
- package/dist/config/feature.js +2 -2
- package/dist/config/table.d.ts +6 -6
- package/dist/custom-fields/entity.d.ts +4 -4
- package/dist/custom-fields/web/client-plugin.d.ts +1 -1
- package/dist/data-retention/schema/tenant-retention-override.d.ts +4 -4
- package/dist/delivery/tables.d.ts +6 -6
- package/dist/delivery/unsubscribe-page.js +1 -1
- package/dist/delivery/web/client-plugin.d.ts +1 -1
- package/dist/delivery/web/delivery-status-cell.js +1 -1
- package/dist/document-ingest-foundation/entity.d.ts +4 -4
- package/dist/feature-toggles/web/client-plugin.d.ts +1 -1
- package/dist/file-provider-s3/env-named-feature.d.ts +17 -0
- package/dist/file-provider-s3/env-named-feature.js +23 -0
- package/dist/file-provider-s3/env-schema.d.ts +7 -0
- package/dist/file-provider-s3/env-schema.js +15 -10
- package/dist/file-provider-s3/index.d.ts +1 -0
- package/dist/file-provider-s3/index.js +1 -0
- package/dist/folders/entity.d.ts +8 -8
- package/dist/folders/web/client-plugin.d.ts +1 -1
- package/dist/form-draft/entity.d.ts +2 -2
- package/dist/host-routing/constants.d.ts +2 -0
- package/dist/host-routing/constants.js +3 -0
- package/dist/host-routing/dispatch.d.ts +1 -1
- package/dist/host-routing/dispatch.js +35 -25
- package/dist/host-routing/feature.js +100 -27
- package/dist/host-routing/host.d.ts +9 -0
- package/dist/host-routing/host.js +24 -2
- package/dist/host-routing/index.d.ts +2 -1
- package/dist/host-routing/index.js +1 -0
- package/dist/host-routing/options.d.ts +14 -0
- package/dist/host-routing/redirects.d.ts +6 -0
- package/dist/host-routing/redirects.js +27 -0
- package/dist/host-routing/tenant-lookup.d.ts +7 -0
- package/dist/host-routing/tenant-lookup.js +39 -0
- package/dist/host-routing/validate-options.js +34 -1
- package/dist/inbound-mail-foundation/entities.d.ts +25 -25
- package/dist/inbound-mail-foundation/feature.js +7 -0
- package/dist/inbound-mail-foundation/i18n.d.ts +3 -0
- package/dist/inbound-mail-foundation/i18n.js +35 -0
- package/dist/inbound-mail-foundation/index.d.ts +1 -0
- package/dist/inbound-mail-foundation/index.js +1 -0
- package/dist/inbound-mail-foundation/projection.d.ts +20 -20
- package/dist/inbound-mail-foundation/screens.d.ts +7 -0
- package/dist/inbound-mail-foundation/screens.js +86 -0
- package/dist/inbound-mail-foundation/types.d.ts +2 -2
- package/dist/jobs/job-run-table.d.ts +6 -6
- package/dist/jobs/web/client-plugin.d.ts +1 -1
- package/dist/ledger/entity.d.ts +13 -13
- package/dist/legal-pages/feature.js +11 -1
- package/dist/legal-pages/layout.d.ts +2 -2
- package/dist/legal-pages/layout.js +4 -4
- package/dist/legal-pages/web/client-plugin.d.ts +1 -1
- package/dist/mail-foundation/feature.d.ts +20 -21
- package/dist/mail-foundation/feature.js +1 -1
- package/dist/managed-pages/table.d.ts +12 -12
- package/dist/managed-pages/web/client-plugin.d.ts +1 -1
- package/dist/notes-history/entity.d.ts +12 -12
- package/dist/notes-history/web/client-plugin.d.ts +1 -1
- package/dist/page-render/branding.js +1 -1
- package/dist/page-render/layout.d.ts +1 -1
- package/dist/page-render/layout.js +2 -2
- package/dist/page-render/markdown.js +1 -1
- package/dist/personal-access-tokens/i18n.js +1 -1
- package/dist/personal-access-tokens/schema/api-token.d.ts +6 -6
- package/dist/personal-access-tokens/screens.js +1 -1
- package/dist/secrets/feature.d.ts +1 -1
- package/dist/secrets/feature.js +3 -3
- package/dist/secrets/table.d.ts +1 -1
- package/dist/seo/sitemap.js +1 -1
- package/dist/sessions/schema/user-session.d.ts +4 -4
- package/dist/shared/mail-template-body.js +1 -1
- package/dist/shared/purge-files-reaching-subject.js +98 -3
- package/dist/tags/entity.d.ts +9 -9
- package/dist/tags/web/client-plugin.d.ts +1 -1
- package/dist/template-resolver/document-blocks.d.ts +5 -0
- package/dist/template-resolver/document-blocks.js +21 -0
- package/dist/template-resolver/document-ops.d.ts +167 -0
- package/dist/template-resolver/document-ops.js +390 -0
- package/dist/template-resolver/document-sanitizer.d.ts +3 -0
- package/dist/template-resolver/document-sanitizer.js +6 -0
- package/dist/template-resolver/feature.d.ts +2 -0
- package/dist/template-resolver/feature.js +4 -0
- package/dist/template-resolver/handlers/bundle-import.js +1 -0
- package/dist/template-resolver/handlers/document-base.d.ts +23 -0
- package/dist/template-resolver/handlers/document-base.js +55 -0
- package/dist/template-resolver/handlers/document-blocks.query.d.ts +22 -0
- package/dist/template-resolver/handlers/document-blocks.query.js +46 -0
- package/dist/template-resolver/handlers/export-bundle.query.d.ts +1 -0
- package/dist/template-resolver/handlers/export-bundle.query.js +9 -2
- package/dist/template-resolver/handlers/import-bundle-system.write.d.ts +1 -0
- package/dist/template-resolver/handlers/import-bundle.write.d.ts +1 -0
- package/dist/template-resolver/handlers/patch-draft.write.d.ts +66 -0
- package/dist/template-resolver/handlers/patch-draft.write.js +105 -0
- package/dist/template-resolver/handlers/preview.query.d.ts +1 -0
- package/dist/template-resolver/handlers/preview.query.js +2 -1
- package/dist/template-resolver/handlers/send-test.write.d.ts +1 -0
- package/dist/template-resolver/handlers/send-test.write.js +2 -1
- package/dist/template-resolver/handlers/template-write.d.ts +2 -0
- package/dist/template-resolver/handlers/template-write.js +14 -2
- package/dist/template-resolver/handlers/validate.query.d.ts +10 -4
- package/dist/template-resolver/handlers/validate.query.js +12 -7
- package/dist/template-resolver/{web/placeholder-text.js → placeholder-text.js} +1 -1
- package/dist/template-resolver/qualified-names.d.ts +2 -0
- package/dist/template-resolver/qualified-names.js +2 -0
- package/dist/template-resolver/seeding.d.ts +1 -0
- package/dist/template-resolver/seeding.js +2 -1
- package/dist/template-resolver/table.d.ts +10 -10
- package/dist/template-resolver/template-bundle.d.ts +1 -0
- package/dist/template-resolver/template-bundle.js +6 -1
- package/dist/template-resolver/template-preview.d.ts +4 -1
- package/dist/template-resolver/template-preview.js +16 -3
- package/dist/template-resolver/template-validation.d.ts +2 -1
- package/dist/template-resolver/template-validation.js +1 -1
- package/dist/template-resolver/user-content-table.d.ts +10 -10
- package/dist/template-resolver/web/client-plugin.d.ts +1 -1
- package/dist/template-resolver/web/client-plugin.js +3 -3
- package/dist/template-resolver/web/document-tiptap-editor.js +1 -1
- package/dist/template-resolver/web/document-toolbar-blocks.d.ts +2 -5
- package/dist/template-resolver/web/document-toolbar-blocks.js +1 -21
- package/dist/template-resolver/web/i18n.js +14 -0
- package/dist/template-resolver/web/placeholder-line-editor.js +2 -2
- package/dist/tenant/invitation-table.d.ts +6 -6
- package/dist/tenant/membership-table.d.ts +4 -4
- package/dist/tenant/schema/tenant.d.ts +6 -6
- package/dist/tenant/web/client-plugin.d.ts +1 -1
- package/dist/tenant/web/member-status-cell.js +1 -1
- package/dist/tier-engine/entity.d.ts +1 -1
- package/dist/tier-engine/tier-resolver.d.ts +1 -1
- package/dist/tier-engine/web/client-plugin.d.ts +1 -1
- package/dist/user/schema/user.d.ts +12 -12
- package/dist/user-data-rights/env.d.ts +5 -0
- package/dist/user-data-rights/env.js +11 -0
- package/dist/user-data-rights/feature.js +4 -2
- package/dist/user-data-rights/index.d.ts +1 -0
- package/dist/user-data-rights/index.js +1 -0
- package/dist/user-data-rights/lib/mail-transport-resolver.js +1 -1
- package/dist/user-data-rights/lib/storage-provider-resolver.js +1 -1
- package/dist/user-data-rights/mail-notifications.js +1 -1
- package/dist/user-data-rights/schema/download-attempt.d.ts +14 -14
- package/dist/user-data-rights/schema/download-token.d.ts +8 -8
- package/dist/user-data-rights/schema/export-job.d.ts +6 -6
- package/dist/user-data-rights/web/client-plugin.d.ts +1 -1
- package/dist/user-data-rights/web/privacy-center-screen.js +2 -2
- package/dist/waitlist/entity.d.ts +14 -14
- package/dist/waitlist/legacy-migrate.d.ts +2 -0
- package/dist/waitlist/legacy-migrate.js +38 -3
- package/package.json +16 -11
- package/src/account-security/changes.json +6 -0
- package/src/auth-foundation/changes.json +6 -0
- package/src/billing-foundation/changes.json +6 -0
- package/src/channel-email/changes.json +6 -0
- package/src/crypto-shredding/changes.json +6 -0
- package/src/file-provider-s3/changes.json +6 -0
- package/src/host-routing/changes.json +6 -0
- package/src/inbound-mail-foundation/changes.json +7 -0
- package/src/legal-pages/changes.json +6 -0
- package/src/template-resolver/changes.json +18 -0
- package/src/user-data-rights/changes.json +6 -0
- package/src/waitlist/changes.json +6 -0
- /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-
|
|
20
|
-
publishIssues: import("@cosmicdrift/kumiko-
|
|
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-
|
|
62
|
-
publishIssues: import("@cosmicdrift/kumiko-
|
|
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
|
-
//
|
|
22
|
-
export async function
|
|
23
|
-
const
|
|
24
|
-
|
|
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 "
|
|
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({
|
|
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
|
|
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 {
|
|
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
|
|
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);
|