@raphiniert/ra-data-postgrest 2.0.0-alpha.1 → 2.0.0-alpha.3
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/CHANGELOG.md +38 -0
- package/IDEAS.md +46 -0
- package/README.md +50 -17
- package/esm/index.d.ts +58 -7
- package/esm/index.js +226 -219
- package/esm/urlBuilder.js +37 -8
- package/lib/index.d.ts +58 -7
- package/lib/index.js +228 -219
- package/lib/urlBuilder.js +37 -8
- package/package.json +5 -4
- package/src/index.ts +180 -103
- package/src/urlBuilder.ts +38 -8
package/src/index.ts
CHANGED
|
@@ -48,13 +48,24 @@ import qs from 'qs';
|
|
|
48
48
|
* @example
|
|
49
49
|
*
|
|
50
50
|
* import * as React from 'react';
|
|
51
|
-
* import { Admin, Resource } from 'react-admin';
|
|
52
|
-
* import postgrestRestProvider
|
|
51
|
+
* import { Admin, Resource, fetchUtils } from 'react-admin';
|
|
52
|
+
* import postgrestRestProvider,
|
|
53
|
+
* { IDataProviderConfig,
|
|
54
|
+
* defaultPrimaryKeys,
|
|
55
|
+
* defaultSchema } from '@raphiniert/ra-data-postgrest';
|
|
53
56
|
*
|
|
54
57
|
* import { PostList } from './posts';
|
|
55
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
|
+
*
|
|
56
67
|
* const App = () => (
|
|
57
|
-
* <Admin dataProvider={postgrestRestProvider(
|
|
68
|
+
* <Admin dataProvider={postgrestRestProvider(config)}>
|
|
58
69
|
* <Resource name="posts" list={PostList} />
|
|
59
70
|
* </Admin>
|
|
60
71
|
* );
|
|
@@ -62,20 +73,50 @@ import qs from 'qs';
|
|
|
62
73
|
* export default App;
|
|
63
74
|
*/
|
|
64
75
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
export
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
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
|
+
primaryKeys: Map<string, PrimaryKey>;
|
|
87
|
+
schema: () => string;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const useCustomSchema = (
|
|
91
|
+
schema: () => string,
|
|
92
|
+
metaSchema: MetaOption,
|
|
93
|
+
method: string
|
|
94
|
+
) => {
|
|
95
|
+
let funcHeaderSchema = schema;
|
|
96
|
+
if (metaSchema !== undefined) {
|
|
97
|
+
funcHeaderSchema = () => metaSchema;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (funcHeaderSchema().length > 0) {
|
|
101
|
+
let schemaHeader = '';
|
|
102
|
+
if (['GET', 'HEAD'].includes(method)) {
|
|
103
|
+
schemaHeader = 'Accept-Profile';
|
|
104
|
+
} else if (['POST', 'PATCH', 'PUT', 'DELETE'].includes(method)) {
|
|
105
|
+
schemaHeader = 'Content-Profile';
|
|
106
|
+
} else return {};
|
|
107
|
+
|
|
108
|
+
return { [schemaHeader]: funcHeaderSchema() };
|
|
109
|
+
} else return {};
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
export default (config: IDataProviderConfig): DataProvider => ({
|
|
73
113
|
getList: (resource, params: Partial<GetListParams> = {}) => {
|
|
74
|
-
const primaryKey = getPrimaryKey(resource, primaryKeys);
|
|
114
|
+
const primaryKey = getPrimaryKey(resource, config.primaryKeys);
|
|
75
115
|
|
|
76
116
|
const { page, perPage } = params.pagination;
|
|
77
117
|
const { field, order } = params.sort || {};
|
|
78
|
-
const { filter, select } = parseFilters(params, defaultListOp);
|
|
118
|
+
const { filter, select } = parseFilters(params, config.defaultListOp);
|
|
119
|
+
const metaSchema = params.meta?.schema;
|
|
79
120
|
|
|
80
121
|
let query = {
|
|
81
122
|
offset: String((page - 1) * perPage),
|
|
@@ -98,12 +139,13 @@ export default (
|
|
|
98
139
|
Accept: 'application/json',
|
|
99
140
|
Prefer: 'count=exact',
|
|
100
141
|
...(params.meta?.headers || {}),
|
|
142
|
+
...useCustomSchema(config.schema, metaSchema, 'GET'),
|
|
101
143
|
}),
|
|
102
144
|
};
|
|
103
145
|
|
|
104
|
-
const url = `${apiUrl}/${resource}?${qs.stringify(query)}`;
|
|
146
|
+
const url = `${config.apiUrl}/${resource}?${qs.stringify(query)}`;
|
|
105
147
|
|
|
106
|
-
return httpClient(url, options).then(({ headers, json }) => {
|
|
148
|
+
return config.httpClient(url, options).then(({ headers, json }) => {
|
|
107
149
|
if (!headers.has('content-range')) {
|
|
108
150
|
throw new Error(
|
|
109
151
|
`The Content-Range header is missing in the HTTP Response. The postgREST data provider expects
|
|
@@ -123,32 +165,44 @@ export default (
|
|
|
123
165
|
|
|
124
166
|
getOne: (resource, params: Partial<GetOneParams> = {}) => {
|
|
125
167
|
const { id, meta } = params;
|
|
126
|
-
const primaryKey = getPrimaryKey(resource, primaryKeys);
|
|
168
|
+
const primaryKey = getPrimaryKey(resource, config.primaryKeys);
|
|
127
169
|
|
|
128
170
|
const query = getQuery(primaryKey, id, resource, meta);
|
|
129
171
|
|
|
130
|
-
const url = `${apiUrl}/${resource}?${qs.stringify(query)}`;
|
|
172
|
+
const url = `${config.apiUrl}/${resource}?${qs.stringify(query)}`;
|
|
173
|
+
const metaSchema = params.meta?.schema;
|
|
131
174
|
|
|
132
|
-
return
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
175
|
+
return config
|
|
176
|
+
.httpClient(url, {
|
|
177
|
+
headers: new Headers({
|
|
178
|
+
accept: 'application/vnd.pgrst.object+json',
|
|
179
|
+
...(params.meta?.headers || {}),
|
|
180
|
+
...useCustomSchema(config.schema, metaSchema, 'GET'),
|
|
181
|
+
}),
|
|
182
|
+
})
|
|
183
|
+
.then(({ json }) => ({
|
|
184
|
+
data: dataWithId(json, primaryKey),
|
|
185
|
+
}));
|
|
140
186
|
},
|
|
141
187
|
|
|
142
188
|
getMany: (resource, params: Partial<GetManyParams> = {}) => {
|
|
143
189
|
const ids = params.ids;
|
|
144
|
-
const primaryKey = getPrimaryKey(resource, primaryKeys);
|
|
190
|
+
const primaryKey = getPrimaryKey(resource, config.primaryKeys);
|
|
145
191
|
|
|
146
192
|
const query = getQuery(primaryKey, ids, resource, params.meta);
|
|
147
|
-
const url = `${apiUrl}/${resource}?${qs.stringify(query)}`;
|
|
148
193
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
194
|
+
const url = `${config.apiUrl}/${resource}?${qs.stringify(query)}`;
|
|
195
|
+
const metaSchema = params.meta?.schema;
|
|
196
|
+
|
|
197
|
+
return config
|
|
198
|
+
.httpClient(url, {
|
|
199
|
+
headers: new Headers({
|
|
200
|
+
...useCustomSchema(config.schema, metaSchema, 'GET'),
|
|
201
|
+
}),
|
|
202
|
+
})
|
|
203
|
+
.then(({ json }) => ({
|
|
204
|
+
data: json.map(data => dataWithId(data, primaryKey)),
|
|
205
|
+
}));
|
|
152
206
|
},
|
|
153
207
|
|
|
154
208
|
getManyReference: (
|
|
@@ -158,8 +212,10 @@ export default (
|
|
|
158
212
|
const { page, perPage } = params.pagination;
|
|
159
213
|
const { field, order } = params.sort;
|
|
160
214
|
|
|
161
|
-
const { filter, select } = parseFilters(params, defaultListOp);
|
|
162
|
-
const primaryKey = getPrimaryKey(resource, primaryKeys);
|
|
215
|
+
const { filter, select } = parseFilters(params, config.defaultListOp);
|
|
216
|
+
const primaryKey = getPrimaryKey(resource, config.primaryKeys);
|
|
217
|
+
|
|
218
|
+
const metaSchema = params.meta?.schema;
|
|
163
219
|
|
|
164
220
|
let query = params.target
|
|
165
221
|
? {
|
|
@@ -186,12 +242,13 @@ export default (
|
|
|
186
242
|
Accept: 'application/json',
|
|
187
243
|
Prefer: 'count=exact',
|
|
188
244
|
...(params.meta?.headers || {}),
|
|
245
|
+
...useCustomSchema(config.schema, metaSchema, 'GET'),
|
|
189
246
|
}),
|
|
190
247
|
};
|
|
191
248
|
|
|
192
|
-
const url = `${apiUrl}/${resource}?${qs.stringify(query)}`;
|
|
249
|
+
const url = `${config.apiUrl}/${resource}?${qs.stringify(query)}`;
|
|
193
250
|
|
|
194
|
-
return httpClient(url, options).then(({ headers, json }) => {
|
|
251
|
+
return config.httpClient(url, options).then(({ headers, json }) => {
|
|
195
252
|
if (!headers.has('content-range')) {
|
|
196
253
|
throw new Error(
|
|
197
254
|
`The Content-Range header is missing in the HTTP Response. The postgREST data provider expects
|
|
@@ -211,34 +268,38 @@ export default (
|
|
|
211
268
|
|
|
212
269
|
update: (resource, params: Partial<UpdateParams> = {}) => {
|
|
213
270
|
const { id, data, meta } = params;
|
|
214
|
-
const primaryKey = getPrimaryKey(resource, primaryKeys);
|
|
271
|
+
const primaryKey = getPrimaryKey(resource, config.primaryKeys);
|
|
215
272
|
|
|
216
273
|
const query = getQuery(primaryKey, id, resource, meta);
|
|
217
274
|
|
|
218
275
|
const primaryKeyData = getKeyData(primaryKey, data);
|
|
219
276
|
|
|
220
|
-
const url = `${apiUrl}/${resource}?${qs.stringify(query)}`;
|
|
277
|
+
const url = `${config.apiUrl}/${resource}?${qs.stringify(query)}`;
|
|
278
|
+
const metaSchema = params.meta?.schema;
|
|
221
279
|
|
|
222
280
|
const body = JSON.stringify({
|
|
223
281
|
...data,
|
|
224
282
|
...primaryKeyData,
|
|
225
283
|
});
|
|
226
284
|
|
|
227
|
-
return
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
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 }) => ({ data: dataWithId(json, primaryKey) }));
|
|
237
298
|
},
|
|
238
299
|
|
|
239
300
|
updateMany: (resource, params: Partial<UpdateManyParams> = {}) => {
|
|
240
301
|
const ids = params.ids;
|
|
241
|
-
const primaryKey = getPrimaryKey(resource, primaryKeys);
|
|
302
|
+
const primaryKey = getPrimaryKey(resource, config.primaryKeys);
|
|
242
303
|
|
|
243
304
|
const body = JSON.stringify(
|
|
244
305
|
params.ids.map(id => {
|
|
@@ -261,78 +322,94 @@ export default (
|
|
|
261
322
|
})
|
|
262
323
|
);
|
|
263
324
|
|
|
264
|
-
const url = `${apiUrl}/${resource}`;
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
325
|
+
const url = `${config.apiUrl}/${resource}`;
|
|
326
|
+
const metaSchema = params.meta?.schema;
|
|
327
|
+
|
|
328
|
+
return config
|
|
329
|
+
.httpClient(url, {
|
|
330
|
+
method: 'PATCH',
|
|
331
|
+
headers: new Headers({
|
|
332
|
+
Prefer: 'return=representation',
|
|
333
|
+
'Content-Type': 'application/json',
|
|
334
|
+
...(params.meta?.headers || {}),
|
|
335
|
+
...useCustomSchema(config.schema, metaSchema, 'PATCH'),
|
|
336
|
+
}),
|
|
337
|
+
body,
|
|
338
|
+
})
|
|
339
|
+
.then(({ json }) => ({
|
|
340
|
+
data: json.map(data => encodeId(data, primaryKey)),
|
|
341
|
+
}));
|
|
277
342
|
},
|
|
278
343
|
|
|
279
344
|
create: (resource, params: Partial<CreateParams> = {}) => {
|
|
280
|
-
const primaryKey = getPrimaryKey(resource, primaryKeys);
|
|
281
|
-
const url = `${apiUrl}/${resource}`;
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
}
|
|
297
|
-
|
|
345
|
+
const primaryKey = getPrimaryKey(resource, config.primaryKeys);
|
|
346
|
+
const url = `${config.apiUrl}/${resource}`;
|
|
347
|
+
const metaSchema = params.meta?.schema;
|
|
348
|
+
|
|
349
|
+
return config
|
|
350
|
+
.httpClient(url, {
|
|
351
|
+
method: 'POST',
|
|
352
|
+
headers: new Headers({
|
|
353
|
+
Accept: 'application/vnd.pgrst.object+json',
|
|
354
|
+
Prefer: 'return=representation',
|
|
355
|
+
'Content-Type': 'application/json',
|
|
356
|
+
...(params.meta?.headers || {}),
|
|
357
|
+
...useCustomSchema(config.schema, metaSchema, 'POST'),
|
|
358
|
+
}),
|
|
359
|
+
body: JSON.stringify(params.data),
|
|
360
|
+
})
|
|
361
|
+
.then(({ json }) => ({
|
|
362
|
+
data: {
|
|
363
|
+
...json,
|
|
364
|
+
id: encodeId(json, primaryKey),
|
|
365
|
+
},
|
|
366
|
+
}));
|
|
298
367
|
},
|
|
299
368
|
|
|
300
369
|
delete: (resource, params: Partial<DeleteParams> = {}) => {
|
|
301
370
|
const { id, meta } = params;
|
|
302
|
-
const primaryKey = getPrimaryKey(resource, primaryKeys);
|
|
371
|
+
const primaryKey = getPrimaryKey(resource, config.primaryKeys);
|
|
303
372
|
|
|
304
373
|
const query = getQuery(primaryKey, id, resource, meta);
|
|
305
374
|
|
|
306
|
-
const url = `${apiUrl}/${resource}?${qs.stringify(query)}`;
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
375
|
+
const url = `${config.apiUrl}/${resource}?${qs.stringify(query)}`;
|
|
376
|
+
const metaSchema = params.meta?.schema;
|
|
377
|
+
|
|
378
|
+
return config
|
|
379
|
+
.httpClient(url, {
|
|
380
|
+
method: 'DELETE',
|
|
381
|
+
headers: new Headers({
|
|
382
|
+
Accept: 'application/vnd.pgrst.object+json',
|
|
383
|
+
Prefer: 'return=representation',
|
|
384
|
+
'Content-Type': 'application/json',
|
|
385
|
+
...(params.meta?.headers || {}),
|
|
386
|
+
...useCustomSchema(config.schema, metaSchema, 'DELETE'),
|
|
387
|
+
}),
|
|
388
|
+
})
|
|
389
|
+
.then(({ json }) => ({ data: dataWithId(json, primaryKey) }));
|
|
317
390
|
},
|
|
318
391
|
|
|
319
392
|
deleteMany: (resource, params: Partial<DeleteManyParams> = {}) => {
|
|
320
393
|
const { ids, meta } = params;
|
|
321
|
-
const primaryKey = getPrimaryKey(resource, primaryKeys);
|
|
394
|
+
const primaryKey = getPrimaryKey(resource, config.primaryKeys);
|
|
322
395
|
|
|
323
396
|
const query = getQuery(primaryKey, ids, resource, meta);
|
|
324
397
|
|
|
325
|
-
const url = `${apiUrl}/${resource}?${qs.stringify(query)}`;
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
398
|
+
const url = `${config.apiUrl}/${resource}?${qs.stringify(query)}`;
|
|
399
|
+
const metaSchema = params.meta?.schema;
|
|
400
|
+
|
|
401
|
+
return config
|
|
402
|
+
.httpClient(url, {
|
|
403
|
+
method: 'DELETE',
|
|
404
|
+
headers: new Headers({
|
|
405
|
+
Prefer: 'return=representation',
|
|
406
|
+
'Content-Type': 'application/json',
|
|
407
|
+
...(params.meta?.headers || {}),
|
|
408
|
+
...useCustomSchema(config.schema, metaSchema, 'DELETE'),
|
|
409
|
+
}),
|
|
410
|
+
})
|
|
411
|
+
.then(({ json }) => ({
|
|
412
|
+
data: json.map(data => encodeId(data, primaryKey)),
|
|
413
|
+
}));
|
|
337
414
|
},
|
|
338
415
|
});
|
package/src/urlBuilder.ts
CHANGED
|
@@ -72,21 +72,51 @@ export const parseFilters = (
|
|
|
72
72
|
if (['like', 'ilike'].includes(operation)) {
|
|
73
73
|
// we split the search term in words
|
|
74
74
|
values = filter[key].trim().split(/\s+/);
|
|
75
|
+
}
|
|
76
|
+
// CASE: Logical operator
|
|
77
|
+
else if (['or', 'and'].includes(operation)) {
|
|
78
|
+
// we extract each value entries and make it dot separated string.
|
|
79
|
+
const { filter: subFilter } = parseFilters(
|
|
80
|
+
{ filter: filter[key] },
|
|
81
|
+
defaultListOp
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
// something like { "age@lt": 18 } and { "age@gt": 21 }
|
|
85
|
+
const filterExpressions: string[] = [];
|
|
86
|
+
Object.entries(subFilter).forEach(([op, val]) => {
|
|
87
|
+
if (Array.isArray(val))
|
|
88
|
+
filterExpressions.push(...val.map(v => [op, v].join('.')));
|
|
89
|
+
else filterExpressions.push([op, val].join('.'));
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
// finally we flatten all as single string and enclose with bracket.
|
|
93
|
+
values = [`(${filterExpressions.join(',')})`];
|
|
75
94
|
} else {
|
|
76
95
|
values = [filter[key]];
|
|
77
96
|
}
|
|
78
97
|
|
|
79
98
|
values.forEach(value => {
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
: operation.length == 0
|
|
84
|
-
? `${value}`
|
|
85
|
-
: `${operation}.${value}`;
|
|
99
|
+
let op: string = (() => {
|
|
100
|
+
// if operator is intentionally blank, rpc syntax
|
|
101
|
+
if (operation.length === 0) return `${value}`;
|
|
86
102
|
|
|
103
|
+
if (operation.includes('like'))
|
|
104
|
+
return `${operation}.*${value}*`;
|
|
105
|
+
if (['and', 'or'].includes(operation)) return `${value}`;
|
|
106
|
+
if (['cs', 'cd'].includes(operation))
|
|
107
|
+
return `${operation}.{${value}}`;
|
|
108
|
+
return `${operation}.${value}`;
|
|
109
|
+
})();
|
|
110
|
+
|
|
111
|
+
// If resulting filter haven't contains the fieldname, then add it.
|
|
87
112
|
if (result.filter[splitKey[0]] === undefined) {
|
|
88
|
-
// first operator for the key,
|
|
89
|
-
|
|
113
|
+
// first operator for the key,
|
|
114
|
+
if (['and', 'or'].includes(operation)) {
|
|
115
|
+
result.filter[operation] = op;
|
|
116
|
+
} else {
|
|
117
|
+
// we add it to the dict
|
|
118
|
+
result.filter[splitKey[0]] = op;
|
|
119
|
+
}
|
|
90
120
|
} else {
|
|
91
121
|
if (!Array.isArray(result[splitKey[0]])) {
|
|
92
122
|
// second operator, we transform to an array
|