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

@peekling/runtime

v0.1.5

Published

Dependency-free browser runtime for interactive Peekling characters

Downloads

1,137

Readme

@peekling/runtime

The dependency-free browser engine for data-only Peekling character Packs. Developers choose a Pack and describe page behavior with one Configuration and one immutable Plan. Peekling validates both before it renders.

The runtime performs no telemetry and requires no Peekling backend. Character data cannot contain code, callbacks, DOM nodes, or raw HTML.

Install

npm install @peekling/[email protected]

The runtime supports modern ESM applications and the complete browser bundle. Tooling and server-side imports require Node 22.14 or newer.

First character with ESM

This Vite example emits the package stylesheet as an asset, selects the pinned peek Pack, waits for the first valid runtime state, and owns teardown:

import { hatch } from "@peekling/runtime";
import peeklingStyles from "@peekling/runtime/peekling.css?url";

let companion;

try {
  companion = hatch({
    character: "peek",
    styles: { url: peeklingStyles },
  });

  await companion.ready;
} catch (error) {
  console.error("Peekling did not start", error);
}

// Call this from the owning view's teardown hook.
export function destroyPeekling() {
  companion?.destroy();
}

After ready resolves, the Pack, stylesheet, initial State, and owned DOM are ready. character: "peek" is a networked selection. It resolves an exact pinned manifest and verifies the manifest and selected atlas before browser decoding.

The default companion behavior follows the pointer when the Pack provides locomotion. The character can also be dragged and thrown. Clicking the character shows or hides its owned content bubble. Set interaction: false only when the host needs a pointer-transparent character with no direct control.

Other bundlers can copy @peekling/runtime/peekling.css to a public asset and pass its final URL through styles.url. An HTTP or HTTPS ESM module can omit styles when a usable peekling.css sits beside the module. A module location that cannot identify an HTTP or HTTPS stylesheet fails with styles.default instead of silently rendering without styles.

Choose a Pack

Configure one source when possible:

| Source | Example | Network behavior | | ----------------- | -------------------------------------------------- | ------------------------------------------------------------- | | Registered alias | { character: "peek" } | Fetches the alias's pinned manifest and one atlas. | | Explicit manifest | { packUrl: "/characters/moss/character.json" } | Fetches that manifest and one declared atlas. | | Inline Pack | { pack, atlasUrl: "/characters/moss/atlas.png" } | Uses supplied Pack data and fetches the declared atlas bytes. |

If merged Configuration contains several sources, the runtime uses pack first, then packUrl, then character. atlasUrl changes only the asset location for bytes already declared by the selected Pack. It is not an image conversion hook.

Every atlas candidate needs a 64-character lowercase SHA-256. For an adaptive Pack used with atlasUrl, set density to one declared variant. The runtime loads that candidate once and disables automatic density upgrades for the mirrored asset.

Public instance API

hatch returns one independently owned instance:

| Member | Purpose | | --------------------- | ----------------------------------------------------------------------------------------------------------- | | ready | Rejecting startup promise. Await it before relying on Pack-specific behavior. | | finished | Non-rejecting terminal promise. Settles after cleanup with destroyed, pagehide, or failed. | | emit(name, payload) | Admit one bounded application Event and return its admission result. | | override(input) | Request temporary ownership of declared presentation channels. | | pause() | Add the host pause reason and park runtime work. | | resume() | Remove the host pause reason. Work resumes after every suspension reason clears. | | refreshTargets() | Refresh host-approved target geometry after an application-specific layout change. | | setIndicator(value) | Replace the accessible dot or count notification, including its optional color. Pass null to clear it. | | destroy() | Abort pending work and release the instance's listeners, observers, frames, timers, roots, and object URLs. |

Configuration failures that can be decided without Pack bytes throw before hatch returns. Pack loading, Pack-dependent references, stylesheet setup, and browser resource failures reject ready. Wrap both stages in the same try block when the host needs one startup error path.

finished never rejects and settles once. Cleanup is idempotent, so calling destroy() again is safe.

Events and temporary control

