Skip to content

Commit 12faa3d

Browse files
authored
fix(types): type result for throwOnError responses (#590)
* fix(types): type result for throwOnError responses When using throwOnError(), the response type is now more strictly typed: - Data is guaranteed to be non-null - Error field is removed from response type - Response type is controlled by generic ThrowOnError boolean parameter Fixes #563 * chore: re-use generic types * fix: return this to comply with PostgresFilterBuilder * chore: fix test to check inheritance and not equality
1 parent 8d32089 commit 12faa3d

File tree

3 files changed

+93
-16
lines changed

3 files changed

+93
-16
lines changed

src/PostgrestBuilder.ts

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
// @ts-ignore
22
import nodeFetch from '@supabase/node-fetch'
33

4-
import type { Fetch, PostgrestSingleResponse } from './types'
4+
import type { Fetch, PostgrestSingleResponse, PostgrestResponseSuccess } from './types'
55
import PostgrestError from './PostgrestError'
66

7-
export default abstract class PostgrestBuilder<Result>
8-
implements PromiseLike<PostgrestSingleResponse<Result>>
7+
export default abstract class PostgrestBuilder<Result, ThrowOnError extends boolean = false>
8+
implements
9+
PromiseLike<
10+
ThrowOnError extends true ? PostgrestResponseSuccess<Result> : PostgrestSingleResponse<Result>
11+
>
912
{
1013
protected method: 'GET' | 'HEAD' | 'POST' | 'PATCH' | 'DELETE'
1114
protected url: URL
@@ -42,9 +45,9 @@ export default abstract class PostgrestBuilder<Result>
4245
*
4346
* {@link https://github.com/supabase/supabase-js/issues/92}
4447
*/
45-
throwOnError(): this {
48+
throwOnError(): this & PostgrestBuilder<Result, true> {
4649
this.shouldThrowOnError = true
47-
return this
50+
return this as this & PostgrestBuilder<Result, true>
4851
}
4952

5053
/**
@@ -56,9 +59,18 @@ export default abstract class PostgrestBuilder<Result>
5659
return this
5760
}
5861

59-
then<TResult1 = PostgrestSingleResponse<Result>, TResult2 = never>(
62+
then<
63+
TResult1 = ThrowOnError extends true
64+
? PostgrestResponseSuccess<Result>
65+
: PostgrestSingleResponse<Result>,
66+
TResult2 = never
67+
>(
6068
onfulfilled?:
61-
| ((value: PostgrestSingleResponse<Result>) => TResult1 | PromiseLike<TResult1>)
69+
| ((
70+
value: ThrowOnError extends true
71+
? PostgrestResponseSuccess<Result>
72+
: PostgrestSingleResponse<Result>
73+
) => TResult1 | PromiseLike<TResult1>)
6274
| undefined
6375
| null,
6476
onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | undefined | null

src/PostgrestTransformBuilder.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,7 @@ export default class PostgrestTransformBuilder<
192192
ResultOne = Result extends (infer ResultOne)[] ? ResultOne : never
193193
>(): PostgrestBuilder<ResultOne> {
194194
this.headers['Accept'] = 'application/vnd.pgrst.object+json'
195-
return this as PostgrestBuilder<ResultOne>
195+
return this as unknown as PostgrestBuilder<ResultOne>
196196
}
197197

198198
/**
@@ -212,23 +212,23 @@ export default class PostgrestTransformBuilder<
212212
this.headers['Accept'] = 'application/vnd.pgrst.object+json'
213213
}
214214
this.isMaybeSingle = true
215-
return this as PostgrestBuilder<ResultOne | null>
215+
return this as unknown as PostgrestBuilder<ResultOne | null>
216216
}
217217

218218
/**
219219
* Return `data` as a string in CSV format.
220220
*/
221221
csv(): PostgrestBuilder<string> {
222222
this.headers['Accept'] = 'text/csv'
223-
return this as PostgrestBuilder<string>
223+
return this as unknown as PostgrestBuilder<string>
224224
}
225225

226226
/**
227227
* Return `data` as an object in [GeoJSON](https://geojson.org) format.
228228
*/
229229
geojson(): PostgrestBuilder<Record<string, unknown>> {
230230
this.headers['Accept'] = 'application/geo+json'
231-
return this as PostgrestBuilder<Record<string, unknown>>
231+
return this as unknown as PostgrestBuilder<Record<string, unknown>>
232232
}
233233

234234
/**
@@ -285,8 +285,8 @@ export default class PostgrestTransformBuilder<
285285
this.headers[
286286
'Accept'
287287
] = `application/vnd.pgrst.plan+${format}; for="${forMediatype}"; options=${options};`
288-
if (format === 'json') return this as PostgrestBuilder<Record<string, unknown>[]>
289-
else return this as PostgrestBuilder<string>
288+
if (format === 'json') return this as unknown as PostgrestBuilder<Record<string, unknown>[]>
289+
else return this as unknown as PostgrestBuilder<string>
290290
}
291291

292292
/**

test/index.test-d.ts

Lines changed: 68 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1+
import { TypeEqual } from 'ts-expect'
12
import { expectError, expectType } from 'tsd'
2-
import { PostgrestClient } from '../src/index'
3+
import { PostgrestClient, PostgrestError } from '../src/index'
34
import { Prettify } from '../src/types'
45
import { Database, Json } from './types'
56

@@ -208,6 +209,70 @@ const postgrest = new PostgrestClient<Database>(REST_URL)
208209
const x = postgrest.from('channels').select()
209210
const y = x.throwOnError()
210211
const z = x.setHeader('', '')
211-
expectType<typeof x>(y)
212-
expectType<typeof x>(z)
212+
expectType<typeof y extends typeof x ? true : false>(true)
213+
expectType<typeof z extends typeof x ? true : false>(true)
214+
}
215+
216+
// Should have nullable data and error field
217+
{
218+
const result = await postgrest.from('users').select('username, messages(id, message)').limit(1)
219+
let expected:
220+
| {
221+
username: string
222+
messages: {
223+
id: number
224+
message: string | null
225+
}[]
226+
}[]
227+
| null
228+
const { data } = result
229+
const { error } = result
230+
expectType<TypeEqual<typeof data, typeof expected>>(true)
231+
let err: PostgrestError | null
232+
expectType<TypeEqual<typeof error, typeof err>>(true)
233+
}
234+
235+
// Should have non nullable data and no error fields if throwOnError is added
236+
{
237+
const result = await postgrest
238+
.from('users')
239+
.select('username, messages(id, message)')
240+
.limit(1)
241+
.throwOnError()
242+
const { data } = result
243+
const { error } = result
244+
let expected:
245+
| {
246+
username: string
247+
messages: {
248+
id: number
249+
message: string | null
250+
}[]
251+
}[]
252+
expectType<TypeEqual<typeof data, typeof expected>>(true)
253+
expectType<TypeEqual<typeof error, null>>(true)
254+
error
255+
}
256+
257+
// Should work with throwOnError middle of the chaining
258+
{
259+
const result = await postgrest
260+
.from('users')
261+
.select('username, messages(id, message)')
262+
.throwOnError()
263+
.eq('username', 'test')
264+
.limit(1)
265+
const { data } = result
266+
const { error } = result
267+
let expected:
268+
| {
269+
username: string
270+
messages: {
271+
id: number
272+
message: string | null
273+
}[]
274+
}[]
275+
expectType<TypeEqual<typeof data, typeof expected>>(true)
276+
expectType<TypeEqual<typeof error, null>>(true)
277+
error
213278
}

0 commit comments

Comments
 (0)