npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

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

About

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

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

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

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

Open Software & Tools

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

© 2026 – Pkg Stats / Ryan Hefner

@forgeax/engine-rhi-null

v0.3.4

Published

Headless no-op RHI backend for structural unit tests — implements @forgeax/engine-rhi with zero GPU/DOM dependency.

Readme

@forgeax/engine-rhi-null

Headless no-op RHI backend for structural unit tests -- inject via createRenderer(canvas, { rhi }), zero GPU / DOM dependency. Covers the full RhiDevice surface with no-op brands and a per-device handle ledger so command-stream assertions (draw count / pass schedule / create-destroy pairing / bind-group assembly) work in test:unit without a real GPU.

Access -- Channel 1 injection

import { rhi } from '@forgeax/engine-rhi-null';
import { createRenderer } from '@forgeax/engine-runtime';

// `canvas` is a required positional param. In headless CI hand it any object
// shaped like a canvas -- RhiNull never touches the DOM, so a minimal stub is
// enough (the dogfood tests use one; see packages/runtime/src/__tests__).
const canvas = { width: 1, height: 1 } as unknown as HTMLCanvasElement;
const created = await createRenderer(canvas, { rhi });
if (!created.ok) throw created.error;
const renderer = created.value;

The exported rhi singleton has the RhiBackendPack-mandated shape (RhiInstance & { acquireCanvasContext; createShaderModule }). createRenderer picks it up via Channel 1's rendererOptions.rhi escape hatch -- no backend auto-selection logic is modified, no navigator.gpu probe involved.

| Entry | Type | Purpose | |:--|:--|:--| | rhi | RhiInstance & { acquireCanvasContext; createShaderModule } | Singleton; inject into createRenderer({ rhi }) | | rhi.requestAdapter(opts?, canvas?) | async fn | Returns ok(new RhiNullAdapter()) | | rhi.createShaderModule(device, desc) | async fn | Returns ok(ShaderModule) without real compile | | rhi.acquireCanvasContext(canvas?) | fn | Returns ok(RhiNullCanvasContext) for any canvas / null / undefined | | rhi.adapter.requestDevice() | async fn | Returns ok(new RhiNullDevice(queue, encoderFactory)) | | RhiNullDevice | class implements RhiDevice | All create* mint legal brands + ledger entries; all destroy* fail-fast on double-destroy | | RhiNullQueue | class implements RhiQueue | submit returns ok; onSubmittedWorkDone resolves immediately |

Capabilities

renderer.inspect().capabilities reports the D-5 profile: every boolean cap is true except the three @reserved-for-wgpu-native-only fields (multiDrawIndirect, pushConstants, textureBindingArray), which are false. maxColorAttachments is 8. The headless backend maximizes structural coverage -- any caps.X gate in the engine codebase passes through:

| Cap | Value | Note | |:--|:--|:--| | backendKind | 'null' | 4th union member alongside 'webgpu' / 'wgpu-native' / 'wgpu-webgl2' | | compute | true | | | timestampQuery | false | Structural backend; GPU timing is refused | | indirectDrawing | true | | | textureCompressionBc | false | headless has no compression hardware (AC-06) | | textureCompressionEtc2 | false | headless has no compression hardware (AC-06) | | textureCompressionAstc | false | headless has no compression hardware (AC-06) | | multiDrawIndirect | false | @reserved-for-wgpu-native-only | | pushConstants | false | @reserved-for-wgpu-native-only | | textureBindingArray | false | @reserved-for-wgpu-native-only | | samplerAliasing | true | | | firstInstanceIndirect | true | | | storageBuffer | true | | | storageTexture | true | | | rgba16floatRenderable | true | | | rg11b10ufloatRenderable | true | | | float32Filterable | true | | | maxColorAttachments | 8 | >= 4 (HDRP deferred minimum) |

[!WARNING] RhiNull is deliberately non-timing. Timestamp query creation returns a structured feature error and membership timing must terminate as timestamp-query-unsupported; do not turn the structural ledger into fake GPU evidence.

device.features returns an empty ReadonlySet; device.limits returns an empty Record. Capability planning reads caps booleans, not the feature set.

Handle bookkeeping

Every create* call on RhiNullDevice registers the returned handle in a per-device Bookkeeper. Each ledger row is a HandleRecord:

interface HandleRecord {
  readonly id: number;          // per-device monotonic
  readonly kind: string;        // 'Buffer' / 'Texture' / 'BindGroupLayout' / ...
  destroyed: boolean;           // flipped on first destroy
  readonly sourceDeviceId: number;  // cross-device validation
}

