• Blog
  • Docs
  • Pricing
  • We’re hiring!
Log inSign up
kamenxrider

kamenxrider

slimarmor

Semantic vector DB on Val Town SQLite — DiskANN, hybrid search
Public
Like
slimarmor
Home
Code
7
CHANGES.md
GUIDE.md
HANDOVER.md
README.md
H
api.ts
ui.ts
vectordb.ts
Environment variables
4
Branches
1
Pull requests
Remixes
History
Val Town is a collaborative website to build and scale JavaScript apps.
Deploy APIs, crons, & store data – all from the browser, and deployed in milliseconds.
Sign up now
Code
/
GUIDE.md
Code
/
GUIDE.md
Search
…
Viewing readonly version of main branch: v87
View latest version
GUIDE.md

🛡️ SlimArmor — Beginner's Guide

Everything you need to go from zero to running semantic search in your own Val Town project.


What Are We Building?

A semantic search engine that understands meaning. Unlike normal search (which matches exact words), SlimArmor finds results based on what text means.

Real example:

  • You store: "The patient requires immediate surgery"
  • You search: "medical emergency"
  • It finds it — even though none of the words match ✅

This works because text is converted into embeddings — lists of numbers that capture meaning. Similar meanings produce similar numbers, so we can measure "how close" two pieces of text are in meaning-space.


Part 1: Deploy Your Instance

1.1 — Fork the val

  1. Go to val.town/x/kamenxrider/slimarmor
  2. Click Fork (top right)
  3. Your own copy is now live!

1.2 — Get an embedding API key

SlimArmor needs an AI API to convert text into embeddings. The default provider is Nebius — it's free to start and has great quality.

  1. Sign up at nebius.com
  2. Go to your API Keys page and create a new key
  3. Copy the key — you'll need it in the next step

Prefer OpenAI? Set EMBEDDING_PROVIDER=openai and use your OPENAI_API_KEY instead. See README.md for all provider options.

1.3 — Set environment variables

In your forked val on Val Town:

  1. Click Settings (the gear icon)
  2. Go to Environment Variables
  3. Add these:
KeyValueRequired?
NEBIUS_API_KEYYour Nebius API key✅ Yes
ADMIN_TOKENAny secret string (e.g. my-secret-123)Recommended

ADMIN_TOKEN protects your write endpoints. Without it, anyone can add or delete your data.

1.4 — Find your endpoint URL

Click on api.ts in your val. At the top you'll see an endpoint URL like:

https://yourusername--abc123.web.val.run

Open that URL in a browser — you should see the API info page. That's your SlimArmor instance! 🎉


Part 2: Add Your First Data

Option A — Use the Browser CLI (easiest)

Visit https://YOUR_ENDPOINT/ui for a terminal-style interface.

Type help to see all commands. To add your first record:

auth your-admin-token
upsert my-first-note "The quick brown fox jumps over the lazy dog"

Then search:

search "animals jumping"

Option B — Use curl

# Replace with your actual values ENDPOINT="https://YOUR_ENDPOINT" TOKEN="your-admin-token" # Add a record curl -X POST $ENDPOINT/upsert \ -H "Content-Type: application/json" \ -H "Authorization: Bearer $TOKEN" \ -d '{"id": "note-1", "text": "The quick brown fox jumps over the lazy dog"}' # Search curl -X POST $ENDPOINT/search \ -H "Content-Type: application/json" \ -d '{"query": "animals jumping", "k": 5}'

Option C — From JavaScript/TypeScript

const ENDPOINT = "https://YOUR_ENDPOINT"; const TOKEN = "your-admin-token"; // Add a record await fetch(`${ENDPOINT}/upsert`, { method: "POST", headers: { "Content-Type": "application/json", "Authorization": `Bearer ${TOKEN}`, }, body: JSON.stringify({ id: "note-1", text: "The quick brown fox jumps over the lazy dog", meta: { category: "example" }, }), }); // Search const res = await fetch(`${ENDPOINT}/search`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ query: "animals jumping", k: 5 }), }); const { results } = await res.json(); console.log(results);

Part 3: Understanding Search Results

A search result looks like this:

{ "id": "note-1", "text": "The quick brown fox jumps over the lazy dog", "meta": { "category": "example" }, "distance": 0.52 }

The key field is distance — it tells you how similar the result is to your query:

DistanceWhat it meansShould you include it?
0.0 – 0.3Near-identical meaningAlways ✅
0.3 – 0.5Very similarYes ✅
0.5 – 0.65RelatedUsually ✅
0.65 – 0.75Loosely relatedMaybe ⚠️
0.75+Probably unrelatedNo ❌

Setting a threshold

Use maxDistance to filter out weak matches:

{ "query": "animals jumping", "k": 10, "maxDistance": 0.65 }

Not sure what threshold to use? Use the calibrate endpoint:

GET /calibrate?q=your+search+query

It analyzes your actual data and recommends tight/balanced/loose thresholds.


Part 4: Common Use Cases

Personal note search

// Store notes await fetch(`${ENDPOINT}/upsert`, { method: "POST", headers: { "Content-Type": "application/json", "Authorization": `Bearer ${TOKEN}` }, body: JSON.stringify([ { id: "note-2024-01", text: "Meeting with Sarah about Q4 budget planning", meta: { date: "2024-01", tag: "work" } }, { id: "note-2024-02", text: "Research best frameworks for mobile development", meta: { date: "2024-01", tag: "tech" } }, { id: "note-2024-03", text: "Book flight to Amsterdam for the conference", meta: { date: "2024-01", tag: "travel" } }, ]), }); // Find work-related notes const res = await fetch(`${ENDPOINT}/search`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ query: "work meetings finance", k: 5, maxDistance: 0.65 }), });

Document / knowledge base search

For long documents, use chunked upsert to split them first:

curl -X POST $ENDPOINT/upsert_chunked \ -H "Content-Type: application/json" \ -H "Authorization: Bearer $TOKEN" \ -d '{ "id": "my-essay", "text": "...a very long piece of text...", "meta": { "source": "blog", "author": "Alice" }, "chunkSize": 800, "overlap": 100 }'

Each chunk is stored separately (my-essay::chunk1, my-essay::chunk2, etc.) and can be searched individually.

Filter by category

Use meta fields to organize data, then filter on search:

# Store records with categories curl -X POST $ENDPOINT/upsert \ -H "Content-Type: application/json" \ -H "Authorization: Bearer $TOKEN" \ -d '[ {"id": "r1", "text": "Python tutorial for beginners", "meta": {"type": "article"}}, {"id": "r2", "text": "Python course on Coursera", "meta": {"type": "course"}}, {"id": "r3", "text": "JavaScript for web development", "meta": {"type": "article"}} ]' # Search only within articles curl -X POST $ENDPOINT/search \ -H "Content-Type: application/json" \ -d '{"query": "learn programming", "k": 5, "filters": {"type": "article"}}'

Build a FAQ matcher

// Store FAQ pairs using the question as the text await fetch(`${ENDPOINT}/upsert`, { method: "POST", headers: { "Content-Type": "application/json", "Authorization": `Bearer ${TOKEN}` }, body: JSON.stringify([ { id: "faq-1", text: "How do I reset my password?", meta: { answer: "Go to Settings → Security → Reset Password" } }, { id: "faq-2", text: "How do I cancel my subscription?", meta: { answer: "Go to Billing → Cancel Plan" } }, { id: "faq-3", text: "How do I contact support?", meta: { answer: "Email support@example.com" } }, ]), }); // When a user asks a question, find the closest FAQ const userQuestion = "I forgot my password, what do I do?"; const res = await fetch(`${ENDPOINT}/search`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ query: userQuestion, k: 1, maxDistance: 0.6 }), }); const { results } = await res.json(); if (results.length > 0) { console.log("Answer:", results[0].meta.answer); }

Part 5: Import as a TypeScript Library

Instead of using the HTTP API, you can import SlimArmor's core directly into another val:

