From b233aa49873650348df16b5854e03529708ec190 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Fri, 23 Oct 2026 12:26:16 +1300 Subject: [PATCH] fix(cache): report a cache that refuses an invalidation as unavailable When the cache refused the save that blocks or publishes a collection's document epoch, or the purge that releases a writer or finishes its invalidation, the write failed with a plain RuntimeException. Callers could not tell that refusal from a bug: Appwrite Cloud translates Utopia\Cache\Exception\Unavailable into a retryable 503, but these reached clients as a 500 general_unknown (CLOUD-3R8F, CLOUD-3R8D, CLOUD-3R94, CLOUD-3RB1), most recently while a Dragonfly node was reloading its dataset. Throw Unavailable for those refusals, and for the same steps of the query cache. It extends RuntimeException, so existing handlers and the retries around each step behave as before. An owner that belongs to another writer stays a RuntimeException: that is a broken invariant, not an unavailable cache. Unavailable ships in utopia-php/cache 5.2, so require ^5.2. Co-Authored-By: Claude Opus 5.5 --- composer.json | 2 +- composer.lock | 256 +++++++++--------- src/Database/Cache/Query.php | 13 +- src/Database/Trait/Documents.php | 9 +- tests/unit/Cache/QueryTest.php | 3 +- tests/unit/Cache/UnavailableCache.php | 6 +- tests/unit/Documents/CacheUnavailableTest.php | 3 +- 7 files changed, 154 insertions(+), 138 deletions(-) diff --git a/composer.json b/composer.json index e35127263..ae6e6b043 100755 --- a/composer.json +++ b/composer.json @@ -51,7 +51,7 @@ "ext-redis": "*", "utopia-php/validators": "^1.0 || ^2.0", "utopia-php/console": "^0.1 || ^0.2", - "utopia-php/cache": "^4.0 || ^5.0", + "utopia-php/cache": "^5.2", "utopia-php/pools": "^2.0", "utopia-php/mongo": "^1.5.4", "utopia-php/query": "^0.7.2", diff --git a/composer.lock b/composer.lock index 6893e9e93..ec33b8a12 100644 --- a/composer.lock +++ b/composer.lock @@ -4,28 +4,28 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "166a6243504fe68982cde7684c933e3d", + "content-hash": "f0a5261ac168d5060c5bcd2e5f5dacf9", "packages": [ { "name": "brick/math", - "version": "0.14.8", + "version": "1.0.0", "source": { "type": "git", "url": "https://github.com/brick/math.git", - "reference": "63422359a44b7f06cae63c3b429b59e8efcc0629" + "reference": "2effe05d2177c451b86c6a073196a4034c02f211" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/brick/math/zipball/63422359a44b7f06cae63c3b429b59e8efcc0629", - "reference": "63422359a44b7f06cae63c3b429b59e8efcc0629", + "url": "https://api.github.com/repos/brick/math/zipball/2effe05d2177c451b86c6a073196a4034c02f211", + "reference": "2effe05d2177c451b86c6a073196a4034c02f211", "shasum": "" }, "require": { "php": "^8.2" }, "require-dev": { - "php-coveralls/php-coveralls": "^2.2", - "phpstan/phpstan": "2.1.22", + "phpstan/phpstan": "2.2.13", + "phpstan/phpstan-phpunit": "2.0.18", "phpunit/phpunit": "^11.5" }, "type": "library", @@ -56,7 +56,7 @@ ], "support": { "issues": "https://github.com/brick/math/issues", - "source": "https://github.com/brick/math/tree/0.14.8" + "source": "https://github.com/brick/math/tree/1.0.0" }, "funding": [ { @@ -64,20 +64,20 @@ "type": "github" } ], - "time": "2026-02-10T14:33:43+00:00" + "time": "2026-09-12T10:28:18+00:00" }, { "name": "composer/semver", - "version": "3.4.4", + "version": "3.5.2", "source": { "type": "git", "url": "https://github.com/composer/semver.git", - "reference": "198166618906cb2de69b95d7d47e5fa8aa1b2b95" + "reference": "9dfce66f1e97360ea5e6d3e38ab14277e4cc591e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/semver/zipball/198166618906cb2de69b95d7d47e5fa8aa1b2b95", - "reference": "198166618906cb2de69b95d7d47e5fa8aa1b2b95", + "url": "https://api.github.com/repos/composer/semver/zipball/9dfce66f1e97360ea5e6d3e38ab14277e4cc591e", + "reference": "9dfce66f1e97360ea5e6d3e38ab14277e4cc591e", "shasum": "" }, "require": { @@ -119,7 +119,7 @@ "homepage": "http://robbast.nl" } ], - "description": "Semver library that offers utilities, version constraint parsing and validation.", + "description": "Version comparison library that offers utilities, version constraint parsing and validation.", "keywords": [ "semantic", "semver", @@ -129,7 +129,7 @@ "support": { "irc": "ircs://irc.libera.chat:6697/composer", "issues": "https://github.com/composer/semver/issues", - "source": "https://github.com/composer/semver/tree/3.4.4" + "source": "https://github.com/composer/semver/tree/3.5.2" }, "funding": [ { @@ -141,27 +141,27 @@ "type": "github" } ], - "time": "2025-08-20T19:15:30+00:00" + "time": "2026-10-22T15:37:26+00:00" }, { "name": "google/protobuf", - "version": "v4.33.6", + "version": "v5.36.2", "source": { "type": "git", "url": "https://github.com/protocolbuffers/protobuf-php.git", - "reference": "84b008c23915ed94536737eae46f41ba3bccfe67" + "reference": "020bc8e831611869ac387ddd38fbbadd78d15195" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/protocolbuffers/protobuf-php/zipball/84b008c23915ed94536737eae46f41ba3bccfe67", - "reference": "84b008c23915ed94536737eae46f41ba3bccfe67", + "url": "https://api.github.com/repos/protocolbuffers/protobuf-php/zipball/020bc8e831611869ac387ddd38fbbadd78d15195", + "reference": "020bc8e831611869ac387ddd38fbbadd78d15195", "shasum": "" }, "require": { - "php": ">=8.1.0" + "php": ">=8.2.0" }, "require-dev": { - "phpunit/phpunit": ">=10.5.62 <11.0.0" + "phpunit/phpunit": ">=11.5.50 <12.0.0" }, "suggest": { "ext-bcmath": "Need to support JSON deserialization" @@ -183,9 +183,9 @@ "proto" ], "support": { - "source": "https://github.com/protocolbuffers/protobuf-php/tree/v4.33.6" + "source": "https://github.com/protocolbuffers/protobuf-php/tree/v5.36.2" }, - "time": "2026-03-18T17:32:05+00:00" + "time": "2026-09-17T15:56:42+00:00" }, { "name": "nyholm/psr7", @@ -333,16 +333,16 @@ }, { "name": "open-telemetry/api", - "version": "1.9.0", + "version": "1.10.1", "source": { "type": "git", "url": "https://github.com/opentelemetry-php/api.git", - "reference": "6f8d237ce2c304ca85f31970f788e7f074d147be" + "reference": "b2bc26bf8bd6b2b6447b3626b2a1b5733873aa0b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/opentelemetry-php/api/zipball/6f8d237ce2c304ca85f31970f788e7f074d147be", - "reference": "6f8d237ce2c304ca85f31970f788e7f074d147be", + "url": "https://api.github.com/repos/opentelemetry-php/api/zipball/b2bc26bf8bd6b2b6447b3626b2a1b5733873aa0b", + "reference": "b2bc26bf8bd6b2b6447b3626b2a1b5733873aa0b", "shasum": "" }, "require": { @@ -399,7 +399,7 @@ "issues": "https://github.com/open-telemetry/opentelemetry-php/issues", "source": "https://github.com/open-telemetry/opentelemetry-php" }, - "time": "2026-02-25T13:24:05+00:00" + "time": "2026-09-16T01:59:09+00:00" }, { "name": "open-telemetry/context", @@ -526,20 +526,20 @@ }, { "name": "open-telemetry/gen-otlp-protobuf", - "version": "1.9.0", + "version": "1.10.0", "source": { "type": "git", "url": "https://github.com/opentelemetry-php/gen-otlp-protobuf.git", - "reference": "a229cf161d42001d64c8f21e8f678581fe1c66b9" + "reference": "66f04d0e448ad333033bfc7baae1aa56330be088" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/opentelemetry-php/gen-otlp-protobuf/zipball/a229cf161d42001d64c8f21e8f678581fe1c66b9", - "reference": "a229cf161d42001d64c8f21e8f678581fe1c66b9", + "url": "https://api.github.com/repos/opentelemetry-php/gen-otlp-protobuf/zipball/66f04d0e448ad333033bfc7baae1aa56330be088", + "reference": "66f04d0e448ad333033bfc7baae1aa56330be088", "shasum": "" }, "require": { - "google/protobuf": "^3.22 || ^4.0", + "google/protobuf": "^3.22 || ^4.0 || ^5.0", "php": "^8.0" }, "suggest": { @@ -585,20 +585,20 @@ "issues": "https://github.com/open-telemetry/opentelemetry-php/issues", "source": "https://github.com/open-telemetry/opentelemetry-php" }, - "time": "2025-10-19T06:44:33+00:00" + "time": "2026-06-17T12:06:32+00:00" }, { "name": "open-telemetry/sdk", - "version": "1.14.0", + "version": "1.15.0", "source": { "type": "git", "url": "https://github.com/opentelemetry-php/sdk.git", - "reference": "6e3d0ce93e76555dd5e2f1d19443ff45b990e410" + "reference": "77e1aa73850154abb86937d52a70883edc3b4547" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/opentelemetry-php/sdk/zipball/6e3d0ce93e76555dd5e2f1d19443ff45b990e410", - "reference": "6e3d0ce93e76555dd5e2f1d19443ff45b990e410", + "url": "https://api.github.com/repos/opentelemetry-php/sdk/zipball/77e1aa73850154abb86937d52a70883edc3b4547", + "reference": "77e1aa73850154abb86937d52a70883edc3b4547", "shasum": "" }, "require": { @@ -606,7 +606,7 @@ "nyholm/psr7-server": "^1.1", "open-telemetry/api": "^1.8", "open-telemetry/context": "^1.4", - "open-telemetry/sem-conv": "^1.0", + "open-telemetry/sem-conv": "^1.38.0", "php": "^8.1", "php-http/discovery": "^1.14", "psr/http-client": "^1.0", @@ -629,7 +629,10 @@ "spi": { "OpenTelemetry\\API\\Configuration\\ConfigEnv\\EnvComponentLoader": [ "OpenTelemetry\\API\\Instrumentation\\Configuration\\General\\ConfigEnv\\EnvComponentLoaderHttpConfig", - "OpenTelemetry\\API\\Instrumentation\\Configuration\\General\\ConfigEnv\\EnvComponentLoaderPeerConfig" + "OpenTelemetry\\API\\Instrumentation\\Configuration\\General\\ConfigEnv\\EnvComponentLoaderPeerConfig", + "OpenTelemetry\\SDK\\ConfigEnv\\Trace\\SpanSuppressionStrategySemConv", + "OpenTelemetry\\SDK\\ConfigEnv\\Trace\\SpanSuppressionStrategySpanKind", + "OpenTelemetry\\SDK\\ConfigEnv\\Distribution\\DistributionConfigurationSdk" ], "OpenTelemetry\\SDK\\Common\\Configuration\\Resolver\\ResolverInterface": [ "OpenTelemetry\\SDK\\Common\\Configuration\\Resolver\\SdkConfigurationResolver" @@ -639,7 +642,7 @@ ] }, "branch-alias": { - "dev-main": "1.12.x-dev" + "dev-main": "1.14.x-dev" } }, "autoload": { @@ -682,20 +685,20 @@ "issues": "https://github.com/open-telemetry/opentelemetry-php/issues", "source": "https://github.com/open-telemetry/opentelemetry-php" }, - "time": "2026-03-21T11:50:01+00:00" + "time": "2026-07-14T13:09:54+00:00" }, { "name": "open-telemetry/sem-conv", - "version": "1.38.0", + "version": "1.44.0", "source": { "type": "git", "url": "https://github.com/opentelemetry-php/sem-conv.git", - "reference": "e613bc640a407def4991b8a936a9b27edd9a3240" + "reference": "c94bc7ebacef039cda4f4c9270807dcc58b4e3af" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/opentelemetry-php/sem-conv/zipball/e613bc640a407def4991b8a936a9b27edd9a3240", - "reference": "e613bc640a407def4991b8a936a9b27edd9a3240", + "url": "https://api.github.com/repos/opentelemetry-php/sem-conv/zipball/c94bc7ebacef039cda4f4c9270807dcc58b4e3af", + "reference": "c94bc7ebacef039cda4f4c9270807dcc58b4e3af", "shasum": "" }, "require": { @@ -739,7 +742,7 @@ "issues": "https://github.com/open-telemetry/opentelemetry-php/issues", "source": "https://github.com/open-telemetry/opentelemetry-php" }, - "time": "2026-01-21T04:14:03+00:00" + "time": "2026-09-09T22:06:17+00:00" }, { "name": "opis/closure", @@ -1226,20 +1229,20 @@ }, { "name": "ramsey/uuid", - "version": "4.9.2", + "version": "4.9.4", "source": { "type": "git", "url": "https://github.com/ramsey/uuid.git", - "reference": "8429c78ca35a09f27565311b98101e2826affde0" + "reference": "75d73f48d02797c2c285a7e9f348fadc0102ffe2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ramsey/uuid/zipball/8429c78ca35a09f27565311b98101e2826affde0", - "reference": "8429c78ca35a09f27565311b98101e2826affde0", + "url": "https://api.github.com/repos/ramsey/uuid/zipball/75d73f48d02797c2c285a7e9f348fadc0102ffe2", + "reference": "75d73f48d02797c2c285a7e9f348fadc0102ffe2", "shasum": "" }, "require": { - "brick/math": "^0.8.16 || ^0.9 || ^0.10 || ^0.11 || ^0.12 || ^0.13 || ^0.14", + "brick/math": "^0.8.16 || ^0.9 || ^0.10 || ^0.11 || ^0.12 || ^0.13 || ^0.14 || ^0.15 || ^0.16 || ^0.17 || ^0.18 || ^0.19 || ^0.20 || ^1.0", "php": "^8.0", "ramsey/collection": "^1.2 || ^2.0" }, @@ -1298,22 +1301,22 @@ ], "support": { "issues": "https://github.com/ramsey/uuid/issues", - "source": "https://github.com/ramsey/uuid/tree/4.9.2" + "source": "https://github.com/ramsey/uuid/tree/4.9.4" }, - "time": "2025-12-14T04:43:48+00:00" + "time": "2026-09-16T11:39:30+00:00" }, { "name": "symfony/deprecation-contracts", - "version": "v3.6.0", + "version": "v3.7.1", "source": { "type": "git", "url": "https://github.com/symfony/deprecation-contracts.git", - "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62" + "reference": "f3202fa1b5097b0af062dc978b32ecf63404e31d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/63afe740e99a13ba87ec199bb07bbdee937a5b62", - "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/f3202fa1b5097b0af062dc978b32ecf63404e31d", + "reference": "f3202fa1b5097b0af062dc978b32ecf63404e31d", "shasum": "" }, "require": { @@ -1326,7 +1329,7 @@ "name": "symfony/contracts" }, "branch-alias": { - "dev-main": "3.6-dev" + "dev-main": "3.7-dev" } }, "autoload": { @@ -1351,7 +1354,7 @@ "description": "A generic function and convention to trigger deprecation notices", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/deprecation-contracts/tree/v3.6.0" + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.7.1" }, "funding": [ { @@ -1362,25 +1365,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-09-25T14:21:43+00:00" + "time": "2026-06-05T06:23:12+00:00" }, { "name": "symfony/http-client", - "version": "v7.4.8", + "version": "v7.4.21", "source": { "type": "git", "url": "https://github.com/symfony/http-client.git", - "reference": "01933e626c3de76bea1e22641e205e78f6a34342" + "reference": "121bf3a973bfd852db85ec32d0d64e9fbed4449b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-client/zipball/01933e626c3de76bea1e22641e205e78f6a34342", - "reference": "01933e626c3de76bea1e22641e205e78f6a34342", + "url": "https://api.github.com/repos/symfony/http-client/zipball/121bf3a973bfd852db85ec32d0d64e9fbed4449b", + "reference": "121bf3a973bfd852db85ec32d0d64e9fbed4449b", "shasum": "" }, "require": { @@ -1448,7 +1455,7 @@ "http" ], "support": { - "source": "https://github.com/symfony/http-client/tree/v7.4.8" + "source": "https://github.com/symfony/http-client/tree/v7.4.21" }, "funding": [ { @@ -1468,20 +1475,20 @@ "type": "tidelift" } ], - "time": "2026-03-30T12:55:43+00:00" + "time": "2026-10-13T15:31:38+00:00" }, { "name": "symfony/http-client-contracts", - "version": "v3.6.0", + "version": "v3.7.3", "source": { "type": "git", "url": "https://github.com/symfony/http-client-contracts.git", - "reference": "75d7043853a42837e68111812f4d964b01e5101c" + "reference": "35be0019e2c2c9fba80f9dc033290a5240f7b44f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-client-contracts/zipball/75d7043853a42837e68111812f4d964b01e5101c", - "reference": "75d7043853a42837e68111812f4d964b01e5101c", + "url": "https://api.github.com/repos/symfony/http-client-contracts/zipball/35be0019e2c2c9fba80f9dc033290a5240f7b44f", + "reference": "35be0019e2c2c9fba80f9dc033290a5240f7b44f", "shasum": "" }, "require": { @@ -1494,7 +1501,7 @@ "name": "symfony/contracts" }, "branch-alias": { - "dev-main": "3.6-dev" + "dev-main": "3.7-dev" } }, "autoload": { @@ -1530,7 +1537,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/http-client-contracts/tree/v3.6.0" + "source": "https://github.com/symfony/http-client-contracts/tree/v3.7.3" }, "funding": [ { @@ -1541,25 +1548,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-04-29T11:18:49+00:00" + "time": "2026-08-04T08:41:16+00:00" }, { "name": "symfony/polyfill-mbstring", - "version": "v1.37.0", + "version": "v1.43.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "6a21eb99c6973357967f6ce3708cd55a6bec6315" + "reference": "4d5d331bf731e0aedbc6b2f5e61135b7b7996a72" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/6a21eb99c6973357967f6ce3708cd55a6bec6315", - "reference": "6a21eb99c6973357967f6ce3708cd55a6bec6315", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/4d5d331bf731e0aedbc6b2f5e61135b7b7996a72", + "reference": "4d5d331bf731e0aedbc6b2f5e61135b7b7996a72", "shasum": "" }, "require": { @@ -1611,7 +1622,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.37.0" + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.43.0" }, "funding": [ { @@ -1631,20 +1642,20 @@ "type": "tidelift" } ], - "time": "2026-04-10T17:25:58+00:00" + "time": "2026-09-24T15:19:04+00:00" }, { "name": "symfony/polyfill-php82", - "version": "v1.37.0", + "version": "v1.43.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php82.git", - "reference": "34808efe3e68f69685796f7c253a2f1d8ea9df59" + "reference": "843bbe8b2a7934307b749751eede0f111f5d1cfc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php82/zipball/34808efe3e68f69685796f7c253a2f1d8ea9df59", - "reference": "34808efe3e68f69685796f7c253a2f1d8ea9df59", + "url": "https://api.github.com/repos/symfony/polyfill-php82/zipball/843bbe8b2a7934307b749751eede0f111f5d1cfc", + "reference": "843bbe8b2a7934307b749751eede0f111f5d1cfc", "shasum": "" }, "require": { @@ -1691,7 +1702,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php82/tree/v1.37.0" + "source": "https://github.com/symfony/polyfill-php82/tree/v1.43.0" }, "funding": [ { @@ -1711,20 +1722,20 @@ "type": "tidelift" } ], - "time": "2026-04-10T16:19:22+00:00" + "time": "2026-09-25T15:50:05+00:00" }, { "name": "symfony/polyfill-php83", - "version": "v1.37.0", + "version": "v1.43.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php83.git", - "reference": "3600c2cb22399e25bb226e4a135ce91eeb2a6149" + "reference": "80ccff923a8d61f73ebe9da0aa94e25232f004ce" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/3600c2cb22399e25bb226e4a135ce91eeb2a6149", - "reference": "3600c2cb22399e25bb226e4a135ce91eeb2a6149", + "url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/80ccff923a8d61f73ebe9da0aa94e25232f004ce", + "reference": "80ccff923a8d61f73ebe9da0aa94e25232f004ce", "shasum": "" }, "require": { @@ -1771,7 +1782,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php83/tree/v1.37.0" + "source": "https://github.com/symfony/polyfill-php83/tree/v1.43.0" }, "funding": [ { @@ -1791,20 +1802,20 @@ "type": "tidelift" } ], - "time": "2026-04-10T17:25:58+00:00" + "time": "2026-09-25T15:50:05+00:00" }, { "name": "symfony/service-contracts", - "version": "v3.6.1", + "version": "v3.7.3", "source": { "type": "git", "url": "https://github.com/symfony/service-contracts.git", - "reference": "45112560a3ba2d715666a509a0bc9521d10b6c43" + "reference": "15e6a07ec2a2c75ceb1b21dd98105ee8456d2257" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/service-contracts/zipball/45112560a3ba2d715666a509a0bc9521d10b6c43", - "reference": "45112560a3ba2d715666a509a0bc9521d10b6c43", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/15e6a07ec2a2c75ceb1b21dd98105ee8456d2257", + "reference": "15e6a07ec2a2c75ceb1b21dd98105ee8456d2257", "shasum": "" }, "require": { @@ -1822,7 +1833,7 @@ "name": "symfony/contracts" }, "branch-alias": { - "dev-main": "3.6-dev" + "dev-main": "3.7-dev" } }, "autoload": { @@ -1858,7 +1869,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/service-contracts/tree/v3.6.1" + "source": "https://github.com/symfony/service-contracts/tree/v3.7.3" }, "funding": [ { @@ -1878,7 +1889,7 @@ "type": "tidelift" } ], - "time": "2025-07-15T11:30:57+00:00" + "time": "2026-07-27T15:39:01+00:00" }, { "name": "tbachert/spi", @@ -1996,22 +2007,22 @@ }, { "name": "utopia-php/cache", - "version": "4.0.2", + "version": "5.2.0", "source": { "type": "git", "url": "https://github.com/utopia-php/cache.git", - "reference": "92e02dab63606234b993b841ebf4c58845dd4620" + "reference": "05a631796066a7cf96da289df358909fa2190ebd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/cache/zipball/92e02dab63606234b993b841ebf4c58845dd4620", - "reference": "92e02dab63606234b993b841ebf4c58845dd4620", + "url": "https://api.github.com/repos/utopia-php/cache/zipball/05a631796066a7cf96da289df358909fa2190ebd", + "reference": "05a631796066a7cf96da289df358909fa2190ebd", "shasum": "" }, "require": { "ext-json": "*", "php": ">=8.4", - "utopia-php/circuit-breaker": "^0.3", + "utopia-php/circuit-breaker": "^0.4", "utopia-php/pools": "^2.0", "utopia-php/telemetry": "^0.4" }, @@ -2019,6 +2030,7 @@ "swoole/ide-helper": "^6.0" }, "suggest": { + "ext-igbinary": "Required for the Igbinary codec.", "ext-memcached": "Required for the Memcached and Hazelcast adapters.", "ext-redis": "Required for the Redis, RedisCluster and Sharding adapters.", "ext-swoole": "Required for the multiplexing Redis adapter (>=6.0)." @@ -2026,7 +2038,7 @@ "type": "library", "autoload": { "psr-4": { - "Utopia\\Cache\\": "src/Cache" + "Utopia\\Cache\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -2049,22 +2061,22 @@ ], "support": { "issues": "https://github.com/utopia-php/cache/issues", - "source": "https://github.com/utopia-php/cache/tree/4.0.2" + "source": "https://github.com/utopia-php/cache/tree/5.2.0" }, - "time": "2026-08-12T07:48:59+00:00" + "time": "2026-10-19T05:47:09+00:00" }, { "name": "utopia-php/circuit-breaker", - "version": "0.3.2", + "version": "0.4.1", "source": { "type": "git", "url": "https://github.com/utopia-php/circuit-breaker.git", - "reference": "5fbc3802471b0d1b4260bd9f5544514e6929b481" + "reference": "51c9b9b0de6754af4da6364da0f3a5d7e940ef2c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/circuit-breaker/zipball/5fbc3802471b0d1b4260bd9f5544514e6929b481", - "reference": "5fbc3802471b0d1b4260bd9f5544514e6929b481", + "url": "https://api.github.com/repos/utopia-php/circuit-breaker/zipball/51c9b9b0de6754af4da6364da0f3a5d7e940ef2c", + "reference": "51c9b9b0de6754af4da6364da0f3a5d7e940ef2c", "shasum": "" }, "require": { @@ -2108,9 +2120,9 @@ ], "support": { "issues": "https://github.com/utopia-php/circuit-breaker/issues", - "source": "https://github.com/utopia-php/circuit-breaker/tree/0.3.2" + "source": "https://github.com/utopia-php/circuit-breaker/tree/0.4.1" }, - "time": "2026-08-05T18:07:20+00:00" + "time": "2026-09-16T10:12:28+00:00" }, { "name": "utopia-php/console", @@ -2322,20 +2334,19 @@ }, { "name": "utopia-php/telemetry", - "version": "0.4.6", + "version": "0.4.12", "source": { "type": "git", "url": "https://github.com/utopia-php/telemetry.git", - "reference": "f96778a01792c32df0876fe1f38a79b9588445d8" + "reference": "e628707fe262dc0ca418fbe6f5e421f2db719907" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/telemetry/zipball/f96778a01792c32df0876fe1f38a79b9588445d8", - "reference": "f96778a01792c32df0876fe1f38a79b9588445d8", + "url": "https://api.github.com/repos/utopia-php/telemetry/zipball/e628707fe262dc0ca418fbe6f5e421f2db719907", + "reference": "e628707fe262dc0ca418fbe6f5e421f2db719907", "shasum": "" }, "require": { - "ext-protobuf": "*", "nyholm/psr7": "1.*", "open-telemetry/exporter-otlp": "1.*", "open-telemetry/sdk": "1.*", @@ -2346,13 +2357,14 @@ "swoole/ide-helper": "6.*" }, "suggest": { + "ext-protobuf": "For better performance, when dealing with the protobuf format", "ext-sockets": "Required for the Swoole transport implementation", "ext-swoole": "Required for the Swoole transport implementation" }, "type": "library", "autoload": { "psr-4": { - "Utopia\\Telemetry\\": "src/Telemetry" + "Utopia\\Telemetry\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -2367,9 +2379,9 @@ ], "support": { "issues": "https://github.com/utopia-php/telemetry/issues", - "source": "https://github.com/utopia-php/telemetry/tree/0.4.6" + "source": "https://github.com/utopia-php/telemetry/tree/0.4.12" }, - "time": "2026-08-05T17:56:48+00:00" + "time": "2026-09-28T10:14:23+00:00" }, { "name": "utopia-php/validators", diff --git a/src/Database/Cache/Query.php b/src/Database/Cache/Query.php index 6f962c3db..dbc499b2c 100644 --- a/src/Database/Cache/Query.php +++ b/src/Database/Cache/Query.php @@ -5,6 +5,7 @@ use InvalidArgumentException; use RuntimeException; use Utopia\Cache\Cache; +use Utopia\Cache\Exception\Unavailable; use Utopia\Database\Document; /** @@ -209,11 +210,11 @@ public function createToken(): string public function blockCollection(string $key, string $token): void { if (! (new Owners($this->cache))->register($key, $token)) { - throw new RuntimeException("Failed to register query cache owner for '{$key}'"); + throw new Unavailable("Failed to register query cache owner for '{$key}'"); } if ($this->cache->save($this->getEpochKey($key), self::BLOCKED_PREFIX.$token.self::SEPARATOR.\time()) === false) { - throw new RuntimeException("Failed to block query cache epoch for '{$key}'"); + throw new Unavailable("Failed to block query cache epoch for '{$key}'"); } $this->cache->purge($this->getStartedKey($key)); @@ -237,7 +238,7 @@ public function activateCollection(string $key, string $token, int $writerTimeou if ($owned && ! $this->cache->purge($registration->key, $registration->field)) { $owner = $this->cache->load($registration->key, self::PERMANENT, $registration->field); if ($owner !== false && $owner !== null) { - throw new RuntimeException("Failed to release query cache owner for '{$key}'"); + throw new Unavailable("Failed to release query cache owner for '{$key}'"); } $owned = false; } @@ -277,7 +278,7 @@ public function activateCollection(string $key, string $token, int $writerTimeou } if ($nextFinished === $finished && $this->isTombstoneOf($this->cache->load($epochKey, self::PERMANENT), $token)) { - throw new RuntimeException("Failed to finish query cache invalidation for '{$key}'"); + throw new Unavailable("Failed to finish query cache invalidation for '{$key}'"); } if ($registration->field !== '' && $this->releaseAbandonedOwners($registration->key, $writerTimeout)) { @@ -288,7 +289,7 @@ public function activateCollection(string $key, string $token, int $writerTimeou public function flush(): void { if (! $this->cache->flush()) { - throw new RuntimeException('Failed to flush query cache'); + throw new Unavailable('Failed to flush query cache'); } } @@ -411,7 +412,7 @@ private function publish(string $key, string $finished): void $epoch = self::ACTIVE_PREFIX.\bin2hex(\random_bytes(16)).self::SEPARATOR.$finished; if ($this->cache->save($this->getEpochKey($key), $epoch) === false) { - throw new RuntimeException("Failed to activate query cache for '{$key}'"); + throw new Unavailable("Failed to activate query cache for '{$key}'"); } } diff --git a/src/Database/Trait/Documents.php b/src/Database/Trait/Documents.php index c73dbef17..478602787 100644 --- a/src/Database/Trait/Documents.php +++ b/src/Database/Trait/Documents.php @@ -8,6 +8,7 @@ use Generator; use RuntimeException; use Throwable; +use Utopia\Cache\Exception\Unavailable as CacheUnavailable; use Utopia\Console; use Utopia\Database\Adapter; use Utopia\Database\Adapter\Feature; @@ -3799,7 +3800,7 @@ private function blockDocumentCacheEpoch(string $collectionKey, string $token, s $this->withRetries(function () use ($epochKey, $token): void { if ($this->cache->save($epochKey, self::DOCUMENT_CACHE_BLOCKED_PREFIX.$token.self::DOCUMENT_CACHE_SEPARATOR.\time()) === false) { - throw new RuntimeException("Failed to block document cache epoch '{$epochKey}'"); + throw new CacheUnavailable("Failed to block document cache epoch '{$epochKey}'"); } }); @@ -3920,7 +3921,7 @@ private function publishDocumentCacheEpoch(string $collectionKey, string $starte $epoch = self::DOCUMENT_CACHE_ACTIVE_PREFIX.\bin2hex(\random_bytes(16)).self::DOCUMENT_CACHE_SEPARATOR.$started; $this->withRetries(function () use ($epochKey, $epoch): void { if ($this->cache->save($epochKey, $epoch) === false) { - throw new RuntimeException("Failed to activate document cache epoch '{$epochKey}'"); + throw new CacheUnavailable("Failed to activate document cache epoch '{$epochKey}'"); } }); @@ -3941,7 +3942,7 @@ private function releaseDocumentCacheOwner(Registration $registration, string $c $owner = $this->cache->load($registration->key, self::TTL, $registration->field); if ($owner !== false && $owner !== null) { - throw new RuntimeException("Failed to release document cache owner '{$token}' for '{$collectionKey}'"); + throw new CacheUnavailable("Failed to release document cache owner '{$token}' for '{$collectionKey}'"); } $released = false; }); @@ -3975,7 +3976,7 @@ private function finishDocumentCacheWrite(string $collectionKey, string $finishe && \is_string($epoch) && $this->cache->load($epochKey, self::DOCUMENT_CACHE_PERMANENT) === $epoch ) { - throw new RuntimeException("Failed to finish document cache invalidation '{$epochKey}'"); + throw new CacheUnavailable("Failed to finish document cache invalidation '{$epochKey}'"); } }); diff --git a/tests/unit/Cache/QueryTest.php b/tests/unit/Cache/QueryTest.php index 517df759f..ac7de9b0a 100644 --- a/tests/unit/Cache/QueryTest.php +++ b/tests/unit/Cache/QueryTest.php @@ -7,6 +7,7 @@ use PHPUnit\Framework\TestCase; use Utopia\Cache\Adapter\Memory; use Utopia\Cache\Cache; +use Utopia\Cache\Exception\Unavailable; use Utopia\Database\Attribute; use Utopia\Database\Cache\Entry; use Utopia\Database\Cache\Invalidator; @@ -940,7 +941,7 @@ public function save(string $key, mixed $data, string $hash = '', int $ttl = 0): } }); - $this->expectException(\RuntimeException::class); + $this->expectException(Unavailable::class); $this->expectExceptionMessage('Failed to block query cache epoch'); $queryCache->invalidateCollection(new Scope(), 'users'); } diff --git a/tests/unit/Cache/UnavailableCache.php b/tests/unit/Cache/UnavailableCache.php index c207cb840..d3341684c 100644 --- a/tests/unit/Cache/UnavailableCache.php +++ b/tests/unit/Cache/UnavailableCache.php @@ -2,8 +2,8 @@ namespace Tests\Unit\Cache; -use RuntimeException; use Utopia\Cache\Adapter as CacheAdapter; +use Utopia\Cache\Exception\Unavailable; use Utopia\Cache\Feature\Leasable; /** @@ -70,11 +70,11 @@ private function confirmPurge(): void if ($this->failingPurges > 0) { $this->failingPurges--; - throw new RuntimeException('Cache purge could not be confirmed'); + throw new Unavailable('Cache purge could not be confirmed'); } if (! $this->writable()) { - throw new RuntimeException('Cache purge could not be confirmed'); + throw new Unavailable('Cache purge could not be confirmed'); } } diff --git a/tests/unit/Documents/CacheUnavailableTest.php b/tests/unit/Documents/CacheUnavailableTest.php index b0bd57f3f..2bb1ccc3d 100644 --- a/tests/unit/Documents/CacheUnavailableTest.php +++ b/tests/unit/Documents/CacheUnavailableTest.php @@ -9,6 +9,7 @@ use Tests\Unit\Support\UncachedTwin; use Throwable; use Utopia\Cache\Cache; +use Utopia\Cache\Exception\Unavailable; use Utopia\Database\Adapter\Memory as DatabaseMemory; use Utopia\Database\Attribute; use Utopia\Database\Collection; @@ -63,7 +64,7 @@ public function testAWriteTheCacheCannotInvalidateIsRolledBack(string $outage, C $failure = $this->attempt(fn (): mixed => $write($database)); $cache->recover(); - $this->assertInstanceOf(Throwable::class, $failure, 'A write whose cache invalidation could not be confirmed must fail'); + $this->assertInstanceOf(Unavailable::class, $failure, 'A write whose cache invalidation could not be confirmed must fail as unavailable, so a caller can tell it was refused'); $this->assertSame('original', $this->stored($database), 'The write must be rolled back'); $this->assertSame('original', $this->cached($database));