Skip to content

Commit 04a1397

Browse files
soedirgoavallete
andauthored
feat: reland postgrest 13 support (#1537)
* feat: reland postgrest 13 support * wip: restore tests * wip: exclude __InternalSupabase * feat: infer right version form __InternalSupabase * chore: bump postgrest-js --------- Co-authored-by: avallete <andrew.valleteau@supabase.io>
1 parent 1137035 commit 04a1397

File tree

10 files changed

+399
-35
lines changed

10 files changed

+399
-35
lines changed

package-lock.json

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@
5353
"@supabase/auth-js": "2.71.1",
5454
"@supabase/functions-js": "2.4.5",
5555
"@supabase/node-fetch": "2.6.15",
56-
"@supabase/postgrest-js": "1.19.4",
56+
"@supabase/postgrest-js": "1.21.3",
5757
"@supabase/realtime-js": "2.15.1",
5858
"@supabase/storage-js": "^2.10.4"
5959
},

src/SupabaseClient.ts

Lines changed: 38 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -30,12 +30,36 @@ import { Fetch, GenericSchema, SupabaseClientOptions, SupabaseAuthClientOptions
3030
*/
3131
export default class SupabaseClient<
3232
Database = any,
33-
SchemaName extends string & keyof Database = 'public' extends keyof Database
33+
// The second type parameter is also used for specifying db_schema, so we
34+
// support both cases.
35+
// TODO: Allow setting db_schema from ClientOptions.
36+
SchemaNameOrClientOptions extends
37+
| (string & keyof Omit<Database, '__InternalSupabase'>)
38+
| { PostgrestVersion: string } = 'public' extends keyof Omit<Database, '__InternalSupabase'>
3439
? 'public'
35-
: string & keyof Database,
36-
Schema extends GenericSchema = Database[SchemaName] extends GenericSchema
37-
? Database[SchemaName]
38-
: any
40+
: string & keyof Omit<Database, '__InternalSupabase'>,
41+
SchemaName extends string &
42+
keyof Omit<Database, '__InternalSupabase'> = SchemaNameOrClientOptions extends string &
43+
keyof Omit<Database, '__InternalSupabase'>
44+
? SchemaNameOrClientOptions
45+
: 'public' extends keyof Omit<Database, '__InternalSupabase'>
46+
? 'public'
47+
: string & keyof Omit<Omit<Database, '__InternalSupabase'>, '__InternalSupabase'>,
48+
Schema extends Omit<Database, '__InternalSupabase'>[SchemaName] extends GenericSchema
49+
? Omit<Database, '__InternalSupabase'>[SchemaName]
50+
: never = Omit<Database, '__InternalSupabase'>[SchemaName] extends GenericSchema
51+
? Omit<Database, '__InternalSupabase'>[SchemaName]
52+
: never,
53+
ClientOptions extends { PostgrestVersion: string } = SchemaNameOrClientOptions extends string &
54+
keyof Omit<Database, '__InternalSupabase'>
55+
? // If the version isn't explicitly set, look for it in the __InternalSupabase object to infer the right version
56+
Database extends { __InternalSupabase: { PostgrestVersion: string } }
57+
? Database['__InternalSupabase']
58+
: // otherwise default to 12
59+
{ PostgrestVersion: '12' }
60+
: SchemaNameOrClientOptions extends { PostgrestVersion: string }
61+
? SchemaNameOrClientOptions
62+
: never
3963
> {
4064
/**
4165
* Supabase Auth allows you to create and manage user sessions for access to data that is secured by access policies.
@@ -51,7 +75,7 @@ export default class SupabaseClient<
5175
protected authUrl: URL
5276
protected storageUrl: URL
5377
protected functionsUrl: URL
54-
protected rest: PostgrestClient<Database, SchemaName, Schema>
78+
protected rest: PostgrestClient<Database, ClientOptions, SchemaName>
5579
protected storageKey: string
5680
protected fetch?: Fetch
5781
protected changedAccessToken?: string
@@ -161,16 +185,16 @@ export default class SupabaseClient<
161185
from<
162186
TableName extends string & keyof Schema['Tables'],
163187
Table extends Schema['Tables'][TableName]
164-
>(relation: TableName): PostgrestQueryBuilder<Schema, Table, TableName>
188+
>(relation: TableName): PostgrestQueryBuilder<ClientOptions, Schema, Table, TableName>
165189
from<ViewName extends string & keyof Schema['Views'], View extends Schema['Views'][ViewName]>(
166190
relation: ViewName
167-
): PostgrestQueryBuilder<Schema, View, ViewName>
191+
): PostgrestQueryBuilder<ClientOptions, Schema, View, ViewName>
168192
/**
169193
* Perform a query on a table or a view.
170194
*
171195
* @param relation - The table or view name to query
172196
*/
173-
from(relation: string): PostgrestQueryBuilder<Schema, any, any> {
197+
from(relation: string): PostgrestQueryBuilder<ClientOptions, Schema, any> {
174198
return this.rest.from(relation)
175199
}
176200

@@ -182,10 +206,11 @@ export default class SupabaseClient<
182206
*
183207
* @param schema - The schema to query
184208
*/
185-
schema<DynamicSchema extends string & keyof Database>(
209+
schema<DynamicSchema extends string & keyof Omit<Database, '__InternalSupabase'>>(
186210
schema: DynamicSchema
187211
): PostgrestClient<
188212
Database,
213+
ClientOptions,
189214
DynamicSchema,
190215
Database[DynamicSchema] extends GenericSchema ? Database[DynamicSchema] : any
191216
> {
@@ -225,6 +250,7 @@ export default class SupabaseClient<
225250
count?: 'exact' | 'planned' | 'estimated'
226251
} = {}
227252
): PostgrestFilterBuilder<
253+
ClientOptions,
228254
Schema,
229255
Fn['Returns'] extends any[]
230256
? Fn['Returns'][number] extends Record<string, unknown>
@@ -233,7 +259,8 @@ export default class SupabaseClient<
233259
: never,
234260
Fn['Returns'],
235261
FnName,
236-
null
262+
null,
263+
'RPC'
237264
> {
238265
return this.rest.rpc(fn, args, options)
239266
}

src/index.ts

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import SupabaseClient from './SupabaseClient'
2-
import type { GenericSchema, SupabaseClientOptions } from './lib/types'
2+
import type { SupabaseClientOptions } from './lib/types'
33

44
export * from '@supabase/auth-js'
55
export type { User as AuthUser, Session as AuthSession } from '@supabase/auth-js'
@@ -26,18 +26,28 @@ export type { SupabaseClientOptions, QueryResult, QueryData, QueryError } from '
2626
*/
2727
export const createClient = <
2828
Database = any,
29-
SchemaName extends string & keyof Database = 'public' extends keyof Database
29+
SchemaNameOrClientOptions extends
30+
| (string & keyof Omit<Database, '__InternalSupabase'>)
31+
| { PostgrestVersion: string } = 'public' extends keyof Omit<Database, '__InternalSupabase'>
3032
? 'public'
31-
: string & keyof Database,
32-
Schema extends GenericSchema = Database[SchemaName] extends GenericSchema
33-
? Database[SchemaName]
34-
: any
33+
: string & keyof Omit<Database, '__InternalSupabase'>,
34+
SchemaName extends string &
35+
keyof Omit<Database, '__InternalSupabase'> = SchemaNameOrClientOptions extends string &
36+
keyof Omit<Database, '__InternalSupabase'>
37+
? SchemaNameOrClientOptions
38+
: 'public' extends keyof Omit<Database, '__InternalSupabase'>
39+
? 'public'
40+
: string & keyof Omit<Omit<Database, '__InternalSupabase'>, '__InternalSupabase'>
3541
>(
3642
supabaseUrl: string,
3743
supabaseKey: string,
3844
options?: SupabaseClientOptions<SchemaName>
39-
): SupabaseClient<Database, SchemaName, Schema> => {
40-
return new SupabaseClient<Database, SchemaName, Schema>(supabaseUrl, supabaseKey, options)
45+
): SupabaseClient<Database, SchemaNameOrClientOptions, SchemaName> => {
46+
return new SupabaseClient<Database, SchemaNameOrClientOptions, SchemaName>(
47+
supabaseUrl,
48+
supabaseKey,
49+
options
50+
)
4151
}
4252

4353
// Check for Node.js <= 18 deprecation

test/integration/next/app/layout.tsx

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import type { Metadata } from 'next'
2+
3+
export const metadata: Metadata = {
4+
title: 'Supabase Integration Test',
5+
description: 'Testing Supabase integration with Next.js',
6+
}
7+
8+
export default function RootLayout({ children }: { children: React.ReactNode }) {
9+
return (
10+
<html lang="en">
11+
<body>{children}</body>
12+
</html>
13+
)
14+
}

test/integration/next/package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@
77
"lint": "next lint",
88
"test": "playwright test",
99
"test:ui": "playwright test --ui",
10-
"test:debug": "playwright test --debug"
10+
"test:debug": "playwright test --debug",
11+
"test:types": "npx tsd --files tests/types/*.test-d.ts"
1112
},
1213
"dependencies": {
1314
"@radix-ui/react-checkbox": "^1.3.1",
@@ -37,6 +38,7 @@
3738
"postcss": "^8",
3839
"tailwindcss": "^3.4.1",
3940
"tailwindcss-animate": "^1.0.7",
41+
"tsd": "^0.33.0",
4042
"typescript": "^5"
4143
}
4244
}
Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
import { createServerClient, createBrowserClient } from '@supabase/ssr'
2+
import { expectType } from 'tsd'
3+
4+
// Copied from ts-expect
5+
// https://github.com/TypeStrong/ts-expect/blob/master/src/index.ts#L23-L27
6+
export type TypeEqual<Target, Value> = (<T>() => T extends Target ? 1 : 2) extends <
7+
T
8+
>() => T extends Value ? 1 : 2
9+
? true
10+
: false
11+
12+
type Database = {
13+
public: {
14+
Tables: {
15+
shops: {
16+
Row: {
17+
address: string | null
18+
id: number
19+
shop_geom: unknown | null
20+
}
21+
Insert: {
22+
address?: string | null
23+
id: number
24+
shop_geom?: unknown | null
25+
}
26+
Update: {
27+
address?: string | null
28+
id?: number
29+
shop_geom?: unknown | null
30+
}
31+
Relationships: []
32+
}
33+
}
34+
Views: {
35+
[_ in never]: never
36+
}
37+
Functions: {
38+
[_ in never]: never
39+
}
40+
Enums: {
41+
[_ in never]: never
42+
}
43+
CompositeTypes: {
44+
[_ in never]: never
45+
}
46+
}
47+
}
48+
49+
{
50+
// createBrowserClient should return a typed client
51+
const pg12Client = createBrowserClient<Database>('HTTP://localhost:3000', '')
52+
const res12 = await pg12Client.from('shops').select('*')
53+
expectType<
54+
TypeEqual<
55+
| {
56+
address: string | null
57+
id: number
58+
shop_geom: unknown | null
59+
}[]
60+
| null,
61+
typeof res12.data
62+
>
63+
>(true)
64+
}
65+
66+
{
67+
// createBrowserClient should infer everything to any without types provided
68+
const pg12Client = createBrowserClient('HTTP://localhost:3000', '')
69+
const res12 = await pg12Client.from('shops').select('address, id, relation(field)')
70+
expectType<
71+
TypeEqual<
72+
| {
73+
address: any
74+
id: any
75+
relation: {
76+
field: any
77+
}[]
78+
}[]
79+
| null,
80+
typeof res12.data
81+
>
82+
>(true)
83+
}
84+
85+
{
86+
// createServerClient should return a typed client
87+
const pg12Server = createServerClient<Database>('HTTP://localhost:3000', '')
88+
const res12 = await pg12Server.from('shops').select('*')
89+
expectType<
90+
TypeEqual<
91+
| {
92+
address: string | null
93+
id: number
94+
shop_geom: unknown | null
95+
}[]
96+
| null,
97+
typeof res12.data
98+
>
99+
>(true)
100+
}
101+
102+
{
103+
// createServerClient should infer everything to any without types provided
104+
const pg12Server = createServerClient('HTTP://localhost:3000', '')
105+
const res12 = await pg12Server.from('shops').select('address, id, relation(field)')
106+
expectType<
107+
TypeEqual<
108+
| {
109+
address: any
110+
id: any
111+
relation: {
112+
field: any
113+
}[]
114+
}[]
115+
| null,
116+
typeof res12.data
117+
>
118+
>(true)
119+
}
120+
// Should be able to get a PostgrestVersion 13 client from __InternalSupabase
121+
{
122+
type DatabaseWithInternals = {
123+
__InternalSupabase: {
124+
PostgrestVersion: '13'
125+
}
126+
public: {
127+
Tables: {
128+
shops: {
129+
Row: {
130+
address: string | null
131+
id: number
132+
shop_geom: unknown | null
133+
}
134+
Insert: {
135+
address?: string | null
136+
id: number
137+
shop_geom?: unknown | null
138+
}
139+
Update: {
140+
address?: string | null
141+
id?: number
142+
shop_geom?: unknown | null
143+
}
144+
Relationships: []
145+
}
146+
}
147+
Views: {
148+
[_ in never]: never
149+
}
150+
Functions: {
151+
[_ in never]: never
152+
}
153+
Enums: {
154+
[_ in never]: never
155+
}
156+
CompositeTypes: {
157+
[_ in never]: never
158+
}
159+
}
160+
}
161+
const pg13BrowserClient = createBrowserClient<DatabaseWithInternals>('HTTP://localhost:3000', '')
162+
const pg13ServerClient = createServerClient<DatabaseWithInternals>('HTTP://localhost:3000', '', {
163+
cookies: { getAll: () => [], setAll: () => {} },
164+
})
165+
const res13 = await pg13BrowserClient.from('shops').update({ id: 21 }).maxAffected(1)
166+
expectType<typeof res13.data>(null)
167+
const res13Server = await pg13ServerClient.from('shops').update({ id: 21 }).maxAffected(1)
168+
expectType<typeof res13Server.data>(null)
169+
}
170+
{
171+
// Should default to PostgrestVersion 12
172+
const pg12BrowserClient = createBrowserClient<Database>('HTTP://localhost:3000', '')
173+
const pg12ServerClient = createServerClient<Database>('HTTP://localhost:3000', '', {
174+
cookies: { getAll: () => [], setAll: () => {} },
175+
})
176+
const res12 = await pg12BrowserClient.from('shops').update({ id: 21 }).maxAffected(1)
177+
expectType<typeof res12.Error>('maxAffected method only available on postgrest 13+')
178+
const res12Server = await pg12ServerClient.from('shops').update({ id: 21 }).maxAffected(1)
179+
expectType<typeof res12Server.Error>('maxAffected method only available on postgrest 13+')
180+
}

test/integration/next/tsconfig.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,5 +24,5 @@
2424
}
2525
},
2626
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
27-
"exclude": ["node_modules"]
27+
"exclude": ["node_modules", "test/types/*.test-d.ts"]
2828
}

0 commit comments

Comments
 (0)