@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
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
|
|
19
19
|
import { createTransport, type Transporter } from "nodemailer";
|
|
20
20
|
import addressparser from "nodemailer/lib/addressparser";
|
|
21
|
-
import type { EmailMessage, EmailTransport } from "./types.js";
|
|
21
|
+
import type { EmailAttachment, EmailMessage, EmailTransport } from "./types.js";
|
|
22
22
|
|
|
23
23
|
export type SmtpTransportOptions = {
|
|
24
24
|
/** SMTP-Server-Host (z.B. "smtp.gmail.com", "in-v3.mailjet.com",
|
|
@@ -74,6 +74,17 @@ function fromHeader(
|
|
|
74
74
|
return parsed?.address ? { name, address: parsed.address } : address;
|
|
75
75
|
}
|
|
76
76
|
|
|
77
|
+
function toNodemailerAttachment(attachment: EmailAttachment) {
|
|
78
|
+
const base = {
|
|
79
|
+
filename: attachment.fileName,
|
|
80
|
+
content: Buffer.from(attachment.content),
|
|
81
|
+
contentType: attachment.contentType,
|
|
82
|
+
};
|
|
83
|
+
return attachment.kind === "inline"
|
|
84
|
+
? { ...base, cid: attachment.cid, contentDisposition: "inline" as const }
|
|
85
|
+
: base;
|
|
86
|
+
}
|
|
87
|
+
|
|
77
88
|
export function createSmtpTransport(options: SmtpTransportOptions): EmailTransport {
|
|
78
89
|
const transporter: Transporter = createTransport({
|
|
79
90
|
host: options.host,
|
|
@@ -95,6 +106,9 @@ export function createSmtpTransport(options: SmtpTransportOptions): EmailTranspo
|
|
|
95
106
|
...(message.text !== undefined && { text: message.text }),
|
|
96
107
|
...(message.replyTo && { replyTo: message.replyTo }),
|
|
97
108
|
...(message.headers && { headers: message.headers }),
|
|
109
|
+
...(message.attachments && {
|
|
110
|
+
attachments: message.attachments.map(toNodemailerAttachment),
|
|
111
|
+
}),
|
|
98
112
|
});
|
|
99
113
|
},
|
|
100
114
|
};
|
|
@@ -1,3 +1,24 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
EmailAttachmentRef,
|
|
3
|
+
FileAttachmentRef,
|
|
4
|
+
InlineAttachmentRef,
|
|
5
|
+
RenderedPdfAttachmentRef,
|
|
6
|
+
} from "../delivery/index.js";
|
|
7
|
+
|
|
8
|
+
export type { EmailAttachmentRef };
|
|
9
|
+
|
|
10
|
+
// What a transport receives: the reference plus the bytes the send job read. A rendered
|
|
11
|
+
// PDF keeps no variables here, they may be personal data and the transport has no use for them.
|
|
12
|
+
export type EmailAttachment = (
|
|
13
|
+
| InlineAttachmentRef
|
|
14
|
+
| FileAttachmentRef
|
|
15
|
+
| Pick<RenderedPdfAttachmentRef, "kind" | "templateSlug">
|
|
16
|
+
) & {
|
|
17
|
+
readonly content: Uint8Array;
|
|
18
|
+
readonly contentType: string;
|
|
19
|
+
readonly fileName: string;
|
|
20
|
+
};
|
|
21
|
+
|
|
1
22
|
// Transport interface — SMTP in prod, InMemory in tests
|
|
2
23
|
export type EmailMessage = {
|
|
3
24
|
readonly to: string;
|
|
@@ -19,6 +40,8 @@ export type EmailMessage = {
|
|
|
19
40
|
// Extra RFC-5322 headers — In-Reply-To / References thread a reply into the
|
|
20
41
|
// conversation it answers.
|
|
21
42
|
readonly headers?: Readonly<Record<string, string>>;
|
|
43
|
+
// Refs travel through jobs; the bytes are only read at send time.
|
|
44
|
+
readonly attachments?: readonly EmailAttachment[];
|
|
22
45
|
};
|
|
23
46
|
|
|
24
47
|
export type EmailTransport = {
|
|
@@ -20,7 +20,7 @@ export const markReadWrite = defineWriteHandler({
|
|
|
20
20
|
description:
|
|
21
21
|
"Marks one in-app message of the calling user as read by its numeric id, failing with not-found when no such message belongs to the caller; use it when a user opens a single notification.",
|
|
22
22
|
handler: async (event, ctx) => {
|
|
23
|
-
const rows = await updateMany(
|
|
23
|
+
const rows = await updateMany<{ id: number }>(
|
|
24
24
|
ctx.db,
|
|
25
25
|
inAppMessagesTable,
|
|
26
26
|
{ isRead: true, readAt: Temporal.Now.instant() },
|
|
@@ -29,6 +29,6 @@ export const markReadWrite = defineWriteHandler({
|
|
|
29
29
|
if (rows.length === 0) {
|
|
30
30
|
return writeFailure(new NotFoundError("inAppMessage", event.payload.id));
|
|
31
31
|
}
|
|
32
|
-
return { isSuccess: true, data: { id:
|
|
32
|
+
return { isSuccess: true, data: { id: rows[0]?.id } };
|
|
33
33
|
},
|
|
34
34
|
});
|
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
[
|
|
2
|
+
{
|
|
3
|
+
"version": "0.364.0",
|
|
4
|
+
"type": "fix",
|
|
5
|
+
"title": "Config key validate() receives a db scoped to the written tenant",
|
|
6
|
+
"detail": "A SystemAdmin writing a config value for another tenant previously handed the validator a system-mode db that could read every tenant. `ConfigValidateContext.db` is now a `TenantDb` locked to the tenant the value is written for. The validator runs inside the write transaction, so a slow query there should set its own timeout."
|
|
7
|
+
},
|
|
2
8
|
{
|
|
3
9
|
"version": "0.338.0",
|
|
4
10
|
"type": "improvement",
|
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
encryptPiiValueForSubject,
|
|
5
5
|
type KmsContext,
|
|
6
6
|
} from "@cosmicdrift/kumiko-framework/crypto";
|
|
7
|
-
import { createEventStoreExecutor } from "@cosmicdrift/kumiko-framework/db";
|
|
7
|
+
import { createEventStoreExecutor, scopeTenantDbToTenant } from "@cosmicdrift/kumiko-framework/db";
|
|
8
8
|
import {
|
|
9
9
|
ConfigScopes,
|
|
10
10
|
defineWriteHandler,
|
|
@@ -111,7 +111,7 @@ export const setWrite = defineWriteHandler({
|
|
|
111
111
|
scope,
|
|
112
112
|
tenantId,
|
|
113
113
|
userId,
|
|
114
|
-
db,
|
|
114
|
+
db: scopeTenantDbToTenant(db, tenantId),
|
|
115
115
|
});
|
|
116
116
|
} catch (error) {
|
|
117
117
|
if (isKumikoError(error)) return writeFailure(error);
|
|
@@ -164,11 +164,7 @@ export const setWrite = defineWriteHandler({
|
|
|
164
164
|
});
|
|
165
165
|
}
|
|
166
166
|
// scope="system" was already rejected above — narrow accordingly.
|
|
167
|
-
const subject = resolvePiiSubject(
|
|
168
|
-
scope as Exclude<typeof scope, "system">, // @cast-boundary runtime-checked above
|
|
169
|
-
tenantId,
|
|
170
|
-
userId,
|
|
171
|
-
);
|
|
167
|
+
const subject = resolvePiiSubject(scope as Exclude<typeof scope, "system">, tenantId, userId);
|
|
172
168
|
const kmsCtx: KmsContext = {
|
|
173
169
|
requestId: requestContext.get()?.requestId ?? "config:write:set",
|
|
174
170
|
tenantId,
|
|
@@ -119,6 +119,19 @@ async function decryptPiiEncrypted(raw: string, qualifiedKey: string): Promise<s
|
|
|
119
119
|
return decryptPiiValueForSubject(kms, raw, kmsCtx, qualifiedKey);
|
|
120
120
|
}
|
|
121
121
|
|
|
122
|
+
// Plaintext form of a stored config_values.value for every read path (resolver,
|
|
123
|
+
// bootstrap idempotency check).
|
|
124
|
+
export async function decryptStoredValue(
|
|
125
|
+
raw: string,
|
|
126
|
+
keyDef: ConfigKeyDefinition,
|
|
127
|
+
cipher: EnvelopeCipher | undefined,
|
|
128
|
+
qualifiedKey: string,
|
|
129
|
+
): Promise<string> {
|
|
130
|
+
if (keyDef.encrypted) return decryptEncrypted(cipher, raw, qualifiedKey);
|
|
131
|
+
if (keyDef.piiEncrypted) return decryptPiiEncrypted(raw, qualifiedKey);
|
|
132
|
+
return raw;
|
|
133
|
+
}
|
|
134
|
+
|
|
122
135
|
// backing="secrets" keys store their value in the secrets store (flat per
|
|
123
136
|
// (tenant,key) at SYSTEM_TENANT_ID), not in config_values. Both read paths
|
|
124
137
|
// (getWithSource + buildCascade) route the system rung here. Missing reader =
|
|
@@ -230,12 +243,7 @@ async function buildCascade(
|
|
|
230
243
|
|
|
231
244
|
const row = await fetchRow(lookup.tenantId, lookup.userId);
|
|
232
245
|
if (row?.value !== null && row?.value !== undefined) {
|
|
233
|
-
|
|
234
|
-
if (keyDef.encrypted) {
|
|
235
|
-
raw = await decryptEncrypted(cipher, raw, qualifiedKey);
|
|
236
|
-
} else if (keyDef.piiEncrypted) {
|
|
237
|
-
raw = await decryptPiiEncrypted(raw, qualifiedKey);
|
|
238
|
-
}
|
|
246
|
+
const raw = await decryptStoredValue(row.value, keyDef, cipher, qualifiedKey);
|
|
239
247
|
if (activeIndex === -1) activeIndex = levels.length;
|
|
240
248
|
levels.push({
|
|
241
249
|
label: lookup.label,
|
|
@@ -409,12 +417,7 @@ export function createConfigResolver(options: ConfigResolverOptions = {}): Confi
|
|
|
409
417
|
for (const lookup of lookups) {
|
|
410
418
|
const row = await findRow(qualifiedKey, lookup.tenantId, lookup.userId, db);
|
|
411
419
|
if (row?.value !== null && row?.value !== undefined) {
|
|
412
|
-
|
|
413
|
-
if (keyDef.encrypted) {
|
|
414
|
-
raw = await decryptEncrypted(cipher, raw, qualifiedKey);
|
|
415
|
-
} else if (keyDef.piiEncrypted) {
|
|
416
|
-
raw = await decryptPiiEncrypted(raw, qualifiedKey);
|
|
417
|
-
}
|
|
420
|
+
const raw = await decryptStoredValue(row.value, keyDef, cipher, qualifiedKey);
|
|
418
421
|
return { value: deserializeValue(raw, keyDef.type), source: lookup.source };
|
|
419
422
|
}
|
|
420
423
|
}
|
|
@@ -28,8 +28,10 @@ import {
|
|
|
28
28
|
type WriteFailure,
|
|
29
29
|
writeFailure,
|
|
30
30
|
} from "@cosmicdrift/kumiko-framework/errors";
|
|
31
|
+
import type { EnvelopeCipher } from "@cosmicdrift/kumiko-framework/secrets";
|
|
31
32
|
import { assertUnreachable } from "@cosmicdrift/kumiko-framework/utils";
|
|
32
33
|
import { ConfigErrors } from "./constants.js";
|
|
34
|
+
import { decryptStoredValue } from "./resolver.js";
|
|
33
35
|
import { configValuesTable } from "./table.js";
|
|
34
36
|
|
|
35
37
|
export type ConfigRowLookup = {
|
|
@@ -62,6 +64,27 @@ export async function findConfigRow(
|
|
|
62
64
|
};
|
|
63
65
|
}
|
|
64
66
|
|
|
67
|
+
// Idempotency check for declarative writers: does the stored row already hold
|
|
68
|
+
// `serializedValue` (JSON of the plaintext)? Encrypted rows are decrypted
|
|
69
|
+
// first, since their ciphertext differs on every encrypt.
|
|
70
|
+
export async function configRowHoldsValue(
|
|
71
|
+
row: ConfigRowLookup | null,
|
|
72
|
+
keyDef: ConfigKeyDefinition | undefined,
|
|
73
|
+
cipher: EnvelopeCipher | undefined,
|
|
74
|
+
qualifiedKey: string,
|
|
75
|
+
serializedValue: string,
|
|
76
|
+
): Promise<boolean> {
|
|
77
|
+
if (row?.value === null || row?.value === undefined) return false;
|
|
78
|
+
if (!keyDef?.encrypted && !keyDef?.piiEncrypted) return row.value === serializedValue;
|
|
79
|
+
if (!keyDef) return false;
|
|
80
|
+
try {
|
|
81
|
+
return (await decryptStoredValue(row.value, keyDef, cipher, qualifiedKey)) === serializedValue;
|
|
82
|
+
} catch {
|
|
83
|
+
// Unreadable (rotated key, missing cipher/KMS): the caller rewrites the row, which is the repair.
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
65
88
|
// Three-stage pre-write gate that set + reset both need: resolve the key
|
|
66
89
|
// definition (404 when unknown), check the user's roles can write it (403),
|
|
67
90
|
// and map the target scope to the (tenantId | null, userId | null) pair the
|
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
[
|
|
2
|
+
{
|
|
3
|
+
"version": "0.364.0",
|
|
4
|
+
"type": "fix",
|
|
5
|
+
"title": "forget-subject keeps the user status flip and PAT revocation when the sweep fails",
|
|
6
|
+
"detail": "The user is set to Deleted and its personal access tokens are revoked right after the key erase, in their own transaction on the outside-transaction db. A failing blind-index or search sweep still makes the handler throw so the cleanup can be retried, but it no longer rolls the login door back. Without an outside-transaction db the previous in-transaction savepoint path is used. A failure while closing the login door of an existing user now fails the forget (retry is safe) instead of passing silently; subjects without a user row are skipped."
|
|
7
|
+
},
|
|
2
8
|
{
|
|
3
9
|
"version": "0.357.0",
|
|
4
10
|
"type": "improvement",
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import { requestContext } from "@cosmicdrift/kumiko-framework/api";
|
|
2
2
|
import { ROLES } from "@cosmicdrift/kumiko-framework/auth";
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
fetchOne,
|
|
5
|
+
runInSavepointIfSupported,
|
|
6
|
+
runnerCanBeginTransaction,
|
|
7
|
+
transaction,
|
|
8
|
+
} from "@cosmicdrift/kumiko-framework/bun-db";
|
|
4
9
|
import {
|
|
5
10
|
computeBlindIndex,
|
|
6
11
|
configuredBlindIndexKey,
|
|
@@ -42,7 +47,7 @@ import * as z from "zod";
|
|
|
42
47
|
import { resolveRetentionPolicyForTenant } from "../../data-retention/index.js";
|
|
43
48
|
import { eraseNoteBodiesReachingSubject } from "../../notes-history-user-data/index.js";
|
|
44
49
|
import { revokeAllPatTokensForUser } from "../../personal-access-tokens/index.js";
|
|
45
|
-
import { USER_STATUS } from "../../user/index.js";
|
|
50
|
+
import { USER_STATUS, userTable } from "../../user/index.js";
|
|
46
51
|
import {
|
|
47
52
|
denyIfTargetOutsideAdminTenant,
|
|
48
53
|
isSystemAdminActor,
|
|
@@ -337,25 +342,33 @@ async function sweepDerivedSubjectData(
|
|
|
337
342
|
}
|
|
338
343
|
}
|
|
339
344
|
|
|
340
|
-
//
|
|
341
|
-
//
|
|
342
|
-
//
|
|
343
|
-
//
|
|
344
|
-
//
|
|
345
|
-
|
|
345
|
+
// Email subscribers and other non-user entities use user-style subject keys
|
|
346
|
+
// without a read_users row — the key erase is the GDPR-relevant part, so no row
|
|
347
|
+
// means nothing to close. For an existing row, failures propagate: the handler
|
|
348
|
+
// throws and the retry re-runs the (idempotent) erase plus the door close. With
|
|
349
|
+
// an outside-transaction runner that can open its own transaction, the door
|
|
350
|
+
// closes there and stays committed when the handler throws afterwards (sweep
|
|
351
|
+
// error). Otherwise a savepoint confines a failure to the handler transaction.
|
|
352
|
+
async function closeUserLoginDoor(
|
|
346
353
|
ctx: HandlerContext,
|
|
347
354
|
userId: string,
|
|
348
|
-
|
|
355
|
+
outsideRunner: DbRunner | undefined,
|
|
356
|
+
handlerTxRunner: DbRunner,
|
|
349
357
|
): Promise<void> {
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
358
|
+
const userRow = await fetchOne(handlerTxRunner, userTable, { id: userId });
|
|
359
|
+
// skip: no user row for this subject key (e.g. email subscribers); there is no door to close.
|
|
360
|
+
if (!userRow) return;
|
|
361
|
+
|
|
362
|
+
const closeDoor = async (runner: DbRunner): Promise<void> => {
|
|
363
|
+
await updateUserLifecycle(runner, userId, { status: USER_STATUS.Deleted });
|
|
364
|
+
if (ctx.registry.features.has("personal-access-tokens")) {
|
|
365
|
+
await revokeAllPatTokensForUser(runner, userId);
|
|
366
|
+
}
|
|
367
|
+
};
|
|
368
|
+
if (outsideRunner && runnerCanBeginTransaction(outsideRunner)) {
|
|
369
|
+
await transaction(outsideRunner, closeDoor);
|
|
370
|
+
} else {
|
|
371
|
+
await runInSavepointIfSupported(handlerTxRunner, closeDoor);
|
|
359
372
|
}
|
|
360
373
|
}
|
|
361
374
|
|
|
@@ -490,14 +503,6 @@ export const forgetSubjectWrite = defineWriteHandler({
|
|
|
490
503
|
eraseReason: event.payload.reason,
|
|
491
504
|
});
|
|
492
505
|
|
|
493
|
-
const cleanupError = await sweepDerivedSubjectData(
|
|
494
|
-
ctx,
|
|
495
|
-
subject,
|
|
496
|
-
subjectKey,
|
|
497
|
-
ctx.db.unsafeRaw(),
|
|
498
|
-
{ requestId, erasedBy: event.user.id },
|
|
499
|
-
);
|
|
500
|
-
|
|
501
506
|
// User subject: close the login door. DEK-erase makes the passwordHash
|
|
502
507
|
// ciphertext unreadable, but status + PATs are standalone credentials —
|
|
503
508
|
// the PAT resolver only checks revokedAt/expiresAt, NOT user.status
|
|
@@ -513,10 +518,26 @@ export const forgetSubjectWrite = defineWriteHandler({
|
|
|
513
518
|
// crash recovery is safe. User-feature guard: without the user feature
|
|
514
519
|
// read_users doesn't exist (crypto-only stack). Tenant subjects have no
|
|
515
520
|
// credentials.
|
|
521
|
+
// Runs before the sweep and in its own transaction: a failing sweep rolls the
|
|
522
|
+
// handler transaction back, and the handler tx holds no lock on this user's
|
|
523
|
+
// read_users row yet (only read gates ran), so there is no lock conflict.
|
|
516
524
|
if (raw.kind === "user" && ctx.registry.features.has("user")) {
|
|
517
|
-
await
|
|
525
|
+
await closeUserLoginDoor(
|
|
526
|
+
ctx,
|
|
527
|
+
raw.userId,
|
|
528
|
+
ctx.dbOutsideTransaction?.unsafeRaw(),
|
|
529
|
+
ctx.db.unsafeRaw(),
|
|
530
|
+
);
|
|
518
531
|
}
|
|
519
532
|
|
|
533
|
+
const cleanupError = await sweepDerivedSubjectData(
|
|
534
|
+
ctx,
|
|
535
|
+
subject,
|
|
536
|
+
subjectKey,
|
|
537
|
+
ctx.db.unsafeRaw(),
|
|
538
|
+
{ requestId, erasedBy: event.user.id },
|
|
539
|
+
);
|
|
540
|
+
|
|
520
541
|
await appendSubjectForgotten(
|
|
521
542
|
ctx,
|
|
522
543
|
event,
|
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
[
|
|
2
|
+
{
|
|
3
|
+
"version": "0.364.0",
|
|
4
|
+
"type": "fix",
|
|
5
|
+
"title": "update-tenant-field no longer hides `type` from the handler schema",
|
|
6
|
+
"detail": "The `type` field is part of the documented payload; the 422 check against changing the stored type remains the protection."
|
|
7
|
+
},
|
|
2
8
|
{
|
|
3
9
|
"version": "0.359.0",
|
|
4
10
|
"type": "improvement",
|
|
@@ -31,9 +31,9 @@ export async function selectFieldDefinitionsForEntity(
|
|
|
31
31
|
entityName: string,
|
|
32
32
|
tenantId: string,
|
|
33
33
|
): Promise<readonly { field_key: string; serialized_field: unknown }[]> {
|
|
34
|
-
return unsafeReadRetrying(
|
|
34
|
+
return unsafeReadRetrying<{ field_key: string; serialized_field: unknown }>(
|
|
35
35
|
db,
|
|
36
36
|
"SELECT field_key, serialized_field FROM read_custom_field_definitions WHERE entity_name = $1 AND tenant_id = $2",
|
|
37
37
|
[entityName, tenantId],
|
|
38
|
-
)
|
|
38
|
+
);
|
|
39
39
|
}
|
|
@@ -46,7 +46,6 @@ export function createUpdateTenantFieldHandler(
|
|
|
46
46
|
access: { roles: opts.roles ?? DEFAULT_FIELD_DEFINITION_WRITE_ROLES },
|
|
47
47
|
description:
|
|
48
48
|
"Replaces a tenant-owned custom-field definition with a complete new spec; serializedField.type may be omitted, in which case the stored type applies, and a different type is refused. Use it to relabel or re-constrain an existing field rather than to create or remove one.",
|
|
49
|
-
excludedFields: ["type"],
|
|
50
49
|
handler: async (event, ctx) => {
|
|
51
50
|
const payload = event.payload as UpdateFieldPayload; // @cast-boundary engine-payload
|
|
52
51
|
const tenantId = event.user.tenantId;
|
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
type FieldDefinition,
|
|
4
4
|
fieldToZod,
|
|
5
5
|
} from "@cosmicdrift/kumiko-framework/engine";
|
|
6
|
+
import { isPlainObject } from "@cosmicdrift/kumiko-types/type-guards";
|
|
6
7
|
import type * as z from "zod";
|
|
7
8
|
import { SUPPORTED_FIELD_TYPES } from "../constants.js";
|
|
8
9
|
|
|
@@ -38,9 +39,8 @@ const SUPPORTED_EMBEDDED_SUB_TYPES: ReadonlySet<string> = new Set([
|
|
|
38
39
|
const CONSTRAINT_KEYS = ["required", "maxLength", "format", "default"] as const;
|
|
39
40
|
|
|
40
41
|
export function buildCustomFieldValueSchema(parsedField: unknown): z.ZodTypeAny | null {
|
|
41
|
-
if (!parsedField
|
|
42
|
-
const
|
|
43
|
-
const rawType = obj["type"];
|
|
42
|
+
if (!isPlainObject(parsedField)) return null;
|
|
43
|
+
const rawType = parsedField["type"];
|
|
44
44
|
if (typeof rawType !== "string") return null;
|
|
45
45
|
if (!SUPPORTED_TYPES_SET.has(rawType)) return null;
|
|
46
46
|
|
|
@@ -54,35 +54,34 @@ export function buildCustomFieldValueSchema(parsedField: unknown): z.ZodTypeAny
|
|
|
54
54
|
// drops everywhere else.
|
|
55
55
|
let strippedSubSchema: Record<string, Record<string, unknown>> | undefined;
|
|
56
56
|
if (rawType === "embedded") {
|
|
57
|
-
const schema =
|
|
57
|
+
const schema = parsedField["schema"];
|
|
58
58
|
if (!schema || typeof schema !== "object") return null;
|
|
59
59
|
strippedSubSchema = {};
|
|
60
60
|
for (const [subKey, sub] of Object.entries(schema)) {
|
|
61
|
-
if (!sub
|
|
62
|
-
const
|
|
63
|
-
const subType = subObj["type"];
|
|
61
|
+
if (!isPlainObject(sub)) return null;
|
|
62
|
+
const subType = sub["type"];
|
|
64
63
|
if (typeof subType !== "string" || !SUPPORTED_EMBEDDED_SUB_TYPES.has(subType)) {
|
|
65
64
|
return null;
|
|
66
65
|
}
|
|
67
|
-
const strippedSub = { ...
|
|
66
|
+
const strippedSub = { ...sub };
|
|
68
67
|
for (const k of CONSTRAINT_KEYS) delete strippedSub[k];
|
|
69
68
|
strippedSubSchema[subKey] = strippedSub;
|
|
70
69
|
}
|
|
71
70
|
}
|
|
72
71
|
|
|
73
|
-
const fieldDef: Record<string, unknown> = { ...
|
|
72
|
+
const fieldDef: Record<string, unknown> = { ...parsedField };
|
|
74
73
|
for (const k of CONSTRAINT_KEYS) delete fieldDef[k];
|
|
75
74
|
if (strippedSubSchema !== undefined) fieldDef["schema"] = strippedSubSchema;
|
|
76
75
|
if (rawType === "enum") {
|
|
77
76
|
fieldDef["type"] = "select";
|
|
78
|
-
fieldDef["options"] =
|
|
77
|
+
fieldDef["options"] = parsedField["values"] ?? parsedField["options"] ?? [];
|
|
79
78
|
}
|
|
80
79
|
|
|
81
80
|
// fieldToZod's money case validates `currency` against the passed list, not
|
|
82
81
|
// a field-level key — so surface the field's own currency when it declares
|
|
83
82
|
// one, else fall back to the framework defaults.
|
|
84
83
|
const currencies =
|
|
85
|
-
typeof
|
|
84
|
+
typeof parsedField["currency"] === "string" ? [parsedField["currency"]] : DEFAULT_CURRENCIES;
|
|
86
85
|
|
|
87
86
|
// @cast-boundary serialized-field is the dehydrated r.field.X() output =
|
|
88
87
|
// a FieldDefinition; fieldToZod reads only its type-specific keys (the
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import { type DbRunner, extractTableName } from "@cosmicdrift/kumiko-framework/db";
|
|
4
4
|
import type { UserDataExportSnippet, UserDataHookCtx } from "@cosmicdrift/kumiko-framework/engine";
|
|
5
5
|
import { EXT_USER_DATA, type FeatureRegistrar } from "@cosmicdrift/kumiko-framework/engine";
|
|
6
|
+
import { isPlainObject } from "@cosmicdrift/kumiko-types/type-guards";
|
|
6
7
|
import { selectCustomFieldsHostRows } from "./db/queries/user-data-rights.js";
|
|
7
8
|
|
|
8
9
|
export interface WireCustomFieldsUserDataRightsOptions {
|
|
@@ -17,14 +18,14 @@ interface CustomFieldsHostRow {
|
|
|
17
18
|
}
|
|
18
19
|
|
|
19
20
|
function asCustomFieldsHostRow(value: unknown): CustomFieldsHostRow | null {
|
|
20
|
-
if (!
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
const cf =
|
|
21
|
+
if (!isPlainObject(value)) return null;
|
|
22
|
+
const id = value["id"];
|
|
23
|
+
if (typeof id !== "string") return null;
|
|
24
|
+
const cf = value["custom_fields"] ?? value["customFields"];
|
|
24
25
|
if (cf === undefined) return null;
|
|
25
|
-
if (cf === null) return { id
|
|
26
|
+
if (cf === null) return { id, customFields: null };
|
|
26
27
|
if (!cf || typeof cf !== "object" || Array.isArray(cf)) return null;
|
|
27
|
-
return { id
|
|
28
|
+
return { id, customFields: Object.fromEntries(Object.entries(cf)) };
|
|
28
29
|
}
|
|
29
30
|
|
|
30
31
|
// fw#2914 — selectCustomFieldsHostRows runs raw SQL against a dynamic host
|
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
[
|
|
2
|
+
{
|
|
3
|
+
"version": "0.363.0",
|
|
4
|
+
"type": "improvement",
|
|
5
|
+
"title": "data-retention exports computeCutoff",
|
|
6
|
+
"detail": "computeCutoff(keepFor, now) and its Instant type are exported from @cosmicdrift/kumiko-bundled-features/data-retention, so apps that compute a retention cutoff themselves (for example a backfill) use the same keepFor parsing as the cleanup job instead of a copy."
|
|
7
|
+
},
|
|
2
8
|
{
|
|
3
9
|
"version": "0.335.0",
|
|
4
10
|
"type": "fix",
|
|
@@ -72,6 +72,7 @@ import {
|
|
|
72
72
|
fileRefEntity,
|
|
73
73
|
fileRefsTable,
|
|
74
74
|
} from "@cosmicdrift/kumiko-framework/files";
|
|
75
|
+
import { propertyOf } from "@cosmicdrift/kumiko-types/type-guards";
|
|
75
76
|
import { runInSubTransaction } from "../shared/index.js";
|
|
76
77
|
import { computeCutoff, type Instant } from "./keep-for.js";
|
|
77
78
|
import type { RetentionPresetKey } from "./presets.js";
|
|
@@ -347,10 +348,9 @@ function collectSingleFileHolders(registry: Registry): readonly SingleFileHolder
|
|
|
347
348
|
if (!entity) continue;
|
|
348
349
|
const { singleFields } = planFileFields(entity.fields);
|
|
349
350
|
if (singleFields.length === 0) continue;
|
|
350
|
-
const table = proj.table as Record<string, unknown>; // @cast-boundary column-presence probe
|
|
351
351
|
holders.push({
|
|
352
352
|
table: proj.table,
|
|
353
|
-
tableHasTenantId: table
|
|
353
|
+
tableHasTenantId: propertyOf(proj.table, "tenantId") !== undefined,
|
|
354
354
|
singleFields,
|
|
355
355
|
});
|
|
356
356
|
}
|
|
@@ -699,11 +699,10 @@ export async function runRetentionCleanup(
|
|
|
699
699
|
const entity = registry.getEntity(entityName);
|
|
700
700
|
if (!entity) continue;
|
|
701
701
|
|
|
702
|
-
const table = proj.table as Record<string, unknown>; // @cast-boundary column-presence probe
|
|
703
702
|
const declaredReference = policy.reference ?? DEFAULT_REFERENCE_FIELD;
|
|
704
703
|
const referenceField = FRAMEWORK_REFERENCE_ALIAS[declaredReference] ?? declaredReference;
|
|
705
704
|
|
|
706
|
-
if (table
|
|
705
|
+
if (propertyOf(proj.table, referenceField) === undefined) {
|
|
707
706
|
skipped.push({ entityName, reason: "missing_reference_column" });
|
|
708
707
|
continue;
|
|
709
708
|
}
|
|
@@ -713,7 +712,7 @@ export async function runRetentionCleanup(
|
|
|
713
712
|
// Tenant-Scope nur wenn die Tabelle eine tenantId-Spalte hat — identisch zu
|
|
714
713
|
// soft-delete-cleanup. Ohne diesen Filter wuerde ein Tenant die Rows eines
|
|
715
714
|
// anderen treffen.
|
|
716
|
-
if (table
|
|
715
|
+
if (propertyOf(proj.table, "tenantId") !== undefined) {
|
|
717
716
|
where["tenantId"] = tenantId;
|
|
718
717
|
}
|
|
719
718
|
|
|
@@ -762,7 +761,10 @@ export async function runRetentionCleanup(
|
|
|
762
761
|
break;
|
|
763
762
|
}
|
|
764
763
|
case "softDelete": {
|
|
765
|
-
if (
|
|
764
|
+
if (
|
|
765
|
+
propertyOf(proj.table, "isDeleted") === undefined ||
|
|
766
|
+
propertyOf(proj.table, "deletedAt") === undefined
|
|
767
|
+
) {
|
|
766
768
|
skipped.push({ entityName, reason: "missing_softdelete_columns" });
|
|
767
769
|
break;
|
|
768
770
|
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import * as z from "zod";
|
|
2
|
+
import type {
|
|
3
|
+
FileAttachmentRef,
|
|
4
|
+
InlineAttachmentRef,
|
|
5
|
+
RenderedPdfAttachmentRef,
|
|
6
|
+
} from "../renderer-foundation/index.js";
|
|
7
|
+
import { attachmentFileNameSchema } from "../shared/index.js";
|
|
8
|
+
import { isDocumentResourceKey, templateSlugSchema } from "../template-resolver/index.js";
|
|
9
|
+
|
|
10
|
+
export type { FileAttachmentRef, InlineAttachmentRef, RenderedPdfAttachmentRef };
|
|
11
|
+
export type EmailAttachmentRef = InlineAttachmentRef | FileAttachmentRef | RenderedPdfAttachmentRef;
|
|
12
|
+
|
|
13
|
+
export const MAX_EMAIL_ATTACHMENTS = 20;
|
|
14
|
+
|
|
15
|
+
export const MAX_RENDERED_PDF_VARIABLES_BYTES = 64_000;
|
|
16
|
+
|
|
17
|
+
// Channel-data keys that steer delivery itself (envelope, headers, attachments);
|
|
18
|
+
// they are not document variables and must not be copied into attached PDF refs.
|
|
19
|
+
export const DELIVERY_ENVELOPE_DATA_KEYS = [
|
|
20
|
+
"attachments",
|
|
21
|
+
"from",
|
|
22
|
+
"fromName",
|
|
23
|
+
"replyTo",
|
|
24
|
+
"headers",
|
|
25
|
+
"unsubscribeUrl",
|
|
26
|
+
] as const;
|
|
27
|
+
|
|
28
|
+
const DELIVERY_ENVELOPE_DATA_KEY_SET: ReadonlySet<string> = new Set(DELIVERY_ENVELOPE_DATA_KEYS);
|
|
29
|
+
|
|
30
|
+
export function withoutDeliveryEnvelopeKeys(
|
|
31
|
+
variables: Readonly<Record<string, unknown>>,
|
|
32
|
+
): Readonly<Record<string, unknown>> {
|
|
33
|
+
return Object.fromEntries(
|
|
34
|
+
Object.entries(variables).filter(([key]) => !DELIVERY_ENVELOPE_DATA_KEY_SET.has(key)),
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const MAX_ID_LENGTH = 64;
|
|
39
|
+
|
|
40
|
+
const fileRefIdSchema = z.string().min(1).max(MAX_ID_LENGTH);
|
|
41
|
+
|
|
42
|
+
export const inlineAttachmentRefSchema = z
|
|
43
|
+
.object({
|
|
44
|
+
kind: z.literal("inline"),
|
|
45
|
+
cid: z.string().min(1).max(MAX_ID_LENGTH).refine(isDocumentResourceKey),
|
|
46
|
+
fileRefId: fileRefIdSchema,
|
|
47
|
+
})
|
|
48
|
+
.strict();
|
|
49
|
+
|
|
50
|
+
export const fileAttachmentRefSchema = z
|
|
51
|
+
.object({
|
|
52
|
+
kind: z.literal("file"),
|
|
53
|
+
fileRefId: fileRefIdSchema,
|
|
54
|
+
fileName: attachmentFileNameSchema,
|
|
55
|
+
})
|
|
56
|
+
.strict();
|
|
57
|
+
|
|
58
|
+
export const renderedPdfAttachmentRefSchema = z
|
|
59
|
+
.object({
|
|
60
|
+
kind: z.literal("rendered-pdf"),
|
|
61
|
+
templateSlug: templateSlugSchema,
|
|
62
|
+
variables: z
|
|
63
|
+
.record(z.string(), z.unknown())
|
|
64
|
+
.refine(
|
|
65
|
+
(value) => Buffer.byteLength(JSON.stringify(value)) <= MAX_RENDERED_PDF_VARIABLES_BYTES,
|
|
66
|
+
{
|
|
67
|
+
message: `variables must not exceed ${MAX_RENDERED_PDF_VARIABLES_BYTES} bytes as JSON`,
|
|
68
|
+
},
|
|
69
|
+
),
|
|
70
|
+
fileName: attachmentFileNameSchema.refine((name) => name.toLowerCase().endsWith(".pdf"), {
|
|
71
|
+
message: "file name must end with .pdf",
|
|
72
|
+
}),
|
|
73
|
+
locale: z
|
|
74
|
+
.string()
|
|
75
|
+
.min(2)
|
|
76
|
+
.max(8)
|
|
77
|
+
.regex(/^[a-z]{2}(-[a-z]{2})?$/i, "locale must be ISO 639-1 (e.g. de, en, en-us)")
|
|
78
|
+
.optional(),
|
|
79
|
+
})
|
|
80
|
+
.strict();
|
|
81
|
+
|
|
82
|
+
export const emailAttachmentRefSchema = z.discriminatedUnion("kind", [
|
|
83
|
+
inlineAttachmentRefSchema,
|
|
84
|
+
fileAttachmentRefSchema,
|
|
85
|
+
renderedPdfAttachmentRefSchema,
|
|
86
|
+
]);
|
|
87
|
+
|
|
88
|
+
// Rebuilds only the contract fields so bytes a caller attached to a ref object
|
|
89
|
+
// can never ride into a job payload.
|
|
90
|
+
export function toAttachmentRef(ref: EmailAttachmentRef): EmailAttachmentRef {
|
|
91
|
+
switch (ref.kind) {
|
|
92
|
+
case "inline":
|
|
93
|
+
return { kind: "inline", cid: ref.cid, fileRefId: ref.fileRefId };
|
|
94
|
+
case "file":
|
|
95
|
+
return { kind: "file", fileRefId: ref.fileRefId, fileName: ref.fileName };
|
|
96
|
+
case "rendered-pdf":
|
|
97
|
+
return {
|
|
98
|
+
kind: "rendered-pdf",
|
|
99
|
+
templateSlug: ref.templateSlug,
|
|
100
|
+
variables: ref.variables,
|
|
101
|
+
fileName: ref.fileName,
|
|
102
|
+
...(ref.locale !== undefined && { locale: ref.locale }),
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
}
|