@cosmicdrift/kumiko-samples 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 (82) hide show
  1. package/package.json +1 -1
  2. package/packages/bundled-features/package.json +1 -1
  3. package/packages/bundled-features/src/audit/handlers/list.query.ts +1 -1
  4. package/packages/bundled-features/src/auth-email-password/email-templates.ts +1 -1
  5. package/packages/bundled-features/src/auth-email-password/handlers/invite-accept-with-login.write.ts +1 -1
  6. package/packages/bundled-features/src/auth-email-password/handlers/invite-accept.write.ts +1 -1
  7. package/packages/bundled-features/src/auth-email-password/handlers/invite-create.write.ts +1 -1
  8. package/packages/bundled-features/src/auth-email-password/handlers/signup-request.write.ts +1 -1
  9. package/packages/bundled-features/src/auth-email-password/handlers/token-request-handler.ts +1 -1
  10. package/packages/bundled-features/src/auth-email-password/reset-token.ts +1 -1
  11. package/packages/bundled-features/src/auth-email-password/unlock-token.ts +1 -1
  12. package/packages/bundled-features/src/auth-email-password/verification-token.ts +1 -1
  13. package/packages/bundled-features/src/auth-mfa/handlers/enable-confirm-preauth.write.ts +1 -1
  14. package/packages/bundled-features/src/auth-mfa/handlers/enable-confirm.write.ts +1 -1
  15. package/packages/bundled-features/src/auth-mfa/hmac-token-codec.ts +1 -1
  16. package/packages/bundled-features/src/auth-mfa/mfa-challenge-token.ts +1 -1
  17. package/packages/bundled-features/src/auth-mfa/mfa-preauth-setup-token.ts +1 -1
  18. package/packages/bundled-features/src/auth-mfa/mfa-setup-token.ts +1 -1
  19. package/packages/bundled-features/src/billing-foundation/constants.ts +3 -17
  20. package/packages/bundled-features/src/billing-foundation/consumer-protection/confirmation-mail.ts +1 -1
  21. package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-mail.ts +1 -1
  22. package/packages/bundled-features/src/billing-foundation/feature.ts +2 -9
  23. package/packages/bundled-features/src/billing-foundation/handlers/sync-subscription.write.ts +3 -11
  24. package/packages/bundled-features/src/cap-counter/book-cap-usage.ts +1 -1
  25. package/packages/bundled-features/src/cap-counter/enforce-cap.ts +1 -9
  26. package/packages/bundled-features/src/channel-email/changes.json +6 -0
  27. package/packages/bundled-features/src/channel-email/email-channel.ts +15 -5
  28. package/packages/bundled-features/src/channel-email/pii-guard.ts +6 -1
  29. package/packages/bundled-features/src/channel-email/smtp-transport.ts +1 -0
  30. package/packages/bundled-features/src/channel-email/types.ts +3 -0
  31. package/packages/bundled-features/src/delivery/handlers/log.query.ts +1 -1
  32. package/packages/bundled-features/src/delivery/jobs.ts +3 -1
  33. package/packages/bundled-features/src/delivery/types.ts +3 -0
  34. package/packages/bundled-features/src/feature-toggles/handlers/set.write.ts +1 -1
  35. package/packages/bundled-features/src/form-draft/db/queries/cleanup.ts +1 -1
  36. package/packages/bundled-features/src/form-draft/db/queries/owned-file-refs.ts +1 -1
  37. package/packages/bundled-features/src/form-draft/lookup.ts +1 -1
  38. package/packages/bundled-features/src/inbound-mail-foundation/handlers/ingest-message.write.ts +1 -1
  39. package/packages/bundled-features/src/inbound-mail-foundation/oauth-access-token.ts +1 -1
  40. package/packages/bundled-features/src/inbound-mail-foundation/oauth-state.ts +1 -1
  41. package/packages/bundled-features/src/inbound-mail-foundation/watch-supervisor.ts +1 -1
  42. package/packages/bundled-features/src/inbound-provider-imap/feature.ts +1 -1
  43. package/packages/bundled-features/src/inbound-provider-imap/imap-client.ts +1 -1
  44. package/packages/bundled-features/src/jobs/job-instant.ts +1 -1
  45. package/packages/bundled-features/src/metrics/compute.ts +1 -1
  46. package/packages/bundled-features/src/metrics/feature.ts +1 -1
  47. package/packages/bundled-features/src/metrics/types.ts +1 -1
  48. package/packages/bundled-features/src/metrics/window.ts +1 -1
  49. package/packages/bundled-features/src/personal-access-tokens/expiry.ts +1 -1
  50. package/packages/bundled-features/src/personal-access-tokens/handlers/create.write.ts +1 -1
  51. package/packages/bundled-features/src/personal-access-tokens/handlers/revoke.write.ts +1 -1
  52. package/packages/bundled-features/src/personal-access-tokens/revoke-for-user.ts +1 -1
  53. package/packages/bundled-features/src/renderer-simple/simple-renderer.ts +68 -15
  54. package/packages/bundled-features/src/sessions/handlers/revoke-all-for-user.write.ts +1 -1
  55. package/packages/bundled-features/src/sessions/handlers/revoke-all-others.write.ts +1 -1
  56. package/packages/bundled-features/src/sessions/handlers/revoke.write.ts +1 -1
  57. package/packages/bundled-features/src/sessions/session-callbacks.ts +1 -1
  58. package/packages/bundled-features/src/shared/grace-period.ts +3 -10
  59. package/packages/bundled-features/src/shared/row-bound-grant.ts +1 -1
  60. package/packages/bundled-features/src/shared/signed-token.ts +1 -1
  61. package/packages/bundled-features/src/subscription-mollie/verify-webhook.ts +1 -1
  62. package/packages/bundled-features/src/subscription-stripe/verify-webhook.ts +1 -1
  63. package/packages/bundled-features/src/template-resolver/api.ts +1 -1
  64. package/packages/bundled-features/src/template-resolver/handlers/by-tenant.query.ts +1 -1
  65. package/packages/bundled-features/src/template-resolver/handlers/collection-shared.ts +1 -1
  66. package/packages/bundled-features/src/template-resolver/table.ts +1 -1
  67. package/packages/bundled-features/src/tenant/handlers/team-list.query.ts +1 -1
  68. package/packages/bundled-features/src/tenant/invited-membership.ts +1 -1
  69. package/packages/bundled-features/src/tenant-handover/grant.ts +1 -1
  70. package/packages/bundled-features/src/user-data-rights/db/queries/export-jobs.ts +1 -1
  71. package/packages/bundled-features/src/user-data-rights/db/queries/forget-cleanup.ts +3 -9
  72. package/packages/bundled-features/src/user-data-rights/deletion-token.ts +1 -1
  73. package/packages/bundled-features/src/user-data-rights/email-templates.ts +29 -5
  74. package/packages/bundled-features/src/user-data-rights/handlers/list-download-attempts.query.ts +1 -1
  75. package/packages/bundled-features/src/user-data-rights/handlers/my-audit-log.query.ts +1 -1
  76. package/packages/bundled-features/src/user-data-rights/lib/default-mailers.ts +8 -8
  77. package/packages/bundled-features/src/waitlist/handlers/invite.write.ts +1 -1
  78. package/packages/bundled-features/src/waitlist/handlers/submit.write.ts +1 -1
  79. package/samples/apps/marketing-demo/package.json +1 -0
  80. package/samples/apps/marketing-demo/src/app/seed.ts +1 -1
  81. package/samples/apps/use-all-bundled/package.json +1 -0
  82. package/samples/apps/use-all-bundled/src/app/seed.ts +1 -1
