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

@fn-code-blocks/core

v0.1.5

Published

The SDK, CLI, build tools, and runtime servers for **Fn-Code-Blocks** — a functional programming framework for building API services with typed, language-agnostic function composition.

Readme

@fn-code-blocks/core

The SDK, CLI, build tools, and runtime servers for Fn-Code-Blocks — a functional programming framework for building API services with typed, language-agnostic function composition.

Write functions in TypeScript and Python. Compose them into API routes. The language a function is written in is an implementation detail.

// A route composing functions — languages are invisible
const user      = await fn.fetch_user({ id: 123 });
const inventory = await fn.check_inventory({ sku: "A1" });

const [pricing, risk] = await Promise.all([
  fn.fetch_pricing({ sku: "A1" }),
  fn.assess_risk({ user_id: 123, amount: 500 }),
]);

Quick start

npx create-fn-code-blocks my-project
code my-project
# Click "Reopen in Container" when VS Code prompts
npm run dev
# Visit http://localhost:3000/hello

Prerequisites

Your local machine needs only:

  • Docker Desktop — runs the development environment
  • VS Code — editor
  • VS Code extension: Dev Containers (ID: ms-vscode-remote.remote-containers)
  • Node.js — only for the initial npx create-fn-code-blocks scaffold command

After scaffolding, Node.js is no longer needed locally. Everything runs inside the Dev Container with exact version parity to production (Node 24, Python 3.12).

Three concepts

| Concept | What it is | Where it lives | |---|---|---| | Function | (input, ctx) → output. Typed, testable, language-agnostic. | src/functions/ | | Route | Composes functions into a workflow endpoint. | src/routes/ | | Config | Global env, logging transports, project settings. | fn-code-blocks.config.ts |

Architecture

Three services run in Docker. The framework manages all of them.

Client → Orchestrator :3000 → TS Runtime :3001
                             → Python Runtime :3002
  • Client → Orchestrator: Standard HTTP REST
  • Orchestrator → Runtimes: POST /invoke/:fnName (internal HTTP)
  • Runtimes → Log Collector: Persistent WebSocket log streams

Writing functions

Each function lives in its own folder under src/functions/. The language is determined by the entry point file.

TypeScript

// src/functions/fetch_user/index.ts
import type { Context } from "@fn-code-blocks/core/context";

export interface Input {
  id: number;
}

export interface Output {
  name: string;
  email: string;
}

export default async function fetch_user(input: Input, ctx: Context): Promise<Output> {
  ctx.trace.info("Fetching user", { id: input.id });
  // ... your logic
  return { name: "Alice", email: "[email protected]" };
}

Python

# src/functions/analyze_data/index.py
from typing import TypedDict
from fn_code_blocks import Context

class Input(TypedDict):
    dataset: str

class Output(TypedDict):
    result: str
    score: float

def main(input: Input, ctx: Context) -> Output:
    ctx.trace.info("Analyzing dataset", {"dataset": input["dataset"]})
    # ... your logic
    return {"result": "positive", "score": 0.95}

Also create an empty __init__.py in the function folder.

Rules

  • One entry point per folder: index.ts OR index.py
  • Must export Input and Output types
  • Folder names use snake_case
  • Must include trace logging and tests

Writing routes

Routes compose functions into workflow endpoints. A route handler receives exactly five things: input, params, fn, ctx, respond.

// src/routes/orders.ts
import { defineRoute } from "@fn-code-blocks/core";
import { Hono } from "hono";

const orders = new Hono();

orders.post("/", defineRoute(async ({ input, fn, ctx, respond }) => {
  ctx.trace.info("Processing order", { item: input.sku });

  const inventory = await fn.check_inventory({ sku: input.sku });

  if (!inventory.available) {
    return respond.error(400, "Out of stock");
  }

  const order = await fn.create_order({
    sku: input.sku,
    quantity: input.quantity,
  });

  return respond.json(201, order);
}));

export default orders;

Routes auto-mount by filename: orders.ts/orders.

The rule: If it can't be expressed with fn.*, if/else, await, and respond.*, it belongs in a function.

Configuration

All project-specific decisions live in fn-code-blocks.config.ts:

import { defineConfig, ConsoleTransport } from "@fn-code-blocks/core";

export default defineConfig({
  // Orchestrator port (default: 3000)
  port: 3000,

  // Header to extract trace IDs from incoming requests
  traceIdHeader: "x-trace-id",

  // Global environment — available to every route and function via ctx.env
  env: {
    SERVICE_NAME: "my-project",
    REGION: process.env.REGION ?? "eu-west-1",
    DATABASE_URL: process.env.DATABASE_URL ?? "postgres://localhost:5432/dev",
    API_KEY: process.env.API_KEY ?? "",
  },

  // Keys to redact in trace logs (values replaced with [REDACTED])
  redactKeys: ["API_KEY", "DATABASE_URL"],

  // Logging — wire transports explicitly
  logging(registry) {
    registry.addTransport("log", new ConsoleTransport());
    registry.addTransport("trace", new ConsoleTransport());
  },

  // Runtime-specific configuration (optional)
  runtimes: {
    python: {
      systemDeps: [],    // Extra apt packages for Docker image
      extraDeps: [],     // Extra pip packages
    },
    typescript: {
      extraDeps: [],     // Extra npm packages
    },
  },
});

Configuration options

| Option | Type | Default | Description | |---|---|---|---| | port | number | 3000 | Orchestrator HTTP port | | traceIdHeader | string | "x-trace-id" | Request header to extract trace IDs from | | env | Record<string, string> | {} | Global environment passed to all routes and functions via ctx.env | | redactKeys | string[] | [] | Env keys whose values are replaced with [REDACTED] in trace logs | | logging | (registry) => void | — | Wire logging transports for "log" and "trace" channels | | runtimes | object | — | Per-runtime system deps and extra packages |