Application Events are bounded immutable facts:

await companion.ready;

const result = companion.emit("job.progress", {
  session: "build-42",
  revision: 3,
  completed: 12,
  total: 20,
});

if (!result.accepted) {
  console.warn("Event was not admitted", result.reason);
}

emit does not mutate the Plan, render inline, or run host code inline. It validates and snapshots the payload, then schedules ordinary Plan evaluation. The queue holds 32 Events. A full queue rejects the new Event without evicting accepted work. Eligible streams can opt into coalesce: "latest". Other rate limits belong in host code before emit.

An Override uses the same Effect and channel model as the Plan:

const handle = companion.override({
  effect: {
    channels: ["state"],
    state: { state: "happy" },
  },
  until: { type: "duration", ms: 2000 },
});

console.log(await handle.finished);

The selected State must exist in the Pack. An Override never edits the Plan. When its lifetime ends, the runtime reevaluates the unchanged Plan from current world state.

Drag, throw, click, and custom routes

Direct manipulation is enabled without adding Plan rules:

const companion = hatch({
  character: "peek",
  interaction: {
    gravity: 1800,
    maxThrowSpeed: 1800,
    bounce: 0.35,
    label: "Open controls or drag Peek",
  },
});

Press actions can toggle, show, or hide content. They can also emit one bounded application Event. A drag suppresses its trailing click. Throw integration is time based, bounded to the viewport, and releases ownership back to the Plan after landing.

Targets connect motion or catching to host-approved geometry. Custom SVG paths remain data and are sampled with detached browser geometry. See Character interaction and motion for a complete sunlit nook, viewport traversal, target catching, notification badge, and custom path examples.

Optional Canvas renderer

Use the Canvas entry point when character frame presentation should use Canvas 2D:

import { hatchCanvas as hatch } from "@peekling/runtime/canvas";

Canvas uses the same Configuration, Pack, Plan, Event queue, interaction, physics, targets, and lifecycle as the default DOM renderer. Content bubbles remain DOM surfaces so native controls and accessibility keep working.

See the repository configuration guide for Plan syntax and recipes. The execution model defines Event ordering, channel ownership, Override conflicts, and cleanup.

Web Component

The complete browser file registers <peekling-character> when that name is free. The element is a lifecycle facade over hatch, not a second engine.

<script
  defer
  src="https://cdn.jsdelivr.net/npm/@peekling/[email protected]/dist/peekling.min.js"
  integrity="sha384-<runtime-release-hash>"
  crossorigin="anonymous"
></script>

<peekling-character
  character="peek"
  styles-url="https://cdn.jsdelivr.net/npm/@peekling/[email protected]/dist/peekling.css"
  styles-integrity="sha256-<stylesheet-release-hash>"
></peekling-character>

Replace both integrity placeholders with the values emitted by the selected release. Cross-origin JavaScript and CSS need CORS. The page must allow the runtime, stylesheet, manifest, atlas fetch, and verified Blob atlas through its CSP.

Connection hatches one instance. Disconnection destroys it. Reconnection gets a new ready promise and a fresh instance. Object values such as plan, content, bindings, theme, styles, accessibility, diagnostics, and logger are JavaScript properties. Scalar Pack and stylesheet selections also have documented attributes.

Importing the ESM root does not register the element or create a browser global. Call definePeeklingElement() explicitly when an ESM application wants the declarative facade.

The browser file claims globalThis.Peekling and <peekling-character> independently. It never overwrites a host value or foreign custom element. A collision dispatches peekling:collision on globalThis and names the occupied surface in event.detail.

Content and trusted host code

Packs and serialized Configuration remain data only. A Plan Effect may select a named content item and immutable data. Built-in text and links render through safe DOM operations.

Trusted JavaScript Configuration may register a mount function for custom host content. Peekling supplies an empty engine-owned root, immutable data, an AbortSignal, and an emit helper. The host creates nodes with DOM APIs and returns cleanup. Peekling contains mount, update, cleanup, and rejected-promise failures to that surface. The scheduler never waits for host code.

