Skip to content

Commit 8cf6e00

Browse files
dshukertjrsoedirgo
andauthored
feat: Adding head and count option on select(), insert(), update(), delete(), and rpc() (#147)
* select method now accepts home and count option * removed head and count option from PostgrestTransformBuilder and added home and count option to select in PostgrestQueryBuilder * select now returns count value when count option is selected * Enabled head for select * Updated the test snapshots to include count field * Set default values for head and count Co-authored-by: Bobbie Soedirgo <[email protected]> * Added basic test cases for select with head and count parameters * Added count and head option to rpc, and added count option to insert, update, delete * Added count tests for insert, update, delete rpc * Added head and count test for rpc * Updated test so that count value for 'planned' and 'estimated' would match any number Co-authored-by: Bobbie Soedirgo <[email protected]>
1 parent d56d69a commit 8cf6e00

File tree

7 files changed

+986
-20
lines changed

7 files changed

+986
-20
lines changed

src/PostgrestClient.ts

-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import PostgrestQueryBuilder from './lib/PostgrestQueryBuilder'
22
import PostgrestTransformBuilder from './lib/PostgrestTransformBuilder'
3-
import { PostgrestBuilder } from './lib/types'
43

54
export default class PostgrestClient {
65
url: string

src/lib/PostgrestQueryBuilder.ts

+66-6
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,19 @@ export default class PostgrestQueryBuilder<T> extends PostgrestBuilder<T> {
1717
* Performs vertical filtering with SELECT.
1818
*
1919
* @param columns The columns to retrieve, separated by commas.
20+
* @param head When set to true, select will void data.
21+
* @param count Count algorithm to use to count rows in a table.
2022
*/
21-
select(columns = '*'): PostgrestFilterBuilder<T> {
23+
select(
24+
columns = '*',
25+
{
26+
head = false,
27+
count = null,
28+
}: {
29+
head?: boolean
30+
count?: null | 'exact' | 'planned' | 'estimated'
31+
} = {}
32+
): PostgrestFilterBuilder<T> {
2233
this.method = 'GET'
2334
// Remove whitespaces except when quoted
2435
let quoted = false
@@ -35,6 +46,12 @@ export default class PostgrestQueryBuilder<T> extends PostgrestBuilder<T> {
3546
})
3647
.join('')
3748
this.url.searchParams.set('select', cleanedColumns)
49+
if (count) {
50+
this.headers['Prefer'] = `count=${count}`
51+
}
52+
if (head) {
53+
this.method = 'HEAD'
54+
}
3855
return new PostgrestFilterBuilder(this)
3956
}
4057

@@ -52,10 +69,12 @@ export default class PostgrestQueryBuilder<T> extends PostgrestBuilder<T> {
5269
upsert = false,
5370
onConflict,
5471
returning = 'representation',
72+
count = null,
5573
}: {
5674
upsert?: boolean
5775
onConflict?: string
5876
returning?: 'minimal' | 'representation'
77+
count?: null | 'exact' | 'planned' | 'estimated'
5978
} = {}
6079
): PostgrestFilterBuilder<T> {
6180
this.method = 'POST'
@@ -67,6 +86,10 @@ export default class PostgrestQueryBuilder<T> extends PostgrestBuilder<T> {
6786

6887
if (upsert && onConflict !== undefined) this.url.searchParams.set('on_conflict', onConflict)
6988
this.body = values
89+
if (count) {
90+
prefersHeaders.push(`count=${count}`)
91+
this.headers['Prefer'] = prefersHeaders.join(',')
92+
}
7093
return new PostgrestFilterBuilder(this)
7194
}
7295

@@ -78,11 +101,23 @@ export default class PostgrestQueryBuilder<T> extends PostgrestBuilder<T> {
78101
*/
79102
update(
80103
values: Partial<T>,
81-
{ returning = 'representation' }: { returning?: 'minimal' | 'representation' } = {}
104+
{
105+
returning = 'representation',
106+
count = null,
107+
}: {
108+
returning?: 'minimal' | 'representation'
109+
count?: null | 'exact' | 'planned' | 'estimated'
110+
} = {}
82111
): PostgrestFilterBuilder<T> {
83112
this.method = 'PATCH'
84-
this.headers['Prefer'] = `return=${returning}`
113+
let prefersHeaders = []
114+
prefersHeaders.push(`return=${returning}`)
115+
this.headers['Prefer'] = prefersHeaders.join(',')
85116
this.body = values
117+
if (count) {
118+
prefersHeaders.push(`count=${count}`)
119+
this.headers['Prefer'] = prefersHeaders.join(',')
120+
}
86121
return new PostgrestFilterBuilder(this)
87122
}
88123

@@ -93,16 +128,41 @@ export default class PostgrestQueryBuilder<T> extends PostgrestBuilder<T> {
93128
*/
94129
delete({
95130
returning = 'representation',
96-
}: { returning?: 'minimal' | 'representation' } = {}): PostgrestFilterBuilder<T> {
131+
count = null,
132+
}: {
133+
returning?: 'minimal' | 'representation'
134+
count?: null | 'exact' | 'planned' | 'estimated'
135+
} = {}): PostgrestFilterBuilder<T> {
97136
this.method = 'DELETE'
98-
this.headers['Prefer'] = `return=${returning}`
137+
let prefersHeaders = []
138+
prefersHeaders.push(`return=${returning}`)
139+
this.headers['Prefer'] = prefersHeaders.join(',')
140+
if (count) {
141+
prefersHeaders.push(`count=${count}`)
142+
this.headers['Prefer'] = prefersHeaders.join(',')
143+
}
99144
return new PostgrestFilterBuilder(this)
100145
}
101146

102147
/** @internal */
103-
rpc(params?: object): PostgrestTransformBuilder<T> {
148+
rpc(
149+
params?: object,
150+
{
151+
head = false,
152+
count = null,
153+
}: {
154+
head?: boolean
155+
count?: null | 'exact' | 'planned' | 'estimated'
156+
} = {}
157+
): PostgrestTransformBuilder<T> {
104158
this.method = 'POST'
105159
this.body = params
160+
if (count) {
161+
this.headers['Prefer'] = `count=${count}`
162+
}
163+
if (head) {
164+
this.method = 'HEAD'
165+
}
106166
return new PostgrestTransformBuilder(this)
107167
}
108168
}

src/lib/PostgrestTransformBuilder.ts

+2-9
Original file line numberDiff line numberDiff line change
@@ -59,10 +59,7 @@ export default class PostgrestTransformBuilder<T> extends PostgrestBuilder<T> {
5959
* @param count The maximum no. of rows to limit to.
6060
* @param foreignTable The foreign table to use (for foreign columns).
6161
*/
62-
limit(
63-
count: number,
64-
{ foreignTable }: { foreignTable?: string } = {}
65-
): this {
62+
limit(count: number, { foreignTable }: { foreignTable?: string } = {}): this {
6663
const key = typeof foreignTable === 'undefined' ? 'limit' : `"${foreignTable}".limit`
6764
this.url.searchParams.set(key, `${count}`)
6865
return this
@@ -75,11 +72,7 @@ export default class PostgrestTransformBuilder<T> extends PostgrestBuilder<T> {
7572
* @param to The last index to which to limit the result, inclusive.
7673
* @param foreignTable The foreign table to use (for foreign columns).
7774
*/
78-
range(
79-
from: number,
80-
to: number,
81-
{ foreignTable }: { foreignTable?: string } = {}
82-
): this {
75+
range(from: number, to: number, { foreignTable }: { foreignTable?: string } = {}): this {
8376
const keyOffset = typeof foreignTable === 'undefined' ? 'offset' : `"${foreignTable}".offset`
8477
const keyLimit = typeof foreignTable === 'undefined' ? 'limit' : `"${foreignTable}".limit`
8578
this.url.searchParams.set(keyOffset, `${from}`)

src/lib/types.ts

+23-3
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,14 @@ interface PostgrestResponseSuccess<T> extends PostgrestResponseBase {
2626
error: null
2727
data: T[]
2828
body: T[]
29+
count: number | null
2930
}
3031
interface PostgrestResponseFailure extends PostgrestResponseBase {
3132
error: PostgrestError
3233
data: null
3334
// For backward compatibility: body === data
3435
body: null
36+
count: null
3537
}
3638
export type PostgrestResponse<T> = PostgrestResponseSuccess<T> | PostgrestResponseFailure
3739

@@ -80,18 +82,36 @@ export abstract class PostgrestBuilder<T> implements PromiseLike<PostgrestRespon
8082
body: JSON.stringify(this.body),
8183
})
8284
.then(async (res) => {
83-
let error, data
85+
let error, data, count
8486
if (res.ok) {
8587
error = null
86-
const isReturnMinimal = this.headers['Prefer']?.split(',').includes('return=minimal')
87-
data = isReturnMinimal ? null : await res.json()
88+
if (this.method !== 'HEAD') {
89+
const isReturnMinimal = this.headers['Prefer']?.split(',').includes('return=minimal')
90+
data = isReturnMinimal ? null : await res.json()
91+
} else {
92+
data = null
93+
}
94+
95+
const countHeader = this.headers['Prefer']?.match(/count=(exact|planned|estimated)/)
96+
if (countHeader) {
97+
const contentRange = res.headers.get('content-range')?.split('/')
98+
if (contentRange && contentRange.length > 1) {
99+
count = parseInt(contentRange[1])
100+
} else {
101+
count = null
102+
}
103+
} else {
104+
count = null
105+
}
88106
} else {
89107
error = await res.json()
90108
data = null
109+
count = null
91110
}
92111
const postgrestResponse: PostgrestResponse<T> = {
93112
error,
94113
data,
114+
count,
95115
status: res.status,
96116
statusText: res.statusText,
97117
body: data,

0 commit comments

Comments
 (0)