@cosmicdrift/kumiko-bundled-features 0.363.0 → 0.364.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/audit/handlers/list.query.js +9 -5
- package/dist/auth-email-password/bootstrap.d.ts +8 -5
- package/dist/auth-email-password/bootstrap.js +12 -4
- package/dist/auth-email-password/email-rate-limits.d.ts +4 -0
- package/dist/auth-email-password/email-rate-limits.js +13 -0
- package/dist/auth-email-password/handlers/change-password.write.d.ts +1 -1
- package/dist/auth-email-password/handlers/change-password.write.js +6 -2
- package/dist/auth-email-password/handlers/confirm-account-unlock.write.d.ts +1 -1
- package/dist/auth-email-password/handlers/invite-accept-with-login.write.d.ts +1 -1
- package/dist/auth-email-password/handlers/invite-accept.write.d.ts +1 -1
- package/dist/auth-email-password/handlers/invite-create.write.d.ts +2 -2
- package/dist/auth-email-password/handlers/invite-signup-complete.write.d.ts +1 -1
- package/dist/auth-email-password/handlers/login.write.d.ts +1 -1
- package/dist/auth-email-password/handlers/logout.write.d.ts +1 -1
- package/dist/auth-email-password/handlers/logout.write.js +6 -2
- package/dist/auth-email-password/handlers/request-account-unlock.write.d.ts +1 -1
- package/dist/auth-email-password/handlers/request-email-verification.write.d.ts +1 -1
- package/dist/auth-email-password/handlers/request-password-reset.write.d.ts +1 -1
- package/dist/auth-email-password/handlers/reset-password.write.d.ts +1 -1
- package/dist/auth-email-password/handlers/signup-confirm.write.d.ts +1 -1
- package/dist/auth-email-password/handlers/signup-request.write.d.ts +1 -1
- package/dist/auth-email-password/handlers/signup-request.write.js +2 -1
- package/dist/auth-email-password/handlers/switch-tenant-mfa-gate.write.d.ts +1 -1
- package/dist/auth-email-password/handlers/token-request-handler.d.ts +1 -1
- package/dist/auth-email-password/handlers/token-request-handler.js +2 -1
- package/dist/auth-email-password/handlers/verify-email.write.d.ts +1 -1
- package/dist/auth-email-password/web/auth-gate.js +16 -4
- package/dist/auth-mfa/handlers/disable.write.d.ts +1 -1
- package/dist/auth-mfa/handlers/enable-confirm-preauth.write.d.ts +1 -1
- package/dist/auth-mfa/handlers/enable-confirm.write.d.ts +1 -1
- package/dist/auth-mfa/handlers/enable-start-preauth.write.d.ts +1 -1
- package/dist/auth-mfa/handlers/enable-start.write.d.ts +1 -1
- package/dist/auth-mfa/handlers/regenerate-recovery.write.d.ts +1 -1
- package/dist/auth-mfa/handlers/verify.write.d.ts +1 -1
- package/dist/billing-foundation/consumer-protection/confirmation-mail.js +52 -5
- package/dist/billing-foundation/consumer-protection/consent-gate.d.ts +3 -0
- package/dist/billing-foundation/consumer-protection/consent-gate.js +21 -2
- package/dist/billing-foundation/consumer-protection/mail-notifications.js +1 -1
- package/dist/billing-foundation/consumer-protection/register-termination.js +11 -1
- package/dist/billing-foundation/consumer-protection/termination-mail.d.ts +5 -3
- package/dist/billing-foundation/consumer-protection/termination-mail.js +1 -2
- package/dist/billing-foundation/consumer-protection/termination-receipt-limit.d.ts +6 -0
- package/dist/billing-foundation/consumer-protection/termination-receipt-limit.js +34 -0
- package/dist/billing-foundation/consumer-protection/termination-record.d.ts +2 -0
- package/dist/billing-foundation/consumer-protection/termination-record.js +7 -1
- package/dist/billing-foundation/consumer-protection/termination-request.js +30 -20
- package/dist/billing-foundation/consumer-protection/termination-texts.d.ts +1 -1
- package/dist/billing-foundation/consumer-protection/termination-texts.js +6 -0
- package/dist/billing-foundation/consumer-protection/withdrawal-period.d.ts +10 -0
- package/dist/billing-foundation/consumer-protection/withdrawal-period.js +26 -0
- package/dist/billing-foundation/handlers/create-checkout-session.write.js +11 -5
- package/dist/billing-foundation/handlers/process-event.write.js +10 -6
- package/dist/billing-foundation/i18n.js +3 -0
- package/dist/billing-foundation/subscription-tier-sync.d.ts +7 -1
- package/dist/billing-foundation/subscription-tier-sync.js +13 -5
- package/dist/billing-foundation/types.d.ts +7 -2
- package/dist/cap-counter/calendar-cap-usage-reservation.js +2 -0
- package/dist/cap-counter/enforce-cap.d.ts +18 -1
- package/dist/cap-counter/enforce-cap.js +49 -6
- package/dist/cap-counter/index.d.ts +1 -1
- package/dist/cap-counter/index.js +1 -1
- package/dist/cap-counter/with-cap-enforcement.d.ts +2 -0
- package/dist/channel-email/email-channel.js +16 -4
- package/dist/channel-in-app/handlers/mark-all-read.write.d.ts +1 -1
- package/dist/channel-in-app/handlers/mark-read.write.d.ts +1 -1
- package/dist/compliance-profiles/handlers/set-profile.write.d.ts +1 -1
- package/dist/config/handlers/reset.write.d.ts +1 -1
- package/dist/config/handlers/set.write.d.ts +1 -1
- package/dist/config/handlers/set.write.js +2 -2
- package/dist/config/resolver.d.ts +1 -0
- package/dist/config/resolver.js +11 -14
- package/dist/config/write-helpers.d.ts +2 -0
- package/dist/config/write-helpers.js +19 -0
- package/dist/crypto-shredding/handlers/forget-subject.write.d.ts +1 -1
- package/dist/crypto-shredding/handlers/forget-subject.write.js +29 -19
- package/dist/custom-fields/handlers/update-tenant-field.write.js +0 -1
- package/dist/delivery/attachment-ref.d.ts +2 -0
- package/dist/delivery/attachment-ref.js +14 -0
- package/dist/delivery/channel-context.js +19 -1
- package/dist/delivery/handlers/resubscribe-address.write.d.ts +1 -1
- package/dist/delivery/handlers/resubscribe-user.write.d.ts +1 -1
- package/dist/delivery/handlers/set-preference.write.d.ts +1 -1
- package/dist/delivery/handlers/unsubscribe-address.write.d.ts +1 -1
- package/dist/delivery/handlers/unsubscribe-user.write.d.ts +1 -1
- package/dist/delivery/index.d.ts +1 -1
- package/dist/delivery/index.js +1 -1
- package/dist/delivery/jobs.js +43 -11
- package/dist/delivery/types.d.ts +7 -1
- package/dist/feature-toggles/handlers/set.write.d.ts +1 -1
- package/dist/file-derivatives/handlers/public-variant.query.js +1 -1
- package/dist/file-foundation/read-file-ref-content.d.ts +1 -1
- package/dist/file-foundation/read-file-ref-content.js +6 -4
- package/dist/files-tenant-data/hooks.js +4 -0
- package/dist/form-draft/handlers/discard.write.d.ts +1 -1
- package/dist/form-draft/handlers/save.write.d.ts +1 -1
- package/dist/inbound-mail-foundation/oauth-access-token.js +8 -3
- package/dist/inbound-mail-foundation/watch-supervisor.js +3 -1
- package/dist/jobs/db/queries/stale-run-sweep.d.ts +1 -1
- package/dist/jobs/db/queries/stale-run-sweep.js +12 -5
- package/dist/jobs/feature.js +5 -3
- package/dist/jobs/handlers/list.query.js +3 -1
- package/dist/jobs/handlers/retry.write.d.ts +1 -1
- package/dist/jobs/handlers/stale-run-sweep.job.d.ts +1 -1
- package/dist/jobs/handlers/stale-run-sweep.job.js +2 -2
- package/dist/jobs/handlers/trigger.write.d.ts +1 -1
- package/dist/jobs/job-run-logger.js +32 -10
- package/dist/jobs/tenant-job-run-table.js +3 -2
- package/dist/ledger/entity.js +3 -1
- package/dist/managed-pages/handlers/set.write.d.ts +1 -1
- package/dist/personal-access-tokens/handlers/create.write.d.ts +1 -1
- package/dist/personal-access-tokens/handlers/revoke.write.d.ts +1 -1
- package/dist/renderer-foundation/types.d.ts +2 -0
- package/dist/renderer-pdf/render-table-pdf.d.ts +27 -33
- package/dist/renderer-pdf/render-table-pdf.js +95 -34
- package/dist/secrets/feature.js +4 -3
- package/dist/secrets/handlers/delete.write.d.ts +1 -1
- package/dist/secrets/handlers/set.write.d.ts +1 -1
- package/dist/secrets/secrets-context.js +15 -3
- package/dist/sessions/handlers/revoke-all-for-user.write.d.ts +1 -1
- package/dist/sessions/handlers/revoke-all-others.write.d.ts +1 -1
- package/dist/sessions/handlers/revoke.write.d.ts +1 -1
- package/dist/shared/chunked-entity-migration.d.ts +1 -0
- package/dist/shared/chunked-entity-migration.js +1 -1
- package/dist/shared/mail-template-body.d.ts +3 -0
- package/dist/shared/mail-template-body.js +10 -0
- package/dist/subscription-stripe/plugin-methods.js +18 -8
- package/dist/subscription-stripe/verify-webhook.d.ts +1 -1
- package/dist/template-resolver/api.js +3 -2
- package/dist/template-resolver/compile-document-body.d.ts +4 -0
- package/dist/template-resolver/compile-document-body.js +58 -16
- package/dist/template-resolver/constants.d.ts +2 -2
- package/dist/template-resolver/constants.js +3 -1
- package/dist/template-resolver/document-schema.d.ts +1 -1
- package/dist/template-resolver/document-schema.js +23 -3
- package/dist/template-resolver/feature.d.ts +2 -0
- package/dist/template-resolver/feature.js +4 -1
- package/dist/template-resolver/handlers/collection-set.write.d.ts +1 -1
- package/dist/template-resolver/handlers/copy-locale.write.d.ts +1 -1
- package/dist/template-resolver/handlers/copy-locale.write.js +29 -11
- package/dist/template-resolver/handlers/diff-to-system.query.js +4 -38
- package/dist/template-resolver/handlers/discard-draft.write.d.ts +13 -0
- package/dist/template-resolver/handlers/discard-draft.write.js +37 -0
- package/dist/template-resolver/handlers/export-bundle.query.js +51 -1
- package/dist/template-resolver/handlers/find-by-id.query.d.ts +1 -0
- package/dist/template-resolver/handlers/find-by-id.query.js +6 -3
- package/dist/template-resolver/handlers/import-bundle-system.write.d.ts +1 -1
- package/dist/template-resolver/handlers/import-bundle.write.d.ts +1 -1
- package/dist/template-resolver/handlers/layout-source.query.d.ts +5 -0
- package/dist/template-resolver/handlers/layout-source.query.js +46 -23
- package/dist/template-resolver/handlers/list.query.js +4 -1
- package/dist/template-resolver/handlers/override-diff.d.ts +20 -0
- package/dist/template-resolver/handlers/override-diff.js +41 -0
- package/dist/template-resolver/handlers/overview.query.d.ts +6 -0
- package/dist/template-resolver/handlers/overview.query.js +51 -11
- package/dist/template-resolver/handlers/reset-to-system.write.d.ts +1 -1
- package/dist/template-resolver/handlers/resource-link-pdf.write.d.ts +1 -1
- package/dist/template-resolver/handlers/resource-remove.write.d.ts +1 -1
- package/dist/template-resolver/handlers/resource-upload.write.d.ts +1 -1
- package/dist/template-resolver/handlers/resource-upload.write.js +1 -1
- package/dist/template-resolver/handlers/send-test.write.d.ts +3 -1
- package/dist/template-resolver/handlers/send-test.write.js +37 -10
- package/dist/template-resolver/handlers/set.write.d.ts +1 -1
- package/dist/template-resolver/handlers/template-gates.js +13 -2
- package/dist/template-resolver/handlers/template-write.js +36 -4
- package/dist/template-resolver/handlers/tenant-overrides.query.d.ts +32 -0
- package/dist/template-resolver/handlers/tenant-overrides.query.js +80 -16
- package/dist/template-resolver/handlers/toggle-status.write.d.ts +17 -4
- package/dist/template-resolver/handlers/toggle-status.write.js +35 -19
- package/dist/template-resolver/handlers/upsert-system.write.d.ts +1 -1
- package/dist/template-resolver/handlers/upsert-tenant.write.d.ts +1 -1
- package/dist/template-resolver/handlers/upsert-tenant.write.js +1 -1
- package/dist/template-resolver/handlers/usage.query.js +11 -2
- package/dist/template-resolver/index.d.ts +4 -3
- package/dist/template-resolver/index.js +4 -3
- package/dist/template-resolver/layout-appearance.d.ts +10 -0
- package/dist/template-resolver/layout-appearance.js +26 -0
- package/dist/template-resolver/pdf-constants.d.ts +7 -0
- package/dist/template-resolver/pdf-constants.js +8 -0
- package/dist/template-resolver/qualified-names.d.ts +4 -0
- package/dist/template-resolver/qualified-names.js +4 -0
- package/dist/template-resolver/seeding.js +21 -4
- package/dist/template-resolver/store-template-resource.js +2 -2
- package/dist/template-resolver/table.d.ts +103 -0
- package/dist/template-resolver/table.js +9 -2
- package/dist/template-resolver/template-draft.d.ts +51 -0
- package/dist/template-resolver/template-draft.js +72 -0
- package/dist/template-resolver/template-meta.d.ts +30 -5
- package/dist/template-resolver/template-meta.js +60 -16
- package/dist/template-resolver/template-preview.d.ts +19 -2
- package/dist/template-resolver/template-preview.js +98 -54
- package/dist/template-resolver/template-resources.d.ts +5 -0
- package/dist/template-resolver/template-resources.js +35 -4
- package/dist/template-resolver/template-usage.d.ts +5 -1
- package/dist/template-resolver/template-usage.js +14 -9
- package/dist/template-resolver/template-validation.js +1 -0
- package/dist/template-resolver/web/client-plugin.js +4 -2
- package/dist/template-resolver/web/document-editor-extensions.d.ts +4 -1
- package/dist/template-resolver/web/document-editor-extensions.js +50 -12
- package/dist/template-resolver/web/document-editor.d.ts +2 -1
- package/dist/template-resolver/web/document-editor.js +8 -2
- package/dist/template-resolver/web/document-tiptap-editor.d.ts +1 -1
- package/dist/template-resolver/web/document-tiptap-editor.js +144 -28
- package/dist/template-resolver/web/document-toolbar-blocks.d.ts +8 -0
- package/dist/template-resolver/web/document-toolbar-blocks.js +4 -0
- package/dist/template-resolver/web/i18n.js +19 -4
- package/dist/template-resolver/web/index.d.ts +1 -0
- package/dist/template-resolver/web/index.js +1 -0
- package/dist/template-resolver/web/placeholder-line-editor.d.ts +3 -1
- package/dist/template-resolver/web/placeholder-line-editor.js +6 -6
- package/dist/tenant/handlers/add-member.write.d.ts +1 -1
- package/dist/tenant/handlers/cancel-invitation.write.d.ts +1 -1
- package/dist/tenant/handlers/create.write.d.ts +1 -1
- package/dist/tenant/handlers/remove-member.write.d.ts +1 -1
- package/dist/tenant/handlers/toggle-enabled.write.d.ts +2 -2
- package/dist/tenant/handlers/update-member-roles.write.d.ts +1 -1
- package/dist/tenant/handlers/update.write.d.ts +1 -1
- package/dist/tenant-handover/handlers/claim.write.d.ts +1 -1
- package/dist/tenant-lifecycle/handlers/cancel-destruction.write.d.ts +1 -1
- package/dist/tenant-lifecycle/handlers/request-destruction.write.d.ts +1 -1
- package/dist/tier-engine/feature.js +9 -3
- package/dist/tier-engine/handlers/set-tenant-tier.write.d.ts +1 -1
- package/dist/user/handlers/create.write.d.ts +1 -1
- package/dist/user/handlers/update.write.d.ts +1 -1
- package/dist/user/resolve-display-names.js +5 -9
- package/dist/user/seeding.js +20 -1
- package/dist/user-data-rights/handlers/cancel-deletion.write.d.ts +1 -1
- package/dist/user-data-rights/handlers/confirm-deletion-by-token.write.d.ts +1 -1
- package/dist/user-data-rights/handlers/lift-restriction.write.d.ts +1 -1
- package/dist/user-data-rights/handlers/request-deletion-by-email.write.d.ts +1 -1
- package/dist/user-data-rights/handlers/request-deletion.write.d.ts +1 -1
- package/dist/user-data-rights/handlers/request-export.write.d.ts +1 -1
- package/dist/user-data-rights/handlers/restrict-account.write.d.ts +1 -1
- package/dist/user-data-rights/handlers/run-forget-cleanup.write.d.ts +1 -1
- package/dist/user-profile/handlers/change-email.write.d.ts +1 -1
- package/dist/waitlist/handlers/invite.write.d.ts +1 -1
- package/dist/waitlist/handlers/reject.write.d.ts +1 -1
- package/dist/waitlist/handlers/submit.write.d.ts +1 -1
- package/dist/waitlist/options.js +1 -1
- package/dist/workflow-runner/event-trigger.js +2 -2
- package/dist/workflow-runner/handlers/resume-run.write.js +7 -2
- package/package.json +19 -10
- package/src/audit/changes.json +6 -0
- package/src/auth-email-password/changes.json +37 -0
- package/src/billing-foundation/changes.json +36 -0
- package/src/cap-counter/changes.json +6 -0
- package/src/config/changes.json +6 -0
- package/src/crypto-shredding/changes.json +6 -0
- package/src/custom-fields/changes.json +6 -0
- package/src/delivery/changes.json +18 -0
- package/src/file-foundation/changes.json +6 -0
- package/src/files-tenant-data/changes.json +6 -0
- package/src/inbound-mail-foundation/changes.json +6 -0
- package/src/jobs/changes.json +19 -0
- package/src/ledger/changes.json +7 -0
- package/src/renderer-pdf/changes.json +12 -0
- package/src/secrets/changes.json +6 -0
- package/src/shared/changes.json +6 -0
- package/src/subscription-stripe/changes.json +6 -0
- package/src/template-resolver/changes.json +68 -0
- package/src/tier-engine/changes.json +6 -0
- package/src/user/changes.json +6 -0
- package/src/user-data-rights/changes.json +6 -0
- package/src/user-profile/changes.json +1 -1
- package/src/workflow-runner/changes.json +6 -0
|
@@ -17,8 +17,24 @@ export const CAP_TOLERANCES = {
|
|
|
17
17
|
export function assertBelowHardCap(valueSeenByLastUnit, cap) {
|
|
18
18
|
const tolerance = CAP_TOLERANCES[cap.profile];
|
|
19
19
|
if (valueSeenByLastUnit >= cap.limit * tolerance.hard) {
|
|
20
|
-
|
|
20
|
+
const resetAt = resolvePeriodEndIso(cap.periodStartIso, cap.periodEndIso);
|
|
21
|
+
throw new CapExceededError(cap.capName, cap.limit, valueSeenByLastUnit, tolerance, {
|
|
22
|
+
...(resetAt !== undefined && { resetAt }),
|
|
23
|
+
...(cap.i18nKey !== undefined && { i18nKey: cap.i18nKey }),
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
function resolvePeriodEndIso(periodStartIso, periodEndIso) {
|
|
28
|
+
if (periodStartIso === undefined)
|
|
29
|
+
return periodEndIso;
|
|
30
|
+
if (periodEndIso === undefined)
|
|
31
|
+
return calendarMonthPeriodEndIso(periodStartIso);
|
|
32
|
+
const start = Temporal.Instant.from(periodStartIso);
|
|
33
|
+
const end = Temporal.Instant.from(periodEndIso);
|
|
34
|
+
if (Temporal.Instant.compare(end, start) <= 0) {
|
|
35
|
+
throw new Error(`cap-counter: periodEndIso (${periodEndIso}) must be after periodStartIso (${periodStartIso})`);
|
|
21
36
|
}
|
|
37
|
+
return end.toString();
|
|
22
38
|
}
|
|
23
39
|
// =============================================================================
|
|
24
40
|
// Enforce-Cap helper
|
|
@@ -115,6 +131,9 @@ export async function enforceRollingCap(ctx, options) {
|
|
|
115
131
|
* Thrown by enforceCap/enforceRollingCap when value ≥ hard-threshold.
|
|
116
132
|
* Extends `KumikoError` — the dispatcher auto-maps this to HTTP 429
|
|
117
133
|
* with `code: "cap_exceeded"`, no caller-side catch needed.
|
|
134
|
+
*
|
|
135
|
+
* Calendar caps carry `resetAt` (end of the period) and `retryAfterSeconds`, which the API
|
|
136
|
+
* answers as Retry-After. Rolling caps have no fixed reset instant, so both stay undefined.
|
|
118
137
|
*/
|
|
119
138
|
export class CapExceededError extends KumikoError {
|
|
120
139
|
capName;
|
|
@@ -123,17 +142,32 @@ export class CapExceededError extends KumikoError {
|
|
|
123
142
|
tolerance;
|
|
124
143
|
code = "cap_exceeded";
|
|
125
144
|
httpStatus = 429;
|
|
126
|
-
|
|
145
|
+
resetAt;
|
|
146
|
+
constructor(capName, limit, currentValue, tolerance, options) {
|
|
147
|
+
const resetAt = options?.resetAt;
|
|
148
|
+
const retryAfterSeconds = resetAt === undefined
|
|
149
|
+
? undefined
|
|
150
|
+
: Math.max(0, Math.ceil(Temporal.Instant.from(resetAt).epochMilliseconds / 1000 -
|
|
151
|
+
(options?.now ?? Temporal.Now.instant()).epochMilliseconds / 1000));
|
|
127
152
|
super({
|
|
128
|
-
message: `Cap "${capName}" exceeded: current=${currentValue}, limit=${limit}, hard-threshold=${limit * tolerance.hard}. Upgrade tier or wait for next period reset
|
|
129
|
-
i18nKey: "errors.cap.exceeded",
|
|
130
|
-
i18nParams: { capName, limit, currentValue },
|
|
131
|
-
details: {
|
|
153
|
+
message: `Cap "${capName}" exceeded: current=${currentValue}, limit=${limit}, hard-threshold=${limit * tolerance.hard}. Upgrade tier or wait for next period reset.${resetAt === undefined ? "" : ` Resets at ${resetAt}.`}`,
|
|
154
|
+
i18nKey: options?.i18nKey ?? "errors.cap.exceeded",
|
|
155
|
+
i18nParams: { capName, limit, currentValue, ...(resetAt !== undefined && { resetAt }) },
|
|
156
|
+
details: {
|
|
157
|
+
capName,
|
|
158
|
+
limit,
|
|
159
|
+
currentValue,
|
|
160
|
+
tolerance,
|
|
161
|
+
...(resetAt !== undefined && { resetAt, retryAfterSeconds }),
|
|
162
|
+
},
|
|
163
|
+
...(retryAfterSeconds !== undefined && { retryAfterSeconds }),
|
|
164
|
+
...(options?.cause && { cause: options.cause }),
|
|
132
165
|
});
|
|
133
166
|
this.capName = capName;
|
|
134
167
|
this.limit = limit;
|
|
135
168
|
this.currentValue = currentValue;
|
|
136
169
|
this.tolerance = tolerance;
|
|
170
|
+
this.resetAt = resetAt;
|
|
137
171
|
}
|
|
138
172
|
}
|
|
139
173
|
// =============================================================================
|
|
@@ -158,6 +192,13 @@ export function currentCalendarMonthStartIso(now = Temporal.Now.instant()) {
|
|
|
158
192
|
});
|
|
159
193
|
return start.toInstant().toString();
|
|
160
194
|
}
|
|
195
|
+
export function calendarMonthPeriodEndIso(periodStartIso) {
|
|
196
|
+
return Temporal.Instant.from(periodStartIso)
|
|
197
|
+
.toZonedDateTimeISO("UTC")
|
|
198
|
+
.add({ months: 1 })
|
|
199
|
+
.toInstant()
|
|
200
|
+
.toString();
|
|
201
|
+
}
|
|
161
202
|
/**
|
|
162
203
|
* Calendar-Period-enforcement + automatische soft-hit-Notification.
|
|
163
204
|
* Ruft `enforceCap`, bei `crossed: true` den Notifier UND
|
|
@@ -175,6 +216,8 @@ export async function enforceCapAndMaybeNotify(ctx, options) {
|
|
|
175
216
|
limit: options.limit,
|
|
176
217
|
profile: options.profile,
|
|
177
218
|
...(options.amount !== undefined && { amount: options.amount }),
|
|
219
|
+
...(options.periodEndIso !== undefined && { periodEndIso: options.periodEndIso }),
|
|
220
|
+
...(options.i18nKey !== undefined && { i18nKey: options.i18nKey }),
|
|
178
221
|
});
|
|
179
222
|
if (result.state === "soft-hit" && result.crossed) {
|
|
180
223
|
await options.notify({
|
|
@@ -2,7 +2,7 @@ export { capCounterAggregateId, rollingCapAggregateId } from "./aggregate-id.js"
|
|
|
2
2
|
export { type BookCapUsageOptions, bookCapUsage, type MarkCapSoftWarnedOptions, markCapSoftWarned, type ReadRollingCapUsageOptions, readRollingCapUsage, } from "./book-cap-usage.js";
|
|
3
3
|
export { type CalendarCapUsageReservation, type CalendarCapUsageReservationSpec, reserveCalendarCapUsage, } from "./calendar-cap-usage-reservation.js";
|
|
4
4
|
export { CAP_COUNTER_FEATURE, CAP_COUNTER_ROLLING_AGGREGATE_TYPE, CapCounterHandlers, CapCounterQueries, ROLLING_INCREMENTED_EVENT_QN, ROLLING_INCREMENTED_EVENT_SHORT, ROLLING_RELEASED_EVENT_QN, ROLLING_RELEASED_EVENT_SHORT, } from "./constants.js";
|
|
5
|
-
export { CAP_TOLERANCES, CapExceededError, type CapToleranceProfile, type CapToleranceProfileName, currentCalendarMonthStartIso, type EnforceCapResult, enforceCap, enforceCapAndMaybeNotify, enforceRollingCap, enforceRollingCapAndMaybeNotify, enforceStockCap, type SoftHitNotifier, type StockCapResult, } from "./enforce-cap.js";
|
|
5
|
+
export { CAP_TOLERANCES, CapExceededError, type CapToleranceProfile, type CapToleranceProfileName, calendarMonthPeriodEndIso, currentCalendarMonthStartIso, type EnforceCapResult, enforceCap, enforceCapAndMaybeNotify, enforceRollingCap, enforceRollingCapAndMaybeNotify, enforceStockCap, type SoftHitNotifier, type StockCapResult, } from "./enforce-cap.js";
|
|
6
6
|
export { capCounterEntity } from "./entity.js";
|
|
7
7
|
export { capCounterFeature } from "./feature.js";
|
|
8
8
|
export { readPayloadField } from "./payload-field.js";
|
|
@@ -3,7 +3,7 @@ export { capCounterAggregateId, rollingCapAggregateId } from "./aggregate-id.js"
|
|
|
3
3
|
export { bookCapUsage, markCapSoftWarned, readRollingCapUsage, } from "./book-cap-usage.js";
|
|
4
4
|
export { reserveCalendarCapUsage, } from "./calendar-cap-usage-reservation.js";
|
|
5
5
|
export { CAP_COUNTER_FEATURE, CAP_COUNTER_ROLLING_AGGREGATE_TYPE, CapCounterHandlers, CapCounterQueries, ROLLING_INCREMENTED_EVENT_QN, ROLLING_INCREMENTED_EVENT_SHORT, ROLLING_RELEASED_EVENT_QN, ROLLING_RELEASED_EVENT_SHORT, } from "./constants.js";
|
|
6
|
-
export { CAP_TOLERANCES, CapExceededError, currentCalendarMonthStartIso, enforceCap, enforceCapAndMaybeNotify, enforceRollingCap, enforceRollingCapAndMaybeNotify, enforceStockCap, } from "./enforce-cap.js";
|
|
6
|
+
export { CAP_TOLERANCES, CapExceededError, calendarMonthPeriodEndIso, currentCalendarMonthStartIso, enforceCap, enforceCapAndMaybeNotify, enforceRollingCap, enforceRollingCapAndMaybeNotify, enforceStockCap, } from "./enforce-cap.js";
|
|
7
7
|
export { capCounterEntity } from "./entity.js";
|
|
8
8
|
export { capCounterFeature } from "./feature.js";
|
|
9
9
|
export { readPayloadField } from "./payload-field.js";
|
|
@@ -10,6 +10,8 @@ import { type CapToleranceProfileName, type SoftHitNotifier } from "./enforce-ca
|
|
|
10
10
|
export type CalendarCapDef = {
|
|
11
11
|
readonly capName: string;
|
|
12
12
|
readonly periodStartIso: string;
|
|
13
|
+
readonly periodEndIso?: string;
|
|
14
|
+
readonly i18nKey?: string;
|
|
13
15
|
readonly limit: number;
|
|
14
16
|
readonly profile: CapToleranceProfileName;
|
|
15
17
|
/** Increment-amount post-success. Default 1. */
|
|
@@ -138,13 +138,20 @@ export function createEmailChannel(options) {
|
|
|
138
138
|
throw new Error(`email channel: notification "${notificationType}" has a mail template but the channel context carries no renderMail`);
|
|
139
139
|
}
|
|
140
140
|
const unsubscribeUrl = message.data?.["unsubscribeUrl"];
|
|
141
|
-
const { subject, html, text, attachments } = await ctx.renderMail({
|
|
141
|
+
const { subject, html, text, attachments, fromName, replyTo } = await ctx.renderMail({
|
|
142
142
|
slug: notificationType,
|
|
143
143
|
locale: message.locale ?? "en",
|
|
144
144
|
variables: message.data ?? {},
|
|
145
145
|
...(typeof unsubscribeUrl === "string" && { unsubscribeUrl }),
|
|
146
146
|
});
|
|
147
|
-
return {
|
|
147
|
+
return {
|
|
148
|
+
html,
|
|
149
|
+
subject,
|
|
150
|
+
text,
|
|
151
|
+
...(fromName !== undefined && { fromName }),
|
|
152
|
+
...(replyTo !== undefined && { replyTo }),
|
|
153
|
+
...(attachments !== undefined && { attachments }),
|
|
154
|
+
};
|
|
148
155
|
}
|
|
149
156
|
return {
|
|
150
157
|
name: EMAIL_CHANNEL_NAME,
|
|
@@ -161,8 +168,13 @@ export function createEmailChannel(options) {
|
|
|
161
168
|
return renderMessage(message, ctx);
|
|
162
169
|
},
|
|
163
170
|
async send(address, message, ctx, rendered) {
|
|
164
|
-
const { html, subject, text, attachments } = rendered ?? (await renderMessage(message, ctx));
|
|
165
|
-
|
|
171
|
+
const { html, subject, text, attachments, fromName, replyTo } = rendered ?? (await renderMessage(message, ctx));
|
|
172
|
+
// Template-level sender defaults; notify data wins.
|
|
173
|
+
const envelope = {
|
|
174
|
+
...(fromName && { fromName }),
|
|
175
|
+
...(replyTo && { replyTo }),
|
|
176
|
+
...emailEnvelopeFrom(message.data),
|
|
177
|
+
};
|
|
166
178
|
const resolvedAttachments = await resolveAttachments([
|
|
167
179
|
...(attachments ?? []).map((ref) => ({ ref, origin: "template" })),
|
|
168
180
|
...fileAttachmentsFrom(message.data, message.locale).map((ref) => ({ ref, origin: "message" })),
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import * as z from "zod";
|
|
2
2
|
export declare const markAllReadWrite: import("@cosmicdrift/kumiko-types/define-handler").WriteHandlerDefinition<"markAllRead", z.ZodObject<{}, z.core.$strip>, {
|
|
3
3
|
marked: number;
|
|
4
|
-
}, import("@cosmicdrift/kumiko-types/event-type-map").KumikoEventTypeMap>;
|
|
4
|
+
}, import("@cosmicdrift/kumiko-types/event-type-map").KumikoEventTypeMap, undefined>;
|
|
@@ -3,4 +3,4 @@ export declare const markReadWrite: import("@cosmicdrift/kumiko-types/define-han
|
|
|
3
3
|
id: z.ZodNumber;
|
|
4
4
|
}, z.core.$strip>, {
|
|
5
5
|
id: number | undefined;
|
|
6
|
-
}, import("@cosmicdrift/kumiko-types/event-type-map").KumikoEventTypeMap>;
|
|
6
|
+
}, import("@cosmicdrift/kumiko-types/event-type-map").KumikoEventTypeMap, undefined>;
|
|
@@ -10,4 +10,4 @@ export declare const setProfileWrite: import("@cosmicdrift/kumiko-types/define-h
|
|
|
10
10
|
}, z.core.$strip>, {
|
|
11
11
|
profileKey: "de-hr-dsgvo-hgb" | "eu-dsgvo" | "swiss-dsg";
|
|
12
12
|
isNew: boolean;
|
|
13
|
-
}, import("@cosmicdrift/kumiko-types/event-type-map").KumikoEventTypeMap>;
|
|
13
|
+
}, import("@cosmicdrift/kumiko-types/event-type-map").KumikoEventTypeMap, undefined>;
|
|
@@ -9,4 +9,4 @@ export declare const resetWrite: import("@cosmicdrift/kumiko-types/define-handle
|
|
|
9
9
|
}, z.core.$strip>, {
|
|
10
10
|
key: string;
|
|
11
11
|
scope: import("@cosmicdrift/kumiko-types/config-scope").ConfigScope;
|
|
12
|
-
}, import("@cosmicdrift/kumiko-types/event-type-map").KumikoEventTypeMap>;
|
|
12
|
+
}, import("@cosmicdrift/kumiko-types/event-type-map").KumikoEventTypeMap, undefined>;
|
|
@@ -11,4 +11,4 @@ export declare const setWrite: import("@cosmicdrift/kumiko-types/define-handler"
|
|
|
11
11
|
key: string;
|
|
12
12
|
value: string | number | boolean;
|
|
13
13
|
scope: import("@cosmicdrift/kumiko-types/config-scope").ConfigScope;
|
|
14
|
-
}, import("@cosmicdrift/kumiko-types/event-type-map").KumikoEventTypeMap>;
|
|
14
|
+
}, import("@cosmicdrift/kumiko-types/event-type-map").KumikoEventTypeMap, undefined>;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { requestContext } from "@cosmicdrift/kumiko-framework/api";
|
|
2
2
|
import { configuredPiiSubjectKms, encryptPiiValueForSubject, } from "@cosmicdrift/kumiko-framework/crypto";
|
|
3
|
-
import { createEventStoreExecutor } from "@cosmicdrift/kumiko-framework/db";
|
|
3
|
+
import { createEventStoreExecutor, scopeTenantDbToTenant } from "@cosmicdrift/kumiko-framework/db";
|
|
4
4
|
import { ConfigScopes, defineWriteHandler, isEncryptedAtRest, SYSTEM_TENANT_ID, } from "@cosmicdrift/kumiko-framework/engine";
|
|
5
5
|
import { InternalError, isKumikoError, UnprocessableError, writeFailure, } from "@cosmicdrift/kumiko-framework/errors";
|
|
6
6
|
import * as z from "zod";
|
|
@@ -74,7 +74,7 @@ export const setWrite = defineWriteHandler({
|
|
|
74
74
|
scope,
|
|
75
75
|
tenantId,
|
|
76
76
|
userId,
|
|
77
|
-
db,
|
|
77
|
+
db: scopeTenantDbToTenant(db, tenantId),
|
|
78
78
|
});
|
|
79
79
|
}
|
|
80
80
|
catch (error) {
|
|
@@ -7,6 +7,7 @@ export type ConfigResolverOptions = {
|
|
|
7
7
|
cipher?: EnvelopeCipher;
|
|
8
8
|
appOverrides?: AppConfigOverrides;
|
|
9
9
|
};
|
|
10
|
+
export declare function decryptStoredValue(raw: string, keyDef: ConfigKeyDefinition, cipher: EnvelopeCipher | undefined, qualifiedKey: string): Promise<string>;
|
|
10
11
|
export declare function createConfigResolver(options?: ConfigResolverOptions): ConfigResolver;
|
|
11
12
|
export declare function validateAppOverrides(registry: {
|
|
12
13
|
getConfigKey: (key: string) => import("@cosmicdrift/kumiko-framework/engine").ConfigKeyDefinition | undefined;
|
package/dist/config/resolver.js
CHANGED
|
@@ -57,6 +57,15 @@ async function decryptPiiEncrypted(raw, qualifiedKey) {
|
|
|
57
57
|
const kmsCtx = { requestId: requestContext.get()?.requestId ?? "config:resolver" };
|
|
58
58
|
return decryptPiiValueForSubject(kms, raw, kmsCtx, qualifiedKey);
|
|
59
59
|
}
|
|
60
|
+
// Plaintext form of a stored config_values.value for every read path (resolver,
|
|
61
|
+
// bootstrap idempotency check).
|
|
62
|
+
export async function decryptStoredValue(raw, keyDef, cipher, qualifiedKey) {
|
|
63
|
+
if (keyDef.encrypted)
|
|
64
|
+
return decryptEncrypted(cipher, raw, qualifiedKey);
|
|
65
|
+
if (keyDef.piiEncrypted)
|
|
66
|
+
return decryptPiiEncrypted(raw, qualifiedKey);
|
|
67
|
+
return raw;
|
|
68
|
+
}
|
|
60
69
|
// backing="secrets" keys store their value in the secrets store (flat per
|
|
61
70
|
// (tenant,key) at SYSTEM_TENANT_ID), not in config_values. Both read paths
|
|
62
71
|
// (getWithSource + buildCascade) route the system rung here. Missing reader =
|
|
@@ -142,13 +151,7 @@ async function buildCascade(qualifiedKey, keyDef, tenantId, userId, db, fetchRow
|
|
|
142
151
|
}
|
|
143
152
|
const row = await fetchRow(lookup.tenantId, lookup.userId);
|
|
144
153
|
if (row?.value !== null && row?.value !== undefined) {
|
|
145
|
-
|
|
146
|
-
if (keyDef.encrypted) {
|
|
147
|
-
raw = await decryptEncrypted(cipher, raw, qualifiedKey);
|
|
148
|
-
}
|
|
149
|
-
else if (keyDef.piiEncrypted) {
|
|
150
|
-
raw = await decryptPiiEncrypted(raw, qualifiedKey);
|
|
151
|
-
}
|
|
154
|
+
const raw = await decryptStoredValue(row.value, keyDef, cipher, qualifiedKey);
|
|
152
155
|
if (activeIndex === -1)
|
|
153
156
|
activeIndex = levels.length;
|
|
154
157
|
levels.push({
|
|
@@ -296,13 +299,7 @@ export function createConfigResolver(options = {}) {
|
|
|
296
299
|
for (const lookup of lookups) {
|
|
297
300
|
const row = await findRow(qualifiedKey, lookup.tenantId, lookup.userId, db);
|
|
298
301
|
if (row?.value !== null && row?.value !== undefined) {
|
|
299
|
-
|
|
300
|
-
if (keyDef.encrypted) {
|
|
301
|
-
raw = await decryptEncrypted(cipher, raw, qualifiedKey);
|
|
302
|
-
}
|
|
303
|
-
else if (keyDef.piiEncrypted) {
|
|
304
|
-
raw = await decryptPiiEncrypted(raw, qualifiedKey);
|
|
305
|
-
}
|
|
302
|
+
const raw = await decryptStoredValue(row.value, keyDef, cipher, qualifiedKey);
|
|
306
303
|
return { value: deserializeValue(raw, keyDef.type), source: lookup.source };
|
|
307
304
|
}
|
|
308
305
|
}
|
|
@@ -2,12 +2,14 @@ import type { SubjectId } from "@cosmicdrift/kumiko-framework/crypto";
|
|
|
2
2
|
import type { DbConnection, TenantDb } from "@cosmicdrift/kumiko-framework/db";
|
|
3
3
|
import { type ConfigKeyDefinition, type ConfigScope, type Registry, type SessionUser, type TenantId } from "@cosmicdrift/kumiko-framework/engine";
|
|
4
4
|
import { type KumikoError, type WriteFailure } from "@cosmicdrift/kumiko-framework/errors";
|
|
5
|
+
import type { EnvelopeCipher } from "@cosmicdrift/kumiko-framework/secrets";
|
|
5
6
|
export type ConfigRowLookup = {
|
|
6
7
|
readonly id: string;
|
|
7
8
|
readonly version: number;
|
|
8
9
|
readonly value: string | null;
|
|
9
10
|
};
|
|
10
11
|
export declare function findConfigRow(db: DbConnection | TenantDb, key: string, tenantId: TenantId, userId: string | null): Promise<ConfigRowLookup | null>;
|
|
12
|
+
export declare function configRowHoldsValue(row: ConfigRowLookup | null, keyDef: ConfigKeyDefinition | undefined, cipher: EnvelopeCipher | undefined, qualifiedKey: string, serializedValue: string): Promise<boolean>;
|
|
11
13
|
export type PrepareConfigWriteArgs = {
|
|
12
14
|
readonly registry: Registry;
|
|
13
15
|
readonly user: SessionUser;
|
|
@@ -6,6 +6,7 @@ import { ConfigScopes, extensionSelectorTargets, isEncryptedAtRest, SYSTEM_ROLE,
|
|
|
6
6
|
import { AccessDeniedError, InternalError, NotFoundError, UnprocessableError, ValidationError, writeFailure, } from "@cosmicdrift/kumiko-framework/errors";
|
|
7
7
|
import { assertUnreachable } from "@cosmicdrift/kumiko-framework/utils";
|
|
8
8
|
import { ConfigErrors } from "./constants.js";
|
|
9
|
+
import { decryptStoredValue } from "./resolver.js";
|
|
9
10
|
import { configValuesTable } from "./table.js";
|
|
10
11
|
// Locate an existing config_values row by the (key, tenant, user) triple —
|
|
11
12
|
// the effective natural key. System-scope rows carry SYSTEM_TENANT_ID on
|
|
@@ -26,6 +27,24 @@ export async function findConfigRow(db, key, tenantId, userId) {
|
|
|
26
27
|
value: row.value ?? null,
|
|
27
28
|
};
|
|
28
29
|
}
|
|
30
|
+
// Idempotency check for declarative writers: does the stored row already hold
|
|
31
|
+
// `serializedValue` (JSON of the plaintext)? Encrypted rows are decrypted
|
|
32
|
+
// first, since their ciphertext differs on every encrypt.
|
|
33
|
+
export async function configRowHoldsValue(row, keyDef, cipher, qualifiedKey, serializedValue) {
|
|
34
|
+
if (row?.value === null || row?.value === undefined)
|
|
35
|
+
return false;
|
|
36
|
+
if (!keyDef?.encrypted && !keyDef?.piiEncrypted)
|
|
37
|
+
return row.value === serializedValue;
|
|
38
|
+
if (!keyDef)
|
|
39
|
+
return false;
|
|
40
|
+
try {
|
|
41
|
+
return (await decryptStoredValue(row.value, keyDef, cipher, qualifiedKey)) === serializedValue;
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
// Unreadable (rotated key, missing cipher/KMS): the caller rewrites the row, which is the repair.
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
29
48
|
export function prepareConfigWrite(args) {
|
|
30
49
|
const { registry, user, key, scope: requestedScope } = args;
|
|
31
50
|
const keyDef = registry.getConfigKey(key);
|
|
@@ -48,4 +48,4 @@ export declare const forgetSubjectWrite: import("@cosmicdrift/kumiko-types/defin
|
|
|
48
48
|
reason: z.ZodString;
|
|
49
49
|
}, z.core.$strip>, {
|
|
50
50
|
subjectKey: string;
|
|
51
|
-
}, import("@cosmicdrift/kumiko-types/event-type-map").KumikoEventTypeMap>;
|
|
51
|
+
}, import("@cosmicdrift/kumiko-types/event-type-map").KumikoEventTypeMap, undefined>;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { requestContext } from "@cosmicdrift/kumiko-framework/api";
|
|
2
2
|
import { ROLES } from "@cosmicdrift/kumiko-framework/auth";
|
|
3
|
-
import { runInSavepointIfSupported } from "@cosmicdrift/kumiko-framework/bun-db";
|
|
3
|
+
import { fetchOne, runInSavepointIfSupported, runnerCanBeginTransaction, transaction, } from "@cosmicdrift/kumiko-framework/bun-db";
|
|
4
4
|
import { computeBlindIndex, configuredBlindIndexKey, configuredPiiSubjectKms, subjectIdSchema, subjectIdToKey, } from "@cosmicdrift/kumiko-framework/crypto";
|
|
5
5
|
import { nullBlindIndexesForSubject, recordEventsOwnedExclusivelyByTenant, recordRowExistsInTenant, recordRowOwningTenantId, subjectRowExistsInTenant, } from "@cosmicdrift/kumiko-framework/db";
|
|
6
6
|
import { defineWriteHandler, } from "@cosmicdrift/kumiko-framework/engine";
|
|
@@ -12,7 +12,7 @@ import * as z from "zod";
|
|
|
12
12
|
import { resolveRetentionPolicyForTenant } from "../../data-retention/index.js";
|
|
13
13
|
import { eraseNoteBodiesReachingSubject } from "../../notes-history-user-data/index.js";
|
|
14
14
|
import { revokeAllPatTokensForUser } from "../../personal-access-tokens/index.js";
|
|
15
|
-
import { USER_STATUS } from "../../user/index.js";
|
|
15
|
+
import { USER_STATUS, userTable } from "../../user/index.js";
|
|
16
16
|
import { denyIfTargetOutsideAdminTenant, isSystemAdminActor, updateUserLifecycle, } from "../../user-data-rights/index.js";
|
|
17
17
|
import { CRYPTO_SHREDDING_AGGREGATE_TYPE, CRYPTO_SHREDDING_FEATURE_NAME, RECORD_ENTITY_NOT_REGISTERED, SUBJECT_FORGET_DENIED_EVENT_NAME, SUBJECT_FORGOTTEN_EVENT_NAME, TARGET_RECORD_NOT_ADMIN_TENANT, TARGET_RECORD_RETENTION_BLOCK_DELETE, TARGET_TENANT_NOT_ADMIN_TENANT, } from "../constants.js";
|
|
18
18
|
export { subjectIdSchema };
|
|
@@ -241,22 +241,29 @@ async function sweepDerivedSubjectData(ctx, subject, subjectKey, crossTenantRunn
|
|
|
241
241
|
return sweepErrorLabel(err);
|
|
242
242
|
}
|
|
243
243
|
}
|
|
244
|
-
//
|
|
245
|
-
//
|
|
246
|
-
//
|
|
247
|
-
//
|
|
248
|
-
//
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
244
|
+
// Email subscribers and other non-user entities use user-style subject keys
|
|
245
|
+
// without a read_users row — the key erase is the GDPR-relevant part, so no row
|
|
246
|
+
// means nothing to close. For an existing row, failures propagate: the handler
|
|
247
|
+
// throws and the retry re-runs the (idempotent) erase plus the door close. With
|
|
248
|
+
// an outside-transaction runner that can open its own transaction, the door
|
|
249
|
+
// closes there and stays committed when the handler throws afterwards (sweep
|
|
250
|
+
// error). Otherwise a savepoint confines a failure to the handler transaction.
|
|
251
|
+
async function closeUserLoginDoor(ctx, userId, outsideRunner, handlerTxRunner) {
|
|
252
|
+
const userRow = await fetchOne(handlerTxRunner, userTable, { id: userId });
|
|
253
|
+
// skip: no user row for this subject key (e.g. email subscribers); there is no door to close.
|
|
254
|
+
if (!userRow)
|
|
255
|
+
return;
|
|
256
|
+
const closeDoor = async (runner) => {
|
|
257
|
+
await updateUserLifecycle(runner, userId, { status: USER_STATUS.Deleted });
|
|
258
|
+
if (ctx.registry.features.has("personal-access-tokens")) {
|
|
259
|
+
await revokeAllPatTokensForUser(runner, userId);
|
|
260
|
+
}
|
|
261
|
+
};
|
|
262
|
+
if (outsideRunner && runnerCanBeginTransaction(outsideRunner)) {
|
|
263
|
+
await transaction(outsideRunner, closeDoor);
|
|
257
264
|
}
|
|
258
|
-
|
|
259
|
-
|
|
265
|
+
else {
|
|
266
|
+
await runInSavepointIfSupported(handlerTxRunner, closeDoor);
|
|
260
267
|
}
|
|
261
268
|
}
|
|
262
269
|
// Through the outside-transaction db when available, so the audit event commits
|
|
@@ -351,7 +358,6 @@ export const forgetSubjectWrite = defineWriteHandler({
|
|
|
351
358
|
userId: event.user.id,
|
|
352
359
|
eraseReason: event.payload.reason,
|
|
353
360
|
});
|
|
354
|
-
const cleanupError = await sweepDerivedSubjectData(ctx, subject, subjectKey, ctx.db.unsafeRaw(), { requestId, erasedBy: event.user.id });
|
|
355
361
|
// User subject: close the login door. DEK-erase makes the passwordHash
|
|
356
362
|
// ciphertext unreadable, but status + PATs are standalone credentials —
|
|
357
363
|
// the PAT resolver only checks revokedAt/expiresAt, NOT user.status
|
|
@@ -367,9 +373,13 @@ export const forgetSubjectWrite = defineWriteHandler({
|
|
|
367
373
|
// crash recovery is safe. User-feature guard: without the user feature
|
|
368
374
|
// read_users doesn't exist (crypto-only stack). Tenant subjects have no
|
|
369
375
|
// credentials.
|
|
376
|
+
// Runs before the sweep and in its own transaction: a failing sweep rolls the
|
|
377
|
+
// handler transaction back, and the handler tx holds no lock on this user's
|
|
378
|
+
// read_users row yet (only read gates ran), so there is no lock conflict.
|
|
370
379
|
if (raw.kind === "user" && ctx.registry.features.has("user")) {
|
|
371
|
-
await
|
|
380
|
+
await closeUserLoginDoor(ctx, raw.userId, ctx.dbOutsideTransaction?.unsafeRaw(), ctx.db.unsafeRaw());
|
|
372
381
|
}
|
|
382
|
+
const cleanupError = await sweepDerivedSubjectData(ctx, subject, subjectKey, ctx.db.unsafeRaw(), { requestId, erasedBy: event.user.id });
|
|
373
383
|
await appendSubjectForgotten(ctx, event, subjectAggregateId(raw), {
|
|
374
384
|
subjectKey,
|
|
375
385
|
reason: event.payload.reason,
|
|
@@ -11,7 +11,6 @@ export function createUpdateTenantFieldHandler(opts = {}) {
|
|
|
11
11
|
schema: updateFieldPayloadSchema,
|
|
12
12
|
access: { roles: opts.roles ?? DEFAULT_FIELD_DEFINITION_WRITE_ROLES },
|
|
13
13
|
description: "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.",
|
|
14
|
-
excludedFields: ["type"],
|
|
15
14
|
handler: async (event, ctx) => {
|
|
16
15
|
const payload = event.payload; // @cast-boundary engine-payload
|
|
17
16
|
const tenantId = event.user.tenantId;
|
|
@@ -4,6 +4,8 @@ export type { FileAttachmentRef, InlineAttachmentRef, RenderedPdfAttachmentRef }
|
|
|
4
4
|
export type EmailAttachmentRef = InlineAttachmentRef | FileAttachmentRef | RenderedPdfAttachmentRef;
|
|
5
5
|
export declare const MAX_EMAIL_ATTACHMENTS = 20;
|
|
6
6
|
export declare const MAX_RENDERED_PDF_VARIABLES_BYTES = 64000;
|
|
7
|
+
export declare const DELIVERY_ENVELOPE_DATA_KEYS: readonly ["attachments", "from", "fromName", "replyTo", "headers", "unsubscribeUrl"];
|
|
8
|
+
export declare function withoutDeliveryEnvelopeKeys(variables: Readonly<Record<string, unknown>>): Readonly<Record<string, unknown>>;
|
|
7
9
|
export declare const inlineAttachmentRefSchema: z.ZodObject<{
|
|
8
10
|
kind: z.ZodLiteral<"inline">;
|
|
9
11
|
cid: z.ZodString;
|
|
@@ -3,6 +3,20 @@ import { attachmentFileNameSchema } from "../shared/index.js";
|
|
|
3
3
|
import { isDocumentResourceKey, templateSlugSchema } from "../template-resolver/index.js";
|
|
4
4
|
export const MAX_EMAIL_ATTACHMENTS = 20;
|
|
5
5
|
export const MAX_RENDERED_PDF_VARIABLES_BYTES = 64_000;
|
|
6
|
+
// Channel-data keys that steer delivery itself (envelope, headers, attachments);
|
|
7
|
+
// they are not document variables and must not be copied into attached PDF refs.
|
|
8
|
+
export const DELIVERY_ENVELOPE_DATA_KEYS = [
|
|
9
|
+
"attachments",
|
|
10
|
+
"from",
|
|
11
|
+
"fromName",
|
|
12
|
+
"replyTo",
|
|
13
|
+
"headers",
|
|
14
|
+
"unsubscribeUrl",
|
|
15
|
+
];
|
|
16
|
+
const DELIVERY_ENVELOPE_DATA_KEY_SET = new Set(DELIVERY_ENVELOPE_DATA_KEYS);
|
|
17
|
+
export function withoutDeliveryEnvelopeKeys(variables) {
|
|
18
|
+
return Object.fromEntries(Object.entries(variables).filter(([key]) => !DELIVERY_ENVELOPE_DATA_KEY_SET.has(key)));
|
|
19
|
+
}
|
|
6
20
|
const MAX_ID_LENGTH = 64;
|
|
7
21
|
const fileRefIdSchema = z.string().min(1).max(MAX_ID_LENGTH);
|
|
8
22
|
export const inlineAttachmentRefSchema = z
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { createTenantDb } from "@cosmicdrift/kumiko-framework/db";
|
|
2
2
|
import { SYSTEM_TENANT_ID } from "@cosmicdrift/kumiko-framework/engine";
|
|
3
|
+
import { createFallbackLogger } from "@cosmicdrift/kumiko-framework/logging";
|
|
3
4
|
import { readFileRefContent } from "../file-foundation/index.js";
|
|
4
5
|
import { collectRendererPlugins, createRendererFoundationApi, RendererError, } from "../renderer-foundation/index.js";
|
|
5
6
|
import { FALLBACK_LOCALE } from "../template-resolver/index.js";
|
|
@@ -15,7 +16,10 @@ export function createTemplateMailRenderer(db, registry, tenantId, config) {
|
|
|
15
16
|
html: response.html,
|
|
16
17
|
text: response.text,
|
|
17
18
|
...(response.previewText !== undefined && { previewText: response.previewText }),
|
|
19
|
+
...(response.fromName !== undefined && { fromName: response.fromName }),
|
|
20
|
+
...(response.replyTo !== undefined && { replyTo: response.replyTo }),
|
|
18
21
|
...(response.attachments !== undefined && { attachments: response.attachments }),
|
|
22
|
+
...(response.layout !== undefined && { layout: response.layout }),
|
|
19
23
|
};
|
|
20
24
|
};
|
|
21
25
|
}
|
|
@@ -40,6 +44,19 @@ function createPdfRenderer(db, registry, tenantId, config, filesForTenant) {
|
|
|
40
44
|
return { content: response.pdfBytes, contentType: PDF_CONTENT_TYPE };
|
|
41
45
|
};
|
|
42
46
|
}
|
|
47
|
+
// A decorative inline image (logo) that cannot be read must not fail the whole mail.
|
|
48
|
+
async function readInlineImageOrNull(fileRefId, read) {
|
|
49
|
+
try {
|
|
50
|
+
return await read();
|
|
51
|
+
}
|
|
52
|
+
catch (err) {
|
|
53
|
+
createFallbackLogger("delivery").warn("inline image could not be read, sending without it", {
|
|
54
|
+
fileRefId,
|
|
55
|
+
errorName: err instanceof Error ? err.name : typeof err,
|
|
56
|
+
});
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
43
60
|
// Inline images and file attachments the renderer took from a template may come from the system
|
|
44
61
|
// tenant (system template or tenant override of one). A file ref from notify data stays limited to
|
|
45
62
|
// the job's own tenant, since a caller picks it. Inline content that is not an image is dropped, so
|
|
@@ -49,12 +66,13 @@ export function createEmailAttachmentReader(db, registry, tenantId, config, file
|
|
|
49
66
|
return async (ref, origin = "message") => {
|
|
50
67
|
if (ref.kind === "rendered-pdf")
|
|
51
68
|
return renderPdf(ref);
|
|
52
|
-
const
|
|
69
|
+
const readRef = () => readFileRefContent({
|
|
53
70
|
db,
|
|
54
71
|
filesForTenant,
|
|
55
72
|
fileRefId: ref.fileRefId,
|
|
56
73
|
allowedTenantIds: ref.kind === "file" && origin === "message" ? [tenantId] : [tenantId, SYSTEM_TENANT_ID],
|
|
57
74
|
});
|
|
75
|
+
const found = ref.kind === "inline" ? await readInlineImageOrNull(ref.fileRefId, readRef) : await readRef();
|
|
58
76
|
if (found === null)
|
|
59
77
|
return null;
|
|
60
78
|
if (ref.kind === "inline" && !found.contentType.startsWith(INLINE_IMAGE_CONTENT_TYPE_PREFIX)) {
|
|
@@ -6,4 +6,4 @@ export declare const resubscribeAddressWrite: import("@cosmicdrift/kumiko-types/
|
|
|
6
6
|
}, z.core.$strip>, {
|
|
7
7
|
notificationType: string;
|
|
8
8
|
channel: string;
|
|
9
|
-
}, import("@cosmicdrift/kumiko-types/event-type-map").KumikoEventTypeMap>;
|
|
9
|
+
}, import("@cosmicdrift/kumiko-types/event-type-map").KumikoEventTypeMap, undefined>;
|
|
@@ -6,4 +6,4 @@ export declare const resubscribeUserWrite: import("@cosmicdrift/kumiko-types/def
|
|
|
6
6
|
}, z.core.$strip>, {
|
|
7
7
|
notificationType: string;
|
|
8
8
|
channel: string;
|
|
9
|
-
}, import("@cosmicdrift/kumiko-types/event-type-map").KumikoEventTypeMap>;
|
|
9
|
+
}, import("@cosmicdrift/kumiko-types/event-type-map").KumikoEventTypeMap, undefined>;
|
|
@@ -7,4 +7,4 @@ export declare const setPreferenceWrite: import("@cosmicdrift/kumiko-types/defin
|
|
|
7
7
|
notificationType: string;
|
|
8
8
|
channel: string;
|
|
9
9
|
enabled: boolean;
|
|
10
|
-
}, import("@cosmicdrift/kumiko-types/event-type-map").KumikoEventTypeMap>;
|
|
10
|
+
}, import("@cosmicdrift/kumiko-types/event-type-map").KumikoEventTypeMap, undefined>;
|
|
@@ -6,4 +6,4 @@ export declare const unsubscribeAddressWrite: import("@cosmicdrift/kumiko-types/
|
|
|
6
6
|
}, z.core.$strip>, {
|
|
7
7
|
notificationType: string;
|
|
8
8
|
channel: string;
|
|
9
|
-
}, import("@cosmicdrift/kumiko-types/event-type-map").KumikoEventTypeMap>;
|
|
9
|
+
}, import("@cosmicdrift/kumiko-types/event-type-map").KumikoEventTypeMap, undefined>;
|
|
@@ -6,4 +6,4 @@ export declare const unsubscribeUserWrite: import("@cosmicdrift/kumiko-types/def
|
|
|
6
6
|
}, z.core.$strip>, {
|
|
7
7
|
notificationType: string;
|
|
8
8
|
channel: string;
|
|
9
|
-
}, import("@cosmicdrift/kumiko-types/event-type-map").KumikoEventTypeMap>;
|
|
9
|
+
}, import("@cosmicdrift/kumiko-types/event-type-map").KumikoEventTypeMap, undefined>;
|
package/dist/delivery/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export { sanitizePdfAttachmentFileName } from "../shared/index.js";
|
|
2
2
|
export { hashUnsubscribeAddress } from "./address-opt-out.js";
|
|
3
|
-
export { type EmailAttachmentRef, emailAttachmentRefSchema, type FileAttachmentRef, fileAttachmentRefSchema, type InlineAttachmentRef, inlineAttachmentRefSchema, MAX_EMAIL_ATTACHMENTS, MAX_RENDERED_PDF_VARIABLES_BYTES, type RenderedPdfAttachmentRef, renderedPdfAttachmentRefSchema, toAttachmentRef, } from "./attachment-ref.js";
|
|
3
|
+
export { DELIVERY_ENVELOPE_DATA_KEYS, type EmailAttachmentRef, emailAttachmentRefSchema, type FileAttachmentRef, fileAttachmentRefSchema, type InlineAttachmentRef, inlineAttachmentRefSchema, MAX_EMAIL_ATTACHMENTS, MAX_RENDERED_PDF_VARIABLES_BYTES, type RenderedPdfAttachmentRef, renderedPdfAttachmentRefSchema, toAttachmentRef, withoutDeliveryEnvelopeKeys, } from "./attachment-ref.js";
|
|
4
4
|
export { type AttemptLogRetentionOptions, DEFAULT_ATTEMPT_LOG_RETENTION_DAYS, runAttemptLogRetention, } from "./attempt-log-retention.js";
|
|
5
5
|
export { createEmailAttachmentReader, createTemplateMailRenderer } from "./channel-context.js";
|
|
6
6
|
export { type ChatWebhookChannelOptions, chatMessageText, createChatWebhookChannel, resolveChatWebhookTarget, toChannelResult, } from "./chat-webhook-channel.js";
|
package/dist/delivery/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export { sanitizePdfAttachmentFileName } from "../shared/index.js";
|
|
2
2
|
export { hashUnsubscribeAddress } from "./address-opt-out.js";
|
|
3
|
-
export { emailAttachmentRefSchema, fileAttachmentRefSchema, inlineAttachmentRefSchema, MAX_EMAIL_ATTACHMENTS, MAX_RENDERED_PDF_VARIABLES_BYTES, renderedPdfAttachmentRefSchema, toAttachmentRef, } from "./attachment-ref.js";
|
|
3
|
+
export { DELIVERY_ENVELOPE_DATA_KEYS, emailAttachmentRefSchema, fileAttachmentRefSchema, inlineAttachmentRefSchema, MAX_EMAIL_ATTACHMENTS, MAX_RENDERED_PDF_VARIABLES_BYTES, renderedPdfAttachmentRefSchema, toAttachmentRef, withoutDeliveryEnvelopeKeys, } from "./attachment-ref.js";
|
|
4
4
|
export { DEFAULT_ATTEMPT_LOG_RETENTION_DAYS, runAttemptLogRetention, } from "./attempt-log-retention.js";
|
|
5
5
|
export { createEmailAttachmentReader, createTemplateMailRenderer } from "./channel-context.js";
|
|
6
6
|
export { chatMessageText, createChatWebhookChannel, resolveChatWebhookTarget, toChannelResult, } from "./chat-webhook-channel.js";
|