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

talkdom

v0.3.0

Published

Smalltalk-inspired message passing for the DOM

Readme

talkDOM

talkdom.org

Smalltalk inspired message passing for the DOM. Declarative HTTP interactions via HTML attributes. No build step, no dependencies. As a big admirer of htmx, it was a major muse when starting this project. ALL HAIL THE HORSEY!

How it works

Receivers are named DOM elements. Senders dispatch keyword messages to receivers. which is currently in use @ eringen.com

<div receiver="content"></div>
<button sender="content get: /partial apply: inner">Load</button>

The sender attribute is parsed as a Smalltalk keyword message:

content get: /partial apply: inner
^^^^^^^                             receiver name
        ^^^^                        keyword 1
             ^^^^^^^^               arg 1
                      ^^^^^^        keyword 2
                             ^^^^^  arg 2

selector: "get:apply:"
args:     ["/partial", "inner"]

Features

  • get:, post:, put:, delete: selectors (return response for piping)
  • get:apply:, post:apply:, put:apply:, delete:apply: shorthand selectors
  • apply: consumes piped content
  • Apply operations: inner, text, append, outer
  • Pipes (|) chain return values between messages
  • Independent messages (;) fire separately
  • An element can be both sender and receiver
  • Receivers declare allowed operations via accepts
  • Polling with poll: keyword
  • Persistent state via persist attribute
  • URL persistence via push-url attribute
  • Server-triggered messages via X-TalkDOM-Trigger response header
  • Lifecycle events (talkdom:done, talkdom:error) on receiver elements
  • Programmatic API via talkDOM.send (returns a promise)
  • Extensible methods via talkDOM.methods
  • Configurable max pollers via talkDOM.maxPollers

Usage

<!-- jsDelivr -->
<script src="https://cdn.jsdelivr.net/npm/talkdom/dist/talkdom.min.js"></script>

<!-- unpkg -->
<script src="https://unpkg.com/talkdom/dist/talkdom.min.js"></script>

<!-- local -->
<script src="index.js"></script>

Multiple targets

A sender can address multiple receivers with ;:

<button sender="content get: /page apply: inner; log get: /page apply: text">Load</button>

Multiple elements can share the same receiver name. All matching elements receive the message:

<div receiver="alert" class="top-banner"></div>
<div receiver="alert" class="bottom-banner"></div>
<button sender="alert get: /notice apply: inner">Notify both</button>

Pipes

| chains the return value of one message into the next as the first argument.

<!-- fetch then apply -->
<button sender="content get: /partial | content apply: inner">Load</button>

<!-- pipe to a different receiver -->
<button sender="content get: /partial | sidebar apply: append">Load to sidebar</button>

Accepts

Receivers declare what operations they allow.

<div receiver="content" accepts="inner text"></div>

Polling

Receivers poll by adding poll: as the last keyword with an interval (s or ms) as its argument. The method keywords before poll: run on each tick.

<div receiver="feed get:apply: /updates inner poll: 10s"></div>

Polling stops automatically when the element is removed from the DOM. A maximum of 64 concurrent pollers is enforced by default. Adjust via:

talkDOM.maxPollers = 128;

Persist

Receivers with persist save their content to localStorage after each apply and restore it on page load.

<div receiver="sidebar" persist></div>

Push URL

Senders with push-url update the browser URL via history.pushState. The message replays on back/forward navigation.

<button sender="content get: /about apply: inner" push-url="/about">About</button>

If push-url has no value, the first message's first arg is used as the URL.

Server trigger

The server can trigger client-side messages by setting the X-TalkDOM-Trigger response header. The value uses the same message syntax.

X-TalkDOM-Trigger: toast apply: Saved inner

Multiple triggers separated by ;:

X-TalkDOM-Trigger: toast apply: Saved inner; counter get: /count apply: text

Works with pipes, extended methods, and everything else — it dispatches through the same path as sender clicks.

For CORS, expose the header: Access-Control-Expose-Headers: X-TalkDOM-Trigger.

Request headers

Every fetch sends:

| Header | Value | |---|---| | X-TalkDOM-Request | "true" | | X-TalkDOM-Current-URL | location.href | | X-TalkDOM-Receiver | receiver name (if element has one) | | X-CSRF-Token | from <meta name="csrf-token"> (non-GET only) |

Self-replacing elements

<button receiver="btn" sender="btn get: /next-step.html apply: outer">Click me</button>

Lifecycle events

Every operation dispatches a CustomEvent on the receiver element after completion. Events bubble, so you can listen at any ancestor or document.

| Event | When | Detail | |---|---|---| | talkdom:done | Method completed successfully | { receiver, selector, args } | | talkdom:error | Method rejected (HTTP error, network failure, confirm cancel) | { receiver, selector, args, error } |

// per-element
document.getElementById("content").addEventListener("talkdom:done", function (e) {
  console.log(e.detail.selector, "finished");
});

// global
document.addEventListener("talkdom:error", function (e) {
  alert("Failed: " + e.detail.error);
});

For apply: outer, the event fires on the replacement element (looked up by receiver name) so it still bubbles.

Programmatic API

talkDOM.send accepts the same message syntax as the sender attribute and returns a promise.

// single operation
talkDOM.send("#content get:apply: /api/data inner").then(function () {
  console.log("done");
});

// pipes
await talkDOM.send("#content get: /api/data | #output apply: inner");

// parallel chains
await talkDOM.send("#a get:apply: /x inner ; #b get:apply: /y inner");

// errors propagate
talkDOM.send("#content get:apply: /bad-url inner").catch(function (err) {
  console.error("failed", err);
});

Extending

talkDOM.methods["toggle:"] = function (el, cls) {
  el.classList.toggle(cls);
};
talkDOM.methods["show:"] = function (el, message) {
  el.textContent = message;
  el.style.display = "block";
};

Security

talkDOM does not sanitize HTML. Content from get:apply:, post:apply:, server triggers, and piped apply: is inserted via innerHTML / insertAdjacentHTML / outerHTML as-is. You are responsible for ensuring that server responses do not contain untrusted markup.

The persist attribute stores receiver content in localStorage in plain text. Do not use it for sensitive data.

CSRF tokens are read from <meta name="csrf-token"> and sent automatically on non-GET requests. Make sure this tag is present if your server requires CSRF protection.

Browser compatibility

talkDOM works in all modern browsers. No polyfills needed.

| Browser | Minimum version | |---------|-----------------| | Chrome | 51+ | | Firefox | 49+ | | Safari | 10+ | | Edge | 79+ (Chromium) |

IE is not supported.

Performance

Receiver lookups are cached and invalidated automatically via MutationObserver. Repeated dispatches to the same receiver name within a stable DOM hit the cache.

Polling is capped at 64 concurrent pollers by default (configurable via talkDOM.maxPollers). Pollers clean up automatically when their element is removed from the DOM. Method lookups are cached at poll setup time.

The CSRF meta tag element is cached after the first lookup and only re-queried if removed from the DOM.

Whitespace regex patterns are precompiled and shared across the library. Internal helpers like receiverName and resolveTarget avoid unnecessary allocations.

For most pages, talkDOM adds negligible overhead. On pages with thousands of receivers, keep in mind that querySelectorAll runs once per unique receiver name per DOM mutation cycle.

License

MIT. See LICENSE.