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/dist/esm/rbt_object.js
CHANGED
|
@@ -15,15 +15,27 @@ export default class RbtObject {
|
|
|
15
15
|
} else {
|
|
16
16
|
this._data = record.dataJson ? this._deepUnpackJson(record.dataJson) : {};
|
|
17
17
|
}
|
|
18
|
-
if (options.websocketClient && this.id) {
|
|
18
|
+
if (options.websocketClient && this.id && options.enableRealtime) {
|
|
19
19
|
this._realtime = true;
|
|
20
20
|
this._ws = options.websocketClient;
|
|
21
21
|
this._subscribeToRealtime(this._ws);
|
|
22
|
+
} else if (options.websocketClient && this.id) {
|
|
23
|
+
// Store websocket client for potential later use, but don't auto-subscribe
|
|
24
|
+
this._ws = options.websocketClient;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// If enableRealtime is requested but no websocketClient provided, try to initialize lazily
|
|
28
|
+
if (options.enableRealtime && this.id && !this._realtime) {
|
|
29
|
+
this._initRealtime();
|
|
22
30
|
}
|
|
23
31
|
this._eventHandlers = {
|
|
24
32
|
change: [],
|
|
25
33
|
save: []
|
|
26
34
|
};
|
|
35
|
+
|
|
36
|
+
// Debounce properties for WebSocket broadcasting
|
|
37
|
+
this._broadcastDebounceTimer = null;
|
|
38
|
+
this._pendingBroadcasts = new Map(); // path -> value
|
|
27
39
|
}
|
|
28
40
|
get(path) {
|
|
29
41
|
return _.get(this._data, path);
|
|
@@ -80,16 +92,34 @@ export default class RbtObject {
|
|
|
80
92
|
if (!_.isEqual(currentValue, mergedValue)) {
|
|
81
93
|
_.set(this._data, path, mergedValue); // Set the merged value at the deep path
|
|
82
94
|
this._addChange(path);
|
|
95
|
+
// Trigger local change events for any registered handlers
|
|
96
|
+
this._trigger('change', {
|
|
97
|
+
path,
|
|
98
|
+
value: mergedValue,
|
|
99
|
+
options
|
|
100
|
+
});
|
|
83
101
|
}
|
|
84
102
|
} else {
|
|
85
103
|
// If value is undefined and not merging, delete the key
|
|
86
104
|
if (value === undefined) {
|
|
87
105
|
_.unset(this._data, path);
|
|
88
106
|
this._addChange(path);
|
|
107
|
+
// Trigger local change events for any registered handlers
|
|
108
|
+
this._trigger('change', {
|
|
109
|
+
path,
|
|
110
|
+
value: undefined,
|
|
111
|
+
options
|
|
112
|
+
});
|
|
89
113
|
} else if (!_.isEqual(currentValue, value)) {
|
|
90
114
|
_.set(this._data, path, value); // Set the value directly at the deep path
|
|
91
115
|
this._addChange(path);
|
|
92
116
|
this._broadcastChange(path, value);
|
|
117
|
+
// Trigger local change events for any registered handlers
|
|
118
|
+
this._trigger('change', {
|
|
119
|
+
path,
|
|
120
|
+
value,
|
|
121
|
+
options
|
|
122
|
+
});
|
|
93
123
|
}
|
|
94
124
|
}
|
|
95
125
|
}
|
|
@@ -116,6 +146,12 @@ export default class RbtObject {
|
|
|
116
146
|
if (!_.isEqual(currentValue, mergedValue)) {
|
|
117
147
|
_.set(this._data, path, mergedValue);
|
|
118
148
|
this._addChange(path);
|
|
149
|
+
// Trigger change event for this path
|
|
150
|
+
this._trigger('change', {
|
|
151
|
+
path,
|
|
152
|
+
value: mergedValue,
|
|
153
|
+
options
|
|
154
|
+
});
|
|
119
155
|
}
|
|
120
156
|
} else if (!_.isEqual(currentValue, newValue)) {
|
|
121
157
|
_.setWith(this._data, path, newValue, (nsValue, key, nsObject, nsPath) => {
|
|
@@ -125,6 +161,12 @@ export default class RbtObject {
|
|
|
125
161
|
return nsValue;
|
|
126
162
|
});
|
|
127
163
|
this._addChange(path);
|
|
164
|
+
// Trigger change event for this path
|
|
165
|
+
this._trigger('change', {
|
|
166
|
+
path,
|
|
167
|
+
value: newValue,
|
|
168
|
+
options
|
|
169
|
+
});
|
|
128
170
|
}
|
|
129
171
|
});
|
|
130
172
|
}
|
|
@@ -301,16 +343,26 @@ export default class RbtObject {
|
|
|
301
343
|
_initRealtime() {
|
|
302
344
|
if (this._realtime || !this._axios) return;
|
|
303
345
|
|
|
304
|
-
//
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
346
|
+
// Use stored websocket client if available
|
|
347
|
+
let ws = this._ws;
|
|
348
|
+
|
|
349
|
+
// Otherwise, lazily pull WebSocket from parent API (injected via axios instance)
|
|
350
|
+
if (!ws) {
|
|
351
|
+
const api = this._axios?.__rbtApiInstance;
|
|
352
|
+
if (!api || typeof api.getWebSocketClient !== 'function') return;
|
|
353
|
+
ws = api.getWebSocketClient();
|
|
354
|
+
}
|
|
308
355
|
if (!ws || this._realtime) return;
|
|
309
356
|
this._ws = ws;
|
|
310
357
|
this._realtime = true;
|
|
311
358
|
this._subscribeToRealtime(ws);
|
|
312
359
|
}
|
|
313
360
|
_subscribeToRealtime(ws) {
|
|
361
|
+
// Track subscription for reconnection
|
|
362
|
+
const api = this._axios?.__rbtApiInstance;
|
|
363
|
+
if (api && typeof api._trackSubscription === 'function') {
|
|
364
|
+
api._trackSubscription(this.id);
|
|
365
|
+
}
|
|
314
366
|
if (ws.readyState === 1) {
|
|
315
367
|
ws.send(JSON.stringify({
|
|
316
368
|
type: 'subscribe',
|
|
@@ -325,31 +377,82 @@ export default class RbtObject {
|
|
|
325
377
|
});
|
|
326
378
|
}
|
|
327
379
|
ws.addEventListener('message', event => {
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
380
|
+
try {
|
|
381
|
+
const msg = JSON.parse(event.data);
|
|
382
|
+
if (msg.objectId !== this.id) return;
|
|
383
|
+
if (msg.type === 'update') {
|
|
384
|
+
if (msg.delta && msg.delta.path !== undefined) {
|
|
385
|
+
_.set(this._data, msg.delta.path, msg.delta.value);
|
|
386
|
+
|
|
387
|
+
// Add metadata to indicate if this is state sync vs live update
|
|
388
|
+
const changeData = {
|
|
389
|
+
...msg.delta,
|
|
390
|
+
isStateSync: msg.isStateSync || false,
|
|
391
|
+
source: msg.isStateSync ? 'state-sync' : 'realtime',
|
|
392
|
+
userId: msg.userId || msg.delta.userId || null
|
|
393
|
+
};
|
|
394
|
+
this._trigger('change', changeData);
|
|
395
|
+
if (msg.isStateSync) {
|
|
396
|
+
console.log('[RbtObject] Applied state sync:', msg.delta.path, '=', msg.delta.value);
|
|
397
|
+
}
|
|
398
|
+
} else {
|
|
399
|
+
console.warn('[RbtObject] Received update message without valid delta:', msg);
|
|
400
|
+
}
|
|
401
|
+
} else if (msg.type === 'save') {
|
|
402
|
+
this._trigger('save', msg.revision || {});
|
|
403
|
+
}
|
|
404
|
+
} catch (error) {
|
|
405
|
+
console.error('[RbtObject] Error processing WebSocket message:', error, event.data);
|
|
335
406
|
}
|
|
336
407
|
});
|
|
337
408
|
}
|
|
338
|
-
|
|
409
|
+
|
|
410
|
+
// General change handler for both local and remote changes
|
|
411
|
+
onChange(cb) {
|
|
339
412
|
this._eventHandlers.change.push(cb);
|
|
340
|
-
this
|
|
413
|
+
// Auto-initialize realtime if this object has WebSocket capability
|
|
414
|
+
if (this._ws && !this._realtime) {
|
|
415
|
+
this._initRealtime();
|
|
416
|
+
}
|
|
341
417
|
}
|
|
342
|
-
|
|
418
|
+
|
|
419
|
+
// General save handler for both local and remote saves
|
|
420
|
+
onSave(cb) {
|
|
343
421
|
this._eventHandlers.save.push(cb);
|
|
344
|
-
|
|
422
|
+
// Note: Does not initialize realtime connection
|
|
345
423
|
}
|
|
346
424
|
_trigger(type, data) {
|
|
425
|
+
console.log('[AgentProviderSync] _trigger called:', type, 'handlers:', this._eventHandlers[type]?.length, 'data:', data);
|
|
347
426
|
for (const fn of this._eventHandlers[type] || []) {
|
|
348
427
|
fn(data);
|
|
349
428
|
}
|
|
350
429
|
}
|
|
351
430
|
_broadcastChange(path, value) {
|
|
352
|
-
if (this._realtime
|
|
431
|
+
if (!this._realtime || !this._ws || this._ws.readyState !== 1) {
|
|
432
|
+
return;
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
// Store the pending broadcast
|
|
436
|
+
this._pendingBroadcasts.set(path, value);
|
|
437
|
+
|
|
438
|
+
// Clear existing timer if any
|
|
439
|
+
if (this._broadcastDebounceTimer) {
|
|
440
|
+
clearTimeout(this._broadcastDebounceTimer);
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
// Set new debounced timer
|
|
444
|
+
this._broadcastDebounceTimer = setTimeout(() => {
|
|
445
|
+
this._flushPendingBroadcasts();
|
|
446
|
+
}, 300);
|
|
447
|
+
}
|
|
448
|
+
_flushPendingBroadcasts() {
|
|
449
|
+
if (!this._realtime || !this._ws || this._ws.readyState !== 1) {
|
|
450
|
+
this._pendingBroadcasts.clear();
|
|
451
|
+
return;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
// Send all pending broadcasts
|
|
455
|
+
for (const [path, value] of this._pendingBroadcasts) {
|
|
353
456
|
this._ws.send(JSON.stringify({
|
|
354
457
|
type: 'update',
|
|
355
458
|
objectId: this.id,
|
|
@@ -359,5 +462,18 @@ export default class RbtObject {
|
|
|
359
462
|
}
|
|
360
463
|
}));
|
|
361
464
|
}
|
|
465
|
+
|
|
466
|
+
// Clear pending broadcasts and timer
|
|
467
|
+
this._pendingBroadcasts.clear();
|
|
468
|
+
this._broadcastDebounceTimer = null;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
// Cleanup method to clear any pending debounced broadcasts
|
|
472
|
+
_cleanup() {
|
|
473
|
+
if (this._broadcastDebounceTimer) {
|
|
474
|
+
clearTimeout(this._broadcastDebounceTimer);
|
|
475
|
+
this._broadcastDebounceTimer = null;
|
|
476
|
+
}
|
|
477
|
+
this._pendingBroadcasts.clear();
|
|
362
478
|
}
|
|
363
479
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "roboto-js",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.7.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "",
|
|
6
6
|
"main": "dist/cjs/index.cjs",
|
|
@@ -16,7 +16,8 @@
|
|
|
16
16
|
"xbuild:esm": "babel src --out-dir dist/esm --presets=@babel/preset-env --no-babelrc --config-file ./babel.esm.config.json",
|
|
17
17
|
"postbuild:cjs": "node postbuild-cjs.js",
|
|
18
18
|
"test": "echo \"Error: no test specified\" && exit 1",
|
|
19
|
-
"prepublishOnly": "npm run build"
|
|
19
|
+
"prepublishOnly": "npm run build",
|
|
20
|
+
"prepare": "npm run build"
|
|
20
21
|
},
|
|
21
22
|
"author": "",
|
|
22
23
|
"license": "ISC",
|
package/src/index.js
CHANGED
|
@@ -211,6 +211,15 @@ export default class Roboto{
|
|
|
211
211
|
return this.api.query(type, params);
|
|
212
212
|
}
|
|
213
213
|
|
|
214
|
+
// WebSocket methods
|
|
215
|
+
getWebSocketClient(){
|
|
216
|
+
return this.api.getWebSocketClient();
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
closeWebSocket(){
|
|
220
|
+
return this.api.closeWebSocket();
|
|
221
|
+
}
|
|
222
|
+
|
|
214
223
|
//
|
|
215
224
|
//
|
|
216
225
|
//
|