Skip to content

Commit

Permalink
Added "satisfies" checks to sync functions
Browse files Browse the repository at this point in the history
Added PathLike type
Simplified exported fs type
Removed old types in comments
Added BufferToUint8Array type
  • Loading branch information
james-pre committed Feb 14, 2024
1 parent 5767195 commit 055e9c7
Show file tree
Hide file tree
Showing 6 changed files with 310 additions and 222 deletions.
162 changes: 81 additions & 81 deletions src/emulation/callbacks.ts

Large diffs are not rendered by default.

15 changes: 1 addition & 14 deletions src/emulation/fs.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,7 @@
import * as fs_mock from './index.js';
import type * as fs_node from 'node:fs';

/**
* fixes __promisify__
*/
type FSMock = {
[K in keyof typeof fs_mock]: K extends keyof typeof fs_mock.promises
? (typeof fs_mock.promises)[K] extends (...args) => Promise<unknown>
? (typeof fs_node)[K] extends { __promisify__(...args): unknown }
? (typeof fs_mock)[K] & { __promisify__: (typeof fs_node)[K]['__promisify__'] }
: (typeof fs_mock)[K]
: (typeof fs_mock)[K]
: (typeof fs_mock)[K];
};

const fs: typeof fs_node & typeof fs_mock = fs_mock as FSMock;
const fs = fs_mock as typeof fs_node & typeof fs_mock;

export * from './index.js';
export default fs;
127 changes: 65 additions & 62 deletions src/emulation/promises.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import type { ReadStream, WriteStream, FSWatcher, symlink as _symlink, StatOptions } from 'node:fs';
import type { ReadStream, WriteStream, FSWatcher, symlink as _symlink, StatOptions, BaseEncodingOptions, BufferEncodingOption } from 'node:fs';
import { ApiError, ErrorCode } from '../ApiError.js';

import * as constants from './constants.js';
export { constants };

