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