# Labels
Labels in RushDB are an essential part of the database schema, providing a way to categorize and organize records.
## How it works
Every record in RushDB has two labels:
1. A default system label (`__RUSHDB__LABEL__RECORD__`). This label is never exposed publicly.
2. A user-defined label that is searchable (e.g., `User`, `Car`, `Product`)
Labels help in organizing your data and enable efficient querying across similar types of records. They function similarly to table names in relational databases but with the flexibility of graph databases.
```typescript
// Record with label "User"
{
"__id": "01968aa4-22c1-781a-8e8c-8fe6be6c3fd4",
"__label": "User", // User-defined label (required and limited to one per record)
"__proptypes": {
"name": "string",
"emailConfirmed": "boolean",
"registeredAt": "datetime",
"rating": "number",
"currency": "string",
"email": "string"
},
"name": "John Galt",
"emailConfirmed": true,
"registeredAt": "2022-07-19T08:30:28.000Z",
"rating": 4.98,
"currency": "USD",
"email": "john.galt@example.com"
}
```
## Label Requirements and Limitations
Currently, RushDB has the following requirements for labels:
1. **Single Custom Label**: Each record can have only one custom label at a time.
2. **Required Field**: A custom label is required for each record by default.
3. **Case-Sensitive**: Labels are case-sensitive, so "User" and "user" would be considered different labels.
These requirements help maintain a clean and consistent data structure across your database. For more details on how labels interact with other database elements, see [Records](../concepts/records) and [Properties](../concepts/properties).
## Label Assignment
Labels can be:
1. **Explicitly provided** by the user for top-level records
2. **Automatically derived** from parent keys for nested objects during the data import process
When importing nested JSON data, RushDB's breadth-first search algorithm automatically assigns appropriate labels based on the parent keys, making the process intuitive without requiring manual schema design.
For example, when importing this JSON:
```json
{
"car": {
"make": "Tesla",
"model": "Model 3",
"engine": {
"power": 283,
"type": "electric"
}
}
}
```
The label "car" is assigned to the top record, and "engine" is assigned to the nested record.
## Internal Representation
Internally, labels are stored as the `__RUSHDB__KEY__LABEL__` property and exposed to clients as `__label`. This property is essential for organizing records and enabling efficient queries across similar types of data.
To learn more about how records are structured and interconnected, see [Records](../concepts/records) and [Relationships](../concepts/relationships).
---
# Properties
The fundamental unit of meaningful data in RushDB is known as a **Property**. Properties are first-class citizens in the RushDB architecture and serve as critical links that interconnect diverse data within [Records](../concepts/records) across the graph database.
## How it works
In RushDB's unique property graph model, properties connect records through a combination of field name and data type. This creates a powerful network that reveals relationships that might otherwise remain hidden.
Here is a simplified diagram illustrating how properties appear in a graph:
```mermaid
graph LR
P0((Property:color)) --> b[Matte black] --> R0((Record:Jacket))
P0 --> d[Pale green] --> R2((Record:House))
P1 --> j[Villa Vista] --> R2
P1((Property:name)) --> e[Porsche 911] --> R1
P0 --> c[Dark grey] --> R1((Record:SportCar))
P2((Property:maxSpeed)) --> k[295] --> R1
```
And this is how those **Records** can be represented in code:
```typescript
// Record:Jacket
const jacket = {
color: "Matte black" // Property `color` [string]
}
// Record:SportCar
const sportCar = {
name: "Porsche 911", // Property `name` [string]
color: "Dark grey", // Property `color` [string]
maxSpeed: 295 // Property `maxSpeed` [number]
}
// Record:House
const house = {
name: "Villa Vista", // Property `name` [string]
color: "Pale green" // Property `color` [string]
}
```
## Internal Structure
Internally, properties are stored as nodes with the label `__RUSHDB__LABEL__PROPERTY__` and contain only the name and type fields (not the actual values). The values are stored in the record nodes, and properties are connected to their records via `__RUSHDB__RELATION__VALUE__` relationships.
```mermaid
graph LR
subgraph Properties
B[":__RUSHDB__LABEL__PROPERTY__
name: color
type: string"]
end
subgraph Records
A[":__RUSHDB__LABEL__RECORD__ :Car"]
C[":__RUSHDB__LABEL__RECORD__ :Flower"]
end
B -->|__RUSHDB__RELATION__VALUE__| A
B -->|__RUSHDB__RELATION__VALUE__| C
```
This approach enables:
1. Performant queries across different record types
2. Discovery of hidden insights in data through property-based connections
3. Optimized graph traversals leveraging Neo4j's native capabilities
## Considerations
Real-world data can be considerably more intricate and may encompass all conceivable
[data types](../concepts/storage#data-types) within a single **Record**.
However, rest assured that RushDB adeptly manages this complexity without hesitation. Nevertheless, there are a few
important considerations you should be aware of:
1. **Property** is designed to accommodate only consistent values. This means that RushDB will strive to retain the
original value type. However, if there are any inconsistent or non-convertible values in the data, RushDB will
automatically convert them to a _string_ type.
Payload contains inconsistent values but can be converted to desired _number_ type:
```js
{
name: "Combination",
type: "number",
value: [4, 8, 15, 16, "23", "42.0"]
}
// ---> converts to
{
name: "Combination",
type: "number",
value: [4, 8, 15, 16, 23, 42.0]
}
```
Payload contains inconsistent values but cannot be converted to desired _number_ type:
```js
{
name: "Secret",
type:"number",
value: [1, 2, 3, "jelly bear"]
}
// ---> converts to
{
name: "Secret",
type: "string",
value: ["1", "2", "3", "jelly bear"]
}
```
---
2. When two (or more) properties with the same name but different types come into play, RushDB will maintain both Properties as separate entities. For instance:
```js
[
{
type: "Raincoat",
size: "M"
},
{
type: "Cardigan",
size: 38
}
]
```
Will be saved as distinct properties (size:string and size:number) connecting to their respective records.
## Supported Data Types
RushDB supports a variety of data types to accommodate diverse data needs in your applications:
### `string`
Used for any textual information with virtually unlimited length.
```js
{
name: "productName",
type: "string",
value: "Premium Leather Jacket"
}
```
### `number`
Accommodates both floating-point numbers and integers.
```js
{
name: "price",
type: "number",
value: 129.99
}
```
### `boolean`
Represents true or false values.
```js
{
name: "inStock",
type: "boolean",
value: true
}
```
### `datetime`
Follows ISO 8601 format, including timezone information.
```js
{
name: "manufacturedAt",
type: "datetime",
value: "2025-03-15T14:30:00Z"
}
```
### `null`
Represents the absence of a value.
```js
{
name: "discount",
type: "null",
value: null
}
```
### `vector`
Arrays of floating-point numbers or integers, particularly useful for vector similarity searches and machine learning operations.
```js
{
name: "imageEmbedding",
type: "vector",
value: [0.99070, 0.78912, 1.0, 0.0]
}
```
### Arrays
RushDB also supports arrays as property values, but they must contain consistent value types:
> **Note:** Every data type mentioned above (except `vector`, since it's already an array by default) supports an array representation.
```js
// String array
{
name: "categories",
type: "string",
value: ["outerwear", "winter", "premium"]
}
// Number array
{
name: "availableSizes",
type: "number",
value: [36, 38, 40, 42, 44]
}
// Boolean array
{
name: "features",
type: "boolean",
value: [true, false, true, true]
}
```
RushDB automatically handles type inference during data import, ensuring optimal storage and retrieval of your property values. If there are mixed types within an array that can be consistently converted (like strings to numbers), RushDB will attempt the conversion. However, if conversion isn't possible, it will default to the most accommodating type (usually string).
## Multi-tenant Isolation
Properties are not shared amongst projects (database instances), ensuring complete isolation in multi-tenant environments. Each project has its own set of property nodes, maintaining data security and isolation.
For more information on how properties are imported and processed, see [REST API - Import Data](../rest-api/records/import-data).
---
# Records
In RushDB, Records are fundamental data structures that store meaningful key-value data. Each Record consists of individual properties (key-value pairs) and can be connected to other Records through relationships.
## How it works
Records in RushDB can be thought of as nodes in a graph database or rows in a traditional database. While the underlying implementation utilizes complex graph structures, from a user perspective, a Record is simply a key-value object containing properties.
Each record in RushDB consists of:
- User-defined properties (key-value pairs)
- System properties (prefixed with `__`)
Below is an example of a record with the label "User":
```typescript
{
"__id": "01968aa4-22c1-781a-8e8c-8fe6be6c3fd4", // Unique identifier
"__label": "User", // User-defined label (required and limited to one per record)
"__proptypes": { // Property types
"name": "string",
"emailConfirmed": "boolean",
"registeredAt": "datetime",
"rating": "number",
"currency": "string",
"email": "string"
},
"name": "John Galt",
"emailConfirmed": true,
"registeredAt": "2022-07-19T08:30:28.000Z",
"rating": 4.98,
"currency": "USD",
"email": "john.galt@example.com"
}
```
Or this example with the label "Coffee":
```typescript
{
"__id": "01968aa4-88c1-781a-8e8c-8fc6be7c3fd4",
"__label": "Coffee",
"__proptypes": {
"origin": "string",
"process": "string",
"cupping": "number",
"inStock": "boolean",
"roasted": "datetime",
"notes": "string"
},
"origin": "Guatemala",
"process": "washed",
"cupping": 86,
"inStock": true,
"roasted": "2023-07-20T14:50:00Z",
"notes": ["Nuts", "Caramel", "Lime"]
}
```
## Internal Structure
Internally, each Record in RushDB is represented as a node with two labels:
1. The system label `__RUSHDB__LABEL__RECORD__`
2. A user-defined label (exposed as `__label`)
In addition to user-defined properties, each Record contains several internal properties that enable advanced functionality:
| Internal Key | Client Representation | Description |
|-------------------------------------|-----------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `__RUSHDB__KEY__ID__` | `__id` | UUIDv7 that enables lexicographic ordering without relying on user-defined fields like `createdAt`. RushDB SDKs support converting `__id` to timestamp and ISO8601 date. For more details, see [UUIDv7 specification](https://www.ietf.org/archive/id/draft-peabody-uuid-v7-01.html). |
| `__RUSHDB__KEY__PROPERTIES__META__` | `__proptypes` | Stringified meta-object holding the types of data in the current record, e.g., `{ name: "string", active: "boolean", ... }` |
| `__RUSHDB__KEY__LABEL__` | `__label` | Record Label identifier. Every record has two labels: a default one (`__RUSHDB__LABEL__RECORD__`) and a user-defined one that is searchable. Currently, RushDB allows only one custom label per record, and it is required by default. For more details about labels, see [Labels](../concepts/labels). |
| `__RUSHDB__KEY__PROJECT__ID__` | (not exposed) | Project identifier for multitenancy isolation. This property is never exposed to clients via UI or API. |
## Supported Data Types
RushDB supports a wide range of data types to accommodate diverse data needs:
| Data Type | Description | Example |
|------------|----------------------------------------------|----------------------------|
| `string` | Textual information of unlimited length | `"Hello World"` |
| `number` | Both floating-point numbers and integers | `-120.209817`, `42` |
| `datetime` | ISO 8601 format, including timezones | `"2012-12-21T18:29:37Z"` |
| `boolean` | True or false values | `true`, `false` |
| `null` | Explicit null value | `null` |
| `vector` | Arrays of floating-point numbers or integers | `[0.99070, 0.78912, 1, 0]` |
### Arrays
RushDB supports arrays as property values, but they must contain consistent value types:
Examples of valid arrays include:
- `["apple", "banana", "carrot"]` - string array
- `[4, 8, 15, 16, 23, 42]` - number array
- `["2023-09-17T02:47:54+04:00", "1990-08-18T04:35:00+05:00"]` - datetime array
- `[true, false, true, false, true]` - boolean array
When records are imported, data types are automatically inferred and stored in the `__proptypes` metadata, which helps maintain type consistency across your database.
## Creating Records
Records can be created through:
1. Direct creation via the API or SDK
2. Automatic creation during data import
During record creation:
- A unique `__id` is automatically generated if not provided
- Property types are inferred from values when not specified
- Relationships are established based on data structure
Learn more at [REST API - Create Records](../rest-api/records/create-records.md) or through the language-specific SDKs:
- [TypeScript SDK](../typescript-sdk/records/create-records.md)
- [Python SDK](../python-sdk/records/create-records.md)
## Graph Representation
In RushDB's underlying Neo4j database, records are represented as nodes in a property graph model:
```mermaid
graph TD
subgraph Records
A[":__RUSHDB__LABEL__RECORD__ :Car"]
B[":__RUSHDB__LABEL__RECORD__ :Engine"]
end
subgraph Properties
C[":__RUSHDB__LABEL__PROPERTY__
name: make
type: string"]
D[":__RUSHDB__LABEL__PROPERTY__
name: model
type: string"]
E[":__RUSHDB__LABEL__PROPERTY__
name: power
type: number"]
end
A -->|"__RUSHDB__RELATION__DEFAULT__"| B
C -->|"__RUSHDB__RELATION__VALUE__"| A
D -->|"__RUSHDB__RELATION__VALUE__"| A
E -->|"__RUSHDB__RELATION__VALUE__"| B
```
In this structure:
- Records are nodes with the label `__RUSHDB__LABEL__RECORD__` plus a user-defined label
- Records store actual property values directly as node attributes
- Properties are connected to records via `__RUSHDB__RELATION__VALUE__` relationships
- Related records are connected through `__RUSHDB__RELATION__DEFAULT__` relationships
## Complex Data Structure
RushDB's architecture allows for nested data structures where Records can contain other Records. When importing hierarchical data like JSON objects, RushDB automatically:
1. Assigns unique IDs to all records
2. Applies appropriate labels based on parent keys or user specifications
3. Creates relationships between parent and child records
4. Stores property metadata for type inference
This process enables you to structure your data in a natural, intuitive way while maintaining the graph-based relationships that power efficient queries and traversals.
RushDB automatically manages parent-child relationships between records. When importing nested JSON:
```json
{
"user": {
"name": "Jane Doe",
"address": {
"city": "San Francisco",
"country": "USA"
}
}
}
```
RushDB creates:
1. A "user" record containing the name property
2. An "address" record containing city and country properties
3. A relationship from "user" to "address" of type `__RUSHDB__RELATION__DEFAULT__`
Each nested object becomes its own record with:
- A label derived from its key in the parent object
- A unique ID (following UUIDv7 format)
- Default relationships connecting it to its parent
This transforms into the following graph structure:
```mermaid
graph TD
subgraph Records
A[":__RUSHDB__LABEL__RECORD__ :user
name: Jane Doe"]
B[":__RUSHDB__LABEL__RECORD__ :address
city: San Francisco
country: USA"]
end
subgraph Properties
C[":__RUSHDB__LABEL__PROPERTY__
name: name
type: string"]
D[":__RUSHDB__LABEL__PROPERTY__
name: city
type: string"]
E[":__RUSHDB__LABEL__PROPERTY__
name: country
type: string"]
end
A -->|"__RUSHDB__RELATION__DEFAULT__"| B
C -->|"__RUSHDB__RELATION__VALUE__"| A
D -->|"__RUSHDB__RELATION__VALUE__"| B
E -->|"__RUSHDB__RELATION__VALUE__"| B
```
For details on how to import complex data structures, see [REST API - Import Data](../rest-api/records/import-data) or through the language-specific SDKs:
- [TypeScript SDK](../typescript-sdk/records/import-data)
- [Python SDK](../python-sdk/records/import-data)
For details on how properties are stored and managed, see the [Properties](../concepts/properties) section.
For information about the underlying storage structure, visit the [Storage](../concepts/storage) section.
---
# Relationships
In RushDB, relationships are the connections that link Records together, creating a powerful graph structure that represents both the data itself and how different pieces of data relate to one another. These connections enable intuitive data modeling that aligns with how we naturally think about information and its associations.
## Types of Relationships
RushDB implements three main types of relationships:
### 1. Default Relationships (`__RUSHDB__RELATION__DEFAULT__`)
Default relationships connect related records, typically representing parent-child relationships in nested data structures. For example, a Car record might connect to an Engine record via a default relationship.
```mermaid
graph TD
A[":__RUSHDB__LABEL__RECORD__ :Car"] -->|"__RUSHDB__RELATION__DEFAULT__"| B[":__RUSHDB__LABEL__RECORD__ :Engine"]
C[":__RUSHDB__LABEL__RECORD__ :Motorcycle"] -->|"__RUSHDB__RELATION__DEFAULT__"| D[":__RUSHDB__LABEL__RECORD__ :Engine"]
```
These relationships are automatically created during the data import process when nested objects are detected. Learn more at [REST API - Import Data](../rest-api/records/import-data) or through the language-specific SDKs:
- [TypeScript SDK](../typescript-sdk/records/import-data)
- [Python SDK](../python-sdk/records/import-data)
### 2. Value Relationships (`__RUSHDB__RELATION__VALUE__`)
Value relationships connect Property nodes to their Record nodes. These relationships flow from Properties to Records, indicating which records have which properties.
```mermaid
graph LR
C[":__RUSHDB__LABEL__PROPERTY__
name: color"] -->|"__RUSHDB__RELATION__VALUE__"| A[":__RUSHDB__LABEL__RECORD__ :Car"]
C -->|"__RUSHDB__RELATION__VALUE__"| F[":__RUSHDB__LABEL__RECORD__ :Flower"]
```
This structure allows for finding connections between otherwise unrelated records based on shared properties.
> **Note:** RushDB manages Property-Record relationships (Value relationships) autonomously and doesn't provide APIs to manually interact with or modify this type of relationship. This design ensures data integrity and consistency within the graph model.
### 3. Custom Relationships
Beyond the built-in relationships that RushDB creates automatically during data import, users can define and reconstruct relationships manually in any direction and of any type needed. This flexibility enables sophisticated data modeling that precisely captures your domain's relationship semantics.
You can create, modify, and delete relationships programmatically using the [REST API](../rest-api/relationships) or through the language-specific SDKs:
- [TypeScript SDK](../typescript-sdk/relationships)
- [Python SDK](../python-sdk/relationships)
This capability allows you to:
- Define domain-specific relationship types (e.g., "BELONGS_TO", "MANAGES", "DEPENDS_ON")
- Create relationships between previously unconnected records
- Build complex graph structures that evolve over time
- Restructure relationships as your data model changes
Bulk creation and many-to-many caution
RushDB supports a bulk relationship creation endpoint (`POST /relationships/create-many`) that can either:
- join source and target records by equality on provided keys (the common case), or
- when explicitly requested, create a many-to-many (cartesian) set of relationships between all matched sources and targets.
The many-to-many mode is opt-in and guarded: the request must set a flag (e.g. `manyToMany`) and provide non-empty `where` filters for both sides; otherwise the server requires keys to perform a safe equality join. This prevents accidental, unbounded cartesian products which can be expensive to execute and store.
## Nested Data Example
Consider this JSON structure:
```json
{
"Person": {
"Name": "John Galt",
"Age": 30,
"Contact": {
"Email": "john.galt@example.com",
"Phone": "123-456-7890"
},
"Address": {
"Street": "123 Main Street",
"City": "Anytown",
"State": "CA",
"ZipCode": "12345"
}
}
}
```
When imported into RushDB, this is transformed into a graph structure with:
- 3 Records (Person, Contact, and Address)
- 8 Properties (Name, Age, Email, Phone, Street, City, State, ZipCode)
- Default relationships connecting Person to Contact and Person to Address
```mermaid
graph LR
Person[":__RUSHDB__LABEL__RECORD__ :Person"] -->|"__RUSHDB__RELATION__DEFAULT__"| Contact[":__RUSHDB__LABEL__RECORD__ :Contact"]
Person -->|"__RUSHDB__RELATION__DEFAULT__"| Address[":__RUSHDB__LABEL__RECORD__ :Address"]
Name[":__RUSHDB__LABEL__PROPERTY__
name: Name"] -->|"__RUSHDB__RELATION__VALUE__"| Person
Age[":__RUSHDB__LABEL__PROPERTY__
name: Age"] -->|"__RUSHDB__RELATION__VALUE__"| Person
Email[":__RUSHDB__LABEL__PROPERTY__
name: Email"] -->|"__RUSHDB__RELATION__VALUE__"| Contact
Phone[":__RUSHDB__LABEL__PROPERTY__
name: Phone"] -->|"__RUSHDB__RELATION__VALUE__"| Contact
Street[":__RUSHDB__LABEL__PROPERTY__
name: Street"] -->|"__RUSHDB__RELATION__VALUE__"| Address
City[":__RUSHDB__LABEL__PROPERTY__
name: City"] -->|"__RUSHDB__RELATION__VALUE__"| Address
State[":__RUSHDB__LABEL__PROPERTY__
name: State"] -->|"__RUSHDB__RELATION__VALUE__"| Address
ZipCode[":__RUSHDB__LABEL__PROPERTY__
name: ZipCode"] -->|"__RUSHDB__RELATION__VALUE__"| Address
```
## Data Import Process
RushDB's data import mechanism uses a breadth-first search (BFS) algorithm to parse JSON structures and establish relationships:
1. Nested objects are detected and converted to separate Record nodes
2. Parent-child relationships are established via `__RUSHDB__RELATION__DEFAULT__` edges
3. Property nodes are connected to their respective Record nodes via `__RUSHDB__RELATION__VALUE__` edges
This approach allows for intuitive transformation of hierarchical data into a graph structure without requiring users to understand the underlying graph model.
## Benefits of RushDB's Relationship Structure
This relationship model provides several advantages:
1. **Intuitive Data Modeling**: You can structure your data in a way that matches how you think about it
2. **Efficient Traversals**: The graph structure enables fast navigation between related records
3. **Hidden Insights**: Property connections can reveal relationships between seemingly unrelated records
4. **Flexible Structure**: Relationships can be easily rearranged or modified as your data model evolves
To learn more about how to work with relationships in your queries and data operations, see the [REST API](../rest-api/relationships) or through the language-specific SDKs:
- [TypeScript SDK](../typescript-sdk/relationships)
- [Python SDK](../python-sdk/relationships)
---
# Aggregations
SearchQuery provides powerful aggregation capabilities that allow you to perform calculations and collect data from your records and their relationships.
#### Aggregation Placement in SearchQuery DTO
All aggregation clauses are defined in the `aggregate` key of the SearchQuery DTO, which is at the same level as other query parameters such as `where`, `limit`, `skip`, `orderBy`, and `labels`:
```typescript
// SearchQuery
{
labels: ['COMPANY'], // Record labels to search
where: { /* conditions */ }, // Filtering conditions
limit: 10, // Results limit
skip: 0, // Results offset
orderBy: { name: 'asc' }, // Sorting
aggregate: { // Aggregation definitions
count: {
fn: 'count',
alias: '$record'
},
// More aggregations...
}
}
```
> **Note**: Aggregations are applied only when fetching records and have no effect on other endpoints supporting SearchQuery. The main goal of aggregations is to fetch Records in the desired shape, optimizing data retrieval and transformation in a single query.
## Available Aggregation Functions
The following aggregation functions are supported:
- `avg` - Calculate average value of a numeric field
- `count` - Count records (with optional `uniq` parameter)
- `max` - Get maximum value from a field
- `min` - Get minimum value from a field
- `sum` - Calculate sum of a numeric field
- `collect` - Gather field values or entire records into an array
- `gds.similarity.*` - Calculate vector similarity using various algorithms:
- `cosine` - Cosine similarity [-1,1]
- `euclidean` - Euclidean distance normalized to (0,1]
- `euclideanDistance` - Raw euclidean distance [0,∞)
- `jaccard` - Jaccard similarity [0,1]
- `overlap` - Overlap coefficient [0,1]
- `pearson` - Pearson correlation [-1,1]
## Aliases
Every aggregation clause requires an `alias` parameter that specifies which record from graph traversal should be used. To reference fields from related records in aggregations, you need to define aliases in the `where` clause using the `$alias` parameter. By default, the root record has alias `$record`:
```typescript
{
labels: ['COMPANY'],
where: {
DEPARTMENT: {
$alias: '$department',
PROJECT: {
$alias: '$project',
EMPLOYEE: {
$alias: '$employee'
}
}
}
},
aggregate: {
// Referencing to root record using '$record' alias
companyName: '$record.name',
// Now can use $employee in aggregations
avgSalary: {
fn: 'avg',
field: 'salary',
alias: '$employee'
}
}
}
```
## Basic Aggregations
Example topology:
```mermaid
graph LR
A[COMPANY] --has--> B[EMPLOYEE]
```
### avg
**Parameters:**
- `fn`: 'avg' - The aggregation function name
- `field`: string - The field to calculate average for
- `alias`: string - The record alias to use
- `precision?`: number - Optional decimal precision for the result
```typescript
{
labels: ['COMPANY'],
where: {
EMPLOYEE: {
$alias: '$employee',
salary: {
$gte: 50000 // Filter employees by salary
}
}
},
aggregate: {
avgSalary: {
fn: 'avg',
field: 'salary',
alias: '$employee',
precision: 2 // Optional: Set precision for the result
}
}
}
```
### count
**Parameters:**
- `fn`: 'count' - The aggregation function name
- `alias`: string - The record alias to use
- `field?`: string - Optional field to count
- `uniq?`: boolean - Optional flag to count unique values
```typescript
{
labels: ['COMPANY'],
where: {
EMPLOYEE: {
$alias: '$employee'
}
},
aggregate: {
employeesCount: {
fn: 'count',
uniq: true, // Count unique employees
alias: '$employee'
}
}
}
```
### max
**Parameters:**
- `fn`: 'max' - The aggregation function name
- `field`: string - The field to find maximum value from
- `alias`: string - The record alias to use
```typescript
{
labels: ['COMPANY'],
where: {
EMPLOYEE: {
$alias: '$employee'
}
},
aggregate: {
maxSalary: {
fn: 'max',
field: 'salary',
alias: '$employee'
}
}
}
```
### min
**Parameters:**
- `fn`: 'min' - The aggregation function name
- `field`: string - The field to find minimum value from
- `alias`: string - The record alias to use
```typescript
{
labels: ['COMPANY'],
where: {
EMPLOYEE: {
$alias: '$employee'
}
},
aggregate: {
minSalary: {
fn: 'min',
field: 'salary',
alias: '$employee'
}
}
}
```
### sum
**Parameters:**
- `fn`: 'sum' - The aggregation function name
- `field`: string - The field to calculate sum for
- `alias`: string - The record alias to use
```typescript
{
labels: ['COMPANY'],
where: {
EMPLOYEE: {
$alias: '$employee'
}
},
aggregate: {
totalWage: {
fn: 'sum',
field: 'salary',
alias: '$employee'
}
}
}
```
### collect
**Parameters:**
- `fn`: 'collect' - The aggregation function name
- `alias`: string - The record alias to use
- `field?`: string - Optional field to collect (if not provided, collects entire records)
- `uniq?`: boolean - Optional flag to collect unique values only. True by default.
- `limit?`: number - Optional maximum number of items to collect
- `skip?`: number - Optional number of items to skip
- `orderBy?`: TSearchSort - Optional sorting configuration
```typescript
{
labels: ['COMPANY'],
where: {
EMPLOYEE: {
$alias: '$employee'
}
},
aggregate: {
employeeNames: {
fn: 'collect',
field: 'name',
alias: '$employee',
uniq: true // Optional: true by default
}
}
}
```
---
### Complete Example
```typescript
{
labels: ['COMPANY'],
where: {
EMPLOYEE: {
$alias: '$employee', // Define alias for employee records
salary: {
$gte: 50000 // Filter employees by salary
}
}
},
aggregate: {
// Use field directly from record
companyName: '$record.name',
// Count unique employees using the defined alias
employeesCount: {
fn: 'count',
uniq: true,
alias: '$employee'
},
// Calculate total salary using the defined alias
totalWage: {
fn: 'sum',
field: 'salary',
alias: '$employee'
},
// Collect unique employees names
employeeNames: {
fn: 'collect',
field: 'name',
alias: '$employee'
},
// Get average salary with precision
avgSalary: {
fn: 'avg',
field: 'salary',
alias: '$employee',
precision: 0
},
// Get min and max salary
minSalary: {
fn: 'min',
field: 'salary',
alias: '$employee'
},
maxSalary: {
fn: 'max',
field: 'salary',
alias: '$employee'
}
}
}
```
Example data and response
```typescript
// Company record
{
__id: "018838b8-2e1f-7000-8000-a29392548450",
__label: "COMPANY",
name: "TechCorp",
stage: ["seed"]
}
// Employee records
[
{
__id: "018838b8-2e1f-7000-8000-b45fd8932abc",
__label: "EMPLOYEE",
name: "John Doe",
salary: 550000
},
{
__id: "018838b8-2e1f-7000-8000-c67de9043def",
__label: "EMPLOYEE",
name: "Jane Smith",
salary: 600000
}
]
// Query result:
{
data: [{
__id: "018838b8-2e1f-7000-8000-a29392548450",
__label: "COMPANY",
companyName: "TechCorp",
employeesCount: 2,
totalWage: 1150000,
avgSalary: 575000,
minSalary: 550000,
maxSalary: 600000,
employeeNames: ["Jane Smith", "John Doe"]
}],
total: 1,
success: true
}
```
## Nested Aggregations
SearchQuery supports two types of nested aggregations:
### 1. Collecting Nested Records
You can use the `collect` operator to build nested JSON structures containing arrays of related records. Due to Cypher limitations, when using nested collection, only the `collect` operator is supported at nested levels.
Example topology:
```mermaid
graph LR
A[COMPANY] --has--> B[DPEARTMENT]
B --has--> C[PROJECT]
C --has--> D[EMPLOYEE]
```
Example with nested where clauses and corresponding aggregations:
```typescript
{
labels: ['COMPANY'],
where: {
DPEARTMENT: {
$alias: '$department', // Level 1 alias
PROJECT: {
$alias: '$project', // Level 2 alias
EMPLOYEE: {
$alias: '$employee', // Level 3 alias
salary: {
$gte: 100000 // Filter condition
}
}
}
}
},
aggregate: {
departments: {
fn: 'collect',
alias: '$department', // Use Level 1 alias
aggregate: {
projects: {
fn: 'collect',
alias: '$project', // Use Level 2 alias
orderBy: {
projectName: 'asc'
},
aggregate: {
employees: {
fn: 'collect',
alias: '$employee', // Use Level 3 alias
orderBy: {
salary: 'desc'
},
limit: 3
}
}
}
}
}
}
}
```
Example data and response
```typescript
// Company record
{
__id: "018838b8-2e1f-7000-8000-a29392548450",
__label: "COMPANY",
name: "TechCorp",
rating: 4
}
// Department records
[
{
__id: "018838b8-2e1f-7000-8000-d89ef1154abc",
__label: "departments",
name: "Engineering"
},
{
__id: "018838b8-2e1f-7000-8000-e92fg2265bcd",
__label: "departments",
name: "Sales"
}
]
// Project records
[
{
__id: "018838b8-2e1f-7000-8000-f34gh3376cde",
__label: "projects",
projectName: "Mobile App"
},
{
__id: "018838b8-2e1f-7000-8000-g56hi4487def",
__label: "projects",
projectName: "Web Platform"
}
]
// Employee records
[
{
__id: "018838b8-2e1f-7000-8000-h78ij5598efg",
__label: "employees",
name: "John Doe",
salary: 500000
},
{
__id: "018838b8-2e1f-7000-8000-i90kl6609fgh",
__label: "employees",
name: "Jane Smith",
salary: 550000
},
{
__id: "018838b8-2e1f-7000-8000-j12mn7710ghi",
__label: "employees",
name: "Bob Wilson",
salary: 600000
}
]
// Query result with nested collection:
{
data: [{
__id: "018838b8-2e1f-7000-8000-a29392548450",
__label: "COMPANY",
departments: [{
__id: "018838b8-2e1f-7000-8000-d89ef1154abc",
name: "Engineering",
projects: [{
__id: "018838b8-2e1f-7000-8000-f34gh3376cde",
projectName: "Mobile App",
employees: [
{
__id: "018838b8-2e1f-7000-8000-j12mn7710ghi",
name: "Bob Wilson",
salary: 600000
},
{
__id: "018838b8-2e1f-7000-8000-i90kl6609fgh",
name: "Jane Smith",
salary: 550000
},
{
__id: "018838b8-2e1f-7000-8000-h78ij5598efg",
name: "John Doe",
salary: 500000
}
]
}]
}]
}],
total: 1,
success: true
}
```
### 2. Aggregating Values from Nested Records
Example topology:
```mermaid
graph LR
A[COMPANY] --has--> B[PROJECT]
B --has--> C[EMPLOYEE]
```
Example with deep nested aggregation:
```typescript
{
labels: ['COMPANY'],
where: {
PROJECT: {
EMPLOYEE: {
$alias: '$employee',
salary: {
$gte: 50000 // Filter condition
}
}
}
},
aggregate: {
avgEmployeeSalary: {
fn: 'avg',
field: 'salary',
alias: '$employee' // Use alias from deepest level
}
}
}
```
Example data and response
```typescript
// Company record
{
__id: "018838b8-2e1f-7000-8000-a29392548450",
__label: "COMPANY",
name: "TechCorp",
rating: 4
}
// Project records
[
{
__id: "018838b8-2e1f-7000-8000-f34gh3376cde",
__label: "PROJECT",
name: "Mobile App"
}
]
// Employee records under projects
[
{
__id: "018838b8-2e1f-7000-8000-h78ij5598efg",
__label: "EMPLOYEE",
name: "John Doe",
salary: 500000
},
{
__id: "018838b8-2e1f-7000-8000-i90kl6609fgh",
__label: "EMPLOYEE",
name: "Jane Smith",
salary: 550000
}
]
// Query result with aggregated values:
{
data: [{
__id: "018838b8-2e1f-7000-8000-a29392548450",
__label: "COMPANY",
avgEmployeeSalary: 525000
}],
total: 1,
success: true
}
```
## Collect Operator Options
The `collect` operator supports additional options for pagination and sorting:
- `limit` - Maximum number of records to collect
- `skip` - Number of records to skip
- `orderBy` - Sort collected records by specified fields
- `uniq` - Collect only unique values (when collecting field values)
- `field` - Collect specific field values instead of entire records
Example:
```typescript
{
labels: ['COMPANY'],
where: {
DEPARTMENT: {
$alias: '$department'
}
},
aggregate: {
// Collect unique tags from departments
tags: {
fn: 'collect',
alias: '$department',
field: 'tags', // Collect only tags field
uniq: true, // Remove duplicates
limit: 100, // Collect up to 100 tags
orderBy: { // Sort alphabetically
name: 'asc'
}
}
}
}
```
Example data and response
```typescript
// Company record
{
__id: "018838b8-2e1f-7000-8000-a29392548450",
__label: "COMPANY",
name: "TechCorp"
}
// Department records
[
{
__id: "018838b8-2e1f-7000-8000-d89ef1154abc",
__label: "DEPARTMENT",
name: "Engineering",
tags: ["tech", "development", "agile"]
},
{
__id: "018838b8-2e1f-7000-8000-e92fg2265bcd",
__label: "DEPARTMENT",
name: "Sales",
tags: ["sales", "business", "development"]
}
]
// Query result with collected tags:
{
data: [{
__id: "018838b8-2e1f-7000-8000-a29392548450",
__label: "COMPANY",
tags: ["agile", "business", "development", "sales", "tech"]
}],
total: 1,
success: true
}
```
## Vector Similarity Aggregations
Example topology:
```mermaid
graph LR
A[DOCUMENT] --has--> B[CHUNK]
```
### gds.similarity.*
**Parameters:**
- `fn`: 'gds.similarity.[algorithm]' - The similarity algorithm to use
- `gds.similarity.cosine` - Cosine similarity [-1,1]
- `gds.similarity.euclidean` - Euclidean distance normalized to (0,1]
- `gds.similarity.euclideanDistance` - Raw euclidean distance [0,∞)
- `gds.similarity.jaccard` - Jaccard similarity [0,1]
- `gds.similarity.overlap` - Overlap coefficient [0,1]
- `gds.similarity.pearson` - Pearson correlation [-1,1]
- `field`: string - The vector field to compare
- `alias`: string - The record alias to use
- `query`: number[] - The query vector to calculate similarity against
Example showing vector search with where clause and similarity aggregation:
```typescript
{
labels: ['DOCUMENT'],
where: {},
aggregate: {
// Calculate similarity score using root level alias
similarity: {
fn: 'gds.similarity.cosine',
field: 'embedding',
query: [1, 2, 3, 4, 5],
alias: '$record'
}
}
}
```
Example data and response
```typescript
// Document record
{
__id: "018838b8-2e1f-7000-8000-k34op8821hij",
__label: "DOCUMENT",
title: "Machine Learning Basics"
}
// Chunk records
[
{
__id: "018838b8-2e1f-7000-8000-l56pq9932ijk",
__label: "CHUNK",
content: "Introduction to neural networks",
embedding: [1.2, 0.5, -0.3, 0.8, 0.1]
},
{
__id: "018838b8-2e1f-7000-8000-m78rs0043jkl",
__label: "CHUNK",
content: "Deep learning architectures",
embedding: [0.9, 0.4, -0.2, 0.7, 0.3]
}
]
// Query result with similarity scores:
{
data: [{
__id: "018838b8-2e1f-7000-8000-k34op8821hij",
__label: "DOCUMENT",
similarity: 0.82,
chunks: [
{
__id: "018838b8-2e1f-7000-8000-l56pq9932ijk",
__label: "CHUNK",
content: "Introduction to neural networks",
similarity: 0.78
},
{
__id: "018838b8-2e1f-7000-8000-m78rs0043jkl",
__label: "CHUNK",
content: "Deep learning architectures",
similarity: 0.65
}
]
}],
total: 1,
success: true
}
```
---
# Search
RushDB provides a powerful and flexible search system that allows you to efficiently query and traverse your graph data. The Search API is a cornerstone of RushDB, enabling you to find records, filter by conditions, navigate relationships, aggregate results, and format the returned data exactly as needed.
## Core Capabilities
RushDB's Search API offers a comprehensive set of features:
- **Powerful Filtering**: Use the [`where` clause](./where.md) with a wide range of operators to precisely filter records
- **Graph Traversal**: Navigate through connected records with relationship queries
- **Aggregation**: Perform calculations and transform data using [aggregation functions](./aggregations.md)
- **Pagination and Sorting**: Control result volume and order with [pagination and sorting options](./pagination-order.md)
- **Label-Based Filtering**: Target specific types of records using [label filtering](./labels.md)
- **Vector Search**: Find records based on vector similarity for AI and machine learning applications
## SearchQuery Structure
All search operations in RushDB use a consistent SearchQuery data structure:
```typescript
interface SearchQuery {
labels?: string[]; // Filter by record labels
where?: WhereCondition; // Filter by property values and relationships
limit?: number; // Maximum number of records to return (default: 100)
skip?: number; // Number of records to skip (for pagination)
orderBy?: OrderByClause; // Sorting configuration
aggregate?: AggregateClause; // Data aggregation and transformation
}
```
## Basic Example
Here's a simple example of searching for products:
```typescript
db.records.find({
labels: ["PRODUCT"],
where: {
title: { $contains: "Sneakers" },
SIZE: {
uk: { $gte: 8, $lte: 9 },
qty: { $gt: 0 }
}
},
limit: 20,
orderBy: { price: 'asc' }
})
```
This query:
1. Searches for records with the `PRODUCT` label
2. Filters for products with "Sneakers" in the title
3. Finds only those with UK sizes 8-9 that are in stock
4. Limits results to 20 records
5. Sorts results by price in ascending order
## Advanced Search Features
### Filtering with Where Clauses
The [`where` clause](./where.md) is the primary mechanism for filtering records. It supports:
- **Property Matching**: Filter by exact values, string patterns, numeric ranges, etc.
- **Logical Operators**: Combine conditions with AND, OR, NOT, etc.
- **Relationship Traversal**: Filter based on properties of related records
```typescript
{
where: {
category: "Electronics",
price: { $gte: 100, $lte: 500 },
$or: [
{ inStock: true },
{ preorderAvailable: true }
]
}
}
```
### Aggregating Results
[Aggregations](./aggregations.md) allow you to perform calculations and transform the structure of your results:
```typescript
{
labels: ["ORDER"],
where: {
PRODUCT: {
$alias: "$product",
category: "Electronics"
}
},
aggregate: {
totalSpent: {
fn: "sum",
field: "amount",
alias: "$record"
},
products: {
fn: "collect",
alias: "$product"
}
}
}
```
### Pagination and Sorting
Control the volume and order of results using [pagination and sorting options](./pagination-order.md):
```typescript
{
labels: ["CUSTOMER"],
limit: 50, // Return up to 50 records
skip: 100, // Skip the first 100 records
orderBy: {
lastName: "asc", // Sort by lastName ascending
firstName: "asc" // Then by firstName ascending
}
}
```
## Performance Best Practices
For optimal performance when using the Search API:
1. **Be Specific**: Filter by labels when possible to narrow the search scope
2. **Use Indexed Properties**: Prioritize filtering on properties that have indexes
3. **Limit Results**: Use pagination to retrieve only the records you need
4. **Optimize Queries**: Avoid deep relationship traversals when possible
5. **Use Aliases Efficiently**: Define aliases only for records you need to reference in aggregations
## Next Steps
- Learn more about [filtering with where clauses](./where.md)
- Explore [data aggregation capabilities](./aggregations.md)
- Understand [pagination and sorting options](./pagination-order.md)
- Discover how to filter by [record labels](./labels.md)
---
# Labels
The `labels` property in SearchQuery allows you to filter records by their label types. Labels in RushDB are categories or classifications assigned to records that help organize and identify different types of data.
#### Labels Placement in SearchQuery DTO
The `labels` array is defined at the top level of the SearchQuery DTO, alongside other query parameters:
```typescript
// SearchQuery
{
labels: ['COMPANY'], // Record labels to search (this is what we focus on)
where: { /* conditions */ }, // Filtering conditions
limit: 10, // Results limit
skip: 0, // Results offset
orderBy: { name: 'asc' }, // Sorting
aggregate: { /* aggregations */ } // Aggregation definitions
}
```
## Basic Usage
### Filtering by a Single Label
The simplest way to use labels is to specify a single label to filter the records:
```typescript
{
labels: ['PERSON'] // Only search for records with the PERSON label
}
```
This query will only return records that have the PERSON label.
### Implicit Filtering
When no labels are provided in the query, RushDB will search across all record labels:
```typescript
{
// No 'labels' property - will search across all record types
where: {
name: "John"
}
}
```
## Label Behavior
There are a few important things to understand about how labels work in SearchQuery:
1. **Case sensitivity**: Labels are case-sensitive. 'PERSON' and 'Person' are treated as different labels.
2. **Single label optimization**: When you specify exactly one label in the array, RushDB can optimize the query execution significantly. This is because it can use label-specific indexes in the graph database.
3. **Multiple labels behavior**: When you specify multiple labels, the search will return records that match ANY of the provided labels (OR condition).
## Examples
### Single Label Search
```typescript
{
labels: ['COMPANY'],
where: {
founded: { $gte: 2010 }
}
}
```
This query searches for COMPANY records founded in or after 2010. This is the most efficient way to query when you know exactly what type of record you're looking for.
### Multiple Labels Search
```typescript
{
labels: ['PERSON', 'EMPLOYEE'],
where: {
age: { $gte: 18 }
}
}
```
This query searches for records that have either the PERSON label OR the EMPLOYEE label, and have an age greater than or equal to 18.
### Combining Labels with Relationship Queries
You can combine label filtering with relationship traversal in the where clause:
```typescript
{
labels: ['COMPANY'],
where: {
founded: { $gte: 2010 },
EMPLOYEE: {
position: "Software Engineer",
SKILL: {
name: "TypeScript"
}
}
}
}
```
This query finds companies founded in or after 2010 that have employees with the position "Software Engineer" who possess the "TypeScript" skill.
### Using Labels with Aggregations
Labels are particularly useful when you want to perform aggregations on specific types of records:
```typescript
{
labels: ['COMPANY'],
where: {
EMPLOYEE: {
$alias: '$employee'
}
},
aggregate: {
employeeCount: {
fn: 'count',
alias: '$employee'
}
}
}
```
This query counts the number of employees for each company.
## Performance Considerations
1. **Always specify a label when possible**: Queries with a single label are significantly faster than queries without label constraints. This is especially important for large databases.
2. **Be specific**: The more specific you can be with your label, the better the performance of your query will be.
3. **Label-based indexes**: RushDB uses label-based indexes to quickly locate records of a specific type. Without labels, the system must scan a much larger set of records.
## Additional Notes
- **System labels**: Some labels in RushDB are prefixed with `__RUSHDB__` and are used for internal purposes. These are not typically used in user queries.
- **Default behavior**: If you don't specify any labels, the search will include all non-system labeled records.
- **Labels vs properties**: While you can also filter records based on their properties, using labels is generally more efficient when you know the type of record you're looking for.
- **Label inheritance**: RushDB does not have a concept of label hierarchy or inheritance. Each record can have its own set of independent labels.
## Complete Examples
Using labels with complex where conditions
```typescript
{
labels: ['PRODUCT'],
where: {
price: { $gte: 100, $lte: 500 },
inStock: true,
CATEGORY: {
name: { $in: ["Electronics", "Computers"] }
},
REVIEW: {
$alias: '$review',
rating: { $gte: 4 }
}
},
aggregate: {
avgRating: {
fn: 'avg',
field: 'rating',
alias: '$review',
precision: 1
}
},
orderBy: { price: 'desc' },
limit: 20
}
```
This example searches for PRODUCT records with a price between 100 and 500 that are in stock, belong to either the "Electronics" or "Computers" category, and have reviews with ratings of at least 4. It also calculates the average rating for each product, sorts the results by price in descending order, and limits the results to 20 records.
Using multiple labels to search across record types
```typescript
{
labels: ['ARTICLE', 'BLOG_POST', 'NEWS'],
where: {
published: true,
$or: [
{ title: { $contains: "AI" } },
{ content: { $contains: "artificial intelligence" } }
],
AUTHOR: {
$alias: '$author',
reputation: { $gte: 100 }
}
},
orderBy: { publishedAt: 'desc' },
limit: 50,
aggregate: {
authorName: '$author.name'
}
}
```
This example searches across three types of content records (ARTICLE, BLOG_POST, and NEWS) that are published and contain either "AI" in the title or "artificial intelligence" in the content, written by authors with a reputation of at least 100. It retrieves the author's name for each record, sorts the results by publication date in descending order, and limits the results to 50 records.
---
# Pagination and Order
SearchQuery provides flexible pagination and ordering capabilities to control the volume of returned data and the sequence in which records are presented.
## Overview
When querying data with the SearchQuery DTO, you can control:
- **Result Limit**: The maximum number of records to return
- **Offset**: The number of records to skip
- **Sorting Order**: How results should be sorted
These settings are defined at the top level in the SearchQuery DTO, alongside filtering conditions and other parameters:
```typescript
// SearchQuery
{
labels: ['COMPANY'], // Record labels to search
where: { /* conditions */ }, // Filtering conditions
limit: 100, // Results limit (optional)
skip: 0, // Results offset (optional)
orderBy: { name: 'asc' }, // Sorting (optional)
aggregate: { /* aggregations */ } // Aggregations (optional)
}
```
## Pagination Parameters
### `limit`
- **Type**: `number`
- **Optional**: Yes
- **Default**: `100`
- **Valid Range**: `1` to `1000`
- **Description**: Specifies the maximum number of records to return in the result set.
### `skip`
- **Type**: `number`
- **Optional**: Yes
- **Default**: `0`
- **Description**: Specifies the number of records to skip before starting to return results. Useful for implementing paged access to large result sets.
## Sorting Records with `orderBy`
The `orderBy` parameter controls the order in which records are returned.
### Types of Sorting
You can specify sorting in two ways:
1. **Simple Direction Sorting**: Apply a sort direction to the default ID field
2. **Field-Specific Sorting**: Sort by specific fields with individual directions
### Sort Direction Values
- `asc`: Ascending order (A → Z, 0 → 9)
- `desc`: Descending order (Z → A, 9 → 0)
### Examples
#### Simple Direction Sorting
When using a string value for `orderBy`, the system sorts by the internal ID field:
```typescript
{
labels: ['EMPLOYEE'],
orderBy: 'asc', // Sort by ID in ascending order
limit: 50
}
```
#### Field-Specific Sorting
For sorting by specific fields, use an object with field names as keys and sort directions as values:
```typescript
{
labels: ['EMPLOYEE'],
orderBy: {
salary: 'desc', // Sort by salary in descending order
name: 'asc' // Then by name in ascending order (if salaries are equal)
},
limit: 50
}
```
## Pagination Examples
### Basic Pagination
```typescript
// First page (records 1-100)
{
labels: ['COMPANY'],
limit: 100,
skip: 0
}
// Second page (records 101-200)
{
labels: ['COMPANY'],
limit: 100,
skip: 100
}
// Third page (records 201-300)
{
labels: ['COMPANY'],
limit: 100,
skip: 200
}
```
### Different Page Sizes
```typescript
// Get first 25 records
{
labels: ['PRODUCT'],
limit: 25,
skip: 0
}
// Get 50 records starting from the 26th record
{
labels: ['PRODUCT'],
limit: 50,
skip: 25
}
```
## Combined Pagination and Sorting
You can combine pagination and sorting to implement sophisticated data access patterns:
```typescript
// Get the top 10 highest-paid employees
{
labels: ['EMPLOYEE'],
orderBy: { salary: 'desc' },
limit: 10,
skip: 0
}
// Get the next 10 highest-paid employees
{
labels: ['EMPLOYEE'],
orderBy: { salary: 'desc' },
limit: 10,
skip: 10
}
```
## Default Behavior
- If `limit` is not specified, the default value is `100`
- If `limit` is greater than `1000`, it will be capped at `1000`
- If `skip` is not specified, the default value is `0`
- If `orderBy` is not specified, results are sorted by the internal ID field in descending order
## Performance Considerations
- For large datasets, combining high `skip` values with complex filtering conditions may impact performance
- Consider using filtering conditions that leverage indexes for better performance with larger offsets
- When possible, structure your application to use smaller page sizes (lower `limit` values)
- The maximum allowed `limit` value (1000) is designed to prevent excessive resource consumption
---
# Where
The `where` clause in SearchQuery is a powerful mechanism to filter records based on property values and relationships. It's one of the key elements that make RushDB queries flexible and expressive.
#### Where Placement in SearchQuery DTO
The `where` clause is defined in the `where` key of the SearchQuery DTO:
```typescript
// SearchQuery
{
labels: ['COMPANY'], // Record labels to search
where: { /* conditions */ }, // Filtering conditions (this is what we focus on)
limit: 10, // Results limit
skip: 0, // Results offset
orderBy: { name: 'asc' }, // Sorting
aggregate: { /* aggregations */ } // Aggregation definitions
}
```
## Basic Operators
RushDB provides various operators for different data types to create precise conditions for filtering records.
### Primitive Value Matching
The simplest form of filtering is direct equality matching:
```typescript
{
where: {
name: "John Doe", // Exact string match
isActive: true, // Boolean match
age: 30, // Number match
created: "2023-01-01T00:00:00Z" // ISO 8601 datetime match
}
}
```
### String Operators
#### $contains
The `$contains` operator checks if a string field contains the specified substring.
```typescript
{
where: {
name: { $contains: "John" } // Matches "John", "Johnny", "Johnson", etc.
}
}
```
#### $startsWith
The `$startsWith` operator checks if a string field starts with the specified substring.
```typescript
{
where: {
name: { $startsWith: "J" } // Matches "John", "Jane", "James", etc.
}
}
```
#### $endsWith
The `$endsWith` operator checks if a string field ends with the specified substring.
```typescript
{
where: {
name: { $endsWith: "son" } // Matches "Johnson", "Jackson", etc.
}
}
```
#### $in
The `$in` operator checks if a string field matches any value in the specified array.
```typescript
{
where: {
status: { $in: ["active", "pending"] } // Matches "active" or "pending"
}
}
```
#### $nin
The `$nin` operator checks if a string field does not match any value in the specified array.
```typescript
{
where: {
status: { $nin: ["deleted", "archived"] } // Matches anything except "deleted" or "archived"
}
}
```
#### $ne
The `$ne` operator checks if a string field is not equal to the specified value.
```typescript
{
where: {
status: { $ne: "deleted" } // Matches anything except "deleted"
}
}
```
### Number Operators
#### $gt
The `$gt` (greater than) operator checks if a number field is greater than the specified value.
```typescript
{
where: {
age: { $gt: 18 } // Matches age greater than 18
}
}
```
#### $gte
The `$gte` (greater than or equal to) operator checks if a number field is greater than or equal to the specified value.
```typescript
{
where: {
age: { $gte: 21 } // Matches age 21 or greater
}
}
```
#### $lt
The `$lt` (less than) operator checks if a number field is less than the specified value.
```typescript
{
where: {
age: { $lt: 65 } // Matches age less than 65
}
}
```
#### $lte
The `$lte` (less than or equal to) operator checks if a number field is less than or equal to the specified value.
```typescript
{
where: {
age: { $lte: 64 } // Matches age 64 or less
}
}
```
#### $in
The `$in` operator checks if a number field matches any value in the specified array.
```typescript
{
where: {
age: { $in: [20, 30, 40] } // Matches age 20, 30, or 40
}
}
```
#### $nin
The `$nin` operator checks if a number field does not match any value in the specified array.
```typescript
{
where: {
age: { $nin: [20, 30, 40] } // Matches any age except 20, 30, or 40
}
}
```
#### $ne
The `$ne` operator checks if a number field is not equal to the specified value.
```typescript
{
where: {
age: { $ne: 18 } // Matches any age except 18
}
}
```
### Boolean Operators
#### Direct matching
```typescript
{
where: {
isActive: true // Matches records where isActive is true
}
}
```
#### $ne
The `$ne` operator checks if a boolean field is not equal to the specified value.
```typescript
{
where: {
isActive: { $ne: false } // Matches records where isActive is not false (i.e., true or not set)
}
}
```
### Datetime Operators
RushDB provides specialized operators for filtering records based on datetime values.
#### ISO 8601 string
You can match datetime values using ISO 8601 formatted strings:
```typescript
{
where: {
created: "2023-01-01T00:00:00Z" // Exact datetime match
}
}
```
#### Datetime object components
You can also match based on specific datetime components:
```typescript
{
where: {
created: {
$year: 2023,
$month: 1,
$day: 1
} // Matches January 1, 2023 (any time)
}
}
```
Available datetime components:
- `$year`: Match by year
- `$month`: Match by month (1-12)
- `$day`: Match by day of month (1-31)
- `$hour`: Match by hour (0-23)
- `$minute`: Match by minute (0-59)
- `$second`: Match by second (0-59)
- `$millisecond`: Match by millisecond
- `$microsecond`: Match by microsecond
- `$nanosecond`: Match by nanosecond
#### Comparison operators
Datetime values support comparison operators:
```typescript
{
where: {
created: { $gte: "2023-01-01T00:00:00Z" } // Matches dates on or after January 1, 2023
}
}
```
```typescript
{
where: {
created: {
$gte: { $year: 2023, $month: 1, $day: 1 },
$lt: { $year: 2024, $month: 1, $day: 1 }
} // Matches dates in the year 2023
}
}
```
All number comparison operators (`$gt`, `$gte`, `$lt`, `$lte`, `$ne`) are supported for datetime values.
#### Array operators
You can also use array operators with datetime values:
```typescript
{
where: {
created: {
$in: [
"2023-01-01T00:00:00Z",
"2023-02-01T00:00:00Z"
]
} // Matches either of these two specific dates
}
}
```
```typescript
{
where: {
created: {
$nin: [
{ $year: 2020, $month: 1, $day: 1 },
{ $year: 2021, $month: 1, $day: 1 }
]
} // Matches dates that are not January 1 of 2020 or 2021
}
}
```
### Vector Operators
RushDB supports vector similarity searches through the `$vector` operator, which is useful for semantic searches, embeddings comparison, and machine learning applications.
```typescript
{
where: {
embedding: {
$vector: {
fn: "gds.similarity.cosine", // Similarity function
query: [1, 2, 3, 4, 5], // Query vector
threshold: 0.75 // Minimum similarity threshold
}
}
}
}
```
Available similarity functions:
- `cosine`: Cosine similarity [-1,1]
- `euclidean`: Euclidean distance normalized to (0,1]
- `euclideanDistance`: Raw euclidean distance [0,∞)
- `jaccard`: Jaccard similarity [0,1]
- `overlap`: Overlap coefficient [0,1]
- `pearson`: Pearson correlation [-1,1]
The `threshold` parameter can be:
- A simple number (with different default behaviors):
- For `euclidean` and `euclideanDistance` functions, a simple threshold is treated as `$lte` (less than or equal to)
- For all other functions (`cosine`, `jaccard`, `overlap`, `pearson`), a simple threshold is treated as `$gte` (greater than or equal to)
- An object with comparison operators for more precise filtering:
```typescript
{
where: {
embedding: {
$vector: {
fn: "gds.similarity.cosine",
query: [1, 2, 3, 4, 5],
threshold: {
$gte: 0.5, // Similarity >= 0.5
$lte: 0.8, // Similarity <= 0.8
$ne: 0.75 // Similarity != 0.75
}
}
}
}
}
```
#### Default Threshold Behavior
When providing a simple number as threshold, the comparison differs by function type:
```typescript
// For cosine similarity, higher values mean more similar
// So threshold: 0.75 means "find vectors with similarity >= 0.75"
{
where: {
embedding: {
$vector: {
fn: "gds.similarity.cosine",
query: [1, 2, 3, 4, 5],
threshold: 0.75 // Interpreted as $gte: 0.75
}
}
}
}
// For euclidean distance, lower values mean more similar
// So threshold: 0.5 means "find vectors with distance <= 0.5"
{
where: {
embedding: {
$vector: {
fn: "gds.similarity.euclidean",
query: [1, 2, 3, 4, 5],
threshold: 0.5 // Interpreted as $lte: 0.5
}
}
}
}
```
## Field Existence Operator
### $exists
The `$exists` operator checks whether a field exists in the record or not. This is useful for filtering records based on the presence or absence of specific fields.
#### Check if field exists
```typescript
{
where: {
phoneNumber: { $exists: true } // Only records that have a phoneNumber field
}
}
```
#### Check if field does not exist
```typescript
{
where: {
phoneNumber: { $exists: false } // Only records that don't have a phoneNumber field
}
}
```
The `$exists` operator works with all field types (string, number, boolean, datetime, null, arrays) and considers a field to:
- **Exist** (`$exists: true`) when the field is not null and not empty
- **Not exist** (`$exists: false`) when the field is null or empty
**Examples:**
```typescript
// Find users who have provided their email address
{
where: {
email: { $exists: true }
}
}
// Find products that don't have a description
{
where: {
description: { $exists: false }
}
}
// Combine with other operators
{
where: {
$and: [
{ email: { $exists: true } },
{ isActive: true }
]
}
}
```
### $type
The `$type` operator checks whether a field has a specific data type. This is useful for filtering records based on the data type of a field, especially when working with heterogeneous data or when you need to ensure type consistency.
```typescript
{
where: {
value: { $type: "string" } // Only records where 'value' field is a string
}
}
```
Available types:
- `"string"`: Text values
- `"number"`: Numeric values
- `"boolean"`: True/false values
- `"datetime"`: Date and time values
- `"null"`: Null values
- `"vector"`: Vector/array values for similarity search
**Examples:**
```typescript
// Find records where age is actually a number (not stored as string)
{
where: {
age: { $type: "number" }
}
}
// Find records where the status field is a boolean
{
where: {
status: { $type: "boolean" }
}
}
// Find records with vector embeddings
{
where: {
embedding: { $type: "vector" }
}
}
// Combine with other operators to find string fields that contain specific text
{
where: {
$and: [
{ description: { $type: "string" } },
{ description: { $contains: "important" } }
]
}
}
```
The `$type` operator is particularly useful when:
- Working with imported data that might have inconsistent types
- Validating data integrity across your records
- Building queries that need to handle fields that might contain different types of values
- Filtering records before applying type-specific operations
## Logical Grouping Operators
When you need to create complex conditions, logical grouping operators allow you to combine multiple conditions.
### $and
The `$and` operator combines multiple conditions and returns records that match all the conditions. This is the default behavior when listing multiple conditions.
```typescript
// Explicit $and
{
where: {
$and: [
{ name: { $startsWith: "J" } },
{ age: { $gte: 21 } }
]
}
}
// Implicit $and (equivalent to above)
{
where: {
name: { $startsWith: "J" },
age: { $gte: 21 }
}
}
```
### $or
The `$or` operator returns records that match at least one of the specified conditions.
```typescript
{
where: {
$or: [
{ name: { $startsWith: "J" } },
{ age: { $gte: 21 } }
]
}
}
```
### $not
The `$not` operator inverts the specified condition, returning records that don't match it.
```typescript
{
where: {
$not: {
status: "deleted"
}
}
}
```
### $nor
The `$nor` operator returns records that don't match any of the specified conditions.
```typescript
{
where: {
$nor: [
{ status: "deleted" },
{ status: "archived" }
]
}
}
```
### $xor
The `$xor` operator (exclusive OR) returns records that match exactly one of the specified conditions.
```typescript
{
where: {
$xor: [
{ isPremium: true },
{ hasFreeTrialAccess: true }
]
}
}
```
### Nested Logical Grouping
Logical operators can be nested to create complex conditions:
```typescript
{
where: {
$or: [
{ status: "active" },
{
$and: [
{ status: "pending" },
{ createdAt: { $gte: "2023-01-01T00:00:00Z" } }
]
}
]
}
}
```
## Relationship Queries
One of the most powerful features of RushDB is its ability to query based on relationships between records.
> **Note:** The underlying mechanism in RushDB works as follows: when the SearchQuery parser encounters a nested object that is neither a flat object nor contains criteria operators (like $gt, $contains, etc.), it interprets this object as a related record reference, using the provided key as the desired label for the related record.
### Basic Relationship Queries
To filter records based on related records, use the label of the related record as a key:
```typescript
{
where: {
name: "Tech Corp", // Property on the current record
DEPARTMENT: { // Related record by label
name: "Engineering", // Property on the related record
headcount: { $gte: 10 } // Another property on the related record
}
}
}
```
This will find records named "Tech Corp" that are connected to at least one DEPARTMENT record named "Engineering" with a headcount of at least 10.
### Relationship Direction
You can specify the direction of the relationship using the `$relation` operator:
```typescript
{
where: {
POST: {
$relation: {
type: "AUTHORED", // Relationship type
direction: "in" // Direction: "in" or "out"
},
title: { $contains: "Graph" } // Property on the related record
}
}
}
```
You can also use a simplified syntax for specifying just the relationship type:
```typescript
{
where: {
POST: {
$relation: "AUTHORED", // Relationship type only
title: { $contains: "Graph" } // Property on the related record
}
}
}
```
### Multi-Level Relationships
You can query through multiple levels of relationships:
```typescript
{
where: {
DEPARTMENT: { // First level relationship
name: "Engineering",
PROJECT: { // Second level relationship
name: "Database",
EMPLOYEE: { // Third level relationship
role: "Developer"
}
}
}
}
}
```
This will find records connected to a DEPARTMENT named "Engineering" that has a PROJECT named "Database" with at least one EMPLOYEE with the role "Developer".
### Aliasing for Aggregations
When you need to reference related records in aggregations, use the `$alias` operator:
```typescript
{
where: {
DEPARTMENT: {
$alias: "$department", // Define alias for department records
PROJECT: {
$alias: "$project", // Define alias for project records
budget: { $gte: 10000 }
}
}
},
aggregate: {
departmentCount: {
fn: "count",
alias: "$department" // Use the alias in aggregation
}
}
}
```
> For more detailed information about aggregations, including available functions and advanced usage, see [Aggregations](./aggregations.md).
### ID Filtering
You can filter by record ID using the special `$id` operator:
```typescript
{
where: {
$id: "123456", // Filter by ID of the current record
DEPARTMENT: {
$id: "789012" // Filter by ID of the related record
}
}
}
```
The `$id` operator supports all the string comparison operators:
```typescript
{
where: {
$id: { $in: ["123456", "789012"] }
}
}
```
## Logical Grouping with Relationships
You can combine logical operators with relationships to create powerful queries.
### $or with Relationships
```typescript
{
where: {
$or: [
{
DEPARTMENT: {
name: "Engineering"
}
},
{
DEPARTMENT: {
name: "Product"
}
}
]
}
}
```
This will find records connected to either a DEPARTMENT named "Engineering" OR a DEPARTMENT named "Product".
### $and with Relationships
```typescript
{
where: {
$and: [
{
DEPARTMENT: {
name: "Engineering"
}
},
{
PROJECT: {
budget: { $gte: 10000 }
}
}
]
}
}
```
This will find records connected to both a DEPARTMENT named "Engineering" AND a PROJECT with a budget of at least 10000.
### Mixed Logical Operations
You can combine different logical operators:
```typescript
{
where: {
name: "Tech Corp", // Implicit $and
$or: [
{
DEPARTMENT: {
name: "Engineering"
}
},
{
DEPARTMENT: {
name: "Product",
$not: {
PROJECT: { // No projects that are "Canceled"
status: "Canceled"
}
}
}
}
]
}
}
```
### Nested Logical Operations within Relationships
You can use logical operators inside relationship queries:
```typescript
{
where: {
DEPARTMENT: {
$or: [
{ name: "Engineering" },
{ name: "Product" }
],
PROJECT: {
$and: [
{ budget: { $gte: 10000 } },
{ status: { $ne: "Canceled" } }
]
}
}
}
}
```
## Complete Examples
Basic filtering with multiple conditions
```typescript
{
where: {
name: { $startsWith: "Tech" },
foundingYear: { $gte: 2010 },
active: true,
industry: { $in: ["Software", "AI", "Cloud"] }
}
}
```
This query finds records whose name starts with "Tech", founded in or after 2010, that are active, and are in the Software, AI, or Cloud industry.
Nested relationships with conditions
```typescript
{
where: {
COMPANY: {
name: "Tech Corp",
DEPARTMENT: {
$relation: {
type: "HAS_DEPARTMENT",
direction: "out"
},
name: "Engineering",
PROJECT: {
budget: { $gte: 100000 },
EMPLOYEE: {
role: "Developer",
skills: { $contains: "TypeScript" }
}
}
}
}
}
}
```
This query traverses a complex relationship structure from COMPANY to DEPARTMENT to PROJECT to EMPLOYEE, applying filters at each level.
Complex logical grouping
```typescript
{
where: {
$or: [
{
$and: [
{ rating: { $gte: 4.5 } },
{ reviews: { $gte: 100 } }
]
},
{
$and: [
{ rating: { $gte: 4.0 } },
{ reviews: { $gte: 1000 } },
{ featured: true }
]
}
],
$not: {
status: "deprecated"
}
}
}
```
This query uses nested logical operators to find records that either have a high rating with moderate review count OR a slightly lower rating with high review count and are featured, but in either case are not deprecated.
Field existence filtering
```typescript
{
where: {
$and: [
{ email: { $exists: true } }, // Must have email
{ phoneNumber: { $exists: false } }, // Must not have phone number
{ isActive: true },
{
$or: [
{ lastLoginDate: { $exists: true } }, // Has logged in before
{ createdAt: { $gte: "2024-01-01T00:00:00Z" } } // Or is a recent signup
]
}
]
}
}
```
This query finds active users who have provided an email address but no phone number, and either have logged in before or are recent signups.
Vector search with relationship filtering
```typescript
{
where: {
DOCUMENT: {
title: { $contains: "Neural Networks" },
CHUNK: {
content: { $contains: "embedding" },
embedding: {
$vector: {
fn: "gds.similarity.cosine",
query: [0.1, 0.2, 0.3, 0.4, 0.5],
threshold: {
$gte: 0.75
}
}
}
}
}
}
}
```
This query finds records related to documents about neural networks, specifically chunks that mention "embedding" and have a high vector similarity to the provided embedding.
Date range with related record filtering
```typescript
{
where: {
created: {
$gte: { $year: 2023, $month: 1, $day: 1 },
$lt: { $year: 2024, $month: 1, $day: 1 }
},
AUTHOR: {
$relation: "CREATED_BY",
reputation: { $gte: 100 },
POST: {
$not: {
status: "deleted"
}
}
}
}
}
```
This query finds records created in 2023 by authors with a reputation of at least 100 who have at least one non-deleted post.
## Additional Notes
- Field names are case-sensitive.
- Missing fields are not included in the result. For example, searching for `{ active: true }` won't match records that don't have an `active` field.
- String comparison (`$contains`, `$startsWith`, `$endsWith`) is case-insensitive by default.
- When working with arrays in records, the conditions are satisfied if any element in the array matches. For example, `{ tags: "typescript" }` will match a record with `tags: ["javascript", "typescript", "react"]`.
- Logical operators can be used both at the root level and at any nested level, including inside relationship queries.
- Relationship queries are executed using `OPTIONAL MATCH` in Cypher, which means records will be included even if the related record doesn't exist unless you specifically filter for it.
---
# Storage
RushDB leverages [Neo4j](https://neo4j.com/docs/get-started/get-started-with-neo4j/) (version 5.25.1 or higher) as its underlying storage engine, enhanced with the [APOC](https://neo4j.com/labs/apoc/) (Awesome Procedures On Cypher) and [GDS](https://neo4j.com/docs/graph-data-science/current/) (Graph Data Science) plugins to perform efficient vector similarity searches and advanced graph operations.
## Graph Database vs. Traditional Databases
Unlike traditional database models, Neo4j's graph approach offers distinct advantages for connected data:
| Database Type | Core Concept | RushDB Analogy |
|---------------|--------------|----------------|
| **Relational DB** | Tables with rows and columns | A table would be a label, a row would be a record, but relationships would require complex JOIN operations |
| **Document DB** | Collections of JSON documents | Each JSON document would be a record, but connecting documents requires explicit reference fields |
| **Graph DB (Neo4j)** | Nodes and relationships | Records are nodes with properties, and relationships are first-class citizens that connect related data |
In Neo4j, relationships are physical connections in the database, not just foreign key references, enabling:
- Traversing connections without costly JOIN operations
- Discovering patterns across different data types
- Modeling complex, interconnected data naturally
## Neo4j Foundation
Neo4j provides RushDB with a robust graph database foundation, allowing for:
- High-performance graph traversals
- ACID-compliant transactions
- Property graph model flexibility
- Scalable data storage and retrieval
The integration with [APOC](https://neo4j.com/labs/apoc/4.4/overview/) and [GDS](https://neo4j.com/docs/graph-data-science/current/introduction/) plugins extends Neo4j's native capabilities with vector-based operations critical for machine learning workflows and similarity search functions.
## Data Overhead
Each record in RushDB (a meaningful key-value data piece) is extended with several internal properties that enable advanced functionality:
| Internal Key | Client Representation | Description |
|--------------|----------------------|-------------|
| `__RUSHDB__KEY__ID__` | `__id` | UUIDv7 that enables lexicographic ordering without relying on user-defined fields like `createdAt`. RushDB SDKs support converting `__id` to timestamp and ISO8601 date. For more details, see [UUIDv7 specification](https://www.ietf.org/archive/id/draft-peabody-uuid-v7-01.html). |
| `__RUSHDB__KEY__PROPERTIES__META__` | `__proptypes` | Stringified meta-object holding the types of data in the current record, e.g., `{ name: "string", active: "boolean", ... }` |
| `__RUSHDB__KEY__LABEL__` | `__label` | Record Label identifier. Every record has two labels: a default one (`__RUSHDB__LABEL__RECORD__`) and a user-defined one that is searchable. Currently, RushDB allows only one custom label per record, and it is required by default. For more details about labels, see [Labels](../concepts/labels). |
| `__RUSHDB__KEY__PROJECT__ID__` | `__projectId` | Project identifier for multitenancy isolation. This property is never exposed to clients via UI or API. |
## Data Structure
RushDB organizes data in a graph structure that represents both records and properties as first-class entities:
```mermaid
graph TD
subgraph Records
A[":__RUSHDB__LABEL__RECORD__ :Car"]
B[":__RUSHDB__LABEL__RECORD__ :Engine"]
F[":__RUSHDB__LABEL__RECORD__ :Motorcycle"]
G[":__RUSHDB__LABEL__RECORD__ :Engine"]
end
subgraph Properties
C[":__RUSHDB__LABEL__PROPERTY__
name: color"]
D[":__RUSHDB__LABEL__PROPERTY__
name: year"]
E[":__RUSHDB__LABEL__PROPERTY__
name: power"]
end
A -->|"__RUSHDB__RELATION__DEFAULT__"| B
F -->|"__RUSHDB__RELATION__DEFAULT__"| G
C -->|"__RUSHDB__RELATION__VALUE__"| A
C -->|"__RUSHDB__RELATION__VALUE__"| F
D -->|"__RUSHDB__RELATION__VALUE__"| A
E -->|"__RUSHDB__RELATION__VALUE__"| B
E -->|"__RUSHDB__RELATION__VALUE__"| G
```
In this structure:
| Graph Element | Internal Label | Description |
|--------------|----------------|-------------|
| Records | `__RUSHDB__LABEL__RECORD__` | Nodes that represent user-defined entities (Car, Engine, Motorcycle) |
| Properties | `__RUSHDB__LABEL__PROPERTY__` | Nodes that represent data attributes with a "name" field storing the property name |
| Value Relations | `__RUSHDB__RELATION__VALUE__` | Edges connecting properties to their records (the property → record direction) |
| Default Relations | `__RUSHDB__RELATION__DEFAULT__` | Edges connecting related records (like Car to Engine) |
This structure allows efficient traversals across related records while maintaining property-based connections that can reveal relationships between otherwise unrelated entities.
## Property Graph Model
RushDB implements a unique property graph model where properties are first-class citizens:
```mermaid
graph LR
subgraph Properties
B[":__RUSHDB__LABEL__PROPERTY__
name: color
type: string"]
end
subgraph Records
A[":__RUSHDB__LABEL__RECORD__ :Car"]
C[":__RUSHDB__LABEL__RECORD__ :Flower"]
end
B -->|__RUSHDB__RELATION__VALUE__| A
B -->|__RUSHDB__RELATION__VALUE__| C
```
Properties interconnect records through a unique set of field name and type. For example, both a `Car` record and a `Flower` record can share a common property `color:string`. This approach:
1. Enables performant queries across different record types
2. Facilitates the discovery of hidden insights in data
3. Creates a natural graph structure that leverages Neo4j's native traversal capabilities
Properties are not shared amongst projects (database instances), ensuring complete isolation in multi-tenant environments.
## Data Types
RushDB supports a wide range of data types to accommodate diverse data needs and provide a flexible environment for your applications. Below is a comprehensive find of the supported data types along with their descriptions:
### `string`
This data type is used for any textual information and can hold text of unlimited length.
### `number`
This data type accommodates both floating-point numbers and integers. For instance, it can handle values like
`-120.209817` (a float) or `42` (an integer).
### `datetime`
This data type adheres to the ISO 8601 format, including timezones. For example: `2012-12-21T18:29:37Z`.
### `boolean`
This data type can only have two possible values: `true` or `false`.
### `null`
This data type has only one possible value: `null`.
### `vector`
This data type accommodates arrays of both floating-point numbers and integers. It handles values like
`[0.99070,0.78912, 1, 0]`. This is particularly useful for vector similarity searches and machine learning operations.
---
### Arrays
In essence, RushDB supports all the data types that JSON does. However, when it comes to arrays (or Lists), RushDB can indeed
hold them as **Property** values, but it's important to note that it can only store consistent values within those
arrays. To learn more, check out the [Properties](../concepts/properties) section.
> **Note:** Every data type mentioned above (except `vector`, since it's already an array by default) supports an array representation.
Here some valid examples:
- `["apple", "banana", "carrot"]` - good
- `[null, null, null, null, null]` - weird, but works fine 🤔
- `[4, 8, 15, 16, 23, 42]` - works as well
- `["2023-09-17T02:47:54+04:00", "1990-08-18T04:35:00+05:00"]` - also good
- `[true, false, true, false, true]` - love is an answer (🌼)
### Type Handling
When records are imported into RushDB, data types are automatically inferred and stored in the `__RUSHDB__KEY__PROPERTIES__META__` internal field (exposed to clients as `__proptypes`). This metadata is crucial for maintaining type consistency and enabling efficient property-based connections across the graph.
To learn more about how RushDB uses data types for property values and type inference during data import, see [REST API - Import Data](../rest-api/records/import-data).
## Data Import Mechanism
RushDB applies a breadth-first search (BFS) algorithm to parse JSON tree structures, enhancing each flat level with:
1. A unique ID (`__id`), automatically generated or provided by the user for top-level records
2. Labels (`__label`), either explicitly provided by the user for top-level records or derived from parent keys for nested objects
3. Type inference, automatically suggesting data types for all properties
4. Relationship establishment, connecting nested records with `__RUSHDB__RELATION__DEFAULT__` relationships
This approach allows for intuitive transformation of hierarchical JSON data into graph structures without requiring users to understand the underlying graph model.
Example of JSON to graph transformation:
```json
{
"car": {
"make": "Tesla",
"model": "Model 3",
"engine": {
"power": 283,
"type": "electric"
}
}
}
```
Which transforms into the following graph structure:
```mermaid
graph TD
subgraph Records
A[":__RUSHDB__LABEL__RECORD__ :car
__id: 01968aa4-22c1-781a-8e8c-8fe6be6c3fd4
__label: car
make: Tesla
model: Model 3
__proptypes: {make: string, model: string}"]
B[":__RUSHDB__LABEL__RECORD__ :engine
__id: 01968aa4-74af-73e4-984d-3888d63ec72e
__label: engine
power: 283
type: electric
__proptypes: {power: number, type: string}"]
end
subgraph Properties
C[":__RUSHDB__LABEL__PROPERTY__
name: make
type: string"]
D[":__RUSHDB__LABEL__PROPERTY__
name: model
type: string"]
E[":__RUSHDB__LABEL__PROPERTY__
name: power
type: number"]
F[":__RUSHDB__LABEL__PROPERTY__
name: type
type: string"]
end
A -->|"__RUSHDB__RELATION__DEFAULT__"| B
C -->|"__RUSHDB__RELATION__VALUE__"| A
D -->|"__RUSHDB__RELATION__VALUE__"| A
E -->|"__RUSHDB__RELATION__VALUE__"| B
F -->|"__RUSHDB__RELATION__VALUE__"| B
```
In this representation:
- Records are nodes with the label `__RUSHDB__LABEL__RECORD__` plus a user-defined label (car, engine)
- Records store the actual values of properties directly as node attributes
- Records also store `__proptypes` metadata about property types
- Properties are nodes with the single label `__RUSHDB__LABEL__PROPERTY__` and contain only the name and type fields (not values)
- Nested objects become connected records with `__RUSHDB__RELATION__DEFAULT__` relationships
- Properties are connected to their records via `__RUSHDB__RELATION__VALUE__` relationships (the property → record direction)
The JSON representation of these records as stored in the database would look like:
```json
[
{
"__RUSHDB__KEY__ID__": "01968aa4-22c1-781a-8e8c-8fe6be6c3fd4",
"__RUSHDB__KEY__LABEL__": "car",
"__RUSHDB__KEY__PROJECT__ID__": "01968aa4-4225-7833-ba60-2f5e4383bf1b",
"__RUSHDB__KEY__PROPERTIES__META__": "{\"make\":\"string\",\"model\":\"string\"}",
"make": "Tesla",
"model": "Model 3"
},
{
"__RUSHDB__KEY__ID__": "01968aa4-74af-73e4-984d-3888d63ec72e",
"__RUSHDB__KEY__LABEL__": "engine",
"__RUSHDB__KEY__PROJECT__ID__": "01968aa4-4225-7833-ba60-2f5e4383bf1b",
"__RUSHDB__KEY__PROPERTIES__META__": "{\"power\":\"number\",\"type\":\"string\"}",
"power": 283,
"type": "electric"
}
]
```
When these records are returned through RushDB's API or SDKs, the internal keys are transformed to their client-friendly aliases:
```json
[
{
"__id": "01968aa4-22c1-781a-8e8c-8fe6be6c3fd4",
"__label": "car",
"__proptypes": {"make":"string","model":"string"},
"make": "Tesla",
"model": "Model 3"
},
{
"__id": "01968aa4-74af-73e4-984d-3888d63ec72e",
"__label": "engine",
"__proptypes": {"power":"number","type":"string"},
"power": 283,
"type": "electric"
}
]
```
Note that the `__projectId` field is never exposed to clients via the API or SDKs as noted in the Data Overhead section.
Each of these records is also connected to Property nodes which define the metadata for their fields, but those Property nodes don't store the actual values.
## Database Indexes and Constraints
When RushDB initializes a new database connection, it automatically creates several indexes and constraints to ensure data integrity and optimize query performance:
### Core Constraints
The following uniqueness constraints are created to enforce data consistency:
| Constraint Name | Node Label | Property | Description |
|-----------------|------------|----------|-------------|
| `constraint_user_login` | `__RUSHDB__LABEL__USER__` | `login` | Ensures each user has a unique login |
| `constraint_user_id` | `__RUSHDB__LABEL__USER__` | `id` | Ensures each user has a unique ID |
| `constraint_token_id` | `__RUSHDB__LABEL__TOKEN__` | `id` | Ensures each token has a unique ID |
| `constraint_project_id` | `__RUSHDB__LABEL__PROJECT__` | `id` | Ensures each project has a unique ID |
| `constraint_workspace_id` | `__RUSHDB__LABEL__WORKSPACE__` | `id` | Ensures each workspace has a unique ID |
| `constraint_record_id` | `__RUSHDB__LABEL__RECORD__` | `__RUSHDB__KEY__ID__` | Ensures each record has a unique ID |
| `constraint_property_id` | `__RUSHDB__LABEL__PROPERTY__` | `id` | Ensures each property has a unique ID |
### Performance Indexes
The following indexes are created to optimize query performance:
| Index Name | Node Label | Properties | Description |
|------------|------------|------------|-------------|
| `index_record_id` | `__RUSHDB__LABEL__RECORD__` | `__RUSHDB__KEY__ID__` | Speeds up record lookups by ID |
| `index_record_projectid` | `__RUSHDB__LABEL__RECORD__` | `__RUSHDB__KEY__PROJECT__ID__` | Enables fast filtering of records by project |
| `index_property_name` | `__RUSHDB__LABEL__PROPERTY__` | `name` | Enables fast property lookups by name |
| `index_property_mergerer` | `__RUSHDB__LABEL__PROPERTY__` | `name`, `type`, `projectId`, `metadata` | Optimizes property node merging operations during data imports |
These indexes and constraints are essential for RushDB's performance and data integrity, particularly when dealing with large datasets and complex queries across the property graph model. They ensure that:
1. Record IDs are always unique within the database
2. Project isolation is maintained in multi-tenant environments
3. Property lookups are efficient, especially during joins and traversals
4. User management operations perform optimally
Learn more at [REST API - Import Data](../rest-api/records/import-data) or through the language-specific SDKs:
- [TypeScript SDK](../typescript-sdk/records/import-data)
- [Python SDK](../python-sdk/records/import-data)
## Performance Considerations
This approach is carefully designed to:
- Enable efficient indexing and querying
- Support advanced graph traversals and pattern matching
- Facilitate vector similarity searches with minimal computational cost
By structuring data this way, RushDB achieves a balance between storage overhead and query performance, optimizing for use cases that require both traditional database operations and advanced graph analytics capabilities.
---
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
# Transactions
In RushDB, Transactions provide a mechanism to group multiple database operations into a single atomic unit of work. They ensure data consistency by guaranteeing that either all operations within the transaction succeed, or none of them do.
## How It Works
Transactions in RushDB are built on Neo4j's native transaction capabilities, providing ACID guarantees:
- **Atomicity**: All operations within a transaction either succeed completely or fail completely, with no partial changes.
- **Consistency**: Transactions transform the database from one valid state to another, maintaining data integrity.
- **Isolation**: Concurrent transactions do not interfere with each other, ensuring data consistency.
- **Durability**: Once a transaction is committed, changes are permanent even in case of system failure.
For example:
```typescript
// Start a transaction
const tx = await db.tx.begin({ ttl: 10000 });
try {
// Create a new user record within the transaction
const user = await db.records.create(
{
label: 'User',
data: {
name: 'Alice Smith',
email: 'alice@example.com',
emailConfirmed: false
}
},
tx
);
// Create a related profile record within the same transaction
const profile = await db.record.create(
{
label: "Profile",
data: {
bio: "Software engineer",
joinDate: new Date().toISOString()
}
},
tx
);
// Create a relationship between the records within the same transaction
await db.records.attach(
{
source: user,
target: profile,
options: {
type: 'HAS_PROFILE'
}
},
tx
);
// Commit the transaction to make all changes permanent
await tx.commit();
// or await db.tx.commit(tx);
} catch (error) {
// If any operation fails, roll back the entire transaction
await tx.rollback();
// or await db.tx.rollback(tx);
throw error;
}
```
```python
# Start a transaction
tx = db.tx.begin(ttl=10000)
try:
# Create a new user record within the transaction
user = db.records.create(
label="User",
data={
"name": "Alice Smith",
"email": "alice@example.com",
"emailConfirmed": False
},
transaction=tx
)
# Create a related profile record within the same transaction
profile = db.records.create(
label="Profile",
data={
"bio": "Software engineer",
"joinDate": datetime.now().isoformat()
},
transaction=tx
)
# Create a relationship between the records within the same transaction
db.records.attach(
source=user,
target=profile,
options={
"type": "HAS_PROFILE"
},
transaction=tx
)
# Commit the transaction to make all changes permanent
tx.commit()
# or db.tx.commit(tx)
except Exception as error:
# If any operation fails, roll back the entire transaction
tx.rollback()
# or db.tx.rollback(tx)
raise error
```
## Transaction Lifecycle
Each transaction in RushDB follows a clear lifecycle:
1. **Creation**: A transaction is initiated with an optional Time-To-Live (TTL) parameter
2. **Operation Phase**: Multiple database operations are performed using the transaction ID
3. **Termination**: The transaction is explicitly committed to make changes permanent or rolled back to discard all changes
4. **Automatic Cleanup**: If neither committed nor rolled back within the TTL, the transaction is automatically rolled back
## Internal Structure
Built on Neo4j's transaction management system, RushDB transactions maintain several internal states:
| State | Description |
|-------|-------------|
| Active | Transaction is open and can accept operations |
| Committed | Transaction has been successfully completed |
| Rolled Back | Transaction has been explicitly or automatically reverted |
| Timed Out | Transaction exceeded its TTL and was automatically rolled back |
Internally, RushDB maintains a transaction registry that:
1. Tracks all active transactions
2. Monitors their TTL
3. Maps transaction IDs to internal Neo4j transaction objects
4. Manages transaction cleanup and resource release
For more information about the underlying storage system, see [Storage](../concepts/storage).
## Use Cases
Transactions are particularly valuable in several scenarios:
### Complex Data Operations
When creating interconnected data structures, transactions ensure that all components are created successfully or not at all:
```mermaid
graph TD
A[Create User] -->|Success| B[Create Profile]
B -->|Success| C[Create Address]
C -->|Success| D[Create Relationships]
D -->|Success| E[Commit Transaction]
A -->|Failure| F[Rollback]
B -->|Failure| F
C -->|Failure| F
D -->|Failure| F
```
This approach ensures that complex data structures are properly maintained, with [Records](../concepts/records) and their [Relationships](../concepts/relationships) remaining consistent.
### Concurrent Operations
When multiple users or services access the same data simultaneously, transactions maintain data consistency:
```typescript
// Service 1
const tx1 = await db.tx.begin();
try {
const record = await db.records.findById(recordId, tx1);
await db.records.update(
{
target: record,
label: record.label(),
data: { status: "processing" }
},
tx1
);
await tx1.commit();
} catch (error) {
await tx1.rollback();
}
// Service 2 (concurrent)
const tx2 = await db.tx.begin();
try {
const record = await db.records.findById(recordId, tx2);
// Will see the original record state until tx1 is committed
await tx2.commit()
} catch (error) {
await tx2.rollback()
}
```
```python
# Service 1
tx1 = db.tx.begin()
try:
record = db.records.find_by_id(record_id, transaction=tx1)
db.records.update(
target=record,
label=record.label(),
data={"status": "processing"},
transaction=tx1
)
tx1.commit()
except Exception as error:
tx1.rollback()
# Service 2 (concurrent)
tx2 = db.tx.begin()
try:
record = db.records.find_by_id(record_id, transaction=tx2)
# Will see the original record state until tx1 is committed
tx2.commit()
except Exception as error:
tx2.rollback()
```
Transactions help prevent race conditions when multiple operations might affect the same [Records](../concepts/records) or [Properties](../concepts/properties).
### Data Migrations
When upgrading data structures or transforming records, transactions ensure that data integrity is maintained:
```typescript
const tx = await db.tx.begin({ ttl: 30000 }); // Longer TTL for migrations
try {
const users = await db.records.find({ labels: ["User"] }, tx);
for (const user of users) {
// Create new format record
await db.records.create(
{
label: "Person",
data: {
fullName: `${user.firstName} ${user.lastName}`,
email: user.email,
migratedFrom: user.__id
}
},
tx
);
}
await tx.commit();
} catch (error) {
await tx.rollback();
console.error("Migration failed:", error);
}
```
```python
tx = db.tx.begin(ttl=30000) # Longer TTL for migrations
try:
users = db.records.find({"labels": ["User"]}, transaction=tx)
for user in users:
# Create new format record
db.records.create(
label="Person",
data={
"fullName": f"{user.get('firstName')} {user.get('lastName')}",
"email": user.get('email'),
"migratedFrom": user.get('__id')
},
transaction=tx
)
tx.commit()
except Exception as error:
tx.rollback()
print(f"Migration failed: {error}")
```
During migrations, transactions ensure that [Labels](../concepts/labels) and [Properties](../concepts/properties) are consistently updated across related records.
## Time-To-Live (TTL)
RushDB transactions include a configurable TTL mechanism to prevent hanging transactions:
- **Default**: 5000ms (5 seconds)
- **Maximum**: 30000ms (30 seconds)
- **Purpose**: Automatically rolls back transactions that aren't explicitly committed or rolled back within the specified time
- **Recommendation**: Set TTL based on expected operation duration plus a reasonable buffer
## Transaction Limitations
While transactions provide powerful data consistency guarantees, they come with certain limitations:
1. **Resource Consumption**: Active transactions consume database resources, particularly memory
2. **Performance Impact**: Very long-running transactions can impact overall database performance
3. **TTL Constraints**: Maximum TTL is capped at 30 seconds to prevent resource exhaustion
4. **Isolation Level**: RushDB uses Neo4j's default read-committed isolation level
See [Storage](../concepts/storage) for more details on how database resources are managed.
## Best Practices
To effectively use transactions in RushDB:
1. **Keep transactions short**: Minimize the number and duration of operations within a transaction
2. **Set appropriate TTL**: Choose a TTL that provides enough time for operations to complete without being unnecessarily long
3. **Explicit termination**: Always explicitly commit or rollback transactions rather than relying on automatic TTL-based rollback
4. **Error handling**: Implement proper error handling with rollback in catch blocks
5. **Avoid nested transactions**: Instead of nesting transactions, design workflows to use a single transaction level
6. **Batch operations**: For bulk operations, consider batching changes into multiple smaller transactions
When working with complex data models, refer to [Records](../concepts/records) and [Relationships](../concepts/relationships) documentation to understand how transactions affect your data structures.
## Integration with Neo4j
RushDB's transaction system leverages Neo4j's native transaction capabilities while adding:
1. Client-friendly transaction IDs
2. Configurable TTL with automatic cleanup
3. Cross-platform SDK integration
4. HTTP API support via transaction headers
This provides the robustness of Neo4j's proven transaction system with the ease of use of RushDB's modern API design.
For detailed information on using transactions, see [REST API - Transactions](../rest-api/transactions) or through the language-specific SDKs:
- [TypeScript SDK](../typescript-sdk/transactions)
- [Python SDK](../python-sdk/transactions)
---
# Get API Key
To use RushDB, you'll need an API token for authentication. Here's how to get one:
## 1. Create an Account
Visit [RushDB Dashboard](https://dashboard.rushdb.com) and sign up for an account if you haven't already.
## 2. Create a Project
1. After signing in, click on "New Project"
2. Enter a name for your project
3. Choose your preferred region
4. Click "Create"
## 3. Generate API key
1. In your project dashboard, navigate to the "API Keys" section
2. Click "Generate New API Key"
3. Give your token a name (e.g., "Development", "Production")
4. Click "Generate Key"
## 4. Copy and Store Your API Key
Your API token will be displayed only once. Make sure to:
1. Copy the token immediately
2. Store it securely
3. Never commit it to version control
4. Use environment variables or secure configuration management
Example of using environment variables:
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
```typescript
// Load from environment variable
const db = new RushDB(process.env.RUSHDB_API_KEY);
```
```python
import os
db = RushDB(api_key=os.environ['RUSHDB_API_KEY'])
```
```bash
# Set in your shell
export RUSHDB_API_KEY='RUSHDB_API_KEY'
# Use in requests
curl -H "Authorization: Bearer $RUSHDB_API_TOKEN" \
https://api.rushdb.com/api/v1/records
```
## Token Security
- Keep your tokens secure and private
- Rotate tokens periodically
- Use different tokens for development and production
- Revoke tokens immediately if compromised
## Next Steps
- Follow the [Quick Tutorial](../get-started/quick-tutorial) to start using your token
- Learn about [RushDB](../concepts/records)
- Check out the [Basic Concepts](../concepts/records)
---
# Quick Tutorial
This tutorial will help you get started with RushDB by walking through a simple example of creating and querying a small social network using the RushDB SDK.
## Prerequisites
- Create a RushDB account and get an API token (see [Get API Key](../get-started/get-api-key))
- Your preferred programming language: Python, TypeScript/JavaScript, or any HTTP client for REST API
## Step 1: Initialize RushDB
Choose your preferred SDK:
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
```typescript
import RushDB from '@rushdb/javascript-sdk';
// Initialize with your API token
const db = new RushDB('RUSHDB_API_KEY');
// Or with additional configuration options
// const db = new RushDB('RUSHDB_API_KEY', {
// url: 'https://api.rushdb.com/api/v1',
// timeout: 5000
// });
```
```python
from rushdb import RushDB
# Connect with your API token
db = RushDB("RUSHDB_API_KEY")
```
```bash
# Set your API token for future requests
export TOKEN="RUSHDB_API_KEY"
```
## Step 2: Create [Records](../concepts/records.md) with Labels
Let's create two users in our social network:
```typescript
// Create users with the Person [label](../concepts/labels.md)
const alice = await db.records.create({
label: "PERSON",
data: {
name: 'Alice',
age: 28,
interests: ['coding', 'hiking']
}
});
const bob = await db.records.create({
label: "PERSON",
data: {
name: 'Bob',
age: 32,
interests: ['photography', 'travel']
}
});
```
```python
# Create users with the Person [label](../concepts/labels.md)
alice = db.records.create(
label="PERSON",
data={
"name": "Alice",
"age": 28,
"interests": ["coding", "hiking"]
}
)
bob = db.records.create(
label="PERSON",
data={
"name": "Bob",
"age": 32,
"interests": ["photography", "travel"]
}
)
```
```bash
# Create Alice with PERSON label
curl -X POST "https://api.rushdb.com/api/v1/records" \
-H "Authorization: Bearer $RUSHDB_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"label": "PERSON",
"data": {
"name": "Alice",
"age": 28,
"interests": ["coding", "hiking"]
}
}'
# Save the ID from the response for Alice
export ALICE_ID="response_id_here"
# Create Bob with PERSON label
curl -X POST "https://api.rushdb.com/api/v1/records" \
-H "Authorization: Bearer $RUSHDB_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"label": "PERSON",
"data": {
"name": "Bob",
"age": 32,
"interests": ["photography", "travel"]
}
}'
# Save the ID from the response for Bob
export BOB_ID="response_id_here"
```
## Step 3: Create [Relationships](../concepts/relationships.md)
Let's make Alice and Bob friends:
```typescript
// Create a FRIENDS_WITH relationship between Alice and Bob
await alice.attach({
target: bob,
options: {
type: "FRIENDS_WITH"
}
});
```
```python
# Create a FRIENDS_WITH relationship between Alice and Bob
alice.attach(
target=bob,
options={
"type": "FRIENDS_WITH"
}
)
```
```bash
# Create a FRIENDS_WITH relationship between Alice and Bob
curl -X POST "https://api.rushdb.com/api/v1/relationships/$ALICE_ID" \
-H "Authorization: Bearer $RUSHDB_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"targetIds": ["'$BOB_ID'"],
"type": "FRIENDS_WITH"
}'
```
## Step 4: Query Records with [Search](../concepts/search/introduction.md)
Let's find all people who are interested in outdoor activities:
```typescript
// Find all people who are interested in outdoor activities using [where](../concepts/search/where.md) conditions
const outdoorsy = await db.records.find({
labels: ['PERSON'],
where: {
interests: { $in: ['hiking', 'travel'] }
}
});
console.log('Found:', outdoorsy.map(person => person.data.name));
```
```python
# Find all people who are interested in outdoor activities using [where](../concepts/search/where.md) conditions
outdoorsy = db.records.find({
"where": {
"interests": {"$in": ["hiking", "travel"]}
},
"labels": ["PERSON"]
})
print('Found:', [person.data["name"] for person in outdoorsy])
```
```bash
# Find all people who are interested in outdoor activities
curl -X POST "https://api.rushdb.com/api/v1/records/search" \
-H "Authorization: Bearer $RUSHDB_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"labels": ["PERSON"],
"where": {
"interests": {
"$in": ["hiking", "travel"]
}
}
}'
```
## Step 5: Using [Transactions](../concepts/transactions.mdx) (Optional)
Transactions ensure data consistency by making a series of operations atomic:
```typescript
// Begin a transaction
const transaction = await db.transactions.begin();
try {
// Create a post
const post = await db.records.create({
label: "POST",
data: {
title: "My Hiking Adventure",
content: "Today I went hiking in the mountains...",
createdAt: new Date().toISOString()
},
transaction
});
// Create a relationship between Alice and the post
await alice.attach(
post,
{
type: 'CREATED'
},
transaction
)
// Commit the transaction
await transaction.commit();
console.log("Post created and linked to Alice successfully!");
} catch (error) {
// Roll back the transaction if anything fails
await transaction.rollback();
console.error("Error occurred, transaction rolled back:", error);
}
```
```python
# Using a transaction with context manager
with db.transactions.begin() as transaction:
# Create a post
post = db.records.create(
label="POST",
data={
"title": "My Hiking Adventure",
"content": "Today I went hiking in the mountains...",
"createdAt": "2024-05-17T10:30:00.000Z"
},
transaction=transaction
)
# Create a relationship between Alice and the post
alice.attach(
target=post,
options={
"type": "CREATED"
},
transaction=transaction
)
# The transaction will automatically commit if no errors occur
# or roll back if an exception is raised
```
```bash
# Start a transaction
TRANSACTION_ID=$(curl -X POST "https://api.rushdb.com/api/v1/transactions" \
-H "Authorization: Bearer $RUSHDB_API_KEY" \
-H "Content-Type: application/json" \
| jq -r '.id')
# Create a post with the transaction ID
POST_ID=$(curl -X POST "https://api.rushdb.com/api/v1/records" \
-H "Authorization: Bearer $RUSHDB_API_KEY" \
-H "Content-Type: application/json" \
-H "X-Transaction-ID: $TRANSACTION_ID" \
-d '{
"label": "POST",
"data": {
"title": "My Hiking Adventure",
"content": "Today I went hiking in the mountains...",
"createdAt": "'$(date -u +"%Y-%m-%dT%H:%M:%S.000Z")'"
}
}' \
| jq -r '.id')
# Create relationship within the transaction
curl -X POST "https://api.rushdb.com/api/v1/relationships/$ALICE_ID" \
-H "Authorization: Bearer $RUSHDB_API_KEY" \
-H "Content-Type: application/json" \
-H "X-Transaction-ID: $TRANSACTION_ID" \
-d '{
"targetIds": ["'$POST_ID'"],
"type": "CREATED"
}'
# Commit the transaction
curl -X POST "https://api.rushdb.com/api/v1/transactions/$TRANSACTION_ID/commit" \
-H "Authorization: Bearer $RUSHDB_API_KEY"
```
## Next Steps
- Learn about basic concepts:
- [Records](../concepts/records.md)
- [Labels](../concepts/labels.md)
- [Relationships](../concepts/relationships.md)
- [Properties](../concepts/properties.md)
- [Search & Querying](../concepts/search/introduction.md)
- [Transactions](../concepts/transactions.mdx)
- Explore the SDK documentation in more detail:
- [TypeScript/JavaScript SDK](../typescript-sdk/introduction)
- [Python SDK](../python-sdk/introduction)
- [REST API](../rest-api/introduction)
- Try working with [transactions](../typescript-sdk/transactions) for ensuring data consistency
- Check out tutorials on specific use cases in the [Tutorials](../tutorials/reusable-search-query) section
---
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
# Welcome to RushDB

[Homepage](https://rushdb.com) — [Blog](https://rushdb.com/blog) — [Dashboard](https://app.rushdb.com)
## Instant Graph Database for AI & Modern Apps
**RushDB** is an open-source, graph-powered zero-config database designed to radically simplify data operations. Push any JSON or CSV data, and RushDB intelligently maps relationships, types, and labels without requiring you to understand the underlying graph model.
### Why RushDB?
- **Zero Configuration**: Start developing in minutes without complex database setup
- **Graph-Powered**: Built on Neo4j's robust foundation with advanced graph capabilities
- **Developer Experience First**: Intuitive APIs designed to keep you focused on building, not fighting your database
- **AI & Vector Ready**: Native support for embeddings, vector search, and knowledge graphs
- **Flexible Deployment**: Connect to your Neo4j instance (Aura or self-hosted) or use RushDB Cloud
## Core Capabilities
- **Intuitive Data Handling**: Push any JSON structure and RushDB intelligently organizes your data
- **Powerful Search**: Filter with precision using an expressive query system without learning a query language
- **Graph Traversal**: Navigate through connected data effortlessly to unlock hidden relationships
- **ACID Transactions**: Ensure data integrity with fully-compliant transaction support
- **Vector Similarity**: Build AI-powered applications with native vector search capabilities
## Get Started Quickly
RushDB offers multiple ways to interact with your data:
- [TypeScript/JavaScript SDK](../typescript-sdk/introduction): Ideal for web and Node.js applications
- [Python SDK](../python-sdk/introduction): Perfect for data science and backend systems
- [REST API](../rest-api/introduction): Language-agnostic access for any platform
For a deeper understanding of how RushDB works, explore our [Core Concepts](../concepts/storage) or dive into our [Getting Started Guide](/get-started/quick-tutorial).
## Connect Your Way
RushDB gives you options:
- **RushDB Cloud**: 2 projects free forever with no maintenance required
- **Self-Hosted**: Connect to your own Neo4j instance in minutes
## 🚀 Need Interactive Help?
Get instant guidance through our **[RushDB Docs Chat](https://chatgpt.com/g/g-67d3a9c3088081918201be103b22b83f-rushdb-docs-chat)** - a custom GPT that provides code examples, best practices, and real-time support based on our official documentation.
---
## Quick Example
```typescript
import RushDB from '@rushdb/javascript-sdk';
// Connect to RushDB
const db = new RushDB("RUSHDB_API_KEY");
// Push data with any structure you need
await db.records.createMany({
label: "PRODUCT",
data: {
title: "Ergonomic Chair",
price: 299.99,
inStock: true,
features: ["adjustable height", "lumbar support", "neck rest"],
manufacturer: {
name: "ErgoDesigns",
location: "Zurich"
}
}
});
// Query with precision - no query language to learn
const results = await db.records.find({
labels: ["PRODUCT"],
where: {
price: { $lt: 500 },
features: { $in: ["lumbar support"] },
manufacturer: {
location: "Zurich"
}
}
});
```
```python
from rushdb import RushDB
# Connect to RushDB
db = RushDB("RUSHDB_API_KEY")
# Push data with any structure you need
db.records.create_many(
label="PRODUCT",
data={
"title": "Ergonomic Chair",
"price": 299.99,
"inStock": True,
"features": ["adjustable height", "lumbar support", "neck rest"],
"manufacturer": {
"name": "ErgoDesigns",
"location": "Zurich"
}
}
)
# Query with precision - no query language to learn
results = db.records.find({
"where": {
"price": {"$lt": 500},
"features": {"$in": ["lumbar support"]},
"manufacturer": {
"location": "Zurich"
}
}
})
```
Explore [Tutorials](/tutorials/reusable-search-query) to see more examples and use cases.
---
# RushDB Python SDK
The RushDB Python SDK provides a powerful, intuitive interface for interacting with RushDB from Python applications. Whether you're building data science pipelines, web applications, or AI-driven services, this SDK offers a clean, Pythonic way to work with your graph data.
## Features
- **Intuitive API Design**: Simple methods that map directly to common database operations
- **Type Hinting Support**: Comprehensive type annotations for better IDE support
- **Transaction Management**: ACID-compliant transactions with context manager support
- **Flexible Query System**: Expressive query capabilities without learning a graph query language
- **Vector Support**: Built-in handling for vector embeddings and similarity search
- **Data Import Tools**: Easy import of structured data from JSON, CSV, and other formats
## Installation
Install the RushDB Python SDK using pip:
```bash
pip install rushdb
```
## Quick Start
### Initialize Client
```python
from rushdb import RushDB
# Connect to RushDB with your API token
db = RushDB("RUSHDB_API_KEY")
```
### Basic Operations
```python
# Create a record
user = db.records.create(
label="USER",
data={
"name": "John Doe",
"email": "john@example.com",
"age": 30
},
options={"suggestTypes": True}
)
# Find records
result = db.records.find({
"where": {
"age": {"$gte": 18},
"name": {"$startsWith": "J"}
},
"limit": 10
})
# Iterate over results
for user in result:
print(f"Found user: {user.get('name')}")
# Check result metadata
print(f"Found {len(result)} users out of {result.total} total")
# Update a record
user.update({
"last_login": "2025-05-04T12:30:45Z"
})
# Create relationships
company = db.records.create(
label="COMPANY",
data={"name": "Acme Inc."}
)
# Attach records with a relationship
user.attach(
target=company,
options={"type": "WORKS_AT", "direction": "out"}
)
```
## Using Transactions
Ensure data consistency with transactions:
```python
# Begin a transaction
with db.transactions.begin() as transaction:
# Create a user
user = db.records.create(
label="USER",
data={"name": "Alice Smith"},
transaction=transaction
)
# Create a product
product = db.records.create(
label="PRODUCT",
data={"name": "Smartphone", "price": 799.99},
transaction=transaction
)
# Create a purchase relationship
user.attach(
target=product,
options={"type": "PURCHASED", "direction": "out"},
transaction=transaction
)
# Everything will be committed if no errors occur
# If an error occurs, the transaction will be automatically rolled back
```
## Next Steps
Explore the detailed documentation for each component of the SDK:
- [Records](./records/create-records.md) - Create, read, update, and delete record operations
- [Properties](./properties.md) - Manage data properties
- [Labels](./labels.md) - Work with node labels
- [Relationships](./relationships.md) - Handle connections between records
- [Transactions](./transactions.md) - Manage transaction operations for data consistency
For more advanced use cases, check our [Tutorials](../tutorials/reusable-search-query) section.
---
# Labels
In RushDB, [labels](../concepts/labels.md) are used to categorize records and define their types. The Python SDK provides methods for managing labels, finding records by labels, and working with label hierarchies.
## Overview
Labels in RushDB serve several important purposes:
- Categorizing records into logical groups
- Defining the type or class of a record
- Enabling efficient filtering and searching
- Supporting hierarchical data modeling
## Prerequisites
Before working with labels, make sure you have initialized the RushDB client with your API token:
```python
from rushdb import RushDB
db = RushDB("RUSHDB_API_KEY", base_url="https://api.rushdb.com/api/v1")
```
## Creating Records with Labels
When creating records, you specify labels to categorize them:
```python
# Create a record with a single label
person = db.records.create(
label="PERSON",
data={
"name": "John Doe",
"age": 30
}
)
# The record now has the label "PERSON"
print(person.label) # Output: "PERSON"
```
## Working with Label Case
By default, labels are stored as provided. However, you can use the `capitalizeLabels` option to automatically capitalize labels:
```python
# Create a record with automatic label capitalization
product = db.records.create(
label="product", # Will be automatically capitalized to "PRODUCT"
data={
"name": "Smartphone",
"price": 999.99
},
options={
"capitalizeLabels": True
}
)
print(product.label) # Output: "PRODUCT"
```
## Finding Records by Label
You can search for records by their labels using the `find()` method with the `labels` parameter:
```python
# Find all records with the "PERSON" label
people = db.records.find({
"labels": ["PERSON"]
})
# Find records with either "EMPLOYEE" or "CONTRACTOR" labels
workers = db.records.find({
"labels": ["EMPLOYEE", "CONTRACTOR"]
})
# Combine label filtering with other search criteria
senior_engineers = db.records.find({
"labels": ["EMPLOYEE"],
"where": {
"position": "Senior Engineer",
"yearsOfExperience": {"$gte": 5}
}
})
```
## Label Hierarchy and Inheritance
RushDB supports label inheritance, allowing you to model hierarchical relationships between labels. For example, an "EMPLOYEE" can also be a "PERSON":
```python
# Create a record with multiple labels
employee = db.records.create_many(
label="EMPLOYEE",
data={
"name": "Jane Smith",
"email": "jane@example.com",
"department": "Engineering",
"PERSON": { # Nested object creates a relationship with the label PERSON
"age": 28,
"address": "123 Main St"
}
},
options={
"relationshipType": "IS_A" # Establishes an inheritance relationship
}
)
# Finding the employee will also include PERSON properties
found_employee = db.records.find({
"labels": ["EMPLOYEE"],
"where": {
"name": "Jane Smith"
}
})
```
## Discovering and Searching Labels with LabelsAPI
The `LabelsAPI` provides dedicated functionality for discovering and working with record labels in the database. This API allows you to find what types of records exist in your database and search for labels based on the properties of records that have those labels.
**Important**: The LabelsAPI uses a Record-centric approach. When searching for labels, you specify properties of the records that have those labels, not properties of the labels themselves. This means the `where` clause contains Record properties to find labels from records that match those criteria.
### Overview
The LabelsAPI enables you to:
- Discover all labels (record types) in the database
- Search for labels based on record properties
- Understand the data structure and schema of your database
- Monitor label usage and distribution
- Work with labels in transaction contexts
### Accessing the LabelsAPI
You access the LabelsAPI through the main RushDB client:
```python
from rushdb import RushDB
# Initialize the client
db = RushDB("RUSHDB_API_KEY", base_url="https://api.rushdb.com/api/v1")
# Access the labels API
labels_api = db.labels
```
### The find() Method
The `find()` method is the primary way to discover and search for labels in your database. It uses a Record-centric approach where you can filter labels based on the properties of records that have those labels.
#### Method Signature
```python
def find(
self,
search_query: Optional[SearchQuery] = None,
transaction: Optional[Transaction] = None,
) -> List[str]
```
#### Parameters
- **search_query** (`Optional[SearchQuery]`): Search criteria to filter labels. Uses a Record-centric approach where the `where` clause contains Record properties to find labels from records that match those criteria:
- `where`: Filter conditions for record properties (not label properties)
- `labels`: Not typically used in LabelsAPI as you're discovering labels
- `orderBy`: Not applicable for label discovery
- **transaction** (`Optional[Transaction]`): Optional transaction context for the operation
#### Return Value
Returns a `List[str]` containing label names (strings) that exist in the database. Each string represents a unique label/type used in the database.
### Basic Label Discovery
#### Find All Labels
```python
# Get all labels in the database
all_labels = db.labels.find()
print("Available record types:", all_labels)
# Output: ['USER', 'COMPANY', 'PROJECT', 'EMPLOYEE', 'DEPARTMENT']
# Check how many different types of records exist
print(f"Database contains {len(all_labels)} different record types")
```
#### Discover Labels Based on Record Properties
```python
# Find labels from records that are active
active_record_labels = db.labels.find({
"where": {
"isActive": True
}
})
print("Labels from active records:", active_record_labels)
# Output: ['USER', 'PROJECT', 'EMPLOYEE']
# Find labels from records in Engineering department
engineering_labels = db.labels.find({
"where": {
"department": "Engineering"
}
})
print("Labels used in Engineering:", engineering_labels)
# Output: ['EMPLOYEE', 'MANAGER', 'PROJECT']
```
### Advanced Label Searching
#### Filter by Record Creation Date
```python
# Find labels from recently created records
recent_labels = db.labels.find({
"where": {
"createdAt": {"$gte": "2024-01-01T00:00:00Z"}
}
})
print("Labels from records created this year:", recent_labels)
```
#### Filter by Complex Record Properties
```python
# Find labels from high-value records
valuable_record_labels = db.labels.find({
"where": {
"$or": [
{"revenue": {"$gte": 1000000}}, # High revenue companies
{"salary": {"$gte": 150000}}, # High salary employees
{"budget": {"$gte": 500000}} # High budget projects
]
}
})
print("Labels from high-value records:", valuable_record_labels)
```
#### Find Labels by Record Status
```python
# Find labels from records matching specific status
published_labels = db.labels.find({
"where": {
"status": {"$in": ["published", "active", "approved"]}
}
})
# Find labels from records with specific properties
tech_labels = db.labels.find({
"where": {
"$and": [
{"industry": "Technology"},
{"employees": {"$gte": 50}},
{"isPublic": True}
]
}
})
```
### Label Analytics and Insights
#### Analyze Database Schema
```python
# Get comprehensive view of your database schema
all_labels = db.labels.find()
print("Database Schema Overview:")
print(f"Total record types: {len(all_labels)}")
for label in sorted(all_labels):
print(f" - {label}")
# Find labels by different criteria to understand data distribution
active_labels = db.labels.find({"where": {"isActive": True}})
inactive_labels = db.labels.find({"where": {"isActive": False}})
print(f"\nActive record types: {len(active_labels)}")
print(f"Inactive record types: {len(inactive_labels)}")
```
#### Monitor Label Usage Patterns
```python
# Discover which types of records exist in different departments
departments = ["Engineering", "Sales", "Marketing", "HR"]
label_distribution = {}
for dept in departments:
dept_labels = db.labels.find({
"where": {"department": dept}
})
label_distribution[dept] = dept_labels
print(f"{dept} department uses labels: {dept_labels}")
# Find common labels across departments
common_labels = set(label_distribution["Engineering"])
for dept_labels in label_distribution.values():
common_labels &= set(dept_labels)
print(f"Labels common across all departments: {list(common_labels)}")
```
### Using Labels API with Transactions
The LabelsAPI supports transactions for consistent discovery:
```python
# Start a transaction
tx = db.tx.begin()
try:
# Discover labels within the transaction context
labels = db.labels.find({
"where": {
"department": "Sales",
"isActive": True
}
}, transaction=tx)
print(f"Found labels in Sales: {labels}")
# Perform additional operations in the same transaction
for label in labels:
# Query records of each discovered type
records = db.records.find({
"labels": [label],
"where": {"department": "Sales"}
}, transaction=tx)
print(f"Found {len(records)} {label} records in Sales")
# Commit the transaction
tx.commit()
except Exception as e:
# Roll back on error
tx.rollback()
print(f"Transaction failed: {e}")
```
### Practical Use Cases
#### Database Migration and Schema Discovery
```python
# Discover existing schema before migration
def analyze_database_schema():
"""Analyze the current database schema and structure."""
# Get all labels
all_labels = db.labels.find()
schema_info = {}
for label in all_labels:
# Find sample records for each label to understand structure
sample_records = db.records.find({
"labels": [label]
}, limit=5)
# Analyze properties
properties = set()
for record in sample_records:
properties.update(record.data.keys())
schema_info[label] = {
"sample_properties": list(properties),
"sample_count": len(sample_records)
}
return schema_info
# Run schema analysis
schema = analyze_database_schema()
for label, info in schema.items():
print(f"\n{label}:")
print(f" Sample properties: {info['sample_properties']}")
print(f" Sample records found: {info['sample_count']}")
```
#### Data Quality Assessment
```python
# Find labels from incomplete or problematic records
def assess_data_quality():
"""Assess data quality by finding labels from problematic records."""
# Find labels from records missing critical fields
incomplete_labels = db.labels.find({
"where": {
"$or": [
{"name": None},
{"createdAt": None},
{"id": None}
]
}
})
# Find labels from very old records that might need updating
old_labels = db.labels.find({
"where": {
"updatedAt": {"$lt": "2023-01-01T00:00:00Z"}
}
})
return {
"incomplete_data_labels": incomplete_labels,
"outdated_labels": old_labels
}
# Run data quality assessment
quality_report = assess_data_quality()
print("Data Quality Report:")
print(f"Labels with incomplete data: {quality_report['incomplete_data_labels']}")
print(f"Labels with outdated records: {quality_report['outdated_labels']}")
```
### Performance Considerations
When using the LabelsAPI:
1. **Use specific filters**: Apply `where` conditions to reduce the scope of label discovery
2. **Cache results**: Label discovery results can be cached as they don't change frequently
3. **Combine with record queries**: Use LabelsAPI to discover types, then use RecordsAPI for detailed data
4. **Monitor database growth**: Regular label discovery helps track database schema evolution
5. **Use in schema validation**: Incorporate label discovery in data validation pipelines
### Error Handling
```python
try:
labels = db.labels.find({
"where": {"department": "NonexistentDepartment"}
})
print(f"Found labels: {labels}") # Will return empty list if no matches
except Exception as e:
print(f"Error discovering labels: {e}")
# Handle the error appropriately
```
### Integration with Record Operations
The LabelsAPI works seamlessly with record operations for comprehensive data management:
```python
# 1. Discover available labels
available_labels = db.labels.find()
print(f"Available record types: {available_labels}")
# 2. Find labels from specific types of data
user_related_labels = db.labels.find({
"where": {
"$or": [
{"email": {"$ne": None}},
{"username": {"$ne": None}},
{"role": {"$ne": None}}
]
}
})
# 3. Query records for each discovered label
for label in user_related_labels:
records = db.records.find({
"labels": [label]
})
print(f"Found {len(records)} records with label '{label}'")
# 4. Create new records based on discovered patterns
if "USER" in available_labels:
# Safe to create USER records
new_user = db.records.create(
label="USER",
data={"name": "New User", "email": "new@example.com"}
)
```
## API Reference
### LabelsAPI.find()
The `find()` method discovers and retrieves labels (record types) from the database based on record properties.
#### Method Signature
```python
def find(
self,
search_query: Optional[SearchQuery] = None,
transaction: Optional[Transaction] = None,
) -> List[str]
```
#### Parameters
- **search_query** (`Optional[SearchQuery]`): Search criteria to filter labels using a Record-centric approach
- **where** (dict): Filter conditions for record properties. The API finds labels from records that match these conditions
- **labels**: Not typically used in LabelsAPI since you're discovering labels
- **orderBy**: Not applicable for label discovery
- **transaction** (`Optional[Transaction]`): Transaction context for the operation
#### Return Value
- **List[str]**: List of unique label names (strings) found in the database
#### Examples
```python
# Get all labels
all_labels = db.labels.find()
# Get labels from active records
active_labels = db.labels.find({
"where": {"isActive": True}
})
# Get labels from records in specific department
dept_labels = db.labels.find({
"where": {"department": "Engineering"}
}, transaction=tx)
```
### SearchQuery Structure for Labels
When using the LabelsAPI, the SearchQuery follows this structure:
```python
from rushdb.models.search_query import SearchQuery
# Example SearchQuery for label discovery
query = SearchQuery(
where={
# Record properties to filter by
"isActive": True,
"department": "Engineering",
"createdAt": {"$gte": "2024-01-01T00:00:00Z"}
}
)
labels = db.labels.find(query)
```
## Best Practices for Working with Labels
1. **Use consistent naming conventions** - Consider using uppercase for labels (e.g., "PERSON" instead of "Person") for consistency with graph database conventions.
2. **Leverage the `capitalizeLabels` option** - Use this option to ensure consistent capitalization across your database.
3. **Use specific labels** - More specific labels make searching and filtering more efficient.
4. **Consider label hierarchies** - Use label inheritance to model "is-a" relationships between entities.
5. **Combine labels with where clauses** - For precise filtering, combine label filtering with property conditions in the where clause.
6. **Be mindful of performance** - Searching with very common labels might return large result sets. Use additional filters to narrow down results.
7. **Use LabelsAPI for schema discovery** - Regularly use the LabelsAPI to understand your database structure and monitor schema evolution.
8. **Cache label discovery results** - Since labels don't change frequently, consider caching the results of label discovery operations.
9. **Filter by record properties for targeted discovery** - Use the Record-centric approach to discover labels from specific subsets of your data.
10. **Integrate with data validation** - Use label discovery to validate that expected record types exist before performing operations.
11. **Monitor label distribution** - Use LabelsAPI to understand how different types of data are distributed across your database.
12. **Combine with record operations** - Use LabelsAPI to discover types, then use RecordsAPI for detailed record manipulation.
## Related Documentation
- [Labels Concept](../concepts/labels.md) - Learn more about how labels work in RushDB
- [Search by Labels](../concepts/search/labels.md) - Advanced techniques for searching by labels
- [Record Creation](./records/create-records.md) - Creating records with labels
- [Finding Records](./records/get-records.md) - Search techniques including label filtering
---
# Properties
The `PropertiesAPI` class provides methods for managing and querying properties in RushDB.
## Class Definition
```python
class PropertiesAPI(BaseAPI):
```
## Methods
### find()
Retrieves a find of properties based on optional search criteria.
**Signature:**
```python
def find(
self,
search_query: Optional[SearchQuery] = None,
transaction: Optional[Transaction] = None
) -> List[Property]
```
**Arguments:**
- `search_query` (Optional[SearchQuery]): Search query parameters for filtering properties
- `transaction` (Optional[Transaction]): Optional transaction object
**Returns:**
- `List[Property]`: List of properties matching the search criteria
**Example:**
```python
# Find all properties
properties = client.properties.find()
# Find properties with specific criteria
query = {
"where": {
"name": {"$startsWith": "user_"}, # Properties starting with 'user_'
"type": "string" # Only string type properties
},
"limit": 10 # Limit to 10 results
}
filtered_properties = client.properties.find(query)
```
### find_by_id()
Retrieves a specific property by its ID.
**Signature:**
```python
def find_by_id(
self,
property_id: str,
transaction: Optional[Transaction] = None
) -> Property
```
**Arguments:**
- `property_id` (str): Unique identifier of the property
- `transaction` (Optional[Transaction]): Optional transaction object
**Returns:**
- `Property`: Property details
**Example:**
```python
# Retrieve a specific property by ID
property_details = client.properties.find_by_id("prop_123456")
```
### delete()
Deletes a property by its ID.
**Signature:**
```python
def delete(
self,
property_id: str,
transaction: Optional[Transaction] = None
) -> None
```
**Arguments:**
- `property_id` (str): Unique identifier of the property to delete
- `transaction` (Optional[Transaction]): Optional transaction object
**Returns:**
- `None`
**Example:**
```python
# Delete a property
client.properties.delete("prop_123456")
```
### values()
Retrieves values for a specific property with optional filtering, sorting and pagination using SearchQuery.
**Signature:**
```python
def values(
self,
property_id: str,
search_query: Optional[SearchQuery] = None,
transaction: Optional[Transaction] = None
) -> PropertyValuesData
```
**Arguments:**
- `property_id` (str): Unique identifier of the property
- `search_query` (Optional[SearchQuery]): Search query parameters for filtering the records containing this property. This can include:
- `where`: Filter criteria for records containing this property
- `labels`: Array of labels to filter records by
- `query`: Filter values by this text string
- `orderBy`: Sort direction (`asc` or `desc`)
- `skip`: Number of values to skip (for pagination)
- `limit`: Maximum number of values to return
- `transaction` (Optional[Transaction]): Optional transaction object
**Returns:**
- `PropertyValuesData`: Property values data, including optional min/max and list of values
**Example:**
```python
# Get property values with filtering
values_data = client.properties.values(
property_id="prop_age",
search_query={
"where": {
"status": "active", # Only get values from active records
"region": "US" # Only from US region
},
"query": "2", # Filter values containing "2"
"orderBy": "desc", # Sort values in descending order
"skip": 0, # Start from the first value
"limit": 100 # Return up to 100 values
}
)
# Access values
print(values_data.get('values', [])) # List of property values
print(values_data.get('min')) # Minimum value (for numeric properties)
print(values_data.get('max')) # Maximum value (for numeric properties)
```
## Comprehensive Usage Example
```python
# Find all properties
all_properties = client.properties.find()
for prop in all_properties:
print(f"Property ID: {prop['id']}")
print(f"Name: {prop['name']}")
print(f"Type: {prop['type']}")
print(f"Metadata: {prop.get('metadata', 'No metadata')}")
print("---")
# Detailed property search
query = {
"where": {
"type": "number", # Only numeric properties
"name": {"$contains": "score"} # Properties with 'score' in name
},
"limit": 5 # Limit to 5 results
}
numeric_score_properties = client.properties.find(query)
# Get values for a specific property
if numeric_score_properties:
first_prop = numeric_score_properties[0]
prop_values = client.properties.values(
property_id=first_prop['id'],
search_query={
"orderBy": "desc",
"limit": 50
}
)
print(f"Values for {first_prop['name']}:")
print(f"Min: {prop_values.get('min')}")
print(f"Max: {prop_values.get('max')}")
# Detailed property examination
detailed_prop = client.properties.find_by_id(first_prop['id'])
print("Detailed Property Info:", detailed_prop)
```
## Property Types and Structures
RushDB supports the following property types:
- `"boolean"`: True/False values
- `"datetime"`: Date and time values
- `"null"`: Null/empty values
- `"number"`: Numeric values
- `"string"`: Text values
### Property Structure Example
```python
property = {
"id": "prop_unique_id",
"name": "user_score",
"type": "number",
"metadata": Optional[str] # Optional additional information
}
property_with_value = {
"id": "prop_unique_id",
"name": "user_score",
"type": "number",
"value": 95.5 # Actual property value
}
```
## Transactions
Properties API methods support optional transactions for atomic operations:
```python
# Using a transaction
with client.transactions.begin() as transaction:
# Perform multiple property-related operations
property_to_delete = client.properties.find(
{"where": {"name": "temp_property"}},
transaction=transaction
)[0]
client.properties.delete(
property_id=property_to_delete['id'],
transaction=transaction
)
# Transaction will automatically commit if no errors occur
```
## Error Handling
When working with the PropertiesAPI, be prepared to handle potential errors:
```python
try:
# Attempt to find or delete a property
property_details = client.properties.find_by_id("non_existent_prop")
except RushDBError as e:
print(f"Error: {e}")
print(f"Error Details: {e.details}")
```
---
# Record
The `Record` class represents a record in RushDB and provides methods for manipulating individual records, including updates, relationships, and deletions.
## Class Definition
```python
class Record:
"""Represents a record in RushDB with methods for manipulation."""
def __init__(self, client: "RushDB", data: Union[Dict[str, Any], None] = None)
```
## Properties
### id
Gets the record's unique identifier.
**Type:** `str`
**Example:**
```python
record = client.records.create("USER", {"name": "John"})
print(record.id) # e.g., "1234abcd-5678-..."
```
### proptypes
Gets the record's property types.
**Type:** `str`
**Example:**
```python
record = client.records.create("USER", {"name": "John", "age": 25})
print(record.proptypes) # Returns property type definitions
```
### label
Gets the record's label.
**Type:** `str`
**Example:**
```python
record = client.records.create("USER", {"name": "John"})
print(record.label) # "USER"
```
### timestamp
Gets the record's creation timestamp from its ID.
**Type:** `int`
**Example:**
```python
record = client.records.create("USER", {"name": "John"})
print(record.timestamp) # Unix timestamp in milliseconds
```
### date
Gets the record's creation date.
**Type:** `datetime`
**Example:**
```python
record = client.records.create("USER", {"name": "John"})
print(record.date) # datetime object
```
## Methods
### set()
Updates all data for the record.
**Signature:**
```python
def set(
self,
data: Dict[str, Any],
transaction: Optional[Transaction] = None
) -> Dict[str, str]
```
**Arguments:**
- `data` (Dict[str, Any]): New record data
- `transaction` (Optional[Transaction]): Optional transaction object
**Returns:**
- `Dict[str, str]`: Response data
**Example:**
```python
record = client.records.create("USER", {"name": "John"})
response = record.set({
"name": "John Doe",
"email": "john@example.com",
"age": 30
})
```
### update()
Updates specific fields of the record.
**Signature:**
```python
def update(
self,
data: Dict[str, Any],
transaction: Optional[Transaction] = None
) -> Dict[str, str]
```
**Arguments:**
- `data` (Dict[str, Any]): Partial record data to update
- `transaction` (Optional[Transaction]): Optional transaction object
**Returns:**
- `Dict[str, str]`: Response data
**Example:**
```python
record = client.records.create("USER", {
"name": "John",
"email": "john@example.com"
})
response = record.update({
"email": "john.doe@example.com"
})
```
### attach()
Creates relationships with other records.
**Signature:**
```python
def attach(
self,
target: Union[str, List[str], Dict[str, Any], List[Dict[str, Any]], "Record", List["Record"]],
options: Optional[RelationshipOptions] = None,
transaction: Optional[Transaction] = None
) -> Dict[str, str]
```
**Arguments:**
- `target` (Union[str, List[str], Dict[str, Any], List[Dict[str, Any]], Record, List[Record]]): Target record(s)
- `options` (Optional[RelationshipOptions]): Relationship options
- `direction` (Optional[Literal["in", "out"]]): Relationship direction
- `type` (Optional[str]): Relationship type
- `transaction` (Optional[Transaction]): Optional transaction object
**Returns:**
- `Dict[str, str]`: Response data
**Example:**
```python
# Create two records
user = client.records.create("USER", {"name": "John"})
group = client.records.create("GROUP", {"name": "Admins"})
# Attach user to group
response = user.attach(
target=group,
options=RelationshipOptions(
type="BELONGS_TO",
direction="out"
)
)
```
### detach()
Removes relationships with other records.
**Signature:**
```python
def detach(
self,
target: Union[str, List[str], Dict[str, Any], List[Dict[str, Any]], "Record", List["Record"]],
options: Optional[RelationshipDetachOptions] = None,
transaction: Optional[Transaction] = None
) -> Dict[str, str]
```
**Arguments:**
- `target` (Union[str, List[str], Dict[str, Any], List[Dict[str, Any]], Record, List[Record]]): Target record(s)
- `options` (Optional[RelationshipDetachOptions]): Detach options
- `direction` (Optional[Literal["in", "out"]]): Relationship direction
- `typeOrTypes` (Optional[Union[str, List[str]]]): Relationship type(s)
- `transaction` (Optional[Transaction]): Optional transaction object
**Returns:**
- `Dict[str, str]`: Response data
**Example:**
```python
# Detach user from group
response = user.detach(
target=group,
options=RelationshipDetachOptions(
typeOrTypes="BELONGS_TO",
direction="out"
)
)
```
### delete()
Deletes the record.
**Signature:**
```python
def delete(
self,
transaction: Optional[Transaction] = None
) -> Dict[str, str]
```
**Arguments:**
- `transaction` (Optional[Transaction]): Optional transaction object
**Returns:**
- `Dict[str, str]`: Response data
**Example:**
```python
user = client.records.create("USER", {"name": "John"})
response = user.delete()
```
## Complete Usage Example
Here's a comprehensive example demonstrating various Record operations:
```python
# Create a new record
user = client.records.create("USER", {
"name": "John Doe",
"email": "john@example.com",
"age": 30
})
# Access properties
print(f"Record ID: {user.id}")
print(f"Label: {user.label}")
print(f"Created at: {user.date}")
# Update record data
user.update({
"age": 31,
"title": "Senior Developer"
})
# Create related records
department = client.records.create("DEPARTMENT", {
"name": "Engineering"
})
project = client.records.create("PROJECT", {
"name": "Secret Project"
})
# Create relationships
user.attach(
target=department,
options=RelationshipOptions(
type="BELONGS_TO",
direction="out"
)
)
user.attach(
target=project,
options=RelationshipOptions(
type="WORKS_ON",
direction="out"
)
)
# Remove relationship
user.detach(
target=project,
options=RelationshipDetachOptions(
typeOrTypes="WORKS_ON",
direction="out"
)
)
# Delete record
user.delete()
```
## Working with Transactions
Records can be manipulated within transactions for atomic operations:
```python
# Start a transaction
with client.transactions.begin() as transaction:
# Create user
user = client.records.create(
"USER",
{"name": "John Doe"},
transaction=transaction
)
# Update user
user.update(
{"status": "active"},
transaction=transaction
)
# Create and attach department
dept = client.records.create(
"DEPARTMENT",
{"name": "Engineering"},
transaction=transaction
)
user.attach(
target=dept,
options=RelationshipOptions(type="BELONGS_TO"),
transaction=transaction
)
# Transaction will automatically commit if no errors occur
# If an error occurs, it will automatically rollback
```
---
# SearchResult
The `SearchResult` class is a container for search results that follows Python SDK best practices. It provides both list-like access and iteration support, along with metadata about the search operation including total count and pagination information.
This class is designed to be familiar to Python developers, following patterns used by popular libraries like boto3 (AWS SDK), google-cloud libraries, and requests libraries.
## Class Definition
```python
from typing import Generic, Iterator, List, Optional, TypeVar
from .record import Record
from .search_query import SearchQuery
T = TypeVar("T")
class SearchResult(Generic[T]):
"""Container for search results following Python SDK best practices."""
def __init__(
self,
data: List[T],
total: Optional[int] = None,
search_query: Optional[SearchQuery] = None,
)
```
## Type Aliases
```python
# Type alias for record search results
RecordSearchResult = SearchResult[Record]
```
## Properties
The `SearchResult` class provides the following properties:
- **`data`** - List of result items
- **`total`** - Total number of matching records in the database
- **`search_query`** - The search query used to generate this result
- **`has_more`** - Whether there are more records available beyond this result set
- **`skip`** - Number of records that were skipped in the search query
- **`limit`** - Limit that was applied to the search query
### data
Gets the list of result items.
**Type:** `List[T]`
**Example:**
```python
result = client.records.find({"labels": ["USER"]})
records = result.data
print(f"Retrieved {len(records)} records")
```
### total
Gets the total number of records in the database that match your search criteria.
**Type:** `int`
**Important:** This represents the total count of all records matching your search criteria in the entire database, not the number of records in the current page/result set. When pagination is used (`limit` and `skip`), this number will typically be larger than the number of records actually returned in `data`.
**Example:**
```python
# Search for users with a limit of 10 records per page
result = client.records.find({"labels": ["USER"], "limit": 10})
# total = all users in database matching the criteria (e.g., 1,847)
# len(result) = records in this result set (e.g., 10)
print(f"Showing {len(result)} out of {result.total} total matching users")
# Output: "Showing 10 out of 1,847 total matching users"
```
### search_query
Gets the search query used to generate this result.
**Type:** `SearchQuery`
**Example:**
```python
query = {"labels": ["USER"], "where": {"active": True}}
result = client.records.find(query)
original_query = result.search_query
```
### has_more
Checks if there are more records available beyond this result set.
**Type:** `bool`
**Example:**
```python
result = client.records.find({"labels": ["USER"], "limit": 10})
if result.has_more:
print("There are more records available")
# Fetch next page
next_result = client.records.find({
"labels": ["USER"],
"limit": 10,
"skip": result.skip + len(result)
})
```
### skip
Gets the number of records that were skipped in the search query.
**Type:** `int`
**Example:**
```python
result = client.records.find({
"labels": ["USER"],
"limit": 10,
"skip": 20
})
print(f"Skipped {result.skip} records") # Will print: "Skipped 20 records"
```
### limit
Gets the limit that was applied to the search query.
**Type:** `Optional[int]`
**Note:** If no limit was specified in the original query, this returns `len(result.data)`.
**Example:**
```python
result = client.records.find({"labels": ["USER"], "limit": 25})
print(f"Limit applied: {result.limit}") # Will print: "Limit applied: 25"
# If no limit was specified
result = client.records.find({"labels": ["USER"]})
print(f"Effective limit: {result.limit}") # Will print the actual number of records returned
```
## Methods
### `__len__() -> int`
Returns the number of records in this result set.
**Example:**
```python
result = client.records.find({"labels": ["USER"]})
record_count = len(result)
print(f"Found {record_count} records")
```
### `__iter__() -> Iterator[T]`
Allows iteration over the result items.
**Example:**
```python
result = client.records.find({"labels": ["USER"]})
for record in result:
print(f"User: {record.get('name', 'Unknown')}")
```
### `__getitem__(index) -> T`
Gets an item by index or slice.
**Parameters:**
- `index`: Integer index or slice object
**Example:**
```python
result = client.records.find({"labels": ["USER"]})
# Get first record
first_user = result[0] if result else None
# Get last record
last_user = result[-1] if result else None
# Get slice of records
first_five = result[0:5]
```
### `__bool__() -> bool`
Checks if the result set contains any items.
**Example:**
```python
result = client.records.find({"labels": ["USER"], "where": {"active": False}})
if result:
print(f"Found {len(result)} inactive users")
else:
print("No inactive users found")
```
### `to_dict() -> dict`
Returns the result in a standardized dictionary format.
**Returns:** Dictionary with keys: `total`, `data`, `search_query`
**Example:**
```python
result = client.records.find({"labels": ["USER"]})
result_dict = result.to_dict()
print(result_dict["total"]) # Total count
print(len(result_dict["data"])) # Records in this result set
```
### `get_page_info() -> dict`
Gets pagination information about the current result set.
**Returns:** Dictionary with pagination metadata
**Example:**
```python
result = client.records.find({
"labels": ["USER"],
"limit": 10,
"skip": 20
})
page_info = result.get_page_info()
print(f"Total records: {page_info['total']}") # Total matching records in database
print(f"Records loaded: {page_info['loaded']}") # Records in this result set (len(result))
print(f"Has more: {page_info['has_more']}") # Whether there are more records available
print(f"Current skip: {page_info['skip']}") # Number of records skipped
print(f"Current limit: {page_info['limit']}") # Limit applied to the query
```
## Usage Examples
### Basic Iteration
```python
# Find all active users
result = client.records.find({
"labels": ["USER"],
"where": {"active": True}
})
# Iterate over results
for user in result:
print(f"User: {user.get('name')} ({user.get('email')})")
# Check if results exist
if result:
print(f"Found {len(result)} active users")
else:
print("No active users found")
```
### Pagination Handling
```python
def get_all_users():
"""Example of handling pagination to get all users."""
all_users = []
skip = 0
limit = 100
while True:
result = client.records.find({
"labels": ["USER"],
"limit": limit,
"skip": skip
})
# Add records to our collection
all_users.extend(result.data)
# Check if we have more records
if not result.has_more:
break
skip += limit
return all_users
```
### List-like Operations
```python
result = client.records.find({"labels": ["USER"]})
# Access specific records
first_user = result[0] if result else None
last_user = result[-1] if result else None
# Get a subset
top_five = result[:5]
# Check length
user_count = len(result)
# Convert to regular list if needed
user_list = list(result)
```
### Working with Metadata
```python
result = client.records.find({
"labels": ["USER"],
"where": {"department": "Engineering"},
"limit": 20,
"skip": 40
})
print(f"Page size: {len(result)}") # Records in current page
print(f"Total engineers: {result.total}") # Total matching records
print(f"Records skipped: {result.skip}") # Records skipped (40)
print(f"Limit applied: {result.limit}") # Limit for this query (20)
print(f"Showing results for query: {result.search_query}")
if result.has_more:
remaining = result.total - (result.skip + len(result))
print(f"{remaining} more engineers available")
# Get next page
next_result = client.records.find({
"labels": ["USER"],
"where": {"department": "Engineering"},
"limit": 20,
"skip": result.skip + len(result) # Skip to next page
})
```
### Error Handling
```python
# SearchResult is designed to be safe - it won't raise exceptions for empty results
result = client.records.find({"labels": ["NONEXISTENT"]})
# These operations are safe even if no results found
print(f"Found {len(result)} records") # Will print 0
print(f"Total: {result.total}") # Will print 0
# Iteration is safe
for record in result:
print("This won't execute if result is empty")
# Boolean check is safe
if result:
print("This won't execute if result is empty")
```
## Integration with Records API
The `SearchResult` class is returned by the `RecordsAPI.find()` method:
```python
from rushdb.models.search_query import SearchQuery
# Using SearchQuery class
query = SearchQuery(
labels=["USER"],
where={"active": True},
limit=10,
order_by={"created_at": "desc"}
)
result = client.records.find(query)
# Working with the result
for user in result:
print(f"User: {user.get('name')}")
print(f"Loaded {len(result)} out of {result.total} total users")
```
## Best Practices
1. **Check for Results**: Always check if results exist before accessing individual records:
```python
result = client.records.find(query)
if result:
first_record = result[0]
```
2. **Handle Pagination**: Use the `has_more` property to implement proper pagination:
```python
if result.has_more:
# Load next page
pass
```
3. **Use Iteration**: Prefer iteration over index access when processing all records:
```python
# Good
for record in result:
process(record)
# Less efficient
for i in range(len(result)):
process(result[i])
```
4. **Monitor Total vs Length**: Understand the difference between `total` (all matching records) and `len()` (records in current page):
```python
print(f"Showing {len(result)} of {result.total} total records")
```
5. **Use Skip and Limit for Pagination**: Leverage the `skip` and `limit` properties for implementing pagination:
```python
# Get next page
next_result = client.records.find({
"labels": ["USER"],
"limit": result.limit,
"skip": result.skip + len(result)
})
```
---
# Transaction
The `Transaction` class represents a record in RushDB and provides methods for manipulating individual records, including updates, relationships, and deletions.
## Class Definition
```python
class Transaction:
"""Represents a RushDB transaction."""
def __init__(self, client: "RushDB", transaction_id: str):
```
## Properties
### id
Gets the record's unique identifier.
**Type:** `str`
**Example:**
```python
record = client.records.create("USER", {"name": "John"})
print(record.id) # e.g., "1234abcd-5678-..."
```
### proptypes
Gets the record's property types.
**Type:** `str`
**Example:**
```python
record = client.records.create("USER", {"name": "John", "age": 25})
print(record.proptypes) # Returns property type definitions
```
### label
Gets the record's label.
**Type:** `str`
**Example:**
```python
record = client.records.create("USER", {"name": "John"})
print(record.label) # "USER"
```
### timestamp
Gets the record's creation timestamp from its ID.
**Type:** `int`
**Example:**
```python
record = client.records.create("USER", {"name": "John"})
print(record.timestamp) # Unix timestamp in milliseconds
```
### date
Gets the record's creation date.
**Type:** `datetime`
**Example:**
```python
record = client.records.create("USER", {"name": "John"})
print(record.date) # datetime object
```
## Methods
### set()
Updates all data for the record.
**Signature:**
```python
def set(
self,
data: Dict[str, Any],
transaction: Optional[Transaction] = None
) -> Dict[str, str]
```
**Arguments:**
- `data` (Dict[str, Any]): New record data
- `transaction` (Optional[Transaction]): Optional transaction object
**Returns:**
- `Dict[str, str]`: Response data
**Example:**
```python
record = client.records.create("USER", {"name": "John"})
response = record.set({
"name": "John Doe",
"email": "john@example.com",
"age": 30
})
```
### update()
Updates specific fields of the record.
**Signature:**
```python
def update(
self,
data: Dict[str, Any],
transaction: Optional[Transaction] = None
) -> Dict[str, str]
```
**Arguments:**
- `data` (Dict[str, Any]): Partial record data to update
- `transaction` (Optional[Transaction]): Optional transaction object
**Returns:**
- `Dict[str, str]`: Response data
**Example:**
```python
record = client.records.create("USER", {
"name": "John",
"email": "john@example.com"
})
response = record.update({
"email": "john.doe@example.com"
})
```
### attach()
Creates relationships with other records.
**Signature:**
```python
def attach(
self,
target: Union[str, List[str], Dict[str, Any], List[Dict[str, Any]], "Record", List["Record"]],
options: Optional[RelationshipOptions] = None,
transaction: Optional[Transaction] = None
) -> Dict[str, str]
```
**Arguments:**
- `target` (Union[str, List[str], Dict[str, Any], List[Dict[str, Any]], Record, List[Record]]): Target record(s)
- `options` (Optional[RelationshipOptions]): Relationship options
- `direction` (Optional[Literal["in", "out"]]): Relationship direction
- `type` (Optional[str]): Relationship type
- `transaction` (Optional[Transaction]): Optional transaction object
**Returns:**
- `Dict[str, str]`: Response data
**Example:**
```python
# Create two records
user = client.records.create("USER", {"name": "John"})
group = client.records.create("GROUP", {"name": "Admins"})
# Attach user to group
response = user.attach(
target=group,
options=RelationshipOptions(
type="BELONGS_TO",
direction="out"
)
)
```
### detach()
Removes relationships with other records.
**Signature:**
```python
def detach(
self,
target: Union[str, List[str], Dict[str, Any], List[Dict[str, Any]], "Record", List["Record"]],
options: Optional[RelationshipDetachOptions] = None,
transaction: Optional[Transaction] = None
) -> Dict[str, str]
```
**Arguments:**
- `target` (Union[str, List[str], Dict[str, Any], List[Dict[str, Any]], Record, List[Record]]): Target record(s)
- `options` (Optional[RelationshipDetachOptions]): Detach options
- `direction` (Optional[Literal["in", "out"]]): Relationship direction
- `typeOrTypes` (Optional[Union[str, List[str]]]): Relationship type(s)
- `transaction` (Optional[Transaction]): Optional transaction object
**Returns:**
- `Dict[str, str]`: Response data
**Example:**
```python
# Detach user from group
response = user.detach(
target=group,
options=RelationshipDetachOptions(
typeOrTypes="BELONGS_TO",
direction="out"
)
)
```
### delete()
Deletes the record.
**Signature:**
```python
def delete(
self,
transaction: Optional[Transaction] = None
) -> Dict[str, str]
```
**Arguments:**
- `transaction` (Optional[Transaction]): Optional transaction object
**Returns:**
- `Dict[str, str]`: Response data
**Example:**
```python
user = client.records.create("USER", {"name": "John"})
response = user.delete()
```
## Complete Usage Example
Here's a comprehensive example demonstrating various Record operations:
```python
# Create a new record
user = client.records.create("USER", {
"name": "John Doe",
"email": "john@example.com",
"age": 30
})
# Access properties
print(f"Record ID: {user.id}")
print(f"Label: {user.label}")
print(f"Created at: {user.date}")
# Update record data
user.update({
"age": 31,
"title": "Senior Developer"
})
# Create related records
department = client.records.create("DEPARTMENT", {
"name": "Engineering"
})
project = client.records.create("PROJECT", {
"name": "Secret Project"
})
# Create relationships
user.attach(
target=department,
options=RelationshipOptions(
type="BELONGS_TO",
direction="out"
)
)
user.attach(
target=project,
options=RelationshipOptions(
type="WORKS_ON",
direction="out"
)
)
# Remove relationship
user.detach(
target=project,
options=RelationshipDetachOptions(
typeOrTypes="WORKS_ON",
direction="out"
)
)
# Delete record
user.delete()
```
## Working with Transactions
Records can be manipulated within transactions for atomic operations:
```python
# Start a transaction
with client.transactions.begin() as transaction:
# Create user
user = client.records.create(
"USER",
{"name": "John Doe"},
transaction=transaction
)
# Update user
user.update(
{"status": "active"},
transaction=transaction
)
# Create and attach department
dept = client.records.create(
"DEPARTMENT",
{"name": "Engineering"},
transaction=transaction
)
user.attach(
target=dept,
options=RelationshipOptions(type="BELONGS_TO"),
transaction=transaction
)
# Transaction will automatically commit if no errors occur
# If an error occurs, it will automatically rollback
```
---
# Raw Queries
> **Important (cloud-only):** This endpoint is available only on the RushDB managed cloud service or when your project is connected to a custom database through RushDB Cloud. It is not available for self-hosted or local-only deployments — attempting to use it against a non-cloud instance will fail.
### Python SDK example
```py
from rushdb import RushDB
db = RushDB("RUSHDB_API_KEY")
result = db.query.raw({
"query": "MATCH (n:Person) RETURN n LIMIT $limit",
"params": {"limit": 10}
})
print(result)
```
### Real-world example: employees at a company
```py
company = 'Acme Corp'
result = db.query.raw({
"query": "MATCH (c:Company { name: $company })<-[:EMPLOYS]-(p:Person) RETURN p { .name, .email, company: c.name } AS employee ORDER BY p.name LIMIT $limit",
"params": {"company": company, "limit": 50}
})
print(result['data'])
```
---
# Create Records
RushDB Python SDK provides flexible methods for creating [records](../../concepts/records.md). You can create single records or multiple records at once, with automatic data type inference and relationship handling.
## Overview
The Python SDK offers two main methods for creating records:
- `create()` - Create a single record with a label and data
- `create_many()` - Create multiple records in a batch operation
Both methods support options for controlling data processing and formatting.
## Prerequisites
Before creating records, make sure you have initialized the RushDB client with your API token:
```python
from rushdb import RushDB
db = RushDB("RUSHDB_API_KEY", base_url="https://api.rushdb.com/api/v1")
```
## Creating a Single Record
The `create()` method creates a single record with the specified label and data.
### Syntax
```python
db.records.create(
label: str,
data: Dict[str, Any],
options: Optional[Dict[str, bool]] = None,
transaction: Optional[Transaction] = None
) -> Record
```
### Parameters
| Parameter | Type | Description |
|-----------|------|-------------|
| `label` | str | [Label](../../concepts/labels.md) for the new record |
| `data` | Dict[str, Any] | Record data as key-value pairs |
| `options` | Optional[Dict[str, bool]] | Optional configuration parameters |
| `transaction` | Optional[Transaction] | Optional [transaction](../../concepts/transactions.mdx) object |
#### Options Dictionary
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `suggestTypes` | bool | `True` | When true, automatically infers data types for [properties](../../concepts/properties.md) |
| `castNumberArraysToVectors` | bool | `False` | When true, converts numeric arrays to vector type |
| `convertNumericValuesToNumbers` | bool | `False` | When true, converts string numbers to number type |
| `capitalizeLabels` | bool | `False` | When true, converts all [labels](../../concepts/labels.md) to uppercase |
| `relationshipType` | str | `None` | Custom [relationship](../../concepts/relationships.md) type for nested objects |
| `returnResult` | bool | `True` | Whether to return the created record |
### Returns
A `Record` object representing the newly created record.
### Examples
#### Basic Record Creation
```python
# Create a simple record
person = db.records.create(
label="PERSON",
data={
"name": "John Doe",
"age": 30,
"email": "john@example.com"
}
)
print(f"Created record with ID: {person.id}")
print(f"Record label: {person.label}")
```
#### Record with Complex Data Types
```python
# Create a record with various data types
product = db.records.create(
label="PRODUCT",
data={
"name": "Smartphone X",
"price": 899.99,
"isAvailable": True,
"tags": ["electronics", "smartphone", "new"],
"releaseDate": "2025-03-15T00:00:00Z",
"specifications": {
"dimensions": "150x70x8mm",
"weight": "180g",
"color": "Midnight Blue"
},
"ratings": [4.7, 4.8, 4.9, 5.0] # Could be converted to a vector
},
options={
"returnResult": True,
"suggestTypes": True,
"castNumberArraysToVectors": True
}
)
```
#### With Type Control
When you need precise control over how data types are handled:
```python
# Create a record with explicit options
customer = db.records.create(
label="customer", # Will be capitalized to "CUSTOMER"
data={
"id": "C-12345", # Will be stored as string
"name": "Jane Smith",
"joinDate": "2025-01-20T09:30:00Z", # Will be stored as datetime
"loyalty_points": "250", # Will be converted to number
"scores": ["95", "87", "92"] # Will be converted to numbers
},
options={
"suggestTypes": True,
"convertNumericValuesToNumbers": True,
"capitalizeLabels": True
}
)
# Access the property types
print(customer.proptypes)
```
## Creating Multiple Records
The `create_many()` method allows you to create multiple records in a single operation, which is more efficient for batch operations.
### Syntax
```python
db.records.create_many(
label: str,
data: Union[Dict[str, Any], List[Dict[str, Any]]],
options: Optional[Dict[str, bool]] = None,
transaction: Optional[Transaction] = None
) -> List[Record]
```
### Parameters
| Parameter | Type | Description |
|-----------|------|-------------|
| `label` | str | [Label](../../concepts/labels.md) for all created records |
| `data` | Union[Dict[str, Any], List[Dict[str, Any]]] | List of record data or a single dictionary |
| `options` | Optional[Dict[str, bool]] | Optional configuration parameters |
| `transaction` | Optional[Transaction] | Optional [transaction](../../concepts/transactions.mdx) object |
### Returns
A list of `Record` objects representing the newly created records.
### Examples
#### Creating Multiple Simple Records
```python
# Create multiple employee records
employees = db.records.create_many(
label="EMPLOYEE",
data=[
{
"name": "John Smith",
"position": "Developer",
"department": "Engineering"
},
{
"name": "Sarah Johnson",
"position": "Product Manager",
"department": "Product"
},
{
"name": "Michael Chen",
"position": "Data Scientist",
"department": "Data"
}
],
options={
"returnResult": True
}
)
# Access the created records
for employee in employees:
print(f"Created {employee.label} record: {employee.id}")
```
#### Working with Structured Data
```python
# Create records with relationships
products_data = [
{
"name": "Laptop Pro",
"price": "1299.99", # Will be converted to number
"category": "Computers",
"specs": {
"processor": "i9 13th Gen",
"memory": "32GB",
"storage": "1TB SSD"
},
"inStock": True,
"featureVector": [0.23, 0.45, 0.67, 0.89] # Will be stored as vector
},
{
"name": "Smartphone Ultra",
"price": "999.99", # Will be converted to number
"category": "Phones",
"specs": {
"processor": "Snapdragon 8 Gen 3",
"memory": "12GB",
"storage": "512GB"
},
"inStock": False,
"featureVector": [0.33, 0.55, 0.77, 0.99] # Will be stored as vector
}
]
products = db.records.create_many(
label="product", # Will be capitalized to "PRODUCT"
data=products_data,
options={
"returnResult": True,
"suggestTypes": True,
"convertNumericValuesToNumbers": True,
"castNumberArraysToVectors": True,
"capitalizeLabels": True,
"relationshipType": "HAS_SPECS" # Custom relationship for nested objects
}
)
```
#### With Nested Objects and Arrays
RushDB automatically handles nested objects and arrays by creating proper [relationships](../../concepts/relationships.md) between [records](../../concepts/records.md):
```python
# Create a company with employees as nested objects
company_data = {
"name": "Tech Innovations Inc.",
"founded": "2020-01-01T00:00:00Z",
"location": "San Francisco, CA",
"employees": [
{
"name": "Alice Cooper",
"role": "CEO",
"joinDate": "2020-01-01T00:00:00Z"
},
{
"name": "Bob Williams",
"role": "CTO",
"joinDate": "2020-02-15T00:00:00Z"
}
]
}
# This will create a COMPANY record connected to two EMPLOYEE records
# with custom relationship type "EMPLOYS"
result = db.records.create_many(
label="COMPANY",
data=company_data,
options={
"relationshipType": "EMPLOYS",
"capitalizeLabels": True,
"returnResult": True
}
)
```
## Best Practices
1. **Use Type Inference**: Let RushDB automatically infer data types with `suggestTypes: True` for most use cases.
2. **Batch Operations**: Use `create_many()` for better performance when creating multiple [records](../../concepts/records.md).
3. **Nested Data**: Use nested objects and arrays to create related records automatically with proper [relationships](../../concepts/relationships.md).
4. **Transactions**: For operations that need to be atomic, use the optional [transaction](../../concepts/transactions.mdx) parameter.
5. **Data Validation**: Validate your data on the client side before sending it to RushDB.
6. **Label Convention**: Consider using uppercase for [labels](../../concepts/labels.md) (e.g., "PERSON" instead of "Person") for consistency with graph database conventions.
---
# Delete Records
RushDB Python SDK provides methods for deleting [records](../../concepts/records.md) from your database. You can delete individual records by ID or delete multiple records matching specific criteria.
## Overview
The delete methods allow you to:
- Delete a single record by ID
- Delete multiple records using search query filters
- Delete records directly from Record objects
- Perform conditional bulk deletions
## Prerequisites
Before deleting records, make sure you have initialized the RushDB client with your API token:
```python
from rushdb import RushDB
db = RushDB("RUSHDB_API_KEY", base_url="https://api.rushdb.com/api/v1")
```
## Deleting a Single Record by ID
The `delete_by_id()` method allows you to delete a record using its unique identifier.
### Syntax
```python
db.records.delete_by_id(
id_or_ids: Union[str, List[str]],
transaction: Optional[Transaction] = None
) -> Dict[str, str]
```
### Parameters
| Parameter | Type | Description |
|-----------|------|-------------|
| `id_or_ids` | Union[str, List[str]] | Single ID or list of IDs to delete |
| `transaction` | Optional[Transaction] | Optional [transaction](../../concepts/transactions.mdx) object |
### Returns
A dictionary with the response data confirming the deletion.
### Examples
#### Deleting a Single Record
```python
# First, create or retrieve a record
product = db.records.create(
label="PRODUCT",
data={
"name": "Discontinued Item",
"price": 19.99
}
)
# Delete the record by its ID
response = db.records.delete_by_id(product.id)
print(f"Deletion response: {response}")
# Example output: {'message': 'Record deleted successfully'}
```
#### Deleting Multiple Records by ID
```python
# Delete multiple records by their IDs
response = db.records.delete_by_id([
"018e4c71-f35a-7000-89cd-850db63a1e77",
"018e4c75-a2b3-7000-89cd-850db63a1e77",
"018e4c79-c4d5-7000-89cd-850db63a1e77"
])
print(f"Deletion response: {response}")
# Example output: {'message': '3 record(s) deleted successfully'}
```
## Deleting Records with Query Filters
The `delete()` method allows you to delete multiple records that match specific criteria. The search query parameters are consistent across all RushDB APIs and follow the same structure as used in [search operations](../../concepts/search/introduction.md).
### Syntax
```python
db.records.delete(
query: SearchQuery,
transaction: Optional[Transaction] = None
) -> Dict[str, str]
```
### Parameters
| Parameter | Type | Description |
|-----------|------|-------------|
| `query` | SearchQuery | Query to match records for deletion. See [Search Introduction](../../concepts/search/introduction.md) |
| `transaction` | Optional[Transaction] | Optional [transaction](../../concepts/transactions.mdx) object |
### Returns
A dictionary with the response data confirming the deletion.
### Examples
#### Basic Query Deletion
```python
# Delete records matching specific criteria
response = db.records.delete({
"labels": ["PRODUCT"], # See Labels in search: https://docs.rushdb.com/concepts/search/labels
"where": { # See Where clause: https://docs.rushdb.com/concepts/search/where
"price": {"$lt": 10},
"discontinued": True
}
})
print(f"Deletion response: {response}")
# Example output: {'message': '5 record(s) deleted successfully'}
```
#### Advanced Query Deletion
```python
# Delete records with complex conditions using $or operator
response = db.records.delete({
"where": {
"$or": [ # Logical operators as described in Where clause documentation
{
"status": "archived",
"lastModified": {"$lt": "2024-01-01T00:00:00Z"}
},
{
"status": "inactive",
"isTemporary": True
}
]
},
"labels": ["DOCUMENT", "ATTACHMENT"] # Records with either DOCUMENT or ATTACHMENT label
})
print(f"Deletion response: {response}")
# Example output: {'message': '12 record(s) deleted successfully'}
```
## Deleting Records from Record Objects
You can also delete records directly from Record objects.
### Syntax
```python
record.delete(
transaction: Optional[Transaction] = None
) -> Dict[str, str]
```
### Parameters
| Parameter | Type | Description |
|-----------|------|-------------|
| `transaction` | Optional[Transaction] | Optional [transaction](../../concepts/transactions.mdx) object |
### Returns
A dictionary with the response data confirming the deletion.
### Example
```python
# Create a record
user = db.records.create(
label="USER",
data={
"name": "John Doe",
"email": "john@example.com"
}
)
# Perform operations with the record
# ...
# Delete the record when no longer needed
response = user.delete()
print(f"Deletion response: {response}")
# Example output: {'message': 'Record deleted successfully'}
```
## Working with Transactions
For operations that need to be atomic, you can use transactions:
```python
# Start a transaction
tx = db.tx.begin()
try:
# Create records in the transaction
product1 = db.records.create(
label="PRODUCT",
data={"name": "Item 1", "price": 10.99},
transaction=tx
)
product2 = db.records.create(
label="PRODUCT",
data={"name": "Item 2", "price": 20.99},
transaction=tx
)
# Delete the first product
db.records.delete_by_id(
id_or_ids=product1.id,
transaction=tx
)
# Delete other records matching a query
db.records.delete(
query={"labels": ["PRODUCT"], "where": {"discontinued": True}},
transaction=tx
)
# Commit all changes
tx.commit()
except Exception as e:
# If any operation fails, roll back all changes
tx.rollback()
print(f"Transaction failed: {e}")
```
## Handling Relationships
When deleting records, all [relationships](../../concepts/relationships.md) associated with those records are automatically deleted. This ensures database integrity and prevents orphaned relationships.
## Best Practices
1. **Use IDs for specific deletions** when you know exactly which records to remove.
2. **Use queries for conditional deletions** when you need to delete records based on specific criteria.
3. **Use transactions** when deleting multiple related records to ensure data consistency.
4. **Consider performance** for large-scale deletions by using appropriate filters.
5. **Handle exceptions** properly to manage failed delete operations.
6. **Verify deletions** after bulk operations to ensure the expected records were removed.
7. **Use [label filtering](../../concepts/search/labels.md)** to narrow down the scope of deletion operations.
8. **Leverage search operators** from the [Where clause documentation](../../concepts/search/where.md) for precise targeting of records to delete.
9. **Remember that search parameters** are consistent across all RushDB operations, including [find()](../../concepts/search/introduction.md), delete(), and other methods.
---
# Get Records
The Search API is one of the most powerful features of RushDB, allowing you to find records, navigate relationships, and transform results to exactly match your application's needs. This guide demonstrates how to effectively use the Python SDK to search and query data in your RushDB database.
## Direct Record Search
The RushDB Python SDK provides several ways to search for records, from simple lookups to complex queries with filtering, sorting, and pagination.
### Basic Searching with `find()`
The most versatile search method is `find()`, which accepts a search query dictionary to filter, sort, and paginate results.
```python
# Basic search for records with the "USER" label
result = db.records.find({
"labels": ["USER"],
"where": {
"isActive": True
},
"limit": 10,
"orderBy": {"createdAt": "desc"}
})
# Access the returned records
print(f"Found {len(result)} records out of {result.total} total users")
# Iterate over results
for user in result:
print(f"User: {user.get('name', 'Unknown')}")
# Access specific records
first_user = result[0] if result else None
```
Search queries support a powerful and flexible syntax for filtering records. For a detailed explanation of all the available operators and capabilities, see the [Where clause documentation](../../concepts/search/where).
### Finding Records by ID with `find_by_id()`
When you already know the ID of the record(s) you need:
```python
# Find a single record by ID
user = db.records.find_by_id("user-123")
# Find multiple records by ID
users = db.records.find_by_id(["user-123", "user-456", "user-789"])
```
### Relationship Traversal
One of RushDB's most powerful features is the ability to search across relationships between records:
```python
# Find all blog posts by users who work at tech companies
result = db.records.find({
"labels": ["POST"],
"where": {
"USER": { # Traverse to related USER records
"COMPANY": { # Traverse to related COMPANY records
"industry": "Technology"
}
},
"publishedAt": {"$lte": datetime.now()} # Only published posts
},
"orderBy": {"publishedAt": "desc"},
"limit": 20
})
posts = result.data
total = result.total
```
For more complex relationship queries, you can specify relationship types and directions:
```python
# Find users who follow specific topics
result = db.records.find({
"labels": ["USER"],
"where": {
"TOPIC": {
"$relation": {
"type": "FOLLOWS",
"direction": "out" # User -> FOLLOWS -> Topic
},
"name": {"$in": ["Python", "GraphDB", "RushDB"]}
}
}
})
users = result.data
total = result.total
```
See the [Where clause documentation](../../concepts/search/where#relationship-queries) for more details on relationship queries.
### Vector Search
RushDB supports vector similarity searches for AI and machine learning applications:
```python
# Find documents similar to a query embedding
result = db.records.find({
"labels": ["DOCUMENT"],
"where": {
"embedding": {
"$vector": {
"fn": "gds.similarity.cosine", # Similarity function
"query": query_embedding, # Your vector embedding
"threshold": {"$gte": 0.75} # Minimum similarity threshold
}
}
},
"limit": 10
})
documents = result.data
total = result.total
```
See the [Vector operators documentation](../../concepts/search/where#vector-operators) for more details on vector search capabilities.
### Field Existence and Type Checking
RushDB provides operators to check for field existence and data types, which is particularly useful when working with heterogeneous data:
```python
# Find users who have provided an email but not a phone number
email_only_users = db.records.find({
"labels": ["USER"],
"where": {
"$and": [
{"email": {"$exists": True}}, # Must have email
{"phone_number": {"$exists": False}} # Must not have phone number
]
}
})
# Find records where age is actually stored as a number (not string)
proper_age_records = db.records.find({
"labels": ["USER"],
"where": {
"age": {"$type": "number"}
}
})
# Complex query combining type and existence checks
valid_profiles = db.records.find({
"labels": ["PROFILE"],
"where": {
"$and": [
{"bio": {"$type": "string"}}, # Bio must be text
{"bio": {"$contains": "developer"}}, # Bio mentions developer
{"skills": {"$exists": True}}, # Skills must exist
{"avatar": {"$exists": False}} # No avatar uploaded yet
]
}
})
```
The `$exists` operator is useful for:
- Data validation and cleanup
- Finding incomplete profiles
- Filtering by optional fields
The `$type` operator is useful for:
- Working with imported data that might have inconsistent types
- Validating data integrity
- Ensuring type consistency before operations
See the [Field existence operators documentation](../../concepts/search/where#field-existence-operator) for more details.
### Pagination and Sorting
Control the order and volume of results:
```python
# Get the second page of results (20 items per page)
result = db.records.find({
"labels": ["PRODUCT"],
"where": {
"category": "Electronics"
},
"skip": 20, # Skip the first 20 results
"limit": 20, # Return 20 results
"orderBy": {
"price": "asc" # Sort by price ascending
}
})
products = result.data
total_products = result.total
```
For more details on pagination and sorting options, see the [Pagination and ordering documentation](../../concepts/search/pagination-order).
### Aggregations
Transform and aggregate your search results:
```python
# Calculate sales statistics
result = db.records.find({
"labels": ["ORDER"],
"where": {
"status": "completed",
"createdAt": {"$gte": "2023-01-01T00:00:00Z"}
},
"aggregate": {
"totalSales": {
"fn": "sum",
"alias": "$record",
"field": "amount"
},
"orderCount": {
"fn": "count",
"alias": "$record"
},
"avgOrderValue": {
"fn": "avg",
"alias": "$record",
"field": "amount"
}
}
})
stats = result.data
total = result.total
```
For comprehensive details on available aggregation functions and usage, see the [Aggregations documentation](../../concepts/search/aggregations).
### Searching Within a Record's Context
You can search for records within the context of a specific record's relationships using the `record_id` parameter:
```python
# Find all records related to a specific user
result = db.records.find(
search_query={
"labels": ["POST", "COMMENT"],
"where": {
"isPublished": True
}
},
record_id="user_123" # Search within this user's context
)
related_records = result.data
total = result.total
# Find only posts created by a specific user
result = db.records.find(
search_query={
"labels": ["POST"],
"orderBy": {"createdAt": "desc"}
},
record_id="user_123"
)
user_posts = result.data
# Search for documents shared with a specific team
result = db.records.find(
search_query={
"labels": ["DOCUMENT"],
"where": {
"status": "shared",
"category": {"$in": ["proposal", "contract"]}
}
},
record_id="team_456"
)
team_documents = result.data
```
This is particularly useful when you want to:
- Find all records that have relationships with a specific record
- Search within the scope of a particular entity's connected data
- Implement features like "user's posts", "team's documents", or "company's projects"
## Return Format and Error Handling
The `find()` method returns a [`SearchResult`](../python-reference/search-result.md) object that provides list-like access and comprehensive metadata:
```python
# The method returns a SearchResult object
result = db.records.find({
"labels": ["USER"],
"limit": 10
})
# len(result) = records in this result set (affected by limit)
print(f"Retrieved {len(result)} records in this page")
# total = all records matching criteria in the entire database
print(f"Total matching records in database: {result.total}")
# Example: if you have 1,000 users total but limit to 10:
# len(result) = 10 (records returned in this request)
# result.total = 1000 (total users matching your criteria)
# Iterate over results
for record in result:
print(f"User: {record.get('name')}")
# Access records by index
first_user = result[0] if result else None
# Check if there are more records beyond this page
if result.has_more:
print("There are more records available")
# Handle cases where no records are found
if not result:
print("No records found matching the criteria")
# Use total count for pagination calculations
pages = (result.total + 9) // 10 # Calculate number of pages (10 per page)
```
### Understanding Total vs Length
It's important to understand the difference between these two key concepts:
- **`result.total`** - The total number of records in your database that match your search criteria
- **`len(result)`** - The number of records actually returned in this specific request (limited by `limit` parameter)
```python
# Example: searching users in a database with 10,000 total users
result = db.records.find({
"labels": ["USER"],
"where": {"active": True}, # Let's say 8,500 users are active
"limit": 25 # But we only want 25 per page
})
print(f"Records in this page: {len(result)}") # Will show: 25
print(f"Total active users: {result.total}") # Will show: 8,500
print(f"Has more pages: {result.has_more}") # Will show: True
# This is useful for building pagination UIs:
current_page = 1
per_page = 25
total_pages = (result.total + per_page - 1) // per_page # = 340 pages
print(f"Page {current_page} of {total_pages}")
```
### Error Handling
The `find()` method includes built-in error handling that returns an empty SearchResult on exceptions:
```python
# If an error occurs, the method returns an empty SearchResult instead of raising an exception
result = db.records.find({
"labels": ["INVALID_LABEL"],
"where": {
"nonexistent_field": "some_value"
}
})
# Always returns a SearchResult, even on errors
print(f"Found {len(result)} records") # Will print "Found 0 records"
print(f"Total: {result.total}") # Will print "Total: 0"
# Safe iteration
for record in result:
print("This won't execute if result is empty")
# Boolean check is safe
if result:
print("This won't execute if result is empty")
# For more explicit error handling in production code, you may want to validate
# your queries before calling find() or implement additional error checking
```
## Search Within Transactions
All search operations can be performed within transactions for consistency:
```python
# Begin a transaction
tx = db.tx.begin()
try:
# Perform search within the transaction
result = db.records.find({
"labels": ["USER"],
"where": {"is_active": True}
}, transaction=tx)
# Use the results to make changes
for user in result:
if user.last_login < older_than_3_months:
db.records.update({
"target": user,
"data": {"is_active": False}
}, transaction=tx)
# Commit the transaction when done
tx.commit()
except Exception as error:
# Roll back the transaction on error
tx.rollback()
raise error
```
For more details on transactions, see the [Transactions documentation](../../python-sdk/transactions).
## Performance Best Practices
When working with the Search API, follow these best practices for optimal performance:
1. **Be Specific with Labels**: Always specify labels to narrow the search scope.
2. **Use Indexed Properties**: Prioritize filtering on properties that have indexes.
3. **Limit Results**: Use pagination to retrieve only the records you need.
4. **Optimize Relationship Traversal**: Avoid deep relationship traversals when possible.
5. **Use Aliases Efficiently**: Define aliases only for records you need to reference in aggregations.
6. **Filter Early**: Apply filters as early as possible in relationship traversals to reduce the amount of data processed.
## Next Steps
- Explore [filtering with where clauses](../../concepts/search/where) in depth
- Learn about [data aggregation capabilities](../../concepts/search/aggregations)
- Understand [pagination and sorting options](../../concepts/search/pagination-order)
- Discover how to filter by [record labels](../../concepts/search/labels)
- Learn about the [`SearchResult](../python-reference/search-result.md) class returned by find operations
- See how to use [Records API](../../python-sdk/records/create-records.md) for other operations
---
# Import Data
The RushDB Python SDK provides powerful methods for importing data into your database. You can import data from various formats including JSON and CSV, with options to customize how the data is processed and stored.
## Overview
The import functionality in the Python SDK allows you to:
- Import JSON data structures
- Import CSV data from files or strings
- Control data type inference and handling
- Set default relationship types
- Configure property value handling
## Importing CSV Data
### import_csv()
Imports records from CSV data into RushDB.
**Signature:**
```python
def import_csv(
self,
label: str,
data: str,
options: Optional[Dict[str, bool]] = None,
transaction: Optional[Transaction] = None
) -> List[Dict[str, Any]]
```
**Arguments:**
- `label` (str): Label for all imported records
- `csv_data` (str): CSV data to import as a string
- `options` (Optional[Dict[str, bool]]): Import options
- `suggestTypes` (bool): When true, automatically infers data types for properties
- `castNumberArraysToVectors` (bool): When true, converts numeric arrays to vector type
- `convertNumericValuesToNumbers` (bool): When true, converts string numbers to number type
- `capitalizeLabels` (bool): When true, converts all labels to uppercase
- `relationshipType` (str): Default relationship type between nodes
- `returnResult` (bool): When true, returns imported records in response
- `transaction` (Optional[Transaction]): Optional transaction object
**Returns:**
- `List[Dict[str, Any]]`: Imported records data (if returnResult is True)
**Example:**
```python
# Import records from CSV string
csv_data = """name,email,age
John Doe,john@example.com,30
Jane Smith,jane@example.com,25
Bob Wilson,bob@example.com,45"""
records = client.records.import_csv(
label="CUSTOMER",
csv_data=csv_data,
options={
"returnResult": True,
"suggestTypes": True,
"convertNumericValuesToNumbers": True
}
)
# Import records from CSV file
with open('employees.csv', 'r') as file:
csv_content = file.read()
records = client.records.import_csv(
label="EMPLOYEE",
csv_data=csv_content,
options={"returnResult": True, "suggestTypes": True}
)
```
## Importing JSON Data
### create_many()
Imports records from JSON data into RushDB.
**Signature:**
```python
def create_many(
self,
label: str,
data: Union[Dict[str, Any], List[Dict[str, Any]]],
options: Optional[Dict[str, Any]] = None,
transaction: Optional[Transaction] = None
) -> List[Dict[str, Any]]
```
**Arguments:**
- `label` (str): Label for the root node(s)
- `data` (Union[Dict[str, Any], List[Dict[str, Any]]]): JSON data to import as dict or find of dicts
- `options` (Optional[Dict[str, Any]]): Import options
- `suggestTypes` (bool): When true, automatically infers data types for properties
- `castNumberArraysToVectors` (bool): When true, converts numeric arrays to vector type
- `convertNumericValuesToNumbers` (bool): When true, converts string numbers to number type
- `capitalizeLabels` (bool): When true, converts all labels to uppercase
- `relationshipType` (str): Default relationship type between nodes
- `returnResult` (bool): When true, returns imported records in response
- `transaction` (Optional[Transaction]): Optional transaction object
**Returns:**
- `List[Dict[str, Any]]`: Imported records data (if returnResult is True)
**Example:**
```python
# Import a single JSON object
person_data = {
"name": "John Doe",
"age": "30",
"addresses": [
{
"type": "home",
"street": "123 Main St",
"city": "Anytown"
},
{
"type": "work",
"street": "456 Business Rd",
"city": "Workville"
}
],
"scores": [85, 90, 95],
"active": True
}
records = client.records.create_many(
label="PERSON",
data=person_data,
options={
"returnResult": True,
"suggestTypes": True,
"convertNumericValuesToNumbers": True,
"relationshipType": "OWNS"
}
)
# Import multiple JSON objects
employees_data = [
{
"name": "Alice Johnson",
"department": "Engineering",
"skills": ["Python", "JavaScript", "AWS"]
},
{
"name": "Bob Smith",
"department": "Marketing",
"skills": ["SEO", "Content Writing", "Analytics"]
}
]
records = client.records.create_many(
label="EMPLOYEE",
data=employees_data,
options={"returnResult": True, "suggestTypes": True}
)
```
## Data Type Handling
When the `suggestTypes` option is enabled, RushDB will infer the following types:
- `string`: Text values
- `number`: Number values (& numeric values when `convertNumericValuesToNumbers` is true)
- `boolean`: True/false values
- `null`: Null values
- `vector`: Arrays of numbers (when `castNumberArraysToVectors` is true)
- `datetime`: ISO8601 format strings (e.g., "2025-04-23T10:30:00Z") will be automatically cast to datetime values
When `convertNumericValuesToNumbers` is enabled, string values that represent numbers (e.g., '123') will be automatically converted to their numeric equivalents (e.g., 123).
Arrays with consistent data types (e.g., all numbers, all strings) will be handled seamlessly according to their type. However, for inconsistent arrays (e.g., `[1, 'two', None, False]`), all values will be automatically converted to strings to mitigate data loss, and the property type will be stored as `string`.
## Graph Construction
When importing nested JSON data, RushDB automatically creates relationships between parent and child nodes. For example, if you import a person with addresses, RushDB will create:
1. A node with the "PERSON" label for the person data
2. Nodes with the "ADDRESS" label for each address
3. Relationships from the person to each address (using the default relationship type or the one specified)
This allows you to maintain complex data structures in a graph database format without manually creating the relationships.
## Performance Considerations
- For large imports, consider setting `returnResult: False` to improve performance
- Imports are processed in batches for optimal database performance
- Consider using transactions for large imports to ensure data consistency
- For very large datasets (millions of records), consider breaking the import into multiple smaller operations
---
# Update Records
RushDB Python SDK provides two main methods for updating [records](../../concepts/records.md): `update()` for partial updates and `set()` for complete replacement of record data.
## Overview
The update methods allow you to:
- Update specific properties while preserving others (`update()`)
- Completely replace record data (`set()`)
- Apply changes either through the RecordsAPI or directly on Record objects
## Prerequisites
Before updating records, make sure you have initialized the RushDB client with your API token:
```python
from rushdb import RushDB
db = RushDB("RUSHDB_API_KEY", base_url="https://api.rushdb.com/api/v1")
```
## Updating Records with `update()`
The `update()` method allows you to modify specific properties of a record while preserving other existing properties.
### Syntax
```python
# Using RecordsAPI
db.records.update(
record_id: str,
data: Dict[str, Any],
transaction: Optional[Transaction] = None
) -> Dict[str, str]
# Using Record object
record.update(
data: Dict[str, Any],
transaction: Optional[Transaction] = None
) -> Dict[str, str]
```
### Parameters
| Parameter | Type | Description |
|-----------|------|-------------|
| `record_id` | str | ID of the [record](../../concepts/records.md) to update (when using RecordsAPI) |
| `data` | Dict[str, Any] | Partial record data containing only the properties to update |
| `transaction` | Optional[Transaction] | Optional [transaction](../../concepts/transactions.mdx) object |
### Returns
A dictionary with the response data confirming the update.
### Examples
#### Using RecordsAPI
```python
# First, create or retrieve a record
person = db.records.create(
label="PERSON",
data={
"name": "John Doe",
"age": 30,
"email": "john@example.com",
"active": True
}
)
# Later, update specific properties using the record's ID
response = db.records.update(
record_id=person.id,
data={
"age": 31,
"title": "Senior Developer",
"active": False
}
)
# The record now contains both original and updated properties:
# name: "John Doe" (preserved)
# age: 31 (updated)
# email: "john@example.com" (preserved)
# active: False (updated)
# title: "Senior Developer" (added)
```
#### Using Record Object
```python
# If you have a record object, you can update it directly
person = db.records.create(
label="PERSON",
data={
"name": "Jane Smith",
"age": 28,
"department": "Engineering"
}
)
# Update the record
response = person.update({
"age": 29,
"department": "Product",
"role": "Product Manager"
})
# The record now contains:
# name: "Jane Smith" (preserved)
# age: 29 (updated)
# department: "Product" (updated)
# role: "Product Manager" (added)
```
## Replacing Records with `set()`
The `set()` method completely replaces all properties of a record with new data.
### Syntax
```python
# Using RecordsAPI
db.records.set(
record_id: str,
data: Dict[str, Any],
transaction: Optional[Transaction] = None
) -> Dict[str, str]
# Using Record object
record.set(
data: Dict[str, Any],
transaction: Optional[Transaction] = None
) -> Dict[str, str]
```
### Parameters
| Parameter | Type | Description |
|-----------|------|-------------|
| `record_id` | str | ID of the [record](../../concepts/records.md) to replace (when using RecordsAPI) |
| `data` | Dict[str, Any] | New record data that will completely replace existing properties |
| `transaction` | Optional[Transaction] | Optional [transaction](../../concepts/transactions.mdx) object |
### Returns
A dictionary with the response data confirming the replacement.
### Examples
#### Using RecordsAPI
```python
# First, create or retrieve a record
product = db.records.create(
label="PRODUCT",
data={
"name": "Smartphone X",
"price": 899.99,
"category": "Electronics",
"features": ["5G", "Water Resistant"]
}
)
# Later, completely replace the record data
response = db.records.set(
record_id=product.id,
data={
"name": "Smartphone X Pro",
"price": 1099.99,
"inStock": True,
"specifications": {
"storage": "256GB",
"color": "Midnight Blue"
}
}
)
# The record now ONLY contains the new properties:
# name: "Smartphone X Pro"
# price: 1099.99
# inStock: True
# specifications: { storage: "256GB", color: "Midnight Blue" }
# Note: "category" and "features" properties are removed
```
#### Using Record Object
```python
# If you have a record object, you can replace it directly
product = db.records.create(
label="PRODUCT",
data={
"name": "Laptop Basic",
"price": 699.99,
"category": "Computer"
}
)
# Replace all record data
response = product.set({
"name": "Laptop Pro",
"price": 1299.99,
"memory": "16GB",
"processor": "i7"
})
# The record now ONLY contains:
# name: "Laptop Pro"
# price: 1299.99
# memory: "16GB"
# processor: "i7"
# Note: "category" property is removed
```
## Working with Transactions
For operations that need to be atomic, you can use transactions:
```python
# Start a transaction
tx = db.tx.begin()
try:
# Update multiple records in the same transaction
product1 = db.records.create(
label="PRODUCT",
data={"name": "Item 1", "price": 10.99},
transaction=tx
)
product2 = db.records.create(
label="PRODUCT",
data={"name": "Item 2", "price": 20.99},
transaction=tx
)
# Update first product
db.records.update(
record_id=product1.id,
data={"price": 11.99, "featured": True},
transaction=tx
)
# Replace second product
db.records.set(
record_id=product2.id,
data={"name": "Item 2 Pro", "price": 29.99, "featured": True},
transaction=tx
)
# Commit all changes
tx.commit()
except Exception as e:
# If any operation fails, roll back all changes
tx.rollback()
print(f"Transaction failed: {e}")
```
## Best Practices
1. **Use `update()` for partial updates** when you want to preserve existing data.
2. **Use `set()` for complete replacement** when you want to ensure the record only has the properties you specify.
3. **Use Record objects directly** for more concise code when you already have a reference to the record.
4. **Use transactions** when updating multiple related records to ensure data consistency.
5. **Validate data** on the client side before sending update requests.
6. **Handle exceptions** properly to manage failed update operations.
---
# Relationships
[Relationships](../concepts/relationships.md) in RushDB connect records to form a rich, interconnected network of data. The Python SDK provides powerful methods for creating, managing, and traversing relationships between records.
## Overview
Relationships in RushDB enable you to:
- Connect related records
- Model complex domain relationships
- Query data based on connections
- Build graph-like data structures
- Navigate between connected entities
## Prerequisites
Before working with relationships, make sure you have initialized the RushDB client with your API token:
```python
from rushdb import RushDB
db = RushDB("RUSHDB_API_KEY", base_url="https://api.rushdb.com/api/v1")
```
## Creating Records with Relationships
When creating records, you can automatically establish relationships through nested objects:
```python
# Create a company with departments and employees
company_data = {
"name": "Acme Inc.",
"founded": "2010-01-15T00:00:00Z",
"departments": [ # This creates relationships to DEPARTMENT records
{
"name": "Engineering",
"employees": [ # This creates relationships to EMPLOYEE records
{
"name": "Alice Chen",
"position": "Senior Developer"
},
{
"name": "Bob Smith",
"position": "QA Engineer"
}
]
},
{
"name": "Marketing",
"employees": [
{
"name": "Carol Davis",
"position": "Marketing Director"
}
]
}
]
}
# Create the company with all related records
records = db.records.create_many(
label="COMPANY",
data=company_data,
options={
"relationshipType": "HAS_DEPARTMENT", # Custom relationship type for departments
"returnResult": True
}
)
```
## Explicitly Creating Relationships with attach()
You can also explicitly create relationships between existing records using the `attach()` method:
### Using RecordsAPI
```python
# Create two separate records
user = db.records.create(
label="USER",
data={"name": "John Doe", "email": "john@example.com"}
)
project = db.records.create(
label="PROJECT",
data={"name": "Website Redesign", "deadline": "2025-06-30T00:00:00Z"}
)
# Create a relationship between them
response = db.records.attach(
source=user.id,
target=project.id,
options={
"type": "MANAGES", # Relationship type
"direction": "out" # Relationship direction (user -> project)
}
)
```
### Using Record Objects Directly
```python
# Create two separate records
team = db.records.create(
label="TEAM",
data={"name": "Frontend Team", "size": 5}
)
employee = db.records.create(
label="EMPLOYEE",
data={"name": "Alice Johnson", "role": "Developer"}
)
# Create a relationship from the team to the employee
response = team.attach(
target=employee,
options={
"type": "INCLUDES",
"direction": "out" # From team to employee
}
)
```
## Creating Multiple Relationships at Once
```python
# Create a manager record
manager = db.records.create(
label="MANAGER",
data={"name": "Sarah Williams", "department": "Engineering"}
)
# Create multiple employee records
employees = db.records.create_many(
label="EMPLOYEE",
data=[
{"name": "John Smith", "skills": ["Python", "JavaScript"]},
{"name": "Jane Brown", "skills": ["Java", "SQL"]},
{"name": "Mike Davis", "skills": ["React", "TypeScript"]}
]
)
# Create relationships from the manager to all employees at once
response = manager.attach(
target=[emp.id for emp in employees],
options={
"type": "MANAGES",
"direction": "out"
}
)
```
## Bulk Relationship Creation by Key Match
When importing tabular data in separate steps, you can create relationships in bulk by matching a key on the source label to a key on the target label. Use `relationships.create_many` for this.
```python
# Create USER -[:ORDERED]-> ORDER for all pairs where
# USER.id = ORDER.userId and both belong to the same tenant
tenant_id = "ACME"
db.relationships.create_many(
source={"label": "USER", "key": "id", "where": {"tenantId": tenant_id}},
target={"label": "ORDER", "key": "userId", "where": {"tenantId": tenant_id}},
type="ORDERED",
direction="out" # (source) -[:ORDERED]-> (target)
)
```
Parameters
- `source`: Dict describing the source side
- `label` (str): Source record label
- `key` (str): Property on the source used for equality match
- `where` (optional, dict): Additional filters for source records; same shape as SearchQuery `where`
- `target`: Dict describing the target side
- `label` (str): Target record label
- `key` (str): Property on the target used for equality match
- `where` (optional, dict): Additional filters for target records; same shape as SearchQuery `where`
- `type` (optional, str): Relationship type. Defaults to the RushDB default type when omitted
- `direction` (optional, str): 'in' or 'out'. Defaults to 'out'
- `transaction` (optional): Include to run the operation atomically
Notes
- The join condition is always `source[key] = target[key]` combined with any additional `where` constraints.
- `where` follows the same operators as record search (e.g., `{"tenantId": "ACME"}` or `{"tenantId": "ACME"}`).
- This is efficient for connecting data created in separate imports (e.g., users and orders).
Many-to-many (cartesian) creation
If you omit `key` on both `source` and `target` you can opt-in to a many-to-many (cartesian) creation by passing `many_to_many=True`. This will create relationships between every matching source and every matching target produced by the provided `where` filters.
Important safeguards
- `many_to_many=True` requires non-empty `where` filters for both `source` and `target` to avoid accidentally creating an unbounded cartesian product.
- By default (when `many_to_many` is omitted or false) the server requires `source["key"]` and `target["key"]` to be provided and will join using `source[key] = target[key]`.
- Many-to-many operations can create large numbers of relationships and may be expensive; use specific filters and limits in your `where` clauses.
Example: key-based join (same as above)
```python
db.relationships.create_many(
source={"label": "USER", "key": "id", "where": {"tenantId": tenant_id}},
target={"label": "ORDER", "key": "userId", "where": {"tenantId": tenant_id}},
type="ORDERED",
direction="out"
)
```
Example: explicit many-to-many (cartesian) creation — opt-in
```python
# Create every USER_MTM × TAG_MTM link where tenantId matches
db.relationships.create_many(
source={"label": "USER_MTM", "where": {"tenantId": tenant_id}},
target={"label": "TAG_MTM", "where": {"tenantId": tenant_id}},
type="HAS_TAG",
direction="out",
many_to_many=True
)
```
Deleting relationships in bulk
The API also supports deleting relationships created by a matching condition. Use `relationships.delete_many` with the same arguments as `create_many` to remove relationships in bulk.
Examples mirror the creation API:
Key-based deletion
```python
db.relationships.delete_many(
source={"label": "USER", "key": "id", "where": {"tenantId": tenant_id}},
target={"label": "ORDER", "key": "userId", "where": {"tenantId": tenant_id}},
type="ORDERED",
direction="out"
)
```
Many-to-many deletion
```python
db.relationships.delete_many(
source={"label": "USER_MTM", "where": {"tenantId": tenant_id}},
target={"label": "TAG_MTM", "where": {"tenantId": tenant_id}},
type="HAS_TAG",
direction="out",
many_to_many=True
)
```
## Removing Relationships with detach()
You can remove relationships between records without deleting the records themselves using the `detach()` method:
### Using RecordsAPI
```python
# Remove a specific relationship type
db.records.detach(
source=user.id,
target=project.id,
options={
"typeOrTypes": "MANAGES",
"direction": "out"
}
)
# Remove multiple relationship types at once
db.records.detach(
source=manager.id,
target=employee.id,
options={
"typeOrTypes": ["MANAGES", "MENTORS"],
"direction": "out"
}
)
```
### Using Record Objects Directly
```python
# Remove a relationship directly from a record object
team.detach(
target=employee,
options={
"typeOrTypes": "INCLUDES",
"direction": "out"
}
)
```
## Finding Related Records
You can find records based on their relationships:
```python
# Find all employees of a specific department
result = db.records.find({
"labels": ["EMPLOYEE"],
"where": {
"_in": { # Use _in to find incoming relationships
"relation": "WORKS_IN", # Relationship type
"source": department.id # The source record ID
}
}
})
employees = result.data
# Find all projects managed by a specific user
result = db.records.find({
"labels": ["PROJECT"],
"where": {
"_in": {
"relation": "MANAGES",
"source": user.id
}
}
})
projects = result.data
```
## Using Custom Relationship Types
By default, RushDB uses a standard relationship type, but you can specify custom types:
```python
# When creating records with nested objects
company = db.records.create_many(
label="COMPANY",
data={
"name": "Tech Corp",
"employees": [
{"name": "Jane Smith", "position": "CTO"},
{"name": "John Doe", "position": "Lead Developer"}
]
},
options={
"relationshipType": "EMPLOYS" # Custom relationship type
}
)
# When explicitly creating relationships
db.records.attach(
source=mentor.id,
target=mentee.id,
options={
"type": "MENTORS",
"direction": "out"
}
)
```
## Searching and Querying Relationships with RelationsAPI
The `RelationsAPI` provides dedicated functionality for searching and analyzing relationships directly. This API allows you to query relationships themselves rather than records, giving you insights into the connections within your graph.
**Important**: The RelationsAPI uses a Record-centric approach. When filtering relationships, you specify properties of the records involved in those relationships, not properties of the relationships themselves. This means the `where` clause contains Record properties to find relationships involving records that match those criteria.
### Overview
The RelationsAPI enables you to:
- Search for specific relationships based on criteria
- Analyze relationship patterns across your data
- Discover connections between records
- Perform relationship-based analytics
- Monitor relationship types and their usage
### Accessing the RelationsAPI
You access the RelationsAPI through the main RushDB client:
```python
from rushdb import RushDB
# Initialize the client
db = RushDB("RUSHDB_API_KEY", base_url="https://api.rushdb.com/api/v1")
# Access the relationships API
relationships_api = db.relationships
```
### The find() Method
The `find()` method is the primary way to search for relationships in your database. It supports the same powerful SearchQuery pattern used throughout RushDB.
#### Method Signature
```python
async def find(
self,
search_query: Optional[SearchQuery] = None,
pagination: Optional[PaginationParams] = None,
transaction: Optional[Union[Transaction, str]] = None,
) -> List[Relationship]
```
#### Parameters
- **search_query** (`Optional[SearchQuery]`): Search criteria to filter relationships. Uses a Record-centric approach where the `where` clause contains Record properties to find relationships involving records that match those criteria:
- `where`: Filter conditions for record properties (not relationship properties)
- `labels`: Filter by record labels to find relationships involving specific record types
- `orderBy`: Sort relationships by various criteria
- **pagination** (`Optional[PaginationParams]`): Control result set size and pagination:
- `limit` (int): Maximum number of relationships to return (default: 100, max: 1000)
- `skip` (int): Number of relationships to skip for pagination (default: 0)
- **transaction** (`Optional[Union[Transaction, str]]`): Optional transaction context for the operation
#### Return Value
Returns a `List[Relationship]` containing relationship objects that match the search criteria. Each relationship object contains information about the source record, target record, relationship type, and direction.
### Basic Relationship Searching
#### Find All Relationships
```python
# Get all relationships in the database
all_relationships = await db.relationships.find()
print(f"Total relationships: {len(all_relationships)}")
for rel in all_relationships[:5]: # Show first 5
print(f"{rel.sourceId} -> {rel.targetId} ({rel.type})")
```
#### Find Relationships with Pagination
```python
# Get relationships with pagination
pagination_params = {
"limit": 50,
"skip": 0
}
first_page = await db.relationships.find(pagination=pagination_params)
# Get next page
next_page_params = {
"limit": 50,
"skip": 50
}
second_page = await db.relationships.find(pagination=next_page_params)
```
### Advanced Relationship Queries
#### Filter by Record Properties
The RelationsAPI uses a Record-centric approach. You specify record properties in the `where` clause to find relationships involving records that match those criteria:
```python
# Find relationships involving active users in Engineering department
engineering_relationships = await db.relationships.find({
"where": {
"isActive": True,
"department": "Engineering"
}
})
# Find relationships involving large technology companies
tech_company_relationships = await db.relationships.find({
"where": {
"industry": "Technology",
"employees": {"$gte": 100}
}
})
# Find relationships involving records with specific labels
user_relationships = await db.relationships.find({
"labels": ["USER"] # Only find relationships involving USER records
})
# Find relationships involving records matching complex criteria
senior_dev_relationships = await db.relationships.find({
"where": {
"role": "Developer",
"experience": {"$gte": 5},
"isActive": True
}
})
```
#### Complex Relationship Queries
```python
# Find relationships involving engineering employees who are active
engineering_relationships = await db.relationships.find({
"where": {
"$and": [
{"department": "Engineering"},
{"isActive": True},
{"role": {"$in": ["Developer", "QA Engineer", "DevOps"]}}
]
},
"orderBy": {"name": "asc"}
})
# Find relationships involving recently created records
recent_record_relationships = await db.relationships.find({
"where": {
"createdAt": {"$gte": "2024-01-01T00:00:00Z"}
},
"orderBy": {"createdAt": "desc"}
}, pagination={"limit": 25, "skip": 0})
# Find relationships involving records with specific labels and properties
manager_relationships = await db.relationships.find({
"labels": ["MANAGER"],
"where": {
"department": "Engineering",
"teamSize": {"$gte": 5}
}
})
```
### Relationship Analytics and Insights
#### Count Relationships by Type
```python
# Get all relationships and analyze by type
all_relationships = await db.relationships.find()
# Count by type
type_counts = {}
for rel in all_relationships:
rel_type = rel.type
type_counts[rel_type] = type_counts.get(rel_type, 0) + 1
print("Relationship types and counts:")
for rel_type, count in sorted(type_counts.items()):
print(f" {rel_type}: {count}")
```
#### Find Highly Connected Records
```python
# Find relationships involving all records first
all_relationships = await db.relationships.find()
# Count outgoing relationships per record
outgoing_counts = {}
for rel in all_relationships:
source_id = rel.sourceId
outgoing_counts[source_id] = outgoing_counts.get(source_id, 0) + 1
# Find top 10 most connected records
top_connected = sorted(outgoing_counts.items(), key=lambda x: x[1], reverse=True)[:10]
print("Most connected records (outgoing):")
for record_id, count in top_connected:
print(f" {record_id}: {count} relationships")
# Alternative: Find relationships for specific high-activity records
manager_relationships = await db.relationships.find({
"labels": ["MANAGER"],
"where": {
"isActive": True,
"teamSize": {"$gte": 10} # Managers with large teams likely have many relationships
}
})
```
### Using Relationships API with Transactions
The RelationsAPI supports transactions for consistent querying:
```python
# Start a transaction
tx = db.tx.begin()
try:
# Query relationships involving records in Sales department within the transaction
relationships = await db.relationships.find({
"where": {
"department": "Sales"
}
}, transaction=tx)
# Perform additional operations in the same transaction
for rel in relationships:
# Update related records or create new relationships
pass
# Commit the transaction
tx.commit()
except Exception as e:
# Roll back on error
tx.rollback()
print(f"Transaction failed: {e}")
```
### Pagination Best Practices
When working with large numbers of relationships, use pagination effectively:
```python
# Process relationships in batches
async def process_all_relationships(batch_size=100):
skip = 0
processed_count = 0
while True:
# Get next batch
relationships = await db.relationships.find(
pagination={"limit": batch_size, "skip": skip}
)
if not relationships:
break # No more relationships
# Process this batch
for rel in relationships:
# Process individual relationship
processed_count += 1
print(f"Processing relationship {rel.sourceId} -> {rel.targetId}")
# Move to next batch
skip += batch_size
print(f"Processed {processed_count} relationships so far...")
print(f"Finished processing {processed_count} total relationships")
# Run the batch processor
await process_all_relationships()
```
### Performance Considerations
When using the RelationsAPI:
1. **Use specific filters**: Apply `where` conditions to reduce the result set
2. **Limit result sizes**: Use pagination to avoid loading too many relationships at once
3. **Filter by relationship type**: Use type filters when you know the specific relationship types you need
4. **Index frequently queried properties**: Ensure properties used in filters are indexed
5. **Combine with record queries**: Use RelationsAPI to discover connections, then use RecordsAPI for detailed record data
### Error Handling
```python
try:
relationships = await db.relationships.find({
"where": {"department": "InvalidDepartment"}
})
except Exception as e:
print(f"Error querying relationships: {e}")
# Handle the error appropriately
```
### Integration with Record Operations
The RelationsAPI works seamlessly with record operations:
```python
# 1. Discover relationships involving specific types of records
management_rels = await db.relationships.find({
"labels": ["MANAGER"],
"where": {
"department": "Engineering",
"isActive": True
}
})
# 2. Extract record IDs from relationships
manager_ids = [rel.sourceId for rel in management_rels]
employee_ids = [rel.targetId for rel in management_rels]
# 3. Query the actual records using RecordsAPI for detailed information
managers = db.records.find_by_id(manager_ids)
employees = db.records.find_by_id(employee_ids)
# 4. Combine data for analysis
for rel in management_rels:
manager = next(m for m in managers if m.id == rel.sourceId)
employee = next(e for e in employees if e.id == rel.targetId)
print(f"{manager.name} manages {employee.name}")
```
## Relationship Direction
Relationships in RushDB have direction. You can specify the direction when creating or querying relationships:
- `"out"` - Relationship goes from source to target (source → target)
- `"in"` - Relationship goes from target to source (target → source)
```python
# Creating an outgoing relationship (source → target)
user.attach(
target=group,
options={
"type": "BELONGS_TO",
"direction": "out"
}
)
# Creating an incoming relationship (target → source)
project.attach(
target=employee,
options={
"type": "WORKS_ON",
"direction": "in" # Employee → Project
}
)
```
## Working with Transactions
For operations that need to be atomic, you can use [transactions](../concepts/transactions.mdx) when creating or modifying relationships:
```python
# Start a transaction
tx = db.tx.begin()
try:
# Create records in the transaction
team = db.records.create(
label="TEAM",
data={"name": "Product Team"},
transaction=tx
)
member1 = db.records.create(
label="EMPLOYEE",
data={"name": "Alice"},
transaction=tx
)
member2 = db.records.create(
label="EMPLOYEE",
data={"name": "Bob"},
transaction=tx
)
# Create relationships in the same transaction
team.attach(
target=[member1, member2],
options={"type": "HAS_MEMBER"},
transaction=tx
)
# Commit all changes
tx.commit()
except Exception as e:
# If any operation fails, roll back all changes
tx.rollback()
print(f"Transaction failed: {e}")
```
## Best Practices for Working with Relationships
1. **Use meaningful relationship types** - Choose relationship types that clearly express the connection's nature (e.g., "MANAGES", "BELONGS_TO")
2. **Consider relationship direction** - Think about which way the relationship should point based on your domain model
3. **Use nested objects for hierarchical data** - When creating hierarchical data, structure your JSON to reflect the relationships
4. **Create relationships in transactions** - Use transactions when creating multiple related records to ensure data consistency
5. **Be consistent with relationship types** - Use the same relationship types for similar connections throughout your application
6. **Think in graphs** - Approach relationships as a graph model, considering paths between records
7. **Balance denormalization and relationships** - In some cases, it may be better to duplicate data rather than create complex relationship chains
8. **Use RelationsAPI for analysis** - Use the dedicated RelationsAPI for relationship analytics and discovery
9. **Optimize relationship queries** - Use appropriate filters and pagination when querying large numbers of relationships
10. **Combine APIs effectively** - Use RelationsAPI to discover connections, then RecordsAPI for detailed record data
## API Reference
### PaginationParams
The `PaginationParams` TypedDict defines the structure for pagination options when querying relationships:
```python
from typing import TypedDict
class PaginationParams(TypedDict, total=False):
"""TypedDict for pagination parameters in relationship queries.
Defines the structure for pagination options when querying relationships,
allowing for efficient retrieval of large result sets.
"""
limit: int # Maximum number of relationships to return in a single request
skip: int # Number of relationships to skip from the beginning of the result set
```
#### Parameters
- **limit** (`int`): Maximum number of relationships to return in a single request
- Default: 100
- Maximum: 1000
- Used for controlling the size of result sets and implementing pagination
- **skip** (`int`): Number of relationships to skip from the beginning of the result set
- Default: 0
- Used for implementing pagination by skipping already retrieved items
- Useful for getting subsequent pages of results
#### Usage Example
```python
# Define pagination parameters
pagination = PaginationParams(
limit=50, # Return at most 50 relationships
skip=100 # Skip the first 100 relationships
)
# Use with the find method
relationships = await db.relationships.find(
search_query={"where": {"isActive": True}},
pagination=pagination
)
```
### Relationship Object
When you query relationships using the RelationsAPI, you receive `Relationship` objects with the following structure:
```python
# Example Relationship object attributes
relationship = relationships[0]
print(f"Source ID: {relationship.sourceId}") # ID of the source record
print(f"Source Label: {relationship.sourceLabel}") # Label of the source record
print(f"Target ID: {relationship.targetId}") # ID of the target record
print(f"Target Label: {relationship.targetLabel}") # Label of the target record
print(f"Type: {relationship.type}") # Relationship type (e.g., "MANAGES")
print(f"Direction: {relationship.direction}") # Relationship direction
```
## Related Documentation
- [Relationships Concept](../concepts/relationships.md) - Learn more about how relationships work in RushDB
- [Transactions](../concepts/transactions.mdx) - Using transactions for relationship consistency
- [Record Creation](./records/create-records.md) - Creating records with relationships
- [Finding Records](./records/get-records.md) - Search techniques including relationship-based queries
---
# Transactions
[Transactions](../concepts/transactions.mdx) in RushDB ensure data consistency by grouping multiple operations into a single atomic unit. The Python SDK provides a simple and powerful way to work with transactions, allowing you to perform multiple related operations with guaranteed consistency.
## Overview
Transactions in RushDB enable you to:
- Perform multiple operations as a single atomic unit
- Ensure data consistency across related records
- Roll back changes automatically if any operation fails
- Prevent partial updates that could leave your data in an inconsistent state
## Working with Transactions
The RushDB Python SDK offers two ways to work with transactions:
### 1. Explicit Transaction Management
```python
from rushdb import RushDB
db = RushDB("RUSHDB_API_KEY", base_url="https://api.rushdb.com/api/v1")
# Start a transaction
tx = db.tx.begin()
try:
# Perform operations within the transaction
product = db.records.create(
label="PRODUCT",
data={"name": "Smartphone X", "price": 999.99},
transaction=tx
)
inventory = db.records.create(
label="INVENTORY",
data={"productId": product.id, "stock": 100},
transaction=tx
)
# Create a relationship between records
product.attach(
target=inventory,
options={"type": "HAS_INVENTORY"},
transaction=tx
)
# Commit the transaction once all operations are successful
tx.commit()
print("Transaction committed successfully")
except Exception as e:
# Roll back the transaction if any operation fails
tx.rollback()
print(f"Transaction rolled back due to error: {e}")
```
### 2. Context Manager (with statement)
```python
# Using transaction as a context manager
try:
with db.tx.begin() as tx:
# Create an order record
order = db.records.create(
label="ORDER",
data={"orderId": "ORD-12345", "total": 129.99},
transaction=tx
)
# Create order items
item1 = db.records.create(
label="ORDER_ITEM",
data={"productId": "PROD-001", "quantity": 2, "price": 49.99},
transaction=tx
)
item2 = db.records.create(
label="ORDER_ITEM",
data={"productId": "PROD-002", "quantity": 1, "price": 30.01},
transaction=tx
)
# Connect items to the order
order.attach(
target=[item1, item2],
options={"type": "CONTAINS_ITEM"},
transaction=tx
)
# Transaction is automatically committed when the block exits normally
except Exception as e:
# Transaction is automatically rolled back if an exception occurs
print(f"Transaction failed: {e}")
```
## Transaction Operations
The Transaction API provides the following operations:
### begin()
Starts a new transaction.
```python
tx = db.tx.begin()
```
### commit()
Commits all operations in the transaction.
```python
tx.commit()
```
### rollback()
Rolls back all operations in the transaction.
```python
tx.rollback()
```
## Supported Methods with Transactions
Most RushDB Python SDK methods support an optional `transaction` parameter. Here are some examples:
### Records API
```python
# Create a record within a transaction
record = db.records.create(
label="USER",
data={"name": "John Doe"},
transaction=tx
)
# Update a record within a transaction
db.records.update(
record_id=record.id,
data={"status": "active"},
transaction=tx
)
# Delete a record within a transaction
db.records.delete_by_id(
id_or_ids=record.id,
transaction=tx
)
# Find records within a transaction
result = db.records.find(
query={"labels": ["USER"]},
transaction=tx
)
users = result.data
```
### Relationships
```python
# Create a relationship within a transaction
db.records.attach(
source=user.id,
target=group.id,
options={"type": "BELONGS_TO"},
transaction=tx
)
# Remove a relationship within a transaction
db.records.detach(
source=user.id,
target=group.id,
options={"typeOrTypes": "BELONGS_TO"},
transaction=tx
)
```
## Complex Transaction Example
Here's a more complex example showing how to use transactions to ensure data consistency in an e-commerce scenario:
```python
def process_order(db, customer_id, items):
# Start a transaction
tx = db.tx.begin()
try:
# 1. Create the order record
order = db.records.create(
label="ORDER",
data={
"orderDate": datetime.now().isoformat(),
"status": "processing",
"totalAmount": sum(item["price"] * item["quantity"] for item in items)
},
transaction=tx
)
# 2. Retrieve the customer
result = db.records.find(
query={"where": {"id": customer_id}},
transaction=tx
)
if not result:
raise Exception(f"Customer {customer_id} not found")
customer = result[0]
# 3. Connect order to customer
customer.attach(
target=order,
options={"type": "PLACED_ORDER"},
transaction=tx
)
# 4. Process each order item
order_items = []
for item in items:
# 4.1. Check inventory
result = db.records.find(
query={
"labels": ["INVENTORY"],
"where": {"productId": item["productId"]}
},
transaction=tx
)
if not result or result[0]["stock"] < item["quantity"]:
raise Exception(f"Insufficient stock for product {item['productId']}")
inventory = result[0]
# 4.2. Create order item
order_item = db.records.create(
label="ORDER_ITEM",
data={
"productId": item["productId"],
"quantity": item["quantity"],
"price": item["price"],
"subtotal": item["price"] * item["quantity"]
},
transaction=tx
)
order_items.append(order_item)
# 4.3. Update inventory
db.records.update(
record_id=inventory[0].id,
data={"stock": inventory[0]["stock"] - item["quantity"]},
transaction=tx
)
# 5. Connect order items to order
order.attach(
target=order_items,
options={"type": "CONTAINS"},
transaction=tx
)
# 6. Update order status
order.update(
data={"status": "confirmed"},
transaction=tx
)
# Commit the transaction
tx.commit()
return {"success": True, "orderId": order.id}
except Exception as e:
# Roll back the transaction if any step fails
tx.rollback()
return {"success": False, "error": str(e)}
```
## Transaction Limitations
1. **Timeouts**: Transactions have a timeout period. Long-running transactions may be automatically aborted.
2. **Isolation Level**: RushDB uses the underlying Neo4j transaction isolation level, which is READ_COMMITTED.
3. **Nested Transactions**: Nested transactions are not supported. You should use a single transaction for a set of related operations.
4. **Transaction Size**: Very large transactions with many operations may impact performance. Consider breaking extremely large operations into smaller batches.
## Best Practices
1. **Keep transactions short** - Transaction locks are held until the transaction is committed or rolled back.
2. **Handle exceptions properly** - Always include exception handling to ensure transactions are properly rolled back.
3. **Use appropriate scope** - Only include necessary operations in a transaction.
4. **Consider using the context manager** - The context manager approach guarantees proper transaction handling.
5. **Avoid long-running transactions** - Long-running transactions can impact system performance.
6. **Don't mix transactional and non-transactional operations** - Keep all related operations within the transaction.
7. **Test transaction rollback scenarios** - Ensure your application properly handles transaction failures.
## Related Documentation
- [Transactions Concept](../concepts/transactions.mdx) - Learn more about how transactions work in RushDB
- [Record Operations](./records/create-records.md) - Record operations supporting transactions
- [Relationships](./relationships.md) - Working with relationships in transactions
---
# RushDB REST API
Welcome to the RushDB REST API documentation! The RushDB REST API provides a modern, flexible interface for managing your data, relationships, and metadata in RushDB. Whether you are building applications, automating workflows, or integrating with other systems, the API gives you full control over your graph data with simple HTTP requests.
## What is RushDB?
RushDB is an instant, cloud-native database built on top of Neo4j, designed for modern applications and data science/ML operations. It automates data normalization, manages relationships, and features automatic type inference, so you can focus on building features instead of managing data infrastructure.
## Key Features
- **Flexible Data Model**: Store structured, semi-structured, and nested data as records and relationships.
- **Relationship Management**: Easily create, query, and manage relationships between records.
- **Batch Operations**: Import and export data in bulk using JSON or CSV.
- **ACID Transactions**: Perform multiple operations atomically for data consistency.
- **Powerful Search**: Query records with advanced filters, ordering, and pagination.
- **Property & Label APIs**: Manage metadata, property types, and record labels.
- **Secure & Scalable**: Built for both cloud and self-hosted deployments, with robust authentication and access control.
## How to Use the API
- **Base URL**: The API is available at `https://api.rushdb.com/api/v1` for cloud users, or your custom URL for self-hosted deployments.
- **Authentication**: All endpoints require authentication via a token header. Get your API token from the [RushDB dashboard](https://app.rushdb.com).
- **Content-Type**: All requests and responses use JSON unless otherwise specified.
## API Specifications
The RushDB API is documented using OpenAPI (Swagger) specification for easy integration and exploration:
- **Swagger UI**: [Interactive API Documentation](https://api.rushdb.com/api)
- **OpenAPI JSON**: [JSON Schema Specification](https://api.rushdb.com/api-json)
- **OpenAPI YAML**: [YAML Specification](https://api.rushdb.com/api-yaml)
You can use these specifications to:
- Generate client libraries in your preferred programming language
- Import the API into tools like Postman, Insomnia, or SwaggerHub
- Understand request/response formats with machine-readable schemas
## Common Use Cases
- Create, update, and delete records
- Manage relationships between records
- Import/export data in bulk
- Search and filter records with complex queries
- Manage property types and labels
- Use transactions for atomic multi-step operations
## Getting Started
1. **Get an API Key**: Sign up at [app.rushdb.com](https://app.rushdb.com) or set up a self-hosted instance.
2. **Read the Endpoint Docs**: Explore the sidebar for detailed documentation on each API endpoint, including request/response formats and examples.
3. **Try It Out**: Use cURL, Postman, or your favorite HTTP client to interact with the API.
## Example: Create a Record
```http
POST /api/v1/records
Content-Type: application/json
token: RUSHDB_API_KEY
{
"label": "Person",
"data": {
"name": "John Doe",
"age": 30,
"email": "john.doe@email.com"
}
}
```
## Support & Resources
- [RushDB Documentation](https://docs.rushdb.com)
- [RushDB Homepage](https://rushdb.com)
- [Community & Support](https://rushdb.com/contact)
---
Browse the sidebar to learn more about each API endpoint, best practices, and advanced features!
---
# Labels API
RushDB provides a Labels API that allows you to retrieve information about the [labels](../concepts/labels.md) used in your records. Labels are a powerful way to categorize and organize [records](../concepts/records.md) in your database.
## Overview
The Labels API allows you to:
- Retrieve all labels used in your project
- Get the count of records with each label
- Filter labels based on record properties
All labels endpoints require authentication using a token header.
## List Labels
```http
POST /api/v1/labels/search
```
Returns a find of all [labels](../concepts/labels.md) in the current project along with the count of records having each label. You can filter the results using the `where` clause.
### Request Body
| Field | Type | Description |
|---------|--------|----------------------------------------------------------------------------------------------------------------|
| `where` | Object | Optional [filter criteria](../concepts/search/introduction.md) to narrow down which labeled records to include |
### Example Request
```json
{
"where": {
"country": "USA"
}
}
```
This will return labels for all records where the `country` property equals "USA".
### Response
```json
{
"success": true,
"data": {
"Person": 35,
"Company": 12,
"Customer": 24
}
}
```
The response is a map where each key is a label name and each value is the count of records with that label.
## Filtering Labels
You can use [complex queries](../concepts/search/introduction.md) to filter which labeled records to include:
### Example with Multiple Conditions
```json
{
"where": {
"age": { "$gt": 30 },
"active": true
}
}
```
This will return labels for records where `age` is greater than 30 AND `active` is true.
### Example with OR Logic
```json
{
"where": {
"$or": [
{ "country": "USA" },
{ "country": "Canada" }
]
}
}
```
This will return labels for records where `country` is either "USA" OR "Canada".
## Working with Labels
### Best Practices
1. **Consistent naming conventions**: Use a consistent pattern for [label](../concepts/labels.md) names (e.g., singular nouns, PascalCase)
2. **Meaningful labels**: Choose labels that describe what the record represents, not just its attributes
3. **Hierarchical labeling**: Consider using more specific labels for specialized record types (e.g., "Employee" and "Manager" instead of just "Person")
4. **Multiple labels**: Remember that records can have multiple labels in RushDB, allowing for flexible classification
### Common Use Cases
- **Data organization**: Group related records for easier querying and visualization
- **Access control**: Set permissions based on record labels
- **Conditional processing**: Apply different business logic depending on record types
- **Schema validation**: Enforce data structure based on record labels
---
# Properties API
RushDB provides a powerful Properties API that enables you to manage the properties associated with your records. This API allows you to find, retrieve, create, update, and delete properties, as well as manage property values.
## Overview
The Properties API allows you to:
- List all properties in your project
- Get details about a specific property
- Get distinct values for a property
- Delete properties
All properties endpoints require authentication using a token header.
## Property Types
RushDB supports the following property types:
| Type | Description |
|------|-------------|
| `string` | Text values |
| `number` | Numeric values |
| `boolean` | True/false values |
| `null` | Null values |
| `datetime` | ISO8601 format datetime values |
| `vector` | Arrays of numbers (for embeddings/vector search) |
## List Properties
```http
POST /api/v1/properties/search
```
Returns a find of all properties in the current project, with filtering options.
### Request Body
| Field | Type | Description |
|-----------|--------|-------------|
| `where` | Object | Optional filter criteria ([learn more](../../concepts/search/where)) |
| `labels` | Array | Optional array of labels to filter records by ([learn more](../../concepts/search/labels)) |
### Example Request
```json
{
"where": {
"type": "string"
}
}
```
### Response
```json
{
"success": true,
"data": [
{
"id": "018dfc84-d6cb-7000-89cd-850db63a1e78",
"name": "name",
"type": "string",
"projectId": "018dfc84-d6cb-7000-89cd-850db63a1e76",
"metadata": ""
},
{
"id": "018dfc84-d6cb-7000-89cd-850db63a1e79",
"name": "email",
"type": "string",
"projectId": "018dfc84-d6cb-7000-89cd-850db63a1e76",
"metadata": ""
}
]
}
```
## Get Property
```http
GET /api/v1/properties/:propertyId
```
Retrieve detailed information about a specific property by its ID.
### Parameters
| Parameter | Type | Description |
|-------------|--------|-------------|
| `propertyId` | String | The ID of the property to retrieve |
### Response
```json
{
"success": true,
"data": {
"id": "018dfc84-d6cb-7000-89cd-850db63a1e78",
"name": "name",
"type": "string",
"projectId": "018dfc84-d6cb-7000-89cd-850db63a1e76",
"metadata": ""
}
}
```
## Get Property Values
```http
POST /api/v1/properties/:propertyId/values
```
Retrieves distinct values for a specific property across all records using SearchQuery filtering.
### Parameters
| Parameter | Type | Description |
|-------------|--------|-------------|
| `propertyId` | String | The ID of the property |
### Request Body
The request body supports SearchQuery parameters along with value-specific filtering:
| Field | Type | Description |
|-----------|--------|-------------|
| `where` | Object | Optional. SearchQuery filter criteria ([learn more](../../concepts/search/where)) |
| `labels` | Array | Optional array of labels to filter records by ([learn more](../../concepts/search/labels)) |
| `skip` | Number | Optional. Number of values to skip (default: 0) |
| `limit` | Number | Optional. Maximum number of values to return (default: 100) |
| `query` | String | Optional. Filter values by this text string |
| `orderBy` | String | Optional. Sort direction (`asc` or `desc`) |
### Example Request
```http
POST /api/v1/properties/018dfc84-d6cb-7000-89cd-850db63a1e78/values
Content-Type: application/json
{
"where": {
"status": "active"
},
"query": "jo",
"orderBy": "asc",
"skip": 0,
"limit": 10
}
```
### Response
```json
{
"success": true,
"data": {
"values": ["John", "Johnny", "Jon"],
"min": null,
"max": null,
"type": "string"
}
}
```
For numeric properties, the response includes minimum and maximum values:
```json
{
"success": true,
"data": {
"values": [18, 19, 20, 21],
"min": 18,
"max": 21,
"type": "number"
}
}
```
## Delete Property
```http
DELETE /api/v1/properties/:propertyId
```
Deletes a property from all records.
### Parameters
| Parameter | Type | Description |
|-------------|--------|-------------|
| `propertyId` | String | The ID of the property to delete |
### Response
```json
{
"success": true,
"data": {
"message": "Property (018dfc84-d6cb-7000-89cd-850db63a1e78) has been successfully deleted."
}
}
```
## Value Handling
### Single Values
Single values are stored directly:
```json
{
"name": "John Doe",
"age": 30,
"active": true
}
```
### Multiple Values
Arrays can store multiple values of the same type:
```json
{
"tags": ["important", "urgent", "follow-up"],
"scores": [85, 90, 95]
}
```
### Value Separators
When updating properties, you can use value separators to split a string into multiple values:
```json
{
"name": "tags",
"type": "string",
"value": "important,urgent,follow-up",
"valueSeparator": ","
}
```
This will result in an array of values: `["important", "urgent", "follow-up"]`.
## Property Metadata
Properties can have optional metadata, which can be used to store additional information about the property. This is useful for storing things like property descriptions, validation rules, or display preferences.
```json
{
"name": "email",
"type": "string",
"metadata": "{\"description\":\"User's email address\",\"required\":true,\"unique\":true}"
}
```
Metadata is stored as a JSON string and can contain any valid JSON data.
## Best Practices
1. **Use consistent naming**: Follow a consistent naming convention for property names
2. **Set appropriate types**: Use the correct type for each property to facilitate operations like sorting and filtering
3. **Use metadata**: Leverage the metadata field to add useful information about your properties
4. **Batch updates**: When updating property values across many records, use the batch update endpoint
5. **Consider relationships**: For complex data models, consider using relationships between records instead of deeply nested property structures
---
# Raw Queries
> **Important (cloud-only):** This endpoint is available only on the RushDB managed cloud service or when your project is connected to a custom database through RushDB Cloud. It is not available for self-hosted or local-only deployments — attempting to use it against a non-cloud instance will fail.
### REST API
Endpoint: POST /query/raw
Body:
```json
{
"query": "MATCH (n:Person) RETURN n LIMIT $limit",
"params": { "limit": 10 }
}
```
Response: raw Neo4j driver result object.
### Real-world example: employees at a company
Request body:
```json
{
"query": "MATCH (c:Company { name: $company })<-[:EMPLOYS]-(p:Person) RETURN p { .name, .email, company: c.name } AS employee ORDER BY p.name LIMIT $limit",
"params": { "company": "Acme Corp", "limit": 50 }
}
```
---
# Create Records
RushDB provides multiple ways to create records via its REST API. You can create single [records](../../concepts/records.md), control how your data is processed, and work with transactions for data consistency.
## Overview
The create records endpoints allow you to:
- Create a single record with properties and a label
- Control data type inference and other formatting options
- Create records within transactions for data consistency
All create record endpoints require authentication using a token header.
## Create a Record
```http
POST /api/v1/records
```
This endpoint creates a record with the provided label and data.
### Request Body
| Field | Type | Description |
|-------------|--------|-------------|
| `label` | String | Label for the new record |
| `data` | Object | Object containing property name/value pairs |
| `options` | Object | Optional configuration parameters |
#### Options Object
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `suggestTypes` | Boolean | `true` | When true, automatically infers data types for properties |
| `castNumberArraysToVectors` | Boolean | `false` | When true, converts numeric arrays to vector type |
| `convertNumericValuesToNumbers` | Boolean | `false` | When true, converts string numbers to number type |
### Example Request
```json
{
"label": "Person",
"data": {
"name": "John Doe",
"age": "30",
"isActive": true,
"skills": ["JavaScript", "Python", "SQL"],
"joinDate": "2025-04-23T10:30:00Z",
"score": 92.5
},
"options": {
"suggestTypes": true,
"convertNumericValuesToNumbers": true
}
}
```
### Response
```json
{
"__id": "018e4c71-f35a-7000-89cd-850db63a1e77",
"__label": "Person",
"__proptypes": {
"name": "string",
"age": "number",
"isActive": "boolean",
"skills": "string",
"joinDate": "datetime",
"score": "number"
},
"name": "John Doe",
"age": 30,
"isActive": true,
"skills": ["JavaScript", "Python", "SQL"],
"joinDate": "2025-04-23T10:30:00Z",
"score": 92.5
}
```
## Property-Based Approach
If you need precise control over property types and values, you can use the property-based approach:
```http
POST /api/v1/records
```
### Request Body
| Field | Type | Description |
|-------------|--------|-------------|
| `label` | String | Label for the new record |
| `properties` | Array | Array of property objects defining record data with explicit types |
#### Property Object
| Field | Type | Description |
|-----------|--------|-------------|
| `name` | String | The property name |
| `type` | String | The data type for the property ('string', 'number', 'boolean', 'datetime', etc.) |
| `value` | Any | The value of the property |
| `valueSeparator` | String | Optional separator to split string values into arrays |
### Example Request
```json
{
"label": "Person",
"properties": [
{
"name": "name",
"type": "string",
"value": "John Doe"
},
{
"name": "age",
"type": "number",
"value": 30
},
{
"name": "isActive",
"type": "boolean",
"value": true
},
{
"name": "skills",
"type": "string",
"value": "JavaScript,Python,SQL",
"valueSeparator": ","
},
{
"name": "joinDate",
"type": "datetime",
"value": "2025-04-23T10:30:00Z"
},
{
"name": "scores",
"type": "number",
"value": "85,90,95",
"valueSeparator": ","
}
]
}
```
### Response
```json
{
"__id": "018e4c71-f35a-7000-89cd-850db63a1e77",
"__label": "Person",
"__proptypes": {
"name": "string",
"age": "number",
"isActive": "boolean",
"skills": "string",
"joinDate": "datetime",
"scores": "number"
},
"name": "John Doe",
"age": 30,
"isActive": true,
"skills": ["JavaScript", "Python", "SQL"],
"joinDate": "2025-04-23T10:30:00Z",
"scores": [85, 90, 95]
}
```
## Working with Multiple Records and Complex Data
For batch operations and working with multiple records or complex data structures, please refer to the [Import Data documentation](./import-data.md). The Import Data API provides dedicated endpoints for:
- Batch creation of multiple records in a single request
- Importing JSON or CSV data
- Creating nested record hierarchies
- Handling arrays of objects as linked records
- Setting relationship types between records
- Processing complex object graphs with automatic type inference
The Import Data API is optimized for performance when working with large datasets or complex structures. It offers additional configuration options and better throughput for batch operations.
## Creating Records in Transactions
To ensure data consistency when creating multiple related [records](../../concepts/records.md), you can use [transactions](../../concepts/transactions.mdx):
1. Create a transaction:
```http
POST /api/v1/tx
```
2. Use the returned transaction ID in your create record requests:
```http
POST /api/v1/records
Token: $RUSHDB_API_KEY
X-Transaction-Id: $YOUR_TRANSACTION_ID
```
3. Commit the transaction when all operations are successful:
```http
POST /api/v1/tx/YOUR_TRANSACTION_ID/commit
```
Or roll back if there's an error:
```http
POST /api/v1/tx/YOUR_TRANSACTION_ID/rollback
```
## Data Type Handling
RushDB supports the following [property](../../concepts/properties.md) types:
- `string`: Text values
- `number`: Numeric values
- `boolean`: True/false values
- `null`: Null values
- `datetime`: ISO8601 format strings (e.g., "2025-04-23T10:30:00Z")
- `vector`: Arrays of numbers (when `castNumberArraysToVectors` is true)
When `suggestTypes` is enabled (default in the simplified approach), RushDB automatically infers these types from your data.
When `convertNumericValuesToNumbers` is enabled, string values that represent numbers (e.g., '30') will be converted to their numeric equivalents (e.g., 30).
## Best Practices
- Use the default approach for typical use cases and when automatic type inference is desired
- Use the property-based approach when precise control over [property](../../concepts/properties.md) types is required
- Use the [Import Data API](./import-data.md) for batch operations and creating multiple records
- Use [transactions](../../concepts/transactions.mdx) when creating related records to ensure data consistency
- Validate data on the client side before sending it to the API
---
# Delete Records
RushDB provides efficient APIs for deleting records from your database. This capability allows you to remove individual records by ID or delete multiple records at once using search query filters.
## Overview
The delete endpoints allow you to:
- Delete a single record by ID
- Delete multiple records using [SearchQuery capabilities](../../concepts/search/introduction)
- Perform conditional bulk deletions
- Safely remove records with proper authentication
All delete operations require authentication using a bearer token and handle relationships appropriately.
## Delete a Single Record
```http
DELETE /api/v1/records/{entityId}
```
This endpoint deletes a specific record identified by its unique ID.
### Path Parameters
| Parameter | Type | Description |
|------------|--------|-------------|
| `entityId` | String | The unique identifier of the record to delete |
### Response
```json
{
"success": true,
"data": {
"message": "Record deleted successfully"
}
}
```
## Delete Multiple Records
```http
POST /api/v1/records/delete
```
This endpoint deletes multiple records that match the specified search criteria.
### Request Body
You can use search parameters to filter the data you want to delete:
| Field | Type | Description |
|-----------|--------|-------------|
| `where` | Object | Filter conditions for records ([learn more](../../concepts/search/where)) |
| `labels` | Array | Optional array of labels to filter records by ([learn more](../../concepts/search/labels)) |
### Example Request
```json
{
"where": {
"age": { "$lt": 18 },
"status": "inactive"
},
"labels": ["USER"]
}
```
### Response
```json
{
"success": true,
"data": {
"message": "25 record(s) deleted successfully"
}
}
```
## Bulk Deletion with Complex Queries
For more advanced deletion scenarios, you can use the full power of RushDB's search query system:
```json
{
"where": {
"$or": [
{ "status": "archived", "lastModified": { "$lt": "2024-01-01" } },
{ "status": "deleted", "isTemporary": true }
]
},
"labels": ["DOCUMENT", "ATTACHMENT"]
}
```
## Handling Relationships
When deleting records, all relationships associated with those records are automatically deleted. This ensures database integrity and prevents orphaned relationships.
## Delete Operation Safety
RushDB implements several safeguards for delete operations:
1. **Authentication**: All delete operations require a valid authentication token
2. **Authorization**: Users can only delete records in projects they have access to
3. **Validation**: Input data is validated before processing
4. **Transactions**: Delete operations are performed within transactions for data consistency
5. **Partial Failure Handling**: If a deletion affects multiple records and some operations fail, all changes are rolled back
## Performance Considerations
- For large-scale deletions, RushDB processes operations in batches
- Complex query conditions may increase processing time
- Consider using [label filtering](../../concepts/search/labels) to narrow down records before deletion
- For very large datasets, consider multiple smaller delete operations
## Related Documentation
- [Search Introduction](../../concepts/search/introduction)
- [Where Clause](../../concepts/search/where)
- [Labels](../../concepts/search/labels)
- [Record Relationships](../../concepts/relationships)
---
# Export Data
RushDB provides efficient APIs for exporting your database records in different formats. This capability allows you to retrieve and analyze your data externally or integrate it with other systems.
## Overview
The export endpoints allow you to:
- Export data in CSV format
- Filter and query the data to be exported using [SearchQuery capabilities](../../concepts/search/introduction)
- Order results as needed
- Handle large exports efficiently through pagination
All export endpoints require authentication using a bearer token.
## Export CSV Data
```http
POST /api/v1/records/export/csv
```
This endpoint exports data in CSV format with headers in the first row.
### Request Body
You can send search parameters to filter the data you want to export:
| Field | Type | Description |
|-----------|--------|-------------|
| `where` | Object | Filter conditions for records ([learn more](../../concepts/search/where)) |
| `orderBy` | String or Object | Sorting criteria ([learn more](../../concepts/search/pagination-order)) |
| `skip` | Number | Number of records to skip for pagination ([learn more](../../concepts/search/pagination-order)) |
| `limit` | Number | Maximum number of records to return (up to 1000) |
| `labels` | Array | Optional array of labels to filter records by ([learn more](../../concepts/search/labels)) |
### Example Request
```json
{
"where": {
"age": { "$gt": 25 }
},
"orderBy": { "name": "asc" },
"limit": 1000
}
```
### Response
```json
{
"success": true,
"data": {
"fileContent": "id,label,name,age,email\n018dfc84-d6cb-7000-89cd-850db63a1e77,PERSON,John Doe,30,john@example.com\n018dfc84-d78c-7000-89cd-85db63d6a120,PERSON,Jane Smith,28,jane@example.com",
"dateTime": "2025-04-23T10:15:32.123Z"
}
}
```
The `fileContent` field contains the CSV data string that can be saved directly to a file.
## Data Processing
When exporting data, RushDB:
1. **Filters**: Applies any specified filters to select records using the [where clause](../../concepts/search/where)
2. **Sorts**: Orders records based on the `orderBy` parameter as described in [pagination and order](../../concepts/search/pagination-order)
3. **Paginates**: Processes data in efficient batches using [pagination capabilities](../../concepts/search/pagination-order)
4. **Transforms**: Converts internal data structures to CSV format
5. **Cleans**: Removes internal system properties before returning data
## Performance Considerations
- Exports process data in batches of 1000 records for optimal performance
- For large datasets, consider using pagination parameters (`skip` and `limit`) as described in the [pagination documentation](../../concepts/search/pagination-order)
- Complex queries may increase processing time
- RushDB automatically handles large exports by chunking the data retrieval
- Consider using [label filtering](../../concepts/search/labels) to narrow down the data scope before exporting
## Working with Exported Data
The exported CSV can be:
- Imported into spreadsheet software
- Processed by data analysis tools
- Used for backups and data archiving
- Imported into other databases
## Related Documentation
- [Search Introduction](../../concepts/search/introduction)
- [Where Clause](../../concepts/search/where)
- [Labels](../../concepts/search/labels)
- [Pagination and Order](../../concepts/search/pagination-order)
---
# Get Records
RushDB provides flexible APIs for retrieving records from your database. This capability allows you to access individual records by ID or retrieve multiple records using powerful search queries.
## Overview
The record retrieval endpoints allow you to:
- Get a single record by its ID
- Search for multiple records using [SearchQuery capabilities](../../concepts/search/introduction)
- Filter, sort, and paginate results
- Retrieve records with related data
All record retrieval operations require authentication using a bearer token.
## Get a Single Record
```http
GET /api/v1/records/{entityId}
```
This endpoint retrieves a specific record identified by its unique ID.
### Path Parameters
| Parameter | Type | Description |
|------------|--------|-------------|
| `entityId` | String | The unique identifier of the record to retrieve |
### Response
```json
{
"success": true,
"data": {
"id": "018e4c71-5f20-7db2-b0b1-e7e681542af9",
"label": "PERSON",
"name": "John Doe",
"age": 30,
"email": "john@example.com"
}
}
```
## Search for Records
```http
POST /api/v1/records/search
```
This endpoint searches for records that match the specified criteria, with support for filtering, pagination, and sorting.
### Request Body
You can use search parameters to filter the data you want to retrieve:
| Field | Type | Description |
|-----------|------------------|----------------------------------------------------------------------------------------------|
| `where` | Object | Filter conditions for records ([learn more](../../concepts/search/where)) |
| `orderBy` | String or Object | Sorting criteria ([learn more](../../concepts/search/pagination-order)) |
| `skip` | Number | Number of records to skip for pagination ([learn more](../../concepts/search/pagination-order)) |
| `limit` | Number | Maximum number of records to return (default: 1000) |
| `labels` | Array | Optional array of labels to filter records by ([learn more](../../concepts/search/labels)) |
### Example Request
```json
{
"where": {
"age": { "$gt": 25 }
},
"orderBy": { "name": "asc" },
"skip": 0,
"limit": 50,
"labels": ["PERSON"]
}
```
### Response
```json
{
"success": true,
"data": {
"data": [
{
"id": "018e4c71-5f20-7db2-b0b1-e7e681542af9",
"label": "PERSON",
"name": "John Doe",
"age": 30,
"email": "john@example.com"
},
{
"id": "018e4c71-6a38-7db2-b0b1-e7e681542c13",
"label": "PERSON",
"name": "Jane Smith",
"age": 28,
"email": "jane@example.com"
}
// ... more records
],
"total": 125
}
}
```
## Search Related Records
```http
POST /api/v1/records/{entityId}/search
```
This endpoint searches for records that are related to a specific record, identified by its ID.
### Path Parameters
| Parameter | Type | Description |
|------------|--------|-------------|
| `entityId` | String | The unique identifier of the record to search from |
### Request Body
The request body is the same as for the regular search endpoint, allowing you to filter, paginate, and sort the related records.
### Example Request
```json
{
"where": {
"status": "active"
},
"orderBy": { "createdAt": "desc" },
"limit": 20
}
```
### Response
```json
{
"success": true,
"data": {
"data": [
{
"id": "018e4c71-7b42-7db2-b0b1-e7e681543d21",
"label": "DOCUMENT",
"title": "Project Plan",
"status": "active",
"createdAt": "2025-04-12T10:30:15Z"
},
// ... more records
],
"total": 8
}
}
```
## Advanced Filtering
RushDB supports complex filtering through the `where` clause, allowing you to create sophisticated queries:
```json
{
"where": {
"$or": [
{ "status": "active", "priority": { "$gte": 2 } },
{ "status": "pending", "deadline": { "$lt": "2025-06-01" } }
],
"assignedTo": { "$ne": null }
},
"orderBy": [
{ "priority": "desc" },
{ "deadline": "asc" }
],
"limit": 100
}
```
### Field Existence and Type Checking
You can check for field existence and data types:
```json
{
"where": {
"$and": [
{ "email": { "$exists": true } },
{ "phoneNumber": { "$exists": false } },
{ "age": { "$type": "number" } }
]
}
}
```
This query finds records that have an email address, don't have a phone number, and where age is stored as a number.
See the [Where Clause documentation](../../concepts/search/where) for a complete reference of available operators.
## Performance Considerations
- Use appropriate `limit` values to control response size and query performance
- When working with large datasets, use pagination (`skip` and `limit`) as described in [pagination documentation](../../concepts/search/pagination-order)
- Complex query conditions may increase processing time
- Use [label filtering](../../concepts/search/labels) to narrow down the search scope before applying other filters
- For frequently accessed records, consider optimizing query patterns
## Related Documentation
- [Search Introduction](../../concepts/search/introduction)
- [Where Clause](../../concepts/search/where)
- [Labels](../../concepts/search/labels)
- [Pagination and Order](../../concepts/search/pagination-order)
- [Record Relationships](../../concepts/relationships)
---
# Import Data
RushDB provides powerful and flexible APIs for importing data into your database. You can import data in various formats including JSON and CSV, with options to customize how the data is processed and stored.
## Overview
The import endpoints allow you to:
- Import JSON data
- Import CSV data
- Control data type inference and handling
- Set default relationship types
- Configure property value handling
All import endpoints require authentication using a token header.
## Nested Data Processing
When importing nested JSON data structures, RushDB automatically processes and organizes your data using a breadth-first search (BFS) algorithm. This approach efficiently:
1. **Traverses hierarchical structures**: Processes your JSON tree level by level, ensuring proper parent-child relationships
2. **Optimizes object normalization**: Converts nested objects into separate records with appropriate relationships
3. **Preserves data integrity**: Maintains the original structure and relationships between your data elements
For example, when importing a nested object like a person with embedded address information, the BFS algorithm will:
- Create a separate record for the person
- Create separate records for embedded objects (addresses)
- Establish relationships between parent and child records
- Apply proper labels derived from the JSON structure
- Set up property nodes with appropriate type inference
For more details on how RushDB manages data storage and the underlying data import mechanism, see [Storage - Data Import Mechanism](../../concepts/storage#data-import-mechanism).
## Import JSON Data
```http
POST /api/v1/records/import/json
```
### Request Body
| Field | Type | Description |
|-----------|--------|-------------|
| `data` | Object or Array | JSON data to import |
| `label` | String | Label for the root node(s) |
| `options` | Object | Optional configuration parameters |
#### Options Object
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `suggestTypes` | Boolean | `true` | When true, automatically infers data types for properties |
| `castNumberArraysToVectors` | Boolean | `false` | When true, converts numeric arrays to vector type |
| `convertNumericValuesToNumbers` | Boolean | `false` | When true, converts string numbers to number type |
| `capitalizeLabels` | Boolean | `false` | When true, converts all labels to uppercase |
| `relationshipType` | String | `__RUSHDB__RELATION__DEFAULT__` | Default relationship type between nodes |
| `returnResult` | Boolean | `false` | When true, returns imported records in response |
### Example Request
```json
{
"label": "Person",
"data": {
"name": "John Doe",
"age": "30",
"addresses": [
{
"type": "home",
"street": "123 Main St",
"city": "Anytown"
},
{
"type": "work",
"street": "456 Business Rd",
"city": "Workville"
}
],
"scores": [85, 90, 95],
"active": true
},
"options": {
"suggestTypes": true,
"convertNumericValuesToNumbers": true,
"relationshipType": "OWNS"
}
}
```
### Response
```json
{
"success": true,
"data": true
}
```
If `returnResult: true` is specified in options, the response will include the imported records:
```json
{
"success": true,
"data": [
{
"__id": "018dfc84-d6cb-7000-89cd-850db63a1e77",
"__label": "Person",
"__proptypes": { ... },
"name": "John Doe",
"age": 30,
// Additional properties...
}
// Additional records...
]
}
```
## Import CSV Data
```http
POST /api/v1/records/import/csv
```
### Request Body
| Field | Type | Description |
|-----------|--------|-------------|
| `data` | String | CSV data as a string |
| `label` | String | Label for the nodes |
| `options` | Object | Optional configuration parameters (same as JSON import) |
CSV files must have headers in the first row.
### Example Request
```json
{
"label": "Customer",
"data": "name,email,age\nJohn Doe,john@example.com,30\nJane Smith,jane@example.com,25",
"options": {
"suggestTypes": true,
"convertNumericValuesToNumbers": true
}
}
```
### Response
Same as JSON import.
## Data Transformation Process
When importing data, RushDB processes your data through the following steps:
1. **Parsing**: Converts your input format (JSON/CSV) into internal structures
2. **Type Inference**: If `suggestTypes` is enabled, analyzes values to determine appropriate data types
3. **Graph Construction**: Creates nodes and relationships based on your data structure
4. **Validation**: Checks against workspace limits
5. **Storage**: Inserts data into the database in optimized batches
## Data Type Handling
When `suggestTypes` is enabled, RushDB will infer the following types:
- `string`: Text values
- `number`: Number values (& numeric values when `convertNumericValuesToNumbers` is true)
- `boolean`: True/false values
- `null`: Null values
- `vector`: Arrays of numbers (when `castNumberArraysToVectors` is true)
- `datetime`: ISO8601 format strings (e.g., "2025-04-23T10:30:00Z") will be automatically cast to Neo4j datetime values
When `convertNumericValuesToNumbers` is enabled, string values that represent numbers (e.g., '123') will be automatically converted to their numeric equivalents (e.g., 123).
Arrays with consistent data types (e.g., all numbers, all strings) will be handled seamlessly according to their type. However, for inconsistent arrays (e.g., `[1, 'two', null, false]`), all values will be automatically converted to strings to mitigate data loss, and the property type will be stored as `string`.
## Performance Considerations
- Imports are processed in chunks of 1000 records for optimal performance
- For large imports (>25MB), consider splitting into multiple requests
- Setting `returnResult: false` is recommended for large imports to improve performance
---
# Update Records
RushDB offers powerful methods to update existing records in your database. You can update record properties and labels through the REST API.
## Overview
The update endpoints allow you to:
- Update specific properties while preserving others (PATCH)
- Completely replace record data (PUT)
All update endpoints require authentication using a token header.
## Update Record (PATCH)
The PATCH method allows you to update specific properties of a record while preserving other existing properties.
```http
PATCH /api/v1/records/{entityId}
```
### Path Parameters
| Parameter | Type | Description |
|-----------|------|-------------|
| `entityId` | String | The unique identifier of the record to update |
### Request Body
| Field | Type | Description |
|-------|------|-------------|
| `label` | String | (Optional) New label for the record |
| `properties` | Array | Array of property objects to update or add |
#### Property Object
| Field | Type | Description |
|-------|------|-------------|
| `key` | String | Property name |
| `value` | Any | Property value |
| `type` | String | (Optional) Data type of the property |
### Example Request
```json
{
"label": "Person",
"properties": [
{
"key": "name",
"value": "John Smith"
},
{
"key": "age",
"value": 32,
"type": "number"
},
{
"key": "active",
"value": true,
"type": "boolean"
}
]
}
```
### Response
```json
{
"id": "018dfc84-d6cb-7000-89cd-850db63a1e77",
"label": "Person",
"name": "John Smith",
"age": 32,
"email": "john@example.com", // Preserved from existing record
"active": true,
"_rushdb_properties_meta": {
// Metadata about properties
}
}
```
### How PATCH Works
When you use PATCH to update a record:
1. The system first retrieves the current record data
2. Merges your new properties with the existing properties
3. Updates only the specified properties while preserving any properties not included in your request
4. Returns the complete updated record
This makes PATCH ideal for updating specific fields without having to resend all record data.
## Replace Record (PUT)
The PUT method allows you to completely replace a record's data.
```http
PUT /api/v1/records/{entityId}
```
### Path Parameters
| Parameter | Type | Description |
|-----------|------|-------------|
| `entityId` | String | The unique identifier of the record to update |
### Request Body
Same as PATCH method, but all existing properties not included in the request will be removed.
### Example Request
```json
{
"label": "Customer",
"properties": [
{
"key": "name",
"value": "John Smith"
},
{
"key": "age",
"value": 32
}
]
}
```
### Response
```json
{
"id": "018dfc84-d6cb-7000-89cd-850db63a1e77",
"label": "Customer",
"name": "John Smith",
"age": 32,
"_rushdb_properties_meta": {
// Metadata about properties
}
}
```
### How PUT Works
When you use PUT to update a record:
1. The specified properties completely replace the existing record properties
2. Any properties not included in your request will be removed
3. The operation returns the new state of the record
This makes PUT ideal when you want to ensure the record only has the exact properties you specify.
## Error Handling
Update operations may return the following error responses:
| Status Code | Description |
|-------------|-------------|
| 400 | Bad Request - Invalid input format |
| 401 | Unauthorized - Authentication required |
| 403 | Forbidden - Insufficient permissions |
| 404 | Not Found - Record does not exist |
| 500 | Server Error - Processing failed |
### Example Error Response
```json
{
"success": false,
"message": "Record with id '018dfc84-d6cb-7000-89cd-850db63a1e77' not found",
"statusCode": 404
}
```
## Best Practices
1. **Use PATCH for partial updates** when you want to preserve existing data
2. **Use PUT for complete replacement** when you want to ensure the record only has the properties you specify
3. **Include property types** when you want to ensure proper data type conversion
4. **Check for 404 errors** when updating records that might not exist
5. **Retrieve current properties** with GET before updating to understand the record's current state
---
# Relationships API
RushDB provides a powerful Relationships API that enables you to manage connections between [records](../concepts/records.md). This API allows you to create, retrieve, update, and delete [relationships](../concepts/relationships.md) between any records in your database.
## Overview
The Relationships API allows you to:
- Create relationships between records
- Retrieve relationships for a specific record
- Search relationships across your entire database
- Delete specific or all relationships between records
- Specify relationship types and directions
All relationships endpoints require authentication using a token header.
## Create Many Relationships (by key match)
```http
POST /api/v1/relationships/create-many
```
Creates relationships in bulk by matching a property from source-labeled records to a property from target-labeled records.
### Request Body
| Field | Type | Description |
|-----------------|--------|---------------------------------------------------------------------------------------------------|
| `source` | Object | Source selector: `{ label: string; key?: string; where?: object }` — `key` is required unless using `manyToMany` |
| `target` | Object | Target selector: `{ label: string; key?: string; where?: object }` — `key` is required unless using `manyToMany` |
| `type` | String | Optional. Relationship type to create. Defaults to `__RUSHDB__RELATION__DEFAULT__` |
| `direction` | String | Optional. Relationship direction: `in` or `out`. Defaults to `out` |
| `manyToMany` | Boolean| Optional. When true, allows creating a cartesian product between matched source and target sets. Requires non-empty `where` on both sides. |
The matching condition is always `source[key] = target[key]`, combined with optional `where` filters on each side.
The `where` objects follow the standard SearchQuery `where` syntax used across the platform.
Notes on many-to-many/cartesian creation
- If `manyToMany` is set to `true`, the server will not require `source.key`/`target.key` and will create relationships between every matching source and every matching target produced by the `where` filters.
- `manyToMany=true` requires non-empty `where` filters for both `source` and `target` to avoid accidental unbounded cartesian products.
- When `manyToMany` is not provided or is false, `source.key` and `target.key` are required and the server joins with `source[key] = target[key]`.
### Example Request
```json
{
"source": { "label": "USER", "key": "id", "where": { "tenantId": "ACME" } },
"target": { "label": "ORDER", "key": "userId", "where": { "tenantId": "ACME" } },
"type": "ORDERED",
"direction": "out"
}
```
### Response
```json
{
"success": true,
"data": {
"message": "Relations have been successfully created"
}
}
## Delete Many Relationships (by key match or filters)
```http
POST /api/v1/relationships/delete-many
```
Deletes relationships in bulk that match the provided source/target selectors. The request body mirrors the create-many API.
### Request Body
| Field | Type | Description |
|-----------------|--------|---------------------------------------------------------------------------------------------------|
| `source` | Object | Source selector: `{ label: string; key?: string; where?: object }` — `key` is required unless using `manyToMany` |
| `target` | Object | Target selector: `{ label: string; key?: string; where?: object }` — `key` is required unless using `manyToMany` |
| `type` | String | Optional. Relationship type to delete. If omitted, matches any type |
| `direction` | String | Optional. Relationship direction: `in` or `out`. Defaults to `out` |
| `manyToMany` | Boolean| Optional. When true, allows matching every source to every target produced by the `where` filters. Requires non-empty `where` on both sides. |
### Example Request
```json
{
"source": { "label": "USER", "key": "id", "where": { "tenantId": "ACME" } },
"target": { "label": "ORDER", "key": "userId", "where": { "tenantId": "ACME" } },
"type": "ORDERED",
"direction": "out"
}
```
### Response
```json
{
"success": true,
"data": {
"message": "Relations have been successfully deleted"
}
}
```
```
## Create Relationship
```http
POST /api/v1/records/:entityId/relationships
```
Creates one or more [relationships](../concepts/relationships.md) from a source record to one or more target records.
### Parameters
| Parameter | Type | Description |
|------------|--------|-----------------------------|
| `entityId` | String | The ID of the source record |
### Request Body
| Field | Type | Description |
|-------------|-----------------|-------------------------------------------------------------------------------------------|
| `targetIds` | String or Array | ID(s) of target record(s) to create relationship(s) with |
| `type` | String | Optional. The type of relationship to create. Defaults to `__RUSHDB__RELATION__DEFAULT__` |
| `direction` | String | Optional. Direction of the relationship: `in` or `out`. Defaults to `out` |
### Example Request - Single Target
```json
{
"targetIds": "018e4c71-f35a-7000-89cd-850db63a1e78",
"type": "WORKS_FOR"
}
```
### Example Request - Multiple Targets
```json
{
"targetIds": [
"018e4c71-f35a-7000-89cd-850db63a1e78",
"018e4c71-f35a-7000-89cd-850db63a1e79"
],
"type": "KNOWS",
"direction": "out"
}
```
### Response
```json
{
"success": true,
"data": {
"message": "Relations to Record 018e4c71-f35a-7000-89cd-850db63a1e77 have been successfully created"
}
}
```
## Get Record Relationships
```http
GET /api/v1/records/:entityId/relationships
```
Retrieves all relationships for a specific [record](../concepts/records.md).
### Parameters
| Parameter | Type | Description |
|------------|--------|---------------------------------------------------------------------|
| `entityId` | String | The ID of the record |
| `skip` | Number | Optional. Number of relationships to skip (default: 0) |
| `limit` | Number | Optional. Maximum number of relationships to return (default: 1000) |
### Response
```json
{
"success": true,
"data": {
"total": 3,
"data": [
{
"sourceId": "018e4c71-f35a-7000-89cd-850db63a1e77",
"sourceLabel": "Person",
"targetId": "018e4c71-f35a-7000-89cd-850db63a1e78",
"targetLabel": "Company",
"type": "WORKS_FOR"
},
{
"sourceId": "018e4c71-f35a-7000-89cd-850db63a1e77",
"sourceLabel": "Person",
"targetId": "018e4c71-f35a-7000-89cd-850db63a1e79",
"targetLabel": "Person",
"type": "KNOWS"
},
{
"sourceId": "018e4c71-f35a-7000-89cd-850db63a1e80",
"sourceLabel": "Department",
"targetId": "018e4c71-f35a-7000-89cd-850db63a1e77",
"targetLabel": "Person",
"type": "HAS_MEMBER"
}
]
}
}
```
## Delete Relationships
```http
PUT /api/v1/records/:entityId/relationships
```
Deletes one or more relationships from a source record to one or more target records.
### Parameters
| Parameter | Type | Description |
|------------|--------|-----------------------------|
| `entityId` | String | The ID of the source record |
### Request Body
| Field | Type | Description |
|---------------|-----------------|--------------------------------------------------------------------------------------------------------------|
| `targetIds` | String or Array | ID(s) of target record(s) to delete relationship(s) with |
| `typeOrTypes` | String or Array | Optional. Type(s) of relationships to delete. If omitted, deletes relationships of any type |
| `direction` | String | Optional. Direction of the relationship: `in` or `out`. If omitted, deletes relationships in both directions |
### Example Request - Delete All Relationship Types
```json
{
"targetIds": "018e4c71-f35a-7000-89cd-850db63a1e78"
}
```
### Example Request - Delete Specific Relationship Types
```json
{
"targetIds": [
"018e4c71-f35a-7000-89cd-850db63a1e78",
"018e4c71-f35a-7000-89cd-850db63a1e79"
],
"typeOrTypes": ["KNOWS", "WORKS_FOR"],
"direction": "out"
}
```
### Response
```json
{
"success": true,
"data": {
"message": "Relations to Record 018e4c71-f35a-7000-89cd-850db63a1e77 have been successfully deleted"
}
}
```
## Search Relationships
```http
POST /api/v1/relationships/search
```
Searches for [relationships](../concepts/relationships.md) across your database with optional filtering.
### Request Body
| Field | Type | Description |
|---------|--------|----------------------------------------------------------------------------------------------|
| `where` | Object | Optional [filter criteria](../concepts/search/where.md) to search for specific relationships |
### Query Parameters
| Parameter | Type | Description |
|-----------|--------|-----------------------------------------------------------------------------------------------------------------|
| `skip` | Number | Optional. Number of relationships to skip for [pagination](../concepts/search/pagination-order.md) (default: 0) |
| `limit` | Number | Optional. Maximum number of relationships to return (default: 1000) |
### Example Request - Filter by Record Properties
```json
{
"where": {
"sourceRecord": {
"name": "John Doe"
},
"targetRecord": {
"name": "Acme Inc"
}
}
}
```
### Response
```json
{
"success": true,
"data": {
"total": 1,
"data": [
{
"sourceId": "018e4c71-f35a-7000-89cd-850db63a1e77",
"sourceLabel": "Person",
"targetId": "018e4c71-f35a-7000-89cd-850db63a1e78",
"targetLabel": "Company",
"type": "WORKS_FOR"
}
]
}
}
```
## Relationship Directionality
RushDB supports three types of [relationship](../concepts/relationships.md) directionality:
1. **Outgoing relationships (`direction: "out"`)**:
The source record points to the target record: `(source)-[relationship]->(target)`
2. **Incoming relationships (`direction: "in"`)**:
The target record points to the source record: `(source)<-[relationship]-(target)`
3. **Undirected relationships (no direction specified)**:
The relationship has no specific direction: `(source)-[relationship]-(target)`
## Best Practices
1. **Use meaningful relationship types**: Choose relationship types that clearly describe the connection between [records](../concepts/records.md)
2. **Consider directionality**: Choose the right direction for your relationships based on your domain model
3. **Use relationship metadata**: When your use case requires it, store additional information about relationships
4. **Use consistent naming**: Establish naming conventions for relationship types (e.g., uppercase with underscores)
5. **Mind performance**: For highly connected records, paginate relationships with the `skip` and `limit` parameters
---
# Relationships
RushDB provides dedicated endpoints to create, read, update, and delete relationships between records. These endpoints allow you to build complex graph structures and model real-world relationships in your data.
## Overview
The relationship management endpoints enable you to:
- Create relationships between records
- List relationships for a record
- Remove specific relationships
- Search across all relationships
- Manage relationship types and directions
All relationship endpoints require authentication using a bearer token.
## Create Relationship
Create one or more relationships between records.
```http
POST /api/v1/records/{entityId}/relationships
```
### Path Parameters
| Parameter | Type | Description |
|------------|--------|-----------------------------------|
| `entityId` | String | Source record identifier (UUIDv7) |
### Request Body
| Field | Type | Description |
|-------------|-----------------|------------------------------------------------------------------------------------|
| `targetIds` | String or Array | Target record identifier(s). Cannot be empty or contain empty strings |
| `type` | String | (Optional) Relationship type. Cannot be an empty string |
| `direction` | String | (Optional) Relationship direction. Must be either "in" or "out". Defaults to "out" |
### Example Request
```json
{
"targetIds": ["018dfc84-d6cb-7000-89cd-850db63a1e78"],
"type": "FOLLOWS",
"direction": "out"
}
```
#### Creating Multiple Relationships
You can create multiple relationships in a single request by passing an array of target IDs:
```json
{
"targetIds": [
"018dfc84-d6cb-7000-89cd-850db63a1e78",
"018dfc84-d6cb-7000-89cd-850db63a1e79"
],
"type": "FOLLOWS",
"direction": "out"
}
```
### Response
```json
{
"message": "Relations created successfully"
}
```
## List Relationships
Retrieve relationships for a specific record.
```http
GET /api/v1/records/{entityId}/relationships
```
### Path Parameters
| Parameter | Type | Description |
|------------|--------|----------------------------|
| `entityId` | String | Record identifier (UUIDv7) |
### Query Parameters
| Parameter | Type | Description | Default |
|-----------|--------|------------------------------------------------------|---------|
| `skip` | Number | (Optional) Number of relationships to skip | 0 |
| `limit` | Number | (Optional) Maximum number of relationships to return | 1000 |
### Example Response
```json
{
"data": [
{
"sourceId": "018dfc84-d6cb-7000-89cd-850db63a1e77",
"sourceLabel": "Person",
"targetId": "018dfc84-d6cb-7000-89cd-850db63a1e78",
"targetLabel": "Person",
"type": "FOLLOWS"
}
],
"total": 1
}
```
## Remove Relationship
Remove one or more relationships between records.
```http
PUT /api/v1/records/{entityId}/relationships
```
### Path Parameters
| Parameter | Type | Description |
|------------|--------|-----------------------------------|
| `entityId` | String | Source record identifier (UUIDv7) |
### Request Body
| Field | Type | Description |
|---------------|-----------------|--------------------------------------------------------------------------------|
| `targetIds` | String or Array | Target record identifier(s). Cannot be empty or contain empty strings |
| `typeOrTypes` | String or Array | (Optional) One or more relationship type(s) to remove. Cannot be empty strings |
| `direction` | String | (Optional) Filter relationships by direction: "in" or "out" |
### Example Request - Single Type
```json
{
"targetIds": ["018dfc84-d6cb-7000-89cd-850db63a1e78"],
"typeOrTypes": "FOLLOWS",
"direction": "out"
}
```
### Example Request - Multiple Types
```json
{
"targetIds": ["018dfc84-d6cb-7000-89cd-850db63a1e78"],
"typeOrTypes": ["FOLLOWS", "LIKES"],
"direction": "out"
}
```
### Response
```json
{
"message": "Relations removed successfully"
}
```
## Search Relations
```http
POST /api/v1/relationships/search
```
This endpoint searches for [relationships](../concepts/relationships.md) between records based on specified criteria.
### Request Body
The request body follows the standard [search parameters](../concepts/search/introduction.md) format.
### Query Parameters
| Parameter | Type | Description |
|-----------|--------|-------------------------------------------------------------------------------------------------------|
| `skip` | Number | Number of relationships to skip for [pagination](../concepts/search/pagination-order.md) (default: 0) |
| `limit` | Number | Maximum number of relationships to return (default: 1000) |
### Response
```json
{
"success": true,
"data": {
"data": [
// relationships matching the search criteria
],
"total": 42
}
}
```
## Search Relationships
Search across all [relationships](../concepts/relationships.md) in the project. This endpoint allows you to query relationships with powerful filtering options.
```http
POST /api/v1/relationships/search
```
### Query Parameters
| Parameter | Type | Description | Default |
|-----------|--------|-----------------------------------------------------------------------------------------------------|---------|
| `skip` | Number | (Optional) Number of relationships to skip for [pagination](../concepts/search/pagination-order.md) | 0 |
| `limit` | Number | (Optional) Maximum number of relationships to return | 1000 |
### Request Body
The search endpoint accepts a SearchDto object with the following fields:
| Field | Type | Description |
|-----------|--------|----------------------------------------------------------------------------------------------------|
| `where` | Object | (Optional) [Filter criteria](../concepts/search/where.md) for the search |
| `orderBy` | Object | (Optional) [Sorting criteria](../concepts/search/pagination-order.md#sorting-records-with-orderby) |
| `labels` | Array | (Optional) Filter by [record labels](../concepts/search/labels.md) |
### Example Request - With Filters
```json
{
"where": {
"sourceLabel": "Person",
"type": "FOLLOWS"
},
"orderBy": {
"type": "ASC"
},
"limit": 10
}
```
### Response
```json
{
"data": [
{
"sourceId": "018dfc84-d6cb-7000-89cd-850db63a1e77",
"sourceLabel": "Person",
"targetId": "018dfc84-d6cb-7000-89cd-850db63a1e78",
"targetLabel": "Person",
"type": "FOLLOWS"
}
],
"total": 1
}
```
## Relationship Types
RushDB supports several relationship configurations:
### Default Relationship
If no type is specified when creating a relationship, it uses the default type `__RUSHDB__RELATION__DEFAULT__`. This relationship type is useful for simple connections where semantic meaning isn't required.
### Custom Types
You can define custom relationship types to represent specific semantic meanings in your data model. For example:
- `FOLLOWS` for social connections
- `BELONGS_TO` for hierarchical relationships
- `WORKS_FOR` for organizational relationships
### Bidirectional Relationships
While relationships have a direction, you can create bidirectional relationships by:
1. Creating two relationships with opposite directions
2. Querying relationships without specifying direction
### Relationship Properties
Relationships can have properties attached to them, which is useful for storing metadata about the connection, such as:
- Timestamps (when the relationship was established)
- Weights or strengths
- Additional context
## Validation
The API enforces the following validation rules:
1. `targetIds` cannot be empty or contain empty strings
2. `type` and `typeOrTypes` cannot be empty strings when provided
3. `direction` must be either "in" or "out" when provided
4. Record IDs must be valid UUIDv7 strings
5. Source and target records must exist in the database
## Best Practices
1. **Use meaningful relationship types** that describe the semantic connection between records
2. **Consider directionality** when designing your data model - choose directions that make semantic sense
3. **Batch relationship operations** when creating or modifying many relationships at once
4. **Use pagination** when retrieving large sets of relationships to improve performance
5. **Validate record existence** before creating relationships
6. **Index important relationship types** that are frequently queried
7. **Use consistent naming conventions** for relationship types (e.g., uppercase with underscores)
8. **Document relationship types** and their meanings in your application
---
# Transactions API
RushDB provides a powerful Transactions API that allows you to perform multiple database operations atomically. This ensures data consistency by either committing all operations or rolling back all changes if an error occurs.
## Overview
Transactions in RushDB:
- Allow multiple operations to be executed as a single atomic unit
- Provide ACID (Atomicity, Consistency, Isolation, Durability) guarantees
- Automatically rollback after timeout to prevent hanging transactions
- Can be explicitly committed or rolled back
## Transaction Lifecycle
1. **Create** a transaction to get a transaction ID
2. **Use** the transaction ID in subsequent API requests
3. **Commit** the transaction to make changes permanent, or **Rollback** to discard changes
4. If neither committed nor rolled back within the TTL (Time To Live), the transaction will automatically rollback
## API Endpoints
### Create Transaction
Creates a new transaction and returns a transaction ID.
```http
POST /api/v1/tx
```
#### Request Body
| Field | Type | Description |
|-------|--------|-------------|
| `ttl` | Number | Optional. Time to live in milliseconds. Default: 5000ms. Maximum: 30000ms (30 seconds). |
#### Example Request
```json
{
"ttl": 10000
}
```
#### Response
```json
{
"success": true,
"data": {
"id": "018e5c31-f35a-7000-89cd-850db63a1e77"
}
}
```
### Get Transaction
Check if a transaction exists.
```http
GET /api/v1/tx/:txId
```
#### Parameters
| Parameter | Type | Description |
|-----------|--------|-------------|
| `txId` | String | The transaction ID |
#### Response
```json
{
"success": true,
"data": {
"id": "018e5c31-f35a-7000-89cd-850db63a1e77"
}
}
```
### Commit Transaction
Commits all changes made within the transaction, making them permanent in the database.
```http
POST /api/v1/tx/:txId/commit
```
#### Parameters
| Parameter | Type | Description |
|-----------|--------|-------------|
| `txId` | String | The transaction ID |
#### Response
```json
{
"success": true,
"data": {
"message": "Transaction (018e5c31-f35a-7000-89cd-850db63a1e77) has been successfully committed."
}
}
```
### Rollback Transaction
Discards all changes made within the transaction.
```http
POST /api/v1/tx/:txId/rollback
```
#### Parameters
| Parameter | Type | Description |
|-----------|--------|-------------|
| `txId` | String | The transaction ID |
#### Response
```json
{
"success": true,
"data": {
"message": "Transaction (018e5c31-f35a-7000-89cd-850db63a1e77) has been rolled back."
}
}
```
## Using Transactions with Other APIs
To use a transaction with other API endpoints, include the transaction ID in the `X-Transaction-Id` header.
### Example
```http
POST /api/v1/records
Content-Type: application/json
token: RUSHDB_API_KEY
X-Transaction-Id: 018e5c31-f35a-7000-89cd-850db63a1e77
{
"label": "Person",
"properties": [
{
"name": "name",
"type": "string",
"value": "John Doe"
}
]
}
```
## Transaction Timeout
Transactions have a timeout mechanism to prevent hanging transactions:
- Default timeout: 5 seconds (5000ms)
- Maximum timeout: 30 seconds (30000ms)
- If a transaction isn't committed or rolled back within its TTL, it will be automatically rolled back
## Best Practices
1. **Keep transactions short**: Long-running transactions can lead to resource contention.
2. **Set appropriate TTL**: Choose a TTL that gives your operations enough time to complete, but not so long that resources are unnecessarily tied up.
3. **Always commit or rollback**: Explicitly commit or rollback transactions rather than relying on automatic timeout.
4. **Error handling**: Implement proper error handling in your client code to rollback transactions if operations fail.
5. **Avoid unnecessary transactions**: For single operations, you don't need to use transactions.
## Transaction Example Workflow
```javascript
// 1. Create a transaction
const createTxResponse = await fetch('https://api.rushdb.com/api/v1/tx', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'token': 'RUSHDB_API_KEY'
},
body: JSON.stringify({ ttl: 10000 })
});
const { data: { id: txId } } = await createTxResponse.json();
try {
// 2. Perform operations within the transaction
await fetch('https://api.rushdb.com/api/v1/records', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'token': 'RUSHDB_API_KEY',
'X-Transaction-Id': txId
},
body: JSON.stringify({
label: 'Person',
properties: [
{ name: 'name', type: 'string', value: 'John Doe' }
]
})
});
// 3. Commit the transaction if all operations succeeded
await fetch(`https://api.rushdb.com/api/v1/tx/${txId}/commit`, {
method: 'POST',
headers: {
'token': 'RUSHDB_API_KEY'
}
});
} catch (error) {
// 4. Rollback the transaction if any operation failed
await fetch(`https://api.rushdb.com/api/v1/tx/${txId}/rollback`, {
method: 'POST',
headers: {
'token': 'RUSHDB_API_KEY'
}
});
throw error;
}
```
---
# Get API Key
In this section, we'll walk through the process of registering for RushDB and generating an API token necessary for using the RushDB SDK. This token is essential for authenticating your application's requests to the RushDB backend.
## Step 1: Sign Up for RushDB
First, you need to create a RushDB account. Go to the [RushDB sign-up page](https://app.rushdb.com/signup) and register using your email address or via third-party authentication providers.
## Step 2: Create a Project
Once signed in, you'll be directed to the dashboard. To start working with RushDB, you need to create a project where your records will be stored and managed.
- Click on the **Create Project** button to set up a new project. You might need to provide some basic information about your project, such as its name.

## Step 3: Copy an API Key
After you create your project, you’ll be taken to its Help page, where an API key will already be available. If needed, you can create additional API tokens on the **API Keys** tab.

- In the Authorization section, click the automatically generated API token to copy it. This token will be used to authenticate your SDK instances and allow them to interact with your RushDB project.
**Important:** Keep your API token secure and do not share it publicly. This token provides access to your RushDB project and the data within it.
With your API token generated, you're now ready to initialize the RushDB SDK in your application and begin creating and managing Records programmatically. Proceed to the next section to learn about integrating the SDK into your project.
---
# Deployment Guide
This guide provides comprehensive instructions for deploying RushDB in various environments. Choose the deployment option that best suits your needs.
## Deployment Options
RushDB offers two primary deployment options:
1. **RushDB Cloud (Managed Service)** - The simplest option with zero setup
2. **Self-Hosted RushDB** - Full control over your infrastructure with multiple deployment methods
## Option 1: RushDB Cloud (Managed Service)
The easiest way to start using RushDB is through the managed cloud service.
### Features
- Zero setup required
- Free tier available
- Fully managed infrastructure
- Automatic updates and maintenance
- Professional support
### Getting Started with RushDB Cloud
1. Sign up at [app.rushdb.com](https://app.rushdb.com)
2. Create a new project
3. Get your API token from the dashboard
4. Start using RushDB APIs via SDKs or REST
## Option 2: Self-Hosted RushDB
Self-hosting gives you complete control over your RushDB deployment and data.
### Prerequisites
Before deploying RushDB, ensure you have:
1. **Neo4j Instance**:
- Minimum version: `5.25.1`
- Required plugins:
- `apoc-core` (installed and enabled)
- `graph-data-science` (required for vector search capabilities)
- Can be self-hosted or using Neo4j Aura cloud service
2. **For Docker Deployment**:
- Docker Engine 20.10.0+
- Docker Compose 2.0.0+ (if using Docker Compose)
- Minimum 2GB RAM for the container
3. **For AWS Deployment**:
- AWS account with necessary permissions
- Terraform 1.0.0+ installed locally
### Option 2A: Docker Container Deployment
The simplest way to self-host RushDB is using Docker.
#### Basic Docker Run Command
```bash
docker run -p 3000:3000 \
--name rushdb \
-e NEO4J_URL='neo4j+s://your-neo4j-instance.databases.neo4j.io' \
-e NEO4J_USERNAME='neo4j' \
-e NEO4J_PASSWORD='your-password' \
rushdb/platform
```
#### Docker Compose Deployment
Create a `docker-compose.yml` file:
```yaml
version: '3.8'
services:
rushdb:
image: rushdb/platform
container_name: rushdb
ports:
- "3000:3000"
environment:
- NEO4J_URL=neo4j+s://your-neo4j-instance.databases.neo4j.io
- NEO4J_USERNAME=neo4j
- NEO4J_PASSWORD=your-password
# Add additional environment variables as needed
```
Then run:
```bash
docker-compose up -d
```
#### All-in-One Docker Compose Deployment (with Neo4j)
For development or testing environments, you can run both RushDB and Neo4j together:
docker-compose.yml
```yaml
version: '3.8'
services:
rushdb:
image: rushdb/platform
container_name: rushdb
depends_on:
neo4j:
condition: service_healthy
ports:
- "3000:3000"
environment:
- NEO4J_URL=bolt://neo4j
- NEO4J_USERNAME=neo4j
- NEO4J_PASSWORD=password
# Add additional environment variables as needed
neo4j:
image: neo4j:5.25.1
healthcheck:
test: [ "CMD-SHELL", "wget --no-verbose --tries=1 --spider localhost:7474 || exit 1" ]
interval: 5s
retries: 30
start_period: 10s
ports:
- "7474:7474"
- "7687:7687"
environment:
- NEO4J_ACCEPT_LICENSE_AGREEMENT=yes
- NEO4J_AUTH=neo4j/password
- NEO4J_PLUGINS=["apoc", "graph-data-science"]
volumes:
- neo4j-plugins:/var/lib/neo4j/plugins
- neo4j-data:/data
- neo4j-logs:/logs
- neo4j-conf:/var/lib/neo4j/conf
volumes:
neo4j-plugins:
neo4j-data:
neo4j-logs:
neo4j-conf:
```
### Option 2B: AWS Deployment with Terraform
For production-grade deployments, RushDB can be deployed to AWS using Terraform.
#### Terraform Deployment Steps
1. **Prepare Your Environment**
Clone the RushDB repository or create a new directory for your Terraform configuration.
2. **Create Terraform Configuration File**
Create a `main.tf` file with the following content (adjust as needed):
rushdb-terraform.tf
```hcl
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 4.0"
}
}
}
# Configure AWS provider
provider "aws" {
region = "us-east-1" # Change to your preferred region
}
# Use default VPC and subnets
data "aws_vpc" "default" {
default = true
}
data "aws_subnets" "all" {
filter {
name = "vpc-id"
values = [data.aws_vpc.default.id]
}
}
# IAM role for ECS task execution
resource "aws_iam_role" "ecs_task_execution_role" {
name = "rushdb-ecs-task-execution-role"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Action = "sts:AssumeRole"
Effect = "Allow"
Principal = {
Service = "ecs-tasks.amazonaws.com"
}
}
]
})
}
resource "aws_iam_role_policy_attachment" "ecs_task_execution_policy" {
role = aws_iam_role.ecs_task_execution_role.name
policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy"
}
resource "aws_iam_role_policy_attachment" "cloudwatch_logs_access" {
role = aws_iam_role.ecs_task_execution_role.name
policy_arn = "arn:aws:iam::aws:policy/CloudWatchLogsFullAccess"
}
# CloudWatch log group for application logs
resource "aws_cloudwatch_log_group" "rushdb_logs" {
name = "/ecs/rushdb"
retention_in_days = 30
tags = {
Name = "rushdb-logs"
Environment = "production"
}
}
# Security group for RushDB
resource "aws_security_group" "rushdb_sg" {
name = "rushdb-security-group"
description = "Allow traffic for RushDB"
vpc_id = data.aws_vpc.default.id
ingress {
from_port = 0
to_port = 0
protocol = "-1"
self = true
cidr_blocks = ["0.0.0.0/0"]
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
}
# ECS cluster
resource "aws_ecs_cluster" "rushdb_cluster" {
name = "rushdb-ecs-cluster"
}
# Task execution role
resource "aws_iam_role" "ecs_task_execution_role" {
name = "rushdb-ecs-task-execution-role"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Action = "sts:AssumeRole"
Effect = "Allow"
Principal = {
Service = "ecs-tasks.amazonaws.com"
}
}
]
})
}
resource "aws_iam_role_policy_attachment" "ecs_task_execution_policy" {
role = aws_iam_role.ecs_task_execution_role.name
policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy"
}
# ECS task definition
resource "aws_ecs_task_definition" "rushdb_task" {
family = "rushdb-task-definition"
network_mode = "awsvpc"
requires_compatibilities = ["FARGATE"]
cpu = "1024"
memory = "2048"
execution_role_arn = aws_iam_role.ecs_task_execution_role.arn
container_definitions = jsonencode([{
name = "rushdb"
image = "rushdb/platform:latest"
essential = true
environment = [
{ name = "NEO4J_URL", value = "neo4j+s://your-neo4j-instance.databases.neo4j.io" },
{ name = "NEO4J_USERNAME", value = "neo4j" },
{ name = "NEO4J_PASSWORD", value = "your-password" },
{ name = "RUSHDB_SELF_HOSTED", value = "true" },
{ name = "RUSHDB_AES_256_ENCRYPTION_KEY", value = "your-32-character-encryption-key" }
]
portMappings = [{
containerPort = 3000
hostPort = 3000
protocol = "tcp"
}]
logConfiguration = {
logDriver = "awslogs"
options = {
"awslogs-group" = aws_cloudwatch_log_group.rushdb_logs.name
"awslogs-region" = "us-east-1"
"awslogs-stream-prefix" = "ecs-rushdb"
}
}
}])
}
# ECS service
resource "aws_ecs_service" "rushdb_service" {
name = "rushdb-ecs-service"
cluster = aws_ecs_cluster.rushdb_cluster.id
task_definition = aws_ecs_task_definition.rushdb_task.arn
desired_count = 1
launch_type = "FARGATE"
network_configuration {
subnets = data.aws_subnets.all.ids
security_groups = [aws_security_group.rushdb_sg.id]
assign_public_ip = true
}
depends_on = [
aws_cloudwatch_log_group.rushdb_logs
]
}
# Output the CloudWatch log group for easy access
output "cloudwatch_log_group" {
value = aws_cloudwatch_log_group.rushdb_logs.name
description = "CloudWatch log group name for RushDB application logs"
}
# Output the service URL
output "rushdb_public_ip_note" {
value = "Check the ECS service in AWS Console for the public IP address"
description = "Note about accessing RushDB service"
}
```
3. **Initialize Terraform**
```bash
terraform init
```
4. **Plan Deployment**
```bash
terraform plan -out=tfplan
```
5. **Apply the Configuration**
```bash
terraform apply tfplan
```
6. **Access Your RushDB Service**
After deployment completes, Terraform will output information about your deployment including the CloudWatch log group name.
#### Viewing Application Logs
To view your RushDB application logs:
1. **Using AWS Console**:
- Go to CloudWatch in the AWS Console
- Navigate to "Log groups"
- Find the log group `/ecs/rushdb`
- Click on it to view log streams with prefix `ecs-rushdb`
2. **Using AWS CLI**:
```bash
# List log streams
aws logs describe-log-streams --log-group-name "/ecs/rushdb"
# View recent logs
aws logs tail "/ecs/rushdb" --follow
```
#### Advanced AWS Deployment with Load Balancer and SSL
For a production deployment with a load balancer and SSL:
1. Modify the Terraform configuration to include an Application Load Balancer
2. Add Route53 DNS records
3. Configure SSL certificates using ACM
For a complete example with these features, refer to the `main.tf` in the RushDB repository.
## Environment Variables
The following environment variables can be used to configure your RushDB deployment:
### Required Environment Variables
| Variable | Description | Example |
|----------|-------------|---------|
| `NEO4J_URL` | Connection string for Neo4j database | `neo4j+s://your-instance.databases.neo4j.io` or `bolt://localhost:7687` |
| `NEO4J_USERNAME` | Username for Neo4j database | `neo4j` |
| `NEO4J_PASSWORD` | Password for Neo4j database | `your-password` |
### Core Application Settings
| Variable | Description | Default | Required |
|----------|----------------------------------------------|---------|----------|
| `RUSHDB_PORT` | Port for the application server | `3000` | No |
| `RUSHDB_AES_256_ENCRYPTION_KEY` | 32-character key for token encryption | `32SymbolStringForTokenEncryption` | Yes, for production |
| `RUSHDB_DASHBOARD_URL` | URL for dashboard access | `/` | No |
| `RUSHDB_SELF_HOSTED` | Whether running in self-hosted mode | `true` | No |
| `RUSHDB_SERVE_STATIC` | Whether to serve static files (Dashboard UI) | `true` | No |
### Authentication Settings
| Variable | Description | Default | Required |
|----------|-------------|---------|----------|
| `RUSHDB_LOGIN` | Admin username | `admin` | No |
| `RUSHDB_PASSWORD` | Admin password | `password` | Yes, for production |
| `RUSHDB_ALLOWED_LOGINS` | List of allowed login usernames | `[]` (all allowed) | No |
### Rate Limiting
| Variable | Description | Default | Required |
|----------|-------------|---------|----------|
| `RATE_LIMITER_REQUESTS_LIMIT` | Max requests within time frame | `10` | No |
| `RATE_LIMITER_TTL` | Time frame for rate limiting (ms) | `1000` | No |
### OAuth and Authentication
| Variable | Description | Required |
|----------|-------------|----------|
| `GOOGLE_CLIENT_ID` | Google OAuth client ID | For Google auth |
| `GOOGLE_SECRET` | Google OAuth secret | For Google auth |
| `GH_CLIENT_ID` | GitHub OAuth client ID | For GitHub auth |
| `GH_SECRET` | GitHub OAuth secret | For GitHub auth |
| `SERVICE_CAPTCHA_KEY` | CAPTCHA service private key | For CAPTCHA |
### Email Configuration
| Variable | Description | Required |
|----------|-------------|----------|
| `MAIL_HOST` | Email service host | For email |
| `MAIL_USER` | Email service username | For email |
| `MAIL_PASSWORD` | Email service password | For email |
| `MAIL_FROM` | Default "from" email address | For email |
## CLI Commands
RushDB provides CLI commands for managing users in self-hosted installations:
### Create a New User
```bash
rushdb create-user
```
Example:
```bash
rushdb create-user admin@example.com securepassword123
```
### Update User Password
```bash
rushdb update-password
```
Example:
```bash
rushdb update-password admin@example.com newsecurepassword456
```
## Security Best Practices
When deploying RushDB to production, follow these security best practices:
1. **Change default credentials**:
- Change `RUSHDB_LOGIN` and `RUSHDB_PASSWORD`
- Use a strong, unique `RUSHDB_AES_256_ENCRYPTION_KEY`
2. **Secure your Neo4j database**:
- Use strong passwords
- Limit network access to the database
- Use encrypted connections where possible
3. **Use HTTPS**:
- Configure SSL/TLS on your load balancer
- Redirect HTTP to HTTPS
4. **Set up proper monitoring and logging**:
- Monitor API usage
- Set up alerts for unusual activity
## System Requirements
### Minimum Specifications
- **CPU**: 1 vCPU (2+ recommended for production)
- **Memory**: 1GB RAM (2GB+ recommended for production)
- **Storage**: 1GB for RushDB (excluding Neo4j storage requirements)
- **Neo4j Requirements**: Refer to [Neo4j system requirements](https://neo4j.com/docs/operations-manual/current/installation/requirements/)
### Recommended Production Specifications
- **CPU**: 2+ vCPUs
- **Memory**: 4GB+ RAM
- **Storage**: SSD storage for both RushDB and Neo4j
- **Network**: Low-latency connection between RushDB and Neo4j
## Troubleshooting
### Common Issues
1. **Connection Issues to Neo4j**:
- Ensure Neo4j instance is running and accessible
- Verify credentials and connection string format
- Check network connectivity and firewall settings
2. **Authentication Failures**:
- Verify admin credentials are correctly set
- Check encryption key length (must be 32 characters)
3. **Performance Issues**:
- Monitor resource utilization
- Consider scaling up resources or optimizing Neo4j queries
### Getting Help
If you encounter problems with your RushDB deployment:
1. Check the RushDB logs for error messages
2. Visit the [RushDB documentation](https://docs.rushdb.com)
3. Submit an issue on the [RushDB GitHub repository](https://github.com/rush-db/rushdb)
## Conclusion
Following this guide, you should have successfully deployed RushDB in your chosen environment. Whether you're using the managed cloud service or self-hosting, RushDB provides a powerful database solution for modern applications.
---
# Importing data from external sources
RushDB provides comprehensive toolkit to import data. While most of the data sources operate with flat tabular
data and context awareness emerges at query time, in RushDB instead relationships are static because of graph nature.
This guide will help you to import your data and make it breathe: relationships, types inferring and records itself are
created easily.
What you'll use:
- `records.createMany` (JSON and CSV import)
- `relationships.createMany` (bulk linking by key match)
Tip: You can do the same via REST. See REST docs: Records Import and Relationships API.
## Core pattern: import first, then link by external keys
Most external systems already have stable identifiers (MongoDB's ObjectId, HubSpot record IDs, SQL primary/foreign keys).
When importing to RushDB, store those external IDs on your records (e.g., `mongoId`, `hubspotId`, `pgId`). Then create
relationships by matching those keys using `relationships.createMany`:
1) Import data (keep external IDs as properties).
2) Create relationships by joining `source[key] = target[key]`.
Safeguards and notes
- You control the relationship `type` and `direction` (default direction is `out`).
- For key-based creation, provide both `source.key` and `target.key`.
- Only use `manyToMany` when you explicitly want a cartesian link across filtered sets.
---
## 1) MongoDB → RushDB
Goal: Import MongoDB collections (e.g., `users`, `orders`) and connect them using Mongo's ObjectId values.
Recommended mapping
- Persist the original `_id` as a string field on the RushDB record: `mongoId`.
- For references (e.g., `orders.userId`), persist as `userMongoId` so you can join `users.mongoId = orders.userMongoId`.
### Example: TypeScript (Node.js)
```ts
import RushDB from '@rushdb/javascript-sdk'
import { MongoClient, ObjectId } from 'mongodb'
const db = new RushDB(process.env.RUSHDB_API_KEY!)
async function run() {
const mongo = await MongoClient.connect(process.env.MONGO_URI!)
const mdb = mongo.db('acme')
// 1) Extract from Mongo
const users = await mdb.collection('users').find({ tenantId: 'ACME' }).toArray()
const orders = await mdb.collection('orders').find({ tenantId: 'ACME' }).toArray()
// 2) Normalize docs for RushDB
const usersPayload = users.map(u => ({
mongoId: String(u._id), // keep external id as string
tenantId: u.tenantId,
name: u.name,
email: u.email
}))
const ordersPayload = orders.map(o => ({
mongoId: String(o._id),
tenantId: o.tenantId,
total: o.total,
// capture the referenced user id for later linking
userMongoId: String(o.userId instanceof ObjectId ? o.userId : new ObjectId(o.userId))
}))
// 3) Import into RushDB
await db.records.createMany({ label: 'USER', data: usersPayload })
await db.records.createMany({ label: 'ORDER', data: ordersPayload })
// 4) Link: USER -[:ORDERED]-> ORDER using mongo ids
await db.relationships.createMany({
source: { label: 'USER', key: 'mongoId', where: { tenantId: 'ACME' } },
target: { label: 'ORDER', key: 'userMongoId', where: { tenantId: 'ACME' } },
type: 'ORDERED',
direction: 'out'
})
await mongo.close()
}
run().catch(console.error)
```
### Example: REST (create-many)
```json
POST /api/v1/relationships/create-many
{
"source": { "label": "USER", "key": "mongoId", "where": { "tenantId": "ACME" } },
"target": { "label": "ORDER", "key": "userMongoId", "where": { "tenantId": "ACME" } },
"type": "ORDERED",
"direction": "out"
}
```
Common pitfalls
- Ensure you convert `ObjectId` to string when storing in RushDB; the join is string equality.
- Keep tenant/workspace scoping in your `where` filters to avoid cross-tenant links.
---
## 2) HubSpot → RushDB
Goal: Import HubSpot objects (Contacts, Companies, Deals) and connect them using HubSpot IDs.
Recommended mapping
- Store the HubSpot object ID on the record (e.g., `hubspotId`).
- For associations, store the associated object’s HubSpot ID on the related record (e.g., a Deal with `companyHubspotId`).
### Example: TypeScript (using official HubSpot client)
```ts
import RushDB from '@rushdb/javascript-sdk'
import Hubspot from '@hubspot/api-client'
const db = new RushDB(process.env.RUSHDB_API_KEY!)
const hubspot = new Hubspot.Client({ accessToken: process.env.HUBSPOT_TOKEN! })
async function importHubspot() {
// 1) Fetch Contacts and Companies
const contactsRes = await hubspot.crm.contacts.basicApi.getPage(100, undefined, ['email'])
const companiesRes = await hubspot.crm.companies.basicApi.getPage(100, undefined, ['name', 'domain'])
const contacts = contactsRes.results.map(c => ({
hubspotId: c.id,
email: c.properties?.email,
tenantId: 'ACME'
}))
const companies = companiesRes.results.map(co => ({
hubspotId: co.id,
name: co.properties?.name,
domain: co.properties?.domain,
tenantId: 'ACME'
}))
// 2) Import
await db.records.createMany({ label: 'HS_CONTACT', data: contacts })
await db.records.createMany({ label: 'HS_COMPANY', data: companies })
// 3) Associate Contacts to Companies by joining HubSpot IDs
// If you exported associations separately, persist contact.companyHubspotId on contact
// Example join: HS_CONTACT.companyHubspotId = HS_COMPANY.hubspotId
await db.relationships.createMany({
source: { label: 'HS_CONTACT', key: 'companyHubspotId', where: { tenantId: 'ACME' } },
target: { label: 'HS_COMPANY', key: 'hubspotId', where: { tenantId: 'ACME' } },
type: 'WORKS_AT',
direction: 'out'
})
}
importHubspot().catch(console.error)
```
Alternative: Deals to Companies
```ts
await db.relationships.createMany({
source: { label: 'HS_DEAL', key: 'companyHubspotId', where: { tenantId: 'ACME' } },
target: { label: 'HS_COMPANY', key: 'hubspotId', where: { tenantId: 'ACME' } },
type: 'RELATED_TO',
direction: 'out'
})
```
Notes
- HubSpot v3 uses string IDs; storing them verbatim is fine for equality joins.
- If you rely on HubSpot association APIs, mirror those association IDs onto one side to enable the key match.
---
## 3) PostgreSQL → RushDB
Goal: Import relational tables (e.g., `users`, `orders`) and connect them using primary/foreign keys.
Recommended mapping
- Store the SQL primary key as `pgId` (for `users`) and the foreign key as `userPgId` (for `orders`).
Then join `USER.pgId = ORDER.userPgId`.
### Example: TypeScript (Node.js) using `pg`
```ts
import RushDB from '@rushdb/javascript-sdk'
import { Client } from 'pg'
const db = new RushDB(process.env.RUSHDB_API_KEY!)
async function importPg() {
const client = new Client({ connectionString: process.env.PG_URI })
await client.connect()
// 1) Extract
const usersRes = await client.query('select id, name, email, tenant_id from users where tenant_id = $1', ['ACME'])
const ordersRes = await client.query('select id, user_id, total, tenant_id from orders where tenant_id = $1', ['ACME'])
// 2) Normalize
const users = usersRes.rows.map(r => ({ pgId: String(r.id), name: r.name, email: r.email, tenantId: r.tenant_id }))
const orders = ordersRes.rows.map(r => ({ pgId: String(r.id), userPgId: String(r.user_id), total: r.total, tenantId: r.tenant_id }))
// 3) Import
await db.records.createMany({ label: 'USER', data: users })
await db.records.createMany({ label: 'ORDER', data: orders })
// 4) Link: USER -[:ORDERED]-> ORDER by key equality
await db.relationships.createMany({
source: { label: 'USER', key: 'pgId', where: { tenantId: 'ACME' } },
target: { label: 'ORDER', key: 'userPgId', where: { tenantId: 'ACME' } },
type: 'ORDERED',
direction: 'out'
})
await client.end()
}
importPg().catch(console.error)
```
### CSV path (no code runtime)
If you export tables to CSV, you can import with REST `POST /api/v1/records/import/csv` or SDK `records.createMany`, then run the same `relationships.createMany` call as above by joining the columns you preserved (e.g., `pgId` and `userPgId`).
---
## 4) Supabase → RushDB
Supabase uses PostgreSQL under the hood, so the mapping mirrors the PostgreSQL example. If you prefer the Supabase client:
```ts
import RushDB from '@rushdb/javascript-sdk'
import { createClient } from '@supabase/supabase-js'
const db = new RushDB(process.env.RUSHDB_API_KEY!)
const supabase = createClient(process.env.SUPABASE_URL!, process.env.SUPABASE_SERVICE_ROLE_KEY!)
async function importSupabase() {
// 1) Extract
const { data: users, error: uerr } = await supabase
.from('users')
.select('id,name,email,tenant_id')
.eq('tenant_id', 'ACME')
if (uerr) throw uerr
const { data: orders, error: oerr } = await supabase
.from('orders')
.select('id,user_id,total,tenant_id')
.eq('tenant_id', 'ACME')
if (oerr) throw oerr
// 2) Normalize
const usersPayload = (users ?? []).map(r => ({ pgId: String(r.id), name: r.name, email: r.email, tenantId: r.tenant_id }))
const ordersPayload = (orders ?? []).map(r => ({ pgId: String(r.id), userPgId: String(r.user_id), total: r.total, tenantId: r.tenant_id }))
// 3) Import and link
await db.records.createMany({ label: 'USER', data: usersPayload })
await db.records.createMany({ label: 'ORDER', data: ordersPayload })
await db.relationships.createMany({
source: { label: 'USER', key: 'pgId', where: { tenantId: 'ACME' } },
target: { label: 'ORDER', key: 'userPgId', where: { tenantId: 'ACME' } },
type: 'ORDERED',
direction: 'out'
})
}
importSupabase().catch(console.error)
```
---
## 5) Firebase (Firestore) → RushDB
Map Firestore document IDs to a stable key. Example with collections `users` and `orders` (each order has `userId` that equals a user doc id):
```ts
import RushDB from '@rushdb/javascript-sdk'
import admin from 'firebase-admin'
const db = new RushDB(process.env.RUSHDB_API_KEY!)
admin.initializeApp({
credential: admin.credential.applicationDefault(),
projectId: process.env.GCLOUD_PROJECT
})
const fs = admin.firestore()
async function importFirestore() {
// 1) Fetch
const usersSnap = await fs.collection('users').where('tenantId', '==', 'ACME').get()
const ordersSnap = await fs.collection('orders').where('tenantId', '==', 'ACME').get()
// 2) Normalize
const users = usersSnap.docs.map(d => ({
firebaseId: d.id,
tenantId: d.get('tenantId'),
name: d.get('name'),
email: d.get('email')
}))
const orders = ordersSnap.docs.map(d => ({
firebaseId: d.id,
tenantId: d.get('tenantId'),
total: d.get('total'),
userFirebaseId: String(d.get('userId')) // reference to users doc id
}))
// 3) Import and link
await db.records.createMany({ label: 'USER', data: users })
await db.records.createMany({ label: 'ORDER', data: orders })
await db.relationships.createMany({
source: { label: 'USER', key: 'firebaseId', where: { tenantId: 'ACME' } },
target: { label: 'ORDER', key: 'userFirebaseId', where: { tenantId: 'ACME' } },
type: 'ORDERED',
direction: 'out'
})
}
importFirestore().catch(console.error)
```
Notes
- For multi-tenant Firestore, include a `tenantId` field and filter `where` accordingly.
- If orders reference users via DocumentReference objects, resolve to `ref.id` when building the payload.
---
## 6) Airtable → RushDB
Use Airtable record IDs for joins. Example: link Contacts to Companies where a Contact has a single `companyId` field storing the linked record ID.
```ts
import RushDB from '@rushdb/javascript-sdk'
import Airtable from 'airtable'
const db = new RushDB(process.env.RUSHDB_API_KEY!)
const base = new Airtable({ apiKey: process.env.AIRTABLE_TOKEN! }).base(process.env.AIRTABLE_BASE_ID!)
async function importAirtable() {
// 1) Fetch
const companiesTable = base('Companies')
const contactsTable = base('Contacts')
const companies = await companiesTable.select({ pageSize: 100 }).all()
const contacts = await contactsTable.select({ pageSize: 100 }).all()
// 2) Normalize
const companiesPayload = companies.map(r => ({
airtableId: r.id,
tenantId: 'ACME',
name: r.get('Name') as string,
domain: (r.get('Domain') as string) || undefined
}))
const contactsPayload = contacts.map(r => ({
airtableId: r.id,
tenantId: 'ACME',
name: r.get('Name') as string,
email: (r.get('Email') as string) || undefined,
// If you use Airtable "Link to another record", it returns an array of record IDs
companyAirtableId: Array.isArray(r.get('Company')) && (r.get('Company') as string[])[0]
? (r.get('Company') as string[])[0]
: undefined
}))
// 3) Import and link
await db.records.createMany({ label: 'AT_COMPANY', data: companiesPayload })
await db.records.createMany({ label: 'AT_CONTACT', data: contactsPayload })
await db.relationships.createMany({
source: { label: 'AT_CONTACT', key: 'companyAirtableId', where: { tenantId: 'ACME' } },
target: { label: 'AT_COMPANY', key: 'airtableId', where: { tenantId: 'ACME' } },
type: 'WORKS_AT',
direction: 'out'
})
}
importAirtable().catch(console.error)
```
Notes
- If a contact can link to multiple companies, iterate those IDs and use `records.attach` per contact, or pre-expand into multiple joinable rows.
---
## 7) Notion → RushDB
Use Notion page IDs for joins. Example: People and Tasks databases; each Task has a single-person relation stored in `assignee`.
```ts
import RushDB from '@rushdb/javascript-sdk'
import { Client } from '@notionhq/client'
const db = new RushDB(process.env.RUSHDB_API_KEY!)
const notion = new Client({ auth: process.env.NOTION_TOKEN! })
async function importNotion() {
const peopleDbId = process.env.NOTION_PEOPLE_DB_ID!
const tasksDbId = process.env.NOTION_TASKS_DB_ID!
// 1) Fetch
const peopleRes = await notion.databases.query({ database_id: peopleDbId })
const tasksRes = await notion.databases.query({ database_id: tasksDbId })
// 2) Normalize
const people = peopleRes.results.map(p => ({
notionId: p.id,
tenantId: 'ACME',
name: (p as any).properties?.Name?.title?.[0]?.plain_text || 'Unknown'
}))
const tasks = tasksRes.results.map(t => {
const props = (t as any).properties
const assignees = props?.assignee?.relation as Array<{ id: string }> | undefined
const firstAssigneeId = assignees && assignees.length ? assignees[0].id : undefined
return {
notionId: t.id,
tenantId: 'ACME',
title: props?.Name?.title?.[0]?.plain_text || 'Untitled',
assigneeNotionId: firstAssigneeId
}
})
// 3) Import and link (single-assignee example)
await db.records.createMany({ label: 'NT_PERSON', data: people })
await db.records.createMany({ label: 'NT_TASK', data: tasks })
await db.relationships.createMany({
source: { label: 'NT_TASK', key: 'assigneeNotionId', where: { tenantId: 'ACME' } },
target: { label: 'NT_PERSON', key: 'notionId', where: { tenantId: 'ACME' } },
type: 'ASSIGNED_TO',
direction: 'out'
})
}
importNotion().catch(console.error)
```
Notes
- If a Task can have multiple assignees, either:
- iterate assignee IDs and call `records.attach` per Task, or
- pre-expand into multiple Task rows (each with a single `assigneeNotionId`) before import to keep `createMany`-by-key workflow.
---
## Python equivalents (quick reference)
Below are the equivalent Python SDK calls once you have your lists of dicts ready:
```python
from rushdb import RushDB
db = RushDB("RUSHDB_API_KEY")
# Import
db.records.create_many(label="USER", data=users)
db.records.create_many(label="ORDER", data=orders)
# Link by key equality
db.relationships.create_many(
source={"label": "USER", "key": "mongoId", "where": {"tenantId": "ACME"}},
target={"label": "ORDER", "key": "userMongoId", "where": {"tenantId": "ACME"}},
type="ORDERED",
direction="out",
)
```
---
## Troubleshooting
- Mismatched types: Ensure the join keys are the same type (strings are safest). Convert DB-specific IDs to strings before import.
- Missing keys: Key-based mode requires both `source.key` and `target.key`. If you truly need cartesian linking, set `manyToMany: true` and provide non-empty `where` on both sides.
- Scope filters: Always restrict with `where` (e.g., `tenantId`) to avoid unintended cross-linking.
## See also
- TypeScript SDK: [Relationships](../typescript-sdk/relationships) · [Import Data](../typescript-sdk/records/import-data)
- Python SDK: [Relationships](../python-sdk/relationships) · [Import Data](../python-sdk/records/import-data)
- REST API: [Relationships API](../rest-api/relationships) · [Records Import](../rest-api/records/import-data)
---
# Local Setup
This guide will help you set up a local development environment for RushDB using Docker, without needing to clone the repository. This is ideal for developers who want to work with RushDB in a containerized environment.
## Prerequisites
Before starting, ensure you have the following installed:
1. **Docker Engine**:
- For macOS: [Docker Desktop for Mac](https://docs.docker.com/desktop/install/mac-install/)
- For Windows: [Docker Desktop for Windows](https://docs.docker.com/desktop/install/windows-install/)
- For Linux: [Docker Engine](https://docs.docker.com/engine/install/)
2. **Docker Compose** (included with Docker Desktop, but may need to be installed separately on Linux)
3. **Recommended System Resources**:
- Minimum: 2GB RAM, 1 CPU
- Recommended: 4GB RAM, 2 CPUs
## Option 1: Quick Setup with External Neo4j Instance
If you already have a Neo4j instance running (either locally or in the cloud), you can quickly start RushDB connected to it using Docker.
### Using Docker Run Command
```bash
docker run -p 3000:3000 \
--name rushdb \
-e NEO4J_URL='neo4j+s://your-neo4j-instance.databases.neo4j.io' \
-e NEO4J_USERNAME='neo4j' \
-e NEO4J_PASSWORD='your-password' \
rushdb/platform
```
### Using Docker Compose
Create a `docker-compose.yml` file with the following content:
```yaml
version: '3.8'
services:
rushdb:
image: rushdb/platform
container_name: rushdb
ports:
- "3000:3000"
environment:
- NEO4J_URL=neo4j+s://your-neo4j-instance.databases.neo4j.io
- NEO4J_USERNAME=neo4j
- NEO4J_PASSWORD=your-password
```
Then run:
```bash
docker-compose up -d
```
## Option 2: Complete Development Environment with Neo4j
For a fully self-contained development environment with both RushDB and Neo4j:
### Create a Development Docker Compose Setup
1. Create a `docker-compose.yml` file with the following content:
docker-compose.yml
```yaml
version: '3.8'
services:
rushdb:
image: rushdb/platform
container_name: rushdb
depends_on:
neo4j:
condition: service_healthy
ports:
- "3000:3000"
environment:
- NEO4J_URL=bolt://neo4j
- NEO4J_USERNAME=neo4j
- NEO4J_PASSWORD=password
neo4j:
image: neo4j:5.25.1
healthcheck:
test: [ "CMD-SHELL", "wget --no-verbose --tries=1 --spider localhost:7474 || exit 1" ]
interval: 5s
retries: 30
start_period: 10s
ports:
- "7474:7474"
- "7687:7687"
environment:
- NEO4J_ACCEPT_LICENSE_AGREEMENT=yes
- NEO4J_AUTH=neo4j/password
- NEO4J_PLUGINS=["apoc", "graph-data-science"]
volumes:
- neo4j-plugins:/var/lib/neo4j/plugins
- neo4j-data:/data
- neo4j-logs:/logs
- neo4j-conf:/var/lib/neo4j/conf
volumes:
neo4j-plugins:
neo4j-data:
neo4j-logs:
neo4j-conf:
```
2. Start the environment:
```bash
docker-compose up -d
```
3. Verify both services are running:
```bash
docker-compose ps
```
## Accessing the Development Environment
Once your containers are running:
1. **RushDB Dashboard**: Access at `http://localhost:3000`
2. **Neo4j Browser**: Access at `http://localhost:7474` (if using the local Neo4j setup)
### Default Credentials
- **RushDB Dashboard**:
- Username: `admin`
- Password: `password`
- **Neo4j Browser** (if using local Neo4j):
- Username: `neo4j`
- Password: `password`
## Advanced Development Workflow
### 1. Exposing Additional Ports
If you need to expose additional ports for development:
```yaml
services:
rushdb:
# ...existing configuration...
ports:
- "3000:3000"
- "9229:9229" # For Node.js debugging
```
### 2. Using Local Volume Mounts
For more extensive development, you might want to mount local files into the container:
```yaml
services:
rushdb:
# ...existing configuration...
volumes:
- ./your-local-code:/app/platform/core/src
```
### 3. Persistent Data Storage
The default configuration includes volume mounts for Neo4j data persistence. Your data will survive container restarts.
### Environment Variables
Before running the container, ensure you provide the following required environment variables:
- **`NEO4J_URL`**: The connection string for your Neo4j database (e.g., `neo4j+s://.databases.neo4j.io`).
- **`NEO4J_USERNAME`**: The username for accessing the Neo4j database (default is `neo4j`).
- **`NEO4J_PASSWORD`**: The password for your Neo4j database instance.
### Additional Environment Variables
#### 1. `RUSHDB_PORT`
- **Description**: The port on which the application server will listen for incoming requests.
- **Default**: `3000`
#### 2. `RUSHDB_AES_256_ENCRYPTION_KEY`
- **Description**: The encryption key for securing API tokens using AES-256 encryption.
- **Requirement**: Must be exactly 32 characters long to meet the 256-bit key length requirement.
- **Important**: Change this to a secure value in production.
- **Default**: `32SymbolStringForTokenEncryption`
#### 3. `RUSHDB_LOGIN`
- **Description**: The login username for the RushDB admin account.
- **Important**: Change this to a secure value in production.
- **Default**: `admin`
#### 4. `RUSHDB_PASSWORD`
- **Description**: The password for the RushDB admin account.
- **Important**: Change this to a secure value in production.
- **Default**: `password`
## Working with the RushDB CLI
The RushDB Docker image includes a command-line interface (CLI) that you can access from the running container.
### **CLI Commands**
The RushDB CLI allows you to manage users in self-hosted installations. Below are the available commands:
#### **Create a New User**
Command:
```bash
docker exec rushdb rushdb create-user
```
Example:
```bash
docker exec rushdb rushdb create-user admin@example.com securepassword123
```
This command creates a new user with the specified login and password. It is only allowed in self-hosted setups.
#### **Update User Password**
Command:
```bash
docker exec rushdb rushdb update-password
```
Example:
```bash
docker exec rushdb rushdb update-password admin@example.com newsecurepassword456
```
This command updates the password for an existing user identified by the provided login. Like `create-user`, this command is restricted to self-hosted environments.
## Troubleshooting Common Issues
### 1. Connection Issues to Neo4j
If RushDB cannot connect to Neo4j:
- Verify Neo4j is running: `docker ps | grep neo4j`
- Check Neo4j logs: `docker logs neo4j`
- Ensure credentials match in your environment variables
- If using the local Neo4j setup, ensure the hostname `neo4j` resolves to the Neo4j container
### 2. RushDB Container Fails to Start
If the RushDB container exits unexpectedly:
- Check logs with: `docker logs rushdb`
- Verify all required environment variables are set correctly
- Ensure Neo4j is fully initialized before RushDB attempts to connect
### 3. Memory Issues
If containers are being killed due to memory constraints:
- Increase Docker's memory allocation in Docker Desktop settings
- Consider reducing memory usage in Neo4j configuration
- Use the `--memory` flag with `docker run` or set memory limits in `docker-compose.yml`
## Next Steps
After successfully setting up your local development environment:
1. Explore the RushDB Dashboard at `http://localhost:3000`
2. Create your first project and database
3. Generate API tokens for your applications
4. Explore the API documentation available in the dashboard
5. Connect your applications to RushDB using the available SDKs or REST API
---
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
# Reusable SearchQuery
One of the most powerful concepts behind RushDB is its "fractal" API architecture with a self-aware design that's exposed through a consistent, easy-to-understand interface. This design philosophy allows you to use the same query structure across different aspects of your graph database, creating a highly flexible and intuitive developer experience.
## The Power of Consistency
At the heart of RushDB's API design is the SearchQuery pattern - a standardized way to query your data that remains consistent regardless of what entity you're working with: records, relationships, labels, or properties. This consistent approach brings several powerful benefits:
- **Reduced learning curve**: Learn the query pattern once, apply it everywhere
- **Predictable API usage**: No need to learn different filtering paradigms for different entity types
- **Code reusability**: Reuse query logic across different parts of your application
- **Self-discoverability**: The graph intrinsically knows its structure and exposes it consistently
## SearchQuery Structure
The SearchQuery object provides a standardized way to filter, sort, and paginate results:
```typescript
interface SearchQuery {
// Filter by record labels
labels?: string[];
// Filter by property values and relationships
where?: WhereClause;
// Maximum number of records to return (default: 100)
limit?: number;
// Number of records to skip (for pagination)
skip?: number;
// Sorting configuration
orderBy?: OrderByClause;
// Data aggregation and transformation
aggregate?: AggregateClause;
}
```
## Fractal API in Action
The power of RushDB's fractal API design becomes apparent when you see the same query structure used across different endpoints:
### 1. Searching Records
```typescript
// Find all active PRODUCT records with price between $10-$50
const products = await db.records.find({
labels: ['PRODUCT'],
where: {
active: true,
price: { $gte: 10, $lte: 50 }
},
orderBy: { price: 'asc' },
limit: 20
});
```
```python
# Find all active PRODUCT records with price between $10-$50
result = db.records.find({
"labels": ["PRODUCT"],
"where": {
"active": True,
"price": {"$gte": 10, "$lte": 50}
},
"orderBy": {"price": "asc"},
"limit": 20
})
products = result.data
```
```bash
# Find all active PRODUCT records with price between $10-$50
curl -X POST "https://api.rushdb.com/api/v1/records/search" \
-H "Authorization: Bearer $RUSHDB_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"labels": ["PRODUCT"],
"where": {
"active": true,
"price": {
"$gte": 10,
"$lte": 50
}
},
"orderBy": {"price": "asc"},
"limit": 20
}'
```
### 2. Deleting Records with the Same Query Structure
```typescript
// Delete discontinued products with zero inventory
await db.records.delete({
labels: ['PRODUCT'],
where: {
discontinued: true,
inventory: 0
}
});
```
```python
# Delete discontinued products with zero inventory
db.records.delete({
"labels": ["PRODUCT"],
"where": {
"discontinued": True,
"inventory": 0
}
})
```
```bash
# Delete discontinued products with zero inventory
curl -X PUT "https://api.rushdb.com/api/v1/records/delete" \
-H "Authorization: Bearer $RUSHDB_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"labels": ["PRODUCT"],
"where": {
"discontinued": true,
"inventory": 0
}
}'
```
### 3. Searching Relationships
```typescript
// Find all CREATED relationships by users in the admin group
const createdRelationships = await db.relationships.find({
where: {
groups: { $in: ["admin"] },
$relation: {
type: "CREATED"
}
},
limit: 50
});
```
```python
# Find all CREATED relationships by users in the admin group
created_relationships = db.relationships.find({
"where": {
"groups": { "$in": ["admin"] },
"$relation": {
"type": "CREATED"
}
},
"limit": 50
})
```
```bash
# Find all CREATED relationships by users in the admin group
curl -X POST "https://api.rushdb.com/api/v1/relationships/search" \
-H "Authorization: Bearer $RUSHDB_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"where": {
"groups": { "$in": ["admin"] },
"$relation": {
"type": "CREATED"
}
},
"limit": 50
}'
```
### 4. Discovering Labels
```typescript
// Find all labels used on records in North America region
const labels = await db.labels.find({
where: {
region: { $in: ["US", "CA", "MX"] }
}
});
```
```python
# Find all labels used on records in North America region
labels = db.labels.find({
"where": {
"region": {"$in": ["US", "CA", "MX"]}
}
})
```
```bash
# Find all labels used on records in North America region
curl -X POST "https://api.rushdb.com/api/v1/labels/search" \
-H "Authorization: Bearer $RUSHDB_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"where": {
"region": {"$in": ["US", "CA", "MX"]}
}
}'
```
### 5. Exploring Properties
```typescript
// Get all string properties used on PRODUCT records
const productProps = await db.properties.find({
labels: ["PRODUCT"],
where: {
// ...
}
});
```
```python
# Get all string properties used on PRODUCT records
product_props = db.properties.find({
"labels": ["PRODUCT"],
"where": {
// ...
}
})
```
```bash
# Get all string properties used on PRODUCT records
curl -X POST "https://api.rushdb.com/api/v1/properties/search" \
-H "Authorization: Bearer $RUSHDB_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"labels": ["PRODUCT"],
"where": {
// ...
}
}'
```
## APIs Supporting SearchQuery
RushDB provides consistent SearchQuery capabilities across multiple API endpoints:
| API Endpoint | Description | Documentation |
|--------------------------------------|--------------------------------------|---------------------------------------------------------|
| `/api/v1/records/search` | Search for records | [Records API](../rest-api/records/get-records.md) |
| `/api/v1/records/delete` | Delete records using search criteria | [Delete Records](../rest-api/records/delete-records.md) |
| `/api/v1/relationships/search` | Search for relationships | [Relationships API](../rest-api/relationships.md) |
| `/api/v1/labels/search` | Search for labels | [Labels API](../rest-api/labels.md) |
| `/api/v1/properties/search` | Search for properties | [Properties API](../rest-api/properties.md) |
| `/api/v1/properties/:id/values` | Get property values with filtering | [Properties API](../rest-api/properties.md) |
## Powerful Use Cases
### Dynamic Filtering in Catalog Applications
With RushDB's fractal API design, building dynamic filtering interfaces for catalog or marketplace applications becomes dramatically simplified. The more you filter records (and simultaneously filter properties), the more precise your results become.
```mermaid
flowchart TD
A[Initial Query: No Filters] --> B[Get All Records]
A --> C[Get All Properties]
B --> D[Start Applying Filters]
C --> D
D --> E[Refined Records]
D --> F[Available Properties Update]
E --> G[Further Filter Refinement]
F --> G
G --> H[Final Results and Options]
```
To implement this pattern:
1. Fetch all records and all available properties (no filters applied)
2. As users select filters, apply the same SearchQuery to both the records and properties endpoints
3. The filtered properties API will return only properties that exist in the remaining record set
4. Update your UI to display only filter options that are still relevant
```typescript
// User selects a category filter
const filterQuery = {
where: { category: "electronics" }
};
// Get filtered products
const products = await db.records.find({
labels: ["PRODUCT"],
...filterQuery
});
// Get available properties for the remaining product set
const availableProperties = await db.properties.find(filterQuery);
// Generate dynamic filters based on available properties
const dynamicFilters = generateFiltersFromProperties(availableProperties);
```
```python
# User selects a category filter
filter_query = {
"where": {"category": "electronics"}
}
# Get filtered products
result = db.records.find({
"labels": ["PRODUCT"],
**filter_query
})
products = result.data
# Get available properties for the remaining product set
available_properties = db.properties.find(filter_query)
# Generate dynamic filters based on available properties
dynamic_filters = generate_filters_from_properties(available_properties)
```
### AI-Powered Data Analytics Without ETL
RushDB's fractal API design makes it exceptionally well-suited for AI workflows and RAG (Retrieval Augmented Generation) systems. By importing raw JSON data and allowing RushDB to automatically recognize and index structures, you can eliminate traditional ETL processes.
```mermaid
graph TD
A[Raw JSON Dataset] --> B[RushDB Import]
B --> C[Automatic Topology Detection]
C --> D[Labels Generated]
C --> E[Properties Mapped]
C --> F[Relationships Discovered]
D --> G[AI Agent Explores Dataset]
E --> G
F --> G
G --> H[Dynamic Query Generation]
H --> I[Insight Extraction]
I --> J[Visualization/Report]
```
This allows AI agents to:
1. Explore available data structure without predefined schemas
2. Dynamically generate queries based on discovered patterns
3. Refetch and recalculate results on-the-fly as new insights emerge
4. Perform complex aggregations without manual data preparation
For example, an AI agent could:
```typescript
// Discovery phase: Explore available labels
const availableLabels = await db.labels.find({});
console.log("Discovered entity types:", Object.keys(availableLabels));
// Explore properties of a specific label
const personProperties = await db.properties.find({
labels: ["PERSON"],
where: {
// ...
}
});
// Generate insights based on discovered structure
const insightQuery = generateQueryFromDiscoveredStructure(personProperties);
const results = await db.records.find(insightQuery);
```
```python
# Discovery phase: Explore available labels
available_labels = db.labels.find({})
print("Discovered entity types:", list(available_labels.keys()))
# Explore properties of a specific label
person_properties = db.properties.find({
"labels": ["PERSON"],
"where": {
// ...
}
})
# Generate insights based on discovered structure
insight_query = generate_query_from_discovered_structure(person_properties)
result = db.records.find(insight_query)
results = result.data
```
## Conclusion
RushDB's fractal API design with the reusable SearchQuery pattern represents a significant advancement in database interaction. By maintaining a consistent query structure across different entities and operations, RushDB enables developers to build more intuitive, flexible, and powerful applications with less code and cognitive overhead.
This design philosophy reflects a deep understanding of how developers work with data, ensuring that once you learn the SearchQuery pattern, you can apply that knowledge universally throughout your application's interaction with RushDB.
---
# RushDB TypeScript/JavaScript SDK
Welcome to the comprehensive guide on working with the RushDB SDK. This SDK provides a modern, flexible interface for managing your data, relationships, and metadata in RushDB through JavaScript and TypeScript applications.
## What is RushDB SDK?
The RushDB JavaScript/TypeScript SDK is a powerful client library that lets you interact with RushDB's features directly from your JavaScript or TypeScript applications. Whether you're building web applications, server backends, or automation scripts, this SDK gives you full access to RushDB's capabilities with an intuitive, type-safe API.
## Highlights
- **✨ No Configuration Needed**: Plug-and-play design requires minimal setup to get started
- **🤖 Automatic Type Inference**: Enjoy seamless type safety with automatic TypeScript inference
- **↔️ Isomorphic Architecture**: Fully compatible with both server and browser environments
- **🏋️ Zero Dependencies**: Lightweight (just 6.9KB gzipped) and efficient with no external dependencies
## Getting Started
### Installation
To begin using RushDB SDK, add it to your project with your preferred package manager:
```bash
# Using npm
npm install @rushdb/javascript-sdk
# Using yarn
yarn add @rushdb/javascript-sdk
# Using pnpm
pnpm add @rushdb/javascript-sdk
```
### Quick Setup
After installation, create an instance of the RushDB SDK in your project:
```typescript
import RushDB from '@rushdb/javascript-sdk';
const db = new RushDB('RUSHDB_API_KEY');
```
Replace `RUSHDB_API_KEY` with your actual API token from the [RushDB Dashboard](https://app.rushdb.com/).
### Usage Example
```typescript
import RushDB from '@rushdb/javascript-sdk'
// Setup SDK
const db = new RushDB("RUSHDB_API_KEY");
// Push any data, and RushDB will automatically flatten it into Records
// and establish relationships between them accordingly.
await db.records.createMany({
label: "COMPANY",
data: {
name: 'Google LLC',
address: '1600 Amphitheatre Parkway, Mountain View, CA 94043, USA',
foundedAt: '1998-09-04T00:00:00.000Z',
rating: 4.9,
DEPARTMENT: [{
name: 'Research & Development',
description: 'Innovating and creating advanced technologies for AI, cloud computing, and consumer devices.',
// Nested relationships are automatically created
PROJECT: [{
name: 'Bard AI',
// ... more properties
}]
}]
}
})
// Find Records by specific criteria
const employees = await db.records.find({
labels: ['EMPLOYEE'],
where: {
position: { $contains: 'AI' }
}
})
```
## SDK Configuration Options
The RushDB SDK is designed to be flexible and configurable. When initializing the SDK, you can provide configuration options to customize its behavior.
### Constructor Parameters
```typescript
const db = new RushDB(token, config);
```
**Parameters:**
- `token` (`string`): Your API token from the RushDB Dashboard
- `config` (`SDKConfig`): Optional configuration object
### Configuration Object (`SDKConfig`)
The configuration object allows you to customize the SDK's behavior and connection details:
```typescript
type SDKConfig = {
httpClient?: HttpClientInterface;
timeout?: number;
logger?: Logger;
options?: {
allowForceDelete?: boolean;
}
} & ApiConnectionConfig;
```
Where `ApiConnectionConfig` is either:
```typescript
{
host?: string;
port?: number;
protocol?: string;
}
```
Or:
```typescript
{
url?: string;
}
```
### Configuration Options Explained
- **Connection settings**:
- `url`: The complete URL to the RushDB API (e.g., `https://api.rushdb.com/api/v1`)
- **OR** the individual components:
- `host`: The domain name or IP address (e.g., `api.rushdb.com/api/v1`)
- `port`: The port number (defaults to 80 for HTTP, 443 for HTTPS)
- `protocol`: Either `http` or `https` (defaults to `https`)
- **Advanced options**:
- `timeout`: Request timeout in milliseconds (default: 30000)
- `httpClient`: Custom HTTP client implementation
- `logger`: Custom logging function
- `options.allowForceDelete`: When set to `true`, allows deleting all records without specifying criteria (defaults to `false` for safety)
### Example with Configuration
```typescript
import RushDB from '@rushdb/javascript-sdk';
const db = new RushDB('RUSHDB_API_KEY', {
url: 'http://localhost:3000/api/v1',
timeout: 5000,
options: {
allowForceDelete: false
}
});
```
## SDK Architecture
The RushDB SDK uses a consistent approach for accessing the RushDB API instance across all SDK components. Classes like `Transaction`, `DBRecordInstance`, `DBRecordsArrayInstance` and `Model` all use the static `RushDB.init()` method to obtain the API instance, ensuring a uniform pattern throughout the SDK.
This architecture provides several benefits:
1. **Simplified Access**: Components can access the API without managing dependencies
2. **Consistency**: All components use the same mechanism to access API methods
3. **Cleaner Code**: Removes the need for inheritance from a base proxy class
Example of the implementation pattern:
```typescript
// Internal implementation example
async someMethod(param: string): Promise {
const instance = await RushDB.init() // Get the RushDB instance
return await instance.someApi.someMethod(param) // Use the instance to make API calls
}
```
## Next Steps
To continue learning about the RushDB TypeScript SDK, explore these related sections:
- [Working with Records](../typescript-sdk/records/create-records.md)
- [Managing Relationships](../typescript-sdk/relationships)
- [Working with Properties](../typescript-sdk/properties)
- [Working with Labels](../typescript-sdk/labels)
- [Working with Transactions](../typescript-sdk/transactions)
Before you begin exploring the SDK features, make sure you have a valid API token. If you haven't set up your RushDB account yet, follow our guide to [registering on the dashboard and generating an API token](../get-started/quick-tutorial.mdx).
---
# Labels
The RushDB TypeScript SDK provides a simple interface for working with [labels](../concepts/labels.md) in your database. Labels in RushDB help categorize and organize [records](../concepts/records.md), functioning similarly to table names in relational databases but with the flexibility of graph databases.
## Labels Overview
Labels in RushDB:
- Provide a way to categorize and organize records
- Enable efficient querying across similar types of records
- Each record has exactly one user-defined label (e.g., `User`, `Product`, `Car`)
- Are case-sensitive (e.g., "User" and "user" are treated as different labels)
- Function similarly to table names in relational databases but with graph database flexibility
## Labels API
The SDK provides label-related methods through the `labels` object:
```typescript
// Access the labels API
const labels = db.labels;
```
The Labels API is built on the powerful [SearchQuery](../concepts/search/introduction.md) interface, which enables you to use the same querying capabilities that are available throughout the RushDB search API. This means you can leverage complex filters, logical operators, and comparison operators when working with labels.
### Find Labels
Searches for labels based on the provided query parameters and returns label names with their record counts:
```typescript
const response = await db.labels.find({
// Optional: Any search parameters to filter labels
// Similar to record search queries
where: {
// You can filter by record properties that have specific labels
name: "John"
},
// Other search parameters like skip, limit, etc.
});
// Response contains labels with their counts
console.log(response.data);
/* Example output:
{
"User": 125
}
*/
```
## Using Labels with Records
When creating or updating records, you need to specify a label:
```typescript
// Create a record with the "User" label
const user = await db.records.create({
label: "User",
data: {
name: "John Doe",
email: "john.doe@example.com"
}
});
// Find all records with the "User" label
const users = await db.records.find({
labels: ["User"]
});
```
## Filtering Labels
The labels API leverages the powerful [`SearchQuery`](../concepts/search/introduction.md) interface, allowing you to use the same advanced querying capabilities that are available throughout the RushDB search API. You can use complex queries to filter which labeled records to include:
### Example with Multiple Conditions
```typescript
const response = await db.labels.find({
where: {
age: { $gt: 30 },
active: true
}
});
```
This will return labels for records where `age` is greater than 30 AND `active` is true.
### Example with OR Logic
```typescript
const response = await db.labels.find({
where: {
$or: [
{ country: "USA" },
{ country: "Canada" }
]
}
});
```
This will return labels for records where `country` is either "USA" OR "Canada".
### Advanced Query Operators
Since the Labels API uses the [`SearchQuery`](../concepts/search/introduction.md) interface, you can use all the query operators available in the [RushDB search API](../concepts/search/introduction.md):
```typescript
const response = await db.labels.find({
where: {
// String operators
name: { $contains: "Smith" },
email: { $endsWith: "@example.com" },
// Numeric operators
age: { $gt: 18, $lt: 65 },
score: { $gte: 4.5 },
// Array operators
tags: { $in: ["premium", "verified"] },
// Negation
status: { $ne: "inactive" }
}
});
```
## Label Requirements and Limitations
- **Single Custom Label**: Each record can have only one custom label at a time
- **Required Field**: A custom label is required for each record
- **Case-Sensitive**: Labels are case-sensitive ("User" ≠ "user")
## Working with Labels
### Best Practices
1. **Consistent naming conventions**: Use a consistent pattern for [label](../concepts/labels.md) names (e.g., singular nouns, PascalCase)
2. **Meaningful labels**: Choose labels that describe what the record represents, not just its attributes
3. **Hierarchical labeling**: Consider using more specific labels for specialized record types (e.g., "Employee" and "Manager" instead of just "Person")
### Common Use Cases
- **Data organization**: Group related records for easier querying and visualization
- **Access control**: Set permissions based on record labels
- **Conditional processing**: Apply different business logic depending on record types
- **Schema validation**: Enforce data structure based on record labels
## Internal Representation
Internally, labels are stored as the `__RUSHDB__KEY__LABEL__` property and exposed to clients as `__label`. This property is essential for organizing records and enabling efficient queries across similar types of data.
## Additional Resources
- [Labels Concept Documentation](../concepts/labels.md) - Learn more about labels and their role in the RushDB data model
- [Search API Documentation](../concepts/search/introduction.md) - Explore the powerful search capabilities available in RushDB
---
# Models
In this section, we focus on how to define models using the RushDB SDK. Defining models accurately is crucial as it not only aids in validating the fields according to the schema but also enhances the developer experience with features like autocomplete and field name suggestions.
## Understanding Schema
The `Schema` is at the core of model definitions in RushDB. It specifies the structure and constraints of the data fields within your model. Here's a breakdown of the properties you can define within a `Schema`:
```typescript
type Schema = Record;
```
**Schema Properties Explained:**
- `default`: This is the initial value of the field if no value is provided during record creation. It can be a static value or a function that returns a value asynchronously, allowing for dynamic default values.
- `multiple`: Indicates whether the field can hold multiple values (array) or just a single value.
- `required`: Specifies whether a field is mandatory. If set to true, you cannot create a record without providing a value for this field.
- `type`: Defines the data type of the field. The type determines the available search operators and how data is validated and stored. Possible types include:
- `boolean`
- `datetime` (can be either a detailed object or an ISO string)
- `null`
- `number`
- `string`
- `vector` (for embedding vectors used in similarity search)
- `uniq`: If set to true, the field must have a unique value across all records in the database, useful for fields like email addresses or custom identifiers.
### Working with Default Values
Default values are especially useful for automatically setting fields like timestamps, status flags, or counters without requiring explicit values for each record creation. RushDB supports both static default values and dynamic values generated by functions:
```typescript
// Helper function to get current ISO timestamp
const getCurrentISO = () => new Date().toISOString();
// Using static and dynamic default values
const UserModel = new Model('USER', {
name: { type: 'string' },
avatar: { type: 'string' },
login: { type: 'string', uniq: true },
password: { type: 'string' },
active: { type: 'boolean', default: true }, // Static default
createdAt: { type: 'datetime', default: getCurrentISO }, // Dynamic default
tags: { type: 'string', multiple: true, required: false },
});
```
When you create a record without specifying values for fields with defaults, the system automatically applies these defaults:
```typescript
// The createdAt field will be automatically set to the current date/time
// The active field will be set to true
const newUser = await UserModel.create({
name: 'John Doe',
login: 'johndoe',
password: 'securePassword123',
avatar: 'avatar.jpg'
});
```
Default value functions can also be asynchronous, allowing for operations like fetching configuration values:
```typescript
const ConfigModel = new Model('CONFIG', {
key: { type: 'string', uniq: true },
value: { type: 'string' },
expiresAt: {
type: 'datetime',
default: async () => {
// Default expiration is 7 days from now
const date = new Date();
date.setDate(date.getDate() + 7);
return date.toISOString();
}
}
});
```
## Creating a Model with Model
With an understanding of `Schema`, you can define a model in the RushDB system. Here's how to define a simple `Author` model:
```typescript
const Author = new Model('author', {
name: { type: 'string' },
email: { type: 'string', uniq: true }
});
```
**Model Constructor Parameters:**
- `label`: A unique string identifier for the model, which represents a [Label](../concepts/labels) in RushDB. It's used to categorize records and define their type in the database system. Labels are crucial for organizing and querying your data.
- `schema`: The schema definition based on `Schema`, which dictates the structure and rules of the data stored.
### Type Helpers in Models
The `Model` class offers several built-in type helpers that enhance TypeScript integration:
```typescript
// These are defined in the Model class and available as readonly properties
readonly draft!: InferType>
readonly record!: DBRecord
readonly recordInstance!: DBRecordInstance
readonly recordsArrayInstance!: DBRecordsArrayInstance
```
**Type Helpers Explained:**
- `draft`: Represents a draft version of the schema - a flat object containing only the record's own properties defined by the schema, excluding system fields such as `__id`, `__label`, and `__proptypes`. This is useful when creating new records.
- `record`: Represents a fully-defined record with database representation, including all fields that come with the record's database-side representation.
- `recordInstance`: Extends the record by providing additional methods to operate on a specific record, such as saving, updating, or deleting it.
- `recordsArrayInstance`: Similar to a single record instance but supports batch or bulk operations for efficient management of multiple records.
### Practical Type Helpers Example
Here's a practical example of how to use the type helpers to create strongly-typed variables and functions in your application:
```typescript
// Define the Label as a constant
export const USER = 'USER' as const;
// Create a model with the USER label
export const UserModel = new Model(USER, {
name: { type: 'string' },
avatar: { type: 'string' },
login: { type: 'string', uniq: true },
password: { type: 'string' },
createdAt: { type: 'datetime', default: getCurrentISO },
tags: { type: 'string', multiple: true, required: false },
});
// Export type definitions derived from model
export type UserRecord = typeof UserModel.record;
export type UserRecordResult = never> =
typeof UserModel.recordInstance & { data: T };
export type UserRecordsArrayResult = typeof UserModel.recordsArrayInstance;
export type UserRecordDraft = typeof UserModel.draft;
export type UserSearchQuery = SearchQuery;
```
### Model Implementation Architecture
The `Model` class uses the same architectural pattern as other SDK components like `Transaction` and `DBRecordInstance`. It uses the static `RushDB.init()` method to access the API:
```typescript
// Internal implementation pattern (from model.ts)
async someMethod(params) {
const instance = await this.getRushDBInstance()
return await instance.someApi.someMethod(params)
}
// getRushDBInstance method in Model class
public async getRushDBInstance(): Promise {
const instance = RushDB.getInstance()
if (instance) {
return await RushDB.init()
}
throw new Error('No RushDB instance found. Please create a RushDB instance first: new RushDB("RUSHDB_API_KEY")')
}
```
This architecture ensures consistent API access across all SDK components.
These exported types can then be used throughout your application to ensure type safety:
```typescript
// Function that accepts a user draft (without system fields)
function prepareUserForRegistration(user: UserRecordDraft): UserRecordDraft {
return {
...user,
// Add additional processing if needed
};
}
// Function that works with a complete user record (with system fields)
function getUserDisplayName(user: UserRecord): string {
return user.name || user.__id;
}
// Function that receives a user recordInstance with additional methods
async function updateUserAvatar(user: UserRecordResult): Promise {
const newAvatar = generateAvatarUrl(user.data.name);
return await UserModel.update(user.data.__id, { avatar: newAvatar });
}
// Function that creates a type-safe search query
function buildUserSearchQuery(nameFilter: string): UserSearchQuery {
return {
where: {
name: { $contains: nameFilter },
// TypeScript will ensure only valid fields and operators are used
},
sort: { createdAt: 'desc' }
};
}
```
This approach gives you several advantages:
- **Consistent Type Definitions**: All user-related types are derived from a single source of truth.
- **Autocomplete Support**: Your IDE will suggest valid field names and types.
- **Type Safety**: TypeScript will catch errors if you try to access non-existent fields.
- **Maintainability**: Changes to the model automatically propagate to all derived types.
## Registering and Managing Models
Models in RushDB don't need to be registered explicitly. When you create a model, it's ready to use right away:
```typescript
// Create the model
const AuthorModel = new Model('author', {
name: { type: 'string' },
email: { type: 'string', uniq: true }
});
// Start using it directly
const author = await AuthorModel.create({
name: "Jane Doe",
email: "jane@example.com"
});
```
### Important: RushDB Initialization Architecture
Due to the async initialization architecture of RushDB, it's important to initialize the RushDB instance early in your application's lifecycle. This is because JavaScript modules are lazy-loaded and only executed when imported.
To ensure that the RushDB instance is available when needed by your models, it's recommended to:
1. Create your RushDB instance in a dedicated file at the root of your application
2. Export this instance so it can be imported by other modules
3. Import this file early in your application's bootstrap process
Example of proper initialization:
```typescript
// db.ts (at the root of your project)
import RushDB from '@rushdb/javascript-sdk';
// Initialize RushDB with your API token
export const db = new RushDB('RUSHDB_API_KEY');
// You can also export a helper function to access the instance
export const getRushDBInstance = async () => {
return await RushDB.init();
};
```
```typescript
// app.ts or index.ts (your application entry point)
import { db } from './db';
// Import your models after importing the db
import { UserModel, PostModel } from './models';
// The rest of your application code...
```
This approach ensures that the RushDB instance is initialized before any model tries to use it, preventing "No RushDB instance found" errors.
## Model CRUD Operations
After creating a model, you can perform CRUD (Create, Read, Update, Delete) operations through the model's methods.
### Creating Records
```typescript
// Create a single record
const newAuthor = await AuthorModel.create({
name: 'Alice Smith',
email: 'alice.smith@example.com'
});
// Create multiple records
const authors = await AuthorModel.createMany([
{ name: 'Bob Johnson', email: 'bob.johnson@example.com' },
{ name: 'Carol Davis', email: 'carol.davis@example.com' }
]);
```
### Reading Records
```typescript
// Find all records of this model
const allAuthors = await AuthorModel.find();
// Find specific records with search criteria
const specificAuthors = await AuthorModel.find({
where: { name: { $contains: 'Smith' } }
});
// Find a single record
const oneAuthor = await AuthorModel.findOne({
where: { email: 'alice.smith@example.com' }
});
// Find by unique identifier
const authorById = await AuthorModel.findById('author_id_123');
```
### Updating Records
```typescript
// Update a specific record by ID
await AuthorModel.update('author_id_123', {
name: 'Alice Johnson-Smith'
});
// Set all values of a record (replace existing data)
await AuthorModel.set('author_id_123', {
name: 'Alice Johnson',
email: 'alice.johnson@example.com'
});
```
### Deleting Records
```typescript
// Delete records matching criteria
await AuthorModel.delete({
where: { name: { $contains: 'temp' } }
});
// Delete records by ID
await AuthorModel.deleteById(['author_id_123', 'author_id_456']);
```
### Working with Relationships
```typescript
// Attach a relationship
await AuthorModel.attach({
source: 'author_id_123',
target: 'book_id_456',
options: { type: 'WROTE' }
});
// Detach a relationship
await AuthorModel.detach({
source: 'author_id_123',
target: 'book_id_456',
options: { type: 'WROTE' }
});
```
## Advanced TypeScript Support
When working with RushDB SDK, achieving perfect TypeScript contracts ensures a seamless development experience. TypeScript's strong typing system allows for precise autocomplete suggestions and error checking, particularly when dealing with complex queries and nested models. This section will guide you on how to enhance TypeScript support by defining comprehensive type definitions for your models.
### Defining Comprehensive TypeScript Types
To fully leverage TypeScript's capabilities, you can define types that include all schemas you've registered with `Model`. This will allow you to perform complex queries with nested model fields, ensuring type safety and better autocompletion.
#### Step 1: Create Models with Model
First, define your models using `Model`:
```typescript
import { Model } from '@rushdb/javascript-sdk'
// Create models
const AuthorModel = new Model('author', {
name: { type: 'string' },
email: { type: 'string', uniq: true }
});
const PostModel = new Model('post', {
created: { type: 'datetime', default: () => new Date().toISOString() },
title: { type: 'string' },
content: { type: 'string' },
rating: { type: 'number' }
});
const BlogModel = new Model('blog', {
title: { type: 'string' },
description: { type: 'string' }
});
```
#### Step 2: Create an Exportable Type for All Schemas
Next, create an exportable type that includes all the schemas defined in your application:
```typescript
export type MyModels = {
author: typeof AuthorModel.schema
post: typeof PostModel.schema
blog: typeof BlogModel.schema
}
```
#### Step 3: Extend the Models Interface
Add this type declaration to your project. This ensures that RushDB SDK is aware of your models:
```typescript
// index.d.ts or other d.ts file added to include in tsconfig.json
import { MyModels } from './types';
declare module '@rushdb/javascript-sdk' {
export interface Models extends MyModels {}
}
```
### Example Usage: Complex Queries with Type Safety
By following these steps, you can now write complex queries with confidence, knowing that TypeScript will help you avoid errors and provide accurate autocomplete suggestions. Here's an example demonstrating how you can leverage this setup:
```typescript
const query = await db.records.find({
labels: ['post'],
where: {
author: {
name: { $contains: 'John' }, // Checking if the author's name contains 'John'
post: {
rating: { $gt: 5 } // Posts with rating greater than 5
}
}
}
});
```
In this example, the `db.records.find` method allows you to use nested fields in the `where` condition, thanks to the enhanced TypeScript definitions. This ensures that you can easily and accurately query your data, leveraging the full power of TypeScript.
By defining comprehensive type definitions for your models and extending the `Models` interface, you can significantly enhance your TypeScript support when working with RushDB SDK. This approach ensures type safety, better autocompletion, and a more efficient development experience.
## Working with Transactions
Model operations can be performed within transactions to ensure data integrity. For more information on using transactions with models, see the [Transactions](../typescript-sdk/transactions) documentation.
## Conclusion
Defining models with `Model` and `Schema` sets a robust foundation for your application's data architecture. It enables strong type-checking, validation, and inter-model relationships, enhancing the robustness and scalability of your applications. In subsequent sections, we will explore how to interact with these models to create, retrieve, update, and delete records.
---
## Related Documentation
For a more in-depth understanding of the RushDB TypeScript SDK and its capabilities, refer to these related sections:
- [Introduction to TypeScript SDK](../typescript-sdk/introduction) - Learn about the basics of using the SDK
- [Transactions](../typescript-sdk/transactions) - Learn how to use transactions with models for atomic operations
- [Labels](../concepts/labels) - Understand how Labels work in RushDB and how they're used to categorize records
---
# Properties
[Properties](../concepts/properties.md) are the individual key-value pairs that make up the data within a [record](../concepts/records.md) in RushDB. This guide covers how to work with properties using the TypeScript SDK, including finding, retrieving, and managing property values.
## Overview
The properties API in the SDK enables you to:
- Find properties based on search criteria
- Retrieve specific properties by ID
- Get possible values for a property
- Delete properties from the database
## Finding Properties
### Using RushDB's `find()` Method
To search for properties that match specific criteria, use the `properties.find` method:
```typescript
const properties = await db.properties.find({
where: {
name: 'email',
type: 'string'
}
});
console.log(properties);
/*
{
data: [
{
id: 'property_id_1',
name: 'email',
type: 'string',
...
},
{
id: 'property_id_2',
name: 'email',
type: 'string',
...
}
],
total: 2
}
*/
```
#### Parameters
- `searchQuery`: A search query object to find matching properties
- `where`: Conditions to filter properties
- `sort`: Sort criteria for results
- `limit`: Maximum number of results to return
- `skip`: Number of results to skip
- `transaction` (optional): A [transaction](../concepts/transactions.mdx) object or string to include the operation within a transaction
#### Returns
- A promise that resolves to an array of property objects
### Finding Properties in Transactions
```typescript
const transaction = await db.tx.begin();
try {
const properties = await db.properties.find({
where: {
name: { $in: ['email', 'phone'] }
}
}, transaction);
// Perform other operations...
await transaction.commit();
console.log(properties);
} catch (error) {
await transaction.rollback();
throw error;
}
```
## Retrieving a Property by ID
### Using RushDB's `findById()` Method
To retrieve a specific property by its ID, use the `properties.findById` method:
```typescript
const property = await db.properties.findById('property_id_1');
console.log(property);
/*
{
id: 'property_id_1',
name: 'email',
type: 'string',
...
}
*/
```
#### Parameters
- `id`: The ID of the property to retrieve
- `transaction` (optional): A [transaction](../concepts/transactions.mdx) object or string to include the operation within a transaction
#### Returns
- A promise that resolves to the property object if found, or null if not found
## Getting Property Values
### Using RushDB's `values()` Method
To retrieve possible values for a specific property, use the `properties.values` method:
```typescript
const values = await db.properties.values('property_id_1', {
where: {
status: 'active'
},
query: 'john',
orderBy: 'asc',
limit: 10
});
console.log(values);
/*
{
data: ['john@example.com', 'johnny@example.com'],
total: 2
}
*/
```
#### Parameters
- `id`: The ID of the property to get values for
- `searchQuery` (optional): SearchQuery object with filtering options:
- `where` (object): Filter criteria for records containing this property
- `query` (string): Filter values by this text string
- `orderBy` (string): Sort direction ('asc' or 'desc')
- `limit` (number): Maximum number of values to return
- `skip` (number): Number of values to skip for pagination
- `transaction` (optional): A [transaction](../concepts/transactions.mdx) object or string to include the operation within a transaction
#### Returns
- A promise that resolves to an object containing the values and a total count
## Deleting Properties
### Using RushDB's `delete()` Method
To delete a property from the database, use the `properties.delete` method:
```typescript
const result = await db.properties.delete('property_id_1');
console.log(result);
/*
{
success: true,
message: "Property deleted successfully"
}
*/
```
#### Parameters
- `id`: The ID of the property to delete
- `transaction` (optional): A [transaction](../concepts/transactions.mdx) object or string to include the operation within a transaction
#### Returns
- A promise that resolves to a success object
#### Deleting Properties in Transactions
```typescript
const transaction = await db.tx.begin();
try {
await db.properties.delete('property_id_1', transaction);
// Perform other operations...
await transaction.commit();
} catch (error) {
await transaction.rollback();
throw error;
}
```
## Best Practices for Working with Properties
1. **Use Transactions for Related Operations**
- When performing multiple operations that need to be atomic, use [transactions](../concepts/transactions.mdx)
- This ensures data consistency and prevents partial changes
2. **Optimize Search Queries**
- Use specific search criteria to minimize the amount of data returned
- Filter by name, type, or other attributes to narrow down results
3. **Cache Property IDs When Appropriate**
- If you frequently access the same properties, cache their IDs
- This reduces the need for repeated lookups
4. **Consider the Impact of Property Deletion**
- Deleting a property affects all records that use it
- Instead of deleting common properties, consider marking them as deprecated
5. **Use Distinct Values for Enumeration**
- When fetching property values for UI dropdown elements, use the `distinct: true` option
- This provides a cleaner list of possible values without duplicates
## Conclusion
The Properties API in the RushDB TypeScript SDK provides a comprehensive set of methods for working with properties. By understanding these methods and their parameters, you can effectively manage properties in your application.
For more information on related topics, see:
- [Records](./records/create-records.md) - Work with records that contain properties
- [Relationships](./relationships.md) - Connect records with relationships
- [Models](./models.md) - Define structured schemas for your data
---
# Raw Queries
> **Important (cloud-only):** This endpoint is available only on the RushDB managed cloud service or when your project is connected to a custom database through RushDB Cloud. It is not available for self-hosted or local-only deployments — attempting to use it against a non-cloud instance will fail.
Use this endpoint to run arbitrary Cypher queries against your connected Neo4j database. This is intended for advanced use-cases and requires the managed service or a custom DB connection.
### TypeScript SDK example
```ts
const result = await db.query.raw({
query: 'MATCH (n:Person) RETURN n LIMIT $limit',
params: { limit: 10 }
})
// `result` contains the server response with query records as returned by Neo4j driver
console.log(result)
```
### Real-world example: employees at a company
This example shows a parameterized query that finds people employed by a company and returns selected fields.
```ts
const company = 'Acme Corp'
const result = await db.query.raw({
query: `
MATCH (c:Company { name: $company })<-[:EMPLOYS]-(p:Person)
RETURN p { .name, .email, company: c.name } AS employee
ORDER BY p.name
LIMIT $limit
`,
params: { company, limit: 50 }
})
console.log(result.data)
```
---
# Create Records
Creating [records](../../concepts/records.md) is a fundamental operation when working with any data-driven application. RushDB provides multiple ways to create records, from direct API calls to Model-based abstractions.
This guide covers different approaches to creating records, from the most basic to more advanced patterns.
## Overview
The create record methods in the SDK enable you to:
- Create a single [record](../../concepts/records.md) with [properties](../../concepts/properties.md) and a [label](../../concepts/labels.md)
- Create multiple records in one operation
- Control data type inference and other formatting options
- Create records with precise type control
- Create records within [transactions](../../concepts/transactions.mdx) for data consistency
- Create records using Model abstractions for type safety
## Creating Single Records
There are multiple ways to create records in RushDB. Let's start with the most basic approach using the direct API methods.
### Using RushDB's `create()` Method
The most direct way to create a record is using the API client's `records.create` method:
```typescript
const newAuthor = await db.records.create({
label: 'AUTHOR',
data: {
name: 'John Doe',
email: 'john.doe@example.com'
},
options: {
suggestTypes: true
}
});
console.log(newAuthor);
/*
{
__id: 'generated_id',
__label: 'AUTHOR',
name: 'John Doe',
email: 'john.doe@example.com'
}
*/
```
#### Parameters
- `label`: The [label](../../concepts/labels.md)/type for the record
- `data`: The data for the record as a flat object
- `options` (optional): Configuration options for record creation:
- `suggestTypes` (boolean, default: `true`): When true, automatically infers data types for [properties](../../concepts/properties.md)
- `castNumberArraysToVectors` (boolean, default: `false`): When true, converts numeric arrays to vector type
- `convertNumericValuesToNumbers` (boolean, default: `false`): When true, converts string numbers to number type
- `transaction` (optional): A [transaction](../../concepts/transactions.mdx) object or string to include the operation within a transaction
#### Returns
- A promise that resolves to a `DBRecordInstance` containing the created [record](../../concepts/records.md)
#### Creating Records in Transactions
```typescript
const transaction = await db.tx.begin();
try {
const newAuthor = await db.records.create({
label: 'AUTHOR',
data: {
name: 'Jane Smith',
email: 'jane.smith@example.com'
}
}, transaction);
// Perform other operations...
await transaction.commit();
console.log(newAuthor);
} catch (error) {
await transaction.rollback();
throw error;
}
```
### Property-Based Approach for Precise Type Control
When you need precise control over property types, you can use the property-based approach by passing an array of `PropertyDraft` objects instead of a flat data object:
```typescript
const newAuthor = await db.records.create({
label: 'AUTHOR',
data: [
{
name: 'name',
type: 'string',
value: 'John Doe'
},
{
name: 'age',
type: 'number',
value: 42
},
{
name: 'isActive',
type: 'boolean',
value: true
},
{
name: 'tags',
type: 'string',
value: 'fiction,sci-fi,bestseller',
valueSeparator: ','
},
{
name: 'scores',
type: 'number',
value: '85,90,95',
valueSeparator: ','
},
{
name: 'joinDate',
type: 'datetime',
value: '2025-04-23T10:30:00Z'
}
]
});
console.log(newAuthor);
/*
{
__id: 'generated_id',
__label: 'AUTHOR',
__proptypes: {
name: 'string',
age: 'number',
isActive: 'boolean',
tags: 'string',
scores: 'number',
joinDate: 'datetime'
},
name: 'John Doe',
age: 42,
isActive: true,
tags: ['fiction', 'sci-fi', 'bestseller'],
scores: [85, 90, 95],
joinDate: '2025-04-23T10:30:00Z'
}
*/
```
#### Property Draft Object Properties
Each property draft object supports the following properties:
| Property | Type | Description |
|----------|------|-------------|
| `name` | `string` | The property name |
| `type` | `string` | The data type ('string', 'number', 'boolean', 'datetime', etc.) |
| `value` | `any` | The property value |
| `valueSeparator` | `string` (optional) | Separator to split string values into arrays |
## Creating Multiple Records
When you need to create multiple records in a single operation, use the `records.createMany` method.
### Using RushDB's `createMany()` Method
```typescript
const authors = await db.records.createMany({
label: 'AUTHOR',
data: [
{ name: 'Alice Johnson', email: 'alice.johnson@example.com' },
{ name: 'Bob Brown', email: 'bob.brown@example.com' }
],
options: {
suggestTypes: true
}
});
console.log(authors);
/*
{
data: [
{
__id: 'generated_id_1',
__label: 'AUTHOR',
name: 'Alice Johnson',
email: 'alice.johnson@example.com'
},
{
__id: 'generated_id_2',
__label: 'AUTHOR',
name: 'Bob Brown',
email: 'bob.brown@example.com'
}
],
total: 2
}
*/
```
#### Parameters
- `label`: The [label](../../concepts/labels.md)/type for all records
- `data`: Object (nested too) or an array of objects, each representing a record to create
- `options` (optional): Configuration options for record creation:
- `suggestTypes` (boolean, default: `true`): When true, automatically infers data types for [properties](../../concepts/properties.md)
- `castNumberArraysToVectors` (boolean, default: `false`): When true, converts numeric arrays to vector type
- `convertNumericValuesToNumbers` (boolean, default: `false`): When true, converts string numbers to number type
- `capitalizeLabels` (bool): When true, converts all labels to uppercase
- `relationshipType` (str): Default relationship type between nodes
- `returnResult` (bool, default: `false`): When true, returns imported records in response
- `transaction` (optional): A [transaction](../../concepts/transactions.mdx) object or string to include the operation within a transaction
#### Returns
- A promise that resolves to a `DBRecordsArrayInstance` containing the created [records](../../concepts/records.md)
#### Creating Multiple Records in Transactions
```typescript
const transaction = await db.tx.begin();
try {
const authors = await db.records.createMany({
label: 'AUTHOR',
data: [
{ name: 'Charlie Green', email: 'charlie.green@example.com' },
{ name: 'David Blue', email: 'david.blue@example.com' }
]
}, transaction);
// Perform other operations...
await transaction.commit();
console.log(authors);
} catch (error) {
await transaction.rollback();
throw error;
}
```
## Creating Records with Models
The recommended approach for structured applications is to use RushDB's [Models](../models.md). Models provide type safety, validation, and a more intuitive API for working with records.
We'll use the following model definitions for these examples:
```typescript
const AuthorRepo = new Model('author', {
name: { type: 'string' },
email: { type: 'string', uniq: true }
});
```
### Using Model's `create` Method
The `create` method on a model creates a single record.
#### Signature
```typescript
create(
record: InferSchemaTypesWrite,
transaction?: Transaction | string
): Promise>;
```
#### Parameters
- `record`: An object that adheres to the schema defined for the model
- `transaction` (optional): A [transaction](../../concepts/transactions.mdx) object or string to include the operation within a transaction
#### Returns
- A promise that resolves to a `DBRecordInstance` containing the created [record](../../concepts/records.md)
#### Example
```typescript
const newAuthor = await AuthorRepo.create({
name: 'John Doe',
email: 'john.doe@example.com'
});
console.log(newAuthor);
/*
{
data: {
__id: 'generated_id',
__label: 'author',
name: 'John Doe',
email: 'john.doe@example.com'
}
}
*/
```
#### Using with Transactions
```typescript
const transaction = await db.tx.begin();
try {
const newAuthor = await AuthorRepo.create({
name: 'Jane Smith',
email: 'jane.smith@example.com'
}, transaction);
// Perform other operations...
await transaction.commit();
console.log(newAuthor);
} catch (error) {
await transaction.rollback();
throw error;
}
```
### Using Model's `createMany` Method
The `createMany` method on a model creates multiple records in a single operation.
#### Signature
```typescript
createMany(
records: Array>,
transaction?: Transaction | string
): Promise>;
```
#### Parameters
- `records`: An array of objects, each adhering to the schema defined for the model
- `transaction` (optional): A transaction object or string to include the operation within a transaction
#### Returns
- A promise that resolves to a `DBRecordsArrayInstance` containing the created records
#### Example
```typescript
const authors = await AuthorRepo.createMany([
{ name: 'Alice Johnson', email: 'alice.johnson@example.com' },
{ name: 'Bob Brown', email: 'bob.brown@example.com' }
]);
console.log(authors);
/*
{
data: [
{
__id: 'generated_id_1',
__label: 'author',
name: 'Alice Johnson',
email: 'alice.johnson@example.com'
},
{
__id: 'generated_id_2',
__label: 'author',
name: 'Bob Brown',
email: 'bob.brown@example.com'
}
],
total: 2
}
*/
```
#### Using with Transactions
```typescript
const transaction = await db.tx.begin();
try {
const authors = await AuthorRepo.createMany([
{ name: 'Charlie Green', email: 'charlie.green@example.com' },
{ name: 'David Blue', email: 'david.blue@example.com' }
], transaction);
// Perform other operations...
await transaction.commit();
console.log(authors);
} catch (error) {
await transaction.rollback();
throw error;
}
```
## Best Practices for Creating Records
1. **Use Models for Structured Applications**
- Models provide type safety, validation, and better organization
- They enforce schema consistency across your application
2. **Use Transactions for Related Operations**
- When creating multiple records that are related, use [transactions](../../concepts/transactions.mdx)
- Transactions ensure data consistency and allow rollback if operations fail
3. **Handle Uniqueness Constraints**
- Models automatically check uniqueness before creating records
- Handle `UniquenessError` exceptions appropriately
4. **Leverage Batch Operations**
- Use `createMany` for better performance when creating multiple records
- It minimizes network requests and database overhead
5. **Consider Default Values**
- Define default values in your schema to reduce repetitive code
- Default values can be static or derived from functions (like timestamps)
6. **Choose the Right Data Type Control Approach**
- Use the flat object approach for most cases where automatic type inference is sufficient
- Use the property-based approach with `PropertyDraft` objects when you need precise control over types
## Data Type Handling
RushDB supports the following property types:
- `string`: Text values
- `number`: Numeric values
- `boolean`: True/false values
- `null`: Null values
- `datetime`: ISO8601 format strings (e.g., "2025-04-23T10:30:00Z")
- `vector`: Arrays of numbers (when `castNumberArraysToVectors` is true)
When `suggestTypes` is enabled (default), RushDB automatically infers these types from your data.
When `convertNumericValuesToNumbers` is enabled, string values that represent numbers (e.g., '30') will be converted to their numeric equivalents (e.g., 30).
For more complex data import operations, refer to the [Import Data](./import-data.md) documentation.
## Conclusion
Creating records in RushDB can be done through direct API calls or through the Model abstraction. While direct API calls offer flexibility for dynamic or ad-hoc operations, using Models is recommended for most applications due to their type safety, validation capabilities, and more intuitive API.
For more advanced record operations, see the other guides in this section:
- [Get Records](./get-records.md) - Retrieve records from the database
- [Update Records](./update-records.md) - Modify existing records
- [Delete Records](./delete-records.md) - Remove records from the database
- [Import Data](./import-data.md) - Import data in bulk
---
# Delete Records
RushDB provides flexible APIs for deleting records from your database. This capability lets you remove individual records by ID or delete multiple records at once using search query filters.
## Overview
The delete endpoints allow you to:
- Delete a single record or multiple records by ID using `deleteById`
- Delete records using search queries with the `delete` method
- Delete records directly from record instances
- Perform atomic deletions using transactions
- Safely remove records with proper authentication
All delete operations require authentication using a bearer token and handle relationships appropriately. Deletion operations can also be performed within transactions for atomic operations.
## Delete a Single Record by ID
```typescript
// Delete a single record by ID
await db.records.deleteById('record-id-here');
```
This method deletes a single record identified by its unique ID.
### Parameters
| Parameter | Type | Description |
|-----------|--------|-------------|
| `idOrIds` | `String` or `Array` | The unique identifier of the record to delete, or an array of IDs |
| `transaction` | `Transaction` or `String` | Optional transaction for atomic operations |
### Example
```typescript
// Delete a specific record
try {
const response = await db.records.deleteById('018e4c71-5f20-7db2-b0b1-e7e681542af9');
if (response.success) {
console.log('Record deleted successfully');
}
} catch (error) {
console.error('Failed to delete record:', error);
}
// Delete multiple records by their IDs
try {
const response = await db.records.deleteById([
'018e4c71-5f20-7db2-b0b1-e7e681542af9',
'018e4c71-5f20-7db2-b0b1-e7e681542af8'
]);
if (response.success) {
console.log('Records deleted successfully');
}
} catch (error) {
console.error('Failed to delete records:', error);
}
// Delete within a transaction
const tx = await db.tx.begin();
try {
await db.records.deleteById('018e4c71-5f20-7db2-b0b1-e7e681542af9', tx);
await db.tx.commit(tx);
console.log('Record deleted successfully in transaction');
} catch (error) {
await db.tx.rollback(tx);
console.error('Transaction failed:', error);
}
```
## Delete Records Using a Search Query
```typescript
// Delete records using search query
await db.records.delete(
{
where: { /* search conditions */ }
},
transaction // optional
);
```
This method deletes records that match the specified search criteria.
### Parameters
| Parameter | Type | Description |
|-----------|------|-------------|
| `searchQuery` | `SearchQuery` | Query to identify records to delete |
| `transaction` | `Transaction` or `String` | Optional transaction for atomic operations |
Note: Using an empty `where` clause without allowing force delete will throw an `EmptyTargetError`.
You can use search parameters to define which records to delete:
| SearchQuery Field | Type | Description |
|-------------------|----------|--------------------------------------------------------------------------------------------|
| `where` | `Object` | Filter conditions for records to delete ([learn more](../../concepts/search/where)) |
| `labels` | `Array` | Optional array of labels to filter records by ([learn more](../../concepts/search/labels)) |
| `limit` | `Number` | Maximum number of records to delete (optional) |
### Example
```typescript
// Delete all users with age under 18
try {
const response = await db.records.delete({
where: {
label: 'USER',
age: { $lt: 18 }
}
});
if (response.success) {
console.log(response.data.message); // Displays success message with deletion count
}
} catch (error) {
console.error('Failed to delete records:', error);
}
// Delete inactive products in a specific category
try {
const response = await db.records.delete({
where: {
label: 'PRODUCT',
category: 'electronics',
isActive: false
}
});
if (response.success) {
console.log(response.data.message);
}
} catch (error) {
console.error('Failed to delete records:', error);
}
```
## Bulk Deletion with Complex Queries
For more advanced deletion scenarios, you can use the full power of RushDB's search query system:
```typescript
// Delete records with complex criteria
try {
const response = await db.records.delete({
where: {
$or: [
{ status: 'archived', lastModified: { $lt: '2024-01-01' } },
{ status: 'deleted', isTemporary: true }
]
},
labels: ['DOCUMENT', 'ATTACHMENT'],
limit: 1000 // Optional: limit the number of records deleted
});
console.log(`${response.data.message}`);
} catch (error) {
console.error('Bulk deletion failed:', error);
}
```
## Deleting Records from a Record Instance
If you already have a record instance, you can delete it directly:
```typescript
// Find a record first
const record = await db.records.findById('018e4c71-5f20-7db2-b0b1-e7e681542af9');
// Then delete it
try {
const response = await record.delete();
if (response.success) {
console.log('Record deleted successfully');
}
} catch (error) {
console.error('Failed to delete record:', error);
}
// With a transaction
const tx = await db.tx.begin();
try {
await record.delete(tx);
await db.tx.commit(tx);
console.log('Record deleted successfully within transaction');
} catch (error) {
await db.tx.rollback(tx);
console.error('Transaction failed:', error);
}
```
## Handling Relationships
When deleting records, all relationships associated with those records are automatically deleted. This ensures database integrity and prevents orphaned relationships.
## Safety Features and Transactions
RushDB implements several safeguards for delete operations:
1. **Authentication**: All delete operations require a valid authentication token
2. **Authorization**: Users can only delete records in projects they have access to
3. **Validation**: Input data is validated before processing
4. **Transactions**: Delete operations can be wrapped in transactions for data consistency
5. **Partial Failure Handling**: If a deletion affects multiple records and some operations fail, all changes are rolled back when using transactions
6. **Empty Query Protection**: The API prevents accidental deletion of all records by requiring explicit configuration to allow force deletion with empty `where` clauses
## Performance Considerations
- For large-scale deletions, RushDB processes operations in batches
- Complex query conditions may increase processing time
- Consider using [label filtering](../../concepts/search/labels) to narrow down records before deletion
- For very large datasets, use pagination in combination with delete operations
## Related Documentation
- [Search Introduction](../../concepts/search/introduction)
- [Where Clause](../../concepts/search/where)
- [Labels](../../concepts/search/labels)
- [Record Relationships](../../concepts/relationships)
- [Transactions](../../concepts/transactions.mdx)
---
# Get Records
RushDB provides flexible TypeScript SDK methods for retrieving records from your database. The Search API is one of the most powerful features of RushDB, allowing you to find records, navigate relationships, and transform results to exactly match your application's needs.
## Overview
The record retrieval and search methods in the SDK enable you to:
- Get a single record by its ID
- Find a single record that matches specific criteria
- Find records that match complex queries with filtering, sorting, and pagination
- Traverse relationships between records
- Perform vector similarity searches
- Retrieve records with related data
- Transform and aggregate search results
## Get Single Records
RushDB provides several methods for retrieving individual records, whether you know their ID or need to find them using search criteria.
### Get a Record by ID with `findById()`
When you already know the unique identifier of the record you need:
```typescript
// Get a single record by ID
const user = await db.records.findById('user-123');
// Get multiple records by their IDs
const users = await db.records.findById(['user-123', 'user-456', 'user-789']);
```
This method retrieves one or more records identified by their unique IDs.
#### Parameters
| Parameter | Type | Description |
|-----------|--------|-------------|
| `idOrIds` | `String` or `Array` | The unique identifier(s) of the record(s) to retrieve |
| `transaction` | `Transaction` or `String` | Optional transaction for atomic operations |
#### Examples
```typescript
// Retrieve a single record
try {
const person = await db.records.findById('018e4c71-5f20-7db2-b0b1-e7e681542af9');
console.log(`Found ${person.label()} with name: ${person.data.name}`);
} catch (error) {
console.error('Failed to retrieve record:', error);
}
// Retrieve multiple records by ID
try {
const employees = await db.records.findById([
'018e4c71-5f20-7db2-b0b1-e7e681542af9',
'018e4c71-5f20-7db2-b0b1-e7e681542af8'
]);
console.log(`Found ${employees.data.length} records`);
} catch (error) {
console.error('Failed to retrieve records:', error);
}
// Using with a transaction
const tx = await db.tx.begin();
try {
const record = await db.records.findById('018e4c71-5f20-7db2-b0b1-e7e681542af9', tx);
// Use the record
await tx.commit();
} catch (error) {
await tx.rollback();
console.error('Transaction failed:', error);
}
```
### Find a Single Record with `findOne()`
When you need to find a record that matches specific criteria:
```typescript
const user = await db.records.findOne({
labels: ["USER"],
where: {
email: "jane@example.com"
}
});
```
This method returns a single record that matches your query parameters, or null if no match is found.
#### Parameters
| Parameter | Type | Description |
|-----------|------|-------------|
| `searchQuery` | `SearchQuery` | Query object with filters to match records |
| `transaction` | `Transaction` or `String` | Optional transaction for atomic operations |
#### Example
```typescript
try {
const user = await db.records.findOne({
labels: ["USER"],
where: {
email: "jane@example.com"
}
});
if (user.data) {
console.log(`Found user: ${user.data.name}`);
} else {
console.log("User not found");
}
} catch (error) {
console.error('Error searching for user:', error);
}
```
### Find a Unique Record with `findUniq()`
When you expect exactly one matching record and want to ensure uniqueness:
```typescript
try {
const user = await db.records.findUniq({
labels: ["USER"],
where: {
email: "jane@example.com" // Assuming email is a unique field
}
});
} catch (error) {
if (error instanceof NonUniqueResultError) {
console.error(`Expected one result but found multiple matches`);
} else {
console.error('Error searching for user:', error);
}
}
```
This method throws a `NonUniqueResultError` if more than one record matches your criteria. This is useful when querying fields that should be unique.
#### Parameters
| Parameter | Type | Description |
|-----------|------|-------------|
| `searchQuery` | `SearchQuery` | Query object with filters to match records |
| `transaction` | `Transaction` or `String` | Optional transaction for atomic operations |
#### Example with Error Handling
```typescript
try {
const user = await db.records.findUniq({
labels: ["USER"],
where: {
email: "jane@example.com"
}
});
if (user.data) {
console.log(`Found unique user: ${user.data.name}`);
}
} catch (error) {
if (error instanceof NonUniqueResultError) {
console.error(`Expected one result but found ${error.count} matches`);
} else {
console.error('Error searching for user:', error);
}
}
```
## Search for Multiple Records
### Basic Searching with `find()`
The most versatile search method is `find()`, which accepts a SearchQuery object to filter, sort, and paginate results.
```typescript
// Basic search for records with the "USER" label
const result = await db.records.find({
labels: ["USER"],
where: {
isActive: true
},
limit: 10,
orderBy: { createdAt: "desc" }
});
// Access the returned records
const users = result.data;
console.log(`Found ${result.total} total users`);
```
This method searches for records that match the specified criteria, with support for filtering, pagination, and sorting.
### Parameters
| Field | Type | Description |
|-----------|------------------|-------------------------------------------------------------------------------------------------|
| `where` | Object | Filter conditions for records ([learn more](../../concepts/search/where)) |
| `orderBy` | String or Object | Sorting criteria ([learn more](../../concepts/search/pagination-order)) |
| `skip` | Number | Number of records to skip for pagination ([learn more](../../concepts/search/pagination-order)) |
| `limit` | Number | Maximum number of records to return (default: 1000) |
| `labels` | Array | Optional array of labels to filter records by ([learn more](../../concepts/search/labels)) |
### Return Value
The find method returns an object containing:
- `data`: An array of record instances matching the query
- `total`: The total number of records that match the query (before pagination)
### Examples
**Basic Search**
```typescript
// Find all active users sorted by name
const result = await db.records.find({
where: {
isActive: true
},
labels: ["USER"],
orderBy: { name: 'asc' },
limit: 50
});
console.log(`Found ${result.total} active users, showing first ${result.data.length}`);
```
**Advanced Filtering**
```typescript
// Find products with specific criteria
const results = await db.records.find({
labels: ["PRODUCT"],
where: {
$or: [
{ status: 'in_stock', price: { $lt: 100 } },
{ status: 'pre_order', releaseDate: { $lt: '2025-06-01' } }
]
},
orderBy: [
{ popularity: 'desc' },
{ price: 'asc' }
],
limit: 20
});
```
Search queries support a powerful and flexible syntax for filtering records. For a detailed explanation of all the available operators and capabilities, see the [Where clause documentation](../../concepts/search/where).
## Advanced Search Features
### Relationship Traversal
One of RushDB's most powerful features is the ability to search across relationships between records:
```typescript
// Find all blog posts by users who work at tech companies
const techBloggers = await db.records.find({
labels: ["POST"],
where: {
USER: { // Traverse to related USER records
COMPANY: { // Traverse to related COMPANY records
industry: "Technology"
}
},
publishedAt: { $lte: new Date() } // Only published posts
},
orderBy: { publishedAt: "desc" },
limit: 20
});
```
For more complex relationship queries, you can specify relationship types and directions:
```typescript
// Find users who follow specific topics
const users = await db.records.find({
labels: ["USER"],
where: {
TOPIC: {
$relation: {
type: "FOLLOWS",
direction: "out" // User -> FOLLOWS -> Topic
},
name: { $in: ["TypeScript", "GraphDB", "RushDB"] }
}
}
});
```
See the [Where clause documentation](../../concepts/search/where#relationship-queries) for more details on relationship queries.
### Vector Search
RushDB supports vector similarity searches for AI and machine learning applications:
```typescript
// Find documents similar to a query embedding
const similarDocuments = await db.records.find({
labels: ["DOCUMENT"],
where: {
embedding: {
$vector: {
fn: "gds.similarity.cosine", // Similarity function
query: queryEmbedding, // Your vector embedding
threshold: { $gte: 0.75 } // Minimum similarity threshold
}
}
},
limit: 10
});
```
See the [Vector operators documentation](../../concepts/search/where#vector-operators) for more details on vector search capabilities.
### Field Existence and Type Checking
RushDB provides operators to check for field existence and data types, which is particularly useful when working with heterogeneous data:
```typescript
// Find users who have provided an email but not a phone number
const emailOnlyUsers = await db.records.find({
labels: ["USER"],
where: {
$and: [
{ email: { $exists: true } }, // Must have email
{ phoneNumber: { $exists: false } } // Must not have phone number
]
}
});
// Find records where age is actually stored as a number (not string)
const properAgeRecords = await db.records.find({
labels: ["USER"],
where: {
age: { $type: "number" }
}
});
// Complex query combining type and existence checks
const validProfiles = await db.records.find({
labels: ["PROFILE"],
where: {
$and: [
{ bio: { $type: "string" } }, // Bio must be text
{ bio: { $contains: "developer" } }, // Bio mentions developer
{ skills: { $exists: true } }, // Skills must exist
{ avatar: { $exists: false } } // No avatar uploaded yet
]
}
});
```
The `$exists` operator is useful for:
- Data validation and cleanup
- Finding incomplete profiles
- Filtering by optional fields
The `$type` operator is useful for:
- Working with imported data that might have inconsistent types
- Validating data integrity
- Ensuring type consistency before operations
See the [Field existence operators documentation](../../concepts/search/where#field-existence-operator) for more details.
### Pagination and Sorting
Control the order and volume of results:
```typescript
// Get the second page of results (20 items per page)
const page2 = await db.records.find({
labels: ["PRODUCT"],
where: {
category: "Electronics"
},
skip: 20, // Skip the first 20 results
limit: 20, // Return 20 results
orderBy: {
price: "asc" // Sort by price ascending
}
});
// Get total number of results for pagination UI
const totalProducts = page2.total;
```
For more details on pagination and sorting options, see the [Pagination and ordering documentation](../../concepts/search/pagination-order).
### Aggregations
Transform and aggregate your search results:
```typescript
// Calculate comapany statis by employees and salaries
const companySalaryStats = await db.records.find({
labels: ['COMPANY'],
where: {
EMPLOYEE: {
$alias: '$employee', // Define alias for employee records
salary: {
$gte: 50000 // Filter employees by salary
}
}
},
aggregate: {
// Use field directly from record
companyName: '$record.name',
// Count unique employees using the defined alias
employeesCount: {
fn: 'count',
uniq: true,
alias: '$employee'
},
// Calculate total salary using the defined alias
totalWage: {
fn: 'sum',
field: 'salary',
alias: '$employee'
},
// Collect unique employees names
employeeNames: {
fn: 'collect',
field: 'name',
alias: '$employee'
},
// Get average salary with precision
avgSalary: {
fn: 'avg',
field: 'salary',
alias: '$employee',
precision: 0
},
// Get min and max salary
minSalary: {
fn: 'min',
field: 'salary',
alias: '$employee'
},
maxSalary: {
fn: 'max',
field: 'salary',
alias: '$employee'
}
}
});
```
For comprehensive details on available aggregation functions and usage, see the [Aggregations documentation](../../concepts/search/aggregations).
## Model-Based Search
If you're using RushDB's Model system (recommended), you get the same powerful search capabilities with additional type safety and convenience.
### Searching with Models
Models provide type-safe search methods that understand your data structure:
```typescript
// Define your model
const UserModel = new Model('USER', {
email: { type: 'string', unique: true },
name: { type: 'string' },
age: { type: 'number' },
isActive: { type: 'boolean', default: true }
});
// Search using the model
const activeUsers = await UserModel.find({
where: {
age: { $gte: 21 },
isActive: true
},
orderBy: { name: "asc" }
});
// TypeScript provides full type safety for your results
const firstUser = activeUsers.data[0];
const userName: string = firstUser.name; // Correctly typed as string
```
### Model Search Methods
Models provide the same search methods as direct record search, but with label pre-filled:
```typescript
// Find all matching records
const users = await UserModel.find({
where: { isActive: true }
});
// Find a single record
const jane = await UserModel.findOne({
where: { email: "jane@example.com" }
});
// Find by ID
const user = await UserModel.findById("user-123");
// Find a unique record
const uniqueUser = await UserModel.findUniq({
where: { email: "unique@example.com" }
});
```
Note that when using model search methods, you don't need to specify the `labels` field in the search query since it's automatically set to the model's label.
For more details on models and type safety, see:
- [Models documentation](../../typescript-sdk/models)
## Search Within Transactions
All search operations can be performed within transactions for consistency:
```typescript
// Begin a transaction
const tx = await db.tx.begin();
try {
// Perform search within the transaction
const users = await db.records.find({
labels: ["USER"],
where: { isActive: true }
}, tx);
// Use the results to make changes
for (const user of users.data) {
if (user.data.lastLogin < olderThan3Months) {
await user.update({ isActive: false }, tx);
}
}
// Commit the transaction when done
await tx.commit();
} catch (error) {
// Roll back the transaction on error
await tx.rollback();
throw error;
}
```
For more details on transactions, see the [Transactions documentation](../../typescript-sdk/transactions).
## Performance Best Practices
When working with the Search API, follow these best practices for optimal performance:
1. **Be Specific with Labels**: Always specify labels to narrow the search scope.
2. **Use Indexed Properties**: Prioritize filtering on properties that have indexes.
3. **Limit Results**: Use pagination to retrieve only the records you need.
4. **Optimize Relationship Traversal**: Avoid deep relationship traversals when possible.
5. **Use Aliases Efficiently**: Define aliases only for records you need to reference in aggregations.
6. **Filter Early**: Apply filters as early as possible in relationship traversals to reduce the amount of data processed.
## Search Related Records
You can efficiently search for records that are related to a specific record using the entry point feature in search queries or direct relationship traversal.
```typescript
// Search for records related to a specific record
const relatedRecords = await db.records.find({
id: 'source-record-id', // Starting from this record
where: { /* search conditions */ }
});
```
This method searches for records that are directly related to a specific record, identified by its ID.
### Parameters
| Parameter | Type | Description |
|-----------|------------------|---------------------------------------------------------------------------|
| `id` | String | The unique identifier of the source record |
| `where` | Object | Filter conditions for records ([learn more](../../concepts/search/where)) |
| `orderBy` | String or Object | Sorting criteria (same as regular search) |
| `skip` | Number | Number of records to skip for pagination |
| `limit` | Number | Maximum number of records to return |
### Example
```typescript
// Find all documents associated with a specific person
const personId = '018e4c71-5f20-7db2-b0b1-e7e681542af9';
const result = await db.records.find({
id: personId,
labels: ['DOCUMENT'],
where: {
status: 'active'
},
orderBy: { createdAt: 'desc' }
});
console.log(`Found ${result.total} documents for this person`);
```
## Get Record Properties
```typescript
// Get properties of a specific record
const properties = await db.records.getProperties('record-id-here');
```
This method retrieves all properties of a specific record.
### Example
```typescript
const properties = await db.records.getProperties('018e4c71-5f20-7db2-b0b1-e7e681542af9');
console.log(properties);
// Output:
// [
// { name: 'firstName', type: 'string', value: 'John' },
// { name: 'lastName', type: 'string', value: 'Doe' },
// { name: 'age', type: 'number', value: 30 }
// ]
```
## Get Record Relations
```typescript
// Get relationships of a specific record
const relationships = await db.records.getRelations('record-id-here', {
skip: 0,
limit: 20
});
```
This method retrieves the relationships of a specific record.
### Parameters
| Parameter | Type | Description |
|------------|--------|-------------|
| `id` | String | The unique identifier of the record |
| `options` | Object | Optional pagination parameters |
### Example
```typescript
const { data, total } = await db.records.getRelations('018e4c71-5f20-7db2-b0b1-e7e681542af9');
console.log(`This record has ${total} relationships`);
data.forEach(relation => {
console.log(`Relation type: ${relation.type}`);
console.log(`Target: ${relation.target.id} (${relation.target.label})`);
});
```
## Search Relations
```typescript
// Search for relationships
const relationships = await db.records.searchRelations({
where: { /* search conditions */ }
});
```
This method searches for relationships between records based on specified criteria.
### Example
```typescript
// Find all employment relationships created in the last month
const lastMonth = new Date();
lastMonth.setMonth(lastMonth.getMonth() - 1);
const { data, total } = await db.records.searchRelations({
where: {
type: 'WORKS_AT',
startDate: { $gte: lastMonth.toISOString() }
}
});
console.log(`Found ${total} new employment relationships`);
```
## TypeScript Type Support
The RushDB SDK provides TypeScript types to enhance developer experience and type safety:
```typescript
import {
SearchQuery,
DBRecord,
DBRecordInstance,
DBRecordsArrayInstance,
Schema,
PropertyDefinition,
Relation
} from '@rushdb/javascript-sdk';
// Define a schema type for better type checking
type UserSchema = {
name: { type: 'string' };
age: { type: 'number' };
email: { type: 'string', unique: true };
isActive: { type: 'boolean', default: true };
};
// Strongly-typed search
const query: SearchQuery = {
where: {
age: { $gt: 21 },
isActive: true
}
};
// Type-safe result handling
const result = await db.records.find(query);
// Working with typed data
result.data.forEach((record) => {
// TypeScript knows that name is a string, age is a number, etc.
console.log(`${record.data.name} (${record.data.age}): ${record.data.email}`);
});
```
## Performance Considerations
To optimize your record retrieval and search operations:
- **Use Appropriate Methods**: Choose the right method for your needs (`findById` for known IDs, `find` for searches)
- **Specify Labels**: Always include label filters to limit the search scope
- **Use Appropriate Limits**: Set reasonable `limit` values to control response size and query performance
- **Implement Pagination**: Use pagination (`skip` and `limit`) for large result sets
- **Optimize Complex Queries**: Break down complex queries when possible
- **Leverage Indexes**: Prioritize filtering on indexed properties
- **Filter Early in Traversals**: Apply filters as early as possible in relationship traversals
- **Consider Caching**: For frequently accessed records, implement caching strategies
- **Use Transactions**: Wrap related operations in transactions for consistency and improved performance
- **Monitor Query Performance**: Test and optimize slow queries
## Related Documentation
- [Search Introduction](../../concepts/search/introduction)
- [Where Clause](../../concepts/search/where)
- [Labels](../../concepts/search/labels)
- [Pagination and Order](../../concepts/search/pagination-order)
- [Record Relationships](../../concepts/relationships)
- [Aggregations](../../concepts/search/aggregations)
- [Transactions](../../concepts/transactions.mdx)
- [Models](../../typescript-sdk/models)
---
# Import Data
When working with RushDB SDK, creating models like Author, Post, and Blog repositories allows us to define clear TypeScript contracts, ensuring type safety and better development experience. However, in many scenarios, you might need to quickly import data from external sources, such as JSON files, without going through the process of registering models.
Using the `createMany` method, you can efficiently import large datasets into RushDB by directly specifying the label and payload data. This approach is ideal for batch imports, data migrations, and integrating external data sources.
## Example: Importing Data from JSON
In this example, we will demonstrate how to import user, post, and blog data from a JSON file into RushDB SDK using the `createMany` method:
```typescript
import RushDB from '@rushdb/javascript-sdk';
import fs from 'fs';
// Initialize the SDK
const db = new RushDB('RUSHDB_API_KEY');
// Load data from a JSON file
const data = JSON.parse(fs.readFileSync('data.json', 'utf8'));
// Example JSON structure
/*
{
"users": [
{ "name": "Alice Johnson", "email": "alice@example.com", "age": 30 },
{ "name": "Bob Smith", "email": "bob@example.com", "age": 25 }
],
"posts": [
{ "title": "Introduction to RushDB SDK", "content": "This is a post about RushDB SDK...", "authorEmail": "alice@example.com" },
{ "title": "Advanced RushDB SDK Usage", "content": "This post covers advanced usage of RushDB SDK...", "authorEmail": "bob@example.com" }
],
"blogs": [
{ "title": "Alice's Tech Blog", "description": "A blog about tech by Alice.", "ownerEmail": "alice@example.com" },
{ "title": "Bob's Coding Adventures", "description": "Bob shares his coding journey.", "ownerEmail": "bob@example.com" }
]
}
*/
// Function to import data
async function importData() {
try {
// Import users
const importedUsers = await db.records.createMany({label: 'user', data: data.users});
console.log('Imported Users:', importedUsers.data);
// Import posts
const importedPosts = await db.records.createMany({label: 'post', data: data.posts});
console.log('Imported Posts:', importedPosts.data);
// Import blogs
const importedBlogs = await db.records.createMany({label: 'blog', data: data.blogs});
console.log('Imported Blogs:', importedBlogs.data);
} catch (error) {
console.error('Error importing data:', error);
}
}
// Run the import function
importData();
```
## Advanced Usage: Import Options
The `createMany` method accepts an optional third parameter to customize how your data is processed and stored:
```typescript
const importOptions = {
suggestTypes: true,
convertNumericValuesToNumbers: true,
capitalizeLabels: false,
relationshipType: 'OWNS',
returnResult: true,
castNumberArraysToVectors: false
};
const importedUsers = await db.records.createMany({
labels: 'user',
data: data.users,
options: importOptions
});
```
### Available Options
| Option | Type | Default | Description |
|---------------------------------|---------|---------------------------------|---------------------------------------------------|
| `suggestTypes` | Boolean | `true` | Automatically infers data types for properties |
| `castNumberArraysToVectors` | Boolean | `false` | Converts numeric arrays to vector type |
| `convertNumericValuesToNumbers` | Boolean | `false` | Converts string numbers to number type |
| `capitalizeLabels` | Boolean | `false` | Converts all labels to uppercase |
| `relationshipType` | String | `__RUSHDB__RELATION__DEFAULT__` | Default relationship type between Records (nodes) |
| `returnResult` | Boolean | `false` | Returns imported records in response |
## How RushDB Import Works
When you import data through the TypeScript SDK, RushDB applies a breadth-first search (BFS) algorithm to parse and transform your data:
1. **Data Preparation**: Each record is assigned a unique UUIDv7 `__id` (unless provided)
2. **Type Inference**: If `suggestTypes` is enabled, RushDB analyzes values to determine appropriate data types
3. **Graph Construction**: Records become nodes in the graph database with properties and relationships
4. **Metadata Generation**: Type information is stored in `__proptypes` for each record
5. **Storage**: Data is efficiently inserted into the underlying Neo4j database
### Data Structure Example
For example, importing this JSON:
```json
{
"car": {
"make": "Tesla",
"model": "Model 3",
"engine": {
"power": 283,
"type": "electric"
}
}
}
```
Creates this graph structure in RushDB:
- A `car` node with properties `make: "Tesla"` and `model: "Model 3"`
- An `engine` node with properties `power: 283` and `type: "electric"`
- A relationship connecting the car to its engine
- Property metadata nodes tracking property names and types
The TypeScript SDK abstracts this complexity, allowing you to focus on your data models.
## Performance Considerations
- For large data imports (>1,000 records), consider batching your requests in chunks
- Setting `returnResult: false` is recommended for large imports to improve performance
- For time-critical imports, pre-process your data to ensure type consistency
## Related Documentation
- [REST API - Import Data](../../rest-api/records/import-data) - Complete API details for data import
- [Storage Internals](../../concepts/storage) - Technical details about how RushDB stores your data
- [Properties](../../concepts/properties) - Learn about property handling and type inference
- [Transactions](../../concepts/transactions.mdx) - Understand how RushDB ensures data integrity during imports
---
# Update Records
Updating [records](../../concepts/records.md) is a crucial operation for maintaining and modifying data within your application. RushDB provides multiple ways to update records, from direct API calls to Model-based abstractions.
This guide covers different approaches to updating records, from the most basic to more advanced patterns.
## Overview
The update record methods in the SDK enable you to:
- Update a single [record](../../concepts/records.md) with new [properties](../../concepts/properties.md)
- Update multiple records in one operation
- Control data type inference and other formatting options
- Update records with precise type control
- Update records within [transactions](../../concepts/transactions.mdx) for data consistency
- Update records using Model abstractions for type safety
## Updating Single Records
There are multiple ways to update records in RushDB. Let's start with the most basic approach using the direct API methods.
### Using RushDB's `update()` Method
The most direct way to update a record is using the API client's `records.update` method:
```typescript
const updatedAuthor = await db.records.update({
target: 'author_id',
label: 'AUTHOR',
data: {
name: 'John Doe Updated',
email: 'john.doe.updated@example.com'
},
options: {
suggestTypes: true
}
});
console.log(updatedAuthor);
/*
{
__id: 'author_id',
__label: 'AUTHOR',
name: 'John Doe Updated',
email: 'john.doe.updated@example.com'
}
*/
```
#### Parameters
- `target`: The target record to modify (record ID, record instance, or record object)
- `label`: The [label](../../concepts/labels.md)/type for the record
- `data`: The updated data for the record as a flat object
- `options` (optional): Configuration options for record update:
- `suggestTypes` (boolean, default: `true`): When true, automatically infers data types for [properties](../../concepts/properties.md)
- `castNumberArraysToVectors` (boolean, default: `false`): When true, converts numeric arrays to vector type
- `convertNumericValuesToNumbers` (boolean, default: `false`): When true, converts string numbers to number type
- `transaction` (optional): A [transaction](../../concepts/transactions.mdx) object or string to include the operation within a transaction
#### Returns
- A promise that resolves to a `DBRecordInstance` containing the updated [record](../../concepts/records.md)
#### Updating Records in Transactions
#### Updating Records in Transactions
```typescript
const transaction = await db.tx.begin();
try {
const updatedAuthor = await db.records.update({
target: 'author_id',
label: 'AUTHOR',
data: {
name: 'Jane Smith Updated',
email: 'jane.smith.updated@example.com'
}
}, transaction);
// Perform other operations...
await transaction.commit();
console.log(updatedAuthor);
} catch (error) {
await transaction.rollback();
throw error;
}
```
### Using RushDB's `set()` Method
While the `update()` method only modifies the specified fields while preserving other existing fields, the `set()` method replaces all fields of a record with the provided values. This is useful when you want to completely reset a record's state.
```typescript
const updatedAuthor = await db.records.set({
target: 'author_id',
label: 'AUTHOR',
data: {
name: 'John Doe Reset',
email: 'john.reset@example.com'
// All other fields will be removed
},
options: {
suggestTypes: true
}
});
console.log(updatedAuthor);
/*
{
__id: 'author_id',
__label: 'AUTHOR',
name: 'John Doe Reset',
email: 'john.reset@example.com'
// Previous fields that were not specified are now gone
}
*/
```
#### Parameters
The parameters for `set()` are identical to those for `update()`:
- `target`: The target record to modify (record ID, record instance, or record object)
- `label`: The [label](../../concepts/labels.md)/type for the record
- `data`: The complete new data for the record as a flat object
- `options` (optional): Configuration options identical to those for `update()`
- `transaction` (optional): A [transaction](../../concepts/transactions.mdx) object or string
#### Returns
- A promise that resolves to a `DBRecordInstance` containing the updated [record](../../concepts/records.md)
#### Difference between `update()` and `set()`
The key difference between these methods:
- `update()`: Performs a partial update, only modifying the specified fields
- `set()`: Performs a complete replacement, removing any fields not specified in the data
#### Setting Records in Transactions
```typescript
const transaction = await db.tx.begin();
try {
const resetAuthor = await db.records.set({
target: 'author_id',
label: 'AUTHOR',
data: {
name: 'Reset Author',
email: 'reset@example.com'
}
}, transaction);
await transaction.commit();
console.log(resetAuthor);
} catch (error) {
await transaction.rollback();
throw error;
}
```
### Property-Based Approach for Precise Type Control
When you need precise control over property types, you can use the property-based approach by passing an array of `PropertyDraft` objects instead of a flat data object:
```typescript
const updatedAuthor = await db.records.update({
target: 'author_id',
label: 'AUTHOR',
data: [
{
name: 'name',
type: 'string',
value: 'John Doe Updated'
},
{
name: 'joinDate',
type: 'datetime',
value: '2025-05-15T14:30:00Z'
}
]
});
console.log(updatedAuthor);
/*
{
__id: 'author_id',
__label: 'AUTHOR',
name: 'John Doe Updated',
joinDate: '2025-05-15T14:30:00Z'
}
*/
```
#### Property Draft Object Properties
Each property draft object supports the following properties:
| Property | Type | Description |
|----------|------|-------------|
| `name` | `string` | The property name |
| `type` | `string` | The data type ('string', 'number', 'boolean', 'datetime', etc.) |
| `value` | `any` | The property value |
| `valueSeparator` | `string` (optional) | Separator to split string values into arrays |
## Updating Multiple Records
## Updating Multiple Records
When you need to update multiple records in a single operation, you can use a combination of `find` and `update` methods.
### Using Find and Update Pattern
```typescript
// Find all authors with a specific name
const authorsToUpdate = await db.records.find({
labels: ['AUTHOR'],
where: { name: 'John Doe' }
});
// Update each record individually
for (const author of authorsToUpdate.data) {
await db.records.update({
target: author.__id,
label: 'AUTHOR',
data: { name: 'John Doe Updated' }
});
}
console.log(authorsToUpdate);
/*
{
data: [
{
__id: 'author_id_1',
__label: 'AUTHOR',
name: 'John Doe Updated',
email: 'john.doe@example.com'
},
{
__id: 'author_id_2',
__label: 'AUTHOR',
name: 'John Doe Updated',
email: 'john.doe@example.com'
}
],
total: 2
}
*/
```
#### Updating Multiple Records in Transactions
```typescript
// Find records matching criteria
const postsToUpdate = await db.records.find({
labels: ['POST'],
where: { rating: { $lt: 5 } }
});
const transaction = await db.tx.begin();
try {
// Update each record within the transaction
for (const post of postsToUpdate.data) {
await db.records.update({
target: post.__id,
label: 'POST',
data: { rating: 5 }
}, transaction);
}
await transaction.commit();
console.log(postsToUpdate);
/*
{
data: [
{
__id: 'post_id_1',
__label: 'POST',
created: '2023-01-02T00:00:00Z',
title: 'Blog Post Title 1',
content: 'This is a blog post content.',
rating: 5
},
{
__id: 'post_id_2',
__label: 'POST',
created: '2023-01-03T00:00:00Z',
title: 'Blog Post Title 2',
content: 'This is another blog post content.',
rating: 5
}
],
total: 2
}
*/
} catch (error) {
await transaction.rollback();
throw error;
}
```
## Updating Records with Models
## Updating Records with Models
The recommended approach for structured applications is to use RushDB's [Models](../models.md). Models provide type safety, validation, and a more intuitive API for working with records.
We'll use the following model definitions for these examples:
```typescript
const AuthorRepo = new Model('author', {
name: { type: 'string' },
email: { type: 'string', uniq: true }
});
```
### Using Model's `update` Method
The `update` method on a model updates a single record.
#### Signature
```typescript
update(
target: DBRecordTarget,
record: Partial>,
transaction?: Transaction | string
): Promise>;
```
#### Parameters
- `target`: The target record to update (ID string, record instance, or record object)
- `record`: An object containing the fields to update and their new values
- `transaction` (optional): A [transaction](../../concepts/transactions.mdx) object or string to include the operation within a transaction
#### Returns
- A promise that resolves to a `DBRecordInstance` containing the updated [record](../../concepts/records.md)
#### Example
```typescript
const updatedAuthor = await AuthorRepo.update('author_id', {
name: 'John Doe Updated'
});
console.log(updatedAuthor);
/*
{
__id: 'author_id',
__label: 'author',
name: 'John Doe Updated',
email: 'john.doe@example.com'
}
*/
```
#### Using with Transactions
```typescript
const transaction = await db.tx.begin();
try {
const updatedAuthor = await AuthorRepo.update('author_id', {
name: 'Jane Smith Updated'
}, transaction);
// Perform other operations...
await transaction.commit();
console.log(updatedAuthor);
} catch (error) {
await transaction.rollback();
throw error;
}
```
### Using Model's `set` Method
The `set` method on a model completely replaces a record's data with the new values.
#### Signature
```typescript
set(
target: DBRecordTarget,
record: InferSchemaTypesWrite,
transaction?: Transaction | string
): Promise>;
```
#### Parameters
- `target`: The target record to modify (ID string, record instance, or record object)
- `record`: An object containing all the fields to set for the record (fields not included will be removed)
- `transaction` (optional): A [transaction](../../concepts/transactions.mdx) object or string to include the operation within a transaction
#### Returns
- A promise that resolves to a `DBRecordInstance` containing the modified [record](../../concepts/records.md)
#### Example
```typescript
const resetAuthor = await AuthorRepo.set('author_id', {
name: 'John Doe Reset',
email: 'john.reset@example.com'
// All fields not specified will be removed
});
console.log(resetAuthor);
/*
{
__id: 'author_id',
__label: 'author',
name: 'John Doe Reset',
email: 'john.reset@example.com'
}
*/
```
#### Difference between `update` and `set`
- `update`: Performs a partial update, preserving fields not specified in the update data
- `set`: Completely replaces all record data with the new values, removing any fields not specified
#### Using with Transactions
```typescript
const transaction = await db.tx.begin();
try {
const resetAuthor = await AuthorRepo.set('author_id', {
name: 'Complete Reset',
email: 'reset@example.com'
}, transaction);
// Perform other operations...
await transaction.commit();
console.log(resetAuthor);
} catch (error) {
await transaction.rollback();
throw error;
}
```
## Complex Example with Multiple Updates in a Transaction
## Complex Example with Multiple Updates in a Transaction
In this example, we'll update an `Author` and a `Post` within the same transaction. This ensures that either both updates succeed, or both are rolled back in case of an error.
```typescript
const transaction = await db.tx.begin();
try {
// Update the author
const updatedAuthor = await AuthorRepo.update('author_id', {
name: 'Updated Author Name'
}, transaction);
// Update the post
const updatedPost = await PostRepo.update('post_id', {
title: 'Updated Post Title',
content: 'Updated content for the post.',
rating: 4.8
}, transaction);
await transaction.commit();
console.log(updatedAuthor);
console.log(updatedPost);
/*
{
__id: 'author_id',
__label: 'author',
name: 'Updated Author Name',
email: 'john.doe@example.com'
}
{
__id: 'post_id',
__label: 'post',
created: '2023-01-02T00:00:00Z',
title: 'Updated Post Title',
content: 'Updated content for the post.',
rating: 4.8
}
*/
} catch (error) {
await transaction.rollback();
throw error;
}
```
## Best Practices for Updating Records
1. **Use Models for Structured Applications**
- Models provide type safety, validation, and better organization
- They enforce schema consistency across your application
2. **Use Transactions for Related Operations**
- When updating multiple records that are related, use [transactions](../../concepts/transactions.mdx)
- Transactions ensure data consistency and allow rollback if operations fail
3. **Handle Uniqueness Constraints**
- Models automatically check uniqueness before updating records
- Handle `UniquenessError` exceptions appropriately
4. **Partial Updates vs. Complete Replacement**
- Use the `update` method for partial updates when you only need to change specific fields
- Use the `set` method when you want to completely replace a record's data
- This minimizes network traffic and avoids unintended side effects
5. **Consider Validation**
- Validate your data on the client side before sending updates
- This improves performance and provides a better user experience
6. **Choose the Right Data Type Control Approach**
- Use the flat object approach for most cases where automatic type inference is sufficient
- Use the property-based approach with `PropertyDraft` objects when you need precise control over types
## Data Type Handling
RushDB supports the same property types for updates as it does for creating records:
- `string`: Text values
- `number`: Numeric values
- `boolean`: True/false values
- `null`: Null values
- `datetime`: ISO8601 format strings (e.g., "2025-04-23T10:30:00Z")
- `vector`: Arrays of numbers (when `castNumberArraysToVectors` is true)
When `suggestTypes` is enabled (default), RushDB automatically infers these types from your data.
When `convertNumericValuesToNumbers` is enabled, string values that represent numbers (e.g., '30') will be converted to their numeric equivalents (e.g., 30).
## Conclusion
Updating records in RushDB can be done through direct API calls or through the Model abstraction. While direct API calls offer flexibility for dynamic or ad-hoc operations, using Models is recommended for most applications due to their type safety, validation capabilities, and more intuitive API.
For more advanced record operations, see the other guides in this section:
- [Get Records](./get-records.md) - Retrieve records from the database
- [Create Records](./create-records.md) - Create new records
- [Delete Records](./delete-records.md) - Remove records from the database
- [Import Data](./import-data.md) - Import data in bulk
---
# Relationships
[Relationships](../concepts/relationships.md) in RushDB connect records to form a rich, interconnected network of data. The TypeScript SDK provides powerful methods for creating, managing, and traversing relationships between records.
## Overview
The relationships API in the SDK enables you to:
- Create connections between records
- Remove relationships between records
- Search for relationships based on specific criteria
- Build complex graph-like data structures
- Navigate between connected entities
## Creating Relationships
### Using RushDB's `attach()` Method
To create a relationship between records, use the `records.attach` method:
```typescript
// Attaching one record to another
const result = await db.records.attach({
source: 'user_123',
target: 'company_456',
options: {
type: 'WORKS_AT',
direction: 'out' // User -> WORKS_AT -> Company
}
});
console.log(result);
/*
{
success: true,
message: "Relationship created successfully"
}
*/
```
#### Parameters
- `params`: An object containing:
- `source`: The source record (ID, record object, or record instance)
- `target`: The target record(s) (ID, array of IDs, record object, record instance, or array of record instances)
- `options` (optional): Configuration for the relationship:
- `type`: The type/name of the relationship
- `direction`: Direction of the relationship ('in' or 'out')
- `transaction` (optional): A [transaction](../concepts/transactions.mdx) object or string to include the operation within a transaction
#### Returns
- A promise that resolves to a success object
### Using Model's `attach()` Method
If you're using models, you can use the model's `attach` method:
```typescript
// Define models
const UserModel = new Model('USER', {
name: { type: 'string' },
email: { type: 'string', uniq: true }
});
const CompanyModel = new Model('COMPANY', {
name: { type: 'string' },
industry: { type: 'string' }
});
// Create records
const user = await UserModel.create({
name: 'John Doe',
email: 'john@example.com'
});
const company = await CompanyModel.create({
name: 'Acme Inc.',
industry: 'Technology'
});
// Create relationship between user and company
await UserModel.attach({
source: user,
target: company,
options: {
type: 'WORKS_AT',
direction: 'out'
}
});
```
### Creating Relationships in Transactions
```typescript
const transaction = await db.tx.begin();
try {
const user = await db.records.create({
label: 'USER',
data: {
name: 'Jane Smith',
email: 'jane@example.com'
}
}, transaction);
const company = await db.records.create({
label: 'COMPANY',
data: {
name: 'Tech Corp',
industry: 'Software'
}
}, transaction);
await db.records.attach({
source: user.data.__id,
target: company.data.__id,
options: {
type: 'WORKS_AT',
direction: 'out'
}
}, transaction);
await transaction.commit();
} catch (error) {
await transaction.rollback();
throw error;
}
```
### Bulk Relationship Creation by Key Match
Use `relationships.createMany` to create relationships in bulk by matching a key from a source label to a key from a target label. This is useful when you ingest data in batches (e.g., from CSV/JSON) and want to connect records created at different times.
```ts
// Create USER -[:ORDERED]-> ORDER for all pairs where
// USER.id = ORDER.userId and both match the given tenant
await db.relationships.createMany({
source: { label: 'USER', key: 'id', where: { tenantId } },
target: { label: 'ORDER', key: 'userId', where: { tenantId } },
type: 'ORDERED',
direction: 'out' // (source) -[:ORDERED]-> (target)
})
```
Parameters
- `source`: Object describing the source side
- `label`: Source record label (string)
- `key`: Property on the source used for equality match (string)
- `where` (optional): Additional filters for source records; same shape as SearchQuery `where`
- `target`: Object describing the target side
- `label`: Target record label (string)
- `key`: Property on the target used for equality match (string)
- `where` (optional): Additional filters for target records; same shape as SearchQuery `where`
- `type` (optional): Relationship type. Defaults to the RushDB default type when omitted
- `direction` (optional): 'in' or 'out'. Defaults to 'out'.
Notes
- Matching condition is always `source[key] = target[key]` plus any additional `where` constraints.
- `where` uses the same operators as record search (e.g., plain equality `{ tenantId: 'ACME' }`, or explicit `{ tenantId: 'ACME' }`).
- Operation can run within a transaction if provided.
## Removing Relationships
### Using RushDB's `detach()` Method
To remove a relationship between records, use the `records.detach` method:
```typescript
const result = await db.records.detach({
source: 'user_123',
target: 'company_456',
options: {
type: 'WORKS_AT' // Optional: Only detach relationships of this type
}
});
console.log(result);
/*
{
success: true,
message: "Relationship removed successfully"
}
*/
```
#### Parameters
- `params`: An object containing:
- `source`: The source record (ID, record object, or record instance)
- `target`: The target record(s) (ID, array of IDs, record object, record instance, or array of record instances)
- `options` (optional): Configuration for the detach operation:
- `typeOrTypes`: The type(s) of relationships to remove (string or array of strings)
- `direction`: Direction of the relationship to remove ('in' or 'out')
- `transaction` (optional): A [transaction](../concepts/transactions.mdx) object or string to include the operation within a transaction
#### Returns
- A promise that resolves to a success object
### Using Model's `detach()` Method
If you're using models, you can use the model's `detach` method:
```typescript
// Detach a relationship between user and company
await UserModel.detach({
source: user.data.__id,
target: company.data.__id,
options: {
typeOrTypes: 'WORKS_AT'
}
});
```
### Bulk Relationship Deletion by Key Match
You can remove relationships in bulk with the SDK using `relationships.deleteMany`. It accepts the same shape as `createMany` and supports two modes:
- key-match mode: match source and target records by equality of a pair of properties (e.g. `USER.id = ORDER.userId`) and delete the relationship between matched pairs.
- many-to-many (cartesian) mode: opt-in operation that deletes relationships between every matching source and target pair that satisfy provided filters — use with extreme caution.
TypeScript example — key match deletion:
```ts
await db.relationships.deleteMany({
source: { label: 'USER', key: 'id', where: { tenantId } },
target: { label: 'ORDER', key: 'userId', where: { tenantId } },
type: 'ORDERED',
direction: 'out'
})
```
TypeScript example — many-to-many deletion (explicit opt-in):
```ts
// WARNING: manyToMany will perform a cartesian-style deletion across the
// filtered sets. Only use with explicit filters on both sides.
await db.relationships.deleteMany({
source: { label: 'USER', where: { tenantId } },
target: { label: 'TAG', where: { tenantId } },
type: 'HAS_TAG',
direction: 'out',
manyToMany: true
})
```
Parameters
- `source`: Object describing the source side
- `label`: Source record label (string)
- `key` (optional): Property on the source used for equality match (string)
- `where` (optional): Additional filters for source records; same shape as SearchQuery `where`
- `target`: Object describing the target side
- `label`: Target record label (string)
- `key` (optional): Property on the target used for equality match (string)
- `where` (optional): Additional filters for target records; same shape as SearchQuery `where`
- `type` (optional): Relationship type to restrict deletions
- `direction` (optional): 'in' or 'out'. Defaults to 'out'.
- `manyToMany` (optional): boolean. When `true` the operation will perform deletions across all source/target pairs matching provided filters (cartesian). This must be explicitly set.
Important notes and safeguards
- If `manyToMany` is not provided or is `false`, both `source.key` and `target.key` must be supplied — deletion matches records where `source[key] = target[key]`.
- If `manyToMany` is `true`, the server requires non-empty `where` filters for both `source` and `target` to avoid accidental full-cartesian deletions.
- Use `manyToMany` only when you intentionally want to delete relationships across filtered sets. Consider testing on a staging dataset first.
## Finding Relationships
### Using RushDB's `relationships.find()` Method
To search for relationships based on specific criteria, use the `relationships.find` method:
```typescript
const relationships = await db.relationships.find({
where: {
type: 'WORKS_AT',
source: {
label: 'USER',
name: { $contains: 'John' }
},
target: {
label: 'COMPANY',
industry: 'Technology'
}
},
limit: 10
});
console.log(relationships);
/*
{
data: [
{
id: 'relation_id_1',
type: 'WORKS_AT',
source: 'user_123',
target: 'company_456',
direction: 'out'
},
// More relationships...
],
total: 5
}
*/
```
#### Parameters
- `searchQuery`: A search query object to find matching relationships
- `where`: Conditions to filter relationships
- `limit`: Maximum number of results to return
- `skip`: Number of results to skip
- `transaction` (optional): A [transaction](../concepts/transactions.mdx) object or string to include the operation within a transaction
#### Returns
- A promise that resolves to an array of relationship objects
### Finding Relationships in Transactions
```typescript
const transaction = await db.tx.begin();
try {
const relationships = await db.relationships.find({
where: {
type: 'WORKS_AT',
direction: 'out'
}
}, transaction);
// Perform other operations...
await transaction.commit();
console.log(relationships);
} catch (error) {
await transaction.rollback();
throw error;
}
```
## Retrieving Relationships for a Record
### Using RushDB's `records.relations()` Method
To get all relationships for a specific record, use the `records.relations` method:
```typescript
const relations = await db.records.relations('user_123');
console.log(relations);
/*
{
data: [
{
id: 'relation_id_1',
type: 'WORKS_AT',
source: 'user_123',
target: 'company_456',
direction: 'out'
},
{
id: 'relation_id_2',
type: 'FOLLOWS',
source: 'user_789',
target: 'user_123',
direction: 'in'
},
// More relationships...
],
total: 5
}
*/
```
#### Parameters
- `target`: The record to get relationships for (ID, record object, or record instance)
- `transaction` (optional): A [transaction](../concepts/transactions.mdx) object or string to include the operation within a transaction
#### Returns
- A promise that resolves to an array of relationship objects
## Relationship Direction
Relationships in RushDB have a direction, which defines how records are connected. When creating or querying relationships, you can specify the direction:
- `out`: The relationship goes from source to target: `(source) -[RELATIONSHIP]-> (target)`
- `in`: The relationship goes from target to source: `(source) <-[RELATIONSHIP]- (target)`
For example:
```typescript
// Outgoing relationship (User -[WORKS_AT]-> Company)
await db.records.attach({
source: userId,
target: companyId,
options: {
type: 'WORKS_AT',
direction: 'out'
}
});
// Incoming relationship (Department <-[BELONGS_TO]- Employee)
await db.records.attach({
source: departmentId,
target: employeeId,
options: {
type: 'BELONGS_TO',
direction: 'in'
}
});
```
## Custom Relationship Types
By default, RushDB uses a standard relationship type, but you can specify custom types to model your domain more accurately:
```typescript
// Creating a relationship with a custom type
await db.records.attach({
source: mentorId,
target: menteeId,
options: {
type: 'MENTORS',
direction: 'out'
}
});
// Creating a relationship when importing nested data
const company = await db.records.create({
label: 'COMPANY',
data: {
name: 'Tech Corp',
employees: [
{ name: 'Jane Smith', position: 'CTO' },
{ name: 'John Doe', position: 'Developer' }
]
},
options: {
relationshipType: 'EMPLOYS' // Custom relationship type
}
});
```
## Best Practices for Working with Relationships
1. **Use Meaningful Relationship Types**
- Choose descriptive names for relationship types that clearly convey their meaning
- Establish a consistent naming convention for relationships (e.g., using verbs like 'FOLLOWS', 'WORKS_AT')
2. **Consider Relationship Direction**
- Use the direction parameter to model the natural flow of relationships
- For bidirectional relationships, create two relationships with opposite directions
3. **Use Transactions for Multiple Operations**
- When creating or updating multiple records and their relationships, use transactions
- This ensures all operations succeed or fail together, maintaining data consistency
4. **Optimize Relationship Queries**
- Specify relationship types when searching to improve performance
- Use direction filters to narrow down search results
5. **Model Domain Relationships Carefully**
- Use relationship types that map to real-world concepts in your domain
- Consider the cardinality of relationships (one-to-one, one-to-many, many-to-many)
## Conclusion
The Relationships API in the RushDB TypeScript SDK provides a comprehensive set of methods for creating, managing, and querying relationships between records. By understanding these methods and their parameters, you can effectively build interconnected data structures in your application.
For more information on related topics, see:
- [Records](./records/create-records.md) - Work with records that participate in relationships
- [Search](./records/get-records.md) - Advanced querying across relationships
- [Models](./models.md) - Define structured schemas for your data
---
# Transactions
The RushDB TypeScript SDK provides a simple but powerful interface for working with database transactions. Transactions allow you to perform multiple database operations atomically, ensuring that either all operations succeed or none do, which helps maintain data consistency.
## Transaction Overview
Transactions in RushDB TypeScript SDK:
- Enable multiple database operations to be executed as a single atomic unit
- Provide ACID (Atomicity, Consistency, Isolation, Durability) guarantees
- Automatically roll back after a timeout to prevent hanging transactions
- Can be explicitly committed or rolled back
## Transaction API
The SDK provides transaction-related methods through the `tx` object:
```typescript
// Access the transaction API
const tx = db.tx;
```
### Begin a Transaction
Creates a new transaction and returns a transaction object:
```typescript
const transaction = await db.tx.begin({
ttl: 10000 // Optional: Time to live in milliseconds (default: 5000ms, max: 30000ms)
});
// transaction object contains the transaction ID
console.log(transaction.id); // e.g., "018e5c31-f35a-7000-89cd-850db63a1e77"
```
### Get a Transaction
Checks if a transaction exists and retrieves its information:
```typescript
// You can pass either a transaction object or a transaction ID string
const txInfo = await db.tx.get(transaction);
// or
const txInfo = await db.tx.get("018e5c31-f35a-7000-89cd-850db63a1e77");
```
### Commit a Transaction
Commits all changes made within the transaction, making them permanent in the database:
```typescript
// You can pass either a transaction object or a transaction ID string
await transaction.commit()
// or
await db.tx.commit(transaction);
// or
await db.tx.commit("018e5c31-f35a-7000-89cd-850db63a1e77");
```
### Rollback a Transaction
Discards all changes made within the transaction:
```typescript
// You can pass either a transaction object or a transaction ID string
await transaction.rollback()
// or
await db.tx.rollback(transaction);
// or
await db.tx.rollback("018e5c31-f35a-7000-89cd-850db63a1e77");
```
## Using Transactions with API Methods
Most API methods in the RushDB TypeScript SDK accept an optional transaction parameter that allows you to include the operation in a transaction:
```typescript
// Create a transaction
const transaction = await db.tx.begin({ ttl: 10000 });
try {
// Perform operations as part of the transaction
const person = await db.records.create({
label: "Person",
data: { name: "John Doe", age: 30 }
}, transaction); // Pass the transaction as the second parameter
const address = await db.records.create({
label: "Address",
data: { street: "123 Main St", city: "New York" }
}, transaction);
// Create a relationship between the person and address
await db.records.attach({
source: person,
target: address,
options: {
type: "LIVES_AT",
direction: "out"
}
}, transaction);
// Commit the transaction if all operations succeeded
await transaction.commit()
// or
// await db.tx.commit(transaction);
console.log("All operations completed successfully!");
} catch (error) {
// Rollback the transaction if any operation failed
await transaction.rollback()
// or
// await db.tx.rollback(transaction);
console.error("Transaction failed:", error);
}
```
## Transaction Timeout
Transactions in RushDB have a timeout mechanism to prevent hanging transactions:
- Default timeout: 5 seconds (5000ms)
- Maximum timeout: 30 seconds (30000ms)
- If a transaction is not committed or rolled back within its TTL, it will be automatically rolled back
## Best Practices
1. **Keep transactions short and focused**
Long-running transactions can lead to resource contention and reduce overall system performance.
2. **Set appropriate TTL**
Choose a TTL that gives your operations enough time to complete, but not so long that resources are unnecessarily tied up.
3. **Always commit or rollback explicitly**
Explicitly commit or rollback transactions rather than relying on automatic timeout.
4. **Implement proper error handling**
Always use try/catch blocks when working with transactions to ensure proper rollback in case of errors.
5. **Use transactions only when necessary**
For single operations, you don't need to use transactions. Only use transactions when multiple operations need to be atomic.
6. **Be aware of transaction scope**
Transactions in RushDB are tied to your API token and will affect only the operations performed with that token.
## Example: Complete Transaction Workflow
Here's a complete example showing a transaction workflow for creating a user profile with multiple related records:
```typescript
import RushDB from '@rushdb/javascript-sdk';
// Initialize SDK
const db = new RushDB('RUSHDB_API_KEY');
async function createUserProfile(userData) {
// Begin a transaction with 15-second TTL
const transaction = await db.tx.begin({ ttl: 15000 });
try {
// Create user record
const user = await db.records.create({
label: "User",
data: {
username: userData.username,
email: userData.email
}
}, transaction);
// Create profile record
const profile = await db.records.create({
label: "Profile",
data: {
firstName: userData.firstName,
lastName: userData.lastName,
birthDate: userData.birthDate
}
}, transaction);
// Create address record
const address = await db.records.create({
label: "Address",
data: {
street: userData.street,
city: userData.city,
postalCode: userData.postalCode,
country: userData.country
}
}, transaction);
// Create relationships
await db.records.attach({
source: user,
target: profile,
options: {
type: "HAS_PROFILE",
direction: "out"
}
}, transaction);
await db.records.attach({
source: profile,
target: address,
options: {
type: "HAS_ADDRESS",
direction: "out"
}
}, transaction);
// Commit the transaction
await transaction.commit()
// or
// await db.tx.commit(transaction);
return {
success: true,
user
};
} catch (error) {
// Rollback the transaction on any error
await transaction.rollback()
// or
// await db.tx.rollback(transaction);
return {
success: false,
error: error.message
};
}
}
// Usage
createUserProfile({
username: "johndoe",
email: "john@example.com",
firstName: "John",
lastName: "Doe",
birthDate: "1990-01-01",
street: "123 Main St",
city: "New York",
postalCode: "10001",
country: "USA"
}).then(result => {
if (result.success) {
console.log("User profile created successfully:", result.user);
} else {
console.error("Failed to create user profile:", result.error);
}
});
```
---
# DBRecord
`DBRecord` is a type representing a database [record](../../concepts/records) in RushDB. It combines internal system properties with schema-defined data [properties](../../concepts/properties).
## Type Definition
```typescript
export type DBRecord = FlattenTypes<
DBRecordInternalProps & FlattenTypes>
>
```
## Type Parameters
| Parameter | Description |
|-----------|-------------|
| `S extends Schema = Schema` | The schema type that defines the structure of the record |
## Properties
A `DBRecord` consists of both system properties and schema-defined properties:
### System Properties
| Property | Type | Description |
|----------|------|-------------|
| `__id` | `string` | The unique identifier of the record |
| `__label` | `string` | The label/type of the record |
| `__proptypes` | `object` | Property type information for the record |
### Schema Properties
The remaining properties are defined by the schema `S` that was used to create the record. These properties can be:
- Required properties (as defined in the schema with `required: true`)
- Optional properties (as defined in the schema without `required: true` or with `required: false`)
## Related Types
### DBRecordInternalProps
```typescript
type DBRecordInternalProps = {
readonly __id: string
readonly __label: string
readonly __proptypes?: FlattenTypes<
{
[Key in RequiredKeysRead]: S[Key]['type']
} & {
[Key in OptionalKeysRead]?: S[Key]['type']
}
>
}
```
Contains the internal system properties of a database record.
### RecordProps
```typescript
export type RecordProps =
S extends S ? InferSchemaTypesRead
: {
[K in keyof S]?: S[K]
}
```
Contains the schema-defined properties of a database record.
### DBRecordInferred
```typescript
export type DBRecordInferred> =
Q extends { aggregate: infer A extends Record } ? DBRecord & ExtractAggregateFields
: DBRecord
```
An extension of `DBRecord` that includes any aggregated fields from a search query.
## Usage Example
```typescript
// Define a schema
const UserSchema = {
name: { type: 'string', required: true },
email: { type: 'string', required: true, uniq: true },
age: { type: 'number' }
};
// A record matching this schema would have type:
type UserRecord = DBRecord;
// Example of what the record would look like:
const user: UserRecord = {
__id: 'user_123',
__label: 'User',
__proptypes: { name: 'string', email: 'string', age: 'number' },
name: 'John Doe',
email: 'john@example.com',
age: 30
};
```
---
# DBRecordInstance
`DBRecordInstance` is a class that wraps a DBRecord and provides methods for manipulating it. This class serves as an interface for working with individual [records](../../concepts/records) in the database. It allows for updating [properties](../../concepts/properties) and managing [relationships](../../concepts/relationships).
## Class Definition
```typescript
export class DBRecordInstance<
S extends Schema = Schema,
Q extends SearchQuery = SearchQuery
> {
data?: DBRecordInferred
}
```
## Type Parameters
| Parameter | Description |
|-----------|-------------|
| `S extends Schema = Schema` | The schema type that defines the structure of the record |
| `Q extends SearchQuery = SearchQuery` | The search query type used to retrieve this record |
## Constructor
```typescript
constructor(data?: DBRecordInferred)
```
Creates a new `DBRecordInstance`.
### Parameters
| Parameter | Type | Description |
|-----------|------|-------------|
| `data` | `DBRecordInferred` | Optional data for the record |
## Properties
### data
```typescript
data?: DBRecordInferred
```
The actual record data, which may include aggregated fields if the record was retrieved via a query with aggregation.
## Methods
### exists()
```typescript
exists(): boolean
```
Checks if this record instance exists. A record is considered to exist if it has a valid ID and label.
**Returns**: `true` if the record exists (has both valid ID and label), `false` otherwise
**Note**: Unlike other getter methods (`id()`, `label()`, `proptypes()`, etc.), this method does not throw an error when the record data is missing or invalid. It safely returns `false` instead.
### id()
```typescript
id(): string
```
Gets the unique identifier of the record.
**Returns**: The ID of the record
**Throws**: Error if the record's ID is missing or incorrect
### label()
```typescript
label(): string
```
Gets the label/type of the record.
**Returns**: The label of the record
**Throws**: Error if the record's label is missing or incorrect
### proptypes()
```typescript
proptypes(): object | undefined
```
Gets the property types of the record.
**Returns**: The property types of the record or undefined if not available
**Throws**: Error if the record's proptypes are missing or incorrect
### date()
```typescript
date(): Date
```
Gets the date derived from the record's ID.
**Returns**: The date from the record's ID
**Throws**: Error if the record's ID is missing or incorrect
### timestamp()
```typescript
timestamp(): number
```
Gets the timestamp derived from the record's ID.
**Returns**: The timestamp from the record's ID
**Throws**: Error if the record's ID is missing or incorrect
### delete()
```typescript
async delete(transaction?: Transaction | string): Promise
```
Deletes the record from the database.
**Parameters**:
- `transaction`: Optional transaction or transaction ID
**Returns**: Promise resolving to the result of the delete operation
**Throws**: Error if the record data is undefined
### update()
```typescript
async update(
data: Partial> | Array,
transaction?: Transaction | string
): Promise
```
Updates the record with the given data.
**Parameters**:
- `data`: The data to update the record with
- `transaction`: Optional transaction or transaction ID
**Returns**: Promise resolving to the result of the update operation
**Throws**: Error if the record data is undefined
### set()
```typescript
async set(
data: InferSchemaTypesWrite | Array,
transaction?: Transaction | string
): Promise
```
Replaces the record's data with the given data.
**Parameters**:
- `data`: The data to set on the record
- `transaction`: Optional transaction or transaction ID
**Returns**: Promise resolving to the result of the set operation
**Throws**: Error if the record data is undefined
### attach()
```typescript
async attach(
target: RelationTarget,
options?: RelationOptions,
transaction?: Transaction | string
): Promise
```
Creates a relationship from this record to the target record(s).
**Parameters**:
- `target`: The target record(s) to create a relationship to
- `options`: Optional relationship options
- `transaction`: Optional transaction or transaction ID
**Returns**: Promise resolving to the result of the attach operation
**Throws**: Error if the record data is undefined
### detach()
```typescript
async detach(
target: RelationTarget,
options?: RelationDetachOptions,
transaction?: Transaction | string
): Promise
```
Removes a relationship from this record to the target record(s).
**Parameters**:
- `target`: The target record(s) to remove a relationship from
- `options`: Optional relationship detach options
- `transaction`: Optional transaction or transaction ID
**Returns**: Promise resolving to the result of the detach operation
**Throws**: Error if the record data is undefined
## Usage Example
```typescript
// Get a record instance from a model
const userRecord = await UserModel.findById('user_123');
// Access record data
console.log(userRecord.id()); // 'user_123'
console.log(userRecord.label()); // 'User'
console.log(userRecord.data?.name); // 'John Doe'
// Update the record
await userRecord.update({
name: 'Jane Doe',
age: 31
});
// Create a relationship to another record
const postRecord = await PostModel.findById('post_456');
await userRecord.attach(postRecord, { type: 'AUTHORED' });
// Delete the record
await userRecord.delete();
```
---
# DBRecordTarget
`DBRecordTarget` is a type that represents a target [record](../../concepts/records) for operations like set, update, attach, detach, and delete. It is commonly used when working with [relationships](../../concepts/relationships) and [property](../../concepts/properties) updates.
## Type Definition
```typescript
export type DBRecordTarget = DBRecord | DBRecordInstance | string
```
This union type allows for multiple ways to reference a record when performing operations on it.
## Type Options
| Type | Description |
|------|-------------|
| `DBRecord` | A record object with all its properties |
| `DBRecordInstance` | A record instance with methods for manipulating the record |
| `string` | A record ID string |
## Usage
This type is used in various operations that need to target a specific record:
### In Model Methods
```typescript
// Using a record ID string
await UserModel.attach({
source: 'user_123',
target: 'post_456',
options: { type: 'AUTHORED' }
});
// Using a record object
const user = {
__id: 'user_123',
__label: 'User',
name: 'John Doe'
};
await UserModel.attach({
source: user,
target: 'post_456',
options: { type: 'AUTHORED' }
});
// Using a record instance
const userInstance = await UserModel.findById('user_123');
await UserModel.attach({
source: userInstance,
target: 'post_456',
options: { type: 'AUTHORED' }
});
```
### In Record Instance Methods
While `DBRecordInstance` methods like `attach` and `detach` use `DBRecordTarget` internally, you don't need to explicitly use this type as the instance's ID is already known:
```typescript
const userInstance = await UserModel.findById('user_123');
// Using a record ID string
await userInstance.attach('post_456', { type: 'AUTHORED' });
// Using a record object
const post = {
__id: 'post_456',
__label: 'Post',
title: 'My Post'
};
await userInstance.attach(post, { type: 'AUTHORED' });
// Using a record instance
const postInstance = await PostModel.findById('post_456');
await userInstance.attach(postInstance, { type: 'AUTHORED' });
```
---
# DBRecordsArrayInstance
`DBRecordsArrayInstance` is a class that manages an array of `DBRecordInstance` objects. It typically represents the result of a [search query](../../concepts/search/introduction.md) that returns multiple [records](../../concepts/records).
## Class Definition
```typescript
export class DBRecordsArrayInstance<
S extends Schema = Schema,
Q extends SearchQuery = SearchQuery
> {
data?: Array>
total: number | undefined
searchQuery?: SearchQuery
}
```
## Type Parameters
| Parameter | Description |
|---------------------------------------------|-----------------------------------------------------------|
| `S extends Schema = Schema` | The schema type that defines the structure of the records |
| `Q extends SearchQuery = SearchQuery` | The search query type used to retrieve these records |
## Constructor
```typescript
constructor(
data?: Array>,
total?: number,
searchQuery?: SearchQuery
)
```
Creates a new `DBRecordsArrayInstance`.
### Parameters
| Parameter | Type | Description |
|---------------|---------------------------------|----------------------------------------------------------------------------------------------------|
| `data` | `Array>` | Optional array of record instances |
| `total` | `number` | Optional total count of records (may be greater than the length of `data` when pagination is used) |
| `searchQuery` | `SearchQuery` | Optional search query that was used to retrieve these records |
## Properties
### data
```typescript
data?: Array>
```
An array of record instances.
### total
```typescript
total: number | undefined
```
The total number of records that match the search query, which may be greater than the number of records in `data` if pagination is used.
### searchQuery
```typescript
searchQuery?: SearchQuery
```
The search query that was used to retrieve these records.
## Methods
Currently, the `DBRecordsArrayInstance` class has no dedicated methods implemented. According to code comments, future improvements may include:
```typescript
// @TODO: Bulk actions: Delete (by ids or searchQuery?); Export to csv; Props update for found Records; Attach/Detach
// @TODO: Create next({preserveData?: boolean}) method (or smth similar) to fetch next portion of data based on this.searchQuery
```
## Usage Example
```typescript
// Get multiple records from a model
const userRecords = await UserModel.find({
where: {
age: { $gt: 30 }
},
limit: 10
});
// Access the records
console.log(userRecords.total); // Total number of matching records
console.log(userRecords.data?.length); // Number of records in this page (max 10)
// Access individual record instances
userRecords.data?.forEach(user => {
console.log(user.id(), user.data?.name);
});
// Access the original search query
console.log(userRecords.searchQuery);
```
## Future Enhancements
According to the code comments, future enhancements may include:
1. Bulk actions for operations like delete, export to CSV, property updates, and relationship management (attach/detach)
2. Pagination methods to fetch the next set of records based on the original search query
---
# Model
The `Model` class represents a schema-defined entity in RushDB. It provides methods to perform CRUD operations and manage [relationships](../../concepts/relationships) between [records](../../concepts/records). Models are identified by [labels](../../concepts/labels) in the database.
## Usage
```typescript
import { Model } from '@rushdb/javascript-sdk';
// Define a schema
const UserSchema = {
name: { type: 'string', required: true },
email: { type: 'string', required: true, uniq: true },
age: { type: 'number' },
active: { type: 'boolean', default: true }
};
// Create a model
const UserModel = new Model('User', UserSchema);
```
## Type Parameters
| Parameter | Description |
|--------------------------|-------------------------------------------------------------------|
| `S extends Schema = any` | The schema type that defines the structure of the model's records |
## Constructor
```typescript
constructor(modelName: string, schema: S)
```
Creates a new `Model` instance.
### Parameters
| Parameter | Type | Description |
|------------------|------------------|----------------------------------------------------------------------------|
| `modelName` | `string` | The name/label of the model in the database |
| `schema` | `S` | The schema definition that describes the model's structure |
## Properties
### label
```typescript
public readonly label: string
```
The name/label of the model in the database.
### schema
```typescript
public readonly schema: S
```
The schema definition that describes the model's structure.
### draft
```typescript
readonly draft!: InferType>
```
Type helper for a draft version of the schema. Represents a flat object containing only the record's own properties (defined by the schema), excluding system fields such as `__id`, `__label`, and `__proptypes`. This type does not yet have a representation on the database side.
### record
```typescript
readonly record!: DBRecord
```
Type helper for a fully-defined record with database representation. Similar to the draft, but includes all fields that come with the record's database-side representation, such as `__id`, `__label`, and `__proptypes`.
### searchQuery
```typescript
readonly searchQuery!: SearchQuery
```
Type helper for a SearchQuery of the schema. Represents a structured query input that enables filtering, sorting, pagination, and aggregation of records based on schema-defined fields. Useful for composing reusable, type-safe search expressions.
### recordInstance
```typescript
readonly recordInstance!: DBRecordInstance
```
Type helper for a single record instance. Extends the record by providing additional methods to operate on this specific record, such as saving, updating, or deleting it.
### recordsArrayInstance
```typescript
readonly recordsArrayInstance!: DBRecordsArrayInstance
```
Type helper for an array of record instances. Similar to a single record instance but supports batch or bulk operations, allowing efficient management of multiple records simultaneously.
## Methods
### getLabel()
```typescript
public getLabel(): string
```
Retrieves the model's label.
**Returns**: The label/name of the model
### toInstance()
```typescript
public toInstance(record: DBRecord): DBRecordInstance
```
Converts a database record to a record instance with additional methods.
**Parameters**:
- `record`: The database record to convert
**Returns**: A record instance with additional methods
**Throws**: Error if no RushDB instance was provided during initialization
### find()
```typescript
async find = SearchQuery>(
searchQuery?: Q & { labels?: never },
transaction?: Transaction | string
): Promise>
```
Finds records that match the given search criteria.
**Parameters**:
- `searchQuery`: Optional search criteria
- `transaction`: Optional transaction or transaction ID
**Returns**: Promise resolving to matching records
### findOne()
```typescript
async findOne = SearchQuery>(
searchQuery?: Q & {
labels?: never
limit?: never
skip?: never
},
transaction?: Transaction | string
): Promise | null>
```
Finds a single record that matches the given search criteria.
**Parameters**:
- `searchQuery`: Optional search criteria
- `transaction`: Optional transaction or transaction ID
**Returns**: Promise resolving to the matching record or null if not found
### findById()
```typescript
async findById(
id: string,
transaction?: Transaction | string
): Promise | null>
```
Finds a record by its ID.
**Parameters**:
- `id`: The ID of the record to find
- `transaction`: Optional transaction or transaction ID
**Returns**: Promise resolving to the matching record or null if not found
### findUniq()
```typescript
async findUniq = SearchQuery>(
searchQuery?: Q & {
labels?: never
limit?: never
skip?: never
},
transaction?: Transaction | string
): Promise | null>
```
Finds a unique record that matches the given search criteria.
**Parameters**:
- `searchQuery`: Optional search criteria
- `transaction`: Optional transaction or transaction ID
**Returns**: Promise resolving to the unique matching record or null if not found
### create()
```typescript
async create(
record: InferSchemaTypesWrite,
transaction?: Transaction | string
): Promise>
```
Creates a new record in the database.
**Parameters**:
- `record`: The record data to create
- `transaction`: Optional transaction or transaction ID
**Returns**: Promise resolving to the created record
**Throws**: UniquenessError if the record violates uniqueness constraints
### attach()
```typescript
attach(
{
source,
target,
options
}: {
source: DBRecordTarget
target: RelationTarget
options?: RelationOptions
},
transaction?: Transaction | string
): Promise
```
Attaches a relationship between records.
**Parameters**:
- `params`: Object containing source, target, and relationship options
- `transaction`: Optional transaction or transaction ID
**Returns**: Promise resolving to the result of the attach operation
## Type Definitions
### InferType
```typescript
export type InferType = Model> = FlattenTypes>
```
Helper type that infers the type structure from a Model instance.
**Type Parameters**:
- `M`: The Model type to infer from
---
# RelationTarget
`RelationTarget` is a type that represents the target(s) for [relationship](../../concepts/relationships) operations like attach and detach.
## Type Definition
```typescript
export type RelationTarget =
| DBRecordsArrayInstance
| MaybeArray>
| MaybeArray>
| MaybeArray
```
This union type allows for multiple ways to reference one or more target records when creating or removing relationships.
## Type Options
| Type | Description |
|------|-------------|
| `DBRecordsArrayInstance` | An array instance containing multiple record instances |
| `MaybeArray>` | A single record or array of records |
| `MaybeArray>` | A single record instance or array of record instances |
| `MaybeArray` | A single record ID string or array of record ID strings |
Where `MaybeArray` is defined as:
```typescript
type MaybeArray = T | Array
```
## Related Types
### Relation
```typescript
export type Relation = {
sourceId: string
sourceLabel: string
targetId: string
targetLabel: string
type: string
}
```
Represents a relationship between two records.
### RelationDirection
```typescript
export type RelationDirection = 'in' | 'out'
```
Specifies the direction of a relationship.
### RelationOptions
```typescript
export type RelationOptions = {
direction?: RelationDirection;
type?: string
}
```
Options for creating a relationship.
### RelationDetachOptions
```typescript
export type RelationDetachOptions = {
direction?: RelationDirection
typeOrTypes?: MaybeArray
}
```
Options for removing a relationship.
## Usage
### Single Target
```typescript
// Using a record ID string
await userInstance.attach('post_123', { type: 'AUTHORED' });
// Using a record object
const post = {
__id: 'post_123',
__label: 'Post',
title: 'My Post'
};
await userInstance.attach(post, { type: 'AUTHORED' });
// Using a record instance
const postInstance = await PostModel.findById('post_123');
await userInstance.attach(postInstance, { type: 'AUTHORED' });
```
### Multiple Targets
```typescript
// Using an array of record ID strings
await userInstance.attach(['post_123', 'post_456'], { type: 'AUTHORED' });
// Using an array of record objects
const posts = [
{ __id: 'post_123', __label: 'Post', title: 'First Post' },
{ __id: 'post_456', __label: 'Post', title: 'Second Post' }
];
await userInstance.attach(posts, { type: 'AUTHORED' });
// Using an array of record instances
const firstPost = await PostModel.findById('post_123');
const secondPost = await PostModel.findById('post_456');
await userInstance.attach([firstPost, secondPost], { type: 'AUTHORED' });
// Using a DBRecordsArrayInstance from a find operation
const posts = await PostModel.find({
where: {
title: { $regex: '^My' }
}
});
await userInstance.attach(posts, { type: 'AUTHORED' });
```
### Detaching Relationships
```typescript
// Detach all relationships to a specific record
await userInstance.detach('post_123');
// Detach all relationships of a specific type
await userInstance.detach('post_123', { typeOrTypes: 'AUTHORED' });
// Detach multiple relationship types
await userInstance.detach('post_123', { typeOrTypes: ['AUTHORED', 'LIKED'] });
// Detach incoming relationships only
await userInstance.detach('post_123', { direction: 'in' });
```
---
# RushDB
The `RushDB` class is the main entry point for interacting with the RushDB database. It manages API connections and model registration. It provides access to [records](../../concepts/records), [labels](../../concepts/labels), and [transactions](../../concepts/transactions).
## Initialization
```typescript
import RushDB from '@rushdb/javascript-sdk';
// Create an instance with an API token
const db = new RushDB('RUSHDB_API_KEY', {
// Optionnaly provide API url to your RushDB instance
url: 'https://api.rushdb.com/api/v1'
});
```
## Constructor
```typescript
constructor(token?: string, config?: SDKConfig)
```
Creates a new `RushDB` instance.
### Parameters
| Parameter | Type | Description |
|-----------|------|-------------|
| `token` | `string` | Optional API token for authentication |
| `config` | `SDKConfig` | Optional configuration object |
### SDKConfig Options
| Option | Type | Description |
|--------|------|-------------|
| `url` | `string` | The base URL of the RushDB API |
| `timeout` | `number` | Request timeout in milliseconds |
| `httpClient` | `HttpClient` | Custom HTTP client implementation |
| `debug` | `boolean` | Enable debug mode |
## Properties
### state
```typescript
state: State
```
The internal state of the SDK instance.
## Static Methods
### getInstance()
```typescript
public static getInstance(): RushDB
```
Gets the singleton instance of the RushDB class.
**Returns**: The RushDB instance
### init()
```typescript
public static async init(): Promise
```
Initializes and returns the RushDB instance. This method is used internally by other SDK components (like Transaction and DBRecordInstance) to access the API.
**Returns**: A Promise that resolves to the RushDB instance
**Example**:
```typescript
// Internal usage in SDK components
async someMethod() {
const instance = await RushDB.init()
return await instance.someApi.someMethod()
}
```
## Instance Methods
### toInstance()
### toInstance()
```typescript
public toInstance(record: DBRecord): DBRecordInstance
```
Converts a database record to a record instance.
**Parameters**:
- `record`: The record to convert
**Returns**: A record instance with additional methods
## Usage Example
```typescript
import RushDB, { Model } from '@rushdb/javascript-sdk';
// Initialize the SDK
const db = new RushDB('RUSHDB_API_KEY', {
url: 'https://api.rushdb.com/api/v1'
});
// Define schemas
const UserSchema = {
name: { type: 'string', required: true },
email: { type: 'string', required: true, uniq: true },
age: { type: 'number' }
};
const PostSchema = {
title: { type: 'string', required: true },
content: { type: 'string', required: true },
published: { type: 'boolean', default: false }
};
// Create models
const UserModel = new Model('User', UserSchema);
const PostModel = new Model('Post', PostSchema);
// Use the models to interact with the database
async function main() {
// Create a user
const user = await UserModel.create({
name: 'John Doe',
email: 'john@example.com',
age: 30
});
// Create a post
const post = await PostModel.create({
title: 'My First Post',
content: 'This is my first post!',
published: true
});
// Create a relationship
await UserModel.attach({
source: user,
target: post,
options: { type: 'AUTHORED' }
});
// Find all users
const users = await UserModel.find();
console.log(users.data?.length);
// Find a specific user
const foundUser = await UserModel.findOne({
where: { email: 'john@example.com' }
});
console.log(foundUser?.data?.name);
}
main().catch(console.error);
```
---
# SearchQuery
`SearchQuery` is a type that defines the structure for querying [records](../../concepts/records) in RushDB. It provides a flexible way to filter, sort, paginate, and aggregate data. For more information on search concepts, see the [search documentation](../../concepts/search/introduction.md).
## Type Definition
```typescript
export type SearchQuery = SearchQueryLabelsClause &
PaginationClause &
OrderClause &
WhereClause &
AggregateClause
```
## Type Parameters
| Parameter | Description |
|--------------------------|-------------------------------------------------------------------------|
| `S extends Schema = any` | The schema type that defines the structure of the records being queried |
## Query Components
### Labels Clause
```typescript
export type SearchQueryLabelsClause = {
labels?: Array
}
```
Specifies the labels (types) of records to search for. If omitted, only records with the model's label will be searched.
### Pagination Clause
```typescript
export type PaginationClause = {
limit?: number
skip?: number
}
```
Controls pagination of the query results.
| Property | Type | Description |
|----------|----------|-------------------------------------|
| `limit` | `number` | Maximum number of records to return |
| `skip` | `number` | Number of records to skip |
### Order Clause
```typescript
export type OrderClause = {
orderBy?: Order
}
export type OrderDirection = 'asc' | 'desc'
export type Order = OrderDirection | Partial>
```
Specifies how to sort the query results.
### Where Clause
```typescript
export type WhereClause = {
where?: Where
}
```
Filters records based on property values and relationships.
### Aggregate Clause
```typescript
export type AggregateClause = {
aggregate?: Aggregate
}
```
Defines aggregation operations to perform on the query results.
## Where Expressions
The `where` property of a search query can include various expressions to filter records:
### Property Expressions
```typescript
export type PropertyExpression =
BooleanExpression |
DatetimeExpression |
NullExpression |
NumberExpression |
StringExpression |
TypeExpression |
VectorExpression
```
#### Number Expressions
```typescript
export type NumberExpression = number | {
$gt?: number
$gte?: number
$in?: Array
$lt?: number
$lte?: number
$ne?: number
$nin?: Array
$exists?: boolean
}
```
#### String Expressions
```typescript
export type StringExpression = string | {
$in?: Array
$ne?: string
$nin?: Array
$endsWith?: string
$startsWith?: string
$contains?: string
$exists?: boolean
}
```
#### Boolean Expressions
```typescript
export type BooleanExpression = boolean | {
$ne?: boolean
$exists?: boolean
}
```
#### Null Expressions
```typescript
export type NullExpression = null | {
$ne?: null
$exists?: boolean | null
}
$ne?: null
}
```
#### Type Expressions
```typescript
export type TypeExpression = {
$type: 'string' | 'number' | 'boolean' | 'datetime' | 'null' | 'vector'
}
```
The `$type` operator checks whether a field has a specific data type:
```typescript
// Find records where age is actually stored as a number
{
where: {
age: { $type: "number" }
}
}
// Find records with vector embeddings
{
where: {
embedding: { $type: "vector" }
}
}
```
### Logical Expressions
```typescript
export type LogicalExpression = RequireAtLeastOne<
AndExpression & OrExpression & NotExpression & XorExpression & NorExpression
>
export type AndExpression = {
$and: MaybeArray>
}
export type OrExpression = {
$or: MaybeArray>
}
export type NotExpression = {
$not: MaybeArray>
}
export type XorExpression = {
$xor: MaybeArray>
}
export type NorExpression = {
$nor: MaybeArray>
}
```
### Related Records
```typescript
export type Related = Models> =
keyof M extends never ? AnyObject
: {
[Key in keyof M]?: {
$alias?: string
$relation?: RelationOptions | string
} & Where
}
```
Defines conditions on related records. Learn more about [relationships in RushDB](../../concepts/relationships).
### Aggregation
```typescript
export type AggregateFn =
| { alias: string; field: string; fn: 'avg'; precision?: number }
| { alias: string; field: string; fn: 'max' }
| { alias: string; field: string; fn: 'min' }
| { alias: string; field: string; fn: 'sum' }
| { alias: string; field?: string; fn: 'count'; uniq?: boolean }
| { field: string; fn: `gds.similarity.${VectorSearchFn}`; alias: string; vector: number }
| AggregateCollectFn
```
Defines aggregation functions to apply to the query results.
## Usage Examples
### Basic Query
```typescript
// Find all users aged 30 or above
const users = await UserModel.find({
where: {
age: { $gte: 30 }
}
});
```
### Complex Filtering
```typescript
// Find active users with specific email domains
const users = await UserModel.find({
where: {
$and: [
{ active: true },
{
$or: [
{ email: { $endsWith: '@gmail.com' } },
{ email: { $endsWith: '@outlook.com' } }
]
}
]
}
});
```
### Pagination and Sorting
```typescript
// Get the second page of users sorted by name
const users = await UserModel.find({
orderBy: { name: 'asc' },
skip: 10,
limit: 10
});
```
### Filtering by Related Records
```typescript
// Find users who authored a post with a specific title
const users = await UserModel.find({
where: {
Post: {
$relation: { type: 'AUTHORED' },
title: 'My First Post'
}
}
});
```
### Aggregation
```typescript
// Calculate average age of users per country
const results = await UserModel.find({
aggregate: {
averageAge: { fn: 'avg', field: 'age', alias: 'averageAge' },
countries: {
fn: 'collect',
field: 'country',
alias: 'countries',
uniq: true
}
}
});
```
### Vector Search
```typescript
// Find records with similar vector embeddings
const similar = await EmbeddingModel.find({
where: {
embedding: {
$similarity: {
vector: [0.1, 0.2, 0.3, ...],
limit: 10
}
}
}
});
```
---
# Transaction
The `Transaction` class represents an active database transaction in RushDB. It provides methods for committing or rolling back the transaction and includes the unique transaction identifier.
## Overview
In RushDB, a transaction allows you to execute multiple database operations atomically, ensuring that either all operations succeed or all operations are rolled back if an error occurs. The `Transaction` class encapsulates the logic for managing these database transactions.
## Properties
| Property | Type | Description |
|----------|--------|------------------------------------------------------------------------------------------------------------------------------|
| `id` | string | The unique identifier for this transaction. This ID is used internally when making API calls within the transaction context. |
## Methods
### `commit()`
Commits all operations performed within this transaction, making the changes permanent in the database.
```typescript
async commit(): Promise>
```
#### Returns
- `Promise>`: A promise resolving to the API response with a success message.
#### Example
```typescript
const transaction = await db.tx.begin();
// Perform database operations...
const result = await transaction.commit();
console.log(result.data.message); // "Transaction (id) has been successfully committed."
```
### `rollback()`
Rolls back all operations performed within this transaction, discarding all changes.
```typescript
async rollback(): Promise>
```
#### Returns
- `Promise>`: A promise resolving to the API response with a rollback confirmation message.
#### Example
```typescript
const transaction = await db.tx.begin();
// Perform database operations...
try {
// Some operation failed
const result = await transaction.rollback();
console.log(result.data.message); // "Transaction (id) has been rolled back."
} catch (error) {
console.error("Error rolling back transaction:", error);
}
```
## Usage with SDK Methods
Many SDK methods accept a `Transaction` instance as their last parameter, allowing you to include the operation within the transaction:
```typescript
// Start a transaction
const transaction = await db.tx.begin({ ttl: 10000 });
try {
// Create a record within the transaction
const person = await db.records.create({
label: "Person",
data: { name: "Jane Smith", age: 28 }
}, transaction);
// Update a record within the same transaction
await db.records.update({
target: person,
label: "Person",
data: { age: 29 }
}, transaction);
// Commit the transaction to make changes permanent
await transaction.commit();
} catch (error) {
// If any operation fails, roll back the entire transaction
await transaction.rollback();
throw error;
}
```
## See Also
- [Transactions API Documentation](../transactions)
- [RushDB Class Reference](../typescript-reference/RushDB)