feat(sdk/ts): add createdAtMs to SandboxRef (#4347)

* feat(sdk/ts): add createdAtMs to SandboxRef

Signed-off-by: Eric Curtin <eric.curtin@docker.com>

* docs(sdk/ts): document createdAtMs on SandboxRef

Signed-off-by: Eric Curtin <eric.curtin@docker.com>

---------

Signed-off-by: Eric Curtin <eric.curtin@docker.com>
This commit is contained in:
Eric Curtin
2026-10-12 17:55:49 +00:00
committed by GitHub
parent e0eabae886
commit 775d9071bc
4 changed files with 31 additions and 4 deletions
+3
View File
@@ -130,6 +130,9 @@ if (deletion.outcome === 'accepted') {
}
```
Sandbox refs returned by `create`, `get`, and `list` include `createdAtMs`, the
creation time in epoch milliseconds. It is absent when the gateway omits it.
`client.sandbox` also supports streaming and interactive exec, TCP forwarding,
SSH sessions, sandbox provider attachment, configuration, and policy. Close
operation-scoped streams and forwarding handles when finished. The root client
+2
View File
@@ -64,6 +64,8 @@ resolves to a different ID. Without that option, it waits for name absence,
including any same-name replacement. Pass the same `workspace` to deletion and
its wait when using a non-default workspace.
Sandbox refs from `create`, `get`, and `list` carry `createdAtMs` (epoch milliseconds), absent when the gateway omits it.
`connect()` constructs a lazy client; call `health()` when startup must verify
gateway reachability. Static `oidcToken` and `edgeToken` values remain fixed for
the client's lifetime. For long-running service automation, use the renewable
+16
View File
@@ -83,6 +83,22 @@ describe('sandbox SSH host identity', () => {
});
});
describe('sandbox creation time', () => {
it('maps created_time from get and list and leaves it absent when omitted', async () => {
let createdTime: { seconds: bigint; nanos: number } | undefined = { seconds: 1_700_000_000n, nanos: 123_000_000 };
const sandbox = client({
getSandbox: () => ({ sandbox: { metadata: { id: 'id', name: 'work', createdTime } } }),
listSandboxes: () => ({ sandboxes: [{ metadata: { id: 'id', name: 'work', createdTime } }] }),
});
expect((await sandbox.get('work')).createdAtMs).toBe(1_700_000_000_123);
expect((await sandbox.list().all())[0]?.createdAtMs).toBe(1_700_000_000_123);
createdTime = undefined;
expect((await sandbox.get('work')).createdAtMs).toBeUndefined();
createdTime = { seconds: 0n, nanos: 0 };
expect((await sandbox.list().all())[0]?.createdAtMs).toBeUndefined();
});
});
describe('deletion outcomes', () => {
it('defaults to strict deletion and preserves accepted identity and unknown values', async () => {
const flags: boolean[] = [];
+10 -4
View File
@@ -49,6 +49,11 @@ function timestampMillis(timestamp: { seconds: bigint; nanos: number } | undefin
return millis === 0n ? undefined : millis.toString();
}
function timestampEpochMs(timestamp: { seconds: bigint; nanos: number } | undefined): number | undefined {
const millis = timestampMillis(timestamp);
return millis ? Number(millis) : undefined;
}
// Generated protobuf message shapes that callers need to populate or round-trip
// directly. Re-export these rather than re-curating parallel surfaces.
export type {
@@ -225,6 +230,8 @@ export interface SandboxRef {
/** Service URLs returned by creation, keyed by service name. */
serviceUrls: Record<string, string>;
restartCount: number;
/** Creation time in epoch milliseconds; absent when the gateway omits it. */
createdAtMs?: number;
nextRestartAtMs?: number;
mainProcessStartedAtMs?: number;
}
@@ -529,8 +536,6 @@ function sandboxRef(sandbox: Sandbox | undefined, serviceUrls: Record<string, st
if (!meta?.id || !meta.name) {
throw new SdkError('invalid_config', 'sandbox metadata.id and metadata.name are required in gateway responses');
}
const nextRestartAtMs = timestampMillis(sandbox.status?.nextRestartTime);
const mainProcessStartedAtMs = timestampMillis(sandbox.status?.mainProcessStartedTime);
return {
id: meta.id,
name: meta.name,
@@ -549,8 +554,9 @@ function sandboxRef(sandbox: Sandbox | undefined, serviceUrls: Record<string, st
: undefined,
serviceUrls,
restartCount: sandbox.status?.restartCount ?? 0,
nextRestartAtMs: nextRestartAtMs ? Number(nextRestartAtMs) : undefined,
mainProcessStartedAtMs: mainProcessStartedAtMs ? Number(mainProcessStartedAtMs) : undefined,
createdAtMs: timestampEpochMs(meta.createdTime),
nextRestartAtMs: timestampEpochMs(sandbox.status?.nextRestartTime),
mainProcessStartedAtMs: timestampEpochMs(sandbox.status?.mainProcessStartedTime),
};
}