Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions examples/javascript/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
16 changes: 16 additions & 0 deletions examples/javascript/metadata-filters/and.ts
Original file line number Diff line number Diff line change
@@ -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;
});
14 changes: 14 additions & 0 deletions examples/javascript/metadata-filters/eq.ts
Original file line number Diff line number Diff line change
@@ -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;
});
14 changes: 14 additions & 0 deletions examples/javascript/metadata-filters/in.ts
Original file line number Diff line number Diff line change
@@ -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;
});
14 changes: 14 additions & 0 deletions examples/javascript/metadata-filters/near.ts
Original file line number Diff line number Diff line change
@@ -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;
});
107 changes: 107 additions & 0 deletions examples/javascript/metadata-filters/shared.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import { MossClient, type DocumentInfo } from "@moss-dev/moss";
import { config } from "dotenv";

config();

type MetadataFilter = Record<string, unknown>;

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<void> {
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;

@cubic-dev-ai cubic-dev-ai Bot Jul 18, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: A failure during upload, build, or polling can leave the remotely initialized temporary index behind because indexCreated remains false until the entire multi-stage createIndex call resolves. Cleanup eligibility should be tracked once remote creation may have started, with a best-effort existence check/delete on failure.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At examples/javascript/metadata-filters/shared.ts, line 81:

<comment>A failure during upload, build, or polling can leave the remotely initialized temporary index behind because `indexCreated` remains false until the entire multi-stage `createIndex` call resolves. Cleanup eligibility should be tracked once remote creation may have started, with a best-effort existence check/delete on failure.</comment>

<file context>
@@ -0,0 +1,107 @@
+
+    console.log(`\nCreating temporary index: ${indexName}`);
+    await client.createIndex(indexName, documents, { modelId: "moss-minilm" });
+    indexCreated = true;
+
+    console.log("Loading index locally for filtered query...");
</file context>
Fix with cubic


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:`);

@cubic-dev-ai cubic-dev-ai Bot Jul 18, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: Results that omit optional timeTakenInMs print in undefinedms. Formatting the timing suffix conditionally keeps the runnable example's output valid for every SearchResult.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At examples/javascript/metadata-filters/shared.ts, line 95:

<comment>Results that omit optional `timeTakenInMs` print `in undefinedms`. Formatting the timing suffix conditionally keeps the runnable example's output valid for every `SearchResult`.</comment>

<file context>
@@ -0,0 +1,107 @@
+      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;
</file context>
Fix with cubic

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);

@cubic-dev-ai cubic-dev-ai Bot Jul 18, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: When loading or querying fails and deletion also fails, the finally rejection replaces the original error, hiding the actual example failure. Preserve the primary exception while reporting the cleanup error separately.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At examples/javascript/metadata-filters/shared.ts, line 104:

<comment>When loading or querying fails and deletion also fails, the `finally` rejection replaces the original error, hiding the actual example failure. Preserve the primary exception while reporting the cleanup error separately.</comment>

<file context>
@@ -0,0 +1,107 @@
+  } finally {
+    if (indexCreated) {
+      console.log(`\nDeleting temporary index: ${indexName}`);
+      await client.deleteIndex(indexName);
+    }
+  }
</file context>
Fix with cubic

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CONSIDER ```ts
await client.deleteIndex(indexName);

If create/load/query throws and `deleteIndex` also rejects, the `finally` rejection replaces the original error, so the sample reports only the cleanup failure. Preserve the primary error by catching cleanup separately, for example `try { await client.deleteIndex(indexName); } catch (cleanupError) { console.warn("Failed to delete temporary index", cleanupError); }`.

}
}
Comment on lines +101 to +106
}
4 changes: 4 additions & 0 deletions examples/javascript/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Expand Down
Loading