roboto-js 1.0.21 → 1.1.2

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,81 @@ export default class RbtApi {
66
76
  }
67
77
  }
68
78
 
79
+ async logout() {
80
+ try {
81
+ // Call logout endpoint if necessary. Here, I assume there's a '/user_service/logoutUser' endpoint.
82
+ // This is optional and depends on how your backend is set up.
83
+ const response = await this.axios.post('/user_service/logoutUser');
84
+
85
+ if (response.data.ok === false) {
86
+ return this._handleError(response);
87
+ }
88
+
89
+ // Clear the iac_session and remove the auth token from axios headers
90
+ this.iac_session = null;
91
+ if (this.axios.defaults.headers.common['authtoken']) {
92
+ delete this.axios.defaults.headers.common['authtoken'];
93
+ }
94
+
95
+ // Clear localStorage if it's being used
96
+ if (typeof localStorage !== 'undefined') {
97
+ localStorage.removeItem('authtoken');
98
+ }
99
+
100
+ // Return some kind of success response or the response from the server
101
+ return response.data;
102
+ } catch (e) {
103
+ this._handleError(e);
104
+ }
105
+ }
106
+
107
+ async loadCurrentUser(){
108
+
109
+ // TODO - get the actual user from the session
110
+ let params = { id: 'superuser_tom' };
111
+
112
+ try {
113
+
114
+ const response = await this.axios.post('/user_service/loadUser', [params]);
115
+ let userData = response?.data?.user;
116
+
117
+ let User = new RbtUser({ id: userData.id }, this.axios);
118
+ User.setData(userData);
119
+ return User;
120
+
121
+ //const record = response.data;
122
+ //
123
+ //if(dataHash){
124
+ // record.data = dataHash;
125
+ //}
126
+ //return new RbtObject(record, this.axios);
127
+ } catch (e) {
128
+ return this._handleError(e);
129
+ }
130
+
131
+ }
132
+
133
+ async refreshAuthToken(authtoken){
134
+
135
+ try {
136
+
137
+ const response = await this.axios.post('/user_service/refreshAuthToken', [authtoken]);
138
+ return response.data;
139
+
140
+ } catch (e) {
141
+
142
+ this._handleError(e);
143
+ }
144
+ }
145
+
146
+
147
+ /**
148
+ * Registers a new user.
149
+ *
150
+ * @param {Object} dataHash - The data for the new user.
151
+ * @returns {Promise<RbtObject>} - The newly created user as an RbtObject.
152
+ *
153
+ */
69
154
  async registerUser(dataHash={}) {
70
155
  try {
71
156
  const response = await this.axios.post('/user_service/registerUser', [dataHash]);
@@ -76,10 +161,18 @@ export default class RbtApi {
76
161
  }
77
162
  return new RbtObject(record, this.axios);
78
163
  } catch (e) {
164
+ debugger;
79
165
  return this._handleError(e);
80
166
  }
81
167
  }
82
168
 
