roboto-js 1.6.18 → 1.7.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/.cursorignore +2 -0
- package/dist/cjs/index.cjs +14 -2
- package/dist/cjs/rbt_api.cjs +891 -328
- package/dist/cjs/rbt_object.cjs +166 -29
- package/dist/esm/index.js +9 -1
- package/dist/esm/rbt_api.js +418 -44
- package/dist/esm/rbt_object.js +133 -17
- package/dist/index.js +547 -0
- package/dist/rbt_api.js +1951 -0
- package/dist/rbt_file.js +229 -0
- package/dist/rbt_metrics_api.js +206 -0
- package/dist/rbt_object.js +671 -0
- package/dist/rbt_user.js +241 -0
- package/package.json +3 -2
- package/src/index.js +10 -1
- package/src/rbt_api.js +391 -38
- package/src/rbt_object.js +117 -19
package/dist/esm/rbt_api.js
CHANGED
|
@@ -14,6 +14,15 @@ export default class RbtApi {
|
|
|
14
14
|
localStorageAdaptor = null
|
|
15
15
|
}) {
|
|
16
16
|
this.websocketClient = null;
|
|
17
|
+
|
|
18
|
+
// Object cache for sharing instances across multiple load() calls
|
|
19
|
+
this._objectCache = new Map();
|
|
20
|
+
|
|
21
|
+
// Track pending requests to prevent duplicate loads
|
|
22
|
+
this._pendingLoads = new Map();
|
|
23
|
+
|
|
24
|
+
// Track what we've already logged to reduce console spam
|
|
25
|
+
this._loggedCacheEvents = new Set();
|
|
17
26
|
this.axios = axios.create({
|
|
18
27
|
baseURL: baseUrl,
|
|
19
28
|
headers: {
|
|
@@ -32,31 +41,121 @@ export default class RbtApi {
|
|
|
32
41
|
removeItem: key => Promise.resolve(localStorage.removeItem(key))
|
|
33
42
|
};
|
|
34
43
|
}
|
|
44
|
+
|
|
45
|
+
// Synchronous browser hydration: set auth header and in-memory user immediately
|
|
46
|
+
if (typeof localStorage !== 'undefined') {
|
|
47
|
+
try {
|
|
48
|
+
const token = localStorage.getItem('authtoken');
|
|
49
|
+
if (token) {
|
|
50
|
+
this.authtoken = token;
|
|
51
|
+
this.axios.defaults.headers.common['authtoken'] = token;
|
|
52
|
+
}
|
|
53
|
+
} catch {}
|
|
54
|
+
try {
|
|
55
|
+
const cachedUser = localStorage.getItem('rbtUser');
|
|
56
|
+
if (cachedUser) {
|
|
57
|
+
const parsed = JSON.parse(cachedUser);
|
|
58
|
+
if (parsed && parsed.id) {
|
|
59
|
+
this.currentUser = new RbtUser({
|
|
60
|
+
id: parsed.id
|
|
61
|
+
}, this.axios);
|
|
62
|
+
this.currentUser.setData(parsed);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
} catch {}
|
|
66
|
+
}
|
|
35
67
|
this.localDb = null;
|
|
36
68
|
this.iac_session = null;
|
|
37
69
|
this.appServiceHost = baseUrl;
|
|
38
70
|
this.requestCache = {};
|
|
71
|
+
this._loadCurrentUserPromise = null;
|
|
72
|
+
this._loadCurrentUserExtendedPromise = null;
|
|
39
73
|
|
|
40
74
|
// Use the storageAdaptor to get the authToken, if available
|
|
41
75
|
this.initAuthToken(authtoken);
|
|
42
76
|
this.initApiKey(apikey);
|
|
43
77
|
}
|
|
44
78
|
getWebSocketClient() {
|
|
45
|
-
if (
|
|
79
|
+
// Reuse existing WebSocket if it's OPEN or CONNECTING (to prevent race condition)
|
|
80
|
+
if (this.websocketClient && (this.websocketClient.readyState === WebSocket.OPEN || this.websocketClient.readyState === WebSocket.CONNECTING)) {
|
|
81
|
+
return this.websocketClient;
|
|
82
|
+
}
|
|
46
83
|
const baseUrl = this.axios.defaults.baseURL;
|
|
47
84
|
const wsProtocol = baseUrl.startsWith('https') ? 'wss://' : 'ws://';
|
|
48
85
|
const wsUrl = baseUrl.replace(/^https?:\/\//, wsProtocol);
|
|
86
|
+
console.log('[RbtApi] Creating new WebSocket connection to:', wsUrl + '/realtime');
|
|
49
87
|
this.websocketClient = new WebSocket(`${wsUrl}/realtime`);
|
|
50
|
-
this.websocketClient
|
|
88
|
+
this._setupWebSocketHandlers(this.websocketClient);
|
|
89
|
+
return this.websocketClient;
|
|
90
|
+
}
|
|
91
|
+
_setupWebSocketHandlers(ws) {
|
|
92
|
+
ws.onopen = () => {
|
|
51
93
|
console.log('[RbtApi] WebSocket connected.');
|
|
94
|
+
this._wsReconnectAttempts = 0;
|
|
95
|
+
this._wsConnected = true;
|
|
96
|
+
|
|
97
|
+
// Re-subscribe to all objects that were previously subscribed
|
|
98
|
+
if (this._wsSubscriptions) {
|
|
99
|
+
for (const objectId of this._wsSubscriptions) {
|
|
100
|
+
ws.send(JSON.stringify({
|
|
101
|
+
type: 'subscribe',
|
|
102
|
+
objectId
|
|
103
|
+
}));
|
|
104
|
+
}
|
|
105
|
+
}
|
|
52
106
|
};
|
|
53
|
-
|
|
54
|
-
console.warn('[RbtApi] WebSocket closed.
|
|
107
|
+
ws.onclose = event => {
|
|
108
|
+
console.warn('[RbtApi] WebSocket closed:', event.code, event.reason);
|
|
109
|
+
this._wsConnected = false;
|
|
110
|
+
|
|
111
|
+
// Attempt reconnection with exponential backoff
|
|
112
|
+
if (!this._wsManualClose && this._wsReconnectAttempts < 5) {
|
|
113
|
+
const delay = Math.min(1000 * Math.pow(2, this._wsReconnectAttempts), 30000);
|
|
114
|
+
console.log(`[RbtApi] Attempting reconnection in ${delay}ms (attempt ${this._wsReconnectAttempts + 1}/5)`);
|
|
115
|
+
setTimeout(() => {
|
|
116
|
+
this._wsReconnectAttempts++;
|
|
117
|
+
this.websocketClient = null; // Clear the old connection
|
|
118
|
+
this.getWebSocketClient(); // Create new connection
|
|
119
|
+
}, delay);
|
|
120
|
+
}
|
|
55
121
|
};
|
|
56
|
-
|
|
57
|
-
console.error('[RbtApi] WebSocket error:', err
|
|
122
|
+
ws.onerror = err => {
|
|
123
|
+
console.error('[RbtApi] WebSocket error:', err);
|
|
124
|
+
this._wsConnected = false;
|
|
58
125
|
};
|
|
59
|
-
|
|
126
|
+
|
|
127
|
+
// Handle ping/pong for keep-alive
|
|
128
|
+
ws.addEventListener('ping', () => {
|
|
129
|
+
if (ws.readyState === WebSocket.OPEN) {
|
|
130
|
+
ws.pong();
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
// Initialize connection tracking
|
|
135
|
+
this._wsReconnectAttempts = this._wsReconnectAttempts || 0;
|
|
136
|
+
this._wsConnected = false;
|
|
137
|
+
this._wsManualClose = false;
|
|
138
|
+
this._wsSubscriptions = this._wsSubscriptions || new Set();
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Method to track subscriptions for reconnection
|
|
142
|
+
_trackSubscription(objectId) {
|
|
143
|
+
if (!this._wsSubscriptions) this._wsSubscriptions = new Set();
|
|
144
|
+
this._wsSubscriptions.add(objectId);
|
|
145
|
+
}
|
|
146
|
+
_untrackSubscription(objectId) {
|
|
147
|
+
if (this._wsSubscriptions) {
|
|
148
|
+
this._wsSubscriptions.delete(objectId);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Method to gracefully close WebSocket
|
|
153
|
+
closeWebSocket() {
|
|
154
|
+
if (this.websocketClient) {
|
|
155
|
+
this._wsManualClose = true;
|
|
156
|
+
this.websocketClient.close();
|
|
157
|
+
this.websocketClient = null;
|
|
158
|
+
}
|
|
60
159
|
}
|
|
61
160
|
async initAuthToken(authtoken) {
|
|
62
161
|
if (!authtoken && this.localStorageAdaptor) {
|
|
@@ -140,6 +239,9 @@ export default class RbtApi {
|
|
|
140
239
|
this.authtoken = response.data.authToken;
|
|
141
240
|
if (this.localStorageAdaptor) {
|
|
142
241
|
await this.localStorageAdaptor.setItem('authtoken', response.data.authToken);
|
|
242
|
+
if (this.iac_session?.user) {
|
|
243
|
+
await this.localStorageAdaptor.setItem('rbtUser', JSON.stringify(this.iac_session.user));
|
|
244
|
+
}
|
|
143
245
|
}
|
|
144
246
|
return response.data;
|
|
145
247
|
} catch (e) {
|
|
@@ -200,6 +302,9 @@ export default class RbtApi {
|
|
|
200
302
|
}
|
|
201
303
|
async loadCurrentUser() {
|
|
202
304
|
try {
|
|
305
|
+
if (this._loadCurrentUserPromise) {
|
|
306
|
+
return this._loadCurrentUserPromise;
|
|
307
|
+
}
|
|
203
308
|
if (this.currentUser) {
|
|
204
309
|
return this.currentUser;
|
|
205
310
|
}
|
|
@@ -207,15 +312,23 @@ export default class RbtApi {
|
|
|
207
312
|
// NOT IMPLEMENTED
|
|
208
313
|
return null;
|
|
209
314
|
} else if (this.authtoken) {
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
315
|
+
this._loadCurrentUserPromise = (async () => {
|
|
316
|
+
let response = await this.refreshAuthToken(this.authtoken);
|
|
317
|
+
if (!response) return null;
|
|
318
|
+
if (response?.user) {
|
|
319
|
+
this.currentUser = new RbtUser({
|
|
320
|
+
id: response?.user?.id
|
|
321
|
+
}, this.axios);
|
|
322
|
+
this.currentUser.setData(response.user);
|
|
323
|
+
if (this.localStorageAdaptor) {
|
|
324
|
+
await this.localStorageAdaptor.setItem('rbtUser', JSON.stringify(response.user));
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
return this.currentUser;
|
|
328
|
+
})();
|
|
329
|
+
const result = await this._loadCurrentUserPromise;
|
|
330
|
+
this._loadCurrentUserPromise = null;
|
|
331
|
+
return result;
|
|
219
332
|
//this.currentOrg = new RbtObject(response.organization, this.axios);
|
|
220
333
|
//this.currentOrg.type = '<@iac.organization>';
|
|
221
334
|
} else {
|
|
@@ -241,25 +354,49 @@ export default class RbtApi {
|
|
|
241
354
|
}
|
|
242
355
|
}
|
|
243
356
|
async loadCurrentUserExtended() {
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
357
|
+
if (this._loadCurrentUserExtendedPromise) {
|
|
358
|
+
return this._loadCurrentUserExtendedPromise;
|
|
359
|
+
}
|
|
360
|
+
this._loadCurrentUserExtendedPromise = (async () => {
|
|
361
|
+
let currentUser = await this.loadCurrentUser();
|
|
362
|
+
if (currentUser) {
|
|
363
|
+
return this.loadUser(currentUser.id);
|
|
364
|
+
} else {
|
|
365
|
+
return null;
|
|
366
|
+
}
|
|
367
|
+
})();
|
|
368
|
+
try {
|
|
369
|
+
return await this._loadCurrentUserExtendedPromise;
|
|
370
|
+
} finally {
|
|
371
|
+
this._loadCurrentUserExtendedPromise = null;
|
|
249
372
|
}
|
|
250
373
|
}
|
|
251
374
|
async loadUser(userId) {
|
|
252
375
|
let params = {
|
|
253
376
|
id: userId
|
|
254
377
|
};
|
|
378
|
+
const cacheKey = `loadUser:${userId}`;
|
|
379
|
+
const now = Date.now();
|
|
380
|
+
const existing = this.requestCache[cacheKey];
|
|
381
|
+
if (existing && now - existing.time < 10000) {
|
|
382
|
+
// 10s TTL
|
|
383
|
+
return existing.val;
|
|
384
|
+
}
|
|
255
385
|
try {
|
|
256
|
-
const
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
386
|
+
const p = (async () => {
|
|
387
|
+
const response = await this.axios.post('/user_service/loadUser', [params]);
|
|
388
|
+
let userData = response?.data?.user;
|
|
389
|
+
let User = new RbtUser({
|
|
390
|
+
id: userData.id
|
|
391
|
+
}, this.axios);
|
|
392
|
+
User.setData(userData);
|
|
393
|
+
return User;
|
|
394
|
+
})();
|
|
395
|
+
this.requestCache[cacheKey] = {
|
|
396
|
+
val: p,
|
|
397
|
+
time: now
|
|
398
|
+
};
|
|
399
|
+
return await p;
|
|
263
400
|
} catch (e) {
|
|
264
401
|
return this._handleError(e);
|
|
265
402
|
}
|
|
@@ -388,9 +525,10 @@ export default class RbtApi {
|
|
|
388
525
|
*
|
|
389
526
|
* @param {string} type - The type of object to create.
|
|
390
527
|
* @param {Object} dataHash - The data for the new object.
|
|
528
|
+
* @param {Object} options - Additional options including enableRealtime.
|
|
391
529
|
* @returns {Promise<RbtObject>} - The newly created object as an RbtObject.
|
|
392
530
|
*/
|
|
393
|
-
async create(type, dataHash = {}) {
|
|
531
|
+
async create(type, dataHash = {}, options = {}) {
|
|
394
532
|
try {
|
|
395
533
|
const response = await this.axios.post('/object_service/createObject', [type, dataHash]);
|
|
396
534
|
const record = response.data;
|
|
@@ -399,7 +537,8 @@ export default class RbtApi {
|
|
|
399
537
|
}
|
|
400
538
|
return new RbtObject(record, this.axios, {
|
|
401
539
|
isNew: true,
|
|
402
|
-
websocketClient: this.websocketClient
|
|
540
|
+
websocketClient: this.websocketClient,
|
|
541
|
+
enableRealtime: options.enableRealtime || false
|
|
403
542
|
});
|
|
404
543
|
} catch (e) {
|
|
405
544
|
return this._handleError(e);
|
|
@@ -476,7 +615,7 @@ export default class RbtApi {
|
|
|
476
615
|
const responsePromise = this.axios.post('/object_service/queryObjects', [mergedParams]);
|
|
477
616
|
|
|
478
617
|
// Cache the promise of processing data, not just the raw response
|
|
479
|
-
const processingPromise = responsePromise.then(response => this._processResponseData(response)).catch(e => {
|
|
618
|
+
const processingPromise = responsePromise.then(response => this._processResponseData(response, params)).catch(e => {
|
|
480
619
|
delete this.requestCache[paramsKey]; // Ensure cache cleanup on failure
|
|
481
620
|
//console.log('RBTAPI.query ERROR (Processing)', paramsKey, e);
|
|
482
621
|
return this._handleError(e);
|
|
@@ -496,15 +635,28 @@ export default class RbtApi {
|
|
|
496
635
|
return this._handleError(e);
|
|
497
636
|
}
|
|
498
637
|
}
|
|
499
|
-
_processResponseData(response) {
|
|
638
|
+
_processResponseData(response, options = {}) {
|
|
500
639
|
if (response.data.ok === false) {
|
|
501
640
|
return this._handleError(response);
|
|
502
641
|
}
|
|
503
642
|
if (Array.isArray(response.data.items)) {
|
|
504
643
|
//console.log('RBTAPI.query RESPONSE PRE', response.data.items);
|
|
644
|
+
|
|
645
|
+
// Ensure WebSocket client is available if realtime is requested
|
|
646
|
+
let websocketClient = this.websocketClient;
|
|
647
|
+
if (options.enableRealtime && !websocketClient) {
|
|
648
|
+
console.log('[AgentProviderSync] Creating WebSocket client for realtime objects');
|
|
649
|
+
websocketClient = this.getWebSocketClient();
|
|
650
|
+
}
|
|
651
|
+
console.log('[AgentProviderSync] _processResponseData creating objects with:', {
|
|
652
|
+
enableRealtime: options.enableRealtime || false,
|
|
653
|
+
hasWebSocketClient: !!websocketClient,
|
|
654
|
+
itemCount: response.data.items.length
|
|
655
|
+
});
|
|
505
656
|
response.data.items = response.data.items.map(record => {
|
|
506
657
|
return new RbtObject(record, this.axios, {
|
|
507
|
-
websocketClient:
|
|
658
|
+
websocketClient: websocketClient,
|
|
659
|
+
enableRealtime: options.enableRealtime || false
|
|
508
660
|
});
|
|
509
661
|
});
|
|
510
662
|
}
|
|
@@ -522,27 +674,249 @@ export default class RbtApi {
|
|
|
522
674
|
* @returns {Promise<RbtObject|RbtObject[]>} - The loaded object(s) as RbtObject(s).
|
|
523
675
|
*/
|
|
524
676
|
async load(type, ids, params = {}) {
|
|
677
|
+
if (type == '<@iac.user>') {
|
|
678
|
+
debugger;
|
|
679
|
+
}
|
|
525
680
|
try {
|
|
526
681
|
let mergedParams;
|
|
527
682
|
if (Array.isArray(ids)) {
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
683
|
+
// For array requests, check cache for each ID and only load missing ones
|
|
684
|
+
const cachedObjects = [];
|
|
685
|
+
const missingIds = [];
|
|
686
|
+
for (const id of ids) {
|
|
687
|
+
const cacheKey = `${type}:${id}`;
|
|
688
|
+
const cached = this._objectCache.get(cacheKey);
|
|
689
|
+
if (cached) {
|
|
690
|
+
// Only log cache hits once per object to reduce spam
|
|
691
|
+
const hitLogKey = `hit:${cacheKey}`;
|
|
692
|
+
if (!this._loggedCacheEvents.has(hitLogKey)) {
|
|
693
|
+
console.log('[AgentProviderSync] 🎯 roboto.load cache HIT:', {
|
|
694
|
+
type,
|
|
695
|
+
id,
|
|
696
|
+
hasRealtime: !!cached._realtime,
|
|
697
|
+
requestedRealtime: !!params.enableRealtime
|
|
698
|
+
});
|
|
699
|
+
this._loggedCacheEvents.add(hitLogKey);
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
// If realtime is requested but cached object doesn't have it, upgrade it
|
|
703
|
+
if (params.enableRealtime && !cached._realtime) {
|
|
704
|
+
console.log('[AgentProviderSync] 🔄 Upgrading cached object to realtime:', {
|
|
705
|
+
type,
|
|
706
|
+
id
|
|
707
|
+
});
|
|
708
|
+
cached._initRealtime();
|
|
709
|
+
}
|
|
710
|
+
cachedObjects.push(cached);
|
|
711
|
+
} else {
|
|
712
|
+
missingIds.push(id);
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
// Load missing objects
|
|
717
|
+
let loadedObjects = [];
|
|
718
|
+
if (missingIds.length > 0) {
|
|
719
|
+
// Only log bulk cache miss once
|
|
720
|
+
const bulkMissLogKey = `bulk-miss:${type}:${missingIds.join(',')}`;
|
|
721
|
+
if (!this._loggedCacheEvents.has(bulkMissLogKey)) {
|
|
722
|
+
console.log('[AgentProviderSync] 📦 roboto.load cache MISS, loading:', {
|
|
723
|
+
type,
|
|
724
|
+
ids: missingIds
|
|
725
|
+
});
|
|
726
|
+
this._loggedCacheEvents.add(bulkMissLogKey);
|
|
727
|
+
}
|
|
728
|
+
mergedParams = {
|
|
729
|
+
...params,
|
|
730
|
+
where: `id IN ("${missingIds.join(`","`)}")`
|
|
731
|
+
};
|
|
732
|
+
loadedObjects = await this.query(type, mergedParams);
|
|
733
|
+
|
|
734
|
+
// Cache the newly loaded objects
|
|
735
|
+
for (const obj of loadedObjects) {
|
|
736
|
+
const cacheKey = `${type}:${obj.id}`;
|
|
737
|
+
this._objectCache.set(cacheKey, obj);
|
|
738
|
+
|
|
739
|
+
// Only log cache set once per object to reduce spam
|
|
740
|
+
const setLogKey = `set:${cacheKey}`;
|
|
741
|
+
if (!this._loggedCacheEvents.has(setLogKey)) {
|
|
742
|
+
console.log('[AgentProviderSync] 💾 roboto.load cached object:', {
|
|
743
|
+
type,
|
|
744
|
+
id: obj.id
|
|
745
|
+
});
|
|
746
|
+
this._loggedCacheEvents.add(setLogKey);
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
// Return combined results in original order
|
|
752
|
+
const result = [];
|
|
753
|
+
for (const id of ids) {
|
|
754
|
+
const cacheKey = `${type}:${id}`;
|
|
755
|
+
const obj = this._objectCache.get(cacheKey);
|
|
756
|
+
if (obj) {
|
|
757
|
+
// Ensure realtime is enabled if requested
|
|
758
|
+
if (params.enableRealtime && !obj._realtime) {
|
|
759
|
+
obj._initRealtime();
|
|
760
|
+
}
|
|
761
|
+
result.push(obj);
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
return result;
|
|
533
765
|
} else {
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
766
|
+
// For single object requests, check cache first
|
|
767
|
+
const cacheKey = `${type}:${ids}`;
|
|
768
|
+
const cached = this._objectCache.get(cacheKey);
|
|
769
|
+
if (cached) {
|
|
770
|
+
// Only log cache hits once per object to reduce spam
|
|
771
|
+
const hitLogKey = `hit:${cacheKey}`;
|
|
772
|
+
if (!this._loggedCacheEvents.has(hitLogKey) || console.log('[AgentProviderSync] 🎯 roboto.load cache HIT:', {
|
|
773
|
+
type,
|
|
774
|
+
id: ids,
|
|
775
|
+
hasRealtime: !!cached._realtime,
|
|
776
|
+
requestedRealtime: !!params.enableRealtime
|
|
777
|
+
})) {
|
|
778
|
+
this._loggedCacheEvents.add(hitLogKey);
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
// If realtime is requested but cached object doesn't have it, upgrade it
|
|
782
|
+
if (params.enableRealtime && !cached._realtime) {
|
|
783
|
+
console.log('[AgentProviderSync] 🔄 Upgrading cached object to realtime:', {
|
|
784
|
+
type,
|
|
785
|
+
id: ids
|
|
786
|
+
});
|
|
787
|
+
cached._initRealtime();
|
|
788
|
+
}
|
|
789
|
+
return cached;
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
// Check if we're already loading this object
|
|
793
|
+
const pendingKey = `${type}:${ids}`;
|
|
794
|
+
if (this._pendingLoads.has(pendingKey)) {
|
|
795
|
+
// Wait for the existing request to complete
|
|
796
|
+
return await this._pendingLoads.get(pendingKey);
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
// Only log cache miss once per object to reduce spam
|
|
800
|
+
const missLogKey = `miss:${cacheKey}`;
|
|
801
|
+
if (!this._loggedCacheEvents.has(missLogKey)) {
|
|
802
|
+
console.log('[AgentProviderSync] 📦 roboto.load cache MISS, loading:', {
|
|
803
|
+
type,
|
|
804
|
+
id: ids
|
|
805
|
+
});
|
|
806
|
+
this._loggedCacheEvents.add(missLogKey);
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
// Create the loading promise and store it to prevent duplicate requests
|
|
810
|
+
const loadPromise = (async () => {
|
|
811
|
+
try {
|
|
812
|
+
mergedParams = {
|
|
813
|
+
...params,
|
|
814
|
+
where: `id="${ids}"`
|
|
815
|
+
};
|
|
816
|
+
let res = await this.query(type, mergedParams);
|
|
817
|
+
const obj = res[0];
|
|
818
|
+
if (obj) {
|
|
819
|
+
// Cache the loaded object
|
|
820
|
+
this._objectCache.set(cacheKey, obj);
|
|
821
|
+
|
|
822
|
+
// Only log cache set once per object to reduce spam
|
|
823
|
+
const setLogKey = `set:${cacheKey}`;
|
|
824
|
+
if (!this._loggedCacheEvents.has(setLogKey)) {
|
|
825
|
+
console.log('[AgentProviderSync] 💾 roboto.load cached object:', {
|
|
826
|
+
type,
|
|
827
|
+
id: ids
|
|
828
|
+
});
|
|
829
|
+
this._loggedCacheEvents.add(setLogKey);
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
return obj;
|
|
833
|
+
} finally {
|
|
834
|
+
// Remove from pending loads
|
|
835
|
+
this._pendingLoads.delete(pendingKey);
|
|
836
|
+
}
|
|
837
|
+
})();
|
|
838
|
+
|
|
839
|
+
// Store the promise so other concurrent requests can await it
|
|
840
|
+
this._pendingLoads.set(pendingKey, loadPromise);
|
|
841
|
+
return await loadPromise;
|
|
540
842
|
}
|
|
541
843
|
} catch (e) {
|
|
542
844
|
return this._handleError(e);
|
|
543
845
|
}
|
|
544
846
|
}
|
|
545
847
|
|
|
848
|
+
/**
|
|
849
|
+
* Clears the object cache. Useful for cache invalidation.
|
|
850
|
+
* @param {string} type - Optional object type to clear. If not provided, clears all.
|
|
851
|
+
* @param {string} id - Optional object ID to clear. If not provided with type, clears all objects of that type.
|
|
852
|
+
*/
|
|
853
|
+
clearCache(type = null, id = null) {
|
|
854
|
+
if (type && id) {
|
|
855
|
+
// Clear specific object
|
|
856
|
+
const cacheKey = `${type}:${id}`;
|
|
857
|
+
const removed = this._objectCache.delete(cacheKey);
|
|
858
|
+
|
|
859
|
+
// Clear related log tracking
|
|
860
|
+
this._loggedCacheEvents.delete(`hit:${cacheKey}`);
|
|
861
|
+
this._loggedCacheEvents.delete(`miss:${cacheKey}`);
|
|
862
|
+
this._loggedCacheEvents.delete(`set:${cacheKey}`);
|
|
863
|
+
console.log('[AgentProviderSync] 🗑️ roboto.clearCache specific object:', {
|
|
864
|
+
type,
|
|
865
|
+
id,
|
|
866
|
+
removed
|
|
867
|
+
});
|
|
868
|
+
} else if (type) {
|
|
869
|
+
// Clear all objects of a specific type
|
|
870
|
+
let removedCount = 0;
|
|
871
|
+
for (const [key] of this._objectCache) {
|
|
872
|
+
if (key.startsWith(`${type}:`)) {
|
|
873
|
+
this._objectCache.delete(key);
|
|
874
|
+
removedCount++;
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
// Clear related log tracking for this type
|
|
879
|
+
for (const logKey of this._loggedCacheEvents) {
|
|
880
|
+
if (logKey.includes(`${type}:`)) {
|
|
881
|
+
this._loggedCacheEvents.delete(logKey);
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
console.log('[AgentProviderSync] 🗑️ roboto.clearCache by type:', {
|
|
885
|
+
type,
|
|
886
|
+
removedCount
|
|
887
|
+
});
|
|
888
|
+
} else {
|
|
889
|
+
// Clear all cached objects
|
|
890
|
+
const size = this._objectCache.size;
|
|
891
|
+
this._objectCache.clear();
|
|
892
|
+
this._loggedCacheEvents.clear();
|
|
893
|
+
this._pendingLoads.clear();
|
|
894
|
+
console.log('[AgentProviderSync] 🗑️ roboto.clearCache all objects:', {
|
|
895
|
+
clearedCount: size
|
|
896
|
+
});
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
/**
|
|
901
|
+
* Gets cache status for debugging
|
|
902
|
+
* @returns {Object} Cache status information
|
|
903
|
+
*/
|
|
904
|
+
getCacheStatus() {
|
|
905
|
+
const cacheEntries = Array.from(this._objectCache.entries()).map(([key, obj]) => ({
|
|
906
|
+
key,
|
|
907
|
+
id: obj.id,
|
|
908
|
+
type: obj._internalData?.type || 'unknown'
|
|
909
|
+
}));
|
|
910
|
+
const pendingLoads = Array.from(this._pendingLoads.keys());
|
|
911
|
+
return {
|
|
912
|
+
cacheSize: this._objectCache.size,
|
|
913
|
+
pendingLoads: pendingLoads.length,
|
|
914
|
+
loggedEvents: this._loggedCacheEvents.size,
|
|
915
|
+
entries: cacheEntries,
|
|
916
|
+
pendingKeys: pendingLoads
|
|
917
|
+
};
|
|
918
|
+
}
|
|
919
|
+
|
|
546
920
|
/**
|
|
547
921
|
* Makes a POST request to a specific endpoint to run a task and handle progress updates.
|
|
548
922
|
*
|