-
-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Description
Problem
schedules.task() and schemaTask() are mutually exclusive. If a task needs both schedule support (for cron/imperative schedules) and a typed custom payload (for manual .trigger() calls), users are forced into awkward workarounds.
Current situation
When using schedules.task(), the .trigger() method is typed to only accept ScheduledTaskPayload:
export const myTask = schedules.task({
id: 'my-task',
run: async (payload: ScheduledTaskPayload) => { ... }
});
// TypeScript error — can't pass custom fields
await myTask.trigger({ feedType: 'following', forcePush: true });
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
// Type '{ feedType: string; forcePush: boolean }' is not assignable to 'ScheduledTaskPayload'What users do today
Option A — as any cast (dirty):
// In server route
await myTask.trigger({ feedType: 'following', forcePush: true } as any);
// In task run
run: async (rawPayload: any) => {
const payload = MySchema.parse(rawPayload); // manual Zod parse, no type safety
}Option B — two separate tasks (maintenance burden):
export const myScheduledTask = schedules.task({ id: 'my-task-scheduled', ... });
export const myManualTask = schemaTask({ id: 'my-task-manual', schema: MySchema, ... });Neither option is good. Option A loses type safety entirely. Option B doubles maintenance surface.
Proposed Solution
Allow schedules.task() to accept an optional schema option. When present:
- Manual
.trigger()calls accept the schema's output type - The
runfunction receives a merged payload:ScheduledTaskPayload & z.infer<typeof schema> - When triggered by a schedule (cron), schema fields fall back to Zod defaults
- When triggered manually, schema fields come from the caller
const MySchema = z.object({
feedType: z.enum(['for-you', 'following']).default('for-you'),
forcePush: z.boolean().default(false),
});
export const myTask = schedules.task({
id: 'my-task',
schema: MySchema, // optional
run: async (payload: ScheduledTaskPayload & z.infer<typeof MySchema>) => {
// feedType is always available — from caller or Zod default
console.log(payload.feedType, payload.scheduleId);
}
});
// Now fully typed, no `as any` needed
await myTask.trigger({ feedType: 'following', forcePush: true });Why this matters
This is a common real-world pattern:
- A scheduled task runs on a cron with sensible defaults
- The same task can be triggered manually with overrides (e.g.
forcePush: true,dryRun: true, different config)
The schedule and the schema are orthogonal concerns — there's no good reason they can't coexist.
Implementation notes
Since schema would be optional, this is non-breaking. Internally, when the task receives a ScheduledTaskPayload (from a cron trigger), the schema's .parse() with defaults would fill in missing fields. When triggered manually, the caller's payload is parsed through the schema normally.
The complexity is mainly in the TypeScript generics for the merged payload type — similar to what schemaTask already does.