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

@semaphile/messaging

v0.2.0

Published

Durable local agent messaging with SQLite mailboxes and the semaphile CLI.

Readme

@semaphile/messaging

Durable messaging between local processes, with SQLite and kernel notifications. TypeScript library and semaphile message CLI; no resident daemon or periodic inbox polling. Supports Node 22.18+ and the modern Bun version verified by the conformance suite. Native artifacts currently target macOS arm64 and Linux x64.

This package is experimental. See the release guide for installation and verified GitHub archives. Installation does not compile native code.

Library

import { openMessaging } from '@semaphile/messaging';

const client = await openMessaging({
  path: './.semaphile/messaging',
  configMismatch: 'error', // Default is 'warn', adopting persisted settings.
});
await client.createMailbox('reviewer');
await client.send({
  to: 'reviewer',
  body: JSON.stringify({ instruction: 'Review the change' }),
  dedupeKey: 'review-request-123',
});

const delivery = await client.wait('reviewer', { timeoutMs: 5_000 });
if (delivery) {
  // Durably accept delivery.id and its payload in your adapter before acking.
  console.log(delivery.message.body);
  // Call client.ack(delivery.receipt) after that durable acceptance succeeds.
}
await client.close(); // The outstanding receipt survives this process exiting.

For automatic handling, your adapter supplies a function whose successful return means durable acceptance:

const listenerClient = await openMessaging({ path: './.semaphile/messaging' });
const listener = listenerClient.listen('reviewer', async (delivery, context) => {
  await durableAccept(delivery.id, delivery.message.body, context.signal);
});
await listener.close(); // Stop new claims; finish active handlers.
await listenerClient.close();

durableAccept above is your application function. Printing a message, launching a process, or typing into a terminal is not itself proof of durable acceptance. Task completion can be a later reply with correlationId or replyTo.

A receive() returns an array immediately; wait() blocks until one delivery, cancellation, or its optional timeout. Positive timeouts include time queued for the store gate; zero performs one inspection and never waits for a message. Both default to manual acknowledgment. Each returns a receipt with deliveryId and claimId. A new attempt preserves the delivery ID but gets a new claim ID. ack, release, and renew reject stale ownership through { status: 'stale' }; duplicate retained acknowledgments are idempotent. Renewal of a retained, successfully acknowledged receipt reports acked without changing it, allowing listener cleanup to continue after another process accepts the delivery. History eviction eventually removes old acknowledgment receipts.

Listeners default to handler-success; select ackMode: 'manual' for explicit context.ack() or acknowledgment from another process. A sender's optional ackMode must appear in the receiver's acceptedAckModes, which defaults to its configured mode. Incompatibility fails that delivery without invoking the handler. Accepting both modes means your handler must honor delivery.ackMode.

Listeners renew active claims, bounded by maxHandlingMs and message expiry. An independent deadline watchdog signals context.signal even if renewal is blocked on the store gate. Cancellation is cooperative: a callback or remote request may continue after claim loss. close({ cancel: true }) signals active handlers and may escalate an earlier graceful close. Neither close mode drains the durable queue; both await active handler cleanup. A noncooperative callback or suspended gate holder can therefore delay shutdown.

Handler failures and abandoned claims consume the same attempt budget. After exhaustion, inspect history() and explicitly retry(deliveryId). Release also uses the retry policy. Failed or expired deliveries are visible while history is retained; expired messages require a new send.

CLI and configuration

semaphile init
semaphile message create --name reviewer
semaphile message send --to reviewer --body 'Review the change'
semaphile message wait --as reviewer --timeout 5000 > receipt.json
# Once the adapter has durably accepted the message:
semaphile message ack --receipt-file receipt.json
semaphile info

semaphile.json defines project defaults:

{
  "version": 1,
  "directory": "./.semaphile",
  "pools": {},
  "messaging": {
    "config": { "maxAttempts": 5 },
    "clientDefaults": { "configMismatch": "warn" },
    "listenerDefaults": { "concurrency": 1, "maxHandlingMs": 3600000 },
    "handler": ["node", "./adapter.mjs"]
  }
}

The CLI searches from its working directory upward and uses the nearest config. Invalid nearer files are errors. Relative paths resolve against that file. --store PATH bypasses discovery and selects one individual messaging store; --config-mismatch error overrides the client's default warning policy. Library loadConfig() and messagingOptions() expose discovery explicitly; openMessaging() itself uses only the supplied path and options.

Shared configuration is fixed at creation. Warning mode reports differences and uses persisted values; error mode rejects before registration or logical mutation. client.config exposes effective settings; onWarning(differences) receives structured differences. Editing JSON never silently reconfigures a store. init creates missing stores, validates existing ones strictly, and can resume partial initialization. It does not overwrite an existing config file.

A common root holds messaging/ and independent pools/<name>/ databases. Initializing named pools requires the optional @semaphile/core package; pool configuration mismatches always fail. info shows complete persisted settings and normalized project comparisons. With an explicit store and no expected configuration, it explicitly reports that no comparison was requested. Library info({ store, expectedConfig }) performs that comparison without registering.

semaphile message listen --as reviewer -- node ./adapter.mjs
semaphile message register --name reviewer -- node ./agent.mjs
semaphile message agents
semaphile message send --to '*' --body 'Status check'

