@cosmicdrift/kumiko-samples 0.350.0 → 0.352.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/packages/bundled-features/package.json +2 -2
- package/packages/bundled-features/src/audit/handlers/list.query.ts +1 -1
- package/packages/bundled-features/src/auth-email-password/email-templates.ts +1 -1
- package/packages/bundled-features/src/auth-email-password/handlers/invite-accept-with-login.write.ts +1 -1
- package/packages/bundled-features/src/auth-email-password/handlers/invite-accept.write.ts +1 -1
- package/packages/bundled-features/src/auth-email-password/handlers/invite-create.write.ts +1 -1
- package/packages/bundled-features/src/auth-email-password/handlers/signup-request.write.ts +1 -1
- package/packages/bundled-features/src/auth-email-password/handlers/token-request-handler.ts +1 -1
- package/packages/bundled-features/src/auth-email-password/reset-token.ts +1 -1
- package/packages/bundled-features/src/auth-email-password/unlock-token.ts +1 -1
- package/packages/bundled-features/src/auth-email-password/verification-token.ts +1 -1
- package/packages/bundled-features/src/auth-mfa/handlers/enable-confirm-preauth.write.ts +1 -1
- package/packages/bundled-features/src/auth-mfa/handlers/enable-confirm.write.ts +1 -1
- package/packages/bundled-features/src/auth-mfa/hmac-token-codec.ts +1 -1
- package/packages/bundled-features/src/auth-mfa/mfa-challenge-token.ts +1 -1
- package/packages/bundled-features/src/auth-mfa/mfa-preauth-setup-token.ts +1 -1
- package/packages/bundled-features/src/auth-mfa/mfa-setup-token.ts +1 -1
- package/packages/bundled-features/src/billing-foundation/constants.ts +3 -17
- package/packages/bundled-features/src/billing-foundation/consumer-protection/confirmation-mail.ts +1 -1
- package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-mail.ts +1 -1
- package/packages/bundled-features/src/billing-foundation/feature.ts +2 -9
- package/packages/bundled-features/src/billing-foundation/handlers/sync-subscription.write.ts +3 -11
- package/packages/bundled-features/src/cap-counter/book-cap-usage.ts +96 -55
- package/packages/bundled-features/src/cap-counter/cap-reservation.ts +247 -0
- package/packages/bundled-features/src/cap-counter/changes.json +28 -0
- package/packages/bundled-features/src/cap-counter/constants.ts +8 -0
- package/packages/bundled-features/src/cap-counter/enforce-cap.ts +1 -9
- package/packages/bundled-features/src/cap-counter/feature.ts +7 -0
- package/packages/bundled-features/src/cap-counter/handlers/increment-rolling.write.ts +5 -0
- package/packages/bundled-features/src/cap-counter/index.ts +9 -0
- package/packages/bundled-features/src/cap-counter/payload-field.ts +10 -0
- package/packages/bundled-features/src/cap-counter/reservation-chain.ts +51 -0
- package/packages/bundled-features/src/cap-counter/stock-cap-guard.ts +3 -0
- package/packages/bundled-features/src/cap-counter/tables.ts +53 -0
- package/packages/bundled-features/src/cap-counter/value-cap-guard.ts +70 -0
- package/packages/bundled-features/src/cap-counter/with-cap-enforcement.ts +63 -70
- package/packages/bundled-features/src/channel-discord/feature.ts +1 -0
- package/packages/bundled-features/src/channel-slack/feature.ts +1 -0
- package/packages/bundled-features/src/channel-teams/feature.ts +1 -0
- package/packages/bundled-features/src/delivery/attempt-log-retention.ts +40 -0
- package/packages/bundled-features/src/delivery/attempt-log.ts +26 -3
- package/packages/bundled-features/src/delivery/changes.json +12 -0
- package/packages/bundled-features/src/delivery/constants.ts +1 -0
- package/packages/bundled-features/src/delivery/events.ts +3 -0
- package/packages/bundled-features/src/delivery/feature.ts +50 -2
- package/packages/bundled-features/src/delivery/handlers/log.query.ts +1 -1
- package/packages/bundled-features/src/delivery/index.ts +6 -0
- package/packages/bundled-features/src/delivery/mask-recipient-address.ts +40 -0
- package/packages/bundled-features/src/delivery/public-names.ts +5 -0
- package/packages/bundled-features/src/delivery/types.ts +12 -2
- package/packages/bundled-features/src/derivatives-sharp/changes.json +6 -0
- package/packages/bundled-features/src/feature-toggles/handlers/set.write.ts +1 -1
- package/packages/bundled-features/src/form-draft/db/queries/cleanup.ts +1 -1
- package/packages/bundled-features/src/form-draft/db/queries/owned-file-refs.ts +1 -1
- package/packages/bundled-features/src/form-draft/lookup.ts +1 -1
- package/packages/bundled-features/src/inbound-mail-foundation/handlers/ingest-message.write.ts +1 -1
- package/packages/bundled-features/src/inbound-mail-foundation/oauth-access-token.ts +1 -1
- package/packages/bundled-features/src/inbound-mail-foundation/oauth-state.ts +1 -1
- package/packages/bundled-features/src/inbound-mail-foundation/watch-supervisor.ts +1 -1
- package/packages/bundled-features/src/inbound-provider-imap/feature.ts +1 -1
- package/packages/bundled-features/src/inbound-provider-imap/imap-client.ts +1 -1
- package/packages/bundled-features/src/jobs/job-instant.ts +1 -1
- package/packages/bundled-features/src/legal-pages/README.md +3 -3
- package/packages/bundled-features/src/legal-pages/changes.json +6 -0
- package/packages/bundled-features/src/metrics/compute.ts +1 -1
- package/packages/bundled-features/src/metrics/feature.ts +1 -1
- package/packages/bundled-features/src/metrics/types.ts +1 -1
- package/packages/bundled-features/src/metrics/window.ts +1 -1
- package/packages/bundled-features/src/personal-access-tokens/expiry.ts +1 -1
- package/packages/bundled-features/src/personal-access-tokens/handlers/create.write.ts +1 -1
- package/packages/bundled-features/src/personal-access-tokens/handlers/revoke.write.ts +1 -1
- package/packages/bundled-features/src/personal-access-tokens/revoke-for-user.ts +1 -1
- package/packages/bundled-features/src/renderer-foundation/types.ts +1 -1
- package/packages/bundled-features/src/renderer-simple/changes.json +6 -0
- package/packages/bundled-features/src/renderer-simple/feature.ts +4 -6
- package/packages/bundled-features/src/renderer-simple/index.ts +1 -0
- package/packages/bundled-features/src/renderer-simple/mail-blocks.ts +138 -0
- package/packages/bundled-features/src/renderer-simple/simple-renderer.ts +33 -4
- package/packages/bundled-features/src/sessions/handlers/revoke-all-for-user.write.ts +1 -1
- package/packages/bundled-features/src/sessions/handlers/revoke-all-others.write.ts +1 -1
- package/packages/bundled-features/src/sessions/handlers/revoke.write.ts +1 -1
- package/packages/bundled-features/src/sessions/session-callbacks.ts +1 -1
- package/packages/bundled-features/src/shared/grace-period.ts +3 -10
- package/packages/bundled-features/src/shared/row-bound-grant.ts +1 -1
- package/packages/bundled-features/src/shared/signed-token.ts +1 -1
- package/packages/bundled-features/src/subscription-mollie/verify-webhook.ts +1 -1
- package/packages/bundled-features/src/subscription-stripe/verify-webhook.ts +1 -1
- package/packages/bundled-features/src/template-resolver/api.ts +1 -1
- package/packages/bundled-features/src/template-resolver/handlers/by-tenant.query.ts +1 -1
- package/packages/bundled-features/src/template-resolver/handlers/collection-shared.ts +1 -1
- package/packages/bundled-features/src/template-resolver/table.ts +1 -1
- package/packages/bundled-features/src/tenant/handlers/team-list.query.ts +1 -1
- package/packages/bundled-features/src/tenant/invited-membership.ts +1 -1
- package/packages/bundled-features/src/tenant-handover/grant.ts +1 -1
- package/packages/bundled-features/src/tier-engine/changes.json +6 -0
- package/packages/bundled-features/src/tier-engine/index.ts +7 -0
- package/packages/bundled-features/src/tier-engine/tier-option-gate.ts +148 -0
- package/packages/bundled-features/src/user-data-rights/db/queries/export-jobs.ts +1 -1
- package/packages/bundled-features/src/user-data-rights/db/queries/forget-cleanup.ts +3 -9
- package/packages/bundled-features/src/user-data-rights/deletion-token.ts +1 -1
- package/packages/bundled-features/src/user-data-rights/email-templates.ts +1 -1
- package/packages/bundled-features/src/user-data-rights/handlers/list-download-attempts.query.ts +1 -1
- package/packages/bundled-features/src/user-data-rights/handlers/my-audit-log.query.ts +1 -1
- package/packages/bundled-features/src/user-data-rights-defaults/hooks/delivery-attempt.userdata-hook.ts +36 -6
- package/packages/bundled-features/src/waitlist/handlers/invite.write.ts +1 -1
- package/packages/bundled-features/src/waitlist/handlers/submit.write.ts +1 -1
- package/samples/apps/marketing-demo/package.json +1 -0
- package/samples/apps/marketing-demo/src/app/seed-data.ts +2 -2
- package/samples/apps/marketing-demo/src/app/seed.ts +1 -1
- package/samples/apps/styleguide/src/app/shell.tsx +1 -1
- package/samples/apps/styleguide/src/features/gallery/web/Gallery.tsx +9 -3
- package/samples/apps/ui-walkthrough/src/app/assistant-action.tsx +6 -5
- package/samples/apps/ui-walkthrough/src/app/shell.tsx +3 -2
- package/samples/apps/ui-walkthrough/src/features/tasks/i18n.ts +8 -0
- package/samples/apps/use-all-bundled/package.json +1 -0
- package/samples/apps/use-all-bundled/src/app/seed.ts +1 -1
- package/samples/recipes/files-post-processing/package.json +1 -1
- package/samples/recipes/seed-migration/README.md +13 -1
|
@@ -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
|
|
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,10 @@
|
|
|
1
1
|
[
|
|
2
|
+
{
|
|
3
|
+
"version": "0.352.0",
|
|
4
|
+
"type": "fix",
|
|
5
|
+
"title": "The legal-pages README imprint example uses a fictional demo identity",
|
|
6
|
+
"detail": "The legal-pages README seeds its imprint example with a fictional demo identity and address instead of a real person's details."
|
|
7
|
+
},
|
|
2
8
|
{
|
|
3
9
|
"version": "0.344.0",
|
|
4
10
|
"type": "improvement",
|
|
@@ -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
|
|
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
|
|
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
|
|
|
@@ -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
|
|
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
|
|
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
|
|
6
|
+
import { Temporal } from "@cosmicdrift/kumiko-types/temporal";
|
|
7
7
|
import {
|
|
8
8
|
PAT_REVOKED_AGGREGATE_TYPE,
|
|
9
9
|
PAT_REVOKED_EVENT_QN,
|
|
@@ -12,7 +12,7 @@ export type RenderRequest =
|
|
|
12
12
|
| { kind: "image-snapshot"; payload: DocumentPayload; options?: ImageOptions };
|
|
13
13
|
|
|
14
14
|
export type RenderResponse =
|
|
15
|
-
| { kind: "notification"; html: string }
|
|
15
|
+
| { kind: "notification"; html: string; text?: string }
|
|
16
16
|
| { kind: "mail-html"; html: string; text: string }
|
|
17
17
|
| { kind: "document-pdf"; pdfBytes: Uint8Array; pageCount: number; sizeBytes: number }
|
|
18
18
|
| {
|
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
[
|
|
2
|
+
{
|
|
3
|
+
"version": "0.352.0",
|
|
4
|
+
"type": "improvement",
|
|
5
|
+
"title": "Badge, stages track and chips for the simple mail renderer",
|
|
6
|
+
"detail": "Use badge, { stages } and { chips } in the template variables; the notification RenderResponse carries the matching plain text in text."
|
|
7
|
+
},
|
|
2
8
|
{
|
|
3
9
|
"version": "0.344.0",
|
|
4
10
|
"type": "improvement",
|
|
@@ -31,12 +31,10 @@ export async function adaptToFoundation(
|
|
|
31
31
|
);
|
|
32
32
|
}
|
|
33
33
|
const variables = await resolveNotificationVariables(req, ctx);
|
|
34
|
-
const
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
});
|
|
39
|
-
return { kind: "notification", html };
|
|
34
|
+
const input = { template: req.payload.template ?? "", variables, locale: req.payload.locale };
|
|
35
|
+
const html = await renderer.render(input);
|
|
36
|
+
const text = renderer.renderText ? await renderer.renderText(input) : undefined;
|
|
37
|
+
return text === undefined ? { kind: "notification", html } : { kind: "notification", html, text };
|
|
40
38
|
}
|
|
41
39
|
|
|
42
40
|
export type RendererSimpleOptions = {
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { escapeHtml, escapeHtmlAttr } from "@cosmicdrift/kumiko-headless";
|
|
2
|
+
|
|
3
|
+
export type MailTone = "neutral" | "info" | "success" | "warning" | "danger";
|
|
4
|
+
export type MailStageState = "done" | "current" | "upcoming";
|
|
5
|
+
|
|
6
|
+
export type MailBadge = { readonly label: string; readonly tone?: MailTone };
|
|
7
|
+
export type MailStage = { readonly label: string; readonly state: MailStageState };
|
|
8
|
+
export type MailChip = { readonly label: string; readonly tone?: MailTone };
|
|
9
|
+
|
|
10
|
+
// Literal types (as const) keep the colors compile-time known, so they can go into style
|
|
11
|
+
// attributes without escaping.
|
|
12
|
+
const TONE_COLORS = {
|
|
13
|
+
neutral: { bg: "#f3f4f6", fg: "#374151" },
|
|
14
|
+
info: { bg: "#dbeafe", fg: "#1e40af" },
|
|
15
|
+
success: { bg: "#dcfce7", fg: "#166534" },
|
|
16
|
+
warning: { bg: "#fef3c7", fg: "#92400e" },
|
|
17
|
+
danger: { bg: "#fee2e2", fg: "#991b1b" },
|
|
18
|
+
} as const satisfies Readonly<Record<MailTone, { readonly bg: string; readonly fg: string }>>;
|
|
19
|
+
|
|
20
|
+
type ToneColors = (typeof TONE_COLORS)[MailTone];
|
|
21
|
+
type PillMargin = "0" | "0 6px 6px 0";
|
|
22
|
+
|
|
23
|
+
const STAGE_STATES: readonly MailStageState[] = ["done", "current", "upcoming"];
|
|
24
|
+
const STAGE_DONE_COLOR = "#16a34a";
|
|
25
|
+
const STAGE_UPCOMING_BG = "#e5e7eb";
|
|
26
|
+
const STAGE_UPCOMING_FG = "#9ca3af";
|
|
27
|
+
|
|
28
|
+
function isRecord(value: unknown): value is Readonly<Record<string, unknown>> {
|
|
29
|
+
return typeof value === "object" && value !== null;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function isMailTone(value: unknown): value is MailTone {
|
|
33
|
+
return typeof value === "string" && Object.hasOwn(TONE_COLORS, value);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function toneColors(tone: unknown): ToneColors {
|
|
37
|
+
return TONE_COLORS[isMailTone(tone) ? tone : "neutral"];
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function isStageState(value: unknown): value is MailStageState {
|
|
41
|
+
return STAGE_STATES.some((state) => state === value);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Template data is runtime data (variables of a stored template): entries that do not match are
|
|
45
|
+
// skipped instead of failing the whole mail.
|
|
46
|
+
function isStage(value: unknown): value is MailStage {
|
|
47
|
+
return isRecord(value) && typeof value["label"] === "string" && isStageState(value["state"]);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function isChip(value: unknown): value is MailChip {
|
|
51
|
+
return isRecord(value) && typeof value["label"] === "string";
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function parseBadge(value: unknown): MailBadge | undefined {
|
|
55
|
+
return isChip(value) && value.label !== "" ? value : undefined;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function validStages(value: unknown): readonly MailStage[] {
|
|
59
|
+
return Array.isArray(value) ? value.filter(isStage) : [];
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function validChips(value: unknown): readonly MailChip[] {
|
|
63
|
+
return Array.isArray(value) ? value.filter(isChip) : [];
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function pill(chip: MailChip, margin: PillMargin): string {
|
|
67
|
+
const { bg, fg } = toneColors(chip.tone);
|
|
68
|
+
return `<span style="display:inline-block;margin:${margin};padding:2px 10px;border-radius:999px;background:${bg};color:${fg};font-size:12px;font-weight:600;line-height:18px">${escapeHtml(chip.label)}</span>`;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function renderBadge(badge: MailBadge): string {
|
|
72
|
+
return `<p style="margin:0 0 12px">${pill(badge, "0")}</p>`;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function renderChips(chips: unknown): string {
|
|
76
|
+
const valid = validChips(chips);
|
|
77
|
+
if (valid.length === 0) return "";
|
|
78
|
+
return `<p style="margin:0 0 16px">${valid.map((chip) => pill(chip, "0 6px 6px 0")).join("")}</p>`;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function stageMarker(stage: MailStage, position: number, primaryColor: string): string {
|
|
82
|
+
const base =
|
|
83
|
+
"display:inline-block;width:24px;height:24px;line-height:24px;border-radius:12px;text-align:center;font-size:12px;font-weight:700";
|
|
84
|
+
const primary = escapeHtmlAttr(primaryColor);
|
|
85
|
+
if (stage.state === "done") {
|
|
86
|
+
return `<span style="${base};background:${STAGE_DONE_COLOR};color:#fff">✓</span>`;
|
|
87
|
+
}
|
|
88
|
+
if (stage.state === "current") {
|
|
89
|
+
return `<span style="${base};background:#fff;border:2px solid ${primary};line-height:20px;width:20px;height:20px;color:${primary}">${position}</span>`;
|
|
90
|
+
}
|
|
91
|
+
return `<span style="${base};background:${STAGE_UPCOMING_BG};color:${STAGE_UPCOMING_FG}">${position}</span>`;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function stageCell(stage: MailStage, position: number, primaryColor: string): string {
|
|
95
|
+
const labelColor = stage.state === "upcoming" ? STAGE_UPCOMING_FG : "#111";
|
|
96
|
+
const weight = stage.state === "current" ? 700 : 400;
|
|
97
|
+
return `<td align="center" valign="top" style="padding:0 4px;font-family:sans-serif">${stageMarker(stage, position, primaryColor)}<div style="margin:4px 0 0;font-size:12px;font-weight:${weight};color:${labelColor}">${escapeHtml(stage.label)}</div></td>`;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function stageConnector(previous: MailStage): string {
|
|
101
|
+
const color = previous.state === "done" ? STAGE_DONE_COLOR : STAGE_UPCOMING_BG;
|
|
102
|
+
return `<td valign="top" style="padding:11px 0 0"><div style="width:24px;height:2px;line-height:2px;font-size:2px;background:${color}"> </div></td>`;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export function renderStages(stages: unknown, primaryColor: string): string {
|
|
106
|
+
const valid = validStages(stages);
|
|
107
|
+
if (valid.length === 0) return "";
|
|
108
|
+
const cellsHtml = valid
|
|
109
|
+
.map((stage, index) => {
|
|
110
|
+
const previous = valid[index - 1];
|
|
111
|
+
const connector = previous ? stageConnector(previous) : "";
|
|
112
|
+
return `${connector}${stageCell(stage, index + 1, primaryColor)}`;
|
|
113
|
+
})
|
|
114
|
+
.join("");
|
|
115
|
+
return `<table role="presentation" cellpadding="0" cellspacing="0" border="0" style="margin:0 0 16px"><tr>${cellsHtml}</tr></table>`;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const STAGE_TEXT_MARKER: Readonly<Record<MailStageState, string>> = {
|
|
119
|
+
done: "✓",
|
|
120
|
+
current: "▶",
|
|
121
|
+
upcoming: "○",
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
export function badgeText(badge: MailBadge): string {
|
|
125
|
+
return `[${badge.label}]`;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export function stagesText(stages: unknown): string {
|
|
129
|
+
return validStages(stages)
|
|
130
|
+
.map((stage) => `${STAGE_TEXT_MARKER[stage.state]} ${stage.label}`)
|
|
131
|
+
.join(" → ");
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export function chipsText(chips: unknown): string {
|
|
135
|
+
return validChips(chips)
|
|
136
|
+
.map((chip) => chip.label)
|
|
137
|
+
.join(" · ");
|
|
138
|
+
}
|
|
@@ -1,6 +1,18 @@
|
|
|
1
1
|
import { escapeHtml, escapeHtmlAttr } from "@cosmicdrift/kumiko-headless";
|
|
2
2
|
import type { NotificationRenderer } from "../delivery/index.js";
|
|
3
3
|
import { renderSafeMarkdown } from "../page-render/index.js";
|
|
4
|
+
import {
|
|
5
|
+
badgeText,
|
|
6
|
+
chipsText,
|
|
7
|
+
type MailBadge,
|
|
8
|
+
type MailChip,
|
|
9
|
+
type MailStage,
|
|
10
|
+
parseBadge,
|
|
11
|
+
renderBadge,
|
|
12
|
+
renderChips,
|
|
13
|
+
renderStages,
|
|
14
|
+
stagesText,
|
|
15
|
+
} from "./mail-blocks.js";
|
|
4
16
|
|
|
5
17
|
export type MailBranding = {
|
|
6
18
|
/** Shown as text when there is no logo, and as the logo alt text. */
|
|
@@ -170,10 +182,13 @@ type Section =
|
|
|
170
182
|
| { readonly text: string }
|
|
171
183
|
| { readonly heading: string }
|
|
172
184
|
| { readonly markdown: string }
|
|
173
|
-
| { readonly button: { readonly label: string; readonly url: string } }
|
|
185
|
+
| { readonly button: { readonly label: string; readonly url: string } }
|
|
186
|
+
| { readonly stages: readonly MailStage[] }
|
|
187
|
+
| { readonly chips: readonly MailChip[] };
|
|
174
188
|
|
|
175
189
|
type EmailTemplateData = {
|
|
176
190
|
// Preferred: structured email data
|
|
191
|
+
readonly badge?: MailBadge;
|
|
177
192
|
readonly header?: string;
|
|
178
193
|
readonly sections?: readonly Section[];
|
|
179
194
|
readonly footer?: string;
|
|
@@ -183,6 +198,7 @@ type EmailTemplateData = {
|
|
|
183
198
|
};
|
|
184
199
|
|
|
185
200
|
function templateContent(variables: Readonly<Record<string, unknown>>): {
|
|
201
|
+
readonly badge: MailBadge | undefined;
|
|
186
202
|
readonly header: string | undefined;
|
|
187
203
|
readonly sections: readonly Section[] | undefined;
|
|
188
204
|
readonly footer: string | undefined;
|
|
@@ -190,6 +206,7 @@ function templateContent(variables: Readonly<Record<string, unknown>>): {
|
|
|
190
206
|
const data = variables as EmailTemplateData; // @cast-boundary render-helper
|
|
191
207
|
// Without structured fields, title + body become the header and a single text section.
|
|
192
208
|
return {
|
|
209
|
+
badge: parseBadge(data.badge),
|
|
193
210
|
header: data.header ?? data.title,
|
|
194
211
|
sections: data.sections ?? (data.body ? [{ text: data.body }] : undefined),
|
|
195
212
|
footer: data.footer,
|
|
@@ -202,6 +219,8 @@ function sectionText(section: Section): string {
|
|
|
202
219
|
if ("heading" in section) return section.heading;
|
|
203
220
|
if ("markdown" in section) return section.markdown;
|
|
204
221
|
if ("button" in section) return `${section.button.label}: ${section.button.url}`;
|
|
222
|
+
if ("stages" in section) return stagesText(section.stages);
|
|
223
|
+
if ("chips" in section) return chipsText(section.chips);
|
|
205
224
|
return "";
|
|
206
225
|
}
|
|
207
226
|
|
|
@@ -219,6 +238,8 @@ function renderSection(section: Section, primaryColor: string): string {
|
|
|
219
238
|
if ("button" in section) {
|
|
220
239
|
return `<p style="margin:0 0 16px"><a href="${escapeHtml(section.button.url)}" style="display:inline-block;padding:10px 24px;background:${escapeHtmlAttr(primaryColor)};color:#fff;text-decoration:none;border-radius:4px;font-size:14px">${escapeHtml(section.button.label)}</a></p>`;
|
|
221
240
|
}
|
|
241
|
+
if ("stages" in section) return renderStages(section.stages, primaryColor);
|
|
242
|
+
if ("chips" in section) return renderChips(section.chips);
|
|
222
243
|
return "";
|
|
223
244
|
}
|
|
224
245
|
|
|
@@ -232,13 +253,15 @@ export function createSimpleRenderer(branding?: MailBranding): NotificationRende
|
|
|
232
253
|
name: "simple",
|
|
233
254
|
|
|
234
255
|
async render(input) {
|
|
235
|
-
const { header, sections, footer } = templateContent(input.variables);
|
|
256
|
+
const { badge, header, sections, footer } = templateContent(input.variables);
|
|
236
257
|
|
|
237
258
|
const parts: string[] = [];
|
|
238
259
|
parts.push('<!DOCTYPE html><html><body style="margin:0;padding:0;font-family:sans-serif">');
|
|
239
260
|
parts.push('<div style="max-width:600px;margin:0 auto;padding:24px">');
|
|
240
261
|
parts.push(renderBrandingHeader(branding, primaryColor, logoUrl));
|
|
241
262
|
|
|
263
|
+
if (badge) parts.push(renderBadge(badge));
|
|
264
|
+
|
|
242
265
|
if (header) {
|
|
243
266
|
parts.push(
|
|
244
267
|
`<h1 style="margin:0 0 24px;color:#111;font-size:20px;font-weight:600">${escapeHtml(header)}</h1>`,
|
|
@@ -263,9 +286,15 @@ export function createSimpleRenderer(branding?: MailBranding): NotificationRende
|
|
|
263
286
|
},
|
|
264
287
|
|
|
265
288
|
async renderText(input) {
|
|
266
|
-
const { header, sections, footer } = templateContent(input.variables);
|
|
289
|
+
const { badge, header, sections, footer } = templateContent(input.variables);
|
|
267
290
|
const brandingFooter = brandingFooterLines(branding, input.locale).join("\n");
|
|
268
|
-
return [
|
|
291
|
+
return [
|
|
292
|
+
badge ? badgeText(badge) : undefined,
|
|
293
|
+
header,
|
|
294
|
+
...(sections ?? []).map(sectionText),
|
|
295
|
+
footer,
|
|
296
|
+
brandingFooter,
|
|
297
|
+
]
|
|
269
298
|
.filter((block): block is string => block !== undefined && block !== "")
|
|
270
299
|
.join("\n\n");
|
|
271
300
|
},
|
|
@@ -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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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,4 +1,10 @@
|
|
|
1
1
|
[
|
|
2
|
+
{
|
|
3
|
+
"version": "0.352.0",
|
|
4
|
+
"type": "improvement",
|
|
5
|
+
"title": "createTierOptionGate builds the availability rows and gates select options by tier on write",
|
|
6
|
+
"detail": "withTierOptionGate rejects a disallowed option with the lowest tier that allows it and lets an unchanged stored value pass on update."
|
|
7
|
+
},
|
|
2
8
|
{
|
|
3
9
|
"version": "0.278.0",
|
|
4
10
|
"type": "breaking",
|