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

@zeeuw/bag-of-holding

v0.2.0

Published

A tiny, AI-agnostic D&D 5e (SRD 5.2) rules kernel. Zero runtime dependencies, single ESM file, CDN-loadable, plugin-extensible. Dice, checks, combat, weapon mastery, conditions, XP, movesets, and a beat-based story runtime.

Readme

bag-of-holding

npm version bundle size zero deps types: built-in coverage 100% SRD 5.2 (2025) license: MPL 2.0

A tiny, AI-agnostic D&D 5e (SRD 5.2) rules kernel. Zero runtime dependencies, single ESM file, CDN-loadable. Designed to plug into any AI-driven RPG host — or any host — without locking you to a model, a framework, or a virtual tabletop.

The math is the engine. The host owns the prose, the persistence, and the AI loop. See docs/why.md for the case behind the design.

Highlights

  • Zero runtime dependencies. One ESM file, auditable line by line.
  • AI-agnostic by construction. No network calls, no DOM, never talks to a model. See docs/boundary.md.
  • Pure functions, plain data. Every result is serialisable; replay-deterministic when seeded with Dice.seededRng(seed).
  • Forensically inspectable randomness. Append-only rollLog, optional context tags per roll, and a verifyLog replay verifier that flags any divergence between recorded and re-executed outcomes.
  • Plugin-extensible at the kernel. Phase A (content: species, classes, items, conditions, weapon-mastery handlers) and Phase B (rule knobs: critOn/fumbleOn, damageFloor, explodingDamageDice, XP curve overrides) both via createEngine({ … }). Phase C (behavioural hooks) is next on the roadmap.
  • Character sheets. Character.deriveSheet(record, engine) turns a host-owned record into a frozen sheet (AC, HP, saves, skills, attacks, spellcasting). Host owns the record; engine derives.
  • TypeScript types included. Hand-maintained index.d.ts with a tsc --noEmit drift gate. No @types/ install needed.
  • SRD 5.2 (2025). Weapon Mastery, numeric Exhaustion, Backgrounds-as-ability-source — the current rules, not a 5.1 carry-over.
  • 100% line / branch / function coverage as an ongoing contract.

Install

npm install @zeeuw/bag-of-holding

Or drop it straight into a static page from a CDN — no build step:

<script type="module">
  import { Combat, SRD } from 'https://unpkg.com/@zeeuw/bag-of-holding';

  const result = Combat.attackRoll({ attackBonus: 5, ac: 14 });
  console.log(result);
</script>

Use

import {
  Dice, Checks, Combat, Conditions, XP, Movesets, Beats, SRD
} from '@zeeuw/bag-of-holding';

// Roll dice with the standard XdY±Z grammar.
Dice.roll('2d6+3');
// → { spec: '2d6+3', rolls: [4, 5], modifier: 3, total: 12 }

// Resolve an attack and (on a hit) the damage.
const attack = Combat.attackRoll({ attackBonus: 5, ac: 14 });
if (attack.hit) {
  Combat.damageRoll({
    damageDice: '1d8',
    damageMod: 3,
    critical: attack.critical
  });
}

// SRD 5.2 Weapon Mastery — riders resolve declaratively.
const longsword = SRD.items.longsword;       // mastery: 'sap'
const rider = Combat.applyMastery(longsword, target, attack);
// → { kind: 'sap', disadvantage: true }     (target's next attack)

// Conditions are immutable — apply / remove returns a new actor.
const blinded = Conditions.apply(actor, 'blinded');
const tired   = Conditions.exhaustion.gain(actor);   // 0..6, SRD 5.2

// XP and level math.
XP.levelForXP(2700);          // → 4
XP.nextLevelThreshold(2700);  // → 6500

Custom rules (plugins)

For homebrew content — extra species, alternate conditions, custom weapon-mastery properties — instantiate a custom engine:

import { createEngine } from '@zeeuw/bag-of-holding';

const engine = createEngine({
  extraSpecies: {
    'half-elf': {
      id: 'half-elf', name: 'Half-Elf',
      size: 'medium', speed: 30, traits: ['Adaptable']
    }
  },
  extraConditions: ['cursed'],
  extraMastery: {
    pin: (weapon, target, result) =>
      result.hit
        ? { kind: 'pin', condition: 'grappled', duration: '1 turn' }
        : { kind: 'none' }
  }
});

engine.Combat.applyMastery({ mastery: 'pin' }, target, attackResult);
engine.Conditions.apply(actor, 'cursed');
engine.species['half-elf'];

The default singleton (the Combat, Conditions, … you import directly) is just createEngine() with no opts. Two engines on the same page have fully independent registries; nothing leaks.

See docs/spec.md § Plugins for the full contract, validation behaviour, and merge semantics.

Documentation

  • docs/recipes.md — pragmatic patterns for building clients: dice as a static-page widget, attack-from-stealth, custom weapon mastery, seeded sessions, save & restore, AI-loop shape, and more. The "how do I actually do X?" reference.
  • docs/why.md — the case for the library: market gap, niche, moat, the conditions under which this would be a waste of time. Read first.
  • docs/spec.md — what the engine implements (and what it doesn't); plugin contract; types.
  • docs/character-sheet.md — the host/engine contract for character records, the DerivedSheet schema, and the worked example end-to-end.
  • docs/roadmap.md — versioned milestones from today (0.x) to feature-complete (1.0) and the vision behind them.
  • docs/boundary.md — the contract: what the engine won't do.
  • docs/character-sheet.md — the CharacterRecord (host-owned) ↔ DerivedSheet (engine-derived) contract with a worked example.
  • docs/beat-schema.md — the story-beat shape and runtime.

Requirements

  • Any ESM-capable runtime for consumption (Node ≥ 18, every modern browser, Deno, Bun).
  • Node ≥ 22 for development (the test suite uses node --test with --experimental-test-coverage).

Development

npm test                  # 115 tests, ~70ms
npm run test:coverage     # 100 / 100 / 100 line / branch / function
npm run typecheck         # tsc --noEmit against the hand-maintained .d.ts

The coverage and typecheck scripts are the quality gates; the library maintains 100 / 100 / 100 as an ongoing contract, not a one-time achievement.

License

MPL 2.0 — file-level copyleft. Use the engine in any application, closed or open. If you modify any of the engine's files, those modifications stay under MPL 2.0 and remain available in source form; everything around the engine in your application is unaffected. See docs/why.md § Why MPL 2.0 for the reasoning.

Built on the SRD 5.2 by Wizards of the Coast, licensed CC-BY-4.0.