@cosmicdrift/kumiko-samples 0.387.0 → 0.389.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 +8 -1
- package/packages/bundled-features/src/agent-tools/changes.json +12 -0
- package/packages/bundled-features/src/agent-tools/index.ts +1 -1
- package/packages/bundled-features/src/agent-tools/tool-dispatch.ts +48 -32
- package/packages/bundled-features/src/audit/changes.json +12 -0
- package/packages/bundled-features/src/audit/feature.ts +1 -0
- package/packages/bundled-features/src/audit/handlers/audit-events.ts +1 -1
- package/packages/bundled-features/src/auth-email-password/changes.json +12 -0
- package/packages/bundled-features/src/auth-email-password/handlers/request-account-unlock.write.ts +1 -0
- package/packages/bundled-features/src/auth-email-password/handlers/request-email-verification.write.ts +1 -0
- package/packages/bundled-features/src/auth-email-password/handlers/request-password-reset.write.ts +1 -0
- package/packages/bundled-features/src/auth-email-password/handlers/signup-request.write.ts +4 -0
- package/packages/bundled-features/src/auth-email-password/handlers/token-request-handler.ts +8 -0
- package/packages/bundled-features/src/auth-email-password/magic-link-mail.ts +16 -3
- package/packages/bundled-features/src/auth-email-password/signup-activation.ts +2 -0
- package/packages/bundled-features/src/auth-email-password/web/auth-client.ts +8 -2
- package/packages/bundled-features/src/auth-email-password/web/auth-redirect.ts +23 -21
- package/packages/bundled-features/src/auth-email-password/web/default-auth-pages-gate.tsx +6 -3
- package/packages/bundled-features/src/auth-email-password/web/forgot-password-screen.tsx +11 -1
- package/packages/bundled-features/src/auth-email-password/web/index.ts +5 -0
- package/packages/bundled-features/src/auth-email-password/web/login-screen.tsx +16 -5
- package/packages/bundled-features/src/auth-email-password/web/reset-password-screen.tsx +7 -1
- package/packages/bundled-features/src/auth-email-password/web/return-path.ts +35 -0
- package/packages/bundled-features/src/auth-email-password/web/session.tsx +6 -1
- package/packages/bundled-features/src/auth-email-password/web/signup-complete-screen.tsx +11 -3
- package/packages/bundled-features/src/auth-email-password/web/signup-screen.tsx +12 -2
- package/packages/bundled-features/src/auth-mfa/changes.json +12 -0
- package/packages/bundled-features/src/auth-mfa/config.ts +3 -7
- package/packages/bundled-features/src/auth-mfa/feature-exports.ts +2 -2
- package/packages/bundled-features/src/auth-mfa/feature.ts +6 -0
- package/packages/bundled-features/src/auth-mfa/mfa-status-checker.ts +8 -5
- package/packages/bundled-features/src/auth-mfa/web/i18n.ts +6 -1
- package/packages/bundled-features/src/auth-mfa/web/mfa-code-or-recovery-field.tsx +68 -0
- package/packages/bundled-features/src/auth-mfa/web/mfa-disable-dialog.tsx +23 -13
- package/packages/bundled-features/src/auth-mfa/web/mfa-regenerate-recovery-dialog.tsx +23 -13
- package/packages/bundled-features/src/auth-mfa/web/mfa-setup-preauth-screen.tsx +15 -6
- package/packages/bundled-features/src/auth-mfa/web/mfa-verify-screen.tsx +65 -18
- package/packages/bundled-features/src/billing-foundation/changes.json +55 -0
- package/packages/bundled-features/src/billing-foundation/checkout-core.ts +32 -42
- package/packages/bundled-features/src/billing-foundation/consumer-protection/confirmation-mail.ts +8 -2
- package/packages/bundled-features/src/billing-foundation/consumer-protection/consent-gate.ts +2 -0
- package/packages/bundled-features/src/billing-foundation/consumer-protection/issue-confirmation.ts +14 -3
- package/packages/bundled-features/src/billing-foundation/consumer-protection/mail-notifications.ts +3 -4
- package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-receipt-limit.ts +12 -6
- package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-record.ts +35 -19
- package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-request.ts +12 -8
- package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-revoke.ts +14 -8
- package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-texts.ts +5 -0
- package/packages/bundled-features/src/billing-foundation/consumer-protection/withdrawal-period.ts +8 -10
- package/packages/bundled-features/src/billing-foundation/events.ts +1 -0
- package/packages/bundled-features/src/billing-foundation/feature.ts +3 -1
- package/packages/bundled-features/src/billing-foundation/handlers/billing-plans.query.ts +71 -6
- package/packages/bundled-features/src/billing-foundation/handlers/create-checkout-session.write.ts +1 -0
- package/packages/bundled-features/src/billing-foundation/handlers/plan-switch-preview.query.ts +65 -0
- package/packages/bundled-features/src/billing-foundation/handlers/process-event.write.ts +9 -2
- package/packages/bundled-features/src/billing-foundation/handlers/start-plan-checkout.write.ts +44 -8
- package/packages/bundled-features/src/billing-foundation/handlers/switch-plan.write.ts +72 -82
- package/packages/bundled-features/src/billing-foundation/handlers/sync-subscription.write.ts +14 -1
- package/packages/bundled-features/src/billing-foundation/i18n.ts +22 -0
- package/packages/bundled-features/src/billing-foundation/index.ts +2 -0
- package/packages/bundled-features/src/billing-foundation/plan-catalog.ts +62 -27
- package/packages/bundled-features/src/billing-foundation/plan-switch.ts +84 -0
- package/packages/bundled-features/src/billing-foundation/types.ts +80 -1
- package/packages/bundled-features/src/billing-foundation/validate-options.ts +19 -0
- package/packages/bundled-features/src/billing-foundation/web/billing-plans-panel.tsx +119 -47
- package/packages/bundled-features/src/billing-foundation/web/checkout-consent-dialog.tsx +89 -42
- package/packages/bundled-features/src/billing-foundation/web/constants.ts +4 -0
- package/packages/bundled-features/src/billing-foundation/web/plan-price.ts +55 -0
- package/packages/bundled-features/src/billing-foundation/web/plan-switch-confirm-dialog.tsx +224 -0
- package/packages/bundled-features/src/cap-counter/cap-reservation.ts +25 -8
- package/packages/bundled-features/src/cap-counter/changes.json +30 -0
- package/packages/bundled-features/src/cap-counter/enforce-cap.ts +2 -1
- package/packages/bundled-features/src/cap-counter/feature.ts +1 -0
- package/packages/bundled-features/src/cap-counter/stock-cap-guard.ts +8 -1
- package/packages/bundled-features/src/cap-overview/feature.ts +1 -0
- package/packages/bundled-features/src/channel-email/changes.json +6 -0
- package/packages/bundled-features/src/channel-email/feature.ts +1 -1
- package/packages/bundled-features/src/config/changes.json +18 -0
- package/packages/bundled-features/src/config/handlers/cascade.query.ts +4 -2
- package/packages/bundled-features/src/config/handlers/set.write.ts +19 -2
- package/packages/bundled-features/src/config/handlers/values.query.ts +4 -2
- package/packages/bundled-features/src/config/write-helpers.ts +27 -8
- package/packages/bundled-features/src/crypto-shredding/changes.json +18 -0
- package/packages/bundled-features/src/crypto-shredding/handlers/forget-subject.write.ts +11 -10
- package/packages/bundled-features/src/custom-fields/lib/parse-serialized-field.ts +1 -1
- package/packages/bundled-features/src/delivery/changes.json +42 -0
- package/packages/bundled-features/src/delivery/channel-context.ts +9 -3
- package/packages/bundled-features/src/delivery/db/queries/preferences.ts +2 -2
- package/packages/bundled-features/src/delivery/delivery-service.ts +35 -5
- package/packages/bundled-features/src/delivery/feature.ts +1 -0
- package/packages/bundled-features/src/delivery/handlers/preferences.query.ts +20 -1
- package/packages/bundled-features/src/delivery/handlers/unsubscribe-page-chrome.query.ts +1 -1
- package/packages/bundled-features/src/delivery/index.ts +5 -0
- package/packages/bundled-features/src/delivery/system-channel-context.ts +33 -0
- package/packages/bundled-features/src/delivery/unsubscribe-link.ts +3 -1
- package/packages/bundled-features/src/delivery/unsubscribe-page-strings.ts +1 -0
- package/packages/bundled-features/src/delivery/unsubscribe-page.ts +6 -0
- package/packages/bundled-features/src/delivery/unsubscribe.ts +36 -15
- package/packages/bundled-features/src/document-ingest-foundation/append-missing-extract-event.ts +84 -0
- package/packages/bundled-features/src/document-ingest-foundation/changes.json +6 -0
- package/packages/bundled-features/src/document-ingest-foundation/events.ts +15 -0
- package/packages/bundled-features/src/document-ingest-foundation/index.ts +11 -0
- package/packages/bundled-features/src/feature-toggles/feature.ts +1 -0
- package/packages/bundled-features/src/file-foundation/index.ts +1 -0
- package/packages/bundled-features/src/file-foundation/read-file-ref-content.ts +7 -1
- package/packages/bundled-features/src/file-provider-s3/env-named-feature.ts +0 -2
- package/packages/bundled-features/src/files/README.md +1 -1
- package/packages/bundled-features/src/form-draft/changes.json +6 -0
- package/packages/bundled-features/src/form-draft/db/queries/cleanup.ts +2 -2
- package/packages/bundled-features/src/form-draft/decrypt-draft.ts +4 -1
- package/packages/bundled-features/src/host-routing/README.md +1 -1
- package/packages/bundled-features/src/host-routing/changes.json +12 -0
- package/packages/bundled-features/src/host-routing/dispatch.ts +16 -17
- package/packages/bundled-features/src/host-routing/options.ts +2 -1
- package/packages/bundled-features/src/host-routing/validate-options.ts +5 -0
- package/packages/bundled-features/src/inbound-mail-foundation/changes.json +18 -0
- package/packages/bundled-features/src/inbound-mail-foundation/feature.ts +1 -0
- package/packages/bundled-features/src/inbound-mail-foundation/handlers/update-account.write.ts +17 -5
- package/packages/bundled-features/src/inbound-mail-foundation/i18n.ts +0 -1
- package/packages/bundled-features/src/inbound-mail-foundation/screens.ts +0 -3
- package/packages/bundled-features/src/inbound-mail-foundation/types.ts +4 -3
- package/packages/bundled-features/src/inbound-mail-foundation/watch-supervisor.ts +32 -16
- package/packages/bundled-features/src/jobs/changes.json +13 -0
- package/packages/bundled-features/src/jobs/feature.ts +15 -4
- package/packages/bundled-features/src/jobs/handlers/tenant-failures.query.ts +45 -40
- package/packages/bundled-features/src/jobs/handlers/tenant-runs.query.ts +63 -59
- package/packages/bundled-features/src/jobs/job-run-logger.ts +5 -4
- package/packages/bundled-features/src/ledger/web/index.ts +1 -1
- package/packages/bundled-features/src/legal-pages/README.md +1 -1
- package/packages/bundled-features/src/legal-pages/seed.ts +5 -2
- package/packages/bundled-features/src/mail-foundation/changes.json +6 -0
- package/packages/bundled-features/src/mail-foundation/feature.ts +5 -3
- package/packages/bundled-features/src/mail-transport-smtp/changes.json +6 -0
- package/packages/bundled-features/src/mail-transport-smtp/config-keys.ts +1 -1
- package/packages/bundled-features/src/mail-transport-smtp/feature.ts +9 -11
- package/packages/bundled-features/src/managed-pages/changes.json +6 -0
- package/packages/bundled-features/src/managed-pages/constants.ts +1 -0
- package/packages/bundled-features/src/managed-pages/feature.ts +3 -1
- package/packages/bundled-features/src/managed-pages/index.ts +1 -0
- package/packages/bundled-features/src/notes-history/handlers/add-note.write.ts +1 -1
- package/packages/bundled-features/src/personal-access-tokens/feature.ts +5 -1
- package/packages/bundled-features/src/renderer-pdf/changes.json +6 -0
- package/packages/bundled-features/src/renderer-pdf/render-table-pdf.ts +27 -10
- package/packages/bundled-features/src/secrets/changes.json +6 -0
- package/packages/bundled-features/src/secrets/write-gate.ts +9 -2
- package/packages/bundled-features/src/sessions/changes.json +7 -0
- package/packages/bundled-features/src/sessions/feature.ts +8 -2
- package/packages/bundled-features/src/sessions/index.ts +1 -1
- package/packages/bundled-features/src/sessions/session-callbacks.ts +1 -9
- package/packages/bundled-features/src/shared/decrypt-stored-pii.ts +10 -0
- package/packages/bundled-features/src/shared/index.ts +1 -1
- package/packages/bundled-features/src/shared/mail-template-body.ts +10 -3
- package/packages/bundled-features/src/shared/purge-files-reaching-subject.ts +24 -2
- package/packages/bundled-features/src/shared/run-in-sub-transaction.ts +5 -5
- package/packages/bundled-features/src/step-dispatcher/changes.json +12 -0
- package/packages/bundled-features/src/step-dispatcher/deliver-dispatch-request.ts +17 -3
- package/packages/bundled-features/src/step-dispatcher/feature.ts +1 -0
- package/packages/bundled-features/src/step-dispatcher/mail-runner.ts +2 -1
- package/packages/bundled-features/src/subscription-dodo/changes.json +14 -0
- package/packages/bundled-features/src/subscription-dodo/client.ts +45 -0
- package/packages/bundled-features/src/subscription-dodo/constants.ts +24 -0
- package/packages/bundled-features/src/subscription-dodo/feature.ts +207 -0
- package/packages/bundled-features/src/subscription-dodo/index.ts +16 -0
- package/packages/bundled-features/src/subscription-dodo/plugin-methods.ts +345 -0
- package/packages/bundled-features/src/subscription-dodo/runtime.ts +126 -0
- package/packages/bundled-features/src/subscription-dodo/verify-webhook.ts +262 -0
- package/packages/bundled-features/src/subscription-stripe/changes.json +12 -0
- package/packages/bundled-features/src/subscription-stripe/feature.ts +1 -0
- package/packages/bundled-features/src/subscription-stripe/runtime.ts +7 -6
- package/packages/bundled-features/src/tags/feature.ts +1 -0
- package/packages/bundled-features/src/template-resolver/api.ts +31 -4
- package/packages/bundled-features/src/template-resolver/changes.json +84 -0
- package/packages/bundled-features/src/template-resolver/compile-document-body.ts +1 -0
- package/packages/bundled-features/src/template-resolver/document-ops.ts +20 -1
- package/packages/bundled-features/src/template-resolver/feature.ts +4 -4
- package/packages/bundled-features/src/template-resolver/handlers/bundle-import.ts +3 -2
- package/packages/bundled-features/src/template-resolver/handlers/copy-locale.write.ts +27 -5
- package/packages/bundled-features/src/template-resolver/handlers/diff-to-system.query.ts +3 -2
- package/packages/bundled-features/src/template-resolver/handlers/document-base.ts +3 -2
- package/packages/bundled-features/src/template-resolver/handlers/find-by-id.query.ts +3 -2
- package/packages/bundled-features/src/template-resolver/handlers/layout-source.query.ts +36 -11
- package/packages/bundled-features/src/template-resolver/handlers/list.query.ts +18 -19
- package/packages/bundled-features/src/template-resolver/handlers/overview.query.ts +45 -10
- package/packages/bundled-features/src/template-resolver/handlers/patch-draft.write.ts +1 -1
- package/packages/bundled-features/src/template-resolver/handlers/send-test-status.query.ts +22 -19
- package/packages/bundled-features/src/template-resolver/handlers/tenant-override-counts.query.ts +9 -5
- package/packages/bundled-features/src/template-resolver/handlers/tenant-override-selection.ts +8 -6
- package/packages/bundled-features/src/template-resolver/handlers/tenant-overrides.query.ts +44 -5
- package/packages/bundled-features/src/template-resolver/handlers/usage.query.ts +37 -26
- package/packages/bundled-features/src/template-resolver/handlers/validate.query.ts +7 -2
- package/packages/bundled-features/src/template-resolver/seeding.ts +42 -17
- package/packages/bundled-features/src/template-resolver/store-template-resource.ts +74 -12
- package/packages/bundled-features/src/template-resolver/template-delivery-policy.ts +0 -11
- package/packages/bundled-features/src/template-resolver/template-meta.ts +12 -4
- package/packages/bundled-features/src/template-resolver/template-preview.ts +11 -3
- package/packages/bundled-features/src/template-resolver/web/constants.ts +7 -5
- package/packages/bundled-features/src/template-resolver/web/i18n.ts +6 -0
- package/packages/bundled-features/src/template-resolver/web/index.ts +7 -0
- package/packages/bundled-features/src/template-resolver/web/mail-layout-constants.ts +8 -0
- package/packages/bundled-features/src/template-resolver/web/qualified-names.ts +1 -0
- package/packages/bundled-features/src/tenant/changes.json +6 -0
- package/packages/bundled-features/src/tenant/feature.ts +1 -0
- package/packages/bundled-features/src/tenant/handlers/member-directory.query.ts +9 -3
- package/packages/bundled-features/src/tenant/handlers/team-list.query.ts +3 -3
- package/packages/bundled-features/src/tenant/handlers/tenant-directory.query.ts +7 -4
- package/packages/bundled-features/src/tenant-lifecycle/changes.json +12 -0
- package/packages/bundled-features/src/tenant-lifecycle/constants.ts +5 -0
- package/packages/bundled-features/src/tenant-lifecycle/run-tenant-destroy.ts +5 -1
- package/packages/bundled-features/src/tenant-lifecycle/stages.ts +35 -1
- package/packages/bundled-features/src/tier-engine/changes.json +6 -0
- package/packages/bundled-features/src/tier-engine/handlers/set-tenant-tier.write.ts +4 -1
- package/packages/bundled-features/src/tier-engine/web/constants.ts +3 -3
- package/packages/bundled-features/src/user/feature.ts +1 -0
- package/packages/bundled-features/src/user-data-rights/changes.json +12 -0
- package/packages/bundled-features/src/user-data-rights/feature.ts +1 -0
- package/packages/bundled-features/src/user-data-rights/index.ts +6 -1
- package/packages/bundled-features/src/user-data-rights/run-export-jobs.ts +3 -4
- package/packages/bundled-features/src/user-data-rights/run-forget-cleanup.ts +2 -1
- package/packages/bundled-features/src/user-data-rights/web/constants.ts +6 -0
- package/packages/bundled-features/src/user-data-rights/web/index.ts +1 -0
- package/packages/bundled-features/src/waitlist/changes.json +36 -0
- package/packages/bundled-features/src/waitlist/feature.ts +1 -0
- package/packages/bundled-features/src/waitlist/handlers/export-purge.write.ts +2 -1
- package/packages/bundled-features/src/waitlist/handlers/submit.write.ts +28 -9
- package/packages/bundled-features/src/waitlist/import.ts +28 -3
- package/packages/bundled-features/src/waitlist/legacy-migrate.ts +24 -4
- package/packages/bundled-features/src/waitlist/link-on-signup.ts +20 -10
- package/packages/bundled-features/src/waitlist/options.ts +5 -1
- package/packages/bundled-features/src/workflow-runner/handlers/resume-run.write.ts +1 -1
- package/samples/apps/styleguide/src/app/client.tsx +2 -0
- package/samples/apps/styleguide/src/app/seed.ts +6 -4
- package/samples/apps/styleguide/src/app/server.ts +4 -1
- package/samples/apps/styleguide/src/features/widgets/feature.ts +15 -4
- package/samples/apps/styleguide/src/features/widgets/i18n.ts +11 -0
- package/samples/apps/styleguide/src/features/widgets/web/Widgets.tsx +24 -0
- package/samples/apps/styleguide/src/features/workboard/feature.ts +185 -0
- package/samples/apps/styleguide/src/features/workboard/i18n.ts +27 -0
- package/samples/apps/styleguide/src/features/workboard/seed.ts +121 -0
- package/samples/apps/styleguide/src/features/workboard/web.ts +13 -0
- package/samples/apps/ui-walkthrough/src/features/rental/i18n.ts +2 -0
- package/samples/apps/ui-walkthrough/src/features/rental/screens.ts +9 -0
- package/samples/apps/use-all-bundled/bin/main.ts +2 -0
- package/samples/apps/use-all-bundled/e2e-billing-gallery/gallery-constants.ts +5 -0
- package/samples/apps/use-all-bundled/e2e-billing-gallery/gallery-features.ts +90 -15
- package/samples/apps/use-all-bundled/e2e-billing-gallery/server.ts +16 -6
- package/samples/apps/use-all-bundled/feature-manifest.json +136 -3
- package/samples/apps/use-all-bundled/kumiko/schema.ts +2 -0
- package/samples/apps/use-all-bundled/kumiko.config.ts +2 -0
- package/samples/apps/use-all-bundled/package.json +2 -1
- package/samples/apps/use-all-bundled/src/app/dev-app-options.ts +2 -0
- package/samples/apps/use-all-bundled/src/app/notes-feature.ts +2 -0
- package/samples/apps/use-all-bundled/src/bundled-lists.ts +15 -0
- package/samples/apps/use-all-bundled/src/run-config.ts +9 -3
- package/samples/recipes/auth-mfa/README.md +5 -0
- package/samples/recipes/auth-mfa/package.json +5 -2
- package/samples/recipes/auth-mfa/src/web/confirmation-code-step.tsx +49 -0
- package/samples/recipes/auth-mfa/tsconfig.json +8 -0
- package/samples/recipes/env-composition/README.md +3 -1
- package/samples/recipes/env-composition/src/env.ts +2 -0
- package/samples/recipes/golden-path/src/feature.ts +2 -1
- package/samples/recipes/legal-pages/README.md +1 -1
- package/samples/recipes/lifecycle-hooks/README.md +4 -0
- package/samples/recipes/lifecycle-hooks/src/feature.ts +26 -1
- package/samples/recipes/list-table-options/README.md +50 -0
- package/samples/recipes/list-table-options/package.json +11 -0
- package/samples/recipes/list-table-options/src/feature.ts +221 -0
- package/samples/recipes/pdf-audit-export/src/feature.ts +2 -1
- package/samples/recipes/rate-limiting/README.md +18 -0
- package/samples/recipes/rate-limiting/src/feature.ts +12 -0
- package/samples/recipes/state-machine/src/events.ts +3 -2
- package/samples/recipes/tenant-isolation/README.md +1 -0
- package/samples/recipes/tenant-isolation/src/feature.ts +36 -0
- package/packages/bundled-features/src/billing-foundation/consumer-protection/stored-user-email.ts +0 -12
|
@@ -27,6 +27,10 @@ back.
|
|
|
27
27
|
- **`r.hook({ type: "postDelete", target: { allOf: entityRef }, handler })`** — fires after commit.
|
|
28
28
|
External side effects (logs, notifications) belong here because
|
|
29
29
|
failures cannot roll the delete back.
|
|
30
|
+
- **`ctx.scheduleAfterRollback(fn)`** — the counterpart of `scheduleAfterCommit`: runs `fn` when
|
|
31
|
+
the handler transaction (or the enclosing savepoint of a nested `ctx.write`) does not commit.
|
|
32
|
+
Use it to give back something reserved outside the transaction, like the slot in
|
|
33
|
+
`article:reserve-slot`. A failed COMMIT triggers it too, so write the callback idempotent.
|
|
30
34
|
|
|
31
35
|
## When to reach for it
|
|
32
36
|
|
|
@@ -13,7 +13,12 @@ import {
|
|
|
13
13
|
defineFeature,
|
|
14
14
|
type SaveContext,
|
|
15
15
|
} from "@cosmicdrift/kumiko-framework/engine";
|
|
16
|
-
import {
|
|
16
|
+
import {
|
|
17
|
+
ConflictError,
|
|
18
|
+
UnprocessableError,
|
|
19
|
+
writeFailure,
|
|
20
|
+
} from "@cosmicdrift/kumiko-framework/errors";
|
|
21
|
+
import * as z from "zod";
|
|
17
22
|
|
|
18
23
|
export const articleEntity = createEntity({
|
|
19
24
|
table: "read_sample_articles",
|
|
@@ -35,6 +40,9 @@ export const articleEntity = createEntity({
|
|
|
35
40
|
// Collect hook events for testing
|
|
36
41
|
export const hookLog: { type: string; data: Record<string, unknown> }[] = [];
|
|
37
42
|
|
|
43
|
+
// Stands in for a quota held in an external system, outside the handler transaction.
|
|
44
|
+
export const reservedSlots = new Set<string>();
|
|
45
|
+
|
|
38
46
|
const editorWrite = { access: { roles: ["Admin", "Editor"] } } as const;
|
|
39
47
|
|
|
40
48
|
export const articlesFeature = defineFeature("blog", (r) => {
|
|
@@ -48,6 +56,23 @@ export const articlesFeature = defineFeature("blog", (r) => {
|
|
|
48
56
|
);
|
|
49
57
|
r.writeHandler(defineEntityDeleteHandler("article", articleEntity, editorWrite));
|
|
50
58
|
|
|
59
|
+
// --- scheduleAfterRollback: give back a reservation made outside the transaction when it does
|
|
60
|
+
// not commit. The callback must be idempotent: a failed COMMIT also triggers it.
|
|
61
|
+
r.writeHandler({
|
|
62
|
+
name: "article:reserve-slot",
|
|
63
|
+
schema: z.object({ slot: z.string(), reject: z.boolean() }),
|
|
64
|
+
access: editorWrite.access,
|
|
65
|
+
handler: async (event, ctx) => {
|
|
66
|
+
const { slot, reject } = event.payload;
|
|
67
|
+
reservedSlots.add(slot);
|
|
68
|
+
ctx.scheduleAfterRollback?.(async () => {
|
|
69
|
+
reservedSlots.delete(slot);
|
|
70
|
+
});
|
|
71
|
+
if (reject) return writeFailure(new UnprocessableError("slot_rejected"));
|
|
72
|
+
return { isSuccess: true as const, data: {} };
|
|
73
|
+
},
|
|
74
|
+
});
|
|
75
|
+
|
|
51
76
|
// --- Validation hook on create: reject banned words + length ---
|
|
52
77
|
r.hook({
|
|
53
78
|
type: "validation",
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# List table options
|
|
2
|
+
|
|
3
|
+
Opt-in table behaviour for entity, projection and related lists. A list without
|
|
4
|
+
the `table` block renders and behaves as before.
|
|
5
|
+
|
|
6
|
+
## What it shows
|
|
7
|
+
|
|
8
|
+
- `table.rememberState`: search, sort, page size, facets and date range survive
|
|
9
|
+
leaving and re-entering the list within the browser session.
|
|
10
|
+
- `table.resetAll`: one button resets everything that differs from the defaults.
|
|
11
|
+
- `table.columnPicker`: the view menu lets the user hide and reorder columns;
|
|
12
|
+
`hidden: true` on a column starts it hidden.
|
|
13
|
+
- `table.density`: row density default and an optional user choice.
|
|
14
|
+
- `table.pinning`: the user pins columns left or right in the view menu;
|
|
15
|
+
`pinned: "left"` on a column starts it pinned.
|
|
16
|
+
- `table.resizing`: columns can be resized (table-layout fixed); `width` and
|
|
17
|
+
`minWidth` on a column are start values in px.
|
|
18
|
+
- `table.keyboardNavigation`: rows are one tab stop, arrow keys move between rows,
|
|
19
|
+
Space selects the focused row.
|
|
20
|
+
- `table.export`: an Export button downloads the whole filtered list as an XLSX
|
|
21
|
+
file; `maxRows` lowers the default limit of 10000 rows.
|
|
22
|
+
Reference columns export their label and timestamps use the browser time zone.
|
|
23
|
+
- `bulkActions`: row checkboxes and a selection bar. "Delete" runs the delete
|
|
24
|
+
row action once per selected row; done tasks are skipped.
|
|
25
|
+
- `table.grouping`: the server groups the list by status; each header shows the
|
|
26
|
+
group's count and estimate sum, "done" starts collapsed and the view menu
|
|
27
|
+
offers grouping by priority or none.
|
|
28
|
+
- `table.totals`: a footer row with the estimate sum over the filtered list.
|
|
29
|
+
- `facets.estimateHours: { display: "range" }`: from/to inputs for a number field.
|
|
30
|
+
- The same block on a `projectionList`, and `task:workload`, a hand-written list
|
|
31
|
+
query that keeps grouping and totals by accepting `aggregate`
|
|
32
|
+
(`entityListSchema`) and answering it with `answerListAggregate`.
|
|
33
|
+
|
|
34
|
+
## Source
|
|
35
|
+
|
|
36
|
+
Feature entry point: `src/feature.ts`.
|
|
37
|
+
|
|
38
|
+
## Tests
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
cd samples/recipes/list-table-options
|
|
42
|
+
bun test
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
The test boots the feature through the boot validator and checks that a
|
|
46
|
+
misspelled key, a hidden column without the picker, an unknown density, a pinned column
|
|
47
|
+
without `pinning`, a width without `resizing` and a bulk action on a navigate
|
|
48
|
+
row action are rejected.
|
|
49
|
+
`workload.integration.test.ts` runs the hand-written query over HTTP and checks
|
|
50
|
+
the grouped sums and the filtered total.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@cosmicdrift/kumiko-sample-list-table-options",
|
|
3
|
+
"description": "Opt-in table behaviour of a list: remembered search/sort/filters, one reset button, column picker with hidden columns, row density, server-side groups with sums, a totals row and a number range facet.",
|
|
4
|
+
"private": true,
|
|
5
|
+
"dependencies": {
|
|
6
|
+
"@cosmicdrift/kumiko-framework": "workspace:*"
|
|
7
|
+
},
|
|
8
|
+
"devDependencies": {
|
|
9
|
+
"@cosmicdrift/kumiko-testing": "workspace:*"
|
|
10
|
+
}
|
|
11
|
+
}
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
import {
|
|
2
|
+
answerListAggregate,
|
|
3
|
+
buildEntityTable,
|
|
4
|
+
createEventStoreExecutor,
|
|
5
|
+
} from "@cosmicdrift/kumiko-framework/db";
|
|
6
|
+
import {
|
|
7
|
+
createDateField,
|
|
8
|
+
createEntity,
|
|
9
|
+
createNumberField,
|
|
10
|
+
createSelectField,
|
|
11
|
+
createTextField,
|
|
12
|
+
defineFeature,
|
|
13
|
+
entityListSchema,
|
|
14
|
+
type FeatureDefinition,
|
|
15
|
+
} from "@cosmicdrift/kumiko-framework/engine";
|
|
16
|
+
import type { ListTableOptions } from "@cosmicdrift/kumiko-framework/ui-types";
|
|
17
|
+
import { z } from "zod";
|
|
18
|
+
|
|
19
|
+
export const taskEntity = createEntity({
|
|
20
|
+
table: "read_list_table_tasks",
|
|
21
|
+
fields: {
|
|
22
|
+
title: createTextField({
|
|
23
|
+
personal: false,
|
|
24
|
+
reason: "is_business_data",
|
|
25
|
+
required: true,
|
|
26
|
+
searchable: true,
|
|
27
|
+
sortable: true,
|
|
28
|
+
}),
|
|
29
|
+
status: createSelectField({
|
|
30
|
+
options: ["open", "inProgress", "blocked", "done"] as const,
|
|
31
|
+
default: "open",
|
|
32
|
+
sortable: true,
|
|
33
|
+
filterable: true,
|
|
34
|
+
}),
|
|
35
|
+
priority: createSelectField({
|
|
36
|
+
options: ["low", "medium", "high"] as const,
|
|
37
|
+
default: "medium",
|
|
38
|
+
sortable: true,
|
|
39
|
+
filterable: true,
|
|
40
|
+
}),
|
|
41
|
+
dueDate: createDateField({ sortable: true }),
|
|
42
|
+
assignee: createTextField({ personal: false, reason: "is_business_data", sortable: true }),
|
|
43
|
+
estimateHours: createNumberField({ sortable: true, filterable: true }),
|
|
44
|
+
},
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
export const openAccess = {
|
|
48
|
+
access: {
|
|
49
|
+
openToAll: { reason: "recipe: any signed-in user manages the tasks; no per-user ownership" },
|
|
50
|
+
},
|
|
51
|
+
} as const;
|
|
52
|
+
|
|
53
|
+
// Overridable so a test can feed the boot validator a block that must be rejected.
|
|
54
|
+
export function createTaskBoardFeature(
|
|
55
|
+
taskTable: ListTableOptions = {
|
|
56
|
+
rememberState: true,
|
|
57
|
+
resetAll: true,
|
|
58
|
+
columnPicker: true,
|
|
59
|
+
density: { default: "comfortable", userChoice: true },
|
|
60
|
+
pinning: true,
|
|
61
|
+
resizing: true,
|
|
62
|
+
keyboardNavigation: true,
|
|
63
|
+
export: { xlsx: true, maxRows: 5000 },
|
|
64
|
+
grouping: { field: "status", collapsedWhen: "done", userChoice: true, fields: ["priority"] },
|
|
65
|
+
totals: [{ field: "estimateHours", kind: "sum" }],
|
|
66
|
+
},
|
|
67
|
+
): FeatureDefinition {
|
|
68
|
+
return defineFeature("task-board", (r) => {
|
|
69
|
+
r.crud("task", taskEntity, { write: openAccess, read: openAccess });
|
|
70
|
+
|
|
71
|
+
r.screen({
|
|
72
|
+
id: "task-list",
|
|
73
|
+
type: "entityList",
|
|
74
|
+
entity: "task",
|
|
75
|
+
columns: [
|
|
76
|
+
// Starts pinned left; width and minWidth are start values for resizing.
|
|
77
|
+
{ field: "title", pinned: "left", width: 280, minWidth: 160 },
|
|
78
|
+
"status",
|
|
79
|
+
"priority",
|
|
80
|
+
{ field: "dueDate", hideOnNarrow: true },
|
|
81
|
+
{ field: "assignee", hideOnNarrow: true },
|
|
82
|
+
// Starts hidden; the user can add it back in the view menu.
|
|
83
|
+
{ field: "estimateHours", hidden: true, hideOnNarrow: true },
|
|
84
|
+
],
|
|
85
|
+
facets: { status: { display: "chips" }, priority: {}, estimateHours: { display: "range" } },
|
|
86
|
+
defaultSort: { field: "dueDate", dir: "asc" },
|
|
87
|
+
searchable: true,
|
|
88
|
+
table: taskTable,
|
|
89
|
+
rowActions: [
|
|
90
|
+
{ kind: "navigate", id: "open", label: "Open", screen: "task-edit", rowClick: true },
|
|
91
|
+
{
|
|
92
|
+
kind: "writeHandler",
|
|
93
|
+
id: "delete",
|
|
94
|
+
label: "Delete",
|
|
95
|
+
handler: "task-board:write:task:delete",
|
|
96
|
+
style: "danger",
|
|
97
|
+
// Done tasks are skipped by the bulk run and counted as skipped.
|
|
98
|
+
visible: { field: "status", ne: "done" },
|
|
99
|
+
},
|
|
100
|
+
],
|
|
101
|
+
// Row checkboxes and a selection bar; each selected row gets its own delete write.
|
|
102
|
+
bulkActions: [{ id: "delete", rowAction: "delete" }],
|
|
103
|
+
access: openAccess.access,
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
r.screen({
|
|
107
|
+
id: "task-edit",
|
|
108
|
+
type: "entityEdit",
|
|
109
|
+
entity: "task",
|
|
110
|
+
dormant: true,
|
|
111
|
+
listScreenId: "task-list",
|
|
112
|
+
layout: {
|
|
113
|
+
sections: [
|
|
114
|
+
{
|
|
115
|
+
columns: 2,
|
|
116
|
+
fields: ["title", "assignee", "status", "priority", "dueDate", "estimateHours"],
|
|
117
|
+
},
|
|
118
|
+
],
|
|
119
|
+
},
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
// The same block works on a list over any query.
|
|
123
|
+
r.queryHandler({
|
|
124
|
+
name: "task:overdue",
|
|
125
|
+
schema: z.object({}),
|
|
126
|
+
handler: async () => ({
|
|
127
|
+
rows: [{ id: "t1", title: "Quarterly VAT filing", assignee: "Ines", daysLate: 3 }],
|
|
128
|
+
nextCursor: null,
|
|
129
|
+
}),
|
|
130
|
+
access: openAccess.access,
|
|
131
|
+
outputSchema: z.object({
|
|
132
|
+
rows: z.array(
|
|
133
|
+
z.object({
|
|
134
|
+
id: z.string(),
|
|
135
|
+
title: z.string(),
|
|
136
|
+
assignee: z.string(),
|
|
137
|
+
daysLate: z.number(),
|
|
138
|
+
}),
|
|
139
|
+
),
|
|
140
|
+
nextCursor: z.string().nullable(),
|
|
141
|
+
}),
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
r.screen({
|
|
145
|
+
id: "task-overdue",
|
|
146
|
+
type: "projectionList",
|
|
147
|
+
query: "task-board:query:task:overdue",
|
|
148
|
+
columns: ["title", "assignee", "daysLate"],
|
|
149
|
+
table: { rememberState: true, density: { userChoice: true } },
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
// A hand-written list query keeps grouping and totals when it accepts
|
|
153
|
+
// `aggregate` (entityListSchema carries it) and answers it on the server.
|
|
154
|
+
const taskExecutor = createEventStoreExecutor(
|
|
155
|
+
buildEntityTable("task", taskEntity),
|
|
156
|
+
taskEntity,
|
|
157
|
+
{
|
|
158
|
+
entityName: "task",
|
|
159
|
+
},
|
|
160
|
+
);
|
|
161
|
+
r.queryHandler({
|
|
162
|
+
name: "task:workload",
|
|
163
|
+
schema: entityListSchema,
|
|
164
|
+
handler: async (query, ctx) => {
|
|
165
|
+
const parentVisibility = { entities: ctx.registry.getAllEntities() };
|
|
166
|
+
const listPayload = query.payload;
|
|
167
|
+
if (listPayload.aggregate !== undefined) {
|
|
168
|
+
return answerListAggregate(
|
|
169
|
+
taskExecutor,
|
|
170
|
+
{ ...listPayload, aggregate: listPayload.aggregate },
|
|
171
|
+
query.user,
|
|
172
|
+
ctx.db,
|
|
173
|
+
{ parentVisibility },
|
|
174
|
+
{
|
|
175
|
+
entity: taskEntity,
|
|
176
|
+
resolveEntity: (name) => ctx.registry.getEntity(name),
|
|
177
|
+
viewer: { user: query.user, parentVisibility },
|
|
178
|
+
},
|
|
179
|
+
);
|
|
180
|
+
}
|
|
181
|
+
return taskExecutor.list(listPayload, query.user, ctx.db, { parentVisibility });
|
|
182
|
+
},
|
|
183
|
+
access: openAccess.access,
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
r.screen({
|
|
187
|
+
id: "task-workload",
|
|
188
|
+
type: "projectionList",
|
|
189
|
+
query: "task-board:query:task:workload",
|
|
190
|
+
columns: ["title", "assignee", "status", "estimateHours"],
|
|
191
|
+
defaultSort: { field: "title", dir: "asc" },
|
|
192
|
+
table: {
|
|
193
|
+
grouping: { field: "assignee" },
|
|
194
|
+
totals: [{ field: "estimateHours", kind: "sum" }],
|
|
195
|
+
},
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
r.nav({ id: "tasks", label: "task-board:nav.tasks", icon: "list", order: 10 });
|
|
199
|
+
r.nav({
|
|
200
|
+
id: "task-list",
|
|
201
|
+
label: "task-board:nav.list",
|
|
202
|
+
parent: "task-board:nav:tasks",
|
|
203
|
+
screen: "task-board:screen:task-list",
|
|
204
|
+
order: 10,
|
|
205
|
+
});
|
|
206
|
+
r.nav({
|
|
207
|
+
id: "task-overdue",
|
|
208
|
+
label: "task-board:nav.overdue",
|
|
209
|
+
parent: "task-board:nav:tasks",
|
|
210
|
+
screen: "task-board:screen:task-overdue",
|
|
211
|
+
order: 20,
|
|
212
|
+
});
|
|
213
|
+
r.nav({
|
|
214
|
+
id: "task-workload",
|
|
215
|
+
label: "task-board:nav.workload",
|
|
216
|
+
parent: "task-board:nav:tasks",
|
|
217
|
+
screen: "task-board:screen:task-workload",
|
|
218
|
+
order: 30,
|
|
219
|
+
});
|
|
220
|
+
});
|
|
221
|
+
}
|
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
createTextField,
|
|
16
16
|
defineFeature,
|
|
17
17
|
defineWriteHandler,
|
|
18
|
+
MAX_LIST_LIMIT,
|
|
18
19
|
} from "@cosmicdrift/kumiko-framework/engine";
|
|
19
20
|
import { UnprocessableError, writeFailure } from "@cosmicdrift/kumiko-framework/errors";
|
|
20
21
|
import {
|
|
@@ -51,7 +52,7 @@ function toFileNameSegment(text: string): string {
|
|
|
51
52
|
}
|
|
52
53
|
|
|
53
54
|
// One row above the limit tells "exactly full" from "cut off"; entity lists cap at 200 rows.
|
|
54
|
-
const AUDIT_ROW_LIMIT =
|
|
55
|
+
const AUDIT_ROW_LIMIT = MAX_LIST_LIMIT - 1;
|
|
55
56
|
|
|
56
57
|
const access = { access: { roles: ["Admin", "User"] } } as const;
|
|
57
58
|
|
|
@@ -28,6 +28,24 @@ exhausted) and no bucket is charged. Each `per` may appear once; an empty list f
|
|
|
28
28
|
An anonymous handler needs every bucket to be ip-keyed, and `{ disabled: true, reason }`
|
|
29
29
|
stays a single declaration.
|
|
30
30
|
|
|
31
|
+
## Cost per payload element
|
|
32
|
+
|
|
33
|
+
A bulk handler books one token per item instead of one per request. Declare `rateLimitCost`
|
|
34
|
+
next to `rateLimit`:
|
|
35
|
+
|
|
36
|
+
```ts illustration
|
|
37
|
+
r.writeHandler({
|
|
38
|
+
// ...
|
|
39
|
+
rateLimit: { per: "tenant+handler", limit: 20, windowSeconds: 600 },
|
|
40
|
+
rateLimitCost: (payload) => payload.files.length,
|
|
41
|
+
});
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
The cost is computed from the validated payload and booked on every declared bucket, so an
|
|
45
|
+
invalid payload costs nothing and a rejected request consumes nothing. Results below 1 count
|
|
46
|
+
as 1. Because it books after the parse, boot rejects `rateLimitCost` on anonymous or ip-limited
|
|
47
|
+
handlers.
|
|
48
|
+
|
|
31
49
|
## Per-payload limits
|
|
32
50
|
|
|
33
51
|
A public write handler that mails an address typed by the caller is also limited per
|
|
@@ -33,5 +33,17 @@ export function createRateLimitShowcaseFeature(): FeatureDefinition {
|
|
|
33
33
|
{ per: "tenant+handler", limit: 3, windowSeconds: 3600 },
|
|
34
34
|
],
|
|
35
35
|
});
|
|
36
|
+
|
|
37
|
+
// Bulk upload: a request books one token per item instead of one per request. The cost
|
|
38
|
+
// comes from the validated payload, so a schema-invalid request costs nothing; a request
|
|
39
|
+
// that would exceed the remaining budget is rejected and consumes nothing.
|
|
40
|
+
r.writeHandler({
|
|
41
|
+
name: "bulk-upload",
|
|
42
|
+
schema: z.object({ files: z.array(z.string()).min(1).max(20) }),
|
|
43
|
+
handler: async () => ({ isSuccess: true as const, data: {} }),
|
|
44
|
+
access: { roles: ["Admin", "User"] },
|
|
45
|
+
rateLimit: { per: "tenant+handler", limit: 20, windowSeconds: 600 },
|
|
46
|
+
rateLimitCost: (payload) => payload.files.length,
|
|
47
|
+
});
|
|
36
48
|
});
|
|
37
49
|
}
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
import type { EventDef } from "@cosmicdrift/kumiko-framework/engine";
|
|
7
7
|
import { z } from "zod";
|
|
8
|
+
import { INVOICE_STATES } from "./entities/invoice.ts";
|
|
8
9
|
|
|
9
10
|
export const FEATURE_NAME = "billing";
|
|
10
11
|
export const ENTITY_NAME = "invoice";
|
|
@@ -36,8 +37,8 @@ export const INVOICE_CREATED_EVENT = `${ENTITY_NAME}.created` as const;
|
|
|
36
37
|
|
|
37
38
|
export const emptyEventPayload = z.object({});
|
|
38
39
|
export const statusForcedPayload = z.object({
|
|
39
|
-
newStatus: z.enum(
|
|
40
|
-
fromStatus: z.enum(
|
|
40
|
+
newStatus: z.enum(INVOICE_STATES),
|
|
41
|
+
fromStatus: z.enum(INVOICE_STATES),
|
|
41
42
|
});
|
|
42
43
|
|
|
43
44
|
// The EventDefs r.defineEvent returns inside the feature setup. Handlers receive them so that
|
|
@@ -5,6 +5,7 @@ Multi-tenant data isolation by default.
|
|
|
5
5
|
## What it shows
|
|
6
6
|
|
|
7
7
|
- Tenant filter on ordinary handlers
|
|
8
|
+
- `ctx.db.upsert`: insert-or-update on a tenant-scoped, non-event-sourced table. `conflictTarget` must contain `tenantId` and match a declared unique index (here `tenant_id, key`); event-fed tables are rejected
|
|
8
9
|
- What breaks if you forget tenancy (and how the framework prevents it)
|
|
9
10
|
|
|
10
11
|
## Source
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import { defineUnmanagedTable } from "@cosmicdrift/kumiko-framework/db";
|
|
1
2
|
import { createEntity, createTextField, defineFeature } from "@cosmicdrift/kumiko-framework/engine";
|
|
3
|
+
import * as z from "zod";
|
|
2
4
|
|
|
3
5
|
export const noteEntity = createEntity({
|
|
4
6
|
table: "read_sample_notes",
|
|
@@ -8,6 +10,26 @@ export const noteEntity = createEntity({
|
|
|
8
10
|
},
|
|
9
11
|
});
|
|
10
12
|
|
|
13
|
+
// Not event-sourced: ctx.db.upsert rejects executor-managed tables.
|
|
14
|
+
export const preferenceTable = defineUnmanagedTable({
|
|
15
|
+
tableName: "sample_tenant_preferences",
|
|
16
|
+
columns: [
|
|
17
|
+
{
|
|
18
|
+
name: "id",
|
|
19
|
+
pgType: "uuid",
|
|
20
|
+
notNull: true,
|
|
21
|
+
primaryKey: true,
|
|
22
|
+
defaultSql: "gen_random_uuid()",
|
|
23
|
+
},
|
|
24
|
+
{ name: "tenant_id", pgType: "uuid", notNull: true },
|
|
25
|
+
{ name: "key", pgType: "text", notNull: true },
|
|
26
|
+
{ name: "value", pgType: "text", notNull: true },
|
|
27
|
+
],
|
|
28
|
+
indexes: [
|
|
29
|
+
{ name: "sample_tenant_preferences_tenant_key", columns: ["tenant_id", "key"], unique: true },
|
|
30
|
+
],
|
|
31
|
+
});
|
|
32
|
+
|
|
11
33
|
const adminWrite = { access: { roles: ["Admin"] } } as const;
|
|
12
34
|
const openRead = {
|
|
13
35
|
access: {
|
|
@@ -25,4 +47,18 @@ export const noteFeature = defineFeature("notes", (r) => {
|
|
|
25
47
|
read: openRead,
|
|
26
48
|
verbs: { update: false, delete: false, restore: false },
|
|
27
49
|
});
|
|
50
|
+
|
|
51
|
+
r.storeTable(preferenceTable, { reason: "settings.tenant_preferences" });
|
|
52
|
+
|
|
53
|
+
r.writeHandler({
|
|
54
|
+
name: "preference:set",
|
|
55
|
+
schema: z.object({ key: z.string().min(1), value: z.string() }),
|
|
56
|
+
handler: async (event, ctx) => {
|
|
57
|
+
const row = await ctx.db.upsert<{ value: string }>(preferenceTable, event.payload, {
|
|
58
|
+
conflictTarget: ["tenantId", "key"],
|
|
59
|
+
});
|
|
60
|
+
return { isSuccess: true, data: { value: row?.value } };
|
|
61
|
+
},
|
|
62
|
+
...adminWrite,
|
|
63
|
+
});
|
|
28
64
|
});
|
package/packages/bundled-features/src/billing-foundation/consumer-protection/stored-user-email.ts
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import { PII_ERASED_SENTINEL } from "@cosmicdrift/kumiko-framework/crypto";
|
|
2
|
-
import { decryptStoredPii } from "../../shared/index.ts";
|
|
3
|
-
|
|
4
|
-
// findForAuth returns the stored column, which is ciphertext under an active KMS;
|
|
5
|
-
// channel-email refuses to mail a ciphertext address. Null for an erased subject.
|
|
6
|
-
export async function openStoredUserEmail(
|
|
7
|
-
stored: string,
|
|
8
|
-
fallbackRequestId: string,
|
|
9
|
-
): Promise<string | null> {
|
|
10
|
-
const email = await decryptStoredPii(stored, "email", fallbackRequestId);
|
|
11
|
-
return email === PII_ERASED_SENTINEL ? null : email;
|
|
12
|
-
}
|