@raphiniert/ra-data-postgrest 2.4.0 → 2.5.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
@@ -24,7 +24,7 @@ import {
24
24
  PostgRestSortOrder,
25
25
  } from './urlBuilder';
26
26
  import qs from 'qs';
27
- import isEqual from 'lodash/isEqual';
27
+ import { isDeepStrictEqual as isEqual } from 'node:util';
28
28
 
29
29
  /**
30
30
  * Maps react-admin queries to a postgrest REST API
@@ -186,12 +186,15 @@ export default (config: IDataProviderConfig): DataProvider => ({
186
186
  the pagination. If you are using CORS, did you declare Content-Range in the Access-Control-Expose-Headers header?`
187
187
  );
188
188
  }
189
+ const data = json.map(obj => dataWithVirtualId(obj, primaryKey));
190
+ const prefetched = getPrefetchedData(data, params.meta?.prefetch);
189
191
  return {
190
- data: json.map(obj => dataWithVirtualId(obj, primaryKey)),
192
+ data: removePrefetchedData(data, params.meta?.prefetch),
191
193
  total: parseInt(
192
194
  headers.get('content-range').split('/').pop(),
193
195
  10
194
196
  ),
197
+ meta: params.meta?.prefetch ? { prefetched } : undefined,
195
198
  };
196
199
  });
197
200
  },
@@ -213,9 +216,17 @@ export default (config: IDataProviderConfig): DataProvider => ({
213
216
  ...useCustomSchema(config.schema, metaSchema, 'GET'),
214
217
  }),
215
218
  })
216
- .then(({ json }) => ({
217
- data: dataWithVirtualId(json, primaryKey),
218
- }));
219
+ .then(({ json }) => {
220
+ const data = dataWithVirtualId(json, primaryKey);
221
+ const prefetched = getPrefetchedData(
222
+ data,
223
+ params.meta?.prefetch
224
+ );
225
+ return {
226
+ data: removePrefetchedData(data, params.meta?.prefetch),
227
+ meta: params.meta?.prefetch ? { prefetched } : undefined,
228
+ };
229
+ });
219
230
  },
220
231
 
221
232
  getMany: (resource, params: Partial<GetManyParams> = {}) => {
@@ -236,9 +247,19 @@ export default (config: IDataProviderConfig): DataProvider => ({
236
247
  ...useCustomSchema(config.schema, metaSchema, 'GET'),
237
248
  }),
238
249
  })
239
- .then(({ json }) => ({
240
- data: json.map(data => dataWithVirtualId(data, primaryKey)),
241
- }));
250
+ .then(({ json }) => {
251
+ const data = json.map(data =>
252
+ dataWithVirtualId(data, primaryKey)
253
+ );
254
+ const prefetched = getPrefetchedData(
255
+ data,
256
+ params.meta?.prefetch
257
+ );
258
+ return {
259
+ data: removePrefetchedData(data, params.meta?.prefetch),
260
+ meta: params.meta?.prefetch ? { prefetched } : undefined,
261
+ };
262
+ });
242
263
  },
243
264
 
244
265
  getManyReference: (
@@ -292,12 +313,15 @@ export default (config: IDataProviderConfig): DataProvider => ({
292
313
  the pagination. If you are using CORS, did you declare Content-Range in the Access-Control-Expose-Headers header?`
293
314
  );
294
315
  }
316
+ const data = json.map(data => dataWithVirtualId(data, primaryKey));
317
+ const prefetched = getPrefetchedData(data, params.meta?.prefetch);
295
318
  return {
296
- data: json.map(data => dataWithVirtualId(data, primaryKey)),
319
+ data: removePrefetchedData(data, params.meta?.prefetch),
297
320
  total: parseInt(
298
321
  headers.get('content-range').split('/').pop(),
299
322
  10
300
323
  ),
324
+ meta: params.meta?.prefetch ? { prefetched } : undefined,
301
325
  };
302
326
  });
303
327
  },
@@ -332,9 +356,17 @@ export default (config: IDataProviderConfig): DataProvider => ({
332
356
  }),
333
357
  body,
334
358
  })
335
- .then(({ json }) => ({
336
- data: dataWithVirtualId(json, primaryKey),
337
- }));
359
+ .then(({ json }) => {
360
+ const data = dataWithVirtualId(json, primaryKey);
361
+ const prefetched = getPrefetchedData(
362
+ data,
363
+ params.meta?.prefetch
364
+ );
365
+ return {
366
+ data: removePrefetchedData(data, params.meta?.prefetch),
367
+ meta: params.meta?.prefetch ? { prefetched } : undefined,
368
+ };
369
+ });
338
370
  },
339
371
 
340
372
  updateMany: (resource, params: Partial<UpdateManyParams> = {}) => {
@@ -391,12 +423,20 @@ export default (config: IDataProviderConfig): DataProvider => ({
391
423
  dataWithoutVirtualId(params.data, primaryKey)
392
424
  ),
393
425
  })
394
- .then(({ json }) => ({
395
- data: {
426
+ .then(({ json }) => {
427
+ const data = {
396
428
  ...json,
397
429
  id: encodeId(json, primaryKey),
398
- },
399
- }));
430
+ };
431
+ const prefetched = getPrefetchedData(
432
+ data,
433
+ params.meta?.prefetch
434
+ );
435
+ return {
436
+ data: removePrefetchedData(data, params.meta?.prefetch),
437
+ meta: params.meta?.prefetch ? { prefetched } : undefined,
438
+ };
439
+ });
400
440
  },
401
441
 
402
442
  delete: (resource, params: Partial<DeleteParams> = {}) => {
@@ -419,9 +459,17 @@ export default (config: IDataProviderConfig): DataProvider => ({
419
459
  ...useCustomSchema(config.schema, metaSchema, 'DELETE'),
420
460
  }),
421
461
  })
422
- .then(({ json }) => ({
423
- data: dataWithVirtualId(json, primaryKey),
424
- }));
462
+ .then(({ json }) => {
463
+ const data = dataWithVirtualId(json, primaryKey);
464
+ const prefetched = getPrefetchedData(
465
+ data,
466
+ params.meta?.prefetch
467
+ );
468
+ return {
469
+ data: removePrefetchedData(data, params.meta?.prefetch),
470
+ meta: params.meta?.prefetch ? { prefetched } : undefined,
471
+ };
472
+ });
425
473
  },
426
474
 
427
475
  deleteMany: (resource, params: Partial<DeleteManyParams> = {}) => {
@@ -458,3 +506,76 @@ const getChanges = (data: any, previousData: any) => {
458
506
  }, {});
459
507
  return changes;
460
508
  };
509
+
510
+ /**
511
+ * Extract embeds from Postgrest responses
512
+ *
513
+ * When calling Postgrest database.getOne('posts', 123, { embed: 'tags' }),
514
+ * the Postgrest response adds a `tags` key to the response, containing the
515
+ * related tags. Something like:
516
+ *
517
+ * { id: 123, title: 'React-query in details', tags: [{ id: 1, name: 'react' }, { id: 1, name: 'query' }] }
518
+ *
519
+ * We want to copy all the embeds in a data object, that will later
520
+ * be included into the response meta.prefetched key.
521
+ *
522
+ * @example getPrefetchedData({ id: 123, title: 'React-query in details', tags: [{ id: 1, name: 'react' }, { id: 1, name: 'query' }] }, ['tags'])
523
+ * // {
524
+ * // tags: [{ id: 1, name: 'react' }, { id: 1, name: 'query' }]
525
+ * // }
526
+ */
527
+ const getPrefetchedData = (data, prefetchParam?: string[]) => {
528
+ if (!prefetchParam) return undefined;
529
+ const prefetched = {};
530
+ const dataArray = Array.isArray(data) ? data : [data];
531
+ prefetchParam.forEach(resource => {
532
+ dataArray.forEach(record => {
533
+ if (!prefetched[resource]) {
534
+ prefetched[resource] = [];
535
+ }
536
+ const prefetchedData = Array.isArray(record[resource])
537
+ ? record[resource]
538
+ : [record[resource]];
539
+ prefetchedData.forEach(prefetchedRecord => {
540
+ if (
541
+ prefetched[resource].some(r => r.id === prefetchedRecord.id)
542
+ ) {
543
+ // do not add the record if it's already there
544
+ return;
545
+ }
546
+ prefetched[resource].push(prefetchedRecord);
547
+ });
548
+ });
549
+ });
550
+
551
+ return prefetched;
552
+ };
553
+
554
+ /**
555
+ * Remove embeds from Postgrest responses
556
+ *
557
+ * When calling Postgrest database.getOne('posts', 123, { embed: 'tags' }),
558
+ * the Postgrest response adds a `post` key to the response, containing the
559
+ * related post. Something like:
560
+ *
561
+ * { id: 123, title: 'React-query in details', tags: [{ id: 1, name: 'react' }, { id: 1, name: 'query' }] }
562
+ *
563
+ * We want to remove all the embeds from the response.
564
+ *
565
+ * @example removePrefetchedData({ id: 123, title: 'React-query in details', tags: [{ id: 1, name: 'react' }, { id: 1, name: 'query' }] }, 'tags')
566
+ * // { id: 123, title: 'React-query in details' }
567
+ */
568
+ const removePrefetchedData = (data, prefetchParam?: string[]) => {
569
+ if (!prefetchParam) return data;
570
+ const dataArray = Array.isArray(data) ? data : [data];
571
+ const newDataArray = dataArray.map(record => {
572
+ const newRecord = {};
573
+ for (const key in record) {
574
+ if (!prefetchParam.includes(key)) {
575
+ newRecord[key] = record[key];
576
+ }
577
+ }
578
+ return newRecord;
579
+ });
580
+ return Array.isArray(data) ? newDataArray : newDataArray[0];
581
+ };
package/src/urlBuilder.ts CHANGED
@@ -168,6 +168,15 @@ export const parseFilters = (
168
168
  : meta.columns;
169
169
  }
