Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions packages/react/src/api/atoms.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -169,10 +169,10 @@ export const refreshConnection = ExecutorApiClient.mutation("connections", "refr
/** Probe a SAVED connection's health on demand (the "Check now" button). The
* server persists the verdict on the connection row (`last_health`), so a check
* that changes the verdict must invalidate the connections cache or a later
* read within the atom TTL serves the pre-check state. Callers pass
* `reactivityKeys: connectionCheckKeys` (see `use-connection-health.ts` for the
* manual-vs-automatic split that keeps the automatic path from churning the
* cache on every load). */
* read within the atom TTL serves the pre-check state. Callers either pass
* `reactivityKeys: connectionCheckKeys` when a broad refresh is correct, or
* refresh the exact connection read surfaces after folding the returned verdict
* into local row state (see `use-connection-health.ts`). */
export const checkConnectionHealth = ExecutorApiClient.mutation("connections", "checkHealth");

/** Validate an IN-FLIGHT credential without saving it (the key-first connect
Expand Down
5 changes: 3 additions & 2 deletions packages/react/src/api/reactivity-keys.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,9 @@ export const connectionWriteKeys = [ReactivityKey.connections, ReactivityKey.too

/** A connection health check persists only the connection's `last_health`
* verdict, never its tools, so it invalidates `connections` alone (no `tools`
* churn). Passed at the manual "Check now" call site; the automatic mount-time
* probe invalidates conditionally instead (only when the verdict changed). */
* churn). Use this for flows where every connection read should reconcile from
* persisted health immediately; row-level checks can refresh narrower read
* surfaces after applying their own local probe result. */
export const connectionCheckKeys = [ReactivityKey.connections] as const;

/** Mutations that register / replace an OAuth client (app). */
Expand Down
27 changes: 16 additions & 11 deletions packages/react/src/lib/use-connection-health.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,7 @@ import { RegistryContext, useAtomSet } from "@effect/atom-react";
import * as Exit from "effect/Exit";
import type { Connection, HealthCheckResult, HealthStatus, Owner } from "@executor-js/sdk/shared";

import { checkConnectionHealth, connectionsOptimisticAtom } from "../api/atoms";
import { connectionCheckKeys } from "../api/reactivity-keys";
import { checkConnectionHealth, connectionsAllAtom, connectionsOptimisticAtom } from "../api/atoms";

/** Freshness window for automatic revalidation: a HEALTHY verdict younger
* than this renders as-is; anything else (stale, missing, or non-healthy)
Expand Down Expand Up @@ -47,13 +46,17 @@ const revalidateQuery = (
* persists every verdict on `last_health`, so after a check we must re-read the
* connection rows or a later render within the atom TTL serves the pre-check
* state. Returns a stable callback usable from a probe's `.then` for any owner
* (the loop surface probes across both owners), refreshing the optimistic atom
* every connections view derives from.
* (the loop surface probes across both owners), refreshing the owner-scoped
* optimistic atom plus the all-connections view that provider accounts derive
* from.
*/
function useInvalidateConnections(): (owner: Owner) => void {
const registry = useContext(RegistryContext);
return useCallback(
(owner: Owner) => registry.refresh(connectionsOptimisticAtom(owner)),
(owner: Owner) => {
registry.refresh(connectionsOptimisticAtom(owner));
registry.refresh(connectionsAllAtom);
},
[registry],
);
}
Expand Down Expand Up @@ -107,17 +110,19 @@ export function useConnectionHealth(connection: Connection): {
}, [connection, doCheck, invalidateConnections]);

const runCheck = useCallback(async () => {
// Manual "Check now": invalidate the connections cache unconditionally so
// every surface picks up the freshly persisted verdict. Re-running this
// effect after the refetch is harmless: the ref guard blocks a re-probe.
// Manual "Check now": refresh connection reads after folding the returned
// verdict into this row. Re-running this effect after the refetch is
// harmless: the ref guard blocks a re-probe.
const exit = await doCheck({
params: connectionParams(connection),
query: {},
reactivityKeys: connectionCheckKeys,
});
if (Exit.isSuccess(exit)) setLiveProbe(exit.value);
if (Exit.isSuccess(exit)) {
setLiveProbe(exit.value);
invalidateConnections(connection.owner);
}
return exit;
}, [connection, doCheck]);
}, [connection, doCheck, invalidateConnections]);

return { probe, status, runCheck };
}
Expand Down
Loading