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;
});
142 changes: 142 additions & 0 deletions examples/javascript/metadata-filters/shared.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
import { MossClient, type DocumentInfo } from "@moss-dev/moss";
import { randomUUID } from "node:crypto";
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)}-${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}`);
console.log(example.description);

console.log(`\nCreating temporary index: ${indexName}`);
cleanupNeeded = true;
await client.createIndex(indexName, documents, { modelId: "moss-minilm" });

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,
Comment thread
moroshani marked this conversation as resolved.
});

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 ?? {})}`);
});
} 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;
}
}
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