{"id":49790999,"url":"https://github.com/androidpoet/supabase-kmp","last_synced_at":"2026-05-12T04:04:24.238Z","repository":{"id":355751248,"uuid":"1178033383","full_name":"AndroidPoet/supabase-kmp","owner":"AndroidPoet","description":"A Kotlin Multiplatform (KMP) SDK for Supabase, enabling seamless integration across Android, iOS, and other platforms.","archived":false,"fork":false,"pushed_at":"2026-05-05T04:12:03.000Z","size":1368,"stargazers_count":13,"open_issues_count":0,"forks_count":1,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-05-05T05:34:32.877Z","etag":null,"topics":["kmp","kotlin","kotlin-multiplatform","sdk","supabase"],"latest_commit_sha":null,"homepage":null,"language":"Kotlin","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/AndroidPoet.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-03-10T16:09:31.000Z","updated_at":"2026-05-05T04:11:28.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/AndroidPoet/supabase-kmp","commit_stats":null,"previous_names":["androidpoet/supabase-kmp"],"tags_count":2,"template":false,"template_full_name":null,"purl":"pkg:github/AndroidPoet/supabase-kmp","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/AndroidPoet%2Fsupabase-kmp","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/AndroidPoet%2Fsupabase-kmp/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/AndroidPoet%2Fsupabase-kmp/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/AndroidPoet%2Fsupabase-kmp/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/AndroidPoet","download_url":"https://codeload.github.com/AndroidPoet/supabase-kmp/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/AndroidPoet%2Fsupabase-kmp/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32922894,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-11T17:09:15.040Z","status":"online","status_checked_at":"2026-05-12T02:00:06.338Z","response_time":102,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["kmp","kotlin","kotlin-multiplatform","sdk","supabase"],"created_at":"2026-05-12T04:04:05.794Z","updated_at":"2026-05-12T04:04:24.229Z","avatar_url":"https://github.com/AndroidPoet.png","language":"Kotlin","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cp align=\"center\"\u003e\n  \u003cimg src=\"art/logo.png\" width=\"720\" alt=\"Supabase KMP Logo\"\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"https://img.shields.io/badge/Kotlin-2.1.10-blue.svg?logo=kotlin\" alt=\"Kotlin\"\u003e\n  \u003cimg src=\"https://img.shields.io/badge/Ktor-3.1.1-blue.svg\" alt=\"Ktor\"\u003e\n  \u003cimg src=\"https://img.shields.io/badge/Platform-Android%20%7C%20iOS%20%7C%20macOS%20%7C%20tvOS%20%7C%20watchOS%20%7C%20JVM%20%7C%20Linux%20%7C%20Windows%20%7C%20WasmJs-green.svg\" alt=\"Platforms\"\u003e\n  \u003cimg src=\"https://img.shields.io/badge/Maven%20Central-0.1.0-blue.svg\" alt=\"Maven Central\"\u003e\n  \u003cimg src=\"https://img.shields.io/badge/License-MIT-orange.svg\" alt=\"License\"\u003e\n\u003c/p\u003e\n\n# Supabase KMP\n\nKotlin Multiplatform SDK for [Supabase](https://supabase.com) — type-safe, coroutine-first, modular client for every platform Kotlin runs on.\n\n## Features\n\n- **Type-safe Result monad** — `SupabaseResult\u003cT\u003e` with `map`, `flatMap`, `recover` — no exceptions leak to callers\n- **Value class IDs** — `UserId`, `BucketId`, `SessionId`, `ChannelId` prevent mixups at compile time\n- **PostgREST filter DSL** — `eq`, `neq`, `gt`, `like`, `ilike`, `in`, `is`, `textSearch`, `contains`, and more\n- **OAuth (17 providers) + MFA** — TOTP and phone-based multi-factor auth with PKCE flow support\n- **Session management** — Auto-refresh, token persistence, `SessionState` observation via `StateFlow`\n- **Realtime WebSocket** — Phoenix protocol with auto-reconnection, exponential backoff, presence\n- **Edge Functions** — Invoke Supabase Edge Functions with typed request/response\n- **Koin DI** — First-class dependency injection modules for every component\n- **15 platform targets** — Android, iOS, macOS, tvOS, watchOS, JVM, Linux, Windows, and WasmJs\n\n## Setup\n\nAdd the dependencies you need to your `build.gradle.kts`:\n\n```kotlin\n// Version catalog (gradle/libs.versions.toml)\n[versions]\nsupabase-kmp = \"0.1.0\"\n\n[libraries]\nsupabase-core = { module = \"io.github.androidpoet:supabase-core\", version.ref = \"supabase-kmp\" }\nsupabase-client = { module = \"io.github.androidpoet:supabase-client\", version.ref = \"supabase-kmp\" }\nsupabase-auth = { module = \"io.github.androidpoet:supabase-auth\", version.ref = \"supabase-kmp\" }\nsupabase-database = { module = \"io.github.androidpoet:supabase-database\", version.ref = \"supabase-kmp\" }\nsupabase-storage = { module = \"io.github.androidpoet:supabase-storage\", version.ref = \"supabase-kmp\" }\nsupabase-realtime = { module = \"io.github.androidpoet:supabase-realtime\", version.ref = \"supabase-kmp\" }\nsupabase-functions = { module = \"io.github.androidpoet:supabase-functions\", version.ref = \"supabase-kmp\" }\n```\n\n```kotlin\n// build.gradle.kts\nkotlin {\n    sourceSets {\n        commonMain.dependencies {\n            implementation(libs.supabase.client)     // includes supabase-core\n            implementation(libs.supabase.auth)       // optional\n            implementation(libs.supabase.database)   // optional\n            implementation(libs.supabase.storage)    // optional\n            implementation(libs.supabase.realtime)   // optional\n            implementation(libs.supabase.functions)  // optional\n        }\n    }\n}\n```\n\n## Usage\n\n### Create a Client\n\n```kotlin\n// Direct instantiation\nval client = Supabase.create(\n    projectUrl = \"https://your-project.supabase.co\",\n    apiKey = \"your-anon-key\",\n) {\n    logging = true\n}\n\n// Or with Koin DI\nstartKoin {\n    modules(\n        supabaseModule(\n            projectUrl = \"https://your-project.supabase.co\",\n            apiKey = \"your-anon-key\",\n            config = SupabaseConfig(),\n        ),\n        authModule(),\n        databaseModule,\n        storageModule,\n        realtimeModule(),\n        functionsModule,\n    )\n}\n\nval auth: AuthClient by inject()\nval database: DatabaseClient by inject()\n```\n\n### Database — PostgREST with Filter DSL\n\n```kotlin\n@Serializable\ndata class Todo(val id: String, val title: String, val done: Boolean)\n\n// Select with filters\nval todos: SupabaseResult\u003cList\u003cTodo\u003e\u003e = database.selectTyped\u003cTodo\u003e(\n    table = \"todos\",\n) {\n    eq(\"done\", \"false\")\n    gt(\"priority\", \"3\")\n    order(\"created_at\", ascending = false)\n    limit(25)\n}\n\ntodos.onSuccess { items -\u003e\n    println(\"Got ${items.size} todos\")\n}.onFailure { error -\u003e\n    println(\"Error: ${error.message}\")\n}\n\n// Insert\nval result = database.insertTyped(\n    table = \"todos\",\n    value = Todo(id = \"1\", title = \"Ship it\", done = false),\n)\n\n// Update with filters\ndatabase.update(table = \"todos\", body = \"\"\"{\"done\": true}\"\"\") {\n    eq(\"id\", \"1\")\n}\n\n// RPC call\nval stats = database.rpc(\"get_dashboard_stats\", params = \"\"\"{\"user_id\": \"123\"}\"\"\")\n```\n\n### Auth — Sign In with Session Management\n\n```kotlin\nval auth: AuthClient by inject()\nval sessionManager: SessionManager by inject()\n\n// Email/password sign-up\nauth.signUpWithEmail(\n    email = \"user@example.com\",\n    password = \"secure-password\",\n).onSuccess { session -\u003e\n    sessionManager.saveSession(session)\n}\n\n// Email/password sign-in\nauth.signInWithEmail(\n    email = \"user@example.com\",\n    password = \"secure-password\",\n).onSuccess { session -\u003e\n    sessionManager.saveSession(session)\n}\n\n// OAuth — get URL for the provider, open in browser\nval oauthUrl = auth.getOAuthSignInUrl(\n    provider = OAuthProvider.GOOGLE,\n    redirectTo = \"myapp://callback\",\n)\n\n// After OAuth callback, exchange the code\nval pkce = auth.generatePkceParams()\nauth.exchangeCodeForSession(authCode = \"code-from-callback\", codeVerifier = pkce.codeVerifier)\n\n// MFA enrollment\nval accessToken = sessionManager.accessToken!!\nauth.mfaEnroll(factorType = MfaFactorType.TOTP, accessToken = accessToken).onSuccess { factor -\u003e\n    // Show factor.totp?.qrCode to user, then verify:\n    auth.mfaVerify(\n        factorId = factor.id,\n        challengeId = \"challenge-id\",\n        code = \"123456\",\n        accessToken = accessToken,\n    )\n}\n\n// Observe session state\nsessionManager.sessionState.collect { state -\u003e\n    when (state) {\n        is SessionState.Authenticated -\u003e println(\"User: ${state.session.user.id}\")\n        is SessionState.Expired -\u003e println(\"Session expired, refreshing...\")\n        SessionState.NotAuthenticated -\u003e println(\"Signed out\")\n        SessionState.Loading -\u003e println(\"Loading...\")\n    }\n}\n```\n\n### Storage — File Upload \u0026 Download\n\n```kotlin\nval storage: StorageClient by inject()\n\n// Upload a file\nstorage.upload(\n    bucket = \"avatars\",\n    path = \"user123/avatar.png\",\n    data = imageBytes,\n    contentType = \"image/png\",\n).onSuccess { key -\u003e\n    println(\"Uploaded: $key\")\n}\n\n// Get a signed URL\nstorage.createSignedUrl(\n    bucket = \"avatars\",\n    path = \"user123/avatar.png\",\n    expiresIn = 3600,\n).onSuccess { url -\u003e\n    println(\"Signed URL: $url\")\n}\n\n// Get public URL (no auth needed)\nval publicUrl = storage.getPublicUrl(bucket = \"avatars\", path = \"user123/avatar.png\")\n\n// List files\nstorage.list(bucket = \"avatars\", prefix = \"user123/\").onSuccess { files -\u003e\n    files.forEach { println(it.name) }\n}\n```\n\n### Realtime — WebSocket Subscriptions\n\n```kotlin\nval realtime: RealtimeClient by inject()\n\n// Connect and observe connection state\nrealtime.connect()\nrealtime.connectionState.collect { state -\u003e\n    when (state) {\n        is ConnectionState.Connected -\u003e println(\"Connected\")\n        is ConnectionState.Reconnecting -\u003e println(\"Reconnecting attempt ${state.attempt}...\")\n        is ConnectionState.Failed -\u003e println(\"Failed: ${state.reason}\")\n        else -\u003e {}\n    }\n}\n\n// Subscribe to table changes\nval subscription = realtime.channel(\"todos\")\n    .onPostgresChange(table = \"todos\", event = PostgresChangeEvent.INSERT) { record -\u003e\n        println(\"New todo: $record\")\n    }\n    .onPostgresChange(table = \"todos\", event = PostgresChangeEvent.DELETE) { record -\u003e\n        println(\"Deleted: $record\")\n    }\n    .subscribe()\n\n// Presence\nrealtime.channel(\"room:lobby\")\n    .onPresence { state -\u003e\n        println(\"Online users: ${state.size}\")\n    }\n    .subscribe()\n\n// Broadcast\nsubscription.broadcast(event = \"cursor\", payload = buildJsonObject {\n    put(\"x\", 100)\n    put(\"y\", 200)\n})\n\n// Unsubscribe\nsubscription.unsubscribe()\nrealtime.disconnect()\n```\n\n### Edge Functions\n\n```kotlin\nval functions: FunctionsClient by inject()\n\n// Invoke a function\nfunctions.invoke(\n    functionName = \"hello-world\",\n    body = \"\"\"{\"name\": \"Kotlin\"}\"\"\",\n).onSuccess { data -\u003e\n    println(\"Response: $data\")\n}\n\n// Typed response\nfunctions.invokeTyped\u003cWelcomeResponse\u003e(\n    functionName = \"hello-world\",\n    body = \"\"\"{\"name\": \"Kotlin\"}\"\"\",\n).onSuccess { response -\u003e\n    println(\"Message: ${response.message}\")\n}\n\n// Binary body\nfunctions.invokeWithBody(\n    functionName = \"process-image\",\n    body = imageBytes,\n    contentType = \"image/png\",\n)\n```\n\n## Architecture\n\n```\n┌─────────────────────────────────────────────────────────────────────┐\n│                              Your App                               │\n├──────────┬───────────┬───────────┬───────────┬───────────┬─────────┤\n│ supabase │ supabase  │ supabase  │ supabase  │ supabase  │supabase │\n│ auth     │ database  │ storage   │ realtime  │ functions │ client  │\n│          │           │           │           │           │         │\n│ OAuth    │ PostgREST │ Buckets   │ WebSocket │ Invoke    │ HTTP    │\n│ MFA/PKCE │ Filter DSL│ Upload    │ Phoenix   │ Typed Req │ Auth    │\n│ Session  │ RPC       │ SignedURL │ Presence  │ Response  │ Koin DI │\n│ Manager  │ Typed Ext │ Public URL│ Reconnect │ Binary    │ Config  │\n├──────────┴───────────┴───────────┼───────────┤           │         │\n│                                   │   Ktor    │           │         │\n│                                   │  Engines  │           │         │\n├───────────────────────────────────┴───────────┤           │         │\n│                 supabase-core                  │           │         │\n│  SupabaseResult · Value IDs · Filter DSL      │           │         │\n│  Error Types · Response Models                │           │         │\n└────────────────────────────────────────────────┴───────────┴─────────┘\n```\n\n## Modules\n\n| Module | Artifact | Description |\n|--------|----------|-------------|\n| **supabase-core** | `io.github.androidpoet:supabase-core` | Result monad, error types, value class IDs, filter DSL |\n| **supabase-client** | `io.github.androidpoet:supabase-client` | HTTP transport, platform engines, auth state, Koin module |\n| **supabase-auth** | `io.github.androidpoet:supabase-auth` | Email, phone, OTP, OAuth (17 providers), MFA, PKCE, session management |\n| **supabase-database** | `io.github.androidpoet:supabase-database` | PostgREST CRUD, RPC, typed filter extensions |\n| **supabase-storage** | `io.github.androidpoet:supabase-storage` | Bucket CRUD, file upload/download, signed \u0026 public URLs |\n| **supabase-realtime** | `io.github.androidpoet:supabase-realtime` | WebSocket (Phoenix protocol), auto-reconnect, broadcast, presence |\n| **supabase-functions** | `io.github.androidpoet:supabase-functions` | Edge function invocation with typed responses |\n\n## Targets\n\n| Platform | Target | Ktor Engine |\n|----------|--------|-------------|\n| Android | `androidTarget()` | OkHttp |\n| JVM | `jvm()` | OkHttp |\n| iOS | `iosX64()` `iosArm64()` `iosSimulatorArm64()` | Darwin |\n| macOS | `macosX64()` `macosArm64()` | Darwin |\n| tvOS | `tvosX64()` `tvosArm64()` `tvosSimulatorArm64()` | Darwin |\n| watchOS | `watchosX64()` `watchosArm64()` `watchosSimulatorArm64()` | Darwin |\n| Linux | `linuxX64()` | CIO |\n| Windows | `mingwX64()` | CIO |\n| Web | `wasmJs()` | Js |\n\n## Why supabase-kmp?\n\n| | supabase-kmp | supabase-kt (official) |\n|--|--|--|\n| **Codebase** | ~3K LOC | ~26K LOC |\n| **Error handling** | `SupabaseResult\u003cT\u003e` monad | Thrown exceptions |\n| **Type safety** | Value class IDs | String IDs |\n| **DI** | Koin modules | Manual composition |\n| **Dependencies** | 3 core | 7+ |\n| **Session mgmt** | `SessionManager` + `StateFlow` | Built-in (heavier) |\n| **Reconnection** | Exponential backoff | Exponential backoff |\n| **Targets** | 15 | 15+ |\n\n## Tech Stack\n\n| Layer | Library |\n|-------|---------|\n| Language | [Kotlin 2.1.10](https://kotlinlang.org/) |\n| Networking | [Ktor 3.1.1](https://ktor.io/) |\n| Serialization | [kotlinx.serialization 1.8.0](https://github.com/Kotlin/kotlinx.serialization) |\n| Coroutines | [kotlinx.coroutines 1.10.1](https://github.com/Kotlin/kotlinx.coroutines) |\n| DI | [Koin 4.0.2](https://insert-koin.io/) |\n| Publishing | [vanniktech maven-publish 0.30.0](https://github.com/vanniktech/gradle-maven-publish-plugin) |\n\n## Build\n\n```bash\n# Compile all targets\n./gradlew compileKotlinJvm\n\n# Run tests\n./gradlew jvmTest\n\n# Full build (all platforms)\n./gradlew build --no-configuration-cache\n\n# Publish to Maven Central (CI only)\n./gradlew publishAllPublicationsToMavenCentral --no-configuration-cache\n```\n\n## License\n\n```\nMIT License\n\nCopyright (c) 2026 Ranbir Singh\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fandroidpoet%2Fsupabase-kmp","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fandroidpoet%2Fsupabase-kmp","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fandroidpoet%2Fsupabase-kmp/lists"}