HBForge Documentation
Complete API reference, usage guides, and code examples for all 75 modules (106,300+ lines) and 1,280+ APIs. Web7-L6 AAA conformant — 30/30 baseline tests passing in ~8 ms.
What is HBForge?
HBForge is a unified JavaScript framework built entirely from scratch by HyperBridge Digital. It ships 27 production-ready modules covering every layer of a modern application — UI, server, auth, data, animation, charting, PDF, search, email, i18n, testing, CLI, plus the full Web7 Layer 6 primitive set (AMP, AIG, PoO, memory, consent, provenance, ZK-ML, conformance) — with zero external dependencies.
Instead of assembling 60+ npm packages from different authors with incompatible APIs, versioning headaches, and cascading security vulnerabilities, HBForge gives your team one coherent toolkit. Every module is designed to work standalone or together, with shared conventions and no transitive bloat.
Philosophy
node_modules except HBForge itself.d.ts types for all 1,280+ APIs, no @types/* packages neededArchitecture
HBForge is organized into four logical layers. Each layer can be used independently:
UI Layer (Browser)
forge/client · forge/chart · forge/animate · forge/form · forge/i18n
Server Layer (Node.js)
forge/server · forge/auth · forge/data · forge/notify · forge/mail
Platform Layer
forge/pwa · forge/display · forge/3d
Tooling Layer
forge/test · forge/cli · forge/pdf · forge/search · forge/schema
Module Overview
| Module | Purpose | Lines | APIs |
|---|---|---|---|
forge/3d |
WebGL2 3D engine — geometry, materials, lights, cameras, particles, post-fx | 28,640 | 368+ |
forge/client |
React-like UI framework with VDOM, hooks, signals, router, SSR | 12,276 | 171+ |
forge/server |
HTTP server, routing, middleware, WebSocket, Cron, background tasks | 11,177 | 85+ |
forge/data |
PostgreSQL ORM, query builder, migrations, transactions, seeding | 5,488 | 95+ |
forge/animate |
Tweens, springs, scroll effects, FLIP, stagger, timeline, easings | 7,528 | 45+ |
forge/auth |
Password, JWT, OAuth2, MagicLink, TOTP, WebAuthn, RBAC, MFA | 3,820 | 65+ |
forge/form |
Form management, async validation, field arrays, file upload, steps | 2,890 | 40+ |
forge/schema |
Zod-compatible schema validation, coercion, transforms, OpenAPI gen | 2,340 | 42+ |
forge/search |
Full-text search, BM25 ranking, facets, autocomplete, highlights | 2,100 | 35+ |
forge/pwa |
Service Workers, Cache API, Web Push, Background Sync, offline-first | 1,880 | 35+ |
forge/test |
Test runner, matchers, mocks, DOM testing, snapshots, property-based | 2,050 | 45+ |
forge/chart |
SVG/Canvas charts: line, bar, pie, scatter, radar, heatmap, streaming | 1,960 | 25+ |
forge/pdf |
PDF generation, tables, custom fonts, encryption, watermarks, QR codes | 1,830 | 30+ |
forge/mail |
SMTP client, DKIM signing, email templates, queue, tracking, calendar | 1,750 | 22+ |
forge/notify |
Toast UI, multi-channel notifications, retry queue, notification center | 1,620 | 18+ |
forge/i18n |
ICU MessageFormat, lazy namespaces, date/number/currency formatters, RTL | 1,540 | 30+ |
forge/cli |
CLI framework, interactive prompts, spinners, progress bars, config | 1,450 | 25+ |
forge/display |
Canvas 2D, DOM utilities, layout, color, CSS variables, typography, SVG, theme management | 1,944 | 60+ |
forge/prime Web7 |
Web7 bridge — DID identity, AMP routing, PoO, AIG, Vigil, BYOA, Telemetry, Model Routing, Kynetra Prime. Lite · Pro · Enterprise | 3,016 | 32+ |
forge/wasm Web7 |
Universal WASM loader for KYRx (L1) & ClearScript (L5) — memory manager, WASI-lite, worker pool, host imports (web7/prime · vigil · auth · amp), hot-reload, type bridge | 1,481 | 20+ |
forge/_internal shared |
Shared primitives — _internal/crypto (HMAC/SHA-256/random), _internal/binary (UTF-8/LEB128), _internal/time (now/duration/sleep). De-duplicates code across 6–8 modules. Advisory API; stable signatures. |
442 | 27 |
forge/ai roadmap |
LLM clients, streaming, tool use, embeddings, RAG pipeline | — | — |
Why Zero Dependencies?
Most JavaScript frameworks pull in dozens of transitive dependencies. Each one is a potential security vector, a version conflict, and a maintenance burden. HBForge is built entirely from scratch using modern JavaScript (ES2022+) and Web APIs available in Node 18+:
node:cryptofor all cryptography (bcrypt-style PBKDF2, HMAC, AES-GCM)node:http/node:httpsfor the server — no Express, no Fastifynode:net/node:tlsfor the SMTP client — no Nodemailer- Native
fetchfor HTTP requests — no Axios, no node-fetch - Inverted indexes and BM25 ranking written from scratch — no Lunr, no Flexsearch
- Canvas 2D / WebGL2 used directly — no Three.js dependency
structuredClone,AbortController,ReadableStream,URLPattern
Running npm ls --depth=0 after installing HBForge shows exactly one package: @hyperbridge/forge.
Installation
Enterprise Access Only
HBForge is currently available exclusively for enterprise clients. Opening to the Developer Community on June 25, 2026. Contact kr@hyperbridge.in for early access.
Requirements
- Node.js ≥ 18.0.0 (uses native fetch, crypto, streams)
- npm ≥ 8, yarn ≥ 3, pnpm ≥ 7, or bun ≥ 1.0
- TypeScript 5+ (optional — ships full
.d.tstypes, no@types/*needed)
Package Managers
# npm
npm install @hyperbridge/forge
# yarn
yarn add @hyperbridge/forge
# pnpm
pnpm add @hyperbridge/forge
# bun
bun add @hyperbridge/forge
Verify Installation
node -e "const forge = require('@hyperbridge/forge'); console.log(forge.VERSION);"
# → 3.2.1
Subpath Imports (Recommended)
Each module is a separate subpath entry. Import only what you need — the rest is never loaded. All subpaths are defined in package.json#exports and work with Node.js, bundlers (Vite, webpack, esbuild, Rollup), and TypeScript.
// ── UI Layer (Browser) ─────────────────────────────────────
// React-like component system, hooks, router, signals, SSR
import { createElement, Component, useState, useEffect, useRef,
useContext, createContext, useMemo, useCallback, useSignal,
useComputed, Router, Route, Link, render } from '@hyperbridge/forge/client';
// SVG/Canvas charts — line, bar, pie, scatter, radar, heatmap, etc.
import { ChartForge, ChartDashboard, ChartBrush } from '@hyperbridge/forge/chart';
// Tween, spring, scroll, FLIP, stagger, timeline animations
import { Tween, Spring, Timeline, onScroll, stagger, flip } from '@hyperbridge/forge/animate';
// Form management with async/cross-field validation and file upload
import { Form, Controller, DragDropUpload, RepeatingGroup,
AccessibleForm, fromSchema } from '@hyperbridge/forge/form';
// ICU MessageFormat, formatters, lazy namespaces, RTL support
import { I18n, RTLManager, NumberFormatterEx } from '@hyperbridge/forge/i18n';
// ── Server Layer (Node.js) ──────────────────────────────────
// HTTP/HTTPS server, routing, middleware, WebSockets, cron
import { Forge, Router, WebSocketServer, CronJob, TaskQueue,
rateLimit, cors, compress, staticFiles } from '@hyperbridge/forge/server';
// Password, JWT, OAuth2, MagicLink, TOTP, WebAuthn, RBAC, MFA
import { Password, JWT, OAuth2Provider, TOTP, BackupCodes,
MagicLink, RBAC, SessionManager, WebAuthn, OAuth2PKCE,
AdaptiveMFA, SAMLProvider, IPAllowList, AuditTrail,
DeviceManager } from '@hyperbridge/forge/auth';
// PostgreSQL ORM, query builder, migrations, seeding, transactions
import { Pool, PgConnection, Model, QueryBuilder, Migrator,
SchemaBuilder, ReadReplica, QueryCache, AuditPlugin,
SeedFactory } from '@hyperbridge/forge/data';
// Multi-channel notifications (toast, WebSocket, email, SMS)
import { NotificationManager, ToastManager, RetryQueue,
DedupeFilter, NotificationCenter, QuietHours } from '@hyperbridge/forge/notify';
// SMTP client with DKIM, templates, queue, tracking, calendar
import { SMTPClient, EmailTemplate, MailQueue, MailTracker,
BounceList, CalendarInvite, ABTest } from '@hyperbridge/forge/mail';
// ── Platform Layer ──────────────────────────────────────────
// Service Workers, Cache API, Web Push, Background Sync
import { PWAForge, CacheStrategy, PrecacheManager, PushManager,
BackgroundSync, OfflineManager, AppShell, BadgeManager,
UpdateManager, generateVAPIDKeys } from '@hyperbridge/forge/pwa';
// DPR/Retina/4K detection, quality tiers, canvas scaling
import { getDPR, isRetina, is4K, qualityTier, qualityScore,
snapshot, scaleCanvas, setupWebGLViewport,
createVariant, createVariants, onDPRChange } from '@hyperbridge/forge/display';
// WebGL2 3D engine — scene graph, geometry, materials, post-fx
import { Scene, WebGLRenderer, PerspectiveCamera, Mesh,
BoxGeometry, SphereGeometry, StandardMaterial,
DirectionalLight, PointLight, AnimationMixer,
ParticleSystem, Raycaster } from '@hyperbridge/forge/3d';
// ── Tooling Layer ───────────────────────────────────────────
// Test runner, matchers, mocking, DOM/HTTP testing, snapshots
import { TestRunner, expect, fn, spyOn, bench } from '@hyperbridge/forge/test';
// CLI framework, prompts, spinners, progress bars, task runner
import { CLI, Command, Prompt, Spinner, ProgressBar,
TaskRunner, Logger, c } from '@hyperbridge/forge/cli';
// PDF generation with tables, fonts, encryption, QR codes
import { PDFForge, PDFReader, QRCode, Barcode } from '@hyperbridge/forge/pdf';
// Full-text BM25 search, autocomplete, facets, analytics
import { SearchForge, Autocomplete, FacetedSearch,
SearchAnalytics, Highlighter, RelevanceTuner } from '@hyperbridge/forge/search';
// Zod-compatible schema validation with OpenAPI generation
import { z, Schema } from '@hyperbridge/forge/schema';
CommonJS (require)
// Full bundle (lazy-loaded per module)
const forge = require('@hyperbridge/forge');
console.log(forge.VERSION); // 3.2.1
// Specific modules
const { Password, JWT } = require('@hyperbridge/forge/auth');
const { Pool, Model } = require('@hyperbridge/forge/data');
const { Forge, Router } = require('@hyperbridge/forge/server');
const { PDFForge } = require('@hyperbridge/forge/pdf');
const { z } = require('@hyperbridge/forge/schema');
TypeScript Setup
No extra packages required — all types ship with the package. Recommended tsconfig.json:
{
"compilerOptions": {
"target": "ES2022",
"module": "Node16",
"moduleResolution": "Node16",
"strict": true,
"types": [] // no @types/* needed
}
}
// Types are inferred automatically
import { z } from '@hyperbridge/forge/schema';
const UserSchema = z.object({
id: z.number(),
email: z.string().email(),
role: z.enum(['admin', 'user']),
});
type User = z.infer<typeof UserSchema>;
// → { id: number; email: string; role: 'admin' | 'user' }
ESM in Browsers (CDN)
Client-side modules work in modern browsers via ESM CDN (available at GA on June 25, 2026):
<!-- Via CDN (GA June 2026) -->
<script type="module">
import { createElement, useState, render }
from 'https://cdn.hbforge.dev/v3/client/index.js';
import { Tween }
from 'https://cdn.hbforge.dev/v3/animate/index.js';
</script>
Quick Start
One working example per module. Each snippet is self-contained — copy, paste, run.
Browser UI — forge/client
import { createElement as h, useState, useEffect, render } from '@hyperbridge/forge/client';
function Counter() {
const [count, setCount] = useState(0);
useEffect(() => {
document.title = `Count: ${count}`;
}, [count]);
return h('div', { style: { padding: '20px' } },
h('h1', null, `Count: ${count}`),
h('button', { onClick: () => setCount(c => c + 1) }, 'Increment'),
h('button', { onClick: () => setCount(0), style: { marginLeft: 8 } }, 'Reset')
);
}
render(h(Counter), document.getElementById('root'));
HTTP Server — forge/server
import { Forge, Router, rateLimit, cors } from '@hyperbridge/forge/server';
const app = new Forge();
const users = new Router({ prefix: '/users' });
app.use(cors({ origin: '*' }));
app.use(rateLimit({ max: 100, windowMs: 60_000 }));
users.get('/', async (req, res) => res.json(await db.all()));
users.get('/:id', async (req, res) => res.json(await db.find(req.params.id)));
users.post('/', async (req, res) => {
const body = await req.json();
res.status(201).json(await db.insert(body));
});
app.use(users);
app.listen(3000, () => console.log('Server on :3000'));
Schema Validation — forge/schema
import { z } from '@hyperbridge/forge/schema';
const UserSchema = z.object({
id: z.number().int().positive(),
email: z.string().email(),
role: z.enum(['admin', 'editor', 'viewer']).default('viewer'),
age: z.number().min(13).max(120).optional(),
tags: z.array(z.string()).max(10).default([]),
settings: z.record(z.string(), z.unknown()).optional(),
});
// Parse (throws on failure)
const user = UserSchema.parse({ id: 1, email: 'a@b.com' });
// Safe parse (returns result object)
const { success, data, error } = UserSchema.safeParse(rawInput);
// Infer TypeScript type
type User = z.infer<typeof UserSchema>;
// Generate OpenAPI schema
const openApiSchema = UserSchema.toOpenAPI();
Forms — forge/form
import { Form, fromSchema } from '@hyperbridge/forge/form';
import { z } from '@hyperbridge/forge/schema';
const schema = z.object({
email: z.string().email('Invalid email'),
password: z.string().min(8, 'Min 8 characters'),
name: z.string().min(1),
});
// Build form from schema — field types inferred automatically
const form = fromSchema(schema, {
onSubmit: async (data) => {
const res = await fetch('/api/register', {
method: 'POST',
body: JSON.stringify(data),
});
return res.json();
},
});
// Use in UI
form.mount('#register-form');
// Access state programmatically
form.on('change', ({ field, value, errors }) => {
console.log(field, 'changed to', value, 'errors:', errors);
});
form.on('submit', ({ data }) => console.log('Submitted:', data));
form.on('error', ({ errors }) => console.warn('Errors:', errors));
Database ORM — forge/data
import { Pool, Model, QueryBuilder } from '@hyperbridge/forge/data';
const pool = new Pool({
host: 'localhost', database: 'myapp',
user: 'postgres', password: 'secret',
max: 10,
});
// Declare a model
class User extends Model {
static table = 'users';
static pool = pool;
static fillable = ['email', 'name', 'role'];
static hidden = ['password_hash'];
static scopes = {
active: qb => qb.where('active', true),
admins: qb => qb.where('role', 'admin'),
};
}
// CRUD
const user = await User.create({ email: 'a@b.com', name: 'Alex' });
const all = await User.scope('active').orderBy('name').limit(20).get();
const one = await User.where('email', 'a@b.com').first();
await one.update({ name: 'Alexandra' });
await one.delete();
// Raw query builder
const rows = await new QueryBuilder(pool)
.table('orders')
.join('users', 'users.id', 'orders.user_id')
.where('orders.total', '>', 100)
.orderBy('orders.created_at', 'desc')
.select('orders.*', 'users.email')
.get();
Authentication — forge/auth
import { Password, JWT, TOTP, RBAC } from '@hyperbridge/forge/auth';
// Hash & verify passwords (PBKDF2-SHA-256, 310,000 iterations)
const hash = await Password.hash('mypassword');
const valid = await Password.verify('mypassword', hash); // true
// JWT — sign, verify, refresh
const secret = process.env.JWT_SECRET;
const token = await JWT.sign({ userId: 42, role: 'admin' }, secret, { expiresIn: '15m' });
const claims = await JWT.verify(token, secret);
// { userId: 42, role: 'admin', iat: ..., exp: ... }
const refreshed = await JWT.refresh(token, secret, { expiresIn: '15m' });
// TOTP (Google Authenticator compatible)
const { secret: totpSecret, qrDataUrl } = await TOTP.generateSecret('user@app.com', 'MyApp');
const isValid = TOTP.verify('123456', totpSecret);
// RBAC
const rbac = new RBAC({
roles: {
admin: { permissions: ['*'] },
editor: { permissions: ['posts:read', 'posts:write'] },
viewer: { permissions: ['posts:read'] },
},
});
rbac.can('editor', 'posts:write'); // true
rbac.can('viewer', 'posts:write'); // false
Animations — forge/animate
import { Tween, Spring, Timeline, onScroll, stagger } from '@hyperbridge/forge/animate';
// Tween with easing
const tween = new Tween({
target: document.querySelector('.card'),
duration: 400,
values: { opacity: [0, 1], translateY: [20, 0] },
easing: 'easeOutCubic',
});
tween.play();
// Physics spring
const spring = new Spring({
target: document.querySelector('.modal'),
values: { scale: [0.9, 1], opacity: [0, 1] },
tension: 280, friction: 20, mass: 1,
});
spring.play();
// Chained timeline
const tl = new Timeline();
tl.add(new Tween({ target: logo, values: { opacity: [0, 1] }, duration: 300 }))
.add(new Tween({ target: nav, values: { translateY: [-20, 0] }, duration: 200 }), 150)
.add(new Tween({ target: hero, values: { scale: [0.95, 1] }, duration: 400 }), 250)
.play();
// Scroll-driven animation
onScroll(({ progress, element }) => {
element.style.opacity = progress;
element.style.transform = `translateY(${(1 - progress) * 30}px)`;
}, { target: document.querySelector('.section'), threshold: 0.1 });
// Stagger list items
stagger(document.querySelectorAll('.list-item'), { delay: 60, duration: 300, values: { opacity: [0, 1], translateX: [-20, 0] } });
Charts — forge/chart
import { ChartForge } from '@hyperbridge/forge/chart';
const chart = new ChartForge({
container: '#sales-chart',
type: 'line',
width: 800,
height: 400,
theme: 'dark',
});
chart.setData({
labels: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun'],
datasets: [
{ label: 'Revenue', data: [42, 58, 39, 71, 63, 88], color: '#6366f1' },
{ label: 'Expenses', data: [30, 35, 28, 45, 50, 60], color: '#f43f5e' },
],
});
chart.render();
// Live streaming updates
const ws = new WebSocket('/api/metrics');
ws.onmessage = ({ data }) => {
chart.push(JSON.parse(data));
};
// Export
const pngBlob = await chart.export('png', { scale: 2 });
const svgStr = chart.export('svg');
PDF Generation — forge/pdf
import { PDFForge, QRCode } from '@hyperbridge/forge/pdf';
const doc = new PDFForge({ pageSize: 'A4', margin: { top: 40, left: 40, right: 40, bottom: 40 } });
// Page 1 — invoice
doc.setFont('Helvetica-Bold', 18);
doc.text('INVOICE #1042', { x: 40, y: 40 });
doc.setFont('Helvetica', 11);
doc.text('HyperBridge Digital · Chennai, India', { x: 40, y: 65 });
doc.text(`Date: ${new Date().toLocaleDateString()}`, { x: 420, y: 40 });
// Draw divider
doc.line({ x1: 40, y1: 90, x2: 555, y2: 90, width: 0.5, color: '#cccccc' });
// Table
doc.table({
x: 40, y: 105,
columns: [
{ header: 'Item', width: 250 },
{ header: 'Qty', width: 60, align: 'right' },
{ header: 'Rate', width: 90, align: 'right' },
{ header: 'Amount', width: 105, align: 'right' },
],
rows: [
['HBForge Enterprise License', '1', '$2,400.00', '$2,400.00'],
['Setup & Onboarding', '3', ' $150.00', ' $450.00'],
],
headerStyle: { fillColor: '#f5f5f5', bold: true },
cellPadding: 8,
});
// QR code embedded in PDF
const qr = QRCode.generate('https://hbforge.dev', { size: 80 });
doc.image(qr, { x: 450, y: 680 });
const pdfBytes = doc.build();
// Node.js: await fs.writeFile('invoice.pdf', pdfBytes)
// Browser: const url = URL.createObjectURL(new Blob([pdfBytes], { type: 'application/pdf' }))
Full-Text Search — forge/search
import { SearchForge, Autocomplete, FacetedSearch } from '@hyperbridge/forge/search';
// Build an index
const search = new SearchForge({
fields: ['title', 'body', 'author'],
weights: { title: 3, author: 2, body: 1 },
});
search.addDocuments([
{ id: 1, title: 'Getting Started with HBForge', body: 'A complete guide...', author: 'HyperBridge', category: 'tutorial' },
{ id: 2, title: 'forge/auth deep dive', body: 'JWT, OAuth2, TOTP...', author: 'HyperBridge', category: 'guide' },
{ id: 3, title: 'Building REST APIs', body: 'forge/server routing...', author: 'HyperBridge', category: 'guide' },
]);
// BM25 search
const results = search.search('jwt auth tokens');
// [{ id: 2, score: 4.82, title: 'forge/auth deep dive', ... }, ...]
// Autocomplete (prefix matching)
const ac = new Autocomplete(search);
const suggestions = ac.suggest('forg'); // ['forge/auth deep dive', 'Getting Started with HBForge', ...]
// Faceted search
const faceted = new FacetedSearch(search, { facets: ['category'] });
const { hits, facets } = faceted.search('guide', { filters: { category: 'guide' } });
// hits = [...], facets = { category: { tutorial: 1, guide: 2 } }
Notifications — forge/notify
import { ToastManager, NotificationManager, NotificationCenter } from '@hyperbridge/forge/notify';
// DOM toasts (browser)
const toast = new ToastManager({ position: 'top-right', maxStack: 5 });
toast.success('File uploaded successfully');
toast.error('Connection failed — retrying…');
toast.warning('Your session expires in 5 minutes');
toast.info('New version available');
// Async toast (shows loading, resolves to success/error)
await toast.promise(
fetch('/api/upload', { method: 'POST', body: formData }),
{ loading: 'Uploading…', success: 'Done!', error: 'Upload failed' }
);
// Multi-channel notification manager (Node.js)
const nm = new NotificationManager({
channels: {
email: { type: 'smtp', host: 'smtp.example.com', from: 'noreply@app.com' },
sms: { type: 'twilio', accountSid: '...', authToken: '...', from: '+1555...' },
},
});
await nm.send({
to: 'user@example.com',
channels: ['email', 'sms'],
subject: 'Password changed',
body: 'Your password was changed. If this wasn\'t you, contact support.',
});
// Notification center with persistence
const center = new NotificationCenter({ maxHistory: 100 });
center.add({ id: '1', title: 'New comment', body: 'Alex replied to your post', read: false });
const unread = center.getUnread();
Email — forge/mail
import { SMTPClient, EmailTemplate, MailQueue } from '@hyperbridge/forge/mail';
// SMTP client with DKIM signing
const smtp = new SMTPClient({
host: 'smtp.postmarkapp.com',
port: 587,
secure: false,
auth: { user: process.env.SMTP_USER, pass: process.env.SMTP_PASS },
dkim: { domain: 'hyperbridge.digital', keySelector: 'default', privateKey: DKIM_KEY },
pool: true, // connection pooling
maxConns: 5,
});
// HTML templates with variable substitution
const tmpl = new EmailTemplate({
subject: 'Welcome to {{appName}}, {{firstName}}!',
html: 'Hi {{firstName}},
Your account is ready.
',
text: 'Hi {{firstName}}, your account is ready.',
});
await smtp.send({
to: 'user@example.com',
from: 'welcome@hyperbridge.digital',
...tmpl.render({ appName: 'HBForge', firstName: 'Alex' }),
});
// Queue for bulk sending with rate limiting
const queue = new MailQueue(smtp, { rateLimit: 50, rateLimitInterval: 1000 });
for (const subscriber of subscribers) {
queue.enqueue({ to: subscriber.email, ...newsletter.render(subscriber) });
}
await queue.flush(); // sends all queued emails respecting rate limits
Internationalization — forge/i18n
import { I18n, NumberFormatterEx } from '@hyperbridge/forge/i18n';
const i18n = new I18n({
locale: 'en',
fallback: 'en',
messages: {
en: {
'greeting': 'Hello, {name}!',
'unread': 'You have {count, plural, one {# message} other {# messages}}',
'appointment': 'Your appointment is on {date, date, long} at {time, time, short}',
'balance': 'Balance: {amount, number, ::currency/USD}',
'progress': '{pct, number, ::percent}',
},
ta: {
'greeting': 'வணக்கம், {name}!',
'unread': '{count} செய்திகள் உள்ளன',
},
},
});
i18n.t('greeting', { name: 'Alex' });
// en → "Hello, Alex!"
i18n.t('unread', { count: 3 });
// en → "You have 3 messages"
i18n.t('appointment', { date: new Date(), time: new Date() });
// en → "Your appointment is on April 14, 2026 at 9:00 AM"
// Locale negotiation
I18n.negotiate(['ta-IN', 'en-US'], ['en', 'ta', 'fr']); // → 'ta'
// Extended number formatting
const fmt = new NumberFormatterEx('en-US');
fmt.currency(2400, 'USD'); // "$2,400.00"
fmt.compact(81954); // "82K"
fmt.percent(0.876); // "87.6%"
fmt.ordinal(3); // "3rd"
CLI Tools — forge/cli
import { CLI, Command, Prompt, Spinner, TaskRunner, c } from '@hyperbridge/forge/cli';
const cli = new CLI({ name: 'myapp', version: '1.0.0' });
cli.add(new Command({
name: 'deploy',
desc: 'Deploy to production',
options: [
{ flag: '--env, -e', desc: 'Environment', default: 'staging' },
{ flag: '--dry-run', desc: 'Simulate without deploying', boolean: true },
],
action: async ({ env, dryRun }) => {
// Interactive confirmation
const confirm = await Prompt.confirm(`Deploy to ${c.red(env)}?`);
if (!confirm) return;
const spinner = new Spinner('Building…').start();
const runner = new TaskRunner([
{ name: 'Build', run: () => build() },
{ name: 'Test', run: () => test() },
{ name: 'Upload', run: () => upload(), skip: dryRun },
{ name: 'Migrate', run: () => migrate(), skip: dryRun },
]);
runner.on('task:done', ({ name }) => console.log(c.green('✔') + ' ' + name));
runner.on('task:failed', ({ name, error }) => console.error(c.red('✖') + ' ' + name, error));
await runner.run();
spinner.succeed('Deployed to ' + c.cyan(env));
},
}));
cli.run(process.argv.slice(2));
Testing — forge/test
import { TestRunner, expect, fn, spyOn, bench } from '@hyperbridge/forge/test';
const runner = new TestRunner({ timeout: 5000, bail: false });
runner.describe('User service', () => {
runner.it('creates a user', async () => {
const user = await createUser({ email: 'a@b.com', name: 'Alex' });
expect(user.id).toBeGreaterThan(0);
expect(user.email).toBe('a@b.com');
expect(user.createdAt).toBeInstanceOf(Date);
});
runner.it('hashes password', async () => {
const mockHash = fn().mockResolvedValue('hashed_pw');
const user = await createUser({ email: 'b@c.com', password: 'secret' }, { hashFn: mockHash });
expect(mockHash).toHaveBeenCalledWith('secret');
expect(user.passwordHash).toBe('hashed_pw');
});
runner.it('throws on duplicate email', async () => {
await createUser({ email: 'dup@b.com' });
await expect(createUser({ email: 'dup@b.com' })).rejects.toThrow('Email already exists');
});
});
// Benchmark
runner.describe('Performance', () => {
bench('schema parse', () => UserSchema.parse(rawUser), { iterations: 10_000 });
});
await runner.run();
Display & Device Detection — forge/display
import {
getDPR, isRetina, is4K, qualityTier, qualityScore, snapshot,
scaleCanvas, setupWebGLViewport, createVariants, onDPRChange,
} from '@hyperbridge/forge/display';
// Device capabilities
console.log(getDPR()); // 2 (on a Retina display)
console.log(isRetina()); // true (DPR >= 2)
console.log(is4K()); // false (viewport < 3840)
console.log(qualityScore()); // 72 (0–100)
console.log(qualityTier()); // 'high' ('low'|'medium'|'high'|'ultra')
// Full capability snapshot
const caps = snapshot();
// { dpr: 2, tier: 'high', score: 72, gpu: 'high', width: 1920,
// height: 1080, hdr: false, saveData: false, webgl2: true, ... }
// Pixel-perfect canvas scaling
const canvas = document.getElementById('my-canvas');
const ctx = scaleCanvas(canvas, 800, 600);
// canvas is now 1600×1200 px (Retina), CSS size is 800×600
// WebGL DPR-aware viewport
const gl = canvas.getContext('webgl2');
setupWebGLViewport(gl, canvas);
// Adaptive asset variants
const { src, srcset } = createVariants('/hero', {
low: { suffix: '@1x', ext: 'webp' },
medium: { suffix: '@1.5x', ext: 'webp' },
high: { suffix: '@2x', ext: 'webp' },
ultra: { suffix: '@3x', ext: 'avif' },
});
// On a Retina high-tier device: src='/hero@2x.webp'
// React to DPR changes (e.g. drag to external monitor)
onDPRChange(({ from, to }) => {
console.log(`DPR changed ${from} → ${to}`);
scaleCanvas(canvas, 800, 600); // rescale
});
Progressive Web App — forge/pwa
// service-worker.js (register with navigator.serviceWorker.register('/sw.js'))
import { PWAForge, CacheStrategy, PrecacheManager,
PushManager, BackgroundSync } from '@hyperbridge/forge/pwa';
const pwa = new PWAForge({ version: '1.0.0', debug: false });
// Precache app shell at install time
const precache = new PrecacheManager(pwa);
precache.add([
'/', '/app.js', '/app.css', '/offline.html',
'/icons/icon-192.png', '/icons/icon-512.png',
]);
// Route-level caching strategies
pwa.route('/api/*', CacheStrategy.NetworkFirst({ timeout: 3000, cacheName: 'api-v1' }));
pwa.route('/images/*', CacheStrategy.CacheFirst({ cacheName: 'images-v1', maxAge: 86400 }));
pwa.route('/*', CacheStrategy.StaleWhileRevalidate({ cacheName: 'pages-v1' }));
// Web Push
const push = new PushManager(pwa, { vapidPublicKey: VAPID_KEY });
push.onMessage(payload => {
self.registration.showNotification(payload.title, { body: payload.body, icon: '/icon.png' });
});
// Background Sync — replay failed requests when back online
const sync = new BackgroundSync('api-queue');
sync.register(failedFetchRequest); // queues the request
// → replayed automatically when connection restores
pwa.install();
forge/client
Fiber-based concurrent UI framework with VDOM, 146+ APIs, priority lanes, hooks, signals, atoms, routing, CSS-in-JS, SSR, and more. Zero dependencies. 12,276 lines.
forge/client uses a fiber-based reconciler with 6 priority lanes for time-sliced rendering. State updates are batched by default -- multiple setState calls within the same microtask produce only a single re-render. Priority lanes range from Immediate (user input) down to Idle (off-screen prefetching), giving you React 18-level scheduling without any external dependencies.
Core Rendering
createElement / h
Create virtual DOM nodes. h(tag, props, ...children) — supports components, fragments, and nested arrays.
render
Mount a virtual tree to a DOM container. Kicks off the fiber reconciler with priority scheduling.
hydrate
Rehydrate server-rendered HTML. Attaches event listeners without re-rendering existing DOM.
Fragment
Group children without adding extra DOM nodes. Use h(Fragment, null, ...children).
createPortal
Render children into a different DOM container. SSR-safe with event bubbling through the virtual tree.
PortalHost
Named portal target component. Place PortalHost in layout, render into it from anywhere.
createErrorBoundary
Catch render errors in child trees. Accepts fallback UI and onError callback.
Suspense
Show fallback UI while async children resolve. Works with lazy() and useSuspenseData().
import { h, render, Fragment } from '@hyperbridge/forge/client';
function App() {
return h(Fragment, null,
h('h1', null, 'Hello HBForge'),
h('p', null, 'Fiber-based concurrent rendering')
);
}
render(h(App), document.getElementById('root'));
Hooks
useState
Local state. Returns [value, setter]. Setter accepts value or updater function. Auto-batched.
useReducer
Complex state via reducer(state, action). Returns [state, dispatch]. Supports lazy initializer.
useEffect
Run side effects after render. Dependency array controls re-runs. Return cleanup function.
useLayoutEffect
Like useEffect but fires synchronously after DOM mutations. Use for measurements.
useRef
Mutable ref object persisting across renders. Attach to DOM with ref prop.
useMemo
Memoize expensive computations. Re-computes only when dependencies change.
useCallback
Memoize callback functions. Prevents unnecessary child re-renders.
useContext
Read value from nearest Provider of a given context. Re-renders on change.
useId
Generate unique stable IDs. SSR-safe. Useful for form labels and aria attributes.
usePrevious
Track the previous value of a variable across renders.
useTransition
Mark state updates as non-urgent. Returns [isPending, startTransition].
useDeferredValue
Defer a value to keep UI responsive. Low-priority re-render with the new value.
useSyncExternalStore
Subscribe to external stores with concurrent-safe reads. Accepts subscribe + getSnapshot.
import { h, useState, useEffect, useRef } from '@hyperbridge/forge/client';
function Timer() {
const [seconds, setSeconds] = useState(0);
const intervalRef = useRef(null);
useEffect(() => {
intervalRef.current = setInterval(() => {
setSeconds(s => s + 1);
}, 1000);
return () => clearInterval(intervalRef.current);
}, []);
return h('p', null, `Elapsed: ${seconds}s`);
}
Signals & Reactive State
Signals provide fine-grained reactivity outside the VDOM. Unlike hooks, which schedule a component re-render, a signal update surgically patches the DOM node it is bound to. This makes signals ideal for high-frequency updates like counters, progress bars, and real-time data feeds.
| Parameter | Type | Description |
|---|---|---|
| initialValue | T | The starting value of the signal. Can be any serialisable type. |
| .value | T | Read or write the current value. Setting triggers subscribers. |
| .peek() | T | Read the current value without subscribing (no tracking). |
| .subscribe(fn) | (value: T) => void | Register a listener. Returns an unsubscribe function. |
signal
Create a reactive signal. signal(initialValue) returns { value, subscribe, peek }. Fine-grained updates bypass VDOM.
computed
Derive a read-only signal from other signals. Auto-tracks dependencies. Lazy evaluation.
useSignal
Use a signal inside a component. Auto-subscribes and re-renders on change.
bindSignalToText
Bind a signal directly to a text node. Updates textContent without re-rendering component.
bindSignalToAttribute
Bind a signal to a DOM attribute. Updates attribute value directly.
bindSignalToStyle
Bind a signal to a CSS style property. Surgical style updates, no reconciliation.
useSignalEffect
Run an effect that auto-tracks signal reads. Re-runs when any tracked signal changes.
import { h, signal, computed, useSignal, bindSignalToText } from '@hyperbridge/forge/client';
const count = signal(0);
const doubled = computed(() => count.value * 2);
function Display() {
const val = useSignal(doubled);
return h('p', null, `Doubled: ${val}`);
}
// Direct DOM binding — bypasses VDOM entirely
const el = document.getElementById('counter');
bindSignalToText(count, el);
count.value = 5; // el.textContent instantly becomes "5"
Signals bypass the VDOM entirely. If you bind a signal to DOM via bindSignalToText(), changes will not trigger component re-renders. This is by design for performance, but it means component-level effects (useEffect) won't fire in response to signal changes. Use useSignal() inside components when you need both reactivity and lifecycle integration.
Atoms & Global State
atom
Global state atom. atom(initialValue) returns a subscribable store. Shared across components.
derived
Derive a read-only atom from other atoms. Re-computes when source atoms change.
useAtom
Bind an atom to a component. Returns [value, setValue]. Component re-renders on atom change.
createStore
Create a centralized store with actions and selectors. Supports middleware.
useStore
Subscribe to a store with an optional selector. Only re-renders when selected slice changes.
import { h, atom, derived, useAtom } from '@hyperbridge/forge/client';
const todosAtom = atom([]);
const countAtom = derived([todosAtom], ([todos]) => todos.length);
function TodoList() {
const [todos, setTodos] = useAtom(todosAtom);
const [count] = useAtom(countAtom);
const add = () => setTodos(t => [...t, { text: 'New', done: false }]);
return h('div', null,
h('p', null, `${count} items`),
h('button', { onClick: add }, 'Add Todo'),
...todos.map(t => h('p', null, t.text))
);
}
Async Data & Mutations
useAsync
Fetch async data with built-in SWR cache. Returns { data, loading, error, refetch }. Dependency-driven.
useMutation
Trigger async writes. Returns { mutate, loading, error }. Supports optimistic updates.
invalidateCache
Invalidate cached async data by key. Forces refetch on next access.
useSuspenseData
Throw promise for Suspense integration. Caches resolved data. clearSuspenseCache() to reset.
preloadSuspenseData
Pre-fetch data before component mounts. Warm up the cache for instant renders.
useOptimistic
Optimistic UI updates. Shows predicted state immediately, rolls back on failure.
useSuspenseMutation
Combine Suspense with mutation. Suspends during async operation.
useDeferredMutation
Deferred mutation that keeps UI responsive. Low-priority state update on completion.
createResource
Declarative async resource. createResource(fetcher) returns { read, preload, invalidate }.
useResource
Hook to consume a resource. Suspends until data is available.
useActionState
Form action state. Combines submission state with result tracking.
import { h, useAsync, useMutation, invalidateCache } from '@hyperbridge/forge/client';
function UserProfile({ userId }) {
const { data: user, loading, error } = useAsync(
() => fetch(`/api/users/${userId}`).then(r => r.json()),
[userId]
);
const { mutate: updateName, loading: saving } = useMutation(
(name) => fetch(`/api/users/${userId}`, {
method: 'PATCH', body: JSON.stringify({ name }),
})
);
if (loading) return h('p', null, 'Loading...');
if (error) return h('p', null, `Error: ${error.message}`);
return h('div', null,
h('h2', null, user.name),
h('button', {
onClick: () => updateName('New Name').then(() => invalidateCache()),
disabled: saving,
}, saving ? 'Saving...' : 'Rename')
);
}
Router & Navigation
Router
SPA router with history-based navigation. Supports nested routes, params, query strings, and wildcards.
Link
Declarative navigation link. Prevents full page reload. Supports active class styling.
Navigate
Programmatic redirect component. Renders nothing, navigates on mount.
useRouter
Access router instance. Returns { path, params, query, push, replace, back }.
createFileRouter
File-system-based routing. Automatically creates routes from directory structure.
router.beforeEach
Navigation guard. Called before each route change. Return false to cancel navigation.
router.afterEach
Post-navigation hook. Called after route change completes. Useful for analytics.
router.guard
Per-route guard. Attach auth checks or data loading to individual routes.
RouterWithGuards
Enhanced router component with built-in guard support and navigation middleware.
useGuardedRouter
Hook for guarded router. Access guards, pending state, and navigation methods.
import { h, render, Router, Route, Link, useRouter } from '@hyperbridge/forge/client';
function Home() {
return h('div', null,
h('h1', null, 'Home'),
h(Link, { to: '/users/42' }, 'View User')
);
}
function UserPage() {
const { params, back } = useRouter();
return h('div', null,
h('h1', null, `User #${params.id}`),
h('button', { onClick: back }, 'Go Back')
);
}
const router = new Router([
new Route('/', Home),
new Route('/users/:id', UserPage),
]);
// Navigation guards
router.beforeEach((to, from) => {
if (to.path.startsWith('/admin') && !isLoggedIn()) return false;
});
render(h(router), document.getElementById('root'));
Context & Selectors
createContext
Create a context with default value. Returns { Provider, Consumer } pair.
useContext
Consume context value from nearest Provider ancestor. Re-renders on any change.
useContextSelector
Select a slice of context. Only re-renders when the selected value changes. Prevents wasted renders.
import { h, createContext, useContextSelector } from '@hyperbridge/forge/client';
const AppContext = createContext({ user: null, theme: 'dark', lang: 'en' });
// Only re-renders when user.name changes — not on theme/lang changes
function UserName() {
const name = useContextSelector(AppContext, ctx => ctx.user?.name);
return h('span', null, name || 'Guest');
}
Forms & Validation
useFormStatus
Track form submission state. Returns { pending, data, method, action }.
FormStatusProvider
Wrap forms to provide submission status to descendant useFormStatus() calls.
CSS-in-JS
css
Generate scoped className from CSS string. Returns hashed class name. Deduplicates styles.
styled
Create styled components. styled('div', cssString) or styled('button', props => cssString).
globalStyles
Inject global CSS. Returns cleanup function. Useful for resets and base styles.
keyframes
Define CSS @keyframes. Returns animation name. Use with css() or styled().
injectCSSReset
Inject a production CSS reset. Modes: "modern" (Josh Comeau-style) or "normalize".
import { h, css, styled, keyframes, globalStyles } from '@hyperbridge/forge/client';
globalStyles(`body { margin: 0; font-family: system-ui; }`);
const fadeIn = keyframes(`
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
`);
const cardClass = css(`
padding: 24px;
border-radius: 12px;
background: #1a1a2e;
animation: ${fadeIn} 0.3s ease-out;
`);
const Button = styled('button', `
padding: 10px 20px;
border: none;
border-radius: 6px;
background: #6366f1;
color: white;
cursor: pointer;
&:hover { background: #4f46e5; }
`);
function Card() {
return h('div', { className: cardClass },
h(Button, { onClick: () => alert('Clicked') }, 'Click Me')
);
}
Styles created with css() are automatically deduped. The same template literal always returns the same className, and the corresponding <style> tag is injected only once. This means you can call css() inside render functions without worrying about style bloat -- the framework fingerprints each rule and reuses existing injections.
Animation Hooks
useSpring
Physics-based spring animation hook. Returns [animatedValue, api]. api.start(), api.stop().
useAnimation
Keyframe animation hook. Define from/to states with easing and duration.
Animated.*
Animated component wrappers. Animated.div, Animated.span, etc. Accept spring/animation values.
usePresence
Track mount/unmount state for exit animations. Returns [isPresent, safeToRemove].
PresenceGroup
Animate children entering and leaving. Keeps exiting children in DOM until animation completes.
useAnimatedMount
Simple mount/unmount animation. Returns { style, mounted }. Handles enter and exit transitions.
import { h, useSpring, Animated } from '@hyperbridge/forge/client';
function BouncyCard() {
const [style, api] = useSpring({ scale: 1, opacity: 1 });
return h(Animated.div, {
style,
onMouseEnter: () => api.start({ scale: 1.05 }),
onMouseLeave: () => api.start({ scale: 1 }),
}, 'Hover me!');
}
Transitions
Transition
Animate a single element entering/leaving. CSS class-based transitions with configurable durations.
TransitionGroup
Manage transitions for a list of elements. Handles enter, update, and exit animations.
Batching & Concurrent
batch
Explicitly batch multiple state updates into a single re-render.
flushSync
Force synchronous flush of pending state updates. Useful for DOM measurements.
useBatchedState
Like useState but auto-batches all updates within a microtask.
enableAutoBatching
Enable automatic microtask-based batching globally. All setState calls are coalesced.
disableAutoBatching
Disable auto-batching. Each setState triggers its own render cycle.
startTransition
Mark updates as low-priority. UI stays responsive while transition processes in background.
useTransitionState
Track transition state. Returns isPending boolean for showing loading indicators.
use() Hook
use
React 19-style hook. use(promise) suspends until resolved. use(context) reads context value. Works in conditionals and loops.
import { h, use, Suspense } from '@hyperbridge/forge/client';
const dataPromise = fetch('/api/data').then(r => r.json());
function DataView() {
const data = use(dataPromise); // suspends until resolved
return h('pre', null, JSON.stringify(data, null, 2));
}
function App() {
return h(Suspense, { fallback: h('p', null, 'Loading...') },
h(DataView)
);
}
SSR & Server Components
renderToString
Render component tree to HTML string. Synchronous. For traditional SSR.
renderToReadableStream
Streaming SSR via ReadableStream. Progressive HTML delivery with Suspense boundaries.
selectiveHydrate
Hydrate specific subtrees with priority scheduling. Prioritize visible/interactive regions.
createServerComponent
Define server-only components. Fetches data on server, sends serialized props to client.
Error Boundaries
createErrorBoundary
Basic error boundary. Catches render errors, shows fallback UI.
createEnhancedErrorBoundary
Advanced boundary with retry logic. Configurable maxRetries, onError callback.
createRecoverableErrorBoundary
Auto-recovery with exponential backoff. Retries rendering with increasing delays.
import { h, createRecoverableErrorBoundary } from '@hyperbridge/forge/client';
const SafeZone = createRecoverableErrorBoundary({
maxRetries: 3,
fallback: (error, retry) => h('div', null,
h('p', null, `Error: ${error.message}`),
h('button', { onClick: retry }, 'Retry')
),
onError: (error) => console.error('Boundary caught:', error),
});
function App() {
return h(SafeZone, null, h(RiskyComponent));
}
Events & EventBus
EventBus
Global pub/sub event system. new EventBus() creates isolated bus. on(), off(), emit().
eventBus
Default shared EventBus instance. Use for app-wide cross-component communication.
useEvent
Subscribe to EventBus events inside a component. Auto-unsubscribes on unmount.
useEmit
Returns a stable emit function bound to the EventBus. No re-render on emit.
Internationalization (i18n)
createI18n
Create i18n instance with translations and locale. Supports nested keys and interpolation.
I18nProvider
Provide i18n context to component tree. All descendants can access translations.
useI18n
Access translation function t() and locale management. t('key', { params }).
Head & Meta Management
Head
Manage document head. Set title, meta tags, link tags. Supports nesting (last wins).
useCanonical
Set canonical URL meta tag. Useful for SEO deduplication.
useStructuredData
Inject JSON-LD structured data into head. Pass a schema.org-compatible object.
useOpenGraph
Set Open Graph meta tags (og:title, og:image, etc.) for social sharing.
useTwitterCard
Set Twitter Card meta tags (twitter:card, twitter:title, twitter:image).
Theme & Accessibility
createTheme
Define a theme with CSS custom properties. Returns theme object for ThemeProvider.
ThemeProvider
Apply theme to component subtree. Injects CSS variables on the container element.
useTheme
Access current theme values and toggle function inside a component.
useFocusTrap
Trap keyboard focus within a container. Essential for modals and dialogs. Returns ref.
useAnnouncer
Screen reader announcements via ARIA live region. announce(message, priority).
SkipLink
Accessibility skip-to-content link. Visually hidden until focused.
useReducedMotion
Detect prefers-reduced-motion media query. Returns boolean. Respect user preferences.
import { h, createTheme, ThemeProvider, useTheme } from '@hyperbridge/forge/client';
const lightTheme = createTheme({
bg: '#ffffff', text: '#1a202c', primary: '#6366f1',
});
const darkTheme = createTheme({
bg: '#0a0a0f', text: '#f1f5f9', primary: '#818cf8',
});
function ThemeToggle() {
const { theme, setTheme } = useTheme();
return h('button', {
onClick: () => setTheme(theme === lightTheme ? darkTheme : lightTheme),
}, 'Toggle Theme');
}
function App() {
return h(ThemeProvider, { theme: darkTheme }, h(ThemeToggle));
}
Fetch Client
createFetchClient
Create configured fetch wrapper. Supports baseURL, interceptors, retry, timeout, and middleware.
useFetch
Hook wrapper around fetch client. Returns { data, loading, error, refetch }.
import { createFetchClient } from '@hyperbridge/forge/client';
const api = createFetchClient({
baseURL: 'https://api.example.com',
timeout: 5000,
retry: 3,
interceptors: {
request: (config) => {
config.headers.Authorization = `Bearer ${getToken()}`;
return config;
},
response: (response) => {
if (response.status === 401) refreshToken();
return response;
},
},
});
const users = await api.get('/users');
await api.post('/users', { name: 'Alice' });
Multi-Tab Sync & Web Vitals
useSyncedState
State synchronized across browser tabs via BroadcastChannel. Atomic updates with conflict resolution.
reportWebVitals
Collect Core Web Vitals: LCP, FID, CLS, TTFB, INP. Calls callback with metric objects.
useWebVitals
Hook to access web vitals inside a component. Returns live metrics object.
import { h, useSyncedState, reportWebVitals } from '@hyperbridge/forge/client';
// Sync cart state across tabs
function CartIcon() {
const [cartCount, setCartCount] = useSyncedState('cart-count', 0);
// Updates in one tab instantly reflect in all other tabs
return h('span', null, `Cart (${cartCount})`);
}
// Report metrics to analytics
reportWebVitals((metric) => {
console.log(metric.name, metric.value); // "LCP" 1234
navigator.sendBeacon('/analytics', JSON.stringify(metric));
});
Utility Hooks
useDebounce
Debounce a value. Updates after delay of inactivity. Great for search inputs.
useThrottle
Throttle a value. Updates at most once per interval.
useLocalStorage
Persist state to localStorage. Returns [value, setValue]. Syncs across tabs.
useSessionStorage
Persist state to sessionStorage. Returns [value, setValue]. Cleared on tab close.
useMediaQuery
React to CSS media query changes. Returns boolean match state.
useIntersectionObserver
Observe element visibility via IntersectionObserver. Returns { ref, isIntersecting, entry }.
useEventListener
Attach event listeners declaratively. Auto-cleans up. Supports window, document, or element ref.
useOnClickOutside
Detect clicks outside a referenced element. Useful for dropdown/modal dismiss.
useWindowSize
Track window dimensions. Returns { width, height }. Debounced for performance.
useClipboard
Copy text to clipboard. Returns { copy, copied, error }.
useOnline
Track network status. Returns boolean. Updates on online/offline events.
useDocumentTitle
Set document.title reactively. Restores previous title on unmount.
useHover
Track hover state on an element. Returns [ref, isHovered].
useKeyPress
Detect specific key presses. useKeyPress('Enter') returns boolean.
usePermission
Query browser Permissions API. Returns state: "granted", "denied", or "prompt".
useIdle
Detect user inactivity. Returns isIdle boolean after configurable timeout.
useScrollPosition
Track scroll position. Returns { x, y }. Throttled for performance.
useLongPress
Detect long press/touch gestures. Returns event handlers { onMouseDown, onTouchStart, ... }.
import { h, useLocalStorage, useMediaQuery, useOnClickOutside, useRef }
from '@hyperbridge/forge/client';
function Dropdown() {
const ref = useRef(null);
const [open, setOpen] = useLocalStorage('dropdown-open', false);
const isMobile = useMediaQuery('(max-width: 768px)');
useOnClickOutside(ref, () => setOpen(false));
return h('div', { ref },
h('button', { onClick: () => setOpen(o => !o) },
isMobile ? 'Menu' : 'Options'
),
open && h('ul', { className: 'dropdown' },
h('li', null, 'Profile'),
h('li', null, 'Settings'),
h('li', null, 'Logout')
)
);
}
Advanced APIs
memo
Memoize a component. Skips re-render when props are shallowly equal.
forwardRef
Forward a ref to a child DOM node. Enables parent to access child's DOM element.
cloneElement
Clone a virtual element with merged props. Useful for HOC patterns.
Children
Utilities for working with children: Children.map, Children.forEach, Children.count, Children.toArray.
lazy
Code-split components. lazy(() => import('./Heavy')) loads on first render with Suspense.
Profiler
Measure render timing. Profiler({ id, onRender }) reports phase, duration, and commit time.
StrictMode
Development checks. Double-invokes effects and renders to catch side-effect bugs.
VirtualList
Windowed list rendering. Only renders visible items. Supports variable item heights.
Slot / SlotProvider
Named content slots. Define slot regions in layout, fill them from child components.
memoDeep
Deep equality memoization. Compares props by value, not reference.
useShallowMemo
Memoize with shallow comparison. Useful for objects/arrays as dependencies.
createSelector
Reselect-style memoized selector. Derived values only recompute when inputs change.
DevTools
enableDevOverlay
Show development overlay with component tree, render counts, and performance metrics.
disableDevOverlay
Hide the development overlay.
getProfilerData
Retrieve collected profiler metrics. Returns render counts, durations, and commit log.
Form Actions
useFormState
Manage form state with server-validated feedback. Returns [state, formAction] where state updates after the server action completes.
useFormAction
Create a form action bound to a server endpoint. Automatically handles serialization, pending state, and error recovery.
ProgressiveForm
Progressive enhancement form component. Works without JavaScript, upgrades to async submission with optimistic UI when JS is available.
import { h, useFormState, useFormAction, ProgressiveForm } from '@hyperbridge/forge/client';
function CreatePost() {
const [state, formAction] = useFormState('/api/posts', { error: null, success: false });
const { pending } = useFormAction(formAction);
return h(ProgressiveForm, { action: formAction },
h('input', { name: 'title', placeholder: 'Post title', required: true }),
h('textarea', { name: 'body', placeholder: 'Write your post...' }),
h('button', { type: 'submit', disabled: pending }, pending ? 'Saving...' : 'Publish'),
state.error && h('p', { class: 'error' }, state.error),
state.success && h('p', { class: 'success' }, 'Post created!')
);
}
Hydration Mismatch Detection
hydrateWithMismatchDetection
Like hydrate() but compares server HTML against client VDOM. Logs detailed diffs of mismatched attributes, text, and tree structure in development.
suppressHydrationWarning
Prop to suppress mismatch warnings on a per-element basis. Use for intentional differences like timestamps or randomized content.
configureHydration
Global configuration for hydration behavior. Options: strict (throw on mismatch), warn (default), silent, and onMismatch callback.
import { h, hydrateWithMismatchDetection, configureHydration } from '@hyperbridge/forge/client';
configureHydration({
mode: 'strict', // 'strict' | 'warn' | 'silent'
onMismatch(path, expected, actual) {
reportToMonitoring({ path, expected, actual });
}
});
function App() {
return h('div', null,
h('h1', null, 'My App'),
h('span', { suppressHydrationWarning: true }, new Date().toLocaleTimeString())
);
}
hydrateWithMismatchDetection(h(App), document.getElementById('root'));
Enhanced Server Components
createServerAction
Define a server-side action callable from client components. Handles serialization, CSRF, and streaming response automatically.
ServerBoundary
Mark a subtree as server-rendered. Children are streamed as HTML and hydrated on the client. Supports fallback UI during loading.
registerClientComponent
Explicitly register a component for client-side rendering within a server component tree. Creates a hydration island with its own state.
import { h, createServerAction, ServerBoundary, registerClientComponent } from '@hyperbridge/forge/client';
// Server action — runs on the server, callable from client
const saveUser = createServerAction(async (formData) => {
const user = await db.users.update(formData.id, {
name: formData.name,
email: formData.email
});
return { success: true, user };
});
// Client island inside a server-rendered page
const LikeButton = registerClientComponent(function LikeButton({ postId }) {
const [liked, setLiked] = useState(false);
return h('button', { onClick: () => setLiked(!liked) }, liked ? 'Liked' : 'Like');
});
function UserProfile({ userId }) {
return h(ServerBoundary, { fallback: h('p', null, 'Loading profile...') },
h('div', { class: 'profile' },
h('h2', null, 'User Profile'),
h(LikeButton, { postId: userId })
)
);
}
Auto-Memoization
enableAutoMemo
Enable automatic memoization for all function components. The framework wraps each component in a shallow-equality memo check, eliminating unnecessary re-renders.
disableAutoMemo
Disable automatic memoization globally. Useful for debugging or when manual control over memo behavior is preferred.
AutoMemoProvider
Scoped auto-memoization provider. Wraps a subtree to enable or disable auto-memo independently of the global setting.
import { h, render, enableAutoMemo, disableAutoMemo, AutoMemoProvider } from '@hyperbridge/forge/client';
// Enable globally — all components are auto-memoized
enableAutoMemo();
function ExpensiveList({ items }) {
return h('ul', null, items.map(item =>
h('li', { key: item.id }, item.name)
));
}
function App() {
return h('div', null,
h(ExpensiveList, { items: data }), // auto-memoized
h(AutoMemoProvider, { enabled: false },
h(AlwaysRerenderWidget, null) // opts out of auto-memo
)
);
}
render(h(App), document.getElementById('root'));
Enhanced useOptimistic
useOptimisticAction
Combine optimistic state with a server action. Immediately applies the optimistic update, then reconciles when the server responds or rolls back on error.
useOptimisticMutation
Optimistic mutations for list data. Supports add, update, and remove operations with automatic rollback and conflict resolution.
import { h, useOptimisticAction, useOptimisticMutation } from '@hyperbridge/forge/client';
function TodoList() {
const [todos, addTodo] = useOptimisticMutation('/api/todos', {
add(current, newTodo) {
return [...current, { ...newTodo, id: 'temp-' + Date.now(), pending: true }];
},
rollback(current, failedTodo) {
return current.filter(t => t.id !== failedTodo.id);
}
});
const [likeState, likeAction] = useOptimisticAction(
'/api/like',
(prev, postId) => ({ ...prev, [postId]: (prev[postId] || 0) + 1 })
);
return h('div', null,
h('button', { onClick: () => addTodo({ title: 'New task' }) }, 'Add Todo'),
h('ul', null, todos.map(todo =>
h('li', { key: todo.id, class: todo.pending ? 'pending' : '' }, todo.title)
))
);
}
Test Utilities
renderForTest
Render a component into a test container. Returns a cleanup function and the container element. Automatically runs inside an act() scope.
screen
Query helpers for the test DOM: screen.getByText, screen.getByRole, screen.queryByTestId, screen.findByLabelText (async).
fireEvent
Dispatch DOM events in tests. fireEvent.click(el), fireEvent.change(el, { target: { value } }), fireEvent.submit(form).
waitForUpdate
Async utility that resolves after the next render cycle completes. Useful for testing state transitions and async effects.
act
Wrap test actions that trigger state updates. Ensures all effects and re-renders complete before assertions.
import { h } from '@hyperbridge/forge/client';
import { renderForTest, screen, fireEvent, waitForUpdate, act } from '@hyperbridge/forge/client/test';
function Counter() {
const [count, setCount] = useState(0);
return h('div', null,
h('span', { 'data-testid': 'count' }, String(count)),
h('button', { onClick: () => setCount(c => c + 1) }, 'Increment')
);
}
// Test
const { cleanup } = renderForTest(h(Counter));
expect(screen.getByTestId('count').textContent).toBe('0');
await act(async () => {
fireEvent.click(screen.getByText('Increment'));
await waitForUpdate();
});
expect(screen.getByTestId('count').textContent).toBe('1');
cleanup();
Hot Module Replacement
createHMRClient
Initialize the HMR WebSocket client. Connects to the dev server and listens for module update events.
hot.accept
Accept hot updates for the current module or specific dependencies. Callback receives the updated module for custom re-rendering logic.
hot.dispose
Register a cleanup function that runs before the module is replaced. Use to tear down subscriptions, timers, or side effects.
preserveStateOnHMR
HOC that preserves component state across hot reloads. Wraps a component to persist its useState and useReducer values during development.
import { h, render, preserveStateOnHMR } from '@hyperbridge/forge/client';
import { createHMRClient } from '@hyperbridge/forge/client/hmr';
const hmr = createHMRClient({ port: 3001 });
const App = preserveStateOnHMR('App', function App() {
const [count, setCount] = useState(0);
return h('div', null,
h('p', null, `Count: ${count}`),
h('button', { onClick: () => setCount(c => c + 1) }, '+1')
);
});
render(h(App), document.getElementById('root'));
// Accept hot updates and re-render
if (hmr.hot) {
hmr.hot.accept('./App', (updatedModule) => {
render(h(updatedModule.default), document.getElementById('root'));
});
hmr.hot.dispose(() => {
console.log('Cleaning up before hot reload...');
});
}
forge/server
Full-featured HTTP framework — radix-trie router, 20+ built-in middleware, WebSocket (RFC 6455), GraphQL engine, cron scheduler, task queue, JWT auth, circuit breaker, LRU cache, HTTP client, PubSub, plugin system, DI container, OpenTelemetry tracing, Prometheus metrics, HTTP/2, cluster mode, and more. 120+ APIs, zero dependencies, 11,177 lines.
forge/server uses a radix-trie router for O(k) path matching (k = path length), significantly faster than Express's linear middleware scan. With 500 registered routes, Express checks up to 500 regex patterns per request; forge/server walks the trie in at most ~5 node hops. Combined with zero-copy body parsing and pre-compiled middleware chains, this typically yields 2-5x higher throughput than Express on the same hardware.
| Option | Type | Description |
|---|---|---|
| port | number | Port to listen on. Default: 3000. Overridden by PORT env var. |
| host | string | Bind address. Default: '0.0.0.0'. Use '127.0.0.1' for local-only. |
| trustProxy | boolean | number | Trust X-Forwarded-* headers. true trusts one hop; a number sets the hop count. |
| maxBodySize | string | Maximum request body size. Default: '1mb'. Accepts '10kb', '5mb', etc. |
| cluster | number | boolean | Number of worker processes. true = CPU count. Only used with Forge.cluster(). |
1. Core
Forge
Main application class. Extends EventEmitter. Owns router, middleware stack, cron, task queue, health checks, and shutdown hooks.
app.listen(port, host?, cb?)
Start an HTTP server. Auto-wires WebSocket, cron, graceful shutdown, and GraphQL if configured.
app.listenTLS(port, tlsOpts, host?, cb?)
Start an HTTPS server with the given TLS cert and key options.
app.use(...middleware)
Register global middleware. Accepts optional path prefix: app.use('/api', cors()).
app.set(key, value) / app.get(key)
Application settings store. When get is called with a non-path string, returns the setting value.
app.onShutdown(fn)
Register an async cleanup hook. Runs on SIGTERM/SIGINT before the process exits.
app.onError(handler)
Custom error handler: (err, req, res) => { ... }. Receives ForgeError instances.
app.close(cb?)
Stop the server and cron scheduler. Runs onClose plugin hooks.
Forge.cluster(workerFn, opts?)
Static method. Forks workers using Node cluster. Auto-restarts crashed workers. opts.workers defaults to CPU count.
ForgeError
Custom error class with status, message, and optional details. Serializes to JSON via toJSON().
import { Forge } from '@hyperbridge/forge/server';
const app = new Forge();
app.set('env', 'production');
app.onShutdown(async () => {
await db.close();
console.log('Cleanup complete');
});
app.get('/api/status', (req, res) => {
res.json({ status: 'ok', uptime: process.uptime() });
});
app.listen(3000, () => console.log('Running'));
// Cluster mode — spawns one worker per CPU core
Forge.cluster(() => {
const app = new Forge();
app.get('/', (req, res) => res.json({ pid: process.pid }));
app.listen(3000);
}, { workers: 4 });
2. Router
Router(prefix?)
Radix-trie based router. Supports static, parameterized (:id), optional (:id?), and wildcard (*) segments.
HTTP Methods
app.get, app.post, app.put, app.delete, app.patch, app.options, app.head, app.all — register route handlers directly on Forge or Router.
Route Params & Wildcards
/users/:id populates req.params.id. Wildcard /files/* captures the rest in req.params['*'].
Named Routes
Pass { name: 'getUser' } as option. Generate URLs with router.url('getUser', { id: 42 }).
app.group(prefix, fn)
Route groups with shared prefix and middleware. Supports nested groups. Methods: get, post, put, delete, patch, all, use.
app.version(ver, routerOrFn)
API versioning via URL prefix (/v2/...) or Accept header (application/vnd.api.v2+json).
Per-Route Middleware
Stack middleware before the handler: app.get('/admin', authMw, adminMw, handler).
405 Method Not Allowed
Auto-detected when a path matches but the HTTP method does not. Returns Allow header with valid methods.
const app = new Forge();
// Route params
app.get('/users/:id', (req, res) => {
res.json({ userId: req.params.id });
});
// Wildcard catch-all
app.get('/files/*', (req, res) => {
res.json({ path: req.params['*'] });
});
// Route groups with shared auth middleware
app.group('/api/v1', (g) => {
g.use(authMiddleware);
g.get('/users', listUsers);
g.post('/users', createUser);
g.group('/admin', (admin) => {
admin.get('/stats', getStats);
});
});
app.listen(3000);
3. Middleware (21+)
bodyParser.json(opts?)
Parse JSON request bodies. opts.limit (default '1mb'). Sets req.body.
bodyParser.urlencoded(opts?)
Parse URL-encoded form bodies. Sets req.body.
bodyParser.raw(opts?)
Collect raw request body as a Buffer. opts.limit default '5mb'.
bodyParser.multipart(opts?)
Parse multipart/form-data. Sets req.body (fields) and req.files (uploaded files).
cors(opts?)
CORS headers. origin (string, function, or '*'), methods, allowedHeaders, credentials, maxAge. Auto-handles OPTIONS preflight.
helmet(opts?)
Security headers: X-Content-Type-Options, X-Frame-Options, HSTS, Referrer-Policy, Permissions-Policy. Optional csp for Content-Security-Policy.
logger(opts?)
Colored request logger with method, path, status, duration, size, and request ID. opts.level: error, warn, info, debug.
rateLimit(opts?)
Sliding-window rate limiter. windowMs, max, keyGenerator, skip. Sets X-RateLimit-* and Retry-After headers.
rateLimitAdvanced(opts?)
Four strategies: fixed-window, sliding-window, token-bucket, leaky-bucket. keyBy: 'ip' | 'user' | function.
compression(opts?)
Gzip/deflate response compression. opts.threshold (default 1024 bytes) — responses below this size are not compressed.
serveStatic(rootDir, opts?)
Serve static files with ETag, 304 caching, range requests, directory index, and dotfile protection. opts.maxAge, opts.index.
session(opts?)
Cookie-based sessions with in-memory store. opts.secret, opts.maxAge, opts.name. Auto-cleanup of expired sessions.
upload(opts?)
File upload middleware. opts.maxSize, opts.types (allowed MIME types), opts.dest. Populates req.files.
proxy(target, opts?)
Reverse proxy. pathRewrite, changeOrigin, xfwd (X-Forwarded-*), timeout. Streams request/response bodies.
wsProxy(target, opts?)
WebSocket reverse proxy. Tunnels upgrade requests to a target WebSocket server.
healthProbe(checkFn?)
Responds on GET /health with status, uptime, and timestamp. Optional async check function.
readyProbe(checkFn?)
Responds on GET /ready. Returns 200 if ready, 503 if not. For Kubernetes readiness probes.
correlationId(opts?)
Extracts or generates req.id / req.correlationId from X-Request-Id header. Propagates to response.
signedCookies(secret)
HMAC-SHA256 signed cookie verification. Populates req.signedCookies. Tampered cookies are silently rejected.
etag(opts?)
Auto-generate ETag headers (SHA-256). Supports If-None-Match and If-Modified-Since for 304 responses. opts.weak for weak ETags.
timeout(ms)
Request timeout middleware. Returns 408 if handler exceeds ms. Exposes req.timedOut() check.
decompress(opts?)
Decompress incoming request bodies (gzip, deflate, brotli). Place before body parsers.
cacheMiddleware(opts?)
LRU-backed GET response cache with ETag and X-Cache HIT/MISS headers. opts.max, opts.ttl.
apiVersion(version, router)
Mount versioned routes via URL prefix or Accept header versioning.
import { Forge, bodyParser, cors, helmet, logger, rateLimit,
compression, serveStatic, timeout } from '@hyperbridge/forge/server';
const app = new Forge();
// Middleware stack
app.use(logger({ level: 'info' }));
app.use(cors({ origin: 'https://myapp.com', credentials: true }));
app.use(helmet({ csp: "default-src 'self'" }));
app.use(compression({ threshold: 512 }));
app.use(rateLimit({ windowMs: 60000, max: 100 }));
app.use(timeout(30000));
app.use(bodyParser.json({ limit: '2mb' }));
app.use(serveStatic('./public', { maxAge: 86400 }));
app.get('/api/data', (req, res) => res.json({ ok: true }));
app.listen(3000);
rateLimit Options
| Option | Type | Description |
|---|---|---|
| windowMs | number | Time window in milliseconds. Default: 60000 (1 minute). |
| max | number | Max requests per window per key. Default: 100. |
| message | string | object | Response body when limit is exceeded. Default: { error: 'Too many requests' }. |
| keyGenerator | (req) => string | Function that returns the rate-limit key. Default: req.ip. |
| algorithm | string | Rate limiting strategy: 'sliding-window' (default), 'fixed-window', 'token-bucket', 'leaky-bucket'. |
| skip | (req) => boolean | Return true to bypass rate limiting for this request (e.g. health checks). |
4. WebSocket
WebSocketServer
Full RFC 6455 WebSocket server. Extends EventEmitter. Auto-wires on upgrade event. Handles text/binary frames, ping/pong, and close handshake.
ws.broadcast(data, exclude?)
Send a message to all connected clients. Optionally exclude a specific client.
ws.to(room).emit(data)
Send a message to all clients in a named room.
ws.join(client, room) / ws.leave(client, room)
Room management. Add or remove a WebSocket client from a named room.
WebSocketClient
Per-connection wrapper. Properties: id (UUID), readyState, req. Methods: send(data), close(). Events: message, close, error.
import { Forge, WebSocketServer } from '@hyperbridge/forge/server';
const app = new Forge();
app.ws((wss) => {
wss.on('connection', (client, req) => {
console.log(`Client ${client.id} connected`);
wss.join(client, 'lobby');
client.on('message', (data) => {
// Broadcast to room
wss.to('lobby').emit({ type: 'chat', ...data });
});
client.on('close', () => {
wss.leave(client, 'lobby');
});
});
});
app.listen(3000);
WebSocket connections are per-worker in cluster mode. A client connected to worker 2 will not receive messages broadcast from worker 1. Use the built-in PubSub adapter to broadcast across all workers -- it coordinates via the primary process's IPC channel, so messages fan out to every worker's connected clients automatically.
5. GraphQL Engine
GraphQLEngine
Built-in GraphQL engine. Parses SDL schemas, supports Query, Mutation, enums, scalars, input types, aliases, variables, and introspection.
app.graphql(schema, resolvers, opts?)
Register a GraphQL endpoint on the app. Default path: /graphql. Supports both GET and POST.
engine.middleware(endpoint?)
Standalone GraphQL middleware for use with app.use().
engine.subscription(name, { subscribe, resolve? })
Register a subscription resolver with async generator. Supports graphql-ws protocol over WebSocket.
Fragment Support
Use fragment UserFields on User { ... } and spread with ...UserFields in queries.
@deprecated Directive
Mark fields as deprecated in the schema: oldField: String @deprecated(reason: "Use newField"). Reported in introspection.
DataLoader
Batch and cache data fetches to prevent N+1 queries. Methods: load(key), loadMany(keys), clear(key), clearAll(), prime(key, value).
import { Forge, DataLoader } from '@hyperbridge/forge/server';
const app = new Forge();
const schema = `
type User { id: ID!, name: String!, email: String }
type Query {
user(id: ID!): User
users: [User!]!
}
type Mutation {
createUser(name: String!, email: String!): User!
}
`;
const userLoader = new DataLoader(async (ids) => {
const users = await db.findByIds(ids);
return ids.map(id => users.find(u => u.id === id) || null);
});
app.graphql(schema, {
Query: {
user: (_, { id }) => userLoader.load(id),
users: () => db.allUsers(),
},
Mutation: {
createUser: (_, { name, email }) => db.createUser({ name, email }),
},
});
app.listen(3000); // GraphQL at POST /graphql
6. CronScheduler
CronScheduler
Built-in cron scheduler. Parses 5-field cron expressions (min, hour, day, month, weekday). Supports ranges, steps, and lists.
cron.schedule(name, expression, fn)
Schedule a named job. Concurrent execution is prevented — running jobs are skipped.
cron.cancel(name) / cron.cancelAll()
Cancel a specific job or all scheduled jobs.
app.cron(name, expression, fn)
Shortcut to schedule a cron job on the Forge app instance.
import { Forge } from '@hyperbridge/forge/server';
const app = new Forge();
// Run every day at 2:00 AM
app.cron('cleanup', '0 2 * * *', async () => {
await db.cleanup();
console.log('Daily maintenance complete');
});
// Run every 5 minutes
app.cron('sync', '*/5 * * * *', async () => {
await syncExternalData();
});
app.listen(3000);
7. TaskQueue
TaskQueue(opts?)
Async task queue with configurable concurrency. opts.concurrency (default 5). Extends EventEmitter.
queue.register(name, handler)
Register a named task handler function.
queue.add(name, data, opts?)
Enqueue a task. Options: priority, delay (ms), maxRetries (default 3). Returns the task object with UUID.
queue.persist(filepath) / queue.restore()
Enable file-based persistence. Restore re-queues pending and retryable jobs on restart.
queue.history(limit?) / queue.stats()
View completed/failed history (ring buffer, max 1000). Stats: pending, running, completed, failed, total.
Events
added, processing, completed, failed, retry — listen for task lifecycle events.
import { TaskQueue } from '@hyperbridge/forge/server';
const queue = new TaskQueue({ concurrency: 10 });
queue.persist('./data/jobs.json').restore();
queue.register('sendEmail', async (data) => {
await emailService.send(data.to, data.subject, data.body);
});
queue.on('failed', (task, err) => {
console.error(`Task ${task.id} failed: ${err.message}`);
});
await queue.add('sendEmail', {
to: 'user@example.com',
subject: 'Welcome',
body: 'Hello!'
}, { priority: 10, maxRetries: 5 });
8. JWT
JWT(secret, opts?)
JWT signing and verification. opts.algorithm (HS256), opts.expiresIn (seconds, default 3600).
jwt.sign(payload, opts?)
Create a signed token. Auto-adds iat, exp, jti. Optional issuer, audience.
jwt.verify(token)
Verify and decode a token. Checks signature (timing-safe) and expiration. Throws ForgeError on failure.
jwt.middleware(opts?)
Express-style middleware. Extracts Bearer token from Authorization header, sets req.user. opts.optional skips auth if no token.
import { Forge, JWT } from '@hyperbridge/forge/server';
const jwt = new JWT(process.env.JWT_SECRET, { expiresIn: 7200 });
const app = new Forge();
app.post('/auth/login', async (req, res) => {
const user = await authenticate(req.body);
const token = jwt.sign({ id: user.id, role: user.role });
res.json({ token });
});
// Protected route
app.get('/api/profile', jwt.middleware(), (req, res) => {
res.json({ user: req.user });
});
app.listen(3000);
9. Validator & Schema
Validator.body(schema) / .query(schema) / .params(schema)
Middleware factories. Schema is a plain object: { field: { required, type, min, max, pattern, enum, email, custom } }.
v.string()
Zod-like string schema. Chain: min, max, email, url, regex, nonempty, trim, toLowerCase, toUpperCase, includes, startsWith, endsWith, length, enum.
v.number()
Number schema. Chain: min, max, int, positive, negative, nonnegative, multipleOf, finite, enum.
v.boolean() / v.enum(values)
Boolean coercion (accepts "true"/"false" strings). Enum validates against a fixed set of values.
v.array(itemSchema?) / v.object(shape)
Array with optional item validation. Object validates nested shape recursively. Chain: min, max, nonempty, length.
schema.optional() / .default(val) / .transform(fn) / .label(name)
Common modifiers on all schema types. transform mutates the parsed value. label sets the field name in error messages.
objectSchema.bodyParser()
Middleware factory from an ObjectSchema. Validates req.body and replaces it with the parsed, transformed result.
import { Forge, v } from '@hyperbridge/forge/server';
const app = new Forge();
const createUserSchema = v.object({
name: v.string().min(2).max(100),
email: v.string().email(),
age: v.number().int().min(18).optional(),
role: v.enum(['admin', 'user']).default('user'),
});
app.post('/users', createUserSchema.bodyParser(), (req, res) => {
// req.body is validated and transformed
res.status(201).json(req.body);
});
app.listen(3000);
10. CircuitBreaker
CircuitBreaker(fn, opts?)
Wraps an async function. States: closed (normal), open (failing), half-open (probing). opts.threshold (5), opts.timeout (10s), opts.resetTimeout (30s).
breaker.fire(...args)
Call the protected function. Throws ForgeError(503) when open. Transitions to half-open after reset timeout.
breaker.state / breaker.getStats() / breaker.reset()
Read current state, get failure/success counts, or manually reset to closed.
Events
success, failure, open, halfOpen, close, rejected — monitor circuit state transitions.
import { CircuitBreaker } from '@hyperbridge/forge/server';
const breaker = new CircuitBreaker(
(url) => fetch(url).then(r => r.json()),
{ threshold: 3, timeout: 5000, resetTimeout: 15000 }
);
breaker.on('open', () => console.warn('Circuit opened!'));
try {
const data = await breaker.fire('https://api.example.com/data');
} catch (err) {
// Fallback logic when circuit is open
}
11. LRU Cache
LRUCache(opts?)
Doubly-linked-list LRU cache. opts.max (1000), opts.ttl (ms, 0 = no expiry). O(1) get/set/delete.
cache.get(key) / cache.set(key, value)
Get promotes to head. Set evicts the least-recently-used item when capacity is exceeded.
cache.has(key) / cache.delete(key) / cache.clear()
Check existence, remove a key, or clear all entries. TTL-expired entries return false/undefined.
cache.size / cache.keys() / cache.values()
Current entry count, list all keys, or list all values.
import { LRUCache } from '@hyperbridge/forge/server';
const cache = new LRUCache({ max: 500, ttl: 60000 });
cache.set('user:42', { id: 42, name: 'Alice' });
const user = cache.get('user:42'); // { id: 42, name: 'Alice' }
console.log(cache.size); // 1
12. PubSub
PubSub
In-process publish/subscribe with topic strings and wildcard pattern matching (e.g. user.*).
pubsub.subscribe(topic, fn)
Subscribe to a topic or pattern. Returns an unsubscribe function. Handler receives (data, topic).
pubsub.publish(topic, data)
Publish data to all matching subscribers. Returns the number of handlers invoked.
pubsub.unsubscribe(topic, fn) / pubsub.clear() / pubsub.topics()
Remove a handler, clear all subscriptions, or list active topics.
import { PubSub } from '@hyperbridge/forge/server';
const pubsub = new PubSub();
// Wildcard pattern
pubsub.subscribe('order.*', (data, topic) => {
console.log(`Event ${topic}:`, data);
});
pubsub.publish('order.created', { id: 1, total: 99.99 }); // matches
pubsub.publish('order.shipped', { id: 1 }); // matches
13. HTTP Client
httpClient.request(url, opts?)
Full-featured HTTP client. Auto-serializes JSON body, auto-parses JSON responses, follows redirects (max 5), basic auth, timeout (30s).
httpClient.get / .post / .put / .patch / .delete
Shorthand methods. post(url, body, opts?) etc. Returns { status, statusText, headers, data, raw }.
import { httpClient } from '@hyperbridge/forge/server';
const { data } = await httpClient.get('https://api.example.com/users', {
headers: { Authorization: 'Bearer ...' },
timeout: 5000,
});
await httpClient.post('https://api.example.com/users', {
name: 'Alice', email: 'alice@example.com'
});
14. Plugin System
app.register(plugin, opts?)
Register a plugin. Plugin is { name, dependencies?, register(scope, opts) } or a plain function. Supports dependency resolution.
PluginScope
Encapsulated scope passed to the register function. Delegates all HTTP methods, use(), group() to the parent app. Adds scoped hooks.
scope.addHook(event, handler)
Hook events: onRequest, onResponse, onError, onClose. Hooks are merged into the global lifecycle.
scope.decorate(key, value)
Add a named property to the app instance. Throws if the key already exists.
scope.decorateRequest(key, value) / scope.decorateReply(key, value)
Extend request or response objects with custom properties. Applied automatically on each request.
app.hasPlugin(name) / app.listPlugins()
Check if a plugin is registered or list all registered plugins with their dependencies.
import { Forge } from '@hyperbridge/forge/server';
const authPlugin = {
name: 'auth',
async register(app, opts) {
app.decorate('authenticate', (req) => { /* ... */ });
app.addHook('onRequest', async (req, res) => {
req.user = await app.authenticate(req);
});
app.get('/auth/me', (req, res) => res.json(req.user));
}
};
const app = new Forge();
await app.register(authPlugin, { secret: 'my-secret' });
app.listen(3000);
Plugins are encapsulated by default. A plugin's decorators and hooks don't leak to sibling plugins -- they only affect the plugin's own scope and its children. This means two plugins can safely decorate req with the same key name without conflicting. If you need a decorator to be globally visible, register it on the root app instance instead of the plugin scope.
15. Dependency Injection
createContainer()
Create a DI container. Supports singleton, transient, and scoped lifetimes.
container.register(name, factory, opts?)
Register a service factory. opts.lifetime: 'singleton' (default), 'transient', 'scoped'. opts.deps for explicit dependency list.
container.registerValue(name, value)
Register a constant value (always singleton). Immediately cached.
container.resolve(name)
Resolve a service. Auto-injects dependencies by introspecting factory parameter names. Detects circular dependencies.
container.createScope()
Create a child scope. Scoped services are fresh within the child; singletons are shared with the parent.
container.middleware()
Middleware that creates a scoped container per request at req.container.
import { createContainer, Forge } from '@hyperbridge/forge/server';
const container = createContainer();
container.register('logger', () => new Logger(), { lifetime: 'singleton' });
container.register('userService', (logger) => new UserService(logger));
container.register('requestCtx', () => new RequestContext(), { lifetime: 'scoped' });
const app = new Forge();
app.use(container.middleware());
app.get('/users', (req, res) => {
const svc = req.container.resolve('userService');
res.json(svc.findAll());
});
app.listen(3000);
16. OpenTelemetry Tracing
createTracer(name, opts?)
Create a Tracer instance. opts.maxSpans (10000), opts.sampler (function returning true/false).
tracer.startSpan(name, opts?)
Create a new span. Inherits parent from AsyncLocalStorage context. opts.parentSpanId, opts.attributes.
tracer.trace(name, fn, opts?)
Start a span, run an async function within its context, auto-end and set status. Records exceptions on throw.
span.setAttribute / .setAttributes / .addEvent / .recordException / .end()
Full OpenTelemetry Span API. Duration computed from high-resolution timers. Status codes: 0=UNSET, 1=OK, 2=ERROR.
tracer.middleware()
Auto-trace HTTP requests. Extracts W3C traceparent header for distributed context propagation. Sets req._traceSpan.
tracer.export() / tracer.flush() / tracer.addExporter(fn)
Export spans as JSON, flush and clear, or add a real-time exporter callback.
import { createTracer, Forge } from '@hyperbridge/forge/server';
const tracer = createTracer('my-service');
const app = new Forge();
app.use(tracer.middleware());
app.get('/api/data', async (req, res) => {
const result = await tracer.trace('fetchData', async (span) => {
span.setAttribute('db.system', 'postgres');
const data = await db.query('SELECT * FROM items');
span.addEvent('query-complete', { rowCount: data.length });
return data;
});
res.json(result);
});
app.listen(3000);
17. Prometheus Metrics
createMetrics(opts?)
Create a MetricsRegistry. opts.prefix, opts.defaultLabels, opts.collectDefaults (process CPU, memory, Node version).
metrics.counter({ name, help, labels? })
Prometheus Counter. Methods: inc(labels?, value?), get(labels?), reset().
metrics.gauge({ name, help, labels? })
Prometheus Gauge. Methods: set(labels, value), inc(labels?, value?), dec(labels?, value?), get(labels?).
metrics.histogram({ name, help, buckets? })
Prometheus Histogram with configurable buckets. observe(labels, value), startTimer(labels?) returns an end function.
metrics.middleware()
Auto-tracks http_request_duration_seconds, http_requests_total, and http_requests_in_flight with method/route/status labels.
metrics.endpoint(path?) / metrics.collect()
Expose GET /metrics in Prometheus text format. collect() returns the raw text output.
import { createMetrics, Forge } from '@hyperbridge/forge/server';
const metrics = createMetrics({ prefix: 'myapp_' });
const app = new Forge();
app.use(metrics.middleware());
app.use(metrics.endpoint('/metrics'));
const orderCounter = metrics.counter({
name: 'orders_total',
help: 'Total orders placed',
});
app.post('/orders', (req, res) => {
orderCounter.inc({ status: 'created' });
res.status(201).json({ ok: true });
});
app.listen(3000); // Prometheus scrapes GET /metrics
18. Webhook Verification
verifyWebhook(opts)
Middleware for HMAC signature verification. opts.secret, opts.algorithm ('sha256'|'sha512'), opts.header, opts.tolerance (seconds for replay protection).
signWebhook(payload, secret, opts?)
Generate a webhook signature for testing or sending. Returns { signature, timestamp, body }.
Custom Parsing
Pass opts.parse to handle custom header formats (e.g. Stripe's t=...,v1=... format). req.webhookVerified is set on success.
import { Forge, verifyWebhook } from '@hyperbridge/forge/server';
const app = new Forge();
app.post('/webhooks/stripe',
verifyWebhook({
secret: process.env.STRIPE_SECRET,
header: 'stripe-signature',
tolerance: 300,
parse: (header) => {
const parts = {};
header.split(',').forEach(p => {
const [k, val] = p.split('=');
parts[k] = val;
});
return { timestamp: parts.t, signature: parts.v1 };
},
}),
(req, res) => {
console.log('Verified webhook:', req.body);
res.json({ received: true });
}
);
app.listen(3000);
19. Enhanced Sessions
sessionEnhanced(opts?)
Enhanced session middleware with pluggable stores, session rotation (opts.rotateInterval), flash messages, and req.session.regenerate() / destroy().
MemorySessionStore
Default in-memory store with auto-cleanup of expired sessions. Interface: get, set, destroy, touch, clear.
FileSessionStore(opts?)
File-based persistence. opts.dir, opts.cleanupInterval. Each session is a separate JSON file.
RedisSessionStore(opts?)
Redis-protocol TCP client. opts.host, opts.port, opts.password, opts.db, opts.prefix. Uses RESP protocol directly — no Redis library needed.
Flash Messages
req.flash('error', 'Bad input') to set; req.flash('error') to read and clear. Enabled by default.
import { Forge, sessionEnhanced, createRedisSessionStore }
from '@hyperbridge/forge/server';
const app = new Forge();
app.use(sessionEnhanced({
secret: 'session-secret',
maxAge: 86400,
store: createRedisSessionStore({ host: '127.0.0.1', port: 6379 }),
rotateInterval: 3600,
}));
app.post('/login', async (req, res) => {
req.session.userId = user.id;
await req.session.regenerate(); // new session ID, same data
res.json({ ok: true });
});
app.get('/dashboard', (req, res) => {
const error = req.flash('error');
res.json({ userId: req.session.userId, flashError: error });
});
20. Backpressure
backpressure(opts?)
Limits concurrent requests with overflow queueing. maxConcurrent (100), maxQueue (500), queueTimeout (10s). Returns 503 when overloaded.
opts.strategy
Queue strategy: 'fifo' (default), 'lifo', or 'priority'. Use opts.getPriority(req) for priority mode.
middleware.stats()
Live snapshot: { active, queued, maxConcurrent, maxQueue, totalProcessed, totalShed, totalQueued, totalTimedOut }.
import { Forge, backpressure } from '@hyperbridge/forge/server';
const bp = backpressure({
maxConcurrent: 50,
maxQueue: 200,
queueTimeout: 5000,
strategy: 'priority',
getPriority: (req) => req.headers['x-priority'] === 'high' ? 10 : 0,
});
const app = new Forge();
app.use(bp);
app.get('/stats', (req, res) => res.json(bp.stats()));
app.listen(3000);
21. Config Management
createConfig(schema)
Define a typed config schema. Each field specifies type, default, env (env var name), required, enum, min, max, pattern, validate.
config.load(opts?)
Load from multiple sources in priority order: env vars > env-specific JSON > JSON file > .env file > defaults. Validates against schema.
config.get(keyPath) / config.set(keyPath, value) / config.has(keyPath)
Dot-notation access: config.get('db.host'). Supports nested objects.
config.env / config.isProduction / config.isDevelopment / config.isTest
Environment helpers. config.env reads NODE_ENV.
import { createConfig } from '@hyperbridge/forge/server';
const config = createConfig({
port: { type: 'number', default: 3000, env: 'PORT' },
db: {
host: { type: 'string', default: 'localhost', env: 'DB_HOST' },
port: { type: 'number', default: 5432, env: 'DB_PORT' },
name: { type: 'string', required: true, env: 'DB_NAME' },
},
logLevel: { type: 'string', enum: ['debug','info','warn','error'], default: 'info' },
});
config.load({ envFile: '.env', jsonFile: 'config.json' });
console.log(config.get('db.host')); // 'localhost' or DB_HOST value
console.log(config.isProduction); // true if NODE_ENV=production
22. Streaming JSON
res.streamJSON(asyncIterable, opts?)
Stream an async iterable as NDJSON (Newline Delimited JSON). Handles backpressure via drain events. Content-Type: application/x-ndjson.
res.jsonLines(items, opts?)
Send an array as JSON Lines (one JSON object per line). Non-streaming, sets Content-Length.
res.sseStream(opts?)
Enhanced SSE with auto-incrementing IDs, typed events, reconnect (retry), last-event-id support, and keepAlive. Methods: event(type, data, id?), data(payload, id?), comment(text), keepAlive(), close().
res.sse()
Basic SSE helper. Returns { send(data, event?, id?), retry(ms), comment(text), close() }.
import { Forge } from '@hyperbridge/forge/server';
const app = new Forge();
// NDJSON streaming from async generator
app.get('/stream', async (req, res) => {
async function* generate() {
for (let i = 0; i < 100; i++) {
yield { id: i, timestamp: Date.now() };
}
}
await res.streamJSON(generate());
});
// Server-Sent Events with typed events
app.get('/events', (req, res) => {
const sse = res.sseStream({ retry: 5000, keepAlive: 15000 });
const interval = setInterval(() => {
sse.event('tick', { time: new Date().toISOString() });
}, 1000);
req.on('close', () => {
clearInterval(interval);
sse.close();
});
});
app.listen(3000);
23. Request/Response Interceptors
app.addHook(event, handler)
Fastify-style lifecycle hooks. Events: onRequest, preParsing, preValidation, preHandler, preSerialization, onResponse, onError, onClose, onRoute, onReady.
preSerialization
Transform response data before JSON serialization. Hook receives (req, res, data) and returns the modified payload.
Hook Execution Order
preParsing → preValidation → preHandler → [route handler] → preSerialization → onResponse. Hooks can short-circuit by ending the response.
import { Forge } from '@hyperbridge/forge/server';
const app = new Forge();
// Log every request before parsing
app.addHook('preParsing', async (req, res) => {
console.log(`Incoming: ${req.method} ${req.url}`);
});
// Transform all JSON responses
app.addHook('preSerialization', async (req, res, data) => {
return { ...data, timestamp: Date.now(), requestId: req.id };
});
app.get('/api/users', (req, res) => {
res.json({ users: [] }); // { users: [], timestamp: ..., requestId: ... }
});
app.listen(3000);
24. HTTP/2
createHttp2Server(app, opts?)
Create an HTTP/2 server with ALPN negotiation. opts.key, opts.cert, opts.allowHTTP1 (default true). Supports maxConcurrentStreams, initialWindowSize, maxFrameSize.
res.push(path, opts?)
HTTP/2 Server Push. Push assets to the client before they request them. opts.contentType, opts.body or opts.file, opts.maxAge.
Http2Request / Http2Response
Compatibility wrappers that map HTTP/2 streams to the standard Node.js req/res interface. Full API parity with HTTP/1.1 handlers.
import { Forge, createHttp2Server } from '@hyperbridge/forge/server';
import fs from 'fs';
const app = new Forge();
app.get('/', (req, res) => {
// Push CSS and JS before the client requests them
if (res.push) {
res.push('/styles.css', { file: './public/styles.css', contentType: 'text/css' });
res.push('/app.js', { file: './public/app.js', contentType: 'application/javascript' });
}
res.html('<html><head><link rel="stylesheet" href="/styles.css"></head>...</html>');
});
const server = createHttp2Server(app, {
key: fs.readFileSync('key.pem'),
cert: fs.readFileSync('cert.pem'),
});
server.listen(3000);
25. OpenAPI
generateOpenAPI(app, info?)
Auto-generate an OpenAPI 3.0.3 spec from registered routes. Extracts path params, request body schemas from validators, and named route metadata.
app.openapi(info?)
Shortcut on Forge instance. info: { title, version, description, contact, license, servers }.
import { Forge, generateOpenAPI } from '@hyperbridge/forge/server';
const app = new Forge();
app.get('/users/:id', (req, res) => { /* ... */ });
app.post('/users', (req, res) => { /* ... */ });
const spec = app.openapi({
title: 'My API',
version: '2.0.0',
description: 'User management API',
});
app.get('/docs/openapi.json', (req, res) => res.json(spec));
app.listen(3000);
26. HTTPS & ACME
app.listenTLS(port, tlsOpts, host?, cb?)
Start an HTTPS server with pre-existing cert and key files.
createHttpsServer(app, opts)
ACME/Let's Encrypt helper. opts.domain, opts.email, opts.certDir, opts.staging. Serves HTTP-01 challenges on port 80, auto-renews certificates.
import { Forge, createHttpsServer } from '@hyperbridge/forge/server';
const app = new Forge();
app.get('/', (req, res) => res.json({ secure: true }));
// Auto-obtain TLS cert via Let's Encrypt
const server = await createHttpsServer(app, {
domain: 'api.example.com',
email: 'admin@example.com',
certDir: './.forge-certs',
port: 443,
});
27. Cluster
Forge.cluster(workerFn, opts?)
Multi-process cluster mode using Node.js cluster module. Forks opts.workers (default: CPU count). Auto-restarts crashed workers.
import { Forge } from '@hyperbridge/forge/server';
Forge.cluster(() => {
const app = new Forge();
app.get('/', (req, res) => {
res.json({ worker: process.pid });
});
app.onShutdown(async () => {
await db.close();
});
app.listen(3000);
}, { workers: 8 });
Response Helpers
res.json(data) / res.html(str) / res.text(str) / res.xml(str)
Send typed responses with correct Content-Type headers.
res.send(body)
Auto-detect type: objects become JSON, numbers become status codes, buffers become binary, strings become HTML.
res.status(code)
Chainable status setter. res.status(201).json({ created: true }).
res.redirect(url, code?) / res.noContent()
HTTP redirect (default 302). noContent() sends 204 with empty body.
res.sendFile(path, opts?) / res.download(path, filename?)
Stream a file with ETag, range requests, and proper MIME type. download adds Content-Disposition attachment header.
res.cookie(name, value, opts?) / res.clearCookie(name)
Set/clear cookies. Options: maxAge, expires, path, domain, httpOnly, secure, sameSite.
res.set(key, value) / res.stream(readable, opts?)
Chainable header setter. stream pipes a ReadableStream with backpressure handling.
res.jsonp(data)
JSONP response using req.query.callback. Falls back to res.json if no callback.
Request Properties
req.params / req.query / req.body / req.files
Route parameters, query string, parsed body, and uploaded files.
req.id / req.correlationId
Auto-generated UUID or propagated from X-Request-Id header.
req.ip / req.hostname / req.protocol / req.baseUrl / req.fullUrl
Proxy-aware IP detection (X-Forwarded-For), hostname, protocol, and full URL reconstruction.
req.cookies / req.signedCookies / req.session
Parsed cookies, verified signed cookies, and session data (when middleware is active).
req.is(type) / req.accepts(type) / req.get(header)
Content-type check, Accept header check, and case-insensitive header getter.
Type-Safe RPC
createRPCRouter
Create a type-safe RPC router with automatic input validation, serialization, and error handling. Mount on any Forge app with app.use('/rpc', router).
procedure.input().query()
Define a read-only RPC procedure. Chain .input(schema) for validation and .query(resolver) for the handler. Accessed via GET.
procedure.input().mutation()
Define a write RPC procedure. Chain .input(schema) for validation and .mutation(resolver) for the handler. Accessed via POST.
mergeRouters
Combine multiple RPC routers into one. Namespaces are preserved, and procedures are merged with conflict detection.
Batch Support
Multiple RPC calls in a single HTTP request. The client batches calls automatically and the server resolves them in parallel.
import { Forge, createRPCRouter, procedure, mergeRouters } from '@hyperbridge/forge/server';
const userRouter = createRPCRouter({
getUser: procedure
.input({ id: 'string' })
.query(async ({ input, ctx }) => {
return ctx.db.users.findById(input.id);
}),
updateUser: procedure
.input({ id: 'string', name: 'string', email: 'string?' })
.mutation(async ({ input, ctx }) => {
return ctx.db.users.update(input.id, input);
})
});
const postRouter = createRPCRouter({
list: procedure.query(async ({ ctx }) => ctx.db.posts.findAll())
});
const appRouter = mergeRouters({ user: userRouter, post: postRouter });
const app = new Forge();
app.use('/rpc', appRouter); // POST /rpc/user.getUser, /rpc/post.list, etc.
app.listen(3000);
AI/LLM Streaming
res.streamTokens
Stream tokens from an LLM response using Server-Sent Events. OpenAI-compatible format with data: {"choices":[...]} lines. Handles backpressure and auto-sends [DONE].
res.streamChunks
Stream arbitrary chunks as SSE events. Supports custom event types, retry intervals, and ID-based resumption for reconnecting clients.
import { Forge } from '@hyperbridge/forge/server';
const app = new Forge();
// Stream LLM tokens — OpenAI-compatible SSE
app.post('/api/chat', async (req, res) => {
const { messages } = req.body;
const llmStream = await callLLM({ messages, stream: true });
res.streamTokens(async function* () {
for await (const chunk of llmStream) {
yield { content: chunk.text, role: 'assistant' };
}
});
});
// Stream arbitrary data chunks
app.get('/api/feed', (req, res) => {
res.streamChunks(async function* () {
yield { event: 'status', data: { connected: true } };
for await (const update of liveUpdates()) {
yield { event: 'update', data: update, id: update.id };
}
}, { retry: 3000 });
});
app.listen(3000);
Content Negotiation
negotiate
Middleware that parses the Accept header and sets req.negotiated with the best matching content type, encoding, and language.
res.format
Respond with different representations based on the Accept header. Provide a map of content-type handlers. Falls back to 406 Not Acceptable.
res.vary
Append a Vary header field. Used by caches to determine when a response can be reused. Automatically deduplicates values.
import { Forge, negotiate } from '@hyperbridge/forge/server';
const app = new Forge();
app.use(negotiate());
app.get('/api/users/:id', async (req, res) => {
const user = await db.users.findById(req.params.id);
res.vary('Accept');
res.format({
'application/json': () => res.json(user),
'text/html': () => res.html(`<h1>${user.name}</h1>`),
'text/csv': () => res.text(`${user.id},${user.name},${user.email}`),
default: () => res.status(406).json({ error: 'Not Acceptable' })
});
});
app.listen(3000);
Streaming Uploads
streamUpload
Middleware for streaming file uploads without buffering the entire file in memory. Processes multipart data as a readable stream. Supports size limits, allowed MIME types, and progress callbacks.
import { Forge, streamUpload } from '@hyperbridge/forge/server';
import { createWriteStream } from 'fs';
const app = new Forge();
app.post('/api/upload', streamUpload({
maxSize: '500mb',
allowedTypes: ['video/*', 'image/*'],
onProgress(bytes, total) { console.log(`${Math.round(bytes/total*100)}%`); }
}), async (req, res) => {
// req.file is a readable stream — pipe directly to storage
const dest = createWriteStream(`./uploads/${req.file.filename}`);
await req.file.stream.pipe(dest);
res.status(201).json({
filename: req.file.filename,
size: req.file.size,
type: req.file.mimetype
});
});
app.listen(3000);
Test Client
app.inject()
Create an in-process test client without starting a real server. Simulates HTTP requests through the full middleware and routing stack.
.get() / .post() / .put() / .delete()
Chainable request builders. Set headers, body, and query params: app.inject().post('/api/users').send({ name: 'Alice' }).
.expect(status) / .expect(header, value)
Assertion helpers on the response. Chain multiple expects. Throws descriptive errors on mismatch.
.end()
Execute the request and return a promise resolving to the full response object with status, headers, body, and text.
import { Forge } from '@hyperbridge/forge/server';
const app = new Forge();
app.get('/api/health', (req, res) => res.json({ status: 'ok' }));
app.post('/api/users', (req, res) => {
res.status(201).json({ id: 1, name: req.body.name });
});
// Test without starting a server
const res = await app.inject()
.get('/api/health')
.expect(200)
.expect('content-type', /json/)
.end();
console.assert(res.body.status === 'ok');
// Test POST with body
const created = await app.inject()
.post('/api/users')
.set('Authorization', 'Bearer test-token')
.send({ name: 'Alice' })
.expect(201)
.end();
console.assert(created.body.name === 'Alice');
Dev Mode
app.enableDevMode
Enable development mode with enhanced error pages, stack traces, request logging, and auto-reload on file changes. Automatically disabled in production.
devLogger
Colorized development logger middleware. Logs method, path, status code, response time, and payload size. Supports filtering by path or status.
import { Forge, devLogger } from '@hyperbridge/forge/server';
const app = new Forge();
if (process.env.NODE_ENV !== 'production') {
app.enableDevMode({
errorOverlay: true, // rich HTML error pages with stack traces
watchDirs: ['./src'], // auto-restart on file changes
openBrowser: true // open browser on first listen
});
app.use(devLogger({
filter: (req) => !req.path.startsWith('/health'),
colorize: true,
showBody: true // log request/response bodies
}));
}
app.get('/', (req, res) => res.json({ hello: 'world' }));
app.listen(3000);
// DevLogger output: GET / 200 1.2ms 27B
OpenAPI 3.1
generateOpenAPI31
Auto-generate an OpenAPI 3.1 specification from your Forge routes, including path params, query schemas, request bodies, and response types.
swaggerUI
Serve an interactive Swagger UI explorer at a given path. Powered by the generated OpenAPI spec. Supports try-it-out and authentication.
app.swagger
Convenience method to mount both the JSON spec endpoint and the Swagger UI in one call. Configurable path prefix, title, and version.
import { Forge, generateOpenAPI31, swaggerUI } from '@hyperbridge/forge/server';
const app = new Forge();
app.get('/api/users', { summary: 'List users', tags: ['Users'] }, (req, res) => {
res.json([{ id: 1, name: 'Alice' }]);
});
app.post('/api/users', {
summary: 'Create user',
tags: ['Users'],
body: { name: 'string', email: 'string' },
response: { 201: { id: 'number', name: 'string' } }
}, (req, res) => {
res.status(201).json({ id: 2, name: req.body.name });
});
// Mount spec + UI
app.swagger({
path: '/docs',
info: { title: 'My API', version: '2.0.0' }
});
// Or manually:
// const spec = generateOpenAPI31(app, { info: { title: 'My API', version: '2.0.0' } });
// app.get('/openapi.json', (req, res) => res.json(spec));
// app.use('/docs', swaggerUI({ specUrl: '/openapi.json' }));
app.listen(3000);
// Swagger UI at http://localhost:3000/docs
Enhanced Errors
EnhancedForgeError
Extended error class with code, status, details, cause chain, and serialization. Supports toJSON() and toString() with full context.
Error Factories
Pre-built factory functions: notFound(), badRequest(), unauthorized(), forbidden(), conflict(), tooManyRequests(), internalError(). Accept message and details.
Typed Error Handlers
Register error handlers by error code or class. app.onError('VALIDATION', handler) catches only validation errors. Supports fallback and wildcard handlers.
import { Forge, EnhancedForgeError, notFound, badRequest, unauthorized } from '@hyperbridge/forge/server';
const app = new Forge();
// Typed error handlers — catch specific error codes
app.onError('VALIDATION', (err, req, res) => {
res.status(400).json({
code: err.code,
message: err.message,
fields: err.details.fields // per-field validation errors
});
});
app.onError('NOT_FOUND', (err, req, res) => {
res.status(404).json({ code: err.code, message: err.message });
});
// Throw enhanced errors from routes
app.get('/api/users/:id', async (req, res) => {
const user = await db.users.findById(req.params.id);
if (!user) throw notFound(`User ${req.params.id} not found`);
res.json(user);
});
app.post('/api/users', async (req, res) => {
if (!req.body.email) {
throw badRequest('Validation failed', {
fields: { email: 'Email is required' }
});
}
// EnhancedForgeError with cause chain
try {
const user = await db.users.create(req.body);
res.status(201).json(user);
} catch (dbErr) {
throw new EnhancedForgeError('DB_ERROR', 'Failed to create user', 500, { cause: dbErr });
}
});
app.listen(3000);
forge/auth
Complete authentication & authorization — Password, JWT, OAuth2, TOTP, WebAuthn, MagicLink, RBAC, Sessions, MFA, SAML, and audit trails. Zero external dependencies.
Never store JWT secrets in source code or commit them to version control. forge/auth reads process.env.JWT_SECRET by default. In production use a 256-bit (32-byte) random secret minimum — shorter secrets are vulnerable to brute-force. Generate with: node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"
Password
PBKDF2-SHA-256 with 310,000 iterations (OWASP 2023 minimum). No bcrypt dependency required.
| Method | Returns | Description |
|---|---|---|
Password.hash(plaintext, opts?) | Promise<string> | Hash a password. Opts: { iterations, saltLength, digest } |
Password.verify(plaintext, hash) | Promise<boolean> | Constant-time comparison to prevent timing attacks |
Password.needsRehash(hash) | boolean | Returns true if hash was created with fewer iterations than current config — signal to rehash on next login |
Password.generate(length?) | string | Generate a cryptographically random password. Default 16 chars. |
JWT
| Method | Description |
|---|---|
JWT.sign(payload, secret, opts?) | Sign payload. Opts: { expiresIn, issuer, audience, algorithm }. Algorithms: HS256/384/512, RS256, ES256. |
JWT.verify(token, secret, opts?) | Verify and decode token. Throws on expiry, bad signature, or claim mismatch. Opts: { issuer, audience, clockTolerance } |
JWT.decode(token) | Decode without verification (for reading claims only — do NOT use for auth) |
JWT.refresh(token, secret, opts?) | Re-sign token with new expiry without requiring re-login. Original claims preserved. |
JWT.isExpired(token) | Quick expiry check without full verification |
JWT.getExpiry(token) | Returns expiry Date or null |
OAuth2Provider
Built-in presets for Google, GitHub, Microsoft, GitLab, Discord, Facebook, LinkedIn. Pass 'custom' for any OAuth2-compliant provider.
| Method | Description |
|---|---|
getAuthUrl(state?, scope?) | Generate redirect URL. state is auto-generated (CSRF protection) if omitted. |
handleCallback(code, state?) | Exchange authorization code for access token. Validates state parameter. |
getUserInfo(token) | Fetch user profile from provider. Normalized: { id, email, name, avatar, raw } |
refreshToken(refreshToken) | Refresh expired access token |
revokeToken(token) | Revoke access or refresh token |
TOTP (2FA)
| Method | Description |
|---|---|
TOTP.generateSecret(account, issuer) | Returns { secret, qrDataUrl, otpauthUrl }. Show qrDataUrl as an <img> — user scans with Authenticator app. |
TOTP.verify(code, secret, opts?) | Validate 6-digit TOTP code. Opts: { window: 1 } — accepts codes ±1 time step (handles clock skew). |
TOTP.generate(secret) | Generate current TOTP code (for testing) |
BackupCodes
| Method | Description |
|---|---|
BackupCodes.generate(count?) | Generate N one-time backup codes (default 10). Returns { codes, hashes }. Store hashes, show codes once. |
BackupCodes.verify(inputCode, storedHashes) | Check if code is valid and unused. Returns index of matched hash or -1. |
BackupCodes.consume(index, hashes) | Returns new hashes array with the used code removed (immutable) |
RBAC
| Method | Description |
|---|---|
new RBAC(config) | Config: { roles: { roleName: { permissions: string[], inherits?: string[] } } } |
.can(role, permission) | Check if role has permission. Supports wildcards: 'posts:*', '*'. |
.cannot(role, permission) | Inverse of .can() |
.getRoles() | List all defined roles |
.getPermissions(role) | List all permissions for a role (including inherited) |
.addRole(name, permissions, inherits?) | Dynamically add a role at runtime |
MagicLink
| Method | Description |
|---|---|
new MagicLink(opts) | Opts: { secret, expiresIn, baseUrl }. baseUrl prefixes the verification URL. |
.generate(email, metadata?) | Returns { token, url, expiresAt }. Email the URL to the user. |
.verify(token) | Returns { email, metadata } or throws if expired/invalid |
.invalidate(token) | Revoke token before expiry (e.g., after use) |
SessionManager
| Method | Description |
|---|---|
new SessionManager(opts) | Opts: { store: 'memory'|'redis'|Store, ttl, maxSessions } |
.create(userId, data?) | Create session. Returns { sessionId, expiresAt } |
.get(sessionId) | Returns session data or null if expired/invalid |
.touch(sessionId) | Extend TTL (call on each authenticated request) |
.destroy(sessionId) | Log out one session |
.destroyAll(userId) | Log out all sessions for a user (e.g., after password change) |
.listSessions(userId) | List active sessions with device info and last-active time |
Example: Password + JWT Auth Middleware
import { Password, JWT } from '@hyperbridge/forge/auth';
// Registration
router.post('/auth/register', async (req, res) => {
const { email, password } = req.body;
const hash = await Password.hash(password);
const user = await db.users.create({ email, passwordHash: hash });
res.status(201).json({ id: user.id, email: user.email });
});
// Login
router.post('/auth/login', async (req, res) => {
const { email, password } = req.body;
const user = await db.users.findByEmail(email);
if (!user) return res.status(401).json({ error: 'Invalid credentials' });
const valid = await Password.verify(password, user.passwordHash);
if (!valid) return res.status(401).json({ error: 'Invalid credentials' });
// Rehash if iterations are outdated
if (Password.needsRehash(user.passwordHash)) {
const newHash = await Password.hash(password);
await db.users.update(user.id, { passwordHash: newHash });
}
const accessToken = JWT.sign({ sub: user.id, role: user.role }, process.env.JWT_SECRET, { expiresIn: '15m' });
const refreshToken = JWT.sign({ sub: user.id, type: 'refresh' }, process.env.JWT_SECRET, { expiresIn: '30d' });
res.json({ accessToken, refreshToken });
});
// Auth middleware
function requireAuth(req, res, next) {
const token = req.headers.authorization?.replace('Bearer ', '');
if (!token) return res.status(401).json({ error: 'Missing token' });
try {
req.user = JWT.verify(token, process.env.JWT_SECRET);
next();
} catch {
res.status(401).json({ error: 'Invalid or expired token' });
}
}
Example: TOTP Two-Factor Authentication
import { TOTP, BackupCodes, JWT } from '@hyperbridge/forge/auth';
// Step 1: Generate TOTP secret for user
router.post('/auth/2fa/setup', requireAuth, async (req, res) => {
const { secret, qrDataUrl, otpauthUrl } = await TOTP.generateSecret(
req.user.email, 'MyApp'
);
// Save secret temporarily (not activated until verified)
await db.users.update(req.user.sub, { totpSecretPending: secret });
res.json({ qrDataUrl, otpauthUrl }); // show qrDataUrl as <img>
});
// Step 2: Verify setup (user scans QR and enters first code)
router.post('/auth/2fa/verify-setup', requireAuth, async (req, res) => {
const { code } = req.body;
const user = await db.users.find(req.user.sub);
const valid = TOTP.verify(code, user.totpSecretPending);
if (!valid) return res.status(400).json({ error: 'Invalid code' });
// Generate backup codes
const { codes, hashes } = BackupCodes.generate(10);
await db.users.update(user.id, {
totpSecret: user.totpSecretPending,
totpSecretPending: null,
backupCodeHashes: hashes,
twoFactorEnabled: true,
});
res.json({ backupCodes: codes }); // show once, never again
});
// Step 3: Login flow
router.post('/auth/login/2fa', async (req, res) => {
const { tempToken, code } = req.body;
const { sub } = JWT.verify(tempToken, process.env.JWT_SECRET);
const user = await db.users.find(sub);
const validTotp = TOTP.verify(code, user.totpSecret);
const backupIndex = BackupCodes.verify(code, user.backupCodeHashes);
if (!validTotp && backupIndex === -1) {
return res.status(401).json({ error: 'Invalid 2FA code' });
}
// Consume backup code if used
if (backupIndex !== -1) {
const newHashes = BackupCodes.consume(backupIndex, user.backupCodeHashes);
await db.users.update(user.id, { backupCodeHashes: newHashes });
}
const accessToken = JWT.sign({ sub: user.id, role: user.role }, process.env.JWT_SECRET, { expiresIn: '15m' });
res.json({ accessToken });
});
Example: RBAC Middleware
import { RBAC, JWT } from '@hyperbridge/forge/auth';
const rbac = new RBAC({
roles: {
admin: { permissions: ['*'] },
editor: { permissions: ['posts:read', 'posts:write', 'media:upload'], inherits: ['viewer'] },
viewer: { permissions: ['posts:read', 'comments:read'] },
},
});
// Middleware factory
function can(permission) {
return (req, res, next) => {
if (!req.user?.role) return res.status(401).json({ error: 'Unauthenticated' });
if (!rbac.can(req.user.role, permission)) {
return res.status(403).json({ error: `Permission denied: ${permission}` });
}
next();
};
}
// Apply to routes
router.get('/posts', can('posts:read'), listPosts);
router.post('/posts', can('posts:write'), createPost);
router.delete('/posts/:id', can('posts:*'), deletePost); // wildcard
router.post('/admin/users', can('*'), adminAction); // admin only
Example: OAuth2 PKCE Flow
import { OAuth2PKCE } from '@hyperbridge/forge/auth';
const pkce = new OAuth2PKCE({
clientId: process.env.GITHUB_CLIENT_ID,
redirectUri: 'http://localhost:3000/auth/github/callback',
provider: 'github',
scopes: ['user:email', 'read:user'],
});
// 1. Start login — generates code_verifier + code_challenge
router.get('/auth/github', (req, res) => {
const { url, state, codeVerifier } = pkce.startFlow();
req.session.pkceState = state;
req.session.pkceCodeVerifier = codeVerifier;
res.redirect(url);
});
// 2. Handle callback — exchanges code + verifier for token
router.get('/auth/github/callback', async (req, res) => {
const { code, state } = req.query;
if (state !== req.session.pkceState) return res.status(400).send('State mismatch');
const token = await pkce.exchangeCode(code, req.session.pkceCodeVerifier);
const user = await pkce.getUserInfo(token);
// { id, email, name, avatar }
const jwt = JWT.sign({ sub: user.id, email: user.email }, process.env.JWT_SECRET, { expiresIn: '7d' });
res.redirect(`/dashboard?token=${jwt}`);
});
forge/data
PostgreSQL ORM — connection pooling, fluent query builder, full ORM with relations & hooks, schema builder, migrations, read replicas, query caching, seeding, and audit logging.
Always use parameterized queries. Never concatenate user input into SQL strings. All Model and QueryBuilder methods use parameterized queries automatically. For raw SQL, use placeholders: pool.query('SELECT * FROM users WHERE id = $1', [userId]).
PostgreSQL-only. forge/data is purpose-built for PostgreSQL 13+ and uses node:net / node:tls to speak the PostgreSQL wire protocol directly. No pg, libpq, or native bindings required.
Pool Configuration
| Option | Type | Default | Description |
|---|---|---|---|
host | string | 'localhost' | PostgreSQL server host |
port | number | 5432 | PostgreSQL server port |
database | string | — | Database name |
user | string | — | PostgreSQL username |
password | string | — | PostgreSQL password |
ssl | boolean | object | false | true for default TLS, or { ca, cert, key, rejectUnauthorized } |
max | number | 10 | Max connections in pool |
min | number | 2 | Min idle connections to maintain |
idleTimeout | number | 30000 | Release idle connections after ms |
connectionTimeout | number | 5000 | Fail if connection not acquired within ms |
statementTimeout | number | 0 | Cancel queries running longer than ms. 0 = no limit. |
QueryBuilder Methods
| Method | Description |
|---|---|
.select(...cols) | Columns to return. Default *. Supports raw: .select('COUNT(*) as total') |
.table(name) | Set/override the table name |
.where(col, op, val) | Add WHERE clause. Also accepts object: .where({ status: 'active' }) |
.orWhere(col, op, val) | Add OR WHERE clause |
.whereIn(col, values[]) | WHERE col IN ($1, $2, …) |
.whereNull(col) | WHERE col IS NULL |
.whereRaw(sql, bindings) | Raw SQL fragment with parameterized bindings |
.join(table, a, b) | INNER JOIN. Also: .leftJoin(), .rightJoin(), .fullJoin() |
.orderBy(col, dir?) | dir: 'asc' (default) or 'desc' |
.groupBy(...cols) | GROUP BY columns |
.having(col, op, val) | HAVING clause (used with GROUP BY) |
.limit(n) | LIMIT n rows |
.offset(n) | OFFSET n rows (for pagination) |
.get() | Execute SELECT, return row array |
.first() | Execute SELECT with LIMIT 1, return first row or null |
.count(col?) | Execute COUNT query, return number |
.insert(data) | INSERT row(s), return inserted row(s) |
.update(data) | UPDATE matching rows, return updated count |
.delete() | DELETE matching rows, return deleted count |
.toSQL() | Return { sql, bindings } without executing |
.paginate(page, perPage) | Returns { data, total, page, perPage, lastPage } |
Model Static Methods
| Method | Description |
|---|---|
Model.create(data) | INSERT and return instance |
Model.find(id) | Find by primary key |
Model.findOrFail(id) | Find by PK — throws ModelNotFoundError if missing |
Model.where(col, val) | Start a scoped QueryBuilder |
Model.scope(name) | Apply a named scope defined in static scopes |
Model.all() | SELECT all rows |
Model.count(col?) | COUNT query |
Model.truncate() | TRUNCATE TABLE (dangerous — dev/test only) |
Model Instance Methods
| Method | Description |
|---|---|
.save() | INSERT if new, UPDATE if existing (dirty checking) |
.update(data) | UPDATE specific fields. Merges into current state. |
.delete() | DELETE this row |
.fresh() | Re-fetch from DB, return new instance with latest data |
.refresh() | Re-fetch and mutate this instance in place |
.toJSON() | Serialize to plain object (respects hidden field list) |
.isDirty(field?) | Check if any (or specific) field has changed since load |
.load(relation) | Lazy-load a relation: await post.load('comments') |
Example: Full ORM Setup
import { Pool, Model } from '@hyperbridge/forge/data';
const pool = new Pool({
host: process.env.DB_HOST,
database: process.env.DB_NAME,
user: process.env.DB_USER,
password: process.env.DB_PASS,
ssl: true,
max: 20,
});
class User extends Model {
static table = 'users';
static pool = pool;
static fillable = ['email', 'name', 'role'];
static hidden = ['passwordHash']; // excluded from toJSON()
static casts = { createdAt: 'date', settings: 'json' };
// Named scopes
static scopes = {
active: qb => qb.where('active', true),
admins: qb => qb.where('role', 'admin'),
recent: qb => qb.orderBy('created_at', 'desc').limit(20),
};
// Lifecycle hooks
static beforeCreate = async (data) => ({
...data,
createdAt: new Date(),
});
static afterCreate = async (user) => {
await EmailQueue.enqueue({ to: user.email, template: 'welcome' });
};
// Relations
static relations = {
posts: { type: 'hasMany', model: () => Post, foreignKey: 'user_id' },
profile: { type: 'hasOne', model: () => Profile, foreignKey: 'user_id' },
roles: { type: 'belongsToMany', model: () => Role, pivot: 'user_roles' },
};
}
// CRUD
const user = await User.create({ email: 'a@b.com', name: 'Alex', role: 'editor' });
const found = await User.findOrFail(user.id);
await found.update({ name: 'Alexandra' });
// Scoped query
const admins = await User.scope('active').scope('admins').orderBy('name').get();
// Eager-load relations
const posts = await user.load('posts');
// Pagination
const page = await User.scope('active').paginate(1, 20);
// { data: User[], total: 154, page: 1, perPage: 20, lastPage: 8 }
Example: Migrations
import { Pool, Migrator, SchemaBuilder } from '@hyperbridge/forge/data';
const migrator = new Migrator(pool, {
directory: './db/migrations',
tableName: 'schema_migrations',
});
// Create a migration file
await migrator.create('create_posts_table');
// → ./db/migrations/20260414120000_create_posts_table.js
// Migration file content (generated template):
export const up = async (sb) => {
sb.createTable('posts', (t) => {
t.serial('id').primaryKey();
t.string('title', 255).notNull();
t.text('body').notNull();
t.string('status', 20).defaultValue('draft');
t.integer('user_id').references('users.id').onDelete('CASCADE');
t.boolean('featured').defaultValue(false);
t.jsonb('metadata').defaultValue('{}');
t.timestamps(); // created_at, updated_at
t.index(['status', 'user_id']);
});
};
export const down = async (sb) => {
sb.dropTable('posts');
};
// Run migrations
await migrator.latest(); // run pending
await migrator.rollback(); // revert last batch
await migrator.rollback(3); // revert last 3 batches
const status = await migrator.status(); // list all with run/pending
Example: Transactions with Savepoints
import { Pool } from '@hyperbridge/forge/data';
// Simple transaction
await pool.transaction(async (trx) => {
const user = await trx.table('users').insert({ email: 'x@y.com' }).returning('*').first();
await trx.table('profiles').insert({ userId: user.id, bio: 'Hello' });
// auto-commit on return, auto-rollback on throw
});
// Nested savepoints
await pool.transaction(async (trx) => {
await trx.table('orders').insert({ userId: 1, total: 500 });
await trx.savepoint('sp1', async () => {
await trx.table('inventory').update({ stock: 0 }, { where: { sku: 'ABC' } });
// Only this savepoint rolls back on failure, outer transaction continues
});
await trx.table('audit_log').insert({ action: 'order_placed', userId: 1 });
});
Example: Read Replica + Query Cache
import { Pool, ReadReplica, QueryCache } from '@hyperbridge/forge/data';
// Primary (writes) + replicas (reads)
const primary = new Pool({ host: 'primary.db.internal', /* ... */ });
const replica1 = new Pool({ host: 'replica1.db.internal', /* ... */ });
const replica2 = new Pool({ host: 'replica2.db.internal', /* ... */ });
const replica = new ReadReplica(primary, [replica1, replica2], {
strategy: 'round-robin', // or 'random', 'least-connections'
});
// Writes go to primary, reads auto-routed to replicas
await replica.query('INSERT INTO events VALUES ($1)', [data]); // → primary
const rows = await replica.query('SELECT * FROM events'); // → replica
// Query result cache (Redis or in-memory)
const cache = new QueryCache(replica, {
store: 'memory', // or { type: 'redis', url: process.env.REDIS_URL }
default: 60_000, // 60s default TTL
});
const hot = await cache.query(
'SELECT * FROM featured_products ORDER BY rank LIMIT 20',
[],
{ ttl: 300_000 } // 5-minute TTL for this query
);
forge/form
Headless form management — async & cross-field validation, schema binding, file upload, field arrays, multi-step forms, conditional fields, and accessibility helpers.
Schema-first. fromSchema(zodSchema) infers field types, default values, and validation rules from a forge/schema definition — eliminating duplicate validation logic between your form and API layer. The same schema validates on submit and on the server.
Headless. forge/form manages state and validation only — it has no opinion on your HTML or CSS. Wire it to any UI framework (forge/client, React, Svelte, or vanilla JS) by binding form.values, form.errors, and form.touched.
Form Constructor Options
| Option | Type | Default | Description |
|---|---|---|---|
initialValues | object | {} | Initial field values. Supports nested objects and arrays. |
schema | ZodSchema | null | forge/schema object for automatic validation on change and submit |
validate | fn(values) → errors | null | Custom synchronous validator. Return an errors object. Runs alongside schema. |
validateAsync | async fn(values) → errors | null | Async validator (e.g., check email uniqueness). Debounced automatically. |
validateOn | string | 'submit' | When to validate: 'submit', 'change', 'blur' |
onSubmit | async fn(data) | null | Submit handler. Called only when validation passes. |
resetOnSubmit | boolean | false | Reset to initialValues after successful submit |
Form State
| Property | Type | Description |
|---|---|---|
form.values | object | Current field values (deeply reactive) |
form.errors | object | Field-level error messages. { email: 'Invalid email' } |
form.touched | object | Which fields have been blurred. { email: true } |
form.dirty | boolean | True if any field differs from initialValues |
form.isSubmitting | boolean | True during async submit handler execution |
form.isValid | boolean | True if errors object is empty |
form.submitCount | number | Number of submit attempts (use for showing all errors after first attempt) |
Form Methods
| Method | Description |
|---|---|
.setField(path, value) | Set a field value by dot-path: form.setField('address.city', 'Chennai') |
.setValues(data) | Batch-update multiple fields |
.setError(path, msg) | Manually set an error (e.g., from server response) |
.setErrors(errors) | Batch-set errors from server validation response |
.touch(path) | Mark field as touched (shows validation errors for that field) |
.touchAll() | Mark all fields touched (use before showing all errors) |
.validate() | Run validation. Returns { valid, data, errors } |
.submit() | Validate then call onSubmit if valid |
.reset(values?) | Reset to initialValues (or custom values if provided) |
.watch(path, fn) | Subscribe to field changes: form.watch('country', val => updateStates(val)) |
.getFieldProps(path) | Returns { value, onChange, onBlur, name } spread onto <input> |
Example: Registration Form with Schema
import { Form, fromSchema } from '@hyperbridge/forge/form';
import { z } from '@hyperbridge/forge/schema';
const RegisterSchema = z.object({
email: z.string().email('Invalid email'),
password: z.string().min(8, 'At least 8 characters'),
confirmPassword: z.string(),
name: z.string().min(1, 'Name is required'),
role: z.enum(['admin', 'editor', 'viewer']).default('viewer'),
acceptTerms: z.literal(true, { errorMap: () => ({ message: 'You must accept the terms' }) }),
}).refine(d => d.password === d.confirmPassword, {
message: 'Passwords do not match',
path: ['confirmPassword'],
});
// fromSchema infers initial values and wires validation automatically
const form = fromSchema(RegisterSchema, {
validateOn: 'blur',
onSubmit: async (data) => {
const res = await fetch('/api/register', { method: 'POST', body: JSON.stringify(data) });
if (!res.ok) {
const { errors } = await res.json();
form.setErrors(errors); // map server errors back to fields
}
},
});
// Wire to HTML (vanilla JS example)
document.querySelector('#register-form').addEventListener('submit', async (e) => {
e.preventDefault();
form.touchAll(); // show all validation errors
await form.submit();
});
document.querySelectorAll('input').forEach(input => {
const props = form.getFieldProps(input.name);
input.value = props.value;
input.addEventListener('input', (e) => props.onChange(e.target.value));
input.addEventListener('blur', props.onBlur);
});
// React to state changes
form.on('change', ({ path, value }) => {
const errEl = document.querySelector(`[data-error="${path}"]`);
if (errEl) errEl.textContent = form.errors[path] ?? '';
});
Example: Multi-Step Form
import { Form } from '@hyperbridge/forge/form';
const wizard = new Form({
steps: [
{
name: 'personal',
fields: ['firstName', 'lastName', 'email'],
validate: (v) => {
const e = {};
if (!v.firstName) e.firstName = 'Required';
if (!v.email?.includes('@')) e.email = 'Valid email required';
return e;
},
},
{
name: 'company',
fields: ['company', 'role', 'teamSize'],
},
{
name: 'billing',
fields: ['plan', 'cardholderName'],
condition: (v) => v.teamSize > 1, // skip if solo
},
],
initialValues: {
firstName: '', lastName: '', email: '',
company: '', role: '', teamSize: 1,
plan: 'starter', cardholderName: '',
},
onSubmit: async (data) => {
await fetch('/api/onboarding', { method: 'POST', body: JSON.stringify(data) });
},
});
// Navigation
await wizard.nextStep(); // validate current step, advance if valid
await wizard.prevStep(); // go back (no validation)
wizard.currentStep.name; // 'personal' | 'company' | 'billing'
wizard.progress; // 0.0 – 1.0 (for progress bar)
wizard.isLastStep; // true on final step
Example: DragDropUpload
import { DragDropUpload } from '@hyperbridge/forge/form';
const uploader = new DragDropUpload('#drop-zone', {
accept: ['image/jpeg', 'image/png', 'application/pdf'],
maxSize: 10 * 1024 * 1024, // 10MB per file
maxFiles: 5,
multiple: true,
preview: true, // show image thumbnails
onSelect: (files) => console.log('Selected:', files.map(f => f.name)),
onError: (err) => toast.error(err.message),
upload: async (file, { onProgress }) => {
const fd = new FormData();
fd.append('file', file);
return fetch('/api/upload', {
method: 'POST',
body: fd,
onUploadProgress: ({ loaded, total }) => onProgress(loaded / total),
}).then(r => r.json());
},
});
// Programmatic control
uploader.open(); // open file picker
uploader.clear(); // remove all files
const urls = await uploader.uploadAll(); // upload all selected files
Example: RepeatingGroup (Field Arrays)
import { Form, RepeatingGroup } from '@hyperbridge/forge/form';
const form = new Form({
initialValues: {
recipients: [{ name: '', email: '', role: 'viewer' }],
},
});
const recipients = new RepeatingGroup(form, 'recipients', {
template: () => ({ name: '', email: '', role: 'viewer' }),
min: 1,
max: 10,
validate: (item) => {
const errors = {};
if (!item.email?.includes('@')) errors.email = 'Valid email required';
return errors;
},
});
// API
recipients.add(); // append new row from template
recipients.remove(2); // remove row at index 2
recipients.move(0, 2); // reorder (drag-drop support)
recipients.swap(1, 3); // swap two rows
recipients.items; // current array value
recipients.errors; // per-item error objects
forge/animate
Zero-dependency animation engine with spring physics, tweens, gestures, scroll-linked timelines, FLIP layout animations, SVG morphing, text effects, motion paths, and 50+ transition presets. 80+ APIs. 7,528 lines.
All animations respect prefers-reduced-motion by default. When a user has enabled reduced motion in their OS settings, tweens resolve instantly and springs jump to their final value. Use prefersReducedMotion() to check manually, or wrap any animation call with respectMotionPreference() for automatic handling.
Tween & Core Animation
Tween
Time-based animation with easing. new Tween({ duration, easing, values, onUpdate }). play/pause/reverse/seek.
animate
Shorthand animate(element, props, options). Returns Tween instance. CSS transforms and properties.
Animate
Class-based animation with chaining. new Animate(el).to({ opacity: 1 }, 500).start().
applyAnimation
Apply animation config object to an element. Declarative animation from plain objects.
repeatAnimation
Loop or repeat an animation. repeatAnimation(tween, { count: 3, alternate: true }).
import { Tween, animate } from '@hyperbridge/forge/animate';
// Full Tween API
const tween = new Tween({
duration: 800,
easing: 'easeOutCubic',
values: { opacity: [0, 1], x: [100, 0], y: [-50, 0] },
onUpdate: (v) => {
el.style.opacity = v.opacity;
el.style.transform = `translate(${v.x}px, ${v.y}px)`;
},
});
tween.play();
tween.pause();
tween.reverse();
tween.seek(0.5); // jump to 50%
// Shorthand
animate('.box', { opacity: 1, transform: 'translateY(0)' }, { duration: 600 });
Spring Physics
Spring
Physics-based spring animation. Configure tension, friction, mass. Natural deceleration.
springPresets
Built-in spring presets: gentle, wobbly, stiff, slow, molasses. Ready-to-use configs.
springChain
Chain multiple springs in sequence. Each spring triggers the next on settle.
springTo
Animate a spring to a target value. springTo(spring, { scale: 1.2 }). Returns promise.
springEasing
Convert spring physics into a CSS easing function. Use with CSS transitions.
import { Spring, springPresets, springChain } from '@hyperbridge/forge/animate';
const spring = new Spring({
...springPresets.wobbly,
values: { scale: [0.8, 1], opacity: [0, 1] },
onUpdate: (v) => {
el.style.transform = `scale(${v.scale})`;
el.style.opacity = v.opacity;
},
});
spring.play();
// Chain springs: fade in, then scale up, then slide right
springChain([
{ values: { opacity: [0, 1] }, ...springPresets.gentle },
{ values: { scale: [0.9, 1] }, ...springPresets.stiff },
{ values: { x: [0, 100] }, ...springPresets.wobbly },
], (v) => { /* onUpdate for each step */ });
Spring Configuration
| Parameter | Type | Description |
|---|---|---|
| tension | number | Spring stiffness. Higher values = snappier motion. Default: 170. |
| friction | number | Resistance / damping. Higher values = less oscillation. Default: 26. |
| mass | number | Mass of the animated object. Higher = more inertia, slower acceleration. Default: 1. |
| velocity | number | Initial velocity. Use to continue momentum from a gesture. Default: 0. |
| values | object | Map of animated properties with [from, to] pairs, e.g. { scale: [0.8, 1] }. |
| onUpdate | (values) => void | Called on each frame with the current interpolated values. |
| onComplete | () => void | Called when the spring settles (velocity below threshold). |
Easing Functions
Easing
15+ built-in easings: linear, easeInQuad, easeOutCubic, easeInOutExpo, easeOutBack, easeOutBounce, easeOutElastic, and more.
registerEasing
Register custom easing functions. registerEasing('myEase', t => t * t * t).
interpolate
Interpolate between values. interpolate(0.5, [0, 100]) returns 50. Supports multi-stop ranges.
transform
Map value from one range to another. transform(50, [0, 100], [0, 1]) returns 0.5.
mix
Linear interpolation. mix(0.5, 0, 100) returns 50. Works with colors and numbers.
clamp / wrap
clamp(value, min, max) constrains to range. wrap(value, min, max) wraps around cyclically.
Timeline & Sequencing
Timeline
Sequence animations with labels and overlaps. new Timeline(). add(tween, offset). play/pause/seek.
timeline
Shorthand timeline builder. timeline([anim1, anim2, ...], { defaults }). Returns Timeline instance.
sequence
Run animations in strict sequence. sequence([a, b, c]) plays each after the previous finishes.
keyframes
Multi-step keyframe animation. keyframes(el, [{ opacity: 0 }, { opacity: 1 }], options).
orchestrate
Coordinate complex animation graphs. orchestrate({ enter: [...], exit: [...], stagger: 50 }).
import { Timeline, Tween, sequence } from '@hyperbridge/forge/animate';
const tl = new Timeline();
tl.add(new Tween({ duration: 300, values: { opacity: [0, 1] }, onUpdate: ... }));
tl.add(new Tween({ duration: 500, values: { x: [0, 200] }, onUpdate: ... }), 200); // overlap by 100ms
tl.play();
// Or use sequence for strict ordering
sequence([
animate('.title', { opacity: 1 }, { duration: 400 }),
animate('.subtitle', { opacity: 1 }, { duration: 300 }),
animate('.cta', { opacity: 1, transform: 'scale(1)' }, { duration: 500 }),
]);
Stagger Animations
stagger
Stagger animations across elements. stagger(elements, { delay, duration, values }). Sequential entrance effects.
staggerAdvanced
Advanced stagger with custom delay functions. Supports ease-based, random, and from-center patterns.
staggerGrid
Grid-aware stagger. Animates by row, column, or diagonal. staggerGrid(els, { columns, from }).
staggerPresets
Built-in stagger patterns: linear, center, edges, random. Ready-to-use configurations.
import { stagger, staggerGrid } from '@hyperbridge/forge/animate';
// Simple stagger
stagger(document.querySelectorAll('.card'), {
duration: 500,
delay: 80,
easing: 'easeOutQuad',
values: { opacity: [0, 1], y: [30, 0] },
});
// Grid stagger — animate from center outward
staggerGrid(document.querySelectorAll('.grid-item'), {
columns: 4,
from: 'center',
duration: 400,
delay: 60,
values: { scale: [0, 1], opacity: [0, 1] },
});
Scroll Animations
onScroll
Bind animation to scroll position. onScroll(container, (progress) => {}). Progress is 0 to 1.
scrollProgress
Get current scroll progress of an element. Returns 0-1 float.
scrollPin
Pin an element during scroll range. Element stays fixed while content scrolls past.
useScroll
Reactive scroll position. useScroll(container) returns { x, y, progress }.
scrollTimeline
ScrollTrigger-inspired API. Link animation playback to scroll position with start/end markers.
scrollProgressDriver
Drive any animation from scroll progress. Connects scroll to Tween/Spring seek().
parallax
Parallax scrolling effect. parallax(element, { speed, direction }). Layers move at different rates.
import { scrollTimeline, parallax, inView } from '@hyperbridge/forge/animate';
// Scroll-linked animation (GSAP ScrollTrigger-style)
scrollTimeline('.progress-bar', {
trigger: '.section',
start: 'top center',
end: 'bottom center',
animation: { scaleX: [0, 1] },
});
// Parallax layers
parallax('.bg-layer', { speed: 0.3 });
parallax('.fg-layer', { speed: 0.8 });
scrollTimeline() pins elements by setting position: fixed during the active scroll range. Ensure your layout accounts for this -- pinned elements are removed from the document flow, so sibling content may collapse. Use a wrapper div with an explicit height equal to the pinned duration to reserve space.
scrollTimeline Options
| Option | Type | Description |
|---|---|---|
| scrub | boolean | number | Link animation progress to scroll position. A number adds smoothing (e.g. 0.5 = 500ms ease). Default: true. |
| pin | boolean | Pin the element in place during the scroll range. Default: false. |
| snap | number | number[] | Snap to progress points. 0.25 snaps to 0%, 25%, 50%, 75%, 100%. Array for custom stops. |
| start | string | Scroll start position. Format: '<element edge> <viewport edge>', e.g. 'top center'. |
| end | string | Scroll end position. Same format as start. Default: 'bottom top'. |
| onEnter | () => void | Callback fired when the trigger element scrolls into the active range. |
| onLeave | () => void | Callback fired when the trigger element scrolls out of the active range. |
Viewport & Reveal
inView
Trigger callback when element enters viewport. IntersectionObserver-based. Returns cleanup function.
reveal
Declarative scroll reveal. reveal('.card', { preset: 'fadeUp' }). Auto-triggers on viewport entry.
onResize
Observe element resize events. onResize(element, ({ width, height }) => {}). ResizeObserver-based.
Gesture System
gesture
Unified gesture recognizer. gesture(element, { onDrag, onPinch, onRotate }). Touch and mouse support.
draggable
Make element draggable. draggable(element, { axis, bounds, onDrag }). Supports constraints.
inertia
Apply inertia/momentum after gesture release. Element decelerates naturally based on velocity.
useVelocity
Track velocity of a MotionValue. Useful for gesture-driven animations and fling detection.
import { gesture, draggable, inertia } from '@hyperbridge/forge/animate';
// Full gesture handling
gesture(card, {
onDrag: ({ x, y, velocity }) => {
card.style.transform = `translate(${x}px, ${y}px)`;
},
onDragEnd: ({ velocity }) => {
inertia(card, { velocity, friction: 0.95 });
},
});
// Simple draggable
draggable('.handle', { axis: 'x', bounds: '.container' });
Layout Animations (FLIP)
flip
FLIP technique. flip(element, changeFunction, options). Smoothly animates layout changes.
layoutTransition
Automatic FLIP on DOM changes. layoutTransition(container, { duration, easing }). Watches mutations.
layoutGroup
Group elements for shared layout animation. Children animate together during reordering.
import { flip, layoutTransition } from '@hyperbridge/forge/animate';
// Manual FLIP for a layout change
flip(listContainer, () => {
// DOM mutation happens here
listContainer.prepend(listContainer.lastChild);
}, { duration: 400, easing: 'easeOutCubic' });
// Auto FLIP — watches for any DOM changes
const cleanup = layoutTransition('.grid', {
duration: 350,
easing: 'easeInOutQuad',
});
SVG & Path Animations
pathDraw
SVG path drawing animation. Animates stroke-dashoffset for line-draw effect.
morphSVG
Morph between SVG shapes. morphSVG(fromPath, toPath, options). Interpolates path data.
morphPath
Enhanced path morphing with point matching. Handles paths with different point counts.
motionPath
Animate element along an SVG path. motionPath(element, path, { duration, easing }).
Text Animations
splitText
Split text into characters, words, or lines for individual animation. Returns { chars, words, lines }.
typewriter
Typewriter effect. typewriter(element, { speed, cursor }). Types text character by character.
scrambleText
Scramble/decode text effect. Characters randomly resolve to final text. Cyberpunk style.
animateNumber
Animate a number from start to end. animateNumber(el, { from: 0, to: 1000, duration: 2000 }).
import { splitText, typewriter, scrambleText, stagger } from '@hyperbridge/forge/animate';
// Split and stagger characters
const { chars } = splitText('.heading');
stagger(chars, {
duration: 400, delay: 30,
values: { opacity: [0, 1], y: [20, 0] },
});
// Typewriter effect
typewriter('.terminal-text', { speed: 50, cursor: true });
// Scramble reveal
scrambleText('.secret-code', { duration: 1500, chars: '!@#$%^&*' });
Presence & Transitions
AnimatePresence
Animate component enter/exit. Wraps children, plays exit animation before removal from DOM.
AnimatePresenceStandalone
Standalone version (no framework dependency). Works with vanilla JS DOM manipulation.
VariantAnimator
State-based animation variants. Define named states, transition between them.
variants
Define animation variant sets. variants({ hidden: {...}, visible: {...} }). Propagates to children.
transition
50+ built-in transition presets: fadeIn, slideUp, scaleIn, flipX, blur, bounce, rotate, and more.
presets
Animation preset library. presets.fadeIn, presets.slideUp, presets.bounceIn, etc.
crossfade
Crossfade between two elements. Fades out old, fades in new simultaneously.
sharedElement
Shared element transition between views. Animates position/size from source to destination.
Motion Values & Reactivity
MotionValue
Reactive animated value. Subscribe to changes. Drives animations without re-rendering.
useVelocity
Derive velocity from a MotionValue. Tracks rate of change over time.
Performance Utilities
batchRAF
Batch requestAnimationFrame calls. Reduces layout thrashing for multiple simultaneous animations.
willChange
Apply will-change CSS hint. willChange(element, ['transform', 'opacity']). Improves GPU compositing.
gpuPromote / gpuDemote
Force GPU layer promotion or demotion. gpuPromote(el) adds translateZ(0). gpuDemote(el) removes it.
animateAll
WAAPI batch helper. Animate multiple elements via Web Animations API in one call.
waitForAnimation
Promise that resolves when element's animations complete. Await transition end.
cancelAllAnimations
Cancel all running animations on an element. Clean stop with optional fill state.
Reduced Motion & Accessibility
prefersReducedMotion
Check if user prefers reduced motion. Returns boolean. Query prefers-reduced-motion media.
prefersReducedMotionCheck
Enhanced check with callback. Runs callback on preference change. Returns unsubscribe function.
respectMotionPreference
Wrap any animation to respect reduced motion. Returns static state if user prefers reduced motion.
import { animate, respectMotionPreference, prefersReducedMotion } from '@hyperbridge/forge/animate';
// Automatically respects user preferences
const safeAnimate = respectMotionPreference(animate);
safeAnimate('.hero', { opacity: 1, y: 0 }, { duration: 600 });
// If reduced motion: instant jump, no animation
// Manual check
if (!prefersReducedMotion()) {
animate('.fancy-element', { rotate: 360 }, { duration: 2000 });
}
forge/chart
SVG chart library with line, bar, pie, scatter, radar, heatmap, treemap, gauge, and funnel charts.
Zero-dependency SVG rendering. forge/chart generates pure SVG markup — no Canvas, no WebGL, no D3. Charts are resolution-independent, print-ready, and fully accessible with ARIA labels. Every element is a real DOM node you can style with CSS or animate with forge/animate.
Responsive by default. Pass responsive: true and charts automatically resize with their container using a ResizeObserver. No manual chart.resize() calls needed — the layout engine recalculates axes, labels, and legends on every frame.
Quick Start
Every chart follows the same pattern: pick a type, point to a container, pass data, and call render(). The data shape is consistent across all chart types — labels for categories, series for datasets.
Chart Constructor Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
type | string | — | Chart type: 'line', 'bar', 'area', 'pie', 'donut', 'scatter', 'bubble', 'radar', 'heatmap', 'treemap', 'gauge', 'funnel', 'candlestick', 'sparkline' |
container | HTMLElement | — | DOM element to render into |
data | ChartData | — | Object with labels (string[]) and series (array of { name, data }) |
options.title | string | '' | Chart title displayed above the chart area |
options.responsive | boolean | true | Auto-resize with container via ResizeObserver |
options.animation | boolean | true | Enable enter/update/exit animations |
options.colors | string[] | Built-in palette | Custom color palette for series |
options.legend | boolean | object | true | Show legend. Pass object for position: { position: 'bottom' } |
options.tooltip | boolean | function | true | Show tooltip on hover. Pass function for custom formatting |
options.grid | boolean | true | Show background grid lines |
Instance Methods
| Method | Returns | Description |
|---|---|---|
render() | void | Render the chart into the container |
update(data) | void | Update data with animated transitions |
resize() | void | Force recalculate layout (automatic when responsive) |
destroy() | void | Remove chart, cleanup observers and event listeners |
toSVG() | string | Export chart as raw SVG string |
toImage(format) | Promise<Blob> | Export as PNG or JPEG blob |
on(event, fn) | void | Listen to chart events: 'click', 'hover', 'legendClick' |
Large datasets. For datasets over 10,000 points, enable options.downsample: true — the chart engine will use the Largest-Triangle-Three-Buckets algorithm to reduce points while preserving visual shape. Sparklines and heatmaps handle large datasets natively.
Supported Charts
Example: Line Chart
import { Chart } from '@hyperbridge/forge/chart';
const chart = new Chart({
type: 'line',
container: document.getElementById('chart'),
data: {
labels: ['Jan', 'Feb', 'Mar', 'Apr', 'May'],
series: [
{ name: 'Sales', data: [120, 150, 200, 180, 220] },
{ name: 'Profit', data: [30, 45, 60, 50, 80] },
],
},
options: {
title: 'Monthly Performance',
responsive: true,
animation: true,
},
});
chart.render();
Example: Pie Chart
import { Chart } from '@hyperbridge/forge/chart';
const chart = new Chart({
type: 'pie',
container: document.getElementById('chart'),
data: {
labels: ['Desktop', 'Mobile', 'Tablet'],
series: [
{ name: 'Traffic', data: [45, 35, 20] },
],
},
options: {
title: 'Traffic by Device',
colors: ['#6366f1', '#22d3ee', '#f97316'],
},
});
chart.render();
Example: Real-Time Dashboard
import { Chart } from '@hyperbridge/forge/chart';
// Live-updating gauge
const gauge = new Chart({
type: 'gauge',
container: document.getElementById('cpu-gauge'),
data: { value: 0, min: 0, max: 100, label: 'CPU %' },
options: {
colors: ['#22c55e', '#eab308', '#ef4444'], // green → yellow → red
thresholds: [60, 85],
animation: { duration: 300 },
},
});
gauge.render();
// Update every second from WebSocket
ws.on('metrics', (data) => {
gauge.update({ value: data.cpuPercent });
});
Example: Multi-Series Line + Overlays
import { ChartForge } from '@hyperbridge/forge/chart';
const chart = new ChartForge({
container: '#revenue-chart',
type: 'line',
width: 900,
height: 420,
theme: 'dark',
});
chart.setData({
labels: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug'],
datasets: [
{
label: 'Revenue',
data: [42000, 58000, 39000, 71000, 63000, 88000, 74000, 95000],
color: '#6366f1',
fill: true, // area fill
smooth: true, // cubic bezier curves
},
{
label: 'Target',
data: [50000, 55000, 60000, 65000, 70000, 75000, 80000, 85000],
color: '#22d3ee',
dash: [6, 3], // dashed line
},
],
});
// Statistical overlays
chart.addOverlay('sma', { period: 3, color: '#f59e0b' }); // simple moving average
chart.addOverlay('trendline', { color: '#ef4444', lineWidth: 1.5 }); // least-squares trendline
chart.addOverlay('bollinger', { period: 5, stdDev: 2, color: 'rgba(99,102,241,0.2)' });
// Annotations
chart.annotate({ x: 'Apr', label: 'Campaign launch', color: '#22c55e' });
chart.annotate({ x: 'Jun', y: 88000, label: 'Record month', type: 'point' });
chart.render();
// Zoom into a date range
chart.zoom({ from: 'Mar', to: 'Jul' });
Example: Real-Time Streaming Chart
import { ChartForge } from '@hyperbridge/forge/chart';
const liveChart = new ChartForge({
container: '#metrics-chart',
type: 'line',
width: 800,
height: 300,
streaming: {
maxPoints: 60, // rolling 60-point window
updateInterval: 1000, // redraw every 1000ms
},
});
liveChart.setData({
datasets: [
{ label: 'CPU %', data: [], color: '#6366f1' },
{ label: 'Memory %', data: [], color: '#22d3ee' },
],
});
liveChart.render();
// Push new data points from WebSocket
const ws = new WebSocket('/api/metrics');
ws.onmessage = ({ data }) => {
const { cpu, memory, ts } = JSON.parse(data);
liveChart.push([
{ label: ts, value: cpu },
{ label: ts, value: memory },
]);
};
Example: ChartDashboard — Multiple Charts
import { ChartDashboard, ChartForge } from '@hyperbridge/forge/chart';
// Manage multiple charts as a synchronized grid
const dashboard = new ChartDashboard('#analytics-dashboard', {
columns: 2,
gap: 16,
theme: 'dark',
});
dashboard.add('revenue', new ChartForge({ type: 'bar', /* ... */ }));
dashboard.add('devices', new ChartForge({ type: 'donut', /* ... */ }));
dashboard.add('sessions', new ChartForge({ type: 'area', /* ... */ }));
dashboard.add('funnel', new ChartForge({ type: 'funnel', /* ... */ }));
dashboard.render(); // renders all charts, coordinates resize events
// Update all from one API response
const { data } = await fetch('/api/analytics').then(r => r.json());
dashboard.updateAll(data);
Example: Candlestick + Volume
import { ChartForge } from '@hyperbridge/forge/chart';
const ohlcv = new ChartForge({
container: '#price-chart',
type: 'candlestick',
width: 1000,
height: 500,
});
ohlcv.setData({
datasets: [{
label: 'BTC/USD',
data: [
// { t, o, h, l, c, v }
{ t: '2026-04-01', o: 71000, h: 73500, l: 70200, c: 72800, v: 1240000 },
{ t: '2026-04-02', o: 72800, h: 74100, l: 71500, c: 71900, v: 980000 },
{ t: '2026-04-03', o: 71900, h: 75000, l: 71800, c: 74500, v: 2100000 },
],
}],
});
ohlcv.addOverlay('volume', { position: 'bottom', height: 100 }); // volume bars below
ohlcv.addOverlay('rsi', { period: 14, overbought: 70, oversold: 30 });
ohlcv.render();
Example: Export
// SVG string — resolution-independent, embed in HTML or PDF
const svg = chart.toSVG();
document.getElementById('preview').innerHTML = svg;
// PNG blob — for download or attaching to emails/PDFs
const blob = await chart.toImage('png', { scale: 2 }); // 2× for Retina
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url; a.download = 'chart.png'; a.click();
// Base64 — for embedding in email HTML
const b64 = await chart.toImage('png', { format: 'base64' });
// <img src="data:image/png;base64,${b64}">
forge/schema
Zod-compatible schema validation with TypeScript inference, coercion, transforms, and OpenAPI generation.
forge/schema is wire-compatible with Zod. Replace import { z } from 'zod' with import { z } from '@hyperbridge/forge/schema'. The full API surface — .object(), .string(), .parse(), .safeParse(), .refine(), .transform(), .infer — is identical. Existing Zod schemas migrate with a one-line import change.
Email validation checks TLD against 100+ known domains. Typos like gmial.com or yaho.com are caught and a suggestion field is returned in the error object — perfect for "Did you mean…?" UX.
Primitive Types
| Constructor | TypeScript Type | Description |
|---|---|---|
z.string() | string | UTF-8 string |
z.number() | number | IEEE-754 float |
z.boolean() | boolean | true / false |
z.bigint() | bigint | Arbitrary precision integer |
z.date() | Date | JS Date object |
z.symbol() | symbol | Unique symbol |
z.undefined() | undefined | Exactly undefined |
z.null() | null | Exactly null |
z.any() | any | Bypass validation entirely |
z.unknown() | unknown | Unknown — must narrow before use |
z.never() | never | Rejects all values (useful in discriminated unions) |
z.literal(value) | typeof value | Exact value match: z.literal('admin') |
String Validators
| Method | Description |
|---|---|
.min(n, msg?) | Minimum character length |
.max(n, msg?) | Maximum character length |
.length(n, msg?) | Exact length |
.email(msg?) | RFC 5322 email with TLD check |
.url(msg?) | Valid URL (http/https/ftp) |
.uuid(msg?) | UUID v1–v5 format |
.cuid(msg?) | CUID / CUID2 format |
.nanoid(msg?) | NanoID format |
.regex(pattern, msg?) | Custom regex test |
.startsWith(str, msg?) | Must begin with prefix |
.endsWith(str, msg?) | Must end with suffix |
.includes(str, msg?) | Must contain substring |
.ip(msg?) | IPv4 or IPv6 address |
.datetime(msg?) | ISO 8601 datetime string |
.trim() | Transform: strip whitespace |
.toLowerCase() | Transform: force lowercase |
.toUpperCase() | Transform: force uppercase |
Number Validators
| Method | Description |
|---|---|
.min(n, msg?) | ≥ n |
.max(n, msg?) | ≤ n |
.gt(n) / .gte(n) | > n / ≥ n (aliases for min/max with exclusive) |
.lt(n) / .lte(n) | < n / ≤ n |
.int(msg?) | Must be an integer (no fractional part) |
.positive(msg?) | > 0 |
.negative(msg?) | < 0 |
.nonnegative(msg?) | ≥ 0 |
.nonpositive(msg?) | ≤ 0 |
.finite(msg?) | Not Infinity or NaN |
.safe(msg?) | Within Number.MIN/MAX_SAFE_INTEGER |
.multipleOf(n, msg?) | Divisible by n (step validation) |
Complex Types
| Constructor | Description |
|---|---|
z.object(shape) | Object with named fields. Strips unknown keys by default. |
z.array(schema) | Array where every element is validated by schema |
z.tuple([s1, s2, ...]) | Fixed-length array with per-index schemas |
z.record(keySchema, valSchema) | Object with dynamic keys (like Record<string, T>) |
z.map(keySchema, valSchema) | ES6 Map |
z.set(schema) | ES6 Set |
z.enum(['a', 'b']) | One of a fixed set of string values |
z.nativeEnum(MyEnum) | TypeScript enum (numeric or string) |
z.union([s1, s2]) | Value must satisfy at least one schema |
z.discriminatedUnion(key, [s1, s2]) | Faster union via discriminant field (e.g., 'type') |
z.intersection(s1, s2) | Value must satisfy both schemas (like TypeScript &) |
z.lazy(() => schema) | Recursive/self-referential schemas |
z.function() | Validates a function, with .args() and .returns() |
Object Methods
| Method | Description |
|---|---|
.shape | Access the shape object of an object schema |
.pick({ key: true }) | Return a new schema with only selected keys |
.omit({ key: true }) | Return a new schema without selected keys |
.partial() | Make all fields optional |
.required() | Make all fields required |
.extend(shape) | Add new fields to the schema |
.merge(other) | Merge two object schemas (like Object.assign for schemas) |
.strict() | Error on unknown keys instead of stripping them |
.passthrough() | Pass unknown keys through unvalidated |
.strip() | Strip unknown keys (default) |
.keyof() | Return an enum schema of the object's keys |
Schema Modifiers
| Method | TypeScript Effect | Description |
|---|---|---|
.optional() | T | undefined | Field may be absent or undefined |
.nullable() | T | null | Field may be null |
.nullish() | T | null | undefined | Combines optional + nullable |
.default(value) | T | Use value when input is undefined |
.catch(value) | T | Use value when validation fails (never throws) |
.describe(text) | T | Attach description (used in OpenAPI) |
.brand(symbol) | T & Brand | Nominal typing — prevents accidental cross-assignment |
.readonly() | Readonly<T> | Freezes object output |
Transforms & Custom Validation
| Method | Description |
|---|---|
.transform(fn) | Map output value: z.string().transform(s => s.split(',')) |
.refine(fn, msg?) | Custom validator — fn returns boolean. Sync or async. |
.superRefine(fn) | Full control — fn receives (val, ctx), call ctx.addIssue() |
.preprocess(fn, schema) | Transform input before validation: z.preprocess(Number, z.number()) |
.pipe(schema) | Chain schemas: output of this becomes input to next |
Parsing Methods
| Method | Returns | On failure |
|---|---|---|
.parse(data) | T | Throws ZodError |
.safeParse(data) | { success: true, data: T } | { success: false, error: ZodError } | Never throws |
.parseAsync(data) | Promise<T> | Rejects with ZodError |
.safeParseAsync(data) | Promise<SafeParseResult> | Never rejects |
Coercion
The z.coerce namespace wraps schemas with a type-cast transform, useful for URL params and form data that arrive as strings:
import { z } from '@hyperbridge/forge/schema';
// URL param ?page=3 arrives as string
const querySchema = z.object({
page: z.coerce.number().int().positive().default(1),
limit: z.coerce.number().int().min(1).max(100).default(20),
active: z.coerce.boolean().default(true), // '1','true','yes' → true
date: z.coerce.date().optional(), // ISO string → Date
});
querySchema.parse({ page: '3', limit: '50', active: '1' });
// → { page: 3, limit: 50, active: true }
TypeScript Inference
import { z } from '@hyperbridge/forge/schema';
const PostSchema = z.object({
id: z.number().int().positive(),
title: z.string().min(1).max(200),
body: z.string(),
status: z.enum(['draft', 'published', 'archived']).default('draft'),
tags: z.array(z.string()).max(10).default([]),
publishedAt: z.date().optional(),
author: z.object({
id: z.number(),
email: z.string().email(),
name: z.string(),
}),
});
// Infer the output type
type Post = z.infer<typeof PostSchema>;
// {
// id: number;
// title: string;
// body: string;
// status: 'draft' | 'published' | 'archived';
// tags: string[];
// publishedAt?: Date;
// author: { id: number; email: string; name: string };
// }
// Input type (before defaults are applied)
type PostInput = z.input<typeof PostSchema>;
Async Validation
import { z } from '@hyperbridge/forge/schema';
const RegisterSchema = z.object({
email: z.string().email().refine(
async (email) => {
const count = await db.count('users', { email });
return count === 0;
},
{ message: 'Email already registered' }
),
username: z.string().min(3).max(20).regex(/^[a-z0-9_]+$/).refine(
async (username) => {
const taken = await db.exists('users', { username });
return !taken;
},
{ message: 'Username taken' }
),
password: z.string().min(8),
confirmPassword: z.string(),
}).refine(data => data.password === data.confirmPassword, {
message: 'Passwords do not match',
path: ['confirmPassword'],
});
const result = await RegisterSchema.safeParseAsync(req.body);
if (!result.success) {
return res.status(422).json({ errors: result.error.flatten() });
}
OpenAPI Generation
import { z } from '@hyperbridge/forge/schema';
const ProductSchema = z.object({
id: z.number().int().positive().describe('Unique product ID'),
name: z.string().min(1).max(100).describe('Product display name'),
price: z.number().positive().describe('Price in USD'),
category: z.enum(['electronics', 'clothing', 'food']).describe('Product category'),
inStock: z.boolean().default(true).describe('Whether product is available'),
});
// Generate OpenAPI 3.0 schema component
const openApiSchema = ProductSchema.toOpenAPI({ title: 'Product' });
// {
// title: 'Product',
// type: 'object',
// required: ['id', 'name', 'price', 'category'],
// properties: {
// id: { type: 'integer', minimum: 1, description: 'Unique product ID' },
// name: { type: 'string', minLength: 1, maxLength: 100 },
// price: { type: 'number', exclusiveMinimum: 0 },
// category: { type: 'string', enum: ['electronics', 'clothing', 'food'] },
// inStock: { type: 'boolean', default: true },
// },
// }
forge/test
Jest-compatible test runner — describe/it/expect, mocking, spies, DOM testing, HTTP mocking, property-based testing, snapshots, benchmarks, and coverage. Zero config, zero dependencies.
Jest-compatible API. The same describe/it/expect/fn/spyOn API you already know — but forge/test runs without Babel, without ts-jest, and without 47 transitive dependencies. Drop in as a replacement or use it fresh.
Built-in component testing. render(), screen, and fireEvent work like React Testing Library but are built for forge/client. No @testing-library/react, no jest-dom, no user-event needed.
Runner Configuration
| Option | Type | Default | Description |
|---|---|---|---|
include | string[] | ['**/*.test.js'] | Glob patterns for test files. Also matches *.spec.js and *.test.ts. |
exclude | string[] | ['node_modules', 'dist'] | Glob patterns to skip |
parallel | boolean | true | Run test files in isolated worker threads |
timeout | number | 5000 | Per-test timeout in ms. Override per-test with it('...', fn, 10000) |
coverage | boolean | false | Collect V8 coverage. Report in terminal + write coverage/. |
coverageThreshold | object | {} | Fail CI if coverage drops: { lines: 80, branches: 70 } |
bail | boolean | number | false | Stop after N failures. true = stop after first failure. |
watch | boolean | false | Re-run affected tests on file change (uses file dependency graph) |
reporter | string | 'default' | 'default', 'dot', 'json', 'junit', or custom reporter function |
Expect Matchers
| Matcher | Description |
|---|---|
.toBe(val) | Strict equality (===). Use for primitives. |
.toEqual(val) | Deep structural equality. Use for objects and arrays. |
.toStrictEqual(val) | Deep equality + checks object types (class instances) |
.toBeCloseTo(num, digits?) | Floating-point comparison to N decimal places (default 2) |
.toBeTruthy() / .toBeFalsy() | Truthy/falsy in boolean context |
.toBeNull() / .toBeUndefined() | Null / undefined checks |
.toBeDefined() | Not undefined |
.toBeNaN() | Is NaN |
.toBeGreaterThan(n) / .toBeLessThan(n) | Numeric comparison |
.toBeGreaterThanOrEqual(n) / .toBeLessThanOrEqual(n) | Numeric comparison (inclusive) |
.toBeInstanceOf(Class) | instanceof check |
.toContain(item) | Array includes item or string contains substring |
.toContainEqual(item) | Array contains item with deep equality |
.toHaveLength(n) | Array/string .length === n |
.toHaveProperty(path, val?) | Object has nested property (dot path: 'user.email') |
.toMatch(regex | string) | String matches regex or contains substring |
.toThrow(msg?) | Sync function throws. Optional message match. |
.toThrowError(Class) | Throws specific error class |
.toMatchSnapshot() | Serialize and compare to stored snapshot |
.toMatchInlineSnapshot(str) | Inline snapshot (stored in test file) |
.resolves.toBe(val) | Chain for Promise assertions |
.rejects.toThrow(msg?) | Promise rejects with error |
.toHaveBeenCalled() | Mock was called at least once |
.toHaveBeenCalledTimes(n) | Mock call count equals n |
.toHaveBeenCalledWith(...args) | Mock called with specific arguments (deep equal) |
.toHaveBeenLastCalledWith(...args) | Most recent call had these arguments |
.toHaveReturnedWith(val) | Mock returned specific value |
Snapshot files. Stored in __snapshots__/ next to the test file. Run with --update-snapshots (or -u) to regenerate after intentional changes. Commit snapshots to version control — they are your visual regression baseline.
Mock API (fn / spyOn)
| Method | Description |
|---|---|
fn(impl?) | Create a mock function. Optionally provide default implementation. |
spyOn(obj, method) | Replace obj.method with a spy that passes through to the original by default |
mock.mockReturnValue(val) | Always return val |
mock.mockReturnValueOnce(val) | Return val for the next call only, then fall through |
mock.mockResolvedValue(val) | Return Promise.resolve(val) |
mock.mockRejectedValue(err) | Return Promise.reject(err) |
mock.mockImplementation(fn) | Replace the mock's full implementation |
mock.mockClear() | Reset call history (not implementation) |
mock.mockReset() | Reset call history AND implementation |
mock.mockRestore() | Restore original (spyOn only) |
mock.calls | Array of call argument arrays: [[arg1, arg2], [arg1]] |
mock.results | Array of { type: 'return'|'throw', value } |
Example: Unit Tests with Lifecycle Hooks
import { describe, it, expect, beforeEach, afterEach, beforeAll, afterAll } from '@hyperbridge/forge/test';
import { UserService } from './user-service.js';
describe('UserService', () => {
let service;
let db;
beforeAll(async () => {
db = await createTestDatabase();
});
afterAll(async () => {
await db.destroy();
});
beforeEach(async () => {
await db.truncate('users');
service = new UserService(db);
});
it('creates a user', async () => {
const user = await service.create({ email: 'a@b.com', name: 'Alex' });
expect(user.id).toBeGreaterThan(0);
expect(user.email).toBe('a@b.com');
expect(user.createdAt).toBeInstanceOf(Date);
});
it('throws on duplicate email', async () => {
await service.create({ email: 'dup@b.com' });
await expect(service.create({ email: 'dup@b.com' })).rejects.toThrowError('Email already exists');
});
it('finds by id', async () => {
const created = await service.create({ email: 'find@b.com', name: 'Find Me' });
const found = await service.findById(created.id);
expect(found).toEqual(expect.objectContaining({ email: 'find@b.com', name: 'Find Me' }));
});
});
Example: Mocking & Spies
import { describe, it, expect, fn, spyOn, beforeEach } from '@hyperbridge/forge/test';
import { NotificationService } from './notifications.js';
import * as mailer from '@hyperbridge/forge/mail';
describe('NotificationService', () => {
let sendMailSpy;
beforeEach(() => {
sendMailSpy = spyOn(mailer.SMTPClient.prototype, 'sendMail')
.mockResolvedValue({ messageId: 'test-123' });
});
afterEach(() => {
sendMailSpy.mockRestore();
});
it('sends welcome email on registration', async () => {
const svc = new NotificationService();
await svc.sendWelcome({ email: 'user@example.com', name: 'Alex' });
expect(sendMailSpy).toHaveBeenCalledTimes(1);
expect(sendMailSpy).toHaveBeenCalledWith(
expect.objectContaining({
to: 'user@example.com',
subject: expect.stringContaining('Welcome'),
})
);
});
it('retries on transient failure', async () => {
sendMailSpy
.mockRejectedValueOnce(new Error('SMTP connection refused'))
.mockResolvedValueOnce({ messageId: 'retry-ok' });
const svc = new NotificationService({ retries: 2 });
await svc.sendWelcome({ email: 'retry@example.com', name: 'Retry' });
expect(sendMailSpy).toHaveBeenCalledTimes(2);
});
});
Example: DOM / Component Testing
import { describe, it, expect, render, screen, fireEvent, waitFor } from '@hyperbridge/forge/test';
import { Counter, LoginForm, UserList } from './components.js';
describe('Counter', () => {
it('renders initial count', () => {
render(Counter, { props: { initial: 5 } });
expect(screen.getByText('Count: 5')).toBeDefined();
});
it('increments on button click', async () => {
render(Counter);
fireEvent.click(screen.getByRole('button', { name: 'Increment' }));
expect(screen.getByText('Count: 1')).toBeDefined();
});
it('matches snapshot', () => {
const { container } = render(Counter);
expect(container.innerHTML).toMatchSnapshot();
});
});
describe('LoginForm', () => {
it('shows error on empty submit', async () => {
render(LoginForm);
fireEvent.submit(screen.getByRole('form'));
await waitFor(() => {
expect(screen.getByText('Email is required')).toBeDefined();
});
});
it('calls onSubmit with form data', async () => {
const onSubmit = fn();
render(LoginForm, { props: { onSubmit } });
await fireEvent.type(screen.getByLabelText('Email'), 'user@example.com');
await fireEvent.type(screen.getByLabelText('Password'), 'pass1234');
fireEvent.submit(screen.getByRole('form'));
await waitFor(() => {
expect(onSubmit).toHaveBeenCalledWith({ email: 'user@example.com', password: 'pass1234' });
});
});
});
Example: Property-Based Testing
import { describe, it, expect, forAll, gen } from '@hyperbridge/forge/test';
import { sortByAge } from './sort.js';
describe('sortByAge', () => {
// Runs 100 random inputs automatically
forAll('is idempotent', gen.array(gen.object({ name: gen.string(), age: gen.integer(0, 120) })), (users) => {
const once = sortByAge(users);
const twice = sortByAge(once);
expect(once).toEqual(twice); // sorting twice == sorting once
});
forAll('output is sorted', gen.array(gen.object({ age: gen.integer(0, 120) }), { minLength: 1 }), (users) => {
const sorted = sortByAge(users);
for (let i = 0; i < sorted.length - 1; i++) {
expect(sorted[i].age).toBeLessThanOrEqual(sorted[i + 1].age);
}
});
});
// Available generators
gen.integer(min, max) // random integer
gen.float(min, max) // random float
gen.string(minLen?, maxLen?) // random alphanumeric string
gen.boolean() // random boolean
gen.enum(['a', 'b', 'c']) // random enum value
gen.array(itemGen, opts?) // random array
gen.object(shape) // random object from shape map
gen.nullable(gen) // randomly null or the generator's value
gen.oneOf([gen1, gen2]) // pick one of multiple generators
Example: HTTP Mocking
import { describe, it, expect, mockFetch, clearFetchMocks } from '@hyperbridge/forge/test';
import { GithubClient } from './github.js';
describe('GithubClient', () => {
afterEach(clearFetchMocks);
it('fetches repos', async () => {
mockFetch('GET https://api.github.com/users/octocat/repos', {
status: 200,
body: [{ id: 1, name: 'Hello-World', stargazers_count: 1000 }],
});
const client = new GithubClient({ token: 'test' });
const repos = await client.getRepos('octocat');
expect(repos).toHaveLength(1);
expect(repos[0].name).toBe('Hello-World');
});
it('throws on 404', async () => {
mockFetch('GET https://api.github.com/users/nobody/repos', { status: 404 });
const client = new GithubClient({ token: 'test' });
await expect(client.getRepos('nobody')).rejects.toThrow('Not found');
});
});
Example: Benchmarks
import { bench, describe } from '@hyperbridge/forge/test';
import { z } from '@hyperbridge/forge/schema';
const UserSchema = z.object({ id: z.number(), email: z.string().email(), name: z.string() });
const user = { id: 1, email: 'a@b.com', name: 'Alex' };
describe('Schema performance', () => {
bench('z.parse (valid)', () => UserSchema.parse(user), { iterations: 50_000 });
bench('z.safeParse (valid)', () => UserSchema.safeParse(user), { iterations: 50_000 });
bench('z.safeParse (invalid)', () => UserSchema.safeParse({ id: 'bad' }), { iterations: 50_000 });
});
// Output:
// z.parse (valid) ~1,840,000 ops/s (0.54µs/op)
// z.safeParse (valid) ~1,750,000 ops/s (0.57µs/op)
// z.safeParse (invalid) ~1,200,000 ops/s (0.83µs/op)
forge/mail
SMTP client with DKIM signing, connection pooling, templating, queuing, open/click tracking, bounce management, calendar invites, and A/B testing. No Nodemailer, no external transport libraries.
Two transports, one API. forge/mail supports direct SMTP (RFC 5321) and the Resend HTTP API. Switch with a config change — your sendMail() calls stay identical. Use SMTP in development, Resend in production.
DKIM signing built in. Pass your RSA private key and domain selector — forge/mail signs every outgoing email with DKIM automatically. No additional library, no DNS configuration tool. Just provide the key and it works.
Never hardcode credentials. Use process.env.SMTP_PASS, never a string literal. Gmail requires an App Password (not your account password) — generate one at Google Account → Security → App Passwords.
SMTPClient Configuration
| Option | Type | Default | Description |
|---|---|---|---|
host | string | — | SMTP server hostname (e.g. smtp.postmarkapp.com) |
port | number | 587 | 587 for STARTTLS, 465 for implicit TLS, 25 for no TLS |
secure | boolean | false | true for port 465 (TLS from connect). Leave false for 587 (STARTTLS). |
auth | object | — | { user, pass } for LOGIN/PLAIN. { type: 'oauth2', accessToken } for OAuth2. |
dkim | object | null | { domain, keySelector, privateKey } — RSA-SHA256 DKIM signing |
pool | boolean | false | Enable persistent connection pool (faster for bulk sending) |
maxConnections | number | 5 | Max simultaneous pooled SMTP connections |
rateLimit | number | 0 | Max messages per second. 0 = unlimited. |
timeout | number | 30000 | Connection + data timeout in ms |
tls | object | {} | Node.js TLS options: { rejectUnauthorized, ca, cert, key } |
sendMail Options
| Field | Type | Description |
|---|---|---|
from | string | Sender: 'Display Name <addr@domain.com>' |
to | string | string[] | Recipient(s). Max 50 per call (SMTP limit) |
cc / bcc | string | string[] | CC and BCC recipients |
replyTo | string | Reply-To header address |
subject | string | Subject line. UTF-8, encoded automatically. |
html | string | HTML body. Plain text is auto-generated by stripping tags if text is omitted. |
text | string | Plain text fallback (used by screen readers and text-only clients) |
attachments | Attachment[] | { filename, content: Buffer|string, contentType, encoding? } |
inlineImages | object[] | { cid, content, contentType } — embed images in HTML via cid: src |
headers | object | Custom SMTP headers: { 'X-Campaign-ID': 'spring-2026' } |
priority | string | 'high', 'normal', 'low' — sets X-Priority header |
Additional Classes
| Class | Description |
|---|---|
EmailTemplate | Mustache-compatible templates: {{var}}, {{#if}}, {{#each}}, partials, layouts |
MailQueue | In-memory queue with rate limiting, retry, and flush. Feeds into any transport. |
MailTracker | Open/click pixel tracking via HTTP webhooks. Returns { opens, clicks, lastOpened } |
BounceList | Hard bounce registry. add(email), has(email), import(list). Prevent sending to bounced addresses. |
CalendarInvite | Generate iCal (.ics) attachments for meeting invites. Compatible with Outlook, Google Calendar, Apple Calendar. |
ABTest | Split subject lines or HTML variants across recipients. Track open rates per variant. |
Example: SMTP with DKIM
import { SMTPClient } from '@hyperbridge/forge/mail';
import fs from 'node:fs';
const smtp = new SMTPClient({
host: 'smtp.postmarkapp.com',
port: 587,
auth: { user: process.env.SMTP_USER, pass: process.env.SMTP_PASS },
dkim: {
domain: 'hyperbridge.digital',
keySelector: 'default',
privateKey: fs.readFileSync('./keys/dkim-private.pem', 'utf8'),
},
pool: true,
maxConnections: 5,
rateLimit: 20, // 20 msgs/sec
});
// Transactional email with attachment
await smtp.sendMail({
from: 'HyperBridge Digital <noreply@hyperbridge.digital>',
to: 'user@example.com',
subject: 'Your invoice is ready',
html: `<h1>Invoice #1042</h1><p>See attached.</p>`,
attachments: [{
filename: 'invoice-1042.pdf',
content: pdfBuffer,
contentType: 'application/pdf',
}],
});
Example: EmailTemplate
import { SMTPClient, EmailTemplate } from '@hyperbridge/forge/mail';
// Template with conditionals and loops
const orderTemplate = new EmailTemplate({
subject: 'Order #{{orderId}} confirmed',
html: `
<h1>Hi {{firstName}},</h1>
<p>Your order #{{orderId}} has been confirmed.</p>
<table>
{{#each items}}
<tr>
<td>{{name}}</td>
<td>{{qty}}</td>
<td>{{price}}</td>
</tr>
{{/each}}
</table>
<p>Total: {{total}}</p>
{{#if isPremium}}
<p>🎁 As a Premium member, you get free shipping!</p>
{{/if}}
`,
});
const email = orderTemplate.render({
firstName: 'Priya',
orderId: 'ORD-9981',
items: [
{ name: 'HBForge License', qty: 1, price: '$2,400' },
{ name: 'Support', qty: 3, price: '$450' },
],
total: '$2,850',
isPremium: true,
});
await smtp.sendMail({ from: 'orders@app.com', to: 'priya@example.com', ...email });
Example: MailQueue — Bulk Newsletter
import { SMTPClient, MailQueue, EmailTemplate, BounceList } from '@hyperbridge/forge/mail';
const bounces = new BounceList();
await bounces.import(await db.getBounces()); // load from DB
const queue = new MailQueue(smtp, {
rateLimit: 50, // 50 msgs/sec
concurrency: 5,
retries: 2,
onError: (err, msg) => console.error('Failed:', msg.to, err.message),
});
const newsletter = new EmailTemplate({
subject: 'HBForge April Update',
html: await fs.promises.readFile('./emails/newsletter.html', 'utf8'),
});
for (const subscriber of subscribers) {
if (bounces.has(subscriber.email)) continue; // skip bounced
queue.enqueue({
to: subscriber.email,
from: 'newsletter@hyperbridge.digital',
...newsletter.render({ name: subscriber.firstName }),
});
}
const result = await queue.flush();
console.log(`Sent: ${result.sent}, Failed: ${result.failed}`);
Example: CalendarInvite
import { SMTPClient, CalendarInvite } from '@hyperbridge/forge/mail';
const invite = new CalendarInvite({
uid: 'meet-2026-04-20@hyperbridge.digital',
summary: 'HBForge Onboarding Call',
description: 'Welcome call with your HBForge onboarding team.',
location: 'Google Meet — meet.google.com/abc-defg-hij',
organizer: { name: 'HyperBridge Digital', email: 'hello@hyperbridge.digital' },
attendees: [{ name: 'Alex', email: 'alex@client.com', rsvp: true }],
start: new Date('2026-04-20T10:00:00+05:30'),
end: new Date('2026-04-20T11:00:00+05:30'),
timezone: 'Asia/Kolkata',
reminders: [{ minutes: 15 }, { minutes: 1440 }], // 15 min + 1 day before
});
await smtp.sendMail({
from: 'hello@hyperbridge.digital',
to: 'alex@client.com',
subject: 'HBForge Onboarding Call — April 20',
html: '<p>You are invited to an onboarding call. See the attached invite.</p>',
attachments: [invite.toAttachment()], // .ics attachment
});
forge/pdf
Pure JavaScript PDF 1.7 generation — text, tables, images, custom fonts, encryption, QR codes, and streaming output.
No headless browser. forge/pdf writes valid PDF 1.7 binary output directly — no Puppeteer, no Chromium, no wkhtmltopdf, no system fonts required. The output renders identically in Adobe Reader, macOS Preview, Chrome, Firefox, and every major PDF viewer.
Streaming output. For large documents (1,000+ pages), use pdf.build('stream') to pipe directly to an HTTP response or file write stream. Memory usage stays constant regardless of page count — critical for invoice batch jobs and report generators.
Constructor
| Option | Type | Default | Description |
|---|---|---|---|
pageSize | string | object | 'A4' | 'A4', 'Letter', 'Legal', 'A3', 'A5', or { width, height } in points |
orientation | string | 'portrait' | 'portrait' or 'landscape' |
margin | number | object | 40 | Margin in points. Object: { top, right, bottom, left }. 72 points = 1 inch. |
compress | boolean | true | Flate-compress content streams for smaller output |
metadata | object | {} | { title, author, subject, keywords, creator } — embedded in PDF metadata |
encrypt | object | null | { userPassword, ownerPassword, permissions: ['print', 'copy'] } |
Coordinate origin. PDF natively places (0,0) at the bottom-left. forge/pdf flips this — (0,0) is the top-left, matching web conventions. An A4 page is 595 × 842 points. The usable area with 40pt margins is 515 × 762 points.
Drawing API
| Method | Signature | Description |
|---|---|---|
text() | (content, options) | Add text at { x, y, fontSize, color, font, align, lineHeight, maxWidth } |
line() | (options) | Draw line: { x1, y1, x2, y2, width, color, dash } |
rect() | (options) | Rectangle: { x, y, width, height, fill, stroke, strokeWidth, radius } |
circle() | (options) | Circle: { cx, cy, r, fill, stroke } |
image() | (src, options) | Embed PNG/JPEG: { x, y, width, height, opacity }. Accepts Buffer, file path, or data URI. |
table() | (options) | Auto-layout table: { x, y, columns, rows, headerStyle, cellPadding, stripedRows, borderColor } |
addPage() | (options?) | Add new page. Can override pageSize and orientation per page. |
setHeader() | (fn | string) | Global header rendered on every page. Function receives ({ page, total, doc }). |
setFooter() | (fn | string) | Global footer. Use '{page} of {total}' for automatic page numbering. |
watermark() | (text, options) | Diagonal text across every page: { opacity, angle, color, fontSize } |
setFont() | (name, size) | Set current font. Built-in: Helvetica, Times, Courier. Load custom: loadFont(name, ttfBuffer). |
setColor() | (hex) | Set current fill/stroke color (persists until changed) |
moveTo() | (y) | Move cursor to absolute Y position (for sequential text flow) |
build() | (format?) | Finalize document. Returns Uint8Array (default), 'base64' string, or 'stream' (Node.js ReadableStream). |
QR Codes & Barcodes
| Class | Method | Description |
|---|---|---|
QRCode | QRCode.generate(data, options) | Generate QR code as PNG Buffer. Options: { size, errorLevel, margin, dark, light } |
QRCode | QRCode.toDataURL(data) | Generate as data URI (for browser use) |
Barcode | Barcode.generate(data, options) | Generate barcode PNG. Options: { format: 'CODE128'|'EAN13'|'QR', width, height } |
Example: Professional Invoice
import { PDFForge, QRCode } from '@hyperbridge/forge/pdf';
const doc = new PDFForge({
pageSize: 'A4',
margin: { top: 40, right: 50, bottom: 60, left: 50 },
metadata: { title: 'Invoice #1042', author: 'HyperBridge Digital' },
});
// Header bar
doc.rect({ x: 0, y: 0, width: 595, height: 80, fill: '#6366f1' });
doc.setFont('Helvetica-Bold', 22);
doc.setColor('#ffffff');
doc.text('HyperBridge Digital', { x: 50, y: 28 });
doc.setFont('Helvetica', 11);
doc.text('hyperbridge.digital · Chennai, India', { x: 50, y: 52 });
// Invoice number (right-aligned)
doc.setFont('Helvetica-Bold', 18);
doc.text('INVOICE #1042', { x: 340, y: 35, align: 'right', maxWidth: 205 });
doc.setColor('#111111');
// Bill to / date block
doc.setFont('Helvetica-Bold', 10);
doc.text('BILL TO', { x: 50, y: 105 });
doc.setFont('Helvetica', 10);
doc.text('Acme Technologies\nMumbai, India\nbilling@acme.example', { x: 50, y: 118, lineHeight: 1.5 });
doc.setFont('Helvetica-Bold', 10);
doc.text('DATE', { x: 380, y: 105 });
doc.setFont('Helvetica', 10);
doc.text('April 14, 2026', { x: 380, y: 118 });
doc.text('DUE: April 28, 2026', { x: 380, y: 132 });
// Horizontal rule
doc.line({ x1: 50, y1: 175, x2: 545, y2: 175, width: 0.5, color: '#cccccc' });
// Items table
doc.table({
x: 50, y: 185,
columns: [
{ header: 'Description', width: 260 },
{ header: 'Qty', width: 55, align: 'right' },
{ header: 'Unit Price', width: 90, align: 'right' },
{ header: 'Amount', width: 90, align: 'right' },
],
rows: [
['HBForge Enterprise License (Annual)', '1', '$2,400.00', '$2,400.00'],
['Onboarding & Setup (hrs)', '4', ' $150.00', ' $600.00'],
['Priority Support (Monthly)', '3', ' $200.00', ' $600.00'],
],
headerStyle: { fillColor: '#f5f7ff', bold: true, fontSize: 10 },
cellPadding: 9,
borderColor: '#e5e7eb',
stripedRows: true,
});
// Totals
doc.setFont('Helvetica-Bold', 11);
doc.text('Total: $3,600.00', { x: 545, y: 320, align: 'right' });
// QR code for payment URL
const qr = QRCode.generate('https://pay.hyperbridge.digital/inv/1042', { size: 80 });
doc.image(qr, { x: 450, y: 680, width: 80, height: 80 });
doc.setFont('Helvetica', 8);
doc.setColor('#888888');
doc.text('Scan to pay', { x: 490, y: 765, align: 'center', maxWidth: 80 });
// Footer
doc.setFooter(({ page, total }) =>
`Page ${page} of ${total} · Thank you for your business!`
);
const pdfBytes = doc.build();
await fs.writeFile('invoice-1042.pdf', pdfBytes);
Example: Custom TTF Fonts
import { PDFForge } from '@hyperbridge/forge/pdf';
import fs from 'node:fs';
const doc = new PDFForge({ pageSize: 'A4' });
// Load custom font from TTF buffer
const interBold = fs.readFileSync('./fonts/Inter-Bold.ttf');
const interRegular = fs.readFileSync('./fonts/Inter-Regular.ttf');
doc.loadFont('Inter-Bold', interBold);
doc.loadFont('Inter-Regular', interRegular);
doc.setFont('Inter-Bold', 24);
doc.text('Custom Fonts', { x: 50, y: 60 });
doc.setFont('Inter-Regular', 12);
doc.text(
'This paragraph uses the Inter typeface, loaded from a .ttf file. ' +
'forge/pdf subsets the font automatically — only the glyphs actually ' +
'used in the document are embedded, keeping file size minimal.',
{ x: 50, y: 100, maxWidth: 495, lineHeight: 1.6 }
);
const pdf = doc.build();
await fs.promises.writeFile('custom-font.pdf', pdf);
Example: Password-Protected PDF
import { PDFForge } from '@hyperbridge/forge/pdf';
const doc = new PDFForge({
pageSize: 'A4',
encrypt: {
userPassword: 'view-password', // required to open
ownerPassword: 'admin-password', // required to change permissions
permissions: ['print'], // allow printing, disallow copy/edit
},
});
doc.setFont('Helvetica-Bold', 18);
doc.text('Confidential Report', { x: 50, y: 50 });
doc.setFont('Helvetica', 12);
doc.text('This document is encrypted. Copying is disabled.', { x: 50, y: 80 });
const pdf = doc.build();
await fs.promises.writeFile('confidential.pdf', pdf);
Example: Streaming to HTTP Response
import { PDFForge } from '@hyperbridge/forge/pdf';
// forge/server route — streams PDF directly to client
router.get('/invoices/:id/pdf', async (req, res) => {
const invoice = await Invoice.findById(req.params.id);
const doc = new PDFForge({ pageSize: 'A4' });
buildInvoiceContent(doc, invoice);
const stream = doc.build('stream');
res.setHeader('Content-Type', 'application/pdf');
res.setHeader('Content-Disposition', `attachment; filename="invoice-${invoice.id}.pdf"`);
stream.pipe(res);
});
Example: PDFReader — Extract Text
import { PDFReader } from '@hyperbridge/forge/pdf';
const reader = new PDFReader(pdfBuffer);
// Extract all text
const text = await reader.extractText();
// Get page count
const pageCount = reader.pageCount;
// Extract text per page
for (let p = 1; p <= pageCount; p++) {
const pageText = await reader.extractText({ page: p });
console.log(`Page ${p}:`, pageText);
}
// Read metadata
const meta = reader.metadata;
// { title, author, creator, createdAt, modifiedAt, pageCount }
forge/search
In-process full-text search engine with BM25 ranking, autocomplete, facets, highlights, and persistence.
Elasticsearch power, zero infrastructure. forge/search runs entirely in-process — no Java, no Elasticsearch cluster, no Docker. For up to 500K documents you get sub-millisecond BM25 search, faceted filtering, boolean queries, and fuzzy matching. The query syntax is compatible with Elasticsearch — migration is a config swap, not a rewrite.
Trie-based autocomplete under 1ms. suggest(prefix) uses a compact trie prefix index that returns results in under 1ms even with 100K+ documents — perfect for search-as-you-type without debounce hacks.
SearchForge Constructor
| Option | Type | Default | Description |
|---|---|---|---|
fields | string[] | — | Document fields to index: ['title', 'body', 'author'] |
weights | object | All 1.0 | Per-field boost: { title: 3, author: 2, body: 1 } |
tokenizer | string | fn | 'standard' | 'standard' (alphanumeric), 'whitespace', 'ngram', or custom (text) => string[] |
stemming | boolean | true | Porter stemmer: "running" → "run", "searches" → "search" |
stopWords | boolean | string[] | true | Remove common words ("the", "a", "is"). Pass array for custom list. |
minWordLength | number | 2 | Ignore tokens shorter than N characters |
persistence | string | null | File path — index is saved to disk and hot-loaded on startup |
Search Options
| Option | Type | Default | Description |
|---|---|---|---|
limit | number | 10 | Max results to return |
offset | number | 0 | Pagination offset |
fields | string[] | All indexed | Restrict search to specific fields |
filters | object | {} | Hard filter (not ranked): { category: 'guide', status: 'published' } |
facets | string[] | [] | Return value counts per field alongside results |
highlight | boolean | object | false | Return snippets with <mark> tags. { fields, contextChars } |
fuzzy | boolean | number | false | Fuzzy matching. true = edit distance 1, number = custom distance |
sort | string | object | 'score' | Sort by 'score', a field name, or { field: 'date', order: 'desc' } |
Memory sizing. The inverted index consumes ~3-5× the raw text size. 100K documents at 1KB each → expect ~400MB RAM. Use persistence to save/restore the index to disk at startup — build time is fast but not free.
Core API Reference
| Method | Returns | Description |
|---|---|---|
addDocuments(docs[]) | void | Index an array of documents in one batch call |
addDocument(doc) | void | Index a single document. Document must have a unique id field. |
removeDocument(id) | boolean | Remove document from index by ID |
updateDocument(doc) | void | Remove old document, index new version atomically |
search(query, opts?) | SearchResult[] | BM25-ranked results with optional facets and highlights |
searchAsync(query, opts?) | Promise<SearchResult[]> | Non-blocking version for large indexes |
suggest(prefix, opts?) | string[] | Autocomplete suggestions from trie index. opts: { limit, field } |
size() | number | Number of indexed documents |
clear() | void | Remove all documents and reset index |
export() | string | Serialize index to JSON string |
import(json) | void | Restore index from exported JSON |
Example: Documentation Search
import { SearchForge, Highlighter } from '@hyperbridge/forge/search';
const docs = new SearchForge({
fields: ['title', 'body', 'section'],
weights: { title: 4, section: 2, body: 1 },
stemming: true,
persistence: './data/search-index.json', // saved across restarts
});
// Index at startup
docs.addDocuments([
{ id: 'forge-client', title: 'forge/client', section: 'UI', body: 'React-like component system...' },
{ id: 'forge-server', title: 'forge/server', section: 'Backend', body: 'HTTP server with routing...' },
{ id: 'forge-auth', title: 'forge/auth', section: 'Backend', body: 'Password, JWT, OAuth2...' },
{ id: 'forge-display', title: 'forge/display',section: 'Platform', body: 'DPR detection, quality tiers...' },
// ... all 22 modules
]);
// Full-text search with highlights
const results = docs.search('jwt authentication', {
limit: 5,
highlight: { fields: ['title', 'body'], contextChars: 80 },
facets: ['section'],
});
// results[0] →
// {
// id: 'forge-auth',
// score: 4.82,
// doc: { id, title, section, body },
// highlights: {
// title: 'authentication',
// body: '...validate a JWT token with JWT.verify()...',
// },
// }
// results.facets →
// { section: { Backend: 2, UI: 1, Platform: 1 } }
// Autocomplete for search-as-you-type
docs.suggest('jw'); // ['jwt', 'jwt.sign', 'jwt.verify']
docs.suggest('an'); // ['animate', 'animation', 'and', 'api']
Example: Boolean & Faceted Search
import { SearchForge, FacetedSearch, Pagination } from '@hyperbridge/forge/search';
const index = new SearchForge({
fields: ['name', 'description', 'category', 'tags'],
weights: { name: 3, description: 1 },
});
index.addDocuments(products); // your product catalog
// Boolean query syntax
index.search('(laptop OR notebook) AND -refurbished'); // AND, OR, NOT
index.search('"wireless mouse"'); // exact phrase
index.search('title:animation'); // field-specific
index.search('price:>100 AND category:electronics'); // range filter
// Faceted search with filters
const faceted = new FacetedSearch(index, {
facets: ['category', 'brand', 'rating'],
});
const { hits, facets, total } = faceted.search('bluetooth headphones', {
filters: { category: 'electronics', rating: '5' },
limit: 20,
offset: 0,
});
// hits = [{ id, score, doc, highlights }, ...]
// facets = {
// category: { electronics: 45, accessories: 12 },
// brand: { Sony: 8, Bose: 6, JBL: 4 },
// rating: { '5': 14, '4': 18, '3': 9 },
// }
// total = 31
Example: RelevanceTuner
import { SearchForge, RelevanceTuner } from '@hyperbridge/forge/search';
const index = new SearchForge({ fields: ['title', 'body'] });
index.addDocuments(articles);
// Boost documents clicked/opened more often
const tuner = new RelevanceTuner(index);
// Record user interactions
tuner.recordClick({ query: 'react hooks', docId: 'forge-client-hooks' });
tuner.recordClick({ query: 'react hooks', docId: 'forge-client-hooks' }); // clicked twice
// Tuner blends BM25 score with engagement signals
const results = tuner.search('react hooks', { limit: 10 });
// 'forge-client-hooks' is boosted compared to raw BM25
// Decay over time — older clicks count less
tuner.setDecay({ halfLife: 7 * 86400 * 1000 }); // 7-day half-life
forge/notify
Unified notification system — DOM toasts, multi-channel delivery (email, SMS, push, WebSocket), retry queue, deduplication, and notification center.
Multi-channel, single API. forge/notify unifies DOM toasts, email, SMS, Web Push, and WebSocket delivery behind one API. Define channels once during setup, then call nm.send() — it routes to all configured transports without per-channel boilerplate.
Smart deduplication. The built-in DedupeFilter uses a djb2 hash of the notification content. Identical messages within the dedup window (default 5 s) are collapsed to one — no more stacking 10 "Connection lost" toasts when a WebSocket flickers.
ToastManager
ToastManager handles DOM toast notifications in the browser. It injects its own CSS and manages the toast container automatically.
| Option | Type | Default | Description |
|---|---|---|---|
position | string | 'top-right' | 'top-right', 'top-left', 'bottom-right', 'bottom-left', 'top-center', 'bottom-center' |
maxStack | number | 5 | Max simultaneous visible toasts. Oldest dismissed when exceeded. |
defaultDuration | number | 4000 | Auto-dismiss delay in ms. 0 = persistent (must be closed manually). |
dedupWindow | number | 5000 | Ignore duplicate content within this ms window. 0 = disabled. |
pauseOnHover | boolean | true | Pause auto-dismiss timer while mouse is over the toast |
ToastManager Methods
| Method | Returns | Description |
|---|---|---|
.success(message, opts?) | string (id) | Green success toast |
.error(message, opts?) | string | Red error toast. Default duration 6000ms (longer for errors). |
.warning(message, opts?) | string | Amber warning toast |
.info(message, opts?) | string | Blue info toast |
.loading(message, opts?) | string | Spinner toast — persists until resolved |
.promise(promise, messages) | Promise<T> | Shows loading → success/error. messages: { loading, success, error } |
.dismiss(id) | void | Dismiss specific toast by ID |
.dismissAll() | void | Dismiss all visible toasts immediately |
.update(id, opts) | void | Update content/type of an existing toast (e.g., loading → success) |
Accessibility. Toasts use role="status" for success/info and role="alert" for warnings/errors. The ARIA live region announces new toasts to screen readers without stealing focus. Modal notifications trap focus and restore it on dismiss.
NotificationManager
NotificationManager is the server-side multi-channel dispatcher. Configure channels once, then .send() routes to all of them.
| Channel key | Config fields | Description |
|---|---|---|
email | { type: 'smtp', host, port, auth, from } | Routes to SMTPClient internally |
sms | { type: 'twilio', accountSid, authToken, from } | Twilio SMS API |
push | { type: 'webpush', vapidPublicKey, vapidPrivateKey } | RFC 8291 Web Push |
websocket | { server: wsServer } | Broadcast to connected WebSocket clients |
slack | { webhookUrl } | Slack incoming webhook |
NotificationCenter
A persistent in-memory notification inbox — track read/unread state, pin, archive, group, and paginate notifications.
| Method | Returns | Description |
|---|---|---|
.add(notification) | string (id) | Add notification to center. { id, title, body, type, read, pinned, groupId } |
.getAll(opts?) | Notification[] | All notifications. opts: { page, limit, filter: 'unread'|'pinned' } |
.getUnread() | Notification[] | Only unread notifications |
.markRead(id) | void | Mark single notification as read |
.markAllRead() | void | Mark all as read |
.pin(id) | void | Pin notification (always shown at top) |
.archive(id) | void | Archive (hidden from default view) |
.unreadCount() | number | Badge count |
.on(event, fn) | void | Events: 'add', 'read', 'archive' |
RetryQueue
Automatically retries failed notification deliveries with exponential backoff.
| Option | Default | Description |
|---|---|---|
maxAttempts | 3 | Max delivery attempts before giving up |
initialDelay | 1000 | First retry delay in ms |
backoffFactor | 2 | Multiplier per attempt: 1s, 2s, 4s, 8s… |
maxDelay | 30000 | Cap on retry delay in ms |
jitter | true | Add random ±20% jitter to prevent thundering herd |
Example: Toast Notifications
import { ToastManager } from '@hyperbridge/forge/notify';
const toast = new ToastManager({ position: 'top-right', maxStack: 5 });
// Basic types
toast.success('File uploaded successfully');
toast.error('Connection failed — please retry');
toast.warning('Your session expires in 5 minutes');
toast.info('New version available — reload to update');
// Custom duration + action button
toast.success('3 files deleted', {
duration: 6000,
action: { label: 'Undo', onClick: () => restoreFiles() },
});
// Loading → result (the most common pattern)
await toast.promise(
fetch('/api/data', { method: 'POST', body: payload }),
{
loading: 'Saving changes…',
success: (data) => `Saved ${data.count} records`,
error: (err) => `Save failed: ${err.message}`,
}
);
// Update an existing toast
const id = toast.loading('Processing…');
await runHeavyTask();
toast.update(id, { type: 'success', message: 'Done!' });
Example: Multi-Channel NotificationManager
import { NotificationManager, RetryQueue } from '@hyperbridge/forge/notify';
const nm = new NotificationManager({
channels: {
email: {
type: 'smtp',
host: 'smtp.postmarkapp.com',
port: 587,
auth: { user: process.env.SMTP_USER, pass: process.env.SMTP_PASS },
from: 'noreply@myapp.com',
},
sms: {
type: 'twilio',
accountSid: process.env.TWILIO_SID,
authToken: process.env.TWILIO_TOKEN,
from: '+15551234567',
},
},
retry: new RetryQueue({ maxAttempts: 3, backoffFactor: 2 }),
});
// Route to all configured channels
await nm.send({
to: { email: 'user@example.com', phone: '+919876543210' },
channels: ['email', 'sms'],
subject: 'Security alert',
body: 'A new login was detected from Chennai, India.',
type: 'warning',
priority: 'urgent',
});
// Channel events — track delivery status
nm.on('delivered', ({ channel, notification }) =>
console.log(`Delivered via ${channel}:`, notification.id));
nm.on('failed', ({ channel, error, attempt }) =>
console.error(`Channel ${channel} failed (attempt ${attempt}):`, error));
Example: NotificationCenter
import { NotificationCenter, QuietHours } from '@hyperbridge/forge/notify';
const center = new NotificationCenter({ maxHistory: 200 });
// Add notifications (typically from server events or WebSocket)
center.add({ id: '1', title: 'PR merged', body: 'main ← feature/auth', type: 'success' });
center.add({ id: '2', title: 'Build failed', body: 'deploy-prod failed at step 3', type: 'error' });
center.add({ id: '3', title: 'New comment', body: 'Alex replied to your post', type: 'info' });
// Badge count (for notification bell icon)
center.unreadCount(); // 3
// Mark interactions
center.markRead('1');
center.pin('2'); // always at top
center.archive('3'); // hide from default view
// Paginated listing
const page1 = center.getAll({ page: 1, limit: 20 });
const unread = center.getUnread();
// Quiet hours — suppress delivery between 22:00 and 08:00
const quiet = new QuietHours({ start: '22:00', end: '08:00', timezone: 'Asia/Kolkata' });
quiet.isSilent(new Date()); // true if currently in quiet window
// Subscribe to changes (update badge in UI)
center.on('add', () => updateBadge(center.unreadCount()));
center.on('read', () => updateBadge(center.unreadCount()));
center.on('archive', () => updateBadge(center.unreadCount()));
forge/cli
CLI framework with commands, options, prompts, colors, spinners, and config management.
Commander + Inquirer + Chalk in one module. forge/cli replaces the classic CLI trio — commander for parsing, inquirer for prompts, chalk for colors — with a single zero-dep module. Define commands, add interactive prompts, show spinners, and format output, all from one import.
Auto-generated help. Every command automatically gets --help output with usage, options, and examples. The help text is generated from your command definitions — no manual formatting. Run myapp --help and get a polished, colored help screen.
CLI Constructor
| Parameter | Type | Default | Description |
|---|---|---|---|
name | string | — | CLI tool name (used in help text and error messages) |
version | string | — | Version string (enables --version flag automatically) |
description | string | '' | Tool description shown in help |
strict | boolean | true | Error on unknown options (disable for passthrough CLIs) |
Command Definition
| Method | Parameters | Description |
|---|---|---|
command(name, handler) | string, async function | Register a command. Handler receives (args, options) |
option(name, config) | string, { alias, type, default, description } | Add option to current command |
argument(name, config) | string, { required, description } | Add positional argument |
example(usage, description) | string, string | Add example to help text |
parse(argv) | string[] | Parse arguments and execute matched command |
Interactive Prompts
| Method | Returns | Description |
|---|---|---|
Prompt.input(message) | Promise<string> | Free text input |
Prompt.confirm(message) | Promise<boolean> | Yes/no confirmation |
Prompt.password(message) | Promise<string> | Hidden input (masked with •••) |
Prompt.select(message, choices) | Promise<string> | Arrow-key selection from list |
Prompt.multiSelect(message, choices) | Promise<string[]> | Multi-select with space to toggle |
CI environments. Interactive prompts detect non-TTY environments (CI/CD pipelines, cron jobs) and automatically use default values instead of blocking. Override with Prompt.input('Name:', { nonInteractive: 'DefaultName' }) to control fallback behavior explicitly.
Core APIs
- CLI - Main CLI class
- command(name, fn) - Define command
- option(name, config) - Add option/flag
- argument(name, config) - Add argument
- Prompt - Interactive prompts
- Spinner - Loading spinner
- ProgressBar - Progress visualization
- colors - Terminal colors
- table - Format data tables
- log - Logging utilities
Example: CLI Application
import { CLI, Prompt, Spinner, colors } from '@hyperbridge/forge/cli';
const cli = new CLI({
name: 'myapp',
version: '1.0.0',
description: 'My awesome CLI tool',
});
// Define command
cli.command('create', async (args, options) => {
const name = await Prompt.input('Project name:');
const spinner = new Spinner(`Creating ${name}...`);
spinner.start();
// Do work...
await new Promise(r => setTimeout(r, 2000));
spinner.succeed(`Project created: ${colors.green(name)}`);
});
// Define another command
cli.command('build', async (args, options) => {
const watch = options.watch || false;
console.log(`Building${watch ? ' (watch)' : ''}...`);
// Build logic...
});
cli.command('serve', async (args, options) => {
const port = options.port || 3000;
console.log(`Server running on ${colors.cyan(`http://localhost:${port}`)}`);
});
// Parse and execute
cli.parse(process.argv);
Example: Interactive Prompts
import { Prompt, colors } from '@hyperbridge/forge/cli';
// Text input
const name = await Prompt.input('Your name:');
// Confirmation
const proceed = await Prompt.confirm('Continue?');
// Select from options
const choice = await Prompt.select('Choose one:', [
{ label: 'Option A', value: 'a' },
{ label: 'Option B', value: 'b' },
{ label: 'Option C', value: 'c' },
]);
// Multi-select
const tags = await Prompt.multiSelect('Select tags:', [
'important',
'urgent',
'feature',
'bugfix',
]);
// Display results
console.log(`${colors.green('✓')} Name: ${name}`);
console.log(`${colors.blue('ℹ')} Choice: ${choice}`);
console.log(`${colors.yellow('⚠')} Tags: ${tags.join(', ')}`);
forge/i18n
Internationalization with translations, formatting, and locale management.
ICU MessageFormat compatible. forge/i18n handles plurals, gender, ordinals, and select statements following the ICU MessageFormat standard used by Java's ResourceBundle and Android's string resources. Complex plural rules for languages like Arabic (zero, one, two, few, many, other) work out of the box.
Intl API powered. Date, number, currency, list, and relative time formatting delegates to the native Intl APIs — so you get full CLDR locale data for 400+ locales without bundling any data files. The formatted output matches what users expect in their region.
I18n Constructor
| Parameter | Type | Default | Description |
|---|---|---|---|
defaultLocale | string | 'en' | Default locale (BCP 47 tag, e.g., 'en-US', 'fr-FR', 'ja') |
fallbackLocale | string | 'en' | Fallback when translation key is missing in current locale |
missingKeyHandler | function | Returns key | Custom handler: (key, locale) => string |
interpolation | object | { prefix: '{', suffix: '}' } | Variable delimiters in translation strings |
Translation API
| Method | Returns | Description |
|---|---|---|
t(key, context?) | string | Translate key with optional interpolation context |
addMessages(locale, messages) | void | Register translations for a locale (deep-merged with existing) |
setLocale(locale) | void | Switch active locale (triggers re-renders in forge/client) |
getLocale() | string | Get current active locale |
hasKey(key, locale?) | boolean | Check if translation exists |
pluralize(count, keys) | string | Select plural form based on count and locale rules |
isRTL(locale?) | boolean | Check if locale is right-to-left (Arabic, Hebrew, Urdu, etc.) |
Formatting API
| Method | Returns | Description |
|---|---|---|
formatDate(date, locale?) | string | Locale-aware date string |
formatTime(date, locale?) | string | Locale-aware time string |
formatNumber(num, locale?) | string | Locale-aware number with grouping separators |
formatCurrency(amount, currency, locale?) | string | Currency formatting (symbol, position, decimals) |
formatRelative(date) | string | Relative time (e.g., "2 hours ago", "in 3 days") |
formatList(items, locale?) | string | Locale-aware list ("A, B, and C" vs "A, B y C") |
SSR hydration. When using forge/i18n with server-side rendering, pass the locale in the initial state to avoid hydration mismatches. The server and client must agree on the locale before the first render — use Accept-Language header detection on the server and navigator.language on the client.
Don't concatenate translated strings. Instead of t('hello') + ' ' + t('world'), use a single key with interpolation: t('greeting', { name: 'World' }). Word order varies by language — Japanese puts the subject last, Arabic reads right-to-left. Let the translator control the full sentence structure.
Core APIs
- I18n - Main i18n class
- t(key, context) - Translate key
- setLocale(locale) - Set current locale
- getLocale() - Get current locale
- formatDate(date, locale) - Format date
- formatTime(date, locale) - Format time
- formatNumber(num, locale) - Format number
- formatCurrency(amount, currency) - Format currency
- formatList(items) - Format list
- formatRelative(date) - Relative time (e.g., "2 hours ago")
- pluralize(count, keys) - Plural handling
- isRTL(locale) - Check RTL language
Example: Setup & Usage
import { I18n } from '@hyperbridge/forge/i18n';
const i18n = new I18n({
defaultLocale: 'en',
fallbackLocale: 'en',
});
// Register translations
i18n.addMessages('en', {
'greeting': 'Hello, {name}!',
'items.one': 'You have 1 item',
'items.other': 'You have {count} items',
'welcome': 'Welcome to {app}',
});
i18n.addMessages('es', {
'greeting': '¡Hola, {name}!',
'items.one': 'Tienes 1 artículo',
'items.other': 'Tienes {count} artículos',
'welcome': 'Bienvenido a {app}',
});
// Translate
console.log(i18n.t('greeting', { name: 'John' }));
// en: "Hello, John!"
// es: "¡Hola, John!"
// Pluralize
console.log(i18n.t('items', { count: 1 }));
// "You have 1 item" (en) / "Tienes 1 artículo" (es)
console.log(i18n.t('items', { count: 5 }));
// "You have 5 items" (en) / "Tienes 5 artículos" (es)
Example: Formatting
import { I18n } from '@hyperbridge/forge/i18n';
const i18n = new I18n({ defaultLocale: 'en-US' });
// Date formatting
const date = new Date('2026-03-15');
console.log(i18n.formatDate(date)); // "3/15/2026"
console.log(i18n.formatDate(date, 'es-ES')); // "15/3/2026"
// Number formatting
console.log(i18n.formatNumber(1234.56)); // "1,234.56" (en-US)
console.log(i18n.formatNumber(1234.56, 'de-DE')); // "1.234,56"
// Currency formatting
console.log(i18n.formatCurrency(99.99, 'USD')); // "$99.99"
console.log(i18n.formatCurrency(99.99, 'EUR', 'de-DE')); // "99,99 €"
// Relative time
console.log(i18n.formatRelative(new Date() - 3600000)); // "1 hour ago"
console.log(i18n.formatRelative(new Date() + 86400000)); // "in 1 day"
// List formatting
const items = ['Apple', 'Banana', 'Orange'];
console.log(i18n.formatList(items)); // "Apple, Banana, and Orange"
console.log(i18n.formatList(items, 'es-ES')); // "Apple, Banana y Orange"
forge/pwa
Progressive Web App toolkit — Service Workers, offline caching, Web Push (RFC 8291/8292), Background Sync, App Shell, and install management. Replaces workbox, web-push, next-pwa, and vite-plugin-pwa with zero dependencies.
Two runtime contexts. Some forge/pwa classes (PWAForge, PushManager, PrecacheManager) run in Node.js — they generate Service Worker code and send push payloads. Others (BackgroundSync, AppShell, UpdateManager, BadgeManager) run inside the Service Worker. The module detects its context automatically.
No build step required. Unlike Workbox, forge/pwa does not need webpack/Vite plugin integration. Generate the SW code once with PWAForge.generateServiceWorkerCode(), write it to /sw.js, and register it. Works with any bundler or no bundler at all.
PWAForge — Manifest & Service Worker
| Option | Type | Description |
|---|---|---|
name | string | Full app name (shown on install prompt and splash screen) |
shortName | string | Short name for home screen icon (≤12 characters recommended) |
description | string | App description (shown in app stores) |
themeColor | string | Browser chrome color (hex) |
backgroundColor | string | Splash screen background color |
display | string | 'standalone' (no browser chrome), 'fullscreen', 'minimal-ui', 'browser' |
startUrl | string | URL opened when app is launched from home screen. Default '/'. |
scope | string | Navigation scope. Default '/'. |
icons | object[] | Array of { src, sizes, type, purpose }. Minimum: 192×192 and 512×512 PNG. |
shortcuts | object[] | Quick-action shortcuts in the OS long-press menu |
screenshots | object[] | App store screenshots for install prompt |
CacheStrategy
Five built-in strategies matching Workbox's patterns, each with configurable options:
| Strategy | Behaviour | Best for |
|---|---|---|
CacheFirst | Serve from cache, fall back to network. Update cache on miss. | Static assets (JS, CSS, fonts, images) |
NetworkFirst | Try network, fall back to cache on timeout or failure. | API responses, dynamic HTML pages |
StaleWhileRevalidate | Serve from cache immediately, update cache in background. | Avatar images, non-critical API data |
NetworkOnly | Always go to network. Cache never used. | Analytics, payment endpoints |
CacheOnly | Always serve from cache. Error if not cached. | Precached offline fallback resources |
PushManager
| Method | Returns | Description |
|---|---|---|
generateVAPIDKeys() | { publicKey, privateKey } | P-256 ECDH key pair for VAPID authentication. Generate once, store in env vars. |
sendNotification(sub, payload, opts) | Promise<void> | Send encrypted Web Push (RFC 8291/8292). sub = browser subscription object. |
sendBatch(subs[], payload, opts) | Promise<BatchResult> | Send to multiple subscribers with concurrency control and failure tracking |
Key API Reference
| Class | Key Methods | Description |
|---|---|---|
PWAForge | generateManifest(), generateServiceWorkerCode(opts) | Create manifest JSON and complete SW script |
PrecacheManager | .add(urls[]), .install(), .activate() | Precache app shell at SW install time |
CacheStrategy | CacheFirst(opts), NetworkFirst(opts), StaleWhileRevalidate(opts) | Route-level caching strategy factories |
PushManager | generateVAPIDKeys(), sendNotification(), sendBatch() | Web Push via RFC 8291/8292 |
BackgroundSync | registerSync(name), onSync(fn) | Queue failed requests, replay when online |
OfflineManager | enableOfflineMode(), onOnline(fn), onOffline(fn) | Online/offline detection and request queuing |
AppShell | generateShellCode(opts) | SPA app shell SW pattern — serve index.html for all navigation requests |
BadgeManager | setBadge(n), clearBadge() | App icon badge (unread count) via Badging API |
UpdateManager | checkForUpdates(), onUpdateFound(fn), applyUpdate() | Detect new SW version, prompt user, apply update |
generateVAPIDKeys() | standalone export | Convenience wrapper — same as new PushManager().generateVAPIDKeys() |
Example: Full PWA Setup
// build-sw.js — run during build to generate /public/sw.js
import { PWAForge, CacheStrategy, AppShell } from '@hyperbridge/forge/pwa';
import fs from 'node:fs';
const pwa = new PWAForge({
name: 'HBForge Docs',
shortName: 'HBForge',
themeColor: '#6366f1',
backgroundColor: '#0a0a0f',
display: 'standalone',
startUrl: '/',
icons: [
{ src: '/icons/icon-192.png', sizes: '192x192', type: 'image/png' },
{ src: '/icons/icon-512.png', sizes: '512x512', type: 'image/png', purpose: 'maskable' },
],
});
// 1. Serve manifest.json
fs.writeFileSync('./public/manifest.json', JSON.stringify(pwa.generateManifest(), null, 2));
// 2. Generate service worker
const sw = pwa.generateServiceWorkerCode({
version: '1.2.0',
offlineFallback: '/offline.html',
// Precache app shell
precacheUrls: ['/', '/app.js', '/app.css', '/offline.html', '/icons/icon-192.png'],
// Route-level strategies
routes: [
{ match: '/api/*', strategy: CacheStrategy.NetworkFirst({ timeout: 3000, cacheName: 'api-v1' }) },
{ match: '/images/*', strategy: CacheStrategy.CacheFirst({ cacheName: 'images-v1', maxEntries: 200, maxAge: 30 * 86400 }) },
{ match: '/*', strategy: CacheStrategy.StaleWhileRevalidate({ cacheName: 'pages-v1' }) },
],
// SPA — serve index.html for all navigation
appShell: AppShell.generateShellCode({ shell: '/index.html' }),
});
fs.writeFileSync('./public/sw.js', sw);
console.log('Service worker generated.');
Example: Web Push
// 1. GENERATE VAPID KEYS ONCE (store in environment variables)
import { generateVAPIDKeys } from '@hyperbridge/forge/pwa';
const keys = generateVAPIDKeys();
// { publicKey: 'BN...', privateKey: 'k2...' }
// → VAPID_PUBLIC_KEY=BN... VAPID_PRIVATE_KEY=k2...
// 2. CLIENT — subscribe the browser
const registration = await navigator.serviceWorker.ready;
const subscription = await registration.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: process.env.VAPID_PUBLIC_KEY,
});
await fetch('/api/push/subscribe', {
method: 'POST',
body: JSON.stringify(subscription),
});
// 3. SERVER — send notifications
import { PushManager } from '@hyperbridge/forge/pwa';
const push = new PushManager({
vapidPublicKey: process.env.VAPID_PUBLIC_KEY,
vapidPrivateKey: process.env.VAPID_PRIVATE_KEY,
subject: 'mailto:hello@hyperbridge.digital',
});
// Send to one subscriber
await push.sendNotification(subscription, {
title: 'New message',
body: 'You have 3 unread messages',
icon: '/icons/icon-192.png',
badge: '/icons/badge-72.png',
url: '/messages',
actions: [
{ action: 'view', title: 'View' },
{ action: 'dismiss', title: 'Dismiss' },
],
});
// Bulk send (e.g. broadcast to all users)
const results = await push.sendBatch(allSubscriptions, payload, { concurrency: 50 });
console.log(`Sent: ${results.sent}, Failed: ${results.failed}`);
Example: Background Sync
// In your app code — queue a request when offline
import { BackgroundSync } from '@hyperbridge/forge/pwa';
const sync = new BackgroundSync({ queueName: 'form-submissions' });
async function submitForm(data) {
try {
await fetch('/api/submit', { method: 'POST', body: JSON.stringify(data) });
} catch (err) {
// Network failed — queue for later
await sync.enqueue({ url: '/api/submit', method: 'POST', body: JSON.stringify(data) });
console.log('Queued for background sync when connection restores');
}
}
// In service-worker.js — replay queued requests on sync event
self.addEventListener('sync', (event) => {
if (event.tag === 'form-submissions') {
event.waitUntil(sync.replay());
}
});
Example: UpdateManager
// In your app — prompt user when a new SW version is available
import { UpdateManager } from '@hyperbridge/forge/pwa';
const updater = new UpdateManager('/sw.js');
updater.onUpdateFound(() => {
// Show a toast or banner
const banner = document.createElement('div');
banner.textContent = 'App updated — reload to apply';
const btn = document.createElement('button');
btn.textContent = 'Reload';
btn.onclick = () => updater.applyUpdate(); // skipWaiting + reload
banner.appendChild(btn);
document.body.prepend(banner);
});
// Check on focus (catches updates while tab was in background)
window.addEventListener('focus', () => updater.checkForUpdates());
forge/3d
NEWWebGL2-based 3D engine — scene graph, PBR materials, cameras, lights, post-processing, particle systems, animation, and controls. A zero-dependency Three.js alternative with 368 APIs in 5,400 lines.
Browser-only: forge/3d requires a WebGL2-capable browser. It runs entirely on the GPU via the WebGL2 API — no server-side rendering, no native dependencies.
Installation
import {
Scene, WebGLRenderer, PerspectiveCamera, OrthographicCamera,
Mesh, BoxGeometry, SphereGeometry, PlaneGeometry, StandardMaterial,
AmbientLight, DirectionalLight, PointLight,
OrbitControls, AnimationMixer, EffectComposer
} from '@hyperbridge/forge/3d';
Quick Start — Rotating Cube
import {
Scene, WebGLRenderer, PerspectiveCamera,
Mesh, BoxGeometry, StandardMaterial,
AmbientLight, DirectionalLight, Clock
} from '@hyperbridge/forge/3d';
// Create scene
const scene = new Scene();
const camera = new PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.set(0, 2, 5);
camera.lookAt(0, 0, 0);
// Create renderer
const renderer = new WebGLRenderer({ canvas: document.getElementById('canvas'), antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setClearColor(0x0a0a0f);
// Add lights
scene.add(new AmbientLight(0x404040, 0.5));
const dirLight = new DirectionalLight(0xffffff, 1.0);
dirLight.position.set(5, 10, 7);
scene.add(dirLight);
// Create cube
const cube = new Mesh(
new BoxGeometry(1, 1, 1),
new StandardMaterial({ color: 0x6366f1, metalness: 0.3, roughness: 0.4 })
);
scene.add(cube);
// Animation loop
const clock = new Clock();
function animate() {
requestAnimationFrame(animate);
const dt = clock.getDelta();
cube.rotation.x += 0.5 * dt;
cube.rotation.y += 0.8 * dt;
renderer.render(scene, camera);
}
animate();
Math Primitives
Full linear algebra library for 3D math — vectors, matrices, quaternions, rays, and bounding volumes.
Vec2 / Vec3 / Vec4
2D, 3D, and 4D vector types with add, sub, scale, dot, cross, normalize, lerp, distance, and more.
Mat3 / Mat4
3x3 and 4x4 matrix types — multiply, invert, transpose, determinant, lookAt, perspective, ortho projections.
Quat
Quaternion rotation — fromAxisAngle, fromEuler, slerp, conjugate, multiply, toMat4. Avoids gimbal lock.
Euler
Euler angle representation with XYZ/YXZ/ZYX order. Converts to/from Quat and Mat4.
Color
RGB/HSL color with hex parsing, lerp, multiply, gamma/linear conversion. Feeds into materials and lights.
Ray / Box3 / Sphere
Ray casting, AABB bounding boxes, bounding spheres — intersect tests, containment, expansion, clamp.
import { Vec3, Mat4, Quat, Color } from '@hyperbridge/forge/3d';
const v = new Vec3(1, 2, 3);
const n = v.normalize(); // unit vector
const d = v.dot(new Vec3(0, 1, 0)); // dot product
const m = Mat4.perspective(Math.PI / 4, 16 / 9, 0.1, 100);
const q = Quat.fromAxisAngle(new Vec3(0, 1, 0), Math.PI / 2);
const c = new Color(0x6366f1);
const lighter = c.lerp(new Color(0xffffff), 0.3);
Geometry
Built-in geometry primitives with customizable segments, plus a base class for custom geometry.
BufferGeometry
Base class — custom vertex positions, normals, UVs, indices. setAttribute / setIndex API.
BoxGeometry
Box with width, height, depth and configurable segments per axis.
SphereGeometry
UV sphere with radius, widthSegments, heightSegments, phi/theta range.
PlaneGeometry
Flat plane with width, height, and segment count. Ideal for floors, walls, water.
IcosahedronGeometry
Geodesic sphere from icosahedron subdivision. Smoother than UV sphere at low poly.
CylinderGeometry
Cylinder / cone with radiusTop, radiusBottom, height, radialSegments, openEnded.
InstancedMesh
GPU-instanced rendering — draw thousands of copies with per-instance transforms and colors.
import { BoxGeometry, SphereGeometry, CylinderGeometry, InstancedMesh, Mesh, StandardMaterial } from '@hyperbridge/forge/3d';
const box = new BoxGeometry(2, 2, 2);
const sphere = new SphereGeometry(1, 32, 32);
const cylinder = new CylinderGeometry(0.5, 0.5, 3, 16);
// Instanced rendering — 10,000 cubes in one draw call
const instanced = new InstancedMesh(box, new StandardMaterial({ color: 0x22d3ee }), 10000);
for (let i = 0; i < 10000; i++) {
instanced.setMatrixAt(i, Mat4.translation(Math.random() * 100, Math.random() * 100, Math.random() * 100));
}
scene.add(instanced);
Materials
PBR and non-PBR material types for every rendering need — from wireframe debugging to physically-based metallic/roughness workflows.
BasicMaterial
Unlit flat color — no lighting calculations. Fastest option for UI overlays and debug visuals.
StandardMaterial
PBR metallic-roughness workflow with color, metalness, roughness, emissive, normalMap, aoMap.
PhongMaterial
Classic Blinn-Phong shading with ambient, diffuse, specular, shininess. Good for stylized rendering.
WireframeMaterial
Wireframe overlay with configurable line width and color. Great for debugging and technical visuals.
ShaderMaterial
Custom GLSL vertex/fragment shaders with uniform bindings. Full control over the rendering pipeline.
import { StandardMaterial, ShaderMaterial } from '@hyperbridge/forge/3d';
const pbr = new StandardMaterial({
color: 0x6366f1,
metalness: 0.8,
roughness: 0.2,
emissive: 0x111111,
});
const custom = new ShaderMaterial({
vertexShader: `
attribute vec3 position;
uniform mat4 projectionMatrix, modelViewMatrix;
void main() { gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); }
`,
fragmentShader: `
precision mediump float;
uniform float time;
void main() { gl_FragColor = vec4(sin(time), 0.4, 0.9, 1.0); }
`,
uniforms: { time: { value: 0 } }
});
Scene Graph
Hierarchical scene graph with parent-child transforms, groups, and visibility control.
Scene
Root container. Holds all objects, lights, and environment settings (background, fog).
Object3D
Base class for all scene objects — position, rotation, scale, visible, parent, children, add/remove.
Group
Empty container for grouping objects. Transforms propagate to children.
Mesh
Geometry + Material pair. The primary visible object in the scene.
LineSegments
Renders line segments from vertex pairs. Used for grids, wireframes, debug outlines.
import { Scene, Group, Mesh, BoxGeometry, StandardMaterial } from '@hyperbridge/forge/3d';
const scene = new Scene();
scene.background = new Color(0x0a0a0f);
const group = new Group();
group.position.set(0, 1, 0);
const mesh = new Mesh(new BoxGeometry(1, 1, 1), new StandardMaterial({ color: 0x22d3ee }));
group.add(mesh);
scene.add(group);
group.rotation.y = Math.PI / 4; // rotates all children
Cameras
PerspectiveCamera
Standard perspective projection — fov, aspect, near, far. Mimics human eye depth perception.
OrthographicCamera
Orthographic projection — left, right, top, bottom, near, far. Ideal for 2D overlays, isometric views.
import { PerspectiveCamera, OrthographicCamera } from '@hyperbridge/forge/3d';
const cam = new PerspectiveCamera(60, 16 / 9, 0.1, 1000);
cam.position.set(0, 5, 10);
cam.lookAt(0, 0, 0);
const ortho = new OrthographicCamera(-10, 10, 10, -10, 0.1, 100);
Lights
AmbientLight
Uniform light in all directions. No shadows. Sets base illumination level for the scene.
DirectionalLight
Parallel rays from a direction (like the sun). Supports shadow mapping.
PointLight
Omni-directional point source with distance and decay. Light bulbs, candles, explosions.
SpotLight
Cone-shaped light with angle, penumbra, distance, decay. Flashlights, stage lights.
HemisphereLight
Sky + ground gradient light. Simulates outdoor ambient with sky color above and ground color below.
import { AmbientLight, DirectionalLight, PointLight, SpotLight, HemisphereLight } from '@hyperbridge/forge/3d';
scene.add(new AmbientLight(0x404040, 0.4));
const sun = new DirectionalLight(0xffffff, 1.0);
sun.position.set(10, 20, 10);
scene.add(sun);
const bulb = new PointLight(0xff9900, 1.0, 50, 2);
bulb.position.set(0, 3, 0);
scene.add(bulb);
const spot = new SpotLight(0xffffff, 1.0, 100, Math.PI / 6, 0.5, 2);
spot.position.set(0, 10, 0);
scene.add(spot);
scene.add(new HemisphereLight(0x87ceeb, 0x362907, 0.6));
Renderer
WebGLRenderer
WebGL2-based renderer — antialias, alpha, stencil, depth, preserveDrawingBuffer, pixelRatio, shadow maps, tone mapping, gamma correction.
import { WebGLRenderer } from '@hyperbridge/forge/3d';
const renderer = new WebGLRenderer({
canvas: document.getElementById('canvas'),
antialias: true,
alpha: false,
stencil: true,
});
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setClearColor(0x0a0a0f, 1);
renderer.shadowMap.enabled = true;
renderer.toneMapping = 'aces';
renderer.render(scene, camera);
Animation
Keyframe animation system with mixers, clips, actions, and blending — animate any property over time.
AnimationMixer
Drives animations on a target object. Manages multiple clips, blending, and cross-fading.
AnimationClip
Container for keyframe tracks. Defines a named animation with duration.
AnimationAction
Playback control — play, pause, stop, loop, clampWhenFinished, timeScale, weight, crossFadeTo.
KeyframeTrack
Typed keyframe data — NumberKeyframeTrack, VectorKeyframeTrack, QuaternionKeyframeTrack.
Clock
High-resolution timer — getDelta(), getElapsedTime(). Drives animation loops.
import { AnimationMixer, AnimationClip, KeyframeTrack, Clock } from '@hyperbridge/forge/3d';
const times = [0, 1, 2];
const values = [0, 0, 0, 0, 3, 0, 0, 0, 0]; // Vec3 keyframes
const track = new KeyframeTrack('.position', times, values);
const clip = new AnimationClip('bounce', 2, [track]);
const mixer = new AnimationMixer(cube);
const action = mixer.clipAction(clip);
action.setLoop('repeat');
action.play();
const clock = new Clock();
function animate() {
requestAnimationFrame(animate);
mixer.update(clock.getDelta());
renderer.render(scene, camera);
}
animate();
Post-Processing
Multi-pass post-processing pipeline — bloom, SSAO, glitch, film grain, and more via composable passes.
EffectComposer
Manages a chain of render passes. Reads from/writes to framebuffers for multi-pass effects.
RenderPass
Renders the scene/camera to a framebuffer. Usually the first pass in the chain.
UnrealBloomPass
HDR bloom with threshold, strength, and radius. Emissive materials glow realistically.
SSAOPass
Screen-space ambient occlusion — darkens creases and contact edges for added depth.
GlitchPass
Digital glitch effect with configurable intensity and frequency. Great for cyberpunk aesthetics.
FilmPass
Film grain, scanlines, and vignette for a cinematic look.
import { EffectComposer, RenderPass, UnrealBloomPass, SSAOPass } from '@hyperbridge/forge/3d';
const composer = new EffectComposer(renderer);
composer.addPass(new RenderPass(scene, camera));
composer.addPass(new UnrealBloomPass({ threshold: 0.8, strength: 1.5, radius: 0.4 }));
composer.addPass(new SSAOPass(scene, camera, { radius: 0.5, minDistance: 0.001, maxDistance: 0.1 }));
// Replace renderer.render() with:
function animate() {
requestAnimationFrame(animate);
composer.render();
}
animate();
Controls
Camera and object interaction — orbit, fly, pointer lock, drag, and first-person controls.
OrbitControls
Orbit around a target with mouse drag, scroll zoom, right-click pan. Auto-damping, min/max distance.
FlyControls
Free-flight camera with WASD movement and mouse look. Configurable speed and roll.
PointerLockControls
First-person mouse look with pointer lock API. Ideal for FPS-style navigation.
DragControls
Drag objects in 3D space with mouse. Fires dragstart, drag, dragend events.
FirstPersonControls
WASD + mouse look without pointer lock. Smooth movement with acceleration and deceleration.
import { OrbitControls, FlyControls } from '@hyperbridge/forge/3d';
const controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
controls.dampingFactor = 0.05;
controls.minDistance = 2;
controls.maxDistance = 50;
controls.target.set(0, 1, 0);
function animate() {
requestAnimationFrame(animate);
controls.update();
renderer.render(scene, camera);
}
Particle Systems
CPU and GPU particle emitters with trail rendering and configurable force fields.
ParticleEmitter
CPU-based particle emitter — rate, lifetime, velocity, color over life, size over life, gravity.
GPUParticleSystem
Transform-feedback GPU particles — millions of particles with minimal CPU overhead.
TrailRenderer
Ribbon trail effect following moving objects. Width, color, and fade over lifetime.
Force Types
Gravity, wind, turbulence, vortex, attractor, repulsor — composable force fields for particles.
import { ParticleEmitter, GPUParticleSystem, TrailRenderer } from '@hyperbridge/forge/3d';
const emitter = new ParticleEmitter({
maxParticles: 5000,
rate: 200,
lifetime: [1, 3],
velocity: { x: [-1, 1], y: [2, 5], z: [-1, 1] },
color: [0x6366f1, 0x22d3ee],
size: [0.1, 0.5],
gravity: -9.8,
});
scene.add(emitter);
const trail = new TrailRenderer({ width: 0.2, length: 20, color: 0x6366f1, fade: true });
trail.attach(movingObject);
scene.add(trail);
Raycaster & Helpers
Raycaster
Cast rays into the scene — mouse picking, collision detection, intersection tests with meshes.
AxesHelper
RGB axes indicator (X=red, Y=green, Z=blue) for orientation debugging.
GridHelper
Ground-plane grid with configurable size, divisions, and colors.
ArrowHelper
Directional arrow for visualizing vectors, normals, and forces.
import { Raycaster, AxesHelper, GridHelper } from '@hyperbridge/forge/3d';
scene.add(new AxesHelper(5));
scene.add(new GridHelper(20, 20));
const raycaster = new Raycaster();
canvas.addEventListener('click', (event) => {
const x = (event.clientX / window.innerWidth) * 2 - 1;
const y = -(event.clientY / window.innerHeight) * 2 + 1;
raycaster.setFromCamera({ x, y }, camera);
const hits = raycaster.intersectObjects(scene.children);
if (hits.length > 0) console.log('Hit:', hits[0].object, 'at', hits[0].point);
});
Curves & Loaders
CatmullRomCurve3 / LineCurve3
3D spline and line curves — getPoint(t), getPoints(n), getTangent(t), getLength(). Camera paths, extrusions.
TextureLoader / OBJLoader / GLTFLoader
Asset loaders — load textures (PNG/JPG), OBJ meshes, and glTF 2.0 scenes with materials and animations.
import { CatmullRomCurve3, Vec3, TextureLoader } from '@hyperbridge/forge/3d';
const curve = new CatmullRomCurve3([
new Vec3(-5, 0, 0), new Vec3(-2, 3, 0),
new Vec3(2, -1, 0), new Vec3(5, 2, 0)
]);
const points = curve.getPoints(50); // 50 sample points along the spline
const texture = await new TextureLoader().load('/textures/brick.jpg');
const wall = new Mesh(
new PlaneGeometry(10, 5),
new StandardMaterial({ map: texture, roughness: 0.9 })
);
Key APIs
| Category | API | Description |
|---|---|---|
| Math | Vec2, Vec3, Vec4, Mat3, Mat4, Quat, Euler, Color, Ray, Box3, Sphere | Linear algebra, bounding volumes, color |
| Geometry | BufferGeometry, BoxGeometry, SphereGeometry, PlaneGeometry, IcosahedronGeometry, CylinderGeometry | Built-in geometry primitives |
| Instancing | InstancedMesh | GPU-instanced rendering for thousands of objects |
| Materials | BasicMaterial, StandardMaterial, PhongMaterial, WireframeMaterial, ShaderMaterial | PBR, classic shading, custom GLSL |
| Scene | Scene, Object3D, Group, Mesh, LineSegments | Hierarchical scene graph |
| Cameras | PerspectiveCamera, OrthographicCamera | Perspective and orthographic projection |
| Lights | AmbientLight, DirectionalLight, PointLight, SpotLight, HemisphereLight | Scene illumination and shadow casting |
| Renderer | WebGLRenderer | WebGL2-based renderer with shadow maps, tone mapping |
| Animation | AnimationMixer, AnimationClip, AnimationAction, Clock, KeyframeTrack | Keyframe animation and blending |
| Post-FX | EffectComposer, RenderPass, UnrealBloomPass, SSAOPass, GlitchPass, FilmPass | Multi-pass post-processing pipeline |
| Controls | OrbitControls, FlyControls, PointerLockControls, DragControls, FirstPersonControls | Camera and object interaction |
| Particles | ParticleEmitter, GPUParticleSystem, TrailRenderer | CPU/GPU particles and trails |
| Raycaster | Raycaster | Mouse picking and collision detection |
| Helpers | AxesHelper, GridHelper, ArrowHelper | Visual debugging aids |
| Curves | CatmullRomCurve3, LineCurve3 | 3D spline and line curves |
| Loaders | TextureLoader, OBJLoader, GLTFLoader | Asset loading (images, meshes, scenes) |
368 APIs, 5,400 lines, zero dependencies. A complete Three.js alternative built from scratch — pure WebGL2 with no external packages.
forge/display
Retina, 4K, and device capability detection with an adaptive quality tier system. Pixel-perfect canvas scaling, WebGL DPR setup, per-tier variant resolution, and deep integration with forge/3d, forge/animate, and forge/client. 60+ APIs. 955 lines. Zero dependencies.
forge/display uses a composite scoring system (0–100) that weighs GPU tier, device RAM, CPU cores, DPR, HDR support, and network quality to produce one of four quality tiers: low, medium, high, or ultra. All variant APIs resolve against this tier, so a single config object works correctly on a 2015 budget phone, a MacBook Pro Retina, and a 4K workstation with a discrete GPU — with no manual branching in application code.
Installation & Import
// Named import (tree-shakeable)
import Display from '@hyperbridge/forge/display';
// Or via the full bundle
import forge from '@hyperbridge/forge';
const Display = forge.Display;
Quality Tier & Scoring
| API | Returns | Description |
|---|---|---|
qualityTier() | 'low'|'medium'|'high'|'ultra' | Composite tier from GPU, RAM, CPU, DPR, network, and accessibility prefs |
qualityScore() | number | Raw score 0–100 for fine-grained comparisons |
snapshot() | DisplaySnapshot | Full immutable capability snapshot, cached until DPR changes |
clearSnapshot() | void | Invalidate cache — call after system settings change |
Score breakdown
| Signal | Points |
|---|---|
| GPU high / mid / low | +50 / +30 / +10 |
| RAM+CPU high / mid / low | +25 / +15 / +5 |
| DPR ≥ 3 / ≥ 2 | +20 / +10 |
| WebGL 2 | +5 |
| HDR display | +3 |
| Save-data mode | −30 |
| Slow connection | −10 |
| Reduced motion | −5 |
const tier = Display.qualityTier(); // 'ultra'
const score = Display.qualityScore(); // 87
console.log(Display.profileString());
// "ultra|4k|p3|hdr|retina|2x|gpu:high|ram:16|cores:12|score:87"
Device Pixel Ratio
| API | Returns | Description |
|---|---|---|
getDPR(maxDPR?) | number | DPR clamped to maxDPR (default 4). Use for canvas/WebGL sizing. |
getDPRRaw() | number | Unclamped DPR for analytics |
isRetina() | boolean | DPR ≥ 2 (standard Retina / HiDPI) |
isHiDPI() | boolean | DPR > 1 (any non-standard display) |
is4K() | boolean | Physical or logical×DPR ≥ 3840px wide |
is8K() | boolean | Physical ≥ 7680px wide |
resolutionTier() | 'sd'|'hd'|'fhd'|'qhd'|'4k'|'8k' | Named tier from physical pixel dimensions |
physicalResolution() | { width, height, dpr } | Actual hardware pixel count of the screen |
if (Display.is4K()) {
loadTexture('/assets/hero@4x.webp');
} else if (Display.isRetina()) {
loadTexture('/assets/hero@2x.webp');
} else {
loadTexture('/assets/hero.webp');
}
Color Gamut & HDR
| API | Returns | Description |
|---|---|---|
colorGamut() | 'srgb'|'p3'|'rec2020' | Widest supported color gamut |
supportsHDR() | boolean | dynamic-range: high media query |
supportsWideColor() | boolean | True for P3 or wider |
colorDepth() | number | screen.colorDepth (typically 24 or 30) |
if (Display.colorGamut() === 'p3') {
canvas.style.colorSpace = 'display-p3';
ctx = canvas.getContext('2d', { colorSpace: 'display-p3' });
}
GPU Detection
| API | Returns | Description |
|---|---|---|
getGPUInfo() | { vendor, renderer, tier, api } | WebGL renderer string + estimated tier. Cached for the session. |
supportsWebGL2() | boolean | WebGL 2.0 context availability check |
const gpu = Display.getGPUInfo();
console.log(gpu.renderer); // "Apple M3 Pro"
console.log(gpu.tier); // "high"
console.log(gpu.api); // "webgl2"
Hardware: Memory & CPU
| API | Returns | Description |
|---|---|---|
deviceMemoryGB() | number | Navigator.deviceMemory (approximate GB). Falls back to 4. |
cpuCores() | number | navigator.hardwareConcurrency. Falls back to 4. |
hardwareTier() | 'low'|'mid'|'high' | Combined RAM + CPU tier |
Network & Connection
| API | Returns | Description |
|---|---|---|
connectionInfo() | { type, downlink, rtt, saveData, tier } | Network Information API data with fast/slow tier |
saveDataMode() | boolean | True when user has enabled Save Data / Lite mode |
Accessibility Preferences
| API | Returns | Description |
|---|---|---|
prefersReducedMotion() | boolean | prefers-reduced-motion: reduce |
prefersReducedTransparency() | boolean | prefers-reduced-transparency: reduce |
prefersHighContrast() | boolean | prefers-contrast: more |
prefersDarkMode() | boolean | prefers-color-scheme: dark |
Variant System
The variant system is the primary way to branch logic per tier. Pass a config map, get back the right value for the current device.
| API | Description |
|---|---|
createVariant(map) | Resolve a single per-tier value. Accepts { low, medium?, high?, ultra? } or a 4-element array. |
createVariants(schema) | Resolve a full schema object — each key maps to a variant map. Returns a flat config object. |
// Single value
const particles = Display.createVariant({ low: 50, medium: 500, high: 2000, ultra: 8000 });
// Full config object — the recommended pattern
const cfg = Display.createVariants({
shadows: { low: false, medium: 'simple', high: 'pcf', ultra: 'pcss' },
particles: { low: 50, medium: 500, high: 2000, ultra: 8000 },
antialias: { low: false, medium: false, high: true, ultra: true },
texSize: { low: 512, medium: 1024, high: 2048, ultra: 4096 },
});
// On a Retina MacBook Pro (ultra): { shadows:'pcss', particles:8000, antialias:true, texSize:4096 }
// On a budget Android phone (low): { shadows:false, particles:50, antialias:false, texSize:512 }
Canvas Scaling (Pixel-Perfect)
| API | Description |
|---|---|
scaleCanvas(canvas, ctx?, opts?) | Sets canvas physical buffer = CSS size × DPR. Calls ctx.scale(dpr,dpr) so draw calls use logical px. |
observeCanvas(canvas, ctx, onResize?) | Attaches a ResizeObserver that re-scales on container resize. Returns the observer for cleanup. |
createHiDPICanvas(w, h, opts?) | Creates a new canvas (or OffscreenCanvas) at physical DPR resolution. |
const canvas = document.getElementById('chart');
const ctx = canvas.getContext('2d');
// One call — pixel-perfect on Retina, 4K, anything
Display.scaleCanvas(canvas, ctx);
ctx.fillText('Crisp on every display', 10, 20);
// Auto re-scale when container resizes
const observer = Display.observeCanvas(canvas, ctx, ({ dpr, width, height }) => {
redraw(); // your draw function
});
// cleanup: observer.disconnect()
WebGL Viewport Setup
| API | Description |
|---|---|
setupWebGLViewport(canvas, gl, opts?) | Sizes canvas buffer for current tier DPR cap (ultra=full, high=2×, medium=1.5×, low=1×). Calls gl.viewport(). |
get3DDPR() | Optimal DPR for 3D rendering — caps at 2 for high tier (rendering 3D at 3× DPR costs 9× fill rate). |
Rendering WebGL at full 3× DPR on a Retina display costs 9× the fill rate compared to 1×. forge/display automatically caps WebGL DPR at 2 for the high tier and only allows full native DPR for ultra tier devices — giving you sharp visuals without tanking performance on most hardware.
const canvas = document.getElementById('scene');
const gl = canvas.getContext('webgl2');
// Auto-caps DPR by tier — sharp but performant
Display.setupWebGLViewport(canvas, gl);
// Or pass into forge/3d renderer
import { createRenderer } from '@hyperbridge/forge/3d';
const renderer = createRenderer(canvas, { dpr: Display.get3DDPR() });
Adaptive Images
| API | Description |
|---|---|
adaptiveImageSrc(base, variants?) | Returns the right src variant for current DPR and connection. Degrades to base on save-data or slow network. |
adaptiveSrcset(base, variants?) | Generates a complete srcset string. Auto-derives @2x/@3x paths if variants not supplied. |
// Explicit variant map
const src = Display.adaptiveImageSrc('/img/hero.webp', {
x2: '/img/hero@2x.webp',
x3: '/img/hero@3x.webp',
x4: '/img/hero@4x.webp',
});
img.src = src;
// Auto-generated srcset (derives @2x/@3x filenames)
img.srcset = Display.adaptiveSrcset('/img/banner.webp');
// → "/img/banner.webp 1x, /img/banner@2x.webp 2x, /img/banner@3x.webp 3x"
CSS Variable Injection
Call Display.injectCSS() once at app startup. It adds CSS custom properties and data-* attributes to <html> that you can use in stylesheets:
| What's injected | Example value | Use |
|---|---|---|
--dpr | 2 | Scale factor for CSS transforms, border-image-width |
--display-tier | high | Feature flags in CSS |
--display-color-gamut | p3 | Color space switching |
--canvas-scale | 2 | Same as --dpr, aliased for clarity |
--anim-scale | 1.0 | Scale animation durations in CSS |
--particle-scale | 0.8 | Scale particle counts in shaders |
data-tier | high | CSS selectors: [data-tier="high"] .glow { … } |
data-retina | true | [data-retina="true"] img { image-rendering: … } |
data-hdr | true | Enable HDR color palettes |
data-gamut | p3 | Wide-color palette activation |
// app entry point
Display.injectCSS();
/* your stylesheet */
.hero-gradient {
background: linear-gradient(135deg, oklch(70% 0.25 264), oklch(60% 0.3 310));
animation-duration: calc(0.4s * var(--anim-scale));
}
[data-tier="low"] .hero-gradient { animation: none; }
[data-hdr="true"] .hero-gradient { /* wide-gamut override */ }
[data-retina="true"] .logo { image-rendering: -webkit-optimize-contrast; }
forge/3d Integration
| API | Returns | Description |
|---|---|---|
get3DConfig() | Config3D | Full tier-resolved 3D config object ready to spread into a forge/3d renderer |
get3DDPR() | number | WebGL-safe DPR (capped by tier) |
import { createRenderer } from '@hyperbridge/forge/3d';
import Display from '@hyperbridge/forge/display';
const cfg = Display.get3DConfig();
const renderer = createRenderer(canvas, cfg);
/* cfg on ultra tier:
{
antialias: true, shadows: 'pcss', shadowMapSize: 4096,
textureMaxSize: 4096, maxParticles: 20000,
postFX: ['bloom','ssao','dof','ssr'],
dpr: 2, instanceLimit: 10000, anisotropy: 16, mipmap: true
}
cfg on low tier:
{
antialias: false, shadows: false, shadowMapSize: 512,
textureMaxSize: 512, maxParticles: 100,
postFX: [], dpr: 1, instanceLimit: 100, anisotropy: 1, mipmap: false
} */
forge/animate Integration
| API | Description |
|---|---|
getAnimateConfig() | Returns { duration, stagger, springs, parallax, blur, willChange } adjusted for tier and reduced-motion preference. All durations are 0 when reduced-motion is active. |
import { animate } from '@hyperbridge/forge/animate';
import Display from '@hyperbridge/forge/display';
const aCfg = Display.getAnimateConfig();
animate(el, { opacity: [0, 1], y: [20, 0] }, {
duration: aCfg.duration,
stagger: aCfg.stagger,
easing: aCfg.springs ? 'spring' : 'ease-out',
});
// On a low-end device: duration 0.15s, no parallax, no blur
// With reduced-motion: duration 0, instant resolve
forge/client Integration
| API | Description |
|---|---|
getClientConfig() | Returns { lazyThreshold, imageFormat, preloadVideos, srcsetMaxDPR, placeholders } for forge/client image components and loaders. |
Pixel-Perfect Utilities
| API | Description |
|---|---|
toPhysical(css) | CSS px → physical device px (Math.round(css × DPR)) |
toLogical(px) | Physical px → CSS px |
snapToPhysical(css) | Round CSS value to nearest clean physical-pixel boundary — prevents sub-pixel blur |
hairlineWidth() | Thinnest visible line: 1 / DPR. Use for borders, strokes, dividers. |
// Hairline border — 0.5px on Retina, 1px on standard
el.style.borderWidth = Display.hairlineWidth() + 'px';
// Snap a shadow offset to physical pixels (no blurry sub-pixel shadows)
const offset = Display.snapToPhysical(8);
ctx.shadowOffsetX = offset;
ctx.shadowOffsetY = offset;
DPR Change Events
Fires when the user drags a window to a different monitor (e.g. from a Retina display to a standard external monitor).
| API | Description |
|---|---|
onDPRChange(fn) | Subscribe to DPR changes. Returns an unsubscribe function. |
onTierChange(fn) | Subscribe to quality tier changes (fires when DPR change crosses a tier boundary). |
const unsub = Display.onDPRChange(({ dpr, prev }) => {
console.log(`DPR changed: ${prev} → ${dpr}`);
Display.scaleCanvas(canvas, ctx);
redraw();
});
Display.onTierChange(({ tier, prev }) => {
console.log(`Tier: ${prev} → ${tier}`);
Display.injectCSS({ force: true }); // re-inject CSS vars
renderer.setPixelRatio(Display.get3DDPR());
});
Debug & Analytics
| API | Description |
|---|---|
profileString() | Compact analytics string, e.g. "ultra|4k|p3|hdr|retina|2x|gpu:high|ram:16|cores:12|score:87" |
logCapabilities() | Formatted console group with the full capability report (dev tool aid) |
Display.logCapabilities();
// ─────────────────────────────────────────────────────
// Quality tier : ultra (87/100)
// DPR : 2 → clamped 2
// Display : 4K 3840×2160px
// Color gamut : p3 + HDR
// GPU tier : high (Apple M3 Pro)
// WebGL 2 : true
// RAM / CPU : 16 GB / 12 cores
// Connection : 4g fast
// Reduced motion : false
// ─────────────────────────────────────────────────────
// Send to analytics
analytics.track('display_profile', { profile: Display.profileString() });
60+ APIs, 955 lines, zero dependencies. Works in all modern browsers and Node.js (SSR-safe — returns sensible defaults on the server). Integrates natively with forge/3d, forge/animate, and forge/client.
forge/prime
Web7 compliance bridge — DID-based identity, intent-first APIs, AMP envelope routing, Proof-of-Outcome, Vigil audit trail, and Kynetra Prime agent delegation. 1,200 lines. 32+ APIs. Three tiers: Lite (free) · Pro (hosted) · Enterprise (PoO + ZK-ML).
Web7 in one import. forge/prime is the L6→L4 bridge in the Web7 stack. It connects your HBForge app to Kynetra Prime (agent kernel), the Skill Registry, and the AIG audit graph — without changing your existing HBForge code. Add it progressively: start with Lite (zero infrastructure), upgrade to Pro when you need hosted routing, Enterprise when regulators need proof.
BYOA included at every tier. Register any custom agent class with prime.registerAgent() and it immediately inherits token-scoped auth, telemetry, memory access, and PRIME self-training. No boilerplate, no separate infra.
Installation
import { prime, vigil, KynetraPrime } from '@hyperbridge/forge/prime';
Tier Overview
| Feature | Lite | Pro | Enterprise |
|---|---|---|---|
| DID token auth | ✓ | ✓ | ✓ |
| Intent-first API + AMP envelope | ✓ | ✓ | ✓ |
| BYOA (registerAgent) | ✓ | ✓ | ✓ |
/.well-known/web7.json manifest | ✓ | ✓ | ✓ |
| Vigil basic logging | ✓ | ✓ | ✓ |
| Hosted Kynetra Prime routing | — | ✓ | ✓ |
| Skill Registry + 10 built-in agents | — | ✓ | ✓ |
| Vigil full audit trail + AIG graph | — | ✓ | ✓ |
| Proof-of-Outcome (PoO) verification | — | — | ✓ |
| ZK-ML verifiable inference | — | — | ✓ |
| L0 settlement + outcome escrow | — | — | ✓ |
| Reputation ledger + slashing | — | — | ✓ |
| w7 conformance certificate | — | — | ✓ |
KynetraPrime Constructor
| Option | Type | Default | Description |
|---|---|---|---|
token | string | — | Scoped API token (required). No password ever stored or transmitted. |
tier | 'lite'|'pro'|'enterprise' | 'lite' | Determines which infrastructure is available |
agents | string[] | [] | Built-in agents to activate: ECHO, HUNTER, PULSE, FLOW, CORTEX, FORGE, SHIELD, SPARK, LENS, HIVE |
telemetry | boolean | true | Enable Vigil telemetry events |
endpoint | string | 'https://prime.kynetra.ai' | Kynetra Prime API endpoint (Pro/Enterprise only) |
policy | string | 'deny-all' | Default policy ref. Deny-by-default; pass 'org:acme:default' to allow |
Core API Reference
| Method | Returns | Tier | Description |
|---|---|---|---|
prime.delegate(opts) | Promise<DelegateResult> | All | Delegate an intent to an agent. Emits AMP envelope, routes via Prime kernel (Pro/Enterprise) or runs locally (Lite) |
prime.register(agentDef) | Promise<void> | All | Register a built-in or BYOA agent with Prime. Publishes to Skill Registry on Pro/Enterprise |
prime.registerAgent(name, cls, opts) | void | All | BYOA — register any custom agent class. Inherits token auth, telemetry, memory, model routing automatically |
prime.run(agentName, payload) | Promise<any> | All | Run a named agent (built-in or BYOA) with a payload. Same API regardless of tier |
prime.identity.verify(opts) | Promise<boolean> | All | Verify a DID signature against a nonce. Used in login flows |
prime.identity.authenticate(opts) | Promise<{did, token}> | All | Passkey/biometric DID authentication. Returns DID + scoped JWT. No password |
prime.identity.current() | Promise<string> | All | Get the authenticated user's DID |
prime.manifest() | object | All | Generate the /.well-known/web7.json manifest object for your app |
prime.conform() | Promise<ConformResult> | All | Run local Web7 Lite conformance checks. Returns pass/fail per requirement |
vigil.record(opts) | Promise<void> | All | Log an action to the Vigil audit trail. Creates an AIG node |
vigil.trace(intentId) | Promise<AIGSubgraph> | Pro+ | Retrieve the full AIG subgraph for an intent: intent → delegation → inference → outcome |
vigil.export(intentId) | Promise<SignedBlob> | Enterprise | Export a self-verifying signed audit blob suitable for regulator submission |
prime.poo.verify(opts) | Promise<PoOResult> | Enterprise | Verify the full PoO predicate: σ_principal ∧ σ_prime ∧ zkml ∧ σ_agent ∧ policy ∧ stake |
prime.settle(intentId, rail) | Promise<Settlement> | Enterprise | Trigger L0 settlement. Rails: amp-escrow, amp-outcome, amp-split |
delegate() Options
| Option | Type | Required | Description |
|---|---|---|---|
from | string | Yes | Principal DID (the user or service delegating) |
to | string | Yes | Target skill ref or agent DID e.g. 'skill:ledger-close' |
intent | object | Yes | { action, params, constraints } |
budget | string | Yes | Hard spend cap e.g. '10 USD'. Prime rejects uncapped delegations |
policy | string | No | Policy ref for this delegation. Overrides instance default |
settle | object | No | { rail: 'amp-escrow'|'amp-outcome'|'amp-split' } (Enterprise) |
require | object | No | { zkml: true } — demand verifiable inference proof (Enterprise) |
Example: Lite — DID Login + Intent API
import { prime } from '@hyperbridge/forge/prime';
import { JWT } from '@hyperbridge/forge/auth';
const p = new KynetraPrime({ token: process.env.KP_TOKEN, tier: 'lite' });
// DID login — no password, no session cookie
app.post('/auth/login', async (req, res) => {
const { did, signature, nonce } = req.body;
const ok = await p.identity.verify({ did, signature, nonce });
if (!ok) return res.status(401).json({ error: 'invalid DID' });
const token = JWT.sign(
{ sub: did, scope: ['read', 'delegate'] },
{ secret: process.env.JWT_SECRET, expiresIn: '8h' }
);
res.json({ token, did });
});
// Intent-first route — declare what the user wants
app.post('/generate-invoice', jwtMiddleware, async (req, res) => {
const result = await p.delegate({
from: req.user.did,
to: 'skill:generate-invoice',
intent: { action: 'generate-invoice', params: req.body },
budget: '5 USD',
policy: 'org:acme:billing',
});
res.json({ ok: true, aig: result.aigNodeId });
});
// Well-known manifest
app.get('/.well-known/web7.json', (req, res) => res.json(p.manifest()));
Example: BYOA — Register Custom Agent
import { KynetraPrime } from '@hyperbridge/forge/prime';
import { Pool } from '@hyperbridge/forge/data';
const prime = new KynetraPrime({ token: process.env.KP_TOKEN });
// 1. Define your agent class
class InvoiceAgent {
async run({ input, memory, model }) {
const client = await memory.recall(input.clientId);
const draft = await model.generate('invoice-v1', client);
return { invoice: draft, status: 'draft' };
}
}
// 2. Register — gets full Prime infrastructure for free
prime.registerAgent('INVOICE', InvoiceAgent, {
token: { scope: ['invoices:write'] },
telemetry: true,
trainable: true, // PRIME learns from every run
});
// 3. Run — identical API to built-in agents
const result = await prime.run('INVOICE', { clientId: 'c_123' });
Example: Pro — Hosted Prime + Vigil Audit
import { KynetraPrime, vigil } from '@hyperbridge/forge/prime';
const prime = new KynetraPrime({
token: process.env.KP_TOKEN,
tier: 'pro',
endpoint: 'https://prime.kynetra.ai',
agents: ['FORGE', 'SHIELD'],
});
// Delegate to a hosted agent with full audit trail
const result = await prime.delegate({
from: req.user.did,
to: 'skill:ledger-close',
intent: { action: 'ledger-close', params: { month: '2026-04' } },
budget: '10 USD',
policy: 'org:acme:finance',
});
// result.aigNodeId — committed AIG node (queryable)
console.log(result.aigNodeId); // 'aig:abc123'
// Retrieve the full audit trace
const trace = await vigil.trace(result.intentId);
// Returns: intent → delegation → inference → outcome graph
// With all signatures, timestamps, model used, cost
// Export for internal audit
const blob = JSON.stringify(trace);
Example: Enterprise — Proof-of-Outcome + L0 Settlement
import { KynetraPrime, vigil } from '@hyperbridge/forge/prime';
const prime = new KynetraPrime({
token: process.env.KP_TOKEN,
tier: 'enterprise',
});
// Delegate with ZK-ML requirement + escrow settlement
const result = await prime.delegate({
from: req.user.did,
to: 'skill:tax-filing',
intent: { action: 'file-tax', params: req.body, constraints: { jurisdiction: 'IN' } },
budget: '50 USD',
policy: 'org:acme:compliance',
settle: { rail: 'amp-outcome' }, // pay only on verified success
require: { zkml: true }, // demand ZK-ML proof of inference
});
// Verify PoO before trusting the result
const poo = await prime.poo.verify({
intentId: result.intentId,
agentDid: result.agentDid,
outcome: result.outcome,
});
if (!poo.verified) throw new Error('PoO failed — do not settle');
// Trigger L0 settlement (funds released from escrow to agent)
const settlement = await prime.settle(result.intentId, 'amp-outcome');
console.log(settlement.status); // 'settled'
console.log(settlement.receipt); // signed receipt (self-verifying)
// Export audit blob for regulator
const auditBlob = await vigil.export(result.intentId);
// Self-verifying: contains all sigs + ZK-ML proof + L0 anchor hash
Web7 Conformance Checklist
// Run local conformance check (Lite tier)
const report = await prime.conform();
// {
// did_token_auth: true, // JWT sub is a DID
// intent_first_api: true, // at least one prime.delegate() call
// amp_envelope: true, // AMP message format valid
// well_known: true, // /.well-known/web7.json accessible
// vigil_logging: true, // at least one vigil.record() call
// budget_caps: true, // all delegations have budget set
// byoa_registered: true, // custom agents registered
// poo_verified: false, // Enterprise only
// zkml_proofs: false, // Enterprise only
// l0_settlement: false, // Enterprise only
// }
// Full conformance suite (Enterprise — run in CI)
// w7 test conformance --endpoint https://my-app.com
32+ APIs, 1,200 lines, zero extra dependencies. forge/prime is the single bridge between your HBForge app and the entire Web7 stack. Start Lite for free — upgrade when your compliance requirements need proof.
forge/wasm Web7 ✦
Universal WebAssembly loader. forge/wasm is how HBForge apps instantiate KYRx (L1 settlement) and ClearScript (L5 contract) modules, plus any other WASM payload — with zero external tooling (no wabt, no wasm-bindgen, no wasmtime).
Installation
import {
loadModule, loadStreaming,
MemoryManager, WorkerPool,
WASIBridge, HostImports,
parseWasm, validateWasm,
hotReload, typeBridge
} from '@hyperbridge/forge/wasm';
What it does
- Module loading —
loadModule(bytes)/loadStreaming(url)with caching and streaming compilation. - Memory manager — typed-array views on linear memory, bounds-checked read/write helpers.
- WASI-lite — enough of the WASI filesystem/stdio API to run Rust or AssemblyScript compiled output.
- Worker pool — run heavy WASM workloads off the main thread with transferable memory buffers.
- Host imports — preconfigured namespaces for
web7/prime,vigil(audit trail),auth,amp(agent mesh). - Hot-reload — development-only module swap without losing app state.
- Type bridge — automatic conversion between JS and WASM primitives;
stringRef,arrayBuffer,bigint, struct layouts. - Parser / validator — LEB128 varint decode of WASM sections (via
_internal/binary); catches malformed payloads before instantiation.
Example
// Load a Rust-compiled KYRx module and call its exports
const bytes = await fetch('/kyrx.wasm').then(r => r.arrayBuffer());
const mod = await loadModule(bytes, {
imports: HostImports.web7Prime({ did: 'did:hb:42' })
});
const balance = mod.exports.balance_of('0xabc');
console.log('KYRx balance:', balance);
// Long-running compute? Move it to a worker
const pool = new WorkerPool({ size: 4, module: bytes });
const results = await pool.runAll(inputs, 'process');
1,481 lines, 20+ APIs. Built on WebAssembly.Module + WebAssembly.Instance. LEB128 decoding shares forge/_internal/binary. Runs in Node (18+) and modern browsers (Chromium, Firefox, Safari 14+).
forge/memory Web7 ✦ new in v4.1.0
Scoped agent memory store. Four nested scopes — task → session → principal → global — with fall-through reads, TTL sweep, trigram search, and snapshot/restore. Implements the Web7 L6 agent memory primitive.
Installation
import { MemoryStore, SCOPES } from '@hyperbridge/forge/memory';
What it does
- Four scopes —
global(app-wide),principal(per DID / user),session(per sign-in),task(per agent run). - Fall-through reads —
get(principal, key)looks intaskfirst, thensession, thenprincipal, thenglobal. Write to a scope, read it from a narrower one. - TTL sweep — set a per-key TTL;
sweep()garbage-collects expired entries. - Trigram search —
search(q)ranks stored keys/values by Jaccard similarity, no vector store needed. - Promote / snapshot — lift a value up a scope (
promote('task'→'session')), or serialize the whole store for persistence.
Example
const mem = new MemoryStore();
// Global knowledge
mem.set(null, 'org-name', 'HyperBridge Digital', { scope: 'global' });
// Per-principal preference
mem.set('did:w7:alice', 'lang', 'en-IN', { scope: 'principal' });
// Per-task scratch with 5-min TTL
mem.set('did:w7:alice', 'draft', 'reply-text',
{ scope: 'task', taskId: 'run-42', ttlMs: 300_000 });
// Read falls through: task → session → principal → global
mem.get('did:w7:alice', 'org-name', { taskId: 'run-42' });
// → 'HyperBridge Digital' (resolved from global)
// Cleanup
mem.sweep();
const blob = mem.snapshot(); // serializable
267 lines, 12 methods, zero deps. Keys are hashed with FNV-1a for O(1) lookups. Search is vector-free — no embedding model, no similarity service. Works in Node & every browser.
forge/consent Web7 ✦ new in v4.1.0
Hash-chained, tamper-evident consent ledger. GDPR-aligned purposes, grant / revoke / amend / query verbs, isPermitted() checks, daily Merkle roots. Implements the Web7 L6 consent primitive.
Installation
import { ConsentLedger, ConsentGrant, PURPOSES, checkConsent }
from '@hyperbridge/forge/consent';
Standard purposes
GDPR-aligned PURPOSES enum covers the common lawful bases:
NECESSARY— strictly required for service delivery.CONSENT— generic opt-in.MODEL_TRAIN— user content may be used to train models.MODEL_INFER— user content may be sent to a model at inference time.ANALYTICS,MARKETING,PERSONALIZATION,PROFILING,THIRD_PARTY_SHARE,CROSS_BORDER.
Example
const ledger = new ConsentLedger();
// Grant consent for inference with a 90-day expiry
ledger.grant({
principal: 'did:w7:alice',
processor: 'did:w7:hbforge-ai',
purpose: PURPOSES.MODEL_INFER,
scope: ['chat', 'support'],
expiresAt: Date.now() + 90 * 86_400_000,
});
// Before calling the model, check
if (!ledger.isPermitted({
principal: 'did:w7:alice',
processor: 'did:w7:hbforge-ai',
purpose: PURPOSES.MODEL_INFER,
scope: 'chat',
})) {
throw new Error('Consent not granted');
}
// Revoke is a ledger append — never a delete
ledger.revoke({
principal: 'did:w7:alice',
processor: 'did:w7:hbforge-ai',
purpose: PURPOSES.MODEL_INFER,
});
// Tamper-evidence: every entry chains a SHA-256 of the previous head
const head = ledger.chainHead();
const ok = ledger.verify(); // re-walks + re-hashes the whole chain
const days = ledger.dailyRoots(); // Merkle root per UTC day for external anchoring
234 lines, 11 methods. Every grant/revoke/amend is an append to a hash-chained log — no silent mutation, no deletes. dailyRoots() produces values you can publish to an external anchor (OP_RETURN, KYRx, Ethereum) for public tamper-evidence.
forge/provenance Web7 ✦ new in v4.1.0
C2PA 2.0 style content provenance. Signed assertion boxes (c2pa.* + w7.* labels), parent manifests, selective disclosure. Implements the Web7 L6 provenance primitive and pairs with forge/zkml for model-output manifests.
Installation
import { Manifest, Assertion, LABELS, buildInferenceManifest }
from '@hyperbridge/forge/provenance';
What it does
- Manifests — an ordered list of signed assertion boxes with a final seal over the whole manifest.
- Assertions — labelled records (
c2pa.hash.data,c2pa.actions,w7.inference,w7.attestation, …) individually signed with HMAC-SHA-256. - Parent linkage — a new manifest can reference a prior manifest's hash, chaining provenance across edits.
- Selective disclosure —
manifest.select([labels])returns a compact manifest exposing only chosen assertions while keeping the seal valid. - Inference helper —
buildInferenceManifest({ model, inputHash, outputHash, attestations })constructs a ready-to-sign Web7 inference manifest in one call.
Example
const m = new Manifest({ issuer: 'did:w7:hbforge' });
m.addAssertion(new Assertion({
label: LABELS.C2PA_HASH_DATA,
data: { algo: 'sha256', value: '0x7f12…' },
}));
m.addAssertion(new Assertion({
label: LABELS.W7_INFERENCE,
data: { model: 'gpt-local-7b', inputHash: '0xaa…', outputHash: '0xbb…' },
}));
m.linkParent('0xparent-manifest-hash');
const sealed = m.seal(secretKey);
// Selective disclosure: share hash + inference, hide other boxes
const trimmed = sealed.select([LABELS.C2PA_HASH_DATA, LABELS.W7_INFERENCE]);
trimmed.verify(publicKey); // still valid
const wire = sealed.toJSON();
const back = Manifest.fromJSON(wire);
230 lines, 10+ APIs. Assertion signatures use the shared _internal/crypto HMAC-SHA-256 primitive. Label namespace is split c2pa.* (C2PA 2.0 compatible) + w7.* (Web7 extensions); you can add custom labels without touching the core.
forge/zkml Web7 ✦ new in v4.1.0
ZK-ML multi-attestor MVP — M-of-N threshold attestations over (model, inputHash, outputHash) tuples. Swappable proof backend via registerProofBackend() so you can drop in a real SNARK/STARK later without changing callers. Implements the Web7 L6 zk-ml primitive.
Installation
import {
ZKMLClaim, Attestor, AttestationBundle,
verifyBundle, collectAttestations,
registerProofBackend, getProofBackend,
} from '@hyperbridge/forge/zkml';
Why multi-attestor, not a proof
A cryptographic zero-knowledge proof of ML inference is research-grade today — slow, costly, and model-shape-specific. A multi-attestor bundle is the deployable approximation: N independent operators (you + partners + third-party auditors) each attest to having observed the same (inputHash → outputHash) transition; a client accepts if M-of-N signatures verify. When real SNARK backends become practical, register one via registerProofBackend() and callers switch over transparently.
Example
const claim = new ZKMLClaim({
model: 'gpt-local-7b',
inputHash: '0xaa…',
outputHash: '0xbb…',
});
// Three independent attestors (each with its own HMAC key)
const attestors = [
new Attestor({ id: 'att-1', secret: s1 }),
new Attestor({ id: 'att-2', secret: s2 }),
new Attestor({ id: 'att-3', secret: s3 }),
];
// Require 2-of-3 threshold
const bundle = await collectAttestations(claim, attestors, { threshold: 2 });
// Anyone with the attestor pubkeys (here: HMAC secrets) can verify
const res = verifyBundle(bundle, { 'att-1': s1, 'att-2': s2, 'att-3': s3 });
// → { ok: true, threshold: 2, signed: 3 }
// Later: swap in a SNARK backend
registerProofBackend('groth16', { prove, verify });
const be = getProofBackend('groth16');
201 lines, 9 APIs. Bundle verifier rejects duplicate attestor IDs, unknown signers, and invalid signatures. Designed so the bundle wire format survives the transition from HMAC MVP to SNARK proofs — only the backend changes.
forge/conformance Web7 ✦ new in v4.1.0
Web7-L6 conformance suite v1.0.0. 30 tests across 12 categories. Runs in ~8 ms. Grade AAA / AA / A / B / C / FAIL. Zero deps.
Installation
import { runConformance, formatReport, SUITE_VERSION, CATEGORIES }
from '@hyperbridge/forge/conformance';
const report = await runConformance();
console.log(formatReport(report));
Coverage
| Category | Coverage | What's checked |
|---|---|---|
identity | 4 / 4 | DID registry, resolver, VC issuer, VC roundtrip |
amp | 6 / 6 | envelope, discover, negotiate, revoke, skill registry, SLASH_RULES |
aig | 2 / 2 | node types, Merkle root of audit chain |
poo | 2 / 2 | proof-of-outcome write + verify |
settlement | 3 / 3 | escrow rail, subscription rail, outcome rail |
consent | 2 / 2 | grant/revoke chain, isPermitted |
provenance | 2 / 2 | manifest seal/verify, selective disclosure |
zkml | 2 / 2 | M-of-N bundle collect + verify, swappable backend |
memory | 3 / 3 | scoped set/get, fall-through, TTL sweep |
reputation | 2 / 2 | score update, slashing (SLASH_RULES) |
revocation | 1 / 1 | amp.revoke writes REVOCATION node |
negotiation | 1 / 1 | amp.negotiate with counter/accept/reject |
Grading
AAA— ≥98% (≥29/30)AA— ≥95% (≥28/30)A— ≥90% (≥27/30)B— ≥80%C— ≥70%FAIL— below 70%
Current baseline for @hyperbridge/forge@4.2.2: AAA · 30 / 30 · ~8 ms · 0 deps.
Example report
Web7-L6 Conformance v1.0.0 — @hyperbridge/forge@4.2.2
────────────────────────────────────────────────────────
identity ██████████ 4/4
amp ██████████ 6/6
aig ██████████ 2/2
poo ██████████ 2/2
settlement ██████████ 3/3
consent ██████████ 2/2
provenance ██████████ 2/2
zkml ██████████ 2/2
memory ██████████ 3/3
reputation ██████████ 2/2
revocation ██████████ 1/1
negotiation ██████████ 1/1
────────────────────────────────────────────────────────
TOTAL 30 / 30 (100.0%) Grade: AAA 8 ms
319 lines, drop-in CI check. Add await runConformance() to your CI pipeline; fail the build if grade drops below your target. The suite version tag (SUITE_VERSION = '1.0.0') lets you pin a spec version alongside your framework version.
forge/policy ✦ new in v4.2.2
OPA-style declarative policy engine — evaluate rule sets, gate HTTP handlers, enforce jurisdiction / budget / data-class / reputation constraints with zero deps. Web7 L6 ambient-authority primitive.
Installation
import {
PolicyEngine, PolicySet, PolicyRule,
PolicyDeniedError, policyGate, buildStandardSet,
EFFECT, COMBINER, getPolicy,
} from '@hyperbridge/forge/policy';
Concepts
| Concept | Description |
|---|---|
PolicyRule | A single condition → effect (ALLOW / DENY) with optional priority |
PolicySet | Ordered collection of rules with a combiner strategy |
PolicyEngine | Registry of sets; evaluate(ctx, setId) returns ALLOW/DENY + reasons |
policyGate() | HTTP middleware — 403 on DENY, passes ctx.policy to downstream handlers |
buildStandardSet() | Quick-builder: jurisdiction + budget + dataClass + reputation + riskTier |
Combiners
| Combiner | Behaviour |
|---|---|
deny-overrides | Any DENY wins (default — fail-secure) |
permit-overrides | Any ALLOW wins (whitelist pattern) |
first-applicable | First matching rule wins, ordered by priority desc |
Example — HTTP gate
import { getPolicy, buildStandardSet, policyGate } from '@hyperbridge/forge/policy';
import { createServer } from '@hyperbridge/forge/server';
const engine = getPolicy();
// Register a policy set for the /pay endpoint
buildStandardSet('pay-policy', {
allowedJurisdictions: ['US', 'EU', 'IN'],
maxBudget: 500,
allowedDataClasses: ['financial'],
minReputation: 0.6,
maxRiskTier: 2,
}, engine);
const server = createServer();
server.use(policyGate(engine, ctx => ({
setId: 'pay-policy',
jurisdiction: ctx.headers['x-jurisdiction'] ?? 'US',
budget: Number(ctx.headers['x-budget'] ?? 0),
dataClass: ctx.headers['x-data-class'] ?? 'public',
reputation: Number(ctx.headers['x-reputation'] ?? 1),
riskTier: Number(ctx.headers['x-risk-tier'] ?? 0),
})));
server.post('/pay', async ctx => {
// ctx.policy → { effect: 'allow', reasons: [...] }
ctx.body = { ok: true };
});
server.listen(3000);
Custom rules
const set = new PolicySet('custom', { combiner: 'deny-overrides', defaultEffect: EFFECT.ALLOW });
set.addRule(new PolicyRule({
id: 'no-weekend-writes',
type: 'custom',
effect: EFFECT.DENY,
params: { fn: ctx => new Date().getDay() % 6 === 0 && ctx.method === 'write' },
reason: 'Write operations disabled on weekends',
}));
engine.addSet(set);
const result = engine.evaluate({ method: 'write' }, 'custom');
// → { effect: 'deny', reasons: ['Write operations disabled on weekends'] }
~280 lines, 12 APIs. engine.assert(ctx, setId) throws PolicyDeniedError (HTTP 403) on deny — use inside service handlers for non-HTTP contexts. Time-window rules let you enforce rate-limiting or business-hours access without extra middleware.
forge/trace ✦ new in v4.2.2
OpenTelemetry-compatible distributed tracing — W3C Trace Context, Span/Tracer/TraceProvider, OTLP/HTTP export, Jaeger/Grafana/Vigil integration. Zero external deps.
Installation
import {
TraceProvider, TraceContext, Span, Tracer,
ConsoleExporter, JsonLineExporter, OtlpHttpExporter, VigilExporter,
traceMiddleware, tracePrime,
getProvider, setProvider, getTracer,
SpanStatus, SpanKind,
} from '@hyperbridge/forge/trace';
Exporters
| Exporter | When to use |
|---|---|
ConsoleExporter | Dev / debug — pretty-prints to stdout |
JsonLineExporter(stream) | NDJSON to a writable stream; pipe to a file or log aggregator |
OtlpHttpExporter(endpoint, headers) | Production — sends to Grafana Tempo, Jaeger, Honeycomb, etc. |
VigilExporter(vigil) | Mirrors spans into forge/vigil audit log |
Quick start
import { getProvider, getTracer, OtlpHttpExporter } from '@hyperbridge/forge/trace';
import { createServer } from '@hyperbridge/forge/server';
import { traceMiddleware } from '@hyperbridge/forge/trace';
// Wire up an OTLP exporter (Grafana Cloud / Jaeger)
getProvider().addExporter(new OtlpHttpExporter(
'http://localhost:4318/v1/traces',
{ Authorization: 'Bearer ' }
));
const tracer = getTracer('my-app', '1.0.0');
const server = createServer();
// Reads W3C traceparent from requests, propagates it downstream
server.use(traceMiddleware(tracer));
server.get('/users/:id', async ctx => {
await tracer.trace('db.getUser', async span => {
span.setAttribute('db.user_id', ctx.params.id);
ctx.body = await db.getUser(ctx.params.id);
});
});
Manual spans
const span = tracer.startSpan('payment.process', {
kind: SpanKind.CLIENT,
attributes: { 'payment.amount': 99.99, 'payment.currency': 'USD' },
});
try {
const result = await stripe.charge({ amount: 9999 });
span.addEvent('charge.created', { id: result.id });
span.ok();
return result;
} catch (err) {
span.error(err.message);
throw err;
} finally {
span.end();
}
W3C Trace Context
// Parse incoming traceparent header
const ctx = TraceContext.fromTraceparent(req.headers['traceparent']);
// → TraceContext { traceId: '4bf92f3577b34da6...', spanId: 'a2fb4a1d...', sampled: true }
// Serialise for outgoing calls
const childCtx = ctx.child();
outboundHeaders['traceparent'] = childCtx.toTraceparent();
// → '00-4bf92f3577b34da6...-newSpanId-01'
~340 lines, 18 APIs. Spans batch-export every 100 spans (configurable). provider.shutdown() flushes the buffer before process exit — call it in your SIGTERM handler. The singleton getProvider() / setProvider() pattern lets test suites swap exporters without touching application code.
forge/replay ✦ new in v4.2.2
Time-travel debugger for HTTP + database layers. Record every request and query into a deterministic NDJSON tape; replay it identically without touching live services. Zero deps.
Installation
import {
RecordingSession, ReplaySession, Tape, TraceEvent, EVENT,
httpRecorder, dbRecorder,
httpReplayer, dbReplayer,
replayAll, diffTapes,
} from '@hyperbridge/forge/replay';
Architecture
| Class / Function | Role |
|---|---|
RecordingSession | Captures events into a Tape; call .stop() to get the finished tape |
Tape | Ordered NDJSON log — .save(path), Tape.load(path), .stats() |
httpRecorder(session) | Middleware — records http:req + http:res events |
dbRecorder(pool, session) | Proxy around a forge/data Pool — records all SQL queries |
httpReplayer(tape) | Returns recorded responses in order — no network calls |
dbReplayer(tape) | Mock pool — returns recorded rows for every query — no DB needed |
replayAll(tape, handler) | High-level: re-executes a handler with both mocks wired up |
diffTapes(a, b) | Regression testing — surfaces status/body/db-count differences |
Record a session
import { RecordingSession, httpRecorder, dbRecorder } from '@hyperbridge/forge/replay';
import { createServer } from '@hyperbridge/forge/server';
import { Pool } from '@hyperbridge/forge/data';
const pool = new Pool({ connectionString: process.env.DB_URL });
const session = new RecordingSession({ label: 'checkout-flow' });
const server = createServer();
server.use(httpRecorder(session)); // record HTTP layer
// Wrap pool — all queries are recorded transparently
const recordedPool = dbRecorder(pool, session);
server.post('/checkout', async ctx => {
const items = await recordedPool.query('SELECT * FROM cart WHERE user_id = $1', [ctx.user.id]);
ctx.body = { items: items.rows };
});
await server.listen(3000);
// After your test scenario runs:
const tape = session.stop();
await tape.save('./fixtures/checkout.ndjson');
Replay without live services
import { Tape, replayAll } from '@hyperbridge/forge/replay';
const tape = await Tape.load('./fixtures/checkout.ndjson');
const { results, stats } = await replayAll(tape, async (mockPool, mockHttp, requests) => {
// mockPool.query() returns recorded rows — no database
const items = await mockPool.query('SELECT * FROM cart WHERE user_id = $1', ['user-123']);
return items.rows;
});
console.log(stats);
// → { total: 4, counts: { 'http:req': 1, 'http:res': 1, 'db:query': 1, 'db:result': 1 }, duration: 42 }
Regression diff
import { diffTapes, Tape } from '@hyperbridge/forge/replay';
const baseline = await Tape.load('./fixtures/checkout-v1.ndjson');
const candidate = await Tape.load('./fixtures/checkout-v2.ndjson');
const { hasDiff, diffs } = diffTapes(baseline, candidate);
if (hasDiff) {
console.error('Regression detected:', diffs);
process.exit(1);
}
~300 lines, 14 APIs. Tape format is NDJSON — one JSON object per line — so you can grep, jq, and git diff fixtures like normal text files. session.annotate('checkout started') embeds human-readable markers between events.
forge/simulator ✦ new in v4.2.2
In-process agent-mesh scenario simulator — simulate slashing, escrow unwind, SLA breach, reputation cascade, and double-spend scenarios without touching live infrastructure. Six built-in scenarios; fully composable.
Installation
import {
Simulator, SimAgent, SimEscrow, SimResult,
happyPath, agentSlashed, slaBreached,
reputationCascade, escrowUnwind, doubleSpend,
runSuite,
} from '@hyperbridge/forge/simulator';
Built-in scenarios
| Scenario | What it tests |
|---|---|
happyPath | Full task cycle — escrow locks, agent delivers, escrow releases payout |
agentSlashed | Agent misbehaves (badZkml, hallucination, sla-breach, etc.) — stake slashed, requester refunded |
slaBreached | SLA timer expires before delivery — partial payout (configurable), rest refunded |
reputationCascade | Multiple slashes compound — reputation collapses below floor, agent disqualified |
escrowUnwind | Escrow fully refunded to requester without any payout (dispute resolution path) |
doubleSpend | Second release on same escrow is rejected — idempotency enforced |
Run the full suite
import { runSuite } from '@hyperbridge/forge/simulator';
const results = await runSuite({
agentStake: 1000,
escrowAmount: 500,
slaDurationMs: 60_000,
slashPenalty: 0.2, // 20% of stake per infraction
});
for (const r of results) {
console.log(r.summary());
}
// happyPath ✓ 3 assertions 0 failures
// agentSlashed ✓ 4 assertions 0 failures
// slaBreached ✓ 3 assertions 0 failures
// reputationCascade ✓ 5 assertions 0 failures
// escrowUnwind ✓ 2 assertions 0 failures
// doubleSpend ✓ 2 assertions 0 failures
Custom scenario
import { Simulator } from '@hyperbridge/forge/simulator';
const sim = new Simulator({ slashPenalty: 0.15 });
const agent = sim.addAgent('provider-1', { stake: 800, reputation: 90 });
const escrow = sim.createEscrow('req-1', agent.did, 300, { slaDurationMs: 5_000 });
const result = await sim.run('partial-delivery', async () => {
// Agent delivers late — partial payout at 50%
const { payout, refund } = escrow.release(0.5);
sim.assert('partial payout correct', payout === 150);
sim.assert('remaining refunded', refund === 150);
agent.earn(payout);
sim.assert('balance updated', agent.balance === payout);
});
console.log(result.summary());
// partial-delivery ✓ 3 assertions 0 failures
SimAgent API
agent.slash('badZkml'); // reduces reputation, emits slash event
agent.earn(amount); // increases balance
agent.reputation; // 0–100 score
agent.balance; // current token balance
agent.did; // 'did:w7:provider-1'
agent.behaviour; // 'honest' | 'lazy' | 'malicious'
~280 lines, 16 APIs. Designed for CI — runSuite() throws on any assertion failure, so add it to your test runner and it will catch regressions in slashing / escrow math whenever you touch the settlement module. Zero network calls, runs in milliseconds.
forge/_internal Shared Primitives
Three tiny modules that host the primitives several other modules used to reinvent — cryptography, byte-level encoding, and timing. The underscore prefix is advisory: signatures are stable, internals may improve between minor versions.
You rarely need to import _internal/* directly. It's primarily here so the bundler can de-duplicate across modules. That said, these are pure functions with zero dependencies and safe to use.
_internal/crypto
import {
hmacSha256, hmacSha256Async,
sha256, sha256Async,
timingSafeEqual,
randomBytes, randomId,
toHex, fromHex, toBase64, fromBase64
} from '@hyperbridge/forge/_internal/crypto';
// Node: sync, backed by node:crypto
const sig = hmacSha256('secret', 'payload'); // hex
const mac = hmacSha256('secret', 'payload', { encoding: 'base64' });
// Browser: use async variant (WebCrypto is async-only)
const sig = await hmacSha256Async('secret', 'payload');
// Timing-safe compare — constant-time even in the fallback
timingSafeEqual(expectedSig, receivedSig);
// Cryptographically-random IDs
const id = randomId(16); // 32-char hex string
_internal/binary
import {
encodeUtf8, decodeUtf8,
readULEB128, writeULEB128, readSLEB128,
concatBytes,
readCStr, writeCStr
} from '@hyperbridge/forge/_internal/binary';
// UTF-8 with TextEncoder/TextDecoder when present, manual fallback otherwise
const bytes = encodeUtf8('héllo 🌍');
const str = decodeUtf8(bytes);
// LEB128 varints — used by the WASM parser
const { value, nextOffset } = readULEB128(bytes, 0);
const encoded = writeULEB128(624485);
// Concatenate chunks without the BufferList overhead
const all = concatBytes([header, body, footer]);
_internal/time
import {
now, nowNs,
parseDuration, formatDuration,
sleep, withTimeout
} from '@hyperbridge/forge/_internal/time';
// Monotonic clock — performance.now() with Date.now fallback
const t0 = now();
await doWork();
const elapsed = now() - t0;
console.log(formatDuration(elapsed)); // "1.23 s"
// Parse human-readable durations
parseDuration('1h30m'); // → 5_400_000
parseDuration('500ms'); // → 500
parseDuration('7d'); // → 604_800_000
// Deadline-bounded promise race
const result = await withTimeout(fetch(url), 3000, 'fetch');
// → rejects with "withTimeout: fetch exceeded 3000ms" on overrun
Who uses what
| Primitive | Used by modules |
|---|---|
_internal/crypto | auth, prime, mail, wasm, pwa |
_internal/binary | wasm, pdf, schema, client, search |
_internal/time | prime, wasm, server, ai, notify, cli |
27 APIs across 442 lines. Before v3.4.4, each of the modules above shipped its own copy of HMAC, SHA-256, UTF-8, or a duration parser. Bundlers couldn't de-duplicate. Now every module resolves to the same path — a tree-shaken build importing auth + mail no longer ships two copies of HMAC-SHA-256.
Bundle Size
HBForge ships dual CJS + ESM, with 1,400+ named exports, deep subpath imports, browser/Node conditional exports, and a sideEffects: false declaration — so modern bundlers can cut the tree aggressively.
Five phases of splitting
Versions 3.4.0 → 3.4.4 were a phased rollout, each shipped independently so apps can adopt incrementally. Full release notes →
| Version | Phase | What it delivered | Size impact |
|---|---|---|---|
v3.4.0 |
1 — tree-shake | sideEffects: false declaration |
~12–18% smaller |
v3.4.1 |
2 — dual CJS/ESM | 22 × .mjs wrappers, 1,400+ named exports, conditional import/require/types/browser |
enables phase 3+ |
v3.4.2 |
3 — deep paths | 371 leaf files for client + server + animate. forge/client/signal resolves standalone. |
enables phase 4+ |
v3.4.3 |
4 — browser/Node | 5 server-only modules (server, mail, pdf, cli, test) stubbed in browser bundles with a 1.4 KB Proxy |
~1 MB saved (browser) |
v3.4.4 |
5 — _internal |
Shared crypto, binary, time primitives at one resolvable path. De-duplicates across 6–8 modules each. | per-build savings |
What a typical app imports
// ❌ Pre-3.4: pulls everything from forge root
import forge from '@hyperbridge/forge';
forge.signal(0);
// ✅ Subpath: pulls one module
import { signal } from '@hyperbridge/forge/client';
// ✅✅ Deep path: pulls one export
import { signal } from '@hyperbridge/forge/client/signal';
// ✅ Browser-side Next.js page: mail/pdf/cli/test/server auto-stub
import { sendMail } from '@hyperbridge/forge/mail'; // → 1.4 KB stub in browser bundle
// (same code in a Node API route pulls the full 223 KB implementation)
Bundler compatibility
- Rollup / Vite / esbuild / Webpack 5+ / Parcel 2+ — honour
sideEffects, conditional exports, and wildcard subpaths. Full benefit. - Next.js 13+ / SvelteKit / Remix / Astro — set
platform: "browser"on client bundles. Phase 4 browser stubs apply automatically. - Webpack 4 — reads
sideEffects, ignores conditional exports. Subpath imports still work viamain. - Node 18+ — native conditional exports, deep paths, and
import/requireboth honoured.
Zero app changes required. Existing require('@hyperbridge/forge') and import forge from '@hyperbridge/forge' still work. Every phase is additive.