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

@shamar/wire

v1.0.10

Published

Server-driven reactive components. Usable on its own or inside a Shamar admin panel.

Readme

@shamar/wire

Server-driven reactive components for Shamar admin panels. This is the runtime those panels are built on: the same role Livewire plays for Filament, and Conduit plays for Almasix Orbit.

@shamar/wire is the kernel and the browser script (client/wire.js, wireClientPath()). On Adonis, @shamar/adonis discovers app/wire, serves GET /wire.js and POST /wire, and renders @wire('counter'). node ace make:wire counter creates the class and the Edge view. A Shamar admin panel is one host. Any other Node server calls mount and update itself. The docs section Wire walks through both.

Existing Alpine UI stays. A piece of the panel moves onto Wire when its markup opts in with wire:*. live() form fields still use POST {panel}/{slug}/form-state.

Install

pnpm add @shamar/adonis installs the panel and @shamar/wire. The host re-exports WireKernel, WireComponent, and WireRequest. Install the kernel on its own only when you are not using the Adonis host:

pnpm add @shamar/wire

No peer dependencies. Node 20+.

New versions are published by GitHub Actions, not from a laptop token. npm trusted publishing for @shamar/wire accepts publishes from the publish.yml workflow in coolsam726/shamar. A GitHub release of the monorepo runs it.

What a request does

  1. Mount. The server creates the component, copies data into a signed snapshot, and returns one HTML island.
  2. Commit. The browser posts that snapshot plus property updates and method calls to the island’s wire:endpoint.
  3. Verify. The server rejects the body unless the snapshot checksum matches. Public state is whatever is on data after create(); keys the client invents are ignored.
  4. Run. Property hooks run, then method calls, in that order.
  5. Render. The server signs a new snapshot with the same island id and returns HTML. The browser morphs the current island so focus, cursor, and nested Alpine trees survive.
{
  "snapshot": { "id": "…", "name": "counter", "data": { "count": 0 }, "checksum": "…" },
  "updates": { "count": 1 },
  "calls": [{ "method": "increment", "params": [] }]
}

The response is the same shape the kernel returns from mount and update:

{
  "snapshot": { "id": "…", "name": "counter", "data": { "count": 2 }, "checksum": "…" },
  "html": "<div wire:id=\"…\" wire:snapshot=\"…\" wire:endpoint=\"/admin/wire\">…</div>",
  "effects": { "redirect": "/admin/orders/1" }
}

effects is present only when the component set one. A redirect is not stored in the snapshot.

Write a component

A component is a small object with public data and optional methods. Register it by name on a WireKernel.

import { WireKernel, type WireComponent } from '@shamar/wire'

class Counter implements WireComponent {
  data = { count: 0 }

  increment(): void {
    this.data.count = Number(this.data.count) + 1
  }

  updatedCount(): void {
    if (Number(this.data.count) < 0) this.data.count = 0
  }
}

const kernel = new WireKernel(process.env.APP_KEY!, {
  counter: {
    create: () => new Counter(),
    render: (component) => `<button wire:click="increment">${component.data.count}</button>`,
  },
})

const island = kernel.mount('counter', '/admin/wire')
// island.html is the first render
// island.snapshot is what the browser must post back

create() runs on every update. The kernel then copies snapshot data onto the new instance, applying only keys that already exist. Do not keep request-only state on the class and expect it to survive the next POST. Put it on data, or recompute it inside updated / the action method.

Property hooks

After a public property changes, the kernel calls, in order:

| Hook | When | |------|------| | updating(key, value) | Before data[key] is assigned | | updated(key) | After the assignment | | updatedTitle() | After title changes. The name is updated + the key with its first letter uppercased |

Hooks may be async. A throw fails the request; the browser leaves the island as it was.

Methods

wire:click, wire:submit, and wire:keydown become calls. The kernel invokes the matching function on the component with the parsed arguments.

These names are rejected: data, updated, call, constructor, hydrate, dehydrate, render, and any name that starts with _. An unknown name throws Unknown wire method.

Effects

Set this.effects during a method. The browser navigates on redirect and does not morph.

save() {
  this.effects = { redirect: '/admin/orders/4' }
}

HTML the kernel emits

mount and update wrap your render() result:

<div wire:id="a1b2…" wire:snapshot="{…signed json…}" wire:endpoint="/admin/wire">
  <!-- render() -->
