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

tosijs

v1.7.9

Published

path-based state management for web apps

Readme

tosijs

tosijs.net | tosijs-ui | github | npm | cdn | react-tosijs | discord

tosijs is on NPM tosijs is about 15kB gzipped tosijs on jsdelivr

Better apps with less code

Less code to write, read, run, debug, and maintain — which, as a bonus in the age of AI assistants, also means fewer tokens to generate and reason about. tosijs gets there by leaning into the browser instead of re-implementing it:

  • Your knowledge of the browser is the API. HTML, the DOM, CSS, real events, standard <input>s with their native accessibility — not a framework-shaped replacement you have to learn (and re-learn every major version).
  • Learn how the browser works, not how some framework works. The skills are durable and transferable; they don't evaporate with the next migration guide.
  • O(1) DOM updates, even for big lists. A state change surgically updates exactly the bound nodes — no virtual DOM, no diffing, no re-render-the-world. And because virtual list bindings are built in, a 100,000-row list only ever renders (and updates) the handful of rows actually on screen.
  • No JSX, no transpilation, no build step required. Pure JS/TS in, native DOM nodes out — works in plain JavaScript or TypeScript.
  • No lock-in. State is a plain observable object graph, not a framework you marry; bind it to vanilla DOM, web-components, React, or Angular.
  • ~15kB gzipped, zero runtime dependencies.

On top of that you get the conveniences you'd actually want: most binding code eliminated, web-components you can build in pure JS more compactly than JSX, and CSS handled with variables and real Color math.

import { elements, tosi, touch, deleteListItem } from 'tosijs'

const todo = {
  list: [],
  addItem(reminder) {
    if (reminder.trim()) {
      todo.list.push({ id: Math.random(), reminder })
    }
  },
}

todo.addItem('wash the cat')
todo.addItem('buy milk')

const { readmeTodoDemo } = tosi({ readmeTodoDemo: todo })

const { h4, ul, label, input } = elements
preview.append(
  h4('To Do List'),
  ul(
    ...readmeTodoDemo.list.listBinding(
      ({ li, button }, item) =>
        li(
          item.reminder,
          button('Done!', {
            style: {
              marginLeft: 10,
            },
            onClick(event) {
              // deleteListItem resolves the row from any child node — pass the
              // button and it walks up to find its list item automatically
              deleteListItem(event.target)
            },
          })
        ),
      { idPath: 'id' }
    )
  ),
  label(
    'Reminder',
    input({
      placeholder: 'enter a reminder',
      onKeydown(event) {
        if (event.key === 'Enter') {
          event.preventDefault()
          readmeTodoDemo.addItem(event.target.value)
          event.target.value = ''
          touch(readmeTodoDemo)
        }
      },
    })
  )
)

In general, tosijs is able to accomplish the same or better compactness, expressiveness, and simplicity as you get with highly-refined React-centric toolchains, but without transpilation, domain-specific-languages, or other tricks that provide "convenience" at the cost of becoming locked-in to React, a specific state-management system (which permeates your business logic), and usually a specific UI framework.

tosijs lets you work with pure HTML and web-components as cleanly—more cleanly—and efficiently than React toolchains let you work with JSX.

export default function App() {
  return (
    <div className="App">
      <h1>Hello React</h1>
      <h2>Start editing to see some magic happen!</h2>
    </div>
  );
}

Becomes:

const { div, h1, h2 } = elements // exported from tosijs
export const App = () => div(
  { class: 'App' },
  h1('Hello tosijs'),
  h2('Start editing to see some magic happen!')
)

Except this reusable component outputs native DOM nodes. No transpilation, spooky magic at a distance, or virtual DOM required. And it all works just as well with web-components. This is what you get when you run App() in the console:

▼ <div class="App">
    <h1>Hello tosijs</h1>
    <h2>Start editing to see some magic happen!</h2>
  </div>

The ▼ is there to show that's DOM nodes, not HTML.

tosijs lets you lean into web-standards and native browser functionality while writing less code that's easier to run, debug, deploy, and maintain. Bind data direct to standard input elements—without having to fight their basic behavior—and now you're using native functionality with deep accessibility support as opposed to whatever the folks who wrote the library you're using have gotten around to implementing.

Aside: tosijs will also probably work perfectly well with Angular, Vue, et al, but I haven't bothered digging into it and don't want to deal with ngZone stuff unless someone is paying me.

