wasm-rquickjs 0.2.2

Tool for wrapping JavaScript modules as WebAssembly components using the QuickJS engine
Documentation
import { constants as fsConstants } from "node:fs";
import {
    ERR_INVALID_ARG_TYPE,
    ERR_INVALID_ARG_VALUE,
    ERR_OUT_OF_RANGE,
} from "__wasm_rquickjs_builtin/internal/errors";

const {
    O_APPEND = 0,
    O_CREAT = 0,
    O_EXCL = 0,
    O_RDONLY = 0,
    O_RDWR = 0,
    O_SYNC = 0,
    O_TRUNC = 0,
    O_WRONLY = 0,
} = fsConstants;

export function stringToFlags(flags, name = "flags") {
    if (typeof flags === "number") {
        return flags;
    }

    if (flags == null) {
        return O_RDONLY;
    }

    switch (flags) {
        case "r": return O_RDONLY;
        case "rs":
        case "sr": return O_RDONLY | O_SYNC;
        case "r+": return O_RDWR;
        case "rs+":
        case "sr+": return O_RDWR | O_SYNC;
        case "w": return O_TRUNC | O_CREAT | O_WRONLY;
        case "wx":
        case "xw": return O_TRUNC | O_CREAT | O_WRONLY | O_EXCL;
        case "w+": return O_TRUNC | O_CREAT | O_RDWR;
        case "wx+":
        case "xw+": return O_TRUNC | O_CREAT | O_RDWR | O_EXCL;
        case "a": return O_APPEND | O_CREAT | O_WRONLY;
        case "ax":
        case "xa": return O_APPEND | O_CREAT | O_WRONLY | O_EXCL;
        case "as":
        case "sa": return O_APPEND | O_CREAT | O_WRONLY | O_SYNC;
        case "a+": return O_APPEND | O_CREAT | O_RDWR;
        case "ax+":
        case "xa+": return O_APPEND | O_CREAT | O_RDWR | O_EXCL;
        case "as+":
        case "sa+": return O_APPEND | O_CREAT | O_RDWR | O_SYNC;
        default:
            throw new ERR_INVALID_ARG_VALUE(name, flags);
    }
}

const defaultRmOptions = {
    recursive: false,
    force: false,
    retryDelay: 100,
    maxRetries: 0,
};

const defaultRmdirOptions = {
    retryDelay: 100,
    maxRetries: 0,
    recursive: false,
};

function validateObject(value, name) {
    if (value === null || typeof value !== 'object' || Array.isArray(value)) {
        throw new ERR_INVALID_ARG_TYPE(name, 'object', value);
    }
}

function validateBoolean(value, name) {
    if (typeof value !== 'boolean') {
        throw new ERR_INVALID_ARG_TYPE(name, 'boolean', value);
    }
}

function validateInt32(value, name, min = -2147483648, max = 2147483647) {
    if (typeof value !== 'number') {
        throw new ERR_INVALID_ARG_TYPE(name, 'number', value);
    }
    if (!Number.isInteger(value)) {
        throw new ERR_OUT_OF_RANGE(name, 'an integer', value);
    }
    if (value < min || value > max) {
        throw new ERR_OUT_OF_RANGE(name, `>= ${min} && <= ${max}`, value);
    }
}

export function validateRmdirOptions(options, defaults = defaultRmdirOptions) {
    if (options === undefined) {
        return defaults;
    }
    validateObject(options, 'options');
    options = { ...defaults, ...options };
    validateBoolean(options.recursive, 'options.recursive');
    validateInt32(options.retryDelay, 'options.retryDelay', 0);
    validateInt32(options.maxRetries, 'options.maxRetries', 0);
    return options;
}

export function validateRmOptionsSync(path, options, expectDir) {
    const fs = require('node:fs');
    options = validateRmdirOptions(options, defaultRmOptions);
    validateBoolean(options.force, 'options.force');

    if (!options.force || expectDir || !options.recursive) {
        const isDirectory = fs
            .lstatSync(path, { throwIfNoEntry: !options.force })?.isDirectory();

        if (expectDir && !isDirectory) {
            return false;
        }

        if (isDirectory && !options.recursive) {
            const err = new Error(`EISDIR: is a directory, rm '${path}'`);
            err.code = 'EISDIR';
            err.syscall = 'rm';
            err.path = path;
            throw err;
        }
    }

    return options;
}

