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

@deployowl/nosql

v2.2.7

Published

Zero-config edge NoSQL SDK for DeployOwl

Readme

@deployowl/nosql

Zero-config Edge NoSQL SDK for DeployOwl.

Use this SDK to store, query, and manage JSON document collections on DeployOwl's global edge infrastructure from Vercel, Render, Cloud Run, Supabase, Railway, Fly.io, or any server environment.


Installation

npm install @deployowl/nosql
# or
pnpm add @deployowl/nosql
# or
yarn add @deployowl/nosql

Quick Start

1. Initialize the client

Construct the client once at module load:

import { createClient } from '@deployowl/nosql';

const db = createClient({
  apiKey: process.env.OWL_NOSQL_KEY!, // Your owl_ API key (min 32 chars)
  // gatewayUrl: 'https://owlnosql.deployowl.com', // Optional default
  // autoHashIds: true, // Auto-hash non-alphanumeric IDs to SHA-256
  // cacheQueries: true, // Opt-in: cache identical query() results for 15s (see §4c)
  // ulidIds: false, // Set false to require explicit insert() IDs (default: auto-ULID when id omitted)
});

const users = db.collection('users');

Security Note: createClient() must only be run in server environments. Calling it in a browser context will throw an error to prevent API key exposure.

2. Write a document

await users.insert('user_9482', {
  email: '[email protected]',
  plan: 'pro',
  signupAt: Date.now(),
  tags: ['early', 'beta']
});
// → { id: 'user_9482', written: true, bytes: 87 }

// Omit the ID to get a time-ordered ULID (sorts chronologically, unique
// without coordination — ideal for feeds, logs, and "latest N" views):
const { id } = await users.insert(null, { body: 'hello', createdAt: Date.now() });
// → { id: '01JD2Q5R8ZKABCDEF012345678', written: true, ... }

3. Read a document

const ada = await users.find('user_9482');
// → { email: '[email protected]', plan: 'pro', ... }

// Bypass edge cache for fresh read:
const fresh = await users.find('user_9482', { bypassCache: true });

4. Index and Query

query() filters documents server-side by matching field values. It works by scanning index shards — lightweight summaries of each document that store field values for fast lookup.

How indexing works:

  • By default (no index() call), all primitive fields are indexed. Every insert() and update() writes field values into the index shards automatically. query() can filter by any field without any setup.
  • If you call index(['field1', 'field2']), only those fields are indexed. This reduces index size and write overhead for collections with large documents. Fields not in the list are excluded from the index and cannot be used in query().
  • index(fields, { covering }) inlines listed fields into the index shards. When a query requests only covering fields via fields, results are served straight from the shards — the matching documents are never fetched. This is the fastest way to serve list views: declare the fields your lists display as covering, then query with fields: [...].
  • Calling index() automatically reindexes existing documents. You do not need to call reindex() separately after changing the indexed fields — the rebuild happens in the background.
  • reindex() repairs index divergence. It compares the index against stored documents and rebuilds only what diverged (a full rebuild only when divergence is large). Use it if you suspect the index is stale or corrupted, or after manual data imports that bypassed the SDK.
// Option A: Query by any field (no setup needed — all fields indexed by default):
const pros = await users.query({ plan: 'pro' });
const byEmail = await users.query({ email: '[email protected]' });

// Option B: Restrict indexing to specific fields (reduces index size):
await users.index(['email', 'plan']);
// Now only 'email' and 'plan' are in the index. 'name', 'tags', etc. are excluded.
// Existing documents are automatically reindexed in the background — no manual reindex() needed.

// Query by an indexed field:
const pros = await users.query({ plan: 'pro' });

// Guaranteed-fresh query (skips edge cache):
const freshResults = await users.query({ plan: 'pro' }, { bypassCache: true });

// Paginated query — fetch only the first 20 matches (server caps at 500 by default):
const first20 = await users.query({ plan: 'pro' }, { limit: 20 });
const nextPage = await users.query({ plan: 'pro' }, { limit: 20, offset: 20 });

// Sorted query — newest first (sort runs server-side on indexed values):
const newest = await users.query({ plan: 'pro' }, { sort: [{ field: 'created_at', dir: 'desc' }], limit: 10 });

// Field projection — return only the fields your list view displays:
const summaries = await users.query({ plan: 'pro' }, { fields: ['email', 'plan'] });

// Covering query — the fastest list view possible. Declare the fields your
// lists display as covering, then request exactly those fields: results are
// served from the index shards and matching documents are never fetched.
await users.index(['email', 'plan', 'created_at'], { covering: ['email', 'plan', 'created_at'] });
const rows = await users.query({ plan: 'pro' }, { fields: ['email', 'plan', 'created_at'] });

Important: query() only matches fields that are present in the index shards. If you query by a field and get 0 results despite documents existing with that field, either (a) the field value is 64+ chars and was classified as encrypted data, (b) index() was called with a field list that doesn't include the queried field, or (c) the index shards are stale — call reindex() to rebuild.

4b. Count and Batch Fetch

