0
# Real-time Subscriptions
1
2
Subscribe to file change notifications that are delivered automatically as changes occur. Subscriptions provide continuous monitoring of file system changes without polling.
3
4
## Capabilities
5
6
### Create Subscription
7
8
Subscribe to file changes with optional filtering expressions that automatically sends notifications as changes occur.
9
10
```bash { .api }
11
# Use JSON input for subscription commands
12
watchman -j <<-EOT
13
["subscribe", "/path", "subscription-name", {
14
"expression": [...],
15
"fields": [...]
16
}]
17
EOT
18
```
19
20
**JSON Protocol:**
21
```json
22
["subscribe", "/absolute/path", "subscription-name", {
23
"expression": ["allof", ["type", "f"], ["suffix", "js"]],
24
"fields": ["name", "exists", "mtime"],
25
"since": "c:123:456"
26
}]
27
```
28
29
**Parameters:**
30
- `path`: Absolute path to watched directory (required)
31
- `subscription-name`: Unique name for this subscription (required)
32
- `expression`: Query expression for filtering files (optional)
33
- `fields`: Array of file metadata fields to return (optional)
34
- `since`: Optional clockspec to get changes since specific time
35
36
**Initial Response:**
37
```json
38
{
39
"version": "2.0",
40
"subscribe": "subscription-name"
41
}
42
```
43
44
**Notification Format:**
45
When files change, unilateral notifications are sent:
46
```json
47
{
48
"version": "2.0",
49
"clock": "c:1234:568",
50
"files": [
51
{
52
"name": "src/main.js",
53
"exists": true,
54
"mtime": 1234567890
55
}
56
],
57
"root": "/absolute/path",
58
"subscription": "subscription-name"
59
}
60
```
61
62
**Example:**
63
```bash
64
# Subscribe to JavaScript file changes
65
watchman -j <<-EOT
66
["subscribe", "/home/user/project", "jsfiles", {
67
"expression": ["allof", ["type", "f"], ["suffix", "js"]],
68
"fields": ["name", "exists", "size"]
69
}]
70
EOT
71
72
# Subscribe with since parameter for incremental updates
73
watchman -j <<-EOT
74
["subscribe", "/home/user/project", "incremental", {
75
"since": "n:myapp_state",
76
"expression": ["not", "empty"],
77
"fields": ["name"]
78
}]
79
EOT
80
```
81
82
### Cancel Subscription
83
84
Remove a named subscription to stop receiving notifications.
85
86
```bash { .api }
87
watchman unsubscribe <path> <subscription-name>
88
```
89
90
**JSON Protocol:**
91
```json
92
["unsubscribe", "/absolute/path", "subscription-name"]
93
```
94
95
**Parameters:**
96
- `path`: Absolute path to watched directory (required)
97
- `subscription-name`: Name of subscription to cancel (required)
98
99
**Response:**
100
```json
101
{
102
"version": "2.0",
103
"unsubscribe": "subscription-name",
104
"deleted": true
105
}
106
```
107
108
**Example:**
109
```bash
110
# Cancel a specific subscription
111
watchman unsubscribe /home/user/project jsfiles
112
113
# Using JSON protocol
114
watchman -j <<-EOT
115
["unsubscribe", "/home/user/project", "jsfiles"]
116
EOT
117
```
118
119
## Subscription Behavior
120
121
### Connection-Based Lifecycle
122
- Subscriptions are tied to the client connection
123
- Automatically removed when connection closes
124
- Not persisted across Watchman restarts
125
- Require persistent connection to receive notifications
126
127
### Initial File Set
128
- When first created, subscription evaluates expression against all files
129
- Sends initial notification if any files match
130
- Subsequent notifications sent only for changes
131
132
### File System Settling
133
- Respects Watchman's settle period before sending notifications
134
- Batches multiple changes into single notification
135
- Reduces notification volume during bulk operations
136
137
### Notification Delivery
138
- Sent unilaterally by server as changes occur
139
- Client must be prepared to receive at any time
140
- No acknowledgment required from client
141
- Notifications are JSON objects followed by newline
142
143
## Expression Filtering
144
145
Subscriptions support the same expression syntax as queries for flexible file filtering.
146
147
### Basic Expressions
148
149
```bash
150
# All files
151
"expression": ["true"]
152
153
# Regular files only
154
"expression": ["type", "f"]
155
156
# Specific file extension
157
"expression": ["suffix", "js"]
158
159
# Multiple extensions
160
"expression": ["anyof", ["suffix", "js"], ["suffix", "ts"]]
161
162
# Non-empty files
163
"expression": ["not", "empty"]
164
```
165
166
### Complex Filtering
167
168
```bash
169
# JavaScript test files only
170
watchman -j <<-EOT
171
["subscribe", "/project", "tests", {
172
"expression": ["allof",
173
["type", "f"],
174
["suffix", "js"],
175
["match", "*test*", "basename"]
176
],
177
"fields": ["name"]
178
}]
179
EOT
180
181
# Source files excluding node_modules
182
watchman -j <<-EOT
183
["subscribe", "/project", "sources", {
184
"expression": ["allof",
185
["type", "f"],
186
["anyof", ["suffix", "js"], ["suffix", "ts"], ["suffix", "json"]],
187
["not", ["match", "node_modules/**", "wholename"]]
188
],
189
"fields": ["name", "size"]
190
}]
191
EOT
192
```
193
194
## Field Selection
195
196
Control which file metadata is included in notifications:
197
198
### Common Fields
199
- `name`: Relative file path (always useful)
200
- `exists`: File existence flag
201
- `size`: File size in bytes
202
- `mode`: Unix file permissions
203
- `mtime`: Modification timestamp
204
205
### Extended Fields
206
- `uid`, `gid`: User and group IDs
207
- `ctime`, `atime`: Creation and access times
208
- `ino`: Inode number
209
- `dev`: Device ID
210
- `nlink`: Hard link count
211
- `new`: True if newly created (since queries only)
212
213
**Example with custom fields:**
214
```bash
215
watchman -j <<-EOT
216
["subscribe", "/project", "detailed", {
217
"expression": ["suffix", "py"],
218
"fields": ["name", "exists", "size", "mtime", "mode"]
219
}]
220
EOT
221
```
222
223
## Usage Examples
224
225
### Development File Watcher
226
227
```bash
228
#!/bin/bash
229
# dev-watcher.sh - Monitor development files
230
231
# Connect to watchman and maintain persistent connection
232
exec 3< <(watchman -j -p <<-'EOT'
233
["subscribe", "/home/user/project", "dev-files", {
234
"expression": ["allof",
235
["type", "f"],
236
["anyof",
237
["suffix", "js"], ["suffix", "ts"], ["suffix", "jsx"],
238
["suffix", "css"], ["suffix", "html"], ["suffix", "json"]
239
],
240
["not", ["match", "node_modules/**", "wholename"]]
241
],
242
"fields": ["name", "exists", "size"]
243
}]
244
EOT
245
)
246
247
# Read notifications
248
while read -r -u 3 notification; do
249
echo "Received: $notification"
250
251
# Parse and process notification
252
if echo "$notification" | jq -e '.subscription == "dev-files"' > /dev/null; then
253
files=$(echo "$notification" | jq -r '.files[].name')
254
echo "Changed files: $files"
255
256
# Trigger build or other actions
257
echo "Rebuilding..."
258
npm run build
259
fi
260
done
261
```
262
263
### Live Reload Implementation
264
265
```bash
266
#!/bin/bash
267
# live-reload.sh - Browser live reload on file changes
268
269
# WebSocket server for browser communication (conceptual)
270
start_websocket_server() {
271
# Start WebSocket server on port 8080
272
websocketd --port=8080 cat &
273
WS_PID=$!
274
}
275
276
# Subscribe to web asset changes
277
monitor_assets() {
278
watchman -j -p <<-'EOT' | while read -r notification; do
279
["subscribe", "/var/www/html", "assets", {
280
"expression": ["allof",
281
["type", "f"],
282
["anyof", ["suffix", "html"], ["suffix", "css"], ["suffix", "js"]]
283
],
284
"fields": ["name"]
285
}]
286
EOT
287
if echo "$notification" | jq -e '.subscription == "assets"' > /dev/null; then
288
# Send reload signal to browser
289
echo '{"type": "reload"}' > /dev/tcp/localhost/8080
290
fi
291
done
292
}
293
294
start_websocket_server
295
monitor_assets
296
```
297
298
### Test Runner Integration
299
300
```bash
301
#!/bin/bash
302
# auto-test.sh - Run tests when source files change
303
304
watchman -j -p <<-'EOT' | while read -r notification; do
305
["subscribe", "/project", "test-runner", {
306
"expression": ["allof",
307
["type", "f"],
308
["anyof",
309
["match", "src/**/*.py", "wholename"],
310
["match", "tests/**/*.py", "wholename"]
311
]
312
],
313
"fields": ["name"]
314
}]
315
EOT
316
if echo "$notification" | jq -e '.subscription == "test-runner"' > /dev/null; then
317
changed_files=$(echo "$notification" | jq -r '.files[].name | select(test("test_.*\\.py$"))')
318
319
if [ -n "$changed_files" ]; then
320
echo "Running tests for: $changed_files"
321
python -m pytest $changed_files
322
else
323
echo "Source files changed, running all tests"
324
python -m pytest tests/
325
fi
326
fi
327
done
328
```
329
330
## Protocol Considerations
331
332
### Persistent Connections
333
- Use `-p` flag for persistent mode
334
- Maintain connection to receive notifications
335
- Handle connection failures and reconnection
336
337
### JSON Parsing
338
- Each notification is a complete JSON object
339
- Followed by newline character
340
- Parse incrementally as messages arrive
341
342
### Error Handling
343
- Watch for error responses in JSON stream
344
- Handle subscription failures gracefully
345
- Implement reconnection logic for production use
346
347
### Performance
348
- Use specific expressions to reduce notification volume
349
- Select only needed fields to minimize data transfer
350
- Consider batching file operations based on notifications