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

brussle

v1.9.0

Published

Brussle SDK for TypeScript. Brussle is the database for AI columns: define a column, keep it current as records change, record outcomes, and read the calibrated number with its 95% interval.

Readme

brussle (TypeScript)

Brussle calibrates AI decisions on the outcomes that you record, and judges each one again when a document it read changes. A decision, a judgment, is an AI model's typed answer about a document, and it can read other documents. For example, Which open invoice does this payment settle? reads the payment beside its customer's open invoices. When an invoice changes, Brussle judges the payments that read it again. Each answer says whether it is current, and records the judgment version and engine version behind it. From the outcomes that you record, Brussle fits a correction to each answer's number, and uses it only when it does better than the engine on held-out outcomes. If an answer reads only its own document, a column in your own database may be enough.

This package is the TypeScript client for the Brussle API.

import { Client } from "brussle";

const db = new Client({ apiKey: process.env.BRUSSLE_API_KEY! });
const ns = db.namespace("acme/prod/tenant_123");

// A judgment on unmatched payments that reads the open invoices in the same block.
await ns.judgments.create({
  from_starter: "payments.settles",
  paths: {
    kind: "attributes.kind",
    payment_kind: "payment",
    invoice_kind: "invoice",
    status: "attributes.status",
    unmatched: "unmatched",
    open: "open",
    block: "attributes.block",
    payment_amount: "state.amount",
    reference: "state.reference",
    invoice_number: "state.number",
    invoice_amount: "state.amount",
  },
  engine: { name: "jev", version: "current" },
  freshness: { policy: "on_change" },
  confirm: true,
});

// An open invoice, and a payment in the same block.
await ns.write({
  upsert: [
    { id: "inv_1002", attributes: { kind: "invoice", status: "open", block: "northwind-gbp" }, state: { number: "INV-1002", amount: 1200 } },
    { id: "pay_1", attributes: { kind: "payment", status: "unmatched", block: "northwind-gbp" }, state: { amount: 1200, reference: "Northwind INV-1002" } },
  ],
});

// A change to the block's open invoices judges its payments again. Query the payments that wait for a person,
// with a current answer only.
const { rows } = await ns.query({
  filters: ["answers.settles.thresholds.unmatched", "Eq", true],
  answers: "fresh_only",
  include: { answers: ["settles"] },
});

The quickstart runs this example step by step. Relations lists what else a judgment can read.

The example uses Jev current. A judgment can name any engine version that db.engines.list() lists as active, with its limits: today jev current and gpt-6-luna current. Both are unpinned, so each answer records the epoch of the model behind it. They differ in two ways that you choose by: only gpt-6-luna reads images, and each row kept on gpt-6-luna counts 5 times its size class on the bill, against 2 times on jev. With your own key for a provider, a judgment can instead name one of its models as <provider>/<model id> at current, once db.engines.list() lists that provider as active; none is yet. Store the key with db.credentials.set("openai", { key, namespace }). Leave engine out and Brussle picks the namespace's default_engine, else your key's default model, else the included engine. See engines. The API also names pplx-decider, which has no active version yet, so a judgment that names it is refused with engine_version_unavailable.

A document keeps its images in images, beside attributes and state. Each name holds one image or a list of images. A write sends a PNG, JPEG or WebP as a data URL, and the document then holds a reference to the stored image:

await ns.write({
  upsert: [{
    id: "claim_1",
    attributes: { kind: "claim" },
    state: { description: "Rear door dented." },
    images: { photo: "data:image/jpeg;base64,/9j/4AAQ..." },
  }],
});
// images.photo is now { digest: "sha256:e0db...", type: "image/jpeg", width: 1440, height: 960 }
const { bytes, type } = await ns.image("sha256:e0db...");

To keep an image when you write the document again, send the reference you read. ns.image returns the stored bytes, which are exactly what an engine that reads images reads. A judgment reads an image when its context recipe names it as images.<name>, on an engine whose limits in db.engines.list() have images. Any other engine gets the image's digest in its place, and the create's warnings say so. This judgment reads the photo on gpt-6-luna:

await ns.judgments.create({
  name: "damage_matches",
  type: "bool",
  applies_to: { "attributes.kind": "claim" },
  question: "Does the photo show damage that matches the description?",
  context: { fields: ["state.description", "images.photo"] },
  engine: { name: "gpt-6-luna", version: "current" },
  freshness: { policy: "on_change" },
});

ns.eraseImage(digest) erases an image: its bytes are deleted, and documents and evaluations keep its reference, marked erased: true. It returns { digest, erased_at }, and erasing again returns the same. Reading the image then throws image_erased (410), and a write that sends its bytes again is refused with image_erased.

See Images.

To load many documents, importDocuments takes any iterable or async iterable and writes it in batches, 4 at a time, with retries:

const { documents, failures } = await ns.importDocuments(records(), { concurrency: 4 });

How the client works:

  • Methods mirror the API's routes, in camelCase.
  • Every route that changes something takes { idempotencyKey } as its last argument. A call that would not be safe to repeat without one, such as a create, gets a key the SDK generates when you pass none, the same on each retry of that call.
  • Calls are retried after network errors, 502, 503 and 504, and after a 429 or 503 with a Retry-After of at most 60 s, 2 times by default (maxRetries). A 500 is retried only for reads, writes and the other calls that are safe to repeat without a key. A call that would create a namespace past the organization's 1,000 a minute gets a 429 with Retry-After until the next minute, so it is retried; raise maxRetries to create many namespaces at once. A 413 too_large, such as a write past a namespace's size or attribute-name limit, is never retried.
  • A 429 past a rate limit says which in details.rate_limit: per_key, or per_org for all your organization's API keys together. The per-organization limit counts every process and key, so a retry can meet another 429 because of your other clients. See limits.
  • ns.judgments.create, update and backfill can take more than 20 s. The API then answers with a job, and the call waits for it: it checks the job every 1 to 10 s, or after the API's Retry-After, for up to waitTimeoutMs (10 minutes by default). It then returns or throws what the call would have. After the timeout it throws JobWaitTimeoutError, whose job you can pass to db.jobs.wait. With { wait: false } the call returns { accepted: false, result } or { accepted: true, job }.
  • Request and response bodies keep the API's field names, so JSON from the docs pastes in. One exception: ns.outcomes.append takes the array of outcomes, not the whole request body.
  • Answers and definitions are discriminated unions on type. Filters are typed tuples.
  • Errors throw ApiError with the API's code and details. The code is unknown for an error response that is not the API's JSON, for example a proxy's error page, and for a success whose body is not JSON (for ns.image, not a PNG, JPEG or WebP).
  • A call that your organization's plan does not include throws plan_required (402). details.required_plan names the plan that includes it.

See the SDK reference.

The source, its issues and the changelog are at github.com/brussle/sdk.