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

behavior-contracts

v0.11.19

Published

Language-agnostic runtime core for behavior contracts (dsl-contracts): validateEnvelope / evaluateExpression / renderTemplate / runPlan / canonical serialization. Single source of truth for the shared DSL runtime primitives.

Readme

behavior-contracts

behavior-contracts (bc) is a DSL for declaring what a piece of behavior means.

In most codebases, business logic, persistence, transactions, external calls, error handling and execution order all end up tangled inside one function in one language. Responsibility boundaries blur, the implementation and the documentation drift apart, and the order of execution gets baked into the code.

bc separates the two questions. You declare what the behavior means — which components compose into which result — as a contract. You do not write how it is orchestrated: execution order is derived from the data dependencies, not written by hand. The declaration is ordinary TypeScript, so it is simultaneously the specification and the running code.

import { behavior, type Int } from "behavior-contracts";

class UserQueries {
  @behavior
  static findByEmail(email: string): User[] {
    return Db.query(`SELECT id, email FROM users WHERE email = ?`, [email]);
  }
}

That method is the contract. @behavior is a marker: at runtime it does nothing — the method runs as plain TypeScript — and at build time it marks the entry point bc reads.

Because the meaning is implementation-independent, it can target any language

A declaration says what the behavior means, not how one language happens to implement it. That independence is what makes the rest possible: one declaration compiles to Go, Rust, Python and PHP — Go and Rust as genuinely native code running at hand-written-SDK-floor parity, Python and PHP as modules driven by a small shared runtime. Compilation is deterministic and canonically serialized, so the same declaration produces byte-identical results in every language.

Multi-language codegen is a consequence of the design, not the point of it.

flowchart TD
    TS["Declaration — ordinary TypeScript<br/>(the contract AND the running code)"]
    RUN["runs directly in TypeScript"]
    IR["bc-internal IR"]
    ML["Go · Rust · Python · PHP"]
    LEAF["leaf — the one hand-written<br/>I/O function per language"]

    TS --> RUN
    TS -->|"bc generate (build time)"| IR
    IR --> ML
    LEAF -. called by .-> RUN
    LEAF -. called by .-> ML

The IR is bc's own internal intermediate — never hand-written or hand-handled. The only code hand-written per language is one op-agnostic leaf transport per leaf (the actual I/O), and the library that owns the behaviors ships those.

The declaration style bc implements is Semantic Contract Programming (SCP) — "contract the meaning itself as components, and build larger meaning by combining them". The full paradigm is specified in docs/semantic-contract-programming.md; you do not need to read it to use bc.

This is the README bundled with the published npm package. It mirrors the monorepo root README; the repository also holds the specs, conformance vectors and examples it links to.

Who this is for

