Skip to content

Instantly share code, notes, and snippets.

@ianmartorell
Last active January 22, 2024 20:46
Show Gist options
  • Select an option

  • Save ianmartorell/73a544a7d1a83d305dc846f2ecb6b5f3 to your computer and use it in GitHub Desktop.

Select an option

Save ianmartorell/73a544a7d1a83d305dc846f2ecb6b5f3 to your computer and use it in GitHub Desktop.

Revisions

  1. ianmartorell revised this gist Jan 22, 2024. No changes.
  2. ianmartorell created this gist Jan 22, 2024.
    30 changes: 30 additions & 0 deletions inngest.ts
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,30 @@
    import { retryFunction } from '@lib/utils/retryFunction';
    import { ClientOptions, EventSchemas, EventsFromOpts, Inngest } from 'inngest';
    import { SendEventPayload } from 'inngest/helpers/types';

    import { Events } from './events';

    const inngestOptions = {
    id: 'my-app',
    schemas: new EventSchemas().fromRecord<Events>(),
    } satisfies ClientOptions;

    export const inngest = new Inngest(inngestOptions);

    /**
    * Sends an event to Inngest with retry functionality. If the event
    * fails to send we will log it without bubbling up the error.
    *
    * @param event The event payload to send.
    */
    export async function sendInngestEvent<
    Payload extends SendEventPayload<EventsFromOpts<typeof inngestOptions>>,
    >(event: Payload) {
    const sendInngestEvent = async () => await inngest.send(event);
    try {
    await retryFunction(sendInngestEvent);
    } catch (error) {
    const message = error instanceof Error ? error.message : error;
    logger.error(`Failed to send Inngest event: ${message}`, { error, event });
    }
    }
    60 changes: 60 additions & 0 deletions retryFunction.ts
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,60 @@
    import { logger } from '@lib/server/logger';

    /**
    * This function retries a given function a specified number of times.
    * The function will be run at most 1 + `retries` times.
    *
    * Example output:
    *
    * Running sendInngestEvent: 1 of 4
    * Error: fetch failed
    * Retrying in 2s... { runCount: 1, retries: 3 }
    * Running sendInngestEvent: 2 of 4
    * Error: fetch failed
    * Retrying in 4s... { runCount: 2, retries: 3 }
    * Running sendInngestEvent: 3 of 4
    * Error: fetch failed
    * Retrying in 6s... { runCount: 3, retries: 3 }
    * Running sendInngestEvent: 4 of 4
    * Error: fetch failed
    * Max retries reached {
    * "runCount": 4,
    * "retries": 3
    * }
    */
    export async function retryFunction<T>(
    fn: () => T | Promise<T>,
    options: { runCount: number; retries: number } = {
    runCount: 1,
    retries: 3,
    }
    ): Promise<T> {
    logger.debug(`Running ${fn.name}: ${options.runCount} of ${options.retries + 1}`);
    try {
    return await fn();
    } catch (error: unknown) {
    if (error instanceof Error) {
    logger.error(`Error: ${error.message}`);
    } else {
    logger.error(error);
    }
    // Throw if we've reached the max number of retries
    if (options.runCount >= options.retries + 1) {
    logger.error('Max retries reached', options);
    throw error || new Error('Max retries reached');
    }
    // Else retry with an incremental backoff time
    const wait = 2000 * options.runCount;
    logger.debug(`Retrying in ${wait / 1000}s...`, options);
    return new Promise((resolve, reject) => {
    setTimeout(
    () =>
    retryFunction(fn, {
    runCount: options.runCount + 1,
    retries: options.retries,
    }).then(resolve, reject),
    wait
    );
    });
    }
    }