skiptools / skip-supabase

Supabase integration for Skip apps
GNU Lesser General Public License v3.0
3 stars 1 forks source link

Feature: Want to call rpc function #3

Closed RyoichiroNagami closed 1 day ago

RyoichiroNagami commented 6 days ago

Currently

the supabase-skip package does not support the rpc function

Goal

Use supabase-skip package to call rpc function

Implementation Issues

Supabase Document

RyoichiroNagami commented 5 days ago

@marcprux

I'm reading the source now and looking at it with a few modifications, but would like to discuss the direction.

I'm looking at two patterns that I'm considering as ways to implement this

The first is. It's a form that follows the Supabase swift call. I could reproduce this by mimicking the existing source.

Plan 1

Supabase Client


public class SupabaseClient {
 ...etc codes

 public func rpc(_ fn: String) async throws -> PostgrestResponse<Void> {
        PostgrestResponse(result: await client.postgrest.rpc(fn))
    }
}

PostgrestResponse

public struct PostgrestResponse<T> {
    private let result: io.github.jan.supabase.postgrest.result.PostgrestResult

    init(result: io.github.jan.supabase.postgrest.result.PostgrestResult) {
        self.result = result
    }

    // dummy function
    // supabase-kt rpc not call execute
    public func execute() -> PostgrestResponse {
        return self
    }

    public var count: Int? {
        result.countOrNull()?.toInt()
    }

    public var value: T? {
        nil
    }

    public var data: Data {
        let res: String = result.data
        return res.data(using: String.Encoding.utf8) ?? Data()
    }

    //public let response: HTTPURLResponse
    //public let count: Int?
    //public let value: T

//    public var status: Int {
//        response.statusCode
//    }
}

example calling


let value = try await client.rpc("function_name").execute().data
let dto: CodableStructureName = try JSONDecoder().decode(CodableStructureName.self, from: value)

I don't need the execute process after the ppc function call on the kotlin side.

This would require redundancy in the single point code.


    // dummy function
    // supabase-kt rpc not call execute
    public func execute() -> PostgrestResponse {
        return self
    }

It could be implemented by adding an execute process to PostgrestResponse to return its own value, but I'm having a bit of trouble with that since it's a completely useless process.

If you want to solve that, I wonder if you could extend SupabaseClient and solve it in the following way? I think.

Plan 2


#if !SKIP
extension SupabaseClient {
    public func skipRpc(_ fn: String) async throws -> Data {
        return try await self.rpc(fn).execute().data
    }
}
#endif

#if SKIP
public class SupabaseClient {
    public func skipRpc(fn: String) async -> Data {
        // SKIP INSERT:
        // client.postgrest.rpc(fn).data().data(using = StringEncoding.utf8) ?: Data()
    }
}
#endif

example calling

let value = try await client.skipRpc("function_name")
let res: CodableStructureName = try JSONDecoder().decode(CodableStructureName.self, from: value!)

I feel like if you don't write documentation, they won't know how to use it, and it takes a little time for the user side to understand it.

Honestly, I could implement it either way, I just want to make sure we have a policy. Which one do you think is better?

*Personally, I think Plan1 is fine.

RyoichiroNagami commented 5 days ago

@marcprux @aabewhite

If you find out anything, I'd love to know.

I'm writing a process to call supabase rpc when there is a parameter, but I can't get it to work.

swift code

    public func rpc(_ fn: String) async throws -> PostgrestResponse<Void> {
        PostgrestResponse(result: await client.postgrest.rpc(fn))
    }

スクリーンショット 2024-11-20 19 25 26

