From 6006619c2a30f1c224182c89ce65739bfd1d3b6b Mon Sep 17 00:00:00 2001 From: Guilherme Souza Date: Wed, 10 Apr 2024 11:57:27 -0300 Subject: [PATCH 1/4] chore(docs): add Swift guides (#22625) * chore(docs): add Swift guides * chore(docs): add Swift guides for postgres changes * chore: fix link identity swift method * chore(docs): add Swift guides for realtime and storage * chore: run npm run format * chore(docs): add Swift example for passing params to `rpc` function --- apps/docs/content/guides/api/quickstart.mdx | 7 + .../content/guides/auth/auth-anonymous.mdx | 29 ++ apps/docs/content/guides/auth/auth-email.mdx | 34 ++ .../guides/auth/auth-identity-linking.mdx | 25 ++ .../auth/enterprise-sso/auth-sso-saml.mdx | 11 + .../guides/auth/managing-user-data.mdx | 42 +++ .../passwordless-login/auth-email-otp.mdx | 25 ++ .../passwordless-login/auth-magic-link.mdx | 23 ++ apps/docs/content/guides/auth/passwords.mdx | 21 ++ apps/docs/content/guides/auth/phone-login.mdx | 53 ++++ apps/docs/content/guides/database/arrays.mdx | 48 +++ .../guides/database/extensions/postgis.mdx | 72 +++++ .../guides/database/full-text-search.mdx | 123 ++++++++ .../content/guides/database/functions.mdx | 43 +++ .../guides/database/joins-and-nesting.mdx | 100 ++++++ apps/docs/content/guides/database/json.mdx | 82 +++++ apps/docs/content/guides/database/tables.mdx | 22 ++ apps/docs/content/guides/realtime.mdx | 27 ++ .../content/guides/realtime/broadcast.mdx | 93 ++++++ .../guides/realtime/postgres-changes.mdx | 292 ++++++++++++++++++ .../docs/content/guides/realtime/presence.mdx | 69 +++++ .../storage/serving/image-transformations.mdx | 83 +++++ .../storage/uploads/standard-uploads.mdx | 50 +++ 23 files changed, 1374 insertions(+) diff --git a/apps/docs/content/guides/api/quickstart.mdx b/apps/docs/content/guides/api/quickstart.mdx index fbd141e9309..7ba5b9b8809 100644 --- a/apps/docs/content/guides/api/quickstart.mdx +++ b/apps/docs/content/guides/api/quickstart.mdx @@ -169,5 +169,12 @@ final data = await supabase.from('todos').select('*'); response = supabase.table('todos').select("*").execute() ``` + + + +```swift +let response = try await supabase.from("todos").select() +``` + diff --git a/apps/docs/content/guides/auth/auth-anonymous.mdx b/apps/docs/content/guides/auth/auth-anonymous.mdx index 461bb45acec..12acf4f8e9f 100644 --- a/apps/docs/content/guides/auth/auth-anonymous.mdx +++ b/apps/docs/content/guides/auth/auth-anonymous.mdx @@ -64,6 +64,15 @@ Call the [`signInAnonymously()`](/docs/reference/dart/auth-signinanonymously) me await supabase.auth.signInAnonymously(); ``` + + + +Call the [`signInAnonymously()`](/docs/reference/swift/auth-signinanonymously) method: + +```swift +let session = try await supabase.auth.signInAnonymously() +``` + @@ -106,6 +115,17 @@ You can use the [`updateUser()`](/docs/reference/dart/auth-updateuser) method to await supabase.auth.updateUser(UserAttributes(email: 'example@email.com')); ``` + + + +You can use the [`updateUser()`](/docs/reference/swift/auth-updateuser) method to link an email or phone identity to the anonymous user. + +```swift +try await supabase.auth.updateUser( + user: UserAttributes(email: "example@email.com") +) +``` + @@ -146,6 +166,15 @@ You can use the [`linkIdentity()`](/docs/reference/dart/auth-linkidentity) metho await supabase.auth.linkIdentity(OAuthProvider.google); ``` + + + +You can use the [`linkIdentity()`](/docs/reference/swift/auth-linkidentity) method to link an oauth identity to the anonymous user. + +```swift +try await supabase.auth.linkIdentity(provider: .google) +``` + diff --git a/apps/docs/content/guides/auth/auth-email.mdx b/apps/docs/content/guides/auth/auth-email.mdx index 38eb93095c6..fade3ca1802 100644 --- a/apps/docs/content/guides/auth/auth-email.mdx +++ b/apps/docs/content/guides/auth/auth-email.mdx @@ -57,6 +57,19 @@ Future signUpNewUser() async { } ``` + + + +To sign up the user, call [signUp()](/docs/reference/swift/auth-signup) with their email address and password: + +```swift +let response = try await supabase.auth.signUp( + email: "example@email.com", + password: "example-password", + redirectTo: URL(string: "https://example.com/welcome") +) +``` + @@ -110,6 +123,18 @@ Future signInWithEmail() async { } ``` + + + +When your user signs in, call [signInWithPassword()](/docs/reference/swift/auth-signinwithpassword) with their email address and password: + +```swift +try await supabase.auth.signIn( + email: "example@email.com", + password: "example-password" +) +``` + @@ -157,6 +182,15 @@ Future signOut() async { } ``` + + + +When your user signs out, call [signOut()](/docs/reference/swift/auth-signout) to remove them from the browser session and any objects from localStorage: + +```swift +try await supabase.auth.signOut() +``` + diff --git a/apps/docs/content/guides/auth/auth-identity-linking.mdx b/apps/docs/content/guides/auth/auth-identity-linking.mdx index befba001809..112a2005a17 100644 --- a/apps/docs/content/guides/auth/auth-identity-linking.mdx +++ b/apps/docs/content/guides/auth/auth-identity-linking.mdx @@ -54,6 +54,15 @@ Supabase Auth allows a user to initiate identity linking with a different email const { data, error } = await supabase.auth.linkIdentity({ provider: 'google' }) ``` + + + +Supabase Auth allows a user to initiate identity linking with a different email address when they are logged in. To link an OAuth identity to the user, call [`linkIdentity()`](/docs/reference/swift/auth-linkidentity): + +```swift +try await supabase.auth.linkIdentity(provider: .google) +``` + @@ -94,6 +103,22 @@ const googleIdentity = identities.find((identity) => identity.provider === 'goog const { data, error } = await supabase.auth.unlinkIdentity(googleIdentity) ``` + + + +You can use [`getUserIdentities()`](/docs/reference/swift/auth-getuseridentities) to fetch all the identities linked to a user. Then, call [`unlinkIdentity()`](/docs/reference/swift/auth-unlinkidentity) to unlink the identity. The user needs to be logged in and have at least 2 linked identities in order to unlink an existing identity. + +```swift +// retrieve all identities linked to a user +let identities = try await supabase.auth.userIdentities() + +// find the google identity linked to the user +let googleIdentity = identities.first { $0.provider == .google } + +// unlink the google identity from the user +try await supabase.auth.unlinkIdentity(googleIdentity) +``` + diff --git a/apps/docs/content/guides/auth/enterprise-sso/auth-sso-saml.mdx b/apps/docs/content/guides/auth/enterprise-sso/auth-sso-saml.mdx index 48a16a52779..aa87f390aa8 100644 --- a/apps/docs/content/guides/auth/enterprise-sso/auth-sso-saml.mdx +++ b/apps/docs/content/guides/auth/enterprise-sso/auth-sso-saml.mdx @@ -213,6 +213,17 @@ await supabase.auth.signInWithSSO( Calling [`signInWithSSO`](/docs/reference/dart/auth-signinwithsso) starts the sign-in process using the identity provider registered for the `company.com` domain name. It is not required that identity providers be assigned one or multiple domain names, in which case you can use the provider's unique ID instead. + + + +```swift +try await supabase.auth.signInWithSSO( + domain: "company.com" +) +``` + +Calling [`signInWithSSO`](/docs/reference/swift/auth-signinwithsso) starts the sign-in process using the identity provider registered for the `company.com` domain name. It is not required that identity providers be assigned one or multiple domain names, in which case you can use the provider's unique ID instead. + diff --git a/apps/docs/content/guides/auth/managing-user-data.mdx b/apps/docs/content/guides/auth/managing-user-data.mdx index dd73f0757ac..831f269bb20 100644 --- a/apps/docs/content/guides/auth/managing-user-data.mdx +++ b/apps/docs/content/guides/auth/managing-user-data.mdx @@ -107,6 +107,26 @@ const { data: profile } = await supabase .select('id, username, avatar_url, website') ``` + + + +```swift +// This will return nothing while the user is logged out +let profiles: [Profile] = try await supabase.from("profiles") + .select("id, username, avatar_url, website") + .execute() + .value + +// After the user is logged in, this will only return +// the logged-in user's data - in this case a single row +try await supabase.auth.signInWithOTP(email: email) +let profile: Profile = try await supabase.from("profiles") + .select("id, username, avatar_url, website") + .single() + .execute() + .value +``` + @@ -163,6 +183,20 @@ const { data, error } = await supabase.auth.signUp({ }) ``` + + + +```swift +try await supabase.auth.signUp( + email: "example@email.com", + password: "example-password", + data: [ + "first_name": .string("John"), + "age": .integer(27), + ] +) +``` + @@ -198,6 +232,14 @@ const { let metadata = user.user_metadata ``` + + + +```swift +let user = try await supabase.auth.user() +let metadata = user.userMetadata +``` + diff --git a/apps/docs/content/guides/auth/passwordless-login/auth-email-otp.mdx b/apps/docs/content/guides/auth/passwordless-login/auth-email-otp.mdx index 820f70e4b5c..2605da556fc 100644 --- a/apps/docs/content/guides/auth/passwordless-login/auth-email-otp.mdx +++ b/apps/docs/content/guides/auth/passwordless-login/auth-email-otp.mdx @@ -54,6 +54,18 @@ Future signInWithEmailOtp() async { } ``` + + +When your user signs in, call [signInWithOTP()](/docs/reference/swift/auth-signinwithotp) with their email address: + +```swift +try await supabase.auth.signInWithOTP( + email: "example@email.com", + // set this to false if you do not want the user to be automatically signed up + shouldCreateUser: false +) +``` + @@ -106,6 +118,19 @@ const { }) ``` + + + +Provide an input field for the user to key in the one-time code. To verify the code and complete the user's sign in, call [verifyOTP()](/docs/reference/swift/auth-verifyotp) with their email address, the code, and `type: .email`: + +```swift +try await supabase.auth.verifyOTP( + email: email, + token: "123456", + type: .email +) +``` + diff --git a/apps/docs/content/guides/auth/passwordless-login/auth-magic-link.mdx b/apps/docs/content/guides/auth/passwordless-login/auth-magic-link.mdx index 4b6ffdfdc87..7074da63847 100644 --- a/apps/docs/content/guides/auth/passwordless-login/auth-magic-link.mdx +++ b/apps/docs/content/guides/auth/passwordless-login/auth-magic-link.mdx @@ -19,6 +19,8 @@ Set up Magic Link logins for your Supabase application. When using Magic Links in [the PKCE Flow](/blog/supabase-auth-sso-pkce#introducing-pkce), keep in mind that links can only be opened from the same browser that they are sent from (as defined in the PKCE spec). Consequently, a magic link sent from Chrome on Desktop will be invalid if used on a Mobile Device. +The same applies for Magic Links in mobile devices, magic link can only be opened on the same device that originated it. + signInWithEmail() async { } ``` + + +When your user signs in, call [signInWithOTP()](/docs/reference/swift/auth-signinwithotp) with their email address: + +```swift +try await supabase.auth.signInWithOTP( + email: "example@email.com", + redirectTo: URL(string: "https://example.com/welcome"), + // set this to false if you do not want the user to be automatically signed up + shouldCreateUser: false +) +``` + @@ -119,6 +134,14 @@ Future signOut() async { } ``` + + +When your user signs out, call [signOut()](/docs/reference/swift/auth-signout) to remove them from the local storage: + +```swift +try await supabase.auth.signOut() +``` + diff --git a/apps/docs/content/guides/auth/passwords.mdx b/apps/docs/content/guides/auth/passwords.mdx index 25877dad288..eb6a54ff65e 100644 --- a/apps/docs/content/guides/auth/passwords.mdx +++ b/apps/docs/content/guides/auth/passwords.mdx @@ -91,6 +91,18 @@ await supabase.auth.resetPasswordForEmail('hello@example.com', { }) ``` + + + +Supabase provides a convenient method [`supabase.auth.resetPasswordForEmail`](/docs/reference/swift/auth-resetpasswordforemail) to reset a user password. This method takes a parameter of `redirectTo`, which is an absolute URL to the update password page. This URL must be saved in your allowed [Redirect URLs](https://supabase.com/dashboard/project/_/auth/url-configuration) list found at [Authentication > Redirect Configuration](https://supabase.com/dashboard/project/_/auth/url-configuration). + +```swift +try await supabase.auth.resetPasswordForEmail( + "hello@example.com", + redirectTo: URL(string: "http://example.com/account/update-password") +) +``` + @@ -125,6 +137,15 @@ To update the password, call the [`supabase.auth.updateUser`](/docs/reference/ja await supabase.auth.updateUser({ password: new_password }) ``` + + + +To update the password, call the [`supabase.auth.updateUser`](/docs/reference/swift/auth-updateuser) method with the new password. + +```swift +try await supabase.auth.updateUser(user: UserAttributes(password: newPassword)) +``` + diff --git a/apps/docs/content/guides/auth/phone-login.mdx b/apps/docs/content/guides/auth/phone-login.mdx index e35520a002a..0846897aaea 100644 --- a/apps/docs/content/guides/auth/phone-login.mdx +++ b/apps/docs/content/guides/auth/phone-login.mdx @@ -80,6 +80,16 @@ const { data, error } = await supabase.auth.signUp({ }) ``` + + + +```swift +try await supabase.auth.signUp( + phone: "+13334445555", + password: "some-password" +) +``` + @@ -126,6 +136,16 @@ const { user, error } = await supabase.auth.signInWithPassword({ }) ``` + + + +```swift +try await supabase.auth.signIn( + phone: "+13334445555", + password: "some-password" +) +``` + @@ -171,6 +191,15 @@ const { data, error } = await supabase.auth.signInWithOtp({ }) ``` + + + +```swift +try await supabase.auth.signInWithOTP( + phone: "+13334445555" +) +``` + @@ -216,6 +245,17 @@ const { data, error } = await supabase.auth.updateUser({ }) ``` + + + +```swift +try await supabase.auth.updateUser( + user: UserAttributes( + phone: "123456789" + ) +) +``` + @@ -247,6 +287,19 @@ const { }) ``` + + + +You should present a form to the user so they can input the 6 digit pin, then send it along with the phone number to `verifyOTP`: + +```swift +try await supabase.auth.verifyOTP( + phone: "+13334445555", + token: "123456", + type: .sms +) +``` + diff --git a/apps/docs/content/guides/database/arrays.mdx b/apps/docs/content/guides/database/arrays.mdx index 63557ca992f..eb405bd293f 100644 --- a/apps/docs/content/guides/database/arrays.mdx +++ b/apps/docs/content/guides/database/arrays.mdx @@ -72,6 +72,30 @@ const { data, error } = await supabase .insert([{ id: 2, textarray: ['one', 'two', 'three', 'four'] }]) ``` + + + +Insert a record from the Swift client: + +```swift +struct ArrayTest: Encodable { + let id: Int + let textarray: [String] +} + +try await supabase + .from("arraytest") + .insert( + [ + ArrayTest( + id: 2, + textarray: ["one", "two", "three", "four"] + ) + ] + ) + .execute() +``` + @@ -156,6 +180,30 @@ returns: ] ``` + + + +This returns the entire array field: + +```swift +struct Response: Decodable { + let textarray: [String] +} + +let response: [Response] = try await supabase.from("arraytest").select("textarray").execute().value +dump(response) +``` + +returns: + +``` +[ + Response( + textarray: ["Harry", "Larry", "Moe"], + ) +] +``` + diff --git a/apps/docs/content/guides/database/extensions/postgis.mdx b/apps/docs/content/guides/database/extensions/postgis.mdx index 06c51beb308..83e4057a62f 100644 --- a/apps/docs/content/guides/database/extensions/postgis.mdx +++ b/apps/docs/content/guides/database/extensions/postgis.mdx @@ -139,6 +139,26 @@ await supabase.from('restaurants').insert([ ]); ``` + + + +```swift +struct Restaurant: Codable { + let name: String + let location: String // You could also use a custom type with a custom `Encodable` conformance for convenience. +} + +try await supabase.from("restaurants") + .insert( + [ + Restaurant(name: "Supa Burger", location: "POINT(-73.946823 40.807416)"), + Restaurant(name: "Supa Pizza", location: "POINT(-73.94581 40.807475)"), + Restaurant(name: "Supa Taco", location: "POINT(-73.945826 40.80629)"), + ] + ) + .execute() +``` + @@ -215,6 +235,34 @@ final data = await supabase.rpc('nearby_restaurants',params: { }); ``` + + + +```swift +struct Response: Codable { + let id: Int + let name: String + let lat: Double + let long: Double + let distance: Double + + enum CodingKeys: String, CodingKey { + case id, name, lat, long + case distance = "dist_meters" + } +} + +let response: Response = try await supabase.rpc( + "nearby_restaurants", + params: [ + "lat": 40.807313, + "long": -73.946713 + ] +) +.execute() +.value +``` + @@ -314,6 +362,30 @@ final data = await supabase.rpc('restaurants_in_view', params: { }); ``` + + + +```swift +struct Response: Codable { + let id: Int + let name: String + let lat: Double + let long: Double +} + +let response: Response = try await supabase.rpc( + "restaurants_in_view", + params: [ + "min_lat": 40.807, + "min_long": -73.946, + "max_long": -73.945, + "max_lat": 40.808, + ] +) +.execute() +.value +``` + diff --git a/apps/docs/content/guides/database/full-text-search.mdx b/apps/docs/content/guides/database/full-text-search.mdx index 1f75efa7230..f84c5b2fe59 100644 --- a/apps/docs/content/guides/database/full-text-search.mdx +++ b/apps/docs/content/guides/database/full-text-search.mdx @@ -138,6 +138,16 @@ final result = await client .eq('title', 'Harry'); ``` + + + +```swift +let response = try await supabase.from("books") + .select() + .eq("title", value: "Harry") + .execute() +``` + @@ -187,6 +197,15 @@ final result = await client .textSearch('title', "'Harry'"); ``` + + + +```swift +let response = try await supabase.from("books") + .select() + .textSearch("title", value: "'Harry'") +``` + @@ -243,6 +262,16 @@ final result = await client .textSearch('description', "'big'"); ``` + + + +```swift +let response = await client.from("books") + .select() + .textSearch("description", value: "'big'") + .execute() +``` + @@ -326,6 +355,23 @@ final result = await client .textSearch('title_description', "little") ``` + + + +```sql +create function title_description(books) returns text as $$ + select $1.title || ' ' || $1.description; +$$ language sql immutable; +``` + +```swift +let response = try await client + .from("books") + .select() + .textSearch("title_description", value: "little") + .execute() +``` + @@ -401,6 +447,17 @@ final result = await client .textSearch('description', "'little' & 'big'"); ``` + + + +```swift +let response = try await client + .from("books") + .select() + .textSearch("description", value: "'little' & 'big'"); + .execute() +``` + @@ -469,6 +526,17 @@ final result = await client .textSearch('description', "'little' | 'big'"); ``` + + + +```swift +let response = try await client + .from("books") + .select() + .textSearch("description", value: "'little' | 'big'") + .execute() +``` + @@ -548,6 +616,17 @@ const { data, error } = await supabase.rpc('search_books_by_title_prefix', { pre final data = await supabase.rpc('search_books_by_title_prefix', params: { 'prefix': 'Lit' }); ``` + + + +```swift +let response = try await supabase.rpc( + "search_books_by_title_prefix", + params: ["prefix": "Lit"] +) +.execute() +``` + @@ -660,6 +739,17 @@ final result = await client .textSearch('fts', "'little' & 'big'"); ``` + + + +```swift +let response = try await client + .from("books") + .select() + .textSearch("fts", value: "'little' & 'big'") + .execute() +``` + @@ -733,6 +823,17 @@ final result = await client .textSearch('description', "'big' <-> 'dreams'"); ``` + + + +```swift +let response = try await client + .from("books") + .select() + .textSearch("description", value: "'big' <-> 'dreams'") + .execute() +``` + @@ -787,6 +888,17 @@ final result = await client .textSearch('description', "'year' <2> 'school'"); ``` + + + +```swift +let response = try await supabase + .from("books") + .select() + .textSearch("description", value: "'year' <2> 'school'") + .execute() +``` + @@ -844,6 +956,17 @@ final result = await client .textSearch('description', "'big' & !'little'"); ``` + + + +```swift +let response = try await client + .from("books") + .select() + .textSearch("description", value: "'big' & !'little'") + .execute() +``` + diff --git a/apps/docs/content/guides/database/functions.mdx b/apps/docs/content/guides/database/functions.mdx index cdb910df794..6e7664054af 100644 --- a/apps/docs/content/guides/database/functions.mdx +++ b/apps/docs/content/guides/database/functions.mdx @@ -94,6 +94,15 @@ final data = await supabase Reference: [rpc()](../../reference/dart/rpc) + + + +```swift +try await supabase.rpc("hello_world").execute() +``` + +Reference: [rpc()](../../reference/swift/rpc) + @@ -216,6 +225,13 @@ final data = await supabase .eq('id', 1); ``` + + + +```swift +let response = try await supabase.rpc("get_planets").eq("id", value: 1).execute() +``` + @@ -281,6 +297,33 @@ final data = await supabase .rpc('add_planet', params: { 'name': 'Jakku' }); ``` + + + +Using `Encodable` type: + +```swift +struct Planet: Encodable { + let name: String +} + +try await supabase.rpc( + "add_planet", + params: Planet(name: "Jakku") +) +.execute() +``` + +Using `AnyJSON` convenience` type: + +```swift +try await supabase.rpc( + "add_planet", + params: ["name": AnyJSON.string("Jakku")] +) +.execute() +``` + diff --git a/apps/docs/content/guides/database/joins-and-nesting.mdx b/apps/docs/content/guides/database/joins-and-nesting.mdx index 997dc5935f7..18584fded25 100644 --- a/apps/docs/content/guides/database/joins-and-nesting.mdx +++ b/apps/docs/content/guides/database/joins-and-nesting.mdx @@ -135,6 +135,28 @@ const countriesWithCities: CountriesWithCities = data final data = await supabase.from('countries').select('id, name, cities(id, name)'); ``` + + + +```swift +struct Country: Codable { + let id: Int + let name: String + let cities: [City] + + struct City: Codable { + let id: Int + let name: String + } +} + +let countries: [Country] = try await supabase + .from("countries") + .select("id, name, cities(id, name)") + .execute() + .value +``` + @@ -222,6 +244,38 @@ const { data, error } = await supabase.from('teams').select(` final data = await supabase.from('teams').select('id, team_name, users(id, name)'); ``` + + + +```swift +struct Team: Codable { + let id: Int + let name: String + let users: [User] + + struct User: Codable { + let id: Int + let name: String + } + + enum CodingKeys: String, CodingKey { + case id, users + case name = "team_name" + } +} +let teams [Team] = try await supabase + .from("teams") + .select( + """ + id, + team_name, + users ( id, name ) + """ + ) + .execute() + .value +``` + @@ -315,5 +369,51 @@ const { data, error } = await supabase.from('shifts').select( ) ``` + + + +```swift +struct Shift: Codable { + let id: Int + let userId: Int + let attendanceStatus: String? + + let scans: [Scan] + + struct Scan: Codable { + let id: Int + let userId: Int + let badgeScanTime: TimeInterval + + enum CodingKeys: String, CodingKey { + case id + case userId = "user_id" + case badgeScanTime = "badge_scan_time" + } + } + + enum CodingKeys: String, CodingKey { + case id + case userId = "user_id" + case attendanceStatus = "attendance_status" + } +} + +let shifts: [Shift] = try await supabase + .from("shifts") + .select( + """ + *, + scans:scan_id_start ( + id, + user_id, + badge_scan_time + ) + """ + ) + .execute() + .value +``` + diff --git a/apps/docs/content/guides/database/json.mdx b/apps/docs/content/guides/database/json.mdx index 6f2cd0c1771..97e7a8d3f0d 100644 --- a/apps/docs/content/guides/database/json.mdx +++ b/apps/docs/content/guides/database/json.mdx @@ -177,6 +177,70 @@ const { data, error } = await supabase.from('books').insert([ ]) ``` + + + +Supabase Swift provides a convenience `AnyJSON` type. + +```swift +struct Book { + val title: String, + val author: String, + val metadata: [String: AnyJSON] +} + +try await supabase.from("books") + .insert( + [ + Book( + title: "The Poky Little Puppy", + author: "Janette Sebring Lowrey", + metadata: [ + "description": "Puppy is slower than other, bigger animals.", + "price": 5.95, + "ages": [3, 6] + ] + ), + Book( + title: "Tale of Peter Rabbit", + author: "Beatrix Potter", + metadata: [ + "description": "Rabbit eats some vegetables.", + "price": 4.49, + "ages": [2, 5] + ] + ), + Book( + title: "Tootle", + author: "Gertrude Crampton", + metadata: [ + "description": "Little toy train has big dreams.", + "price": 3.99, + "ages": [2, 5] + ] + ), + Book( + title: "Green Eggs and Ham", + author: "Dr. Seuss", + metadata: [ + "description": "Sam has changing food preferences and eats unusually colored food.", + "price": 7.49, + "ages": [4, 8] + ] + ), + Book( + title: "Harry Potter and the Goblet of Fire", + author: "J.K. Rowling", + metadata: [ + "description": "Fourth year of school starts, big drama ensues.", + "price": 24.95, + "ages": [10, 99] + ] + ) + ] + ) +``` + @@ -247,6 +311,24 @@ const { data, error } = await supabase.from('books').select(` `) ``` + + + +```swift +try await supabase + .from("books") + .select( + """ + title, + description: metadata->description, + price: metadata->price, + low_age: metadata->ages->0, + high_age: metadata->ages->1 + """ + ) + .execute() +``` + diff --git a/apps/docs/content/guides/database/tables.mdx b/apps/docs/content/guides/database/tables.mdx index 328740a09fa..54945eef4ea 100644 --- a/apps/docs/content/guides/database/tables.mdx +++ b/apps/docs/content/guides/database/tables.mdx @@ -230,6 +230,28 @@ await supabase }]); ``` + + + +```swift +try await supabase.from("movies") + .insert( + [ + [ + "name": "The Empire Strikes Back", + "description": + "After the Rebels are brutally overpowered by the Empire on the ice planet Hoth, Luke Skywalker begins Jedi training with Yoda.", + ], + [ + "name": "Return of the Jedi", + "description": + "After a daring mission to rescue Han Solo from Jabba the Hutt, the Rebels dispatch to Endor to destroy the second Death Star.", + ], + ] + ) + .execute() +``` + diff --git a/apps/docs/content/guides/realtime.mdx b/apps/docs/content/guides/realtime.mdx index dee6cf6766f..8bf656ed6ba 100644 --- a/apps/docs/content/guides/realtime.mdx +++ b/apps/docs/content/guides/realtime.mdx @@ -110,6 +110,33 @@ supabase .subscribe(); ``` + + + +```swift +// Initialize the Swift client +import Supabase + +let supabase = SupabaseClient(supabaseURL: SUPABASE_URL, supabaseKey: SUPABASE_ANON_KEY) + +// Create channel +let channel = await supabase.realtime.channel("todos") + +// Create the observations before subscribing +let insertions = await channel.postgresChanges( + InsertAction.self, + schema: "public", + table: "todos" +) + +await channel.subscribe() + +for await insertion in insertions { + let todo = try insertion.decoded(as: Todo.self) + print("Todo inserted", todo) +} +``` + diff --git a/apps/docs/content/guides/realtime/broadcast.mdx b/apps/docs/content/guides/realtime/broadcast.mdx index ea497c0c89e..ae0ef21f32a 100644 --- a/apps/docs/content/guides/realtime/broadcast.mdx +++ b/apps/docs/content/guides/realtime/broadcast.mdx @@ -49,6 +49,18 @@ Go to your Supabase project's [API Settings](https://supabase.com/dashboard/proj final supabase = Supabase.instance.client; ``` + + + + ```swift + import Supabase + + let SUPABASE_URL = "https://.supabase.co" + let SUPABASE_KEY = "" + + let client = SupabaseClient(supabaseURL: URL(string: SUPABASE_URL)!, supabaseKey: SUPABASE_KEY) + ``` + @@ -112,6 +124,22 @@ You can provide a callback for the `broadcast` channel to receive message. In th .subscribe(); ``` + + + + ```swift + let channelA = await clientA.channel("room-1") + + // Listen for broadcast messages + let broadcastStream = await channelA.broadcast(event: "test") + + await channelA.subscribe() + + for await event in broadcastStream { + print(event) + } + ``` + @@ -185,6 +213,22 @@ You can provide a callback for the `broadcast` channel to receive message. In th }); ``` + + + + We can send Broadcast messages using `channelB.broadcast()`. Let's set up another client to send messages. + + ```swift + let channelB = await clientA.channel("room-1") + + await channelB.subscribe() + + try await channelB.broadcast( + event: "test", + message: YourMessage(message: "hello, world!") + ) + ``` + @@ -273,6 +317,28 @@ You can pass configuration options while initializing the Supabase Client. }); ``` + + + + By default, broadcast messages are only sent to other clients. You can broadcast messages back to the sender by setting Broadcast's `receiveOwnBroadcasts` parameter to `true`. + + ```swift + let myChannel = await supabase.channel("room-2") { + $0.broadcast.receiveOwnBroadcasts = true + } + + let broadcastStream = await myChannel.broadcast(event: "test-my-messages") + + await myChannel.subscribe() + + try await myChannel.broadcast( + event: "test-my-messages", + payload: YourMessage( + message: "talking to myself" + ) + ) + ``` + @@ -361,6 +427,21 @@ You can pass configuration options while initializing the Supabase Client. }); ``` + + + + You can confirm that Realtime received your message by setting Broadcast's `acknowledgeBroadcasts` config to `true`. + + ```swift + let myChannel = await clientD.channel("room-3") { + $0.broadcast.acknowledgeBroadcasts = true + } + + await myChannel.subscribe() + + await myChannel.broadcast(event: "acknowledge", message: [:]) + ``` + @@ -435,6 +516,18 @@ You can also send a Broadcast message by making an HTTP request to Realtime serv print(res); ``` + + + ```swift + let myChannel = await supabase.channel("room-2") { + $0.broadcast.acknowledgeBroadcasts = true + } + + // No need to subscribe to channel + + await myChannel.broadcast(event: "test", message: ["message": "HI"]) + ``` + ```kotlin diff --git a/apps/docs/content/guides/realtime/postgres-changes.mdx b/apps/docs/content/guides/realtime/postgres-changes.mdx index 252e79330e5..134ca62edd1 100644 --- a/apps/docs/content/guides/realtime/postgres-changes.mdx +++ b/apps/docs/content/guides/realtime/postgres-changes.mdx @@ -236,6 +236,26 @@ supabase .subscribe(); ``` + + + +```swift +let myChannel = await supabase.channel("schema-db-changes") + +let changes = await myChannel.postgresChange(AnyAction.self, schema: "public") + +await myChannel.subscribe() + +for await change in changes { + switch change { + case .insert(let action): print(action) + case .update(let action): print(action) + case .delete(let action): print(action) + case .select(let action): print(action) + } +} +``` + @@ -302,6 +322,23 @@ final changes = supabase .subscribe(); ``` + + + +Use `InsertAction.self` as type to listen only to database `INSERT`s: + +```swift +let myChannel = await supabase.channel("schema-db-changes") + +let changes = await myChannel.postgresChange(InsertAction.self, schema: "public") + +await myChannel.subscribe() + +for await change in changes { + print(change.record) +} +``` + @@ -366,6 +403,23 @@ supabase .subscribe(); ``` + + + +Use `UpdateAction.self` as type to listen only to database `UPDATE`s: + +```swift +let myChannel = await supabase.channel("schema-db-changes") + +let changes = await myChannel.postgresChange(UpdateAction.self, schema: "public") + +await myChannel.subscribe() + +for await change in changes { + print(change.oldRecord, change.record) +} +``` + @@ -430,6 +484,23 @@ supabase .subscribe(); ``` + + + +Use `DeleteAction.self` as type to listen only to database `DELETE`s: + +```swift +let myChannel = await supabase.channel("schema-db-changes") + +let changes = await myChannel.postgresChange(DeleteAction.self, schema: "public") + +await myChannel.subscribe() + +for await change in changes { + print(change.oldRecord) +} +``` + @@ -496,6 +567,26 @@ supabase .subscribe(); ``` + + + +```swift +let myChannel = await supabase.channel("db-changes") + +let changes = await myChannel.postgresChange(AnyAction.self, schema: "public", table: "todos") + +await myChannel.subscribe() + +for await change in changes { + switch change { + case .insert(let action): print(action) + case .update(let action): print(action) + case .delete(let action): print(action) + case .select(let action): print(action) + } +} +``` + @@ -576,6 +667,18 @@ supabase .subscribe(); ``` + + + +```swift +let myChannel = await supabase.channel("db-changes") + +let messageChanges = await myChannel.postgresChange(AnyAction.self, schema: "public", table: "messages") +let userChanges = await myChannel.postgresChange(InsertAction.self, schema: "public", table: "users") + +await myChannel.subscribe() +``` + @@ -641,6 +744,26 @@ const changes = client .subscribe(); ``` + + + +```swift +let myChannel = await supabase.channel("db-changes") + +let changes = await myChannel.postgresChange( + InsertAction.self, + schema: "public", + table: "todos", + filter: "id=eq.1" +) + +await myChannel.subscribe() + +for await change in changes { + print(change.record) +} +``` + @@ -716,6 +839,26 @@ supabase .subscribe(); ``` + + + +```swift +let myChannel = await supabase.channel("db-changes") + +let changes = await myChannel.postgresChange( + UpdateAction.self, + schema: "public", + table: "messages", + filter: "body=eq.hey" +) + +await myChannel.subscribe() + +for await change in changes { + print(change.record) +} +``` + @@ -789,6 +932,26 @@ supabase .subscribe(); ``` + + + +```swift +let myChannel = await supabase.channel("db-changes") + +let changes = await myChannel.postgresChange( + UpdateAction.self, + schema: "public", + table: "messages", + filter: "body=neq.hey" +) + +await myChannel.subscribe() + +for await change in changes { + print(change.record) +} +``` + @@ -863,6 +1026,26 @@ supabase .subscribe(); ``` + + + +```swift +let myChannel = await supabase.channel("db-changes") + +let changes = await myChannel.postgresChange( + InsertAction.self, + schema: "public", + table: "profiles", + filter: "age=lt.65" +) + +await myChannel.subscribe() + +for await change in changes { + print(change.record) +} +``` + @@ -936,6 +1119,26 @@ supabase .subscribe(); ``` + + + +```swift +let myChannel = await supabase.channel("db-changes") + +let changes = await myChannel.postgresChange( + InsertAction.self, + schema: "public", + table: "profiles", + filter: "age=lte.65" +) + +await myChannel.subscribe() + +for await change in changes { + print(change.record) +} +``` + @@ -1009,6 +1212,26 @@ supabase .subscribe(); ``` + + + +```swift +let myChannel = await supabase.channel("db-changes") + +let changes = await myChannel.postgresChange( + InsertAction.self, + schema: "public", + table: "products", + filter: "quantity=gt.10" +) + +await myChannel.subscribe() + +for await change in changes { + print(change.record) +} +``` + @@ -1082,6 +1305,26 @@ supabase .subscribe(); ``` + + + +```swift +let myChannel = await supabase.channel("db-changes") + +let changes = await myChannel.postgresChange( + InsertAction.self, + schema: "public", + table: "products", + filter: "quantity=gte.10" +) + +await myChannel.subscribe() + +for await change in changes { + print(change.record) +} +``` + @@ -1155,6 +1398,26 @@ supabase .subscribe(); ``` + + + +```swift +let myChannel = await supabase.channel("db-changes") + +let changes = await myChannel.postgresChange( + InsertAction.self, + schema: "public", + table: "products", + filter: "name=in.(red, blue, yellow)" +) + +await myChannel.subscribe() + +for await change in changes { + print(change.record) +} +``` + @@ -1277,6 +1540,28 @@ supabase .subscribe(); ``` + + + +```swift +await supabase.realtime.setAuth("your-custom-jwt") + +let myChannel = await supabase.channel("db-changes") + +let changes = await myChannel.postgresChange( + UpdateAction.self, + schema: "public", + table: "products", + filter: "name=in.(red, blue, yellow)" +) + +await myChannel.subscribe() + +for await change in changes { + print(change.record) +} +``` + @@ -1333,6 +1618,13 @@ supabase.realtime.setAuth('fresh-token') supabase.realtime.setAuth('fresh-token'); ``` + + + +```swift +await supabase.realtime.setAuth("fresh-token") +``` + diff --git a/apps/docs/content/guides/realtime/presence.mdx b/apps/docs/content/guides/realtime/presence.mdx index d9712186d3b..02c6cf182ad 100644 --- a/apps/docs/content/guides/realtime/presence.mdx +++ b/apps/docs/content/guides/realtime/presence.mdx @@ -48,6 +48,17 @@ void main() { final supabase = Supabase.instance.client; ``` + + + +```swift +let supabaseURL = "https://.supabase.co" +let supabaseKey = "" +let supabase = SupabaseClient(supabaseURL: URL(string: supabaseURL)!, supabaseKey: supabaseKey) + +let realtime = supabase.realtime +``` + @@ -110,6 +121,23 @@ roomOne.onPresenceSync((_) { }).subscribe(); ``` + + + +Listen to the presence change stream, emitting a new `PresenceAction` whenever someone joins or leaves: + +```swift +let roomOne = await supabase.channel("room_01") +let presenceStream = await roomOne.presenceChange() + +await roomOne.subscribe() + +for await presence in presenceStream { + print(presence.join) // You can also use presence.decodeJoins(as: MyType.self) + print(presence.leaves) // You can also use presence.decodeLeaves(as: MyType.self) +} +``` + @@ -180,6 +208,31 @@ roomOne.subscribe((status, error) async { }); ``` + + + +```swift +let roomOne = await supabase.channel("room_01") + +// Using a custom type +let userStatus = UserStatus( + user: "user-1", + onlineAt: Date().timeIntervalSince1970 +) + +await roomOne.subscribe() + +try await roomOne.track(userStatus) + +// Or using a raw JSONObject. +await roomOne.track( + [ + "user": .string("user-1"), + "onlineAt": .double(Date().timeIntervalSince1970) + ] +) +``` + @@ -237,6 +290,13 @@ untrackPresence() async { untrackPresence(); ``` + + + +```swift +await roomOne.untrack() +``` + @@ -290,6 +350,15 @@ final channelC = supabase.channel( ); ``` + + + +```swift +let channelC = await supabase.channel("test") { + $0.presence.key = "userId-123" +} +``` + diff --git a/apps/docs/content/guides/storage/serving/image-transformations.mdx b/apps/docs/content/guides/storage/serving/image-transformations.mdx index d60dc23a173..deab689297c 100644 --- a/apps/docs/content/guides/storage/serving/image-transformations.mdx +++ b/apps/docs/content/guides/storage/serving/image-transformations.mdx @@ -37,6 +37,17 @@ supabase.storage.from('bucket').getPublicUrl('image.jpg', { }) ``` + + + +```swift +let url = try await supabase.storage.from("bucket") + .getPublicURL( + path: "image.jpg" + options: TransformOptions(with: 500, height: 600) + ) +``` + @@ -75,6 +86,21 @@ supabase.storage.from('bucket').createSignedUrl('image.jpg', 60000, { }) ``` + + + +```swift +let url = try await supabase.storage.from("bucket") + .createSignedURL( + path: "image.jpg", + expiresIn: 60, + transform: TransformOptions( + width: 200, + height: 200 + ) + ) +``` + @@ -111,6 +137,20 @@ supabase.storage.from('bucket').download('image.jpg', { }) ``` + + + +```swift +let data = try await supabase.storage.from("bucket") + .download( + path: "image.jpg", + options: TransformOptions( + width: 800, + height: 300 + ) + ) +``` + @@ -164,6 +204,21 @@ await storage.from('bucket').download('image.jpeg', { }) ``` + + + +```swift +let data = try await supabase.storage.from("bucket") + .download( + path: "image.jpg", + options: TransformOptions( + width: 200, + height: 200, + format: "origin" + ) + ) +``` + @@ -248,6 +303,19 @@ supabase.storage.from('bucket').download('image.jpg', { }) ``` + + + +```swift +let data = try await supabase.storage.from("bucket") + .download( + path: "image.jpg", + options: TransformOptions( + quality: 50 + ) + ) +``` + @@ -303,6 +371,21 @@ supabase.storage.from('bucket').download('image.jpg', { }) ``` + + + +```swift +let data = try await supabase.storage.from("bucket") + .download( + path: "image.jpg", + options: TransformOptions( + width: 800, + height: 300, + resize: "contain" // "cover" | "fill" + ) + ) +``` + diff --git a/apps/docs/content/guides/storage/uploads/standard-uploads.mdx b/apps/docs/content/guides/storage/uploads/standard-uploads.mdx index 90f219f5726..a38a23e2691 100644 --- a/apps/docs/content/guides/storage/uploads/standard-uploads.mdx +++ b/apps/docs/content/guides/storage/uploads/standard-uploads.mdx @@ -44,6 +44,18 @@ async function uploadFile(file) { } ``` + + + +```swift +import Supabase + +// Create Supabase client +let supabase = SupabaseClient(supabaseURL: URL(string: "your_project_url")!, supabaseKey: "your_supabase_api_key") + +try await supabase.storage.from("bucket_name").upload(path: "file_path", file: file) +``` + @@ -88,6 +100,25 @@ await supabase.storage.from('bucket_name').upload('file_path', file, { }) ``` + + + +```swift +import Supabase + +// Create Supabase client +let supabase = SupabaseClient(supabaseURL: URL(string: "your_project_url")!, supabaseKey: "your_supabase_api_key") + +try await supabase.storage.from("bucket_name") + .upload( + path: "file_path", + file: file, + options: FileOptions( + upsert: true + ) + ) +``` + @@ -116,6 +147,25 @@ await supabase.storage.from('bucket_name').upload('file_path', file, { }) ``` + + + +```swift +import Supabase + +// Create Supabase client +let supabase = SupabaseClient(supabaseURL: URL(string: "your_project_url")!, supabaseKey: "your_supabase_api_key") + +try await supabase.storage.from("bucket_name") + .upload( + path: "file_path", + file: file, + options: FileOptions( + contentType: "image/jpeg" + ) + ) +``` + From 12ec9a80d8fdbce3a9b3b7a72c8e573711b8f168 Mon Sep 17 00:00:00 2001 From: Jelf Date: Wed, 10 Apr 2024 23:22:26 +0800 Subject: [PATCH 2/4] docs: fix link (#22612) --- apps/www/data/Footer.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/www/data/Footer.ts b/apps/www/data/Footer.ts index 1ac455ff534..a4c785d1758 100644 --- a/apps/www/data/Footer.ts +++ b/apps/www/data/Footer.ts @@ -96,7 +96,7 @@ const footerData = [ }, { text: 'Contributing', - url: '/docs/handbook/contributing', + url: 'https://github.com/supabase/supabase/blob/master/CONTRIBUTING.md', }, { text: 'Open Source', From 69ad84281492bd790c1ac20da8924272d7095cb4 Mon Sep 17 00:00:00 2001 From: Joshua Kim Date: Thu, 11 Apr 2024 01:40:40 +1000 Subject: [PATCH 3/4] Slight changes in how llama_index is now organized. (#22609) Co-authored-by: Charis <26616127+charislam@users.noreply.github.com> --- examples/ai/llamaindex/llamaindex.ipynb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/ai/llamaindex/llamaindex.ipynb b/examples/ai/llamaindex/llamaindex.ipynb index 6f1a26b0d65..435b13cd01c 100644 --- a/examples/ai/llamaindex/llamaindex.ipynb +++ b/examples/ai/llamaindex/llamaindex.ipynb @@ -53,10 +53,10 @@ "# logging.basicConfig(stream=sys.stdout, level=logging.DEBUG)\n", "# logging.getLogger().addHandler(logging.StreamHandler(stream=sys.stdout))\n", "\n", - "from llama_index.storage import StorageContext\n", + "from llama_index.core.storage import StorageContext\n", "from llama_index.readers.web import SimpleWebPageReader\n", "from llama_index.indices.vector_store import VectorStoreIndex\n", - "from llama_index.vector_stores import SupabaseVectorStore\n", + "from llama_index.vector_stores.supabase import SupabaseVectorStore\n", "import textwrap\n", "import html2text" ] From dca045048f9bacc1dff238bb2dbacae99e568c44 Mon Sep 17 00:00:00 2001 From: Guilherme Souza Date: Wed, 10 Apr 2024 13:12:30 -0300 Subject: [PATCH 4/4] chore(docs): general improvements to Swift guides (#22627) --- apps/docs/content/guides/auth/auth-anonymous.mdx | 2 +- apps/docs/content/guides/auth/auth-email.mdx | 4 ++-- .../guides/auth/passwordless-login/auth-magic-link.mdx | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/docs/content/guides/auth/auth-anonymous.mdx b/apps/docs/content/guides/auth/auth-anonymous.mdx index 12acf4f8e9f..a67c430ddb6 100644 --- a/apps/docs/content/guides/auth/auth-anonymous.mdx +++ b/apps/docs/content/guides/auth/auth-anonymous.mdx @@ -169,7 +169,7 @@ await supabase.auth.linkIdentity(OAuthProvider.google); -You can use the [`linkIdentity()`](/docs/reference/swift/auth-linkidentity) method to link an oauth identity to the anonymous user. +You can use the [`linkIdentity()`](/docs/reference/swift/auth-linkidentity) method to link an OAuth identity to the anonymous user. ```swift try await supabase.auth.linkIdentity(provider: .google) diff --git a/apps/docs/content/guides/auth/auth-email.mdx b/apps/docs/content/guides/auth/auth-email.mdx index fade3ca1802..ab2636abfe6 100644 --- a/apps/docs/content/guides/auth/auth-email.mdx +++ b/apps/docs/content/guides/auth/auth-email.mdx @@ -126,7 +126,7 @@ Future signInWithEmail() async { -When your user signs in, call [signInWithPassword()](/docs/reference/swift/auth-signinwithpassword) with their email address and password: +When your user signs in, call [signIn(email:password:)](/docs/reference/swift/auth-signinwithpassword) with their email address and password: ```swift try await supabase.auth.signIn( @@ -185,7 +185,7 @@ Future signOut() async { -When your user signs out, call [signOut()](/docs/reference/swift/auth-signout) to remove them from the browser session and any objects from localStorage: +When your user signs out, call [signOut()](/docs/reference/swift/auth-signout) to remove the session from the local storage: ```swift try await supabase.auth.signOut() diff --git a/apps/docs/content/guides/auth/passwordless-login/auth-magic-link.mdx b/apps/docs/content/guides/auth/passwordless-login/auth-magic-link.mdx index 7074da63847..001cab6c545 100644 --- a/apps/docs/content/guides/auth/passwordless-login/auth-magic-link.mdx +++ b/apps/docs/content/guides/auth/passwordless-login/auth-magic-link.mdx @@ -19,7 +19,7 @@ Set up Magic Link logins for your Supabase application. When using Magic Links in [the PKCE Flow](/blog/supabase-auth-sso-pkce#introducing-pkce), keep in mind that links can only be opened from the same browser that they are sent from (as defined in the PKCE spec). Consequently, a magic link sent from Chrome on Desktop will be invalid if used on a Mobile Device. -The same applies for Magic Links in mobile devices, magic link can only be opened on the same device that originated it. +The same applies for Magic Links in mobile devices. Magic Links can only be opened on the device they are sent from.