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