@supabase/postgrest-js 1.0.0-rc.3 → 1.0.0-rc.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,431 @@
1
+ import PostgrestTransformBuilder from './PostgrestTransformBuilder'
2
+
3
+ /**
4
+ * Filters
5
+ */
6
+
7
+ type FilterOperator =
8
+ | 'eq'
9
+ | 'neq'
10
+ | 'gt'
11
+ | 'gte'
12
+ | 'lt'
13
+ | 'lte'
14
+ | 'like'
15
+ | 'ilike'
16
+ | 'is'
17
+ | 'in'
18
+ | 'cs'
19
+ | 'cd'
20
+ | 'sl'
21
+ | 'sr'
22
+ | 'nxl'
23
+ | 'nxr'
24
+ | 'adj'
25
+ | 'ov'
26
+ | 'fts'
27
+ | 'plfts'
28
+ | 'phfts'
29
+ | 'wfts'
30
+
31
+ export default class PostgrestFilterBuilder<
32
+ Table extends Record<string, unknown>,
33
+ Result
34
+ > extends PostgrestTransformBuilder<Table, Result> {
35
+ /**
36
+ * Finds all rows which doesn't satisfy the filter.
37
+ *
38
+ * @param column The column to filter on.
39
+ * @param operator The operator to filter with.
40
+ * @param value The value to filter with.
41
+ */
42
+ not<ColumnName extends string & keyof Table>(
43
+ column: ColumnName,
44
+ operator: FilterOperator,
45
+ value: Table[ColumnName]
46
+ ): this
47
+ not(column: string, operator: string, value: unknown): this
48
+ not(column: string, operator: string, value: unknown): this {
49
+ this.url.searchParams.append(column, `not.${operator}.${value}`)
50
+ return this
51
+ }
52
+
53
+ /**
54
+ * Finds all rows satisfying at least one of the filters.
55
+ *
56
+ * @param filters The filters to use, separated by commas.
57
+ * @param foreignTable The foreign table to use (if `column` is a foreign column).
58
+ */
59
+ or(filters: string, { foreignTable }: { foreignTable?: string } = {}): this {
60
+ const key = foreignTable ? `${foreignTable}.or` : 'or'
61
+ this.url.searchParams.append(key, `(${filters})`)
62
+ return this
63
+ }
64
+
65
+ /**
66
+ * Finds all rows whose value on the stated `column` exactly matches the
67
+ * specified `value`.
68
+ *
69
+ * @param column The column to filter on.
70
+ * @param value The value to filter with.
71
+ */
72
+ eq<ColumnName extends string & keyof Table>(column: ColumnName, value: Table[ColumnName]): this
73
+ eq(column: string, value: unknown): this
74
+ eq(column: string, value: unknown): this {
75
+ this.url.searchParams.append(column, `eq.${value}`)
76
+ return this
77
+ }
78
+
79
+ /**
80
+ * Finds all rows whose value on the stated `column` doesn't match the
81
+ * specified `value`.
82
+ *
83
+ * @param column The column to filter on.
84
+ * @param value The value to filter with.
85
+ */
86
+ neq<ColumnName extends string & keyof Table>(column: ColumnName, value: Table[ColumnName]): this
87
+ neq(column: string, value: unknown): this
88
+ neq(column: string, value: unknown): this {
89
+ this.url.searchParams.append(column, `neq.${value}`)
90
+ return this
91
+ }
92
+
93
+ /**
94
+ * Finds all rows whose value on the stated `column` is greater than the
95
+ * specified `value`.
96
+ *
97
+ * @param column The column to filter on.
98
+ * @param value The value to filter with.
99
+ */
100
+ gt<ColumnName extends string & keyof Table>(column: ColumnName, value: Table[ColumnName]): this
101
+ gt(column: string, value: unknown): this
102
+ gt(column: string, value: unknown): this {
103
+ this.url.searchParams.append(column, `gt.${value}`)
104
+ return this
105
+ }
106
+
107
+ /**
108
+ * Finds all rows whose value on the stated `column` is greater than or
109
+ * equal to the specified `value`.
110
+ *
111
+ * @param column The column to filter on.
112
+ * @param value The value to filter with.
113
+ */
114
+ gte<ColumnName extends string & keyof Table>(column: ColumnName, value: Table[ColumnName]): this
115
+ gte(column: string, value: unknown): this
116
+ gte(column: string, value: unknown): this {
117
+ this.url.searchParams.append(column, `gte.${value}`)
118
+ return this
119
+ }
120
+
121
+ /**
122
+ * Finds all rows whose value on the stated `column` is less than the
123
+ * specified `value`.
124
+ *
125
+ * @param column The column to filter on.
126
+ * @param value The value to filter with.
127
+ */
128
+ lt<ColumnName extends string & keyof Table>(column: ColumnName, value: Table[ColumnName]): this
129
+ lt(column: string, value: unknown): this
130
+ lt(column: string, value: unknown): this {
131
+ this.url.searchParams.append(column, `lt.${value}`)
132
+ return this
133
+ }
134
+
135
+ /**
136
+ * Finds all rows whose value on the stated `column` is less than or equal
137
+ * to the specified `value`.
138
+ *
139
+ * @param column The column to filter on.
140
+ * @param value The value to filter with.
141
+ */
142
+ lte<ColumnName extends string & keyof Table>(column: ColumnName, value: Table[ColumnName]): this
143
+ lte(column: string, value: unknown): this
144
+ lte(column: string, value: unknown): this {
145
+ this.url.searchParams.append(column, `lte.${value}`)
146
+ return this
147
+ }
148
+
149
+ /**
150
+ * Finds all rows whose value in the stated `column` matches the supplied
151
+ * `pattern` (case sensitive).
152
+ *
153
+ * @param column The column to filter on.
154
+ * @param pattern The pattern to filter with.
155
+ */
156
+ like<ColumnName extends string & keyof Table>(column: ColumnName, pattern: string): this
157
+ like(column: string, pattern: string): this
158
+ like(column: string, pattern: string): this {
159
+ this.url.searchParams.append(column, `like.${pattern}`)
160
+ return this
161
+ }
162
+
163
+ /**
164
+ * Finds all rows whose value in the stated `column` matches the supplied
165
+ * `pattern` (case insensitive).
166
+ *
167
+ * @param column The column to filter on.
168
+ * @param pattern The pattern to filter with.
169
+ */
170
+ ilike<ColumnName extends string & keyof Table>(column: ColumnName, pattern: string): this
171
+ ilike(column: string, pattern: string): this
172
+ ilike(column: string, pattern: string): this {
173
+ this.url.searchParams.append(column, `ilike.${pattern}`)
174
+ return this
175
+ }
176
+
177
+ /**
178
+ * A check for exact equality (null, true, false), finds all rows whose
179
+ * value on the stated `column` exactly match the specified `value`.
180
+ *
181
+ * @param column The column to filter on.
182
+ * @param value The value to filter with.
183
+ */
184
+ is<ColumnName extends string & keyof Table>(
185
+ column: ColumnName,
186
+ value: Table[ColumnName] & (boolean | null)
187
+ ): this
188
+ is(column: string, value: boolean | null): this
189
+ is(column: string, value: boolean | null): this {
190
+ this.url.searchParams.append(column, `is.${value}`)
191
+ return this
192
+ }
193
+
194
+ /**
195
+ * Finds all rows whose value on the stated `column` is found on the
196
+ * specified `values`.
197
+ *
198
+ * @param column The column to filter on.
199
+ * @param values The values to filter with.
200
+ */
201
+ in<ColumnName extends string & keyof Table>(column: ColumnName, values: Table[ColumnName][]): this
202
+ in(column: string, values: unknown[]): this
203
+ in(column: string, values: unknown[]): this {
204
+ const cleanedValues = values
205
+ .map((s) => {
206
+ // handle postgrest reserved characters
207
+ // https://postgrest.org/en/v7.0.0/api.html#reserved-characters
208
+ if (typeof s === 'string' && new RegExp('[,()]').test(s)) return `"${s}"`
209
+ else return `${s}`
210
+ })
211
+ .join(',')
212
+ this.url.searchParams.append(column, `in.(${cleanedValues})`)
213
+ return this
214
+ }
215
+
216
+ /**
217
+ * Finds all rows whose json, array, or range value on the stated `column`
218
+ * contains the values specified in `value`.
219
+ *
220
+ * @param column The column to filter on.
221
+ * @param value The value to filter with.
222
+ */
223
+ contains<ColumnName extends string & keyof Table>(
224
+ column: ColumnName,
225
+ value: string | Table[ColumnName][] | Record<string, unknown>
226
+ ): this
227
+ contains(column: string, value: string | unknown[] | Record<string, unknown>): this
228
+ contains(column: string, value: string | unknown[] | Record<string, unknown>): this {
229
+ if (typeof value === 'string') {
230
+ // range types can be inclusive '[', ']' or exclusive '(', ')' so just
231
+ // keep it simple and accept a string
232
+ this.url.searchParams.append(column, `cs.${value}`)
233
+ } else if (Array.isArray(value)) {
234
+ // array
235
+ this.url.searchParams.append(column, `cs.{${value.join(',')}}`)
236
+ } else {
237
+ // json
238
+ this.url.searchParams.append(column, `cs.${JSON.stringify(value)}`)
239
+ }
240
+ return this
241
+ }
242
+
243
+ /**
244
+ * Finds all rows whose json, array, or range value on the stated `column` is
245
+ * contained by the specified `value`.
246
+ *
247
+ * @param column The column to filter on.
248
+ * @param value The value to filter with.
249
+ */
250
+ containedBy<ColumnName extends string & keyof Table>(
251
+ column: ColumnName,
252
+ value: string | Table[ColumnName][] | Record<string, unknown>
253
+ ): this
254
+ containedBy(column: string, value: string | unknown[] | Record<string, unknown>): this
255
+ containedBy(column: string, value: string | unknown[] | Record<string, unknown>): this {
256
+ if (typeof value === 'string') {
257
+ // range
258
+ this.url.searchParams.append(column, `cd.${value}`)
259
+ } else if (Array.isArray(value)) {
260
+ // array
261
+ this.url.searchParams.append(column, `cd.{${value.join(',')}}`)
262
+ } else {
263
+ // json
264
+ this.url.searchParams.append(column, `cd.${JSON.stringify(value)}`)
265
+ }
266
+ return this
267
+ }
268
+
269
+ /**
270
+ * Finds all rows whose range value on the stated `column` is strictly to the
271
+ * left of the specified `range`.
272
+ *
273
+ * @param column The column to filter on.
274
+ * @param range The range to filter with.
275
+ */
276
+ rangeLt<ColumnName extends string & keyof Table>(column: ColumnName, range: string): this
277
+ rangeLt(column: string, range: string): this
278
+ rangeLt(column: string, range: string): this {
279
+ this.url.searchParams.append(column, `sl.${range}`)
280
+ return this
281
+ }
282
+
283
+ /**
284
+ * Finds all rows whose range value on the stated `column` is strictly to
285
+ * the right of the specified `range`.
286
+ *
287
+ * @param column The column to filter on.
288
+ * @param range The range to filter with.
289
+ */
290
+ rangeGt<ColumnName extends string & keyof Table>(column: ColumnName, range: string): this
291
+ rangeGt(column: string, range: string): this
292
+ rangeGt(column: string, range: string): this {
293
+ this.url.searchParams.append(column, `sr.${range}`)
294
+ return this
295
+ }
296
+
297
+ /**
298
+ * Finds all rows whose range value on the stated `column` does not extend
299
+ * to the left of the specified `range`.
300
+ *
301
+ * @param column The column to filter on.
302
+ * @param range The range to filter with.
303
+ */
304
+ rangeGte<ColumnName extends string & keyof Table>(column: ColumnName, range: string): this
305
+ rangeGte(column: string, range: string): this
306
+ rangeGte(column: string, range: string): this {
307
+ this.url.searchParams.append(column, `nxl.${range}`)
308
+ return this
309
+ }
310
+
311
+ /**
312
+ * Finds all rows whose range value on the stated `column` does not extend
313
+ * to the right of the specified `range`.
314
+ *
315
+ * @param column The column to filter on.
316
+ * @param range The range to filter with.
317
+ */
318
+ rangeLte<ColumnName extends string & keyof Table>(column: ColumnName, range: string): this
319
+ rangeLte(column: string, range: string): this
320
+ rangeLte(column: string, range: string): this {
321
+ this.url.searchParams.append(column, `nxr.${range}`)
322
+ return this
323
+ }
324
+
325
+ /**
326
+ * Finds all rows whose range value on the stated `column` is adjacent to
327
+ * the specified `range`.
328
+ *
329
+ * @param column The column to filter on.
330
+ * @param range The range to filter with.
331
+ */
332
+ rangeAdjacent<ColumnName extends string & keyof Table>(column: ColumnName, range: string): this
333
+ rangeAdjacent(column: string, range: string): this
334
+ rangeAdjacent(column: string, range: string): this {
335
+ this.url.searchParams.append(column, `adj.${range}`)
336
+ return this
337
+ }
338
+
339
+ /**
340
+ * Finds all rows whose array or range value on the stated `column` overlaps
341
+ * (has a value in common) with the specified `value`.
342
+ *
343
+ * @param column The column to filter on.
344
+ * @param value The value to filter with.
345
+ */
346
+ overlaps<ColumnName extends string & keyof Table>(
347
+ column: ColumnName,
348
+ value: string | Table[ColumnName][]
349
+ ): this
350
+ overlaps(column: string, value: string | unknown[]): this
351
+ overlaps(column: string, value: string | unknown[]): this {
352
+ if (typeof value === 'string') {
353
+ // range
354
+ this.url.searchParams.append(column, `ov.${value}`)
355
+ } else {
356
+ // array
357
+ this.url.searchParams.append(column, `ov.{${value.join(',')}}`)
358
+ }
359
+ return this
360
+ }
361
+
362
+ /**
363
+ * Finds all rows whose text or tsvector value on the stated `column` matches
364
+ * the tsquery in `query`.
365
+ *
366
+ * @param column The column to filter on.
367
+ * @param query The Postgres tsquery string to filter with.
368
+ * @param config The text search configuration to use.
369
+ * @param type The type of tsquery conversion to use on `query`.
370
+ */
371
+ textSearch<ColumnName extends string & keyof Table>(
372
+ column: ColumnName,
373
+ query: string,
374
+ options?: { config?: string; type?: 'plain' | 'phrase' | 'websearch' }
375
+ ): this
376
+ textSearch(
377
+ column: string,
378
+ query: string,
379
+ options?: { config?: string; type?: 'plain' | 'phrase' | 'websearch' }
380
+ ): this
381
+ textSearch(
382
+ column: string,
383
+ query: string,
384
+ { config, type }: { config?: string; type?: 'plain' | 'phrase' | 'websearch' } = {}
385
+ ): this {
386
+ let typePart = ''
387
+ if (type === 'plain') {
388
+ typePart = 'pl'
389
+ } else if (type === 'phrase') {
390
+ typePart = 'ph'
391
+ } else if (type === 'websearch') {
392
+ typePart = 'w'
393
+ }
394
+ const configPart = config === undefined ? '' : `(${config})`
395
+ this.url.searchParams.append(column, `${typePart}fts${configPart}.${query}`)
396
+ return this
397
+ }
398
+
399
+ /**
400
+ * Finds all rows whose `column` satisfies the filter.
401
+ *
402
+ * @param column The column to filter on.
403
+ * @param operator The operator to filter with.
404
+ * @param value The value to filter with.
405
+ */
406
+ filter<ColumnName extends string & keyof Table>(
407
+ column: ColumnName,
408
+ operator: `${'' | 'not.'}${FilterOperator}`,
409
+ value: unknown
410
+ ): this
411
+ filter(column: string, operator: string, value: unknown): this
412
+ filter(column: string, operator: string, value: unknown): this {
413
+ this.url.searchParams.append(column, `${operator}.${value}`)
414
+ return this
415
+ }
416
+
417
+ /**
418
+ * Finds all rows whose columns match the specified `query` object.
419
+ *
420
+ * @param query The object to filter with, with column names as keys mapped
421
+ * to their filter values.
422
+ */
423
+ match<ColumnName extends string & keyof Table>(query: Record<ColumnName, Table[ColumnName]>): this
424
+ match(query: Record<string, unknown>): this
425
+ match(query: Record<string, unknown>): this {
426
+ Object.entries(query).forEach(([column, value]) => {
427
+ this.url.searchParams.append(column, `eq.${value}`)
428
+ })
429
+ return this
430
+ }
431
+ }
@@ -0,0 +1,235 @@
1
+ import PostgrestBuilder from './PostgrestBuilder'
2
+ import PostgrestFilterBuilder from './PostgrestFilterBuilder'
3
+ import { GetResult } from './select-query-parser'
4
+ import { Fetch, GenericTable } from './types'
5
+
6
+ export default class PostgrestQueryBuilder<Table extends GenericTable> {
7
+ url: URL
8
+ headers: Record<string, string>
9
+ schema?: string
10
+ signal?: AbortSignal
11
+ fetch?: Fetch
12
+
13
+ constructor(
14
+ url: URL,
15
+ {
16
+ headers = {},
17
+ schema,
18
+ fetch,
19
+ }: {
20
+ headers?: Record<string, string>
21
+ schema?: string
22
+ fetch?: Fetch
23
+ }
24
+ ) {
25
+ this.url = url
26
+ this.headers = headers
27
+ this.schema = schema
28
+ this.fetch = fetch
29
+ }
30
+
31
+ /**
32
+ * Performs vertical filtering with SELECT.
33
+ *
34
+ * @param columns The columns to retrieve, separated by commas.
35
+ */
36
+ select<
37
+ Query extends string = '*',
38
+ Result = GetResult<Table['Row'], Query extends '*' ? '*' : Query>
39
+ >(
40
+ columns?: Query,
41
+ {
42
+ head = false,
43
+ count,
44
+ }: {
45
+ /** When set to true, select will void data. */
46
+ head?: boolean
47
+ /** Count algorithm to use to count rows in a table. */
48
+ count?: 'exact' | 'planned' | 'estimated'
49
+ } = {}
50
+ ): PostgrestFilterBuilder<Table['Row'], Result> {
51
+ const method = head ? 'HEAD' : 'GET'
52
+ // Remove whitespaces except when quoted
53
+ let quoted = false
54
+ const cleanedColumns = (columns ?? '*')
55
+ .split('')
56
+ .map((c) => {
57
+ if (/\s/.test(c) && !quoted) {
58
+ return ''
59
+ }
60
+ if (c === '"') {
61
+ quoted = !quoted
62
+ }
63
+ return c
64
+ })
65
+ .join('')
66
+ this.url.searchParams.set('select', cleanedColumns)
67
+ if (count) {
68
+ this.headers['Prefer'] = `count=${count}`
69
+ }
70
+
71
+ return new PostgrestFilterBuilder({
72
+ method,
73
+ url: this.url,
74
+ headers: this.headers,
75
+ schema: this.schema,
76
+ fetch: this.fetch,
77
+ allowEmpty: false,
78
+ } as unknown as PostgrestBuilder<Result>)
79
+ }
80
+
81
+ /**
82
+ * Performs an INSERT into the table.
83
+ *
84
+ * @param values The values to insert.
85
+ */
86
+ insert<Row extends Table['Insert']>(
87
+ values: Row | Row[],
88
+ {
89
+ count,
90
+ }: {
91
+ /** Count algorithm to use to count rows in a table. */
92
+ count?: 'exact' | 'planned' | 'estimated'
93
+ } = {}
94
+ ): PostgrestFilterBuilder<Table['Row'], undefined> {
95
+ const method = 'POST'
96
+
97
+ const prefersHeaders = []
98
+ const body = values
99
+ if (count) {
100
+ prefersHeaders.push(`count=${count}`)
101
+ }
102
+ if (this.headers['Prefer']) {
103
+ prefersHeaders.unshift(this.headers['Prefer'])
104
+ }
105
+ this.headers['Prefer'] = prefersHeaders.join(',')
106
+
107
+ if (Array.isArray(values)) {
108
+ const columns = values.reduce((acc, x) => acc.concat(Object.keys(x)), [] as string[])
109
+ if (columns.length > 0) {
110
+ const uniqueColumns = [...new Set(columns)].map((column) => `"${column}"`)
111
+ this.url.searchParams.set('columns', uniqueColumns.join(','))
112
+ }
113
+ }
114
+
115
+ return new PostgrestFilterBuilder({
116
+ method,
117
+ url: this.url,
118
+ headers: this.headers,
119
+ schema: this.schema,
120
+ body,
121
+ fetch: this.fetch,
122
+ allowEmpty: false,
123
+ } as unknown as PostgrestBuilder<undefined>)
124
+ }
125
+
126
+ /**
127
+ * Performs an UPSERT into the table.
128
+ *
129
+ * @param values The values to insert.
130
+ */
131
+ upsert<Row extends Table['Insert']>(
132
+ values: Row | Row[],
133
+ {
134
+ onConflict,
135
+ count,
136
+ ignoreDuplicates = false,
137
+ }: {
138
+ /** By specifying the `on_conflict` query parameter, you can make UPSERT work on a column(s) that has a UNIQUE constraint. */
139
+ onConflict?: string
140
+ /** Count algorithm to use to count rows in a table. */
141
+ count?: 'exact' | 'planned' | 'estimated'
142
+ /** Specifies if duplicate rows should be ignored and not inserted. */
143
+ ignoreDuplicates?: boolean
144
+ } = {}
145
+ ): PostgrestFilterBuilder<Table['Row'], undefined> {
146
+ const method = 'POST'
147
+
148
+ const prefersHeaders = [`resolution=${ignoreDuplicates ? 'ignore' : 'merge'}-duplicates`]
149
+
150
+ if (onConflict !== undefined) this.url.searchParams.set('on_conflict', onConflict)
151
+ const body = values
152
+ if (count) {
153
+ prefersHeaders.push(`count=${count}`)
154
+ }
155
+ if (this.headers['Prefer']) {
156
+ prefersHeaders.unshift(this.headers['Prefer'])
157
+ }
158
+ this.headers['Prefer'] = prefersHeaders.join(',')
159
+
160
+ return new PostgrestFilterBuilder({
161
+ method,
162
+ url: this.url,
163
+ headers: this.headers,
164
+ schema: this.schema,
165
+ body,
166
+ fetch: this.fetch,
167
+ allowEmpty: false,
168
+ } as unknown as PostgrestBuilder<undefined>)
169
+ }
170
+
171
+ /**
172
+ * Performs an UPDATE on the table.
173
+ *
174
+ * @param values The values to update.
175
+ */
176
+ update<Row extends Table['Update']>(
177
+ values: Row,
178
+ {
179
+ count,
180
+ }: {
181
+ /** Count algorithm to use to count rows in a table. */
182
+ count?: 'exact' | 'planned' | 'estimated'
183
+ } = {}
184
+ ): PostgrestFilterBuilder<Table['Row'], undefined> {
185
+ const method = 'PATCH'
186
+ const prefersHeaders = []
187
+ const body = values
188
+ if (count) {
189
+ prefersHeaders.push(`count=${count}`)
190
+ }
191
+ if (this.headers['Prefer']) {
192
+ prefersHeaders.unshift(this.headers['Prefer'])
193
+ }
194
+ this.headers['Prefer'] = prefersHeaders.join(',')
195
+
196
+ return new PostgrestFilterBuilder({
197
+ method,
198
+ url: this.url,
199
+ headers: this.headers,
200
+ schema: this.schema,
201
+ body,
202
+ fetch: this.fetch,
203
+ allowEmpty: false,
204
+ } as unknown as PostgrestBuilder<undefined>)
205
+ }
206
+
207
+ /**
208
+ * Performs a DELETE on the table.
209
+ */
210
+ delete({
211
+ count,
212
+ }: {
213
+ /** Count algorithm to use to count rows in a table. */
214
+ count?: 'exact' | 'planned' | 'estimated'
215
+ } = {}): PostgrestFilterBuilder<Table['Row'], undefined> {
216
+ const method = 'DELETE'
217
+ const prefersHeaders = []
218
+ if (count) {
219
+ prefersHeaders.push(`count=${count}`)
220
+ }
221
+ if (this.headers['Prefer']) {
222
+ prefersHeaders.unshift(this.headers['Prefer'])
223
+ }
224
+ this.headers['Prefer'] = prefersHeaders.join(',')
225
+
226
+ return new PostgrestFilterBuilder({
227
+ method,
228
+ url: this.url,
229
+ headers: this.headers,
230
+ schema: this.schema,
231
+ fetch: this.fetch,
232
+ allowEmpty: false,
233
+ } as unknown as PostgrestBuilder<undefined>)
234
+ }
235
+ }