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

@syncropel/sdk

v0.8.0

Published

Integration SDK for the Syncropel protocol — TypeScript. Async client, grammar enforcement, canonical references, fail-open transport.

Readme

@syncropel/sdk

TypeScript SDK for the Syncropel protocol — emit content-addressed records, query threads, validate grammar, fail open on transport errors.

import { Client, Identity, Ref } from "@syncropel/sdk";

const client = new Client({
  endpoint: "http://localhost:9100",
  identity: Identity.static("did:example:my-app"),
});

const result = await client.emit({
  act: "PUT",
  kind: "music.catalog.track",
  body: { title: "Glow", artists: ["Zonke"] },
  refs: [Ref.musicTrack({ isrc: "USJI19810404" })],
  thread: "music.library",
});

console.log(result.success, result.recordId);

One call validates the grammar, builds the record envelope, retries on transient errors, and resolves cleanly on transport failures so a flaky network never crashes your handler.


Features

  • Async clientemit, query, queryThread, subscribe, intend, fulfill, plus reserved-kind helpers
  • Grammar enforcementbody.kind validated before any network call
  • Canonical references — 11 community ref constructors (@music.track, @code.file, @social.person, …) for cross-publisher correlation
  • Fail-open transport — every emit resolves to a result; transport errors never reject the promise
  • Identity-aware — every record signed with the configured DID
  • Zero runtime dependencies — uses the platform fetch
  • Universal runtime — Node 18+, Deno, Bun, Cloudflare Workers, modern browsers
  • In-memory MockKernel for tests at @syncropel/sdk/testing — no server needed

Install

npm install @syncropel/sdk

| Runtime | Minimum | |---|---| | Node.js | 18 (built-in fetch + AbortController) | | Deno | 1.30+ | | Bun | 1.0+ | | Cloudflare Workers | anywhere fetch is available | | Modern browsers | anywhere fetch is available |

Ships compiled JavaScript + TypeScript declarations. ESM only.


Quickstart

Spin up a local Syncropel server, then emit your first record:

// hello.ts
import { Client, Identity, Ref } from "@syncropel/sdk";

const client = new Client({
  endpoint: "http://localhost:9100",
  identity: Identity.static("did:example:me"),
});

const result = await client.emit({
  act: "PUT",
  kind: "music.catalog.track",
  body: { title: "Glow" },
  refs: [Ref.musicTrack({ isrc: "USJI19810404" })],
  thread: "music.library",
});
console.log("emitted:", result.recordId);

const records = await client.queryThread({ thread: "music.library" });
console.log(`thread has ${records.length} record(s)`);
npx tsx hello.ts

See syncropel.com for installing the Syncropel server (spl) and a full hosted-vs-local guide.


Concepts in 60 seconds

  • Record — the immutable, content-addressed unit. 8 fields; the SDK builds the envelope for you.
  • Kindbody.kind names what the record is about, e.g. music.catalog.track. Follows a strict grammar: scope.category.entity[.version].
  • Thread — a logical conversation / workflow. Records share a thread when they're part of the same activity.
  • Actor — who emitted the record, expressed as a DID.
  • Ref — a canonical pointer to a real-world entity (a song, a file, a person…) so records about the same thing correlate across apps.

API reference

Client

| Method | Purpose | |---|---| | emit({ act, kind, body, thread, refs?, parents?, dataType?, clock? }) | Primary emit. Returns Promise<EmitResult>. Grammar errors throw; transport errors resolve to success: false. | | intend({ goal, thread?, parents?, kind?, body?, clock? }) | Open a thread. Emits INTEND. Generates a random thread id if none supplied; returns it on result.thread. | | fulfill({ thread, summary, fulfills?, kind?, body?, parents?, clock? }) | Close a thread. Emits KNOW with body.summary + optional body.fulfills (id or list of ids). | | emitCorrection({ corrects, revisedFields, reason, thread, ... }) | Reserved-kind helper for core.correction — supersede earlier records with revised values. | | emitErasure({ erases, reason, thread, ... }) | Reserved-kind helper for core.erasure — mark records as erased (e.g. for compliance). | | emitAlias({ oldKind, newKind, reason, thread, ... }) | Reserved-kind helper for core.alias — declare that one kind supersedes another. | | emitScopeTransfer({ scope, fromPublisher, toPublisher, reason, thread, ... }) | Reserved-kind helper for core.scope_transfer — record ownership of a scope changing publisher. | | emitScopeClaim({ scope, governancePolicy, thread, ... }) | Reserved-kind helper for core.scope_claim — claim a scope with a governance policy. | | queryThread({ thread, limit?, since? }) | All records in a thread. Fail-open. | | query({ kind?, actor?, thread?, since?, limit?, where? }) | Filtered query. At least one of kind / actor / thread required. | | subscribe(thread, callback, options?) | Live record subscription via SSE. Returns a Subscription handle (close(), cursor, closed). Auto-reconnects with exponential backoff + jitter; resumes from last cursor. See examples/subscribe.ts. | | health() | Server health probe. Fail-open. |

