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

@bpmnkit/core

v1.6.1

Published

TypeScript-first BPMN 2.0 SDK — parse, build, layout, and optimize diagrams

Readme

npm license typescript ai-assisted tier: core

Website · Documentation · GitHub · Changelog

Core tier. Semver at 1.0: nothing breaks without a major release. See product tiers.


Overview

@bpmnkit/core is the foundation of the BPMN Kit. It gives you everything to work with BPMN 2.0, DMN 1.3, and Camunda Form definitions in pure TypeScript — no XML wrestling, no runtime dependencies.

Parse → Modify → Validate → Export

Features

  • BPMN 2.0 — parse, create, and export process diagrams with full Zeebe/Camunda 8 extension support
  • Fluent Builder API — construct valid processes programmatically, never touch raw XML
  • Sugiyama Layout Engine — auto-position elements with clean orthogonal edge routing
  • DMN 1.3 — decision tables, including FEEL expression support
  • Camunda Form Definitions — type-safe form schema builder
  • Optimizer — built-in rule engine to detect and auto-fix anti-patterns
  • Compact Format — 70% smaller token-efficient JSON representation for AI/LLM workflows
  • Connector catalog — all Camunda 8 out-of-the-box connector templates, applied deterministically (@bpmnkit/core/connectors)
  • Zero Dependencies — runs in browsers, Node.js, Deno, Bun, and edge runtimes

Installation

npm install @bpmnkit/core
pnpm add @bpmnkit/core

Quick Start

Build a process from code

Every element takes (id, options). Branches off a gateway are declared with .branch(), not by wiring flows by hand — the builder creates the sequence flows.

import { Bpmn } from "@bpmnkit/core"

const defs = Bpmn.createProcess("order-flow")
  .name("Order Flow")
  .startEvent("start", { name: "Order Received" })
  .serviceTask("validate", {
    name: "Validate Order",
    taskType: "order-validator",
    ioMapping: {
      inputs: [{ source: "=order", target: "order" }],
      outputs: [{ source: "=valid", target: "isValid" }],
    },
  })
  .exclusiveGateway("check", { name: "Order Valid?" })
  .branch("valid", (b) =>
    b
      .condition("=isValid = true")
      .serviceTask("fulfill", { name: "Fulfill Order", taskType: "fulfillment-service" })
      .endEvent("end", { name: "Order Complete" }),
  )
  .branch("invalid", (b) =>
    b.defaultFlow().endEvent("reject-end", { name: "Order Rejected" }),
  )
  .withAutoLayout()
  .build()

const xml = Bpmn.export(defs)

Sub-processes, boundary events and multi-instance

These are the constructs most often reached for and most often guessed at, so the exact shapes are worth stating. subProcess takes its body as a callback (second argument) and options third. A boundary event needs the id of the activity it attaches to; withBoundary supplies it from the preceding activity for you.

import { Bpmn } from "@bpmnkit/core"

const defs = Bpmn.createProcess("fulfilment")
  .name("Fulfilment")
  .startEvent("start")

  // Sub-process: id, content callback, then options.
  // multiInstance runs the body once per item in the collection.
  .subProcess(
    "provision",
    (sub) => {
      sub
        .startEvent("p-start")
        .serviceTask("provision-line", { name: "Provision Line", taskType: "provision" })
        .endEvent("p-end")
    },
    {
      name: "Provision Each Line",
      multiInstance: { collection: "=order.lines", elementVariable: "line", isSequential: false },
    },
  )

  // withBoundary attaches to the activity just added — no id repeated.
  .serviceTask("charge", { name: "Charge Payment", taskType: "payment" })
  .withBoundary("charge-failed", { errorCode: "PAYMENT_FAILED" }, (b) => {
    b.serviceTask("retry-charge", { name: "Retry Charge", taskType: "payment" }).endEvent("gave-up")
  })

  // Wait for whichever event arrives first.
  .eventBasedGateway("await-payment", { name: "Paid?" })
  .branch("paid", (b) =>
    b
      .intermediateCatchEvent("payment-confirmed", {
        messageName: "PaymentConfirmed",
        correlationKey: "=orderId",
      })
      .endEvent("done"),
  )
  .branch("timeout", (b) =>
    b.intermediateCatchEvent("payment-timeout", { timerDuration: "P14D" }).endEvent("written-off"),
  )
  .withAutoLayout()
  .build()

