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

@rozek/sds-sidecar

v0.0.13

Published

backend-agnostic sidecar library for shareable-data-store — persistent sync + WebHook notifications

Readme

@rozek/sds-sidecar

The backend-agnostic core library for the SDS sidecar daemon family. It implements all CLI argument parsing, SQLite persistence, sync-engine wiring, exponential-backoff reconnect, and HTTP webhook dispatch — behind a single pluggable SDS_StoreFactory interface.

This package is not a standalone CLI. To run a sidecar daemon, install the backend-specific wrapper for your CRDT backend:

| backend | package | | --- | --- | | json-joy | @rozek/sds-sidecar-jj | | Loro | @rozek/sds-sidecar-loro | | Y.js | @rozek/sds-sidecar-yjs |

Use @rozek/sds-sidecar directly only when implementing support for a custom CRDT backend.


Prerequisites

  • Node.js 22.5 or later
  • a running SDS WebSocket relay server reachable at a ws:// or wss:// URL
  • a valid JWT with at minimum read scope and an aud claim matching the target store ID
  • all clients connected to the same relay must use the same CRDT backend. Patch and snapshot bytes are backend-specific binary formats; mixing backends causes silent data corruption

Installation

npm install @rozek/sds-sidecar

API

SDS_StoreFactory

The single extension point. Implement this interface to connect any CRDT backend:

export interface SDS_StoreFactory {
  fromScratch (): SDS_DataStore        // create a new, empty store
  fromBinary  (Data: Uint8Array): SDS_DataStore  // restore a store from a snapshot
}

SDS_DataStore is the abstract base class from @rozek/sds-core. The concrete implementation is provided by the backend package (e.g. @rozek/sds-core-jj).

runSidecar

export async function runSidecar (
  Factory:     SDS_StoreFactory,
  CommandName: string = 'sds-sidecar',
): Promise<void>

Parses process.argv, opens the local SQLite database, loads or creates the store via Factory, starts the sync engine, sets up webhook dispatch, and installs SIGINT / SIGTERM handlers. CommandName is displayed in --help output and log messages; pass the name of the backend-specific binary.

Implementing a custom backend

import { SDS_DataStore }                      from '@rozek/my-sds-backend'
import { runSidecar }                         from '@rozek/sds-sidecar'
import type { SDS_StoreFactory }              from '@rozek/sds-sidecar'

const Factory: SDS_StoreFactory = {
  fromScratch: ()     => SDS_DataStore.fromScratch(),
  fromBinary:  (Data) => SDS_DataStore.fromBinary(Data),
}

if (process.argv[1]?.endsWith('sds-sidecar-custom.js')) {
  runSidecar(Factory, 'sds-sidecar-custom').catch((Signal) => {
    process.stderr.write(`sds-sidecar-custom: fatal: ${(Signal as Error).message}\n`)
    process.exit(1)
  })
}

CLI reference

The following options, environment variables, trigger syntax, and webhook payload format apply to all backend-specific wrappers. Refer to the individual backend package README for usage examples.

Synopsis

<backend-cli> <ws-url> <store-id> [options]

Both positional arguments may also be supplied through environment variables or a JSON config file; the command-line values always take precedence.


Options

Identity

| Option | Description | | --- | --- | | --token <jwt> | JWT for the WebSocket server (env: SDS_TOKEN) | | --config <file> | path to a JSON config file |

Persistence

| Option | Description | | --- | --- | | --persistence-dir <path> | directory for the local SQLite database (env: SDS_PERSISTENCE_DIR, default: ~/.sds) |

Inline webhook

| Option | Description | | --- | --- | | --webhook-url <url> | webhook endpoint URL | | --webhook-token <token> | bearer token sent with all outgoing webhook calls (env: SDS_WEBHOOK_TOKEN) | | --topic <string> | opaque string echoed in the Topic field of every payload from this webhook | | --watch <uuid> | restrict notifications to entries physically nested inside this subtree (identified by UUID) | | --depth <n> | maximum watch depth (default: unlimited) | | --on <trigger> | trigger condition — repeatable; at least one required when --webhook-url is set |

Note: --watch uses the UUID of the subtree root. Only entries that are physically nested inside that subtree are observed. Entries that are merely linked to from within the subtree are not automatically included. If the UUID does not exist in the store at the time a change occurs, the webhook is silently suppressed for that change.

Auth-error webhook

| Option | Description | | --- | --- | | --on-auth-error <url> | webhook URL to notify when the server rejects the token (uses --webhook-token too) |

Logging

| Option | Description | | --- | --- | | --verbose | log incoming patches and store changes (env: SDS_VERBOSE=1) |

Reconnect tuning

| Option | Default | Description | | --- | --- | --- | | --reconnect-initial <ms> | 1000 | initial reconnect delay in milliseconds | | --reconnect-max <ms> | 60000 | maximum reconnect delay in milliseconds | | --reconnect-jitter <f> | 0.1 | jitter fraction 0–1 applied to each delay |