Constructor options: endpoint (string), identity (required), timeoutMs (default 30000), maxRetries (default 2), backoffMs (default 250), onEmit (observability hook), apiKey (sets x-api-key header), fetch (custom transport — useful for tests, proxies, observability wrappers).

Namespaces

Beyond the flat emit/query methods, the client groups the rest of the surface into namespaces:

| Namespace | Purpose | |---|---| | client.records | .create(emitOpts) (emit alias), .get(id), .richQuery({ filter, sort?, limit? }) — server-side filtered query. | | client.threads | .list(), .records(id), .state(id), .project(id, format), .watch(id, opts?) — live AsyncIterable stream. | | client.folds | .resolve<V>(name, cacheKey?), .watermark(name, cacheKey) — read any kernel fold through one typed method. | | client.data | The data plane — files, blobs, materials (see below). | | client.aitl | .pending(), .decide(id, { decision, reason? }) — actor-in-the-loop proposals. | | client.namespaces | .list(), .create({ id, description?, policy? }). | | client.federation | .peers(), .pair(peer), .sync(thread, peer?), .discoverPeers(), .mergeResults(). | | client.invites | issue / list / preview / redeem / revoke / audit invitations. | | client.expr | .eval({ expression, context, record? }) — evaluate a CEL expression. | | client.graph | .query({ start, edges?, depth?, filter? }) — graph queries over the substrate. |

client.data — the data plane

Files, blobs, and materials. Unlike the record reads (which fail open), the data plane surfaces errors as SyncropelDataError — a dropped write never looks like a success.

| Method | Purpose | |---|---| | write({ path, data, contentType?, expectedHash? }) | One-call three-step write (init → chunked upload → commit). Returns { contentHash, sizeBytes }. expectedHash: omit = overwrite, a hash = compare-and-swap, null = create-only. | | read(path) / readText(path) | Fetch a path's bytes / UTF-8 text. | | readUrl(path) | Resolve a path to a short-lived blob URL. | | list(path, { cursor?, limit? }) | Directory listing. | | stat(path) | Node metadata: size, hash, content type, pin state, provenance. | | mkdir(path) / mv(from, to) / rm(path, { recursive? }) | Tree mutations. | | publish(path, { storageClass? }) | Promote a file to a durable, content-addressed artifact. | | materials({ q?, limit? }) / node(path) | Typed, byte-free views over files (pickers, previews). | | usage() / capabilities() / provenance({ … }) | Quota, self-describing limits, the "made by" feed. |

// Create, then safely edit with compare-and-swap.
const { contentHash } = await client.data.write({
  path: "/files/notes.md",
  data: "# hello",
  contentType: "text/markdown",
});
try {
  await client.data.write({ path: "/files/notes.md", data: "# edited", expectedHash: contentHash });
} catch (e) {
  if (e instanceof SyncropelDataError && e.isConflict) {
    // someone else wrote first — reload e.currentHash and retry
  }
}
const text = await client.data.readText("/files/notes.md");

Identity

| Form | Status | |---|---| | Identity.static(did) | Available | | Identity.key(pathOrBytes) | Planned — throws if called today | | Identity.federated(...) | Planned — throws if called today |

Ref — canonical reference constructors

| Constructor | Canonical | Schemes | |---|---|---| | Ref.musicTrack({ isrc? / spotifyId? / appleId? }) | @music.track | isrc:, spotify:, apple: | | Ref.codeFile({ repo? / gitUrl?, path }) | @code.file | repo:, git: | | Ref.opsIncident({ pagerduty? / linear? / url? }) | @ops.incident | pd:, linear:, url: | | Ref.calEvent({ uid }) | @cal.event | uid: | | Ref.socialPerson({ did? / email? / handle?+name? }) | @social.person | did:, email:, handle: | | Ref.mediaPhoto({ sha256? / url? }) | @media.photo | sha256:, url: | | Ref.mediaVideo({ youtube? / vimeo? / sha256? }) | @media.video | youtube:, vimeo:, sha256: | | Ref.docText({ doi? / url? / platformId? }) | @doc.text | doi:, url:, platform: | | Ref.finTransaction({ stripe? / plaid? / iso20022? }) | @fin.transaction | stripe:, plaid:, iso20022: | | Ref.researchPaper({ doi? / arxiv? / s2? }) | @research.paper | doi:, arxiv:, s2: | | Ref.coreThread({ id }) | @core.thread | id: |

Pass a list as refs: to emit(); the SDK merges them into body._refs. Two records anywhere in the network sharing the same canonical ref are joinable.

EmitResult

interface EmitResult {
  success: boolean;
  recordId?: string;
  clock?: number;
  error?: string;
  retried: number;
  kind: string;
  act: string;
  thread: string;
}

SyncropelKindError