kotlin(after transcompile


    open suspend fun rpc(fn: String, params: Any): PostgrestResponse<Unit> = Async.run l@{
        return@l PostgrestResponse(result = client.postgrest.rpc(fn, params))
    }

スクリーンショット 2024-11-20 19 26 09

error message

SkipSupabase.kt:71:62 None of the following candidates is applicable:

supabase-kt rpc function

RyoichiroNagami commented 5 days ago

I tried it with similar values, using the korlin supabase docs as a guide with using SKIP INSERT:

https://supabase.com/docs/reference/kotlin/rpc?queryGroups=build-file&build-file=groovy#:~:text=%7D-,Call%20a%20Postgres%20function,-You%20can%20call

スクリーンショット 2024-11-20 19 36 12

スクリーンショット 2024-11-20 19 36 27

スクリーンショット 2024-11-20 19 36 35

I'm getting the same error:'-(

RyoichiroNagami commented 5 days ago

https://docs-ajzc160j5-supabase.vercel.app/docs/reference/kotlin/installing

My knowledge of Kotlin serialization was vague. I will read this document carefully and try again tomorrow.

marcprux commented 5 days ago

It is surprising that the function is not getting matched. I wonder if it might have to do with the parameter being a reified type:

suspend inline fun <reified T : Any> Postgrest.rpc(
    function: String,
    parameters: T,
    noinline request: RpcRequestBuilder.() -> Unit = {},
): PostgrestResult

You should be able to still call it with an Any, though.

What happens if you manually specify the argument names? E.g.:

supabase.postgrest.rpc(function = "echo_city", parameters = rpcParams)

It might help if you look at the gradle log and paste the full context of the error, which might give other hints about the problem.

RyoichiroNagami commented 4 days ago

@marcprux The error when embedding the kotlin code directly was still that there was no type consistency!

supabase.postgrest.rpc(function = "echo_city", parameters = rpcParams)

スクリーンショット 2024-11-21 20 03 23 スクリーンショット 2024-11-21 20 03 16

I embedded the above code directly and got this output.

errors

SkipSupabase.kt:101:62 None of the following candidates is applicable:
SkipSupabase.kt:110:59 Argument type mismatch: actual type is 'skip.supabase.City', but 'kotlinx.serialization.json.JsonObject' was expected.

Sorry, I just didn't have much time to work on it today.

I'm in the process of setting it up so I can get the gradle logs.

RyoichiroNagami commented 4 days ago

memo If you set the parameter to the following, the compile will pass

kotlinx.serialization.json.JsonObject

スクリーンショット 2024-11-21 20 55 44

If there is no type biosynthesis in the calling process, it will result in a compile error.

スクリーンショット 2024-11-21 20 58 31

RyoichiroNagami commented 4 days ago

@marcprux Is the following the correct way to take Grade logs?

gradle -p . /Android launchDebug

gradle -p ./Android launchDebug
[1/1] Compiling plugin Create SkipLink
[2/2] Compiling plugin skipstone
Building for debugging...
[2/5] Write swift-version--58304C5D6DBC2206.txt
Build complete! (0.22s)
Kotlin does not yet support 22 JDK target, falling back to Kotlin JVM_21 JVM target

> Task :skip-plugins:compileKotlin UP-TO-DATE
Kotlin does not yet support 22 JDK target, falling back to Kotlin JVM_21 JVM target

FAILURE: Build failed with an exception.

* What went wrong:
Could not determine the dependencies of task ':app:compileDebugJavaWithJavac'.
> SDK location not found. Define a valid SDK location with an ANDROID_HOME environment variable or by setting the sdk.dir path in your project's local properties file at '/Users/nagamiryoichiro/Documents/work/git/KochiDempApp/mow-karte-educator/project-name/Android/local.properties'.

* Try:
> Run with --stacktrace option to get the stack trace.
> Run with --info or --debug option to get more log output.
> Run with --scan to get full insights.
> Get more help at https://help.gradle.org.

Deprecated Gradle features were used in this build, making it incompatible with Gradle 9.0.

You can use '--warning-mode all' to show the individual deprecation warnings and determine if they come from your own scripts or plugins.

For more on this, please refer to https://docs.gradle.org/8.10.1/userguide/command_line_interface.html#sec:command_line_warnings in the Gradle documentation.

BUILD FAILED in 1s
5 actionable tasks: 1 executed, 4 up-to-date

I tried to check, but the version of the JVM doesn't seem to be supported,

marcprux commented 4 days ago

SDK location not found. Define a valid SDK location with an ANDROID_HOME environment variable or by setting the sdk.dir path in your project's local properties file at '/Users/nagamiryoichiro/Documents/work/git/KochiDempApp/mow-karte-educator/project-name/Android/local.properties'.

Try setting your ANDROID_HOME environment with something like:

export ANDROID_HOME=$HOME/Library/Android/sdk
marcprux commented 4 days ago

I'm in the process of setting it up so I can get the gradle logs.

You should be able to view the Gradle logs directly from within Xcode by clicking on the error that mentions the log. It should open as a text file within Xcode.

marcprux commented 4 days ago

I just implemented basic RPC function calling in 0.0.4 (in https://github.com/skiptools/skip-supabase/pull/5). Note that it doesn't yet support passing parameters to RPC functions, nor does it support returning a Codable result (but you can get the data of the result and parse it yourself).

Hopefully that is enough to get you started.

RyoichiroNagami commented 3 days ago

@marcprux Thank you.

But I also plan to use rpc with parameters.

I will investigate once I get the latest version so that I can continue to call it there as well.

PS. I guess it's the time difference, but sorry for not getting back to you quicker.

RyoichiroNagami commented 2 days ago

@marcprux modified to call rpc function with parameter and created PR

However, it is not perfect, and the

The parameter type only supports Dictionary<String, String>.

Test code is included, but now it fails because the rpc function is not set on the supabase side.

However, I have successfully called the rpc function in my environment as shown in the image attached in the PR.

lexum0 commented 2 days ago

As far as I remember, the Supabase swift library didn't support mixed types yet for RPC function parameters, but you could mix them by using an AnyJSON dictionary. I don't have access to the code right now to confirm but in case it helps, it could save some time debugging the issue with mixed data types

marcprux commented 2 days ago

Test code is included, but now it fails because the rpc function is not set on the supabase side.

Look great! Can you let me know how you define the public.rpc_test_with_param(testParam1, testParam2) function? I can create the same one in the demo schema so the test cases pass.

marcprux commented 2 days ago

but you could mix them by using an AnyJSON dictionary

And for the record, we do have an equivalent AnyJSON type for the Kotlin side that should be compatible with the Swift side (and thus can be used with Skip without any further coercion). But as you can see, dict2JsonObject is left as an "exercise for the reader".

lexum0 commented 2 days ago

I tried implementing dict2JsonObject but I have no idea how to do it. What are the steps to do it?

marcprux commented 2 days ago

I tried implementing dict2JsonObject but I have no idea how to do it. What are the steps to do it?

If I knew, I would have implemented it. 🤷

I guess I would walk through the AnyJSON dict and use the kotlinx.serialization.json.JsonObject API to build up a JsonObject equivalent of the keys, values, arrays, and null and stuff. It should be fairly rote, but just require some trial-and-error and good test cases.

RyoichiroNagami commented 2 days ago

@marcprux

Look great! Can you let me know how you define the public.rpc_test_with_param(testParam1, testParam2) function? I can create the same one in the demo schema so the test cases pass.

I just checked the Github history and it looks like you have the rpc function ready for us

Thank you very much.

marcprux commented 1 day ago

I've merged https://github.com/skiptools/skip-supabase/pull/7 and released skip-supabase 0.0.5. Thanks for all the work, and we look forward to future contributions!