roboto-js 1.6.17 → 1.7.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/.cursorignore +2 -0
- package/dist/cjs/index.cjs +13 -1
- package/dist/cjs/rbt_api.cjs +891 -328
- package/dist/cjs/rbt_metrics_api.cjs +23 -3
- package/dist/cjs/rbt_object.cjs +166 -29
- package/dist/esm/index.js +8 -0
- package/dist/esm/rbt_api.js +418 -44
- package/dist/esm/rbt_metrics_api.js +16 -2
- package/dist/esm/rbt_object.js +133 -17
- package/package.json +3 -2
- package/src/index.js +9 -0
- package/src/rbt_api.js +391 -38
- package/src/rbt_metrics_api.js +16 -2
- package/src/rbt_object.js +117 -19
package/src/rbt_object.js
CHANGED
|
@@ -19,15 +19,27 @@ export default class RbtObject{
|
|
|
19
19
|
this._data = record.dataJson ? this._deepUnpackJson(record.dataJson) : {};
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
if (options.websocketClient && this.id) {
|
|
22
|
+
if (options.websocketClient && this.id && options.enableRealtime) {
|
|
23
23
|
this._realtime = true;
|
|
24
24
|
this._ws = options.websocketClient;
|
|
25
25
|
this._subscribeToRealtime(this._ws);
|
|
26
|
+
} else if (options.websocketClient && this.id) {
|
|
27
|
+
// Store websocket client for potential later use, but don't auto-subscribe
|
|
28
|
+
this._ws = options.websocketClient;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// If enableRealtime is requested but no websocketClient provided, try to initialize lazily
|
|
32
|
+
if (options.enableRealtime && this.id && !this._realtime) {
|
|
33
|
+
this._initRealtime();
|
|
26
34
|
}
|
|
27
35
|
this._eventHandlers = {
|
|
28
36
|
change: [],
|
|
29
37
|
save: []
|
|
30
38
|
};
|
|
39
|
+
|
|
40
|
+
// Debounce properties for WebSocket broadcasting
|
|
41
|
+
this._broadcastDebounceTimer = null;
|
|
42
|
+
this._pendingBroadcasts = new Map(); // path -> value
|
|
31
43
|
}
|
|
32
44
|
|
|
33
45
|
get(path) {
|
|
@@ -86,16 +98,22 @@ export default class RbtObject{
|
|
|
86
98
|
if (!_.isEqual(currentValue, mergedValue)) {
|
|
87
99
|
_.set(this._data, path, mergedValue); // Set the merged value at the deep path
|
|
88
100
|
this._addChange(path);
|
|
101
|
+
// Trigger local change events for any registered handlers
|
|
102
|
+
this._trigger('change', { path, value: mergedValue, options });
|
|
89
103
|
}
|
|
90
104
|
} else {
|
|
91
105
|
// If value is undefined and not merging, delete the key
|
|
92
106
|
if (value === undefined) {
|
|
93
107
|
_.unset(this._data, path);
|
|
94
108
|
this._addChange(path);
|
|
109
|
+
// Trigger local change events for any registered handlers
|
|
110
|
+
this._trigger('change', { path, value: undefined, options });
|
|
95
111
|
} else if (!_.isEqual(currentValue, value)) {
|
|
96
112
|
_.set(this._data, path, value); // Set the value directly at the deep path
|
|
97
113
|
this._addChange(path);
|
|
98
114
|
this._broadcastChange(path, value);
|
|
115
|
+
// Trigger local change events for any registered handlers
|
|
116
|
+
this._trigger('change', { path, value, options });
|
|
99
117
|
}
|
|
100
118
|
}
|
|
101
119
|
}
|
|
@@ -125,6 +143,8 @@ export default class RbtObject{
|
|
|
125
143
|
if (!_.isEqual(currentValue, mergedValue)) {
|
|
126
144
|
_.set(this._data, path, mergedValue);
|
|
127
145
|
this._addChange(path);
|
|
146
|
+
// Trigger change event for this path
|
|
147
|
+
this._trigger('change', { path, value: mergedValue, options });
|
|
128
148
|
}
|
|
129
149
|
} else if (!_.isEqual(currentValue, newValue)) {
|
|
130
150
|
_.setWith(this._data, path, newValue, (nsValue, key, nsObject, nsPath) => {
|
|
@@ -134,6 +154,8 @@ export default class RbtObject{
|
|
|
134
154
|
return nsValue;
|
|
135
155
|
});
|
|
136
156
|
this._addChange(path);
|
|
157
|
+
// Trigger change event for this path
|
|
158
|
+
this._trigger('change', { path, value: newValue, options });
|
|
137
159
|
}
|
|
138
160
|
});
|
|
139
161
|
}
|
|
@@ -341,11 +363,16 @@ export default class RbtObject{
|
|
|
341
363
|
_initRealtime() {
|
|
342
364
|
if (this._realtime || !this._axios) return;
|
|
343
365
|
|
|
344
|
-
//
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
366
|
+
// Use stored websocket client if available
|
|
367
|
+
let ws = this._ws;
|
|
368
|
+
|
|
369
|
+
// Otherwise, lazily pull WebSocket from parent API (injected via axios instance)
|
|
370
|
+
if (!ws) {
|
|
371
|
+
const api = this._axios?.__rbtApiInstance;
|
|
372
|
+
if (!api || typeof api.getWebSocketClient !== 'function') return;
|
|
373
|
+
ws = api.getWebSocketClient();
|
|
374
|
+
}
|
|
375
|
+
|
|
349
376
|
if (!ws || this._realtime) return;
|
|
350
377
|
|
|
351
378
|
this._ws = ws;
|
|
@@ -354,6 +381,12 @@ export default class RbtObject{
|
|
|
354
381
|
}
|
|
355
382
|
|
|
356
383
|
_subscribeToRealtime(ws) {
|
|
384
|
+
// Track subscription for reconnection
|
|
385
|
+
const api = this._axios?.__rbtApiInstance;
|
|
386
|
+
if (api && typeof api._trackSubscription === 'function') {
|
|
387
|
+
api._trackSubscription(this.id);
|
|
388
|
+
}
|
|
389
|
+
|
|
357
390
|
if (ws.readyState === 1) {
|
|
358
391
|
ws.send(JSON.stringify({ type: 'subscribe', objectId: this.id }));
|
|
359
392
|
} else {
|
|
@@ -363,42 +396,107 @@ export default class RbtObject{
|
|
|
363
396
|
}
|
|
364
397
|
|
|
365
398
|
ws.addEventListener('message', (event) => {
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
399
|
+
try {
|
|
400
|
+
const msg = JSON.parse(event.data);
|
|
401
|
+
if (msg.objectId !== this.id) return;
|
|
402
|
+
|
|
403
|
+
if (msg.type === 'update') {
|
|
404
|
+
if (msg.delta && msg.delta.path !== undefined) {
|
|
405
|
+
_.set(this._data, msg.delta.path, msg.delta.value);
|
|
406
|
+
|
|
407
|
+
// Add metadata to indicate if this is state sync vs live update
|
|
408
|
+
const changeData = {
|
|
409
|
+
...msg.delta,
|
|
410
|
+
isStateSync: msg.isStateSync || false,
|
|
411
|
+
source: msg.isStateSync ? 'state-sync' : 'realtime',
|
|
412
|
+
userId: msg.userId || msg.delta.userId || null
|
|
413
|
+
};
|
|
414
|
+
|
|
415
|
+
this._trigger('change', changeData);
|
|
416
|
+
|
|
417
|
+
if (msg.isStateSync) {
|
|
418
|
+
console.log('[RbtObject] Applied state sync:', msg.delta.path, '=', msg.delta.value);
|
|
419
|
+
}
|
|
420
|
+
} else {
|
|
421
|
+
console.warn('[RbtObject] Received update message without valid delta:', msg);
|
|
422
|
+
}
|
|
423
|
+
} else if (msg.type === 'save') {
|
|
424
|
+
this._trigger('save', msg.revision || {});
|
|
425
|
+
}
|
|
426
|
+
} catch (error) {
|
|
427
|
+
console.error('[RbtObject] Error processing WebSocket message:', error, event.data);
|
|
374
428
|
}
|
|
375
429
|
});
|
|
376
430
|
}
|
|
377
431
|
|
|
378
|
-
|
|
432
|
+
// General change handler for both local and remote changes
|
|
433
|
+
onChange(cb) {
|
|
379
434
|
this._eventHandlers.change.push(cb);
|
|
380
|
-
this
|
|
435
|
+
// Auto-initialize realtime if this object has WebSocket capability
|
|
436
|
+
if (this._ws && !this._realtime) {
|
|
437
|
+
this._initRealtime();
|
|
438
|
+
}
|
|
381
439
|
}
|
|
382
440
|
|
|
383
|
-
|
|
441
|
+
// General save handler for both local and remote saves
|
|
442
|
+
onSave(cb) {
|
|
384
443
|
this._eventHandlers.save.push(cb);
|
|
385
|
-
|
|
444
|
+
// Note: Does not initialize realtime connection
|
|
386
445
|
}
|
|
387
446
|
|
|
388
447
|
_trigger(type, data) {
|
|
448
|
+
console.log('[AgentProviderSync] _trigger called:', type, 'handlers:', this._eventHandlers[type]?.length, 'data:', data)
|
|
389
449
|
for (const fn of this._eventHandlers[type] || []) {
|
|
390
450
|
fn(data);
|
|
391
451
|
}
|
|
392
452
|
}
|
|
393
453
|
|
|
394
454
|
_broadcastChange(path, value) {
|
|
395
|
-
if (this._realtime
|
|
455
|
+
if (!this._realtime || !this._ws || this._ws.readyState !== 1) {
|
|
456
|
+
return;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
// Store the pending broadcast
|
|
460
|
+
this._pendingBroadcasts.set(path, value);
|
|
461
|
+
|
|
462
|
+
// Clear existing timer if any
|
|
463
|
+
if (this._broadcastDebounceTimer) {
|
|
464
|
+
clearTimeout(this._broadcastDebounceTimer);
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
// Set new debounced timer
|
|
468
|
+
this._broadcastDebounceTimer = setTimeout(() => {
|
|
469
|
+
this._flushPendingBroadcasts();
|
|
470
|
+
}, 300);
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
_flushPendingBroadcasts() {
|
|
474
|
+
if (!this._realtime || !this._ws || this._ws.readyState !== 1) {
|
|
475
|
+
this._pendingBroadcasts.clear();
|
|
476
|
+
return;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
// Send all pending broadcasts
|
|
480
|
+
for (const [path, value] of this._pendingBroadcasts) {
|
|
396
481
|
this._ws.send(JSON.stringify({
|
|
397
482
|
type: 'update',
|
|
398
483
|
objectId: this.id,
|
|
399
484
|
delta: { path, value }
|
|
400
485
|
}));
|
|
401
486
|
}
|
|
487
|
+
|
|
488
|
+
// Clear pending broadcasts and timer
|
|
489
|
+
this._pendingBroadcasts.clear();
|
|
490
|
+
this._broadcastDebounceTimer = null;
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
// Cleanup method to clear any pending debounced broadcasts
|
|
494
|
+
_cleanup() {
|
|
495
|
+
if (this._broadcastDebounceTimer) {
|
|
496
|
+
clearTimeout(this._broadcastDebounceTimer);
|
|
497
|
+
this._broadcastDebounceTimer = null;
|
|
498
|
+
}
|
|
499
|
+
this._pendingBroadcasts.clear();
|
|
402
500
|
}
|
|
403
501
|
|
|
404
502
|
|