Attach a boundary event explicitly when it is not the preceding activity — both forms below are accepted, and a boundary event with no host throws rather than exporting BPMN that cannot be read back:

.boundaryEvent("timeout", { attachedTo: "charge", timerDuration: "PT30M" })
.boundaryEvent("timeout", "charge", { timerDuration: "PT30M" })

Event definitions are named options, not nested objects — timerDuration, timerDate, timerCycle, messageName (with correlationKey), errorCode, signalName, compensation. An option the builder does not know is dropped silently, so timer: { duration: "P7D" } produces a boundary event with no timer on it.

Parse and modify existing BPMN

import { Bpmn } from "@bpmnkit/core"

const defs = Bpmn.parse(xml)
const process = defs.processes[0]

// Access flow elements
for (const el of process.flowElements) {
  console.log(el.type, el.id, el.name)
}

// Serialize back to XML
const updated = Bpmn.export(defs)

Auto-layout a process

import { applyAutoLayout, Bpmn } from "@bpmnkit/core"

// Lays out every process and writes the diagram interchange back onto the model.
const laid = Bpmn.export(applyAutoLayout(Bpmn.parse(xml)))

Optimize a diagram

import { Bpmn, optimize } from "@bpmnkit/core"

const defs = Bpmn.parse(xml)
const report = optimize(defs)

console.log(`${report.summary.total} findings`)

for (const finding of report.findings) {
  console.log(`[${finding.severity}] ${finding.message}`)
  if (finding.applyFix) {
    const { description } = finding.applyFix(defs)
    console.log("Fixed:", description)
  }
}

Compact format for AI/LLM workflows

import { Bpmn, compactify, expand } from "@bpmnkit/core"

// Shrink for AI prompt
const defs = Bpmn.parse(xml)
const compact = compactify(defs)          // ~70% smaller JSON
const json = JSON.stringify(compact)      // send to LLM

// Restore full BPMN from AI response
const restored = expand(JSON.parse(json))
const outXml = Bpmn.export(restored)

Rendering while the model is still writing

A model emits a diagram one token at a time, and the outermost } — the one JSON.parse waits for — is the last character it sends. createCompactStream reads the elements out of the text as their own literals close, so there is something to draw long before the document is finished:

import { createCompactStream } from "@bpmnkit/core"

// `base` is the diagram being edited, so a frame shows the whole thing rather
// than the fragment the model is adding to it. Omit it to build from nothing.
const diagramStream = createCompactStream({ base: null })

function onModelChunk(chunk: string): void {
  const frame = diagramStream.push(chunk) // null until the frame changes
  if (frame) console.log("elements so far:", frame.processes[0]?.flowElements.length)
}

Frames are a guess at an unfinished document: push never throws, drops what it cannot place, and expects the caller to have an authoritative result coming.

API Reference

BPMN

| Export | Description | |--------|-------------| | Bpmn.parse(xml) | Parse BPMN XML → BpmnDefinitions | | Bpmn.export(defs) | Serialize BpmnDefinitions → XML | | Bpmn.createProcess(id) | Start a ProcessBuilder; set the name with .name(…) | | Bpmn.makeEmpty(processId?, name?) | Minimal BPMN XML with one start event | | Bpmn.SAMPLE_XML | 3-node sample diagram string |

Process builder

Every element method is (id, options) and returns the builder. The same methods exist inside .branch() and inside a sub-process body.

