Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding head and count option on select(), insert(), update(), delete(), and rpc() #147

Merged
merged 12 commits into from
Jan 15, 2021
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 0 additions & 1 deletion src/PostgrestClient.ts
Original file line number Diff line number Diff line change
@@ -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
Expand Down
72 changes: 66 additions & 6 deletions src/lib/PostgrestQueryBuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,19 @@ export default class PostgrestQueryBuilder<T> extends PostgrestBuilder<T> {
* 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<T> {
select(
columns = '*',
{
head = false,
count = null,
}: {
head?: boolean
count?: null | 'exact' | 'planned' | 'estimated'
} = {}
): PostgrestFilterBuilder<T> {
this.method = 'GET'
// Remove whitespaces except when quoted
let quoted = false
Expand All @@ -35,6 +46,12 @@ export default class PostgrestQueryBuilder<T> extends PostgrestBuilder<T> {
})
.join('')
this.url.searchParams.set('select', cleanedColumns)
if (count) {
this.headers['Prefer'] = `count=${count}`
}
if (head) {
this.method = 'HEAD'
}
return new PostgrestFilterBuilder(this)
}

Expand All @@ -52,10 +69,12 @@ export default class PostgrestQueryBuilder<T> extends PostgrestBuilder<T> {
upsert = false,
onConflict,
returning = 'representation',
count = null,
}: {
upsert?: boolean
onConflict?: string
returning?: 'minimal' | 'representation'
count?: null | 'exact' | 'planned' | 'estimated'
} = {}
): PostgrestFilterBuilder<T> {
this.method = 'POST'
Expand All @@ -67,6 +86,10 @@ export default class PostgrestQueryBuilder<T> extends PostgrestBuilder<T> {

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)
}

Expand All @@ -78,11 +101,23 @@ export default class PostgrestQueryBuilder<T> extends PostgrestBuilder<T> {
*/
update(
values: Partial<T>,
{ returning = 'representation' }: { returning?: 'minimal' | 'representation' } = {}
{
returning = 'representation',
count = null,
}: {
returning?: 'minimal' | 'representation'
count?: null | 'exact' | 'planned' | 'estimated'
} = {}
): PostgrestFilterBuilder<T> {
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)
}

Expand All @@ -93,16 +128,41 @@ export default class PostgrestQueryBuilder<T> extends PostgrestBuilder<T> {
*/
delete({
returning = 'representation',
}: { returning?: 'minimal' | 'representation' } = {}): PostgrestFilterBuilder<T> {
count = null,
}: {
returning?: 'minimal' | 'representation'
count?: null | 'exact' | 'planned' | 'estimated'
} = {}): PostgrestFilterBuilder<T> {
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<T> {
rpc(
params?: object,
{
head = false,
count = null,
}: {
head?: boolean
count?: null | 'exact' | 'planned' | 'estimated'
} = {}
): PostgrestTransformBuilder<T> {
this.method = 'POST'
this.body = params
if (count) {
this.headers['Prefer'] = `count=${count}`
}
if (head) {
this.method = 'HEAD'
}
return new PostgrestTransformBuilder(this)
}
}
11 changes: 2 additions & 9 deletions src/lib/PostgrestTransformBuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,10 +59,7 @@ export default class PostgrestTransformBuilder<T> extends PostgrestBuilder<T> {
* @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
Expand All @@ -75,11 +72,7 @@ export default class PostgrestTransformBuilder<T> extends PostgrestBuilder<T> {
* @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}`)
Expand Down
26 changes: 23 additions & 3 deletions src/lib/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,14 @@ interface PostgrestResponseSuccess<T> 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<T> = PostgrestResponseSuccess<T> | PostgrestResponseFailure

Expand Down Expand Up @@ -80,18 +82,36 @@ export abstract class PostgrestBuilder<T> implements PromiseLike<PostgrestRespon
body: JSON.stringify(this.body),
})
.then(async (res) => {
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)/)
dshukertjr marked this conversation as resolved.
Show resolved Hide resolved
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<T> = {
error,
data,
count,
status: res.status,
statusText: res.statusText,
body: data,
Expand Down