diff --git a/.optimize-cache.json b/.optimize-cache.json index 43eb0fa549..2484f047bd 100644 --- a/.optimize-cache.json +++ b/.optimize-cache.json @@ -183,6 +183,7 @@ "images/blog/announcing-init-faster-smoother-better/init-swag.png": "2894ba9370588ff92a0d94ddb410e1700ae368834391603f0bceadf57ac89fab", "images/blog/announcing-init-faster-smoother-better/init-ticket.png": "fe4e16ef27d3fcba378c52882ce3458aab3f1de84cb183d39db577e5264ef905", "images/blog/announcing-inversion-queries/cover.png": "232f806b8b655f469cb5398ba3abce2074e959d2fb49b9782b1889b22f1ee16e", + "images/blog/announcing-list-cache-ttl/cover.png": "ca1554dc34d1222b86ccc295252af8e07b2f635b9b10e1227a21fff81138e409", "images/blog/announcing-new-push-notifications-features/cover.png": "a0c758cf6c8a95e09a0d2ca562b0775a50d34a4d691d675cda70e44ad21805ac", "images/blog/announcing-opt-in-relationship-loading/cover.png": "e16cc16ea6d968b29af19bcd6274741141584a7efe5e1bb18be19b77c3a380c8", "images/blog/announcing-phone-OTP-pricing/cover.png": "598d55359ca4cb2b46846a8fd76b1f051be7c5f3199b50ffa92a28e84e5f3d67", diff --git a/src/routes/blog/post/announcing-list-cache-ttl/+page.markdoc b/src/routes/blog/post/announcing-list-cache-ttl/+page.markdoc new file mode 100644 index 0000000000..fa9012babd --- /dev/null +++ b/src/routes/blog/post/announcing-list-cache-ttl/+page.markdoc @@ -0,0 +1,348 @@ +--- +layout: post +title: "Announcing list response caching: Instant reads with TTL-based caching" +description: Cache list query responses in memory with a single parameter. Set a TTL, skip the database round-trip on repeated reads, and purge on demand when freshness matters. +date: 2026-04-17 +cover: /images/blog/announcing-list-cache-ttl/cover.png +timeToRead: 4 +author: jake-barnby +category: announcement +featured: false +callToAction: true +--- + +Read-heavy workloads hit the same queries over and over. Leaderboards, product listings, dashboard feeds, and reference tables all follow the same pattern: the data changes infrequently, but the reads never stop. Every request still runs a full database query, even when the result hasn't changed since the last call. + +Until now, the only option was to build your own caching layer on top of Appwrite. That meant extra infrastructure, invalidation logic, and another moving part to maintain. + +Today, we are introducing **TTL-based list response caching** for Appwrite Databases. Pass a `ttl` parameter to any list endpoint, and Appwrite caches the response in memory. Repeated identical requests return the cached result instantly, without touching the database, until the TTL expires. + +# How it works + +Add the `ttl` parameter (in seconds) to any `listRows` call. The first request executes normally and stores the result in an in-memory cache. Every subsequent identical request returns the cached response until the TTL expires. + +{% multicode %} +```client-web +const rows = await tablesDB.listRows({ + databaseId: '', + tableId: '', + queries: [ + Query.equal('status', 'active'), + Query.limit(25) + ], + ttl: 60 // Cache for 60 seconds +}); +``` +```server-nodejs +const rows = await tablesDB.listRows({ + databaseId: '', + tableId: '', + queries: [ + sdk.Query.equal('status', 'active'), + sdk.Query.limit(25) + ], + ttl: 60 // Cache for 60 seconds +}); +``` +```server-python +rows = tables_db.list_rows( + database_id='', + table_id='', + queries=[ + Query.equal('status', 'active'), + Query.limit(25) + ], + ttl=60 # Cache for 60 seconds +) +``` +```server-ruby +rows = tables_db.list_rows( + database_id: '', + table_id: '', + queries: [ + Appwrite::Query.equal('status', 'active'), + Appwrite::Query.limit(25) + ], + ttl: 60 # Cache for 60 seconds +) +``` +```server-deno +const rows = await tablesDB.listRows({ + databaseId: '', + tableId: '', + queries: [ + Query.equal('status', 'active'), + Query.limit(25) + ], + ttl: 60 // Cache for 60 seconds +}); +``` +```server-php +$rows = $tablesDB->listRows( + databaseId: '', + tableId: '', + queries: [ + Query::equal('status', ['active']), + Query::limit(25) + ], + ttl: 60 // Cache for 60 seconds +); +``` +```server-go +rows, err := tablesDB.ListRows( + "", + "", + tablesDB.WithListRowsQueries([]string{ + query.Equal("status", []interface{}{"active"}), + query.Limit(25), + }), + tablesDB.WithListRowsTtl(60), // Cache for 60 seconds +) +``` +```server-swift +let rows = try await tablesDB.listRows( + databaseId: "", + tableId: "", + queries: [ + Query.equal("status", value: "active"), + Query.limit(25) + ], + ttl: 60 // Cache for 60 seconds +) +``` +```server-kotlin +val rows = tablesDB.listRows( + databaseId = "", + tableId = "", + queries = listOf( + Query.equal("status", "active"), + Query.limit(25) + ), + ttl = 60 // Cache for 60 seconds +) +``` +```server-rust +let rows = tables_db.list_rows( + "", + "", + Some(vec![ + "equal(\"status\", [\"active\"])".to_string(), + "limit(25)".to_string(), + ]), + None, // transaction_id + None, // total + Some(60), // ttl - Cache for 60 seconds +).await?; +``` +```server-dotnet +RowList rows = await tablesDB.ListRows( + databaseId: "", + tableId: "", + queries: new List { + Query.Equal("status", new List { "active" }), + Query.Limit(25) + }, + ttl: 60 // Cache for 60 seconds +); +``` +```server-dart +RowList rows = await tablesDB.listRows( + databaseId: '', + tableId: '', + queries: [ + Query.equal('status', 'active'), + Query.limit(25) + ], + ttl: 60, // Cache for 60 seconds +); +``` +```server-java +tablesDB.listRows( + "", + "", + List.of( + Query.equal("status", List.of("active")), + Query.limit(25) + ), + null, // transactionId + null, // total + 60, // ttl - Cache for 60 seconds + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + System.out.println(result); + }) +); +``` +```client-flutter +final rows = await tablesDB.listRows( + databaseId: '', + tableId: '', + queries: [ + Query.equal('status', 'active'), + Query.limit(25) + ], + ttl: 60, // Cache for 60 seconds +); +``` +```client-apple +let rows = try await tablesDB.listRows( + databaseId: "", + tableId: "", + queries: [ + Query.equal("status", value: "active"), + Query.limit(25) + ], + ttl: 60 // Cache for 60 seconds +) +``` +```client-android-kotlin +val rows = tablesDB.listRows( + databaseId = "", + tableId = "", + queries = listOf( + Query.equal("status", "active"), + Query.limit(25) + ), + ttl = 60 // Cache for 60 seconds +) +``` +{% /multicode %} + +Set `ttl` between `1` and `86400` (24 hours). The default is `0`, which means caching is disabled. The response includes an `X-Appwrite-Cache` header with value `hit` or `miss`, so you always know whether a response was served from cache. + +# Permission-aware by design + +Caching does not compromise security. Each cached entry is scoped to the caller's authorization roles, so users with different permissions always receive their own results. There is no risk of one user seeing another user's data through the cache. + +# Built for stale-tolerant reads + +This feature is designed for workloads where slightly stale data is acceptable. Row writes do **not** automatically invalidate the cache. If you update a row, cached responses will continue to serve the previous result until the TTL expires. + +This is a deliberate trade-off. Automatic invalidation on every write would eliminate most of the performance benefit. Instead, you choose the TTL that fits your use case: + +- **Short TTLs (5 to 30 seconds)** for feeds and dashboards where near-real-time matters +- **Medium TTLs (60 to 300 seconds)** for product listings, search results, and leaderboards +- **Long TTLs (3600+ seconds)** for reference data, configuration tables, and rarely changing content + +Schema changes (adding or removing columns and indexes) invalidate cached entries automatically, so structural updates always take effect immediately. + +# Purge on demand + +When you need fresh data immediately, you can purge the cache manually by calling `updateTable` with `purge` set to `true`. + +{% multicode %} +```server-nodejs +await tablesDB.updateTable({ + databaseId: '', + tableId: '', + purge: true +}); +``` +```server-python +tables_db.update_table( + database_id='', + table_id='', + purge=True +) +``` +```server-ruby +tables_db.update_table( + database_id: '', + table_id: '', + purge: true +) +``` +```server-deno +await tablesDB.updateTable({ + databaseId: '', + tableId: '', + purge: true +}); +``` +```server-php +$tablesDB->updateTable( + databaseId: '', + tableId: '', + purge: true +); +``` +```server-go +tablesDB.UpdateTable( + "", + "", + tablesDB.WithUpdateTablePurge(true), +) +``` +```server-swift +let _ = try await tablesDB.updateTable( + databaseId: "", + tableId: "", + purge: true +) +``` +```server-kotlin +tablesDB.updateTable( + databaseId = "", + tableId = "", + purge = true +) +``` +```server-rust +tables_db.update_table( + "", + "", + None, // name + None, // permissions + None, // row_security + None, // enabled + Some(true), // purge +).await?; +``` +```server-dotnet +await tablesDB.UpdateTable( + databaseId: "", + tableId: "", + purge: true +); +``` +```server-dart +await tablesDB.updateTable( + databaseId: '', + tableId: '', + purge: true, +); +``` +```server-java +tablesDB.updateTable( + "", + "", + null, // name + null, // permissions + null, // rowSecurity + null, // enabled + true, // purge + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + System.out.println(result); + }) +); +``` +{% /multicode %} + +This clears all cached list responses for that table in a single operation. + +# Available now + +List response caching is available today on Appwrite Cloud. + +# More resources + +- [Rows: Cache list responses](/docs/products/databases/rows#cache-list-responses) +- [Pagination: Cache list responses](/docs/products/databases/pagination#cache-list-responses) +- [Announcing Full Schema Creation: Provision complete tables in one atomic call](/blog/post/announcing-full-schema-creation) diff --git a/src/routes/changelog/(entries)/2026-04-17.markdoc b/src/routes/changelog/(entries)/2026-04-17.markdoc new file mode 100644 index 0000000000..bcbc03b5bc --- /dev/null +++ b/src/routes/changelog/(entries)/2026-04-17.markdoc @@ -0,0 +1,14 @@ +--- +layout: changelog +title: "TTL-based list response caching" +date: 2026-04-17 +cover: /images/blog/announcing-list-cache-ttl/cover.png +--- + +You can now cache `listRows` responses by passing a `ttl` parameter (in seconds). The first request executes normally and stores the result in an in-memory cache. Subsequent identical requests return the cached response instantly until the TTL expires. The cache is permission-aware, so users with different roles never see each other's cached data. + +Set `ttl` between `1` and `86400` (24 hours). The default is `0` (caching disabled). Row writes do not invalidate the cache. To force a purge, call `updateTable` with `purge` set to `true`. + +{% arrow_link href="/blog/post/announcing-list-cache-ttl" %} +Read the full announcement +{% /arrow_link %} diff --git a/src/routes/docs/products/databases/pagination/+page.markdoc b/src/routes/docs/products/databases/pagination/+page.markdoc index 310c4dc970..a138a22014 100644 --- a/src/routes/docs/products/databases/pagination/+page.markdoc +++ b/src/routes/docs/products/databases/pagination/+page.markdoc @@ -649,3 +649,354 @@ X-Appwrite-Project: } ``` {% /multicode %} + +# Cache list responses {% #cache-list-responses %} + +You can cache list responses by passing a `ttl` (time-to-live) value in seconds. Subsequent identical requests return the cached result until the TTL expires. The cache is permission-aware, so users with different roles never see each other's cached data. + +Set `ttl` between `1` and `86400` (24 hours). The default is `0` (caching disabled). The response includes an `X-Appwrite-Cache` header with value `hit` or `miss`. Combine with `total=false` for the best performance on paginated queries over large tables. + +{% multicode %} +```client-web +import { Client, Query, TablesDB } from "appwrite"; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + +const tablesDB = new TablesDB(client); + +const page = await tablesDB.listRows({ + databaseId: '', + tableId: '', + queries: [ + Query.limit(25) + ], + ttl: 60 // Cache for 60 seconds +}); +``` +```server-nodejs +const sdk = require('node-appwrite'); + +const client = new sdk.Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject('') + .setKey(''); + +const tablesDB = new sdk.TablesDB(client); + +const page = await tablesDB.listRows({ + databaseId: '', + tableId: '', + queries: [ + sdk.Query.limit(25) + ], + ttl: 60 // Cache for 60 seconds +}); +``` +```server-python +from appwrite.client import Client +from appwrite.services.tables_db import TablesDB +from appwrite.query import Query + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') +client.set_project('') +client.set_key('') + +tables_db = TablesDB(client) + +page = tables_db.list_rows( + database_id='', + table_id='', + queries=[ + Query.limit(25) + ], + ttl=60 # Cache for 60 seconds +) +``` +```server-ruby +require 'appwrite' + +client = Appwrite::Client.new + .set_endpoint('https://.cloud.appwrite.io/v1') + .set_project('') + .set_key('') + +tables_db = Appwrite::TablesDB.new(client) + +page = tables_db.list_rows( + database_id: '', + table_id: '', + queries: [ + Appwrite::Query.limit(25) + ], + ttl: 60 # Cache for 60 seconds +) +``` +```server-deno +import { Client, Query, TablesDB } from "https://deno.land/x/appwrite/mod.ts"; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject('') + .setKey(''); + +const tablesDB = new TablesDB(client); + +const page = await tablesDB.listRows({ + databaseId: '', + tableId: '', + queries: [ + Query.limit(25) + ], + ttl: 60 // Cache for 60 seconds +}); +``` +```server-php +setEndpoint('https://.cloud.appwrite.io/v1') + ->setProject('') + ->setKey(''); + +$tablesDB = new TablesDB($client); + +$page = $tablesDB->listRows( + databaseId: '', + tableId: '', + queries: [ + Query::limit(25) + ], + ttl: 60 // Cache for 60 seconds +); +``` +```server-go +package main + +import ( + "fmt" + "github.com/appwrite/sdk-for-go/client" + "github.com/appwrite/sdk-for-go/tablesdb" + "github.com/appwrite/sdk-for-go/query" +) + +func main() { + clt := client.New( + client.WithEndpoint("https://.cloud.appwrite.io/v1"), + client.WithProject(""), + client.WithKey(""), + ) + + tablesDB := tablesdb.New(clt) + + page, err := tablesDB.ListRows( + "", + "", + tablesDB.WithListRowsQueries([]string{ + query.Limit(25), + }), + tablesDB.WithListRowsTtl(60), // Cache for 60 seconds + ) + + if err != nil { + fmt.Println(err) + } + _ = page +} +``` +```server-swift +import Appwrite +import AppwriteModels + +func main() async throws { + let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + .setKey("") + + let tablesDB = TablesDB(client) + + let page = try await tablesDB.listRows( + databaseId: "", + tableId: "", + queries: [ + Query.limit(25) + ], + ttl: 60 // Cache for 60 seconds + ) +} +``` +```server-kotlin +import io.appwrite.Client +import io.appwrite.Query +import io.appwrite.services.TablesDB + +suspend fun main() { + val client = Client(applicationContext) + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + .setKey("") + + val tablesDB = TablesDB(client) + + val page = tablesDB.listRows( + databaseId = "", + tableId = "", + queries = listOf( + Query.limit(25) + ), + ttl = 60 // Cache for 60 seconds + ) +} +``` +```server-rust +use appwrite::Client; +use appwrite::services::tables_db::TablesDB; +use appwrite::query::Query; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let client = Client::new() + .set_endpoint("https://.cloud.appwrite.io/v1") + .set_project("") + .set_key(""); + + let tables_db = TablesDB::new(&client); + + let page = tables_db.list_rows( + "", + "", + Some(vec![ + Query::limit(25).to_string(), + ]), + None, // transaction_id + None, // total + Some(60), // ttl - Cache for 60 seconds + ).await?; + + println!("{:?}", page); + Ok(()) +} +``` +```server-java +import io.appwrite.Client; +import io.appwrite.Query; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.TablesDB; + +Client client = new Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + .setKey(""); + +TablesDB tablesDB = new TablesDB(client); + +tablesDB.listRows( + "", + "", + List.of(Query.limit(25)), + null, // transactionId + null, // total + 60, // ttl - Cache for 60 seconds + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + System.out.println(result); + }) +); +``` +```client-flutter +import 'package:appwrite/appwrite.dart'; + +void main() async { + final client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + + final tablesDB = TablesDB(client); + + final page = await tablesDB.listRows( + databaseId: '', + tableId: '', + queries: [ + Query.limit(25) + ], + ttl: 60, // Cache for 60 seconds + ); +} +``` +```client-apple +import Appwrite +import AppwriteModels + +func main() async throws { + let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + + let tablesDB = TablesDB(client) + + let page = try await tablesDB.listRows( + databaseId: "", + tableId: "", + queries: [ + Query.limit(25) + ], + ttl: 60 // Cache for 60 seconds + ) +} +``` +```client-android-kotlin +import io.appwrite.Client +import io.appwrite.Query +import io.appwrite.services.TablesDB + +suspend fun main() { + val client = Client(applicationContext) + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + + val tablesDB = TablesDB(client) + + val page = tablesDB.listRows( + databaseId = "", + tableId = "", + queries = listOf( + Query.limit(25) + ), + ttl = 60 // Cache for 60 seconds + ) +} +``` +```graphql +query { + tablesListRows( + databaseId: "", + tableId: "", + queries: ["limit(25)"], + ttl: 60 + ) { + total + rows { + _id + data + } + } +} +``` +```http +GET /v1/tablesdb//tables//rows?ttl=60 HTTP/1.1 +Content-Type: application/json +X-Appwrite-Project: +``` +{% /multicode %} + +Row writes do **not** invalidate the cache, so cached responses may contain stale data until the TTL expires. Schema changes invalidate cached entries automatically. To force an immediate purge, see [Rows: Purge cache](/docs/products/databases/rows#purge-cache). diff --git a/src/routes/docs/products/databases/rows/+page.markdoc b/src/routes/docs/products/databases/rows/+page.markdoc index 17d5ad9c39..7b2c8541e3 100644 --- a/src/routes/docs/products/databases/rows/+page.markdoc +++ b/src/routes/docs/products/databases/rows/+page.markdoc @@ -261,6 +261,627 @@ query { ``` {% /multicode %} +# Cache list responses {% #cache-list-responses %} + +You can cache list responses by passing a `ttl` (time-to-live) value in seconds to `listRows`. Subsequent identical requests return the cached result until the TTL expires. The cache is permission-aware, so users with different roles never see each other's cached data. + +Set `ttl` between `1` and `86400` (24 hours). The default is `0` (caching disabled). The response includes an `X-Appwrite-Cache` header with value `hit` or `miss`. + +{% multicode %} +```client-web +import { Client, Query, TablesDB } from "appwrite"; + +const client = new Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject(""); + +const tablesDB = new TablesDB(client); + +const rows = await tablesDB.listRows({ + databaseId: "", + tableId: "", + queries: [ + Query.equal('title', 'Avatar') + ], + ttl: 60 // Cache for 60 seconds +}); +``` +```server-nodejs +const sdk = require('node-appwrite'); + +const client = new sdk.Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject('') + .setKey(''); + +const tablesDB = new sdk.TablesDB(client); + +const rows = await tablesDB.listRows({ + databaseId: '', + tableId: '', + queries: [ + sdk.Query.equal('title', 'Avatar') + ], + ttl: 60 // Cache for 60 seconds +}); +``` +```server-python +from appwrite.client import Client +from appwrite.services.tables_db import TablesDB +from appwrite.query import Query + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') +client.set_project('') +client.set_key('') + +tables_db = TablesDB(client) + +rows = tables_db.list_rows( + database_id='', + table_id='', + queries=[ + Query.equal('title', 'Avatar') + ], + ttl=60 # Cache for 60 seconds +) +``` +```server-ruby +require 'appwrite' + +client = Appwrite::Client.new + .set_endpoint('https://.cloud.appwrite.io/v1') + .set_project('') + .set_key('') + +tables_db = Appwrite::TablesDB.new(client) + +rows = tables_db.list_rows( + database_id: '', + table_id: '', + queries: [ + Appwrite::Query.equal('title', 'Avatar') + ], + ttl: 60 # Cache for 60 seconds +) +``` +```server-deno +import { Client, Query, TablesDB } from "https://deno.land/x/appwrite/mod.ts"; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject('') + .setKey(''); + +const tablesDB = new TablesDB(client); + +const rows = await tablesDB.listRows({ + databaseId: '', + tableId: '', + queries: [ + Query.equal('title', 'Avatar') + ], + ttl: 60 // Cache for 60 seconds +}); +``` +```server-php +setEndpoint('https://.cloud.appwrite.io/v1') + ->setProject('') + ->setKey(''); + +$tablesDB = new TablesDB($client); + +$rows = $tablesDB->listRows( + databaseId: '', + tableId: '', + queries: [ + Query::equal('title', ['Avatar']) + ], + ttl: 60 // Cache for 60 seconds +); +``` +```server-go +package main + +import ( + "fmt" + "github.com/appwrite/sdk-for-go/client" + "github.com/appwrite/sdk-for-go/tablesdb" + "github.com/appwrite/sdk-for-go/query" +) + +func main() { + clt := client.New( + client.WithEndpoint("https://.cloud.appwrite.io/v1"), + client.WithProject(""), + client.WithKey(""), + ) + + tablesDB := tablesdb.New(clt) + + rows, err := tablesDB.ListRows( + "", + "", + tablesDB.WithListRowsQueries([]string{ + query.Equal("title", []interface{}{"Avatar"}), + }), + tablesDB.WithListRowsTtl(60), // Cache for 60 seconds + ) + + if err != nil { + fmt.Println(err) + } + _ = rows +} +``` +```server-swift +import Appwrite +import AppwriteModels + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + .setKey("") + +let tablesDB = TablesDB(client) + +let rows = try await tablesDB.listRows( + databaseId: "", + tableId: "", + queries: [ + Query.equal("title", value: "Avatar") + ], + ttl: 60 // Cache for 60 seconds +) +``` +```server-kotlin +import io.appwrite.Client +import io.appwrite.Query +import io.appwrite.services.TablesDB + +suspend fun main() { + val client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + .setKey("") + + val tablesDB = TablesDB(client) + + val rows = tablesDB.listRows( + databaseId = "", + tableId = "", + queries = listOf( + Query.equal("title", "Avatar") + ), + ttl = 60 // Cache for 60 seconds + ) +} +``` +```server-rust +use appwrite::Client; +use appwrite::services::TablesDB; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let client = Client::new(); + client.set_endpoint("https://.cloud.appwrite.io/v1"); + client.set_project(""); + client.set_key(""); + + let tables_db = TablesDB::new(&client); + + let rows = tables_db.list_rows( + "", + "", + Some(vec![ + "equal(\"title\", [\"Avatar\"])".to_string(), + ]), + None, // transaction_id + None, // total + Some(60), // ttl - Cache for 60 seconds + ).await?; + + let _ = rows; + Ok(()) +} +``` +```server-dotnet +using Appwrite; +using Appwrite.Models; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://.cloud.appwrite.io/v1") + .SetProject("") + .SetKey(""); + +TablesDB tablesDB = new TablesDB(client); + +RowList rows = await tablesDB.ListRows( + databaseId: "", + tableId: "", + queries: new List { + Query.Equal("title", new List { "Avatar" }) + }, + ttl: 60 // Cache for 60 seconds +); +``` +```server-dart +import 'package:dart_appwrite/dart_appwrite.dart'; + +Client client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject('') + .setKey(''); + +TablesDB tablesDB = TablesDB(client); + +RowList rows = await tablesDB.listRows( + databaseId: '', + tableId: '', + queries: [ + Query.equal('title', 'Avatar') + ], + ttl: 60, // Cache for 60 seconds +); +``` +```server-java +import io.appwrite.Client; +import io.appwrite.Query; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.TablesDB; + +Client client = new Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + .setKey(""); + +TablesDB tablesDB = new TablesDB(client); + +tablesDB.listRows( + "", + "", + List.of(Query.equal("title", List.of("Avatar"))), + null, // transactionId + null, // total + 60, // ttl - Cache for 60 seconds + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + System.out.println(result); + }) +); +``` +```client-flutter +import 'package:appwrite/appwrite.dart'; + +void main() async { + final client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject(""); + + final tablesDB = TablesDB(client); + + final rows = await tablesDB.listRows( + databaseId: '', + tableId: '', + queries: [ + Query.equal('title', 'Avatar') + ], + ttl: 60, // Cache for 60 seconds + ); +} +``` +```client-apple +import Appwrite +import AppwriteModels + +func main() async throws { + let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + + let tablesDB = TablesDB(client) + + let rows = try await tablesDB.listRows( + databaseId: "", + tableId: "", + queries: [ + Query.equal("title", value: "Avatar") + ], + ttl: 60 // Cache for 60 seconds + ) +} +``` +```client-android-kotlin +import io.appwrite.Client +import io.appwrite.Query +import io.appwrite.services.TablesDB + +suspend fun main() { + val client = Client(applicationContext) + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + + val tablesDB = TablesDB(client) + + val rows = tablesDB.listRows( + databaseId = "", + tableId = "", + queries = listOf( + Query.equal("title", "Avatar") + ), + ttl = 60 // Cache for 60 seconds + ) +} +``` +```graphql +query { + tablesListRows( + databaseId: "", + tableId: "", + queries: ["equal(\"title\", [\"Avatar\"])"], + ttl: 60 + ) { + total + rows { + _id + data + } + } +} +``` +{% /multicode %} + +## Purge cache {% #purge-cache %} + +Row writes do **not** invalidate the cache, so cached responses may contain stale data until the TTL expires. Schema changes (adding or removing columns and indexes) invalidate cached entries automatically. + +To force an immediate cache purge, call `updateTable` with `purge` set to `true` using a [Server SDK](/docs/sdks#server). + +{% multicode %} +```server-nodejs +const sdk = require('node-appwrite'); + +const client = new sdk.Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject('') + .setKey(''); + +const tablesDB = new sdk.TablesDB(client); + +await tablesDB.updateTable({ + databaseId: '', + tableId: '', + purge: true +}); +``` +```server-python +from appwrite.client import Client +from appwrite.services.tables_db import TablesDB + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') +client.set_project('') +client.set_key('') + +tables_db = TablesDB(client) + +tables_db.update_table( + database_id='', + table_id='', + purge=True +) +``` +```server-php +setEndpoint('https://.cloud.appwrite.io/v1') + ->setProject('') + ->setKey(''); + +$tablesDB = new TablesDB($client); + +$tablesDB->updateTable( + databaseId: '', + tableId: '', + purge: true +); +``` +```server-ruby +require 'appwrite' + +client = Appwrite::Client.new + .set_endpoint('https://.cloud.appwrite.io/v1') + .set_project('') + .set_key('') + +tables_db = Appwrite::TablesDB.new(client) + +tables_db.update_table( + database_id: '', + table_id: '', + purge: true +) +``` +```server-deno +import { Client, TablesDB } from "https://deno.land/x/appwrite/mod.ts"; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject('') + .setKey(''); + +const tablesDB = new TablesDB(client); + +await tablesDB.updateTable({ + databaseId: '', + tableId: '', + purge: true +}); +``` +```server-dart +import 'package:dart_appwrite/dart_appwrite.dart'; + +final client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject('') + .setKey(''); + +final tablesDB = TablesDB(client); + +await tablesDB.updateTable( + databaseId: '', + tableId: '', + purge: true, +); +``` +```server-swift +import Appwrite + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + .setKey("") + +let tablesDB = TablesDB(client) + +let _ = try await tablesDB.updateTable( + databaseId: "", + tableId: "", + purge: true +) +``` +```server-kotlin +import io.appwrite.Client +import io.appwrite.services.TablesDB + +suspend fun main() { + val client = Client(applicationContext) + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + .setKey("") + + val tablesDB = TablesDB(client) + + tablesDB.updateTable( + databaseId = "", + tableId = "", + purge = true + ) +} +``` +```server-go +package main + +import ( + "github.com/appwrite/sdk-for-go/client" + "github.com/appwrite/sdk-for-go/tablesdb" +) + +func main() { + clt := client.New( + client.WithEndpoint("https://.cloud.appwrite.io/v1"), + client.WithProject(""), + client.WithKey(""), + ) + + tablesDB := tablesdb.New(clt) + + tablesDB.UpdateTable( + "", + "", + tablesDB.WithUpdateTablePurge(true), + ) +} +``` +```server-java +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.TablesDB; + +Client client = new Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + .setKey(""); + +TablesDB tablesDB = new TablesDB(client); + +tablesDB.updateTable( + "", + "", + null, // name + null, // permissions + null, // rowSecurity + null, // enabled + true, // purge + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + System.out.println(result); + }) +); +``` +```server-dotnet +using Appwrite; +using Appwrite.Services; + +var client = new Client() + .SetEndpoint("https://.cloud.appwrite.io/v1") + .SetProject("") + .SetKey(""); + +var tablesDB = new TablesDB(client); + +await tablesDB.UpdateTable( + databaseId: "", + tableId: "", + purge: true +); +``` +```server-rust +use appwrite::Client; +use appwrite::services::tables_db::TablesDB; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let client = Client::new() + .set_endpoint("https://.cloud.appwrite.io/v1") + .set_project("") + .set_key(""); + + let tables_db = TablesDB::new(&client); + + tables_db.update_table( + "", + "", + None, // name + None, // permissions + None, // row_security + None, // enabled + Some(true), // purge + ).await?; + + Ok(()) +} +``` +{% /multicode %} + # Update row {% #update-row %} {% info title="Permissions required" %} You must grant _update_ permissions to users at the _table level_ or _row level_ before users can update rows. diff --git a/static/images/blog/announcing-list-cache-ttl/cover.png b/static/images/blog/announcing-list-cache-ttl/cover.png new file mode 100644 index 0000000000..7768e52347 Binary files /dev/null and b/static/images/blog/announcing-list-cache-ttl/cover.png differ