@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/CHANGELOG.md +236 -224
- package/README.md +247 -232
- package/esm/index.d.ts +2 -1
- package/esm/index.js +16 -5
- package/esm/urlBuilder.d.ts +7 -1
- package/esm/urlBuilder.js +12 -3
- package/lib/index.d.ts +2 -1
- package/lib/index.js +16 -5
- package/lib/urlBuilder.d.ts +7 -1
- package/lib/urlBuilder.js +13 -4
- package/package.json +51 -50
- package/src/index.ts +422 -408
- package/src/urlBuilder.ts +320 -311
package/src/urlBuilder.ts
CHANGED
|
@@ -1,311 +1,320 @@
|
|
|
1
|
-
import { Identifier } from 'ra-core';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Interface to clearly seperate the operator like < oder >
|
|
5
|
-
* from the name and the value of the filter
|
|
6
|
-
*/
|
|
7
|
-
interface PostgRestFilter {
|
|
8
|
-
name: string;
|
|
9
|
-
operator?: PostgRestOperator;
|
|
10
|
-
value: string;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* List of all possible operators in PostgRest
|
|
15
|
-
* (https://postgrest.org/en/stable/api.html#operators)
|
|
16
|
-
*/
|
|
17
|
-
const postgrestOperators = [
|
|
18
|
-
'eq',
|
|
19
|
-
'gt',
|
|
20
|
-
'gte',
|
|
21
|
-
'lt',
|
|
22
|
-
'lte',
|
|
23
|
-
'neq',
|
|
24
|
-
'like',
|
|
25
|
-
'ilike',
|
|
26
|
-
'match',
|
|
27
|
-
'imatch',
|
|
28
|
-
'in',
|
|
29
|
-
'is',
|
|
30
|
-
'fts',
|
|
31
|
-
'plfts',
|
|
32
|
-
'phfts',
|
|
33
|
-
'wfts',
|
|
34
|
-
'cs',
|
|
35
|
-
'cd',
|
|
36
|
-
'ov',
|
|
37
|
-
'sl',
|
|
38
|
-
'sr',
|
|
39
|
-
'nxr',
|
|
40
|
-
'nxl',
|
|
41
|
-
'adj',
|
|
42
|
-
'not',
|
|
43
|
-
'or',
|
|
44
|
-
'and',
|
|
45
|
-
] as const;
|
|
46
|
-
|
|
47
|
-
type ParsedFiltersResult = {
|
|
48
|
-
filter: any;
|
|
49
|
-
select: any;
|
|
50
|
-
};
|
|
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
|
-
|
|
64
|
-
export type PostgRestOperator = (typeof postgrestOperators)[number];
|
|
65
|
-
|
|
66
|
-
export const parseFilters = (
|
|
67
|
-
params: any,
|
|
68
|
-
defaultListOp: PostgRestOperator
|
|
69
|
-
): Partial<ParsedFiltersResult> => {
|
|
70
|
-
const { filter, meta = {} } = params;
|
|
71
|
-
|
|
72
|
-
let result: Partial<ParsedFiltersResult> = {};
|
|
73
|
-
result.filter = {};
|
|
74
|
-
Object.keys(filter).forEach(function (key) {
|
|
75
|
-
// key: the name of the object key
|
|
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
|
-
|
|
100
|
-
const splitKey = key.split('@');
|
|
101
|
-
const operation: PostgRestOperator =
|
|
102
|
-
splitKey.length == 2
|
|
103
|
-
? (splitKey[1] as PostgRestOperator)
|
|
104
|
-
: defaultListOp;
|
|
105
|
-
|
|
106
|
-
if (['like', 'ilike'].includes(operation)) {
|
|
107
|
-
// we split the search term in words
|
|
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(',')})`];
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
values.forEach(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
|
-
return `${operation}.${value}`;
|
|
139
|
-
})();
|
|
140
|
-
|
|
141
|
-
// If resulting filter doesn't contain the fieldname, then add it.
|
|
142
|
-
if (result.filter[splitKey[0]] === undefined) {
|
|
143
|
-
// first operator for the key,
|
|
144
|
-
if (['and', 'or'].includes(operation)) {
|
|
145
|
-
result.filter[operation] = op;
|
|
146
|
-
} else {
|
|
147
|
-
// we add it to the dict
|
|
148
|
-
result.filter[splitKey[0]] = op;
|
|
149
|
-
}
|
|
150
|
-
} else {
|
|
151
|
-
if (!Array.isArray(result[splitKey[0]])) {
|
|
152
|
-
// second operator, we transform to an array
|
|
153
|
-
result.filter[splitKey[0]] = [
|
|
154
|
-
result.filter[splitKey[0]],
|
|
155
|
-
op,
|
|
156
|
-
];
|
|
157
|
-
} else {
|
|
158
|
-
// third and subsequent, we add to array
|
|
159
|
-
result.filter[splitKey[0]].push(op);
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
});
|
|
163
|
-
});
|
|
164
|
-
|
|
165
|
-
if (meta?.columns) {
|
|
166
|
-
result.select = Array.isArray(meta.columns)
|
|
167
|
-
? meta.columns.join(',')
|
|
168
|
-
: meta.columns;
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
return result;
|
|
172
|
-
};
|
|
173
|
-
|
|
174
|
-
// compound keys capability
|
|
175
|
-
export type PrimaryKey = Array<string>;
|
|
176
|
-
|
|
177
|
-
export const getPrimaryKey = (
|
|
178
|
-
resource: string,
|
|
179
|
-
primaryKeys: Map<string, PrimaryKey>
|
|
180
|
-
) => {
|
|
181
|
-
return primaryKeys.get(resource) || ['id'];
|
|
182
|
-
};
|
|
183
|
-
|
|
184
|
-
export const decodeId = (
|
|
185
|
-
id: Identifier,
|
|
186
|
-
primaryKey: PrimaryKey
|
|
187
|
-
): string[] | number[] => {
|
|
188
|
-
if (isCompoundKey(primaryKey)) {
|
|
189
|
-
return JSON.parse(id.toString());
|
|
190
|
-
} else {
|
|
191
|
-
return [id.toString()];
|
|
192
|
-
}
|
|
193
|
-
};
|
|
194
|
-
|
|
195
|
-
export const encodeId = (data: any, primaryKey: PrimaryKey): Identifier => {
|
|
196
|
-
if (isCompoundKey(primaryKey)) {
|
|
197
|
-
return JSON.stringify(primaryKey.map(key => data[key]));
|
|
198
|
-
} else {
|
|
199
|
-
return data[primaryKey[0]];
|
|
200
|
-
}
|
|
201
|
-
};
|
|
202
|
-
|
|
203
|
-
export const removePrimaryKey = (data: any, primaryKey: PrimaryKey) => {
|
|
204
|
-
const newData = { ...data };
|
|
205
|
-
primaryKey.forEach(key => {
|
|
206
|
-
delete newData[key];
|
|
207
|
-
});
|
|
208
|
-
return newData;
|
|
209
|
-
};
|
|
210
|
-
|
|
211
|
-
export const dataWithVirtualId = (data: any, primaryKey: PrimaryKey) => {
|
|
212
|
-
if (primaryKey.length === 1 && primaryKey[0] === 'id') {
|
|
213
|
-
return data;
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
return Object.assign(data, {
|
|
217
|
-
id: encodeId(data, primaryKey),
|
|
218
|
-
});
|
|
219
|
-
};
|
|
220
|
-
|
|
221
|
-
export const dataWithoutVirtualId = (data: any, primaryKey: PrimaryKey) => {
|
|
222
|
-
if (primaryKey.length === 1 && primaryKey[0] === 'id') {
|
|
223
|
-
return data;
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
const { id, ...dataWithoutId } = data;
|
|
227
|
-
return dataWithoutId;
|
|
228
|
-
};
|
|
229
|
-
|
|
230
|
-
const isCompoundKey = (primaryKey: PrimaryKey): Boolean => {
|
|
231
|
-
return primaryKey.length > 1;
|
|
232
|
-
};
|
|
233
|
-
|
|
234
|
-
export const getQuery = (
|
|
235
|
-
primaryKey: PrimaryKey,
|
|
236
|
-
ids: Identifier | Array<Identifier> | undefined,
|
|
237
|
-
resource: string,
|
|
238
|
-
meta: any = null
|
|
239
|
-
): any => {
|
|
240
|
-
let result: any = {};
|
|
241
|
-
if (Array.isArray(ids) && ids.length > 1) {
|
|
242
|
-
// no standardized query with multiple ids possible for rpc endpoints which are api-exposed database functions
|
|
243
|
-
if (resource.startsWith('rpc/')) {
|
|
244
|
-
console.error(
|
|
245
|
-
"PostgREST's rpc endpoints are not intended to be handled as views. Therefore, no query generation for multiple key values implemented!"
|
|
246
|
-
);
|
|
247
|
-
|
|
248
|
-
return;
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
if (isCompoundKey(primaryKey)) {
|
|
252
|
-
result = {
|
|
253
|
-
or: `(${ids.map(id => {
|
|
254
|
-
const primaryKeyParams = decodeId(id, primaryKey);
|
|
255
|
-
return `and(${primaryKey
|
|
256
|
-
.map((key, i) => `${key}.eq.${primaryKeyParams[i]}`)
|
|
257
|
-
.join(',')})`;
|
|
258
|
-
})})`,
|
|
259
|
-
};
|
|
260
|
-
} else {
|
|
261
|
-
result = {
|
|
262
|
-
[primaryKey[0]]: `in.(${ids.join(',')})`,
|
|
263
|
-
};
|
|
264
|
-
}
|
|
265
|
-
} else if (ids) {
|
|
266
|
-
// if ids is one Identifier
|
|
267
|
-
const id: Identifier = ids.toString();
|
|
268
|
-
const primaryKeyParams = decodeId(id, primaryKey);
|
|
269
|
-
|
|
270
|
-
if (isCompoundKey(primaryKey)) {
|
|
271
|
-
if (resource.startsWith('rpc/')) {
|
|
272
|
-
result = {};
|
|
273
|
-
primaryKey.map(
|
|
274
|
-
(key: string, i: any) =>
|
|
275
|
-
(result[key] = `${primaryKeyParams[i]}`)
|
|
276
|
-
);
|
|
277
|
-
} else {
|
|
278
|
-
result = {
|
|
279
|
-
and: `(${primaryKey.map(
|
|
280
|
-
(key: string, i: any) =>
|
|
281
|
-
`${key}.eq.${primaryKeyParams[i]}`
|
|
282
|
-
)})`,
|
|
283
|
-
};
|
|
284
|
-
}
|
|
285
|
-
} else {
|
|
286
|
-
result = {
|
|
287
|
-
[primaryKey[0]]: `eq.${id}`,
|
|
288
|
-
};
|
|
289
|
-
}
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
if (meta && meta.columns) {
|
|
293
|
-
result.select = Array.isArray(meta.columns)
|
|
294
|
-
? meta.columns.join(',')
|
|
295
|
-
: meta.columns;
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
return result;
|
|
299
|
-
};
|
|
300
|
-
|
|
301
|
-
export
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
1
|
+
import { Identifier } from 'ra-core';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Interface to clearly seperate the operator like < oder >
|
|
5
|
+
* from the name and the value of the filter
|
|
6
|
+
*/
|
|
7
|
+
interface PostgRestFilter {
|
|
8
|
+
name: string;
|
|
9
|
+
operator?: PostgRestOperator;
|
|
10
|
+
value: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* List of all possible operators in PostgRest
|
|
15
|
+
* (https://postgrest.org/en/stable/api.html#operators)
|
|
16
|
+
*/
|
|
17
|
+
const postgrestOperators = [
|
|
18
|
+
'eq',
|
|
19
|
+
'gt',
|
|
20
|
+
'gte',
|
|
21
|
+
'lt',
|
|
22
|
+
'lte',
|
|
23
|
+
'neq',
|
|
24
|
+
'like',
|
|
25
|
+
'ilike',
|
|
26
|
+
'match',
|
|
27
|
+
'imatch',
|
|
28
|
+
'in',
|
|
29
|
+
'is',
|
|
30
|
+
'fts',
|
|
31
|
+
'plfts',
|
|
32
|
+
'phfts',
|
|
33
|
+
'wfts',
|
|
34
|
+
'cs',
|
|
35
|
+
'cd',
|
|
36
|
+
'ov',
|
|
37
|
+
'sl',
|
|
38
|
+
'sr',
|
|
39
|
+
'nxr',
|
|
40
|
+
'nxl',
|
|
41
|
+
'adj',
|
|
42
|
+
'not',
|
|
43
|
+
'or',
|
|
44
|
+
'and',
|
|
45
|
+
] as const;
|
|
46
|
+
|
|
47
|
+
type ParsedFiltersResult = {
|
|
48
|
+
filter: any;
|
|
49
|
+
select: any;
|
|
50
|
+
};
|
|
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
|
+
|
|
64
|
+
export type PostgRestOperator = (typeof postgrestOperators)[number];
|
|
65
|
+
|
|
66
|
+
export const parseFilters = (
|
|
67
|
+
params: any,
|
|
68
|
+
defaultListOp: PostgRestOperator
|
|
69
|
+
): Partial<ParsedFiltersResult> => {
|
|
70
|
+
const { filter, meta = {} } = params;
|
|
71
|
+
|
|
72
|
+
let result: Partial<ParsedFiltersResult> = {};
|
|
73
|
+
result.filter = {};
|
|
74
|
+
Object.keys(filter).forEach(function (key) {
|
|
75
|
+
// key: the name of the object key
|
|
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
|
+
|
|
100
|
+
const splitKey = key.split('@');
|
|
101
|
+
const operation: PostgRestOperator =
|
|
102
|
+
splitKey.length == 2
|
|
103
|
+
? (splitKey[1] as PostgRestOperator)
|
|
104
|
+
: defaultListOp;
|
|
105
|
+
|
|
106
|
+
if (['like', 'ilike'].includes(operation)) {
|
|
107
|
+
// we split the search term in words
|
|
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(',')})`];
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
values.forEach(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
|
+
return `${operation}.${value}`;
|
|
139
|
+
})();
|
|
140
|
+
|
|
141
|
+
// If resulting filter doesn't contain the fieldname, then add it.
|
|
142
|
+
if (result.filter[splitKey[0]] === undefined) {
|
|
143
|
+
// first operator for the key,
|
|
144
|
+
if (['and', 'or'].includes(operation)) {
|
|
145
|
+
result.filter[operation] = op;
|
|
146
|
+
} else {
|
|
147
|
+
// we add it to the dict
|
|
148
|
+
result.filter[splitKey[0]] = op;
|
|
149
|
+
}
|
|
150
|
+
} else {
|
|
151
|
+
if (!Array.isArray(result[splitKey[0]])) {
|
|
152
|
+
// second operator, we transform to an array
|
|
153
|
+
result.filter[splitKey[0]] = [
|
|
154
|
+
result.filter[splitKey[0]],
|
|
155
|
+
op,
|
|
156
|
+
];
|
|
157
|
+
} else {
|
|
158
|
+
// third and subsequent, we add to array
|
|
159
|
+
result.filter[splitKey[0]].push(op);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
});
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
if (meta?.columns) {
|
|
166
|
+
result.select = Array.isArray(meta.columns)
|
|
167
|
+
? meta.columns.join(',')
|
|
168
|
+
: meta.columns;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return result;
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
// compound keys capability
|
|
175
|
+
export type PrimaryKey = Array<string>;
|
|
176
|
+
|
|
177
|
+
export const getPrimaryKey = (
|
|
178
|
+
resource: string,
|
|
179
|
+
primaryKeys: Map<string, PrimaryKey>
|
|
180
|
+
) => {
|
|
181
|
+
return primaryKeys.get(resource) || ['id'];
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
export const decodeId = (
|
|
185
|
+
id: Identifier,
|
|
186
|
+
primaryKey: PrimaryKey
|
|
187
|
+
): string[] | number[] => {
|
|
188
|
+
if (isCompoundKey(primaryKey)) {
|
|
189
|
+
return JSON.parse(id.toString());
|
|
190
|
+
} else {
|
|
191
|
+
return [id.toString()];
|
|
192
|
+
}
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
export const encodeId = (data: any, primaryKey: PrimaryKey): Identifier => {
|
|
196
|
+
if (isCompoundKey(primaryKey)) {
|
|
197
|
+
return JSON.stringify(primaryKey.map(key => data[key]));
|
|
198
|
+
} else {
|
|
199
|
+
return data[primaryKey[0]];
|
|
200
|
+
}
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
export const removePrimaryKey = (data: any, primaryKey: PrimaryKey) => {
|
|
204
|
+
const newData = { ...data };
|
|
205
|
+
primaryKey.forEach(key => {
|
|
206
|
+
delete newData[key];
|
|
207
|
+
});
|
|
208
|
+
return newData;
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
export const dataWithVirtualId = (data: any, primaryKey: PrimaryKey) => {
|
|
212
|
+
if (primaryKey.length === 1 && primaryKey[0] === 'id') {
|
|
213
|
+
return data;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
return Object.assign(data, {
|
|
217
|
+
id: encodeId(data, primaryKey),
|
|
218
|
+
});
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
export const dataWithoutVirtualId = (data: any, primaryKey: PrimaryKey) => {
|
|
222
|
+
if (primaryKey.length === 1 && primaryKey[0] === 'id') {
|
|
223
|
+
return data;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const { id, ...dataWithoutId } = data;
|
|
227
|
+
return dataWithoutId;
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
const isCompoundKey = (primaryKey: PrimaryKey): Boolean => {
|
|
231
|
+
return primaryKey.length > 1;
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
export const getQuery = (
|
|
235
|
+
primaryKey: PrimaryKey,
|
|
236
|
+
ids: Identifier | Array<Identifier> | undefined,
|
|
237
|
+
resource: string,
|
|
238
|
+
meta: any = null
|
|
239
|
+
): any => {
|
|
240
|
+
let result: any = {};
|
|
241
|
+
if (Array.isArray(ids) && ids.length > 1) {
|
|
242
|
+
// no standardized query with multiple ids possible for rpc endpoints which are api-exposed database functions
|
|
243
|
+
if (resource.startsWith('rpc/')) {
|
|
244
|
+
console.error(
|
|
245
|
+
"PostgREST's rpc endpoints are not intended to be handled as views. Therefore, no query generation for multiple key values implemented!"
|
|
246
|
+
);
|
|
247
|
+
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
if (isCompoundKey(primaryKey)) {
|
|
252
|
+
result = {
|
|
253
|
+
or: `(${ids.map(id => {
|
|
254
|
+
const primaryKeyParams = decodeId(id, primaryKey);
|
|
255
|
+
return `and(${primaryKey
|
|
256
|
+
.map((key, i) => `${key}.eq.${primaryKeyParams[i]}`)
|
|
257
|
+
.join(',')})`;
|
|
258
|
+
})})`,
|
|
259
|
+
};
|
|
260
|
+
} else {
|
|
261
|
+
result = {
|
|
262
|
+
[primaryKey[0]]: `in.(${ids.join(',')})`,
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
} else if (ids) {
|
|
266
|
+
// if ids is one Identifier
|
|
267
|
+
const id: Identifier = ids.toString();
|
|
268
|
+
const primaryKeyParams = decodeId(id, primaryKey);
|
|
269
|
+
|
|
270
|
+
if (isCompoundKey(primaryKey)) {
|
|
271
|
+
if (resource.startsWith('rpc/')) {
|
|
272
|
+
result = {};
|
|
273
|
+
primaryKey.map(
|
|
274
|
+
(key: string, i: any) =>
|
|
275
|
+
(result[key] = `${primaryKeyParams[i]}`)
|
|
276
|
+
);
|
|
277
|
+
} else {
|
|
278
|
+
result = {
|
|
279
|
+
and: `(${primaryKey.map(
|
|
280
|
+
(key: string, i: any) =>
|
|
281
|
+
`${key}.eq.${primaryKeyParams[i]}`
|
|
282
|
+
)})`,
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
} else {
|
|
286
|
+
result = {
|
|
287
|
+
[primaryKey[0]]: `eq.${id}`,
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
if (meta && meta.columns) {
|
|
293
|
+
result.select = Array.isArray(meta.columns)
|
|
294
|
+
? meta.columns.join(',')
|
|
295
|
+
: meta.columns;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
return result;
|
|
299
|
+
};
|
|
300
|
+
|
|
301
|
+
export enum PostgRestSortOrder {
|
|
302
|
+
AscendingNullsLastDescendingNullsFirst = 'asc,desc',
|
|
303
|
+
AscendingNullsLastDescendingNullsLast = 'asc,desc.nullslast',
|
|
304
|
+
AscendingNullsFirstDescendingNullsFirst = 'asc.nullsfirst,desc',
|
|
305
|
+
AscendingNullsFirstDescendingNullsLast = 'asc.nullsfirst,desc.nullslast',
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
export const getOrderBy = (
|
|
309
|
+
field: string,
|
|
310
|
+
order: string,
|
|
311
|
+
primaryKey: PrimaryKey,
|
|
312
|
+
sortOrder: PostgRestSortOrder = PostgRestSortOrder.AscendingNullsLastDescendingNullsFirst
|
|
313
|
+
) => {
|
|
314
|
+
const postgRestOrder = sortOrder.split(',')[order === 'ASC' ? 0 : 1];
|
|
315
|
+
if (field == 'id') {
|
|
316
|
+
return primaryKey.map(key => `${key}.${postgRestOrder}`).join(',');
|
|
317
|
+
} else {
|
|
318
|
+
return `${field}.${postgRestOrder}`;
|
|
319
|
+
}
|
|
320
|
+
};
|