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

metano-runtime

v2.3.0

Published

Runtime support library for Metano-generated TypeScript code

Downloads

960

Readme

metano-runtime

Runtime support library for code generated by the Metano C# → TypeScript transpiler. You generally don't install or use this directly — Metano adds it to your generated package.json#dependencies automatically, and the generated code imports from it as needed.

What's in here

HashCode — C#-compatible hash combining

Used by generated hashCode() methods on records/classes. Implements xxHash32 for fast, well-distributed hashes.

import { HashCode } from "metano-runtime";

const hc = new HashCode();
hc.add(someString);
hc.add(someNumber);
hc.add(someObject);
const hash = hc.toHashCode(); // number

HashSet<T> — value-equality set

A Set-like collection that uses equals()/hashCode() methods on objects when available, instead of reference equality. Mirrors C# HashSet<T> semantics.

import { HashSet } from "metano-runtime";

class Point {
  constructor(readonly x: number, readonly y: number) {}
  equals(other: Point) { return this.x === other.x && this.y === other.y; }
  hashCode() { const hc = new HashCode(); hc.add(this.x); hc.add(this.y); return hc.toHashCode(); }
}

const set = new HashSet<Point>();
set.add(new Point(1, 2));
set.has(new Point(1, 2)); // true — structural equality

Enumerable — LINQ for TypeScript

Lazy, composable sequences with the full LINQ API. Used by the transpiler to lower C# IEnumerable<T> chains.

import { Enumerable } from "metano-runtime";

const result = Enumerable.from([1, 2, 3, 4, 5])
  .where((x) => x > 1)
  .select((x) => x * 2)
  .toArray(); // [4, 6, 8, 10]

Operators supported:

  • Composition: where, select, selectMany, orderBy, orderByDescending, thenBy, thenByDescending, take, takeWhile, skip, skipWhile, distinct, distinctBy, groupBy, concat, reverse, zip, append, prepend, union, intersect, except
  • Terminals: toArray, toMap, toSet, first, firstOrDefault, last, lastOrDefault, single, singleOrDefault, any, all, count, sum, average, min, max, minBy, maxBy, contains, aggregate

linq() — descriptor-based pipeline + provider introspection

For IQueryable-style scenarios, the runtime also exposes a tagged-descriptor pipeline: each operator (where, select, orderBy, …) is a standalone factory that returns a { kind, …closures, apply, queryable? } descriptor. linq(source, ...stages) runs the chain by calling each stage's apply, returning a lazy Iterable<T> (or a scalar, for terminal-bearing chains).

import { getStages, linq, select, where } from "metano-runtime";

const query = linq(
  users,
  where((u) => u.age >= 18),
  select((u) => u.name),
);

for (const name of query) {
  console.log(name);
}

When the result is an object (the lazy Iterable<T> from a composition chain, or any object-typed terminal result like toArray), the runtime attaches the descriptor list at a non-enumerable symbol-keyed slot — LINQ_STAGES, registered via Symbol.for("metano-runtime/system/linq:stages"). An introspecting consumer (IQueryable provider, query planner, debugger) reads it via getStages:

import { getStages, linq, select, where } from "metano-runtime";

const query = linq(items, where((x) => x.active), select((x) => x.name));
const stages = getStages(query); // [WhereOp, SelectOp] | undefined

if (stages) {
  // walk `stages[i].queryable` (ExprTree) to translate into SQL / GraphQL / …
}

Contract:

  • The slot is non-enumerable, so JSON.stringify, { ...spread }, and Object.keys do not leak the chain into the wire shape.
  • getStages returns undefined for primitive results (scalar terminals like count / sum) and for values produced outside this runtime.
  • The slot is scoped via Symbol.for, so multiple module copies (duplicate npm resolutions) share the same key — getStages from any copy sees the stages attached by any other.
  • The closure side-channel (stage.apply) keeps working in parallel; a provider that cannot introspect a given stage may fall back to it.

ExprTree — queryable expression trees

