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

@zerostackai/sdk

v0.1.2

Published

ZeroStack SDK — one authorized, isomorphic interface over data, storage, and LLM calls.

Readme

@zerostackai/sdk

One authorized, isomorphic interface over your app's database, file storage, and LLM — for apps generated by ZeroStack.

createZeroStack(config) returns a single client with four surfaces:

| Surface | What it talks to | Client-side (browser / React Native) | Server-side (Node / Cloudflare Workers) | | --- | --- | --- | --- | | auth | ZeroStack edge (/auth) | email + password → JWT the Data API trusts | n/a (server holds a token already) | | db | Neon Data API (PostgREST-compatible) | signed-in user's JWT → RLS enforced | Same, or a scoped API key | | storage | Cloudflare R2 | via the edge presign endpoint (no secrets in bundle) | via direct S3 credentials | | llm | DeepSeek / OpenAI-compatible | via the edge LLM proxy (company key stays server-side) | direct provider key |

Auth is built in. Configure auth and zs.auth.signIn(email, password) returns a token the Neon Data API accepts — zs.db then uses it automatically. No Neon Auth / Stack Auth setup needed.

The same code runs in both environments — you just pass the transports that make sense for where it runs. No secrets ever need to reach a browser bundle.

  • Dual ESM + CJS builds, full TypeScript types.
  • Zero framework assumptions — works in React, React Native (Expo), Next.js, Node, and Cloudflare Workers.
  • One runtime dependency (aws4fetch, for server-side S3 signing).

Install

npm install @zerostackai/sdk
import { createZeroStack } from '@zerostackai/sdk'

Quickstart

Client (browser / React Native) — no secrets in the bundle

import { createZeroStack } from '@zerostackai/sdk'

const zs = createZeroStack({
  data: { url: import.meta.env.VITE_ZS_DATA_API_URL }, // Neon Data API URL — no token: auth feeds it
  auth: {
    url: import.meta.env.VITE_ZS_EDGE_URL, // edge base — hosts /auth/signup + /auth/login
    apiKey: import.meta.env.VITE_ZS_APP_KEY
  },
  storage: {
    presignUrl: import.meta.env.VITE_ZS_EDGE_URL + '/presign',
    apiKey: import.meta.env.VITE_ZS_APP_KEY
  },
  llm: {
    proxyUrl: import.meta.env.VITE_ZS_EDGE_URL + '/llm',
    apiKey: import.meta.env.VITE_ZS_APP_KEY
  }
})

// Sign in → the token is stored and used by db automatically. RLS is enforced by it.
await zs.auth!.signIn('[email protected]', 'password')
const bookings = await zs.db.from('bookings').select('*').eq('status', 'confirmed').limit(20)
const { key } = await zs.storage!.upload(`avatars/${userId}.jpg`, file, { contentType: 'image/jpeg' })
const reply = await zs.llm!.chat([{ role: 'user', content: 'Summarize my week.' }])

Server (Node / Cloudflare Workers) — scoped credentials allowed

const zs = createZeroStack({
  data: { url: process.env.ZS_DATA_API_URL!, apiKey: process.env.ZS_SERVICE_KEY },
  storage: {
    accountId: process.env.CF_ACCOUNT_ID!,
    accessKeyId: process.env.R2_ACCESS_KEY_ID!,
    secretAccessKey: process.env.R2_SECRET_ACCESS_KEY!,
    bucket: process.env.R2_BUCKET!
  },
  llm: { provider: 'deepseek', apiKey: process.env.DEEPSEEK_API_KEY! }
})

storage and llm are optional — omit them and those fields are undefined. data is always required.


Configuration reference

createZeroStack(config: ZeroStackConfig) => { db, storage?, llm? }

interface ZeroStackConfig {
  data: DataConfig       // required
  storage?: StorageConfig
  llm?: LlmConfig
}

data: DataConfig

| Field | Type | Notes | | --- | --- | --- | | url | string (required) | Neon Data API base URL for the project (PostgREST-compatible). | | token | string \| () => string \| null \| Promise<...> | End-user JWT. A getter is re-read on every request, so token refresh just works. | | apiKey | string | Optional API-key header for server contexts. |

storage: StorageConfig — one of:

Presign (client): | Field | Type | Notes | | --- | --- | --- | | presignUrl | string | Edge endpoint that mints short-lived, tenant-scoped upload URLs. | | apiKey | string? | App-level key sent to the endpoint. | | token | TokenProvider? | End-user JWT, forwarded so the URL can be scoped to the user. |

R2 direct (server-only): | Field | Type | | --- | --- | | accountId, accessKeyId, secretAccessKey, bucket | string | | publicBaseUrl | string? (if the bucket is served publicly) |

⚠️ Never put R2 secret credentials in a browser bundle. Use the presign config client-side.