If you want to build your own web-components versus use something off-the-rack like Shoelace, tosijs offers a Component base class that, along with its elements and css libraries allows you to implement component views in pure Javascript more compactly than with jsx (and without a virtual DOM).

import { Component, elements, css } from 'tosijs'

const { h1, slot } = elements
export class MyComponent extends Component {
  static shadowStyleSpec = css({
    h1: {
      color: 'blue'
    }
  })
  content = [ h1('hello world'), slot() ]
}

The difference is that web-components are drop-in replacements for standard HTML elements and interoperate happily with one-another and other libraries, load asynchronously, and are natively supported by all modern browsers.

An ecosystem with tosijs at its heart

tosijs is the observable core a whole family of tools is built on. Each is useful on its own; together they let you build almost anything without leaving web standards behind.

| Project | What it is | | --- | --- | | tosijs | This library — the path-based observant state core everything else is built on. | | tosijs-ui | Just enough extra web-components to build any interface — it complements the native elements that already work rather than replacing them. Also ships the documentation-site system that renders these very docs: literate programming with live, editable examples pulled straight from Markdown and source comments. | | tjs-lang | TypeScript that really transpiles in the browser (no server, no "just strip the types" fake) — and a better JavaScript: types that survive to runtime as contracts, safety boundaries, inline tests, and a gas-metered VM for genuinely safe eval (ship the logic, not a container to run it in). | | react-tosijs | Dramatically simplify state management in React apps, integrate React with other frameworks or web-components, or give yourself an off-ramp from React. | | ngx-tosijs | The same for Angular (signals, zoneless-first). | | tosijs-schema | Foundational, slightly bleeding-edge plumbing: a type-by-example JSON-Schema engine with arguably the strongest performance / flexibility / architecture story of any JSON-Schema implementation — and increasingly so as it grows computed predicates. Most people won't need to think about it; the rest of the stack leans on it. | | tosijs-product | Cinematic, scroll-linked product pages (Lottie, video, 3D, maps) authored in plain HTML. | | tosijs-3d | Declarative 3D / VR / XR as web components, built on Babylon.js (WIP). |

What tosijs does

Observe Object State

tosijs tracks the state of objects you assign to it using paths allowing economical and direct updates to application state.

import { tosi, observe } from 'tosijs'

const { app } = tosi({
  app: {
    prefs: {
      darkmode: false
    },
    docs: [
      {
        id: 1234,
        title: 'title',
        body: 'markdown goes here'
      }
    ]
  }
})

observe('app.prefs.darkmode', () => {
  document.body.classList.toggle('dark-mode', app.prefs.darkmode.value)
})

observe('app.docs', () => {
  // render docs
})

What does tosi do, and what is a BoxedProxy?

tosi registers your object into tosijs's central state tree and hands it back to you as a BoxedProxy.

A BoxedProxy is an ES Proxy wrapped around an object (which in Javascript means anything that has a constructor which in particular includes Arrays, class instances, functions and so on, but not "scalars" like numbers, strings, booleans, null, and undefined)

All you need to know about a BoxedProxy is that it's a Proxy wrapped around your original object that allows you to interact with the object normally, but which allows tosijs to observe changes made to the wrapped object and tell interested parties about the changes.

If you want the original object back you can use .value on any proxy to unwrap it.

No Tax, No Packaging

tosijs does not modify the stuff you hand over to it… it just wraps objects with a Proxy, and when you make changes through the returned proxy, tosijs notifies any interested observers.

import { tosi, observe } from 'tosijs'
const { foo } = tosi({
  foo: {
    bar: 17
  }
})

observe('foo.bar', (path) => {
  console.log('foo.bar was changed to', foo.bar.value)
})

foo.bar = 17        // does not trigger the observer
foo.bar = Math.PI   // triggers the observer

Paths are like JavaScript

A proxy behaves just like the JavaScript Object it wraps — tosijs doesn't copy or replace your object, so what you put in is what you get out (call .value to unwrap a scalar):

import { tosi } from 'tosijs'

const original = { bar: 'baz' }
const { foo } = tosi({ foo: original })

// read through the proxy; .value unwraps the scalar
foo.bar.value === 'baz'

// really, it's just the original object
foo.bar = 'lurman'
original.bar === 'lurman' // true

// seriously, it's just the original object
original.bar = 'luhrman'
foo.bar.value === 'luhrman' // true

