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

@go-denki/fraft

v0.2.2

Published

Dynamically fetch or send API data using JSON/YAML config with a built-in transform pipeline

Readme

fraft

Declarative API fetching for Node.js. Define HTTP requests and data transformations in a JSON or YAML config file, then execute them with a single method call.

Playground · Docs

# api.yaml
version: 1
baseUrl: https://jsonplaceholder.typicode.com

requests:
  todos:
    path: /todos
    transform:
      - filter: { field: completed, op: eq, value: true }
      - pick: [id, title]
import { FraftClient } from '@go-denki/fraft';

const client = new FraftClient({ config: 'api.yaml' });
const todos = await client.run('todos');

Table of Contents


Installation

npm install @go-denki/fraft

Quick Start

1. Create a config file (api.yaml or api.json):

version: 1
baseUrl: https://api.example.com
headers:
  Accept: application/json
auth:
  type: apiKey
  in: header
  name: x-api-key
  value: "${env.API_KEY}"
requests:
  users:
    path: /users
    transform:
      - filter: { field: active, op: eq, value: true }
      - pick: [id, name, email]

2. Run it:

import { FraftClient } from '@go-denki/fraft';

const client = new FraftClient({ config: 'api.yaml' });
const users = await client.run('users');
console.log(users);

Config Format

Config files can be YAML (.yaml / .yml) or JSON (.json). You can also pass a plain JavaScript object directly to FraftClient.

Top-level fields

| Field | Type | Required | Description | |-------|------|----------|-------------| | version | number | No | Schema version. Currently must be 1 if provided. | | baseUrl | string | Yes | Base URL prepended to all request paths. | | headers | Record<string, string> | No | Global headers merged into every request. | | auth | AuthConfig | No | Global auth applied to every request. | | requests | Record<string, RequestDef> | Yes | Named request definitions. |

Request definition

Each key under requests maps to a RequestDef:

| Field | Type | Default | Description | |-------|------|---------|-------------| | path | string | — | URL path appended to baseUrl. | | method | HttpMethod | "GET" | HTTP method: GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS. | | headers | Record<string, string> | — | Per-request headers, merged over global headers. | | params | Record<string, unknown> | — | Query string parameters. | | body | unknown | — | Request body (serialized to JSON). | | transform | TransformStep[] | — | Ordered list of transform steps applied to the response. | | axiosConfig | AxiosRequestConfig | — | Advanced axios options (timeout, responseType, etc.). |

Example — POST with a body:

requests:
  createUser:
    path: /users
    method: POST
    headers:
      Content-Type: application/json
    body:
      name: Alice
      role: admin

Example — GET with query params:

requests:
  search:
    path: /search
    params:
      q: typescript
      limit: 10

Auth

Currently supports API key auth (type: apiKey).

| Field | Type | Description | |-------|------|-------------| | type | "apiKey" | Auth strategy. | | in | "header" | "query" | Where to inject the key. | | name | string | Header name or query param key. | | value | string | Key value. Supports ${env.VAR_NAME} interpolation. |

auth:
  type: apiKey
  in: header
  name: Authorization
  value: "Bearer ${env.ACCESS_TOKEN}"

Transform pipeline

transform is an ordered array of steps. Each step is applied to the output of the previous one. Steps operate on the full response body (object or array).


pick — keep only selected fields

transform:
  - pick: [id, name, email]

Applied to an array, picks fields from every element. Applied to an object, picks fields from that object.


rename — rename fields

transform:
  - rename:
      userId: id
      userName: name

Maps old_name → new_name. Works on arrays and single objects.


filter — filter array items

transform:
  - filter:
      field: status
      op: eq
      value: active

Only works on arrays. Supported operators:

| op | Description | |------|-------------| | eq | Strict equal (===) | | neq | Not equal (!==) | | gt | Greater than | | gte | Greater than or equal | | lt | Less than | | lte | Less than or equal | | contains | String contains substring | | startsWith | String starts with value | | endsWith | String ends with value |


coerce — type coercion and arithmetic

transform:
  - coerce:
      price:
        type: number
        expr: "*100"
        format: "%.2f"
      active: boolean
      code: string

Each key maps to a field name. The value can be:

  • A shorthand string: "string", "number", or "boolean"
  • A rule object:

| Field | Type | Description | |-------|------|-------------| | type | "string" | "number" | "boolean" | Cast the value to this type. | | expr | string | Arithmetic applied after type cast: "+5", "-1", "*100", "/1000". | | format | string | sprintf-style format applied last. Only "%.Nf" (fixed decimal) is supported. |


middleware — custom transform function

transform:
  - middleware: myTransform

Calls a named function registered via client.use(). See Custom Middleware.


API Reference

FraftClient

import { FraftClient } from '@go-denki/fraft';

new FraftClient(options)

| Option | Type | Description | |--------|------|-------------| | config | string \| Record<string, unknown> | Path to a JSON/YAML file, or a pre-parsed config object. | | axiosInstance | AxiosInstance | Optional custom axios instance (useful for testing or interceptors). |

client.run(requestName, overrides?)

Executes a single named request and returns the transformed response.

const data = await client.run('users');

// With runtime overrides:
const data = await client.run('users', {
  params: { limit: 5 },
  headers: { 'X-Trace-Id': '123' },
});

// With path params:
const user = await client.run('getUser', { pathParams: { id: 42 } });
// path: '/users/:id' → GET /users/42

Overrides (RunOverrides):

