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

@odla-ai/db

v0.6.4

Published

Official odla-db admin, realtime, and React clients with transaction and schema tooling.

Downloads

1,968

Readme

@odla-ai/db

⚠️ Early access — pre-1.0. Agents work from bounded runbooks; humans approve credentials, production changes, releases, and merges. APIs and exact package availability can change. Review the documented guarantees and limitations; this software is MIT-licensed and provided without warranty.

The official clients for odla-db — a realtime, graph-shaped database on Cloudflare. The package has separate entry points for trusted administration, permission-governed realtime applications, and React bindings:

  • @odla-ai/db — backend/admin HTTP client and schema tooling.

  • @odla-ai/db/client — WebSocket subscriptions, optimistic writes, presence, and end-user authentication.

  • @odla-ai/db/reactOdlaProvider, useQuery, and useTransact.

  • Isomorphic transport — runs in Node 20+, Cloudflare Workers, and the browser. The root admin entry still belongs only in trusted backends because its full application key bypasses rules.

  • Self-contained — zero runtime dependencies; the wire protocol, the tx builder, and the schema builder are bundled in.

  • Graph-native — entities are nodes, typed links are edges, and Lookup refs give you upsert-by-natural-key (MERGE) for re-ingestion-safe writes.

  • Default-deny permissions — end-users (and rules-scoped credentials) can't read or write a namespace until its CEL rules are set; a missing namespace or action means "no". Full app API keys bypass rules (trusted backends only).

  • Provenance on every node — the server maintains $createdAt / $createdBy / $updatedAt / $updatedBy from the verified writer identity. $-prefixed attrs are reserved: client writes to them are rejected.

Install

npm i @odla-ai/db

Use

import { initAdmin, tx } from "@odla-ai/db";

const db = initAdmin({
  appId: "myapp",
  adminToken: process.env.ODLA_SK!,          // an app API key: odla_sk_...
  endpoint: process.env.ODLA_URL!,           // your odla-db worker, e.g. https://<worker>.workers.dev
});

// write
await db.transact(
  tx.notes[crypto.randomUUID()].update({ text: "hi", createdAt: Date.now() }),
);

// read
const { notes } = await db.query({ notes: { $: { order: { createdAt: "desc" } } } });

Every query node returns at most 100 rows unless it sets limit; the maximum is 1,000. Queries are also bounded to six relation levels, 32 nodes, 100 where nodes, a 512-character search string, and a 10,000-row candidate scan. Add an indexed filter and paginate instead of relying on an unbounded materialization. Encoded results and aggregate/query materialization are capped at 1 MB; aggregates have the same 10,000-row scan guard. $like is deliberately indexable and case-sensitive: use an exact literal ("Ada") or one trailing wildcard ("Ada%"). Leading/interior % and _ patterns are rejected; use full-text search for substring/token search. Per-entity full-text documents index at most 1 MB of text fields in attribute-name order.

For a browser or end-user application, use a signed-in user token. Never expose an odla_sk_ application key:

import { init } from "@odla-ai/db/client";

const db = init({
  appId: "myapp",
  endpoint: "wss://db.odla.ai",
  getToken: () => auth.getToken(),
});

const unsubscribe = db.subscribeQuery({ notes: {} }, ({ notes }) => render(notes));
import { OdlaProvider, useQuery } from "@odla-ai/db/react";

Getting a token (for agents) — no secret required

You need an odla-db credential to read/write. An agent should never be handed the platform admin secret. Instead, do a device-authorization handshake at the odla platform: name the existing account by email, show that human a short code, and wait for the same signed-in account to explicitly review and approve it in odla.ai/studio. The email is a non-secret identity hint; never ask for a password, Clerk session, or other human credential. You get back a tracked, revocable, ~24-hour developer token scoped to that account's apps. If it ever stops working, call requestToken again. The delegated token never inherits the approver's platform-admin role, even when an administrator approves it.

import { requestToken, initAdmin } from "@odla-ai/db";

const platform = "https://odla.ai";
const endpoint = "https://db.odla.ai";

const { token } = await requestToken({
  endpoint: platform,
  email: process.env.ODLA_USER_EMAIL!,
  onCode: ({ userCode, verificationUriComplete }) =>
    console.log(`Review ${userCode}: ${verificationUriComplete ?? "https://odla.ai/studio"}`),
});