count(filter?) counts matching documents directly from the index — zero document reads. Never export a collection just to call .length:

const total = await users.count();
// → 1240

const proCount = await users.count({ plan: 'pro' });
// → 312

findMany(ids) fetches up to 200 documents by ID in one request — one network round trip instead of N parallel find() calls:

const { documents, nextCursor } = await users.findMany(['user_1', 'user_2', 'user_9482']);
// → { documents: [...], nextCursor: null }

// Guaranteed-fresh batch read:
const fresh = await users.findMany(ids, { bypassCache: true });

page({ limit, offset }) (experimental) is a client-side pagination bridge — it lists IDs, slices, and batch-fetches the page. Downloads all IDs per call, so prefer query() with limit/offset where possible:

const { documents, total } = await users.page({ limit: 20, offset: 40 });
// → { documents: [...20 docs...], total: 1240 }

5. List

list() returns all document IDs in a collection by scanning the index shards.

const ids = await users.list();
// → ['user_1', 'user_2', 'user_9482', ...]

// Guaranteed-fresh (skips edge cache):
const freshIds = await users.list({ bypassCache: true });

list() returns the exact IDs passed to insert() — no transformation. If you sanitize IDs client-side (e.g. replacing hyphens with underscores), filter list() results using the same sanitized format.

6. Delete

await users.delete('user_9482');
// → { deleted: true }

7. Export (Backups & Migration Only)

Export an entire collection as a JSON array — importable into MongoDB, Postgres, DynamoDB, or any database that accepts JSON.

// Export entire collection:
const result = await users.export();
// → { collection: 'users', count: 1240, total: 1240, offset: 0, limit: null, documents: [...] }

Do not use export() to build list views. It downloads the entire collection on every call. For lists, use query() with limit/offset; for counts, use count().


API Reference

| Method | Signature | Description | |---|---|---| | collection(name) | name: string | Get collection handle (alphanumeric, _, -, max 64 chars). | | find(id, opts?) | id: string, opts.bypassCache?: boolean | Fetch document. Returns T \| null. | | insert(id, payload, opts?) | id: string \| null, payload: object, opts.ifMatch?: string | Insert document. Omit id (or pass null) for an auto-generated time-ordered ULID. | | delete(id) | id: string | Delete document. | | query(filter, opts?) | filter: object, opts.bypassCache?: boolean, opts.limit?: number, opts.offset?: number, opts.sort?: { field: string; dir?: 'asc' \| 'desc' }[], opts.fields?: string[] | Query documents by indexed fields. Returns T[]. limit caps results (server default: 500); offset skips matches for pagination; sort orders results server-side (missing values sort last); fields projects results to the listed fields — when every requested field is a covering field, documents are served from the index without per-document fetches. | | count(filter?) | filter?: object | Count matching documents from the index — zero document reads. Returns number. | | findMany(ids, opts?) | ids: string[] (max 200), opts.bypassCache?: boolean | Batch-fetch documents by ID in one request. Returns { documents, nextCursor }. | | page(opts) (experimental) | opts.limit: number, opts.offset?: number | Client-side pagination bridge. Returns { documents, total }. Prefer query() with limit/offset. | | list(opts?) | opts.bypassCache?: boolean | List all document IDs in the collection. | | export(opts?) | opts.limit?: number, opts.offset?: number | Export entire collection as a JSON array. Supports pagination. | | index(fields, opts?) | fields: string[], opts.covering?: string[] | Create indexes on collection fields. covering inlines the listed fields into the index shards so queries requesting only those fields (via query(..., { fields })) skip per-document fetches entirely. | | reindex() | — | Repair collection shard indexes: rebuilds only divergent entries (full rebuild only when divergence is large). |

Read consistency

Reads (find, list, query) are served from a per-region edge cache for low latency. Writes (insert, update, delete) invalidate the cached document immediately on the edge that processed the write, so read-your-writes is guaranteed within the same request region.

For guaranteed-fresh reads across all edge locations (e.g. immediately after a write from a different region), pass { bypassCache: true }:

// Guaranteed-fresh — skips the edge cache entirely and reads from storage
const fresh = await users.find('user_9482', { bypassCache: true });
const freshIds = await users.list({ bypassCache: true });
const freshResults = await users.query({ plan: 'pro' }, { bypassCache: true });

Opt-in query caching (cacheQueries: true): identical query() calls within 15 seconds are served from the edge cache without recomputation — ideal for dashboards that re-run the same queries on a short poll. A write invalidates the cached results on the write's edge immediately; other edges may serve the cached result for up to 15 seconds. Use { bypassCache: true } on any query that must be guaranteed-fresh. Default is off.


Document & ID Rules

  • ID Rules: Alphanumeric, _, -, max 128 chars. IDs starting with _ are reserved.
  • Auto-Hashing: With autoHashIds: true, non-conforming IDs are automatically SHA-256 hashed to hash_<base64url>.
  • Errors: Throws OwlError with .code, .message, .statusCode. find() swallows 404 and returns null.

License

Proprietary © DeployOwl. All rights reserved.