From 33c92c93a4591919fcdc80957ed381bb8ad7d09b Mon Sep 17 00:00:00 2001 From: Atharva Deosthale Date: Wed, 11 Mar 2026 01:30:32 +0530 Subject: [PATCH] feat tablesdb + documentsdb --- .../docs/products/databases/+layout.svelte | 156 ++- .../docs/products/databases/+page.markdoc | 22 +- .../databases/ai-suggestions/+page.markdoc | 20 +- .../atomic-numeric-operations/+page.ts | 6 + .../databases/bulk-operations/+page.ts | 6 + .../databases/csv-exports/+page.markdoc | 4 + .../databases/csv-imports/+page.markdoc | 4 + .../databases/databases/+page.markdoc | 7 +- .../atomic-numeric-operations/+page.markdoc | 763 +++++++++++ .../documentsdb/bulk-operations/+page.markdoc | 446 +++++++ .../documentsdb/collections/+page.markdoc | 899 +++++++++++++ .../documentsdb/documents/+page.markdoc | 991 ++++++++++++++ .../documentsdb/quick-start/+page.markdoc | 375 ++++++ .../documentsdb/relationships/+page.markdoc | 1083 ++++++++++++++++ .../timestamp-overrides/+page.markdoc | 215 ++- .../documentsdb/transactions/+page.markdoc | 1149 +++++++++++++++++ .../documentsdb/type-generation/+page.markdoc | 570 ++++++++ .../databases/geo-queries/+page.markdoc | 4 + .../databases/operators/+page.markdoc | 4 + .../products/databases/order/+page.markdoc | 4 + .../databases/pagination/+page.markdoc | 6 +- .../databases/permissions/+page.markdoc | 28 +- .../products/databases/queries/+page.markdoc | 10 +- .../products/databases/quick-start/+page.ts | 6 + .../products/databases/relationships/+page.ts | 6 + .../products/databases/rows/+page.markdoc | 32 +- .../products/databases/tables/+page.markdoc | 3 +- .../atomic-numeric-operations/+page.markdoc | 52 +- .../bulk-operations/+page.markdoc | 42 +- .../{ => tablesdb}/quick-start/+page.markdoc | 2 +- .../relationships/+page.markdoc | 76 +- .../timestamp-overrides/+page.markdoc | 1038 +++++++++++++++ .../{ => tablesdb}/transactions/+page.markdoc | 0 .../type-generation/+page.markdoc | 0 .../databases/timestamp-overrides/+page.ts | 6 + .../products/databases/transactions/+page.ts | 6 + .../databases/type-generation/+page.ts | 6 + 37 files changed, 7735 insertions(+), 312 deletions(-) create mode 100644 src/routes/docs/products/databases/atomic-numeric-operations/+page.ts create mode 100644 src/routes/docs/products/databases/bulk-operations/+page.ts create mode 100644 src/routes/docs/products/databases/documentsdb/atomic-numeric-operations/+page.markdoc create mode 100644 src/routes/docs/products/databases/documentsdb/bulk-operations/+page.markdoc create mode 100644 src/routes/docs/products/databases/documentsdb/collections/+page.markdoc create mode 100644 src/routes/docs/products/databases/documentsdb/documents/+page.markdoc create mode 100644 src/routes/docs/products/databases/documentsdb/quick-start/+page.markdoc create mode 100644 src/routes/docs/products/databases/documentsdb/relationships/+page.markdoc rename src/routes/docs/products/databases/{ => documentsdb}/timestamp-overrides/+page.markdoc (87%) create mode 100644 src/routes/docs/products/databases/documentsdb/transactions/+page.markdoc create mode 100644 src/routes/docs/products/databases/documentsdb/type-generation/+page.markdoc create mode 100644 src/routes/docs/products/databases/quick-start/+page.ts create mode 100644 src/routes/docs/products/databases/relationships/+page.ts rename src/routes/docs/products/databases/{ => tablesdb}/atomic-numeric-operations/+page.markdoc (96%) rename src/routes/docs/products/databases/{ => tablesdb}/bulk-operations/+page.markdoc (95%) rename src/routes/docs/products/databases/{ => tablesdb}/quick-start/+page.markdoc (97%) rename src/routes/docs/products/databases/{ => tablesdb}/relationships/+page.markdoc (96%) create mode 100644 src/routes/docs/products/databases/tablesdb/timestamp-overrides/+page.markdoc rename src/routes/docs/products/databases/{ => tablesdb}/transactions/+page.markdoc (100%) rename src/routes/docs/products/databases/{ => tablesdb}/type-generation/+page.markdoc (100%) create mode 100644 src/routes/docs/products/databases/timestamp-overrides/+page.ts create mode 100644 src/routes/docs/products/databases/transactions/+page.ts create mode 100644 src/routes/docs/products/databases/type-generation/+page.ts diff --git a/src/routes/docs/products/databases/+layout.svelte b/src/routes/docs/products/databases/+layout.svelte index 87f1ffb5a3..8708aa839e 100644 --- a/src/routes/docs/products/databases/+layout.svelte +++ b/src/routes/docs/products/databases/+layout.svelte @@ -1,11 +1,8 @@ diff --git a/src/routes/docs/products/databases/+page.markdoc b/src/routes/docs/products/databases/+page.markdoc index 9217bde1e6..094c363eb5 100644 --- a/src/routes/docs/products/databases/+page.markdoc +++ b/src/routes/docs/products/databases/+page.markdoc @@ -4,16 +4,30 @@ title: Databases description: Store and query structured data with Appwrite Databases. Databases provide performant and scalable storage for your application, business, and user data. --- -Appwrite Databases let you store and query structured data. +Appwrite Databases let you store and query structured data. Databases provide high-performance and scalable data storage for your key application, business, and user data. {% info title="Looking for file storage?" %} Databases store data, if you need to store files like images, PDFs or videos, use [Appwrite Storage](/docs/products/storage). {% /info %} -You can organize data into databases, tables, and rows. You can also paginate, order, and query rows. +Appwrite supports two database backends, each optimized for different workloads: + +| | Tables DB | Documents DB | +|---|---|---| +| **Backed by** | MariaDB (SQL) | MongoDB (NoSQL) | +| **Terminology** | Tables, rows, columns | Collections, documents, attributes | +| **Best for** | Relational data, geo queries, CSV workflows | Flexible schemas, large attribute counts | +| **Spatial/Geo** | Full support | Not available | +| **CSV import/export** | Supported | Not available | + +You can organize data into databases, tables (or collections), and rows (or documents). You can also paginate, order, and query your data. For complex business logic, Appwrite supports relationships to help you model your data. -{% arrow_link href="/docs/products/databases/quick-start" %} -Quick start +{% arrow_link href="/docs/products/databases/tablesdb/quick-start" %} +Get started with Tables DB +{% /arrow_link %} + +{% arrow_link href="/docs/products/databases/documentsdb/quick-start" %} +Get started with Documents DB {% /arrow_link %} \ No newline at end of file diff --git a/src/routes/docs/products/databases/ai-suggestions/+page.markdoc b/src/routes/docs/products/databases/ai-suggestions/+page.markdoc index b6b653ad4a..6f5cb2c7a2 100644 --- a/src/routes/docs/products/databases/ai-suggestions/+page.markdoc +++ b/src/routes/docs/products/databases/ai-suggestions/+page.markdoc @@ -1,19 +1,19 @@ --- layout: article title: AI suggestions -description: Use AI suggestions to automatically generate database schemas. Learn how to create tables with recommended columns and indexes based on your table name and context. +description: Use AI suggestions to automatically generate database schemas. Learn how to create tables or collections with recommended columns/attributes and indexes based on your name and context. --- -AI suggestions generate columns and indexes for your tables based on the table name, existing database structure, and optional context you provide. +AI suggestions generate columns (Tables DB) or attributes (Documents DB) and indexes for your tables or collections based on the name, existing database structure, and optional context you provide. This feature analyzes your database to recommend appropriate schema designs that follow best practices. -{% section #create-table step=1 title="Create table" %} -Navigate to **Databases** in the Appwrite Console, select your database, and click **Create table**. +{% section #create-table step=1 title="Create table or collection" %} +Navigate to **Databases** in the Appwrite Console, select your database, and click **Create table** (Tables DB) or **Create collection** (Documents DB). -Enter a descriptive table name. AI suggestions will use this name to generate relevant columns and indexes. +Enter a descriptive name. AI suggestions will use this name to generate relevant columns/attributes and indexes. {% /section %} {% section #enable-suggestions step=2 title="Enable AI suggestions" %} -In the table creation dialog, enable **AI suggestions**. +In the table or collection creation dialog, enable **AI suggestions**. {% only_dark %} ![Enable AI suggestions](/images/docs/databases/dark/ai-suggestions-enable.png) @@ -22,13 +22,13 @@ In the table creation dialog, enable **AI suggestions**. ![Enable AI suggestions](/images/docs/databases/ai-suggestions-enable.png) {% /only_light %} -Optionally, provide additional context about your use case to refine the suggestions. For example, if creating an `Orders` table, you might add context like "e-commerce orders with payment tracking." +Optionally, provide additional context about your use case to refine the suggestions. For example, if creating an `Orders` table or collection, you might add context like "e-commerce orders with payment tracking." -Click **Generate suggestions** to analyze your table name and database structure. +Click **Generate suggestions** to analyze your table or collection name and database structure. {% /section %} {% section #review-suggestions step=3 title="Review suggestions" %} -AI suggestions will generate recommended columns and indexes for your table. +AI suggestions will generate recommended columns or attributes and indexes for your table or collection. {% only_dark %} ![Review AI suggestions](/images/docs/databases/dark/ai-suggestions-review.png) @@ -52,7 +52,7 @@ You can modify any suggestion before applying: - Remove suggestions you don't need - Add additional columns manually -Click **Apply** to apply your schema. The table will be created with your approved columns and indexes. +Click **Apply** to apply your schema. The table or collection will be created with your approved columns or attributes and indexes. {% info title="Manual columns" %} You can always add, modify, or remove [columns](/docs/products/databases/tables#columns) after table creation by navigating to your table's **Columns** tab. diff --git a/src/routes/docs/products/databases/atomic-numeric-operations/+page.ts b/src/routes/docs/products/databases/atomic-numeric-operations/+page.ts new file mode 100644 index 0000000000..25a1650eb0 --- /dev/null +++ b/src/routes/docs/products/databases/atomic-numeric-operations/+page.ts @@ -0,0 +1,6 @@ +import type { PageLoad } from './$types'; +import { redirect } from '@sveltejs/kit'; + +export const load: PageLoad = async () => { + redirect(303, '/docs/products/databases/tablesdb/atomic-numeric-operations'); +}; diff --git a/src/routes/docs/products/databases/bulk-operations/+page.ts b/src/routes/docs/products/databases/bulk-operations/+page.ts new file mode 100644 index 0000000000..4ed5c2eda8 --- /dev/null +++ b/src/routes/docs/products/databases/bulk-operations/+page.ts @@ -0,0 +1,6 @@ +import type { PageLoad } from './$types'; +import { redirect } from '@sveltejs/kit'; + +export const load: PageLoad = async () => { + redirect(303, '/docs/products/databases/tablesdb/bulk-operations'); +}; diff --git a/src/routes/docs/products/databases/csv-exports/+page.markdoc b/src/routes/docs/products/databases/csv-exports/+page.markdoc index ea524f4cc7..479cd54928 100644 --- a/src/routes/docs/products/databases/csv-exports/+page.markdoc +++ b/src/routes/docs/products/databases/csv-exports/+page.markdoc @@ -4,6 +4,10 @@ title: CSV exports description: Export table data to CSV files from the Console. Share clean datasets with your team without writing custom scripts. --- +{% info title="Tables DB only" %} +CSV exports are only available with **Tables DB** (MariaDB). Documents DB (MongoDB) does not support CSV exports. +{% /info %} + Appwrite's CSV Export feature allows you to export rows from a table to a CSV file. This is especially useful for reporting, sharing data with non-technical team members, creating custom backups, or handing off datasets to analytics tools. This feature is available in both Appwrite Cloud and the self-hosted version. diff --git a/src/routes/docs/products/databases/csv-imports/+page.markdoc b/src/routes/docs/products/databases/csv-imports/+page.markdoc index 341c96eeef..9081588bf9 100644 --- a/src/routes/docs/products/databases/csv-imports/+page.markdoc +++ b/src/routes/docs/products/databases/csv-imports/+page.markdoc @@ -4,6 +4,10 @@ title: CSV imports description: Master row imports with Appwrite's CSV Import feature. Learn how to create rows within your tables by uploading a CSV file. --- +{% info title="Tables DB only" %} +CSV imports are only available with **Tables DB** (MariaDB). Documents DB (MongoDB) does not support CSV imports. +{% /info %} + Appwrite's CSV Import feature allows you to create multiple rows in a table by uploading a single CSV file. This is especially useful for importing existing data, seeding test environments, or migrating from other systems. This feature is available in both Appwrite Cloud and the self-hosted version. diff --git a/src/routes/docs/products/databases/databases/+page.markdoc b/src/routes/docs/products/databases/databases/+page.markdoc index ef98f07201..f8c35175f3 100644 --- a/src/routes/docs/products/databases/databases/+page.markdoc +++ b/src/routes/docs/products/databases/databases/+page.markdoc @@ -4,8 +4,11 @@ title: Databases description: Dive deeper into Appwrite Databases and their configuration. Learn how to create, manage, and optimize multiple databases for your application. --- Databases are the largest organizational unit in Appwrite. -Each database contains a group of [tables](/docs/products/databases/tables). -In future versions, different databases may be backed by a different database technology of your choosing. +Each database contains a group of [tables](/docs/products/databases/tables) (Tables DB) or [collections](/docs/products/databases/documentsdb/collections) (Documents DB). + +{% info title="Terminology mapping" %} +Code examples on this page use Tables DB (`TablesDB`). For Documents DB, use `DocumentsDB` instead of `TablesDB`. +{% /info %} # Create in Console {% #create-in-console %} The easiest way to create a database using the Appwrite Console. diff --git a/src/routes/docs/products/databases/documentsdb/atomic-numeric-operations/+page.markdoc b/src/routes/docs/products/databases/documentsdb/atomic-numeric-operations/+page.markdoc new file mode 100644 index 0000000000..8a9abbe526 --- /dev/null +++ b/src/routes/docs/products/databases/documentsdb/atomic-numeric-operations/+page.markdoc @@ -0,0 +1,763 @@ +--- +layout: article +title: Atomic numeric operations +description: Safely increment and decrement numeric fields without race conditions. Perfect for counters, quotas, inventory, and usage metrics in high-concurrency applications. +--- + +Atomic numeric operations allow you to safely increase or decrease numeric fields without fetching the full document. This eliminates race conditions and reduces bandwidth usage when updating any numeric values that need to be modified atomically, such as counters, scores, balances, and other fast-moving numeric data. + +# How atomic operations work {% #how-atomic-operations-work %} + +Instead of the traditional read-modify-write pattern, atomic numeric operations use dedicated methods to modify values directly on the server. The server applies the change atomically under concurrency control and returns the new value. + +**Traditional approach:** +1. Fetch document → `{ likes: 42 }` +2. Update client-side → `likes: 43` +3. Write back → `{ likes: 43 }` + +**Atomic approach:** +1. Call `incrementDocumentAttribute()` with the attribute name and the value to increment by +2. Server applies atomically → `likes: 43` + +# When to use atomic operations {% #when-to-use-atomic-operations %} + +Atomic numeric operations work well for: + +- **Social features**: Likes, follows, comment counts +- **Usage metering**: API credits, storage quotas, request limits +- **Game state**: Scores, lives, currency, experience points +- **E-commerce**: Stock counts, inventory levels +- **Workflow tracking**: Retry counts, progress indicators +- **Rate limiting**: Request counters, usage tracking + +# Perform atomic operations {% #perform-atomic-operations %} + +Use the `incrementDocumentAttribute` and `decrementDocumentAttribute` methods to perform atomic numeric operations. The server will apply these changes atomically under concurrency control. + +## Increment a field {% #increment-field %} + +{% multicode %} +```client-web +import { Client, DocumentsDB } from "appwrite"; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + .setProject(''); // Your project ID + +const documentsDB = new DocumentsDB(client); + +const result = await documentsDB.incrementDocumentAttribute({ + databaseId: '', + collectionId: '', + documentId: '', + attribute: 'likes', // attribute + value: 1 // value +}); +``` +```client-flutter +import 'package:appwrite/appwrite.dart'; + +final client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + +final documentsDB = DocumentsDB(client); + +final document = await documentsDB.incrementDocumentAttribute( + databaseId: '', + collectionId: '', + documentId: '', + attribute: 'likes', + value: 1 +); +``` +```client-apple +import Appwrite +import AppwriteModels + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + +let documentsDB = DocumentsDB(client) + +let document = try await documentsDB.incrementDocumentAttribute( + databaseId: "", + collectionId: "", + documentId: "", + attribute: "likes", + value: 1 +) +``` +```client-android-kotlin +import io.appwrite.Client +import io.appwrite.services.DocumentsDB + +val client = Client(applicationContext) + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + +val documentsDB = DocumentsDB(client) + +val document = documentsDB.incrementDocumentAttribute( + databaseId = "", + collectionId = "", + documentId = "", + attribute = "likes", + value = 1 +) +``` +```server-nodejs +const sdk = require('node-appwrite'); + +const client = new sdk.Client() + .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('') // Your project ID + .setKey(''); // Your secret API key + +const documentsDB = new sdk.DocumentsDB(client); + +const result = await documentsDB.incrementDocumentAttribute({ + databaseId: '', + collectionId: '', + documentId: '', + attribute: 'likes', // attribute + value: 1 // value +}); +``` +```server-python +from appwrite.client import Client +from appwrite.services.documents_db import DocumentsDB + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint +client.set_project('') # Your project ID +client.set_key('') # Your secret API key + +documentsDB = DocumentsDB(client) + +result = documentsDB.increment_document_attribute( + database_id = '', + collection_id = '', + document_id = '', + attribute = 'likes', # attribute + value = 1 # value +) +``` +```graphql +mutation { + databasesIncrementDocumentAttribute( + databaseId: "", + collectionId: "", + documentId: "", + attribute: "likes", + value: 1 + ) { + _id + _collectionId + _databaseId + _createdAt + _updatedAt + _permissions + data + } +} +``` +{% /multicode %} + +## Decrement a field {% #decrement-field %} + +Use the `decrementDocumentAttribute` method to decrease numeric fields: + +{% multicode %} +```client-web +import { Client, DocumentsDB } from "appwrite"; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + .setProject(''); // Your project ID + +const documentsDB = new DocumentsDB(client); + +const result = await documentsDB.decrementDocumentAttribute({ + databaseId: '', + collectionId: '', + documentId: '', + attribute: 'credits', // attribute + value: 5 // value +}); +``` +```client-flutter +import 'package:appwrite/appwrite.dart'; + +final client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + +final documentsDB = DocumentsDB(client); + +final document = await documentsDB.decrementDocumentAttribute( + databaseId: '', + collectionId: '', + documentId: '', + attribute: 'credits', + value: 5 +); +``` +```client-apple +import Appwrite +import AppwriteModels + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + +let documentsDB = DocumentsDB(client) + +let document = try await documentsDB.decrementDocumentAttribute( + databaseId: "", + collectionId: "", + documentId: "", + attribute: "credits", + value: 5 +) +``` +```client-android-kotlin +import io.appwrite.Client +import io.appwrite.services.DocumentsDB + +val client = Client(applicationContext) + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + +val documentsDB = DocumentsDB(client) + +val document = documentsDB.decrementDocumentAttribute( + databaseId = "", + collectionId = "", + documentId = "", + attribute = "credits", + value = 5 +) +``` +```server-nodejs +const sdk = require('node-appwrite'); + +const client = new sdk.Client() + .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('') // Your project ID + .setKey(''); // Your secret API key + +const documentsDB = new sdk.DocumentsDB(client); + +const result = await documentsDB.decrementDocumentAttribute({ + databaseId: '', + collectionId: '', + documentId: '', + attribute: 'credits', // attribute + value: 5 // value +}); +``` +```server-python +from appwrite.client import Client +from appwrite.services.documents_db import DocumentsDB + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint +client.set_project('') # Your project ID +client.set_key('') # Your secret API key + +documentsDB = DocumentsDB(client) + +result = documentsDB.decrement_document_attribute( + database_id = '', + collection_id = '', + document_id = '', + attribute = 'credits', # attribute + value = 5 # value +) +``` +```graphql +mutation { + databasesDecrementDocumentAttribute( + databaseId: "", + collectionId: "", + documentId: "", + attribute: "credits", + value: 5 + ) { + _id + _collectionId + _databaseId + _createdAt + _updatedAt + _permissions + data + } +} +``` +{% /multicode %} + +# Set constraints and bounds {% #set-constraints-and-bounds %} + +You can set minimum and maximum bounds for individual operations to prevent invalid values. Use the optional `min` and `max` parameters to ensure the final value stays within acceptable limits: + +## Example with constraints {% #example-with-constraints %} + +{% multicode %} +```client-web +// Increment with maximum constraint +const result = await documentsDB.incrementDocumentAttribute({ + databaseId: '', + collectionId: '', + documentId: '', + attribute: 'credits', // attribute + value: 100, // value + max: 1000 // max (optional) +}); + +// Decrement with minimum constraint +const result2 = await documentsDB.decrementDocumentAttribute({ + databaseId: '', + collectionId: '', + documentId: '', + attribute: 'credits', // attribute + value: 50, // value + min: 0 // min (optional) +}); +``` +```client-flutter +// Increment with maximum constraint +final document = await documentsDB.incrementDocumentAttribute( + databaseId: '', + collectionId: '', + documentId: '', + attribute: 'credits', + value: 100, + max: 1000 +); + +// Decrement with minimum constraint +final document2 = await documentsDB.decrementDocumentAttribute( + databaseId: '', + collectionId: '', + documentId: '', + attribute: 'credits', + value: 50, + min: 0 +); +``` +```client-apple +// Increment with maximum constraint +let document = try await documentsDB.incrementDocumentAttribute( + databaseId: "", + collectionId: "", + documentId: "", + attribute: "credits", + value: 100, + max: 1000 +) + +// Decrement with minimum constraint +let document2 = try await documentsDB.decrementDocumentAttribute( + databaseId: "", + collectionId: "", + documentId: "", + attribute: "credits", + value: 50, + min: 0 +) +``` +```client-android-kotlin +// Increment with maximum constraint +val document = documentsDB.incrementDocumentAttribute( + databaseId = "", + collectionId = "", + documentId = "", + attribute = "credits", + value = 100, + max = 1000 +) + +// Decrement with minimum constraint +val document2 = documentsDB.decrementDocumentAttribute( + databaseId = "", + collectionId = "", + documentId = "", + attribute = "credits", + value = 50, + min = 0 +) +``` +```server-nodejs +// Increment with maximum constraint +const result = await documentsDB.incrementDocumentAttribute({ + databaseId: '', + collectionId: '', + documentId: '', + attribute: 'credits', // attribute + value: 100, // value + max: 1000 // max (optional) +}); + +// Decrement with minimum constraint +const result2 = await documentsDB.decrementDocumentAttribute({ + databaseId: '', + collectionId: '', + documentId: '', + attribute: 'credits', // attribute + value: 50, // value + min: 0 // min (optional) +}); +``` +```server-python +# Increment with maximum constraint +result = documentsDB.increment_document_attribute( + database_id = '', + collection_id = '', + document_id = '', + attribute = 'credits', # attribute + value = 100, # value + max = 1000 # max (optional) +) + +# Decrement with minimum constraint +result2 = documentsDB.decrement_document_attribute( + database_id = '', + collection_id = '', + document_id = '', + attribute = 'credits', # attribute + value = 50, # value + min = 0 # min (optional) +) +``` +{% /multicode %} + +# Follow best practices {% #follow-best-practices %} + +## Use for high-concurrency scenarios {% #use-for-high-concurrency-scenarios %} + +Atomic numeric operations are most beneficial when multiple users or processes might update the same numeric field simultaneously. + +## Combine with regular updates {% #combine-with-regular-updates %} + +For complex updates that include both atomic operations and regular field changes, you'll need to use separate API calls: + +{% multicode %} +```client-web +// First, increment the likes atomically +const likeResult = await documentsDB.incrementDocumentAttribute( + '', + '', + '', + 'likes', // attribute + 1 // value +); + +// Then, update other fields +const updateResult = await documentsDB.updateDocument( + '', + '', + '', + { + lastLikedBy: userId, + lastLikedAt: new Date().toISOString() + } +); +``` +```client-flutter +// First, increment the likes atomically +final likeResult = await documentsDB.incrementDocumentAttribute( + databaseId: '', + collectionId: '', + documentId: '', + attribute: 'likes', + value: 1 +); + +// Then, update other fields +final updateResult = await documentsDB.updateDocument( + databaseId: '', + collectionId: '', + documentId: '', + data: { + 'lastLikedBy': userId, + 'lastLikedAt': DateTime.now().toIso8601String() + } +); +``` +```client-apple +// First, increment the likes atomically +let likeResult = try await documentsDB.incrementDocumentAttribute( + databaseId: "", + collectionId: "", + documentId: "", + attribute: "likes", + value: 1 +) + +// Then, update other fields +let updateResult = try await documentsDB.updateDocument( + databaseId: "", + collectionId: "", + documentId: "", + data: [ + "lastLikedBy": userId, + "lastLikedAt": ISO8601DateFormatter().string(from: Date()) + ] +) +``` +```client-android-kotlin +// First, increment the likes atomically +val likeResult = documentsDB.incrementDocumentAttribute( + databaseId = "", + collectionId = "", + documentId = "", + attribute = "likes", + value = 1 +) + +// Then, update other fields +val updateResult = documentsDB.updateDocument( + databaseId = "", + collectionId = "", + documentId = "", + data = mapOf( + "lastLikedBy" to userId, + "lastLikedAt" to Instant.now().toString() + ) +) +``` +```server-nodejs +// First, increment the likes atomically +const likeResult = await documentsDB.incrementDocumentAttribute( + '', + '', + '', + 'likes', // attribute + 1 // value +); + +// Then, update other fields +const updateResult = await documentsDB.updateDocument( + '', + '', + '', + { + lastLikedBy: userId, + lastLikedAt: new Date().toISOString() + } +); +``` +```server-python +# First, increment the likes atomically +like_result = documentsDB.increment_document_attribute( + database_id = '', + collection_id = '', + document_id = '', + attribute = 'likes', # attribute + value = 1 # value +) + +# Then, update other fields +update_result = documentsDB.update_document( + database_id = '', + collection_id = '', + document_id = '', + data = { + 'lastLikedBy': user_id, + 'lastLikedAt': datetime.now().isoformat() + } +) +``` +{% /multicode %} + +# Use transactions {% #use-transactions %} + +Atomic numeric operations accept `transactionId`. When provided, increments/decrements are staged and applied on commit. + +{% multicode %} +```client-web +await documentsDB.incrementDocumentAttribute({ + databaseId: '', + collectionId: '', + documentId: '', + attribute: 'likes', + value: 1, + transactionId: '' +}); +``` +```client-flutter +await documentsDB.incrementDocumentAttribute( + databaseId: '', + collectionId: '', + documentId: '', + attribute: 'likes', + value: 1, + transactionId: '' +); +``` +```client-apple +try await documentsDB.incrementDocumentAttribute( + databaseId: "", + collectionId: "", + documentId: "", + attribute: "likes", + value: 1, + transactionId: "" +) +``` +```client-android-kotlin +documentsDB.incrementDocumentAttribute( + databaseId = "", + collectionId = "", + documentId = "", + attribute = "likes", + value = 1, + transactionId = "" +) +``` +```client-android-java +documentsDB.incrementDocumentAttribute( + "", + "", + "", + "likes", + 1, + "", + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return null; + } + System.out.println(result); + return null; + }) +); +``` +```client-react-native +await documentsDB.incrementDocumentAttribute({ + databaseId: '', + collectionId: '', + documentId: '', + attribute: 'likes', + value: 1, + transactionId: '' +}); +``` +```server-nodejs +await documentsDB.incrementDocumentAttribute({ + databaseId: '', + collectionId: '', + documentId: '', + attribute: 'likes', + value: 1, + transactionId: '' +}); +``` +```server-deno +await documentsDB.incrementDocumentAttribute({ + databaseId: '', + collectionId: '', + documentId: '', + attribute: 'likes', + value: 1, + transactionId: '' +}); +``` +```server-python +documentsDB.increment_document_attribute( + database_id = '', + collection_id = '', + document_id = '', + attribute = 'likes', + value = 1, + transaction_id = '' +) +``` +```server-php +$documentsDB->incrementDocumentAttribute( + databaseId: '', + collectionId: '', + documentId: '', + attribute: 'likes', + value: 1, + transactionId: '' +); +``` +```server-ruby +documentsDB.increment_document_attribute( + database_id: '', + collection_id: '', + document_id: '', + attribute: 'likes', + value: 1, + transaction_id: '' +) +``` +```server-dotnet +await documentsDB.IncrementDocumentAttribute( + databaseId: "", + collectionId: "", + documentId: "", + attribute: "likes", + value: 1, + transactionId: "" +); +``` +```server-dart +await documentsDB.incrementDocumentAttribute( + databaseId: '', + collectionId: '', + documentId: '', + attribute: 'likes', + value: 1, + transactionId: '' +); +``` +```server-swift +try await documentsDB.incrementDocumentAttribute( + databaseId: "", + collectionId: "", + documentId: "", + attribute: "likes", + value: 1, + transactionId: "" +) +``` +```server-kotlin +documentsDB.incrementDocumentAttribute( + databaseId = "", + collectionId = "", + documentId = "", + attribute = "likes", + value = 1, + transactionId = "" +) +``` +```server-java +documentsDB.incrementDocumentAttribute( + "", + "", + "", + "likes", + 1, + "", + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return null; + } + System.out.println(result); + return null; + }) +); +``` +{% /multicode %} + +## Explore related features + +- [Bulk operations](/docs/products/databases/bulk-operations) - Update multiple documents at once +- [Permissions](/docs/products/databases/permissions) - Control access to documents +- [Queries](/docs/products/databases/queries) - Find documents to update +- [Relationships](/docs/products/databases/relationships) - Update related documents diff --git a/src/routes/docs/products/databases/documentsdb/bulk-operations/+page.markdoc b/src/routes/docs/products/databases/documentsdb/bulk-operations/+page.markdoc new file mode 100644 index 0000000000..2b96f4166b --- /dev/null +++ b/src/routes/docs/products/databases/documentsdb/bulk-operations/+page.markdoc @@ -0,0 +1,446 @@ +--- +layout: article +title: Bulk operations +description: Perform bulk operations on documents within your collections for efficient data handling. +--- + +Appwrite Databases supports bulk operations for documents, allowing you to create, update, or delete multiple documents in a single request. This can significantly improve performance for apps as it allows you to reduce the number of API calls needed while working with large data sets. + +Bulk **update**, **upsert**, and **delete** operations can only be performed via the server-side SDKs. This restriction ensures that only trusted server environments can perform large-scale data modifications. Bulk **create** (`createDocuments`) can be used from both client and server SDKs. + +For client applications that need bulk update, upsert, or delete functionality, consider using [Appwrite Functions](/docs/products/functions) with proper rate limiting and validation. + +{% info title="Important notes" %} +- Bulk operations trigger Functions, Webhooks, or Realtime events for each document manipulated. Rather than a single event for the entire bulk operation, each document generates a separate event on the existing realtime channels for its operation type. +- Collections that contain relationship attributes are not supported via bulk operations. Use individual document operations for collections with relationships. +{% /info %} + +# Atomic behavior {% #atomic-behavior %} + +Bulk operations in Appwrite are **atomic**, meaning they follow an all-or-nothing approach. Either all documents in your bulk request succeed, or all documents fail. + +This atomicity ensures: +- **Data consistency**: Your database remains in a consistent state even if some operations would fail. +- **Race condition prevention**: Multiple clients can safely perform bulk operations simultaneously. +- **Simplified error handling**: You only need to handle complete success or complete failure scenarios. + +For example, if you attempt to create 100 documents and one fails due to a validation error, none of the 100 documents will be created. + +# Plan limits {% #plan-limits %} + +Bulk operations have different limits based on your Appwrite plan: + +| Plan | Documents per request | +|------|----------------------| +| Free | 100 | +| Pro | 1,000 | + +These limits apply to all bulk operations including create, update, upsert, and delete operations. If you need higher limits than what the Pro plan offers, you can [inquire](/contact-us/enterprise) about a custom plan. + +# Create documents {% #create-documents %} + +You can create multiple documents in a single request using the `createDocuments` method. + +{% info title="Custom timestamps" %} +When creating, updating or upserting in bulk, you can set `$createdAt` and `$updatedAt` for each document in the payload. Values must be ISO 8601 date-time strings. If omitted, Appwrite sets them automatically. +{% /info %} + +{% multicode %} +```client-web +import { Client, DocumentsDB, ID } from 'appwrite'; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + +const documentsDB = new DocumentsDB(client); + +const result = await documentsDB.createDocuments( + '', + '', + [ + { + $id: ID.unique(), + name: 'Document 1' + }, + { + $id: ID.unique(), + name: 'Document 2' + } + ] +); +``` + +```server-nodejs +const sdk = require('node-appwrite'); + +const client = new sdk.Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject('') + .setKey(''); + +const documentsDB = new sdk.DocumentsDB(client); + +const result = await documentsDB.createDocuments( + '', + '', + [ + { + $id: sdk.ID.unique(), + name: 'Document 1' + }, + { + $id: sdk.ID.unique(), + name: 'Document 2' + } + ] +); +``` + +```server-python +from appwrite.client import Client +from appwrite.services.documents_db import DocumentsDB + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') +client.set_project('') +client.set_key('') + +documentsDB = DocumentsDB(client) + +result = documentsDB.create_documents( + database_id = '', + collection_id = '', + documents = [ + { + '$id': appwrite.ID.unique(), + 'name': 'Document 1' + }, + { + '$id': appwrite.ID.unique(), + 'name': 'Document 2' + } + ] +) +``` +{% /multicode %} + +# Update documents {% #update-documents %} + +{% info title="Permissions required" %} +You must grant **update** permissions to users at the **collection level** before users can update documents. +[Learn more about permissions](/docs/products/databases/permissions) +{% /info %} + +You can update multiple documents in a single request using the `updateDocuments` method. + +{% multicode %} +```server-nodejs +const sdk = require('node-appwrite'); + +const client = new sdk.Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject('') + .setKey(''); + +const documentsDB = new sdk.DocumentsDB(client); + +const result = await documentsDB.updateDocuments( + '', + '', + { + status: 'published' + }, + [ + sdk.Query.equal('status', 'draft') + ] +); +``` + +```server-python +from appwrite.client import Client +from appwrite.services.documents_db import DocumentsDB + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') +client.set_project('') +client.set_key('') + +documentsDB = DocumentsDB(client) + +result = documentsDB.update_documents( + database_id = '', + collection_id = '', + data = { + 'status': 'published' + }, + queries = [ + Query.equal('status', 'draft') + ] +) +``` +{% /multicode %} + +# Upsert documents {% #upsert-documents %} + +{% info title="Permissions required" %} +You must grant **create** and **update** permissions to users at the **collection level** before users can create documents. +[Learn more about permissions](/docs/products/databases/permissions) +{% /info %} + +You can upsert multiple documents in a single request using the `upsertDocuments(` method. + +{% multicode %} +```server-nodejs +const sdk = require('node-appwrite'); + +const client = new sdk.Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject('') + .setKey(''); + +const documentsDB = new sdk.DocumentsDB(client); + +const result = await documentsDB.upsertDocuments( + '', + '', + [ + { + $id: sdk.ID.unique(), + name: 'New Document 1' + }, + { + $id: 'document-id-2', // Existing document ID + name: 'New Document 2' + } + ] +); +``` + +```server-python +from appwrite.client import Client +from appwrite.services.documents_db import DocumentsDB +from appwrite.id import ID + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') +client.set_project('') +client.set_key('') + +documentsDB = DocumentsDB(client) + +result = documentsDB.upsert_documents( + database_id = '', + collection_id = '', + documents = [ + { + '$id': appwrite.ID.unique(), + 'name': 'Document 1' + }, + { + '$id': 'document-id-2', # Existing document ID + 'name': 'New Document 2' + } + ] +) +``` +{% /multicode %} + +# Delete documents {% #delete-documents %} + +{% info title="Permissions required" %} +You must grant **delete** permissions to users at the **collection level** before users can delete documents. +[Learn more about permissions](/docs/products/databases/permissions) +{% /info %} + +You can delete multiple documents in a single request using the `deleteDocuments` method. + +{% multicode %} +```server-nodejs +const sdk = require('node-appwrite'); + +const client = new sdk.Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject('') + .setKey(''); + +const documentsDB = new sdk.DocumentsDB(client); + +const result = await documentsDB.deleteDocuments( + '', + '', + [ + sdk.Query.equal('status', 'archived') + ] +); +``` + +```server-python +from appwrite.client import Client +from appwrite.services.documents_db import DocumentsDB +from appwrite.query import Query + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') +client.set_project('') +client.set_key('') + +documentsDB = DocumentsDB(client) + +result = documentsDB.delete_documents( + database_id = '', + collection_id = '', + queries = [ + Query.equal('status', 'archived') + ] +) +``` +{% /multicode %} + +{% info title="Queries for deletion" %} + +When deleting documents, you must specify queries to filter which documents to delete. +If no queries are provided, all documents in the collection will be deleted. +[Learn more about queries](/docs/products/databases/queries). + +{% /info %} + +# Use transactions {% #use-transactions %} + +All bulk operations accept `transactionId`. When provided, Appwrite stages the bulk request and applies it on commit. See [Transactions](/docs/products/databases/transactions). + +{% multicode %} +```server-nodejs +await documentsDB.createDocuments({ + databaseId: '', + collectionId: '', + documents: [ + { $id: sdk.ID.unique(), name: 'One' }, + { $id: sdk.ID.unique(), name: 'Two' } + ], + transactionId: '' +}); +``` +```server-python +documentsDB.create_documents( + database_id = '', + collection_id = '', + documents = [ + { '$id': appwrite.ID.unique(), 'name': 'One' }, + { '$id': appwrite.ID.unique(), 'name': 'Two' } + ], + transaction_id = '' +) +``` +```server-deno +await documentsDB.createDocuments({ + databaseId: '', + collectionId: '', + documents: [ + { $id: sdk.ID.unique(), name: 'One' }, + { $id: sdk.ID.unique(), name: 'Two' } + ], + transactionId: '' +}); +``` +```server-php +$documentsDB->createDocuments( + databaseId: '', + collectionId: '', + documents: [ + [ '$id' => ID::unique(), 'name' => 'One' ], + [ '$id' => ID::unique(), 'name' => 'Two' ] + ], + transactionId: '' +); +``` +```server-ruby +documentsDB.create_documents( + database_id: '', + collection_id: '', + documents: [ + { '$id' => ID.unique(), 'name' => 'One' }, + { '$id' => ID.unique(), 'name' => 'Two' } + ], + transaction_id: '' +) +``` +```server-dotnet +await documentsDB.CreateDocuments( + databaseId: "", + collectionId: "", + documents: new List> + { + new Dictionary + { + ["$id"] = ID.Unique(), + ["name"] = "One" + }, + new Dictionary + { + ["$id"] = ID.Unique(), + ["name"] = "Two" + } + }, + transactionId: "" +); +``` +```server-dart +await documentsDB.createDocuments( + databaseId: '', + collectionId: '', + documents: [ + { '\$id': ID.unique(), 'name': 'One' }, + { '\$id': ID.unique(), 'name': 'Two' } + ], + transactionId: '' +); +``` +```server-swift +try await documentsDB.createDocuments( + databaseId: "", + collectionId: "", + documents: [ + ["$id": ID.unique(), "name": "One"], + ["$id": ID.unique(), "name": "Two"] + ], + transactionId: "" +) +``` +```server-kotlin +documentsDB.createDocuments( + databaseId = "", + collectionId = "", + documents = listOf( + mapOf("\$id" to ID.unique(), "name" to "One"), + mapOf("\$id" to ID.unique(), "name" to "Two") + ), + transactionId = "" +) +``` +```server-java +documentsDB.createDocuments( + "", + "", + Arrays.asList( + Map.of( + "$id", ID.unique(), + "name", "One" + ), + Map.of( + "$id", ID.unique(), + "name", "Two" + ) + ), + "", + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return null; + } + System.out.println(result); + return null; + }) +); +``` +{% /multicode %} diff --git a/src/routes/docs/products/databases/documentsdb/collections/+page.markdoc b/src/routes/docs/products/databases/documentsdb/collections/+page.markdoc new file mode 100644 index 0000000000..92d6b80adb --- /dev/null +++ b/src/routes/docs/products/databases/documentsdb/collections/+page.markdoc @@ -0,0 +1,899 @@ +--- +layout: article +title: Collections +description: Organize your data with Appwrite Documents DB collections. Explore how to create and configure collections to store and structure your data effectively. +--- +Appwrite Documents DB uses collections as containers of documents. Each collection contains many documents identical in structure. + +# Create collection {% #create-collection %} +You can create collections using the Appwrite Console, a [Server SDK](/docs/sdks#server), or using the [CLI](/docs/tooling/command-line/installation). +{% tabs %} + +{% tabsitem #console title="Console" %} +You can create a collection by heading to the **Databases** page, navigate to a [database](/docs/products/databases/databases), and click **Create collection**. + +{% /tabsitem %} + +{% tabsitem #server-sdk title="Server SDK" %} +You can also create collections programmatically using a [Server SDK](/docs/sdks#server). Appwrite [Server SDKs](/docs/sdks#server) require an [API key](/docs/advanced/platform/api-keys). + +{% multicode %} + +```server-nodejs +const sdk = require('node-appwrite'); + +// Init SDK +const client = new sdk.Client(); + +const documentsDB = new sdk.DocumentsDB(client); + +client + .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('') // Your project ID + .setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key +; + +const promise = documentsDB.createCollection({ + databaseId: '', + collectionId: '', + name: '', + attributes: [ + { + key: 'name', + type: 'string', + size: 255, + required: true + }, + { + key: 'email', + type: 'string', + size: 254, + format: 'email', + required: true + }, + { + key: 'age', + type: 'integer', + required: false + }, + { + key: 'score', + type: 'float', + required: false + }, + { + key: 'is_active', + type: 'boolean', + required: true + }, + { + key: 'created_at', + type: 'datetime', + required: false + }, + { + key: 'status', + type: 'string', + size: 255, + format: 'enum', + elements: ['draft', 'published', 'archived'], + required: true + } + ], + indexes: [ + { + key: 'idx_email', + type: 'unique', + attributes: ['email'] + }, + { + key: 'idx_name', + type: 'key', + attributes: ['name'] + } + ] +}); + +promise.then(function (response) { + console.log(response); +}, function (error) { + console.log(error); +}); +``` +```deno +import * as sdk from "npm:node-appwrite"; + +// Init SDK +let client = new sdk.Client(); + +let documentsDB = new sdk.DocumentsDB(client); + +client + .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('') // Your project ID + .setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key +; + +let promise = documentsDB.createCollection({ + databaseId: '', + collectionId: '', + name: '', + attributes: [ + { + key: 'name', + type: 'string', + size: 255, + required: true + }, + { + key: 'email', + type: 'string', + size: 254, + format: 'email', + required: true + }, + { + key: 'age', + type: 'integer', + required: false + }, + { + key: 'score', + type: 'float', + required: false + }, + { + key: 'is_active', + type: 'boolean', + required: true + }, + { + key: 'created_at', + type: 'datetime', + required: false + }, + { + key: 'status', + type: 'string', + size: 255, + format: 'enum', + elements: ['draft', 'published', 'archived'], + required: true + } + ], + indexes: [ + { + key: 'idx_email', + type: 'unique', + attributes: ['email'] + }, + { + key: 'idx_name', + type: 'key', + attributes: ['name'] + } + ] +}); + +promise.then(function (response) { + console.log(response); +}, function (error) { + console.log(error); +}); +``` +```php +setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + ->setProject('') // Your project ID + ->setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key +; + +$documentsDB = new DocumentsDB($client); + +$result = $documentsDB->createCollection( + databaseId: '', + collectionId: '', + name: '', + attributes: [ + [ + 'key' => 'name', + 'type' => 'string', + 'size' => 255, + 'required' => true + ], + [ + 'key' => 'email', + 'type' => 'string', + 'size' => 254, + 'format' => 'email', + 'required' => true + ], + [ + 'key' => 'age', + 'type' => 'integer', + 'required' => false + ], + [ + 'key' => 'score', + 'type' => 'float', + 'required' => false + ], + [ + 'key' => 'is_active', + 'type' => 'boolean', + 'required' => true + ], + [ + 'key' => 'created_at', + 'type' => 'datetime', + 'required' => false + ], + [ + 'key' => 'status', + 'type' => 'string', + 'size' => 255, + 'format' => 'enum', + 'elements' => ['draft', 'published', 'archived'], + 'required' => true + ] + ], + indexes: [ + [ + 'key' => 'idx_email', + 'type' => 'unique', + 'attributes' => ['email'] + ], + [ + 'key' => 'idx_name', + 'type' => 'key', + 'attributes' => ['name'] + ] + ] +); +``` +```python +from appwrite.client import Client +from appwrite.services.documents_db import DocumentsDB + +client = Client() + +(client + .set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint + .set_project('') # Your project ID + .set_key('919c2d18fb5d4...a2ae413da83346ad2') # Your secret API key +) + +documentsDB = DocumentsDB(client) + +result = documentsDB.create_collection( + database_id='', + collection_id='', + name='', + attributes=[ + { + 'key': 'name', + 'type': 'string', + 'size': 255, + 'required': True + }, + { + 'key': 'email', + 'type': 'string', + 'size': 254, + 'format': 'email', + 'required': True + }, + { + 'key': 'age', + 'type': 'integer', + 'required': False + }, + { + 'key': 'score', + 'type': 'float', + 'required': False + }, + { + 'key': 'is_active', + 'type': 'boolean', + 'required': True + }, + { + 'key': 'created_at', + 'type': 'datetime', + 'required': False + }, + { + 'key': 'status', + 'type': 'string', + 'size': 255, + 'format': 'enum', + 'elements': ['draft', 'published', 'archived'], + 'required': True + } + ], + indexes=[ + { + 'key': 'idx_email', + 'type': 'unique', + 'attributes': ['email'] + }, + { + 'key': 'idx_name', + 'type': 'key', + 'attributes': ['name'] + } + ] +) +``` +```ruby +require 'Appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint + .set_project('') # Your project ID + .set_key('919c2d18fb5d4...a2ae413da83346ad2') # Your secret API key + +documentsDB = DocumentsDB.new(client) + +response = documentsDB.create_collection( + database_id: '', + collection_id: '', + name: '', + attributes: [ + { + key: 'name', + type: 'string', + size: 255, + required: true + }, + { + key: 'email', + type: 'string', + size: 254, + format: 'email', + required: true + }, + { + key: 'age', + type: 'integer', + required: false + }, + { + key: 'score', + type: 'float', + required: false + }, + { + key: 'is_active', + type: 'boolean', + required: true + }, + { + key: 'created_at', + type: 'datetime', + required: false + }, + { + key: 'status', + type: 'string', + size: 255, + format: 'enum', + elements: ['draft', 'published', 'archived'], + required: true + } + ], + indexes: [ + { + key: 'idx_email', + type: 'unique', + attributes: ['email'] + }, + { + key: 'idx_name', + type: 'key', + attributes: ['name'] + } + ] +) + +puts response.inspect +``` +```csharp +using Appwrite; +using Appwrite.Services; +using Appwrite.Models; + +var client = new Client() + .SetEndPoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .SetProject("") // Your project ID + .SetKey("919c2d18fb5d4...a2ae413da83346ad2"); // Your secret API key + +var documentsDB = new DocumentsDB(client); + +Collection result = await documentsDB.CreateCollection( + databaseId: "", + collectionId: "", + name: "", + attributes: new List> + { + new Dictionary + { + { "key", "name" }, + { "type", "string" }, + { "size", 255 }, + { "required", true } + }, + new Dictionary + { + { "key", "email" }, + { "type", "string" }, + { "size", 254 }, + { "format", "email" }, + { "required", true } + }, + new Dictionary + { + { "key", "age" }, + { "type", "integer" }, + { "required", false } + }, + new Dictionary + { + { "key", "score" }, + { "type", "float" }, + { "required", false } + }, + new Dictionary + { + { "key", "is_active" }, + { "type", "boolean" }, + { "required", true } + }, + new Dictionary + { + { "key", "created_at" }, + { "type", "datetime" }, + { "required", false } + }, + new Dictionary + { + { "key", "status" }, + { "type", "string" }, + { "size", 255 }, + { "format", "enum" }, + { "elements", new List { "draft", "published", "archived" } }, + { "required", true } + } + }, + indexes: new List> + { + new Dictionary + { + { "key", "idx_email" }, + { "type", "unique" }, + { "attributes", new List { "email" } } + }, + new Dictionary + { + { "key", "idx_name" }, + { "type", "key" }, + { "attributes", new List { "name" } } + } + }); +``` +```dart +import 'package:dart_appwrite/dart_appwrite.dart'; + +void main() { // Init SDK + Client client = Client(); + Databases documentsDB = DocumentsDB(client); + + client + .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('') // Your project ID + .setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key + ; + + Future result = documentsDB.createCollection( + databaseId: '', + collectionId: '', + name: '', + attributes: [ + { + 'key': 'name', + 'type': 'string', + 'size': 255, + 'required': true + }, + { + 'key': 'email', + 'type': 'string', + 'size': 254, + 'format': 'email', + 'required': true + }, + { + 'key': 'age', + 'type': 'integer', + 'required': false + }, + { + 'key': 'score', + 'type': 'float', + 'required': false + }, + { + 'key': 'is_active', + 'type': 'boolean', + 'required': true + }, + { + 'key': 'created_at', + 'type': 'datetime', + 'required': false + }, + { + 'key': 'status', + 'type': 'string', + 'size': 255, + 'format': 'enum', + 'elements': ['draft', 'published', 'archived'], + 'required': true + } + ], + indexes: [ + { + 'key': 'idx_email', + 'type': 'unique', + 'attributes': ['email'] + }, + { + 'key': 'idx_name', + 'type': 'key', + 'attributes': ['name'] + } + ], + ); + + result + .then((response) { + print(response); + }).catchError((error) { + print(error.response); + }); +} +``` +```kotlin +import io.appwrite.Client +import io.appwrite.services.DocumentsDB + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + .setKey("919c2d18fb5d4...a2ae413da83346ad2") // Your secret API key + +val documentsDB = DocumentsDB(client) + +val response = documentsDB.createCollection( + databaseId = "", + collectionId = "", + name = "", + attributes = listOf( + mapOf( + "key" to "name", + "type" to "string", + "size" to 255, + "required" to true + ), + mapOf( + "key" to "email", + "type" to "string", + "size" to 254, + "format" to "email", + "required" to true + ), + mapOf( + "key" to "age", + "type" to "integer", + "required" to false + ), + mapOf( + "key" to "score", + "type" to "float", + "required" to false + ), + mapOf( + "key" to "is_active", + "type" to "boolean", + "required" to true + ), + mapOf( + "key" to "created_at", + "type" to "datetime", + "required" to false + ), + mapOf( + "key" to "status", + "type" to "string", + "size" to 255, + "format" to "enum", + "elements" to listOf("draft", "published", "archived"), + "required" to true + ) + ), + indexes = listOf( + mapOf( + "key" to "idx_email", + "type" to "unique", + "attributes" to listOf("email") + ), + mapOf( + "key" to "idx_name", + "type" to "key", + "attributes" to listOf("name") + ) + ) +) +``` +```java +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.DocumentsDB; +import java.util.*; + +Client client = new Client() + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + .setKey("919c2d18fb5d4...a2ae413da83346ad2"); // Your secret API key + +Databases documentsDB = new DocumentsDB(client); + +List> attributes = Arrays.asList( + new HashMap() {{ + put("key", "name"); + put("type", "string"); + put("size", 255); + put("required", true); + }}, + new HashMap() {{ + put("key", "email"); + put("type", "string"); + put("size", 254); + put("format", "email"); + put("required", true); + }}, + new HashMap() {{ + put("key", "age"); + put("type", "integer"); + put("required", false); + }}, + new HashMap() {{ + put("key", "score"); + put("type", "float"); + put("required", false); + }}, + new HashMap() {{ + put("key", "is_active"); + put("type", "boolean"); + put("required", true); + }}, + new HashMap() {{ + put("key", "created_at"); + put("type", "datetime"); + put("required", false); + }}, + new HashMap() {{ + put("key", "status"); + put("type", "string"); + put("size", 255); + put("format", "enum"); + put("elements", Arrays.asList("draft", "published", "archived")); + put("required", true); + }} +); + +List> indexes = Arrays.asList( + new HashMap() {{ + put("key", "idx_email"); + put("type", "unique"); + put("attributes", Arrays.asList("email")); + }}, + new HashMap() {{ + put("key", "idx_name"); + put("type", "key"); + put("attributes", Arrays.asList("name")); + }} +); + +documentsDB.createCollection( + "", + "", + "", + attributes, + indexes, + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + System.out.println(result); + }) +); +``` +```swift +import Appwrite + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + .setKey("919c2d18fb5d4...a2ae413da83346ad2") // Your secret API key + +let documentsDB = DocumentsDB(client) + +let collection = try await documentsDB.createCollection( + databaseId: "", + collectionId: "", + name: "", + attributes: [ + [ + "key": "name", + "type": "string", + "size": 255, + "required": true + ], + [ + "key": "email", + "type": "string", + "size": 254, + "format": "email", + "required": true + ], + [ + "key": "age", + "type": "integer", + "required": false + ], + [ + "key": "score", + "type": "float", + "required": false + ], + [ + "key": "is_active", + "type": "boolean", + "required": true + ], + [ + "key": "created_at", + "type": "datetime", + "required": false + ], + [ + "key": "status", + "type": "string", + "size": 255, + "format": "enum", + "elements": ["draft", "published", "archived"], + "required": true + ] + ], + indexes: [ + [ + "key": "idx_email", + "type": "unique", + "attributes": ["email"] + ], + [ + "key": "idx_name", + "type": "key", + "attributes": ["name"] + ] + ] +) +``` +{% /multicode %} + +You can also configure **permissions** in the `createCollection` method. Learn more about the `createCollection` method in the [API references](/docs/references). +{% /tabsitem %} + +{% tabsitem #cli title="CLI" %} + +{% partial file="cli-disclaimer.md" /%} + +To create your collection using the CLI, first use the `appwrite init collections` command to initialize your collection. + +```sh +appwrite init collections +``` + +Then push your collection using the `appwrite push collections` command. + +```sh +appwrite push collections +``` + +This will create your collection in the Console with all of your `appwrite.json` configurations. + +{% arrow_link href="/docs/tooling/command-line/collections#commands" %} +Learn more about the CLI collections commands +{% /arrow_link %} + +{% /tabsitem %} + +{% /tabs %} + +{% info title="AI suggestions" %} +Enable **AI suggestions** to generate attributes and indexes based on your collection name and existing database structure. [Learn more about AI suggestions](/docs/products/databases/ai-suggestions). +{% /info %} + +# Permissions {% #permissions %} +Appwrite uses permissions to control data access. +For security, only users that are granted permissions can access a resource. +This helps prevent accidental data leaks by forcing you to make more conscious decisions around permissions. + +By default, Appwrite doesn't grant permissions to any users when a new collection is created. +This means users can't create new documents or read, update, and delete existing documents. + +[Learn about configuring permissions](/docs/products/databases/permissions). + +# Attributes {% #attributes %} +All documents in a collection follow the same structure. +Attributes are used to define the structure of your documents and help the Appwrite's API validate your users' input. +Add your first attribute by clicking the **Add attribute** button. + +You can choose between the following types. + +| Attribute | Description | +|--------------|------------------------------------------------------------------| +| `string` | String attribute. Requires a `size` parameter (1 to 1,073,741,823). Supports optional `format` for email, enum, IP, and URL validation. | +| `integer` | Integer attribute. Supports optional `min` and `max` range constraints. | +| `float` | Float attribute. Supports optional `min` and `max` range constraints. | +| `boolean` | Boolean attribute. | +| `datetime` | Datetime attribute formatted as an ISO 8601 string. | +| `relationship` | Relationship attribute relates one collection to another. [Learn more about relationships.](/docs/products/databases/documentsdb/relationships) | + +### String formats {% #string-formats %} + +String attributes support an optional `format` field for built-in validation: + +| Format | Description | Additional fields | +|--------|-------------|------------------| +| `email` | Validates as an email address. | - | +| `enum` | Restricts values to a set of allowed strings. | `elements` (array of allowed values) | +| `ip` | Validates as an IPv4 or IPv6 address. | - | +| `url` | Validates as a URL. | - | + +If an attribute must be populated in all documents, set it as `required`. +If not, you may optionally set a default value. +Additionally, decide if the attribute should be a single value or an array of values. + +If needed, you can change an attribute's key, default value, size (for string attributes), and whether it is required or not after creation. + +You can increase a string attribute's size without any restrictions. When decreasing size, you must ensure that your existing data is less than or equal to the new size, or the operation will fail. + +# Indexes {% #indexes %} + +Databases use indexes to quickly locate data without having to search through every document for matches. +To ensure the best performance, Appwrite recommends an index for every attribute queried. +If you plan to query multiple attributes in a single query, creating an index with **all** queried attributes will yield optimal performance. + +The following indexes are currently supported: + +| Type | Description | +|------------|--------------------------------------------------------------------------------------------------------------| +| `key` | Plain Index to allow queries. | +| `unique` | Unique Index to disallow duplicates. | +| `fulltext` | For searching within text attributes. Required for the [search query method](/docs/products/databases/queries#query-class). | + +You can create an index by navigating to your collection's **Indexes** tab or by using your favorite [Server SDK](/docs/sdks#server). diff --git a/src/routes/docs/products/databases/documentsdb/documents/+page.markdoc b/src/routes/docs/products/databases/documentsdb/documents/+page.markdoc new file mode 100644 index 0000000000..9085cd9a78 --- /dev/null +++ b/src/routes/docs/products/databases/documentsdb/documents/+page.markdoc @@ -0,0 +1,991 @@ +--- +layout: article +title: Documents +description: Master document management with Appwrite Databases. Learn how to create, update, upsert, and query documents within your collections for dynamic data storage. +--- +Each piece of data or information in Appwrite Databases is a document. +Documents have a structure defined by the parent collection. + +# Create documents {% #create-documents %} +{% info title="Permissions required" %} +You must grant _create_ permissions to users at the _collection level_ before users can create documents. +[Learn more about permissions](#permissions) +{% /info %} + +In most use cases, you will create documents programmatically. + +{% multicode %} +```client-web +import { Client, ID, DocumentsDB } from "appwrite"; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + +const documentsDB = new DocumentsDB(client); + +const promise = documentsDB.createDocument({ + databaseId: '', + collectionId: '', + documentId: ID.unique(), + data: {} +}); + +promise.then(function (response) { + console.log(response); +}, function (error) { + console.log(error); +}); +``` +```client-flutter +import 'package:appwrite/appwrite.dart'; + +void main() async { + final client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + + final documentsDB = DocumentsDB(client); + + try { + final document = documentsDB.createDocument( + databaseId: '', + collectionId: '', + documentId: ID.unique(), + data: {} + ); + } on AppwriteException catch(e) { + print(e); + } +} +``` +```client-apple +import Appwrite +import AppwriteModels + +func main() async throws { + let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + + let documentsDB = DocumentsDB(client) + + do { + let document = try await documentsDB.createDocument( + databaseId: "", + collectionId: "", + documentId: ID.unique(), + data: [:] + ) + } catch { + print(error.localizedDescription) + } +} +``` +```client-android-kotlin +import io.appwrite.Client +import io.appwrite.services.DocumentsDB + +suspend fun main() { + val client = Client(applicationContext) + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + + val documentsDB = DocumentsDB(client) + + try { + val document = documentsDB.createDocument( + databaseId = "", + collectionId = "", + documentId = ID.unique(), + data = mapOf("a" to "b"), + ) + } catch (e: AppwriteException) { + Log.e("Appwrite", "Error: " + e.message) + } +} +``` +```graphql +mutation { + documentsCreateDocument( + databaseId: "", + collectionId: "", + documentId: "", + data: "{}" + ) { + _id + _collectionId + _databaseId + _createdAt + _updatedAt + _permissions + data + } +} +``` +{% /multicode %} + +During testing, you might prefer to create documents in the Appwrite Console. +To do so, navigate to the **Documents** tab of your collection and click the **Add document** button. + +# List documents {% #list-documents %} + +{% info title="Permissions required" %} +You must grant _read_ permissions to users at the _collection level_ before users can read documents. +[Learn more about permissions](#permissions) +{% /info %} + +Documents can be retrieved using the [List documents](/docs/references/cloud/client-web/documents-db#listDocuments) endpoint. + +Results can be filtered, sorted, and paginated using Appwrite's shared set of query methods. +You can find a full guide on querying in the [Queries Guide](/docs/products/databases/queries). + +By default, results are limited to the _first 25 items_. +You can change this through [pagination](/docs/products/databases/pagination). + +{% info title="Speed up lists by skipping totals" %} +If your UI doesn't need an exact total, set the `total` flag to `false` on list calls. The response keeps the same shape and sets `total` to `0`. +This reduces latency for large collections and filtered queries. Learn more in [Pagination: Skip totals](/docs/products/databases/pagination#skip-totals). +{% /info %} + +{% multicode %} +```client-web +import { Client, Query, DocumentsDB } from "appwrite"; + +const client = new Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + +const documentsDB = new DocumentsDB(client); + +let promise = documentsDB.listDocuments({ + databaseId: "", + collectionId: "", + queries: [ + Query.equal('title', 'Avatar') + ] +}); + +promise.then(function (response) { + console.log(response); +}, function (error) { + console.log(error); +}); +``` +```client-flutter +import 'package:appwrite/appwrite.dart'; + +void main() async { + final client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + + final documentsDB = DocumentsDB(client); + + try { + final documents = await documentsDB.listDocuments( + databaseId: '', + collectionId: '', + queries: [ + Query.equal('title', 'Avatar') + ] + ); + } on AppwriteException catch(e) { + print(e); + } +} +``` +```client-apple +import Appwrite +import AppwriteModels + +func main() async throws { + let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + + let documentsDB = DocumentsDB(client) + + do { + let documents = try await documentsDB.listDocuments( + databaseId: "", + collectionId: "", + queries: [ + Query.equal("title", value: "Avatar") + ] + ) + } catch { + print(error.localizedDescription) + } +} +``` +```client-android-kotlin +import io.appwrite.Client +import io.appwrite.Query +import io.appwrite.services.DocumentsDB + +suspend fun main() { + val client = Client(applicationContext) + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + + val documentsDB = DocumentsDB(client) + + try { + val documents = documentsDB.listDocuments( + databaseId = "", + collectionId = "", + queries = listOf( + Query.equal("title", "Avatar") + ) + ) + } catch (e: AppwriteException) { + Log.e("Appwrite", "Error: " + e.message) + } +} +``` +```graphql +query { + documentsListDocuments( + databaseId: "", + collectionId: "", + queries: ["equal(\"title\", [\"Avatar\"])"] + ) { + total + documents { + _id + data + } + } +} +``` +{% /multicode %} + +# Update document {% #update-document %} +{% info title="Permissions required" %} +You must grant _update_ permissions to users at the _collection level_ or _document level_ before users can update documents. +[Learn more about permissions](#permissions) +{% /info %} + +In most use cases, you will update documents programmatically. + +{% multicode %} +```client-web +import { Client, DocumentsDB } from "appwrite"; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + +const documentsDB = new DocumentsDB(client); + +const promise = documentsDB.updateDocument({ + databaseId: '', + collectionId: '', + documentId: '', + data: { title: 'Updated Title' } +}); + +promise.then(function (response) { + console.log(response); +}, function (error) { + console.log(error); +}); +``` +```client-flutter +import 'package:appwrite/appwrite.dart'; + +void main() async { + final client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + + final documentsDB = DocumentsDB(client); + + try { + final document = await documentsDB.updateDocument( + databaseId: '', + collectionId: '', + documentId: '', + data: { 'title': 'Updated Title' } + ); + } on AppwriteException catch(e) { + print(e); + } +} +``` +```client-apple +import Appwrite +import AppwriteModels + +func main() async throws { + let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + + let documentsDB = DocumentsDB(client) + + do { + let document = try await documentsDB.updateDocument( + databaseId: "", + collectionId: "", + documentId: "", + data: ["title": "Updated Title"] + ) + } catch { + print(error.localizedDescription) + } +} +``` +```client-android-kotlin +import io.appwrite.Client +import io.appwrite.services.DocumentsDB + +suspend fun main() { + val client = Client(applicationContext) + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + + val documentsDB = DocumentsDB(client) + + try { + val document = documentsDB.updateDocument( + databaseId = "", + collectionId = "", + documentId = "", + data = mapOf("title" to "Updated Title"), + ) + } catch (e: AppwriteException) { + Log.e("Appwrite", "Error: " + e.message) + } +} +```graphql +mutation { + documentsDBUpdateDocument( + databaseId: "", + collectionId: "", + documentId: "", + data: "{\"title\": \"Updated Title\"}" + ) { + _id + _collectionId + _databaseId + _createdAt + _updatedAt + _permissions + data + } +} +``` +{% /multicode %} + +# Upsert documents {% #upsert-documents %} + +Upsert is a combination of "update" and "insert" operations. It creates a new document if one doesn't exist with the given ID, or updates an existing document if it does exist. + +In most use cases, you will upsert documents programmatically. + +{% info title="Permissions required" %} +You must grant _create_ permissions to users at the _collection level_, and _update_ permissions to users at the _collection_ or _document_ level before users can upsert documents. +[Learn more about permissions](#permissions) +{% /info %} +{% multicode %} +```client-web +import { Client, ID, DocumentsDB } from "appwrite"; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + +const documentsDB = new DocumentsDB(client); + +const promise = documentsDB.upsertDocument({ + databaseId: '', + collectionId: '', + documentId: ID.unique(), + data: {} +}); + +promise.then(function (response) { + console.log(response); +}, function (error) { + console.log(error); +}); +``` +```client-flutter +import 'package:appwrite/appwrite.dart'; + +void main() async { + final client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + + final documentsDB = DocumentsDB(client); + + try { + final document = documentsDB.upsertDocument( + databaseId: '', + collectionId: '', + documentId: ID.unique(), + data: {} + ); + } on AppwriteException catch(e) { + print(e); + } +} +``` +```client-apple +import Appwrite +import AppwriteModels + +func main() async throws { + let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + + let documentsDB = DocumentsDB(client) + + do { + let document = try await documentsDB.upsertDocument( + databaseId: "", + collectionId: "", + documentId: ID.unique(), + data: [:] + ) + } catch { + print(error.localizedDescription) + } +} +``` +```client-android-kotlin +import io.appwrite.Client +import io.appwrite.services.DocumentsDB + +suspend fun main() { + val client = Client(applicationContext) + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + + val documentsDB = DocumentsDB(client) + + try { + val document = documentsDB.upsertDocument( + databaseId = "", + collectionId = "", + documentId = ID.unique(), + data = mapOf("a" to "b"), + ) + } catch (e: AppwriteException) { + Log.e("Appwrite", "Error: " + e.message) + } +} +``` +```graphql +mutation { + documentsUpsertDocument( + databaseId: "", + collectionId: "", + documentId: "", + data: "{}" + ) { + _id + _collectionId + _databaseId + _createdAt + _updatedAt + _permissions + data + } +} +``` +{% /multicode %} + +# Type safety with models {% #type-safety %} + +Mobile and native SDKs provide type safety when working with documents through the `nestedType` parameter. This allows you to specify custom model types for complete auto-completion and type safety. + +## Define your model + +Create a data class or struct that matches your collection structure: + +{% tabs %} +{% tabsitem #kotlin title="Kotlin/Java" %} +```kotlin +data class Book( + val title: String, + val author: String, + val publishedYear: Int? = null, + val genre: List? = null, + val isAvailable: Boolean = true +) +``` +{% /tabsitem %} + +{% tabsitem #swift title="Swift" %} +```swift +struct Book: Codable { + let title: String + let author: String + let publishedYear: Int? + let genre: [String]? + let isAvailable: Bool +} +``` +{% /tabsitem %} + +{% tabsitem #web title="Web/Node" %} +```typescript +interface Book { + title: string; + author: string; + publishedYear?: number; + genre?: string[]; + isAvailable: boolean; +} +``` +{% /tabsitem %} +{% /tabs %} + +## Using type-safe operations + +Use the `nestedType` parameter for full type safety in native SDKs, or generics in web SDKs: + +{% multicode %} +```client-android-kotlin +val documentsDB = DocumentsDB(client) + +try { + // Create with type safety + val newBook = documentsDB.createDocument( + databaseId = "", + collectionId = "", + documentId = ID.unique(), + data = mapOf( + "title" to "The Great Gatsby", + "author" to "F. Scott Fitzgerald", + "isAvailable" to true + ), + nestedType = Book::class.java + ) + + // List with type safety + val books = documentsDB.listDocuments( + databaseId = "", + collectionId = "", + nestedType = Book::class.java + ) + + // Now you have full type safety + for (book in books.documents) { + Log.d("Appwrite", "Book: ${book.title} by ${book.author}") + if (book.isAvailable) { + Log.d("Appwrite", "Available for checkout") + } + } +} catch (e: AppwriteException) { + Log.e("Appwrite", "Error: ${e.message}") +} +``` +```client-apple +let documentsDB = DocumentsDB(client) + +do { + // Create with type safety + let newBook = try await documentsDB.createDocument( + databaseId: "", + collectionId: "", + documentId: ID.unique(), + data: [ + "title": "The Great Gatsby", + "author": "F. Scott Fitzgerald", + "isAvailable": true + ], + nestedType: Book.self + ) + + // List with type safety + let books = try await documentsDB.listDocuments( + databaseId: "", + collectionId: "", + nestedType: Book.self + ) + + // Now you have full type safety + for book in books.documents { + print("Book: \(book.title) by \(book.author)") + if book.isAvailable { + print("Available for checkout") + } + } +} catch { + print(error.localizedDescription) +} +``` +```client-web +const documentsDB = new DocumentsDB(client); + +try { + // Create with generics + const newBook = await documentsDB.createDocument({ + databaseId: '', + collectionId: '', + documentId: ID.unique(), + data: { + title: "The Great Gatsby", + author: "F. Scott Fitzgerald", + isAvailable: true + } + }); + + // List with generics + const books = await documentsDB.listDocuments({ + databaseId: '', + collectionId: '' + }); + + // TypeScript provides full type safety + books.documents.forEach(book => { + console.log(`Book: ${book.title} by ${book.author}`); + if (book.isAvailable) { + console.log("Available for checkout"); + } + }); +} catch (error) { + console.log(error); +} +``` +{% /multicode %} + +## Model methods + +Models returned by native SDKs include helpful utility methods: + +{% tabs %} +{% tabsitem #kotlin-methods title="Kotlin/Java" %} +```kotlin +val book = books.documents.first() + +// Convert model to Map for debugging or manual manipulation +val bookMap = book.toMap() +Log.d("Appwrite", "Book data: ${bookMap}") + +// Create model instance from Map data +val bookData = mapOf( + "title" to "1984", + "author" to "George Orwell", + "isAvailable" to false +) +val newBook = Book.from(bookData, Book::class.java) + +// JSON serialization using Gson (used internally by SDK) +import com.google.gson.Gson +val gson = Gson() +val jsonString = gson.toJson(book) +val bookFromJson = gson.fromJson(jsonString, Book::class.java) +``` +{% /tabsitem %} + +{% tabsitem #swift-methods title="Swift" %} +```swift +let book = books.documents.first! + +// Convert model to dictionary for debugging +let bookMap = book.toMap() +print("Book data: \(bookMap)") + +// Create model instance from dictionary +let bookData: [String: Any] = [ + "title": "1984", + "author": "George Orwell", + "isAvailable": false +] +let newBook = Book.from(map: bookData) + +// JSON encoding using Swift's Codable +let jsonData = try JSONEncoder().encode(book) +let jsonString = String(data: jsonData, encoding: .utf8) + +// JSON decoding +if let jsonString = jsonString, + let data = jsonString.data(using: .utf8) { + let bookFromJson = try JSONDecoder().decode(Book.self, from: data) +} +``` +{% /tabsitem %} +{% /tabs %} + +{% info title="Generate types automatically" %} +You can generate types for your collections. Learn more in [Type generation](/docs/products/databases/documentsdb/type-generation). +{% /info %} + +# Permissions {% #permissions %} +In Appwrite, permissions can be granted at the collection level and the document level. +Before a user can create a document, you need to grant create permissions to the user. + +Read, update, and delete permissions can be granted at both the collection and document level. +Users only need to be granted access at either the collection or document level to access documents. + +[Learn about configuring permissions](/docs/products/databases/permissions). + +# Use transactions {% #use-transactions %} + +All document operations support `transactionId`. When provided, operations are staged to an internal log and not applied until the transaction is committed. Learn more in the [Transactions guide](/docs/products/databases/documentsdb/transactions). + +{% multicode %} +```client-web +// Create document inside a transaction +await documentsDB.createDocument({ + databaseId: '', + collectionId: '', + documentId: '', + data: { title: 'Draft' }, + transactionId: '' +}); + +// Update document inside a transaction +await documentsDB.updateDocument({ + databaseId: '', + collectionId: '', + documentId: '', + data: { title: 'Published' }, + transactionId: '' +}); +``` +```client-flutter +// Create document inside a transaction +await documentsDB.createDocument( + databaseId: '', + collectionId: '', + documentId: '', + data: { 'title': 'Draft' }, + transactionId: '' +); + +// Update document inside a transaction +await documentsDB.updateDocument( + databaseId: '', + collectionId: '', + documentId: '', + data: { 'title': 'Published' }, + transactionId: '' +); +``` +```client-apple +// Create document inside a transaction +let _ = try await documentsDB.createDocument( + databaseId: "", + collectionId: "", + documentId: "", + data: ["title": "Draft"], + transactionId: "" +) + +// Update document inside a transaction +let _2 = try await documentsDB.updateDocument( + databaseId: "", + collectionId: "", + documentId: "", + data: ["title": "Published"], + transactionId: "" +) +``` +```client-android-kotlin +// Create document inside a transaction +val _ = documentsDB.createDocument( + databaseId = "", + collectionId = "", + documentId = "", + data = mapOf("title" to "Draft"), + transactionId = "" +) + +// Update document inside a transaction +val _2 = documentsDB.updateDocument( + databaseId = "", + collectionId = "", + documentId = "", + data = mapOf("title" to "Published"), + transactionId = "" +) +``` +```client-android-java +// Create document inside a transaction (asynchronous) +Map data = new HashMap<>(); +data.put("title", "Draft"); + +documentsDB.createDocument( + "", + "", + "", + data, + "", + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return null; + } + System.out.println(result); + return null; + }) +); + +// Update document inside a transaction (asynchronous) +Map update = new HashMap<>(); +update.put("title", "Published"); + +documentsDB.updateDocument( + "", + "", + "", + update, + "", + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return null; + } + System.out.println(result); + return null; + }) +); +``` +```client-react-native +// Create document inside a transaction +await documentsDB.createDocument({ + databaseId: '', + collectionId: '', + documentId: '', + data: { title: 'Draft' }, + transactionId: '' +}); + +// Update document inside a transaction +await documentsDB.updateDocument({ + databaseId: '', + collectionId: '', + documentId: '', + data: { title: 'Published' }, + transactionId: '' +}); +``` +```server-nodejs +// Delete document inside a transaction +await documentsDB.deleteDocument({ + databaseId: '', + collectionId: '', + documentId: '', + transactionId: '' +}); +``` +```server-deno +// Delete document inside a transaction +await documentsDB.deleteDocument({ + databaseId: '', + collectionId: '', + documentId: '', + transactionId: '' +}); +``` +```server-python +# Delete document inside a transaction +documentsDB.delete_document( + database_id = '', + collection_id = '', + document_id = '', + transaction_id = '' +) +``` +```server-php +// Delete document inside a transaction +$documentsDB->deleteDocument( + databaseId: '', + collectionId: '', + documentId: '', + transactionId: '' +); +``` +```server-ruby +# Delete document inside a transaction +documentsDB.delete_document( + database_id: '', + collection_id: '', + document_id: '', + transaction_id: '' +) +``` +```server-dotnet +// Delete document inside a transaction +await documentsDB.DeleteDocument( + databaseId: "", + collectionId: "", + documentId: "", + transactionId: "" +); +``` +```server-dart +// Delete document inside a transaction +await documentsDB.deleteDocument( + databaseId: '', + collectionId: '', + documentId: '', + transactionId: '' +); +``` +```server-swift +// Delete document inside a transaction +let _ = try await documentsDB.deleteDocument( + databaseId: "", + collectionId: "", + documentId: "", + transactionId: "" +) +``` +```server-kotlin +// Delete document inside a transaction +val _ = documentsDB.deleteDocument( + databaseId = "", + collectionId = "", + documentId = "", + transactionId = "" +) +``` +```server-java +// Delete document inside a transaction (asynchronous) +documentsDB.deleteDocument( + "", + "", + "", + "", + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return null; + } + System.out.println(result); + return null; + }) +); +``` +{% /multicode %} + +# Next steps {% #next-steps %} + +Continue learning with these related guides: + +{% cards %} +{% cards_item href="/docs/products/databases/queries" title="Queries" %} +Learn how to filter, sort, and search your documents with various query operators. +{% /cards_item %} + +{% cards_item href="/docs/products/databases/pagination" title="Pagination" %} +Handle large datasets by implementing pagination in your document queries. +{% /cards_item %} + +{% cards_item href="/docs/products/databases/documentsdb/bulk-operations" title="Bulk operations" %} +Perform create, update, and delete operations on multiple documents simultaneously. +{% /cards_item %} + +{% cards_item href="/docs/products/databases/documentsdb/timestamp-overrides" title="Timestamp overrides" %} +Set custom creation and update timestamps when migrating data or backdating records. +{% /cards_item %} +{% /cards %} diff --git a/src/routes/docs/products/databases/documentsdb/quick-start/+page.markdoc b/src/routes/docs/products/databases/documentsdb/quick-start/+page.markdoc new file mode 100644 index 0000000000..8772ca086b --- /dev/null +++ b/src/routes/docs/products/databases/documentsdb/quick-start/+page.markdoc @@ -0,0 +1,375 @@ +--- +layout: article +title: Start with Documents DB +description: Get started with Appwrite Documents DB (MongoDB). Follow a step-by-step guide to create your first database, define collections, and perform basic data operations. +--- + + +{% section #create-database step=1 title="Create database" %} +Head to your [Appwrite Console](https://cloud.appwrite.io/console/) and create a database and name it `Oscar`. +Optionally, add a custom database ID. +{% /section %} + +{% section #create-collection step=2 title="Create collection" %} +Create a collection and name it `My books`. Optionally, add a custom collection ID. + +Navigate to **Attributes** and create attributes by clicking **Create attribute** and select **String**. +Attributes define the structure of your collection's documents. Enter **Attribute key** and **Size**. For example, `title` and `100`. + +Navigate to **Settings** > **Permissions** and add a new role **Any**. +Check the **CREATE** and **READ** permissions, so anyone can create and read documents. +{% /section %} + + +{% section #create-documents step=3 title="Create documents" %} +To create a document use the `createDocument` method. + +In the **Settings** menu, find your project ID and replace `` in the example. + +Navigate to the `Oscar` database, copy the database ID, and replace ``. +Then, in the `My books` collection, copy the collection ID, and replace ``. + +{% multicode %} +```client-web +import { Client, ID, DocumentsDB } from "appwrite"; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + +const documentsDB = new DocumentsDB(client); + +const promise = documentsDB.createDocument({ + databaseId: '', + collectionId: '', + documentId: ID.unique(), + data: { title: "Hamlet" } +}); + +promise.then(function (response) { + console.log(response); +}, function (error) { + console.log(error); +}); +``` +```client-flutter +import 'package:appwrite/appwrite.dart'; + +void main() async { + final client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + + final documentsDB = DocumentsDB(client); + + try { + final document = documentsDB.createDocument( + databaseId: '', + collectionId: '', + documentId: ID.unique(), + data: { "title": "Hamlet" } + ); + } on AppwriteException catch(e) { + print(e); + } +} +``` +```client-apple +import Appwrite +import AppwriteModels + +func main() async throws { + let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + + let documentsDB = DocumentsDB(client) + + do { + let document = try await documentsDB.createDocument( + databaseId: "", + collectionId: "", + documentId: ID.unique(), + data: ["title" : "hamlet"] + ) + } catch { + print(error.localizedDescription) + } +} +``` +```client-android-kotlin +import io.appwrite.Client +import io.appwrite.services.DocumentsDB + +suspend fun main() { + val client = Client(applicationContext) + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + + val documentsDB = DocumentsDB(client) + + try { + val document = documentsDB.createDocument( + databaseId = "", + collectionId = "", + documentId = ID.unique(), + data = mapOf("title" to "hamlet"), + ) + } catch (e: Exception) { + Log.e("Appwrite", "Error: " + e.message) + } +} +``` +{% /multicode %} + +The response should look similar to this. + +```json +{ + "title": "Hamlet", + "$id": "65013138dcd8618e80c4", + "$permissions": [], + "$createdAt": "2023-09-13T03:49:12.905+00:00", + "$updatedAt": "2023-09-13T03:49:12.905+00:00", + "$databaseId": "650125c64b3c25ce4bc4", + "$collectionId": "650125cff227cf9f95ad" +} +``` + +{% /section %} + +{% section #list-documents step=4 title="List documents" %} +To read and query data from your collection, use the `listDocuments` endpoint. + +Like the previous step, replace ``, ``, and `` with their respective IDs. +{% multicode %} +```client-web +import { Client, Query, DocumentsDB } from "appwrite"; + +const client = new Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + +const documentsDB = new DocumentsDB(client); + +const promise = documentsDB.listDocuments({ + databaseId: "", + collectionId: "", + queries: [ + Query.equal('title', 'Hamlet') + ] +}); + +promise.then(function (response) { + console.log(response); +}, function (error) { + console.log(error); +}); +``` +```client-flutter +import 'package:appwrite/appwrite.dart'; + +void main() async { + final client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + + final documentsDB = DocumentsDB(client); + + try { + final documents = await documentsDB.listDocuments( + databaseId: '', + collectionId: '', + queries: [ + Query.equal('title', 'Hamlet') + ] + ); + } on AppwriteException catch(e) { + print(e); + } +} +``` +```client-apple +import Appwrite +import AppwriteModels + +func main() async throws{ + let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + + let documentsDB = DocumentsDB(client) + + do { + let documents = try await documentsDB.listDocuments( + databaseId: "", + collectionId: "", + queries: [ + Query.equal("title", value: "Hamlet") + ] + ) + } catch { + print(error.localizedDescription) + } +} +``` +```client-android-kotlin +import io.appwrite.Client +import io.appwrite.Query +import io.appwrite.services.DocumentsDB + +suspend fun main() { + val client = Client(applicationContext) + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + + val documentsDB = DocumentsDB(client) + + try { + val documents = documentsDB.listDocuments( + databaseId = "", + collectionId = "", + queries = listOf( + Query.equal("title", "Hamlet") + ) + ) + } catch (e: AppwriteException) { + Log.e("Appwrite", "Error: " + e.message) + } +} +``` + +{% /multicode %} +{% /section %} + +{% section #type-safety step=5 title="Type safety with models" %} +For added type safety and better development experience, mobile and native SDKs support custom model types with the `nestedType` parameter. + +Define a data class or model that matches your collection structure: + +{% multicode %} +```client-android-kotlin +data class Book( + val title: String, + val author: String? = null, + val pages: Int? = null, + val isAvailable: Boolean = true +) + +val documentsDB = DocumentsDB(client) + +try { + // Use nestedType for type-safe responses + val books = documentsDB.listDocuments( + databaseId = "", + collectionId = "", + nestedType = Book::class.java + ) + + for (book in books.documents) { + Log.d("Appwrite", "Book: ${book.title} by ${book.author}") + } +} catch (e: AppwriteException) { + Log.e("Appwrite", "Error: ${e.message}") +} +``` +```client-apple +struct Book: Codable { + let title: String + let author: String? + let pages: Int? + let isAvailable: Bool +} + +let documentsDB = DocumentsDB(client) + +do { + // Use nestedType for type-safe responses + let books = try await documentsDB.listDocuments( + databaseId: "", + collectionId: "", + nestedType: Book.self + ) + + for book in books.documents { + print("Book: \(book.title) by \(book.author ?? "Unknown")") + } +} catch { + print(error.localizedDescription) +} +``` +```client-web +// Web SDK supports generics for type safety +interface Book { + title: string; + author?: string; + pages?: number; + isAvailable: boolean; +} + +const documentsDB = new DocumentsDB(client); + +try { + const books = await documentsDB.listDocuments({ + databaseId: '', + collectionId: '' + }); + + books.documents.forEach(book => { + console.log(`Book: ${book.title} by ${book.author}`); + }); +} catch (error) { + console.log(error); +} +``` +{% /multicode %} + +{% info title="Automatic type generation" %} +You can generate types for your collections. Learn more in [Type generation](/docs/products/databases/documentsdb/type-generation). +{% /info %} + +### Model methods + +Models returned by native SDKs include helpful methods for data manipulation: + +{% tabs %} +{% tabsitem #kotlin title="Kotlin/Java" %} +```kotlin +val book = books.documents.first() + +// Convert to Map for debugging or manual manipulation +val bookMap = book.toMap() +Log.d("Appwrite", bookMap.toString()) + +// Create model from Map +val bookData = mapOf( + "title" to "The Great Gatsby", + "author" to "F. Scott Fitzgerald" +) +val newBook = Book.from(bookData, Book::class.java) +``` +{% /tabsitem %} + +{% tabsitem #swift title="Swift" %} +```swift +let book = books.documents.first! + +// Convert to dictionary for debugging +let bookMap = book.toMap() +print(bookMap) + +// Create model from dictionary +let bookData: [String: Any] = [ + "title": "The Great Gatsby", + "author": "F. Scott Fitzgerald" +] +let newBook = Book.from(map: bookData) + +// Encode to JSON +let jsonData = try JSONEncoder().encode(book) +let jsonString = String(data: jsonData, encoding: .utf8) +``` +{% /tabsitem %} +{% /tabs %} + +{% /section %} diff --git a/src/routes/docs/products/databases/documentsdb/relationships/+page.markdoc b/src/routes/docs/products/databases/documentsdb/relationships/+page.markdoc new file mode 100644 index 0000000000..bc2238eda1 --- /dev/null +++ b/src/routes/docs/products/databases/documentsdb/relationships/+page.markdoc @@ -0,0 +1,1083 @@ +--- +layout: article +title: Relationships +description: Manage complex data relationships with Appwrite Databases. Discover how to define and work with relationships between documents for interconnected data. +difficulty: advanced +readtime: 20 +--- +Relationships describe how documents in different collections are associated, so that related documents can be read, updated, or deleted together. Entities in real-life often associate with each other in an organic and logical way, like a person and their dog, an album and its songs, or friends in a social network. + +These types of association between entities can be modeled in Appwrite using relationships. + +{% info title="Experimental feature" %} +Appwrite Relationships is an experimental feature. The API and behavior are subject to change in future versions. +{% /info %} + +# Relationship attributes {% #relationship-attributes %} + +Relationships are represented in a collection using **relationship attributes**. +The relationship attribute contains the ID of related documents, which it references during read, update, and delete operations. +This attribute is **null** if a document has no related documents. + +# When to use a relationship {% #when-to-use-relationships %} + +Relationships help reduce redundant information. For example, a user can create many posts in your app. You can model this without relationships by keeping a copy of the user's information in all the documents representing posts, but this creates a lot of duplicate information in your database about the user. + +# Benefits of relationships {% #benefit-of-relationships %} + +Duplicated records waste storage, but more importantly, makes the database much harder to maintain. If the user changes their user name, you will have to update dozens or hundreds of records, a problem commonly known as an update anomaly in documentsDB. You can avoid duplicate information by storing users and posts in separate collections and relating a user and their posts through a relationship. + +# Opt-in loading {% #performance-loading %} + +By default, Appwrite returns only a document's own fields when you retrieve documents. Related documents are **not automatically loaded** unless you explicitly request them using query selection. This eliminates unintentional payload bloat and gives you precise control over performance. + +{% arrow_link href="/docs/products/databases/queries#relationship-select" %} +Learn how to load relationships with queries +{% /arrow_link %} + +# Directionality {% #directionality %} + +Appwrite relationships can be one-way or two-way. + +| Type | Description | +| -------- | ----------------------------------------------------------------------------------------------------------------- | +| One-way | The relationship is only visible to one side of the relation. This is similar to a tree data structure. | +| Two-way | The relationship is visible to both sides of the relationship. This is similar to a graph data structure. | + +# Types {% #types %} + +Appwrite provides four different relationship types to enforce different associative rules between documents. + +| Type | Description | +| ----------- | ----------------------------------------------------------------------- | +| One-to-one | A document can only be related to one and only one document. | +| One-to-many | A document can be related to many other documents. | +| Many-to-one | Many documents can be related to a single document. | +| Many-to-many| A document can be related to many other documents. | + + +# On-delete {% #on-delete %} + +Appwrite also allows you to define the behavior of a relationship when a document is deleted. + +| Type | Description | +| ---------- | ---------------------------------------------------------------------- | +| Restrict | If a document has at least one related document, it cannot be deleted.| +| Cascade | If a document has related documents, when it is deleted, the related documents are also deleted.| +| Set null | If a document has related documents, when it is deleted, the related documents are kept with their relationship attribute set to null.| + +# Creating relationships {% #create-relationships %} +You can define relationships in the Appwrite Console, or using a [Server SDK](/docs/sdks#server). + +{% info title="Inline relationship creation" %} +In DocumentsDB, relationship attributes must be defined **inline during collection creation** as part of the `attributes` array in the `createCollection` method. Unlike TablesDB, which provides a standalone `createRelationshipColumn` method to add relationships to existing tables, DocumentsDB does not have a separate `createRelationshipAttribute` endpoint. +{% /info %} + +{% tabs %} +{% tabsitem #console title="Console" %} + +You can create relationships in the Appwrite Console by adding a relationship attribute to a collection. + +1. In your project, navigate to **Databases** > **Select your database** > **Select your collection** > **Attributes** > **Create attribute**. +2. Select **Relationship** as the attribute type. +3. In the **Relationship** modal, select the [relationship type](#types) and pick the related collection and attributes. +4. Pick relationship attribute key(s) to represent the related collection. Relationship attribute keys are used to reference the related collection in queries, so pick something that's intuitive and easy to remember. +5. Select desired [on delete](#on-delete) behavior. +6. Click the **Create** button to create the relationship. +{% /tabsitem %} + +{% tabsitem #sdk title="SDK" %} +Here's an example that creates a **movies** collection with a relationship to a **reviews** collection. +The relationship attribute with the key `reviews` is defined inline in the `attributes` array during collection creation. A two-way relationship attribute with the key `movie` is also created on the reviews collection. + +{% multicode %} +```js +const { Client, ID, DocumentsDB } = require('node-appwrite'); + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + .setProject(''); // Your project ID + +const documentsDB = new DocumentsDB(client); + +const collection = await documentsDB.createCollection({ + databaseId: 'marvel', + collectionId: ID.unique(), + name: 'Movies', + attributes: [ + { + key: 'title', + type: 'varchar', + size: 255, + required: true + }, + { + key: 'year', + type: 'integer', + required: false + }, + { + key: 'reviews', + type: 'relationship', + relatedCollectionId: '', + relationType: 'oneToMany', + twoWay: true, + twoWayKey: 'movie', + onDelete: 'cascade' + } + ] +}); +``` + + +```php +use \Appwrite\Client; +use \Appwrite\Services\DocumentsDB; +use \Appwrite\ID; + +$client = (new Client()) + ->setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + ->setProject(''); // Your project ID + +$documentsDB = new DocumentsDB($client); + +$collection = $documentsDB->createCollection( + databaseId: 'marvel', + collectionId: ID::unique(), + name: 'Movies', + attributes: [ + [ + 'key' => 'title', + 'type' => 'varchar', + 'size' => 255, + 'required' => true + ], + [ + 'key' => 'year', + 'type' => 'integer', + 'required' => false + ], + [ + 'key' => 'reviews', + 'type' => 'relationship', + 'relatedCollectionId' => '', + 'relationType' => 'oneToMany', + 'twoWay' => true, + 'twoWayKey' => 'movie', + 'onDelete' => 'cascade' + ] + ] +); +``` + + +```python +from appwrite.client import Client +from appwrite.services.documents_db import DocumentsDB +from appwrite.id import ID + +client = (Client() + .set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint + .set_project('')) # Your project ID + +documentsDB = DocumentsDB(client) + +collection = documentsDB.create_collection( + database_id='marvel', + collection_id=ID.unique(), + name='Movies', + attributes=[ + { + 'key': 'title', + 'type': 'varchar', + 'size': 255, + 'required': True + }, + { + 'key': 'year', + 'type': 'integer', + 'required': False + }, + { + 'key': 'reviews', + 'type': 'relationship', + 'relatedCollectionId': '', + 'relationType': 'oneToMany', + 'twoWay': True, + 'twoWayKey': 'movie', + 'onDelete': 'cascade' + } + ] +) +``` + + +```ruby +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint + .set_project('') # Your project ID + +documentsDB = DocumentsDB.new(client) + +collection = documentsDB.create_collection( + database_id: 'marvel', + collection_id: ID.unique(), + name: 'Movies', + attributes: [ + { + key: 'title', + type: 'varchar', + size: 255, + required: true + }, + { + key: 'year', + type: 'integer', + required: false + }, + { + key: 'reviews', + type: 'relationship', + relatedCollectionId: '', + relationType: 'oneToMany', + twoWay: true, + twoWayKey: 'movie', + onDelete: 'cascade' + } + ] +) +``` + + +```deno +import { Client, ID, DocumentsDB } from "npm:node-appwrite"; + +const client = new Client() + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject(""); // Your project ID + +const documentsDB = new DocumentsDB(client); + +const collection = await documentsDB.createCollection({ + databaseId: "marvel", + collectionId: ID.unique(), + name: "Movies", + attributes: [ + { + key: "title", + type: "varchar", + size: 255, + required: true + }, + { + key: "year", + type: "integer", + required: false + }, + { + key: "reviews", + type: "relationship", + relatedCollectionId: "", + relationType: "oneToMany", + twoWay: true, + twoWayKey: "movie", + onDelete: "cascade" + } + ] +}); +``` + + +```dart +import 'package:appwrite/appwrite.dart'; + +final client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + .setProject(''); // Your project ID + +final documentsDB = DocumentsDB(client); + +final collection = await documentsDB.createCollection( + databaseId: 'marvel', + collectionId: ID.unique(), + name: 'Movies', + attributes: [ + { + 'key': 'title', + 'type': 'varchar', + 'size': 255, + 'required': true + }, + { + 'key': 'year', + 'type': 'integer', + 'required': false + }, + { + 'key': 'reviews', + 'type': 'relationship', + 'relatedCollectionId': '', + 'relationType': 'oneToMany', + 'twoWay': true, + 'twoWayKey': 'movie', + 'onDelete': 'cascade' + } + ], +); +``` + + +```kotlin +import io.appwrite.Client +import io.appwrite.ID +import io.appwrite.services.DocumentsDB + +val client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + +val documentsDB = DocumentsDB(client) + +val collection = documentsDB.createCollection( + databaseId = "marvel", + collectionId = ID.unique(), + name = "Movies", + attributes = listOf( + mapOf( + "key" to "title", + "type" to "varchar", + "size" to 255, + "required" to true + ), + mapOf( + "key" to "year", + "type" to "integer", + "required" to false + ), + mapOf( + "key" to "reviews", + "type" to "relationship", + "relatedCollectionId" to "", + "relationType" to "oneToMany", + "twoWay" to true, + "twoWayKey" to "movie", + "onDelete" to "cascade" + ) + ) +) +``` + + +```swift +import Appwrite + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + +let documentsDB = DocumentsDB(client) + +let collection = try await documentsDB.createCollection( + databaseId: "marvel", + collectionId: ID.unique(), + name: "Movies", + attributes: [ + [ + "key": "title", + "type": "varchar", + "size": 255, + "required": true + ], + [ + "key": "year", + "type": "integer", + "required": false + ], + [ + "key": "reviews", + "type": "relationship", + "relatedCollectionId": "", + "relationType": "oneToMany", + "twoWay": true, + "twoWayKey": "movie", + "onDelete": "cascade" + ] + ] +) +``` + + +```csharp +using Appwrite; +using Appwrite.Services; + +var client = new Client() + .SetEndpoint("https://.cloud.appwrite.io/v1") + .SetProject(""); + +var documentsDB = new DocumentsDB(client); + +var collection = await documentsDB.CreateCollection( + databaseId: "marvel", + collectionId: ID.Unique(), + name: "Movies", + attributes: new List> + { + new Dictionary + { + { "key", "title" }, + { "type", "varchar" }, + { "size", 255 }, + { "required", true } + }, + new Dictionary + { + { "key", "year" }, + { "type", "integer" }, + { "required", false } + }, + new Dictionary + { + { "key", "reviews" }, + { "type", "relationship" }, + { "relatedCollectionId", "" }, + { "relationType", "oneToMany" }, + { "twoWay", true }, + { "twoWayKey", "movie" }, + { "onDelete", "cascade" } + } + }); +``` +{% /multicode %} +{% /tabsitem %} +{% /tabs %} + +# Creating documents {% #create-documents %} +If a collection has relationship attributes, you can create documents in two ways. +You create both parent and child at the same time using a **nested** syntax or link parent and child documents through **references***. + +{% tabs %} +{% tabsitem #nested title="Nested" %} +You can create both the **parent** and **child** at once in a relationship by nesting data. + +{% multicode %} +```js +const { Client, ID, DocumentsDB } = require('node-appwrite'); + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + .setProject(''); // Your project ID + +const documentsDB = new DocumentsDB(client); + +await documentsDB.createDocument({ + databaseId: 'marvel', + collectionId: 'movies', + documentId: ID.unique(), + data: { + title: 'Spiderman', + year: 2002, + reviews: [ + { author: 'Bob', text: 'Great movie!' }, + { author: 'Alice', text: 'Loved it!' } + ] + } +}); +``` + +```dart +import 'package:appwrite/appwrite.dart'; + +final client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + .setProject(''); // Your project ID + +final documentsDB = DocumentsDB(client); + +await documentsDB.createDocument( + databaseId: 'marvel', + collectionId: 'movies', + documentId: ID.unique(), + data: { + 'title': 'Spiderman', + 'year': 2002, + 'reviews': [ + { 'author': 'Bob', 'text': 'Great movie!' }, + { 'author': 'Alice', 'text': 'Loved it!' } + ] + }, +) +``` + +```swift +import Appwrite + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + +let documentsDB = DocumentsDB(client: client) + +documentsDB.createDocument( + databaseId: "marvel", + collectionId: "movies", + documentId: ID.unique(), + data: [ + "title": "Spiderman", + "year": 2002, + "reviews": [ + [ "author": "Bob", "text": "Great movie!" ], + [ "author": "Alice", "text": "Loved it!" ] + ] + ] +) +``` + +```kotlin +import io.appwrite.Client +import io.appwrite.services.DocumentsDB +import io.appwrite.ID + +val client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + +val documentsDB = DocumentsDB(client) + +documentsDB.createDocument( + databaseId = "marvel", + collectionId = "movies", + documentId = ID.unique(), + data = mapOf( + "title" to "Spiderman", + "year" to 2002, + "reviews" to listOf( + mapOf("author" to "Bob", "text" to "Great movie!"), + mapOf("author" to "Alice", "text" to "Loved it!") + ) + ) +) +``` +{% /multicode %} + +## Edge case behaviors {% #edge-case-behaviors %} +- If a nested child document is included and **no child document ID** is provided, the child document will be given a unique ID. +- If a nested child document is included and **no conflicting child document ID** exists, the child document will be **created**. +- If a nested child document is included and the **child document ID already exists**, the child document will be **updated**. + +{% /tabsitem %} +{% tabsitem #reference title="Reference" %} +If the child documents are already present in the related collection, you can create the parent and **reference the child documents** using their IDs. +Here's an example connecting reviews to a movie. +{% multicode %} +```js +const { Client, ID, DocumentsDB } = require('node-appwrite'); + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + .setProject(''); // Your project ID + +await documentsDB.createDocument({ + databaseId: 'marvel', + collectionId: 'movies', + documentId: ID.unique(), + data: { + title: 'Spiderman', + year: 2002, + reviews: [ + '', + '' +}); + } +) +``` + +```dart +import 'package:appwrite/appwrite.dart'; + +final client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + .setProject(''); // Your project ID + +final documentsDB = DocumentsDB(client); + +await documentsDB.createDocument( + databaseId: 'marvel', + collectionId: 'movies', + documentId: ID.unique(), + data: { + 'title': 'Spiderman', + 'year': 2002, + 'reviews': [ + '', + '' + ] + }, +) +``` + +```swift +import Appwrite + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + +let documentsDB = DocumentsDB(client: client) + +documentsDB.createDocument( + databaseId: "marvel", + collectionId: "movies", + documentId: ID.unique(), + data: [ + "title": "Spiderman", + "year": 2002, + "reviews": [ + "", + "" + ] + ] +) +``` + +```kotlin +import io.appwrite.Client +import io.appwrite.services.DocumentsDB +import io.appwrite.ID + +val client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + +val documentsDB = DocumentsDB(client) + +documentsDB.createDocument( + databaseId = "marvel", + collectionId = "movies", + documentId = ID.unique(), + data = mapOf( + "title" to "Spiderman", + "year" to 2002, + "reviews" to listOf( + "", + "" + ) + ) +) +``` +{% /multicode %} +{% /tabsitem %} +{% /tabs %} + +# Queries {% #queries %} + +You can use filter queries directly against relationship attributes using dot notation. This lets you filter documents based on the values of their related documents, such as filtering posts by an author's name or filtering orders by a product's category. + +Use the format `relationshipKey.field` to reference fields on related documents. + +{% multicode %} +```js +const { Client, DocumentsDB, Query } = require('node-appwrite'); + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + .setProject(''); // Your project ID + +const documentsDB = new DocumentsDB(client); + +await documentsDB.listDocuments({ + databaseId: 'marvel', + collectionId: 'movies', + queries: [ + Query.equal('reviews.author', ['Bob']) + ], +}); +``` + +```dart +import 'package:appwrite/appwrite.dart'; + +final client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + +final documentsDB = DocumentsDB(client); + +await documentsDB.listDocuments( + databaseId: 'marvel', + collectionId: 'movies', + queries: [ + Query.equal('reviews.author', ['Bob']), + ], +); +``` + +```swift +import Appwrite + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + +let documentsDB = DocumentsDB(client) + +documentsDB.listDocuments( + databaseId: "marvel", + collectionId: "movies", + queries: [ + Query.equal("reviews.author", value: ["Bob"]) + ] +) +``` + +```kotlin +import io.appwrite.Client +import io.appwrite.services.DocumentsDB +import io.appwrite.Query + +val client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + +val documentsDB = DocumentsDB(client) + +documentsDB.listDocuments( + databaseId = "marvel", + collectionId = "movies", + queries = listOf( + Query.equal("reviews.author", listOf("Bob")) + ) +) +``` +{% /multicode %} + +All filter queries are supported on relationship fields, including `equal`, `notEqual`, `greaterThan`, `lessThan`, `between`, `contains`, and other [comparison operators](/docs/products/databases/queries#comparison). + +{% arrow_link href="/docs/products/databases/queries#relationship-select" %} +Learn how to select and load relationship data +{% /arrow_link %} + +# Update relationships {% #update %} +Relationships can be updated by updating the relationship attribute. + +{% multicode %} +```js +const { Client, DocumentsDB } = require('node-appwrite'); + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + .setProject(''); // Your project ID + +const documentsDB = new DocumentsDB(client); + +await documentsDB.updateDocument({ + databaseId: 'marvel', + collectionId: 'movies', + documentId: 'spiderman', + data: { + title: 'Spiderman', + year: 2002, + reviews: [ + 'review4', + 'review5' + ] + } +}); +``` + +```dart +import 'package:appwrite/appwrite.dart'; + +final client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + +final documentsDB = DocumentsDB(client); + +await documentsDB.updateDocument( + databaseId: 'marvel', + collectionId: 'movies', + documentId: 'spiderman', + data: { + 'title': 'Spiderman', + 'year': 2002, + 'reviews': [ + 'review4', + 'review5' + ] + }, +); +``` + +```swift +import Appwrite + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + +let documentsDB = DocumentsDB(client: client) + +documentsDB.updateDocument( + databaseId: "marvel", + collectionId: "movies", + documentId: "spiderman", + data: [ + "title": "Spiderman", + "year": 2002, + "reviews": [ + "review4", + "review5" + ] + ] +) +``` + +```kotlin +import io.appwrite.Client +import io.appwrite.services.DocumentsDB + +val client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + +val documentsDB = DocumentsDB(client) + +documentsDB.updateDocument( + databaseId = "marvel", + collectionId = "movies", + documentId = "spiderman", + data = mapOf( + "title" to "Spiderman", + "year" to 2002, + "reviews" to listOf( + "review4", + "review5" + ) + ) +) +``` + +{% /multicode %} + +# Delete relationships {% #delete %} +## Unlink relationships, retain documents {% #unlink %} + +If you need to unlink documents in a relationship but retain the documents, you can do this by **updating the relationship attribute** and removing the ID of the related document. + +If a document can be related to **only one document**, you can delete the relationship by setting the relationship attribute to `null`. + +If a document can be related to **more than one document**, you can delete the relationship by setting the relationship attribute to an empty list. + +## Delete relationships and documents {% #delete-both %} + +If you need to delete the documents as well as unlink the relationship, the approach depends on the [on-delete behavior](#on-delete) of a relationship. + +If the on-delete behavior is **restrict**, the link between the documents needs to be deleted first before the documents can be deleted **individually**. + +If the on-delete behavior is **set null**, deleting a document will leave related documents in place with their relationship attribute **set to null**. If you wish to also delete related documents, they must be deleted **individually**. + +If the on-delete behavior is **cascade**, deleting the parent documents also deletes **related child documents**, except for many-to-one relationships. In many-to-one relationships, there are multiple parent documents related to a single child document, and when the child document is deleted, the parents are deleted in cascade. + +{% multicode %} +```js +const { Client, DocumentsDB } = require('node-appwrite'); + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + .setProject(''); // Your project ID + +const documentsDB = new DocumentsDB(client); + +await documentsDB.deleteDocument({ + databaseId: 'marvel', + collectionId: 'movies', + documentId: 'spiderman' +}); +``` + +```dart +import 'package:appwrite/appwrite.dart'; + +final client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + +final documentsDB = DocumentsDB(client); + +await documentsDB.deleteDocument( + databaseId: 'marvel', + collectionId: 'movies', + documentId: 'spiderman' +); +``` + +```swift +import Appwrite + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + +let documentsDB = DocumentsDB(client: client) + +documentsDB.deleteDocument( + databaseId: "marvel", + collectionId: "movies", + documentId: "spiderman" +) +``` + +```kotlin +import io.appwrite.Client +import io.appwrite.services.DocumentsDB + +val client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + +val documentsDB = DocumentsDB(client) + +documentsDB.deleteDocument( + databaseId = "marvel", + collectionId = "movies", + documentId = "spiderman" +) +``` +{% /multicode %} + +# Permissions {% #permissions %} + +To access documents in a relationship, you must have permission to access both the parent and child documents. + +When creating both the parent and child documents, the child document will **inherit permissions** from its parent. + +You can also provide explicit permissions to the child document if they should be **different from their parent**. + +{% multicode %} +```js +const { Client, ID, DocumentsDB } = require('node-appwrite'); + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + .setProject(''); // Your project ID + +const documentsDB = new DocumentsDB(client); + +await documentsDB.createDocument({ + databaseId: 'marvel', + collectionId: 'movies', + documentId: ID.unique(), + data: { + title: 'Spiderman', + year: 2002, + reviews: [ + { + author: 'Bob', + text: 'Great movie!', + $permissions: [ + Permission.read(Role.any()) + ] + }, + ] + } +}); +``` +```dart +import 'package:appwrite/appwrite.dart'; + +final client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + +final documentsDB = DocumentsDB(client); + +await documentsDB.createDocument( + databaseId: 'marvel', + collectionId: 'movies', + documentId: ID.unique(), + data: { + 'title': 'Spiderman', + 'year': 2002, + 'reviews': [ + { + 'author': 'Bob', + 'text': 'Great movie!', + '\$permissions': [ + Permission.read(Role.any()) + ] + }, + ] + }, +); +``` +```swift +import Appwrite + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + +let documentsDB = DocumentsDB(client: client) + +documentsDB.createDocument( + databaseId: "marvel", + collectionId: "movies", + documentId: ID.unique(), + data: [ + "title": "Spiderman", + "year": 2002, + "reviews": [ + [ + "author": "Bob", + "text": "Great movie!", + "$permissions": [ + Permission.read(Role.any()) + ] + ], + ] + ] +); +``` +```kotlin +import Appwrite + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + +let documentsDB = DocumentsDB(client: client) + +documentsDB.createDocument( + databaseId: "marvel", + collectionId: "movies", + documentId: ID.unique(), + data: [ + "title": "Spiderman", + "year": 2002, + "reviews": [ + [ + "author": "Bob", + "text": "Great movie!", + "$permissions": [ + Permission.read(Role.any()) + ] + ], + ] + ] +); +``` +{% /multicode %} + +When creating, updating, or deleting in a relationship, you must have permission to access all documents referenced. +If the user does not have read permission to any document, an exception will be thrown. + +# Limitations {% #limitations %} + +Relationships can be nested between collections, but are restricted to a **max depth of three levels**. +Relationship attribute key, type, and directionality can't be updated. +On-delete behavior is the only option that can be updated for relationship attributes. diff --git a/src/routes/docs/products/databases/timestamp-overrides/+page.markdoc b/src/routes/docs/products/databases/documentsdb/timestamp-overrides/+page.markdoc similarity index 87% rename from src/routes/docs/products/databases/timestamp-overrides/+page.markdoc rename to src/routes/docs/products/databases/documentsdb/timestamp-overrides/+page.markdoc index 733c344196..8924d90ec1 100644 --- a/src/routes/docs/products/databases/timestamp-overrides/+page.markdoc +++ b/src/routes/docs/products/databases/documentsdb/timestamp-overrides/+page.markdoc @@ -31,32 +31,32 @@ const client = new sdk.Client() .setProject('') .setKey(''); -const databases = new sdk.Databases(client); +const documentsDB = new sdk.DocumentsDB(client); -await databases.createDocument( - '', - '', - sdk.ID.unique(), - { +await documentsDB.createDocument({ + databaseId: '', + collectionId: '', + documentId: sdk.ID.unique(), + data: { '$createdAt': new Date('2025-08-10T12:34:56.000Z').toISOString(), '$updatedAt': new Date('2025-08-10T12:34:56.000Z').toISOString(), // ...your attributes } -); +}); ``` ```server-php use Appwrite\Client; use Appwrite\ID; -use Appwrite\Services\Databases; +use Appwrite\Services\DocumentsDB; $client = (new Client()) ->setEndpoint('https://.cloud.appwrite.io/v1') ->setProject('') ->setKey(''); -$databases = new Databases($client); +$documentsDB = new DocumentsDB($client); -$databases->createDocument( +$documentsDB->createDocument( databaseId: '', collectionId: '', documentId: '', @@ -76,7 +76,7 @@ let client = Client() .setProject("") .setKey("") -let databases = Databases(client) +let documentsDB = DocumentsDB(client) let isoFormatter = ISO8601DateFormatter() isoFormatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds] @@ -85,7 +85,7 @@ let createdAt = isoFormatter.string(from: customDate) let updatedAt = isoFormatter.string(from: customDate) do { - let created = try await databases.createDocument( + let created = try await documentsDB.createDocument( databaseId: "", collectionId: "", documentId: "", @@ -102,7 +102,7 @@ do { ``` ```server-python from appwrite.client import Client -from appwrite.services.databases import Databases +from appwrite.services.documents_db import DocumentsDB from appwrite.id import ID from datetime import datetime, timezone @@ -111,11 +111,11 @@ client.set_endpoint('https://.cloud.appwrite.io/v1') client.set_project('') client.set_key('') -databases = Databases(client) +documents_db = DocumentsDB(client) iso = datetime(2025, 8, 10, 12, 34, 56, tzinfo=timezone.utc).isoformat() -databases.create_document( +documents_db.create_document( database_id='', collection_id='', document_id=ID.unique(), @@ -137,11 +137,11 @@ client = Client.new .set_project('') .set_key('') -databases = Databases.new(client) +documents_db = DocumentsDB.new(client) custom_date = Time.parse('2025-08-10T12:34:56.000Z').iso8601 -databases.create_document( +documents_db.create_document( database_id: '', collection_id: '', document_id: ID.unique(), @@ -162,11 +162,11 @@ Client client = new Client() .SetProject("") .SetKey(""); -Databases databases = new Databases(client); +DocumentsDB documentsDB = new DocumentsDB(client); string customDate = DateTimeOffset.Parse("2025-08-10T12:34:56.000Z").ToString("O"); -await databases.CreateDocument( +await documentsDB.CreateDocument( databaseId: "", collectionId: "", documentId: ID.Unique(), @@ -186,11 +186,11 @@ Client client = Client() .setProject('') .setKey(''); -Databases databases = Databases(client); +DocumentsDB documentsDB = DocumentsDB(client); String customDate = DateTime.parse('2025-08-10T12:34:56.000Z').toIso8601String(); -await databases.createDocument( +await documentsDB.createDocument( databaseId: '', collectionId: '', documentId: ID.unique(), @@ -209,18 +209,18 @@ When updating documents, you can also set a custom `$updatedAt` timestamp: {% multicode %} ```server-nodejs -await databases.updateDocument( - '', - '', - '', - { +await documentsDB.updateDocument({ + databaseId: '', + collectionId: '', + documentId: '', + data: { '$updatedAt': new Date('2025-08-10T12:34:56.000Z').toISOString(), // ...your attributes } -); +}); ``` ```server-php -$databases->updateDocument( +$documentsDB->updateDocument( databaseId: '', collectionId: '', documentId: '', @@ -233,7 +233,7 @@ $databases->updateDocument( ```server-python from datetime import datetime, timezone -databases.update_document( +documents_db.update_document( database_id='', collection_id='', document_id='', @@ -252,14 +252,14 @@ let client = Client() .setProject("") .setKey("") -let databases = Databases(client) +let documentsDB = DocumentsDB(client) let isoFormatter = ISO8601DateFormatter() isoFormatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds] let updatedAt = isoFormatter.string(from: isoFormatter.date(from: "") ?? Date()) do { - let updated = try await databases.updateDocument( + let updated = try await documentsDB.updateDocument( databaseId: "", collectionId: "", documentId: "", @@ -283,11 +283,11 @@ client = Client.new .set_project('') .set_key('') -databases = Databases.new(client) +documents_db = DocumentsDB.new(client) custom_date = Time.parse('').iso8601 -databases.update_document( +documents_db.update_document( database_id: '', collection_id: '', document_id: '', @@ -307,11 +307,11 @@ Client client = new Client() .SetProject("") .SetKey(""); -Databases databases = new Databases(client); +DocumentsDB documentsDB = new DocumentsDB(client); string customDate = DateTimeOffset.Parse("").ToString("O"); -await databases.UpdateDocument( +await documentsDB.UpdateDocument( databaseId: "", collectionId: "", documentId: "", @@ -330,11 +330,11 @@ Client client = Client() .setProject('') .setKey(''); -Databases databases = Databases(client); +DocumentsDB documentsDB = DocumentsDB(client); String customDate = DateTime.parse('').toIso8601String(); -await databases.updateDocument( +await documentsDB.updateDocument( databaseId: '', collectionId: '', documentId: '', @@ -354,10 +354,10 @@ Custom timestamps also work with bulk operations, allowing you to set different {% multicode %} ```server-nodejs -await databases.createDocuments( - '', - '', - [ +await documentsDB.createDocuments({ + databaseId: '', + collectionId: '', + documents: [ { '$id': sdk.ID.unique(), '$createdAt': new Date('2024-01-01T00:00:00.000Z').toISOString(), @@ -371,10 +371,10 @@ await databases.createDocuments( // ...your attributes } ] -); +}); ``` ```server-python -databases.create_documents( +documents_db.create_documents( database_id='', collection_id='', documents=[ @@ -396,16 +396,16 @@ databases.create_documents( ```server-php use Appwrite\Client; use Appwrite\ID; -use Appwrite\Services\Databases; +use Appwrite\Services\DocumentsDB; $client = (new Client()) ->setEndpoint('https://.cloud.appwrite.io/v1') ->setProject('') ->setKey(''); -$databases = new Databases($client); +$documentsDB = new DocumentsDB($client); -$databases->createDocuments( +$documentsDB->createDocuments( databaseId: '', collectionId: '', documents: [ @@ -433,7 +433,7 @@ let client = Client() .setProject("") .setKey("") -let databases = Databases(client) +let documentsDB = DocumentsDB(client) let isoFormatter = ISO8601DateFormatter() isoFormatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds] @@ -442,7 +442,7 @@ let first = isoFormatter.string(from: isoFormatter.date(from: "") ? let second = isoFormatter.string(from: isoFormatter.date(from: "") ?? Date()) do { - let bulkCreated = try await databases.createDocuments( + let bulkCreated = try await documentsDB.createDocuments( databaseId: "", collectionId: "", documents: [ @@ -475,12 +475,12 @@ client = Client.new .set_project('') .set_key('') -databases = Databases.new(client) +documents_db = DocumentsDB.new(client) first = Time.parse('').iso8601 second = Time.parse('').iso8601 -databases.create_documents( +documents_db.create_documents( database_id: '', collection_id: '', documents: [ @@ -509,12 +509,12 @@ Client client = new Client() .SetProject("") .SetKey(""); -Databases databases = new Databases(client); +DocumentsDB documentsDB = new DocumentsDB(client); string first = DateTimeOffset.Parse("").ToString("O"); string second = DateTimeOffset.Parse("").ToString("O"); -await databases.CreateDocuments( +await documentsDB.CreateDocuments( databaseId: "", collectionId: "", documents: new List @@ -544,12 +544,12 @@ Client client = Client() .setProject('') .setKey(''); -Databases databases = Databases(client); +DocumentsDB documentsDB = DocumentsDB(client); String first = DateTime.parse('').toIso8601String(); String second = DateTime.parse('').toIso8601String(); -await databases.createDocuments( +await documentsDB.createDocuments( databaseId: '', collectionId: '', documents: [ @@ -574,10 +574,10 @@ await databases.createDocuments( {% multicode %} ```server-nodejs -await databases.upsertDocuments( - '', - '', - [ +await documentsDB.upsertDocuments({ + databaseId: '', + collectionId: '', + documents: [ { '$id': '', '$createdAt': new Date('2024-01-01T00:00:00.000Z').toISOString(), @@ -585,10 +585,10 @@ await databases.upsertDocuments( // ...your attributes } ] -); +}); ``` ```server-python -databases.upsert_documents( +documents_db.upsert_documents( database_id='', collection_id='', documents=[ @@ -604,16 +604,16 @@ databases.upsert_documents( ```server-php use Appwrite\Client; use Appwrite\ID; -use Appwrite\Services\Databases; +use Appwrite\Services\DocumentsDB; $client = (new Client()) ->setEndpoint('https://.cloud.appwrite.io/v1') ->setProject('') ->setKey(''); -$databases = new Databases($client); +$documentsDB = new DocumentsDB($client); -$databases->upsertDocuments( +$documentsDB->upsertDocuments( databaseId: '', collectionId: '', documents: [ @@ -635,7 +635,7 @@ let client = Client() .setProject("") .setKey("") -let databases = Databases(client) +let documentsDB = DocumentsDB(client) let isoFormatter = ISO8601DateFormatter() isoFormatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds] @@ -643,7 +643,7 @@ let createdAt = isoFormatter.string(from: isoFormatter.date(from: " let updatedAt = isoFormatter.string(from: isoFormatter.date(from: "") ?? Date()) do { - let bulkUpserted = try await databases.upsertDocuments( + let bulkUpserted = try await documentsDB.upsertDocuments( databaseId: "", collectionId: "", documents: [ @@ -671,11 +671,11 @@ client = Client.new .set_project('') .set_key('') -databases = Databases.new(client) +documents_db = DocumentsDB.new(client) custom_date = Time.parse('').iso8601 -databases.upsert_documents( +documents_db.upsert_documents( database_id: '', collection_id: '', documents: [ @@ -698,12 +698,12 @@ Client client = new Client() .SetProject("") .SetKey(""); -Databases databases = new Databases(client); +DocumentsDB documentsDB = new DocumentsDB(client); string createdAt = DateTimeOffset.Parse("").ToString("O"); string updatedAt = DateTimeOffset.Parse("").ToString("O"); -await databases.UpsertDocuments( +await documentsDB.UpsertDocuments( databaseId: "", collectionId: "", documents: new List @@ -726,12 +726,12 @@ Client client = Client() .setProject('') .setKey(''); -Databases databases = Databases(client); +DocumentsDB documentsDB = DocumentsDB(client); String createdAt = DateTime.parse('').toIso8601String(); String updatedAt = DateTime.parse('').toIso8601String(); -await databases.upsertDocuments( +await documentsDB.upsertDocuments( databaseId: '', collectionId: '', documents: [ @@ -756,20 +756,20 @@ creation and modification times: {% multicode %} ```server-nodejs -await databases.createDocument( - '', - 'blog_posts', - sdk.ID.unique(), - { +await documentsDB.createDocument({ + databaseId: '', + collectionId: 'blog_posts', + documentId: sdk.ID.unique(), + data: { '$createdAt': '', '$updatedAt': '', title: '', content: '<CONTENT>' } -) +}) ``` ```server-php -$databases->createDocument( +$documentsDB->createDocument( databaseId: '<DATABASE_ID>', collectionId: 'blog_posts', documentId: ID::unique(), @@ -782,7 +782,7 @@ $databases->createDocument( ); ``` ```server-swift -let _ = try await databases.createDocument( +let _ = try await documentsDB.createDocument( databaseId: "<DATABASE_ID>", collectionId: "blog_posts", documentId: ID.unique(), @@ -795,7 +795,7 @@ let _ = try await databases.createDocument( ) ``` ```server-python -databases.create_document( +documents_db.create_document( database_id='<DATABASE_ID>', collection_id='blog_posts', document_id=ID.unique(), @@ -808,7 +808,7 @@ databases.create_document( ) ``` ```server-ruby -databases.create_document( +documents_db.create_document( database_id: '<DATABASE_ID>', collection_id: 'blog_posts', document_id: ID.unique(), @@ -821,7 +821,7 @@ databases.create_document( ) ``` ```server-dotnet -await databases.CreateDocument( +await documentsDB.CreateDocument( databaseId: "<DATABASE_ID>", collectionId: "blog_posts", documentId: ID.Unique(), @@ -835,7 +835,7 @@ await databases.CreateDocument( ); ``` ```server-dart -await databases.createDocument( +await documentsDB.createDocument( databaseId: '<DATABASE_ID>', collectionId: 'blog_posts', documentId: ID.unique(), @@ -854,20 +854,20 @@ For historical data entry or when creating records that represent past events: {% multicode %} ```server-nodejs -await databases.createDocument( - '<DATABASE_ID>', - 'transactions', - sdk.ID.unique(), - { +await documentsDB.createDocument({ + databaseId: '<DATABASE_ID>', + collectionId: 'transactions', + documentId: sdk.ID.unique(), + data: { '$createdAt': '2023-12-31T23:59:59.000Z', '$updatedAt': '2023-12-31T23:59:59.000Z', amount: 1000, type: 'year-end-bonus' } -) +}) ``` ```server-php -$databases->createDocument( +$documentsDB->createDocument( databaseId: '<DATABASE_ID>', collectionId: 'transactions', documentId: ID::unique(), @@ -880,7 +880,7 @@ $databases->createDocument( ); ``` ```server-swift -let _ = try await databases.createDocument( +let _ = try await documentsDB.createDocument( databaseId: "<DATABASE_ID>", collectionId: "transactions", documentId: ID.unique(), @@ -893,7 +893,7 @@ let _ = try await databases.createDocument( ) ``` ```server-python -databases.create_document( +documents_db.create_document( database_id='<DATABASE_ID>', collection_id='transactions', document_id=ID.unique(), @@ -906,7 +906,7 @@ databases.create_document( ) ``` ```server-ruby -databases.create_document( +documents_db.create_document( database_id: '<DATABASE_ID>', collection_id: 'transactions', document_id: ID.unique(), @@ -919,7 +919,7 @@ databases.create_document( ) ``` ```server-dotnet -await databases.CreateDocument( +await documentsDB.CreateDocument( databaseId: "<DATABASE_ID>", collectionId: "transactions", documentId: ID.Unique(), @@ -933,7 +933,7 @@ await databases.CreateDocument( ); ``` ```server-dart -await databases.createDocument( +await documentsDB.createDocument( databaseId: '<DATABASE_ID>', collectionId: 'transactions', documentId: ID.unique(), @@ -952,18 +952,18 @@ When synchronizing data between systems while maintaining timestamp consistency: {% multicode %} ```server-nodejs -await databases.upsertDocument( - '<DATABASE_ID>', - 'users', - '<DOCUMENT_ID_OR_NEW_ID>', - { +await documentsDB.upsertDocument({ + databaseId: '<DATABASE_ID>', + collectionId: 'users', + documentId: '<DOCUMENT_ID_OR_NEW_ID>', + data: { '$updatedAt': '<EXTERNAL_LAST_MODIFIED_ISO>', profile: '<PROFILE_DATA>' } -) +}) ``` ```server-php -$databases->upsertDocument( +$documentsDB->upsertDocument( databaseId: '<DATABASE_ID>', collectionId: 'users', documentId: '<DOCUMENT_ID_OR_NEW_ID>', @@ -974,7 +974,7 @@ $databases->upsertDocument( ); ``` ```server-swift -let _ = try await databases.upsertDocument( +let _ = try await documentsDB.upsertDocument( databaseId: "<DATABASE_ID>", collectionId: "users", documentId: "<DOCUMENT_ID_OR_NEW_ID>", @@ -985,7 +985,7 @@ let _ = try await databases.upsertDocument( ) ``` ```server-python -databases.upsert_document( +documents_db.upsert_document( database_id='<DATABASE_ID>', collection_id='users', document_id='<DOCUMENT_ID_OR_NEW_ID>', @@ -996,7 +996,7 @@ databases.upsert_document( ) ``` ```server-ruby -databases.upsert_document( +documents_db.upsert_document( database_id: '<DATABASE_ID>', collection_id: 'users', document_id: '<DOCUMENT_ID_OR_NEW_ID>', @@ -1007,7 +1007,7 @@ databases.upsert_document( ) ``` ```server-dotnet -await databases.UpsertDocument( +await documentsDB.UpsertDocument( databaseId: "<DATABASE_ID>", collectionId: "users", documentId: "<DOCUMENT_ID_OR_NEW_ID>", @@ -1019,7 +1019,7 @@ await databases.UpsertDocument( ); ``` ```server-dart -await databases.upsertDocument( +await documentsDB.upsertDocument( databaseId: '<DATABASE_ID>', collectionId: 'users', documentId: '<DOCUMENT_ID_OR_NEW_ID>', @@ -1035,4 +1035,3 @@ await databases.upsertDocument( - Values must be valid ISO 8601 date-time strings (UTC recommended). Using `toISOString()` (JavaScript) or `datetime.isoformat()` (Python) is a good default. - You can set either or both attributes as needed. If omitted, Appwrite sets them automatically. {% /info %} - diff --git a/src/routes/docs/products/databases/documentsdb/transactions/+page.markdoc b/src/routes/docs/products/databases/documentsdb/transactions/+page.markdoc new file mode 100644 index 0000000000..4d78bf5276 --- /dev/null +++ b/src/routes/docs/products/databases/documentsdb/transactions/+page.markdoc @@ -0,0 +1,1149 @@ +--- +layout: article +title: Transactions +description: Stage multiple database operations and commit them atomically. Group changes across databases and collections with ordering, isolation, and conflict detection. +--- + +Transactions let you stage multiple database operations and apply them together, atomically. Use transactions to keep related changes consistent, even when they span multiple databases and collections. + +# How transactions work {% #how-transactions-work %} + +1. Call the [createTransaction](#create-a-transaction) method to create a transaction. This will return a transaction model, including its ID. +2. Stage operations by passing the `transactionId` parameter to supported document, bulk, and atomic numeric methods. You can stage many operations at once with the [createOperations](#create-operations) method. +3. Call the [updateTransaction](#update-transaction) method to commit or roll back. + +On commit, Appwrite replays all staged logs in order inside a real database transaction. Staged operations see earlier staged changes (read your own writes). If any affected document changed outside your transaction, the commit fails with a conflict. + +{% info title="Scope and limitations" %} +You can stage operations across any database and collection within the same transaction. Schema operations (for example, adding or removing attributes) are not included in transactions. +{% /info %} + +# Limits {% #limits %} + +The maximum number of operations you can stage per transaction depends on your plan: + +| Plan | Max operations per transaction | +|------|-------------------------------| +| Free | 100 | +| Pro | 1,000 | +| Scale | 2,500 | + +# Create a transaction {% #create-a-transaction %} + +Call the `createTransaction` method to begin. It returns a transaction model that includes `$id`. Pass this ID as `transactionId` to subsequent operations. + +{% multicode %} +```client-web +import { Client, DocumentsDB } from 'appwrite'; + +const client = new Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>'); + +const documentsDB = new DocumentsDB(client); + +const tx = await documentsDB.createTransaction(); +// tx.$id is your transactionId +``` +```client-react-native +import { Client, DocumentsDB } from 'react-native-appwrite'; + +const client = new Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>'); + +const documentsDB = new DocumentsDB(client); + +const tx = await documentsDB.createTransaction(); +// tx.$id is your transactionId +``` +```client-flutter +import 'package:appwrite/appwrite.dart'; + +final client = Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>'); + +final documentsDB = DocumentsDB(client); + +final tx = await documentsDB.createTransaction(); +// tx.$id is your transactionId +``` +```client-apple +import Appwrite + +let client = Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") + .setProject("<PROJECT_ID>") + +let documentsDB = DocumentsDB(client) + +let tx = try await documentsDB.createTransaction() +// tx.$id is your transactionId +``` +```client-android-kotlin +import io.appwrite.Client +import io.appwrite.services.DocumentsDB + +val client = Client(applicationContext) + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") + .setProject("<PROJECT_ID>") + +val documentsDB = DocumentsDB(client) + +val tx = documentsDB.createTransaction() +// tx.$id is your transactionId +``` +```client-android-java +import io.appwrite.Client; +import io.appwrite.services.DocumentsDB; + +Client client = new Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") + .setProject("<PROJECT_ID>"); + +DocumentsDB documentsDB = new DocumentsDB(client); + +// Create a transaction (asynchronous) +documentsDB.createTransaction(new CoroutineCallback<>((tx, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + System.out.println(tx); +})); +``` +```server-nodejs +const sdk = require('node-appwrite'); + +const client = new sdk.Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>') + .setKey('<API_KEY>'); + +const documentsDB = new sdk.DocumentsDB(client); + +const tx = await documentsDB.createTransaction(); +// tx.$id is your transactionId +``` +```server-deno +import * as sdk from 'npm:node-appwrite'; + +const client = new sdk.Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>') + .setKey('<API_KEY>'); + +const documentsDB = new sdk.DocumentsDB(client); + +const tx = await documentsDB.createTransaction(); +// tx.$id is your transactionId +``` +```server-python +from appwrite.client import Client +from appwrite.services.documents_db import DocumentsDB + +client = Client() +client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1') +client.set_project('<PROJECT_ID>') +client.set_key('<API_KEY>') + +documentsDB = DocumentsDB(client) + +tx = documentsDB.create_transaction() +# tx.$id is your transactionId +``` +```server-php +<?php + +use Appwrite\Client; +use Appwrite\Services\DocumentsDB; + +$client = new Client(); + +$client + ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + ->setProject('<PROJECT_ID>') + ->setKey('<API_KEY>') +; + +$documentsDB = new DocumentsDB($client); + +$tx = $documentsDB->createTransaction(); +// $tx->\$id is your transactionId +``` +```server-ruby +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') + .set_project('<PROJECT_ID>') + .set_key('<API_KEY>') + +documentsDB = DocumentsDB.new(client) + +tx = documentsDB.create_transaction +# tx['$id'] is your transactionId +``` +```server-dotnet +using Appwrite; +using Appwrite.Services; + +var client = new Client() + .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1") + .SetProject("<PROJECT_ID>") + .SetKey("<API_KEY>"); + +var documentsDB = new DocumentsDB(client); + +var tx = await documentsDB.CreateTransaction(); +// tx.$id is your transactionId +``` +```server-dart +import 'package:dart_appwrite/dart_appwrite.dart'; + +void main() async { + Client client = Client(); + DocumentsDB documentsDB = DocumentsDB(client); + + client + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>') + .setKey('<API_KEY>'); + + final tx = await documentsDB.createTransaction(); + // tx contains the transaction ID +} +``` +```server-go +package main + +import ( + "log" + "github.com/appwrite/sdk-for-go/appwrite" +) + +func main() { + client := appwrite.NewClient( + appwrite.WithEndpoint("https://<REGION>.cloud.appwrite.io/v1"), + appwrite.WithProject("<PROJECT_ID>"), + appwrite.WithKey("<API_KEY>"), + ) + + documentsDB := appwrite.NewDocumentsDB(client) + + tx, err := documentsDB.CreateTransaction() + if err != nil { log.Fatal(err) } + _ = tx +} +``` +```server-swift +import Appwrite + +let client = Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") + .setProject("<PROJECT_ID>") + .setKey("<API_KEY>") + +let documentsDB = DocumentsDB(client) + +let tx = try await documentsDB.createTransaction() +// tx.$id is your transactionId +``` +```server-kotlin +import io.appwrite.Client +import io.appwrite.services.DocumentsDB + +val client = Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") + .setProject("<PROJECT_ID>") + .setKey("<API_KEY>") + +val documentsDB = DocumentsDB(client) + +val tx = documentsDB.createTransaction() +// tx.$id is your transactionId +``` +```server-java +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.DocumentsDB; + +Client client = new Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") + .setProject("<PROJECT_ID>") + .setKey("<API_KEY>"); + +DocumentsDB documentsDB = new DocumentsDB(client); + +documentsDB.createTransaction(new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + System.out.println(result); +})); +``` +{% /multicode %} + +# Stage operations {% #stage-operations %} + +Add the `transactionId` parameter to supported methods to stage them instead of immediately persisting. + +When you pass `transactionId`, Appwrite writes the operation to an internal staging area. The target collection is not modified until you commit the transaction. + +## Stage single operations {% #stage-single-operations %} + +Create, update, upsert, delete, and atomic numeric operations accept `transactionId`, as well as their bulk versions (createDocuments, updateDocuments, upsertDocuments, deleteDocuments). + +{% multicode %} +```client-web +// Create inside a transaction +await documentsDB.createDocument({ + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documentId: '<DOCUMENT_ID>', + data: { name: 'Walter' }, + transactionId: tx.$id +}); + +// Increment inside a transaction +await documentsDB.incrementDocumentAttribute({ + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documentId: '<DOCUMENT_ID>', + attribute: 'credits', + value: 1, + transactionId: tx.$id +}); +``` +```client-flutter +// Create inside a transaction +await documentsDB.createDocument( + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documentId: '<DOCUMENT_ID>', + data: { 'name': 'Walter' }, + transactionId: tx.$id +); + +// Increment inside a transaction +await documentsDB.incrementDocumentAttribute( + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documentId: '<DOCUMENT_ID>', + attribute: 'credits', + value: 1, + transactionId: tx.$id +); +``` +```client-apple +// Create inside a transaction +try await documentsDB.createDocument( + databaseId: "<DATABASE_ID>", + collectionId: "<COLLECTION_ID>", + documentId: "<DOCUMENT_ID>", + data: ["name": "Walter"], + transactionId: tx.$id +) + +// Increment inside a transaction +try await documentsDB.incrementDocumentAttribute( + databaseId: "<DATABASE_ID>", + collectionId: "<COLLECTION_ID>", + documentId: "<DOCUMENT_ID>", + attribute: "credits", + value: 1, + transactionId: tx.$id +) +``` +```server-kotlin +// Create inside a transaction +documentsDB.createDocument( + databaseId = "<DATABASE_ID>", + collectionId = "<COLLECTION_ID>", + documentId = "<DOCUMENT_ID>", + data = mapOf("name" to "Walter"), + transactionId = tx.$id +) + +// Increment inside a transaction +documentsDB.incrementDocumentAttribute( + databaseId = "<DATABASE_ID>", + collectionId = "<COLLECTION_ID>", + documentId = "<DOCUMENT_ID>", + attribute = "credits", + value = 1, + transactionId = tx.$id +) +``` +```server-java +// Create inside a transaction (asynchronous) +documentsDB.createDocument( + "<DATABASE_ID>", + "<COLLECTION_ID>", + "<DOCUMENT_ID>", + Map.of("name", "Walter"), + "<TRANSACTION_ID>", + new CoroutineCallback<>((document, error) -> { + if (error != null) { + error.printStackTrace(); + return null; + } + System.out.println(document); + return null; + }) +); + +// Increment inside a transaction (asynchronous) +documentsDB.incrementDocumentAttribute( + "<DATABASE_ID>", + "<COLLECTION_ID>", + "<DOCUMENT_ID>", + "credits", + 1, + "<TRANSACTION_ID>", + new CoroutineCallback<>((document, error) -> { + if (error != null) { + error.printStackTrace(); + return null; + } + System.out.println(document); + return null; + }) +); +``` +```client-react-native +// Create inside a transaction +await documentsDB.createDocument({ + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documentId: '<DOCUMENT_ID>', + data: { name: 'Walter' }, + transactionId: tx.$id +}); + +// Increment inside a transaction +await documentsDB.incrementDocumentAttribute({ + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documentId: '<DOCUMENT_ID>', + attribute: 'credits', + value: 1, + transactionId: tx.$id +}); +``` +```server-nodejs +// Update inside a transaction +await documentsDB.updateDocument({ + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documentId: '<DOCUMENT_ID>', + data: { plan: 'pro' }, + transactionId: tx.$id +}); + +// Delete inside a transaction +await documentsDB.deleteDocument({ + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documentId: '<DOCUMENT_ID>', + transactionId: tx.$id +}); +``` +```server-python +# Upsert inside a transaction +documentsDB.upsert_document( + database_id = '<DATABASE_ID>', + collection_id = '<COLLECTION_ID>', + document_id = '<DOCUMENT_ID>', + data = { 'name': 'Walter' }, + transaction_id = tx['$id'] +) + +# Decrement inside a transaction +documentsDB.decrement_document_attribute( + database_id = '<DATABASE_ID>', + collection_id = '<COLLECTION_ID>', + document_id = '<DOCUMENT_ID>', + attribute = 'credits', + value = 1, + transaction_id = tx['$id'] +) +``` +```server-php +// Create inside a transaction +$documentsDB->createDocument( + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documentId: '<DOCUMENT_ID>', + data: ['name' => 'Walter'], + transactionId: $tx['$id'] +); + +// Increment inside a transaction +$documentsDB->incrementDocumentAttribute( + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documentId: '<DOCUMENT_ID>', + attribute: 'credits', + value: 1, + transactionId: $tx['$id'] +); +``` +```server-ruby +# Create inside a transaction +documentsDB.create_document( + database_id: '<DATABASE_ID>', + collection_id: '<COLLECTION_ID>', + document_id: '<DOCUMENT_ID>', + data: { 'name' => 'Walter' }, + transaction_id: tx['$id'] +) + +# Increment inside a transaction +documentsDB.increment_document_attribute( + database_id: '<DATABASE_ID>', + collection_id: '<COLLECTION_ID>', + document_id: '<DOCUMENT_ID>', + attribute: 'credits', + value: 1, + transaction_id: tx['$id'] +) +``` +```server-dotnet +// Create inside a transaction +await documentsDB.CreateDocument( + databaseId: "<DATABASE_ID>", + collectionId: "<COLLECTION_ID>", + documentId: "<DOCUMENT_ID>", + data: new Dictionary<string, object> { ["name"] = "Walter" }, + transactionId: tx.Id +); + +// Increment inside a transaction +await documentsDB.IncrementDocumentAttribute( + databaseId: "<DATABASE_ID>", + collectionId: "<COLLECTION_ID>", + documentId: "<DOCUMENT_ID>", + attribute: "credits", + value: 1, + transactionId: tx.Id +); +``` +```server-dart +// Create inside a transaction +await documentsDB.createDocument( + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documentId: '<DOCUMENT_ID>', + data: { 'name': 'Walter' }, + transactionId: tx.Id +); + +// Increment inside a transaction +await documentsDB.incrementDocumentAttribute( + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documentId: '<DOCUMENT_ID>', + attribute: 'credits', + value: 1, + transactionId: tx.Id +); +``` +```server-deno +// Create inside a transaction +await documentsDB.createDocument({ + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documentId: '<DOCUMENT_ID>', + data: { name: 'Walter' }, + transactionId: tx.$id +}); + +// Increment inside a transaction +await documentsDB.incrementDocumentAttribute({ + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documentId: '<DOCUMENT_ID>', + attribute: 'credits', + value: 1, + transactionId: tx.$id +}); +``` +```server-swift +// Create inside a transaction +try await documentsDB.createDocument( + databaseId: "<DATABASE_ID>", + collectionId: "<COLLECTION_ID>", + documentId: "<DOCUMENT_ID>", + data: ["name": "Walter"], + transactionId: tx.$id +) + +// Increment inside a transaction +try await documentsDB.incrementDocumentAttribute( + databaseId: "<DATABASE_ID>", + collectionId: "<COLLECTION_ID>", + documentId: "<DOCUMENT_ID>", + attribute: "credits", + value: 1, + transactionId: tx.$id +) +``` +{% /multicode %} + +## Stage many with createOperations {% #create-operations %} + +Use the `createOperations` method to stage multiple operations across databases and collections in a single request. Provide an array of operation objects: + +```json +[ + { + "action": "create|update|upsert|increment|decrement|delete|bulkCreate|bulkUpdate|bulkUpsert|bulkDelete", + "databaseId": "<DATABASE_ID>", + "collectionId": "<COLLECTION_ID>", + "documentId": "<DOCUMENT_ID>", + "data": {} + } +] +``` + +### Provide data for each action (createOperations) {% #provide-data-for-each-action %} + +### Create, update, and upsert {% #create-update-upsert %} +Pass a raw data object. +```json +{ "name": "Walter" } +``` + +### Increment and decrement {% #increment-decrement %} +Pass a value and optionally `min`/`max` bounds. +```json +{ "value": 1, "min": 0, "max": 1000, "attribute": "<ATTRIBUTE_NAME>" } +``` + +### Bulk create and bulk upsert {% #bulk-create-upsert %} +Pass an array of raw data objects. +```json +[{ "$id": "123", "name": "Walter" }] +``` + +### Bulk update {% #bulk-update %} +Pass queries and the data to apply. +```json +{ "queries": [{"method": "equal", "attribute": "status", "values": ["draft"]}], "data": { "status": "published" } } +``` + +### Bulk delete {% #bulk-delete %} +Pass queries to select documents to delete. +```json +{ "queries": [{"method": "equal", "attribute": "archived", "values": [true]}] } +``` + +{% multicode %} +```server-nodejs +// Stage multiple operations at once +await documentsDB.createOperations({ + transactionId: tx.$id, + operations: [ + { + action: 'create', + databaseId: '<DB_A>', + collectionId: '<COLLECTION_1>', + documentId: 'u1', + data: { name: 'Walter' } + }, + { + action: 'increment', + databaseId: '<DB_B>', + collectionId: '<COLLECTION_2>', + documentId: 'u2', + data: { value: 1, min: 0, attribute: 'credits' } + } + ] +}); +``` +```server-python +documentsDB.create_operations( + transaction_id = tx['$id'], + operations = [ + { + 'action': 'create', + 'databaseId': '<DB_A>', + 'collectionId': '<COLLECTION_1>', + 'documentId': 'u1', + 'data': { 'name': 'Walter' } + }, + { + 'action': 'increment', + 'databaseId': '<DB_B>', + 'collectionId': '<COLLECTION_2>', + 'documentId': 'u2', + 'data': { 'value': 1, 'min': 0, 'attribute': 'credits' } + } + ] +) +``` +```client-web +await documentsDB.createOperations({ + transactionId: tx.$id, + operations: [ + { + action: 'create', + databaseId: '<DB_A>', + collectionId: '<COLLECTION_1>', + documentId: 'u1', + data: { name: 'Walter' } + }, + { + action: 'increment', + databaseId: '<DB_B>', + collectionId: '<COLLECTION_2>', + documentId: 'u2', + data: { value: 1, min: 0, attribute: 'credits' } + } + ] +}); +``` +```client-flutter +await documentsDB.createOperations( + transactionId: tx.$id, + operations: [ + { + 'action': 'create', + 'databaseId': '<DB_A>', + 'collectionId': '<COLLECTION_1>', + 'documentId': 'u1', + 'data': { 'name': 'Walter' } + }, + { + 'action': 'increment', + 'databaseId': '<DB_B>', + 'collectionId': '<COLLECTION_2>', + 'documentId': 'u2', + 'data': { 'value': 1, 'min': 0, 'attribute': 'credits' } + } + ], +); +``` +```client-apple +try await documentsDB.createOperations( + transactionId: tx.$id, + operations: [ + [ + "action": "create", + "databaseId": "<DB_A>", + "collectionId": "<COLLECTION_1>", + "documentId": "u1", + "data": ["name": "Walter"] + ], + [ + "action": "increment", + "databaseId": "<DB_B>", + "collectionId": "<COLLECTION_2>", + "documentId": "u2", + "data": ["value": 1, "min": 0, "attribute": "credits"] + ] + ] +) +``` +```server-kotlin +documentsDB.createOperations( + transactionId = tx.$id, + operations = listOf( + mapOf( + "action" to "create", + "databaseId" to "<DB_A>", + "collectionId" to "<COLLECTION_1>", + "documentId" to "u1", + "data" to mapOf("name" to "Walter") + ), + mapOf( + "action" to "increment", + "databaseId" to "<DB_B>", + "collectionId" to "<COLLECTION_2>", + "documentId" to "u2", + "data" to mapOf("value" to 1, "min" to 0, "attribute" to "credits") + ) + ) +) +``` +```server-java +// Stage multiple operations at once (asynchronous) +List<Map<String, Object>> operations = Arrays.asList( + Map.of( + "action", "create", + "databaseId", "<DB_A>", + "collectionId", "<COLLECTION_1>", + "documentId", "u1", + "data", Map.of("name", "Walter") + ), + Map.of( + "action", "increment", + "databaseId", "<DB_B>", + "collectionId", "<COLLECTION_2>", + "documentId", "u2", + "data", Map.of("value", 1, "min", 0, "attribute", "credits") + ) +); + +documentsDB.createOperations( + "<TRANSACTION_ID>", + operations, + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return null; + } + System.out.println(result); + return null; + }) +); +``` +```client-react-native +await documentsDB.createOperations({ + transactionId: tx.$id, + operations: [ + { + action: 'create', + databaseId: '<DB_A>', + collectionId: '<COLLECTION_1>', + documentId: 'u1', + data: { name: 'Walter' } + }, + { + action: 'increment', + databaseId: '<DB_B>', + collectionId: '<COLLECTION_2>', + documentId: 'u2', + data: { value: 1, min: 0, attribute: 'credits' } + } + ] +}); +``` +```server-deno +await documentsDB.createOperations({ + transactionId: tx.$id, + operations: [ + { + action: 'create', + databaseId: '<DB_A>', + collectionId: '<COLLECTION_1>', + documentId: 'u1', + data: { name: 'Walter' } + }, + { + action: 'increment', + databaseId: '<DB_B>', + collectionId: '<COLLECTION_2>', + documentId: 'u2', + data: { value: 1, min: 0, attribute: 'credits' } + } + ] +}); +``` +```server-php +$documentsDB->createOperations( + transactionId: $tx['$id'], + operations: [ + [ + 'action' => 'create', + 'databaseId' => '<DB_A>', + 'collectionId' => '<COLLECTION_1>', + 'documentId' => 'u1', + 'data' => [ 'name' => 'Walter' ] + ], + [ + 'action' => 'increment', + 'databaseId' => '<DB_B>', + 'collectionId' => '<COLLECTION_2>', + 'documentId' => 'u2', + 'data' => [ 'value' => 1, 'min' => 0, 'attribute' => 'credits' ] + ] + ] +); +``` +```server-ruby +documentsDB.create_operations( + transaction_id: tx['$id'], + operations: [ + { + 'action' => 'create', + 'databaseId' => '<DB_A>', + 'collectionId' => '<COLLECTION_1>', + 'documentId' => 'u1', + 'data' => { 'name' => 'Walter' } + }, + { + 'action' => 'increment', + 'databaseId' => '<DB_B>', + 'collectionId' => '<COLLECTION_2>', + 'documentId' => 'u2', + 'data' => { 'value' => 1, 'min' => 0, 'attribute' => 'credits' } + } + ] +) +``` +```server-dotnet +await documentsDB.CreateOperations( + transactionId: tx.Id, + operations: new List<Dictionary<string, object>> + { + new Dictionary<string, object> + { + ["action"] = "create", + ["databaseId"] = "<DB_A>", + ["collectionId"] = "<COLLECTION_1>", + ["documentId"] = "u1", + ["data"] = new Dictionary<string, object> { ["name"] = "Walter" } + }, + new Dictionary<string, object> + { + ["action"] = "increment", + ["databaseId"] = "<DB_B>", + ["collectionId"] = "<COLLECTION_2>", + ["documentId"] = "u2", + ["data"] = new Dictionary<string, object> { ["value"] = 1, ["min"] = 0, ["attribute"] = "credits" } + } + } +); +``` +```server-dart +await documentsDB.createOperations( + transactionId: tx.Id, + operations: [ + { + 'action': 'create', + 'databaseId': '<DB_A>', + 'collectionId': '<COLLECTION_1>', + 'documentId': 'u1', + 'data': { 'name': 'Walter' } + }, + { + 'action': 'increment', + 'databaseId': '<DB_B>', + 'collectionId': '<COLLECTION_2>', + 'documentId': 'u2', + 'data': { 'value': 1, 'min': 0, 'attribute': 'credits' } + } + ] +); +``` +```server-swift +try await documentsDB.createOperations( + transactionId: tx.$id, + operations: [ + [ + "action": "create", + "databaseId": "<DB_A>", + "collectionId": "<COLLECTION_1>", + "documentId": "u1", + "data": ["name": "Walter"] + ], + [ + "action": "increment", + "databaseId": "<DB_B>", + "collectionId": "<COLLECTION_2>", + "documentId": "u2", + "data": ["value": 1, "min": 0, "attribute": "credits"] + ] + ] +) +``` +{% /multicode %} + +# Commit or roll back {% #commit-or-rollback %} + +When you are done staging operations, call the `updateTransaction` method to finalize the transaction. + +{% multicode %} +```client-web +// Commit +await documentsDB.updateTransaction({ + transactionId: tx.$id, + commit: true +}); + +// Or roll back +await documentsDB.updateTransaction({ + transactionId: tx.$id, + rollback: true +}); +``` +```client-flutter +// Commit +await documentsDB.updateTransaction( + transactionId: tx.$id, + commit: true +); + +// Roll back +await documentsDB.updateTransaction( + transactionId: tx.$id, + rollback: true +); +``` +```client-apple +// Commit +try await documentsDB.updateTransaction( + transactionId: tx.$id, + commit: true +) + +// Roll back +try await documentsDB.updateTransaction( + transactionId: tx.$id, + rollback: true +) +``` +```server-kotlin +// Commit +documentsDB.updateTransaction( + transactionId = tx.$id, + commit = true +) + +// Roll back +documentsDB.updateTransaction( + transactionId = tx.$id, + rollback = true +) +``` +```server-java +// Commit (asynchronous) +documentsDB.updateTransaction( + "<TRANSACTION_ID>", + true, + false, + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return null; + } + System.out.println(result); + return null; + }) +); + +// Roll back (asynchronous) +documentsDB.updateTransaction( + "<TRANSACTION_ID>", + false, + true, + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return null; + } + System.out.println(result); + return null; + }) +); +``` +```client-react-native +// Commit +await documentsDB.updateTransaction({ + transactionId: tx.$id, + commit: true +}); + +// Roll back +await documentsDB.updateTransaction({ + transactionId: tx.$id, + rollback: true +}); +``` +```server-nodejs +// Commit +await documentsDB.updateTransaction({ + transactionId: tx.$id, + commit: true +}); + +// Roll back +await documentsDB.updateTransaction({ + transactionId: tx.$id, + rollback: true +}); +``` +```server-python +# Commit +documentsDB.update_transaction( + transaction_id = tx['$id'], + commit = True +) + +# Roll back +documentsDB.update_transaction( + transaction_id = tx['$id'], + rollback = True +) +``` +```server-php +// Commit +$documentsDB->updateTransaction( + transactionId: $tx['$id'], + commit: true +); + +// Roll back +$documentsDB->updateTransaction( + transactionId: $tx['$id'], + rollback: true +); +``` +```server-ruby +# Commit +documentsDB.update_transaction( + transaction_id: tx['$id'], + commit: true +) + +# Roll back +documentsDB.update_transaction( + transaction_id: tx['$id'], + rollback: true +) +``` +```server-dotnet +// Commit +await documentsDB.UpdateTransaction( + transactionId: tx.Id, + commit: true +); + +// Roll back +await documentsDB.UpdateTransaction( + transactionId: tx.Id, + rollback: true +); +``` +```server-dart +// Commit +await documentsDB.updateTransaction( + transactionId: tx.Id, + commit: true +); + +// Roll back +await documentsDB.updateTransaction( + transactionId: tx.Id, + rollback: true +); +``` +{% /multicode %} + +# Handle conflicts {% #handle-conflicts %} + +On commit, Appwrite verifies that documents affected by your transaction haven't changed externally since they were staged. If a conflicting change is detected, the commit fails with a conflict error. Resolve the conflict (for example, refetch and re-stage) and try again. + +{% info title="Best practices" %} +Keep transactions short-lived to reduce the likelihood of conflicts. Stage related updates in the order they must be applied. Prefer `createOperations` when you need to stage many changes across multiple collections. +{% /info %} + +{% arrow_link href="/docs/references" %} +Explore the API references +{% /arrow_link %} diff --git a/src/routes/docs/products/databases/documentsdb/type-generation/+page.markdoc b/src/routes/docs/products/databases/documentsdb/type-generation/+page.markdoc new file mode 100644 index 0000000000..e186b1a5ce --- /dev/null +++ b/src/routes/docs/products/databases/documentsdb/type-generation/+page.markdoc @@ -0,0 +1,570 @@ +--- +layout: article +title: Type generation +description: Generate types from your Appwrite database schema. Learn how to use the Appwrite CLI to create and manage your types effectively. +--- + +The Appwrite CLI provides a simple way to generate types based on your Appwrite database schema. This feature is particularly useful for developers who want to ensure type safety in their applications by generating type definitions that match their database collections and attributes. + +To generate types, the CLI reads the database schema from your project's `appwrite.json` file and generates type definitions for each collection. + +## Generating types + +First, ensure you have the [Appwrite CLI](/docs/tooling/command-line/installation#getting-started) installed and your project is [initialised](/docs/tooling/command-line/installation#initialization). Then, run the following command in your terminal to pull collections from your Appwrite project: + +```bash +appwrite pull collections +``` + +To generate types, you can use the Appwrite CLI command: + +```bash +appwrite types [options] <output-directory> +``` + +The following options are currently available: + +| Option | Description | +|--------|-------------| +| `--language` or `-l` | The programming language for which types can be generated. Choices include `ts`, `js`, `php`, `kotlin`, `swift`, `java`, `dart`, `auto`. The CLI will use `auto` as the default option if this option is skipped. | +| `--strict` or `-s` | Enables strict type generation. This option ensures that all the attributes follow language conventions, even if that leads to mismatches with the schema defined in your Appwrite console. | +| `--help` or `-h` | Displays help information for the command. | + +## Example usage + +Suppose you want to generate types for a collection with data on books with the following schema from your `appwrite.json` file: + +```json +{ + "projectId": "682ca9a50004cf4b330f", + "endpoint": "https://<REGION>.cloud.appwrite.io/v1", + "projectName": "Appwrite project", + "databases": [ + { + "$id": "684c678b00211ddac082", + "name": "Library", + "enabled": true + } + ], + "collections": [ + { + "$id": "684c6790002d457ee89d", + "$permissions": [], + "databaseId": "684c678b00211ddac082", + "name": "Books", + "enabled": true, + "documentSecurity": false, + "attributes": [ + { + "key": "name", + "type": "string", + "required": true, + "array": false, + "size": 255, + "default": null + }, + { + "key": "author", + "type": "string", + "required": true, + "array": false, + "size": 255, + "default": null + }, + { + "key": "release_year", + "type": "datetime", + "required": false, + "array": false, + "format": "", + "default": null + }, + { + "key": "category", + "type": "string", + "required": false, + "array": false, + "elements": [ + "fiction", + "nonfiction" + ], + "format": "enum", + "default": null + }, + { + "key": "genre", + "type": "string", + "required": false, + "array": true, + "size": 100, + "default": null + }, + { + "key": "is_checked_out", + "type": "boolean", + "required": true, + "array": false, + "default": null + } + ], + "indexes": [] + } + ] +} +``` + +Here's how you can generate types for this collection across all supported languages: + +{% tabs %} +{% tabsitem #ts title="TypeScript" %} +Run the following command in your terminal: + +```bash +appwrite types --language ts ./types +``` + +This will generate the following types in the `./types` sub-directory of your project: + +```ts +import { type Models } from 'appwrite'; + +export enum Category { + FICTION = "fiction", + NONFICTION = "nonfiction", +} + +export type Books = Models.Document & { + name: string; + author: string; + releaseYear: string | null; + category: Category | null; + genre: string[] | null; + isCheckedOut: boolean; +} +``` +{% /tabsitem %} + +{% tabsitem #js title="JavaScript" %} +Run the following command in your terminal: + +```bash +appwrite types --language js ./types +``` + +This will generate the following types in the `./types` sub-directory of your project: + +```js +/** + * @typedef {import('appwrite').Models.Document} Document + */ + + +/** + * @typedef {Object} Books + * @property {string} name + * @property {string} author + * @property {string|null|undefined} releaseYear + * @property {"fiction"|"nonfiction"|null|undefined} category + * @property {string[]|null|undefined} genre + * @property {boolean} isCheckedOut + */ +``` +{% /tabsitem %} + +{% tabsitem #java title="Java" %} +Run the following command in your terminal: + +```bash +appwrite types --language java ./types +``` + +This will generate the following types in the `./types` sub-directory of your project: + +```java +package io.appwrite.models; + +import java.util.*; +public class Books { + + public enum Category { + fiction, + nonfiction; + } + + private String name; + private String author; + private String releaseYear; + private Category category; + private List<String> genre; + private boolean isCheckedOut; + + public Books() { + } + + public Books( + String name, + String author, + String releaseYear, + Category category, + List<String> genre, + boolean isCheckedOut + ) { + this.name = name; + this.author = author; + this.releaseYear = releaseYear; + this.category = category; + this.genre = genre; + this.isCheckedOut = isCheckedOut; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getAuthor() { + return author; + } + + public void setAuthor(String author) { + this.author = author; + } + + public String getReleaseYear() { + return releaseYear; + } + + public void setReleaseYear(String releaseYear) { + this.releaseYear = releaseYear; + } + + public Category getCategory() { + return category; + } + + public void setCategory(Category category) { + this.category = category; + } + + public List<String> getGenre() { + return genre; + } + + public void setGenre(List<String> genre) { + this.genre = genre; + } + + public boolean getIsCheckedOut() { + return isCheckedOut; + } + + public void setIsCheckedOut(boolean isCheckedOut) { + this.isCheckedOut = isCheckedOut; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (obj == null || getClass() != obj.getClass()) return false; + Books that = (Books) obj; + return Objects.equals(name, that.name) && + Objects.equals(author, that.author) && + Objects.equals(releaseYear, that.releaseYear) && + Objects.equals(category, that.category) && + Objects.equals(genre, that.genre) && + Objects.equals(isCheckedOut, that.isCheckedOut); + } + + @Override + public int hashCode() { + return Objects.hash(name, author, releaseYear, category, genre, isCheckedOut); + } + + @Override + public String toString() { + return "Books{" + + "name=" + name + + "author=" + author + + "releaseYear=" + releaseYear + + "category=" + category + + "genre=" + genre + + "isCheckedOut=" + isCheckedOut + + '}'; + } +} +``` +{% /tabsitem %} + +{% tabsitem #php title="PHP" %} +Run the following command in your terminal: + +```bash +appwrite types --language php ./types +``` + +This will generate the following types in the `./types` sub-directory of your project: + +```php +<?php +namespace Appwrite\Models; + +enum Category: string { + case FICTION = 'fiction'; + case NONFICTION = 'nonfiction'; +} + +class Books { + private string $name; + private string $author; + private string|null $releaseYear; + private Category|null $category; + private array $genre; + private bool $isCheckedOut; + + public function __construct( + string $name, + string $author, + ?string $releaseYear = null, + ?Category $category = null, + ?array $genre = null, + bool $isCheckedOut + ) { + $this->name = $name; + $this->author = $author; + $this->releaseYear = $releaseYear; + $this->category = $category; + $this->genre = $genre; + $this->isCheckedOut = $isCheckedOut; + } + + public function getName(): string { + return $this->name; + } + + public function setName(string $name): void { + $this->name = $name; + } + public function getAuthor(): string { + return $this->author; + } + + public function setAuthor(string $author): void { + $this->author = $author; + } + public function getReleaseYear(): string|null { + return $this->releaseYear; + } + + public function setReleaseYear(string|null $releaseYear): void { + $this->releaseYear = $releaseYear; + } + public function getCategory(): Category|null { + return $this->category; + } + + public function setCategory(Category|null $category): void { + $this->category = $category; + } + public function getGenre(): array { + return $this->genre; + } + + public function setGenre(array $genre): void { + $this->genre = $genre; + } + public function getIsCheckedOut(): bool { + return $this->isCheckedOut; + } + + public function setIsCheckedOut(bool $isCheckedOut): void { + $this->isCheckedOut = $isCheckedOut; + } +} +``` +{% /tabsitem %} + +{% tabsitem #dart title="Dart" %} +Run the following command in your terminal: + +```bash +appwrite types --language dart ./types +``` + +This will generate the following types in the `./types` sub-directory of your project: + +```dart +enum Category { + fiction, + nonfiction, +} + +class Books { + String name; + String author; + String? releaseYear; + Category? category; + List<String>? genre; + bool isCheckedOut; + + Books({ + required this.name, + required this.author, + this.releaseYear, + this.category, + this.genre, + required this.isCheckedOut, + }); + + factory Books.fromMap(Map<String, dynamic> map) { + return Books( + name: map['name'].toString(), + author: map['author'].toString(), + releaseYear: map['release_year']?.toString() ?? null, + category: map['category'] != null ? Category.values.where((e) => e.name == map['category']).firstOrNull : null, + genre: List<String>.from(map['genre'] ?? []) ?? [], + isCheckedOut: map['is_checked_out'], + ); + } + + Map<String, dynamic> toMap() { + return { + "name": name, + "author": author, + "release_year": releaseYear, + "category": category?.name ?? null, + "genre": genre, + "is_checked_out": isCheckedOut, + }; + } +} +``` +{% /tabsitem %} + +{% tabsitem #kotlin title="Kotlin" %} +Run the following command in your terminal: + +```bash +appwrite types --language kotlin ./types +``` + +This will generate the following types in the `./types` sub-directory of your project: + +```kotlin +package io.appwrite.models + +enum class Category { + fiction, + nonfiction +} + +data class Books( + val name: String, + val author: String, + val releaseYear: String?, + val category: Category?, + val genre: List<String>?, + val isCheckedOut: Boolean, +) +``` +{% /tabsitem %} + +{% tabsitem #swift title="Swift" %} +Run the following command in your terminal: + +```bash +appwrite types --language swift ./types +``` + +This will generate the following types in the `./types` sub-directory of your project: + +```swift +import Foundation + +public enum Category: String, Codable, CaseIterable { + case fiction = "fiction" + case nonfiction = "nonfiction" +} + +public class Books: Codable { + public let name: String + public let author: String + public let releaseYear: String? + public let category: Category? + public let genre: [String]? + public let isCheckedOut: Bool + + enum CodingKeys: String, CodingKey { + case name = "name" + case author = "author" + case releaseYear = "release_year" + case category = "category" + case genre = "genre" + case isCheckedOut = "is_checked_out" + } + + init( + name: String, + author: String, + releaseYear: String?, + category: Category?, + genre: [String]?, + isCheckedOut: Bool + ) { + self.name = name + self.author = author + self.releaseYear = releaseYear + self.category = category + self.genre = genre + self.isCheckedOut = isCheckedOut + } + + public required init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + + self.name = try container.decode(String.self, forKey: .name) + self.author = try container.decode(String.self, forKey: .author) + self.releaseYear = try container.decodeIfPresent(String.self, forKey: .releaseYear) + self.category = try container.decodeIfPresent(Category.self, forKey: .category) + self.genre = try container.decodeIfPresent([String].self, forKey: .genre) + self.isCheckedOut = try container.decode(Bool.self, forKey: .isCheckedOut) + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + + try container.encode(name, forKey: .name) + try container.encode(author, forKey: .author) + try container.encodeIfPresent(releaseYear, forKey: .releaseYear) + try container.encodeIfPresent(category, forKey: .category) + try container.encodeIfPresent(genre, forKey: .genre) + try container.encode(isCheckedOut, forKey: .isCheckedOut) + } + + public func toMap() -> [String: Any] { + return [ + "name": name as Any, + "author": author as Any, + "release_year": releaseYear as Any, + "category": category as Any, + "genre": genre as Any, + "is_checked_out": isCheckedOut as Any + ] + } + + public static func from(map: [String: Any]) -> Books { + return Books( + name: map["name"] as! String, + author: map["author"] as! String, + releaseYear: map["release_year"] as? String, + category: map["category"] as? String, + genre: map["genre"] as? [String], + isCheckedOut: map["is_checked_out"] as! Bool + ) + } +} +``` +{% /tabsitem %} +{% /tabs %} diff --git a/src/routes/docs/products/databases/geo-queries/+page.markdoc b/src/routes/docs/products/databases/geo-queries/+page.markdoc index 155a05a037..c0abdfb884 100644 --- a/src/routes/docs/products/databases/geo-queries/+page.markdoc +++ b/src/routes/docs/products/databases/geo-queries/+page.markdoc @@ -4,6 +4,10 @@ title: Geo queries description: Query geographic data with distance, intersects, overlaps, and other location-based operations using spatial columns. --- +{% info title="Tables DB only" %} +Geo queries and spatial column types are only available with **Tables DB** (MariaDB). Documents DB (MongoDB) does not support spatial data types or geo queries. +{% /info %} + Geo queries let you perform location-based operations on geographic data stored in your database. Find nearby locations, check if coordinates fall within boundaries, calculate distances between points, and more. Appwrite supports geo queries through spatial columns that store coordinates, shapes, and areas as first-class data types. diff --git a/src/routes/docs/products/databases/operators/+page.markdoc b/src/routes/docs/products/databases/operators/+page.markdoc index c243585363..16bfa92300 100644 --- a/src/routes/docs/products/databases/operators/+page.markdoc +++ b/src/routes/docs/products/databases/operators/+page.markdoc @@ -4,6 +4,10 @@ title: Operators description: Update multiple fields atomically without fetching the full row. Perform numeric, array, string, and date updates in a single, consistent workflow. --- +{% info title="Terminology mapping" %} +Code examples on this page use Tables DB (`TablesDB`, `tableId`, `updateRow`). For Documents DB, use `DocumentsDB` instead of `TablesDB`, `collectionId` instead of `tableId`, `updateDocument` instead of `updateRow`, etc. +{% /info %} + Database operators let you update fields directly on the server without fetching the full row. Instead of sending new values, you describe the action you want: increment, append, replace, or adjust. This eliminates race conditions and reduces bandwidth usage when updating any values that need to be modified atomically. The operation is applied atomically at the storage layer for safe, concurrent updates. - Atomic by field: Each operation is applied safely at the storage layer to prevent lost updates under concurrency. diff --git a/src/routes/docs/products/databases/order/+page.markdoc b/src/routes/docs/products/databases/order/+page.markdoc index 7fbe828b3d..cb183cdaa7 100644 --- a/src/routes/docs/products/databases/order/+page.markdoc +++ b/src/routes/docs/products/databases/order/+page.markdoc @@ -4,6 +4,10 @@ title: Order description: Understand how to do data ordering in Appwrite Databases. Learn how to order and sort your database records for efficient data retrieval. --- +{% info title="Terminology mapping" %} +Code examples on this page use Tables DB (`TablesDB`, `tableId`, `listRows`). For Documents DB, use `DocumentsDB` instead of `TablesDB`, `collectionId` instead of `tableId`, `listDocuments` instead of `listRows`, etc. +{% /info %} + You can order results returned by Appwrite Databases by using an order query. For best performance, create an [index](/docs/products/databases/tables#indexes) on the column you plan to order by. diff --git a/src/routes/docs/products/databases/pagination/+page.markdoc b/src/routes/docs/products/databases/pagination/+page.markdoc index f34b5dea36..2eca3df07f 100644 --- a/src/routes/docs/products/databases/pagination/+page.markdoc +++ b/src/routes/docs/products/databases/pagination/+page.markdoc @@ -4,6 +4,10 @@ title: Pagination description: Implement pagination for large data sets in Appwrite Databases. Explore techniques for splitting and displaying data across multiple pages. --- +{% info title="Terminology mapping" %} +Code examples on this page use Tables DB (`TablesDB`, `tableId`, `listRows`). For Documents DB, use `DocumentsDB` instead of `TablesDB`, `collectionId` instead of `tableId`, `listDocuments` instead of `listRows`, etc. +{% /info %} + As your database grows in size, you'll need to paginate results returned. Pagination improves performance by returning a subset of results that match a query at a time, called a page. @@ -292,7 +296,7 @@ Offset pagination allow you to create indicator of the current page number and t For example, a list with up to 20 pages or static data like a list of countries or currencies. Using offset pagination on large tables and frequently updated tables may result in slow performance and **missing and duplicate** results. -Cursor pagination should be used for frequently updated tablesDB. +Cursor pagination should be used for frequently updated tables. It is best suited for lazy-loaded pages with infinite scrolling. For example, a feed, comment section, chat history, or high volume datasets. diff --git a/src/routes/docs/products/databases/permissions/+page.markdoc b/src/routes/docs/products/databases/permissions/+page.markdoc index e6b9ee22b0..22ab131f54 100644 --- a/src/routes/docs/products/databases/permissions/+page.markdoc +++ b/src/routes/docs/products/databases/permissions/+page.markdoc @@ -4,29 +4,29 @@ title: Database permissions description: Enhance data security and access control with Appwrite Database Permissions. Learn how to set permissions and access rules for your database tables --- -Permissions define who can access rows in a table. By default **no permissions** are granted to any users, so no user can access any rows. -Permissions exist at two levels, table level and row level permissions. +Permissions define who can access rows (Tables DB) or documents (Documents DB) in a table or collection. By default **no permissions** are granted to any users, so no user can access any data. +Permissions exist at two levels: table/collection level and row/document level permissions. In Appwrite, permissions are **granted**, meaning a user has no access by default and receive access when granted. -A user with access granted at either table level or row level will be able to access a row. -Users **don't need access at both levels** to access rows. +A user with access granted at either level will be able to access the data. +Users **don't need access at both levels** to access rows or documents. -# Table level {% #table-level %} -Table level permissions apply to every row in the table. -If a user has read, create, update, or delete permissions at the table level, the user can access **all rows** inside the table. +# Table / collection level {% #table-level %} +Table-level (or collection-level) permissions apply to every row or document in the table or collection. +If a user has read, create, update, or delete permissions at this level, the user can access **all rows/documents** inside. -Configure table level permissions by navigating to **Your table** > **Settings** > **Permissions**. +Configure these permissions by navigating to **Your table or collection** > **Settings** > **Permissions**. [Learn more about permissions and roles](/docs/advanced/platform/permissions) -# Row level {% #row-level %} -Row level permissions grant access to individual rows. -If a user has read, create, update, or delete permissions at the row level, the user can access the **individual row**. +# Row / document level {% #row-level %} +Row-level (or document-level) permissions grant access to individual rows or documents. +If a user has read, create, update, or delete permissions at this level, the user can access the **individual row or document**. -Row level permissions are only applied if Row Security is enabled in the settings of your table. -Enable row level permissions by navigating to **Your table** > **Settings** > **Row security**. +Row/document level permissions are only applied if **Row Security** (Tables DB, `rowSecurity`) or **Document Security** (Documents DB, `documentSecurity`) is enabled in the settings. +Enable this by navigating to **Your table or collection** > **Settings** > **Row security** (or **Document security**). -Row level permissions are configured in individual rows. +Row/document level permissions are configured in individual rows or documents. [Learn more about permissions and roles](/docs/advanced/platform/permissions) diff --git a/src/routes/docs/products/databases/queries/+page.markdoc b/src/routes/docs/products/databases/queries/+page.markdoc index 341fbb5a3b..e75b797215 100644 --- a/src/routes/docs/products/databases/queries/+page.markdoc +++ b/src/routes/docs/products/databases/queries/+page.markdoc @@ -1,9 +1,13 @@ --- layout: article title: Queries -description: Harness the power of querying with Appwrite tablesDB. Discover various query options, filtering, sorting, and advanced querying techniques. +description: Harness the power of querying with Appwrite Databases. Discover various query options, filtering, sorting, and advanced querying techniques. --- +{% info title="Terminology mapping" %} +Code examples on this page use Tables DB (`TablesDB`, `tableId`, `listRows`). For Documents DB, use `DocumentsDB` instead of `TablesDB`, `collectionId` instead of `tableId`, `listDocuments` instead of `listRows`, etc. Spatial query operators (distance, intersects, etc.) are Tables DB only. +{% /info %} + Many list endpoints in Appwrite allow you to filter, sort, and paginate results using queries. Appwrite provides a common set of syntax to build queries. # Query class {% #query-class %} @@ -197,7 +201,7 @@ $client $tablesDB = new TablesDB($client); -$result = $tables->listRows( +$result = $tablesDB->listRows( '<DATABASE_ID>', '<TABLE_ID>', [ @@ -1865,7 +1869,7 @@ Query.updatedAfter("2025-01-01T00:00:00Z") {% /multicode %} # Geo queries and spatial operations {% #geo-queries %} -Geo queries enable geographic operations on [spatial columns](/docs/products/databases/spatial). Coordinates are specified as `[longitude, latitude]` arrays. Distance measurements can be specified in meters or degrees. +Geo queries enable geographic operations on [spatial columns](/docs/products/databases/geo-queries). Coordinates are specified as `[longitude, latitude]` arrays. Distance measurements can be specified in meters or degrees. For conceptual information about spatial data types, spatial columns and indexing, see [Geo queries](/docs/products/databases/geo-queries). diff --git a/src/routes/docs/products/databases/quick-start/+page.ts b/src/routes/docs/products/databases/quick-start/+page.ts new file mode 100644 index 0000000000..1f8b65da03 --- /dev/null +++ b/src/routes/docs/products/databases/quick-start/+page.ts @@ -0,0 +1,6 @@ +import type { PageLoad } from './$types'; +import { redirect } from '@sveltejs/kit'; + +export const load: PageLoad = async () => { + redirect(303, '/docs/products/databases/tablesdb/quick-start'); +}; diff --git a/src/routes/docs/products/databases/relationships/+page.ts b/src/routes/docs/products/databases/relationships/+page.ts new file mode 100644 index 0000000000..5ad32a7e5a --- /dev/null +++ b/src/routes/docs/products/databases/relationships/+page.ts @@ -0,0 +1,6 @@ +import type { PageLoad } from './$types'; +import { redirect } from '@sveltejs/kit'; + +export const load: PageLoad = async () => { + redirect(303, '/docs/products/databases/tablesdb/relationships'); +}; diff --git a/src/routes/docs/products/databases/rows/+page.markdoc b/src/routes/docs/products/databases/rows/+page.markdoc index 84901cb967..bbb17a1f8d 100644 --- a/src/routes/docs/products/databases/rows/+page.markdoc +++ b/src/routes/docs/products/databases/rows/+page.markdoc @@ -279,12 +279,12 @@ const client = new Client() const tablesDB = new TablesDB(client); -const promise = tablesDB.updateRow( - '<DATABASE_ID>', - '<TABLE_ID>', - '<ROW_ID>', - { title: 'Updated Title' } -); +const promise = tablesDB.updateRow({ + databaseId: '<DATABASE_ID>', + tableId: '<TABLE_ID>', + rowId: '<ROW_ID>', + data: { title: 'Updated Title' } +}); promise.then(function (response) { console.log(response); @@ -399,12 +399,12 @@ const client = new Client() const tablesDB = new TablesDB(client); -const promise = tablesDB.upsertRow( - '<DATABASE_ID>', - '<TABLE_ID>', - ID.unique(), - {} -); +const promise = tablesDB.upsertRow({ + databaseId: '<DATABASE_ID>', + tableId: '<TABLE_ID>', + rowId: ID.unique(), + data: {} +}); promise.then(function (response) { console.log(response); @@ -715,7 +715,7 @@ if let jsonString = jsonString, {% /tabs %} {% info title="Generate types automatically" %} -You can automatically generate model definitions for your tables using the [Appwrite CLI](/docs/products/databases/type-generation). Run `appwrite types collection` to generate types based on your database schema. +You can generate types for your tables. Learn more in [Type generation](/docs/products/databases/tablesdb/type-generation). {% /info %} # Permissions {% #permissions %} @@ -729,7 +729,7 @@ Users only need to be granted access at either the table or row level to access # Use transactions {% #use-transactions %} -All row operations support `transactionId`. When provided, operations are staged to an internal log and not applied until the transaction is committed. Learn more in the [Transactions guide](/docs/products/databases/transactions). +All row operations support `transactionId`. When provided, operations are staged to an internal log and not applied until the transaction is committed. Learn more in the [Transactions guide](/docs/products/databases/tablesdb/transactions). {% multicode %} ```client-web @@ -981,11 +981,11 @@ Learn how to filter, sort, and search your rows with various query operators. Handle large datasets by implementing pagination in your row queries. {% /cards_item %} -{% cards_item href="/docs/products/databases/bulk-operations" title="Bulk operations" %} +{% cards_item href="/docs/products/databases/tablesdb/bulk-operations" title="Bulk operations" %} Perform create, update, and delete operations on multiple rows simultaneously. {% /cards_item %} -{% cards_item href="/docs/products/databases/timestamp-overrides" title="Timestamp overrides" %} +{% cards_item href="/docs/products/databases/tablesdb/timestamp-overrides" title="Timestamp overrides" %} Set custom creation and update timestamps when migrating data or backdating records. {% /cards_item %} {% /cards %} diff --git a/src/routes/docs/products/databases/tables/+page.markdoc b/src/routes/docs/products/databases/tables/+page.markdoc index b6e0ad86e9..2c21666616 100644 --- a/src/routes/docs/products/databases/tables/+page.markdoc +++ b/src/routes/docs/products/databases/tables/+page.markdoc @@ -1394,7 +1394,7 @@ You can choose between the following types. | `point` | Geographic point specified as `[longitude, latitude]`. | | `line` | Geographic line represented by an ordered list of coordinates. | | `polygon` | Geographic polygon representing a closed area; supports interior holes. | -| `relationship` | Relationship column relates one table to another. [Learn more about relationships.](/docs/products/databases/relationships) | +| `relationship` | Relationship column relates one table to another. [Learn more about relationships.](/docs/products/databases/tablesdb/relationships) | | `string` | **Deprecated.** Use `varchar`, `text`, `mediumtext`, or `longtext` instead. | If an column must be populated in all rows, set it as `required`. @@ -1418,5 +1418,6 @@ The following indexes are currently supported: | `key` | Plain Index to allow queries. | | `unique` | Unique Index to disallow duplicates. | | `fulltext` | For searching within text columns. Required for the [search query method](/docs/products/databases/queries#query-class). | +| `spatial` | For querying geographic data types (`point`, `line`, `polygon`). | You can create an index by navigating to your table's **Indexes** tab or by using your favorite [Server SDK](/docs/sdks#server). diff --git a/src/routes/docs/products/databases/atomic-numeric-operations/+page.markdoc b/src/routes/docs/products/databases/tablesdb/atomic-numeric-operations/+page.markdoc similarity index 96% rename from src/routes/docs/products/databases/atomic-numeric-operations/+page.markdoc rename to src/routes/docs/products/databases/tablesdb/atomic-numeric-operations/+page.markdoc index 1333dc0e26..d497116657 100644 --- a/src/routes/docs/products/databases/atomic-numeric-operations/+page.markdoc +++ b/src/routes/docs/products/databases/tablesdb/atomic-numeric-operations/+page.markdoc @@ -446,24 +446,24 @@ For complex updates that include both atomic operations and regular field change {% multicode %} ```client-web // First, increment the likes atomically -const likeResult = await tablesDB.incrementRowColumn( - '<DATABASE_ID>', - '<TABLE_ID>', - '<ROW_ID>', - 'likes', // column - 1 // value -); +const likeResult = await tablesDB.incrementRowColumn({ + databaseId: '<DATABASE_ID>', + tableId: '<TABLE_ID>', + rowId: '<ROW_ID>', + column: 'likes', // column + value: 1 // value +}); // Then, update other fields -const updateResult = await tablesDB.updateRow( - '<DATABASE_ID>', - '<TABLE_ID>', - '<ROW_ID>', - { +const updateResult = await tablesDB.updateRow({ + databaseId: '<DATABASE_ID>', + tableId: '<TABLE_ID>', + rowId: '<ROW_ID>', + data: { lastLikedBy: userId, lastLikedAt: new Date().toISOString() } -); +}); ``` ```client-flutter // First, increment the likes atomically @@ -530,24 +530,24 @@ val updateResult = tablesDB.updateRow( ``` ```server-nodejs // First, increment the likes atomically -const likeResult = await tablesDB.incrementRowColumn( - '<DATABASE_ID>', - '<TABLE_ID>', - '<ROW_ID>', - 'likes', // column - 1 // value -); +const likeResult = await tablesDB.incrementRowColumn({ + databaseId: '<DATABASE_ID>', + tableId: '<TABLE_ID>', + rowId: '<ROW_ID>', + column: 'likes', // column + value: 1 // value +}); // Then, update other fields -const updateResult = await tablesDB.updateRow( - '<DATABASE_ID>', - '<TABLE_ID>', - '<ROW_ID>', - { +const updateResult = await tablesDB.updateRow({ + databaseId: '<DATABASE_ID>', + tableId: '<TABLE_ID>', + rowId: '<ROW_ID>', + data: { lastLikedBy: userId, lastLikedAt: new Date().toISOString() } -); +}); ``` ```server-python # First, increment the likes atomically diff --git a/src/routes/docs/products/databases/bulk-operations/+page.markdoc b/src/routes/docs/products/databases/tablesdb/bulk-operations/+page.markdoc similarity index 95% rename from src/routes/docs/products/databases/bulk-operations/+page.markdoc rename to src/routes/docs/products/databases/tablesdb/bulk-operations/+page.markdoc index 2b04fa59d4..f5c2d42681 100644 --- a/src/routes/docs/products/databases/bulk-operations/+page.markdoc +++ b/src/routes/docs/products/databases/tablesdb/bulk-operations/+page.markdoc @@ -56,10 +56,10 @@ const client = new sdk.Client() const tablesDB = new sdk.TablesDB(client); -const result = await tablesDB.createRows( - '<DATABASE_ID>', - '<TABLE_ID>', - [ +const result = await tablesDB.createRows({ + databaseId: '<DATABASE_ID>', + tableId: '<TABLE_ID>', + rows: [ { $id: sdk.ID.unique(), name: 'Row 1' @@ -69,7 +69,7 @@ const result = await tablesDB.createRows( name: 'Row 2' } ] -); +}); ``` ```server-python @@ -120,16 +120,16 @@ const client = new sdk.Client() const tablesDB = new sdk.TablesDB(client); -const result = await tablesDB.updateRows( - '<DATABASE_ID>', - '<TABLE_ID>', - { +const result = await tablesDB.updateRows({ + databaseId: '<DATABASE_ID>', + tableId: '<TABLE_ID>', + data: { status: 'published' }, - [ + queries: [ sdk.Query.equal('status', 'draft') ] -); +}); ``` ```server-python @@ -176,10 +176,10 @@ const client = new sdk.Client() const tablesDB = new sdk.TablesDB(client); -const result = await tablesDB.upsertRows( - '<DATABASE_ID>', - '<TABLE_ID>', - [ +const result = await tablesDB.upsertRows({ + databaseId: '<DATABASE_ID>', + tableId: '<TABLE_ID>', + rows: [ { $id: sdk.ID.unique(), name: 'New Row 1' @@ -189,7 +189,7 @@ const result = await tablesDB.upsertRows( name: 'New Row 2' } ] -); +}); ``` ```server-python @@ -241,13 +241,13 @@ const client = new sdk.Client() const tablesDB = new sdk.TablesDB(client); -const result = await tablesDB.deleteRows( - '<DATABASE_ID>', - '<TABLE_ID>', - [ +const result = await tablesDB.deleteRows({ + databaseId: '<DATABASE_ID>', + tableId: '<TABLE_ID>', + queries: [ sdk.Query.equal('status', 'archived') ] -); +}); ``` ```server-python diff --git a/src/routes/docs/products/databases/quick-start/+page.markdoc b/src/routes/docs/products/databases/tablesdb/quick-start/+page.markdoc similarity index 97% rename from src/routes/docs/products/databases/quick-start/+page.markdoc rename to src/routes/docs/products/databases/tablesdb/quick-start/+page.markdoc index 180e3b3cf1..be8c93352d 100644 --- a/src/routes/docs/products/databases/quick-start/+page.markdoc +++ b/src/routes/docs/products/databases/tablesdb/quick-start/+page.markdoc @@ -325,7 +325,7 @@ try { {% /multicode %} {% info title="Automatic type generation" %} -You can automatically generate type definitions for your tables using the [Appwrite CLI type generation](/docs/products/databases/type-generation) feature. Run `appwrite types collection` to generate models for your collections. +You can generate types for your tables. Learn more in [Type generation](/docs/products/databases/tablesdb/type-generation). {% /info %} ### Model methods diff --git a/src/routes/docs/products/databases/relationships/+page.markdoc b/src/routes/docs/products/databases/tablesdb/relationships/+page.markdoc similarity index 96% rename from src/routes/docs/products/databases/relationships/+page.markdoc rename to src/routes/docs/products/databases/tablesdb/relationships/+page.markdoc index 4c87424542..90062c40f7 100644 --- a/src/routes/docs/products/databases/relationships/+page.markdoc +++ b/src/routes/docs/products/databases/tablesdb/relationships/+page.markdoc @@ -96,16 +96,16 @@ const client = new Client() const tablesDB = new TablesDB(client); -tablesDB.createRelationshipColumn( - 'marvel', // Database ID - 'movies', // Table ID - 'reviews', // Related table ID - 'oneToMany', // Relationship type - true, // Is two-way - 'reviews', // Column key - 'movie', // Two-way column key - 'cascade' // On delete action -); +tablesDB.createRelationshipColumn({ + databaseId: 'marvel', + tableId: 'movies', + relatedTableId: 'reviews', + type: 'oneToMany', + twoWay: true, + key: 'reviews', + twoWayKey: 'movie', + onDelete: 'cascade' +}); ``` @@ -119,7 +119,7 @@ $client = (new Client()) $tablesDB = new TablesDB($client); -$tables->createRelationshipColumn( +$tablesDB->createRelationshipColumn( databaseId: 'marvel', // Database ID tableId: 'movies', // Table ID relatedTableId: 'reviews', // Related table ID @@ -188,16 +188,16 @@ const client = new Client() const tablesDB = new TablesDB(client); -tablesDB.createRelationshipColumn( - "marvel", // Database ID - "movies", // Table ID - "reviews", // Related table ID - "oneToMany", // Relationship type - true, // Is two-way - "reviews", // Column key - "movie", // Two-way column key - "cascade" // On delete action -); +tablesDB.createRelationshipColumn({ + databaseId: "marvel", + tableId: "movies", + relatedTableId: "reviews", + type: "oneToMany", + twoWay: true, + key: "reviews", + twoWayKey: "movie", + onDelete: "cascade" +}); ``` @@ -610,11 +610,11 @@ const client = new Client() const tablesDB = new TablesDB(client); -await tablesDB.updateRow( - 'marvel', - 'movies', - 'spiderman', - { +await tablesDB.updateRow({ + databaseId: 'marvel', + tableId: 'movies', + rowId: 'spiderman', + data: { title: 'Spiderman', year: 2002, reviews: [ @@ -622,7 +622,7 @@ await tablesDB.updateRow( 'review5' ] } -); +}); ``` ```dart @@ -729,11 +729,11 @@ const client = new Client() const tablesDB = new TablesDB(client); -await tablesDB.deleteRow( - 'marvel', - 'movies', - 'spiderman' -); +await tablesDB.deleteRow({ + databaseId: 'marvel', + tableId: 'movies', + rowId: 'spiderman' +}); ``` ```dart @@ -804,11 +804,11 @@ const client = new Client() const tablesDB = new TablesDB(client); -await tablesDB.createRow( - 'marvel', - 'movies', - ID.unique(), - { +await tablesDB.createRow({ + databaseId: 'marvel', + tableId: 'movies', + rowId: ID.unique(), + data: { title: 'Spiderman', year: 2002, reviews: [ @@ -821,7 +821,7 @@ await tablesDB.createRow( }, ] } -); +}); ``` ```dart import 'package:appwrite/appwrite.dart'; diff --git a/src/routes/docs/products/databases/tablesdb/timestamp-overrides/+page.markdoc b/src/routes/docs/products/databases/tablesdb/timestamp-overrides/+page.markdoc new file mode 100644 index 0000000000..3b66de15d2 --- /dev/null +++ b/src/routes/docs/products/databases/tablesdb/timestamp-overrides/+page.markdoc @@ -0,0 +1,1038 @@ +--- +layout: article +title: Timestamp overrides +description: Set custom $createdAt and $updatedAt timestamps for your rows when using server SDKs. +--- + +When creating or updating rows, Appwrite automatically sets `$createdAt` and `$updatedAt` timestamps. However, there are scenarios where you might need to set these timestamps manually, such as when migrating data from another system or backfilling historical records. + +{% info title="Server SDKs required" %} +To manually set `$createdAt` and `$updatedAt`, you must use a **server SDK** with an **API key**. These columns can be passed inside the `data` parameter on any of the create, update, or upsert routes (single or bulk). +{% /info %} + +# Setting custom timestamps {% #setting-custom-timestamps %} + +You can override a row's timestamps by providing ISO 8601 strings (for example, `2025-08-10T12:34:56.000Z`) in the `data` payload. If these columns are not provided, Appwrite will set them automatically. + +Custom timestamps work with all row operations: create, update, upsert, and their bulk variants. + +## Single row operations {% #single-row-operations %} + +When working with individual rows, you can set custom timestamps during create, update, and upsert operations. + +### Create with custom timestamps {% #create-custom %} + +{% multicode %} +```server-nodejs +const sdk = require('node-appwrite'); + +const client = new sdk.Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>') + .setKey('<API_KEY>'); + +const tablesDB = new sdk.TablesDB(client); + +await tablesDB.createRow({ + databaseId: '<DATABASE_ID>', + tableId: '<TABLE_ID>', + rowId: sdk.ID.unique(), + data: { + '$createdAt': new Date('2025-08-10T12:34:56.000Z').toISOString(), + '$updatedAt': new Date('2025-08-10T12:34:56.000Z').toISOString(), + // ...your columns + } +}); +``` +```server-php +use Appwrite\Client; +use Appwrite\ID; +use Appwrite\Services\TablesDB; + +$client = (new Client()) + ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + ->setProject('<YOUR_PROJECT_ID>') + ->setKey('<YOUR_API_KEY>'); + +$tablesDB = new TablesDB($client); + +$tablesDB->createRow( + databaseId: '<DATABASE_ID>', + tableId: '<TABLE_ID>', + rowId: '<ROW_ID>', + [ + '$createdAt' => (new DateTime('<CUSTOM_DATE>'))->format(DATE_ATOM), + '$updatedAt' => (new DateTime('<CUSTOM_DATE>'))->format(DATE_ATOM), + // ...your columns + ] +); +``` +```server-swift +import Appwrite +import Foundation + +let client = Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") + .setProject("<YOUR_PROJECT_ID>") + .setKey("<YOUR_API_KEY>") + +let tablesDB = TablesDB(client) + +let isoFormatter = ISO8601DateFormatter() +isoFormatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds] +let customDate = isoFormatter.date(from: "<CUSTOM_DATE>") ?? Date() +let createdAt = isoFormatter.string(from: customDate) +let updatedAt = isoFormatter.string(from: customDate) + +do { + let created = try await tablesDB.createRow( + databaseId: "<DATABASE_ID>", + tableId: "<TABLE_ID>", + rowId: "<ROW_ID>", + data: [ + "$createdAt": createdAt, + "$updatedAt": updatedAt, + // ...your columns + ] + ) + print("Created:", created) +} catch { + print("Create error:", error) +} +``` +```server-python +from appwrite.client import Client +from appwrite.services.tables_db import TablesDB +from appwrite.id import ID +from datetime import datetime, timezone + +client = Client() +client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1') +client.set_project('<PROJECT_ID>') +client.set_key('<API_KEY>') + +tables_db = TablesDB(client) + +iso = datetime(2025, 8, 10, 12, 34, 56, tzinfo=timezone.utc).isoformat() + +tables_db.create_row( + database_id='<DATABASE_ID>', + table_id='<TABLE_ID>', + row_id=ID.unique(), + data={ + '$createdAt': iso, + '$updatedAt': iso, + # ...your columns + } +) +``` +```server-ruby +require 'appwrite' +require 'time' + +include Appwrite + +client = Client.new + .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') + .set_project('<YOUR_PROJECT_ID>') + .set_key('<YOUR_API_KEY>') + +tables_db = TablesDB.new(client) + +custom_date = Time.parse('2025-08-10T12:34:56.000Z').iso8601 + +tables_db.create_row( + database_id: '<DATABASE_ID>', + table_id: '<TABLE_ID>', + row_id: ID.unique(), + data: { + '$createdAt' => custom_date, + '$updatedAt' => custom_date, + # ...your columns + } +) +``` +```server-dotnet +using Appwrite; +using Appwrite.Models; +using Appwrite.Services; + +Client client = new Client() + .SetEndpoint("https://<REGION>.cloud.appwrite.io/v1") + .SetProject("<YOUR_PROJECT_ID>") + .SetKey("<YOUR_API_KEY>"); + +TablesDB tablesDB = new TablesDB(client); + +string customDate = DateTimeOffset.Parse("2025-08-10T12:34:56.000Z").ToString("O"); + +await tablesDB.CreateRow( + databaseId: "<DATABASE_ID>", + tableId: "<TABLE_ID>", + rowId: ID.Unique(), + data: new Dictionary<string, object> + { + ["$createdAt"] = customDate, + ["$updatedAt"] = customDate, + // ...your columns + } +); +``` +```server-dart +import 'package:dart_appwrite/dart_appwrite.dart'; + +Client client = Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + .setProject('<YOUR_PROJECT_ID>') + .setKey('<YOUR_API_KEY>'); + +TablesDB tablesDB = TablesDB(client); + +String customDate = DateTime.parse('2025-08-10T12:34:56.000Z').toIso8601String(); + +await tablesDB.createRow( + databaseId: '<DATABASE_ID>', + tableId: '<TABLE_ID>', + rowId: ID.unique(), + data: { + '\$createdAt': customDate, + '\$updatedAt': customDate, + // ...your columns + }, +); +``` +{% /multicode %} + +### Update with custom timestamps {% #update-custom %} + +When updating rows, you can also set a custom `$updatedAt` timestamp: + +{% multicode %} +```server-nodejs +await tablesDB.updateRow({ + databaseId: '<DATABASE_ID>', + tableId: '<TABLE_ID>', + rowId: '<ROW_ID>', + data: { + '$updatedAt': new Date('2025-08-10T12:34:56.000Z').toISOString(), + // ...your columns + } +}); +``` +```server-php +$tablesDB->updateRow( + databaseId: '<DATABASE_ID>', + tableId: '<TABLE_ID>', + rowId: '<ROW_ID>', + [ + '$updatedAt' => (new DateTime('<CUSTOM_DATE>'))->format(DATE_ATOM), + // ...your columns + ] +); +``` +```server-python +from datetime import datetime, timezone + +tables_db.update_row( + database_id='<DATABASE_ID>', + table_id='<TABLE_ID>', + row_id='<ROW_ID>', + data={ + '$updatedAt': datetime(2025, 8, 10, 12, 34, 56, tzinfo=timezone.utc).isoformat(), + # ...your columns + } +) +``` +```server-swift +import Appwrite +import Foundation + +let client = Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") + .setProject("<YOUR_PROJECT_ID>") + .setKey("<YOUR_API_KEY>") + +let tablesDB = TablesDB(client) + +let isoFormatter = ISO8601DateFormatter() +isoFormatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds] +let updatedAt = isoFormatter.string(from: isoFormatter.date(from: "<CUSTOM_DATE>") ?? Date()) + +do { + let updated = try await tablesDB.updateRow( + databaseId: "<DATABASE_ID>", + tableId: "<TABLE_ID>", + rowId: "<ROW_ID>", + data: [ + "$updatedAt": updatedAt, + // ...your columns + ] + ) + print("Updated:", updated) +} catch { + print("Update error:", error) +} +``` +```server-ruby +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') + .set_project('<YOUR_PROJECT_ID>') + .set_key('<YOUR_API_KEY>') + +tables_db = TablesDB.new(client) + +custom_date = Time.parse('<CUSTOM_DATE>').iso8601 + +tables_db.update_row( + database_id: '<DATABASE_ID>', + table_id: '<TABLE_ID>', + row_id: '<ROW_ID>', + data: { + '$updatedAt' => custom_date, + # ...your columns + } +) +``` +```server-dotnet +using Appwrite; +using Appwrite.Models; +using Appwrite.Services; + +Client client = new Client() + .SetEndpoint("https://<REGION>.cloud.appwrite.io/v1") + .SetProject("<YOUR_PROJECT_ID>") + .SetKey("<YOUR_API_KEY>"); + +TablesDB tablesDB = new TablesDB(client); + +string customDate = DateTimeOffset.Parse("<CUSTOM_DATE>").ToString("O"); + +await tablesDB.UpdateRow( + databaseId: "<DATABASE_ID>", + tableId: "<TABLE_ID>", + rowId: "<ROW_ID>", + data: new Dictionary<string, object> + { + ["$updatedAt"] = customDate, + // ...your columns + } +); +``` +```server-dart +import 'package:dart_appwrite/dart_appwrite.dart'; + +Client client = Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + .setProject('<YOUR_PROJECT_ID>') + .setKey('<YOUR_API_KEY>'); + +TablesDB tablesDB = TablesDB(client); + +String customDate = DateTime.parse('<CUSTOM_DATE>').toIso8601String(); + +await tablesDB.updateRow( + databaseId: '<DATABASE_ID>', + tableId: '<TABLE_ID>', + rowId: '<ROW_ID>', + data: { + '\$updatedAt': customDate, + // ...your columns + }, +); +``` +{% /multicode %} + +## Bulk operations {% #bulk-operations %} + +Custom timestamps also work with bulk operations, allowing you to set different timestamps for each row in the batch: + +### Bulk create {% #bulk-create %} + +{% multicode %} +```server-nodejs +await tablesDB.createRows({ + databaseId: '<DATABASE_ID>', + tableId: '<TABLE_ID>', + rows: [ + { + '$id': sdk.ID.unique(), + '$createdAt': new Date('2024-01-01T00:00:00.000Z').toISOString(), + '$updatedAt': new Date('2024-01-01T00:00:00.000Z').toISOString(), + // ...your columns + }, + { + '$id': sdk.ID.unique(), + '$createdAt': new Date('2024-02-01T00:00:00.000Z').toISOString(), + '$updatedAt': new Date('2024-02-01T00:00:00.000Z').toISOString(), + // ...your columns + } + ] +}); +``` +```server-python +tables_db.create_rows( + database_id='<DATABASE_ID>', + table_id='<TABLE_ID>', + rows=[ + { + '$id': ID.unique(), + '$createdAt': datetime(2024, 1, 1, tzinfo=timezone.utc).isoformat(), + '$updatedAt': datetime(2024, 1, 1, tzinfo=timezone.utc).isoformat(), + # ...your columns + }, + { + '$id': ID.unique(), + '$createdAt': datetime(2024, 2, 1, tzinfo=timezone.utc).isoformat(), + '$updatedAt': datetime(2024, 2, 1, tzinfo=timezone.utc).isoformat(), + # ...your columns + } + ] +) +``` +```server-php +use Appwrite\Client; +use Appwrite\ID; +use Appwrite\Services\TablesDB; + +$client = (new Client()) + ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + ->setProject('<YOUR_PROJECT_ID>') + ->setKey('<YOUR_API_KEY>'); + +$tablesDB = new TablesDB($client); + +$tablesDB->createRows( + databaseId: '<DATABASE_ID>', + tableId: '<TABLE_ID>', + rows: [ + [ + '$id' => ID::unique(), + '$createdAt' => (new DateTime('<CUSTOM_DATE>'))->format(DATE_ATOM), + '$updatedAt' => (new DateTime('<CUSTOM_DATE>'))->format(DATE_ATOM), + // ...your columns + ], + [ + '$id' => ID::unique(), + '$createdAt' => (new DateTime('<CUSTOM_DATE>'))->format(DATE_ATOM), + '$updatedAt' => (new DateTime('<CUSTOM_DATE>'))->format(DATE_ATOM), + // ...your columns + ], + ] +); +``` +```server-swift +import Appwrite +import Foundation + +let client = Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") + .setProject("<YOUR_PROJECT_ID>") + .setKey("<YOUR_API_KEY>") + +let tablesDB = TablesDB(client) + +let isoFormatter = ISO8601DateFormatter() +isoFormatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds] + +let first = isoFormatter.string(from: isoFormatter.date(from: "<CUSTOM_DATE>") ?? Date()) +let second = isoFormatter.string(from: isoFormatter.date(from: "<CUSTOM_DATE>") ?? Date()) + +do { + let bulkCreated = try await tablesDB.createRows( + databaseId: "<DATABASE_ID>", + tableId: "<TABLE_ID>", + rows: [ + [ + "$id": ID.unique(), + "$createdAt": first, + "$updatedAt": first, + // ...your columns + ], + [ + "$id": ID.unique(), + "$createdAt": second, + "$updatedAt": second, + // ...your columns + ] + ] + ) + print("Bulk create:", bulkCreated) +} catch { + print("Bulk create error:", error) +} +``` +```server-ruby +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') + .set_project('<YOUR_PROJECT_ID>') + .set_key('<YOUR_API_KEY>') + +tables_db = TablesDB.new(client) + +first = Time.parse('<CUSTOM_DATE>').iso8601 +second = Time.parse('<CUSTOM_DATE>').iso8601 + +tables_db.create_rows( + database_id: '<DATABASE_ID>', + table_id: '<TABLE_ID>', + rows: [ + { + '$id' => ID.unique(), + '$createdAt' => first, + '$updatedAt' => first, + # ...your columns + }, + { + '$id' => ID.unique(), + '$createdAt' => second, + '$updatedAt' => second, + # ...your columns + } + ] +) +``` +```server-dotnet +using Appwrite; +using Appwrite.Models; +using Appwrite.Services; + +Client client = new Client() + .SetEndpoint("https://<REGION>.cloud.appwrite.io/v1") + .SetProject("<YOUR_PROJECT_ID>") + .SetKey("<YOUR_API_KEY>"); + +TablesDB tablesDB = new TablesDB(client); + +string first = DateTimeOffset.Parse("<CUSTOM_DATE>").ToString("O"); +string second = DateTimeOffset.Parse("<CUSTOM_DATE>").ToString("O"); + +await tablesDB.CreateRows( + databaseId: "<DATABASE_ID>", + tableId: "<TABLE_ID>", + rows: new List<object> + { + new Dictionary<string, object> + { + ["$id"] = ID.Unique(), + ["$createdAt"] = first, + ["$updatedAt"] = first, + // ...your columns + }, + new Dictionary<string, object> + { + ["$id"] = ID.Unique(), + ["$createdAt"] = second, + ["$updatedAt"] = second, + // ...your columns + } + } +); +``` +```server-dart +import 'package:dart_appwrite/dart_appwrite.dart'; + +Client client = Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + .setProject('<YOUR_PROJECT_ID>') + .setKey('<YOUR_API_KEY>'); + +TablesDB tablesDB = TablesDB(client); + +String first = DateTime.parse('<CUSTOM_DATE>').toIso8601String(); +String second = DateTime.parse('<CUSTOM_DATE>').toIso8601String(); + +await tablesDB.createRows( + databaseId: '<DATABASE_ID>', + tableId: '<TABLE_ID>', + rows: [ + { + '\$id': ID.unique(), + '\$createdAt': first, + '\$updatedAt': first, + // ...your columns + }, + { + '\$id': ID.unique(), + '\$createdAt': second, + '\$updatedAt': second, + // ...your columns + } + ], +); +``` +{% /multicode %} + +### Bulk upsert {% #bulk-upsert %} + +{% multicode %} +```server-nodejs +await tablesDB.upsertRows({ + databaseId: '<DATABASE_ID>', + tableId: '<TABLE_ID>', + rows: [ + { + '$id': '<ROW_ID_OR_NEW_ID>', + '$createdAt': new Date('2024-01-01T00:00:00.000Z').toISOString(), + '$updatedAt': new Date('2025-01-01T00:00:00.000Z').toISOString(), + // ...your columns + } + ] +}); +``` +```server-python +tables_db.upsert_rows( + database_id='<DATABASE_ID>', + table_id='<TABLE_ID>', + rows=[ + { + '$id': '<ROW_ID_OR_NEW_ID>', + '$createdAt': datetime(2024, 1, 1, tzinfo=timezone.utc).isoformat(), + '$updatedAt': datetime(2025, 1, 1, tzinfo=timezone.utc).isoformat(), + # ...your columns + } + ] +) +``` +```server-php +use Appwrite\Client; +use Appwrite\ID; +use Appwrite\Services\TablesDB; + +$client = (new Client()) + ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + ->setProject('<YOUR_PROJECT_ID>') + ->setKey('<YOUR_API_KEY>'); + +$tablesDB = new TablesDB($client); + +$tablesDB->upsertRows( + databaseId: '<DATABASE_ID>', + tableId: '<TABLE_ID>', + rows: [ + [ + '$id' => '<ROW_ID_OR_NEW_ID>', + '$createdAt' => (new DateTime('<CUSTOM_DATE>'))->format(DATE_ATOM), + '$updatedAt' => (new DateTime('<CUSTOM_DATE>'))->format(DATE_ATOM), + // ...your columns + ], + ] +); +``` +```server-swift +import Appwrite +import Foundation + +let client = Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") + .setProject("<YOUR_PROJECT_ID>") + .setKey("<YOUR_API_KEY>") + +let tablesDB = TablesDB(client) + +let isoFormatter = ISO8601DateFormatter() +isoFormatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds] +let createdAt = isoFormatter.string(from: isoFormatter.date(from: "<CUSTOM_DATE>") ?? Date()) +let updatedAt = isoFormatter.string(from: isoFormatter.date(from: "<CUSTOM_DATE>") ?? Date()) + +do { + let bulkUpserted = try await tablesDB.upsertRows( + databaseId: "<DATABASE_ID>", + tableId: "<TABLE_ID>", + rows: [ + [ + "$id": "<ROW_ID_OR_NEW_ID>", + "$createdAt": createdAt, + "$updatedAt": updatedAt, + // ...your columns + ] + ] + ) + print("Bulk upsert:", bulkUpserted) +} catch { + print("Bulk upsert error:", error) +} +``` +```server-ruby +require 'appwrite' +require 'time' + +include Appwrite + +client = Client.new + .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') + .set_project('<YOUR_PROJECT_ID>') + .set_key('<YOUR_API_KEY>') + +tables_db = TablesDB.new(client) + +custom_date = Time.parse('<CUSTOM_DATE>').iso8601 + +tables_db.upsert_rows( + database_id: '<DATABASE_ID>', + table_id: '<TABLE_ID>', + rows: [ + { + '$id' => '<ROW_ID_OR_NEW_ID>', + '$createdAt' => custom_date, + '$updatedAt' => custom_date, + # ...your columns + } + ] +) +``` +```server-dotnet +using Appwrite; +using Appwrite.Models; +using Appwrite.Services; + +Client client = new Client() + .SetEndpoint("https://<REGION>.cloud.appwrite.io/v1") + .SetProject("<YOUR_PROJECT_ID>") + .SetKey("<YOUR_API_KEY>"); + +TablesDB tablesDB = new TablesDB(client); + +string createdAt = DateTimeOffset.Parse("<CUSTOM_DATE>").ToString("O"); +string updatedAt = DateTimeOffset.Parse("<CUSTOM_DATE>").ToString("O"); + +await tablesDB.UpsertRows( + databaseId: "<DATABASE_ID>", + tableId: "<TABLE_ID>", + rows: new List<object> + { + new Dictionary<string, object> + { + ["$id"] = "<ROW_ID_OR_NEW_ID>", + ["$createdAt"] = createdAt, + ["$updatedAt"] = updatedAt, + // ...your columns + } + } +); +``` +```server-dart +import 'package:dart_appwrite/dart_appwrite.dart'; + +Client client = Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + .setProject('<YOUR_PROJECT_ID>') + .setKey('<YOUR_API_KEY>'); + +TablesDB tablesDB = TablesDB(client); + +String createdAt = DateTime.parse('<CUSTOM_DATE>').toIso8601String(); +String updatedAt = DateTime.parse('<CUSTOM_DATE>').toIso8601String(); + +await tablesDB.upsertRows( + databaseId: '<DATABASE_ID>', + tableId: '<TABLE_ID>', + rows: [ + { + '\$id': '<ROW_ID_OR_NEW_ID>', + '\$createdAt': createdAt, + '\$updatedAt': updatedAt, + // ...your columns + } + ], +); +``` +{% /multicode %} + +# Common use cases {% #use-cases %} + +Custom timestamps are particularly useful in several scenarios: + +## Data migration {% #data-migration %} +When migrating existing data from another system, you can preserve the original +creation and modification times: + +{% multicode %} +```server-nodejs +await tablesDB.createRow({ + databaseId: '<DATABASE_ID>', + tableId: 'blog_posts', + rowId: sdk.ID.unique(), + data: { + '$createdAt': '<ORIGINAL_CREATED_AT_ISO>', + '$updatedAt': '<LAST_MODIFIED_ISO>', + title: '<TITLE>', + content: '<CONTENT>' + } +}) +``` +```server-php +$tablesDB->createRow( + databaseId: '<DATABASE_ID>', + tableId: 'blog_posts', + rowId: ID::unique(), + [ + '$createdAt' => '<ORIGINAL_CREATED_AT_ISO>', + '$updatedAt' => '<LAST_MODIFIED_ISO>', + 'title' => '<TITLE>', + 'content' => '<CONTENT>' + ] +); +``` +```server-swift +let _ = try await tablesDB.createRow( + databaseId: "<DATABASE_ID>", + tableId: "blog_posts", + rowId: ID.unique(), + data: [ + "$createdAt": "<ORIGINAL_CREATED_AT_ISO>", + "$updatedAt": "<LAST_MODIFIED_ISO>", + "title": "<TITLE>", + "content": "<CONTENT>" + ] +) +``` +```server-python +tables_db.create_row( + database_id='<DATABASE_ID>', + table_id='blog_posts', + row_id=ID.unique(), + data={ + '$createdAt': '<ORIGINAL_CREATED_AT_ISO>', + '$updatedAt': '<LAST_MODIFIED_ISO>', + 'title': '<TITLE>', + 'content': '<CONTENT>' + } +) +``` +```server-ruby +tables_db.create_row( + database_id: '<DATABASE_ID>', + table_id: 'blog_posts', + row_id: ID.unique(), + data: { + '$createdAt' => '<ORIGINAL_CREATED_AT_ISO>', + '$updatedAt' => '<LAST_MODIFIED_ISO>', + 'title' => '<TITLE>', + 'content' => '<CONTENT>' + } +) +``` +```server-dotnet +await tablesDB.CreateRow( + databaseId: "<DATABASE_ID>", + tableId: "blog_posts", + rowId: ID.Unique(), + data: new Dictionary<string, object> + { + ["$createdAt"] = "<ORIGINAL_CREATED_AT_ISO>", + ["$updatedAt"] = "<LAST_MODIFIED_ISO>", + ["title"] = "<TITLE>", + ["content"] = "<CONTENT>" + } +); +``` +```server-dart +await tablesDB.createRow( + databaseId: '<DATABASE_ID>', + tableId: 'blog_posts', + rowId: ID.unique(), + data: { + '\$createdAt': '<ORIGINAL_CREATED_AT_ISO>', + '\$updatedAt': '<LAST_MODIFIED_ISO>', + 'title': '<TITLE>', + 'content': '<CONTENT>' + }, +); +``` +{% /multicode %} + +## Backdating records {% #backdating %} +For historical data entry or when creating records that represent past events: + +{% multicode %} +```server-nodejs +await tablesDB.createRow({ + databaseId: '<DATABASE_ID>', + tableId: 'transactions', + rowId: sdk.ID.unique(), + data: { + '$createdAt': '2023-12-31T23:59:59.000Z', + '$updatedAt': '2023-12-31T23:59:59.000Z', + amount: 1000, + type: 'year-end-bonus' + } +}) +``` +```server-php +$tablesDB->createRow( + databaseId: '<DATABASE_ID>', + tableId: 'transactions', + rowId: ID::unique(), + [ + '$createdAt' => '2023-12-31T23:59:59.000Z', + '$updatedAt' => '2023-12-31T23:59:59.000Z', + 'amount' => 1000, + 'type' => 'year-end-bonus' + ] +); +``` +```server-swift +let _ = try await tablesDB.createRow( + databaseId: "<DATABASE_ID>", + tableId: "transactions", + rowId: ID.unique(), + data: [ + "$createdAt": "2023-12-31T23:59:59.000Z", + "$updatedAt": "2023-12-31T23:59:59.000Z", + "amount": 1000, + "type": "year-end-bonus" + ] +) +``` +```server-python +tables_db.create_row( + database_id='<DATABASE_ID>', + table_id='transactions', + row_id=ID.unique(), + data={ + '$createdAt': '2023-12-31T23:59:59.000Z', + '$updatedAt': '2023-12-31T23:59:59.000Z', + 'amount': 1000, + 'type': 'year-end-bonus' + } +) +``` +```server-ruby +tables_db.create_row( + database_id: '<DATABASE_ID>', + table_id: 'transactions', + row_id: ID.unique(), + data: { + '$createdAt' => '2023-12-31T23:59:59.000Z', + '$updatedAt' => '2023-12-31T23:59:59.000Z', + 'amount' => 1000, + 'type' => 'year-end-bonus' + } +) +``` +```server-dotnet +await tablesDB.CreateRow( + databaseId: "<DATABASE_ID>", + tableId: "transactions", + rowId: ID.Unique(), + data: new Dictionary<string, object> + { + ["$createdAt"] = "2023-12-31T23:59:59.000Z", + ["$updatedAt"] = "2023-12-31T23:59:59.000Z", + ["amount"] = 1000, + ["type"] = "year-end-bonus" + } +); +``` +```server-dart +await tablesDB.createRow( + databaseId: '<DATABASE_ID>', + tableId: 'transactions', + rowId: ID.unique(), + data: { + '\$createdAt': '2023-12-31T23:59:59.000Z', + '\$updatedAt': '2023-12-31T23:59:59.000Z', + 'amount': 1000, + 'type': 'year-end-bonus' + }, +); +``` +{% /multicode %} + +## Synchronization {% #synchronization %} +When synchronizing data between systems while maintaining timestamp consistency: + +{% multicode %} +```server-nodejs +await tablesDB.upsertRow( + '<DATABASE_ID>', + 'users', + '<ROW_ID_OR_NEW_ID>', + { + '$updatedAt': '<EXTERNAL_LAST_MODIFIED_ISO>', + profile: '<PROFILE_DATA>' + } +) +``` +```server-php +$tablesDB->upsertRow( + databaseId: '<DATABASE_ID>', + tableId: 'users', + rowId: '<ROW_ID_OR_NEW_ID>', + [ + '$updatedAt' => '<EXTERNAL_LAST_MODIFIED_ISO>', + 'profile' => '<PROFILE_DATA>' + ] +); +``` +```server-swift +let _ = try await tablesDB.upsertRow( + databaseId: "<DATABASE_ID>", + tableId: "users", + rowId: "<ROW_ID_OR_NEW_ID>", + data: [ + "$updatedAt": "<EXTERNAL_LAST_MODIFIED_ISO>", + "profile": "<PROFILE_DATA>" + ] +) +``` +```server-python +tables_db.upsert_row( + database_id='<DATABASE_ID>', + table_id='users', + row_id='<ROW_ID_OR_NEW_ID>', + data={ + '$updatedAt': '<EXTERNAL_LAST_MODIFIED_ISO>', + 'profile': '<PROFILE_DATA>' + } +) +``` +```server-ruby +tables_db.upsert_row( + database_id: '<DATABASE_ID>', + table_id: 'users', + row_id: '<ROW_ID_OR_NEW_ID>', + data: { + '$updatedAt' => '<EXTERNAL_LAST_MODIFIED_ISO>', + 'profile' => '<PROFILE_DATA>' + } +) +``` +```server-dotnet +await tablesDB.UpsertRow( + databaseId: "<DATABASE_ID>", + tableId: "users", + rowId: "<ROW_ID_OR_NEW_ID>", + data: new Dictionary<string, object> + { + ["$updatedAt"] = "<EXTERNAL_LAST_MODIFIED_ISO>", + ["profile"] = "<PROFILE_DATA>" + } +); +``` +```server-dart +await tablesDB.upsertRow( + databaseId: '<DATABASE_ID>', + tableId: 'users', + rowId: '<ROW_ID_OR_NEW_ID>', + data: { + '\$updatedAt': '<EXTERNAL_LAST_MODIFIED_ISO>', + 'profile': '<PROFILE_DATA>' + }, +); +``` +{% /multicode %} + +{% info title="Timestamp format and usage" %} +- Values must be valid ISO 8601 date-time strings (UTC recommended). Using `toISOString()` (JavaScript) or `datetime.isoformat()` (Python) is a good default. +- You can set either or both columns as needed. If omitted, Appwrite sets them automatically. +{% /info %} + diff --git a/src/routes/docs/products/databases/transactions/+page.markdoc b/src/routes/docs/products/databases/tablesdb/transactions/+page.markdoc similarity index 100% rename from src/routes/docs/products/databases/transactions/+page.markdoc rename to src/routes/docs/products/databases/tablesdb/transactions/+page.markdoc diff --git a/src/routes/docs/products/databases/type-generation/+page.markdoc b/src/routes/docs/products/databases/tablesdb/type-generation/+page.markdoc similarity index 100% rename from src/routes/docs/products/databases/type-generation/+page.markdoc rename to src/routes/docs/products/databases/tablesdb/type-generation/+page.markdoc diff --git a/src/routes/docs/products/databases/timestamp-overrides/+page.ts b/src/routes/docs/products/databases/timestamp-overrides/+page.ts new file mode 100644 index 0000000000..41b11d9060 --- /dev/null +++ b/src/routes/docs/products/databases/timestamp-overrides/+page.ts @@ -0,0 +1,6 @@ +import type { PageLoad } from './$types'; +import { redirect } from '@sveltejs/kit'; + +export const load: PageLoad = async () => { + redirect(303, '/docs/products/databases/tablesdb/timestamp-overrides'); +}; diff --git a/src/routes/docs/products/databases/transactions/+page.ts b/src/routes/docs/products/databases/transactions/+page.ts new file mode 100644 index 0000000000..ac52b6ec7b --- /dev/null +++ b/src/routes/docs/products/databases/transactions/+page.ts @@ -0,0 +1,6 @@ +import type { PageLoad } from './$types'; +import { redirect } from '@sveltejs/kit'; + +export const load: PageLoad = async () => { + redirect(303, '/docs/products/databases/tablesdb/transactions'); +}; diff --git a/src/routes/docs/products/databases/type-generation/+page.ts b/src/routes/docs/products/databases/type-generation/+page.ts new file mode 100644 index 0000000000..63cd452e04 --- /dev/null +++ b/src/routes/docs/products/databases/type-generation/+page.ts @@ -0,0 +1,6 @@ +import type { PageLoad } from './$types'; +import { redirect } from '@sveltejs/kit'; + +export const load: PageLoad = async () => { + redirect(303, '/docs/products/databases/tablesdb/type-generation'); +};