Skip to content

feat: Isomorphic SDKs#1512

Open
ChiragAgg5k wants to merge 51 commits intomasterfrom
feat/isomorphic-sdks
Open

feat: Isomorphic SDKs#1512
ChiragAgg5k wants to merge 51 commits intomasterfrom
feat/isomorphic-sdks

Conversation

@ChiragAgg5k
Copy link
Copy Markdown
Member

@ChiragAgg5k ChiragAgg5k commented May 7, 2026

Summary

Implements the isomorphic SDK client pattern across the generated Web, Flutter, Apple, and Android SDKs. Each SDK now exposes explicit auth factory methods for the recommended setup path while preserving the existing Client() constructor and setter style for backwards compatibility.

This consolidates the work from #1481, #1510, and #1511, and extends the same client SDK pattern to Android.

Web SDK

The generated Web SDK now uses one typed generic Client across browser, server, and console output. Static factories describe both runtime and auth capability, and generated services narrow their available methods from the client auth type.

import { Client, Account, TablesDB } from 'appwrite';

const browserClient = Client.fromBrowser({
  endpoint: 'https://<REGION>.cloud.appwrite.io/v1',
  projectId: '<PROJECT_ID>',
});

const serverClient = Client.fromAPIKey({
  endpoint: 'https://<REGION>.cloud.appwrite.io/v1',
  projectId: '<PROJECT_ID>',
  apiKey: '<API_KEY>',
});

new Account(browserClient);              // OK
new TablesDB(browserClient).createRow;   // OK
new TablesDB(browserClient).createTable; // Type error for browser auth
new TablesDB(serverClient).createTable;  // OK

Available Web factories include fromBrowser, fromSession, fromDevKey, fromImpersonation, fromAPIKey, fromJWT, and fromCookie, with server-only factories omitted from client-platform output.

Flutter SDK

The generated Flutter client SDK now exposes a ClientAuth interface returned by factory-created clients. Services and Realtime accept ClientAuth, so the construction syntax stays the same while legacy setters are hidden from factory-created clients.

import 'package:appwrite/appwrite.dart';

final client = Client.fromBrowser(
  endPoint: 'https://<REGION>.cloud.appwrite.io/v1',
  projectId: '<PROJECT_ID>',
);

Account(client);      // OK
Realtime(client);     // OK
client.setProject('other'); // Analyzer error

Available Flutter factories are fromBrowser, fromSession, fromDevKey, and fromImpersonation. This is intentionally scoped to Flutter client output and does not add server auth factories to Flutter.

Apple SDK

The generated Apple SDK now exposes a ClientAuth protocol returned by factory-created clients. Generated services and Realtime accept ClientAuth, and services read auth values through getConfig(key:) instead of exposing the raw config dictionary.

let client = Client.fromBrowser(
    projectId: "<PROJECT_ID>"
)

let sessionClient = Client.fromSession(
    projectId: "<PROJECT_ID>",
    session: "<SESSION>"
)

let tables = TablesDB(client)
let realtime = Realtime(client)

Available Apple factories are fromBrowser, fromSession, fromDevKey, and throwing fromImpersonation. All factories accept optional endpoint, realtime endpoint, locale, self-signed, and compression values. fromImpersonation throws an AppwriteError when callers pass zero or multiple impersonation targets.

Android SDK

The generated Android SDK now exposes a ClientAuth interface returned by factory-created clients. Generated services and Realtime accept ClientAuth, while the legacy concrete Client keeps the existing constructor and setter surface.

import io.appwrite.Client
import io.appwrite.services.Account
import io.appwrite.services.Realtime

val client = Client.fromBrowser(
    context = context,
    projectId = "<PROJECT_ID>",
    endpoint = "https://<REGION>.cloud.appwrite.io/v1"
)

Account(client)   // OK
Realtime(client)  // OK
client.setProject("other") // Compile error on ClientAuth

Available Android factories are fromBrowser, fromSession, fromDevKey, and fromImpersonation. Factories accept optional endpoint, realtime endpoint, locale, and self-signed values. fromImpersonation validates that exactly one of userId, userEmail, or userPhone is provided.

Backwards Compatibility

Existing constructor/setter setup remains supported in all four SDKs:

const webClient = new Client()
  .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
  .setProject('<PROJECT_ID>');
final flutterClient = Client()
  ..setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
  ..setProject('<PROJECT_ID>');
