@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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cosmicdrift/kumiko-samples",
3
- "version": "0.349.0",
3
+ "version": "0.351.0",
4
4
  "description": "Source trees of the Kumiko sample recipes, sample apps and bundled features in repo layout, for tooling such as few-shot corpus builds.",
5
5
  "license": "BUSL-1.1",
6
6
  "author": "Marc Frost <marc@cosmicdriftgamestudio.com>",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cosmicdrift/kumiko-bundled-features",
3
- "version": "0.349.0",
3
+ "version": "0.351.0",
4
4
  "description": "Built-in features — tenant, user, auth, delivery. The stuff you'd rewrite anyway, already typed.",
5
5
  "license": "BUSL-1.1",
6
6
  "author": "Marc Frost <marc@cosmicdriftgamestudio.com>",
@@ -17,7 +17,7 @@
17
17
  import { selectMany, type WhereObject } from "@cosmicdrift/kumiko-framework/bun-db";
18
18
  import { access, defineQueryHandler } from "@cosmicdrift/kumiko-framework/engine";
19
19
  import { eventsTable } from "@cosmicdrift/kumiko-framework/event-store";
20
- import { Temporal } from "temporal-polyfill";
20
+ import { Temporal } from "@cosmicdrift/kumiko-types/temporal";
21
21
  import * as z from "zod";
22
22
  import { AUDIT_SCOPE_VALUES } from "../constants.js";
23
23
  import { resolveAuditScopeFilter } from "./resolve-audit-tenant.js";
@@ -9,7 +9,7 @@
9
9
  // (localeDe() / localeEs() call registerMailTranslations).
10
10
 
11
11
  import { mailT, registerMailTranslations } from "@cosmicdrift/kumiko-framework/i18n";
12
- import { Temporal } from "temporal-polyfill";
12
+ import { Temporal } from "@cosmicdrift/kumiko-types/temporal";
13
13
 
14
14
  export type AuthMailLocale = string;
15
15
 
@@ -31,7 +31,7 @@ import {
31
31
  type TenantId,
32
32
  } from "@cosmicdrift/kumiko-framework/engine";
33
33
  import { InternalError, writeFailure } from "@cosmicdrift/kumiko-framework/errors";
34
- import type { Temporal } from "temporal-polyfill";
34
+ import type { Temporal } from "@cosmicdrift/kumiko-types/temporal";
35
35
  import * as z from "zod";
36
36
  import { decryptStoredPii, sessionLocaleField, sessionTimezoneField } from "../../shared/index.js";
37
37
  // kumiko-lint-ignore cross-feature-import invite-flow
@@ -23,7 +23,7 @@ import {
23
23
  type TenantId,
24
24
  } from "@cosmicdrift/kumiko-framework/engine";
25
25
  import { InternalError, writeFailure } from "@cosmicdrift/kumiko-framework/errors";
26
- import type { Temporal } from "temporal-polyfill";
26
+ import type { Temporal } from "@cosmicdrift/kumiko-types/temporal";
27
27
  import * as z from "zod";
28
28
  import { decryptStoredPii } from "../../shared/index.js";
29
29
  // kumiko-lint-ignore cross-feature-import invite-flow lebt in auth-email-password (Magic-Link), DB-row-owner ist tenant-feature
@@ -32,7 +32,7 @@ import {
32
32
  type WriteFailure,
33
33
  writeFailure,
34
34
  } from "@cosmicdrift/kumiko-framework/errors";
35
- import { Temporal } from "temporal-polyfill";
35
+ import { Temporal } from "@cosmicdrift/kumiko-types/temporal";
36
36
  import * as z from "zod";
37
37
  // kumiko-lint-ignore cross-feature-import invite-flow lebt in auth-email-password (Magic-Link-Pattern), DB-row-owner ist tenant-feature
38
38
  import {
@@ -24,7 +24,7 @@
24
24
  import { generateToken } from "@cosmicdrift/kumiko-framework/api";
25
25
  import { defineWriteHandler } from "@cosmicdrift/kumiko-framework/engine";
26
26
  import { InternalError, writeFailure } from "@cosmicdrift/kumiko-framework/errors";
27
- import { Temporal } from "temporal-polyfill";
27
+ import { Temporal } from "@cosmicdrift/kumiko-types/temporal";
28
28
  import * as z from "zod";
29
29
  import { findSignupHandoverProvider, type SignupHandoverBinding } from "../../shared/index.js";
30
30
  import { AUTH_SIGNUP_DEFAULT_TTL_MINUTES } from "../constants.js";
@@ -15,7 +15,7 @@
15
15
  import { createSystemUser, defineWriteHandler } from "@cosmicdrift/kumiko-framework/engine";
16
16
  import { UnprocessableError, writeFailure } from "@cosmicdrift/kumiko-framework/errors";
17
17
  import { isValidIanaTimeZone } from "@cosmicdrift/kumiko-framework/time";
18
- import type { Temporal } from "temporal-polyfill";
18
+ import type { Temporal } from "@cosmicdrift/kumiko-types/temporal";
19
19
  import * as z from "zod";
20
20
  import { UserQueries } from "../../user/index.js";
21
21
  import { type AuthUserRow, parseAuthUserRow } from "../auth-user-row.js";
@@ -3,7 +3,7 @@
3
3
  // the shared HMAC logic lives in one place. verification-token.ts mirrors
4
4
  // this pattern with purpose="verify".
5
5
 
6
- import type { Temporal } from "temporal-polyfill";
6
+ import type { Temporal } from "@cosmicdrift/kumiko-types/temporal";
7
7
  import { signToken, TokenPurpose, verifyToken } from "./signed-token.js";
8
8
 
9
9
  export type VerifyResult =
@@ -1,7 +1,7 @@
1
1
  // Thin wrapper around signed-token.ts pinning the purpose to "unlock".
2
2
  // Mirrors reset-token.ts / verification-token.ts.
3
3
 
4
- import type { Temporal } from "temporal-polyfill";
4
+ import type { Temporal } from "@cosmicdrift/kumiko-types/temporal";
5
5
  import { signToken, TokenPurpose, verifyToken } from "./signed-token.js";
6
6
 
7
7
  export type VerifyResult =
@@ -2,7 +2,7 @@
2
2
  // Mirrors reset-token.ts so callers can import a flow-specific helper
3
3
  // without knowing the underlying HMAC scheme.
4
4
 
5
- import type { Temporal } from "temporal-polyfill";
5
+ import type { Temporal } from "@cosmicdrift/kumiko-types/temporal";
6
6
  import { signToken, TokenPurpose, verifyToken } from "./signed-token.js";
7
7
 
8
8
  export type VerifyResult =
@@ -7,7 +7,7 @@ import {
7
7
  } from "@cosmicdrift/kumiko-framework/engine";
8
8
  import { InternalError, 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 * as z from "zod";
12
12
  import { burnToken, sessionLocaleField, sessionTimezoneField } from "../../shared/index.js";
13
13
  import { USER_STATUS, UserQueries } from "../../user/index.js";
@@ -1,7 +1,7 @@
1
1
  import { createEventStoreExecutor } from "@cosmicdrift/kumiko-framework/db";
2
2
  import { defineWriteHandler } from "@cosmicdrift/kumiko-framework/engine";
3
3
  import { InternalError, writeFailure } from "@cosmicdrift/kumiko-framework/errors";
4
- import { Temporal } from "temporal-polyfill";
4
+ import { Temporal } from "@cosmicdrift/kumiko-types/temporal";
5
5
  import * as z from "zod";
6
6
  import { burnToken } from "../../shared/index.js";
7
7
  import { base32Decode } from "../base32.js";
@@ -1,5 +1,5 @@
1
1
  import { createHmac, timingSafeEqual } from "node:crypto";
2
- import { Temporal } from "temporal-polyfill";
2
+ import { Temporal } from "@cosmicdrift/kumiko-types/temporal";
3
3
 
4
4
  function hmacSign(input: string, secret: string): string {
5
5
  return createHmac("sha256", secret).update(input).digest("base64url");
@@ -1,5 +1,5 @@
1
1
  import type { TenantId } from "@cosmicdrift/kumiko-framework/engine";
2
- import { Temporal } from "temporal-polyfill";
2
+ import { Temporal } from "@cosmicdrift/kumiko-types/temporal";
3
3
  import { createHmacTokenCodec, type HmacTokenVerifyResult } from "./hmac-token-codec.js";
4
4
 
5
5
  export type MfaChallengePayload = {
@@ -1,5 +1,5 @@
1
1
  import type { TenantId } from "@cosmicdrift/kumiko-framework/engine";
2
- import { Temporal } from "temporal-polyfill";
2
+ import { Temporal } from "@cosmicdrift/kumiko-types/temporal";
3
3
  import { createHmacTokenCodec, type HmacTokenVerifyResult } from "./hmac-token-codec.js";
4
4
 
5
5
  export type MfaPreauthSetupPayload = {
@@ -10,7 +10,7 @@
10
10
  // signed-token.ts (userId-only payload) because setup needs to carry
11
11
  // generated secret material, not just re-derive it from a lookup.
12
12
  import { createHmac, timingSafeEqual } from "node:crypto";
13
- import { Temporal } from "temporal-polyfill";
13
+ import { Temporal } from "@cosmicdrift/kumiko-types/temporal";
14
14
 
15
15
  export type MfaSetupPayload = {
16
16
  readonly userId: string;
@@ -1,12 +1,6 @@
1
1
  /// <reference types="temporal-polyfill/global" preserve="true" />
2
2
  // @runtime client
3
- // temporal-polyfill is a plain npm dependency (not a framework runtime
4
- // module), so importing it here doesn't break browser bundles. Aliased —
5
- // same reason as event-store.ts's own import: the un-aliased name would
6
- // shadow the ambient global `Temporal` TYPE every `Temporal.Instant`
7
- // annotation in this file (and its callers, e.g. StoredEvent.createdAt)
8
- // resolves against, see #1438.
9
- import { Temporal as TemporalPolyfill } from "temporal-polyfill";
3
+ import { Temporal } from "@cosmicdrift/kumiko-types/temporal";
10
4
 
11
5
  // Feature name
12
6
  export const BILLING_FOUNDATION_FEATURE = "billing-foundation" as const;
@@ -185,7 +179,7 @@ export function isTerminalSubscriptionStatus(status: string): boolean {
185
179
  // after roughly 23 hours; without a matching staleness cutoff on our side, a
186
180
  // stale incomplete row would block that tenant from ever starting a new
187
181
  // checkout, since the projection has no expiry-triggered event of its own.
188
- export const STALE_INCOMPLETE_AFTER = TemporalPolyfill.Duration.from({ hours: 24 });
182
+ export const STALE_INCOMPLETE_AFTER = Temporal.Duration.from({ hours: 24 });
189
183
 
190
184
  /** Time-aware terminal check for the checkout gate: a canceled subscription
191
185
  * is terminal (see `isTerminalSubscriptionStatus`), and so is an
@@ -199,15 +193,7 @@ export function isSubscriptionBlockingCheckout(
199
193
  if (isTerminalSubscriptionStatus(sub.status)) return false;
200
194
  if (sub.status !== SubscriptionStatuses.incomplete) return true;
201
195
  const staleAt = sub.lastChangedAt.add(STALE_INCOMPLETE_AFTER);
202
- // @cast-boundary temporal-polyfill-vs-ambient: same TC39 Temporal.Instant
203
- // at runtime — row types resolve against ambient Temporal; polyfill Instant
204
- // is a separate nominal type across the two .d.ts sources.
205
- return (
206
- TemporalPolyfill.Instant.compare(
207
- now as unknown as InstanceType<typeof TemporalPolyfill.Instant>,
208
- staleAt as unknown as InstanceType<typeof TemporalPolyfill.Instant>,
209
- ) < 0
210
- );
196
+ return Temporal.Instant.compare(now, staleAt) < 0;
211
197
  }
212
198
 
213
199
  // A subscription in one of these statuses can be switched to a different
@@ -1,4 +1,4 @@
1
- import { Temporal } from "temporal-polyfill";
1
+ import { Temporal } from "@cosmicdrift/kumiko-types/temporal";
2
2
  import type { CheckoutConsentRecordedPayload } from "../events.js";
3
3
  import { CONSENT_TEXTS, type ConsentLocale, resolveConsentLocale } from "./consent-text.js";
4
4
 
@@ -1,4 +1,4 @@
1
- import { Temporal } from "temporal-polyfill";
1
+ import { Temporal } from "@cosmicdrift/kumiko-types/temporal";
2
2
  import type {
3
3
  ContractTerminationDeclarationType,
4
4
  ContractTerminationKind,
@@ -55,10 +55,7 @@ import {
55
55
  EXT_TENANT_DATA,
56
56
  type FeatureDefinition,
57
57
  } from "@cosmicdrift/kumiko-framework/engine";
58
- // Aliased — an un-aliased `Temporal` would shadow the ambient global
59
- // `Temporal` TYPE `ResolvedBillingFoundationOptions.now`'s return type
60
- // resolves against, see event-store.ts's own import comment (#1438).
61
- import { Temporal as TemporalPolyfill } from "temporal-polyfill";
58
+ import { Temporal } from "@cosmicdrift/kumiko-types/temporal";
62
59
  import {
63
60
  BILLING_FOUNDATION_FEATURE,
64
61
  SUBSCRIPTION_PROVIDER_EXTENSION,
@@ -131,11 +128,7 @@ export function createBillingFoundationFeature<TTier extends string = string>(
131
128
  // ever calls `Temporal.Now.instant` itself.
132
129
  const widened: ResolvedBillingFoundationOptions = {
133
130
  ...options,
134
- // @cast-boundary temporal-polyfill-vs-ambient: same TC39 Temporal.Instant
135
- // at runtime — `now` is typed against the ambient Temporal global;
136
- // TemporalPolyfill.Now.instant() returns the polyfill's own nominal
137
- // Instant type across the two .d.ts sources.
138
- now: options.now ?? (() => TemporalPolyfill.Now.instant() as unknown as Temporal.Instant),
131
+ now: options.now ?? (() => Temporal.Now.instant()),
139
132
  ...(options.baseUrl !== undefined && {
140
133
  baseUrl: options.baseUrl.endsWith("/") ? options.baseUrl.slice(0, -1) : options.baseUrl,
141
134
  }),
@@ -15,10 +15,7 @@
15
15
 
16
16
  import { createHash } from "node:crypto";
17
17
  import { SYSTEM_ROLE, type WriteHandlerDef } from "@cosmicdrift/kumiko-framework/engine";
18
- // Aliased — an un-aliased `Temporal` would shadow the ambient global
19
- // `Temporal` TYPE `SubscriptionView.currentPeriodEnd`/`.cancelAt` resolve
20
- // against, same reasoning as constants.ts's own import.
21
- import { Temporal as TemporalPolyfill } from "temporal-polyfill";
18
+ import { Temporal } from "@cosmicdrift/kumiko-types/temporal";
22
19
  import * as z from "zod";
23
20
  import { findProviderPlugin } from "../checkout-core.js";
24
21
  import { isTerminalSubscriptionStatus, SubscriptionEventTypes } from "../constants.js";
@@ -37,18 +34,13 @@ export type SyncSubscriptionResult =
37
34
  | { readonly synced: true }
38
35
  | { readonly synced: false; readonly reason: SyncSubscriptionSkipReason };
39
36
 
40
- // Parses both sides fresh via the polyfill before comparing — tolerant of a
37
+ // Parses both sides fresh via Temporal before comparing — tolerant of a
41
38
  // provider snapshot's ISO string using a different (but equivalent)
42
39
  // representation than `Temporal.Instant#toString()`'s canonical form
43
40
  // (offset spelling, sub-second precision, ...), unlike a plain `===` on the
44
41
  // raw strings.
45
42
  function isoInstantsEqual(a: string, b: string): boolean {
46
- return (
47
- TemporalPolyfill.Instant.compare(
48
- TemporalPolyfill.Instant.from(a),
49
- TemporalPolyfill.Instant.from(b),
50
- ) === 0
51
- );
43
+ return Temporal.Instant.compare(Temporal.Instant.from(a), Temporal.Instant.from(b)) === 0;
52
44
  }
53
45
 
54
46
  // Deterministic per (snapshot-content, current stream head) — not per-call.
@@ -8,7 +8,7 @@ import {
8
8
  type WriteResult,
9
9
  } from "@cosmicdrift/kumiko-framework/engine";
10
10
  import { eventsTable } from "@cosmicdrift/kumiko-framework/event-store";
11
- import { Temporal } from "temporal-polyfill";
11
+ import { Temporal } from "@cosmicdrift/kumiko-types/temporal";
12
12
  import * as z from "zod";
13
13
  import { capCounterAggregateId, rollingCapAggregateId } from "./aggregate-id.js";
14
14
  import { CAP_COUNTER_ROLLING_AGGREGATE_TYPE, ROLLING_INCREMENTED_EVENT_QN } from "./constants.js";
@@ -1,17 +1,9 @@
1
- /// <reference types="temporal-polyfill/global" preserve="true" />
2
1
  import { createEntityExecutor, type HandlerContext } from "@cosmicdrift/kumiko-framework/engine";
3
2
  import { KumikoError, reraiseAsKumikoError } from "@cosmicdrift/kumiko-framework/errors";
3
+ import { Temporal } from "@cosmicdrift/kumiko-types/temporal";
4
4
  import { markCapSoftWarned, readRollingCapUsage } from "./book-cap-usage.js";
5
5
  import { capCounterEntity } from "./entity.js";
6
6
 
7
- // Temporal globally provided by the framework's polyfill init
8
- // (ensureTemporalPolyfill() in time/polyfill.ts, called from
9
- // setupTestStack/boot). Importing from "temporal-polyfill" gives us
10
- // the polyfill-package types which don't quite match drizzle's
11
- // `instant()`-customType (temporal-spec narrowing of `until(...).sign`).
12
- // Mirror the audit-handler pattern: rely on the global ambient
13
- // declaration from temporal-spec.
14
-
15
7
  const { table } = createEntityExecutor("cap-counter", capCounterEntity);
16
8
 
17
9
  // =============================================================================
@@ -1,4 +1,10 @@
1
1
  [
2
+ {
3
+ "version": "0.350.0",
4
+ "type": "improvement",
5
+ "title": "Mails get a plain-text part (EmailMessage.text, NotificationRenderer.renderText, simple renderer and GDPR mails fill it)",
6
+ "detail": "Email text part\n`EmailMessage` gets an optional `text`. With it the SMTP transport sends `multipart/alternative` with a plain-text part. `NotificationRenderer` gets an optional `renderText`, which the email channel sends next to the HTML through the queued render and send jobs. `createSimpleRenderer` implements it from the template data (header, sections, footer, branding footer; buttons as `label: url`). The GDPR default mails return and send `text` as well. The PII guard checks and redacts `text` like the body."
7
+ },
2
8
  {
3
9
  "version": "0.348.1",
4
10
  "type": "fix",
@@ -106,13 +106,15 @@ export function createEmailChannel(options: EmailChannelOptions): DeliveryChanne
106
106
  title: message.title,
107
107
  body: message.body,
108
108
  };
109
- const html = await renderer.render({
109
+ const rendererInput = {
110
110
  template: message.notificationType,
111
111
  variables,
112
112
  locale: message.locale,
113
- });
113
+ };
114
+ const html = await renderer.render(rendererInput);
115
+ const text = renderer.renderText ? await renderer.renderText(rendererInput) : undefined;
114
116
  const subject = (variables["subject"] as string) ?? message.title; // @cast-boundary dynamic-key
115
- return { html, subject };
117
+ return { html, subject, ...(text !== undefined && { text }) };
116
118
  }
117
119
 
118
120
  return {
@@ -132,9 +134,17 @@ export function createEmailChannel(options: EmailChannelOptions): DeliveryChanne
132
134
  },
133
135
 
134
136
  async send(address, message, _ctx, rendered) {
135
- const { html, subject } = rendered ?? (await renderMessage(message));
137
+ const { html, subject, text } = rendered ?? (await renderMessage(message));
136
138
  const envelope = emailEnvelopeFrom(message.data);
137
- await transport.send(guardEmailMessage({ to: address, subject, html, ...envelope }));
139
+ await transport.send(
140
+ guardEmailMessage({
141
+ to: address,
142
+ subject,
143
+ html,
144
+ ...(text !== undefined && { text }),
145
+ ...envelope,
146
+ }),
147
+ );
138
148
  return { status: "sent", address };
139
149
  },
140
150
  };
@@ -36,7 +36,9 @@ export function guardEmailMessage(message: EmailMessage): EmailMessage {
36
36
  }
37
37
  }
38
38
  const leaking =
39
- message.subject.includes(CIPHERTEXT_MARKER) || message.html.includes(CIPHERTEXT_MARKER);
39
+ message.subject.includes(CIPHERTEXT_MARKER) ||
40
+ message.html.includes(CIPHERTEXT_MARKER) ||
41
+ message.text?.includes(CIPHERTEXT_MARKER) === true;
40
42
  if (!leaking) return message;
41
43
  const detail =
42
44
  "[channel-email] mail subject/body contains a PII ciphertext " +
@@ -48,6 +50,9 @@ export function guardEmailMessage(message: EmailMessage): EmailMessage {
48
50
  ...message,
49
51
  subject: message.subject.replace(CIPHERTEXT_RE, "[pii-redacted]"),
50
52
  html: message.html.replace(CIPHERTEXT_RE, "[pii-redacted]"),
53
+ ...(message.text !== undefined && {
54
+ text: message.text.replace(CIPHERTEXT_RE, "[pii-redacted]"),
55
+ }),
51
56
  };
52
57
  }
53
58
 
@@ -92,6 +92,7 @@ export function createSmtpTransport(options: SmtpTransportOptions): EmailTranspo
92
92
  to: message.to,
93
93
  subject: message.subject,
94
94
  html: message.html,
95
+ ...(message.text !== undefined && { text: message.text }),
95
96
  ...(message.replyTo && { replyTo: message.replyTo }),
96
97
  ...(message.headers && { headers: message.headers }),
97
98
  });
@@ -3,6 +3,9 @@ export type EmailMessage = {
3
3
  readonly to: string;
4
4
  readonly subject: string;
5
5
  readonly html: string;
6
+ // Plain-text alternative to `html`. Set → the mail goes out as
7
+ // multipart/alternative, and clients without HTML show this part.
8
+ readonly text?: string;
6
9
  // Per-message From override. Absent → the transport's configured From (the
7
10
  // app-wide default). Set when one send must originate from a specific
8
11
  // mailbox — a reply from the address the original mail reached, not noreply@.
@@ -16,7 +16,7 @@ import {
16
16
  SYSTEM_USER_ID,
17
17
  } from "@cosmicdrift/kumiko-framework/engine";
18
18
  import { InternalError } from "@cosmicdrift/kumiko-framework/errors";
19
- import { Temporal } from "temporal-polyfill";
19
+ import { Temporal } from "@cosmicdrift/kumiko-types/temporal";
20
20
  import * as z from "zod";
21
21
  import { decryptStoredPii } from "../../shared/index.js";
22
22
  import { resolveUserDisplayNames, USER_FEATURE } from "../../user/index.js";
@@ -47,7 +47,9 @@ const renderJobPayloadSchema = z.object({
47
47
  });
48
48
 
49
49
  const sendJobPayloadSchema = renderJobPayloadSchema.extend({
50
- rendered: z.object({ html: z.string(), subject: z.string() }).optional(),
50
+ rendered: z
51
+ .object({ html: z.string(), subject: z.string(), text: z.string().optional() })
52
+ .optional(),
51
53
  });
52
54
 
53
55
  type RenderJobPayload = z.infer<typeof renderJobPayloadSchema>;
@@ -48,6 +48,7 @@ export type ChannelResult = {
48
48
  export type RenderedMessage = {
49
49
  readonly html: string;
50
50
  readonly subject: string;
51
+ readonly text?: string;
51
52
  };
52
53
 
53
54
  // `mode` decides how the delivery-service dispatches a channel:
@@ -83,6 +84,8 @@ export type RendererInput = {
83
84
  export type NotificationRenderer = {
84
85
  readonly name: string;
85
86
  render(input: RendererInput): Promise<string>;
87
+ /** Plain-text version of the same mail; the email channel sends it as the text part of a multipart/alternative mail. */
88
+ renderText?(input: RendererInput): Promise<string>;
86
89
  };
87
90
 
88
91
  // --- Delivery Log Entry ---
@@ -5,7 +5,7 @@ import {
5
5
  VersionConflictError,
6
6
  writeFailure,
7
7
  } from "@cosmicdrift/kumiko-framework/errors";
8
- import { Temporal } from "temporal-polyfill";
8
+ import { Temporal } from "@cosmicdrift/kumiko-types/temporal";
9
9
  import * as z from "zod";
10
10
  import {
11
11
  FEATURE_TOGGLE_AGGREGATE_TYPE,
@@ -1,7 +1,7 @@
1
1
  import { unsafeReadRetrying } 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 { Temporal } from "temporal-polyfill";
4
+ import { Temporal } from "@cosmicdrift/kumiko-types/temporal";
5
5
  import type { FormDraftBlob } from "../../schemas.js";
6
6
 
7
7
  export type StaleDraftRow = {
@@ -1,7 +1,7 @@
1
1
  import { unsafeReadRetrying } from "@cosmicdrift/kumiko-framework/bun-db";
2
2
  import type { DbRunner } 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
 
6
6
  // Narrows a draft blob's raw FileRef-shaped storageKeys (collectDraftFileRefKeys
7
7
  // output — extracted from free-form, client-supplied JSON, issue #1889) down to
@@ -1,7 +1,7 @@
1
1
  import { fetchOne, selectMany } from "@cosmicdrift/kumiko-framework/bun-db";
2
2
  import type { TenantDb } 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 { formDraftTable } from "./executor.js";
6
6
  import type { FormDraftBlob } from "./schemas.js";
7
7
 
@@ -28,7 +28,7 @@ import {
28
28
  import { acquireNamespacedAdvisoryLock } from "@cosmicdrift/kumiko-framework/db";
29
29
  import type { WriteHandlerDef } from "@cosmicdrift/kumiko-framework/engine";
30
30
  import { InternalError } from "@cosmicdrift/kumiko-framework/errors";
31
- import { Temporal } from "temporal-polyfill";
31
+ import { Temporal } from "@cosmicdrift/kumiko-types/temporal";
32
32
  import * as z from "zod";
33
33
  import { inboundMessageAggregateId, mailThreadAggregateId } from "../aggregate-id.js";
34
34
  import {
@@ -19,7 +19,7 @@
19
19
  import { transaction } from "@cosmicdrift/kumiko-framework/bun-db";
20
20
  import { acquireNamespacedAdvisoryLock, type DbConnection } from "@cosmicdrift/kumiko-framework/db";
21
21
  import type { SecretsContext } from "@cosmicdrift/kumiko-framework/secrets";
22
- import { Temporal } from "temporal-polyfill";
22
+ import { Temporal } from "@cosmicdrift/kumiko-types/temporal";
23
23
  import { InboundMailAuthMethods, inboundCredentialSecretKey } from "./constants.js";
24
24
  import {
25
25
  InboundAuthError,
@@ -11,7 +11,7 @@
11
11
  // HMAC-Input, timing-safe compare, Expiry im Klartext-Segment).
12
12
 
13
13
  import { createHmac, randomUUID, timingSafeEqual } from "node:crypto";
14
- import { Temporal } from "temporal-polyfill";
14
+ import { Temporal } from "@cosmicdrift/kumiko-types/temporal";
15
15
 
16
16
  const STATE_PURPOSE = "inbound-mail-oauth-connect";
17
17
 
@@ -51,7 +51,7 @@ import {
51
51
  } from "@cosmicdrift/kumiko-framework/crypto";
52
52
  import type { DbConnection, EntityTableMeta } from "@cosmicdrift/kumiko-framework/db";
53
53
  import type { DistributedLock } from "@cosmicdrift/kumiko-framework/pipeline";
54
- import { Temporal } from "temporal-polyfill";
54
+ import { Temporal } from "@cosmicdrift/kumiko-types/temporal";
55
55
  import { InboundMailAccountStatuses, InboundMailFoundationHandlers } from "./constants.js";
56
56
  import { MAIL_ACCOUNT_PII_FIELDS, syncCursorTable } from "./entities.js";
57
57
  import {
@@ -36,8 +36,8 @@ import {
36
36
  import { requireSecretsContext } from "@cosmicdrift/kumiko-bundled-features/secrets";
37
37
  import { defineFeature } from "@cosmicdrift/kumiko-framework/engine";
38
38
  import { instantToLegacyDate } from "@cosmicdrift/kumiko-framework/time";
39
+ import { Temporal } from "@cosmicdrift/kumiko-types/temporal";
39
40
  import type { ImapFlow, MailboxObject } from "imapflow";
40
- import { Temporal } from "temporal-polyfill";
41
41
  import { type ImapCredentialDocument, parseImapCredentialDocument } from "./credential-document.js";
42
42
  import {
43
43
  assertUidValidity,
@@ -17,9 +17,9 @@ import {
17
17
  } from "@cosmicdrift/kumiko-bundled-features/inbound-mail-foundation";
18
18
  import { createFallbackLogger } from "@cosmicdrift/kumiko-framework/logging";
19
19
  import { legacyDateToInstant } from "@cosmicdrift/kumiko-framework/time";
20
+ import { Temporal } from "@cosmicdrift/kumiko-types/temporal";
20
21
  import { ImapFlow } from "imapflow";
21
22
  import { type AddressObject, type ParsedMail, simpleParser } from "mailparser";
22
- import { Temporal } from "temporal-polyfill";
23
23
  import type { ImapCredentialDocument } from "./credential-document.js";
24
24
 
25
25
  export const IMAP_MAILBOX = "INBOX";
@@ -1,4 +1,4 @@
1
- import { Temporal } from "temporal-polyfill";
1
+ import { Temporal } from "@cosmicdrift/kumiko-types/temporal";
2
2
 
3
3
  // kumiko-framework#1525: the five value-position Temporal.Instant.from
4
4
  // call sites in feature.ts live inside defineApply callbacks with no
@@ -4,8 +4,8 @@ import type {
4
4
  AggregateKey,
5
5
  AggregateRow,
6
6
  } from "@cosmicdrift/kumiko-types/aggregate-types";
7
+ import type { Temporal } from "@cosmicdrift/kumiko-types/temporal";
7
8
  import type { TenantDb } from "@cosmicdrift/kumiko-types/tenant-db-types";
8
- import type { Temporal } from "temporal-polyfill";
9
9
  import { MAX_GROUP_ROWS, type MetricRange } from "./constants.js";
10
10
  import type {
11
11
  MetricDefinition,
@@ -6,7 +6,7 @@ import {
6
6
  type QueryHandlerDefinition,
7
7
  } from "@cosmicdrift/kumiko-framework/engine";
8
8
  import { InternalError, ValidationError } from "@cosmicdrift/kumiko-framework/errors";
9
- import { Temporal } from "temporal-polyfill";
9
+ import { Temporal } from "@cosmicdrift/kumiko-types/temporal";
10
10
  import * as z from "zod";
11
11
  import { runMetric } from "./compute.js";
12
12
  import {
@@ -5,8 +5,8 @@ import type {
5
5
  AggregateTimeBucket,
6
6
  } from "@cosmicdrift/kumiko-types/aggregate-types";
7
7
  import type { SchemaTable } from "@cosmicdrift/kumiko-types/schema-table-types";
8
+ import type { Temporal } from "@cosmicdrift/kumiko-types/temporal";
8
9
  import type { WhereObject } from "@cosmicdrift/kumiko-types/where-clause-types";
9
- import type { Temporal } from "temporal-polyfill";
10
10
  import type { MetricRange, MetricScope } from "./constants.js";
11
11
 
12
12
  /** 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. */
@@ -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
 
@@ -1,4 +1,4 @@
1
- import { Temporal } from "temporal-polyfill";
1
+ import { Temporal } from "@cosmicdrift/kumiko-types/temporal";
2
2
 
3
3
  export function isExpiredAt(expiresAt: { epochMilliseconds: number } | null): boolean {
4
4
  return (