import * as db from "https://esm.town/v/kamenxrider/slimarmor/vectordb.ts"; export default async function handler(req: Request) { // Setup runs once per cold start (idempotent) await db.setup(); const url = new URL(req.url); if (req.method === "POST" && url.pathname === "/add") { const { id, text } = await req.json(); await db.upsert(id, text); return Response.json({ ok: true }); } if (req.method === "POST" && url.pathname === "/find") { const { query } = await req.json(); const results = await db.search(query, 5, 0.65); return Response.json({ results }); } return new Response("Not found", { status: 404 }); }

The library uses your val's own SQLite database — you don't need to run the API separately. Just import and use.


Part 6: Tips & Best Practices

✅ Do

  • Use meaningful IDs — blog-post-2024-01 is better than 1
  • Keep text focused — shorter, topic-focused chunks search better than walls of text
  • Use metadata — store category, date, author, tags etc. so you can filter later
  • Calibrate your threshold — use /calibrate?q=... before going to production
  • Batch your upserts — send arrays of records instead of one at a time (much faster)

❌ Avoid

  • Storing empty or near-duplicate text — SlimArmor deduplicates by content hash, so identical text won't re-embed, but similar-but-different text will generate redundant embeddings
  • Deleting via raw SQL — always use POST /clear or POST /delete so the vector index stays in sync
  • Switching providers without clearing — embeddings from different models are completely incompatible

💡 Good to know

  • Deduplication is automatic — if you upsert the same id with the same text, it skips the embedding API call and only updates metadata. You can safely re-run upserts.
  • Hybrid search helps with specific terms — if your data has product codes, names, or exact terms, enable hybrid: { enabled: true } to boost keyword matches.
  • /validate is your friend — run it after setup to confirm everything is working before adding real data.

Part 7: Troubleshooting

"Unauthorized" on write operations

Make sure you're sending the header: Authorization: Bearer YOUR_ADMIN_TOKEN

In the browser CLI, type auth your-token first.

"Embedding API error 401"

Your API key is wrong or expired. Go to your val's Settings → Environment Variables and update NEBIUS_API_KEY (or whichever provider you're using).

Search returns unexpected results

  1. Run /calibrate?q=your+query to see distance distributions
  2. Try lowering maxDistance
  3. Try enabling hybrid search: "hybrid": {"enabled": true}

"vector index(insert): failed to insert shadow row"

The DiskANN index got out of sync (happens if you manually deleted rows via SQL). Fix it with:

curl -X POST $ENDPOINT/reindex -H "Authorization: Bearer $TOKEN"

Inserts are slow

Normal — each batch of records requires one API call to the embedding provider (~460ms). For bulk imports, batch as many records as possible in each /upsert call (arrays of up to ~96 records per batch are ideal).


Quick Reference

ENDPOINT="https://YOUR_ENDPOINT" TOKEN="your-admin-token" # Health check curl $ENDPOINT/ping # View stats curl $ENDPOINT/stats # Add one record curl -X POST $ENDPOINT/upsert -H "Content-Type: application/json" -H "Authorization: Bearer $TOKEN" \ -d '{"id":"doc-1","text":"Your text here","meta":{"category":"notes"}}' # Add many records curl -X POST $ENDPOINT/upsert -H "Content-Type: application/json" -H "Authorization: Bearer $TOKEN" \ -d '[{"id":"a","text":"first"},{"id":"b","text":"second"}]' # Search curl -X POST $ENDPOINT/search -H "Content-Type: application/json" \ -d '{"query":"your query","k":10,"maxDistance":0.65}' # Search with filter curl -X POST $ENDPOINT/search -H "Content-Type: application/json" \ -d '{"query":"your query","k":10,"filters":{"category":"notes"}}' # Get a record curl "$ENDPOINT/get?id=doc-1" # List IDs curl "$ENDPOINT/list?limit=20" # Delete a record curl -X POST $ENDPOINT/delete -H "Content-Type: application/json" -H "Authorization: Bearer $TOKEN" \ -d '{"id":"doc-1"}' # Calibrate threshold curl "$ENDPOINT/calibrate?q=your+query" # Seed test data curl -H "Authorization: Bearer $TOKEN" "$ENDPOINT/seed?n=50" # Export curl -H "Authorization: Bearer $TOKEN" "$ENDPOINT/export?limit=500" # Clear all (careful!) curl -X POST "$ENDPOINT/clear?confirm=yes" -H "Authorization: Bearer $TOKEN" # Rebuild index curl -X POST $ENDPOINT/reindex -H "Authorization: Bearer $TOKEN"

Happy searching! 🔍 If you get stuck, open the /ui browser CLI and type help.

FeaturesVersion controlCode intelligenceCLIMCP
Use cases
TeamsAI agentsSlackGTM
DocsShowcaseTemplatesNewestTrendingAPI examplesNPM packages
PricingNewsletterBlogAboutCareers
We’re hiring!
Brandhi@val.townStatus
X (Twitter)
Discord community
GitHub discussions
YouTube channel
Bluesky
Open Source Pledge
Terms of usePrivacy policyAbuse contact
© 2026 Val Town, Inc.