montycat 1.1.5 β†’ 1.1.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,12 +1,33 @@
1
- # πŸš€ Montycat Node.js Client β€” High-Performance NoSQL for the Async Era
1
+ # πŸš€ Montycat for Node.js & TypeScript β€” The AI-Native NoSQL Database with Semantic Search for RAG & Agents
2
+
3
+ ### Abolish the two-database stack.
4
+
5
+ The official Node.js & TypeScript SDK for [Montycat](https://montygovernance.com) β€” a self-hosted **NoSQL + vector database** with AI **semantic search** forged into the core, built for **RAG and AI-agent memory**. One Rust engine, not a sprawl of services. **Your hardware. Your data. Your meaning.**
2
6
 
3
7
  [![npm version](https://img.shields.io/npm/v/montycat.svg)](https://www.npmjs.com/package/montycat)
4
8
  [![npm downloads](https://img.shields.io/npm/dt/montycat.svg)](https://www.npmjs.com/package/montycat)
5
- [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
9
+ [![Docker Pulls](https://img.shields.io/docker/pulls/montygovernance/montycat)](https://hub.docker.com/r/montygovernance/montycat)
6
10
  [![Node.js](https://img.shields.io/node/v/montycat.svg)](https://nodejs.org/)
7
- [![TypeScript](https://img.shields.io/badge/TS-ready-blue.svg)]()
11
+ [![TypeScript](https://img.shields.io/badge/TypeScript-ready-blue.svg)](https://www.typescriptlang.org/)
12
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/MontyGovernance/montycat_node/blob/master/LICENSE)
13
+
14
+ ```typescript
15
+ // Search your data by MEANING β€” no external APIs, no separate vector database.
16
+ // (already ON by default in the montycat-semantic server edition)
17
+ const hits = await Sales.semanticSearchGetValues({ query: 'Show all Bluetooth devices', limitOutput: { start: 0, stop: 5 } });
18
+ // β†’ [{ __key__, __score__, __value__: { name: 'Wireless Headphones' } }, ...] (matched by meaning, not keywords)
19
+ ```
20
+
21
+ > ### 🧩 All-in-one. AI-native. **Zero external dependencies.**
22
+ > The vector-embedding engine runs **inside** the database β€” **no** separate vector DB, **no** embedding API, **no** API keys, **no** sidecar service. One engine, one binary, your hardware.
23
+
24
+ ## What is Montycat?
25
+
26
+ For a generation we were told the price of intelligence was two systems: a database for your records, and a separate vector store β€” with its per-query bill β€” for their meaning. Montycat rejects that tax. It is a **self-hosted NoSQL + vector database**: one Rust-powered engine with semantic search built in, so **RAG, AI-agent memory, and vector search** live where your data already lives. No cloud lock-in. No ops headache. Decentralized by nature, ultra-fast, and natively async.
8
27
 
9
- Montycat Node.js Client is the official JavaScript and TypeScript SDK for Montycat β€” the next-generation NoSQL engine built on Data Mesh architecture and written in Rust for blazing-fast performance. It gives Node.js developers the power to work with structured, decentralized, and secure data using a clean, asynchronous API that feels native to JavaScript β€” not like another bloated driver. Montycat is more than a database β€” it’s a redefinition of how data should feel in modern, real-time systems.
28
+ Think of it as an **open-source, self-hosted alternative to Pinecone, Weaviate, Chroma, Qdrant, and Redis** β€” a **vector database _and_ a NoSQL store in a single engine**, so your records and their embeddings live together instead of in two systems you have to keep in sync.
29
+
30
+ This client gives JavaScript and TypeScript developers structured, decentralized, secure data through a clean async API that feels native to the language β€” not another bloated driver. Montycat is not an incremental improvement on the databases you know. It is a break with them.
10
31
 
11
32
  ## 🧭 The Montycat Philosophy
12
33
 
@@ -27,6 +48,7 @@ Montycat Node.js Client is the official JavaScript and TypeScript SDK for Montyc
27
48
  - πŸ“‘ `Real-Time Subscriptions` - Subscribe to keyspace or key changes with live updates.
28
49
  - πŸ” `TLS Security` - Encrypted communication and authenticated connections.
29
50
  - 🧬 `Schema Support` - Optional schema layer for validation, structure, and type safety.
51
+ - 🧠 `AI Semantic & Vector Search` - Rank stored items by meaning with on-device embeddings. kNN vector search for **RAG, AI agents & LLM apps** β€” no external API, no separate vector database.
30
52
  - 🧱 `Zero Dependencies on ORMs` - No extra abstractions. Just pure, beautiful logic.
31
53
  - 🧠 `Easy Integration` - Works with Express, Fastify, Next.js, Deno, and any Node.js runtime.
32
54
 
@@ -45,22 +67,37 @@ Montycat Node.js Client is the official JavaScript and TypeScript SDK for Montyc
45
67
 
46
68
  ## 🏁 The Future Is Structured
47
69
 
48
- Other databases are written in C, C++, or Java.
49
- `Montycat` was built with Rust β€” and made accessible to JavaScript / TypeScript.
50
- No adapters, no legacy baggage β€” just pure async data flow.
70
+ Other databases were written in C, C++, or Java β€” for a world that no longer exists.
71
+ `Montycat` was built in Rust and made to speak JavaScript and TypeScript fluently.
72
+ No adapters. No legacy baggage. No permission asked. Just pure async data flow.
73
+
74
+ The old stack β€” a database here, a vector store there, an embedding API somewhere in the cloud, and a tangle of sync jobs holding it together β€” is not an architecture. It is a compromise. Montycat refuses the compromise: **one engine, your hardware, your data, your meaning.**
75
+
76
+ > **When you use Montycat, you are not querying a database. You are interacting with a living data mesh that speaks your language.**
77
+
78
+ ## πŸ” Example Use Cases
51
79
 
52
- ## ⚑ Benchmarks (Node.js)
80
+ - **RAG pipelines & semantic retrieval** for LLM-powered apps
81
+ - **AI agent / chatbot long-term memory** that survives restarts
82
+ - **Semantic product search & recommendations** β€” match intent, not keywords
83
+ - Real-time dashboards, analytics, and live collaborative apps
84
+ - Microservice data stores and event-driven systems
85
+ - Data products in a decentralized Mesh architecture
53
86
 
54
- | Operation | Throughput |
55
- | ------------------------------ | ------------------------ |
56
- | Insert (10k records) | 40k/sec |
57
- | Lookup (single key) | 80k/sec |
58
- | Lookup (filtered query) | 30k/sec |
59
- | Real-Time subscription updates | < 2ms latency per update |
87
+ ## πŸš€ Get the Engine (30 seconds)
60
88
 
61
- Performance depends on engine deployment, network, and schema complexity, but Node.js client overhead is negligible thanks to Rust core.
89
+ The client talks to a Montycat server. Fastest way β€” Docker, with AI semantic search built in:
90
+
91
+ ```bash
92
+ docker run -d --name montycat \
93
+ -p 21210:21210 -p 21211:21211 \
94
+ -e MONTYCAT_SUPEROWNER="admin" \
95
+ -e MONTYCAT_PASSWORD="change-me" \
96
+ -v montycat_data:/app/.montycat \
97
+ montygovernance/montycat:semantic
98
+ ```
62
99
 
63
- ## When you use Montycat, you’re NOT querying a database. You’re interacting with a living data mesh that feels native to your language. πŸ‘‰ Learn more about Montycat Engine https://montygovernance.com
100
+ Prefer the lean edition without the embedding engine? Use the `latest` tag. Prebuilt packages (apt, macOS, Windows) at **https://montygovernance.com**.
64
101
 
65
102
  ## Installation
66
103
 
@@ -74,6 +111,10 @@ or with Yarn:
74
111
  yarn add montycat
75
112
  ```
76
113
 
114
+ > **ESM-only, Node.js 18+.** This package ships native ES modules β€” use `import`, not
115
+ > `require()`. Works out of the box with TypeScript, Deno, Bun, and any modern Node.js
116
+ > project (`"type": "module"` or `.mjs`).
117
+
77
118
  ## Quick Start
78
119
 
79
120
  ### TypeScript Example
@@ -151,7 +192,7 @@ const newSale = new SalesSchema({
151
192
 
152
193
  const newOrder = new ProductionSchema({
153
194
  workOrder: 'WO 000012',
154
- amount: 100,
195
+ customer: 'ACME Corp',
155
196
  }).serialize();
156
197
 
157
198
  const res3 = await Sales.insertValue({ value: newSale });
@@ -167,12 +208,12 @@ const res5 = await Sales.lookupValuesWhere({
167
208
  });
168
209
 
169
210
  const res6 = await Production.lookupValuesWhere({
170
- searchCriteria: { amount: 100 },
211
+ searchCriteria: { customer: 'ACME Corp' },
171
212
  schema: ProductionSchema,
172
213
  keyIncluded: true
173
214
  });
174
215
 
175
- console.log('Lookup results:', res3, res4);
216
+ console.log('Lookup results:', res5, res6);
176
217
 
177
218
  ```
178
219
 
@@ -233,7 +274,7 @@ const newSale = new SalesSchema({
233
274
 
234
275
  const newOrder = new ProductionSchema({
235
276
  workOrder: 'WO 000012',
236
- amount: 100,
277
+ customer: 'ACME Corp',
237
278
  }).serialize();
238
279
 
239
280
  const res3 = await Sales.insertValue({ value: newSale });
@@ -249,11 +290,71 @@ const res5 = await Sales.lookupValuesWhere({
249
290
  });
250
291
 
251
292
  const res6 = await Production.lookupValuesWhere({
252
- searchCriteria: { amount: 100 },
253
- schema: ProductionSchema
293
+ searchCriteria: { customer: 'ACME Corp' },
294
+ schema: ProductionSchema,
254
295
  keyIncluded: true
255
296
  });
256
297
 
257
- console.log('Lookup results:', res3, res4);
298
+ console.log('Lookup results:', res5, res6);
299
+
300
+ ```
301
+
302
+ ## 🧠 AI-Native Semantic Search β€” Vector Search Built Into Your Database
303
+
304
+ **Stop bolting a separate vector database onto your stack.** Montycat ranks your data by
305
+ *meaning*, not keywords β€” an embedded, on-device vector-embedding engine turns every write
306
+ into a searchable vector automatically. It's the retrieval layer for **RAG pipelines, AI
307
+ agents, semantic search, recommendation engines, and LLM-powered apps** β€” with **zero
308
+ external APIs, zero API keys, and zero extra infrastructure.**
309
+
310
+ - πŸ”Ž **Semantic / vector search** β€” kNN similarity over on-device embeddings, not brittle keyword matches.
311
+ - πŸ€– **Built for AI** β€” RAG, semantic retrieval, AI agents, recommendations, dedup, clustering.
312
+ - πŸ”’ **Private & free** β€” embeddings never leave your machine. No OpenAI/Cohere bill, no data egress.
313
+ - ⚑ **One system, not two** β€” your data *and* its vectors live in the same database. No sync jobs, no drift, no second service to run.
314
+ - πŸš€ **Zero setup** β€” no index tuning, no pipeline: `enableSemanticSearch()` and you're ranking by meaning.
315
+
316
+ > **⚠️ Requires the semantic edition of the server β€” nothing to compile.** Semantic
317
+ > search runs an embedded ONNX vector-embedding engine that ships only in the
318
+ > **`montycat-semantic`** edition; the default lean `montycat` server does not include it.
319
+ > Get it the way that suits you β€” pull the **Docker image**
320
+ > (`montygovernance/montycat:semantic`), download the prebuilt **package**, or install
321
+ > `montycat-semantic` from the **apt repository**. The Node.js client API is identical
322
+ > either way; just point it at a semantic-edition server (semantic search is enabled by
323
+ > default there, using the `bge-small` model).
324
+
325
+ The switch is DB-wide and already on in the semantic edition; every keyspace is embedded
326
+ in the background as data is written (the embedding model is downloaded on demand).
327
+
328
+ ```typescript
329
+ // Semantic search is ON by default in the montycat-semantic edition β€” just search.
330
+ // Rank stored items by meaning β€” two flavors:
331
+ // getValues β†’ each hit is { __key__, __score__, __value__ }
332
+ // getKeys β†’ each hit is { __key__, __score__ } (lighter; fetch a page later with getBulk)
333
+ const hits = await Sales.semanticSearchGetValues({ query: 'Show all Bluetooth devices', limitOutput: { start: 0, stop: 5 } });
334
+ const keys = await Sales.semanticSearchGetKeys({ query: 'Show all Bluetooth devices', limitOutput: { start: 0, stop: 5 } });
335
+
336
+ // Optionally drop weak matches by cosine similarity (range [-1, 1]).
337
+ const strong = await Sales.semanticSearchGetKeys({ query: 'Show all Bluetooth devices', limitOutput: { start: 0, stop: 5 }, minScore: 0.35 });
338
+
339
+ // Control the DB-wide switch (optional β€” it's already on):
340
+ // switch the embedding model: 'minilm' | 'bge-small' (default) | 'bge-base' | 'e5-small'
341
+ await engine.enableSemanticSearch({ model: 'bge-base' });
342
+
343
+ // turn it off (vectors are kept so re-enabling resumes instantly;
344
+ // pass { dropVectors: true } to also clear stored vectors)
345
+ await engine.disableSemanticSearch();
346
+ ```
347
+
348
+ ## πŸ”— Links
349
+
350
+ - 🌐 **Website & Docs** β€” https://montygovernance.com
351
+ - πŸ“¦ **npm** β€” https://www.npmjs.com/package/montycat
352
+ - 🐳 **Docker Hub** β€” https://hub.docker.com/r/montygovernance/montycat
353
+ - πŸ’» **Source** β€” https://github.com/MontyGovernance/montycat_node
354
+
355
+ ## ❓ FAQ
258
356
 
259
- ```
357
+ - **Is Montycat a vector database or a NoSQL database?** Both β€” one engine. Store records and query them by *meaning* (vector / semantic search) or by key/schema, without running two systems.
358
+ - **Do I need OpenAI or an embedding API?** No. Embeddings run on-device in the `montycat-semantic` server. No API keys, no per-query bill, no data egress.
359
+ - **Is it a Pinecone / Weaviate / Chroma / Qdrant alternative?** Yes β€” self-hosted and open-source, with a NoSQL store built in.
360
+ - **TypeScript support?** First-class β€” the package ships its own type definitions. Works with Node.js, Deno, Bun, Express, Fastify, and Next.js.
@@ -52,9 +52,10 @@ declare class GenericKV {
52
52
  * @param customKey - The custom key to delete.
53
53
  * @return A promise that resolves with the result of the deletion.
54
54
  * */
55
- static deleteKey({ key, customKey }?: {
55
+ static deleteKey({ key, customKey, waitForIndex }?: {
56
56
  key?: string;
57
57
  customKey?: string | null;
58
+ waitForIndex?: boolean | null;
58
59
  }): Promise<any>;
59
60
  /**
60
61
  * Deletes a bulk of keys from the keyspace.
@@ -62,9 +63,10 @@ declare class GenericKV {
62
63
  * @param bulkCustomKeys - An array of custom keys to delete.
63
64
  * @return A promise that resolves with the result of the deletion.
64
65
  * */
65
- static deleteBulk({ bulkKeys, bulkCustomKeys }?: {
66
+ static deleteBulk({ bulkKeys, bulkCustomKeys, waitForIndex }?: {
66
67
  bulkKeys?: string[];
67
68
  bulkCustomKeys?: string[];
69
+ waitForIndex?: boolean | null;
68
70
  }): Promise<any>;
69
71
  /**
70
72
  * Gets bulk values from the keyspace.
@@ -97,13 +99,14 @@ declare class GenericKV {
97
99
  * @param bulkCustomKeysValues - An object where custom keys are the keys to update and values are the new values.
98
100
  * @return A promise that resolves with the result of the update.
99
101
  */
100
- static updateBulk({ bulkKeysValues, bulkCustomKeysValues }?: {
102
+ static updateBulk({ bulkKeysValues, bulkCustomKeysValues, waitForIndex }?: {
101
103
  bulkKeysValues?: {
102
104
  [key: string]: any;
103
105
  };
104
106
  bulkCustomKeysValues?: {
105
107
  [key: string]: any;
106
108
  };
109
+ waitForIndex?: boolean | null;
107
110
  }): Promise<any>;
108
111
  /**
109
112
  * Looks up keys based on search criteria.
@@ -145,6 +148,76 @@ declare class GenericKV {
145
148
  keyIncluded?: boolean;
146
149
  pointersMetadata?: boolean;
147
150
  }): Promise<any>;
151
+ /**
152
+ * Shared core for `semanticSearchGetKeys` / `semanticSearchGetValues`.
153
+ * The server command is the same either way (`semantic_search`); the two
154
+ * public methods differ only in which value-inclusion flags they pass,
155
+ * so the wire call lives here once.
156
+ */
157
+ private static semanticSearchCore;
158
+ /**
159
+ * Semantic (vector similarity) search returning ranked keys only.
160
+ *
161
+ * Ranks stored items by how close their embeddings are to the embedding of
162
+ * `query` and returns just the matched key and score for each hit β€” the
163
+ * lightweight variant when you only need identity + ranking (e.g. to then
164
+ * `getBulk` a page, or to test membership). Use `semanticSearchGetValues`
165
+ * when you want the values inline.
166
+ *
167
+ * Semantic search must be enabled first (see `Engine.enableSemanticSearch`).
168
+ * The keyspace is embedded in the background as items are written, so
169
+ * results reflect whatever has been embedded so far.
170
+ *
171
+ * @param query - The natural-language query text to embed and search for.
172
+ * @param limitOutput - Start/stop over the ranked hits; `{start: 0, stop: 0}`
173
+ * (the default) lets the server apply its default top-k (10).
174
+ * @param minScore - Drop hits whose cosine similarity (in [-1, 1]) is below
175
+ * this value. Default null (no score filter).
176
+ * @return A promise resolving with ranked hits, each `{__key__, __score__}`.
177
+ */
178
+ static semanticSearchGetKeys({ query, limitOutput, minScore }: {
179
+ query: string;
180
+ limitOutput?: {
181
+ start: number;
182
+ stop: number;
183
+ };
184
+ minScore?: number | null;
185
+ }): Promise<any>;
186
+ /**
187
+ * Semantic (vector similarity) search returning ranked hits with their values.
188
+ *
189
+ * Ranks stored items by how close their embeddings are to the embedding of
190
+ * `query` and returns the value inline with each hit β€” the key is always
191
+ * included so every value is tagged with its key. Use
192
+ * `semanticSearchGetKeys` when you only need keys + scores.
193
+ *
194
+ * Semantic search must be enabled first (see `Engine.enableSemanticSearch`).
195
+ * The keyspace is embedded in the background as items are written, so
196
+ * results reflect whatever has been embedded so far.
197
+ *
198
+ * @param query - The natural-language query text to embed and search for.
199
+ * @param limitOutput - Start/stop over the ranked hits; `{start: 0, stop: 0}`
200
+ * (the default) lets the server apply its default top-k (10).
201
+ * @param minScore - Drop hits whose cosine similarity (in [-1, 1]) is below
202
+ * this value. Default null (no score filter).
203
+ * @param withPointers - Whether to include pointers (foreign values) in each
204
+ * returned value.
205
+ * @param pointersMetadata - Whether to include pointer metadata in each
206
+ * returned value.
207
+ * @return A promise resolving with ranked hits, each
208
+ * `{__key__, __score__, __value__}` β€” the same dunder envelope
209
+ * `lookupValuesWhere` returns with `keyIncluded: true`, plus the score.
210
+ */
211
+ static semanticSearchGetValues({ query, limitOutput, minScore, withPointers, pointersMetadata }: {
212
+ query: string;
213
+ limitOutput?: {
214
+ start: number;
215
+ stop: number;
216
+ };
217
+ minScore?: number | null;
218
+ withPointers?: boolean;
219
+ pointersMetadata?: boolean;
220
+ }): Promise<any>;
148
221
  /**
149
222
  * Gets the length of the keyspace.
150
223
  * @returns A promise that resolves with the length of the keyspace.
@@ -86,7 +86,7 @@ class GenericKV {
86
86
  * @param customKey - The custom key to delete.
87
87
  * @return A promise that resolves with the result of the deletion.
88
88
  * */
89
- static async deleteKey({ key = "", customKey = null } = {}) {
89
+ static async deleteKey({ key = "", customKey = null, waitForIndex = null } = {}) {
90
90
  try {
91
91
  if (key && customKey) {
92
92
  throw new Error("Provide either 'key' or 'customKey', not both.");
@@ -97,7 +97,7 @@ class GenericKV {
97
97
  throw new Error("No key provided");
98
98
  }
99
99
  this.command = "delete_key";
100
- const query = convertToBinaryQuery(this, { key });
100
+ const query = convertToBinaryQuery(this, { key, waitForIndex });
101
101
  return runQuery(this, query);
102
102
  }
103
103
  catch (err) {
@@ -110,7 +110,7 @@ class GenericKV {
110
110
  * @param bulkCustomKeys - An array of custom keys to delete.
111
111
  * @return A promise that resolves with the result of the deletion.
112
112
  * */
113
- static async deleteBulk({ bulkKeys = [], bulkCustomKeys = [] } = {}) {
113
+ static async deleteBulk({ bulkKeys = [], bulkCustomKeys = [], waitForIndex = null } = {}) {
114
114
  try {
115
115
  if (bulkCustomKeys.length)
116
116
  bulkKeys = bulkKeys.concat(convertCustomKeys(bulkCustomKeys));
@@ -118,7 +118,7 @@ class GenericKV {
118
118
  throw new Error("No keys provided");
119
119
  }
120
120
  this.command = "delete_bulk";
121
- const query = convertToBinaryQuery(this, { bulkKeys });
121
+ const query = convertToBinaryQuery(this, { bulkKeys, waitForIndex });
122
122
  return runQuery(this, query);
123
123
  }
124
124
  catch (err) {
@@ -162,7 +162,7 @@ class GenericKV {
162
162
  * @param bulkCustomKeysValues - An object where custom keys are the keys to update and values are the new values.
163
163
  * @return A promise that resolves with the result of the update.
164
164
  */
165
- static async updateBulk({ bulkKeysValues = {}, bulkCustomKeysValues = {} } = {}) {
165
+ static async updateBulk({ bulkKeysValues = {}, bulkCustomKeysValues = {}, waitForIndex = null } = {}) {
166
166
  try {
167
167
  if (Object.keys(bulkCustomKeysValues).length) {
168
168
  bulkKeysValues = { ...bulkKeysValues, ...convertCustomKeysValues(bulkCustomKeysValues) };
@@ -171,7 +171,7 @@ class GenericKV {
171
171
  throw new Error("No keys provided");
172
172
  }
173
173
  this.command = "update_bulk";
174
- const query = convertToBinaryQuery(this, { bulkKeysValues });
174
+ const query = convertToBinaryQuery(this, { bulkKeysValues, waitForIndex });
175
175
  return runQuery(this, query);
176
176
  }
177
177
  catch (err) {
@@ -215,6 +215,78 @@ class GenericKV {
215
215
  throw err;
216
216
  }
217
217
  }
218
+ /**
219
+ * Shared core for `semanticSearchGetKeys` / `semanticSearchGetValues`.
220
+ * The server command is the same either way (`semantic_search`); the two
221
+ * public methods differ only in which value-inclusion flags they pass,
222
+ * so the wire call lives here once.
223
+ */
224
+ static async semanticSearchCore(query, limitOutput, minScore, withPointers, keyIncluded, pointersMetadata) {
225
+ if (!query || !query.trim()) {
226
+ throw new Error("No query text provided for semantic search.");
227
+ }
228
+ this.command = "semantic_search";
229
+ const binaryQuery = convertToBinaryQuery(this, {
230
+ semanticQuery: query,
231
+ limitOutput,
232
+ minScore,
233
+ withPointers,
234
+ keyIncluded,
235
+ pointersMetadata,
236
+ });
237
+ return runQuery(this, binaryQuery);
238
+ }
239
+ /**
240
+ * Semantic (vector similarity) search returning ranked keys only.
241
+ *
242
+ * Ranks stored items by how close their embeddings are to the embedding of
243
+ * `query` and returns just the matched key and score for each hit β€” the
244
+ * lightweight variant when you only need identity + ranking (e.g. to then
245
+ * `getBulk` a page, or to test membership). Use `semanticSearchGetValues`
246
+ * when you want the values inline.
247
+ *
248
+ * Semantic search must be enabled first (see `Engine.enableSemanticSearch`).
249
+ * The keyspace is embedded in the background as items are written, so
250
+ * results reflect whatever has been embedded so far.
251
+ *
252
+ * @param query - The natural-language query text to embed and search for.
253
+ * @param limitOutput - Start/stop over the ranked hits; `{start: 0, stop: 0}`
254
+ * (the default) lets the server apply its default top-k (10).
255
+ * @param minScore - Drop hits whose cosine similarity (in [-1, 1]) is below
256
+ * this value. Default null (no score filter).
257
+ * @return A promise resolving with ranked hits, each `{__key__, __score__}`.
258
+ */
259
+ static async semanticSearchGetKeys({ query, limitOutput = { start: 0, stop: 0 }, minScore = null }) {
260
+ return this.semanticSearchCore(query, limitOutput, minScore, false, false, false);
261
+ }
262
+ /**
263
+ * Semantic (vector similarity) search returning ranked hits with their values.
264
+ *
265
+ * Ranks stored items by how close their embeddings are to the embedding of
266
+ * `query` and returns the value inline with each hit β€” the key is always
267
+ * included so every value is tagged with its key. Use
268
+ * `semanticSearchGetKeys` when you only need keys + scores.
269
+ *
270
+ * Semantic search must be enabled first (see `Engine.enableSemanticSearch`).
271
+ * The keyspace is embedded in the background as items are written, so
272
+ * results reflect whatever has been embedded so far.
273
+ *
274
+ * @param query - The natural-language query text to embed and search for.
275
+ * @param limitOutput - Start/stop over the ranked hits; `{start: 0, stop: 0}`
276
+ * (the default) lets the server apply its default top-k (10).
277
+ * @param minScore - Drop hits whose cosine similarity (in [-1, 1]) is below
278
+ * this value. Default null (no score filter).
279
+ * @param withPointers - Whether to include pointers (foreign values) in each
280
+ * returned value.
281
+ * @param pointersMetadata - Whether to include pointer metadata in each
282
+ * returned value.
283
+ * @return A promise resolving with ranked hits, each
284
+ * `{__key__, __score__, __value__}` β€” the same dunder envelope
285
+ * `lookupValuesWhere` returns with `keyIncluded: true`, plus the score.
286
+ */
287
+ static async semanticSearchGetValues({ query, limitOutput = { start: 0, stop: 0 }, minScore = null, withPointers = false, pointersMetadata = false }) {
288
+ return this.semanticSearchCore(query, limitOutput, minScore, withPointers, true, pointersMetadata);
289
+ }
218
290
  /**
219
291
  * Gets the length of the keyspace.
220
292
  * @returns A promise that resolves with the length of the keyspace.
@@ -109,11 +109,130 @@ declare class Engine {
109
109
  keyspace: string;
110
110
  };
111
111
  }): Promise<unknown>;
112
+ /**
113
+ * Enable semantic (vector similarity) search.
114
+ *
115
+ * Without `store`, this is DB-wide: it flips the whole database on, sets the
116
+ * default embedding model and field, and enrolls every existing keyspace that
117
+ * has no semantic config yet (each gets a background backfill so its existing
118
+ * items become searchable). The chosen model is downloaded on demand on first
119
+ * enable, so this call may take a while the first time.
120
+ *
121
+ * With `store`, it is scoped: only that store's un-enrolled keyspaces are
122
+ * enrolled and backfilled; the DB-wide switch and default model/field are left
123
+ * untouched. Use this to (re-)enable one store without re-embedding the entire
124
+ * database.
125
+ *
126
+ * @param {Object} options - Options for enabling semantic search.
127
+ * @param {string} [options.model] - The embedding model key to use by default.
128
+ * One of 'minilm', 'bge-small', 'bge-base',
129
+ * 'e5-small'. Defaults to the server default
130
+ * ('bge-small').
131
+ * @param {string} [options.field] - The JSON field of each value to embed.
132
+ * Defaults to embedding the whole value.
133
+ * @param {string} [options.store] - Restrict enrollment/backfill to this store
134
+ * only. If the DB-wide switch is off, a scoped
135
+ * enable enrolls but nothing embeds until a
136
+ * DB-wide enable.
137
+ * @returns {Promise<unknown>} The server's response describing the enabled
138
+ * model and enrolled keyspaces.
139
+ */
140
+ enableSemanticSearch({ model, field, store }?: {
141
+ model?: string;
142
+ field?: string;
143
+ store?: string;
144
+ }): Promise<unknown>;
145
+ /**
146
+ * Disable semantic search.
147
+ *
148
+ * Without `store`, this is DB-wide: embedding and semantic queries stop across
149
+ * the whole database; stored vectors are kept by default so re-enabling
150
+ * resumes without a full re-embed.
151
+ *
152
+ * With `store`, it is scoped: only that store's keyspaces are unenrolled
153
+ * (their configs and resident graphs dropped); the DB-wide switch and all
154
+ * other stores are left untouched. This is the surgical way to reset one
155
+ * store's semantic state instead of nuking and re-backfilling the whole
156
+ * database.
157
+ *
158
+ * @param {Object} options - Options for disabling semantic search.
159
+ * @param {boolean} [options.dropVectors=false] - If true, also clear stored
160
+ * vectors β€” every keyspace's
161
+ * DB-wide, or the scoped store's
162
+ * when `store` is set. Required
163
+ * before switching to a different
164
+ * embedding model.
165
+ * @param {string} [options.store] - Restrict the disable to this store only.
166
+ * @returns {Promise<unknown>} The server's response confirming the disable.
167
+ */
168
+ disableSemanticSearch({ dropVectors, store }?: {
169
+ dropVectors?: boolean;
170
+ store?: string;
171
+ }): Promise<unknown>;
112
172
  /**
113
173
  * Retrieves the structure of the store.
114
174
  * @returns {Promise<unknown>} A promise that resolves with the structure of the store
115
175
  * */
116
176
  getStructureAvailable(): Promise<unknown>;
177
+ /**
178
+ * Enable the DB-wide "wait for index" default: writes block until their
179
+ * secondary indexes are updated before returning, so a write is immediately
180
+ * visible to index-backed reads (e.g. lookupValuesWhere) at the cost of
181
+ * higher write latency. Requires superowner credentials.
182
+ * @returns {Promise<unknown>} The server's response confirming the change.
183
+ */
184
+ enableWaitForIndex(): Promise<unknown>;
185
+ /**
186
+ * Disable the DB-wide "wait for index" default: writes return as soon as the
187
+ * data is committed and indexing happens asynchronously in the background
188
+ * (lower write latency; index-backed reads may briefly lag). This is the
189
+ * default behavior. Requires superowner credentials.
190
+ * @returns {Promise<unknown>} The server's response confirming the change.
191
+ */
192
+ disableWaitForIndex(): Promise<unknown>;
193
+ /** Internal helper for no-argument superowner raw commands. */
194
+ private _adminCommand;
195
+ /**
196
+ * Enable server-side operation reporting (logging). Requires superowner credentials.
197
+ * @returns {Promise<unknown>} The server's response confirming the change.
198
+ */
199
+ enableReports(): Promise<unknown>;
200
+ /**
201
+ * Disable server-side operation reporting (logging). Requires superowner credentials.
202
+ * @returns {Promise<unknown>} The server's response confirming the change.
203
+ */
204
+ disableReports(): Promise<unknown>;
205
+ /**
206
+ * Allow clients to open keyspace subscriptions DB-wide. Requires superowner credentials.
207
+ * @returns {Promise<unknown>} The server's response confirming the change.
208
+ */
209
+ allowSubscriptions(): Promise<unknown>;
210
+ /**
211
+ * Restrict (disallow) keyspace subscriptions DB-wide. Requires superowner credentials.
212
+ * @returns {Promise<unknown>} The server's response confirming the change.
213
+ */
214
+ restrictSubscriptions(): Promise<unknown>;
215
+ /**
216
+ * Sample the current depth of every background task queue (index, timer,
217
+ * counting) β€” an observability probe for whether the background runners are
218
+ * keeping up with the write rate. Requires superowner credentials.
219
+ * @returns {Promise<unknown>} The server's response whose payload maps
220
+ * "index" | "timer" | "counting" to per-queue depth maps.
221
+ */
222
+ queueDepths(): Promise<unknown>;
223
+ /**
224
+ * Set the server-wide snapshot rate. Requires superowner credentials.
225
+ * @param {number} rate - The snapshot rate value (server-defined units).
226
+ * @returns {Promise<unknown>} The server's response confirming the change.
227
+ */
228
+ setSnapshotRate(rate: number): Promise<unknown>;
229
+ /**
230
+ * Set how often the server scans for expired keys. Requires superowner credentials.
231
+ * @param {number} rate - The check period in whole seconds (e.g. rate=10 β†’ a scan
232
+ * every 10 seconds). Stored as-is, like the snapshot rate. Defaults to 1 second.
233
+ * @returns {Promise<unknown>} The server's response confirming the change.
234
+ */
235
+ setExpirationCheckRate(rate: number): Promise<unknown>;
117
236
  }
118
237
  /**
119
238
  * Sends a string to the specified host and port, and returns the parsed response.
@@ -206,6 +206,81 @@ class Engine {
206
206
  }
207
207
  return sendData(this.host, this.port, JSONbig.stringify(query), undefined, this.useTls);
208
208
  }
209
+ /**
210
+ * Enable semantic (vector similarity) search.
211
+ *
212
+ * Without `store`, this is DB-wide: it flips the whole database on, sets the
213
+ * default embedding model and field, and enrolls every existing keyspace that
214
+ * has no semantic config yet (each gets a background backfill so its existing
215
+ * items become searchable). The chosen model is downloaded on demand on first
216
+ * enable, so this call may take a while the first time.
217
+ *
218
+ * With `store`, it is scoped: only that store's un-enrolled keyspaces are
219
+ * enrolled and backfilled; the DB-wide switch and default model/field are left
220
+ * untouched. Use this to (re-)enable one store without re-embedding the entire
221
+ * database.
222
+ *
223
+ * @param {Object} options - Options for enabling semantic search.
224
+ * @param {string} [options.model] - The embedding model key to use by default.
225
+ * One of 'minilm', 'bge-small', 'bge-base',
226
+ * 'e5-small'. Defaults to the server default
227
+ * ('bge-small').
228
+ * @param {string} [options.field] - The JSON field of each value to embed.
229
+ * Defaults to embedding the whole value.
230
+ * @param {string} [options.store] - Restrict enrollment/backfill to this store
231
+ * only. If the DB-wide switch is off, a scoped
232
+ * enable enrolls but nothing embeds until a
233
+ * DB-wide enable.
234
+ * @returns {Promise<unknown>} The server's response describing the enabled
235
+ * model and enrolled keyspaces.
236
+ */
237
+ async enableSemanticSearch({ model, field, store } = {}) {
238
+ const rawQuery = {
239
+ raw: ['enable-semantic-search'],
240
+ credentials: [this.username, this.password],
241
+ };
242
+ if (model)
243
+ rawQuery.raw.push('model', model);
244
+ if (field)
245
+ rawQuery.raw.push('field', field);
246
+ if (store)
247
+ rawQuery.raw.push('store', store);
248
+ return sendData(this.host, this.port, JSONbig.stringify(rawQuery), undefined, this.useTls);
249
+ }
250
+ /**
251
+ * Disable semantic search.
252
+ *
253
+ * Without `store`, this is DB-wide: embedding and semantic queries stop across
254
+ * the whole database; stored vectors are kept by default so re-enabling
255
+ * resumes without a full re-embed.
256
+ *
257
+ * With `store`, it is scoped: only that store's keyspaces are unenrolled
258
+ * (their configs and resident graphs dropped); the DB-wide switch and all
259
+ * other stores are left untouched. This is the surgical way to reset one
260
+ * store's semantic state instead of nuking and re-backfilling the whole
261
+ * database.
262
+ *
263
+ * @param {Object} options - Options for disabling semantic search.
264
+ * @param {boolean} [options.dropVectors=false] - If true, also clear stored
265
+ * vectors β€” every keyspace's
266
+ * DB-wide, or the scoped store's
267
+ * when `store` is set. Required
268
+ * before switching to a different
269
+ * embedding model.
270
+ * @param {string} [options.store] - Restrict the disable to this store only.
271
+ * @returns {Promise<unknown>} The server's response confirming the disable.
272
+ */
273
+ async disableSemanticSearch({ dropVectors = false, store } = {}) {
274
+ const rawQuery = {
275
+ raw: ['disable-semantic-search'],
276
+ credentials: [this.username, this.password],
277
+ };
278
+ if (dropVectors)
279
+ rawQuery.raw.push('drop-vectors');
280
+ if (store)
281
+ rawQuery.raw.push('store', store);
282
+ return sendData(this.host, this.port, JSONbig.stringify(rawQuery), undefined, this.useTls);
283
+ }
209
284
  /**
210
285
  * Retrieves the structure of the store.
211
286
  * @returns {Promise<unknown>} A promise that resolves with the structure of the store
@@ -218,6 +293,105 @@ class Engine {
218
293
  };
219
294
  return sendData(this.host, this.port, JSONbig.stringify(rawQuery), undefined, this.useTls);
220
295
  }
296
+ /**
297
+ * Enable the DB-wide "wait for index" default: writes block until their
298
+ * secondary indexes are updated before returning, so a write is immediately
299
+ * visible to index-backed reads (e.g. lookupValuesWhere) at the cost of
300
+ * higher write latency. Requires superowner credentials.
301
+ * @returns {Promise<unknown>} The server's response confirming the change.
302
+ */
303
+ async enableWaitForIndex() {
304
+ const rawQuery = {
305
+ raw: ['enable-wait-for-index'],
306
+ credentials: [this.username, this.password],
307
+ };
308
+ return sendData(this.host, this.port, JSONbig.stringify(rawQuery), undefined, this.useTls);
309
+ }
310
+ /**
311
+ * Disable the DB-wide "wait for index" default: writes return as soon as the
312
+ * data is committed and indexing happens asynchronously in the background
313
+ * (lower write latency; index-backed reads may briefly lag). This is the
314
+ * default behavior. Requires superowner credentials.
315
+ * @returns {Promise<unknown>} The server's response confirming the change.
316
+ */
317
+ async disableWaitForIndex() {
318
+ const rawQuery = {
319
+ raw: ['disable-wait-for-index'],
320
+ credentials: [this.username, this.password],
321
+ };
322
+ return sendData(this.host, this.port, JSONbig.stringify(rawQuery), undefined, this.useTls);
323
+ }
324
+ /** Internal helper for no-argument superowner raw commands. */
325
+ async _adminCommand(command) {
326
+ const rawQuery = {
327
+ raw: [command],
328
+ credentials: [this.username, this.password],
329
+ };
330
+ return sendData(this.host, this.port, JSONbig.stringify(rawQuery), undefined, this.useTls);
331
+ }
332
+ /**
333
+ * Enable server-side operation reporting (logging). Requires superowner credentials.
334
+ * @returns {Promise<unknown>} The server's response confirming the change.
335
+ */
336
+ async enableReports() {
337
+ return this._adminCommand('enable-reports');
338
+ }
339
+ /**
340
+ * Disable server-side operation reporting (logging). Requires superowner credentials.
341
+ * @returns {Promise<unknown>} The server's response confirming the change.
342
+ */
343
+ async disableReports() {
344
+ return this._adminCommand('disable-reports');
345
+ }
346
+ /**
347
+ * Allow clients to open keyspace subscriptions DB-wide. Requires superowner credentials.
348
+ * @returns {Promise<unknown>} The server's response confirming the change.
349
+ */
350
+ async allowSubscriptions() {
351
+ return this._adminCommand('allow-subscriptions');
352
+ }
353
+ /**
354
+ * Restrict (disallow) keyspace subscriptions DB-wide. Requires superowner credentials.
355
+ * @returns {Promise<unknown>} The server's response confirming the change.
356
+ */
357
+ async restrictSubscriptions() {
358
+ return this._adminCommand('restrict-subscriptions');
359
+ }
360
+ /**
361
+ * Sample the current depth of every background task queue (index, timer,
362
+ * counting) β€” an observability probe for whether the background runners are
363
+ * keeping up with the write rate. Requires superowner credentials.
364
+ * @returns {Promise<unknown>} The server's response whose payload maps
365
+ * "index" | "timer" | "counting" to per-queue depth maps.
366
+ */
367
+ async queueDepths() {
368
+ return this._adminCommand('queue-depths');
369
+ }
370
+ /**
371
+ * Set the server-wide snapshot rate. Requires superowner credentials.
372
+ * @param {number} rate - The snapshot rate value (server-defined units).
373
+ * @returns {Promise<unknown>} The server's response confirming the change.
374
+ */
375
+ async setSnapshotRate(rate) {
376
+ const rawQuery = {
377
+ raw: ['snapshot-rate', String(rate)],
378
+ credentials: [this.username, this.password],
379
+ };
380
+ return sendData(this.host, this.port, JSONbig.stringify(rawQuery), undefined, this.useTls);
381
+ }
382
+ /**
383
+ * Set how often the server scans for expired keys. Requires superowner credentials.
384
+ * @param {number} rate - The check period in whole seconds (e.g. rate=10 β†’ a scan
385
+ * every 10 seconds). Stored as-is, like the snapshot rate. Defaults to 1 second.
386
+ * @returns {Promise<unknown>} The server's response confirming the change.
387
+ */
388
+ async setExpirationCheckRate(rate) {
389
+ const rawQuery = {
390
+ raw: ['expiration-check', String(rate)],
391
+ credentials: [this.username, this.password],
392
+ };
393
+ return sendData(this.host, this.port, JSONbig.stringify(rawQuery), undefined, this.useTls);
394
+ }
221
395
  }
222
396
  /**
223
397
  * Sends data to a server. Supports one-time request/response or subscription.
@@ -32,31 +32,38 @@ declare class InMemory extends GenericKV {
32
32
  * @param expireSec - The expiration time in seconds for the value.
33
33
  * @return A promise that resolves with the result of the insertion.
34
34
  * */
35
- static insertValue({ value, expireSec }?: {
35
+ static insertValue({ value, expireSec, waitForIndex }?: {
36
36
  value?: any;
37
37
  expireSec?: number;
38
+ waitForIndex?: boolean | null;
38
39
  }): Promise<any>;
39
40
  /**
40
41
  * Inserts a custom key into the in-memory store.
41
42
  * @param customKey - The custom key for the value.
42
43
  * @param expireSec - The expiration time in seconds for the value.
44
+ * @param waitForIndex - Per-request synchronous-index override. No-op for in-memory
45
+ * keyspaces (snapshot-driven indexing); present for API symmetry with the persistent
46
+ * variant, where it takes effect.
43
47
  * @return A promise that resolves with the result of the insertion.
44
48
  */
45
- static insertCustomKey({ customKey, expireSec }: {
49
+ static insertCustomKey({ customKey, expireSec, waitForIndex }: {
46
50
  customKey: string;
47
51
  expireSec?: number;
52
+ waitForIndex?: boolean | null;
48
53
  }): Promise<any>;
49
54
  /**
50
55
  * Inserts a custom key-value pair into the in-memory store.
51
56
  * @param customKey - The custom key for the value.
52
57
  * @param value - The value to insert.
53
58
  * @param expireSec - The expiration time in seconds for the value.
59
+ * @param waitForIndex - Per-request synchronous-index override; no-op for in-memory.
54
60
  * @return A promise that resolves with the result of the insertion.
55
61
  */
56
- static insertCustomKeyValue({ customKey, value, expireSec }: {
62
+ static insertCustomKeyValue({ customKey, value, expireSec, waitForIndex }: {
57
63
  customKey: string;
58
64
  value?: any;
59
65
  expireSec?: number;
66
+ waitForIndex?: boolean | null;
60
67
  }): Promise<any>;
61
68
  /**
62
69
  * Updates a value in the in-memory store.
@@ -64,23 +71,27 @@ declare class InMemory extends GenericKV {
64
71
  * @param customKey - The custom key for the value to update.
65
72
  * @param value - The new value to set.
66
73
  * @param expireSec - The expiration time in seconds for the value.
74
+ * @param waitForIndex - Per-request synchronous-index override; no-op for in-memory.
67
75
  * @return A promise that resolves with the result of the update.
68
76
  */
69
- static updateValue({ key, customKey, value, expireSec }?: {
77
+ static updateValue({ key, customKey, value, expireSec, waitForIndex }?: {
70
78
  key?: string;
71
79
  customKey?: string | null;
72
80
  value?: any;
73
81
  expireSec?: number;
82
+ waitForIndex?: boolean | null;
74
83
  }): Promise<any>;
75
84
  /**
76
85
  * Inserts a bulk of values into the in-memory store.
77
86
  * @param bulk - An array of values to insert.
78
87
  * @param expireSec - The expiration time in seconds for the values.
88
+ * @param waitForIndex - Per-request synchronous-index override; no-op for in-memory.
79
89
  * @return A promise that resolves with the result of the insertion.
80
90
  */
81
- static insertBulk({ bulk, expireSec }?: {
91
+ static insertBulk({ bulk, expireSec, waitForIndex }?: {
82
92
  bulk?: any[];
83
93
  expireSec?: number;
94
+ waitForIndex?: boolean | null;
84
95
  }): Promise<any>;
85
96
  /**
86
97
  * Gets keys from the in-memory store.
@@ -84,23 +84,26 @@ class InMemory extends GenericKV {
84
84
  * @param expireSec - The expiration time in seconds for the value.
85
85
  * @return A promise that resolves with the result of the insertion.
86
86
  * */
87
- static async insertValue({ value = {}, expireSec = 0 } = {}) {
87
+ static async insertValue({ value = {}, expireSec = 0, waitForIndex = null } = {}) {
88
88
  this.command = "insert_value";
89
- const query = convertToBinaryQuery(this, { value, expireSec });
89
+ const query = convertToBinaryQuery(this, { value, expireSec, waitForIndex });
90
90
  return runQuery(this, query);
91
91
  }
92
92
  /**
93
93
  * Inserts a custom key into the in-memory store.
94
94
  * @param customKey - The custom key for the value.
95
95
  * @param expireSec - The expiration time in seconds for the value.
96
+ * @param waitForIndex - Per-request synchronous-index override. No-op for in-memory
97
+ * keyspaces (snapshot-driven indexing); present for API symmetry with the persistent
98
+ * variant, where it takes effect.
96
99
  * @return A promise that resolves with the result of the insertion.
97
100
  */
98
- static async insertCustomKey({ customKey, expireSec = 0 }) {
101
+ static async insertCustomKey({ customKey, expireSec = 0, waitForIndex = null }) {
99
102
  if (!customKey) {
100
103
  throw new Error("No key provided");
101
104
  }
102
105
  this.command = "insert_custom_key";
103
- const query = convertToBinaryQuery(this, { key: convertCustomKey(customKey), expireSec });
106
+ const query = convertToBinaryQuery(this, { key: convertCustomKey(customKey), expireSec, waitForIndex });
104
107
  return runQuery(this, query);
105
108
  }
106
109
  /**
@@ -108,11 +111,12 @@ class InMemory extends GenericKV {
108
111
  * @param customKey - The custom key for the value.
109
112
  * @param value - The value to insert.
110
113
  * @param expireSec - The expiration time in seconds for the value.
114
+ * @param waitForIndex - Per-request synchronous-index override; no-op for in-memory.
111
115
  * @return A promise that resolves with the result of the insertion.
112
116
  */
113
- static async insertCustomKeyValue({ customKey, value = {}, expireSec = 0 }) {
117
+ static async insertCustomKeyValue({ customKey, value = {}, expireSec = 0, waitForIndex = null }) {
114
118
  this.command = "insert_custom_key_value";
115
- const query = convertToBinaryQuery(this, { key: convertCustomKey(customKey), value, expireSec });
119
+ const query = convertToBinaryQuery(this, { key: convertCustomKey(customKey), value, expireSec, waitForIndex });
116
120
  return runQuery(this, query);
117
121
  }
118
122
  /**
@@ -121,29 +125,31 @@ class InMemory extends GenericKV {
121
125
  * @param customKey - The custom key for the value to update.
122
126
  * @param value - The new value to set.
123
127
  * @param expireSec - The expiration time in seconds for the value.
128
+ * @param waitForIndex - Per-request synchronous-index override; no-op for in-memory.
124
129
  * @return A promise that resolves with the result of the update.
125
130
  */
126
- static async updateValue({ key = "", customKey = null, value = {}, expireSec = 0 } = {}) {
131
+ static async updateValue({ key = "", customKey = null, value = {}, expireSec = 0, waitForIndex = null } = {}) {
127
132
  const effectiveKey = customKey ? convertCustomKey(customKey) : key;
128
133
  if (!effectiveKey) {
129
134
  throw new Error("No key provided");
130
135
  }
131
136
  this.command = "update_value";
132
- const query = convertToBinaryQuery(this, { key: effectiveKey, value, expireSec });
137
+ const query = convertToBinaryQuery(this, { key: effectiveKey, value, expireSec, waitForIndex });
133
138
  return runQuery(this, query);
134
139
  }
135
140
  /**
136
141
  * Inserts a bulk of values into the in-memory store.
137
142
  * @param bulk - An array of values to insert.
138
143
  * @param expireSec - The expiration time in seconds for the values.
144
+ * @param waitForIndex - Per-request synchronous-index override; no-op for in-memory.
139
145
  * @return A promise that resolves with the result of the insertion.
140
146
  */
141
- static async insertBulk({ bulk = [], expireSec = 0 } = {}) {
147
+ static async insertBulk({ bulk = [], expireSec = 0, waitForIndex = null } = {}) {
142
148
  if (!bulk || bulk.length === 0) {
143
149
  throw new Error("No values provided");
144
150
  }
145
151
  this.command = "insert_bulk";
146
- const query = convertToBinaryQuery(this, { bulkValues: bulk, expireSec });
152
+ const query = convertToBinaryQuery(this, { bulkValues: bulk, expireSec, waitForIndex });
147
153
  return runQuery(this, query);
148
154
  }
149
155
  /**
@@ -18,46 +18,58 @@ declare class Persistent extends GenericKV {
18
18
  * @param value - The value to insert.
19
19
  * @return A promise that resolves with the result of the insertion.
20
20
  */
21
- static insertValue({ value }?: {
21
+ static insertValue({ value, waitForIndex }?: {
22
22
  value?: any;
23
+ waitForIndex?: boolean | null;
23
24
  }): Promise<any>;
24
25
  /**
25
26
  * Inserts a custom key into the persistent store.
26
27
  * @param customKey - The custom key to insert.
28
+ * @param waitForIndex - Per-request synchronous-index override. `true` β†’ the write
29
+ * returns only after its secondary indexes are updated (read-your-writes); `false`
30
+ * β†’ fire-and-forget indexing; `null` (default) β†’ use the DB-wide default (see
31
+ * `Engine.enable/disableWaitForIndex`). Applies to persistent writes.
27
32
  * @return A promise that resolves with the result of the insertion.
28
33
  */
29
- static insertCustomKey({ customKey }: {
34
+ static insertCustomKey({ customKey, waitForIndex }: {
30
35
  customKey: string;
36
+ waitForIndex?: boolean | null;
31
37
  }): Promise<any>;
32
38
  /**
33
39
  * Inserts a custom key-value pair into the persistent store.
34
40
  * @param customKey - The custom key for the value.
35
41
  * @param value - The value to insert.
42
+ * @param waitForIndex - Per-request synchronous-index override; null (default) uses the DB-wide default.
36
43
  * @return A promise that resolves with the result of the insertion.
37
44
  */
38
- static insertCustomKeyValue({ customKey, value }: {
45
+ static insertCustomKeyValue({ customKey, value, waitForIndex }: {
39
46
  customKey: string;
40
47
  value?: any;
48
+ waitForIndex?: boolean | null;
41
49
  }): Promise<any>;
42
50
  /**
43
51
  * Updates a value in the persistent store.
44
52
  * @param key - The key for the value to update.
45
53
  * @param customKey - The custom key for the value to update.
46
54
  * @param value - The new value to set.
55
+ * @param waitForIndex - Per-request synchronous-index override; null (default) uses the DB-wide default.
47
56
  * @return A promise that resolves with the result of the update.
48
57
  */
49
- static updateValue({ key, customKey, value }?: {
58
+ static updateValue({ key, customKey, value, waitForIndex }?: {
50
59
  key?: string;
51
60
  customKey?: string | null;
52
61
  value?: any;
62
+ waitForIndex?: boolean | null;
53
63
  }): Promise<any>;
54
64
  /**
55
65
  * Inserts multiple key-value pairs into the persistent store in bulk.
56
66
  * @param bulk - An array of key-value pairs to insert.
67
+ * @param waitForIndex - Per-request synchronous-index override; null (default) uses the DB-wide default.
57
68
  * @return A promise that resolves with the result of the bulk insertion.
58
69
  */
59
- static insertBulk({ bulk }?: {
70
+ static insertBulk({ bulk, waitForIndex }?: {
60
71
  bulk?: any[];
72
+ waitForIndex?: boolean | null;
61
73
  }): Promise<any>;
62
74
  /**
63
75
  * Retrieves keys from the persistent store based on provided options.
@@ -17,36 +17,41 @@ class Persistent extends GenericKV {
17
17
  * @param value - The value to insert.
18
18
  * @return A promise that resolves with the result of the insertion.
19
19
  */
20
- static async insertValue({ value = {} } = {}) {
20
+ static async insertValue({ value = {}, waitForIndex = null } = {}) {
21
21
  this.command = "insert_value";
22
- const query = convertToBinaryQuery(this, { value });
22
+ const query = convertToBinaryQuery(this, { value, waitForIndex });
23
23
  return runQuery(this, query);
24
24
  }
25
25
  /**
26
26
  * Inserts a custom key into the persistent store.
27
27
  * @param customKey - The custom key to insert.
28
+ * @param waitForIndex - Per-request synchronous-index override. `true` β†’ the write
29
+ * returns only after its secondary indexes are updated (read-your-writes); `false`
30
+ * β†’ fire-and-forget indexing; `null` (default) β†’ use the DB-wide default (see
31
+ * `Engine.enable/disableWaitForIndex`). Applies to persistent writes.
28
32
  * @return A promise that resolves with the result of the insertion.
29
33
  */
30
- static async insertCustomKey({ customKey }) {
34
+ static async insertCustomKey({ customKey, waitForIndex = null }) {
31
35
  if (!customKey) {
32
36
  throw new Error("No key provided");
33
37
  }
34
38
  this.command = "insert_custom_key";
35
- const query = convertToBinaryQuery(this, { key: convertCustomKey(customKey) });
39
+ const query = convertToBinaryQuery(this, { key: convertCustomKey(customKey), waitForIndex });
36
40
  return runQuery(this, query);
37
41
  }
38
42
  /**
39
43
  * Inserts a custom key-value pair into the persistent store.
40
44
  * @param customKey - The custom key for the value.
41
45
  * @param value - The value to insert.
46
+ * @param waitForIndex - Per-request synchronous-index override; null (default) uses the DB-wide default.
42
47
  * @return A promise that resolves with the result of the insertion.
43
48
  */
44
- static async insertCustomKeyValue({ customKey, value = {} }) {
49
+ static async insertCustomKeyValue({ customKey, value = {}, waitForIndex = null }) {
45
50
  if (!customKey) {
46
51
  throw new Error("No key provided");
47
52
  }
48
53
  this.command = "insert_custom_key_value";
49
- const query = convertToBinaryQuery(this, { key: convertCustomKey(customKey), value });
54
+ const query = convertToBinaryQuery(this, { key: convertCustomKey(customKey), value, waitForIndex });
50
55
  return runQuery(this, query);
51
56
  }
52
57
  /**
@@ -54,28 +59,30 @@ class Persistent extends GenericKV {
54
59
  * @param key - The key for the value to update.
55
60
  * @param customKey - The custom key for the value to update.
56
61
  * @param value - The new value to set.
62
+ * @param waitForIndex - Per-request synchronous-index override; null (default) uses the DB-wide default.
57
63
  * @return A promise that resolves with the result of the update.
58
64
  */
59
- static async updateValue({ key = "", customKey = null, value = {} } = {}) {
65
+ static async updateValue({ key = "", customKey = null, value = {}, waitForIndex = null } = {}) {
60
66
  const effectiveKey = customKey ? convertCustomKey(customKey) : key;
61
67
  if (!effectiveKey) {
62
68
  throw new Error("No key provided");
63
69
  }
64
70
  this.command = "update_value";
65
- const query = convertToBinaryQuery(this, { key: effectiveKey, value });
71
+ const query = convertToBinaryQuery(this, { key: effectiveKey, value, waitForIndex });
66
72
  return runQuery(this, query);
67
73
  }
68
74
  /**
69
75
  * Inserts multiple key-value pairs into the persistent store in bulk.
70
76
  * @param bulk - An array of key-value pairs to insert.
77
+ * @param waitForIndex - Per-request synchronous-index override; null (default) uses the DB-wide default.
71
78
  * @return A promise that resolves with the result of the bulk insertion.
72
79
  */
73
- static async insertBulk({ bulk = [] } = {}) {
80
+ static async insertBulk({ bulk = [], waitForIndex = null } = {}) {
74
81
  if (!bulk || bulk.length === 0) {
75
82
  throw new Error("No values provided");
76
83
  }
77
84
  this.command = "insert_bulk";
78
- const query = convertToBinaryQuery(this, { bulkValues: bulk });
85
+ const query = convertToBinaryQuery(this, { bulkValues: bulk, waitForIndex });
79
86
  return runQuery(this, query);
80
87
  }
81
88
  /**
@@ -58,12 +58,12 @@ function processBulkKeysValues(bulkKeysValues) {
58
58
  * @returns A JSON string representing the binary query.
59
59
  * */
60
60
  function convertToBinaryQuery(cls, options = {}) {
61
- const { key = null, value = {}, expireSec = 0, bulkValues = [], bulkKeys = [], bulkKeysValues = {}, searchCriteria = {}, limitOutput = { start: 0, stop: 0 }, withPointers = false, schema = null, keyIncluded = false, pointersMetadata = false, volumes = [], latestVolume = false, } = options;
61
+ const { key = null, value = {}, expireSec = 0, bulkValues = [], bulkKeys = [], bulkKeysValues = {}, searchCriteria = {}, limitOutput = { start: 0, stop: 0 }, withPointers = false, schema = null, keyIncluded = false, pointersMetadata = false, volumes = [], latestVolume = false, semanticQuery = null, minScore = null, waitForIndex = null, } = options;
62
62
  const { processedValue, foundSchema } = processValue(value);
63
63
  const { processedBulkValues, uniqueSchema } = processBulkValues(bulkValues);
64
64
  const bulkKeysValuesProcessed = processBulkKeysValues(bulkKeysValues);
65
65
  const processedSearchCriteria = processSearchCriteria(searchCriteria);
66
- return JSON.stringify({
66
+ const queryDict = {
67
67
  username: cls.username,
68
68
  password: cls.password,
69
69
  namespace: cls.namespace,
@@ -79,14 +79,27 @@ function convertToBinaryQuery(cls, options = {}) {
79
79
  bulk_values: processedBulkValues.map(v => JSON.stringify(v)),
80
80
  bulk_keys: bulkKeys,
81
81
  bulk_keys_values: bulkKeysValuesProcessed,
82
- search_criteria: JSON.stringify(processedSearchCriteria),
82
+ // `semantic_search` sends the raw query text (the engine trims it as a
83
+ // plain string); every other command sends a JSON-encoded filter map.
84
+ search_criteria: semanticQuery !== null ? semanticQuery : JSON.stringify(processedSearchCriteria),
83
85
  with_pointers: withPointers,
84
86
  schema: foundSchema ? foundSchema : uniqueSchema ? uniqueSchema : schema,
85
87
  key_included: keyIncluded,
86
88
  pointers_metadata: pointersMetadata,
87
89
  volumes,
88
90
  latest_volume: latestVolume,
89
- });
91
+ };
92
+ // Only `semantic_search` honors min_score; omit it otherwise so the wire is
93
+ // unchanged for existing commands (the engine defaults the field to None).
94
+ if (minScore !== null) {
95
+ queryDict.min_score = minScore;
96
+ }
97
+ // Per-request wait_for_index override for persistent writes; omit when null
98
+ // so the server falls back to its DB-wide default (existing wire unchanged).
99
+ if (waitForIndex !== null) {
100
+ queryDict.wait_for_index = waitForIndex;
101
+ }
102
+ return JSON.stringify(queryDict);
90
103
  }
91
104
  function processSearchCriteria(searchCriteria) {
92
105
  const processedCriteria = {};
package/package.json CHANGED
@@ -1,20 +1,52 @@
1
1
  {
2
2
  "name": "montycat",
3
- "version": "1.1.5",
4
- "description": "A Node.js client for Montycat, NoSQL database utilizing Data Mesh architecture.",
3
+ "version": "1.1.7",
4
+ "description": "Self-hosted vector database + NoSQL with built-in AI semantic search β€” the Node.js & TypeScript client for Montycat. A Rust-powered, AI-native Pinecone / Weaviate / Chroma alternative for RAG, AI agents & LLM memory.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
7
7
  "module": "dist/index.js",
8
8
  "types": "dist/index.d.ts",
9
+ "homepage": "https://montygovernance.com",
10
+ "repository": {
11
+ "type": "git",
12
+ "url": "git+https://github.com/MontyGovernance/montycat_node.git"
13
+ },
14
+ "bugs": {
15
+ "url": "https://github.com/MontyGovernance/montycat_node/issues"
16
+ },
9
17
  "keywords": [
18
+ "vector-database",
19
+ "vector-search",
20
+ "semantic-search",
21
+ "embeddings",
22
+ "similarity-search",
23
+ "knn",
24
+ "hnsw",
10
25
  "database",
11
26
  "nosql",
12
- "sql",
27
+ "database-client",
28
+ "rag",
29
+ "retrieval-augmented-generation",
30
+ "ai",
31
+ "ai-agents",
32
+ "agent-memory",
33
+ "ai-memory",
34
+ "llm",
35
+ "mcp",
36
+ "self-hosted",
37
+ "embedded-database",
38
+ "pinecone-alternative",
39
+ "weaviate-alternative",
40
+ "chroma-alternative",
41
+ "qdrant-alternative",
42
+ "redis-alternative",
13
43
  "data-mesh",
14
- "cache",
15
- "lsm",
16
- "key-value",
44
+ "async",
45
+ "typescript",
46
+ "rust",
17
47
  "realtime",
48
+ "key-value",
49
+ "cache",
18
50
  "montycat"
19
51
  ],
20
52
  "exports": {
@@ -23,8 +55,14 @@
23
55
  "types": "./dist/index.d.ts"
24
56
  }
25
57
  },
58
+ "engines": {
59
+ "node": ">=18"
60
+ },
61
+ "files": [
62
+ "dist"
63
+ ],
26
64
  "scripts": {
27
- "test": "echo \"Error: no test specified\" && exit 1",
65
+ "test": "tsc --noEmit",
28
66
  "build": "tsc",
29
67
  "start": "node dist/index.js",
30
68
  "dev": "tsc --watch"
@@ -39,7 +77,6 @@
39
77
  "license": "MIT",
40
78
  "dependencies": {
41
79
  "json-bigint": "^1.0.0",
42
- "xxhash-wasm": "^1.0.2",
43
80
  "xxhashjs": "^0.2.2"
44
81
  }
45
82
  }