As33
@periodic/
arsenic

Fastify Integration

The fastifyContext plugin registers as a Fastify plugin, hooking into the request lifecycle to propagate context to all downstream queries.

Setup

typescript
import Fastify from 'fastify';
import { PrismaClient } from '@prisma/client';
import { createMonitor, fastifyContext, prismaAdapter } from '@periodic/arsenic';

const app = Fastify({ logger: true });
const prisma = new PrismaClient();

const monitor = createMonitor({
  slowQueryThresholdMs: 200,
  exporter: (event) => {
    if (event.severity === 'critical') sendToPagerDuty(event);
  },
});

// Register BEFORE routes
await app.register(fastifyContext(monitor, {
  attachUser: (req) => req.user?.id,
}));

prismaAdapter(monitor, prisma);

app.get('/api/users/:id', async (req, res) => {
  const user = await prisma.user.findUnique({
    where: { id: req.params.id },
  });
  return user;
});

await app.listen({ port: 3000, host: '0.0.0.0' });

Register before routes

Use await app.register() to ensure the plugin is fully initialized before routes are registered.

With TypeScript typed request

typescript
import { FastifyRequest } from 'fastify';

interface JWTUser { id: string; email: string; role: string; }

declare module 'fastify' {
  interface FastifyRequest {
    user?: JWTUser;
  }
}

await app.register(fastifyContext(monitor, {
  attachUser: (req: FastifyRequest) => req.user?.id,
}));

With multiple databases

typescript
import Redis from 'ioredis';

const pgMonitor = createMonitor({ slowQueryThresholdMs: 100, exporter: pgExporter });
const redisMonitor = createMonitor({ slowQueryThresholdMs: 50, exporter: redisExporter });

// One fastifyContext is enough — it covers all DB adapters
await app.register(fastifyContext(pgMonitor));

// Attach adapters with their own monitors
prismaAdapter(pgMonitor, prisma);
redisAdapter(redisMonitor, redis);