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

@oasys/oecs

v0.5.3

Published

Archetype-based Entity Component System

Readme

oecs

A full-featured, archetype-based Entity Component System for TypeScript.

@oasys/oecs is a complete ECS — not just storage-and-queries, but the whole toolkit you expect from a mature engine: observers, relations with wildcards, sparse storage, system sets and run conditions, entity enable/disable, templates, deterministic hashing with snapshot/restore, a typed host→ECS write seam, and an optional reactive UI bridge. It is pure TypeScript and zero-dependency by default — it runs over a plain resizable ArrayBuffer, so it needs no SharedArrayBuffer and no cross-origin isolation (COOP/COEP). An opt-in shared-memory profile swaps in a SharedArrayBuffer for worker offload or a WASM compute backend; both profiles share one core and agree, byte-for-byte, on stateHash.

  • Fast — struct-of-arrays column storage grouped by archetype; iteration is a tight loop over typed arrays with no per-entity object allocation.
  • Type-safe — component handles are callable defs with stable numeric ids at runtime and fully-typed schemas at compile time; misspelled fields are compile errors.
  • Deterministic — an opt-in mode gives a backing-agnostic stateHash plus snapshot/restore and command-log replay.
  • Complete — the feature surface below is the whole engine, not a starting point.

Installation

pnpm add @oasys/oecs        # npm / pnpm / yarn
# or
deno add jsr:@oasys/oecs    # JSR (Deno)
# or
npx jsr add @oasys/oecs     # JSR (npm-compatible)

Requires a runtime with resizable ArrayBuffer (the default heap profile grows in place): Node ≥ 20, Deno ≥ 1.38, Chrome 111+, Firefox 128+, Safari 16.4+.

Quick start

import { ECS, SCHEDULE } from "@oasys/oecs";

const ecs = new ECS(); // pure-TS heap profile — no SharedArrayBuffer needed

// Components — record syntax (per-field type) or array shorthand (defaults to "f64")
const Pos = ecs.registerComponent({ x: "f64", y: "f64" });
const Vel = ecs.registerComponent(["vx", "vy"] as const);

// A query is a live, cached view over matching archetypes — build it once, reuse it.
const movers = ecs.query(Pos, Vel);

// Systems declare the components they read/write (checked in dev builds).
const move = ecs.registerSystem({
  reads: [Vel],
  writes: [Pos], // a declared write implies read of the same component
  fn: (ctx, dt) => {
    movers.eachChunk((cols, count) => {
      const { x, y } = cols.mut(Pos);    // whole group; stamps Pos's change tick once
      const { vx, vy } = cols.read(Vel); // read-only group
      for (let i = 0; i < count; i++) {
        x[i] += vx[i] * dt;
        y[i] += vy[i] * dt;
      }
    });
  },
});

ecs.addSystems(SCHEDULE.UPDATE, move);
ecs.startup();

const e = ecs.spawn();
ecs.addComponent(e, Pos, { x: 0, y: 0 });
ecs.addComponent(e, Vel, { vx: 100, vy: 50 });

ecs.update(1 / 60);
ecs.getField(e, Pos, "x"); // ≈ 1.667

Features

Storage & data model

  • Archetype SoA storage over a backing-neutral ColumnStore — entities with the same component set share contiguous typed-array columns; cache-friendly loops, no per-entity object allocation.
  • Phantom-typed componentsregisterComponent({ x: "f64", y: "f64" }) returns a callable ComponentDef with a stable numeric .id at runtime and a fully-typed schema at compile time. Record syntax for per-field types, array shorthand for uniform f64, and registerTag() for data-free markers. Field types: f32 f64 i8 i16 i32 u8 u16 u32.
  • Two storage profiles, one core — pure-TS heap (ArrayBuffer) by default; opt-in SharedArrayBuffer for workers / WASM. Same code path, same stateHash, sized through a single memory surface (entity budget, byte cap, or pinned capacity).

