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

model-language

v1.1.0

Published

A typed, safe template language for AI-agent prompts. Variables, conditions, loops and filters that render to clean text against live data, type-checked at edit time — never crashes, never leaks syntax. One WebAssembly engine, eight languages.

Readme

model-language

A typed, safe template language for AI-agent prompts — write once, run in eight languages.

npm PyPI crates.io RubyGems NuGet Hex CI bundle size License: MIT powered by wexio.io

Live demo — model-language in a Tiptap editor

Type a template and watch it validate, autocomplete, and render in real time — right in the browser, powered by this package. See tiptap-model-language.

model-language lets non-technical users write prompts with variables, conditions, loops, and filters. The engine type-checks them against your data schema and renders them into clean prompts against live data — it never crashes and never leaks template syntax. One canonical TypeScript engine, compiled to WebAssembly, runs byte-for-byte identically in JavaScript, Python, Rust, Ruby, C#, C++, Go, and Elixir — verified by a shared conformance suite in CI.

Learn more: documentation · runnable examples · language hosts · contributing

Installation

You will need Node.js 18+ and npm (or another package manager).

npm install model-language

Using another language? See Other languages — the same engine ships to Python, Rust, Ruby, C#, Go, C++, and Elixir.

Rendering a prompt

Parse once, render many. render() is a pure function — it never throws, and the output has zero template syntax left in it.

import { parse, render, type FieldSchema } from 'model-language';

const schema: FieldSchema = [
  { path: 'user.name', type: 'string', nullable: true },
  { path: 'user.plan', type: 'enum', values: ['free', 'pro', 'team'] },
];

const source = `Hi {{user.name | default: "there"}}!
{{if user.plan == "pro"}}Priority support is on.{{/if}}`;

const { ast } = parse(source);
const { text } = render(ast, { user: { name: 'vasyl', plan: 'pro' } }, schema);
// text → "Hi vasyl!\nPriority support is on."

Validating in the editor

validate() type-checks a template against the schema and returns stable ML### diagnostics for live squiggles, quickfixes, and a worst-case token estimate — so authoring mistakes are caught at edit time, not in production.

import { validate } from 'model-language';

const { diagnostics, maxTokenEstimate } = validate(
  '{{if user.plan == "premium"}}...{{/if}}',
  [{ path: 'user.plan', type: 'enum', values: ['free', 'pro'] }],
);
// diagnostics → [{ code: 'ML202', message: "'premium' is not a valid value…", … }]

Examples: ML101 unknown-field (with a "did you mean" suggestion), ML102 unknown-filter, ML201 type-mismatch, ML210 missing-default on a nullable field, ML213 prompt-over-budget, ML214 raw-date-comparison, ML220 ==-on-multi-select (quickfix to contains). Full list: diagnostics catalog.

Directives

Inline directives embed machine-readable instructions directly in a prompt template. They are stripped from render().text and returned as structured data in render().directives, where the host (e.g. the backend) consumes them as runtime constraints — routing decisions, tool gates, assignment rules.

import { parse, render, validate } from 'model-language';
import type { DirectiveSpec, FieldSchema } from 'model-language';

const DIRECTIVES: DirectiveSpec[] = [
  { name: 'verify_before',      hasBody: false, arg: { kind: 'scalar',     type: 'enum',   values: ['payments', 'calendar'] } },
  { name: 'identity',           hasBody: false, arg: { kind: 'comparison', type: 'field',  comparison: { operators: ['=='], operandType: 'field' } } },
  { name: 'assignedTo',         hasBody: false, arg: { kind: 'list',       type: 'id' } },
  { name: 'assignedToFallback', hasBody: false, arg: { kind: 'list',       type: 'id' } },
  { name: 'assignedToRoles',    hasBody: false, arg: { kind: 'list',       type: 'enum',   values: ['OWNER', 'ADMIN', 'EDITOR', 'AGENT'] } },
  { name: 'assignedToMaxCount', hasBody: false, arg: { kind: 'scalar',     type: 'number' } },
];

const src = `Help with billing.
{{verify_before: payments}}
{{identity: contact.email == payment.email}}
Greet {{contact.first_name | default: "there"}}.`;

const { diagnostics } = validate(src, schema, { directives: DIRECTIVES });
// diagnostics → []  (zero errors)

const r = render(parse(src).ast, { contact: { first_name: 'Vasyl' } }, schema);
// r.text       → 'Help with billing.\n\nGreet Vasyl.'
// r.directives → [{ name: 'verify_before', params: { raw: 'payments' } },
//                 { name: 'identity',      params: { raw: 'contact.email == payment.email' } }]

A directive inside a {{if}} branch fires only when that branch renders — giving conditional gates for free (e.g. "require payment verification only on the pro plan"). Full reference: docs/directives/README.md.