170
170
 
171
+ if (meta?.embed || meta?.prefetch) {
172
+ const columns = getColumnsParam(meta.embed, meta.prefetch);
173
+ if (columns) {
174
+ result.select = result.select
175
+ ? `${result.select},${columns.join(',')}`
176
+ : `*,${columns.join(',')}`;
177
+ }
178
+ }
179
+
171
180
  return result;
172
181
  };
173
182
 
@@ -295,6 +304,16 @@ export const getQuery = (
295
304
  : meta.columns;
296
305
  }
297
306
 
307
+ if (meta?.embed || meta?.prefetch) {
308
+ const columns = getColumnsParam(meta.embed, meta.prefetch);
309
+ if (columns) {
310
+ result.select = result.select
311
+ ? `${result.select},${columns.join(',')}`
312
+ : // if users did not specify any columns, we must add the wildcard select to not get only the embeds/prefetch
313
+ `*,${columns.join(',')}`;
314
+ }
315
+ }
316
+
298
317
  return result;
299
318
  };
300
319
 
@@ -318,3 +337,14 @@ export const getOrderBy = (
318
337
  return `${field}.${postgRestOrder}`;
319
338
  }
320
339
  };
340
+
341
+ /**
342
+ * Compute the columns parameter for the embed and prefetch query meta properties
343
+ */
344
+ const getColumnsParam = (embed: string[], prefetch: string[]) => {
345
+ if (!embed && !prefetch) return;
346
+ const param = new Set<string>();
347
+ if (embed) embed.forEach(resource => param.add(`${resource}(*)`));
348
+ if (prefetch) prefetch.forEach(resource => param.add(`${resource}(*)`));
349
+ return Array.from(param);
350
+ };