Skip to content

Commit 2b67bf0

Browse files
authored
Implement find() wait and RECONNECT structure event (#24)
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 - Per-listener event dedup with composite keys and history wait window - Value dedup via per-node timestamp tracking - Exponential backoff with jitter for reconnection - Stall detection: force reconnect after prolonged server silence with active subscriptions - 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 2b67bf0

5 files changed

Lines changed: 551 additions & 83 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)