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

@mirk/store

v0.6.1

Published

Code-split storage ports + source adapters under one namespace. KV + collection store and vector similarity store as interface subpaths; the sqlite source-adapter implements both over one connection.

Downloads

425

Readme

@mirk/store

Code-split storage ports + source adapters under one namespace. Import the whole namespace, or just the specific subpath you need — the interface ports and their in-memory reference implementations are zero-native, and only the SQLite adapter references native bindings (as optional peers).

ESM-only (the package exposes import entry points; there is no CommonJS build).

Install

npm install @mirk/store
# Using the SQLite adapter (@mirk/store/sqlite)? Add its peer:
npm install better-sqlite3
# Optional: vec0 KNN acceleration (graceful exact-JS fallback without it)
npm install sqlite-vec

Subpaths

| Import | What it gives you | Native deps | |---|---|---| | @mirk/store | the ports + their in-memory references + toAsync + cosine helpers | none | | @mirk/store/kv | SyncStore port (key-value + collections), InMemoryKv, toAsync | none | | @mirk/store/vector | VectorStore port, InMemoryVectorStore, cosine helpers | none | | @mirk/store/search | SearchStore port, InMemorySearchStore, BM25-style keyword search | none | | @mirk/store/graph | graph helpers over the collection port (neighbors, traverse, traverseFrontierBatched) | none | | @mirk/store/sqlite | the SQLite source adapter — one connection, .kv + .vector + .search facets | better-sqlite3 (peer), sqlite-vec (optional peer) |

Source adapters are reached only through their own subpath (e.g. /sqlite) — the root and the port subpaths never re-export them, so importing @mirk/store, /kv, /vector, /search, or /graph never drags a native binding into a consumer bundle.

Quickstart — zero native deps

The in-memory references implement the same ports as the backends, so you can build against them with nothing installed:

import { InMemoryKv, toAsync } from "@mirk/store/kv";

const kv = new InMemoryKv();
kv.set("user:1", { name: "Ada" });
kv.get<{ name: string }>("user:1");   // { name: "Ada" }
kv.keys("user:");                      // ["user:1"]

// Lift any SyncStore to a Promise-returning API (one-way: sync ⊂ async):
const asyncKv = toAsync(kv);
await asyncKv.get("user:1");

Collections

A SyncStore is also a small document store, keyed by id:

kv.put("posts", { id: "p1", title: "Hello", pinned: true });
kv.getById("posts", "p1");                       // { id: "p1", title: "Hello", pinned: true }
kv.list("posts", { where: { pinned: true }, sortBy: "title", limit: 10 });
kv.count("posts");                               // 1
kv.remove("posts", "p1");

Full-text search

SearchStore indexes documents by id and returns BM25-ranked keyword matches. Use text for the single-field shorthand or fields for named columns with query-time weighting:

import { InMemorySearchStore } from "@mirk/store/search";

const search = new InMemorySearchStore();
search.index("pages", { id: "a", fields: { title: "Opal guide", body: "plain body" } });
search.index("pages", { id: "b", fields: { title: "plain title", body: "Opal guide" } });
search.search("pages", "opal", { fieldWeights: { title: 4, body: 1 } }); // [a, b]

The first indexed document fixes a collection's field schema; later documents must use the same field names. text and fields: { text } are the same single-field schema for backwards compatibility.

Graph helpers

@mirk/store/graph stores edges as ordinary collection records and traverses them through the existing collection port. Policy stays caller-owned through StoreFilter.

import { traverse } from "@mirk/store/graph";

const hits = traverse(kv, { start: "node:a", depth: 2, direction: "out" });

SQLite adapter — one connection, many capabilities

SqliteAdapter opens a single better-sqlite3 database and exposes .kv (SyncStore), .vector (VectorStore), and .search (SearchStore) facets over it:

import { SqliteAdapter } from "@mirk/store/sqlite";

// .kv and .search work immediately; vector dimensions infer on first write.
const db = new SqliteAdapter({ path: "data.db" });

db.kv.set("user:1", { name: "Ada" });

db.search.index("pages", { id: "intro", fields: { title: "Intro", body: "hello world" } });
db.search.search("pages", "hello", { fieldWeights: { title: 4, body: 1 } });

const embedding = new Float32Array(768); // your real embedding here
const query = new Float32Array(768);
db.vector.upsert("docs", { id: "a", vector: embedding });
const results = db.vector.search("docs", query, { topK: 10 }); // ranked by cosine

db.close();

SqliteAdapter options

| Option | Type | Notes | |---|---|---| | path | string | DB file path, or ":memory:". | | db | Database | Reuse an existing better-sqlite3 connection instead of opening one. | | dimensions | number | Optional embedding dimensionality. If omitted, inferred and persisted from the first vector upsert / upsertMany; search still requires known dimensions. | | forceJsCosine | boolean | Pin the exact JS-cosine path even when sqlite-vec is installed (mainly for tests). |

Vectors (Vector is a Float32Array) are stored as little-endian float32 BLOBs and ranked by exact cosine. When the optional sqlite-vec peer is installed, search is transparently accelerated by vec0 using the cosine distance metric, so rankings are identical to the JS path; without it, the exact JS-cosine fallback runs. db.vector.meta.accelerated reports which path is live.

Sync by design

Embedded backends are synchronousbetter-sqlite3 is synchronous, and an async-everywhere interface would tax every local call with a Promise it doesn't need. A SyncStore lifts to an async API via toAsync(store); the reverse is impossible. Pick sync for embedded/local, and reach for async only where a remote backend genuinely requires it.

License

Apache-2.0 © David Robinson