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

@driftengine/tools

v4.10.0

Published

The editor panels a shipped game can carry, and the command stack that makes their edits undoable

Readme

@driftengine/tools

The editor's panels, as something a shipped game can carry: an inspector, a console, a profiler and a network panel, with the command stack that makes their edits undoable, and the overlay that puts them on screen on a key.

5,782 bytes gzipped, measured by scripts/size-gate.test.mjs against scripts/fixtures/size/tools-only.ts. Optional — nothing in @driftengine/core imports it, so a game that never asks for these pays nothing.

Why four panels and not seven

The editor has seven. Three of them stayed behind, and the line between them is what a panel needs rather than what it shows.

A scene tree and an asset browser need a project: a hierarchy somebody is authoring and a directory of source files. A shipped game has neither — its scene is whatever was baked and its assets are inside a .drft container. A graph editor is the same, one level further out.

An inspector, a console, a profiler and a network panel need only the running world. That is exactly the situation a tester is in on somebody else's machine, or a developer is in on a console devkit where no debugger will attach, or anyone is in when the bug happens in a build. This package is the four that work there.

The panels mutate through commands, so an in-game edit is undoable

Panel.route returns a Command or nothing. It is a signature rather than a convention: a panel is handed the world Readonly and given no other way to change it, so "everything goes through the undo stack" has no worst offender. A game carrying these panels carries that rule, and gets undo for nothing extra.

import {
  createSelection,
  createUndoStack,
  entitiesInspectable,
  selectOnly,
  selectedEntities,
  setFieldCommand,
} from '@driftengine/tools';

const undo = createUndoStack(200);
const selection = createSelection();
selectOnly(selection, ball);
/* One edit to every selected entity, as one entry: `undo.undo()` takes it back. */
const command = setFieldCommand(
  entitiesInspectable(world, [Bounce]),
  selectedEntities(selection),
  'Bounce',
  ['amount'],
  [0.6],
);
if (command !== null) undo.push(command);

The inspector panel shows a selection's fields and edits none of them yet — its route answers nothing — so a game edits through setFieldCommand as above, from whatever key or control it likes.

The overlay, because a panel that nothing mounts is a tree nobody sees

Every panel above builds a UiNode tree and stops there. For 4.0.0 the half that turns a tree into something a person can look at — geometry, event routing, painting — lived only in the editor application, which is private and ships to nobody, so a game wanting an in-game inspector had to write it again. createToolsOverlay is that half.

A column rather than a dock: nobody arranges a debug overlay, they press a key, read a number and press it again, so the panels stack down one edge in the order they are given.

The painter is supplied. Four calls — a rectangle, a line of text, a clip and its close — which is every operation the overlay makes. A host with a 2D context writes four lines; a host drawing through @driftengine/ui2d writes them over a sprite batch; a host with no screen writes them into an array, which is what this package's own tests do. Nothing here needs a graphics device, which is what makes the whole of it assertable.

import { bindPanel, createToolsOverlay, paintOverlay, profilerPanel } from '@driftengine/tools';

const overlay = createToolsOverlay({
  panels: [bindPanel(profilerPanel, () => profilerWorld, profilerView)],
  key: 'F3',
});

// once, and whenever the surface changes size
overlay.resize(canvas.width, canvas.height);

// every frame, after the scene
overlay.invalidate();
overlay.frame(now);
paintOverlay(painter, overlay);

// wherever input arrives; true means the overlay took it and the game should not
if (overlay.route(keyEvent(event.key, event.shiftKey, event.ctrlKey))) event.preventDefault();

While it is closed frame returns before it builds anything and route refuses every event but the one that opens it, so a game that never presses the key pays for a boolean.

Two adapters, because every consumer was about to write the same ones

A panel needs its world in the shape the panel reads. Two of those shapes are the same in every game that has the thing behind them, so they are here rather than in each of them.

entitiesInspectable(world, types) turns an entity world into an InspectableWorld: the inspector addresses a component by name because that is what a row carries, and a world addresses it by the type object because that is what indexes its stores. A name with no type behind it does nothing rather than guessing, so a row built from a stale schema cannot write a field into a component the world does not have and report success.

createSessionRecorder plus observeSession and sessionReadout turn a lockstep session into a NetworkReadout. The watching is the part worth having written once: session.desync is latched rather than an event, so it keeps answering with the same disagreement every frame until the next one arrives, and a recorder that appended what it read would turn one divergence into sixty a second. snapshotBytes is the caller's, because RewindLoop is generic over the state it snapshots and cannot know the size of one.

Both are typed structurally, the way @driftengine/drft takes DTEX types that @driftengine/texture satisfies, so this package depends on nothing new and anything shaped right can be read — including a consumer whose state is not an entity world at all.

Building it in or leaving it out

Import it and it is in the bundle; do not and it is not. There is no flag, because a flag would be a second mechanism doing what a module graph already does — and the size gate is what says so: the floors for every other package are unchanged by this one existing.

A game that wants the tools in a development build and not in a shipping one imports them behind the bundler's own dead-code elimination, the same way it would any other optional dependency.

What is not here

The timeline scrubber. It belongs to the editor's play-in-editor session, which needs snapshots of the authored world to put back — a shipped game has no authored world to return to. The pieces underneath it (@driftengine/network's Snapshotter, RewindLoop and input log) are available to a game directly.