|
| 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 |
0 commit comments