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

@podge/sdk-node

v0.2.0

Published

SDK for the Podge API

Downloads

349

Readme

@podge/sdk-node

Find the needle, keep the haystack. Podge takes data of every shape and size and turns it into shippable, performant search for your application — no infrastructure to run, no query tuning, no schemas to perfect. Just ingest and search.

Highlighting, faceting, and the rest of the polish come standard. Easy for humans and AI agents alike.

This SDK is the fastest way to wire Podge into a Node.js or TypeScript application.

Core concepts

Podge organizes everything into four primitives:

  • Workspace — the top-level container, similar to a project. Everything else lives inside it.
  • Environment — an isolated context within a workspace. A dev environment is created for you by default; add more to match each environment where you keep data (e.g. staging, production). Collections and their data are scoped to an environment, so you can work in one without affecting another.
  • Collection — a group of items that can be searched together. If your application is a directory of burritos, you'd create a burritos collection.
  • Item — an individual searchable record within a collection (a single burrito).

Keeping data in sync

Podge is a search index that mirrors your source of truth, so the goal is to keep it in step with your application's data. Hook into the changes in your app and push them to Podge as they happen:

  • Item addedpodge.createItem()
  • Item modifiedpodge.updateItem()
  • Item deletedpodge.deleteItem()

For data that already exists, use createImportJob() to backfill an entire collection in one go via a presigned S3 upload — no need to loop through records one at a time.

Note: Podge is currently in closed beta and isn't recommended for production use yet. Create an account to get access.

Installation

pnpm add @podge/sdk-node

Getting Started

import { PodgeSDK } from "@podge/sdk-node";

interface Product {
  title: string;
  category: string;
  price: number;
}

const podge = new PodgeSDK({
  apiKey: "your-api-key",
  workspace: "my-workspace",
  environment: "production",
});

// Insert a typed item
await podge.createItem<Product>("products", {
  title: "Ergonomic Keyboard",
  category: "Electronics",
  price: 149.99,
});

// Search — results are typed as Product
const { results } = await podge.search<Product>("products", {
  search: "keyboard",
});
console.log(results[0].document.title);

Usage

Inserting Items

// Insert a single item
await podge.createItem<Product>("products", {
  title: "Ergonomic Keyboard",
  category: "Electronics",
  price: 149.99,
});

// Dry run — validate without persisting
await podge.createItem("products", { title: "Test" }, { dryRun: true });

Items are ingested asynchronously — after insertion, the document is queued for tokenization and indexing. Use getDocument() to check ingestion status. Typical ingestion latency is a few seconds for single items.

Search

// Simple search
const { results } = await podge.search<Product>("products", {
  search: "keyboard",
});

// With filters, facets, sorting, and highlighting
const { results, facets } = await podge.search<Product>("products", {
  search: "ergonomic",
  filters: {
    category: "Electronics",
    price: { gte: 50, lte: 500 },
  },
  options: {
    requireAllWords: true,
    limit: 20,
    offset: 0,
    rank: true,
    highlight: {
      fields: ["title", "description"],
      pre_tag: "<b>",
      post_tag: "</b>",
    },
  },
  facets: ["category", "brand"],
  sort: [{ field: "price", order: "asc" }],
});

Search behavior

  • Default mode: Token matching with term count scoring. Each word in the query is matched independently. Results are ranked by how many query terms appear in the document.
  • rank: true: Enables BM25 relevance scoring (k1=2.5, b=0.75). Documents are ranked by statistical relevance rather than simple term counts.
  • requireAllWords: true: All query terms must appear in the document (AND logic). Default is OR logic.
  • Quoted phrases: Use "exact phrase" in the search string for contiguous word matching.
  • Exclusions: Prefix a term with - to exclude documents containing that word (e.g., keyboard -wireless).
  • Field-scoped search: Use field option to restrict which fields are searched instead of searching all indexed fields.

Pagination

  • limit: 1–100 (default 25)
  • offset: 0-based offset for pagination
  • Response includes total (total matching documents) and count (results in this page)

Facets

  • Request facets by passing field names in the facets array
  • Returns up to 100 values per faceted field, sorted by count
  • Facet counts reflect all matching documents, not just the current page

Sorting

  • Sort by any indexed field with sort: [{ field: "price", order: "asc" }]
  • Use _score as a special field name to sort by relevance score

Filters

Filters support direct values for exact match, or operator objects for advanced filtering:

// Exact match
filters: { category: "Electronics" }

