refactor(gateway): dedupe cron protocol param schemas

This commit is contained in:
Peter Steinberger
2026-02-18 22:31:29 +00:00
parent bb0516655c
commit f054cd6709
2 changed files with 96 additions and 78 deletions

View File

@@ -0,0 +1,49 @@
import { describe, expect, it } from "vitest";
import {
validateCronAddParams,
validateCronRemoveParams,
validateCronRunParams,
validateCronRunsParams,
validateCronUpdateParams,
} from "./index.js";
const minimalAddParams = {
name: "daily-summary",
schedule: { kind: "every", everyMs: 60_000 },
sessionTarget: "main",
wakeMode: "next-heartbeat",
payload: { kind: "systemEvent", text: "tick" },
} as const;
describe("cron protocol validators", () => {
it("accepts minimal add params", () => {
expect(validateCronAddParams(minimalAddParams)).toBe(true);
});
it("rejects add params when required scheduling fields are missing", () => {
const { wakeMode: _wakeMode, ...withoutWakeMode } = minimalAddParams;
expect(validateCronAddParams(withoutWakeMode)).toBe(false);
});
it("accepts update params for id and jobId selectors", () => {
expect(validateCronUpdateParams({ id: "job-1", patch: { enabled: false } })).toBe(true);
expect(validateCronUpdateParams({ jobId: "job-2", patch: { enabled: true } })).toBe(true);
});
it("accepts remove params for id and jobId selectors", () => {
expect(validateCronRemoveParams({ id: "job-1" })).toBe(true);
expect(validateCronRemoveParams({ jobId: "job-2" })).toBe(true);
});
it("accepts run params mode for id and jobId selectors", () => {
expect(validateCronRunParams({ id: "job-1", mode: "force" })).toBe(true);
expect(validateCronRunParams({ jobId: "job-2", mode: "due" })).toBe(true);
});
it("enforces runs limit minimum for id and jobId selectors", () => {
expect(validateCronRunsParams({ id: "job-1", limit: 1 })).toBe(true);
expect(validateCronRunsParams({ jobId: "job-2", limit: 1 })).toBe(true);
expect(validateCronRunsParams({ id: "job-1", limit: 0 })).toBe(false);
expect(validateCronRunsParams({ jobId: "job-2", limit: 0 })).toBe(false);
});
});

View File

