0
# Network Operations
1
2
HTTP operations with fetch API and piping support for data streaming and network requests in scripts.
3
4
## Capabilities
5
6
### HTTP Fetch
7
8
Enhanced fetch implementation with piping support for streaming data directly to commands or files.
9
10
```typescript { .api }
11
/**
12
* Fetch HTTP resources with piping support
13
* @param url - URL to fetch or Request object
14
* @param init - Request configuration options
15
* @returns Promise resolving to Response with pipe method
16
*/
17
function fetch(
18
url: RequestInfo,
19
init?: RequestInit
20
): Promise<Response> & {
21
pipe: {
22
/** Pipe response to shell command */
23
(dest: TemplateStringsArray, ...args: any[]): ProcessPromise;
24
/** Pipe response to destination */
25
<D>(dest: D): D;
26
};
27
};
28
```
29
30
**Basic Usage:**
31
32
```typescript
33
import { fetch, echo } from "zx";
34
35
// Simple HTTP request
36
const response = await fetch('https://api.github.com/user', {
37
headers: {
38
'Authorization': 'token ghp_xxxxxxxxxxxx',
39
'User-Agent': 'MyScript/1.0'
40
}
41
});
42
43
const userData = await response.json();
44
echo`User: ${userData.login}`;
45
46
// POST request
47
const createResponse = await fetch('https://api.example.com/items', {
48
method: 'POST',
49
headers: {
50
'Content-Type': 'application/json',
51
},
52
body: JSON.stringify({
53
name: 'New Item',
54
description: 'Created from script'
55
})
56
});
57
58
if (createResponse.ok) {
59
echo('Item created successfully');
60
} else {
61
echo`Error: ${createResponse.status} ${createResponse.statusText}`;
62
}
63
```
64
65
### Streaming and Piping
66
67
Stream HTTP responses directly to commands or files without loading into memory.
68
69
```typescript { .api }
70
interface FetchWithPipe extends Promise<Response> {
71
pipe: {
72
/** Pipe response data to shell command */
73
(dest: TemplateStringsArray, ...args: any[]): ProcessPromise;
74
/** Pipe response data to any destination */
75
<D>(dest: D): D;
76
};
77
}
78
```
79
80
**Piping Examples:**
81
82
```typescript
83
import { fetch, $ } from "zx";
84
85
// Download and pipe to file
86
await fetch('https://releases.ubuntu.com/20.04/ubuntu-20.04.6-desktop-amd64.iso')
87
.pipe('./ubuntu.iso');
88
89
// Download and pipe through commands
90
await fetch('https://api.github.com/repos/microsoft/vscode/releases')
91
.pipe`jq '.[0].tag_name'`
92
.pipe('./latest-version.txt');
93
94
// Stream processing
95
const logProcessor = fetch('https://logs.example.com/app.log')
96
.pipe`grep "ERROR"`
97
.pipe`wc -l`;
98
99
const errorCount = await logProcessor;
100
echo`Found ${errorCount.stdout.trim()} errors in logs`;
101
102
// Multiple pipe operations
103
await fetch('https://raw.githubusercontent.com/user/repo/main/data.csv')
104
.pipe`head -100` // First 100 lines
105
.pipe`cut -d',' -f1,3` // Extract columns 1 and 3
106
.pipe`sort` // Sort the data
107
.pipe('./processed-data.csv'); // Save to file
108
```
109
110
### Response Processing
111
112
Handle different response types and status codes.
113
114
**Examples:**
115
116
```typescript
117
import { fetch, echo, $ } from "zx";
118
119
// JSON API responses
120
const apiResponse = await fetch('https://api.github.com/repos/google/zx');
121
if (apiResponse.ok) {
122
const repo = await apiResponse.json();
123
echo`Repository: ${repo.full_name}`;
124
echo`Stars: ${repo.stargazers_count}`;
125
echo`Language: ${repo.language}`;
126
} else {
127
echo`API Error: ${apiResponse.status}`;
128
}
129
130
// Text responses
131
const readmeResponse = await fetch('https://raw.githubusercontent.com/google/zx/main/README.md');
132
const readmeText = await readmeResponse.text();
133
echo`README length: ${readmeText.length} characters`;
134
135
// Binary data
136
const imageResponse = await fetch('https://github.com/google/zx/raw/main/docs/img/logo.svg');
137
const imageBuffer = await imageResponse.arrayBuffer();
138
echo`Image size: ${imageBuffer.byteLength} bytes`;
139
140
// Stream processing
141
const largeFileResponse = await fetch('https://example.com/large-dataset.json');
142
const reader = largeFileResponse.body?.getReader();
143
144
if (reader) {
145
let chunks = 0;
146
let totalBytes = 0;
147
148
while (true) {
149
const { done, value } = await reader.read();
150
if (done) break;
151
152
chunks++;
153
totalBytes += value.length;
154
155
if (chunks % 100 === 0) {
156
echo`Processed ${chunks} chunks, ${totalBytes} bytes`;
157
}
158
}
159
}
160
```
161
162
### Request Configuration
163
164
Configure requests with headers, authentication, and other options.
165
166
**Examples:**
167
168
```typescript
169
import { fetch, echo, argv } from "zx";
170
171
// With authentication
172
const apiKey = argv.apiKey || process.env.API_KEY;
173
const authenticatedResponse = await fetch('https://api.example.com/protected', {
174
headers: {
175
'Authorization': `Bearer ${apiKey}`,
176
'Content-Type': 'application/json'
177
}
178
});
179
180
// With timeout using AbortController
181
const controller = new AbortController();
182
const timeoutId = setTimeout(() => controller.abort(), 10000); // 10 second timeout
183
184
try {
185
const response = await fetch('https://slow-api.example.com/data', {
186
signal: controller.signal
187
});
188
clearTimeout(timeoutId);
189
190
const data = await response.json();
191
echo`Received data: ${JSON.stringify(data, null, 2)}`;
192
} catch (error) {
193
if (error.name === 'AbortError') {
194
echo('Request timed out');
195
} else {
196
echo`Request failed: ${error.message}`;
197
}
198
}
199
200
// Form data submission
201
const formData = new FormData();
202
formData.append('file', './upload.txt');
203
formData.append('description', 'Uploaded from script');
204
205
const uploadResponse = await fetch('https://api.example.com/upload', {
206
method: 'POST',
207
body: formData
208
});
209
210
if (uploadResponse.ok) {
211
echo('File uploaded successfully');
212
}
213
214
// Custom request headers
215
const customResponse = await fetch('https://api.example.com/data', {
216
headers: {
217
'User-Agent': 'ZX-Script/1.0',
218
'Accept': 'application/json',
219
'X-Custom-Header': 'custom-value'
220
}
221
});
222
```
223
224
### Error Handling and Retry
225
226
Handle network errors and implement retry logic.
227
228
**Examples:**
229
230
```typescript
231
import { fetch, echo, retry, sleep } from "zx";
232
233
// Simple retry on failure
234
const dataWithRetry = await retry(3, async () => {
235
const response = await fetch('https://unreliable-api.example.com/data');
236
if (!response.ok) {
237
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
238
}
239
return response.json();
240
});
241
242
// Retry with exponential backoff
243
const reliableData = await retry(5, '1s', async () => {
244
try {
245
const response = await fetch('https://api.example.com/critical-data');
246
247
if (response.status === 429) { // Rate limited
248
throw new Error('Rate limited, will retry');
249
}
250
251
if (!response.ok) {
252
throw new Error(`HTTP ${response.status}`);
253
}
254
255
return response.json();
256
} catch (error) {
257
echo`Fetch failed: ${error.message}, retrying...`;
258
throw error;
259
}
260
});
261
262
// Health check with timeout
263
async function checkServiceHealth(url) {
264
const controller = new AbortController();
265
const timeout = setTimeout(() => controller.abort(), 5000);
266
267
try {
268
const response = await fetch(`${url}/health`, {
269
signal: controller.signal
270
});
271
clearTimeout(timeout);
272
273
return response.ok;
274
} catch (error) {
275
clearTimeout(timeout);
276
return false;
277
}
278
}
279
280
const services = ['https://api1.example.com', 'https://api2.example.com'];
281
for (const service of services) {
282
const healthy = await checkServiceHealth(service);
283
echo`${service}: ${healthy ? 'healthy' : 'unhealthy'}`;
284
}
285
```
286
287
### Working with APIs
288
289
Common patterns for working with REST APIs and webhooks.
290
291
**Examples:**
292
293
```typescript
294
import { fetch, echo, question, argv } from "zx";
295
296
// GitHub API integration
297
async function getLatestRelease(repo) {
298
const response = await fetch(`https://api.github.com/repos/${repo}/releases/latest`);
299
if (!response.ok) {
300
throw new Error(`Failed to get release info: ${response.status}`);
301
}
302
return response.json();
303
}
304
305
const release = await getLatestRelease('google/zx');
306
echo`Latest ZX version: ${release.tag_name}`;
307
echo`Published: ${new Date(release.published_at).toLocaleDateString()}`;
308
309
// Webhook notifications
310
async function sendSlackNotification(webhook, message) {
311
const response = await fetch(webhook, {
312
method: 'POST',
313
headers: { 'Content-Type': 'application/json' },
314
body: JSON.stringify({ text: message })
315
});
316
317
return response.ok;
318
}
319
320
const webhookUrl = argv.slackWebhook;
321
if (webhookUrl) {
322
await sendSlackNotification(webhookUrl, 'Deployment completed successfully! ๐');
323
}
324
325
// REST API CRUD operations
326
const baseUrl = 'https://api.example.com';
327
const apiKey = process.env.API_KEY;
328
329
// Create
330
const newUser = await fetch(`${baseUrl}/users`, {
331
method: 'POST',
332
headers: {
333
'Authorization': `Bearer ${apiKey}`,
334
'Content-Type': 'application/json'
335
},
336
body: JSON.stringify({
337
name: 'John Doe',
338
email: 'john@example.com'
339
})
340
});
341
342
// Read
343
const users = await fetch(`${baseUrl}/users?limit=10`, {
344
headers: { 'Authorization': `Bearer ${apiKey}` }
345
});
346
347
// Update
348
const updatedUser = await fetch(`${baseUrl}/users/123`, {
349
method: 'PATCH',
350
headers: {
351
'Authorization': `Bearer ${apiKey}`,
352
'Content-Type': 'application/json'
353
},
354
body: JSON.stringify({ status: 'active' })
355
});
356
357
// Delete
358
const deleteResult = await fetch(`${baseUrl}/users/123`, {
359
method: 'DELETE',
360
headers: { 'Authorization': `Bearer ${apiKey}` }
361
});
362
```
363
364
## Types
365
366
```typescript { .api }
367
/**
368
* Request URL or Request object
369
*/
370
type RequestInfo = string | URL | Request;
371
372
/**
373
* Request configuration options
374
*/
375
interface RequestInit {
376
method?: string;
377
headers?: HeadersInit;
378
body?: BodyInit | null;
379
mode?: RequestMode;
380
credentials?: RequestCredentials;
381
cache?: RequestCache;
382
redirect?: RequestRedirect;
383
referrer?: string;
384
referrerPolicy?: ReferrerPolicy;
385
integrity?: string;
386
keepalive?: boolean;
387
signal?: AbortSignal | null;
388
}
389
390
/**
391
* HTTP response object
392
*/
393
interface Response extends Body {
394
readonly headers: Headers;
395
readonly ok: boolean;
396
readonly redirected: boolean;
397
readonly status: number;
398
readonly statusText: string;
399
readonly type: ResponseType;
400
readonly url: string;
401
402
clone(): Response;
403
}
404
405
/**
406
* Response body methods
407
*/
408
interface Body {
409
readonly body: ReadableStream<Uint8Array> | null;
410
readonly bodyUsed: boolean;
411
412
arrayBuffer(): Promise<ArrayBuffer>;
413
blob(): Promise<Blob>;
414
formData(): Promise<FormData>;
415
json(): Promise<any>;
416
text(): Promise<string>;
417
}
418
```