npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

fluxrun

v0.0.22

Published

fluxRun wraps async functions so every execution is recorded, encrypted, and replayable — without changing how you write or call your code.

Readme

fluxrun

fluxRun wraps async functions so every execution is recorded, encrypted, and replayable — without changing how you write or call your code.

How it works

your async function
  └─ wrapped with fluxFunc(fn, "name")
       ├─ runs inside an isolated QuickJS sandbox
       ├─ every side effect (fetch, env, timers, …) crosses a Flux boundary
       ├─ boundary events are encrypted and sent to the FluxBase backend
       └─ replay re-runs the function using the recorded events — no real IO fires

Quick start

import { fluxFunc, fluxENV } from 'fluxrun';

export const createOrder = fluxFunc(async (order: Order) => {
  const apiKey = fluxENV.PAYMENT_API_KEY;
  const response = await fetch('https://payments.example.com/charge', {
    method: 'POST',
    headers: { authorization: `Bearer ${apiKey}` },
    body: JSON.stringify(order),
  });
  return response.json();
}, 'orders.create');

// Call it like a normal function — Flux handles tracing automatically.
const result = await createOrder({ amount: 99_00, currency: 'usd' });

End-to-end flow

1. Developer wraps functions with fluxFunc(...)
2. Each execution is recorded and encrypted at the boundary
3. Encrypted trace is sent to the FluxBase backend (POST /v1/executions)
4. Developer runs an agent endpoint on their own server:

     import { fluxAgent } from "fluxrun";
     // Express / Next.js / Bun.serve / any HTTP framework:
     app.post("/api/flux-agent", async (req, res) => {
       const result = await fluxAgent(req.body, {
         authorization: req.headers.authorization,
       });
       res.json(result);
     });