let appleClient = Client()
    .setEndpoint("https://cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
val androidClient = Client(context)
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")

Those legacy setters are marked deprecated and remain available from direct Client() instances. Factory-created clients intentionally expose the narrower auth surface.

Implementation Notes

  • Adds auth capability typing/protocols/interfaces and static factories to Web, Flutter, Apple, and Android client templates.
  • Keeps service construction syntax unchanged across all four SDKs.
  • Hides service .client internals from the public generated service surface where supported by the SDK language.
  • Updates Realtime templates to accept factory-created clients.
  • Marks legacy setters as deprecated without removing them.
  • Generates Web factory base params from spec-backed global headers instead of hardcoded template branches.
  • Adds SDK build validation coverage for the combined generated surfaces.

Validation

Ran on the consolidated branch:

php example.php web server
php example.php web client
php example.php web console
php example.php flutter client
php example.php apple client
php example.php android client
composer lint-twig
php -l src/SDK/Language/Web.php
php -l src/SDK/Language/Flutter.php
php -l src/SDK/Language/Apple.php
git diff --check origin/master...HEAD
sh ./gradlew :library:compileDebugKotlin # from examples/android

Android compilation succeeded after Gradle fell back from the Kotlin daemon. The daemon failed locally because the installed Java version reports as 25.0.2; the non-daemon compiler completed successfully with existing generated-code warnings.

Adds a ServerClient sibling class to the web SDK alongside the existing
Client. Service classes are generic over `Client | ServerClient` when
they have any client-tier methods, with TypeScript `this`-types gating
admin methods (e.g. `Databases.createCollection` requires
`Databases<ServerClient>`). Services with no client-tier methods
(Health, Tokens, Sites, Users) are non-generic and require a
ServerClient at construction.

Tier detection is driven entirely off existing `x-appwrite.platforms`
spec tags. The existing Client surface is unchanged — purely additive
for current `appwrite` web users; sets up the path to consolidate
`appwrite` + `node-appwrite` into a single isomorphic package.

- Filter `Key` out of Client header iterations so Client cannot setKey
- Add server-client.ts.twig (setKey/setJWT/setLocale + HTTP plumbing,
  no realtime, no session/devkey/impersonate)
- Type-gate service methods via `this: Service<ServerClient>` (or
  `<Client>` for the few client-only methods like webAuth/location)
- Re-export ServerClient from index.ts
- Register the new template in Web.php getFiles()

Verified on regenerated examples/web/: tsc --noEmit passes; negative
tests confirm `new Health(browserClient)` and admin calls on a
Client-bound service fail to type-check; djlint passes.
- Rename fromApiKey -> fromAPIKey for naming consistency
- Make all setters private; expose only static factory methods
- Guard window access with typeof window !== 'undefined' in realtime
- Gate fromAPIKey behind server/console platform builds only
- Normalize ClientRuntime to 'client' | 'server'; remove 'browser'
- Add withJWT and withForwardedUserAgent builder methods
- Fix clearTimeout misuse on interval handles
Moves the service-level auth tier detection and per-method this-gate
construction from template.ts.twig set blocks into PHP helpers exposed
as Twig filters (webServiceAuth, webMethodThisGate). This makes the
template easier to read while keeping generated output identical.
- Rename ClientRuntime -> SDKPlatform and field runtime -> sdkPlatform
- Remove ConsoleAuth type; merge cookie auth into ServerAuth (covers
  both console and SSR cookie-forwarding use cases)
- Emit fromCookie on all platforms instead of console-only
- Default mode: 'admin' in fromCookie on console builds so the wire
  request authenticates as admin without requiring callers to remember
  the X-Appwrite-Mode header
- Add Prettify utility type and wrap factory params so IDE hover shows
  the full parameter shape instead of an opaque alias name
- Simplify Web.php helpers (webServiceAuth, webMethodThisGate) by
  removing the platform argument now that ServerAuth covers all
  server-tier cases
- Wrap fromJWT in a platform guard mirroring fromAPIKey. JWT auth lives
  in ServerAuth, so emitting fromJWT on client builds produced a dead
  factory: the returned Client<'jwt'> could not satisfy any service
  generated from the client spec (which only carry ClientAuth).
- Add selfSigned?: boolean to BaseClientParams and apply it inside
  applyBase so every factory gets a public migration path. Previously
  setSelfSigned was the only way to set the flag and was made private
  by the factory refactor, leaving callers without a public hook.
Replaces the hardcoded config block and ten manually-written auth
setters with a single spec.global.headers loop, matching the pattern
every other SDK template uses (Python, Dart, Kotlin, etc.).

Setters become primitive: header write + config write + return this.
The redundant sdkPlatform / x-sdk-platform writes are removed because
applyBase already sets both before the setter runs, and the setters
are private — only callable from inside factories. Dropping the
duplication also lets the typed Client<'apiKey'> etc. flow through
chained calls without `as unknown as Client<...>` casts.

Each platform spec carries a different subset of securityDefinitions,
so a Web.php Twig filter (webClientHeaders) augments the parsed list
with auth headers the unified client needs but the loaded spec omits
(e.g. Session/DevKey on console, Cookie on client). The filter has a
TODO pointing at appwrite/appwrite#12211, which moves the union into
each platform spec's securityDefinitions directly. Once that ships
and specs regenerate, the filter and its registration can be deleted
in a follow-up.

Verified against console, client, and server builds plus an end-to-end
smoke test calling Account.get() through fromCookie on Appwrite Cloud.
The server platform spec includes ForwardedUserAgent in
securityDefinitions, so the new spec.global.headers loop generates a
config field and setForwardedUserAgent setter for it on server builds.
That collided with the manual versions left over in the template,
producing TS2300/TS1117/TS2393 duplicate-identifier errors when running
tsc --declaration in the web (server) CI job.

Add ForwardedUserAgent to webClientHeaders so it is universally present
across all platform builds (the unified web client always exposes
withForwardedUserAgent for chained user-agent forwarding) and remove
the manual config field and setter from the template. The loop now
owns it on every build target.

Verified npm run build:types passes for web (server), web (console),
and web (client) on a clean examples/web tree.
…-server-client

# Conflicts:
#	templates/web/src/client.ts.twig
@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented May 7, 2026

Greptile Summary

This PR introduces the isomorphic SDK client pattern across Web, Flutter, Apple, Android, Kotlin, Node, React Native, and Dart templates. Each SDK gains static factory methods (fromBrowser, fromSession, fromDevKey, fromImpersonation, and platform-appropriate server factories) that return a narrower ClientAuth interface or typed Client<TAuth>, while the legacy Client() constructor and setter chain remain fully backward-compatible and are marked deprecated.

  • Auth narrowing: Web uses phantom-type branding (Client<TAuth extends Auth>), Apple and Flutter/Android each add a ClientAuth protocol/interface; factory-created clients hide deprecated mutable setters so callers get type-level enforcement of their auth mode.
  • Realtime refactor (Flutter): is! casts to ClientIO/ClientBrowser are removed; WebSocket creation and fallback-cookie logic are moved onto new ClientAuth.realtimeWebSocket() and realtimeFallbackCookie() interface methods, eliminating the previous runtime cast fragility.
  • Validation coverage: All platforms now validate endpoint URLs (and realtime endpoints where applicable) in factory code and the test matrix verifies the corresponding error messages via new *_AUTH_FACTORY_RESPONSES constant arrays.

Confidence Score: 5/5

Safe to merge; factory auth paths are well-validated and backward-compatible with the legacy setter API.

The change is a purely additive narrowing layer delegating to proven existing plumbing. URL validation is present and tested on all four new platform factories. The only issues found are withJWT/withForwardedUserAgent being unreachable dead code and the ImpersonationTarget email/phone vs userEmail/userPhone naming inconsistency across platforms — neither affects runtime correctness.

templates/web/src/client.ts.twig — the withJWT/withForwardedUserAgent dead-code methods and the ImpersonationTarget naming mismatch are both localized here.

Important Files Changed

Filename Overview
templates/web/src/client.ts.twig Adds typed ClientRuntime with static factory methods, phantom-type auth branding, and isomorphic window/setTimeout guards; withJWT/withForwardedUserAgent are unreachable dead code from the public surface, and ImpersonationTarget uses email/phone rather than userEmail/userPhone used by all other SDK platforms.
src/SDK/Language/Web.php Adds webServiceAuth, webMethodThisGate, webClientBaseParams, and webClientSetterReturnType PHP filter methods registered as Twig filters; logic for deriving mixed/server/client tier flags and setter return types is straightforward and well-covered by tests.
templates/web/src/services/template.ts.twig Services now generate typed ServiceRuntime with Omit public type aliases; webServiceAuth and webMethodThisGate filters are wired to produce ClientAuth/ServerAuth type imports and per-method this: gates for mixed-tier services.
templates/android/library/src/main/java/io/package/Client.kt.twig Introduces ClientAuth interface, extension functions for call/chunkedUpload, and factory methods with URL validation via require; refactors setSelfSigned into applySelfSigned(Unit).
templates/flutter/lib/src/client.dart.twig Splits Client into ClientAuth and Client extends ClientAuth; adds factory methods with endpoint/realtime URL validation via AppwriteException and uses _setHeader to keep config and headers in sync.
templates/apple/Sources/Client.swift.twig Adds ClientAuth protocol and factory methods with throwing URL validation; extracts configureConnection/configureEndpoint/configureEndpointRealtime helpers to deduplicate logic.
templates/kotlin/src/main/kotlin/io/appwrite/Client.kt.twig Adds factory methods via a fromBase private helper; URL validation delegates to the existing setEndpoint setter. Returns plain Client without ClientAuth narrowing.
templates/node/src/client.ts.twig Adds applyBase and full set of factory methods; no endpointRealtime in BaseClientParams (intentionally server-only). URL validation relies on the existing setEndpoint.
templates/flutter/lib/src/realtime_io.dart.twig Removes direct is ClientIO casts; realtime socket creation moved into ClientIO.realtimeWebSocket() on the ClientAuth interface, eliminating previous runtime cast fragility.
tests/Base.php Adds eight *_AUTH_FACTORY_RESPONSES constant arrays covering all SDK platforms with config values, endpoint strings, and error messages for validation failures.

Reviews (11): Last reviewed commit: "Use SDK exception for Flutter impersonat..." | Re-trigger Greptile

Comment thread templates/web/src/services/template.ts.twig
Comment thread templates/flutter/lib/src/realtime_browser.dart.twig Outdated
Comment thread templates/android/library/src/main/java/io/package/Client.kt.twig
Comment thread templates/flutter/lib/src/client.dart.twig
Comment thread templates/web/src/client.ts.twig Outdated
Comment thread templates/flutter/lib/src/client.dart.twig
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant