From b465a76e4dcd3d0cd2fe78f5dbe364450a933846 Mon Sep 17 00:00:00 2001 From: Zhongliang Wang Date: Tue, 8 Nov 2022 11:18:00 +0800 Subject: [PATCH 1/5] wip: throw on error typing --- src/PostgrestBuilder.ts | 27 ++++++++++++++++++++------- src/PostgrestClient.ts | 27 ++++++++++++++++----------- src/PostgrestFilterBuilder.ts | 5 +++-- src/PostgrestQueryBuilder.ts | 28 +++++++++++++--------------- src/PostgrestTransformBuilder.ts | 18 ++++++++++-------- src/types.ts | 4 +++- test/transforms.ts | 2 +- 7 files changed, 66 insertions(+), 45 deletions(-) diff --git a/src/PostgrestBuilder.ts b/src/PostgrestBuilder.ts index c1529e91..bbbdcd47 100644 --- a/src/PostgrestBuilder.ts +++ b/src/PostgrestBuilder.ts @@ -1,9 +1,22 @@ import crossFetch from 'cross-fetch' +import PostgrestFilterBuilder from './PostgrestFilterBuilder' +import PostgrestQueryBuilder from './PostgrestQueryBuilder' +import PostgrestTransformBuilder from './PostgrestTransformBuilder' import type { Fetch, PostgrestResponse } from './types' -export default abstract class PostgrestBuilder - implements PromiseLike> +type EnableThrowOnError = T extends PostgrestBuilder + ? PostgrestBuilder + : T extends PostgrestFilterBuilder + ? PostgrestFilterBuilder + : T extends PostgrestQueryBuilder + ? PostgrestQueryBuilder + : T extends PostgrestTransformBuilder + ? PostgrestTransformBuilder + : T + +export default abstract class PostgrestBuilder + implements PromiseLike> { protected method: 'GET' | 'HEAD' | 'POST' | 'PATCH' | 'DELETE' protected url: URL @@ -15,7 +28,7 @@ export default abstract class PostgrestBuilder protected fetch: Fetch protected allowEmpty: boolean - constructor(builder: PostgrestBuilder) { + constructor(builder: PostgrestBuilder) { this.method = builder.method this.url = builder.url this.headers = builder.headers @@ -40,14 +53,14 @@ export default abstract class PostgrestBuilder * * {@link https://github.com/supabase/supabase-js/issues/92} */ - throwOnError(): this { + throwOnError(): EnableThrowOnError { this.shouldThrowOnError = true - return this + return this as EnableThrowOnError } - then, TResult2 = never>( + then, TResult2 = never>( onfulfilled?: - | ((value: PostgrestResponse) => TResult1 | PromiseLike) + | ((value: PostgrestResponse) => TResult1 | PromiseLike) | undefined | null, onrejected?: ((reason: any) => TResult2 | PromiseLike) | undefined | null diff --git a/src/PostgrestClient.ts b/src/PostgrestClient.ts index 2e14a12f..911ec90c 100644 --- a/src/PostgrestClient.ts +++ b/src/PostgrestClient.ts @@ -63,15 +63,18 @@ export default class PostgrestClient< */ from< TableName extends string & keyof Schema['Tables'], - Table extends Schema['Tables'][TableName] - >(relation: TableName): PostgrestQueryBuilder - from( - relation: ViewName - ): PostgrestQueryBuilder - from(relation: string): PostgrestQueryBuilder - from(relation: string): PostgrestQueryBuilder { + Table extends Schema['Tables'][TableName], + ThrowOnError + >(relation: TableName): PostgrestQueryBuilder + from< + ViewName extends string & keyof Schema['Views'], + View extends Schema['Views'][ViewName], + ThrowOnError + >(relation: ViewName): PostgrestQueryBuilder + from(relation: string): PostgrestQueryBuilder + from(relation: string): PostgrestQueryBuilder { const url = new URL(`${this.url}/${relation}`) - return new PostgrestQueryBuilder(url, { + return new PostgrestQueryBuilder(url, { headers: { ...this.headers }, schema: this.schema, fetch: this.fetch, @@ -101,7 +104,8 @@ export default class PostgrestClient< */ rpc< FunctionName extends string & keyof Schema['Functions'], - Function_ extends Schema['Functions'][FunctionName] + Function_ extends Schema['Functions'][FunctionName], + ThrowOnError >( fn: FunctionName, args: Function_['Args'] = {}, @@ -119,7 +123,8 @@ export default class PostgrestClient< ? Function_['Returns'][number] : never : never, - Function_['Returns'] + Function_['Returns'], + ThrowOnError > { let method: 'HEAD' | 'POST' const url = new URL(`${this.url}/rpc/${fn}`) @@ -147,6 +152,6 @@ export default class PostgrestClient< body, fetch: this.fetch, allowEmpty: false, - } as unknown as PostgrestBuilder) + } as unknown as PostgrestBuilder) } } diff --git a/src/PostgrestFilterBuilder.ts b/src/PostgrestFilterBuilder.ts index bab03de5..b052ba57 100644 --- a/src/PostgrestFilterBuilder.ts +++ b/src/PostgrestFilterBuilder.ts @@ -28,8 +28,9 @@ type FilterOperator = export default class PostgrestFilterBuilder< Schema extends GenericSchema, Row extends Record, - Result -> extends PostgrestTransformBuilder { + Result, + ThrowOnError +> extends PostgrestTransformBuilder { /** * Match only rows where `column` is equal to `value`. * diff --git a/src/PostgrestQueryBuilder.ts b/src/PostgrestQueryBuilder.ts index d25966f9..120e728a 100644 --- a/src/PostgrestQueryBuilder.ts +++ b/src/PostgrestQueryBuilder.ts @@ -5,7 +5,8 @@ import { Fetch, GenericSchema, GenericTable, GenericView } from './types' export default class PostgrestQueryBuilder< Schema extends GenericSchema, - Relation extends GenericTable | GenericView + Relation extends GenericTable | GenericView, + ThrowOnError > { url: URL headers: Record @@ -52,10 +53,7 @@ export default class PostgrestQueryBuilder< * `"estimated"`: Uses exact count for low numbers and planned count for high * numbers. */ - select< - Query extends string = '*', - Result = GetResult - >( + select>( columns?: Query, { head = false, @@ -64,7 +62,7 @@ export default class PostgrestQueryBuilder< head?: boolean count?: 'exact' | 'planned' | 'estimated' } = {} - ): PostgrestFilterBuilder { + ): PostgrestFilterBuilder { const method = head ? 'HEAD' : 'GET' // Remove whitespaces except when quoted let quoted = false @@ -92,7 +90,7 @@ export default class PostgrestQueryBuilder< schema: this.schema, fetch: this.fetch, allowEmpty: false, - } as unknown as PostgrestBuilder) + } as unknown as PostgrestBuilder) } /** @@ -124,7 +122,7 @@ export default class PostgrestQueryBuilder< }: { count?: 'exact' | 'planned' | 'estimated' } = {} - ): PostgrestFilterBuilder { + ): PostgrestFilterBuilder { const method = 'POST' const prefersHeaders = [] @@ -153,7 +151,7 @@ export default class PostgrestQueryBuilder< body, fetch: this.fetch, allowEmpty: false, - } as unknown as PostgrestBuilder) + } as unknown as PostgrestBuilder) } /** @@ -200,7 +198,7 @@ export default class PostgrestQueryBuilder< ignoreDuplicates?: boolean count?: 'exact' | 'planned' | 'estimated' } = {} - ): PostgrestFilterBuilder { + ): PostgrestFilterBuilder { const method = 'POST' const prefersHeaders = [`resolution=${ignoreDuplicates ? 'ignore' : 'merge'}-duplicates`] @@ -223,7 +221,7 @@ export default class PostgrestQueryBuilder< body, fetch: this.fetch, allowEmpty: false, - } as unknown as PostgrestBuilder) + } as unknown as PostgrestBuilder) } /** @@ -254,7 +252,7 @@ export default class PostgrestQueryBuilder< }: { count?: 'exact' | 'planned' | 'estimated' } = {} - ): PostgrestFilterBuilder { + ): PostgrestFilterBuilder { const method = 'PATCH' const prefersHeaders = [] const body = values @@ -274,7 +272,7 @@ export default class PostgrestQueryBuilder< body, fetch: this.fetch, allowEmpty: false, - } as unknown as PostgrestBuilder) + } as unknown as PostgrestBuilder) } /** @@ -300,7 +298,7 @@ export default class PostgrestQueryBuilder< count, }: { count?: 'exact' | 'planned' | 'estimated' - } = {}): PostgrestFilterBuilder { + } = {}): PostgrestFilterBuilder { const method = 'DELETE' const prefersHeaders = [] if (count) { @@ -318,6 +316,6 @@ export default class PostgrestQueryBuilder< schema: this.schema, fetch: this.fetch, allowEmpty: false, - } as unknown as PostgrestBuilder) + } as unknown as PostgrestBuilder) } } diff --git a/src/PostgrestTransformBuilder.ts b/src/PostgrestTransformBuilder.ts index c4f97812..455a3775 100644 --- a/src/PostgrestTransformBuilder.ts +++ b/src/PostgrestTransformBuilder.ts @@ -10,8 +10,9 @@ import { export default class PostgrestTransformBuilder< Schema extends GenericSchema, Row extends Record, - Result -> extends PostgrestBuilder { + Result, + ThrowOnError +> extends PostgrestBuilder { /** * Perform a SELECT on the query result. * @@ -23,7 +24,7 @@ export default class PostgrestTransformBuilder< */ select>( columns?: Query - ): PostgrestTransformBuilder { + ): PostgrestTransformBuilder { // Remove whitespaces except when quoted let quoted = false const cleanedColumns = (columns ?? '*') @@ -43,7 +44,7 @@ export default class PostgrestTransformBuilder< this.headers['Prefer'] += ',' } this.headers['Prefer'] += 'return=representation' - return this as unknown as PostgrestTransformBuilder + return this as unknown as PostgrestTransformBuilder } /** @@ -207,7 +208,7 @@ export default class PostgrestTransformBuilder< wal?: boolean format?: 'json' | 'text' } = {}): - | PromiseLike>> + | PromiseLike, ThrowOnError>> | PromiseLike> { const options = [ analyze ? 'analyze' : null, @@ -223,7 +224,8 @@ export default class PostgrestTransformBuilder< this.headers[ 'Accept' ] = `application/vnd.pgrst.plan+${format}; for="${forMediatype}"; options=${options};` - if (format === 'json') return this as PromiseLike>> + if (format === 'json') + return this as PromiseLike, ThrowOnError>> else return this as PromiseLike> } @@ -246,7 +248,7 @@ export default class PostgrestTransformBuilder< * * @typeParam NewResult - The new result type to override with */ - returns(): PostgrestTransformBuilder { - return this as unknown as PostgrestTransformBuilder + returns(): PostgrestTransformBuilder { + return this as unknown as PostgrestTransformBuilder } } diff --git a/src/types.ts b/src/types.ts index 07cc656f..c8061b0f 100644 --- a/src/types.ts +++ b/src/types.ts @@ -32,7 +32,9 @@ interface PostgrestResponseFailure extends PostgrestResponseBase { data: null count: null } -export type PostgrestResponse = PostgrestResponseSuccess | PostgrestResponseFailure +export type PostgrestResponse = + | PostgrestResponseSuccess + | PostgrestResponseFailure interface PostgrestSingleResponseSuccess extends PostgrestResponseBase { error: null diff --git a/test/transforms.ts b/test/transforms.ts index 98b310cc..dc4bd953 100644 --- a/test/transforms.ts +++ b/test/transforms.ts @@ -30,7 +30,7 @@ test('range', async () => { }) test('single', async () => { - const res = await postgrest.from('users').select().limit(1).single() + const res = await postgrest.from('users').select().limit(1).throwOnError().single() expect(res).toMatchSnapshot() }) From c01956501010b513e5835c70248651e89a5d4ce6 Mon Sep 17 00:00:00 2001 From: Zhongliang Wang Date: Tue, 8 Nov 2022 11:33:20 +0800 Subject: [PATCH 2/5] Rearrange --- src/PostgrestBuilder.ts | 23 +++++++++++++---------- src/PostgrestTransformBuilder.ts | 18 ++++++++++-------- src/types.ts | 8 ++++---- 3 files changed, 27 insertions(+), 22 deletions(-) diff --git a/src/PostgrestBuilder.ts b/src/PostgrestBuilder.ts index bbbdcd47..37f272e3 100644 --- a/src/PostgrestBuilder.ts +++ b/src/PostgrestBuilder.ts @@ -1,19 +1,22 @@ import crossFetch from 'cross-fetch' -import PostgrestFilterBuilder from './PostgrestFilterBuilder' -import PostgrestQueryBuilder from './PostgrestQueryBuilder' -import PostgrestTransformBuilder from './PostgrestTransformBuilder' +import type PostgrestFilterBuilder from './PostgrestFilterBuilder' +import type PostgrestQueryBuilder from './PostgrestQueryBuilder' +import type PostgrestTransformBuilder from './PostgrestTransformBuilder' import type { Fetch, PostgrestResponse } from './types' -type EnableThrowOnError = T extends PostgrestBuilder - ? PostgrestBuilder - : T extends PostgrestFilterBuilder +type EnableThrowOnError = T extends PostgrestFilterBuilder< + infer Schema, + infer Row, + infer Result, + any +> ? PostgrestFilterBuilder + : T extends PostgrestTransformBuilder + ? PostgrestTransformBuilder : T extends PostgrestQueryBuilder ? PostgrestQueryBuilder - : T extends PostgrestTransformBuilder - ? PostgrestTransformBuilder - : T + : any export default abstract class PostgrestBuilder implements PromiseLike> @@ -55,7 +58,7 @@ export default abstract class PostgrestBuilder */ throwOnError(): EnableThrowOnError { this.shouldThrowOnError = true - return this as EnableThrowOnError + return this as any } then, TResult2 = never>( diff --git a/src/PostgrestTransformBuilder.ts b/src/PostgrestTransformBuilder.ts index 455a3775..30d74d02 100644 --- a/src/PostgrestTransformBuilder.ts +++ b/src/PostgrestTransformBuilder.ts @@ -139,9 +139,9 @@ export default class PostgrestTransformBuilder< * Query result must be one row (e.g. using `.limit(1)`), otherwise this * returns an error. */ - single(): PromiseLike> { + single(): PromiseLike> { this.headers['Accept'] = 'application/vnd.pgrst.object+json' - return this as PromiseLike> + return this as unknown as PromiseLike> } /** @@ -159,17 +159,19 @@ export default class PostgrestTransformBuilder< /** * Return `data` as a string in CSV format. */ - csv(): PromiseLike> { + csv(): PromiseLike> { this.headers['Accept'] = 'text/csv' - return this as PromiseLike> + return this as unknown as PromiseLike> } /** * Return `data` as an object in [GeoJSON](https://geojson.org) format. */ - geojson(): PromiseLike>> { + geojson(): PromiseLike, ThrowOnError>> { this.headers['Accept'] = 'application/geo+json' - return this as PromiseLike>> + return this as unknown as PromiseLike< + PostgrestSingleResponse, ThrowOnError> + > } /** @@ -209,7 +211,7 @@ export default class PostgrestTransformBuilder< format?: 'json' | 'text' } = {}): | PromiseLike, ThrowOnError>> - | PromiseLike> { + | PromiseLike> { const options = [ analyze ? 'analyze' : null, verbose ? 'verbose' : null, @@ -226,7 +228,7 @@ export default class PostgrestTransformBuilder< ] = `application/vnd.pgrst.plan+${format}; for="${forMediatype}"; options=${options};` if (format === 'json') return this as PromiseLike, ThrowOnError>> - else return this as PromiseLike> + else return this as unknown as PromiseLike> } /** diff --git a/src/types.ts b/src/types.ts index c8061b0f..68f46c31 100644 --- a/src/types.ts +++ b/src/types.ts @@ -41,10 +41,10 @@ interface PostgrestSingleResponseSuccess extends PostgrestResponseBase { data: T count: number | null } -export type PostgrestSingleResponse = - | PostgrestSingleResponseSuccess - | PostgrestResponseFailure -export type PostgrestMaybeSingleResponse = PostgrestSingleResponse +export type PostgrestSingleResponse = ThrowOnError extends true + ? PostgrestSingleResponseSuccess + : PostgrestSingleResponseSuccess | PostgrestResponseFailure +export type PostgrestMaybeSingleResponse = PostgrestSingleResponse export type GenericTable = { Row: Record From e74d12ed26883faa3339a25c60b11760fd6a5180 Mon Sep 17 00:00:00 2001 From: Zhongliang Wang Date: Tue, 8 Nov 2022 12:39:05 +0800 Subject: [PATCH 3/5] Update PostgrestResponse --- src/types.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/types.ts b/src/types.ts index 68f46c31..8265c71f 100644 --- a/src/types.ts +++ b/src/types.ts @@ -32,9 +32,9 @@ interface PostgrestResponseFailure extends PostgrestResponseBase { data: null count: null } -export type PostgrestResponse = - | PostgrestResponseSuccess - | PostgrestResponseFailure +export type PostgrestResponse = ThrowOnError extends true + ? PostgrestResponseSuccess + : PostgrestResponseSuccess | PostgrestResponseFailure interface PostgrestSingleResponseSuccess extends PostgrestResponseBase { error: null From 8187252a06d9b8f0adcb0f61ec7412e08a4b5487 Mon Sep 17 00:00:00 2001 From: Zhongliang Wang Date: Tue, 8 Nov 2022 12:44:57 +0800 Subject: [PATCH 4/5] revert the accidental change in transform.ts --- test/transforms.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/transforms.ts b/test/transforms.ts index dc4bd953..98b310cc 100644 --- a/test/transforms.ts +++ b/test/transforms.ts @@ -30,7 +30,7 @@ test('range', async () => { }) test('single', async () => { - const res = await postgrest.from('users').select().limit(1).throwOnError().single() + const res = await postgrest.from('users').select().limit(1).single() expect(res).toMatchSnapshot() }) From 10d710f28799ca62e82094fe757e87ab0bb34be6 Mon Sep 17 00:00:00 2001 From: Zhongliang Wang Date: Tue, 8 Nov 2022 12:48:02 +0800 Subject: [PATCH 5/5] PostgrestMaybeSingleResponse respects ThrowOnError --- src/PostgrestTransformBuilder.ts | 4 ++-- src/types.ts | 5 ++++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/PostgrestTransformBuilder.ts b/src/PostgrestTransformBuilder.ts index 30d74d02..78685879 100644 --- a/src/PostgrestTransformBuilder.ts +++ b/src/PostgrestTransformBuilder.ts @@ -150,10 +150,10 @@ export default class PostgrestTransformBuilder< * Query result must be zero or one row (e.g. using `.limit(1)`), otherwise * this returns an error. */ - maybeSingle(): PromiseLike> { + maybeSingle(): PromiseLike> { this.headers['Accept'] = 'application/vnd.pgrst.object+json' this.allowEmpty = true - return this as PromiseLike> + return this as unknown as PromiseLike> } /** diff --git a/src/types.ts b/src/types.ts index 8265c71f..0277a6ff 100644 --- a/src/types.ts +++ b/src/types.ts @@ -44,7 +44,10 @@ interface PostgrestSingleResponseSuccess extends PostgrestResponseBase { export type PostgrestSingleResponse = ThrowOnError extends true ? PostgrestSingleResponseSuccess : PostgrestSingleResponseSuccess | PostgrestResponseFailure -export type PostgrestMaybeSingleResponse = PostgrestSingleResponse +export type PostgrestMaybeSingleResponse = PostgrestSingleResponse< + T | null, + ThrowOnError +> export type GenericTable = { Row: Record