Skip to content

Commit c275e9c

Browse files
committed
0.0.8 - fix empty Blob URL and invalid byte range
1 parent 803781b commit c275e9c

9 files changed

Lines changed: 506 additions & 310 deletions

File tree

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
# CHANGELOG
22

3+
## [0.0.8]
4+
5+
- return with no iteration if the URL is an empty Blob URL
6+
- return with no iteration if the byte range is invalid
7+
38
## [0.0.7]
49

510
- export toURL utility function

package-lock.json

Lines changed: 7 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "csv-range",
3-
"version": "0.0.7",
3+
"version": "0.0.8",
44
"description": "Parse ranges of a CSV file.",
55
"type": "module",
66
"license": "MIT",
@@ -46,7 +46,7 @@
4646
"@vitest/ui": "4.0.10",
4747
"bumpp": "10.3.1",
4848
"eslint": "9.39.1",
49-
"eslint-plugin-jsdoc": "61.2.1",
49+
"eslint-plugin-jsdoc": "61.3.0",
5050
"eslint-plugin-simple-import-sort": "12.1.1",
5151
"globals": "16.5.0",
5252
"tsdown": "0.16.5",

src/fetch.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,10 @@ export async function fetchRange({
7878
},
7979
}
8080
const response = await fetch(url, mergedRequestInit)
81+
// Note that the range fetch might throw if the range is invalid, and the URL is a Blob URL.
82+
// With "normal" URLs (http, https), the server should return a 416 status code instead.
83+
// See the spec https://fetch.spec.whatwg.org/#scheme-fetch.
84+
8185
if (response.status === 416) {
8286
// Requested Range Not Satisfiable
8387
throw new Error(

src/url.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { defaultChunkSize } from './options/constants'
44
import { type DelimiterError, type ParseOptions, validateAndGuessParseOptions } from './options/parseOptions'
55
import { parse } from './parser'
66
import type { ParseResult } from './types'
7-
import { decode } from './utils'
7+
import { decode, isEmptyBlobURL } from './utils'
88

99
interface FetchOptions {
1010
chunkSize?: number
@@ -45,7 +45,19 @@ export async function* parseURL(
4545
): AsyncGenerator<ParseResult, void, unknown> {
4646
const chunkSize = checkIntegerGreaterOrEqualThan(options.chunkSize, 1) ?? defaultChunkSize
4747
let firstByte = checkIntegerGreaterOrEqualThan(options.firstByte, 0) ?? 0
48+
// Note: lastByte can be -1 to indicate no data to fetch.
49+
// It's related to the bug in Node.js (https://github.com/nodejs/node/issues/60382) and the
50+
// workaround we propose with toURL util (add ' ' at the end of the file, and set lastByte
51+
// to the original file size - 1). If the file is empty, lastByte will be -1.
4852
let lastByte = checkIntegerGreaterOrEqualThan(options.lastByte, -1)
53+
if (lastByte !== undefined && lastByte < firstByte) {
54+
// No data to fetch
55+
return
56+
}
57+
// Return if the URL is an empty Blob URL
58+
if (await isEmptyBlobURL(url)) {
59+
return
60+
}
4961

5062
const { delimitersToGuess, stripBOM } = options
5163
let parseOptions: ParseOptions | undefined = undefined

src/utils.ts

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,50 @@
11
/**
22
* Creates a blob URL from the given text.
33
* @param text The text to create a blob URL from.
4-
* @returns An object containing the blob URL, the size of the text in bytes, and a function to revoke the URL.
4+
* @param options Options.
5+
* @param options.withNodeWorkaround Whether to add an extra space at the end of the text
6+
* to work around the Node.js bug (https://github.com/nodejs/node/issues/60382). Defaults to false.
7+
* @returns An object containing the blob URL, the size of the file in bytes (without the extra space,
8+
* if `withNodeWorkaround` is true), and a function to revoke the URL.
59
*/
6-
export function toURL(text: string): {
10+
export function toURL(text: string, { withNodeWorkaround }: { withNodeWorkaround?: boolean } = {}): {
711
url: string
812
fileSize: number
913
revoke: () => void
1014
} {
15+
withNodeWorkaround = withNodeWorkaround ?? false
1116
// add an extra space to fix https://github.com/nodejs/node/issues/60382
12-
const blob = new Blob([text + ' '])
17+
const blob = new Blob([withNodeWorkaround ? text + ' ' : text])
1318
const url = URL.createObjectURL(blob)
1419
return {
1520
url,
16-
fileSize: blob.size - 1, // subtract the extra space
21+
// remove the extra space from the file size
22+
fileSize: withNodeWorkaround ? blob.size - 1 : blob.size,
1723
revoke: () => {
1824
URL.revokeObjectURL(url)
1925
},
2026
}
2127
}
2228

29+
/**
30+
* Checks if the given URL is an empty Blob URL.
31+
* @param url The URL to check.
32+
* @returns Whether the URL is an empty Blob URL.
33+
*/
34+
export async function isEmptyBlobURL(url: string): Promise<boolean> {
35+
if (!url.startsWith('blob:')) {
36+
return false
37+
}
38+
try {
39+
const response = await fetch(url)
40+
const blob = await response.blob()
41+
return blob.size === 0
42+
}
43+
catch {
44+
return false
45+
}
46+
}
47+
2348
/**
2449
* Decodes the given bytes using the provided decoder.
2550
* @param bytes The bytes to decode.

0 commit comments

Comments
 (0)