llm: LlmConfig — one of:

Proxy (client): { proxyUrl: string; apiKey: string } — the edge proxy holds the real provider key and meters usage per tenant.

Direct (server-only): { provider: 'deepseek' | 'openai'; apiKey: string; baseUrl?: string; model?: string }.


auth — end-user sign-in

Configure auth: { url, apiKey } (the edge base + tenant app key). zs.auth signs users in against the ZeroStack edge, which issues an RS256 JWT the Neon Data API is configured to trust — so signed-in users can read/write via db with RLS scoped to their sub. No Neon Auth / Stack Auth, no manual token wiring.

await zs.auth!.signUp('[email protected]', 'password')  // create + sign in
await zs.auth!.signIn('[email protected]', 'password')  // sign in
zs.auth!.isAuthenticated        // boolean
zs.auth!.user                   // { id, email } | null
zs.auth!.signOut()

The token is held in memory and persisted to localStorage in browsers (key zs_jwt; React Native is in-memory). Once configured, you don't pass data.tokendb reads the signed-in token from auth automatically.

db — query builder

zs.db.from(table) returns a chainable builder. It is thenableawait it to run the query and get T[], or call .single() for T | null.

zs.db.from<T = any>(table: string)
  .select(columns = '*')          // 'id, name' or '*'
  .insert(rows)                   // Partial<T> | Partial<T>[]
  .update(patch)                  // Partial<T>
  .delete()
  .eq(column, value)              // column = value
  .in(column, values[])           // column IN (…)
  .order(column, { ascending })   // default ascending: true
  .limit(n)
  .single(): Promise<T | null>    // first row or null
// Read
const users = await zs.db.from('users').select('id, email').eq('active', true).order('created_at', { ascending: false }).limit(50)

// Read one
const user = await zs.db.from('users').select('*').eq('id', id).single()

// Insert
await zs.db.from('notifications').insert({ user_id: id, title: 'Welcome' })

// Update
await zs.db.from('profiles').update({ bio: 'hi' }).eq('user_id', id)

// Delete
await zs.db.from('sessions').delete().eq('id', sessionId)

Every call returns this, so order is flexible; the request fires when you await (or call .single()).


storage

zs.storage!.upload(
  key: string,
  data: Blob | ArrayBuffer | Uint8Array | string,
  opts?: { contentType?: string; expiresIn?: number }
): Promise<{ key: string; url?: string }>

zs.storage!.getSignedUrl(key: string, expiresIn = 3600): Promise<string>
const { key } = await zs.storage!.upload(`u/${id}/photo.jpg`, file, { contentType: 'image/jpeg' })
const url = await zs.storage!.getSignedUrl(key, 600) // 10-minute download URL

Client uploads go through the edge presign flow (no secrets in the bundle); server uploads sign directly against R2 (S3) with aws4fetch. Same method, either way.


llm

zs.llm!.chat(
  messages: { role: 'system' | 'user' | 'assistant'; content: string }[],
  opts?: { model?: string }
): Promise<{ role: 'assistant'; content: string }>
const reply = await zs.llm!.chat([
  { role: 'system', content: 'You are terse.' },
  { role: 'user', content: 'One-line summary of RLS.' }
])
console.log(reply.content)

Client calls hit the edge proxy (company key stays server-side, usage metered per tenant); server calls go direct to the provider.


Environments & safety

import { isBrowser, ZeroStackError } from '@zerostackai/sdk'
  • isBrowsertrue in a browser or React Native. Use it to branch config.
  • Privileged config paths (R2 direct, direct LLM keys) are guarded: attempting them in a browser throws a ZeroStackError, so a mis-wired bundle fails loudly instead of leaking a key.
  • ZeroStackError — every SDK error is an instance; it carries an optional status and detail.
try {
  await zs.db.from('users').select('*')
} catch (e) {
  if (e instanceof ZeroStackError) console.error(e.status, e.message, e.detail)
}

For AI agents / tooling

  • Entry: createZeroStack(config) — the only constructor. Returns { db, auth?, storage?, llm? }.
  • Auth: if config.auth is set, zs.auth.signIn/signUp(email, password) obtains a token the Data API trusts and db uses it automatically — don't pass data.token and don't wire Neon Auth.
  • Required config: data.url (throws ZeroStackError if missing).
  • Auth model: client passes an end-user JWT via data.token (a getter, re-read per request); the Neon Data API enforces Postgres RLS. Never embed service credentials client-side.
  • db is thenableawait zs.db.from(t).select() yields T[]; .single() yields T | null.
  • Optional surfaces: guard with zs.storage? / zs.llm? (or ! when you configured them).
  • Module formats: import (ESM) and require (CJS) both work; types are bundled.

License

MIT