@raphiniert/ra-data-postgrest 2.0.0-alpha.1 → 2.0.0-alpha.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.
package/src/index.ts CHANGED
@@ -1,5 +1,4 @@
1
1
  import {
2
- fetchUtils,
3
2
  DataProvider,
4
3
  GetListParams,
5
4
  GetOneParams,
@@ -17,12 +16,11 @@ import {
17
16
  getPrimaryKey,
18
17
  parseFilters,
19
18
  getOrderBy,
20
- dataWithId,
19
+ dataWithVirtualId,
20
+ dataWithoutVirtualId,
21
+ removePrimaryKey,
21
22
  getQuery,
22
- getKeyData,
23
23
  encodeId,
24
- decodeId,
25
- isCompoundKey,
26
24
  } from './urlBuilder';
27
25
  import qs from 'qs';
28
26
 
@@ -48,13 +46,24 @@ import qs from 'qs';
48
46
  * @example
49
47
  *
50
48
  * import * as React from 'react';
51
- * import { Admin, Resource } from 'react-admin';
52
- * import postgrestRestProvider from '@raphiniert/ra-data-postgrest';
49
+ * import { Admin, Resource, fetchUtils } from 'react-admin';
50
+ * import postgrestRestProvider,
51
+ * { IDataProviderConfig,
52
+ * defaultPrimaryKeys,
53
+ * defaultSchema } from '@raphiniert/ra-data-postgrest';
53
54
  *
54
55
  * import { PostList } from './posts';
55
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
+ *
56
65
  * const App = () => (
57
- * <Admin dataProvider={postgrestRestProvider('http://path.to.my.api/')}>
66
+ * <Admin dataProvider={postgrestRestProvider(config)}>
58
67
  * <Resource name="posts" list={PostList} />
59
68
  * </Admin>
60
69
  * );
@@ -62,20 +71,50 @@ import qs from 'qs';
62
71
  * export default App;
63
72
  */
64
73
 
65
- const defaultPrimaryKeys = new Map<string, PrimaryKey>();
66
-
67
- export default (
68
- apiUrl: string,
69
- httpClient = fetchUtils.fetchJson,
70
- defaultListOp: PostgRestOperator = 'eq',
71
- primaryKeys: Map<string, PrimaryKey> = defaultPrimaryKeys
72
- ): DataProvider => ({
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 => ({
73
111
  getList: (resource, params: Partial<GetListParams> = {}) => {
74
- const primaryKey = getPrimaryKey(resource, primaryKeys);
112
+ const primaryKey = getPrimaryKey(resource, config.primaryKeys);
75
113
 
76
114
  const { page, perPage } = params.pagination;
77
115
  const { field, order } = params.sort || {};
78
- const { filter, select } = parseFilters(params, defaultListOp);
116
+ const { filter, select } = parseFilters(params, config.defaultListOp);
117
+ const metaSchema = params.meta?.schema;
79
118
 
80
119
  let query = {
81
120
  offset: String((page - 1) * perPage),
@@ -98,12 +137,13 @@ export default (
98
137
  Accept: 'application/json',
99
138
  Prefer: 'count=exact',
100
139
  ...(params.meta?.headers || {}),
140
+ ...useCustomSchema(config.schema, metaSchema, 'GET'),
101
141
  }),
102
142
  };
103
143
 
104
- const url = `${apiUrl}/${resource}?${qs.stringify(query)}`;
144
+ const url = `${config.apiUrl}/${resource}?${qs.stringify(query)}`;
105
145
 
106
- return httpClient(url, options).then(({ headers, json }) => {
146
+ return config.httpClient(url, options).then(({ headers, json }) => {
107
147
  if (!headers.has('content-range')) {
108
148
  throw new Error(
109
149
  `The Content-Range header is missing in the HTTP Response. The postgREST data provider expects
@@ -112,7 +152,7 @@ export default (
112
152
  );
113
153
  }
114
154
  return {
115
- data: json.map(obj => dataWithId(obj, primaryKey)),
155
+ data: json.map(obj => dataWithVirtualId(obj, primaryKey)),
116
156
  total: parseInt(
117
157
  headers.get('content-range').split('/').pop(),
118
158
  10
@@ -123,32 +163,44 @@ export default (
123
163
 
124
164
  getOne: (resource, params: Partial<GetOneParams> = {}) => {
125
165
  const { id, meta } = params;
126
- const primaryKey = getPrimaryKey(resource, primaryKeys);
166
+ const primaryKey = getPrimaryKey(resource, config.primaryKeys);
127
167
 
128
168
  const query = getQuery(primaryKey, id, resource, meta);
129
169
 
130
- const url = `${apiUrl}/${resource}?${qs.stringify(query)}`;
170
+ const url = `${config.apiUrl}/${resource}?${qs.stringify(query)}`;
171
+ const metaSchema = params.meta?.schema;
131
172
 
132
- return httpClient(url, {
133
- headers: new Headers({
134
- accept: 'application/vnd.pgrst.object+json',
135
- ...(params.meta?.headers || {}),
136
- }),
137
- }).then(({ json }) => ({
138
- data: dataWithId(json, primaryKey),
139
- }));
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
+ }));
140
184
  },
141
185
 
142
186
  getMany: (resource, params: Partial<GetManyParams> = {}) => {
143
187
  const ids = params.ids;
144
- const primaryKey = getPrimaryKey(resource, primaryKeys);
188
+ const primaryKey = getPrimaryKey(resource, config.primaryKeys);
145
189
 
146
190
  const query = getQuery(primaryKey, ids, resource, params.meta);
147
- const url = `${apiUrl}/${resource}?${qs.stringify(query)}`;
148
191
 
149
- return httpClient(url).then(({ json }) => ({
150
- data: json.map(data => dataWithId(data, primaryKey)),
151
- }));
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
+ }));
152
204
  },
153
205
 
154
206
  getManyReference: (
@@ -158,8 +210,10 @@ export default (
158
210
  const { page, perPage } = params.pagination;
159
211
  const { field, order } = params.sort;
160
212
 
161
- const { filter, select } = parseFilters(params, defaultListOp);
162
- const primaryKey = getPrimaryKey(resource, primaryKeys);
213
+ const { filter, select } = parseFilters(params, config.defaultListOp);
214
+ const primaryKey = getPrimaryKey(resource, config.primaryKeys);
215
+
216
+ const metaSchema = params.meta?.schema;
163
217
 
164
218
  let query = params.target
165
219
  ? {
@@ -186,12 +240,13 @@ export default (
186
240
  Accept: 'application/json',
187
241
  Prefer: 'count=exact',
188
242
  ...(params.meta?.headers || {}),
243
+ ...useCustomSchema(config.schema, metaSchema, 'GET'),
189
244
  }),
190
245
  };
191
246
 
192
- const url = `${apiUrl}/${resource}?${qs.stringify(query)}`;
247
+ const url = `${config.apiUrl}/${resource}?${qs.stringify(query)}`;
193
248
 
194
- return httpClient(url, options).then(({ headers, json }) => {
249
+ return config.httpClient(url, options).then(({ headers, json }) => {
195
250
  if (!headers.has('content-range')) {
196
251
  throw new Error(
197
252
  `The Content-Range header is missing in the HTTP Response. The postgREST data provider expects
@@ -200,7 +255,7 @@ export default (
200
255
  );
201
256
  }
202
257
  return {
203
- data: json.map(data => dataWithId(data, primaryKey)),
258
+ data: json.map(data => dataWithVirtualId(data, primaryKey)),
204
259
  total: parseInt(
205
260
  headers.get('content-range').split('/').pop(),
206
261
  10
@@ -211,128 +266,143 @@ export default (
211
266
 
212
267
  update: (resource, params: Partial<UpdateParams> = {}) => {
213
268
  const { id, data, meta } = params;
214
- const primaryKey = getPrimaryKey(resource, primaryKeys);
215
-
269
+ const primaryKey = getPrimaryKey(resource, config.primaryKeys);
216
270
  const query = getQuery(primaryKey, id, resource, meta);
217
-
218
- const primaryKeyData = getKeyData(primaryKey, data);
219
-
220
- const url = `${apiUrl}/${resource}?${qs.stringify(query)}`;
271
+ const url = `${config.apiUrl}/${resource}?${qs.stringify(query)}`;
272
+ const metaSchema = params.meta?.schema;
221
273
 
222
274
  const body = JSON.stringify({
223
- ...data,
224
- ...primaryKeyData,
275
+ ...dataWithoutVirtualId(
276
+ removePrimaryKey(data, primaryKey),
277
+ primaryKey
278
+ ),
225
279
  });
226
280
 
227
- return httpClient(url, {
228
- method: 'PATCH',
229
- headers: new Headers({
230
- Accept: 'application/vnd.pgrst.object+json',
231
- Prefer: 'return=representation',
232
- 'Content-Type': 'application/json',
233
- ...(params.meta?.headers || {}),
234
- }),
235
- body,
236
- }).then(({ json }) => ({ data: dataWithId(json, primaryKey) }));
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
+ }));
237
296
  },
238
297
 
239
298
  updateMany: (resource, params: Partial<UpdateManyParams> = {}) => {
240
- const ids = params.ids;
241
- const primaryKey = getPrimaryKey(resource, primaryKeys);
242
-
243
- const body = JSON.stringify(
244
- params.ids.map(id => {
245
- const { data } = params;
246
- const primaryKeyParams = decodeId(id, primaryKey);
247
-
248
- const primaryKeyData = {};
249
- if (isCompoundKey(primaryKey)) {
250
- primaryKey.forEach((key, index) => {
251
- primaryKeyData[key] = primaryKeyParams[index];
252
- });
253
- } else {
254
- primaryKeyData[primaryKey[0]] = primaryKeyParams[0];
255
- }
256
-
257
- return {
258
- ...data,
259
- ...primaryKeyData,
260
- };
261
- })
262
- );
263
-
264
- const url = `${apiUrl}/${resource}`;
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
+ });
265
309
 
266
- return httpClient(url, {
267
- method: 'PATCH',
268
- headers: new Headers({
269
- Prefer: 'return=representation',
270
- 'Content-Type': 'application/json',
271
- ...(params.meta?.headers || {}),
272
- }),
273
- body,
274
- }).then(({ json }) => ({
275
- data: json.map(data => encodeId(data, primaryKey)),
276
- }));
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
+ }));
277
326
  },
278
327
 
279
328
  create: (resource, params: Partial<CreateParams> = {}) => {
280
- const primaryKey = getPrimaryKey(resource, primaryKeys);
281
- const url = `${apiUrl}/${resource}`;
282
-
283
- return httpClient(url, {
284
- method: 'POST',
285
- headers: new Headers({
286
- Accept: 'application/vnd.pgrst.object+json',
287
- Prefer: 'return=representation',
288
- 'Content-Type': 'application/json',
289
- ...(params.meta?.headers || {}),
290
- }),
291
- body: JSON.stringify(params.data),
292
- }).then(({ json }) => ({
293
- data: {
294
- ...json,
295
- id: encodeId(json, primaryKey),
296
- },
297
- }));
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
+ }));
298
358
  },
299
359
 
300
360
  delete: (resource, params: Partial<DeleteParams> = {}) => {
301
361
  const { id, meta } = params;
302
- const primaryKey = getPrimaryKey(resource, primaryKeys);
362
+ const primaryKey = getPrimaryKey(resource, config.primaryKeys);
303
363
 
304
364
  const query = getQuery(primaryKey, id, resource, meta);
305
365
 
306
- const url = `${apiUrl}/${resource}?${qs.stringify(query)}`;
307
-
308
- return httpClient(url, {
309
- method: 'DELETE',
310
- headers: new Headers({
311
- Accept: 'application/vnd.pgrst.object+json',
312
- Prefer: 'return=representation',
313
- 'Content-Type': 'application/json',
314
- ...(params.meta?.headers || {}),
315
- }),
316
- }).then(({ json }) => ({ data: dataWithId(json, primaryKey) }));
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
+ }));
317
383
  },