Framework adapters, if used, belong above this mount seam. They do not become runtime dependencies or introduce another scheduler.

Lifecycle and visibility

Host pause, hidden-page suspension, page lifecycle, and site dismissal are composable reasons. The instance resumes only after every active reason clears. Ordinary parked instances own no runtime animation frame, Plan timer, or evaluator work. A clock-based dismissal owns one recovery timeout until show, expiry, or teardown.

Reduced motion is a rendering policy, not another Plan. It shows a validated static tableau and suppresses incompatible motion while preserving host-page interaction. motionPreference defaults to "system". Choose "full" when direct manipulation and physics are essential, or "reduce" for an explicitly static instance.

Direct hatch integrations in single-page applications must call destroy() when the owning view unmounts. pushState, hash changes, and client-side route changes do not end the document. A disconnected Web Component performs its own teardown.

The public visibility helpers let a host provide a normal recovery action after site dismissal:

import {
  hidePeekling,
  isPeeklingHidden,
  showPeekling,
} from "@peekling/runtime";

hidePeekling("1-hour");
console.log(isPeeklingHidden());
showPeekling();

Browser and security boundary

  • Pack data is schema-validated, bounded, and never evaluated.
  • Pack strings never enter HTML-parsing sinks.
  • The character root is aria-hidden and pointer-transparent.
  • Browser listeners are passive and never cancel host interaction.
  • Strict CSP needs no unsafe-inline or unsafe-eval exception.
  • The runtime sends no telemetry and opens no WebSocket or event stream.
  • Closed Shadow DOM provides encapsulation, not a security boundary.
  • Host code and Peekling still share the browser main thread.

Manifest and atlas origins belong in connect-src. The verified atlas renders from a browser-created Blob URL, so img-src must allow blob:. The runtime script uses script-src, and peekling.css uses style-src. See compatibility and hosting for CORS, SRI, MIME, caching, self-hosting, diagnostics, and browser limits.

For symptom-to-action help with startup, Pack loading, CSP, lifecycle, and Event admission, use the repository troubleshooting guide.

Package entry points

| Import | Contents | | -------------------------------- | ---------------------------------------------------------------------------------------------------------- | | @peekling/runtime | Side-effect-free ESM hatch API, public types, visibility helpers, and explicit Web Component registration. | | @peekling/runtime/canvas | Optional Canvas 2D character renderer using the same runtime contract. | | @peekling/runtime/browser | Complete browser-global artifact. | | @peekling/runtime/peekling.css | Required runtime stylesheet asset. | | @peekling/runtime/pack | Supported Pack parsing and validation surface for authoring tools. | | @peekling/runtime/preflight | Node-safe validation tooling used by developer packages. |

Internal compilers, evaluators, queues, renderers, schedulers, and browser host classes are package-private. @peekling/preflight, @peekling/vite, @peekling/cli, and @peekling/adapter-codex-pet are separate packages. They do not enter the complete browser JavaScript artifact.

Verification and size

Browser tests cover strict CSP, ESM and browser hatch, Web Component lifecycle, host surfaces, blocked stylesheets, suspension, dismissal recovery, remount cleanup, hostile host layout, root repair, and contained failures in Chromium, Firefox, and WebKit.

The 0.1.5 record below measures the interaction and renderer release with the exact release toolchain and required reserve.

The recorded canonical delivery measurement is 38,428 bytes gzip and 33,684 bytes Brotli. Against the 40 KiB gzip and 40 KiB Brotli caps, that recorded build leaves 2,532 bytes of gzip headroom and 7,276 bytes of Brotli headroom. After the required 256-byte reserve, 2,276 gzip bytes and 7,020 Brotli bytes remain for that build.

The record is bound to artifact hashes. Node 22.14.0, npm 11.16.0, and Node's default zlib compression certify those recorded values. Other supported Node versions still enforce their local size cap and reserve.

The browser performance guide explains the three-browser release harness and what its result can and cannot prove.

License

Apache-2.0. Character artwork keeps its own Pack license. See licensing and attribution.