roboto-js 1.0.20 → 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/rbt_api.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import axios from 'axios';
2
2
  import CryptoJS from 'crypto-js';
3
3
  import RbtObject from './rbt_object.js';
4
+ import RbtUser from './rbt_user.js';
4
5
  import RbtFile from './rbt_file.js';
5
6
  import _ from 'lodash';
6
7
  import { openDB } from 'idb';
@@ -22,6 +23,7 @@ export default class RbtApi {
22
23
  this.localDb = null;
23
24
  this.iac_session = null;
24
25
  this.authtoken = authTokenToUse;
26
+
25
27
  }
26
28
 
27
29
  async initLocalDb(){
@@ -36,6 +38,14 @@ export default class RbtApi {
36
38
 
37
39
  }
38
40
 
41
+ /**
42
+ * Logs in a user and stores the authToken.
43
+ *
44
+ * @param {Object} params - The login parameters.
45
+ * @param {string} params.email - The email of the user.
46
+ * @param {string} params.password - The password of the user.
47
+ * @returns {Promise<Object>} - The response data from the API.
48
+ */
39
49
  async login(params) {
40
50
  try {
41
51
 
@@ -66,6 +76,53 @@ export default class RbtApi {
66
76
  }
67
77
  }
68
78
 
79
+ async loadCurrentUser(){
80
+
81
+ // TODO - get the actual user from the session
82
+ let params = { id: 'superuser_tom' };
83
+
84
+ try {
85
+
86
+ const response = await this.axios.post('/user_service/loadUser', [params]);
87
+ let userData = response?.data?.user;
88
+
89
+ let User = new RbtUser({ id: userData.id }, this.axios);
90
+ User.setData(userData);
91
+ return User;
92
+
93
+ //const record = response.data;
94
+ //
95
+ //if(dataHash){
96
+ // record.data = dataHash;
97
+ //}
98
+ //return new RbtObject(record, this.axios);
99
+ } catch (e) {
100
+ return this._handleError(e);
101
+ }
102
+
103
+ }
104
+
105
+ async refreshAuthToken(authtoken){
106
+
107
+ try {
108
+
109
+ const response = await this.axios.post('/user_service/refreshAuthToken', [authtoken]);
110
+ return response.data;
111
+
112
+ } catch (e) {
113
+
114
+ this._handleError(e);
115
+ }
116
+ }
117
+
118
+
119
+ /**
120
+ * Registers a new user.
121
+ *
122
+ * @param {Object} dataHash - The data for the new user.
123
+ * @returns {Promise<RbtObject>} - The newly created user as an RbtObject.
124
+ *
125
+ */
69
126
  async registerUser(dataHash={}) {
70
127
  try {
71
128
  const response = await this.axios.post('/user_service/registerUser', [dataHash]);
@@ -80,6 +137,13 @@ export default class RbtApi {
80
137
  }
81
138
  }
82
139
 
140
+ /**
141
+ * Creates a new file in the system.
142
+ *
143
+ * @param {Object} dataHash - The data for the new file.
144
+ * @returns {Promise<RbtFile>} - The newly created file as an RbtFile.
145
+ *
146
+ */
83
147
  async createFile(dataHash){
84
148
 
85
149
  //return this.create('<@filekit.file>', dataHash);
@@ -98,6 +162,13 @@ export default class RbtApi {
98
162
  }
99
163
 
100
164
 
165
+ /**
166
+ * Creates a new object of the given type.
167
+ *
168
+ * @param {string} type - The type of object to create.
169
+ * @param {Object} dataHash - The data for the new object.
170
+ * @returns {Promise<RbtObject>} - The newly created object as an RbtObject.
171
+ */
101
172
  async create(type, dataHash={}) {
102
173
  try {
103
174
  const response = await this.axios.post('/object_service/createObject', [type, dataHash]);
@@ -106,19 +177,50 @@ export default class RbtApi {
106
177
  if(dataHash){
107
178
  record.data = dataHash;
108
179
  }
109
- return new RbtObject(record, this.axios);
180
+ return new RbtObject(record, this.axios, { isNew: true });
110
181
  } catch (e) {
111
182
  return this._handleError(e);
112
183
  }
113
184
  }
114
185
 
186
+ /**
187
+ * Queries objects of a given type based on specified parameters.
188
+ *
189
+ * @param {string} type - The type of object to query, specified as a doctree.typedef.
190
+ * @param {Object} params - The query parameters, including optional filters and configurations.
191
+ * @returns {Promise<Array<RbtObject>>} - An array of queried objects as RbtObjects.
192
+ *
193
+ * The `params` object can include the following properties:
194
+ * - where: A SQL-like where clause string for filtering the results.
195
+ * - orderBy: An object specifying the ordering of the results. It should include:
196
+ * - column: The attribute name to sort by. This must be either a column in the @doctree.model schema or an indexed type attribute.
197
+ * - direction: The sort direction, either 'ASC' for ascending or 'DESC' for descending.
198
+ * - limit: An object to control the pagination of results. It includes:
199
+ * - offset: The starting point from where to fetch the results.
200
+ * - results: The maximum number of results to return.
201
+ * - resolveReferences: An array of attribute names whose references should be resolved in the returned objects.
202
+ * - timeout: A numerical value in milliseconds to set a maximum time limit for the query execution.
203
+ *
204
+ * Example usage:
205
+ * query("<@testuser>", {
206
+ * where: 'email="tom@pospa.com"',
207
+ * orderBy: { column: 'timeCreated', direction: 'DESC' },
208
+ * limit: { offset: 0, results: 50 },
209
+ * resolveReferences: ['translatableContent']
210
+ * });
211
+ *
212
+ * Note: A default orderBy is applied if none is provided, ordering items by 'timeCreated' in descending order.
213
+ */
115
214
  async query(type, params = {}) {
116
215
  try {
117
216
  params.type = type;
118
217
 
119
- // default
218
+ // Default ordering and pagination
120
219
  const defaultOrderBy = { orderBy: { column: 'timeCreated', direction: 'DESC' } };
121
- const mergedParams = { ...defaultOrderBy, ...params };
220
+ const defaultLimit = { limit: { offset: 0, results: 50 } };
221
+
222
+ // Merge defaults with provided params
223
+ const mergedParams = { ...defaultOrderBy, ...defaultLimit, ...params };
122
224
 
123
225
  const response = await this.axios.post('/object_service/queryObjects', [mergedParams]);
124
226
  if (response.data.ok === false) {
@@ -127,7 +229,7 @@ export default class RbtApi {
127
229
 
128
230
  // Process items into RbtObject instances
129
231
  if (Array.isArray(response.data.items)) {
130
- response.data.items = response.data.items.map(record => new RbtObject(record, this.axios));
232
+ response.data.items = response.data.items.map(record => new RbtObject(record, this.axios, { isNew: true }));
131
233
  }
132
234
  return response.data.items;
133
235
  } catch (e) {
@@ -135,15 +237,26 @@ export default class RbtApi {
135
237
  }
136
238
  }
137
239
 
138
- async load(type, ids){
240
+ /**
241
+ * Loads one or multiple objects of a given type by their IDs.
242
+ *
243
+ * @param {string} type - The type of object to load.
244
+ * @param {Array<string>|string} ids - The ID(s) of the object(s) to load.
245
+ *
246
+ * @returns {Promise<RbtObject|RbtObject[]>} - The loaded object(s) as RbtObject(s).
247
+ */
248
+ async load(type, ids, params={}){
139
249
 
140
250
  try{
141
- debugger;
251
+ let mergedParams;
252
+
142
253
  if(Array.isArray(ids)){
143
- return this.query(type, { where: "id IN ("+ids.join(',')+")" });
254
+ mergedParams = { ...params, where: "id IN (" + ids.join(',') + ")" };
255
+ return this.query(type, mergedParams);
144
256
  }
145
257
  else{
146
- let res = await this.query(type, { where: "id="+ids});
258
+ mergedParams = { ...params, where: "id="+ids };
259
+ let res = await this.query(type, mergedParams);
147
260
  return res[0];
148
261
  }
149
262
 
@@ -153,6 +266,112 @@ export default class RbtApi {
153
266
 
154
267
  }
155
268
 
269
+ /**
270
+ * Makes a POST request to a specific endpoint to run a task and handle progress updates.
271
+ *
272
+ * @param {Object} params - The parameters to be sent in the POST request.
273
+ * @param {Object} callbacks - An object containing callback functions for progress and error handling.
274
+ *
275
+ * The function expects a response in the following format:
276
+ * {
277
+ * ok: boolean, // Indicates if the request was successful or not
278
+ * jobId: string, // The job identifier
279
+ * status: string // Can be 'RUNNING', 'DONE', or 'ERROR'
280
+ * }
281
+ */
282
+ async runTask(params = {}, callbacks = {}) {
283
+ const { onProgress, onError, onFinish } = callbacks;
284
+
285
+ try {
286
+ const response = await this.post('http://localhost:3004/runChain', params);
287
+ // Check if response and response.data are defined
288
+ if (!response) {
289
+ throw new Error('Invalid server response');
290
+ }
291
+
292
+ // Validate response structure
293
+ const { ok, jobId, status, message, output } = response;
294
+ if (!ok || typeof jobId !== 'string' || typeof status !== 'string') {
295
+ throw new Error('Invalid response structure');
296
+ }
297
+
298
+ // If the task is still in progress, start polling for updates
299
+ if (status === 'RUNNING' || status === 'SCHEDULED' || status === 'QUEUED') {
300
+ this.pollTaskProgress(jobId, callbacks);
301
+ }
302
+ if (status === 'ERROR' && onError){
303
+ // Provide the current progress to the callback function
304
+ onError(response);
305
+ }
306
+ if (status === 'DONE' && onFinish){
307
+ // Provide the current progress to the callback function
308
+ console.log('Finish (request) ',response);
309
+ onFinish(response);
310
+ }
311
+
312
+ return { ok, jobId, status, message, output };
313
+
314
+ } catch (e) {
315
+
316
+ if (typeof onError === 'function') {
317
+ onError(e);
318
+ } else {
319
+ console.error('Error in runTask:', e);
320
+ }
321
+ return { ok: false, jobId: null, status: 'ERROR', error: e.message };
322
+
323
+ }
324
+ }
325
+
326
+
327
+ /**
328
+ * Polls the progress of a long-running task.
329
+ *
330
+ * @param {string} jobId - The ID of the job to poll for progress.
331
+ * @param {function} onProgress - Callback function that receives progress updates.
332
+ *
333
+ * The function periodically sends GET requests to check the task's progress
334
+ * and reports back via the provided callback function. The polling stops when
335
+ * the task is completed or an error occurs.
336
+ */
337
+ async pollTaskProgress(jobId, callbacks) {
338
+ const { onProgress, onError, onFinish } = callbacks;
339
+ try {
340
+ const checkProgress = async () => {
341
+ const response = await this.get(`http://localhost:3004/pollChainProgress`, { jobId: jobId });
342
+
343
+ // If the task is still in progress, start polling for updates
344
+ if (response.status === 'DONE' && onFinish){
345
+ // Provide the current progress to the callback function
346
+ console.log('Finish (progress) ',response);
347
+ onFinish(response);
348
+ }
349
+ if (response.status === 'ERROR' && onError){
350
+ // Provide the current progress to the callback function
351
+ onError(response);
352
+ }
353
+
354
+ // Provide the current progress to the callback function
355
+ if(response.status == 'RUNNING'){
356
+ onProgress(response);
357
+ }
358
+
359
+ // Continue polling if the status is 'RUNNING'
360
+ if (['RUNNING'].includes(response.status)) {
361
+ setTimeout(checkProgress, 1000); // Poll every 2 seconds
362
+ }
363
+ };
364
+
365
+ checkProgress();
366
+ } catch (e) {
367
+ // Handle error based on your application's logic
368
+ return this._handleError(e);
369
+ }
370
+ }
371
+
372
+
373
+
374
+
156
375
  /**
157
376
  * Performs a GET request to the specified endpoint.
158
377
  *
@@ -224,18 +443,24 @@ export default class RbtApi {
224
443
  }
225
444
 
226
445
 
227
- _handleError(err){
446
+ setErrorHandler(customErrorHandler) {
447
+ this.customErrorHandler = customErrorHandler;
448
+ }
228
449
 
229
- if(_.isObject(err)){
230
- // response object?
231
- let msg = _.get(err, 'response.data.message');
232
- msg = (msg)? msg: err;
233
- throw new Error(msg);
234
- }
235
- else{
236
- // all other errors
237
- throw new Error(err);
450
+ _handleError(err) {
451
+
452
+ // Invoke the custom error handler if provided
453
+ if (this.customErrorHandler) {
454
+ let res = this.customErrorHandler(err);
455
+ if(res) return;
238
456
  }
239
457
 
458
+ if (_.isObject(err) && _.get(err, 'response')) {
459
+ const msg = _.get(err, 'response.data.message', 'Error in API response');
460
+ throw new Error(msg);
461
+ } else {
462
+ throw new Error(err.message || 'Unknown error');
463
+ }
240
464
  }
241
- }
465
+
466
+ }
package/src/rbt_object.js CHANGED
@@ -2,48 +2,67 @@ import _ from 'lodash';
2
2
 
3
3
  export default class RbtObject {
4
4
 
5
- constructor(record, axiosInstance) {
5
+ constructor(record, axiosInstance, options = {}) {
6
6
  this._axios = axiosInstance;
7
7
  this._internalData = record;
8
+ this.id = record.id;
9
+ this.id_revision = record.id_revision;
10
+ this.rpcMeta = {
11
+ changes: [],
12
+ isNew: options.isNew || false
13
+ };
14
+
8
15
  if(record.data){
9
16
  this._data = record.data;
10
- }
11
- else{
17
+ } else {
12
18
  this._data = record.dataJson ? JSON.parse(record.dataJson) : {};
13
19
  }
20
+
14
21
  }
15
22
 
16
23
  get(path) {
17
24
  return _.get(this._data, path);
18
25
  }
19
26
 
20
- set(path, value) {
21
- _.set(this._data, path, value);
22
- }
23
-
24
27
  getData() {
25
28
  return {
26
29
  ...this._data
27
30
  };
28
31
  }
29
32
 
33
+ _addChange(path) {
34
+ // Ensure no duplicate paths
35
+ if (!this.rpcMeta.changes.includes(path)) {
36
+ this.rpcMeta.changes.push(path);
37
+ }
38
+ }
39
+
40
+ set(path, value) {
41
+ const currentValue = _.get(this._data, path);
42
+ if (!_.isEqual(currentValue, value)) {
43
+ _.set(this._data, path, value);
44
+ this._addChange(path);
45
+ }
46
+ }
47
+
30
48
  setData(newData) {
31
- // Ensure the input is an object
32
49
  if (typeof newData !== 'object' || newData === null) {
33
50
  throw new Error('setData expects an object');
34
51
  }
35
52
 
36
- // Iterate over each key in the newData object
37
53
  Object.keys(newData).forEach(key => {
38
- // Update the corresponding key in this._data
39
- _.set(this._data, key, newData[key]);
54
+ if (!_.isEqual(_.get(this._data, key), newData[key])) {
55
+ _.set(this._data, key, newData[key]);
56
+ this._addChange(key);
57
+ }
40
58
  });
41
59
  }
42
60
 
43
61
  toRecord() {
44
62
  return {
45
63
  ...this._internalData,
46
- dataJson: JSON.stringify(this._data)
64
+ dataJson: JSON.stringify(this._data),
65
+ rpcMeta: this.rpcMeta
47
66
  };
48
67
  }
49
68
 
@@ -56,7 +75,7 @@ export default class RbtObject {
56
75
  delete clonedData.id_revision;
57
76
 
58
77
  // Create a new instance of RbtObject with the cloned data
59
- const clonedObject = new RbtObject(clonedData, this._axios);
78
+ const clonedObject = new RbtObject(clonedData, this._axios, { isNew: true });
60
79
 
61
80
  return clonedObject;
62
81
  }
@@ -80,11 +99,14 @@ export default class RbtObject {
80
99
  this.id_revision = response.data.id_revision;
81
100
  this.type = response.data.type;
82
101
 
102
+ this.rpcMeta.isNew = false;
103
+
83
104
  return this;
84
105
 
85
106
  } catch (e) {
86
107
  console.log('RbtObject.save.error:');
87
108
  console.log(e.response.data);
109
+ throw e;
88
110
  }
89
111
  }
90
112
 
@@ -107,6 +129,7 @@ export default class RbtObject {
107
129
  } catch (e) {
108
130
  console.log('RbtObject.delete.error:');
109
131
  console.log(e.response.data);
132
+ throw e;
110
133
  }
111
134
  }
112
135
 
@@ -0,0 +1,135 @@
1
+ import _ from 'lodash';
2
+
3
+ export default class RbtUser {
4
+
5
+ constructor(record, axiosInstance, options = {}) {
6
+ this._axios = axiosInstance;
7
+ this._internalData = record;
8
+ this.id = record.id;
9
+ this.id_revision = record.id_revision;
10
+ this.rpcMeta = {
11
+ changes: [],
12
+ isNew: options.isNew || false
13
+ };
14
+
15
+ if(record.data){
16
+ this._data = record.data;
17
+ } else {
18
+ this._data = record.dataJson ? JSON.parse(record.dataJson) : {};
19
+ }
20
+
21
+ }
22
+
23
+ get(path) {
24
+ return _.get(this._data, path);
25
+ }
26
+
27
+ getData() {
28
+ return {
29
+ ...this._data
30
+ };
31
+ }
32
+
33
+ _addChange(path) {
34
+ // Ensure no duplicate paths
35
+ if (!this.rpcMeta.changes.includes(path)) {
36
+ this.rpcMeta.changes.push(path);
37
+ }
38
+ }
39
+
40
+ set(path, value) {
41
+ const currentValue = _.get(this._data, path);
42
+ if (!_.isEqual(currentValue, value)) {
43
+ _.set(this._data, path, value);
44
+ this._addChange(path);
45
+ }
46
+ }
47
+
48
+ setData(newData) {
49
+ if (typeof newData !== 'object' || newData === null) {
50
+ throw new Error('setData expects an object');
51
+ }
52
+
53
+ Object.keys(newData).forEach(key => {
54
+ if (!_.isEqual(_.get(this._data, key), newData[key])) {
55
+ _.set(this._data, key, newData[key]);
56
+ this._addChange(key);
57
+ }
58
+ });
59
+ }
60
+
61
+ toRecord() {
62
+ return {
63
+ ...this._internalData,
64
+ dataJson: JSON.stringify(this._data),
65
+ rpcMeta: this.rpcMeta
66
+ };
67
+ }
68
+
69
+ clone() {
70
+ // Create a deep copy of the current object's data
71
+ const clonedData = _.cloneDeep(this._internalData);
72
+
73
+ // Reset unique identifiers to ensure a new ID is generated upon saving
74
+ delete clonedData.id;
75
+ delete clonedData.id_revision;
76
+
77
+ // Create a new instance of RbtUser with the cloned data
78
+ const clonedObject = new RbtUser(clonedData, this._axios, { isNew: true });
79
+
80
+ return clonedObject;
81
+ }
82
+
83
+ async save() {
84
+
85
+ try {
86
+ debugger;
87
+ const record = this.toRecord();
88
+ record.type = '<@iac.user>';
89
+ const response = await this._axios.post('/user_service/saveUser', [record]);
90
+
91
+ if (response.data.ok === false) {
92
+ throw new Error(response.data.message);
93
+ }
94
+
95
+ this._internalData = response.data;
96
+
97
+ this.id = response.data.id;
98
+ this.id_revision = response.data.id_revision;
99
+ this.type = response.data.type;
100
+
101
+ this.rpcMeta.isNew = false;
102
+
103
+ return this;
104
+
105
+ } catch (e) {
106
+ console.log('RbtUser.save.error:');
107
+ console.log(e.response.data);
108
+ throw e;
109
+ }
110
+ }
111
+
112
+ //async delete() {
113
+ // if (!this._internalData.type) {
114
+ // throw new Error('Cannot delete object without type');
115
+ // }
116
+ //
117
+ // try {
118
+ // const record = this.toRecord();
119
+ // const response = await this._axios.post('/object_service/deleteObject', [record]);
120
+ //
121
+ // if (response.data.ok === false) {
122
+ // throw new Error(response.data.message);
123
+ // }
124
+ //
125
+ // this._internalData = response.data;
126
+ // return this;
127
+ //
128
+ // } catch (e) {
129
+ // console.log('RbtUser.delete.error:');
130
+ // console.log(e.response.data);
131
+ // throw e;
132
+ // }
133
+ //}
134
+
135
+ }