UNPKG

alepha

Version:

Easy-to-use modern TypeScript framework for building many kind of applications.

186 lines (185 loc) 6.55 kB
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