@cosmicdrift/kumiko-samples 0.362.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/package.json +1 -1
- package/packages/bundled-features/package.json +23 -6
- package/packages/bundled-features/src/admin-shell/changes.json +6 -0
- package/packages/bundled-features/src/admin-shell/feature.ts +8 -1
- package/packages/bundled-features/src/agent-tools/agent-manifest.ts +3 -4
- package/packages/bundled-features/src/agent-tools/filter-ops.ts +1 -3
- package/packages/bundled-features/src/agent-tools/tool-catalog.ts +1 -3
- package/packages/bundled-features/src/agent-tools/tool-dispatch.ts +2 -1
- package/packages/bundled-features/src/audit/changes.json +6 -0
- package/packages/bundled-features/src/audit/handlers/list.query.ts +10 -5
- package/packages/bundled-features/src/auth-email-password/bootstrap.ts +29 -8
- package/packages/bundled-features/src/auth-email-password/changes.json +51 -0
- package/packages/bundled-features/src/auth-email-password/email-rate-limits.ts +16 -0
- package/packages/bundled-features/src/auth-email-password/email-templates.ts +12 -138
- package/packages/bundled-features/src/auth-email-password/feature.ts +30 -9
- package/packages/bundled-features/src/auth-email-password/handlers/change-password.write.ts +6 -2
- package/packages/bundled-features/src/auth-email-password/handlers/invite-create.write.ts +4 -5
- package/packages/bundled-features/src/auth-email-password/handlers/logout.write.ts +7 -2
- package/packages/bundled-features/src/auth-email-password/handlers/request-account-unlock.write.ts +2 -5
- package/packages/bundled-features/src/auth-email-password/handlers/request-email-verification.write.ts +2 -5
- package/packages/bundled-features/src/auth-email-password/handlers/request-password-reset.write.ts +2 -5
- package/packages/bundled-features/src/auth-email-password/handlers/signup-confirm.write.ts +3 -4
- package/packages/bundled-features/src/auth-email-password/handlers/signup-request.write.ts +2 -1
- package/packages/bundled-features/src/auth-email-password/handlers/token-request-handler.ts +5 -13
- package/packages/bundled-features/src/auth-email-password/index.ts +2 -17
- package/packages/bundled-features/src/auth-email-password/magic-link-mail.ts +30 -22
- package/packages/bundled-features/src/auth-email-password/mail-notifications.ts +158 -0
- package/packages/bundled-features/src/auth-email-password/signup-activation.ts +2 -3
- package/packages/bundled-features/src/auth-email-password/web/auth-gate.tsx +24 -5
- package/packages/bundled-features/src/auth-foundation/types.ts +8 -18
- package/packages/bundled-features/src/auth-mfa/feature.ts +8 -10
- package/packages/bundled-features/src/auth-mfa/hmac-token-codec.ts +4 -4
- package/packages/bundled-features/src/auth-mfa/mfa-challenge-token.ts +3 -3
- package/packages/bundled-features/src/auth-mfa/mfa-preauth-setup-token.ts +3 -3
- package/packages/bundled-features/src/auth-mfa/mfa-setup-token.ts +8 -8
- package/packages/bundled-features/src/billing-foundation/changes.json +43 -0
- package/packages/bundled-features/src/billing-foundation/constants.ts +5 -4
- package/packages/bundled-features/src/billing-foundation/consumer-protection/confirmation-mail.ts +90 -44
- package/packages/bundled-features/src/billing-foundation/consumer-protection/consent-gate.ts +22 -1
- package/packages/bundled-features/src/billing-foundation/consumer-protection/issue-confirmation.ts +3 -3
- package/packages/bundled-features/src/billing-foundation/consumer-protection/mail-notifications.ts +118 -0
- package/packages/bundled-features/src/billing-foundation/consumer-protection/register-consent.ts +2 -0
- package/packages/bundled-features/src/billing-foundation/consumer-protection/register-termination.ts +11 -1
- package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-mail.ts +39 -33
- package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-notify.ts +2 -2
- package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-pages.ts +1 -4
- package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-receipt-limit.ts +41 -0
- package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-record.ts +10 -1
- package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-request.ts +36 -20
- package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-texts.ts +9 -0
- package/packages/bundled-features/src/billing-foundation/consumer-protection/withdrawal-period.ts +35 -0
- package/packages/bundled-features/src/billing-foundation/get-subscription-for-tenant.ts +2 -2
- package/packages/bundled-features/src/billing-foundation/handlers/create-checkout-session.write.ts +11 -3
- package/packages/bundled-features/src/billing-foundation/handlers/create-portal-session.write.ts +4 -9
- package/packages/bundled-features/src/billing-foundation/handlers/list-subscriptions.query.ts +1 -1
- package/packages/bundled-features/src/billing-foundation/handlers/process-event.write.ts +12 -6
- package/packages/bundled-features/src/billing-foundation/handlers/process-payment-event.write.ts +1 -1
- package/packages/bundled-features/src/billing-foundation/i18n.ts +3 -0
- package/packages/bundled-features/src/billing-foundation/subscription-tier-sync.ts +20 -11
- package/packages/bundled-features/src/billing-foundation/types.ts +7 -2
- package/packages/bundled-features/src/cap-counter/calendar-cap-usage-reservation.ts +2 -0
- package/packages/bundled-features/src/cap-counter/changes.json +6 -0
- package/packages/bundled-features/src/cap-counter/enforce-cap.ts +75 -5
- package/packages/bundled-features/src/cap-counter/index.ts +1 -0
- package/packages/bundled-features/src/cap-counter/with-cap-enforcement.ts +3 -0
- package/packages/bundled-features/src/cap-overview/handlers/tenant-caps-list.query.ts +2 -1
- package/packages/bundled-features/src/cap-overview/web/cap-usage-cell.tsx +6 -4
- package/packages/bundled-features/src/channel-email/changes.json +19 -0
- package/packages/bundled-features/src/channel-email/email-channel.ts +135 -29
- package/packages/bundled-features/src/channel-email/feature.ts +6 -3
- package/packages/bundled-features/src/channel-email/index.ts +7 -1
- package/packages/bundled-features/src/channel-email/pii-guard.ts +13 -0
- package/packages/bundled-features/src/channel-email/smtp-transport.ts +15 -1
- package/packages/bundled-features/src/channel-email/types.ts +23 -0
- package/packages/bundled-features/src/channel-in-app/handlers/mark-read.write.ts +2 -2
- package/packages/bundled-features/src/config/changes.json +6 -0
- package/packages/bundled-features/src/config/handlers/set.write.ts +3 -7
- package/packages/bundled-features/src/config/resolver.ts +15 -12
- package/packages/bundled-features/src/config/write-helpers.ts +23 -0
- package/packages/bundled-features/src/crypto-shredding/changes.json +6 -0
- package/packages/bundled-features/src/crypto-shredding/handlers/forget-subject.write.ts +48 -27
- package/packages/bundled-features/src/custom-fields/changes.json +6 -0
- package/packages/bundled-features/src/custom-fields/db/queries/user-data-rights.ts +2 -2
- package/packages/bundled-features/src/custom-fields/handlers/update-tenant-field.write.ts +0 -1
- package/packages/bundled-features/src/custom-fields/lib/value-schema.ts +10 -11
- package/packages/bundled-features/src/custom-fields/wire-user-data-rights.ts +7 -6
- package/packages/bundled-features/src/data-retention/changes.json +6 -0
- package/packages/bundled-features/src/data-retention/index.ts +1 -0
- package/packages/bundled-features/src/data-retention/run-retention-cleanup.ts +8 -6
- package/packages/bundled-features/src/delivery/attachment-ref.ts +105 -0
- package/packages/bundled-features/src/delivery/changes.json +66 -0
- package/packages/bundled-features/src/delivery/channel-context.ts +152 -4
- package/packages/bundled-features/src/delivery/constants.ts +2 -0
- package/packages/bundled-features/src/delivery/delivery-service.ts +104 -26
- package/packages/bundled-features/src/delivery/feature.ts +1 -1
- package/packages/bundled-features/src/delivery/index.ts +19 -2
- package/packages/bundled-features/src/delivery/jobs.ts +97 -17
- package/packages/bundled-features/src/delivery/notify-factory.ts +14 -9
- package/packages/bundled-features/src/delivery/testing.ts +9 -1
- package/packages/bundled-features/src/delivery/types.ts +34 -18
- package/packages/bundled-features/src/delivery/unsubscribe.ts +5 -2
- package/packages/bundled-features/src/derivatives-sharp/render.ts +1 -22
- package/packages/bundled-features/src/document-ingest-foundation/pages.ts +6 -3
- package/packages/bundled-features/src/feature-toggles/toggle-sync-signal.ts +4 -4
- package/packages/bundled-features/src/file-derivatives/handlers/public-variant.query.ts +1 -1
- package/packages/bundled-features/src/file-foundation/changes.json +6 -0
- package/packages/bundled-features/src/file-foundation/index.ts +7 -0
- package/packages/bundled-features/src/file-foundation/read-file-ref-content.ts +89 -0
- package/packages/bundled-features/src/files-tenant-data/changes.json +6 -0
- package/packages/bundled-features/src/files-tenant-data/hooks.ts +4 -0
- package/packages/bundled-features/src/folders/web/folder-manager.tsx +5 -2
- package/packages/bundled-features/src/form-draft/db/queries/cleanup.ts +11 -11
- package/packages/bundled-features/src/form-draft/db/queries/draft-count.ts +2 -2
- package/packages/bundled-features/src/form-draft/db/queries/owned-file-refs.ts +2 -2
- package/packages/bundled-features/src/form-draft/release-file-refs.ts +3 -2
- package/packages/bundled-features/src/inbound-mail-foundation/changes.json +6 -0
- package/packages/bundled-features/src/inbound-mail-foundation/handlers/connect-account.write.ts +1 -1
- package/packages/bundled-features/src/inbound-mail-foundation/handlers/ingest-message.write.ts +6 -6
- package/packages/bundled-features/src/inbound-mail-foundation/handlers/list-accounts.query.ts +1 -1
- package/packages/bundled-features/src/inbound-mail-foundation/handlers/list-messages.query.ts +1 -1
- package/packages/bundled-features/src/inbound-mail-foundation/handlers/scope-visibility.ts +2 -2
- package/packages/bundled-features/src/inbound-mail-foundation/oauth-access-token.ts +13 -8
- package/packages/bundled-features/src/inbound-mail-foundation/oauth-state.ts +7 -7
- package/packages/bundled-features/src/inbound-mail-foundation/retention-sweep.ts +2 -6
- package/packages/bundled-features/src/inbound-mail-foundation/tenant-destroy-hook.ts +5 -13
- package/packages/bundled-features/src/inbound-mail-foundation/types.ts +4 -6
- package/packages/bundled-features/src/inbound-mail-foundation/watch-supervisor.ts +42 -25
- package/packages/bundled-features/src/inbound-provider-imap/imap-client.ts +4 -3
- package/packages/bundled-features/src/jobs/changes.json +19 -0
- package/packages/bundled-features/src/jobs/db/queries/stale-run-sweep.ts +12 -4
- package/packages/bundled-features/src/jobs/feature.ts +7 -4
- package/packages/bundled-features/src/jobs/handlers/catalog.query.ts +1 -1
- package/packages/bundled-features/src/jobs/handlers/list.query.ts +3 -1
- package/packages/bundled-features/src/jobs/handlers/stale-run-sweep.job.ts +5 -2
- package/packages/bundled-features/src/jobs/job-run-logger.ts +52 -17
- package/packages/bundled-features/src/jobs/tenant-job-run-table.ts +7 -2
- package/packages/bundled-features/src/ledger/changes.json +7 -0
- package/packages/bundled-features/src/ledger/entity.ts +3 -1
- package/packages/bundled-features/src/ledger/recurring.ts +1 -3
- package/packages/bundled-features/src/managed-pages/branding.ts +33 -9
- package/packages/bundled-features/src/managed-pages/changes.json +6 -0
- package/packages/bundled-features/src/managed-pages/feature.ts +1 -0
- package/packages/bundled-features/src/managed-pages/i18n.ts +1 -0
- package/packages/bundled-features/src/managed-pages/screens/branding-screen.ts +8 -0
- package/packages/bundled-features/src/page-render/branding.ts +3 -0
- package/packages/bundled-features/src/page-render/markdown.ts +1 -1
- package/packages/bundled-features/src/personal-access-tokens/feature.ts +2 -4
- package/packages/bundled-features/src/personal-access-tokens/handlers/list.query.ts +2 -1
- package/packages/bundled-features/src/personal-access-tokens/index.ts +1 -1
- package/packages/bundled-features/src/personal-access-tokens/revoke-for-user.ts +2 -1
- package/packages/bundled-features/src/renderer-foundation/README.md +11 -10
- package/packages/bundled-features/src/renderer-foundation/api.ts +14 -14
- package/packages/bundled-features/src/renderer-foundation/changes.json +39 -0
- package/packages/bundled-features/src/renderer-foundation/constants.ts +6 -8
- package/packages/bundled-features/src/renderer-foundation/feature.ts +7 -7
- package/packages/bundled-features/src/renderer-foundation/index.ts +13 -2
- package/packages/bundled-features/src/renderer-foundation/types.ts +134 -35
- package/packages/bundled-features/src/renderer-pdf/changes.json +12 -0
- package/packages/bundled-features/src/renderer-pdf/feature.ts +7 -1
- package/packages/bundled-features/src/renderer-pdf/render-table-pdf.ts +162 -82
- package/packages/bundled-features/src/secrets/changes.json +6 -0
- package/packages/bundled-features/src/secrets/feature.ts +4 -3
- package/packages/bundled-features/src/secrets/secrets-context.ts +23 -3
- package/packages/bundled-features/src/sessions/db/queries/cleanup.ts +2 -2
- package/packages/bundled-features/src/sessions/feature.ts +2 -4
- package/packages/bundled-features/src/sessions/handlers/list.query.ts +2 -1
- package/packages/bundled-features/src/shared/attachment-file-name.ts +39 -0
- package/packages/bundled-features/src/shared/changes.json +6 -0
- package/packages/bundled-features/src/shared/chunked-entity-migration.ts +4 -1
- package/packages/bundled-features/src/shared/index.ts +6 -0
- package/packages/bundled-features/src/shared/is-tenant-db.ts +1 -1
- package/packages/bundled-features/src/shared/mail-template-body.ts +42 -0
- package/packages/bundled-features/src/shared/sharp-input-limits.ts +26 -0
- package/packages/bundled-features/src/shared/signup-handover.ts +4 -4
- package/packages/bundled-features/src/subscription-stripe/changes.json +6 -0
- package/packages/bundled-features/src/subscription-stripe/plugin-methods.ts +19 -11
- package/packages/bundled-features/src/subscription-stripe/verify-webhook.ts +3 -1
- package/packages/bundled-features/src/tags/handlers/assign-tag.write.ts +5 -1
- package/packages/bundled-features/src/tags/web/tag-chip.tsx +2 -1
- package/packages/bundled-features/src/template-resolver/README.md +66 -8
- package/packages/bundled-features/src/template-resolver/api.ts +85 -9
- package/packages/bundled-features/src/template-resolver/changes.json +186 -0
- package/packages/bundled-features/src/template-resolver/compile-document-body.ts +278 -0
- package/packages/bundled-features/src/template-resolver/constants.ts +49 -11
- package/packages/bundled-features/src/template-resolver/document-sanitizer.ts +335 -0
- package/packages/bundled-features/src/template-resolver/document-schema.ts +345 -0
- package/packages/bundled-features/src/template-resolver/feature.ts +59 -6
- package/packages/bundled-features/src/template-resolver/handlers/bundle-import.ts +188 -0
- package/packages/bundled-features/src/template-resolver/handlers/collection-set.write.ts +30 -2
- package/packages/bundled-features/src/template-resolver/handlers/copy-locale.write.ts +160 -0
- package/packages/bundled-features/src/template-resolver/handlers/diff-to-system.query.ts +33 -0
- package/packages/bundled-features/src/template-resolver/handlers/discard-draft.write.ts +52 -0
- package/packages/bundled-features/src/template-resolver/handlers/export-bundle.query.ts +138 -0
- package/packages/bundled-features/src/template-resolver/handlers/find-by-id.query.ts +9 -6
- package/packages/bundled-features/src/template-resolver/handlers/import-bundle-system.write.ts +42 -0
- package/packages/bundled-features/src/template-resolver/handlers/import-bundle.write.ts +22 -0
- package/packages/bundled-features/src/template-resolver/handlers/layout-source.query.ts +151 -0
- package/packages/bundled-features/src/template-resolver/handlers/list.query.ts +19 -18
- package/packages/bundled-features/src/template-resolver/handlers/override-diff.ts +49 -0
- package/packages/bundled-features/src/template-resolver/handlers/overview.query.ts +323 -0
- package/packages/bundled-features/src/template-resolver/handlers/preview.query.ts +25 -0
- package/packages/bundled-features/src/template-resolver/handlers/reset-to-system.write.ts +58 -0
- package/packages/bundled-features/src/template-resolver/handlers/resource-link-pdf.write.ts +34 -0
- package/packages/bundled-features/src/template-resolver/handlers/resource-remove.write.ts +50 -0
- package/packages/bundled-features/src/template-resolver/handlers/resource-shared.ts +24 -0
- package/packages/bundled-features/src/template-resolver/handlers/resource-upload.write.ts +135 -0
- package/packages/bundled-features/src/template-resolver/handlers/send-test.write.ts +135 -0
- package/packages/bundled-features/src/template-resolver/handlers/set.write.ts +32 -3
- package/packages/bundled-features/src/template-resolver/handlers/shared.ts +7 -2
- package/packages/bundled-features/src/template-resolver/handlers/template-gates.ts +196 -0
- package/packages/bundled-features/src/template-resolver/handlers/template-write.ts +233 -0
- package/packages/bundled-features/src/template-resolver/handlers/tenant-overrides.query.ts +137 -0
- package/packages/bundled-features/src/template-resolver/handlers/toggle-status.write.ts +91 -17
- package/packages/bundled-features/src/template-resolver/handlers/upsert-system.write.ts +19 -71
- package/packages/bundled-features/src/template-resolver/handlers/upsert-tenant.write.ts +16 -77
- package/packages/bundled-features/src/template-resolver/handlers/usage.query.ts +31 -0
- package/packages/bundled-features/src/template-resolver/handlers/validate.query.ts +116 -0
- package/packages/bundled-features/src/template-resolver/index.ts +111 -0
- package/packages/bundled-features/src/template-resolver/layout-appearance.ts +40 -0
- package/packages/bundled-features/src/template-resolver/layout-regions.ts +206 -0
- package/packages/bundled-features/src/template-resolver/linked-resources.ts +102 -0
- package/packages/bundled-features/src/template-resolver/pdf-constants.ts +12 -0
- package/packages/bundled-features/src/template-resolver/preview-display-html.ts +36 -0
- package/packages/bundled-features/src/template-resolver/print-document.ts +58 -0
- package/packages/bundled-features/src/template-resolver/qualified-names.ts +34 -0
- package/packages/bundled-features/src/template-resolver/record-mapping.ts +26 -0
- package/packages/bundled-features/src/template-resolver/resource-image.ts +47 -0
- package/packages/bundled-features/src/template-resolver/seeding.ts +171 -20
- package/packages/bundled-features/src/template-resolver/store-template-resource.ts +416 -0
- package/packages/bundled-features/src/template-resolver/table.ts +27 -1
- package/packages/bundled-features/src/template-resolver/template-bundle.ts +79 -0
- package/packages/bundled-features/src/template-resolver/template-diff.ts +78 -0
- package/packages/bundled-features/src/template-resolver/template-draft.ts +106 -0
- package/packages/bundled-features/src/template-resolver/template-meta.ts +192 -0
- package/packages/bundled-features/src/template-resolver/template-preview.ts +676 -0
- package/packages/bundled-features/src/template-resolver/template-resources.ts +114 -0
- package/packages/bundled-features/src/template-resolver/template-usage.ts +68 -0
- package/packages/bundled-features/src/template-resolver/template-validation.ts +230 -0
- package/packages/bundled-features/src/template-resolver/testing.ts +5 -5
- package/packages/bundled-features/src/template-resolver/web/client-plugin.tsx +20 -13
- package/packages/bundled-features/src/template-resolver/web/document-editor-extensions.ts +215 -0
- package/packages/bundled-features/src/template-resolver/web/document-editor.tsx +37 -0
- package/packages/bundled-features/src/template-resolver/web/document-tiptap-editor.tsx +1065 -0
- package/packages/bundled-features/src/template-resolver/web/document-toolbar-blocks.ts +72 -0
- package/packages/bundled-features/src/template-resolver/web/editor-handle.ts +26 -0
- package/packages/bundled-features/src/template-resolver/web/i18n.ts +121 -0
- package/packages/bundled-features/src/template-resolver/web/index.ts +26 -0
- package/packages/bundled-features/src/template-resolver/web/placeholder-line-editor-lazy.tsx +36 -0
- package/packages/bundled-features/src/template-resolver/web/placeholder-line-editor.tsx +176 -0
- package/packages/bundled-features/src/template-resolver/web/placeholder-text.ts +37 -0
- package/packages/bundled-features/src/template-resolver/web/use-stable-by-key.ts +14 -0
- package/packages/bundled-features/src/tenant/handlers/team-list.query.ts +3 -2
- package/packages/bundled-features/src/tenant/seeding.ts +41 -7
- package/packages/bundled-features/src/tenant-lifecycle/boot-checks.ts +1 -1
- package/packages/bundled-features/src/tenant-lifecycle/handlers/cancel-destruction.write.ts +2 -1
- package/packages/bundled-features/src/tier-engine/changes.json +6 -0
- package/packages/bundled-features/src/tier-engine/feature.ts +11 -3
- package/packages/bundled-features/src/user/changes.json +6 -0
- package/packages/bundled-features/src/user/db/queries/stream-tenant-backfill.ts +4 -4
- package/packages/bundled-features/src/user/handlers/detail.query.ts +3 -5
- package/packages/bundled-features/src/user/resolve-display-names.ts +8 -15
- package/packages/bundled-features/src/user/roles-input-schema.ts +2 -2
- package/packages/bundled-features/src/user/seeding.ts +19 -5
- package/packages/bundled-features/src/user-data-rights/changes.json +13 -0
- package/packages/bundled-features/src/user-data-rights/db/queries/forget-cleanup.ts +2 -2
- package/packages/bundled-features/src/user-data-rights/email-templates.ts +5 -149
- package/packages/bundled-features/src/user-data-rights/feature.ts +72 -35
- package/packages/bundled-features/src/user-data-rights/handlers/cancel-deletion.write.ts +2 -1
- package/packages/bundled-features/src/user-data-rights/handlers/request-deletion.write.ts +5 -11
- package/packages/bundled-features/src/user-data-rights/lib/default-mailers.ts +134 -53
- package/packages/bundled-features/src/user-data-rights/lib/update-user-lifecycle.ts +2 -2
- package/packages/bundled-features/src/user-data-rights/mail-notifications.ts +157 -0
- package/packages/bundled-features/src/user-data-rights/run-forget-cleanup.ts +1 -1
- package/packages/bundled-features/src/user-data-rights/web/privacy-center-screen.tsx +2 -1
- package/packages/bundled-features/src/user-profile/changes.json +1 -1
- package/packages/bundled-features/src/user-profile/web/profile-screen.tsx +2 -1
- package/packages/bundled-features/src/waitlist/changes.json +7 -0
- package/packages/bundled-features/src/waitlist/feature.ts +17 -0
- package/packages/bundled-features/src/waitlist/handlers/submit.write.ts +4 -5
- package/packages/bundled-features/src/waitlist/mail.ts +69 -18
- package/packages/bundled-features/src/waitlist/options.ts +1 -1
- package/packages/bundled-features/src/workflow-runner/changes.json +6 -0
- package/packages/bundled-features/src/workflow-runner/event-trigger.ts +3 -3
- package/packages/bundled-features/src/workflow-runner/handlers/resume-run.write.ts +7 -1
- package/samples/apps/ui-walkthrough/package.json +11 -0
- package/samples/apps/ui-walkthrough/playwright.config.ts +40 -7
- package/samples/apps/ui-walkthrough/src/app/auth-constants.ts +9 -0
- package/samples/apps/ui-walkthrough/src/app/client.tsx +6 -0
- package/samples/apps/ui-walkthrough/src/app/seed-templates.ts +239 -0
- package/samples/apps/ui-walkthrough/src/app/server.ts +5 -1
- package/samples/apps/ui-walkthrough/src/app/shell.tsx +12 -1
- package/samples/apps/ui-walkthrough/src/app/template-seed-data.ts +570 -0
- package/samples/apps/ui-walkthrough/src/features/billing/feature.ts +72 -0
- package/samples/apps/ui-walkthrough/src/features/billing/i18n.ts +18 -0
- package/samples/apps/ui-walkthrough/src/features/billing/index.ts +2 -0
- package/samples/apps/ui-walkthrough/src/features/billing/web/index.ts +7 -0
- package/samples/apps/ui-walkthrough/src/run-config.ts +41 -1
- package/samples/apps/use-all-bundled/e2e-billing-gallery/gallery-features.ts +0 -2
- package/samples/apps/use-all-bundled/feature-manifest.json +118 -21
- package/samples/apps/use-all-bundled/kumiko/migrations/0045_template-resource-meta.rebuild.json +4 -0
- package/samples/apps/use-all-bundled/kumiko/migrations/0046_pdf-rendered.rebuild.json +4 -0
- package/samples/apps/use-all-bundled/package.json +2 -0
- package/samples/apps/use-all-bundled/playwright.config.ts +38 -3
- package/samples/apps/use-all-bundled/schema/generate.ts +13 -3
- package/samples/apps/use-all-bundled/schema/schema.generated.ts +3 -0
- package/samples/apps/use-all-bundled/scripts/check-coverage.ts +3 -0
- package/samples/apps/use-all-bundled/src/app/auth-constants.ts +3 -0
- package/samples/apps/use-all-bundled/src/app/client.tsx +4 -0
- package/samples/apps/use-all-bundled/src/app/seed-documents.ts +128 -0
- package/samples/apps/use-all-bundled/src/app/seed.ts +4 -0
- package/samples/apps/use-all-bundled/src/run-config.ts +11 -11
- package/samples/recipes/delivery-notifications/README.md +26 -25
- package/samples/recipes/delivery-notifications/package.json +2 -1
- package/samples/recipes/delivery-notifications/src/feature.ts +64 -58
- package/samples/recipes/seed-migration/README.md +1 -1
- package/packages/bundled-features/src/renderer-simple/changes.json +0 -27
- package/packages/bundled-features/src/renderer-simple/feature.ts +0 -66
- package/packages/bundled-features/src/renderer-simple/index.ts +0 -8
- package/packages/bundled-features/src/renderer-simple/mail-blocks.ts +0 -138
- package/packages/bundled-features/src/renderer-simple/resolve-variables.ts +0 -57
- package/packages/bundled-features/src/renderer-simple/simple-renderer.ts +0 -304
|
@@ -4,8 +4,11 @@ import {
|
|
|
4
4
|
defineWriteHandler,
|
|
5
5
|
type TenantId,
|
|
6
6
|
} from "@cosmicdrift/kumiko-framework/engine";
|
|
7
|
+
import { ValidationError, writeFailure } from "@cosmicdrift/kumiko-framework/errors";
|
|
7
8
|
import * as z from "zod";
|
|
8
9
|
import { requireForTenant } from "../../shared/index.js";
|
|
10
|
+
import { COLLECTION_REFUSED_KINDS } from "../constants.js";
|
|
11
|
+
import { documentViolationsToFieldIssues, sanitizeTemplateBody } from "../document-sanitizer.js";
|
|
9
12
|
import {
|
|
10
13
|
type CollectionEntryRow,
|
|
11
14
|
collectionStore,
|
|
@@ -40,6 +43,20 @@ export function makeCollectionSetWrite(collection: ContentCollectionDefinition)
|
|
|
40
43
|
? `Creates or overwrites the caller's own "${collection.id}" entry at a slug and locale, live on save with no draft stage; it always writes the acting user's entry, never another user's.`
|
|
41
44
|
: `Creates or overwrites the tenant-wide "${collection.id}" entry at a slug and locale, live on save with no draft stage; the change is visible to everyone who may reach this shared collection.`,
|
|
42
45
|
handler: async (event, ctx) => {
|
|
46
|
+
if (COLLECTION_REFUSED_KINDS.includes(collection.kind)) {
|
|
47
|
+
return writeFailure(
|
|
48
|
+
new ValidationError({
|
|
49
|
+
fields: [
|
|
50
|
+
{
|
|
51
|
+
path: "kind",
|
|
52
|
+
code: "kind_not_allowed",
|
|
53
|
+
i18nKey: "templateResolver.errors.collection.kindNotAllowed",
|
|
54
|
+
params: { detail: collection.kind },
|
|
55
|
+
},
|
|
56
|
+
],
|
|
57
|
+
}),
|
|
58
|
+
);
|
|
59
|
+
}
|
|
43
60
|
const override = event.payload.tenantIdOverride;
|
|
44
61
|
// @cast-boundary engine-payload — override is a zod-validated string, the
|
|
45
62
|
// user's own tenantId is already TenantId-branded.
|
|
@@ -50,6 +67,17 @@ export function makeCollectionSetWrite(collection: ContentCollectionDefinition)
|
|
|
50
67
|
// someone else's signature.
|
|
51
68
|
const owner = store.scopeOf(event.user);
|
|
52
69
|
|
|
70
|
+
const body = sanitizeTemplateBody({
|
|
71
|
+
kind: collection.kind,
|
|
72
|
+
contentFormat: event.payload.contentFormat,
|
|
73
|
+
content: event.payload.content,
|
|
74
|
+
});
|
|
75
|
+
if (!body.ok) {
|
|
76
|
+
return writeFailure(
|
|
77
|
+
new ValidationError({ fields: documentViolationsToFieldIssues(body.violations) }),
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
|
|
53
81
|
const existing = await fetchOne<CollectionEntryRow>(db, store.table, {
|
|
54
82
|
tenantId,
|
|
55
83
|
slug: event.payload.slug,
|
|
@@ -68,7 +96,7 @@ export function makeCollectionSetWrite(collection: ContentCollectionDefinition)
|
|
|
68
96
|
// the template upserts put on the row.
|
|
69
97
|
changes: {
|
|
70
98
|
title: event.payload.title,
|
|
71
|
-
content:
|
|
99
|
+
content: body.content,
|
|
72
100
|
contentFormat: event.payload.contentFormat,
|
|
73
101
|
folder: event.payload.folder ?? null,
|
|
74
102
|
},
|
|
@@ -91,7 +119,7 @@ export function makeCollectionSetWrite(collection: ContentCollectionDefinition)
|
|
|
91
119
|
kind: collection.kind,
|
|
92
120
|
locale: event.payload.locale,
|
|
93
121
|
title: event.payload.title,
|
|
94
|
-
content:
|
|
122
|
+
content: body.content,
|
|
95
123
|
contentFormat: event.payload.contentFormat,
|
|
96
124
|
folder: event.payload.folder ?? null,
|
|
97
125
|
...store.createDefaults(tenantId),
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import { fetchOne } from "@cosmicdrift/kumiko-framework/bun-db";
|
|
2
|
+
import {
|
|
3
|
+
defineWriteHandler,
|
|
4
|
+
type HandlerContext,
|
|
5
|
+
SYSTEM_TENANT_ID,
|
|
6
|
+
} from "@cosmicdrift/kumiko-framework/engine";
|
|
7
|
+
import {
|
|
8
|
+
AccessDeniedError,
|
|
9
|
+
NotFoundError,
|
|
10
|
+
ValidationError,
|
|
11
|
+
writeFailure,
|
|
12
|
+
} from "@cosmicdrift/kumiko-framework/errors";
|
|
13
|
+
import { isOneOf } from "@cosmicdrift/kumiko-types/type-guards";
|
|
14
|
+
import * as z from "zod";
|
|
15
|
+
import { requireForTenant } from "../../shared/index.js";
|
|
16
|
+
import { CONTENT_FORMATS } from "../constants.js";
|
|
17
|
+
import { parseLinkedResourceEntries } from "../linked-resources.js";
|
|
18
|
+
import { TemplateResolverErrors } from "../qualified-names.js";
|
|
19
|
+
import { type TemplateResourceRow, templateResourcesTable } from "../table.js";
|
|
20
|
+
import { toWorkingRow } from "../template-draft.js";
|
|
21
|
+
import { attachTemplateResources, duplicateLinkedResourceFiles } from "../template-resources.js";
|
|
22
|
+
import { resolveTemplateVariables } from "../template-validation.js";
|
|
23
|
+
import { targetExistsIssue } from "./bundle-import.js";
|
|
24
|
+
import { kindSchema, localeSchema, templateSlugSchema } from "./shared.js";
|
|
25
|
+
import { parseStoredJson, storedRecord } from "./template-gates.js";
|
|
26
|
+
import { refuseTenantScopeUnderSystemTenant, writeTemplateRow } from "./template-write.js";
|
|
27
|
+
|
|
28
|
+
type CopyScope = "tenant" | "system";
|
|
29
|
+
type WriteCtx = HandlerContext;
|
|
30
|
+
|
|
31
|
+
function readDbFor(ctx: WriteCtx, scope: CopyScope) {
|
|
32
|
+
return scope === "system" ? requireForTenant(ctx, SYSTEM_TENANT_ID).db : ctx.db;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// A tenant copy may start from the system default; a system copy only from system rows.
|
|
36
|
+
async function findCopySource(
|
|
37
|
+
ctx: WriteCtx,
|
|
38
|
+
where: {
|
|
39
|
+
scope: CopyScope;
|
|
40
|
+
tenantId: string;
|
|
41
|
+
slug: string;
|
|
42
|
+
kind: string;
|
|
43
|
+
locale: string;
|
|
44
|
+
},
|
|
45
|
+
) {
|
|
46
|
+
const { scope, tenantId, slug, kind, locale } = where;
|
|
47
|
+
const readDb = readDbFor(ctx, scope);
|
|
48
|
+
const own = await fetchOne<TemplateResourceRow>(readDb, templateResourcesTable, {
|
|
49
|
+
tenantId,
|
|
50
|
+
slug,
|
|
51
|
+
kind,
|
|
52
|
+
locale,
|
|
53
|
+
});
|
|
54
|
+
// An archived tenant row is not an active source: the system default applies instead.
|
|
55
|
+
if (scope === "system" || (own && own.status !== "archived")) return own;
|
|
56
|
+
return fetchOne<TemplateResourceRow>(readDb, templateResourcesTable, {
|
|
57
|
+
tenantId: SYSTEM_TENANT_ID,
|
|
58
|
+
slug,
|
|
59
|
+
kind,
|
|
60
|
+
locale,
|
|
61
|
+
status: "active",
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export const copyLocaleWrite = defineWriteHandler({
|
|
66
|
+
name: "copy-locale",
|
|
67
|
+
schema: z.object({
|
|
68
|
+
slug: templateSlugSchema,
|
|
69
|
+
kind: kindSchema,
|
|
70
|
+
fromLocale: localeSchema,
|
|
71
|
+
toLocale: localeSchema,
|
|
72
|
+
overwrite: z.boolean().default(false),
|
|
73
|
+
/** "system" copies between system-default rows; SystemAdmin only. */
|
|
74
|
+
scope: z.enum(["tenant", "system"]).default("tenant"),
|
|
75
|
+
}),
|
|
76
|
+
access: { roles: ["TenantAdmin", "SystemAdmin"] },
|
|
77
|
+
description:
|
|
78
|
+
"Copies the text of one locale of a template into another locale as a draft override of the caller's tenant, taking the tenant's own row or else the system default as the source; use it as the starting point for a translation. It refuses an existing target unless overwrite is set. With scope system (SystemAdmin only) both source and target are system-default rows and the target lands as a system draft; any other caller gets a 403.",
|
|
79
|
+
handler: async (event, ctx) => {
|
|
80
|
+
const { slug, kind, fromLocale, toLocale, overwrite, scope } = event.payload;
|
|
81
|
+
if (scope === "system" && !event.user.roles.includes("SystemAdmin")) {
|
|
82
|
+
return writeFailure(
|
|
83
|
+
new AccessDeniedError({
|
|
84
|
+
i18nKey: "errors.access.denied",
|
|
85
|
+
details: { reason: TemplateResolverErrors.systemAdminRequired },
|
|
86
|
+
}),
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
const tenantId = scope === "system" ? SYSTEM_TENANT_ID : event.user.tenantId;
|
|
90
|
+
const refused = refuseTenantScopeUnderSystemTenant(scope, tenantId);
|
|
91
|
+
if (refused !== null) return refused;
|
|
92
|
+
|
|
93
|
+
const readDb = readDbFor(ctx, scope);
|
|
94
|
+
const storedSource = await findCopySource(ctx, {
|
|
95
|
+
scope,
|
|
96
|
+
tenantId,
|
|
97
|
+
slug,
|
|
98
|
+
kind,
|
|
99
|
+
locale: fromLocale,
|
|
100
|
+
});
|
|
101
|
+
const source = storedSource && toWorkingRow(storedSource, tenantId);
|
|
102
|
+
if (!source || !isOneOf(CONTENT_FORMATS, source.contentFormat)) {
|
|
103
|
+
return writeFailure(new NotFoundError("template-resource", `${slug}/${fromLocale}`));
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (!overwrite) {
|
|
107
|
+
const target = await fetchOne<TemplateResourceRow>(readDb, templateResourcesTable, {
|
|
108
|
+
tenantId,
|
|
109
|
+
slug,
|
|
110
|
+
kind,
|
|
111
|
+
locale: toLocale,
|
|
112
|
+
});
|
|
113
|
+
if (target) {
|
|
114
|
+
return writeFailure(new ValidationError({ fields: [targetExistsIssue(toLocale)] }));
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const variableSchema = storedRecord(source.variableSchema);
|
|
119
|
+
const written = await writeTemplateRow({
|
|
120
|
+
ctx,
|
|
121
|
+
user: event.user,
|
|
122
|
+
tenantId,
|
|
123
|
+
scope,
|
|
124
|
+
status: "draft",
|
|
125
|
+
variables: resolveTemplateVariables(ctx.registry, slug, variableSchema),
|
|
126
|
+
payload: {
|
|
127
|
+
slug,
|
|
128
|
+
kind,
|
|
129
|
+
locale: toLocale,
|
|
130
|
+
content: source.content,
|
|
131
|
+
contentFormat: source.contentFormat,
|
|
132
|
+
variableSchema,
|
|
133
|
+
meta: parseStoredJson(source.meta),
|
|
134
|
+
title: source.title,
|
|
135
|
+
folder: source.folder,
|
|
136
|
+
},
|
|
137
|
+
});
|
|
138
|
+
if (!written.isSuccess) return written;
|
|
139
|
+
const sourceResources = parseLinkedResourceEntries(source.linkedResources);
|
|
140
|
+
// A system source's files belong to the system tenant: the tenant gets its own copies so
|
|
141
|
+
// previews, exports and the tenant's override keep working independent of the system row.
|
|
142
|
+
const resources =
|
|
143
|
+
source.tenantId === tenantId
|
|
144
|
+
? { isSuccess: true as const, data: sourceResources }
|
|
145
|
+
: await duplicateLinkedResourceFiles(ctx, {
|
|
146
|
+
user: event.user,
|
|
147
|
+
tenantId,
|
|
148
|
+
resources: sourceResources,
|
|
149
|
+
});
|
|
150
|
+
if (!resources.isSuccess) return resources;
|
|
151
|
+
const attached = await attachTemplateResources(ctx, {
|
|
152
|
+
user: event.user,
|
|
153
|
+
tenantId,
|
|
154
|
+
templateId: written.data.id,
|
|
155
|
+
resources: resources.data,
|
|
156
|
+
});
|
|
157
|
+
if (!attached.isSuccess) return attached;
|
|
158
|
+
return written;
|
|
159
|
+
},
|
|
160
|
+
});
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { fetchOne } from "@cosmicdrift/kumiko-framework/bun-db";
|
|
2
|
+
import { defineQueryHandler, SYSTEM_TENANT_ID } from "@cosmicdrift/kumiko-framework/engine";
|
|
3
|
+
import { NotFoundError } from "@cosmicdrift/kumiko-framework/errors";
|
|
4
|
+
import * as z from "zod";
|
|
5
|
+
import { type TemplateResourceRow, templateResourcesTable } from "../table.js";
|
|
6
|
+
import { isHiddenSystemDraft } from "../template-draft.js";
|
|
7
|
+
import { buildOverrideDiff } from "./override-diff.js";
|
|
8
|
+
|
|
9
|
+
export const diffToSystemQuery = defineQueryHandler({
|
|
10
|
+
name: "diff-to-system",
|
|
11
|
+
schema: z.object({ id: z.string().min(1) }),
|
|
12
|
+
access: { roles: ["TenantAdmin", "SystemAdmin"] },
|
|
13
|
+
description:
|
|
14
|
+
"Compares one tenant override with the system template of the same slug, kind and locale block by block and lists the changed meta settings; use it to show what an override changes before resetting it to the system version.",
|
|
15
|
+
handler: async (query, ctx) => {
|
|
16
|
+
const tenantRow = await fetchOne<TemplateResourceRow>(ctx.db, templateResourcesTable, {
|
|
17
|
+
id: query.payload.id,
|
|
18
|
+
});
|
|
19
|
+
if (!tenantRow || tenantRow.tenantId !== query.user.tenantId || tenantRow.scope !== "tenant") {
|
|
20
|
+
throw new NotFoundError("template-resource", query.payload.id);
|
|
21
|
+
}
|
|
22
|
+
const systemRow = await fetchOne<TemplateResourceRow>(ctx.db, templateResourcesTable, {
|
|
23
|
+
tenantId: SYSTEM_TENANT_ID,
|
|
24
|
+
slug: tenantRow.slug,
|
|
25
|
+
kind: tenantRow.kind,
|
|
26
|
+
locale: tenantRow.locale,
|
|
27
|
+
});
|
|
28
|
+
if (!systemRow || isHiddenSystemDraft(query.user, systemRow))
|
|
29
|
+
throw new NotFoundError("template-resource", tenantRow.slug);
|
|
30
|
+
|
|
31
|
+
return buildOverrideDiff(tenantRow, systemRow);
|
|
32
|
+
},
|
|
33
|
+
});
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { defineWriteHandler } from "@cosmicdrift/kumiko-framework/engine";
|
|
2
|
+
import { NotFoundError, ValidationError, writeFailure } from "@cosmicdrift/kumiko-framework/errors";
|
|
3
|
+
import * as z from "zod";
|
|
4
|
+
import { TemplateResolverErrors } from "../qualified-names.js";
|
|
5
|
+
import { executor } from "./shared.js";
|
|
6
|
+
import { loadTargetRow, type StatusTarget, writeAccessFor } from "./toggle-status.write.js";
|
|
7
|
+
|
|
8
|
+
function createDiscardDraftHandler(name: string, target: StatusTarget, description: string) {
|
|
9
|
+
return defineWriteHandler({
|
|
10
|
+
name,
|
|
11
|
+
schema: z.object({ id: z.string().min(1) }),
|
|
12
|
+
access: writeAccessFor(target),
|
|
13
|
+
...(target === "system" ? { agent: { risk: "high" as const } } : {}),
|
|
14
|
+
description,
|
|
15
|
+
handler: async (event, ctx) => {
|
|
16
|
+
const { db, scoped, row } = await loadTargetRow(ctx, target, event.user, event.payload.id);
|
|
17
|
+
if (!row) return writeFailure(new NotFoundError("template-resource", event.payload.id));
|
|
18
|
+
if (row.draft === null) {
|
|
19
|
+
return writeFailure(
|
|
20
|
+
new ValidationError({
|
|
21
|
+
fields: [
|
|
22
|
+
{
|
|
23
|
+
path: "id",
|
|
24
|
+
code: TemplateResolverErrors.noDraft,
|
|
25
|
+
i18nKey: "templateResolver.errors.noDraft",
|
|
26
|
+
},
|
|
27
|
+
],
|
|
28
|
+
}),
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
const result = await executor.update(
|
|
32
|
+
{ id: row.id, version: row.version, changes: { draft: null } },
|
|
33
|
+
event.user,
|
|
34
|
+
db,
|
|
35
|
+
scoped ? { streamTenantId: scoped.streamTenantId } : undefined,
|
|
36
|
+
);
|
|
37
|
+
if (!result.isSuccess) return result;
|
|
38
|
+
return { isSuccess: true as const, data: { id: String(row.id), discarded: true } };
|
|
39
|
+
},
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export const discardDraftWrite = createDiscardDraftHandler(
|
|
44
|
+
"discard-draft",
|
|
45
|
+
"own",
|
|
46
|
+
"Throws away the open draft of one template resource of the caller's tenant so the editor shows the active version again; the served version is untouched. Refused with no_draft when there is no draft.",
|
|
47
|
+
);
|
|
48
|
+
export const discardDraftSystemWrite = createDiscardDraftHandler(
|
|
49
|
+
"discard-draft-system",
|
|
50
|
+
"system",
|
|
51
|
+
"Throws away the open draft of one system-default template; the served version stays. SystemAdmin only, refused with no_draft when there is no draft.",
|
|
52
|
+
);
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { isDeepStrictEqual } from "node:util";
|
|
2
|
+
import { selectMany } from "@cosmicdrift/kumiko-framework/bun-db";
|
|
3
|
+
import { defineQueryHandler, SYSTEM_TENANT_ID } from "@cosmicdrift/kumiko-framework/engine";
|
|
4
|
+
import { NotFoundError, ValidationError } from "@cosmicdrift/kumiko-framework/errors";
|
|
5
|
+
import * as z from "zod";
|
|
6
|
+
import {
|
|
7
|
+
isFileAttachmentEntry,
|
|
8
|
+
isPdfAttachmentLink,
|
|
9
|
+
type LinkedResourceEntry,
|
|
10
|
+
parseLinkedResourceEntries,
|
|
11
|
+
} from "../linked-resources.js";
|
|
12
|
+
import { TemplateResolverErrors } from "../qualified-names.js";
|
|
13
|
+
import { type TemplateResourceRow, templateResourcesTable } from "../table.js";
|
|
14
|
+
import type { TemplateBundle } from "../template-bundle.js";
|
|
15
|
+
import { readTemplateResource } from "../template-resources.js";
|
|
16
|
+
import { kindSchema, templateSlugSchema } from "./shared.js";
|
|
17
|
+
import { parseStoredJson, storedRecord } from "./template-gates.js";
|
|
18
|
+
|
|
19
|
+
// The files live in the system tenant's key space; readTemplateResource only
|
|
20
|
+
// serves the caller's own tenant, so anyone else would end in a misleading NotFound.
|
|
21
|
+
function assertSystemFilesReadable(
|
|
22
|
+
scope: "tenant" | "system",
|
|
23
|
+
entries: Iterable<LinkedResourceEntry>,
|
|
24
|
+
callerTenantId: string,
|
|
25
|
+
): void {
|
|
26
|
+
// skip: tenant exports read the caller's own files.
|
|
27
|
+
if (scope === "tenant") return;
|
|
28
|
+
// skip: the system tenant reads its own key space.
|
|
29
|
+
if (callerTenantId === SYSTEM_TENANT_ID) return;
|
|
30
|
+
const hasFileResources = [...entries].some((entry) => !isPdfAttachmentLink(entry));
|
|
31
|
+
// skip: PDF attachment links carry no file to read.
|
|
32
|
+
if (!hasFileResources) return;
|
|
33
|
+
throw new ValidationError({
|
|
34
|
+
fields: [
|
|
35
|
+
{
|
|
36
|
+
path: "scope",
|
|
37
|
+
code: "system_resources_need_system_tenant",
|
|
38
|
+
i18nKey: "templateResolver.errors.template.systemResourcesNeedSystemTenant",
|
|
39
|
+
},
|
|
40
|
+
],
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// A bundle carries one contentFormat and one variableSchema for all locales; importing it would
|
|
45
|
+
// relabel the rows that differ from the first.
|
|
46
|
+
function assertLocalesShareFormat(rows: readonly TemplateResourceRow[]): void {
|
|
47
|
+
const [first, ...rest] = rows;
|
|
48
|
+
// skip: a bundle without rows has nothing to compare
|
|
49
|
+
if (first === undefined) return;
|
|
50
|
+
const firstSchema = storedRecord(first.variableSchema);
|
|
51
|
+
const differing = rest.find(
|
|
52
|
+
(row) =>
|
|
53
|
+
row.contentFormat !== first.contentFormat ||
|
|
54
|
+
!isDeepStrictEqual(storedRecord(row.variableSchema), firstSchema),
|
|
55
|
+
);
|
|
56
|
+
// skip: all locales agree
|
|
57
|
+
if (differing === undefined) return;
|
|
58
|
+
throw new ValidationError({
|
|
59
|
+
fields: [
|
|
60
|
+
{
|
|
61
|
+
path: "slug",
|
|
62
|
+
code: TemplateResolverErrors.bundleLocalesDiffer,
|
|
63
|
+
i18nKey: "templateResolver.errors.template.bundleLocalesDiffer",
|
|
64
|
+
},
|
|
65
|
+
],
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export const exportBundleQuery = defineQueryHandler({
|
|
70
|
+
name: "export-bundle",
|
|
71
|
+
schema: z.object({
|
|
72
|
+
slug: templateSlugSchema,
|
|
73
|
+
kind: kindSchema,
|
|
74
|
+
scope: z.enum(["tenant", "system"]).default("tenant"),
|
|
75
|
+
}),
|
|
76
|
+
access: { roles: ["TenantAdmin", "SystemAdmin"] },
|
|
77
|
+
description:
|
|
78
|
+
"Exports every locale of one template (the caller's own rows, or the active system rows) together with its linked resource files as a self-contained bundle that import-bundle can read back, for example to move a template between tenants or environments.",
|
|
79
|
+
handler: async (query, ctx) => {
|
|
80
|
+
const { slug, kind, scope } = query.payload;
|
|
81
|
+
const found = await selectMany<TemplateResourceRow>(
|
|
82
|
+
ctx.db,
|
|
83
|
+
templateResourcesTable,
|
|
84
|
+
scope === "system"
|
|
85
|
+
? { tenantId: SYSTEM_TENANT_ID, slug, kind, scope: "system", status: "active" }
|
|
86
|
+
: { tenantId: query.user.tenantId, slug, kind, scope: "tenant" },
|
|
87
|
+
);
|
|
88
|
+
const rows = found
|
|
89
|
+
.filter((row) => row.status !== "archived")
|
|
90
|
+
.sort((a, b) => a.locale.localeCompare(b.locale));
|
|
91
|
+
const first = rows[0];
|
|
92
|
+
if (!first) throw new NotFoundError("template-resource", slug);
|
|
93
|
+
assertLocalesShareFormat(rows);
|
|
94
|
+
|
|
95
|
+
const locales: Record<string, { body: string; meta?: unknown }> = {};
|
|
96
|
+
const entryByKey = new Map<string, LinkedResourceEntry>();
|
|
97
|
+
for (const row of rows) {
|
|
98
|
+
const meta = parseStoredJson(row.meta);
|
|
99
|
+
locales[row.locale] = { body: row.content ?? "", ...(meta === undefined ? {} : { meta }) };
|
|
100
|
+
for (const [key, entry] of Object.entries(parseLinkedResourceEntries(row.linkedResources))) {
|
|
101
|
+
if (!entryByKey.has(key)) entryByKey.set(key, entry);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
assertSystemFilesReadable(scope, entryByKey.values(), query.user.tenantId);
|
|
106
|
+
|
|
107
|
+
const resources: TemplateBundle["resources"] = {};
|
|
108
|
+
const pdfAttachments: TemplateBundle["pdfAttachments"] = {};
|
|
109
|
+
for (const [key, entry] of entryByKey) {
|
|
110
|
+
if (isPdfAttachmentLink(entry)) {
|
|
111
|
+
pdfAttachments[key] = { templateSlug: entry.templateSlug, fileName: entry.fileName };
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
const attachment = isFileAttachmentEntry(entry);
|
|
115
|
+
const file = await readTemplateResource(
|
|
116
|
+
{ db: ctx.db, files: ctx.files, user: query.user },
|
|
117
|
+
attachment ? entry.fileRefId : entry,
|
|
118
|
+
);
|
|
119
|
+
if (!file) throw new NotFoundError("template-resource-file", key);
|
|
120
|
+
resources[key] = {
|
|
121
|
+
fileName: attachment ? entry.fileName : file.fileName,
|
|
122
|
+
contentType: file.contentType,
|
|
123
|
+
base64: Buffer.from(file.bytes).toString("base64"),
|
|
124
|
+
...(attachment && { purpose: "attachment" as const }),
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return {
|
|
129
|
+
slug,
|
|
130
|
+
kind,
|
|
131
|
+
contentFormat: first.contentFormat,
|
|
132
|
+
variableSchema: storedRecord(first.variableSchema),
|
|
133
|
+
locales,
|
|
134
|
+
resources,
|
|
135
|
+
pdfAttachments,
|
|
136
|
+
};
|
|
137
|
+
},
|
|
138
|
+
});
|
|
@@ -2,27 +2,28 @@ import { fetchOne } from "@cosmicdrift/kumiko-framework/bun-db";
|
|
|
2
2
|
import { defineQueryHandler } from "@cosmicdrift/kumiko-framework/engine";
|
|
3
3
|
import * as z from "zod";
|
|
4
4
|
import { type TemplateResourceRow, templateResourcesTable } from "../table.js";
|
|
5
|
+
import { draftViewerTenantId, isHiddenSystemDraft, toWorkingRow } from "../template-draft.js";
|
|
5
6
|
|
|
6
7
|
// Admin-Lookup für UI-Edit-Flow. Returnt das raw Template inkl. draft/
|
|
7
8
|
// archived Status. Tenant-Isolation: User sieht nur Templates des
|
|
8
9
|
// eigenen Tenants oder SYSTEM_TENANT (system-defaults sind public-
|
|
9
|
-
// readable).
|
|
10
|
+
// readable); ctx.db is tenant-scoped for every role, so the check below is defense in depth. Anonymous bleibt aus — Public-
|
|
10
11
|
// Read geht über resolveTemplate aus der API, nicht hier.
|
|
11
12
|
export const findByIdQuery = defineQueryHandler({
|
|
12
13
|
name: "find-by-id",
|
|
13
14
|
schema: z.object({ id: z.string().min(1) }),
|
|
14
15
|
access: { roles: ["TenantAdmin", "SystemAdmin", "User"] },
|
|
15
16
|
description:
|
|
16
|
-
"Reads one template resource by row id with
|
|
17
|
+
"Reads the version of one template resource an editor works on, by row id: the open draft if there is one (status draft), else the stored version, with content, variable schema and scope, but only when it belongs to the caller's tenant or is a served system default (a never-served system draft is visible to a SystemAdmin only); `active` is the version number and since-time the resolver serves, null when none is served. Use it to load a template into the admin editor.",
|
|
17
18
|
handler: async (query, ctx) => {
|
|
18
|
-
const
|
|
19
|
+
const stored = await fetchOne<TemplateResourceRow>(ctx.db, templateResourcesTable, {
|
|
19
20
|
id: query.payload.id,
|
|
20
21
|
});
|
|
21
|
-
if (!
|
|
22
|
-
const
|
|
22
|
+
if (!stored || isHiddenSystemDraft(query.user, stored)) return null;
|
|
23
|
+
const row = toWorkingRow(stored, draftViewerTenantId(query.user, stored));
|
|
23
24
|
const isOwnTenant = row.tenantId === query.user.tenantId;
|
|
24
25
|
const isSystemTemplate = row.scope === "system";
|
|
25
|
-
if (!
|
|
26
|
+
if (!isOwnTenant && !isSystemTemplate) return null;
|
|
26
27
|
|
|
27
28
|
return {
|
|
28
29
|
id: String(row.id),
|
|
@@ -35,10 +36,12 @@ export const findByIdQuery = defineQueryHandler({
|
|
|
35
36
|
contentFormat: row.contentFormat,
|
|
36
37
|
variableSchema: row.variableSchema,
|
|
37
38
|
linkedResources: row.linkedResources,
|
|
39
|
+
meta: row.meta,
|
|
38
40
|
scope: row.scope,
|
|
39
41
|
parentTemplateId: row.parentTemplateId,
|
|
40
42
|
status: row.status,
|
|
41
43
|
modifiedAt: row.modifiedAt,
|
|
44
|
+
active: row.active,
|
|
42
45
|
};
|
|
43
46
|
},
|
|
44
47
|
});
|
package/packages/bundled-features/src/template-resolver/handlers/import-bundle-system.write.ts
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { defineWriteHandler, SYSTEM_TENANT_ID } from "@cosmicdrift/kumiko-framework/engine";
|
|
2
|
+
import { ValidationError, writeFailure } from "@cosmicdrift/kumiko-framework/errors";
|
|
3
|
+
import * as z from "zod";
|
|
4
|
+
import { templateBundleSchema } from "../template-bundle.js";
|
|
5
|
+
import { importTemplateBundle } from "./bundle-import.js";
|
|
6
|
+
|
|
7
|
+
export const importBundleSystemWrite = defineWriteHandler({
|
|
8
|
+
name: "import-bundle-system",
|
|
9
|
+
schema: z.object({ bundle: templateBundleSchema }),
|
|
10
|
+
access: { roles: ["SystemAdmin"] },
|
|
11
|
+
// A system template change reaches every tenant without an override.
|
|
12
|
+
agent: { risk: "high" as const },
|
|
13
|
+
description:
|
|
14
|
+
"Imports a template bundle as system-default templates, active straight away and overwriting existing system rows; SystemAdmin only. Bundles with resource files need a caller in the system tenant, because the files are stored in the caller's file space.",
|
|
15
|
+
handler: (event, ctx) => {
|
|
16
|
+
const { bundle } = event.payload;
|
|
17
|
+
if (Object.keys(bundle.resources).length > 0 && event.user.tenantId !== SYSTEM_TENANT_ID) {
|
|
18
|
+
return Promise.resolve(
|
|
19
|
+
writeFailure(
|
|
20
|
+
new ValidationError({
|
|
21
|
+
fields: [
|
|
22
|
+
{
|
|
23
|
+
path: "bundle.resources",
|
|
24
|
+
code: "system_resources_need_system_tenant",
|
|
25
|
+
i18nKey: "templateResolver.errors.template.systemResourcesNeedSystemTenant",
|
|
26
|
+
},
|
|
27
|
+
],
|
|
28
|
+
}),
|
|
29
|
+
),
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
return importTemplateBundle({
|
|
33
|
+
ctx,
|
|
34
|
+
user: event.user,
|
|
35
|
+
tenantId: SYSTEM_TENANT_ID,
|
|
36
|
+
scope: "system",
|
|
37
|
+
status: "active",
|
|
38
|
+
bundle,
|
|
39
|
+
overwrite: true,
|
|
40
|
+
});
|
|
41
|
+
},
|
|
42
|
+
});
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { defineWriteHandler } from "@cosmicdrift/kumiko-framework/engine";
|
|
2
|
+
import * as z from "zod";
|
|
3
|
+
import { templateBundleSchema } from "../template-bundle.js";
|
|
4
|
+
import { importTemplateBundle } from "./bundle-import.js";
|
|
5
|
+
|
|
6
|
+
export const importBundleWrite = defineWriteHandler({
|
|
7
|
+
name: "import-bundle",
|
|
8
|
+
schema: z.object({ bundle: templateBundleSchema, overwrite: z.boolean().default(false) }),
|
|
9
|
+
access: { roles: ["TenantAdmin", "SystemAdmin"] },
|
|
10
|
+
description:
|
|
11
|
+
"Imports a template bundle (all locales plus resource files, as produced by export-bundle) into the caller's tenant as drafts; it refuses a locale that already has an override unless overwrite is set, and publish makes the drafts live.",
|
|
12
|
+
handler: (event, ctx) =>
|
|
13
|
+
importTemplateBundle({
|
|
14
|
+
ctx,
|
|
15
|
+
user: event.user,
|
|
16
|
+
tenantId: event.user.tenantId,
|
|
17
|
+
scope: "tenant",
|
|
18
|
+
status: "draft",
|
|
19
|
+
bundle: event.payload.bundle,
|
|
20
|
+
overwrite: event.payload.overwrite,
|
|
21
|
+
}),
|
|
22
|
+
});
|