memory-tools-client
Version:
Memory-Tools client Nodejs library.
357 lines (249 loc) • 13.2 kB
Markdown
# Memory Tools Node.js Client
An asynchronous Node.js client for interacting with the **Memory Tools** database via its secure, TLS-based binary protocol. This library is designed to be efficient, robust, and easy to use.
---
## 📖 Getting Started
### Connection and Basic Operations
The `MemoryToolsClient` class allows you to connect to your server. It's recommended to provide credentials for authentication. The client manages a persistent connection that should be closed when your application finishes.
```javascript
// For ESM: import MemoryToolsClient from "memory-tools-client";
const { MemoryToolsClient } = require("memory-tools-client");
// Server Configuration
const DB_HOST = "127.0.0.1";
const DB_PORT = 5876;
const DB_USER = "admin";
const DB_PASS = "adminpass";
// For production, set rejectUnauthorized to 'true' and provide a serverCertPath.
const client = new MemoryToolsClient(
DB_HOST,
DB_PORT,
DB_USER,
DB_PASS,
null, // serverCertPath
false // rejectUnauthorized
);
async function runExample() {
try {
await client.connect();
console.log(`Connected and authenticated as: ${client.authenticatedUser}`);
const collName = "users";
await client.collectionCreate(collName);
console.log(`✔ Collection '${collName}' created.`);
// Set an item with an auto-generated UUID key
const userValue = { name: "Elena", city: "Madrid", active: true };
await client.collectionItemSet(collName, userValue);
// The '_id' is automatically added to the userValue object
console.log(`✔ User '${userValue._id}' set successfully.`);
// Set an item with a specific key
const specificKey = "user:marcos";
await client.collectionItemSet(
collName,
{ name: "Marcos", city: "Bogota", active: true },
specificKey
);
console.log(`✔ User '${specificKey}' set successfully.`);
// Get an item back
const result = await client.collectionItemGet(collName, specificKey);
if (result.found) {
console.log("✔ Item retrieved:", result.value);
}
} catch (error) {
console.error("✖ Connection or operation error:", error.message);
} finally {
// Ensure the connection is always closed
client.close();
console.log("Connection closed.");
}
}
runExample();
```
### Atomic Transactions
For operations that must either all succeed or all fail, you can use transactions.
```javascript
async function transactionExample() {
await client.connect();
// ... create a collection 'accounts' ...
// Start a transaction
await client.begin();
try {
// Perform operations within the transaction
await client.collectionItemUpdate("accounts", "acc1", { balance: 50 });
await client.collectionItemUpdate("accounts", "acc2", { balance: 150 });
// If all operations succeed, commit the changes
await client.commit();
console.log("✔ Transaction committed successfully.");
} catch (error) {
// If any operation fails, roll back all changes
await client.rollback();
console.error("✖ Transaction failed, changes were rolled back.");
} finally {
client.close();
}
}
```
---
## 🔬 Advanced Queries with the `Query` Object
The `Query` interface is the most powerful feature of the client, allowing you to build complex, server-side queries. This is highly efficient as it avoids transferring entire collections over the network.
### Query Parameters
- `filter` (object): Defines conditions to select items (like a `WHERE` clause).
- `order_by` (array): Sorts the results based on one or more fields.
- `limit` (number): Restricts the maximum number of results returned.
- `offset` (number): Skips a specified number of results, used for pagination.
- `count` (boolean): If `true`, returns a count of matching items instead of the items themselves.
- `distinct` (string): Returns a list of unique values for the specified field.
- `group_by` (array): Groups results by one or more fields to perform aggregations.
- `aggregations` (object): Defines aggregation functions to run on groups (e.g., `SUM`, `AVG`, `COUNT`).
- `having` (object): Filters the results _after_ grouping and aggregation (like a `HAVING` clause).
- **`projection` (array of strings)**: Selects which fields to return, reducing network traffic.
- **`lookups` (array of objects)**: Enriches documents by joining data from other collections, similar to a `LEFT JOIN`.
### Building Filters
The `filter` object is the core of your query. It can be a single condition or a nested structure using logical operators like `and`, `or`, and `not`.
**Single Condition Structure:** `{ field: "field_name", op: "operator", value: ... }`
| Operator (`op`) | Description | Example `value` |
| --------------- | --------------------------------------------------- | -------------------------- |
| `=` | Equal to | `"some_string"` or `123` |
| `!=` | Not equal to | `"some_string"` or `123` |
| `>` | Greater than | `100` |
| `>=` | Greater than or equal to | `100` |
| `<` | Less than | `50` |
| `<=` | Less than or equal to | `50` |
| `like` | Case-insensitive pattern matching (`%` is wildcard) | `"start%"` or `"%middle%"` |
| `in` | Value is in a list of possibilities | `["value1", "value2"]` |
| `between` | Value is between two values (inclusive) | `[10, 20]` |
| `is null` | The field does not exist or is `null` | `true` (or any value) |
| `is not null` | The field exists and is not `null` | `true` (or any value) |
**Example: Complex Filter** This query finds all users who are active and older than 30.
```javascript
// Find active users with age > 30
const query = {
filter: {
and: [
{ field: "active", op: "=", value: true },
{ field: "age", op: ">", value: 30 },
],
},
};
const results = await client.collectionQuery("users", query);
// Returns: [ { _id: 'u1', name: 'Elena', age: 34, active: true } ]
```
### Joins and Data Enrichment with `lookups`
The **`lookups`** parameter allows you to perform powerful server-side joins. It accepts an array of lookup objects, which are executed in a sequence (a pipeline).
**Example: Enriching Profiles with User Data** Based on our test data, let's query the `profiles` collection and join the user's information from the `users` collection.
```javascript
// Sample Data:
// users: [ { _id: 'u1', name: 'Elena' }, { _id: 'u2', name: 'Marcos' } ]
// profiles: [ { _id: 'p1', user_id: 'u1' }, { _id: 'p2', user_id: 'u2' } ]
const lookupQuery = {
lookups: [
{
from: "users",
localField: "user_id", // Field from the 'profiles' collection
foreignField: "_id", // Field from the 'users' collection
as: "user_info", // New field to store the joined user document
},
],
};
const enrichedProfiles = await client.collectionQuery("profiles", lookupQuery);
console.log(JSON.stringify(enrichedProfiles, null, 2));
// Output:
// [
// {
// "_id": "p1", "user_id": "u1",
// "user_info": { "_id": "u1", "name": "Elena" }
// },
// {
// "_id": "p2", "user_id": "u2",
// "user_info": { "_id": "u2", "name": "Marcos" }
// }
// ]
```
### Deep Query Example: Aggregations & Grouping
You can perform powerful data analysis directly on the server.
**Example: Counting Active vs. Inactive Users** Let's group the `users` collection by the `active` field and count how many fall into each category.
```javascript
const analyticsQuery = {
// 1. Group documents by the 'active' field
group_by: ["active"],
// 2. Define the aggregations to perform on each group
aggregations: {
userCount: { func: "count", field: "_id" }, // Count documents in each group
maxAge: { func: "max", field: "age" }, // Find the max age in each group
},
};
const report = await client.collectionQuery("users", analyticsQuery);
console.log(JSON.stringify(report, null, 2));
// Example output:
// [
// { "active": true, "userCount": 2, "maxAge": 34 },
// { "active": false, "userCount": 1, "maxAge": 45 }
// ]
```
## ⚡ API Reference
### Connection and Session
#### `constructor(host, port, username?, password?, serverCertPath?, rejectUnauthorized?)`
Creates a new client instance.
#### `connect(): Promise<tls.TLSSocket>`
Establishes the TLS connection and authenticates.
#### `close(): void`
Closes the underlying socket connection.
#### `isAuthenticatedSession` (property)
A `boolean` that is `true` if the client session is currently authenticated.
#### `authenticatedUser` (property)
A `string` containing the username of the authenticated user, or `null`.
### Transaction Operations
#### `begin(): Promise<string>`
Starts a new transaction.
#### `commit(): Promise<string>`
Commits the current transaction, making all changes permanent.
#### `rollback(): Promise<string>`
Rolls back the current transaction, discarding all changes.
### Collection & Index Operations
#### `collectionCreate(collectionName: string): Promise<string>`
Creates a new collection.
#### `collectionDelete(collectionName: string): Promise<string>`
Deletes an entire collection.
#### `collectionList(): Promise<string[]>`
Lists all accessible collection names.
#### `collectionIndexCreate(collectionName: string, fieldName: string): Promise<string>`
Creates an index on a field to speed up queries.
#### `collectionIndexDelete(collectionName: string, fieldName: string): Promise<string>`
Deletes an index.
#### `collectionIndexList(collectionName: string): Promise<string[]>`
Lists all indexed fields for a collection.
### Item (CRUD) Operations
#### `collectionItemSet<T>(collName: string, value: T, key?: string, ttl?: number): Promise<string>`
Stores an item. If `key` is omitted, a UUID is generated and assigned to the `_id` property of the `value` object.
#### `collectionItemSetMany<T>(collName: string, items: T[]): Promise<string>`
Stores multiple items. If `key` is omitted, a UUID is generated and assigned to the `_id` property of the `value` object.
#### `collectionItemUpdate<T>(collName: string, key: string, patchValue: Partial<T>): Promise<string>`
Partially updates an item.
#### `collectionItemUpdateMany<T>(collName: string, items: { _id: string, patch: Partial<T> }[]): Promise<string>`
Partially updates multiple items in a batch.
#### `collectionItemGet<T>(collName: string, key: string): Promise<GetResult<T>>`
Retrieves an item. Returns `{ found: boolean, value: T | null }`.
#### `collectionItemDelete(collName:string, key: string): Promise<string>`
Deletes an item by its key.
#### `collectionItemDeleteMany(collName: string, keys: string[]): Promise<string>`
Deletes multiple items by their keys.
### Query Operations
#### `collectionQuery<T>(collectionName: string, query: Query): Promise<T>`
Executes a complex query. See the "Advanced Queries" section for details.
---
## 🔒 Security Considerations
- **TLS is Essential**: Always use TLS to encrypt data in transit.
- **Certificate Verification**: For production, always set `rejectUnauthorized` to `true` (the default) and provide a `serverCertPath` to prevent man-in-the-middle attacks.
- **Credential Management**: Avoid hardcoding credentials. Use environment variables or a secret management service like HashiCorp Vault or AWS Secrets Manager.
## Support the Project!
Hello! I'm the developer behind **Memory Tools**. This is an open-source project.
I've dedicated a lot of time and effort to this project, and with your support, I can continue to maintain it, add new features, and make it better for everyone.
---
### How You Can Help
Every contribution, no matter the size, is a great help and is enormously appreciated. If you would like to support the continued development of this project, you can make a donation via PayPal.
You can donate directly to my PayPal account by clicking the link below:
**[Click here to donate](https://paypal.me/AdonayB?locale.x=es_XC&country.x=VE)**
---
### Other Ways to Contribute
If you can't donate, don't worry! You can still help in other ways:
- **Share the project:** Talk about it on social media or with your friends.
- **Report bugs:** If you find a problem, open an issue on GitHub.
- **Contribute code:** If you have coding skills, you can help improve the code.
Thank you for your support!