bc sits under a stack of three levels; the two audiences below are levels 2 and 3.

  1. behavior-contracts (bc) — this package: the declaration substrate and its native codegen.
  2. A library built on bc — a bc consumer (e.g. litedbmodel or graphddb), the library author. Its job is to express its own domain surface (e.g. litedbmodel's decorators) as bc declarations: its own decorators stay metadata-only collectors, and its lowering step produces the declaration-limited TypeScript that bc reads. It also ships the per-language leaf transports (Go / Rust / …). This is the audience that writes @behavior / @leaf methods and hand-writes each leaf transport once per language. It provides everything except the end user's model. → Section A.
  3. A user of such a library — the end user (the library's consumer, e.g. a litedbmodel user). They declare their model with the library's own surface and run it. In TypeScript they simply call the method — the markers are no-ops at runtime, so the declaration is just TypeScript that runs. For Go / Rust / Python / PHP it is bc generate (the bc CLI, run directly) linked to the library's shipped leaf transports, then a call to the generated entry point. They write no declarations for bc, no leaf transport, no IR — just the model and the run call. → Section B.

If you are building a library on top of bc, read Section A. If you are using a library that was built on bc, read Section B.

Features

  • Native typed-native codegen for Go and Rust. A covered behavior is emitted as straight-line native code — direct struct field access, concrete per-node calls, and a local error type — running at SDK-floor parity (~0–5% overhead vs. a hand-written SDK baseline) and ~2–3× faster than the shared interpreter.
  • Zero-runtime generated modules. The generated Go/Rust modules import no behavior-contracts runtime at all — a property the compiler verifies (a build gate rejects any runtime reference or boxing primitive that leaks into the output).
  • BC owns the wire type; one transport per leaf. BC generates the concrete wire type (WireValue/WireRow/WireList) and all of the strict de-box. The only hand-written native code is a single op-agnostic leaf transport that reads its inputs from a WireRow payload and returns a WireValue — no trait, no probe protocol, no per-role signature.
  • Fail-closed, never silently boxed. Shapes not yet natively coverable are rejected at generation time rather than falling back to a boxed/interpreted path labelled as native. An ambiguous or non-canonical type is rejected loudly at build time.
  • In TypeScript the declaration just runs. @behavior / @leaf are no-op markers at runtime, so a declared method is an ordinary static method: calling it executes the leaf implementations directly at native JS speed — no interpreter, no lowering call, no generated file, zero runtime deps.
  • Shared interpreter for Python and PHP. The same declaration emits an interpreter module that runs on the shared runtime with identical semantics (a TypeScript interpreter module is available too, when you want an emitted file rather than a direct call).
  • Deterministic, canonical serialization. Key-sorted, typed-value encoding with a canonical decimal float representation — the same behavior always serializes to the same bytes, which is what makes cross-language conformance verifiable.

Native de-box codegen is for Go and Rust. TypeScript needs no codegen at all — the declaration runs as-is; Python and PHP consume the compiled behavior through the shared interpreter.

What you can write in a declaration

A declaration body is ordinary TypeScript, but only the subset below carries meaning across languages. Everything in it is written as native TypeScript syntax — bc reads the syntax and derives the contract. Anything outside this subset is rejected loudly at build time (never silently reinterpreted).

Values and references

| You write | Meaning | |---|---| | email (a parameter) | an input port | | row.title | a field reference | | row?.title | an optional reference (missing is a value, not an error) | | `A#${id}` / "a" + id | string concatenation | | 1, "x", true, null, […], {…} | literals (Int / Float distinguish integer from float) |

Operators

| You write | Meaning | |---|---| | + - * / % -x | arithmetic (add sub mul div mod neg) | | === !== < <= > >= | comparison | | && \|\| ! | boolean logic (operands must be bool — no truthiness) | | ?? | default when null (coalesce) | | xs.length | length of an array |

Loose equality (== / !=) and truthy conditions are rejected — the meaning must be unambiguous in every target language.

Control flow

| You write | Meaning | |---|---| | cond ? a : b | choose a value, or choose which component runs | | if (cond) { … } | run a component only when the condition holds (otherwise it is skipped) | | xs.map(x => …) | run a component once per element |

Execution order is not written: bc derives it from the data dependencies, and independent work is scheduled concurrently.

Components (the units of meaning)

| You write | Meaning | |---|---| | @behavior static m(a: T): R | a declared behavior — arguments are input ports, the return type is the output | | @leaf static f(a: T): R | a leaf — the boundary where real I/O happens (one hand-written transport per language) | | Db.query(sql, params) | calling a leaf = one component in the graph | | const x: T = … | pins the result type of a node | | opt(x) | make a reference optional | | xs.map(f, { into, batched }) | map options: zip the result into each element / batch the calls | | refsConnection(…, { dedupeKey, drop, implicitSource }) | fan out over references, de-duplicating them |

@behavior and @leaf are markers: at runtime they do nothing, so the method is an ordinary static method that just runs. opt and refsConnection are the only two helpers with no native TypeScript equivalent.

Install

npm install behavior-contracts
  • ESM-only library; TypeScript type declarations are included.
  • Requires Node.js 22+.
  • No runtime dependencies. (typescript is a build-time-only optional peer, needed by the CLI to codegen from a .ts declaration module.)
  • If you run a generated TypeScript module, keep behavior-contracts as a dependency of the project that runs it — the generated module pulls what it needs from this package itself.

Section A — For library authors (building a library on bc)

You are writing the layer that maps your library's own DSL/decorators onto bc. Your decorators stay metadata-only collectors; your lowering step turns that metadata into declaration-limited TypeScript — the @behavior / @leaf methods below — and bc generate --from reads that source statically (it parses the AST; it never executes your module). You also ship one op-agnostic leaf transport per leaf, per language — so your users only declare a model and run it (Section B).

Because the lowering step produces source, one of your decorators can expand into several leaves or a composed behavior: the structure you emit is your choice, and bc reads whatever declaration comes out.

Declare in TypeScript

A behavior is a @behavior static method. Its parameters are the inputs (natural positional arguments — no bag object), and its return type is the output. A leaf is a @leaf static method whose signature is the contract bc reads: parameters become the leaf's input ports and the return type its output (a T[] return means the leaf yields many). Numeric precision uses the Int / Float branded aliases — plain TS number is rejected because it is ambiguous between a 64-bit int and a 64-bit float and bc does not infer.

import { behavior, leaf, type Int } from "behavior-contracts";

export interface User {
  id: Int;
  email: string;
}

// A leaf is a typed static method. Its SIGNATURE is what bc reads (input ports `sql` / `key`,
// output `User[]`). The BODY is the TypeScript run path — it is never touched by `bc generate`;
// the native languages call the leaf transport of the corresponding name instead.
export class Db {
  @leaf static Select(sql: string, key: string): User[] {
    return runYourQuery(sql, key);
  }
}

export class UserRepo {
  @behavior static findByEmail(email: string) {
    return Db.Select("SELECT id, email FROM users WHERE email = ?", email);
  }
}

That module is what your library ships (or generates from its own decorator surface); it is the --from <model.ts> --behavior <Class> the bc CLI compiles for Go / Rust / Python / PHP, and in TypeScript it is simply called (Section B).

Bodies are ordinary TypeScript, restricted to what carries a portable meaning: + / ?: / && / if / .map / ?. / template literals / comparisons all lower to the portable expression and control vocabulary. Two helpers exist because native syntax cannot express them: opt(x) (an optional reference — opt(x).field) and refsConnection(list, cb, opts) (a de-duplicated fan-out). Anything outside the portable vocabulary is rejected loudly at build time rather than silently mis-lowered.

Declare where the chain de-boxes

A driver boundary is generically typed, so a leaf may declare its result as the opaque WireValue[]. That result stays wire through the chain — nothing is re-boxed between hops — and the one point that declares a concrete type is where the de-box happens. There are two ways to declare it, and both are ordinary TypeScript that tsc --noEmit accepts:

// The op-agnostic transports: one per hop, no row type in the signature.
export class Db {
  @leaf static Exec(sql: string, params: WireValue[]): WireValue[] { … }
  @leaf static Materialize(rows: WireValue[]): User[] { … }   // declared concrete = the de-box point
}

export class UserRepo {
  // (1) the terminal LEAF declares the concrete type — no cast at the call site.
  @behavior static findAll(sql: string): User[] {
    const rows: WireValue[] = Db.Exec(sql, []);   // opaque hop: stays wire
    return Db.Materialize(rows);                  // the single de-box
  }

  // (2) the BINDING declares it — for one generic leaf that yields a different row type per call
  // site. Opaque → concrete is a narrowing, so TypeScript wants the assertion spelled out; the
  // ANNOTATION is what bc reads, and bc generates the runtime-checked de-box for it.
  @behavior static findAllInline(sql: string): User[] {
    const rows: User[] = Db.Exec(sql, []) as User[];
    return rows;
  }
}

A behavior's declared return type is its output contract: bc checks it against the type derived from the body's terminal node and rejects a disagreement loudly (OUTPUT_TYPE_MISMATCH) instead of silently emitting the derived one. So a cast alone never moves the contract — drop the annotation in (2) and the build fails rather than quietly generating Vec<WireValue>. A chain that ends opaque is rejected too (BAD_TYPE_NOTATION): opacity is a hop's property, and a caller needs a real type.

Ship the per-language leaf transports

For every language your library targets natively, you ship one op-agnostic leaf transport per leaf — this is the library's job, done once for all its users, so the end user writes none of it. A *-typed-native module is runtime-free and fully typed: BC owns and generates the concrete wire type and all of the strict de-box, so the transport implements nothing over it. It is a single function, with the spec-fixed name, that reads its inputs from the WireRow payload by field name, does the raw I/O, and builds a BC-owned WireValue:

  • Go — func <Sym>(payload wire.WireRow) (wire.WireValue, error)
  • Rust — fn <sym>(payload: WireRow) -> Result<WireValue, BehaviorError>

The same one transport serves a leaf across all its roles and cardinalities (base / GSI / limited / batched / fanout) — divergent port sets are just different payload fields, and there is no per-role signature. The generated native hot path stays fully typed; only the leaf boundary is wire (de-box once). The default symbol name is Leaf_<comp> (Go) / leaf_<comp> (Rust), and --leaf-transport <leaf>=<sym> overrides it per leaf.

package tx

import "example.com/app/wire"

// One op-agnostic function per leaf. Reads its inputs from the payload by name, does the raw
// I/O, and builds the BC-owned WireValue. The covered module owns all classification/de-box.
func Leaf_Select(payload wire.WireRow) (wire.WireValue, error) {
	email := payload.ProbeString("key").Got // the port the covered module bound
	_ = email
	return wire.WireListOf([]wire.WireValue{
		wire.WireRowOf([]wire.WireField{
			{Key: "id", Val: wire.WireInt(1)},
			{Key: "email", Val: wire.WireStr(email)},
		}),
	}), nil
}

opt fields are key-omittable (absent → None/null); an integer for a float slot is widened. A type the emitter cannot bake, or an output type inconsistent with its node types, is a generation-time error — never silently-broken code. See docs/strict-typing-and-debox.md for the full typing + de-box contract.

BC-owned wire types & the cross-package / cross-crate layout

BC generates the concrete wire types. By default they live in the covered module and the transport references them back (legal within a single Rust crate or Go package). For a layout where the transport lives in its own package/crate, --shared-types-out emits the BC-owned wire types (plus the batched/fanout batch ports structs; Rust also the shared BehaviorError) into a shared package/crate, and the covered module imports them via --shared-types-import — so the transport package returns them with no import cycle (covered → transport → shared). --leaf-transport-import names where the transport symbols are imported from. The library runs these emits once and ships the result:

# Go cross-package — emit ONLY the shared wire package (no --out ⇒ no covered module here):
bc generate --lang go-typed-native --from model.ts --behavior UserRepo \
  --shared-types-out wire/wire.go \
  --shared-types-import example.com/app/wire \
  --leaf-transport-import example.com/app/tx

# Rust cross-crate:
bc generate --lang rust-typed-native --from model.ts --behavior UserRepo \
  --shared-types-out wire/src/lib.rs \
  --shared-types-import bcwire \
  --leaf-transport-import bctx

Runnable end-to-end samples of the library side + a shared wire seam: examples/building-a-library-on-bc/ (custom decorators → declarations + Go/Rust transports) and examples/library-lowering-patterns/ (metadata → emitted declaration source, one relation declaration expanding into several leaf calls, and CQRS derived by the library), plus the standalone cross-layout samples examples/go-cross-package/ (Go, separate transport package), examples/rust-cross-crate/ (Rust, separate transport crate), and examples/rust-cross-module/ (Rust, intra-crate — no shared crate needed).

Public API reference

Everything you import from behavior-contracts when declaring behaviors — this is the whole list. Cross-language codegen is CLI-only: the bc binary is the sole public codegen entry, so no emitter and no compile function appears here.

| Export | Kind | Purpose | |---|---|---| | behavior | decorator | Marks a static method as one root behavior. No-op at runtime; the entry point bc reads at build time. | | leaf | decorator | Marks a static method as one leaf. Its signature is the contract (parameters → input ports, return type → output); its body is the TypeScript run path. | | opt | fn | Optional reference — opt(x).field lowers to an absence-tolerant read. Identity at runtime. | | refsConnection | fn | De-duplicated fan-out over a list (dedupeKey, optional implicitSource), returning { items, cursor }. | | AuthoringFailure | error | Thrown for an invalid declaration (e.g. a non-portable body, ambiguous number). | | Int / Float | type | Branded numeric aliases that disambiguate 64-bit int vs. float (plain number is rejected). | | AuthoringFailureCode | type | Discriminant codes for AuthoringFailure. |

Spec versions

This package tracks the IR/vector protocol versions independently from its library (semver) version:

| Spec | Version | |---|---| | expression | 2 | | template | 1 | | plan | 1 | | canonical | 2 | | behavior | 5 | | guard | 3 | | c2 | 1 | | provenance | 1 | | envelope | 1.1 |


Section B — For users of a library built on bc

You are using a library (e.g. litedbmodel or graphddb) that was built on bc. You never write leaf transports or IR — the library already did all of that (Section A). You declare a model with the library's surface, name your operations, then run it.

1. Declare your model with the library's surface

You describe your model with the library's own surface — its decorators, its schema DSL, whatever it exposes. bc does not dictate this; the library does. An endpoint is a @behavior static method whose body calls the library's leaf:

import { entity, field, behavior, Db } from "your-library";

@entity("users")
class User {
  @field.int id!: number;
  @field.string email!: string;
}

export class UserRepo {
  @behavior static findByEmail(email: string) {
    return Db.Select("SELECT id, email FROM users WHERE email = ?", email);
  }
}

2a. Run it in TypeScript — just call it

@behavior / @leaf are no-op markers at runtime, so the declaration is ordinary TypeScript: calling the method runs the library's leaf implementation directly, at native JS speed. There is no lowering call, no interpreter, no bc generate, and no generated file:

import { UserRepo } from "./model.ts"; // your model, declared with the library's decorators

const rows = UserRepo.findByEmail("[email protected]"); // a plain static-method call
// rows === [{ id: 1n, email: "[email protected]" }]    (int is a bigint in bc's value model)

Runnable sample: examples/using-a-bc-library/run-ts.mjs.

2b. Run it in Go / Rust / Python / PHP — bc generate

For the other languages the behavior-contracts package installs a bc binary you run yourself (not through the library). bc generate --from <model.ts> --behavior <Class> reads your TypeScript source with the TypeScript compiler — type-extracting each method's parameter and return types and lowering its body's AST — and compiles source → ir → native (Go / Rust) or a shared-runtime interpreter module (Python / PHP) in one pass. Your module is never executed. You wire it to the library's shipped leaf transports + BC-owned wire types with --leaf-transport-import / --shared-types-import — you write neither; the library ships them (Section A):

# Go — native, wired to the library's transport + wire packages:
bc generate --lang go-typed-native   --from model.ts --behavior UserRepo \
  --out go/behaviors/covered.go   --shared-types-import bcorm/wire   --leaf-transport-import bcorm/tx

# Rust — native:
bc generate --lang rust-typed-native --from model.ts --behavior UserRepo \
  --out rust/src/behaviors.rs     --shared-types-import bcorm_wire    --leaf-transport-import bcorm_tx

# Python / PHP — the shared-runtime interpreter module:
bc generate --lang python --from model.ts --behavior UserRepo --out behaviors_generated.py
bc generate --lang php    --from model.ts --behavior UserRepo --out BehaviorsGenerated.php

# Drift gate — non-zero exit if the committed --out differs from a fresh generation:
bc check --lang go-typed-native --from model.ts --behavior UserRepo --out go/behaviors/covered.go

Full flag reference: bc generate --help (also generated at docs/cli-reference.md). --in <ir-doc.json> is an internal/advanced input; the consumer surface is --from.

3. Call the generated entry point

The covered module exposes a typed entry function per behavior: you pass a typed input struct and get a typed output struct back. The transport + wire wiring is automatic — baked into the covered module at generate time by the import flags — so there is no runtime injection at the call site, and the covered module de-boxes the leaf's wire result once into the concrete typed output:

// go — the wiring to bcorm/tx + bcorm/wire is baked in; the call is just typed-in, typed-out.
rows, err := behaviors.RunNativeRawStruct_findByEmail(behaviors.In_findByEmail{Email: "[email protected]"})
// rust
let rows = run_native_raw_struct_findByEmail(InNRFindByEmail { email: "[email protected]".to_string() })?;

A --lang python / --lang php module runs on the shared runtime with the IR embedded. A --lang typescript interpreter module (an alternative to calling the declaration directly, when you want an emitted file) embeds the compiled IR and walks it at run time — it wires itself up, so there is nothing to import by hand.

The complete, runnable end-user example — one model, with the direct TypeScript run and the Go / Rust bc generate runs all printing the same row — is examples/using-a-bc-library/.


Documentation

Deeper design and specification documents live alongside the package:

License

MIT — see LICENSE.