alepha
Version:
Easy-to-use modern TypeScript framework for building many kind of applications.
82 lines (70 loc) • 2.81 kB
text/typescript
import { type Static, z } from "alepha";
import { logEntrySchema } from "alepha/logger";
import { $entity, db } from "alepha/orm";
/**
* Job execution record.
*
* Stores durable state for queue-mode jobs (outbox pattern) and error records
* for cron-mode jobs. Successful executions are trimmed by the sweep to keep
* the last N rows per job (configurable via `jobConfig.keepLastSuccess`).
*
* Status transitions:
* - queue push → pending (or `scheduled` if `delay`/`scheduledAt` was given)
* - worker claim → running
* - success → ok (or row deleted, depending on `record` and `keepLastSuccess`)
* - terminal failure → error
* - retryable failure → scheduled (with scheduledAt = now; sweep picks it up)
* - delay → scheduled (with scheduledAt = now + delay)
* - sweep picks due ones → pending
* - cancel → cancelled
*/
export const jobExecutionEntity = $entity({
name: "job_executions",
schema: z.object({
id: db.primaryKey(z.uuid()),
createdAt: db.createdAt(),
updatedAt: db.updatedAt(),
jobName: z.text(),
key: z.text().nullable().optional(),
/**
* Owning tenant for this execution, when it was pushed in (or for) a tenant
* context. Used to org-scope tenant-facing views — notably the notification
* admin list, which is backed by this outbox. Nullable: cron / global / non-
* tenant pushes carry none. Deliberately NOT `db.organization()`: the job
* worker + sweep must see every org's rows, so this stays a plain,
* non-auto-scoping column rather than an auto-filtered one.
*/
organizationId: z.uuid().nullable().optional(),
status: db.default(
z.enum(["pending", "running", "scheduled", "ok", "error", "cancelled"]),
"pending",
),
priority: db.default(z.integer().min(0).max(3), 2),
attempt: db.default(z.integer(), 0),
maxAttempts: db.default(z.integer(), 1),
payload: z.record(z.text(), z.any()).optional(),
scheduledAt: z.datetime().optional(),
startedAt: z.datetime().optional(),
completedAt: z.datetime().optional(),
error: z.text().optional(),
logs: z.array(logEntrySchema).optional(),
triggeredBy: z.text().optional(),
triggeredByName: z.text().optional(),
cancelledBy: z.text().optional(),
cancelledByName: z.text().optional(),
}),
indexes: [
{ columns: ["jobName", "status", "scheduledAt"] },
{ columns: ["jobName", "status", "createdAt"] },
{ columns: ["jobName", "startedAt"] },
{ columns: ["jobName", "key"], unique: true },
],
});
export type JobExecutionEntity = Static<typeof jobExecutionEntity.schema>;
export type JobStatus =
| "pending"
| "running"
| "scheduled"
| "ok"
| "error"
| "cancelled";