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

v2.1.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), Phase B (rule knobs), Phase C (behavioural hooks), and Phase D (turn- lifecycle and scene events) all via createEngine({ ... }). See docs/spec.md § Plugins.
  • 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.
  • SRD-completeness tracked transparently. See docs/srd-coverage.md for the live per-section worklist.

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, Combat, Conditions, XP, 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 → damage → applied HP, with resistance + tempHp
// folded in automatically.
const attack = Combat.attackRoll({ attackBonus: 5, ac: 14 });
if (attack.hit) {
  const dmg = Combat.damageRoll({
    damageDice: '1d8', damageMod: 3,
    damageType: 'slashing', critical: attack.critical
  });
  Combat.applyDamage(target, { amount: dmg.total, type: dmg.damageType });
}

// SRD 5.2 Weapon Mastery, riders resolve declaratively.
const longsword = SRD.items.longsword;       // mastery: 'sap'
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

The engine ships ~20 namespaces: Dice, Checks, Combat, Conditions, XP, Spellcasting, Rest, Mechanics, SceneClock, MagicItems, Monsters, Movement, Multiclass, Inspiration, EncounterDesign, Movesets, Beats, Character, plus the SRD content alias. See docs/recipes.md for worked examples of how they combine.

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 and compound flows (combat actions menu, damage pipeline, class mechanics, magic-item lifecycle, ritual casting, AoE spells, ...). 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/srd-coverage.md, line-by-line checklist against the SRD 5.2. The live SRD-compliance worklist.
  • docs/character-sheet.md, the CharacterRecord (host-owned) ↔ DerivedSheet (engine-derived) contract with a worked example.
  • docs/roadmap.md, versioned milestones and the vision behind them.
  • docs/boundary.md, what the engine won't do.
  • docs/beat-schema.md, the story-beat shape and runtime.
  • docs/legal.md, what we can and can't reference from D&D / Wizards of the Coast; SRD 5.2 vs Product Identity.

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                  # node --test
npm run test:coverage     # 100 / 100 / 100 line / branch / function
npm run typecheck         # tsc --noEmit against the hand-maintained .d.ts
npm run bundle-size       # measure min + gzip against the budget

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.