@moss-dev/moss
v1.7.1
Published
Maintainers
Readme
Moss client library for JavaScript
@moss-dev/moss enables private, on-device semantic search in your web, mobile, and edge applications - without cloud dependencies.
Built for developers who want instant, memory-efficient, privacy-first AI features inside their apps.
✨ Features
- ⚡ On-Device Vector Search - Sub-millisecond retrieval with zero network latency
- 🔍 Semantic Search & Hybrid Search - Beyond keyword matching
- 📦 Multi-Index Support - Manage multiple isolated search spaces
- 🛠️ Tiny SDK - Optimized for edge deployments
- 🛡️ Privacy-First by Design - No server-side cloud calls required to perform searches
📦 Installation
npm install @moss-dev/moss🚀 Quick Start
import { MossClient, DocumentInfo } from "@moss-dev/moss";
async function main() {
// Initialize search client with project credentials
const mossClient = new MossClient(
"your-project-id",
"your-project-key"
);
// Prepare documents to index
const documents: DocumentInfo[] = [
{
id: "doc1",
text: "How do I track my order? You can track your order by logging into your account.",
},
{
id: "doc2",
text: "What is your return policy? We offer a 30-day return policy for most items.",
},
{
id: "doc3",
text: "How can I change my shipping address? Contact our customer service team.",
},
// Add more documents here
// .
// .
// .
];
try {
// Create an index with documents and model
const indexName = "faqs";
const created = await mossClient.createIndex(
indexName,
documents
); // Defaults to the service's `moss-minilm` model when omitted
console.log("Index created:", created);
// Load the index before searching
await mossClient.loadIndex(indexName);
// Search the index
const result = await mossClient.query(indexName, "How do I return a damaged product?", {
topK: 3,
});
// Display results
console.log(`Query: ${result.query}`);
result.docs.forEach((match) => {
console.log(`Score: ${match.score.toFixed(4)}`);
console.log(`ID: ${match.id}`);
console.log(`Text: ${match.text}`);
console.log("---");
});
} finally {
// Release the client's underlying resources once you're done with it
await mossClient.close();
}
}
main().catch(console.error);To use a host-managed stable id for MAD billing while retaining Moss's own device UUID for correlation:
const mossClient = new MossClient("project", "key", {
identity: { deviceId: "host-installation-id", userId: "optional-user-id" },
});deviceId is billable. userId is optional and non-billable. Telemetry also
includes Moss's file-backed UUID as mossDeviceId; without a host id, both
device fields contain that Moss UUID.
Resource cleanup
MossClient and sessions returned by client.session(...) hold native resources that must be released explicitly. Call close() on each one when you are done with it, in a finally block or equivalent. close() is idempotent, so it is safe to call more than once.
client.close() also closes every live session created from that client. Closing a session individually first is fine; close() is idempotent on both.
On Node 24 and later, both MossClient and sessions implement Symbol.asyncDispose, so you can use await using instead of a manual try/finally:
await using mossClient = new MossClient("your-project-id", "your-project-key");
// mossClient.close() is called automatically when it goes out of scope🔥 Example Use Cases
- Smart knowledge base search
- Realtime Voice AI agents
- Personal note-taking search
- Private in-app AI features (recommendations, retrieval)
- Local semantic search in edge devices, AR/VR, mobile apps
🧠 Providing custom embeddings
Already using your own embedding model? Supply vectors directly when managing indexes:
const documents = [
{
id: "doc-1",
text: "Attach a caller-provided embedding",
embedding: myEmbeddingModel("doc-1"),
},
{
id: "doc-2",
text: "Fallback to the built-in model when the field is omitted.",
},
];
await mossClient.createIndex("custom-embeddings", documents);
await mossClient.loadIndex("custom-embeddings");
const results = await mossClient.query("custom-embeddings", "", {
embedding: myEmbeddingModel("query"),
topK: 10,
});Leaving modelId undefined defaults to moss-minilm. You can still pass { modelId: "moss-mediumlm" } or another supported identifier if you want the service to generate embeddings for documents without the optional embedding field.
Foundation model identity
Moss generates local query embeddings for a foundation-model index only when the service supplies the exact immutable model artifact identity used to build that index. This prevents a query from being embedded with different model bytes than the stored document vectors.
Fresh local sessions using moss-minilm, moss-mediumlm, or moss-litelm
bind that model's official current artifact automatically before model warmup.
Caller-supplied artifact identities are validated and kept, while custom models
are never auto-bound. saveToDisk records the exact model id, artifact version,
and manifest SHA-256, so the same session can be restored and queried with raw
text:
const first = await mossClient.session("memory");
await first.addDocs([{ id: "refund", text: "Customer requested a refund" }]);
await first.saveToDisk(cachePath);
await first.close();
const restored = await mossClient.session("memory");
await restored.loadFromDisk(cachePath);
const results = await restored.query("refund");Session caches created by older SDKs without an artifact version and manifest
SHA-256 load as custom sessions through the legacy fail-closed path. Their raw
text queries require an explicit caller-generated embedding. Rebuild those
caches from their source documents with the current SDK, then save them again
so the automatically bound foundation identity is persisted.
Older indexes without that identity still load and remain queryable, but the query must include a caller-generated vector of the correct dimension:
const results = await mossClient.query("legacy-index", "search text", {
embedding: myEmbeddingModel("search text"),
topK: 10,
});If the vector is omitted, the query fails closed with an error stating that an explicit query embedding is required. Moss does not silently fall back to an unverified local model or send the query to the cloud.
Metadata filtering
You can pass a metadata filter in query options after loading an index locally:
const results = await mossClient.query("my-docs", "running shoes", {
topK: 5,
alpha: 0.6,
filter: {
$and: [
{ field: "category", condition: { $eq: "shoes" } },
{ field: "price", condition: { $lt: "100" } },
],
},
});For a complete runnable example, see javascript/user-facing-sdk/samples/metadata-filtering.ts.
📄 License
This package is licensed under the PolyForm Shield License 1.0.0.
- ✅ Free for testing, evaluation, internal use, and modifications.
- ❌ Not permitted for production or competing commercial use.
- 📩 For commercial licenses, contact: [email protected]
📬 Contact
For support, commercial licensing, or partnership inquiries, contact us: [email protected]