| Method | Description | |--------|-------------| | .name(name) / .versionTag(v) | Process-level metadata | | .startEvent(id?, options?) / .endEvent(id?, options?) | Events; options.name labels them | | .serviceTask(id, { taskType, ioMapping?, taskHeaders? }) | Zeebe job worker task | | .userTask(id, opts) / .businessRuleTask(id, { decisionRef }) / .scriptTask(id, opts) | Other task types | | .receiveTask(id, { message }) / .sendTask(id, opts) / .callActivity(id, opts) | Message and call activities | | .restConnector(id, { method, url, … }) | Camunda 8 HTTP connector task | | .exclusiveGateway(id, opts) / .parallelGateway / .inclusiveGateway / .eventBasedGateway | Gateways | | .branch(name, b => …) | One path off the preceding gateway | | b.condition(feel) / b.defaultFlow() | Mark a branch's condition, or make it the default | | .connectTo(id) | Flow to an existing or later element — merges and loops | | .subProcess(id, content, options?) | Embedded sub-process; content is a callback | | .transaction(id, content, options?) / .eventSubProcess / .adHocSubProcess | Other containers | | .withBoundary(id, options, handler) | Boundary event on the preceding activity, plus its path | | .boundaryEvent(id, options) | Boundary event naming its host in options.attachedTo | | .intermediateCatchEvent(id, opts) / .intermediateThrowEvent(id, opts) | Intermediate events | | .withAutoLayout() | Compute coordinates on build() — no x/y by hand | | .build() | Produce BpmnDefinitions; pass to Bpmn.export |

Event definitions are options on the element: timerDuration, timerDate, timerCycle, messageName + correlationKey, errorCode, signalName, compensation. Multi-instance is options.multiInstance = { collection, elementVariable, isSequential? }.

Semantics

| Export | Description | |--------|-------------| | semanticHash(defs) | SHA-256 of the model, excluding the diagram. Unchanged by layout | | projectSemantics(defs) | The canonical, presentation-free projection the hash covers | | diffSemantics(a, b) | What changed between two models, keyed by element id |

Typed code generation

| Export | Description | |--------|-------------| | generateProcessTypes(defs \| defs[], options?) | TypeScript source typing job types (variables, output, headers, errors), process ids, messages, signals, error and escalation codes. Deterministic | | extractProcessContract(defs \| defs[]) | The same contract as data — what casen gen types renders and --check-workers compares |

Editing

| Export | Description | |--------|-------------| | applyBpmnOperations(defs, ops) | Apply edit operations to the full model. Strict: unresolved ids throw | | reconcileCompact(defs, compact) | Apply a compact diagram as changes, keeping what compact cannot carry | | compactify(defs) | Read-only token-efficient view for LLM prompts. Lossy — not an edit path |

Writing files (@bpmnkit/core/node)

| Export | Description | |--------|-------------| | writeBpmn(defs, opts) | Serialize, read back, verify the model survived, then write atomically |

import { writeBpmn } from "@bpmnkit/core/node"

// Refuses rather than overwrite; pass force: true to replace.
const { semanticHash, changes } = await writeBpmn(defs, { output: "flow.bpmn" })

Connectors (@bpmnkit/core/connectors)

The Camunda 8 out-of-the-box connector catalog, behind its own subpath so its data is only bundled by code that imports it. The templates leave out icons, groups, tooltips and placeholders; @bpmnkit/connectors adds those back for a property panel.

| Export | Description | |--------|-------------| | listConnectors() / searchConnectors(query) | Summaries: task type, direction, required and optional inputs | | applyConnectorTemplate(id, values) | Builder options for a bundled template, plus problems | | applyTemplateToElement(defs, id, template, values) | Apply a template to an existing element | | validateElementTemplate(value) | Structural check of an element template | | findConnectorCards(query) / formatConnectorCard(card) | One card per operation, with only its inputs — for a model's prompt | | applyConnectorLines(defs, lines) | Apply the with lines a model wrote in the line format | | selectConnectors({ text, tasks }) | Pick the cards a model sees for each task of a diagram |

import { applyConnectorTemplate } from "@bpmnkit/core/connectors"

const { serviceTask, problems } = applyConnectorTemplate("io.camunda.connectors.HttpJson.v2", {
  url: "https://api.example.com/orders",
})

DMN

| Export | Description | |--------|-------------| | Dmn.parse(xml) | Parse DMN XML → DmnDefinitions | | Dmn.export(defs) | Serialize → XML | | Dmn.createDecisionTable(id, name?) | Start a DecisionTableBuilder | | Dmn.makeEmpty() | Minimal DMN with one empty decision table |

Form

| Export | Description | |--------|-------------| | Form.create() | Start a FormBuilder | | Form.parse(json) | Parse a form schema | | Form.export(schema) | Serialize → JSON string |