…but better!

It's very common to deal with arrays of objects that have unique id values, so tosijs supports the idea of id-paths

import { tosi, boxed } from 'tosijs'

const { app } = tosi({
  app: {
    list: [
      {
        id: '1234abcd',
        text: 'hello world'
      },
      {
        id: '5678efgh',
        text: 'so long, redux'
      }
    ]
  }
})

console.log(app.list[0].text.value)                     // hello world
console.log(app.list['id=5678efgh'].text.value)         // so long, redux
console.log(boxed['app.list[id=1234abcd]'].text.value)  // hello world

Telling tosijs about changes using touch()

Sometimes you will modify an object behind tosijs's back (e.g. for efficiency). When you want to trigger updates, simply touch the path.

import { tosi, boxed, observe, touch } from 'tosijs'

const raw = { bar: 17 }
const { foo } = tosi({ foo: raw })
observe('foo.bar', (path) => console.log(path, '->', boxed[path].value))
foo.bar = -2                  // console will show: foo.bar -> -2

raw.bar = 100                 // nothing happens (changed behind tosijs's back)
touch('foo.bar')              // console will show: foo.bar -> 100

Every BoxedProxy also has a .touch() method:

app.user.name.touch()     // force update for a scalar
app.items[2].touch()      // force update for a list item

For list items with idPath, .touch() automatically synthesizes the equivalent id-path touch, so DOM bindings update correctly.

List Operations

Proxied arrays have listFind, listUpdate, and listRemove methods for common list operations:

// Find — returns proxied item (mutations trigger observers)
const item = app.items.listFind((item) => item.id, 'abc')

// Find by DOM element (in click handlers)
const item = app.items.listFind(clickedElement)

// Upsert — update in place or push if not found
app.items.listUpdate((item) => item.id, { id: 'abc', name: 'New' })

// Remove — returns true if found
app.items.listRemove((item) => item.id, 'abc')

listUpdate preserves object identity — it mutates the existing object property by property, so only changed properties fire observers and DOM elements are reused (no teardown/recreation).

CSS

tosijs includes utilities for working with css.

import { css, vars } from 'tosijs'

The vars proxy converts camelCase properties into css variable references:

vars.fooBar // emits 'var(--foo-bar)'
`calc(${vars.width} + 2 * ${vars.spacing})` // emits 'calc(var(--width) + 2 * var(--spacing))'

css() processes an object, rendering it as CSS:

css({
  '.container': {
    position: 'relative'
  }
}) // emits .container { position: relative; }

CSS variables can be declared using _ and __ prefixes in css() objects:

css({
  ':root': {
    _textFont: 'sans-serif',   // emits --text-font: sans-serif
    _color: '#111',            // emits --color: #111
  }
})

Color

tosijs includes a powerful Color class for manipulating colors.

import { Color } from 'tosijs'
const translucentBlue = new Color(0, 0, 255, 0.5) // r, g, b, a parameters
const postItBackground = Color.fromCss('#e7e79d')
const darkGrey = Color.fromHsl(0, 0, 0.2)

The color objects have computed properties for rendering the color in different ways, making adjustments, blending colors, and so forth.

Use invertLuminance() to generate dark-mode equivalents of color values.

Hot Reload

One of the nice things about working with the React toolchain is hot reloading. tosijs supports hot reloading (and not just in development!) via the hotReload() function:

import { tosi, hotReload } from 'tosijs'

tosi({
  app: {
    // ...your initial state
  }
})

hotReload()

hotReload stores serializable state managed by tosijs in localStorage and restores it (by overlay) on reload. Because any functions (for example) won't be persisted, simply call hotReload after initializing your app state and you're good to go.

hotReload accepts a test function (path => boolean) as a parameter. Only top-level properties in your state that pass the test will be persisted.

To completely reset the app, run localStorage.clear() in the console.

Development Notes

You'll need to install bun and then run bun install.

bun start                  # dev server with hot reload (https://localhost:8018)
bun test                   # run all tests
bun run build              # production build (runs tests, then bundles + docs)
bun run format             # lint and format (ESLint + Prettier)
bun pack                   # create local package tarball

History & credits

tosijs descends from b8rjs → xinjs → tosijs — see tosijs history for the full lineage and migration notes (coming from xinjs? old names still work). Developed with bun; logo animation by @anicoremotion.