@raphiniert/ra-data-postgrest 2.2.0 → 2.4.0

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.
package/src/index.ts CHANGED
@@ -1,422 +1,460 @@
1
- import {
2
- DataProvider,
3
- GetListParams,
4
- GetOneParams,
5
- GetManyParams,
6
- GetManyReferenceParams,
7
- UpdateParams,
8
- UpdateManyParams,
9
- CreateParams,
10
- DeleteParams,
11
- DeleteManyParams,
12
- } from 'ra-core';
13
- import {
14
- PrimaryKey,
15
- PostgRestOperator,
16
- getPrimaryKey,
17
- parseFilters,
18
- getOrderBy,
19
- dataWithVirtualId,
20
- dataWithoutVirtualId,
21
- removePrimaryKey,
22
- getQuery,
23
- encodeId,
24
- PostgRestSortOrder,
25
- } from './urlBuilder';
26
- import qs from 'qs';
27
- import isEqual from 'lodash/isEqual';
28
-
29
- /**
30
- * Maps react-admin queries to a postgrest REST API
31
- *
32
- * This REST dialect uses postgrest syntax
33
- *
34
- * @see https://postgrest.org/en/stable/api.html#embedded-filters
35
- *
36
- * @example
37
- *
38
- * getList => GET http://my.api.url/posts?order=title.asc&offset=0&limit=24&filterField=eq.value
39
- * getOne => GET http://my.api.url/posts?id=eq.123
40
- * getMany => GET http://my.api.url/posts?id=in.(123,456,789)
41
- * getManyReference => GET http://my.api.url/posts?author_id=eq.345
42
- * create => POST http://my.api.url/posts
43
- * update => PATCH http://my.api.url/posts?id=eq.123
44
- * updateMany => PATCH http://my.api.url/posts?id=in.(123,456,789)
45
- * delete => DELETE http://my.api.url/posts?id=eq.123
46
- * deleteMany => DELETE http://my.api.url/posts?id=in.(123,456,789)
47
- *
48
- * @example
49
- *
50
- * import * as React from 'react';
51
- * import { Admin, Resource, fetchUtils } from 'react-admin';
52
- * import postgrestRestProvider,
53
- * { IDataProviderConfig,
54
- * defaultPrimaryKeys,
55
- * defaultSchema } from '@raphiniert/ra-data-postgrest';
56
- *
57
- * import { PostList } from './posts';
58
- *
59
- * const config: IDataProviderConfig = {
60
- * apiUrl: 'http://path.to.my.api/',
61
- * httpClient: fetchUtils.fetchJson,
62
- * defaultListOp: 'eq',
63
- * primaryKeys: defaultPrimaryKeys,
64
- * schema: defaultSchema
65
- * }
66
- *
67
- * const App = () => (
68
- * <Admin dataProvider={postgrestRestProvider(config)}>
69
- * <Resource name="posts" list={PostList} />
70
- * </Admin>
71
- * );
72
- *
73
- * export default App;
74
- */
75
-
76
- export type MetaOption = string | undefined;
77
-
78
- export const defaultPrimaryKeys = new Map<string, PrimaryKey>();
79
-
80
- export const defaultSchema = () => '';
81
-
82
- export interface IDataProviderConfig {
83
- apiUrl: string;
84
- httpClient: (string, Options) => Promise<any>;
85
- defaultListOp: PostgRestOperator;
86
- sortOrder?: PostgRestSortOrder;
87
- primaryKeys: Map<string, PrimaryKey>;
88
- schema: () => string;
89
- }
90
-
91
- const useCustomSchema = (
92
- schema: () => string,
93
- metaSchema: MetaOption,
94
- method: string
95
- ) => {
96
- let funcHeaderSchema = schema;
97
- if (metaSchema !== undefined) {
98
- funcHeaderSchema = () => metaSchema;
99
- }
100
-
101
- if (funcHeaderSchema().length > 0) {
102
- let schemaHeader = '';
103
- if (['GET', 'HEAD'].includes(method)) {
104
- schemaHeader = 'Accept-Profile';
105
- } else if (['POST', 'PATCH', 'PUT', 'DELETE'].includes(method)) {
106
- schemaHeader = 'Content-Profile';
107
- } else return {};
108
-
109
- return { [schemaHeader]: funcHeaderSchema() };
110
- } else return {};
111
- };
112
-
113
- export default (config: IDataProviderConfig): DataProvider => ({
114
- getList: (resource, params: Partial<GetListParams> = {}) => {
115
- const primaryKey = getPrimaryKey(resource, config.primaryKeys);
116
-
117
- const { page, perPage } = params.pagination;
118
- const { field, order } = params.sort || {};
119
- const { filter, select } = parseFilters(params, config.defaultListOp);
120
- const metaSchema = params.meta?.schema;
121
-
122
- let query = {
123
- offset: String((page - 1) * perPage),
124
- limit: String(perPage),
125
- // append filters
126
- ...filter,
127
- };
128
-
129
- if (field) {
130
- query.order = getOrderBy(field, order, primaryKey);
131
- }
132
-
133
- if (select) {
134
- query.select = select;
135
- }
136
-
137
- // add header that Content-Range is in returned header
138
- const options = {
139
- headers: new Headers({
140
- Accept: 'application/json',
141
- Prefer: 'count=exact',
142
- ...(params.meta?.headers || {}),
143
- ...useCustomSchema(config.schema, metaSchema, 'GET'),
144
- }),
145
- };
146
-
147
- const url = `${config.apiUrl}/${resource}?${qs.stringify(query)}`;
148
-
149
- return config.httpClient(url, options).then(({ headers, json }) => {
150
- if (!headers.has('content-range')) {
151
- throw new Error(
152
- `The Content-Range header is missing in the HTTP Response. The postgREST data provider expects
153
- responses for lists of resources to contain this header with the total number of results to build
154
- the pagination. If you are using CORS, did you declare Content-Range in the Access-Control-Expose-Headers header?`
155
- );
156
- }
157
- return {
158
- data: json.map(obj => dataWithVirtualId(obj, primaryKey)),
159
- total: parseInt(
160
- headers.get('content-range').split('/').pop(),
161
- 10
162
- ),
163
- };
164
- });
165
- },
166
-
167
- getOne: (resource, params: Partial<GetOneParams> = {}) => {
168
- const { id, meta } = params;
169
- const primaryKey = getPrimaryKey(resource, config.primaryKeys);
170
-
171
- const query = getQuery(primaryKey, id, resource, meta);
172
-
173
- const url = `${config.apiUrl}/${resource}?${qs.stringify(query)}`;
174
- const metaSchema = params.meta?.schema;
175
-
176
- return config
177
- .httpClient(url, {
178
- headers: new Headers({
179
- accept: 'application/vnd.pgrst.object+json',
180
- ...(params.meta?.headers || {}),
181
- ...useCustomSchema(config.schema, metaSchema, 'GET'),
182
- }),
183
- })
184
- .then(({ json }) => ({
185
- data: dataWithVirtualId(json, primaryKey),
186
- }));
187
- },
188
-
189
- getMany: (resource, params: Partial<GetManyParams> = {}) => {
190
- const ids = params.ids;
191
- const primaryKey = getPrimaryKey(resource, config.primaryKeys);
192
-
193
- const query = getQuery(primaryKey, ids, resource, params.meta);
194
-
195
- const url = `${config.apiUrl}/${resource}?${qs.stringify(query)}`;
196
- const metaSchema = params.meta?.schema;
197
-
198
- return config
199
- .httpClient(url, {
200
- headers: new Headers({
201
- ...useCustomSchema(config.schema, metaSchema, 'GET'),
202
- }),
203
- })
204
- .then(({ json }) => ({
205
- data: json.map(data => dataWithVirtualId(data, primaryKey)),
206
- }));
207
- },
208
-
209
- getManyReference: (
210
- resource,
211
- params: Partial<GetManyReferenceParams> = {}
212
- ) => {
213
- const { page, perPage } = params.pagination;
214
- const { field, order } = params.sort;
215
-
216
- const { filter, select } = parseFilters(params, config.defaultListOp);
217
- const primaryKey = getPrimaryKey(resource, config.primaryKeys);
218
-
219
- const metaSchema = params.meta?.schema;
220
-
221
- let query = params.target
222
- ? {
223
- [params.target]: `eq.${params.id}`,
224
- order: getOrderBy(field, order, primaryKey, config.sortOrder),
225
- offset: String((page - 1) * perPage),
226
- limit: String(perPage),
227
- ...filter,
228
- }
229
- : {
230
- order: getOrderBy(field, order, primaryKey),
231
- offset: String((page - 1) * perPage),
232
- limit: String(perPage),
233
- ...filter,
234
- };
235
-
236
- if (select) {
237
- query.select = select;
238
- }
239
-
240
- // add header that Content-Range is in returned header
241
- const options = {
242
- headers: new Headers({
243
- Accept: 'application/json',
244
- Prefer: 'count=exact',
245
- ...(params.meta?.headers || {}),
246
- ...useCustomSchema(config.schema, metaSchema, 'GET'),
247
- }),
248
- };
249
-
250
- const url = `${config.apiUrl}/${resource}?${qs.stringify(query)}`;
251
-
252
- return config.httpClient(url, options).then(({ headers, json }) => {
253
- if (!headers.has('content-range')) {
254
- throw new Error(
255
- `The Content-Range header is missing in the HTTP Response. The postgREST data provider expects
256
- responses for lists of resources to contain this header with the total number of results to build
257
- the pagination. If you are using CORS, did you declare Content-Range in the Access-Control-Expose-Headers header?`
258
- );
259
- }
260
- return {
261
- data: json.map(data => dataWithVirtualId(data, primaryKey)),
262
- total: parseInt(
263
- headers.get('content-range').split('/').pop(),
264
- 10
265
- ),
266
- };
267
- });
268
- },
269
-
270
- update: (resource, params: Partial<UpdateParams> = {}) => {
271
- const { id, data, meta, previousData } = params;
272
- const primaryKey = getPrimaryKey(resource, config.primaryKeys);
273
- const query = getQuery(primaryKey, id, resource, meta);
274
- const url = `${config.apiUrl}/${resource}?${qs.stringify(query)}`;
275
- const changedData = getChanges(data, previousData);
276
- const metaSchema = params.meta?.schema;
277
-
278
- const body = JSON.stringify({
279
- ...dataWithoutVirtualId(
280
- removePrimaryKey(changedData, primaryKey),
281
- primaryKey
282
- ),
283
- });
284
-
285
- return config
286
- .httpClient(url, {
287
- method: 'PATCH',
288
- headers: new Headers({
289
- Accept: 'application/vnd.pgrst.object+json',
290
- Prefer: 'return=representation',
291
- 'Content-Type': 'application/json',
292
- ...(params.meta?.headers || {}),
293
- ...useCustomSchema(config.schema, metaSchema, 'PATCH'),
294
- }),
295
- body,
296
- })
297
- .then(({ json }) => ({
298
- data: dataWithVirtualId(json, primaryKey),
299
- }));
300
- },
301
-
302
- updateMany: (resource, params: Partial<UpdateManyParams> = {}) => {
303
- const { ids, meta, data } = params;
304
- const primaryKey = getPrimaryKey(resource, config.primaryKeys);
305
- const query = getQuery(primaryKey, ids, resource, meta);
306
- const url = `${config.apiUrl}/${resource}?${qs.stringify(query)}`;
307
- const body = JSON.stringify({
308
- ...dataWithoutVirtualId(
309
- removePrimaryKey(data, primaryKey),
310
- primaryKey
311
- ),
312
- });
313
-
314
- const metaSchema = params.meta?.schema;
315
-
316
- return config
317
- .httpClient(url, {
318
- method: 'PATCH',
319
- headers: new Headers({
320
- Prefer: 'return=representation',
321
- 'Content-Type': 'application/json',
322
- ...(params.meta?.headers || {}),
323
- ...useCustomSchema(config.schema, metaSchema, 'PATCH'),
324
- }),
325
- body,
326
- })
327
- .then(({ json }) => ({
328
- data: json.map(data => encodeId(data, primaryKey)),
329
- }));
330
- },
331
-
332
- create: (resource, params: Partial<CreateParams> = {}) => {
333
- const { meta } = params;
334
- const primaryKey = getPrimaryKey(resource, config.primaryKeys);
335
- const query = getQuery(primaryKey, undefined, resource, meta);
336
- const queryStr = qs.stringify(query);
337
- const url = `${config.apiUrl}/${resource}${
338
- queryStr.length > 0 ? '?' : ''
339
- }${queryStr}`;
340
- const metaSchema = params.meta?.schema;
341
-
342
- return config
343
- .httpClient(url, {
344
- method: 'POST',
345
- headers: new Headers({
346
- Accept: 'application/vnd.pgrst.object+json',
347
- Prefer: 'return=representation',
348
- 'Content-Type': 'application/json',
349
- ...(params.meta?.headers || {}),
350
- ...useCustomSchema(config.schema, metaSchema, 'POST'),
351
- }),
352
- body: JSON.stringify(
353
- dataWithoutVirtualId(params.data, primaryKey)
354
- ),
355
- })
356
- .then(({ json }) => ({
357
- data: {
358
- ...json,
359
- id: encodeId(json, primaryKey),
360
- },
361
- }));
362
- },
363
-
364
- delete: (resource, params: Partial<DeleteParams> = {}) => {
365
- const { id, meta } = params;
366
- const primaryKey = getPrimaryKey(resource, config.primaryKeys);
367
-
368
- const query = getQuery(primaryKey, id, resource, meta);
369
-
370
- const url = `${config.apiUrl}/${resource}?${qs.stringify(query)}`;
371
- const metaSchema = params.meta?.schema;
372
-
373
- return config
374
- .httpClient(url, {
375
- method: 'DELETE',
376
- headers: new Headers({
377
- Accept: 'application/vnd.pgrst.object+json',
378
- Prefer: 'return=representation',
379
- 'Content-Type': 'application/json',
380
- ...(params.meta?.headers || {}),
381
- ...useCustomSchema(config.schema, metaSchema, 'DELETE'),
382
- }),
383
- })
384
- .then(({ json }) => ({
385
- data: dataWithVirtualId(json, primaryKey),
386
- }));
387
- },
388
-
389
- deleteMany: (resource, params: Partial<DeleteManyParams> = {}) => {
390
- const { ids, meta } = params;
391
- const primaryKey = getPrimaryKey(resource, config.primaryKeys);
392
-
393
- const query = getQuery(primaryKey, ids, resource, meta);
394
-
395
- const url = `${config.apiUrl}/${resource}?${qs.stringify(query)}`;
396
- const metaSchema = params.meta?.schema;
397
-
398
- return config
399
- .httpClient(url, {
400
- method: 'DELETE',
401
- headers: new Headers({
402
- Prefer: 'return=representation',
403
- 'Content-Type': 'application/json',
404
- ...(params.meta?.headers || {}),
405
- ...useCustomSchema(config.schema, metaSchema, 'DELETE'),
406
- }),
407
- })
408
- .then(({ json }) => ({
409
- data: json.map(data => encodeId(data, primaryKey)),
410
- }));
411
- },
412
- });
413
-
414
- const getChanges = (data: any, previousData: any) => {
415
- const changes = Object.keys(data).reduce((changes, key) => {
416
- if (!isEqual(data[key], previousData[key])) {
417
- changes[key] = data[key];
418
- }
419
- return changes;
420
- }, {});
421
- return changes;
422
- };
1
+ import {
2
+ DataProvider,
3
+ GetListParams,
4
+ GetOneParams,
5
+ GetManyParams,
6
+ GetManyReferenceParams,
7
+ UpdateParams,
8
+ UpdateManyParams,
9
+ CreateParams,
10
+ DeleteParams,
11
+ DeleteManyParams,
12
+ } from 'ra-core';
13
+ import {
14
+ PrimaryKey,
15
+ PostgRestOperator,
16
+ getPrimaryKey,
17
+ parseFilters,
18
+ getOrderBy,
19
+ dataWithVirtualId,
20
+ dataWithoutVirtualId,
21
+ removePrimaryKey,
22
+ getQuery,
23
+ encodeId,
24
+ PostgRestSortOrder,
25
+ } from './urlBuilder';
26
+ import qs from 'qs';
27
+ import isEqual from 'lodash/isEqual';
28
+
29
+ /**
30
+ * Maps react-admin queries to a postgrest REST API
31
+ *
32
+ * This REST dialect uses postgrest syntax
33
+ *
34
+ * @see https://postgrest.org/en/stable/api.html#embedded-filters
35
+ *
36
+ * @example
37
+ *
38
+ * getList => GET http://my.api.url/posts?order=title.asc&offset=0&limit=24&filterField=eq.value
39
+ * getOne => GET http://my.api.url/posts?id=eq.123
40
+ * getMany => GET http://my.api.url/posts?id=in.(123,456,789)
41
+ * getManyReference => GET http://my.api.url/posts?author_id=eq.345
42
+ * create => POST http://my.api.url/posts
43
+ * update => PATCH http://my.api.url/posts?id=eq.123
44
+ * updateMany => PATCH http://my.api.url/posts?id=in.(123,456,789)
45
+ * delete => DELETE http://my.api.url/posts?id=eq.123
46
+ * deleteMany => DELETE http://my.api.url/posts?id=in.(123,456,789)
47
+ *
48
+ * @example
49
+ *
50
+ * import * as React from 'react';
51
+ * import { Admin, Resource, fetchUtils } from 'react-admin';
52
+ * import postgrestRestProvider,
53
+ * { IDataProviderConfig,
54
+ * defaultPrimaryKeys,
55
+ * defaultSchema } from '@raphiniert/ra-data-postgrest';
56
+ *
57
+ * import { PostList } from './posts';
58
+ *
59
+ * const config: IDataProviderConfig = {
60
+ * apiUrl: 'http://path.to.my.api/',
61
+ * httpClient: fetchUtils.fetchJson,
62
+ * defaultListOp: 'eq',
63
+ * primaryKeys: defaultPrimaryKeys,
64
+ * schema: defaultSchema
65
+ * }
66
+ *
67
+ * const App = () => (
68
+ * <Admin dataProvider={postgrestRestProvider(config)}>
69
+ * <Resource name="posts" list={PostList} />
70
+ * </Admin>
71
+ * );
72
+ *
73
+ * export default App;
74
+ */
75
+
76
+ export type MetaOption = string | undefined;
77
+
78
+ export const defaultPrimaryKeys = new Map<string, PrimaryKey>();
79
+
80
+ export const defaultSchema = () => '';
81
+
82
+ export { PostgRestSortOrder };
83
+
84
+ export interface IDataProviderConfig {
85
+ apiUrl: string;
86
+ httpClient: (string, Options) => Promise<any>;
87
+ defaultListOp: PostgRestOperator;
88
+ sortOrder?: PostgRestSortOrder;
89
+ primaryKeys: Map<string, PrimaryKey>;
90
+ schema: () => string;
91
+ }
92
+
93
+ const useCustomSchema = (
94
+ schema: () => string,
95
+ metaSchema: MetaOption,
96
+ method: string
97
+ ) => {
98
+ let funcHeaderSchema = schema;
99
+ if (metaSchema !== undefined) {
100
+ funcHeaderSchema = () => metaSchema;
101
+ }
102
+
103
+ if (funcHeaderSchema().length > 0) {
104
+ let schemaHeader = '';
105
+ if (['GET', 'HEAD'].includes(method)) {
106
+ schemaHeader = 'Accept-Profile';
107
+ } else if (['POST', 'PATCH', 'PUT', 'DELETE'].includes(method)) {
108
+ schemaHeader = 'Content-Profile';
109
+ } else return {};
110
+
111
+ return { [schemaHeader]: funcHeaderSchema() };
112
+ } else return {};
113
+ };
114
+
115
+ export default (config: IDataProviderConfig): DataProvider => ({
116
+ getList: (resource, params: Partial<GetListParams> = {}) => {
117
+ const primaryKey = getPrimaryKey(resource, config.primaryKeys);
118
+
119
+ const { page, perPage } = params.pagination;
120
+ const { field, order } = params.sort || {};
121
+ const { filter, select } = parseFilters(params, config.defaultListOp);
122
+ const metaSchema = params.meta?.schema;
123
+
124
+ let query = {
125
+ offset: String((page - 1) * perPage),
126
+ limit: String(perPage),
127
+ // append filters
128
+ ...filter,
129
+ };
130
+
131
+ if (field) {
132
+ query.order = getOrderBy(
133
+ field,
134
+ order,
135
+ primaryKey,
136
+ config.sortOrder
137
+ );
138
+ if (
139
+ params.meta?.nullsfirst &&
140
+ !query.order.includes('nullsfirst')
141
+ ) {
142
+ query.order = query.order.includes('nullslast')
143
+ ? query.order.replace('nullslast', 'nullsfirst')
144
+ : `${query.order}.nullsfirst`;
145
+ }
146
+ if (
147
+ params.meta?.nullsfirst === false &&
148
+ query.order.includes('nullsfirst')
149
+ ) {
150
+ query.order = query.order.replace('.nullsfirst', '');
151
+ }
152
+ if (params.meta?.nullslast && !query.order.includes('nullslast')) {
153
+ query.order = query.order.includes('nullsfirst')
154
+ ? query.order.replace('nullsfirst', 'nullslast')
155
+ : `${query.order}.nullslast`;
156
+ }
157
+ if (
158
+ params.meta?.nullslast === false &&
159
+ query.order.includes('nullslast')
160
+ ) {
161
+ query.order = query.order.replace('.nullslast', '');
162
+ }
163
+ }
164
+
165
+ if (select) {
166
+ query.select = select;
167
+ }
168
+
169
+ // add header that Content-Range is in returned header
170
+ const options = {
171
+ headers: new Headers({
172
+ Accept: 'application/json',
173
+ Prefer: 'count=exact',
174
+ ...(params.meta?.headers || {}),
175
+ ...useCustomSchema(config.schema, metaSchema, 'GET'),
176
+ }),
177
+ };
178
+
179
+ const url = `${config.apiUrl}/${resource}?${qs.stringify(query)}`;
180
+
181
+ return config.httpClient(url, options).then(({ headers, json }) => {
182
+ if (!headers.has('content-range')) {
183
+ throw new Error(
184
+ `The Content-Range header is missing in the HTTP Response. The postgREST data provider expects
185
+ responses for lists of resources to contain this header with the total number of results to build
186
+ the pagination. If you are using CORS, did you declare Content-Range in the Access-Control-Expose-Headers header?`
187
+ );
188
+ }
189
+ return {
190
+ data: json.map(obj => dataWithVirtualId(obj, primaryKey)),
191
+ total: parseInt(
192
+ headers.get('content-range').split('/').pop(),
193
+ 10
194
+ ),
195
+ };
196
+ });
197
+ },
198
+
199
+ getOne: (resource, params: Partial<GetOneParams> = {}) => {
200
+ const { id, meta } = params;
201
+ const primaryKey = getPrimaryKey(resource, config.primaryKeys);
202
+
203
+ const query = getQuery(primaryKey, id, resource, meta);
204
+
205
+ const url = `${config.apiUrl}/${resource}?${qs.stringify(query)}`;
206
+ const metaSchema = params.meta?.schema;
207
+
208
+ return config
209
+ .httpClient(url, {
210
+ headers: new Headers({
211
+ accept: 'application/vnd.pgrst.object+json',
212
+ ...(params.meta?.headers || {}),
213
+ ...useCustomSchema(config.schema, metaSchema, 'GET'),
214
+ }),
215
+ })
216
+ .then(({ json }) => ({
217
+ data: dataWithVirtualId(json, primaryKey),
218
+ }));
219
+ },
220
+
221
+ getMany: (resource, params: Partial<GetManyParams> = {}) => {
222
+ const ids = params.ids;
223
+ if (ids.length === 0) {
224
+ return Promise.resolve({ data: [] });
225
+ }
226
+ const primaryKey = getPrimaryKey(resource, config.primaryKeys);
227
+
228
+ const query = getQuery(primaryKey, ids, resource, params.meta);
229
+
230
+ const url = `${config.apiUrl}/${resource}?${qs.stringify(query)}`;
231
+ const metaSchema = params.meta?.schema;
232
+
233
+ return config
234
+ .httpClient(url, {
235
+ headers: new Headers({
236
+ ...useCustomSchema(config.schema, metaSchema, 'GET'),
237
+ }),
238
+ })
239
+ .then(({ json }) => ({
240
+ data: json.map(data => dataWithVirtualId(data, primaryKey)),
241
+ }));
242
+ },
243
+
244
+ getManyReference: (
245
+ resource,
246
+ params: Partial<GetManyReferenceParams> = {}
247
+ ) => {
248
+ const { page, perPage } = params.pagination;
249
+ const { field, order } = params.sort;
250
+
251
+ const { filter, select } = parseFilters(params, config.defaultListOp);
252
+ const primaryKey = getPrimaryKey(resource, config.primaryKeys);
253
+
254
+ const metaSchema = params.meta?.schema;
255
+
256
+ let query = params.target
257
+ ? {
258
+ [params.target]: `eq.${params.id}`,
259
+ order: getOrderBy(field, order, primaryKey, config.sortOrder),
260
+ offset: String((page - 1) * perPage),
261
+ limit: String(perPage),
262
+ ...filter,
263
+ }
264
+ : {
265
+ order: getOrderBy(field, order, primaryKey),
266
+ offset: String((page - 1) * perPage),
267
+ limit: String(perPage),
268
+ ...filter,
269
+ };
270
+
271
+ if (select) {
272
+ query.select = select;
273
+ }
274
+
275
+ // add header that Content-Range is in returned header
276
+ const options = {
277
+ headers: new Headers({
278
+ Accept: 'application/json',
279
+ Prefer: 'count=exact',
280
+ ...(params.meta?.headers || {}),
281
+ ...useCustomSchema(config.schema, metaSchema, 'GET'),
282
+ }),
283
+ };
284
+
285
+ const url = `${config.apiUrl}/${resource}?${qs.stringify(query)}`;
286
+
287
+ return config.httpClient(url, options).then(({ headers, json }) => {
288
+ if (!headers.has('content-range')) {
289
+ throw new Error(
290
+ `The Content-Range header is missing in the HTTP Response. The postgREST data provider expects
291
+ responses for lists of resources to contain this header with the total number of results to build
292
+ the pagination. If you are using CORS, did you declare Content-Range in the Access-Control-Expose-Headers header?`
293
+ );
294
+ }
295
+ return {
296
+ data: json.map(data => dataWithVirtualId(data, primaryKey)),
297
+ total: parseInt(
298
+ headers.get('content-range').split('/').pop(),
299
+ 10
300
+ ),
301
+ };
302
+ });
303
+ },
304
+
305
+ update: (resource, params: Partial<UpdateParams> = {}) => {
306
+ const { id, data, meta, previousData } = params;
307
+ const primaryKey = getPrimaryKey(resource, config.primaryKeys);
308
+ const query = getQuery(primaryKey, id, resource, meta);
309
+ const url = `${config.apiUrl}/${resource}?${qs.stringify(query)}`;
310
+ const changedData = getChanges(data, previousData);
311
+ if (Object.keys(changedData).length === 0) {
312
+ return Promise.resolve({ data: { ...previousData } });
313
+ }
314
+ const metaSchema = params.meta?.schema;
315
+
316
+ const body = JSON.stringify({
317
+ ...dataWithoutVirtualId(
318
+ removePrimaryKey(changedData, primaryKey),
319
+ primaryKey
320
+ ),
321
+ });
322
+
323
+ return config
324
+ .httpClient(url, {
325
+ method: 'PATCH',
326
+ headers: new Headers({
327
+ Accept: 'application/vnd.pgrst.object+json',
328
+ Prefer: 'return=representation',
329
+ 'Content-Type': 'application/json',
330
+ ...(params.meta?.headers || {}),
331
+ ...useCustomSchema(config.schema, metaSchema, 'PATCH'),
332
+ }),
333
+ body,
334
+ })
335
+ .then(({ json }) => ({
336
+ data: dataWithVirtualId(json, primaryKey),
337
+ }));
338
+ },
339
+
340
+ updateMany: (resource, params: Partial<UpdateManyParams> = {}) => {
341
+ const { ids, meta, data } = params;
342
+ const primaryKey = getPrimaryKey(resource, config.primaryKeys);
343
+ const query = getQuery(primaryKey, ids, resource, meta);
344
+ const url = `${config.apiUrl}/${resource}?${qs.stringify(query)}`;
345
+ const body = JSON.stringify({
346
+ ...dataWithoutVirtualId(
347
+ removePrimaryKey(data, primaryKey),
348
+ primaryKey
349
+ ),
350
+ });
351
+
352
+ const metaSchema = params.meta?.schema;
353
+
354
+ return config
355
+ .httpClient(url, {
356
+ method: 'PATCH',
357
+ headers: new Headers({
358
+ Prefer: 'return=representation',
359
+ 'Content-Type': 'application/json',
360
+ ...(params.meta?.headers || {}),
361
+ ...useCustomSchema(config.schema, metaSchema, 'PATCH'),
362
+ }),
363
+ body,
364
+ })
365
+ .then(({ json }) => ({
366
+ data: json.map(data => encodeId(data, primaryKey)),
367
+ }));
368
+ },
369
+
370
+ create: (resource, params: Partial<CreateParams> = {}) => {
371
+ const { meta } = params;
372
+ const primaryKey = getPrimaryKey(resource, config.primaryKeys);
373
+ const query = getQuery(primaryKey, undefined, resource, meta);
374
+ const queryStr = qs.stringify(query);
375
+ const url = `${config.apiUrl}/${resource}${
376
+ queryStr.length > 0 ? '?' : ''
377
+ }${queryStr}`;
378
+ const metaSchema = params.meta?.schema;
379
+
380
+ return config
381
+ .httpClient(url, {
382
+ method: 'POST',
383
+ headers: new Headers({
384
+ Accept: 'application/vnd.pgrst.object+json',
385
+ Prefer: 'return=representation',
386
+ 'Content-Type': 'application/json',
387
+ ...(params.meta?.headers || {}),
388
+ ...useCustomSchema(config.schema, metaSchema, 'POST'),
389
+ }),
390
+ body: JSON.stringify(
391
+ dataWithoutVirtualId(params.data, primaryKey)
392
+ ),
393
+ })
394
+ .then(({ json }) => ({
395
+ data: {
396
+ ...json,
397
+ id: encodeId(json, primaryKey),
398
+ },
399
+ }));
400
+ },
401
+
402
+ delete: (resource, params: Partial<DeleteParams> = {}) => {
403
+ const { id, meta } = params;
404
+ const primaryKey = getPrimaryKey(resource, config.primaryKeys);
405
+
406
+ const query = getQuery(primaryKey, id, resource, meta);
407
+
408
+ const url = `${config.apiUrl}/${resource}?${qs.stringify(query)}`;
409
+ const metaSchema = params.meta?.schema;
410
+
411
+ return config
412
+ .httpClient(url, {
413
+ method: 'DELETE',
414
+ headers: new Headers({
415
+ Accept: 'application/vnd.pgrst.object+json',
416
+ Prefer: 'return=representation',
417
+ 'Content-Type': 'application/json',
418
+ ...(params.meta?.headers || {}),
419
+ ...useCustomSchema(config.schema, metaSchema, 'DELETE'),
420
+ }),
421
+ })
422
+ .then(({ json }) => ({
423
+ data: dataWithVirtualId(json, primaryKey),
424
+ }));
425
+ },
426
+
427
+ deleteMany: (resource, params: Partial<DeleteManyParams> = {}) => {
428
+ const { ids, meta } = params;
429
+ const primaryKey = getPrimaryKey(resource, config.primaryKeys);
430
+
431
+ const query = getQuery(primaryKey, ids, resource, meta);
432
+
433
+ const url = `${config.apiUrl}/${resource}?${qs.stringify(query)}`;
434
+ const metaSchema = params.meta?.schema;
435
+
436
+ return config
437
+ .httpClient(url, {
438
+ method: 'DELETE',
439
+ headers: new Headers({
440
+ Prefer: 'return=representation',
441
+ 'Content-Type': 'application/json',
442
+ ...(params.meta?.headers || {}),
443
+ ...useCustomSchema(config.schema, metaSchema, 'DELETE'),
444
+ }),
445
+ })
446
+ .then(({ json }) => ({
447
+ data: json.map(data => encodeId(data, primaryKey)),
448
+ }));
449
+ },
450
+ });
451
+
452
+ const getChanges = (data: any, previousData: any) => {
453
+ const changes = Object.keys(data).reduce((changes, key) => {
454
+ if (!isEqual(data[key], previousData[key])) {
455
+ changes[key] = data[key];
456
+ }
457
+ return changes;
458
+ }, {});
459
+ return changes;
460
+ };