Skip to content

Commit 8c43806

Browse files
Fix: TypeScript union type resolution for Commands.run() method (#753)
## Fix TypeScript union type resolution for Commands.run() method ### What's Changed Improved TypeScript type safety for the `Commands.run()` method by adding proper function overloads to handle the union case when the `background` parameter type is not known at compile time. ### Key Improvements **Better Type Safety** - Added a third function overload to correctly handle the `background?: boolean` union case - TypeScript now correctly infers return types: - `background: true` → returns `CommandHandle` - `background: false | undefined` → returns `CommandResult` - `background: boolean` (unknown at compile time) → returns `CommandHandle | CommandResult` **Enhanced Developer Experience** - Added comprehensive JSDoc overloads that clearly document each usage pattern - IntelliSense now provides accurate type hints and autocompletion - Eliminates ambiguous union types when the `background` value is known at compile time **Prevents Runtime Errors** - When `background` is a literal value, developers get precise types and can't accidentally call wrong methods - When `background` is a runtime boolean, TypeScript correctly shows the union type requiring proper type narrowing ### Why This Matters for SDK Users Before this change, developers had to ignore ts warning & manually cast union types when using `commands.run()`: ```typescript // Before: typescript was complaining, because it expected `true` or `false` as literals. // Required manual casting: // @ts-ignore const result = await sbx.commands.run('ls', { background: isBackground }) as CommandHandle | CommandResult ``` After this change, TypeScript automatically infers the correct type: ```typescript // After: TypeScript knows this is CommandHandle const handle = await sbx.commands.run('ls', { background: true }) await handle.wait() // ✅ TypeScript knows .wait() is available // TypeScript knows this is CommandResult const result = await sbx.commands.run('ls') console.log(result.stdout) // ✅ TypeScript knows .stdout is available // When background value is unknown at compile time, returns union type const isBackground: boolean = Math.random() > 0.5 const result = await commands.run('ls', { background: isBackground }) // result is CommandHandle | CommandResult - requires type narrowing or casting ```
1 parent 1b9fd7a commit 8c43806

File tree

2 files changed

+47
-22
lines changed

2 files changed

+47
-22
lines changed

.changeset/early-bags-relate.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'e2b': patch
3+
---
4+
5+
adds a new typescript overload to sbx.commands.run to allow options.background to be parameterized as a boolean variable

packages/js-sdk/src/sandbox/commands/index.ts

Lines changed: 42 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,10 @@ import {
66
Transport,
77
} from '@connectrpc/connect'
88

9-
import { Signal, Process as ProcessService } from '../../envd/process/process_pb'
9+
import {
10+
Signal,
11+
Process as ProcessService,
12+
} from '../../envd/process/process_pb'
1013
import {
1114
ConnectionConfig,
1215
Username,
@@ -15,15 +18,15 @@ import {
1518
KEEPALIVE_PING_HEADER,
1619
} from '../../connectionConfig'
1720
import { authenticationHeader, handleRpcError } from '../../envd/rpc'
18-
import { CommandHandle, CommandResult } from './commandHandle'
21+
import { CommandResult, CommandHandle } from './commandHandle'
1922
import { handleProcessStartEvent } from '../../envd/api'
2023
export { Pty } from './pty'
2124

2225
/**
2326
* Options for sending a command request.
2427
*/
2528
export interface CommandRequestOpts
26-
extends Partial<Pick<ConnectionOpts, 'requestTimeoutMs'>> { }
29+
extends Partial<Pick<ConnectionOpts, 'requestTimeoutMs'>> {}
2730

2831
/**
2932
* Options for starting a new command.
@@ -36,21 +39,21 @@ export interface CommandStartOpts extends CommandRequestOpts {
3639
background?: boolean
3740
/**
3841
* Working directory for the command.
39-
*
42+
*
4043
* @default // home directory of the user used to start the command
4144
*/
4245
cwd?: string
4346
/**
4447
* User to run the command as.
45-
*
48+
*
4649
* @default `user`
4750
*/
4851
user?: Username
4952
/**
5053
* Environment variables used for the command.
51-
*
54+
*
5255
* This overrides the default environment variables from `Sandbox` constructor.
53-
*
56+
*
5457
* @default `{}`
5558
*/
5659
envs?: Record<string, string>
@@ -64,7 +67,7 @@ export interface CommandStartOpts extends CommandRequestOpts {
6467
onStderr?: (data: string) => void | Promise<void>
6568
/**
6669
* Timeout for the command in **milliseconds**.
67-
*
70+
*
6871
* @default 60_000 // 60 seconds
6972
*/
7073
timeoutMs?: number
@@ -109,7 +112,6 @@ export interface ProcessInfo {
109112
cwd?: string
110113
}
111114

112-
113115
/**
114116
* Module for starting and interacting with commands in the sandbox.
115117
*/
@@ -129,7 +131,7 @@ export class Commands {
129131
* List all running commands and PTY sessions.
130132
*
131133
* @param opts connection options.
132-
*
134+
*
133135
* @returns list of running commands and PTY sessions.
134136
*/
135137
async list(opts?: CommandRequestOpts): Promise<ProcessInfo[]> {
@@ -197,7 +199,7 @@ export class Commands {
197199
*
198200
* @param pid process ID of the command. You can get the list of running commands using {@link Commands.list}.
199201
* @param opts connection options.
200-
*
202+
*
201203
* @returns `true` if the command was killed, `false` if the command was not found.
202204
*/
203205
async kill(pid: number, opts?: CommandRequestOpts): Promise<boolean> {
@@ -235,7 +237,7 @@ export class Commands {
235237
*
236238
* @param pid process ID of the command to connect to. You can get the list of running commands using {@link Commands.list}.
237239
* @param opts connection options.
238-
*
240+
*
239241
* @returns `CommandHandle` handle to interact with the running command.
240242
*/
241243
async connect(
@@ -249,8 +251,8 @@ export class Commands {
249251

250252
const reqTimeout = requestTimeoutMs
251253
? setTimeout(() => {
252-
controller.abort()
253-
}, requestTimeoutMs)
254+
controller.abort()
255+
}, requestTimeoutMs)
254256
: undefined
255257

256258
const events = this.rpc.connect(
@@ -292,33 +294,51 @@ export class Commands {
292294

293295
/**
294296
* Start a new command and wait until it finishes executing.
295-
*
297+
*
296298
* @param cmd command to execute.
297299
* @param opts options for starting the command.
298-
*
300+
*
299301
* @returns `CommandResult` result of the command execution.
300302
*/
301303
async run(
302304
cmd: string,
303305
opts?: CommandStartOpts & { background?: false }
304306
): Promise<CommandResult>
307+
305308
/**
306309
* Start a new command in the background.
307310
* You can use {@link CommandHandle.wait} to wait for the command to finish and get its result.
308-
*
311+
*
309312
* @param cmd command to execute.
310313
* @param opts options for starting the command
311-
*
314+
*
312315
* @returns `CommandHandle` handle to interact with the running command.
313316
*/
314317
async run(
315318
cmd: string,
316-
opts?: CommandStartOpts & { background: true }
319+
opts: CommandStartOpts & { background: true }
317320
): Promise<CommandHandle>
321+
322+
// NOTE - The following overload seems redundant, but it's required to make the type inference work correctly.
323+
324+
/**
325+
* Start a new command.
326+
*
327+
* @param cmd command to execute.
328+
* @param opts options for starting the command.
329+
* - `opts.background: true` - runs in background, returns `CommandHandle`
330+
* - `opts.background: false | undefined` - waits for completion, returns `CommandResult`
331+
*
332+
* @returns Either a `CommandHandle` or a `CommandResult` (depending on `opts.background`).
333+
*/
334+
async run(
335+
cmd: string,
336+
opts?: CommandStartOpts & { background?: boolean }
337+
): Promise<CommandHandle | CommandResult>
318338
async run(
319339
cmd: string,
320340
opts?: CommandStartOpts & { background?: boolean }
321-
): Promise<unknown> {
341+
): Promise<CommandHandle | CommandResult> {
322342
const proc = await this.start(cmd, opts)
323343

324344
return opts?.background ? proc : proc.wait()
@@ -335,8 +355,8 @@ export class Commands {
335355

336356
const reqTimeout = requestTimeoutMs
337357
? setTimeout(() => {
338-
controller.abort()
339-
}, requestTimeoutMs)
358+
controller.abort()
359+
}, requestTimeoutMs)
340360
: undefined
341361

342362
const events = this.rpc.start(

0 commit comments

Comments
 (0)