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

@vibedout/store-sdk

v0.2.0

Published

Typed clients for a shop's two APIs, generated from their OpenAPI 3.1 documents: createStoreClient for a storefront (the catalogue, a cart, the checkout, an order and its tracking, with the store's publishable key) and createServerClient for a server (ord

Readme

@vibedout/store-sdk

Typed clients for the two APIs a vibedout shop serves, generated from their OpenAPI 3.1 documents:

| Client | API | Credential | For | |---|---|---|---| | createStoreClient({ baseUrl, publishableKey }) or createStoreClient({ baseUrl, secretKey }) | /api/store (the store feature hosts it; region, catalog, pricing, checkout, fulfilment add to it) | the store's publishable key pk_... (safe in a browser, bound to the origins it lists), or from a server its secret key sk_... (refused where there is a page, and by the shop from any browser) | a storefront or an app: the catalogue, a cart, the checkout, an order and its tracking | | createServerClient({ baseUrl, apiKey }) | /api/v1 (the connectors feature hosts it; catalog, checkout, fulfilment, inventory add to it) | a secret key vsk_... with the permissions it needs | a server: orders, stock, the fulfilment queue, events and hooks |

No dependency but fetch (the platform's JSON transport, @vibedout/api-client-core, is bundled into the build). It runs in a browser, a Worker, Node 20+ and Deno. Every type the documents name is exported (import type { Order } from "@vibedout/store-sdk/store"). The package is private in this repository for now (@vibedout/store-sdk 0.2.0 is not on npm); the documents it is made from are the contract.

A storefront buying

import { createStoreClient, ApiError } from "@vibedout/store-sdk";

const shop = createStoreClient({ baseUrl: "https://tienda.example", publishableKey: "pk_..." });

const { rows } = await shop.listProducts({ query: { limit: 24, sort: "title:asc" } });
const product = await shop.getProduct({ path: { handle: "etiqueta-nfc" } });

// A cart is a capability: every answer carries a fresh `cartToken`, and the next call sends it back.
let { cartToken } = await shop.createCart();
({ cartToken } = await shop.addCartLine({ cartToken, body: { source: "catalog.variant", ref: product.variants[0].id, quantity: 2 } }));
const quote = await shop.quoteCart({ cartToken, body: { destination: { country: "MX", postalCode: "06600" }, shippingRateId: "standard" } });
if (quote.totalMinor === null) throw new Error(`not ready to check out: ${quote.issues.join(", ")}`);   // e.g. a shipping rate is still to be chosen

// The server prices the cart; the buyer is charged only the total they were shown.
const order = await shop.completeCart({
  cartToken,
  headers: { "idempotency-key": crypto.randomUUID() },   // a UUID (or 32+ random characters): the same key again is the same order, while it waits for its payment
  // returnUrl: "https://tienda.example/gracias",         // where the provider sends the buyer after paying: an address at an origin the key lists
  body: { email: "[email protected]", address, shippingRateId: "standard", expectedTotalMinor: quote.totalMinor },
});
location.href = order.redirectUrl!;                      // the payment provider's page

// Later (the order's token is the buyer's way back; keep it): the order is paid by the payment's own event, so poll.
const mine = await shop.getOrder({ path: { orderId: order.orderId }, orderToken: order.orderToken });
const tracking = await shop.getOrderTracking({ path: { orderId: order.orderId }, orderToken: order.orderToken });

An error answer is thrown as ApiError (status, code, message, details, and reason when the API gives one: order_token_invalid, origin_not_allowed, over_max_quantity, ...). A refusal of the key itself (a revoked key, an origin the key does not list, a limit) is readable from a browser when the page's origin is one the shop lists, so a storefront with a revoked key gets unauthorized with reason: "key_invalid" and not a network_error (unless the revoked key was the only one that listed the page's origin: then no active key vouches for the page, the 401 carries no CORS headers and the browser still hides it; make a new key with the same origins); network_error (status 0) is only a request that got no readable answer.

Branching on a refusal

Each operation says what it can be refused with, and refusalOf(operationId, error) returns the documented refusal or undefined, typed by the operation:

import { refusalOf } from "@vibedout/store-sdk";

try {
  await shop.completeCart({ cartToken, body, headers: { "idempotency-key": key } });
} catch (error) {
  const refusal = refusalOf("completeCart", error);
  if (refusal === undefined) throw error;                                        // not one this operation lists (offline, a fault, a newer reason)
  switch (refusal.reason) {
    case "price_changed": showTheNewTotal(refusal.error.details); break;         // the new quote is in details.quote
    case "sold_out": case "over_max_quantity": fixTheCart(); break;
    case "idempotency_key_reused": key = crypto.randomUUID(); break;
    case "guest_checkout_off": tellTheBuyerTheShopIsClosed(); break;
    case "key_invalid": case "key_missing": reloadWithTheRightKey(); break;     // the key itself, from any operation
    default: throw error;                                                        // a reason this page does not handle yet
  }
}

refusalOf is generated from the x-vibedout-errors of each 4xx/5xx response of the document, so it moves with the shop. To have the compiler ask for a new reason, assign refusal.reason to a never in the default branch (or index a Record<NonNullable<Refusals["completeCart"]["reason"]>, ...>, as test/refusals.test.ts does): a reason added to the document then stops the build until it is handled. A refusal that lists no reason (a validation error) has reason === undefined. serverRefusalOf does the same for the server client.

A server calling the storefront API, and a server reading the shop

const shop = createStoreClient({ baseUrl: "https://tienda.example", secretKey: process.env.SHOP_SECRET_KEY! });   // sk_...: made on the Store page, shown once

A secret key is for a storefront rendered on a server, a backend for an app, a script: it never goes in a page. The client refuses to be made where there is a document, and the shop refuses a secret key that arrives with an Origin or Sec-Fetch-Site header (403 secret_key_in_browser). It names a sales channel like a publishable key and has no permissions of its own. Reading the shop's data (orders, stock, hooks) is the server API, with a vsk_ key and the permissions it was made with:

import { createServerClient } from "@vibedout/store-sdk";

const api = createServerClient({ baseUrl: "https://tienda.example", apiKey: process.env.SHOP_API_KEY! });   // never in a browser
const { data, next } = await api.listOrders({ query: { status: "fulfilled", limit: 50 } });             // orders:read
const order = await api.getOrderDetail({ path: { id: data[0].id } });                                  // + the delivery summary
const way = await api.getOrderFulfilment({ path: { id: order.id } });                                  // fulfilment:read: address, parcel, history, changes
const stock = await api.listStock({ query: { sku: "TAG-1,CARD-1" } });                                 // inventory:read
const hook = await api.createHook({ body: { target_url: "https://example.com/hook", events: ["order.*"] } });

A key holds the permissions it was made with (the Connectors page of the admin area): a call it has no permission for is 403 forbidden, a made-up, revoked or missing key is 401. The webhooks of the server document list every event the shop sends (order.paid, order.shipped, ...) by name; the payloads are signed with Standard Webhooks and carry ids and amounts, never an address.

The documents are the contract

openapi/store.json and openapi/server.json are committed reference documents: what a reference shop answers at GET /api/store/openapi.json and GET /api/v1/openapi.json, which each feature writes from the Zod schemas its own routes validate with (@vibedout/api-contract). They are OpenAPI 3.1 (JSON Schema 2020-12), have an operationId on every operation and a named component for every shape, and use x-order-token / x-cart-token / x-publishable-api-key as apiKey security schemes and a bearer scheme for the server key. Because a generator reads only the document, another language's client comes from the same files: point a generator at openapi/store.json (the Flutter apps' Dart client is the intended second consumer) or at a shop's own /api/store/openapi.json, which also lists what that shop has installed (a shop with no cart has no cart operations). No Dart client was generated or run here; the documents were validated with the checks below, which is what a Dart generator needs to start from. Generators differ in how much of 3.1 they read (nullable types are written type: ["string", "null"]): if one does not, down-convert the document first.

