@cosmicdrift/kumiko-bundled-features 0.351.0 → 0.353.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent-tools/agent-manifest.js +8 -2
- package/dist/agent-tools/manifest-deny-qns.d.ts +3 -0
- package/dist/agent-tools/manifest-deny-qns.js +17 -0
- package/dist/agent-tools/tool-catalog.d.ts +2 -1
- package/dist/agent-tools/tool-catalog.js +4 -1
- package/dist/audit/handlers/list.query.js +13 -8
- package/dist/auth-email-password/handlers/signup-confirm.write.js +2 -3
- package/dist/auth-mfa/schema/user-mfa.d.ts +6 -0
- package/dist/auth-mfa/screens.js +2 -2
- package/dist/billing-foundation/checkout-core.d.ts +7 -0
- package/dist/billing-foundation/checkout-core.js +7 -2
- package/dist/billing-foundation/entities.d.ts +8 -0
- package/dist/billing-foundation/handlers/start-plan-checkout.write.js +7 -1
- package/dist/billing-foundation/projection.d.ts +8 -0
- package/dist/cap-counter/book-cap-usage.d.ts +5 -1
- package/dist/cap-counter/book-cap-usage.js +65 -44
- package/dist/cap-counter/cap-reservation.d.ts +12 -0
- package/dist/cap-counter/cap-reservation.js +135 -0
- package/dist/cap-counter/constants.d.ts +3 -0
- package/dist/cap-counter/constants.js +6 -0
- package/dist/cap-counter/entity.d.ts +1 -0
- package/dist/cap-counter/feature.js +7 -2
- package/dist/cap-counter/handlers/increment-rolling.write.d.ts +5 -0
- package/dist/cap-counter/handlers/increment-rolling.write.js +4 -0
- package/dist/cap-counter/index.d.ts +4 -2
- package/dist/cap-counter/index.js +3 -1
- package/dist/cap-counter/payload-field.d.ts +1 -0
- package/dist/cap-counter/payload-field.js +11 -0
- package/dist/cap-counter/reservation-chain.d.ts +2 -0
- package/dist/cap-counter/reservation-chain.js +44 -0
- package/dist/cap-counter/stock-cap-guard.d.ts +3 -0
- package/dist/cap-counter/tables.d.ts +3 -0
- package/dist/cap-counter/tables.js +36 -0
- package/dist/cap-counter/value-cap-guard.d.ts +18 -0
- package/dist/cap-counter/value-cap-guard.js +33 -0
- package/dist/cap-counter/with-cap-enforcement.d.ts +5 -7
- package/dist/cap-counter/with-cap-enforcement.js +35 -51
- package/dist/cap-overview/access.js +2 -9
- package/dist/cap-overview/handlers/tenant-caps-list.query.js +2 -2
- package/dist/cap-overview/handlers/tenant-options.query.js +4 -5
- package/dist/channel-discord/feature.js +1 -0
- package/dist/channel-slack/feature.js +1 -0
- package/dist/channel-teams/feature.js +1 -0
- package/dist/compliance-profiles/schema/profile-selection.d.ts +10 -8
- package/dist/compliance-profiles-ops/handlers/tenants-missing-profile.query.js +2 -4
- package/dist/config/handlers/values.query.d.ts +3 -1
- package/dist/config/handlers/values.query.js +7 -2
- package/dist/config/table.d.ts +6 -0
- package/dist/custom-fields/entity.d.ts +4 -0
- package/dist/data-retention/schema/tenant-retention-override.d.ts +4 -0
- package/dist/delivery/attempt-log-retention.d.ts +9 -0
- package/dist/delivery/attempt-log-retention.js +23 -0
- package/dist/delivery/attempt-log.d.ts +1 -1
- package/dist/delivery/attempt-log.js +21 -2
- package/dist/delivery/constants.d.ts +1 -1
- package/dist/delivery/constants.js +1 -1
- package/dist/delivery/events.d.ts +1 -0
- package/dist/delivery/events.js +2 -0
- package/dist/delivery/feature.d.ts +1 -0
- package/dist/delivery/feature.js +39 -3
- package/dist/delivery/index.d.ts +2 -1
- package/dist/delivery/index.js +2 -1
- package/dist/delivery/mask-recipient-address.d.ts +1 -0
- package/dist/delivery/mask-recipient-address.js +41 -0
- package/dist/delivery/public-names.d.ts +2 -0
- package/dist/delivery/public-names.js +4 -1
- package/dist/delivery/tables.d.ts +6 -0
- package/dist/delivery/types.d.ts +1 -0
- package/dist/delivery/types.js +6 -2
- package/dist/document-ingest-foundation/entity.d.ts +4 -0
- package/dist/files-tenant-data/hooks.js +3 -2
- package/dist/folders/entity.d.ts +8 -0
- package/dist/folders/feature.js +2 -10
- package/dist/form-draft/db/queries/cleanup.d.ts +1 -1
- package/dist/form-draft/db/queries/cleanup.js +4 -3
- package/dist/form-draft/entity.d.ts +2 -0
- package/dist/form-draft/handlers/cleanup.job.d.ts +5 -1
- package/dist/form-draft/handlers/cleanup.job.js +11 -5
- package/dist/inbound-mail-foundation/entities.d.ts +25 -0
- package/dist/inbound-mail-foundation/projection.d.ts +20 -0
- package/dist/jobs/handlers/list.query.d.ts +2 -1
- package/dist/jobs/handlers/list.query.js +39 -14
- package/dist/jobs/i18n.js +1 -0
- package/dist/jobs/job-run-table.d.ts +6 -0
- package/dist/ledger/entity.d.ts +24 -8
- package/dist/ledger/entity.js +4 -0
- package/dist/ledger/schemas.js +8 -2
- package/dist/managed-pages/feature.js +28 -10
- package/dist/managed-pages/table.d.ts +12 -0
- package/dist/notes-history/entity.d.ts +12 -0
- package/dist/notes-history/feature.js +2 -10
- package/dist/personal-access-tokens/schema/api-token.d.ts +6 -0
- package/dist/personal-access-tokens/screens.js +14 -3
- package/dist/renderer-foundation/types.d.ts +1 -0
- package/dist/renderer-simple/feature.js +4 -6
- package/dist/renderer-simple/index.d.ts +1 -0
- package/dist/renderer-simple/mail-blocks.d.ts +21 -0
- package/dist/renderer-simple/mail-blocks.js +107 -0
- package/dist/renderer-simple/simple-renderer.js +21 -3
- package/dist/secrets/table.d.ts +1 -0
- package/dist/seo/feature.js +28 -6
- package/dist/sessions/schema/user-session.d.ts +4 -0
- package/dist/shared/assert-no-where-rule-in-ownership-write.d.ts +2 -0
- package/dist/shared/assert-no-where-rule-in-ownership-write.js +18 -0
- package/dist/shared/index.d.ts +2 -1
- package/dist/shared/index.js +2 -1
- package/dist/shared/rate-limited-text-response.d.ts +2 -0
- package/dist/shared/rate-limited-text-response.js +9 -0
- package/dist/tags/entity.d.ts +9 -0
- package/dist/tags/feature.js +2 -10
- package/dist/template-resolver/table.d.ts +48 -30
- package/dist/template-resolver/user-content-table.d.ts +34 -20
- package/dist/tenant/decrypt-tenant-names.d.ts +3 -0
- package/dist/tenant/decrypt-tenant-names.js +10 -0
- package/dist/tenant/handlers/me.query.js +5 -1
- package/dist/tenant/handlers/memberships.query.js +3 -2
- package/dist/tenant/handlers/tenant-directory.query.js +9 -3
- package/dist/tenant/index.d.ts +1 -0
- package/dist/tenant/index.js +1 -0
- package/dist/tenant/invitation-table.d.ts +16 -8
- package/dist/tenant/membership-table.d.ts +4 -0
- package/dist/tenant/schema/tenant.d.ts +18 -10
- package/dist/tenant/schema/tenant.js +4 -6
- package/dist/tenant/screens.js +1 -1
- package/dist/tenant-handover/move-entity-graph.js +11 -18
- package/dist/tenant-lifecycle/feature.js +1 -0
- package/dist/tenant-lifecycle/run-tenant-destroy.d.ts +3 -0
- package/dist/tenant-lifecycle/run-tenant-destroy.js +2 -0
- package/dist/tenant-lifecycle/stages.d.ts +2 -0
- package/dist/tenant-lifecycle/stages.js +7 -1
- package/dist/tier-engine/entity.d.ts +2 -0
- package/dist/tier-engine/index.d.ts +1 -0
- package/dist/tier-engine/index.js +1 -0
- package/dist/tier-engine/tier-option-gate.d.ts +32 -0
- package/dist/tier-engine/tier-option-gate.js +62 -0
- package/dist/tier-engine/tier-resolver.d.ts +2 -0
- package/dist/user/handlers/list.query.js +2 -2
- package/dist/user/schema/user.d.ts +22 -8
- package/dist/user-data-rights/boot-checks.js +5 -0
- package/dist/user-data-rights/handlers/confirm-deletion-by-token.write.js +2 -2
- package/dist/user-data-rights/schema/download-attempt.d.ts +14 -0
- package/dist/user-data-rights/schema/download-token.d.ts +8 -0
- package/dist/user-data-rights/schema/export-job.d.ts +16 -8
- package/dist/user-data-rights-defaults/hooks/delivery-attempt.userdata-hook.js +25 -6
- package/dist/waitlist/entity.d.ts +22 -6
- package/dist/workflow-runner/event-trigger.js +6 -3
- package/dist/workflow-runner/handlers/resume-run.write.js +3 -0
- package/package.json +10 -10
- package/src/agent-tools/changes.json +12 -0
- package/src/audit/changes.json +6 -0
- package/src/cap-counter/changes.json +28 -0
- package/src/delivery/changes.json +12 -0
- package/src/derivatives-sharp/changes.json +6 -0
- package/src/form-draft/changes.json +7 -1
- package/src/legal-pages/changes.json +6 -0
- package/src/managed-pages/changes.json +6 -0
- package/src/renderer-simple/changes.json +6 -0
- package/src/seo/changes.json +5 -0
- package/src/tenant/changes.json +7 -0
- package/src/tenant-handover/changes.json +6 -0
- package/src/tier-engine/changes.json +6 -0
- package/src/workflow-runner/changes.json +5 -0
- package/dist/shared/has-where-rule.d.ts +0 -2
- package/dist/shared/has-where-rule.js +0 -10
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import { i18nKey, MAX_LIST_LIMIT, } from "@cosmicdrift/kumiko-framework/engine";
|
|
2
|
-
import {
|
|
2
|
+
import { ACTION_FORM_ENTITY } from "@cosmicdrift/kumiko-framework/ui-types";
|
|
3
|
+
import { fieldOptionLabelKeyPrefix } from "@cosmicdrift/kumiko-headless";
|
|
4
|
+
import { PAT_FEATURE, PAT_MINT_SCREEN_ID, PAT_SCREEN_ID, PatHandlers, PatQueries, } from "./constants.js";
|
|
3
5
|
const PAT_STATUS_OPTION_KEY_PREFIX = "pat.list.status.";
|
|
6
|
+
// Same keys the mint form's `scopes` multiSelect options resolve through
|
|
7
|
+
// (see patScopeOptionTranslations), so list and form show identical labels.
|
|
8
|
+
const PAT_SCOPE_OPTION_KEY_PREFIX = fieldOptionLabelKeyPrefix(PAT_FEATURE, ACTION_FORM_ENTITY, "scopes");
|
|
4
9
|
// Both screens render only for tenants whose tier includes the feature; the
|
|
5
10
|
// availability query is rejected by the feature gate otherwise.
|
|
6
11
|
const PAT_SCREEN_VISIBLE_WHEN = {
|
|
@@ -20,11 +25,17 @@ export const patListScreen = {
|
|
|
20
25
|
defaultSort: { field: "createdAt", dir: "desc" },
|
|
21
26
|
columns: [
|
|
22
27
|
{ field: "name", label: i18nKey("pat.list.col.name") },
|
|
23
|
-
{ field: "prefix", label: i18nKey("pat.list.col.prefix") },
|
|
24
|
-
{
|
|
28
|
+
{ field: "prefix", label: i18nKey("pat.list.col.prefix"), sortable: false },
|
|
29
|
+
{
|
|
30
|
+
field: "scopes",
|
|
31
|
+
label: i18nKey("pat.list.col.scopes"),
|
|
32
|
+
sortable: false,
|
|
33
|
+
renderer: { format: "enumOption", keyPrefix: PAT_SCOPE_OPTION_KEY_PREFIX },
|
|
34
|
+
},
|
|
25
35
|
{
|
|
26
36
|
field: "status",
|
|
27
37
|
label: i18nKey("pat.list.col.status"),
|
|
38
|
+
sortable: false,
|
|
28
39
|
renderer: { format: "enumOption", keyPrefix: PAT_STATUS_OPTION_KEY_PREFIX },
|
|
29
40
|
},
|
|
30
41
|
{
|
|
@@ -16,12 +16,10 @@ export async function adaptToFoundation(req, ctx, renderer = simpleRenderer) {
|
|
|
16
16
|
throw new RendererError(`renderer-simple supports only kind="notification", got "${req.kind}"`, "invalid_payload");
|
|
17
17
|
}
|
|
18
18
|
const variables = await resolveNotificationVariables(req, ctx);
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
});
|
|
24
|
-
return { kind: "notification", html };
|
|
19
|
+
const input = { template: req.payload.template ?? "", variables, locale: req.payload.locale };
|
|
20
|
+
const html = await renderer.render(input);
|
|
21
|
+
const text = renderer.renderText ? await renderer.renderText(input) : undefined;
|
|
22
|
+
return text === undefined ? { kind: "notification", html } : { kind: "notification", html, text };
|
|
25
23
|
}
|
|
26
24
|
export function createRendererSimpleFeature(options) {
|
|
27
25
|
const renderer = options?.mailBranding
|
|
@@ -1,2 +1,3 @@
|
|
|
1
1
|
export { createRendererSimpleFeature, type RendererSimpleOptions } from "./feature.js";
|
|
2
|
+
export type { MailBadge, MailChip, MailStage, MailStageState, MailTone } from "./mail-blocks.js";
|
|
2
3
|
export { createSimpleRenderer, type LocalizedText, type MailBranding, simpleRenderer, } from "./simple-renderer.js";
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export type MailTone = "neutral" | "info" | "success" | "warning" | "danger";
|
|
2
|
+
export type MailStageState = "done" | "current" | "upcoming";
|
|
3
|
+
export type MailBadge = {
|
|
4
|
+
readonly label: string;
|
|
5
|
+
readonly tone?: MailTone;
|
|
6
|
+
};
|
|
7
|
+
export type MailStage = {
|
|
8
|
+
readonly label: string;
|
|
9
|
+
readonly state: MailStageState;
|
|
10
|
+
};
|
|
11
|
+
export type MailChip = {
|
|
12
|
+
readonly label: string;
|
|
13
|
+
readonly tone?: MailTone;
|
|
14
|
+
};
|
|
15
|
+
export declare function parseBadge(value: unknown): MailBadge | undefined;
|
|
16
|
+
export declare function renderBadge(badge: MailBadge): string;
|
|
17
|
+
export declare function renderChips(chips: unknown): string;
|
|
18
|
+
export declare function renderStages(stages: unknown, primaryColor: string): string;
|
|
19
|
+
export declare function badgeText(badge: MailBadge): string;
|
|
20
|
+
export declare function stagesText(stages: unknown): string;
|
|
21
|
+
export declare function chipsText(chips: unknown): string;
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { escapeHtml, escapeHtmlAttr } from "@cosmicdrift/kumiko-headless";
|
|
2
|
+
// Literal types (as const) keep the colors compile-time known, so they can go into style
|
|
3
|
+
// attributes without escaping.
|
|
4
|
+
const TONE_COLORS = {
|
|
5
|
+
neutral: { bg: "#f3f4f6", fg: "#374151" },
|
|
6
|
+
info: { bg: "#dbeafe", fg: "#1e40af" },
|
|
7
|
+
success: { bg: "#dcfce7", fg: "#166534" },
|
|
8
|
+
warning: { bg: "#fef3c7", fg: "#92400e" },
|
|
9
|
+
danger: { bg: "#fee2e2", fg: "#991b1b" },
|
|
10
|
+
};
|
|
11
|
+
const STAGE_STATES = ["done", "current", "upcoming"];
|
|
12
|
+
const STAGE_DONE_COLOR = "#16a34a";
|
|
13
|
+
const STAGE_UPCOMING_BG = "#e5e7eb";
|
|
14
|
+
const STAGE_UPCOMING_FG = "#9ca3af";
|
|
15
|
+
function isRecord(value) {
|
|
16
|
+
return typeof value === "object" && value !== null;
|
|
17
|
+
}
|
|
18
|
+
function isMailTone(value) {
|
|
19
|
+
return typeof value === "string" && Object.hasOwn(TONE_COLORS, value);
|
|
20
|
+
}
|
|
21
|
+
function toneColors(tone) {
|
|
22
|
+
return TONE_COLORS[isMailTone(tone) ? tone : "neutral"];
|
|
23
|
+
}
|
|
24
|
+
function isStageState(value) {
|
|
25
|
+
return STAGE_STATES.some((state) => state === value);
|
|
26
|
+
}
|
|
27
|
+
// Template data is runtime data (variables of a stored template): entries that do not match are
|
|
28
|
+
// skipped instead of failing the whole mail.
|
|
29
|
+
function isStage(value) {
|
|
30
|
+
return isRecord(value) && typeof value["label"] === "string" && isStageState(value["state"]);
|
|
31
|
+
}
|
|
32
|
+
function isChip(value) {
|
|
33
|
+
return isRecord(value) && typeof value["label"] === "string";
|
|
34
|
+
}
|
|
35
|
+
export function parseBadge(value) {
|
|
36
|
+
return isChip(value) && value.label !== "" ? value : undefined;
|
|
37
|
+
}
|
|
38
|
+
function validStages(value) {
|
|
39
|
+
return Array.isArray(value) ? value.filter(isStage) : [];
|
|
40
|
+
}
|
|
41
|
+
function validChips(value) {
|
|
42
|
+
return Array.isArray(value) ? value.filter(isChip) : [];
|
|
43
|
+
}
|
|
44
|
+
function pill(chip, margin) {
|
|
45
|
+
const { bg, fg } = toneColors(chip.tone);
|
|
46
|
+
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>`;
|
|
47
|
+
}
|
|
48
|
+
export function renderBadge(badge) {
|
|
49
|
+
return `<p style="margin:0 0 12px">${pill(badge, "0")}</p>`;
|
|
50
|
+
}
|
|
51
|
+
export function renderChips(chips) {
|
|
52
|
+
const valid = validChips(chips);
|
|
53
|
+
if (valid.length === 0)
|
|
54
|
+
return "";
|
|
55
|
+
return `<p style="margin:0 0 16px">${valid.map((chip) => pill(chip, "0 6px 6px 0")).join("")}</p>`;
|
|
56
|
+
}
|
|
57
|
+
function stageMarker(stage, position, primaryColor) {
|
|
58
|
+
const base = "display:inline-block;width:24px;height:24px;line-height:24px;border-radius:12px;text-align:center;font-size:12px;font-weight:700";
|
|
59
|
+
const primary = escapeHtmlAttr(primaryColor);
|
|
60
|
+
if (stage.state === "done") {
|
|
61
|
+
return `<span style="${base};background:${STAGE_DONE_COLOR};color:#fff">✓</span>`;
|
|
62
|
+
}
|
|
63
|
+
if (stage.state === "current") {
|
|
64
|
+
return `<span style="${base};background:#fff;border:2px solid ${primary};line-height:20px;width:20px;height:20px;color:${primary}">${position}</span>`;
|
|
65
|
+
}
|
|
66
|
+
return `<span style="${base};background:${STAGE_UPCOMING_BG};color:${STAGE_UPCOMING_FG}">${position}</span>`;
|
|
67
|
+
}
|
|
68
|
+
function stageCell(stage, position, primaryColor) {
|
|
69
|
+
const labelColor = stage.state === "upcoming" ? STAGE_UPCOMING_FG : "#111";
|
|
70
|
+
const weight = stage.state === "current" ? 700 : 400;
|
|
71
|
+
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>`;
|
|
72
|
+
}
|
|
73
|
+
function stageConnector(previous) {
|
|
74
|
+
const color = previous.state === "done" ? STAGE_DONE_COLOR : STAGE_UPCOMING_BG;
|
|
75
|
+
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>`;
|
|
76
|
+
}
|
|
77
|
+
export function renderStages(stages, primaryColor) {
|
|
78
|
+
const valid = validStages(stages);
|
|
79
|
+
if (valid.length === 0)
|
|
80
|
+
return "";
|
|
81
|
+
const cellsHtml = valid
|
|
82
|
+
.map((stage, index) => {
|
|
83
|
+
const previous = valid[index - 1];
|
|
84
|
+
const connector = previous ? stageConnector(previous) : "";
|
|
85
|
+
return `${connector}${stageCell(stage, index + 1, primaryColor)}`;
|
|
86
|
+
})
|
|
87
|
+
.join("");
|
|
88
|
+
return `<table role="presentation" cellpadding="0" cellspacing="0" border="0" style="margin:0 0 16px"><tr>${cellsHtml}</tr></table>`;
|
|
89
|
+
}
|
|
90
|
+
const STAGE_TEXT_MARKER = {
|
|
91
|
+
done: "✓",
|
|
92
|
+
current: "▶",
|
|
93
|
+
upcoming: "○",
|
|
94
|
+
};
|
|
95
|
+
export function badgeText(badge) {
|
|
96
|
+
return `[${badge.label}]`;
|
|
97
|
+
}
|
|
98
|
+
export function stagesText(stages) {
|
|
99
|
+
return validStages(stages)
|
|
100
|
+
.map((stage) => `${STAGE_TEXT_MARKER[stage.state]} ${stage.label}`)
|
|
101
|
+
.join(" → ");
|
|
102
|
+
}
|
|
103
|
+
export function chipsText(chips) {
|
|
104
|
+
return validChips(chips)
|
|
105
|
+
.map((chip) => chip.label)
|
|
106
|
+
.join(" · ");
|
|
107
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { escapeHtml, escapeHtmlAttr } from "@cosmicdrift/kumiko-headless";
|
|
2
2
|
import { renderSafeMarkdown } from "../page-render/index.js";
|
|
3
|
+
import { badgeText, chipsText, parseBadge, renderBadge, renderChips, renderStages, stagesText, } from "./mail-blocks.js";
|
|
3
4
|
const DEFAULT_PRIMARY_COLOR = "#2563eb";
|
|
4
5
|
const HEX_COLOR_PATTERN = /^#(?:[0-9a-fA-F]{3,4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$/;
|
|
5
6
|
function isHttpUrl(value) {
|
|
@@ -107,6 +108,7 @@ function templateContent(variables) {
|
|
|
107
108
|
const data = variables; // @cast-boundary render-helper
|
|
108
109
|
// Without structured fields, title + body become the header and a single text section.
|
|
109
110
|
return {
|
|
111
|
+
badge: parseBadge(data.badge),
|
|
110
112
|
header: data.header ?? data.title,
|
|
111
113
|
sections: data.sections ?? (data.body ? [{ text: data.body }] : undefined),
|
|
112
114
|
footer: data.footer,
|
|
@@ -122,6 +124,10 @@ function sectionText(section) {
|
|
|
122
124
|
return section.markdown;
|
|
123
125
|
if ("button" in section)
|
|
124
126
|
return `${section.button.label}: ${section.button.url}`;
|
|
127
|
+
if ("stages" in section)
|
|
128
|
+
return stagesText(section.stages);
|
|
129
|
+
if ("chips" in section)
|
|
130
|
+
return chipsText(section.chips);
|
|
125
131
|
return "";
|
|
126
132
|
}
|
|
127
133
|
function renderSection(section, primaryColor) {
|
|
@@ -138,6 +144,10 @@ function renderSection(section, primaryColor) {
|
|
|
138
144
|
if ("button" in section) {
|
|
139
145
|
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>`;
|
|
140
146
|
}
|
|
147
|
+
if ("stages" in section)
|
|
148
|
+
return renderStages(section.stages, primaryColor);
|
|
149
|
+
if ("chips" in section)
|
|
150
|
+
return renderChips(section.chips);
|
|
141
151
|
return "";
|
|
142
152
|
}
|
|
143
153
|
// Simple Renderer: turns structured email template data into HTML with inline CSS.
|
|
@@ -150,11 +160,13 @@ export function createSimpleRenderer(branding) {
|
|
|
150
160
|
return {
|
|
151
161
|
name: "simple",
|
|
152
162
|
async render(input) {
|
|
153
|
-
const { header, sections, footer } = templateContent(input.variables);
|
|
163
|
+
const { badge, header, sections, footer } = templateContent(input.variables);
|
|
154
164
|
const parts = [];
|
|
155
165
|
parts.push('<!DOCTYPE html><html><body style="margin:0;padding:0;font-family:sans-serif">');
|
|
156
166
|
parts.push('<div style="max-width:600px;margin:0 auto;padding:24px">');
|
|
157
167
|
parts.push(renderBrandingHeader(branding, primaryColor, logoUrl));
|
|
168
|
+
if (badge)
|
|
169
|
+
parts.push(renderBadge(badge));
|
|
158
170
|
if (header) {
|
|
159
171
|
parts.push(`<h1 style="margin:0 0 24px;color:#111;font-size:20px;font-weight:600">${escapeHtml(header)}</h1>`);
|
|
160
172
|
}
|
|
@@ -171,9 +183,15 @@ export function createSimpleRenderer(branding) {
|
|
|
171
183
|
return parts.join("");
|
|
172
184
|
},
|
|
173
185
|
async renderText(input) {
|
|
174
|
-
const { header, sections, footer } = templateContent(input.variables);
|
|
186
|
+
const { badge, header, sections, footer } = templateContent(input.variables);
|
|
175
187
|
const brandingFooter = brandingFooterLines(branding, input.locale).join("\n");
|
|
176
|
-
return [
|
|
188
|
+
return [
|
|
189
|
+
badge ? badgeText(badge) : undefined,
|
|
190
|
+
header,
|
|
191
|
+
...(sections ?? []).map(sectionText),
|
|
192
|
+
footer,
|
|
193
|
+
brandingFooter,
|
|
194
|
+
]
|
|
177
195
|
.filter((block) => block !== undefined && block !== "")
|
|
178
196
|
.join("\n\n");
|
|
179
197
|
},
|
package/dist/secrets/table.d.ts
CHANGED
|
@@ -8,6 +8,7 @@ export declare const tenantSecretEntity: import("@cosmicdrift/kumiko-types/field
|
|
|
8
8
|
readonly type: "text";
|
|
9
9
|
readonly description?: string;
|
|
10
10
|
readonly maxLength?: number;
|
|
11
|
+
readonly minLength?: number;
|
|
11
12
|
readonly required?: boolean;
|
|
12
13
|
readonly searchable?: boolean;
|
|
13
14
|
readonly sortable?: boolean;
|
package/dist/seo/feature.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
// kumiko-lint-ignore app-feature-structure Phase-3 conversion tracked in #2312 (feature.ts > 300 lines)
|
|
2
2
|
import { computeRevisionEtag } from "@cosmicdrift/kumiko-framework/api";
|
|
3
3
|
import { defineFeature, SYSTEM_TENANT_ID, } from "@cosmicdrift/kumiko-framework/engine";
|
|
4
|
+
import { RateLimitError } from "@cosmicdrift/kumiko-framework/errors";
|
|
4
5
|
import { LEGAL_ROUTES } from "../legal-pages/index.js";
|
|
5
6
|
import { cachedSecurePageResponse } from "../page-render/index.js";
|
|
7
|
+
import { rateLimitedTextResponse } from "../shared/index.js";
|
|
6
8
|
import { SEO_CONFIG_KEYS, SEO_DEFAULT_PATHS } from "./constants.js";
|
|
7
9
|
import { seoConfigQuery } from "./handlers/seo-config.query.js";
|
|
8
10
|
import { buildLlmsTxt } from "./llms-txt.js";
|
|
@@ -62,7 +64,10 @@ async function gatherEntries(opts, systemQuery, origin, host) {
|
|
|
62
64
|
});
|
|
63
65
|
}
|
|
64
66
|
}
|
|
65
|
-
catch {
|
|
67
|
+
catch (error) {
|
|
68
|
+
// A rate limit must not look like "pages removed" to a crawler.
|
|
69
|
+
if (error instanceof RateLimitError)
|
|
70
|
+
throw error;
|
|
66
71
|
// managed-pages unreachable/not mounted — degrade to callback-only entries.
|
|
67
72
|
}
|
|
68
73
|
}
|
|
@@ -113,7 +118,15 @@ export function createSeoFeature(opts) {
|
|
|
113
118
|
anonymous: true,
|
|
114
119
|
handler: async (c, { systemQuery }) => {
|
|
115
120
|
const { origin, host } = requestHost(c);
|
|
116
|
-
|
|
121
|
+
let entries;
|
|
122
|
+
try {
|
|
123
|
+
entries = await gatherEntries(opts, systemQuery, origin, host);
|
|
124
|
+
}
|
|
125
|
+
catch (error) {
|
|
126
|
+
if (error instanceof RateLimitError)
|
|
127
|
+
return rateLimitedTextResponse(error);
|
|
128
|
+
throw error;
|
|
129
|
+
}
|
|
117
130
|
const xml = buildSitemapXml(entries);
|
|
118
131
|
const etag = computeRevisionEtag([host, xml]);
|
|
119
132
|
return cachedSecurePageResponse(c.req.raw, {
|
|
@@ -133,10 +146,19 @@ export function createSeoFeature(opts) {
|
|
|
133
146
|
const tenantId = opts.managedPages
|
|
134
147
|
? ((await opts.managedPages.resolveApexTenant(host)) ?? SYSTEM_TENANT_ID)
|
|
135
148
|
: SYSTEM_TENANT_ID;
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
149
|
+
let entries;
|
|
150
|
+
let seoConfig;
|
|
151
|
+
try {
|
|
152
|
+
[entries, seoConfig] = await Promise.all([
|
|
153
|
+
gatherEntries(opts, systemQuery, origin, host),
|
|
154
|
+
readSeoConfig(systemQuery, tenantId),
|
|
155
|
+
]);
|
|
156
|
+
}
|
|
157
|
+
catch (error) {
|
|
158
|
+
if (error instanceof RateLimitError)
|
|
159
|
+
return rateLimitedTextResponse(error);
|
|
160
|
+
throw error;
|
|
161
|
+
}
|
|
140
162
|
const sections = entries.length > 0
|
|
141
163
|
? [
|
|
142
164
|
{
|
|
@@ -3,6 +3,7 @@ export declare const userSessionEntity: import("@cosmicdrift/kumiko-types/fields
|
|
|
3
3
|
readonly type: "text";
|
|
4
4
|
readonly description?: string;
|
|
5
5
|
readonly maxLength?: number;
|
|
6
|
+
readonly minLength?: number;
|
|
6
7
|
readonly required?: boolean;
|
|
7
8
|
readonly searchable?: boolean;
|
|
8
9
|
readonly sortable?: boolean;
|
|
@@ -24,6 +25,7 @@ export declare const userSessionEntity: import("@cosmicdrift/kumiko-types/fields
|
|
|
24
25
|
readonly type: "text";
|
|
25
26
|
readonly description?: string;
|
|
26
27
|
readonly maxLength?: number;
|
|
28
|
+
readonly minLength?: number;
|
|
27
29
|
readonly required?: boolean;
|
|
28
30
|
readonly searchable?: boolean;
|
|
29
31
|
readonly sortable?: boolean;
|
|
@@ -105,6 +107,7 @@ export declare const userSessionEntity: import("@cosmicdrift/kumiko-types/fields
|
|
|
105
107
|
readonly type: "text";
|
|
106
108
|
readonly description?: string;
|
|
107
109
|
readonly maxLength?: number;
|
|
110
|
+
readonly minLength?: number;
|
|
108
111
|
readonly required?: boolean;
|
|
109
112
|
readonly searchable?: boolean;
|
|
110
113
|
readonly sortable?: boolean;
|
|
@@ -126,6 +129,7 @@ export declare const userSessionEntity: import("@cosmicdrift/kumiko-types/fields
|
|
|
126
129
|
readonly type: "text";
|
|
127
130
|
readonly description?: string;
|
|
128
131
|
readonly maxLength?: number;
|
|
132
|
+
readonly minLength?: number;
|
|
129
133
|
readonly required?: boolean;
|
|
130
134
|
readonly searchable?: boolean;
|
|
131
135
|
readonly sortable?: boolean;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// Boot validation rejects `{ kind: "where" }` in access.write too (fw#2626);
|
|
2
|
+
// failing at factory time names the option instead of an entity scope.
|
|
3
|
+
export function assertNoWhereRuleInOwnershipWrite(factoryName, writeOwnership) {
|
|
4
|
+
// skip: no write ownership declared, nothing to validate
|
|
5
|
+
if (!writeOwnership)
|
|
6
|
+
return;
|
|
7
|
+
const hasWhereRule = Object.values(writeOwnership).some((rule) => rule !== "all" && rule.kind === "where");
|
|
8
|
+
// skip: no where-rule present, the option is valid
|
|
9
|
+
if (!hasWhereRule)
|
|
10
|
+
return;
|
|
11
|
+
throw new Error(`${factoryName}({ ownership }): ownership.write must not contain a ` +
|
|
12
|
+
'`{ kind: "where" }` rule — where-rules are evaluated only at the SQL ' +
|
|
13
|
+
"layer (the read path, via buildOwnershipClause). Write paths that " +
|
|
14
|
+
"consult access.write (userCanCreateFieldRow/userCanWriteFieldRow) can't " +
|
|
15
|
+
"evaluate them, so such a rule can only ever deny — boot validation " +
|
|
16
|
+
"rejects it too (fw#2626). Use a `from()` rule for ownership.write, or " +
|
|
17
|
+
"leave it unset.");
|
|
18
|
+
}
|
package/dist/shared/index.d.ts
CHANGED
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
export { collectErasureFailure, throwIfErasureFailed } from "./assert-erased.js";
|
|
2
|
+
export { assertNoWhereRuleInOwnershipWrite } from "./assert-no-where-rule-in-ownership-write.js";
|
|
2
3
|
export { type ChunkedMigrationOptions, type ChunkedMigrationResult, type ChunkedMigrationStopReason, type MigrationRowOutcome, runChunkedMigration, } from "./chunked-entity-migration.js";
|
|
3
4
|
export { classifyStoredEnvelope, type StoredEnvelopeClassification, } from "./classify-stored-envelope.js";
|
|
4
5
|
export { decryptStoredPii } from "./decrypt-stored-pii.js";
|
|
5
6
|
export { encryptForDirectWrite } from "./encrypt-for-direct-write.js";
|
|
6
7
|
export { entitiesOf } from "./entities-of.js";
|
|
7
8
|
export { isWithinGracePeriod } from "./grace-period.js";
|
|
8
|
-
export { hasWhereRule } from "./has-where-rule.js";
|
|
9
9
|
export { isIdentityV3Hash, verifyIdentityV3Hash } from "./identity-v3-hash.js";
|
|
10
10
|
export { isTenantDb } from "./is-tenant-db.js";
|
|
11
11
|
export { createLockoutCounter, type LockoutCounterState } from "./lockout-counter.js";
|
|
12
12
|
export { mapWithConcurrency } from "./map-with-concurrency.js";
|
|
13
13
|
export { denyUnlessJoinRowParentVisible, joinRowParentIsVisible, parentRowIsVisible, } from "./parent-visibility.js";
|
|
14
14
|
export { hashPassword, verifyDummyPassword, verifyPassword } from "./password-hashing.js";
|
|
15
|
+
export { rateLimitedTextResponse } from "./rate-limited-text-response.js";
|
|
15
16
|
export { redactBotTokens, redactEmailAddresses, redactErrorText, redactUrls, } from "./redact.js";
|
|
16
17
|
export { type RowBoundGrantResult, redeemRowBoundGrant, signRowBoundGrant, } from "./row-bound-grant.js";
|
|
17
18
|
export { runInSubTransaction } from "./run-in-sub-transaction.js";
|
package/dist/shared/index.js
CHANGED
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
export { collectErasureFailure, throwIfErasureFailed } from "./assert-erased.js";
|
|
2
|
+
export { assertNoWhereRuleInOwnershipWrite } from "./assert-no-where-rule-in-ownership-write.js";
|
|
2
3
|
export { runChunkedMigration, } from "./chunked-entity-migration.js";
|
|
3
4
|
export { classifyStoredEnvelope, } from "./classify-stored-envelope.js";
|
|
4
5
|
export { decryptStoredPii } from "./decrypt-stored-pii.js";
|
|
5
6
|
export { encryptForDirectWrite } from "./encrypt-for-direct-write.js";
|
|
6
7
|
export { entitiesOf } from "./entities-of.js";
|
|
7
8
|
export { isWithinGracePeriod } from "./grace-period.js";
|
|
8
|
-
export { hasWhereRule } from "./has-where-rule.js";
|
|
9
9
|
export { isIdentityV3Hash, verifyIdentityV3Hash } from "./identity-v3-hash.js";
|
|
10
10
|
export { isTenantDb } from "./is-tenant-db.js";
|
|
11
11
|
export { createLockoutCounter } from "./lockout-counter.js";
|
|
12
12
|
export { mapWithConcurrency } from "./map-with-concurrency.js";
|
|
13
13
|
export { denyUnlessJoinRowParentVisible, joinRowParentIsVisible, parentRowIsVisible, } from "./parent-visibility.js";
|
|
14
14
|
export { hashPassword, verifyDummyPassword, verifyPassword } from "./password-hashing.js";
|
|
15
|
+
export { rateLimitedTextResponse } from "./rate-limited-text-response.js";
|
|
15
16
|
export { redactBotTokens, redactEmailAddresses, redactErrorText, redactUrls, } from "./redact.js";
|
|
16
17
|
export { redeemRowBoundGrant, signRowBoundGrant, } from "./row-bound-grant.js";
|
|
17
18
|
export { runInSubTransaction } from "./run-in-sub-transaction.js";
|
package/dist/tags/entity.d.ts
CHANGED
|
@@ -4,6 +4,7 @@ export declare const tagEntity: EntityDefinition<{
|
|
|
4
4
|
readonly type: "text";
|
|
5
5
|
readonly description?: string;
|
|
6
6
|
readonly maxLength?: number;
|
|
7
|
+
readonly minLength?: number;
|
|
7
8
|
readonly required?: boolean;
|
|
8
9
|
readonly searchable?: boolean;
|
|
9
10
|
readonly sortable?: boolean;
|
|
@@ -25,6 +26,7 @@ export declare const tagEntity: EntityDefinition<{
|
|
|
25
26
|
readonly type: "text";
|
|
26
27
|
readonly description?: string;
|
|
27
28
|
readonly maxLength?: number;
|
|
29
|
+
readonly minLength?: number;
|
|
28
30
|
readonly required?: boolean;
|
|
29
31
|
readonly searchable?: boolean;
|
|
30
32
|
readonly sortable?: boolean;
|
|
@@ -46,6 +48,7 @@ export declare const tagEntity: EntityDefinition<{
|
|
|
46
48
|
readonly type: "text";
|
|
47
49
|
readonly description?: string;
|
|
48
50
|
readonly maxLength?: number;
|
|
51
|
+
readonly minLength?: number;
|
|
49
52
|
readonly required?: boolean;
|
|
50
53
|
readonly searchable?: boolean;
|
|
51
54
|
readonly sortable?: boolean;
|
|
@@ -69,6 +72,7 @@ export declare function createTagAssignmentEntity(access?: EntityDefinition["acc
|
|
|
69
72
|
readonly type: "text";
|
|
70
73
|
readonly description?: string;
|
|
71
74
|
readonly maxLength?: number;
|
|
75
|
+
readonly minLength?: number;
|
|
72
76
|
readonly required?: boolean;
|
|
73
77
|
readonly searchable?: boolean;
|
|
74
78
|
readonly sortable?: boolean;
|
|
@@ -90,6 +94,7 @@ export declare function createTagAssignmentEntity(access?: EntityDefinition["acc
|
|
|
90
94
|
readonly type: "text";
|
|
91
95
|
readonly description?: string;
|
|
92
96
|
readonly maxLength?: number;
|
|
97
|
+
readonly minLength?: number;
|
|
93
98
|
readonly required?: boolean;
|
|
94
99
|
readonly searchable?: boolean;
|
|
95
100
|
readonly sortable?: boolean;
|
|
@@ -111,6 +116,7 @@ export declare function createTagAssignmentEntity(access?: EntityDefinition["acc
|
|
|
111
116
|
readonly type: "text";
|
|
112
117
|
readonly description?: string;
|
|
113
118
|
readonly maxLength?: number;
|
|
119
|
+
readonly minLength?: number;
|
|
114
120
|
readonly required?: boolean;
|
|
115
121
|
readonly searchable?: boolean;
|
|
116
122
|
readonly sortable?: boolean;
|
|
@@ -134,6 +140,7 @@ export declare const tagAssignmentEntity: EntityDefinition<{
|
|
|
134
140
|
readonly type: "text";
|
|
135
141
|
readonly description?: string;
|
|
136
142
|
readonly maxLength?: number;
|
|
143
|
+
readonly minLength?: number;
|
|
137
144
|
readonly required?: boolean;
|
|
138
145
|
readonly searchable?: boolean;
|
|
139
146
|
readonly sortable?: boolean;
|
|
@@ -155,6 +162,7 @@ export declare const tagAssignmentEntity: EntityDefinition<{
|
|
|
155
162
|
readonly type: "text";
|
|
156
163
|
readonly description?: string;
|
|
157
164
|
readonly maxLength?: number;
|
|
165
|
+
readonly minLength?: number;
|
|
158
166
|
readonly required?: boolean;
|
|
159
167
|
readonly searchable?: boolean;
|
|
160
168
|
readonly sortable?: boolean;
|
|
@@ -176,6 +184,7 @@ export declare const tagAssignmentEntity: EntityDefinition<{
|
|
|
176
184
|
readonly type: "text";
|
|
177
185
|
readonly description?: string;
|
|
178
186
|
readonly maxLength?: number;
|
|
187
|
+
readonly minLength?: number;
|
|
179
188
|
readonly required?: boolean;
|
|
180
189
|
readonly searchable?: boolean;
|
|
181
190
|
readonly sortable?: boolean;
|
package/dist/tags/feature.js
CHANGED
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
// entityList/entityEdit catalog screens (legacy QNs stay for TagManager).
|
|
19
19
|
// Deferred: optional host-projection decoration (`wireTagsFor`), search indexing.
|
|
20
20
|
import { defineEntityDetailHandler, defineEntityListHandler, defineEntityUpdateHandler, defineFeature, } from "@cosmicdrift/kumiko-framework/engine";
|
|
21
|
-
import {
|
|
21
|
+
import { assertNoWhereRuleInOwnershipWrite } from "../shared/index.js";
|
|
22
22
|
import { DEFAULT_TAG_ACCESS, TAGS_FEATURE_NAME } from "./constants.js";
|
|
23
23
|
import { createTagAssignmentEntity, tagEntity } from "./entity.js";
|
|
24
24
|
import { createTagAssignmentExecutor } from "./executor.js";
|
|
@@ -109,15 +109,7 @@ export function createTagsFeature(opts = {}) {
|
|
|
109
109
|
"assignment row. Omit `parents` to keep every registered entity " +
|
|
110
110
|
"admissible as a host instead.");
|
|
111
111
|
}
|
|
112
|
-
|
|
113
|
-
throw new Error("createTagsFeature({ ownership }): ownership.write must not contain a " +
|
|
114
|
-
'`{ kind: "where" }` rule — where-rules are evaluated only at the SQL ' +
|
|
115
|
-
"layer (the read path, via buildOwnershipClause). Write paths that " +
|
|
116
|
-
"consult access.write (userCanCreateFieldRow/userCanWriteFieldRow) can't " +
|
|
117
|
-
"evaluate them, so such a rule can only ever deny — boot validation " +
|
|
118
|
-
"rejects it too (fw#2626). Use a `from()` rule for ownership.write, or " +
|
|
119
|
-
"leave it unset.");
|
|
120
|
-
}
|
|
112
|
+
assertNoWhereRuleInOwnershipWrite("createTagsFeature", opts.ownership?.write);
|
|
121
113
|
const access = resolveAccess(opts);
|
|
122
114
|
return defineFeature(TAGS_FEATURE_NAME, (r) => registerTags(r, access, opts.toggleable, opts.ownership, opts.parents));
|
|
123
115
|
}
|