Readback for assertions -- the device ledger is an owner-local test concern. Public Renderer exposes no device; inject the RhiNull pack into a package-local construction test when counters are needed:

import type { RhiNullDevice } from '@forgeax/engine-rhi-null';

const device = ownerLocalDevice as RhiNullDevice;
device.totalDrawCount = 0;       // reset before a frame if you assert deltas
device.totalDispatchCount = 0;
renderer.draw(world);
const records = device.bookkeeper.allRecords(); // snapshot of all ledger rows

M3 unit tests read this to assert:

  • Create / destroy pairing (kind: 'Buffer' count == kind: 'Buffer' with destroyed === true count)
  • Draw count >= 1 (device.totalDrawCount after renderer.draw(world))
  • Compute dispatch count (device.totalDispatchCount, including indirect dispatch)
  • Bind-group assembly counts (device.totalBindGroupCount)
  • Pass schedule order -- prefer the type-safe renderer.perFramePassNames (no cast needed); device.framePassNames is the same data on the cast RhiNullDevice
  • BGL / PSO shape (ledger entries with kind: 'BindGroupLayout' / kind: 'RenderPipeline')

Handle-chain validation -- setVertexBuffer / setBindGroup validate handle ownership. A handle issued by device A passed to device B returns err({ code: 'rhi-not-available', expected, hint }). A second destroy on the same handle returns err({ code: 'destroy-after-destroy', expected, hint }). Zero new RhiErrorCode members -- reuses existing closed-union codes (D-1).

Pipeline getBindGroupLayout

createRenderPipeline and createComputePipeline return pipeline handles that carry a no-op getBindGroupLayout(index) method (D-2). The method returns a legal BindGroupLayout brand registered in the ledger, so the auto-layout path in debug-draw.ts and existing mock unit tests do not crash on a missing method.

Boundaries

  • Structural only, never produces pixels. RhiNull covers the command-stream protocol layer (buffer / texture / bind-group / pipeline / pass-encoder creation and submission). It does not execute shaders, write to GPU memory, or produce render-target contents. getCurrentTexture() returns a brand with no pixel data.
  • Does not replace dawn / browser pixel readback. Smoke tests (apps/hello/*, apps/learn-render/*) still need a real GPU backend (rhi-webgpu or rhi-wgpu). RhiNull is for test:unit structural assertions, not visual verification.
  • No image-channel dependency (charter P2). The README is ASCII-only; the backend has no screenshots, no pixel baselines, no render-target PNGs.
  • Per-renderer instances are independent. Each createRenderer({ rhi }) creates a fresh RhiNullDevice with its own Bookkeeper. Cross-instance handle pollution is impossible because each device's ledger keys on sourceDeviceId.
  • 'null' union member meaning. The 3 pre-existing backends (webgpu / wgpu-native / wgpu-webgl2) are unchanged. 'null' is the 4th member, representing "no real GPU -- structural-only command-stream ledger." render-graph barrier insertion treats it as no-barrier (same equivalence group as webgpu / wgpu-webgl2).

Distinction from vitest mocks

| Aspect | vitest mock (vi.fn()) | RhiNull (createRenderer({ rhi })) | |:--|:--|:--| | Surface | Partial stub (caller chooses what to mock) | Full implements RhiDevice (tsc-complete) | | Ledger | None (assert .toHaveBeenCalled() on mocks) | Bookkeeper.allRecords() + totalDrawCount / framePassNames | | Integration depth | Tests one function call at a time | Exercises real createRenderer -> Result<Renderer> -> renderer.draw(frame) path | | Handle validation | None | Cross-device and double-destroy fail-fast | | Render-graph coverage | Mock RhiCommandEncoder return values | Real pass scheduling through URP default pipeline |

Use vitest mocks for isolated unit tests (single function / class). Use RhiNull for structural integration tests that need the full engine lifecycle (renderer init -> draw -> command flow).

Dependencies

  • @forgeax/engine-rhi (workspace) -- interface contract SSOT.
  • @forgeax/engine-types (workspace) -- POD types / Result SSOT.
  • @webgpu/types (workspace) -- descriptor type alignment; no real GPU binding.

Render bundles

Bundle recording counts commands without incrementing submitted device counters. Each execution contributes its draws and bindings, rejects foreign bundles and destroyed directly referenced buffers, and permits repeated execution. These are structural facts only; Browser/Dawn pixel and native validation gates remain required.