Skip to content

Commit fb2161b

Browse files
authored
feat: introduce fetch pipe helper (google#1130)
* test: fetch > $ * feat: introduce pipe helper for `fetch` closes google#977 * chore: code imprs * docs: add `fetch.pipe` usage example * docs: extend `fetch.pipe()` descr
1 parent cb6f205 commit fb2161b

File tree

4 files changed

+79
-6
lines changed

4 files changed

+79
-6
lines changed

.size-limit.json

+3-3
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,14 @@
99
{
1010
"name": "zx/index",
1111
"path": "build/*.{js,cjs}",
12-
"limit": "812 kB",
12+
"limit": "813 kB",
1313
"brotli": false,
1414
"gzip": false
1515
},
1616
{
1717
"name": "dts libdefs",
1818
"path": "build/*.d.ts",
19-
"limit": "39.3 kB",
19+
"limit": "39.4 kB",
2020
"brotli": false,
2121
"gzip": false
2222
},
@@ -30,7 +30,7 @@
3030
{
3131
"name": "all",
3232
"path": "build/*",
33-
"limit": "851.1 kB",
33+
"limit": "852.5 kB",
3434
"brotli": false,
3535
"gzip": false
3636
}

docs/api.md

+9
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,15 @@ package.
140140

141141
```js
142142
const resp = await fetch('https://medv.io')
143+
const json = await resp.json()
144+
```
145+
146+
For some cases, `text()` or `json()` can produce extremely large output that exceeds the string size limit.
147+
Streams are just for that, so we've attached a minor adjustment to the `fetch` API to make it more pipe friendly.
148+
149+
```js
150+
const p1 = fetch('https://example.com').pipe($`cat`)
151+
const p2 = fetch('https://example.com').pipe`cat`
143152
```
144153

145154
## `question()`

src/goods.ts

+35-3
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515
import assert from 'node:assert'
1616
import { createInterface } from 'node:readline'
17+
import { Readable } from 'node:stream'
1718
import { $, within, ProcessOutput } from './core.ts'
1819
import {
1920
type Duration,
@@ -63,12 +64,43 @@ export function sleep(duration: Duration): Promise<void> {
6364
})
6465
}
6566

66-
export async function fetch(
67+
const responseToReadable = (response: Response, rs: Readable) => {
68+
const reader = response.body?.getReader()
69+
if (!reader) {
70+
rs.push(null)
71+
return rs
72+
}
73+
rs._read = async () => {
74+
const result = await reader.read()
75+
if (!result.done) rs.push(Buffer.from(result.value))
76+
else rs.push(null)
77+
}
78+
return rs
79+
}
80+
81+
export function fetch(
6782
url: RequestInfo,
6883
init?: RequestInit
69-
): Promise<Response> {
84+
): Promise<Response> & { pipe: <D>(dest: D) => D } {
7085
$.log({ kind: 'fetch', url, init, verbose: !$.quiet && $.verbose })
71-
return nodeFetch(url, init)
86+
const p = nodeFetch(url, init)
87+
88+
return Object.assign(p, {
89+
pipe(dest: any, ...args: any[]) {
90+
const rs = new Readable()
91+
const _dest = isStringLiteral(dest, ...args)
92+
? $({
93+
halt: true,
94+
signal: init?.signal as AbortSignal,
95+
})(dest as TemplateStringsArray, ...args)
96+
: dest
97+
p.then(
98+
(r) => responseToReadable(r, rs).pipe(_dest.run?.()),
99+
(err) => _dest.abort?.(err)
100+
)
101+
return _dest
102+
},
103+
})
72104
}
73105

74106
export function echo(...args: any[]): void

test/core.test.js

+32
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ import {
4343
quiet,
4444
which,
4545
nothrow,
46+
fetch,
4647
} from '../build/index.js'
4748
import { noop } from '../build/util.js'
4849

@@ -716,6 +717,37 @@ describe('core', () => {
716717
assert.equal(stdout, 'TEST')
717718
})
718719

720+
test('fetch (stream) > $', async () => {
721+
// stream.Readable.fromWeb requires Node.js 18+
722+
const responseToReadable = (response) => {
723+
const reader = response.body.getReader()
724+
const rs = new Readable()
725+
rs._read = async () => {
726+
const result = await reader.read()
727+
if (!result.done) rs.push(Buffer.from(result.value))
728+
else rs.push(null)
729+
}
730+
return rs
731+
}
732+
733+
const p = (
734+
await fetch('https://example.com').then(responseToReadable)
735+
).pipe($`cat`)
736+
const o = await p
737+
738+
assert.match(o.stdout, /Example Domain/)
739+
})
740+
741+
test('fetch (pipe) > $', async () => {
742+
const p1 = fetch('https://example.com').pipe($`cat`)
743+
const p2 = fetch('https://example.com').pipe`cat`
744+
const o1 = await p1
745+
const o2 = await p2
746+
747+
assert.match(o1.stdout, /Example Domain/)
748+
assert.equal(o1.stdout, o2.stdout)
749+
})
750+
719751
test('$ > stream > $', async () => {
720752
const p = $`echo "hello"`
721753
const { stdout } = await p.pipe(getUpperCaseTransform()).pipe($`cat`)

0 commit comments

Comments
 (0)