5. Developer registers their agent URL in the FluxBase dashboard
6. Dashboard can now:
   - Decrypt any captured payload (private key never leaves the developer's server)
   - Trigger replay — re-runs the function deterministically, no real IO fires

What fluxRun records

| Capability | What is captured | | --------------- | ------------------------------------------------ | | fetch | URL (path only), method, headers, body, response | | fluxENV | Key names and values (fully encrypted) | | console | All log methods and arguments | | Math.random() | Generated values | | crypto | randomUUID(), getRandomValues(), digest() | | Date.now() | Timestamps | | timers | setTimeout / setInterval scheduling + fire | | fluxHost RPC | Module name, method, arguments, return value | | fluxFetch | URL, method, headers, status, response | | vm | Function entry args + final result or error |

Encryption

Sensitive captured payloads are encrypted before leaving the process using hybrid RSA-OAEP + AES-256-GCM encryption (Web Crypto). The FluxBase backend stores ciphertext for payload data it does not need to route or index.

Sensitive fields (env values, fetch bodies, auth headers, URL query params) and console arguments are encrypted. Non-sensitive routing fields (method, status code, URL path, timer IDs, etc.) stay plaintext so the dashboard remains readable without requiring decryption for every inspection.

Generate a key pair from the FluxRun setup screen. It emits portable, single-line values:

# Public key for app servers
FLUX_PUBLIC_KEY=fluxpub_v1_...

# Private key for the replay agent only
FLUX_PRIVATE_KEY=fluxpriv_v1_...

PEM keys are still accepted for existing installs, but the compact values are easier to copy into .env, hosting dashboards, and secret managers.

Replay guarantees

During replay fluxRun:

  • Does not call any external systems.
  • Serves recorded boundary responses back to the sandbox in the original order.
  • Produces the same final result for the same trace.
  • Is safe to run in production environments — no side effects.

Authoring primitives

fluxFunc(fn, name, options?)

Wraps a function so it executes in the fluxRun sandbox and records a full trace. The function signature is preserved — callers do not need to change anything.

fluxTrack(fn, name, options?)

Native recording alternative — same lifecycle events and fluxHost RPC recording, but executes directly in the host process without QuickJS sandboxing.

import { fluxTrack } from 'fluxrun';

// Use for server actions, middleware, or code that needs
// redirect(), cookies(), or any Node.js API QuickJS can't access.
export const charge = fluxTrack(async (amount: number) => {
  const res = await fluxFetch('https://api.stripe.com/v1/charges', {
    method: 'POST',
    headers: { Authorization: `Bearer ${fluxENV.STRIPE_KEY}` },
    body: JSON.stringify({ amount, currency: 'usd' }),
  });
  return res.json();
}, 'billing.charge');

Equivalent to fluxFunc(fn, name, { mode: 'native' }).

export const getUser = fluxFunc(async (id: string) => {
  return db.users.findUnique({ where: { id } });
}, 'user.get');

For per-request trace labels, keep the function arguments unchanged and call it through withContext:

const createOrder = fluxFunc(async (order: Order) => order.id, 'orders.create');

await createOrder.withContext({ requestId: 'req_123' })({ id: 1 });

fluxHost(name, impl)

Registers a host-side object so its methods are callable from inside a fluxFunc sandbox. Every call crosses the boundary and is recorded/replayed.

// Register once at startup:
const db = fluxHost('db', {
  query: (sql: string, params: unknown[]) => pool.query(sql, params),
});

// Then use naturally inside fluxFunc:
export const listUsers = fluxFunc(async () => {
  return db.query('SELECT * FROM users', []);
}, 'users.list');

Use fluxHost for live resources: database clients, SDK instances, queues, filesystem access, or anything with mutable host state.

For large store objects with many methods, use fluxHost.auto() to auto-discover every function property — no manual fluxHost call per method needed:

// With 54 methods, this is one line instead of 54:
const db = fluxHost.auto('db', {
  user: { findMany: ..., findUnique: ..., create: ..., update: ..., delete: ... },
  order: { findMany: ..., create: ..., ... },
  // ... all 54 methods auto-registered
});

fluxHost.auto iterates Object.keys(impl), registers every function property into the host registry, and returns a traced Proxy with full async type preservation.

fluxInline(fn)

Marks a pure helper function so it is copied into the QuickJS runtime instead of called through RPC. This keeps helper logic inside the recorded execution, while any capabilities used by the helper (fetch, Math.random, fluxENV, timers, etc.) are still captured normally.

import { fluxFunc, fluxInline } from 'fluxrun';

const normalizeEmail = fluxInline(function normalizeEmail(email: string) {
  return email.trim().toLowerCase();
});

export const createUser = fluxFunc(async (email: string) => normalizeEmail(email), 'users.create', {
  bindings: { normalizeEmail },
});

Only use fluxInline for deterministic helpers that do not close over live objects. Plain function bindings still use RPC by default for safety.

fluxENV

Safe environment access that works both in host code and inside the QuickJS sandbox. Reads are recorded and replayed like any other boundary call.

const apiKey = fluxENV.STRIPE_SECRET_KEY; // works inside or outside fluxFunc

fluxFetch(url, init?)

Host-side fetch wrapper that auto-injects x-request-id and W3C traceparent headers from the execution context. Every call is recorded as a fetch boundary event, visible in the FluxRun dashboard alongside sandboxed fetch calls.

import { fluxFetch } from 'fluxrun';

export const charge = fluxFunc(async (amount: number) => {
  const res = await fluxFetch('https://api.stripe.com/v1/charges', {
    method: 'POST',
    headers: { Authorization: `Bearer ${fluxENV.STRIPE_KEY}` },
    body: new URLSearchParams({ amount: String(amount), currency: 'usd' }),
  });
  return res.json();
}, 'billing.charge');

When called outside an execution context, fluxFetch falls back to globalThis.fetch with no tracing or recording.

Postgres / SQL tracing

FluxRun ships with a drop-in pg adapter that routes queries through the Flux boundary layer so every SQL call is recorded and replayable.

import { Pool } from 'fluxrun/adapters/pg';

const pool = new Pool({ connectionString: process.env.DATABASE_URL });

export const getUser = fluxTrack(async (id: string) => {
  const { rows } = await pool.query('SELECT * FROM users WHERE id = $1', [id]);
  return rows[0];
}, 'db.users.get');

For local development or when you already have a pg.Pool instance, use fluxHost.auto() instead — it wraps every method on the object with boundary recording without changing imports:

import { Pool } from 'pg';
import { fluxHost, fluxTrack } from 'fluxrun';

const pool = new Pool({ connectionString: process.env.DATABASE_URL });
fluxHost.auto('db', pool);

export const getUser = fluxTrack(async (id: string) => {
  const { rows } = await (globalThis as any).db.query('SELECT * FROM users WHERE id = $1', [id]);
  return rows[0];
}, 'db.users.get');

fluxAgent(payload, options?)

The agent endpoint handler. Pass parsed request body → get back a JSON-safe result. Handles decryption and replay.

import { fluxAgent } from 'fluxrun';

export async function POST(req: Request) {
  const result = await fluxAgent(await req.json(), {
    authorization: req.headers.get('authorization'),
  });
  return Response.json(result);
}

Framework adapters

Each adapter exports withFlux*(name, handler, options?). The handler always receives the same flux shape (request, context, env, plus fluxHost modules). Install the framework you use as a normal app dependency; fluxrun lists matching versions as optional peerDependencies for discoverability.

Next.js in 5 minutes

Use the adapter in App Router route handlers, and wrap your next.config.ts with the Flux plugin. The plugin installs both webpack and Turbopack loader rules so host-module calls can be rewritten before Next compiles the route.

next.config.ts

import type { NextConfig } from 'next';
import { withFluxNextJsPlugin } from 'fluxrun/build';

const nextConfig: NextConfig = {};

const withFlux = withFluxNextJsPlugin();
export default withFlux(nextConfig);

lib/flux-bindings.ts

import { fluxHost } from 'fluxrun';
import { prisma } from '@/lib/prisma';

type OrderInput = { amount: number; currency: string };

export const db = fluxHost('db', {
  createOrder: async (input: OrderInput) => prisma.order.create({ data: input }),
});

app/api/orders/route.ts

import { withFluxNextJs } from 'fluxrun/adapters/next';
import { db } from '@/lib/flux-bindings';

export const runtime = 'nodejs';

export const POST = withFluxNextJs(
  'orders.create',
  async (flux) => {
    const body = flux.request.body as { amount: number; currency: string };
    const order = await db.createOrder(body);

    return {
      status: 201,
      body: { id: order.id },
    };
  },
  { host: { db } },
);

app/api/flux-agent/route.ts

import { fluxAgent } from 'fluxrun';

export const runtime = 'nodejs';

const corsHeaders = {
  'Access-Control-Allow-Origin': 'https://app.fluxrun.dev',
  'Access-Control-Allow-Headers': 'Content-Type, Authorization',
  'Access-Control-Allow-Methods': 'POST, OPTIONS',
};

export async function OPTIONS() {
  return new Response(null, { headers: corsHeaders });
}

export async function POST(req: Request) {
  const result = await fluxAgent(await req.json(), {
    authorization: req.headers.get('authorization'),
  });
  return Response.json(result, { headers: corsHeaders });
}

Set FLUX_PROJECT_TOKEN, FLUX_PUBLIC_KEY, and FLUX_PRIVATE_KEY on the server runtime that exposes the traced routes and the agent route. FluxBase calls the agent with a short-lived dashboard token; fluxAgent verifies that token with FluxBase by using FLUX_PROJECT_TOKEN.

The adapter automatically populates flux.requestId and flux.traceId from incoming headers (x-request-id, traceparent) for cross-service tracing.

For webhook handlers that need the raw body (HMAC verification), pass rawBody: true:

export const POST = withFluxNextJs(
  'webhooks.github',
  async (flux) => {
    const sig = flux.request.headers['x-hub-signature-256'];
    verifyHmac(sig, flux.request.rawBody!);
    // ...
  },
  { rawBody: true },
);

Adapter First Run

Every adapter follows the same production checklist:

  1. Set FLUX_PROJECT_TOKEN, FLUX_PUBLIC_KEY, and FLUX_PRIVATE_KEY where your app runs.
  2. Put live SDK clients, database handles, queues, and non-serializable objects behind fluxHost.
  3. Make sure your framework body parser runs before Flux when the framework does not parse bodies automatically.

The SDK ships with production ingest failover URLs. Use FLUX_INGEST_URL or FLUX_INGEST_URLS only for private/self-hosted ingest overrides.

Local ingest proxy (Next.js)

For local development you can route ingest through your own app so CORS or network policies don't block the SDK. Set:

FLUX_INGEST_URL=/api/flux-ingest

FluxRun appends /v1/executions to that base URL, so the SDK posts capture batches to /api/flux-ingest/v1/executions.

Important: that local route must forward batches to the real ingest endpoint or you will see 0 executions in the dashboard. Use the built-in proxy helper:

app/api/flux-ingest/v1/executions/route.ts

import { createFluxIngestProxy } from 'fluxrun/adapters/next';

export const runtime = 'nodejs';
export const POST = createFluxIngestProxy();

By default it proxies to https://ingest-0.fluxrun.dev/v1/executions and forwards your FLUX_PROJECT_TOKEN automatically. You can override the destination if you self-host ingest:

export const POST = createFluxIngestProxy('https://my-ingest.example.com/v1/executions');

Local dry-run mode

Set FLUX_DRY_RUN=1 to skip the public key requirement and log capture batches as JSON to stdout instead of POSTing to the ingest service:

FLUX_DRY_RUN=1 node server.js

This works without any credentials — useful for integration testing, CI smoke tests, and verifying recording setup before deploying.

Debug mode

Set FLUX_DEBUG=1 to print every capture event, batch flush, and delivery attempt to the console. This is the fastest way to diagnose why executions aren't appearing:

FLUX_DEBUG=1 node server.js

Doctor

Run fluxrun doctor from your app to verify the SDK pipeline before opening the dashboard. It checks the installed SDK version, required environment variables, ingest health, token delivery, and the optional replay agent route:

FLUX_AGENT_URL=https://your-app.com/api/flux-agent fluxrun doctor

If you set FLUX_INGEST_URL=/api/flux-ingest, doctor warns when it sees a relative local route because dashboard delivery depends on that route forwarding with createFluxIngestProxy.

Console capture

Set FLUX_CAPTURE_HOST_CONSOLE=1 to record console.log, console.error, console.warn, and console.info calls that happen in adapter boilerplate (outside the QuickJS sandbox) as boundary events visible in the dashboard:

Express

Use express.json() before the Flux middleware. Register the agent endpoint in the same app or in a separate private service.

import express from 'express';
import { fluxAgent, fluxHost } from 'fluxrun';
import { withFluxExpress } from 'fluxrun/adapters/express';

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

const db = fluxHost('db', {
  createOrder: (input: { amount: number }) => prisma.order.create({ data: input }),
});

app.post(
  '/api/orders',
  withFluxExpress(
    'orders.create',
    async (flux) => {
      const order = await flux.db.createOrder(flux.request.body as { amount: number });
      return { status: 201, body: { id: order.id } };
    },
    { host: { db } },
  ),
);

app.post('/api/flux-agent', async (req, res) => {
  res.json(await fluxAgent(req.body, { authorization: req.headers.authorization }));
});

Hono

Hono gives the adapter a web Request; use host modules for bindings that cannot be serialized.

import { Hono } from 'hono';
import { fluxAgent, fluxHost } from 'fluxrun';
import { withFluxHono } from 'fluxrun/adapters/hono';

type Bindings = {
  API_TOKEN: string;
};

const app = new Hono<{ Bindings: Bindings }>();

const billing = fluxHost('billing', {
  lookup: (id: string) => fetch(`https://billing.example/${id}`).then((r) => r.json()),
});

app.get(
  '/api/accounts',
  withFluxHono(
    'accounts.lookup',
    async (flux) => ({
      status: 200,
      body: await flux.billing.lookup(flux.request.searchParams['id'] ?? ''),
    }),
    { host: { billing } },
  ),
);

app.post('/api/flux-agent', async (c) =>
  c.json(
    await fluxAgent(await c.req.json(), {
      authorization: c.req.header('authorization') ?? null,
    }),
  ),
);

Fastify

Fastify parses JSON bodies for you. flux.request.pathname is the request path without the query string, and flux.request.searchParams contains the query.

import Fastify from 'fastify';
import { fluxAgent, fluxHost } from 'fluxrun';
import { withFluxFastify } from 'fluxrun/adapters/fastify';

const app = Fastify();

const audit = fluxHost('audit', {
  write: (event: unknown) => auditClient.write(event),
});

app.post(
  '/api/orders',
  withFluxFastify(
    'orders.fastify.create',
    async (flux) => {
      await flux.audit.write({ path: flux.request.pathname, body: flux.request.body });
      return { status: 201, body: { ok: true } };
    },
    { host: { audit } },
  ),
);

app.post('/api/flux-agent', async (request, reply) => {
  const authorization = Array.isArray(request.headers.authorization)
    ? request.headers.authorization[0]
    : request.headers.authorization;
  return reply.send(await fluxAgent(request.body, { authorization }));
});

Koa

Install a body parser such as koa-bodyparser before Flux when you need flux.request.body.

import Koa from 'koa';
import bodyParser from 'koa-bodyparser';
import Router from '@koa/router';
import { fluxAgent, fluxHost } from 'fluxrun';
import { withFluxKoa } from 'fluxrun/adapters/koa';

const app = new Koa();
const router = new Router();
app.use(bodyParser());

const mail = fluxHost('mail', {
  send: (to: string) => resend.emails.send({ to, subject: 'Hello' }),
});

router.post(
  '/api/invite',
  withFluxKoa(
    'invite.send',
    async (flux) => {
      const body = flux.request.body as { email: string };
      await flux.mail.send(body.email);
      return { status: 202, body: { queued: true } };
    },
    { host: { mail } },
  ),
);

router.post('/api/flux-agent', async (ctx) => {
  ctx.body = await fluxAgent(ctx.request.body, {
    authorization: ctx.get('authorization') || null,
  });
});

app.use(router.routes());

Web Request runtimes

For Node.js or Bun runtimes that expose standard Web Request / Response objects, export fetch(request, env, ctx). String environment bindings are copied into flux.env; live objects should be exposed through fluxHost.

Cloudflare Workers are not part of the current publish contract because the Flux runtime depends on the Node.js QuickJS bootstrap. Do not advertise Workers support until the runtime has a verified edge-safe QuickJS build.

import { fluxAgent, fluxHost } from 'fluxrun';
import { withFluxWeb } from 'fluxrun/adapters/web';

type Env = {
  API_TOKEN: string;
  ORDERS: { send(message: unknown): Promise<void> };
};

export default {
  async fetch(request: Request, env: Env, ctx: ExecutionContext) {
    if (new URL(request.url).pathname === '/api/flux-agent') {
      return Response.json(
        await fluxAgent(await request.json(), {
          authorization: request.headers.get('authorization'),
        }),
      );
    }

    const queue = fluxHost('queue', {
      send: (message: unknown) => env.ORDERS.send(message),
    });

    const orders = withFluxWeb(
      'worker.orders',
      async (flux) => {
        await flux.queue.send({ token: flux.env.API_TOKEN, body: flux.request.body });
        return { status: 202, body: { queued: true } };
      },
      { host: { queue } },
    );

    return orders(request, env, ctx);
  },
};

AWS Lambda

API Gateway HTTP API v2 and Function URLs provide rawQueryString; Flux parses it into flux.request.searchParams when queryStringParameters is absent.

import { fluxAgent, fluxHost } from 'fluxrun';
import { withFluxLambda } from 'fluxrun/adapters/aws';

const crm = fluxHost('crm', {
  findAccount: (region: string) => crmClient.accounts.find({ region }),
});

export const handler = withFluxLambda(
  'lambda.accounts',
  async (flux) => {
    const region = flux.request.searchParams['region'] ?? 'us';
    return { status: 200, body: await flux.crm.findAccount(region) };
  },
  { host: { crm } },
);

export const agent = withFluxLambda('lambda.agent', async (flux) => ({
  status: 200,
  body: await fluxAgent(flux.request.body),
}));

TypeScript utilities

fluxrun exports helper types for common patterns:

import type { ExtractParams, FluxResult, RequireDefined } from 'fluxrun';

// Next.js App Router route params
type RouteCtx = { params: Promise<{ id: string }> };
type P = ExtractParams<RouteCtx>; // { id: string }

// Discriminated result wrapper — no `| undefined` issues
async function getOrder(id: string): FluxResult<{ total: number }> {
  const o = await db.find(id);
  if (!o) return { ok: false, error: 'Not found' };
  return { ok: true, data: o };
}

// Strip `| null | undefined` from GraphQL types
type User = { name?: string | null; profile?: { bio?: string | null } | null };
type Clean = RequireDefined<User>; // { name: string; profile: { bio: string } }

Closures and bindings

QuickJS evaluates serialized function source so closures do not work out of the box.

  • Pass data as arguments — cleanest approach for plain values.
  • Use bindings — inject serializable values at call time:
const config = { apiUrl: 'https://api.example.com' };

const fetchUser = fluxFunc(
  async (id: string) => fetch(`${config.apiUrl}/users/${id}`),
  'user.fetch',
  { bindings: { config } },
);
  • Use fluxInline — explicitly copy pure helper functions into the runtime:
const score = fluxInline((value: number) => value * 2);

const rank = fluxFunc(async (value: number) => score(value), 'score.rank', {
  bindings: { score },
});
  • Use fluxHost — for live objects, class instances, SDK clients, and anything that cannot be serialized.

Package exports

| Export | Description | | -------------------------- | ----------------------------------------------------- | | fluxrun | Main package (fluxFunc, fluxHost, fluxFetch, …) | | fluxrun/agent | Agent handler (fluxAgent) | | fluxrun/build | esbuild plugins for build-time transforms | | fluxrun/adapters/pg | PostgreSQL HTTP adapter for edge targets | | fluxrun/adapters/redis | Redis HTTP adapter for edge targets | | fluxrun/adapters/mongo | MongoDB HTTP adapter for edge targets | | fluxrun/adapters/next | Next.js App Router RequestResponse | | fluxrun/adapters/express | Express middleware | | fluxrun/adapters/hono | Hono handler → Response | | fluxrun/adapters/fastify | Fastify route handler | | fluxrun/adapters/koa | Koa middleware | | fluxrun/adapters/web | Web RequestResponse | | fluxrun/adapters/aws | Lambda handler (API Gateway v2 style) |

Development

bun install
bun test          # run the test suite
bun run build     # compile to dist/