Skip to content

Schedule Domain

Core Entity: ScheduledJob

ScheduledJob tracks a scheduling request from creation through its terminal state. It extends BaseEntity<ScheduledJobProps>.

Key properties:

PropertyTypeNotes
idstring (UUIDv7)Generated on creation
topicstringRouting key — max 3 dot-separated lowercase segments
kind'one_shot' | 'cron' | 'interval'Determines which BullMQ option is used
status'pending' | 'active' | 'completed' | 'failed' | 'cancelled'State machine (see invariants)
runAtDate | nullUTC instant — only set for one_shot
cronPatternstring | null5-field cron — only set for cron
intervalMsnumber | nullms — only set for interval
timezonestringIANA timezone (e.g. "America/Mexico_City")
payloadRecord<string, unknown>Opaque JSON delivered to listeners
metadata.ownerIdstring | nullUUIDv7 — audit/filter only
metadata.tenantIdstring | nullUUIDv7 — audit/filter only
metadata.correlationIdstring | nullUUIDv7 — tracing
metadata.clientRequestIdstring | nullUUIDv7 — idempotency key
retryPolicyobject | nullPer-job override; defaults apply when null
attemptsnumberHow many times the worker has tried
maxAttemptsnumber5 by default
lastErrorstring | nullSet on failure
firedAtDate | nullUTC instant of first successful worker entry

Status machine:

pending → active → completed
                 → failed  (retrying until maxAttempts; then terminal)
         → cancelled
active  → cancelled  (prevents further retries)

completed, failed, and cancelled are terminal — no further transitions.

Value Objects

FileWhat it validates
ScheduleTopicVoRegex ^[a-z][a-z0-9-]*(\.[a-z][a-z0-9-]*){0,2}$ — 1 to 3 lowercase segments
ScheduleTimezoneVoValid IANA string via Luxon IANAZone.isValidZone
ScheduleSpecVoDiscriminated union: OneShot (requires runAt > now) | Cron (valid 5-field expression) | Interval (everyMs ≥ 1000)
ScheduledJobIdVoBranded UUIDv7 wrapper
ScheduleKindEnum: one_shot | cron | interval
ScheduleStatusEnum: pending | active | completed | failed | cancelled

IScheduleService — Public API

Declared in domain/services/schedule.service.interface.ts. Injected via ScheduleServiceKey symbol.

ts
export interface IScheduleService {
  scheduleAt(spec: IScheduleOneShotSpec): Promise<IScheduledJob>;
  scheduleRepeat(spec: IScheduleRepeatSpec): Promise<IScheduledJob>;
  cancel(id: string): Promise<void>;
  reschedule(id: string, newSpec: IScheduleOneShotSpec | IScheduleRepeatSpec): Promise<IScheduledJob>;
  list(filter: IScheduleListFilter): Promise<IScheduleListResult>;
  getById(id: string): Promise<IScheduledJob | null>;
}

Metadata fields are for audit and routing only — not access control. Callers must authorize the request before calling IScheduleService. See Authorization Boundary.

IScheduleArrivedEvent — Event Contract

Declared in domain/events/schedule-arrived.event.interface.ts.

ts
export interface IScheduleArrivedEvent {
  scheduledJobId: string;
  topic: string;
  userPayload: Record<string, unknown>;
  metadata: {
    ownerId: string | null;
    tenantId: string | null;
    correlationId: string | null;
    clientRequestId: string | null;
  };
  timezone: string;              // IANA string echoed from original spec
  originalScheduledAt: Date;     // UTC
  firedAt: Date;                 // UTC — worker entry instant
  attempt: number;               // 1-based
  maxAttempts: number;
}

Two event names are always emitted per fire:

Event namePattern
schedule.arrivedUniform — all jobs
schedule.<topic>.arrivedPer-topic alias (e.g. schedule.notifications.task-reminder.arrived)

Error Codes

CodeCapabilityMeaning
SCHEDULE_MOMENT_IN_PASTscheduleAtrunAt ≤ current UTC
SCHEDULE_TIMEZONE_INVALIDbothtimezone is not a valid IANA string
SCHEDULE_TOPIC_INVALIDbothtopic fails regex or exceeds 3 segments
SCHEDULE_CRON_INVALIDscheduleRepeatpattern is not valid 5-field cron
SCHEDULE_INTERVAL_TOO_SHORTscheduleRepeateveryMs < 1000
SCHEDULE_CLIENT_REQUEST_ID_IN_USEbothclientRequestId already exists in any status
SCHEDULE_JOB_NOT_FOUNDrescheduleno job with given id
SCHEDULE_JOB_NOT_CANCELLABLEreschedulejob exists but status is not 'pending'
SCHEDULE_JOB_ALREADY_CANCELLEDcancelinformational only — not thrown, logged at INFO
SCHEDULE_RETRY_POLICY_INVALIDbothretry values outside allowed bounds
SCHEDULE_ENQUEUE_FAILUREbothBullMQ enqueue failed after Postgres write