alepha
Version:
Easy-to-use modern TypeScript framework for building many kind of applications.
186 lines (185 loc) • 6.55 kB
JavaScript
import { $atom, $module, z } from "alepha";
import { logEntrySchema } from "alepha/logger";
import { $entity, db } from "alepha/orm";
//#region ../../src/api/jobs/entities/jobExecutionEntity.ts
/**
* 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
*/
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
}
]
});
//#endregion
//#region ../../src/api/jobs/schemas/jobConfigAtom.ts
const jobConfig = $atom({
name: "alepha.jobs",
description: "Configuration for the $job primitive.",
schema: z.object({
sweepCron: z.text({ description: "Cron expression for the sweep tick. Must be minute-granular at minimum (cron resolution). On Cloudflare Workers this expression is emitted into wrangler.jsonc by the build." }),
trimCron: z.text({ description: "Cron expression for the ring-buffer trim tick (per-job keepLastSuccess/keepLastError enforcement). Decoupled from `sweepCron` because trim is bounded by job execution rate, not retry latency — running it every sweep is wasted work for most apps." }),
staleThreshold: z.integer().describe("Pending age (ms) before the sweep re-dispatches it."),
runTimeout: z.integer().describe("Running age (ms) before assumed crash (fallback when no per-job timeout)."),
keepLastSuccess: z.integer().describe("Max successful rows to keep per job. Set 0 to disable and delete on success."),
keepLastError: z.integer().describe("Max error rows to keep per job."),
logMaxEntries: z.integer().describe("Max log entries captured per execution."),
drainTimeout: z.integer().describe("Max time (ms) to wait for in-flight jobs during shutdown.")
}),
default: {
sweepCron: "*/15 * * * *",
trimCron: "0 * * * *",
staleThreshold: 3e5,
runTimeout: 18e5,
keepLastSuccess: 10,
keepLastError: 10,
logMaxEntries: 100,
drainTimeout: 3e4
}
});
//#endregion
//#region ../../src/api/jobs/schemas/jobExecutionQuerySchema.ts
const jobExecutionQuerySchema = z.object({
status: z.enum([
"pending",
"running",
"scheduled",
"ok",
"error",
"cancelled"
]).optional(),
limit: z.integer().min(1).max(200).default(20).optional()
});
//#endregion
//#region ../../src/api/jobs/schemas/jobExecutionResourceSchema.ts
/**
* Public-facing schema for a job execution row.
*
* Diverges from the raw entity in two places, both for API ergonomics:
*
* - `priority` is exposed as the **string enum** (`critical`/`high`/...)
* instead of the numeric value used internally for SQL ordering. The
* `JobService` is responsible for the int → string transform.
* - `can` derives the available admin actions from the row's status.
*/
const jobExecutionResourceSchema = jobExecutionEntity.schema.omit({ priority: true }).extend({
priority: z.enum([
"critical",
"high",
"normal",
"low"
]),
can: z.object({
retry: z.boolean(),
cancel: z.boolean()
})
}).meta({
title: "JobExecutionResource",
description: "A job execution row with derived actions."
});
//#endregion
//#region ../../src/api/jobs/schemas/jobRegistrationSchema.ts
const jobRegistrationSchema = z.object({
name: z.text(),
description: z.text().optional(),
type: z.enum([
"cron",
"queue",
"direct"
]).describe("Effective runtime mode. 'cron' = scheduled. 'queue' = push-driven, dispatched via AlephaApiJobsQueue. 'direct' = push-driven, processed in-process (no queue infrastructure loaded), with the sweep as the safety net."),
priority: z.enum([
"critical",
"high",
"normal",
"low"
]),
cron: z.text().optional(),
timeout: z.text().optional(),
retry: z.object({ retries: z.integer() }).optional(),
recent: z.object({
ok: z.integer(),
error: z.integer(),
lastRun: z.datetime().optional()
})
});
//#endregion
//#region ../../src/api/jobs/schemas/triggerJobSchema.ts
const triggerJobSchema = z.object({ payload: z.record(z.text(), z.any()).optional() });
//#endregion
//#region ../../src/api/jobs/index.browser.ts
const AlephaApiJobs = $module({
name: "alepha.api.jobs",
services: []
});
const AlephaApiJobsQueue = $module({
name: "alepha.api.jobs.queue",
services: []
});
//#endregion
export { AlephaApiJobs, AlephaApiJobsQueue, jobConfig, jobExecutionEntity, jobExecutionQuerySchema, jobExecutionResourceSchema, jobRegistrationSchema, triggerJobSchema };
//# sourceMappingURL=index.browser.js.map