Environment passing

Functions receive ctx.env containing the global env from config. You can override per-call:

// Default — function gets full global env
await fn.fetch_user({ id: 123 });

// Filtered — function only sees what you pass
await fn.call_third_party(
  { query: "..." },
  { env: { SERVICE_NAME: ctx.env.SERVICE_NAME } }
);

// Injected — function sees global env plus extra values
await fn.process_order(
  { order_id: 456 },
  { env: { ...ctx.env, TENANT_ID: "acme-corp" } }
);

Logging

Two channels, each wired independently:

| Channel | Purpose | Use for | |---|---|---| | trace | Diagnostic visibility | Every decision point, external call, data transformation | | log | Operational output | Business events, metrics, alerts |

ctx.trace.info("Calling payment API", { amount, currency });
ctx.log.error("Payment declined", { reason });

Custom transports

Implement the LogTransport interface:

import type { LogTransport, LogEntry } from "@fn-code-blocks/core";

const myTransport: LogTransport = {
  write(entry: LogEntry) {
    // Send to your logging service
  },
};

export default defineConfig({
  logging(registry) {
    registry.addTransport("log", myTransport);
    registry.addTransport("trace", myTransport);
  },
});

Pipe to pino

import pino from "pino";
import { defineConfig, type LogTransport, type LogEntry } from "@fn-code-blocks/core";

const logger = pino({ level: "debug" });

const pinoTransport: LogTransport = {
  write(entry: LogEntry) {
    logger.child({ channel: entry.channel, fn: entry.fn, traceId: entry.traceId })
      [entry.level](entry.args[0] ?? {}, entry.message);
  },
};

export default defineConfig({
  logging(registry) {
    registry.addTransport("log", pinoTransport);
    registry.addTransport("trace", pinoTransport);
  },
});

Silence trace in production

logging(registry) {
  registry.addTransport("log", new ConsoleTransport());
  if (process.env.NODE_ENV !== "production") {
    registry.addTransport("trace", new ConsoleTransport());
  }
},

Testing

Functions are tested with zero infrastructure. Each language has a createTestContext helper that returns a Context backed by in-memory transports.

TypeScript

import { describe, it, expect } from "vitest";
import { createTestContext } from "@fn-code-blocks/core/testing";
import my_function from "./index";

describe("my_function", () => {
  it("works", async () => {
    const { ctx, traceSink, logSink } = createTestContext("my_function");
    const result = await my_function({ key: "value" }, ctx);

    expect(result.field).toBe("expected");
    expect(traceSink.entries).toHaveLength(2);
    expect(logSink.filter((e) => e.level === "error")).toHaveLength(0);
  });
});

Python

from fn_code_blocks import create_test_context
from .index import main

def test_my_function():
    ctx, trace_sink, log_sink = create_test_context("my_function")
    result = main({"key": "value"}, ctx)

    assert result["field"] == "expected"
    assert len(trace_sink.entries) == 2

CLI commands

All commands run inside the Dev Container terminal:

| Command | What it does | |---|---| | npm run dev | Start all services with hot reload | | npm test | Run all tests (TS, Python) | | npm test -- --fn my_function | Test a single function (auto-detects language) | | npm run test:ts | TypeScript tests only | | npm run test:py | Python tests only | | npm run build | Rebuild manifests | | npm run docker:build | Build production Docker images | | npm run docker:up | Start production containers |

Error handling

The fn proxy throws typed errors that routes can catch:

import { FnTimeoutError } from "@fn-code-blocks/core/errors";

defineRoute(async ({ fn, respond }) => {
  try {
    const user = await fn.fetch_user({ id: input.user_id });
    return respond.json(200, user);
  } catch (err) {
    if (err instanceof FnTimeoutError) {
      return respond.error(504, "Function timed out");
    }
    throw err; // re-throw — framework returns 500
  }
});

| Error class | When | |---|---| | FnExecutionError | Function threw an exception | | FnTimeoutError | Invocation exceeded deadline | | FnServiceError | Runtime unreachable | | FnNotFoundError | Function not in manifest |

Production Docker

npm run docker         # Generate Dockerfiles + docker-compose.yml
npm run docker:build   # Build production images
npm run docker:up      # Start production containers

Each production image contains only the runtime it needs. Language versions match the Dev Container exactly.

Project structure

my-project/
├── fn-code-blocks.config.ts        # Configuration
├── package.json                     # Single dependency: @fn-code-blocks/core
├── .devcontainer/                   # Dev Container (auto-generated)
├── .vscode/launch.json              # Debug configurations
├── src/
│   ├── functions/                   # Your functions
│   │   ├── fetch_user/index.ts
│   │   ├── analyze_data/index.py
│   │   └── shared/                  # Cross-function utilities
│   └── routes/                      # Your routes
│       └── orders.ts
└── .fn-code-blocks/                 # Generated (gitignored)

Exports

// Main entry
import { defineConfig, defineRoute, ConsoleTransport } from "@fn-code-blocks/core";

// Context type (for function signatures)
import type { Context } from "@fn-code-blocks/core/context";

// Testing
import { createTestContext } from "@fn-code-blocks/core/testing";

// Logger
import { LoggerRegistry, MemoryTransport } from "@fn-code-blocks/core/logger";
import type { LogEntry, LogLevel, LogChannel, LogTransport } from "@fn-code-blocks/core/logger";

// Errors
import { FnExecutionError, FnTimeoutError, FnServiceError, FnNotFoundError } from "@fn-code-blocks/core/errors";

License

MIT