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

sodamem

v0.1.1

Published

TypeScript/JavaScript client SDK for the SodaMem REST API.

Readme

sodamem (TypeScript / JavaScript SDK)

Official TypeScript client for the SodaMem REST API — evidence-grounded temporal memory for AI agents. Ships ESM and CJS builds with full type definitions, zero runtime dependencies, and a waitForJob helper so you don't have to hand-roll a poll loop for async ingest.

Install

npm install sodamem

Requires Node.js >= 18 (for global fetch/AbortController), or pass your own fetch implementation (see Custom fetch below).

Quick start

import { SodaMemClient } from "sodamem";

const client = new SodaMemClient({
  baseUrl: "http://localhost:8000",
  apiKey: process.env.SODAMEM_API_KEY!,
});

// 1. Ingest a conversation. async_mode defaults to true: the server returns
//    202 + a job_id immediately and extracts facts in the background.
const accepted = await client.add({
  user_id: "user-42",
  messages: [
    { role: "user", content: "I switched teams last week, I'm on Platform now." },
    { role: "assistant", content: "Got it — I'll remember you're on Platform." },
  ],
});
console.log(accepted.job_id, accepted.status); // "pending"

// 2. Block until extraction finishes, instead of writing your own poll loop.
const job = await client.waitForJob(accepted.job_id, {
  pollMs: 500,     // how often to poll GET /v1/jobs/{id} (default 500ms)
  timeoutMs: 60_000, // give up and throw after this long (default 60s)
});

if (job.status === "failed") {
  throw new Error(`ingest failed: ${job.error}`);
}
console.log(job.result); // { facts_extracted, spans_written, turns_written }

// 3. Now the fact is searchable.
const results = await client.search({
  user_id: "user-42",
  query: "what team are they on?",
  top_k: 5,
});
for (const hit of results.hits) {
  console.log(hit.score, hit.content);
}

If you don't want to wait, pass async_mode: false to add() and it blocks server-side instead, returning AddMemoriesResult directly (TypeScript narrows the return type for you based on the literal false):

const result = await client.add({
  user_id: "user-42",
  messages: [{ role: "user", content: "..." }],
  async_mode: false,
});
result.facts_extracted; // typed, no job/poll needed

Auth

Every request (except health()) is sent with both Authorization: Bearer <apiKey> and X-API-Key: <apiKey> headers — the server accepts either, so you don't need to know which one it's configured for.

const client = new SodaMemClient({
  baseUrl: "https://sodamem.example.com",
  apiKey: process.env.SODAMEM_API_KEY!,
});

Error handling

Every non-2xx response throws SodaMemApiError — nothing resolves to undefined on failure:

import { SodaMemApiError, SodaMemTimeoutError } from "sodamem";

try {
  await client.get("does-not-exist", { user_id: "user-42" });
} catch (err) {
  if (err instanceof SodaMemApiError) {
    console.error(err.status, err.code, err.message, err.details);
  } else if (err instanceof SodaMemTimeoutError) {
    console.error("request timed out after", err.timeoutMs, "ms");
  } else {
    throw err; // network failure, etc. — not swallowed
  }
}

SodaMemApiError fields:

| Field | Type | Notes | |-----------|--------------------------|-------------------------------------------------------------------------------------------| | code | string | Machine-readable. "http_error" when the server didn't send the ErrorBody envelope (e.g. a raw FastAPI 401). | | status | number | HTTP status code. | | details | Record<string, unknown>| Structured details, if any. | | message | string | Human-readable (from Error). |

SodaMemTimeoutError is thrown by both per-request timeouts (timeoutMs on the client) and by waitForJob when the job doesn't reach a terminal state in time.

Custom fetch

Inject any fetch-compatible implementation — useful for older Node, non-standard runtimes, or tests:

import { fetch } from "undici";

const client = new SodaMemClient({
  baseUrl: "http://localhost:8000",
  apiKey: "...",
  fetch, // structurally compatible: (url, init) => Promise<Response>
});

API reference

| Method | HTTP | Returns | |------------------------------------------|------------------------------|----------------------------------------| | client.health() | GET /health | Health | | client.add(request) | POST /v1/memories | AddMemoriesAccepted (202, default) or AddMemoriesResult (200, async_mode: false) | | client.waitForJob(jobId, opts?) | polls GET /v1/jobs/{id} | Job (terminal: succeeded or failed) | | client.list(params) | GET /v1/memories | MemoryList | | client.get(id, scope) | GET /v1/memories/{id} | Memory | | client.delete(id, scope, opts?) | DELETE /v1/memories/{id} | DeleteResult | | client.search(request) | POST /v1/search | SearchResponse | | client.context(request) | GET /v1/context | ContextResponse | | client.job(jobId) | GET /v1/jobs/{id} | Job |

client.delete archives by default — the memory disappears from get, list, search and context, but its row and provenance are retained. client.delete(id, scope, { purge: true }) asks for an irreversible physical erase instead, which the server rejects with 403 unless it was deployed with SODAMEM_ALLOW_PURGE=true. Check result.purged to tell the two apart, and result.already_deleted to tell a fresh archive from a repeat call.

All request types mirror server/models.py field-for-field — see src/types.ts for the full definitions (Scope, Message, AddMemoriesRequest, Memory, SearchHit, etc.).

Every request except health() requires user_id (via Scope); agent_id and run_id are optional narrowing filters, matching mem0's scope model.

Development

cd sdk-ts
npm install
npm run build   # emits dist/esm and dist/cjs
npm test        # vitest, all HTTP calls mocked — no server required