NextRush

@nextrush/runtime

Runtime detection and cross-runtime abstractions

Detect JavaScript runtimes and use cross-runtime abstractions for consistent behavior across Node.js, Bun, Deno, Deno Deploy, and Edge environments.

$ pnpm add @nextrush/runtime

Usually Included

Runtime detection is included in adapters. Install directly only when building custom adapters or tooling.

The Real Problem

JavaScript runs on Node.js, Bun, Deno, Cloudflare Workers, Vercel Edge, and Deno Deploy. Each runtime exposes different globals, stream APIs, and body reading mechanisms. Framework code that targets all runtimes needs two things:

  1. Runtime detection — know which environment you're in
  2. Unified abstractions — read request bodies, parse query strings, and extract headers without runtime-specific branches

@nextrush/runtime provides both.

What It Provides

// Runtime detection
import {
  detectRuntime,
  getRuntime,
  getRuntimeVersion,
  getRuntimeInfo,
  getRuntimeCapabilities,
  detectEdgeRuntime,
  resetRuntimeCache,
} from '@nextrush/runtime';

// Runtime checks
import { isRuntime, isNode, isBun, isDeno, isEdge } from '@nextrush/runtime';

// Body source abstraction
import {
  AbstractBodySource,
  WebBodySource,
  EmptyBodySource,
  createWebBodySource,
  createEmptyBodySource,
  BodyConsumedError,
  BodyTooLargeError,
  DEFAULT_BODY_LIMIT,
} from '@nextrush/runtime';

// Query string parsing
import { parseQueryString } from '@nextrush/runtime';

// Headers utilities
import { headersToRecord, getClientIp, getEdgeClientIp } from '@nextrush/runtime';

// Constants
import { METHODS_WITHOUT_BODY } from '@nextrush/runtime';

// Types
import type {
  Runtime,
  RuntimeInfo,
  RuntimeCapabilities,
  BodySource,
  BodySourceOptions,
  EdgeRuntimeInfo,
} from '@nextrush/runtime';

Runtime Detection

Basic Usage

import { getRuntime, getRuntimeVersion } from '@nextrush/runtime';

const runtime = getRuntime();
// 'node' | 'bun' | 'deno' | 'deno-deploy' | 'cloudflare-workers'
// | 'vercel-edge' | 'edge' | 'unknown'

const version = getRuntimeVersion();
// '20.10.0' (Node.js)
// '1.0.0' (Bun)
// '1.38.0' (Deno)

Detection Order

Runtime detection checks in this order (most specific first):

  1. Bun — has global Bun object
  2. Deno Deploy — has global Deno object + DENO_DEPLOYMENT_ID environment variable
  3. Deno — has global Deno object
  4. Cloudflare Workersnavigator.userAgent includes "Cloudflare-Workers"
  5. Node.js — has process.versions.node
  6. Vercel Edge — has process.env.VERCEL_REGION but no process.versions.node
  7. Generic Edge — has Request/Response globals but not Node.js
  8. Unknown — none of the above

Deno Deploy is detected before regular Deno. If your code needs to distinguish between Deno and Deno Deploy (e.g., no filesystem on Deploy), check for 'deno-deploy' explicitly.

detectRuntime() vs getRuntime()

import { detectRuntime, getRuntime } from '@nextrush/runtime';

// detectRuntime() — always runs full detection logic
const runtime1 = detectRuntime();

// getRuntime() — caches the result after first call (recommended)
const runtime2 = getRuntime(); // First call: detects and caches
const runtime3 = getRuntime(); // Subsequent calls: returns cached value

Use getRuntime() in production code for better performance.

Runtime Checks

Convenience functions for common checks:

import { isNode, isBun, isDeno, isEdge, isRuntime } from '@nextrush/runtime';

if (isNode()) {
  // Node.js specific code
}

if (isBun()) {
  // Bun specific optimization
}

if (isDeno()) {
  // Deno specific code (does NOT match Deno Deploy)
}

if (isEdge()) {
  // Returns true for Cloudflare Workers, Vercel Edge, and generic edge
}

// Check for a specific runtime
if (isRuntime('cloudflare-workers')) {
  // Cloudflare Workers specific code
}

if (isRuntime('deno-deploy')) {
  // Deno Deploy specific code
}

Runtime Capabilities

Different runtimes support different features. Check before using runtime-specific APIs:

import { getRuntimeCapabilities } from '@nextrush/runtime';

const caps = getRuntimeCapabilities();

if (caps.fileSystem) {
  // Safe to use fs operations
}

if (caps.webStreams) {
  // Safe to use ReadableStream
}

if (caps.cryptoSubtle) {
  // Safe to use crypto.subtle
}

Capabilities by Runtime

CapabilityNode.jsBunDenoDeno DeployEdge Runtimes
nodeStreams
webStreams
fileSystem
webSocket
fetch
cryptoSubtle
workers

"Edge Runtimes" covers Cloudflare Workers, Vercel Edge, and generic edge environments.

Edge Runtime Detection

For more granular edge platform detection, use detectEdgeRuntime():

import { detectEdgeRuntime } from '@nextrush/runtime';
import type { EdgeRuntimeInfo } from '@nextrush/runtime';

const edgeInfo: EdgeRuntimeInfo = detectEdgeRuntime();
// {
//   runtime: 'cloudflare-workers',
//   isCloudflare: true,
//   isVercel: false,
//   isNetlify: false,
//   isGenericEdge: false,
// }

EdgeRuntimeInfo

PropertyTypeDescription
runtimeRuntimeDetected runtime identifier
isCloudflarebooleanRunning on Cloudflare Workers
isVercelbooleanRunning on Vercel Edge
isNetlifybooleanRunning on Netlify Edge Functions
isGenericEdgebooleanTrue when no specific edge platform is detected

The result is cached after the first call.

Complete Runtime Info

Get runtime, version, and capabilities in one call:

import { getRuntimeInfo } from '@nextrush/runtime';

const info = getRuntimeInfo();

RuntimeInfo

PropertyTypeDescription
runtimeRuntimeDetected runtime identifier
versionstring | undefinedRuntime version string
capabilitiesRuntimeCapabilitiesFeature support flags for the detected runtime
Example RuntimeInfo output
{
  "runtime": "node",
  "version": "20.10.0",
  "capabilities": {
    "nodeStreams": true,
    "webStreams": true,
    "fileSystem": true,
    "webSocket": true,
    "fetch": true,
    "cryptoSubtle": true,
    "workers": true
  }
}

Body Source Abstraction

BodySource provides a unified API for reading request bodies across runtimes.

interface BodySource {
  text(): Promise<string>;
  buffer(): Promise<Uint8Array>;
  json<T = unknown>(): Promise<T>;
  stream(): NodeStreamLike | WebStreamLike;
  readonly consumed: boolean;
  readonly contentLength: number | undefined;
  readonly contentType: string | undefined;
}

WebBodySource

For Web API-based runtimes (Bun, Deno, Cloudflare Workers, Vercel Edge):

import { WebBodySource, createWebBodySource } from '@nextrush/runtime';

const request = new Request('https://example.com', {
  method: 'POST',
  body: JSON.stringify({ name: 'Alice' }),
  headers: { 'Content-Type': 'application/json' },
});

// Using factory function
const bodySource = createWebBodySource(request, {
  limit: 1024 * 1024, // 1MB limit (default)
});

const data = await bodySource.json();
// { name: 'Alice' }

// Or using the class directly
const bodySource2 = new WebBodySource(request, { limit: 512 * 1024 });

EmptyBodySource

For requests without a body (GET, HEAD, OPTIONS):

import { createEmptyBodySource } from '@nextrush/runtime';

const emptyBody = createEmptyBodySource();

await emptyBody.text(); // ''
await emptyBody.buffer(); // Uint8Array(0)
await emptyBody.json(); // throws BadRequestError (code: 'EMPTY_BODY_JSON')

AbstractBodySource

Base class for building custom BodySource implementations. Adapter packages extend this to provide runtime-specific body reading:

import { AbstractBodySource } from '@nextrush/runtime';
import type { IncomingMessage } from 'node:http';

export class NodeBodySource extends AbstractBodySource {
  private readonly req: IncomingMessage;

  constructor(req: IncomingMessage) {
    super(req.headers['content-length'], req.headers['content-type']);
    this.req = req;
  }

  protected async _buffer(): Promise<Uint8Array> {
    const chunks: Buffer[] = [];
    for await (const chunk of this.req) {
      chunks.push(chunk);
    }
    return Buffer.concat(chunks);
  }

  protected _stream() {
    return this.req;
  }
}

Body Source Errors

import { BodyConsumedError, BodyTooLargeError } from '@nextrush/runtime';

// BodyConsumedError — thrown when body is read twice
try {
  await bodySource.text();
  await bodySource.json(); // throws BodyConsumedError
} catch (err) {
  if (err instanceof BodyConsumedError) {
    // Body was already read
  }
}

// BodyTooLargeError — thrown when body exceeds size limit
const limited = new WebBodySource(request, { limit: 1024 });

try {
  await limited.buffer();
} catch (err) {
  if (err instanceof BodyTooLargeError) {
    console.log(`Limit: ${err.limit}, Received: ${err.received}`);
  }
}

Body Source Options

BodySourceOptions

PropertyTypeDescription
limitnumber= 1048576 (1MB)Maximum body size in bytes
encoding'utf-8' | 'utf8' | 'ascii' | 'latin1' | 'iso-8859-1' | 'utf-16le' | 'utf-16be'= 'utf-8'Text encoding for text() method

Query String Parsing

