UNPKG

@convex-dev/action-retrier

Version:

Convex component for retrying idempotent actions.

156 lines 6.65 kB
import { createFunctionHandle, } from "convex/server"; import { v } from "convex/values"; import { runResult } from "../component/schema.js"; export const runIdValidator = v.string(); export const onCompleteValidator = v.object({ runId: runIdValidator, result: runResult, }); const DEFAULT_INITIAL_BACKOFF_MS = 250; const DEFAULT_BASE = 2; const DEFAULT_MAX_FAILURES = 4; export class ActionRetrier { component; options; /** * Create a new ActionRetrier, which retries failed actions with exponential backoff. * ```ts * import { components } from "./_generated/server" * const actionRetrier = new ActionRetrier(components.actionRetrier) * * // In a mutation or action... * await actionRetrier.run(ctx, internal.module.myAction, { arg: 123 }); * ``` * * @param component - The registered action retrier from `components`. * @param options - Optional overrides for the default backoff and retry behavior. */ constructor(component, options) { this.component = component; let DEFAULT_LOG_LEVEL = "INFO"; if (process.env.ACTION_RETRIER_LOG_LEVEL) { if (!["DEBUG", "INFO", "WARN", "ERROR"].includes(process.env.ACTION_RETRIER_LOG_LEVEL)) { console.warn(`Invalid log level (${process.env.ACTION_RETRIER_LOG_LEVEL}), defaulting to "INFO"`); } DEFAULT_LOG_LEVEL = process.env.ACTION_RETRIER_LOG_LEVEL; } this.options = { initialBackoffMs: options?.initialBackoffMs ?? DEFAULT_INITIAL_BACKOFF_MS, base: options?.base ?? DEFAULT_BASE, maxFailures: options?.maxFailures ?? DEFAULT_MAX_FAILURES, logLevel: options?.logLevel ?? DEFAULT_LOG_LEVEL, }; } /** * Run an action with retries, optionally with an `onComplete` mutation callback. * * @param ctx - The context object from your mutation or action. * @param reference - The function reference to run, e.g., `internal.module.myAction`. * @param args - Arguments for the action, e.g., `{ arg: 123 }`. * @param options.initialBackoffMs - Optional override for the default initial backoff on failure. * @param options.base - Optional override for the default base for the exponential backoff. * @param options.maxFailures - Optional override for the default maximum number of retries. * @param options.onComplete - Optional mutation to run after the function succeeds, fails, * or is canceled. This function must take in a single `result` argument of type `RunResult`: use * `runResultValidator` to validate this argument. * @returns - A `RunId` for the run that can be used to query its status below. */ async run(ctx, reference, args, options) { const handle = await createFunctionHandle(reference); let onComplete; if (options?.onComplete) { onComplete = await createFunctionHandle(options.onComplete); } const runId = await ctx.runMutation(this.component.public.start, { functionHandle: handle, functionArgs: args ?? {}, options: { ...this.options, ...stripUndefined(options), onComplete, }, }); return runId; } /** * Run an action like {@link run} but no earlier than a specific timestamp. * * @param ctx - The context object from your mutation or action. * @param runAtTimestampMs - The timestamp in milliseconds to run the action at. * @param reference - The function reference to run, e.g., `internal.module.myAction`. * @param args - Arguments for the action, e.g., `{ arg: 123 }`. * @param options - See {@link RunOptions}. */ async runAt(ctx, runAtTimestampMs, reference, args, options) { const opts = { ...options, runAt: runAtTimestampMs, }; return this.run(ctx, reference, args, opts); } /** * Run an action like {@link run} but no earlier than after specific delay. * * Note: the delay is from the time of calling this, not from when it's made * it to the front of the queue. * * @param ctx - The context object from your mutation or action. * @param runAfterMs - The delay in milliseconds before running the action. * @param reference - The function reference to run, e.g., `internal.module.myAction`. * @param args - Arguments for the action, e.g., `{ arg: 123 }`. * @param options - See {@link RunOptions}. */ async runAfter(ctx, runAfterMs, reference, args, options) { const opts = { ...options, runAfter: runAfterMs, }; return this.run(ctx, reference, args, opts); } /** * Query the status of a run. * * @param ctx - The context object from your query, mutation, or action. * @param runId - The `RunId` returned from `run`. * @returns - An object indicating whether the run is in progress or has completed. If * the run has completed, the `result.type` field indicates whether it succeeded, * failed, or was canceled. */ async status(ctx, runId) { return ctx.runQuery(this.component.public.status, { runId }); } /** * Attempt to cancel a run. This method throws if the run isn't currently executing. * If the run is currently executing (and not waiting for retry), action execution may * continue after this method successfully returns. * * @param ctx - The context object from your mutation or action. * @param runId - The `RunId` returned from `run`. */ async cancel(ctx, runId) { await ctx.runMutation(this.component.public.cancel, { runId }); } /** * Cleanup a completed run's storage from the system. This method throws if the run * doesn't exist or isn't in the completed state. * * The system will also automatically clean up runs that are more than 7 days old. * * @param ctx - The context object from your mutation or action. * @param runId - The `RunId` returned from `run`. */ async cleanup(ctx, runId) { await ctx.runMutation(this.component.public.cleanup, { runId }); } } function stripUndefined(obj) { if (obj === undefined) { return obj; } return Object.fromEntries(Object.entries(obj).filter(([_, value]) => value !== undefined)); } /** * Validator for the `result` argument of the `onComplete` callback. */ export const runResultValidator = runResult; //# sourceMappingURL=index.js.map