Skip to content

Commit fd47e9f

Browse files
committed
Implement find() wait and RECONNECT structure event
find() now waits for the target app to appear instead of failing immediately. subscribeToStructure fires RECONNECT (2) when an app reappears after being removed, distinguishing restarts from first-time appearances. Changes: - find() waits indefinitely by default; { timeout: N } limits the wait; { timeout: 0 } preserves the old immediate-fail behavior - find() works without prior root() call (triggers connection internally) - close() is terminal: sets isClosed flag, rejects pending find() waiters, prevents reconnection after close - RECONNECT constant (2) added alongside ADD (1) and REMOVE (0) - Per-app lifecycle via announceApp/unannounceApp: each app is tracked independently, fixing the bug where one sibling disconnecting lost all sibling connections - Subscription keepalive via inactivityResendInterval field (client requests server resend values/events after 120s of no changes, matching C++ client ServicesProtocol::InactivityResendIntervalSec) - Value dedup via lastServerTimestamp and event dedup via recentEventIds to handle server replay after reconnection - Exponential backoff with jitter for reconnection (1s to 30s max) - Stall detection: force reconnect after 150s of server silence with active subscriptions (must exceed 120s keepalive interval) - Direct mode sibling discovery uses server-pushed eStructureChangeResponse instead of client-side polling - CDP_FORCE_DIRECT_MODE=1 env var to force direct mode on proxy-capable servers (for testing) - Cache key fix: use join('.') instead of toString() for correct invalidateApp prefix matching on app structure changes - Remove unused serviceId storage on proxy connections - Fix instanceId serialization: pass through as-is instead of defaulting falsy values to 0 - appAddress() helper for DRY server address construction - README updated with Structure Events section and find() options - Version bump to 3.0.0 (breaking: find() default changed from immediate fail to indefinite wait) CDP-6069
1 parent ea72cfd commit fd47e9f

5 files changed

Lines changed: 516 additions & 80 deletions

File tree

README.rst

Lines changed: 66 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ Example
3838
root.subscribeToStructure((name, change) => {
3939
if (change === studio.api.structure.ADD)
4040
subscribeToApp(name);
41+
if (change === studio.api.structure.RECONNECT)
42+
console.log(`${name} restarted, subscriptions intact`);
4143
});
4244
}).catch(err => console.error("Connection failed:", err));
4345
@@ -61,6 +63,41 @@ Benefits
6163
- Simplified firewall configuration - only one port needs to be opened
6264
- SSH port forwarding - forward a single port to access entire CDP system
6365

66+
Structure Events
67+
----------------
68+
69+
On the root node, ``subscribeToStructure`` tracks application lifecycle with three event types:
70+
71+
- ``studio.api.structure.ADD`` (1) — An application appeared for the first time
72+
- ``studio.api.structure.REMOVE`` (0) — An application went offline
73+
- ``studio.api.structure.RECONNECT`` (2) — An application restarted (was seen before, went offline, came back)
74+
75+
On other nodes, ADD and REMOVE fire when children are added or removed at runtime.
76+
RECONNECT only fires at the root level.
77+
78+
When an app restarts, the client automatically restores value and event subscriptions,
79+
so user code does not need to re-subscribe. RECONNECT is informational — use it for
80+
logging or UI updates.
81+
82+
.. code:: javascript
83+
84+
client.root().then(root => {
85+
root.subscribeToStructure((appName, change) => {
86+
if (change === studio.api.structure.ADD) {
87+
console.log(`New app online: ${appName}`);
88+
client.find(appName + '.CPULoad').then(node => {
89+
node.subscribeToValues(v => console.log(`[${appName}] CPULoad: ${v}`));
90+
}).catch(err => console.error(`Failed to find ${appName}.CPULoad:`, err));
91+
}
92+
if (change === studio.api.structure.REMOVE) {
93+
console.log(`App offline: ${appName}`);
94+
}
95+
if (change === studio.api.structure.RECONNECT) {
96+
console.log(`App restarted: ${appName}, subscriptions intact`);
97+
}
98+
});
99+
}).catch(err => console.error("Connection failed:", err));
100+
64101
API
65102
---
66103

@@ -325,31 +362,46 @@ client.root()
325362
// use the system INode object to access connected structure.
326363
});
327364
328-
client.find(path)
329-
^^^^^^^^^^^^^^^^^
365+
client.find(path, options)
366+
^^^^^^^^^^^^^^^^^^^^^^^^^
330367

331368
- Arguments
332369

333-
path - Path of the object to look for.
370+
path - Dot-separated path to target node (e.g. ``'App2.CPULoad'``).
371+
372+
options - Optional. Without this, find() waits indefinitely for the app to appear.
373+
``{ timeout: milliseconds }`` to limit wait time.
374+
``{ timeout: 0 }`` to fail immediately if the app is not available.
334375

335376
- Returns
336377

337378
Promise containing requested INode object when fulfilled.
338379

339-
- Restriction
380+
- Behavior
340381

341-
The requested node must reside in the application client was connected to.
382+
When no timeout option is provided, waits indefinitely for the target application to appear.
383+
If the application is already available, resolves immediately. No prior ``root()`` call is needed — ``find()`` triggers
384+
the connection internally.
342385

343-
- Usage
386+
- Examples
344387

345-
The provided path must contain dot separated path to target node. **Root node is not considered part of the path.**
388+
.. code:: javascript
346389
347-
- Example
390+
// Waits indefinitely for App2 to appear
391+
client.find("App2.CPULoad").then(function (load) {
392+
load.subscribeToValues(function (value) {
393+
console.log("CPULoad:", value);
394+
});
395+
});
348396
349-
.. code:: javascript
397+
// Wait up to 5 seconds
398+
client.find("App2.CPULoad", { timeout: 5000 }).catch(function (err) {
399+
console.log(err.message); // "App2 not found within 5000ms"
400+
});
350401
351-
client.find("MyApp.CPULoad").then(function (load) {
352-
// use the load object referring to CPULoad in MyApp
402+
// Fail immediately if not available (old behavior)
403+
client.find("App2.CPULoad", { timeout: 0 }).catch(function (err) {
404+
console.log("Not available right now");
353405
});
354406
355407
client.close()
@@ -604,8 +656,9 @@ node.subscribeToStructure(structureConsumer)
604656

605657
- Usage
606658

607-
Subscribe to structure changes on this node. Each time child is added or removed from current node
608-
structureConsumer function is called with the name of the node and change argument where ADD == 1 and REMOVE == 0.
659+
Subscribe to structure changes on this node. Each time a child is added or removed,
660+
structureConsumer is called with the child name and change (ADD == 1, REMOVE == 0).
661+
On the root node, RECONNECT (2) fires when a previously-seen application restarts.
609662

610663
node.unsubscribeFromStructure(structureConsumer)
611664
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

0 commit comments

Comments
 (0)