Handlers receive one JSON delivery envelope on stdin. They must consume the input and exit zero only after durable acceptance. Input-write errors and unsuccessful exits fail the attempt. SIGINT/SIGTERM cancels active CLI handlers; registration wrappers use the same five-second escalation for an owned child that ignores SIGTERM. Commands run without implicit shell interpolation. Handler output is inherited. Cancellation sends SIGTERM to the owned child, then SIGKILL after five seconds if it has not closed; this does not promise to terminate descendants or undo an external action.

Registration is optional presence. The registering command remains alive, or wraps the supplied child, holding its own lifetime lock. A listener does not implicitly register. Broadcast atomically targets the names observed online at send time; new names never join a deduplicated broadcast retry. Replacing a session with the same name inherits its mailbox's pending deliveries.

Other commands: receive, renew, release, retry, history, events, and append. Run semaphile --help for argument names. History supports recipient, sender, correlation, topic, time and sequence filters. Events support sequence, limit, topic and time filters; unsupported filters fail explicitly. Exit codes: 0 success, 2 wait timeout, 3 cancellation, 4 store/config failure, 5 input or protocol refusal. Claimed message JSON includes the receipt, so a subsequent command can acknowledge it after the waiter exits.

Shared defaults and bounds

| Setting | Default | | ---------------------- | ----------: | | claimTtlMs | 300,000 | | maxHandlingMs | 3,600,000 | | maxAttempts | 5 | | retryDelayMs | 1,000 | | retainHistoryMs | 604,800,000 | | dedupeRetentionMs | 604,800,000 | | maxBodyBytes | 1,048,576 | | maxPendingPerRecipient | 1,000 | | maxMessages | 10,000 | | maxEvents | 100,000 | | maxContentBytes | 134,217,728 |

Retry delays double after each unsuccessful attempt. Terminal history may be evicted earlier than its age limit under storage pressure. Pending bodies are protected; deduplication survives until the configured period after the entire message becomes terminal, even if history bodies were evicted. When protected data fills capacity, new sends fail. Bounded cleanup yields the gate between batches and repeats only after actual cleanup progress.

Logical accounting includes payloads, envelope/dedupe metadata, session metadata, mailboxes and events, with conservative row overhead. Each pending delivery reserves 8 KiB for its future claim/error state so acknowledgment and failure recording do not require additional capacity. Terminal transitions return unused reservation. This is not a physical filesystem quota. Stable owner and inbox coordination files are retained. Historical row cleanup is opportunistic; reads hide history past its age window without requiring a background cleanup process.

Use local filesystems and cooperative processes. Kernel lifetime locks indicate process participation, not whether an agent is ready to handle a task. Claims expire independently of presence. At-least-once delivery permits duplicate external effects, so adapters should deduplicate using the stable delivery ID. Clock adjustments, reboot handling, power-loss durability and network filesystems are outside the currently verified contract. Redis messaging and topic subscriptions are deferred; the existing Redis limiter is separate.

Development

From the repository root, install dependencies with --ignore-scripts, build the core first, then messaging. Run node conformance/run.mjs messaging and repeat with the supported Bun executable. The core provides private native and pure policy build inputs; installed messaging artifacts are self-contained.

Pool administration

The CLI can administer existing limiter pools as well as messaging stores. Install the matching optional @semaphile/core peer for SQLite, and additionally @semaphile/redis for Redis. Memory pools have no external CLI: their state lives inside one JavaScript runtime.

semaphile pool status --name service
semaphile pool drain --store ./.semaphile/pools/service
semaphile pool wait --store ./.semaphile/pools/service --generation 1 --timeout 30000
semaphile pool resume --store ./.semaphile/pools/service --generation 1

--name selects a pool from the nearest semaphile.json; its full configuration must match the existing store. --store selects an existing SQLite directory and reads persisted policy. Add --pool-config policy.json to enforce explicit expectations. Named configuration cannot be combined with --pool-config. Empty selectors reject. These commands do not initialize missing pools.

For Redis, provide the URL through an environment variable and put the expected limiter configuration in a JSON file. Match the existing ownership timeout too:

semaphile pool status --redis-url-env REDIS_URL --pool service \
  --namespace team --pool-config policy.json --owner-timeout 30000

Use the same selectors with drain, wait, acknowledge and resume. Redis administration atomically refuses missing state. Keep credentials in the named environment variable; policy files contain limiter settings, not the Redis URL.

Drain returns its generation. Supply that generation to subsequent commands; a stale generation cannot alter a newer drain. If a status snapshot contains unconfirmed operations, investigate their remote effects before acknowledging:

semaphile pool acknowledge --name service --generation 1 \
  --ids-file reviewed-operation-ids.json --reason 'Operator reviewed provider state'

The ID file is a nonempty JSON string array; the reason must be nonblank and at most 4096 characters. Acknowledgement can make a drain settled, but cannot make uncertain work clean. A timed-out or cancelled wait never resumes the pool. Successful commands print JSON. Exit 2 means timeout, 3 means cancellation after opening, 4 means store/configuration failure and 5 means input/protocol refusal. During startup, SIGINT/SIGTERM retain normal OS termination behavior so a blocked native gate wait cannot swallow shutdown. After opening, signals cancel waits and close the administrative client. No command kills producer processes.

See execution and maintenance for the library API, recovery behavior and experimental-format replacement procedure.

Optional tracing (0.2.0)

The next release adds optional client-local tracing and explicit offline upgrades from messaging format 1.0 to 1.1. See the observability guide for API hooks, @semaphile/otel/messaging, CLI --otel, handler propagation, baggage limits and the semaphile message upgrade --store PATH procedure. Stop all old clients before upgrading; ordinary opens never migrate a store.