169
+ /**
170
+ * Creates a new file in the system.
171
+ *
172
+ * @param {Object} dataHash - The data for the new file.
173
+ * @returns {Promise<RbtFile>} - The newly created file as an RbtFile.
174
+ *
175
+ */
83
176
  async createFile(dataHash){
84
177
 
85
178
  //return this.create('<@filekit.file>', dataHash);
@@ -98,6 +191,13 @@ export default class RbtApi {
98
191
  }
99
192
 
100
193
 
194
+ /**
195
+ * Creates a new object of the given type.
196
+ *
197
+ * @param {string} type - The type of object to create.
198
+ * @param {Object} dataHash - The data for the new object.
199
+ * @returns {Promise<RbtObject>} - The newly created object as an RbtObject.
200
+ */
101
201
  async create(type, dataHash={}) {
102
202
  try {
103
203
  const response = await this.axios.post('/object_service/createObject', [type, dataHash]);
@@ -106,19 +206,50 @@ export default class RbtApi {
106
206
  if(dataHash){
107
207
  record.data = dataHash;
108
208
  }
109
- return new RbtObject(record, this.axios);
209
+ return new RbtObject(record, this.axios, { isNew: true });
110
210
  } catch (e) {
111
211
  return this._handleError(e);
112
212
  }
113
213
  }
114
214
 
215
+ /**
216
+ * Queries objects of a given type based on specified parameters.
217
+ *
218
+ * @param {string} type - The type of object to query, specified as a doctree.typedef.
219
+ * @param {Object} params - The query parameters, including optional filters and configurations.
220
+ * @returns {Promise<Array<RbtObject>>} - An array of queried objects as RbtObjects.
221
+ *
222
+ * The `params` object can include the following properties:
223
+ * - where: A SQL-like where clause string for filtering the results.
224
+ * - orderBy: An object specifying the ordering of the results. It should include:
225
+ * - column: The attribute name to sort by. This must be either a column in the @doctree.model schema or an indexed type attribute.
226
+ * - direction: The sort direction, either 'ASC' for ascending or 'DESC' for descending.
227
+ * - limit: An object to control the pagination of results. It includes:
228
+ * - offset: The starting point from where to fetch the results.
229
+ * - results: The maximum number of results to return.
230
+ * - resolveReferences: An array of attribute names whose references should be resolved in the returned objects.
231
+ * - timeout: A numerical value in milliseconds to set a maximum time limit for the query execution.
232
+ *
233
+ * Example usage:
234
+ * query("<@testuser>", {
235
+ * where: 'email="tom@pospa.com"',
236
+ * orderBy: { column: 'timeCreated', direction: 'DESC' },
237
+ * limit: { offset: 0, results: 50 },
238
+ * resolveReferences: ['translatableContent']
239
+ * });
240
+ *
241
+ * Note: A default orderBy is applied if none is provided, ordering items by 'timeCreated' in descending order.
242
+ */
115
243
  async query(type, params = {}) {
116
244
  try {
117
245
  params.type = type;
118
246
 
119
- // default
247
+ // Default ordering and pagination
120
248
  const defaultOrderBy = { orderBy: { column: 'timeCreated', direction: 'DESC' } };
121
- const mergedParams = { ...defaultOrderBy, ...params };
249
+ const defaultLimit = { limit: { offset: 0, results: 50 } };
250
+
251
+ // Merge defaults with provided params
252
+ const mergedParams = { ...defaultOrderBy, ...defaultLimit, ...params };
122
253
 
123
254
  const response = await this.axios.post('/object_service/queryObjects', [mergedParams]);
124
255
  if (response.data.ok === false) {
@@ -127,7 +258,7 @@ export default class RbtApi {
127
258
 
128
259
  // Process items into RbtObject instances
129
260
  if (Array.isArray(response.data.items)) {
130
- response.data.items = response.data.items.map(record => new RbtObject(record, this.axios));
261
+ response.data.items = response.data.items.map(record => new RbtObject(record, this.axios, { isNew: true }));
131
262
  }
132
263
  return response.data.items;
133
264
  } catch (e) {
@@ -135,15 +266,26 @@ export default class RbtApi {
135
266
  }
136
267
  }
137
268
 
138
- async load(type, ids){
269
+ /**
270
+ * Loads one or multiple objects of a given type by their IDs.
271
+ *
272
+ * @param {string} type - The type of object to load.
273
+ * @param {Array<string>|string} ids - The ID(s) of the object(s) to load.
274
+ *
275
+ * @returns {Promise<RbtObject|RbtObject[]>} - The loaded object(s) as RbtObject(s).
276
+ */
277
+ async load(type, ids, params={}){
139
278
 
140
279
  try{
141
- debugger;
280
+ let mergedParams;
281
+
142
282
  if(Array.isArray(ids)){
143
- return this.query(type, { where: "id IN ("+ids.join(',')+")" });
283
+ mergedParams = { ...params, where: "id IN (" + ids.join(',') + ")" };
284
+ return this.query(type, mergedParams);
144
285
  }
145
286
  else{
146
- let res = await this.query(type, { where: "id="+ids});
287
+ mergedParams = { ...params, where: "id="+ids };
288
+ let res = await this.query(type, mergedParams);
147
289
  return res[0];
148
290
  }
149
291
 
@@ -153,6 +295,112 @@ export default class RbtApi {
153
295
 
154
296
  }
155
297
 
298
+ /**
299
+ * Makes a POST request to a specific endpoint to run a task and handle progress updates.
300
+ *
301
+ * @param {Object} params - The parameters to be sent in the POST request.
302
+ * @param {Object} callbacks - An object containing callback functions for progress and error handling.
303
+ *
304
+ * The function expects a response in the following format:
305
+ * {
306
+ * ok: boolean, // Indicates if the request was successful or not
307
+ * jobId: string, // The job identifier
308
+ * status: string // Can be 'RUNNING', 'DONE', or 'ERROR'
309
+ * }
310
+ */
311
+ async runTask(params = {}, callbacks = {}) {
312
+ const { onProgress, onError, onFinish } = callbacks;
313
+
314
+ try {
315
+ const response = await this.post('http://localhost:3004/runChain', params);
316
+ // Check if response and response.data are defined
317
+ if (!response) {
318
+ throw new Error('Invalid server response');
319
+ }
320
+
321
+ // Validate response structure
322
+ const { ok, jobId, status, message, output } = response;
323
+ if (!ok || typeof jobId !== 'string' || typeof status !== 'string') {
324
+ throw new Error('Invalid response structure');
325
+ }
326
+
327
+ // If the task is still in progress, start polling for updates
328
+ if (status === 'RUNNING' || status === 'SCHEDULED' || status === 'QUEUED') {
329
+ this.pollTaskProgress(jobId, callbacks);
330
+ }
331
+ if (status === 'ERROR' && onError){
332
+ // Provide the current progress to the callback function
333
+ onError(response);
334
+ }
335
+ if (status === 'DONE' && onFinish){
336
+ // Provide the current progress to the callback function
337
+ console.log('Finish (request) ',response);
338
+ onFinish(response);
339
+ }
340
+
341
+ return { ok, jobId, status, message, output };
342
+
343
+ } catch (e) {
344
+
345
+ if (typeof onError === 'function') {
346
+ onError(e);
347
+ } else {
348
+ console.error('Error in runTask:', e);
349
+ }
350
+ return { ok: false, jobId: null, status: 'ERROR', error: e.message };
351
+
352
+ }
353
+ }
354
+
355
+
356
+ /**
357
+ * Polls the progress of a long-running task.
358
+ *
359
+ * @param {string} jobId - The ID of the job to poll for progress.
360
+ * @param {function} onProgress - Callback function that receives progress updates.
361
+ *
362
+ * The function periodically sends GET requests to check the task's progress
363
+ * and reports back via the provided callback function. The polling stops when
364
+ * the task is completed or an error occurs.
365
+ */
366
+ async pollTaskProgress(jobId, callbacks) {
367
+ const { onProgress, onError, onFinish } = callbacks;
368
+ try {
369
+ const checkProgress = async () => {
370
+ const response = await this.get(`http://localhost:3004/pollChainProgress`, { jobId: jobId });
371
+
372
+ // If the task is still in progress, start polling for updates
373
+ if (response.status === 'DONE' && onFinish){
374
+ // Provide the current progress to the callback function
375
+ console.log('Finish (progress) ',response);
376
+ onFinish(response);
377
+ }
378
+ if (response.status === 'ERROR' && onError){
379
+ // Provide the current progress to the callback function
380
+ onError(response);
381
+ }
382
+
383
+ // Provide the current progress to the callback function
384
+ if(response.status == 'RUNNING'){
385
+ onProgress(response);
386
+ }
387
+
388
+ // Continue polling if the status is 'RUNNING'
389
+ if (['RUNNING'].includes(response.status)) {
390
+ setTimeout(checkProgress, 1000); // Poll every 2 seconds
391
+ }
392
+ };
393
+
394
+ checkProgress();
395
+ } catch (e) {
396
+ // Handle error based on your application's logic
397
+ return this._handleError(e);
398
+ }
399
+ }
400
+
401
+
402
+
403
+
156
404
  /**
157
405
  * Performs a GET request to the specified endpoint.
158
406
  *
@@ -224,18 +472,31 @@ export default class RbtApi {
224
472
  }
225
473
 
226
474
 
227
- _handleError(err){
475
+ setErrorHandler(customErrorHandler) {
476
+ this.customErrorHandler = customErrorHandler;
477
+ }
228
478
 
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);
479
+ _handleError(err) {
480
+
481
+ debugger;
482
+ // Invoke the custom error handler if provided
483
+ if (this.customErrorHandler) {
484
+ let res = this.customErrorHandler(err);
485
+ if(res) return;
238
486
  }
239
487
 
488
+ if (_.isObject(err) && _.get(err, 'response')) {
489
+ const msg = _.get(err, 'response.data.message', 'Error in API response');
490
+
491
+ if(msg.key){
492
+ throw new Error(msg.key);
493
+ }
494
+ else{
495
+ throw new Error(msg);
496
+ }
497
+ } else {
498
+ throw new Error(err.message || 'Unknown error');
499
+ }
240
500
  }
241
- }
501
+
502
+ }
package/src/rbt_object.js CHANGED
@@ -2,50 +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
8
  this.id = record.id;
9
9
  this.id_revision = record.id_revision;
10
+ this.rpcMeta = {
11
+ changes: [],
12
+ isNew: options.isNew || false
13
+ };
14
+
10
15
  if(record.data){
11
- this._data = record.data;
12
- }
13
- else{
14
- this._data = record.dataJson ? JSON.parse(record.dataJson) : {};
16
+ this._data = this._deepUnpackJson(record.data);
17
+ } else {
18
+ this._data = record.dataJson ? this._deepUnpackJson(record.dataJson) : {};
15
19
  }
20
+
16
21
  }
17
22
 
18
23
  get(path) {
19
24
  return _.get(this._data, path);
20
25
  }
21
26
 
22
- set(path, value) {
23
- _.set(this._data, path, value);
24
- }
25
-
26
27
  getData() {
27
28
  return {
28
29
  ...this._data
29
30
  };
30
31
  }
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
+
32
48
  setData(newData) {
33
- // Ensure the input is an object
34
49
  if (typeof newData !== 'object' || newData === null) {
35
50
  throw new Error('setData expects an object');
36
51
  }
37
52
 
38
- // Iterate over each key in the newData object
39
53
  Object.keys(newData).forEach(key => {
40
- // Update the corresponding key in this._data
41
- _.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
+ }
42
58
  });
43
59
  }
44
60
 
45
61
  toRecord() {
46
62
  return {
47
63
  ...this._internalData,
48
- dataJson: JSON.stringify(this._data)
64
+ dataJson: JSON.stringify(this._data),
65
+ rpcMeta: this.rpcMeta
49
66
  };
50
67
  }
51
68
 
@@ -58,7 +75,7 @@ export default class RbtObject {
58
75
  delete clonedData.id_revision;
59
76
 
60
77
  // Create a new instance of RbtObject with the cloned data
61
- const clonedObject = new RbtObject(clonedData, this._axios);
78
+ const clonedObject = new RbtObject(clonedData, this._axios, { isNew: true });
62
79
 
63
80
  return clonedObject;
64
81
  }
@@ -82,11 +99,14 @@ export default class RbtObject {
82
99
  this.id_revision = response.data.id_revision;
83
100
  this.type = response.data.type;
84
101
 
102
+ this.rpcMeta.isNew = false;
103
+
85
104
  return this;
86
105
 
87
106
  } catch (e) {
88
107
  console.log('RbtObject.save.error:');
89
108
  console.log(e.response.data);
109
+ throw e;
90
110
  }
91
111
  }
92
112
 
@@ -109,7 +129,25 @@ export default class RbtObject {
109
129
  } catch (e) {
110
130
  console.log('RbtObject.delete.error:');
111
131
  console.log(e.response.data);
132
+ throw e;
133
+ }
134
+ }
135
+
136
+ _deepUnpackJson(value){
137
+ if (typeof value === 'string') {
138
+ try {
139
+ const parsed = JSON.parse(value);
140
+ return this._deepUnpackJson(parsed); // Recursively parse if the result is a string, object, or array
141
+ } catch (e) {
142
+ return value; // Return the original string if parsing fails
143
+ }
144
+ } else if (value !== null && typeof value === 'object') {
145
+ // If it's an object (including arrays), recursively parse each value
146
+ for (const key in value) {
147
+ value[key] = this._deepUnpackJson(value[key]);
148
+ }
112
149
  }
150
+ return value;
113
151
  }
114
152
 
115
153
  }
@@ -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
+ }