| Field | Type | Description | |-------|------|-------------| | pathParams | Record<string, string \| number> | Values interpolated into :param segments of the request path. | | params | Record<string, unknown> | Merged over config-defined params. | | body | unknown | Replaces the config-defined body. | | headers | Record<string, string> | Merged over config-defined headers. |

Returns Promise<unknown> — the raw or transformed response data.

client.runAll(overrides?)

Executes all requests defined in the config sequentially.

const results = await client.runAll();
// { users: [...], posts: [...] }

// With per-request overrides:
const results = await client.runAll({
  users: { params: { limit: 10 } },
});

Returns Promise<Record<string, unknown>> — a map of requestName → result.

client.use(name, fn)

Registers a named middleware function for use in { middleware: "<name>" } transform steps.

client.use('addTimestamp', (data) => {
  return { ...(data as object), fetchedAt: new Date().toISOString() };
});

The middleware function receives (data: unknown, context: RequestContext) and may return a value or a Promise. Returns this for chaining.

client.getConfig()

Returns the loaded and validated FraftConfig object (loads from file on first call, then cached).


Types

All types are exported from the package root:

import type {
  FraftConfig,
  RequestDef,
  TransformStep,
  PickStep,
  RenameStep,
  CoerceStep,
  CoerceRule,
  FilterStep,
  MiddlewareStep,
  AuthConfig,
  ApiKeyAuth,
  HttpMethod,
  RunOverrides,
  RequestContext,
  MiddlewareFn,
  FraftClientOptions,
} from '@go-denki/fraft';

Environment Variable Interpolation

Any string value in the config can reference an environment variable using the syntax ${env.VAR_NAME}. The variable is resolved at load time from process.env.

auth:
  type: apiKey
  in: header
  name: x-api-key
  value: "${env.MY_API_KEY}"

baseUrl: "${env.API_BASE_URL}"

If the referenced variable is not set, fraft throws an error at startup.


Custom Middleware

Middleware lets you run arbitrary JavaScript/TypeScript logic as a transform step.

# api.yaml
requests:
  posts:
    path: /posts
    transform:
      - pick: [id, title, body]
      - middleware: enrichPosts
import { FraftClient } from '@go-denki/fraft';

const client = new FraftClient({ config: 'api.yaml' });

client.use('enrichPosts', async (data, context) => {
  const posts = data as Array<{ id: number; title: string; body: string }>;
  return posts.map(post => ({
    ...post,
    url: `https://example.com/posts/${post.id}`,
    wordCount: post.body.split(' ').length,
  }));
});

const posts = await client.run('posts');

The context argument provides:

| Field | Type | Description | |-------|------|-------------| | requestName | string | The key of the current request. | | config | FraftConfig | The full parsed config. | | def | RequestDef | The current request definition. |


Examples

See the examples/ directory for ready-to-run examples:

| Example | Description | |---------|-------------| | examples/github-repos.yaml | Fetch public GitHub repos with filtering and field picking | | examples/weather.yaml | Weather API with API key auth via env var | | examples/create-post.yaml | POST request with a JSON body | | examples/basic.mjs | Simple programmatic usage (ESM) | | examples/transforms.mjs | Demonstrates all built-in transform steps | | examples/middleware.mjs | Custom middleware transform | | examples/inline-config.mjs | Passing a config object instead of a file path |


Roadmap / Todo

Auth

  • [ ] Bearer / JWT auth type
  • [ ] HTTP Basic auth support
  • [ ] Auth token refresh and retry on 401
  • [ ] Dynamic auth value resolution at request time

Transforms

  • [ ] map step — apply a transform to every element of an array
  • [ ] sort step — sort arrays by field
  • [ ] flatten / unflatten steps
  • [ ] Conditional transforms — skip a step based on data shape
  • [ ] Dot-notation support for nested field access (user.profile.name)
  • [ ] Additional filter operators — regex, in (array membership), nested paths
  • [ ] Additional coerce formats — dates, locales, string templates
  • [ ] Error recovery in pipelines — fallbacks on step failure

Config & Environment

  • [ ] Default values for env vars — ${env.VAR:default}
  • [ ] Config composition / inheritance — extend a base config
  • [ ] Environment-specific config files (api.dev.yaml, api.prod.yaml)
  • [ ] Built-in .env file loading via dotenv

Reliability

  • [ ] Retry logic with configurable backoff (useful for 429 / 5xx)
  • [ ] Per-request timeout override at the top level
  • [ ] Partial failure mode for runAll() — continue on individual request errors
  • [ ] Structured error types (distinguish auth errors, network errors, transform errors)

HTTP & Requests

  • [ ] Multipart / form-data request body support
  • [ ] Request and response interceptor hooks
  • [ ] Streaming response support
  • [x] Expand RunOverrides to allow path params (:param syntax) — method and transform overrides still pending

Middleware

  • [ ] Global middleware — registered middleware runs on every request
  • [ ] Middleware execution ordering / priority
  • [ ] Ability to update or unregister middleware entries
  • [ ] Built-in utility middleware (CSV parsing, pagination handling, etc.)

Performance

  • [ ] Parallel execution mode for runAll()
  • [ ] Client-side response caching with TTL
  • [ ] Request deduplication

Testing & DX

  • [ ] Test utilities exported for consumers to test their own configs
  • [ ] Config validation CLI (fraft validate api.yaml)
  • [ ] Config loader unit tests

License

MIT