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

@burojs/ops-client

v0.2.0

Published

Buro: thin client for a neutral operations RPC surface (POST <baseUrl>/<operation>, a {result}/{error} envelope) — not a DataProvider

Downloads

98

Readme

@burojs/ops-client

A thin client for a neutral RPC surface — POST <baseUrl>/<operation> over a JSON body, answering { result } on success or { error: { kind, code, message } } on failure. For buro.

This is not a DataProvider. It has no notion of a resource, a list, or CRUD — it calls one named operation and hands back (or throws) whatever that operation returns. Wiring a specific operation behind a framework defineQuery/defineMutation (@burojs/core's queries.custom / mutations.custom seam) is a separate, deliberately later decision, made by whoever knows what that operation's own domain error codes mean.

Install

pnpm add @burojs/ops-client

Zero runtime dependencies — this package is plain fetch, nothing else.

Usage

import { createOpsClient, OpsCallError } from '@burojs/ops-client';

const ops = createOpsClient({
  baseUrl: 'https://example.com/api/marketplace/ops',
  getToken: () => authProvider.getToken(),
});

try {
  const summary = await ops.call('exportOrders', { status: 'paid' });
} catch (error) {
  if (error instanceof OpsCallError) {
    // error.kind is 'transport' | 'domain' — check this FIRST.
    // error.code disambiguates within it; error.status is the HTTP status.
  }
}

createOpsClient({ baseUrl, getToken, fetchImpl? })

  • baseUrl — origin + mount path, e.g. https://host/api/marketplace/ops. A trailing slash is tolerated.
  • getToken: () => string | null | Promise<string | null> — called on every call(). A resolved non-null value is sent as Authorization: Bearer <token>; null omits the header entirely.
  • fetchImpl?: typeof fetch — defaults to the global fetch.

Returns { call }.

call(operation, args?, options?)

Posts args (defaulting to {} — the wire contract requires a well-formed JSON body even for an argument-less operation) as the JSON body to <baseUrl>/<operation>, and resolves with the operation's own result value on success. options.signal, if given, is forwarded to fetch.

Rejects with OpsCallError on any failure:

class OpsCallError extends Error {
  readonly kind: 'transport' | 'domain';
  readonly code: string;
  readonly status: number; // 0 when no response was ever received
  readonly details?: unknown;
}

kind is the field to check first — mirrors the wire contract's own framing of it as the one part of the envelope fully closed to 'transport' | 'domain'. code disambiguates within it:

  • For kind: 'transport', code is one of the RPC surface's own five transport codes (invalid-request / unknown-operation / invalid-arguments / unauthorized / internal-error, exported as the OpsTransportErrorCode type for reference) — or one of this client's OWN two codes for a failure that never produced a parseable response at all: network-error (the request failed, or the response was not this contract's envelope) or request-aborted (the call's AbortSignal fired). opsAdapter never sends these last two; a caller that only recognises the five wire codes still catches them correctly by checking kind === 'transport' first.
  • For kind: 'domain', code is whatever free-form string the operation's own author chose (OperationError's code on the server side) — this client does not know or enumerate it.

The contract, and why this package doesn't import an implementation of it

The one implementation of this contract in this repository is @burojs/mock-kit's opsAdapter (packages/mock-kit/src/ops/adapter.ts). This package does not depend on it, at runtime or in its type exports: the whole reason the demo program chose a neutral RPC here (program spec docs/superpowers/specs/2026-08-11-marketplace-ops-demo-design.md §11 — "адоптеру понятнее вендор-независимый контракт", "an adopter finds a vendor-independent contract easier to understand") is that a client is written against the WIRE SHAPE, not against whichever backend happens to implement it today. A real backend that speaks the same envelope needs no different client than the mock does.

This package's own test suite (tests/client.test.ts) proves the two stay in step the honest way: it mounts a real opsAdapter (a devDependency, used only in tests) and drives it over real HTTP through this client — not against a hand-written fixture of what the maintainer believes the server returns.

What this package deliberately does not decide

Translating an OpsCallError into @burojs/core's DataError taxonomy is left to whoever wires a specific operation behind queries.custom / mutations.custom. DataError.kind is a closed set (configuration | capability | transport | auth | validation | protocol | cancelled | conflict) with no 'domain' member, and only that caller knows what a given operation's own domain code MEANS — is a "blocked product" sync failure a conflict? a validation? This package has no way to guess that generically, and a wrong generic guess would be worse than leaving the seam open.

License

MIT