From 2060ba9ddb18b3eacd886732ee58bf2b217b0a30 Mon Sep 17 00:00:00 2001 From: Moroshani Date: Sat, 18 Jul 2026 11:59:07 +0330 Subject: [PATCH 1/3] docs(examples): add metadata filter operator snippets --- examples/javascript/README.md | 13 +++ examples/javascript/metadata-filters/and.ts | 16 +++ examples/javascript/metadata-filters/eq.ts | 14 +++ examples/javascript/metadata-filters/in.ts | 14 +++ examples/javascript/metadata-filters/near.ts | 14 +++ .../javascript/metadata-filters/shared.ts | 107 ++++++++++++++++++ examples/javascript/package.json | 4 + 7 files changed, 182 insertions(+) create mode 100644 examples/javascript/metadata-filters/and.ts create mode 100644 examples/javascript/metadata-filters/eq.ts create mode 100644 examples/javascript/metadata-filters/in.ts create mode 100644 examples/javascript/metadata-filters/near.ts create mode 100644 examples/javascript/metadata-filters/shared.ts diff --git a/examples/javascript/README.md b/examples/javascript/README.md index 470894e0..b562ca3a 100644 --- a/examples/javascript/README.md +++ b/examples/javascript/README.md @@ -54,6 +54,19 @@ Provision a fresh index (using the name supplied via `MOSS_INDEX_NAME`), push do npx tsx custom_embedding_sample.ts ``` +### Metadata Filter Operator Samples + +Create a temporary index with metadata-rich product documents, load it locally, +run one filtered query, then delete the temporary index. Each script focuses on +one operator so the filter object is easy to copy into another app. + +```bash +npm run metadata:eq +npm run metadata:and +npm run metadata:in +npm run metadata:near +``` + ### Session Sample Open a local-first `SessionIndex`, add documents in real time (no cloud round trip), query the in-memory index, then `pushIndex` to the cloud so another agent or device can resume it. This is how Moss indexes a live conversation. diff --git a/examples/javascript/metadata-filters/and.ts b/examples/javascript/metadata-filters/and.ts new file mode 100644 index 00000000..ff854e7e --- /dev/null +++ b/examples/javascript/metadata-filters/and.ts @@ -0,0 +1,16 @@ +import { runMetadataFilterExample } from "./shared"; + +runMetadataFilterExample({ + operator: "$and", + description: "Use $and to require multiple metadata filters to match together.", + query: "running shoes available in New York", + filter: { + $and: [ + { field: "category", condition: { $eq: "shoes" } }, + { field: "city", condition: { $eq: "new-york" } }, + ], + }, +}).catch((error) => { + console.error(error); + process.exitCode = 1; +}); diff --git a/examples/javascript/metadata-filters/eq.ts b/examples/javascript/metadata-filters/eq.ts new file mode 100644 index 00000000..98cc4546 --- /dev/null +++ b/examples/javascript/metadata-filters/eq.ts @@ -0,0 +1,14 @@ +import { runMetadataFilterExample } from "./shared"; + +runMetadataFilterExample({ + operator: "$eq", + description: "Use $eq to match documents whose metadata field has one exact value.", + query: "running shoes for city training", + filter: { + field: "category", + condition: { $eq: "shoes" }, + }, +}).catch((error) => { + console.error(error); + process.exitCode = 1; +}); diff --git a/examples/javascript/metadata-filters/in.ts b/examples/javascript/metadata-filters/in.ts new file mode 100644 index 00000000..2b044b07 --- /dev/null +++ b/examples/javascript/metadata-filters/in.ts @@ -0,0 +1,14 @@ +import { runMetadataFilterExample } from "./shared"; + +runMetadataFilterExample({ + operator: "$in", + description: "Use $in to match documents whose metadata value is in an allowed list.", + query: "portable gear for commuters", + filter: { + field: "city", + condition: { $in: ["new-york", "seattle"] }, + }, +}).catch((error) => { + console.error(error); + process.exitCode = 1; +}); diff --git a/examples/javascript/metadata-filters/near.ts b/examples/javascript/metadata-filters/near.ts new file mode 100644 index 00000000..0d604fa9 --- /dev/null +++ b/examples/javascript/metadata-filters/near.ts @@ -0,0 +1,14 @@ +import { runMetadataFilterExample } from "./shared"; + +runMetadataFilterExample({ + operator: "$near", + description: "Use $near to match location metadata within a radius in meters.", + query: "city products near Times Square", + filter: { + field: "location", + condition: { $near: "40.7580,-73.9855,5000" }, + }, +}).catch((error) => { + console.error(error); + process.exitCode = 1; +}); diff --git a/examples/javascript/metadata-filters/shared.ts b/examples/javascript/metadata-filters/shared.ts new file mode 100644 index 00000000..f08b2822 --- /dev/null +++ b/examples/javascript/metadata-filters/shared.ts @@ -0,0 +1,107 @@ +import { MossClient, type DocumentInfo } from "@moss-dev/moss"; +import { config } from "dotenv"; + +config(); + +type MetadataFilter = Record; + +interface MetadataFilterExample { + operator: string; + description: string; + query: string; + filter: MetadataFilter; +} + +const documents: DocumentInfo[] = [ + { + id: "shoe-nyc-001", + text: "Breathable road running shoes for daily city training.", + metadata: { + category: "shoes", + brand: "swiftfit", + city: "new-york", + location: "40.7580,-73.9855", + }, + }, + { + id: "shoe-denver-002", + text: "Trail running shoes built for rocky mountain paths.", + metadata: { + category: "shoes", + brand: "peakstride", + city: "denver", + location: "39.7392,-104.9903", + }, + }, + { + id: "bag-nyc-003", + text: "Lightweight commuter backpack with a laptop sleeve.", + metadata: { + category: "bags", + brand: "urbanpack", + city: "new-york", + location: "40.7505,-73.9934", + }, + }, + { + id: "bag-seattle-004", + text: "Compact travel duffel with a separate shoe compartment.", + metadata: { + category: "bags", + brand: "swiftfit", + city: "seattle", + location: "47.6205,-122.3493", + }, + }, +]; + +function requireEnv(name: string): string { + const value = process.env[name]?.trim(); + + if (!value) { + throw new Error(`Missing ${name}. Add it to .env before running this example.`); + } + + return value; +} + +export async function runMetadataFilterExample(example: MetadataFilterExample): Promise { + const projectId = requireEnv("MOSS_PROJECT_ID"); + const projectKey = requireEnv("MOSS_PROJECT_KEY"); + const indexName = `metadata-filter-${example.operator.slice(1)}-${Date.now()}`; + const client = new MossClient(projectId, projectKey); + let indexCreated = false; + + try { + console.log(`Moss metadata filter example: ${example.operator}`); + console.log(example.description); + + console.log(`\nCreating temporary index: ${indexName}`); + await client.createIndex(indexName, documents, { modelId: "moss-minilm" }); + indexCreated = true; + + console.log("Loading index locally for filtered query..."); + await client.loadIndex(indexName); + + console.log(`\nQuery: ${example.query}`); + console.log(`Filter: ${JSON.stringify(example.filter)}`); + + const results = await client.query(indexName, example.query, { + topK: 5, + alpha: 0.5, + filter: example.filter, + }); + + console.log(`\nFound ${results.docs.length} result(s) in ${results.timeTakenInMs}ms:`); + results.docs.forEach((doc, index) => { + const preview = doc.text.length > 80 ? `${doc.text.slice(0, 80)}...` : doc.text; + console.log(`${index + 1}. [${doc.id}] score=${doc.score.toFixed(3)} ${preview}`); + console.log(` metadata=${JSON.stringify(doc.metadata ?? {})}`); + }); + } finally { + if (indexCreated) { + console.log(`\nDeleting temporary index: ${indexName}`); + await client.deleteIndex(indexName); + } + } +} diff --git a/examples/javascript/package.json b/examples/javascript/package.json index 9de30e45..d8f4911a 100644 --- a/examples/javascript/package.json +++ b/examples/javascript/package.json @@ -39,6 +39,10 @@ "session": "tsx session_sample.ts", "session-cache": "tsx session_cache_sample.ts", "session-custom-auth": "tsx session_custom_auth_sample.ts", + "metadata:eq": "tsx metadata-filters/eq.ts", + "metadata:and": "tsx metadata-filters/and.ts", + "metadata:in": "tsx metadata-filters/in.ts", + "metadata:near": "tsx metadata-filters/near.ts", "test": "echo \"Error: no test specified\" && exit 1" }, "dependencies": { From de35eea63ff06c665e511a1d5baa76a282733db9 Mon Sep 17 00:00:00 2001 From: Moroshani Date: Wed, 29 Jul 2026 12:27:44 +0330 Subject: [PATCH 2/3] fix(examples): make metadata filter cleanup robust --- examples/javascript/metadata-filters/shared.ts | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/examples/javascript/metadata-filters/shared.ts b/examples/javascript/metadata-filters/shared.ts index f08b2822..7ca061a2 100644 --- a/examples/javascript/metadata-filters/shared.ts +++ b/examples/javascript/metadata-filters/shared.ts @@ -70,15 +70,15 @@ export async function runMetadataFilterExample(example: MetadataFilterExample): const projectKey = requireEnv("MOSS_PROJECT_KEY"); const indexName = `metadata-filter-${example.operator.slice(1)}-${Date.now()}`; const client = new MossClient(projectId, projectKey); - let indexCreated = false; + let cleanupNeeded = false; try { console.log(`Moss metadata filter example: ${example.operator}`); console.log(example.description); console.log(`\nCreating temporary index: ${indexName}`); + cleanupNeeded = true; await client.createIndex(indexName, documents, { modelId: "moss-minilm" }); - indexCreated = true; console.log("Loading index locally for filtered query..."); await client.loadIndex(indexName); @@ -92,16 +92,21 @@ export async function runMetadataFilterExample(example: MetadataFilterExample): filter: example.filter, }); - console.log(`\nFound ${results.docs.length} result(s) in ${results.timeTakenInMs}ms:`); + const timing = results.timeTakenInMs === undefined ? "" : ` in ${results.timeTakenInMs}ms`; + console.log(`\nFound ${results.docs.length} result(s)${timing}:`); results.docs.forEach((doc, index) => { const preview = doc.text.length > 80 ? `${doc.text.slice(0, 80)}...` : doc.text; console.log(`${index + 1}. [${doc.id}] score=${doc.score.toFixed(3)} ${preview}`); console.log(` metadata=${JSON.stringify(doc.metadata ?? {})}`); }); } finally { - if (indexCreated) { + if (cleanupNeeded) { console.log(`\nDeleting temporary index: ${indexName}`); - await client.deleteIndex(indexName); + try { + await client.deleteIndex(indexName); + } catch (cleanupError) { + console.warn(`Failed to delete temporary index: ${indexName}`, cleanupError); + } } } } From 5a0219cf655320b7cb69c39799f0970e7ab4bda0 Mon Sep 17 00:00:00 2001 From: Moroshani Date: Thu, 13 Aug 2026 09:51:26 +0330 Subject: [PATCH 3/3] Address metadata example cleanup review --- .../javascript/metadata-filters/shared.ts | 56 ++++++++++++++----- 1 file changed, 43 insertions(+), 13 deletions(-) diff --git a/examples/javascript/metadata-filters/shared.ts b/examples/javascript/metadata-filters/shared.ts index 7ca061a2..deb5911f 100644 --- a/examples/javascript/metadata-filters/shared.ts +++ b/examples/javascript/metadata-filters/shared.ts @@ -1,4 +1,5 @@ import { MossClient, type DocumentInfo } from "@moss-dev/moss"; +import { randomUUID } from "node:crypto"; import { config } from "dotenv"; config(); @@ -59,18 +60,24 @@ function requireEnv(name: string): string { const value = process.env[name]?.trim(); if (!value) { - throw new Error(`Missing ${name}. Add it to .env before running this example.`); + throw new Error( + `Missing ${name}. Add it to .env before running this example.`, + ); } return value; } -export async function runMetadataFilterExample(example: MetadataFilterExample): Promise { +export async function runMetadataFilterExample( + example: MetadataFilterExample, +): Promise { const projectId = requireEnv("MOSS_PROJECT_ID"); const projectKey = requireEnv("MOSS_PROJECT_KEY"); - const indexName = `metadata-filter-${example.operator.slice(1)}-${Date.now()}`; + const indexName = `metadata-filter-${example.operator.slice(1)}-${randomUUID()}`; const client = new MossClient(projectId, projectKey); let cleanupNeeded = false; + let operationError: unknown; + let operationFailed = false; try { console.log(`Moss metadata filter example: ${example.operator}`); @@ -92,21 +99,44 @@ export async function runMetadataFilterExample(example: MetadataFilterExample): filter: example.filter, }); - const timing = results.timeTakenInMs === undefined ? "" : ` in ${results.timeTakenInMs}ms`; + const timing = + results.timeTakenInMs === undefined + ? "" + : ` in ${results.timeTakenInMs}ms`; console.log(`\nFound ${results.docs.length} result(s)${timing}:`); results.docs.forEach((doc, index) => { - const preview = doc.text.length > 80 ? `${doc.text.slice(0, 80)}...` : doc.text; - console.log(`${index + 1}. [${doc.id}] score=${doc.score.toFixed(3)} ${preview}`); + const preview = + doc.text.length > 80 ? `${doc.text.slice(0, 80)}...` : doc.text; + console.log( + `${index + 1}. [${doc.id}] score=${doc.score.toFixed(3)} ${preview}`, + ); console.log(` metadata=${JSON.stringify(doc.metadata ?? {})}`); }); - } finally { - if (cleanupNeeded) { - console.log(`\nDeleting temporary index: ${indexName}`); - try { - await client.deleteIndex(indexName); - } catch (cleanupError) { - console.warn(`Failed to delete temporary index: ${indexName}`, cleanupError); + } catch (error) { + operationError = error; + operationFailed = true; + } + + if (cleanupNeeded) { + console.log(`\nDeleting temporary index: ${indexName}`); + try { + const deleted = await client.deleteIndex(indexName); + if (!deleted) { + throw new Error(`Moss did not delete temporary index: ${indexName}`); + } + } catch (cleanupError) { + if (operationFailed) { + console.warn( + `Failed to delete temporary index: ${indexName}`, + cleanupError, + ); + } else { + throw cleanupError; } } } + + if (operationFailed) { + throw operationError; + } }