Secure query string parser that protects against prototype pollution, parameter flooding, and malformed URIs:

import { parseQueryString } from '@nextrush/runtime';

const params = parseQueryString('name=Alice&age=30&tag=a&tag=b');
// { name: 'Alice', age: '30', tag: ['a', 'b'] }

Security features:

  • Uses Object.create(null) to prevent prototype pollution
  • Rejects __proto__, constructor, and prototype keys
  • Enforces a maximum of 256 parameters
  • Enforces a maximum query string length of 2048 characters
  • Replaces + with space (form-encoded convention)

Headers Utilities

headersToRecord()

Convert a Web API Headers object to a plain record:

import { headersToRecord } from '@nextrush/runtime';

const headers = new Headers();
headers.append('Content-Type', 'application/json');
headers.append('Accept', 'text/html');
headers.append('Accept', 'application/json');

const record = headersToRecord(headers);
// { 'content-type': 'application/json', accept: ['text/html', 'application/json'] }

Multi-value headers are stored as string[]. The result uses a null-prototype object to prevent prototype pollution.

getClientIp()

Extract client IP from a Web API Request, respecting proxy trust settings:

import { getClientIp } from '@nextrush/runtime';

const ip = getClientIp(request, directSocketIp, trustProxy);

When trustProxy is false, returns directIp only. When true, reads from X-Forwarded-For (first entry) then X-Real-IP.

getEdgeClientIp()

Extract client IP for Cloudflare-style edge runtimes:

import { getEdgeClientIp } from '@nextrush/runtime';

const ip = getEdgeClientIp(request, trustProxy);

When trustProxy is true, checks CF-Connecting-IP first, then falls back to standard proxy headers.

Constants

METHODS_WITHOUT_BODY

HTTP methods that do not carry a request body:

import { METHODS_WITHOUT_BODY } from '@nextrush/runtime';

// ReadonlySet<string> containing: 'GET', 'HEAD', 'OPTIONS', 'TRACE'
// DELETE is intentionally excluded — RFC 7231 §4.3.5 permits a body on DELETE

TypeScript Types

import type {
  Runtime,
  RuntimeInfo,
  RuntimeCapabilities,
  BodySource,
  BodySourceOptions,
  EdgeRuntimeInfo,
} from '@nextrush/runtime';

Runtime

type Runtime =
  | 'node'
  | 'bun'
  | 'deno'
  | 'deno-deploy'
  | 'cloudflare-workers'
  | 'vercel-edge'
  | 'edge'
  | 'unknown';

RuntimeCapabilities

interface RuntimeCapabilities {
  nodeStreams: boolean;
  webStreams: boolean;
  fileSystem: boolean;
  webSocket: boolean;
  fetch: boolean;
  cryptoSubtle: boolean;
  workers: boolean;
}

RuntimeInfo

interface RuntimeInfo {
  runtime: Runtime;
  version: string | undefined;
  capabilities: RuntimeCapabilities;
}

API Reference

Detection Functions

FunctionReturn TypeDescription
detectRuntime()RuntimeDetect runtime (runs full detection each call)
getRuntime()RuntimeGet cached runtime
getRuntimeVersion()string | undefinedGet runtime version string
getRuntimeCapabilities()RuntimeCapabilitiesGet feature capabilities
getRuntimeInfo()RuntimeInfoGet runtime, version, and capabilities
detectEdgeRuntime()EdgeRuntimeInfoDetect specific edge platform (cached)
resetRuntimeCache()voidClear cached runtime (for testing)

Check Functions

FunctionReturn TypeDescription
isRuntime(runtime)booleanCheck for a specific runtime
isNode()booleanCheck if Node.js
isBun()booleanCheck if Bun
isDeno()booleanCheck if Deno (not Deno Deploy)
isEdge()booleanCheck if Cloudflare Workers, Vercel Edge, or generic edge

Body Source

ExportTypeDescription
AbstractBodySourceclassBase class for custom BodySource implementations
WebBodySourceclassBodySource from Web API Request
EmptyBodySourceclassBodySource returning empty values
createWebBodySource(request, options?)functionFactory for WebBodySource
createEmptyBodySource()functionFactory returning shared EmptyBodySource singleton
BodyConsumedErrorclassThrown when body already read (extends BadRequestError)
BodyTooLargeErrorclassThrown when body exceeds limit (extends PayloadTooLargeError)
DEFAULT_BODY_LIMITconstantDefault body size limit: 1048576 (1MB)

Headers & Query

ExportTypeDescription
headersToRecord(headers)functionConvert Web API Headers to plain record
getClientIp(request, directIp, trustProxy)functionExtract client IP from request
getEdgeClientIp(request, trustProxy)functionExtract client IP for edge runtimes
parseQueryString(qs)functionSecure query string parser
METHODS_WITHOUT_BODYconstantReadonlySet<string> of bodyless HTTP methods

See Also

On this page