When a LINQ chain opts into queryable mode (IQueryable<T> receiver or a [Queryable] parameter on the C# side), the transpiler emits a paired expression tree (QueryableMeta) alongside the closure. Providers walk the tree by kind to translate predicates into SQL, OData, GraphQL, etc.

Supported node kinds today: param, capture, literal, member, call, binary, unary, conditional, new (object-initializer projections such as select(u => new UserDto { Name = u.Name })), and lambda (nested arrows like where(u => u.orders.some(o => o.active))).

Param, capture, literal, and new nodes carry an optional qualified type field shaped { name, from? }name is the simple identifier the provider dispatches on, and from is the cross-package origin (the [EmitPackage] id) when the type lives in another package. Local and primitive types omit from. This disambiguates same-named types defined in different packages without forcing providers to parse module paths.

// where((p: Product) => p.unitPrice >= minPrice)
//
// `Product` lives in another package consumed via [EmitPackage("inventory")];
// `number` is a primitive (no origin), so `from` is omitted.
{
  kind: "binary",
  op: ">=",
  left: {
    kind: "member",
    target: { kind: "param", name: "p", type: { name: "Product", from: "inventory" } },
    member: "unitPrice",
  },
  right: { kind: "capture", name: "minPrice", type: { name: "number" } },
}

ExprTreeVisitor — generic walker for IQueryable provider authors

metano-runtime/system/linq ships a stable visitor surface so providers that translate QueryableMeta into another dialect (SQL, GraphQL, OData, HTTP query strings) do not need to reimplement parameter scoping, the recursive walker, captures lookup, or the primitive comparator. Three entry points cover the common cases:

  • evaluateExprTree(tree, scope) — pure JS evaluation. Default behavior for mock / in-memory providers; resolves the param, looks up captures, short-circuits boolean ops, mirrors JS semantics on arithmetic.
  • compileLambdaBody(meta, fallbackParamName?) — wraps the evaluator into a (item) => unknown predicate by closing over meta.captures. Pass fallbackParamName (defaults to "x") for literal-only bodies that never mention the param.
  • ExprTreeVisitor<R> — abstract base with one visitX method per node kind. Default implementations recurse, so subclasses override only the nodes they translate. Helpers readParamName and compareValues are exported for providers that bypass the visitor.
import {
  ExprTreeVisitor,
  type ExprBinary,
  type ExprLiteral,
  type ExprMember,
  type ExprParam,
} from "metano-runtime";

class SqlPredicate extends ExprTreeVisitor<string> {
  protected override visitParam(node: ExprParam): string {
    return node.name;
  }
  protected override visitLiteral(node: ExprLiteral): string {
    return JSON.stringify(node.value);
  }
  protected override visitMember(node: ExprMember): string {
    return `${this.visit(node.target)}.${node.member}`;
  }
  protected override visitBinary(node: ExprBinary): string {
    return `(${this.visit(node.left)} ${node.op} ${this.visit(node.right)})`;
  }
}

// new SqlPredicate().visit(meta.tree) → "(u.age >= 18)"

The array-backed sample (targets/js/sample-queryable-arrays) uses compileLambdaBody directly — its provider is now ~50 LOC of stage dispatch with all generic walker concerns delegated to the runtime.

UUID — branded UUID type

A branded primitive that System.Guid maps to. At runtime it's literally a string, so serialization and interop with ordinary string APIs work without ceremony, but the type system distinguishes "a validated UUID" from "any old string".

import { UUID } from "metano-runtime";

const id = UUID.newUuid();                // "550e8400-e29b-41d4-a716-446655440000"
const compact = UUID.newCompact();         // "550e8400e29b41d4a716446655440000"
const empty = UUID.empty;                  // "00000000-0000-0000-0000-000000000000"
const wrapped = UUID.create("abc-123");    // unchecked wrap, caller is trusted

if (UUID.isUuid(someValue)) {
  // type narrowed to UUID here
}

The Metano transpiler uses UUID.create() in JSON deserialization and UUID.newUuid() as the lowering for Guid.NewGuid().

Primitive type guards

Runtime type predicates used by generated overload dispatchers:

import {
  isInt32,
  isString,
  isBoolean,
  isFloat64,
  isBigInt,
  // etc.
} from "metano-runtime";

if (isInt32(value)) {
  // value is number && Number.isInteger(value) && within int32 range
}

JSON serialization (metano-runtime/system/json)

A declarative serialization framework that the transpiler uses to convert JsonSerializerContext subclasses. You don't construct these by hand — Metano generates them from your C# code.

import { JsonSerializer } from "metano-runtime/system/json";
import { JsonContext } from "#/json-context"; // generated

const todo = new TodoItem("Write docs", false, "high");
const json = JsonSerializer.serialize(todo, JsonContext.default.todoItem);
// → { title: "Write docs", completed: false, priority: "high" }

const parsed = JsonSerializer.deserialize(json, JsonContext.default.todoItem);
// → TodoItem { title: "Write docs", completed: false, priority: "high" }

Built-in type descriptors:

  • primitive — string, number, boolean passthrough
  • temporalTemporal.* types via toString() / parse()
  • decimaldecimal.js via toNumber() / new Decimal()
  • mapMap<K,V> ↔ plain object
  • arrayT[] with recursive element handling
  • hashSetHashSet<T> ↔ array
  • branded[Branded] / [InlineWrapper] types (passthrough + .create())
  • enum — string enum validation
  • numericEnum — numeric enum validation
  • nullable — wraps inner descriptor with null handling
  • ref — lazy reference to another TypeSpec

ImmutableCollection helpers

Pure functions that emulate System.Collections.Immutable.ImmutableList<T> / ImmutableArray<T> operations on plain arrays, so immutable collections serialize naturally without a custom wrapper:

import { ImmutableCollection } from "metano-runtime";

const a = [1, 2, 3];
const b = ImmutableCollection.add(a, 4); // [1, 2, 3, 4] — new array
const c = ImmutableCollection.removeAt(b, 0); // [2, 3, 4]

Installation

You normally do not install this manually. Metano adds it to your generated package.json#dependencies automatically. If you need to install it for some reason:

# Bun (recommended for Metano projects)
bun add metano-runtime

# npm
npm install metano-runtime

# pnpm
pnpm add metano-runtime

Peer dependencies

  • @js-temporal/polyfill — required if your transpiled code uses DateTime, DateOnly, TimeOnly, etc. Metano adds this to your dependencies automatically whenever it detects a Temporal mapping.

Development

This package lives in the Metano monorepo.

cd js/metano-runtime
bun install
bun run build     # tsgo -b .
bun test          # 245+ tests

Tests live under test/ mirroring the src/ directory structure. Imports use #/ subpath imports pointing at src/ (configured in package.json#imports).

License

MIT