The language

{{ user.name | default: "there" }}                variables + filter pipelines
{{ (order.total - order.discount) | round: 2 }}   arithmetic (+ - * /, parens)
{{ calculate(user.mrr / user.seats, 2) }}         calculate(expr, decimals?)

{{if x == "a" or y > 3}} … {{elseif …}} … {{else}} … {{/if}}   conditionals
{{if user.csm exists}} … {{/if}}                  null-safe existence checks
{{if user.tags contains "vip"}} … {{/if}}         array / multi-select membership

{{for item in order.items | where: "status", "==", "open" | limit: 3}}
  - {{ item.title }} (#{{ loop.index }}/{{ loop.count }})
{{else}}
  No open items.
{{/for}}

{{#priority high}} … {{/priority}}                runtime directives → RenderResult
{{include "signature"}}                           reusable snippets (cycle-guarded)
{{# a comment, never rendered #}}

Operators== != < > <= >=, and / or / not, in, contains, contains_any, contains_all, is_empty, exists, startsWith, endsWith, matches.

Filters (total — the wrong input type passes through, never throws):

  • text: upper lower trim capitalize truncate replace default
  • number: round floor ceil abs percent currency
  • array: count join first last limit pluck where sort sum max min
  • datetime: date (presets + token formats + timezone) time_ago days_ago days_until is_past is_future

Typesstring · number · boolean · datetime · array · enum · multiEnum · object · dynamic, plus null (present-but-empty) vs undefined (missing). Dates are compared through filters (| days_ago > 30), never raw.

Public API

| Function | Purpose | |---|---| | parse(source) | text → { ast, diagnostics } (with error recovery) | | serialize(ast) | AST → canonical text (deterministic whitespace) | | validate(source, schema, opts?) | typecheck → { ast, diagnostics, maxTokenEstimate } | | render(ast, snapshot, schema, opts?) | AST + data → { text, warnings, resolvedBranches, directives, tokenEstimate }never throws | | registerFilter(def) | add a host filter (e.g. locale-aware currency) | | registerRule(rule) | add a host lint rule (e.g. a private-field policy) |

Full type contract: src/types.ts.

Performance

Parse once (cold, cacheable), render many (hot). Rendering a pre-parsed AST is the per-message cost; caching it is the whole game.

| Template | render (hot) | |---|---| | typical prompt (tens–hundreds of lines) | 13–75 µs | | pathological 3,500-line, 500-rule prompt | ~1 ms |

Cost scales linearly with template size — logical rules never blow up (each condition is short-circuit-evaluated once). Numbers and methodology: bench/RESULTS.md.

Other languages

The engine is compiled once to a self-contained WebAssembly (WASI) module and called over a stable JSON contract, so hosts don't reimplement the language — the same .wasm produces byte-for-byte identical output everywhere, verified against the shared conformance suite in CI.

| Language | Install | Package | Host | |---|---|---|---| | JavaScript | npm i model-language | npm | this package | | Python | pip install model-language | PyPI | hosts/python | | Rust | cargo add model-language | crates.io | hosts/rust | | Ruby | gem install model-language | RubyGems | hosts/ruby | | C# | dotnet add package ModelLanguage | NuGet | hosts/csharp | | Elixir | {:model_language, "~> 1.0"} | Hex | hosts/elixir | | Go | go get …/hosts/go | — (git) | hosts/go | | C++ | vendor + the C API | — (vendor) | hosts/cpp |

Each package embeds the module — nothing else to install. See hosts/ for a guide to hosting in any other WASI language, and RELEASING.md for the publish flow.

Documentation

Complete reference in docs/:

Runnable templates with data + expected output live in examples/; language-neutral golden fixtures (the cross-host contract) in conformance/.

Development

pnpm install
pnpm test          # vitest — golden + unit + fuzz + round-trip (100% coverage gate)
pnpm bench         # vitest bench — parse / render / validate
pnpm lint          # Biome
pnpm typecheck     # tsc --noEmit
pnpm build         # tsup → dist (ESM + CJS + .d.ts)
pnpm wasm:build    # esbuild bundle → Javy → wasm/dist/model_language.wasm

The golden/conformance suite is the contract — breaking a case is a breaking change. Every doc example runs as a test (docs can't drift), fuzzing asserts the prime directive (render() never throws, parse() always recovers), and the round-trip invariant holds: parse(serialize(ast)) ≡ ast.

Contributing

Contributions welcome — see CONTRIBUTING.md. The engine is one canonical TypeScript implementation; the conformance suite is the contract, and coverage is a hard 100% gate. Security issues: SECURITY.md.

License

MIT © Wexio