Layout & Optimization

| Export | Description | |--------|-------------| | layoutProcess(process) | Auto-layout all elements; returns LayoutResult | | optimize(defs) | Run all optimization rules; returns OptimizeReport | | analyzeCamundaCompat(defs, version?) | What the targeted Camunda 8 version cannot run or requires, as compat/* findings — Camunda Modeler's @camunda/linting rules | | compactify(defs) | Convert to compact CompactDiagram | | expand(compact) | Restore full BpmnDefinitions | | createCompactStream(opts?) | Read a diagram out of a model's token stream, frame by frame | | parseProcessText(text) | Read a process a model wrote in the line format (PROCESS_TEXT_GUIDE); never throws | | createProcessTextStream() | Read the line format while it streams, one finished line at a time | | writeProcessText(defs) | Write an existing diagram in the line format, for a model to read; returns the text and an alias → element id map | | parseProcessDelta(text) | Read a change script (PROCESS_DELTA_GUIDE): only what changes, by id; never throws | | generateId(prefix) | Generate a unique short ID |

Process documentation

| Export | Description | |--------|-------------| | renderDocumentationHtml(defs, options?) | Self-contained, print-ready HTML: diagram, contents, every element in flow order, linked DMN tables and forms. Print → Save as PDF | | renderDocumentationMarkdown(defs, options?) | The same content as Markdown, for wikis and Confluence | | renderDocumentationDocx(defs, options?) | The same content as a Word .docx (bytes), diagram as SVG | | buildProcessDocumentation(defs, options?) | The structured content, for a renderer of your own |

options takes decisions (DmnDefinitions[]), forms (FormDefinition[]), title, subtitle and diagram. Output is deterministic and all model text is escaped.


Related Packages

| Package | Description | |---------|-------------| | @bpmnkit/canvas | Zero-dependency SVG BPMN viewer | | @bpmnkit/editor | Full-featured interactive BPMN editor | | @bpmnkit/engine | Lightweight BPMN process simulator for tests and demos | | @bpmnkit/feel | FEEL expression language parser & evaluator | | @bpmnkit/plugins | 34 composable canvas plugins | | @bpmnkit/api | Camunda 8 REST API TypeScript client | | @bpmnkit/ascii | Render BPMN diagrams as Unicode ASCII art | | @bpmnkit/markdown | BPMN diagrams in Markdown — remark, markdown-it and README pre-rendering | | @bpmnkit/docspack | BPMN Kit docs as an offline docspack package for AI agents | | @bpmnkit/camunda-docspack | Camunda 8 docs as an offline docspack package for AI agents | | @bpmnkit/ui | Shared design tokens and UI components | | @bpmnkit/profiles | Shared auth, profile storage, and client factories for CLI & proxy | | @bpmnkit/operate | Monitoring & operations frontend for Camunda clusters | | @bpmnkit/connector-gen | Generate connector templates from OpenAPI specs | | @bpmnkit/connectors | Camunda 8 OOTB connector catalog and deterministic template application | | @bpmnkit/cli | Camunda 8 command-line interface (casen) | | @bpmnkit/proxy | Local AI bridge and Camunda API proxy server | | @bpmnkit/patterns | Domain process patterns for BPMNKit AIKit | | @bpmnkit/reebe-wasm | WebAssembly BPMN engine for browser simulation | | @bpmnkit/worker-client | Thin Zeebe REST client for standalone workers | | @bpmnkit/flow | Code-first durable flows that derive BPMN, job types and workers | | @bpmnkit/user-tasks | Embeddable user task widget for Camunda 8 | | @bpmnkit/cli-sdk | Plugin authoring SDK for the casen CLI | | @bpmnkit/create-casen-plugin | Scaffold a new casen CLI plugin in seconds | | @bpmnkit/casen-report | HTML reports from Camunda 8 incident and SLA data | | @bpmnkit/casen-worker-http | Example HTTP worker plugin — completes jobs with live JSONPlaceholder API data | | @bpmnkit/casen-worker-ai | AI task worker — classify, summarize, extract, and decide using Claude |

License

MIT © BPMN Kit — made by u11g