@@ -19,6 +19,35 @@ function cronAgentTurnPayloadSchema(params: { message: TSchema }) {
);
}
const CronSessionTargetSchema = Type.Union([Type.Literal("main"), Type.Literal("isolated")]);
const CronWakeModeSchema = Type.Union([Type.Literal("next-heartbeat"), Type.Literal("now")]);
const CronCommonOptionalFields = {
agentId: Type.Optional(Type.Union([NonEmptyString, Type.Null()])),
sessionKey: Type.Optional(Type.Union([NonEmptyString, Type.Null()])),
description: Type.Optional(Type.String()),
enabled: Type.Optional(Type.Boolean()),
deleteAfterRun: Type.Optional(Type.Boolean()),
};
function cronIdOrJobIdParams(extraFields: Record<string, TSchema>) {
return Type.Union([
Type.Object(
{
id: NonEmptyString,
...extraFields,
},
{ additionalProperties: false },
),
Type.Object(
{
jobId: NonEmptyString,
...extraFields,
},
{ additionalProperties: false },
),
]);
}
export const CronScheduleSchema = Type.Union([
Type.Object(
{
@@ -144,8 +173,8 @@ export const CronJobSchema = Type.Object(
createdAtMs: Type.Integer({ minimum: 0 }),
updatedAtMs: Type.Integer({ minimum: 0 }),
schedule: CronScheduleSchema,
sessionTarget: Type.Union([Type.Literal("main"), Type.Literal("isolated")]),
wakeMode: Type.Union([Type.Literal("next-heartbeat"), Type.Literal("now")]),
sessionTarget: CronSessionTargetSchema,
wakeMode: CronWakeModeSchema,
payload: CronPayloadSchema,
delivery: Type.Optional(CronDeliverySchema),
state: CronJobStateSchema,
@@ -165,14 +194,10 @@ export const CronStatusParamsSchema = Type.Object({}, { additionalProperties: fa
export const CronAddParamsSchema = Type.Object(
{
name: NonEmptyString,
agentId: Type.Optional(Type.Union([NonEmptyString, Type.Null()])),
sessionKey: Type.Optional(Type.Union([NonEmptyString, Type.Null()])),
description: Type.Optional(Type.String()),
enabled: Type.Optional(Type.Boolean()),
deleteAfterRun: Type.Optional(Type.Boolean()),
...CronCommonOptionalFields,
schedule: CronScheduleSchema,
sessionTarget: Type.Union([Type.Literal("main"), Type.Literal("isolated")]),
wakeMode: Type.Union([Type.Literal("next-heartbeat"), Type.Literal("now")]),
sessionTarget: CronSessionTargetSchema,
wakeMode: CronWakeModeSchema,
payload: CronPayloadSchema,
delivery: Type.Optional(CronDeliverySchema),
},
@@ -182,14 +207,10 @@ export const CronAddParamsSchema = Type.Object(
export const CronJobPatchSchema = Type.Object(
{
name: Type.Optional(NonEmptyString),
agentId: Type.Optional(Type.Union([NonEmptyString, Type.Null()])),
sessionKey: Type.Optional(Type.Union([NonEmptyString, Type.Null()])),
description: Type.Optional(Type.String()),
enabled: Type.Optional(Type.Boolean()),
deleteAfterRun: Type.Optional(Type.Boolean()),
...CronCommonOptionalFields,
schedule: Type.Optional(CronScheduleSchema),
sessionTarget: Type.Optional(Type.Union([Type.Literal("main"), Type.Literal("isolated")])),
wakeMode: Type.Optional(Type.Union([Type.Literal("next-heartbeat"), Type.Literal("now")])),
sessionTarget: Type.Optional(CronSessionTargetSchema),
wakeMode: Type.Optional(CronWakeModeSchema),
payload: Type.Optional(CronPayloadPatchSchema),
delivery: Type.Optional(CronDeliveryPatchSchema),
state: Type.Optional(Type.Partial(CronJobStateSchema)),
@@ -197,71 +218,19 @@ export const CronJobPatchSchema = Type.Object(
{ additionalProperties: false },
);
export const CronUpdateParamsSchema = Type.Union([
Type.Object(
{
id: NonEmptyString,
patch: CronJobPatchSchema,
},
{ additionalProperties: false },
),
Type.Object(
{
jobId: NonEmptyString,
patch: CronJobPatchSchema,
},
{ additionalProperties: false },
),
]);
export const CronUpdateParamsSchema = cronIdOrJobIdParams({
patch: CronJobPatchSchema,
});
export const CronRemoveParamsSchema = Type.Union([
Type.Object(
{
id: NonEmptyString,
},
{ additionalProperties: false },
),
Type.Object(
{
jobId: NonEmptyString,
},
{ additionalProperties: false },
),
]);
export const CronRemoveParamsSchema = cronIdOrJobIdParams({});
export const CronRunParamsSchema = Type.Union([
Type.Object(
{
id: NonEmptyString,
mode: Type.Optional(Type.Union([Type.Literal("due"), Type.Literal("force")])),
},
{ additionalProperties: false },
),
Type.Object(
{
jobId: NonEmptyString,
mode: Type.Optional(Type.Union([Type.Literal("due"), Type.Literal("force")])),
},
{ additionalProperties: false },
),
]);
export const CronRunParamsSchema = cronIdOrJobIdParams({
mode: Type.Optional(Type.Union([Type.Literal("due"), Type.Literal("force")])),
});
export const CronRunsParamsSchema = Type.Union([
Type.Object(
{
id: NonEmptyString,
limit: Type.Optional(Type.Integer({ minimum: 1, maximum: 5000 })),
},
{ additionalProperties: false },
),
Type.Object(
{
jobId: NonEmptyString,
limit: Type.Optional(Type.Integer({ minimum: 1, maximum: 5000 })),
},
{ additionalProperties: false },
),
]);
export const CronRunsParamsSchema = cronIdOrJobIdParams({
limit: Type.Optional(Type.Integer({ minimum: 1, maximum: 5000 })),
});
export const CronRunLogEntrySchema = Type.Object(
{