// `token` (odla_dev_…) can create apps, push schemas, and read/write ITS OWN apps.
const db = initAdmin({ appId: "my-app", adminToken: token, endpoint });

Under the hood: POST https://odla.ai/handshake with { email, ... } → show userCode → the named user signs in and explicitly claims that exact code → poll POST https://odla.ai/handshake/poll with the returned deviceCode until the approval is collected once. Unknown and never-signed-in accounts do not create a claimable request, and their public response is deliberately indistinguishable from a valid start to prevent account enumeration. Only one claimed pending or approved-but-uncollected request may be active per account. The constants above are the hosted odla defaults. For a self-hosted or alternate deployment, supply its platform and database endpoints through configuration rather than hard-coding either URL in application logic.

Permission rules (required before end-users can touch data)

odla-db is default-deny: until you install rules, end-users see nothing and every end-user write is rejected with permission_denied. Install per-namespace CEL rules with an operator/dev token (this replaces the app's rule set):

await fetch(`${endpoint}/app/${appId}/admin/rules`, {
  method: "POST",
  headers: { authorization: `Bearer ${token}`, "content-type": "application/json" },
  body: JSON.stringify({
    notes: {
      view: "auth.id == data.ownerId",     // read own rows
      create: "auth.id == data.ownerId",   // may only create rows they own
      update: "auth.id == data.ownerId",
      delete: "false",                     // nobody deletes
    },
  }),
});

Rule context: auth.id/email/signedIn/entitlements/user/claims, auth.kind ("user" | "agent" | "admin"), auth.agent ({ id, label } for scoped keys), data.<field>, newData.<field>, ref('label.field'). Reserved namespaces default to own-rows-only reads ($users, $entitlements, $subscriptions) or fully closed ($files).

Scoped keys — the right credential for an agent

A full odla_sk_ key bypasses rules. When minting a key for an agent, pass scopes so it is governed by the rules instead (auth.kind == "agent" in CEL), optionally read-only and/or restricted to specific namespaces. Once a key has a scope object, vault access is also default-deny; list the exact secret names it needs in secrets (or deliberately grant "*"):

await fetch(`${endpoint}/admin/apps/${appId}/keys`, {
  method: "POST",
  headers: { authorization: `Bearer ${operatorToken}`, "content-type": "application/json" },
  body: JSON.stringify({
    label: "kanban-bot",
    scopes: {
      mode: "rules",
      namespaces: ["cards", "columns"],
      secrets: ["anthropic_api_key"],
    },
  }),
});

Scoped credentials are data-plane only: they can never edit rules, mint keys, or act as operators. A rules/read-only/namespaced key without secrets receives 403 missing_capability from db.secrets.get().

Graph writes — upsert by natural key + link

Lookup refs ({ ns, attr, value }) resolve to an existing entity by a unique attribute, or create one — so re-discovering the same node is idempotent. Emit raw Op literals for full control (node updates first, then links):

await db.transact([
  { t: "update", ns: "company", id: { ns: "company", attr: "slug", value: "acme" }, attrs: { name: "Acme" } },
  { t: "update", ns: "person",  id: { ns: "person",  attr: "slug", value: "ada"  }, attrs: { name: "Ada Lovelace" } },
  { t: "link",   ns: "company", id: { ns: "company", attr: "slug", value: "acme" }, label: "founded_by",
    target: { ns: "person", attr: "slug", value: "ada" } },
], { mutationId: "ingest:acme-foundedby-ada" });   // stable id => exactly-once

Author & push a schema

import { i } from "@odla-ai/db";

const schema = i.schema({
  entities: {
    company: i.entity({ slug: i.string().unique(), name: i.string() }),
    person:  i.entity({ slug: i.string().unique(), name: i.string() }),
  },
  links: {
    founded: {
      forward: { on: "company", has: "many", label: "founded_by" },
      reverse: { on: "person",  has: "many", label: "founded" },
    },
  },
});

await fetch(`${endpoint}/app/${appId}/schema`, {
  method: "POST",
  headers: { authorization: `Bearer ${odlaSk}`, "content-type": "application/json" },
  body: JSON.stringify({ schema: schema.serialize() }),
});

Porting relational code — semantics that differ from SQL

Field notes from porting a production Postgres app (a membership flow) onto odla-db. Each of these is a silent behavior difference; design for them up front rather than discovering them in production:

  • Entity ids are not attributes. where: { id } matches nothing. If you query rows by id (every SELECT ... WHERE id = $1 port does), mirror the id as a unique attr on each row: { t: "update", ns, id: rowId, attrs: { id: rowId, ... } }. Query results are unchanged — hydration returns the same id either way.

  • Null depends on the declared type. JSON attributes can preserve JSON null; a typed scalar attribute rejects it in strict schema mode. Omit an optional scalar on write when absence is the intended state.

  • Always pass order on list queries. Unordered results sort lexicographically by entity id: creation order for uuidv7() ids, arbitrary for anything else.

  • Pagination is bounded. Omitted limit means 100; explicit limits may be 0–1,000 and offsets 0–100,000. Each nested relation node is bounded too.

  • Realtime state is bounded. One socket may retain 32 subscriptions, and its total hibernation state (identity, subscriptions, and presence) must fit 16 KiB. Excess operations fail as subscription_limit or session_state_too_large before registering state. The server retains at most 4 MB of prior diff snapshots per app Durable Object; subscriptions over that budget continue with full query-result snapshots instead of diffs.

  • Wire bodies are bounded. JSON HTTP bodies are stream-read; both HTTP bodies and WebSocket messages cap at 1.1 MB (request_too_large / message_too_large). Query results have the stricter 1 MB encoded cap.

  • Uniqueness is single-attr. A composite SQL unique becomes a derived unique attr (for example group_email: "<groupId>|<email>"); a violation aborts the whole transact with unique_violation, exactly like an ON CONFLICT abort in a SQL transaction.

  • A multi-op transact is atomic (one Durable Object transaction), so batch a change and its audit row together. There is no SELECT ... FOR UPDATE: a check between a query and a transact can race. Put idempotency where it matters instead — a stable mutationId gives exactly-once semantics, and INSERT ... ON CONFLICT DO NOTHING with a rowCount check is the returned duplicate flag:

    const { duplicate } = await db.transact(ops, { mutationId: `stripe_evt:${event.id}` });
    const fresh = !duplicate; // first delivery: this call did the write
  • Aggregates are one-shot: await db.aggregate("applications", { count: true }, { where: { status: "pending" } }){ count }.

  • Health probes: GET /app/:id/schema (Bearer: the API key) is the cheap connectivity and schema-presence check — compare Object.keys(schema.entities) against the namespaces you expect.

  • Schema pushes validate compatibility. Declared namespaces are strict; unsafe type, required-field, uniqueness, link, and removal changes are rejected before metadata changes. See the database schema contract for strict/schemaless behavior and the accepted migration envelope.

API surface

  • initAdmin(opts) (legacy alias: root init) → HTTP administration client; its application key bypasses rules and belongs only in trusted backends.
  • @odla-ai/db/client → realtime init, subscriptions, optimistic writes, persistence, aggregate queries, and rooms under an end-user token.
  • @odla-ai/db/reactOdlaProvider, useDb, useQuery, useTransact. transact resolves to { txId, duplicate } (TransactResult).
  • tx, flattenTx, TxChain, uuidv7, PROTOCOL_VERSION.
  • OdlaError — stable code, optional HTTP status/detail/requestId, and retryable; realtime clients surface connection errors through onError.
  • i, OdlaSchema, Attr — schema builder.
  • All wire types: Op, EntityRef, Lookup, InstaQLQuery, QueryResult, Entity, Value, Json, SerializedSchema, …

0.6 migration notes

  • Queries that omitted limit now return at most 100 rows; paginate explicitly if you previously relied on full-namespace reads. The maximum limit is 1,000.
  • Oversized query shapes and scans fail with stable query_* error codes.
  • $like now accepts only case-sensitive exact or trailing-% prefix patterns; replace substring patterns with full-text search.
  • Realtime sockets cap active subscriptions at 32 and combined hibernation state at 16 KiB. Diff retention exhaustion falls back to full snapshots.
  • JSON wire bodies over 1.1 MB and encoded query results over 1 MB are rejected.
  • HTTP and WebSocket peers negotiate protocol v1; declared mismatches fail with protocol_version instead of being decoded as an unknown response.
  • SDK failures are OdlaError instances. Existing message matching should move to error.code; server detail is preserved when it is JSON-safe.
  • Device authorization uses handshake_denied, handshake_expired, handshake_timeout, aborted, network_error, and protocol_version; these are stable codes, not strings to recover from the message.