// Comparison operators
filters: { price: { gte: 50, lte: 500 } }

// Not equal
filters: { status: { ne: "discontinued" } }

// Match any value in a list
filters: { category: { in: ["Electronics", "Accessories"] } }

// Exclude values
filters: { category: { nin: ["Furniture"] } }

// Field existence
filters: { description: { exists: true } }

// Nested fields use dot notation
filters: { "metadata.region": "us-east" }

Available operators: eq, ne, gt, gte, lt, lte, in, nin, exists.

Operator–type compatibility:

| Operator | string | number | date | boolean | |----------|--------|--------|------|---------| | eq, ne | yes | yes | yes | yes | | gt, gte, lt, lte | no | yes | yes | no | | in, nin | yes | yes | yes | yes | | exists | yes | yes | yes | yes |

Field Types and Schema

Documents are schema-on-write — field types are automatically detected when documents are ingested:

| Detected type | Examples | Storage | |---|---|---| | string | Any text value | string map (searchable) | | number | Integer values | number map (filterable/sortable) | | date | ISO 8601, MM/DD/YYYY, DD/MM/YYYY, RFC 2822, Unix timestamps | datetime map (filterable/sortable) | | boolean | true, false | string map |

Nested objects are flattened with dot notation (e.g., address.city).

All fields default to search status (indexed and searchable). Use updateCollectionSchema to set fields to ignore if they should be excluded from search results:

// View discovered fields and their types
const schema = await podge.getCollectionSchema("products");
// schema.fields → [{ field: "title", fieldType: "string", status: "search", ... }]

// Exclude a field from search indexing
await podge.updateCollectionSchema("products", {
  fields: [{ field: "internal_id", status: "ignore" }],
});

Collections

const collection = await podge.createCollection({ name: "Products" });

const collections = await podge.listCollections();

const schema = await podge.getCollectionSchema("products");

Batch Insert

await podge.createItemsBatch<Product>("products", [
  { title: "Standing Desk", category: "Furniture", price: 599.99 },
  { title: "Monitor Arm", category: "Accessories", price: 89.99 },
]);

Bulk Import

For large datasets, use import jobs with presigned S3 upload URLs.

Supported formats: JSON arrays ([{...}, {...}]) or single JSON objects ({...}).

Limits: 200 MB max per file, 10 MB JSON buffer limit.

// Create an import job with presigned upload URLs
const job = await podge.createImportJob("products", {
  fileCount: 2,
  fileNames: ["batch-1.json", "batch-2.json"],
});

// Upload files to the presigned URLs (expire after 1 hour)
for (const { url } of job.uploadUrls) {
  await fetch(url, {
    method: "PUT",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify([{ title: "Product", price: 9.99 }]),
  });
}

// Poll job status
const status = await podge.getImportJob(job.jobId);
console.log(status.status);
// Job progresses: "created" → "streaming" → "ingesting" → "completed" | "failed"
// Track progress: status.ingestedItems, status.failedItems, status.totalItems

Environments

const environments = await podge.listEnvironments();

const env = await podge.createEnvironment({ name: "staging" });

Document Retrieval

const doc = await podge.getDocument<Product>("products", "doc-abc123");
console.log(doc.status); // "INGESTED" | "PENDING"
console.log(doc.document?.title);

Error Handling

The SDK uses Axios under the hood. API errors are thrown as AxiosError instances:

import { AxiosError } from "axios";

try {
  await podge.search("products", { search: "test" });
} catch (err) {
  if (err instanceof AxiosError) {
    console.error(err.response?.status); // 401, 400, 404, etc.
    console.error(err.response?.data);   // Error details from API
  }
}

Common error status codes:

| Status | Meaning | |--------|---------| | 400 | Validation error — check request body/parameters | | 401 | Invalid or missing API key | | 403 | API key does not have access to this workspace/environment | | 404 | Workspace, environment, or collection not found |

Development

# Install dependencies
pnpm install

# Build
pnpm run build

# Run tests
pnpm test

# Watch mode
pnpm run dev

# Lint
pnpm run lint

Examples

See the examples/ directory for runnable scripts:

  • basic-usage.ts — End-to-end walkthrough: create environments, collections, insert items, and search
  • bulk-import.ts — Import workflow with presigned uploads and job polling
  • search-with-facets.ts — Advanced search with filters, facets, sorting, and highlighting

Run any example with:

PODGE_API_KEY=your-key npx tsx examples/basic-usage.ts