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

@syndroo/sdk

v0.4.0-rc.1

Published

Lightweight TypeScript HTTP client for the Syndroo publishing API.

Readme

@syndroo/sdk

A thin TypeScript HTTP client for one deployed Syndroo instance.

The SDK wraps the documented /v1 HTTP contract and adds no server behavior. It has no runtime dependencies, ships no bundler or platform SDK, and never reconciles your data on its own.

your app / CLI / CI  ->  @syndroo/sdk  ->  Syndroo HTTP API  ->  Worker

Requirements

Node.js 22 or newer. The client uses the runtime's global fetch, AbortSignal, and Web Streams; there is no polyfill and no dependency to install.

npm install @syndroo/sdk

Quickstart

import { SyndrooClient } from "@syndroo/sdk";

const syndroo = new SyndrooClient({
  baseUrl: process.env.SYNDROO_BASE_URL!,
  apiKey: process.env.SYNDROO_API_KEY!,
});

const receipt = await syndroo.posts.create(
  {
    content: "We just shipped a new release.",
    platforms: ["bluesky"],
  },
  { idempotencyKey: "release-announcement-001" },
);

const post = await syndroo.posts.get(receipt.id);

Two boundaries

A create receipt is not a delivery. posts.create returns an acceptance result. HTTP 202 means the request was accepted for processing, and queued means "waiting for a publication job". Nothing has reached a platform yet. Read the post back and check each publication before telling a user that a post is live:

const post = await syndroo.posts.wait(receipt.id, { timeoutMs: 60_000 });

if (post.status === "published") {
  // Every selected platform confirmed.
} else if (post.status === "partial") {
  // Some platforms confirmed; inspect each publication and do not resend the
  // successful ones.
} else {
  // failed: read errorCode, errorMessage, and errorAmbiguous per publication.
}

A failed publication with errorAmbiguous: true may still have been accepted by the platform. Check the platform account by hand; do not resend that content automatically.

The API key belongs in a trusted environment. Keep it in a server, CLI, or CI credential store. Do not put it in a static page, a browser bundle, or a NEXT_PUBLIC_* variable: SYNDROO_API_KEY can publish, and anyone who reads the page can use it. Platform credentials stay in the Worker's secrets and are never part of this client. The unauthenticated /health call is the only request that sends no Authorization header.

API

new SyndrooClient(options)

| Option | Default | Meaning | |---|---|---| | baseUrl | required | Origin of the instance, for example https://syndroo.example.com. https is required outside loopback; http is accepted for localhost and 127.0.0.1, or anywhere with allowInsecureHttp: true. | | apiKey | required | Instance API key, sent as a Bearer token. | | timeoutMs | 30000 | Per-request deadline. | | waitTimeoutMs | 60000 | Default total deadline for posts.wait. | | maxResponseBytes | 8388608 | Response size limit. | | allowInsecureHttp | false | Permit plaintext http:// for a non-loopback host. |

Configuration problems throw SyndrooConfigError from the constructor, before any request is made.

Methods

| Call | HTTP | Notes | |---|---|---| | syndroo.health() | GET /health | Unauthenticated reachability check. Proves the URL answers, not that the key or the platform credentials work. | | syndroo.posts.create(input, options?) | POST /v1/posts | One request, never retried. options.idempotencyKey makes a retry safe. | | syndroo.posts.get(id, options?) | GET /v1/posts/<id> | Read-only; safe to repeat. | | syndroo.posts.list({ limit }, options?) | GET /v1/posts | limit is 1-100. | | syndroo.posts.wait(id, options?) | repeated GET /v1/posts/<id> | Reads until published, partial, or failed, or until timeoutMs. |

create accepts content, platforms, optional per-platform overrides, and an optional scheduledAt. The client validates the structure and sends your object unchanged; it does not rewrite, trim, or truncate content. Platform-specific length limits are enforced by the server and surface as typed errors or as publication failures.

wait options: timeoutMs (total budget), pollIntervalMs (default 250), maxPollIntervalMs (default 2000), and signal. Reading never creates, cancels, or resends anything, so a wait timeout leaves the server-side work running; SyndrooWaitTimeoutError keeps postId, lastStatus, lastPost, and lastError so you can resume instead of starting over.

scheduledAt is submitted to the server, so a scheduled post does not depend on this process staying alive.

Errors

Every failure is a SyndrooError. Use isSyndrooError() to narrow, or the exported classes:

| Class | Raised when | |---|---| | SyndrooConfigError | The client configuration is unusable. No request is made. | | SyndrooValidationError | An argument cannot become a valid request. No request is made. | | SyndrooApiError | Syndroo answered with a documented error envelope. Carries status, code, retryAfterMs, and retryable. | | SyndrooResponseError | The answer was not the documented JSON contract: not JSON, missing fields, or over the size limit. | | SyndrooAbortError | You aborted through an AbortSignal. | | SyndrooTimeoutError | The deadline elapsed before the response was received. | | SyndrooNetworkError | No HTTP response arrived; networkCode carries codes such as ECONNREFUSED. | | SyndrooWaitTimeoutError | posts.wait hit its deadline before a terminal status. |

Server codes such as UNAUTHORIZED (401), POST_NOT_FOUND (404), IDEMPOTENCY_CONFLICT (409), PLATFORM_NOT_CONFIGURED (422), and SERVICE_UNAVAILABLE (503) appear on SyndrooApiError.code.

requestMayHaveBeenApplied is the flag that decides whether a resend is safe. It is true when a write may already have reached Syndroo: a timeout, an abort, a network failure, or a 5xx after a POST. A 4xx is a decision, so it is false. When it is true, look the post up with posts.list or repeat the identical request with the same Idempotency-Key — never with a new key and never with different content.

Timeouts, aborts, and retries

  • The SDK performs exactly one HTTP request per call. It never retries a write. A silent retry without a stable idempotency key can duplicate a post.
  • A timed-out POST is reported as an unknown outcome, not as "not created".
  • AbortSignal is supported on every method; aborting a read is always safe.
  • API errors carry no retry attempt, only retryable and retryAfterMs, so your own policy decides.
  • posts.wait absorbs transient failures (network errors, timeouts, 5xx, 429) with bounded exponential backoff, honours Retry-After, and stops at the first authentication, validation, or "not found" answer instead of polling.

Redirects

The client never follows redirects. A 3xx answer becomes a SyndrooApiError with code REDIRECT_NOT_FOLLOWED, so the Authorization header can never be forwarded to another host. Point baseUrl at the final API origin, for example the https://...workers.dev URL of the deployment.

Not in this version

The SDK is deliberately small. There is no preview, approval, retry, or cancel endpoint, no dashboard, no media upload, and no OAuth handling, because the server does not offer those operations yet. Direct HTTP remains the fallback for anything this client does not cover.

License

Apache-2.0. See LICENSE and NOTICE.