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

@cratebase/extras

v0.2.0

Published

Optional client extensions for Cratebase endpoints the official `pocketbase` SDK has no first-class surface for (vector search, MCP tool schemas, the LLM chat gateway, a client-side presence pattern). Bolts onto an existing PocketBase client instance rath

Readme

@cratebase/extras

Optional client extensions for the handful of Cratebase endpoints the official pocketbase npm SDK has no first-class surface for: vector search, MCP tool schemas, and the LLM chat gateway.

This surface is available two ways. @cratebase/client, Cratebase's first-party SDK, has it built in — no extra package needed, and it's the default recommendation for new projects. This package, @cratebase/extras, is the other way: for projects that stay on the official pocketbase SDK, it bolts the same methods on top of an existing PocketBase client instance without replacing or forking it — see docs/superpowers/specs/2026-09-04-value-add-strategy.md §1 for the original reasoning (superseded as the default path, still valid for this audience). A project that only wants PocketBase-parity behavior never installs @cratebase/extras and pays zero cost. A project that wants the extra surface on the pocketbase SDK adds one dependency on top of the client it already has, and passes that existing client instance in — nothing here constructs its own connection or duplicates auth/session handling.

bun add pocketbase @cratebase/extras

pocketbase is a peer dependency (pinned to ^0.28.0, the version this package was built and verified against) — you bring your own PocketBase instance, already authenticated however your app normally authenticates it.

Every helper is available two ways: as a standalone function (nearestTo, getToolSchema(s), chat) taking the PocketBase client as its first argument, or bundled as methods on a CratebaseExtras facade that holds the client for you. Pick whichever fits your call sites; they call the exact same code.

import PocketBase from "pocketbase";
import { CratebaseExtras } from "@cratebase/extras";

const pb = new PocketBase("http://127.0.0.1:8090");
await pb.collection("_superusers").authWithPassword(email, password);

const extras = new CratebaseExtras(pb);

Vector search — nearestTo

Wraps ?nearestTo= on the records list endpoint: application-side cosine-similarity ranking over a vector field (FieldKind::Vector in crates/core/src/field.rs, ranking logic in crates/server/src/embeddings.rs). There is no native ANN index behind this in this pass — it's a Rust-side rank over the rows your listRule/?filter= would already return, capped at 20,000 candidate rows server-side. Fine for app-scale collections (thousands to low millions of vectors); not a substitute for a dedicated vector DB at massive scale.

