@cosmicdrift/kumiko-bundled-features 0.349.0 → 0.351.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.
Files changed (98) hide show
  1. package/dist/audit/handlers/list.query.js +1 -1
  2. package/dist/auth-email-password/email-templates.js +1 -1
  3. package/dist/auth-email-password/handlers/invite-create.write.js +1 -1
  4. package/dist/auth-email-password/handlers/signup-request.write.js +1 -1
  5. package/dist/auth-email-password/handlers/token-request-handler.d.ts +1 -1
  6. package/dist/auth-email-password/reset-token.d.ts +1 -1
  7. package/dist/auth-email-password/unlock-token.d.ts +1 -1
  8. package/dist/auth-email-password/verification-token.d.ts +1 -1
  9. package/dist/auth-mfa/handlers/enable-confirm-preauth.write.js +1 -1
  10. package/dist/auth-mfa/handlers/enable-confirm.write.js +1 -1
  11. package/dist/auth-mfa/hmac-token-codec.d.ts +1 -1
  12. package/dist/auth-mfa/hmac-token-codec.js +1 -1
  13. package/dist/auth-mfa/mfa-challenge-token.d.ts +1 -1
  14. package/dist/auth-mfa/mfa-challenge-token.js +1 -1
  15. package/dist/auth-mfa/mfa-preauth-setup-token.d.ts +1 -1
  16. package/dist/auth-mfa/mfa-preauth-setup-token.js +1 -1
  17. package/dist/auth-mfa/mfa-setup-token.d.ts +1 -1
  18. package/dist/auth-mfa/mfa-setup-token.js +1 -1
  19. package/dist/billing-foundation/constants.d.ts +2 -2
  20. package/dist/billing-foundation/constants.js +3 -12
  21. package/dist/billing-foundation/consumer-protection/confirmation-mail.js +1 -1
  22. package/dist/billing-foundation/consumer-protection/termination-mail.js +1 -1
  23. package/dist/billing-foundation/feature.js +2 -9
  24. package/dist/billing-foundation/handlers/sync-subscription.write.js +3 -6
  25. package/dist/cap-counter/book-cap-usage.js +1 -1
  26. package/dist/cap-counter/enforce-cap.d.ts +1 -1
  27. package/dist/cap-counter/enforce-cap.js +1 -8
  28. package/dist/channel-email/email-channel.js +13 -5
  29. package/dist/channel-email/pii-guard.js +6 -1
  30. package/dist/channel-email/smtp-transport.js +1 -0
  31. package/dist/channel-email/types.d.ts +1 -0
  32. package/dist/delivery/handlers/log.query.d.ts +1 -2
  33. package/dist/delivery/handlers/log.query.js +1 -1
  34. package/dist/delivery/jobs.js +3 -1
  35. package/dist/delivery/types.d.ts +3 -0
  36. package/dist/feature-toggles/handlers/set.write.js +1 -1
  37. package/dist/form-draft/db/queries/cleanup.d.ts +1 -1
  38. package/dist/form-draft/db/queries/cleanup.js +1 -1
  39. package/dist/form-draft/db/queries/owned-file-refs.d.ts +1 -1
  40. package/dist/form-draft/lookup.d.ts +1 -1
  41. package/dist/inbound-mail-foundation/handlers/ingest-message.write.js +1 -1
  42. package/dist/inbound-mail-foundation/oauth-access-token.d.ts +1 -1
  43. package/dist/inbound-mail-foundation/oauth-access-token.js +1 -1
  44. package/dist/inbound-mail-foundation/oauth-state.d.ts +1 -1
  45. package/dist/inbound-mail-foundation/oauth-state.js +1 -1
  46. package/dist/inbound-mail-foundation/watch-supervisor.js +1 -1
  47. package/dist/inbound-provider-imap/feature.js +1 -1
  48. package/dist/inbound-provider-imap/imap-client.js +1 -1
  49. package/dist/jobs/job-instant.d.ts +1 -1
  50. package/dist/jobs/job-instant.js +1 -1
  51. package/dist/metrics/compute.d.ts +1 -1
  52. package/dist/metrics/feature.js +1 -1
  53. package/dist/metrics/types.d.ts +1 -1
  54. package/dist/metrics/window.d.ts +1 -1
  55. package/dist/metrics/window.js +1 -1
  56. package/dist/personal-access-tokens/expiry.js +1 -1
  57. package/dist/personal-access-tokens/handlers/create.write.js +1 -1
  58. package/dist/personal-access-tokens/handlers/revoke.write.js +1 -1
  59. package/dist/personal-access-tokens/revoke-for-user.js +1 -1
  60. package/dist/renderer-simple/simple-renderer.js +49 -13
  61. package/dist/sessions/handlers/revoke-all-for-user.write.js +1 -1
  62. package/dist/sessions/handlers/revoke-all-others.write.js +1 -1
  63. package/dist/sessions/handlers/revoke.write.js +1 -1
  64. package/dist/sessions/session-callbacks.js +1 -1
  65. package/dist/shared/grace-period.d.ts +2 -1
  66. package/dist/shared/grace-period.js +3 -7
  67. package/dist/shared/row-bound-grant.d.ts +1 -1
  68. package/dist/shared/signed-token.d.ts +1 -1
  69. package/dist/shared/signed-token.js +1 -1
  70. package/dist/subscription-mollie/verify-webhook.js +1 -1
  71. package/dist/subscription-stripe/verify-webhook.js +1 -1
  72. package/dist/template-resolver/api.d.ts +1 -1
  73. package/dist/template-resolver/handlers/by-slug.query.d.ts +1 -1
  74. package/dist/template-resolver/handlers/by-tenant.query.d.ts +2 -2
  75. package/dist/template-resolver/handlers/collection-item.query.d.ts +1 -1
  76. package/dist/template-resolver/handlers/collection-list.query.d.ts +1 -1
  77. package/dist/template-resolver/handlers/collection-shared.d.ts +2 -2
  78. package/dist/template-resolver/handlers/find-by-id.query.d.ts +1 -1
  79. package/dist/template-resolver/handlers/list.query.d.ts +1 -1
  80. package/dist/template-resolver/table.d.ts +1 -1
  81. package/dist/tenant/handlers/team-list.query.d.ts +1 -1
  82. package/dist/tenant/handlers/team-list.query.js +1 -1
  83. package/dist/tenant/invited-membership.d.ts +1 -1
  84. package/dist/tenant/invited-membership.js +1 -1
  85. package/dist/tenant-handover/grant.d.ts +1 -1
  86. package/dist/user-data-rights/db/queries/export-jobs.d.ts +1 -1
  87. package/dist/user-data-rights/db/queries/forget-cleanup.d.ts +2 -2
  88. package/dist/user-data-rights/db/queries/forget-cleanup.js +2 -8
  89. package/dist/user-data-rights/deletion-token.d.ts +1 -1
  90. package/dist/user-data-rights/email-templates.d.ts +1 -0
  91. package/dist/user-data-rights/email-templates.js +27 -5
  92. package/dist/user-data-rights/handlers/list-download-attempts.query.js +1 -1
  93. package/dist/user-data-rights/handlers/my-audit-log.query.js +1 -1
  94. package/dist/user-data-rights/lib/default-mailers.js +8 -8
  95. package/dist/waitlist/handlers/invite.write.js +1 -1
  96. package/dist/waitlist/handlers/submit.write.js +1 -1
  97. package/package.json +9 -9
  98. package/src/channel-email/changes.json +6 -0
@@ -2,8 +2,8 @@ import type { EntityTableMeta } from "@cosmicdrift/kumiko-framework/db";
2
2
  import type { HandlerContext } from "@cosmicdrift/kumiko-framework/engine";
3
3
  import type { AggregateMeasure, AggregateTimeBucket } from "@cosmicdrift/kumiko-types/aggregate-types";
4
4
  import type { SchemaTable } from "@cosmicdrift/kumiko-types/schema-table-types";
5
+ import type { Temporal } from "@cosmicdrift/kumiko-types/temporal";
5
6
  import type { WhereObject } from "@cosmicdrift/kumiko-types/where-clause-types";
6
- import type { Temporal } from "temporal-polyfill";
7
7
  import type { MetricRange, MetricScope } from "./constants.js";
8
8
  /** Maps a payload value (e.g. a folder id) to the values `column` may have (e.g. the folder and its subfolders' loan ids). Runs with the tenant handler context, so reads stay tenant-scoped; the result only narrows. An empty result yields no rows. */
9
9
  export type MetricFilterResolver = {
@@ -1,5 +1,5 @@
1
1
  import type { AggregateTimeBucket } from "@cosmicdrift/kumiko-types/aggregate-types";
2
- import { Temporal } from "temporal-polyfill";
2
+ import { Temporal } from "@cosmicdrift/kumiko-types/temporal";
3
3
  import type { MetricRange } from "./constants.js";
4
4
  import type { MetricDefinition } from "./types.js";
5
5
  export type MetricWindow = {
@@ -1,4 +1,4 @@
1
- import { Temporal } from "temporal-polyfill";
1
+ import { Temporal } from "@cosmicdrift/kumiko-types/temporal";
2
2
  const RANGE_HOURS = {
3
3
  "24h": 24,
4
4
  "7d": 7 * 24,
@@ -1,4 +1,4 @@
1
- import { Temporal } from "temporal-polyfill";
1
+ import { Temporal } from "@cosmicdrift/kumiko-types/temporal";
2
2
  export function isExpiredAt(expiresAt) {
3
3
  return (expiresAt !== null && expiresAt.epochMilliseconds <= Temporal.Now.instant().epochMilliseconds);
4
4
  }
@@ -2,7 +2,7 @@ import { insertOne } from "@cosmicdrift/kumiko-framework/bun-db";
2
2
  import { createSystemUser, defineWriteHandler, } from "@cosmicdrift/kumiko-framework/engine";
3
3
  import { UnprocessableError, writeFailure } from "@cosmicdrift/kumiko-framework/errors";
4
4
  import { generateId } from "@cosmicdrift/kumiko-framework/utils";
5
- import { Temporal } from "temporal-polyfill";
5
+ import { Temporal } from "@cosmicdrift/kumiko-types/temporal";
6
6
  import * as z from "zod";
7
7
  import { encryptForDirectWrite, verifyPassword } from "../../shared/index.js";
8
8
  import { UserQueries } from "../../user/index.js";
@@ -2,7 +2,7 @@ import { updateMany } from "@cosmicdrift/kumiko-framework/bun-db";
2
2
  import { defineWriteHandler } from "@cosmicdrift/kumiko-framework/engine";
3
3
  import { UnprocessableError, writeFailure } from "@cosmicdrift/kumiko-framework/errors";
4
4
  import { generateId } from "@cosmicdrift/kumiko-framework/utils";
5
- import { Temporal } from "temporal-polyfill";
5
+ import { Temporal } from "@cosmicdrift/kumiko-types/temporal";
6
6
  import * as z from "zod";
7
7
  import { API_TOKEN_ENTITY_NAME, PatErrors } from "../constants.js";
8
8
  import { PAT_REVOKED_AGGREGATE_TYPE, PAT_REVOKED_EVENT_QN } from "../pat-revoked-event.js";
@@ -2,7 +2,7 @@ import { transaction, updateMany } from "@cosmicdrift/kumiko-framework/bun-db";
2
2
  import { SYSTEM_TENANT_ID } from "@cosmicdrift/kumiko-framework/engine";
3
3
  import { append } from "@cosmicdrift/kumiko-framework/event-store";
4
4
  import { generateId } from "@cosmicdrift/kumiko-framework/utils";
5
- import { Temporal } from "temporal-polyfill";
5
+ import { Temporal } from "@cosmicdrift/kumiko-types/temporal";
6
6
  import { PAT_REVOKED_AGGREGATE_TYPE, PAT_REVOKED_EVENT_QN, patRevokedSchema, } from "./pat-revoked-event.js";
7
7
  import { apiTokenTable } from "./schema/api-token.js";
8
8
  // A pool has begin(); a tx/savepoint handle does not and is already atomic.
@@ -73,25 +73,57 @@ function renderBrandingHeader(branding, primaryColor, logoUrl) {
73
73
  : `<span style="font-size:18px;font-weight:700;color:${escapeHtmlAttr(primaryColor)}">${escapeHtml(branding.productName ?? "")}</span>`;
74
74
  return `<div style="margin:0 0 24px;padding:0 0 16px;border-bottom:3px solid ${escapeHtmlAttr(primaryColor)}">${logo}</div>`;
75
75
  }
76
- function renderBrandingFooter(branding, locale) {
77
- if (!branding)
78
- return "";
76
+ function resolveBrandingFooter(branding, locale) {
79
77
  const pick = (value) => resolveLocalized(value, locale, branding.defaultLocale);
80
78
  const links = (branding.footerLinks ?? []).flatMap((link) => {
81
79
  const url = pick(link.url);
82
80
  if (url === undefined || !isHttpUrl(url))
83
81
  return [];
84
- return [
85
- `<a href="${escapeHtmlAttr(url)}" style="color:#999">${escapeHtml(pick(link.label) ?? "")}</a>`,
86
- ];
82
+ return [{ label: pick(link.label) ?? "", url }];
87
83
  });
88
- const footerText = pick(branding.footerText);
84
+ return { footerText: pick(branding.footerText), links };
85
+ }
86
+ function renderBrandingFooter(branding, locale) {
87
+ if (!branding)
88
+ return "";
89
+ const { footerText, links: resolvedLinks } = resolveBrandingFooter(branding, locale);
90
+ const links = resolvedLinks.map((link) => `<a href="${escapeHtmlAttr(link.url)}" style="color:#999">${escapeHtml(link.label)}</a>`);
89
91
  const footerPartsHtml = [footerText ? escapeHtml(footerText) : "", links.join(" · ")].filter((part) => part !== "");
90
92
  if (footerPartsHtml.length === 0)
91
93
  return "";
92
94
  const footerHtml = footerPartsHtml.join("<br />");
93
95
  return `<p style="margin:16px 0 0;color:#999;font-size:12px">${footerHtml}</p>`;
94
96
  }
97
+ function brandingFooterLines(branding, locale) {
98
+ if (!branding)
99
+ return [];
100
+ const { footerText, links } = resolveBrandingFooter(branding, locale);
101
+ return [
102
+ ...(footerText ? [footerText] : []),
103
+ ...links.map((link) => (link.label ? `${link.label}: ${link.url}` : link.url)),
104
+ ];
105
+ }
106
+ function templateContent(variables) {
107
+ const data = variables; // @cast-boundary render-helper
108
+ // Without structured fields, title + body become the header and a single text section.
109
+ return {
110
+ header: data.header ?? data.title,
111
+ sections: data.sections ?? (data.body ? [{ text: data.body }] : undefined),
112
+ footer: data.footer,
113
+ };
114
+ }
115
+ // Markdown stays as written: its source is already readable plain text.
116
+ function sectionText(section) {
117
+ if ("text" in section)
118
+ return section.text;
119
+ if ("heading" in section)
120
+ return section.heading;
121
+ if ("markdown" in section)
122
+ return section.markdown;
123
+ if ("button" in section)
124
+ return `${section.button.label}: ${section.button.url}`;
125
+ return "";
126
+ }
95
127
  function renderSection(section, primaryColor) {
96
128
  if ("text" in section) {
97
129
  return `<p style="margin:0 0 16px;color:#333;font-size:14px;line-height:1.5">${escapeHtml(section.text)}</p>`;
@@ -118,10 +150,7 @@ export function createSimpleRenderer(branding) {
118
150
  return {
119
151
  name: "simple",
120
152
  async render(input) {
121
- const data = input.variables; // @cast-boundary render-helper
122
- // Fallback: if no structured fields, use title + body as header + single text section
123
- const header = data.header ?? data.title;
124
- const sections = data.sections ?? (data.body ? [{ text: data.body }] : undefined);
153
+ const { header, sections, footer } = templateContent(input.variables);
125
154
  const parts = [];
126
155
  parts.push('<!DOCTYPE html><html><body style="margin:0;padding:0;font-family:sans-serif">');
127
156
  parts.push('<div style="max-width:600px;margin:0 auto;padding:24px">');
@@ -134,13 +163,20 @@ export function createSimpleRenderer(branding) {
134
163
  parts.push(renderSection(section, primaryColor));
135
164
  }
136
165
  }
137
- if (data.footer) {
138
- parts.push(`<p style="margin:24px 0 0;color:#999;font-size:12px;border-top:1px solid #eee;padding-top:16px">${escapeHtml(data.footer)}</p>`);
166
+ if (footer) {
167
+ parts.push(`<p style="margin:24px 0 0;color:#999;font-size:12px;border-top:1px solid #eee;padding-top:16px">${escapeHtml(footer)}</p>`);
139
168
  }
140
169
  parts.push(renderBrandingFooter(branding, input.locale));
141
170
  parts.push("</div></body></html>");
142
171
  return parts.join("");
143
172
  },
173
+ async renderText(input) {
174
+ const { header, sections, footer } = templateContent(input.variables);
175
+ const brandingFooter = brandingFooterLines(branding, input.locale).join("\n");
176
+ return [header, ...(sections ?? []).map(sectionText), footer, brandingFooter]
177
+ .filter((block) => block !== undefined && block !== "")
178
+ .join("\n\n");
179
+ },
144
180
  };
145
181
  }
146
182
  export const simpleRenderer = createSimpleRenderer();
@@ -4,7 +4,7 @@ import { access, defineWriteHandler, SYSTEM_TENANT_ID } from "@cosmicdrift/kumik
4
4
  import { UnprocessableError, writeFailure } from "@cosmicdrift/kumiko-framework/errors";
5
5
  import { append } from "@cosmicdrift/kumiko-framework/event-store";
6
6
  import { generateId } from "@cosmicdrift/kumiko-framework/utils";
7
- import { Temporal } from "temporal-polyfill";
7
+ import { Temporal } from "@cosmicdrift/kumiko-types/temporal";
8
8
  import * as z from "zod";
9
9
  import { userSessionTable } from "../schema/user-session.js";
10
10
  import { SESSION_REVOKED_AGGREGATE_TYPE, SESSION_REVOKED_EVENT_QN, sessionRevokedSchema, } from "../session-revoked-event.js";
@@ -2,7 +2,7 @@ import { updateMany } from "@cosmicdrift/kumiko-framework/bun-db";
2
2
  import { defineWriteHandler } from "@cosmicdrift/kumiko-framework/engine";
3
3
  import { UnprocessableError, writeFailure } from "@cosmicdrift/kumiko-framework/errors";
4
4
  import { generateId } from "@cosmicdrift/kumiko-framework/utils";
5
- import { Temporal } from "temporal-polyfill";
5
+ import { Temporal } from "@cosmicdrift/kumiko-types/temporal";
6
6
  import * as z from "zod";
7
7
  import { SessionErrors } from "../constants.js";
8
8
  import { userSessionTable } from "../schema/user-session.js";
@@ -2,7 +2,7 @@ import { fetchOne, updateMany } from "@cosmicdrift/kumiko-framework/bun-db";
2
2
  import { defineWriteHandler } from "@cosmicdrift/kumiko-framework/engine";
3
3
  import { UnprocessableError, writeFailure } from "@cosmicdrift/kumiko-framework/errors";
4
4
  import { generateId } from "@cosmicdrift/kumiko-framework/utils";
5
- import { Temporal } from "temporal-polyfill";
5
+ import { Temporal } from "@cosmicdrift/kumiko-types/temporal";
6
6
  import * as z from "zod";
7
7
  import { SessionErrors, USER_SESSION_ENTITY_NAME } from "../constants.js";
8
8
  import { userSessionTable } from "../schema/user-session.js";
@@ -2,7 +2,7 @@ import { fetchOne, insertOne, transaction, updateMany } from "@cosmicdrift/kumik
2
2
  import { buildSessionRoles, SYSTEM_TENANT_ID } from "@cosmicdrift/kumiko-framework/engine";
3
3
  import { append } from "@cosmicdrift/kumiko-framework/event-store";
4
4
  import { generateId, parseRoles } from "@cosmicdrift/kumiko-framework/utils";
5
- import { Temporal } from "temporal-polyfill";
5
+ import { Temporal } from "@cosmicdrift/kumiko-types/temporal";
6
6
  import { encryptForDirectWrite } from "../shared/index.js";
7
7
  import { tenantMembershipsTable } from "../tenant/index.js";
8
8
  import { isPrincipalBlocked, userTable } from "../user/index.js";
@@ -1,7 +1,8 @@
1
1
  /** Shared grace-period check for user-data-rights + tenant-lifecycle cancel flows. */
2
2
  /// <reference types="temporal-polyfill/global" preserve="true" />
3
+ import { Temporal } from "@cosmicdrift/kumiko-types/temporal";
3
4
  /**
4
5
  * True when `gracePeriodEnd` is still in the future.
5
- * Uses the polyfill Instant API so callers don't depend on `globalThis.Temporal`.
6
+ * Uses the module's Temporal so callers don't depend on `globalThis.Temporal`.
6
7
  */
7
8
  export declare function isWithinGracePeriod(gracePeriodEnd: Temporal.Instant | null): boolean;
@@ -1,14 +1,10 @@
1
1
  /// <reference types="temporal-polyfill/global" preserve="true" />
2
2
  /** Shared grace-period check for user-data-rights + tenant-lifecycle cancel flows. */
3
- import { Temporal as TemporalPolyfill } from "temporal-polyfill";
3
+ import { Temporal } from "@cosmicdrift/kumiko-types/temporal";
4
4
  /**
5
5
  * True when `gracePeriodEnd` is still in the future.
6
- * Uses the polyfill Instant API so callers don't depend on `globalThis.Temporal`.
6
+ * Uses the module's Temporal so callers don't depend on `globalThis.Temporal`.
7
7
  */
8
8
  export function isWithinGracePeriod(gracePeriodEnd) {
9
- // @cast-boundary temporal-polyfill-vs-ambient: same TC39 Temporal.Instant
10
- // at runtime — row types resolve against ambient Temporal; polyfill Instant
11
- // is a separate nominal type across the two .d.ts sources.
12
- return (gracePeriodEnd != null &&
13
- TemporalPolyfill.Instant.compare(gracePeriodEnd, TemporalPolyfill.Now.instant()) > 0);
9
+ return (gracePeriodEnd != null && Temporal.Instant.compare(gracePeriodEnd, Temporal.Now.instant()) > 0);
14
10
  }
@@ -1,4 +1,4 @@
1
- import type { Temporal } from "temporal-polyfill";
1
+ import type { Temporal } from "@cosmicdrift/kumiko-types/temporal";
2
2
  export type RowBoundGrantResult = {
3
3
  readonly ok: true;
4
4
  readonly subject: string;
@@ -1,4 +1,4 @@
1
- import { Temporal } from "temporal-polyfill";
1
+ import { Temporal } from "@cosmicdrift/kumiko-types/temporal";
2
2
  export type VerifyResult = {
3
3
  readonly ok: true;
4
4
  readonly userId: string;
@@ -12,7 +12,7 @@
12
12
  // Timing-safe comparison on verify so a valid-length forgery can't leak
13
13
  // signal through a short-circuit.
14
14
  import { createHmac, timingSafeEqual } from "node:crypto";
15
- import { Temporal } from "temporal-polyfill";
15
+ import { Temporal } from "@cosmicdrift/kumiko-types/temporal";
16
16
  function sign(input, secret) {
17
17
  return createHmac("sha256", secret).update(input).digest("base64url");
18
18
  }
@@ -15,7 +15,7 @@
15
15
  // Created-Event mit subscription-id bekommt.
16
16
  import { parseProviderConsentId, SubscriptionEventTypes, SubscriptionStatuses, } from "@cosmicdrift/kumiko-bundled-features/billing-foundation";
17
17
  import { createFallbackLogger } from "@cosmicdrift/kumiko-framework/logging";
18
- import { Temporal } from "temporal-polyfill";
18
+ import { Temporal } from "@cosmicdrift/kumiko-types/temporal";
19
19
  import { MOLLIE_PROVIDER_NAME } from "./constants.js";
20
20
  const log = createFallbackLogger("subscription-mollie");
21
21
  export function verifyAndParseMollieWebhook(client, options) {
@@ -34,8 +34,8 @@
34
34
  import { BillingEventKinds, parseProviderConsentId, SubscriptionEventTypes, SubscriptionStatuses, } from "@cosmicdrift/kumiko-bundled-features/billing-foundation";
35
35
  import { ExtraRouteRejection } from "@cosmicdrift/kumiko-framework/api";
36
36
  import { createFallbackLogger } from "@cosmicdrift/kumiko-framework/logging";
37
+ import { Temporal } from "@cosmicdrift/kumiko-types/temporal";
37
38
  import Stripe from "stripe";
38
- import { Temporal } from "temporal-polyfill";
39
39
  import { STRIPE_PROVIDER_NAME, StripeEventTypes } from "./constants.js";
40
40
  const log = createFallbackLogger("subscription-stripe");
41
41
  export function isResourceMissingStripeError(error) {
@@ -1,6 +1,6 @@
1
1
  import type { DbConnection } from "@cosmicdrift/kumiko-framework/db";
2
2
  import type { SessionUser, TenantId } from "@cosmicdrift/kumiko-framework/engine";
3
- import type { Temporal } from "temporal-polyfill";
3
+ import type { Temporal } from "@cosmicdrift/kumiko-types/temporal";
4
4
  import type { ContentFormat, TemplateKind } from "./constants.js";
5
5
  export type TemplateResource = {
6
6
  readonly id: string;
@@ -10,5 +10,5 @@ export declare const bySlugQuery: import("@cosmicdrift/kumiko-types/define-handl
10
10
  content: string | null;
11
11
  contentFormat: string;
12
12
  folder: string | null;
13
- modifiedAt: import("temporal-spec").Temporal.Instant | null;
13
+ modifiedAt: Temporal.Instant | null;
14
14
  } | null, import("@cosmicdrift/kumiko-types/event-type-map").KumikoEventTypeMap>;
@@ -1,4 +1,4 @@
1
- import type { Temporal } from "temporal-polyfill";
1
+ import type { Temporal } from "@cosmicdrift/kumiko-types/temporal";
2
2
  import * as z from "zod";
3
3
  export type TextBlockSummary = {
4
4
  readonly slug: string;
@@ -17,6 +17,6 @@ export declare const byTenantQuery: import("@cosmicdrift/kumiko-types/define-han
17
17
  title: string | null;
18
18
  content: string | null;
19
19
  folder: string | null;
20
- modifiedAt: Temporal.Instant | null;
20
+ modifiedAt: globalThis.Temporal.Instant | null;
21
21
  }[];
22
22
  }, import("@cosmicdrift/kumiko-types/event-type-map").KumikoEventTypeMap>;
@@ -11,5 +11,5 @@ export declare function makeCollectionItemQuery(collection: ContentCollectionDef
11
11
  content: string | null;
12
12
  contentFormat: string;
13
13
  folder: string | null;
14
- modifiedAt: import("temporal-spec").Temporal.Instant | null;
14
+ modifiedAt: Temporal.Instant | null;
15
15
  } | null, import("@cosmicdrift/kumiko-types/event-type-map").KumikoEventTypeMap>;
@@ -10,6 +10,6 @@ export declare function makeCollectionListQuery(collection: ContentCollectionDef
10
10
  content: string | null;
11
11
  contentFormat: string;
12
12
  folder: string | null;
13
- modifiedAt: import("temporal-spec").Temporal.Instant | null;
13
+ modifiedAt: Temporal.Instant | null;
14
14
  }[];
15
15
  }, import("@cosmicdrift/kumiko-types/event-type-map").KumikoEventTypeMap>;
@@ -1,6 +1,6 @@
1
1
  import type { EventStoreExecutor } from "@cosmicdrift/kumiko-framework/db";
2
2
  import type { AccessRule, ContentCollectionDefinition, SessionUser, TenantId } from "@cosmicdrift/kumiko-framework/engine";
3
- import type { Temporal } from "temporal-polyfill";
3
+ import type { Temporal } from "@cosmicdrift/kumiko-types/temporal";
4
4
  import { templateResourcesTable } from "../table.js";
5
5
  import { userContentEntriesTable } from "../user-content-table.js";
6
6
  export declare const DEFAULT_COLLECTION_ACCESS: AccessRule;
@@ -31,5 +31,5 @@ export declare function toCollectionEntry(row: CollectionEntryRow): {
31
31
  content: string | null;
32
32
  contentFormat: string;
33
33
  folder: string | null;
34
- modifiedAt: Temporal.Instant | null;
34
+ modifiedAt: globalThis.Temporal.Instant | null;
35
35
  };
@@ -15,5 +15,5 @@ export declare const findByIdQuery: import("@cosmicdrift/kumiko-types/define-han
15
15
  scope: string;
16
16
  parentTemplateId: string | null;
17
17
  status: string;
18
- modifiedAt: import("temporal-spec").Temporal.Instant | null;
18
+ modifiedAt: Temporal.Instant | null;
19
19
  } | null, import("@cosmicdrift/kumiko-types/event-type-map").KumikoEventTypeMap>;
@@ -22,5 +22,5 @@ export declare const listQuery: import("@cosmicdrift/kumiko-types/define-handler
22
22
  scope: string;
23
23
  status: string;
24
24
  contentFormat: string;
25
- modifiedAt: import("temporal-spec").Temporal.Instant | null;
25
+ modifiedAt: Temporal.Instant | null;
26
26
  }[], import("@cosmicdrift/kumiko-types/event-type-map").KumikoEventTypeMap>;
@@ -1,4 +1,4 @@
1
- import type { Temporal } from "temporal-polyfill";
1
+ import type { Temporal } from "@cosmicdrift/kumiko-types/temporal";
2
2
  export declare const templateResourceEntity: import("@cosmicdrift/kumiko-types/fields").EntityDefinition<{
3
3
  slug: {
4
4
  readonly type: "text";
@@ -1,4 +1,4 @@
1
- import { Temporal } from "temporal-polyfill";
1
+ import { Temporal } from "@cosmicdrift/kumiko-types/temporal";
2
2
  import * as z from "zod";
3
3
  type TeamStatus = "active" | "pending";
4
4
  type TeamRow = {
@@ -2,7 +2,7 @@ import { selectMany } from "@cosmicdrift/kumiko-framework/bun-db";
2
2
  import { access, definePagedQueryHandler, MAX_LIST_LIMIT, } from "@cosmicdrift/kumiko-framework/engine";
3
3
  import { InternalError } from "@cosmicdrift/kumiko-framework/errors";
4
4
  import { parseRoles } from "@cosmicdrift/kumiko-framework/utils";
5
- import { Temporal } from "temporal-polyfill";
5
+ import { Temporal } from "@cosmicdrift/kumiko-types/temporal";
6
6
  import * as z from "zod";
7
7
  import { userSessionTable } from "../../sessions/index.js";
8
8
  import { decryptStoredPii, mapWithConcurrency } from "../../shared/index.js";
@@ -1,6 +1,6 @@
1
1
  import { type DbRunner } from "@cosmicdrift/kumiko-framework/db";
2
2
  import { type TenantId } from "@cosmicdrift/kumiko-framework/engine";
3
- import { Temporal } from "temporal-polyfill";
3
+ import { Temporal } from "@cosmicdrift/kumiko-types/temporal";
4
4
  export type InvitedMembershipOptions = {
5
5
  readonly userId: string;
6
6
  readonly tenantId: TenantId;
@@ -3,7 +3,7 @@ import { createEventStoreExecutor, createTenantDb, } from "@cosmicdrift/kumiko-f
3
3
  import { createSystemUser } from "@cosmicdrift/kumiko-framework/engine";
4
4
  import { ConflictError, writeFailure } from "@cosmicdrift/kumiko-framework/errors";
5
5
  import { parseRoles } from "@cosmicdrift/kumiko-framework/utils";
6
- import { Temporal } from "temporal-polyfill";
6
+ import { Temporal } from "@cosmicdrift/kumiko-types/temporal";
7
7
  import { TenantErrors } from "./constants.js";
8
8
  import { findForbiddenMembershipRole, reservedMembershipRoleError } from "./membership-roles.js";
9
9
  import { tenantMembershipEntity, tenantMembershipsTable } from "./membership-table.js";
@@ -1,4 +1,4 @@
1
- import type { Temporal } from "temporal-polyfill";
1
+ import type { Temporal } from "@cosmicdrift/kumiko-types/temporal";
2
2
  export declare function tenantHandoverPurpose(entityType: string): string;
3
3
  export declare function signTenantHandoverGrant(args: {
4
4
  readonly entityType: string;
@@ -1,6 +1,6 @@
1
1
  import type { DbConnection } from "@cosmicdrift/kumiko-framework/db";
2
2
  import type { TenantId } from "@cosmicdrift/kumiko-framework/engine";
3
- import type { Temporal } from "temporal-polyfill";
3
+ import type { Temporal } from "@cosmicdrift/kumiko-types/temporal";
4
4
  export type ExportJobCleanupCandidate = {
5
5
  readonly id: string;
6
6
  readonly version: number;
@@ -1,5 +1,5 @@
1
1
  import type { DbConnection } from "@cosmicdrift/kumiko-framework/db";
2
- import { Temporal as TemporalPolyfill } from "temporal-polyfill";
3
- export declare function selectUsersDueForForgetCleanup(db: DbConnection, status: string, gracePeriodEndCutoff: InstanceType<typeof TemporalPolyfill.Instant> | string): Promise<readonly {
2
+ import { Temporal } from "@cosmicdrift/kumiko-types/temporal";
3
+ export declare function selectUsersDueForForgetCleanup(db: DbConnection, status: string, gracePeriodEndCutoff: Temporal.Instant | string): Promise<readonly {
4
4
  id: string;
5
5
  }[]>;
@@ -1,15 +1,9 @@
1
1
  import { selectMany } from "@cosmicdrift/kumiko-framework/bun-db";
2
- // Value-only import, aliased to avoid shadowing the ambient global
3
- // `Temporal` TYPE that gracePeriodEndCutoff's parameter type resolves
4
- // against (same #1438 dual-package-hazard pattern as event-store.ts).
5
- import { Temporal as TemporalPolyfill } from "temporal-polyfill";
2
+ import { Temporal } from "@cosmicdrift/kumiko-types/temporal";
6
3
  import { userTable } from "../../../user/index.js";
7
4
  export async function selectUsersDueForForgetCleanup(db, status, gracePeriodEndCutoff) {
8
- // @cast-boundary temporal-polyfill-vs-ambient: same TC39 Temporal.Instant
9
- // at runtime, distinct nominal types across the two .d.ts sources (see
10
- // event-store.ts). Only ever consumed via the `lte` comparison below.
11
5
  const cutoff = typeof gracePeriodEndCutoff === "string"
12
- ? TemporalPolyfill.Instant.from(gracePeriodEndCutoff)
6
+ ? Temporal.Instant.from(gracePeriodEndCutoff)
13
7
  : gracePeriodEndCutoff;
14
8
  return selectMany(db, userTable, {
15
9
  status,
@@ -1,4 +1,4 @@
1
- import type { Temporal } from "temporal-polyfill";
1
+ import type { Temporal } from "@cosmicdrift/kumiko-types/temporal";
2
2
  import { type RowBoundGrantResult } from "../shared/index.js";
3
3
  export declare function signDeletionToken(userId: string, requestId: string, ttlMinutes: number, secret: string, now?: Temporal.Instant): {
4
4
  readonly token: string;
@@ -4,6 +4,7 @@ export declare const GDPR_MAIL_EN: Readonly<Record<string, string>>;
4
4
  export type RenderedEmail = {
5
5
  readonly subject: string;
6
6
  readonly html: string;
7
+ readonly text: string;
7
8
  };
8
9
  export type RenderExportReadyEmailArgs = {
9
10
  readonly downloadUrl: string;
@@ -4,7 +4,7 @@
4
4
  // branding pass send*Email callbacks and these defaults do not run.
5
5
  import { mailT, registerMailTranslations } from "@cosmicdrift/kumiko-framework/i18n";
6
6
  import { escapeHtml, escapeHtmlAttr } from "@cosmicdrift/kumiko-headless";
7
- import { Temporal } from "temporal-polyfill";
7
+ import { Temporal } from "@cosmicdrift/kumiko-types/temporal";
8
8
  export function normalizeGdprMailLocale(raw) {
9
9
  if (!raw)
10
10
  return undefined;
@@ -45,7 +45,13 @@ export function renderExportReadyEmail(args) {
45
45
  <p style="margin: 0 0 24px;">${renderButton({ url: args.downloadUrl, label: t(locale, "gdpr.mail.exportReady.button") })}</p>
46
46
  <p style="margin: 0 0 8px; font-size: 13px; color: #555;">${escapeHtml(t(locale, "gdpr.mail.exportReady.expiry", { when: formatTimestamp(args.expiresAt) }))}</p>
47
47
  ${renderFallbackUrl({ url: args.downloadUrl, label: t(locale, "gdpr.mail.fallbackUrl") })}`;
48
- return { subject, html: renderShell({ title: subject, bodyHtml: wrapCell(body), locale }) };
48
+ const text = plainTextBody([
49
+ t(locale, "gdpr.mail.greeting"),
50
+ t(locale, "gdpr.mail.exportReady.intro", { app }),
51
+ `${t(locale, "gdpr.mail.exportReady.button")}: ${args.downloadUrl}`,
52
+ t(locale, "gdpr.mail.exportReady.expiry", { when: formatTimestamp(args.expiresAt) }),
53
+ ]);
54
+ return { subject, html: renderShell({ title: subject, bodyHtml: wrapCell(body), locale }), text };
49
55
  }
50
56
  export function renderExportFailedEmail(args) {
51
57
  const locale = args.locale ?? "en";
@@ -54,7 +60,11 @@ export function renderExportFailedEmail(args) {
54
60
  const body = `
55
61
  <p style="margin: 0 0 16px; font-size: 16px;">${escapeHtml(t(locale, "gdpr.mail.greeting"))}</p>
56
62
  <p style="margin: 0; font-size: 14px; line-height: 1.5;">${escapeHtml(t(locale, "gdpr.mail.exportFailed.intro", { app }))}</p>`;
57
- return { subject, html: renderShell({ title: subject, bodyHtml: wrapCell(body), locale }) };
63
+ const text = plainTextBody([
64
+ t(locale, "gdpr.mail.greeting"),
65
+ t(locale, "gdpr.mail.exportFailed.intro", { app }),
66
+ ]);
67
+ return { subject, html: renderShell({ title: subject, bodyHtml: wrapCell(body), locale }), text };
58
68
  }
59
69
  export function renderDeletionRequestedEmail(args) {
60
70
  const locale = args.locale ?? "en";
@@ -65,7 +75,12 @@ export function renderDeletionRequestedEmail(args) {
65
75
  <p style="margin: 0 0 16px; font-size: 16px;">${escapeHtml(t(locale, "gdpr.mail.greeting"))}</p>
66
76
  <p style="margin: 0 0 16px; font-size: 14px; line-height: 1.5;">${escapeHtml(t(locale, "gdpr.mail.deletionRequested.intro", { app, when }))}</p>
67
77
  <p style="margin: 0; font-size: 13px; color: #555;">${escapeHtml(t(locale, "gdpr.mail.deletionRequested.cancel"))}</p>`;
68
- return { subject, html: renderShell({ title: subject, bodyHtml: wrapCell(body), locale }) };
78
+ const text = plainTextBody([
79
+ t(locale, "gdpr.mail.greeting"),
80
+ t(locale, "gdpr.mail.deletionRequested.intro", { app, when }),
81
+ t(locale, "gdpr.mail.deletionRequested.cancel"),
82
+ ]);
83
+ return { subject, html: renderShell({ title: subject, bodyHtml: wrapCell(body), locale }), text };
69
84
  }
70
85
  export function renderDeletionExecutedEmail(args) {
71
86
  const locale = args.locale ?? "en";
@@ -75,7 +90,14 @@ export function renderDeletionExecutedEmail(args) {
75
90
  const body = `
76
91
  <p style="margin: 0 0 16px; font-size: 16px;">${escapeHtml(t(locale, "gdpr.mail.greeting"))}</p>
77
92
  <p style="margin: 0; font-size: 14px; line-height: 1.5;">${escapeHtml(t(locale, "gdpr.mail.deletionExecuted.intro", { app, when }))}</p>`;
78
- return { subject, html: renderShell({ title: subject, bodyHtml: wrapCell(body), locale }) };
93
+ const text = plainTextBody([
94
+ t(locale, "gdpr.mail.greeting"),
95
+ t(locale, "gdpr.mail.deletionExecuted.intro", { app, when }),
96
+ ]);
97
+ return { subject, html: renderShell({ title: subject, bodyHtml: wrapCell(body), locale }), text };
98
+ }
99
+ function plainTextBody(paragraphs) {
100
+ return paragraphs.join("\n\n");
79
101
  }
80
102
  function wrapCell(bodyHtml) {
81
103
  return `<tr><td>${bodyHtml}</td></tr>`;
@@ -1,6 +1,6 @@
1
1
  import { selectMany } from "@cosmicdrift/kumiko-framework/bun-db";
2
2
  import { defineQueryHandler } from "@cosmicdrift/kumiko-framework/engine";
3
- import { Temporal } from "temporal-polyfill";
3
+ import { Temporal } from "@cosmicdrift/kumiko-types/temporal";
4
4
  import * as z from "zod";
5
5
  import { downloadAttemptsTable } from "../schema/download-attempt.js";
6
6
  // Operator-Query: invalid Download-Attempts (S2.U7).
@@ -1,7 +1,7 @@
1
1
  import { selectMany } from "@cosmicdrift/kumiko-framework/bun-db";
2
2
  import { defineQueryHandler } from "@cosmicdrift/kumiko-framework/engine";
3
3
  import { eventsTable } from "@cosmicdrift/kumiko-framework/event-store";
4
- import { Temporal } from "temporal-polyfill";
4
+ import { Temporal } from "@cosmicdrift/kumiko-types/temporal";
5
5
  import * as z from "zod";
6
6
  // DSGVO Art. 15 Selbstauskunft — User reads HIS OWN audit-log.
7
7
  // WHERE createdBy = ctx.user.id ist hard-coded (kein userId-Param,
@@ -29,34 +29,34 @@ function localeFor(userLocale, defaults) {
29
29
  export function makeDefaultExportReadyEmail(resolveTransport, defaults = {}) {
30
30
  return async (args) => {
31
31
  const transport = await resolveTransport(args.tenantId);
32
- const { subject, html } = renderExportReadyEmail({
32
+ const { subject, html, text } = renderExportReadyEmail({
33
33
  downloadUrl: args.downloadUrl,
34
34
  expiresAt: args.expiresAt,
35
35
  locale: localeFor(args.userLocale, defaults),
36
36
  appName: defaults.appName,
37
37
  });
38
- await transport.send({ to: args.userEmail, subject, html });
38
+ await transport.send({ to: args.userEmail, subject, html, text });
39
39
  };
40
40
  }
41
41
  export function makeDefaultExportFailedEmail(resolveTransport, defaults = {}) {
42
42
  return async (args) => {
43
43
  const transport = await resolveTransport(args.tenantId);
44
- const { subject, html } = renderExportFailedEmail({
44
+ const { subject, html, text } = renderExportFailedEmail({
45
45
  locale: localeFor(args.userLocale, defaults),
46
46
  appName: defaults.appName,
47
47
  });
48
- await transport.send({ to: args.userEmail, subject, html });
48
+ await transport.send({ to: args.userEmail, subject, html, text });
49
49
  };
50
50
  }
51
51
  export function makeDefaultDeletionRequestedEmail(resolveTransport, defaults = {}) {
52
52
  return async (args) => {
53
53
  const transport = await resolveTransport(args.tenantId);
54
- const { subject, html } = renderDeletionRequestedEmail({
54
+ const { subject, html, text } = renderDeletionRequestedEmail({
55
55
  gracePeriodEnd: args.gracePeriodEnd,
56
56
  locale: localeFor(args.userLocale, defaults),
57
57
  appName: defaults.appName,
58
58
  });
59
- await transport.send({ to: args.userEmail, subject, html });
59
+ await transport.send({ to: args.userEmail, subject, html, text });
60
60
  };
61
61
  }
62
62
  export function makeDefaultDeletionExecutedEmail(resolveTransport, defaults = {}) {
@@ -70,11 +70,11 @@ export function makeDefaultDeletionExecutedEmail(resolveTransport, defaults = {}
70
70
  return;
71
71
  }
72
72
  const transport = await resolveTransport(tenantId);
73
- const { subject, html } = renderDeletionExecutedEmail({
73
+ const { subject, html, text } = renderDeletionExecutedEmail({
74
74
  executedAt: args.executedAt,
75
75
  locale: localeFor(args.userLocale, defaults),
76
76
  appName: defaults.appName,
77
77
  });
78
- await transport.send({ to: args.userEmail, subject, html });
78
+ await transport.send({ to: args.userEmail, subject, html, text });
79
79
  };
80
80
  }