Environment Variables

| Variable | Description | | --- | --- | | SDS_SERVER_URL | WebSocket base URL — must start with ws:// or wss:// (overridden by positional <ws-url>) | | SDS_STORE_ID | store identifier (overridden by positional <store-id>) | | SDS_TOKEN | JWT for the WebSocket server | | SDS_WEBHOOK_TOKEN | bearer token for all outgoing webhook HTTP calls | | SDS_PERSISTENCE_DIR | directory for the local SQLite database | | SDS_ON_AUTH_ERROR | webhook URL to notify when the server rejects the token | | SDS_VERBOSE | set to 1 to log incoming patches and store changes |


JSON Config File

When --config <file> is given, options are read from a JSON file. CLI options and environment variables take precedence over file values.

{
  "ServerURL":     "wss://relay.example.com",
  "StoreId":       "my-store",
  "Token":         "<jwt>",
  "PersistenceDir": "/var/lib/sds",
  "WebHookToken":  "<bearer-token>",
  "onAuthError":   "https://admin.example.com/auth-error",
  "Verbose":       true,

  "reconnect": {
    "initialDelay": 1000,
    "maxDelay":     60000,
    "Jitter":       0.1
  },

  "WebHooks": [
    {
      "URL":      "https://hooks.example.com/store-changed",
      "Topic":    "my-topic",
      "Watch":    "<entry-uuid>",   // UUID of the subtree root; link targets not included
      "maxDepth": 2,
      "on":       [ "create", "delete", "value:application/json" ]
    }
  ]
}

Trigger Syntax

Each --on value (or "on" array element in the config file) is one of:

| Trigger | Fires when… | | --- | --- | | change | any watched entry changes in any way | | create | a watched entry is moved into a non-trash container | | delete | a watched entry is moved to the trash or purged | | value | the value of a watched item changes | | value:<mime-glob> | the value changes and the item's MIME type matches the glob (e.g. value:image/*) | | info:<key>=<value> | the Info.<key> field of a watched entry changes to the given value |

MIME globs support * (any sequence) and ? (any single character). Matching is case-insensitive. Only the first = in an info: trigger is the separator, so values may contain =.


Webhook Payload

Every matching webhook receives an HTTP POST with a JSON body. Each request times out after 10 seconds; a non-2xx response is logged to stderr but does not stop the sidecar.

{
  "StoreId":        "my-store",
  "Trigger":        "value:image/*",
  "Topic":          "my-topic",
  "changedEntries": ["<uuid-1>", "<uuid-2>"],
  "Timestamp":      "2026-01-15T10:30:00.000Z"
}

Topic is only present when --topic (or "Topic" in the config file) is set. The Authorization: Bearer <token> header is included when a webhook token is set.


Concurrent access with CLI commands

The sidecar and the sds CLI tool can safely operate on the same store at the same time. Both share the same SQLite database (WAL mode), and the sync engine automatically merges patches written by other processes before saving a checkpoint snapshot. This means that CLI operations like trash purge-all or entry create are never silently overwritten by the sidecar's own checkpoint.

Reconnect Behaviour

When the WebSocket connection drops for any reason other than an auth error, the sidecar reconnects automatically using exponential backoff:

  • initial delay: --reconnect-initial ms (default 1 s)
  • doubles each attempt: 1 s → 2 s → 4 s → … → cap
  • hard cap: --reconnect-max ms (default 60 s)
  • ±--reconnect-jitter fraction added randomly to each delay (default 10 %)

Auth Errors

When the server closes the WebSocket connection with code 4001 (Unauthorized — JWT rejected) or 4003 (Forbidden — JWT valid but does not match the store's audience), the sidecar:

  1. Logs the error to stderr with a clear message
  2. Fires the --on-auth-error webhook (if configured), with a JSON body:
{
  "StoreId":   "my-store",
  "ServerURL": "wss://relay.example.com",
  "Code":      4001,
  "Reason":    "Unauthorized"
}

Code is either 4001 (JWT rejected) or 4003 (JWT valid but store audience mismatch). Reason is the close-frame reason string, or the label "Unauthorized" / "Forbidden" when the server sends none.

  1. Exits without attempting to reconnect

The bearer token sent with the auth-error webhook is the same --webhook-token / SDS_WEBHOOK_TOKEN used for all other webhooks — it is entirely separate from the SDS JWT (--token / SDS_TOKEN).


Exit Codes

| Code | Name | Meaning | | --- | --- | --- | | 0 | OK | clean shutdown (SIGINT / SIGTERM) | | 1 | GeneralError | unspecified runtime error | | 2 | UsageError | bad arguments or missing required option | | 3 | NotFound | config file not found | | 4 | Unauthorized | server rejected the JWT (WS close code 4001) | | 5 | NetworkError | reserved (not currently used at exit) | | 6 | Forbidden | JWT valid but store access denied (WS close code 4003) |


License

MIT License © Andreas Rozek