318
384
 
319
385
  deleteMany: (resource, params: Partial<DeleteManyParams> = {}) => {
320
386
  const { ids, meta } = params;
321
- const primaryKey = getPrimaryKey(resource, primaryKeys);
387
+ const primaryKey = getPrimaryKey(resource, config.primaryKeys);
322
388
 
323
389
  const query = getQuery(primaryKey, ids, resource, meta);
324
390
 
325
- const url = `${apiUrl}/${resource}?${qs.stringify(query)}`;
326
-
327
- return httpClient(url, {
328
- method: 'DELETE',
329
- headers: new Headers({
330
- Prefer: 'return=representation',
331
- 'Content-Type': 'application/json',
332
- ...(params.meta?.headers || {}),
333
- }),
334
- }).then(({ json }) => ({
335
- data: json.map(data => encodeId(data, primaryKey)),
336
- }));
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
+ }));
337
407
  },
338
408
  });
package/src/urlBuilder.ts CHANGED
@@ -49,6 +49,18 @@ type ParsedFiltersResult = {
49
49
  select: any;
50
50
  };
51
51
 
52
+ const isObject = obj =>
53
+ typeof obj === 'object' && !Array.isArray(obj) && obj !== null;
54
+
55
+ const resolveKeys = (filter: any, keys: Array<any>) => {
56
+ let result = filter[keys[0]];
57
+ for (let i = 1; i < keys.length; ++i) {
58
+ result = result[keys[i]];
59
+ }
60
+
61
+ return result;
62
+ };
63
+
52
64
  export type PostgRestOperator = (typeof postgrestOperators)[number];
