-
Notifications
You must be signed in to change notification settings - Fork 9
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
13 changed files
with
1,060 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
/** @type {typeof globalThis.File} */ export const File: typeof globalThis.File; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
import { Blob } from './index.js' | ||
|
||
const _File = class File extends Blob { | ||
#lastModified = 0 | ||
#name = '' | ||
|
||
/** | ||
* @param {*[]} fileBits | ||
* @param {string} fileName | ||
* @param {{lastModified?: number, type?: string}} options | ||
*/// @ts-ignore | ||
constructor (fileBits, fileName, options = {}) { | ||
if (arguments.length < 2) { | ||
throw new TypeError(`Failed to construct 'File': 2 arguments required, but only ${arguments.length} present.`) | ||
} | ||
super(fileBits, options) | ||
|
||
if (options === null) options = {} | ||
|
||
// Simulate WebIDL type casting for NaN value in lastModified option. | ||
const lastModified = options.lastModified === undefined ? Date.now() : Number(options.lastModified) | ||
if (!Number.isNaN(lastModified)) { | ||
this.#lastModified = lastModified | ||
} | ||
|
||
this.#name = String(fileName) | ||
} | ||
|
||
get name () { | ||
return this.#name | ||
} | ||
|
||
get lastModified () { | ||
return this.#lastModified | ||
} | ||
|
||
get [Symbol.toStringTag] () { | ||
return 'File' | ||
} | ||
|
||
static [Symbol.hasInstance] (object) { | ||
return !!object && object instanceof Blob && | ||
/^(File)$/.test(object[Symbol.toStringTag]) | ||
} | ||
} | ||
|
||
/** @type {typeof globalThis.File} */// @ts-ignore | ||
export const File = _File |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
export default blobFromSync; | ||
/** | ||
* @param {string} path filepath on the disk | ||
* @param {string} [type] mimetype to use | ||
*/ | ||
export function blobFromSync(path: string, type?: string): Blob; | ||
import { Blob } from "./index.js"; | ||
/** | ||
* @param {string} path filepath on the disk | ||
* @param {string} [type] mimetype to use | ||
* @returns {Promise<Blob>} | ||
*/ | ||
export function blobFrom(path: string, type?: string): Promise<Blob>; | ||
/** | ||
* Creates a temporary blob backed by the filesystem. | ||
* NOTE: requires node.js v14 or higher to use FinalizationRegistry | ||
* | ||
* @param {*} data Same as fs.writeFile data | ||
* @param {BlobPropertyBag & {signal?: AbortSignal}} options | ||
* @param {AbortSignal} [signal] in case you wish to cancel the write operation | ||
* @returns {Promise<Blob>} | ||
*/ | ||
export function createTemporaryBlob(data: any, { signal, type }?: BlobPropertyBag & { | ||
signal?: AbortSignal; | ||
}): Promise<Blob>; | ||
import { File } from "./file.js"; | ||
/** | ||
* @param {string} path filepath on the disk | ||
* @param {string} [type] mimetype to use | ||
* @returns {Promise<File>} | ||
*/ | ||
export function fileFrom(path: string, type?: string): Promise<File>; | ||
/** | ||
* @param {string} path filepath on the disk | ||
* @param {string} [type] mimetype to use | ||
*/ | ||
export function fileFromSync(path: string, type?: string): File; | ||
/** | ||
* Creates a temporary File backed by the filesystem. | ||
* Pretty much the same as constructing a new File(data, name, options) | ||
* | ||
* NOTE: requires node.js v14 or higher to use FinalizationRegistry | ||
* @param {*} data | ||
* @param {string} name | ||
* @param {FilePropertyBag & {signal?: AbortSignal}} opts | ||
* @returns {Promise<File>} | ||
*/ | ||
export function createTemporaryFile(data: any, name: string, opts: FilePropertyBag & { | ||
signal?: AbortSignal; | ||
}): Promise<File>; | ||
export { Blob, File }; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,164 @@ | ||
import { | ||
realpathSync, | ||
statSync, | ||
rmdirSync, | ||
createReadStream, | ||
promises as fs | ||
} from 'node:fs' | ||
import { basename, sep, join } from 'node:path' | ||
import { tmpdir } from 'node:os' | ||
import process from 'node:process' | ||
import DOMException from 'node-domexception' | ||
|
||
import { File } from './file.js' | ||
import { Blob } from './index.js' | ||
|
||
const { stat, mkdtemp } = fs | ||
let i = 0, tempDir, registry | ||
|
||
/** | ||
* @param {string} path filepath on the disk | ||
* @param {string} [type] mimetype to use | ||
*/ | ||
const blobFromSync = (path, type) => fromBlob(statSync(path), path, type) | ||
|
||
/** | ||
* @param {string} path filepath on the disk | ||
* @param {string} [type] mimetype to use | ||
* @returns {Promise<Blob>} | ||
*/ | ||
const blobFrom = (path, type) => stat(path).then(stat => fromBlob(stat, path, type)) | ||
|
||
/** | ||
* @param {string} path filepath on the disk | ||
* @param {string} [type] mimetype to use | ||
* @returns {Promise<File>} | ||
*/ | ||
const fileFrom = (path, type) => stat(path).then(stat => fromFile(stat, path, type)) | ||
|
||
/** | ||
* @param {string} path filepath on the disk | ||
* @param {string} [type] mimetype to use | ||
*/ | ||
const fileFromSync = (path, type) => fromFile(statSync(path), path, type) | ||
|
||
// @ts-ignore | ||
const fromBlob = (stat, path, type = '') => new Blob([new BlobDataItem({ | ||
path, | ||
size: stat.size, | ||
lastModified: stat.mtimeMs, | ||
start: 0 | ||
})], { type }) | ||
|
||
// @ts-ignore | ||
const fromFile = (stat, path, type = '') => new File([new BlobDataItem({ | ||
path, | ||
size: stat.size, | ||
lastModified: stat.mtimeMs, | ||
start: 0 | ||
})], basename(path), { type, lastModified: stat.mtimeMs }) | ||
|
||
/** | ||
* Creates a temporary blob backed by the filesystem. | ||
* NOTE: requires node.js v14 or higher to use FinalizationRegistry | ||
* | ||
* @param {*} data Same as fs.writeFile data | ||
* @param {BlobPropertyBag & {signal?: AbortSignal}} options | ||
* @param {AbortSignal} [signal] in case you wish to cancel the write operation | ||
* @returns {Promise<Blob>} | ||
*/ | ||
const createTemporaryBlob = async (data, {signal, type} = {}) => { | ||
registry = registry || new FinalizationRegistry(fs.unlink) | ||
tempDir = tempDir || await mkdtemp(realpathSync(tmpdir()) + sep) | ||
const id = `${i++}` | ||
const destination = join(tempDir, id) | ||
if (data instanceof ArrayBuffer) data = new Uint8Array(data) | ||
await fs.writeFile(destination, data, { signal }) | ||
const blob = await blobFrom(destination, type) | ||
registry.register(blob, destination) | ||
return blob | ||
} | ||
|
||
/** | ||
* Creates a temporary File backed by the filesystem. | ||
* Pretty much the same as constructing a new File(data, name, options) | ||
* | ||
* NOTE: requires node.js v14 or higher to use FinalizationRegistry | ||
* @param {*} data | ||
* @param {string} name | ||
* @param {FilePropertyBag & {signal?: AbortSignal}} opts | ||
* @returns {Promise<File>} | ||
*/ | ||
const createTemporaryFile = async (data, name, opts) => { | ||
const blob = await createTemporaryBlob(data) | ||
return new File([blob], name, opts) | ||
} | ||
|
||
/** | ||
* This is a blob backed up by a file on the disk | ||
* with minium requirement. Its wrapped around a Blob as a blobPart | ||
* so you have no direct access to this. | ||
* | ||
* @private | ||
*/ | ||
class BlobDataItem { | ||
#path | ||
#start | ||
|
||
constructor (options) { | ||
this.#path = options.path | ||
this.#start = options.start | ||
this.size = options.size | ||
this.lastModified = options.lastModified | ||
this.originalSize = options.originalSize === undefined | ||
? options.size | ||
: options.originalSize | ||
} | ||
|
||
/** | ||
* Slicing arguments is first validated and formatted | ||
* to not be out of range by Blob.prototype.slice | ||
*/ | ||
slice (start, end) { | ||
return new BlobDataItem({ | ||
path: this.#path, | ||
lastModified: this.lastModified, | ||
originalSize: this.originalSize, | ||
size: end - start, | ||
start: this.#start + start | ||
}) | ||
} | ||
|
||
async * stream () { | ||
const { mtimeMs, size } = await stat(this.#path) | ||
|
||
if (mtimeMs > this.lastModified || this.originalSize !== size) { | ||
throw new DOMException('The requested file could not be read, typically due to permission problems that have occurred after a reference to a file was acquired.', 'NotReadableError') | ||
} | ||
|
||
yield * createReadStream(this.#path, { | ||
start: this.#start, | ||
end: this.#start + this.size - 1 | ||
}) | ||
} | ||
|
||
get [Symbol.toStringTag] () { | ||
return 'Blob' | ||
} | ||
} | ||
|
||
process.once('exit', () => { | ||
tempDir && rmdirSync(tempDir, { recursive: true }) | ||
}) | ||
|
||
export default blobFromSync | ||
export { | ||
Blob, | ||
blobFrom, | ||
blobFromSync, | ||
createTemporaryBlob, | ||
File, | ||
fileFrom, | ||
fileFromSync, | ||
createTemporaryFile | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
/** @type {typeof globalThis.Blob} */ | ||
export const Blob: typeof globalThis.Blob; |
Oops, something went wrong.