alepha 0.20.2 → 0.20.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +0 -1
- package/assets/swagger-ui/swagger-ui-bundle.js +1 -1
- package/assets/swagger-ui/swagger-ui.css +1 -1
- package/dist/api/audits/index.browser.js +49 -0
- package/dist/api/audits/index.browser.js.map +1 -1
- package/dist/api/audits/index.d.ts.map +1 -1
- package/dist/api/audits/index.js +49 -0
- package/dist/api/audits/index.js.map +1 -1
- package/dist/api/files/index.d.ts.map +1 -1
- package/dist/api/files/index.js.map +1 -1
- package/dist/api/jobs/index.d.ts +16 -75
- package/dist/api/jobs/index.d.ts.map +1 -1
- package/dist/api/jobs/index.js.map +1 -1
- package/dist/api/keys/index.js.map +1 -1
- package/dist/api/notifications/index.d.ts +1 -10
- package/dist/api/notifications/index.d.ts.map +1 -1
- package/dist/api/organizations/index.d.ts.map +1 -1
- package/dist/api/parameters/index.browser.js +37 -0
- package/dist/api/parameters/index.browser.js.map +1 -1
- package/dist/api/parameters/index.d.ts +4 -65
- package/dist/api/parameters/index.d.ts.map +1 -1
- package/dist/api/parameters/index.js +37 -0
- package/dist/api/parameters/index.js.map +1 -1
- package/dist/api/payments/index.d.ts.map +1 -1
- package/dist/api/payments/index.js.map +1 -1
- package/dist/api/users/index.d.ts +207 -5184
- package/dist/api/users/index.d.ts.map +1 -1
- package/dist/api/users/index.js +2 -4
- package/dist/api/users/index.js.map +1 -1
- package/dist/api/verifications/index.d.ts.map +1 -1
- package/dist/api/verifications/index.js +2 -1
- package/dist/api/verifications/index.js.map +1 -1
- package/dist/bucket/index.js +5 -1
- package/dist/bucket/index.js.map +1 -1
- package/dist/bucket/index.workerd.js +5 -1
- package/dist/bucket/index.workerd.js.map +1 -1
- package/dist/cache/core/index.js.map +1 -1
- package/dist/cache/core/index.workerd.js.map +1 -1
- package/dist/captcha/index.js.map +1 -1
- package/dist/cli/core/index.d.ts +217 -11647
- package/dist/cli/core/index.d.ts.map +1 -1
- package/dist/cli/core/index.js +706 -42
- package/dist/cli/core/index.js.map +1 -1
- package/dist/cli/devtools/index.js +7 -1
- package/dist/cli/devtools/index.js.map +1 -1
- package/dist/cli/platform/index.d.ts +41 -64
- package/dist/cli/platform/index.d.ts.map +1 -1
- package/dist/cli/platform/index.js +47 -0
- package/dist/cli/platform/index.js.map +1 -1
- package/dist/cli/vendor/index.js +15 -0
- package/dist/cli/vendor/index.js.map +1 -1
- package/dist/command/index.js +1 -1
- package/dist/command/index.js.map +1 -1
- package/dist/core/index.browser.js.map +1 -1
- package/dist/core/index.d.ts +2 -8
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js.map +1 -1
- package/dist/core/index.native.js.map +1 -1
- package/dist/core/index.workerd.js.map +1 -1
- package/dist/crypto/index.js.map +1 -1
- package/dist/datetime/index.js.map +1 -1
- package/dist/email/core/index.js.map +1 -1
- package/dist/email/smtp/index.js +2 -10522
- package/dist/email/smtp/index.js.map +1 -1
- package/dist/fake/index.d.ts +4 -8085
- package/dist/fake/index.d.ts.map +1 -1
- package/dist/fake/index.js +3 -33554
- package/dist/fake/index.js.map +1 -1
- package/dist/lock/core/index.js.map +1 -1
- package/dist/lock/redis/index.js.map +1 -1
- package/dist/logger/index.js +32 -1
- package/dist/logger/index.js.map +1 -1
- package/dist/mcp/index.js +5 -1
- package/dist/mcp/index.js.map +1 -1
- package/dist/orm/core/index.browser.js +1 -361
- package/dist/orm/core/index.browser.js.map +1 -1
- package/dist/orm/core/index.bun.js +14 -406
- package/dist/orm/core/index.bun.js.map +1 -1
- package/dist/orm/core/index.d.ts +96 -5117
- package/dist/orm/core/index.d.ts.map +1 -1
- package/dist/orm/core/index.js +23 -419
- package/dist/orm/core/index.js.map +1 -1
- package/dist/orm/postgres/index.bun.js +17 -20
- package/dist/orm/postgres/index.bun.js.map +1 -1
- package/dist/orm/postgres/index.d.ts +2 -613
- package/dist/orm/postgres/index.d.ts.map +1 -1
- package/dist/orm/postgres/index.js +17 -20
- package/dist/orm/postgres/index.js.map +1 -1
- package/dist/react/core/index.js.map +1 -1
- package/dist/react/i18n/index.js.map +1 -1
- package/dist/react/intro/index.js +22 -17
- package/dist/react/intro/index.js.map +1 -1
- package/dist/react/router/index.browser.js +78 -2
- package/dist/react/router/index.browser.js.map +1 -1
- package/dist/react/router/index.d.ts +22 -1
- package/dist/react/router/index.d.ts.map +1 -1
- package/dist/react/router/index.js +102 -4
- package/dist/react/router/index.js.map +1 -1
- package/dist/react/testing/index.d.ts +1 -411
- package/dist/react/testing/index.d.ts.map +1 -1
- package/dist/react/testing/index.js +13 -12293
- package/dist/react/testing/index.js.map +1 -1
- package/dist/react/ui/index.js +3 -0
- package/dist/react/ui/index.js.map +1 -1
- package/dist/react/websocket/index.js.map +1 -1
- package/dist/redis/index.js.map +1 -1
- package/dist/scheduler/index.d.ts +1 -83
- package/dist/scheduler/index.d.ts.map +1 -1
- package/dist/scheduler/index.js +2 -391
- package/dist/scheduler/index.js.map +1 -1
- package/dist/scheduler/index.workerd.js +2 -391
- package/dist/scheduler/index.workerd.js.map +1 -1
- package/dist/security/index.browser.js.map +1 -1
- package/dist/security/index.d.ts +2 -325
- package/dist/security/index.d.ts.map +1 -1
- package/dist/security/index.js +3 -1362
- package/dist/security/index.js.map +1 -1
- package/dist/server/auth/index.d.ts +1 -1054
- package/dist/server/auth/index.d.ts.map +1 -1
- package/dist/server/auth/index.js +16 -1224
- package/dist/server/auth/index.js.map +1 -1
- package/dist/server/cookies/index.js.map +1 -1
- package/dist/server/core/index.browser.js.map +1 -1
- package/dist/server/core/index.d.ts +1 -4
- package/dist/server/core/index.d.ts.map +1 -1
- package/dist/server/core/index.js +19 -4
- package/dist/server/core/index.js.map +1 -1
- package/dist/server/links/index.browser.js.map +1 -1
- package/dist/server/links/index.js.map +1 -1
- package/dist/server/metrics/index.d.ts +1 -514
- package/dist/server/metrics/index.d.ts.map +1 -1
- package/dist/server/metrics/index.js +4 -4356
- package/dist/server/metrics/index.js.map +1 -1
- package/dist/server/rate-limit/index.js.map +1 -1
- package/dist/server/static/index.js.map +1 -1
- package/dist/server/swagger/index.js +1 -1
- package/dist/server/swagger/index.js.map +1 -1
- package/dist/sms/index.js.map +1 -1
- package/dist/system/index.browser.js.map +1 -1
- package/dist/system/index.js.map +1 -1
- package/dist/system/index.workerd.js.map +1 -1
- package/dist/topic/core/index.js.map +1 -1
- package/dist/websocket/index.browser.js +21 -0
- package/dist/websocket/index.browser.js.map +1 -1
- package/dist/websocket/index.js +21 -0
- package/dist/websocket/index.js.map +1 -1
- package/package.json +18 -15
- package/src/api/files/__tests__/FileController.spec.ts +1 -1
- package/src/api/jobs/__tests__/$job.spec.ts +5 -1
- package/src/api/users/schemas/userQuerySchema.ts +0 -1
- package/src/api/users/services/UserService.ts +1 -5
- package/src/api/verifications/__tests__/CodeVerification.spec.ts +14 -0
- package/src/api/verifications/__tests__/LinkVerification.spec.ts +14 -0
- package/src/api/verifications/services/VerificationService.ts +1 -0
- package/src/cli/core/__tests__/init.spec.ts +208 -0
- package/src/cli/core/commands/init.ts +12 -0
- package/src/cli/core/services/PackageManagerUtils.ts +23 -6
- package/src/cli/core/services/ProjectScaffolder.ts +298 -20
- package/src/cli/core/tasks/BuildDockerTask.ts +9 -10
- package/src/cli/core/tasks/BuildServerTask.ts +8 -0
- package/src/cli/core/templates/apiIndexTs.ts +23 -1
- package/src/cli/core/templates/componentsJsonTs.ts +39 -0
- package/src/cli/core/templates/mainCss.ts +1 -0
- package/src/cli/core/templates/saasAdminLayoutTsx.ts +77 -0
- package/src/cli/core/templates/saasAdminPagesTsx.ts +26 -0
- package/src/cli/core/templates/saasAuthLayoutTsx.ts +20 -0
- package/src/cli/core/templates/saasAuthPagesTsx.ts +62 -0
- package/src/cli/core/templates/saasRealmProviderTs.ts +46 -0
- package/src/cli/core/templates/webAppRouterTs.ts +104 -1
- package/src/cli/core/templates/webIndexTs.ts +23 -1
- package/src/cli/platform/__tests__/SecretsCommand.spec.ts +2 -0
- package/src/command/providers/CliProvider.ts +1 -1
- package/src/core/interfaces/Service.ts +3 -1
- package/src/core/providers/TypeProvider.ts +1 -1
- package/src/logger/services/Logger.ts +1 -1
- package/src/mcp/__tests__/$resource.spec.ts +1 -1
- package/src/mcp/__tests__/$tool.spec.ts +1 -1
- package/src/mcp/__tests__/McpServerProvider.spec.ts +1 -1
- package/src/orm/__tests__/$repository-tests.ts +1 -0
- package/src/orm/__tests__/orm-next-tests.ts +2 -67
- package/src/orm/__tests__/orm-next.spec.ts +0 -21
- package/src/orm/core/index.shared.ts +0 -2
- package/src/orm/core/index.ts +1 -2
- package/src/orm/core/primitives/$repository.ts +3 -6
- package/src/orm/core/providers/drivers/DatabaseProvider.ts +0 -5
- package/src/orm/core/providers/drivers/NodeSqliteProvider.ts +11 -13
- package/src/orm/core/services/ModelBuilder.ts +1 -13
- package/src/orm/core/services/Repository.ts +1 -42
- package/src/orm/core/services/SqliteModelBuilder.ts +2 -33
- package/src/orm/postgres/services/PostgresModelBuilder.ts +10 -45
- package/src/react/intro/components/GettingStartedAuthSlide.tsx +11 -4
- package/src/react/router/__tests__/ReactBrowserProvider.browser.spec.ts +213 -2
- package/src/react/router/providers/ReactBrowserProvider.ts +73 -0
- package/src/react/router/providers/ReactBrowserRouterProvider.ts +1 -1
- package/src/react/router/providers/ReactPreloadProvider.ts +1 -1
- package/src/react/router/providers/ReactServerProvider.ts +1 -0
- package/src/scheduler/providers/CronProvider.ts +1 -1
- package/src/security/primitives/$basicAuth.ts +1 -1
- package/src/server/auth/providers/ServerAuthProvider.ts +5 -1
- package/src/server/core/interfaces/ServerRequest.ts +1 -0
- package/src/server/core/providers/ServerProvider.ts +1 -1
- package/src/server/core/providers/ServerRouterProvider.ts +2 -2
- package/src/server/core/services/HttpClient.ts +1 -1
- package/src/server/swagger/providers/ServerSwaggerProvider.ts +1 -1
- package/dist/react/testing/chunk-DBEY4PJZ.js +0 -16
- package/src/orm/core/__tests__/parseQueryString.spec.ts +0 -196
- package/src/orm/core/helpers/parseQueryString.ts +0 -502
- package/src/orm/core/primitives/$view.ts +0 -88
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../../../src/api/jobs/schemas/jobExecutionQuerySchema.ts","../../../src/api/jobs/entities/jobExecutionEntity.ts","../../../src/api/jobs/schemas/jobExecutionResourceSchema.ts","../../../src/api/jobs/schemas/jobRegistrationSchema.ts","../../../src/api/jobs/schemas/triggerJobSchema.ts","../../../src/api/jobs/schemas/jobConfigAtom.ts","../../../src/api/jobs/providers/JobProvider.ts","../../../src/api/jobs/primitives/$job.ts","../../../src/api/jobs/services/JobService.ts","../../../src/api/jobs/controllers/AdminJobController.ts","../../../src/api/jobs/providers/JobQueueProvider.ts","../../../src/api/jobs/index.ts"],"sourcesContent":["import { type Static, t } from \"alepha\";\n\nexport const jobExecutionQuerySchema = t.object({\n status: t.optional(\n t.enum([\"pending\", \"running\", \"scheduled\", \"ok\", \"error\", \"cancelled\"]),\n ),\n limit: t.optional(t.integer({ minimum: 1, maximum: 200, default: 20 })),\n});\n\nexport type JobExecutionQuery = Static<typeof jobExecutionQuerySchema>;\n","import { type Static, t } from \"alepha\";\nimport { logEntrySchema } from \"alepha/logger\";\nimport { $entity, db } from \"alepha/orm\";\n\n/**\n * Job execution record.\n *\n * Stores durable state for queue-mode jobs (outbox pattern) and error records\n * for cron-mode jobs. Successful executions are trimmed by the sweep to keep\n * the last N rows per job (configurable via `jobConfig.keepLastSuccess`).\n *\n * Status transitions:\n * - queue push → pending\n * - worker claim → running\n * - success → ok\n * - terminal failure → error\n * - retry → scheduled (with scheduledAt = now + backoff)\n * - delay → scheduled (with scheduledAt = now + delay)\n * - sweep picks due ones → pending\n * - cancel → cancelled\n */\nexport const jobExecutionEntity = $entity({\n name: \"job_executions\",\n schema: t.object({\n id: db.primaryKey(t.uuid()),\n createdAt: db.createdAt(),\n updatedAt: db.updatedAt(),\n\n jobName: t.text(),\n key: t.optional(t.nullable(t.text())),\n\n status: db.default(\n t.enum([\"pending\", \"running\", \"scheduled\", \"ok\", \"error\", \"cancelled\"]),\n \"pending\",\n ),\n priority: db.default(t.integer({ minimum: 0, maximum: 3 }), 2),\n\n attempt: db.default(t.integer(), 0),\n maxAttempts: db.default(t.integer(), 1),\n\n payload: t.optional(t.record(t.text(), t.any())),\n\n scheduledAt: t.optional(t.datetime()),\n startedAt: t.optional(t.datetime()),\n completedAt: t.optional(t.datetime()),\n\n error: t.optional(t.text()),\n logs: t.optional(t.array(logEntrySchema)),\n\n triggeredBy: t.optional(t.text()),\n triggeredByName: t.optional(t.text()),\n cancelledBy: t.optional(t.text()),\n cancelledByName: t.optional(t.text()),\n }),\n indexes: [\n { columns: [\"jobName\", \"status\", \"scheduledAt\"] },\n { columns: [\"jobName\", \"startedAt\"] },\n { columns: [\"jobName\", \"key\"], unique: true },\n ],\n});\n\nexport type JobExecutionEntity = Static<typeof jobExecutionEntity.schema>;\n\nexport type JobStatus =\n | \"pending\"\n | \"running\"\n | \"scheduled\"\n | \"ok\"\n | \"error\"\n | \"cancelled\";\n","import { type Static, t } from \"alepha\";\nimport { jobExecutionEntity } from \"../entities/jobExecutionEntity.ts\";\n\nexport const jobExecutionResourceSchema = t.extend(\n jobExecutionEntity.schema,\n {\n can: t.object({\n retry: t.boolean(),\n cancel: t.boolean(),\n }),\n },\n {\n title: \"JobExecutionResource\",\n description: \"A job execution row with derived actions.\",\n },\n);\n\nexport type JobExecutionResource = Static<typeof jobExecutionResourceSchema>;\n","import { type Static, t } from \"alepha\";\n\nexport const jobRegistrationSchema = t.object({\n name: t.text(),\n description: t.optional(t.text()),\n type: t.enum([\"cron\", \"queue\"]),\n priority: t.enum([\"critical\", \"high\", \"normal\", \"low\"]),\n cron: t.optional(t.text()),\n timeout: t.optional(t.text()),\n retry: t.optional(\n t.object({\n retries: t.integer(),\n hasBackoff: t.boolean(),\n }),\n ),\n recent: t.object({\n ok: t.integer(),\n error: t.integer(),\n lastRun: t.optional(t.datetime()),\n }),\n});\n\nexport type JobRegistration = Static<typeof jobRegistrationSchema>;\n","import { type Static, t } from \"alepha\";\n\nexport const triggerJobSchema = t.object({\n payload: t.optional(t.record(t.text(), t.any())),\n});\n\nexport type TriggerJob = Static<typeof triggerJobSchema>;\n","import { $atom, type Static, t } from \"alepha\";\n\nexport const jobConfig = $atom({\n name: \"alepha.jobs\",\n description: \"Configuration for the $job primitive.\",\n schema: t.object({\n sweepInterval: t.integer({\n description: \"Sweep cron interval in milliseconds.\",\n }),\n staleThreshold: t.integer({\n description: \"Pending age (ms) before the sweep re-dispatches it.\",\n }),\n runTimeout: t.integer({\n description:\n \"Running age (ms) before assumed crash (fallback when no per-job timeout).\",\n }),\n keepLastSuccess: t.integer({\n description:\n \"Max successful rows to keep per job. Set 0 to disable and delete on success.\",\n }),\n keepLastError: t.integer({\n description: \"Max error rows to keep per job.\",\n }),\n logMaxEntries: t.integer({\n description: \"Max log entries captured per execution.\",\n }),\n drainTimeout: t.integer({\n description: \"Max time (ms) to wait for in-flight jobs during shutdown.\",\n }),\n }),\n default: {\n sweepInterval: 300_000,\n staleThreshold: 300_000,\n runTimeout: 1_800_000,\n keepLastSuccess: 10,\n keepLastError: 10,\n logMaxEntries: 100,\n drainTimeout: 30_000,\n },\n});\n\nexport type JobConfig = Static<typeof jobConfig.schema>;\n\ndeclare module \"alepha\" {\n interface State {\n [jobConfig.key]: JobConfig;\n }\n}\n","import {\n $hook,\n $inject,\n $state,\n Alepha,\n AlephaError,\n type Static,\n type TSchema,\n} from \"alepha\";\nimport { DateTimeProvider, type DurationLike } from \"alepha/datetime\";\nimport type { LogEntry } from \"alepha/logger\";\nimport { $logger } from \"alepha/logger\";\nimport { $repository } from \"alepha/orm\";\nimport { CronProvider } from \"alepha/scheduler\";\nimport {\n type JobStatus,\n jobExecutionEntity,\n} from \"../entities/jobExecutionEntity.ts\";\nimport type {\n JobPrimitiveOptions,\n JobPriority,\n JobRetryBackoff,\n JobRetryOptions,\n} from \"../primitives/$job.ts\";\nimport { jobConfig } from \"../schemas/jobConfigAtom.ts\";\n\n// -----------------------------------------------------------------------------------------------------------------\n\nconst PRIORITY_MAP: Record<JobPriority, number> = {\n critical: 0,\n high: 1,\n normal: 2,\n low: 3,\n};\n\nconst PRIORITY_REVERSE: Record<number, JobPriority> = {\n 0: \"critical\",\n 1: \"high\",\n 2: \"normal\",\n 3: \"low\",\n};\n\nconst SWEEP_CRON = \"*/5 * * * *\";\n\n// -----------------------------------------------------------------------------------------------------------------\n\nexport interface PushOptions {\n delay?: DurationLike;\n key?: string;\n priority?: JobPriority;\n scheduledAt?: Date;\n triggeredBy?: string;\n triggeredByName?: string;\n}\n\nexport interface PushManyItem<T extends TSchema = TSchema> {\n payload: Static<T>;\n key?: string;\n delay?: DurationLike;\n priority?: JobPriority;\n scheduledAt?: Date;\n}\n\nexport interface JobTriggerContext<T extends TSchema = TSchema> {\n payload?: Static<T>;\n triggeredBy?: string;\n triggeredByName?: string;\n}\n\nexport interface CancelContext {\n cancelledBy?: string;\n cancelledByName?: string;\n}\n\ninterface JobRuntimeRegistration {\n name: string;\n options: JobPrimitiveOptions;\n type: \"cron\" | \"queue\";\n}\n\n// -----------------------------------------------------------------------------------------------------------------\n\n/**\n * Coordinates cron (scheduler) and queue (push) jobs with a durable outbox\n * table and a single reconciliation sweep.\n *\n * Queue-mode flow:\n * push() → INSERT row (pending) + queue.send({ executionId })\n * worker → SELECT row → UPDATE running → handler → DELETE (ok) / UPDATE (error)\n *\n * Cron-mode flow:\n * scheduler tick → handler runs inline → INSERT row only on error\n *\n * Sweep responsibilities (every `sweepInterval`):\n * - re-enqueue pending rows older than `staleThreshold`\n * - fail running rows older than `max(timeout*2, runTimeout)`\n * - move `scheduled` rows with `scheduledAt <= now` to pending + enqueue\n * - trim per-job history beyond `keepLastSuccess` / `keepLastError`\n */\nexport class JobProvider {\n protected readonly alepha = $inject(Alepha);\n protected readonly dt = $inject(DateTimeProvider);\n protected readonly cronProvider = $inject(CronProvider);\n protected readonly config = $state(jobConfig);\n protected readonly log = $logger();\n protected readonly executions = $repository(jobExecutionEntity);\n\n protected readonly jobs = new Map<string, JobRuntimeRegistration>();\n protected readonly inFlight = new Set<Promise<void>>();\n protected readonly abortControllers = new Map<string, AbortController>();\n protected readonly perExecutionLogs = new Map<string, LogEntry[]>();\n protected stopping = false;\n\n /**\n * Set by `JobQueueProvider` when `AlephaApiJobsQueue` is loaded.\n * When null, queue-mode jobs cannot be pushed.\n */\n public queueDispatch:\n | ((jobName: string, executionId: string) => Promise<void>)\n | null = null;\n\n // --- Registration -----------------------------------------------------------------------------------------------\n\n public registerJob(name: string, options: JobPrimitiveOptions): void {\n if (this.jobs.has(name)) {\n throw new AlephaError(`Job already registered: ${name}`);\n }\n if (options.cron && options.schema) {\n throw new AlephaError(\n `Job '${name}' declares both 'cron' and 'schema'. A job must be either cron-only (recurring) or queue-only (push-based). Split into two jobs.`,\n );\n }\n if (!options.cron && !options.schema) {\n throw new AlephaError(\n `Job '${name}' must declare either 'cron' (for recurring tasks) or 'schema' (for queue-mode tasks).`,\n );\n }\n\n const type: \"cron\" | \"queue\" = options.cron ? \"cron\" : \"queue\";\n this.jobs.set(name, { name, options, type });\n this.log.debug(`Registered ${type} job '${name}'`, {\n cron: options.cron,\n priority: options.priority ?? \"normal\",\n retries: options.retry?.retries ?? 0,\n });\n\n if (options.cron) {\n this.cronProvider.createCronJob(name, options.cron, async () => {\n try {\n await this.runCron(name);\n } catch (error) {\n this.log.error(`Cron tick failed for job '${name}'`, error);\n }\n });\n }\n }\n\n public getRegisteredJobs(): Map<string, JobRuntimeRegistration> {\n return this.jobs;\n }\n\n // --- Cron execution (inline, no queue) --------------------------------------------------------------------------\n\n protected async runCron(name: string): Promise<void> {\n const registration = this.getRegistration(name);\n if (registration.type !== \"cron\") {\n throw new AlephaError(`Job '${name}' is not cron-mode`);\n }\n if (this.stopping) return;\n\n const executionId = crypto.randomUUID();\n const promise = this.executeInline(registration, executionId, {\n payload: undefined,\n attempt: 1,\n triggeredBy: \"system\",\n triggeredByName: \"system (cron)\",\n });\n this.inFlight.add(promise);\n try {\n await promise;\n } finally {\n this.inFlight.delete(promise);\n }\n }\n\n /**\n * Execute a cron handler inline. Records a row only on error (or always,\n * when `record: 'all'`). No DB writes on the happy path by default.\n */\n protected async executeInline(\n registration: JobRuntimeRegistration,\n executionId: string,\n ctx: {\n payload: unknown;\n attempt: number;\n triggeredBy?: string;\n triggeredByName?: string;\n },\n ): Promise<void> {\n const opts = registration.options;\n const name = registration.name;\n const record = opts.record ?? \"error\";\n const contextId = this.alepha.context.createContextId();\n this.perExecutionLogs.set(contextId, []);\n\n const abortController = new AbortController();\n this.abortControllers.set(executionId, abortController);\n let timeoutId: ReturnType<typeof setTimeout> | undefined;\n if (opts.timeout) {\n const ms = this.dt.duration(opts.timeout).as(\"milliseconds\");\n timeoutId = setTimeout(() => abortController.abort(), ms);\n }\n\n const startedAt = this.dt.now();\n\n try {\n await this.alepha.context.run(\n async () => {\n await this.alepha.events.emit(\"job:begin\", {\n name,\n now: startedAt,\n executionId,\n });\n\n try {\n await opts.handler({\n payload: ctx.payload,\n attempt: ctx.attempt,\n now: startedAt,\n signal: abortController.signal,\n executionId,\n });\n\n if (record === \"all\") {\n await this.writeTerminalRow(executionId, name, \"ok\", {\n payload: ctx.payload,\n attempt: ctx.attempt,\n startedAt,\n error: undefined,\n context: contextId,\n triggeredBy: ctx.triggeredBy,\n triggeredByName: ctx.triggeredByName,\n });\n }\n\n await this.alepha.events.emit(\n \"job:success\",\n { name, executionId },\n { catch: true },\n );\n } catch (error) {\n const err =\n error instanceof Error ? error : new Error(String(error));\n if (record !== \"none\") {\n await this.writeTerminalRow(executionId, name, \"error\", {\n payload: ctx.payload,\n attempt: ctx.attempt,\n startedAt,\n error: err,\n context: contextId,\n triggeredBy: ctx.triggeredBy,\n triggeredByName: ctx.triggeredByName,\n });\n }\n await this.alepha.events.emit(\n \"job:error\",\n { name, error: err, executionId },\n { catch: true },\n );\n } finally {\n if (timeoutId) clearTimeout(timeoutId);\n this.abortControllers.delete(executionId);\n await this.alepha.events.emit(\n \"job:end\",\n { name, executionId },\n { catch: true },\n );\n }\n },\n { context: contextId },\n );\n } finally {\n this.perExecutionLogs.delete(contextId);\n }\n }\n\n protected async writeTerminalRow(\n executionId: string,\n jobName: string,\n status: \"ok\" | \"error\",\n fields: {\n payload: unknown;\n attempt: number;\n startedAt: ReturnType<DateTimeProvider[\"now\"]>;\n error?: Error;\n context: string;\n triggeredBy?: string;\n triggeredByName?: string;\n },\n ): Promise<void> {\n try {\n const logs =\n status === \"error\" ? this.snapshotLogs(fields.context) : undefined;\n await this.executions.create({\n id: executionId,\n jobName,\n status,\n payload: fields.payload as Record<string, unknown> | undefined,\n attempt: fields.attempt,\n maxAttempts: fields.attempt,\n startedAt: fields.startedAt.toISOString(),\n completedAt: this.dt.nowISOString(),\n error: fields.error?.message,\n logs,\n triggeredBy: fields.triggeredBy,\n triggeredByName: fields.triggeredByName,\n });\n } catch (e) {\n this.log.warn(`Failed to write terminal row for ${executionId}`, e);\n }\n }\n\n // --- Queue push -------------------------------------------------------------------------------------------------\n\n public async push(\n name: string,\n payload: unknown,\n options?: PushOptions,\n ): Promise<string> {\n const registration = this.getRegistration(name);\n if (registration.type !== \"queue\") {\n throw new AlephaError(\n `Job '${name}' is not queue-mode (no schema declared). Use trigger() instead.`,\n );\n }\n const opts = registration.options;\n const validated = this.alepha.codec.validate(opts.schema!, payload);\n\n const priority =\n PRIORITY_MAP[options?.priority ?? opts.priority ?? \"normal\"];\n const maxAttempts = (opts.retry?.retries ?? 0) + 1;\n\n const isDelayed = options?.delay || options?.scheduledAt;\n const status: JobStatus = isDelayed ? \"scheduled\" : \"pending\";\n\n let scheduledAt: string | undefined;\n if (options?.scheduledAt) {\n scheduledAt = options.scheduledAt.toISOString();\n } else if (options?.delay) {\n scheduledAt = this.dt\n .now()\n .add(this.dt.duration(options.delay))\n .toISOString();\n }\n\n if (options?.key) {\n // Key-based dedup: check for existing row first, then insert.\n // Two queries in the no-conflict path, but deterministic across dialects.\n const existing = await this.executions.findMany({\n where: { jobName: { eq: name }, key: { eq: options.key } },\n limit: 1,\n });\n if (existing.length > 0) {\n return existing[0].id;\n }\n const execution = await this.executions.create({\n jobName: name,\n key: options.key,\n payload: validated as Record<string, unknown>,\n status,\n priority,\n maxAttempts,\n scheduledAt,\n triggeredBy: options.triggeredBy,\n triggeredByName: options.triggeredByName,\n });\n if (status === \"pending\") {\n await this.dispatchToQueue(name, execution.id);\n } else if (status === \"scheduled\" && scheduledAt) {\n this.scheduleOptimisticDispatch(name, execution.id, scheduledAt);\n }\n return execution.id;\n }\n\n const execution = await this.executions.create({\n jobName: name,\n payload: validated as Record<string, unknown>,\n status,\n priority,\n maxAttempts,\n scheduledAt,\n triggeredBy: options?.triggeredBy,\n triggeredByName: options?.triggeredByName,\n });\n\n if (status === \"pending\") {\n await this.dispatchToQueue(name, execution.id);\n } else if (status === \"scheduled\" && scheduledAt) {\n this.scheduleOptimisticDispatch(name, execution.id, scheduledAt);\n }\n return execution.id;\n }\n\n /**\n * Fire a local setTimeout so delayed/retrying rows dispatch as close to\n * `scheduledAt` as possible, rather than waiting for the next sweep tick.\n * No-op on stateless runtimes where timers won't survive (the sweep\n * handles those).\n */\n protected scheduleOptimisticDispatch(\n jobName: string,\n executionId: string,\n scheduledAt: string,\n ): void {\n const delayMs = Math.max(\n 0,\n new Date(scheduledAt).getTime() - this.dt.nowMillis(),\n );\n this.dt.createTimeout(() => {\n void this.dispatchScheduled(jobName, executionId);\n }, delayMs);\n }\n\n public async pushMany(\n name: string,\n items: Array<PushManyItem>,\n ): Promise<string[]> {\n if (items.length === 0) return [];\n\n const registration = this.getRegistration(name);\n if (registration.type !== \"queue\") {\n throw new AlephaError(\n `Job '${name}' is not queue-mode (no schema declared).`,\n );\n }\n const opts = registration.options;\n const maxAttempts = (opts.retry?.retries ?? 0) + 1;\n\n const keyed: PushManyItem[] = [];\n const bulk: Array<{\n jobName: string;\n payload: Record<string, unknown>;\n status: JobStatus;\n priority: number;\n maxAttempts: number;\n scheduledAt?: string;\n }> = [];\n\n for (const item of items) {\n const validated = this.alepha.codec.validate(opts.schema!, item.payload);\n if (item.key) {\n keyed.push({ ...item, payload: validated as Static<TSchema> });\n continue;\n }\n const isDelayed = item.delay || item.scheduledAt;\n const status: JobStatus = isDelayed ? \"scheduled\" : \"pending\";\n let scheduledAt: string | undefined;\n if (item.scheduledAt) {\n scheduledAt = item.scheduledAt.toISOString();\n } else if (item.delay) {\n scheduledAt = this.dt\n .now()\n .add(this.dt.duration(item.delay))\n .toISOString();\n }\n bulk.push({\n jobName: name,\n payload: validated as Record<string, unknown>,\n status,\n priority: PRIORITY_MAP[item.priority ?? opts.priority ?? \"normal\"],\n maxAttempts,\n scheduledAt,\n });\n }\n\n const ids: string[] = [];\n\n for (const item of keyed) {\n const id = await this.push(name, item.payload, {\n key: item.key,\n delay: item.delay,\n priority: item.priority,\n scheduledAt: item.scheduledAt,\n });\n ids.push(id);\n }\n\n if (bulk.length > 0) {\n const created = await this.executions.createMany(bulk);\n for (const exec of created) {\n ids.push(exec.id);\n if (exec.status === \"pending\" && !this.stopping) {\n await this.dispatchToQueue(name, exec.id);\n } else if (\n exec.status === \"scheduled\" &&\n exec.scheduledAt &&\n !this.stopping\n ) {\n this.scheduleOptimisticDispatch(name, exec.id, exec.scheduledAt);\n }\n }\n }\n\n this.log.debug(`pushMany '${name}': ${ids.length} jobs created`, {\n bulk: bulk.length,\n keyed: keyed.length,\n });\n\n return ids;\n }\n\n protected async dispatchToQueue(\n jobName: string,\n executionId: string,\n ): Promise<void> {\n if (this.stopping) return;\n if (!this.queueDispatch) {\n throw new AlephaError(\n `Queue-mode job '${jobName}' cannot be pushed: AlephaApiJobsQueue is not loaded. Add '.with(AlephaApiJobsQueue)' to your app.`,\n );\n }\n await this.queueDispatch(jobName, executionId);\n }\n\n // --- Manual trigger (admin / CLI) ------------------------------------------------------------------------------\n\n public async trigger(\n name: string,\n context?: JobTriggerContext,\n ): Promise<void> {\n const registration = this.getRegistration(name);\n\n if (registration.type === \"cron\") {\n const executionId = crypto.randomUUID();\n await this.executeInline(registration, executionId, {\n payload: undefined,\n attempt: 1,\n triggeredBy: context?.triggeredBy,\n triggeredByName: context?.triggeredByName,\n });\n return;\n }\n\n // queue-mode: treat as a normal push with the given payload\n if (!context?.payload) {\n throw new AlephaError(\n `Queue-mode job '${name}' requires a payload for manual trigger.`,\n );\n }\n await this.push(name, context.payload, {\n triggeredBy: context.triggeredBy,\n triggeredByName: context.triggeredByName,\n });\n }\n\n // --- Cancel ----------------------------------------------------------------------------------------------------\n\n public async cancel(\n executionId: string,\n context?: CancelContext,\n ): Promise<void> {\n const execution = await this.executions.findById(executionId);\n if (!execution) {\n throw new AlephaError(`Execution not found: ${executionId}`);\n }\n if (\n execution.status === \"ok\" ||\n execution.status === \"error\" ||\n execution.status === \"cancelled\"\n ) {\n throw new AlephaError(\n `Cannot cancel execution in '${execution.status}' status`,\n );\n }\n\n const controller = this.abortControllers.get(executionId);\n if (controller) controller.abort();\n\n await this.executions.updateById(executionId, {\n status: \"cancelled\",\n key: null,\n cancelledBy: context?.cancelledBy,\n cancelledByName: context?.cancelledByName,\n completedAt: this.dt.nowISOString(),\n });\n\n this.log.info(`Cancelled execution ${executionId}`, {\n jobName: execution.jobName,\n cancelledBy: context?.cancelledByName ?? context?.cancelledBy,\n });\n }\n\n // --- Queue consumer (called by JobQueueProvider) --------------------------------------------------------------\n\n public async processExecution(\n jobName: string,\n executionId: string,\n ): Promise<void> {\n const registration = this.jobs.get(jobName);\n if (!registration) {\n this.log.warn(`Unknown job '${jobName}' — skipping execution`, {\n executionId,\n });\n return;\n }\n if (registration.type !== \"queue\") {\n this.log.warn(`Job '${jobName}' is not queue-mode — skipping`, {\n executionId,\n });\n return;\n }\n\n const promise = this.processQueueExecution(registration, executionId);\n this.inFlight.add(promise);\n try {\n await promise;\n } finally {\n this.inFlight.delete(promise);\n }\n }\n\n protected async processQueueExecution(\n registration: JobRuntimeRegistration,\n executionId: string,\n ): Promise<void> {\n const jobName = registration.name;\n const opts = registration.options;\n const record = opts.record ?? \"error\";\n\n const claimed = await this.claim(executionId);\n if (!claimed) {\n this.log.debug(`Execution ${executionId} already claimed, skipping`);\n return;\n }\n\n const execution = await this.executions.findById(executionId);\n if (!execution) return;\n\n const contextId = this.alepha.context.createContextId();\n this.perExecutionLogs.set(contextId, []);\n\n const abortController = new AbortController();\n this.abortControllers.set(executionId, abortController);\n let timeoutId: ReturnType<typeof setTimeout> | undefined;\n if (opts.timeout) {\n const ms = this.dt.duration(opts.timeout).as(\"milliseconds\");\n timeoutId = setTimeout(() => abortController.abort(), ms);\n }\n\n const now = this.dt.now();\n\n try {\n await this.alepha.context.run(\n async () => {\n await this.alepha.events.emit(\"job:begin\", {\n name: jobName,\n now,\n executionId,\n });\n\n try {\n await opts.handler({\n payload: execution.payload,\n attempt: execution.attempt,\n now,\n signal: abortController.signal,\n executionId,\n });\n\n // Success: either DELETE (keepLastSuccess=0 or record=error)\n // or UPDATE to 'ok' (record=all and keepLastSuccess>0).\n const keepSuccess =\n record === \"all\" && this.config.keepLastSuccess > 0;\n if (keepSuccess) {\n await this.executions.updateById(executionId, {\n status: \"ok\",\n completedAt: this.dt.nowISOString(),\n key: null,\n });\n } else {\n await this.executions.deleteById(executionId);\n }\n\n await this.alepha.events.emit(\n \"job:success\",\n { name: jobName, executionId },\n { catch: true },\n );\n } catch (error) {\n const err =\n error instanceof Error ? error : new Error(String(error));\n\n if (abortController.signal.aborted) {\n const current = await this.executions.findById(executionId);\n if (current?.status === \"cancelled\") {\n await this.alepha.events.emit(\n \"job:cancel\",\n { name: jobName, executionId },\n { catch: true },\n );\n return;\n }\n }\n\n await this.handleFailure(\n executionId,\n registration,\n execution.attempt,\n err,\n contextId,\n );\n } finally {\n if (timeoutId) clearTimeout(timeoutId);\n this.abortControllers.delete(executionId);\n await this.alepha.events.emit(\n \"job:end\",\n { name: jobName, executionId },\n { catch: true },\n );\n }\n },\n { context: contextId },\n );\n } finally {\n this.perExecutionLogs.delete(contextId);\n }\n }\n\n protected async claim(executionId: string): Promise<boolean> {\n const execution = await this.executions.findById(executionId);\n if (!execution) return false;\n try {\n await this.executions.updateOne(\n { id: { eq: executionId }, status: { eq: \"pending\" } },\n {\n status: \"running\",\n attempt: execution.attempt + 1,\n startedAt: this.dt.nowISOString(),\n },\n );\n return true;\n } catch {\n return false;\n }\n }\n\n protected async handleFailure(\n executionId: string,\n registration: JobRuntimeRegistration,\n currentAttempt: number,\n error: Error,\n contextId: string,\n ): Promise<void> {\n const jobName = registration.name;\n const opts = registration.options;\n const retry = opts.retry;\n const maxAttempts = (retry?.retries ?? 0) + 1;\n\n const canRetry =\n retry &&\n currentAttempt + 1 < maxAttempts &&\n (retry.when ? retry.when(error) : true);\n\n if (canRetry) {\n const nextScheduledAt = this.computeBackoff(retry, currentAttempt + 1);\n this.log.info(\n `Job '${jobName}' failed, scheduling retry ${currentAttempt + 1}/${maxAttempts}`,\n { executionId, error: error.message, nextScheduledAt },\n );\n await this.executions.updateById(executionId, {\n status: \"scheduled\",\n error: error.message,\n scheduledAt: nextScheduledAt,\n logs: this.snapshotLogs(contextId),\n });\n // Optimistic dispatch: fire a local timer so the retry runs as close to\n // `scheduledAt` as possible. The sweep is the safety net for worker\n // crashes and stateless runtimes (CF Workers, where setTimeout won't\n // survive across invocations anyway).\n const delayMs = Math.max(\n 0,\n new Date(nextScheduledAt).getTime() - this.dt.nowMillis(),\n );\n this.dt.createTimeout(() => {\n void this.dispatchScheduled(jobName, executionId);\n }, delayMs);\n } else {\n this.log.info(\n `Job '${jobName}' dead after ${currentAttempt} attempt(s)`,\n { executionId, error: error.message },\n );\n await this.executions.updateById(executionId, {\n status: \"error\",\n error: error.message,\n completedAt: this.dt.nowISOString(),\n key: null,\n logs: this.snapshotLogs(contextId),\n });\n }\n\n await this.alepha.events.emit(\n \"job:error\",\n { name: jobName, error, executionId },\n { catch: true },\n );\n }\n\n protected computeBackoff(retry: JobRetryOptions, attempt: number): string {\n const now = this.dt.now();\n if (!retry.backoff) {\n return now.add(1, \"second\").toISOString();\n }\n if (Array.isArray(retry.backoff)) {\n return now.add(this.dt.duration(retry.backoff)).toISOString();\n }\n const backoff = retry.backoff as JobRetryBackoff;\n const initial = this.dt.duration(backoff.initial).as(\"milliseconds\");\n const factor = backoff.factor ?? 2;\n let delayMs = initial * factor ** (attempt - 1);\n if (backoff.max) {\n delayMs = Math.min(\n delayMs,\n this.dt.duration(backoff.max).as(\"milliseconds\"),\n );\n }\n if (backoff.jitter) {\n delayMs = delayMs * (0.75 + Math.random() * 0.5);\n }\n return now.add(delayMs, \"millisecond\").toISOString();\n }\n\n protected snapshotLogs(contextId: string): LogEntry[] | undefined {\n const entries = this.perExecutionLogs.get(contextId);\n if (!entries || entries.length === 0) return undefined;\n const max = this.config.logMaxEntries;\n if (max === 0) return undefined;\n if (entries.length <= max) return [...entries];\n const truncated = entries.slice(0, max);\n truncated.push({\n level: \"WARN\",\n message: `Log entries truncated at ${max}`,\n timestamp: this.dt.nowMillis(),\n service: \"alepha.jobs\",\n module: \"JobProvider\",\n } as LogEntry);\n return truncated;\n }\n\n // --- Sweep ----------------------------------------------------------------------------------------------------\n\n protected async sweep(): Promise<void> {\n if (this.stopping) return;\n this.log.trace(\"Starting job sweep\");\n const now = this.dt.now();\n const nowIso = now.toISOString();\n\n try {\n // 1. Due scheduled rows → pending + dispatch\n const dueWhere = this.executions.createQueryWhere();\n dueWhere.status = { eq: \"scheduled\" };\n dueWhere.scheduledAt = { lte: nowIso };\n const due = await this.executions.findMany({\n where: dueWhere,\n orderBy: { column: \"priority\", direction: \"asc\" },\n });\n for (const exec of due) {\n if (!this.jobs.has(exec.jobName)) continue;\n await this.executions.updateById(exec.id, { status: \"pending\" });\n await this.dispatchToQueueSafe(exec.jobName, exec.id);\n }\n\n // 2. Stale pending rows → re-dispatch\n const staleIso = now\n .subtract(this.config.staleThreshold, \"millisecond\")\n .toISOString();\n const staleWhere = this.executions.createQueryWhere();\n staleWhere.status = { eq: \"pending\" };\n staleWhere.createdAt = { lte: staleIso };\n const stale = await this.executions.findMany({\n where: staleWhere,\n orderBy: { column: \"priority\", direction: \"asc\" },\n });\n for (const exec of stale) {\n if (!this.jobs.has(exec.jobName)) continue;\n await this.dispatchToQueueSafe(exec.jobName, exec.id);\n }\n\n // 3. Crashed running rows → mark as failed + apply retry\n const runningWhere = this.executions.createQueryWhere();\n runningWhere.status = { eq: \"running\" };\n const running = await this.executions.findMany({ where: runningWhere });\n const nowMs = now.valueOf();\n for (const exec of running) {\n const reg = this.jobs.get(exec.jobName);\n if (!reg) continue;\n if (this.abortControllers.has(exec.id)) continue; // still alive locally\n const crashThresholdMs = reg.options.timeout\n ? this.dt.duration(reg.options.timeout).as(\"milliseconds\") * 2\n : this.config.runTimeout;\n const startedAtMs = exec.startedAt\n ? new Date(exec.startedAt).getTime()\n : 0;\n if (startedAtMs > 0 && nowMs - startedAtMs > crashThresholdMs) {\n this.log.warn(\n `Sweep: marking crashed ${exec.jobName} (${exec.id}) as failed`,\n );\n const err = new Error(\n \"Execution assumed crashed (recovered by sweep)\",\n );\n await this.handleFailure(exec.id, reg, exec.attempt, err, \"\");\n }\n }\n\n // 4. Trim ring buffer per job\n await this.trimRingBuffers();\n } catch (e) {\n this.log.error(\"Sweep failed\", { error: e });\n }\n }\n\n protected async dispatchToQueueSafe(\n jobName: string,\n executionId: string,\n ): Promise<void> {\n try {\n await this.dispatchToQueue(jobName, executionId);\n } catch (e) {\n this.log.warn(`Sweep failed to dispatch ${jobName} (${executionId})`, e);\n }\n }\n\n /**\n * Move a row from `scheduled` → `pending` and dispatch it.\n * Used by the optimistic retry/delay timer. If the sweep has already moved\n * the row, or another worker has claimed it, the UPDATE guard fails silently.\n */\n protected async dispatchScheduled(\n jobName: string,\n executionId: string,\n ): Promise<void> {\n if (this.stopping) return;\n try {\n await this.executions.updateOne(\n { id: { eq: executionId }, status: { eq: \"scheduled\" } },\n { status: \"pending\" },\n );\n await this.dispatchToQueueSafe(jobName, executionId);\n } catch {\n // Row already transitioned (sweep ran, another worker claimed, etc.)\n }\n }\n\n protected async trimRingBuffers(): Promise<void> {\n for (const [jobName, reg] of this.jobs) {\n const okLimit = reg.options.keep?.ok ?? this.config.keepLastSuccess;\n const errLimit = reg.options.keep?.error ?? this.config.keepLastError;\n if (okLimit > 0) {\n await this.trimByStatus(jobName, \"ok\", okLimit);\n }\n if (errLimit > 0) {\n await this.trimByStatus(jobName, \"error\", errLimit);\n }\n }\n }\n\n protected async trimByStatus(\n jobName: string,\n status: \"ok\" | \"error\",\n keep: number,\n ): Promise<void> {\n try {\n const rows = await this.executions.findMany({\n where: { jobName: { eq: jobName }, status: { eq: status } },\n orderBy: { column: \"startedAt\", direction: \"desc\" },\n limit: keep + 50,\n });\n if (rows.length <= keep) return;\n const toDelete = rows.slice(keep).map((r) => r.id);\n if (toDelete.length > 0) {\n await this.executions.deleteMany({ id: { inArray: toDelete } });\n this.log.debug(\n `Trimmed ${toDelete.length} ${status} rows for '${jobName}'`,\n );\n }\n } catch (e) {\n this.log.warn(`Failed to trim ${status} rows for '${jobName}'`, e);\n }\n }\n\n // --- Lifecycle -----------------------------------------------------------------------------------------------\n\n protected readonly onStart = $hook({\n on: \"start\",\n handler: async () => {\n // Validate that queue-mode jobs have a dispatcher registered.\n const needsQueue = [...this.jobs.values()].some(\n (j) => j.type === \"queue\",\n );\n if (needsQueue && !this.queueDispatch) {\n throw new AlephaError(\n `Queue-mode jobs are registered but no queue dispatcher is available. Add '.with(AlephaApiJobsQueue)' to your app.`,\n );\n }\n\n this.log.info(`Job system OK`, {\n dispatch: this.queueDispatch ? \"queue\" : \"inline-only\",\n jobs: this.jobs.size,\n });\n\n // Capture logs per execution context.\n this.alepha.events.on(\"log\", ({ entry }) => {\n const ctx = entry.context;\n if (!ctx) return;\n const entries = this.perExecutionLogs.get(ctx);\n if (!entries) return;\n entries.push(entry);\n });\n\n if (!this.alepha.isServerless()) {\n await this.sweep();\n }\n\n this.cronProvider.createCronJob(\n \"api:jobs:sweep\",\n SWEEP_CRON,\n async () => {\n await this.sweep();\n },\n true,\n );\n },\n });\n\n protected readonly onStop = $hook({\n on: \"stop\",\n handler: async () => {\n this.stopping = true;\n if (this.inFlight.size > 0) {\n this.log.info(`Draining ${this.inFlight.size} in-flight job(s)...`);\n await Promise.race([\n Promise.allSettled([...this.inFlight]),\n this.dt.wait([this.config.drainTimeout, \"millisecond\"]),\n ]);\n }\n if (this.abortControllers.size > 0) {\n this.log.warn(\n `Aborting ${this.abortControllers.size} remaining job(s) after drain timeout`,\n );\n for (const controller of this.abortControllers.values()) {\n controller.abort();\n }\n }\n },\n });\n\n // --- Helpers -------------------------------------------------------------------------------------------------\n\n protected getRegistration(name: string): JobRuntimeRegistration {\n const registration = this.jobs.get(name);\n if (!registration) {\n throw new AlephaError(`Job not registered: ${name}`);\n }\n return registration;\n }\n}\n\nexport { PRIORITY_MAP, PRIORITY_REVERSE };\n","import {\n $inject,\n type Async,\n createPrimitive,\n KIND,\n PipelinePrimitive,\n type PipelinePrimitiveOptions,\n type Static,\n type TSchema,\n} from \"alepha\";\nimport type { DateTime, DurationLike } from \"alepha/datetime\";\nimport {\n JobProvider,\n type JobTriggerContext,\n type PushManyItem,\n type PushOptions,\n} from \"../providers/JobProvider.ts\";\n\n/**\n * Job primitive for defining scheduled (cron) or queued (push) tasks.\n *\n * A job must be either **cron-only** (pass `cron`) or **queue-only**\n * (pass `schema`), never both. To run scheduled work that processes\n * payloads, compose two jobs: a cron that pushes payloads, and a\n * queue job that handles them.\n */\nexport const $job = <T extends TSchema = TSchema>(\n options: JobPrimitiveOptions<T>,\n): JobPrimitive<T> => {\n return createPrimitive(JobPrimitive<T>, options);\n};\n\n// -----------------------------------------------------------------------------------------------------------------\n\nexport interface JobHandlerArgs<T extends TSchema = TSchema> {\n payload: Static<T>;\n attempt: number;\n now: DateTime;\n signal: AbortSignal;\n executionId: string;\n}\n\nexport interface JobRetryBackoff {\n initial: DurationLike;\n factor?: number;\n max?: DurationLike;\n jitter?: boolean;\n}\n\nexport interface JobRetryOptions {\n retries: number;\n backoff?: DurationLike | JobRetryBackoff;\n when?: (error: Error) => boolean;\n}\n\nexport type JobPriority = \"critical\" | \"high\" | \"normal\" | \"low\";\n\nexport interface JobPrimitiveOptions<T extends TSchema = TSchema>\n extends PipelinePrimitiveOptions {\n /**\n * Optional explicit job name. Defaults to `ClassName.propertyKey`.\n * Recommended convention for framework-internal jobs: `api:module:jobName`.\n */\n name?: string;\n\n /**\n * Human-readable description (shown in the admin UI).\n */\n description?: string;\n\n /**\n * Payload schema (TypeBox). When set, the job is queue-mode.\n * Must not be combined with `cron`.\n */\n schema?: T;\n\n /**\n * Cron expression for recurring execution. When set, the job is cron-mode.\n * Must not be combined with `schema`.\n */\n cron?: string;\n\n /**\n * Retry policy for queue-mode jobs.\n * Cron-mode jobs do not retry — the next tick re-runs.\n */\n retry?: JobRetryOptions;\n\n /**\n * Max execution time per attempt. Handler receives an `AbortSignal`.\n */\n timeout?: DurationLike;\n\n /**\n * Default priority for pushed jobs. Used by the sweep to order\n * dispatch when there is a backlog. Real-time queue consumption\n * is FIFO.\n * @default \"normal\"\n */\n priority?: JobPriority;\n\n /**\n * Whether to record successful executions.\n *\n * - `\"error\"` (default for cron, default for queue): only error/cancelled rows kept\n * - `\"all\"`: keep success rows too (bounded by `keepLastSuccess`)\n * - `\"none\"`: fire-and-forget, no row even on error\n *\n * Note: queue-mode jobs always write a `pending` row at push time (outbox).\n * This setting controls whether that row is kept on success.\n */\n record?: \"error\" | \"all\" | \"none\";\n\n /**\n * Override the global ring-buffer trim for this job.\n *\n * - `{ ok: 0, error: 0 }` — **keep forever** (no sweep trim). Useful for\n * audit-heavy jobs where retention is time-based (handled by a separate\n * cron) rather than count-based.\n * - `{ ok: 50 }` — keep last 50 successes; fall back to global default for errors.\n * - omitted — use global `keepLastSuccess` / `keepLastError` from `jobConfig`.\n */\n keep?: {\n ok?: number;\n error?: number;\n };\n\n /**\n * Handler function. For cron-mode, `payload` is `undefined`.\n */\n handler: (args: JobHandlerArgs<T>) => Async<void>;\n}\n\n// -----------------------------------------------------------------------------------------------------------------\n\nexport class JobPrimitive<\n T extends TSchema = TSchema,\n> extends PipelinePrimitive<JobPrimitiveOptions<T>> {\n protected readonly jobProvider = $inject(JobProvider);\n\n public get name(): string {\n return (\n this.options.name ??\n `${this.config.service.name}.${this.config.propertyKey}`\n );\n }\n\n protected onInit() {\n const handler = this.handler.run.bind(this.handler);\n this.jobProvider.registerJob(this.name, { ...this.options, handler });\n }\n\n /**\n * Push a single payload to the queue (queue-mode only).\n */\n public async push(\n payload: Static<T>,\n options?: PushOptions,\n ): Promise<string> {\n return this.jobProvider.push(this.name, payload, options);\n }\n\n /**\n * Push multiple payloads at once (queue-mode only).\n * Batched INSERT + batched queue send when supported.\n */\n public async pushMany(items: Array<PushManyItem<T>>): Promise<string[]> {\n return this.jobProvider.pushMany(this.name, items);\n }\n\n /**\n * Cancel a pending or running execution.\n */\n public async cancel(executionId: string): Promise<void> {\n return this.jobProvider.cancel(executionId);\n }\n\n /**\n * Manually fire a cron-mode job, or trigger a queue-mode job with an explicit payload.\n */\n public async trigger(context?: JobTriggerContext<T>): Promise<void> {\n return this.jobProvider.trigger(this.name, context);\n }\n}\n\n$job[KIND] = JobPrimitive;\n","import { $inject, Alepha, AlephaError, t } from \"alepha\";\nimport { $logger } from \"alepha/logger\";\nimport { $repository, sql } from \"alepha/orm\";\nimport { NotFoundError } from \"alepha/server\";\nimport { jobExecutionEntity } from \"../entities/jobExecutionEntity.ts\";\nimport { $job } from \"../primitives/$job.ts\";\nimport type { JobTriggerContext } from \"../providers/JobProvider.ts\";\nimport { JobProvider } from \"../providers/JobProvider.ts\";\nimport type { JobExecutionQuery } from \"../schemas/jobExecutionQuerySchema.ts\";\nimport type { JobRegistration } from \"../schemas/jobRegistrationSchema.ts\";\n\n/**\n * Admin surface for the job system.\n *\n * Six methods: list jobs, list executions, get execution,\n * trigger, retry, cancel. Everything else lives in events — any\n * analytics/observability is an external concern that subscribes\n * to `job:begin` / `job:success` / `job:error`.\n */\nexport class JobService {\n protected readonly alepha = $inject(Alepha);\n protected readonly log = $logger();\n protected readonly jobProvider = $inject(JobProvider);\n protected readonly executions = $repository(jobExecutionEntity);\n\n protected computeCan(status: string) {\n return {\n retry: status === \"error\" || status === \"cancelled\",\n cancel:\n status === \"pending\" || status === \"running\" || status === \"scheduled\",\n };\n }\n\n /**\n * List every registered job with recent ok/error counts and lastRun.\n * One aggregate query covers all jobs.\n */\n public async listJobs(): Promise<JobRegistration[]> {\n const registry = this.jobProvider.getRegisteredJobs();\n\n const aggRows = await this.executions.query(\n (e) => sql`\n SELECT\n ${e.jobName} AS job_name,\n ${e.status} AS status,\n COUNT(*) AS count,\n MAX(${e.completedAt}) AS last_run\n FROM ${e}\n WHERE ${e.status} IN ('ok', 'error')\n GROUP BY ${e.jobName}, ${e.status}\n `,\n t.object({\n job_name: t.string(),\n status: t.string(),\n count: t.string(),\n last_run: t.optional(t.nullable(t.union([t.string(), t.number()]))),\n }),\n );\n\n const toIso = (\n v: string | number | null | undefined,\n ): string | undefined => {\n if (v === null || v === undefined) return undefined;\n if (typeof v === \"number\") return new Date(v).toISOString();\n return v;\n };\n\n const byJob = new Map<\n string,\n { ok: number; error: number; lastRun?: string }\n >();\n for (const row of aggRows) {\n const entry = byJob.get(row.job_name) ?? { ok: 0, error: 0 };\n if (row.status === \"ok\") entry.ok = Number(row.count);\n if (row.status === \"error\") entry.error = Number(row.count);\n const iso = toIso(row.last_run);\n if (iso && (!entry.lastRun || iso > entry.lastRun)) {\n entry.lastRun = iso;\n }\n byJob.set(row.job_name, entry);\n }\n\n const result: JobRegistration[] = [];\n for (const [name, reg] of registry) {\n const opts = reg.options;\n const counts = byJob.get(name) ?? { ok: 0, error: 0 };\n result.push({\n name,\n description: opts.description,\n type: reg.type,\n cron: opts.cron,\n priority: (opts.priority ?? \"normal\") as JobRegistration[\"priority\"],\n timeout: opts.timeout ? String(opts.timeout) : undefined,\n retry: opts.retry\n ? {\n retries: opts.retry.retries,\n hasBackoff: Boolean(opts.retry.backoff),\n }\n : undefined,\n recent: counts,\n });\n }\n return result;\n }\n\n /**\n * Recent executions for a single job, ORDER BY startedAt DESC.\n */\n public async getExecutions(jobName: string, query: JobExecutionQuery = {}) {\n const registry = this.jobProvider.getRegisteredJobs();\n if (!registry.has(jobName)) {\n throw new NotFoundError(`Job not found: ${jobName}`);\n }\n const where = this.executions.createQueryWhere();\n where.jobName = { eq: jobName };\n if (query.status) {\n where.status = { eq: query.status };\n }\n const rows = await this.executions.findMany({\n where,\n orderBy: { column: \"startedAt\", direction: \"desc\" },\n limit: query.limit ?? 20,\n });\n return rows.map((row) => ({\n ...row,\n can: this.computeCan(row.status),\n }));\n }\n\n /**\n * Full execution detail (includes captured logs).\n */\n public async getExecution(id: string) {\n const execution = await this.executions.findById(id);\n if (!execution) {\n throw new NotFoundError(`Execution not found: ${id}`);\n }\n return {\n ...execution,\n can: this.computeCan(execution.status),\n };\n }\n\n /**\n * Manual trigger (cron jobs) or push-with-payload (queue jobs).\n */\n public async triggerJob(\n name: string,\n context?: JobTriggerContext,\n ): Promise<{ ok: boolean }> {\n const jobPrimitives = this.alepha.primitives($job);\n const job = jobPrimitives.find((j) => j.name === name);\n if (!job) {\n throw new NotFoundError(`Job not found: ${name}`);\n }\n this.log.info(`Triggering job '${name}'`, {\n triggeredBy: context?.triggeredByName ?? context?.triggeredBy,\n });\n await job.trigger(context);\n return { ok: true };\n }\n\n /**\n * Retry a dead or cancelled execution by re-pushing with the original payload.\n */\n public async retryExecution(\n id: string,\n context?: { triggeredBy?: string; triggeredByName?: string },\n ): Promise<{ ok: boolean }> {\n const execution = await this.executions.findById(id);\n if (!execution) {\n throw new NotFoundError(`Execution not found: ${id}`);\n }\n if (execution.status !== \"error\" && execution.status !== \"cancelled\") {\n throw new AlephaError(\n `Cannot retry execution in '${execution.status}' status`,\n );\n }\n\n const jobPrimitives = this.alepha.primitives($job);\n const job = jobPrimitives.find((j) => j.name === execution.jobName);\n if (!job) {\n throw new NotFoundError(`Job not found: ${execution.jobName}`);\n }\n\n this.log.info(`Retrying execution ${id}`, {\n jobName: execution.jobName,\n previousStatus: execution.status,\n triggeredBy: context?.triggeredByName ?? context?.triggeredBy,\n });\n\n if (execution.payload) {\n await job.push(execution.payload as any);\n } else {\n await job.trigger({\n triggeredBy: context?.triggeredBy,\n triggeredByName: context?.triggeredByName,\n });\n }\n return { ok: true };\n }\n\n public async cancelExecution(\n id: string,\n context?: { cancelledBy?: string; cancelledByName?: string },\n ): Promise<{ ok: boolean }> {\n this.log.info(`Cancelling execution ${id}`, {\n cancelledBy: context?.cancelledByName ?? context?.cancelledBy,\n });\n await this.jobProvider.cancel(id, {\n cancelledBy: context?.cancelledBy,\n cancelledByName: context?.cancelledByName,\n });\n return { ok: true };\n }\n}\n","import { $inject, t } from \"alepha\";\nimport { $secure } from \"alepha/security\";\nimport { $action, okSchema } from \"alepha/server\";\nimport { jobExecutionQuerySchema } from \"../schemas/jobExecutionQuerySchema.ts\";\nimport { jobExecutionResourceSchema } from \"../schemas/jobExecutionResourceSchema.ts\";\nimport { jobRegistrationSchema } from \"../schemas/jobRegistrationSchema.ts\";\nimport { triggerJobSchema } from \"../schemas/triggerJobSchema.ts\";\nimport { JobService } from \"../services/JobService.ts\";\n\n/**\n * Minimal admin surface for the job system. Six endpoints.\n */\nexport class AdminJobController {\n protected readonly url: string = \"/jobs\";\n protected readonly group: string = \"admin:jobs\";\n protected readonly jobService = $inject(JobService);\n\n public readonly listJobs = $action({\n path: this.url,\n group: this.group,\n use: [$secure({ permissions: [\"admin:job:read\"] })],\n schema: {\n response: t.array(jobRegistrationSchema),\n },\n handler: () => this.jobService.listJobs(),\n });\n\n public readonly listExecutions = $action({\n path: `${this.url}/:name/executions`,\n group: this.group,\n use: [$secure({ permissions: [\"admin:job:read\"] })],\n schema: {\n params: t.object({ name: t.text() }),\n query: jobExecutionQuerySchema,\n response: t.array(jobExecutionResourceSchema),\n },\n handler: ({ params, query }) =>\n this.jobService.getExecutions(params.name, query),\n });\n\n public readonly getExecution = $action({\n path: `${this.url}/executions/:id`,\n group: this.group,\n use: [$secure({ permissions: [\"admin:job:read\"] })],\n schema: {\n params: t.object({ id: t.uuid() }),\n response: jobExecutionResourceSchema,\n },\n handler: ({ params }) => this.jobService.getExecution(params.id),\n });\n\n public readonly triggerJob = $action({\n method: \"POST\",\n path: `${this.url}/:name/trigger`,\n group: this.group,\n use: [$secure({ permissions: [\"admin:job:trigger\"] })],\n schema: {\n params: t.object({ name: t.text() }),\n body: triggerJobSchema,\n response: okSchema,\n },\n handler: ({ params, body, user }) =>\n this.jobService.triggerJob(params.name, {\n payload: body.payload,\n triggeredBy: user?.id,\n triggeredByName: user?.name,\n }),\n });\n\n public readonly retryExecution = $action({\n method: \"POST\",\n path: `${this.url}/executions/:id/retry`,\n group: this.group,\n use: [$secure({ permissions: [\"admin:job:trigger\"] })],\n schema: {\n params: t.object({ id: t.uuid() }),\n response: okSchema,\n },\n handler: ({ params, user }) =>\n this.jobService.retryExecution(params.id, {\n triggeredBy: user?.id,\n triggeredByName: user?.name,\n }),\n });\n\n public readonly cancelExecution = $action({\n method: \"POST\",\n path: `${this.url}/executions/:id/cancel`,\n group: this.group,\n use: [$secure({ permissions: [\"admin:job:cancel\"] })],\n schema: {\n params: t.object({ id: t.uuid() }),\n response: okSchema,\n },\n handler: ({ params, user }) =>\n this.jobService.cancelExecution(params.id, {\n cancelledBy: user?.id,\n cancelledByName: user?.name,\n }),\n });\n}\n","import { $inject, t } from \"alepha\";\nimport { $queue } from \"alepha/queue\";\nimport { JobProvider } from \"./JobProvider.ts\";\n\n/**\n * Plumbs outbox-style dispatch through `AlephaQueue`.\n *\n * Registered only when the app imports `AlephaApiJobsQueue`. Sets\n * `JobProvider.queueDispatch` eagerly at instantiation so queue-mode jobs\n * can dispatch regardless of start-hook ordering.\n */\nexport class JobQueueProvider {\n protected readonly jobProvider = $inject(JobProvider);\n\n protected readonly queue = $queue({\n name: \"api:jobs:dispatch\",\n schema: t.object({ jobName: t.text(), executionId: t.text() }),\n handler: async (msg) => {\n await this.jobProvider.processExecution(\n msg.payload.jobName,\n msg.payload.executionId,\n );\n },\n });\n\n constructor() {\n // Install the dispatcher immediately — before any start hook runs,\n // so JobProvider can validate presence and queue-mode push works\n // from any lifecycle point.\n this.wireDispatcher();\n }\n\n protected wireDispatcher(): void {\n // `$inject` is resolved by the time constructor runs; assignment is safe.\n this.jobProvider.queueDispatch = (jobName, executionId) =>\n this.push(jobName, executionId);\n }\n\n public async push(jobName: string, executionId: string): Promise<void> {\n await this.queue.push({ jobName, executionId });\n }\n}\n","import { $module } from \"alepha\";\nimport type { DateTime } from \"alepha/datetime\";\nimport { AlephaLock } from \"alepha/lock\";\nimport { AlephaQueue } from \"alepha/queue\";\nimport { AlephaScheduler } from \"alepha/scheduler\";\nimport { AdminJobController } from \"./controllers/AdminJobController.ts\";\nimport { JobProvider } from \"./providers/JobProvider.ts\";\nimport { JobQueueProvider } from \"./providers/JobQueueProvider.ts\";\nimport { JobService } from \"./services/JobService.ts\";\n\n// -----------------------------------------------------------------------------------------------------------------\n\nexport * from \"./controllers/AdminJobController.ts\";\nexport * from \"./entities/jobExecutionEntity.ts\";\nexport * from \"./primitives/$job.ts\";\nexport * from \"./providers/JobProvider.ts\";\nexport * from \"./providers/JobQueueProvider.ts\";\nexport * from \"./schemas/jobConfigAtom.ts\";\nexport * from \"./schemas/jobExecutionQuerySchema.ts\";\nexport * from \"./schemas/jobExecutionResourceSchema.ts\";\nexport * from \"./schemas/jobRegistrationSchema.ts\";\nexport * from \"./schemas/triggerJobSchema.ts\";\nexport * from \"./services/JobService.ts\";\n\n// -----------------------------------------------------------------------------------------------------------------\n\ndeclare module \"alepha\" {\n interface Hooks {\n \"job:begin\": { name: string; now: DateTime; executionId: string };\n \"job:success\": { name: string; executionId: string };\n \"job:error\": { name: string; error: Error; executionId: string };\n \"job:cancel\": { name: string; executionId: string };\n \"job:end\": { name: string; executionId: string };\n }\n}\n\n// -----------------------------------------------------------------------------------------------------------------\n\n/**\n * Job execution framework — cron and durable queue work with a single primitive.\n *\n * A `$job` is either **cron-only** (declares `cron`) or **queue-only** (declares `schema`).\n * Cron jobs run inline on their schedule and only record errors by default.\n * Queue jobs use the outbox pattern: push commits to DB first, then notifies via queue.\n *\n * **This module provides cron support only.** To enable queue-mode jobs, also\n * import {@link AlephaApiJobsQueue} — it brings in the queue layer and infrastructure\n * binding (e.g. Cloudflare Queues). Cron-only deployments (Vercel, CF-without-Queues)\n * do not need `AlephaApiJobsQueue`.\n *\n * @module alepha.api.jobs\n */\nexport const AlephaApiJobs = $module({\n name: \"alepha.api.jobs\",\n imports: [AlephaScheduler, AlephaLock],\n services: [JobProvider, JobService, AdminJobController],\n});\n\n/**\n * Queue support for `$job`. Import alongside {@link AlephaApiJobs} when your\n * app declares queue-mode jobs (any `$job` with a `schema`).\n *\n * Adds `JobQueueProvider` which plumbs the outbox dispatch through `AlephaQueue`.\n *\n * @module alepha.api.jobs.queue\n */\nexport const AlephaApiJobsQueue = $module({\n name: \"alepha.api.jobs.queue\",\n imports: [AlephaApiJobs, AlephaQueue],\n services: [JobQueueProvider],\n});\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../../src/api/jobs/schemas/jobExecutionQuerySchema.ts","../../../src/api/jobs/entities/jobExecutionEntity.ts","../../../src/api/jobs/schemas/jobExecutionResourceSchema.ts","../../../src/api/jobs/schemas/jobRegistrationSchema.ts","../../../src/api/jobs/schemas/triggerJobSchema.ts","../../../src/api/jobs/schemas/jobConfigAtom.ts","../../../src/api/jobs/providers/JobProvider.ts","../../../src/api/jobs/primitives/$job.ts","../../../src/api/jobs/services/JobService.ts","../../../src/api/jobs/controllers/AdminJobController.ts","../../../src/api/jobs/providers/JobQueueProvider.ts","../../../src/api/jobs/index.ts"],"sourcesContent":["import { type Static, t } from \"alepha\";\n\nexport const jobExecutionQuerySchema = t.object({\n status: t.optional(\n t.enum([\"pending\", \"running\", \"scheduled\", \"ok\", \"error\", \"cancelled\"]),\n ),\n limit: t.optional(t.integer({ minimum: 1, maximum: 200, default: 20 })),\n});\n\nexport type JobExecutionQuery = Static<typeof jobExecutionQuerySchema>;\n","import { type Static, t } from \"alepha\";\nimport { logEntrySchema } from \"alepha/logger\";\nimport { $entity, db } from \"alepha/orm\";\n\n/**\n * Job execution record.\n *\n * Stores durable state for queue-mode jobs (outbox pattern) and error records\n * for cron-mode jobs. Successful executions are trimmed by the sweep to keep\n * the last N rows per job (configurable via `jobConfig.keepLastSuccess`).\n *\n * Status transitions:\n * - queue push → pending\n * - worker claim → running\n * - success → ok\n * - terminal failure → error\n * - retry → scheduled (with scheduledAt = now + backoff)\n * - delay → scheduled (with scheduledAt = now + delay)\n * - sweep picks due ones → pending\n * - cancel → cancelled\n */\nexport const jobExecutionEntity = $entity({\n name: \"job_executions\",\n schema: t.object({\n id: db.primaryKey(t.uuid()),\n createdAt: db.createdAt(),\n updatedAt: db.updatedAt(),\n\n jobName: t.text(),\n key: t.optional(t.nullable(t.text())),\n\n status: db.default(\n t.enum([\"pending\", \"running\", \"scheduled\", \"ok\", \"error\", \"cancelled\"]),\n \"pending\",\n ),\n priority: db.default(t.integer({ minimum: 0, maximum: 3 }), 2),\n\n attempt: db.default(t.integer(), 0),\n maxAttempts: db.default(t.integer(), 1),\n\n payload: t.optional(t.record(t.text(), t.any())),\n\n scheduledAt: t.optional(t.datetime()),\n startedAt: t.optional(t.datetime()),\n completedAt: t.optional(t.datetime()),\n\n error: t.optional(t.text()),\n logs: t.optional(t.array(logEntrySchema)),\n\n triggeredBy: t.optional(t.text()),\n triggeredByName: t.optional(t.text()),\n cancelledBy: t.optional(t.text()),\n cancelledByName: t.optional(t.text()),\n }),\n indexes: [\n { columns: [\"jobName\", \"status\", \"scheduledAt\"] },\n { columns: [\"jobName\", \"startedAt\"] },\n { columns: [\"jobName\", \"key\"], unique: true },\n ],\n});\n\nexport type JobExecutionEntity = Static<typeof jobExecutionEntity.schema>;\n\nexport type JobStatus =\n | \"pending\"\n | \"running\"\n | \"scheduled\"\n | \"ok\"\n | \"error\"\n | \"cancelled\";\n","import { type Static, t } from \"alepha\";\nimport { jobExecutionEntity } from \"../entities/jobExecutionEntity.ts\";\n\nexport const jobExecutionResourceSchema = t.extend(\n jobExecutionEntity.schema,\n {\n can: t.object({\n retry: t.boolean(),\n cancel: t.boolean(),\n }),\n },\n {\n title: \"JobExecutionResource\",\n description: \"A job execution row with derived actions.\",\n },\n);\n\nexport type JobExecutionResource = Static<typeof jobExecutionResourceSchema>;\n","import { type Static, t } from \"alepha\";\n\nexport const jobRegistrationSchema = t.object({\n name: t.text(),\n description: t.optional(t.text()),\n type: t.enum([\"cron\", \"queue\"]),\n priority: t.enum([\"critical\", \"high\", \"normal\", \"low\"]),\n cron: t.optional(t.text()),\n timeout: t.optional(t.text()),\n retry: t.optional(\n t.object({\n retries: t.integer(),\n hasBackoff: t.boolean(),\n }),\n ),\n recent: t.object({\n ok: t.integer(),\n error: t.integer(),\n lastRun: t.optional(t.datetime()),\n }),\n});\n\nexport type JobRegistration = Static<typeof jobRegistrationSchema>;\n","import { type Static, t } from \"alepha\";\n\nexport const triggerJobSchema = t.object({\n payload: t.optional(t.record(t.text(), t.any())),\n});\n\nexport type TriggerJob = Static<typeof triggerJobSchema>;\n","import { $atom, type Static, t } from \"alepha\";\n\nexport const jobConfig = $atom({\n name: \"alepha.jobs\",\n description: \"Configuration for the $job primitive.\",\n schema: t.object({\n sweepInterval: t.integer({\n description: \"Sweep cron interval in milliseconds.\",\n }),\n staleThreshold: t.integer({\n description: \"Pending age (ms) before the sweep re-dispatches it.\",\n }),\n runTimeout: t.integer({\n description:\n \"Running age (ms) before assumed crash (fallback when no per-job timeout).\",\n }),\n keepLastSuccess: t.integer({\n description:\n \"Max successful rows to keep per job. Set 0 to disable and delete on success.\",\n }),\n keepLastError: t.integer({\n description: \"Max error rows to keep per job.\",\n }),\n logMaxEntries: t.integer({\n description: \"Max log entries captured per execution.\",\n }),\n drainTimeout: t.integer({\n description: \"Max time (ms) to wait for in-flight jobs during shutdown.\",\n }),\n }),\n default: {\n sweepInterval: 300_000,\n staleThreshold: 300_000,\n runTimeout: 1_800_000,\n keepLastSuccess: 10,\n keepLastError: 10,\n logMaxEntries: 100,\n drainTimeout: 30_000,\n },\n});\n\nexport type JobConfig = Static<typeof jobConfig.schema>;\n\ndeclare module \"alepha\" {\n interface State {\n [jobConfig.key]: JobConfig;\n }\n}\n","import {\n $hook,\n $inject,\n $state,\n Alepha,\n AlephaError,\n type Static,\n type TSchema,\n} from \"alepha\";\nimport { DateTimeProvider, type DurationLike } from \"alepha/datetime\";\nimport type { LogEntry } from \"alepha/logger\";\nimport { $logger } from \"alepha/logger\";\nimport { $repository } from \"alepha/orm\";\nimport { CronProvider } from \"alepha/scheduler\";\nimport {\n type JobStatus,\n jobExecutionEntity,\n} from \"../entities/jobExecutionEntity.ts\";\nimport type {\n JobPrimitiveOptions,\n JobPriority,\n JobRetryBackoff,\n JobRetryOptions,\n} from \"../primitives/$job.ts\";\nimport { jobConfig } from \"../schemas/jobConfigAtom.ts\";\n\n// -----------------------------------------------------------------------------------------------------------------\n\nconst PRIORITY_MAP: Record<JobPriority, number> = {\n critical: 0,\n high: 1,\n normal: 2,\n low: 3,\n};\n\nconst PRIORITY_REVERSE: Record<number, JobPriority> = {\n 0: \"critical\",\n 1: \"high\",\n 2: \"normal\",\n 3: \"low\",\n};\n\nconst SWEEP_CRON = \"*/5 * * * *\";\n\n// -----------------------------------------------------------------------------------------------------------------\n\nexport interface PushOptions {\n delay?: DurationLike;\n key?: string;\n priority?: JobPriority;\n scheduledAt?: Date;\n triggeredBy?: string;\n triggeredByName?: string;\n}\n\nexport interface PushManyItem<T extends TSchema = TSchema> {\n payload: Static<T>;\n key?: string;\n delay?: DurationLike;\n priority?: JobPriority;\n scheduledAt?: Date;\n}\n\nexport interface JobTriggerContext<T extends TSchema = TSchema> {\n payload?: Static<T>;\n triggeredBy?: string;\n triggeredByName?: string;\n}\n\nexport interface CancelContext {\n cancelledBy?: string;\n cancelledByName?: string;\n}\n\ninterface JobRuntimeRegistration {\n name: string;\n options: JobPrimitiveOptions;\n type: \"cron\" | \"queue\";\n}\n\n// -----------------------------------------------------------------------------------------------------------------\n\n/**\n * Coordinates cron (scheduler) and queue (push) jobs with a durable outbox\n * table and a single reconciliation sweep.\n *\n * Queue-mode flow:\n * push() → INSERT row (pending) + queue.send({ executionId })\n * worker → SELECT row → UPDATE running → handler → DELETE (ok) / UPDATE (error)\n *\n * Cron-mode flow:\n * scheduler tick → handler runs inline → INSERT row only on error\n *\n * Sweep responsibilities (every `sweepInterval`):\n * - re-enqueue pending rows older than `staleThreshold`\n * - fail running rows older than `max(timeout*2, runTimeout)`\n * - move `scheduled` rows with `scheduledAt <= now` to pending + enqueue\n * - trim per-job history beyond `keepLastSuccess` / `keepLastError`\n */\nexport class JobProvider {\n protected readonly alepha = $inject(Alepha);\n protected readonly dt = $inject(DateTimeProvider);\n protected readonly cronProvider = $inject(CronProvider);\n protected readonly config = $state(jobConfig);\n protected readonly log = $logger();\n protected readonly executions = $repository(jobExecutionEntity);\n\n protected readonly jobs = new Map<string, JobRuntimeRegistration>();\n protected readonly inFlight = new Set<Promise<void>>();\n protected readonly abortControllers = new Map<string, AbortController>();\n protected readonly perExecutionLogs = new Map<string, LogEntry[]>();\n protected stopping = false;\n\n /**\n * Set by `JobQueueProvider` when `AlephaApiJobsQueue` is loaded.\n * When null, queue-mode jobs cannot be pushed.\n */\n public queueDispatch:\n | ((jobName: string, executionId: string) => Promise<void>)\n | null = null;\n\n // --- Registration -----------------------------------------------------------------------------------------------\n\n public registerJob(name: string, options: JobPrimitiveOptions): void {\n if (this.jobs.has(name)) {\n throw new AlephaError(`Job already registered: ${name}`);\n }\n if (options.cron && options.schema) {\n throw new AlephaError(\n `Job '${name}' declares both 'cron' and 'schema'. A job must be either cron-only (recurring) or queue-only (push-based). Split into two jobs.`,\n );\n }\n if (!options.cron && !options.schema) {\n throw new AlephaError(\n `Job '${name}' must declare either 'cron' (for recurring tasks) or 'schema' (for queue-mode tasks).`,\n );\n }\n\n const type: \"cron\" | \"queue\" = options.cron ? \"cron\" : \"queue\";\n this.jobs.set(name, { name, options, type });\n this.log.debug(`Registered ${type} job '${name}'`, {\n cron: options.cron,\n priority: options.priority ?? \"normal\",\n retries: options.retry?.retries ?? 0,\n });\n\n if (options.cron) {\n this.cronProvider.createCronJob(name, options.cron, async () => {\n try {\n await this.runCron(name);\n } catch (error) {\n this.log.error(`Cron tick failed for job '${name}'`, error);\n }\n });\n }\n }\n\n public getRegisteredJobs(): Map<string, JobRuntimeRegistration> {\n return this.jobs;\n }\n\n // --- Cron execution (inline, no queue) --------------------------------------------------------------------------\n\n protected async runCron(name: string): Promise<void> {\n const registration = this.getRegistration(name);\n if (registration.type !== \"cron\") {\n throw new AlephaError(`Job '${name}' is not cron-mode`);\n }\n if (this.stopping) return;\n\n const executionId = crypto.randomUUID();\n const promise = this.executeInline(registration, executionId, {\n payload: undefined,\n attempt: 1,\n triggeredBy: \"system\",\n triggeredByName: \"system (cron)\",\n });\n this.inFlight.add(promise);\n try {\n await promise;\n } finally {\n this.inFlight.delete(promise);\n }\n }\n\n /**\n * Execute a cron handler inline. Records a row only on error (or always,\n * when `record: 'all'`). No DB writes on the happy path by default.\n */\n protected async executeInline(\n registration: JobRuntimeRegistration,\n executionId: string,\n ctx: {\n payload: unknown;\n attempt: number;\n triggeredBy?: string;\n triggeredByName?: string;\n },\n ): Promise<void> {\n const opts = registration.options;\n const name = registration.name;\n const record = opts.record ?? \"error\";\n const contextId = this.alepha.context.createContextId();\n this.perExecutionLogs.set(contextId, []);\n\n const abortController = new AbortController();\n this.abortControllers.set(executionId, abortController);\n let timeoutId: ReturnType<typeof setTimeout> | undefined;\n if (opts.timeout) {\n const ms = this.dt.duration(opts.timeout).as(\"milliseconds\");\n timeoutId = setTimeout(() => abortController.abort(), ms);\n }\n\n const startedAt = this.dt.now();\n\n try {\n await this.alepha.context.run(\n async () => {\n await this.alepha.events.emit(\"job:begin\", {\n name,\n now: startedAt,\n executionId,\n });\n\n try {\n await opts.handler({\n payload: ctx.payload,\n attempt: ctx.attempt,\n now: startedAt,\n signal: abortController.signal,\n executionId,\n });\n\n if (record === \"all\") {\n await this.writeTerminalRow(executionId, name, \"ok\", {\n payload: ctx.payload,\n attempt: ctx.attempt,\n startedAt,\n error: undefined,\n context: contextId,\n triggeredBy: ctx.triggeredBy,\n triggeredByName: ctx.triggeredByName,\n });\n }\n\n await this.alepha.events.emit(\n \"job:success\",\n { name, executionId },\n { catch: true },\n );\n } catch (error) {\n const err =\n error instanceof Error ? error : new Error(String(error));\n if (record !== \"none\") {\n await this.writeTerminalRow(executionId, name, \"error\", {\n payload: ctx.payload,\n attempt: ctx.attempt,\n startedAt,\n error: err,\n context: contextId,\n triggeredBy: ctx.triggeredBy,\n triggeredByName: ctx.triggeredByName,\n });\n }\n await this.alepha.events.emit(\n \"job:error\",\n { name, error: err, executionId },\n { catch: true },\n );\n } finally {\n if (timeoutId) clearTimeout(timeoutId);\n this.abortControllers.delete(executionId);\n await this.alepha.events.emit(\n \"job:end\",\n { name, executionId },\n { catch: true },\n );\n }\n },\n { context: contextId },\n );\n } finally {\n this.perExecutionLogs.delete(contextId);\n }\n }\n\n protected async writeTerminalRow(\n executionId: string,\n jobName: string,\n status: \"ok\" | \"error\",\n fields: {\n payload: unknown;\n attempt: number;\n startedAt: ReturnType<DateTimeProvider[\"now\"]>;\n error?: Error;\n context: string;\n triggeredBy?: string;\n triggeredByName?: string;\n },\n ): Promise<void> {\n try {\n const logs =\n status === \"error\" ? this.snapshotLogs(fields.context) : undefined;\n await this.executions.create({\n id: executionId,\n jobName,\n status,\n payload: fields.payload as Record<string, unknown> | undefined,\n attempt: fields.attempt,\n maxAttempts: fields.attempt,\n startedAt: fields.startedAt.toISOString(),\n completedAt: this.dt.nowISOString(),\n error: fields.error?.message,\n logs,\n triggeredBy: fields.triggeredBy,\n triggeredByName: fields.triggeredByName,\n });\n } catch (e) {\n this.log.warn(`Failed to write terminal row for ${executionId}`, e);\n }\n }\n\n // --- Queue push -------------------------------------------------------------------------------------------------\n\n public async push(\n name: string,\n payload: unknown,\n options?: PushOptions,\n ): Promise<string> {\n const registration = this.getRegistration(name);\n if (registration.type !== \"queue\") {\n throw new AlephaError(\n `Job '${name}' is not queue-mode (no schema declared). Use trigger() instead.`,\n );\n }\n const opts = registration.options;\n const validated = this.alepha.codec.validate(opts.schema!, payload);\n\n const priority =\n PRIORITY_MAP[options?.priority ?? opts.priority ?? \"normal\"];\n const maxAttempts = (opts.retry?.retries ?? 0) + 1;\n\n const isDelayed = options?.delay || options?.scheduledAt;\n const status: JobStatus = isDelayed ? \"scheduled\" : \"pending\";\n\n let scheduledAt: string | undefined;\n if (options?.scheduledAt) {\n scheduledAt = options.scheduledAt.toISOString();\n } else if (options?.delay) {\n scheduledAt = this.dt\n .now()\n .add(this.dt.duration(options.delay))\n .toISOString();\n }\n\n if (options?.key) {\n // Key-based dedup: check for existing row first, then insert.\n // Two queries in the no-conflict path, but deterministic across dialects.\n const existing = await this.executions.findMany({\n where: { jobName: { eq: name }, key: { eq: options.key } },\n limit: 1,\n });\n if (existing.length > 0) {\n return existing[0].id;\n }\n const execution = await this.executions.create({\n jobName: name,\n key: options.key,\n payload: validated as Record<string, unknown>,\n status,\n priority,\n maxAttempts,\n scheduledAt,\n triggeredBy: options.triggeredBy,\n triggeredByName: options.triggeredByName,\n });\n if (status === \"pending\") {\n await this.dispatchToQueue(name, execution.id);\n } else if (status === \"scheduled\" && scheduledAt) {\n this.scheduleOptimisticDispatch(name, execution.id, scheduledAt);\n }\n return execution.id;\n }\n\n const execution = await this.executions.create({\n jobName: name,\n payload: validated as Record<string, unknown>,\n status,\n priority,\n maxAttempts,\n scheduledAt,\n triggeredBy: options?.triggeredBy,\n triggeredByName: options?.triggeredByName,\n });\n\n if (status === \"pending\") {\n await this.dispatchToQueue(name, execution.id);\n } else if (status === \"scheduled\" && scheduledAt) {\n this.scheduleOptimisticDispatch(name, execution.id, scheduledAt);\n }\n return execution.id;\n }\n\n /**\n * Fire a local setTimeout so delayed/retrying rows dispatch as close to\n * `scheduledAt` as possible, rather than waiting for the next sweep tick.\n * No-op on stateless runtimes where timers won't survive (the sweep\n * handles those).\n */\n protected scheduleOptimisticDispatch(\n jobName: string,\n executionId: string,\n scheduledAt: string,\n ): void {\n const delayMs = Math.max(\n 0,\n new Date(scheduledAt).getTime() - this.dt.nowMillis(),\n );\n this.dt.createTimeout(() => {\n void this.dispatchScheduled(jobName, executionId);\n }, delayMs);\n }\n\n public async pushMany(\n name: string,\n items: Array<PushManyItem>,\n ): Promise<string[]> {\n if (items.length === 0) return [];\n\n const registration = this.getRegistration(name);\n if (registration.type !== \"queue\") {\n throw new AlephaError(\n `Job '${name}' is not queue-mode (no schema declared).`,\n );\n }\n const opts = registration.options;\n const maxAttempts = (opts.retry?.retries ?? 0) + 1;\n\n const keyed: PushManyItem[] = [];\n const bulk: Array<{\n jobName: string;\n payload: Record<string, unknown>;\n status: JobStatus;\n priority: number;\n maxAttempts: number;\n scheduledAt?: string;\n }> = [];\n\n for (const item of items) {\n const validated = this.alepha.codec.validate(opts.schema!, item.payload);\n if (item.key) {\n keyed.push({ ...item, payload: validated as Static<TSchema> });\n continue;\n }\n const isDelayed = item.delay || item.scheduledAt;\n const status: JobStatus = isDelayed ? \"scheduled\" : \"pending\";\n let scheduledAt: string | undefined;\n if (item.scheduledAt) {\n scheduledAt = item.scheduledAt.toISOString();\n } else if (item.delay) {\n scheduledAt = this.dt\n .now()\n .add(this.dt.duration(item.delay))\n .toISOString();\n }\n bulk.push({\n jobName: name,\n payload: validated as Record<string, unknown>,\n status,\n priority: PRIORITY_MAP[item.priority ?? opts.priority ?? \"normal\"],\n maxAttempts,\n scheduledAt,\n });\n }\n\n const ids: string[] = [];\n\n for (const item of keyed) {\n const id = await this.push(name, item.payload, {\n key: item.key,\n delay: item.delay,\n priority: item.priority,\n scheduledAt: item.scheduledAt,\n });\n ids.push(id);\n }\n\n if (bulk.length > 0) {\n const created = await this.executions.createMany(bulk);\n for (const exec of created) {\n ids.push(exec.id);\n if (exec.status === \"pending\" && !this.stopping) {\n await this.dispatchToQueue(name, exec.id);\n } else if (\n exec.status === \"scheduled\" &&\n exec.scheduledAt &&\n !this.stopping\n ) {\n this.scheduleOptimisticDispatch(name, exec.id, exec.scheduledAt);\n }\n }\n }\n\n this.log.debug(`pushMany '${name}': ${ids.length} jobs created`, {\n bulk: bulk.length,\n keyed: keyed.length,\n });\n\n return ids;\n }\n\n protected async dispatchToQueue(\n jobName: string,\n executionId: string,\n ): Promise<void> {\n if (this.stopping) return;\n if (!this.queueDispatch) {\n throw new AlephaError(\n `Queue-mode job '${jobName}' cannot be pushed: AlephaApiJobsQueue is not loaded. Add '.with(AlephaApiJobsQueue)' to your app.`,\n );\n }\n await this.queueDispatch(jobName, executionId);\n }\n\n // --- Manual trigger (admin / CLI) ------------------------------------------------------------------------------\n\n public async trigger(\n name: string,\n context?: JobTriggerContext,\n ): Promise<void> {\n const registration = this.getRegistration(name);\n\n if (registration.type === \"cron\") {\n const executionId = crypto.randomUUID();\n await this.executeInline(registration, executionId, {\n payload: undefined,\n attempt: 1,\n triggeredBy: context?.triggeredBy,\n triggeredByName: context?.triggeredByName,\n });\n return;\n }\n\n // queue-mode: treat as a normal push with the given payload\n if (!context?.payload) {\n throw new AlephaError(\n `Queue-mode job '${name}' requires a payload for manual trigger.`,\n );\n }\n await this.push(name, context.payload, {\n triggeredBy: context.triggeredBy,\n triggeredByName: context.triggeredByName,\n });\n }\n\n // --- Cancel ----------------------------------------------------------------------------------------------------\n\n public async cancel(\n executionId: string,\n context?: CancelContext,\n ): Promise<void> {\n const execution = await this.executions.findById(executionId);\n if (!execution) {\n throw new AlephaError(`Execution not found: ${executionId}`);\n }\n if (\n execution.status === \"ok\" ||\n execution.status === \"error\" ||\n execution.status === \"cancelled\"\n ) {\n throw new AlephaError(\n `Cannot cancel execution in '${execution.status}' status`,\n );\n }\n\n const controller = this.abortControllers.get(executionId);\n if (controller) controller.abort();\n\n await this.executions.updateById(executionId, {\n status: \"cancelled\",\n key: null,\n cancelledBy: context?.cancelledBy,\n cancelledByName: context?.cancelledByName,\n completedAt: this.dt.nowISOString(),\n });\n\n this.log.info(`Cancelled execution ${executionId}`, {\n jobName: execution.jobName,\n cancelledBy: context?.cancelledByName ?? context?.cancelledBy,\n });\n }\n\n // --- Queue consumer (called by JobQueueProvider) --------------------------------------------------------------\n\n public async processExecution(\n jobName: string,\n executionId: string,\n ): Promise<void> {\n const registration = this.jobs.get(jobName);\n if (!registration) {\n this.log.warn(`Unknown job '${jobName}' — skipping execution`, {\n executionId,\n });\n return;\n }\n if (registration.type !== \"queue\") {\n this.log.warn(`Job '${jobName}' is not queue-mode — skipping`, {\n executionId,\n });\n return;\n }\n\n const promise = this.processQueueExecution(registration, executionId);\n this.inFlight.add(promise);\n try {\n await promise;\n } finally {\n this.inFlight.delete(promise);\n }\n }\n\n protected async processQueueExecution(\n registration: JobRuntimeRegistration,\n executionId: string,\n ): Promise<void> {\n const jobName = registration.name;\n const opts = registration.options;\n const record = opts.record ?? \"error\";\n\n const claimed = await this.claim(executionId);\n if (!claimed) {\n this.log.debug(`Execution ${executionId} already claimed, skipping`);\n return;\n }\n\n const execution = await this.executions.findById(executionId);\n if (!execution) return;\n\n const contextId = this.alepha.context.createContextId();\n this.perExecutionLogs.set(contextId, []);\n\n const abortController = new AbortController();\n this.abortControllers.set(executionId, abortController);\n let timeoutId: ReturnType<typeof setTimeout> | undefined;\n if (opts.timeout) {\n const ms = this.dt.duration(opts.timeout).as(\"milliseconds\");\n timeoutId = setTimeout(() => abortController.abort(), ms);\n }\n\n const now = this.dt.now();\n\n try {\n await this.alepha.context.run(\n async () => {\n await this.alepha.events.emit(\"job:begin\", {\n name: jobName,\n now,\n executionId,\n });\n\n try {\n await opts.handler({\n payload: execution.payload,\n attempt: execution.attempt,\n now,\n signal: abortController.signal,\n executionId,\n });\n\n // Success: either DELETE (keepLastSuccess=0 or record=error)\n // or UPDATE to 'ok' (record=all and keepLastSuccess>0).\n const keepSuccess =\n record === \"all\" && this.config.keepLastSuccess > 0;\n if (keepSuccess) {\n await this.executions.updateById(executionId, {\n status: \"ok\",\n completedAt: this.dt.nowISOString(),\n key: null,\n });\n } else {\n await this.executions.deleteById(executionId);\n }\n\n await this.alepha.events.emit(\n \"job:success\",\n { name: jobName, executionId },\n { catch: true },\n );\n } catch (error) {\n const err =\n error instanceof Error ? error : new Error(String(error));\n\n if (abortController.signal.aborted) {\n const current = await this.executions.findById(executionId);\n if (current?.status === \"cancelled\") {\n await this.alepha.events.emit(\n \"job:cancel\",\n { name: jobName, executionId },\n { catch: true },\n );\n return;\n }\n }\n\n await this.handleFailure(\n executionId,\n registration,\n execution.attempt,\n err,\n contextId,\n );\n } finally {\n if (timeoutId) clearTimeout(timeoutId);\n this.abortControllers.delete(executionId);\n await this.alepha.events.emit(\n \"job:end\",\n { name: jobName, executionId },\n { catch: true },\n );\n }\n },\n { context: contextId },\n );\n } finally {\n this.perExecutionLogs.delete(contextId);\n }\n }\n\n protected async claim(executionId: string): Promise<boolean> {\n const execution = await this.executions.findById(executionId);\n if (!execution) return false;\n try {\n await this.executions.updateOne(\n { id: { eq: executionId }, status: { eq: \"pending\" } },\n {\n status: \"running\",\n attempt: execution.attempt + 1,\n startedAt: this.dt.nowISOString(),\n },\n );\n return true;\n } catch {\n return false;\n }\n }\n\n protected async handleFailure(\n executionId: string,\n registration: JobRuntimeRegistration,\n currentAttempt: number,\n error: Error,\n contextId: string,\n ): Promise<void> {\n const jobName = registration.name;\n const opts = registration.options;\n const retry = opts.retry;\n const maxAttempts = (retry?.retries ?? 0) + 1;\n\n const canRetry =\n retry &&\n currentAttempt + 1 < maxAttempts &&\n (retry.when ? retry.when(error) : true);\n\n if (canRetry) {\n const nextScheduledAt = this.computeBackoff(retry, currentAttempt + 1);\n this.log.info(\n `Job '${jobName}' failed, scheduling retry ${currentAttempt + 1}/${maxAttempts}`,\n { executionId, error: error.message, nextScheduledAt },\n );\n await this.executions.updateById(executionId, {\n status: \"scheduled\",\n error: error.message,\n scheduledAt: nextScheduledAt,\n logs: this.snapshotLogs(contextId),\n });\n // Optimistic dispatch: fire a local timer so the retry runs as close to\n // `scheduledAt` as possible. The sweep is the safety net for worker\n // crashes and stateless runtimes (CF Workers, where setTimeout won't\n // survive across invocations anyway).\n const delayMs = Math.max(\n 0,\n new Date(nextScheduledAt).getTime() - this.dt.nowMillis(),\n );\n this.dt.createTimeout(() => {\n void this.dispatchScheduled(jobName, executionId);\n }, delayMs);\n } else {\n this.log.info(\n `Job '${jobName}' dead after ${currentAttempt} attempt(s)`,\n { executionId, error: error.message },\n );\n await this.executions.updateById(executionId, {\n status: \"error\",\n error: error.message,\n completedAt: this.dt.nowISOString(),\n key: null,\n logs: this.snapshotLogs(contextId),\n });\n }\n\n await this.alepha.events.emit(\n \"job:error\",\n { name: jobName, error, executionId },\n { catch: true },\n );\n }\n\n protected computeBackoff(retry: JobRetryOptions, attempt: number): string {\n const now = this.dt.now();\n if (!retry.backoff) {\n return now.add(1, \"second\").toISOString();\n }\n if (Array.isArray(retry.backoff)) {\n return now.add(this.dt.duration(retry.backoff)).toISOString();\n }\n const backoff = retry.backoff as JobRetryBackoff;\n const initial = this.dt.duration(backoff.initial).as(\"milliseconds\");\n const factor = backoff.factor ?? 2;\n let delayMs = initial * factor ** (attempt - 1);\n if (backoff.max) {\n delayMs = Math.min(\n delayMs,\n this.dt.duration(backoff.max).as(\"milliseconds\"),\n );\n }\n if (backoff.jitter) {\n delayMs = delayMs * (0.75 + Math.random() * 0.5);\n }\n return now.add(delayMs, \"millisecond\").toISOString();\n }\n\n protected snapshotLogs(contextId: string): LogEntry[] | undefined {\n const entries = this.perExecutionLogs.get(contextId);\n if (!entries || entries.length === 0) return undefined;\n const max = this.config.logMaxEntries;\n if (max === 0) return undefined;\n if (entries.length <= max) return [...entries];\n const truncated = entries.slice(0, max);\n truncated.push({\n level: \"WARN\",\n message: `Log entries truncated at ${max}`,\n timestamp: this.dt.nowMillis(),\n service: \"alepha.jobs\",\n module: \"JobProvider\",\n } as LogEntry);\n return truncated;\n }\n\n // --- Sweep ----------------------------------------------------------------------------------------------------\n\n protected async sweep(): Promise<void> {\n if (this.stopping) return;\n this.log.trace(\"Starting job sweep\");\n const now = this.dt.now();\n const nowIso = now.toISOString();\n\n try {\n // 1. Due scheduled rows → pending + dispatch\n const dueWhere = this.executions.createQueryWhere();\n dueWhere.status = { eq: \"scheduled\" };\n dueWhere.scheduledAt = { lte: nowIso };\n const due = await this.executions.findMany({\n where: dueWhere,\n orderBy: { column: \"priority\", direction: \"asc\" },\n });\n for (const exec of due) {\n if (!this.jobs.has(exec.jobName)) continue;\n await this.executions.updateById(exec.id, { status: \"pending\" });\n await this.dispatchToQueueSafe(exec.jobName, exec.id);\n }\n\n // 2. Stale pending rows → re-dispatch\n const staleIso = now\n .subtract(this.config.staleThreshold, \"millisecond\")\n .toISOString();\n const staleWhere = this.executions.createQueryWhere();\n staleWhere.status = { eq: \"pending\" };\n staleWhere.createdAt = { lte: staleIso };\n const stale = await this.executions.findMany({\n where: staleWhere,\n orderBy: { column: \"priority\", direction: \"asc\" },\n });\n for (const exec of stale) {\n if (!this.jobs.has(exec.jobName)) continue;\n await this.dispatchToQueueSafe(exec.jobName, exec.id);\n }\n\n // 3. Crashed running rows → mark as failed + apply retry\n const runningWhere = this.executions.createQueryWhere();\n runningWhere.status = { eq: \"running\" };\n const running = await this.executions.findMany({ where: runningWhere });\n const nowMs = now.valueOf();\n for (const exec of running) {\n const reg = this.jobs.get(exec.jobName);\n if (!reg) continue;\n if (this.abortControllers.has(exec.id)) continue; // still alive locally\n const crashThresholdMs = reg.options.timeout\n ? this.dt.duration(reg.options.timeout).as(\"milliseconds\") * 2\n : this.config.runTimeout;\n const startedAtMs = exec.startedAt\n ? new Date(exec.startedAt).getTime()\n : 0;\n if (startedAtMs > 0 && nowMs - startedAtMs > crashThresholdMs) {\n this.log.warn(\n `Sweep: marking crashed ${exec.jobName} (${exec.id}) as failed`,\n );\n const err = new Error(\n \"Execution assumed crashed (recovered by sweep)\",\n );\n await this.handleFailure(exec.id, reg, exec.attempt, err, \"\");\n }\n }\n\n // 4. Trim ring buffer per job\n await this.trimRingBuffers();\n } catch (e) {\n this.log.error(\"Sweep failed\", { error: e });\n }\n }\n\n protected async dispatchToQueueSafe(\n jobName: string,\n executionId: string,\n ): Promise<void> {\n try {\n await this.dispatchToQueue(jobName, executionId);\n } catch (e) {\n this.log.warn(`Sweep failed to dispatch ${jobName} (${executionId})`, e);\n }\n }\n\n /**\n * Move a row from `scheduled` → `pending` and dispatch it.\n * Used by the optimistic retry/delay timer. If the sweep has already moved\n * the row, or another worker has claimed it, the UPDATE guard fails silently.\n */\n protected async dispatchScheduled(\n jobName: string,\n executionId: string,\n ): Promise<void> {\n if (this.stopping) return;\n try {\n await this.executions.updateOne(\n { id: { eq: executionId }, status: { eq: \"scheduled\" } },\n { status: \"pending\" },\n );\n await this.dispatchToQueueSafe(jobName, executionId);\n } catch {\n // Row already transitioned (sweep ran, another worker claimed, etc.)\n }\n }\n\n protected async trimRingBuffers(): Promise<void> {\n for (const [jobName, reg] of this.jobs) {\n const okLimit = reg.options.keep?.ok ?? this.config.keepLastSuccess;\n const errLimit = reg.options.keep?.error ?? this.config.keepLastError;\n if (okLimit > 0) {\n await this.trimByStatus(jobName, \"ok\", okLimit);\n }\n if (errLimit > 0) {\n await this.trimByStatus(jobName, \"error\", errLimit);\n }\n }\n }\n\n protected async trimByStatus(\n jobName: string,\n status: \"ok\" | \"error\",\n keep: number,\n ): Promise<void> {\n try {\n const rows = await this.executions.findMany({\n where: { jobName: { eq: jobName }, status: { eq: status } },\n orderBy: { column: \"startedAt\", direction: \"desc\" },\n limit: keep + 50,\n });\n if (rows.length <= keep) return;\n const toDelete = rows.slice(keep).map((r) => r.id);\n if (toDelete.length > 0) {\n await this.executions.deleteMany({ id: { inArray: toDelete } });\n this.log.debug(\n `Trimmed ${toDelete.length} ${status} rows for '${jobName}'`,\n );\n }\n } catch (e) {\n this.log.warn(`Failed to trim ${status} rows for '${jobName}'`, e);\n }\n }\n\n // --- Lifecycle -----------------------------------------------------------------------------------------------\n\n protected readonly onStart = $hook({\n on: \"start\",\n handler: async () => {\n // Validate that queue-mode jobs have a dispatcher registered.\n const needsQueue = [...this.jobs.values()].some(\n (j) => j.type === \"queue\",\n );\n if (needsQueue && !this.queueDispatch) {\n throw new AlephaError(\n `Queue-mode jobs are registered but no queue dispatcher is available. Add '.with(AlephaApiJobsQueue)' to your app.`,\n );\n }\n\n this.log.info(`Job system OK`, {\n dispatch: this.queueDispatch ? \"queue\" : \"inline-only\",\n jobs: this.jobs.size,\n });\n\n // Capture logs per execution context.\n this.alepha.events.on(\"log\", ({ entry }) => {\n const ctx = entry.context;\n if (!ctx) return;\n const entries = this.perExecutionLogs.get(ctx);\n if (!entries) return;\n entries.push(entry);\n });\n\n if (!this.alepha.isServerless()) {\n await this.sweep();\n }\n\n this.cronProvider.createCronJob(\n \"api:jobs:sweep\",\n SWEEP_CRON,\n async () => {\n await this.sweep();\n },\n true,\n );\n },\n });\n\n protected readonly onStop = $hook({\n on: \"stop\",\n handler: async () => {\n this.stopping = true;\n if (this.inFlight.size > 0) {\n this.log.info(`Draining ${this.inFlight.size} in-flight job(s)...`);\n await Promise.race([\n Promise.allSettled([...this.inFlight]),\n this.dt.wait([this.config.drainTimeout, \"millisecond\"]),\n ]);\n }\n if (this.abortControllers.size > 0) {\n this.log.warn(\n `Aborting ${this.abortControllers.size} remaining job(s) after drain timeout`,\n );\n for (const controller of this.abortControllers.values()) {\n controller.abort();\n }\n }\n },\n });\n\n // --- Helpers -------------------------------------------------------------------------------------------------\n\n protected getRegistration(name: string): JobRuntimeRegistration {\n const registration = this.jobs.get(name);\n if (!registration) {\n throw new AlephaError(`Job not registered: ${name}`);\n }\n return registration;\n }\n}\n\nexport { PRIORITY_MAP, PRIORITY_REVERSE };\n","import {\n $inject,\n type Async,\n createPrimitive,\n KIND,\n PipelinePrimitive,\n type PipelinePrimitiveOptions,\n type Static,\n type TSchema,\n} from \"alepha\";\nimport type { DateTime, DurationLike } from \"alepha/datetime\";\nimport {\n JobProvider,\n type JobTriggerContext,\n type PushManyItem,\n type PushOptions,\n} from \"../providers/JobProvider.ts\";\n\n/**\n * Job primitive for defining scheduled (cron) or queued (push) tasks.\n *\n * A job must be either **cron-only** (pass `cron`) or **queue-only**\n * (pass `schema`), never both. To run scheduled work that processes\n * payloads, compose two jobs: a cron that pushes payloads, and a\n * queue job that handles them.\n */\nexport const $job = <T extends TSchema = TSchema>(\n options: JobPrimitiveOptions<T>,\n): JobPrimitive<T> => {\n return createPrimitive(JobPrimitive<T>, options);\n};\n\n// -----------------------------------------------------------------------------------------------------------------\n\nexport interface JobHandlerArgs<T extends TSchema = TSchema> {\n payload: Static<T>;\n attempt: number;\n now: DateTime;\n signal: AbortSignal;\n executionId: string;\n}\n\nexport interface JobRetryBackoff {\n initial: DurationLike;\n factor?: number;\n max?: DurationLike;\n jitter?: boolean;\n}\n\nexport interface JobRetryOptions {\n retries: number;\n backoff?: DurationLike | JobRetryBackoff;\n when?: (error: Error) => boolean;\n}\n\nexport type JobPriority = \"critical\" | \"high\" | \"normal\" | \"low\";\n\nexport interface JobPrimitiveOptions<T extends TSchema = TSchema>\n extends PipelinePrimitiveOptions {\n /**\n * Optional explicit job name. Defaults to `ClassName.propertyKey`.\n * Recommended convention for framework-internal jobs: `api:module:jobName`.\n */\n name?: string;\n\n /**\n * Human-readable description (shown in the admin UI).\n */\n description?: string;\n\n /**\n * Payload schema (TypeBox). When set, the job is queue-mode.\n * Must not be combined with `cron`.\n */\n schema?: T;\n\n /**\n * Cron expression for recurring execution. When set, the job is cron-mode.\n * Must not be combined with `schema`.\n */\n cron?: string;\n\n /**\n * Retry policy for queue-mode jobs.\n * Cron-mode jobs do not retry — the next tick re-runs.\n */\n retry?: JobRetryOptions;\n\n /**\n * Max execution time per attempt. Handler receives an `AbortSignal`.\n */\n timeout?: DurationLike;\n\n /**\n * Default priority for pushed jobs. Used by the sweep to order\n * dispatch when there is a backlog. Real-time queue consumption\n * is FIFO.\n * @default \"normal\"\n */\n priority?: JobPriority;\n\n /**\n * Whether to record successful executions.\n *\n * - `\"error\"` (default for cron, default for queue): only error/cancelled rows kept\n * - `\"all\"`: keep success rows too (bounded by `keepLastSuccess`)\n * - `\"none\"`: fire-and-forget, no row even on error\n *\n * Note: queue-mode jobs always write a `pending` row at push time (outbox).\n * This setting controls whether that row is kept on success.\n */\n record?: \"error\" | \"all\" | \"none\";\n\n /**\n * Override the global ring-buffer trim for this job.\n *\n * - `{ ok: 0, error: 0 }` — **keep forever** (no sweep trim). Useful for\n * audit-heavy jobs where retention is time-based (handled by a separate\n * cron) rather than count-based.\n * - `{ ok: 50 }` — keep last 50 successes; fall back to global default for errors.\n * - omitted — use global `keepLastSuccess` / `keepLastError` from `jobConfig`.\n */\n keep?: {\n ok?: number;\n error?: number;\n };\n\n /**\n * Handler function. For cron-mode, `payload` is `undefined`.\n */\n handler: (args: JobHandlerArgs<T>) => Async<void>;\n}\n\n// -----------------------------------------------------------------------------------------------------------------\n\nexport class JobPrimitive<\n T extends TSchema = TSchema,\n> extends PipelinePrimitive<JobPrimitiveOptions<T>> {\n protected readonly jobProvider = $inject(JobProvider);\n\n public get name(): string {\n return (\n this.options.name ??\n `${this.config.service.name}.${this.config.propertyKey}`\n );\n }\n\n protected onInit() {\n const handler = this.handler.run.bind(this.handler);\n this.jobProvider.registerJob(this.name, { ...this.options, handler });\n }\n\n /**\n * Push a single payload to the queue (queue-mode only).\n */\n public async push(\n payload: Static<T>,\n options?: PushOptions,\n ): Promise<string> {\n return this.jobProvider.push(this.name, payload, options);\n }\n\n /**\n * Push multiple payloads at once (queue-mode only).\n * Batched INSERT + batched queue send when supported.\n */\n public async pushMany(items: Array<PushManyItem<T>>): Promise<string[]> {\n return this.jobProvider.pushMany(this.name, items);\n }\n\n /**\n * Cancel a pending or running execution.\n */\n public async cancel(executionId: string): Promise<void> {\n return this.jobProvider.cancel(executionId);\n }\n\n /**\n * Manually fire a cron-mode job, or trigger a queue-mode job with an explicit payload.\n */\n public async trigger(context?: JobTriggerContext<T>): Promise<void> {\n return this.jobProvider.trigger(this.name, context);\n }\n}\n\n$job[KIND] = JobPrimitive;\n","import { $inject, Alepha, AlephaError, t } from \"alepha\";\nimport { $logger } from \"alepha/logger\";\nimport { $repository, sql } from \"alepha/orm\";\nimport { NotFoundError } from \"alepha/server\";\nimport { jobExecutionEntity } from \"../entities/jobExecutionEntity.ts\";\nimport { $job } from \"../primitives/$job.ts\";\nimport type { JobTriggerContext } from \"../providers/JobProvider.ts\";\nimport { JobProvider } from \"../providers/JobProvider.ts\";\nimport type { JobExecutionQuery } from \"../schemas/jobExecutionQuerySchema.ts\";\nimport type { JobRegistration } from \"../schemas/jobRegistrationSchema.ts\";\n\n/**\n * Admin surface for the job system.\n *\n * Six methods: list jobs, list executions, get execution,\n * trigger, retry, cancel. Everything else lives in events — any\n * analytics/observability is an external concern that subscribes\n * to `job:begin` / `job:success` / `job:error`.\n */\nexport class JobService {\n protected readonly alepha = $inject(Alepha);\n protected readonly log = $logger();\n protected readonly jobProvider = $inject(JobProvider);\n protected readonly executions = $repository(jobExecutionEntity);\n\n protected computeCan(status: string) {\n return {\n retry: status === \"error\" || status === \"cancelled\",\n cancel:\n status === \"pending\" || status === \"running\" || status === \"scheduled\",\n };\n }\n\n /**\n * List every registered job with recent ok/error counts and lastRun.\n * One aggregate query covers all jobs.\n */\n public async listJobs(): Promise<JobRegistration[]> {\n const registry = this.jobProvider.getRegisteredJobs();\n\n const aggRows = await this.executions.query(\n (e) => sql`\n SELECT\n ${e.jobName} AS job_name,\n ${e.status} AS status,\n COUNT(*) AS count,\n MAX(${e.completedAt}) AS last_run\n FROM ${e}\n WHERE ${e.status} IN ('ok', 'error')\n GROUP BY ${e.jobName}, ${e.status}\n `,\n t.object({\n job_name: t.string(),\n status: t.string(),\n count: t.string(),\n last_run: t.optional(t.nullable(t.union([t.string(), t.number()]))),\n }),\n );\n\n const toIso = (\n v: string | number | null | undefined,\n ): string | undefined => {\n if (v === null || v === undefined) return undefined;\n if (typeof v === \"number\") return new Date(v).toISOString();\n return v;\n };\n\n const byJob = new Map<\n string,\n { ok: number; error: number; lastRun?: string }\n >();\n for (const row of aggRows) {\n const entry = byJob.get(row.job_name) ?? { ok: 0, error: 0 };\n if (row.status === \"ok\") entry.ok = Number(row.count);\n if (row.status === \"error\") entry.error = Number(row.count);\n const iso = toIso(row.last_run);\n if (iso && (!entry.lastRun || iso > entry.lastRun)) {\n entry.lastRun = iso;\n }\n byJob.set(row.job_name, entry);\n }\n\n const result: JobRegistration[] = [];\n for (const [name, reg] of registry) {\n const opts = reg.options;\n const counts = byJob.get(name) ?? { ok: 0, error: 0 };\n result.push({\n name,\n description: opts.description,\n type: reg.type,\n cron: opts.cron,\n priority: (opts.priority ?? \"normal\") as JobRegistration[\"priority\"],\n timeout: opts.timeout ? String(opts.timeout) : undefined,\n retry: opts.retry\n ? {\n retries: opts.retry.retries,\n hasBackoff: Boolean(opts.retry.backoff),\n }\n : undefined,\n recent: counts,\n });\n }\n return result;\n }\n\n /**\n * Recent executions for a single job, ORDER BY startedAt DESC.\n */\n public async getExecutions(jobName: string, query: JobExecutionQuery = {}) {\n const registry = this.jobProvider.getRegisteredJobs();\n if (!registry.has(jobName)) {\n throw new NotFoundError(`Job not found: ${jobName}`);\n }\n const where = this.executions.createQueryWhere();\n where.jobName = { eq: jobName };\n if (query.status) {\n where.status = { eq: query.status };\n }\n const rows = await this.executions.findMany({\n where,\n orderBy: { column: \"startedAt\", direction: \"desc\" },\n limit: query.limit ?? 20,\n });\n return rows.map((row) => ({\n ...row,\n can: this.computeCan(row.status),\n }));\n }\n\n /**\n * Full execution detail (includes captured logs).\n */\n public async getExecution(id: string) {\n const execution = await this.executions.findById(id);\n if (!execution) {\n throw new NotFoundError(`Execution not found: ${id}`);\n }\n return {\n ...execution,\n can: this.computeCan(execution.status),\n };\n }\n\n /**\n * Manual trigger (cron jobs) or push-with-payload (queue jobs).\n */\n public async triggerJob(\n name: string,\n context?: JobTriggerContext,\n ): Promise<{ ok: boolean }> {\n const jobPrimitives = this.alepha.primitives($job);\n const job = jobPrimitives.find((j) => j.name === name);\n if (!job) {\n throw new NotFoundError(`Job not found: ${name}`);\n }\n this.log.info(`Triggering job '${name}'`, {\n triggeredBy: context?.triggeredByName ?? context?.triggeredBy,\n });\n await job.trigger(context);\n return { ok: true };\n }\n\n /**\n * Retry a dead or cancelled execution by re-pushing with the original payload.\n */\n public async retryExecution(\n id: string,\n context?: { triggeredBy?: string; triggeredByName?: string },\n ): Promise<{ ok: boolean }> {\n const execution = await this.executions.findById(id);\n if (!execution) {\n throw new NotFoundError(`Execution not found: ${id}`);\n }\n if (execution.status !== \"error\" && execution.status !== \"cancelled\") {\n throw new AlephaError(\n `Cannot retry execution in '${execution.status}' status`,\n );\n }\n\n const jobPrimitives = this.alepha.primitives($job);\n const job = jobPrimitives.find((j) => j.name === execution.jobName);\n if (!job) {\n throw new NotFoundError(`Job not found: ${execution.jobName}`);\n }\n\n this.log.info(`Retrying execution ${id}`, {\n jobName: execution.jobName,\n previousStatus: execution.status,\n triggeredBy: context?.triggeredByName ?? context?.triggeredBy,\n });\n\n if (execution.payload) {\n await job.push(execution.payload as any);\n } else {\n await job.trigger({\n triggeredBy: context?.triggeredBy,\n triggeredByName: context?.triggeredByName,\n });\n }\n return { ok: true };\n }\n\n public async cancelExecution(\n id: string,\n context?: { cancelledBy?: string; cancelledByName?: string },\n ): Promise<{ ok: boolean }> {\n this.log.info(`Cancelling execution ${id}`, {\n cancelledBy: context?.cancelledByName ?? context?.cancelledBy,\n });\n await this.jobProvider.cancel(id, {\n cancelledBy: context?.cancelledBy,\n cancelledByName: context?.cancelledByName,\n });\n return { ok: true };\n }\n}\n","import { $inject, t } from \"alepha\";\nimport { $secure } from \"alepha/security\";\nimport { $action, okSchema } from \"alepha/server\";\nimport { jobExecutionQuerySchema } from \"../schemas/jobExecutionQuerySchema.ts\";\nimport { jobExecutionResourceSchema } from \"../schemas/jobExecutionResourceSchema.ts\";\nimport { jobRegistrationSchema } from \"../schemas/jobRegistrationSchema.ts\";\nimport { triggerJobSchema } from \"../schemas/triggerJobSchema.ts\";\nimport { JobService } from \"../services/JobService.ts\";\n\n/**\n * Minimal admin surface for the job system. Six endpoints.\n */\nexport class AdminJobController {\n protected readonly url: string = \"/jobs\";\n protected readonly group: string = \"admin:jobs\";\n protected readonly jobService = $inject(JobService);\n\n public readonly listJobs = $action({\n path: this.url,\n group: this.group,\n use: [$secure({ permissions: [\"admin:job:read\"] })],\n schema: {\n response: t.array(jobRegistrationSchema),\n },\n handler: () => this.jobService.listJobs(),\n });\n\n public readonly listExecutions = $action({\n path: `${this.url}/:name/executions`,\n group: this.group,\n use: [$secure({ permissions: [\"admin:job:read\"] })],\n schema: {\n params: t.object({ name: t.text() }),\n query: jobExecutionQuerySchema,\n response: t.array(jobExecutionResourceSchema),\n },\n handler: ({ params, query }) =>\n this.jobService.getExecutions(params.name, query),\n });\n\n public readonly getExecution = $action({\n path: `${this.url}/executions/:id`,\n group: this.group,\n use: [$secure({ permissions: [\"admin:job:read\"] })],\n schema: {\n params: t.object({ id: t.uuid() }),\n response: jobExecutionResourceSchema,\n },\n handler: ({ params }) => this.jobService.getExecution(params.id),\n });\n\n public readonly triggerJob = $action({\n method: \"POST\",\n path: `${this.url}/:name/trigger`,\n group: this.group,\n use: [$secure({ permissions: [\"admin:job:trigger\"] })],\n schema: {\n params: t.object({ name: t.text() }),\n body: triggerJobSchema,\n response: okSchema,\n },\n handler: ({ params, body, user }) =>\n this.jobService.triggerJob(params.name, {\n payload: body.payload,\n triggeredBy: user?.id,\n triggeredByName: user?.name,\n }),\n });\n\n public readonly retryExecution = $action({\n method: \"POST\",\n path: `${this.url}/executions/:id/retry`,\n group: this.group,\n use: [$secure({ permissions: [\"admin:job:trigger\"] })],\n schema: {\n params: t.object({ id: t.uuid() }),\n response: okSchema,\n },\n handler: ({ params, user }) =>\n this.jobService.retryExecution(params.id, {\n triggeredBy: user?.id,\n triggeredByName: user?.name,\n }),\n });\n\n public readonly cancelExecution = $action({\n method: \"POST\",\n path: `${this.url}/executions/:id/cancel`,\n group: this.group,\n use: [$secure({ permissions: [\"admin:job:cancel\"] })],\n schema: {\n params: t.object({ id: t.uuid() }),\n response: okSchema,\n },\n handler: ({ params, user }) =>\n this.jobService.cancelExecution(params.id, {\n cancelledBy: user?.id,\n cancelledByName: user?.name,\n }),\n });\n}\n","import { $inject, t } from \"alepha\";\nimport { $queue } from \"alepha/queue\";\nimport { JobProvider } from \"./JobProvider.ts\";\n\n/**\n * Plumbs outbox-style dispatch through `AlephaQueue`.\n *\n * Registered only when the app imports `AlephaApiJobsQueue`. Sets\n * `JobProvider.queueDispatch` eagerly at instantiation so queue-mode jobs\n * can dispatch regardless of start-hook ordering.\n */\nexport class JobQueueProvider {\n protected readonly jobProvider = $inject(JobProvider);\n\n protected readonly queue = $queue({\n name: \"api:jobs:dispatch\",\n schema: t.object({ jobName: t.text(), executionId: t.text() }),\n handler: async (msg) => {\n await this.jobProvider.processExecution(\n msg.payload.jobName,\n msg.payload.executionId,\n );\n },\n });\n\n constructor() {\n // Install the dispatcher immediately — before any start hook runs,\n // so JobProvider can validate presence and queue-mode push works\n // from any lifecycle point.\n this.wireDispatcher();\n }\n\n protected wireDispatcher(): void {\n // `$inject` is resolved by the time constructor runs; assignment is safe.\n this.jobProvider.queueDispatch = (jobName, executionId) =>\n this.push(jobName, executionId);\n }\n\n public async push(jobName: string, executionId: string): Promise<void> {\n await this.queue.push({ jobName, executionId });\n }\n}\n","import { $module } from \"alepha\";\nimport type { DateTime } from \"alepha/datetime\";\nimport { AlephaLock } from \"alepha/lock\";\nimport { AlephaQueue } from \"alepha/queue\";\nimport { AlephaScheduler } from \"alepha/scheduler\";\nimport { AdminJobController } from \"./controllers/AdminJobController.ts\";\nimport { JobProvider } from \"./providers/JobProvider.ts\";\nimport { JobQueueProvider } from \"./providers/JobQueueProvider.ts\";\nimport { JobService } from \"./services/JobService.ts\";\n\n// -----------------------------------------------------------------------------------------------------------------\n\nexport * from \"./controllers/AdminJobController.ts\";\nexport * from \"./entities/jobExecutionEntity.ts\";\nexport * from \"./primitives/$job.ts\";\nexport * from \"./providers/JobProvider.ts\";\nexport * from \"./providers/JobQueueProvider.ts\";\nexport * from \"./schemas/jobConfigAtom.ts\";\nexport * from \"./schemas/jobExecutionQuerySchema.ts\";\nexport * from \"./schemas/jobExecutionResourceSchema.ts\";\nexport * from \"./schemas/jobRegistrationSchema.ts\";\nexport * from \"./schemas/triggerJobSchema.ts\";\nexport * from \"./services/JobService.ts\";\n\n// -----------------------------------------------------------------------------------------------------------------\n\ndeclare module \"alepha\" {\n interface Hooks {\n \"job:begin\": { name: string; now: DateTime; executionId: string };\n \"job:success\": { name: string; executionId: string };\n \"job:error\": { name: string; error: Error; executionId: string };\n \"job:cancel\": { name: string; executionId: string };\n \"job:end\": { name: string; executionId: string };\n }\n}\n\n// -----------------------------------------------------------------------------------------------------------------\n\n/**\n * Job execution framework — cron and durable queue work with a single primitive.\n *\n * A `$job` is either **cron-only** (declares `cron`) or **queue-only** (declares `schema`).\n * Cron jobs run inline on their schedule and only record errors by default.\n * Queue jobs use the outbox pattern: push commits to DB first, then notifies via queue.\n *\n * **This module provides cron support only.** To enable queue-mode jobs, also\n * import {@link AlephaApiJobsQueue} — it brings in the queue layer and infrastructure\n * binding (e.g. Cloudflare Queues). Cron-only deployments (Vercel, CF-without-Queues)\n * do not need `AlephaApiJobsQueue`.\n *\n * @module alepha.api.jobs\n */\nexport const AlephaApiJobs = $module({\n name: \"alepha.api.jobs\",\n imports: [AlephaScheduler, AlephaLock],\n services: [JobProvider, JobService, AdminJobController],\n});\n\n/**\n * Queue support for `$job`. Import alongside {@link AlephaApiJobs} when your\n * app declares queue-mode jobs (any `$job` with a `schema`).\n *\n * Adds `JobQueueProvider` which plumbs the outbox dispatch through `AlephaQueue`.\n *\n * @module alepha.api.jobs.queue\n */\nexport const AlephaApiJobsQueue = $module({\n name: \"alepha.api.jobs.queue\",\n imports: [AlephaApiJobs, AlephaQueue],\n services: [JobQueueProvider],\n});\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../../../src/api/keys/schemas/adminApiKeyQuerySchema.ts","../../../src/api/keys/schemas/adminApiKeyResourceSchema.ts","../../../src/api/keys/entities/apiKeyEntity.ts","../../../src/api/keys/services/ApiKeyService.ts","../../../src/api/keys/controllers/AdminApiKeyController.ts","../../../src/api/keys/schemas/createApiKeyBodySchema.ts","../../../src/api/keys/schemas/createApiKeyResponseSchema.ts","../../../src/api/keys/schemas/listApiKeyResponseSchema.ts","../../../src/api/keys/schemas/revokeApiKeyParamsSchema.ts","../../../src/api/keys/schemas/revokeApiKeyResponseSchema.ts","../../../src/api/keys/controllers/ApiKeyController.ts","../../../src/api/keys/index.ts"],"sourcesContent":["import { t } from \"alepha\";\nimport { pageQuerySchema } from \"alepha/orm\";\n\nexport const adminApiKeyQuerySchema = t.extend(pageQuerySchema, {\n userId: t.optional(t.uuid()),\n includeRevoked: t.optional(t.boolean()),\n});\n","import { t } from \"alepha\";\n\nexport const adminApiKeyResourceSchema = t.object({\n id: t.uuid(),\n userId: t.uuid(),\n name: t.string(),\n description: t.optional(t.string()),\n tokenPrefix: t.string(),\n tokenSuffix: t.string(),\n roles: t.array(t.string()),\n createdAt: t.datetime(),\n lastUsedAt: t.optional(t.datetime()),\n lastUsedIp: t.optional(t.string()),\n expiresAt: t.optional(t.datetime()),\n revokedAt: t.optional(t.datetime()),\n usageCount: t.integer(),\n});\n","import { type Static, t } from \"alepha\";\nimport { $entity, db } from \"alepha/orm\";\n\nexport const apiKeyEntity = $entity({\n name: \"api_keys\",\n schema: t.object({\n id: db.primaryKey(t.uuid()),\n createdAt: db.createdAt(),\n updatedAt: db.updatedAt(),\n\n // Owner\n userId: t.uuid(),\n\n // Key metadata\n name: t.text({ maxLength: 100 }),\n description: t.optional(t.text({ maxLength: 500 })),\n\n // Token (hashed) - internal, not user input\n tokenHash: t.string({ maxLength: 256 }),\n tokenPrefix: t.string({ maxLength: 10 }),\n tokenSuffix: t.string({ maxLength: 8 }),\n\n // Roles (snapshot from user at creation)\n roles: db.default(t.array(t.string()), []),\n\n // Tracking\n lastUsedAt: t.optional(t.datetime()),\n lastUsedIp: t.optional(t.string({ maxLength: 45 })),\n usageCount: db.default(t.integer(), 0),\n\n // Lifecycle\n expiresAt: t.optional(t.datetime()),\n revokedAt: t.optional(t.datetime()),\n }),\n indexes: [\n { columns: [\"userId\", \"name\"], unique: true },\n { columns: [\"tokenHash\"], unique: true },\n ],\n});\n\nexport type ApiKeyEntity = Static<typeof apiKeyEntity.schema>;\n","import { createHash, randomBytes } from \"node:crypto\";\nimport { $inject, Alepha } from \"alepha\";\nimport { $cache } from \"alepha/cache\";\nimport { DateTimeProvider } from \"alepha/datetime\";\nimport { $logger } from \"alepha/logger\";\nimport { $repository, sql } from \"alepha/orm\";\nimport type { IssuerResolver, UserInfo } from \"alepha/security\";\nimport { ForbiddenError, type ServerRequest } from \"alepha/server\";\nimport { type ApiKeyEntity, apiKeyEntity } from \"../entities/apiKeyEntity.ts\";\n\nexport class ApiKeyService {\n protected readonly alepha = $inject(Alepha);\n protected readonly dateTimeProvider = $inject(DateTimeProvider);\n protected readonly log = $logger();\n protected readonly repo = $repository(apiKeyEntity);\n\n /**\n * Cache validated API keys for 15 minutes.\n */\n protected readonly validationCache = $cache<ApiKeyEntity | null, [string]>({\n name: \"api:keys:validation\",\n ttl: [15, \"minutes\"],\n });\n\n // -------------------------------------------------------------------------\n // Resolver\n // -------------------------------------------------------------------------\n\n /**\n * Create an issuer resolver for API key authentication.\n * Lower priority means it runs before JWT resolver.\n *\n * @param options.priority - Priority of this resolver (default: 50, JWT is 100)\n * @param options.prefix - API key prefix to match in Bearer header (default: \"ak\")\n */\n public createResolver(\n options: { priority?: number; prefix?: string } = {},\n ): IssuerResolver {\n const { priority = 50, prefix = \"ak\" } = options;\n const prefixPattern = `${prefix}_`;\n\n return {\n priority,\n onRequest: async (req: ServerRequest) => {\n // Try query param first\n const url = typeof req.url === \"string\" ? new URL(req.url) : req.url;\n let token = url.searchParams.get(\"api_key\");\n\n // Try Bearer header - only if token starts with expected prefix\n if (!token) {\n const auth = req.headers.authorization;\n if (auth?.startsWith(\"Bearer \")) {\n const bearerToken = auth.slice(7);\n if (bearerToken.startsWith(prefixPattern)) {\n token = bearerToken;\n }\n }\n }\n\n if (!token) {\n return null;\n }\n\n return this.validate(token);\n },\n };\n }\n\n // -------------------------------------------------------------------------\n // CRUD\n // -------------------------------------------------------------------------\n\n /**\n * Create a new API key for a user.\n * Returns both the API key entity and the plain token (which is only available once).\n */\n public async create(options: {\n userId: string;\n name: string;\n roles: string[];\n description?: string;\n expiresAt?: Date;\n prefix?: string;\n }): Promise<{ apiKey: ApiKeyEntity; token: string }> {\n const prefix = options.prefix ?? \"ak\";\n const random = randomBytes(24).toString(\"base64url\");\n const token = `${prefix}_${random}`;\n const hash = this.hashToken(token);\n const suffix = token.slice(-8);\n\n const apiKey = await this.repo.create({\n userId: options.userId,\n name: options.name,\n description: options.description,\n tokenHash: hash,\n tokenPrefix: prefix,\n tokenSuffix: suffix,\n roles: options.roles,\n expiresAt: options.expiresAt?.toISOString(),\n });\n\n this.log.info(\"API key created\", {\n apiKeyId: apiKey.id,\n userId: options.userId,\n name: options.name,\n });\n\n return { apiKey, token };\n }\n\n /**\n * List all non-revoked API keys for a user.\n */\n public async list(userId: string): Promise<ApiKeyEntity[]> {\n return this.repo.findMany({\n where: {\n userId: { eq: userId },\n revokedAt: { isNull: true },\n },\n orderBy: { column: \"createdAt\", direction: \"desc\" },\n });\n }\n\n // -------------------------------------------------------------------------\n // Admin Operations\n // -------------------------------------------------------------------------\n\n /**\n * Find all API keys with optional filtering (admin only).\n */\n public async findAll(query: {\n userId?: string;\n includeRevoked?: boolean;\n page?: number;\n size?: number;\n sort?: string;\n }) {\n query.sort ??= \"-createdAt\";\n\n const where = this.repo.createQueryWhere();\n\n if (query.userId) {\n where.userId = { eq: query.userId };\n }\n\n if (!query.includeRevoked) {\n where.revokedAt = { isNull: true };\n }\n\n return this.repo.paginate(query, { where }, { count: true });\n }\n\n /**\n * Get an API key by ID (admin only).\n */\n public async getById(id: string): Promise<ApiKeyEntity> {\n return await this.repo.getById(id);\n }\n\n /**\n * Revoke any API key (admin only).\n */\n public async revokeByAdmin(id: string): Promise<void> {\n const apiKey = await this.repo.getById(id);\n\n if (apiKey.revokedAt) {\n return; // Already revoked\n }\n\n // Invalidate cache\n await this.validationCache.invalidate(apiKey.tokenHash);\n\n await this.repo.updateById(id, {\n revokedAt: this.dateTimeProvider.now().toISOString(),\n });\n\n this.log.info(\"API key revoked by admin\", {\n apiKeyId: id,\n userId: apiKey.userId,\n });\n }\n\n // -------------------------------------------------------------------------\n // User Operations\n // -------------------------------------------------------------------------\n\n /**\n * Revoke an API key. Only the owner can revoke their own keys.\n */\n public async revoke(id: string, userId: string): Promise<void> {\n const apiKey = await this.repo.getById(id);\n\n if (apiKey.userId !== userId) {\n throw new ForbiddenError(\"Not your API key\");\n }\n\n await this.validationCache.invalidate(apiKey.tokenHash);\n\n await this.repo.updateById(id, {\n revokedAt: this.dateTimeProvider.now().toISOString(),\n });\n\n this.log.info(\"API key revoked\", {\n apiKeyId: id,\n userId,\n });\n }\n\n // -------------------------------------------------------------------------\n // Validation\n // -------------------------------------------------------------------------\n\n /**\n * Validate an API key token and return user info if valid.\n */\n public async validate(token: string): Promise<UserInfo | null> {\n // Quick check for API key format\n if (!token.includes(\"_\")) {\n return null;\n }\n\n const hash = this.hashToken(token);\n\n // Try cache first\n let apiKey = await this.validationCache.get(hash);\n\n // If not in cache, look up in database\n if (apiKey === undefined) {\n apiKey =\n (await this.repo.findOne({\n where: { tokenHash: { eq: hash } },\n })) ?? null;\n\n // Store in cache (even if null, to prevent repeated lookups)\n await this.validationCache.set(hash, apiKey);\n }\n\n if (!apiKey) {\n return null;\n }\n\n // Check revocation\n if (apiKey.revokedAt) {\n return null;\n }\n\n // Check expiration\n if (\n apiKey.expiresAt &&\n this.dateTimeProvider.now().isAfter(apiKey.expiresAt)\n ) {\n return null;\n }\n\n // Update usage stats (fire and forget)\n this.updateUsage(apiKey.id).catch((error) => {\n this.log.warn(\"Failed to update API key usage\", { error });\n });\n\n return {\n id: apiKey.userId,\n roles: apiKey.roles,\n };\n }\n\n /**\n * Update usage statistics for an API key.\n */\n protected async updateUsage(id: string): Promise<void> {\n const request = this.alepha.store.get(\"alepha.http.request\");\n\n await this.repo.updateById(id, {\n lastUsedAt: this.dateTimeProvider.now().toISOString(),\n lastUsedIp: request?.ip,\n usageCount: sql`${this.repo.table.usageCount} + 1`,\n });\n }\n\n /**\n * Hash a token using SHA-256.\n */\n protected hashToken(token: string): string {\n return createHash(\"sha256\").update(token).digest(\"hex\");\n }\n}\n","import { $inject, t } from \"alepha\";\nimport { $secure } from \"alepha/security\";\nimport { $action, okSchema } from \"alepha/server\";\nimport { adminApiKeyQuerySchema } from \"../schemas/adminApiKeyQuerySchema.ts\";\nimport { adminApiKeyResourceSchema } from \"../schemas/adminApiKeyResourceSchema.ts\";\nimport { ApiKeyService } from \"../services/ApiKeyService.ts\";\n\n/**\n * REST API controller for admin API key management.\n * Admins can list, view, and revoke any API key.\n */\nexport class AdminApiKeyController {\n protected readonly url = \"/admin/api-keys\";\n protected readonly group = \"admin:api-keys\";\n protected readonly apiKeyService = $inject(ApiKeyService);\n\n /**\n * Find all API keys with optional filtering.\n */\n public readonly findApiKeys = $action({\n path: this.url,\n group: this.group,\n use: [$secure({ permissions: [\"admin:api-key:read\"] })],\n description: \"Find API keys with pagination and filtering\",\n schema: {\n query: adminApiKeyQuerySchema,\n response: t.page(adminApiKeyResourceSchema),\n },\n handler: ({ query }) => {\n const { userId, includeRevoked, ...pagination } = query;\n return this.apiKeyService.findAll({\n userId,\n includeRevoked,\n ...pagination,\n });\n },\n });\n\n /**\n * Get an API key by ID.\n */\n public readonly getApiKey = $action({\n path: `${this.url}/:id`,\n group: this.group,\n use: [$secure({ permissions: [\"admin:api-key:read\"] })],\n description: \"Get an API key by ID\",\n schema: {\n params: t.object({\n id: t.uuid(),\n }),\n response: adminApiKeyResourceSchema,\n },\n handler: ({ params }) => this.apiKeyService.getById(params.id),\n });\n\n /**\n * Revoke any API key.\n */\n public readonly revokeApiKey = $action({\n method: \"DELETE\",\n path: `${this.url}/:id`,\n group: this.group,\n use: [$secure({ permissions: [\"admin:api-key:delete\"] })],\n description: \"Revoke an API key\",\n schema: {\n params: t.object({\n id: t.uuid(),\n }),\n response: okSchema,\n },\n handler: async ({ params }) => {\n await this.apiKeyService.revokeByAdmin(params.id);\n return { ok: true, id: params.id };\n },\n });\n}\n","import { t } from \"alepha\";\n\nexport const createApiKeyBodySchema = t.object({\n name: t.text({ minLength: 1, maxLength: 100 }),\n description: t.optional(t.text({ maxLength: 500 })),\n expiresAt: t.optional(t.datetime()),\n});\n","import { t } from \"alepha\";\n\nexport const createApiKeyResponseSchema = t.object({\n id: t.uuid(),\n name: t.string(),\n token: t.string(),\n tokenSuffix: t.string(),\n roles: t.array(t.string()),\n createdAt: t.datetime(),\n expiresAt: t.optional(t.datetime()),\n});\n","import { t } from \"alepha\";\n\nexport const listApiKeyItemSchema = t.object({\n id: t.uuid(),\n name: t.string(),\n tokenPrefix: t.string(),\n tokenSuffix: t.string(),\n roles: t.array(t.string()),\n createdAt: t.datetime(),\n lastUsedAt: t.optional(t.datetime()),\n expiresAt: t.optional(t.datetime()),\n usageCount: t.integer(),\n});\n\nexport const listApiKeyResponseSchema = t.array(listApiKeyItemSchema);\n","import { t } from \"alepha\";\n\nexport const revokeApiKeyParamsSchema = t.object({\n id: t.uuid(),\n});\n","import { t } from \"alepha\";\n\nexport const revokeApiKeyResponseSchema = t.object({\n ok: t.boolean(),\n});\n","import { $inject } from \"alepha\";\nimport { $secure } from \"alepha/security\";\nimport { $action } from \"alepha/server\";\nimport { createApiKeyBodySchema } from \"../schemas/createApiKeyBodySchema.ts\";\nimport { createApiKeyResponseSchema } from \"../schemas/createApiKeyResponseSchema.ts\";\nimport { listApiKeyResponseSchema } from \"../schemas/listApiKeyResponseSchema.ts\";\nimport { revokeApiKeyParamsSchema } from \"../schemas/revokeApiKeyParamsSchema.ts\";\nimport { revokeApiKeyResponseSchema } from \"../schemas/revokeApiKeyResponseSchema.ts\";\nimport { ApiKeyService } from \"../services/ApiKeyService.ts\";\n\n/**\n * REST API controller for user's own API key management.\n * Users can create, list, and revoke their own API keys.\n */\nexport class ApiKeyController {\n protected readonly url = \"/api-keys\";\n protected readonly group = \"api-keys\";\n protected readonly apiKeyService = $inject(ApiKeyService);\n\n /**\n * Create a new API key for the authenticated user.\n * The token is only returned once upon creation.\n */\n public readonly createApiKey = $action({\n method: \"POST\",\n path: this.url,\n group: this.group,\n description: \"Create a new API key\",\n use: [$secure({ permissions: [\"api-key:create\"] })],\n schema: {\n body: createApiKeyBodySchema,\n response: createApiKeyResponseSchema,\n },\n handler: async (request) => {\n const { apiKey, token } = await this.apiKeyService.create({\n userId: request.user.id,\n name: request.body.name,\n description: request.body.description,\n roles: request.user.roles ?? [],\n expiresAt: request.body.expiresAt\n ? new Date(request.body.expiresAt)\n : undefined,\n });\n\n return {\n id: apiKey.id,\n name: apiKey.name,\n token,\n tokenSuffix: apiKey.tokenSuffix,\n roles: apiKey.roles,\n createdAt: apiKey.createdAt,\n expiresAt: apiKey.expiresAt,\n };\n },\n });\n\n /**\n * List all active API keys for the authenticated user.\n * Does not return the actual tokens.\n */\n public readonly listApiKeys = $action({\n path: this.url,\n group: this.group,\n description: \"List your API keys\",\n use: [$secure({ permissions: [\"api-key:read\"] })],\n schema: {\n response: listApiKeyResponseSchema,\n },\n handler: async (request) => {\n const apiKeys = await this.apiKeyService.list(request.user.id);\n\n return apiKeys.map((apiKey) => ({\n id: apiKey.id,\n name: apiKey.name,\n tokenPrefix: apiKey.tokenPrefix,\n tokenSuffix: apiKey.tokenSuffix,\n roles: apiKey.roles,\n createdAt: apiKey.createdAt,\n lastUsedAt: apiKey.lastUsedAt,\n expiresAt: apiKey.expiresAt,\n usageCount: apiKey.usageCount,\n }));\n },\n });\n\n /**\n * Revoke an API key. Only the owner can revoke their own keys.\n */\n public readonly revokeMyApiKey = $action({\n method: \"DELETE\",\n path: `${this.url}/:id`,\n group: this.group,\n description: \"Revoke an API key\",\n use: [$secure({ permissions: [\"api-key:delete\"] })],\n schema: {\n params: revokeApiKeyParamsSchema,\n response: revokeApiKeyResponseSchema,\n },\n handler: async (request) => {\n await this.apiKeyService.revoke(request.params.id, request.user.id);\n return { ok: true };\n },\n });\n}\n","import { $module } from \"alepha\";\nimport { AdminApiKeyController } from \"./controllers/AdminApiKeyController.ts\";\nimport { ApiKeyController } from \"./controllers/ApiKeyController.ts\";\nimport { ApiKeyService } from \"./services/ApiKeyService.ts\";\n\nexport * from \"./controllers/AdminApiKeyController.ts\";\nexport * from \"./controllers/ApiKeyController.ts\";\nexport * from \"./entities/apiKeyEntity.ts\";\nexport * from \"./schemas/adminApiKeyQuerySchema.ts\";\nexport * from \"./schemas/adminApiKeyResourceSchema.ts\";\nexport * from \"./schemas/createApiKeyBodySchema.ts\";\nexport * from \"./schemas/createApiKeyResponseSchema.ts\";\nexport * from \"./schemas/listApiKeyResponseSchema.ts\";\nexport * from \"./schemas/revokeApiKeyParamsSchema.ts\";\nexport * from \"./schemas/revokeApiKeyResponseSchema.ts\";\nexport * from \"./services/ApiKeyService.ts\";\n\n/**\n * API key management module for programmatic access.\n *\n * **Features:**\n * - Create API keys with role snapshots\n * - List and revoke API keys\n * - 15-minute validation caching\n * - Query param (?api_key=) and Bearer header support\n *\n * **Integration:**\n * To enable API key authentication for an issuer, register the resolver:\n *\n * ```ts\n * class MyApp {\n * apiKeyService = $inject(ApiKeyService);\n * issuer = $issuer({\n * secret: env.APP_SECRET,\n * resolvers: [this.apiKeyService.createResolver()],\n * });\n * }\n * ```\n *\n * @module alepha.api.keys\n */\nexport const AlephaApiKeys = $module({\n name: \"alepha.api.keys\",\n services: [ApiKeyService, ApiKeyController, AdminApiKeyController],\n});\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../../src/api/keys/schemas/adminApiKeyQuerySchema.ts","../../../src/api/keys/schemas/adminApiKeyResourceSchema.ts","../../../src/api/keys/entities/apiKeyEntity.ts","../../../src/api/keys/services/ApiKeyService.ts","../../../src/api/keys/controllers/AdminApiKeyController.ts","../../../src/api/keys/schemas/createApiKeyBodySchema.ts","../../../src/api/keys/schemas/createApiKeyResponseSchema.ts","../../../src/api/keys/schemas/listApiKeyResponseSchema.ts","../../../src/api/keys/schemas/revokeApiKeyParamsSchema.ts","../../../src/api/keys/schemas/revokeApiKeyResponseSchema.ts","../../../src/api/keys/controllers/ApiKeyController.ts","../../../src/api/keys/index.ts"],"sourcesContent":["import { t } from \"alepha\";\nimport { pageQuerySchema } from \"alepha/orm\";\n\nexport const adminApiKeyQuerySchema = t.extend(pageQuerySchema, {\n userId: t.optional(t.uuid()),\n includeRevoked: t.optional(t.boolean()),\n});\n","import { t } from \"alepha\";\n\nexport const adminApiKeyResourceSchema = t.object({\n id: t.uuid(),\n userId: t.uuid(),\n name: t.string(),\n description: t.optional(t.string()),\n tokenPrefix: t.string(),\n tokenSuffix: t.string(),\n roles: t.array(t.string()),\n createdAt: t.datetime(),\n lastUsedAt: t.optional(t.datetime()),\n lastUsedIp: t.optional(t.string()),\n expiresAt: t.optional(t.datetime()),\n revokedAt: t.optional(t.datetime()),\n usageCount: t.integer(),\n});\n","import { type Static, t } from \"alepha\";\nimport { $entity, db } from \"alepha/orm\";\n\nexport const apiKeyEntity = $entity({\n name: \"api_keys\",\n schema: t.object({\n id: db.primaryKey(t.uuid()),\n createdAt: db.createdAt(),\n updatedAt: db.updatedAt(),\n\n // Owner\n userId: t.uuid(),\n\n // Key metadata\n name: t.text({ maxLength: 100 }),\n description: t.optional(t.text({ maxLength: 500 })),\n\n // Token (hashed) - internal, not user input\n tokenHash: t.string({ maxLength: 256 }),\n tokenPrefix: t.string({ maxLength: 10 }),\n tokenSuffix: t.string({ maxLength: 8 }),\n\n // Roles (snapshot from user at creation)\n roles: db.default(t.array(t.string()), []),\n\n // Tracking\n lastUsedAt: t.optional(t.datetime()),\n lastUsedIp: t.optional(t.string({ maxLength: 45 })),\n usageCount: db.default(t.integer(), 0),\n\n // Lifecycle\n expiresAt: t.optional(t.datetime()),\n revokedAt: t.optional(t.datetime()),\n }),\n indexes: [\n { columns: [\"userId\", \"name\"], unique: true },\n { columns: [\"tokenHash\"], unique: true },\n ],\n});\n\nexport type ApiKeyEntity = Static<typeof apiKeyEntity.schema>;\n","import { createHash, randomBytes } from \"node:crypto\";\nimport { $inject, Alepha } from \"alepha\";\nimport { $cache } from \"alepha/cache\";\nimport { DateTimeProvider } from \"alepha/datetime\";\nimport { $logger } from \"alepha/logger\";\nimport { $repository, sql } from \"alepha/orm\";\nimport type { IssuerResolver, UserInfo } from \"alepha/security\";\nimport { ForbiddenError, type ServerRequest } from \"alepha/server\";\nimport { type ApiKeyEntity, apiKeyEntity } from \"../entities/apiKeyEntity.ts\";\n\nexport class ApiKeyService {\n protected readonly alepha = $inject(Alepha);\n protected readonly dateTimeProvider = $inject(DateTimeProvider);\n protected readonly log = $logger();\n protected readonly repo = $repository(apiKeyEntity);\n\n /**\n * Cache validated API keys for 15 minutes.\n */\n protected readonly validationCache = $cache<ApiKeyEntity | null, [string]>({\n name: \"api:keys:validation\",\n ttl: [15, \"minutes\"],\n });\n\n // -------------------------------------------------------------------------\n // Resolver\n // -------------------------------------------------------------------------\n\n /**\n * Create an issuer resolver for API key authentication.\n * Lower priority means it runs before JWT resolver.\n *\n * @param options.priority - Priority of this resolver (default: 50, JWT is 100)\n * @param options.prefix - API key prefix to match in Bearer header (default: \"ak\")\n */\n public createResolver(\n options: { priority?: number; prefix?: string } = {},\n ): IssuerResolver {\n const { priority = 50, prefix = \"ak\" } = options;\n const prefixPattern = `${prefix}_`;\n\n return {\n priority,\n onRequest: async (req: ServerRequest) => {\n // Try query param first\n const url = typeof req.url === \"string\" ? new URL(req.url) : req.url;\n let token = url.searchParams.get(\"api_key\");\n\n // Try Bearer header - only if token starts with expected prefix\n if (!token) {\n const auth = req.headers.authorization;\n if (auth?.startsWith(\"Bearer \")) {\n const bearerToken = auth.slice(7);\n if (bearerToken.startsWith(prefixPattern)) {\n token = bearerToken;\n }\n }\n }\n\n if (!token) {\n return null;\n }\n\n return this.validate(token);\n },\n };\n }\n\n // -------------------------------------------------------------------------\n // CRUD\n // -------------------------------------------------------------------------\n\n /**\n * Create a new API key for a user.\n * Returns both the API key entity and the plain token (which is only available once).\n */\n public async create(options: {\n userId: string;\n name: string;\n roles: string[];\n description?: string;\n expiresAt?: Date;\n prefix?: string;\n }): Promise<{ apiKey: ApiKeyEntity; token: string }> {\n const prefix = options.prefix ?? \"ak\";\n const random = randomBytes(24).toString(\"base64url\");\n const token = `${prefix}_${random}`;\n const hash = this.hashToken(token);\n const suffix = token.slice(-8);\n\n const apiKey = await this.repo.create({\n userId: options.userId,\n name: options.name,\n description: options.description,\n tokenHash: hash,\n tokenPrefix: prefix,\n tokenSuffix: suffix,\n roles: options.roles,\n expiresAt: options.expiresAt?.toISOString(),\n });\n\n this.log.info(\"API key created\", {\n apiKeyId: apiKey.id,\n userId: options.userId,\n name: options.name,\n });\n\n return { apiKey, token };\n }\n\n /**\n * List all non-revoked API keys for a user.\n */\n public async list(userId: string): Promise<ApiKeyEntity[]> {\n return this.repo.findMany({\n where: {\n userId: { eq: userId },\n revokedAt: { isNull: true },\n },\n orderBy: { column: \"createdAt\", direction: \"desc\" },\n });\n }\n\n // -------------------------------------------------------------------------\n // Admin Operations\n // -------------------------------------------------------------------------\n\n /**\n * Find all API keys with optional filtering (admin only).\n */\n public async findAll(query: {\n userId?: string;\n includeRevoked?: boolean;\n page?: number;\n size?: number;\n sort?: string;\n }) {\n query.sort ??= \"-createdAt\";\n\n const where = this.repo.createQueryWhere();\n\n if (query.userId) {\n where.userId = { eq: query.userId };\n }\n\n if (!query.includeRevoked) {\n where.revokedAt = { isNull: true };\n }\n\n return this.repo.paginate(query, { where }, { count: true });\n }\n\n /**\n * Get an API key by ID (admin only).\n */\n public async getById(id: string): Promise<ApiKeyEntity> {\n return await this.repo.getById(id);\n }\n\n /**\n * Revoke any API key (admin only).\n */\n public async revokeByAdmin(id: string): Promise<void> {\n const apiKey = await this.repo.getById(id);\n\n if (apiKey.revokedAt) {\n return; // Already revoked\n }\n\n // Invalidate cache\n await this.validationCache.invalidate(apiKey.tokenHash);\n\n await this.repo.updateById(id, {\n revokedAt: this.dateTimeProvider.now().toISOString(),\n });\n\n this.log.info(\"API key revoked by admin\", {\n apiKeyId: id,\n userId: apiKey.userId,\n });\n }\n\n // -------------------------------------------------------------------------\n // User Operations\n // -------------------------------------------------------------------------\n\n /**\n * Revoke an API key. Only the owner can revoke their own keys.\n */\n public async revoke(id: string, userId: string): Promise<void> {\n const apiKey = await this.repo.getById(id);\n\n if (apiKey.userId !== userId) {\n throw new ForbiddenError(\"Not your API key\");\n }\n\n await this.validationCache.invalidate(apiKey.tokenHash);\n\n await this.repo.updateById(id, {\n revokedAt: this.dateTimeProvider.now().toISOString(),\n });\n\n this.log.info(\"API key revoked\", {\n apiKeyId: id,\n userId,\n });\n }\n\n // -------------------------------------------------------------------------\n // Validation\n // -------------------------------------------------------------------------\n\n /**\n * Validate an API key token and return user info if valid.\n */\n public async validate(token: string): Promise<UserInfo | null> {\n // Quick check for API key format\n if (!token.includes(\"_\")) {\n return null;\n }\n\n const hash = this.hashToken(token);\n\n // Try cache first\n let apiKey = await this.validationCache.get(hash);\n\n // If not in cache, look up in database\n if (apiKey === undefined) {\n apiKey =\n (await this.repo.findOne({\n where: { tokenHash: { eq: hash } },\n })) ?? null;\n\n // Store in cache (even if null, to prevent repeated lookups)\n await this.validationCache.set(hash, apiKey);\n }\n\n if (!apiKey) {\n return null;\n }\n\n // Check revocation\n if (apiKey.revokedAt) {\n return null;\n }\n\n // Check expiration\n if (\n apiKey.expiresAt &&\n this.dateTimeProvider.now().isAfter(apiKey.expiresAt)\n ) {\n return null;\n }\n\n // Update usage stats (fire and forget)\n this.updateUsage(apiKey.id).catch((error) => {\n this.log.warn(\"Failed to update API key usage\", { error });\n });\n\n return {\n id: apiKey.userId,\n roles: apiKey.roles,\n };\n }\n\n /**\n * Update usage statistics for an API key.\n */\n protected async updateUsage(id: string): Promise<void> {\n const request = this.alepha.store.get(\"alepha.http.request\");\n\n await this.repo.updateById(id, {\n lastUsedAt: this.dateTimeProvider.now().toISOString(),\n lastUsedIp: request?.ip,\n usageCount: sql`${this.repo.table.usageCount} + 1`,\n });\n }\n\n /**\n * Hash a token using SHA-256.\n */\n protected hashToken(token: string): string {\n return createHash(\"sha256\").update(token).digest(\"hex\");\n }\n}\n","import { $inject, t } from \"alepha\";\nimport { $secure } from \"alepha/security\";\nimport { $action, okSchema } from \"alepha/server\";\nimport { adminApiKeyQuerySchema } from \"../schemas/adminApiKeyQuerySchema.ts\";\nimport { adminApiKeyResourceSchema } from \"../schemas/adminApiKeyResourceSchema.ts\";\nimport { ApiKeyService } from \"../services/ApiKeyService.ts\";\n\n/**\n * REST API controller for admin API key management.\n * Admins can list, view, and revoke any API key.\n */\nexport class AdminApiKeyController {\n protected readonly url = \"/admin/api-keys\";\n protected readonly group = \"admin:api-keys\";\n protected readonly apiKeyService = $inject(ApiKeyService);\n\n /**\n * Find all API keys with optional filtering.\n */\n public readonly findApiKeys = $action({\n path: this.url,\n group: this.group,\n use: [$secure({ permissions: [\"admin:api-key:read\"] })],\n description: \"Find API keys with pagination and filtering\",\n schema: {\n query: adminApiKeyQuerySchema,\n response: t.page(adminApiKeyResourceSchema),\n },\n handler: ({ query }) => {\n const { userId, includeRevoked, ...pagination } = query;\n return this.apiKeyService.findAll({\n userId,\n includeRevoked,\n ...pagination,\n });\n },\n });\n\n /**\n * Get an API key by ID.\n */\n public readonly getApiKey = $action({\n path: `${this.url}/:id`,\n group: this.group,\n use: [$secure({ permissions: [\"admin:api-key:read\"] })],\n description: \"Get an API key by ID\",\n schema: {\n params: t.object({\n id: t.uuid(),\n }),\n response: adminApiKeyResourceSchema,\n },\n handler: ({ params }) => this.apiKeyService.getById(params.id),\n });\n\n /**\n * Revoke any API key.\n */\n public readonly revokeApiKey = $action({\n method: \"DELETE\",\n path: `${this.url}/:id`,\n group: this.group,\n use: [$secure({ permissions: [\"admin:api-key:delete\"] })],\n description: \"Revoke an API key\",\n schema: {\n params: t.object({\n id: t.uuid(),\n }),\n response: okSchema,\n },\n handler: async ({ params }) => {\n await this.apiKeyService.revokeByAdmin(params.id);\n return { ok: true, id: params.id };\n },\n });\n}\n","import { t } from \"alepha\";\n\nexport const createApiKeyBodySchema = t.object({\n name: t.text({ minLength: 1, maxLength: 100 }),\n description: t.optional(t.text({ maxLength: 500 })),\n expiresAt: t.optional(t.datetime()),\n});\n","import { t } from \"alepha\";\n\nexport const createApiKeyResponseSchema = t.object({\n id: t.uuid(),\n name: t.string(),\n token: t.string(),\n tokenSuffix: t.string(),\n roles: t.array(t.string()),\n createdAt: t.datetime(),\n expiresAt: t.optional(t.datetime()),\n});\n","import { t } from \"alepha\";\n\nexport const listApiKeyItemSchema = t.object({\n id: t.uuid(),\n name: t.string(),\n tokenPrefix: t.string(),\n tokenSuffix: t.string(),\n roles: t.array(t.string()),\n createdAt: t.datetime(),\n lastUsedAt: t.optional(t.datetime()),\n expiresAt: t.optional(t.datetime()),\n usageCount: t.integer(),\n});\n\nexport const listApiKeyResponseSchema = t.array(listApiKeyItemSchema);\n","import { t } from \"alepha\";\n\nexport const revokeApiKeyParamsSchema = t.object({\n id: t.uuid(),\n});\n","import { t } from \"alepha\";\n\nexport const revokeApiKeyResponseSchema = t.object({\n ok: t.boolean(),\n});\n","import { $inject } from \"alepha\";\nimport { $secure } from \"alepha/security\";\nimport { $action } from \"alepha/server\";\nimport { createApiKeyBodySchema } from \"../schemas/createApiKeyBodySchema.ts\";\nimport { createApiKeyResponseSchema } from \"../schemas/createApiKeyResponseSchema.ts\";\nimport { listApiKeyResponseSchema } from \"../schemas/listApiKeyResponseSchema.ts\";\nimport { revokeApiKeyParamsSchema } from \"../schemas/revokeApiKeyParamsSchema.ts\";\nimport { revokeApiKeyResponseSchema } from \"../schemas/revokeApiKeyResponseSchema.ts\";\nimport { ApiKeyService } from \"../services/ApiKeyService.ts\";\n\n/**\n * REST API controller for user's own API key management.\n * Users can create, list, and revoke their own API keys.\n */\nexport class ApiKeyController {\n protected readonly url = \"/api-keys\";\n protected readonly group = \"api-keys\";\n protected readonly apiKeyService = $inject(ApiKeyService);\n\n /**\n * Create a new API key for the authenticated user.\n * The token is only returned once upon creation.\n */\n public readonly createApiKey = $action({\n method: \"POST\",\n path: this.url,\n group: this.group,\n description: \"Create a new API key\",\n use: [$secure({ permissions: [\"api-key:create\"] })],\n schema: {\n body: createApiKeyBodySchema,\n response: createApiKeyResponseSchema,\n },\n handler: async (request) => {\n const { apiKey, token } = await this.apiKeyService.create({\n userId: request.user.id,\n name: request.body.name,\n description: request.body.description,\n roles: request.user.roles ?? [],\n expiresAt: request.body.expiresAt\n ? new Date(request.body.expiresAt)\n : undefined,\n });\n\n return {\n id: apiKey.id,\n name: apiKey.name,\n token,\n tokenSuffix: apiKey.tokenSuffix,\n roles: apiKey.roles,\n createdAt: apiKey.createdAt,\n expiresAt: apiKey.expiresAt,\n };\n },\n });\n\n /**\n * List all active API keys for the authenticated user.\n * Does not return the actual tokens.\n */\n public readonly listApiKeys = $action({\n path: this.url,\n group: this.group,\n description: \"List your API keys\",\n use: [$secure({ permissions: [\"api-key:read\"] })],\n schema: {\n response: listApiKeyResponseSchema,\n },\n handler: async (request) => {\n const apiKeys = await this.apiKeyService.list(request.user.id);\n\n return apiKeys.map((apiKey) => ({\n id: apiKey.id,\n name: apiKey.name,\n tokenPrefix: apiKey.tokenPrefix,\n tokenSuffix: apiKey.tokenSuffix,\n roles: apiKey.roles,\n createdAt: apiKey.createdAt,\n lastUsedAt: apiKey.lastUsedAt,\n expiresAt: apiKey.expiresAt,\n usageCount: apiKey.usageCount,\n }));\n },\n });\n\n /**\n * Revoke an API key. Only the owner can revoke their own keys.\n */\n public readonly revokeMyApiKey = $action({\n method: \"DELETE\",\n path: `${this.url}/:id`,\n group: this.group,\n description: \"Revoke an API key\",\n use: [$secure({ permissions: [\"api-key:delete\"] })],\n schema: {\n params: revokeApiKeyParamsSchema,\n response: revokeApiKeyResponseSchema,\n },\n handler: async (request) => {\n await this.apiKeyService.revoke(request.params.id, request.user.id);\n return { ok: true };\n },\n });\n}\n","import { $module } from \"alepha\";\nimport { AdminApiKeyController } from \"./controllers/AdminApiKeyController.ts\";\nimport { ApiKeyController } from \"./controllers/ApiKeyController.ts\";\nimport { ApiKeyService } from \"./services/ApiKeyService.ts\";\n\nexport * from \"./controllers/AdminApiKeyController.ts\";\nexport * from \"./controllers/ApiKeyController.ts\";\nexport * from \"./entities/apiKeyEntity.ts\";\nexport * from \"./schemas/adminApiKeyQuerySchema.ts\";\nexport * from \"./schemas/adminApiKeyResourceSchema.ts\";\nexport * from \"./schemas/createApiKeyBodySchema.ts\";\nexport * from \"./schemas/createApiKeyResponseSchema.ts\";\nexport * from \"./schemas/listApiKeyResponseSchema.ts\";\nexport * from \"./schemas/revokeApiKeyParamsSchema.ts\";\nexport * from \"./schemas/revokeApiKeyResponseSchema.ts\";\nexport * from \"./services/ApiKeyService.ts\";\n\n/**\n * API key management module for programmatic access.\n *\n * **Features:**\n * - Create API keys with role snapshots\n * - List and revoke API keys\n * - 15-minute validation caching\n * - Query param (?api_key=) and Bearer header support\n *\n * **Integration:**\n * To enable API key authentication for an issuer, register the resolver:\n *\n * ```ts\n * class MyApp {\n * apiKeyService = $inject(ApiKeyService);\n * issuer = $issuer({\n * secret: env.APP_SECRET,\n * resolvers: [this.apiKeyService.createResolver()],\n * });\n * }\n * ```\n *\n * @module alepha.api.keys\n */\nexport const AlephaApiKeys = $module({\n name: \"alepha.api.keys\",\n services: [ApiKeyService, ApiKeyController, AdminApiKeyController],\n});\n"],"mappings":"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"}
|
|
@@ -8,7 +8,6 @@ import { DateTimeProvider } from "alepha/datetime";
|
|
|
8
8
|
import * as _$alepha_logger0 from "alepha/logger";
|
|
9
9
|
import { EmailProvider } from "alepha/email";
|
|
10
10
|
import { SmsProvider } from "alepha/sms";
|
|
11
|
-
import * as _$typebox from "typebox";
|
|
12
11
|
|
|
13
12
|
//#region ../../src/api/notifications/primitives/$notification.d.ts
|
|
14
13
|
/**
|
|
@@ -116,15 +115,7 @@ declare class NotificationSenderService {
|
|
|
116
115
|
};
|
|
117
116
|
protected load(payload: NotificationPayload): {
|
|
118
117
|
template: NotificationPrimitive<_$alepha.TObject<_$alepha.TProperties>>;
|
|
119
|
-
variables:
|
|
120
|
-
type: _$alepha.TUnsafe<"email" | "sms">;
|
|
121
|
-
template: _$alepha.TString;
|
|
122
|
-
contact: _$alepha.TString;
|
|
123
|
-
variables: _$alepha.TOptional<_$alepha.TRecord<"^.*$", _$alepha.TAny>>;
|
|
124
|
-
category: _$alepha.TOptional<_$alepha.TString>;
|
|
125
|
-
critical: _$alepha.TOptional<_$alepha.TBoolean>;
|
|
126
|
-
sensitive: _$alepha.TOptional<_$alepha.TBoolean>;
|
|
127
|
-
}, "^.*$", _$alepha.TAny>;
|
|
118
|
+
variables: Record<string, any>;
|
|
128
119
|
contact: string;
|
|
129
120
|
};
|
|
130
121
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","names":[],"sources":["../../../src/api/notifications/primitives/$notification.ts","../../../src/api/notifications/schemas/notificationPayloadSchema.ts","../../../src/api/notifications/services/NotificationSenderService.ts","../../../src/api/notifications/jobs/NotificationJobs.ts","../../../src/api/notifications/controllers/AdminNotificationController.ts","../../../src/api/notifications/schemas/notificationContactPreferencesSchema.ts","../../../src/api/notifications/schemas/notificationContactSchema.ts","../../../src/api/notifications/schemas/notificationDetailResourceSchema.ts","../../../src/api/notifications/schemas/notificationQuerySchema.ts","../../../src/api/notifications/schemas/notificationResourceSchema.ts","../../../src/api/notifications/index.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":[],"sources":["../../../src/api/notifications/primitives/$notification.ts","../../../src/api/notifications/schemas/notificationPayloadSchema.ts","../../../src/api/notifications/services/NotificationSenderService.ts","../../../src/api/notifications/jobs/NotificationJobs.ts","../../../src/api/notifications/controllers/AdminNotificationController.ts","../../../src/api/notifications/schemas/notificationContactPreferencesSchema.ts","../../../src/api/notifications/schemas/notificationContactSchema.ts","../../../src/api/notifications/schemas/notificationDetailResourceSchema.ts","../../../src/api/notifications/schemas/notificationQuerySchema.ts","../../../src/api/notifications/schemas/notificationResourceSchema.ts","../../../src/api/notifications/index.ts"],"mappings":"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"}
|