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

@milehimikey/em

v0.2.0

Published

AI-friendly Event Modeling — a slice-first text DSL rendered to a strict, self-contained Graphviz grid (SVG/PNG)

Downloads

532

Readme

em — AI friendly event modeling

em is a command-line tool for Event Modeling. You write a model in a small, plain-text, slice-first DSL (.em) and em renders it to a clean, deterministic diagram (SVG by default, PNG on request). Because the source is just text — diff-able, reviewable, and unambiguous — it's as easy for an LLM to generate and edit as it is for a person to write by hand. Layout is a strict grid: swimlane rows and time-ordered slice columns stay perfectly aligned with no manual offsets.

examples/order-fulfillment.em  ->  em render  ->  order-fulfillment.svg

Rendering is self-contained — Graphviz runs as bundled WebAssembly and PNG is rasterized in-process, so there's nothing to apt/brew install for SVG or PNG output.

Install

npm install -g @milehimikey/em     # then `em` is on your PATH

Requires Node ≥ 18. SVG and PNG need no other tools. PDF (and other Graphviz raster formats) are optional and use a system rsvg-convert (librsvg) if one is installed.

Run without installing:

npx @milehimikey/em init model.em
npx @milehimikey/em render model.em

Quickstart

em init model.em          # scaffold a starter model
em render model.em        # -> model.svg  (open it in a browser)
em watch model.em         # re-render on every save
em validate model.em      # check event-modeling rules

A .em model is a list of slices (vertical time steps), each holding elements that fall into swimlane rows:

model "Order Fulfillment"

persona Customer           # UI swimlanes (top), one row per persona
context Order              # event swimlanes (bottom), one row per context
context Payment

slice "Browse Catalog" {           # each slice is one column (time, left -> right)
  ui Product Catalog @Customer     # @Persona places the screen in a persona row
  command Place Order {            # a `{ … }` block declares the element's fields
    customerId
    total: Money                   # optional `: Type` annotation
  }
  event Order Placed @Order note "notes/order-placed.md" {   # note "…" links docs
    orderId
    total: Money
  }
}

slice "View Open Orders" {
  view Open Orders from "Order Placed"   # from "<event>" wires the data flow
  ui Order List @Customer
}

The four patterns

Event Modeling is built from four patterns, all expressible as slices:

| Pattern | Flow | DSL elements | |---|---|---| | Input (state change) | UI → command → event | ui, command, event | | Output (state view) | event → read model → UI | event, view from "…", ui | | Automation | read model (todo) → processor → command → event | view from "…" + processor, then command + event | | Translation | external read model → translation → command → event | view from "…" + translation, then command + event |

Automation/translation slices are not triggered by a command — they react to a read model (a "todo list") or a timer and issue a command when done. The automation slice contains only the read model it reads and the processor/translation; the command it triggers (and that command's event) is the next slice.

DSL reference

Element keywords → swimlane bands (top → bottom)

  1. (header) — each slice name is rendered as a title cell in the top row
  2. automation / processor / saga / translationAutomation band (only shown if used)
  3. uipersona rows (one per persona)
  4. command + view — the API row (commands and read models share one lane)
  5. eventcontext/concept rows (one per context)

Commands and read models share the API row, so a slice holds either a command or a read model. The empty API lane is dropped when a model has neither; pass --keep-empty-lanes to keep it.

@Persona / @Context choose the row within a band (undeclared tags create a new row). view … from "Event"[, "Event2"] declares which events feed a read model and draws the data-flow arrow. Arrows within a slice are inferred from the pattern; use arrow A -> B for anything extra (e.g. a read model feeding a different screen).

Fields

Any element can declare data fields in a { … } block — the data it accepts (command), records (event), projects (read model), or shows (UI). Each field is name with an optional : Type; write one per line, or inline and comma-separated:

command Place Order {            # one per line
  customerId
  items: List<LineItem>
  total: Money
}

event Payment Requested @Payment { orderId, amount: Money }   # inline

Fields render in the box, UML-style: the name, a divider rule, then the fields. The box grows to fit them (width stays fixed, so columns stay aligned), and arrows still anchor to the box edges so the lines stay stable. A field block coexists with note/from clauses on the same element. Fields are the foundation of the later event-modeling phases (the slicing / information-completeness process); field-level validation across slices is planned.

Notes

Any element can carry note "path.md" (valid on every keyword). The prose lives in the markdown file — keeping the diagram uncluttered — and the box gets a small numbered folded-corner marker in its top-right corner. A legend is appended below the diagram mapping each number to its element and note file, so even a static export tells you which note belongs to which box.

In SVG output the markers and legend rows are links; clicking one opens the markdown file. Links resolve relative to the output SVG's location, so they keep working whether the SVG is rendered beside the model or into another folder (as long as the notes travel with it). Open the SVG in a web browser to use the links — image viewers like macOS Preview/Quick Look show the markers but ignore SVG hyperlinks. Raster output (PNG/PDF) can't carry links — that's what the footnote numbers + legend are for.

Commands

em init [file]                        # scaffold a starter model (default: model.em)
em render <file> [-o out.svg]         # render (format from -o extension or -T)
em render <file> -o out.png           # PNG (in-process, no system deps)
em render <file> --emit-dot           # print the generated DOT instead of rendering
em render <file> --keep-empty-lanes   # don't collapse the API lane when empty
em watch <file> [-o out.svg]          # re-render on every save
em validate <file>                    # check event-modeling rules (non-zero exit on errors)

Validation rules (em validate)

  • two same-band elements in one slice (collision) → error (split into separate slices)
  • a read model whose from "Event" doesn't exist → error
  • an automation/translation whose from "<read model>" doesn't exist → error
  • an arrow endpoint that matches no element → error
  • an automation/translation slice that also holds the command it triggers → warning
  • a command that records no event → warning
  • a read model with no source event → warning
  • a name defined more than once and referenced → warning (resolves to first)

Documentation

Development

npm install
npm run build          # produces dist/, exposes the `em` bin
npm test               # vitest
npx tsx src/cli.ts <command> ...   # run straight from source

AI Assistant (Claude Code)

em ships a Claude Code skill that guides you and your team through Event Modeling using the 7-step process and 4 design patterns, producing implementation-ready slice specifications. The AI drives the model — asking focused questions, never guessing domain facts — while em validate keeps the diagram honest.

Install the skill into your project:

em skill install          # copies the skill into .claude/skills/event-modeling/
em skill install --force  # overwrite an existing installation

Then, in Claude Code, run:

/event-modeling           # start (or resume) a guided session
/event-modeling discover  # steps 1–4: brainstorm events, timeline, commands, read models
/event-modeling model     # steps 5–7: swimlanes, patterns, completeness check
/event-modeling slice     # deep implementation specs, one per slice
/event-modeling watch     # open a live browser view for team modeling sessions
/event-modeling validate  # run em validate and resolve all diagnostics

Sessions are resumable — the skill checkpoints progress in .event-modeling.md so you can continue across conversations.

For a complete worked example (a headless CPQ system with ~50 slices), see the em-with-ai repository.

License

MIT © milehimikey