@rushdb/javascript-sdk 0.1.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 +7 -0
- package/LICENSE +21 -0
- package/README.md +119 -0
- package/cjs/api/api.js +328 -0
- package/cjs/api/constants.js +7 -0
- package/cjs/api/create-api.js +258 -0
- package/cjs/api/index.js +8 -0
- package/cjs/api/rest-api-proxy.js +22 -0
- package/cjs/api/types.js +2 -0
- package/cjs/api/utils.js +154 -0
- package/cjs/common/constants.js +10 -0
- package/cjs/common/utils.js +46 -0
- package/cjs/index.cjs.node.js +24 -0
- package/cjs/index.cjs.worker.js +24 -0
- package/cjs/network/FetchHttpClient.js +138 -0
- package/cjs/network/HttpClient.js +59 -0
- package/cjs/network/NodeHttpClient.js +103 -0
- package/cjs/network/index.js +28 -0
- package/cjs/network/types.js +2 -0
- package/cjs/package.json +4 -0
- package/cjs/sdk/constants.js +12 -0
- package/cjs/sdk/errors.js +27 -0
- package/cjs/sdk/index.js +23 -0
- package/cjs/sdk/model.js +147 -0
- package/cjs/sdk/record.js +77 -0
- package/cjs/sdk/sdk.js +46 -0
- package/cjs/sdk/transaction.js +17 -0
- package/cjs/sdk/types.js +2 -0
- package/cjs/sdk/utils.js +103 -0
- package/cjs/types/expressions.js +2 -0
- package/cjs/types/index.js +21 -0
- package/cjs/types/query.js +2 -0
- package/cjs/types/schema.js +2 -0
- package/cjs/types/utils.js +2 -0
- package/cjs/types/value.js +2 -0
- package/esm/api/api.js +324 -0
- package/esm/api/constants.js +4 -0
- package/esm/api/create-api.js +254 -0
- package/esm/api/index.js +2 -0
- package/esm/api/rest-api-proxy.js +18 -0
- package/esm/api/types.js +1 -0
- package/esm/api/utils.js +138 -0
- package/esm/common/constants.js +7 -0
- package/esm/common/utils.js +37 -0
- package/esm/index.esm.node.js +6 -0
- package/esm/index.esm.worker.js +6 -0
- package/esm/network/FetchHttpClient.js +133 -0
- package/esm/network/HttpClient.js +53 -0
- package/esm/network/NodeHttpClient.js +98 -0
- package/esm/network/index.js +24 -0
- package/esm/network/types.js +1 -0
- package/esm/package.json +4 -0
- package/esm/sdk/constants.js +9 -0
- package/esm/sdk/errors.js +21 -0
- package/esm/sdk/index.js +7 -0
- package/esm/sdk/model.js +143 -0
- package/esm/sdk/record.js +70 -0
- package/esm/sdk/sdk.js +42 -0
- package/esm/sdk/transaction.js +13 -0
- package/esm/sdk/types.js +1 -0
- package/esm/sdk/utils.js +93 -0
- package/esm/types/expressions.js +1 -0
- package/esm/types/index.js +5 -0
- package/esm/types/query.js +1 -0
- package/esm/types/schema.js +1 -0
- package/esm/types/utils.js +1 -0
- package/esm/types/value.js +1 -0
- package/package.json +59 -0
- package/types/api/api.d.ts +43 -0
- package/types/api/constants.d.ts +4 -0
- package/types/api/create-api.d.ts +67 -0
- package/types/api/index.d.ts +3 -0
- package/types/api/rest-api-proxy.d.ts +6 -0
- package/types/api/types.d.ts +65 -0
- package/types/api/utils.d.ts +43 -0
- package/types/common/constants.d.ts +7 -0
- package/types/common/utils.d.ts +7 -0
- package/types/index.d.ts +72 -0
- package/types/network/FetchHttpClient.d.ts +26 -0
- package/types/network/HttpClient.d.ts +48 -0
- package/types/network/NodeHttpClient.d.ts +16 -0
- package/types/network/index.d.ts +6 -0
- package/types/network/types.d.ts +4 -0
- package/types/sdk/constants.d.ts +1 -0
- package/types/sdk/errors.d.ts +9 -0
- package/types/sdk/index.d.ts +7 -0
- package/types/sdk/model.d.ts +72 -0
- package/types/sdk/record.d.ts +109 -0
- package/types/sdk/sdk.d.ts +136 -0
- package/types/sdk/transaction.d.ts +11 -0
- package/types/sdk/types.d.ts +19 -0
- package/types/sdk/utils.d.ts +9 -0
- package/types/types/expressions.d.ts +23 -0
- package/types/types/index.d.ts +5 -0
- package/types/types/query.d.ts +102 -0
- package/types/types/schema.d.ts +48 -0
- package/types/types/utils.d.ts +9 -0
- package/types/types/value.d.ts +35 -0
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
import { isArray } from '../common/utils.js';
|
|
2
|
+
import { NonUniqueResultError } from '../sdk/errors.js';
|
|
3
|
+
import { DBRecordsBatchDraft, DBRecordDraft } from '../sdk/record.js';
|
|
4
|
+
import { buildTransactionHeader, pickRecordId, pickTransactionId } from './utils.js';
|
|
5
|
+
// POST /api/v1/records/:id @TODO
|
|
6
|
+
export const createApi = (fetcher) => ({
|
|
7
|
+
labels: {
|
|
8
|
+
async find(searchParams, transaction) {
|
|
9
|
+
const txId = pickTransactionId(transaction);
|
|
10
|
+
return fetcher(`/labels`, {
|
|
11
|
+
headers: Object.assign({}, buildTransactionHeader(txId)),
|
|
12
|
+
method: 'POST',
|
|
13
|
+
requestData: searchParams
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
properties: {
|
|
18
|
+
delete: (id, transaction) => {
|
|
19
|
+
const txId = pickTransactionId(transaction);
|
|
20
|
+
return fetcher(`/properties/${id}`, {
|
|
21
|
+
headers: Object.assign({}, buildTransactionHeader(txId)),
|
|
22
|
+
method: 'DELETE'
|
|
23
|
+
});
|
|
24
|
+
},
|
|
25
|
+
find: (searchParams, transaction) => {
|
|
26
|
+
const txId = pickTransactionId(transaction);
|
|
27
|
+
return fetcher(`/properties`, {
|
|
28
|
+
headers: Object.assign({}, buildTransactionHeader(txId)),
|
|
29
|
+
method: 'POST',
|
|
30
|
+
requestData: searchParams
|
|
31
|
+
});
|
|
32
|
+
},
|
|
33
|
+
findById: (id, transaction) => {
|
|
34
|
+
const txId = pickTransactionId(transaction);
|
|
35
|
+
return fetcher(`/properties/${id}`, {
|
|
36
|
+
headers: Object.assign({}, buildTransactionHeader(txId)),
|
|
37
|
+
method: 'GET'
|
|
38
|
+
});
|
|
39
|
+
},
|
|
40
|
+
// update: () => {
|
|
41
|
+
// // @TODO
|
|
42
|
+
// },
|
|
43
|
+
// updateValues: (id: string, transaction?: Transaction | string) => {
|
|
44
|
+
// // @TODO
|
|
45
|
+
// },
|
|
46
|
+
values: (id, transaction) => {
|
|
47
|
+
const txId = pickTransactionId(transaction);
|
|
48
|
+
return fetcher(`/properties/${id}/values`, {
|
|
49
|
+
headers: Object.assign({}, buildTransactionHeader(txId)),
|
|
50
|
+
method: 'GET'
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
records: {
|
|
55
|
+
attach: async (source, idOrIds, options, transaction) => {
|
|
56
|
+
const txId = pickTransactionId(transaction);
|
|
57
|
+
const recordId = pickRecordId(source);
|
|
58
|
+
return fetcher(`/records/${recordId}/relations`, {
|
|
59
|
+
headers: Object.assign({}, buildTransactionHeader(txId)),
|
|
60
|
+
method: 'POST',
|
|
61
|
+
requestData: Object.assign(Object.assign({ targetIds: idOrIds }, ((options === null || options === void 0 ? void 0 : options.type) && { type: options === null || options === void 0 ? void 0 : options.type })), ((options === null || options === void 0 ? void 0 : options.direction) && { direction: options === null || options === void 0 ? void 0 : options.direction }))
|
|
62
|
+
});
|
|
63
|
+
},
|
|
64
|
+
async create(data, transaction) {
|
|
65
|
+
const txId = pickTransactionId(transaction);
|
|
66
|
+
if (data instanceof DBRecordDraft) {
|
|
67
|
+
return fetcher(`/records`, {
|
|
68
|
+
headers: Object.assign({}, buildTransactionHeader(txId)),
|
|
69
|
+
method: 'POST',
|
|
70
|
+
requestData: data.toJson()
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
return { data: undefined, success: false };
|
|
74
|
+
},
|
|
75
|
+
async createMany(data, transaction) {
|
|
76
|
+
const txId = pickTransactionId(transaction);
|
|
77
|
+
if (data instanceof DBRecordsBatchDraft) {
|
|
78
|
+
return fetcher(`/records/import/json`, {
|
|
79
|
+
headers: Object.assign({}, buildTransactionHeader(txId)),
|
|
80
|
+
method: 'POST',
|
|
81
|
+
requestData: data.toJson()
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
return { data: [], success: false };
|
|
85
|
+
},
|
|
86
|
+
delete(searchParams, transaction) {
|
|
87
|
+
const txId = pickTransactionId(transaction);
|
|
88
|
+
return fetcher(`/records/delete`, {
|
|
89
|
+
headers: Object.assign({}, buildTransactionHeader(txId)),
|
|
90
|
+
method: 'PUT',
|
|
91
|
+
requestData: searchParams
|
|
92
|
+
});
|
|
93
|
+
},
|
|
94
|
+
deleteById(idOrIds, transaction) {
|
|
95
|
+
const txId = pickTransactionId(transaction);
|
|
96
|
+
if (isArray(idOrIds)) {
|
|
97
|
+
return fetcher(`/records/delete`, {
|
|
98
|
+
headers: Object.assign({}, buildTransactionHeader(txId)),
|
|
99
|
+
method: 'PUT',
|
|
100
|
+
requestData: { limit: 1000, where: { $id: { $in: idOrIds } } }
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
return fetcher(`/records/${idOrIds}`, {
|
|
105
|
+
headers: Object.assign({}, buildTransactionHeader(txId)),
|
|
106
|
+
method: 'DELETE'
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
},
|
|
110
|
+
detach: async (source, idOrIds, options, transaction) => {
|
|
111
|
+
const txId = pickTransactionId(transaction);
|
|
112
|
+
const recordId = pickRecordId(source);
|
|
113
|
+
return fetcher(`/records/${recordId}/relations`, {
|
|
114
|
+
headers: Object.assign({}, buildTransactionHeader(txId)),
|
|
115
|
+
method: 'PUT',
|
|
116
|
+
requestData: Object.assign(Object.assign({ targetIds: idOrIds }, ((options === null || options === void 0 ? void 0 : options.typeOrTypes) && { typeOrTypes: options === null || options === void 0 ? void 0 : options.typeOrTypes })), ((options === null || options === void 0 ? void 0 : options.direction) && { direction: options === null || options === void 0 ? void 0 : options.direction }))
|
|
117
|
+
});
|
|
118
|
+
},
|
|
119
|
+
export(searchParams, transaction) {
|
|
120
|
+
const txId = pickTransactionId(transaction);
|
|
121
|
+
return fetcher(`/records/export/csv`, {
|
|
122
|
+
headers: Object.assign({}, buildTransactionHeader(txId)),
|
|
123
|
+
method: 'POST',
|
|
124
|
+
requestData: searchParams
|
|
125
|
+
});
|
|
126
|
+
},
|
|
127
|
+
find(params, transaction) {
|
|
128
|
+
const txId = pickTransactionId(transaction);
|
|
129
|
+
const url = (params === null || params === void 0 ? void 0 : params.id) ? `/records/${params === null || params === void 0 ? void 0 : params.id}/search` : `/records/search`;
|
|
130
|
+
return fetcher(url, {
|
|
131
|
+
headers: Object.assign({}, buildTransactionHeader(txId)),
|
|
132
|
+
method: 'POST',
|
|
133
|
+
requestData: params === null || params === void 0 ? void 0 : params.searchParams
|
|
134
|
+
});
|
|
135
|
+
},
|
|
136
|
+
findById(idOrIds, transaction) {
|
|
137
|
+
const txId = pickTransactionId(transaction);
|
|
138
|
+
if (isArray(idOrIds)) {
|
|
139
|
+
return fetcher(`/records`, {
|
|
140
|
+
headers: Object.assign({}, buildTransactionHeader(txId)),
|
|
141
|
+
method: 'POST',
|
|
142
|
+
requestData: { ids: idOrIds }
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
return fetcher(`/records/${idOrIds}`, {
|
|
146
|
+
headers: Object.assign({}, buildTransactionHeader(txId)),
|
|
147
|
+
method: 'GET'
|
|
148
|
+
});
|
|
149
|
+
},
|
|
150
|
+
async findOne(searchParams, transaction) {
|
|
151
|
+
const txId = pickTransactionId(transaction);
|
|
152
|
+
const response = await fetcher(`/records/search`, {
|
|
153
|
+
headers: Object.assign({}, buildTransactionHeader(txId)),
|
|
154
|
+
method: 'POST',
|
|
155
|
+
requestData: Object.assign(Object.assign({}, searchParams), { limit: 1, skip: 0 })
|
|
156
|
+
});
|
|
157
|
+
const [record] = response.data;
|
|
158
|
+
return Object.assign(Object.assign({}, response), { data: record });
|
|
159
|
+
},
|
|
160
|
+
async findUniq(searchParams, transaction) {
|
|
161
|
+
const txId = pickTransactionId(transaction);
|
|
162
|
+
const response = await fetcher(`/records/search`, {
|
|
163
|
+
headers: Object.assign({}, buildTransactionHeader(txId)),
|
|
164
|
+
method: 'POST',
|
|
165
|
+
requestData: Object.assign(Object.assign({}, searchParams), { limit: 1, skip: 0 })
|
|
166
|
+
});
|
|
167
|
+
if (typeof response.total !== 'undefined' && response.total > 1) {
|
|
168
|
+
throw new NonUniqueResultError(response.total, searchParams);
|
|
169
|
+
}
|
|
170
|
+
const [record] = response.data;
|
|
171
|
+
return Object.assign(Object.assign({}, response), { data: record });
|
|
172
|
+
},
|
|
173
|
+
properties(target, transaction) {
|
|
174
|
+
const txId = pickTransactionId(transaction);
|
|
175
|
+
const recordId = pickRecordId(target);
|
|
176
|
+
return fetcher(`/records/${recordId}/properties`, {
|
|
177
|
+
headers: Object.assign({}, buildTransactionHeader(txId)),
|
|
178
|
+
method: 'GET'
|
|
179
|
+
});
|
|
180
|
+
},
|
|
181
|
+
relations: async (target, transaction) => {
|
|
182
|
+
const txId = pickTransactionId(transaction);
|
|
183
|
+
const recordId = pickRecordId(target);
|
|
184
|
+
return fetcher(`/records/${recordId}/relations`, {
|
|
185
|
+
headers: Object.assign({}, buildTransactionHeader(txId)),
|
|
186
|
+
method: 'GET'
|
|
187
|
+
});
|
|
188
|
+
},
|
|
189
|
+
// upsert() {
|
|
190
|
+
// // @TODO
|
|
191
|
+
// }
|
|
192
|
+
set(target, data, transaction) {
|
|
193
|
+
const txId = pickTransactionId(transaction);
|
|
194
|
+
const recordId = pickRecordId(target);
|
|
195
|
+
return fetcher(`/records/${recordId}`, {
|
|
196
|
+
headers: Object.assign({}, buildTransactionHeader(txId)),
|
|
197
|
+
method: 'PUT',
|
|
198
|
+
requestData: data instanceof DBRecordDraft ? data.toJson() : data
|
|
199
|
+
});
|
|
200
|
+
},
|
|
201
|
+
update(target, data, transaction) {
|
|
202
|
+
const txId = pickTransactionId(transaction);
|
|
203
|
+
const recordId = pickRecordId(target);
|
|
204
|
+
return fetcher(`/records/${recordId}`, {
|
|
205
|
+
headers: Object.assign({}, buildTransactionHeader(txId)),
|
|
206
|
+
method: 'PATCH',
|
|
207
|
+
requestData: data instanceof DBRecordDraft ? data.toJson() : data
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
},
|
|
211
|
+
relations: {
|
|
212
|
+
find: async (searchParams, pagination, transaction) => {
|
|
213
|
+
const txId = pickTransactionId(transaction);
|
|
214
|
+
const queryParams = new URLSearchParams();
|
|
215
|
+
if (typeof (pagination === null || pagination === void 0 ? void 0 : pagination.limit) !== 'undefined') {
|
|
216
|
+
queryParams.append('limit', pagination.limit.toString());
|
|
217
|
+
}
|
|
218
|
+
if (typeof (pagination === null || pagination === void 0 ? void 0 : pagination.skip) !== 'undefined') {
|
|
219
|
+
queryParams.append('skip', pagination === null || pagination === void 0 ? void 0 : pagination.skip.toString());
|
|
220
|
+
}
|
|
221
|
+
const queryString = queryParams.toString() ? '?' + queryParams.toString() : '';
|
|
222
|
+
return fetcher(`/records/relations/search${queryString}`, {
|
|
223
|
+
headers: Object.assign({}, buildTransactionHeader(txId)),
|
|
224
|
+
method: 'POST',
|
|
225
|
+
requestData: searchParams
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
},
|
|
229
|
+
tx: {
|
|
230
|
+
begin(config = {}) {
|
|
231
|
+
return fetcher(`/tx`, {
|
|
232
|
+
method: 'POST',
|
|
233
|
+
requestData: config
|
|
234
|
+
});
|
|
235
|
+
},
|
|
236
|
+
commit(id) {
|
|
237
|
+
return fetcher(`/tx/${id}/commit`, {
|
|
238
|
+
method: 'POST',
|
|
239
|
+
requestData: {}
|
|
240
|
+
});
|
|
241
|
+
},
|
|
242
|
+
get(id) {
|
|
243
|
+
return fetcher(`/tx/${id}`, {
|
|
244
|
+
method: 'GET'
|
|
245
|
+
});
|
|
246
|
+
},
|
|
247
|
+
rollback(id) {
|
|
248
|
+
return fetcher(`/tx/${id}/rollback`, {
|
|
249
|
+
method: 'POST',
|
|
250
|
+
requestData: {}
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
});
|
package/esm/api/index.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export class RestApiProxy {
|
|
2
|
+
constructor() {
|
|
3
|
+
this.apiProxy = {};
|
|
4
|
+
return new Proxy(this, {
|
|
5
|
+
get: (target, prop, receiver) => {
|
|
6
|
+
if (prop in target) {
|
|
7
|
+
return Reflect.get(target, prop, receiver);
|
|
8
|
+
}
|
|
9
|
+
if (this.apiProxy && prop in this.apiProxy) {
|
|
10
|
+
return Reflect.get(this.apiProxy, prop, receiver);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
init(api, methodsWhiteList) {
|
|
16
|
+
this.apiProxy = api;
|
|
17
|
+
}
|
|
18
|
+
}
|
package/esm/api/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/esm/api/utils.js
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { ISO_8601_FULL, PROPERTY_TYPE_BOOLEAN, PROPERTY_TYPE_DATETIME, PROPERTY_TYPE_NULL, PROPERTY_TYPE_NUMBER, PROPERTY_TYPE_STRING } from '../common/constants.js';
|
|
2
|
+
import { isArray, isObject, isString } from '../common/utils.js';
|
|
3
|
+
import { DBRecordInstance } from '../sdk';
|
|
4
|
+
import { Transaction } from '../sdk/transaction.js';
|
|
5
|
+
import { DEFAULT_BASE_PATH, DEFAULT_HOST, DEFAULT_PORT, DEFAULT_PROTOCOL } from './constants.js';
|
|
6
|
+
export const buildTransactionHeader = (txId) => txId ?
|
|
7
|
+
{
|
|
8
|
+
['x-transaction-id']: txId
|
|
9
|
+
}
|
|
10
|
+
: undefined;
|
|
11
|
+
export const isTransaction = (input) => isString(input) || input instanceof Transaction;
|
|
12
|
+
export const pickTransaction = (input) => (isTransaction(input) ? input : undefined);
|
|
13
|
+
export const pickTransactionId = (input) => isTransaction(input) ?
|
|
14
|
+
input instanceof Transaction ?
|
|
15
|
+
input.id
|
|
16
|
+
: input
|
|
17
|
+
: undefined;
|
|
18
|
+
export const pickRecordId = (input) => {
|
|
19
|
+
if (isString(input)) {
|
|
20
|
+
return input;
|
|
21
|
+
}
|
|
22
|
+
else if (input instanceof DBRecordInstance && input.data) {
|
|
23
|
+
return input.data.__id;
|
|
24
|
+
}
|
|
25
|
+
else if ('__id' in input) {
|
|
26
|
+
return input.__id;
|
|
27
|
+
}
|
|
28
|
+
return undefined;
|
|
29
|
+
};
|
|
30
|
+
export const createSearchParams = (labelOrSearchParams, searchParamsOrTransaction) => {
|
|
31
|
+
var _a, _b, _c;
|
|
32
|
+
const isFirstArgString = isString(labelOrSearchParams);
|
|
33
|
+
const isFirstArgUUID = isUUID(labelOrSearchParams);
|
|
34
|
+
const isSecondArgTransaction = isTransaction(searchParamsOrTransaction);
|
|
35
|
+
const isEmptySearchParams = isSecondArgTransaction || !isObject(searchParamsOrTransaction);
|
|
36
|
+
if (isFirstArgString) {
|
|
37
|
+
const baseParams = isFirstArgUUID ?
|
|
38
|
+
{ id: labelOrSearchParams }
|
|
39
|
+
: { searchParams: { labels: [labelOrSearchParams] } };
|
|
40
|
+
return isEmptySearchParams ? Object.assign(Object.assign({}, baseParams), { searchParams: Object.assign({}, baseParams.searchParams) }) : Object.assign(Object.assign({}, baseParams), { searchParams: Object.assign(Object.assign({}, searchParamsOrTransaction), { labels: [
|
|
41
|
+
...((_b = (_a = baseParams.searchParams) === null || _a === void 0 ? void 0 : _a.labels) !== null && _b !== void 0 ? _b : []),
|
|
42
|
+
...((_c = searchParamsOrTransaction.labels) !== null && _c !== void 0 ? _c : [])
|
|
43
|
+
] }) });
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
return { searchParams: labelOrSearchParams !== null && labelOrSearchParams !== void 0 ? labelOrSearchParams : {} };
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
export const isUUID = (value) => {
|
|
50
|
+
const regex = /^[0-9a-fA-F]{8}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{12}$/;
|
|
51
|
+
return regex.test(value);
|
|
52
|
+
};
|
|
53
|
+
export const arrayIsConsistent = (arr) => arr.every((item) => typeof item === typeof arr[0]);
|
|
54
|
+
export const getValueParameters = (value) => {
|
|
55
|
+
if (Array.isArray(value)) {
|
|
56
|
+
return {
|
|
57
|
+
isEmptyArray: value.length === 0,
|
|
58
|
+
isEmptyStringsArray: value.every((v) => v === ''),
|
|
59
|
+
isInconsistentArray: !arrayIsConsistent(value)
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
return { isEmptyString: value === '' };
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
export const suggestPropertyType = (value) => {
|
|
67
|
+
if (typeof value === PROPERTY_TYPE_STRING) {
|
|
68
|
+
return ISO_8601_FULL.test(value) ? PROPERTY_TYPE_DATETIME : PROPERTY_TYPE_STRING;
|
|
69
|
+
}
|
|
70
|
+
else if (typeof value === PROPERTY_TYPE_NUMBER) {
|
|
71
|
+
return PROPERTY_TYPE_NUMBER;
|
|
72
|
+
}
|
|
73
|
+
else if (typeof value === PROPERTY_TYPE_BOOLEAN) {
|
|
74
|
+
return PROPERTY_TYPE_BOOLEAN;
|
|
75
|
+
}
|
|
76
|
+
else if (value === null) {
|
|
77
|
+
return PROPERTY_TYPE_NULL;
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
return PROPERTY_TYPE_STRING;
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
const processArrayValue = (value, suggestTypes) => {
|
|
84
|
+
const { isEmptyArray, isInconsistentArray } = getValueParameters(value);
|
|
85
|
+
if (isEmptyArray) {
|
|
86
|
+
return { type: PROPERTY_TYPE_STRING, value: [] };
|
|
87
|
+
}
|
|
88
|
+
if (isInconsistentArray || !suggestTypes) {
|
|
89
|
+
return { type: PROPERTY_TYPE_STRING, value: value.map(String) };
|
|
90
|
+
}
|
|
91
|
+
return { type: suggestPropertyType(value[0]), value };
|
|
92
|
+
};
|
|
93
|
+
const processNonArrayValue = (value, suggestTypes) => {
|
|
94
|
+
if (!suggestTypes) {
|
|
95
|
+
return { type: PROPERTY_TYPE_STRING, value: String(value) };
|
|
96
|
+
}
|
|
97
|
+
const type = suggestPropertyType(value);
|
|
98
|
+
return { type, value: type === PROPERTY_TYPE_NULL ? null : value };
|
|
99
|
+
};
|
|
100
|
+
export const prepareProperties = (data, options = { suggestTypes: true }) => Object.entries(data).map(([name, value]) => {
|
|
101
|
+
const { type, value: processedValue } = isArray(value) ?
|
|
102
|
+
processArrayValue(value, options.suggestTypes)
|
|
103
|
+
: processNonArrayValue(value, options.suggestTypes);
|
|
104
|
+
return { name, type, value: processedValue };
|
|
105
|
+
});
|
|
106
|
+
export const normalizeRecord = ({ label, options = { suggestTypes: true }, payload }) => ({
|
|
107
|
+
label,
|
|
108
|
+
properties: prepareProperties(payload, options)
|
|
109
|
+
});
|
|
110
|
+
export const buildUrl = (props) => {
|
|
111
|
+
let protocol = DEFAULT_PROTOCOL;
|
|
112
|
+
let host = DEFAULT_HOST;
|
|
113
|
+
let port = DEFAULT_PORT;
|
|
114
|
+
let basePath = DEFAULT_BASE_PATH;
|
|
115
|
+
if ('url' in props) {
|
|
116
|
+
const url = new URL(props.url);
|
|
117
|
+
protocol = url.protocol.replace(':', '');
|
|
118
|
+
host = url.hostname;
|
|
119
|
+
port = parseInt(url.port || protocol === 'http' ? '80'
|
|
120
|
+
: protocol === 'https' ? '443'
|
|
121
|
+
: '');
|
|
122
|
+
}
|
|
123
|
+
if ('host' in props && 'port' in props && 'protocol' in props) {
|
|
124
|
+
protocol = props.protocol.replace(':', '');
|
|
125
|
+
host = props.host;
|
|
126
|
+
port = props.port;
|
|
127
|
+
}
|
|
128
|
+
// Ensure the basePath starts with a '/'
|
|
129
|
+
if (basePath && !basePath.startsWith('/')) {
|
|
130
|
+
basePath = '/' + basePath;
|
|
131
|
+
}
|
|
132
|
+
// If the port is the default for the protocol (80 for http, 443 for https), it can be omitted
|
|
133
|
+
let portString = '';
|
|
134
|
+
if (!((protocol === 'http' && port === 80) || (protocol === 'https' && port === 443))) {
|
|
135
|
+
portString = ':' + port;
|
|
136
|
+
}
|
|
137
|
+
return `${protocol}://${host}${portString}${basePath}`;
|
|
138
|
+
};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export const ISO_8601_FULL = /^(?:\d{4}-(?:(?:0[1-9]|1[0-2])-(?:0[1-9]|1\d|2[0-8])|(?:0[13-9]|1[0-2])-(?:29|30)|(?:0[13578]|1[02])-31)|(?:\d{2}(?:[02468][048]|[13579][26])-02-29))Schema(0[0-9]|1[0-9]|2[0-3]):(0[0-9]|[1-5][0-9]):(0[0-9]|[1-5][0-9])?(?:\.([0-9]{1,9}))?([zZ]?|([\+-])(((([0][0-9])|([1][0-3])):?(([03][0])|([14][5])))|14:00)?)$/;
|
|
2
|
+
export const DEFAULT_TIMEOUT = 80000;
|
|
3
|
+
export const PROPERTY_TYPE_STRING = 'string';
|
|
4
|
+
export const PROPERTY_TYPE_DATETIME = 'datetime';
|
|
5
|
+
export const PROPERTY_TYPE_BOOLEAN = 'boolean';
|
|
6
|
+
export const PROPERTY_TYPE_NUMBER = 'number';
|
|
7
|
+
export const PROPERTY_TYPE_NULL = 'null';
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
export const isArray = (item) => typeof item === 'object' && Array.isArray(item) && item !== null;
|
|
2
|
+
export const isObject = (input) => input !== null && Object.prototype.toString.call(input) === '[object Object]';
|
|
3
|
+
export const isEmptyObject = (input) => isObject(input) && Object.keys(input).length === 0;
|
|
4
|
+
export const isObjectFlat = (input) => {
|
|
5
|
+
return (isObject(input) &&
|
|
6
|
+
Object.keys(input).every((key) => {
|
|
7
|
+
const value = input[key];
|
|
8
|
+
// Check if the value is an array
|
|
9
|
+
if (isArray(value)) {
|
|
10
|
+
// Check if every element in the array is of an allowed type (string, number, boolean, or null)
|
|
11
|
+
return value.every((element) => typeof element === 'string' ||
|
|
12
|
+
typeof element === 'number' ||
|
|
13
|
+
typeof element === 'boolean' ||
|
|
14
|
+
element === null);
|
|
15
|
+
}
|
|
16
|
+
// Check if the value is a non-null object (excluding arrays, which are also typeof 'object')
|
|
17
|
+
return !(value && typeof value === 'object');
|
|
18
|
+
}));
|
|
19
|
+
};
|
|
20
|
+
export const isString = (input) => typeof input === 'string';
|
|
21
|
+
export const toBoolean = (value) => {
|
|
22
|
+
if (typeof value === 'string') {
|
|
23
|
+
if (value.toLowerCase() === 'true') {
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
else if (value.toLowerCase() === 'false') {
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
if (isArray(value) && value.length === 0) {
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
if (isObject(value) && Object.keys(value).length === 0) {
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
return Boolean(value);
|
|
37
|
+
};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { HttpClient, HttpClientResponse } from './network/HttpClient.js';
|
|
2
|
+
import { NodeHttpClient } from './network/NodeHttpClient.js';
|
|
3
|
+
import { DBRecordsBatchDraft, Model, DBRecordInstance, DBRecordsArrayInstance, Transaction, EmptyTargetError, NonUniqueResultError, UniquenessError, initSDK, idToDate, idToTimestamp } from './sdk/index.js';
|
|
4
|
+
const RushDB = initSDK(new NodeHttpClient());
|
|
5
|
+
export { RushDB, DBRecordsBatchDraft, Model, DBRecordInstance, DBRecordsArrayInstance, Transaction, EmptyTargetError, HttpClient, HttpClientResponse, NonUniqueResultError, UniquenessError, idToDate, idToTimestamp };
|
|
6
|
+
export default RushDB;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { FetchHttpClient } from './network/FetchHttpClient.js';
|
|
2
|
+
import { HttpClient, HttpClientResponse } from './network/HttpClient.js';
|
|
3
|
+
import { DBRecordsBatchDraft, Model, DBRecordInstance, DBRecordsArrayInstance, Transaction, EmptyTargetError, NonUniqueResultError, UniquenessError, initSDK, idToDate, idToTimestamp } from './sdk/index.js';
|
|
4
|
+
const RushDB = initSDK(new FetchHttpClient());
|
|
5
|
+
export { RushDB, DBRecordsBatchDraft, Model, DBRecordInstance, DBRecordsArrayInstance, Transaction, EmptyTargetError, HttpClient, HttpClientResponse, NonUniqueResultError, UniquenessError, idToDate, idToTimestamp };
|
|
6
|
+
export default RushDB;
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { DEFAULT_TIMEOUT } from '../common/constants.js';
|
|
2
|
+
import { HttpClient, HttpClientGenericResponse } from './HttpClient.js';
|
|
3
|
+
/**
|
|
4
|
+
* HTTP client which uses a `fetch` function to issue requests.
|
|
5
|
+
*
|
|
6
|
+
* By default, relies on the global `fetch` function, but an optional function
|
|
7
|
+
* can be passed in. If passing in a function, it is expected to match the Web
|
|
8
|
+
* Fetch API. As an example, this could be the function provided by the
|
|
9
|
+
* node-fetch package (https://github.com/node-fetch/node-fetch).
|
|
10
|
+
*/
|
|
11
|
+
export class FetchHttpClient extends HttpClient {
|
|
12
|
+
constructor(fetchFn) {
|
|
13
|
+
super();
|
|
14
|
+
// Default to global fetch if available
|
|
15
|
+
if (!fetchFn) {
|
|
16
|
+
if (!globalThis.fetch) {
|
|
17
|
+
throw new Error('fetch() function not provided and is not defined in the global scope. ' +
|
|
18
|
+
'You must provide a fetch implementation.');
|
|
19
|
+
}
|
|
20
|
+
fetchFn = globalThis.fetch;
|
|
21
|
+
}
|
|
22
|
+
// Both timeout behaviors differs from Node:
|
|
23
|
+
// - Fetch uses a single timeout for the entire length of the request.
|
|
24
|
+
// - Node is more fine-grained and resets the timeout after each stage of the request.
|
|
25
|
+
if (globalThis.AbortController) {
|
|
26
|
+
// Utilise native AbortController if available
|
|
27
|
+
// AbortController was added in Node v15.0.0, v14.17.0
|
|
28
|
+
this._fetchFn = FetchHttpClient.makeFetchWithAbortTimeout(fetchFn);
|
|
29
|
+
}
|
|
30
|
+
else {
|
|
31
|
+
// Fall back to racing against a timeout promise if not available in the runtime
|
|
32
|
+
// This does not actually cancel the underlying fetch operation or resources
|
|
33
|
+
this._fetchFn = FetchHttpClient.makeFetchWithRaceTimeout(fetchFn);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
static makeFetchWithRaceTimeout(fetchFn) {
|
|
37
|
+
return (url, init, timeout) => {
|
|
38
|
+
let pendingTimeoutId;
|
|
39
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
40
|
+
pendingTimeoutId = setTimeout(() => {
|
|
41
|
+
pendingTimeoutId = null;
|
|
42
|
+
reject(HttpClient.makeTimeoutError());
|
|
43
|
+
}, timeout);
|
|
44
|
+
});
|
|
45
|
+
const fetchPromise = fetchFn(url, init);
|
|
46
|
+
return Promise.race([fetchPromise, timeoutPromise]).finally(() => {
|
|
47
|
+
if (pendingTimeoutId) {
|
|
48
|
+
clearTimeout(pendingTimeoutId);
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
static makeFetchWithAbortTimeout(fetchFn) {
|
|
54
|
+
return async (url, init, timeout) => {
|
|
55
|
+
// Use AbortController because AbortSignal.timeout() was added later in Node v17.3.0, v16.14.0
|
|
56
|
+
const abort = new AbortController();
|
|
57
|
+
let timeoutId = setTimeout(() => {
|
|
58
|
+
timeoutId = null;
|
|
59
|
+
abort.abort(HttpClient.makeTimeoutError());
|
|
60
|
+
}, timeout);
|
|
61
|
+
try {
|
|
62
|
+
return await fetchFn(url, Object.assign(Object.assign({}, init), { signal: abort.signal }));
|
|
63
|
+
}
|
|
64
|
+
catch (err) {
|
|
65
|
+
// Some implementations, like node-fetch, do not respect the reason passed to AbortController.abort()
|
|
66
|
+
// and instead it always throws an AbortError
|
|
67
|
+
// We catch this case to normalise all timeout errors
|
|
68
|
+
if (err.name === 'AbortError') {
|
|
69
|
+
throw HttpClient.makeTimeoutError();
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
throw err;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
finally {
|
|
76
|
+
if (timeoutId) {
|
|
77
|
+
clearTimeout(timeoutId);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
async makeRequest(url, { headers, method, protocol, requestData, timeout }) {
|
|
83
|
+
// For methods which expect payloads, we should always pass a body value
|
|
84
|
+
// even when it is empty. Without this, some JS runtimes (eg. Deno) will
|
|
85
|
+
// inject a second Content-Length header.
|
|
86
|
+
const methodHasPayload = method == 'POST' || method == 'PUT' || method == 'PATCH';
|
|
87
|
+
const body = JSON.stringify(requestData) || (methodHasPayload ? '' : undefined);
|
|
88
|
+
const res = await this._fetchFn(url, {
|
|
89
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
90
|
+
// @ts-ignore
|
|
91
|
+
body,
|
|
92
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
93
|
+
// @ts-ignore
|
|
94
|
+
headers,
|
|
95
|
+
method
|
|
96
|
+
}, timeout !== null && timeout !== void 0 ? timeout : DEFAULT_TIMEOUT);
|
|
97
|
+
return new FetchHttpClientResponse(res);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
export class FetchHttpClientResponse extends HttpClientGenericResponse {
|
|
101
|
+
constructor(res) {
|
|
102
|
+
super(res.status, FetchHttpClientResponse._transformHeadersToObject(res.headers));
|
|
103
|
+
this._res = res;
|
|
104
|
+
}
|
|
105
|
+
getRawResponse() {
|
|
106
|
+
return this._res;
|
|
107
|
+
}
|
|
108
|
+
toStream(streamCompleteCallback) {
|
|
109
|
+
// Unfortunately `fetch` does not have event handlers for when the stream is
|
|
110
|
+
// completely read. We therefore invoke the streamCompleteCallback right
|
|
111
|
+
// away. This callback emits a response event with metadata and completes
|
|
112
|
+
// metrics, so it's ok to do this without waiting for the stream to be
|
|
113
|
+
// completely read.
|
|
114
|
+
streamCompleteCallback();
|
|
115
|
+
// Fetch's `body` property is expected to be a readable stream of the body.
|
|
116
|
+
return this._res.body;
|
|
117
|
+
}
|
|
118
|
+
toJSON() {
|
|
119
|
+
return this._res.json();
|
|
120
|
+
}
|
|
121
|
+
static _transformHeadersToObject(headers) {
|
|
122
|
+
// Fetch uses a Headers instance so this must be converted to a barebones
|
|
123
|
+
// JS object to meet the HttpClient interface.
|
|
124
|
+
const headersObj = {};
|
|
125
|
+
for (const entry of headers) {
|
|
126
|
+
if (!Array.isArray(entry) || entry.length != 2) {
|
|
127
|
+
throw new Error('Response objects produced by the fetch function given to FetchHttpClient do not have an iterable headers map. Response#headers should be an iterable object.');
|
|
128
|
+
}
|
|
129
|
+
headersObj[entry[0]] = entry[1];
|
|
130
|
+
}
|
|
131
|
+
return headersObj;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
export class HttpClient {
|
|
2
|
+
makeRequest(url, { headers, method, protocol, requestData, timeout }) {
|
|
3
|
+
throw new Error('makeRequest not implemented.');
|
|
4
|
+
}
|
|
5
|
+
static makeTimeoutError() {
|
|
6
|
+
const timeoutErr = new TypeError(HttpClient.TIMEOUT_ERROR_CODE);
|
|
7
|
+
timeoutErr.code = HttpClient.TIMEOUT_ERROR_CODE;
|
|
8
|
+
return timeoutErr;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
HttpClient.CONNECTION_CLOSED_ERROR_CODES = ['ECONNRESET', 'EPIPE'];
|
|
12
|
+
HttpClient.TIMEOUT_ERROR_CODE = 'ETIMEDOUT';
|
|
13
|
+
export class HttpClientGenericResponse {
|
|
14
|
+
constructor(statusCode, headers) {
|
|
15
|
+
this._statusCode = statusCode;
|
|
16
|
+
this._headers = headers;
|
|
17
|
+
}
|
|
18
|
+
getStatusCode() {
|
|
19
|
+
return this._statusCode;
|
|
20
|
+
}
|
|
21
|
+
getHeaders() {
|
|
22
|
+
return this._headers;
|
|
23
|
+
}
|
|
24
|
+
getRawResponse() {
|
|
25
|
+
throw new Error('getRawResponse not implemented.');
|
|
26
|
+
}
|
|
27
|
+
toStream(streamCompleteCallback) {
|
|
28
|
+
throw new Error('toStream not implemented.');
|
|
29
|
+
}
|
|
30
|
+
toJSON() {
|
|
31
|
+
throw new Error('toJSON not implemented.');
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
export class HttpClientResponse {
|
|
35
|
+
constructor(response) {
|
|
36
|
+
this._res = response;
|
|
37
|
+
}
|
|
38
|
+
getStatusCode() {
|
|
39
|
+
throw new Error('getStatusCode not implemented.');
|
|
40
|
+
}
|
|
41
|
+
getHeaders() {
|
|
42
|
+
throw new Error('getHeaders not implemented.');
|
|
43
|
+
}
|
|
44
|
+
getRawResponse() {
|
|
45
|
+
throw new Error('getRawResponse not implemented.');
|
|
46
|
+
}
|
|
47
|
+
toStream(streamCompleteCallback) {
|
|
48
|
+
throw new Error('toStream not implemented.');
|
|
49
|
+
}
|
|
50
|
+
toJSON() {
|
|
51
|
+
throw new Error('toJSON not implemented.');
|
|
52
|
+
}
|
|
53
|
+
}
|