@@ -7,7 +7,7 @@ import {
7
7
  } from "@cosmicdrift/kumiko-framework/engine";
8
8
  import { UnprocessableError, writeFailure } from "@cosmicdrift/kumiko-framework/errors";
9
9
  import { generateId } from "@cosmicdrift/kumiko-framework/utils";
10
- import { Temporal } from "temporal-polyfill";
10
+ import { Temporal } from "@cosmicdrift/kumiko-types/temporal";
11
11
  import * as z from "zod";
12
12
  import { encryptForDirectWrite, verifyPassword } from "../../shared/index.js";
13
13
  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";
@@ -3,7 +3,7 @@ import type { DbRunner } from "@cosmicdrift/kumiko-framework/db";
3
3
  import { SYSTEM_TENANT_ID } from "@cosmicdrift/kumiko-framework/engine";
4
4
  import { append } from "@cosmicdrift/kumiko-framework/event-store";
5
5
  import { generateId } from "@cosmicdrift/kumiko-framework/utils";
6
- import { Temporal } from "temporal-polyfill";
6
+ import { Temporal } from "@cosmicdrift/kumiko-types/temporal";
7
7
  import {
8
8
  PAT_REVOKED_AGGREGATE_TYPE,
9
9
  PAT_REVOKED_EVENT_QN,
@@ -117,21 +117,35 @@ function renderBrandingHeader(
117
117
  return `<div style="margin:0 0 24px;padding:0 0 16px;border-bottom:3px solid ${escapeHtmlAttr(primaryColor)}">${logo}</div>`;
118
118
  }
119
119
 
120
- function renderBrandingFooter(
121
- branding: MailBranding | undefined,
120
+ type ResolvedBrandingFooter = {
121
+ readonly footerText: string | undefined;
122
+ readonly links: readonly { readonly label: string; readonly url: string }[];
123
+ };
124
+
125
+ function resolveBrandingFooter(
126
+ branding: MailBranding,
122
127
  locale: string | undefined,
123
- ): string {
124
- if (!branding) return "";
128
+ ): ResolvedBrandingFooter {
125
129
  const pick = (value: LocalizedText | undefined): string | undefined =>
126
130
  resolveLocalized(value, locale, branding.defaultLocale);
127
131
  const links = (branding.footerLinks ?? []).flatMap((link) => {
128
132
  const url = pick(link.url);
129
133
  if (url === undefined || !isHttpUrl(url)) return [];
130
- return [
131
- `<a href="${escapeHtmlAttr(url)}" style="color:#999">${escapeHtml(pick(link.label) ?? "")}</a>`,
132
- ];
134
+ return [{ label: pick(link.label) ?? "", url }];
133
135
  });
134
- const footerText = pick(branding.footerText);
136
+ return { footerText: pick(branding.footerText), links };
137
+ }
138
+
139
+ function renderBrandingFooter(
140
+ branding: MailBranding | undefined,
141
+ locale: string | undefined,
142
+ ): string {
143
+ if (!branding) return "";
144
+ const { footerText, links: resolvedLinks } = resolveBrandingFooter(branding, locale);
145
+ const links = resolvedLinks.map(
146
+ (link) =>
147
+ `<a href="${escapeHtmlAttr(link.url)}" style="color:#999">${escapeHtml(link.label)}</a>`,
148
+ );
135
149
  const footerPartsHtml = [footerText ? escapeHtml(footerText) : "", links.join(" · ")].filter(
136
150
  (part) => part !== "",
137
151
  );
@@ -140,6 +154,18 @@ function renderBrandingFooter(
140
154
  return `<p style="margin:16px 0 0;color:#999;font-size:12px">${footerHtml}</p>`;
141
155
  }
142
156
 
157
+ function brandingFooterLines(
158
+ branding: MailBranding | undefined,
159
+ locale: string | undefined,
160
+ ): readonly string[] {
161
+ if (!branding) return [];
162
+ const { footerText, links } = resolveBrandingFooter(branding, locale);
163
+ return [
164
+ ...(footerText ? [footerText] : []),
165
+ ...links.map((link) => (link.label ? `${link.label}: ${link.url}` : link.url)),
166
+ ];
167
+ }
168
+
143
169
  type Section =
144
170
  | { readonly text: string }
145
171
  | { readonly heading: string }
@@ -156,6 +182,29 @@ type EmailTemplateData = {
156
182
  readonly body?: string;
157
183
  };
158
184
 
185
+ function templateContent(variables: Readonly<Record<string, unknown>>): {
186
+ readonly header: string | undefined;
187
+ readonly sections: readonly Section[] | undefined;
188
+ readonly footer: string | undefined;
189
+ } {
190
+ const data = variables as EmailTemplateData; // @cast-boundary render-helper
191
+ // Without structured fields, title + body become the header and a single text section.
192
+ return {
193
+ header: data.header ?? data.title,
194
+ sections: data.sections ?? (data.body ? [{ text: data.body }] : undefined),
195
+ footer: data.footer,
196
+ };
197
+ }
198
+
199
+ // Markdown stays as written: its source is already readable plain text.
200
+ function sectionText(section: Section): string {
201
+ if ("text" in section) return section.text;
202
+ if ("heading" in section) return section.heading;
203
+ if ("markdown" in section) return section.markdown;
204
+ if ("button" in section) return `${section.button.label}: ${section.button.url}`;
205
+ return "";
206
+ }
207
+
159
208
  function renderSection(section: Section, primaryColor: string): string {
160
209
  if ("text" in section) {
161
210
  return `<p style="margin:0 0 16px;color:#333;font-size:14px;line-height:1.5">${escapeHtml(section.text)}</p>`;
@@ -183,11 +232,7 @@ export function createSimpleRenderer(branding?: MailBranding): NotificationRende
183
232
  name: "simple",
184
233
 
185
234
  async render(input) {
186
- const data = input.variables as EmailTemplateData; // @cast-boundary render-helper
187
-
188
- // Fallback: if no structured fields, use title + body as header + single text section
189
- const header = data.header ?? data.title;
190
- const sections = data.sections ?? (data.body ? [{ text: data.body }] : undefined);
235
+ const { header, sections, footer } = templateContent(input.variables);
191
236
 
192
237
  const parts: string[] = [];
193
238
  parts.push('<!DOCTYPE html><html><body style="margin:0;padding:0;font-family:sans-serif">');
@@ -206,9 +251,9 @@ export function createSimpleRenderer(branding?: MailBranding): NotificationRende
206
251
  }
207
252
  }
208
253
 
209
- if (data.footer) {
254
+ if (footer) {
210
255
  parts.push(
211
- `<p style="margin:24px 0 0;color:#999;font-size:12px;border-top:1px solid #eee;padding-top:16px">${escapeHtml(data.footer)}</p>`,
256
+ `<p style="margin:24px 0 0;color:#999;font-size:12px;border-top:1px solid #eee;padding-top:16px">${escapeHtml(footer)}</p>`,
212
257
  );
213
258
  }
214
259
 
@@ -216,6 +261,14 @@ export function createSimpleRenderer(branding?: MailBranding): NotificationRende
216
261
  parts.push("</div></body></html>");
217
262
  return parts.join("");
218
263
  },
264
+
265
+ async renderText(input) {
266
+ const { header, sections, footer } = templateContent(input.variables);
267
+ const brandingFooter = brandingFooterLines(branding, input.locale).join("\n");
268
+ return [header, ...(sections ?? []).map(sectionText), footer, brandingFooter]
269
+ .filter((block): block is string => block !== undefined && block !== "")
270
+ .join("\n\n");
271
+ },
219
272
  };
220
273
  }
221
274
 
@@ -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 {
@@ -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";
@@ -15,7 +15,7 @@ import type { SessionUser, TenantId } from "@cosmicdrift/kumiko-framework/engine
15
15
  import { buildSessionRoles, SYSTEM_TENANT_ID } from "@cosmicdrift/kumiko-framework/engine";
16
16
  import { append } from "@cosmicdrift/kumiko-framework/event-store";
17
17
  import { generateId, parseRoles } from "@cosmicdrift/kumiko-framework/utils";
18
- import { Temporal } from "temporal-polyfill";
18
+ import { Temporal } from "@cosmicdrift/kumiko-types/temporal";
19
19
  import { encryptForDirectWrite } from "../shared/index.js";
20
20
  import { tenantMembershipsTable } from "../tenant/index.js";
21
21
  import { isPrincipalBlocked, type UserStatus, userTable } from "../user/index.js";
@@ -1,21 +1,14 @@
1
1
  /// <reference types="temporal-polyfill/global" preserve="true" />
2
2
  /** Shared grace-period check for user-data-rights + tenant-lifecycle cancel flows. */
3
3
 
4
- import { Temporal as TemporalPolyfill } from "temporal-polyfill";
4
+ import { Temporal } from "@cosmicdrift/kumiko-types/temporal";
5
5
 
6
6
  /**
7
7
  * True when `gracePeriodEnd` is still in the future.
8
- * Uses the polyfill Instant API so callers don't depend on `globalThis.Temporal`.
8
+ * Uses the module's Temporal so callers don't depend on `globalThis.Temporal`.
9
9
  */
10
10
  export function isWithinGracePeriod(gracePeriodEnd: Temporal.Instant | null): boolean {
11
- // @cast-boundary temporal-polyfill-vs-ambient: same TC39 Temporal.Instant
12
- // at runtime — row types resolve against ambient Temporal; polyfill Instant
13
- // is a separate nominal type across the two .d.ts sources.
14
11
  return (
15
- gracePeriodEnd != null &&
16
- TemporalPolyfill.Instant.compare(
17
- gracePeriodEnd as unknown as InstanceType<typeof TemporalPolyfill.Instant>,
18
- TemporalPolyfill.Now.instant(),
19
- ) > 0
12
+ gracePeriodEnd != null && Temporal.Instant.compare(gracePeriodEnd, Temporal.Now.instant()) > 0
20
13
  );
21
14
  }
@@ -31,7 +31,7 @@
31
31
  // (single-use-token-store) instead.
32
32
 
33
33
  import { createFallbackLogger } from "@cosmicdrift/kumiko-framework/logging";
34
- import type { Temporal } from "temporal-polyfill";
34
+ import type { Temporal } from "@cosmicdrift/kumiko-types/temporal";
35
35
  import { peekTokenSubject, signToken, verifyToken } from "./signed-token.js";
36
36
 
37
37
  export type RowBoundGrantResult =
@@ -13,7 +13,7 @@
13
13
  // signal through a short-circuit.
14
14
 
15
15
  import { createHmac, timingSafeEqual } from "node:crypto";
16
- import { Temporal } from "temporal-polyfill";
16
+ import { Temporal } from "@cosmicdrift/kumiko-types/temporal";
17
17
 
18
18
  export type VerifyResult =
19
19
  | { readonly ok: true; readonly userId: string; readonly expiresAtMs: number }
@@ -23,11 +23,11 @@ import {
23
23
  SubscriptionStatuses,
24
24
  } from "@cosmicdrift/kumiko-bundled-features/billing-foundation";
25
25
  import { createFallbackLogger } from "@cosmicdrift/kumiko-framework/logging";
26
+ import { Temporal } from "@cosmicdrift/kumiko-types/temporal";
26
27
  import type {
27
28
  Payment as MolliePayment,
28
29
  Subscription as MollieSubscription,
29
30
  } from "@mollie/api-client";
30
- import { Temporal } from "temporal-polyfill";
31
31
  import { MOLLIE_PROVIDER_NAME } from "./constants.js";
32
32
  import type { MolliePriceConfig } from "./plugin-methods.js";
33
33
 
@@ -47,8 +47,8 @@ import {
47
47
  import { ExtraRouteRejection } from "@cosmicdrift/kumiko-framework/api";
48
48
  import { createFallbackLogger } from "@cosmicdrift/kumiko-framework/logging";
49
49
  import type { SecretsContext } from "@cosmicdrift/kumiko-framework/secrets";
50
+ import { Temporal } from "@cosmicdrift/kumiko-types/temporal";
50
51
  import Stripe from "stripe";
51
- import { Temporal } from "temporal-polyfill";
52
52
  import { STRIPE_PROVIDER_NAME, StripeEventTypes } from "./constants.js";
53
53
  import type { StripeWebhookRuntime } from "./runtime.js";
54
54
 
@@ -6,7 +6,7 @@ import { selectMany } from "@cosmicdrift/kumiko-framework/bun-db";
6
6
  import type { DbConnection } from "@cosmicdrift/kumiko-framework/db";
7
7
  import type { SessionUser, TenantId } from "@cosmicdrift/kumiko-framework/engine";
8
8
  import { InternalError } from "@cosmicdrift/kumiko-framework/errors";
9
- import type { Temporal } from "temporal-polyfill";
9
+ import type { Temporal } from "@cosmicdrift/kumiko-types/temporal";
10
10
  import type { ContentFormat, TemplateKind } from "./constants.js";
11
11
  import { FALLBACK_LOCALE, SYSTEM_TENANT_ID } from "./constants.js";
12
12
  import { type TemplateResourceRow, templateResourcesTable } from "./table.js";
@@ -4,7 +4,7 @@ import {
4
4
  crossTenantOverrideDenied,
5
5
  defineQueryHandler,
6
6
  } from "@cosmicdrift/kumiko-framework/engine";
7
- import type { Temporal } from "temporal-polyfill";
7
+ import type { Temporal } from "@cosmicdrift/kumiko-types/temporal";
8
8
  import * as z from "zod";
9
9
  import { TEXT_BLOCK_KIND } from "../constants.js";
10
10
  import { type TemplateResourceRow, templateResourcesTable } from "../table.js";
@@ -7,7 +7,7 @@ import type {
7
7
  TenantId,
8
8
  } from "@cosmicdrift/kumiko-framework/engine";
9
9
  import { SYSTEM_TENANT_ID } from "@cosmicdrift/kumiko-framework/engine";
10
- import type { Temporal } from "temporal-polyfill";
10
+ import type { Temporal } from "@cosmicdrift/kumiko-types/temporal";
11
11
  import { templateResourceEntity, templateResourcesTable } from "../table.js";
12
12
  import { userContentEntriesTable, userContentEntryEntity } from "../user-content-table.js";
13
13
 
@@ -5,7 +5,7 @@ import {
5
5
  createSelectField,
6
6
  createTextField,
7
7
  } from "@cosmicdrift/kumiko-framework/engine";
8
- import type { Temporal } from "temporal-polyfill";
8
+ import type { Temporal } from "@cosmicdrift/kumiko-types/temporal";
9
9
  import {
10
10
  CONTENT_FORMATS,
11
11
  TEMPLATE_KINDS,
@@ -6,7 +6,7 @@ import {
6
6
  } from "@cosmicdrift/kumiko-framework/engine";
7
7
  import { InternalError } from "@cosmicdrift/kumiko-framework/errors";
8
8
  import { parseRoles } from "@cosmicdrift/kumiko-framework/utils";
9
- import { Temporal } from "temporal-polyfill";
9
+ import { Temporal } from "@cosmicdrift/kumiko-types/temporal";
10
10
  import * as z from "zod";
11
11
  import { userSessionTable } from "../../sessions/index.js";
12
12
  import { decryptStoredPii, mapWithConcurrency } from "../../shared/index.js";
@@ -7,7 +7,7 @@ import {
7
7
  import { createSystemUser, type TenantId } from "@cosmicdrift/kumiko-framework/engine";
8
8
  import { ConflictError, writeFailure } from "@cosmicdrift/kumiko-framework/errors";
9
9
  import { parseRoles } from "@cosmicdrift/kumiko-framework/utils";
10
- import { Temporal } from "temporal-polyfill";
10
+ import { Temporal } from "@cosmicdrift/kumiko-types/temporal";
11
11
  import { TenantErrors } from "./constants.js";
12
12
  import { findForbiddenMembershipRole, reservedMembershipRoleError } from "./membership-roles.js";
13
13
  import { tenantMembershipEntity, tenantMembershipsTable } from "./membership-table.js";
@@ -12,7 +12,7 @@
12
12
  // put in a URL, logged, or emailed. The run's own photos may show plates —
13
13
  // the grant must stay exactly where the anonymous flow minted it.
14
14
 
15
- import type { Temporal } from "temporal-polyfill";
15
+ import type { Temporal } from "@cosmicdrift/kumiko-types/temporal";
16
16
  import { signRowBoundGrant } from "../shared/index.js";
17
17
 
18
18
  const HANDOVER_PURPOSE_PREFIX = "tenant-handover";
@@ -1,7 +1,7 @@
1
1
  import { selectMany } from "@cosmicdrift/kumiko-framework/bun-db";
2
2
  import type { DbConnection } from "@cosmicdrift/kumiko-framework/db";
3
3
  import type { TenantId } from "@cosmicdrift/kumiko-framework/engine";
4
- import type { Temporal } from "temporal-polyfill";
4
+ import type { Temporal } from "@cosmicdrift/kumiko-types/temporal";
5
5
  import { exportJobsTable } from "../../schema/export-job.js";
6
6
 
7
7
  export type ExportJobCleanupCandidate = {
@@ -1,22 +1,16 @@
1
1
  import { selectMany } from "@cosmicdrift/kumiko-framework/bun-db";
2
2
  import type { DbConnection } from "@cosmicdrift/kumiko-framework/db";
3
- // Value-only import, aliased to avoid shadowing the ambient global
4
- // `Temporal` TYPE that gracePeriodEndCutoff's parameter type resolves
5
- // against (same #1438 dual-package-hazard pattern as event-store.ts).
6
- import { Temporal as TemporalPolyfill } from "temporal-polyfill";
3
+ import { Temporal } from "@cosmicdrift/kumiko-types/temporal";
7
4
  import { userTable } from "../../../user/index.js";
8
5
 
9
6
  export async function selectUsersDueForForgetCleanup(
10
7
  db: DbConnection,
11
8
  status: string,
12
- gracePeriodEndCutoff: InstanceType<typeof TemporalPolyfill.Instant> | string,
9
+ gracePeriodEndCutoff: Temporal.Instant | string,
13
10
  ): Promise<readonly { id: string }[]> {
14
- // @cast-boundary temporal-polyfill-vs-ambient: same TC39 Temporal.Instant
15
- // at runtime, distinct nominal types across the two .d.ts sources (see
16
- // event-store.ts). Only ever consumed via the `lte` comparison below.
17
11
  const cutoff =
18
12
  typeof gracePeriodEndCutoff === "string"
19
- ? TemporalPolyfill.Instant.from(gracePeriodEndCutoff)
13
+ ? Temporal.Instant.from(gracePeriodEndCutoff)
20
14
  : gracePeriodEndCutoff;
21
15
  return selectMany<{ id: string }>(db, userTable, {
22
16
  status,
@@ -7,7 +7,7 @@
7
7
  // The purpose string and the anchoring are unchanged from the hand-rolled
8
8
  // version this replaced, so tokens stay byte-compatible.
9
9
 
10
- import type { Temporal } from "temporal-polyfill";
10
+ import type { Temporal } from "@cosmicdrift/kumiko-types/temporal";
11
11
  import {
12
12
  type RowBoundGrantResult,
13
13
  redeemRowBoundGrant,
@@ -5,7 +5,7 @@
5
5
 
6
6
  import { mailT, registerMailTranslations } from "@cosmicdrift/kumiko-framework/i18n";
7
7
  import { escapeHtml, escapeHtmlAttr } from "@cosmicdrift/kumiko-headless";
8
- import { Temporal } from "temporal-polyfill";
8
+ import { Temporal } from "@cosmicdrift/kumiko-types/temporal";
9
9
 
10
10
  export type GdprMailLocale = string;
11
11
 
@@ -44,6 +44,7 @@ registerMailTranslations("en", GDPR_MAIL_EN);
44
44
  export type RenderedEmail = {
45
45
  readonly subject: string;
46
46
  readonly html: string;
47
+ readonly text: string;
47
48
  };
48
49
 
49
50
  export type RenderExportReadyEmailArgs = {
@@ -89,7 +90,13 @@ export function renderExportReadyEmail(args: RenderExportReadyEmailArgs): Render
89
90
  <p style="margin: 0 0 24px;">${renderButton({ url: args.downloadUrl, label: t(locale, "gdpr.mail.exportReady.button") })}</p>
90
91
  <p style="margin: 0 0 8px; font-size: 13px; color: #555;">${escapeHtml(t(locale, "gdpr.mail.exportReady.expiry", { when: formatTimestamp(args.expiresAt) }))}</p>
91
92
  ${renderFallbackUrl({ url: args.downloadUrl, label: t(locale, "gdpr.mail.fallbackUrl") })}`;
92
- 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.exportReady.intro", { app }),
96
+ `${t(locale, "gdpr.mail.exportReady.button")}: ${args.downloadUrl}`,
97
+ t(locale, "gdpr.mail.exportReady.expiry", { when: formatTimestamp(args.expiresAt) }),
98
+ ]);
99
+ return { subject, html: renderShell({ title: subject, bodyHtml: wrapCell(body), locale }), text };
93
100
  }
94
101
 
95
102
  export function renderExportFailedEmail(args: RenderExportFailedEmailArgs): RenderedEmail {
@@ -99,7 +106,11 @@ export function renderExportFailedEmail(args: RenderExportFailedEmailArgs): Rend
99
106
  const body = `
100
107
  <p style="margin: 0 0 16px; font-size: 16px;">${escapeHtml(t(locale, "gdpr.mail.greeting"))}</p>
101
108
  <p style="margin: 0; font-size: 14px; line-height: 1.5;">${escapeHtml(t(locale, "gdpr.mail.exportFailed.intro", { app }))}</p>`;
102
- return { subject, html: renderShell({ title: subject, bodyHtml: wrapCell(body), locale }) };
109
+ const text = plainTextBody([
110
+ t(locale, "gdpr.mail.greeting"),
111
+ t(locale, "gdpr.mail.exportFailed.intro", { app }),
112
+ ]);
113
+ return { subject, html: renderShell({ title: subject, bodyHtml: wrapCell(body), locale }), text };
103
114
  }
104
115
 
105
116
  export function renderDeletionRequestedEmail(
@@ -113,7 +124,12 @@ export function renderDeletionRequestedEmail(
113
124
  <p style="margin: 0 0 16px; font-size: 16px;">${escapeHtml(t(locale, "gdpr.mail.greeting"))}</p>
114
125
  <p style="margin: 0 0 16px; font-size: 14px; line-height: 1.5;">${escapeHtml(t(locale, "gdpr.mail.deletionRequested.intro", { app, when }))}</p>
115
126
  <p style="margin: 0; font-size: 13px; color: #555;">${escapeHtml(t(locale, "gdpr.mail.deletionRequested.cancel"))}</p>`;
116
- return { subject, html: renderShell({ title: subject, bodyHtml: wrapCell(body), locale }) };
127
+ const text = plainTextBody([
128
+ t(locale, "gdpr.mail.greeting"),
129
+ t(locale, "gdpr.mail.deletionRequested.intro", { app, when }),
130
+ t(locale, "gdpr.mail.deletionRequested.cancel"),
131
+ ]);
132
+ return { subject, html: renderShell({ title: subject, bodyHtml: wrapCell(body), locale }), text };
117
133
  }
118
134
 
119
135
  export function renderDeletionExecutedEmail(args: RenderDeletionExecutedEmailArgs): RenderedEmail {
@@ -124,7 +140,15 @@ export function renderDeletionExecutedEmail(args: RenderDeletionExecutedEmailArg
124
140
  const body = `
125
141
  <p style="margin: 0 0 16px; font-size: 16px;">${escapeHtml(t(locale, "gdpr.mail.greeting"))}</p>
126
142
  <p style="margin: 0; font-size: 14px; line-height: 1.5;">${escapeHtml(t(locale, "gdpr.mail.deletionExecuted.intro", { app, when }))}</p>`;
127
- return { subject, html: renderShell({ title: subject, bodyHtml: wrapCell(body), locale }) };
143
+ const text = plainTextBody([
144
+ t(locale, "gdpr.mail.greeting"),
145
+ t(locale, "gdpr.mail.deletionExecuted.intro", { app, when }),
146
+ ]);
147
+ return { subject, html: renderShell({ title: subject, bodyHtml: wrapCell(body), locale }), text };
148
+ }
149
+
150
+ function plainTextBody(paragraphs: readonly string[]): string {
151
+ return paragraphs.join("\n\n");
128
152
  }
129
153
 
130
154
  function wrapCell(bodyHtml: string): string {
@@ -1,6 +1,6 @@
1
1
  import { selectMany, type WhereObject } 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
 
@@ -1,7 +1,7 @@
1
1
  import { selectMany, type WhereObject } 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
 
7
7
  // DSGVO Art. 15 Selbstauskunft — User reads HIS OWN audit-log.
@@ -57,13 +57,13 @@ export function makeDefaultExportReadyEmail(
57
57
  ): SendExportReadyEmailFn {
58
58
  return async (args) => {
59
59
  const transport = await resolveTransport(args.tenantId);
60
- const { subject, html } = renderExportReadyEmail({
60
+ const { subject, html, text } = renderExportReadyEmail({
61
61
  downloadUrl: args.downloadUrl,
62
62
  expiresAt: args.expiresAt,
63
63
  locale: localeFor(args.userLocale, defaults),
64
64
  appName: defaults.appName,
65
65
  });
66
- await transport.send({ to: args.userEmail, subject, html });
66
+ await transport.send({ to: args.userEmail, subject, html, text });
67
67
  };
68
68
  }
69
69
 
@@ -73,11 +73,11 @@ export function makeDefaultExportFailedEmail(
73
73
  ): SendExportFailedEmailFn {
74
74
  return async (args) => {
75
75
  const transport = await resolveTransport(args.tenantId);
76
- const { subject, html } = renderExportFailedEmail({
76
+ const { subject, html, text } = renderExportFailedEmail({
77
77
  locale: localeFor(args.userLocale, defaults),
78
78
  appName: defaults.appName,
79
79
  });
80
- await transport.send({ to: args.userEmail, subject, html });
80
+ await transport.send({ to: args.userEmail, subject, html, text });
81
81
  };
82
82
  }
83
83
 
@@ -87,12 +87,12 @@ export function makeDefaultDeletionRequestedEmail(
87
87
  ): SendDeletionRequestedEmailFn {
88
88
  return async (args) => {
89
89
  const transport = await resolveTransport(args.tenantId);
90
- const { subject, html } = renderDeletionRequestedEmail({
90
+ const { subject, html, text } = renderDeletionRequestedEmail({
91
91
  gracePeriodEnd: args.gracePeriodEnd,
92
92
  locale: localeFor(args.userLocale, defaults),
93
93
  appName: defaults.appName,
94
94
  });
95
- await transport.send({ to: args.userEmail, subject, html });
95
+ await transport.send({ to: args.userEmail, subject, html, text });
96
96
  };
97
97
  }
98
98
 
@@ -110,11 +110,11 @@ export function makeDefaultDeletionExecutedEmail(
110
110
  return;
111
111
  }
112
112
  const transport = await resolveTransport(tenantId);
113
- const { subject, html } = renderDeletionExecutedEmail({
113
+ const { subject, html, text } = renderDeletionExecutedEmail({
114
114
  executedAt: args.executedAt,
115
115
  locale: localeFor(args.userLocale, defaults),
116
116
  appName: defaults.appName,
117
117
  });
118
- await transport.send({ to: args.userEmail, subject, html });
118
+ await transport.send({ to: args.userEmail, subject, html, text });
119
119
  };
120
120
  }
@@ -13,7 +13,7 @@ import {
13
13
  type WriteFailure,
14
14
  writeFailure,
15
15
  } from "@cosmicdrift/kumiko-framework/errors";
16
- import { Temporal } from "temporal-polyfill";
16
+ import { Temporal } from "@cosmicdrift/kumiko-types/temporal";
17
17
  import { AuthErrors, AuthHandlers } from "../../auth-email-password/index.js";
18
18
  import { TenantHandlers, tenantTable } from "../../tenant/index.js";
19
19
  import { DEFAULT_OWN_TENANT_INVITE_ROLE, WAITLIST_STATUS, WaitlistErrors } from "../constants.js";
@@ -6,7 +6,7 @@ import {
6
6
  type WriteResult,
7
7
  } from "@cosmicdrift/kumiko-framework/engine";
8
8
  import { InternalError } from "@cosmicdrift/kumiko-framework/errors";
9
- import { Temporal } from "temporal-polyfill";
9
+ import { Temporal } from "@cosmicdrift/kumiko-types/temporal";
10
10
  import { WAITLIST_NOTIFICATION_TYPES, WAITLIST_STATUS } from "../constants.js";
11
11
  import { normalizeEmail, platformActor, waitlistDb, waitlistExecutor } from "../lib.js";
12
12
  import {
@@ -15,6 +15,7 @@
15
15
  "@cosmicdrift/kumiko-framework": "workspace:*",
16
16
  "@cosmicdrift/kumiko-headless": "workspace:*",
17
17
  "@cosmicdrift/kumiko-renderer-web": "workspace:*",
18
+ "@cosmicdrift/kumiko-types": "workspace:*",
18
19
  "react": "^19.2.6",
19
20
  "react-dom": "^19.2.6",
20
21
  "@cosmicdrift/kumiko-locale-de": "workspace:*"
@@ -10,7 +10,7 @@
10
10
 
11
11
  import type { SeedFn } from "@cosmicdrift/kumiko-dev-server";
12
12
  import { TestUsers } from "@cosmicdrift/kumiko-framework/stack";
13
- import { Temporal } from "temporal-polyfill";
13
+ import { Temporal } from "@cosmicdrift/kumiko-types/temporal";
14
14
  import { countAssetsForTenant, countTicketsForTenant } from "../db/queries/seed-counts";
15
15
  import { ASSET_STATUSES } from "../features/assets/schema";
16
16
  import { TICKET_SEVERITIES, TICKET_STATUSES } from "../features/helpdesk/schema";
@@ -22,6 +22,7 @@
22
22
  "@cosmicdrift/kumiko-headless": "workspace:*",
23
23
  "@cosmicdrift/kumiko-renderer-web": "workspace:*",
24
24
  "@cosmicdrift/kumiko-server-runtime": "workspace:*",
25
+ "@cosmicdrift/kumiko-types": "workspace:*",
25
26
  "react": "^19.2.6",
26
27
  "react-dom": "^19.2.6",
27
28
  "zod": "^4.4.3",
@@ -34,7 +34,7 @@ import {
34
34
  } from "@cosmicdrift/kumiko-framework/engine";
35
35
  import { TestUsers, unsafeCreateEntityTable } from "@cosmicdrift/kumiko-framework/stack";
36
36
  import { generateId } from "@cosmicdrift/kumiko-framework/utils";
37
- import { Temporal } from "temporal-polyfill";
37
+ import { Temporal } from "@cosmicdrift/kumiko-types/temporal";
38
38
  import { ADMIN_EMAIL, BETA_TENANT_ID, DEMO_NOTE_ID, DEV_TENANT_ID } from "./auth-constants";
39
39
  import { noteEntity } from "./notes-feature";
40
40