# 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
![RushDB Logo](https://raw.githubusercontent.com/rush-db/rushdb/main/rushdb-logo.svg) [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. ![Create Project Button](../../static/img/quick-start/create-project-screen.png "Highlighting the 'Create Project' Button") ## 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. ![Copy API Key](../../static/img/quick-start/create-token-screen.png "Copy API Key") - 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)