Provenance
EcosystemAuto-Instrumentation

Frameworks & ORMs

Framework-specific setup for Express, Fastify, Prisma, and Sequelize — plus manual tracking for custom events.

The http.Server patch captures every inbound request automatically for any Node.js framework. Framework-specific patches add enrichment — route patterns, params, parsed body, and authenticated user.

Express

Express is auto-detected when installed — no extra setup needed. The Express patch captures:

  • Route patternreq.route.path (e.g. /api/orders/:id)
  • Paramsreq.params (e.g. { id: 'order-123' })
  • Bodyreq.body (parsed by express.json() or similar)
  • Userreq.user (set by auth middleware like Passport, express-jwt, etc.)
const { instrument } = require('@stdiolabs/provenance-sdk/auto');

instrument({
  apiKey: process.env.PROVENANCE_API_KEY,
  origin: 'order-service',
});

const express = require('express');
const app = express();
app.use(express.json());

app.post('/api/orders/:id/ship', async (req, res) => {
  // Route pattern, params, body, and req.user all captured automatically
  res.json({ shipped: true });
});

Fastify

Fastify requires an explicit call because the Fastify instance is user-created:

const { instrument, instrumentFastify } = require('@stdiolabs/provenance-sdk/auto');

instrument({
  apiKey: process.env.PROVENANCE_API_KEY,
  origin: 'order-service',
});

const fastify = require('fastify')();

// Pass your Fastify instance to enable route-level enrichment
instrumentFastify(fastify);

fastify.get('/api/orders/:id', async (request) => {
  return { id: request.params.id, status: 'shipped' };
});

fastify.listen({ port: 3000 });

instrumentFastify() adds Fastify-specific enrichment:

  • Route patternrequest.routeOptions.url (e.g. /api/orders/:id) instead of the raw URL
  • Paramsrequest.params (e.g. { id: 'order-123' })
  • Bodyrequest.body (parsed by Fastify)
  • Userrequest.user (set by auth plugins like @fastify/jwt)

Without instrumentFastify(), requests are still tracked — but with raw paths instead of route patterns, and without params or user info.

Fastify with auth

If you use @fastify/jwt or similar auth plugins that set request.user, the user is captured automatically:

const { instrument, instrumentFastify } = require('@stdiolabs/provenance-sdk/auto');

instrument({
  apiKey: process.env.PROVENANCE_API_KEY,
  origin: 'order-service',
});

const fastify = require('fastify')();
await fastify.register(require('@fastify/jwt'), { secret: 'your-secret' });

instrumentFastify(fastify);

fastify.addHook('onRequest', async (request) => {
  await request.jwtVerify();
});

fastify.get('/api/orders/:id', async (request) => {
  // request.user is captured automatically
  return { id: request.params.id };
});

The user is picked up from both onRequest hooks (early auth) and preHandler hooks (late auth). Sensitive fields like password, token, and secret are automatically stripped.

Prisma

Prisma requires an explicit call because the client is instantiated in your code:

const { instrument, instrumentPrisma } = require('@stdiolabs/provenance-sdk/auto');

instrument({
  apiKey: process.env.PROVENANCE_API_KEY,
  origin: 'order-service',
});

const { PrismaClient } = require('@prisma/client');
const prisma = new PrismaClient();

// Pass your Prisma client to enable ORM-level tracking
instrumentPrisma(prisma);

This tracks every create, update, upsert, delete, findUnique, findFirst, and findMany call with the model name and operation type.

Sequelize

Sequelize requires an explicit call because the instance is user-created:

const { instrument, instrumentSequelize } = require('@stdiolabs/provenance-sdk/auto');

instrument({
  apiKey: process.env.PROVENANCE_API_KEY,
  origin: 'order-service',
});

const { Sequelize } = require('sequelize');
const sequelize = new Sequelize(process.env.DATABASE_URL);

// Pass your Sequelize instance to enable ORM-level tracking
instrumentSequelize(sequelize);

This hooks into Sequelize's global lifecycle hooks and tracks:

  • afterCreate / afterBulkCreateCREATE
  • afterUpdate / afterBulkUpdate / afterUpsertUPDATE
  • afterDestroy / afterBulkDestroyDELETE
  • afterFindREAD

Model names and resource IDs are extracted automatically from the instance. The resolver's models map works the same as with Prisma:

instrument({
  apiKey: process.env.PROVENANCE_API_KEY,
  origin: 'order-service',
  models: {
    Order: { resourceType: 'PURCHASE_ORDER' },  // override convention
    AuditLog: null,                               // exclude from tracking
  },
});

Manual tracking

Inside an instrumented request, you can manually add interactions to the current buffer:

const { track, getContext } = require('@stdiolabs/provenance-sdk/auto');

app.post('/api/orders/:id/process', async (req, res) => {
  // Your business logic...
  const result = await processOrder(req.params.id);

  // Manually track a domain event
  track({
    resourceType: 'ORDER',
    resourceId: req.params.id,
    action: 'PROCESS',
    interaction: { result: result.status, processor: 'stripe' },
  });

  // Read the current UOW ID
  const ctx = getContext();
  console.log('UOW:', ctx.uowId);

  res.json(result);
});

Manual interactions are batched and flushed with the auto-tracked ones — same UOW, same flush.