refactor: dedupe cron lowercase helpers

This commit is contained in:
Peter Steinberger
2026-04-07 12:00:58 +01:00
parent bbe5a4b31a
commit 934927fd13
6 changed files with 31 additions and 24 deletions

View File

@@ -1,9 +1,10 @@
import { z, type ZodType } from "zod";
import { normalizeOptionalLowercaseString } from "../shared/string-coerce.js";
const trimStringPreprocess = (value: unknown) => (typeof value === "string" ? value.trim() : value);
const trimLowercaseStringPreprocess = (value: unknown) =>
typeof value === "string" ? value.trim().toLowerCase() : value;
normalizeOptionalLowercaseString(value) ?? value;
export const DeliveryModeFieldSchema = z
.preprocess(trimLowercaseStringPreprocess, z.enum(["deliver", "announce", "none", "webhook"]))

View File

@@ -1,5 +1,10 @@
import type { CronFailureDestinationConfig } from "../config/types.cron.js";
import { normalizeOptionalString, normalizeOptionalThreadValue } from "../shared/string-coerce.js";
import {
normalizeLowercaseStringOrEmpty,
normalizeOptionalLowercaseString,
normalizeOptionalString,
normalizeOptionalThreadValue,
} from "../shared/string-coerce.js";
import type { CronDelivery, CronDeliveryMode, CronJob, CronMessageChannel } from "./types.js";
export type CronDeliveryPlan = {
@@ -14,10 +19,7 @@ export type CronDeliveryPlan = {
};
function normalizeChannel(value: unknown): CronMessageChannel | undefined {
if (typeof value !== "string") {
return undefined;
}
const trimmed = value.trim().toLowerCase();
const trimmed = normalizeOptionalLowercaseString(value);
if (!trimmed) {
return undefined;
}
@@ -28,7 +30,8 @@ export function resolveCronDeliveryPlan(job: CronJob): CronDeliveryPlan {
const delivery = job.delivery;
const hasDelivery = delivery && typeof delivery === "object";
const rawMode = hasDelivery ? (delivery as { mode?: unknown }).mode : undefined;
const normalizedMode = typeof rawMode === "string" ? rawMode.trim().toLowerCase() : rawMode;
const normalizedMode =
typeof rawMode === "string" ? normalizeLowercaseStringOrEmpty(rawMode) : rawMode;
const mode =
normalizedMode === "announce"
? "announce"
@@ -97,10 +100,7 @@ export type CronFailureDestinationInput = {
};
function normalizeFailureMode(value: unknown): "announce" | "webhook" | undefined {
if (typeof value !== "string") {
return undefined;
}
const trimmed = value.trim().toLowerCase();
const trimmed = normalizeOptionalLowercaseString(value);
if (trimmed === "announce" || trimmed === "webhook") {
return trimmed;
}

View File

@@ -1,4 +1,8 @@
import { sanitizeAgentId } from "../routing/session-key.js";
import {
normalizeLowercaseStringOrEmpty,
normalizeOptionalLowercaseString,
} from "../shared/string-coerce.js";
import { isRecord } from "../utils.js";
import {
TimeoutSecondsFieldSchema,
@@ -61,7 +65,7 @@ function normalizeTrimmedStringArray(
function coerceSchedule(schedule: UnknownRecord) {
const next: UnknownRecord = { ...schedule };
const rawKind = typeof schedule.kind === "string" ? schedule.kind.trim().toLowerCase() : "";
const rawKind = normalizeLowercaseStringOrEmpty(schedule.kind);
const kind = rawKind === "at" || rawKind === "every" || rawKind === "cron" ? rawKind : undefined;
const exprRaw = typeof schedule.expr === "string" ? schedule.expr.trim() : "";
const legacyCronRaw = typeof schedule.cron === "string" ? schedule.cron.trim() : "";
@@ -141,7 +145,7 @@ function coerceSchedule(schedule: UnknownRecord) {
function coercePayload(payload: UnknownRecord) {
const next: UnknownRecord = { ...payload };
const kindRaw = typeof next.kind === "string" ? next.kind.trim().toLowerCase() : "";
const kindRaw = normalizeLowercaseStringOrEmpty(next.kind);
if (kindRaw === "agentturn") {
next.kind = "agentTurn";
} else if (kindRaw === "systemevent") {
@@ -316,7 +320,7 @@ function normalizeSessionTarget(raw: unknown) {
return undefined;
}
const trimmed = raw.trim();
const lower = trimmed.toLowerCase();
const lower = normalizeLowercaseStringOrEmpty(trimmed);
if (lower === "main" || lower === "isolated" || lower === "current") {
return lower;
}
@@ -331,7 +335,7 @@ function normalizeWakeMode(raw: unknown) {
if (typeof raw !== "string") {
return undefined;
}
const trimmed = raw.trim().toLowerCase();
const trimmed = normalizeOptionalLowercaseString(raw);
if (trimmed === "now" || trimmed === "next-heartbeat") {
return trimmed;
}
@@ -439,7 +443,7 @@ export function normalizeCronJobInput(
if (typeof enabled === "boolean") {
next.enabled = enabled;
} else if (typeof enabled === "string") {
const trimmed = enabled.trim().toLowerCase();
const trimmed = normalizeOptionalLowercaseString(enabled);
if (trimmed === "true") {
next.enabled = true;
}

View File

@@ -2,7 +2,10 @@ import fs from "node:fs/promises";
import path from "node:path";
import { parseByteSize } from "../cli/parse-bytes.js";
import type { CronConfig } from "../config/types.cron.js";
import { normalizeOptionalString } from "../shared/string-coerce.js";
import {
normalizeLowercaseStringOrEmpty,
normalizeOptionalString,
} from "../shared/string-coerce.js";
import type { CronDeliveryStatus, CronRunStatus, CronRunTelemetry } from "./types.js";
export type CronRunLogEntry = {
@@ -361,7 +364,7 @@ export async function readCronRunLogEntriesPage(
const raw = await fs.readFile(path.resolve(filePath), "utf-8").catch(() => "");
const statuses = normalizeRunStatuses(opts);
const deliveryStatuses = normalizeDeliveryStatuses(opts);
const query = opts?.query?.trim().toLowerCase() ?? "";
const query = normalizeLowercaseStringOrEmpty(opts?.query);
const sortDir: CronRunLogSortDir = opts?.sortDir === "asc" ? "asc" : "desc";
const all = parseAllRunLogEntries(raw, { jobId: opts?.jobId });
const filtered = filterRunLogEntries(all, {
@@ -394,7 +397,7 @@ export async function readCronRunLogEntriesPageAll(
const limit = Math.max(1, Math.min(200, Math.floor(opts.limit ?? 50)));
const statuses = normalizeRunStatuses(opts);
const deliveryStatuses = normalizeDeliveryStatuses(opts);
const query = opts.query?.trim().toLowerCase() ?? "";
const query = normalizeLowercaseStringOrEmpty(opts.query);
const sortDir: CronRunLogSortDir = opts.sortDir === "asc" ? "asc" : "desc";
const runsDir = path.resolve(path.dirname(path.resolve(opts.storePath)), "runs");
const files = await fs.readdir(runsDir, { withFileTypes: true }).catch(() => []);

View File

@@ -1,5 +1,6 @@
import { enqueueCommandInLane } from "../../process/command-queue.js";
import { CommandLane } from "../../process/lanes.js";
import { normalizeLowercaseStringOrEmpty } from "../../shared/string-coerce.js";
import {
completeTaskRunByRunId,
createRunningTaskRun,
@@ -220,7 +221,7 @@ function sortJobs(jobs: CronJob[], sortBy: CronJobsSortBy, sortDir: CronSortDir)
export async function listPage(state: CronServiceState, opts?: CronListPageOptions) {
return await locked(state, async () => {
await ensureLoadedForRead(state);
const query = opts?.query?.trim().toLowerCase() ?? "";
const query = normalizeLowercaseStringOrEmpty(opts?.query);
const enabledFilter = resolveEnabledFilter(opts);
const sortBy = opts?.sortBy ?? "nextRunAtMs";
const sortDir = opts?.sortDir ?? "asc";

View File

@@ -2,6 +2,7 @@ import { resolveFailoverReasonFromError } from "../../agents/failover-error.js";
import type { CronConfig, CronRetryOn } from "../../config/types.cron.js";
import type { HeartbeatRunResult } from "../../infra/heartbeat-wake.js";
import { DEFAULT_AGENT_ID } from "../../routing/session-key.js";
import { normalizeOptionalLowercaseString } from "../../shared/string-coerce.js";
import {
completeTaskRunByRunId,
createRunningTaskRun,
@@ -262,10 +263,7 @@ function resolveDeliveryStatus(params: { job: CronJob; delivered?: boolean }): C
}
function normalizeCronMessageChannel(input: unknown): CronMessageChannel | undefined {
if (typeof input !== "string") {
return undefined;
}
const channel = input.trim().toLowerCase();
const channel = normalizeOptionalLowercaseString(input);
return channel ? (channel as CronMessageChannel) : undefined;
}