Queries

  • Live, cached queriesecs.query(Pos, Vel) refined with .and() / .without() / .anyOf(); new matching archetypes are pushed in automatically.
  • Two iteration verbsforEach(arch => …) for read-only archetype iteration, eachChunk((cols, count) => …) for the mutable hot path (cols.mut / cols.read resolve a whole component's columns at once).
  • Change detection — per-(archetype, component) change ticks; query.changed(Pos) visits only archetypes written since the system's threshold tick.
  • Relation & hierarchy queries(R, *) / (*, T) wildcards, forEachRelatedTo, and query.hierarchy(rel, depth). Sparse queries via query.withSparse(...); disabled entities are skipped unless you opt in with query.includeDisabled().

Systems & scheduling

  • Declarative systems — plain functions in a SystemConfig declaring reads / writes, enforced by a dev-mode access checker (tree-shaken in production). Bare (ctx, dt) and (q, ctx, dt) + query-builder overloads exist for no-access glue; lifecycle hooks onAdded / onRemoved / dispose; exclusive: true for full-world setup/teardown.
  • Topological scheduler — seven phases (PRE_STARTUPSTARTUPPOST_STARTUP, FIXED_UPDATE, PRE_UPDATEUPDATEPOST_UPDATE); per-phase Kahn sort by before / after, with insertion order as a deterministic tiebreaker. Always-on cycle detection.
  • Fixed timestep — accumulator loop with configurable fixedTimestep and spiral-of-death protection.
  • System sets & run conditionssystemSet(...) + configureSet(...); runIfResourceEq, runEveryNTicks, runIfAnyMatch, and custom RunConditions.

Structural changes

  • Deferred inside systems, immediate on the hostctx.commands (a Bevy-Commands-style facade) buffers add / remove / despawn / enable / disable until the phase flush, so iterators stay valid (commands.spawn returns the id immediately; its component attaches are deferred). Every host-side mutation (ecs.addComponent / removeComponent / despawn / disable / enable) applies immediately.
  • Entity enable/disabledisable / enable / isDisabled; disabled rows sit in a partitioned tail and are skipped by default queries.
  • Templates & bundlesecs.template(Pos({ x, y }), …) blueprints consumed by spawn / spawnMany for zero-transition spawns; the same callable-bundle varargs drive spawnBundle(...) and addComponents(...).

Reactivity & relationships

  • Observersecs.observe(...) for onAdd / onRemove / onSet / onEnable / onDisable, structural or per-entity.
  • Relations(relation, target) pairs with ChildOf / IsA presets, exclusive / multi arities, bidirectional queries (targetOf / sourcesOf / ancestorsOf / rootOf / cascadeOf), and configurable on-delete cleanup (delete / clear / orphan). Stored sparsely — no archetype transition, no identity bit.
  • Sparse storageregisterSparseComponent / registerSparseTag, addSparse / removeSparse for churny or rare data that shouldn't cause archetype transitions.
  • Resources — typed global singletons via resourceKey<T>. Events — fire-and-forget SoA channels via eventKey<F> / signalKey, cleared at the end of each update.
  • Cached refsctx.ref(def, e) (mutable, bumps the change tick) / ctx.refRead(def, e) (read-only): resolve archetype + row + column once, then pos.x += vel.vx * dt.

Determinism, persistence & integration

  • Determinism (opt-in) — new ECS({ deterministic: true }), then ecs.snapshots.stateHash() (an FNV-1a-style 32-bit digest over live dense bytes, sparse stores, and multi-relation target sets), ecs.snapshots.capture() / ecs.snapshots.restore(...), plus sparse variants. Backing-agnostic: a heap world and a shared world with identical history produce identical hashes.
  • Host → ECS write seaminstallHostCommandSeam(ecs) applies typed HostCommands off-schedule via a blessed exclusive system, with record/replay (HostCommandRecorder, replayCommandLog) and a cross-thread ring transport.
  • Reactive UI seam (optional) — a zero-dep signals kernel (@oasys/oecs/reactive), an ECS→reactive bridge that publishes only dirty entities/columns (@oasys/oecs/reactive-sync), and a SolidJS adapter (@oasys/oecs/solid).
  • Editor layer — undo/redo + field handles over the write seam (@oasys/oecs/editor).
  • Frame tracingecs.setTrace(sink) + FrameTraceRecorder for a structured per-frame event stream (dev-gated).
  • Compute backend seamecs.attachBackend(...) to run a system body on a compiled backend (WASM, …) instead of its TS closure.

Reference

  • Typed errors — an ECSError taxonomy with a category enum and an isEcsError guard, all exported.
  • Reusable primitives (@oasys/oecs/primitives) — BitSet, SparseSet, SparseMap, GrowableTypedArray, BinaryHeap, and topologicalSort, usable standalone.

Entry points

The core is @oasys/oecs; everything else is opt-in and costs nothing until imported.

| Import | What it is | | --- | --- | | @oasys/oecs | the ECS — pure-TS heap profile by default (production build; dev guards stripped) | | @oasys/oecs/dev | the same ECS with the dev guards on — for a direct guards-on import; see Dev vs prod | | @oasys/oecs/shared | opt-in SharedArrayBuffer allocators for worker offload / a WASM backend (needs COOP/COEP) | | @oasys/oecs/reactive | zero-dependency reactive kernel (signal/computed/effect, reactive collections) | | @oasys/oecs/reactive-sync | ECS→reactive bridge — publishes only dirty entities/columns | | @oasys/oecs/editor | undo/redo + field-handle layer over the host-write seam | | @oasys/oecs/solid | SolidJS adapter (solid-js is an optional peer dependency) | | @oasys/oecs/primitives | the standalone data structures oecs is built on | | @oasys/oecs/internal | unstable internals (codecs, ABI constants, access checker) — no semver guarantees |

Dev vs prod

A compile-time __DEV__ flag gates every runtime check — bounds and liveness checks, duplicate-system detection, registration validation, and the system access checker (reads/writes). These are tree-shaken out of production builds, so treat "throws in dev" as a development tripwire, not a production guarantee. The scheduler's cycle detection and constructor-option validation (timestep, memory options, relation cardinality) are always active.

Production is the default on both channels; you opt into the guards. On npm, @oasys/oecs is the stripped production build — dev-mode bundlers (vite dev, webpack --mode development) pick the guards-on build automatically via the development export condition, or import @oasys/oecs/dev for it directly. On JSR/Deno (raw source, no bundler) the default is also production (__DEV__ = false); set globalThis.__DEV__ = true before the first import to turn the guards on while developing. Full details — including the browser/CDN and manual-override paths — are in the Development guards & production builds guide.

Documentation

Development

pnpm install
pnpm test              # vitest
pnpm bench             # vitest bench
pnpm build             # vite library build (multi-entry → dist/)
pnpm exec tsc --noEmit # type check

Acknowledgements

oecs stands on the shoulders of the ECS community. Special thanks to:

  • Bevy, Flecs, and bitECS — a constant source of inspiration; their designs shaped how oecs approaches archetypes, relations, scheduling, and change detection.
  • @clinuxrulz — for his amazing showcase and invaluable input on the ECS.

License

MIT