Checks on a document (run on the committed ones and on what the reference shop served):

npx @redocly/cli lint openapi/store.json openapi/server.json                  # structure, refs, operationIds, security
npx @seriousme/openapi-schema-validator openapi/store.json                    # the OpenAPI 3.1 JSON Schema, strictly

Regenerating, and the stale check

pnpm --filter @vibedout/store-sdk generate    # src/generated/{store,server}.ts from the two documents
pnpm --filter @vibedout/store-sdk check       # exits 1 when a generated file is not what the documents make (also part of the root `pnpm lint` and `pnpm sdk:check`)

The generator (scripts/generate.mjs) has no dependency. It reads servers, components.schemas, securitySchemes, webhooks, and each operation's id, parameters, JSON body, first 2xx JSON answer and security; anything else it meets is an error, not a guess, so a generated client never silently drops a parameter.

A change to an operation in a feature's store-api.ts / api.ts changes the shop's document; bring the committed documents and the client up to date in this order:

  1. Start a reference shop: a cf-react-hono-d1 project with vo blocks add store region catalog pricing inventory checkout fulfilment connectors (checkout --option cart=yes, and what they need: payments, shipping, users, admin). Run it (wrangler dev) and make a publishable key (Store page) and a server key holding events:read, hooks:read, hooks:write, catalog:read, orders:read, inventory:read, fulfilment:read (Connectors page), so the server document lists every feature's operations.
  2. STORE_SDK_API_KEY=vsk_... pnpm --filter @vibedout/store-sdk refresh -- --base http://localhost:8787 --publishable-key pk_... writes the two documents in a canonical form (sorted keys, the shop's name taken out of the title, so another shop with the same blocks makes no diff). --check compares only. The key is read from the environment and never printed.
  3. pnpm --filter @vibedout/store-sdk generate, review the diff, run pnpm --filter @vibedout/store-sdk test, commit the documents and the generated files together.

Tests

pnpm --filter @vibedout/store-sdk test: the documents (OpenAPI 3.1, an operationId on every operation, every {name} a path parameter, every $ref resolves, a closed list of JSON Schema keywords, the credentials and the buyer's proofs as security schemes, the order transitions among the events a hook can follow, the server API reading only besides its hooks), the generator (names, types, nullable, enum and union, what it does not understand is an error, --check fails on a stale or hand-edited file and says how to fix it), the clients against a recording fetch (where the path, query, headers, body, key and proofs go; what is refused before anything is sent; how an error answer becomes ApiError), and the package (MIT, no dependency, no @vibedout/* import left in the build). The proof that the documents tell the truth is the live run in the section's run log: this SDK drives a reference shop on wrangler dev (a storefront buying with a mock payment provider, a server reading the order, the stock and the tracking) and every JSON answer is validated against the schema its document gives it (Ajv, JSON Schema 2020-12).

What it does not do

  • It does not make a shop's payment page, sign a webhook or verify one: verify a delivery with any Standard Webhooks library and the hook's secret.
  • The storefront buyer is always a guest (an order is opened by its token, a cart by its cart token); a signed-in customer's session is not part of this API yet.
  • The payload of each webhook event is not typed in the document (the bus has the event's type, not a runtime schema).

Versions

  • 0.2.0 (50.17-T52, T51, T38): refusalOf / serverRefusalOf over the x-vibedout-errors of the documents, createStoreClient({ secretKey }) (refused where there is a document), and the proofs read across alternative security requirements. Additive; a client made with 0.1.0 options works.
  • 0.1.0: the two clients generated from the two documents.