Skip to content

Commit f04119c

Browse files
Add documentation for file watching feature
Documents the new file watching capability using inotify: - watch() method with comprehensive API reference - WatchEvent, WatchOptions, and WatchHandle interfaces - Use cases including hot reload, build systems, and log monitoring - Performance considerations and best practices - Error handling and AbortSignal usage - Technical implementation details Related to cloudflare/sandbox-sdk PR #324
1 parent d7de5f8 commit f04119c

File tree

2 files changed

+292
-0
lines changed

2 files changed

+292
-0
lines changed
Lines changed: 284 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,284 @@
1+
---
2+
title: File Watching
3+
pcx_content_type: concept
4+
sidebar:
5+
order: 4
6+
---
7+
8+
import { TypeScriptExample } from "~/components";
9+
10+
Watch filesystem changes in real-time using Linux's native inotify system. Get instant notifications when files are created, modified, deleted, or renamed.
11+
12+
## Methods
13+
14+
### `watch()`
15+
16+
Start watching a directory for filesystem changes. Returns a handle that can be used to stop the watch.
17+
18+
```ts
19+
const handle = await sandbox.watch(path: string, options?: WatchOptions): Promise<WatchHandle>
20+
```
21+
22+
**Parameters**:
23+
- `path` - Absolute or relative path to watch (relative paths are resolved from `/workspace`)
24+
- `options` (optional):
25+
- `recursive` - Watch subdirectories recursively (default: `true`)
26+
- `events` - Event types to watch for (default: `['create', 'modify', 'delete', 'rename']`)
27+
- `include` - Glob patterns to include (e.g., `['*.ts', '*.js']`)
28+
- `exclude` - Glob patterns to exclude (default: `['.git', 'node_modules', '.DS_Store']`)
29+
- `signal` - AbortSignal to cancel the watch
30+
- `onEvent` - Callback for file change events
31+
- `onError` - Callback for watch errors
32+
33+
**Returns**: `Promise<WatchHandle>` with `id`, `path`, and `stop()` method
34+
35+
<TypeScriptExample>
36+
```
37+
// Basic usage - watch all changes
38+
const handle = await sandbox.watch('/workspace/src', {
39+
onEvent: (event) => {
40+
console.log(`${event.type}: ${event.path}`);
41+
}
42+
});
43+
44+
// Watch specific file types
45+
const handle = await sandbox.watch('/workspace', {
46+
include: ['*.ts', '*.tsx'],
47+
onEvent: (event) => {
48+
console.log(`TypeScript file ${event.type}: ${event.path}`);
49+
}
50+
});
51+
52+
// Watch with custom exclusions
53+
const handle = await sandbox.watch('/workspace', {
54+
exclude: ['.git', 'node_modules', 'dist', '*.log'],
55+
onEvent: (event) => {
56+
if (event.isDirectory) {
57+
console.log(`Directory ${event.type}: ${event.path}`);
58+
} else {
59+
console.log(`File ${event.type}: ${event.path}`);
60+
}
61+
}
62+
});
63+
64+
// Stop watching
65+
await handle.stop();
66+
```
67+
</TypeScriptExample>
68+
69+
:::note[Watch establishment]
70+
The `watch()` method waits for the watch to be established before returning. If the path does not exist or cannot be watched, the promise rejects with an error.
71+
:::
72+
73+
### Event Types
74+
75+
File watch events include the following types:
76+
77+
- `create` - A file or directory was created
78+
- `modify` - A file was modified
79+
- `delete` - A file or directory was deleted
80+
- `rename` - A file or directory was renamed
81+
82+
### WatchEvent Interface
83+
84+
Each event passed to the `onEvent` callback has this structure:
85+
86+
```ts
87+
interface WatchEvent {
88+
type: 'create' | 'modify' | 'delete' | 'rename';
89+
path: string; // Absolute path to the changed file
90+
isDirectory: boolean; // Whether the path is a directory
91+
}
92+
```
93+
94+
### WatchHandle Interface
95+
96+
The handle returned from `watch()` provides:
97+
98+
```ts
99+
interface WatchHandle {
100+
id: string; // Unique watch identifier
101+
path: string; // Path being watched
102+
stop(): Promise<void>; // Stop the watch and clean up
103+
}
104+
```
105+
106+
## Use Cases
107+
108+
### Hot Reload Development Server
109+
110+
Watch source files and restart your server when changes occur:
111+
112+
<TypeScriptExample>
113+
```
114+
let serverProcess: Process | undefined;
115+
116+
const handle = await sandbox.watch('/workspace/src', {
117+
include: ['*.ts', '*.js'],
118+
onEvent: async (event) => {
119+
if (event.type === 'modify' || event.type === 'create') {
120+
console.log('Detected change, restarting server...');
121+
122+
// Stop existing server
123+
if (serverProcess) {
124+
await serverProcess.kill();
125+
}
126+
127+
// Rebuild and restart
128+
await sandbox.exec('npm run build');
129+
serverProcess = await sandbox.spawn('npm start');
130+
}
131+
}
132+
});
133+
```
134+
</TypeScriptExample>
135+
136+
### Build System
137+
138+
Trigger builds when source files change:
139+
140+
<TypeScriptExample>
141+
```
142+
const handle = await sandbox.watch('/workspace', {
143+
include: ['src/**/*.ts', '*.config.js'],
144+
onEvent: async (event) => {
145+
if (event.type === 'modify' || event.type === 'create') {
146+
console.log('Running build...');
147+
const result = await sandbox.exec('npm run build');
148+
console.log(result.success ? 'Build succeeded' : 'Build failed');
149+
}
150+
}
151+
});
152+
```
153+
</TypeScriptExample>
154+
155+
### Log File Monitoring
156+
157+
Watch log files for new entries:
158+
159+
<TypeScriptExample>
160+
```
161+
const handle = await sandbox.watch('/workspace/logs', {
162+
include: ['*.log'],
163+
onEvent: async (event) => {
164+
if (event.type === 'modify') {
165+
// Read the latest log entries
166+
const file = await sandbox.readFile(event.path);
167+
const lines = file.content.split('\n');
168+
const latestEntry = lines[lines.length - 2]; // Last non-empty line
169+
170+
if (latestEntry.includes('ERROR')) {
171+
console.log('Error detected:', latestEntry);
172+
}
173+
}
174+
}
175+
});
176+
```
177+
</TypeScriptExample>
178+
179+
### File Sync Monitoring
180+
181+
Track file synchronization operations:
182+
183+
<TypeScriptExample>
184+
```
185+
const syncedFiles = new Set<string>();
186+
187+
const handle = await sandbox.watch('/workspace/uploads', {
188+
onEvent: async (event) => {
189+
switch (event.type) {
190+
case 'create':
191+
console.log(`New file uploaded: ${event.path}`);
192+
syncedFiles.add(event.path);
193+
break;
194+
case 'delete':
195+
console.log(`File removed: ${event.path}`);
196+
syncedFiles.delete(event.path);
197+
break;
198+
}
199+
200+
console.log(`Total synced files: ${syncedFiles.size}`);
201+
}
202+
});
203+
```
204+
</TypeScriptExample>
205+
206+
## Using AbortSignal
207+
208+
Cancel a watch using an AbortSignal:
209+
210+
<TypeScriptExample>
211+
```
212+
const controller = new AbortController();
213+
214+
const handle = await sandbox.watch('/workspace/src', {
215+
signal: controller.signal,
216+
onEvent: (event) => {
217+
console.log(`Change detected: ${event.path}`);
218+
},
219+
onError: (error) => {
220+
console.error('Watch error:', error);
221+
}
222+
});
223+
224+
// Cancel the watch after 30 seconds
225+
setTimeout(() => controller.abort(), 30000);
226+
```
227+
</TypeScriptExample>
228+
229+
## Error Handling
230+
231+
Handle watch errors gracefully with the `onError` callback:
232+
233+
<TypeScriptExample>
234+
```
235+
const handle = await sandbox.watch('/workspace/src', {
236+
onEvent: (event) => {
237+
console.log(`Event: ${event.type} - ${event.path}`);
238+
},
239+
onError: (error) => {
240+
console.error('Watch error occurred:', error.message);
241+
242+
// Attempt to restart the watch
243+
setTimeout(async () => {
244+
console.log('Attempting to restart watch...');
245+
await sandbox.watch('/workspace/src', { /* same options */ });
246+
}, 5000);
247+
}
248+
});
249+
```
250+
</TypeScriptExample>
251+
252+
## Performance Considerations
253+
254+
File watching uses Linux's inotify system, which is efficient but has limits:
255+
256+
- **Recursive watching**: Watching large directory trees recursively consumes inotify watches (one per directory)
257+
- **Event filtering**: Use `include` patterns to reduce event processing overhead
258+
- **Default exclusions**: Common directories like `.git` and `node_modules` are excluded by default to reduce noise
259+
260+
For large codebases, consider watching specific subdirectories instead of the entire workspace:
261+
262+
<TypeScriptExample>
263+
```
264+
// Instead of watching the entire workspace
265+
// const handle = await sandbox.watch('/workspace', { ... });
266+
267+
// Watch only relevant directories
268+
const srcHandle = await sandbox.watch('/workspace/src', { ... });
269+
const testHandle = await sandbox.watch('/workspace/tests', { ... });
270+
```
271+
</TypeScriptExample>
272+
273+
## Technical Details
274+
275+
- **Implementation**: Uses Linux's inotify via `inotifywait` command
276+
- **Event delivery**: Real-time events via Server-Sent Events (SSE) stream
277+
- **State management**: Watch lifecycle managed with state machine pattern (establishing → active → stopped)
278+
- **Cleanup**: Watches are automatically cleaned up when the handle is stopped or the sandbox terminates
279+
280+
## Related Resources
281+
282+
- [Files API](/sandbox/api/files/) - File operations
283+
- [Commands API](/sandbox/api/commands/) - Execute commands
284+
- [Manage files guide](/sandbox/guides/manage-files/) - Best practices for file operations

src/content/docs/sandbox/api/index.mdx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,14 @@ The Sandbox SDK provides a comprehensive API for executing code, managing files,
3535
Read, write, and manage files in the sandbox filesystem. Includes directory operations and file metadata.
3636
</LinkTitleCard>
3737

38+
<LinkTitleCard
39+
title="File Watching"
40+
href="/sandbox/api/file-watching/"
41+
icon="seti:eye"
42+
>
43+
Watch filesystem changes in real-time using inotify. Get instant notifications for file creation, modification, deletion, and renames.
44+
</LinkTitleCard>
45+
3846
<LinkTitleCard
3947
title="Code Interpreter"
4048
href="/sandbox/api/interpreter/"

0 commit comments

Comments
 (0)