Fair Supply LogoFair Supply - Docs

Inngest Infrastructure

Inngest handles background jobs and event-driven workflows for the FSA Platform.

Key Files

TypeLocation
Clientpackages/core/src/infrastructure/inngest/client.ts
Eventspackages/core/src/domain/events.ts
Jobspackages/core/src/application/jobs/

Event Definition

// packages/core/src/domain/events.ts
export type InngestEvents = {
  'organisation.create.provisional': {
    data: { accountId: string; name: string; diffbotId?: string };
  };
  'organisation.created': {
    data: { organisationId: string; accountId: string };
  };
};

Job Function Template

// packages/core/src/application/jobs/create-provisional-organisation.ts
import { inngest } from '../../infrastructure/inngest/client';

export const createProvisionalOrganisation = inngest.createFunction(
  {
    id: 'create-provisional-organisation',
    concurrency: { limit: 100 },
    retries: 3,
  },
  { event: 'organisation.create.provisional' },
  async ({ event, step }) => {
    const { accountId, name } = event.data;

    // Step 1: Create organisation
    const org = await step.run('create-organisation', async () => {
      return OrganisationRepository.create({ name });
    });

    // Step 2: Link to account
    await step.run('link-to-account', async () => {
      return OrganisationRepository.linkToAccount(org.id, accountId);
    });

    // Step 3: Emit completion event
    await step.sendEvent('emit-created', {
      name: 'organisation.created',
      data: { organisationId: org.id, accountId },
    });

    return { organisationId: org.id };
  }
);

Step Patterns

Sequential Steps

const result1 = await step.run('step-1', () => doFirst());
const result2 = await step.run('step-2', () => doSecond(result1));

Parallel Steps

const [a, b] = await Promise.all([
  step.run('parallel-1', () => doA()),
  step.run('parallel-2', () => doB()),
]);

Wait for Event

const childEvent = await step.waitForEvent('wait-for-child', {
  event: 'child.completed',
  match: 'data.parentId',
  timeout: '1h',
});

Send Events

await step.sendEvent('emit-event', {
  name: 'something.happened',
  data: { id: '123' },
});

Error Handling

Non-Fatal Errors

await step.run('optional-step', async () => {
  try {
    return await riskyOperation();
  } catch (error) {
    console.error('Non-fatal:', error);
    return null;  // Continue execution
  }
});

Failure Handler

inngest.createFunction(
  {
    id: 'my-function',
    retries: 3,
    onFailure: async ({ error, event }) => {
      await notifyFailure(error, event);
    },
  },
  { event: 'my.event' },
  async ({ event, step }) => { ... }
);

Concurrency & Throttling

{
  id: 'my-function',
  concurrency: {
    limit: 100,
    key: 'event.data.accountId',  // Per account
  },
  throttle: {
    limit: 1000,
    period: '1m',
    burst: 100,
  },
}

Triggering Jobs

import { inngest } from '@repo/core/infrastructure/inngest/client';

// From use case or server action
await inngest.send({
  name: 'organisation.create.provisional',
  data: { accountId, name },
});

Best Practices

  1. Use steps for durability - each step is checkpointed
  2. Idempotent operations - steps may be retried
  3. Meaningful step names - for debugging
  4. Emit completion events - for job coordination
  5. Concurrency limits - prevent overwhelming resources
TypeLocation
Clientpackages/core/src/infrastructure/inngest/client.ts
Eventspackages/core/src/domain/events.ts
Jobspackages/core/src/application/jobs/
API Routeapps/web/src/app/api/inngest/route.ts