@cosmicdrift/kumiko-bundled-features 0.363.0 → 0.364.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/audit/handlers/list.query.js +9 -5
- package/dist/auth-email-password/bootstrap.d.ts +8 -5
- package/dist/auth-email-password/bootstrap.js +12 -4
- package/dist/auth-email-password/email-rate-limits.d.ts +4 -0
- package/dist/auth-email-password/email-rate-limits.js +13 -0
- package/dist/auth-email-password/handlers/change-password.write.d.ts +1 -1
- package/dist/auth-email-password/handlers/change-password.write.js +6 -2
- package/dist/auth-email-password/handlers/confirm-account-unlock.write.d.ts +1 -1
- package/dist/auth-email-password/handlers/invite-accept-with-login.write.d.ts +1 -1
- package/dist/auth-email-password/handlers/invite-accept.write.d.ts +1 -1
- package/dist/auth-email-password/handlers/invite-create.write.d.ts +2 -2
- package/dist/auth-email-password/handlers/invite-signup-complete.write.d.ts +1 -1
- package/dist/auth-email-password/handlers/login.write.d.ts +1 -1
- package/dist/auth-email-password/handlers/logout.write.d.ts +1 -1
- package/dist/auth-email-password/handlers/logout.write.js +6 -2
- package/dist/auth-email-password/handlers/request-account-unlock.write.d.ts +1 -1
- package/dist/auth-email-password/handlers/request-email-verification.write.d.ts +1 -1
- package/dist/auth-email-password/handlers/request-password-reset.write.d.ts +1 -1
- package/dist/auth-email-password/handlers/reset-password.write.d.ts +1 -1
- package/dist/auth-email-password/handlers/signup-confirm.write.d.ts +1 -1
- package/dist/auth-email-password/handlers/signup-request.write.d.ts +1 -1
- package/dist/auth-email-password/handlers/signup-request.write.js +2 -1
- package/dist/auth-email-password/handlers/switch-tenant-mfa-gate.write.d.ts +1 -1
- package/dist/auth-email-password/handlers/token-request-handler.d.ts +1 -1
- package/dist/auth-email-password/handlers/token-request-handler.js +2 -1
- package/dist/auth-email-password/handlers/verify-email.write.d.ts +1 -1
- package/dist/auth-email-password/web/auth-gate.js +16 -4
- package/dist/auth-mfa/handlers/disable.write.d.ts +1 -1
- package/dist/auth-mfa/handlers/enable-confirm-preauth.write.d.ts +1 -1
- package/dist/auth-mfa/handlers/enable-confirm.write.d.ts +1 -1
- package/dist/auth-mfa/handlers/enable-start-preauth.write.d.ts +1 -1
- package/dist/auth-mfa/handlers/enable-start.write.d.ts +1 -1
- package/dist/auth-mfa/handlers/regenerate-recovery.write.d.ts +1 -1
- package/dist/auth-mfa/handlers/verify.write.d.ts +1 -1
- package/dist/billing-foundation/consumer-protection/confirmation-mail.js +52 -5
- package/dist/billing-foundation/consumer-protection/consent-gate.d.ts +3 -0
- package/dist/billing-foundation/consumer-protection/consent-gate.js +21 -2
- package/dist/billing-foundation/consumer-protection/mail-notifications.js +1 -1
- package/dist/billing-foundation/consumer-protection/register-termination.js +11 -1
- package/dist/billing-foundation/consumer-protection/termination-mail.d.ts +5 -3
- package/dist/billing-foundation/consumer-protection/termination-mail.js +1 -2
- package/dist/billing-foundation/consumer-protection/termination-receipt-limit.d.ts +6 -0
- package/dist/billing-foundation/consumer-protection/termination-receipt-limit.js +34 -0
- package/dist/billing-foundation/consumer-protection/termination-record.d.ts +2 -0
- package/dist/billing-foundation/consumer-protection/termination-record.js +7 -1
- package/dist/billing-foundation/consumer-protection/termination-request.js +30 -20
- package/dist/billing-foundation/consumer-protection/termination-texts.d.ts +1 -1
- package/dist/billing-foundation/consumer-protection/termination-texts.js +6 -0
- package/dist/billing-foundation/consumer-protection/withdrawal-period.d.ts +10 -0
- package/dist/billing-foundation/consumer-protection/withdrawal-period.js +26 -0
- package/dist/billing-foundation/handlers/create-checkout-session.write.js +11 -5
- package/dist/billing-foundation/handlers/process-event.write.js +10 -6
- package/dist/billing-foundation/i18n.js +3 -0
- package/dist/billing-foundation/subscription-tier-sync.d.ts +7 -1
- package/dist/billing-foundation/subscription-tier-sync.js +13 -5
- package/dist/billing-foundation/types.d.ts +7 -2
- package/dist/cap-counter/calendar-cap-usage-reservation.js +2 -0
- package/dist/cap-counter/enforce-cap.d.ts +18 -1
- package/dist/cap-counter/enforce-cap.js +49 -6
- package/dist/cap-counter/index.d.ts +1 -1
- package/dist/cap-counter/index.js +1 -1
- package/dist/cap-counter/with-cap-enforcement.d.ts +2 -0
- package/dist/channel-email/email-channel.js +16 -4
- package/dist/channel-in-app/handlers/mark-all-read.write.d.ts +1 -1
- package/dist/channel-in-app/handlers/mark-read.write.d.ts +1 -1
- package/dist/compliance-profiles/handlers/set-profile.write.d.ts +1 -1
- package/dist/config/handlers/reset.write.d.ts +1 -1
- package/dist/config/handlers/set.write.d.ts +1 -1
- package/dist/config/handlers/set.write.js +2 -2
- package/dist/config/resolver.d.ts +1 -0
- package/dist/config/resolver.js +11 -14
- package/dist/config/write-helpers.d.ts +2 -0
- package/dist/config/write-helpers.js +19 -0
- package/dist/crypto-shredding/handlers/forget-subject.write.d.ts +1 -1
- package/dist/crypto-shredding/handlers/forget-subject.write.js +29 -19
- package/dist/custom-fields/handlers/update-tenant-field.write.js +0 -1
- package/dist/delivery/attachment-ref.d.ts +2 -0
- package/dist/delivery/attachment-ref.js +14 -0
- package/dist/delivery/channel-context.js +19 -1
- package/dist/delivery/handlers/resubscribe-address.write.d.ts +1 -1
- package/dist/delivery/handlers/resubscribe-user.write.d.ts +1 -1
- package/dist/delivery/handlers/set-preference.write.d.ts +1 -1
- package/dist/delivery/handlers/unsubscribe-address.write.d.ts +1 -1
- package/dist/delivery/handlers/unsubscribe-user.write.d.ts +1 -1
- package/dist/delivery/index.d.ts +1 -1
- package/dist/delivery/index.js +1 -1
- package/dist/delivery/jobs.js +43 -11
- package/dist/delivery/types.d.ts +7 -1
- package/dist/feature-toggles/handlers/set.write.d.ts +1 -1
- package/dist/file-derivatives/handlers/public-variant.query.js +1 -1
- package/dist/file-foundation/read-file-ref-content.d.ts +1 -1
- package/dist/file-foundation/read-file-ref-content.js +6 -4
- package/dist/files-tenant-data/hooks.js +4 -0
- package/dist/form-draft/handlers/discard.write.d.ts +1 -1
- package/dist/form-draft/handlers/save.write.d.ts +1 -1
- package/dist/inbound-mail-foundation/oauth-access-token.js +8 -3
- package/dist/inbound-mail-foundation/watch-supervisor.js +3 -1
- package/dist/jobs/db/queries/stale-run-sweep.d.ts +1 -1
- package/dist/jobs/db/queries/stale-run-sweep.js +12 -5
- package/dist/jobs/feature.js +5 -3
- package/dist/jobs/handlers/list.query.js +3 -1
- package/dist/jobs/handlers/retry.write.d.ts +1 -1
- package/dist/jobs/handlers/stale-run-sweep.job.d.ts +1 -1
- package/dist/jobs/handlers/stale-run-sweep.job.js +2 -2
- package/dist/jobs/handlers/trigger.write.d.ts +1 -1
- package/dist/jobs/job-run-logger.js +32 -10
- package/dist/jobs/tenant-job-run-table.js +3 -2
- package/dist/ledger/entity.js +3 -1
- package/dist/managed-pages/handlers/set.write.d.ts +1 -1
- package/dist/personal-access-tokens/handlers/create.write.d.ts +1 -1
- package/dist/personal-access-tokens/handlers/revoke.write.d.ts +1 -1
- package/dist/renderer-foundation/types.d.ts +2 -0
- package/dist/renderer-pdf/render-table-pdf.d.ts +27 -33
- package/dist/renderer-pdf/render-table-pdf.js +95 -34
- package/dist/secrets/feature.js +4 -3
- package/dist/secrets/handlers/delete.write.d.ts +1 -1
- package/dist/secrets/handlers/set.write.d.ts +1 -1
- package/dist/secrets/secrets-context.js +15 -3
- package/dist/sessions/handlers/revoke-all-for-user.write.d.ts +1 -1
- package/dist/sessions/handlers/revoke-all-others.write.d.ts +1 -1
- package/dist/sessions/handlers/revoke.write.d.ts +1 -1
- package/dist/shared/chunked-entity-migration.d.ts +1 -0
- package/dist/shared/chunked-entity-migration.js +1 -1
- package/dist/shared/mail-template-body.d.ts +3 -0
- package/dist/shared/mail-template-body.js +10 -0
- package/dist/subscription-stripe/plugin-methods.js +18 -8
- package/dist/subscription-stripe/verify-webhook.d.ts +1 -1
- package/dist/template-resolver/api.js +3 -2
- package/dist/template-resolver/compile-document-body.d.ts +4 -0
- package/dist/template-resolver/compile-document-body.js +58 -16
- package/dist/template-resolver/constants.d.ts +2 -2
- package/dist/template-resolver/constants.js +3 -1
- package/dist/template-resolver/document-schema.d.ts +1 -1
- package/dist/template-resolver/document-schema.js +23 -3
- package/dist/template-resolver/feature.d.ts +2 -0
- package/dist/template-resolver/feature.js +4 -1
- package/dist/template-resolver/handlers/collection-set.write.d.ts +1 -1
- package/dist/template-resolver/handlers/copy-locale.write.d.ts +1 -1
- package/dist/template-resolver/handlers/copy-locale.write.js +29 -11
- package/dist/template-resolver/handlers/diff-to-system.query.js +4 -38
- package/dist/template-resolver/handlers/discard-draft.write.d.ts +13 -0
- package/dist/template-resolver/handlers/discard-draft.write.js +37 -0
- package/dist/template-resolver/handlers/export-bundle.query.js +51 -1
- package/dist/template-resolver/handlers/find-by-id.query.d.ts +1 -0
- package/dist/template-resolver/handlers/find-by-id.query.js +6 -3
- package/dist/template-resolver/handlers/import-bundle-system.write.d.ts +1 -1
- package/dist/template-resolver/handlers/import-bundle.write.d.ts +1 -1
- package/dist/template-resolver/handlers/layout-source.query.d.ts +5 -0
- package/dist/template-resolver/handlers/layout-source.query.js +46 -23
- package/dist/template-resolver/handlers/list.query.js +4 -1
- package/dist/template-resolver/handlers/override-diff.d.ts +20 -0
- package/dist/template-resolver/handlers/override-diff.js +41 -0
- package/dist/template-resolver/handlers/overview.query.d.ts +6 -0
- package/dist/template-resolver/handlers/overview.query.js +51 -11
- package/dist/template-resolver/handlers/reset-to-system.write.d.ts +1 -1
- package/dist/template-resolver/handlers/resource-link-pdf.write.d.ts +1 -1
- package/dist/template-resolver/handlers/resource-remove.write.d.ts +1 -1
- package/dist/template-resolver/handlers/resource-upload.write.d.ts +1 -1
- package/dist/template-resolver/handlers/resource-upload.write.js +1 -1
- package/dist/template-resolver/handlers/send-test.write.d.ts +3 -1
- package/dist/template-resolver/handlers/send-test.write.js +37 -10
- package/dist/template-resolver/handlers/set.write.d.ts +1 -1
- package/dist/template-resolver/handlers/template-gates.js +13 -2
- package/dist/template-resolver/handlers/template-write.js +36 -4
- package/dist/template-resolver/handlers/tenant-overrides.query.d.ts +32 -0
- package/dist/template-resolver/handlers/tenant-overrides.query.js +80 -16
- package/dist/template-resolver/handlers/toggle-status.write.d.ts +17 -4
- package/dist/template-resolver/handlers/toggle-status.write.js +35 -19
- package/dist/template-resolver/handlers/upsert-system.write.d.ts +1 -1
- package/dist/template-resolver/handlers/upsert-tenant.write.d.ts +1 -1
- package/dist/template-resolver/handlers/upsert-tenant.write.js +1 -1
- package/dist/template-resolver/handlers/usage.query.js +11 -2
- package/dist/template-resolver/index.d.ts +4 -3
- package/dist/template-resolver/index.js +4 -3
- package/dist/template-resolver/layout-appearance.d.ts +10 -0
- package/dist/template-resolver/layout-appearance.js +26 -0
- package/dist/template-resolver/pdf-constants.d.ts +7 -0
- package/dist/template-resolver/pdf-constants.js +8 -0
- package/dist/template-resolver/qualified-names.d.ts +4 -0
- package/dist/template-resolver/qualified-names.js +4 -0
- package/dist/template-resolver/seeding.js +21 -4
- package/dist/template-resolver/store-template-resource.js +2 -2
- package/dist/template-resolver/table.d.ts +103 -0
- package/dist/template-resolver/table.js +9 -2
- package/dist/template-resolver/template-draft.d.ts +51 -0
- package/dist/template-resolver/template-draft.js +72 -0
- package/dist/template-resolver/template-meta.d.ts +30 -5
- package/dist/template-resolver/template-meta.js +60 -16
- package/dist/template-resolver/template-preview.d.ts +19 -2
- package/dist/template-resolver/template-preview.js +98 -54
- package/dist/template-resolver/template-resources.d.ts +5 -0
- package/dist/template-resolver/template-resources.js +35 -4
- package/dist/template-resolver/template-usage.d.ts +5 -1
- package/dist/template-resolver/template-usage.js +14 -9
- package/dist/template-resolver/template-validation.js +1 -0
- package/dist/template-resolver/web/client-plugin.js +4 -2
- package/dist/template-resolver/web/document-editor-extensions.d.ts +4 -1
- package/dist/template-resolver/web/document-editor-extensions.js +50 -12
- package/dist/template-resolver/web/document-editor.d.ts +2 -1
- package/dist/template-resolver/web/document-editor.js +8 -2
- package/dist/template-resolver/web/document-tiptap-editor.d.ts +1 -1
- package/dist/template-resolver/web/document-tiptap-editor.js +144 -28
- package/dist/template-resolver/web/document-toolbar-blocks.d.ts +8 -0
- package/dist/template-resolver/web/document-toolbar-blocks.js +4 -0
- package/dist/template-resolver/web/i18n.js +19 -4
- package/dist/template-resolver/web/index.d.ts +1 -0
- package/dist/template-resolver/web/index.js +1 -0
- package/dist/template-resolver/web/placeholder-line-editor.d.ts +3 -1
- package/dist/template-resolver/web/placeholder-line-editor.js +6 -6
- package/dist/tenant/handlers/add-member.write.d.ts +1 -1
- package/dist/tenant/handlers/cancel-invitation.write.d.ts +1 -1
- package/dist/tenant/handlers/create.write.d.ts +1 -1
- package/dist/tenant/handlers/remove-member.write.d.ts +1 -1
- package/dist/tenant/handlers/toggle-enabled.write.d.ts +2 -2
- package/dist/tenant/handlers/update-member-roles.write.d.ts +1 -1
- package/dist/tenant/handlers/update.write.d.ts +1 -1
- package/dist/tenant-handover/handlers/claim.write.d.ts +1 -1
- package/dist/tenant-lifecycle/handlers/cancel-destruction.write.d.ts +1 -1
- package/dist/tenant-lifecycle/handlers/request-destruction.write.d.ts +1 -1
- package/dist/tier-engine/feature.js +9 -3
- package/dist/tier-engine/handlers/set-tenant-tier.write.d.ts +1 -1
- package/dist/user/handlers/create.write.d.ts +1 -1
- package/dist/user/handlers/update.write.d.ts +1 -1
- package/dist/user/resolve-display-names.js +5 -9
- package/dist/user/seeding.js +20 -1
- package/dist/user-data-rights/handlers/cancel-deletion.write.d.ts +1 -1
- package/dist/user-data-rights/handlers/confirm-deletion-by-token.write.d.ts +1 -1
- package/dist/user-data-rights/handlers/lift-restriction.write.d.ts +1 -1
- package/dist/user-data-rights/handlers/request-deletion-by-email.write.d.ts +1 -1
- package/dist/user-data-rights/handlers/request-deletion.write.d.ts +1 -1
- package/dist/user-data-rights/handlers/request-export.write.d.ts +1 -1
- package/dist/user-data-rights/handlers/restrict-account.write.d.ts +1 -1
- package/dist/user-data-rights/handlers/run-forget-cleanup.write.d.ts +1 -1
- package/dist/user-profile/handlers/change-email.write.d.ts +1 -1
- package/dist/waitlist/handlers/invite.write.d.ts +1 -1
- package/dist/waitlist/handlers/reject.write.d.ts +1 -1
- package/dist/waitlist/handlers/submit.write.d.ts +1 -1
- package/dist/waitlist/options.js +1 -1
- package/dist/workflow-runner/event-trigger.js +2 -2
- package/dist/workflow-runner/handlers/resume-run.write.js +7 -2
- package/package.json +19 -10
- package/src/audit/changes.json +6 -0
- package/src/auth-email-password/changes.json +37 -0
- package/src/billing-foundation/changes.json +36 -0
- package/src/cap-counter/changes.json +6 -0
- package/src/config/changes.json +6 -0
- package/src/crypto-shredding/changes.json +6 -0
- package/src/custom-fields/changes.json +6 -0
- package/src/delivery/changes.json +18 -0
- package/src/file-foundation/changes.json +6 -0
- package/src/files-tenant-data/changes.json +6 -0
- package/src/inbound-mail-foundation/changes.json +6 -0
- package/src/jobs/changes.json +19 -0
- package/src/ledger/changes.json +7 -0
- package/src/renderer-pdf/changes.json +12 -0
- package/src/secrets/changes.json +6 -0
- package/src/shared/changes.json +6 -0
- package/src/subscription-stripe/changes.json +6 -0
- package/src/template-resolver/changes.json +68 -0
- package/src/tier-engine/changes.json +6 -0
- package/src/user/changes.json +6 -0
- package/src/user-data-rights/changes.json +6 -0
- package/src/user-profile/changes.json +1 -1
- package/src/workflow-runner/changes.json +6 -0
|
@@ -1,27 +1,40 @@
|
|
|
1
|
+
import { type HandlerContext } from "@cosmicdrift/kumiko-framework/engine";
|
|
1
2
|
import * as z from "zod";
|
|
3
|
+
import type { TemplateResourceRow } from "../table.js";
|
|
2
4
|
type TemplateStatus = "active" | "archived";
|
|
5
|
+
export type StatusTarget = "own" | "system";
|
|
6
|
+
export declare function writeAccessFor(target: StatusTarget): {
|
|
7
|
+
roles: string[];
|
|
8
|
+
};
|
|
9
|
+
export declare function loadTargetRow(ctx: HandlerContext, target: StatusTarget, user: {
|
|
10
|
+
readonly tenantId: string;
|
|
11
|
+
}, id: string): Promise<{
|
|
12
|
+
db: import("@cosmicdrift/kumiko-types/tenant-db-types").TenantDb;
|
|
13
|
+
scoped: import("@cosmicdrift/kumiko-types/handlers").TenantWriteTarget | null;
|
|
14
|
+
row: TemplateResourceRow | null;
|
|
15
|
+
}>;
|
|
3
16
|
export declare const archiveWrite: import("@cosmicdrift/kumiko-types/define-handler").WriteHandlerDefinition<string, z.ZodObject<{
|
|
4
17
|
id: z.ZodString;
|
|
5
18
|
}, z.core.$strip>, {
|
|
6
19
|
id: string;
|
|
7
20
|
status: TemplateStatus;
|
|
8
|
-
}, import("@cosmicdrift/kumiko-types/event-type-map").KumikoEventTypeMap>;
|
|
21
|
+
}, import("@cosmicdrift/kumiko-types/event-type-map").KumikoEventTypeMap, undefined>;
|
|
9
22
|
export declare const publishWrite: import("@cosmicdrift/kumiko-types/define-handler").WriteHandlerDefinition<string, z.ZodObject<{
|
|
10
23
|
id: z.ZodString;
|
|
11
24
|
}, z.core.$strip>, {
|
|
12
25
|
id: string;
|
|
13
26
|
status: TemplateStatus;
|
|
14
|
-
}, import("@cosmicdrift/kumiko-types/event-type-map").KumikoEventTypeMap>;
|
|
27
|
+
}, import("@cosmicdrift/kumiko-types/event-type-map").KumikoEventTypeMap, undefined>;
|
|
15
28
|
export declare const archiveSystemWrite: import("@cosmicdrift/kumiko-types/define-handler").WriteHandlerDefinition<string, z.ZodObject<{
|
|
16
29
|
id: z.ZodString;
|
|
17
30
|
}, z.core.$strip>, {
|
|
18
31
|
id: string;
|
|
19
32
|
status: TemplateStatus;
|
|
20
|
-
}, import("@cosmicdrift/kumiko-types/event-type-map").KumikoEventTypeMap>;
|
|
33
|
+
}, import("@cosmicdrift/kumiko-types/event-type-map").KumikoEventTypeMap, undefined>;
|
|
21
34
|
export declare const publishSystemWrite: import("@cosmicdrift/kumiko-types/define-handler").WriteHandlerDefinition<string, z.ZodObject<{
|
|
22
35
|
id: z.ZodString;
|
|
23
36
|
}, z.core.$strip>, {
|
|
24
37
|
id: string;
|
|
25
38
|
status: TemplateStatus;
|
|
26
|
-
}, import("@cosmicdrift/kumiko-types/event-type-map").KumikoEventTypeMap>;
|
|
39
|
+
}, import("@cosmicdrift/kumiko-types/event-type-map").KumikoEventTypeMap, undefined>;
|
|
27
40
|
export {};
|
|
@@ -4,6 +4,7 @@ import { NotFoundError, ValidationError, writeFailure, } from "@cosmicdrift/kumi
|
|
|
4
4
|
import * as z from "zod";
|
|
5
5
|
import { requireForTenant } from "../../shared/index.js";
|
|
6
6
|
import { templateResourcesTable } from "../table.js";
|
|
7
|
+
import { activationChanges, parseTemplateDraft, toWorkingRow } from "../template-draft.js";
|
|
7
8
|
import { executor } from "./shared.js";
|
|
8
9
|
import { publishGateIssues } from "./template-gates.js";
|
|
9
10
|
function isRowOfTarget(row, target, callerTenantId) {
|
|
@@ -11,36 +12,51 @@ function isRowOfTarget(row, target, callerTenantId) {
|
|
|
11
12
|
? row.tenantId === SYSTEM_TENANT_ID && row.scope === "system"
|
|
12
13
|
: row.tenantId === callerTenantId && row.scope === "tenant";
|
|
13
14
|
}
|
|
14
|
-
function
|
|
15
|
+
export function writeAccessFor(target) {
|
|
16
|
+
return { roles: target === "system" ? ["SystemAdmin"] : ["TenantAdmin", "SystemAdmin"] };
|
|
17
|
+
}
|
|
18
|
+
// The tenant db also reads system rows; the row's own tenant must match the
|
|
19
|
+
// target or the executor would append to a foreign event stream.
|
|
20
|
+
export async function loadTargetRow(ctx, target, user, id) {
|
|
21
|
+
const scoped = target === "system" ? requireForTenant(ctx, SYSTEM_TENANT_ID) : null;
|
|
22
|
+
const db = scoped?.db ?? ctx.db;
|
|
23
|
+
const row = await fetchOne(db, templateResourcesTable, { id });
|
|
24
|
+
return { db, scoped, row: row && isRowOfTarget(row, target, user.tenantId) ? row : null };
|
|
25
|
+
}
|
|
26
|
+
// Publishing promotes an open draft (gated as the version that goes live); a row
|
|
27
|
+
// without one is activated as stored. Archiving leaves the draft standing.
|
|
28
|
+
function planStatusChange(ctx, row, status) {
|
|
29
|
+
if (status === "archived")
|
|
30
|
+
return { ok: true, changes: { status } };
|
|
31
|
+
const issues = publishGateIssues(ctx.registry, toWorkingRow(row, row.tenantId));
|
|
32
|
+
if (issues.length > 0) {
|
|
33
|
+
return { ok: false, failure: writeFailure(new ValidationError({ fields: [...issues] })) };
|
|
34
|
+
}
|
|
35
|
+
return { ok: true, changes: { ...parseTemplateDraft(row.draft), ...activationChanges(row) } };
|
|
36
|
+
}
|
|
37
|
+
function createStatusUpdateHandler(name, target, status, description) {
|
|
15
38
|
return defineWriteHandler({
|
|
16
39
|
name,
|
|
17
40
|
schema: z.object({ id: z.string().min(1) }),
|
|
18
|
-
access:
|
|
41
|
+
access: writeAccessFor(target),
|
|
19
42
|
// A system template change reaches every tenant without an override.
|
|
20
43
|
...(target === "system" ? { agent: { risk: "high" } } : {}),
|
|
21
44
|
description,
|
|
22
45
|
handler: async (event, ctx) => {
|
|
23
|
-
const scoped
|
|
24
|
-
|
|
25
|
-
// The tenant db also reads system rows; the row's own tenant must match
|
|
26
|
-
// the target or the executor would append to a foreign event stream.
|
|
27
|
-
const existing = await fetchOne(db, templateResourcesTable, {
|
|
28
|
-
id: event.payload.id,
|
|
29
|
-
});
|
|
30
|
-
if (!existing || !isRowOfTarget(existing, target, event.user.tenantId)) {
|
|
46
|
+
const { db, scoped, row } = await loadTargetRow(ctx, target, event.user, event.payload.id);
|
|
47
|
+
if (!row)
|
|
31
48
|
return writeFailure(new NotFoundError("template-resource", event.payload.id));
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
const result = await executor.update({ id: existing.id, version: existing.version, changes: { status } }, event.user, db, scoped ? { streamTenantId: scoped.streamTenantId } : undefined);
|
|
49
|
+
const plan = planStatusChange(ctx, row, status);
|
|
50
|
+
if (!plan.ok)
|
|
51
|
+
return plan.failure;
|
|
52
|
+
const result = await executor.update({ id: row.id, version: row.version, changes: plan.changes }, event.user, db, scoped ? { streamTenantId: scoped.streamTenantId } : undefined);
|
|
37
53
|
if (!result.isSuccess)
|
|
38
54
|
return result;
|
|
39
|
-
return { isSuccess: true, data: { id: String(
|
|
55
|
+
return { isSuccess: true, data: { id: String(row.id), status } };
|
|
40
56
|
},
|
|
41
57
|
});
|
|
42
58
|
}
|
|
43
|
-
export const archiveWrite = createStatusUpdateHandler("archive", "own", "archived", "Sets one template resource of the caller's tenant to archived so the resolver stops serving it while the row and its content survive; use it to retire a template reversibly instead of deleting it.");
|
|
44
|
-
export const publishWrite = createStatusUpdateHandler("publish", "own", "active", "Sets one template resource of the caller's tenant to active so the resolver starts serving it; use it to release a draft written by upsert-tenant, or to bring an archived template back; a
|
|
59
|
+
export const archiveWrite = createStatusUpdateHandler("archive", "own", "archived", "Sets one template resource of the caller's tenant to archived so the resolver stops serving it while the row and its content survive; use it to retire a template reversibly instead of deleting it. An open draft stays.");
|
|
60
|
+
export const publishWrite = createStatusUpdateHandler("publish", "own", "active", "Sets one template resource of the caller's tenant to active so the resolver starts serving it; use it to release a draft written by upsert-tenant (a draft saved beside an active version replaces it), or to bring an archived template back; a version with unknown placeholders or unused required variables is refused and the served version stays.");
|
|
45
61
|
export const archiveSystemWrite = createStatusUpdateHandler("archive-system", "system", "archived", "Sets one system-default template to archived so the resolver stops serving it to every tenant that has no override; SystemAdmin only, reversible with publish-system.");
|
|
46
|
-
export const publishSystemWrite = createStatusUpdateHandler("publish-system", "system", "active", "Sets one system-default template to active so the resolver serves it again to every tenant that has no override; SystemAdmin only, a
|
|
62
|
+
export const publishSystemWrite = createStatusUpdateHandler("publish-system", "system", "active", "Sets one system-default template to active so the resolver serves it again to every tenant that has no override; an open draft beside the active version replaces it; SystemAdmin only, a version with unknown placeholders or unused required variables is refused.");
|
|
@@ -21,4 +21,4 @@ export declare const upsertSystemWrite: import("@cosmicdrift/kumiko-types/define
|
|
|
21
21
|
parentTemplateId: import("zod").ZodOptional<import("zod").ZodString>;
|
|
22
22
|
title: import("zod").ZodOptional<import("zod").ZodNullable<import("zod").ZodString>>;
|
|
23
23
|
folder: import("zod").ZodOptional<import("zod").ZodNullable<import("zod").ZodString>>;
|
|
24
|
-
}, import("zod/v4/core").$strip>, import("./template-write.js").WrittenTemplateRow, import("@cosmicdrift/kumiko-types/event-type-map").KumikoEventTypeMap>;
|
|
24
|
+
}, import("zod/v4/core").$strip>, import("./template-write.js").WrittenTemplateRow, import("@cosmicdrift/kumiko-types/event-type-map").KumikoEventTypeMap, undefined>;
|
|
@@ -27,4 +27,4 @@ export declare const upsertTenantWrite: import("@cosmicdrift/kumiko-types/define
|
|
|
27
27
|
active: "active";
|
|
28
28
|
draft: "draft";
|
|
29
29
|
}>>;
|
|
30
|
-
}, z.core.$strip>, import("./template-write.js").WrittenTemplateRow, import("@cosmicdrift/kumiko-types/event-type-map").KumikoEventTypeMap>;
|
|
30
|
+
}, z.core.$strip>, import("./template-write.js").WrittenTemplateRow, import("@cosmicdrift/kumiko-types/event-type-map").KumikoEventTypeMap, undefined>;
|
|
@@ -15,7 +15,7 @@ export const upsertTenantWrite = defineWriteHandler({
|
|
|
15
15
|
status: z.enum(["draft", "active"]).default("draft"),
|
|
16
16
|
}),
|
|
17
17
|
access: { roles: ["TenantAdmin", "SystemAdmin"] },
|
|
18
|
-
description: "Creates or overwrites a tenant-scoped template override with its variable schema, landing as a draft that only publish makes live; it refuses a system-tenant target, which is what upsert-system is for.",
|
|
18
|
+
description: "Creates or overwrites a tenant-scoped template override with its variable schema, landing as a draft that only publish makes live (over an active override the draft is stored beside it, the active version keeps being served until publish); it refuses a system-tenant target, which is what upsert-system is for.",
|
|
19
19
|
handler: async (event, ctx) => {
|
|
20
20
|
// @cast-boundary engine-payload — override aus Zod-parsed string,
|
|
21
21
|
// event.user.tenantId schon TenantId-branded; union als TenantId casten
|
|
@@ -1,15 +1,24 @@
|
|
|
1
|
+
import { selectMany } from "@cosmicdrift/kumiko-framework/bun-db";
|
|
1
2
|
import { defineQueryHandler } from "@cosmicdrift/kumiko-framework/engine";
|
|
2
3
|
import * as z from "zod";
|
|
3
4
|
import { TEMPLATE_KINDS } from "../constants.js";
|
|
5
|
+
import { templateResourcesTable } from "../table.js";
|
|
4
6
|
import { templateSlugSchema } from "../template-meta.js";
|
|
5
7
|
import { collectTemplateUsage } from "../template-usage.js";
|
|
8
|
+
import { effectiveMailLayoutSlugs } from "./overview.query.js";
|
|
9
|
+
const MAIL_ROW_LIMIT = 5000;
|
|
6
10
|
export const usageQuery = defineQueryHandler({
|
|
7
11
|
name: "usage",
|
|
8
12
|
schema: z.object({ slug: templateSlugSchema, kind: z.enum(TEMPLATE_KINDS) }),
|
|
9
13
|
access: { roles: ["TenantAdmin", "SystemAdmin"] },
|
|
10
|
-
description: "Lists the code-registered notifications that use one template: a mail template is used by the notification whose qualified name is its slug, a mail layout by every mail notification
|
|
14
|
+
description: "Lists the code-registered notifications that use one template: a mail template is used by the notification whose qualified name is its slug and that has a code template, a mail layout by every mail notification whose effective template (own override, else system) names it, or whose code meta does when no stored template exists, or that falls back to the default layout; use it to show what a change to a template affects.",
|
|
11
15
|
handler: async (query, ctx) => {
|
|
12
16
|
const { slug, kind } = query.payload;
|
|
13
|
-
|
|
17
|
+
if (kind !== "mail-layout")
|
|
18
|
+
return { usedBy: collectTemplateUsage(ctx.registry, slug, kind) };
|
|
19
|
+
const mailRows = await selectMany(ctx.db, templateResourcesTable, { kind: "mail" }, { limit: MAIL_ROW_LIMIT });
|
|
20
|
+
return {
|
|
21
|
+
usedBy: collectTemplateUsage(ctx.registry, slug, kind, effectiveMailLayoutSlugs(mailRows)),
|
|
22
|
+
};
|
|
14
23
|
},
|
|
15
24
|
});
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
export { createTemplateResolverApi, type ResolveRequest, requireTemplateResolver, TemplateNotFoundError, type TemplateResolverApi, type TemplateResource, } from "./api.js";
|
|
2
2
|
export { type CompileDocumentBodyOptions, collectDocumentResourceKeys, compileDocumentBodyToHandlebars, toResourceDataUri, } from "./compile-document-body.js";
|
|
3
|
-
export { CONTENT_FORMATS, type ContentFormat, DEFAULT_MAIL_LAYOUT_SLUG, DOCUMENT_HTML_KINDS, type DocumentHtmlKind, FALLBACK_LOCALE, NON_RENDER_KINDS, type NonRenderKind, PDF_PAGE_FORMATS, type PdfPageFormat, RENDER_KINDS, type RenderKind, SYSTEM_TENANT_ID, TEMPLATE_KINDS, TEMPLATE_SCOPES, TEMPLATE_STATUSES, TEXT_BLOCK_KIND, type TemplateKind, type TemplateScope, type TemplateStatus, UPSERT_KINDS, type UpsertKind, } from "./constants.js";
|
|
3
|
+
export { CONTENT_FORMATS, type ContentFormat, DEFAULT_MAIL_LAYOUT_SLUG, DOCUMENT_HTML_KINDS, type DocumentHtmlKind, FALLBACK_LOCALE, NON_RENDER_KINDS, type NonRenderKind, PDF_DEFAULT_MARGIN_MM, PDF_FOOTER_MARGIN_MM, PDF_HEADER_MARGIN_MM, PDF_ORIENTATIONS, PDF_PAGE_FORMATS, type PdfOrientation, type PdfPageFormat, RENDER_KINDS, type RenderKind, SYSTEM_TENANT_ID, TEMPLATE_KINDS, TEMPLATE_SCOPES, TEMPLATE_STATUSES, TEXT_BLOCK_KIND, type TemplateKind, type TemplateScope, type TemplateStatus, UPSERT_KINDS, type UpsertKind, } from "./constants.js";
|
|
4
4
|
export { type DocumentViolation, type DocumentViolationCode, documentViolationsToFieldIssues, type SanitizeBodyResult, type SanitizeHtmlResult, sanitizeDocumentHtml, sanitizeTemplateBody, } from "./document-sanitizer.js";
|
|
5
|
-
export { DOCUMENT_NODE_NAMES, DOCUMENT_TEXT_ALIGNMENTS, DocumentButton, DocumentDivider, DocumentLink, DocumentPageBreak,
|
|
5
|
+
export { DOCUMENT_NODE_NAMES, DOCUMENT_TEXT_ALIGNMENTS, DocumentButton, DocumentDivider, DocumentLink, DocumentPageBreak, DocumentResource, DocumentTable, DocumentTableCell, DocumentTableHeader, DocumentTableRow, DocumentTextAlign, type DocumentTextAlignment, DocumentVariable, documentExtensions, isAllowedDocumentLinkHref, isDocumentPlaceholderHref, isDocumentReferenceName, isDocumentResourceKey, } from "./document-schema.js";
|
|
6
6
|
export { createTemplateResolverFeature } from "./feature.js";
|
|
7
7
|
export type { LayoutSourceResponse, LayoutSourceRow } from "./handlers/layout-source.query.js";
|
|
8
8
|
export type { OverviewKindGroup, OverviewOrigin, OverviewResponse, OverviewRow, OverviewUsage, } from "./handlers/overview.query.js";
|
|
9
9
|
export { localeSchema } from "./handlers/shared.js";
|
|
10
|
+
export { isHexColor, type MailLayoutAppearance, mailLayoutAppearance, } from "./layout-appearance.js";
|
|
10
11
|
export { type LayoutRegions, readLayoutRegions, type SanitizeLayoutResult, sanitizeLayoutHtml, } from "./layout-regions.js";
|
|
11
12
|
export { fileAttachmentsOf, imageLinksOf, type LinkedResourceEntry, parseLinkedResourceEntries, pdfAttachmentLinksOf, type TemplatePdfAttachmentLink, templatePdfAttachmentLinkSchema, } from "./linked-resources.js";
|
|
12
13
|
export { prepareDocumentForPrint } from "./print-document.js";
|
|
@@ -15,7 +16,7 @@ export { mapRecordToVariables } from "./record-mapping.js";
|
|
|
15
16
|
export { readTemplateResourceMaxBytes, type StoredTemplateResource, storeTemplateResource, } from "./store-template-resource.js";
|
|
16
17
|
export { type TemplateResourceRow, templateResourceEntity, templateResourcesTable, } from "./table.js";
|
|
17
18
|
export { type TemplateBundle, templateBundleSchema } from "./template-bundle.js";
|
|
18
|
-
export { isMailTemplateMeta, type LayoutTemplateMeta, MAIL_CATEGORIES, type MailCategory, type MailTemplateMeta, type PdfTemplateMeta, parseTemplateMeta, pdfAttachmentFileNamePatternSchema, readPdfTemplateMeta, type TemplateMeta, type TemplateMetaIssue, type TemplateMetaResult, templateSlugSchema, } from "./template-meta.js";
|
|
19
|
+
export { isMailTemplateMeta, type LayoutTemplateMeta, MAIL_CATEGORIES, type MailCategory, type MailLayoutMeta, type MailTemplateMeta, type PdfTemplateMeta, parseTemplateMeta, pdfAttachmentFileNamePatternSchema, readMailLayoutMeta, readPdfTemplateMeta, type TemplateMeta, type TemplateMetaIssue, type TemplateMetaResult, templateSlugSchema, } from "./template-meta.js";
|
|
19
20
|
export { type DocumentDraftOutcome, evaluateDocumentDraft } from "./template-preview.js";
|
|
20
21
|
export { readTemplateResource, type TemplateResourceFile } from "./template-resources.js";
|
|
21
22
|
export { collectTemplateUsage, type TemplateUsageEntry } from "./template-usage.js";
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
export { createTemplateResolverApi, requireTemplateResolver, TemplateNotFoundError, } from "./api.js";
|
|
2
2
|
export { collectDocumentResourceKeys, compileDocumentBodyToHandlebars, toResourceDataUri, } from "./compile-document-body.js";
|
|
3
|
-
export { CONTENT_FORMATS, DEFAULT_MAIL_LAYOUT_SLUG, DOCUMENT_HTML_KINDS, FALLBACK_LOCALE, NON_RENDER_KINDS, PDF_PAGE_FORMATS, RENDER_KINDS, SYSTEM_TENANT_ID, TEMPLATE_KINDS, TEMPLATE_SCOPES, TEMPLATE_STATUSES, TEXT_BLOCK_KIND, UPSERT_KINDS, } from "./constants.js";
|
|
3
|
+
export { CONTENT_FORMATS, DEFAULT_MAIL_LAYOUT_SLUG, DOCUMENT_HTML_KINDS, FALLBACK_LOCALE, NON_RENDER_KINDS, PDF_DEFAULT_MARGIN_MM, PDF_FOOTER_MARGIN_MM, PDF_HEADER_MARGIN_MM, PDF_ORIENTATIONS, PDF_PAGE_FORMATS, RENDER_KINDS, SYSTEM_TENANT_ID, TEMPLATE_KINDS, TEMPLATE_SCOPES, TEMPLATE_STATUSES, TEXT_BLOCK_KIND, UPSERT_KINDS, } from "./constants.js";
|
|
4
4
|
export { documentViolationsToFieldIssues, sanitizeDocumentHtml, sanitizeTemplateBody, } from "./document-sanitizer.js";
|
|
5
|
-
export { DOCUMENT_NODE_NAMES, DOCUMENT_TEXT_ALIGNMENTS, DocumentButton, DocumentDivider, DocumentLink, DocumentPageBreak,
|
|
5
|
+
export { DOCUMENT_NODE_NAMES, DOCUMENT_TEXT_ALIGNMENTS, DocumentButton, DocumentDivider, DocumentLink, DocumentPageBreak, DocumentResource, DocumentTable, DocumentTableCell, DocumentTableHeader, DocumentTableRow, DocumentTextAlign, DocumentVariable, documentExtensions, isAllowedDocumentLinkHref, isDocumentPlaceholderHref, isDocumentReferenceName, isDocumentResourceKey, } from "./document-schema.js";
|
|
6
6
|
export { createTemplateResolverFeature } from "./feature.js";
|
|
7
7
|
export { localeSchema } from "./handlers/shared.js";
|
|
8
|
+
export { isHexColor, mailLayoutAppearance, } from "./layout-appearance.js";
|
|
8
9
|
export { readLayoutRegions, sanitizeLayoutHtml, } from "./layout-regions.js";
|
|
9
10
|
export { fileAttachmentsOf, imageLinksOf, parseLinkedResourceEntries, pdfAttachmentLinksOf, templatePdfAttachmentLinkSchema, } from "./linked-resources.js";
|
|
10
11
|
export { prepareDocumentForPrint } from "./print-document.js";
|
|
@@ -13,7 +14,7 @@ export { mapRecordToVariables } from "./record-mapping.js";
|
|
|
13
14
|
export { readTemplateResourceMaxBytes, storeTemplateResource, } from "./store-template-resource.js";
|
|
14
15
|
export { templateResourceEntity, templateResourcesTable, } from "./table.js";
|
|
15
16
|
export { templateBundleSchema } from "./template-bundle.js";
|
|
16
|
-
export { isMailTemplateMeta, MAIL_CATEGORIES, parseTemplateMeta, pdfAttachmentFileNamePatternSchema, readPdfTemplateMeta, templateSlugSchema, } from "./template-meta.js";
|
|
17
|
+
export { isMailTemplateMeta, MAIL_CATEGORIES, parseTemplateMeta, pdfAttachmentFileNamePatternSchema, readMailLayoutMeta, readPdfTemplateMeta, templateSlugSchema, } from "./template-meta.js";
|
|
17
18
|
export { evaluateDocumentDraft } from "./template-preview.js";
|
|
18
19
|
export { readTemplateResource } from "./template-resources.js";
|
|
19
20
|
export { collectTemplateUsage } from "./template-usage.js";
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { type MailLayoutMeta } from "./template-meta.js";
|
|
2
|
+
export type MailLayoutAppearance = {
|
|
3
|
+
readonly width: number;
|
|
4
|
+
readonly fontFamily: string;
|
|
5
|
+
readonly outerBackground: string;
|
|
6
|
+
readonly accentLine: boolean;
|
|
7
|
+
readonly buttonStyle: string;
|
|
8
|
+
};
|
|
9
|
+
export declare function isHexColor(value: string): boolean;
|
|
10
|
+
export declare function mailLayoutAppearance(meta: MailLayoutMeta | unknown): MailLayoutAppearance;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { HEX_COLOR_PATTERN } from "./constants.js";
|
|
2
|
+
import { readMailLayoutMeta } from "./template-meta.js";
|
|
3
|
+
const FONT_FAMILIES = {
|
|
4
|
+
sans: "Arial, Helvetica, sans-serif",
|
|
5
|
+
serif: "Georgia, 'Times New Roman', serif",
|
|
6
|
+
};
|
|
7
|
+
const BUTTON_COLOR = "#1f5468";
|
|
8
|
+
const BUTTON_BASE = "display:inline-block;padding:10px 20px;font-weight:600;text-decoration:none";
|
|
9
|
+
const BUTTON_STYLES = {
|
|
10
|
+
"filled-rounded": `${BUTTON_BASE};border-radius:6px;background-color:${BUTTON_COLOR};color:#ffffff`,
|
|
11
|
+
"filled-square": `${BUTTON_BASE};background-color:${BUTTON_COLOR};color:#ffffff`,
|
|
12
|
+
outline: `${BUTTON_BASE};border:2px solid ${BUTTON_COLOR};border-radius:6px;color:${BUTTON_COLOR}`,
|
|
13
|
+
};
|
|
14
|
+
export function isHexColor(value) {
|
|
15
|
+
return HEX_COLOR_PATTERN.test(value);
|
|
16
|
+
}
|
|
17
|
+
export function mailLayoutAppearance(meta) {
|
|
18
|
+
const parsed = readMailLayoutMeta(meta);
|
|
19
|
+
return {
|
|
20
|
+
width: parsed.width ?? 600,
|
|
21
|
+
fontFamily: FONT_FAMILIES[parsed.font ?? "sans"],
|
|
22
|
+
outerBackground: parsed.outerBackground ?? "#f5f5f5",
|
|
23
|
+
accentLine: (parsed.accentLine ?? "none") === "branding",
|
|
24
|
+
buttonStyle: BUTTON_STYLES[parsed.buttonStyle ?? "filled-rounded"],
|
|
25
|
+
};
|
|
26
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export declare const PDF_PAGE_FORMATS: readonly ["A4", "Letter"];
|
|
2
|
+
export type PdfPageFormat = (typeof PDF_PAGE_FORMATS)[number];
|
|
3
|
+
export declare const PDF_ORIENTATIONS: readonly ["portrait", "landscape"];
|
|
4
|
+
export type PdfOrientation = (typeof PDF_ORIENTATIONS)[number];
|
|
5
|
+
export declare const PDF_DEFAULT_MARGIN_MM = 15;
|
|
6
|
+
export declare const PDF_HEADER_MARGIN_MM = 30;
|
|
7
|
+
export declare const PDF_FOOTER_MARGIN_MM = 20;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// @runtime client
|
|
2
|
+
// No imports: web/ re-exports these, and constants.ts pulls in the framework engine.
|
|
3
|
+
export const PDF_PAGE_FORMATS = ["A4", "Letter"];
|
|
4
|
+
export const PDF_ORIENTATIONS = ["portrait", "landscape"];
|
|
5
|
+
// Page margins in mm; the header/footer values apply when the layout contributes that region and the template sets no margin itself.
|
|
6
|
+
export const PDF_DEFAULT_MARGIN_MM = 15;
|
|
7
|
+
export const PDF_HEADER_MARGIN_MM = 30;
|
|
8
|
+
export const PDF_FOOTER_MARGIN_MM = 20;
|
|
@@ -6,6 +6,8 @@ export declare const TemplateResolverHandlers: {
|
|
|
6
6
|
readonly archive: "template-resolver:write:archive";
|
|
7
7
|
readonly publishSystem: "template-resolver:write:publish-system";
|
|
8
8
|
readonly archiveSystem: "template-resolver:write:archive-system";
|
|
9
|
+
readonly discardDraft: "template-resolver:write:discard-draft";
|
|
10
|
+
readonly discardDraftSystem: "template-resolver:write:discard-draft-system";
|
|
9
11
|
readonly resetToSystem: "template-resolver:write:reset-to-system";
|
|
10
12
|
readonly set: "template-resolver:write:set";
|
|
11
13
|
readonly importBundle: "template-resolver:write:import-bundle";
|
|
@@ -37,10 +39,12 @@ export declare const TemplateResolverErrors: {
|
|
|
37
39
|
readonly invalidSlug: "invalid_slug";
|
|
38
40
|
readonly invalidLocale: "invalid_locale";
|
|
39
41
|
readonly systemAdminRequired: "system_admin_required";
|
|
42
|
+
readonly noDraft: "no_draft";
|
|
40
43
|
readonly alreadyActive: "template_already_active";
|
|
41
44
|
readonly alreadyArchived: "template_already_archived";
|
|
42
45
|
readonly targetExists: "target_exists";
|
|
43
46
|
readonly systemResourcesNeedSystemTenant: "system_resources_need_system_tenant";
|
|
47
|
+
readonly bundleLocalesDiffer: "bundle_locales_differ";
|
|
44
48
|
readonly resourceTooLarge: "template_resource_too_large";
|
|
45
49
|
readonly resourceUnsupportedImage: "template_resource_unsupported_image";
|
|
46
50
|
readonly attachmentUnsupportedType: "template_attachment_unsupported_type";
|
|
@@ -8,6 +8,8 @@ export const TemplateResolverHandlers = {
|
|
|
8
8
|
archive: "template-resolver:write:archive",
|
|
9
9
|
publishSystem: "template-resolver:write:publish-system",
|
|
10
10
|
archiveSystem: "template-resolver:write:archive-system",
|
|
11
|
+
discardDraft: "template-resolver:write:discard-draft",
|
|
12
|
+
discardDraftSystem: "template-resolver:write:discard-draft-system",
|
|
11
13
|
resetToSystem: "template-resolver:write:reset-to-system",
|
|
12
14
|
set: "template-resolver:write:set",
|
|
13
15
|
importBundle: "template-resolver:write:import-bundle",
|
|
@@ -48,10 +50,12 @@ export const TemplateResolverErrors = {
|
|
|
48
50
|
invalidSlug: "invalid_slug",
|
|
49
51
|
invalidLocale: "invalid_locale",
|
|
50
52
|
systemAdminRequired: "system_admin_required",
|
|
53
|
+
noDraft: "no_draft",
|
|
51
54
|
alreadyActive: "template_already_active",
|
|
52
55
|
alreadyArchived: "template_already_archived",
|
|
53
56
|
targetExists: "target_exists",
|
|
54
57
|
systemResourcesNeedSystemTenant: "system_resources_need_system_tenant",
|
|
58
|
+
bundleLocalesDiffer: "bundle_locales_differ",
|
|
55
59
|
resourceTooLarge: "template_resource_too_large",
|
|
56
60
|
resourceUnsupportedImage: "template_resource_unsupported_image",
|
|
57
61
|
attachmentUnsupportedType: "template_attachment_unsupported_type",
|
|
@@ -18,6 +18,7 @@ import { executor } from "./handlers/shared.js";
|
|
|
18
18
|
import { evaluateTemplateWrite } from "./handlers/template-gates.js";
|
|
19
19
|
import { TEMPLATE_RESOLVER_FEATURE } from "./qualified-names.js";
|
|
20
20
|
import { templateResourcesTable } from "./table.js";
|
|
21
|
+
import { activeStampChanges, servedStampChanges } from "./template-draft.js";
|
|
21
22
|
import { templateSlugSchema } from "./template-meta.js";
|
|
22
23
|
import { variablesFromSchema } from "./template-validation.js";
|
|
23
24
|
/** Drop projection orphans (no events anywhere) so boot seeds can recreate. */
|
|
@@ -97,7 +98,11 @@ export async function seedSystemTemplate(db, opts) {
|
|
|
97
98
|
(linkedResources === undefined || current?.linkedResources === linkedResources)) {
|
|
98
99
|
return { id: String(row.id) };
|
|
99
100
|
}
|
|
100
|
-
const result = await executor.update({
|
|
101
|
+
const result = await executor.update({
|
|
102
|
+
id: row.id,
|
|
103
|
+
version: row.version,
|
|
104
|
+
changes: { ...rowFields, ...activeStampChanges(row) },
|
|
105
|
+
}, by, tdb);
|
|
101
106
|
if (!result.isSuccess) {
|
|
102
107
|
throw new Error(`seedSystemTemplate update failed: ${JSON.stringify(result)}`);
|
|
103
108
|
}
|
|
@@ -107,7 +112,13 @@ export async function seedSystemTemplate(db, opts) {
|
|
|
107
112
|
existing,
|
|
108
113
|
ifExists: opts.ifExists,
|
|
109
114
|
create: async () => {
|
|
110
|
-
const result = await executor.create({
|
|
115
|
+
const result = await executor.create({
|
|
116
|
+
...rowFields,
|
|
117
|
+
...activeStampChanges(null),
|
|
118
|
+
draft: null,
|
|
119
|
+
linkedResources: linkedResources ?? "{}",
|
|
120
|
+
tenantId,
|
|
121
|
+
}, by, tdb);
|
|
111
122
|
if (!result.isSuccess) {
|
|
112
123
|
// Parallel boot pods can both delete the orphan and race on create —
|
|
113
124
|
// unique on (tenantId, slug, kind, locale) → resolve winner and update.
|
|
@@ -235,7 +246,13 @@ export async function seedTextBlock(db, opts) {
|
|
|
235
246
|
const result = await executor.update({
|
|
236
247
|
id: row.id,
|
|
237
248
|
version: row.version,
|
|
238
|
-
changes: {
|
|
249
|
+
changes: {
|
|
250
|
+
title: fields.title,
|
|
251
|
+
content: fields.content,
|
|
252
|
+
folder: fields.folder,
|
|
253
|
+
// A re-seed must not revive an archived block; only a served one gets a new served version.
|
|
254
|
+
...(current?.status === "active" && servedStampChanges(row)),
|
|
255
|
+
},
|
|
239
256
|
}, by, tdb);
|
|
240
257
|
if (!result.isSuccess) {
|
|
241
258
|
throw new Error(`seedTextBlock update failed: ${JSON.stringify(result)}`);
|
|
@@ -246,7 +263,7 @@ export async function seedTextBlock(db, opts) {
|
|
|
246
263
|
existing,
|
|
247
264
|
ifExists: opts.ifExists,
|
|
248
265
|
create: async () => {
|
|
249
|
-
const result = await executor.create({ ...fields, tenantId: opts.tenantId }, by, tdb);
|
|
266
|
+
const result = await executor.create({ ...fields, ...activeStampChanges(null), draft: null, tenantId: opts.tenantId }, by, tdb);
|
|
250
267
|
if (!result.isSuccess) {
|
|
251
268
|
if (result.error?.code === "unique_violation") {
|
|
252
269
|
const again = await fetchOne(db, templateResourcesTable, {
|
|
@@ -55,7 +55,7 @@ export async function storeTemplateResource(args) {
|
|
|
55
55
|
}
|
|
56
56
|
const created = await createFileRefFromBytes({
|
|
57
57
|
db,
|
|
58
|
-
files: fileContextForTenant(ctx, tenantId),
|
|
58
|
+
files: fileContextForTenant(ctx, ctx.user.tenantId, tenantId),
|
|
59
59
|
registry: ctx.registry,
|
|
60
60
|
user: { ...user, tenantId },
|
|
61
61
|
}, {
|
|
@@ -145,7 +145,7 @@ async function createAttachmentFileRef(args) {
|
|
|
145
145
|
const { db } = requireForTenant(ctx, tenantId);
|
|
146
146
|
const created = await createFileRefFromBytes({
|
|
147
147
|
db,
|
|
148
|
-
files: fileContextForTenant(ctx, tenantId),
|
|
148
|
+
files: fileContextForTenant(ctx, ctx.user.tenantId, tenantId),
|
|
149
149
|
registry: ctx.registry,
|
|
150
150
|
user: { ...user, tenantId },
|
|
151
151
|
}, {
|
|
@@ -277,6 +277,56 @@ export declare const templateResourceEntity: import("@cosmicdrift/kumiko-types/f
|
|
|
277
277
|
} & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & {
|
|
278
278
|
required: true;
|
|
279
279
|
};
|
|
280
|
+
draft: {
|
|
281
|
+
readonly type: "longText";
|
|
282
|
+
readonly description?: string;
|
|
283
|
+
readonly maxLength?: number;
|
|
284
|
+
readonly required?: boolean;
|
|
285
|
+
readonly encrypted?: boolean;
|
|
286
|
+
readonly sensitive?: boolean;
|
|
287
|
+
readonly readAsInstruction?: boolean;
|
|
288
|
+
readonly default?: string;
|
|
289
|
+
readonly access?: import("@cosmicdrift/kumiko-types/fields").FieldAccess;
|
|
290
|
+
readonly multiline?: boolean | {
|
|
291
|
+
readonly rows?: number;
|
|
292
|
+
};
|
|
293
|
+
} & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & {
|
|
294
|
+
required: false;
|
|
295
|
+
};
|
|
296
|
+
activeVersion: {
|
|
297
|
+
readonly type: "number";
|
|
298
|
+
readonly description?: string;
|
|
299
|
+
readonly required?: boolean;
|
|
300
|
+
readonly sortable?: boolean;
|
|
301
|
+
readonly filterable?: boolean;
|
|
302
|
+
readonly sensitive?: boolean;
|
|
303
|
+
readonly default?: number;
|
|
304
|
+
readonly access?: import("@cosmicdrift/kumiko-types/fields").FieldAccess;
|
|
305
|
+
readonly min?: number;
|
|
306
|
+
readonly max?: number;
|
|
307
|
+
readonly integer?: boolean;
|
|
308
|
+
readonly unit?: string | {
|
|
309
|
+
readonly field: string;
|
|
310
|
+
};
|
|
311
|
+
readonly grouping?: boolean;
|
|
312
|
+
} & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & {
|
|
313
|
+
required: false;
|
|
314
|
+
};
|
|
315
|
+
activeSince: {
|
|
316
|
+
readonly type: "timestamp";
|
|
317
|
+
readonly description?: string;
|
|
318
|
+
readonly required?: boolean;
|
|
319
|
+
readonly sortable?: boolean;
|
|
320
|
+
readonly filterable?: boolean;
|
|
321
|
+
readonly sensitive?: boolean;
|
|
322
|
+
readonly access?: import("@cosmicdrift/kumiko-types/fields").FieldAccess;
|
|
323
|
+
readonly locatedBy?: string;
|
|
324
|
+
readonly min?: string;
|
|
325
|
+
readonly max?: string;
|
|
326
|
+
readonly locale?: string;
|
|
327
|
+
} & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & {
|
|
328
|
+
required: false;
|
|
329
|
+
};
|
|
280
330
|
}, "tenant">;
|
|
281
331
|
export declare const templateResourcesTable: import("@cosmicdrift/kumiko-framework/db").EntityTable<import("@cosmicdrift/kumiko-types/fields").EntityDefinition<{
|
|
282
332
|
slug: {
|
|
@@ -556,6 +606,56 @@ export declare const templateResourcesTable: import("@cosmicdrift/kumiko-framewo
|
|
|
556
606
|
} & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & {
|
|
557
607
|
required: true;
|
|
558
608
|
};
|
|
609
|
+
draft: {
|
|
610
|
+
readonly type: "longText";
|
|
611
|
+
readonly description?: string;
|
|
612
|
+
readonly maxLength?: number;
|
|
613
|
+
readonly required?: boolean;
|
|
614
|
+
readonly encrypted?: boolean;
|
|
615
|
+
readonly sensitive?: boolean;
|
|
616
|
+
readonly readAsInstruction?: boolean;
|
|
617
|
+
readonly default?: string;
|
|
618
|
+
readonly access?: import("@cosmicdrift/kumiko-types/fields").FieldAccess;
|
|
619
|
+
readonly multiline?: boolean | {
|
|
620
|
+
readonly rows?: number;
|
|
621
|
+
};
|
|
622
|
+
} & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & {
|
|
623
|
+
required: false;
|
|
624
|
+
};
|
|
625
|
+
activeVersion: {
|
|
626
|
+
readonly type: "number";
|
|
627
|
+
readonly description?: string;
|
|
628
|
+
readonly required?: boolean;
|
|
629
|
+
readonly sortable?: boolean;
|
|
630
|
+
readonly filterable?: boolean;
|
|
631
|
+
readonly sensitive?: boolean;
|
|
632
|
+
readonly default?: number;
|
|
633
|
+
readonly access?: import("@cosmicdrift/kumiko-types/fields").FieldAccess;
|
|
634
|
+
readonly min?: number;
|
|
635
|
+
readonly max?: number;
|
|
636
|
+
readonly integer?: boolean;
|
|
637
|
+
readonly unit?: string | {
|
|
638
|
+
readonly field: string;
|
|
639
|
+
};
|
|
640
|
+
readonly grouping?: boolean;
|
|
641
|
+
} & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & {
|
|
642
|
+
required: false;
|
|
643
|
+
};
|
|
644
|
+
activeSince: {
|
|
645
|
+
readonly type: "timestamp";
|
|
646
|
+
readonly description?: string;
|
|
647
|
+
readonly required?: boolean;
|
|
648
|
+
readonly sortable?: boolean;
|
|
649
|
+
readonly filterable?: boolean;
|
|
650
|
+
readonly sensitive?: boolean;
|
|
651
|
+
readonly access?: import("@cosmicdrift/kumiko-types/fields").FieldAccess;
|
|
652
|
+
readonly locatedBy?: string;
|
|
653
|
+
readonly min?: string;
|
|
654
|
+
readonly max?: string;
|
|
655
|
+
readonly locale?: string;
|
|
656
|
+
} & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & {
|
|
657
|
+
required: false;
|
|
658
|
+
};
|
|
559
659
|
}, "tenant">>;
|
|
560
660
|
export type EntityAuditColumns = {
|
|
561
661
|
readonly insertedAt: Temporal.Instant;
|
|
@@ -580,4 +680,7 @@ export type TemplateResourceRow = {
|
|
|
580
680
|
readonly scope: string;
|
|
581
681
|
readonly parentTemplateId: string | null;
|
|
582
682
|
readonly status: string;
|
|
683
|
+
readonly draft: string | null;
|
|
684
|
+
readonly activeVersion: number | null;
|
|
685
|
+
readonly activeSince: Temporal.Instant | null;
|
|
583
686
|
} & EntityAuditColumns;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { buildEntityTable } from "@cosmicdrift/kumiko-framework/db";
|
|
2
|
-
import { createEntity, createLongTextField, createSelectField, createTextField, } from "@cosmicdrift/kumiko-framework/engine";
|
|
2
|
+
import { createEntity, createLongTextField, createNumberField, createSelectField, createTextField, createTimestampField, } from "@cosmicdrift/kumiko-framework/engine";
|
|
3
3
|
import { CONTENT_FORMATS, TEMPLATE_KINDS, TEMPLATE_SCOPES, TEMPLATE_STATUSES, } from "./constants.js";
|
|
4
4
|
// Stored v1 events name the kind "mail-html", which is now "mail".
|
|
5
5
|
const renameMailHtmlKind = {
|
|
@@ -10,7 +10,9 @@ const renameMailHtmlKind = {
|
|
|
10
10
|
// TemplateResource — strukturierte Template-Definition mit Tenant-
|
|
11
11
|
// Override-Hierarchie, Locale-Fallback und Resource-Linking via
|
|
12
12
|
// file-foundation. Pro (tenantId, slug, kind, locale) genau eine Row;
|
|
13
|
-
// scope/status/version differenzieren Lifecycle.
|
|
13
|
+
// scope/status/version differenzieren Lifecycle. Der Entwurf neben einer
|
|
14
|
+
// aktiven Fassung liegt in `draft`; die Hauptfelder bleiben die ausgelieferte
|
|
15
|
+
// Fassung.
|
|
14
16
|
//
|
|
15
17
|
// `variableSchema` + `linkedResources` sind JSON-Strings in longText
|
|
16
18
|
// (Pattern aus compliance-profiles — kein dedizierter jsonbField im
|
|
@@ -44,6 +46,11 @@ export const templateResourceEntity = createEntity({
|
|
|
44
46
|
scope: createSelectField({ required: true, options: [...TEMPLATE_SCOPES] }),
|
|
45
47
|
parentTemplateId: createTextField({ personal: false, reason: "technical_reference" }),
|
|
46
48
|
status: createSelectField({ required: true, options: [...TEMPLATE_STATUSES] }),
|
|
49
|
+
// JSON of the unpublished edit beside the active version (see template-draft.ts).
|
|
50
|
+
draft: createLongTextField({ personal: false, reason: "is_business_data" }),
|
|
51
|
+
// `version` counts every save including drafts; these two describe the served version.
|
|
52
|
+
activeVersion: createNumberField({ integer: true, min: 1 }),
|
|
53
|
+
activeSince: createTimestampField({}),
|
|
47
54
|
},
|
|
48
55
|
indexes: [
|
|
49
56
|
{
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { Temporal } from "@cosmicdrift/kumiko-types/temporal";
|
|
2
|
+
import { type ContentFormat } from "./constants.js";
|
|
3
|
+
import type { TemplateResourceRow } from "./table.js";
|
|
4
|
+
export type TemplateDraft = {
|
|
5
|
+
readonly content: string | null;
|
|
6
|
+
readonly contentFormat: ContentFormat;
|
|
7
|
+
readonly variableSchema: string;
|
|
8
|
+
readonly meta: string | null;
|
|
9
|
+
readonly title: string | null;
|
|
10
|
+
readonly folder: string | null;
|
|
11
|
+
readonly parentTemplateId: string | null;
|
|
12
|
+
};
|
|
13
|
+
export declare function parseTemplateDraft(raw: string | null): TemplateDraft | null;
|
|
14
|
+
export declare function serializeTemplateDraft(draft: TemplateDraft): string;
|
|
15
|
+
export declare function activeVersionOf(row: TemplateResourceRow): number;
|
|
16
|
+
export declare function servedVersionOf(row: TemplateResourceRow): number;
|
|
17
|
+
export declare function activeSinceOf(row: TemplateResourceRow): Temporal.Instant;
|
|
18
|
+
export type ActiveInfo = {
|
|
19
|
+
readonly version: number;
|
|
20
|
+
readonly since: Temporal.Instant;
|
|
21
|
+
};
|
|
22
|
+
export type WorkingRow = TemplateResourceRow & {
|
|
23
|
+
readonly active: ActiveInfo | null;
|
|
24
|
+
};
|
|
25
|
+
export declare function isHiddenSystemDraft(user: {
|
|
26
|
+
readonly roles: readonly string[];
|
|
27
|
+
}, row: Pick<TemplateResourceRow, "scope" | "status">): boolean;
|
|
28
|
+
export declare function draftViewerTenantId(user: {
|
|
29
|
+
readonly tenantId: string;
|
|
30
|
+
readonly roles: readonly string[];
|
|
31
|
+
}, row: TemplateResourceRow): string;
|
|
32
|
+
export declare function toWorkingRow(row: TemplateResourceRow, viewerTenantId: string): WorkingRow;
|
|
33
|
+
export declare function servedStampChanges(row: {
|
|
34
|
+
readonly version: number;
|
|
35
|
+
} | null): {
|
|
36
|
+
activeVersion: number;
|
|
37
|
+
activeSince: globalThis.Temporal.Instant;
|
|
38
|
+
};
|
|
39
|
+
export declare function activeStampChanges(row: {
|
|
40
|
+
readonly version: number;
|
|
41
|
+
} | null): {
|
|
42
|
+
activeVersion: number;
|
|
43
|
+
activeSince: globalThis.Temporal.Instant;
|
|
44
|
+
status: string;
|
|
45
|
+
};
|
|
46
|
+
export declare function activationChanges(row: TemplateResourceRow | null): {
|
|
47
|
+
activeVersion: number;
|
|
48
|
+
activeSince: globalThis.Temporal.Instant;
|
|
49
|
+
status: string;
|
|
50
|
+
draft: null;
|
|
51
|
+
};
|