53
65
 
54
66
  export const parseFilters = (
@@ -62,31 +74,81 @@ export const parseFilters = (
62
74
  Object.keys(filter).forEach(function (key) {
63
75
  // key: the name of the object key
64
76
 
77
+ let keyArray = [key];
78
+ let values: Array<string>;
79
+
80
+ // in case of a nested object selection in a text field
81
+ // RA returns object{ object } structure which is resolved here
82
+ // see issue https://github.com/raphiniert-com/ra-data-postgrest/issues/58
83
+ if (key.split('@')[0] !== '' && isObject(filter[key])) {
84
+ let innerVal = filter[key];
85
+
86
+ do {
87
+ const inner = resolveKeys(filter, keyArray);
88
+ const [innerKey] = Object.keys(inner);
89
+
90
+ keyArray.push(innerKey);
91
+ innerVal = inner[innerKey];
92
+ } while (isObject(innerVal));
93
+
94
+ key = keyArray.join('.');
95
+ values = [innerVal];
96
+ } else {
97
+ values = [filter[key]];
98
+ }
99
+
65
100
  const splitKey = key.split('@');
66
101
  const operation: PostgRestOperator =
67
102
  splitKey.length == 2
68
103
  ? (splitKey[1] as PostgRestOperator)
69
104
  : defaultListOp;
70
105
 
71
- let values: Array<string>;
72
106
  if (['like', 'ilike'].includes(operation)) {
73
107
  // we split the search term in words
74
- values = filter[key].trim().split(/\s+/);
75
- } else {
76
- values = [filter[key]];
108
+ values = resolveKeys(filter, keyArray).trim().split(/\s+/);
109
+ }
110
+ // CASE: Logical operator
111
+ else if (['or', 'and'].includes(operation)) {
112
+ // we extract each value entries and make it dot separated string.
113
+ const { filter: subFilter } = parseFilters(
114
+ { filter: resolveKeys(filter, keyArray) },
115
+ defaultListOp
116
+ );
117
+
118
+ // something like { "age@lt": 18 } and { "age@gt": 21 }
119
+ const filterExpressions: string[] = [];
120
+ Object.entries(subFilter).forEach(([op, val]) => {
121
+ if (Array.isArray(val))
122
+ filterExpressions.push(...val.map(v => [op, v].join('.')));
123
+ else filterExpressions.push([op, val].join('.'));
124
+ });
125
+
126
+ // finally we flatten all as single string and enclose with bracket.
127
+ values = [`(${filterExpressions.join(',')})`];
77
128
  }
78
129
 
79
130
  values.forEach(value => {
80
- // if operator is intentionally blank, rpc syntax
81
- let op = operation.includes('like')
82
- ? `${operation}.*${value}*`
83
- : operation.length == 0
84
- ? `${value}`
85
- : `${operation}.${value}`;
131
+ let op: string = (() => {
132
+ // if operator is intentionally blank, rpc syntax
133
+ if (operation.length === 0) return `${value}`;
134
+
135
+ if (operation.includes('like'))
136
+ return `${operation}.*${value}*`;
137
+ if (['and', 'or'].includes(operation)) return `${value}`;
138
+ if (['cs', 'cd'].includes(operation))
139
+ return `${operation}.{${value}}`;
140
+ return `${operation}.${value}`;
141
+ })();
86
142
 
143
+ // If resulting filter doesn't contain the fieldname, then add it.
87
144
  if (result.filter[splitKey[0]] === undefined) {
88
- // first operator for the key, we add it to the dict
89
- result.filter[splitKey[0]] = op;
145
+ // first operator for the key,
146
+ if (['and', 'or'].includes(operation)) {
147
+ result.filter[operation] = op;
148
+ } else {
149
+ // we add it to the dict
150
+ result.filter[splitKey[0]] = op;
151
+ }
90
152
  } else {
91
153
  if (!Array.isArray(result[splitKey[0]])) {
92
154
  // second operator, we transform to an array
@@ -140,8 +202,16 @@ export const encodeId = (data: any, primaryKey: PrimaryKey): Identifier => {
140
202
  }
141
203
  };
142
204
 
143
- export const dataWithId = (data: any, primaryKey: PrimaryKey) => {
144
- if (JSON.stringify(primaryKey) === JSON.stringify(['id'])) {
205
+ export const removePrimaryKey = (data: any, primaryKey: PrimaryKey) => {
206
+ const newData = { ...data };
207
+ primaryKey.forEach(key => {
208
+ delete newData[key];
209
+ });
210
+ return newData;
211
+ };
212
+
213
+ export const dataWithVirtualId = (data: any, primaryKey: PrimaryKey) => {
214
+ if (primaryKey.length === 1 && primaryKey[0] === 'id') {
145
215
  return data;
146
216
  }
147
217
 
@@ -150,13 +220,22 @@ export const dataWithId = (data: any, primaryKey: PrimaryKey) => {
150
220
  });
151
221
  };
152
222
 
153
- export const isCompoundKey = (primaryKey: PrimaryKey): Boolean => {
223
+ export const dataWithoutVirtualId = (data: any, primaryKey: PrimaryKey) => {
224
+ if (primaryKey.length === 1 && primaryKey[0] === 'id') {
225
+ return data;
226
+ }
227
+
228
+ const { id, ...dataWithoutId } = data;
229
+ return dataWithoutId;
230
+ };
231
+
232
+ const isCompoundKey = (primaryKey: PrimaryKey): Boolean => {
154
233
  return primaryKey.length > 1;
155
234
  };
156
235
 
157
236
  export const getQuery = (
158
237
  primaryKey: PrimaryKey,
159
- ids: Identifier | Array<Identifier>,
238
+ ids: Identifier | Array<Identifier> | undefined,
160
239
  resource: string,
161
240
  meta: any = null
162
241
  ): any => {
@@ -185,7 +264,7 @@ export const getQuery = (
185
264
  [primaryKey[0]]: `in.(${ids.join(',')})`,
186
265
  };
187
266
  }
188
- } else {
267
+ } else if (ids) {
189
268
  // if ids is one Identifier
190
269
  const id: Identifier = ids.toString();
191
270
  const primaryKeyParams = decodeId(id, primaryKey);
@@ -221,20 +300,6 @@ export const getQuery = (
221
300
  return result;
222
301
  };
223
302
 
224
- export const getKeyData = (primaryKey: PrimaryKey, data: object): object => {
225
- if (isCompoundKey(primaryKey)) {
226
- return primaryKey.reduce(
227
- (keyData, key) => ({
228
- ...keyData,
229
- [key]: data[key],
230
- }),
231
- {}
232
- );
233
- } else {
234
- return { [primaryKey[0]]: data[primaryKey[0]] };
235
- }
236
- };
237
-
238
303
  export const getOrderBy = (
239
304
  field: string,
240
305
  order: string,