From da763bd55fc5310a9b460e8eee3a0dc0a490ce46 Mon Sep 17 00:00:00 2001 From: Chirag Aggarwal Date: Tue, 19 May 2026 15:18:35 +0530 Subject: [PATCH] feat(sdks): add isomorphic spec platform MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds an `isomorphic` virtual SDK platform whose spec is the union of the `client` and `server` specs. Lets a single SDK release (e.g. the web SDK) surface methods from both runtimes — including ones that are hidden from the `server` spec or auth-excluded from it. - `APP_SDK_PLATFORM_ISOMORPHIC` constant - Specs.php emits `swagger2--isomorphic.json` (and OpenAPI3) using a union filter; `getKeys()` / `getAuthCounts()` extended accordingly - SDKs.php honors an optional `specPlatform` per-SDK config override so a server-family SDK can point at the isomorphic spec - Format/Swagger2 + OpenAPI3 use a shared `effectivePlatforms()` helper so the additional-methods filter expands isomorphic to client+server, preserving rename pairs like `createMfaAuthenticator` ↔ `createMFAAuthenticator` --- app/init/constants.php | 1 + src/Appwrite/Platform/Tasks/SDKs.php | 3 +- src/Appwrite/Platform/Tasks/Specs.php | 40 ++++++++++++++++--- src/Appwrite/SDK/Specification/Format.php | 16 ++++++++ .../SDK/Specification/Format/OpenAPI3.php | 2 +- .../SDK/Specification/Format/Swagger2.php | 2 +- 6 files changed, 56 insertions(+), 8 deletions(-) diff --git a/app/init/constants.php b/app/init/constants.php index b271b56a14..a3d71a5819 100644 --- a/app/init/constants.php +++ b/app/init/constants.php @@ -103,6 +103,7 @@ const APP_SDK_PLATFORM_SERVER = 'server'; const APP_SDK_PLATFORM_CLIENT = 'client'; const APP_SDK_PLATFORM_CONSOLE = 'console'; const APP_SDK_PLATFORM_STATIC = 'static'; +const APP_SDK_PLATFORM_ISOMORPHIC = 'isomorphic'; const APP_VCS_GITHUB_USERNAME = 'Appwrite'; const APP_VCS_GITHUB_EMAIL = 'team@appwrite.io'; const APP_VCS_GITHUB_URL = 'https://github.com/TeamAppwrite'; diff --git a/src/Appwrite/Platform/Tasks/SDKs.php b/src/Appwrite/Platform/Tasks/SDKs.php index 5d52d905f6..0a9dd1533d 100644 --- a/src/Appwrite/Platform/Tasks/SDKs.php +++ b/src/Appwrite/Platform/Tasks/SDKs.php @@ -308,7 +308,8 @@ class SDKs extends Action } else { Console::log(' Fetching API spec...'); - $specPath = __DIR__ . '/../../../../app/config/specs/swagger2-' . $version . '-' . $language['family'] . '.json'; + $specPlatform = $language['specPlatform'] ?? $language['family']; + $specPath = __DIR__ . '/../../../../app/config/specs/swagger2-' . $version . '-' . $specPlatform . '.json'; if (!file_exists($specPath)) { throw new \Exception('Spec file not found: ' . $specPath . '. Please run "docker compose exec appwrite specs --version=' . $version . '" first to generate the specs.'); diff --git a/src/Appwrite/Platform/Tasks/Specs.php b/src/Appwrite/Platform/Tasks/Specs.php index c8120bd017..533f0e45b0 100644 --- a/src/Appwrite/Platform/Tasks/Specs.php +++ b/src/Appwrite/Platform/Tasks/Specs.php @@ -79,9 +79,28 @@ class Specs extends Action APP_SDK_PLATFORM_CLIENT, APP_SDK_PLATFORM_SERVER, APP_SDK_PLATFORM_CONSOLE, + APP_SDK_PLATFORM_ISOMORPHIC, ]; } + /** + * Real platforms whose filters apply to a given (possibly virtual) platform. + * + * The isomorphic platform is a virtual union of `client` and `server` — its spec includes + * any route or service that would normally be emitted in either platform's spec. All other + * platforms map to themselves. + * + * @return array + */ + private static function effectivePlatformsFor(string $platform): array + { + if ($platform === APP_SDK_PLATFORM_ISOMORPHIC) { + return [APP_SDK_PLATFORM_CLIENT, APP_SDK_PLATFORM_SERVER]; + } + + return [$platform]; + } + /** * Platforms to include in PR creation. * Override in a subclass to exclude specific platforms. @@ -121,6 +140,7 @@ class Specs extends Action 'client' => 1, 'server' => 2, 'console' => 1, + APP_SDK_PLATFORM_ISOMORPHIC => 1, ]; } @@ -131,7 +151,7 @@ class Specs extends Action */ protected function getKeys(): array { - return [ + $keys = [ APP_SDK_PLATFORM_CLIENT => [ 'Project' => [ 'type' => 'apiKey', @@ -325,6 +345,12 @@ class Specs extends Action ], ], ]; + + // Isomorphic platform shares the auth surface of both client and server runtimes; the + // generated SDK picks one auth path at construction time via its factories. + $keys[APP_SDK_PLATFORM_ISOMORPHIC] = $keys[APP_SDK_PLATFORM_CLIENT] + $keys[APP_SDK_PLATFORM_SERVER]; + + return $keys; } protected function verifyParsedSpec(array $spec): void @@ -542,6 +568,7 @@ class Specs extends Action $routes = []; $models = []; $services = []; + $effectivePlatforms = static::effectivePlatformsFor($platform); foreach ($appRoutes as $key => $method) { foreach ($method as $route) { @@ -559,7 +586,10 @@ class Specs extends Action /** @var Method $sdk */ $hide = $sdk->isHidden(); - if ($hide === true || (\is_array($hide) && \in_array($platform, $hide))) { + // A method is hidden from a (possibly virtual) platform when it is hidden + // from every real platform that platform expands to. For real platforms + // this matches the legacy single-element check. + if ($hide === true || (\is_array($hide) && empty(\array_diff($effectivePlatforms, $hide)))) { continue; } @@ -582,7 +612,7 @@ class Specs extends Action continue; } - if (!\in_array($platform, $sdkPlatforms)) { + if (empty(\array_intersect($effectivePlatforms, $sdkPlatforms))) { continue; } @@ -601,8 +631,8 @@ class Specs extends Action continue; } - // Check if current platform is included in service's platforms - if (!\in_array($platform, $service['platforms'] ?? [])) { + // Check if any effective platform is included in service's platforms + if (empty(\array_intersect($effectivePlatforms, $service['platforms'] ?? []))) { continue; } diff --git a/src/Appwrite/SDK/Specification/Format.php b/src/Appwrite/SDK/Specification/Format.php index 60884323c7..cb239482ab 100644 --- a/src/Appwrite/SDK/Specification/Format.php +++ b/src/Appwrite/SDK/Specification/Format.php @@ -125,6 +125,22 @@ abstract class Format $this->enumBlacklist = $this->buildEnumBlacklist(); } + /** + * Real platforms whose filters apply to this (possibly virtual) format platform. + * + * The isomorphic platform expands to client + server; all others map to themselves. + * + * @return array + */ + protected function effectivePlatforms(): array + { + if ($this->platform === APP_SDK_PLATFORM_ISOMORPHIC) { + return [APP_SDK_PLATFORM_CLIENT, APP_SDK_PLATFORM_SERVER]; + } + + return [$this->platform]; + } + protected function buildEnumBlacklist(): array { $blacklist = []; diff --git a/src/Appwrite/SDK/Specification/Format/OpenAPI3.php b/src/Appwrite/SDK/Specification/Format/OpenAPI3.php index 5fb13e853f..86275687bd 100644 --- a/src/Appwrite/SDK/Specification/Format/OpenAPI3.php +++ b/src/Appwrite/SDK/Specification/Format/OpenAPI3.php @@ -172,7 +172,7 @@ class OpenAPI3 extends Format $methodSecurities = $methodObj->getAuth(); $methodSdkPlatforms = $specs->getSDKPlatformsForRouteSecurity($methodSecurities); - if (!\in_array($this->platform, $methodSdkPlatforms)) { + if (empty(\array_intersect($this->effectivePlatforms(), $methodSdkPlatforms))) { continue; } diff --git a/src/Appwrite/SDK/Specification/Format/Swagger2.php b/src/Appwrite/SDK/Specification/Format/Swagger2.php index df6cb5bf3a..7445499a59 100644 --- a/src/Appwrite/SDK/Specification/Format/Swagger2.php +++ b/src/Appwrite/SDK/Specification/Format/Swagger2.php @@ -179,7 +179,7 @@ class Swagger2 extends Format $methodSecurities = $methodObj->getAuth(); $methodSdkPlatforms = $specs->getSDKPlatformsForRouteSecurity($methodSecurities); - if (!\in_array($this->platform, $methodSdkPlatforms)) { + if (empty(\array_intersect($this->effectivePlatforms(), $methodSdkPlatforms))) { continue; }