kit icon indicating copy to clipboard operation
kit copied to clipboard

Allow remote function argument to be optional

Open j4w8n opened this issue 4 months ago • 5 comments

Describe the problem

I'd like to enhance a remote function with additional but optional behavior.

In my remote function, I'm getting the user's session. By default, this function validates the session based on local JWT information from a cookie (i.e. no additional network request). However, there may be scenarios where I still want the session data but also need to verify the user of that session against a third-party backend server (they could have been banned, signed out globally from another device, etc, but their JWT is still valid).

import { getRequestEvent, query } from "$app/server"
import type { Session } from "@supabase/supabase-js"
import { getValidatedSession } from "./shared.js"
import * as v from "valibot"

export const getSession = query(
  v.optional(v.boolean()),
  async (validate_user): Promise<Session | null> => {
    const { locals } = getRequestEvent()

    return await getValidatedSession(locals.supabase, validate_user)
  }
)

The code works, but throws a type error whenever I'm not passing an argument.

Image

Describe the proposed solution

This can be resolved if we make a change to the type (used AI here):

export type RemoteQueryFunction<Input, Output> = (
-  arg: Input
+  ...args: undefined extends Input ? [arg?: Input] : [arg: Input]
) => RemoteQuery<Output>;

Which yields no type error when passing in no arg,

Image

but still, correctly, shows a type error if a passed-in arg doesn't match the schema

Image

Rinse and repeat for any other remote function types this should apply to.

Importance

would make my life easier

j4w8n avatar Sep 22 '25 01:09 j4w8n

The proposed solution is imo better than what we currently have, but does have the limitation that now it's impossible to have required but possibly undefined arguments, which are sometimes useful. I bet there's some trickery we could do to allow both...

Input extends void might work

dummdidumm avatar Sep 22 '25 08:09 dummdidumm

I tried to figure this out on my own, but had no luck. Then went to AI, but still couldn't get it working. I'll leave it up to the experts at this point. Thanks for considering this!

Was good to learn the differences between things like Input extends undefined, undefined extends Input, etc

j4w8n avatar Sep 22 '25 11:09 j4w8n

With more testing and prodding, I still can't find a solution that satisfies the "required, but undefinedable" case.

Regarding my immediate use case: after watching Simon's latest video on remote functions, I realized I can create remote function B that implements remote function A and does the additional checking.

If you'd like to continue pursuing a type solution, then the issue can remain open; if not, I'm good with it being closed.

j4w8n avatar Sep 24 '25 16:09 j4w8n

i feel like the fix would definitely be a dx improvement and in the rare case someone would really want to require an explicit arg (even if undefined) they could wrap it in an object instead: z.object({ value: z.string().optional() })

vs forcing the use of the current workaround for the common case:

const filterSchema = z.union([z.void(), z.object({...})]);
export const getCategories = query(filterSchema, async (filter) => { })

more importantly, it also prevents passing some arbitrary args like ({}) to bypass the ts error resulting in inconsistent cache keys

yayza avatar Dec 01 '25 21:12 yayza