@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,98 @@
|
|
|
1
|
+
import * as http_ from 'http';
|
|
2
|
+
import * as https_ from 'https';
|
|
3
|
+
import { DEFAULT_TIMEOUT } from '../common/constants.js';
|
|
4
|
+
import { HttpClient, HttpClientGenericResponse } from './HttpClient.js';
|
|
5
|
+
// `import * as http_ from 'http'` creates a "Module Namespace Exotic Object"
|
|
6
|
+
// which is immune to monkey-patching, whereas http_.default (in an ES Module context)
|
|
7
|
+
// will resolve to the same thing as require('http'), which is
|
|
8
|
+
// monkey-patchable. We care about this because users in their test
|
|
9
|
+
// suites might be using a library like "nock" which relies on the ability
|
|
10
|
+
// to monkey-patch and intercept calls to http.request.
|
|
11
|
+
const http = http_.default || http_;
|
|
12
|
+
const https = https_.default || https_;
|
|
13
|
+
const defaultHttpAgent = new http.Agent({ keepAlive: true });
|
|
14
|
+
const defaultHttpsAgent = new https.Agent({ keepAlive: true });
|
|
15
|
+
export class NodeHttpClient extends HttpClient {
|
|
16
|
+
constructor(agent) {
|
|
17
|
+
super();
|
|
18
|
+
this._agent = agent;
|
|
19
|
+
}
|
|
20
|
+
makeRequest(urlString, { headers, method, protocol, requestData, timeout }) {
|
|
21
|
+
const url = new URL(urlString);
|
|
22
|
+
const isInsecureConnection = protocol === 'http' || url.protocol === 'http:';
|
|
23
|
+
let agent = this._agent;
|
|
24
|
+
if (!agent) {
|
|
25
|
+
agent = isInsecureConnection ? defaultHttpAgent : defaultHttpsAgent;
|
|
26
|
+
}
|
|
27
|
+
return new Promise((resolve, reject) => {
|
|
28
|
+
const req = (isInsecureConnection ? http : https).request({
|
|
29
|
+
agent,
|
|
30
|
+
ciphers: 'DEFAULT:!aNULL:!eNULL:!LOW:!EXPORT:!SSLv2:!MD5',
|
|
31
|
+
headers,
|
|
32
|
+
host: url.host,
|
|
33
|
+
method,
|
|
34
|
+
path: url.pathname + url.search,
|
|
35
|
+
port: url.port
|
|
36
|
+
});
|
|
37
|
+
req.setTimeout(timeout !== null && timeout !== void 0 ? timeout : DEFAULT_TIMEOUT, () => {
|
|
38
|
+
req.destroy(HttpClient.makeTimeoutError());
|
|
39
|
+
});
|
|
40
|
+
req.on('response', (res) => {
|
|
41
|
+
resolve(new NodeHttpClientResponse(res));
|
|
42
|
+
});
|
|
43
|
+
req.on('error', (error) => {
|
|
44
|
+
reject(error);
|
|
45
|
+
});
|
|
46
|
+
req.once('socket', (socket) => {
|
|
47
|
+
const payload = requestData ? JSON.stringify(requestData) : '';
|
|
48
|
+
if (socket.connecting) {
|
|
49
|
+
socket.once(isInsecureConnection ? 'connect' : 'secureConnect', () => {
|
|
50
|
+
// Send payload; we're safe:
|
|
51
|
+
req.write(payload);
|
|
52
|
+
req.end();
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
// we're already connected
|
|
57
|
+
req.write(payload);
|
|
58
|
+
req.end();
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
export class NodeHttpClientResponse extends HttpClientGenericResponse {
|
|
65
|
+
constructor(res) {
|
|
66
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
67
|
+
// @ts-ignore
|
|
68
|
+
super(res.statusCode, res.headers || {});
|
|
69
|
+
this._res = res;
|
|
70
|
+
}
|
|
71
|
+
getRawResponse() {
|
|
72
|
+
return this._res;
|
|
73
|
+
}
|
|
74
|
+
toStream(streamCompleteCallback) {
|
|
75
|
+
// The raw response is itself the stream, so we just return that. To be
|
|
76
|
+
// backwards compatible, we should invoke the streamCompleteCallback only
|
|
77
|
+
// once the stream has been fully consumed.
|
|
78
|
+
this._res.once('end', () => streamCompleteCallback());
|
|
79
|
+
return this._res;
|
|
80
|
+
}
|
|
81
|
+
toJSON() {
|
|
82
|
+
return new Promise((resolve, reject) => {
|
|
83
|
+
let response = '';
|
|
84
|
+
this._res.setEncoding('utf8');
|
|
85
|
+
this._res.on('data', (chunk) => {
|
|
86
|
+
response += chunk;
|
|
87
|
+
});
|
|
88
|
+
this._res.once('end', () => {
|
|
89
|
+
try {
|
|
90
|
+
resolve(JSON.parse(response));
|
|
91
|
+
}
|
|
92
|
+
catch (e) {
|
|
93
|
+
reject(e);
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
var __rest = (this && this.__rest) || function (s, e) {
|
|
2
|
+
var t = {};
|
|
3
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
|
|
4
|
+
t[p] = s[p];
|
|
5
|
+
if (s != null && typeof Object.getOwnPropertySymbols === "function")
|
|
6
|
+
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
|
|
7
|
+
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
|
|
8
|
+
t[p[i]] = s[p[i]];
|
|
9
|
+
}
|
|
10
|
+
return t;
|
|
11
|
+
};
|
|
12
|
+
const defaultHeaders = {
|
|
13
|
+
'Content-Type': 'application/json'
|
|
14
|
+
};
|
|
15
|
+
export const createFetcher = ({ httpClient, token, url }) => async (input, _a) => {
|
|
16
|
+
var { headers: initHeaders } = _a, init = __rest(_a, ["headers"]);
|
|
17
|
+
const response = await httpClient.makeRequest(`${url}${input}`, Object.assign({ credentials: 'omit', headers: Object.assign(Object.assign(Object.assign({}, defaultHeaders), initHeaders), typeof token !== 'undefined' ? { token } : {}) }, init));
|
|
18
|
+
if (response.getStatusCode() >= 200 && response.getStatusCode() < 300) {
|
|
19
|
+
const data = await response.toJSON();
|
|
20
|
+
return data;
|
|
21
|
+
}
|
|
22
|
+
// @TODO: Make it more useful
|
|
23
|
+
throw new Error(`${response.getStatusCode()}`);
|
|
24
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/esm/package.json
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export class UniquenessError extends Error {
|
|
2
|
+
constructor(label, properties) {
|
|
3
|
+
super(`Record with label "${label}" and properties ${JSON.stringify(properties)} already exists`);
|
|
4
|
+
this.name = 'UniquenessError';
|
|
5
|
+
Object.setPrototypeOf(this, UniquenessError.prototype);
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
export class EmptyTargetError extends Error {
|
|
9
|
+
constructor(message) {
|
|
10
|
+
super(message);
|
|
11
|
+
this.name = 'EmptyTarget';
|
|
12
|
+
Object.setPrototypeOf(this, EmptyTargetError.prototype);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
export class NonUniqueResultError extends Error {
|
|
16
|
+
constructor(duplicateCount, searchParams) {
|
|
17
|
+
super(`Expected a unique result but found ${duplicateCount} matches for the provided search parameters: ${JSON.stringify(searchParams)}. Ensure your search parameters uniquely identify a single result.`);
|
|
18
|
+
this.name = 'NonUniqueResultError';
|
|
19
|
+
Object.setPrototypeOf(this, NonUniqueResultError.prototype);
|
|
20
|
+
}
|
|
21
|
+
}
|
package/esm/sdk/index.js
ADDED
package/esm/sdk/model.js
ADDED
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { RestApiProxy } from '../api/rest-api-proxy.js';
|
|
2
|
+
import { isEmptyObject } from '../common/utils.js';
|
|
3
|
+
import { EmptyTargetError, UniquenessError } from './errors.js';
|
|
4
|
+
import { mergeDefaultsWithPayload, pickUniqFieldsFromRecord, pickUniqFieldsFromRecords } from './utils.js';
|
|
5
|
+
export class Model extends RestApiProxy {
|
|
6
|
+
constructor(modelName, schema, RushDBInstance) {
|
|
7
|
+
super();
|
|
8
|
+
this.label = modelName;
|
|
9
|
+
this.schema = schema;
|
|
10
|
+
RushDBInstance === null || RushDBInstance === void 0 ? void 0 : RushDBInstance.registerModel(this);
|
|
11
|
+
}
|
|
12
|
+
getLabel() {
|
|
13
|
+
return this.label;
|
|
14
|
+
}
|
|
15
|
+
async find(params = {}, transaction) {
|
|
16
|
+
var _a;
|
|
17
|
+
return (_a = this.apiProxy) === null || _a === void 0 ? void 0 : _a.records.find(this.label, params, transaction);
|
|
18
|
+
}
|
|
19
|
+
async findOne(params = {}, transaction) {
|
|
20
|
+
var _a;
|
|
21
|
+
return (_a = this.apiProxy) === null || _a === void 0 ? void 0 : _a.records.findOne(this.label, Object.assign({}, params), transaction);
|
|
22
|
+
}
|
|
23
|
+
async findById(id, transaction) {
|
|
24
|
+
return this.findOne({ where: { $id: id } }, transaction);
|
|
25
|
+
}
|
|
26
|
+
async findUniq(params = {}, transaction) {
|
|
27
|
+
var _a;
|
|
28
|
+
return (_a = this.apiProxy) === null || _a === void 0 ? void 0 : _a.records.findUniq(this.label, Object.assign({}, params), transaction);
|
|
29
|
+
}
|
|
30
|
+
async create(record, transaction) {
|
|
31
|
+
var _a;
|
|
32
|
+
const data = await mergeDefaultsWithPayload(this.schema, record);
|
|
33
|
+
const uniqFields = pickUniqFieldsFromRecord(this.schema, data);
|
|
34
|
+
if (!isEmptyObject(uniqFields)) {
|
|
35
|
+
const tx = transaction !== null && transaction !== void 0 ? transaction : (await this.apiProxy.tx.begin());
|
|
36
|
+
const matchingRecords = await this.find({ where: uniqFields }, tx);
|
|
37
|
+
const hasOwnTransaction = typeof transaction !== 'undefined';
|
|
38
|
+
const canCreate = !((_a = matchingRecords === null || matchingRecords === void 0 ? void 0 : matchingRecords.data) === null || _a === void 0 ? void 0 : _a.length);
|
|
39
|
+
if (canCreate) {
|
|
40
|
+
const result = await this.apiProxy.records.create(this.label, data, tx);
|
|
41
|
+
if (!hasOwnTransaction) {
|
|
42
|
+
await tx.commit();
|
|
43
|
+
}
|
|
44
|
+
return result;
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
if (!hasOwnTransaction) {
|
|
48
|
+
await tx.commit();
|
|
49
|
+
}
|
|
50
|
+
// @TODO: Make it optional
|
|
51
|
+
throw new UniquenessError(this.label, uniqFields);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return await this.apiProxy.records.create(this.label, data, transaction);
|
|
55
|
+
}
|
|
56
|
+
attach(sourceId, target, options, transaction) {
|
|
57
|
+
return this.apiProxy.records.attach(sourceId, target, options, transaction);
|
|
58
|
+
}
|
|
59
|
+
detach(sourceId, target, options, transaction) {
|
|
60
|
+
return this.apiProxy.records.detach(sourceId, target, options, transaction);
|
|
61
|
+
}
|
|
62
|
+
async handleSetOrUpdate(id, record, method, transaction) {
|
|
63
|
+
var _a;
|
|
64
|
+
const data = await mergeDefaultsWithPayload(this.schema, record);
|
|
65
|
+
const uniqFields = pickUniqFieldsFromRecord(this.schema, data);
|
|
66
|
+
if (!isEmptyObject(uniqFields)) {
|
|
67
|
+
const tx = transaction !== null && transaction !== void 0 ? transaction : (await this.apiProxy.tx.begin());
|
|
68
|
+
const matchingRecords = await this.find({ where: uniqFields }, tx);
|
|
69
|
+
const hasOwnTransaction = typeof transaction !== 'undefined';
|
|
70
|
+
const canUpdate = !((_a = matchingRecords === null || matchingRecords === void 0 ? void 0 : matchingRecords.data) === null || _a === void 0 ? void 0 : _a.length) ||
|
|
71
|
+
(matchingRecords.data.length === 1 && matchingRecords.data[0].__id === id);
|
|
72
|
+
if (canUpdate) {
|
|
73
|
+
const result = await this.apiProxy.records[method](id, data, tx);
|
|
74
|
+
if (!hasOwnTransaction) {
|
|
75
|
+
await tx.commit();
|
|
76
|
+
}
|
|
77
|
+
return result;
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
if (!hasOwnTransaction) {
|
|
81
|
+
await tx.commit();
|
|
82
|
+
}
|
|
83
|
+
throw new UniquenessError(this.label, uniqFields);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return await this.apiProxy.records[method](id, data, transaction);
|
|
87
|
+
}
|
|
88
|
+
async set(id, record, transaction) {
|
|
89
|
+
return await this.handleSetOrUpdate(id, record, 'set', transaction);
|
|
90
|
+
}
|
|
91
|
+
async update(id, record, transaction) {
|
|
92
|
+
return await this.handleSetOrUpdate(id, record, 'update', transaction);
|
|
93
|
+
}
|
|
94
|
+
async createMany(records, transaction) {
|
|
95
|
+
var _a;
|
|
96
|
+
// Begin a transaction if one isn't provided.
|
|
97
|
+
const hasOwnTransaction = typeof transaction !== 'undefined';
|
|
98
|
+
const tx = transaction !== null && transaction !== void 0 ? transaction : (await this.apiProxy.tx.begin());
|
|
99
|
+
try {
|
|
100
|
+
// Apply defaults in parallel
|
|
101
|
+
const recordsToStore = await Promise.all(records.map(async (record) => {
|
|
102
|
+
return await mergeDefaultsWithPayload(this.schema, record);
|
|
103
|
+
}));
|
|
104
|
+
// Check uniqueness
|
|
105
|
+
const uniqProperties = pickUniqFieldsFromRecords(recordsToStore, this.schema, this.label);
|
|
106
|
+
if (!isEmptyObject(uniqProperties)) {
|
|
107
|
+
const criteria = Object.entries(uniqProperties).map(([key, values]) => ({
|
|
108
|
+
[key]: { $in: values }
|
|
109
|
+
}));
|
|
110
|
+
const matchingRecords = await this.find({
|
|
111
|
+
where: {
|
|
112
|
+
$or: criteria
|
|
113
|
+
}
|
|
114
|
+
}, tx);
|
|
115
|
+
if ((_a = matchingRecords === null || matchingRecords === void 0 ? void 0 : matchingRecords.data) === null || _a === void 0 ? void 0 : _a.length) {
|
|
116
|
+
throw new UniquenessError(this.label, Object.keys(uniqProperties));
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
// Create records in the database
|
|
120
|
+
const createdRecords = await this.apiProxy.records.createMany(this.label, recordsToStore, tx);
|
|
121
|
+
// Commit the transaction if it was created internally
|
|
122
|
+
if (!hasOwnTransaction) {
|
|
123
|
+
await tx.commit();
|
|
124
|
+
}
|
|
125
|
+
return createdRecords;
|
|
126
|
+
}
|
|
127
|
+
catch (error) {
|
|
128
|
+
if (!hasOwnTransaction) {
|
|
129
|
+
await tx.rollback();
|
|
130
|
+
}
|
|
131
|
+
throw error;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
async delete(searchParams, transaction) {
|
|
135
|
+
if (isEmptyObject(searchParams.where)) {
|
|
136
|
+
throw new EmptyTargetError(`You must specify criteria to delete records of type '${this.label}'. Empty criteria are not allowed. If this was intentional, use the Dashboard instead.`);
|
|
137
|
+
}
|
|
138
|
+
return await this.apiProxy.records.delete(Object.assign(Object.assign({}, searchParams), { labels: [this.label] }), transaction);
|
|
139
|
+
}
|
|
140
|
+
async deleteById(idOrIds, transaction) {
|
|
141
|
+
return await this.apiProxy.records.deleteById(idOrIds, transaction);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { RestApiProxy } from '../api/rest-api-proxy.js';
|
|
2
|
+
export class DBRecordsBatchDraft {
|
|
3
|
+
constructor({ label, options = {
|
|
4
|
+
returnResult: true,
|
|
5
|
+
suggestTypes: true
|
|
6
|
+
}, payload }) {
|
|
7
|
+
this.label = label;
|
|
8
|
+
this.options = options;
|
|
9
|
+
this.payload = payload;
|
|
10
|
+
}
|
|
11
|
+
toJson() {
|
|
12
|
+
return {
|
|
13
|
+
label: this.label,
|
|
14
|
+
options: this.options,
|
|
15
|
+
payload: this.payload
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
export class DBRecordDraft {
|
|
20
|
+
constructor({ label, properties = [] }) {
|
|
21
|
+
this.label = label;
|
|
22
|
+
this.properties = properties;
|
|
23
|
+
}
|
|
24
|
+
toJson() {
|
|
25
|
+
return {
|
|
26
|
+
label: this.label,
|
|
27
|
+
properties: this.properties
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
export class DBRecordInstance extends RestApiProxy {
|
|
32
|
+
constructor(data, searchParams) {
|
|
33
|
+
super();
|
|
34
|
+
this.data = data;
|
|
35
|
+
this.searchParams = searchParams;
|
|
36
|
+
}
|
|
37
|
+
async delete(transaction) {
|
|
38
|
+
if (this.data) {
|
|
39
|
+
return await this.apiProxy.records.deleteById(this.data.__id, transaction);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
async update(partialData, transaction) {
|
|
43
|
+
if (this.data) {
|
|
44
|
+
return this.apiProxy.records.update(this.data.__id, partialData, transaction);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
async set(data, transaction) {
|
|
48
|
+
if (this.data) {
|
|
49
|
+
return this.apiProxy.records.set(this.data.__id, data, transaction);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
async attach(target, options, transaction) {
|
|
53
|
+
if (this.data) {
|
|
54
|
+
return this.apiProxy.records.attach(this.data.__id, target, options, transaction);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
async detach(target, options, transaction) {
|
|
58
|
+
if (this.data) {
|
|
59
|
+
return this.apiProxy.records.detach(this.data.__id, target, options, transaction);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
export class DBRecordsArrayInstance extends RestApiProxy {
|
|
64
|
+
constructor(data, total, searchParams) {
|
|
65
|
+
super();
|
|
66
|
+
this.data = data;
|
|
67
|
+
this.total = total;
|
|
68
|
+
this.searchParams = searchParams;
|
|
69
|
+
}
|
|
70
|
+
}
|
package/esm/sdk/sdk.js
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { RestAPI } from '../api/api.js';
|
|
2
|
+
import { DEFAULT_TIMEOUT } from '../common/constants.js';
|
|
3
|
+
import { DBRecordInstance } from './record.js';
|
|
4
|
+
import { parseConfig, validateInteger } from './utils.js';
|
|
5
|
+
export const initSDK = (httpClient) => {
|
|
6
|
+
class RushDB extends RestAPI {
|
|
7
|
+
constructor(token, config) {
|
|
8
|
+
var _a;
|
|
9
|
+
const props = parseConfig(config);
|
|
10
|
+
super(token, Object.assign(Object.assign({}, props), { httpClient: (_a = props.httpClient) !== null && _a !== void 0 ? _a : httpClient }));
|
|
11
|
+
this.state = {
|
|
12
|
+
debug: false,
|
|
13
|
+
timeout: validateInteger('timeout', props.timeout, DEFAULT_TIMEOUT),
|
|
14
|
+
token
|
|
15
|
+
};
|
|
16
|
+
this.models = new Map();
|
|
17
|
+
RushDB.instance = this;
|
|
18
|
+
}
|
|
19
|
+
static getInstance() {
|
|
20
|
+
return RushDB.instance;
|
|
21
|
+
}
|
|
22
|
+
registerModel(model) {
|
|
23
|
+
const label = model.getLabel();
|
|
24
|
+
// Inject the API into the model
|
|
25
|
+
model.init(this);
|
|
26
|
+
this.models.set(label, model);
|
|
27
|
+
return model; //as unknown as Model<T['schema']> & Omit<RestAPI, 'find'>
|
|
28
|
+
}
|
|
29
|
+
getModels() {
|
|
30
|
+
return this.models;
|
|
31
|
+
}
|
|
32
|
+
getModel(label) {
|
|
33
|
+
return this.models.get(label);
|
|
34
|
+
}
|
|
35
|
+
toInstance(record) {
|
|
36
|
+
const result = new DBRecordInstance(record);
|
|
37
|
+
result.init(this);
|
|
38
|
+
return result;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return RushDB;
|
|
42
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { RestApiProxy } from '../api/rest-api-proxy.js';
|
|
2
|
+
export class Transaction extends RestApiProxy {
|
|
3
|
+
constructor(id) {
|
|
4
|
+
super();
|
|
5
|
+
this.id = id;
|
|
6
|
+
}
|
|
7
|
+
async rollback() {
|
|
8
|
+
return await this.apiProxy.tx.rollback(this.id);
|
|
9
|
+
}
|
|
10
|
+
async commit() {
|
|
11
|
+
return await this.apiProxy.tx.commit(this.id);
|
|
12
|
+
}
|
|
13
|
+
}
|
package/esm/sdk/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/esm/sdk/utils.js
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { isObject } from '../common/utils.js';
|
|
2
|
+
import { ALLOWED_CONFIG_PROPERTIES } from './constants.js';
|
|
3
|
+
import { UniquenessError } from './errors.js';
|
|
4
|
+
export const mergeDefaultsWithPayload = async (schema, payload) => {
|
|
5
|
+
const defaultPromises = Object.entries(schema).map(async ([key, prop]) => {
|
|
6
|
+
if (prop.default &&
|
|
7
|
+
typeof prop.default !== 'undefined' &&
|
|
8
|
+
typeof payload[key] === 'undefined') {
|
|
9
|
+
return {
|
|
10
|
+
key,
|
|
11
|
+
value: typeof prop.default === 'function' ? await prop.default() : prop.default
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
else {
|
|
15
|
+
return { key, value: undefined };
|
|
16
|
+
}
|
|
17
|
+
});
|
|
18
|
+
const resolvedDefaults = await Promise.all(defaultPromises);
|
|
19
|
+
const defaults = resolvedDefaults.reduce((acc, { key, value }) => {
|
|
20
|
+
if (value !== undefined) {
|
|
21
|
+
acc[key] = value;
|
|
22
|
+
}
|
|
23
|
+
return acc;
|
|
24
|
+
}, {});
|
|
25
|
+
return Object.assign(Object.assign({}, defaults), payload);
|
|
26
|
+
};
|
|
27
|
+
export const pickUniqFieldsFromRecord = (schema, data) => {
|
|
28
|
+
return Object.entries(data)
|
|
29
|
+
.filter(([key]) => { var _a; return (_a = schema[key]) === null || _a === void 0 ? void 0 : _a.uniq; })
|
|
30
|
+
.reduce((acc, [key, value]) => {
|
|
31
|
+
if (key in schema) {
|
|
32
|
+
acc[key] = value;
|
|
33
|
+
}
|
|
34
|
+
return acc;
|
|
35
|
+
}, {});
|
|
36
|
+
};
|
|
37
|
+
export const pickUniqFieldsFromRecords = (data, schema, label) => {
|
|
38
|
+
const properties = {};
|
|
39
|
+
const uniqFields = Object.entries(schema)
|
|
40
|
+
.filter(([_, config]) => config.uniq)
|
|
41
|
+
.reduce((acc, [key]) => {
|
|
42
|
+
acc[key] = true;
|
|
43
|
+
return acc;
|
|
44
|
+
}, {});
|
|
45
|
+
data.forEach((record) => {
|
|
46
|
+
Object.entries(record).forEach(([key, value]) => {
|
|
47
|
+
if (key in uniqFields) {
|
|
48
|
+
if (properties[key]) {
|
|
49
|
+
if (properties[key].includes(value)) {
|
|
50
|
+
throw new UniquenessError(label, { [key]: value });
|
|
51
|
+
}
|
|
52
|
+
properties[key] = [...properties[key], value];
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
properties[key] = [value];
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
return properties;
|
|
61
|
+
};
|
|
62
|
+
export const parseConfig = (config) => {
|
|
63
|
+
if (!config) {
|
|
64
|
+
return {};
|
|
65
|
+
}
|
|
66
|
+
if (!isObject(config)) {
|
|
67
|
+
throw new Error('Config must be an object');
|
|
68
|
+
}
|
|
69
|
+
const values = Object.keys(config).filter((value) => !ALLOWED_CONFIG_PROPERTIES.includes(value));
|
|
70
|
+
if (values.length > 0) {
|
|
71
|
+
throw new Error(`Config object may only contain the following: ${ALLOWED_CONFIG_PROPERTIES.join(', ')}`);
|
|
72
|
+
}
|
|
73
|
+
return config;
|
|
74
|
+
};
|
|
75
|
+
export function validateInteger(name, n, defaultVal) {
|
|
76
|
+
if (!Number.isInteger(n)) {
|
|
77
|
+
if (defaultVal !== undefined) {
|
|
78
|
+
return defaultVal;
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
throw new Error(`${name} must be an integer`);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return n;
|
|
85
|
+
}
|
|
86
|
+
export function idToTimestamp(id) {
|
|
87
|
+
const parts = id.split('-');
|
|
88
|
+
const highBitsHex = parts[0] + parts[1].slice(0, 4);
|
|
89
|
+
return parseInt(highBitsHex, 16);
|
|
90
|
+
}
|
|
91
|
+
export function idToDate(id) {
|
|
92
|
+
return new Date(idToTimestamp(id));
|
|
93
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@rushdb/javascript-sdk",
|
|
3
|
+
"description": "RushDB Javascript SDK",
|
|
4
|
+
"license": "MIT",
|
|
5
|
+
"version": "0.1.0",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"main": "cjs/index.cjs.node.js",
|
|
8
|
+
"types": "types/index.d.ts",
|
|
9
|
+
"devDependencies": {},
|
|
10
|
+
"peerDependencies": {},
|
|
11
|
+
"sideEffects": false,
|
|
12
|
+
"publishConfig": {
|
|
13
|
+
"access": "public"
|
|
14
|
+
},
|
|
15
|
+
"bugs": "https://github.com/rush-db/rushdb/issues",
|
|
16
|
+
"repository": {
|
|
17
|
+
"type": "https",
|
|
18
|
+
"url": "https://github.com/rush-db/rushdb"
|
|
19
|
+
},
|
|
20
|
+
"exports": {
|
|
21
|
+
"types": "./types/index.d.ts",
|
|
22
|
+
"browser": {
|
|
23
|
+
"import": "./esm/index.esm.worker.js",
|
|
24
|
+
"require": "./cjs/index.cjs.worker.js"
|
|
25
|
+
},
|
|
26
|
+
"bun": {
|
|
27
|
+
"import": "./esm/index.esm.worker.js",
|
|
28
|
+
"require": "./cjs/index.cjs.worker.js"
|
|
29
|
+
},
|
|
30
|
+
"deno": {
|
|
31
|
+
"import": "./esm/index.esm.worker.js",
|
|
32
|
+
"require": "./cjs/index.cjs.worker.js"
|
|
33
|
+
},
|
|
34
|
+
"worker": {
|
|
35
|
+
"import": "./esm/index.esm.worker.js",
|
|
36
|
+
"require": "./cjs/index.cjs.worker.js"
|
|
37
|
+
},
|
|
38
|
+
"workerd": {
|
|
39
|
+
"import": "./esm/index.esm.worker.js",
|
|
40
|
+
"require": "./cjs/index.cjs.worker.js"
|
|
41
|
+
},
|
|
42
|
+
"default": {
|
|
43
|
+
"import": "./esm/index.esm.node.js",
|
|
44
|
+
"require": "./cjs/index.cjs.node.js"
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
"scripts": {
|
|
48
|
+
"build": "pnpm clean && pnpm build-esm && pnpm build-cjs && pnpm postbuild",
|
|
49
|
+
"build-esm": "mkdir -p esm && tsc -p tsconfig.esm.json",
|
|
50
|
+
"build-cjs": "mkdir -p cjs && tsc -p tsconfig.cjs.json",
|
|
51
|
+
"postbuild": "node scripts/post-build.js",
|
|
52
|
+
"dev": "tsup --watch",
|
|
53
|
+
"types:check": "tsc --noEmit",
|
|
54
|
+
"clean": "rm -rf ./dist ./node_modules/.cache ./coverage ./esm ./cjs ./types",
|
|
55
|
+
"flush": "rm -rf node_modules",
|
|
56
|
+
"lint:circularDeps": "madge --circular ./src/*",
|
|
57
|
+
"lint:circularDepsWithImage": "madge --image graph.svg --circular ./src/*"
|
|
58
|
+
}
|
|
59
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { HttpClient } from '../network/HttpClient.js';
|
|
2
|
+
import type { UserProvidedConfig } from '../sdk/types.js';
|
|
3
|
+
import type { SearchQuery, Schema } from '../types/index.js';
|
|
4
|
+
import type { ApiResponse, RecordsApi } from './types.js';
|
|
5
|
+
import { createFetcher } from '../network/index.js';
|
|
6
|
+
import { Transaction } from '../sdk/transaction.js';
|
|
7
|
+
import { createApi } from './create-api.js';
|
|
8
|
+
export declare class RestAPI {
|
|
9
|
+
api: ReturnType<typeof createApi>;
|
|
10
|
+
fetcher: ReturnType<typeof createFetcher>;
|
|
11
|
+
records: RecordsApi;
|
|
12
|
+
constructor(token?: string, config?: UserProvidedConfig & {
|
|
13
|
+
httpClient: HttpClient;
|
|
14
|
+
});
|
|
15
|
+
relations: {
|
|
16
|
+
find: <S extends Schema = any>({ pagination, search, transaction }: {
|
|
17
|
+
pagination?: Pick<SearchQuery, "limit" | "skip">;
|
|
18
|
+
search?: SearchQuery<S>;
|
|
19
|
+
transaction?: Transaction | string;
|
|
20
|
+
}) => Promise<ApiResponse<import("../sdk/record.js").Relation[]>>;
|
|
21
|
+
};
|
|
22
|
+
properties: {
|
|
23
|
+
delete: (id: string, transaction?: Transaction | string) => Promise<ApiResponse<import("../types/value.js").Property>>;
|
|
24
|
+
find: <S extends Schema = any>(searchParams: SearchQuery<S>, transaction?: Transaction | string) => Promise<ApiResponse<import("../types/value.js").Property[]>>;
|
|
25
|
+
findById: (id: string, transaction?: Transaction | string) => Promise<ApiResponse<import("../types/value.js").Property>>;
|
|
26
|
+
values: (id: string, transaction?: Transaction | string) => Promise<ApiResponse<import("../types/value.js").Property & import("../types/value.js").PropertyValuesData>>;
|
|
27
|
+
};
|
|
28
|
+
labels: {
|
|
29
|
+
find: <S extends Schema = any>(searchParams: SearchQuery<S>, transaction?: Transaction | string) => Promise<ApiResponse<Record<string, number>>>;
|
|
30
|
+
};
|
|
31
|
+
tx: {
|
|
32
|
+
begin: (config?: Partial<{
|
|
33
|
+
ttl: number;
|
|
34
|
+
}>) => Promise<Transaction>;
|
|
35
|
+
commit: (id: string) => Promise<ApiResponse<{
|
|
36
|
+
message: string;
|
|
37
|
+
}>>;
|
|
38
|
+
get: (id: string) => Promise<Transaction>;
|
|
39
|
+
rollback: (id: string) => Promise<ApiResponse<{
|
|
40
|
+
message: string;
|
|
41
|
+
}>>;
|
|
42
|
+
};
|
|
43
|
+
}
|