import { File, FileFlag } from '../file.js';
import { normalizePath, normalizeMode, getFdForFile, normalizeOptions, fd2file, fdMap, normalizeTime, cred, nop, resolveFS, fixError, mounts } from './shared.js';
import { normalizePath, normalizeMode, getFdForFile, normalizeOptions, fd2file, fdMap, normalizeTime, cred, nop, resolveFS, fixError, mounts, PathLike } from './shared.js';
import { FileContents, FileSystem } from '../filesystem.js';
import { BigIntStats, Stats } from '../stats.js';
import { decode, encode } from '../utils.js';
Expand Down Expand Up @@ -48,7 +48,7 @@ async function doOp<M extends FileSystemMethod, RT extends ReturnType<M>>(...[na
* @param oldPath
* @param newPath
*/
export async function rename(oldPath: string, newPath: string): Promise<void> {
export async function rename(oldPath: PathLike, newPath: PathLike): Promise<void> {
oldPath = normalizePath(oldPath);
newPath = normalizePath(newPath);
const _old = resolveFS(oldPath);
Expand All @@ -71,7 +71,7 @@ export async function rename(oldPath: string, newPath: string): Promise<void> {
* Test whether or not the given path exists by checking with the file system.
* @param path
*/
export async function exists(path: string): Promise<boolean> {
export async function exists(path: PathLike): Promise<boolean> {
path = normalizePath(path);
try {
const { fs, path: resolvedPath } = resolveFS(path);
Expand All @@ -90,9 +90,9 @@ export async function exists(path: string): Promise<boolean> {
* @param path
* @returns Stats
*/
export async function stat(path: string, options?: { bigint?: false }): Promise<Stats>;
export async function stat(path: string, options: { bigint: true }): Promise<BigIntStats>;
export async function stat(path: string, options?: StatOptions): Promise<Stats | BigIntStats> {
export async function stat(path: PathLike, options?: { bigint?: false }): Promise<Stats>;
export async function stat(path: PathLike, options: { bigint: true }): Promise<BigIntStats>;
export async function stat(path: PathLike, options?: StatOptions): Promise<Stats | BigIntStats> {
const _stats: Stats = await doOp('stat', true, path, cred);
let stats: Stats | BigIntStats = _stats;
if (options?.bigint) {
Expand All @@ -108,9 +108,9 @@ export async function stat(path: string, options?: StatOptions): Promise<Stats |
* @param path
* @return [BrowserFS.node.fs.Stats]
*/
export async function lstat(path: string, options?: { bigint?: false }): Promise<Stats>;
export async function lstat(path: string, options: { bigint: true }): Promise<BigIntStats>;
export async function lstat(path: string, options?: StatOptions): Promise<Stats | BigIntStats> {
export async function lstat(path: PathLike, options?: { bigint?: false }): Promise<Stats>;
export async function lstat(path: PathLike, options: { bigint: true }): Promise<BigIntStats>;
export async function lstat(path: PathLike, options?: StatOptions): Promise<Stats | BigIntStats> {
const _stats: Stats = await doOp('stat', false, path, cred);
let stats: Stats | BigIntStats = _stats;
if (options?.bigint) {
Expand All @@ -126,7 +126,7 @@ export async function lstat(path: string, options?: StatOptions): Promise<Stats
* @param path
* @param len
*/
export async function truncate(path: string, len: number = 0): Promise<void> {
export async function truncate(path: PathLike, len: number = 0): Promise<void> {
if (len < 0) {
throw new ApiError(ErrorCode.EINVAL);
}
Expand All @@ -137,7 +137,7 @@ export async function truncate(path: string, len: number = 0): Promise<void> {
* `unlink`.
* @param path
*/
export async function unlink(path: string): Promise<void> {
export async function unlink(path: PathLike): Promise<void> {
return doOp('unlink', false, path, cred);
}

Expand All @@ -148,7 +148,7 @@ export async function unlink(path: string): Promise<void> {
* @param flags
* @param mode defaults to `0644`
*/
export async function open(path: string, flag: string, mode: number | string = 0o644): Promise<number> {
export async function open(path: PathLike, flag: string, mode: number | string = 0o644): Promise<number> {
const file: File = await doOp('open', true, path, FileFlag.getFileFlag(flag), normalizeMode(mode, 0o644), cred);
return getFdForFile(file);
}
Expand All @@ -157,14 +157,14 @@ export async function open(path: string, flag: string, mode: number | string = 0
* Synchronously reads the entire contents of a file.
* @param filename
* @param options
* @option options [String] encoding The string encoding for the file contents. Defaults to `null`.
* @option options [String] flag Defaults to `'r'`.
* @return [String | BrowserFS.node.Uint8Array]
*/
export async function readFile(filename: string, options?: { flag?: string }): Promise<Uint8Array>;
export async function readFile(filename: string, options: { encoding: string; flag?: string }): Promise<string>;
export async function readFile(filename: string, encoding: string): Promise<string>;
export async function readFile(filename: string, arg2 = {}): Promise<Uint8Array | string> {
* options.encoding The string encoding for the file contents. Defaults to `null`.
* options.flag Defaults to `'r'`.
* @return Uint8Array
*/
export async function readFile(filename: PathLike, options?: { flag?: string }): Promise<Uint8Array>;
export async function readFile(filename: PathLike, options: { encoding: string; flag?: string }): Promise<string>;
export async function readFile(filename: PathLike, encoding: string): Promise<string>;
export async function readFile(filename: PathLike, arg2 = {}): Promise<Uint8Array | string> {
const options = normalizeOptions(arg2, null, 'r', null);
const flag = FileFlag.getFileFlag(options.flag);
if (!flag.isReadable()) {
Expand Down Expand Up @@ -192,10 +192,10 @@ export async function readFile(filename: string, arg2 = {}): Promise<Uint8Array
* @option options [Number] mode Defaults to `0644`.
* @option options [String] flag Defaults to `'w'`.
*/
export async function writeFile(filename: string, data: FileContents, options?: { encoding?: string; mode?: number | string; flag?: string }): Promise<void>;
export async function writeFile(filename: string, data: FileContents, encoding?: string): Promise<void>;
export async function writeFile(filename: string, data: FileContents, options?: { encoding?: string; mode?: number | string; flag?: string } | string): Promise<void>;
export async function writeFile(filename: string, data: FileContents, arg3?: { encoding?: string; mode?: number | string; flag?: string } | string): Promise<void> {
export async function writeFile(filename: PathLike, data: FileContents, options?: { encoding?: string; mode?: number | string; flag?: string }): Promise<void>;
export async function writeFile(filename: PathLike, data: FileContents, encoding?: string): Promise<void>;
export async function writeFile(filename: PathLike, data: FileContents, options?: { encoding?: string; mode?: number | string; flag?: string } | string): Promise<void>;
export async function writeFile(filename: PathLike, data: FileContents, arg3?: { encoding?: string; mode?: number | string; flag?: string } | string): Promise<void> {
const options = normalizeOptions(arg3, 'utf8', 'w', 0o644);
const flag = FileFlag.getFileFlag(options.flag);
if (!flag.isWriteable()) {
Expand Down Expand Up @@ -224,9 +224,9 @@ export async function writeFile(filename: string, data: FileContents, arg3?: { e
* @option options [Number] mode Defaults to `0644`.
* @option options [String] flag Defaults to `'a'`.
*/
export async function appendFile(filename: string, data: FileContents, options?: { encoding?: string; mode?: number | string; flag?: string }): Promise<void>;
export async function appendFile(filename: string, data: FileContents, encoding?: string): Promise<void>;
export async function appendFile(filename: string, data: FileContents, arg3?): Promise<void> {
export async function appendFile(filename: PathLike, data: FileContents, options?: { encoding?: string; mode?: number | string; flag?: string }): Promise<void>;
export async function appendFile(filename: PathLike, data: FileContents, encoding?: string): Promise<void>;
export async function appendFile(filename: PathLike, data: FileContents, arg3?): Promise<void> {
const options = normalizeOptions(arg3, 'utf8', 'a', 0o644);
const flag = FileFlag.getFileFlag(options.flag);
if (!flag.isAppendable()) {
Expand Down Expand Up @@ -398,7 +398,7 @@ export async function futimes(fd: number, atime: number | Date, mtime: number |
* `rmdir`.
* @param path
*/
export async function rmdir(path: string): Promise<void> {
export async function rmdir(path: PathLike): Promise<void> {
return doOp('rmdir', true, path, cred);
}

Expand All @@ -407,17 +407,17 @@ export async function rmdir(path: string): Promise<void> {
* @param path
* @param mode defaults to `0777`
*/
export async function mkdir(path: string, mode?: number | string): Promise<void> {
export async function mkdir(path: PathLike, mode?: number | string): Promise<void> {
return doOp('mkdir', true, path, normalizeMode(mode, 0o777), cred);
}

/**
* `readdir`. Reads the contents of a directory.
* @param path
*/
export async function readdir(path: string, options: { withFileTypes?: false }): Promise<string[]>;
export async function readdir(path: string, options: { withFileTypes: true }): Promise<Dirent[]>;
export async function readdir(path: string, options?: { withFileTypes?: boolean }): Promise<string[] | Dirent[]> {
export async function readdir(path: PathLike, options: { withFileTypes?: false }): Promise<string[]>;
export async function readdir(path: PathLike, options: { withFileTypes: true }): Promise<Dirent[]>;
export async function readdir(path: PathLike, options?: { withFileTypes?: boolean }): Promise<string[] | Dirent[]> {
path = normalizePath(path);
const entries: string[] = await doOp('readdir', true, path, cred);
const points = [...mounts.keys()];
Expand Down Expand Up @@ -445,7 +445,7 @@ export async function readdir(path: string, options?: { withFileTypes?: boolean
* @param srcpath
* @param dstpath
*/
export async function link(srcpath: string, dstpath: string): Promise<void> {
export async function link(srcpath: PathLike, dstpath: PathLike): Promise<void> {
dstpath = normalizePath(dstpath);
return doOp('link', false, srcpath, dstpath, cred);
}
Expand All @@ -456,7 +456,7 @@ export async function link(srcpath: string, dstpath: string): Promise<void> {
* @param dstpath
* @param type can be either `'dir'` or `'file'` (default is `'file'`)
*/
export async function symlink(srcpath: string, dstpath: string, type: _symlink.Type = 'file'): Promise<void> {
export async function symlink(srcpath: PathLike, dstpath: PathLike, type: _symlink.Type = 'file'): Promise<void> {
if (!['file', 'dir', 'junction'].includes(type)) {
throw new ApiError(ErrorCode.EINVAL, 'Invalid type: ' + type);
}
Expand All @@ -467,10 +467,13 @@ export async function symlink(srcpath: string, dstpath: string, type: _symlink.T
/**
* readlink.
* @param path
* @return [String]
*/
export async function readlink(path: string): Promise<string> {
return doOp('readlink', false, path, cred);
export async function readlink(path: PathLike, options?: BaseEncodingOptions | BufferEncoding): Promise<string>;
export async function readlink(path: PathLike, options: BufferEncodingOption): Promise<Uint8Array>;
export async function readlink(path: PathLike, options?: BaseEncodingOptions | string): Promise<string | Uint8Array>;
export async function readlink(path: PathLike, options?: BufferEncodingOption | BaseEncodingOptions | string): Promise<string | Uint8Array> {
const value: string = await doOp('readlink', false, path, cred);
return encode(value, typeof options == 'object' ? options.encoding : options);
}

// PROPERTY OPERATIONS
Expand All @@ -481,7 +484,7 @@ export async function readlink(path: string): Promise<string> {
* @param uid
* @param gid
*/
export async function chown(path: string, uid: number, gid: number): Promise<void> {
export async function chown(path: PathLike, uid: number, gid: number): Promise<void> {
return doOp('chown', true, path, uid, gid, cred);
}

Expand All @@ -491,7 +494,7 @@ export async function chown(path: string, uid: number, gid: number): Promise<voi
* @param uid
* @param gid
*/
export async function lchown(path: string, uid: number, gid: number): Promise<void> {
export async function lchown(path: PathLike, uid: number, gid: number): Promise<void> {
return doOp('chown', false, path, uid, gid, cred);
}

Expand All @@ -500,7 +503,7 @@ export async function lchown(path: string, uid: number, gid: number): Promise<vo
* @param path
* @param mode
*/
export async function chmod(path: string, mode: string | number): Promise<void> {
export async function chmod(path: PathLike, mode: string | number): Promise<void> {
const numMode = normalizeMode(mode, -1);
if (numMode < 0) {
throw new ApiError(ErrorCode.EINVAL, `Invalid mode.`);
Expand All @@ -513,7 +516,7 @@ export async function chmod(path: string, mode: string | number): Promise<void>
* @param path
* @param mode
*/
export async function lchmod(path: string, mode: number | string): Promise<void> {
export async function lchmod(path: PathLike, mode: number | string): Promise<void> {
const numMode = normalizeMode(mode, -1);
if (numMode < 1) {
throw new ApiError(ErrorCode.EINVAL, `Invalid mode.`);
Expand All @@ -527,7 +530,7 @@ export async function lchmod(path: string, mode: number | string): Promise<void>
* @param atime
* @param mtime
*/
export async function utimes(path: string, atime: number | Date, mtime: number | Date): Promise<void> {
export async function utimes(path: PathLike, atime: number | Date, mtime: number | Date): Promise<void> {
return doOp('utimes', true, path, normalizeTime(atime), normalizeTime(mtime), cred);
}

Expand All @@ -537,19 +540,19 @@ export async function utimes(path: string, atime: number | Date, mtime: number |
* @param atime
* @param mtime
*/
export async function lutimes(path: string, atime: number | Date, mtime: number | Date): Promise<void> {
export async function lutimes(path: PathLike, atime: number | Date, mtime: number | Date): Promise<void> {
return doOp('utimes', false, path, normalizeTime(atime), normalizeTime(mtime), cred);
}

/**
* `realpath`.
* @param path
* @param cache An object literal of mapped paths that can be used to
* force a specific path resolution or avoid additional `fs.stat` calls for
* known real paths.
* @return [String]
* @param options
* @return resolved path
*
* Note: This *Can not* use doOp since doOp depends on it
*/
export async function realpath(path: string, cache: { [path: string]: string } = {}): Promise<string> {
export async function realpath(path: PathLike, options?: BaseEncodingOptions): Promise<string> {
path = normalizePath(path);
const { fs, path: resolvedPath, mountPoint } = resolveFS(path);
try {
Expand All @@ -564,19 +567,19 @@ export async function realpath(path: string, cache: { [path: string]: string } =
}
}

export async function watchFile(filename: string, listener: (curr: Stats, prev: Stats) => void): Promise<void>;
export async function watchFile(filename: string, options: { persistent?: boolean; interval?: number }, listener: (curr: Stats, prev: Stats) => void): Promise<void>;
export async function watchFile(filename: string, arg2: any, listener: (curr: Stats, prev: Stats) => void = nop): Promise<void> {
export async function watchFile(filename: PathLike, listener: (curr: Stats, prev: Stats) => void): Promise<void>;
export async function watchFile(filename: PathLike, options: { persistent?: boolean; interval?: number }, listener: (curr: Stats, prev: Stats) => void): Promise<void>;
export async function watchFile(filename: PathLike, arg2: any, listener: (curr: Stats, prev: Stats) => void = nop): Promise<void> {
throw new ApiError(ErrorCode.ENOTSUP);
}

export async function unwatchFile(filename: string, listener: (curr: Stats, prev: Stats) => void = nop): Promise<void> {
export async function unwatchFile(filename: PathLike, listener: (curr: Stats, prev: Stats) => void = nop): Promise<void> {
throw new ApiError(ErrorCode.ENOTSUP);
}

export async function watch(filename: string, listener?: (event: string, filename: string) => any): Promise<FSWatcher>;
export async function watch(filename: string, options: { persistent?: boolean }, listener?: (event: string, filename: string) => any): Promise<FSWatcher>;
export async function watch(filename: string, arg2: any, listener: (event: string, filename: string) => any = nop): Promise<FSWatcher> {
export async function watch(filename: PathLike, listener?: (event: string, filename: PathLike) => any): Promise<FSWatcher>;
export async function watch(filename: PathLike, options: { persistent?: boolean }, listener?: (event: string, filename: string) => any): Promise<FSWatcher>;
export async function watch(filename: PathLike, arg2: any, listener: (event: string, filename: string) => any = nop): Promise<FSWatcher> {
throw new ApiError(ErrorCode.ENOTSUP);
}

Expand All @@ -585,12 +588,12 @@ export async function watch(filename: string, arg2: any, listener: (event: strin
* @param path
* @param mode
*/
export async function access(path: string, mode: number = 0o600): Promise<void> {
export async function access(path: PathLike, mode: number = 0o600): Promise<void> {
return doOp('access', true, path, mode, cred);
}

export async function createReadStream(
path: string,
path: PathLike,
options?: {
flags?: string;
encoding?: string;
Expand All @@ -603,7 +606,7 @@ export async function createReadStream(
}

export async function createWriteStream(
path: string,
path: PathLike,
options?: {
flags?: string;
encoding?: string;
Expand All @@ -614,14 +617,14 @@ export async function createWriteStream(
throw new ApiError(ErrorCode.ENOTSUP);
}

export async function rm(path: string) {
export async function rm(path: PathLike) {
throw new ApiError(ErrorCode.ENOTSUP);
}

export async function mkdtemp(path: string) {
export async function mkdtemp(path: PathLike) {
throw new ApiError(ErrorCode.ENOTSUP);
}

export async function copyFile(path: string) {
export async function copyFile(path: PathLike) {
throw new ApiError(ErrorCode.ENOTSUP);
}
21 changes: 21 additions & 0 deletions src/emulation/shared.ts
Original file line number Diff line number Diff line change
Expand Up @@ -220,3 +220,24 @@ export function initialize(mountMapping: MountMapping): void {
mount(point, fs);
}
}

/**
* Types supports as path parameters.
*
* In the future, maybe support URL?
*/
export type PathLike = string;

/**
* @internal
*
* Converts any Buffer in T to Uint8Array
*/
export type BufferToUint8Array<T> = T extends Buffer
? Uint8Array
: T extends (...args) => unknown
? (...args: Parameters<T>) => BufferToUint8Array<ReturnType<T>>
: T extends object
? { [K in keyof T]: BufferToUint8Array<T[K]> }
: T;

Loading

0 comments on commit 055e9c7

Please sign in to comment.