Skip to content

Commit 361922b

Browse files
sweetmantechclaude
andcommitted
refactor(sandbox): extract getStateExpiresAt to its own SRP file
Per review: the inline helper in getSandboxReconnectHandler.ts is its own concern (read epoch-ms expiresAt off a sandbox state) and belongs in a dedicated file alongside the other state predicates. Adds `lib/sandbox/getStateExpiresAt.ts` with a focused test (numeric match, non-numeric reject, null/scalar guard). Reconnect handler now imports from the new path; no behavior change. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 8631b8e commit 361922b

3 files changed

Lines changed: 42 additions & 6 deletions

File tree

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { describe, it, expect } from "vitest";
2+
import { getStateExpiresAt } from "@/lib/sandbox/getStateExpiresAt";
3+
4+
describe("getStateExpiresAt", () => {
5+
it("returns the numeric expiresAt when present", () => {
6+
expect(getStateExpiresAt({ type: "vercel", expiresAt: 4_102_444_800_000 })).toBe(
7+
4_102_444_800_000,
8+
);
9+
});
10+
11+
it("returns undefined when expiresAt is not a number", () => {
12+
expect(getStateExpiresAt({ type: "vercel", expiresAt: "soon" })).toBeUndefined();
13+
expect(getStateExpiresAt({ type: "vercel" })).toBeUndefined();
14+
});
15+
16+
it("returns undefined for null / undefined / non-object inputs", () => {
17+
expect(getStateExpiresAt(null)).toBeUndefined();
18+
expect(getStateExpiresAt(undefined)).toBeUndefined();
19+
expect(getStateExpiresAt("nope")).toBeUndefined();
20+
expect(getStateExpiresAt(42)).toBeUndefined();
21+
});
22+
});

lib/sandbox/getSandboxReconnectHandler.ts

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { buildLifecycle } from "@/lib/sandbox/buildLifecycle";
44
import { clearUnavailableSandboxState } from "@/lib/sandbox/clearUnavailableSandboxState";
55
import { connectSandbox } from "@/lib/sandbox/factory";
66
import { getSandboxExpiresAtDate } from "@/lib/sandbox/getSandboxExpiresAtDate";
7+
import { getStateExpiresAt } from "@/lib/sandbox/getStateExpiresAt";
78
import { hasRuntimeSandboxState } from "@/lib/sandbox/hasRuntimeSandboxState";
89
import { isSandboxUnavailableError } from "@/lib/sandbox/isSandboxUnavailableError";
910
import { noSandboxResponse } from "@/lib/sandbox/noSandboxResponse";
@@ -21,12 +22,6 @@ interface ReconnectBody {
2122
lifecycle: ReturnType<typeof buildLifecycle>;
2223
}
2324

24-
function getStateExpiresAt(state: unknown): number | undefined {
25-
if (!state || typeof state !== "object") return undefined;
26-
const expiresAt = (state as { expiresAt?: unknown }).expiresAt;
27-
return typeof expiresAt === "number" ? expiresAt : undefined;
28-
}
29-
3025
/**
3126
* Handles `GET /api/sandbox/reconnect`. Live runtime probe — actually
3227
* runs a quick command inside the sandbox to verify it is reachable.

lib/sandbox/getStateExpiresAt.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
/**
2+
* Reads the runtime `expiresAt` field (epoch ms) off a sandbox state.
3+
* Returns undefined when the input is not an object or when
4+
* `expiresAt` is missing or a non-number — so callers can treat the
5+
* absence of an expiry as "unknown" without coercing to NaN/0.
6+
*
7+
* Distinct from `getSandboxExpiresAtDate`, which formats the same
8+
* field as an ISO-8601 string for persistence to
9+
* `sessions.sandbox_expires_at`.
10+
*
11+
* @param state - The `sandbox_state` JSON value, typically from
12+
* `sandbox.getState()` or the persisted session row.
13+
* @returns Epoch ms expiry, or undefined.
14+
*/
15+
export function getStateExpiresAt(state: unknown): number | undefined {
16+
if (!state || typeof state !== "object") return undefined;
17+
const expiresAt = (state as { expiresAt?: unknown }).expiresAt;
18+
return typeof expiresAt === "number" ? expiresAt : undefined;
19+
}

0 commit comments

Comments
 (0)