Thrown synchronously from validateKind() and every emit* method when body.kind violates the grammar. Subclasses Error; instanceof works across bundlers.


Fail-open contract

emit() never rejects its promise on network errors, 4xx, 5xx, or timeouts. Every call resolves to an EmitResult and your code inspects .success:

const result = await client.emit({ act: "PUT", kind: "music.catalog.track", body: {}, thread: "t" });
if (!result.success) {
  // Transient failure — log and keep going.
  console.warn(`emit failed: ${result.error} (retried ${result.retried})`);
}

A flaky network can't bring down your handler. You decide whether to drop the failure, retry, or escalate.

Grammar errors are different. SyncropelKindError always throws — it indicates programmer error (an invalid body.kind), and no retry will fix it. Failing loud at development time is correct.

Observability hook

const client = new Client({
  endpoint: "...",
  identity: Identity.static("..."),
  onEmit: (result) => {
    metrics.increment("syncropel.emit", { tags: { success: result.success } });
  },
});

Fires on every success and failure. Hook exceptions are swallowed with a console.warn — a broken metrics pipeline can't break your emit path.


Grammar reference

Every record's body.kind follows the scope.category.entity[.version] grammar. The SDK validates at emit time:

| Kind | Valid? | |---|---| | music.catalog.track | ✓ — publisher scope, 3 segments | | music.catalog.track.v2 | ✓ — versioned | | @music.track | ✓ — community canonical (2-segment allowed for @<community> and core scopes) | | core.alias | ✓ — reserved core primitive | | dj.track_imported | ✗ — 2-segment publisher scope forbidden | | Music.Catalog.Track | ✗ — uppercase forbidden | | music.catalog.track.v2.foo | ✗ — 5 segments |

When a canonical ref exists for your domain, use it:

refs: [Ref.musicTrack({ isrc: "USJI19810404" })]

This makes your record correlatable with every other music record across publishers. Without refs, nothing breaks — you lose cross-app correlation.


Testing your adapter

The SDK ships a canonical mock at @syncropel/sdk/testing — exercise your adapter end-to-end without running a server:

import { Client, Identity, Ref } from "@syncropel/sdk";
import { MockKernel } from "@syncropel/sdk/testing";

test("my adapter imports tracks", async () => {
  const kernel = new MockKernel();
  const client = new Client({
    endpoint: "http://mock",
    identity: Identity.static("did:test:me"),
    fetch: kernel.fetch,
  });

  await myAdapter.importLibrary(client, tracks);

  const emitted = kernel.recordsByKind("music.catalog.track");
  assert.equal(emitted.length, tracks.length);
  for (const r of emitted) {
    const refs = (r.body as any)._refs as Array<{ kind: string }>;
    assert.equal(refs[0].kind, "@music.track");
  }
});

Record IDs produced by MockKernel use the same SHA-256-of-canonical-JSON rule as the real server, so result.recordId matches what you'd see in production. The mock also enforces DuplicateClock on (thread, actor, clock).

Failure injection for fail-open coverage: kernel.failNextPostCall(n) and kernel.failNextGetCall(n).


Type-generation pipeline (v0.4.0+)

The SDK ships a manifest-derived type module at src/types-generated.ts — 20 typed interfaces produced from the kernel's published JSON schemas (/v1/capabilities). These are the contract surface; hand-written types in src/types.ts and src/client.ts reference them.

To regenerate after a kernel manifest change:

# 1. Regenerate the manifest snapshot from the kernel.
cargo run --bin export-manifest -p spl > sdks/manifest.json

# 2. Regenerate the SDK types from the new manifest.
cd sdks/typescript
npm run gen:types

# 3. Commit both manifest.json and src/types-generated.ts together.
git add ../manifest.json src/types-generated.ts

Drift detection: npm run build runs npm run check:types first. If the regenerated output differs from the committed types-generated.ts, the build fails with a diff — preventing accidental drift between the SDK and the kernel manifest.

Manifest version pin: package.json declares which manifest version the SDK targets ("syncropel": { "manifest_version": "2" }). Mismatch with the daemon's served manifest version is exposed via the MANIFEST_VERSION constant — useful when a deployment runs an older daemon than the SDK expects.


Versioning + stability

Independent semver. Patch releases ship freely; minor versions can change public behaviour with a CHANGELOG note.

| SDK version | Highlights | |---|---| | 0.4.x | Current. Type-generation pipeline + method expansion (client.threads, client.aitl, client.federation, client.namespaces, client.expr, client.graph, client.records namespaces; do/know/learn/capabilities flat methods; namespace constructor option). | | 0.3.x | Subscribe / SSE record streaming with reconnect + resume. | | 0.2.x | Adds query, search, and infer to the 0.1 surface. | | 0.1.x | Foundation: async Client, grammar enforcement, canonical refs, reserved-kind helpers, MockKernel, fail-open transport. |


License

Apache-2.0.