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 🙏

© 2025 – Pkg Stats / Ryan Hefner

domx-dataos

v0.1.0

Published

DOM state observer for DATAOS — collect, apply, observe, and persist DOM state

Readme

domx

DOM state observer for DATAOS — collect, apply, observe, and persist DOM state.

< 1KB minified + gzipped. Zero dependencies.

What is domx?

domx implements the DATAOS principle: DOM as the single source of truth.

Instead of syncing JavaScript state with DOM state (and inevitably getting them out of sync), domx reads state directly from the DOM when needed. No Redux. No MobX. No useState. Just the DOM.

Security Considerations

⚠️ Important Security Notes

  • Avoid storing sensitive data: State cached to localStorage is accessible to any script on the same domain. Do not include passwords, tokens, or other sensitive information in manifests used with send() or HTMX caching.
  • Use static manifests: Define manifests in code, not dynamically from user input, to prevent selector injection attacks.
  • Safe custom functions: When using custom read/write functions, avoid unsafe DOM methods like innerHTML. Stick to the provided shortcuts for security.
  • Server-controlled attributes: Ensure dx-manifest attributes are rendered server-side, not set by user input, to prevent code injection.
// Define what state lives where in the DOM
const manifest = {
  searchQuery: { selector: '#search', read: 'value' },
  sortDir: { selector: '[data-sort]', read: 'attr:data-sort-dir' },
  filters: { selector: '.filter.active', read: 'data:filter' }
};

// Collect state from DOM
const state = domx.collect(manifest);
// → { searchQuery: "hello", sortDir: "asc", filters: ["status", "priority"] }

// Send to server
const response = await domx.send('/api/search', manifest);

Installation

npm install domx

Or via CDN:

<script src="https://unpkg.com/domx"></script>

Quick Start

1. Define a manifest

The manifest maps state labels to DOM selectors and read/write methods:

const manifest = {
  username: { selector: '#username', read: 'value', write: 'value' },
  rememberMe: { selector: '#remember', read: 'checked', write: 'checked' },
  theme: { selector: '[data-theme]', read: 'data:theme', write: 'data:theme' }
};

2. Collect state

const state = domx.collect(manifest);
// → { username: "alice", rememberMe: true, theme: "dark" }

3. Apply state

domx.apply(manifest, { username: "bob", theme: "light" });
// DOM is updated

4. Observe changes

const unsubscribe = domx.observe(manifest, (state) => {
  console.log('State changed:', state);
});

// Later: stop observing
unsubscribe();

API Reference

collect(manifest)

Reads current DOM state based on manifest. Returns object with labels as keys.

const state = domx.collect(manifest);

apply(manifest, state)

Writes state values to DOM. Only processes entries with write key.

domx.apply(manifest, { username: "alice" });

observe(manifest, callback)

Watches DOM for changes and calls callback with full state. Auto-detects watch mechanism from read type. Returns unsubscribe function.

const unsubscribe = domx.observe(manifest, (state) => {
  // Called on any relevant DOM change
});

on(callback)

Low-level subscription to raw MutationRecords. For framework integration (e.g., genX modules).

const unsubscribe = domx.on((mutations) => {
  // Process raw mutations
});

send(url, manifest, opts?)

Collects state, caches to localStorage, and sends via fetch.

⚠️ Security Warning: Cached state in localStorage is accessible to any script on the same domain. Avoid including sensitive data in manifests used with this function.

const response = await domx.send('/api/save', manifest, {
  headers: { 'X-Custom': 'value' }
});

replay()

Re-sends cached request (for page refresh recovery). Returns null if no valid cache.

// On page load
const response = await domx.replay();
if (response?.ok) {
  const html = await response.text();
  container.innerHTML = html;
}

clearCache()

Clears the cached request.

domx.clearCache();

Manifest Format

Read/Write Shortcuts

| Shortcut | Read | Write | |----------|------|-------| | "value" | el.value | el.value = x | | "checked" | el.checked | el.checked = x | | "text" | el.textContent | el.textContent = x | | "attr:name" | el.getAttribute('name') | el.setAttribute('name', x) | | "data:name" | el.dataset.name | el.dataset.name = x | | Function | Custom extractor | Custom writer |

Custom Functions

For complex cases, pass a function:

⚠️ Security Warning: Custom functions have full access to DOM elements. Avoid using unsafe methods like innerHTML to prevent XSS attacks.

const manifest = {
  combined: {
    selector: '#thing',
    read: (el) => `${el.dataset.foo}-${el.dataset.bar}`,
    write: (el, val) => {
      const [foo, bar] = val.split('-');
      el.dataset.foo = foo;
      el.dataset.bar = bar;
    }
  }
};

Multiple Elements

When selector matches multiple elements, collect() returns an array:

const manifest = {
  tags: { selector: '.tag', read: 'text' }
};

const state = domx.collect(manifest);
// → { tags: ["JavaScript", "TypeScript", "Python"] }

htmx Integration

domx includes an htmx extension for seamless integration:

<script src="domx.js"></script>
<script src="domx-htmx.js"></script>

<script>
const manifest = {
  searchQuery: { selector: '#search', read: 'value' },
  sortDir: { selector: '[data-sort]', read: 'attr:data-sort-dir' }
};
</script>

<body hx-ext="domx" dx-manifest="manifest" dx-cache="true">
  <input id="search" type="text">
  <button data-sort data-sort-dir="asc" hx-post="/api/search" hx-trigger="click">
    Search
  </button>
</body>

Features

  • Auto state collection: State is automatically added to request parameters
  • dx-cache: When true, caches state to localStorage and auto-replays on page refresh (⚠️ avoid sensitive data)
  • dx:change event: Fires when any observed state changes (use with hx-trigger="dx:change")

Attributes

| Attribute | Description | |-----------|-------------| | dx-manifest | Manifest object name or inline JSON | | dx-cache | Enable localStorage caching ("true"/"false") |

⚠️ Security Warning: dx-manifest attributes should be server-rendered, not user-settable, to prevent potential code injection through JSON parsing or window property access.

Page Refresh Handling

domx solves the "lost state on refresh" problem:

  1. Before request: send() caches state to localStorage
  2. On refresh: replay() re-sends the cached request
  3. Server responds: Fresh HTML with correct state
// On page load
document.addEventListener('DOMContentLoaded', async () => {
  const response = await domx.replay();
  if (response?.ok) {
    const html = await response.text();
    document.getElementById('container').innerHTML = html;
  }
});

Comparison with stateless (React)

| stateless (React) | domx (Vanilla) | |-------------------|----------------| | useDomState(manifest) | collect(manifest) | | useDomValue() setter | apply(manifest, state) | | Hook re-render on mutation | observe(manifest, callback) |

Both implement DATAOS principles. Use stateless for React apps, domx for vanilla JS or htmx apps.

Performance

  • Single MutationObserver: Regardless of manifest size
  • Batched callbacks: Uses requestAnimationFrame to batch rapid changes
  • Passive event listeners: For input/change events
  • < 1KB: Minified + gzipped

Related Projects

  • DATAOS - The philosophy behind domx
  • stateless - React implementation of DATAOS
  • genX - Declarative HTML formatting library (uses domx)
  • htmx - High power tools for HTML
  • multicardz - DATAOS in production

License

MIT © Adam Zachary Wasserman