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

@fyrlabs/dead-drop

v0.17.0

Published

dead-drop: a transport-agnostic runtime for distributed applications

Readme

dead-drop

Build the application. Bring the transport. dead-drop connects them.

dead-drop is a local-first runtime that lets applications on different machines talk to each other through infrastructure you already have: a git repository, a shared or synced folder, object storage, or an adapter you write yourself. There is nothing to deploy, no public endpoint, no broker, and the application does not need to know how its bytes move.

Neither machine listens on a public port. Both write and read encrypted objects in a place they can already both reach, which is why this works from behind NAT, from a CI runner, or from a locked-down corporate network.

flowchart LR
    subgraph A ["Machine A, behind NAT"]
        app["Your app on localhost:3000, unmodified"]
        ra["dead-drop runtime"]
        app --- ra
    end

    store[("Shared storage: a git repo, a folder, S3")]

    subgraph B ["Machine B, elsewhere"]
        rb["dead-drop runtime"]
        port["localhost:53219"]
        rb --- port
    end

    ra -- "writes encrypted objects" --> store
    store -- "polled, then decrypted" --> rb
    rb -. "the response returns the same way" .-> store

The storage in the middle only ever holds ciphertext, envelope headers included, so it never learns a channel, peer or workspace name.

ddrop connect gives you an ordinary local URL. Whatever you point at it, curl or a browser or a client library, talks to the app on the other machine without knowing any of the above happened.

# machine A: an ordinary Express app on :3000, unmodified
ddrop start &                                             # the runtime, reading ./deaddrop.config.json
ddrop expose --target http://localhost:3000 --name my-api

# machine B
ddrop connect machine-a/my-api
# http://127.0.0.1:53219  →  curl it, open it in a browser, point a client at it

Both machines need a deaddrop.config.json first: same workspace secret, same transport. The quick start below writes one.

What this is, and what it is not

dead-drop is genuinely useful when you want two machines to talk and the network between them is the problem: a laptop behind NAT, a locked-down corporate environment, a CI runner, an air-gapped review box that can still reach a git remote. It is a real runtime with encryption, retries, failover, deduplication, health-based routing and observability.

It is not a message broker, and it will not pretend to be one. A round trip over a git remote costs seconds, not milliseconds. If you have Kafka, use Kafka. dead-drop is for the case where standing up infrastructure is the expensive part.

Delivery is at-least-once. Ordering is best-effort per recipient. Both are stated plainly in docs/guarantees.md rather than buried.

Install

Requires Node.js 20.11 or newer.

npm install -g @fyrlabs/dead-drop

Quick start: two machines, a shared folder

# Once, on either machine:
ddrop keygen                      # prints ddk1_…, share it with the other peer, securely
export DEADDROP_SECRET='ddk1_…'
ddrop init --name demo            # writes deaddrop.config.json

deaddrop.config.json:

{
  "dataDir": ".deaddrop",
  "workspaces": [
    {
      "name": "demo",
      "peerId": "machine-a",
      "secrets": ["${env:DEADDROP_SECRET}"],
      "transports": [{ "use": "filesystem", "config": { "root": "/Volumes/shared/deaddrop" } }],
      "exposures": [{ "name": "my-api", "type": "http", "target": "http://localhost:3000" }]
    }
  ]
}

ddrop start runs in the foreground, so the commands after it belong in a second shell:

ddrop start          # machine A
ddrop start          # machine B, same secret, peerId "machine-b", no exposures
ddrop discover       # machine B sees machine-a and its exposures
ddrop connect machine-a/my-api

Client commands find the runtime through the dataDir in the config they discover, so run them from the directory holding deaddrop.config.json, or pass --config or --socket.

Quick start: over GitHub

Data moves by pushing and pulling a dedicated branch. Authentication is whatever gh already has; dead-drop never sees a token.

gh auth login && gh auth setup-git
{
  "use": "github",
  "config": {
    "repo": "your-org/deaddrop-workspace",
    "workDir": "./.deaddrop/github",
    "createIfMissing": true
  }
}