to is either the query vector itself (a number[] matching the field's configured dimensions) or another record's id — whose own value on field becomes the query vector, subject to that record's viewRule.

import { nearestTo } from "@cratebase/extras";

// by an explicit query vector, scoped to one document with `filter`
const results = await nearestTo(pb, "chunks", "embedding", [0.12, -0.4, 0.91, /* ... */], {
  limit: 5,
  filter: 'docId = "abc123"',
});

// "more like this record" — reuse an existing row's own vector
const similar = await nearestTo(pb, "chunks", "embedding", "RECORD_ID_HERE", { limit: 5 });

// via the facade
const viaExtras = await extras.nearestTo("chunks", "embedding", [0.12, -0.4, 0.91], { limit: 5 });

results is an ordinary PocketBase ListResult<T> — same shape getList() returns, just ranked by similarity instead of sort, and always exactly one page.

MCP tool schemas — getToolSchema / getToolSchemas

Wraps GET /api/collections/{name}/tool-schema (crates/server/src/routes/tool_schema.rs): the same JSON-Schema / OpenAI-function-calling-shaped description of a collection that the MCP server at GET/POST /api/mcp (crates/server/src/mcp.rs) uses to build its own tool definitions — useful when you want to hand an LLM a create_<collection>-style structured-output target without speaking MCP/JSON-RPC at all. Superuser-gated, same tier as reading a collection's full schema.

import { getToolSchema, getToolSchemas } from "@cratebase/extras";

const postsSchema = await getToolSchema(pb, "posts");
// {
//   name: "posts",
//   description: "The 'posts' base collection (3 fields).",
//   parameters: { type: "object", properties: { title: {...}, body: {...}, tags: {...} }, required: ["title"] }
// }

// build a chat-completions `tools` array directly
const tools = await getToolSchemas(pb, ["docs", "chunks"]);

// via the facade
const viaExtras = await extras.getToolSchema("posts");

LLM chat gateway — chat

Wraps POST /api/llm/chat (crates/server/src/routes/llm.rs): one chat completion against the provider configured in the server's settings.llm. The HTTP call only resolves once the provider finishes generating — incremental chunks arrive over your PocketBase client's existing GET /api/realtime SSE connection as llm_chunk frames, which is why streaming is exposed as an onDelta callback rather than an async-iterable response body.

import { chat } from "@cratebase/extras";

const result = await chat(pb, [{ role: "user", content: "Summarize the onboarding doc." }], {
  // persist {prompt, response, model} to this collection, subject to its own createRule
  collection: "messages",
  onDelta: (delta) => process.stdout.write(delta),
  onError: (message) => console.error("llm stream error:", message),
});

console.log(result.reply, result.promptTokens, result.completionTokens, result.record);

// via the facade, non-streamed (no realtime connection opened)
const plain = await extras.chat([{ role: "user", content: "hi" }]);

onDelta/onError require a runtime with a global EventSource (any browser out of the box; Node needs a polyfill such as the eventsource package assigned to globalThis.EventSource) — this is the same requirement pb.realtime.subscribe already has for any other realtime use, not something this package adds. A plain, non-streamed chat() call (omitting both callbacks) has no such requirement.

Presence — trackPresence

"Who's online right now", built entirely on things Cratebase already ships: an ordinary collection plus GET /api/realtime (crates/server/src/realtime.rs). There is no server-side presence feature to enable — you define a presence collection in your own schema, and trackPresence does the client-side heartbeat-and-observe pattern on top of it: it keeps your own row's timestamp fresh on an interval, subscribes to every other row's changes, and maintains a local online set that sweeps out any peer whose heartbeat has gone stale (closed tab, dead network — no clean "offline" event ever arrives for those).

Create a presence collection first (e.g. via the dashboard or the Admin API) with fields matching your own presence data — a minimal shape:

{
  "name": "presence",
  "type": "base",
  "fields": [
    { "name": "userRef", "type": "text", "required": true },
    { "name": "status", "type": "text" },
    { "name": "lastSeenAt", "type": "date" }
  ],
  "listRule": "@request.auth.id != ''",
  "viewRule": "@request.auth.id != ''",
  "createRule": "@request.auth.id != '' && userRef = @request.auth.id",
  "updateRule": "@request.auth.id != '' && userRef = @request.auth.id",
  "deleteRule": "@request.auth.id != '' && userRef = @request.auth.id"
}
import PocketBase from "pocketbase";
import { trackPresence } from "@cratebase/extras";

const pb = new PocketBase("http://127.0.0.1:8090");
await pb.collection("users").authWithPassword(email, password);

const presence = await trackPresence(pb, "presence", {
  userRef: pb.authStore.record!.id,
  status: "online",
});

const unsubscribe = presence.subscribe((online) => {
  console.log(`${online.size} peers online:`, [...online]);
});

// later, e.g. on unmount or page unload:
unsubscribe();
await presence.stop();

// via the facade
const viaExtras = await extras.trackPresence("presence", { userRef: pb.authStore.record!.id });

trackPresence's third argument upserts: pass { id, ...fields } to keep refreshing a row you already created, or fields with no id to create one on the first call and reuse it for every heartbeat after. heartbeatMs (default 20s), staleMs (default 3 * heartbeatMs) and lastSeenField (default "lastSeenAt") in the options object tune the timing to your schema and how quickly a departed peer should read as offline.

Versioning

This package versions independently from the cratebase server binary. It's an optional client-side add-on with its own semver lifecycle — the pocketbase peer dependency it targets, not the server release cadence, is what actually constrains breaking changes here. Publishing is triggered by pushing a extras-v<version> tag (see .github/workflows/publish-extras.yml), kept deliberately separate from the server's own v* release tags so a server release never forces an unrelated extras publish, and an extras hotfix never waits on one.

Development

bun install
bunx tsc --noEmit   # typecheck
bun run build        # emit dist/