UNPKG

@beignet/core

Version:

Core framework primitives for Beignet

760 lines 24.3 kB
import type { StandardSchemaV1 } from "@standard-schema/spec"; import type { LeaseMetadata, LocksPort } from "../locks/index.js"; import { type TraceCarrier, type TracingPort } from "../tracing/index.js"; /** * Any Standard Schema compatible validator. */ export type StandardSchema = StandardSchemaV1<unknown, unknown>; /** * Value or promise of that value. */ export type MaybePromise<T> = T | Promise<T>; type NoInferJob<T> = [T][T extends unknown ? 0 : never]; /** * Infer the parsed output type from a Standard Schema. */ export type InferSchemaOutput<T extends StandardSchemaV1> = StandardSchemaV1.InferOutput<T>; /** * Duration accepted by job helpers. Numbers are milliseconds. */ export type JobDuration = number | `${number}ms` | `${number}s` | `${number}m` | `${number}h`; /** * Duration accepted by retry helpers. Numbers are milliseconds. */ export type JobRetryDuration = JobDuration; /** * Duration accepted by job timeout helpers. Numbers are milliseconds. */ export type JobTimeoutDuration = JobDuration; /** * Duration accepted by job execution lease helpers. Numbers are milliseconds. */ export type JobExecutionLeaseDuration = JobDuration; /** * Retry strategy understood by Beignet job adapters. */ export type JobRetryStrategy = "none" | "fixed" | "exponential"; /** * Arguments passed to a retry predicate. */ export interface JobRetryPredicateArgs { /** * Error thrown by the previous attempt. */ error: unknown; /** * One-based attempt number that just failed. */ attempt: number; /** * Maximum attempts allowed for this delivery. */ maxAttempts: number; /** * Job name when the retry decision is for a job. */ jobName?: string; } /** * Return whether a failed attempt should be retried. */ export type JobRetryPredicate = (args: JobRetryPredicateArgs) => boolean; /** * Dispatch-time uniqueness metadata for a job. * * Unique jobs are suppressed while the resolved key's lease is active. * The lease is intentionally left to expire after a successful dispatch. */ export interface JobUniqueOptions { /** * Logical uniqueness key within this job name. */ key: string; /** * How long duplicate dispatches should be suppressed. */ ttl: JobDuration; } /** * Arguments passed to a per-payload unique job resolver. */ export interface JobUniqueResolverArgs<Payload extends StandardSchema> { /** * Stable job name. */ jobName: string; /** * Parsed job payload. */ payload: StandardSchemaV1.InferOutput<Payload>; } /** * Per-payload resolver for dispatch-time uniqueness. */ export type JobUniqueResolver<Payload extends StandardSchema> = { bivarianceHack(args: JobUniqueResolverArgs<Payload>): MaybePromise<JobUniqueOptions | null | undefined>; }["bivarianceHack"]; /** * Unique job declaration value. */ export type JobUniqueConfig<Payload extends StandardSchema = StandardSchema> = JobUniqueOptions | JobUniqueResolver<Payload>; /** * Resolved uniqueness metadata used by dispatchers. */ export interface ResolvedJobUniqueOptions { /** * Logical uniqueness key returned by the job declaration. */ key: string; /** * Concrete lock key used by `LocksPort`. */ lockKey: string; /** * Lease TTL in milliseconds. */ ttlMs: number; } /** * Job definition created by `defineJob(...)`. */ export interface JobDef<Name extends string = string, Payload extends StandardSchema = StandardSchema, Ctx = unknown> { /** * Discriminator for job definitions. */ readonly kind: "job"; /** * Stable job name used by dispatchers and provider adapters. */ readonly name: Name; /** * Standard Schema payload validator. */ readonly payload: Payload; /** * Optional human-readable description for docs and tooling. */ readonly description?: string; /** * Retry metadata for durable job providers. */ readonly retry?: JobRetryOptions; /** * Optional dispatch-time uniqueness guard. */ readonly unique?: JobUniqueConfig<Payload>; /** * Optional maximum execution time for each job handler attempt. */ readonly timeout?: JobTimeoutDuration; /** * Optional hooks that wrap each handler attempt for this job. */ readonly hooks?: readonly JobHook<JobDef<Name, Payload, Ctx>, Ctx>[]; /** * Handle a parsed job payload. */ handle(args: JobHandleArgs<JobDef<Name, Payload, Ctx>, Ctx>): MaybePromise<void>; } /** * Infer the parsed payload type for a job definition. */ export type InferJobPayload<J extends JobDef> = J["payload"] extends StandardSchemaV1<unknown, infer Output> ? Output : never; /** * Arguments passed to a job handler. */ export interface JobHandleArgs<J extends JobDef, Ctx> { /** * Job definition being handled. */ job: J; /** * Parsed job payload. */ payload: InferJobPayload<J>; /** Handler context. */ ctx: Ctx; /** * Abort signal that fires when the job's declared timeout expires. * * Beignet dispatchers and worker helpers provide this signal. It is optional * in the type so direct handler tests can stay lightweight. * * JavaScript cannot forcibly stop arbitrary async work, so handlers that * call cancellable APIs should pass this signal through. */ signal?: AbortSignal; } /** * Arguments passed to job execution hooks. */ export interface JobHookArgs<J extends JobDef, Ctx> extends JobHandleArgs<J, Ctx> { /** * Abort signal provided by the Beignet execution runner. */ signal: AbortSignal; /** * One-based execution attempt when the runner can report it. */ attempt?: number; /** * Maximum execution attempts when the runner can report it. */ maxAttempts?: number; } /** * Hook that wraps one job handler attempt. * * Hooks run only through Beignet dispatchers and worker helpers. Direct calls * to `job.handle(...)` bypass hooks, which keeps lightweight unit tests * possible. */ export type JobHook<J extends JobDef = JobDef, Ctx = unknown> = { bivarianceHack(args: JobHookArgs<J, Ctx>, next: () => Promise<void>): MaybePromise<void>; }["bivarianceHack"]; /** * Arguments passed to an execution lease key resolver. */ export type JobExecutionLeaseResolverArgs<J extends JobDef, Ctx> = JobHookArgs<J, Ctx>; /** * Resolver for the logical execution lease key within a job name. */ export type JobExecutionLeaseKeyResolver<J extends JobDef, Ctx> = { bivarianceHack(args: JobExecutionLeaseResolverArgs<J, Ctx>): MaybePromise<string>; }["bivarianceHack"]; /** * Resolver for the locks port used by an execution lease hook. */ export type JobExecutionLeaseLocksResolver<J extends JobDef, Ctx> = LocksPort | { bivarianceHack(args: JobExecutionLeaseResolverArgs<J, Ctx>): MaybePromise<LocksPort>; }["bivarianceHack"]; /** * Resolver for optional diagnostics metadata attached to lease acquisition. */ export type JobExecutionLeaseMetadataResolver<J extends JobDef, Ctx> = LeaseMetadata | { bivarianceHack(args: JobExecutionLeaseResolverArgs<J, Ctx>): MaybePromise<LeaseMetadata | undefined>; }["bivarianceHack"]; /** * Arguments passed when an execution lease cannot be acquired. */ export interface JobExecutionLeaseUnavailableArgs<J extends JobDef, Ctx> extends JobHookArgs<J, Ctx> { /** * Logical lease key returned by the hook configuration. */ key: string; /** * Concrete lock key passed to `LocksPort`. */ lockKey: string; /** * Acquisition failure reason returned by `LocksPort`. */ reason: "unavailable" | "timeout"; } /** * Behavior when an execution lease cannot be acquired. * * `"skip"` treats the attempt as successful without running the handler. * `"throw"` fails the attempt with `JobExecutionLeaseUnavailableError` so the * job retry policy can classify it. A function can log or throw custom errors; * returning from it skips the handler. */ export type JobExecutionLeaseUnavailableBehavior<J extends JobDef, Ctx> = "skip" | "throw" | { bivarianceHack(args: JobExecutionLeaseUnavailableArgs<J, Ctx>): MaybePromise<void>; }["bivarianceHack"]; /** * Options for `createJobExecutionLeaseHook(...)`. */ export interface JobExecutionLeaseHookOptions<J extends JobDef = JobDef, Ctx = unknown> { /** * Locks port or resolver used to acquire the execution lease. */ locks: JobExecutionLeaseLocksResolver<J, Ctx>; /** * Logical lease key within this job name. */ key: string | JobExecutionLeaseKeyResolver<J, Ctx>; /** * Lease time-to-live. This is the real safety boundary in serverless runtimes * where an invocation may terminate before `finally` runs. */ ttl: JobExecutionLeaseDuration; /** * How long to wait for an existing lease before applying `onUnavailable`. * * Defaults to no wait. */ wait?: JobExecutionLeaseDuration; /** * Delay between acquisition attempts while waiting. */ retryDelay?: JobExecutionLeaseDuration; /** * Prefix used to build the concrete lock key. * * Defaults to `"jobs:lease"`. */ keyPrefix?: string; /** * Optional diagnostics metadata attached to the lease acquire call. */ metadata?: JobExecutionLeaseMetadataResolver<J, Ctx>; /** * Behavior when the lease cannot be acquired. * * Defaults to `"skip"` so overlapping executions do not create retry storms. */ onUnavailable?: JobExecutionLeaseUnavailableBehavior<J, Ctx>; } /** * Retry metadata that durable job providers can map to their own retry model. */ export interface JobRetryOptions { /** * Retry strategy. Raw objects without a strategy default to exponential * backoff so existing `{ attempts }` style definitions stay meaningful. */ strategy?: JobRetryStrategy; /** * Maximum total attempts, including the first attempt. */ attempts?: number; /** * Delay between attempts for fixed retry policies. */ delay?: JobRetryDuration; /** * Initial delay for exponential retry policies. */ initialDelay?: JobRetryDuration; /** * Maximum delay for exponential retry policies. */ maxDelay?: JobRetryDuration; /** * Exponential multiplier. Defaults to `2`. */ factor?: number; /** * Whether adapters that compute delays should add jitter. */ jitter?: boolean; /** * Optional app-owned retry classifier. */ retryIf?: JobRetryPredicate; } /** * Options for declaring a typed job. */ export interface DefineJobOptions<Name extends string, Payload extends StandardSchema, Ctx> { /** * Standard Schema payload validator. */ payload: Payload; /** * Optional human-readable description for docs and tooling. */ description?: string; /** * Retry metadata for durable job providers. */ retry?: JobRetryOptions; /** * Optional dispatch-time uniqueness guard. */ unique?: JobUniqueConfig<Payload>; /** * Optional maximum execution time for each job handler attempt. */ timeout?: JobTimeoutDuration; /** * Optional hooks that wrap each handler attempt for this job. */ hooks?: readonly JobHook<JobDef<NoInferJob<Name>, NoInferJob<Payload>, Ctx>, Ctx>[]; /** * Handle a parsed job payload. */ handle(args: JobHandleArgs<JobDef<Name, Payload, Ctx>, Ctx>): MaybePromise<void>; } /** * Options for a fixed job retry policy. */ export interface FixedJobRetryOptions { /** * Maximum total attempts, including the first attempt. */ attempts: number; /** * Delay between attempts. */ delay: JobRetryDuration; /** * Optional app-owned retry classifier. */ retryIf?: JobRetryPredicate; } /** * Options for an exponential job retry policy. */ export interface ExponentialJobRetryOptions { /** * Maximum total attempts, including the first attempt. */ attempts: number; /** * Initial delay. Defaults to `1s`. */ initialDelay?: JobRetryDuration; /** * Maximum delay. Defaults to `1m`. */ maxDelay?: JobRetryDuration; /** * Exponential multiplier. Defaults to `2`. */ factor?: number; /** * Whether computed delays should include jitter. */ jitter?: boolean; /** * Optional app-owned retry classifier. */ retryIf?: JobRetryPredicate; } /** * Retry helper namespace for job definitions. */ export declare const retry: { /** * Disable retries. The first failure is terminal. */ readonly none: () => JobRetryOptions; /** * Retry with the same delay between attempts. */ readonly fixed: (options: FixedJobRetryOptions) => JobRetryOptions; /** * Retry with exponential backoff. */ readonly exponential: (options: ExponentialJobRetryOptions) => JobRetryOptions; }; /** * Options for the inline job dispatcher. */ export interface InlineJobDispatcherOptions<Ctx> { /** * Static job context or factory evaluated for each dispatched job. */ ctx?: Ctx | (() => MaybePromise<Ctx>); /** * Called when a dispatched inline job fails all attempts allowed by its * retry policy. When omitted, the final error is rethrown to the caller. */ onError?: (error: unknown, job: JobDef<string, StandardSchema, Ctx>) => void; /** * Sleep implementation used between retry attempts. Defaults to a real * `setTimeout` delay; inject a fake in tests to keep retries instant. */ sleep?: (ms: number) => Promise<void>; /** * Honor the job's declared retry policy inline. Defaults to `true`. Set * `false` when another layer owns execution retries for every dispatch * through this dispatcher. */ retry?: boolean; /** * Hooks that wrap every job attempt executed by this dispatcher. Runner hooks * wrap job-local hooks. */ hooks?: readonly JobHook<JobDef<string, StandardSchema, Ctx>, Ctx>[]; } /** Metadata propagated when a job is dispatched. */ export interface JobDispatchOptions { /** Versioned trace context captured by the job producer. */ trace?: TraceCarrier; } /** Parsed payload and propagation metadata from a job transport envelope. */ export interface ParsedJobTransportEnvelope { payload: unknown; trace?: TraceCarrier; } /** * Wrap a job payload with transport metadata when a trace is present. * Payloads without metadata retain their legacy wire shape. */ export declare function createJobTransportEnvelope(payload: unknown, options?: JobDispatchOptions): unknown; /** * Decode a Beignet job transport envelope while accepting legacy raw payloads. * Unknown or malformed trace metadata is ignored without dropping the payload. */ export declare function parseJobTransportEnvelope(value: unknown): ParsedJobTransportEnvelope; /** * Well-known symbol under which the inline dispatcher exposes a * single-attempt dispatch. Delivery systems that own execution retries * themselves — the outbox drain — call it instead of `dispatch(...)` so a * job's retry policy runs in exactly one layer. Registered with `Symbol.for` * so multiple core copies in one process agree on the key. */ export declare const SINGLE_ATTEMPT_DISPATCH: unique symbol; /** * Metadata passed to a single-attempt dispatch when another delivery layer * owns retry scheduling. */ export interface SingleAttemptJobDispatchOptions extends JobDispatchOptions { /** * One-based delivery attempt. */ attempt?: number; /** * Maximum delivery attempts. */ maxAttempts?: number; } /** * Shape of the single-attempt dispatch exposed under * `SINGLE_ATTEMPT_DISPATCH`. */ export type SingleAttemptJobDispatch = <J extends JobDef>(job: J, payload: InferJobPayload<J>, options?: SingleAttemptJobDispatchOptions) => Promise<void>; /** * Job dispatcher shape accepted by job wrappers. */ export interface JobDispatcher { dispatch<J extends JobDef>(job: J, payload: InferJobPayload<J>, options?: JobDispatchOptions): MaybePromise<void>; } /** * Arguments passed when a unique job dispatch is suppressed. */ export interface UniqueJobDuplicateArgs<J extends JobDef = JobDef> { /** * Job definition whose dispatch was suppressed. */ job: J; /** * Parsed job payload. */ payload: InferJobPayload<J>; /** * Logical uniqueness key returned by the job declaration. */ key: string; /** * Concrete lock key used by `LocksPort`. */ lockKey: string; /** * Lease TTL in milliseconds. */ ttlMs: number; /** * Lock acquisition reason returned by `LocksPort`. */ reason: "unavailable" | "timeout"; } /** * Options for `createUniqueJobDispatcher(...)`. */ export interface UniqueJobDispatcherOptions { /** * Dispatcher to call when a job is not unique or the unique lease is acquired. */ jobs: JobDispatcher; /** * Lease-backed lock port used as the uniqueness guard. */ locks: LocksPort; /** * Prefix for concrete lock keys. * * Defaults to `"jobs:unique"`. */ keyPrefix?: string; /** * Called when a duplicate dispatch is suppressed. */ onDuplicate?: (args: UniqueJobDuplicateArgs) => MaybePromise<void>; } /** * Local/test job dispatcher that executes job handlers inline. */ export interface InlineJobDispatcher<Ctx = unknown> { /** * Validate a payload and run the job handler inline, honoring the job's * declared retry policy before failing. */ dispatch<J extends JobDef<string, StandardSchema, Ctx>>(job: J, payload: InferJobPayload<J>, options?: JobDispatchOptions): Promise<void>; } /** * Context-bound job helper factory. */ export interface Jobs<Ctx> { /** * Define a job with the bound context type. */ defineJob<Name extends string, Payload extends StandardSchema>(name: Name, options: DefineJobOptions<Name, Payload, Ctx>): JobDef<Name, Payload, Ctx>; } /** * Error thrown when job payload validation fails. */ export declare class JobValidationError extends Error { /** * Raw Standard Schema validation issues. */ readonly issues: readonly StandardSchemaV1.Issue[]; constructor(args: { name: string; issues: readonly StandardSchemaV1.Issue[]; }); } /** * Error thrown when a job handler exceeds its declared timeout. */ export declare class JobTimeoutError extends Error { /** * Stable job name that timed out. */ readonly jobName: string; /** * Timeout in milliseconds. */ readonly timeoutMs: number; constructor(args: { jobName: string; timeoutMs: number; }); } /** * Error thrown when an execution lease hook is configured to fail on an * unavailable lease. */ export declare class JobExecutionLeaseUnavailableError extends Error { /** * Stable job name whose execution lease was unavailable. */ readonly jobName: string; /** * Logical lease key returned by the hook configuration. */ readonly key: string; /** * Concrete lock key passed to `LocksPort`. */ readonly lockKey: string; /** * Acquisition failure reason returned by `LocksPort`. */ readonly reason: "unavailable" | "timeout"; constructor(args: { jobName: string; key: string; lockKey: string; reason: "unavailable" | "timeout"; }); } /** * Return the maximum total attempts configured by a retry policy. */ export declare function getJobRetryMaxAttempts(options: JobRetryOptions | undefined): number | undefined; /** * Return whether a failed job attempt should be retried. */ export declare function shouldRetryJob(options: JobRetryOptions | undefined, args: JobRetryPredicateArgs): boolean; /** * Compute the next retry delay in milliseconds for a failed job attempt. */ export declare function getJobRetryDelayMs(options: JobRetryOptions | undefined, args: Pick<JobRetryPredicateArgs, "attempt" | "error" | "jobName">): number; /** * Return the execution timeout in milliseconds configured by a job. */ export declare function getJobTimeoutMs(job: Pick<JobDef, "timeout">): number | undefined; /** * Create a job hook that prevents overlapping handler attempts for the same * logical execution key. * * The hook uses one bounded `LocksPort.acquire(...)` call and never starts * renewal loops, so it can run in serverless entrypoints as long as `locks` * points at shared storage. `ttl` is the real safety boundary when a runtime * terminates before best-effort release runs. */ export declare function createJobExecutionLeaseHook<J extends JobDef = JobDef, Ctx = unknown>(options: JobExecutionLeaseHookOptions<J, Ctx>): JobHook<J, Ctx>; /** * Validate and parse a job payload with the job's Standard Schema. */ export declare function parseJobPayload<J extends JobDef>(job: J, payload: unknown): Promise<InferJobPayload<J>>; /** * Options for running one parsed job handler attempt. */ export interface RunJobHandlerOptions<J extends JobDef<string, StandardSchema, Ctx>, Ctx> { /** * Job definition to execute. */ job: J; /** * Parsed job payload. */ payload: InferJobPayload<J>; /** Handler context or factory resolved inside the job span. */ ctx: Ctx | (() => MaybePromise<Ctx>); /** Runtime tracing port used before a lazy context factory runs. */ tracing?: TracingPort; /** Trace context captured by the dispatching process. */ trace?: TraceCarrier; /** * Runner-level hooks. These wrap job-local hooks. */ hooks?: readonly JobHook<JobDef<string, StandardSchema, Ctx>, Ctx>[]; /** * One-based execution attempt when the runner can report it. */ attempt?: number; /** * Maximum execution attempts when the runner can report it. */ maxAttempts?: number; } /** * Run a parsed job handler once, enforcing hooks and the job's declared * timeout. */ export declare function runJobHandler<J extends JobDef<string, StandardSchema, Ctx>, Ctx>(args: RunJobHandlerOptions<J, Ctx>): Promise<void>; /** * Resolve a job's dispatch-time uniqueness metadata for a parsed payload. */ export declare function resolveJobUnique<J extends JobDef>(job: J, payload: InferJobPayload<J>, options?: { keyPrefix?: string; }): Promise<ResolvedJobUniqueOptions | undefined>; /** * Create a local/test dispatcher that runs job handlers inline. * * Dispatch honors the job's declared retry policy: failed attempts retry with * the policy's delays until the policy is exhausted. Payload validation * failures never retry. Jobs without a retry policy run exactly once. */ export declare function createInlineJobDispatcher<Ctx>(options?: InlineJobDispatcherOptions<Ctx>): InlineJobDispatcher<Ctx>; /** * Wrap any job dispatcher with dispatch-time unique job suppression. * * When a job has no `unique` declaration, dispatch passes through unchanged. * When it does, the wrapper validates the payload, resolves the unique key, * acquires the matching lease, and calls the underlying dispatcher only when * the lease is acquired. Successful dispatches intentionally keep the lease * until its TTL expires; failed dispatches release it so callers can retry. */ export declare function createUniqueJobDispatcher(options: UniqueJobDispatcherOptions): JobDispatcher; /** * Create job helper methods bound to an application context type. * * Call it once in `lib/jobs.ts`: * * ```ts * export const { defineJob } = createJobs<AppContext>(); * ``` * * Retry options describe the job's retry policy. Inline dispatchers run the * policy in-process with real delays; durable providers map the policy onto * their own runtime and reject options they cannot honor. */ export declare function createJobs<Ctx>(): Jobs<Ctx>; export {}; //# sourceMappingURL=index.d.ts.map