dead-drop writes to a deaddrop-data orphan branch, so your repository's real history is untouched. Deleting that branch discards undelivered messages and nothing else.

Native SDK

For applications that want dead-drop-native interactions rather than HTTP proxying:

import { createClient } from '@fyrlabs/dead-drop/sdk';

// dataDir must match the running runtime's; it defaults to ~/.deaddrop.
const ddrop = createClient({ workspace: 'demo', dataDir: '.deaddrop' });

await ddrop.publish('events/orders', { type: 'order.created', id: 42 });
const sum = await ddrop.call('machine-a', 'math.add', { a: 10, b: 20 });

And on the serving side, from an embedded runtime:

workspace.service('math', {
  add: ({ a, b }) => a + b,
});

The SDK is optional. Proxy mode needs none of it.

Writing a transport

A transport is an ordinary npm package. Nothing in this repository changes, and nothing needs to be merged.

import { defineTransport } from '@fyrlabs/dead-drop-transport-sdk';

export const acmeTransport = defineTransport({
  id: 'acme',
  capabilities: {
    kind: 'store',
    ordering: 'partition',
    binaryPayloads: true,
    delete: true,
    watch: false,
    orderedList: true,
  },
  create(config, context) {
    return {
      kind: 'store',
      async put(key, data) { /* … */ },
      async get(key) { /* … */ },
      async list(prefix, options) { /* … */ },
      async delete(key) { /* … */ },
      async health() { return { status: 'healthy' }; },
      async close() {},
    };
  },
});

Four methods. dead-drop supplies framing, encryption, chunking, acknowledgement, retries, deduplication, dead-lettering and adaptive polling on top. Run the conformance suite against your adapter before publishing it. See docs/writing-a-transport.md.

Commands

ddrop start                         run the runtime
ddrop status                        runtime, workspaces, transports
ddrop list                          workspaces this runtime serves
ddrop discover                      peers visible in the workspace
ddrop transport list | health       transport scores and health
ddrop expose --target <url> --name <n>
ddrop expose <dir> --name <n>
ddrop connect <peer>/<exposure>
ddrop call <peer> <channel> --input '{"a":1}'
ddrop publish <channel> --input '{...}'
ddrop logs | metrics
ddrop trace [<traceId>]             recent traces, or one as a span tree
ddrop keygen | init

--json makes the output machine-readable, including errors. call always returns JSON; metrics is Prometheus text either way.

Entry points

dead-drop ships as two packages. Everything the runtime needs is in one; the transport contract is separate so a third-party adapter does not depend on the whole runtime.

| Import | Purpose | | --- | --- | | @fyrlabs/dead-drop | The ddrop command, and the client at the root import. | | @fyrlabs/dead-drop/sdk | Application client over the control socket. | | @fyrlabs/dead-drop/runtime | Embed the runtime in your own process. | | @fyrlabs/dead-drop/core | Transport manager, reliability, mailbox engine, observability. | | @fyrlabs/dead-drop/protocol | Envelope, framing, encryption, chunking. | | @fyrlabs/dead-drop/transports/<name> | filesystem, git, github, memory. | | @fyrlabs/dead-drop/cli | Run a command programmatically. | | @fyrlabs/dead-drop-transport-sdk | defineTransport and the conformance suite. The only package a transport author needs. |

Security

One 32-byte secret per workspace. Holding it is membership. Everything on a transport is AES-256-GCM ciphertext including the envelope header, so channel, peer and workspace names never appear in clear text. The transport is treated as hostile storage.

What is not protected: message sizes, timing, and the object keys the mailbox writes. Read docs/security-model.md before deciding this fits your threat model.

Documentation

Development

npm install
npm run verify      # lint, build, test with coverage
npm test            # tests only

The git transport tests require a git binary. Everything else runs with no external dependencies, no network and no credentials.

Licence

Apache-2.0.