export function validateOffsetLengthRead(offset, length, bufferLength) {
    if (offset < 0) {
        throw new ERR_OUT_OF_RANGE('offset', '>= 0', offset);
    }
    if (length < 0) {
        throw new ERR_OUT_OF_RANGE('length', '>= 0', length);
    }
    if (offset + length > bufferLength) {
        throw new ERR_OUT_OF_RANGE('length', `<= ${bufferLength - offset}`, length);
    }
}

export function validateOffsetLengthWrite(offset, length, byteLength) {
    if (offset > byteLength) {
        throw new ERR_OUT_OF_RANGE('offset', `<= ${byteLength}`, offset);
    }
    if (length > byteLength - offset) {
        throw new ERR_OUT_OF_RANGE('length', `<= ${byteLength - offset}`, length);
    }
}

const UV_DIRENT_UNKNOWN = 0;

function pathToString(p) {
    if (typeof p === 'string') return p;
    if (Buffer.isBuffer(p)) return p.toString();
    return String(p);
}

class DirentFromStats {
    constructor(name, stats, parentPath) {
        this.name = name;
        this.parentPath = parentPath;
        this.path = parentPath;
        this._stats = stats;
    }

    isFile() { return this._stats.isFile(); }
    isDirectory() { return this._stats.isDirectory(); }
    isSymbolicLink() { return this._stats.isSymbolicLink(); }
    isBlockDevice() { return this._stats.isBlockDevice(); }
    isCharacterDevice() { return this._stats.isCharacterDevice(); }
    isFIFO() { return this._stats.isFIFO(); }
    isSocket() { return this._stats.isSocket(); }
}

function validatePath(path) {
    if (typeof path !== 'string' && !Buffer.isBuffer(path)) {
        throw new ERR_INVALID_ARG_TYPE('path', ['string', 'Buffer'], path);
    }
}

export function getDirents(path, { 0: names, 1: types }, callback) {
    if (typeof callback === 'function') {
        try {
            validatePath(path);
        } catch (err) {
            callback(err);
            return;
        }
        const fs = require('node:fs');
        const len = names.length;
        let toFinish = 0;
        let called = false;
        const done = (err, result) => {
            if (called) return;
            if (err) { called = true; callback(err); return; }
            if (result !== undefined && --toFinish === 0) {
                called = true;
                callback(null, names);
            }
        };

        for (let i = 0; i < len; i++) {
            const type = types[i];
            if (type === UV_DIRENT_UNKNOWN) {
                const name = names[i];
                const idx = i;
                toFinish++;
                let filepath;
                try {
                    filepath = pathToString(path) + '/' + pathToString(name);
                } catch (err) {
                    callback(err);
                    return;
                }
                fs.lstat(filepath, (err, stats) => {
                    if (err) { done(err); return; }
                    names[idx] = new DirentFromStats(name, stats, path);
                    done(null, true);
                });
            } else {
                const { Dirent } = require('node:fs');
                names[i] = new Dirent(names[i], type, path);
            }
        }
        if (toFinish === 0) {
            callback(null, names);
        }
    } else {
        validatePath(path);
        const len = names.length;
        for (let i = 0; i < len; i++) {
            names[i] = getDirent(path, names[i], types[i]);
        }
        return names;
    }
}

export function getDirent(path, name, type, callback) {
    if (typeof callback === 'function') {
        try {
            validatePath(path);
        } catch (err) {
            callback(err);
            return;
        }
        if (type === UV_DIRENT_UNKNOWN) {
            let filepath;
            try {
                filepath = pathToString(path) + '/' + pathToString(name);
            } catch (err) {
                callback(err);
                return;
            }
            const fs = require('node:fs');
            fs.lstat(filepath, (err, stats) => {
                if (err) { callback(err); return; }
                callback(null, new DirentFromStats(name, stats, path));
            });
        } else {
            const { Dirent } = require('node:fs');
            callback(null, new Dirent(name, type, path));
        }
    } else {
        validatePath(path);
        if (type === UV_DIRENT_UNKNOWN) {
            const filepath = pathToString(path) + '/' + pathToString(name);
            const fs = require('node:fs');
            const stats = fs.lstatSync(filepath);
            return new DirentFromStats(name, stats, path);
        } else {
            const { Dirent } = require('node:fs');
            return new Dirent(name, type, path);
        }
    }
}

export default {
    stringToFlags,
    getDirents,
    getDirent,
    validateRmdirOptions,
    validateRmOptionsSync,
    validateOffsetLengthRead,
    validateOffsetLengthWrite,
};