diff --git a/src/PostgrestClient.ts b/src/PostgrestClient.ts index 5cd9be7d..38652acb 100644 --- a/src/PostgrestClient.ts +++ b/src/PostgrestClient.ts @@ -1,6 +1,5 @@ import PostgrestQueryBuilder from './lib/PostgrestQueryBuilder' import PostgrestTransformBuilder from './lib/PostgrestTransformBuilder' -import { PostgrestBuilder } from './lib/types' export default class PostgrestClient { url: string diff --git a/src/lib/PostgrestQueryBuilder.ts b/src/lib/PostgrestQueryBuilder.ts index 47b76b42..43a601d1 100644 --- a/src/lib/PostgrestQueryBuilder.ts +++ b/src/lib/PostgrestQueryBuilder.ts @@ -17,8 +17,19 @@ export default class PostgrestQueryBuilder extends PostgrestBuilder { * Performs vertical filtering with SELECT. * * @param columns The columns to retrieve, separated by commas. + * @param head When set to true, select will void data. + * @param count Count algorithm to use to count rows in a table. */ - select(columns = '*'): PostgrestFilterBuilder { + select( + columns = '*', + { + head = false, + count = null, + }: { + head?: boolean + count?: null | 'exact' | 'planned' | 'estimated' + } = {} + ): PostgrestFilterBuilder { this.method = 'GET' // Remove whitespaces except when quoted let quoted = false @@ -35,6 +46,12 @@ export default class PostgrestQueryBuilder extends PostgrestBuilder { }) .join('') this.url.searchParams.set('select', cleanedColumns) + if (count) { + this.headers['Prefer'] = `count=${count}` + } + if (head) { + this.method = 'HEAD' + } return new PostgrestFilterBuilder(this) } @@ -52,10 +69,12 @@ export default class PostgrestQueryBuilder extends PostgrestBuilder { upsert = false, onConflict, returning = 'representation', + count = null, }: { upsert?: boolean onConflict?: string returning?: 'minimal' | 'representation' + count?: null | 'exact' | 'planned' | 'estimated' } = {} ): PostgrestFilterBuilder { this.method = 'POST' @@ -67,6 +86,10 @@ export default class PostgrestQueryBuilder extends PostgrestBuilder { if (upsert && onConflict !== undefined) this.url.searchParams.set('on_conflict', onConflict) this.body = values + if (count) { + prefersHeaders.push(`count=${count}`) + this.headers['Prefer'] = prefersHeaders.join(',') + } return new PostgrestFilterBuilder(this) } @@ -78,11 +101,23 @@ export default class PostgrestQueryBuilder extends PostgrestBuilder { */ update( values: Partial, - { returning = 'representation' }: { returning?: 'minimal' | 'representation' } = {} + { + returning = 'representation', + count = null, + }: { + returning?: 'minimal' | 'representation' + count?: null | 'exact' | 'planned' | 'estimated' + } = {} ): PostgrestFilterBuilder { this.method = 'PATCH' - this.headers['Prefer'] = `return=${returning}` + let prefersHeaders = [] + prefersHeaders.push(`return=${returning}`) + this.headers['Prefer'] = prefersHeaders.join(',') this.body = values + if (count) { + prefersHeaders.push(`count=${count}`) + this.headers['Prefer'] = prefersHeaders.join(',') + } return new PostgrestFilterBuilder(this) } @@ -93,16 +128,41 @@ export default class PostgrestQueryBuilder extends PostgrestBuilder { */ delete({ returning = 'representation', - }: { returning?: 'minimal' | 'representation' } = {}): PostgrestFilterBuilder { + count = null, + }: { + returning?: 'minimal' | 'representation' + count?: null | 'exact' | 'planned' | 'estimated' + } = {}): PostgrestFilterBuilder { this.method = 'DELETE' - this.headers['Prefer'] = `return=${returning}` + let prefersHeaders = [] + prefersHeaders.push(`return=${returning}`) + this.headers['Prefer'] = prefersHeaders.join(',') + if (count) { + prefersHeaders.push(`count=${count}`) + this.headers['Prefer'] = prefersHeaders.join(',') + } return new PostgrestFilterBuilder(this) } /** @internal */ - rpc(params?: object): PostgrestTransformBuilder { + rpc( + params?: object, + { + head = false, + count = null, + }: { + head?: boolean + count?: null | 'exact' | 'planned' | 'estimated' + } = {} + ): PostgrestTransformBuilder { this.method = 'POST' this.body = params + if (count) { + this.headers['Prefer'] = `count=${count}` + } + if (head) { + this.method = 'HEAD' + } return new PostgrestTransformBuilder(this) } } diff --git a/src/lib/PostgrestTransformBuilder.ts b/src/lib/PostgrestTransformBuilder.ts index 80d874ba..a94c93c8 100644 --- a/src/lib/PostgrestTransformBuilder.ts +++ b/src/lib/PostgrestTransformBuilder.ts @@ -59,10 +59,7 @@ export default class PostgrestTransformBuilder extends PostgrestBuilder { * @param count The maximum no. of rows to limit to. * @param foreignTable The foreign table to use (for foreign columns). */ - limit( - count: number, - { foreignTable }: { foreignTable?: string } = {} - ): this { + limit(count: number, { foreignTable }: { foreignTable?: string } = {}): this { const key = typeof foreignTable === 'undefined' ? 'limit' : `"${foreignTable}".limit` this.url.searchParams.set(key, `${count}`) return this @@ -75,11 +72,7 @@ export default class PostgrestTransformBuilder extends PostgrestBuilder { * @param to The last index to which to limit the result, inclusive. * @param foreignTable The foreign table to use (for foreign columns). */ - range( - from: number, - to: number, - { foreignTable }: { foreignTable?: string } = {} - ): this { + range(from: number, to: number, { foreignTable }: { foreignTable?: string } = {}): this { const keyOffset = typeof foreignTable === 'undefined' ? 'offset' : `"${foreignTable}".offset` const keyLimit = typeof foreignTable === 'undefined' ? 'limit' : `"${foreignTable}".limit` this.url.searchParams.set(keyOffset, `${from}`) diff --git a/src/lib/types.ts b/src/lib/types.ts index 9d6c363f..53f6782f 100644 --- a/src/lib/types.ts +++ b/src/lib/types.ts @@ -26,12 +26,14 @@ interface PostgrestResponseSuccess extends PostgrestResponseBase { error: null data: T[] body: T[] + count: number | null } interface PostgrestResponseFailure extends PostgrestResponseBase { error: PostgrestError data: null // For backward compatibility: body === data body: null + count: null } export type PostgrestResponse = PostgrestResponseSuccess | PostgrestResponseFailure @@ -80,18 +82,36 @@ export abstract class PostgrestBuilder implements PromiseLike { - let error, data + let error, data, count if (res.ok) { error = null - const isReturnMinimal = this.headers['Prefer']?.split(',').includes('return=minimal') - data = isReturnMinimal ? null : await res.json() + if (this.method !== 'HEAD') { + const isReturnMinimal = this.headers['Prefer']?.split(',').includes('return=minimal') + data = isReturnMinimal ? null : await res.json() + } else { + data = null + } + + const countHeader = this.headers['Prefer']?.match(/count=(exact|planned|estimated)/) + if (countHeader) { + const contentRange = res.headers.get('content-range')?.split('/') + if (contentRange && contentRange.length > 1) { + count = parseInt(contentRange[1]) + } else { + count = null + } + } else { + count = null + } } else { error = await res.json() data = null + count = null } const postgrestResponse: PostgrestResponse = { error, data, + count, status: res.status, statusText: res.statusText, body: data, diff --git a/test/__snapshots__/index.test.ts.snap b/test/__snapshots__/index.test.ts.snap index e00e329e..15c9c5b8 100644 --- a/test/__snapshots__/index.test.ts.snap +++ b/test/__snapshots__/index.test.ts.snap @@ -27,6 +27,7 @@ Object { "username": "awailas", }, ], + "count": null, "data": Array [ Object { "age_range": "[1,2)", @@ -114,6 +115,7 @@ Object { "username": "supabot", }, ], + "count": null, "data": Array [ Object { "channel_id": 2, @@ -161,6 +163,7 @@ Object { "username": "supabot", }, ], + "count": null, "data": Array [ Object { "channel_id": 1, @@ -194,6 +197,7 @@ Object { "username": "supabot", }, ], + "count": null, "data": Array [ Object { "channel_id": 1, @@ -234,6 +238,7 @@ Object { "username": "supabot", }, ], + "count": null, "data": Array [ Object { "channel_id": 1, @@ -288,6 +293,7 @@ Object { "username": "supabot", }, ], + "count": null, "data": Array [ Object { "channel_id": 2, @@ -356,6 +362,7 @@ Object { "username": "supabot", }, ], + "count": null, "data": Array [ Object { "channel_id": 1, @@ -417,6 +424,7 @@ Object { "username": "supabot", }, ], + "count": null, "data": Array [ Object { "channel_id": 1, @@ -478,6 +486,7 @@ Object { "username": "supabot", }, ], + "count": null, "data": Array [ Object { "channel_id": 1, @@ -532,6 +541,7 @@ Object { "username": "supabot", }, ], + "count": null, "data": Array [ Object { "channel_id": 2, @@ -572,6 +582,7 @@ Object { "username": "supabot", }, ], + "count": null, "data": Array [ Object { "channel_id": 1, @@ -633,6 +644,7 @@ Object { "username": "dragarcia", }, ], + "count": null, "data": Array [ Object { "age_range": "[1,2)", @@ -680,6 +692,7 @@ Object { "username": "supabot", }, ], + "count": null, "data": Array [ Object { "age_range": "[1,2)", @@ -706,6 +719,7 @@ Object { "username": "supabot", }, ], + "count": null, "data": Array [ Object { "age_range": "[1,2)", @@ -747,6 +761,7 @@ Object { "messages": Array [], }, ], + "count": null, "data": Array [ Object { "messages": Array [ @@ -806,6 +821,7 @@ Object { "messages": Array [], }, ], + "count": null, "data": Array [ Object { "messages": Array [ @@ -865,6 +881,7 @@ Object { "messages": Array [], }, ], + "count": null, "data": Array [ Object { "messages": Array [ @@ -924,6 +941,7 @@ Object { "messages": Array [], }, ], + "count": null, "data": Array [ Object { "messages": Array [ @@ -983,6 +1001,7 @@ Object { "messages": Array [], }, ], + "count": null, "data": Array [ Object { "messages": Array [ @@ -1022,6 +1041,7 @@ Object { "username": "supabot", }, ], + "count": null, "data": Array [ Object { "age_range": "[1,2)", @@ -1048,6 +1068,7 @@ Object { "username": "supabot", }, ], + "count": null, "data": Array [ Object { "age_range": "[1,2)", @@ -1074,6 +1095,7 @@ Object { "username": "supabot", }, ], + "count": null, "data": Array [ Object { "age_range": "[1,2)", @@ -1100,6 +1122,7 @@ Object { "username": "supabot", }, ], + "count": null, "data": Array [ Object { "channel_id": 2, @@ -1133,6 +1156,7 @@ Object { "username": "supabot", }, ], + "count": null, "data": Array [ Object { "channel_id": 1, @@ -1166,6 +1190,7 @@ Object { "username": "supabot", }, ], + "count": null, "data": Array [ Object { "age_range": "[1,2)", @@ -1213,6 +1238,7 @@ Object { "username": "dragarcia", }, ], + "count": null, "data": Array [ Object { "age_range": "[1,2)", @@ -1249,6 +1275,598 @@ Object { } `; +exports[`insert, update, delete with count: 'exact' basic delete count: 'exact' 1`] = ` +Object { + "body": Array [ + Object { + "channel_id": 2, + "data": null, + "id": 6, + "message": "foo", + "username": "supabot", + }, + Object { + "channel_id": 2, + "data": null, + "id": 3, + "message": "foo", + "username": "supabot", + }, + Object { + "channel_id": 2, + "data": null, + "id": 7, + "message": "foo", + "username": "supabot", + }, + Object { + "channel_id": 2, + "data": null, + "id": 8, + "message": "foo", + "username": "supabot", + }, + ], + "count": 4, + "data": Array [ + Object { + "channel_id": 2, + "data": null, + "id": 6, + "message": "foo", + "username": "supabot", + }, + Object { + "channel_id": 2, + "data": null, + "id": 3, + "message": "foo", + "username": "supabot", + }, + Object { + "channel_id": 2, + "data": null, + "id": 7, + "message": "foo", + "username": "supabot", + }, + Object { + "channel_id": 2, + "data": null, + "id": 8, + "message": "foo", + "username": "supabot", + }, + ], + "error": null, + "status": 200, + "statusText": "OK", +} +`; + +exports[`insert, update, delete with count: 'exact' basic delete count: 'exact' 2`] = ` +Object { + "body": Array [ + Object { + "channel_id": 1, + "data": null, + "id": 1, + "message": "Hello World 👋", + "username": "supabot", + }, + Object { + "channel_id": 2, + "data": null, + "id": 2, + "message": "Perfection is attained, not when there is nothing more to add, but when there is nothing left to take away.", + "username": "supabot", + }, + ], + "count": null, + "data": Array [ + Object { + "channel_id": 1, + "data": null, + "id": 1, + "message": "Hello World 👋", + "username": "supabot", + }, + Object { + "channel_id": 2, + "data": null, + "id": 2, + "message": "Perfection is attained, not when there is nothing more to add, but when there is nothing left to take away.", + "username": "supabot", + }, + ], + "error": null, + "status": 200, + "statusText": "OK", +} +`; + +exports[`insert, update, delete with count: 'exact' bulk insert with count: 'exact' 1`] = ` +Object { + "body": Array [ + Object { + "channel_id": 1, + "data": null, + "id": 7, + "message": "foo", + "username": "supabot", + }, + Object { + "channel_id": 1, + "data": null, + "id": 8, + "message": "foo", + "username": "supabot", + }, + ], + "count": 2, + "data": Array [ + Object { + "channel_id": 1, + "data": null, + "id": 7, + "message": "foo", + "username": "supabot", + }, + Object { + "channel_id": 1, + "data": null, + "id": 8, + "message": "foo", + "username": "supabot", + }, + ], + "error": null, + "status": 201, + "statusText": "Created", +} +`; + +exports[`insert, update, delete with count: 'exact' bulk insert with count: 'exact' 2`] = ` +Object { + "body": Array [ + Object { + "channel_id": 1, + "data": null, + "id": 1, + "message": "Hello World 👋", + "username": "supabot", + }, + Object { + "channel_id": 2, + "data": null, + "id": 2, + "message": "Perfection is attained, not when there is nothing more to add, but when there is nothing left to take away.", + "username": "supabot", + }, + Object { + "channel_id": 1, + "data": null, + "id": 6, + "message": "foo", + "username": "supabot", + }, + Object { + "channel_id": 2, + "data": null, + "id": 3, + "message": "foo", + "username": "supabot", + }, + Object { + "channel_id": 1, + "data": null, + "id": 7, + "message": "foo", + "username": "supabot", + }, + Object { + "channel_id": 1, + "data": null, + "id": 8, + "message": "foo", + "username": "supabot", + }, + ], + "count": null, + "data": Array [ + Object { + "channel_id": 1, + "data": null, + "id": 1, + "message": "Hello World 👋", + "username": "supabot", + }, + Object { + "channel_id": 2, + "data": null, + "id": 2, + "message": "Perfection is attained, not when there is nothing more to add, but when there is nothing left to take away.", + "username": "supabot", + }, + Object { + "channel_id": 1, + "data": null, + "id": 6, + "message": "foo", + "username": "supabot", + }, + Object { + "channel_id": 2, + "data": null, + "id": 3, + "message": "foo", + "username": "supabot", + }, + Object { + "channel_id": 1, + "data": null, + "id": 7, + "message": "foo", + "username": "supabot", + }, + Object { + "channel_id": 1, + "data": null, + "id": 8, + "message": "foo", + "username": "supabot", + }, + ], + "error": null, + "status": 200, + "statusText": "OK", +} +`; + +exports[`insert, update, delete with count: 'exact' insert with count: 'exact' 1`] = ` +Object { + "body": Array [ + Object { + "channel_id": 1, + "data": null, + "id": 6, + "message": "foo", + "username": "supabot", + }, + ], + "count": 1, + "data": Array [ + Object { + "channel_id": 1, + "data": null, + "id": 6, + "message": "foo", + "username": "supabot", + }, + ], + "error": null, + "status": 201, + "statusText": "Created", +} +`; + +exports[`insert, update, delete with count: 'exact' insert with count: 'exact' 2`] = ` +Object { + "body": Array [ + Object { + "channel_id": 1, + "data": null, + "id": 1, + "message": "Hello World 👋", + "username": "supabot", + }, + Object { + "channel_id": 2, + "data": null, + "id": 2, + "message": "Perfection is attained, not when there is nothing more to add, but when there is nothing left to take away.", + "username": "supabot", + }, + Object { + "channel_id": 1, + "data": null, + "id": 6, + "message": "foo", + "username": "supabot", + }, + ], + "count": null, + "data": Array [ + Object { + "channel_id": 1, + "data": null, + "id": 1, + "message": "Hello World 👋", + "username": "supabot", + }, + Object { + "channel_id": 2, + "data": null, + "id": 2, + "message": "Perfection is attained, not when there is nothing more to add, but when there is nothing left to take away.", + "username": "supabot", + }, + Object { + "channel_id": 1, + "data": null, + "id": 6, + "message": "foo", + "username": "supabot", + }, + ], + "error": null, + "status": 200, + "statusText": "OK", +} +`; + +exports[`insert, update, delete with count: 'exact' update with count: 'exact' 1`] = ` +Object { + "body": Array [ + Object { + "channel_id": 2, + "data": null, + "id": 6, + "message": "foo", + "username": "supabot", + }, + Object { + "channel_id": 2, + "data": null, + "id": 3, + "message": "foo", + "username": "supabot", + }, + Object { + "channel_id": 2, + "data": null, + "id": 7, + "message": "foo", + "username": "supabot", + }, + Object { + "channel_id": 2, + "data": null, + "id": 8, + "message": "foo", + "username": "supabot", + }, + ], + "count": 4, + "data": Array [ + Object { + "channel_id": 2, + "data": null, + "id": 6, + "message": "foo", + "username": "supabot", + }, + Object { + "channel_id": 2, + "data": null, + "id": 3, + "message": "foo", + "username": "supabot", + }, + Object { + "channel_id": 2, + "data": null, + "id": 7, + "message": "foo", + "username": "supabot", + }, + Object { + "channel_id": 2, + "data": null, + "id": 8, + "message": "foo", + "username": "supabot", + }, + ], + "error": null, + "status": 200, + "statusText": "OK", +} +`; + +exports[`insert, update, delete with count: 'exact' update with count: 'exact' 2`] = ` +Object { + "body": Array [ + Object { + "channel_id": 1, + "data": null, + "id": 1, + "message": "Hello World 👋", + "username": "supabot", + }, + Object { + "channel_id": 2, + "data": null, + "id": 2, + "message": "Perfection is attained, not when there is nothing more to add, but when there is nothing left to take away.", + "username": "supabot", + }, + Object { + "channel_id": 2, + "data": null, + "id": 6, + "message": "foo", + "username": "supabot", + }, + Object { + "channel_id": 2, + "data": null, + "id": 3, + "message": "foo", + "username": "supabot", + }, + Object { + "channel_id": 2, + "data": null, + "id": 7, + "message": "foo", + "username": "supabot", + }, + Object { + "channel_id": 2, + "data": null, + "id": 8, + "message": "foo", + "username": "supabot", + }, + ], + "count": null, + "data": Array [ + Object { + "channel_id": 1, + "data": null, + "id": 1, + "message": "Hello World 👋", + "username": "supabot", + }, + Object { + "channel_id": 2, + "data": null, + "id": 2, + "message": "Perfection is attained, not when there is nothing more to add, but when there is nothing left to take away.", + "username": "supabot", + }, + Object { + "channel_id": 2, + "data": null, + "id": 6, + "message": "foo", + "username": "supabot", + }, + Object { + "channel_id": 2, + "data": null, + "id": 3, + "message": "foo", + "username": "supabot", + }, + Object { + "channel_id": 2, + "data": null, + "id": 7, + "message": "foo", + "username": "supabot", + }, + Object { + "channel_id": 2, + "data": null, + "id": 8, + "message": "foo", + "username": "supabot", + }, + ], + "error": null, + "status": 200, + "statusText": "OK", +} +`; + +exports[`insert, update, delete with count: 'exact' upsert with count: 'exact' 1`] = ` +Object { + "body": Array [ + Object { + "channel_id": 2, + "data": null, + "id": 3, + "message": "foo", + "username": "supabot", + }, + ], + "count": 1, + "data": Array [ + Object { + "channel_id": 2, + "data": null, + "id": 3, + "message": "foo", + "username": "supabot", + }, + ], + "error": null, + "status": 201, + "statusText": "Created", +} +`; + +exports[`insert, update, delete with count: 'exact' upsert with count: 'exact' 2`] = ` +Object { + "body": Array [ + Object { + "channel_id": 1, + "data": null, + "id": 1, + "message": "Hello World 👋", + "username": "supabot", + }, + Object { + "channel_id": 2, + "data": null, + "id": 2, + "message": "Perfection is attained, not when there is nothing more to add, but when there is nothing left to take away.", + "username": "supabot", + }, + Object { + "channel_id": 1, + "data": null, + "id": 6, + "message": "foo", + "username": "supabot", + }, + Object { + "channel_id": 2, + "data": null, + "id": 3, + "message": "foo", + "username": "supabot", + }, + ], + "count": null, + "data": Array [ + Object { + "channel_id": 1, + "data": null, + "id": 1, + "message": "Hello World 👋", + "username": "supabot", + }, + Object { + "channel_id": 2, + "data": null, + "id": 2, + "message": "Perfection is attained, not when there is nothing more to add, but when there is nothing left to take away.", + "username": "supabot", + }, + Object { + "channel_id": 1, + "data": null, + "id": 6, + "message": "foo", + "username": "supabot", + }, + Object { + "channel_id": 2, + "data": null, + "id": 3, + "message": "foo", + "username": "supabot", + }, + ], + "error": null, + "status": 200, + "statusText": "OK", +} +`; + exports[`is 1`] = ` Object { "body": Array [ @@ -1281,6 +1899,7 @@ Object { "username": "dragarcia", }, ], + "count": null, "data": Array [ Object { "age_range": "[1,2)", @@ -1328,6 +1947,7 @@ Object { "username": "supabot", }, ], + "count": null, "data": Array [ Object { "age_range": "[1,2)", @@ -1354,6 +1974,7 @@ Object { "username": "supabot", }, ], + "count": null, "data": Array [ Object { "age_range": "[1,2)", @@ -1380,6 +2001,7 @@ Object { "username": "supabot", }, ], + "count": null, "data": Array [ Object { "channel_id": 1, @@ -1413,6 +2035,7 @@ Object { "username": "supabot", }, ], + "count": null, "data": Array [ Object { "channel_id": 1, @@ -1446,6 +2069,7 @@ Object { "username": "supabot", }, ], + "count": null, "data": Array [ Object { "age_range": "[1,2)", @@ -1464,6 +2088,7 @@ Object { exports[`missing table 1`] = ` Object { "body": null, + "count": null, "data": null, "error": Object { "code": "42P01", @@ -1487,6 +2112,7 @@ Object { "username": "supabot", }, ], + "count": null, "data": Array [ Object { "age_range": "[1,2)", @@ -1527,6 +2153,7 @@ Object { "username": "dragarcia", }, ], + "count": null, "data": Array [ Object { "age_range": "[25,35)", @@ -1581,6 +2208,7 @@ Object { "username": "dragarcia", }, ], + "count": null, "data": Array [ Object { "age_range": "[1,2)", @@ -1635,6 +2263,7 @@ Object { "username": "dragarcia", }, ], + "count": null, "data": Array [ Object { "age_range": "[25,35)", @@ -1675,6 +2304,7 @@ Object { "username": "supabot", }, ], + "count": null, "data": Array [ Object { "age_range": "[1,2)", @@ -1701,6 +2331,7 @@ Object { "username": "dragarcia", }, ], + "count": null, "data": Array [ Object { "age_range": "[20,30)", @@ -1734,6 +2365,7 @@ Object { "username": "kiwicopple", }, ], + "count": null, "data": Array [ Object { "age_range": "[1,2)", @@ -1788,6 +2420,7 @@ Object { "username": "awailas", }, ], + "count": null, "data": Array [ Object { "age_range": "[1,2)", @@ -1835,6 +2468,7 @@ Object { "username": "dragarcia", }, ], + "count": null, "data": Array [ Object { "age_range": "[20,30)", @@ -1868,6 +2502,7 @@ Object { "username": "kiwicopple", }, ], + "count": null, "data": Array [ Object { "age_range": "[1,2)", @@ -1901,6 +2536,7 @@ Object { "username": "supabot", }, ], + "count": null, "data": Array [ Object { "age_range": "[1,2)", @@ -1941,6 +2577,7 @@ Object { "username": "dragarcia", }, ], + "count": null, "data": Array [ Object { "age_range": "[25,35)", @@ -1977,6 +2614,7 @@ Object { "status": "ONLINE", }, ], + "count": null, "data": Array [ Object { "status": "ONLINE", @@ -1995,9 +2633,79 @@ Object { "status": "ONLINE", }, ], + "count": null, + "data": Array [ + Object { + "status": "ONLINE", + }, + ], + "error": null, + "status": 200, + "statusText": "OK", +} +`; + +exports[`select with count:exact 1`] = ` +Object { + "body": Array [ + Object { + "age_range": "[1,2)", + "catchphrase": "'cat' 'fat'", + "data": null, + "status": "ONLINE", + "username": "supabot", + }, + Object { + "age_range": "[25,35)", + "catchphrase": "'bat' 'cat'", + "data": null, + "status": "OFFLINE", + "username": "kiwicopple", + }, + Object { + "age_range": "[25,35)", + "catchphrase": "'bat' 'rat'", + "data": null, + "status": "ONLINE", + "username": "awailas", + }, + Object { + "age_range": "[20,30)", + "catchphrase": "'fat' 'rat'", + "data": null, + "status": "ONLINE", + "username": "dragarcia", + }, + ], + "count": 4, "data": Array [ Object { + "age_range": "[1,2)", + "catchphrase": "'cat' 'fat'", + "data": null, + "status": "ONLINE", + "username": "supabot", + }, + Object { + "age_range": "[25,35)", + "catchphrase": "'bat' 'cat'", + "data": null, + "status": "OFFLINE", + "username": "kiwicopple", + }, + Object { + "age_range": "[25,35)", + "catchphrase": "'bat' 'rat'", + "data": null, + "status": "ONLINE", + "username": "awailas", + }, + Object { + "age_range": "[20,30)", + "catchphrase": "'fat' 'rat'", + "data": null, "status": "ONLINE", + "username": "dragarcia", }, ], "error": null, @@ -2006,6 +2714,50 @@ Object { } `; +exports[`select with head:true 1`] = ` +Object { + "body": null, + "count": null, + "data": null, + "error": null, + "status": 200, + "statusText": "OK", +} +`; + +exports[`select with head:true, count:estimated 1`] = ` +Object { + "body": null, + "count": Any, + "data": null, + "error": null, + "status": 206, + "statusText": "Partial Content", +} +`; + +exports[`select with head:true, count:exact 1`] = ` +Object { + "body": null, + "count": 4, + "data": null, + "error": null, + "status": 200, + "statusText": "OK", +} +`; + +exports[`select with head:true, count:planned 1`] = ` +Object { + "body": null, + "count": Any, + "data": null, + "error": null, + "status": 206, + "statusText": "Partial Content", +} +`; + exports[`single 1`] = ` Object { "body": Object { @@ -2015,6 +2767,7 @@ Object { "status": "ONLINE", "username": "supabot", }, + "count": null, "data": Object { "age_range": "[1,2)", "catchphrase": "'cat' 'fat'", @@ -2037,6 +2790,7 @@ Object { "status": "ONLINE", "username": "foo", }, + "count": null, "data": Object { "age_range": null, "catchphrase": null, @@ -2061,6 +2815,7 @@ Object { "username": "supabot", }, ], + "count": null, "data": Array [ Object { "age_range": "[1,2)", @@ -2094,6 +2849,7 @@ Object { "username": "awailas", }, ], + "count": null, "data": Array [ Object { "age_range": "[25,35)", @@ -2119,6 +2875,7 @@ Object { exports[`stored procedure 1`] = ` Object { "body": "ONLINE", + "count": null, "data": "ONLINE", "error": null, "status": 200, @@ -2126,6 +2883,38 @@ Object { } `; +exports[`stored procedure with count: 'exact' 1`] = ` +Object { + "body": null, + "count": null, + "data": null, + "error": Object { + "code": "42883", + "details": null, + "hint": "No function matches the given name and argument types. You might need to add explicit type casts.", + "message": "function public.get_status(count => text, name_param => text) does not exist", + }, + "status": 404, + "statusText": "Not Found", +} +`; + +exports[`stored procedure with count: 'exact', head: true 1`] = ` +Object { + "body": null, + "count": null, + "data": null, + "error": Object { + "code": "42883", + "details": null, + "hint": "No function matches the given name and argument types. You might need to add explicit type casts.", + "message": "function public.get_status(count => text, head => text, name_param => text) does not exist", + }, + "status": 404, + "statusText": "Not Found", +} +`; + exports[`switch schema 1`] = ` Object { "body": Array [ @@ -2160,6 +2949,7 @@ Object { "username": "leroyjenkins", }, ], + "count": null, "data": Array [ Object { "age_range": "[1,2)", @@ -2209,6 +2999,7 @@ Object { "username": "supabot", }, ], + "count": null, "data": Array [ Object { "age_range": "[1,2)", diff --git a/test/basic.ts b/test/basic.ts index 6a81a982..c3aafdfd 100644 --- a/test/basic.ts +++ b/test/basic.ts @@ -140,3 +140,104 @@ test('Prefer: return=minimal', async () => { await postgrest.from('users').delete().eq('username', 'bar') }) + +test('select with head:true', async () => { + const res = await postgrest.from('users').select('*', { head: true }) + expect(res).toMatchSnapshot() +}) + +test('select with head:true, count:exact', async () => { + const res = await postgrest.from('users').select('*', { head: true, count: 'exact' }) + expect(res).toMatchSnapshot() +}) + +test('select with head:true, count:planned', async () => { + const res = await postgrest.from('users').select('*', { head: true, count: 'planned' }) + expect(res).toMatchSnapshot({ + count: expect.any(Number), + }) +}) + +test('select with head:true, count:estimated', async () => { + const res = await postgrest.from('users').select('*', { head: true, count: 'estimated' }) + expect(res).toMatchSnapshot({ + count: expect.any(Number), + }) +}) + +test('select with count:exact', async () => { + const res = await postgrest.from('users').select('*', { count: 'exact' }) + expect(res).toMatchSnapshot() +}) + +test("stored procedure with count: 'exact'", async () => { + const res = await postgrest.rpc('get_status', { name_param: 'supabot', count: 'exact' }) + expect(res).toMatchSnapshot() +}) + +test("stored procedure with count: 'exact', head: true", async () => { + const res = await postgrest.rpc('get_status', { + name_param: 'supabot', + count: 'exact', + head: true, + }) + expect(res).toMatchSnapshot() +}) + +describe("insert, update, delete with count: 'exact'", () => { + test("insert with count: 'exact'", async () => { + let res = await postgrest + .from('messages') + .insert({ message: 'foo', username: 'supabot', channel_id: 1 }, { count: 'exact' }) + expect(res).toMatchSnapshot() + + res = await postgrest.from('messages').select() + expect(res).toMatchSnapshot() + }) + + test("upsert with count: 'exact'", async () => { + let res = await postgrest + .from('messages') + .insert( + { id: 3, message: 'foo', username: 'supabot', channel_id: 2 }, + { upsert: true, count: 'exact' } + ) + expect(res).toMatchSnapshot() + + res = await postgrest.from('messages').select() + expect(res).toMatchSnapshot() + }) + + test("bulk insert with count: 'exact'", async () => { + let res = await postgrest.from('messages').insert( + [ + { message: 'foo', username: 'supabot', channel_id: 1 }, + { message: 'foo', username: 'supabot', channel_id: 1 }, + ], + { count: 'exact' } + ) + expect(res).toMatchSnapshot() + + res = await postgrest.from('messages').select() + expect(res).toMatchSnapshot() + }) + + test("update with count: 'exact'", async () => { + let res = await postgrest + .from('messages') + .update({ channel_id: 2 }, { count: 'exact' }) + .eq('message', 'foo') + expect(res).toMatchSnapshot() + + res = await postgrest.from('messages').select() + expect(res).toMatchSnapshot() + }) + + test("basic delete count: 'exact'", async () => { + let res = await postgrest.from('messages').delete({ count: 'exact' }).eq('message', 'foo') + expect(res).toMatchSnapshot() + + res = await postgrest.from('messages').select() + expect(res).toMatchSnapshot() + }) +}) diff --git a/test/transforms.ts b/test/transforms.ts index 6c561b89..9a85285b 100644 --- a/test/transforms.ts +++ b/test/transforms.ts @@ -37,6 +37,8 @@ test('select on insert', async () => { }) test('select on stored procedure', async () => { - const res = await postgrest.rpc('get_username_and_status', { name_param: 'supabot' }).select('status') + const res = await postgrest + .rpc('get_username_and_status', { name_param: 'supabot' }) + .select('status') expect(res).toMatchSnapshot() })