diff --git a/packages/core/postgrest-js/src/PostgrestClient.ts b/packages/core/postgrest-js/src/PostgrestClient.ts index 6cefadd00..dbc11bc60 100644 --- a/packages/core/postgrest-js/src/PostgrestClient.ts +++ b/packages/core/postgrest-js/src/PostgrestClient.ts @@ -144,6 +144,15 @@ export default class PostgrestClient< * * `"estimated"`: Uses exact count for low numbers and planned count for high * numbers. + * + * @example + * ```ts + * // For cross-schema functions where type inference fails, use overrideTypes: + * const { data } = await supabase + * .schema('schema_b') + * .rpc('function_a', {}) + * .overrideTypes<{ id: string; user_id: string }[]>() + * ``` */ rpc< FnName extends string & keyof Schema['Functions'], diff --git a/packages/core/postgrest-js/src/types/common/rpc.ts b/packages/core/postgrest-js/src/types/common/rpc.ts index 52e57419a..f9b789211 100644 --- a/packages/core/postgrest-js/src/types/common/rpc.ts +++ b/packages/core/postgrest-js/src/types/common/rpc.ts @@ -62,6 +62,10 @@ type RpcFunctionNotFound = { Relationships: null } +type CrossSchemaError = { + error: true +} & `Function returns SETOF from a different schema ('${TableRef}'). Use .overrideTypes() to specify the return type explicitly.` + export type GetRpcFunctionFilterBuilderByArgs< Schema extends GenericSchema, FnName extends string & keyof Schema['Functions'], @@ -98,13 +102,20 @@ export type GetRpcFunctionFilterBuilderByArgs< ? // If we are dealing with an non-typed client everything is any IsAny extends true ? { Row: any; Result: any; RelationName: FnName; Relationships: null } - : // Otherwise, we use the arguments based function definition narrowing to get the rigt value + : // Otherwise, we use the arguments based function definition narrowing to get the right value Fn extends GenericFunction ? { Row: Fn['SetofOptions'] extends GenericSetofOption - ? Fn['SetofOptions']['isSetofReturn'] extends true + ? Fn['SetofOptions']['to'] extends keyof TablesAndViews ? TablesAndViews[Fn['SetofOptions']['to']]['Row'] - : TablesAndViews[Fn['SetofOptions']['to']]['Row'] + : // Cross-schema fallback: use Returns type when table is not in current schema + Fn['Returns'] extends any[] + ? Fn['Returns'][number] extends Record + ? Fn['Returns'][number] + : CrossSchemaError + : Fn['Returns'] extends Record + ? Fn['Returns'] + : CrossSchemaError : Fn['Returns'] extends any[] ? Fn['Returns'][number] extends Record ? Fn['Returns'][number] @@ -125,7 +136,9 @@ export type GetRpcFunctionFilterBuilderByArgs< Relationships: Fn['SetofOptions'] extends GenericSetofOption ? Fn['SetofOptions']['to'] extends keyof Schema['Tables'] ? Schema['Tables'][Fn['SetofOptions']['to']]['Relationships'] - : Schema['Views'][Fn['SetofOptions']['to']]['Relationships'] + : Fn['SetofOptions']['to'] extends keyof Schema['Views'] + ? Schema['Views'][Fn['SetofOptions']['to']]['Relationships'] + : null : null } : // If we failed to find the function by argument, we still pass with any but also add an overridable diff --git a/packages/core/postgrest-js/test/override-types.test-d.ts b/packages/core/postgrest-js/test/override-types.test-d.ts index c144ad498..367e25b76 100644 --- a/packages/core/postgrest-js/test/override-types.test-d.ts +++ b/packages/core/postgrest-js/test/override-types.test-d.ts @@ -548,3 +548,12 @@ const postgrest = new PostgrestClient(REST_URL) > >(true) } + +// Test cross-schema rpc falls back to Returns type +{ + const result = await postgrest.schema('personal').rpc('get_public_users', {}) + if (result.error) { + throw new Error(result.error.message) + } + expectType>(true) +} diff --git a/packages/core/postgrest-js/test/types.override.ts b/packages/core/postgrest-js/test/types.override.ts index 1f6568eb0..df1047891 100644 --- a/packages/core/postgrest-js/test/types.override.ts +++ b/packages/core/postgrest-js/test/types.override.ts @@ -32,6 +32,18 @@ export type Database = MergeDeep< } } } + Functions: { + get_public_users: { + Args: Record + Returns: { id: string; user_id: string }[] + SetofOptions: { + from: '*' + to: 'public.users' + isOneToOne: false + isSetofReturn: true + } + } + } Views: {} Enums: {} CompositeTypes: {} diff --git a/packages/core/supabase-js/src/lib/rest/types/common/rpc.ts b/packages/core/supabase-js/src/lib/rest/types/common/rpc.ts index fe457bded..25285efe2 100644 --- a/packages/core/supabase-js/src/lib/rest/types/common/rpc.ts +++ b/packages/core/supabase-js/src/lib/rest/types/common/rpc.ts @@ -72,6 +72,10 @@ type RpcFunctionNotFound = { Relationships: null } +type CrossSchemaError = { + error: true +} & `Function returns SETOF from a different schema ('${TableRef}'). Use .overrideTypes() to specify the return type explicitly.` + export type GetRpcFunctionFilterBuilderByArgs< Schema extends GenericSchema, FnName extends string & keyof Schema['Functions'], @@ -108,13 +112,20 @@ export type GetRpcFunctionFilterBuilderByArgs< ? // If we are dealing with an non-typed client everything is any IsAny extends true ? { Row: any; Result: any; RelationName: FnName; Relationships: null } - : // Otherwise, we use the arguments based function definition narrowing to get the rigt value + : // Otherwise, we use the arguments based function definition narrowing to get the right value Fn extends GenericFunction ? { Row: Fn['SetofOptions'] extends GenericSetofOption - ? Fn['SetofOptions']['isSetofReturn'] extends true + ? Fn['SetofOptions']['to'] extends keyof TablesAndViews ? TablesAndViews[Fn['SetofOptions']['to']]['Row'] - : TablesAndViews[Fn['SetofOptions']['to']]['Row'] + : // Cross-schema fallback: use Returns type when table is not in current schema + Fn['Returns'] extends any[] + ? Fn['Returns'][number] extends Record + ? Fn['Returns'][number] + : CrossSchemaError + : Fn['Returns'] extends Record + ? Fn['Returns'] + : CrossSchemaError : Fn['Returns'] extends any[] ? Fn['Returns'][number] extends Record ? Fn['Returns'][number] @@ -135,7 +146,9 @@ export type GetRpcFunctionFilterBuilderByArgs< Relationships: Fn['SetofOptions'] extends GenericSetofOption ? Fn['SetofOptions']['to'] extends keyof Schema['Tables'] ? Schema['Tables'][Fn['SetofOptions']['to']]['Relationships'] - : Schema['Views'][Fn['SetofOptions']['to']]['Relationships'] + : Fn['SetofOptions']['to'] extends keyof Schema['Views'] + ? Schema['Views'][Fn['SetofOptions']['to']]['Relationships'] + : null : null } : // If we failed to find the function by argument, we still pass with any but also add an overridable