</div>

wire:snapshot is the full snapshot, including checksum, escaped for an attribute. Treat it as opaque. Rendering user content inside the island is your job: use escapeHtml / escapeAttr from this package.

Browser runtime

@shamar/adonis serves assets/wire.js at {panel}/assets/wire.js and loads it from the admin shell. The script listens on document, so every island on the page shares one runtime.

It coexists with Alpine. On alpine:init it registers the $wire magic. Island morphs call Alpine.initTree on inserted nodes while the parent scope is still alive. Full-page wire:navigate destroys Alpine on the outgoing region, morphs without initializing orphans, then Alpine.initTrees the destination root so nested x-data (media library, forms, …) sees its scope.

Bindings

| Attribute | Behavior | |-----------|----------| | wire:model="title" | Queue the value. It is sent with the next action (wire:click, wire:submit, …). | | wire:model.live="title" | POST on input. | | wire:model.blur="title" | POST on blur. | | wire:model.debounce.250ms="query" | POST after 250ms of quiet. | | wire:click="save" | POST a call to save. | | wire:click="remove('draft')" | Call with parsed arguments. Quoted strings, numbers, true, false, and null are typed; anything else is a string. | | wire:submit="save" | On the <form>. Prevents the browser submit and calls save. | | wire:keydown.enter="save" | Call when Enter is pressed inside the element. | | wire:keydown="onKey" | Call for any key on that element. | | wire:loading | Hidden until a request for this island is in flight. | | wire:loading.remove | Visible until a request is in flight, then hidden. | | wire:ignore | Morph leaves this element and its descendants alone. | | wire:key="{{ id }}" | Stable identity for a child so morph reuses the DOM node. |

A later request for the same island supersedes an in-flight one. The stale response is discarded.

$wire in Alpine

Inside an island, after Alpine starts:

<button type="button" x-on:click="$wire.increment()">Add</button>
<button type="button" x-on:click="$wire.$set('title', 'Draft')">Rename</button>
<span x-text="$wire.count"></span>

| Access | Effect | |--------|--------| | $wire.count | Read the property from the current snapshot. | | $wire.count = 3 | POST { updates: { count: 3 } }. | | $wire.increment(1) | POST a call. | | $wire.$set('count', 3) | Same as a property write. | | $wire.$call('increment', 1) | Same as a method call. |

Signing

Snapshots are HMAC-SHA256 over id, name, and a canonical JSON form of data (object keys sorted). signSnapshot and verifySnapshot use timingSafeEqual. newComponentId() is 8 random bytes, hex-encoded.

Pass a stable secret into WireKernel. The checksum covers public data only, so a client cannot add properties, rename the component, or change the island id without failing verification.

The panel’s global search uses a process-lifetime secret (randomBytes at module load). A restart invalidates islands that were already on the page; the next full navigation mounts a fresh one.

How @shamar/adonis hosts it

| Piece | Where | |-------|--------| | POST {panel}/wire | AdminController.wire. Requires an authenticated panel session and returns JSON. | | GET {panel}/assets/wire.js | Browser runtime. | | Global search | First island. Rendered in the top bar from mountGlobalSearch. Updates go through updateGlobalSearch. | | Re-exports | WireKernel, WireComponent, WireRequest from @shamar/adonis. |

Global search walks resources that are visible in navigation and have searchable fields, skips any the current user cannot view, and returns up to eight record links. One resource throwing does not clear the other hits.

To add another island, register a component the same way global-search is registered and render kernel.mount(name, endpoint).html into the Edge view. Point endpoint at the panel’s /wire route (or your own route that calls kernel.update).

Helpers

| Export | Use | |--------|-----| | WireKernel | mount(name, endpoint) and update(request, endpoint). | | escapeHtml / escapeAttr | Text and attribute escaping inside render. | | signSnapshot / verifySnapshot | Checksums, if you store or forward snapshots yourself. | | invokeMethod | Call a public method with the same blocked-name rules as the kernel. | | applyUpdates | Copy a patch onto data for keys that already exist. Does not run hooks; WireKernel.update runs hooks itself. | | newComponentId | New island id. |

Tests

pnpm --filter @shamar/wire test

The suite covers a signed mount, a method call, a rejected tampered snapshot, property updated hooks, and a redirect effect that stays out of the snapshot.