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

@domstack/sync

v0.0.5

Published

A modern, minimal live-reload dev server

Readme

@domstack/sync

latest version Actions Status downloads neostandard javascript style Socket Badge

A modern, dependency-minimized live-reload dev server. A love letter to browser-sync: ESM-only, Fastify, native WebSocket, and a server-rendered HTMX UI panel.

Install

npm install @domstack/sync

CLI

# Serve current directory, watch CSS and HTML
domstack-sync --server . --files '**/*.css' '**/*.html'

# Serve current directory and watch the served root
domstack-sync --server . --watch

# Watch files without serving (snippet-injection mode)
domstack-sync --files '**/*.css'

# Short alias
dss --server . --files '**/*'

# Disable the UI panel
domstack-sync --server . --no-ui

# Write a starter ESM config file
domstack-sync init

# Trigger a reload from another terminal
domstack-sync reload --port 3000

CLI flags

| Flag | Default | Description | |---|---|---| | --server, -s | — | Directory to serve | | --files, -f | — | Glob patterns to watch (repeatable) | | --watch, -w | — | Watch server roots in addition to --files | | --ignore | — | Ignore patterns for file watchers (repeatable) | | --port | 3000 | Port to listen on | | --no-ui | — | Disable the UI panel | | --no-notify | — | Disable the notification overlay | | --no-ghost-mode | — | Disable scroll/click/location/form sync | | --cors | — | Enable CORS headers | | --log-level | info | Pino log level, for example silent, trace, debug, info, warn, error, or fatal | | --log-connections | — | Log browser connection events at info level | | --help, -h | — | Show help text | | --version, -v | — | Show version |

domstack-sync init writes domstack-sync.config.mjs using export default. On startup, the CLI loads the first config file it finds in this order: domstack-sync.config.mjs, .mts, .js, then .ts. TypeScript config files rely on Node's built-in type stripping.

API

import { createServer, parseOptions } from '@domstack/sync'

const sync = await createServer({
  server: './public',
  files: ['public/**/*.css', 'public/**/*.html'],
  port: 3000,
})

console.log(sync.url)    // http://localhost:3000
console.log(sync.uiUrl)  // http://localhost:3001

// Trigger a full reload
sync.reload()

// CSS-inject a specific file (falls back to full reload if not matched)
sync.reload(['styles/main.css'])

// Show a notification overlay in connected browsers
sync.notify('Build complete')

// Stream integration — pipe any { path } objects through to trigger reloads
someReadableStream.pipe(sync.stream())

// Listen for server-side events
sync.events.on('client:connect', (info) => console.log('connected', info.ua))
sync.events.on('client:disconnect', (id) => console.log('disconnected', id))
sync.events.on('file:change', (evt) => console.log('changed', evt.path))

// Pause / resume watcher-triggered reloads (e.g. during a build)
sync.pause()
sync.resume()

// Graceful shutdown
await sync.exit()

Logging

Standalone usage creates a pretty Pino logger automatically. When embedding @domstack/sync in another tool, pass a raw Pino logger and keep formatting ownership in the parent process:

import pino from 'pino'
import { createLogger, createServer, logAccessUrls } from '@domstack/sync'

// Standalone helper: returns a regular pino.Logger with domstack-sync's pretty formatter.
const standaloneLogger = createLogger('info')

// Embedded/library usage: pass your own raw pino.Logger.
const ownerLogger = pino({ level: 'info' })
const sync = await createServer({
  server: './public',
  files: ['public/**/*.css', 'public/**/*.html'],
  logger: ownerLogger.child({ component: 'sync', logPrefix: '[domstack-sync]' }),
})

logAccessUrls(standaloneLogger, {
  local: sync.url,
  ui: sync.uiUrl,
})

logAccessUrls(logger, urls) is exported for callers that want the same access-URL table with their own Pino logger.

Options

| Option | Type | Default | Description | |---|---|---|---| | port | number | 3000 | Port to listen on (falls back to OS-assigned if taken) | | server | string \| boolean \| string[] \| object | false | Directory or directories to serve statically | | files | string \| string[] \| object[] | [] | Glob patterns or chokidar watch objects to watch for changes | | ghostMode | boolean \| { scroll, clicks, location, forms } | all true | Sync interactions across connected browsers | | logger | pino.Logger | — | Raw Pino logger supplied by an embedding owner; when omitted, sync creates its own standalone pretty logger | | logLevel | Pino log level string | 'info' | Log verbosity used by the standalone logger when logger is omitted | | logConnections | boolean | false | Log browser connection events at info level | | ui | boolean \| { port: number } | true | UI panel — false disables, { port } pins the port | | notify | boolean | true | Show notification overlay in connected browsers | | cors | boolean | false | Add CORS headers to all responses | | injectChanges | boolean | true | Inject matching file changes instead of full reload | | injectFileTypes | string[] | ['css', 'png', 'jpg', 'jpeg', 'svg', 'gif', 'webp', 'map'] | Extensions eligible for file injection | | tagNames | Record<string, string> | built-in asset tag map | Element tag names used for non-CSS file injection | | codeSync | boolean | true | Broadcast reload and file-injection messages | | reloadDebounce | number | 500 | Milliseconds to debounce file-change reloads | | reloadDelay | number | 0 | Milliseconds to delay reload after a change | | reloadThrottle | number | 0 | Minimum milliseconds between reload broadcasts | | scrollThrottle | number | 0 | Minimum milliseconds between scroll sync messages | | scrollElements | string[] | [] | CSS selectors eligible for element scroll sync | | scrollElementMapping | string[] | [] | Selector mapping for scroll sync between different layouts | | scrollProportionally | boolean | true | Sync scroll position proportionally instead of raw pixels | | watch | boolean | false | Add server roots and routes to watched files | | ignore | string \| string[] | — | Ignore patterns merged into watcher options | | watchOptions | object | {} | Options passed through to chokidar | | watchEvents | string[] | ['change'] | Chokidar event names that trigger reloads | | snippet | boolean | true | Inject the browser client snippet into HTML responses | | snippetOptions | object | {} | Whitelist, blacklist, ignore paths, or custom injection rule | | rewriteRules | object[] | [] | HTML rewrite rules applied before snippet injection | | plugins | array | [] | BrowserSync-compatible plugin entries | | cwd | string | process.cwd() | Working directory for resolving server and files |

TypeScript

All types are exported:

import type { AccessUrls, BsInstance, BsOptions, BsOptionsInput, LoggerOptions, LoggerStreams } from '@domstack/sync'
import type { ServerToClientMessage, ClientToServerMessage } from '@domstack/sync'

How it works

  • Script injection — an IIFE <script> tag is injected immediately after the opening <body> tag (or appended if there is no <body>) in every HTML response. The injected script connects to the WebSocket server.
  • File injection — when a watched injectable file changes and injectChanges: true, matching DOM assets are cache-busted in place without a full page reload. CSS stylesheets, imported stylesheets, images, inline image URLs, and configured asset tags are supported. Falls back to a full reload if no matching asset is found.
  • Ghost mode — scroll position, clicks, location changes, and form input changes in one browser are relayed to all other connected browsers via the WebSocket server. Disable with ghostMode: false or --no-ghost-mode.
  • UI panel — a separate Fastify server on an auto-detected port shows connected browsers, file-change history, and server URLs. Disable with ui: false or --no-ui.
  • Port selection — the requested port is tried first. If it is already in use, the OS assigns a free ephemeral port automatically.

License

MIT