Compare commits

..
Author SHA1 Message Date
Prem Palanisamy a7fb3d91e1 bump utopia-php/database to 5.3.19 2026-04-03 19:23:56 +02:00
23 changed files with 146 additions and 221 deletions
+2 -1
View File
@@ -218,7 +218,8 @@ jobs:
const decode = (content) => JSON.parse(Buffer.from(content, 'base64').toString());
const databaseChanged = getDbVersion(decode(base.content)) !== getDbVersion(decode(head.content));
core.setOutput('databases', JSON.stringify(databaseChanged ? allDatabases : defaultDatabases));
// Like cloud: database version change expands modes only, not engines
core.setOutput('databases', JSON.stringify(defaultDatabases));
core.setOutput('modes', JSON.stringify(databaseChanged ? allModes : defaultModes));
build:
+63 -40
View File
@@ -1,28 +1,43 @@
<img width="1920" height="1080" alt="image" src="https://github.com/user-attachments/assets/55a81268-4ecc-46cd-bdf5-73f7e8662fee" />
> We just announced DB operators for Appwrite Databases - [Learn more](https://appwrite.io/blog/post/announcing-db-operators)
> Appwrite Cloud is now Generally Available - [Learn more](https://appwrite.io/cloud-ga)
> [Get started with Appwrite](https://apwr.dev/appcloud)
<br />
<p align="center">
<h1>Appwrite</h1>
<b>Appwrite is an open-source, all-in-one development platform. Use built-in backend infrastructure and web hosting, all from a single place.</b>
<a href="https://appwrite.io" target="_blank"><img src="./public/images/banner.png" alt="Appwrite banner, with logo and text saying "The Developer's Cloud"></a>
<br />
<br />
<b>Appwrite is a best-in-class, developer-first platform that gives builders everything they need to create scalable, stable, and production-ready software, fast.</b>
<br />
<br />
</p>
[![Discord](https://img.shields.io/badge/chat-5865F2?style=flat-square&logo=discord&logoColor=white)](https://appwrite.io/discord)
[![X](https://img.shields.io/badge/follow-000000?style=flat-square&logo=x&logoColor=white)](https://x.com/appwrite)
[![Appwrite Cloud](https://img.shields.io/badge/Cloud-F02E65?style=flat-square&logo=icloud&logoColor=white)](https://cloud.appwrite.io)
<!-- [![Build Status](https://img.shields.io/travis/com/appwrite/appwrite?style=flat-square)](https://travis-ci.com/appwrite/appwrite) -->
[![We're Hiring label](https://img.shields.io/static/v1?label=We're&message=Hiring&color=blue&style=flat-square)](https://appwrite.io/company/careers)
[![Hacktoberfest label](https://img.shields.io/static/v1?label=hacktoberfest&message=ready&color=191120&style=flat-square)](https://hacktoberfest.appwrite.io)
[![Discord label](https://img.shields.io/discord/564160730845151244?label=discord&style=flat-square)](https://appwrite.io/discord?r=Github)
[![Build Status label](https://img.shields.io/github/actions/workflow/status/appwrite/appwrite/tests.yml?branch=master&label=tests&style=flat-square)](https://github.com/appwrite/appwrite/actions)
[![X Account label](https://img.shields.io/twitter/follow/appwrite?color=00acee&label=twitter&style=flat-square)](https://twitter.com/appwrite)
<!-- [![Docker Pulls](https://img.shields.io/docker/pulls/appwrite/appwrite?color=f02e65&style=flat-square)](https://hub.docker.com/r/appwrite/appwrite) -->
<!-- [![Translate](https://img.shields.io/badge/translate-f02e65?style=flat-square)](docs/tutorials/add-translations.md) -->
<!-- [![Swag Store](https://img.shields.io/badge/swag%20store-f02e65?style=flat-square)](https://store.appwrite.io) -->
English | [简体中文](README-CN.md)
Appwrite is an open-source development platform for building web, mobile, and AI applications. It brings together backend infrastructure and web hosting in one place, so teams can build, ship, and scale without stitching together a fragmented stack. Appwrite is available as a managed cloud platform and can also be self-hosted on infrastructure you control.
Appwrite is an end-to-end platform for building Web, Mobile, Native, or Backend apps, packaged as a set of Docker microservices. It includes both a backend server and a fully integrated hosting solution for deploying static and server-side rendered frontends. Appwrite abstracts the complexity and repetitiveness required to build modern apps from scratch and allows you to build secure, full-stack applications faster.
With Appwrite, you can add authentication, databases, storage, functions, messaging, realtime capabilities, and integrated web app hosting through Sites. It is designed to reduce the repetitive backend work required to launch modern products while giving developers secure primitives and flexible APIs to build production-ready applications faster.
Using Appwrite, you can easily integrate your app with user authentication and multiple sign-in methods, a database for storing and querying users and team data, storage and file management, image manipulation, Cloud Functions, messaging, and [more services](https://appwrite.io/docs).
Find out more at [https://appwrite.io](https://appwrite.io).
![Appwrite project dashboard showing various Appwrite features](public/images/github.png)
Find out more at: [https://appwrite.io](https://appwrite.io).
Table of Contents:
- [Products](#products)
- [Installation \& Setup](#installation--setup)
- [Self-Hosting](#self-hosting)
- [Unix](#unix)
@@ -32,31 +47,17 @@ Table of Contents:
- [Upgrade from an Older Version](#upgrade-from-an-older-version)
- [One-Click Setups](#one-click-setups)
- [Getting Started](#getting-started)
- [Products](#products)
- [SDKs](#sdks)
- [Client](#client)
- [Server](#server)
- [Community](#community)
- [Architecture](#architecture)
- [Contributing](#contributing)
- [Security](#security)
- [Follow Us](#follow-us)
- [License](#license)
## Products
- **[Appwrite Auth](https://appwrite.io/docs/products/authentication)** - Secure user authentication with multiple login methods including email/password, SMS, OAuth, anonymous sessions, and magic links. Includes session management, multi-factor authentication, and user verification flows.
- **[Appwrite Databases](https://appwrite.io/docs/products/databases)** - Scalable structured data storage with support for databases, tables, and rows. Includes querying, pagination, indexing, and relationships to model complex application data.
- **[Appwrite Storage](https://appwrite.io/docs/products/storage)** - Secure file storage with support for uploads, downloads, encryption, compression, and file transformations for media and assets.
- **[Appwrite Functions](https://appwrite.io/docs/products/functions)** - Serverless compute platform to run custom backend logic in isolated runtimes, triggered by events or scheduled jobs.15 runtimes supported.
- **[Appwrite Messaging](https://appwrite.io/docs/products/messaging)** - Multi-channel messaging system for sending emails, SMS, and push notifications to users for engagement, alerts, and transactional workflows.
- **[Appwrite Sites](https://appwrite.io/docs/products/sites)** - Integrated hosting platform to deploy and scale web applications with support for custom domains, SSR, and seamless backend integration. Git integration and previews are supported.
## Installation & Setup
The easiest way to get started with Appwrite is by [signing up for Appwrite Cloud](https://cloud.appwrite.io/). While Appwrite Cloud is in public beta, you can build with Appwrite completely free, and we won't collect your credit card information.
@@ -167,29 +168,51 @@ Getting started with Appwrite is as easy as creating a new project, choosing you
| | [Quick start for Kotlin](https://appwrite.io/docs/quick-starts/kotlin) |
| | [Quick start for Swift](https://appwrite.io/docs/quick-starts/swift) |
### Products
- [**Account**](https://appwrite.io/docs/references/cloud/client-web/account) - Manage current user authentication and account. Track and manage the user sessions, devices, sign-in methods, and security logs.
- [**Users**](https://appwrite.io/docs/server/users) - Manage and list all project users when building backend integrations with Server SDKs.
- [**Teams**](https://appwrite.io/docs/references/cloud/client-web/teams) - Manage and group users in teams. Manage memberships, invites, and user roles within a team.
- [**Databases**](https://appwrite.io/docs/references/cloud/client-web/databases) - Manage databases, collections, and documents. Read, create, update, and delete documents and filter lists of document collections using advanced filters.
- [**Storage**](https://appwrite.io/docs/references/cloud/client-web/storage) - Manage storage files. Read, create, delete, and preview files. Manipulate the preview of your files to perfectly fit your app. All files are scanned by ClamAV and stored in a secure and encrypted way.
- [**Functions**](https://appwrite.io/docs/references/cloud/server-nodejs/functions) - Customize your Appwrite project by executing your custom code in a secure, isolated environment. You can trigger your code on any Appwrite system event either manually or using a CRON schedule.
- [**Messaging**](https://appwrite.io/docs/references/cloud/client-web/messaging) - Communicate with your users through push notifications, emails, and SMS text messages using Appwrite Messaging.
- [**Realtime**](https://appwrite.io/docs/realtime) - Listen to real-time events for any of your Appwrite services including users, storage, functions, databases, and more.
- [**Locale**](https://appwrite.io/docs/references/cloud/client-web/locale) - Track your user's location and manage your app locale-based data.
- [**Avatars**](https://appwrite.io/docs/references/cloud/client-web/avatars) - Manage your users' avatars, countries' flags, browser icons, and credit card symbols. Generate QR codes from links or plaintext strings.
- [**MCP**](https://appwrite.io/docs/tooling/mcp) - Use Appwrite's Model Context Protocol (MCP) server to allow LLMs and AI tools like Claude Desktop, Cursor, and Windsurf Editor to directly interact with your Appwrite project through natural language.
- [**Sites**](https://appwrite.io/docs/products/sites) - Develop, deploy, and scale your web applications directly from Appwrite, alongside your backend.
For the complete API documentation, visit [https://appwrite.io/docs](https://appwrite.io/docs). For more tutorials, news and announcements check out our [blog](https://medium.com/appwrite-io) and [Discord Server](https://discord.gg/GSeTUeA).
### SDKs
Below is a list of currently supported platforms and languages. If you would like to help us add support to your platform of choice, you can go over to our [SDK Generator](https://github.com/appwrite/sdk-generator) project and view our [contribution guide](https://github.com/appwrite/sdk-generator/blob/master/CONTRIBUTING.md).
#### Client
- :white_check_mark: &nbsp; [Web](https://github.com/appwrite/sdk-for-web)
- :white_check_mark: &nbsp; [Flutter](https://github.com/appwrite/sdk-for-flutter)
- :white_check_mark: &nbsp; [Apple](https://github.com/appwrite/sdk-for-apple)
- :white_check_mark: &nbsp; [Android](https://github.com/appwrite/sdk-for-android)
- :white_check_mark: &nbsp; [React Native](https://github.com/appwrite/sdk-for-react-native)
- :white_check_mark: &nbsp; [Web](https://github.com/appwrite/sdk-for-web) (Maintained by the Appwrite Team)
- :white_check_mark: &nbsp; [Flutter](https://github.com/appwrite/sdk-for-flutter) (Maintained by the Appwrite Team)
- :white_check_mark: &nbsp; [Apple](https://github.com/appwrite/sdk-for-apple) (Maintained by the Appwrite Team)
- :white_check_mark: &nbsp; [Android](https://github.com/appwrite/sdk-for-android) (Maintained by the Appwrite Team)
- :white_check_mark: &nbsp; [React Native](https://github.com/appwrite/sdk-for-react-native) - **Beta** (Maintained by the Appwrite Team)
#### Server
- :white_check_mark: &nbsp; [NodeJS](https://github.com/appwrite/sdk-for-node)
- :white_check_mark: &nbsp; [PHP](https://github.com/appwrite/sdk-for-php)
- :white_check_mark: &nbsp; [Dart](https://github.com/appwrite/sdk-for-dart)
- :white_check_mark: &nbsp; [Deno](https://github.com/appwrite/sdk-for-deno)
- :white_check_mark: &nbsp; [Ruby](https://github.com/appwrite/sdk-for-ruby)
- :white_check_mark: &nbsp; [Python](https://github.com/appwrite/sdk-for-python)
- :white_check_mark: &nbsp; [Kotlin](https://github.com/appwrite/sdk-for-kotlin)
- :white_check_mark: &nbsp; [Swift](https://github.com/appwrite/sdk-for-swift)
- :white_check_mark: &nbsp; [.NET](https://github.com/appwrite/sdk-for-dotnet)
- :white_check_mark: &nbsp; [NodeJS](https://github.com/appwrite/sdk-for-node) (Maintained by the Appwrite Team)
- :white_check_mark: &nbsp; [PHP](https://github.com/appwrite/sdk-for-php) (Maintained by the Appwrite Team)
- :white_check_mark: &nbsp; [Dart](https://github.com/appwrite/sdk-for-dart) (Maintained by the Appwrite Team)
- :white_check_mark: &nbsp; [Deno](https://github.com/appwrite/sdk-for-deno) (Maintained by the Appwrite Team)
- :white_check_mark: &nbsp; [Ruby](https://github.com/appwrite/sdk-for-ruby) (Maintained by the Appwrite Team)
- :white_check_mark: &nbsp; [Python](https://github.com/appwrite/sdk-for-python) (Maintained by the Appwrite Team)
- :white_check_mark: &nbsp; [Kotlin](https://github.com/appwrite/sdk-for-kotlin) (Maintained by the Appwrite Team)
- :white_check_mark: &nbsp; [Swift](https://github.com/appwrite/sdk-for-swift) (Maintained by the Appwrite Team)
- :white_check_mark: &nbsp; [.NET](https://github.com/appwrite/sdk-for-dotnet) - **Beta** (Maintained by the Appwrite Team)
#### Community
- :white_check_mark: &nbsp; [Appcelerator Titanium](https://github.com/m1ga/ti.appwrite) (Maintained by [Michael Gangolf](https://github.com/m1ga/))
- :white_check_mark: &nbsp; [Godot Engine](https://github.com/GodotNuts/appwrite-sdk) (Maintained by [fenix-hub @GodotNuts](https://github.com/fenix-hub))
Looking for more SDKs? - Help us by contributing a pull request to our [SDK Generator](https://github.com/appwrite/sdk-generator)!
+5 -9
View File
@@ -167,7 +167,7 @@ $setResource('getProjectDB', function (Group $pools, Database $dbForPlatform, $c
if (\in_array($dsn->getHost(), $sharedTables)) {
$database
->setSharedTables(true)
->setTenant($project->getSequence())
->setTenant((int)$project->getSequence())
->setNamespace($dsn->getParam('namespace'));
} else {
$database
@@ -188,7 +188,7 @@ $setResource('getProjectDB', function (Group $pools, Database $dbForPlatform, $c
if (\in_array($dsn->getHost(), $sharedTables)) {
$database
->setSharedTables(true)
->setTenant($project->getSequence())
->setTenant((int)$project->getSequence())
->setNamespace($dsn->getParam('namespace'));
} else {
$database
@@ -212,7 +212,7 @@ $setResource('getLogsDB', function (Group $pools, Cache $cache, Authorization $a
return function (?Document $project = null) use ($pools, $cache, $database, $authorization) {
if ($database !== null && $project !== null && !$project->isEmpty() && $project->getId() !== 'console') {
$database->setTenant($project->getSequence());
$database->setTenant((int)$project->getSequence());
return $database;
}
@@ -229,7 +229,7 @@ $setResource('getLogsDB', function (Group $pools, Cache $cache, Authorization $a
// set tenant
if ($project !== null && !$project->isEmpty() && $project->getId() !== 'console') {
$database->setTenant($project->getSequence());
$database->setTenant((int)$project->getSequence());
}
return $database;
@@ -329,20 +329,17 @@ $setResource('bus', function (Registry $register) use ($cli) {
$setResource('telemetry', fn () => new NoTelemetry(), []);
$exitCode = 0;
$cli
->error()
->inject('error')
->inject('logError')
->action(function (Throwable $error, callable $logError) use ($taskName, &$exitCode) {
->action(function (Throwable $error, callable $logError) use ($taskName) {
call_user_func_array($logError, [
$error,
'Task',
$taskName,
]);
$exitCode = 1;
Timer::clearAll();
});
@@ -351,4 +348,3 @@ $cli->shutdown()->action(fn () => Timer::clearAll());
Runtime::enableCoroutine(SWOOLE_HOOK_ALL);
require_once __DIR__ . '/init/span.php';
run($cli->run(...));
Console::exit($exitCode);
+4 -4
View File
@@ -3937,7 +3937,7 @@ Http::post('/v1/account/recovery')
->setParam('userId', $profile->getId())
->setParam('tokenId', $recovery->getId())
->setUser($profile)
->setPayload($response->showSensitive(fn () => $response->output($recovery, Response::MODEL_TOKEN)), sensitive: ['secret']);
->setPayload(Response::showSensitive(fn () => $response->output($recovery, Response::MODEL_TOKEN)), sensitive: ['secret']);
$response
->setStatusCode(Response::STATUS_CODE_CREATED)
@@ -4038,7 +4038,7 @@ Http::put('/v1/account/recovery')
$queueForEvents
->setParam('userId', $profile->getId())
->setParam('tokenId', $recoveryDocument->getId())
->setPayload($response->showSensitive(fn () => $response->output($recoveryDocument, Response::MODEL_TOKEN)), sensitive: ['secret']);
->setPayload(Response::showSensitive(fn () => $response->output($recoveryDocument, Response::MODEL_TOKEN)), sensitive: ['secret']);
$response->dynamic($recoveryDocument, Response::MODEL_TOKEN);
});
@@ -4268,7 +4268,7 @@ Http::post('/v1/account/verifications/email')
$queueForEvents
->setParam('userId', $user->getId())
->setParam('tokenId', $verification->getId())
->setPayload($response->showSensitive(fn () => $response->output($verification, Response::MODEL_TOKEN)), sensitive: ['secret']);
->setPayload(Response::showSensitive(fn () => $response->output($verification, Response::MODEL_TOKEN)), sensitive: ['secret']);
$response
->setStatusCode(Response::STATUS_CODE_CREATED)
@@ -4360,7 +4360,7 @@ Http::put('/v1/account/verifications/email')
$queueForEvents
->setParam('userId', $userId)
->setParam('tokenId', $verification->getId())
->setPayload($response->showSensitive(fn () => $response->output($verification, Response::MODEL_TOKEN)), sensitive: ['secret']);
->setPayload(Response::showSensitive(fn () => $response->output($verification, Response::MODEL_TOKEN)), sensitive: ['secret']);
$response->dynamic($verification, Response::MODEL_TOKEN);
});
+2 -15
View File
@@ -868,7 +868,7 @@ Http::init()
* Request format
*/
$route = $utopia->getRoute();
$request->setRoute($route);
Request::setRoute($route);
if ($route === null) {
$response->setStatusCode(404);
@@ -1019,7 +1019,7 @@ Http::init()
return;
}
$route = $request->getRoute();
if ($route?->getLabel('origin', false) === '*') {
if ($route->getLabel('origin', false) === '*') {
return;
}
if (!$originValidator->isValid($origin)) {
@@ -1493,19 +1493,6 @@ Http::error()
'type' => $type,
];
// Add CORS headers to error responses so browsers can read the error.
// Wrapped in try-catch: if the error itself is a DB failure, resolving
// the cors resource (which depends on rule -> DB) would cascade.
// Uses override:true to avoid duplicate headers if init() already set them.
try {
$cors = $utopia->getResource('cors');
foreach ($cors->headers($request->getOrigin()) as $name => $value) {
$response->addHeader($name, $value, override: true);
}
} catch (Throwable) {
// Degrade gracefully - error response without CORS is no worse than before.
}
$response
->addHeader('Cache-Control', 'no-cache, no-store, must-revalidate')
->addHeader('Expires', '0')
+4 -4
View File
@@ -633,7 +633,7 @@ Http::setResource('dbForProject', function (Group $pools, Database $dbForPlatfor
if (\in_array($dsn->getHost(), $sharedTables)) {
$database
->setSharedTables(true)
->setTenant($project->getSequence())
->setTenant((int)$project->getSequence())
->setNamespace($dsn->getParam('namespace'));
} else {
$database
@@ -1054,7 +1054,7 @@ Http::setResource('getProjectDB', function (Group $pools, Database $dbForPlatfor
if (\in_array($dsn->getHost(), $sharedTables)) {
$database
->setSharedTables(true)
->setTenant($project->getSequence())
->setTenant((int)$project->getSequence())
->setNamespace($dsn->getParam('namespace'));
} else {
$database
@@ -1085,7 +1085,7 @@ Http::setResource('getLogsDB', function (Group $pools, Cache $cache, Authorizati
return function (?Document $project = null) use ($pools, $cache, $authorization, &$database) {
if ($database !== null && $project !== null && !$project->isEmpty() && $project->getId() !== 'console') {
$database->setTenant($project->getSequence());
$database->setTenant((int)$project->getSequence());
return $database;
}
@@ -1102,7 +1102,7 @@ Http::setResource('getLogsDB', function (Group $pools, Cache $cache, Authorizati
// set tenant
if ($project !== null && !$project->isEmpty() && $project->getId() !== 'console') {
$database->setTenant($project->getSequence());
$database->setTenant((int)$project->getSequence());
}
return $database;
+1 -20
View File
@@ -125,7 +125,7 @@ if (!function_exists('getProjectDB')) {
if (\in_array($dsn->getHost(), $sharedTables)) {
$database
->setSharedTables(true)
->setTenant($project->getSequence())
->setTenant((int)$project->getSequence())
->setNamespace($dsn->getParam('namespace'));
} else {
$database
@@ -383,22 +383,6 @@ $server->onStart(function () use ($stats, $containerId, &$statsDocument) {
}
});
function cloudRealtimeLogConnectionHostnames(Http $app, Document $project, Request $request): void
{
try {
/** @var array<int, string> $allowed */
$allowed = $app->getResource('allowedHostnames');
Console::info(sprintf(
'[Realtime] project=%s origin=%s allowedHostnames=%s',
$project->getId(),
$request->getOrigin(),
json_encode(array_values($allowed))
));
} catch (Throwable $e) {
Console::error('[Realtime] allowedHostnames log failed: ' . $e->getMessage());
}
}
$server->onWorkerStart(function (int $workerId) use ($server, $register, $stats, $realtime) {
Console::success('Worker ' . $workerId . ' started successfully');
@@ -704,9 +688,6 @@ $server->onOpen(function (int $connection, SwooleRequest $request) use ($server,
*/
$origin = $request->getOrigin();
$originValidator = $app->getResource('originValidator');
cloudRealtimeLogConnectionHostnames($app, $project, $request);
if (!empty($origin) && !$originValidator->isValid($origin) && $project->getId() !== 'console') {
throw new Exception(Exception::REALTIME_POLICY_VIOLATION, $originValidator->getDescription());
+5 -5
View File
@@ -111,7 +111,7 @@ Server::setResource('dbForProject', function (Cache $cache, Registry $register,
if (\in_array($dsn->getHost(), $sharedTables)) {
$database
->setSharedTables(true)
->setTenant($project->getSequence())
->setTenant((int)$project->getSequence())
->setNamespace($dsn->getParam('namespace'));
} else {
$database
@@ -151,7 +151,7 @@ Server::setResource('getProjectDB', function (Group $pools, Database $dbForPlatf
if (\in_array($dsn->getHost(), $sharedTables)) {
$database
->setSharedTables(true)
->setTenant($project->getSequence())
->setTenant((int)$project->getSequence())
->setNamespace($dsn->getParam('namespace'));
} else {
$database
@@ -173,7 +173,7 @@ Server::setResource('getProjectDB', function (Group $pools, Database $dbForPlatf
if (\in_array($dsn->getHost(), $sharedTables)) {
$database
->setSharedTables(true)
->setTenant($project->getSequence())
->setTenant((int)$project->getSequence())
->setNamespace($dsn->getParam('namespace'));
} else {
$database
@@ -196,7 +196,7 @@ Server::setResource('getLogsDB', function (Group $pools, Cache $cache, Authoriza
return function (?Document $project = null) use ($pools, $cache, $database, $authorization) {
if ($database !== null && $project !== null && !$project->isEmpty() && $project->getId() !== 'console') {
$database->setTenant($project->getSequence());
$database->setTenant((int)$project->getSequence());
return $database;
}
@@ -212,7 +212,7 @@ Server::setResource('getLogsDB', function (Group $pools, Cache $cache, Authoriza
->setMaxQueryValues(APP_DATABASE_QUERY_MAX_VALUES_WORKER);
if ($project !== null && !$project->isEmpty() && $project->getId() !== 'console') {
$database->setTenant($project->getSequence());
$database->setTenant((int)$project->getSequence());
}
return $database;
Generated
+6 -6
View File
@@ -3850,16 +3850,16 @@
},
{
"name": "utopia-php/database",
"version": "5.3.17",
"version": "5.3.19",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/database.git",
"reference": "cff2b6ed63d3291b74110d086e16ff089fe05993"
"reference": "72ee1614c37e37c7fdd9d4dc87f1f7cdfa1ca691"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/utopia-php/database/zipball/cff2b6ed63d3291b74110d086e16ff089fe05993",
"reference": "cff2b6ed63d3291b74110d086e16ff089fe05993",
"url": "https://api.github.com/repos/utopia-php/database/zipball/72ee1614c37e37c7fdd9d4dc87f1f7cdfa1ca691",
"reference": "72ee1614c37e37c7fdd9d4dc87f1f7cdfa1ca691",
"shasum": ""
},
"require": {
@@ -3903,9 +3903,9 @@
],
"support": {
"issues": "https://github.com/utopia-php/database/issues",
"source": "https://github.com/utopia-php/database/tree/5.3.17"
"source": "https://github.com/utopia-php/database/tree/5.3.19"
},
"time": "2026-03-20T01:18:52+00:00"
"time": "2026-03-31T15:52:08+00:00"
},
{
"name": "utopia-php/detector",
+12
View File
@@ -244,6 +244,10 @@ services:
- _APP_DATABASE_SHARED_TABLES
- _APP_DATABASE_SHARED_TABLES_V1
- _APP_DATABASE_SHARED_NAMESPACE
- _APP_DATABASE_DOCUMENTSDB_SHARED_TABLES
- _APP_DATABASE_DOCUMENTSDB_SHARED_TABLES_V1
- _APP_DATABASE_VECTORSDB_SHARED_TABLES
- _APP_DATABASE_VECTORSDB_SHARED_TABLES_V1
- _APP_FUNCTIONS_CREATION_ABUSE_LIMIT
- _APP_CUSTOM_DOMAIN_DENY_LIST
- _APP_TRUSTED_HEADERS
@@ -463,6 +467,10 @@ services:
- _APP_EXECUTOR_HOST
- _APP_DATABASE_SHARED_TABLES
- _APP_DATABASE_SHARED_TABLES_V1
- _APP_DATABASE_DOCUMENTSDB_SHARED_TABLES
- _APP_DATABASE_DOCUMENTSDB_SHARED_TABLES_V1
- _APP_DATABASE_VECTORSDB_SHARED_TABLES
- _APP_DATABASE_VECTORSDB_SHARED_TABLES_V1
- _APP_EMAIL_CERTIFICATES
- _APP_MAINTENANCE_RETENTION_AUDIT
- _APP_MAINTENANCE_RETENTION_AUDIT_CONSOLE
@@ -507,6 +515,10 @@ services:
- _APP_WORKERS_NUM
- _APP_QUEUE_NAME
- _APP_DATABASE_SHARED_TABLES
- _APP_DATABASE_DOCUMENTSDB_SHARED_TABLES
- _APP_DATABASE_DOCUMENTSDB_SHARED_TABLES_V1
- _APP_DATABASE_VECTORSDB_SHARED_TABLES
- _APP_DATABASE_VECTORSDB_SHARED_TABLES_V1
appwrite-worker-builds:
entrypoint: worker-builds
@@ -1 +0,0 @@
Export documents to a JSON file from your Appwrite database. This endpoint allows you to export documents to a JSON file stored in a secure internal bucket. You'll receive an email with a download link when the export is complete.
@@ -1 +0,0 @@
Import documents from a JSON file into your Appwrite database. This endpoint allows you to import documents from a JSON file uploaded to Appwrite Storage bucket.
@@ -130,25 +130,10 @@ class Create extends CollectionAction
$indexes[] = new Document($index);
}
try {
// Bootstrap the database metadata without a separate existence
// check to avoid races when multiple first collections are created
// concurrently for the same VectorsDB database.
for ($attempt = 0; $attempt < 5; $attempt++) {
if (!$dbForDatabases->exists(null, Database::METADATA)) {
try {
$dbForDatabases->create();
break;
} catch (DuplicateException) {
break;
} catch (\Throwable $e) {
if ($dbForDatabases->exists(null, Database::METADATA)) {
break;
}
if ($attempt === 4) {
throw $e;
}
\usleep(100_000);
}
}
$dbForDatabases->createCollection(
@@ -61,7 +61,7 @@ class ScheduleFunctions extends ScheduleBase
$nextDate = $cron->getNextRunDate();
$next = DateTime::format($nextDate);
$currentTick = $next <= $timeFrame;
$currentTick = $next < $timeFrame;
if (!$currentTick) {
continue;
@@ -88,7 +88,7 @@ class ScheduleFunctions extends ScheduleBase
$scheduleKey = $delayConfig['key'];
// Ensure schedule was not deleted
if (!\array_key_exists($scheduleKey, $this->schedules)) {
continue;
return;
}
$schedule = $this->schedules[$scheduleKey];
-19
View File
@@ -210,25 +210,6 @@ abstract class Format
return $this->services;
}
protected function getDescriptionContents(?string $description): string
{
if ($description === null || $description === '') {
return '';
}
if (!\str_ends_with($description, '.md')) {
return $description;
}
$contents = @\file_get_contents($description);
if ($contents === false) {
throw new \RuntimeException('Documentation file not found or unreadable: ' . $description);
}
return $contents;
}
protected function getRequestEnumName(string $service, string $method, string $param): ?string
{
/* `$service` is `$namespace` */
@@ -125,7 +125,10 @@ class OpenAPI3 extends Format
$namespace = $sdk->getNamespace() ?? 'default';
$descContents = $this->getDescriptionContents($desc);
if ($desc === null) {
$desc = '';
}
$descContents = \str_ends_with($desc, '.md') ? \file_get_contents($desc) : $desc;
$temp = [
'summary' => $route->getDesc(),
@@ -190,7 +193,7 @@ class OpenAPI3 extends Format
'parameters' => [],
'required' => [],
'responses' => [],
'description' => $this->getDescriptionContents($desc),
'description' => ($desc) ? \file_get_contents($desc) : '',
'demo' => \strtolower($namespace) . '/' . Template::fromCamelCaseToDash($methodObj->getMethodName()) . '.md',
'public' => $methodObj->isPublic(),
];
@@ -126,7 +126,10 @@ class Swagger2 extends Format
$sdkPlatforms = array_values(array_unique($sdkPlatforms));
$namespace = $sdk->getNamespace() ?? 'default';
$descContents = $this->getDescriptionContents($desc);
if ($desc === null) {
$desc = '';
}
$descContents = \str_ends_with($desc, '.md') ? \file_get_contents($desc) : $desc;
$temp = [
'summary' => $route->getDesc(),
@@ -198,7 +201,7 @@ class Swagger2 extends Format
'parameters' => [],
'required' => [],
'responses' => [],
'description' => $this->getDescriptionContents($desc),
'description' => ($desc) ? \file_get_contents($desc) : '',
'demo' => \strtolower($namespace) . '/' . Template::fromCamelCaseToDash($methodObj->getMethodName()) . '.md',
'public' => $methodObj->isPublic(),
];
+9 -9
View File
@@ -17,7 +17,7 @@ class Request extends UtopiaRequest
* @var array<Filter>
*/
private array $filters = [];
private ?Route $route = null;
private static ?Route $route = null;
public function __construct(SwooleRequest $request)
{
@@ -34,11 +34,11 @@ class Request extends UtopiaRequest
{
$parameters = parent::getParams();
if (!$this->hasFilters() || !$this->hasRoute()) {
if (!$this->hasFilters() || !self::hasRoute()) {
return $parameters;
}
$methods = $this->getRoute()?->getLabel('sdk', null);
$methods = self::getRoute()->getLabel('sdk', null);
if (empty($methods)) {
return $parameters;
@@ -131,9 +131,9 @@ class Request extends UtopiaRequest
*
* @return void
*/
public function setRoute(?Route $route): void
public static function setRoute(?Route $route): void
{
$this->route = $route;
self::$route = $route;
}
/**
@@ -141,9 +141,9 @@ class Request extends UtopiaRequest
*
* @return Route|null
*/
public function getRoute(): ?Route
public static function getRoute(): ?Route
{
return $this->route;
return self::$route;
}
/**
@@ -151,9 +151,9 @@ class Request extends UtopiaRequest
*
* @return bool
*/
public function hasRoute(): bool
public static function hasRoute(): bool
{
return $this->route !== null;
return self::$route !== null;
}
/**
+6 -8
View File
@@ -299,7 +299,7 @@ class Response extends SwooleResponse
/**
* @var bool
*/
protected bool $showSensitive = false;
protected static bool $showSensitive = false;
/**
* @var array<string, Model>
@@ -509,7 +509,7 @@ class Response extends SwooleResponse
$isPrivilegedUser = $user->isPrivileged($roles);
$isAppUser = $user->isApp($roles);
if ((!$isPrivilegedUser && !$isAppUser) && !$this->showSensitive) {
if ((!$isPrivilegedUser && !$isAppUser) && !self::$showSensitive) {
$data->setAttribute($key, '');
}
}
@@ -659,20 +659,18 @@ class Response extends SwooleResponse
}
/**
* Wrapper to show sensitive data in response
* Static wrapper to show sensitive data in response
*
* @param callable(): array $callback The callback to show sensitive information for
* @return array
*/
public function showSensitive(callable $callback): array
public static function showSensitive(callable $callback): array
{
$previous = $this->showSensitive;
try {
$this->showSensitive = true;
self::$showSensitive = true;
return $callback();
} finally {
$this->showSensitive = $previous;
self::$showSensitive = false;
}
}
@@ -100,24 +100,6 @@ trait FunctionsBase
'x-appwrite-key' => $this->getProject()['apiKey'],
]));
$this->assertNotEquals(401, $function['headers']['status-code'], 'Auth failed while polling function activation');
if (
($function['body']['deploymentId'] ?? '') !== $deploymentId
&& ($function['body']['latestDeploymentId'] ?? '') === $deploymentId
&& ($function['body']['latestDeploymentStatus'] ?? '') === 'ready'
) {
$activation = $this->updateFunctionDeployment($functionId, $deploymentId);
$this->assertContains(
$activation['headers']['status-code'],
[200, 409],
'Deployment activation request failed: ' . json_encode($activation['body'], JSON_PRETTY_PRINT)
);
if ($activation['headers']['status-code'] === 200) {
$function = $activation;
}
}
$this->assertEquals($deploymentId, $function['body']['deploymentId'] ?? '', 'Deployment is not activated, deployment: ' . json_encode($function['body'], JSON_PRETTY_PRINT));
}, 120000, 500);
}
@@ -3039,8 +3039,21 @@ class RealtimeCustomClientTest extends Scope
$this->assertEquals(200, $update['headers']['status-code']);
$event = json_decode($client->receive(), true);
// Drain WebSocket messages until the .update event arrives.
// Earlier events (e.g. a late-arriving .create from the row seed above) are skipped.
$updateEvent = "tablesdb.{$databaseId}.tables.{$tableId}.rows.{$rowId}.update";
$event = null;
$deadline = \time() + 10;
while (\time() < $deadline) {
$raw = $client->receive();
$msg = json_decode($raw, true);
if (($msg['type'] ?? '') === 'event' && \in_array($updateEvent, $msg['data']['events'] ?? [])) {
$event = $msg;
break;
}
}
$this->assertNotNull($event, 'Timed out waiting for the row update event');
$this->assertArrayHasKey('type', $event);
$this->assertArrayHasKey('data', $event);
$this->assertEquals('event', $event['type']);
-15
View File
@@ -147,21 +147,6 @@ class RequestTest extends TestCase
$this->assertSame('unexpected', $params['extra']);
}
public function testRouteIsScopedToRequestInstance(): void
{
$firstRequest = new Request(new SwooleRequest());
$secondRequest = new Request(new SwooleRequest());
$firstRoute = new Route(Request::METHOD_GET, '/first');
$secondRoute = new Route(Request::METHOD_GET, '/second');
$firstRequest->setRoute($firstRoute);
$secondRequest->setRoute($secondRoute);
$this->assertSame($firstRoute, $firstRequest->getRoute());
$this->assertSame($secondRoute, $secondRequest->getRoute());
}
/**
* Helper to attach a route with multiple SDK methods to the request.
*/
-23
View File
@@ -5,7 +5,6 @@ namespace Tests\Unit\Utopia;
use Appwrite\Utopia\Response;
use Exception;
use PHPUnit\Framework\TestCase;
use ReflectionProperty;
use Swoole\Http\Response as SwooleResponse;
use Tests\Unit\Utopia\Response\Filters\First;
use Tests\Unit\Utopia\Response\Filters\Second;
@@ -177,26 +176,4 @@ class ResponseTest extends TestCase
$this->assertArrayHasKey('required', $single);
$this->assertArrayNotHasKey('hidden', $singleFromArray);
}
public function testShowSensitiveRestoresPreviousState(): void
{
$isShowingSensitive = new ReflectionProperty(Response::class, 'showSensitive');
$this->assertFalse($isShowingSensitive->getValue($this->response));
$payload = $this->response->showSensitive(function () use ($isShowingSensitive) {
return [
'outer' => $isShowingSensitive->getValue($this->response),
'inner' => $this->response->showSensitive(fn () => [
'state' => $isShowingSensitive->getValue($this->response),
]),
'afterInner' => $isShowingSensitive->getValue($this->response),
];
});
$this->assertTrue($payload['outer']);
$this->assertTrue($payload['inner']['state']);
$this->assertTrue($payload['afterInner']);
$this->assertFalse($isShowingSensitive->getValue($this->response));
}
}